External Publication
Visit Post

jME 3.10.0-beta1

jMonkeyEngine Hub June 2, 2026
Source
package com.jme3.renderer.vulkan.context;

import com.jme3.renderer.vulkan.VKRenderer;
import com.jme3.renderer.vulkan.VulkanRuntime;
import com.jme3.system.AppSettings;
import com.jme3.system.lwjgl.LwjglWindow;
import org.lwjgl.system.MemoryStack;

import java.nio.IntBuffer;

import static org.lwjgl.sdl.SDLError.*;
import static org.lwjgl.sdl.SDLEvents.*;
import static org.lwjgl.sdl.SDLInit.*;
import static org.lwjgl.sdl.SDLStdinc.*;
import static org.lwjgl.sdl.SDLVideo.*;
import org.lwjgl.sdl.SDL_Event;

/**
 * 彻底切断父类的 GLFW 调用,使用纯血 SDL3 驱动窗口生命周期和事件拉取。
 */
public class LwjglVulkanContext extends LwjglWindow {

    private VulkanRuntime runtime;
    private VKRenderer renderer;
    private long sdlWindow; // 【修复1】:单独保存 SDL 句柄,不依赖父类

    public LwjglVulkanContext() {
        super(Type.Display);
    }

    @Override
    protected void createContext(AppSettings settings) {
        if (SDL_setenv_unsafe("__GL_THREADED_OPTIMIZATIONS", "0", 1) != 0) {
            throw new IllegalStateException("Unable to disable NVIDIA threaded optimizations: " + SDL_GetError());
        }

        if (!SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) {
            throw new IllegalStateException("Unable to initialize SDL video subsystem: " + SDL_GetError());
        }

        long windowFlags = SDL_WINDOW_VULKAN | SDL_WINDOW_HIDDEN;
        if (settings.isResizable()) {
            windowFlags |= SDL_WINDOW_RESIZABLE;
        }

        sdlWindow = SDL_CreateWindow(settings.getTitle(), settings.getWidth(), settings.getHeight(), windowFlags);
        if (sdlWindow == 0L) {
            throw new RuntimeException("Failed to create SDL Vulkan window: " + SDL_GetError());
        }

        // 依然注入给父类,防止 JME3 内部某些日志或验证报 NullPointer,但后续逻辑全部由我们自己接管
        try {
            java.lang.reflect.Field fWin = LwjglWindow.class.getDeclaredField("window");
            fWin.setAccessible(true);
            fWin.set(this, sdlWindow);
        } catch (Exception ignored) {
        }

        runtime = new VulkanRuntime(settings);
        renderer = new VKRenderer(runtime);

        try {
            runtime.init(sdlWindow);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        renderer.initialize();

        setWindowIcon(settings);
        showWindow(); // 会安全调用重写后的 SDL_ShowWindow
        updateVulkanSizes(); // 初始化真实的物理像素尺寸
    }

    // 【修复2】:重写 showWindow,强行接管调用 SDL API 抛弃 GLFW
    @Override
    protected void showWindow() {
        if (sdlWindow != 0L) {
            SDL_ShowWindow(sdlWindow);
        }
    }

    protected void updateVulkanSizes() {
        if (sdlWindow == 0L) {
            return;
        }

        try (MemoryStack stack = MemoryStack.stackPush()) {
            IntBuffer w = stack.mallocInt(1);
            IntBuffer h = stack.mallocInt(1);

            SDL_GetWindowSizeInPixels(sdlWindow, w, h);
            int width = w.get(0);
            int height = h.get(0);

            if (width > 0 && height > 0) {
                // 如果尺寸发生了变化,或者这是第一次初始化
                if (settings.getWidth() != width || settings.getHeight() != height) {
                    settings.setWidth(width);
                    settings.setHeight(height);
                    if (runtime != null && isCreated()) {
                        runtime.requestResize(width, height);
                    }
                    // 【修复2】:标记摄像机分辨率需要更新
                    needReshape = true;
                }
            }
        }
    }

    // 【修复4】:重写销毁方法,调用 SDL 销毁,不要调 super 避免导致 glfwDestroyWindow 崩溃
    @Override
    protected void destroyContext() {
        if (runtime != null) {
            runtime.cleanup();
        }
        if (sdlWindow != 0L) {
            SDL_DestroyWindow(sdlWindow);
            sdlWindow = 0L;
        }
        SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
    }

    @Override
    protected void runLoop() {
        if (!created.get()) {
            throw new IllegalStateException();
        }

        if (needReshape && listener != null) {
            listener.reshape(settings.getWidth(), settings.getHeight());
            needReshape = false;
        }

        if (listener != null) {
            listener.update();
        }

        if (runtime != null) {
            runtime.renderFrame(timer.getTimePerFrame(), renderer, null);
        }

        if (renderer != null) {
            renderer.postFrame();
        }

        int targetFramerate = getSettings().getFrameRate();
        if (targetFramerate <= 0 && !autoFlush) {
            targetFramerate = 20;
        }
        if (targetFramerate > 0) {
            com.jme3.system.lwjgl.Sync.sync(targetFramerate);
        }

        pollSdlEvents();
    }

    private void pollSdlEvents() {
        try (MemoryStack stack = MemoryStack.stackPush()) {
            SDL_Event event = SDL_Event.malloc(stack);


            while (SDL_PollEvent(event)) {
                int type = event.type();
                if (type == SDL_EVENT_QUIT) {
                    listener.requestClose(false);
                } else if (type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED || type == SDL_EVENT_WINDOW_RESIZED) {
                    updateVulkanSizes();
                }
                // TODO: 可以在这里将 SDL 键盘/鼠标事件翻译并转交给 JME3 InputSystem
            }
        }
    }


    protected void initContext() {

    }

    // =========================================================
    // 【修复2】:阻断 JME3 去 new 默认的 GLRenderer
    // =========================================================
    @Override
    protected void initContextFirstTime() {

    }

    // =========================================================
    // 【修复3】:确保 JME3 引擎核心获取到的是 Vulkan 渲染器
    // =========================================================
    @Override
    public com.jme3.renderer.Renderer getRenderer() {

        return renderer;
    }

    @Override
    public com.jme3.input.MouseInput getMouseInput() {
        // 返回虚拟鼠标输入,防止引擎内部的 NullPointerException
        return new com.jme3.input.dummy.DummyMouseInput();
    }

    @Override
    public com.jme3.input.KeyInput getKeyInput() {
        // 返回虚拟键盘输入
        return new com.jme3.input.dummy.DummyKeyInput();
    }

    @Override
    public com.jme3.input.JoyInput getJoyInput() {
        // 不需要手柄输入,直接返回 null,JME3 能安全处理
        return null;
    }

    @Override
    public com.jme3.input.TouchInput getTouchInput() {
        // 不需要触摸输入,直接返回 null
        return null;
    }
    // 标记是否需要更新 JME3 的摄像机分辨率
    private boolean needReshape = true;

    @Override
    public boolean isRenderable() {
        // 只要我们的 SDL 窗口句柄不为 0,就坚决要求 JME3 进行渲染!
        return sdlWindow != 0L;
    }
}

Thank you for your guidance. I have re-constructed the LwjglVulkanContext.

Thanks! That workaround works for now. In the long run, it would be really helpful if we could have an official configuration option​ for plugging in custom renderers. This would give us a clear and stable development convention, instead of having to replace core modules every time.

Thanks again for all you hard work on this!

Discussion in the ATmosphere

Loading comments...