External Publication
Visit Post

An attempt at Vulkan

jMonkeyEngine Hub March 11, 2026
Source

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

Loading comments...