diff --git a/bin/libimgui-java64.so b/bin/libimgui-java64.so index 55cce7eb..8b8c2147 100644 Binary files a/bin/libimgui-java64.so and b/bin/libimgui-java64.so differ diff --git a/example/src/main/java/Main.java b/example/src/main/java/Main.java index 59822cb5..f4ae93df 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..b1b32e22 --- /dev/null +++ b/imgui-binding/src/generated/java/imgui/ImTextureData.java @@ -0,0 +1,241 @@ +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 native int getUniqueID(); /* + return THIS->UniqueID; + */ + + /** + * Texture status, one of the {@link ImTextureStatus} values. Always use {@link #setStatus(int)} to modify! + * + * @return one of the {@link ImTextureStatus} values + */ + public native int getStatus(); /* + 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 native void setStatus(int status); /* + THIS->SetStatus((ImTextureStatus)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 native void setTexID(long texID); /* + THIS->SetTexID((ImTextureID)(uintptr_t)texID); + */ + + /** + * Texture format, one of the {@link ImTextureFormat} values. + * + * @return one of the {@link ImTextureFormat} values + */ + public native int getFormat(); /* + return THIS->Format; + */ + + /** + * Texture width, in pixels. + */ + public native int getWidth(); /* + return THIS->Width; + */ + + /** + * Texture height, in pixels. + */ + public native int getHeight(); /* + return THIS->Height; + */ + + /** + * Bytes per pixel: 4 for {@code ImTextureFormat_RGBA32} or 1 for {@code ImTextureFormat_Alpha8}. + */ + public native int getBytesPerPixel(); /* + 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 native int getSizeInBytes(); /* + return THIS->GetSizeInBytes(); + */ + + /** + * Row pitch in bytes ({@code Width*BytesPerPixel}). + */ + public native int getPitch(); /* + 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 native int getUnusedFrames(); /* + return THIS->UnusedFrames; + */ + + /** + * Number of contexts using this texture. Used during backend shutdown. + */ + public native int getRefCount(); /* + return THIS->RefCount; + */ + + /** + * Whether the texture data is known to use colors (rather than just white + alpha). + */ + public native boolean getUseColors(); /* + return THIS->UseColors; + */ + + /** + * [Internal] Whether the texture is queued to be destroyed next frame. May still be used in the current frame. + */ + public native boolean getWantDestroyNextFrame(); /* + 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 native void create(int format, int width, int height); /* + THIS->Create((ImTextureFormat)format, width, height); + */ + + /** + * Free the CPU-side pixel buffer. + */ + public native void destroyPixels(); /* + 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..87fdc56f --- /dev/null +++ b/imgui-binding/src/generated/java/imgui/ImTextureRect.java @@ -0,0 +1,53 @@ +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 to update. + */ + public native int getX(); /* + return THIS->x; + */ + + /** + * Upper-left y coordinate of rectangle to update. + */ + public native int getY(); /* + return THIS->y; + */ + + /** + * Width of rectangle to update (in pixels). + */ + public native int getW(); /* + return THIS->w; + */ + + /** + * Height of rectangle to update (in pixels). + */ + public native int getH(); /* + return THIS->h; + */ + + /*JNI + #undef THIS + */ +} 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..b1b32e22 --- /dev/null +++ b/imgui-binding/src/main/java/imgui/ImTextureData.java @@ -0,0 +1,241 @@ +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 native int getUniqueID(); /* + return THIS->UniqueID; + */ + + /** + * Texture status, one of the {@link ImTextureStatus} values. Always use {@link #setStatus(int)} to modify! + * + * @return one of the {@link ImTextureStatus} values + */ + public native int getStatus(); /* + 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 native void setStatus(int status); /* + THIS->SetStatus((ImTextureStatus)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 native void setTexID(long texID); /* + THIS->SetTexID((ImTextureID)(uintptr_t)texID); + */ + + /** + * Texture format, one of the {@link ImTextureFormat} values. + * + * @return one of the {@link ImTextureFormat} values + */ + public native int getFormat(); /* + return THIS->Format; + */ + + /** + * Texture width, in pixels. + */ + public native int getWidth(); /* + return THIS->Width; + */ + + /** + * Texture height, in pixels. + */ + public native int getHeight(); /* + return THIS->Height; + */ + + /** + * Bytes per pixel: 4 for {@code ImTextureFormat_RGBA32} or 1 for {@code ImTextureFormat_Alpha8}. + */ + public native int getBytesPerPixel(); /* + 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 native int getSizeInBytes(); /* + return THIS->GetSizeInBytes(); + */ + + /** + * Row pitch in bytes ({@code Width*BytesPerPixel}). + */ + public native int getPitch(); /* + 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 native int getUnusedFrames(); /* + return THIS->UnusedFrames; + */ + + /** + * Number of contexts using this texture. Used during backend shutdown. + */ + public native int getRefCount(); /* + return THIS->RefCount; + */ + + /** + * Whether the texture data is known to use colors (rather than just white + alpha). + */ + public native boolean getUseColors(); /* + return THIS->UseColors; + */ + + /** + * [Internal] Whether the texture is queued to be destroyed next frame. May still be used in the current frame. + */ + public native boolean getWantDestroyNextFrame(); /* + 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 native void create(int format, int width, int height); /* + THIS->Create((ImTextureFormat)format, width, height); + */ + + /** + * Free the CPU-side pixel buffer. + */ + public native void destroyPixels(); /* + THIS->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..87fdc56f --- /dev/null +++ b/imgui-binding/src/main/java/imgui/ImTextureRect.java @@ -0,0 +1,53 @@ +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 to update. + */ + public native int getX(); /* + return THIS->x; + */ + + /** + * Upper-left y coordinate of rectangle to update. + */ + public native int getY(); /* + return THIS->y; + */ + + /** + * Width of rectangle to update (in pixels). + */ + public native int getW(); /* + return THIS->w; + */ + + /** + * Height of rectangle to update (in pixels). + */ + public native int getH(); /* + return THIS->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); + } + } } //--------------------------------------------------------------------------------------------------------