{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreicbmt52b6bimxtsdiriiytqihc2vyfazdasreb5bgrcsf573c4nqu",
"uri": "at://did:plc:dxjzgxe7cvirxkwfjr2tjspt/app.bsky.feed.post/3mncbkwvu3dk2"
},
"path": "/t/jme-3-10-0-beta1/49603#post_19",
"publishedAt": "2026-06-02T08:29:00.000Z",
"site": "https://hub.jmonkeyengine.org",
"tags": [
"@RiccardoBlb",
"@Override"
],
"textContent": "\n package com.jme3.renderer.vulkan.context;\n\n import com.jme3.input.JoyInput;\n import com.jme3.input.KeyInput;\n import com.jme3.input.MouseInput;\n import com.jme3.input.TouchInput;\n import com.jme3.input.lwjgl.SdlJoystickInput;\n import com.jme3.input.lwjgl.SdlKeyInput;\n import com.jme3.input.lwjgl.SdlMouseInput;\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.AppSettings;\n import com.jme3.system.Displays;\n import com.jme3.system.JmeContext;\n import com.jme3.system.NanoTimer;\n import com.jme3.system.SystemListener;\n import com.jme3.system.Timer;\n import com.jme3.system.lwjgl.Sync;\n\n import org.lwjgl.sdl.SDL_DisplayMode;\n import org.lwjgl.sdl.SDL_Event;\n import org.lwjgl.system.MemoryStack;\n\n import java.io.IOException;\n import java.util.concurrent.atomic.AtomicBoolean;\n import java.util.logging.Level;\n import java.util.logging.Logger;\n\n import static org.lwjgl.sdl.SDL.*;\n import static org.lwjgl.sdl.SDLError.SDL_GetError;\n import static org.lwjgl.sdl.SDLEvents.*;\n import static org.lwjgl.sdl.SDLInit.SDL_INIT_EVENTS;\n import static org.lwjgl.sdl.SDLInit.SDL_INIT_VIDEO;\n import static org.lwjgl.sdl.SDLInit.SDL_InitSubSystem;\n import static org.lwjgl.sdl.SDLInit.SDL_QuitSubSystem;\n import static org.lwjgl.sdl.SDLVideo.SDL_GetCurrentDisplayMode;\n import static org.lwjgl.sdl.SDLVideo.SDL_GetPrimaryDisplay;\n import static org.lwjgl.sdl.SDLVideo.SDL_GetWindowID;\n import static org.lwjgl.sdl.SDLVideo.SDL_GetWindowPosition;\n import static org.lwjgl.sdl.SDLVideo.SDL_GetWindowSizeInPixels;\n import static org.lwjgl.sdl.SDLVideo.SDL_SetWindowTitle;\n import static org.lwjgl.sdl.SDLVideo.SDL_ShowWindow;\n import static org.lwjgl.system.MemoryUtil.NULL;\n\n /**\n * SDL3 + Vulkan 上下文实现 彻底抛弃 LwjglWindow,直接实现 JmeContext 接口。\n */\n public class LwjglVulkanContext implements JmeContext, Runnable {\n\n private static final Logger LOGGER = Logger.getLogger(LwjglVulkanContext.class.getName());\n\n private final AppSettings settings = new AppSettings(true);\n private final JmeContext.Type type;\n\n // --- 生命周期状态 ---\n private final AtomicBoolean created = new AtomicBoolean(false);\n private final AtomicBoolean renderable = new AtomicBoolean(false);\n private final AtomicBoolean needClose = new AtomicBoolean(false);\n private final Object createdLock = new Object();\n\n // --- JME3 引擎组件 ---\n private SystemListener listener;\n private Timer timer;\n private Thread renderThread;\n\n // --- Vulkan 核心组件 ---\n private VulkanRuntime runtime;\n private VKRenderer renderer;\n private volatile long windowHandle = NULL;\n private int windowId;\n\n // --- 输入驱动 ---\n private SdlKeyInput keyInput;\n private SdlMouseInput mouseInput;\n private SdlJoystickInput joyInput;\n\n private boolean wasActive = false;\n private boolean autoFlush = true;\n\n public LwjglVulkanContext() {\n this.type = Type.Display;\n }\n\n public LwjglVulkanContext(JmeContext.Type type) {\n this.type = type;\n }\n\n @Override\n public void setSettings(AppSettings settings) {\n this.settings.copyFrom(settings);\n }\n\n @Override\n public AppSettings getSettings() {\n return settings;\n }\n\n @Override\n public void setSystemListener(SystemListener listener) {\n this.listener = listener;\n }\n\n @Override\n public SystemListener getSystemListener() {\n return listener;\n }\n\n // =======================================================\n // 线程启动与生命周期控制\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 needClose.set(true);\n if (renderThread == Thread.currentThread()) {\n return;\n }\n if (waitFor) {\n try {\n renderThread.join();\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n }\n }\n }\n\n @Override\n public boolean isCreated() {\n return created.get();\n }\n\n @Override\n public boolean isRenderable() {\n return renderable.get();\n }\n\n @Override\n public void run() {\n if (listener == null) {\n throw new IllegalStateException(\"SystemListener is not set!\");\n }\n\n try {\n // 1. 创建 SDL3 窗口与 Vulkan 底层\n createContext();\n\n // 2. 初始化输入驱动\n keyInput = new SdlKeyInput(this);\n mouseInput = new SdlMouseInput(this);\n joyInput = new SdlJoystickInput(settings);\n\n timer = new NanoTimer();\n renderable.set(true);\n\n // 3. 唤醒等待线程\n synchronized (createdLock) {\n created.set(true);\n createdLock.notifyAll();\n }\n\n // 4. 启动 JME3 引擎(会回调 input 的 initialize)\n listener.initialize();\n\n } catch (Exception ex) {\n LOGGER.log(Level.SEVERE, \"Failed to initialize Vulkan Context\", ex);\n listener.handleError(\"Failed to create Vulkan display\", ex);\n synchronized (createdLock) {\n createdLock.notifyAll();\n }\n return; // 启动失败直接退出\n }\n\n // 5. 渲染主循环\n while (!needClose.get() && !runtime.shouldClose()) {\n runLoop();\n }\n\n // 6. 清理退出\n destroyContext();\n }\n\n private void createContext() throws IOException {\n // 先行初始化 SDL Video 子系统,以便能获取到真实的屏幕分辨率\n if (!SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) {\n throw new IllegalStateException(\"Unable to initialize SDL video subsystem: \" + SDL_GetError());\n }\n\n int displayID = SDL_GetPrimaryDisplay();\n SDL_DisplayMode videoMode = SDL_GetCurrentDisplayMode(displayID);\n\n int requestWidth = settings.getWindowWidth() <= 0 ? (videoMode != null ? videoMode.w() : 1280) : settings.getWindowWidth();\n int requestHeight = settings.getWindowHeight() <= 0 ? (videoMode != null ? videoMode.h() : 720) : settings.getWindowHeight();\n\n // 将修正后的长宽写回 settings,这样内部的 SdlWindow 才能按照正确的尺寸创建\n settings.setWidth(requestWidth);\n settings.setHeight(requestHeight);\n\n // 初始化 Runtime,内部的 VulkanRuntimeLifecycle 会通过 SdlWindow.create() 创建窗口\n runtime = new VulkanRuntime(settings);\n runtime.init(); // <--- 【核心修复点】不再传递 windowHandle,调用无参 init()\n\n renderer = new VKRenderer(runtime);\n renderer.initialize();\n\n // 提取出内部创建好的窗口句柄,供 JmeContext 管理\n windowHandle = runtime.getWindowHandle();\n windowId = SDL_GetWindowID(windowHandle);\n\n SDL_ShowWindow(windowHandle);\n updateSizes();\n }\n\n private void runLoop() {\n // 泵取 SDL 事件\n pollEvents();\n\n // 引擎逻辑更新\n listener.update();\n\n // Vulkan 渲染帧\n if (runtime != null && renderer != null) {\n timer.update();\n float tpf = timer.getTimePerFrame();\n\n int fbW = runtime.getFramebufferWidth();\n int fbH = runtime.getFramebufferHeight();\n if (fbW > 0 && fbH > 0) {\n runtime.renderFrame(tpf, renderer, null);\n renderer.postFrame();\n } else {\n try {\n Thread.sleep(16);\n } catch (InterruptedException ignored) {\n }\n }\n }\n\n int frameRateLimit = settings.getFrameRate();\n if (!autoFlush) {\n frameRateLimit = 20;\n }\n Sync.sync(frameRateLimit);\n }\n\n private void pollEvents() {\n try (MemoryStack stack = MemoryStack.stackPush()) {\n SDL_Event event = SDL_Event.malloc(stack);\n while (SDL_PollEvent(event)) {\n int evtType = event.type();\n\n if (evtType == SDL_EVENT_QUIT) {\n needClose.set(true);\n } else if (evtType == SDL_EVENT_WINDOW_RESIZED || evtType == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED) {\n if (event.window().windowID() == windowId) {\n updateSizes();\n }\n } else if (evtType == SDL_EVENT_WINDOW_FOCUS_GAINED) {\n if (!wasActive) {\n listener.gainFocus();\n timer.reset();\n wasActive = true;\n }\n } else if (evtType == SDL_EVENT_WINDOW_FOCUS_LOST) {\n if (wasActive) {\n listener.loseFocus();\n wasActive = false;\n }\n }\n\n // 委派给输入管理器\n if (keyInput != null) {\n keyInput.onSDLEvent(event);\n }\n if (mouseInput != null) {\n mouseInput.onSDLEvent(event);\n }\n if (joyInput != null) {\n joyInput.onSDLEvent(event);\n }\n }\n }\n }\n\n private void updateSizes() {\n if (windowHandle == NULL) {\n return;\n }\n try (MemoryStack stack = MemoryStack.stackPush()) {\n java.nio.IntBuffer w = stack.mallocInt(1);\n java.nio.IntBuffer h = stack.mallocInt(1);\n\n SDL_GetWindowSizeInPixels(windowHandle, w, h);\n int fbW = Math.max(w.get(0), 1);\n int fbH = Math.max(h.get(0), 1);\n\n if (fbW != settings.getWidth() || fbH != settings.getHeight()) {\n settings.setResolution(fbW, fbH);\n if (listener != null) {\n listener.reshape(fbW, fbH);\n }\n if (runtime != null) {\n runtime.requestResize(fbW, fbH);\n }\n }\n }\n }\n\n private void destroyContext() {\n renderable.set(false);\n try {\n if (listener != null) {\n listener.destroy();\n }\n if (renderer != null) {\n renderer.cleanup();\n }\n\n // runtime.cleanup() 内部会级联调用 SdlWindow.destroy(),释放一次 SDL 子系统引用\n if (runtime != null) {\n runtime.cleanup();\n }\n\n windowHandle = NULL;\n windowId = 0;\n\n // 匹配 createContext 顶部的第一次 SDL_InitSubSystem,完美平衡引用计数\n SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_EVENTS);\n\n } catch (Exception ex) {\n LOGGER.log(Level.SEVERE, \"Failed to destroy Vulkan context\", ex);\n }\n }\n\n // =======================================================\n // JmeContext 接口实现\n // =======================================================\n @Override\n public Type getType() {\n return type;\n }\n\n @Override\n public Timer getTimer() {\n return timer;\n }\n\n @Override\n public Renderer getRenderer() {\n return renderer;\n }\n\n @Override\n public MouseInput getMouseInput() {\n return mouseInput;\n }\n\n @Override\n public KeyInput getKeyInput() {\n return keyInput;\n }\n\n @Override\n public JoyInput getJoyInput() {\n return joyInput;\n }\n\n @Override\n public TouchInput getTouchInput() {\n return null;\n }\n\n @Override\n public void setAutoFlushFrames(boolean enabled) {\n this.autoFlush = enabled;\n }\n\n @Override\n public void restart() {\n LOGGER.warning(\"Restart not implemented for Vulkan context yet.\");\n }\n\n @Override\n public Displays getDisplays() {\n return new Displays();\n }\n\n @Override\n public int getPrimaryDisplay() {\n return 0;\n }\n\n @Override\n public void setTitle(String title) {\n if (windowHandle != NULL) {\n SDL_SetWindowTitle(windowHandle, title);\n }\n }\n\n @Override\n public int getFramebufferWidth() {\n return settings.getWidth();\n }\n\n @Override\n public int getFramebufferHeight() {\n return settings.getHeight();\n }\n\n @Override\n public int getWindowXPosition() {\n if (windowHandle == NULL) {\n return 0;\n }\n try (MemoryStack stack = MemoryStack.stackPush()) {\n java.nio.IntBuffer x = stack.mallocInt(1);\n SDL_GetWindowPosition(windowHandle, x, null);\n return x.get(0);\n }\n }\n\n @Override\n public int getWindowYPosition() {\n if (windowHandle == NULL) {\n return 0;\n }\n try (MemoryStack stack = MemoryStack.stackPush()) {\n java.nio.IntBuffer y = stack.mallocInt(1);\n SDL_GetWindowPosition(windowHandle, null, y);\n return y.get(0);\n }\n }\n\n public long getWindowHandle() {\n return windowHandle;\n }\n\n public void getMouseInputScale(com.jme3.math.Vector2f scale) {\n if (windowHandle == NULL) {\n scale.set(1f, 1f);\n return;\n }\n try (MemoryStack stack = MemoryStack.stackPush()) {\n java.nio.IntBuffer w = stack.mallocInt(1);\n java.nio.IntBuffer h = stack.mallocInt(1);\n\n org.lwjgl.sdl.SDLVideo.SDL_GetWindowSize(windowHandle, w, h);\n int winW = Math.max(1, w.get(0));\n int winH = Math.max(1, h.get(0));\n\n org.lwjgl.sdl.SDLVideo.SDL_GetWindowSizeInPixels(windowHandle, w, h);\n int pixW = Math.max(1, w.get(0));\n int pixH = Math.max(1, h.get(0));\n\n scale.set((float) pixW / winW, (float) pixH / winH);\n }\n }\n }\n\n\n\nhello @RiccardoBlb\nI would like to ask you about the correct way to load the custom renderer. This is the way I currently load it.\n\nI’m not sure if this is the correct loading method.\n\nIf this is not the correct loading method, will JME be able to incorporate certain interfaces in the future that would allow custom renderers to be added?",
"title": "jME 3.10.0-beta1"
}