diff --git a/.github/ISSUE_TEMPLATE/issue_template.yml b/.github/ISSUE_TEMPLATE/issue_template.yml index 6ed62493a..dda2213c3 100644 --- a/.github/ISSUE_TEMPLATE/issue_template.yml +++ b/.github/ISSUE_TEMPLATE/issue_template.yml @@ -14,6 +14,7 @@ body: attributes: value: | **Prerequisites:** + - I am not using a LLM to think or write for me. Please distill information to what's necessary and meaningful. - I have read [Frequently Asked Questions](https://github.com/ocornut/imgui/blob/master/docs/FAQ.md). - I have read [Contributing Guidelines -> General Advices](https://github.com/ocornut/imgui/blob/master/docs/CONTRIBUTING.md#getting-started--general-advice). - I have read [Contributing Guidelines -> How to open an Issue](https://github.com/ocornut/imgui/blob/master/docs/CONTRIBUTING.md#how-to-open-an-issue). diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index d40b14ccf..998b25427 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,10 +1,12 @@ (Click "Preview" to turn any http URL into a clickable link) -1. PLEASE CAREFULLY READ: [Contributing Guidelines](https://github.com/ocornut/imgui/blob/master/docs/CONTRIBUTING.md) +1. PLEASE CAREFULLY READ: [Contributing Guidelines](https://github.com/ocornut/imgui/blob/master/docs/CONTRIBUTING.md). By submitting a PR you are agreeing with the [CLA](https://github.com/ocornut/imgui/blob/master/docs/CONTRIBUTING.md#copyright--contributor-license-agreement). 2. **Make sure you're using a special branch just for this pull request**. (In git, 1 PR = 1 branch. If you update the branch the PR will be updated.) 3. Consider running the [imgui_test_suite](https://github.com/ocornut/imgui_test_engine) or adding new tests to test for expected behaviors. -4. Clear this template before submitting your PR. +4. PLEASE DO NOT SUBMIT AI-GENERATED PROSE. ONLY SUBMIT CODE THAT YOU REVIEWED AND UNDERSTAND. Submissions that are lacking proper human review/judgement before being passed to us are not welcome. + +5. Clear this template before submitting your PR. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9bc81bf81..4e276e1de 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,8 +25,8 @@ jobs: working-directory: ${{ github.workspace }}/imgui env: - VS_PATH: C:\Program Files\Microsoft Visual Studio\2022\Enterprise\ - MSBUILD_PATH: C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\ + VS_PATH: C:\Program Files\Microsoft Visual Studio\18\Enterprise + MSBUILD_PATH: C:\Program Files\Microsoft Visual Studio\18\Enterprise\MSBuild\Current\Bin\ steps: - uses: actions/checkout@v6 with: @@ -116,7 +116,7 @@ jobs: cl.exe /D_USRDLL /D_WINDLL /I. example_single_file.cpp /LD /FeImGui.dll /link cl.exe /DIMGUI_API=__declspec(dllimport) -DIMGUI_IMPL_API= /I. ImGui.lib /Feexample_null.exe examples/example_null/main.cpp - # Win64 examples are more frequently compilted than the Win32 examples. + # Win64 examples are more frequently compiled than the Win32 examples. # More of the Win32 examples requires 'workflow_run' to reduce waste. - name: Build Win32 example_glfw_opengl2 shell: cmd @@ -160,6 +160,7 @@ jobs: - name: Build Win32 example_sdl3_opengl3 shell: cmd run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl3_opengl3/example_sdl3_opengl3.vcxproj /p:Platform=Win32 /p:Configuration=Release' + if: github.event_name == 'workflow_run' - name: Build Win32 example_sdl3_sdlgpu3 shell: cmd @@ -200,6 +201,7 @@ jobs: - name: Build Win64 example_glfw_opengl3 shell: cmd run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj /p:Platform=x64 /p:Configuration=Release' + if: github.event_name == 'workflow_run' - name: Build Win64 example_glfw_vulkan shell: cmd @@ -228,6 +230,7 @@ jobs: - name: Build Win64 example_sdl2_directx11 shell: cmd run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl2_directx11/example_sdl2_directx11.vcxproj /p:Platform=x64 /p:Configuration=Release' + if: github.event_name == 'workflow_run' - name: Build Win64 example_sdl3_opengl3 shell: cmd @@ -506,7 +509,7 @@ jobs: run: g++ -c -I. -std=c++11 -DIMGUI_IMPL_VULKAN_NO_PROTOTYPES=1 backends/imgui_impl_vulkan.cpp Build-MacOS: - runs-on: macos-latest + runs-on: macos-26 name: Build - MacOS defaults: @@ -572,6 +575,9 @@ jobs: - name: Build macOS example_sdl2_metal run: make -C examples/example_sdl2_metal + - name: Build macOS example_sdl3_metal4 + run: make -C examples/example_sdl3_metal4 + - name: Build macOS example_sdl2_opengl2 run: make -C examples/example_sdl2_opengl2 if: github.event_name == 'workflow_run' @@ -587,10 +593,18 @@ jobs: run: xcodebuild -project examples/example_apple_metal/example_apple_metal.xcodeproj -target example_apple_metal_macos if: github.event_name == 'workflow_run' + - name: Build macOS example_apple_metal4 + run: xcodebuild -project examples/example_apple_metal4/example_apple_metal4.xcodeproj -target example_apple_metal_macos + if: github.event_name == 'workflow_run' + - name: Build macOS example_apple_opengl2 run: xcodebuild -project examples/example_apple_opengl2/example_apple_opengl2.xcodeproj -target example_osx_opengl2 if: github.event_name == 'workflow_run' + - name: Build macOS example_apple_opengl3 + run: xcodebuild -project examples/example_apple_opengl3/example_apple_opengl3.xcodeproj -target example_osx_opengl3 + if: github.event_name == 'workflow_run' + Build-iOS: runs-on: macos-14 name: Build - iOS @@ -651,8 +665,12 @@ jobs: name: Build - Android steps: - - uses: actions/checkout@v6 + - name: Setup Gradle 9.4.1 + uses: gradle/actions/setup-gradle@v6 + with: + gradle-version: '9.4.1' + - uses: actions/checkout@v6 - name: Build example_android_opengl3 run: | cd examples/example_android_opengl3/android @@ -667,7 +685,7 @@ jobs: working-directory: ${{ github.workspace }}/imgui env: - MSBUILD_PATH: C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\ + MSBUILD_PATH: C:\Program Files\Microsoft Visual Studio\18\Enterprise\MSBuild\Current\Bin\ steps: - uses: actions/checkout@v6 diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 295628368..231d3a972 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -23,9 +23,9 @@ jobs: run: | if [[ "$PVS_STUDIO_LICENSE" != "" ]]; then - wget -q https://files.viva64.com/etc/pubkey.txt + wget -q https://files.pvs-studio.com/etc/pubkey.txt sudo apt-key add pubkey.txt - sudo wget -O /etc/apt/sources.list.d/viva64.list https://files.viva64.com/etc/viva64.list + sudo wget -O /etc/apt/sources.list.d/viva64.list https://files.pvs-studio.com/etc/viva64.list sudo apt-get update sudo apt-get install -y pvs-studio pvs-studio-analyzer credentials -o pvs-studio.lic $PVS_STUDIO_LICENSE diff --git a/.gitignore b/.gitignore index 1aedcf779..009e61ede 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ -## OSX artifacts +## Misc artifacts .DS_Store +.claude +.vscode ## Dear ImGui artifacts imgui.ini @@ -7,6 +9,8 @@ imgui*.ini ## General build artifacts *.o +*.so* +*.a *.obj *.exe examples/*/Debug/* @@ -55,12 +59,15 @@ examples/example_sdl3_wgpu/web/* .idea cmake-build-* -## VS code artifacts -.vscode +## QNX artifacts +*.pinfo +*.dep +*.gcno ## Unix executables from our example Makefiles examples/example_apple_metal/example_apple_metal examples/example_apple_opengl2/example_apple_opengl2 +examples/example_apple_opengl3/example_apple_opengl3 examples/example_glfw_metal/example_glfw_metal examples/example_glfw_opengl2/example_glfw_opengl2 examples/example_glfw_opengl3/example_glfw_opengl3 @@ -73,6 +80,7 @@ examples/example_sdl2_opengl3/example_sdl2_opengl3 examples/example_sdl2_sdlrenderer2/example_sdl2_sdlrenderer2 examples/example_sdl2_vulkan/example_sdl2_vulkan examples/example_sdl3_metal/example_sdl3_metal +examples/example_sdl3_metal4/example_sdl3_metal4 examples/example_sdl3_opengl3/example_sdl3_opengl3 examples/example_sdl3_sdlgpu3/example_sdl3_sdlgpu3 examples/example_sdl3_sdlrenderer3/example_sdl3_sdlrenderer3 diff --git a/backends/imgui_impl_allegro5.cpp b/backends/imgui_impl_allegro5.cpp index 6ee88a184..bd877f322 100644 --- a/backends/imgui_impl_allegro5.cpp +++ b/backends/imgui_impl_allegro5.cpp @@ -9,6 +9,7 @@ // [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // Missing features or Issues: // [ ] Renderer: The renderer is suboptimal as we need to unindex our buffers and convert vertices manually. +// [ ] Renderer: Missing support for DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest callbacks: Allegro5 cannot enable/disable LINEAR bitmap flags after creation. // [ ] Platform: Missing gamepad support. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. @@ -21,6 +22,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState (others cannot be supported by Allegro5). (#9378) // 2025-09-18: Call platform_io.ClearRendererHandlers() and platform_io.ClearPlatformHandlers() on shutdown. // 2025-08-12: Inputs: fixed missing support for ImGuiKey_PrintScreen under Windows, as raw Allegro 5 does not receive it. // 2025-08-12: Added ImGui_ImplAllegro5_SetDisplay() function to change current ALLEGRO_DISPLAY, as Allegro applications often need to do that. @@ -136,7 +138,10 @@ static void ImGui_ImplAllegro5_SetupRenderState(ImDrawData* draw_data) } } -// Render function. +// Draw callbacks +static void ImGui_ImplAllegro5_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. + +// Render function void ImGui_ImplAllegro5_RenderDrawData(ImDrawData* draw_data) { // Avoid rendering when minimized @@ -152,6 +157,7 @@ void ImGui_ImplAllegro5_RenderDrawData(ImDrawData* draw_data) // Backup Allegro state that will be modified ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData(); + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); ALLEGRO_TRANSFORM last_transform = *al_get_current_transform(); ALLEGRO_TRANSFORM last_projection_transform = *al_get_current_projection_transform(); int last_clip_x, last_clip_y, last_clip_w, last_clip_h; @@ -208,8 +214,7 @@ void ImGui_ImplAllegro5_RenderDrawData(ImDrawData* draw_data) if (pcmd->UserCallback) { // User callback, registered via ImDrawList::AddCallback() - // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) - if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + if (pcmd->UserCallback == ImGui_ImplAllegro5_DrawCallback_ResetRenderState) ImGui_ImplAllegro5_SetupRenderState(draw_data); else pcmd->UserCallback(draw_list, pcmd); @@ -263,8 +268,7 @@ void ImGui_ImplAllegro5_UpdateTexture(ImTextureData* tex) IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); - // Create texture - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) + // Create texture (bilinear sampling is required) const int new_bitmap_flags = al_get_new_bitmap_flags(); int new_bitmap_format = al_get_new_bitmap_format(); al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP | ALLEGRO_MIN_LINEAR | ALLEGRO_MAG_LINEAR); @@ -486,11 +490,12 @@ bool ImGui_ImplAllegro5_Init(ALLEGRO_DISPLAY* display) ImGui_ImplAllegro5_SetDisplay(display); -#if ALLEGRO_HAS_CLIPBOARD ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); +#if ALLEGRO_HAS_CLIPBOARD platform_io.Platform_SetClipboardTextFn = ImGui_ImplAllegro5_SetClipboardText; platform_io.Platform_GetClipboardTextFn = ImGui_ImplAllegro5_GetClipboardText; #endif + platform_io.DrawCallback_ResetRenderState = ImGui_ImplAllegro5_DrawCallback_ResetRenderState; return true; } diff --git a/backends/imgui_impl_allegro5.h b/backends/imgui_impl_allegro5.h index 135c4ff8a..84ebf4338 100644 --- a/backends/imgui_impl_allegro5.h +++ b/backends/imgui_impl_allegro5.h @@ -9,6 +9,7 @@ // [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // Missing features or Issues: // [ ] Renderer: The renderer is suboptimal as we need to unindex our buffers and convert vertices manually. +// [ ] Renderer: Missing support for DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest callbacks: Allegro5 cannot enable/disable LINEAR bitmap flags after creation. // [ ] Platform: Missing gamepad support. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. diff --git a/backends/imgui_impl_android.cpp b/backends/imgui_impl_android.cpp index a76de1c26..21a7ae526 100644 --- a/backends/imgui_impl_android.cpp +++ b/backends/imgui_impl_android.cpp @@ -1,5 +1,5 @@ // dear imgui: Platform Binding for Android native app -// This needs to be used along with the OpenGL 3 Renderer (imgui_impl_opengl3) +// This needs to be used along with a Renderer (e.g. OpenGL3, Vulkan..) // Implemented features: // [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy AKEYCODE_* values are obsolete since 1.87 and not supported since 1.91.5] @@ -23,7 +23,9 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) -// 2022-09-26: Inputs: Renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported). +// 2026-07-15: Inputs: clear mouse position on touch release (AMOTION_EVENT_ACTION_UP) to prevent items from staying in hovered state. (#6627, #9474) +// 2023-04-11: Inputs: calling new io.AddMouseSourceEvent() to discriminate Mouse from Touch events. +// 2022-09-26: Inputs: renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported). // 2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion. // 2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+). // 2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range. @@ -230,6 +232,8 @@ int32_t ImGui_ImplAndroid_HandleInputEvent(const AInputEvent* input_event) { io.AddMousePosEvent(AMotionEvent_getX(input_event, event_pointer_index), AMotionEvent_getY(input_event, event_pointer_index)); io.AddMouseButtonEvent(0, event_action == AMOTION_EVENT_ACTION_DOWN); + if (event_action == AMOTION_EVENT_ACTION_UP) // (#6627, #9474) + io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); } break; } diff --git a/backends/imgui_impl_android.h b/backends/imgui_impl_android.h index f6e41039a..8f22e9b76 100644 --- a/backends/imgui_impl_android.h +++ b/backends/imgui_impl_android.h @@ -1,5 +1,5 @@ // dear imgui: Platform Binding for Android native app -// This needs to be used along with the OpenGL 3 Renderer (imgui_impl_opengl3) +// This needs to be used along with a Renderer (e.g. OpenGL3, Vulkan..) // Implemented features: // [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy AKEYCODE_* values are obsolete since 1.87 and not supported since 1.91.5] diff --git a/backends/imgui_impl_dx10.cpp b/backends/imgui_impl_dx10.cpp index 118798325..a81d81305 100644 --- a/backends/imgui_impl_dx10.cpp +++ b/backends/imgui_impl_dx10.cpp @@ -5,6 +5,7 @@ // [X] Renderer: User texture binding. Use 'ID3D10ShaderResourceView*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplDX10_GetRenderState(). // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. @@ -16,7 +17,9 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) -// 2026-01-19: DirectX11: Added 'SamplerNearest' in ImGui_ImplDX11_RenderState. Renamed 'SamplerDefault' to 'SamplerLinear'. +// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) +// 2026-04-23: DirectX10: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. Obsoleting samplers from ImGui_ImplDX10_RenderState. (#9378) +// 2026-01-19: DirectX10: Added 'SamplerNearest' in ImGui_ImplDX10_RenderState. Renamed 'SamplerDefault' to 'SamplerLinear'. // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-06-11: DirectX10: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. // 2025-05-07: DirectX10: Honor draw_data->FramebufferScale to allow for custom backends and experiment using it (consistently with other renderer backends, even though in normal condition it is not set under Windows). @@ -98,6 +101,11 @@ static ImGui_ImplDX10_Data* ImGui_ImplDX10_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplDX10_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +ImGui_ImplDX10_RenderState* ImGui_ImplDX10_GetRenderState() +{ + return (ImGui_ImplDX10_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; +} + // Functions static void ImGui_ImplDX10_SetupRenderState(ImDrawData* draw_data, ID3D10Device* device) { @@ -105,8 +113,8 @@ static void ImGui_ImplDX10_SetupRenderState(ImDrawData* draw_data, ID3D10Device* // Setup viewport D3D10_VIEWPORT vp = {}; - vp.Width = (UINT)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - vp.Height = (UINT)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + vp.Width = (UINT)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + vp.Height = (UINT)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); vp.MinDepth = 0.0f; vp.MaxDepth = 1.0f; vp.TopLeftX = vp.TopLeftY = 0; @@ -147,12 +155,17 @@ static void ImGui_ImplDX10_SetupRenderState(ImDrawData* draw_data, ID3D10Device* device->GSSetShader(nullptr); // Setup render state - const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f }; + const float blend_factor[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; device->OMSetBlendState(bd->pBlendState, blend_factor, 0xffffffff); device->OMSetDepthStencilState(bd->pDepthStencilState, 0); device->RSSetState(bd->pRasterizerState); } +// Draw callbacks +static void ImGui_ImplDX10_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. +static void ImGui_ImplDX10_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData(); bd->pd3dDevice->PSSetSamplers(0, 1, &bd->pTexSamplerLinear); } +static void ImGui_ImplDX10_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData(); bd->pd3dDevice->PSSetSamplers(0, 1, &bd->pTexSamplerNearest); } + // Render function void ImGui_ImplDX10_RenderDrawData(ImDrawData* draw_data) { @@ -177,7 +190,7 @@ void ImGui_ImplDX10_RenderDrawData(ImDrawData* draw_data) bd->VertexBufferSize = draw_data->TotalVtxCount + 5000; D3D10_BUFFER_DESC desc = {}; desc.Usage = D3D10_USAGE_DYNAMIC; - desc.ByteWidth = bd->VertexBufferSize * sizeof(ImDrawVert); + desc.ByteWidth = (UINT)bd->VertexBufferSize * sizeof(ImDrawVert); desc.BindFlags = D3D10_BIND_VERTEX_BUFFER; desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE; desc.MiscFlags = 0; @@ -191,7 +204,7 @@ void ImGui_ImplDX10_RenderDrawData(ImDrawData* draw_data) bd->IndexBufferSize = draw_data->TotalIdxCount + 10000; D3D10_BUFFER_DESC desc = {}; desc.Usage = D3D10_USAGE_DYNAMIC; - desc.ByteWidth = bd->IndexBufferSize * sizeof(ImDrawIdx); + desc.ByteWidth = (UINT)bd->IndexBufferSize * sizeof(ImDrawIdx); desc.BindFlags = D3D10_BIND_INDEX_BUFFER; desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE; if (device->CreateBuffer(&desc, nullptr, &bd->pIB) < 0) @@ -260,8 +273,6 @@ void ImGui_ImplDX10_RenderDrawData(ImDrawData* draw_data) ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); ImGui_ImplDX10_RenderState render_state; render_state.Device = bd->pd3dDevice; - render_state.SamplerLinear = bd->pTexSamplerLinear; - render_state.SamplerNearest = bd->pTexSamplerNearest; render_state.VertexConstantBuffer = bd->pVertexConstantBuffer; platform_io.Renderer_RenderState = &render_state; @@ -279,8 +290,7 @@ void ImGui_ImplDX10_RenderDrawData(ImDrawData* draw_data) if (pcmd->UserCallback != nullptr) { // User callback, registered via ImDrawList::AddCallback() - // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) - if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + if (pcmd->UserCallback == ImGui_ImplDX10_DrawCallback_ResetRenderState) ImGui_ImplDX10_SetupRenderState(draw_data, device); else pcmd->UserCallback(draw_list, pcmd); @@ -443,7 +453,7 @@ bool ImGui_ImplDX10_CreateDeviceObjects() PS_INPUT main(VS_INPUT input)\ {\ PS_INPUT output;\ - output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));\ + output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.0f, 1.0f));\ output.col = input.col;\ output.uv = input.uv;\ return output;\ @@ -554,8 +564,7 @@ bool ImGui_ImplDX10_CreateDeviceObjects() bd->pd3dDevice->CreateDepthStencilState(&desc, &bd->pDepthStencilState); } - // Create texture sampler - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) + // Create texture sampler (bilinear sampling is required) { D3D10_SAMPLER_DESC desc; ZeroMemory(&desc, sizeof(desc)); @@ -563,10 +572,10 @@ bool ImGui_ImplDX10_CreateDeviceObjects() desc.AddressU = D3D10_TEXTURE_ADDRESS_CLAMP; desc.AddressV = D3D10_TEXTURE_ADDRESS_CLAMP; desc.AddressW = D3D10_TEXTURE_ADDRESS_CLAMP; - desc.MipLODBias = 0.f; + desc.MipLODBias = 0.0f; desc.ComparisonFunc = D3D10_COMPARISON_ALWAYS; - desc.MinLOD = 0.f; - desc.MaxLOD = 0.f; + desc.MinLOD = 0.0f; + desc.MaxLOD = 0.0f; bd->pd3dDevice->CreateSamplerState(&desc, &bd->pTexSamplerLinear); desc.Filter = D3D10_FILTER_MIN_MAG_MIP_POINT; bd->pd3dDevice->CreateSamplerState(&desc, &bd->pTexSamplerNearest); @@ -613,6 +622,9 @@ bool ImGui_ImplDX10_Init(ID3D10Device* device) ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = D3D10_REQ_TEXTURE2D_U_OR_V_DIMENSION; + platform_io.DrawCallback_ResetRenderState = ImGui_ImplDX10_DrawCallback_ResetRenderState; + platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplDX10_DrawCallback_SetSamplerLinear; + platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplDX10_DrawCallback_SetSamplerNearest; // Get factory from device IDXGIDevice* pDXGIDevice = nullptr; diff --git a/backends/imgui_impl_dx10.h b/backends/imgui_impl_dx10.h index 87945d64e..3ab1669a9 100644 --- a/backends/imgui_impl_dx10.h +++ b/backends/imgui_impl_dx10.h @@ -5,6 +5,7 @@ // [X] Renderer: User texture binding. Use 'ID3D10ShaderResourceView*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplDX10_GetRenderState(). // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. @@ -41,9 +42,10 @@ IMGUI_IMPL_API void ImGui_ImplDX10_UpdateTexture(ImTextureData* tex); struct ImGui_ImplDX10_RenderState { ID3D10Device* Device; - ID3D10SamplerState* SamplerLinear; - ID3D10SamplerState* SamplerNearest; ID3D10Buffer* VertexConstantBuffer; + //ID3D10SamplerState* SamplerLinear; // Use ImDrawList::AddCallback(ImGui::GetPlatform().DrawCallback_SetSamplerLinear) + //ID3D10SamplerState* SamplerNearest; // Use ImDrawList::AddCallback(ImGui::GetPlatform().DrawCallback_SetSamplerNearest) }; +IMGUI_IMPL_API ImGui_ImplDX10_RenderState* ImGui_ImplDX10_GetRenderState(); #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_dx11.cpp b/backends/imgui_impl_dx11.cpp index 193127b4e..a7d2cdf73 100644 --- a/backends/imgui_impl_dx11.cpp +++ b/backends/imgui_impl_dx11.cpp @@ -5,7 +5,7 @@ // [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). -// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplDX11_GetRenderState(). // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. @@ -17,6 +17,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-04-23: DirectX11: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. Obsoleting samplers from ImGui_ImplDX11_RenderState. (#9378) // 2026-01-19: DirectX11: Added 'SamplerNearest' in ImGui_ImplDX11_RenderState. Renamed 'SamplerDefault' to 'SamplerLinear'. // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-06-11: DirectX11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. @@ -85,6 +86,7 @@ struct ImGui_ImplDX11_Data ID3D11DepthStencilState* pDepthStencilState; int VertexBufferSize; int IndexBufferSize; + ImGui_ImplDX11_RenderState* RenderState; // == (ImGui_ImplDX11_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState during rendering. ImGui_ImplDX11_Data() { memset((void*)this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; } }; @@ -101,6 +103,11 @@ static ImGui_ImplDX11_Data* ImGui_ImplDX11_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplDX11_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +ImGui_ImplDX11_RenderState* ImGui_ImplDX11_GetRenderState() +{ + return (ImGui_ImplDX11_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; +} + // Functions static void ImGui_ImplDX11_SetupRenderState(const ImDrawData* draw_data, ID3D11DeviceContext* device_ctx) { @@ -108,8 +115,8 @@ static void ImGui_ImplDX11_SetupRenderState(const ImDrawData* draw_data, ID3D11D // Setup viewport D3D11_VIEWPORT vp = {}; - vp.Width = draw_data->DisplaySize.x * draw_data->FramebufferScale.x; - vp.Height = draw_data->DisplaySize.y * draw_data->FramebufferScale.y; + vp.Width = (float)(int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + vp.Height = (float)(int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); vp.MinDepth = 0.0f; vp.MaxDepth = 1.0f; vp.TopLeftX = vp.TopLeftY = 0; @@ -153,12 +160,17 @@ static void ImGui_ImplDX11_SetupRenderState(const ImDrawData* draw_data, ID3D11D device_ctx->CSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used.. // Setup render state - const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f }; + const float blend_factor[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; device_ctx->OMSetBlendState(bd->pBlendState, blend_factor, 0xffffffff); device_ctx->OMSetDepthStencilState(bd->pDepthStencilState, 0); device_ctx->RSSetState(bd->pRasterizerState); } +// Draw callbacks +static void ImGui_ImplDX11_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. +static void ImGui_ImplDX11_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); bd->RenderState->DeviceContext->PSSetSamplers(0, 1, &bd->pTexSamplerLinear); } +static void ImGui_ImplDX11_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); bd->RenderState->DeviceContext->PSSetSamplers(0, 1, &bd->pTexSamplerNearest); } + // Render function void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data) { @@ -183,7 +195,7 @@ void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data) bd->VertexBufferSize = draw_data->TotalVtxCount + 5000; D3D11_BUFFER_DESC desc = {}; desc.Usage = D3D11_USAGE_DYNAMIC; - desc.ByteWidth = bd->VertexBufferSize * sizeof(ImDrawVert); + desc.ByteWidth = (UINT)bd->VertexBufferSize * sizeof(ImDrawVert); desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; desc.MiscFlags = 0; @@ -196,7 +208,7 @@ void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data) bd->IndexBufferSize = draw_data->TotalIdxCount + 10000; D3D11_BUFFER_DESC desc = {}; desc.Usage = D3D11_USAGE_DYNAMIC; - desc.ByteWidth = bd->IndexBufferSize * sizeof(ImDrawIdx); + desc.ByteWidth = (UINT)bd->IndexBufferSize * sizeof(ImDrawIdx); desc.BindFlags = D3D11_BIND_INDEX_BUFFER; desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; if (bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pIB) < 0) @@ -274,10 +286,8 @@ void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data) ImGui_ImplDX11_RenderState render_state; render_state.Device = bd->pd3dDevice; render_state.DeviceContext = bd->pd3dDeviceContext; - render_state.SamplerLinear = bd->pTexSamplerLinear; - render_state.SamplerNearest = bd->pTexSamplerNearest; render_state.VertexConstantBuffer = bd->pVertexConstantBuffer; - platform_io.Renderer_RenderState = &render_state; + platform_io.Renderer_RenderState = bd->RenderState = &render_state; // Render command lists // (Because we merged all buffers into a single one, we maintain our own offset into them) @@ -293,8 +303,7 @@ void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data) if (pcmd->UserCallback != nullptr) { // User callback, registered via ImDrawList::AddCallback() - // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) - if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + if (pcmd->UserCallback == ImGui_ImplDX11_DrawCallback_ResetRenderState) ImGui_ImplDX11_SetupRenderState(draw_data, device); else pcmd->UserCallback(draw_list, pcmd); @@ -320,7 +329,7 @@ void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data) global_idx_offset += draw_list->IdxBuffer.Size; global_vtx_offset += draw_list->VtxBuffer.Size; } - platform_io.Renderer_RenderState = nullptr; + platform_io.Renderer_RenderState = bd->RenderState = nullptr; // Restore modified DX state device->RSSetScissorRects(old.ScissorRectsCount, old.ScissorRects); @@ -458,7 +467,7 @@ bool ImGui_ImplDX11_CreateDeviceObjects() PS_INPUT main(VS_INPUT input)\ {\ PS_INPUT output;\ - output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));\ + output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.0f, 1.0f));\ output.col = input.col;\ output.uv = input.uv;\ return output;\ @@ -570,7 +579,6 @@ bool ImGui_ImplDX11_CreateDeviceObjects() } // Create texture sampler - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) { D3D11_SAMPLER_DESC desc; ZeroMemory(&desc, sizeof(desc)); @@ -578,10 +586,10 @@ bool ImGui_ImplDX11_CreateDeviceObjects() desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; - desc.MipLODBias = 0.f; + desc.MipLODBias = 0.0f; desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; - desc.MinLOD = 0.f; - desc.MaxLOD = 0.f; + desc.MinLOD = 0.0f; + desc.MaxLOD = 0.0f; bd->pd3dDevice->CreateSamplerState(&desc, &bd->pTexSamplerLinear); desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; bd->pd3dDevice->CreateSamplerState(&desc, &bd->pTexSamplerNearest); @@ -629,6 +637,9 @@ bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_co ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION; + platform_io.DrawCallback_ResetRenderState = ImGui_ImplDX11_DrawCallback_ResetRenderState; + platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplDX11_DrawCallback_SetSamplerLinear; + platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplDX11_DrawCallback_SetSamplerNearest; // Get factory from device IDXGIDevice* pDXGIDevice = nullptr; diff --git a/backends/imgui_impl_dx11.h b/backends/imgui_impl_dx11.h index 338e0093f..5fd8ddca9 100644 --- a/backends/imgui_impl_dx11.h +++ b/backends/imgui_impl_dx11.h @@ -5,7 +5,7 @@ // [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). -// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplDX11_GetRenderState(). // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. @@ -44,9 +44,10 @@ struct ImGui_ImplDX11_RenderState { ID3D11Device* Device; ID3D11DeviceContext* DeviceContext; - ID3D11SamplerState* SamplerLinear; - ID3D11SamplerState* SamplerNearest; ID3D11Buffer* VertexConstantBuffer; + //ID3D11SamplerState* SamplerLinear; // Use ImDrawList::AddCallback(ImGui::GetPlatform().DrawCallback_SetSamplerLinear) + //ID3D11SamplerState* SamplerNearest; // Use ImDrawList::AddCallback(ImGui::GetPlatform().DrawCallback_SetSamplerNearest) }; +IMGUI_IMPL_API ImGui_ImplDX11_RenderState* ImGui_ImplDX11_GetRenderState(); #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_dx12.cpp b/backends/imgui_impl_dx12.cpp index a5420782a..22a9bdc97 100644 --- a/backends/imgui_impl_dx12.cpp +++ b/backends/imgui_impl_dx12.cpp @@ -5,7 +5,7 @@ // [X] Renderer: User texture binding. Use 'D3D12_GPU_DESCRIPTOR_HANDLE' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). -// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplDX12_GetRenderState(). // The aim of imgui_impl_dx12.h/.cpp is to be usable in your engine without any modification. // IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ @@ -20,6 +20,9 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) +// 2026-08-05: DirectX12: *BREAKING CHANGE* Removed support for legacy `ImGui_ImplDX12_Init()` signature obsoleted in 1.91.6 (2024-11-15) because it needed to forcefully disable support for ImGuiBackendFlags_RendererHasTextures. (#9487) +// 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) // 2025-10-11: DirectX12: Reuse texture upload buffer and grow it only when necessary. (#9002) // 2025-09-29: DirectX12: Rework synchronization logic. (#8961) // 2025-09-29: DirectX12: Enable swapchain tearing to eliminate viewports framerate throttling. (#8965) @@ -91,6 +94,8 @@ struct ImGui_ImplDX12_Texture struct ImGui_ImplDX12_Data { ImGui_ImplDX12_InitInfo InitInfo; + ImGui_ImplDX12_RenderState* RenderState; + IDXGIFactory5* pdxgiFactory; ID3D12Device* pd3dDevice; ID3D12RootSignature* pRootSignatureLinear; @@ -128,6 +133,11 @@ static ImGui_ImplDX12_Data* ImGui_ImplDX12_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplDX12_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +ImGui_ImplDX12_RenderState* ImGui_ImplDX12_GetRenderState() +{ + return (ImGui_ImplDX12_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; +} + // Buffers used during the rendering of a frame struct ImGui_ImplDX12_RenderBuffers { @@ -142,19 +152,15 @@ struct VERTEX_CONSTANT_BUFFER_DX12 float mvp[4][4]; }; -// FIXME-WIP: Allow user to forward declare those two, for until we come up with a backend agnostic API to do this. (#9173) -void ImGui_ImplDX12_SetupSamplerLinear(ID3D12GraphicsCommandList* command_list); -void ImGui_ImplDX12_SetupSamplerNearest(ID3D12GraphicsCommandList* command_list); - // Functions -void ImGui_ImplDX12_SetupSamplerLinear(ID3D12GraphicsCommandList* command_list) +static void ImGui_ImplDX12_SetupSamplerLinear(ID3D12GraphicsCommandList* command_list) { ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); command_list->SetPipelineState(bd->pPipelineStateLinear); command_list->SetGraphicsRootSignature(bd->pRootSignatureLinear); } -void ImGui_ImplDX12_SetupSamplerNearest(ID3D12GraphicsCommandList* command_list) +static void ImGui_ImplDX12_SetupSamplerNearest(ID3D12GraphicsCommandList* command_list) { ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); command_list->SetPipelineState(bd->pPipelineStateNearest); @@ -183,8 +189,8 @@ static void ImGui_ImplDX12_SetupRenderState(ImDrawData* draw_data, ID3D12Graphic // Setup viewport D3D12_VIEWPORT vp = {}; - vp.Width = draw_data->DisplaySize.x * draw_data->FramebufferScale.x; - vp.Height = draw_data->DisplaySize.y * draw_data->FramebufferScale.y; + vp.Width = (float)(int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + vp.Height = (float)(int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); vp.MinDepth = 0.0f; vp.MaxDepth = 1.0f; vp.TopLeftX = vp.TopLeftY = 0.0f; @@ -200,7 +206,7 @@ static void ImGui_ImplDX12_SetupRenderState(ImDrawData* draw_data, ID3D12Graphic command_list->IASetVertexBuffers(0, 1, &vbv); D3D12_INDEX_BUFFER_VIEW ibv = {}; ibv.BufferLocation = fr->IndexBuffer->GetGPUVirtualAddress(); - ibv.SizeInBytes = fr->IndexBufferSize * sizeof(ImDrawIdx); + ibv.SizeInBytes = (UINT)fr->IndexBufferSize * sizeof(ImDrawIdx); ibv.Format = sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT; command_list->IASetIndexBuffer(&ibv); command_list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); @@ -208,7 +214,7 @@ static void ImGui_ImplDX12_SetupRenderState(ImDrawData* draw_data, ID3D12Graphic command_list->SetGraphicsRoot32BitConstants(0, 16, &vertex_constant_buffer, 0); // Setup blend factor - const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f }; + const float blend_factor[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; command_list->OMSetBlendFactor(blend_factor); } @@ -220,6 +226,11 @@ static inline void SafeRelease(T*& res) res = nullptr; } +// Draw callbacks +static void ImGui_ImplDX12_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. +static void ImGui_ImplDX12_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); ImGui_ImplDX12_SetupSamplerLinear(bd->RenderState->CommandList); } +static void ImGui_ImplDX12_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); ImGui_ImplDX12_SetupSamplerNearest(bd->RenderState->CommandList); } + // Render function void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandList* command_list) { @@ -317,7 +328,7 @@ void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandL ImGui_ImplDX12_RenderState render_state; render_state.Device = bd->pd3dDevice; render_state.CommandList = command_list; - platform_io.Renderer_RenderState = &render_state; + platform_io.Renderer_RenderState = bd->RenderState = &render_state; // Render command lists // (Because we merged all buffers into a single one, we maintain our own offset into them) @@ -334,7 +345,7 @@ void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandL { // User callback, registered via ImDrawList::AddCallback() // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) - if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + if (pcmd->UserCallback == ImGui_ImplDX12_DrawCallback_ResetRenderState) ImGui_ImplDX12_SetupRenderState(draw_data, command_list, fr); else pcmd->UserCallback(draw_list, pcmd); @@ -361,7 +372,7 @@ void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandL global_idx_offset += draw_list->IdxBuffer.Size; global_vtx_offset += draw_list->VtxBuffer.Size; } - platform_io.Renderer_RenderState = nullptr; + platform_io.Renderer_RenderState = bd->RenderState = nullptr; } static void ImGui_ImplDX12_DestroyTexture(ImTextureData* tex) @@ -604,17 +615,17 @@ bool ImGui_ImplDX12_CreateDeviceObjects() param[1].DescriptorTable.pDescriptorRanges = &descRange; param[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; - // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling. + // Bilinear sampling is required D3D12_STATIC_SAMPLER_DESC staticSampler[1] = {}; staticSampler[0].Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR; staticSampler[0].AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; staticSampler[0].AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; staticSampler[0].AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - staticSampler[0].MipLODBias = 0.f; + staticSampler[0].MipLODBias = 0.0f; staticSampler[0].MaxAnisotropy = 0; staticSampler[0].ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS; staticSampler[0].BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK; - staticSampler[0].MinLOD = 0.f; + staticSampler[0].MinLOD = 0.0f; staticSampler[0].MaxLOD = D3D12_FLOAT32_MAX; staticSampler[0].ShaderRegister = 0; staticSampler[0].RegisterSpace = 0; @@ -664,7 +675,7 @@ bool ImGui_ImplDX12_CreateDeviceObjects() bd->pd3dDevice->CreateRootSignature(0, blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(&bd->pRootSignatureLinear)); blob->Release(); - // Root Signature for ImDrawCallback_SetSamplerNearest + // Nearest sampler staticSampler[0].Filter = D3D12_FILTER_MIN_MAG_MIP_POINT; if (D3D12SerializeRootSignatureFn(&desc, D3D_ROOT_SIGNATURE_VERSION_1, &blob, nullptr) != S_OK) return false; @@ -717,7 +728,7 @@ bool ImGui_ImplDX12_CreateDeviceObjects() PS_INPUT main(VS_INPUT input)\ {\ PS_INPUT output;\ - output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));\ + output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.0f, 1.0f));\ output.col = input.col;\ output.uv = input.uv;\ return output;\ @@ -813,7 +824,6 @@ bool ImGui_ImplDX12_CreateDeviceObjects() return false; } - // Pipeline State for ImDrawCallback_SetSamplerNearest psoDesc.pRootSignature = bd->pRootSignatureNearest; result_pipeline_state = bd->pd3dDevice->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&bd->pPipelineStateNearest)); @@ -927,6 +937,11 @@ bool ImGui_ImplDX12_Init(ImGui_ImplDX12_InitInfo* init_info) io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.DrawCallback_ResetRenderState = ImGui_ImplDX12_DrawCallback_ResetRenderState; + platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplDX12_DrawCallback_SetSamplerLinear; + platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplDX12_DrawCallback_SetSamplerNearest; + #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS if (init_info->SrvDescriptorAllocFn == nullptr) ImGui_ImplDX12_InitLegacySingleDescriptorMode(init_info); @@ -948,36 +963,6 @@ bool ImGui_ImplDX12_Init(ImGui_ImplDX12_InitInfo* init_info) return true; } -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -// Legacy initialization API Obsoleted in 1.91.5 -// font_srv_cpu_desc_handle and font_srv_gpu_desc_handle are handles to a single SRV descriptor to use for the internal font texture, they must be in 'srv_descriptor_heap' -bool ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames_in_flight, DXGI_FORMAT rtv_format, ID3D12DescriptorHeap* srv_descriptor_heap, D3D12_CPU_DESCRIPTOR_HANDLE font_srv_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE font_srv_gpu_desc_handle) -{ - ImGui_ImplDX12_InitInfo init_info; - init_info.Device = device; - init_info.NumFramesInFlight = num_frames_in_flight; - init_info.RTVFormat = rtv_format; - init_info.SrvDescriptorHeap = srv_descriptor_heap; - init_info.LegacySingleSrvCpuDescriptor = font_srv_cpu_desc_handle; - init_info.LegacySingleSrvGpuDescriptor = font_srv_gpu_desc_handle; - - D3D12_COMMAND_QUEUE_DESC queueDesc = {}; - queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; - queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; - queueDesc.NodeMask = 1; - HRESULT hr = device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&init_info.CommandQueue)); - IM_ASSERT(SUCCEEDED(hr)); - - bool ret = ImGui_ImplDX12_Init(&init_info); - ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); - bd->commandQueueOwned = true; - ImGuiIO& io = ImGui::GetIO(); - io.BackendFlags &= ~ImGuiBackendFlags_RendererHasTextures; // Using legacy ImGui_ImplDX12_Init() call with 1 SRV descriptor we cannot support multiple textures. - - return ret; -} -#endif - void ImGui_ImplDX12_Shutdown() { ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); diff --git a/backends/imgui_impl_dx12.h b/backends/imgui_impl_dx12.h index b8be8db22..106b4b4d2 100644 --- a/backends/imgui_impl_dx12.h +++ b/backends/imgui_impl_dx12.h @@ -5,7 +5,7 @@ // [X] Renderer: User texture binding. Use 'D3D12_GPU_DESCRIPTOR_HANDLE' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). -// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplDX12_GetRenderState(). // The aim of imgui_impl_dx12.h/.cpp is to be usable in your engine without any modification. // IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ @@ -59,13 +59,6 @@ IMGUI_IMPL_API void ImGui_ImplDX12_Shutdown(); IMGUI_IMPL_API void ImGui_ImplDX12_NewFrame(); IMGUI_IMPL_API void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandList* graphics_command_list); -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -// Legacy initialization API Obsoleted in 1.91.5 -// - font_srv_cpu_desc_handle and font_srv_gpu_desc_handle are handles to a single SRV descriptor to use for the internal font texture, they must be in 'srv_descriptor_heap' -// - When we introduced the ImGui_ImplDX12_InitInfo struct we also added a 'ID3D12CommandQueue* CommandQueue' field. -IMGUI_IMPL_API bool ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames_in_flight, DXGI_FORMAT rtv_format, ID3D12DescriptorHeap* srv_descriptor_heap, D3D12_CPU_DESCRIPTOR_HANDLE font_srv_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE font_srv_gpu_desc_handle); -#endif - // Use if you want to reset your rendering device without losing Dear ImGui state. IMGUI_IMPL_API bool ImGui_ImplDX12_CreateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplDX12_InvalidateDeviceObjects(); @@ -81,6 +74,15 @@ struct ImGui_ImplDX12_RenderState ID3D12Device* Device; ID3D12GraphicsCommandList* CommandList; }; +IMGUI_IMPL_API ImGui_ImplDX12_RenderState* ImGui_ImplDX12_GetRenderState(); + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +// Legacy initialization API Obsoleted in 1.91.5 +// - font_srv_cpu_desc_handle and font_srv_gpu_desc_handle are handles to a single SRV descriptor to use for the internal font texture, they must be in 'srv_descriptor_heap' +// - When we introduced the ImGui_ImplDX12_InitInfo struct we also added a 'ID3D12CommandQueue* CommandQueue' field. +// - DOES NOT SUPPORT ImGuiBackendFlags_RendererHasTextures. +//IMGUI_IMPL_API bool ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames_in_flight, DXGI_FORMAT rtv_format, ID3D12DescriptorHeap* srv_descriptor_heap, D3D12_CPU_DESCRIPTOR_HANDLE font_srv_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE font_srv_gpu_desc_handle); +#endif #if defined(__clang__) #pragma clang diagnostic pop diff --git a/backends/imgui_impl_dx9.cpp b/backends/imgui_impl_dx9.cpp index 37b5815cc..360398f04 100644 --- a/backends/imgui_impl_dx9.cpp +++ b/backends/imgui_impl_dx9.cpp @@ -17,6 +17,8 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) +// 2026-03-19: Fixed issue in ImGui_ImplDX9_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295, #9310) // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-06-11: DirectX9: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. // 2024-10-07: DirectX9: Changed default texture sampler to Clamp instead of Repeat/Wrap. @@ -161,6 +163,11 @@ static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data) } } +// Draw callbacks +static void ImGui_ImplDX9_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. +static void ImGui_ImplDX9_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); } +static void ImGui_ImplDX9_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT); bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT); } + // Render function. void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data) { @@ -183,14 +190,14 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data) { if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; } bd->VertexBufferSize = draw_data->TotalVtxCount + 5000; - if (device->CreateVertexBuffer(bd->VertexBufferSize * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &bd->pVB, nullptr) < 0) + if (device->CreateVertexBuffer((UINT)bd->VertexBufferSize * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &bd->pVB, nullptr) < 0) return; } if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount) { if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; } bd->IndexBufferSize = draw_data->TotalIdxCount + 10000; - if (device->CreateIndexBuffer(bd->IndexBufferSize * sizeof(ImDrawIdx), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, &bd->pIB, nullptr) < 0) + if (device->CreateIndexBuffer((UINT)bd->IndexBufferSize * sizeof(ImDrawIdx), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, &bd->pIB, nullptr) < 0) return; } @@ -268,8 +275,7 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data) if (pcmd->UserCallback != nullptr) { // User callback, registered via ImDrawList::AddCallback() - // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) - if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + if (pcmd->UserCallback == ImGui_ImplDX9_DrawCallback_ResetRenderState) ImGui_ImplDX9_SetupRenderState(draw_data); else pcmd->UserCallback(draw_list, pcmd); @@ -324,46 +330,6 @@ static bool ImGui_ImplDX9_CheckFormatSupport(LPDIRECT3DDEVICE9 pDevice, D3DFORMA return support; } -bool ImGui_ImplDX9_Init(IDirect3DDevice9* device) -{ - ImGuiIO& io = ImGui::GetIO(); - IMGUI_CHECKVERSION(); - IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); - - // Setup backend capabilities flags - ImGui_ImplDX9_Data* bd = IM_NEW(ImGui_ImplDX9_Data)(); - io.BackendRendererUserData = (void*)bd; - io.BackendRendererName = "imgui_impl_dx9"; - io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. - io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. - - ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); - platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = 4096; - - bd->pd3dDevice = device; - bd->pd3dDevice->AddRef(); - bd->HasRgbaSupport = ImGui_ImplDX9_CheckFormatSupport(bd->pd3dDevice, D3DFMT_A8B8G8R8); - - return true; -} - -void ImGui_ImplDX9_Shutdown() -{ - ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); - IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); - ImGuiIO& io = ImGui::GetIO(); - ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); - - ImGui_ImplDX9_InvalidateDeviceObjects(); - if (bd->pd3dDevice) { bd->pd3dDevice->Release(); } - - io.BackendRendererName = nullptr; - io.BackendRendererUserData = nullptr; - io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); - platform_io.ClearRendererHandlers(); - IM_DELETE(bd); -} - // Convert RGBA32 to BGRA32 (because RGBA32 is not well supported by DX9 devices) static void ImGui_ImplDX9_CopyTextureRegion(bool tex_use_colors, const ImU32* src, int src_pitch, ImU32* dst, int dst_pitch, int w, int h) { @@ -431,14 +397,15 @@ void ImGui_ImplDX9_UpdateTexture(ImTextureData* tex) } else if (tex->Status == ImTextureStatus_WantDestroy) { - if (LPDIRECT3DTEXTURE9 backend_tex = (LPDIRECT3DTEXTURE9)tex->TexID) - { - IM_ASSERT(tex->TexID == (ImTextureID)(intptr_t)backend_tex); - backend_tex->Release(); + if (tex->TexID != ImTextureID_Invalid) + if (LPDIRECT3DTEXTURE9 backend_tex = (LPDIRECT3DTEXTURE9)tex->TexID) + { + IM_ASSERT(tex->TexID == (ImTextureID)(intptr_t)backend_tex); + backend_tex->Release(); - // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) - tex->SetTexID(ImTextureID_Invalid); - } + // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) + tex->SetTexID(ImTextureID_Invalid); + } tex->SetStatus(ImTextureStatus_Destroyed); } } @@ -475,6 +442,50 @@ void ImGui_ImplDX9_NewFrame() IM_UNUSED(bd); } + +bool ImGui_ImplDX9_Init(IDirect3DDevice9* device) +{ + ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); + IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); + + // Setup backend capabilities flags + ImGui_ImplDX9_Data* bd = IM_NEW(ImGui_ImplDX9_Data)(); + io.BackendRendererUserData = (void*)bd; + io.BackendRendererName = "imgui_impl_dx9"; + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. + + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = 4096; + platform_io.DrawCallback_ResetRenderState = ImGui_ImplDX9_DrawCallback_ResetRenderState; + platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplDX9_DrawCallback_SetSamplerLinear; + platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplDX9_DrawCallback_SetSamplerNearest; + + bd->pd3dDevice = device; + bd->pd3dDevice->AddRef(); + bd->HasRgbaSupport = ImGui_ImplDX9_CheckFormatSupport(bd->pd3dDevice, D3DFMT_A8B8G8R8); + + return true; +} + +void ImGui_ImplDX9_Shutdown() +{ + ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); + IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + + ImGui_ImplDX9_InvalidateDeviceObjects(); + if (bd->pd3dDevice) { bd->pd3dDevice->Release(); } + + io.BackendRendererName = nullptr; + io.BackendRendererUserData = nullptr; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); + platform_io.ClearRendererHandlers(); + IM_DELETE(bd); +} + //----------------------------------------------------------------------------- #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_glfw.cpp b/backends/imgui_impl_glfw.cpp index da54589fe..678129a07 100644 --- a/backends/imgui_impl_glfw.cpp +++ b/backends/imgui_impl_glfw.cpp @@ -29,6 +29,8 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-04-21: Added a Win32-specific implementation of ImGui_ImplGlfw_GetContentScaleXXXX functions for legacy GLFW 3.2. +// 2026-03-25: Mouse cursor is properly restored if changed by user app/code while using glfwSetInputMode(..., GLFW_CURSOR_DISABLED) or ImGuiConfigFlags_NoMouseCursorChange. Amend change from 2025-12-10. // 2026-02-10: Try to set IMGUI_IMPL_GLFW_DISABLE_X11 / IMGUI_IMPL_GLFW_DISABLE_WAYLAND automatically if corresponding headers are not accessible. (#9225) // 2025-12-12: Added IMGUI_IMPL_GLFW_DISABLE_X11 / IMGUI_IMPL_GLFW_DISABLE_WAYLAND to forcefully disable either. // 2025-12-10: Avoid repeated glfwSetCursor()/glfwSetInputMode() calls when unnecessary. Lowers overhead for very high framerates (e.g. 10k+ FPS). @@ -211,7 +213,7 @@ struct ImGui_ImplGlfw_Data bool IsWayland; bool InstalledCallbacks; bool CallbacksChainForAllWindows; - char BackendPlatformName[32]; + char BackendPlatformName[40]; #ifdef EMSCRIPTEN_USE_EMBEDDED_GLFW3 const char* CanvasSelector; #endif @@ -674,7 +676,13 @@ static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, Glfw // Setup backend capabilities flags ImGui_ImplGlfw_Data* bd = IM_NEW(ImGui_ImplGlfw_Data)(); - snprintf(bd->BackendPlatformName, sizeof(bd->BackendPlatformName), "imgui_impl_glfw (%d)", GLFW_VERSION_COMBINED); + bd->Context = ImGui::GetCurrentContext(); + bd->Window = window; + bd->Time = 0.0; + bd->IsWayland = ImGui_ImplGlfw_IsWayland(); + ImGui_ImplGlfw_ContextMap_Add(window, bd->Context); + + snprintf(bd->BackendPlatformName, sizeof(bd->BackendPlatformName), "imgui_impl_glfw (%d)%s", GLFW_VERSION_COMBINED, bd->IsWayland ? " (Wayland)" : ""); io.BackendPlatformUserData = (void*)bd; io.BackendPlatformName = bd->BackendPlatformName; #if GLFW_HAS_CREATECURSOR @@ -682,12 +690,6 @@ static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, Glfw #endif io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used) - bd->Context = ImGui::GetCurrentContext(); - bd->Window = window; - bd->Time = 0.0; - bd->IsWayland = ImGui_ImplGlfw_IsWayland(); - ImGui_ImplGlfw_ContextMap_Add(window, bd->Context); - ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); #if GLFW_VERSION_COMBINED < 3300 platform_io.Platform_SetClipboardTextFn = [](ImGuiContext*, const char* text) { glfwSetClipboardString(ImGui_ImplGlfw_GetBackendData()->Window, text); }; @@ -859,7 +861,10 @@ static void ImGui_ImplGlfw_UpdateMouseCursor() ImGuiIO& io = ImGui::GetIO(); ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); if ((io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) || glfwGetInputMode(bd->Window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED) + { + bd->LastMouseCursor = nullptr; // Invalidate so that if user changes underlying cursor we will update it next time we can. return; + } ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); // (those braces are here to reduce diff with multi-viewports support in 'docking' branch) @@ -944,6 +949,16 @@ static void ImGui_ImplGlfw_UpdateGamepads() #undef MAP_ANALOG } +// For GFLW 3.2 + Windows: include a simplified non-monitor aware version of ImGui_ImplWin32_GetDpiScaleForMonitor(). +// This is merely a band-aid to make using GLFW 3.2 a little bit nicer, but prefer to use GLFW 3.3+ or the full correct functions from the Win32 backend. +#if !GLFW_HAS_PER_MONITOR_DPI && defined(_WIN32) && !defined(NOGDI) +static float ImGui_ImplWin32_GetLegacyDpiScale() { const HDC dc = ::GetDC(nullptr); UINT xdpi = ::GetDeviceCaps(dc, LOGPIXELSX); ::ReleaseDC(nullptr, dc); return (float)xdpi / 96.0f; } +static void glfwGetWindowContentScale(GLFWwindow*, float* x_scale, float* y_scale) { *x_scale = *y_scale = ImGui_ImplWin32_GetLegacyDpiScale(); } +static void glfwGetMonitorContentScale(GLFWmonitor*, float* x_scale, float* y_scale) { *x_scale = *y_scale = ImGui_ImplWin32_GetLegacyDpiScale(); } +#undef GLFW_HAS_PER_MONITOR_DPI +#define GLFW_HAS_PER_MONITOR_DPI 1 +#endif + // - On Windows the process needs to be marked DPI-aware!! SDL2 doesn't do it by default. You can call ::SetProcessDPIAware() or call ImGui_ImplWin32_EnableDpiAwareness() from Win32 backend. // - Apple platforms use FramebufferScale so we always return 1.0f. // - Some accessibility applications are declaring virtual monitors with a DPI of 0.0f, see #7902. We preserve this value for caller to handle. @@ -1012,7 +1027,7 @@ void ImGui_ImplGlfw_NewFrame() // (Accept glfwGetTime() not returning a monotonically increasing value. Seems to happens on disconnecting peripherals and probably on VMs and Emscripten, see #6491, #6189, #6114, #3644) double current_time = glfwGetTime(); if (current_time <= bd->Time) - current_time = bd->Time + 0.00001f; + current_time = bd->Time + 0.00001; io.DeltaTime = bd->Time > 0.0 ? (float)(current_time - bd->Time) : (float)(1.0f / 60.0f); bd->Time = current_time; diff --git a/backends/imgui_impl_metal.mm b/backends/imgui_impl_metal.mm index 96695d9b7..55b33de14 100644 --- a/backends/imgui_impl_metal.mm +++ b/backends/imgui_impl_metal.mm @@ -16,6 +16,12 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) +// 2026-04-28: Added support for standard draw callbacks (in platform_io): DrawCallback_SetSamplerLinear and DrawCallback_SetSamplerNearest. (#9378, #9381) +// 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState (others are not yet supported). (#9378) +// 2026-04-14: Metal: use a dedicated bufferCacheLock to avoid crashing when bufferCache is replaced by a new object while being used for @synchronize(). (#9367) +// 2026-04-03: Metal: avoid redundant vertex buffer bind in SetupRenderState. (#9343) +// 2026-03-19: Fixed issue in ImGui_ImplMetal_RenderDrawData() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295, #9310) // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplMetal_CreateFontsTexture() and ImGui_ImplMetal_DestroyFontsTexture(). // 2025-02-03: Metal: Crash fix. (#8367) @@ -73,9 +79,12 @@ @interface MetalContext : NSObject @property (nonatomic, strong) id device; @property (nonatomic, strong) id depthStencilState; +@property (nonatomic, strong) id samplerStateLinear; +@property (nonatomic, strong) id samplerStateNearest; @property (nonatomic, strong) FramebufferDescriptor* framebufferDescriptor; // framebuffer descriptor for current frame; transient @property (nonatomic, strong) NSMutableDictionary* renderPipelineStateCache; // pipeline cache; keyed on framebuffer descriptors @property (nonatomic, strong) NSMutableArray* bufferCache; +@property (nonatomic, strong) NSObject* bufferCacheLock; @property (nonatomic, assign) double lastBufferCachePurge; - (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id)device; - (id)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id)device; @@ -84,6 +93,7 @@ struct ImGui_ImplMetal_Data { MetalContext* SharedMetalContext; + id RenderCommandEncoder; ImGui_ImplMetal_Data() { memset((void*)this, 0, sizeof(*this)); } }; @@ -114,7 +124,6 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, ImGui_ImplMetal_RenderDrawData(draw_data, (__bridge id)(commandBuffer), (__bridge id)(commandEncoder)); - } bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device) @@ -126,41 +135,6 @@ bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device) #pragma mark - Dear ImGui Metal Backend API -bool ImGui_ImplMetal_Init(id device) -{ - ImGuiIO& io = ImGui::GetIO(); - IMGUI_CHECKVERSION(); - IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); - - ImGui_ImplMetal_Data* bd = IM_NEW(ImGui_ImplMetal_Data)(); - io.BackendRendererUserData = (void*)bd; - io.BackendRendererName = "imgui_impl_metal"; - io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. - io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. - - bd->SharedMetalContext = [[MetalContext alloc] init]; - bd->SharedMetalContext.device = device; - - return true; -} - -void ImGui_ImplMetal_Shutdown() -{ - ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); - IM_UNUSED(bd); - IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); - ImGuiIO& io = ImGui::GetIO(); - ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); - - ImGui_ImplMetal_DestroyDeviceObjects(); - ImGui_ImplMetal_DestroyBackendData(); - - io.BackendRendererName = nullptr; - io.BackendRendererUserData = nullptr; - io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); - platform_io.ClearRendererHandlers(); -} - void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor) { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); @@ -190,8 +164,8 @@ static void ImGui_ImplMetal_SetupRenderState(ImDrawData* draw_data, idDisplaySize.x * draw_data->FramebufferScale.x), - .height = (double)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y), + .width = (double)(int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f), + .height = (double)(int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f), .znear = 0.0, .zfar = 1.0 }; @@ -213,11 +187,16 @@ static void ImGui_ImplMetal_SetupRenderState(ImDrawData* draw_data, idSharedMetalContext.samplerStateLinear atIndex:0]; - [commandEncoder setVertexBuffer:vertexBuffer.buffer offset:0 atIndex:0]; - [commandEncoder setVertexBufferOffset:vertexBufferOffset atIndex:0]; + [commandEncoder setVertexBuffer:vertexBuffer.buffer offset:vertexBufferOffset atIndex:0]; } +// Draw callbacks +static void ImGui_ImplMetal_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. +static void ImGui_ImplMetal_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); [bd->RenderCommandEncoder setFragmentSamplerState:bd->SharedMetalContext.samplerStateLinear atIndex:0]; } +static void ImGui_ImplMetal_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); [bd->RenderCommandEncoder setFragmentSamplerState:bd->SharedMetalContext.samplerStateNearest atIndex:0]; } + // Metal Render function. void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id commandBuffer, id commandEncoder) { @@ -225,8 +204,8 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id MetalContext* ctx = bd->SharedMetalContext; // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); if (fb_width <= 0 || fb_height <= 0 || draw_data->CmdLists.Size == 0) return; @@ -254,6 +233,7 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id MetalBuffer* vertexBuffer = [ctx dequeueReusableBufferOfLength:vertexBufferLength device:commandBuffer.device]; MetalBuffer* indexBuffer = [ctx dequeueReusableBufferOfLength:indexBufferLength device:commandBuffer.device]; + bd->RenderCommandEncoder = commandEncoder; ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, 0); // Will project scissor/clipping rectangles into framebuffer space @@ -274,8 +254,7 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id if (pcmd->UserCallback) { // User callback, registered via ImDrawList::AddCallback() - // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) - if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + if (pcmd->UserCallback == ImGui_ImplMetal_DrawCallback_ResetRenderState) ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, vertexBufferOffset); else pcmd->UserCallback(draw_list, pcmd); @@ -289,8 +268,8 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id // Clamp to viewport as setScissorRect() won't accept values that are off bounds if (clip_min.x < 0.0f) { clip_min.x = 0.0f; } if (clip_min.y < 0.0f) { clip_min.y = 0.0f; } - if (clip_max.x > fb_width) { clip_max.x = (float)fb_width; } - if (clip_max.y > fb_height) { clip_max.y = (float)fb_height; } + if (clip_max.x > (float)fb_width) { clip_max.x = (float)fb_width; } + if (clip_max.y > (float)fb_height) { clip_max.y = (float)fb_height; } if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) continue; if (pcmd->ElemCount == 0) // drawIndexedPrimitives() validation doesn't accept this @@ -307,7 +286,8 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id [commandEncoder setScissorRect:scissorRect]; // Bind texture, Draw - if (ImTextureID tex_id = pcmd->GetTexID()) + ImTextureID tex_id = pcmd->GetTexID(); + if (tex_id != ImTextureID_Invalid) [commandEncoder setFragmentTexture:(__bridge id)(void*)(intptr_t)(tex_id) atIndex:0]; [commandEncoder setVertexBufferOffset:(vertexBufferOffset + pcmd->VtxOffset * sizeof(ImDrawVert)) atIndex:0]; @@ -326,14 +306,13 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id MetalContext* sharedMetalContext = bd->SharedMetalContext; [commandBuffer addCompletedHandler:^(id) { - dispatch_async(dispatch_get_main_queue(), ^{ - @synchronized(sharedMetalContext.bufferCache) - { - [sharedMetalContext.bufferCache addObject:vertexBuffer]; - [sharedMetalContext.bufferCache addObject:indexBuffer]; - } - }); + @synchronized(sharedMetalContext.bufferCacheLock) + { + [sharedMetalContext.bufferCache addObject:vertexBuffer]; + [sharedMetalContext.bufferCache addObject:indexBuffer]; + } }]; + bd->RenderCommandEncoder = nil; } static void ImGui_ImplMetal_DestroyTexture(ImTextureData* tex) @@ -410,7 +389,17 @@ bool ImGui_ImplMetal_CreateDeviceObjects(id device) depthStencilDescriptor.depthWriteEnabled = NO; depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways; bd->SharedMetalContext.depthStencilState = [device newDepthStencilStateWithDescriptor:depthStencilDescriptor]; + MTLSamplerDescriptor* samplerDescriptor = [[MTLSamplerDescriptor alloc] init]; + samplerDescriptor.minFilter = MTLSamplerMinMagFilterLinear; + samplerDescriptor.magFilter = MTLSamplerMinMagFilterLinear; + samplerDescriptor.mipFilter = MTLSamplerMipFilterLinear; + bd->SharedMetalContext.samplerStateLinear = [device newSamplerStateWithDescriptor:samplerDescriptor]; + samplerDescriptor.minFilter = MTLSamplerMinMagFilterNearest; + samplerDescriptor.magFilter = MTLSamplerMinMagFilterNearest; + samplerDescriptor.mipFilter = MTLSamplerMipFilterNearest; + bd->SharedMetalContext.samplerStateNearest = [device newSamplerStateWithDescriptor:samplerDescriptor]; #ifdef IMGUI_IMPL_METAL_CPP + [samplerDescriptor release]; [depthStencilDescriptor release]; #endif @@ -427,6 +416,48 @@ void ImGui_ImplMetal_DestroyDeviceObjects() ImGui_ImplMetal_DestroyTexture(tex); [bd->SharedMetalContext.renderPipelineStateCache removeAllObjects]; + bd->SharedMetalContext.samplerStateLinear = nil; + bd->SharedMetalContext.samplerStateNearest = nil; +} + +bool ImGui_ImplMetal_Init(id device) +{ + ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); + IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); + + ImGui_ImplMetal_Data* bd = IM_NEW(ImGui_ImplMetal_Data)(); + io.BackendRendererUserData = (void*)bd; + io.BackendRendererName = "imgui_impl_metal"; + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. + + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.DrawCallback_ResetRenderState = ImGui_ImplMetal_DrawCallback_ResetRenderState; + platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplMetal_DrawCallback_SetSamplerLinear; + platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplMetal_DrawCallback_SetSamplerNearest; + + bd->SharedMetalContext = [[MetalContext alloc] init]; + bd->SharedMetalContext.device = device; + + return true; +} + +void ImGui_ImplMetal_Shutdown() +{ + ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + IM_UNUSED(bd); + IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + + ImGui_ImplMetal_DestroyDeviceObjects(); + ImGui_ImplMetal_DestroyBackendData(); + + io.BackendRendererName = nullptr; + io.BackendRendererUserData = nullptr; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); + platform_io.ClearRendererHandlers(); } #pragma mark - MetalBuffer implementation @@ -512,6 +543,7 @@ void ImGui_ImplMetal_DestroyDeviceObjects() { self.renderPipelineStateCache = [NSMutableDictionary dictionary]; self.bufferCache = [NSMutableArray array]; + self.bufferCacheLock = [[NSObject alloc] init]; _lastBufferCachePurge = GetMachAbsoluteTimeInSeconds(); } return self; @@ -519,9 +551,9 @@ void ImGui_ImplMetal_DestroyDeviceObjects() - (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id)device { - uint64_t now = GetMachAbsoluteTimeInSeconds(); + double now = GetMachAbsoluteTimeInSeconds(); - @synchronized(self.bufferCache) + @synchronized(self.bufferCacheLock) { // Purge old buffers that haven't been useful for a while if (now - self.lastBufferCachePurge > 1.0) @@ -553,7 +585,7 @@ void ImGui_ImplMetal_DestroyDeviceObjects() return [[MetalBuffer alloc] initWithBuffer:backing]; } -// Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling. +// Bilinear sampling is required. - (id)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id)device { NSError* error = nil; @@ -588,9 +620,9 @@ void ImGui_ImplMetal_DestroyDeviceObjects() "}\n" "\n" "fragment half4 fragment_main(VertexOut in [[stage_in]],\n" - " texture2d texture [[texture(0)]]) {\n" - " constexpr sampler linearSampler(coord::normalized, min_filter::linear, mag_filter::linear, mip_filter::linear);\n" - " half4 texColor = texture.sample(linearSampler, in.texCoords);\n" + " texture2d texture [[texture(0)]],\n" + " sampler textureSampler [[sampler(0)]]) {\n" + " half4 texColor = texture.sample(textureSampler, in.texCoords);\n" " return half4(in.color) * texColor;\n" "}\n"; diff --git a/backends/imgui_impl_metal4.h b/backends/imgui_impl_metal4.h new file mode 100644 index 000000000..b5b54cad0 --- /dev/null +++ b/backends/imgui_impl_metal4.h @@ -0,0 +1,89 @@ +// dear imgui: Renderer Backend for Metal 4 +// This needs to be used along with a Platform Backend (e.g. OSX) +// Metal 4 requires Apple Silicon and macOS 26+. + +// Implemented features: +// [X] Renderer: User texture binding. Use 'MTLTexture.gpuResourceID' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! +// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). +// Missing features or Issues: +// [ ] Texture view pool support? Reevaluate which type to use for ImtextureID. + +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +#pragma once +#include "imgui.h" // IMGUI_IMPL_API +#ifndef IMGUI_DISABLE + +//----------------------------------------------------------------------------- +// ObjC API +//----------------------------------------------------------------------------- + +#ifdef __OBJC__ + +@class MTL4RenderPassDescriptor; +@protocol MTLDevice, MTL4CommandBuffer, MTL4RenderCommandEncoder, MTL4CommandQueue; + +// framesInFlight must match the number of frames your application keeps in flight (e.g. the size of your own +// command buffer/allocator ring). The backend uses it to size its own per-frame-in-flight resources (constant +// buffer, vertex/index buffer cache) so the CPU never overwrites a slot the GPU may still be reading. +// Follow "Getting Started" link and check examples/ folder to learn about using backends! +IMGUI_IMPL_API bool ImGui_ImplMetal4_Init(id device, id commandQueue, int framesInFlight); +IMGUI_IMPL_API void ImGui_ImplMetal4_Shutdown(); +// frameInFlightIndex must match the slot you use to index your own per-frame-in-flight resources +// (e.g. the same index used to pick your command buffer/allocator), and must be < framesInFlight passed to Init(). +IMGUI_IMPL_API void ImGui_ImplMetal4_NewFrame(MTL4RenderPassDescriptor* renderPassDescriptor, int frameInFlightIndex); +IMGUI_IMPL_API void ImGui_ImplMetal4_RenderDrawData(ImDrawData* drawData, + id commandBuffer, + id commandEncoder); + +// Called by Init/NewFrame/Shutdown +IMGUI_IMPL_API bool ImGui_ImplMetal4_CreateDeviceObjects(id device); +IMGUI_IMPL_API void ImGui_ImplMetal4_DestroyDeviceObjects(); + +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = nullptr to handle this manually. +IMGUI_IMPL_API void ImGui_ImplMetal4_UpdateTexture(ImTextureData* tex); + +#endif + +//----------------------------------------------------------------------------- +// C++ API +//----------------------------------------------------------------------------- + +// Enable Metal C++ binding support with '#define IMGUI_IMPL_METAL_CPP' in your imconfig.h file +// More info about using Metal from C++: https://developer.apple.com/metal/cpp/ + +#ifdef IMGUI_IMPL_METAL_CPP +#ifndef __OBJC__ + +// Forward declare relevant classes from the MTL and MTL4 namespace +namespace MTL { class Device; } +namespace MTL4 { class CommandQueue; class CommandBuffer; class RenderPassDescriptor; class RenderCommandEncoder; } + +// Follow "Getting Started" link and check examples/ folder to learn about using backends! +IMGUI_IMPL_API bool ImGui_ImplMetal4_Init(MTL::Device* device, MTL4::CommandQueue* commandQueue, int framesInFlight); +IMGUI_IMPL_API void ImGui_ImplMetal4_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplMetal4_NewFrame(MTL4::RenderPassDescriptor* renderPassDescriptor, int frameInFlightIndex); +IMGUI_IMPL_API void ImGui_ImplMetal4_RenderDrawData(ImDrawData* draw_data, + MTL4::CommandBuffer* commandBuffer, + MTL4::RenderCommandEncoder* commandEncoder); + +// Called by Init/NewFrame/Shutdown +IMGUI_IMPL_API bool ImGui_ImplMetal4_CreateDeviceObjects(MTL::Device* device); +IMGUI_IMPL_API void ImGui_ImplMetal4_DestroyDeviceObjects(); + +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = nullptr to handle this manually. +IMGUI_IMPL_API void ImGui_ImplMetal4_UpdateTexture(ImTextureData* tex); + +#endif +#endif + +//----------------------------------------------------------------------------- + +#endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_metal4.mm b/backends/imgui_impl_metal4.mm new file mode 100644 index 000000000..73f8c5905 --- /dev/null +++ b/backends/imgui_impl_metal4.mm @@ -0,0 +1,744 @@ +// dear imgui: Renderer Backend for Metal 4 +// This needs to be used along with a Platform Backend (e.g. OSX) +// Metal 4 requires Apple Silicon and macOS 26+. + +// Implemented features: +// [X] Renderer: User texture binding. Use 'MTLTexture' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! +// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). +// Missing features or Issues: +// [ ] Texture view pool support? Reevaluate which type to use for ImtextureID. + +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) +// 2026-07-07: Metal 4: Added metal-cpp support. (#9461) +// 2026-07-02: Metal 4: Added new Metal 4 backend implementation. (#9458) + +#include "imgui.h" +#ifndef IMGUI_DISABLE +#include "imgui_impl_metal4.h" +#import +#import + +#ifdef IMGUI_IMPL_METAL_CPP +#include +#endif + +#pragma mark - Support classes and structs + +struct ImGui_Metal4_ConstantData +{ + float ModelViewProjectionMatrix[4][4]; +}; + +@interface MetalBuffer : NSObject +@property (nonatomic, strong) id buffer; +@property (nonatomic, assign) double lastReuseTime; +- (instancetype)initWithBuffer:(id)buffer; +@end + +// An object that encapsulates the data necessary to uniquely identify a +// render pipeline state. These are used as cache keys. +@interface FramebufferDescriptor : NSObject +@property (nonatomic, assign) unsigned long sampleCount; +@property (nonatomic, assign) MTLPixelFormat colorPixelFormat; +@property (nonatomic, assign) MTLPixelFormat depthPixelFormat; +@property (nonatomic, assign) MTLPixelFormat stencilPixelFormat; +- (instancetype)initWithRenderPassDescriptor:(MTL4RenderPassDescriptor*)renderPassDescriptor; +@end + +@interface MetalTexture : NSObject +@property (nonatomic, strong) id metalTexture; +- (instancetype)initWithTexture:(id)metalTexture; +@end + +// A singleton that stores long-lived objects that are needed by the Metal +// renderer backend. Stores the render pipeline state cache and the default +// font texture, and manages the reusable buffer cache. +@interface MetalContext : NSObject +@property (nonatomic, strong) id device; +@property (nonatomic, strong) id commandQueue; +@property (nonatomic, strong) id depthStencilState; +@property (nonatomic, strong) id argumentTable; +@property (nonatomic, strong) id samplerStateLinear; +@property (nonatomic, strong) id samplerStateNearest; +@property (nonatomic, strong) id residencySet; +@property (nonatomic, strong) FramebufferDescriptor* framebufferDescriptor; +@property (nonatomic, strong) NSMutableDictionary* renderPipelineStateCache; +@property (nonatomic, assign) NSUInteger framesInFlight; +@property (nonatomic, assign) NSUInteger currentFrameSlot; +@property (nonatomic, strong) NSArray>* constantBuffers; +@property (nonatomic, assign) ImGui_Metal4_ConstantData** constantBufferContentsArray; +@property (nonatomic, strong) NSMutableArray*>* bufferCaches; +@property (nonatomic, strong) NSObject* bufferCacheLock; +@property (nonatomic, assign) double lastBufferCachePurge; +- (id)currentConstantBuffer; +- (ImGui_Metal4_ConstantData*)currentConstantBufferContents; +- (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id)device; +- (id)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id)device; +@end + +struct ImGui_ImplMetal4_Data +{ + MetalContext* SharedMetalContext; + id RenderCommandEncoder; + + ImGui_ImplMetal4_Data() { memset((void*)this, 0, sizeof(*this)); } +}; + +static ImGui_ImplMetal4_Data* ImGui_ImplMetal4_GetBackendData() { return ImGui::GetCurrentContext() ? (ImGui_ImplMetal4_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +static void ImGui_ImplMetal4_DestroyBackendData(){ IM_DELETE(ImGui_ImplMetal4_GetBackendData()); } + +static inline CFTimeInterval GetMachAbsoluteTimeInSeconds() { return (CFTimeInterval)(double)(clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / 1e9); } + +#ifdef IMGUI_IMPL_METAL_CPP + +#pragma mark - Dear ImGui Metal C++ Backend API + +bool ImGui_ImplMetal4_Init(MTL::Device* device, MTL4::CommandQueue* commandQueue, int framesInFlight) +{ + return ImGui_ImplMetal4_Init((__bridge id)(device),(__bridge id)(commandQueue), framesInFlight); +} + +void ImGui_ImplMetal4_NewFrame(MTL4::RenderPassDescriptor* renderPassDescriptor, int frameInFlightIndex) +{ + ImGui_ImplMetal4_NewFrame((__bridge MTL4RenderPassDescriptor*)(renderPassDescriptor), frameInFlightIndex); +} + +void ImGui_ImplMetal4_RenderDrawData(ImDrawData* draw_data, + MTL4::CommandBuffer* commandBuffer, + MTL4::RenderCommandEncoder* commandEncoder) +{ + ImGui_ImplMetal4_RenderDrawData(draw_data, + (__bridge id)(commandBuffer), + (__bridge id)(commandEncoder)); +} + +bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device) +{ + return ImGui_ImplMetal4_CreateDeviceObjects((__bridge id)(device)); +} + +#endif // #ifdef IMGUI_IMPL_METAL_CPP + +#pragma mark - Dear ImGui Metal Backend API + +void ImGui_ImplMetal4_NewFrame(MTL4RenderPassDescriptor* renderPassDescriptor, int frameInFlightIndex) +{ + ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); + IM_ASSERT(bd != nil && "Context or backend not initialized! Did you call ImGui_ImplMetal4_Init()?"); + IM_ASSERT(frameInFlightIndex < bd->SharedMetalContext.framesInFlight && "frameInFlightIndex out of range! See framesInFlight passed to ImGui_ImplMetal4_Init()."); +#ifdef IMGUI_IMPL_METAL_CPP + bd->SharedMetalContext.framebufferDescriptor = [[[FramebufferDescriptor alloc] initWithRenderPassDescriptor:renderPassDescriptor]autorelease]; +#else + bd->SharedMetalContext.framebufferDescriptor = [[FramebufferDescriptor alloc] initWithRenderPassDescriptor:renderPassDescriptor]; +#endif + bd->SharedMetalContext.currentFrameSlot = (NSUInteger)frameInFlightIndex; + if (bd->SharedMetalContext.depthStencilState == nil) + ImGui_ImplMetal4_CreateDeviceObjects(bd->SharedMetalContext.device); +} + +static void ImGui_ImplMetal4_SetupRenderState(ImDrawData* draw_data, id commandBuffer, + id commandEncoder, id renderPipelineState, + MetalBuffer* vertexBuffer, size_t vertexBufferOffset) +{ + IM_UNUSED(commandBuffer); + ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); + [commandEncoder setCullMode:MTLCullModeNone]; + [commandEncoder setDepthStencilState:bd->SharedMetalContext.depthStencilState]; + + // Setup viewport, orthographic projection matrix + // Our visible imgui space lies from draw_data->DisplayPos (top left) to + // draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayMin is typically (0,0) for single viewport apps. + MTLViewport viewport = + { + .originX = 0.0, + .originY = 0.0, + .width = (double)(int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f), + .height = (double)(int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f), + .znear = 0.0, + .zfar = 1.0 + }; + [commandEncoder setViewport:viewport]; + + float L = draw_data->DisplayPos.x; + float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; + float T = draw_data->DisplayPos.y; + float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; + float N = (float)viewport.znear; + float F = (float)viewport.zfar; + const float ortho_projection[4][4] = + { + { 2.0f/(R-L), 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f/(T-B), 0.0f, 0.0f }, + { 0.0f, 0.0f, 1/(F-N), 0.0f }, + { (R+L)/(L-R), (T+B)/(B-T), N/(F-N), 1.0f }, + }; + ImGui_Metal4_ConstantData* constantBufferContents = [bd->SharedMetalContext currentConstantBufferContents]; + memcpy(constantBufferContents->ModelViewProjectionMatrix, ortho_projection, sizeof(ortho_projection)); + + id argumentTable = bd->SharedMetalContext.argumentTable; + [argumentTable setAddress:[bd->SharedMetalContext currentConstantBuffer].gpuAddress atIndex:1]; + [argumentTable setAddress:(vertexBuffer.buffer.gpuAddress + vertexBufferOffset) attributeStride:sizeof(ImDrawVert) atIndex:0]; + [argumentTable setSamplerState:bd->SharedMetalContext.samplerStateLinear.gpuResourceID atIndex:0]; + [commandEncoder setArgumentTable:argumentTable atStages:MTLRenderStageVertex | MTLRenderStageFragment]; + [commandEncoder setRenderPipelineState:renderPipelineState]; +} + +static void ImGui_ImplMetal4_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. +static void ImGui_ImplMetal4_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); [bd->SharedMetalContext.argumentTable setSamplerState:bd->SharedMetalContext.samplerStateLinear.gpuResourceID atIndex:0]; } +static void ImGui_ImplMetal4_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); [bd->SharedMetalContext.argumentTable setSamplerState:bd->SharedMetalContext.samplerStateNearest.gpuResourceID atIndex:0]; } + +void ImGui_ImplMetal4_RenderDrawData(ImDrawData* draw_data, id commandBuffer, id commandEncoder) +{ + ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); + MetalContext* ctx = bd->SharedMetalContext; + + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); + if (fb_width <= 0 || fb_height <= 0 || draw_data->CmdLists.Size == 0) + return; + + // Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do. + // (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates). + if (draw_data->Textures != nullptr) + for (ImTextureData* tex : *draw_data->Textures) + if (tex->Status != ImTextureStatus_OK) + ImGui_ImplMetal4_UpdateTexture(tex); + + // Try to retrieve a render pipeline state that is compatible with the framebuffer config for this frame + // The hit rate for this cache should be very near 100%. + id renderPipelineState = ctx.renderPipelineStateCache[ctx.framebufferDescriptor]; + if (renderPipelineState == nil) + { + // No luck; make a new render pipeline state + renderPipelineState = [ctx renderPipelineStateForFramebufferDescriptor:ctx.framebufferDescriptor device:commandBuffer.device]; + + // Cache render pipeline state for later reuse + ctx.renderPipelineStateCache[ctx.framebufferDescriptor] = renderPipelineState; + } + + size_t vertexBufferLength = (size_t)draw_data->TotalVtxCount * sizeof(ImDrawVert); + size_t indexBufferLength = (size_t)draw_data->TotalIdxCount * sizeof(ImDrawIdx); + MetalBuffer* vertexBuffer = [ctx dequeueReusableBufferOfLength:vertexBufferLength device:commandBuffer.device]; + MetalBuffer* indexBuffer = [ctx dequeueReusableBufferOfLength:indexBufferLength device:commandBuffer.device]; + + bd->RenderCommandEncoder = commandEncoder; + ImGui_ImplMetal4_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, 0); + + // Will project scissor/clipping rectangles into framebuffer space + ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports + ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) + + // Render command lists + size_t vertexBufferOffset = 0; + size_t indexBufferOffset = 0; + for (const ImDrawList* draw_list : draw_data->CmdLists) + { + memcpy((char*)vertexBuffer.buffer.contents + vertexBufferOffset, draw_list->VtxBuffer.Data, (size_t)draw_list->VtxBuffer.Size * sizeof(ImDrawVert)); + memcpy((char*)indexBuffer.buffer.contents + indexBufferOffset, draw_list->IdxBuffer.Data, (size_t)draw_list->IdxBuffer.Size * sizeof(ImDrawIdx)); + + for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback) + { + // User callback, registered via ImDrawList::AddCallback() + if (pcmd->UserCallback == ImGui_ImplMetal4_DrawCallback_ResetRenderState) + ImGui_ImplMetal4_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, vertexBufferOffset); + else + pcmd->UserCallback(draw_list, pcmd); + } + else + { + // Project scissor/clipping rectangles into framebuffer space + ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y); + ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y); + + // Clamp to viewport as setScissorRect() won't accept values that are off bounds + if (clip_min.x < 0.0f) { clip_min.x = 0.0f; } + if (clip_min.y < 0.0f) { clip_min.y = 0.0f; } + if (clip_max.x > (float)fb_width) { clip_max.x = (float)fb_width; } + if (clip_max.y > (float)fb_height) { clip_max.y = (float)fb_height; } + if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) + continue; + if (pcmd->ElemCount == 0) // drawIndexedPrimitives() validation doesn't accept this + continue; + + // Apply scissor/clipping rectangle + MTLScissorRect scissorRect = + { + .x = NSUInteger(clip_min.x), + .y = NSUInteger(clip_min.y), + .width = NSUInteger(clip_max.x - clip_min.x), + .height = NSUInteger(clip_max.y - clip_min.y) + }; + [commandEncoder setScissorRect:scissorRect]; + + // Bind texture, Draw + ImTextureID tex_id = pcmd->GetTexID(); + if (tex_id != ImTextureID_Invalid) + { + id texture = (__bridge id)(void*)(intptr_t)tex_id; + [bd->SharedMetalContext.residencySet addAllocation:texture]; + [bd->SharedMetalContext.argumentTable setTexture:texture.gpuResourceID atIndex:0]; + } + + [bd->SharedMetalContext.argumentTable setAddress:(vertexBuffer.buffer.gpuAddress + vertexBufferOffset + (pcmd->VtxOffset * sizeof(ImDrawVert))) attributeStride:sizeof(ImDrawVert) atIndex:0]; + + size_t indexBufferCmdOffset = indexBufferOffset + (pcmd->IdxOffset * sizeof(ImDrawIdx)); + [commandEncoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle + indexCount:pcmd->ElemCount + indexType:sizeof(ImDrawIdx) == 2 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32 + indexBuffer:indexBuffer.buffer.gpuAddress + indexBufferCmdOffset + indexBufferLength:indexBuffer.buffer.length - indexBufferCmdOffset]; + } + } + + vertexBufferOffset += (size_t)draw_list->VtxBuffer.Size * sizeof(ImDrawVert); + indexBufferOffset += (size_t)draw_list->IdxBuffer.Size * sizeof(ImDrawIdx); + } + + MetalContext* sharedMetalContext = bd->SharedMetalContext; + @synchronized(sharedMetalContext.bufferCacheLock) + { + NSMutableArray* slotCache = sharedMetalContext.bufferCaches[sharedMetalContext.currentFrameSlot]; + [slotCache addObject:vertexBuffer]; + [slotCache addObject:indexBuffer]; + } + + // Commit residency set + [bd->SharedMetalContext.residencySet commit]; + bd->RenderCommandEncoder = nil; +} + +static void ImGui_ImplMetal4_DestroyTexture(ImTextureData* tex) +{ + if (MetalTexture* backend_tex = (__bridge_transfer MetalTexture*)(tex->BackendUserData)) + { + IM_ASSERT(backend_tex.metalTexture == (__bridge id)(void*)(intptr_t)tex->TexID); + backend_tex.metalTexture = nil; + + // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) + tex->SetTexID(ImTextureID_Invalid); + tex->BackendUserData = nullptr; + } + tex->SetStatus(ImTextureStatus_Destroyed); +} + +void ImGui_ImplMetal4_UpdateTexture(ImTextureData* tex) +{ + ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); + if (tex->Status == ImTextureStatus_WantCreate) + { + // Create and upload new texture to graphics system + //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); + IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); + + // We are retrieving and uploading the font atlas as a 4-channels RGBA texture here. + // In theory we could call GetTexDataAsAlpha8() and upload a 1-channel texture to save on memory access bandwidth. + // However, using a shader designed for 1-channel texture would make it less obvious to use the ImTextureID facility to render users own textures. + // You can make that change in your implementation. + MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm + width:(NSUInteger)tex->Width + height:(NSUInteger)tex->Height + mipmapped:NO]; + textureDescriptor.usage = MTLTextureUsageShaderRead; + textureDescriptor.storageMode = MTLStorageModeShared; + + id texture = [bd->SharedMetalContext.device newTextureWithDescriptor:textureDescriptor]; + [bd->SharedMetalContext.residencySet addAllocation:texture]; + [texture replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)tex->Width, (NSUInteger)tex->Height) mipmapLevel:0 withBytes:tex->Pixels bytesPerRow:(NSUInteger)tex->Width * 4]; + MetalTexture* backend_tex = [[MetalTexture alloc] initWithTexture:texture]; + + // Store identifiers + tex->SetTexID((ImTextureID)(intptr_t)texture); + tex->SetStatus(ImTextureStatus_OK); + tex->BackendUserData = (__bridge_retained void*)(backend_tex); + } + else if (tex->Status == ImTextureStatus_WantUpdates) + { + // Update selected blocks. We only ever write to textures regions which have never been used before! + // This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region. + MetalTexture* backend_tex = (__bridge MetalTexture*)(tex->BackendUserData); + for (ImTextureRect& r : tex->Updates) + { + [backend_tex.metalTexture replaceRegion:MTLRegionMake2D((NSUInteger)r.x, (NSUInteger)r.y, (NSUInteger)r.w, (NSUInteger)r.h) + mipmapLevel:0 + withBytes:tex->GetPixelsAt(r.x, r.y) + bytesPerRow:(NSUInteger)tex->Width * 4]; + } + tex->SetStatus(ImTextureStatus_OK); + } + else if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0) + { + ImGui_ImplMetal4_DestroyTexture(tex); + } +} + +bool ImGui_ImplMetal4_CreateDeviceObjects(id device) +{ + ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); + + MTLResidencySetDescriptor* residencySetDescriptor = [[MTLResidencySetDescriptor alloc] init]; + residencySetDescriptor.initialCapacity = 1000; + + NSError* error = nil; + bd->SharedMetalContext.residencySet = [device newResidencySetWithDescriptor:residencySetDescriptor error:&error]; + IM_ASSERT(bd->SharedMetalContext.residencySet != nil && error == nil); + + [bd->SharedMetalContext.commandQueue addResidencySet:bd->SharedMetalContext.residencySet]; + + MTLDepthStencilDescriptor* depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init]; + depthStencilDescriptor.depthWriteEnabled = NO; + depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways; + bd->SharedMetalContext.depthStencilState = [device newDepthStencilStateWithDescriptor:depthStencilDescriptor]; + MTLSamplerDescriptor* samplerDescriptor = [[MTLSamplerDescriptor alloc] init]; + samplerDescriptor.supportArgumentBuffers = YES; + samplerDescriptor.minFilter = MTLSamplerMinMagFilterLinear; + samplerDescriptor.magFilter = MTLSamplerMinMagFilterLinear; + samplerDescriptor.mipFilter = MTLSamplerMipFilterLinear; + bd->SharedMetalContext.samplerStateLinear = [device newSamplerStateWithDescriptor:samplerDescriptor]; + samplerDescriptor.minFilter = MTLSamplerMinMagFilterNearest; + samplerDescriptor.magFilter = MTLSamplerMinMagFilterNearest; + samplerDescriptor.mipFilter = MTLSamplerMipFilterNearest; + bd->SharedMetalContext.samplerStateNearest = [device newSamplerStateWithDescriptor:samplerDescriptor]; + + NSMutableArray>* constantBuffers = [NSMutableArray array]; + ImGui_Metal4_ConstantData** constantBufferContentsArray = (ImGui_Metal4_ConstantData**)malloc(sizeof(ImGui_Metal4_ConstantData*) * bd->SharedMetalContext.framesInFlight); + for (NSUInteger i = 0; i < bd->SharedMetalContext.framesInFlight; i++) + { + id constantBuffer = [device newBufferWithLength:sizeof(ImGui_Metal4_ConstantData) options:MTLResourceStorageModeShared]; + [constantBuffers addObject:constantBuffer]; + constantBufferContentsArray[i] = (ImGui_Metal4_ConstantData*)constantBuffer.contents; + [bd->SharedMetalContext.residencySet addAllocation:constantBuffer]; + } + bd->SharedMetalContext.constantBuffers = constantBuffers; + bd->SharedMetalContext.constantBufferContentsArray = constantBufferContentsArray; + + MTL4ArgumentTableDescriptor* argumentTableDescriptor = [[MTL4ArgumentTableDescriptor alloc] init]; + argumentTableDescriptor.maxBufferBindCount = 8; + argumentTableDescriptor.maxTextureBindCount = 8; + argumentTableDescriptor.maxSamplerStateBindCount = 8; + argumentTableDescriptor.supportAttributeStrides = YES; // required: vertex buffer is bound via setAddress:stride:atIndex: for stage_in fetch + + bd->SharedMetalContext.argumentTable = [device newArgumentTableWithDescriptor:argumentTableDescriptor error:&error]; + IM_ASSERT(bd->SharedMetalContext.argumentTable != nil && error == nil); + + return true; +} + +void ImGui_ImplMetal4_DestroyDeviceObjects() +{ + ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); + + // Destroy all textures + for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) + if (tex->RefCount == 1) + ImGui_ImplMetal4_DestroyTexture(tex); + + [bd->SharedMetalContext.renderPipelineStateCache removeAllObjects]; + bd->SharedMetalContext.samplerStateLinear = nil; + bd->SharedMetalContext.samplerStateNearest = nil; +} + +bool ImGui_ImplMetal4_Init(id device, id commandQueue, int framesInFlight) +{ + ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); + IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); + IM_ASSERT(framesInFlight > 0 && "framesInFlight must be at least 1!"); + + ImGui_ImplMetal4_Data* bd = IM_NEW(ImGui_ImplMetal4_Data)(); + io.BackendRendererUserData = (void*)bd; + io.BackendRendererName = "imgui_impl_metal4"; + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. + + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.DrawCallback_ResetRenderState = ImGui_ImplMetal4_DrawCallback_ResetRenderState; + platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplMetal4_DrawCallback_SetSamplerLinear; + platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplMetal4_DrawCallback_SetSamplerNearest; + + bd->SharedMetalContext = [[MetalContext alloc] init]; + bd->SharedMetalContext.device = device; + bd->SharedMetalContext.commandQueue = commandQueue; + bd->SharedMetalContext.framesInFlight = (NSUInteger)framesInFlight; + NSMutableArray*>* bufferCaches = [NSMutableArray array]; + for (NSUInteger i = 0; i < framesInFlight; i++) + [bufferCaches addObject:[NSMutableArray array]]; + bd->SharedMetalContext.bufferCaches = bufferCaches; + return true; +} + +void ImGui_ImplMetal4_Shutdown() +{ + ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); + IM_UNUSED(bd); + IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + + ImGui_ImplMetal4_DestroyDeviceObjects(); + ImGui_ImplMetal4_DestroyBackendData(); + + io.BackendRendererName = nullptr; + io.BackendRendererUserData = nullptr; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); + platform_io.ClearRendererHandlers(); +} + +@implementation MetalBuffer +- (instancetype)initWithBuffer:(id)buffer +{ + if ((self = [super init])) + { + _buffer = buffer; + _lastReuseTime = GetMachAbsoluteTimeInSeconds(); + } + return self; +} +@end + +#pragma mark - FramebufferDescriptor implementation + +@implementation FramebufferDescriptor +- (instancetype)initWithRenderPassDescriptor:(MTL4RenderPassDescriptor*)renderPassDescriptor +{ + if ((self = [super init])) + { + _sampleCount = renderPassDescriptor.colorAttachments[0].texture.sampleCount; + _colorPixelFormat = renderPassDescriptor.colorAttachments[0].texture.pixelFormat; + _depthPixelFormat = renderPassDescriptor.depthAttachment.texture.pixelFormat; + _stencilPixelFormat = renderPassDescriptor.stencilAttachment.texture.pixelFormat; + } + return self; +} + +- (nonnull id)copyWithZone:(nullable NSZone*)zone +{ + FramebufferDescriptor* copy = [[FramebufferDescriptor allocWithZone:zone] init]; + copy.sampleCount = self.sampleCount; + copy.colorPixelFormat = self.colorPixelFormat; + copy.depthPixelFormat = self.depthPixelFormat; + copy.stencilPixelFormat = self.stencilPixelFormat; + return copy; +} + +- (NSUInteger)hash +{ + NSUInteger sc = _sampleCount & 0x3; + NSUInteger cf = _colorPixelFormat & 0x3FF; + NSUInteger df = _depthPixelFormat & 0x3FF; + NSUInteger sf = _stencilPixelFormat & 0x3FF; + NSUInteger hash = (sf << 22) | (df << 12) | (cf << 2) | sc; + return hash; +} + +- (BOOL)isEqual:(id)object +{ + FramebufferDescriptor* other = object; + if (![other isKindOfClass:[FramebufferDescriptor class]]) + return NO; + return other.sampleCount == self.sampleCount && + other.colorPixelFormat == self.colorPixelFormat && + other.depthPixelFormat == self.depthPixelFormat && + other.stencilPixelFormat == self.stencilPixelFormat; +} + +@end + +#pragma mark - MetalTexture implementation + +@implementation MetalTexture +- (instancetype)initWithTexture:(id)metalTexture +{ + if ((self = [super init])) + self.metalTexture = metalTexture; + return self; +} + +@end + +#pragma mark - MetalContext implementation + +@implementation MetalContext +- (instancetype)init +{ + if ((self = [super init])) + { + self.renderPipelineStateCache = [NSMutableDictionary dictionary]; + self.bufferCacheLock = [[NSObject alloc] init]; + _lastBufferCachePurge = GetMachAbsoluteTimeInSeconds(); + } + return self; +} + +- (void)dealloc +{ + free(_constantBufferContentsArray); +} + +- (id)currentConstantBuffer +{ + return self.constantBuffers[self.currentFrameSlot]; +} + +- (ImGui_Metal4_ConstantData*)currentConstantBufferContents +{ + return self.constantBufferContentsArray[self.currentFrameSlot]; +} + +- (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id)device +{ + double now = GetMachAbsoluteTimeInSeconds(); + NSMutableArray* slotCache = self.bufferCaches[self.currentFrameSlot]; + + @synchronized(self.bufferCacheLock) + { + // Purge old buffers that haven't been useful for a while + if (now - self.lastBufferCachePurge > 1.0) + { + for (NSMutableArray* cache in self.bufferCaches) + { + NSMutableArray* survivors = [NSMutableArray array]; + for (MetalBuffer* candidate in cache) + if (candidate.lastReuseTime > self.lastBufferCachePurge) + [survivors addObject:candidate]; + [cache setArray:survivors]; + } + self.lastBufferCachePurge = now; + } + + // See if we have a buffer we can reuse, from this frame-in-flight slot's own cache + MetalBuffer* bestCandidate = nil; + for (MetalBuffer* candidate in slotCache) + if (candidate.buffer.length >= length && (bestCandidate == nil || bestCandidate.lastReuseTime > candidate.lastReuseTime)) + bestCandidate = candidate; + + if (bestCandidate != nil) + { + [slotCache removeObject:bestCandidate]; + bestCandidate.lastReuseTime = now; + return bestCandidate; + } + } + + // No luck; make a new buffer + id backing = [device newBufferWithLength:length options:MTLResourceStorageModeShared]; + [self.residencySet addAllocation:backing]; + return [[MetalBuffer alloc] initWithBuffer:backing]; +} + +const char* shaderCode = R"( +#include +using namespace metal; + +struct Uniforms { + float4x4 projectionMatrix; +}; + +struct VertexIn { + float2 position [[attribute(0)]]; + float2 texCoords [[attribute(1)]]; + uchar4 color [[attribute(2)]]; +}; + +struct VertexOut { + float4 position [[position]]; + float2 texCoords; + float4 color; +}; + +vertex VertexOut vertex_main(VertexIn in [[stage_in]], + constant Uniforms &uniforms [[buffer(1)]]) +{ + VertexOut out; + out.position = uniforms.projectionMatrix * float4(in.position, 0, 1); + out.texCoords = in.texCoords; + out.color = float4(in.color) / float4(255.0); + return out; +} + +fragment half4 fragment_main(VertexOut in [[stage_in]], + texture2d texture [[texture(0)]], + sampler textureSampler [[sampler(0)]]) +{ + half4 texColor = texture.sample(textureSampler, in.texCoords); + return half4(in.color) * texColor; +} +)"; + +- (id)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id)device +{ + NSError* error = nil; + + id library = [device newLibraryWithSource:[NSString stringWithUTF8String:shaderCode] options:nil error:&error]; + if (library == nil) + { + NSLog(@"Error: failed to create Metal library: %@", error); + return nil; + } + + id vertexFunction = [library newFunctionWithName:@"vertex_main"]; + id fragmentFunction = [library newFunctionWithName:@"fragment_main"]; + + if (vertexFunction == nil || fragmentFunction == nil) + { + NSLog(@"Error: failed to find Metal shader functions in library: %@", error); + return nil; + } + + MTLVertexDescriptor* vertexDescriptor = [MTLVertexDescriptor vertexDescriptor]; + vertexDescriptor.attributes[0].offset = offsetof(ImDrawVert, pos); + vertexDescriptor.attributes[0].format = MTLVertexFormatFloat2; // position + vertexDescriptor.attributes[0].bufferIndex = 0; + vertexDescriptor.attributes[1].offset = offsetof(ImDrawVert, uv); + vertexDescriptor.attributes[1].format = MTLVertexFormatFloat2; // texCoords + vertexDescriptor.attributes[1].bufferIndex = 0; + vertexDescriptor.attributes[2].offset = offsetof(ImDrawVert, col); + vertexDescriptor.attributes[2].format = MTLVertexFormatUChar4; // color + vertexDescriptor.attributes[2].bufferIndex = 0; + vertexDescriptor.layouts[0].stepRate = 1; + vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex; + vertexDescriptor.layouts[0].stride = sizeof(ImDrawVert); + + MTLRenderPipelineDescriptor* pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; + pipelineDescriptor.vertexFunction = vertexFunction; + pipelineDescriptor.fragmentFunction = fragmentFunction; + pipelineDescriptor.vertexDescriptor = vertexDescriptor; + pipelineDescriptor.rasterSampleCount = self.framebufferDescriptor.sampleCount; + pipelineDescriptor.colorAttachments[0].pixelFormat = self.framebufferDescriptor.colorPixelFormat; + pipelineDescriptor.colorAttachments[0].blendingEnabled = YES; + pipelineDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd; + pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha; + pipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + pipelineDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd; + pipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne; + pipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + pipelineDescriptor.depthAttachmentPixelFormat = self.framebufferDescriptor.depthPixelFormat; + pipelineDescriptor.stencilAttachmentPixelFormat = self.framebufferDescriptor.stencilPixelFormat; + + id renderPipelineState = [device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error]; + if (error != nil) + NSLog(@"Error: failed to create Metal pipeline state: %@", error); + + return renderPipelineState; +} + +@end + +//----------------------------------------------------------------------------- + +#endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_opengl2.cpp b/backends/imgui_impl_opengl2.cpp index af5618fb6..88eed8c5f 100644 --- a/backends/imgui_impl_opengl2.cpp +++ b/backends/imgui_impl_opengl2.cpp @@ -6,6 +6,7 @@ // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // Missing features or Issues: // [ ] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [ ] Renderer: Use of DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest is emulated by poking to glTexParameter(), as legacy OpenGL doesn't have glBindSampler(). // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. @@ -25,6 +26,11 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-17: OpenGL: Added support for platform_io.DrawCallback_SetSamplerFromTex. (#9378) +// 2026-09-07: OpenGL: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) +// 2026-07-15: OpenGL: Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT in UpdateTexture() to avoid corrupting caller GL state. (#8802, #9473) +// 2026-04-23: OpenGL: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) +// 2026-03-12: OpenGL: Fixed invalid assert in ImGui_ImplOpenGL3_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295) // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-07-15: OpenGL: Set GL_UNPACK_ALIGNMENT to 1 before updating textures. (#8802) // 2025-06-11: OpenGL: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplOpenGL2_CreateFontsTexture() and ImGui_ImplOpenGL2_DestroyFontsTexture(). @@ -85,6 +91,9 @@ // OpenGL data struct ImGui_ImplOpenGL2_Data { + bool UseTexParameterToSetSampler; + GLuint NextSampler; + ImGui_ImplOpenGL2_Data() { memset((void*)this, 0, sizeof(*this)); } }; @@ -96,37 +105,6 @@ static ImGui_ImplOpenGL2_Data* ImGui_ImplOpenGL2_GetBackendData() } // Functions -bool ImGui_ImplOpenGL2_Init() -{ - ImGuiIO& io = ImGui::GetIO(); - IMGUI_CHECKVERSION(); - IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); - - // Setup backend capabilities flags - ImGui_ImplOpenGL2_Data* bd = IM_NEW(ImGui_ImplOpenGL2_Data)(); - io.BackendRendererUserData = (void*)bd; - io.BackendRendererName = "imgui_impl_opengl2"; - io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. - - return true; -} - -void ImGui_ImplOpenGL2_Shutdown() -{ - ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); - IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); - ImGuiIO& io = ImGui::GetIO(); - ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); - - ImGui_ImplOpenGL2_DestroyDeviceObjects(); - - io.BackendRendererName = nullptr; - io.BackendRendererUserData = nullptr; - io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasTextures); - platform_io.ClearRendererHandlers(); - IM_DELETE(bd); -} - void ImGui_ImplOpenGL2_NewFrame() { ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); @@ -137,6 +115,7 @@ void ImGui_ImplOpenGL2_NewFrame() static void ImGui_ImplOpenGL2_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height) { // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers, polygon fill. + ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // In order to composite our output buffer we need to preserve alpha @@ -155,6 +134,9 @@ static void ImGui_ImplOpenGL2_SetupRenderState(ImDrawData* draw_data, int fb_wid glShadeModel(GL_SMOOTH); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + bd->UseTexParameterToSetSampler = false; + bd->NextSampler = GL_LINEAR; + // If you are using this code with non-legacy OpenGL header/contexts (which you should not, prefer using imgui_impl_opengl3.cpp!!), // you may need to backup/reset/restore other state, e.g. for current shader using the commented lines below. // (DO NOT MODIFY THIS FILE! Add the code in your calling function) @@ -178,14 +160,24 @@ static void ImGui_ImplOpenGL2_SetupRenderState(ImDrawData* draw_data, int fb_wid glLoadIdentity(); } +// Draw callbacks +// - IMPORTANT: Using DrawCallback_SetSamplerLinear/DrawCallback_SetSamplerNearest will trash glTexParameteri() values for the texture!! +// We cannot decently backup/restore that state because glGetTexParameteriv() is slow on many setups. +// - Using DrawCallback_SetSamplerFromTex will basically instruct the backend to use existing glTexParameteri() values without modifying anything, which is the initial state. +static void ImGui_ImplOpenGL2_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. +static void ImGui_ImplOpenGL2_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); bd->UseTexParameterToSetSampler = true; bd->NextSampler = GL_LINEAR; } +static void ImGui_ImplOpenGL2_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); bd->UseTexParameterToSetSampler = true; bd->NextSampler = GL_NEAREST; } +static void ImGui_ImplOpenGL2_DrawCallback_SetSamplerFromTex(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); bd->UseTexParameterToSetSampler = false; } + // OpenGL2 Render function. // Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly. // This is in order to be able to run within an OpenGL engine that doesn't do so. void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data) { // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); if (fb_width == 0 || fb_height == 0) return; @@ -227,8 +219,7 @@ void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data) if (pcmd->UserCallback) { // User callback, registered via ImDrawList::AddCallback() - // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) - if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + if (pcmd->UserCallback == ImGui_ImplOpenGL2_DrawCallback_ResetRenderState) ImGui_ImplOpenGL2_SetupRenderState(draw_data, fb_width, fb_height); else pcmd->UserCallback(draw_list, pcmd); @@ -245,7 +236,17 @@ void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data) glScissor((int)clip_min.x, (int)((float)fb_height - clip_max.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y)); // Bind texture, Draw - glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID()); + GLuint pcmd_texture = (GLuint)(intptr_t)pcmd->GetTexID(); + glBindTexture(GL_TEXTURE_2D, pcmd_texture); + + // Emulate sampler change (even though it is technically part of texture data) + // As a sort of hack/workaround, we only start writing using glTextParameter() if sampler is ever changed explicitly. + if (bd->UseTexParameterToSetSampler) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, bd->NextSampler); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, bd->NextSampler); + } + glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer + pcmd->IdxOffset); } } @@ -270,17 +271,20 @@ void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data) void ImGui_ImplOpenGL2_UpdateTexture(ImTextureData* tex) { + // Backup GL_UNPACK state that we modify, restore on exit. + GLint last_unpack_row_length = 0; GL_CALL(glGetIntegerv(GL_UNPACK_ROW_LENGTH, &last_unpack_row_length)); + GLint last_unpack_alignment = 0; GL_CALL(glGetIntegerv(GL_UNPACK_ALIGNMENT, &last_unpack_alignment)); + if (tex->Status == ImTextureStatus_WantCreate) { // Create and upload new texture to graphics system //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); - IM_ASSERT(tex->TexID == 0 && tex->BackendUserData == nullptr); + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); const void* pixels = tex->GetPixels(); GLuint gl_texture_id = 0; - // Upload texture to graphics system - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) + // Upload texture to graphics system (bilinear sampling is required). GLint last_texture; GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture)); GL_CALL(glGenTextures(1, &gl_texture_id)); @@ -326,6 +330,10 @@ void ImGui_ImplOpenGL2_UpdateTexture(ImTextureData* tex) tex->SetTexID(ImTextureID_Invalid); tex->SetStatus(ImTextureStatus_Destroyed); } + + // Restore GL_UNPACK state + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, last_unpack_row_length)); + GL_CALL(glPixelStorei(GL_UNPACK_ALIGNMENT, last_unpack_alignment)); } bool ImGui_ImplOpenGL2_CreateDeviceObjects() @@ -343,6 +351,43 @@ void ImGui_ImplOpenGL2_DestroyDeviceObjects() } } +bool ImGui_ImplOpenGL2_Init() +{ + ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); + IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); + + // Setup backend capabilities flags + ImGui_ImplOpenGL2_Data* bd = IM_NEW(ImGui_ImplOpenGL2_Data)(); + io.BackendRendererUserData = (void*)bd; + io.BackendRendererName = "imgui_impl_opengl2"; + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. + + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.DrawCallback_ResetRenderState = ImGui_ImplOpenGL2_DrawCallback_ResetRenderState; + platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplOpenGL2_DrawCallback_SetSamplerLinear; + platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplOpenGL2_DrawCallback_SetSamplerNearest; + platform_io.DrawCallback_SetSamplerFromTex = ImGui_ImplOpenGL2_DrawCallback_SetSamplerFromTex; + + return true; +} + +void ImGui_ImplOpenGL2_Shutdown() +{ + ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); + IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + + ImGui_ImplOpenGL2_DestroyDeviceObjects(); + + io.BackendRendererName = nullptr; + io.BackendRendererUserData = nullptr; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasTextures); + platform_io.ClearRendererHandlers(); + IM_DELETE(bd); +} + //----------------------------------------------------------------------------- #if defined(__clang__) diff --git a/backends/imgui_impl_opengl2.h b/backends/imgui_impl_opengl2.h index 65b0fe0b4..b9f7453f5 100644 --- a/backends/imgui_impl_opengl2.h +++ b/backends/imgui_impl_opengl2.h @@ -6,6 +6,7 @@ // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // Missing features or Issues: // [ ] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [ ] Renderer: Use of DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest is emulated by poking to glTexParameter(), as legacy OpenGL doesn't have glBindSampler(). // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp index 6348b01b0..73dc349d8 100644 --- a/backends/imgui_impl_opengl3.cpp +++ b/backends/imgui_impl_opengl3.cpp @@ -7,6 +7,7 @@ // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [x] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset) [Desktop OpenGL only!] // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplOpenGL3_GetRenderState(). // About WebGL/ES: // - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES. @@ -23,6 +24,14 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-17: OpenGL: Added support for platform_io.DrawCallback_SetSamplerFromTex. (#9378) +// 2026-09-07: OpenGL: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) +// 2026-07-15: OpenGL: Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT in UpdateTexture() to avoid corrupting caller GL state. (#8802, #9473) +// 2026-06-17: OpenGL: Expose selected render state in ImGui_ImplOpenGL3_RenderState, Allowing to dynamically select between use of glBindSampler() and glTexParameter(). You can access in 'void* platform_io.Renderer_RenderState' during rendering. +// 2026-06-03: OpenGL: GLSL version detection assume GLSL 410 when GL context is 4.1. Fixes an issue running on macOS with Wine. (#9427, #6577) +// 2026-04-23: OpenGL: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) +// (Breaking): this change prioritize using glBindSampler() when available, which would override glTexParameter() settings you may have set on custom textures. +// 2026-03-12: OpenGL: Fixed invalid assert in ImGui_ImplOpenGL3_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295) // 2025-12-11: OpenGL: Fixed embedded loader multiple init/shutdown cycles broken on some platforms. (#8792, #9112) // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-07-22: OpenGL: Add and call embedded loader shutdown during ImGui_ImplOpenGL3_Shutdown() to facilitate multiple init/shutdown cycles in same process. (#8792) @@ -251,6 +260,10 @@ struct ImGui_ImplOpenGL3_Data bool HasBindSampler; bool HasClipOrigin; bool UseBufferSubData; +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER + GLuint TexSamplers[2]; // Used if HasBindSimpler. (0=linear, 1=nearest) +#endif + ImVector TempBuffer; ImGui_ImplOpenGL3_Data() { memset((void*)this, 0, sizeof(*this)); } @@ -263,6 +276,11 @@ static ImGui_ImplOpenGL3_Data* ImGui_ImplOpenGL3_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplOpenGL3_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +ImGui_ImplOpenGL3_RenderState* ImGui_ImplOpenGL3_GetRenderState() +{ + return (ImGui_ImplOpenGL3_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; +} + // OpenGL vertex attribute state (for ES 1.0 and ES 2.0 only) #ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY struct ImGui_ImplOpenGL3_VtxAttribState @@ -311,139 +329,6 @@ static void ImGui_ImplOpenGL3_ShutdownLoader() } // Functions -bool ImGui_ImplOpenGL3_Init(const char* glsl_version) -{ - ImGuiIO& io = ImGui::GetIO(); - IMGUI_CHECKVERSION(); - IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); - - // Initialize loader - if (!ImGui_ImplOpenGL3_InitLoader()) - return false; - - // Setup backend capabilities flags - ImGui_ImplOpenGL3_Data* bd = IM_NEW(ImGui_ImplOpenGL3_Data)(); - io.BackendRendererUserData = (void*)bd; - io.BackendRendererName = "imgui_impl_opengl3"; - - // Query for GL version (e.g. 320 for GL 3.2) - const char* gl_version_str = (const char*)glGetString(GL_VERSION); -#if defined(IMGUI_IMPL_OPENGL_ES2) - // GLES 2 - bd->GlVersion = 200; - bd->GlProfileIsES2 = true; - IM_UNUSED(gl_version_str); -#else - // Desktop or GLES 3 - GLint major = 0; - GLint minor = 0; - glGetIntegerv(GL_MAJOR_VERSION, &major); - glGetIntegerv(GL_MINOR_VERSION, &minor); - if (major == 0 && minor == 0) - sscanf(gl_version_str, "%d.%d", &major, &minor); // Query GL_VERSION in desktop GL 2.x, the string will start with "." - bd->GlVersion = (GLuint)(major * 100 + minor * 10); - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &bd->MaxTextureSize); - -#if defined(IMGUI_IMPL_OPENGL_ES3) - bd->GlProfileIsES3 = true; -#else - if (strncmp(gl_version_str, "OpenGL ES 3", 11) == 0) - bd->GlProfileIsES3 = true; -#endif - -#if defined(GL_CONTEXT_PROFILE_MASK) - if (!bd->GlProfileIsES3 && bd->GlVersion >= 320) - glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &bd->GlProfileMask); - bd->GlProfileIsCompat = (bd->GlProfileMask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) != 0; -#endif - - bd->UseBufferSubData = false; - /* - // Query vendor to enable glBufferSubData kludge -#ifdef _WIN32 - if (const char* vendor = (const char*)glGetString(GL_VENDOR)) - if (strncmp(vendor, "Intel", 5) == 0) - bd->UseBufferSubData = true; -#endif - */ -#endif - -#ifdef IMGUI_IMPL_OPENGL_DEBUG - printf("GlVersion = %d, \"%s\"\nGlProfileIsCompat = %d\nGlProfileMask = 0x%X\nGlProfileIsES2/IsEs3 = %d/%d\nGL_VENDOR = '%s'\nGL_RENDERER = '%s'\n", bd->GlVersion, gl_version_str, bd->GlProfileIsCompat, bd->GlProfileMask, bd->GlProfileIsES2, bd->GlProfileIsES3, (const char*)glGetString(GL_VENDOR), (const char*)glGetString(GL_RENDERER)); // [DEBUG] -#endif - -#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET - if (bd->GlVersion >= 320) - io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. -#endif - io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. - - ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); - platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = (int)bd->MaxTextureSize; - - // Store GLSL version string so we can refer to it later in case we recreate shaders. - // Note: GLSL version is NOT the same as GL version. Leave this to nullptr if unsure. - if (glsl_version == nullptr) - { -#if defined(IMGUI_IMPL_OPENGL_ES2) - glsl_version = "#version 100"; -#elif defined(IMGUI_IMPL_OPENGL_ES3) - glsl_version = "#version 300 es"; -#elif defined(__APPLE__) - glsl_version = "#version 150"; -#else - glsl_version = "#version 130"; -#endif - } - IM_ASSERT((int)strlen(glsl_version) + 2 < IM_COUNTOF(bd->GlslVersionString)); - strcpy(bd->GlslVersionString, glsl_version); - strcat(bd->GlslVersionString, "\n"); - - // Make an arbitrary GL call (we don't actually need the result) - // IF YOU GET A CRASH HERE: it probably means the OpenGL function loader didn't do its job. Let us know! - GLint current_texture; - glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_texture); - - // Detect extensions we support -#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE - bd->HasPolygonMode = (!bd->GlProfileIsES2 && !bd->GlProfileIsES3); -#endif -#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER - bd->HasBindSampler = (bd->GlVersion >= 330 || bd->GlProfileIsES3); -#endif - bd->HasClipOrigin = (bd->GlVersion >= 450); -#ifdef IMGUI_IMPL_OPENGL_HAS_EXTENSIONS - GLint num_extensions = 0; - glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions); - for (GLint i = 0; i < num_extensions; i++) - { - const char* extension = (const char*)glGetStringi(GL_EXTENSIONS, i); - if (extension != nullptr && strcmp(extension, "GL_ARB_clip_control") == 0) - bd->HasClipOrigin = true; - } -#endif - - return true; -} - -void ImGui_ImplOpenGL3_Shutdown() -{ - ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); - IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); - ImGuiIO& io = ImGui::GetIO(); - ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); - - ImGui_ImplOpenGL3_DestroyDeviceObjects(); - - io.BackendRendererName = nullptr; - io.BackendRendererUserData = nullptr; - io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); - platform_io.ClearRendererHandlers(); - IM_DELETE(bd); - - ImGui_ImplOpenGL3_ShutdownLoader(); -} - void ImGui_ImplOpenGL3_NewFrame() { ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); @@ -455,7 +340,7 @@ void ImGui_ImplOpenGL3_NewFrame() IM_ASSERT(0 && "ImGui_ImplOpenGL3_CreateDeviceObjects() failed!"); } -static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object) +static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, ImGui_ImplOpenGL3_RenderState* render_state, int fb_width, int fb_height, GLuint vertex_array_object) { ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); @@ -508,9 +393,16 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_wid glUniform1i(bd->AttribLocationTex, 0); glUniformMatrix4fv(bd->AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); + render_state->UseBindSampler = bd->HasBindSampler; + render_state->UseTexParameterFilter = false; + render_state->CurrentSampler = 0; + render_state->CurrentTexParameterFilter = 0; #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER - if (bd->HasBindSampler) - glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 and GL ES 3.0 may set that otherwise. + if (bd->HasBindSampler && render_state->UseBindSampler) + { + render_state->CurrentSampler = bd->TexSamplers[0]; + glBindSampler(0, render_state->CurrentSampler); // We use combined texture/sampler state. Applications using GL 3.3 and GL ES 3.0 may set that otherwise. + } #endif (void)vertex_array_object; @@ -529,14 +421,56 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_wid GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, col))); } +static void ImGui_ImplOpenGL3_DrawCallback_SetSampler(unsigned int sampler_idx, unsigned int tex_parameter_filter) +{ + ImGui_ImplOpenGL3_RenderState* render_state = ImGui_ImplOpenGL3_GetRenderState(); + IM_UNUSED(sampler_idx); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + if (bd->HasBindSampler && render_state->UseBindSampler) + { + render_state->CurrentSampler = bd->TexSamplers[sampler_idx]; + render_state->UseTexParameterFilter = false; + glBindSampler(0, render_state->CurrentSampler); + } + else +#endif + { + render_state->UseTexParameterFilter = true; + render_state->CurrentTexParameterFilter = tex_parameter_filter; + } +} + +// Draw callbacks +// - IMPORTANT: Using DrawCallback_SetSamplerLinear/DrawCallback_SetSamplerNearest will trash glTexParameteri() values for the texture if used on a GL context not supporting glBindSampler()!! +// We cannot decently backup/restore that state because glGetTexParameteriv() is slow on many setups. +// - Using DrawCallback_SetSamplerFromTex will basically instruct the backend to use existing glTexParameteri() values without modifying anything. +static void ImGui_ImplOpenGL3_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. +static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL3_DrawCallback_SetSampler(0, GL_LINEAR); } +static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL3_DrawCallback_SetSampler(1, GL_NEAREST); } +static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerFromTex(const ImDrawList*, const ImDrawCmd*) +{ + ImGui_ImplOpenGL3_RenderState* render_state = ImGui_ImplOpenGL3_GetRenderState(); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + if (bd->HasBindSampler) + { + render_state->CurrentSampler = 0; + glBindSampler(0, 0); + } +#endif + render_state->UseBindSampler = false; + render_state->UseTexParameterFilter = false; +} + // OpenGL3 Render function. // Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly. // This is in order to be able to run within an OpenGL engine that doesn't do so. void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) { // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); if (fb_width <= 0 || fb_height <= 0) return; @@ -597,7 +531,13 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) #ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY GL_CALL(glGenVertexArrays(1, &vertex_array_object)); #endif - ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); + + // Setup render state structure (for callbacks and custom texture bindings) + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + ImGui_ImplOpenGL3_RenderState render_state = {}; + platform_io.Renderer_RenderState = &render_state; + + ImGui_ImplOpenGL3_SetupRenderState(draw_data, &render_state, fb_width, fb_height, vertex_array_object); // Will project scissor/clipping rectangles into framebuffer space ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports @@ -643,9 +583,8 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) if (pcmd->UserCallback != nullptr) { // User callback, registered via ImDrawList::AddCallback() - // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) - if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) - ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); + if (pcmd->UserCallback == ImGui_ImplOpenGL3_DrawCallback_ResetRenderState) + ImGui_ImplOpenGL3_SetupRenderState(draw_data, &render_state, fb_width, fb_height, vertex_array_object); else pcmd->UserCallback(draw_list, pcmd); } @@ -662,6 +601,15 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) // Bind texture, Draw GL_CALL(glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID())); + + // Emulate sampler change (even though it is technically part of texture data) + // As a sort of hack/workaround, we only start writing using glTexParameter() if sampler is ever changed explicitly. + if (render_state.UseTexParameterFilter) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, render_state.CurrentTexParameterFilter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, render_state.CurrentTexParameterFilter); + } + #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET if (bd->GlVersion >= 320) GL_CALL(glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), (GLint)pcmd->VtxOffset)); @@ -671,6 +619,7 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) } } } + platform_io.Renderer_RenderState = nullptr; // Destroy the temporary VAO #ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY @@ -729,13 +678,15 @@ static void ImGui_ImplOpenGL3_DestroyTexture(ImTextureData* tex) void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex) { - // FIXME: Consider backing up and restoring - if (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates) + // Backup GL_UNPACK state that we modify, restore on exit. + GLint last_unpack_row_length = 0; (void)last_unpack_row_length; + GLint last_unpack_alignment = 0; (void)last_unpack_alignment; + const bool last_unpack_state_save_and_restore = (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates); + if (last_unpack_state_save_and_restore) { #ifdef GL_UNPACK_ROW_LENGTH // Not on WebGL/ES - GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); -#endif -#ifdef GL_UNPACK_ALIGNMENT + GL_CALL(glGetIntegerv(GL_UNPACK_ROW_LENGTH, &last_unpack_row_length)); + GL_CALL(glGetIntegerv(GL_UNPACK_ALIGNMENT, &last_unpack_alignment)); GL_CALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); #endif } @@ -744,13 +695,12 @@ void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex) { // Create and upload new texture to graphics system //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); - IM_ASSERT(tex->TexID == 0 && tex->BackendUserData == nullptr); + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); const void* pixels = tex->GetPixels(); GLuint gl_texture_id = 0; - // Upload texture to graphics system - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) + // Upload texture to graphics system (bilinear sampling is required). GLint last_texture; GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture)); GL_CALL(glGenTextures(1, &gl_texture_id)); @@ -759,6 +709,9 @@ void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex) GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); +#if GL_UNPACK_ROW_LENGTH // Not on WebGL/ES + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); +#endif GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex->Width, tex->Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); // Store identifiers @@ -781,7 +734,6 @@ void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex) GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, tex->Width)); for (ImTextureRect& r : tex->Updates) GL_CALL(glTexSubImage2D(GL_TEXTURE_2D, 0, r.x, r.y, r.w, r.h, GL_RGBA, GL_UNSIGNED_BYTE, tex->GetPixelsAt(r.x, r.y))); - GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); #else // GL ES doesn't have GL_UNPACK_ROW_LENGTH, so we need to (A) copy to a contiguous buffer or (B) upload line by line. ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); @@ -801,6 +753,15 @@ void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex) } else if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0) ImGui_ImplOpenGL3_DestroyTexture(tex); + + // Restore GL_UNPACK state + if (last_unpack_state_save_and_restore) + { +#ifdef GL_UNPACK_ROW_LENGTH + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, last_unpack_row_length)); + GL_CALL(glPixelStorei(GL_UNPACK_ALIGNMENT, last_unpack_alignment)); +#endif + } } // If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file. @@ -1027,6 +988,20 @@ bool ImGui_ImplOpenGL3_CreateDeviceObjects() glGenBuffers(1, &bd->VboHandle); glGenBuffers(1, &bd->ElementsHandle); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER + if (bd->HasBindSampler) + { + glGenSamplers(2, &bd->TexSamplers[0]); + for (int sampler_n = 0; sampler_n < 2; sampler_n++) + { + GL_CALL(glSamplerParameteri(bd->TexSamplers[sampler_n], GL_TEXTURE_MIN_FILTER, (sampler_n == 0) ? GL_LINEAR : GL_NEAREST)); + GL_CALL(glSamplerParameteri(bd->TexSamplers[sampler_n], GL_TEXTURE_MAG_FILTER, (sampler_n == 0) ? GL_LINEAR : GL_NEAREST)); + GL_CALL(glSamplerParameteri(bd->TexSamplers[sampler_n], GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); + GL_CALL(glSamplerParameteri(bd->TexSamplers[sampler_n], GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); + } + } +#endif + // Restore modified GL state glBindTexture(GL_TEXTURE_2D, last_texture); glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); @@ -1044,6 +1019,9 @@ void ImGui_ImplOpenGL3_DestroyDeviceObjects() { ImGui_ImplOpenGL3_InitLoader(); ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER + if (bd->TexSamplers[0]) { glDeleteSamplers(2, &bd->TexSamplers[0]); bd->TexSamplers[0] = bd->TexSamplers[1] = 0; } +#endif if (bd->VboHandle) { glDeleteBuffers(1, &bd->VboHandle); bd->VboHandle = 0; } if (bd->ElementsHandle) { glDeleteBuffers(1, &bd->ElementsHandle); bd->ElementsHandle = 0; } if (bd->ShaderHandle) { glDeleteProgram(bd->ShaderHandle); bd->ShaderHandle = 0; } @@ -1054,6 +1032,146 @@ void ImGui_ImplOpenGL3_DestroyDeviceObjects() ImGui_ImplOpenGL3_DestroyTexture(tex); } +bool ImGui_ImplOpenGL3_Init(const char* glsl_version) +{ + ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); + IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); + + // Initialize loader + if (!ImGui_ImplOpenGL3_InitLoader()) + return false; + + // Setup backend capabilities flags + ImGui_ImplOpenGL3_Data* bd = IM_NEW(ImGui_ImplOpenGL3_Data)(); + io.BackendRendererUserData = (void*)bd; + io.BackendRendererName = "imgui_impl_opengl3"; + + // Query for GL version (e.g. 320 for GL 3.2) + const char* gl_version_str = (const char*)glGetString(GL_VERSION); +#if defined(IMGUI_IMPL_OPENGL_ES2) + // GLES 2 + bd->GlVersion = 200; + bd->GlProfileIsES2 = true; + IM_UNUSED(gl_version_str); +#else + // Desktop or GLES 3 + GLint major = 0; + GLint minor = 0; + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MINOR_VERSION, &minor); + if (major == 0 && minor == 0) + sscanf(gl_version_str, "%d.%d", &major, &minor); // Query GL_VERSION in desktop GL 2.x, the string will start with "." + bd->GlVersion = (GLuint)(major * 100 + minor * 10); + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &bd->MaxTextureSize); + +#if defined(IMGUI_IMPL_OPENGL_ES3) + bd->GlProfileIsES3 = true; +#else + if (strncmp(gl_version_str, "OpenGL ES 3", 11) == 0) + bd->GlProfileIsES3 = true; +#endif + +#if defined(GL_CONTEXT_PROFILE_MASK) + if (!bd->GlProfileIsES3 && bd->GlVersion >= 320) + glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &bd->GlProfileMask); + bd->GlProfileIsCompat = (bd->GlProfileMask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) != 0; +#endif + + bd->UseBufferSubData = false; + /* + // Query vendor to enable glBufferSubData kludge +#ifdef _WIN32 + if (const char* vendor = (const char*)glGetString(GL_VENDOR)) + if (strncmp(vendor, "Intel", 5) == 0) + bd->UseBufferSubData = true; +#endif + */ +#endif + +#ifdef IMGUI_IMPL_OPENGL_DEBUG + printf("GlVersion = %d, \"%s\"\nGlProfileIsCompat = %d\nGlProfileMask = 0x%X\nGlProfileIsES2/IsEs3 = %d/%d\nGL_VENDOR = '%s'\nGL_RENDERER = '%s'\n", bd->GlVersion, gl_version_str, bd->GlProfileIsCompat, bd->GlProfileMask, bd->GlProfileIsES2, bd->GlProfileIsES3, (const char*)glGetString(GL_VENDOR), (const char*)glGetString(GL_RENDERER)); // [DEBUG] +#endif + +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET + if (bd->GlVersion >= 320) + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. +#endif + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. + + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = (int)bd->MaxTextureSize; + platform_io.DrawCallback_ResetRenderState = ImGui_ImplOpenGL3_DrawCallback_ResetRenderState; + platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplOpenGL3_DrawCallback_SetSamplerLinear; + platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplOpenGL3_DrawCallback_SetSamplerNearest; + platform_io.DrawCallback_SetSamplerFromTex = ImGui_ImplOpenGL3_DrawCallback_SetSamplerFromTex; + + // Store GLSL version string so we can refer to it later in case we recreate shaders. + // Note: GLSL version is NOT the same as GL version. Leave this to nullptr if unsure. + if (glsl_version == nullptr) + { +#if defined(IMGUI_IMPL_OPENGL_ES2) + glsl_version = "#version 100"; +#elif defined(IMGUI_IMPL_OPENGL_ES3) + glsl_version = "#version 300 es"; +#elif defined(__APPLE__) + glsl_version = "#version 150"; +#else + if (bd->GlVersion >= 410) + glsl_version = "#version 410"; + else + glsl_version = "#version 130"; +#endif + } + IM_ASSERT((int)strlen(glsl_version) + 2 < IM_COUNTOF(bd->GlslVersionString)); + strcpy(bd->GlslVersionString, glsl_version); + strcat(bd->GlslVersionString, "\n"); + + // Make an arbitrary GL call (we don't actually need the result) + // IF YOU GET A CRASH HERE: it probably means the OpenGL function loader didn't do its job. Let us know! + GLint current_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_texture); + + // Detect extensions we support +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE + bd->HasPolygonMode = (!bd->GlProfileIsES2 && !bd->GlProfileIsES3); +#endif +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER + bd->HasBindSampler = (bd->GlVersion >= 330 || bd->GlProfileIsES3); +#endif + bd->HasClipOrigin = (bd->GlVersion >= 450); +#ifdef IMGUI_IMPL_OPENGL_HAS_EXTENSIONS + GLint num_extensions = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions); + for (GLint i = 0; i < num_extensions; i++) + { + const char* extension = (const char*)glGetStringi(GL_EXTENSIONS, i); + if (extension != nullptr && strcmp(extension, "GL_ARB_clip_control") == 0) + bd->HasClipOrigin = true; + } +#endif + + return true; +} + +void ImGui_ImplOpenGL3_Shutdown() +{ + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + + ImGui_ImplOpenGL3_DestroyDeviceObjects(); + + io.BackendRendererName = nullptr; + io.BackendRendererUserData = nullptr; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); + platform_io.ClearRendererHandlers(); + IM_DELETE(bd); + + ImGui_ImplOpenGL3_ShutdownLoader(); +} + //----------------------------------------------------------------------------- #if defined(__GNUC__) diff --git a/backends/imgui_impl_opengl3.h b/backends/imgui_impl_opengl3.h index aabe04b07..e1d999bd3 100644 --- a/backends/imgui_impl_opengl3.h +++ b/backends/imgui_impl_opengl3.h @@ -7,6 +7,7 @@ // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [x] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset) [Desktop OpenGL only!] // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplOpenGL3_GetRenderState(). // About WebGL/ES: // - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES. @@ -65,4 +66,16 @@ IMGUI_IMPL_API void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex); #endif +// [BETA] Selected render state data shared with callbacks. +// This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplOpenGL3_RenderDrawData() call. +// (Please open an issue if you feel you need access to more data) +struct ImGui_ImplOpenGL3_RenderState +{ + bool UseBindSampler; + bool UseTexParameterFilter; + unsigned int CurrentSampler; // (GLuint) Used if UseBindSampler == true (and are available), otherwise always 0 + unsigned int CurrentTexParameterFilter; // (GLuint) Used if UseTexParameterFilter == true +}; +IMGUI_IMPL_API ImGui_ImplOpenGL3_RenderState* ImGui_ImplOpenGL3_GetRenderState(); + #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_opengl3_loader.h b/backends/imgui_impl_opengl3_loader.h index 2c485841a..34b262dff 100644 --- a/backends/imgui_impl_opengl3_loader.h +++ b/backends/imgui_impl_opengl3_loader.h @@ -114,7 +114,7 @@ extern "C" { #define GLAPI extern #endif /* glcorearb.h is for use with OpenGL core profile implementations. -** It should should be placed in the same directory as gl.h and +** It should be placed in the same directory as gl.h and ** included as . ** ** glcorearb.h includes only APIs in the latest OpenGL core profile diff --git a/backends/imgui_impl_qnx.cpp b/backends/imgui_impl_qnx.cpp new file mode 100644 index 000000000..3730f54d2 --- /dev/null +++ b/backends/imgui_impl_qnx.cpp @@ -0,0 +1,759 @@ +// dear imgui: Platform Backend for QNX Screen +// This needs to be used along with a Renderer Backend (e.g. OpenGL3, Vulkan). + +// Implemented features: +// [X] Platform: Keyboard support. Uses io.AddKeyEvent() and native QNX key symbols. +// [X] Platform: Mouse support. Handles position, three buttons and horizontal/vertical wheels. +// [X] Platform: Touch support. Maps the first active touch contact to the primary mouse button. +// [X] Platform: Focus and window-size updates. +// Missing features or Issues: +// [ ] Platform: Clipboard support. +// [ ] Platform: Gamepad support. +// [ ] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). +// [ ] Platform: Multi-viewport support. + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2026-08-04: Initial QNX Screen backend. (#9492) + +#include "imgui.h" +#ifndef IMGUI_DISABLE +#include "imgui_impl_qnx.h" + +#include +#include +#include +#include +#include + +static unsigned int ImGui_ImplQNX_KeySymToUnicode(unsigned int keysym); + +struct ImGui_ImplQNX_Data +{ + screen_context_t Context; + screen_window_t Window; + uint64_t TimeNanoseconds; + int MouseButtons; + int ActiveTouchId; + bool LeftSuper; + bool RightSuper; + + ImGui_ImplQNX_Data() + { + memset(this, 0, sizeof(*this)); + ActiveTouchId = -1; + } +}; + +static ImGui_ImplQNX_Data* ImGui_ImplQNX_GetBackendData() +{ + return ImGui::GetCurrentContext() != nullptr ? (ImGui_ImplQNX_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr; +} + +static uint64_t ImGui_ImplQNX_GetTimeNanoseconds() +{ + struct timespec timestamp = {}; + if (clock_gettime(CLOCK_MONOTONIC, ×tamp) != 0) + { + return 0; + } + return (uint64_t)timestamp.tv_sec * UINT64_C(1000000000) + (uint64_t)timestamp.tv_nsec; +} + +static ImGuiKey ImGui_ImplQNX_KeyCodeToImGuiKey(int key_code) +{ + switch (key_code) + { + case KEYCODE_TAB: return ImGuiKey_Tab; + case KEYCODE_LEFT: return ImGuiKey_LeftArrow; + case KEYCODE_RIGHT: return ImGuiKey_RightArrow; + case KEYCODE_UP: return ImGuiKey_UpArrow; + case KEYCODE_DOWN: return ImGuiKey_DownArrow; + case KEYCODE_PG_UP: return ImGuiKey_PageUp; + case KEYCODE_PG_DOWN: return ImGuiKey_PageDown; + case KEYCODE_HOME: return ImGuiKey_Home; + case KEYCODE_END: return ImGuiKey_End; + case KEYCODE_INSERT: return ImGuiKey_Insert; + case KEYCODE_DELETE: return ImGuiKey_Delete; + case KEYCODE_BACKSPACE: return ImGuiKey_Backspace; + case KEYCODE_SPACE: return ImGuiKey_Space; + case KEYCODE_RETURN: return ImGuiKey_Enter; + case KEYCODE_ESCAPE: return ImGuiKey_Escape; + case KEYCODE_APOSTROPHE: return ImGuiKey_Apostrophe; + case KEYCODE_COMMA: return ImGuiKey_Comma; + case KEYCODE_MINUS: return ImGuiKey_Minus; + case KEYCODE_PERIOD: return ImGuiKey_Period; + case KEYCODE_SLASH: return ImGuiKey_Slash; + case KEYCODE_SEMICOLON: return ImGuiKey_Semicolon; + case KEYCODE_EQUAL: return ImGuiKey_Equal; + case KEYCODE_LEFT_BRACKET: return ImGuiKey_LeftBracket; + case KEYCODE_BACK_SLASH: return ImGuiKey_Backslash; + case KEYCODE_RIGHT_BRACKET: return ImGuiKey_RightBracket; + case KEYCODE_GRAVE: return ImGuiKey_GraveAccent; + case KEYCODE_CAPS_LOCK: return ImGuiKey_CapsLock; + case KEYCODE_SCROLL_LOCK: return ImGuiKey_ScrollLock; + case KEYCODE_NUM_LOCK: return ImGuiKey_NumLock; + case KEYCODE_PRINT: return ImGuiKey_PrintScreen; + case KEYCODE_PAUSE: return ImGuiKey_Pause; + case KEYCODE_KP_DIVIDE: return ImGuiKey_KeypadDivide; + case KEYCODE_KP_MULTIPLY: return ImGuiKey_KeypadMultiply; + case KEYCODE_KP_MINUS: return ImGuiKey_KeypadSubtract; + case KEYCODE_KP_PLUS: return ImGuiKey_KeypadAdd; + case KEYCODE_KP_ENTER: return ImGuiKey_KeypadEnter; + // QNX reports keypad digits by their navigation key-cap names. + case KEYCODE_KP_INSERT: return ImGuiKey_Keypad0; + case KEYCODE_KP_END: return ImGuiKey_Keypad1; + case KEYCODE_KP_DOWN: return ImGuiKey_Keypad2; + case KEYCODE_KP_PG_DOWN: return ImGuiKey_Keypad3; + case KEYCODE_KP_LEFT: return ImGuiKey_Keypad4; + case KEYCODE_KP_FIVE: return ImGuiKey_Keypad5; + case KEYCODE_KP_RIGHT: return ImGuiKey_Keypad6; + case KEYCODE_KP_HOME: return ImGuiKey_Keypad7; + case KEYCODE_KP_UP: return ImGuiKey_Keypad8; + case KEYCODE_KP_PG_UP: return ImGuiKey_Keypad9; + case KEYCODE_KP_DELETE: return ImGuiKey_KeypadDecimal; + case KEYCODE_LEFT_CTRL: return ImGuiKey_LeftCtrl; + case KEYCODE_LEFT_SHIFT: return ImGuiKey_LeftShift; + case KEYCODE_LEFT_ALT: return ImGuiKey_LeftAlt; + case KEYCODE_RIGHT_CTRL: return ImGuiKey_RightCtrl; + case KEYCODE_RIGHT_SHIFT: return ImGuiKey_RightShift; + case KEYCODE_RIGHT_ALT: return ImGuiKey_RightAlt; + case KEYCODE_LEFT_HYPER: return ImGuiKey_LeftSuper; + case KEYCODE_RIGHT_HYPER: return ImGuiKey_RightSuper; + case KEYCODE_MENU: return ImGuiKey_Menu; + case KEYCODE_ZERO: return ImGuiKey_0; + case KEYCODE_ONE: return ImGuiKey_1; + case KEYCODE_TWO: return ImGuiKey_2; + case KEYCODE_THREE: return ImGuiKey_3; + case KEYCODE_FOUR: return ImGuiKey_4; + case KEYCODE_FIVE: return ImGuiKey_5; + case KEYCODE_SIX: return ImGuiKey_6; + case KEYCODE_SEVEN: return ImGuiKey_7; + case KEYCODE_EIGHT: return ImGuiKey_8; + case KEYCODE_NINE: return ImGuiKey_9; + case KEYCODE_A: return ImGuiKey_A; + case KEYCODE_B: return ImGuiKey_B; + case KEYCODE_C: return ImGuiKey_C; + case KEYCODE_D: return ImGuiKey_D; + case KEYCODE_E: return ImGuiKey_E; + case KEYCODE_F: return ImGuiKey_F; + case KEYCODE_G: return ImGuiKey_G; + case KEYCODE_H: return ImGuiKey_H; + case KEYCODE_I: return ImGuiKey_I; + case KEYCODE_J: return ImGuiKey_J; + case KEYCODE_K: return ImGuiKey_K; + case KEYCODE_L: return ImGuiKey_L; + case KEYCODE_M: return ImGuiKey_M; + case KEYCODE_N: return ImGuiKey_N; + case KEYCODE_O: return ImGuiKey_O; + case KEYCODE_P: return ImGuiKey_P; + case KEYCODE_Q: return ImGuiKey_Q; + case KEYCODE_R: return ImGuiKey_R; + case KEYCODE_S: return ImGuiKey_S; + case KEYCODE_T: return ImGuiKey_T; + case KEYCODE_U: return ImGuiKey_U; + case KEYCODE_V: return ImGuiKey_V; + case KEYCODE_W: return ImGuiKey_W; + case KEYCODE_X: return ImGuiKey_X; + case KEYCODE_Y: return ImGuiKey_Y; + case KEYCODE_Z: return ImGuiKey_Z; + case KEYCODE_F1: return ImGuiKey_F1; + case KEYCODE_F2: return ImGuiKey_F2; + case KEYCODE_F3: return ImGuiKey_F3; + case KEYCODE_F4: return ImGuiKey_F4; + case KEYCODE_F5: return ImGuiKey_F5; + case KEYCODE_F6: return ImGuiKey_F6; + case KEYCODE_F7: return ImGuiKey_F7; + case KEYCODE_F8: return ImGuiKey_F8; + case KEYCODE_F9: return ImGuiKey_F9; + case KEYCODE_F10: return ImGuiKey_F10; + case KEYCODE_F11: return ImGuiKey_F11; + case KEYCODE_F12: return ImGuiKey_F12; + default: return ImGuiKey_None; + } +} + +static void ImGui_ImplQNX_UpdateKeyModifiers(ImGuiIO& io, ImGui_ImplQNX_Data* bd, int modifiers, ImGuiKey key, bool down) +{ + // SCREEN_PROPERTY_MODIFIERS is the authoritative state for Shift/Ctrl/Alt. + // Tracking those keys locally loses sticky modifiers and can become stale if + // a key transition is consumed elsewhere. qterm uses this same Screen state. + if (key == ImGuiKey_LeftSuper) + { + bd->LeftSuper = down; + } + else if (key == ImGuiKey_RightSuper) + { + bd->RightSuper = down; + } + + io.AddKeyEvent(ImGuiMod_Ctrl, (modifiers & KEYMOD_CTRL) != 0); + io.AddKeyEvent(ImGuiMod_Shift, (modifiers & KEYMOD_SHIFT) != 0); + io.AddKeyEvent(ImGuiMod_Alt, (modifiers & KEYMOD_ALT) != 0); + io.AddKeyEvent(ImGuiMod_Super, bd->LeftSuper || bd->RightSuper); +} + +static void ImGui_ImplQNX_ClearModifierState(ImGuiIO& io, ImGui_ImplQNX_Data* bd) +{ + bd->LeftSuper = false; + bd->RightSuper = false; + io.AddKeyEvent(ImGuiMod_Ctrl, false); + io.AddKeyEvent(ImGuiMod_Shift, false); + io.AddKeyEvent(ImGuiMod_Alt, false); + io.AddKeyEvent(ImGuiMod_Super, false); +} + +static bool ImGui_ImplQNX_IsTextCodePoint(unsigned int code_point) +{ + // At this point the QNX keysym has already been converted to Unicode. + // Do not reject Unicode private-use characters: a user keymap may emit one. + const bool surrogate = code_point >= 0xd800u && code_point <= 0xdfffu; + const bool control = code_point < 0x20u || (code_point >= 0x7fu && code_point <= 0x9fu); + return code_point <= IM_UNICODE_CODEPOINT_MAX && !surrogate && !control; +} + +static bool ImGui_ImplQNX_ProcessKeyboardEvent(ImGui_ImplQNX_Data* bd, screen_event_t event) +{ + int flags = 0; + int modifiers = 0; + if (screen_get_event_property_iv(event, SCREEN_PROPERTY_FLAGS, &flags) != 0 || + screen_get_event_property_iv(event, SCREEN_PROPERTY_MODIFIERS, &modifiers) != 0) + { + return false; + } + + // KEY_CAP is the unmodified key identity used for ImGuiKey events. SYM is + // the layout/modifier-translated QNX keysym used for text input. + int key_cap = 0; + int key_sym = 0; + int scan_code = 0; + const bool cap_valid = (flags & SCREEN_FLAG_CAP_VALID) != 0; + const bool sym_valid = (flags & SCREEN_FLAG_SYM_VALID) != 0; + const bool scan_valid = (flags & SCREEN_FLAG_SCAN_VALID) != 0; + const bool down = (flags & (SCREEN_FLAG_KEY_DOWN | SCREEN_FLAG_KEY_REPEAT)) != 0; + + if (cap_valid && screen_get_event_property_iv(event, SCREEN_PROPERTY_KEY_CAP, &key_cap) != 0) + { + return false; + } + if (sym_valid && screen_get_event_property_iv(event, SCREEN_PROPERTY_SYM, &key_sym) != 0) + { + return false; + } + if (scan_valid && screen_get_event_property_iv(event, SCREEN_PROPERTY_SCAN, &scan_code) != 0) + { + scan_code = 0; + } + + const ImGuiKey key = cap_valid ? ImGui_ImplQNX_KeyCodeToImGuiKey(key_cap) : ImGuiKey_None; + ImGuiIO& io = ImGui::GetIO(); + + // Modifier events must precede the ordinary key event in Dear ImGui's queue. + // Unlike qterm, we still submit the individual modifier key because ImGui + // exposes Left/Right Ctrl, Shift, Alt and Super as named keys. + ImGui_ImplQNX_UpdateKeyModifiers(io, bd, modifiers, key, down); + + if (key != ImGuiKey_None) + { + io.AddKeyEvent(key, down); + io.SetKeyEventNativeData(key, key_cap, scan_code); + } + + bool text_added = false; + if (down && sym_valid && key_sym >= 0) + { + const unsigned int keysym = (unsigned int)key_sym; + // QNX special/function symbols begin at KEYCODE_PC_KEYS. Printable + // symbols below that boundary use X11 keysym encoding. Also accept the + // standard directly encoded UCS keysym form (0x01000000 | codepoint). + if (keysym < (unsigned int)KEYCODE_PC_KEYS || (keysym & 0xff000000u) == 0x01000000u) + { + const unsigned int code_point = ImGui_ImplQNX_KeySymToUnicode(keysym); + if (ImGui_ImplQNX_IsTextCodePoint(code_point)) + { + io.AddInputCharacter(code_point); + text_added = true; + } + } + } + + return key != ImGuiKey_None || text_added || cap_valid || sym_valid; +} + +static bool ImGui_ImplQNX_ProcessPointerEvent(ImGui_ImplQNX_Data* bd, screen_event_t event) +{ + int position[2] = {}; + int buttons = 0; + int wheel_vertical = 0; + int wheel_horizontal = 0; + + if (screen_get_event_property_iv(event, SCREEN_PROPERTY_SOURCE_POSITION, position) != 0 && + screen_get_event_property_iv(event, SCREEN_PROPERTY_POSITION, position) != 0) + { + return false; + } + if (screen_get_event_property_iv(event, SCREEN_PROPERTY_BUTTONS, &buttons) != 0) + { + buttons = bd->MouseButtons; + } + + ImGuiIO& io = ImGui::GetIO(); + io.AddMouseSourceEvent(ImGuiMouseSource_Mouse); + io.AddMousePosEvent((float)position[0], (float)position[1]); + + const int changed_buttons = buttons ^ bd->MouseButtons; + if ((changed_buttons & SCREEN_LEFT_MOUSE_BUTTON) != 0) + { + io.AddMouseButtonEvent(0, (buttons & SCREEN_LEFT_MOUSE_BUTTON) != 0); + } + if ((changed_buttons & SCREEN_RIGHT_MOUSE_BUTTON) != 0) + { + io.AddMouseButtonEvent(1, (buttons & SCREEN_RIGHT_MOUSE_BUTTON) != 0); + } + if ((changed_buttons & SCREEN_MIDDLE_MOUSE_BUTTON) != 0) + { + io.AddMouseButtonEvent(2, (buttons & SCREEN_MIDDLE_MOUSE_BUTTON) != 0); + } + bd->MouseButtons = buttons; + + if (screen_get_event_property_iv(event, SCREEN_PROPERTY_MOUSE_WHEEL, &wheel_vertical) != 0) + { + wheel_vertical = 0; + } + if (screen_get_event_property_iv(event, SCREEN_PROPERTY_MOUSE_HORIZONTAL_WHEEL, &wheel_horizontal) != 0) + { + wheel_horizontal = 0; + } + if (wheel_vertical != 0 || wheel_horizontal != 0) + { + // Screen: up/left are negative. Dear ImGui: up/left are positive. + io.AddMouseWheelEvent((float)-wheel_horizontal, (float)-wheel_vertical); + } + return true; +} + +static bool ImGui_ImplQNX_ProcessTouchEvent(ImGui_ImplQNX_Data* bd, screen_event_t event, int event_type) +{ + int touch_id = -1; + int position[2] = {}; + if (screen_get_event_property_iv(event, SCREEN_PROPERTY_TOUCH_ID, &touch_id) != 0) + { + return false; + } + + const bool is_release = event_type == SCREEN_EVENT_MTOUCH_RELEASE; + if (bd->ActiveTouchId != -1 && touch_id != bd->ActiveTouchId) + { + return false; + } + + ImGuiIO& io = ImGui::GetIO(); + io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen); + + if (!is_release) + { + if (screen_get_event_property_iv(event, SCREEN_PROPERTY_SOURCE_POSITION, position) != 0 && + screen_get_event_property_iv(event, SCREEN_PROPERTY_POSITION, position) != 0) + { + return false; + } + io.AddMousePosEvent((float)position[0], (float)position[1]); + } + + if (event_type == SCREEN_EVENT_MTOUCH_TOUCH) + { + bd->ActiveTouchId = touch_id; + io.AddMouseButtonEvent(0, true); + } + else if (is_release) + { + io.AddMouseButtonEvent(0, false); + io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); + bd->ActiveTouchId = -1; + } + return true; +} + +bool ImGui_ImplQNX_ProcessEvent(screen_event_t event) +{ + ImGui_ImplQNX_Data* bd = ImGui_ImplQNX_GetBackendData(); + IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplQNX_Init()?"); + if (bd == nullptr || event == nullptr) + { + return false; + } + + int event_type = SCREEN_EVENT_NONE; + if (screen_get_event_property_iv(event, SCREEN_PROPERTY_TYPE, &event_type) != 0 || event_type == SCREEN_EVENT_NONE) + { + return false; + } + switch (event_type) + { + case SCREEN_EVENT_KEYBOARD: + return ImGui_ImplQNX_ProcessKeyboardEvent(bd, event); + case SCREEN_EVENT_POINTER: + return ImGui_ImplQNX_ProcessPointerEvent(bd, event); + case SCREEN_EVENT_MTOUCH_TOUCH: + case SCREEN_EVENT_MTOUCH_MOVE: + case SCREEN_EVENT_MTOUCH_RELEASE: + return ImGui_ImplQNX_ProcessTouchEvent(bd, event, event_type); + case SCREEN_EVENT_PROPERTY: + { + int property = 0; + if (screen_get_event_property_iv(event, SCREEN_PROPERTY_NAME, &property) != 0) + { + return false; + } + if (property == SCREEN_PROPERTY_FOCUS) + { + int focused = 0; + if (screen_get_window_property_iv(bd->Window, SCREEN_PROPERTY_FOCUS, &focused) == 0) + { + ImGuiIO& io = ImGui::GetIO(); + io.AddFocusEvent(focused != 0); + if (focused == 0) + { + ImGui_ImplQNX_ClearModifierState(io, bd); + } + } + return true; + } + if (property == SCREEN_PROPERTY_SIZE || property == SCREEN_PROPERTY_BUFFER_SIZE || property == SCREEN_PROPERTY_VISIBLE) + { + return true; + } + return false; + } + case SCREEN_EVENT_CLOSE: + { + ImGuiIO& io = ImGui::GetIO(); + io.AddFocusEvent(false); + ImGui_ImplQNX_ClearModifierState(io, bd); + return true; + } + default: + return false; + } +} + +bool ImGui_ImplQNX_Init(screen_context_t context, screen_window_t window) +{ + ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); + IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!"); + IM_ASSERT(context != nullptr && window != nullptr); + if (context == nullptr || window == nullptr) + { + return false; + } + + ImGui_ImplQNX_Data* bd = IM_NEW(ImGui_ImplQNX_Data)(); + io.BackendPlatformUserData = (void*)bd; + io.BackendPlatformName = "imgui_impl_qnx"; + + bd->Context = context; + bd->Window = window; + bd->TimeNanoseconds = ImGui_ImplQNX_GetTimeNanoseconds(); + + int focused = 0; + if (screen_get_window_property_iv(window, SCREEN_PROPERTY_FOCUS, &focused) == 0) + { + io.AddFocusEvent(focused != 0); + } + return true; +} + +void ImGui_ImplQNX_Shutdown() +{ + ImGui_ImplQNX_Data* bd = ImGui_ImplQNX_GetBackendData(); + IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?"); + if (bd == nullptr) + { + return; + } + + ImGuiIO& io = ImGui::GetIO(); + io.BackendPlatformName = nullptr; + io.BackendPlatformUserData = nullptr; + IM_DELETE(bd); +} + +void ImGui_ImplQNX_NewFrame() +{ + ImGui_ImplQNX_Data* bd = ImGui_ImplQNX_GetBackendData(); + IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplQNX_Init()?"); + if (bd == nullptr) + { + return; + } + + ImGuiIO& io = ImGui::GetIO(); + int size[2] = {}; + if (screen_get_window_property_iv(bd->Window, SCREEN_PROPERTY_BUFFER_SIZE, size) != 0 || + size[0] <= 0 || size[1] <= 0) + { + size[0] = 0; + size[1] = 0; + (void)screen_get_window_property_iv(bd->Window, SCREEN_PROPERTY_SIZE, size); + } + if (size[0] > 0 && size[1] > 0) + { + io.DisplaySize = ImVec2((float)size[0], (float)size[1]); + io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); + } + + const uint64_t current_time = ImGui_ImplQNX_GetTimeNanoseconds(); + if (current_time > bd->TimeNanoseconds && bd->TimeNanoseconds != 0) + { + io.DeltaTime = (float)((double)(current_time - bd->TimeNanoseconds) / 1000000000.0); + } + else + { + io.DeltaTime = 1.0f / 60.0f; + } + bd->TimeNanoseconds = current_time; +} + +// Generated by Markus Kuhn's public-domain unicode/convmap.pl from X11 keysymdef.h and UnicodeData. +// Original source: https://www.cl.cam.ac.uk/~mgk25/ucs/keysym2ucs.c +// The table must remain sorted by keysym for the binary search below. +struct ImGui_ImplQNX_KeySymPair +{ + uint16_t KeySym; + uint16_t Unicode; +}; + +static const ImGui_ImplQNX_KeySymPair ImGui_ImplQNX_KeySymTable[] = +{ + { 0x01a1, 0x0104 }, { 0x01a2, 0x02d8 }, { 0x01a3, 0x0141 }, { 0x01a5, 0x013d }, + { 0x01a6, 0x015a }, { 0x01a9, 0x0160 }, { 0x01aa, 0x015e }, { 0x01ab, 0x0164 }, + { 0x01ac, 0x0179 }, { 0x01ae, 0x017d }, { 0x01af, 0x017b }, { 0x01b1, 0x0105 }, + { 0x01b2, 0x02db }, { 0x01b3, 0x0142 }, { 0x01b5, 0x013e }, { 0x01b6, 0x015b }, + { 0x01b7, 0x02c7 }, { 0x01b9, 0x0161 }, { 0x01ba, 0x015f }, { 0x01bb, 0x0165 }, + { 0x01bc, 0x017a }, { 0x01bd, 0x02dd }, { 0x01be, 0x017e }, { 0x01bf, 0x017c }, + { 0x01c0, 0x0154 }, { 0x01c3, 0x0102 }, { 0x01c5, 0x0139 }, { 0x01c6, 0x0106 }, + { 0x01c8, 0x010c }, { 0x01ca, 0x0118 }, { 0x01cc, 0x011a }, { 0x01cf, 0x010e }, + { 0x01d0, 0x0110 }, { 0x01d1, 0x0143 }, { 0x01d2, 0x0147 }, { 0x01d5, 0x0150 }, + { 0x01d8, 0x0158 }, { 0x01d9, 0x016e }, { 0x01db, 0x0170 }, { 0x01de, 0x0162 }, + { 0x01e0, 0x0155 }, { 0x01e3, 0x0103 }, { 0x01e5, 0x013a }, { 0x01e6, 0x0107 }, + { 0x01e8, 0x010d }, { 0x01ea, 0x0119 }, { 0x01ec, 0x011b }, { 0x01ef, 0x010f }, + { 0x01f0, 0x0111 }, { 0x01f1, 0x0144 }, { 0x01f2, 0x0148 }, { 0x01f5, 0x0151 }, + { 0x01f8, 0x0159 }, { 0x01f9, 0x016f }, { 0x01fb, 0x0171 }, { 0x01fe, 0x0163 }, + { 0x01ff, 0x02d9 }, { 0x02a1, 0x0126 }, { 0x02a6, 0x0124 }, { 0x02a9, 0x0130 }, + { 0x02ab, 0x011e }, { 0x02ac, 0x0134 }, { 0x02b1, 0x0127 }, { 0x02b6, 0x0125 }, + { 0x02b9, 0x0131 }, { 0x02bb, 0x011f }, { 0x02bc, 0x0135 }, { 0x02c5, 0x010a }, + { 0x02c6, 0x0108 }, { 0x02d5, 0x0120 }, { 0x02d8, 0x011c }, { 0x02dd, 0x016c }, + { 0x02de, 0x015c }, { 0x02e5, 0x010b }, { 0x02e6, 0x0109 }, { 0x02f5, 0x0121 }, + { 0x02f8, 0x011d }, { 0x02fd, 0x016d }, { 0x02fe, 0x015d }, { 0x03a2, 0x0138 }, + { 0x03a3, 0x0156 }, { 0x03a5, 0x0128 }, { 0x03a6, 0x013b }, { 0x03aa, 0x0112 }, + { 0x03ab, 0x0122 }, { 0x03ac, 0x0166 }, { 0x03b3, 0x0157 }, { 0x03b5, 0x0129 }, + { 0x03b6, 0x013c }, { 0x03ba, 0x0113 }, { 0x03bb, 0x0123 }, { 0x03bc, 0x0167 }, + { 0x03bd, 0x014a }, { 0x03bf, 0x014b }, { 0x03c0, 0x0100 }, { 0x03c7, 0x012e }, + { 0x03cc, 0x0116 }, { 0x03cf, 0x012a }, { 0x03d1, 0x0145 }, { 0x03d2, 0x014c }, + { 0x03d3, 0x0136 }, { 0x03d9, 0x0172 }, { 0x03dd, 0x0168 }, { 0x03de, 0x016a }, + { 0x03e0, 0x0101 }, { 0x03e7, 0x012f }, { 0x03ec, 0x0117 }, { 0x03ef, 0x012b }, + { 0x03f1, 0x0146 }, { 0x03f2, 0x014d }, { 0x03f3, 0x0137 }, { 0x03f9, 0x0173 }, + { 0x03fd, 0x0169 }, { 0x03fe, 0x016b }, { 0x047e, 0x203e }, { 0x04a1, 0x3002 }, + { 0x04a2, 0x300c }, { 0x04a3, 0x300d }, { 0x04a4, 0x3001 }, { 0x04a5, 0x30fb }, + { 0x04a6, 0x30f2 }, { 0x04a7, 0x30a1 }, { 0x04a8, 0x30a3 }, { 0x04a9, 0x30a5 }, + { 0x04aa, 0x30a7 }, { 0x04ab, 0x30a9 }, { 0x04ac, 0x30e3 }, { 0x04ad, 0x30e5 }, + { 0x04ae, 0x30e7 }, { 0x04af, 0x30c3 }, { 0x04b0, 0x30fc }, { 0x04b1, 0x30a2 }, + { 0x04b2, 0x30a4 }, { 0x04b3, 0x30a6 }, { 0x04b4, 0x30a8 }, { 0x04b5, 0x30aa }, + { 0x04b6, 0x30ab }, { 0x04b7, 0x30ad }, { 0x04b8, 0x30af }, { 0x04b9, 0x30b1 }, + { 0x04ba, 0x30b3 }, { 0x04bb, 0x30b5 }, { 0x04bc, 0x30b7 }, { 0x04bd, 0x30b9 }, + { 0x04be, 0x30bb }, { 0x04bf, 0x30bd }, { 0x04c0, 0x30bf }, { 0x04c1, 0x30c1 }, + { 0x04c2, 0x30c4 }, { 0x04c3, 0x30c6 }, { 0x04c4, 0x30c8 }, { 0x04c5, 0x30ca }, + { 0x04c6, 0x30cb }, { 0x04c7, 0x30cc }, { 0x04c8, 0x30cd }, { 0x04c9, 0x30ce }, + { 0x04ca, 0x30cf }, { 0x04cb, 0x30d2 }, { 0x04cc, 0x30d5 }, { 0x04cd, 0x30d8 }, + { 0x04ce, 0x30db }, { 0x04cf, 0x30de }, { 0x04d0, 0x30df }, { 0x04d1, 0x30e0 }, + { 0x04d2, 0x30e1 }, { 0x04d3, 0x30e2 }, { 0x04d4, 0x30e4 }, { 0x04d5, 0x30e6 }, + { 0x04d6, 0x30e8 }, { 0x04d7, 0x30e9 }, { 0x04d8, 0x30ea }, { 0x04d9, 0x30eb }, + { 0x04da, 0x30ec }, { 0x04db, 0x30ed }, { 0x04dc, 0x30ef }, { 0x04dd, 0x30f3 }, + { 0x04de, 0x309b }, { 0x04df, 0x309c }, { 0x05ac, 0x060c }, { 0x05bb, 0x061b }, + { 0x05bf, 0x061f }, { 0x05c1, 0x0621 }, { 0x05c2, 0x0622 }, { 0x05c3, 0x0623 }, + { 0x05c4, 0x0624 }, { 0x05c5, 0x0625 }, { 0x05c6, 0x0626 }, { 0x05c7, 0x0627 }, + { 0x05c8, 0x0628 }, { 0x05c9, 0x0629 }, { 0x05ca, 0x062a }, { 0x05cb, 0x062b }, + { 0x05cc, 0x062c }, { 0x05cd, 0x062d }, { 0x05ce, 0x062e }, { 0x05cf, 0x062f }, + { 0x05d0, 0x0630 }, { 0x05d1, 0x0631 }, { 0x05d2, 0x0632 }, { 0x05d3, 0x0633 }, + { 0x05d4, 0x0634 }, { 0x05d5, 0x0635 }, { 0x05d6, 0x0636 }, { 0x05d7, 0x0637 }, + { 0x05d8, 0x0638 }, { 0x05d9, 0x0639 }, { 0x05da, 0x063a }, { 0x05e0, 0x0640 }, + { 0x05e1, 0x0641 }, { 0x05e2, 0x0642 }, { 0x05e3, 0x0643 }, { 0x05e4, 0x0644 }, + { 0x05e5, 0x0645 }, { 0x05e6, 0x0646 }, { 0x05e7, 0x0647 }, { 0x05e8, 0x0648 }, + { 0x05e9, 0x0649 }, { 0x05ea, 0x064a }, { 0x05eb, 0x064b }, { 0x05ec, 0x064c }, + { 0x05ed, 0x064d }, { 0x05ee, 0x064e }, { 0x05ef, 0x064f }, { 0x05f0, 0x0650 }, + { 0x05f1, 0x0651 }, { 0x05f2, 0x0652 }, { 0x06a1, 0x0452 }, { 0x06a2, 0x0453 }, + { 0x06a3, 0x0451 }, { 0x06a4, 0x0454 }, { 0x06a5, 0x0455 }, { 0x06a6, 0x0456 }, + { 0x06a7, 0x0457 }, { 0x06a8, 0x0458 }, { 0x06a9, 0x0459 }, { 0x06aa, 0x045a }, + { 0x06ab, 0x045b }, { 0x06ac, 0x045c }, { 0x06ae, 0x045e }, { 0x06af, 0x045f }, + { 0x06b0, 0x2116 }, { 0x06b1, 0x0402 }, { 0x06b2, 0x0403 }, { 0x06b3, 0x0401 }, + { 0x06b4, 0x0404 }, { 0x06b5, 0x0405 }, { 0x06b6, 0x0406 }, { 0x06b7, 0x0407 }, + { 0x06b8, 0x0408 }, { 0x06b9, 0x0409 }, { 0x06ba, 0x040a }, { 0x06bb, 0x040b }, + { 0x06bc, 0x040c }, { 0x06be, 0x040e }, { 0x06bf, 0x040f }, { 0x06c0, 0x044e }, + { 0x06c1, 0x0430 }, { 0x06c2, 0x0431 }, { 0x06c3, 0x0446 }, { 0x06c4, 0x0434 }, + { 0x06c5, 0x0435 }, { 0x06c6, 0x0444 }, { 0x06c7, 0x0433 }, { 0x06c8, 0x0445 }, + { 0x06c9, 0x0438 }, { 0x06ca, 0x0439 }, { 0x06cb, 0x043a }, { 0x06cc, 0x043b }, + { 0x06cd, 0x043c }, { 0x06ce, 0x043d }, { 0x06cf, 0x043e }, { 0x06d0, 0x043f }, + { 0x06d1, 0x044f }, { 0x06d2, 0x0440 }, { 0x06d3, 0x0441 }, { 0x06d4, 0x0442 }, + { 0x06d5, 0x0443 }, { 0x06d6, 0x0436 }, { 0x06d7, 0x0432 }, { 0x06d8, 0x044c }, + { 0x06d9, 0x044b }, { 0x06da, 0x0437 }, { 0x06db, 0x0448 }, { 0x06dc, 0x044d }, + { 0x06dd, 0x0449 }, { 0x06de, 0x0447 }, { 0x06df, 0x044a }, { 0x06e0, 0x042e }, + { 0x06e1, 0x0410 }, { 0x06e2, 0x0411 }, { 0x06e3, 0x0426 }, { 0x06e4, 0x0414 }, + { 0x06e5, 0x0415 }, { 0x06e6, 0x0424 }, { 0x06e7, 0x0413 }, { 0x06e8, 0x0425 }, + { 0x06e9, 0x0418 }, { 0x06ea, 0x0419 }, { 0x06eb, 0x041a }, { 0x06ec, 0x041b }, + { 0x06ed, 0x041c }, { 0x06ee, 0x041d }, { 0x06ef, 0x041e }, { 0x06f0, 0x041f }, + { 0x06f1, 0x042f }, { 0x06f2, 0x0420 }, { 0x06f3, 0x0421 }, { 0x06f4, 0x0422 }, + { 0x06f5, 0x0423 }, { 0x06f6, 0x0416 }, { 0x06f7, 0x0412 }, { 0x06f8, 0x042c }, + { 0x06f9, 0x042b }, { 0x06fa, 0x0417 }, { 0x06fb, 0x0428 }, { 0x06fc, 0x042d }, + { 0x06fd, 0x0429 }, { 0x06fe, 0x0427 }, { 0x06ff, 0x042a }, { 0x07a1, 0x0386 }, + { 0x07a2, 0x0388 }, { 0x07a3, 0x0389 }, { 0x07a4, 0x038a }, { 0x07a5, 0x03aa }, + { 0x07a7, 0x038c }, { 0x07a8, 0x038e }, { 0x07a9, 0x03ab }, { 0x07ab, 0x038f }, + { 0x07ae, 0x0385 }, { 0x07af, 0x2015 }, { 0x07b1, 0x03ac }, { 0x07b2, 0x03ad }, + { 0x07b3, 0x03ae }, { 0x07b4, 0x03af }, { 0x07b5, 0x03ca }, { 0x07b6, 0x0390 }, + { 0x07b7, 0x03cc }, { 0x07b8, 0x03cd }, { 0x07b9, 0x03cb }, { 0x07ba, 0x03b0 }, + { 0x07bb, 0x03ce }, { 0x07c1, 0x0391 }, { 0x07c2, 0x0392 }, { 0x07c3, 0x0393 }, + { 0x07c4, 0x0394 }, { 0x07c5, 0x0395 }, { 0x07c6, 0x0396 }, { 0x07c7, 0x0397 }, + { 0x07c8, 0x0398 }, { 0x07c9, 0x0399 }, { 0x07ca, 0x039a }, { 0x07cb, 0x039b }, + { 0x07cc, 0x039c }, { 0x07cd, 0x039d }, { 0x07ce, 0x039e }, { 0x07cf, 0x039f }, + { 0x07d0, 0x03a0 }, { 0x07d1, 0x03a1 }, { 0x07d2, 0x03a3 }, { 0x07d4, 0x03a4 }, + { 0x07d5, 0x03a5 }, { 0x07d6, 0x03a6 }, { 0x07d7, 0x03a7 }, { 0x07d8, 0x03a8 }, + { 0x07d9, 0x03a9 }, { 0x07e1, 0x03b1 }, { 0x07e2, 0x03b2 }, { 0x07e3, 0x03b3 }, + { 0x07e4, 0x03b4 }, { 0x07e5, 0x03b5 }, { 0x07e6, 0x03b6 }, { 0x07e7, 0x03b7 }, + { 0x07e8, 0x03b8 }, { 0x07e9, 0x03b9 }, { 0x07ea, 0x03ba }, { 0x07eb, 0x03bb }, + { 0x07ec, 0x03bc }, { 0x07ed, 0x03bd }, { 0x07ee, 0x03be }, { 0x07ef, 0x03bf }, + { 0x07f0, 0x03c0 }, { 0x07f1, 0x03c1 }, { 0x07f2, 0x03c3 }, { 0x07f3, 0x03c2 }, + { 0x07f4, 0x03c4 }, { 0x07f5, 0x03c5 }, { 0x07f6, 0x03c6 }, { 0x07f7, 0x03c7 }, + { 0x07f8, 0x03c8 }, { 0x07f9, 0x03c9 }, { 0x08a1, 0x23b7 }, { 0x08a2, 0x250c }, + { 0x08a3, 0x2500 }, { 0x08a4, 0x2320 }, { 0x08a5, 0x2321 }, { 0x08a6, 0x2502 }, + { 0x08a7, 0x23a1 }, { 0x08a8, 0x23a3 }, { 0x08a9, 0x23a4 }, { 0x08aa, 0x23a6 }, + { 0x08ab, 0x239b }, { 0x08ac, 0x239d }, { 0x08ad, 0x239e }, { 0x08ae, 0x23a0 }, + { 0x08af, 0x23a8 }, { 0x08b0, 0x23ac }, { 0x08bc, 0x2264 }, { 0x08bd, 0x2260 }, + { 0x08be, 0x2265 }, { 0x08bf, 0x222b }, { 0x08c0, 0x2234 }, { 0x08c1, 0x221d }, + { 0x08c2, 0x221e }, { 0x08c5, 0x2207 }, { 0x08c8, 0x223c }, { 0x08c9, 0x2243 }, + { 0x08cd, 0x21d4 }, { 0x08ce, 0x21d2 }, { 0x08cf, 0x2261 }, { 0x08d6, 0x221a }, + { 0x08da, 0x2282 }, { 0x08db, 0x2283 }, { 0x08dc, 0x2229 }, { 0x08dd, 0x222a }, + { 0x08de, 0x2227 }, { 0x08df, 0x2228 }, { 0x08ef, 0x2202 }, { 0x08f6, 0x0192 }, + { 0x08fb, 0x2190 }, { 0x08fc, 0x2191 }, { 0x08fd, 0x2192 }, { 0x08fe, 0x2193 }, + { 0x09e0, 0x25c6 }, { 0x09e1, 0x2592 }, { 0x09e2, 0x2409 }, { 0x09e3, 0x240c }, + { 0x09e4, 0x240d }, { 0x09e5, 0x240a }, { 0x09e8, 0x2424 }, { 0x09e9, 0x240b }, + { 0x09ea, 0x2518 }, { 0x09eb, 0x2510 }, { 0x09ec, 0x250c }, { 0x09ed, 0x2514 }, + { 0x09ee, 0x253c }, { 0x09ef, 0x23ba }, { 0x09f0, 0x23bb }, { 0x09f1, 0x2500 }, + { 0x09f2, 0x23bc }, { 0x09f3, 0x23bd }, { 0x09f4, 0x251c }, { 0x09f5, 0x2524 }, + { 0x09f6, 0x2534 }, { 0x09f7, 0x252c }, { 0x09f8, 0x2502 }, { 0x0aa1, 0x2003 }, + { 0x0aa2, 0x2002 }, { 0x0aa3, 0x2004 }, { 0x0aa4, 0x2005 }, { 0x0aa5, 0x2007 }, + { 0x0aa6, 0x2008 }, { 0x0aa7, 0x2009 }, { 0x0aa8, 0x200a }, { 0x0aa9, 0x2014 }, + { 0x0aaa, 0x2013 }, { 0x0aae, 0x2026 }, { 0x0aaf, 0x2025 }, { 0x0ab0, 0x2153 }, + { 0x0ab1, 0x2154 }, { 0x0ab2, 0x2155 }, { 0x0ab3, 0x2156 }, { 0x0ab4, 0x2157 }, + { 0x0ab5, 0x2158 }, { 0x0ab6, 0x2159 }, { 0x0ab7, 0x215a }, { 0x0ab8, 0x2105 }, + { 0x0abb, 0x2012 }, { 0x0abc, 0x2329 }, { 0x0abe, 0x232a }, { 0x0ac3, 0x215b }, + { 0x0ac4, 0x215c }, { 0x0ac5, 0x215d }, { 0x0ac6, 0x215e }, { 0x0ac9, 0x2122 }, + { 0x0aca, 0x2613 }, { 0x0acc, 0x25c1 }, { 0x0acd, 0x25b7 }, { 0x0ace, 0x25cb }, + { 0x0acf, 0x25af }, { 0x0ad0, 0x2018 }, { 0x0ad1, 0x2019 }, { 0x0ad2, 0x201c }, + { 0x0ad3, 0x201d }, { 0x0ad4, 0x211e }, { 0x0ad6, 0x2032 }, { 0x0ad7, 0x2033 }, + { 0x0ad9, 0x271d }, { 0x0adb, 0x25ac }, { 0x0adc, 0x25c0 }, { 0x0add, 0x25b6 }, + { 0x0ade, 0x25cf }, { 0x0adf, 0x25ae }, { 0x0ae0, 0x25e6 }, { 0x0ae1, 0x25ab }, + { 0x0ae2, 0x25ad }, { 0x0ae3, 0x25b3 }, { 0x0ae4, 0x25bd }, { 0x0ae5, 0x2606 }, + { 0x0ae6, 0x2022 }, { 0x0ae7, 0x25aa }, { 0x0ae8, 0x25b2 }, { 0x0ae9, 0x25bc }, + { 0x0aea, 0x261c }, { 0x0aeb, 0x261e }, { 0x0aec, 0x2663 }, { 0x0aed, 0x2666 }, + { 0x0aee, 0x2665 }, { 0x0af0, 0x2720 }, { 0x0af1, 0x2020 }, { 0x0af2, 0x2021 }, + { 0x0af3, 0x2713 }, { 0x0af4, 0x2717 }, { 0x0af5, 0x266f }, { 0x0af6, 0x266d }, + { 0x0af7, 0x2642 }, { 0x0af8, 0x2640 }, { 0x0af9, 0x260e }, { 0x0afa, 0x2315 }, + { 0x0afb, 0x2117 }, { 0x0afc, 0x2038 }, { 0x0afd, 0x201a }, { 0x0afe, 0x201e }, + { 0x0ba3, 0x003c }, { 0x0ba6, 0x003e }, { 0x0ba8, 0x2228 }, { 0x0ba9, 0x2227 }, + { 0x0bc0, 0x00af }, { 0x0bc2, 0x22a5 }, { 0x0bc3, 0x2229 }, { 0x0bc4, 0x230a }, + { 0x0bc6, 0x005f }, { 0x0bca, 0x2218 }, { 0x0bcc, 0x2395 }, { 0x0bce, 0x22a4 }, + { 0x0bcf, 0x25cb }, { 0x0bd3, 0x2308 }, { 0x0bd6, 0x222a }, { 0x0bd8, 0x2283 }, + { 0x0bda, 0x2282 }, { 0x0bdc, 0x22a2 }, { 0x0bfc, 0x22a3 }, { 0x0cdf, 0x2017 }, + { 0x0ce0, 0x05d0 }, { 0x0ce1, 0x05d1 }, { 0x0ce2, 0x05d2 }, { 0x0ce3, 0x05d3 }, + { 0x0ce4, 0x05d4 }, { 0x0ce5, 0x05d5 }, { 0x0ce6, 0x05d6 }, { 0x0ce7, 0x05d7 }, + { 0x0ce8, 0x05d8 }, { 0x0ce9, 0x05d9 }, { 0x0cea, 0x05da }, { 0x0ceb, 0x05db }, + { 0x0cec, 0x05dc }, { 0x0ced, 0x05dd }, { 0x0cee, 0x05de }, { 0x0cef, 0x05df }, + { 0x0cf0, 0x05e0 }, { 0x0cf1, 0x05e1 }, { 0x0cf2, 0x05e2 }, { 0x0cf3, 0x05e3 }, + { 0x0cf4, 0x05e4 }, { 0x0cf5, 0x05e5 }, { 0x0cf6, 0x05e6 }, { 0x0cf7, 0x05e7 }, + { 0x0cf8, 0x05e8 }, { 0x0cf9, 0x05e9 }, { 0x0cfa, 0x05ea }, { 0x0da1, 0x0e01 }, + { 0x0da2, 0x0e02 }, { 0x0da3, 0x0e03 }, { 0x0da4, 0x0e04 }, { 0x0da5, 0x0e05 }, + { 0x0da6, 0x0e06 }, { 0x0da7, 0x0e07 }, { 0x0da8, 0x0e08 }, { 0x0da9, 0x0e09 }, + { 0x0daa, 0x0e0a }, { 0x0dab, 0x0e0b }, { 0x0dac, 0x0e0c }, { 0x0dad, 0x0e0d }, + { 0x0dae, 0x0e0e }, { 0x0daf, 0x0e0f }, { 0x0db0, 0x0e10 }, { 0x0db1, 0x0e11 }, + { 0x0db2, 0x0e12 }, { 0x0db3, 0x0e13 }, { 0x0db4, 0x0e14 }, { 0x0db5, 0x0e15 }, + { 0x0db6, 0x0e16 }, { 0x0db7, 0x0e17 }, { 0x0db8, 0x0e18 }, { 0x0db9, 0x0e19 }, + { 0x0dba, 0x0e1a }, { 0x0dbb, 0x0e1b }, { 0x0dbc, 0x0e1c }, { 0x0dbd, 0x0e1d }, + { 0x0dbe, 0x0e1e }, { 0x0dbf, 0x0e1f }, { 0x0dc0, 0x0e20 }, { 0x0dc1, 0x0e21 }, + { 0x0dc2, 0x0e22 }, { 0x0dc3, 0x0e23 }, { 0x0dc4, 0x0e24 }, { 0x0dc5, 0x0e25 }, + { 0x0dc6, 0x0e26 }, { 0x0dc7, 0x0e27 }, { 0x0dc8, 0x0e28 }, { 0x0dc9, 0x0e29 }, + { 0x0dca, 0x0e2a }, { 0x0dcb, 0x0e2b }, { 0x0dcc, 0x0e2c }, { 0x0dcd, 0x0e2d }, + { 0x0dce, 0x0e2e }, { 0x0dcf, 0x0e2f }, { 0x0dd0, 0x0e30 }, { 0x0dd1, 0x0e31 }, + { 0x0dd2, 0x0e32 }, { 0x0dd3, 0x0e33 }, { 0x0dd4, 0x0e34 }, { 0x0dd5, 0x0e35 }, + { 0x0dd6, 0x0e36 }, { 0x0dd7, 0x0e37 }, { 0x0dd8, 0x0e38 }, { 0x0dd9, 0x0e39 }, + { 0x0dda, 0x0e3a }, { 0x0ddf, 0x0e3f }, { 0x0de0, 0x0e40 }, { 0x0de1, 0x0e41 }, + { 0x0de2, 0x0e42 }, { 0x0de3, 0x0e43 }, { 0x0de4, 0x0e44 }, { 0x0de5, 0x0e45 }, + { 0x0de6, 0x0e46 }, { 0x0de7, 0x0e47 }, { 0x0de8, 0x0e48 }, { 0x0de9, 0x0e49 }, + { 0x0dea, 0x0e4a }, { 0x0deb, 0x0e4b }, { 0x0dec, 0x0e4c }, { 0x0ded, 0x0e4d }, + { 0x0df0, 0x0e50 }, { 0x0df1, 0x0e51 }, { 0x0df2, 0x0e52 }, { 0x0df3, 0x0e53 }, + { 0x0df4, 0x0e54 }, { 0x0df5, 0x0e55 }, { 0x0df6, 0x0e56 }, { 0x0df7, 0x0e57 }, + { 0x0df8, 0x0e58 }, { 0x0df9, 0x0e59 }, { 0x0ea1, 0x3131 }, { 0x0ea2, 0x3132 }, + { 0x0ea3, 0x3133 }, { 0x0ea4, 0x3134 }, { 0x0ea5, 0x3135 }, { 0x0ea6, 0x3136 }, + { 0x0ea7, 0x3137 }, { 0x0ea8, 0x3138 }, { 0x0ea9, 0x3139 }, { 0x0eaa, 0x313a }, + { 0x0eab, 0x313b }, { 0x0eac, 0x313c }, { 0x0ead, 0x313d }, { 0x0eae, 0x313e }, + { 0x0eaf, 0x313f }, { 0x0eb0, 0x3140 }, { 0x0eb1, 0x3141 }, { 0x0eb2, 0x3142 }, + { 0x0eb3, 0x3143 }, { 0x0eb4, 0x3144 }, { 0x0eb5, 0x3145 }, { 0x0eb6, 0x3146 }, + { 0x0eb7, 0x3147 }, { 0x0eb8, 0x3148 }, { 0x0eb9, 0x3149 }, { 0x0eba, 0x314a }, + { 0x0ebb, 0x314b }, { 0x0ebc, 0x314c }, { 0x0ebd, 0x314d }, { 0x0ebe, 0x314e }, + { 0x0ebf, 0x314f }, { 0x0ec0, 0x3150 }, { 0x0ec1, 0x3151 }, { 0x0ec2, 0x3152 }, + { 0x0ec3, 0x3153 }, { 0x0ec4, 0x3154 }, { 0x0ec5, 0x3155 }, { 0x0ec6, 0x3156 }, + { 0x0ec7, 0x3157 }, { 0x0ec8, 0x3158 }, { 0x0ec9, 0x3159 }, { 0x0eca, 0x315a }, + { 0x0ecb, 0x315b }, { 0x0ecc, 0x315c }, { 0x0ecd, 0x315d }, { 0x0ece, 0x315e }, + { 0x0ecf, 0x315f }, { 0x0ed0, 0x3160 }, { 0x0ed1, 0x3161 }, { 0x0ed2, 0x3162 }, + { 0x0ed3, 0x3163 }, { 0x0ed4, 0x11a8 }, { 0x0ed5, 0x11a9 }, { 0x0ed6, 0x11aa }, + { 0x0ed7, 0x11ab }, { 0x0ed8, 0x11ac }, { 0x0ed9, 0x11ad }, { 0x0eda, 0x11ae }, + { 0x0edb, 0x11af }, { 0x0edc, 0x11b0 }, { 0x0edd, 0x11b1 }, { 0x0ede, 0x11b2 }, + { 0x0edf, 0x11b3 }, { 0x0ee0, 0x11b4 }, { 0x0ee1, 0x11b5 }, { 0x0ee2, 0x11b6 }, + { 0x0ee3, 0x11b7 }, { 0x0ee4, 0x11b8 }, { 0x0ee5, 0x11b9 }, { 0x0ee6, 0x11ba }, + { 0x0ee7, 0x11bb }, { 0x0ee8, 0x11bc }, { 0x0ee9, 0x11bd }, { 0x0eea, 0x11be }, + { 0x0eeb, 0x11bf }, { 0x0eec, 0x11c0 }, { 0x0eed, 0x11c1 }, { 0x0eee, 0x11c2 }, + { 0x0eef, 0x316d }, { 0x0ef0, 0x3171 }, { 0x0ef1, 0x3178 }, { 0x0ef2, 0x317f }, + { 0x0ef3, 0x3181 }, { 0x0ef4, 0x3184 }, { 0x0ef5, 0x3186 }, { 0x0ef6, 0x318d }, + { 0x0ef7, 0x318e }, { 0x0ef8, 0x11eb }, { 0x0ef9, 0x11f0 }, { 0x0efa, 0x11f9 }, + { 0x0eff, 0x20a9 }, { 0x13a4, 0x20ac }, { 0x13bc, 0x0152 }, { 0x13bd, 0x0153 }, + { 0x13be, 0x0178 }, { 0x20ac, 0x20ac }, +}; + +static unsigned int ImGui_ImplQNX_KeySymToUnicode(unsigned int keysym) +{ + // Latin-1 keysyms map directly to Unicode. + if ((keysym >= 0x0020u && keysym <= 0x007eu) || + (keysym >= 0x00a0u && keysym <= 0x00ffu)) + { + return keysym; + } + + // X11-style directly encoded UCS keysyms use 0x01000000 | codepoint. + if ((keysym & 0xff000000u) == 0x01000000u) + { + const unsigned int codepoint = keysym & 0x00ffffffu; + return codepoint <= IM_UNICODE_CODEPOINT_MAX ? codepoint : 0u; + } + + int min = 0; + int max = (int)(sizeof(ImGui_ImplQNX_KeySymTable) / sizeof(ImGui_ImplQNX_KeySymTable[0])) - 1; + while (max >= min) + { + const int mid = min + (max - min) / 2; + if (ImGui_ImplQNX_KeySymTable[mid].KeySym < keysym) + { + min = mid + 1; + } + else if (ImGui_ImplQNX_KeySymTable[mid].KeySym > keysym) + { + max = mid - 1; + } + else + { + return ImGui_ImplQNX_KeySymTable[mid].Unicode; + } + } + + return 0u; +} + +#endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_qnx.h b/backends/imgui_impl_qnx.h new file mode 100644 index 000000000..7bd86f439 --- /dev/null +++ b/backends/imgui_impl_qnx.h @@ -0,0 +1,35 @@ +// dear imgui: Platform Backend for QNX Screen +// This needs to be used along with a Renderer Backend (e.g. OpenGL3, Vulkan). + +// Implemented features: +// [X] Platform: Keyboard support. Uses io.AddKeyEvent() and native QNX key symbols. +// [X] Platform: Mouse support. Handles position, three buttons and horizontal/vertical wheels. +// [X] Platform: Touch support. Maps the first active touch contact to the primary mouse button. +// [X] Platform: Focus and window-size updates. +// Missing features or Issues: +// [ ] Platform: Clipboard support. +// [ ] Platform: Gamepad support. +// [ ] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). + +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +#pragma once +#include "imgui.h" // IMGUI_IMPL_API +#ifndef IMGUI_DISABLE + +#include + +// The application owns the Screen context, window and event object. +// Poll events with screen_get_event() and pass each non-NONE event to ProcessEvent(). +IMGUI_IMPL_API bool ImGui_ImplQNX_Init(screen_context_t context, screen_window_t window); +IMGUI_IMPL_API void ImGui_ImplQNX_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplQNX_NewFrame(); +IMGUI_IMPL_API bool ImGui_ImplQNX_ProcessEvent(screen_event_t event); + +#endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_sdl2.cpp b/backends/imgui_impl_sdl2.cpp index f578977c4..a93bcc29e 100644 --- a/backends/imgui_impl_sdl2.cpp +++ b/backends/imgui_impl_sdl2.cpp @@ -21,6 +21,9 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-08-06: Fixed querying framebuffer scale when using Metal. (#5592) +// 2026-07-15: Inputs: restore SDL_StartTextInput()/SDL_StopTextInput() in IME handler for on-screen keyboard support on Android/mobile. (#7636, #9474) +// 2026-04-16: Made ImGui_ImplSDL2_GetContentScaleForWindow(), ImGui_ImplSDL2_GetContentScaleForDisplay() helpers return a minimum of 1.0f, as some Linux setup seems to report <1.0f value and this breaks scaling border size. (#9369) // 2026-02-13: Inputs: systems other than X11 are back to starting mouse capture on mouse down (reverts 2025-02-26 change). Only X11 requires waiting for a drag by default (not ideal, but a better default for X11 users). Added ImGui_ImplSDL2_SetMouseCaptureMode() for X11 debugger users. (#3650, #6410, #9235) // 2026-01-15: Changed GetClipboardText() handler to return nullptr on error aka clipboard contents is not text. Consistent with other backends. (#9168) // 2025-09-24: Skip using the SDL_GetGlobalMouseState() state when one of our window is hovered, as the SDL_MOUSEMOTION data is reliable. Fix macOS notch mouse coordinates issue in fullscreen mode + better perf on X11. (#7919, #7786) @@ -133,10 +136,18 @@ #endif #define SDL_HAS_PER_MONITOR_DPI SDL_VERSION_ATLEAST(2,0,4) #define SDL_HAS_VULKAN SDL_VERSION_ATLEAST(2,0,6) +#if defined(__APPLE__) +#define SDL_HAS_METAL SDL_VERSION_ATLEAST(2,0,14) +#else +#define SDL_HAS_METAL 0 +#endif #define SDL_HAS_OPEN_URL SDL_VERSION_ATLEAST(2,0,14) #if SDL_HAS_VULKAN #include #endif +#if SDL_HAS_METAL +#include +#endif // SDL Data struct ImGui_ImplSDL2_Data @@ -146,7 +157,8 @@ struct ImGui_ImplSDL2_Data SDL_Renderer* Renderer; Uint64 Time; char* ClipboardTextData; - char BackendPlatformName[48]; + char BackendPlatformName[64]; + bool IsWayland; // Mouse handling Uint32 MouseWindowID; @@ -203,6 +215,18 @@ static void ImGui_ImplSDL2_PlatformSetImeData(ImGuiContext*, ImGuiViewport*, ImG r.w = 1; r.h = (int)data->InputLineHeight; SDL_SetTextInputRect(&r); + +#ifdef __ANDROID__ + // SDL_StartTextInput() is needed on Android (and some other platforms) to show the on-screen keyboard. (#7636, #9474, #6306) + // It was removed in a7703fe6 due to concerns about its relation to desktop IME, but is required on mobile. + SDL_StartTextInput(); +#endif + } + else + { +#ifdef __ANDROID__ + SDL_StopTextInput(); +#endif } } @@ -498,11 +522,12 @@ static bool ImGui_ImplSDL2_Init(SDL_Window* window, SDL_Renderer* renderer, void SDL_version ver_runtime; SDL_VERSION(&ver_compiled); SDL_GetVersion(&ver_runtime); + const char* sdl_video_driver = SDL_GetCurrentVideoDriver(); // Setup backend capabilities flags ImGui_ImplSDL2_Data* bd = IM_NEW(ImGui_ImplSDL2_Data)(); - snprintf(bd->BackendPlatformName, sizeof(bd->BackendPlatformName), "imgui_impl_sdl2 (%u.%u.%u, %u.%u.%u)", - ver_compiled.major, ver_compiled.minor, ver_compiled.patch, ver_runtime.major, ver_runtime.minor, ver_runtime.patch); + snprintf(bd->BackendPlatformName, sizeof(bd->BackendPlatformName), "imgui_impl_sdl2 (%u.%u.%u, %u.%u.%u) (%s)", + ver_compiled.major, ver_compiled.minor, ver_compiled.patch, ver_runtime.major, ver_runtime.minor, ver_runtime.patch, sdl_video_driver); io.BackendPlatformUserData = (void*)bd; io.BackendPlatformName = bd->BackendPlatformName; io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) @@ -511,16 +536,16 @@ static bool ImGui_ImplSDL2_Init(SDL_Window* window, SDL_Renderer* renderer, void bd->Window = window; bd->WindowID = SDL_GetWindowID(window); bd->Renderer = renderer; + bd->IsWayland = strcmp(sdl_video_driver, "Wayland") == 0; // Check and store if we are on a SDL backend that supports SDL_GetGlobalMouseState() and SDL_CaptureMouse() // ("wayland" and "rpi" don't support it, but we chose to use a white-list instead of a black-list) bd->MouseCanUseGlobalState = false; bd->MouseCaptureMode = ImGui_ImplSDL2_MouseCaptureMode_Disabled; #if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE - const char* sdl_backend = SDL_GetCurrentVideoDriver(); const char* capture_and_global_state_whitelist[] = { "windows", "cocoa", "x11", "DIVE", "VMAN" }; for (const char* item : capture_and_global_state_whitelist) - if (strncmp(sdl_backend, item, strlen(item)) == 0) + if (strncmp(sdl_video_driver, item, strlen(item)) == 0) { bd->MouseCanUseGlobalState = true; bd->MouseCaptureMode = (strcmp(item, "x11") == 0) ? ImGui_ImplSDL2_MouseCaptureMode_EnabledAfterDrag : ImGui_ImplSDL2_MouseCaptureMode_Enabled; @@ -748,6 +773,7 @@ float ImGui_ImplSDL2_GetContentScaleForWindow(SDL_Window* window) return ImGui_ImplSDL2_GetContentScaleForDisplay(SDL_GetWindowDisplayIndex(window)); } +// SDL_GetDisplayDPI() seems rather unreliable on Linux. float ImGui_ImplSDL2_GetContentScaleForDisplay(int display_index) { const char* sdl_driver = SDL_GetCurrentVideoDriver(); @@ -757,7 +783,11 @@ float ImGui_ImplSDL2_GetContentScaleForDisplay(int display_index) #if !defined(__APPLE__) && !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) float dpi = 0.0f; if (SDL_GetDisplayDPI(display_index, &dpi, nullptr, nullptr) == 0) + { + if (dpi < 96.0f) + dpi = 96.0f; return dpi / 96.0f; + } #endif #endif IM_UNUSED(display_index); @@ -878,13 +908,17 @@ static void ImGui_ImplSDL2_GetWindowSizeAndFramebufferScale(SDL_Window* window, #if SDL_HAS_VULKAN else if (SDL_GetWindowFlags(window) & SDL_WINDOW_VULKAN) SDL_Vulkan_GetDrawableSize(window, &display_w, &display_h); +#endif +#if SDL_HAS_METAL + else if (SDL_GetWindowFlags(window) & SDL_WINDOW_METAL) + SDL_Metal_GetDrawableSize(window, &display_w, &display_h); #endif else SDL_GL_GetDrawableSize(window, &display_w, &display_h); if (out_size != nullptr) *out_size = ImVec2((float)w, (float)h); if (out_framebuffer_scale != nullptr) - *out_framebuffer_scale = (w > 0 && h > 0) ? ImVec2((float)display_w / w, (float)display_h / h) : ImVec2(1.0f, 1.0f); + *out_framebuffer_scale = (w > 0 && h > 0) ? ImVec2((float)display_w / (float)w, (float)display_h / (float)h) : ImVec2(1.0f, 1.0f); } void ImGui_ImplSDL2_NewFrame() @@ -902,7 +936,7 @@ void ImGui_ImplSDL2_NewFrame() Uint64 current_time = SDL_GetPerformanceCounter(); if (current_time <= bd->Time) current_time = bd->Time + 1; - io.DeltaTime = bd->Time > 0 ? (float)((double)(current_time - bd->Time) / frequency) : (float)(1.0f / 60.0f); + io.DeltaTime = bd->Time > 0 ? (float)((double)(current_time - bd->Time) / (double)frequency) : (float)(1.0f / 60.0f); bd->Time = current_time; if (bd->MouseLastLeaveFrame && bd->MouseLastLeaveFrame >= ImGui::GetFrameCount() && bd->MouseButtonsDown == 0) diff --git a/backends/imgui_impl_sdl3.cpp b/backends/imgui_impl_sdl3.cpp index a2bd606a6..05f6498f1 100644 --- a/backends/imgui_impl_sdl3.cpp +++ b/backends/imgui_impl_sdl3.cpp @@ -111,7 +111,8 @@ struct ImGui_ImplSDL3_Data SDL_Renderer* Renderer; Uint64 Time; char* ClipboardTextData; - char BackendPlatformName[48]; + char BackendPlatformName[64]; + bool IsWayland; // IME handling SDL_Window* ImeWindow; @@ -502,11 +503,13 @@ static bool ImGui_ImplSDL3_Init(SDL_Window* window, SDL_Renderer* renderer, void //SDL_SetHint(SDL_HINT_EVENT_LOGGING, "2"); const int ver_linked = SDL_GetVersion(); + const char* sdl_video_driver = SDL_GetCurrentVideoDriver(); // Setup backend capabilities flags ImGui_ImplSDL3_Data* bd = IM_NEW(ImGui_ImplSDL3_Data)(); - snprintf(bd->BackendPlatformName, sizeof(bd->BackendPlatformName), "imgui_impl_sdl3 (%d.%d.%d; %d.%d.%d)", - SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_MICRO_VERSION, SDL_VERSIONNUM_MAJOR(ver_linked), SDL_VERSIONNUM_MINOR(ver_linked), SDL_VERSIONNUM_MICRO(ver_linked)); + snprintf(bd->BackendPlatformName, sizeof(bd->BackendPlatformName), "imgui_impl_sdl3 (%d.%d.%d; %d.%d.%d) (%s)", + SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_MICRO_VERSION, SDL_VERSIONNUM_MAJOR(ver_linked), SDL_VERSIONNUM_MINOR(ver_linked), SDL_VERSIONNUM_MICRO(ver_linked), + sdl_video_driver); io.BackendPlatformUserData = (void*)bd; io.BackendPlatformName = bd->BackendPlatformName; io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) @@ -515,16 +518,16 @@ static bool ImGui_ImplSDL3_Init(SDL_Window* window, SDL_Renderer* renderer, void bd->Window = window; bd->WindowID = SDL_GetWindowID(window); bd->Renderer = renderer; + bd->IsWayland = strcmp(sdl_video_driver, "wayland") == 0; // Check and store if we are on a SDL backend that supports SDL_GetGlobalMouseState() and SDL_CaptureMouse() // ("wayland" and "rpi" don't support it, but we chose to use a white-list instead of a black-list) bd->MouseCanUseGlobalState = false; bd->MouseCaptureMode = ImGui_ImplSDL3_MouseCaptureMode_Disabled; #if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE - const char* sdl_backend = SDL_GetCurrentVideoDriver(); const char* capture_and_global_state_whitelist[] = { "windows", "cocoa", "x11", "DIVE", "VMAN" }; for (const char* item : capture_and_global_state_whitelist) - if (strncmp(sdl_backend, item, strlen(item)) == 0) + if (strncmp(sdl_video_driver, item, strlen(item)) == 0) { bd->MouseCanUseGlobalState = true; bd->MouseCaptureMode = (strcmp(item, "x11") == 0) ? ImGui_ImplSDL3_MouseCaptureMode_EnabledAfterDrag : ImGui_ImplSDL3_MouseCaptureMode_Enabled; @@ -686,8 +689,8 @@ static void ImGui_ImplSDL3_UpdateMouseData() int window_x, window_y; SDL_GetGlobalMouseState(&mouse_x, &mouse_y); SDL_GetWindowPosition(focused_window, &window_x, &window_y); - mouse_x -= window_x; - mouse_y -= window_y; + mouse_x -= (float)window_x; + mouse_y -= (float)window_y; io.AddMousePosEvent(mouse_x, mouse_y); } } @@ -860,7 +863,7 @@ void ImGui_ImplSDL3_NewFrame() Uint64 current_time = SDL_GetPerformanceCounter(); if (current_time <= bd->Time) current_time = bd->Time + 1; - io.DeltaTime = bd->Time > 0 ? (float)((double)(current_time - bd->Time) / frequency) : (float)(1.0f / 60.0f); + io.DeltaTime = bd->Time > 0 ? (float)((double)(current_time - bd->Time) / (double)frequency) : (float)(1.0f / 60.0f); bd->Time = current_time; if (bd->MousePendingLeaveFrame && bd->MousePendingLeaveFrame >= ImGui::GetFrameCount() && bd->MouseButtonsDown == 0) diff --git a/backends/imgui_impl_sdlgpu3.cpp b/backends/imgui_impl_sdlgpu3.cpp index 975fcefb9..2f6b25015 100644 --- a/backends/imgui_impl_sdlgpu3.cpp +++ b/backends/imgui_impl_sdlgpu3.cpp @@ -5,6 +5,7 @@ // [X] Renderer: User texture binding. Use 'SDL_GPUTexture*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! **IMPORTANT** Before 2025/08/08, ImTextureID was a reference to a SDL_GPUTextureSamplerBinding struct. // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplSDLGPU3_GetRenderState(). // The aim of imgui_impl_sdlgpu3.h/.cpp is to be usable in your engine without any modification. // IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ @@ -22,6 +23,10 @@ // Calling the function is MANDATORY, otherwise the ImGui will not upload neither the vertex nor the index buffer for the GPU. See imgui_impl_sdlgpu3.cpp for more info. // CHANGELOG +// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) +// 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. Obsoleting samplers from ImGui_ImplSDLGPU3_RenderState. (#9378) +// 2026-03-19: Fixed issue in ImGui_ImplSDLGPU3_DestroyTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295, #9310) +// 2026-02-25: Removed unnecessary call to SDL_WaitForGPUIdle when releasing vertex/index buffers. (#9262) // 2025-11-26: macOS version can use MSL shaders in order to support macOS 10.14+ (vs Metallib shaders requiring macOS 14+). Requires calling SDL_CreateGPUDevice() with SDL_GPU_SHADERFORMAT_MSL. // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-08-20: Added ImGui_ImplSDLGPU3_InitInfo::SwapchainComposition and ImGui_ImplSDLGPU3_InitInfo::PresentMode to configure how secondary viewports are created. @@ -57,6 +62,10 @@ struct ImGui_ImplSDLGPU3_Data { ImGui_ImplSDLGPU3_InitInfo InitInfo; + // Render state + ImGui_ImplSDLGPU3_RenderState* RenderState = nullptr; // == ImGui::GetPlatformIO().Renderer_RenderState during rendering. + SDL_GPUSampler* CurrentSampler = nullptr; + // Graphics pipeline & shaders SDL_GPUShader* VertexShader = nullptr; SDL_GPUShader* FragmentShader = nullptr; @@ -85,10 +94,15 @@ static ImGui_ImplSDLGPU3_Data* ImGui_ImplSDLGPU3_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplSDLGPU3_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } -static void ImGui_ImplSDLGPU3_SetupRenderState(ImDrawData* draw_data, ImGui_ImplSDLGPU3_RenderState* render_state, SDL_GPUGraphicsPipeline* pipeline, SDL_GPUCommandBuffer* command_buffer, SDL_GPURenderPass* render_pass, ImGui_ImplSDLGPU3_FrameData* fd, uint32_t fb_width, uint32_t fb_height) +ImGui_ImplSDLGPU3_RenderState* ImGui_ImplSDLGPU3_GetRenderState() +{ + return (ImGui_ImplSDLGPU3_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; +} + +static void ImGui_ImplSDLGPU3_SetupRenderState(ImDrawData* draw_data, SDL_GPUGraphicsPipeline* pipeline, SDL_GPUCommandBuffer* command_buffer, SDL_GPURenderPass* render_pass, ImGui_ImplSDLGPU3_FrameData* fd, uint32_t fb_width, uint32_t fb_height) { ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData(); - render_state->SamplerCurrent = bd->TexSamplerLinear; + bd->CurrentSampler = bd->TexSamplerLinear; // Bind graphics pipeline SDL_BindGPUGraphicsPipeline(render_pass, pipeline); @@ -131,8 +145,7 @@ static void CreateOrResizeBuffers(SDL_GPUBuffer** buffer, SDL_GPUTransferBuffer* ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData(); ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo; - // FIXME-OPT: Not optimal, but this is fairly rarely called. - SDL_WaitForGPUIdle(v->Device); + // There is no need for calling SDL_WaitForGPUIdle here, as SDL3 will handle deferred buffer deletion automatically. SDL_ReleaseGPUBuffer(v->Device, *buffer); SDL_ReleaseGPUTransferBuffer(v->Device, *transferbuffer); @@ -157,8 +170,8 @@ static void CreateOrResizeBuffers(SDL_GPUBuffer** buffer, SDL_GPUTransferBuffer* void ImGui_ImplSDLGPU3_PrepareDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffer* command_buffer) { // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); if (fb_width <= 0 || fb_height <= 0 || draw_data->TotalVtxCount <= 0) return; @@ -173,8 +186,8 @@ void ImGui_ImplSDLGPU3_PrepareDrawData(ImDrawData* draw_data, SDL_GPUCommandBuff ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo; ImGui_ImplSDLGPU3_FrameData* fd = &bd->MainWindowFrameData; - uint32_t vertex_size = draw_data->TotalVtxCount * sizeof(ImDrawVert); - uint32_t index_size = draw_data->TotalIdxCount * sizeof(ImDrawIdx); + uint32_t vertex_size = (uint32_t)draw_data->TotalVtxCount * sizeof(ImDrawVert); + uint32_t index_size = (uint32_t)draw_data->TotalIdxCount * sizeof(ImDrawIdx); if (fd->VertexBuffer == nullptr || fd->VertexBufferSize < vertex_size) CreateOrResizeBuffers(&fd->VertexBuffer, &fd->VertexTransferBuffer, &fd->VertexBufferSize, vertex_size, SDL_GPU_BUFFERUSAGE_VERTEX); if (fd->IndexBuffer == nullptr || fd->IndexBufferSize < index_size) @@ -215,11 +228,16 @@ void ImGui_ImplSDLGPU3_PrepareDrawData(ImDrawData* draw_data, SDL_GPUCommandBuff SDL_EndGPUCopyPass(copy_pass); } +// Draw callbacks +static void ImGui_ImplSDLGPU3_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. +static void ImGui_ImplSDLGPU3_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData(); bd->CurrentSampler = bd->TexSamplerLinear; } +static void ImGui_ImplSDLGPU3_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData(); bd->CurrentSampler = bd->TexSamplerNearest; } + void ImGui_ImplSDLGPU3_RenderDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffer* command_buffer, SDL_GPURenderPass* render_pass, SDL_GPUGraphicsPipeline* pipeline) { // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); if (fb_width <= 0 || fb_height <= 0) return; @@ -237,11 +255,9 @@ void ImGui_ImplSDLGPU3_RenderDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffe ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); ImGui_ImplSDLGPU3_RenderState render_state; render_state.Device = bd->InitInfo.Device; - render_state.SamplerLinear = render_state.SamplerCurrent = bd->TexSamplerLinear; - render_state.SamplerNearest = bd->TexSamplerNearest; - platform_io.Renderer_RenderState = &render_state; + platform_io.Renderer_RenderState = bd->RenderState = &render_state; - ImGui_ImplSDLGPU3_SetupRenderState(draw_data, &render_state, pipeline, command_buffer, render_pass, fd, fb_width, fb_height); + ImGui_ImplSDLGPU3_SetupRenderState(draw_data, pipeline, command_buffer, render_pass, fd, fb_width, fb_height); // Render command lists // (Because we merged all buffers into a single one, we maintain our own offset into them) @@ -255,9 +271,8 @@ void ImGui_ImplSDLGPU3_RenderDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffe if (pcmd->UserCallback != nullptr) { // User callback, registered via ImDrawList::AddCallback() - // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) - if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) - ImGui_ImplSDLGPU3_SetupRenderState(draw_data, &render_state, pipeline, command_buffer, render_pass, fd, fb_width, fb_height); + if (pcmd->UserCallback == ImGui_ImplSDLGPU3_DrawCallback_ResetRenderState) + ImGui_ImplSDLGPU3_SetupRenderState(draw_data, pipeline, command_buffer, render_pass, fd, fb_width, fb_height); else pcmd->UserCallback(draw_list, pcmd); } @@ -270,8 +285,8 @@ void ImGui_ImplSDLGPU3_RenderDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffe // Clamp to viewport as SDL_SetGPUScissor() won't accept values that are off bounds if (clip_min.x < 0.0f) { clip_min.x = 0.0f; } if (clip_min.y < 0.0f) { clip_min.y = 0.0f; } - if (clip_max.x > fb_width) { clip_max.x = (float)fb_width; } - if (clip_max.y > fb_height) { clip_max.y = (float)fb_height; } + if (clip_max.x > (float)fb_width) { clip_max.x = (float)fb_width; } + if (clip_max.y > (float)fb_height) { clip_max.y = (float)fb_height; } if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) continue; @@ -286,7 +301,7 @@ void ImGui_ImplSDLGPU3_RenderDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffe // Bind DescriptorSet with font or user texture SDL_GPUTextureSamplerBinding texture_sampler_binding; texture_sampler_binding.texture = (SDL_GPUTexture*)(intptr_t)pcmd->GetTexID(); - texture_sampler_binding.sampler = render_state.SamplerCurrent; + texture_sampler_binding.sampler = bd->CurrentSampler; SDL_BindGPUFragmentSamplers(render_pass, 0, &texture_sampler_binding, 1); // Draw @@ -305,13 +320,16 @@ void ImGui_ImplSDLGPU3_RenderDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffe // We perform a call to SDL_SetGPUScissor() to set back a full viewport which is likely to fix things for 99% users but technically this is not perfect. (See github #4644) SDL_Rect scissor_rect { 0, 0, fb_width, fb_height }; SDL_SetGPUScissor(render_pass, &scissor_rect); + + platform_io.Renderer_RenderState = bd->RenderState = nullptr; } static void ImGui_ImplSDLGPU3_DestroyTexture(ImTextureData* tex) { ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData(); - if (SDL_GPUTexture* raw_tex = (SDL_GPUTexture*)(intptr_t)tex->GetTexID()) - SDL_ReleaseGPUTexture(bd->InitInfo.Device, raw_tex); + if (tex->GetTexID() != ImTextureID_Invalid) + if (SDL_GPUTexture* raw_tex = (SDL_GPUTexture*)(intptr_t)tex->GetTexID()) + SDL_ReleaseGPUTexture(bd->InitInfo.Device, raw_tex); // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) tex->SetTexID(ImTextureID_Invalid); @@ -580,7 +598,7 @@ void ImGui_ImplSDLGPU3_CreateDeviceObjects() if (bd->TexSamplerLinear == nullptr) { - // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling. + // Bilinear sampling is required. SDL_GPUSamplerCreateInfo sampler_info = {}; sampler_info.min_filter = SDL_GPU_FILTER_LINEAR; sampler_info.mag_filter = SDL_GPU_FILTER_LINEAR; @@ -654,6 +672,11 @@ bool ImGui_ImplSDLGPU3_Init(ImGui_ImplSDLGPU3_InitInfo* info) io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.DrawCallback_ResetRenderState = ImGui_ImplSDLGPU3_DrawCallback_ResetRenderState; + platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplSDLGPU3_DrawCallback_SetSamplerLinear; + platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplSDLGPU3_DrawCallback_SetSamplerNearest; + IM_ASSERT(info->Device != nullptr); IM_ASSERT(info->ColorTargetFormat != SDL_GPU_TEXTUREFORMAT_INVALID); diff --git a/backends/imgui_impl_sdlgpu3.h b/backends/imgui_impl_sdlgpu3.h index 1c73dfd71..a517c43b1 100644 --- a/backends/imgui_impl_sdlgpu3.h +++ b/backends/imgui_impl_sdlgpu3.h @@ -5,6 +5,7 @@ // [X] Renderer: User texture binding. Use 'SDL_GPUTexture*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! **IMPORTANT** Before 2025/08/08, ImTextureID was a reference to a SDL_GPUTextureSamplerBinding struct. // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplSDLGPU3_GetRenderState(). // The aim of imgui_impl_sdlgpu3.h/.cpp is to be usable in your engine without any modification. // IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ @@ -57,9 +58,7 @@ IMGUI_IMPL_API void ImGui_ImplSDLGPU3_UpdateTexture(ImTextureData* tex); struct ImGui_ImplSDLGPU3_RenderState { SDL_GPUDevice* Device; - SDL_GPUSampler* SamplerLinear; // Bilinear filtering sampler - SDL_GPUSampler* SamplerNearest; // Nearest/point filtering sampler - SDL_GPUSampler* SamplerCurrent; // Current sampler (may be changed by callback) }; +IMGUI_IMPL_API ImGui_ImplSDLGPU3_RenderState* ImGui_ImplSDLGPU3_GetRenderState(); #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_sdlrenderer2.cpp b/backends/imgui_impl_sdlrenderer2.cpp index e03d13115..d8dcfb154 100644 --- a/backends/imgui_impl_sdlrenderer2.cpp +++ b/backends/imgui_impl_sdlrenderer2.cpp @@ -14,8 +14,10 @@ // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. +// Missing features or Issues: +// [ ] Renderer: Missing support for DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest callbacks: SDLRenderer2 does not support changing SDL_SCALE_MODE while rendering. -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // Learn about Dear ImGui: // - FAQ https://dearimgui.com/faq @@ -24,6 +26,9 @@ // - Introduction, links and more at the top of imgui.cpp // CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState (others cannot be supported). (#9378) +// 2026-03-12: Fixed invalid assert in ImGui_ImplSDLRenderer2_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295) // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplSDLRenderer2_CreateFontsTexture() and ImGui_ImplSDLRenderer2_DestroyFontsTexture(). // 2025-01-18: Use endian-dependent RGBA32 texture format, to match SDL_Color. @@ -50,7 +55,8 @@ #endif // SDL -#include +#include +#include #if !SDL_VERSION_ATLEAST(2,0,17) #error This backend requires SDL 2.0.17+ because of SDL_RenderGeometry() function #endif @@ -70,42 +76,12 @@ static ImGui_ImplSDLRenderer2_Data* ImGui_ImplSDLRenderer2_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplSDLRenderer2_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +ImGui_ImplSDLRenderer2_RenderState* ImGui_ImplSDLRenderer2_GetRenderState() +{ + return (ImGui_ImplSDLRenderer2_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; +} + // Functions -bool ImGui_ImplSDLRenderer2_Init(SDL_Renderer* renderer) -{ - ImGuiIO& io = ImGui::GetIO(); - IMGUI_CHECKVERSION(); - IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); - IM_ASSERT(renderer != nullptr && "SDL_Renderer not initialized!"); - - // Setup backend capabilities flags - ImGui_ImplSDLRenderer2_Data* bd = IM_NEW(ImGui_ImplSDLRenderer2_Data)(); - io.BackendRendererUserData = (void*)bd; - io.BackendRendererName = "imgui_impl_sdlrenderer2"; - io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. - io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. - - bd->Renderer = renderer; - - return true; -} - -void ImGui_ImplSDLRenderer2_Shutdown() -{ - ImGui_ImplSDLRenderer2_Data* bd = ImGui_ImplSDLRenderer2_GetBackendData(); - IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); - ImGuiIO& io = ImGui::GetIO(); - ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); - - ImGui_ImplSDLRenderer2_DestroyDeviceObjects(); - - io.BackendRendererName = nullptr; - io.BackendRendererUserData = nullptr; - io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); - platform_io.ClearRendererHandlers(); - IM_DELETE(bd); -} - static void ImGui_ImplSDLRenderer2_SetupRenderState(SDL_Renderer* renderer) { // Clear out any viewports and cliprect set by the user @@ -121,6 +97,9 @@ void ImGui_ImplSDLRenderer2_NewFrame() IM_UNUSED(bd); } +// Draw callbacks +static void ImGui_ImplSDLRenderer2_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. + void ImGui_ImplSDLRenderer2_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* renderer) { // If there's a scale factor set by the user, use that instead @@ -183,8 +162,7 @@ void ImGui_ImplSDLRenderer2_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* if (pcmd->UserCallback) { // User callback, registered via ImDrawList::AddCallback() - // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) - if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + if (pcmd->UserCallback == ImGui_ImplSDLRenderer2_DrawCallback_ResetRenderState) ImGui_ImplSDLRenderer2_SetupRenderState(renderer); else pcmd->UserCallback(draw_list, pcmd); @@ -241,8 +219,7 @@ void ImGui_ImplSDLRenderer2_UpdateTexture(ImTextureData* tex) IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); - // Create texture - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) + // Create texture (bilinear sampling is required) SDL_Texture* sdl_texture = SDL_CreateTexture(bd->Renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, tex->Width, tex->Height); IM_ASSERT(sdl_texture != nullptr && "Backend failed to create texture!"); SDL_UpdateTexture(sdl_texture, nullptr, tex->GetPixels(), tex->GetPitch()); @@ -267,8 +244,9 @@ void ImGui_ImplSDLRenderer2_UpdateTexture(ImTextureData* tex) } else if (tex->Status == ImTextureStatus_WantDestroy) { - if (SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID) - SDL_DestroyTexture(sdl_texture); + if (tex->TexID != ImTextureID_Invalid) + if (SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID) + SDL_DestroyTexture(sdl_texture); // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) tex->SetTexID(ImTextureID_Invalid); @@ -291,6 +269,44 @@ void ImGui_ImplSDLRenderer2_DestroyDeviceObjects() } } +bool ImGui_ImplSDLRenderer2_Init(SDL_Renderer* renderer) +{ + ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); + IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); + IM_ASSERT(renderer != nullptr && "SDL_Renderer not initialized!"); + + // Setup backend capabilities flags + ImGui_ImplSDLRenderer2_Data* bd = IM_NEW(ImGui_ImplSDLRenderer2_Data)(); + io.BackendRendererUserData = (void*)bd; + io.BackendRendererName = "imgui_impl_sdlrenderer2"; + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. + + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.DrawCallback_ResetRenderState = ImGui_ImplSDLRenderer2_DrawCallback_ResetRenderState; + + bd->Renderer = renderer; + + return true; +} + +void ImGui_ImplSDLRenderer2_Shutdown() +{ + ImGui_ImplSDLRenderer2_Data* bd = ImGui_ImplSDLRenderer2_GetBackendData(); + IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + + ImGui_ImplSDLRenderer2_DestroyDeviceObjects(); + + io.BackendRendererName = nullptr; + io.BackendRendererUserData = nullptr; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); + platform_io.ClearRendererHandlers(); + IM_DELETE(bd); +} + //----------------------------------------------------------------------------- #if defined(__clang__) diff --git a/backends/imgui_impl_sdlrenderer2.h b/backends/imgui_impl_sdlrenderer2.h index 860e95507..e16b8cae9 100644 --- a/backends/imgui_impl_sdlrenderer2.h +++ b/backends/imgui_impl_sdlrenderer2.h @@ -14,6 +14,8 @@ // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. +// Missing features or Issues: +// [ ] Renderer: Missing support for DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest callbacks: SDLRenderer2 does not support changing SDL_SCALE_MODE while rendering. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. @@ -49,5 +51,6 @@ struct ImGui_ImplSDLRenderer2_RenderState { SDL_Renderer* Renderer; }; +IMGUI_IMPL_API ImGui_ImplSDLRenderer2_RenderState* ImGui_ImplSDLRenderer2_GetRenderState(); #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_sdlrenderer3.cpp b/backends/imgui_impl_sdlrenderer3.cpp index 38a4383c2..6dd4c14fe 100644 --- a/backends/imgui_impl_sdlrenderer3.cpp +++ b/backends/imgui_impl_sdlrenderer3.cpp @@ -13,9 +13,9 @@ // [X] Renderer: User texture binding. Use 'SDL_Texture*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). -// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplSDLRenderer3_GetRenderState(). -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // Learn about Dear ImGui: // - FAQ https://dearimgui.com/faq @@ -24,6 +24,11 @@ // - Introduction, links and more at the top of imgui.cpp // CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2026-09-17: Restore modified texture scale modes at the end of RenderDrawData(). Added DrawCallback_SetSamplerFromTex support. Added CurrentScaleMode in ImGui_ImplSDLRenderer3_RenderState (#9543, #9378) +// 2026-07-15: Fixed default sampler state to be linear (broken 2026-04-23). (#9470, #9378) +// 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) +// 2026-03-12: Fixed invalid assert in ImGui_ImplSDLRenderer3_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295) // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplSDLRenderer3_CreateFontsTexture() and ImGui_ImplSDLRenderer3_DestroyFontsTexture(). // 2025-01-18: Use endian-dependent RGBA32 texture format, to match SDL_Color. @@ -47,16 +52,24 @@ #endif // SDL -#include +#include +#include #if !SDL_VERSION_ATLEAST(3,0,0) #error This backend requires SDL 3.0.0+ #endif // SDL_Renderer data +struct ImGui_ImplSDLRenderer3_TexScaleModeEntry +{ + SDL_Texture* Texture; + SDL_ScaleMode ScaleModeBackup; +}; + struct ImGui_ImplSDLRenderer3_Data { SDL_Renderer* Renderer; // Main viewport's renderer ImVector ColorBuffer; + ImVector TexScaleModeBackups; // Backup of texture scale modes ImGui_ImplSDLRenderer3_Data() { memset((void*)this, 0, sizeof(*this)); } }; @@ -68,48 +81,21 @@ static ImGui_ImplSDLRenderer3_Data* ImGui_ImplSDLRenderer3_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplSDLRenderer3_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +ImGui_ImplSDLRenderer3_RenderState* ImGui_ImplSDLRenderer3_GetRenderState() +{ + return (ImGui_ImplSDLRenderer3_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; +} + // Functions -bool ImGui_ImplSDLRenderer3_Init(SDL_Renderer* renderer) -{ - ImGuiIO& io = ImGui::GetIO(); - IMGUI_CHECKVERSION(); - IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); - IM_ASSERT(renderer != nullptr && "SDL_Renderer not initialized!"); - - // Setup backend capabilities flags - ImGui_ImplSDLRenderer3_Data* bd = IM_NEW(ImGui_ImplSDLRenderer3_Data)(); - io.BackendRendererUserData = (void*)bd; - io.BackendRendererName = "imgui_impl_sdlrenderer3"; - io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. - io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. - - bd->Renderer = renderer; - - return true; -} - -void ImGui_ImplSDLRenderer3_Shutdown() -{ - ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData(); - IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); - ImGuiIO& io = ImGui::GetIO(); - ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); - - ImGui_ImplSDLRenderer3_DestroyDeviceObjects(); - - io.BackendRendererName = nullptr; - io.BackendRendererUserData = nullptr; - io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); - platform_io.ClearRendererHandlers(); - IM_DELETE(bd); -} - static void ImGui_ImplSDLRenderer3_SetupRenderState(SDL_Renderer* renderer) { + ImGui_ImplSDLRenderer3_RenderState* render_state = ImGui_ImplSDLRenderer3_GetRenderState(); + // Clear out any viewports and cliprect set by the user // FIXME: Technically speaking there are lots of other things we could backup/setup/restore during our render process. SDL_SetRenderViewport(renderer, nullptr); SDL_SetRenderClipRect(renderer, nullptr); + render_state->CurrentScaleMode = SDL_SCALEMODE_LINEAR; } void ImGui_ImplSDLRenderer3_NewFrame() @@ -137,6 +123,64 @@ static int SDL_RenderGeometryRaw8BitColor(SDL_Renderer* renderer, ImVectorCurrentScaleMode = SDL_SCALEMODE_LINEAR; } +static void ImGui_ImplSDLRenderer3_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplSDLRenderer3_RenderState* render_state = ImGui_ImplSDLRenderer3_GetRenderState(); render_state->CurrentScaleMode = SDL_SCALEMODE_NEAREST; } +static void ImGui_ImplSDLRenderer3_DrawCallback_SetSamplerFromTex(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplSDLRenderer3_RenderState* render_state = ImGui_ImplSDLRenderer3_GetRenderState(); render_state->CurrentScaleMode = SDL_SCALEMODE_INVALID; } // SDL_SCALEMODE_INVALID = use/keep value in texture. + +// SDLRenderer has various design issues which makes it hard to do it compared to regular graphics API. +// - scale/filtering mode is tied to a texture. +// - scale/filtering mode is not queued as part of the SDL_RenderGeometryXXX commands, so a change in one texture requires a flush. +static void ImGui_ImplSDLRenderer3_TexScaleModeUpdate(SDL_Renderer* renderer, SDL_Texture* tex, SDL_ScaleMode desired_scale_mode) +{ + ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData(); + if (desired_scale_mode != SDL_SCALEMODE_INVALID) + { + // Enforce a scale mode + SDL_ScaleMode tex_scale_mode = desired_scale_mode; + if (!SDL_GetTextureScaleMode(tex, &tex_scale_mode) || tex_scale_mode == desired_scale_mode) + return; + SDL_FlushRenderer(renderer); + SDL_SetTextureScaleMode(tex, desired_scale_mode); + + // Store backup of old scale mode (FIXME-OPT) + for (ImGui_ImplSDLRenderer3_TexScaleModeEntry& entry : bd->TexScaleModeBackups) + if (entry.Texture == tex) + { + if (entry.ScaleModeBackup == SDL_SCALEMODE_INVALID) + entry.ScaleModeBackup = tex_scale_mode; + return; + } + ImGui_ImplSDLRenderer3_TexScaleModeEntry entry = { tex, tex_scale_mode }; + bd->TexScaleModeBackups.push_back(entry); + } + else + { + // Use scale mode that was set in texture by user. If there is an entry, restore it and clear the entry. + for (ImGui_ImplSDLRenderer3_TexScaleModeEntry& entry : bd->TexScaleModeBackups) + if (entry.Texture == tex && entry.ScaleModeBackup != SDL_SCALEMODE_INVALID) + { + SDL_FlushRenderer(renderer); + SDL_SetTextureScaleMode(tex, entry.ScaleModeBackup); + entry.ScaleModeBackup = SDL_SCALEMODE_INVALID; + return; + } + } +} + +static void ImGui_ImplSDLRenderer3_TexScaleModeRestoreBackups(SDL_Renderer* renderer) +{ + ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData(); + if (bd->TexScaleModeBackups.Size == 0) + return; + SDL_FlushRenderer(renderer); + for (ImGui_ImplSDLRenderer3_TexScaleModeEntry& entry : bd->TexScaleModeBackups) + if (entry.ScaleModeBackup != SDL_SCALEMODE_INVALID) + SDL_SetTextureScaleMode(entry.Texture, entry.ScaleModeBackup); + bd->TexScaleModeBackups.resize(0); +} + void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* renderer) { ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData(); @@ -178,15 +222,16 @@ void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* SDL_GetRenderViewport(renderer, &old.Viewport); SDL_GetRenderClipRect(renderer, &old.ClipRect); - // Setup desired state - ImGui_ImplSDLRenderer3_SetupRenderState(renderer); - // Setup render state structure (for callbacks and custom texture bindings) ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); ImGui_ImplSDLRenderer3_RenderState render_state; render_state.Renderer = renderer; platform_io.Renderer_RenderState = &render_state; + // Setup desired state + ImGui_ImplSDLRenderer3_SetupRenderState(renderer); + IM_ASSERT(bd->TexScaleModeBackups.Size == 0); + // Will project scissor/clipping rectangles into framebuffer space ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports ImVec2 clip_scale = render_scale; @@ -203,8 +248,7 @@ void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* if (pcmd->UserCallback) { // User callback, registered via ImDrawList::AddCallback() - // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) - if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + if (pcmd->UserCallback == ImGui_ImplSDLRenderer3_DrawCallback_ResetRenderState) ImGui_ImplSDLRenderer3_SetupRenderState(renderer); else pcmd->UserCallback(draw_list, pcmd); @@ -220,16 +264,17 @@ void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* if (clip_max.y > (float)fb_height) { clip_max.y = (float)fb_height; } if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) continue; - SDL_Rect r = { (int)(clip_min.x), (int)(clip_min.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y) }; SDL_SetRenderClipRect(renderer, &r); + // Setup texture scale/filtering mode + SDL_Texture* tex = (SDL_Texture*)pcmd->GetTexID(); + ImGui_ImplSDLRenderer3_TexScaleModeUpdate(renderer, tex, render_state.CurrentScaleMode); + + // Draw const float* xy = (const float*)(const void*)((const char*)(vtx_buffer + pcmd->VtxOffset) + offsetof(ImDrawVert, pos)); const float* uv = (const float*)(const void*)((const char*)(vtx_buffer + pcmd->VtxOffset) + offsetof(ImDrawVert, uv)); - const SDL_Color* color = (const SDL_Color*)(const void*)((const char*)(vtx_buffer + pcmd->VtxOffset) + offsetof(ImDrawVert, col)); // SDL 2.0.19+ - - // Bind texture, Draw - SDL_Texture* tex = (SDL_Texture*)pcmd->GetTexID(); + const SDL_Color* color = (const SDL_Color*)(const void*)((const char*)(vtx_buffer + pcmd->VtxOffset) + offsetof(ImDrawVert, col)); SDL_RenderGeometryRaw8BitColor(renderer, bd->ColorBuffer, tex, xy, (int)sizeof(ImDrawVert), color, (int)sizeof(ImDrawVert), @@ -244,6 +289,7 @@ void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* // Restore modified SDL_Renderer state SDL_SetRenderViewport(renderer, old.ViewportEnabled ? &old.Viewport : nullptr); SDL_SetRenderClipRect(renderer, old.ClipEnabled ? &old.ClipRect : nullptr); + ImGui_ImplSDLRenderer3_TexScaleModeRestoreBackups(renderer); } void ImGui_ImplSDLRenderer3_UpdateTexture(ImTextureData* tex) @@ -254,11 +300,10 @@ void ImGui_ImplSDLRenderer3_UpdateTexture(ImTextureData* tex) { // Create and upload new texture to graphics system //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); - IM_ASSERT(tex->TexID == 0 && tex->BackendUserData == nullptr); + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); - // Create texture - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) + // Create texture (bilinear sampling is required) SDL_Texture* sdl_texture = SDL_CreateTexture(bd->Renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, tex->Width, tex->Height); IM_ASSERT(sdl_texture != nullptr && "Backend failed to create texture!"); SDL_UpdateTexture(sdl_texture, nullptr, tex->GetPixels(), tex->GetPitch()); @@ -283,8 +328,9 @@ void ImGui_ImplSDLRenderer3_UpdateTexture(ImTextureData* tex) } else if (tex->Status == ImTextureStatus_WantDestroy) { - if (SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID) - SDL_DestroyTexture(sdl_texture); + if (tex->TexID != ImTextureID_Invalid) + if (SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID) + SDL_DestroyTexture(sdl_texture); // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) tex->SetTexID(ImTextureID_Invalid); @@ -307,6 +353,47 @@ void ImGui_ImplSDLRenderer3_DestroyDeviceObjects() } } +bool ImGui_ImplSDLRenderer3_Init(SDL_Renderer* renderer) +{ + ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); + IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); + IM_ASSERT(renderer != nullptr && "SDL_Renderer not initialized!"); + + // Setup backend capabilities flags + ImGui_ImplSDLRenderer3_Data* bd = IM_NEW(ImGui_ImplSDLRenderer3_Data)(); + io.BackendRendererUserData = (void*)bd; + io.BackendRendererName = "imgui_impl_sdlrenderer3"; + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. + + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.DrawCallback_ResetRenderState = ImGui_ImplSDLRenderer3_DrawCallback_ResetRenderState; + platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplSDLRenderer3_DrawCallback_SetSamplerLinear; + platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplSDLRenderer3_DrawCallback_SetSamplerNearest; + platform_io.DrawCallback_SetSamplerFromTex = ImGui_ImplSDLRenderer3_DrawCallback_SetSamplerFromTex; + + bd->Renderer = renderer; + + return true; +} + +void ImGui_ImplSDLRenderer3_Shutdown() +{ + ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData(); + IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + + ImGui_ImplSDLRenderer3_DestroyDeviceObjects(); + + io.BackendRendererName = nullptr; + io.BackendRendererUserData = nullptr; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); + platform_io.ClearRendererHandlers(); + IM_DELETE(bd); +} + //----------------------------------------------------------------------------- #if defined(__clang__) diff --git a/backends/imgui_impl_sdlrenderer3.h b/backends/imgui_impl_sdlrenderer3.h index f5f53b9d2..ee701be38 100644 --- a/backends/imgui_impl_sdlrenderer3.h +++ b/backends/imgui_impl_sdlrenderer3.h @@ -13,9 +13,9 @@ // [X] Renderer: User texture binding. Use 'SDL_Texture*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). -// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplSDLRenderer3_GetRenderState(). -// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // Learn about Dear ImGui: // - FAQ https://dearimgui.com/faq @@ -27,7 +27,8 @@ #include "imgui.h" // IMGUI_IMPL_API #ifndef IMGUI_DISABLE -struct SDL_Renderer; +// For SDL_ScaleMode which cannot be forward declared. +#include // Follow "Getting Started" link and check examples/ folder to learn about using backends! IMGUI_IMPL_API bool ImGui_ImplSDLRenderer3_Init(SDL_Renderer* renderer); @@ -48,6 +49,8 @@ IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_UpdateTexture(ImTextureData* tex) struct ImGui_ImplSDLRenderer3_RenderState { SDL_Renderer* Renderer; + SDL_ScaleMode CurrentScaleMode; // Current scale mode during render. Set to SDL_SCALEMODE_INVALID to use "SetSamplerFromTex" mode. }; +IMGUI_IMPL_API ImGui_ImplSDLRenderer3_RenderState* ImGui_ImplSDLRenderer3_GetRenderState(); #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index c3a304989..80b9d58ab 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -2,10 +2,10 @@ // This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) // Implemented features: -// [!] Renderer: User texture binding. Use 'VkDescriptorSet' as texture identifier. Call ImGui_ImplVulkan_AddTexture() to register one. Read the FAQ about ImTextureID/ImTextureRef + https://github.com/ocornut/imgui/pull/914 for discussions. +// [!] Renderer: User texture binding. Use a VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE 'VkDescriptorSet' as texture identifier. Call ImGui_ImplVulkan_AddTexture() to register one. Read the FAQ about ImTextureID/ImTextureRef + https://github.com/ocornut/imgui/pull/914 for discussions. // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). -// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplVulkan_GetRenderState(). // The aim of imgui_impl_vulkan.h/.cpp is to be usable in your engine without any modification. // IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ @@ -27,6 +27,19 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) +// 2026-09-03: Added for support for multiple Vulkan contexts with custom loaders. (#6616) +// 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) +// 2026-04-22: *BREAKING CHANGE* redesigned to use separate ImageView + Sampler instead of Combined Image Sampler. This change allows us to facilitate changing samplers, in line with other backends. +// - When registering custom textures: changed ImGui_ImplVulkan_AddTexture() signature to remove Sampler. +// - Before: ImGui_ImplVulkan_AddTexture(VkSampler, VkImageView, VkImageLayout) +// - After: ImGui_ImplVulkan_AddTexture(VkImageView, VkImageLayout) +// - Kept inline redirection function that ignores the sampler. +// - When creating your own descriptor pool (instead of letting backend creates its own): +// - Before: need at least IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE descriptors of type VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER. +// - After: need at least IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE descriptors of type VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE. +// + IMGUI_IMPL_VULKAN_MINIMUM_SAMPLER_POOL_SIZE descriptors of type VK_DESCRIPTOR_TYPE_SAMPLER. +// 2026-03-11: Vulkan: Added ImGui_ImplVulkan_PipelineInfo::ExtraDynamicStates[] to allow specifying extra dynamic states to add when creating the VkPipeline. (#9211) // 2025-09-26: [Helpers] *BREAKING CHANGE*: Vulkan: Helper ImGui_ImplVulkanH_DestroyWindow() does not call vkDestroySurfaceKHR(): as surface is created by caller of ImGui_ImplVulkanH_CreateOrResizeWindow(), it is more consistent that we don't destroy it. (#9163) // 2026-01-05: [Helpers] *BREAKING CHANGE*: Vulkan: Helper for creating render pass uses ImGui_ImplVulkanH_Window::AttachmentDesc to create render pass. Removed ClearEnabled. (#9152) // 2025-11-24: [Helpers] Vulkan: Helper for creating a swap-chain (used by examples and multi-viewports) selects VkSwapchainCreateInfoKHR's compositeAlpha based on cap.supportedCompositeAlpha. (#8784) @@ -219,10 +232,28 @@ static bool g_FunctionsLoaded = true; IMGUI_VULKAN_FUNC_MAP_MACRO(vkUpdateDescriptorSets) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkWaitForFences) +struct ImGui_ImplVulkan_Functions +{ // Define function pointers -#define IMGUI_VULKAN_FUNC_DEF(func) static PFN_##func func; +#define IMGUI_VULKAN_FUNC_DEF(func) PFN_##func pfn_##func; IMGUI_VULKAN_FUNC_MAP(IMGUI_VULKAN_FUNC_DEF) #undef IMGUI_VULKAN_FUNC_DEF +}; + +// Globally available function pointers used for initialization of the backend data +static ImGui_ImplVulkan_Functions ImGuiImplVulkanFuncs; + +// Multi Vulkan contexts support: wrap each Vulkan function pointer to properly dispatch it using the current backend data +static ImGui_ImplVulkan_Functions& ImGui_ImplVulkan_GetFunctions(); +#define IMGUI_VULKAN_FUNC_WRAPPER(func) \ + template \ + auto func(Args&&... args) -> decltype(ImGui_ImplVulkan_GetFunctions().pfn_##func(args...)) \ + { \ + return ImGui_ImplVulkan_GetFunctions().pfn_##func(args...); \ + } +IMGUI_VULKAN_FUNC_MAP(IMGUI_VULKAN_FUNC_WRAPPER) +#undef IMGUI_VULKAN_FUNC_WRAPPER + #endif // IMGUI_IMPL_VULKAN_USE_LOADER #ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING @@ -265,10 +296,12 @@ struct ImGui_ImplVulkan_Texture struct ImGui_ImplVulkan_Data { ImGui_ImplVulkan_InitInfo VulkanInitInfo; + ImGui_ImplVulkan_RenderState* RenderState; // == ImGui::GetPlatformIO().Renderer_RenderState during rendering. VkDeviceSize BufferMemoryAlignment; VkDeviceSize NonCoherentAtomSize; VkPipelineCreateFlags PipelineCreateFlags; - VkDescriptorSetLayout DescriptorSetLayout; + VkDescriptorSetLayout DescriptorSetLayoutTexture; + VkDescriptorSetLayout DescriptorSetLayoutSampler; VkPipelineLayout PipelineLayout; VkPipeline Pipeline; // pipeline for main render pass (created by app) VkShaderModule ShaderModuleVert; @@ -277,13 +310,21 @@ struct ImGui_ImplVulkan_Data ImVector PipelineRenderingCreateInfoColorAttachmentFormats; // Deep copy of format array // Texture management - VkSampler TexSamplerLinear; + VkSampler SamplerLinear; + VkSampler SamplerNearest; + VkDescriptorSet SamplerLinearDS; + VkDescriptorSet SamplerNearestDS; VkCommandPool TexCommandPool; VkCommandBuffer TexCommandBuffer; // Render buffers for main window ImGui_ImplVulkan_WindowRenderBuffers MainWindowRenderBuffers; + // Vulkan function pointers loaded for this context +#ifdef IMGUI_IMPL_VULKAN_USE_LOADER + ImGui_ImplVulkan_Functions Functions; +#endif + ImGui_ImplVulkan_Data() { memset((void*)this, 0, sizeof(*this)); @@ -317,7 +358,7 @@ void main() */ static uint32_t __glsl_shader_vert_spv[] = { - 0x07230203,0x00010000,0x00080001,0x0000002e,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x07230203,0x00010000,0x0008000b,0x0000002e,0x00000000,0x00020011,0x00000001,0x0006000b, 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, 0x000a000f,0x00000000,0x00000004,0x6e69616d,0x00000000,0x0000000b,0x0000000f,0x00000015, 0x0000001b,0x0000001c,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, @@ -330,10 +371,10 @@ static uint32_t __glsl_shader_vert_spv[] = 0x00050006,0x0000001e,0x00000000,0x61635375,0x0000656c,0x00060006,0x0000001e,0x00000001, 0x61725475,0x616c736e,0x00006574,0x00030005,0x00000020,0x00006370,0x00040047,0x0000000b, 0x0000001e,0x00000000,0x00040047,0x0000000f,0x0000001e,0x00000002,0x00040047,0x00000015, - 0x0000001e,0x00000001,0x00050048,0x00000019,0x00000000,0x0000000b,0x00000000,0x00030047, - 0x00000019,0x00000002,0x00040047,0x0000001c,0x0000001e,0x00000000,0x00050048,0x0000001e, - 0x00000000,0x00000023,0x00000000,0x00050048,0x0000001e,0x00000001,0x00000023,0x00000008, - 0x00030047,0x0000001e,0x00000002,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002, + 0x0000001e,0x00000001,0x00030047,0x00000019,0x00000002,0x00050048,0x00000019,0x00000000, + 0x0000000b,0x00000000,0x00040047,0x0000001c,0x0000001e,0x00000000,0x00030047,0x0000001e, + 0x00000002,0x00050048,0x0000001e,0x00000000,0x00000023,0x00000000,0x00050048,0x0000001e, + 0x00000001,0x00000023,0x00000008,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002, 0x00030016,0x00000006,0x00000020,0x00040017,0x00000007,0x00000006,0x00000004,0x00040017, 0x00000008,0x00000006,0x00000002,0x0004001e,0x00000009,0x00000007,0x00000008,0x00040020, 0x0000000a,0x00000003,0x00000009,0x0004003b,0x0000000a,0x0000000b,0x00000003,0x00040015, @@ -365,39 +406,44 @@ static uint32_t __glsl_shader_vert_spv[] = /* #version 450 core layout(location = 0) out vec4 fColor; -layout(set=0, binding=0) uniform sampler2D sTexture; +layout(set=0, binding=0) uniform texture2D _Texture; +layout(set=1, binding=0) uniform sampler _Sampler; layout(location = 0) in struct { vec4 Color; vec2 UV; } In; void main() { - fColor = In.Color * texture(sTexture, In.UV.st); + fColor = In.Color * texture(sampler2D(_Texture, _Sampler), In.UV.st); } */ static uint32_t __glsl_shader_frag_spv[] = { - 0x07230203,0x00010000,0x00080001,0x0000001e,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x07230203,0x00010000,0x0008000b,0x00000023,0x00000000,0x00020011,0x00000001,0x0006000b, 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, 0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x00000009,0x0000000d,0x00030010, 0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, 0x00000000,0x00040005,0x00000009,0x6c6f4366,0x0000726f,0x00030005,0x0000000b,0x00000000, 0x00050006,0x0000000b,0x00000000,0x6f6c6f43,0x00000072,0x00040006,0x0000000b,0x00000001, - 0x00005655,0x00030005,0x0000000d,0x00006e49,0x00050005,0x00000016,0x78655473,0x65727574, - 0x00000000,0x00040047,0x00000009,0x0000001e,0x00000000,0x00040047,0x0000000d,0x0000001e, - 0x00000000,0x00040047,0x00000016,0x00000022,0x00000000,0x00040047,0x00000016,0x00000021, - 0x00000000,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002,0x00030016,0x00000006, - 0x00000020,0x00040017,0x00000007,0x00000006,0x00000004,0x00040020,0x00000008,0x00000003, - 0x00000007,0x0004003b,0x00000008,0x00000009,0x00000003,0x00040017,0x0000000a,0x00000006, - 0x00000002,0x0004001e,0x0000000b,0x00000007,0x0000000a,0x00040020,0x0000000c,0x00000001, - 0x0000000b,0x0004003b,0x0000000c,0x0000000d,0x00000001,0x00040015,0x0000000e,0x00000020, - 0x00000001,0x0004002b,0x0000000e,0x0000000f,0x00000000,0x00040020,0x00000010,0x00000001, - 0x00000007,0x00090019,0x00000013,0x00000006,0x00000001,0x00000000,0x00000000,0x00000000, - 0x00000001,0x00000000,0x0003001b,0x00000014,0x00000013,0x00040020,0x00000015,0x00000000, - 0x00000014,0x0004003b,0x00000015,0x00000016,0x00000000,0x0004002b,0x0000000e,0x00000018, - 0x00000001,0x00040020,0x00000019,0x00000001,0x0000000a,0x00050036,0x00000002,0x00000004, - 0x00000000,0x00000003,0x000200f8,0x00000005,0x00050041,0x00000010,0x00000011,0x0000000d, - 0x0000000f,0x0004003d,0x00000007,0x00000012,0x00000011,0x0004003d,0x00000014,0x00000017, - 0x00000016,0x00050041,0x00000019,0x0000001a,0x0000000d,0x00000018,0x0004003d,0x0000000a, - 0x0000001b,0x0000001a,0x00050057,0x00000007,0x0000001c,0x00000017,0x0000001b,0x00050085, - 0x00000007,0x0000001d,0x00000012,0x0000001c,0x0003003e,0x00000009,0x0000001d,0x000100fd, + 0x00005655,0x00030005,0x0000000d,0x00006e49,0x00050005,0x00000015,0x7865545f,0x65727574, + 0x00000000,0x00050005,0x00000019,0x6d61535f,0x72656c70,0x00000000,0x00040047,0x00000009, + 0x0000001e,0x00000000,0x00040047,0x0000000d,0x0000001e,0x00000000,0x00040047,0x00000015, + 0x00000021,0x00000000,0x00040047,0x00000015,0x00000022,0x00000000,0x00040047,0x00000019, + 0x00000021,0x00000000,0x00040047,0x00000019,0x00000022,0x00000001,0x00020013,0x00000002, + 0x00030021,0x00000003,0x00000002,0x00030016,0x00000006,0x00000020,0x00040017,0x00000007, + 0x00000006,0x00000004,0x00040020,0x00000008,0x00000003,0x00000007,0x0004003b,0x00000008, + 0x00000009,0x00000003,0x00040017,0x0000000a,0x00000006,0x00000002,0x0004001e,0x0000000b, + 0x00000007,0x0000000a,0x00040020,0x0000000c,0x00000001,0x0000000b,0x0004003b,0x0000000c, + 0x0000000d,0x00000001,0x00040015,0x0000000e,0x00000020,0x00000001,0x0004002b,0x0000000e, + 0x0000000f,0x00000000,0x00040020,0x00000010,0x00000001,0x00000007,0x00090019,0x00000013, + 0x00000006,0x00000001,0x00000000,0x00000000,0x00000000,0x00000001,0x00000000,0x00040020, + 0x00000014,0x00000000,0x00000013,0x0004003b,0x00000014,0x00000015,0x00000000,0x0002001a, + 0x00000017,0x00040020,0x00000018,0x00000000,0x00000017,0x0004003b,0x00000018,0x00000019, + 0x00000000,0x0003001b,0x0000001b,0x00000013,0x0004002b,0x0000000e,0x0000001d,0x00000001, + 0x00040020,0x0000001e,0x00000001,0x0000000a,0x00050036,0x00000002,0x00000004,0x00000000, + 0x00000003,0x000200f8,0x00000005,0x00050041,0x00000010,0x00000011,0x0000000d,0x0000000f, + 0x0004003d,0x00000007,0x00000012,0x00000011,0x0004003d,0x00000013,0x00000016,0x00000015, + 0x0004003d,0x00000017,0x0000001a,0x00000019,0x00050056,0x0000001b,0x0000001c,0x00000016, + 0x0000001a,0x00050041,0x0000001e,0x0000001f,0x0000000d,0x0000001d,0x0004003d,0x0000000a, + 0x00000020,0x0000001f,0x00050057,0x00000007,0x00000021,0x0000001c,0x00000020,0x00050085, + 0x00000007,0x00000022,0x00000012,0x00000021,0x0003003e,0x00000009,0x00000022,0x000100fd, 0x00010038 }; @@ -413,6 +459,21 @@ static ImGui_ImplVulkan_Data* ImGui_ImplVulkan_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplVulkan_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +ImGui_ImplVulkan_RenderState* ImGui_ImplVulkan_GetRenderState() +{ + return (ImGui_ImplVulkan_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; +} + +#ifdef IMGUI_IMPL_VULKAN_USE_LOADER +// Get the Vulkan function pointers used by the current backend. +// If no backend is available, return the currently loaded global function pointers (ImGuiImplVulkanFuncs). +static ImGui_ImplVulkan_Functions& ImGui_ImplVulkan_GetFunctions() +{ + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + return bd ? bd->Functions : ImGuiImplVulkanFuncs; +} +#endif + static uint32_t ImGui_ImplVulkan_MemoryType(VkMemoryPropertyFlags properties, uint32_t type_bits) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); @@ -480,9 +541,7 @@ static void ImGui_ImplVulkan_SetupRenderState(ImDrawData* draw_data, VkPipeline ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); // Bind pipeline: - { - vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - } + vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); // Bind Vertex And Index Buffer: if (draw_data->TotalVtxCount > 0) @@ -494,37 +553,48 @@ static void ImGui_ImplVulkan_SetupRenderState(ImDrawData* draw_data, VkPipeline } // Setup viewport: - { - VkViewport viewport; - viewport.x = 0; - viewport.y = 0; - viewport.width = (float)fb_width; - viewport.height = (float)fb_height; - viewport.minDepth = 0.0f; - viewport.maxDepth = 1.0f; - vkCmdSetViewport(command_buffer, 0, 1, &viewport); - } + VkViewport viewport; + viewport.x = 0; + viewport.y = 0; + viewport.width = (float)fb_width; + viewport.height = (float)fb_height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(command_buffer, 0, 1, &viewport); // Setup scale and translation: // Our visible imgui space lies from draw_data->DisplayPps (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. - { - float scale[2]; - scale[0] = 2.0f / draw_data->DisplaySize.x; - scale[1] = 2.0f / draw_data->DisplaySize.y; - float translate[2]; - translate[0] = -1.0f - draw_data->DisplayPos.x * scale[0]; - translate[1] = -1.0f - draw_data->DisplayPos.y * scale[1]; - vkCmdPushConstants(command_buffer, bd->PipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, sizeof(float) * 0, sizeof(float) * 2, scale); - vkCmdPushConstants(command_buffer, bd->PipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, sizeof(float) * 2, sizeof(float) * 2, translate); - } + float constants[4]; + constants[0] = 2.0f / draw_data->DisplaySize.x; // Scale + constants[1] = 2.0f / draw_data->DisplaySize.y; + constants[2] = -1.0f - draw_data->DisplayPos.x * constants[0]; // Translate + constants[3] = -1.0f - draw_data->DisplayPos.y * constants[1]; + vkCmdPushConstants(command_buffer, bd->PipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, (uint32_t)sizeof(float) * 4, constants); + + // Setup sampler + vkCmdBindDescriptorSets(bd->RenderState->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 1, 1, &bd->SamplerLinearDS, 0, nullptr); } +// Draw callbacks +static void ImGui_ImplVulkan_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. +static void ImGui_ImplVulkan_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); vkCmdBindDescriptorSets(bd->RenderState->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->RenderState->PipelineLayout, 1, 1, &bd->SamplerLinearDS, 0, nullptr); } +static void ImGui_ImplVulkan_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); vkCmdBindDescriptorSets(bd->RenderState->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->RenderState->PipelineLayout, 1, 1, &bd->SamplerNearestDS, 0, nullptr); } + +// If you want to use your own sampler, you can create your own callback, access ImGui_ImplVulkan_RenderState for command-buffer and pipeline layout, e.g. +/* +void ImGui_ImplVulkan_DrawCallback_SetSamplerCustom(const ImDrawList*, const ImDrawCmd* cmd) +{ + ImGui_ImplVulkan_RenderState* render_state = (ImGui_ImplVulkan_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; + vkCmdBindDescriptorSets(render_state->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, render_state->PipelineLayout, 1, 1, (VkDescriptorSet*)cmd->UserCallbackData, 0, nullptr); +} +*/ + // Render function void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer, VkPipeline pipeline) { // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); if (fb_width <= 0 || fb_height <= 0) return; @@ -590,16 +660,16 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm vkUnmapMemory(v->Device, rb->IndexBufferMemory); } - // Setup desired Vulkan state - ImGui_ImplVulkan_SetupRenderState(draw_data, pipeline, command_buffer, rb, fb_width, fb_height); - // Setup render state structure (for callbacks and custom texture bindings) ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); ImGui_ImplVulkan_RenderState render_state; render_state.CommandBuffer = command_buffer; render_state.Pipeline = pipeline; render_state.PipelineLayout = bd->PipelineLayout; - platform_io.Renderer_RenderState = &render_state; + platform_io.Renderer_RenderState = bd->RenderState = &render_state; + + // Setup desired Vulkan state + ImGui_ImplVulkan_SetupRenderState(draw_data, pipeline, command_buffer, rb, fb_width, fb_height); // Will project scissor/clipping rectangles into framebuffer space ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports @@ -607,7 +677,7 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm // Render command lists // (Because we merged all buffers into a single one, we maintain our own offset into them) - VkDescriptorSet last_desc_set = VK_NULL_HANDLE; + VkDescriptorSet last_image_view = VK_NULL_HANDLE; int global_vtx_offset = 0; int global_idx_offset = 0; for (const ImDrawList* draw_list : draw_data->CmdLists) @@ -618,12 +688,13 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm if (pcmd->UserCallback != nullptr) { // User callback, registered via ImDrawList::AddCallback() - // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) - if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + if (pcmd->UserCallback == ImGui_ImplVulkan_DrawCallback_ResetRenderState) + { ImGui_ImplVulkan_SetupRenderState(draw_data, pipeline, command_buffer, rb, fb_width, fb_height); + last_image_view = VK_NULL_HANDLE; + } else pcmd->UserCallback(draw_list, pcmd); - last_desc_set = VK_NULL_HANDLE; } else { @@ -634,8 +705,8 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm // Clamp to viewport as vkCmdSetScissor() won't accept values that are off bounds if (clip_min.x < 0.0f) { clip_min.x = 0.0f; } if (clip_min.y < 0.0f) { clip_min.y = 0.0f; } - if (clip_max.x > fb_width) { clip_max.x = (float)fb_width; } - if (clip_max.y > fb_height) { clip_max.y = (float)fb_height; } + if (clip_max.x > (float)fb_width) { clip_max.x = (float)fb_width; } + if (clip_max.y > (float)fb_height) { clip_max.y = (float)fb_height; } if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) continue; @@ -647,11 +718,11 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm scissor.extent.height = (uint32_t)(clip_max.y - clip_min.y); vkCmdSetScissor(command_buffer, 0, 1, &scissor); - // Bind DescriptorSet with font or user texture - VkDescriptorSet desc_set = (VkDescriptorSet)pcmd->GetTexID(); - if (desc_set != last_desc_set) - vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 0, 1, &desc_set, 0, nullptr); - last_desc_set = desc_set; + // Bind DescriptorSets for image view (font or user texture) and samplers + VkDescriptorSet image_view = (VkDescriptorSet)pcmd->GetTexID(); + if (image_view != last_image_view) + vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 0, 1, &image_view, 0, nullptr); + last_image_view = image_view; // Draw vkCmdDrawIndexed(command_buffer, pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset, 0); @@ -660,7 +731,7 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm global_idx_offset += draw_list->IdxBuffer.Size; global_vtx_offset += draw_list->VtxBuffer.Size; } - platform_io.Renderer_RenderState = nullptr; + platform_io.Renderer_RenderState = bd->RenderState = nullptr; // Note: at this point both vkCmdSetViewport() and vkCmdSetScissor() have been called. // Our last values will leak into user/application rendering IF: @@ -754,7 +825,7 @@ void ImGui_ImplVulkan_UpdateTexture(ImTextureData* tex) } // Create the Descriptor Set - backend_tex->DescriptorSet = ImGui_ImplVulkan_AddTexture(bd->TexSamplerLinear, backend_tex->ImageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + backend_tex->DescriptorSet = ImGui_ImplVulkan_AddTexture(backend_tex->ImageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); // Store identifiers tex->SetTexID((ImTextureID)backend_tex->DescriptorSet); @@ -1009,11 +1080,13 @@ static VkPipeline ImGui_ImplVulkan_CreatePipeline(VkDevice device, const VkAlloc blend_info.attachmentCount = 1; blend_info.pAttachments = color_attachment; - VkDynamicState dynamic_states[2] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + ImVector dynamic_states = info->ExtraDynamicStates; + dynamic_states.push_back(VK_DYNAMIC_STATE_VIEWPORT); + dynamic_states.push_back(VK_DYNAMIC_STATE_SCISSOR); VkPipelineDynamicStateCreateInfo dynamic_state = {}; dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; - dynamic_state.dynamicStateCount = (uint32_t)IM_COUNTOF(dynamic_states); - dynamic_state.pDynamicStates = dynamic_states; + dynamic_state.dynamicStateCount = dynamic_states.Size; + dynamic_state.pDynamicStates = dynamic_states.Data; VkGraphicsPipelineCreateInfo create_info = {}; create_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; @@ -1047,15 +1120,82 @@ static VkPipeline ImGui_ImplVulkan_CreatePipeline(VkDevice device, const VkAlloc return pipeline; } +static void ImGui_ImplVulkan_CreateDescriptorSetLayout(VkDescriptorSetLayout* p_layout, VkDescriptorType descriptor_type) +{ + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; + + VkDescriptorSetLayoutBinding binding[1] = {}; + binding[0].descriptorType = descriptor_type; + binding[0].descriptorCount = 1; + binding[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + VkDescriptorSetLayoutCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + info.bindingCount = 1; + info.pBindings = binding; + VkResult err = vkCreateDescriptorSetLayout(v->Device, &info, v->Allocator, p_layout); + check_vk_result(err); +} + +static VkDescriptorSet ImGui_ImplVulkan_CreateSamplerDS(VkSampler sampler) +{ + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; + + VkDescriptorSet descriptor_set; + VkDescriptorSetAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + alloc_info.descriptorPool = bd->DescriptorPool ? bd->DescriptorPool : v->DescriptorPool; + alloc_info.descriptorSetCount = 1; + alloc_info.pSetLayouts = &bd->DescriptorSetLayoutSampler; + VkResult err = vkAllocateDescriptorSets(v->Device, &alloc_info, &descriptor_set); + check_vk_result(err); + + VkDescriptorImageInfo desc_image = {}; + desc_image.sampler = sampler; + VkWriteDescriptorSet sampler_write_desc = {}; + sampler_write_desc.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + sampler_write_desc.dstSet = descriptor_set; + sampler_write_desc.descriptorCount = 1; + sampler_write_desc.dstBinding = 0; + sampler_write_desc.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER; + sampler_write_desc.pImageInfo = &desc_image; + vkUpdateDescriptorSets(v->Device, 1, &sampler_write_desc, 0, nullptr); + return descriptor_set; +} + bool ImGui_ImplVulkan_CreateDeviceObjects() { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; VkResult err; - if (!bd->TexSamplerLinear) + if (!bd->DescriptorSetLayoutTexture) + ImGui_ImplVulkan_CreateDescriptorSetLayout(&bd->DescriptorSetLayoutTexture, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE); + if (!bd->DescriptorSetLayoutSampler) + ImGui_ImplVulkan_CreateDescriptorSetLayout(&bd->DescriptorSetLayoutSampler, VK_DESCRIPTOR_TYPE_SAMPLER); + + if (v->DescriptorPoolSize != 0) + { + IM_ASSERT(v->DescriptorPoolSize >= IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE); + VkDescriptorPoolSize pool_sizes[] = + { + { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, v->DescriptorPoolSize }, + { VK_DESCRIPTOR_TYPE_SAMPLER, IMGUI_IMPL_VULKAN_MINIMUM_SAMPLER_POOL_SIZE }, + }; + VkDescriptorPoolCreateInfo pool_info = {}; + pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; + pool_info.maxSets = v->DescriptorPoolSize + IMGUI_IMPL_VULKAN_MINIMUM_SAMPLER_POOL_SIZE; + pool_info.poolSizeCount = (uint32_t)IM_COUNTOF(pool_sizes); + pool_info.pPoolSizes = pool_sizes; + err = vkCreateDescriptorPool(v->Device, &pool_info, v->Allocator, &bd->DescriptorPool); + check_vk_result(err); + } + + // Create samplers (bilinear sampling is required) + if (!bd->SamplerLinear || !bd->SamplerNearest) { - // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling. VkSamplerCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; info.magFilter = VK_FILTER_LINEAR; @@ -1067,37 +1207,16 @@ bool ImGui_ImplVulkan_CreateDeviceObjects() info.minLod = -1000; info.maxLod = 1000; info.maxAnisotropy = 1.0f; - err = vkCreateSampler(v->Device, &info, v->Allocator, &bd->TexSamplerLinear); + err = vkCreateSampler(v->Device, &info, v->Allocator, &bd->SamplerLinear); check_vk_result(err); - } + bd->SamplerLinearDS = ImGui_ImplVulkan_CreateSamplerDS(bd->SamplerLinear); - if (!bd->DescriptorSetLayout) - { - VkDescriptorSetLayoutBinding binding[1] = {}; - binding[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - binding[0].descriptorCount = 1; - binding[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; - VkDescriptorSetLayoutCreateInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - info.bindingCount = 1; - info.pBindings = binding; - err = vkCreateDescriptorSetLayout(v->Device, &info, v->Allocator, &bd->DescriptorSetLayout); - check_vk_result(err); - } - - if (v->DescriptorPoolSize != 0) - { - IM_ASSERT(v->DescriptorPoolSize >= IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE); - VkDescriptorPoolSize pool_size = { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, v->DescriptorPoolSize }; - VkDescriptorPoolCreateInfo pool_info = {}; - pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; - pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; - pool_info.maxSets = v->DescriptorPoolSize; - pool_info.poolSizeCount = 1; - pool_info.pPoolSizes = &pool_size; - - err = vkCreateDescriptorPool(v->Device, &pool_info, v->Allocator, &bd->DescriptorPool); + info.magFilter = VK_FILTER_NEAREST; + info.minFilter = VK_FILTER_NEAREST; + info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + err = vkCreateSampler(v->Device, &info, v->Allocator, &bd->SamplerNearest); check_vk_result(err); + bd->SamplerNearestDS = ImGui_ImplVulkan_CreateSamplerDS(bd->SamplerNearest); } if (!bd->PipelineLayout) @@ -1107,10 +1226,10 @@ bool ImGui_ImplVulkan_CreateDeviceObjects() push_constants[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; push_constants[0].offset = sizeof(float) * 0; push_constants[0].size = sizeof(float) * 4; - VkDescriptorSetLayout set_layout[1] = { bd->DescriptorSetLayout }; + VkDescriptorSetLayout set_layout[2] = { bd->DescriptorSetLayoutTexture, bd->DescriptorSetLayoutSampler }; VkPipelineLayoutCreateInfo layout_info = {}; layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - layout_info.setLayoutCount = 1; + layout_info.setLayoutCount = 2; layout_info.pSetLayouts = set_layout; layout_info.pushConstantRangeCount = 1; layout_info.pPushConstantRanges = push_constants; @@ -1190,10 +1309,12 @@ void ImGui_ImplVulkan_DestroyDeviceObjects() if (bd->TexCommandBuffer) { vkFreeCommandBuffers(v->Device, bd->TexCommandPool, 1, &bd->TexCommandBuffer); bd->TexCommandBuffer = VK_NULL_HANDLE; } if (bd->TexCommandPool) { vkDestroyCommandPool(v->Device, bd->TexCommandPool, v->Allocator); bd->TexCommandPool = VK_NULL_HANDLE; } - if (bd->TexSamplerLinear) { vkDestroySampler(v->Device, bd->TexSamplerLinear, v->Allocator); bd->TexSamplerLinear = VK_NULL_HANDLE; } + if (bd->SamplerLinear) { vkDestroySampler(v->Device, bd->SamplerLinear, v->Allocator); bd->SamplerLinear = VK_NULL_HANDLE; } + if (bd->SamplerNearest) { vkDestroySampler(v->Device, bd->SamplerNearest, v->Allocator); bd->SamplerNearest = VK_NULL_HANDLE; } if (bd->ShaderModuleVert) { vkDestroyShaderModule(v->Device, bd->ShaderModuleVert, v->Allocator); bd->ShaderModuleVert = VK_NULL_HANDLE; } if (bd->ShaderModuleFrag) { vkDestroyShaderModule(v->Device, bd->ShaderModuleFrag, v->Allocator); bd->ShaderModuleFrag = VK_NULL_HANDLE; } - if (bd->DescriptorSetLayout) { vkDestroyDescriptorSetLayout(v->Device, bd->DescriptorSetLayout, v->Allocator); bd->DescriptorSetLayout = VK_NULL_HANDLE; } + if (bd->DescriptorSetLayoutTexture) { vkDestroyDescriptorSetLayout(v->Device, bd->DescriptorSetLayoutTexture, v->Allocator); bd->DescriptorSetLayoutTexture = VK_NULL_HANDLE; } + if (bd->DescriptorSetLayoutSampler) { vkDestroyDescriptorSetLayout(v->Device, bd->DescriptorSetLayoutSampler, v->Allocator); bd->DescriptorSetLayoutSampler = VK_NULL_HANDLE; } if (bd->PipelineLayout) { vkDestroyPipelineLayout(v->Device, bd->PipelineLayout, v->Allocator); bd->PipelineLayout = VK_NULL_HANDLE; } if (bd->Pipeline) { vkDestroyPipeline(v->Device, bd->Pipeline, v->Allocator); bd->Pipeline = VK_NULL_HANDLE; } if (bd->DescriptorPool) { vkDestroyDescriptorPool(v->Device, bd->DescriptorPool, v->Allocator); bd->DescriptorPool = VK_NULL_HANDLE; } @@ -1240,8 +1361,8 @@ bool ImGui_ImplVulkan_LoadFunctions(uint32_t api_version, PFN_vkVoidFunction( #ifdef IMGUI_IMPL_VULKAN_USE_LOADER #define IMGUI_VULKAN_FUNC_LOAD(func) \ - func = reinterpret_cast(loader_func(#func, user_data)); \ - if (func == nullptr) \ + ImGuiImplVulkanFuncs.pfn_##func = reinterpret_cast(loader_func(#func, user_data)); \ + if (ImGuiImplVulkanFuncs.pfn_##func == nullptr) \ return false; IMGUI_VULKAN_FUNC_MAP(IMGUI_VULKAN_FUNC_LOAD) #undef IMGUI_VULKAN_FUNC_LOAD @@ -1289,6 +1410,11 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info) io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.DrawCallback_ResetRenderState = ImGui_ImplVulkan_DrawCallback_ResetRenderState; + platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplVulkan_DrawCallback_SetSamplerLinear; + platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplVulkan_DrawCallback_SetSamplerNearest; + // Sanity checks IM_ASSERT(info->Instance != VK_NULL_HANDLE); IM_ASSERT(info->PhysicalDevice != VK_NULL_HANDLE); @@ -1304,6 +1430,9 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info) IM_ASSERT(info->PipelineInfoMain.RenderPass == VK_NULL_HANDLE); bd->VulkanInitInfo = *info; +#ifdef IMGUI_IMPL_VULKAN_USE_LOADER + bd->Functions = ImGuiImplVulkanFuncs; +#endif VkPhysicalDeviceProperties properties; vkGetPhysicalDeviceProperties(info->PhysicalDevice, &properties); @@ -1354,7 +1483,7 @@ void ImGui_ImplVulkan_SetMinImageCount(uint32_t min_image_count) // Register a texture by creating a descriptor // FIXME: This is experimental in the sense that we are unsure how to best design/tackle this problem, please post to https://github.com/ocornut/imgui/pull/914 if you have suggestions. -VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkSampler sampler, VkImageView image_view, VkImageLayout image_layout) +VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkImageView image_view, VkImageLayout image_layout) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; @@ -1367,28 +1496,36 @@ VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkSampler sampler, VkImageView image alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; alloc_info.descriptorPool = pool; alloc_info.descriptorSetCount = 1; - alloc_info.pSetLayouts = &bd->DescriptorSetLayout; + alloc_info.pSetLayouts = &bd->DescriptorSetLayoutTexture; VkResult err = vkAllocateDescriptorSets(v->Device, &alloc_info, &descriptor_set); check_vk_result(err); } // Update the Descriptor Set: + if (descriptor_set != VK_NULL_HANDLE) { VkDescriptorImageInfo desc_image[1] = {}; - desc_image[0].sampler = sampler; desc_image[0].imageView = image_view; desc_image[0].imageLayout = image_layout; VkWriteDescriptorSet write_desc[1] = {}; write_desc[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; write_desc[0].dstSet = descriptor_set; write_desc[0].descriptorCount = 1; - write_desc[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + write_desc[0].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; write_desc[0].pImageInfo = desc_image; vkUpdateDescriptorSets(v->Device, 1, write_desc, 0, nullptr); } return descriptor_set; } +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkSampler sampler, VkImageView image_view, VkImageLayout image_layout) +{ + IM_UNUSED(sampler); + return ImGui_ImplVulkan_AddTexture(image_view, image_layout); +} +#endif + void ImGui_ImplVulkan_RemoveTexture(VkDescriptorSet descriptor_set) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); @@ -1789,7 +1926,6 @@ void ImGui_ImplVulkanH_CreateOrResizeWindow(VkInstance instance, VkPhysicalDevic VkCommandPoolCreateInfo pool_info = {}; pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; pool_info.queueFamilyIndex = queue_family; - pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; VkResult err = vkCreateCommandPool(device, &pool_info, allocator, &command_pool); check_vk_result(err); diff --git a/backends/imgui_impl_vulkan.h b/backends/imgui_impl_vulkan.h index 5b3baf00c..0f2da5e2d 100644 --- a/backends/imgui_impl_vulkan.h +++ b/backends/imgui_impl_vulkan.h @@ -2,10 +2,10 @@ // This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) // Implemented features: -// [!] Renderer: User texture binding. Use 'VkDescriptorSet' as texture identifier. Call ImGui_ImplVulkan_AddTexture() to register one. Read the FAQ about ImTextureID/ImTextureRef + https://github.com/ocornut/imgui/pull/914 for discussions. +// [!] Renderer: User texture binding. Use a VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE 'VkDescriptorSet' as texture identifier. Call ImGui_ImplVulkan_AddTexture() to register one. Read the FAQ about ImTextureID/ImTextureRef + https://github.com/ocornut/imgui/pull/914 for discussions. // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). -// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplVulkan_GetRenderState(). // The aim of imgui_impl_vulkan.h/.cpp is to be usable in your engine without any modification. // IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ @@ -78,7 +78,8 @@ #endif // Backend uses a small number of descriptors per font atlas + as many as additional calls done to ImGui_ImplVulkan_AddTexture(). -#define IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE (8) // Minimum per atlas +#define IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE (8) // Minimum per atlas +#define IMGUI_IMPL_VULKAN_MINIMUM_SAMPLER_POOL_SIZE (2) // Minimum for linear + nearest // Specify settings to create pipeline and swapchain struct ImGui_ImplVulkan_PipelineInfo @@ -86,6 +87,7 @@ struct ImGui_ImplVulkan_PipelineInfo VkRenderPass RenderPass; // Ignored if using dynamic rendering uint32_t Subpass; // VkSampleCountFlagBits MSAASamples = {}; // 0 defaults to VK_SAMPLE_COUNT_1_BIT + ImVector ExtraDynamicStates; // Optional, allows to insert more dynamic states into our VkPipeline #ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING VkPipelineRenderingCreateInfoKHR PipelineRenderingCreateInfo; // Optional, valid if .sType == VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR #endif @@ -108,7 +110,7 @@ struct ImGui_ImplVulkan_InitInfo uint32_t QueueFamily; VkQueue Queue; VkDescriptorPool DescriptorPool; // See requirements in note above; ignored if using DescriptorPoolSize > 0 - uint32_t DescriptorPoolSize; // Optional: set to create internal descriptor pool automatically instead of using DescriptorPool. + uint32_t DescriptorPoolSize; // Optional: set to create internal ImageView descriptor pool automatically instead of using DescriptorPool. uint32_t MinImageCount; // >= 2 uint32_t ImageCount; // >= MinImageCount VkPipelineCache PipelineCache; // Optional @@ -151,18 +153,21 @@ IMGUI_IMPL_API void ImGui_ImplVulkan_CreateMainPipeline(const ImGui_ // (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = nullptr to handle this manually. IMGUI_IMPL_API void ImGui_ImplVulkan_UpdateTexture(ImTextureData* tex); -// Register a texture (VkDescriptorSet == ImTextureID) -// FIXME: This is experimental in the sense that we are unsure how to best design/tackle this problem -// Please post to https://github.com/ocornut/imgui/pull/914 if you have suggestions. -IMGUI_IMPL_API VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkSampler sampler, VkImageView image_view, VkImageLayout image_layout); +// Register a texture (VkDescriptorSet for a VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE == ImTextureID) +IMGUI_IMPL_API VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkImageView image_view, VkImageLayout image_layout); IMGUI_IMPL_API void ImGui_ImplVulkan_RemoveTexture(VkDescriptorSet descriptor_set); +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +IMGUI_IMPL_API VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkSampler sampler, VkImageView image_view, VkImageLayout image_layout); // Ignore VkSampler +#endif + // Optional: load Vulkan functions with a custom function loader // This is only useful with IMGUI_IMPL_VULKAN_NO_PROTOTYPES / VK_NO_PROTOTYPES IMGUI_IMPL_API bool ImGui_ImplVulkan_LoadFunctions(uint32_t api_version, PFN_vkVoidFunction(*loader_func)(const char* function_name, void* user_data), void* user_data = nullptr); // [BETA] Selected render state data shared with callbacks. // This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplVulkan_RenderDrawData() call. +// ImGui_ImplVulkan_RenderState* render_state = (ImGui_ImplVulkan_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; // (Please open an issue if you feel you need access to more data) struct ImGui_ImplVulkan_RenderState { @@ -170,6 +175,7 @@ struct ImGui_ImplVulkan_RenderState VkPipeline Pipeline; VkPipelineLayout PipelineLayout; }; +IMGUI_IMPL_API ImGui_ImplVulkan_RenderState* ImGui_ImplVulkan_GetRenderState(); //------------------------------------------------------------------------- // Internal / Miscellaneous Vulkan Helpers @@ -253,7 +259,7 @@ struct ImGui_ImplVulkanH_Window memset((void*)this, 0, sizeof(*this)); // Parameters to create SwapChain - PresentMode = (VkPresentModeKHR)~0; // Ensure we get an error if user doesn't set this. + PresentMode = VK_PRESENT_MODE_MAX_ENUM_KHR; // Ensure we get an error if user doesn't set this. // Parameters to create RenderPass AttachmentDesc.format = VK_FORMAT_UNDEFINED; // Will automatically use wd->SurfaceFormat.format. diff --git a/backends/imgui_impl_wgpu.cpp b/backends/imgui_impl_wgpu.cpp index e98e9f3ca..002967b92 100644 --- a/backends/imgui_impl_wgpu.cpp +++ b/backends/imgui_impl_wgpu.cpp @@ -5,8 +5,8 @@ // Implemented features: // [X] Renderer: User texture binding. Use 'WGPUTextureView' as ImTextureID. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). -// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // [X] Renderer: Texture updates support for dynamic font system (ImGuiBackendFlags_RendererHasTextures). +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplWGPU_GetRenderState(). // Read imgui_impl_wgpu.h about how to use the IMGUI_IMPL_WEBGPU_BACKEND_WGPU or IMGUI_IMPL_WEBGPU_BACKEND_DAWN flags. @@ -20,6 +20,12 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) +// 2026-08-31: Update to compile with wgpu-native 29.0. (#9377, #9532, #9530) +// 2026-08-18: Fixed passing non-integer sizes to `wgpuRenderPassEncoderSetViewport() when FramebufferScale is >1.0f. (#9515, #8628) +// 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) +// 2026-03-25: Added support for WGVK native backend via IMGUI_IMPL_WEBGPU_BACKEND_WGVK define, with SPIRV shaders if WGSL is not available. (#9316, #9246, #9257) +// 2026-03-09: Removed support for Emscripten < 4.0.10. (#9281) // 2025-10-16: Update to compile with Dawn and Emscripten's 4.0.10+ '--use-port=emdawnwebgpu' ports. (#8381, #8898) // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-06-12: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. (#8465) @@ -55,20 +61,11 @@ #include // One of IMGUI_IMPL_WEBGPU_BACKEND_DAWN or IMGUI_IMPL_WEBGPU_BACKEND_WGPU must be provided. See imgui_impl_wgpu.h for more details. -#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) == defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) -#error Exactly one of IMGUI_IMPL_WEBGPU_BACKEND_DAWN or IMGUI_IMPL_WEBGPU_BACKEND_WGPU must be defined! +#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) && !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) && !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGVK) +#error Exactly one of IMGUI_IMPL_WEBGPU_BACKEND_DAWN, IMGUI_IMPL_WEBGPU_BACKEND_WGPU or IMGUI_IMPL_WEBGPU_BACKEND_WGVK must be defined! #endif - -// This condition is true when it's built with EMSCRIPTEN using -sUSE_WEBGPU=1 flag (deprecated from 4.0.10) -// This condition is false for all other 3 cases: WGPU-Native, DAWN-Native or DAWN-EMSCRIPTEN (using --use-port=emdawnwebgpu flag) #if defined(__EMSCRIPTEN__) && defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) -#define IMGUI_IMPL_WEBGPU_BACKEND_WGPU_EMSCRIPTEN -#endif - -#ifdef IMGUI_IMPL_WEBGPU_BACKEND_DAWN -// Dawn renamed WGPUProgrammableStageDescriptor to WGPUComputeState (see: https://github.com/webgpu-native/webgpu-headers/pull/413) -// Using type alias until WGPU adopts the same naming convention (#8369) -using WGPUProgrammableStageDescriptor = WGPUComputeState; +#error Emscripten <4.0.10 with '-sUSE_WEBGPU=1' is not supported anymore. #endif // Dear ImGui prototypes from imgui_internal.h @@ -84,11 +81,13 @@ struct ImGui_ImplWGPU_Texture struct RenderResources { - WGPUSampler Sampler = nullptr; // Sampler for textures - WGPUBuffer Uniforms = nullptr; // Shader uniforms - WGPUBindGroup CommonBindGroup = nullptr; // Resources bind-group to bind the common resources to pipeline - ImGuiStorage ImageBindGroups; // Resources bind-group to bind the font/image resources to pipeline (this is a key->value map) - WGPUBindGroupLayout ImageBindGroupLayout = nullptr; // Cache layout used for the image bind group. Avoids allocating unnecessary JS objects when working with WebASM + WGPUSampler SamplerLinear = nullptr; // Bilinear sampler + WGPUSampler SamplerNearest = nullptr; // Nearest/point sampler + WGPUBuffer Uniforms = nullptr; // Shader uniforms + WGPUBindGroup CommonBindGroupLinear = nullptr; // Common bind-group bound to group 0 (uniforms + SamplerLinear) + WGPUBindGroup CommonBindGroupNearest = nullptr; // Common bind-group bound to group 0 (uniforms + SamplerNearest) + ImGuiStorage ImageBindGroups; // Resources bind-group to bind the font/image resources to pipeline (this is a key->value map) + WGPUBindGroupLayout ImageBindGroupLayout = nullptr; // Cache layout used for the image bind group. Avoids allocating unnecessary JS objects when working with WebASM }; struct FrameResources @@ -115,6 +114,7 @@ struct ImGui_ImplWGPU_Data WGPUTextureFormat renderTargetFormat = WGPUTextureFormat_Undefined; WGPUTextureFormat depthStencilFormat = WGPUTextureFormat_Undefined; WGPURenderPipeline pipelineState = nullptr; + ImGui_ImplWGPU_RenderState* RenderState = nullptr; // == ImGui::GetPlatformIO().Renderer_RenderState during rendering. RenderResources renderResources; FrameResources* pFrameResources = nullptr; @@ -129,6 +129,11 @@ static ImGui_ImplWGPU_Data* ImGui_ImplWGPU_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplWGPU_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +ImGui_ImplWGPU_RenderState* ImGui_ImplWGPU_GetRenderState() +{ + return (ImGui_ImplWGPU_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; +} + //----------------------------------------------------------------------------- // SHADERS //----------------------------------------------------------------------------- @@ -187,6 +192,60 @@ fn main(in: VertexOutput) -> @location(0) vec4 { } )"; +// Same shader as __shader_vert_wgsl[] but compiled as SPIRV. +// 'wgslc -o vert.spv vert.wgsl' + 'binary_to_compressed_c -u8 -nocompress vert.spv' +static const unsigned char __shader_vert_spirv[1996] = +{ + 3,2,35,7,0,3,1,0,1,0,23,0,90,0,0,0,0,0,0,0,17,0,2,0,1,0,0,0,14,0,3,0,0,0,0,0,1,0,0,0,15,0,12,0,0,0,0,0,78,0,0,0,109,97,105,110,0,0,0,0,8,0,0,0,12,0,0,0,13,0,0,0,16,0,0,0,18,0,0,0,19, + 0,0,0,21,0,0,0,71,0,4,0,4,0,0,0,6,0,0,0,16,0,0,0,72,0,5,0,3,0,0,0,0,0,0,0,35,0,0,0,0,0,0,0,71,0,3,0,3,0,0,0,2,0,0,0,71,0,4,0,1,0,0,0,34,0,0,0,0,0,0,0,71,0,4,0,1,0,0,0,33,0,0,0,0,0, + 0,0,71,0,3,0,1,0,0,0,24,0,0,0,71,0,4,0,8,0,0,0,30,0,0,0,0,0,0,0,71,0,4,0,12,0,0,0,30,0,0,0,1,0,0,0,71,0,4,0,13,0,0,0,30,0,0,0,2,0,0,0,71,0,4,0,16,0,0,0,11,0,0,0,0,0,0,0,71,0,4,0,18, + 0,0,0,30,0,0,0,0,0,0,0,71,0,4,0,19,0,0,0,30,0,0,0,1,0,0,0,71,0,4,0,21,0,0,0,11,0,0,0,1,0,0,0,21,0,4,0,6,0,0,0,32,0,0,0,0,0,0,0,23,0,4,0,5,0,0,0,6,0,0,0,4,0,0,0,43,0,4,0,6,0,0,0,7,0, + 0,0,5,0,0,0,28,0,4,0,4,0,0,0,5,0,0,0,7,0,0,0,30,0,3,0,3,0,0,0,4,0,0,0,32,0,4,0,2,0,0,0,2,0,0,0,3,0,0,0,59,0,4,0,2,0,0,0,1,0,0,0,2,0,0,0,22,0,3,0,11,0,0,0,32,0,0,0,23,0,4,0,10,0,0,0, + 11,0,0,0,2,0,0,0,32,0,4,0,9,0,0,0,1,0,0,0,10,0,0,0,59,0,4,0,9,0,0,0,8,0,0,0,1,0,0,0,59,0,4,0,9,0,0,0,12,0,0,0,1,0,0,0,23,0,4,0,15,0,0,0,11,0,0,0,4,0,0,0,32,0,4,0,14,0,0,0,1,0,0,0,15, + 0,0,0,59,0,4,0,14,0,0,0,13,0,0,0,1,0,0,0,32,0,4,0,17,0,0,0,3,0,0,0,15,0,0,0,59,0,4,0,17,0,0,0,16,0,0,0,3,0,0,0,59,0,4,0,17,0,0,0,18,0,0,0,3,0,0,0,32,0,4,0,20,0,0,0,3,0,0,0,10,0,0,0, + 59,0,4,0,20,0,0,0,19,0,0,0,3,0,0,0,32,0,4,0,22,0,0,0,3,0,0,0,11,0,0,0,59,0,4,0,22,0,0,0,21,0,0,0,3,0,0,0,30,0,5,0,24,0,0,0,15,0,0,0,15,0,0,0,10,0,0,0,30,0,5,0,25,0,0,0,10,0,0,0,10, + 0,0,0,15,0,0,0,33,0,4,0,27,0,0,0,24,0,0,0,25,0,0,0,32,0,4,0,30,0,0,0,7,0,0,0,24,0,0,0,46,0,3,0,24,0,0,0,31,0,0,0,32,0,4,0,33,0,0,0,7,0,0,0,15,0,0,0,43,0,4,0,6,0,0,0,34,0,0,0,0,0,0, + 0,24,0,4,0,36,0,0,0,15,0,0,0,4,0,0,0,43,0,4,0,11,0,0,0,40,0,0,0,0,0,0,0,43,0,4,0,11,0,0,0,41,0,0,0,0,0,128,63,43,0,4,0,6,0,0,0,44,0,0,0,1,0,0,0,32,0,4,0,47,0,0,0,7,0,0,0,10,0,0,0,43, + 0,4,0,6,0,0,0,48,0,0,0,2,0,0,0,33,0,4,0,52,0,0,0,36,0,0,0,6,0,0,0,43,0,4,0,6,0,0,0,55,0,0,0,16,0,0,0,32,0,4,0,57,0,0,0,2,0,0,0,5,0,0,0,43,0,4,0,6,0,0,0,66,0,0,0,32,0,0,0,43,0,4,0,6, + 0,0,0,72,0,0,0,48,0,0,0,19,0,2,0,79,0,0,0,33,0,3,0,80,0,0,0,79,0,0,0,54,0,5,0,24,0,0,0,23,0,0,0,0,0,0,0,27,0,0,0,55,0,3,0,25,0,0,0,26,0,0,0,248,0,2,0,28,0,0,0,59,0,5,0,30,0,0,0,29, + 0,0,0,7,0,0,0,31,0,0,0,65,0,5,0,33,0,0,0,32,0,0,0,29,0,0,0,34,0,0,0,57,0,5,0,36,0,0,0,35,0,0,0,37,0,0,0,34,0,0,0,81,0,5,0,10,0,0,0,38,0,0,0,26,0,0,0,0,0,0,0,80,0,6,0,15,0,0,0,39,0, + 0,0,38,0,0,0,40,0,0,0,41,0,0,0,145,0,5,0,15,0,0,0,42,0,0,0,35,0,0,0,39,0,0,0,62,0,4,0,32,0,0,0,42,0,0,0,0,0,0,0,65,0,5,0,33,0,0,0,43,0,0,0,29,0,0,0,44,0,0,0,81,0,5,0,15,0,0,0,45,0, + 0,0,26,0,0,0,2,0,0,0,62,0,4,0,43,0,0,0,45,0,0,0,0,0,0,0,65,0,5,0,47,0,0,0,46,0,0,0,29,0,0,0,48,0,0,0,81,0,5,0,10,0,0,0,49,0,0,0,26,0,0,0,1,0,0,0,62,0,4,0,46,0,0,0,49,0,0,0,0,0,0,0, + 61,0,5,0,24,0,0,0,50,0,0,0,29,0,0,0,0,0,0,0,254,0,2,0,50,0,0,0,56,0,1,0,54,0,5,0,36,0,0,0,37,0,0,0,0,0,0,0,52,0,0,0,55,0,3,0,6,0,0,0,51,0,0,0,248,0,2,0,53,0,0,0,134,0,5,0,6,0,0,0,54, + 0,0,0,51,0,0,0,55,0,0,0,65,0,6,0,57,0,0,0,56,0,0,0,1,0,0,0,34,0,0,0,54,0,0,0,61,0,5,0,5,0,0,0,58,0,0,0,56,0,0,0,0,0,0,0,124,0,4,0,15,0,0,0,59,0,0,0,58,0,0,0,128,0,5,0,6,0,0,0,60,0, + 0,0,55,0,0,0,51,0,0,0,134,0,5,0,6,0,0,0,61,0,0,0,60,0,0,0,55,0,0,0,65,0,6,0,57,0,0,0,62,0,0,0,1,0,0,0,34,0,0,0,61,0,0,0,61,0,5,0,5,0,0,0,63,0,0,0,62,0,0,0,0,0,0,0,124,0,4,0,15,0,0, + 0,64,0,0,0,63,0,0,0,128,0,5,0,6,0,0,0,65,0,0,0,66,0,0,0,51,0,0,0,134,0,5,0,6,0,0,0,67,0,0,0,65,0,0,0,55,0,0,0,65,0,6,0,57,0,0,0,68,0,0,0,1,0,0,0,34,0,0,0,67,0,0,0,61,0,5,0,5,0,0,0, + 69,0,0,0,68,0,0,0,0,0,0,0,124,0,4,0,15,0,0,0,70,0,0,0,69,0,0,0,128,0,5,0,6,0,0,0,71,0,0,0,72,0,0,0,51,0,0,0,134,0,5,0,6,0,0,0,73,0,0,0,71,0,0,0,55,0,0,0,65,0,6,0,57,0,0,0,74,0,0,0, + 1,0,0,0,34,0,0,0,73,0,0,0,61,0,5,0,5,0,0,0,75,0,0,0,74,0,0,0,0,0,0,0,124,0,4,0,15,0,0,0,76,0,0,0,75,0,0,0,80,0,7,0,36,0,0,0,77,0,0,0,59,0,0,0,64,0,0,0,70,0,0,0,76,0,0,0,254,0,2,0,77, + 0,0,0,56,0,1,0,54,0,5,0,79,0,0,0,78,0,0,0,0,0,0,0,80,0,0,0,248,0,2,0,81,0,0,0,61,0,5,0,10,0,0,0,82,0,0,0,8,0,0,0,0,0,0,0,61,0,5,0,10,0,0,0,83,0,0,0,12,0,0,0,0,0,0,0,61,0,5,0,15,0,0, + 0,84,0,0,0,13,0,0,0,0,0,0,0,80,0,6,0,25,0,0,0,85,0,0,0,82,0,0,0,83,0,0,0,84,0,0,0,57,0,5,0,24,0,0,0,86,0,0,0,23,0,0,0,85,0,0,0,81,0,5,0,15,0,0,0,87,0,0,0,86,0,0,0,0,0,0,0,62,0,4,0, + 16,0,0,0,87,0,0,0,0,0,0,0,81,0,5,0,15,0,0,0,88,0,0,0,86,0,0,0,1,0,0,0,62,0,4,0,18,0,0,0,88,0,0,0,0,0,0,0,81,0,5,0,10,0,0,0,89,0,0,0,86,0,0,0,2,0,0,0,62,0,4,0,19,0,0,0,89,0,0,0,0,0, + 0,0,62,0,4,0,21,0,0,0,41,0,0,0,0,0,0,0,253,0,1,0,56,0,1,0, +}; + +// Same shader as __shader_frag_wgsl[] but compiled as SPIRV. +// 'wgslc -o frag.spv frag.wgsl' + 'binary_to_compressed_c -u8 -nocompress frag.spv' +static const unsigned char __shader_frag_spirv[1392] = +{ + 3,2,35,7,0,3,1,0,1,0,23,0,60,0,0,0,0,0,0,0,17,0,2,0,1,0,0,0,11,0,6,0,48,0,0,0,71,76,83,76,46,115,116,100,46,52,53,48,0,0,0,0,14,0,3,0,0,0,0,0,1,0,0,0,15,0,9,0,4,0,0,0,51,0,0,0,109, + 97,105,110,0,0,0,0,15,0,0,0,18,0,0,0,19,0,0,0,22,0,0,0,16,0,3,0,51,0,0,0,7,0,0,0,71,0,4,0,4,0,0,0,6,0,0,0,16,0,0,0,72,0,5,0,3,0,0,0,0,0,0,0,35,0,0,0,0,0,0,0,71,0,3,0,3,0,0,0,2,0,0, + 0,71,0,4,0,1,0,0,0,34,0,0,0,0,0,0,0,71,0,4,0,1,0,0,0,33,0,0,0,0,0,0,0,71,0,3,0,1,0,0,0,24,0,0,0,71,0,4,0,8,0,0,0,34,0,0,0,0,0,0,0,71,0,4,0,8,0,0,0,33,0,0,0,1,0,0,0,71,0,4,0,11,0,0, + 0,34,0,0,0,1,0,0,0,71,0,4,0,11,0,0,0,33,0,0,0,0,0,0,0,71,0,4,0,15,0,0,0,11,0,0,0,15,0,0,0,71,0,4,0,18,0,0,0,30,0,0,0,0,0,0,0,71,0,4,0,19,0,0,0,30,0,0,0,1,0,0,0,71,0,4,0,22,0,0,0,30, + 0,0,0,0,0,0,0,21,0,4,0,6,0,0,0,32,0,0,0,0,0,0,0,23,0,4,0,5,0,0,0,6,0,0,0,4,0,0,0,43,0,4,0,6,0,0,0,7,0,0,0,5,0,0,0,28,0,4,0,4,0,0,0,5,0,0,0,7,0,0,0,30,0,3,0,3,0,0,0,4,0,0,0,32,0,4,0, + 2,0,0,0,2,0,0,0,3,0,0,0,59,0,4,0,2,0,0,0,1,0,0,0,2,0,0,0,26,0,2,0,10,0,0,0,32,0,4,0,9,0,0,0,0,0,0,0,10,0,0,0,59,0,4,0,9,0,0,0,8,0,0,0,0,0,0,0,22,0,3,0,14,0,0,0,32,0,0,0,25,0,9,0,13, + 0,0,0,14,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,32,0,4,0,12,0,0,0,0,0,0,0,13,0,0,0,59,0,4,0,12,0,0,0,11,0,0,0,0,0,0,0,23,0,4,0,17,0,0,0,14,0,0,0,4,0,0,0,32,0,4,0,16, + 0,0,0,1,0,0,0,17,0,0,0,59,0,4,0,16,0,0,0,15,0,0,0,1,0,0,0,59,0,4,0,16,0,0,0,18,0,0,0,1,0,0,0,23,0,4,0,21,0,0,0,14,0,0,0,2,0,0,0,32,0,4,0,20,0,0,0,1,0,0,0,21,0,0,0,59,0,4,0,20,0,0,0, + 19,0,0,0,1,0,0,0,32,0,4,0,23,0,0,0,3,0,0,0,17,0,0,0,59,0,4,0,23,0,0,0,22,0,0,0,3,0,0,0,30,0,5,0,25,0,0,0,17,0,0,0,17,0,0,0,21,0,0,0,33,0,4,0,27,0,0,0,17,0,0,0,25,0,0,0,27,0,3,0,34, + 0,0,0,13,0,0,0,23,0,4,0,38,0,0,0,14,0,0,0,3,0,0,0,32,0,4,0,40,0,0,0,2,0,0,0,5,0,0,0,43,0,4,0,6,0,0,0,41,0,0,0,0,0,0,0,43,0,4,0,6,0,0,0,42,0,0,0,4,0,0,0,19,0,2,0,52,0,0,0,33,0,3,0,53, + 0,0,0,52,0,0,0,54,0,5,0,17,0,0,0,24,0,0,0,0,0,0,0,27,0,0,0,55,0,3,0,25,0,0,0,26,0,0,0,248,0,2,0,28,0,0,0,81,0,5,0,17,0,0,0,29,0,0,0,26,0,0,0,1,0,0,0,61,0,5,0,13,0,0,0,30,0,0,0,11,0, + 0,0,0,0,0,0,61,0,5,0,10,0,0,0,31,0,0,0,8,0,0,0,0,0,0,0,81,0,5,0,21,0,0,0,32,0,0,0,26,0,0,0,2,0,0,0,86,0,5,0,34,0,0,0,33,0,0,0,30,0,0,0,31,0,0,0,87,0,6,0,17,0,0,0,35,0,0,0,33,0,0,0, + 32,0,0,0,0,0,0,0,133,0,5,0,17,0,0,0,36,0,0,0,29,0,0,0,35,0,0,0,79,0,8,0,38,0,0,0,37,0,0,0,36,0,0,0,36,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0,65,0,6,0,40,0,0,0,39,0,0,0,1,0,0,0,41,0,0,0,42,0, + 0,0,61,0,5,0,5,0,0,0,43,0,0,0,39,0,0,0,0,0,0,0,81,0,5,0,6,0,0,0,44,0,0,0,43,0,0,0,0,0,0,0,124,0,4,0,14,0,0,0,45,0,0,0,44,0,0,0,80,0,6,0,38,0,0,0,46,0,0,0,45,0,0,0,45,0,0,0,45,0,0,0, + 12,0,7,0,38,0,0,0,47,0,0,0,48,0,0,0,26,0,0,0,37,0,0,0,46,0,0,0,81,0,5,0,14,0,0,0,49,0,0,0,36,0,0,0,3,0,0,0,80,0,5,0,17,0,0,0,50,0,0,0,47,0,0,0,49,0,0,0,254,0,2,0,50,0,0,0,56,0,1,0, + 54,0,5,0,52,0,0,0,51,0,0,0,0,0,0,0,53,0,0,0,248,0,2,0,54,0,0,0,61,0,5,0,17,0,0,0,55,0,0,0,15,0,0,0,0,0,0,0,61,0,5,0,17,0,0,0,56,0,0,0,18,0,0,0,0,0,0,0,61,0,5,0,21,0,0,0,57,0,0,0,19, + 0,0,0,0,0,0,0,80,0,6,0,25,0,0,0,58,0,0,0,55,0,0,0,56,0,0,0,57,0,0,0,57,0,5,0,17,0,0,0,59,0,0,0,24,0,0,0,58,0,0,0,62,0,4,0,22,0,0,0,59,0,0,0,0,0,0,0,253,0,1,0,56,0,1,0, +}; + static void SafeRelease(ImDrawIdx*& res) { if (res) @@ -243,9 +302,11 @@ static void SafeRelease(WGPUShaderModule& res) } static void SafeRelease(RenderResources& res) { - SafeRelease(res.Sampler); + SafeRelease(res.SamplerLinear); + SafeRelease(res.SamplerNearest); SafeRelease(res.Uniforms); - SafeRelease(res.CommonBindGroup); + SafeRelease(res.CommonBindGroupLinear); + SafeRelease(res.CommonBindGroupNearest); SafeRelease(res.ImageBindGroupLayout); }; @@ -257,31 +318,62 @@ static void SafeRelease(FrameResources& res) SafeRelease(res.VertexBufferHost); } -static WGPUProgrammableStageDescriptor ImGui_ImplWGPU_CreateShaderModule(const char* wgsl_source) +static WGPUComputeState ImGui_ImplWGPU_CreateShaderModuleWGSL(const char* wgsl_source) { ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData(); + IM_UNUSED(bd); -#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU_EMSCRIPTEN) WGPUShaderSourceWGSL wgsl_desc = {}; wgsl_desc.chain.sType = WGPUSType_ShaderSourceWGSL; wgsl_desc.code = { wgsl_source, WGPU_STRLEN }; -#else - WGPUShaderModuleWGSLDescriptor wgsl_desc = {}; - wgsl_desc.chain.sType = WGPUSType_ShaderModuleWGSLDescriptor; - wgsl_desc.code = wgsl_source; -#endif WGPUShaderModuleDescriptor desc = {}; desc.nextInChain = (WGPUChainedStruct*)&wgsl_desc; - WGPUProgrammableStageDescriptor stage_desc = {}; + // Detect shader compilation errors by using an error scope. + // Flag to be passed into the validation callback `userdata1` pointer. + int validation_error = 0; + wgpuDevicePushErrorScope(bd->wgpuDevice, WGPUErrorFilter_Validation); + WGPUShaderModule module = wgpuDeviceCreateShaderModule(bd->wgpuDevice, &desc); + WGPUPopErrorScopeCallbackInfo pop_cb = {}; + pop_cb.mode = WGPUCallbackMode_AllowSpontaneous; + pop_cb.callback = [](WGPUPopErrorScopeStatus, WGPUErrorType type, WGPUStringView, void* userdata1, void*) + { + if (type == WGPUErrorType_Validation) + *static_cast(userdata1) = 1; + }; + pop_cb.userdata1 = &validation_error; + wgpuDevicePopErrorScope(bd->wgpuDevice, pop_cb); + + WGPUComputeState stage_desc = {}; + if (module && !validation_error) + { + stage_desc.module = module; + stage_desc.entryPoint = { "main", WGPU_STRLEN }; + } + else if (module) + { + wgpuShaderModuleRelease(module); + } + return stage_desc; +} + +static WGPUComputeState ImGui_ImplWGPU_CreateShaderModuleSPIRV(const void* spirv_binary, size_t spirv_length) +{ + ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData(); + + WGPUShaderSourceSPIRV spirv_desc = {}; + spirv_desc.chain.sType = WGPUSType_ShaderSourceSPIRV; + spirv_desc.code = (const uint32_t *)spirv_binary; + spirv_desc.codeSize = ((uint32_t)(spirv_length / 4)); + + WGPUShaderModuleDescriptor desc = {}; + desc.nextInChain = (WGPUChainedStruct*)&spirv_desc; + + WGPUComputeState stage_desc = {}; stage_desc.module = wgpuDeviceCreateShaderModule(bd->wgpuDevice, &desc); -#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU_EMSCRIPTEN) stage_desc.entryPoint = { "main", WGPU_STRLEN }; -#else - stage_desc.entryPoint = "main"; -#endif return stage_desc; } @@ -350,26 +442,32 @@ static void ImGui_ImplWGPU_SetupRenderState(ImDrawData* draw_data, WGPURenderPas } // Setup viewport - wgpuRenderPassEncoderSetViewport(ctx, 0, 0, draw_data->FramebufferScale.x * draw_data->DisplaySize.x, draw_data->FramebufferScale.y * draw_data->DisplaySize.y, 0, 1); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); + wgpuRenderPassEncoderSetViewport(ctx, 0, 0, (float)fb_width, (float)fb_height, 0, 1); // Bind shader and vertex buffers wgpuRenderPassEncoderSetVertexBuffer(ctx, 0, fr->VertexBuffer, 0, fr->VertexBufferSize * sizeof(ImDrawVert)); wgpuRenderPassEncoderSetIndexBuffer(ctx, fr->IndexBuffer, sizeof(ImDrawIdx) == 2 ? WGPUIndexFormat_Uint16 : WGPUIndexFormat_Uint32, 0, fr->IndexBufferSize * sizeof(ImDrawIdx)); wgpuRenderPassEncoderSetPipeline(ctx, bd->pipelineState); - wgpuRenderPassEncoderSetBindGroup(ctx, 0, bd->renderResources.CommonBindGroup, 0, nullptr); + wgpuRenderPassEncoderSetBindGroup(ctx, 0, bd->renderResources.CommonBindGroupLinear, 0, nullptr); // Setup blend factor - WGPUColor blend_color = { 0.f, 0.f, 0.f, 0.f }; + WGPUColor blend_color = { 0.0f, 0.0f, 0.0f, 0.0f }; wgpuRenderPassEncoderSetBlendConstant(ctx, &blend_color); } +// Draw callbacks +static void ImGui_ImplWGPU_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. +static void ImGui_ImplWGPU_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData(); wgpuRenderPassEncoderSetBindGroup(bd->RenderState->RenderPassEncoder, 0, bd->renderResources.CommonBindGroupLinear, 0, nullptr); } +static void ImGui_ImplWGPU_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData(); wgpuRenderPassEncoderSetBindGroup(bd->RenderState->RenderPassEncoder, 0, bd->renderResources.CommonBindGroupNearest, 0, nullptr); } + // Render function -// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder pass_encoder) { // Avoid rendering when minimized - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); if (fb_width <= 0 || fb_height <= 0 || draw_data->CmdLists.Size == 0) return; @@ -400,11 +498,7 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder WGPUBufferDescriptor vb_desc = { nullptr, -#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU_EMSCRIPTEN) { "Dear ImGui Vertex buffer", WGPU_STRLEN, }, -#else - "Dear ImGui Vertex buffer", -#endif WGPUBufferUsage_CopyDst | WGPUBufferUsage_Vertex, MEMALIGN(fr->VertexBufferSize * sizeof(ImDrawVert), 4), false @@ -428,11 +522,7 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder WGPUBufferDescriptor ib_desc = { nullptr, -#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU_EMSCRIPTEN) { "Dear ImGui Index buffer", WGPU_STRLEN, }, -#else - "Dear ImGui Index buffer", -#endif WGPUBufferUsage_CopyDst | WGPUBufferUsage_Index, MEMALIGN(fr->IndexBufferSize * sizeof(ImDrawIdx), 4), false @@ -456,8 +546,8 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder } int64_t vb_write_size = MEMALIGN((char*)vtx_dst - (char*)fr->VertexBufferHost, 4); int64_t ib_write_size = MEMALIGN((char*)idx_dst - (char*)fr->IndexBufferHost, 4); - wgpuQueueWriteBuffer(bd->defaultQueue, fr->VertexBuffer, 0, fr->VertexBufferHost, vb_write_size); - wgpuQueueWriteBuffer(bd->defaultQueue, fr->IndexBuffer, 0, fr->IndexBufferHost, ib_write_size); + wgpuQueueWriteBuffer(bd->defaultQueue, fr->VertexBuffer, 0, fr->VertexBufferHost, (size_t)vb_write_size); + wgpuQueueWriteBuffer(bd->defaultQueue, fr->IndexBuffer, 0, fr->IndexBufferHost, (size_t)ib_write_size); // Setup desired render state ImGui_ImplWGPU_SetupRenderState(draw_data, pass_encoder, fr); @@ -467,7 +557,7 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder ImGui_ImplWGPU_RenderState render_state; render_state.Device = bd->wgpuDevice; render_state.RenderPassEncoder = pass_encoder; - platform_io.Renderer_RenderState = &render_state; + platform_io.Renderer_RenderState = bd->RenderState = &render_state; // Render command lists // (Because we merged all buffers into a single one, we maintain our own offset into them) @@ -483,8 +573,7 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder if (pcmd->UserCallback != nullptr) { // User callback, registered via ImDrawList::AddCallback() - // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) - if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + if (pcmd->UserCallback == ImGui_ImplWGPU_DrawCallback_ResetRenderState) ImGui_ImplWGPU_SetupRenderState(draw_data, pass_encoder, fr); else pcmd->UserCallback(draw_list, pcmd); @@ -495,12 +584,11 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder ImTextureID tex_id = pcmd->GetTexID(); ImGuiID tex_id_hash = ImHashData(&tex_id, sizeof(tex_id), 0); WGPUBindGroup bind_group = (WGPUBindGroup)bd->renderResources.ImageBindGroups.GetVoidPtr(tex_id_hash); - if (!bind_group) - { - bind_group = ImGui_ImplWGPU_CreateImageBindGroup(bd->renderResources.ImageBindGroupLayout, (WGPUTextureView)tex_id); - bd->renderResources.ImageBindGroups.SetVoidPtr(tex_id_hash, bind_group); - } - wgpuRenderPassEncoderSetBindGroup(pass_encoder, 1, (WGPUBindGroup)bind_group, 0, nullptr); + if (!bind_group && tex_id != 0) + if ((bind_group = ImGui_ImplWGPU_CreateImageBindGroup(bd->renderResources.ImageBindGroupLayout, (WGPUTextureView)tex_id)) != 0) + bd->renderResources.ImageBindGroups.SetVoidPtr(tex_id_hash, bind_group); + if (bind_group) + wgpuRenderPassEncoderSetBindGroup(pass_encoder, 1, (WGPUBindGroup)bind_group, 0, nullptr); // Project scissor/clipping rectangles into framebuffer space ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y); @@ -509,8 +597,8 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder // Clamp to viewport as wgpuRenderPassEncoderSetScissorRect() won't accept values that are off bounds if (clip_min.x < 0.0f) { clip_min.x = 0.0f; } if (clip_min.y < 0.0f) { clip_min.y = 0.0f; } - if (clip_max.x > fb_width) { clip_max.x = (float)fb_width; } - if (clip_max.y > fb_height) { clip_max.y = (float)fb_height; } + if (clip_max.x > (float)fb_width) { clip_max.x = (float)fb_width; } + if (clip_max.y > (float)fb_height) { clip_max.y = (float)fb_height; } if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) continue; @@ -532,7 +620,7 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder } image_bind_groups.Data.resize(0); - platform_io.Renderer_RenderState = nullptr; + platform_io.Renderer_RenderState = bd->RenderState = nullptr; } static void ImGui_ImplWGPU_DestroyTexture(ImTextureData* tex) @@ -564,11 +652,7 @@ void ImGui_ImplWGPU_UpdateTexture(ImTextureData* tex) // Create texture WGPUTextureDescriptor tex_desc = {}; -#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU_EMSCRIPTEN) tex_desc.label = { "Dear ImGui Texture", WGPU_STRLEN }; -#else - tex_desc.label = "Dear ImGui Texture"; -#endif tex_desc.dimension = WGPUTextureDimension_2D; tex_desc.size.width = tex->Width; tex_desc.size.height = tex->Height; @@ -609,20 +693,12 @@ void ImGui_ImplWGPU_UpdateTexture(ImTextureData* tex) // Update full texture or selected blocks. We only ever write to textures regions which have never been used before! // This backend choose to use tex->UpdateRect but you can use tex->Updates[] to upload individual regions. -#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU_EMSCRIPTEN) WGPUTexelCopyTextureInfo dst_view = {}; -#else - WGPUImageCopyTexture dst_view = {}; -#endif dst_view.texture = backend_tex->Texture; dst_view.mipLevel = 0; dst_view.origin = { (uint32_t)upload_x, (uint32_t)upload_y, 0 }; dst_view.aspect = WGPUTextureAspect_All; -#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU_EMSCRIPTEN) WGPUTexelCopyBufferLayout layout = {}; -#else - WGPUTextureDataLayout layout = {}; -#endif layout.offset = 0; layout.bytesPerRow = tex->Width * tex->BytesPerPixel; layout.rowsPerImage = upload_h; @@ -640,11 +716,7 @@ static void ImGui_ImplWGPU_CreateUniformBuffer() WGPUBufferDescriptor ub_desc = { nullptr, -#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU_EMSCRIPTEN) { "Dear ImGui Uniform buffer", WGPU_STRLEN, }, -#else - "Dear ImGui Uniform buffer", -#endif WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform, MEMALIGN(sizeof(Uniforms), 16), false @@ -701,22 +773,17 @@ bool ImGui_ImplWGPU_CreateDeviceObjects() graphics_pipeline_desc.layout = wgpuDeviceCreatePipelineLayout(bd->wgpuDevice, &layout_desc); // Create the vertex shader - WGPUProgrammableStageDescriptor vertex_shader_desc = ImGui_ImplWGPU_CreateShaderModule(__shader_vert_wgsl); + WGPUComputeState vertex_shader_desc = ImGui_ImplWGPU_CreateShaderModuleWGSL(__shader_vert_wgsl); + if (!vertex_shader_desc.module) vertex_shader_desc = ImGui_ImplWGPU_CreateShaderModuleSPIRV(__shader_vert_spirv, sizeof(__shader_vert_spirv)); graphics_pipeline_desc.vertex.module = vertex_shader_desc.module; graphics_pipeline_desc.vertex.entryPoint = vertex_shader_desc.entryPoint; // Vertex input configuration WGPUVertexAttribute attribute_desc[] = { -#ifdef IMGUI_IMPL_WEBGPU_BACKEND_DAWN { nullptr, WGPUVertexFormat_Float32x2, (uint64_t)offsetof(ImDrawVert, pos), 0 }, { nullptr, WGPUVertexFormat_Float32x2, (uint64_t)offsetof(ImDrawVert, uv), 1 }, { nullptr, WGPUVertexFormat_Unorm8x4, (uint64_t)offsetof(ImDrawVert, col), 2 }, -#else - { WGPUVertexFormat_Float32x2, (uint64_t)offsetof(ImDrawVert, pos), 0 }, - { WGPUVertexFormat_Float32x2, (uint64_t)offsetof(ImDrawVert, uv), 1 }, - { WGPUVertexFormat_Unorm8x4, (uint64_t)offsetof(ImDrawVert, col), 2 }, -#endif }; WGPUVertexBufferLayout buffer_layouts[1]; @@ -729,7 +796,8 @@ bool ImGui_ImplWGPU_CreateDeviceObjects() graphics_pipeline_desc.vertex.buffers = buffer_layouts; // Create the pixel shader - WGPUProgrammableStageDescriptor pixel_shader_desc = ImGui_ImplWGPU_CreateShaderModule(__shader_frag_wgsl); + WGPUComputeState pixel_shader_desc = ImGui_ImplWGPU_CreateShaderModuleWGSL(__shader_frag_wgsl); + if (!pixel_shader_desc.module) pixel_shader_desc = ImGui_ImplWGPU_CreateShaderModuleSPIRV(__shader_frag_spirv, sizeof(__shader_frag_spirv)); // Create the blending setup WGPUBlendState blend_state = {}; @@ -756,11 +824,7 @@ bool ImGui_ImplWGPU_CreateDeviceObjects() // Create depth-stencil State WGPUDepthStencilState depth_stencil_state = {}; depth_stencil_state.format = bd->depthStencilFormat; -#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU_EMSCRIPTEN) depth_stencil_state.depthWriteEnabled = WGPUOptionalBool_False; -#else - depth_stencil_state.depthWriteEnabled = false; -#endif depth_stencil_state.depthCompare = WGPUCompareFunction_Always; depth_stencil_state.stencilFront.compare = WGPUCompareFunction_Always; depth_stencil_state.stencilFront.failOp = WGPUStencilOperation_Keep; @@ -778,29 +842,34 @@ bool ImGui_ImplWGPU_CreateDeviceObjects() ImGui_ImplWGPU_CreateUniformBuffer(); - // Create sampler - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) + // Create samplers (Linear/Nearest) (bilinear sampling is required) WGPUSamplerDescriptor sampler_desc = {}; - sampler_desc.minFilter = WGPUFilterMode_Linear; - sampler_desc.magFilter = WGPUFilterMode_Linear; - sampler_desc.mipmapFilter = WGPUMipmapFilterMode_Linear; sampler_desc.addressModeU = WGPUAddressMode_ClampToEdge; sampler_desc.addressModeV = WGPUAddressMode_ClampToEdge; sampler_desc.addressModeW = WGPUAddressMode_ClampToEdge; sampler_desc.maxAnisotropy = 1; - bd->renderResources.Sampler = wgpuDeviceCreateSampler(bd->wgpuDevice, &sampler_desc); + sampler_desc.minFilter = WGPUFilterMode_Linear; + sampler_desc.magFilter = WGPUFilterMode_Linear; + sampler_desc.mipmapFilter = WGPUMipmapFilterMode_Linear; + bd->renderResources.SamplerLinear = wgpuDeviceCreateSampler(bd->wgpuDevice, &sampler_desc); + sampler_desc.minFilter = WGPUFilterMode_Nearest; + sampler_desc.magFilter = WGPUFilterMode_Nearest; + sampler_desc.mipmapFilter = WGPUMipmapFilterMode_Nearest; + bd->renderResources.SamplerNearest = wgpuDeviceCreateSampler(bd->wgpuDevice, &sampler_desc); - // Create resource bind group + // Create resource bind groups (one per sampler, otherwise identical) WGPUBindGroupEntry common_bg_entries[] = { { nullptr, 0, bd->renderResources.Uniforms, 0, MEMALIGN(sizeof(Uniforms), 16), 0, 0 }, - { nullptr, 1, 0, 0, 0, bd->renderResources.Sampler, 0 }, + { nullptr, 1, 0, 0, 0, bd->renderResources.SamplerLinear, 0 }, }; WGPUBindGroupDescriptor common_bg_descriptor = {}; common_bg_descriptor.layout = bg_layouts[0]; common_bg_descriptor.entryCount = sizeof(common_bg_entries) / sizeof(WGPUBindGroupEntry); common_bg_descriptor.entries = common_bg_entries; - bd->renderResources.CommonBindGroup = wgpuDeviceCreateBindGroup(bd->wgpuDevice, &common_bg_descriptor); + bd->renderResources.CommonBindGroupLinear = wgpuDeviceCreateBindGroup(bd->wgpuDevice, &common_bg_descriptor); + common_bg_entries[1].sampler = bd->renderResources.SamplerNearest; + bd->renderResources.CommonBindGroupNearest = wgpuDeviceCreateBindGroup(bd->wgpuDevice, &common_bg_descriptor); bd->renderResources.ImageBindGroupLayout = bg_layouts[1]; SafeRelease(vertex_shader_desc.module); @@ -845,15 +914,18 @@ bool ImGui_ImplWGPU_Init(ImGui_ImplWGPU_InitInfo* init_info) io.BackendRendererName = "imgui_impl_wgpu (Dawn, Native)"; #endif #elif defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) -#if defined(__EMSCRIPTEN__) - io.BackendRendererName = "imgui_impl_wgpu (WGPU, Emscripten)"; // linked using EMSCRIPTEN with "-sUSE_WEBGPU=1" flag, deprecated from EMSCRIPTEN 4.0.10 -#else io.BackendRendererName = "imgui_impl_wgpu (WGPU, Native)"; -#endif +#elif defined(IMGUI_IMPL_WEBGPU_BACKEND_WGVK) + io.BackendRendererName = "imgui_impl_wgpu (WGVK, Native)"; #endif io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.DrawCallback_ResetRenderState = ImGui_ImplWGPU_DrawCallback_ResetRenderState; + platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplWGPU_DrawCallback_SetSamplerLinear; + platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplWGPU_DrawCallback_SetSamplerNearest; + bd->initInfo = *init_info; bd->wgpuDevice = init_info->Device; bd->defaultQueue = wgpuDeviceGetQueue(bd->wgpuDevice); @@ -862,11 +934,7 @@ bool ImGui_ImplWGPU_Init(ImGui_ImplWGPU_InitInfo* init_info) bd->numFramesInFlight = init_info->NumFramesInFlight; bd->frameIndex = UINT_MAX; - bd->renderResources.Sampler = nullptr; - bd->renderResources.Uniforms = nullptr; - bd->renderResources.CommonBindGroup = nullptr; bd->renderResources.ImageBindGroups.Data.reserve(100); - bd->renderResources.ImageBindGroupLayout = nullptr; // Create buffers with a default size (they will later be grown as needed) bd->pFrameResources = new FrameResources[bd->numFramesInFlight]; @@ -921,11 +989,7 @@ void ImGui_ImplWGPU_NewFrame() bool ImGui_ImplWGPU_IsSurfaceStatusError(WGPUSurfaceGetCurrentTextureStatus status) { -#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) return (status == WGPUSurfaceGetCurrentTextureStatus_Error); -#else - return (status == WGPUSurfaceGetCurrentTextureStatus_OutOfMemory || status == WGPUSurfaceGetCurrentTextureStatus_DeviceLost); -#endif } bool ImGui_ImplWGPU_IsSurfaceStatusSubOptimal(WGPUSurfaceGetCurrentTextureStatus status) @@ -938,7 +1002,6 @@ bool ImGui_ImplWGPU_IsSurfaceStatusSubOptimal(WGPUSurfaceGetCurrentTextureStatus } // Helpers to obtain a string -#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) const char* ImGui_ImplWGPU_GetErrorTypeName(WGPUErrorType type) { switch (type) @@ -961,7 +1024,7 @@ const char* ImGui_ImplWGPU_GetDeviceLostReasonName(WGPUDeviceLostReason type) default: return "Unknown"; } } -#elif !defined(__EMSCRIPTEN__) +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) && !defined(__EMSCRIPTEN__) const char* ImGui_ImplWGPU_GetLogLevelName(WGPULogLevel level) { switch (level) diff --git a/backends/imgui_impl_wgpu.h b/backends/imgui_impl_wgpu.h index 522234545..9f206db32 100644 --- a/backends/imgui_impl_wgpu.h +++ b/backends/imgui_impl_wgpu.h @@ -3,20 +3,22 @@ // (Please note that WebGPU is a recent API, may not be supported by all browser, and its ecosystem is generally a mess) // When targeting native platforms: -// - One of IMGUI_IMPL_WEBGPU_BACKEND_DAWN or IMGUI_IMPL_WEBGPU_BACKEND_WGPU *must* be provided. +// - One of IMGUI_IMPL_WEBGPU_BACKEND_DAWN, IMGUI_IMPL_WEBGPU_BACKEND_WGPU or IMGUI_IMPL_WEBGPU_BACKEND_WGVK *must* be provided. // When targeting Emscripten: -// - We now defaults to IMGUI_IMPL_WEBGPU_BACKEND_DAWN is Emscripten version is 4.0.10+, which correspond to using Emscripten '--use-port=emdawnwebgpu'. +// - We now defaults to IMGUI_IMPL_WEBGPU_BACKEND_DAWN and requires Emscripten 4.0.10+, which correspond to using Emscripten '--use-port=emdawnwebgpu'. +// - Emscripten < 4.0.10 is not supported anymore (old '-sUSE_WEBGPU=1' option). // - We can still define IMGUI_IMPL_WEBGPU_BACKEND_WGPU to use Emscripten '-s USE_WEBGPU=1' which is marked as obsolete by Emscripten. // Add #define to your imconfig.h file, or as a compilation flag in your build system. // This requirement may be removed once WebGPU stabilizes and backends converge on a unified interface. //#define IMGUI_IMPL_WEBGPU_BACKEND_DAWN //#define IMGUI_IMPL_WEBGPU_BACKEND_WGPU +//#define IMGUI_IMPL_WEBGPU_BACKEND_WGVK // Implemented features: // [X] Renderer: User texture binding. Use 'WGPUTextureView' as ImTextureID. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). -// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // [X] Renderer: Texture updates support for dynamic font system (ImGuiBackendFlags_RendererHasTextures). +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplWGPU_GetRenderState(). // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. @@ -33,20 +35,7 @@ // Setup Emscripten default if not specified. #if defined(__EMSCRIPTEN__) && !defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) && !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) #include - -#ifdef __EMSCRIPTEN_MAJOR__ -#if (__EMSCRIPTEN_MAJOR__ >= 4) && (__EMSCRIPTEN_MINOR__ >= 0) && (__EMSCRIPTEN_TINY__ >= 10) #define IMGUI_IMPL_WEBGPU_BACKEND_DAWN -#else -#define IMGUI_IMPL_WEBGPU_BACKEND_WGPU -#endif -#else -#if (__EMSCRIPTEN_major__ >= 4) && (__EMSCRIPTEN_minor__ >= 0) && (__EMSCRIPTEN_tiny__ >= 10) -#define IMGUI_IMPL_WEBGPU_BACKEND_DAWN -#else -#define IMGUI_IMPL_WEBGPU_BACKEND_WGPU -#endif -#endif #endif #include @@ -92,6 +81,7 @@ struct ImGui_ImplWGPU_RenderState WGPUDevice Device; WGPURenderPassEncoder RenderPassEncoder; }; +IMGUI_IMPL_API ImGui_ImplWGPU_RenderState* ImGui_ImplWGPU_GetRenderState(); //------------------------------------------------------------------------- // Internal Helpers @@ -106,10 +96,9 @@ bool ImGui_ImplWGPU_IsSurfaceStatusSubOptimal(WGPUSurfaceGetCurrentTextur void ImGui_ImplWGPU_DebugPrintAdapterInfo(const WGPUAdapter& adapter); const char* ImGui_ImplWGPU_GetBackendTypeName(WGPUBackendType type); const char* ImGui_ImplWGPU_GetAdapterTypeName(WGPUAdapterType type); -#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) const char* ImGui_ImplWGPU_GetDeviceLostReasonName(WGPUDeviceLostReason type); const char* ImGui_ImplWGPU_GetErrorTypeName(WGPUErrorType type); -#elif defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) && !defined(__EMSCRIPTEN__) +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) && !defined(__EMSCRIPTEN__) const char* ImGui_ImplWGPU_GetLogLevelName(WGPULogLevel level); #endif diff --git a/backends/imgui_impl_win32.cpp b/backends/imgui_impl_win32.cpp index de92040aa..787329bf6 100644 --- a/backends/imgui_impl_win32.cpp +++ b/backends/imgui_impl_win32.cpp @@ -21,6 +21,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-05-19: DPI: ImGui_ImplWin32_EnableDpiAwareness() helper uses SetProcessDpiAwarenessContext() instead of SetThreadDpiAwarenessContext(), fixes DPI scaling issues with e.g. OpenGL. (#9403) // 2026-01-28: Inputs: Minor optimization not submitting gamepad input if packet number has not changed (reworked from 2025-09-23 attempt). (#9202, #8556) // 2025-12-03: Inputs: handle WM_IME_CHAR/WM_IME_COMPOSITION messages to support Unicode inputs on MBCS (non-Unicode) Windows. (#9099, #3653, #5961) // 2025-10-19: Inputs: Revert previous change to allow for io.ClearInputKeys() on focus-out not losing gamepad state. @@ -412,7 +413,7 @@ void ImGui_ImplWin32_NewFrame() // Setup time step INT64 current_time = 0; ::QueryPerformanceCounter((LARGE_INTEGER*)¤t_time); - io.DeltaTime = (float)(current_time - bd->Time) / bd->TicksPerSecond; + io.DeltaTime = (float)((double)(current_time - bd->Time) / (double)bd->TicksPerSecond); bd->Time = current_time; // Update OS mouse position @@ -886,6 +887,7 @@ DECLARE_HANDLE(DPI_AWARENESS_CONTEXT); typedef HRESULT(WINAPI* PFN_SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS); // Shcore.lib + dll, Windows 8.1+ typedef HRESULT(WINAPI* PFN_GetDpiForMonitor)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); // Shcore.lib + dll, Windows 8.1+ typedef DPI_AWARENESS_CONTEXT(WINAPI* PFN_SetThreadDpiAwarenessContext)(DPI_AWARENESS_CONTEXT); // User32.lib + dll, Windows 10 v1607+ (Creators Update) +typedef BOOL(WINAPI* PFN_SetProcessDpiAwarenessContext)(DPI_AWARENESS_CONTEXT); // User32.lib + dll, Windows 10 v1703+ (Creators Update) // Helper function to enable DPI awareness without setting up a manifest void ImGui_ImplWin32_EnableDpiAwareness() @@ -893,7 +895,12 @@ void ImGui_ImplWin32_EnableDpiAwareness() if (_IsWindows10OrGreater()) { static HINSTANCE user32_dll = ::LoadLibraryA("user32.dll"); // Reference counted per-process - if (PFN_SetThreadDpiAwarenessContext SetThreadDpiAwarenessContextFn = (PFN_SetThreadDpiAwarenessContext)::GetProcAddress(user32_dll, "SetThreadDpiAwarenessContext")) + if (PFN_SetProcessDpiAwarenessContext SetProcessDpiAwarenessContextFn = (PFN_SetProcessDpiAwarenessContext)::GetProcAddress(user32_dll, "SetProcessDpiAwarenessContext")) // Windows 10 v1703+ + { + SetProcessDpiAwarenessContextFn(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + return; + } + if (PFN_SetThreadDpiAwarenessContext SetThreadDpiAwarenessContextFn = (PFN_SetThreadDpiAwarenessContext)::GetProcAddress(user32_dll, "SetThreadDpiAwarenessContext")) // Windows 10 v1607+ { SetThreadDpiAwarenessContextFn(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); return; @@ -930,7 +937,7 @@ float ImGui_ImplWin32_GetDpiScaleForMonitor(void* monitor) { GetDpiForMonitorFn((HMONITOR)monitor, MDT_EFFECTIVE_DPI, &xdpi, &ydpi); IM_ASSERT(xdpi == ydpi); // Please contact me if you hit this assert! - return xdpi / 96.0f; + return (float)xdpi / 96.0f; } } #ifndef NOGDI @@ -940,7 +947,7 @@ float ImGui_ImplWin32_GetDpiScaleForMonitor(void* monitor) IM_ASSERT(xdpi == ydpi); // Please contact me if you hit this assert! ::ReleaseDC(nullptr, dc); #endif - return xdpi / 96.0f; + return (float)xdpi / 96.0f; } float ImGui_ImplWin32_GetDpiScaleForHwnd(void* hwnd) diff --git a/backends/vulkan/glsl_shader.frag b/backends/vulkan/glsl_shader.frag index ce7e6f72b..00fa28c64 100644 --- a/backends/vulkan/glsl_shader.frag +++ b/backends/vulkan/glsl_shader.frag @@ -1,7 +1,8 @@ #version 450 core layout(location = 0) out vec4 fColor; -layout(set=0, binding=0) uniform sampler2D sTexture; +layout(set=0, binding=0) uniform texture2D _Texture; +layout(set=1, binding=0) uniform sampler _Sampler; layout(location = 0) in struct { vec4 Color; @@ -10,5 +11,5 @@ layout(location = 0) in struct { void main() { - fColor = In.Color * texture(sTexture, In.UV.st); + fColor = In.Color * texture(sampler2D(_Texture, _Sampler), In.UV.st); } diff --git a/docs/BACKENDS.md b/docs/BACKENDS.md index 7839764cd..5aa072092 100644 --- a/docs/BACKENDS.md +++ b/docs/BACKENDS.md @@ -72,13 +72,14 @@ For example, the [example_win32_directx11](https://github.com/ocornut/imgui/tree In the [backends/](https://github.com/ocornut/imgui/blob/master/backends) folder: -List of Platforms Backends: +List of Platform Backends: imgui_impl_android.cpp ; Android native app API imgui_impl_glfw.cpp ; GLFW (Windows, macOS, Linux, etc.) http://www.glfw.org/ imgui_impl_osx.mm ; macOS native API (not as feature complete as glfw/sdl backends) imgui_impl_sdl2.cpp ; SDL2 (Windows, macOS, Linux, iOS, Android) https://www.libsdl.org imgui_impl_sdl3.cpp ; SDL3 (Windows, macOS, Linux, iOS, Android) https://www.libsdl.org + imgui_impl_qnx.cpp ; QNX Screen imgui_impl_win32.cpp ; Win32 native API (Windows) imgui_impl_glut.cpp ; GLUT/FreeGLUT (this is prehistoric software and absolutely not recommended today!) @@ -89,6 +90,7 @@ List of Renderer Backends: imgui_impl_dx11.cpp ; DirectX11 imgui_impl_dx12.cpp ; DirectX12 imgui_impl_metal.mm ; Metal (ObjC or C++) + imgui_impl_metal4.mm ; Metal 4 (ObjC or C++) imgui_impl_opengl2.cpp ; OpenGL 2 (legacy fixed pipeline. Don't use with modern OpenGL code!) imgui_impl_opengl3.cpp ; OpenGL 3/4, OpenGL ES 2/3, WebGL imgui_impl_sdlgpu3.cpp ; SDL_GPU (portable 3D graphics API of SDL3) @@ -116,7 +118,7 @@ If you are not sure which backend to use, the recommended platform/frameworks fo | GLFW | https://github.com/glfw/glfw | imgui_impl_glfw.cpp | | | Sokol | https://github.com/floooh/sokol | [util/sokol_imgui.h](https://github.com/floooh/sokol/blob/master/util/sokol_imgui.h) | Lower-level than GLFW/SDL | -If your application runs on Windows or if you are using multi-viewport, the win32 backend handles some details a little better than other backends. +If your application runs on Windows or if you are using multi-viewports, the imgui_impl_win32 backend handles some details better than other backends. ## Using third-party Backends @@ -233,9 +235,7 @@ void MyImGuiBackend_RenderDrawData(ImDrawData* draw_data) // - Scissor enabled MyEngineSetupenderState(); - // TODO: Setup texture sampling state - // - Sample with bilinear filtering (NOT point/nearest filtering). - // - Use 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines;' to allow point/nearest filtering. + // TODO: Setup texture sampler to bilinear (NOT point/nearest filtering). // TODO: Setup viewport covering draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize @@ -255,8 +255,8 @@ void MyImGuiBackend_RenderDrawData(ImDrawData* draw_data) const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; if (pcmd->UserCallback) { - if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) - MyEngineSetupenderState(); + if (pcmd->UserCallback == platform_io.DrawCallback_ResetRenderState) + MyEngineSetupSenderState(); else pcmd->UserCallback(cmd_list, pcmd); } diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index f63c073aa..30afc33be 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -35,6 +35,728 @@ HOW TO UPDATE? and API updates have been a little more frequent lately. They are documented below and in imgui.cpp and should not affect all users. - Please report any issue! +----------------------------------------------------------------------- + VERSION 1.93.0 WIP (In Progress) +----------------------------------------------------------------------- + +Breaking Changes: + +- Style: obsoleted `style.CurveTessellationTol (default 1.25)` which was in Pixels² unit in + favor of `style.CurveTessellationMaxError` (default 1.12)` which is in Pixels unit. It is + easier to tweak + this allows us to easily scale error threshold on high density screens. + - style.CurveTessellationMaxError == sqrf(style.CurveTessellationTol). +- ImGuiTextFilter: removed `float width` parameter of `Draw(const char* filter, float width)`: + prefer using `SetNextItemWidth(float)` which is standard. Kept inline redirection function. +- Backends: + - DirectX12: Removed support for legacy `ImGui_ImplDX12_Init()` signature, which was + obsoleted in 1.91.6 (December 2014), because it needed to forcefully disable support + for `ImGuiBackendFlags_RendererHasTextures`. Use the new signature! (#9487) + +Other Changes: + +- Drags, Sliders: + - Fixed an overflow using `ImGuiSliderFlags_Logarithmic` with S32/S64 types which + leads to clamped interactions. (#9526, #3361, #1823, #1316, #642) [@lailoken] + - Ongoing drags may be cancelled using Right-Click, Escape or Gamepad Triangle. + Cancelling reverts to the initial value. (#8564, #9534) +- TreeNode: + - Fixed issues/asserts with 32+ deep trees when using ImGuiTreeNodeFlags_DrawLinesXXX + features or other features requiring stack storage. (#9509) [@lasrod] +- TabBar: + - Improved scroll target when clicking a Tab that is larger than the scrolling + section width. + - Using <> scrolling buttons while the selected Tab is at either end of the + section mid-section but not fully visible always makes it fully visible + (before eventually selecting a Tab in the leading/trailing section) +- Tables: + - Fixed a bug restoring correct .ini columns order when table use is Sortable + but had no user changes. (regression from 1.92.9). (#9519, #9108) +- InpuText: + - Fixed a crash in some cases when using Home key in a WordWrap+ReadOnly field. (#9561) +- Selectable: + - Fixed `ImGuiSelectableFlags_SelectOnNav` triggering during NavInit. +- Fonts: + - Reworked `AddFontDefault()` to use `io.DisplayFrameBufferScale` as part of the heuristic + to select `AddFontDefaultVector()` by default, effectively using ProggyForever instead of + ProggyClean on most Apple/Retina setups by default. + - imgui_freetype: Fixed blurryness rendering `AddFontDefaultVector()`. Caused by a + non-pixel aligned offset caused by `ExtraSizeScale`. (#9348, #9233) [@redpartizan, @RDMCz] +- ColorEdit: + - ColorButton, ColorEdit, ColorPicker: cancel-out alpha caused by BeginDisabled() so + disabled color buttons have the same color as non-disabled oness. (#9511) + - ColorButton: fixed issues/inconsistencies with checkerboard rendering when combining + color alpha and global style alpha. (#9511) [@enjmiah, @ocornut] +- Popups: + - Amended context menu right-click opening code to check that the right-click + hasn't been owned by another items. This is more correct and also necessary + to avoid Drags/Sliders cancelling also triggering a cancel menu. (#8564, #9534) + - Reworked `GetMousePosOnOpeningCurrentPopup()` to be legal to call within the + BeginPopup/EndPopup scope of a popup which was just closed. (#9551) [@jkunstwald] +- ImGuiTextFilter: + - Added support for space as an "and" filter, which is the standard. (#2435, #30) + e.g. w/ filter 'hello world' (without quotes) both words needs to be in input data. + - Added support for double-quoted "xxx" blocks to e.g. match words sequences. (#2435) + e.g. w/ filter "hello world" the exact sequence needs to be in input data. + - Fixed filtering when mixing "include" and "-exclude" keyword so that an "-exclude" + match always filters out. (#2435) [@kudaba, @ocornut] + - Added `DrawWithHint()` function. (#6206, #6395, #6447) + - Added a "incl -excl" hint by default when the search field is empty. (#6206, #6395, #6447) +- Style: + - Inverted `ImGuiCol_TitleBgXXX` colors for `StyleColorsLight()` so that the selected + window title bar appears brighter. +- Misc: + - Added `ImGuiItemFlags_MixedValue` flag for representing mixed/indeterminate values. + Supported by: Checkbox(), RadioButton(), DragXXX(), SliderXXX(), InputXXX(), ColorXXX() + and Combo() functions. + The scoped flag is designed for usage by advanced property editors. (#5518, #5677, #6865) + - Fixed `GetBackgroundDrawList()`/`GetForegroundDrawList()` not being properly reset + every frame when cumulated session time is very large (e.g. a few days). [@lailoken] + - Added `IM_NODEBUGSTEP` helper to tag functions with `__declspec(non_user_code)` (MSVC) + or `[[gnu::artificial]]` (Clang/GCC) in order to provide a hint to debuggers to skip + trivial functions when stepping into. + - Tagged various trivial functions using `IM_NODEBUGSTEP`: `ImVec2()`, `ImVec4()` etc. + and various operators. + - Fixed strict aliasing violation in `ImHashData()` affecting Clang 23 + SSE 4.2 + -flto. + Could manifest as `ImFontAtlasBakedGetId()` computation omitting font size from + the hashed data. (#9557, #9560) [@sthalik] + - IO: restore default clipboard/ime/shell platform handlers on ClearPlatformHandlers(), fixing + issues switching backends mid-session to one relying on default. (#9537, #8945, #2769) [@lstalmir] +- DrawList: + - Per-viewport FramebufferScale for Apple/Retina screen is applied to draw lists: + - AA Fringe is scaled accordingly. + - Circle and Curves tessellation error are scaled accordingly. + - Reworked assert in PushTexture() to allow convenience grace period of using a + texture that was queue for destroy during the frame. Make it safe to stick to + an existing texture during the frame. (#9528, #8465) + - Added optional `platform_io.DrawCallback_SetSamplerFromTex` standard backend-agnostic + draw callback for requesting to use the sampler associated already associated to a texture. + Currently only supported by the OpenGL2/3 and SDLRenderer3 backend. (#9378) + - Fixed dangling ImDrawList::_OwnerName when using animated label. (#9552) [@ubi-dcastonguay] +- Demo: + - Added `Widgets->Mixed Values` section. (#5518, #5677, #6865) + - Style Editor: fixed style/colors combo box applying style when opening Combo. +- Backends: + - QNX: added QNX Screen backend. (#9492) [@mgorchak-blackberry] + - OpenGL2, OpenGL3: added support for `platform_io.DrawCallback_SetSamplerFromTex` draw + callback to request using the sampler associated already associated to a texture. (#9378) + - OpenGL2, OpenGL3: fixed `DrawCallback_ResetRenderState` handler not reseting all states + in the glTexParameter() path. (#9378) + - SDL2: fixed querying framebuffer scale/density when using Metal without + going through a SDL_Renderer. (#5592) [@lailoken] + - SDLRenderer3: restore any temporarily modified texture scale modes at the of `RenderDrawData()`. (#9378) + - SDLRenderer3: expose CurrentScaleMode in ImGui_ImplSDLRenderer3_RenderState. (#9543, #9378) [@Clownacy] + - SDLRenderer3: added support for `platform_io.DrawCallback_SetSamplerFromTex` draw callback to request + using the sampler associated already associated to a texture via `SDL_SetTextureScaleMode()`. (#9378) + It is equivalent to setting `render_state->CurrentScaleMode = SDL_SCALEMODE_INVALID`. + Note: this callback is only supported by selected rendering backends: most other graphics API + don't have the equivalent of `SDL_SetTextureScaleMode()` and the callback won't work with them. + - Vulkan: added for support for multiple Vulkan contexts with custom loaders. (#6616) [@lstalmir] + - WebGPU: fixed passing non-integer sizes to `wgpuRenderPassEncoderSetViewport()` + when `FramebufferScale` is >1.0f. (#9515, #8628) [@WayU] + - WebGPU: update to build with wgpu-native 29.0+. (#9377, #9532, #9530) [@lucascompython, @ivan-enzhaev] +- Examples: + - MSVC: disabled /JMC in Visual Studio projects (unnecessary overhead). + - GLFW/SDL2/SDL3+WebGPU: fixed running with wgpu-native missing `wgpuInstanceWaitAny()`. (#9377) + - SDL2+Metal: create Metal context without using a `SDL_Renderer`. + - QNX+OpenGL3, QNX+Vulkan: added QNX Screen examples. (#9492) [@mgorchak-blackberry] + - OSX+OpenGL3: added example_apple_opengl3/ (OSX/Cocoa + OpenGL 3.2 Core profile). + + +----------------------------------------------------------------------- + VERSION 1.92.9b (Released 2026-07-31) +----------------------------------------------------------------------- + +Decorated log and release notes: https://github.com/ocornut/imgui/releases/tag/v1.92.9b + +Changes: + +- DragXXX, SliderXXX: fixed a regression in 1.92.9 where clicking a previously + Ctrl+Clicked field would flick it to text edit mode for a frame. (#9476, #701) +- Tables: + - Fixed Y2 position of lower horizontal border (ImGuiTableFlags_BordersOuterH) + being off by one pixel when vertical ones are not also enabled. [@thedmd] + - Fixed X position of angled borders being off by half a pixel. [@thedmd] +- ImDrawData: fixed a regression in 1.92.9 where legacy `ImDrawData::CmdListsCount` was always 0. + Prefer using `ImDrawData::CmdList.Size` anyway. + + +----------------------------------------------------------------------- + VERSION 1.92.9 (Released 2026-07-25) +----------------------------------------------------------------------- + +Decorated log and release notes: https://github.com/ocornut/imgui/releases/tag/v1.92.9 + +Breaking Changes: + +- DragXXX, SliderXXX, InputScalar: with `ImGuiItemFlags_LiveEditOnInputScalar` now + defaulting to being disabled: inputting a value with the keyboard doesn't write + intermediate values to the backing variable. (#9476) + Before: DragFloat() with user typing "123" --> write back 1, then 12, then 123. + After: DragFloat() with user typing "123" --> write back 123 when validated/unfocused. + You can enable the flag back for a given scope if needed by specific widget/behavior. + Read below for details. +- TreeNode: commented out legacy name `ImGuiTreeNodeFlags_SpanTextWidth` which + was obsoleted in 1.90.7 (May 2024). Use `ImGuiTreeNodeFlags_SpanLabelWidth`. +- ColorEdit: obsoleted SetColorEditOptions() function added in 1.51 (June 2017) in favor + of directly poking to io.ConfigColorEditFlags. More consistent and easier to discover. +- Drag and Drop: commented out legacy name `ImGuiDragDropFlags_SourceAutoExpirePayload` + which obsoleted in 1.90.9 (July 2024). Use `ImGuiDragDropFlags_PayloadAutoExpire`. +- ImDrawData: marked `CmdListsCount` as obsolete (technically was obsoleted in 1.89.8). + Before: `draw_data->CmdListsCount`, After: `draw_data->CmdLists.Size`. + +Other Changes: + +- LiveEdit on/off: added `ImGuiItemFlags_LiveEditOnInputText` and `ImGuiItemFlags_LiveEditOnInputScalar` + flags to configure the timing of applying edits of backing variables when typing + values using a keyboard. + (#9476, #701, #3936, #3946, #5904, #6284, #8149, #8065, #8665, #9117, #9299, #700, #1351, + #1875, #2060, #2215, #2380, #2550, #3083, #3338, #3556, #4373, #4714, #4885, #5184, + #5777, #6707, #6766, #8004, #8303, #8915, #9308) + - ImGuiItemFlags_LiveEditOnInputText is enabled by default (same as before): + - The expectation is that for strings/text, enabling LiveEdit is a better default. + - ImGuiItemFlags_LiveEditOnInputScalar is disabled by default (new behavior): + - The expectation is that for numeric/scalars values, disabling LiveEdit is a better default. + - Until now: + - Edits where always applied immediately to backing variable, which is equivalent + to the `ImGuiItemFlags_LiveEditXXX` flags being enabled. + - Typing '123' in an integer field would output successively 1, 12 then 123. + - Most uses of `IsItemDeactivatedAfterEdit()` or `ImGuiInputTextFlags_EnterReturnsTrue` + were actually workarounds for this issue. Advanced applications would typically + use `IsItemDeactivatedAfterEdit()` to distinguish transactions. + Workarounds often required a backing store for scalar values, and there were + also a few niggles related to `IsItemDeactivatedAfterEdit()` when using +/- + buttons of an `InputInt()` widgets. + - Many of those situations can now be naturally simplified when disabling + `ImGuiItemFlags_LiveEditOnInputScalar` is disabled. + - The new flags allows disabling this behavior selectively for strings fields + such as `InputText()` vs scalar fields: `SliderInt()`, `InputFloat()`, etc. + - When LiveEdit is disabled, edits are applied when pressing enter, tabbing out, + clearing a field or deactivating due to a focus loss. + - The flag may be altered programmatically, e.g. for the entire frame scope: + NewFrame(); + PushItemFlag(ImGuiItemFlags_LiveEditOnInputScalar, true); // Enable for scalars for the whole frame + .... + PopItemFlag(); + EndFrame(); + Or for a specific widget: + PushItemFlag(ImGuiItemFlags_LiveEditOnInput, true); // Enable for one widget + SliderInt(...); + PopItemFlag(); + - We intentionally are not adding `io.ConfigLiveEditXXX` fields to dictate the + default value of each `ImGuiItemFlags_LiveEditXXX`, because this is not expected + to be a user preference but a programmer/widget preferences. + And we strive to make the toolkit consistent. +- Windows: + - Clicking on a window's empty-space to move/focus a window checks + for lack of mouse button ownership. This gives an additional opportunity + for user code to bypass it without using a clickable item. (#9382) + - Clicking on a window's empty-space to move/focus a window checks + for lack of queued focus request. (#9382) + - Fixed double-click collapse toggle not owning the mouse button. If a + `SetNextWindowPos()` with pivot was queued in the same frame, the second + click could trigger another item in the same window. (#9439) [@Cleroth] +- Popups: + - Added bool return value to `OpenPopup()`, `OpenPopupOnItemClick()` functions + to notify when the popup was just opened. (#9429) +- InputText: + - Added `style.InputTextCursorSize` to configure cursor/caret thickness. (#7031, #9409) + It is automatically scaled by `style.ScaleAllSizes()`. + - Reworked `io.ConfigInputTextEnterKeepActive` mode so that pressing + Ctrl+Enter or Shift+Enter still allows to deactivate. (#9239) +- Tables: + - Redesigned/rewrote code to reconcile columns and settings on topology changes. (#9108) + - When a column label is passed to TableSetupColumn(), the underlying identifier + is used to match live columns data and .ini settings data when changing. + This makes it possible to add/remove columns from a table without losing + neither live data neither .ini settings data. + - PS: Note that this is distinct from toggling column visibility or reordering + columns, which was always possible. The new matching makes it easier to create + tables that are entirely customized by user or code, without losing state. + - Columns without identifiers or with duplicate identifiers are matched + sequentially, matching old behavior. + - Column ID are stored in .ini file. + - Code is being tested both for live topology changes and for loading .ini + data with mismatched topology. + - Context Menu: added a "Reset" sub-menu with a "Reset Visibility" option. + (which is greyed out when using default settings) + - Headers: fixed label being clipped early to reserve space for a sort marker + even when no sort marker is displayed. Auto-fitting a column still accounts + for the possible marker, so that sorting after an auto-fit doesn't clip the label. +- Multi-Select: + - Reworked `ImGuiMultiSelectFlags_NoAutoSelect` as it carried side-effects that + were hardcoded/designed to use multi-selection on checkboxes. (#9391) + Specifically removed those undocumented behaviors from `_NoAutoSelect`: + - Clicking a selected/checked item always unselect/uncheck. + - Shift+Click inverts targets value and copy to range. + - Shift+Keyboard copy selection source value to range. + Those behaviors are still happening on checkboxes used within multi-selection. +- Fonts: + - Added `IMGUI_DISABLE_DEFAULT_FONT_BITMAP`/`IMGUI_DISABLE_DEFAULT_FONT_VECTOR` to + disable embedding either fonts separately. (#9407) + - Tweak `CalcTextSize()` awkward width rounding/ceiling code to reduce floating-point + precision issues altering the result by 1 even at relatively small width. (#791) + - Better document the fact that `ImFontAtlas::Clear()`/`ClearFonts()` functions are + unlikely to be useful nowadays. Better recover to an edge case of mistakenly + calling `ClearFonts()` during rendering. + - Fixed an issue where passing a manually created ImFontAtlas to CreateContext() would + incorrectly destroy it in `DestroyContext()` when ref-count gets back to zero. (#9426) + - Destroying an ImGui context using a `ImFontAtlas` checks that the later has no references. +- Nav: + - Fixed context menu activation with gamepad erroneously testing for _NavEnableKeyboard + instead of _NavEnableGamepad. (#9454, #8803, #9270) [@Clownacy] +- TreeNode: + - Fixed nav cursor rendering with rounding even though tree nodes don't have it. (#7589) +- Inputs: + - Added `GetItemClickedCountWithSingleClickDelay()` helper for easy disambiguation + between single-click and double-click for actions that needs single-click to do + something other than selection. (#8337) + - Returns 1 on single-click but delayed by io.MouseSingleClickDelay. + - Returns 2 on double-click, and 2+ on subsequent repeated clicks. + - Added `io.MouseSingleClickDelay` to configure default delayed single click delay when + using `GetItemClickedCountWithSingleClickDelay()` or `IsMouseReleasedWithDelay()`. (#8337) + Note that `io.MouseSingleClickDelay` is always `> io.MouseDoubleClickTime`. +- ColorEdit: + - Added `io.ConfigColorEditFlags` to read/modify current color edit/picker settings. + - ColorPicker: added `ImGuiColorEditFlags_PickerNoRotate` to disable rotation of the + S/V triangle when in `ImGuiColorEditFlags_PickerHueWheel` mode. (#9337) [@SeanTheBuilder1] + Likely best passed to `style.ColorEditFlags` to setup globally and once. + - ColorButton: small rendering tweak/optimization for the alpha checkerboard. +- Style: + - Added style.MenuItemRounding, ImGuiStyleVar_MenuItemRounding. (#7589, #9375, #9453) + - Added style.SelectableRounding, ImGuiStyleVar_SelectableRounding. (#7589, #9375, #9453) + The use of this is discouraged because it can easily create problems rendering e.g. + contiguous selection. + - Scale the NavCursor border thickness when using large values with `ScallAllSizes()`. +- Settings: + - Windows/Tables settings entries can now record the last used date in YYYYMMDD format, + allowing tools to run to e.g. delete entries that haven't been used in X months. (#9460) + - Added `bool io.ConfigIniSettingsSaveLastUsedDate` to disable saving that info. (#9460) + - Added `int io.ConfigIniSettingsAutoDiscardMonths` to enable a mode where unused settings + are automatically discard after xx months. (#9460) + - Added a trimming tool under Metrics->Settings, along with a yet-unexposed function. + - The current system date is fed through `ImGuiPlatformIO::Platform_SessionDate`, + which is automatically set by a call to time() done during context creation. (#9460) + - Added `IMGUI_DISABLE_TIME_FUNCTIONS` to disable setting platform_io.Platform_SessionDate. + A custom backend may still set it manually. (#9460) +- DrawList: + - Minor optimization to `AddLine()`, `AddLineH()`, `AddLineV()` functions. (#4091) + - Added `ImDrawListFlags_TextNoPixelSnap` to disable snapping of AddText() + coordinates for a given scope. (#3437, #9417, #2291) +- Demo: + - Extract 'Widgets->Tree Nodes->Selectable Nodes' out of the 'Advanced' + demo for clarity (manual reimplementation of basic selection). +- Debug Tools: + - Added `IM_DEBUG_BREAK()` handler for GCC+AArch64/ARM64. [@tom-seddon] +- Backends: + - Android: + - Clear mouse position on touch release (`AMOTION_EVENT_ACTION_UP`) to prevent + items from staying in hovered state. (#6627, #9474) [@Turtle-PB] + - Metal4: + - Added new Metal 4 backend (forked from Metal 3 backend). (#9458, #9451) [@AmelieHeinrich] + - Added Metal-cpp support enabled with `IMGUI_IMPL_METAL_CPP` define. (#9461) [@MERL10N] + - OpenGL2: + - Backup and restore `GL_UNPACK_ROW_LENGTH` and `GL_UNPACK_ALIGNMENT` when updating texture + to avoid altering caller GL state. (#8802, #9473) [@Turtle-PB] + - OpenGL3: + - GLSL version detection assume GLSL 410 when GL context is 4.1. + Fixes an issue running on macOS with Wine. [#9427, #6577) [@perminovVS] + - Expose selected render state in `ImGui_ImplOpenGL3_RenderState`, allowing to + dynamically select between use of `glBindSampler()` and `glTexParameter()`. (#9378) + - Backup and restore `GL_UNPACK_ROW_LENGTH` and `GL_UNPACK_ALIGNMENT` when updating texture + to avoid altering caller GL state. (#8802, #9473) [@Turtle-PB] + - SDL2: + - Restore SDL_StartTextInput()/SDL_StopTextInput() in IME handler for on-screen keyboard + support on Android. (#7636, #9474) [@Turtle-PB] + - SDLRenderer3: + - Fixed sampler change which didn't work on all graphics backends. (#7616, #9470, #9378) + - Fixed default sampler not being Linear. Regression in 1.92.8. (#7616, #9470, #9378) [@ShiroKSH] + - Win32: + - Uses `SetProcessDpiAwarenessContext()` instead of `SetThreadDpiAwarenessContext()` + when available, fixing OpenGL DPI scaling issues as e.g. NVIDIA drivers tends + to spawn multiple-thread to manage OpenGL. (#9403) +- Examples: + - Android: update to AGP 9.2.0 to support Gradle 9.6.0. + - Apple+Metal4: added new example. (#9465, #9451) [@hoffstadt] + - OpenGL3+GLFW/SDL2/SDL3: allow Wine compatibility by passing empty GLSL version + string to ImGui_ImplOpenGL3_Init() to let backend decide of a GLSL version based + on actual GL version obtained. (#9427, #6577) [@perminovVS] + - OpenGL3+Win32: rework context creation to allow Wine compatibility. (#9427, #6577) [@perminovVS] + - SDL2/SDL3: use `SDL_GetWindowSizeInPixels()` to create frame-buffers. Fixes issues + with non-fractional framebuffer size on Wayland. (#8761, #9124) [@billtran1632001] + - SDL3+Metal4: added new example. (#9458, #9451) [@AmelieHeinrich] + + +----------------------------------------------------------------------- + VERSION 1.92.8 (Released 2026-05-12) +----------------------------------------------------------------------- + +Decorated log and release notes: https://github.com/ocornut/imgui/releases/tag/v1.92.8 + +Breaking Changes: + +- DrawList: swapped the last two arguments of `AddRect()`, `AddPolyline()`, `PathStroke()`. + - Before: `void ImDrawList::AddRect(ImVec2 p_min, ImVec2 p_max, ImU32 col, float rounding = 0.0f, ImDrawFlags flags = 0, float thickness = 1.0f);` + - After: `void ImDrawList::AddRect(ImVec2 p_min, ImVec2 p_max, ImU32 col, float rounding = 0.0f, float thickness = 1.0f, ImDrawFlags flags = 0);` + - Before: `void ImDrawList::AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness);` + - After: `void ImDrawList::AddPolyline(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags = 0);` + - Before: `void ImDrawList::PathStroke(ImU32 col, ImDrawFlags flags = 0, float thickness = 1.0f);` + - After: `void ImDrawList::PathStroke(ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0);` + Added inline redirection functions when IMGUI_DISABLE_OBSOLETE_FUNCTIONS is off. + Marked the old functions are =delete when IMGUI_DISABLE_OBSOLETE_FUNCTIONS is on, to allow for better type-checking. + Effectively the typical call site is changing from: + - Before: `window->DrawList->AddRect(p_min, p_max, color, rounding, ImDrawFlags_None, border_size);` + - After: `window->DrawList->AddRect(p_min, p_max, color, rounding, border_size);` + Notes: + - Users of C++ and other languages with type-checking will be notified at compile-time of any mistakes. + - Users of high-level bindings or languages with no type-checking will be notified at runtime via an assert for invalid flags value. + If you are a binding maintainer consider doing something to facilitate transition or error detection. + - This is perhaps the worst breaking change in our history :( but it makes ImDrawList function signatures consistent. + As we are aiming to add flags and features to variety of ImDrawList functions, that consistency becomes more important. + The new order is also more convenient as `flags` are less frequently used than `thickness` in real code. + - As a general policy in Dear ImGui, all our flags default to 0 so ImDrawFlags_None was likely written 0 in some call sites. + - Consider adding `#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS` in your imconfig.h, even temporarily, to clean up legacy code. + - Reminder: you do NOT need to specify `ImDrawFlags_RoundCornersAll` for rounding, it is already the default! +- DrawList: obsoleted `ImDrawCallback_ResetRenderState` in favor of using `ImGui::GetPlatformIO().DrawCallback_ResetRenderState`, + which is part of our new standard draw callbacks. (#9378) + Redirecting the earlier value into the later one when set, so both old and new code should work. +- DrawList: changed value of `ImDrawFlags_Closed`. It was previously advertised as "always == 1" when introduced + in 1.82 (2021/02), in order to facilitate backward compatibility with the legacy `bool closed` flag. This + guarantee has been removed. The bit is reserved, and `AddPolyline()`, `PathStroke()` will assert when it is used. +- Backends: + - Vulkan: redesigned to use separate ImageView + Sampler instead of Combined Image Sampler. (#914) + This change allows us to facilitate changing samplers, in line with other backends. [@yaz0r, @ocornut] + - When creating your own descriptor pool (instead of letting backend creates its own): + - Before: need at least `IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE` descriptors of type `VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER`. + - After: need at least `IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE` descriptors of type `VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE`. + + `IMGUI_IMPL_VULKAN_MINIMUM_SAMPLER_POOL_SIZE` descriptors of type `VK_DESCRIPTOR_TYPE_SAMPLER`. + - When registering custom textures: changed ImGui_ImplVulkan_AddTexture() signature to remove Sampler. + - Before: `ImGui_ImplVulkan_AddTexture(VkSampler, VkImageView, VkImageLayout)` + - After: `ImGui_ImplVulkan_AddTexture(VkImageView, VkImageLayout)` + - Kept inline redirection function that ignores the sampler (will obsolete). + - DirectX10, DirectX11, SDLGPU3, Vulkan: removed samplers from `ImGui_ImplXXXX_RenderState`. + Prefer to use backend-agnostic DrawCallback_SetSamplerLinear which works everywhere! (#9378) + If there is a legit need/request for them or any render state we can always add them back. + - OpenGL3: the new sampler support system prioritize using glBindSampler() when available, + which would override glTexParameter() settings you may have set on custom textures. (#9378) + +Other Changes: + +- DrawList: + - Added room in `ImGuiPlatformIO` for standard backend-agnostic draw callbacks. Those callbacks + are setup/provided by the backend and available in most of our standard backends. + They allow backend-agnostic code from e.g. switching to a Nearest/Point sampler without + messing with custom Renderer-specific callbacks. + platform_io.DrawCallback_ResetRenderState; // Request to reset the graphics/render state. + platform_io.DrawCallback_SetSamplerLinear; // Request to set current texture sampling to Linear + platform_io.DrawCallback_SetSamplerNearest; // Request to set current texture sampling to Nearest/Point + Note that some backends might not support all callbacks. + (#9378, #9371, #3590, #8926, #2973, #7485, #7468, #6969, #5118, #7616, #9173, #8322, #7230, + #5999, #6452, #5156, #7342, #7592, #7511) + - Made `AddCallback()` user data default to Null for convenience. + - Added `AddLineH()`, `AddLineV()` helpers to draw horizontal and vertical lines. [@memononen] + The new functions are more optimal and will be part of a larger effort in 1.93 to fix + inconsistencies and improve the DrawList API. In the current API, `AddLine()` adds a "magic" +0.5f + offset and then center the line, whereas AddLineH()/AddLineV() use the right side of the line to expand. + For thickness=1.0f lines this is equivalent: + `AddLine({10,3}, {20,3}, ...)` --> `AddLineH(x1=10, x2=20, y=3, ...)`. + `AddLine({3,10}, {3,20}, ...)` --> `AddLineV(x=3, y1=10, y2=20, ...)`. + For larger integer thickness, AddLine() will center but may be blurry, AddLineH()/AddLineV() + will expand "inside" (to the right side) and will always be sharp. + A subsequent release will expand on those concepts and add new options. +- InputText: + - InputTextMultiline: fixed an issue processing deactivation logic when an active + multi-line edit is clipped due to being out of view. + - Fixed a crash when toggling ReadOnly while active. (#9354) + - `CharFilter` callback event sets CursorPos/SelectionStart/SelectionEnd. (#816) +- Tables: + - Fixed issues reporting ideal size to parent window/container: (#9352, #7651) + - When both scrollbars are visible but only one of ScrollX/ScrollY was explicitly requested. + - When vertical scrollbar was not at the top, the computation was often incorrect. +- Windows: + - Fixed a single-axis auto-resizing feedback loop issue with nested containers + and varying scrollbar visibility. (#9352) + - Detect and report error when calling End() instead of EndPopup() on a popup. (#9351) + - Child windows with only `ImGuiChildFlags_AutoResizeY` flag keep using the proportional + default `ItemWidth`. (#9355) + - Using mouse wheel to scroll takes and keeps ownership of the corresponding keys + (e.g. `ImGuiKey_MouseWheelY`) while a wheeling window is locked. (#2604, #3795) +- InputInt, InputFloat, InputScalar: + - Reinstated `ImGuiInputTextFlags_EnterReturnsTrue` support which was removed in 1.91.4. + (#8665, #9299, #8065, #3946, #6284, #9117) + - Fixed the fact that it didn't return true when validating same value. + - Fixed losing value when tabbing out or losing focus. + - Made it that pressing +/- step buttons also return true, which is in line + with 1.91.4 behavior. + - In a majority of cases you should use `IsItemDeactivatedAfterEdit()` instead, + but it still has a few edge cases flaws (to be addressed soon). + - Allow passing a format string that does not display the scalar value. + Parsing input with default format for the type. (#9385) [@FireFox2000000] +- Multi-Select: + - Fixed an issue using Multi-Select within a Table causing column width measurement to + be invalid when trailing column contents is not submitted in the last row. (#9341, #8250) + - Fixed an issue using Multi-Select within a Table with the right-most column visible, + which could lead to an extra vertical offset in the Header row. (#8250) +- Multi-Select + Box-Select: + - Fixed an issue using `ImGuiMultiSelectFlags_BoxSelect1d` mode while scrolling. + Notably, using mouse wheel while holding a box-selection could lead items close to windows + edges from not being correctly unselected. (#7994, #8250, #7821, #7850, #7970) + - Improved dirty/unclip rectangle logic for `ImGuiMultiSelectFlags_BoxSelect2d`. + - Fixed an issue using `ImGuiMultiSelectFlags_BoxSelect2d` mode, where + items out of view wouldn't be properly selected while scrolling while mouse cursor + is hovering outside of selection scope. (#7994, #1861, #6518) + - Fixed an issue where items out of horizontal view would sometimes lead + to incorrect merging of sequential selection requests while also scrolling fast + enough to overlap multiple rows during a frame. (#7994, #1861, #6518) + - Fixed an issue using `ImGuiMultiSelectFlags_BoxSelect2d` mode in a Table + while relying on the `TableNextColumn()` return value to perform coarse clipping. (#7994) + - Disabled merging consecutive selection requests as we have no reliable + way of detecting if user has submitted all consecutive items without clipping gaps, + and `ImGuiSelectionUserData` is technically opaque storage. (#7994, #1861) + (we will probably bring this back as a minor optimization if we have a way to for + user to tell us `ImGuiSelectionUserData` are indices) + - Fixes for using across nested child windows. (#8364) + - Box-Select + Clipper: fixed an issue selecting items while scrolling while a clipper + active. (#7994, #8250, #7821, #7850, #7970) + - Box-Select + Tables: fixed an issue using box-selection in a tables with + items straying out of columns boundaries. (#7994, #2221) + - Box-Select + Tables: fixed an issue when calling `BeginMultiSelect()` in a table + before layout has been locked (first row or headers row submitted). (#8250) +- Menus: + - BeginMenu()/MenuItem(): fixed accidental triggering of child menu items when + opening a menu inside a small host window forcing the child menu window to be + repositioned under the mouse cursor. (#8233, #9394) + Done by reworking `BeginMenu()`/`MenuItem()`: they previously avoiding taking + ActiveID + key/click ownership (in order to allow releasing button on another item). + Now they take them and release them once the mouse is moved outside item boundaries. +- Inputs: + - SetItemKeyOwner(): return true if ownership has been requested, which typically + needs to to checked for gating further tests. This is important as the function + may fail. (#456, #2637, #2620, #2891, #3370, #3724, #4828, #5108, #5242, #5641) + - SetItemKeyOwner(): does not set ownership is key is already taken. Effectively + this makes using `SetItemKeyOwner(ImGuiKey_MouseWheelY)` over an item work as + expected while not having item react if a scroll wheel is actively in progress. + May be subject to further redesign, e.g. conditional flags. Feedback welcome! +- Style: + - Checkbox: added `ImGuiCol_CheckboxSelectedBg` to change or accentuate the + background color of checked/mixed checkboxes. (#9392) + - Added `ImGuiStyleVar_DragDropTargetRounding`. (#9056) + - Fixed vertical scrollbar top coordinates when using thick borders on windows + with no title bar and no menu bar. (#9366) +- Fonts: + - imgui_freetype: add FreeType headers & compiled version in 'About Dear ImGui' details. + - Assert when using MergeMode with an explicit size over a target font with an implicit + size, as scale factor are likely erroneous. (#9361) +- Clipper: + - Improved error reporting when misusing the clipper inside a table (prioritize + reporting the common clipper error over a table sanity check assert). (#9350) + - Tweaked assert triggering when first item height measurement fails, and made it + a better recoverable error. (#9350) +- Misc: + - Minor optimization: reduce redundant label scanning in common widgets. + - Added missing Test Engine hooks for `PlotLines()`, `PlotHistogram()`, `VSliderXXX()` + and `TableHeader()` functions. +- Demo: + - Added simple demo for a scrollable/zoomable image viewer with a grid. + Available in `Examples->Image Viewer` and `Widgets->Images`. +- Backends: + - Added support for new standardized draw callbacks in most backends: (#9378) + - Allegro5: Reset n/a n/a + - DX9: Reset SetSamplerLinear SetSamplerNearest + - DX10: Reset SetSamplerLinear SetSamplerNearest + - DX11: Reset SetSamplerLinear SetSamplerNearest + - DX12: Reset SetSamplerLinear SetSamplerNearest + - Metal: Reset SetSamplerLinear SetSamplerNearest + - OpenGL2: Reset SetSamplerLinear SetSamplerNearest + - OpenGL3+: Reset SetSamplerLinear SetSamplerNearest + - SDLGPU3: Reset SetSamplerLinear SetSamplerNearest + - SDLRenderer2: Reset n/a n/a + - SDLRenderer3: Reset SetSamplerLinear SetSamplerNearest + - Vulkan: Reset SetSamplerLinear SetSamplerNearest + - WebGPU: Reset SetSamplerLinear SetSamplerNearest + (Vulkan backend by @yaz0r, Metal by @ssh4net, others by @ocornut) + - GLFW: added a Win32-specific implementation of `ImGui_ImplGlfw_GetContentScaleXXXX` + functions for legacy GLFW 3.2. (#9003) + - Metal: avoid redundant vertex buffer bind in `SetupRenderState()`, which leads + to validation issue. (#9343) [@Hunam6] + - Metal: use a dedicated `bufferCacheLock` to avoid crashing when `bufferCache` is + replaced by a new object while being used for `@synchronize()`. (#9367) [@andygrundman] + - SDL2: made `ImGui_ImplSDL2_GetContentScaleForWindow()`/`ImGui_ImplSDL2_GetContentScaleForDisplay()` + helpers return a minimum of 1.0f, as some Linux setup seems to report <1.0f value + and this breaks scaling border size. (#9369) + - WebGPU: rework choice/detection of using WGSL/SPIR-V shader on WGVK. (#9316, #9246, #9257, #9387) +- Examples: + - Update VS toolset in all .vcxproj from VS2015 (v140) to VS2017 (v141). The later is the + first that supports vcpkg. Onward we will likely stop testing building the library with VS2015. + - GLFW+Vulkan, SDL2+Vulkan, SDL3+Vulkan, Win32+Vulkan: reworked to create a descriptor pool with: + - `IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE` descriptors of type `VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE`. + - `IMGUI_IMPL_VULKAN_MINIMUM_SAMPLER_POOL_SIZE` descriptors of type `VK_DESCRIPTOR_TYPE_SAMPLER`. + + +----------------------------------------------------------------------- + VERSION 1.92.7 (Released 2026-04-02) +----------------------------------------------------------------------- + +Decorated log and release notes: https://github.com/ocornut/imgui/releases/tag/v1.92.7 + +Breaking Changes: + + - Separator: fixed a legacy quirk where `Separator()` was submitting a zero-height + item for layout purpose, even though it draws a 1-pixel separator. + The fix could affect code e.g. computing height from multiple widgets in order to + allocate vertical space for a footer or multi-line status bar. (#2657, #9263) + The "Console" example had such a bug: + float footer_height = style.ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); + BeginChild("ScrollingRegion", { 0, -footer_height }); + Should be: + float footer_height = style.ItemSpacing.y + style.SeparatorSize + ImGui::GetFrameHeightWithSpacing(); + BeginChild("ScrollingRegion", { 0, -footer_height }); + When such idiom was used and assuming zero-height Separator, it is likely that + in 1.92.7 the resulting window will have unexpected 1 pixel scrolling range. + - Multi-Select: renamed `ImGuiMultiSelectFlags_SelectOnClick` to `ImGuiMultiSelectFlags_SelectOnAuto`. + Kept inline redirection enum (will obsolete). + - Combo(), ListBox(): commented out legacy signatures which were obsoleted in 1.90 + (Nov 2023), when the getter callback type was changed from: + bool (*getter)(void* user_data, int idx, const char** out_text) + To: + const char* (*getter)(void* user_data, int idx) + +Other Changes: + +- TreeNode: + - Moved `TreeNodeGetOpen()` helper to public API. I was hesitant to make this public + because I intend to provide a more generic and feature-full version, but in the meanwhile + this will do. (#3823, #9251, #7553, #6754, #5423, #2958, #2079, #1947, #1131, #722) + - In 'Demo->Property Editor' demonstrate a way to perform tree clipping by fast-forwarding + through non-visible chunks. (#3823, #9251, #6990, #6042) + Using SetNextItemStorageID() + TreeNodeGetOpen() makes this notably easier than + it was prior to 1.91. +- InputText: + - Shift+Enter in multi-line editor always adds a new line, regardless of + `ImGuiInputTextFlags_CtrlEnterForNewLine` being set or not. (#9239) + - Reworked `io.ConfigInputTextEnterKeepActive` mode so that pressing Enter will + deactivate/reactivate the item in order for e.g. `IsItemDeactivatedAfterEdit()` + signals to be emitted the same way regardless of that setting. (#9001, #9115) + - Fixed a glitch when using `ImGuiInputTextFlags_ElideLeft` where the local x offset + would be incorrect during the deactivation frame. (#9298) + - Fixed a crash introduced in 1.92.6 when handling `ImGuiInputTextFlags_CallbackResize` + in certain situations. (#9174) + - Fixed selection highlight Y1 offset being very slightly off (since 1.92.3). (#9311) [@v-ein] + - InputTextMultiline: fixed an issue introduced in 1.92.3 where line count calculated + for vertical scrollbar range would be +1 when the widget is inactive, word-wrap is + disabled and the text buffer ends with '\n'. Fixed a similar issue related to clipping + large amount of text. + - InputTextMultiline: avoid going through reactivation code and fixed losing revert value + when activating scrollbar. + - InputTextMultiline: fixed an issue where edit buffer wouldn't be reapplied to back + buffer on the `IsItemDeactivatedAfterEdit()` frame. This could create issues when + using the idiom of not applying edits before `IsItemDeactivatedAfterEdit()`. + (#9308, #8915, #8273) +- Tables: + - Allow reordering columns by dragging them in the context menu. (#9312) + - Context menu now presents columns in display order. (#9312) + - Fixed and clarified the behavior of using `TableSetupScrollFreeze()` with columns>1, + and where some of the columns within that range were Hidable. + - Before: `TableSetupScrollFreeze(N, 0)`: include the N left-most visible columns as + part of the scroll freeze. So if you intentionally hide columns