{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreigoh3lamiykov2dmoutyscnsg3lxtduvntx3m4z6vjjbrfredsndi",
"uri": "at://did:plc:dxjzgxe7cvirxkwfjr2tjspt/app.bsky.feed.post/3mgttiagea5g2"
},
"path": "/t/an-attempt-at-vulkan/49433#post_1",
"publishedAt": "2026-03-11T17:40:40.000Z",
"site": "https://hub.jmonkeyengine.org",
"tags": [
"@Override"
],
"textContent": "Let me take the first step while minimizing modifications to jme3-lwjgl3 as much as possible.\n\n\n\n\n package com.jme3.renderer.vulkan;\n\n import com.jme3.renderer.Renderer;\n import com.jme3.renderer.vulkan.VKRenderer;\n import com.jme3.renderer.vulkan.VulkanRuntime;\n import com.jme3.system.*;\n\n import java.util.concurrent.atomic.AtomicBoolean;\n\n /**\n * LwjglVulkanContext:把 Vulkan 渲染循环嵌入到 jME3 的 JmeContext 体系里。\n */\n public class LwjglVulkanContext implements JmeContext, Runnable {\n\n private final AppSettings settings = new AppSettings(true);\n\n private final AtomicBoolean created = new AtomicBoolean(false);\n private final AtomicBoolean destroyed = new AtomicBoolean(false);\n\n private final Object createdLock = new Object();\n private final Object destroyedLock = new Object();\n\n private SystemListener engine;\n\n private VulkanRuntime runtime;\n private VKRenderer renderer;\n private Timer timer;\n\n private Thread renderThread;\n\n @Override\n public void run() {\n destroyed.set(false);\n\n runtime = new VulkanRuntime(settings);\n renderer = new VKRenderer(runtime);\n timer = getTimer();\n\n try {\n runtime.init();\n renderer.initialize();\n\n if (engine != null) {\n engine.initialize();\n }\n\n synchronized (createdLock) {\n created.set(true);\n createdLock.notifyAll();\n }\n\n while (!runtime.shouldClose()) {\n runtime.pollEvents();\n\n timer.update();\n float tpf = timer.getTimePerFrame();\n\n // 由 renderer 触发 runtime.renderFrame(..., recorder, ...)\n runtime.renderFrame(tpf,renderer,null);\n\n if (engine != null) {\n engine.update();\n }\n }\n\n } catch (Throwable t) {\n t.printStackTrace();\n if (engine != null) {\n engine.handleError(\"Vulkan render thread crashed\", t);\n }\n } finally {\n try {\n if (engine != null) {\n engine.destroy();\n }\n } catch (Throwable ignored) {\n }\n\n try {\n if (runtime != null) {\n runtime.cleanup();\n }\n } catch (Throwable ignored) {\n }\n\n synchronized (destroyedLock) {\n destroyed.set(true);\n destroyedLock.notifyAll();\n }\n }\n }\n\n // -------- JmeContext overrides --------\n\n @Override\n public void create(boolean waitFor) {\n if (created.get()) {\n return;\n }\n\n renderThread = new Thread(this, \"VulkanRenderThread\");\n renderThread.start();\n\n if (waitFor) {\n synchronized (createdLock) {\n while (!created.get()) {\n try {\n createdLock.wait();\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n break;\n }\n }\n }\n }\n }\n\n @Override\n public void destroy(boolean waitFor) {\n if (runtime != null) {\n runtime.requestClose();\n }\n\n if (waitFor) {\n synchronized (destroyedLock) {\n while (!destroyed.get()) {\n try {\n destroyedLock.wait();\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n break;\n }\n }\n }\n }\n }\n\n @Override\n public boolean isCreated() {\n return created.get();\n }\n\n @Override\n public boolean isRenderable() {\n return created.get();\n }\n\n @Override\n public Type getType() {\n return Type.Display;\n }\n\n @Override\n public AppSettings getSettings() {\n return settings;\n }\n\n @Override\n public void setSettings(AppSettings s) {\n settings.copyFrom(s);\n }\n\n @Override\n public void setSystemListener(SystemListener l) {\n engine = l;\n }\n\n @Override\n public SystemListener getSystemListener() {\n return engine;\n }\n\n @Override\n public Timer getTimer() {\n return new NanoTimer();\n }\n\n @Override\n public Renderer getRenderer() {\n return renderer;\n }\n\n // 以下输入/OpenCL 未接入,先返回 null(最小版本)\n @Override public com.jme3.opencl.Context getOpenCLContext() { return null; }\n @Override public com.jme3.input.MouseInput getMouseInput() { return null; }\n @Override public com.jme3.input.KeyInput getKeyInput() { return null; }\n @Override public com.jme3.input.JoyInput getJoyInput() { return null; }\n @Override public com.jme3.input.TouchInput getTouchInput() { return null; }\n\n @Override\n public void setTitle(String t) {\n if (runtime != null) {\n runtime.setTitle(t);\n }\n }\n\n @Override public int getFramebufferHeight() { return settings.getHeight(); }\n @Override public int getFramebufferWidth() { return settings.getWidth(); }\n\n @Override public int getWindowXPosition() { return 0; }\n @Override public int getWindowYPosition() { return 0; }\n\n @Override public void setAutoFlushFrames(boolean enabled) { }\n @Override public void restart() { }\n }\n\n\n\nReferencing the lwjgl3-vulkan demo, currently I can simply create a JmeContext to launch a window.\n\n\n package com.jme3.lwjgl.test;\n\n import com.jme3.system.AppSettings;\n import com.jme3.system.SystemListener;\n import com.jme3.renderer.vulkan.LwjglVulkanContext;\n\n\n /**\n * 启动 Vulkan 渲染器的测试类\n */\n public class VulkanMainTest implements SystemListener {\n\n private LwjglVulkanContext context;\n\n public static void main(String[] args) {\n VulkanMainTest app = new VulkanMainTest();\n app.start();\n }\n\n public void start() {\n System.out.println(\"os.name=\" + System.getProperty(\"os.name\"));\n // 1. 配置应用参数\n AppSettings settings = new AppSettings(true);\n settings.setTitle(\"LWJGL Vulkan Demo - Two Triangles\");\n settings.setWidth(800);\n settings.setHeight(600);\n settings.setSamples(1); // 如果需要多重采样\n\n // 2. 创建上下文\n context = new LwjglVulkanContext();\n context.setSettings(settings);\n context.setSystemListener(this);\n\n // 3. 启动线程 (LwjglVulkan 会在新线程中运行 run())\n System.out.println(\"Starting Vulkan Context...\");\n context.create(false);\n }\n\n @Override\n public void initialize() {\n // 当 Vulkan 实例、设备、Surface 和渲染器初始化完成后调用\n System.out.println(\"Vulkan System Initialized.\");\n }\n\n @Override\n public void update() {\n // 每一帧渲染前执行的逻辑更新\n // 对应 TwoRotatingTrianglesDemo 中的 updateUbo 逻辑可以在此触发\n // 或者在渲染器内部处理时间增量\n }\n\n @Override\n public void reshape(int width, int height) {\n // 窗口大小改变时的逻辑处理\n System.out.println(\"Window resized to: \" + width + \"x\" + height);\n }\n\n @Override\n public void destroy() {\n // 清理逻辑\n System.out.println(\"Vulkan System Destroying...\");\n }\n\n public void pause() {\n }\n\n public void resume() {\n }\n\n @Override\n public void requestClose(boolean esc) {\n\n }\n\n @Override\n public void gainFocus() {\n throw new UnsupportedOperationException(\"Not supported yet.\"); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody\n }\n\n @Override\n public void loseFocus() {\n throw new UnsupportedOperationException(\"Not supported yet.\"); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody\n }\n\n @Override\n public void handleError(String errorMsg, Throwable t) {\n System.err.println(errorMsg);\n if (t != null) {\n t.printStackTrace();\n }\n }\n }\n\n\n\nLet’s get started!\n\n\n\n\nGood news: I have a window that correctly draws a triangle.\n\nI plan to implement basically the same functionality in Vulkan as in OpenGL, with as minimal modifications to the code as possible.\n\nVulkan is very low-level. You even need to control the synchronization between the CPU and GPU yourself.\n\nAll the backend functions need to be implemented by oneself.\n\nI will record all the subsequent attempts in this post.",
"title": "An attempt at Vulkan"
}