diff --git a/.gitignore b/.gitignore index 39df9edc..3b4fc22c 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,5 @@ out/ # Dear ImGui runtime layout file (written by ImGui on exit) imgui.ini + +bin/libimgui-java64.so diff --git a/bin/libimgui-java64.so b/bin/libimgui-java64.so deleted file mode 100644 index 55cce7eb..00000000 Binary files a/bin/libimgui-java64.so and /dev/null differ diff --git a/example/src/main/java/Main.java b/example/src/main/java/Main.java index 59822cb5..1f503939 100644 --- a/example/src/main/java/Main.java +++ b/example/src/main/java/Main.java @@ -2,11 +2,16 @@ import imgui.ImFontGlyphRangesBuilder; import imgui.ImGui; import imgui.ImGuiIO; +import imgui.ImGuiPlatformIO; +import imgui.ImTextureData; import imgui.app.Application; import imgui.app.Configuration; +import imgui.flag.ImGuiBackendFlags; import imgui.flag.ImGuiConfigFlags; import imgui.flag.ImGuiInputTextFlags; import imgui.flag.ImGuiWindowFlags; +import imgui.flag.ImTextureFormat; +import imgui.flag.ImTextureStatus; import imgui.type.ImString; import java.io.IOException; @@ -68,7 +73,9 @@ private void initFonts(final ImGuiIO io) { io.getFonts().addFontFromMemoryTTF(loadFromResources("NotoSansCJKjp-Medium.otf"), 14, fontConfig, glyphRanges); // japanese glyphs io.getFonts().addFontFromMemoryTTF(loadFromResources("fa-regular-400.ttf"), 14, fontConfig, glyphRanges); // font awesome io.getFonts().addFontFromMemoryTTF(loadFromResources("fa-solid-900.ttf"), 14, fontConfig, glyphRanges); // font awesome - io.getFonts().build(); + + // NB: With the 1.92 texture-management backend (ImGuiBackendFlags_RendererHasTextures) the atlas is baked + // lazily by the renderer — you must NOT call ImFontAtlas::build() yourself, or Dear ImGui will assert. fontConfig.destroy(); } @@ -100,10 +107,73 @@ public void process() { ImGui.separator(); ImGui.text("Extra"); Extra.show(this); + + ImGui.separator(); + showTextureManagement(); } ImGui.end(); } + /** + * Demonstrates Dear ImGui 1.92's texture-management system (ImGuiBackendFlags_RendererHasTextures). + *

+ * The GL3 backend now drives texture create/update/destroy through {@link ImTextureData}, so the font atlas is + * uploaded (and incrementally updated) automatically each frame. Here we simply inspect the live texture list that + * the backend maintains and render the managed atlas texture inline. + */ + private void showTextureManagement() { + if (!ImGui.collapsingHeader("Texture Management")) { + return; + } + + final ImGuiIO io = ImGui.getIO(); + final boolean hasTextures = io.hasBackendFlags(ImGuiBackendFlags.RendererHasTextures); + ImGui.text("RendererHasTextures: " + (hasTextures ? "enabled" : "disabled")); + + final ImGuiPlatformIO platformIO = ImGui.getPlatformIO(); + final int texturesCount = platformIO.getTexturesSize(); + ImGui.text("Textures managed by Dear ImGui: " + texturesCount); + + for (int i = 0; i < texturesCount; i++) { + final ImTextureData tex = platformIO.getTextures(i); + ImGui.bulletText(String.format( + "#%d %dx%d %s status=%s texID=%d", + tex.getUniqueId(), tex.getWidth(), tex.getHeight(), + formatName(tex.getFormat()), statusName(tex.getStatus()), tex.getTexID())); + + // Render any RGBA texture that has already been uploaded (the font atlas is the usual one). + if (tex.getStatus() == ImTextureStatus.OK && tex.getTexID() != 0 && tex.getFormat() == ImTextureFormat.RGBA32) { + final float preview = 256.0f; + final float aspect = tex.getHeight() / (float) tex.getWidth(); + ImGui.image(tex.getTexID(), preview, preview * aspect); + } + } + } + + private static String formatName(final int format) { + if (format == ImTextureFormat.RGBA32) { + return "RGBA32"; + } else if (format == ImTextureFormat.Alpha8) { + return "Alpha8"; + } + return "Unknown(" + format + ")"; + } + + private static String statusName(final int status) { + if (status == ImTextureStatus.OK) { + return "OK"; + } else if (status == ImTextureStatus.WantCreate) { + return "WantCreate"; + } else if (status == ImTextureStatus.WantUpdates) { + return "WantUpdates"; + } else if (status == ImTextureStatus.WantDestroy) { + return "WantDestroy"; + } else if (status == ImTextureStatus.Destroyed) { + return "Destroyed"; + } + return "Unknown(" + status + ")"; + } + private static byte[] loadFromResources(String name) { try { return Files.readAllBytes(Paths.get(Main.class.getResource(name).toURI())); diff --git a/imgui-binding/src/generated/java/imgui/ImDrawData.java b/imgui-binding/src/generated/java/imgui/ImDrawData.java index 75e71898..98829032 100644 --- a/imgui-binding/src/generated/java/imgui/ImDrawData.java +++ b/imgui-binding/src/generated/java/imgui/ImDrawData.java @@ -17,6 +17,8 @@ public final class ImDrawData extends ImGuiStruct { private static final int RESIZE_FACTOR = 5_000; private static ByteBuffer dataBuffer = ByteBuffer.allocateDirect(25_000).order(ByteOrder.nativeOrder()); + private static final ImTextureData _GETTEXTURES_1 = new ImTextureData(0); + public ImDrawData(final long ptr) { super(ptr); } @@ -134,6 +136,30 @@ public ByteBuffer getCmdListVtxBufferData(final int cmdListIdx) { memcpy(vtxBuffer, THIS->CmdLists[cmdListIdx]->VtxBuffer.Data, vtxBufferCapacity); */ + /** + * Number of textures to update before rendering this draw data. Most of the time the list is shared by all + * ImDrawData, has only one texture and does not need any update. This almost always mirrors + * {@link ImGuiPlatformIO#getTexturesSize()}. May be zero if texture updates are disabled (list set to NULL). + */ + public native int getTexturesSize(); /* + return THIS->Textures != NULL ? THIS->Textures->Size : 0; + */ + + /** + * Texture to update at the given index. The returned value is a shared instance, valid only until the next call to + * this method. + * + * @param idx index in {@code [0, getTexturesSize())} + */ + public ImTextureData getTextures(final int idx) { + _GETTEXTURES_1.ptr = nGetTextures(idx); + return _GETTEXTURES_1; + } + + private native long nGetTextures(int idx); /* + return (uintptr_t)(*THIS->Textures)[idx]; + */ + public static native int sizeOfImDrawVert(); /* return (int)sizeof(ImDrawVert); */ diff --git a/imgui-binding/src/generated/java/imgui/ImGuiPlatformIO.java b/imgui-binding/src/generated/java/imgui/ImGuiPlatformIO.java index 4dd498d5..31c254f0 100644 --- a/imgui-binding/src/generated/java/imgui/ImGuiPlatformIO.java +++ b/imgui-binding/src/generated/java/imgui/ImGuiPlatformIO.java @@ -73,6 +73,7 @@ public final class ImGuiPlatformIO extends ImGuiStruct { private static final ImGuiViewport TMP_VIEWPORT = new ImGuiViewport(0); private static final ImGuiPlatformMonitor TMP_MONITOR = new ImGuiPlatformMonitor(0); private static final ImVec2 TMP_IM_VEC2 = new ImVec2(); + private static final ImTextureData TMP_TEXTURE_DATA = new ImTextureData(0); public ImGuiPlatformIO(final long ptr) { super(ptr); @@ -545,4 +546,63 @@ public ImGuiViewport getViewports(final int idx) { private native long nGetViewports(int idx); /* return (uintptr_t)IMGUI_PLATFORM_IO->Viewports[idx]; */ + + //------------------------------------------------------------------ + // Textures list (updated by dear imgui, honored by the renderer backend) + //------------------------------------------------------------------ + + /** + * Maximum texture width the renderer backend supports. Set by the renderer backend during initialization so the + * core library can split the font atlas accordingly. Zero means unbounded. + */ + public native int getRendererTextureMaxWidth(); /* + return IMGUI_PLATFORM_IO->Renderer_TextureMaxWidth; + */ + + /** + * Maximum texture width the renderer backend supports. Set by the renderer backend during initialization so the + * core library can split the font atlas accordingly. Zero means unbounded. + */ + public native void setRendererTextureMaxWidth(int value); /* + IMGUI_PLATFORM_IO->Renderer_TextureMaxWidth = value; + */ + + /** + * Maximum texture height the renderer backend supports. Set by the renderer backend during initialization so the + * core library can split the font atlas accordingly. Zero means unbounded. + */ + public native int getRendererTextureMaxHeight(); /* + return IMGUI_PLATFORM_IO->Renderer_TextureMaxHeight; + */ + + /** + * Maximum texture height the renderer backend supports. Set by the renderer backend during initialization so the + * core library can split the font atlas accordingly. Zero means unbounded. + */ + public native void setRendererTextureMaxHeight(int value); /* + IMGUI_PLATFORM_IO->Renderer_TextureMaxHeight = value; + */ + + /** + * Number of textures used by Dear ImGui (most often one). The renderer backend iterates this list to + * create/update/destroy textures. + */ + public native int getTexturesSize(); /* + return IMGUI_PLATFORM_IO->Textures.Size; + */ + + /** + * Texture at the given index. The returned value is a shared instance, valid only until the next call to this + * method. + * + * @param idx index in {@code [0, getTexturesSize())} + */ + public ImTextureData getTextures(final int idx) { + TMP_TEXTURE_DATA.ptr = nGetTextures(idx); + return TMP_TEXTURE_DATA; + } + + private native long nGetTextures(int idx); /* + return (uintptr_t)IMGUI_PLATFORM_IO->Textures[idx]; + */ } diff --git a/imgui-binding/src/generated/java/imgui/ImTextureData.java b/imgui-binding/src/generated/java/imgui/ImTextureData.java new file mode 100644 index 00000000..7f7b5253 --- /dev/null +++ b/imgui-binding/src/generated/java/imgui/ImTextureData.java @@ -0,0 +1,311 @@ +package imgui; + +import imgui.binding.ImGuiStruct; +import imgui.flag.ImTextureFormat; +import imgui.flag.ImTextureStatus; + +import java.nio.ByteBuffer; + + +/** + * Specs and pixel storage for a texture used by Dear ImGui. + *

+ * This is only useful for (1) core library and (2) renderer backends. End-user/applications do not need to care about + * this. Renderer Backends will create a GPU-side version of this and communicate back through {@link #setTexID(long)} + * and {@link #setStatus(int)}. + *

+ * A texture stores two identifiers: {@code TexID} (the lower-level backend identifier stored in draw commands) and + * {@code BackendUserData} (higher-level opaque storage for the backend's own book-keeping). + */ +public final class ImTextureData extends ImGuiStruct { + private static final ImTextureRect TMP_USED_RECT = new ImTextureRect(0); + private static final ImTextureRect TMP_UPDATE_RECT = new ImTextureRect(0); + private static final ImTextureRect TMP_UPDATE = new ImTextureRect(0); + + public ImTextureData(final long ptr) { + super(ptr); + } + + /*JNI + #include "_common.h" + #define THIS ((ImTextureData*)STRUCT_PTR) + */ + + + /** + * [DEBUG] Sequential index to facilitate identifying a texture when debugging/printing. Unique per atlas. + */ + public int getUniqueId() { + return nGetUniqueId(); + } + + private native int nGetUniqueId(); /* + return THIS->UniqueID; + */ + + + + /** + * Texture status, one of the {@link ImTextureStatus} values. Always use {@link #setStatus(int)} to modify! + */ + public int getStatus() { + return nGetStatus(); + } + + private native int nGetStatus(); /* + return THIS->Status; + */ + + /** + * Set the texture status. Called by the renderer backend after honoring a texture request. + * + * @param status + * one of the {@link ImTextureStatus} values + */ + public void setStatus(final int status) { + nSetStatus(status); + } + + private native void nSetStatus(int status); /* + THIS->SetStatus(static_cast(status)); + */ + + /** + * Convenience storage for the backend. Some backends may have enough with {@code TexID}. + */ + public native long getBackendUserData(); /* + return (uintptr_t)THIS->BackendUserData; + */ + + /** + * Convenience storage for the backend. Some backends may have enough with {@code TexID}. + */ + public native void setBackendUserData(long backendUserData); /* + THIS->BackendUserData = (void*)backendUserData; + */ + + /** + * Backend-specific texture identifier, stored in {@code ImDrawCmd::GetTexID()} and passed to the backend's render + * function. + */ + public native long getTexID(); /* + return (uintptr_t)THIS->TexID; + */ + + /** + * Set the backend-specific texture identifier. Called by the renderer backend after uploading the texture. + */ + public void setTexID(final long texID) { + nSetTexID(texID); + } + + private native void nSetTexID(long texID); /* + THIS->SetTexID(texID); + */ + + /** + * Texture format, one of the {@link ImTextureFormat} values. + */ + public int getFormat() { + return nGetFormat(); + } + + private native int nGetFormat(); /* + return THIS->Format; + */ + + + /** + * Texture width, in pixels. + */ + public int getWidth() { + return nGetWidth(); + } + + private native int nGetWidth(); /* + return THIS->Width; + */ + + /** + * Texture height, in pixels. + */ + public int getHeight() { + return nGetHeight(); + } + + private native int nGetHeight(); /* + return THIS->Height; + */ + + + /** + * Bytes per pixel: 4 for {@code ImTextureFormat_RGBA32} or 1 for {@code ImTextureFormat_Alpha8}. + */ + public int getBytesPerPixel() { + return nGetBytesPerPixel(); + } + + private native int nGetBytesPerPixel(); /* + return THIS->BytesPerPixel; + */ + + /** + * Direct view over the CPU-side pixel buffer holding {@code Width*Height} pixels + * ({@code Width*Height*BytesPerPixel} bytes). The returned buffer wraps the native memory directly (no copy), so it + * is only valid while the texture keeps its pixels (i.e. before {@code DestroyPixels}). + * + * @return a direct {@link ByteBuffer} over the pixel data, or {@code null} if the pixels have been freed + */ + public native ByteBuffer getPixels(); /* + if (THIS->Pixels == NULL) { + return NULL; + } + return env->NewDirectByteBuffer(THIS->Pixels, THIS->GetSizeInBytes()); + */ + + /** + * Total size of the pixel buffer in bytes ({@code Width*Height*BytesPerPixel}). + */ + public int getSizeInBytes() { + return nGetSizeInBytes(); + } + + private native int nGetSizeInBytes(); /* + return THIS->GetSizeInBytes(); + */ + + /** + * Row pitch in bytes ({@code Width*BytesPerPixel}). + */ + public int getPitch() { + return nGetPitch(); + } + + private native int nGetPitch(); /* + return THIS->GetPitch(); + */ + + /** + * Bounding box encompassing all past and queued updates. + */ + public ImTextureRect getUsedRect() { + TMP_USED_RECT.ptr = nGetUsedRect(); + return TMP_USED_RECT; + } + + private native long nGetUsedRect(); /* + return (uintptr_t)&THIS->UsedRect; + */ + + /** + * Bounding box encompassing all queued updates. + */ + public ImTextureRect getUpdateRect() { + TMP_UPDATE_RECT.ptr = nGetUpdateRect(); + return TMP_UPDATE_RECT; + } + + private native long nGetUpdateRect(); /* + return (uintptr_t)&THIS->UpdateRect; + */ + + /** + * Number of individual update rectangles queued for this texture. + */ + public native int getUpdatesSize(); /* + return THIS->Updates.Size; + */ + + /** + * Individual update rectangle at the given index. The returned value is a shared instance, valid only until the + * next call to this method. + * + * @param idx index in {@code [0, getUpdatesSize())} + */ + public ImTextureRect getUpdates(final int idx) { + TMP_UPDATE.ptr = nGetUpdates(idx); + return TMP_UPDATE; + } + + private native long nGetUpdates(int idx); /* + return (uintptr_t)&THIS->Updates[idx]; + */ + + /** + * Count of successive frames where the texture was not used. Always {@code > 0} when the status is + * {@code ImTextureStatus_WantDestroy}. + */ + public int getUnusedFrames() { + return nGetUnusedFrames(); + } + + private native int nGetUnusedFrames(); /* + return THIS->UnusedFrames; + */ + + /** + * Number of contexts using this texture. Used during backend shutdown. + */ + public int getRefCount() { + return nGetRefCount(); + } + + private native int nGetRefCount(); /* + return THIS->RefCount; + */ + + /** + * Whether the texture data is known to use colors (rather than just white + alpha). + */ + public boolean getUseColors() { + return nGetUseColors(); + } + + private native boolean nGetUseColors(); /* + return THIS->UseColors; + */ + + /** + * [Internal] Whether the texture is queued to be destroyed next frame. May still be used in the current frame. + */ + public boolean getWantDestroyNextFrame() { + return nGetWantDestroyNextFrame(); + } + + private native boolean nGetWantDestroyNextFrame(); /* + return THIS->WantDestroyNextFrame; + */ + + /** + * Allocate the CPU-side pixel buffer for the given format and size. Generally called by the core library. + * + * @param format + * one of the {@link ImTextureFormat} values + * @param width + * texture width in pixels + * @param height + * texture height in pixels + */ + public void create(final int format, final int width, final int height) { + nCreate(format, width, height); + } + + private native void nCreate(int format, int width, int height); /* + THIS->Create(static_cast(format), width, height); + */ + + /** + * Free the CPU-side pixel buffer. + */ + public void destroyPixels() { + nDestroyPixels(); + } + + private native void nDestroyPixels(); /* + THIS->DestroyPixels(); + */ + + /*JNI + #undef THIS + */ +} diff --git a/imgui-binding/src/generated/java/imgui/ImTextureRect.java b/imgui-binding/src/generated/java/imgui/ImTextureRect.java new file mode 100644 index 00000000..5b500687 --- /dev/null +++ b/imgui-binding/src/generated/java/imgui/ImTextureRect.java @@ -0,0 +1,70 @@ +package imgui; + +import imgui.binding.ImGuiStruct; + + +/** + * Coordinates of a rectangle within a texture. + *

+ * When a texture is in {@code ImTextureStatus_WantUpdates} state, we provide a list of individual rectangles to copy to + * the graphics system. You may use {@link ImTextureData#getUpdates(int)} for the list, or + * {@link ImTextureData#getUpdateRect()} for a single bounding box. + */ +public final class ImTextureRect extends ImGuiStruct { + public ImTextureRect(final long ptr) { + super(ptr); + } + + /*JNI + #include "_common.h" + #define THIS ((ImTextureRect*)STRUCT_PTR) + */ + + /** + * Upper-left x coordinate of rectangle + */ + public int getX() { + return nGetX(); + } + + private native int nGetX(); /* + return THIS->x; + */ + + /** + * Upper-left y coordinate of rectangle + */ + public int getY() { + return nGetY(); + } + + private native int nGetY(); /* + return THIS->y; + */ + + /** + * Width of rectangle (in pixels). + */ + public int getW() { + return nGetW(); + } + + private native int nGetW(); /* + return THIS->w; + */ + + /** + * Height of rectangle (in pixels). + */ + public int getH() { + return nGetH(); + } + + private native int nGetH(); /* + return THIS->h; + */ + + /*JNI + #undef THIS + */ +} diff --git a/imgui-binding/src/generated/java/imgui/extension/texteditor/TextEditor.java b/imgui-binding/src/generated/java/imgui/extension/texteditor/TextEditor.java index 1cb067b5..445fbe4a 100644 --- a/imgui-binding/src/generated/java/imgui/extension/texteditor/TextEditor.java +++ b/imgui-binding/src/generated/java/imgui/extension/texteditor/TextEditor.java @@ -2,16 +2,10 @@ import imgui.ImVec2; import imgui.binding.ImGuiStructDestroyable; - - - - - import imgui.extension.texteditor.flag.TextEditorColor; import imgui.extension.texteditor.flag.TextEditorScroll; import imgui.internal.ImGuiContext; - public final class TextEditor extends ImGuiStructDestroyable { public TextEditor() { super(); diff --git a/imgui-binding/src/generated/java/imgui/extension/texteditor/TextEditorLanguage.java b/imgui-binding/src/generated/java/imgui/extension/texteditor/TextEditorLanguage.java index 401d75f1..b8b759ab 100644 --- a/imgui-binding/src/generated/java/imgui/extension/texteditor/TextEditorLanguage.java +++ b/imgui-binding/src/generated/java/imgui/extension/texteditor/TextEditorLanguage.java @@ -2,11 +2,6 @@ import imgui.binding.ImGuiStruct; - - - - - public final class TextEditorLanguage extends ImGuiStruct { public TextEditorLanguage(final long ptr) { super(ptr); diff --git a/imgui-binding/src/generated/java/imgui/flag/ImTextureFormat.java b/imgui-binding/src/generated/java/imgui/flag/ImTextureFormat.java new file mode 100644 index 00000000..49c9ea9d --- /dev/null +++ b/imgui-binding/src/generated/java/imgui/flag/ImTextureFormat.java @@ -0,0 +1,21 @@ +package imgui.flag; + + +/** + * We intentionally support a limited amount of texture formats to limit burden on CPU-side code and extension. + * Most standard backends only support RGBA32 but we provide a single channel option for low-resource/embedded systems. + */ +public final class ImTextureFormat { + private ImTextureFormat() { + } + + /** + * 4 components per pixel, each is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 + */ + public static final int RGBA32 = 0; + + /** + * 1 component per pixel, each is unsigned 8-bit. Total size = TexWidth * TexHeight + */ + public static final int Alpha8 = 1; +} diff --git a/imgui-binding/src/generated/java/imgui/flag/ImTextureStatus.java b/imgui-binding/src/generated/java/imgui/flag/ImTextureStatus.java new file mode 100644 index 00000000..c8a7c902 --- /dev/null +++ b/imgui-binding/src/generated/java/imgui/flag/ImTextureStatus.java @@ -0,0 +1,32 @@ +package imgui.flag; + + +/** + * Status of a texture to communicate with Renderer Backend. + */ +public final class ImTextureStatus { + private ImTextureStatus() { + } + + public static final int OK = 0; + + /** + * Backend destroyed the texture. + */ + public static final int Destroyed = 1; + + /** + * Requesting backend to create the texture. Set status OK when done. + */ + public static final int WantCreate = 2; + + /** + * Requesting backend to update specific blocks of pixels (write to texture portions which have never been used before). Set status OK when done. + */ + public static final int WantUpdates = 3; + + /** + * Requesting backend to destroy the texture. Set status to Destroyed when done. + */ + public static final int WantDestroy = 4; +} diff --git a/imgui-binding/src/main/java/imgui/ImDrawData.java b/imgui-binding/src/main/java/imgui/ImDrawData.java index d52b332f..4affff89 100644 --- a/imgui-binding/src/main/java/imgui/ImDrawData.java +++ b/imgui-binding/src/main/java/imgui/ImDrawData.java @@ -21,6 +21,8 @@ public final class ImDrawData extends ImGuiStruct { private static final int RESIZE_FACTOR = 5_000; private static ByteBuffer dataBuffer = ByteBuffer.allocateDirect(25_000).order(ByteOrder.nativeOrder()); + private static final ImTextureData _GETTEXTURES_1 = new ImTextureData(0); + public ImDrawData(final long ptr) { super(ptr); } @@ -138,6 +140,30 @@ public ByteBuffer getCmdListVtxBufferData(final int cmdListIdx) { memcpy(vtxBuffer, THIS->CmdLists[cmdListIdx]->VtxBuffer.Data, vtxBufferCapacity); */ + /** + * Number of textures to update before rendering this draw data. Most of the time the list is shared by all + * ImDrawData, has only one texture and does not need any update. This almost always mirrors + * {@link ImGuiPlatformIO#getTexturesSize()}. May be zero if texture updates are disabled (list set to NULL). + */ + public native int getTexturesSize(); /* + return THIS->Textures != NULL ? THIS->Textures->Size : 0; + */ + + /** + * Texture to update at the given index. The returned value is a shared instance, valid only until the next call to + * this method. + * + * @param idx index in {@code [0, getTexturesSize())} + */ + public ImTextureData getTextures(final int idx) { + _GETTEXTURES_1.ptr = nGetTextures(idx); + return _GETTEXTURES_1; + } + + private native long nGetTextures(int idx); /* + return (uintptr_t)(*THIS->Textures)[idx]; + */ + public static native int sizeOfImDrawVert(); /* return (int)sizeof(ImDrawVert); */ diff --git a/imgui-binding/src/main/java/imgui/ImGuiPlatformIO.java b/imgui-binding/src/main/java/imgui/ImGuiPlatformIO.java index 4dd498d5..31c254f0 100644 --- a/imgui-binding/src/main/java/imgui/ImGuiPlatformIO.java +++ b/imgui-binding/src/main/java/imgui/ImGuiPlatformIO.java @@ -73,6 +73,7 @@ public final class ImGuiPlatformIO extends ImGuiStruct { private static final ImGuiViewport TMP_VIEWPORT = new ImGuiViewport(0); private static final ImGuiPlatformMonitor TMP_MONITOR = new ImGuiPlatformMonitor(0); private static final ImVec2 TMP_IM_VEC2 = new ImVec2(); + private static final ImTextureData TMP_TEXTURE_DATA = new ImTextureData(0); public ImGuiPlatformIO(final long ptr) { super(ptr); @@ -545,4 +546,63 @@ public ImGuiViewport getViewports(final int idx) { private native long nGetViewports(int idx); /* return (uintptr_t)IMGUI_PLATFORM_IO->Viewports[idx]; */ + + //------------------------------------------------------------------ + // Textures list (updated by dear imgui, honored by the renderer backend) + //------------------------------------------------------------------ + + /** + * Maximum texture width the renderer backend supports. Set by the renderer backend during initialization so the + * core library can split the font atlas accordingly. Zero means unbounded. + */ + public native int getRendererTextureMaxWidth(); /* + return IMGUI_PLATFORM_IO->Renderer_TextureMaxWidth; + */ + + /** + * Maximum texture width the renderer backend supports. Set by the renderer backend during initialization so the + * core library can split the font atlas accordingly. Zero means unbounded. + */ + public native void setRendererTextureMaxWidth(int value); /* + IMGUI_PLATFORM_IO->Renderer_TextureMaxWidth = value; + */ + + /** + * Maximum texture height the renderer backend supports. Set by the renderer backend during initialization so the + * core library can split the font atlas accordingly. Zero means unbounded. + */ + public native int getRendererTextureMaxHeight(); /* + return IMGUI_PLATFORM_IO->Renderer_TextureMaxHeight; + */ + + /** + * Maximum texture height the renderer backend supports. Set by the renderer backend during initialization so the + * core library can split the font atlas accordingly. Zero means unbounded. + */ + public native void setRendererTextureMaxHeight(int value); /* + IMGUI_PLATFORM_IO->Renderer_TextureMaxHeight = value; + */ + + /** + * Number of textures used by Dear ImGui (most often one). The renderer backend iterates this list to + * create/update/destroy textures. + */ + public native int getTexturesSize(); /* + return IMGUI_PLATFORM_IO->Textures.Size; + */ + + /** + * Texture at the given index. The returned value is a shared instance, valid only until the next call to this + * method. + * + * @param idx index in {@code [0, getTexturesSize())} + */ + public ImTextureData getTextures(final int idx) { + TMP_TEXTURE_DATA.ptr = nGetTextures(idx); + return TMP_TEXTURE_DATA; + } + + private native long nGetTextures(int idx); /* + return (uintptr_t)IMGUI_PLATFORM_IO->Textures[idx]; + */ } diff --git a/imgui-binding/src/main/java/imgui/ImTextureData.java b/imgui-binding/src/main/java/imgui/ImTextureData.java new file mode 100644 index 00000000..9350055e --- /dev/null +++ b/imgui-binding/src/main/java/imgui/ImTextureData.java @@ -0,0 +1,233 @@ +package imgui; + +import imgui.binding.ImGuiStruct; +import imgui.binding.annotation.ArgValue; +import imgui.binding.annotation.BindingField; +import imgui.binding.annotation.BindingMethod; +import imgui.binding.annotation.BindingSource; +import imgui.flag.ImTextureFormat; +import imgui.flag.ImTextureStatus; + +import java.nio.ByteBuffer; + +import static imgui.binding.annotation.BindingField.Accessor.*; + +/** + * Specs and pixel storage for a texture used by Dear ImGui. + *

+ * This is only useful for (1) core library and (2) renderer backends. End-user/applications do not need to care about + * this. Renderer Backends will create a GPU-side version of this and communicate back through {@link #setTexID(long)} + * and {@link #setStatus(int)}. + *

+ * A texture stores two identifiers: {@code TexID} (the lower-level backend identifier stored in draw commands) and + * {@code BackendUserData} (higher-level opaque storage for the backend's own book-keeping). + */ +@BindingSource +public final class ImTextureData extends ImGuiStruct { + private static final ImTextureRect TMP_USED_RECT = new ImTextureRect(0); + private static final ImTextureRect TMP_UPDATE_RECT = new ImTextureRect(0); + private static final ImTextureRect TMP_UPDATE = new ImTextureRect(0); + + public ImTextureData(final long ptr) { + super(ptr); + } + + /*JNI + #include "_common.h" + #define THIS ((ImTextureData*)STRUCT_PTR) + */ + + + /** + * [DEBUG] Sequential index to facilitate identifying a texture when debugging/printing. Unique per atlas. + */ + @BindingField(accessors = GETTER, callName = "UniqueID") + public int UniqueId; + + + + /** + * Texture status, one of the {@link ImTextureStatus} values. Always use {@link #setStatus(int)} to modify! + */ + @BindingField(accessors = GETTER) + public int Status; + + /** + * Set the texture status. Called by the renderer backend after honoring a texture request. + * + * @param status one of the {@link ImTextureStatus} values + */ + @BindingMethod(callName = "SetStatus") + public native void setStatus(@ArgValue(staticCast = "ImTextureStatus") int status); + + /** + * Convenience storage for the backend. Some backends may have enough with {@code TexID}. + */ + public native long getBackendUserData(); /* + return (uintptr_t)THIS->BackendUserData; + */ + + /** + * Convenience storage for the backend. Some backends may have enough with {@code TexID}. + */ + public native void setBackendUserData(long backendUserData); /* + THIS->BackendUserData = (void*)backendUserData; + */ + + /** + * Backend-specific texture identifier, stored in {@code ImDrawCmd::GetTexID()} and passed to the backend's render + * function. + */ + public native long getTexID(); /* + return (uintptr_t)THIS->TexID; + */ + + /** + * Set the backend-specific texture identifier. Called by the renderer backend after uploading the texture. + */ + @BindingMethod(callName = "SetTexID") + public native void setTexID(long texID); + + /** + * Texture format, one of the {@link ImTextureFormat} values. + */ + @BindingField(accessors = GETTER) + public int Format; + + + /** + * Texture width, in pixels. + */ + @BindingField(accessors = GETTER) + public int Width; + + /** + * Texture height, in pixels. + */ + @BindingField(accessors = GETTER) + public int Height; + + + /** + * Bytes per pixel: 4 for {@code ImTextureFormat_RGBA32} or 1 for {@code ImTextureFormat_Alpha8}. + */ + @BindingField(accessors = GETTER) + public int BytesPerPixel; + + /** + * Direct view over the CPU-side pixel buffer holding {@code Width*Height} pixels + * ({@code Width*Height*BytesPerPixel} bytes). The returned buffer wraps the native memory directly (no copy), so it + * is only valid while the texture keeps its pixels (i.e. before {@code DestroyPixels}). + * + * @return a direct {@link ByteBuffer} over the pixel data, or {@code null} if the pixels have been freed + */ + public native ByteBuffer getPixels(); /* + if (THIS->Pixels == NULL) { + return NULL; + } + return env->NewDirectByteBuffer(THIS->Pixels, THIS->GetSizeInBytes()); + */ + + /** + * Total size of the pixel buffer in bytes ({@code Width*Height*BytesPerPixel}). + */ + @BindingMethod(callName = "GetSizeInBytes") + public native int getSizeInBytes(); + + /** + * Row pitch in bytes ({@code Width*BytesPerPixel}). + */ + @BindingMethod(callName = "GetPitch") + public native int getPitch(); + + /** + * Bounding box encompassing all past and queued updates. + */ + public ImTextureRect getUsedRect() { + TMP_USED_RECT.ptr = nGetUsedRect(); + return TMP_USED_RECT; + } + + private native long nGetUsedRect(); /* + return (uintptr_t)&THIS->UsedRect; + */ + + /** + * Bounding box encompassing all queued updates. + */ + public ImTextureRect getUpdateRect() { + TMP_UPDATE_RECT.ptr = nGetUpdateRect(); + return TMP_UPDATE_RECT; + } + + private native long nGetUpdateRect(); /* + return (uintptr_t)&THIS->UpdateRect; + */ + + /** + * Number of individual update rectangles queued for this texture. + */ + public native int getUpdatesSize(); /* + return THIS->Updates.Size; + */ + + /** + * Individual update rectangle at the given index. The returned value is a shared instance, valid only until the + * next call to this method. + * + * @param idx index in {@code [0, getUpdatesSize())} + */ + public ImTextureRect getUpdates(final int idx) { + TMP_UPDATE.ptr = nGetUpdates(idx); + return TMP_UPDATE; + } + + private native long nGetUpdates(int idx); /* + return (uintptr_t)&THIS->Updates[idx]; + */ + + /** + * Count of successive frames where the texture was not used. Always {@code > 0} when the status is + * {@code ImTextureStatus_WantDestroy}. + */ + @BindingField(accessors = GETTER) + public int UnusedFrames; + + /** + * Number of contexts using this texture. Used during backend shutdown. + */ + @BindingField(accessors = GETTER) + public int RefCount; + + /** + * Whether the texture data is known to use colors (rather than just white + alpha). + */ + @BindingField(accessors = GETTER) + public boolean UseColors; + + /** + * [Internal] Whether the texture is queued to be destroyed next frame. May still be used in the current frame. + */ + @BindingField(accessors = GETTER) + public boolean WantDestroyNextFrame; + + /** + * Allocate the CPU-side pixel buffer for the given format and size. Generally called by the core library. + * + * @param format one of the {@link ImTextureFormat} values + * @param width texture width in pixels + * @param height texture height in pixels + */ + @BindingMethod(callName = "Create") + public native void create(@ArgValue(staticCast = "ImTextureFormat") int format, int width, int height); + + /** + * Free the CPU-side pixel buffer. + */ + @BindingMethod(callName = "DestroyPixels") + public native void destroyPixels(); + + /*JNI + #undef THIS + */ +} diff --git a/imgui-binding/src/main/java/imgui/ImTextureRect.java b/imgui-binding/src/main/java/imgui/ImTextureRect.java new file mode 100644 index 00000000..cfb479ac --- /dev/null +++ b/imgui-binding/src/main/java/imgui/ImTextureRect.java @@ -0,0 +1,54 @@ +package imgui; + +import imgui.binding.ImGuiStruct; +import imgui.binding.annotation.BindingField; +import imgui.binding.annotation.BindingSource; + +import static imgui.binding.annotation.BindingField.Accessor.*; + +/** + * Coordinates of a rectangle within a texture. + *

+ * When a texture is in {@code ImTextureStatus_WantUpdates} state, we provide a list of individual rectangles to copy to + * the graphics system. You may use {@link ImTextureData#getUpdates(int)} for the list, or + * {@link ImTextureData#getUpdateRect()} for a single bounding box. + */ +@BindingSource +public final class ImTextureRect extends ImGuiStruct { + public ImTextureRect(final long ptr) { + super(ptr); + } + + /*JNI + #include "_common.h" + #define THIS ((ImTextureRect*)STRUCT_PTR) + */ + + /** + * Upper-left x coordinate of rectangle + */ + @BindingField(accessors = GETTER, callName = "x") + public int X; + + /** + * Upper-left y coordinate of rectangle + */ + @BindingField(accessors = GETTER, callName = "y") + public int Y; + + /** + * Width of rectangle (in pixels). + */ + @BindingField(accessors = GETTER, callName = "w") + public int W; + + /** + * Height of rectangle (in pixels). + */ + @BindingField(accessors = GETTER, callName = "h") + public int H; + + /*JNI + #undef THIS + */ +} diff --git a/imgui-binding/src/main/java/imgui/flag/ImTextureFormat.java b/imgui-binding/src/main/java/imgui/flag/ImTextureFormat.java new file mode 100644 index 00000000..f7b5dcc4 --- /dev/null +++ b/imgui-binding/src/main/java/imgui/flag/ImTextureFormat.java @@ -0,0 +1,17 @@ +package imgui.flag; + +import imgui.binding.annotation.BindingAstEnum; +import imgui.binding.annotation.BindingSource; + +/** + * We intentionally support a limited amount of texture formats to limit burden on CPU-side code and extension. + * Most standard backends only support RGBA32 but we provide a single channel option for low-resource/embedded systems. + */ +@BindingSource +public final class ImTextureFormat { + private ImTextureFormat() { + } + + @BindingAstEnum(file = "ast-imgui.json", qualType = "ImTextureFormat", sanitizeName = "ImTextureFormat_") + public Void __; +} diff --git a/imgui-binding/src/main/java/imgui/flag/ImTextureStatus.java b/imgui-binding/src/main/java/imgui/flag/ImTextureStatus.java new file mode 100644 index 00000000..80aed169 --- /dev/null +++ b/imgui-binding/src/main/java/imgui/flag/ImTextureStatus.java @@ -0,0 +1,16 @@ +package imgui.flag; + +import imgui.binding.annotation.BindingAstEnum; +import imgui.binding.annotation.BindingSource; + +/** + * Status of a texture to communicate with Renderer Backend. + */ +@BindingSource +public final class ImTextureStatus { + private ImTextureStatus() { + } + + @BindingAstEnum(file = "ast-imgui.json", qualType = "ImTextureStatus", sanitizeName = "ImTextureStatus_") + public Void __; +} diff --git a/imgui-lwjgl3/src/main/java/imgui/gl3/ImGuiImplGl3.java b/imgui-lwjgl3/src/main/java/imgui/gl3/ImGuiImplGl3.java index 7a135a84..0b491252 100644 --- a/imgui-lwjgl3/src/main/java/imgui/gl3/ImGuiImplGl3.java +++ b/imgui-lwjgl3/src/main/java/imgui/gl3/ImGuiImplGl3.java @@ -4,12 +4,16 @@ import imgui.ImFontAtlas; import imgui.ImGui; import imgui.ImGuiIO; +import imgui.ImGuiPlatformIO; import imgui.ImGuiViewport; +import imgui.ImTextureData; +import imgui.ImTextureRect; import imgui.ImVec4; import imgui.callback.ImPlatformFuncViewport; import imgui.flag.ImGuiBackendFlags; import imgui.flag.ImGuiConfigFlags; import imgui.flag.ImGuiViewportFlags; +import imgui.flag.ImTextureStatus; import imgui.type.ImInt; import org.lwjgl.opengl.GL; import org.lwjgl.opengl.GLCapabilities; @@ -130,6 +134,7 @@ import static org.lwjgl.opengl.GL32.glShaderSource; import static org.lwjgl.opengl.GL32.glTexImage2D; import static org.lwjgl.opengl.GL32.glTexParameteri; +import static org.lwjgl.opengl.GL32.glTexSubImage2D; import static org.lwjgl.opengl.GL32.glUniform1i; import static org.lwjgl.opengl.GL32.glUniformMatrix4fv; import static org.lwjgl.opengl.GL32.glUseProgram; @@ -316,15 +321,17 @@ 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 honor ImGuiPlatformIO::Textures[] requests during render: the renderer drives per-frame + // ImTextureData create/update/destroy (see updateTexture / renderDrawData). + io.addBackendFlags(ImGuiBackendFlags.RendererHasTextures); // 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. + // Advertise the maximum texture size so the core library can split large font atlases accordingly. + final ImGuiPlatformIO platformIO = ImGui.getPlatformIO(); + platformIO.setRendererTextureMaxWidth(data.maxTextureSize); + platformIO.setRendererTextureMaxHeight(data.maxTextureSize); if (glslVersion == null) { if (IS_APPLE) { @@ -365,9 +372,9 @@ 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); + // In C++: platform_io.ClearRendererHandlers() also runs here. + // In Java: ClearRendererHandlers is not exposed in imgui-binding (follow-up). + io.removeBackendFlags(ImGuiBackendFlags.RendererHasVtxOffset | ImGuiBackendFlags.RendererHasTextures | ImGuiBackendFlags.RendererHasViewports); data = null; } @@ -375,9 +382,6 @@ public void newFrame() { if (data.shaderHandle == 0) { createDeviceObjects(); } - if (data.fontTexture == 0) { - createFontsTexture(); - } } protected void setupRenderState(final ImDrawData drawData, final int fbWidth, final int fbHeight, final int gVertexArrayObject) { @@ -466,14 +470,20 @@ public void renderDrawData(final ImDrawData drawData) { return; } + // Catch up with texture updates. Most of the time the list has one element with an OK status (nothing to do). + // This almost always mirrors ImGui::GetPlatformIO().Textures[], but is carried on ImDrawData to allow + // overriding or disabling texture updates (in which case getTexturesSize() returns 0). + for (int i = 0; i < drawData.getTexturesSize(); i++) { + final ImTextureData tex = drawData.getTextures(i); + if (tex.getStatus() != ImTextureStatus.OK) { + updateTexture(tex); + } + } + if (drawData.getCmdListsCount() <= 0) { 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); @@ -622,8 +632,106 @@ public void renderDrawData(final ImDrawData drawData) { 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). + /** + * Honor a single texture request coming from Dear ImGui. Called for every {@link ImTextureData} whose status is not + * {@code ImTextureStatus_OK} at the start of {@link #renderDrawData(ImDrawData)}. Creates, updates or destroys the + * backing OpenGL texture and reports the new state back to the core library. + * + * @param tex the texture data to reconcile with the GPU + */ + public void updateTexture(final ImTextureData tex) { + final int status = tex.getStatus(); + + // Backup GL_UNPACK state that we modify, restore on exit (mirrors the C++ backend). + final int[] lastUnpackRowLength = new int[1]; + final int[] lastUnpackAlignment = new int[1]; + final boolean unpackStateSaveAndRestore = status == ImTextureStatus.WantCreate || status == ImTextureStatus.WantUpdates; + if (unpackStateSaveAndRestore) { + glGetIntegerv(GL_UNPACK_ROW_LENGTH, lastUnpackRowLength); // Not on WebGL/ES + glGetIntegerv(GL_UNPACK_ALIGNMENT, lastUnpackAlignment); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + } + + if (status == ImTextureStatus.WantCreate) { + // Create and upload a new texture to the graphics system. + // (Bilinear sampling is required by default. Set 'io.Fonts.Flags |= ImFontAtlasFlags_NoBakedLines' or + // 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling.) + final int[] lastTexture = new int[1]; + glGetIntegerv(GL_TEXTURE_BINDING_2D, lastTexture); + final int glTextureId = glGenTextures(); + glBindTexture(GL_TEXTURE_2D, glTextureId); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + 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_ROW_LENGTH, 0); // Not on WebGL/ES + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.getWidth(), tex.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, tex.getPixels()); + + // Store identifiers back into the shared ImTextureData so draw commands resolve to this texture. + tex.setTexID(glTextureId); + tex.setStatus(ImTextureStatus.OK); + + glBindTexture(GL_TEXTURE_2D, lastTexture[0]); + } else if (status == ImTextureStatus.WantUpdates) { + // Update selected blocks. We only ever write to texture regions which have never been used before. + // This backend uses tex.getUpdates(), but tex.getUpdateRect() could be used to upload a single region. + final int[] lastTexture = new int[1]; + glGetIntegerv(GL_TEXTURE_BINDING_2D, lastTexture); + glBindTexture(GL_TEXTURE_2D, (int) tex.getTexID()); + + final int width = tex.getWidth(); + final int bytesPerPixel = tex.getBytesPerPixel(); + final ByteBuffer pixels = tex.getPixels(); + + glPixelStorei(GL_UNPACK_ROW_LENGTH, width); // Not on WebGL/ES + for (int i = 0; i < tex.getUpdatesSize(); i++) { + final ImTextureRect r = tex.getUpdates(i); + final int rx = r.getX(); + final int ry = r.getY(); + final int rw = r.getW(); + final int rh = r.getH(); + // Mirror tex->GetPixelsAt(r.x, r.y): point the buffer at the region's upper-left pixel; + // UNPACK_ROW_LENGTH handles the source stride. + pixels.position((rx + ry * width) * bytesPerPixel); + glTexSubImage2D(GL_TEXTURE_2D, 0, rx, ry, rw, rh, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + } + + tex.setStatus(ImTextureStatus.OK); + glBindTexture(GL_TEXTURE_2D, lastTexture[0]); + } else if (status == ImTextureStatus.WantDestroy && tex.getUnusedFrames() > 0) { + destroyTexture(tex); + } + + // Restore GL_UNPACK state. + if (unpackStateSaveAndRestore) { + glPixelStorei(GL_UNPACK_ROW_LENGTH, lastUnpackRowLength[0]); + glPixelStorei(GL_UNPACK_ALIGNMENT, lastUnpackAlignment[0]); + } + } + + /** + * Destroy the OpenGL texture backing the given {@link ImTextureData} and mark it as destroyed so the core library + * may release it (or re-create it on demand). + * + * @param tex the texture data whose GPU resource should be freed + */ + public void destroyTexture(final ImTextureData tex) { + glDeleteTextures((int) tex.getTexID()); + + // Clear identifiers and mark as destroyed (allows e.g. destroying device objects while running). + tex.setTexID(0); // ImTextureID_Invalid + tex.setStatus(ImTextureStatus.Destroyed); + } + + /** + * Legacy single-texture font-atlas upload. + * + * @deprecated Since Dear ImGui 1.92 the font atlas is uploaded through {@link #updateTexture(ImTextureData)} driven + * by {@code ImGuiBackendFlags_RendererHasTextures}. This method is retained only for source compatibility and is + * no longer called by the backend. + * @return true when the texture was created + */ + @Deprecated public boolean createFontsTexture() { final ImFontAtlas fontAtlas = ImGui.getIO().getFonts(); @@ -656,6 +764,13 @@ public boolean createFontsTexture() { return true; } + /** + * Legacy font-atlas texture teardown. + * + * @deprecated Since Dear ImGui 1.92 textures are destroyed through {@link #destroyTexture(ImTextureData)}. This + * method is retained only for source compatibility and is no longer called by the backend. + */ + @Deprecated public void destroyFontsTexture() { final ImGuiIO io = ImGui.getIO(); if (data.fontTexture != 0) { @@ -773,10 +888,8 @@ 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(); + // The font texture is created lazily through updateTexture(WantCreate), driven by the per-frame texture + // iteration in renderDrawData(); nothing to upload here. // Restore modified GL state glBindTexture(GL_TEXTURE_2D, lastTexture[0]); @@ -802,9 +915,15 @@ 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(); + // Destroy all textures we still own. RefCount == 1 means this context is the last user, so it is safe to free + // the GPU resource (mirrors ImGui_ImplOpenGL3_DestroyDeviceObjects). + final ImGuiPlatformIO platformIO = ImGui.getPlatformIO(); + for (int i = 0; i < platformIO.getTexturesSize(); i++) { + final ImTextureData tex = platformIO.getTextures(i); + if (tex.getRefCount() == 1) { + destroyTexture(tex); + } + } } //--------------------------------------------------------------------------------------------------------