diff --git a/imgui-lwjgl3/src/main/java/imgui/gl3/ImGuiImplGl3.java b/imgui-lwjgl3/src/main/java/imgui/gl3/ImGuiImplGl3.java index b0379baa..7a135a84 100644 --- a/imgui-lwjgl3/src/main/java/imgui/gl3/ImGuiImplGl3.java +++ b/imgui-lwjgl3/src/main/java/imgui/gl3/ImGuiImplGl3.java @@ -23,6 +23,8 @@ import static org.lwjgl.opengl.GL20.glGetAttribLocation; import static org.lwjgl.opengl.GL20.glGetProgramiv; import static org.lwjgl.opengl.GL20.glGetUniformLocation; +import static org.lwjgl.opengl.GL21.GL_PIXEL_UNPACK_BUFFER; +import static org.lwjgl.opengl.GL21.GL_PIXEL_UNPACK_BUFFER_BINDING; import static org.lwjgl.opengl.GL32.GL_ACTIVE_TEXTURE; import static org.lwjgl.opengl.GL32.GL_ARRAY_BUFFER; import static org.lwjgl.opengl.GL32.GL_ARRAY_BUFFER_BINDING; @@ -34,6 +36,7 @@ import static org.lwjgl.opengl.GL32.GL_BLEND_EQUATION_RGB; import static org.lwjgl.opengl.GL32.GL_BLEND_SRC_ALPHA; import static org.lwjgl.opengl.GL32.GL_BLEND_SRC_RGB; +import static org.lwjgl.opengl.GL32.GL_CLAMP_TO_EDGE; import static org.lwjgl.opengl.GL32.GL_COLOR_BUFFER_BIT; import static org.lwjgl.opengl.GL32.GL_COMPILE_STATUS; import static org.lwjgl.opengl.GL32.GL_CONTEXT_COMPATIBILITY_PROFILE_BIT; @@ -53,6 +56,7 @@ import static org.lwjgl.opengl.GL32.GL_LINEAR; import static org.lwjgl.opengl.GL32.GL_LINK_STATUS; import static org.lwjgl.opengl.GL32.GL_MAJOR_VERSION; +import static org.lwjgl.opengl.GL32.GL_MAX_TEXTURE_SIZE; import static org.lwjgl.opengl.GL32.GL_MINOR_VERSION; import static org.lwjgl.opengl.GL32.GL_ONE; import static org.lwjgl.opengl.GL32.GL_ONE_MINUS_SRC_ALPHA; @@ -69,6 +73,8 @@ import static org.lwjgl.opengl.GL32.GL_TEXTURE_BINDING_2D; import static org.lwjgl.opengl.GL32.GL_TEXTURE_MAG_FILTER; import static org.lwjgl.opengl.GL32.GL_TEXTURE_MIN_FILTER; +import static org.lwjgl.opengl.GL32.GL_TEXTURE_WRAP_S; +import static org.lwjgl.opengl.GL32.GL_TEXTURE_WRAP_T; import static org.lwjgl.opengl.GL32.GL_TRIANGLES; import static org.lwjgl.opengl.GL32.GL_TRUE; import static org.lwjgl.opengl.GL32.GL_UNPACK_ALIGNMENT; @@ -135,7 +141,10 @@ /** * This class is a straightforward port of the - * imgui_impl_opengl3.cpp. + * imgui_impl_opengl3.cpp. + *
+ * The Java side mirrors the C++ file top-to-bottom — it is not a delta port, so reading the two files side-by-side + * is the easiest way to understand the structure here. *
* It does support a backup and restoring of the GL state in the same way the original Dear ImGui code does. * Some of the very specific OpenGL variables may be ignored here, @@ -156,9 +165,10 @@ public class ImGuiImplGl3 { protected static class Data { protected int glVersion = 0; // Extracted at runtime using GL_MAJOR_VERSION, GL_MINOR_VERSION queries (e.g. 320 for GL 3.2) // protected boolean glProfileIsES2; -// protected boolean glProfileIsES3; + protected boolean glProfileIsES3; protected boolean glProfileIsCompat; protected int glProfileMask; + protected int maxTextureSize; protected GLCapabilities glCapabilities = null; protected String glslVersion = ""; protected int fontTexture = 0; @@ -172,7 +182,8 @@ protected static class Data { protected int elementsHandle = 0; // protected int vertexBufferSize; // protected int indexBufferSize; - // protected boolean hasPolygonMode; + protected boolean hasPolygonMode; + protected boolean hasBindSampler; protected boolean hasClipOrigin; } @@ -275,8 +286,20 @@ public boolean init(final String glslVersion) { } } data.glVersion = major * 100 + minor * 10; - data.glProfileMask = glGetInteger(GL_CONTEXT_PROFILE_MASK); + data.maxTextureSize = glGetInteger(GL_MAX_TEXTURE_SIZE); + + // Some Mesa/EGL desktop drivers expose an ES3 context whose GL_VERSION starts with "OpenGL ES 3". + // Mirror upstream's runtime detection so the gates below match the C++ behavior on such configurations. + if (glVersion != null && glVersion.startsWith("OpenGL ES 3")) { + data.glProfileIsES3 = true; + } + + // Upstream changelog 2023-06-20 (#6539): only query GL_CONTEXT_PROFILE_MASK on desktop GL >= 3.2. + if (!data.glProfileIsES3 && data.glVersion >= 320) { + data.glProfileMask = glGetInteger(GL_CONTEXT_PROFILE_MASK); + } data.glProfileIsCompat = (data.glProfileMask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) != 0; + if (data.glVersion < 330) { // Ignore in higher GL versions since they support sampler objects anyway try { data.glCapabilities = GL.getCapabilities(); @@ -293,9 +316,16 @@ public boolean init(final String glslVersion) { io.addBackendFlags(ImGuiBackendFlags.RendererHasVtxOffset); } + // In C++: io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures — the renderer drives per-frame ImTextureData uploads. + // In Java: ImTextureData is not exposed in imgui-binding yet, so we keep the legacy createFontsTexture path + // and intentionally do not advertise the flag (follow-up: expose ImTextureData in the binding). + // We can create multi-viewports on the Renderer side (optional) io.addBackendFlags(ImGuiBackendFlags.RendererHasViewports); + // In C++: platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = bd->MaxTextureSize. + // In Java: setters are not exposed on ImGuiPlatformIO in imgui-binding (follow-up). data.maxTextureSize is queried above for parity. + if (glslVersion == null) { if (IS_APPLE) { data.glslVersion = "#version 150"; @@ -313,7 +343,12 @@ public boolean init(final String glslVersion) { glGetIntegerv(GL_TEXTURE_BINDING_2D, currentTexture); } + // Detect extensions we support + data.hasPolygonMode = !data.glProfileIsES3; // ES2 cannot occur in this binding (desktop GL only). + data.hasBindSampler = data.glVersion >= 330 || data.glProfileIsES3; data.hasClipOrigin = data.glVersion >= 450; + // In C++: scans GL_NUM_EXTENSIONS for "GL_ARB_clip_control" to also enable hasClipOrigin on pre-4.5 contexts. + // In Java: omitted to avoid behavior drift versus the existing port (follow-up: honor the extension probe via glGetStringi). if (ImGui.getIO().hasConfigFlags(ImGuiConfigFlags.ViewportsEnable)) { @@ -330,6 +365,8 @@ public void shutdown() { destroyDeviceObjects(); io.setBackendRendererName(null); + // In C++: io.BackendFlags also clears RendererHasTextures, then platform_io.ClearRendererHandlers() runs. + // In Java: RendererHasTextures is never set (see init), and ClearRendererHandlers is not exposed in imgui-binding (follow-up). io.removeBackendFlags(ImGuiBackendFlags.RendererHasVtxOffset | ImGuiBackendFlags.RendererHasViewports); data = null; } @@ -353,10 +390,10 @@ protected void setupRenderState(final ImDrawData drawData, final int fbWidth, fi glDisable(GL_STENCIL_TEST); glEnable(GL_SCISSOR_TEST); - if (data.glVersion >= 310) { + if (!data.glProfileIsES3 && data.glVersion >= 310) { glDisable(GL_PRIMITIVE_RESTART); } - if (data.glVersion >= 200) { + if (data.hasPolygonMode) { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } @@ -397,8 +434,8 @@ protected void setupRenderState(final ImDrawData drawData, final int fbWidth, fi glUniform1i(data.attribLocationTex, 0); glUniformMatrix4fv(data.attribLocationProjMtx, false, props.orthoProjMatrix); - if (data.glVersion >= 330 || (data.glCapabilities != null && data.glCapabilities.GL_ARB_sampler_objects)) { - glBindSampler(0, 0); + if (data.hasBindSampler) { + glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 and GL ES 3.0 may set that otherwise. } glBindVertexArray(gVertexArrayObject); @@ -433,16 +470,20 @@ public void renderDrawData(final ImDrawData drawData) { return; } + // In C++: iterates draw_data->Textures and calls ImGui_ImplOpenGL3_UpdateTexture for each non-OK status. + // In Java: ImTextureData is not exposed in imgui-binding (follow-up); we keep the legacy createFontsTexture + // path triggered from newFrame(), so dynamic atlas updates are not honored here yet. + glGetIntegerv(GL_ACTIVE_TEXTURE, props.lastActiveTexture); glActiveTexture(GL_TEXTURE0); glGetIntegerv(GL_CURRENT_PROGRAM, props.lastProgram); glGetIntegerv(GL_TEXTURE_BINDING_2D, props.lastTexture); - if (data.glVersion >= 330 || (data.glCapabilities != null && data.glCapabilities.GL_ARB_sampler_objects)) { + if (data.hasBindSampler) { glGetIntegerv(GL_SAMPLER_BINDING, props.lastSampler); } glGetIntegerv(GL_ARRAY_BUFFER_BINDING, props.lastArrayBuffer); glGetIntegerv(GL_VERTEX_ARRAY_BINDING, props.lastVertexArrayObject); - if (data.glVersion >= 200) { + if (data.hasPolygonMode) { glGetIntegerv(GL_POLYGON_MODE, props.lastPolygonMode); } glGetIntegerv(GL_VIEWPORT, props.lastViewport); @@ -458,7 +499,7 @@ public void renderDrawData(final ImDrawData drawData) { props.lastEnableDepthTest = glIsEnabled(GL_DEPTH_TEST); props.lastEnableStencilTest = glIsEnabled(GL_STENCIL_TEST); props.lastEnableScissorTest = glIsEnabled(GL_SCISSOR_TEST); - if (data.glVersion >= 310) { + if (!data.glProfileIsES3 && data.glVersion >= 310) { props.lastEnablePrimitiveRestart = glIsEnabled(GL_PRIMITIVE_RESTART); } @@ -492,6 +533,8 @@ public void renderDrawData(final ImDrawData drawData) { // glBufferSubData(GL_ARRAY_BUFFER, 0, drawData.getCmdListVtxBufferData(n)); // glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, drawData.getCmdListIdxBufferData(n)); + // In C++: also has an UseBufferSubData branch (orphaning + glBufferSubData). + // In Java: upstream forces UseBufferSubData = false, so we mirror only the glBufferData path here. glBufferData(GL_ARRAY_BUFFER, drawData.getCmdListVtxBufferData(n), GL_STREAM_DRAW); glBufferData(GL_ELEMENT_ARRAY_BUFFER, drawData.getCmdListIdxBufferData(n), GL_STREAM_DRAW); @@ -541,7 +584,7 @@ public void renderDrawData(final ImDrawData drawData) { glUseProgram(props.lastProgram[0]); } glBindTexture(GL_TEXTURE_2D, props.lastTexture[0]); - if (data.glVersion >= 330 || (data.glCapabilities != null && data.glCapabilities.GL_ARB_sampler_objects)) { + if (data.hasBindSampler) { glBindSampler(0, props.lastSampler[0]); } glActiveTexture(props.lastActiveTexture[0]); @@ -559,23 +602,28 @@ public void renderDrawData(final ImDrawData drawData) { else glDisable(GL_STENCIL_TEST); if (props.lastEnableScissorTest) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); - if (data.glVersion >= 310) { + if (!data.glProfileIsES3 && data.glVersion >= 310) { if (props.lastEnablePrimitiveRestart) { glEnable(GL_PRIMITIVE_RESTART); } else { glDisable(GL_PRIMITIVE_RESTART); } } - if (data.glVersion <= 310 || data.glProfileIsCompat) { - glPolygonMode(GL_FRONT, props.lastPolygonMode[0]); - glPolygonMode(GL_BACK, props.lastPolygonMode[1]); - } else { - glPolygonMode(GL_FRONT_AND_BACK, props.lastPolygonMode[0]); + // Desktop OpenGL 3.0 and OpenGL 3.1 had separate polygon draw modes for front-facing and back-facing faces of polygons + if (data.hasPolygonMode) { + if (data.glVersion <= 310 || data.glProfileIsCompat) { + glPolygonMode(GL_FRONT, props.lastPolygonMode[0]); + glPolygonMode(GL_BACK, props.lastPolygonMode[1]); + } else { + glPolygonMode(GL_FRONT_AND_BACK, props.lastPolygonMode[0]); + } } glViewport(props.lastViewport[0], props.lastViewport[1], props.lastViewport[2], props.lastViewport[3]); glScissor(props.lastScissorBox[0], props.lastScissorBox[1], props.lastScissorBox[2], props.lastScissorBox[3]); } + // In C++: the legacy CreateFontsTexture has been removed in favor of UpdateTexture(WantCreate), driven by ImGuiPlatformIO::Textures. + // In Java: ImTextureData is not exposed in imgui-binding yet, so we keep the legacy path here (follow-up: migrate once exposed). public boolean createFontsTexture() { final ImFontAtlas fontAtlas = ImGui.getIO().getFonts(); @@ -592,7 +640,9 @@ public boolean createFontsTexture() { glBindTexture(GL_TEXTURE_2D, data.fontTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // Not on WebGL/ES + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Not on WebGL/ES glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); // Not on WebGL/ES glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); // Not on WebGL/ES glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); // Not on WebGL/ES @@ -660,9 +710,14 @@ protected boolean createDeviceObjects() { // Backup GL state final int[] lastTexture = new int[1]; final int[] lastArrayBuffer = new int[1]; + final int[] lastPixelUnpackBuffer = new int[1]; final int[] lastVertexArray = new int[1]; glGetIntegerv(GL_TEXTURE_BINDING_2D, lastTexture); glGetIntegerv(GL_ARRAY_BUFFER_BINDING, lastArrayBuffer); + if (data.glVersion >= 210) { + glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, lastPixelUnpackBuffer); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + } glGetIntegerv(GL_VERTEX_ARRAY_BINDING, lastVertexArray); final int glslVersionValue = parseGlslVersionString(data.glslVersion); @@ -718,11 +773,17 @@ protected boolean createDeviceObjects() { data.vboHandle = glGenBuffers(); data.elementsHandle = glGenBuffers(); + // In C++: the font texture is now created lazily through ImGui_ImplOpenGL3_UpdateTexture(WantCreate), + // driven by the platform_io.Textures iteration in RenderDrawData. + // In Java: ImTextureData is not exposed in imgui-binding (follow-up); keep the legacy createFontsTexture call here. createFontsTexture(); // Restore modified GL state glBindTexture(GL_TEXTURE_2D, lastTexture[0]); glBindBuffer(GL_ARRAY_BUFFER, lastArrayBuffer[0]); + if (data.glVersion >= 210) { + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, lastPixelUnpackBuffer[0]); + } glBindVertexArray(lastVertexArray[0]); return true; @@ -741,6 +802,8 @@ public void destroyDeviceObjects() { glDeleteProgram(data.shaderHandle); data.shaderHandle = 0; } + // In C++: iterates ImGui::GetPlatformIO().Textures and calls ImGui_ImplOpenGL3_DestroyTexture for each entry with RefCount == 1. + // In Java: ImTextureData is not exposed in imgui-binding (follow-up); we delete the legacy fontTexture directly. destroyFontsTexture(); } @@ -754,13 +817,16 @@ private final class RendererRenderWindowFunction extends ImPlatformFuncViewport @Override public void accept(final ImGuiViewport vp) { if (!vp.hasFlags(ImGuiViewportFlags.NoRendererClear)) { - glClearColor(0, 0, 0, 0); + glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); } renderDrawData(vp.getDrawData()); } } + // In C++: renamed to ImGui_ImplOpenGL3_InitMultiViewportSupport / ShutdownMultiViewportSupport. + // In Java: kept the legacy initPlatformInterface / shutdownPlatformInterface names — they are protected and renaming + // would break subclasses that override the multi-viewport hook. protected void initPlatformInterface() { ImGui.getPlatformIO().setRendererRenderWindow(new RendererRenderWindowFunction()); } diff --git a/imgui-lwjgl3/src/main/java/imgui/glfw/ImGuiImplGlfw.java b/imgui-lwjgl3/src/main/java/imgui/glfw/ImGuiImplGlfw.java index 99df2cfe..f59ea669 100644 --- a/imgui-lwjgl3/src/main/java/imgui/glfw/ImGuiImplGlfw.java +++ b/imgui-lwjgl3/src/main/java/imgui/glfw/ImGuiImplGlfw.java @@ -40,6 +40,7 @@ import java.nio.ByteBuffer; import java.nio.FloatBuffer; +import java.nio.IntBuffer; import static org.lwjgl.glfw.GLFW.GLFW_ARROW_CURSOR; import static org.lwjgl.glfw.GLFW.GLFW_CURSOR; @@ -192,11 +193,14 @@ import static org.lwjgl.glfw.GLFW.GLFW_KEY_UP; import static org.lwjgl.glfw.GLFW.GLFW_KEY_V; import static org.lwjgl.glfw.GLFW.GLFW_KEY_W; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_WORLD_1; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_WORLD_2; import static org.lwjgl.glfw.GLFW.GLFW_KEY_X; import static org.lwjgl.glfw.GLFW.GLFW_KEY_Y; import static org.lwjgl.glfw.GLFW.GLFW_KEY_Z; import static org.lwjgl.glfw.GLFW.GLFW_MOUSE_PASSTHROUGH; import static org.lwjgl.glfw.GLFW.GLFW_NOT_ALLOWED_CURSOR; +import static org.lwjgl.glfw.GLFW.GLFW_PLATFORM_WAYLAND; import static org.lwjgl.glfw.GLFW.GLFW_PRESS; import static org.lwjgl.glfw.GLFW.GLFW_RELEASE; import static org.lwjgl.glfw.GLFW.GLFW_RESIZE_ALL_CURSOR; @@ -224,12 +228,14 @@ import static org.lwjgl.glfw.GLFW.glfwGetKey; import static org.lwjgl.glfw.GLFW.glfwGetKeyName; import static org.lwjgl.glfw.GLFW.glfwGetMonitorContentScale; +import static org.lwjgl.glfw.GLFW.glfwGetPlatform; import static org.lwjgl.glfw.GLFW.glfwGetMonitorPos; import static org.lwjgl.glfw.GLFW.glfwGetMonitorWorkarea; import static org.lwjgl.glfw.GLFW.glfwGetMonitors; import static org.lwjgl.glfw.GLFW.glfwGetTime; import static org.lwjgl.glfw.GLFW.glfwGetVideoMode; import static org.lwjgl.glfw.GLFW.glfwGetWindowAttrib; +import static org.lwjgl.glfw.GLFW.glfwGetWindowContentScale; import static org.lwjgl.glfw.GLFW.glfwGetWindowPos; import static org.lwjgl.glfw.GLFW.glfwGetWindowSize; import static org.lwjgl.glfw.GLFW.glfwMakeContextCurrent; @@ -262,7 +268,7 @@ /** * This class is a straightforward port of the - * imgui_impl_glfw.cpp. + * imgui_impl_glfw.cpp. *
* It supports clipboard, gamepad, mouse and keyboard in the same way the original Dear ImGui code does. You can copy-paste this class in your codebase and * modify the rendering routine in the way you'd like. @@ -272,6 +278,7 @@ public class ImGuiImplGlfw { protected static final String OS = System.getProperty("os.name", "generic").toLowerCase(); protected static final boolean IS_WINDOWS = OS.contains("win"); protected static final boolean IS_APPLE = OS.contains("mac") || OS.contains("darwin"); + protected static final boolean IS_LINUX = OS.contains("linux"); /** * Data class to store implementation specific fields. @@ -279,14 +286,18 @@ public class ImGuiImplGlfw { */ protected static class Data { protected long window = -1; + protected GlfwClientApi clientApi = GlfwClientApi.UNKNOWN; protected double time = 0.0; protected long mouseWindow = -1; protected long[] mouseCursors = new long[ImGuiMouseCursor.COUNT]; + protected long lastMouseCursor = -1; + protected boolean mouseIgnoreButtonUpWaitForFocusLoss = false; + protected boolean mouseIgnoreButtonUp = false; protected ImVec2 lastValidMousePos = new ImVec2(); protected long[] keyOwnerWindows = new long[GLFW_KEY_LAST]; + protected boolean isWayland = false; protected boolean installedCallbacks = false; protected boolean callbacksChainForAllWindows = false; - protected boolean wantUpdateMonitors = true; // Chain GLFW callbacks: our callbacks will call the user's previously installed callbacks, if any. protected GLFWWindowFocusCallback prevUserCallbackWindowFocus = null; @@ -321,6 +332,10 @@ private static final class Properties { private final double[] mouseX = new double[1]; private final double[] mouseY = new double[1]; + // Scratch ImVec2 outputs for getWindowSizeAndFramebufferScale + private final ImVec2 tmpDisplaySize = new ImVec2(); + private final ImVec2 tmpFbScale = new ImVec2(); + // Monitor properties private final int[] monitorX = new int[1]; private final int[] monitorY = new int[1]; @@ -344,8 +359,10 @@ private static final class Properties { private final Properties props = new Properties(); // We gather version tests as define in order to easily see which features are version-dependent. + // In C++: GLFW_HAS_X11/GLFW_HAS_WAYLAND derive from compile-time _GLFW_X11/_GLFW_WAYLAND macros. + // In Java: detect Wayland at runtime via glfwGetPlatform() (see isWayland helper), since LWJGL does not expose GLFW's compile-time flags. protected static final int glfwVersionCombined = GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 + GLFW_VERSION_REVISION; - protected static final boolean glfwHawWindowTopmost = glfwVersionCombined >= 3200; // 3.2+ GLFW_FLOATING + protected static final boolean glfwHasWindowTopmost = glfwVersionCombined >= 3200; // 3.2+ GLFW_FLOATING protected static final boolean glfwHasWindowHovered = glfwVersionCombined >= 3300; // 3.3+ GLFW_HOVERED protected static final boolean glfwHasWindowAlpha = glfwVersionCombined >= 3300; // 3.3+ glfwSetWindowOpacity protected static final boolean glfwHasPerMonitorDpi = glfwVersionCombined >= 3300; // 3.3+ glfwGetMonitorContentScale @@ -359,6 +376,7 @@ private static final class Properties { protected static final boolean glfwHasGamepadApi = glfwVersionCombined >= 3300; // 3.3+ glfwGetGamepadState() new api protected static final boolean glfwHasGetKeyName = glfwVersionCombined >= 3200; // 3.2+ glfwGetKeyName() protected static final boolean glfwHasGetError = glfwVersionCombined >= 3300; // 3.3+ glfwGetError() + protected static final boolean glfwHasGetPlatform = glfwVersionCombined >= 3400; // 3.4+ glfwGetPlatform() protected ImStrSupplier getClipboardTextFn() { return new ImStrSupplier() { @@ -429,6 +447,10 @@ protected int glfwKeyToImGuiKey(final int glfwKey) { return ImGuiKey.LeftBracket; case GLFW_KEY_BACKSLASH: return ImGuiKey.Backslash; + case GLFW_KEY_WORLD_1: + return ImGuiKey.Oem102; + case GLFW_KEY_WORLD_2: + return ImGuiKey.Oem102; case GLFW_KEY_RIGHT_BRACKET: return ImGuiKey.RightBracket; case GLFW_KEY_GRAVE_ACCENT: @@ -639,6 +661,11 @@ public void mouseButtonCallback(final long window, final int button, final int a data.prevUserCallbackMousebutton.invoke(window, button, action, mods); } + // Workaround for Linux: ignore mouse up events which are following an focus loss following a viewport creation + if (data.mouseIgnoreButtonUp && action == GLFW_RELEASE) { + return; + } + updateKeyModifiers(window); final ImGuiIO io = ImGui.getIO(); @@ -671,9 +698,13 @@ protected int translateUntranslatedKey(final int key, final int scancode) { } int resultKey = key; + final GLFWErrorCallback prevErrorCallback = glfwSetErrorCallback(null); final String keyName = glfwGetKeyName(key, scancode); + glfwSetErrorCallback(prevErrorCallback); eatErrors(); - if (keyName != null && keyName.length() > 2 && keyName.charAt(0) != 0 && keyName.charAt(1) == 0) { + // C++ checks key_name[0] != 0 && key_name[1] == 0 (NUL-terminated single char). In Java strings have no + // trailing NUL, so the equivalent is "exactly one character". + if (keyName != null && keyName.length() == 1) { if (keyName.charAt(0) >= '0' && keyName.charAt(0) <= '9') { resultKey = GLFW_KEY_0 + (keyName.charAt(0) - '0'); } else if (keyName.charAt(0) >= 'A' && keyName.charAt(0) <= 'Z') { @@ -727,6 +758,10 @@ public void windowFocusCallback(final long window, final boolean focused) { data.prevUserCallbackWindowFocus.invoke(window, focused); } + // Workaround for Linux: when losing focus with mouseIgnoreButtonUpWaitForFocusLoss set, we will temporarily ignore subsequent Mouse Up events + data.mouseIgnoreButtonUp = data.mouseIgnoreButtonUpWaitForFocusLoss && !focused; + data.mouseIgnoreButtonUpWaitForFocusLoss = false; + ImGui.getIO().addFocusEvent(focused); } @@ -765,7 +800,7 @@ public void cursorEnterCallback(final long window, final boolean entered) { } else if (data.mouseWindow == window) { io.getMousePos(data.lastValidMousePos); data.mouseWindow = -1; - io.addMousePosEvent(Float.MIN_VALUE, Float.MIN_VALUE); + io.addMousePosEvent(-Float.MAX_VALUE, -Float.MAX_VALUE); } } @@ -778,7 +813,7 @@ public void charCallback(final long window, final int c) { } public void monitorCallback(final long window, final int event) { - data.wantUpdateMonitors = true; + // This function is technically part of the API even if we stopped using the callback, so leaving it around. } public void installCallbacks(final long window) { @@ -834,19 +869,57 @@ protected Data newData() { } public boolean init(final long window, final boolean installCallbacks) { - final ImGuiIO io = ImGui.getIO(); + // Backward-compatible entry point: historically the binding was used with OpenGL only. + return initImpl(window, installCallbacks, GlfwClientApi.OPENGL); + } - io.setBackendPlatformName("imgui-java_impl_glfw"); - io.addBackendFlags(ImGuiBackendFlags.HasMouseCursors | ImGuiBackendFlags.HasSetMousePos | ImGuiBackendFlags.PlatformHasViewports); - if (glfwHasMousePassthrough || (glfwHasWindowHovered && IS_WINDOWS)) { - io.addBackendFlags(ImGuiBackendFlags.HasMouseHoveredViewport); - } + public boolean initForOpenGL(final long window, final boolean installCallbacks) { + return initImpl(window, installCallbacks, GlfwClientApi.OPENGL); + } + + public boolean initForVulkan(final long window, final boolean installCallbacks) { + return initImpl(window, installCallbacks, GlfwClientApi.VULKAN); + } + + public boolean initForOther(final long window, final boolean installCallbacks) { + return initImpl(window, installCallbacks, GlfwClientApi.OTHER); + } + + private boolean initImpl(final long window, final boolean installCallbacks, final GlfwClientApi clientApi) { + final ImGuiIO io = ImGui.getIO(); data = newData(); data.window = window; data.time = 0.0; - data.wantUpdateMonitors = true; + data.isWayland = isWayland(); + + // Compute runtime GLFW version so the backend name matches the actually-loaded library. + final IntBuffer verMajor = MemoryUtil.memAllocInt(1); + final IntBuffer verMinor = MemoryUtil.memAllocInt(1); + final IntBuffer verRev = MemoryUtil.memAllocInt(1); + try { + org.lwjgl.glfw.GLFW.glfwGetVersion(verMajor, verMinor, verRev); + final int versionCombined = verMajor.get(0) * 1000 + verMinor.get(0) * 100 + verRev.get(0); + io.setBackendPlatformName("imgui_impl_glfw (" + versionCombined + ")"); + } finally { + MemoryUtil.memFree(verMajor); + MemoryUtil.memFree(verMinor); + MemoryUtil.memFree(verRev); + } + io.addBackendFlags(ImGuiBackendFlags.HasMouseCursors | ImGuiBackendFlags.HasSetMousePos); + // In C++ (1.92+): viewports are gated purely on platform support (disabled on Wayland). + // In Java: ImGuiConfigFlags.ViewportsEnable is also required from the user — this is set by the + // application and is intentionally preserved as a divergence from upstream. + if (!data.isWayland) { + io.addBackendFlags(ImGuiBackendFlags.PlatformHasViewports); + } + if (glfwHasMousePassthrough || (glfwHasWindowHovered && IS_WINDOWS)) { + io.addBackendFlags(ImGuiBackendFlags.HasMouseHoveredViewport); + } + + // In C++ (1.92+): Platform_GetClipboardTextFn / Platform_SetClipboardTextFn are installed on ImGuiPlatformIO. + // In Java: still wired via legacy ImGuiIO.setGet/SetClipboardTextFn — migration requires JNI work (see follow-up in review.md). io.setGetClipboardTextFn(getClipboardTextFn()); io.setSetClipboardTextFn(setClipboardTextFn()); @@ -896,9 +969,24 @@ public boolean init(final long window, final boolean installCallbacks) { initPlatformInterface(); } + // In C++: on Windows a WndProc hook is registered (SetPropA "IMGUI_BACKEND_DATA" + SetWindowLongPtrW) + // to surface MouseSourceEvent (touch/pen) and to handle WM_NCHITTEST for NoInputs. + // In Java: this needs a JNI/JNA layer (see follow-up in review.md). Without it, only the passthrough + // branches of multi-viewport handling are correct. + data.clientApi = clientApi; return true; } + private static boolean isWayland() { + // In C++ a third fallback parses glfwGetVersionString() and checks glfwGetX11Display() when GLFW < 3.4 is + // compiled without glfwGetPlatform(). LWJGL 3.4.x always exposes glfwGetPlatform, so we omit that path; if + // we ever downgrade to a 3.3.x LWJGL, Wayland would silently report as non-Wayland here. + if (glfwHasGetPlatform) { + return glfwGetPlatform() == GLFW_PLATFORM_WAYLAND; + } + return false; + } + public void shutdown() { final ImGuiIO io = ImGui.getIO(); @@ -917,10 +1005,15 @@ public void shutdown() { } } + // In C++: the Windows WndProc hook installed during init is unhooked here (SetPropA + SetWindowLongPtrW). + // In Java: the hook is never installed (see follow-up), so there is nothing to unhook. + io.setBackendPlatformName(null); - data = null; io.removeBackendFlags(ImGuiBackendFlags.HasMouseCursors | ImGuiBackendFlags.HasSetMousePos | ImGuiBackendFlags.HasGamepad | ImGuiBackendFlags.PlatformHasViewports | ImGuiBackendFlags.HasMouseHoveredViewport); + // In C++: ImGui::GetPlatformIO().ClearPlatformHandlers() resets the Platform_* handlers. + // In Java: the method is not exposed on ImGuiPlatformIO — see follow-up in review.md. + data = null; } protected void updateMouseData() { @@ -970,12 +1063,12 @@ protected void updateMouseData() { // FIXME: This is currently only correct on Win32. See what we do below with the WM_NCHITTEST, missing an equivalent for other systems. // See https://github.com/glfw/glfw/issues/1236 if you want to help in making this a GLFW feature. - if (glfwHasMousePassthrough || (glfwHasWindowHovered && IS_WINDOWS)) { - boolean windowNoInput = viewport.hasFlags(ImGuiViewportFlags.NoInputs); - if (glfwHasMousePassthrough) { - glfwSetWindowAttrib(window, GLFW_MOUSE_PASSTHROUGH, windowNoInput ? GLFW_TRUE : GLFW_FALSE); - } - if (glfwGetWindowAttrib(window, GLFW_HOVERED) == GLFW_TRUE && !windowNoInput) { + if (glfwHasMousePassthrough) { + final boolean windowNoInput = viewport.hasFlags(ImGuiViewportFlags.NoInputs); + glfwSetWindowAttrib(window, GLFW_MOUSE_PASSTHROUGH, windowNoInput ? GLFW_TRUE : GLFW_FALSE); + } + if (glfwHasMousePassthrough || glfwHasWindowHovered) { + if (glfwGetWindowAttrib(window, GLFW_HOVERED) != 0) { mouseViewportId = viewport.getID(); } } @@ -992,6 +1085,7 @@ protected void updateMouseCursor() { final ImGuiIO io = ImGui.getIO(); if (io.hasConfigFlags(ImGuiConfigFlags.NoMouseCursorChange) || glfwGetInputMode(data.window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED) { + data.lastMouseCursor = -1; // Invalidate so that if user changes underlying cursor we will update it next time we can. return; } @@ -1002,12 +1096,19 @@ protected void updateMouseCursor() { final long windowPtr = platformIO.getViewports(n).getPlatformHandle(); if (imguiCursor == ImGuiMouseCursor.None || io.getMouseDrawCursor()) { - // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor - glfwSetInputMode(windowPtr, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + if (data.lastMouseCursor != -1) { + // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor + glfwSetInputMode(windowPtr, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + data.lastMouseCursor = -1; + } } else { // Show OS mouse cursor // FIXME-PLATFORM: Unfocused windows seems to fail changing the mouse cursor with GLFW 3.2, but 3.3 works here. - glfwSetCursor(windowPtr, data.mouseCursors[imguiCursor] != 0 ? data.mouseCursors[imguiCursor] : data.mouseCursors[ImGuiMouseCursor.Arrow]); + final long cursor = data.mouseCursors[imguiCursor] != 0 ? data.mouseCursors[imguiCursor] : data.mouseCursors[ImGuiMouseCursor.Arrow]; + if (data.lastMouseCursor != cursor) { + glfwSetCursor(windowPtr, cursor); + data.lastMouseCursor = cursor; + } glfwSetInputMode(windowPtr, GLFW_CURSOR, GLFW_CURSOR_NORMAL); } } @@ -1095,25 +1196,22 @@ protected void updateGamepads() { protected void updateMonitors() { final ImGuiPlatformIO platformIO = ImGui.getPlatformIO(); - data.wantUpdateMonitors = false; final PointerBuffer monitors = glfwGetMonitors(); if (monitors == null) { - System.err.println("Unable to get monitors!"); - return; - } - if (monitors.limit() == 0) { // Preserve existing monitor list if there are none. Happens on macOS sleeping (#5683) return; } - platformIO.resizeMonitors(0); - + boolean updatedMonitors = false; for (int n = 0; n < monitors.limit(); n++) { final long monitor = monitors.get(n); final GLFWVidMode vidMode = glfwGetVideoMode(monitor); if (vidMode == null) { - continue; + continue; // Failed to get Video mode (e.g. Emscripten does not support this function) + } + if (vidMode.width() <= 0 || vidMode.height() <= 0) { + continue; // Failed to query suitable monitor info (#9195) } glfwGetMonitorPos(monitor, props.monitorX, props.monitorY); @@ -1123,10 +1221,10 @@ protected void updateMonitors() { final float mainSizeX = vidMode.width(); final float mainSizeY = vidMode.height(); - float workPosX = 0; - float workPosY = 0; - float workSizeX = 0; - float workSizeY = 0; + float workPosX = mainPosX; + float workPosY = mainPosY; + float workSizeX = mainSizeX; + float workSizeY = mainSizeY; // Workaround a small GLFW issue reporting zero on monitor changes: https://github.com/glfw/glfw/pull/1761 if (glfwHasMonitorWorkArea) { @@ -1139,35 +1237,91 @@ protected void updateMonitors() { } } - float dpiScale = 0; - // Warning: the validity of monitor DPI information on Windows depends on the application DPI awareness settings, // which generally needs to be set in the manifest or at runtime. - if (glfwHasPerMonitorDpi) { - glfwGetMonitorContentScale(monitor, props.monitorContentScaleX, props.monitorContentScaleY); - dpiScale = props.monitorContentScaleX[0]; + final float dpiScale = getContentScaleForMonitor(monitor); + if (dpiScale == 0.0f) { + continue; // Some accessibility applications are declaring virtual monitors with a DPI of 0 (#7902) + } + + // Preserve existing monitor list until a valid one is added. + // Happens on macOS sleeping (#5683) and seemingly occasionally on Windows (#9195) + if (!updatedMonitors) { + platformIO.resizeMonitors(0); + updatedMonitors = true; } platformIO.pushMonitors(monitor, mainPosX, mainPosY, mainSizeX, mainSizeY, workPosX, workPosY, workSizeX, workSizeY, dpiScale); } } - public void newFrame() { - final ImGuiIO io = ImGui.getIO(); + // - On Windows the process needs to be marked DPI-aware. You can call SetProcessDPIAware() or call ImGui_ImplWin32_EnableDpiAwareness() from Win32 backend. + // - Apple platforms use FramebufferScale so we always return 1.0f. + // - Some accessibility applications are declaring virtual monitors with a DPI of 0.0f, see #7902. We preserve this value for caller to handle. + public static float getContentScaleForWindow(final long window) { + if (IS_APPLE) { + return 1.0f; + } + if (glfwHasGetPlatform && glfwGetPlatform() == GLFW_PLATFORM_WAYLAND) { + return 1.0f; + } + if (glfwHasPerMonitorDpi) { + final float[] xScale = new float[1]; + final float[] yScale = new float[1]; + glfwGetWindowContentScale(window, xScale, yScale); + return xScale[0]; + } + return 1.0f; + } - // Setup display size (every frame to accommodate for window resizing) - glfwGetWindowSize(data.window, props.windowW, props.windowH); - glfwGetFramebufferSize(data.window, props.displayW, props.displayH); - io.setDisplaySize((float) props.windowW[0], (float) props.windowH[0]); - if (props.windowW[0] > 0 && props.windowH[0] > 0) { - final float scaleX = (float) props.displayW[0] / props.windowW[0]; - final float scaleY = (float) props.displayH[0] / props.windowH[0]; - io.setDisplayFramebufferScale(scaleX, scaleY); + public static float getContentScaleForMonitor(final long monitor) { + if (IS_APPLE) { + return 1.0f; } + if (glfwHasGetPlatform && glfwGetPlatform() == GLFW_PLATFORM_WAYLAND) { + return 1.0f; + } + if (glfwHasPerMonitorDpi) { + final float[] xScale = new float[1]; + final float[] yScale = new float[1]; + glfwGetMonitorContentScale(monitor, xScale, yScale); + return xScale[0]; + } + return 1.0f; + } - if (data.wantUpdateMonitors) { - updateMonitors(); + private void getWindowSizeAndFramebufferScale(final long window, final ImVec2 outSize, final ImVec2 outFbScale) { + glfwGetWindowSize(window, props.windowW, props.windowH); + glfwGetFramebufferSize(window, props.displayW, props.displayH); + float fbScaleX = (props.windowW[0] > 0) ? (float) props.displayW[0] / (float) props.windowW[0] : 1.0f; + float fbScaleY = (props.windowH[0] > 0) ? (float) props.displayH[0] / (float) props.windowH[0] : 1.0f; + // In C++ (1.92+): #if GLFW_HAS_WAYLAND && !bd->IsWayland forces fb_scale to (1, 1). GLFW_HAS_WAYLAND is a + // compile-time guard that is only ever true on Linux GLFW builds, so in practice this overrides + // the ratio on Linux non-Wayland sessions only. The companion change is in imgui_impl_opengl3: + // the renderer no longer multiplies glViewport by DisplayFramebufferScale, it uses DisplaySize + // directly, so reporting (1, 1) does not shrink the output. + // In Java: ImGuiImplGl3.java has been resynced to mirror the new renderer, so we restore the override here. + // Translate the compile-time GLFW_HAS_WAYLAND guard to the runtime check IS_LINUX && !data.isWayland. + if (IS_LINUX && !data.isWayland) { + fbScaleX = 1.0f; + fbScaleY = 1.0f; + } + if (outSize != null) { + outSize.set(props.windowW[0], props.windowH[0]); } + if (outFbScale != null) { + outFbScale.set(fbScaleX, fbScaleY); + } + } + + public void newFrame() { + final ImGuiIO io = ImGui.getIO(); + + // Setup main viewport size (every frame to accommodate for window resizing) + getWindowSizeAndFramebufferScale(data.window, props.tmpDisplaySize, props.tmpFbScale); + io.setDisplaySize(props.tmpDisplaySize.x, props.tmpDisplaySize.y); + io.setDisplayFramebufferScale(props.tmpFbScale.x, props.tmpFbScale.y); + updateMonitors(); // Setup time step // (Accept glfwGetTime() not returning a monotonically increasing value. Seems to happens on disconnecting peripherals and probably on VMs and Emscripten, see #6491, #6189, #6114, #3644) @@ -1178,6 +1332,7 @@ public void newFrame() { io.setDeltaTime(data.time > 0.0 ? (float) (currentTime - data.time) : 1.0f / 60.0f); data.time = currentTime; + data.mouseIgnoreButtonUp = false; updateMouseData(); updateMouseCursor(); @@ -1185,6 +1340,15 @@ public void newFrame() { updateGamepads(); } + // GLFW doesn't provide a portable sleep function. + public static void sleep(final long milliseconds) { + try { + Thread.sleep(milliseconds); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + //-------------------------------------------------------------------------------------------------------- // MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT // This is an _advanced_ and _optional_ feature, allowing the backend to create and handle multiple viewports simultaneously. @@ -1251,6 +1415,11 @@ public void accept(final ImGuiViewport vp) { final ViewportData vd = new ViewportData(); vp.setPlatformUserData(vd); + // Workaround for Linux: ignore mouse up events corresponding to losing focus of the previously focused window (#7733, #3158, #7922) + if (IS_LINUX) { + data.mouseIgnoreButtonUpWaitForFocusLoss = true; + } + // GLFW 3.2 unfortunately always set focus on glfwCreateWindow() if GLFW_VISIBLE is set, regardless of GLFW_FOCUSED // With GLFW 3.3, the hint GLFW_FOCUS_ON_SHOW fixes this problem glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); @@ -1259,17 +1428,22 @@ public void accept(final ImGuiViewport vp) { glfwWindowHint(GLFW_FOCUS_ON_SHOW, GLFW_FALSE); } glfwWindowHint(GLFW_DECORATED, vp.hasFlags(ImGuiViewportFlags.NoDecoration) ? GLFW_FALSE : GLFW_TRUE); - if (glfwHawWindowTopmost) { + if (glfwHasWindowTopmost) { glfwWindowHint(GLFW_FLOATING, vp.hasFlags(ImGuiViewportFlags.TopMost) ? GLFW_TRUE : GLFW_FALSE); } - vd.window = glfwCreateWindow((int) vp.getSizeX(), (int) vp.getSizeY(), "No Title Yet", NULL, data.window); + final long shareWindow = (data.clientApi == GlfwClientApi.OPENGL) ? data.window : NULL; + vd.window = glfwCreateWindow((int) vp.getSizeX(), (int) vp.getSizeY(), "No Title Yet", NULL, shareWindow); vd.windowOwned = true; vp.setPlatformHandle(vd.window); + // In C++: on Linux/X11, ImGui_ImplGlfw_SetWindowFloating is called (dynamic libX11 load + _NET_WM_WINDOW_TYPE change). + // In Java: LWJGL does not expose X11 atoms — feature omitted, see follow-up in review.md. if (IS_WINDOWS) { vp.setPlatformHandleRaw(GLFWNativeWin32.glfwGetWin32Window(vd.window)); + // In C++: SetPropA(hwnd, "IMGUI_BACKEND_DATA", bd) — needed for the shared WndProc hook. + // In Java: WndProc hook is not implemented, see follow-up in review.md. } else if (IS_APPLE) { vp.setPlatformHandleRaw(GLFWNativeCocoa.glfwGetCocoaWindow(vd.window)); } @@ -1288,8 +1462,10 @@ public void accept(final ImGuiViewport vp) { glfwSetWindowPosCallback(vd.window, ImGuiImplGlfw.this::windowPosCallback); glfwSetWindowSizeCallback(vd.window, ImGuiImplGlfw.this::windowSizeCallback); - glfwMakeContextCurrent(vd.window); - glfwSwapInterval(0); + if (data.clientApi == GlfwClientApi.OPENGL) { + glfwMakeContextCurrent(vd.window); + glfwSwapInterval(0); + } } } @@ -1299,8 +1475,10 @@ public void accept(final ImGuiViewport vp) { final ViewportData vd = (ViewportData) vp.getPlatformUserData(); if (vd != null && vd.windowOwned) { + // In C++ (Win32, !GLFW_HAS_MOUSE_PASSTHROUGH && GLFW_HAS_WINDOW_HOVERED): RemovePropA(hwnd, "IMGUI_VIEWPORT"). + // In Java: no WndProc hook means there is no prop to remove — see follow-up in review.md. if (!glfwHasMousePassthrough && glfwHasWindowHovered && IS_WINDOWS) { - // TODO: RemovePropA + // intentionally empty — see comment above. } // Release any keys that were pressed in the window being destroyed and are still held down, @@ -1335,6 +1513,10 @@ public void accept(final ImGuiViewport vp) { ImGuiImplGlfwNative.win32hideFromTaskBar(vp.getPlatformHandleRaw()); } + // In C++ (Win32): SetPropA + SetWindowLongPtrW(hwnd, GWLP_WNDPROC, ImGui_ImplGlfw_WndProc) — for MouseSourceEvent (touch/pen) and WM_NCHITTEST/HTTRANSPARENT. + // In Java: WndProc hook is not implemented (requires JNI/JNA), see follow-up in review.md. + // In C++ (GLFW < 3.3 fallback for NoFocusOnAppearing): workaround uses ::ShowWindow(hwnd, SW_SHOWNA). + // In Java: LWJGL 3.x requires GLFW >= 3.3, this branch does not apply. glfwShowWindow(vd.window); } } @@ -1469,21 +1651,21 @@ public void accept(final ImGuiViewport vp, final float value) { } } - private static final class RenderWindowFunction extends ImPlatformFuncViewport { + private final class RenderWindowFunction extends ImPlatformFuncViewport { @Override public void accept(final ImGuiViewport vp) { final ViewportData vd = (ViewportData) vp.getPlatformUserData(); - if (vd != null) { + if (vd != null && data.clientApi == GlfwClientApi.OPENGL) { glfwMakeContextCurrent(vd.window); } } } - private static final class SwapBuffersFunction extends ImPlatformFuncViewport { + private final class SwapBuffersFunction extends ImPlatformFuncViewport { @Override public void accept(final ImGuiViewport vp) { final ViewportData vd = (ViewportData) vp.getPlatformUserData(); - if (vd != null) { + if (vd != null && data.clientApi == GlfwClientApi.OPENGL) { glfwMakeContextCurrent(vd.window); glfwSwapBuffers(vd.window); } @@ -1491,6 +1673,10 @@ public void accept(final ImGuiViewport vp) { } protected void initPlatformInterface() { + // In C++: when GLFW_HAS_VULKAN is set, Platform_CreateVkSurface is registered — a helper for the Vulkan renderer. + // In Java: the Vulkan branch is intentionally omitted (no Vulkan binding in this project). + // In C++ (1.92+): Platform_GetWindowFramebufferScale is installed on ImGuiPlatformIO for per-viewport DPI. + // In Java: ImGuiPlatformIO does not expose the corresponding setter — see follow-up in review.md. final ImGuiPlatformIO platformIO = ImGui.getPlatformIO(); // Register platform interface (will be coupled with a renderer interface) @@ -1522,4 +1708,12 @@ protected void initPlatformInterface() { protected void shutdownPlatformInterface() { ImGui.destroyPlatformWindows(); } + + // Mirrors C++ enum GlfwClientApi. UNKNOWN matches C++'s GlfwClientApi_Unknown ("anything else"). + enum GlfwClientApi { + UNKNOWN, + OPENGL, + VULKAN, + OTHER + } }