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