From 1c87024840cfb0e945abb4048ca1fbb6d2aa260b Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 29 Sep 2025 13:38:27 +0200 Subject: [PATCH 01/10] InputText: fixed single-line InputText() not applying fine character clipping properly (regression in 1.92.3). (#8967) --- docs/CHANGELOG.txt | 2 ++ imgui_widgets.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 08337ee5c..d51981138 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -62,6 +62,8 @@ Other Changes: - Windows: added lower-right resize grip on child windows using both ImGuiChildFlags_ResizeX and ImGuiChildFlags_ResizeY flags. (#8501) [@aleksijuvani] The grip is not visible before hovering to reduce clutter. +- InputText: fixed single-line InputText() not applying fine character clipping + properly (regression in 1.92.3). (#8967) [@Cyphall] - IO: added ImGuiPlatformIO::ClearPlatformHandlers(), ClearRendererHandlers() helpers to null all handlers. (#8945, #2769) - Misc: Debuggers: added type formatters for the LLDB debuggers (e.g. Xcode, diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 4709af05b..592a76904 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -5525,7 +5525,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ text_col, clip_rect.AsVec4(), line_index->get_line_begin(buf_display, line_visible_n0), line_index->get_line_end(buf_display, line_visible_n1 - 1), - wrap_width, ImDrawTextFlags_WrapKeepBlanks); + wrap_width, ImDrawTextFlags_WrapKeepBlanks | ImDrawTextFlags_CpuFineClip); // Render blinking cursor if (render_cursor) From bad3c14ff6f6f42f130317eeea06e42457355244 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 29 Sep 2025 14:57:03 +0200 Subject: [PATCH 02/10] Backends: Vulkan: SwapChainImageUsage assume VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT which is added automatically. (#8946, #8110, #8111, #8686) Amend e4e3c2c. --- backends/imgui_impl_vulkan.cpp | 2 +- backends/imgui_impl_vulkan.h | 2 +- docs/CHANGELOG.txt | 8 ++++---- examples/example_glfw_vulkan/main.cpp | 5 ++--- examples/example_sdl2_vulkan/main.cpp | 5 ++--- examples/example_sdl3_vulkan/main.cpp | 5 ++--- examples/example_win32_vulkan/main.cpp | 5 ++--- 7 files changed, 14 insertions(+), 18 deletions(-) diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index 2624f5210..f20a9064a 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -1683,7 +1683,7 @@ void ImGui_ImplVulkanH_CreateWindowSwapChain(VkPhysicalDevice physical_device, V info.imageFormat = wd->SurfaceFormat.format; info.imageColorSpace = wd->SurfaceFormat.colorSpace; info.imageArrayLayers = 1; - info.imageUsage = (image_usage != 0) ? image_usage : VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | image_usage; info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; // Assume that graphics family == present family info.preTransform = (cap.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) ? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR : cap.currentTransform; info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; diff --git a/backends/imgui_impl_vulkan.h b/backends/imgui_impl_vulkan.h index 44b8b8bae..544f7f162 100644 --- a/backends/imgui_impl_vulkan.h +++ b/backends/imgui_impl_vulkan.h @@ -78,7 +78,7 @@ struct ImGui_ImplVulkan_PipelineInfo #endif // For Secondary viewports only (created/managed by backend) - VkImageUsageFlags SwapChainImageUsage; // 0 defaults to VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT. Add e.g. VK_IMAGE_USAGE_TRANSFER_SRC_BIT if you need to capture from viewports. + VkImageUsageFlags SwapChainImageUsage; // Extra flags for vkCreateSwapchainKHR() calls for secondary viewports. We automatically add VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT. You can add e.g. VK_IMAGE_USAGE_TRANSFER_SRC_BIT if you need to capture from viewports. }; // Initialization data, for ImGui_ImplVulkan_Init() diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index ece3cbdc0..0bea2cf2e 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -56,10 +56,10 @@ Breaking Changes: (introduced very recently and only used by `ImGui_ImplVulkan_CreateMainPipeline()` so it should not affect many users). (#8110, #8111) - Backends: Vulkan: helper ImGui_ImplVulkanH_CreateOrResizeWindow() added a - `VkImageUsageFlags image_usage` argument. - It was previously hardcoded to `VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT` and defaults - to that when the value is 0. In theory the function is an internal helper but - since it's used by our examples some may have used it. (#8946, #8111, #8686) + `VkImageUsageFlags image_usage` argument to specific extra flags for calls to + vkCreateSwapchainKHR() done for secondary viewports. We automatically add + `VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT`. In theory the function is an internal + helper but since it's used by our examples some may have used it. (#8946, #8111, #8686) Other Changes: diff --git a/examples/example_glfw_vulkan/main.cpp b/examples/example_glfw_vulkan/main.cpp index bf4f9c51f..01e47fdad 100644 --- a/examples/example_glfw_vulkan/main.cpp +++ b/examples/example_glfw_vulkan/main.cpp @@ -54,7 +54,6 @@ static VkDescriptorPool g_DescriptorPool = VK_NULL_HANDLE; static ImGui_ImplVulkanH_Window g_MainWindowData; static uint32_t g_MinImageCount = 2; static bool g_SwapChainRebuild = false; -static VkImageUsageFlags g_SwapChainImageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; static void glfw_error_callback(int error, const char* description) { @@ -240,7 +239,7 @@ static void SetupVulkanWindow(ImGui_ImplVulkanH_Window* wd, VkSurfaceKHR surface // Create SwapChain, RenderPass, Framebuffer, etc. IM_ASSERT(g_MinImageCount >= 2); - ImGui_ImplVulkanH_CreateOrResizeWindow(g_Instance, g_PhysicalDevice, g_Device, wd, g_QueueFamily, g_Allocator, width, height, g_MinImageCount, g_SwapChainImageUsage); + ImGui_ImplVulkanH_CreateOrResizeWindow(g_Instance, g_PhysicalDevice, g_Device, wd, g_QueueFamily, g_Allocator, width, height, g_MinImageCount, 0); } static void CleanupVulkan() @@ -470,7 +469,7 @@ int main(int, char**) if (fb_width > 0 && fb_height > 0 && (g_SwapChainRebuild || g_MainWindowData.Width != fb_width || g_MainWindowData.Height != fb_height)) { ImGui_ImplVulkan_SetMinImageCount(g_MinImageCount); - ImGui_ImplVulkanH_CreateOrResizeWindow(g_Instance, g_PhysicalDevice, g_Device, wd, g_QueueFamily, g_Allocator, fb_width, fb_height, g_MinImageCount, g_SwapChainImageUsage); + ImGui_ImplVulkanH_CreateOrResizeWindow(g_Instance, g_PhysicalDevice, g_Device, wd, g_QueueFamily, g_Allocator, fb_width, fb_height, g_MinImageCount, 0); g_MainWindowData.FrameIndex = 0; g_SwapChainRebuild = false; } diff --git a/examples/example_sdl2_vulkan/main.cpp b/examples/example_sdl2_vulkan/main.cpp index 352034026..52ebe6dd9 100644 --- a/examples/example_sdl2_vulkan/main.cpp +++ b/examples/example_sdl2_vulkan/main.cpp @@ -49,7 +49,6 @@ static VkDescriptorPool g_DescriptorPool = VK_NULL_HANDLE; static ImGui_ImplVulkanH_Window g_MainWindowData; static uint32_t g_MinImageCount = 2; static bool g_SwapChainRebuild = false; -static VkImageUsageFlags g_SwapChainImageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; static void check_vk_result(VkResult err) { @@ -231,7 +230,7 @@ static void SetupVulkanWindow(ImGui_ImplVulkanH_Window* wd, VkSurfaceKHR surface // Create SwapChain, RenderPass, Framebuffer, etc. IM_ASSERT(g_MinImageCount >= 2); - ImGui_ImplVulkanH_CreateOrResizeWindow(g_Instance, g_PhysicalDevice, g_Device, wd, g_QueueFamily, g_Allocator, width, height, g_MinImageCount, g_SwapChainImageUsage); + ImGui_ImplVulkanH_CreateOrResizeWindow(g_Instance, g_PhysicalDevice, g_Device, wd, g_QueueFamily, g_Allocator, width, height, g_MinImageCount, 0); } static void CleanupVulkan() @@ -490,7 +489,7 @@ int main(int, char**) if (fb_width > 0 && fb_height > 0 && (g_SwapChainRebuild || g_MainWindowData.Width != fb_width || g_MainWindowData.Height != fb_height)) { ImGui_ImplVulkan_SetMinImageCount(g_MinImageCount); - ImGui_ImplVulkanH_CreateOrResizeWindow(g_Instance, g_PhysicalDevice, g_Device, wd, g_QueueFamily, g_Allocator, fb_width, fb_height, g_MinImageCount, g_SwapChainImageUsage); + ImGui_ImplVulkanH_CreateOrResizeWindow(g_Instance, g_PhysicalDevice, g_Device, wd, g_QueueFamily, g_Allocator, fb_width, fb_height, g_MinImageCount, 0); g_MainWindowData.FrameIndex = 0; g_SwapChainRebuild = false; } diff --git a/examples/example_sdl3_vulkan/main.cpp b/examples/example_sdl3_vulkan/main.cpp index 02fd2a5b0..c2b7f793b 100644 --- a/examples/example_sdl3_vulkan/main.cpp +++ b/examples/example_sdl3_vulkan/main.cpp @@ -51,7 +51,6 @@ static VkDescriptorPool g_DescriptorPool = VK_NULL_HANDLE; static ImGui_ImplVulkanH_Window g_MainWindowData; static uint32_t g_MinImageCount = 2; static bool g_SwapChainRebuild = false; -static VkImageUsageFlags g_SwapChainImageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; static void check_vk_result(VkResult err) { @@ -233,7 +232,7 @@ static void SetupVulkanWindow(ImGui_ImplVulkanH_Window* wd, VkSurfaceKHR surface // Create SwapChain, RenderPass, Framebuffer, etc. IM_ASSERT(g_MinImageCount >= 2); - ImGui_ImplVulkanH_CreateOrResizeWindow(g_Instance, g_PhysicalDevice, g_Device, wd, g_QueueFamily, g_Allocator, width, height, g_MinImageCount, g_SwapChainImageUsage); + ImGui_ImplVulkanH_CreateOrResizeWindow(g_Instance, g_PhysicalDevice, g_Device, wd, g_QueueFamily, g_Allocator, width, height, g_MinImageCount, 0); } static void CleanupVulkan() @@ -492,7 +491,7 @@ int main(int, char**) if (fb_width > 0 && fb_height > 0 && (g_SwapChainRebuild || g_MainWindowData.Width != fb_width || g_MainWindowData.Height != fb_height)) { ImGui_ImplVulkan_SetMinImageCount(g_MinImageCount); - ImGui_ImplVulkanH_CreateOrResizeWindow(g_Instance, g_PhysicalDevice, g_Device, wd, g_QueueFamily, g_Allocator, fb_height, fb_height, g_MinImageCount, g_SwapChainImageUsage); + ImGui_ImplVulkanH_CreateOrResizeWindow(g_Instance, g_PhysicalDevice, g_Device, wd, g_QueueFamily, g_Allocator, fb_height, fb_height, g_MinImageCount, 0); g_MainWindowData.FrameIndex = 0; g_SwapChainRebuild = false; } diff --git a/examples/example_win32_vulkan/main.cpp b/examples/example_win32_vulkan/main.cpp index de50e8474..c8b455a47 100644 --- a/examples/example_win32_vulkan/main.cpp +++ b/examples/example_win32_vulkan/main.cpp @@ -47,7 +47,6 @@ static VkDescriptorPool g_DescriptorPool = VK_NULL_HANDLE; static ImGui_ImplVulkanH_Window g_MainWindowData; static uint32_t g_MinImageCount = 2; static bool g_SwapChainRebuild = false; -static VkImageUsageFlags g_SwapChainImageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; static void check_vk_result(VkResult err) { @@ -229,7 +228,7 @@ static void SetupVulkanWindow(ImGui_ImplVulkanH_Window* wd, VkSurfaceKHR surface // Create SwapChain, RenderPass, Framebuffer, etc. IM_ASSERT(g_MinImageCount >= 2); - ImGui_ImplVulkanH_CreateOrResizeWindow(g_Instance, g_PhysicalDevice, g_Device, wd, g_QueueFamily, g_Allocator, width, height, g_MinImageCount, g_SwapChainImageUsage); + ImGui_ImplVulkanH_CreateOrResizeWindow(g_Instance, g_PhysicalDevice, g_Device, wd, g_QueueFamily, g_Allocator, width, height, g_MinImageCount, 0); } static void CleanupVulkan() @@ -583,7 +582,7 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) if (fb_width > 0 && fb_height > 0 && (g_SwapChainRebuild || g_MainWindowData.Width != fb_width || g_MainWindowData.Height != fb_height)) { ImGui_ImplVulkan_SetMinImageCount(g_MinImageCount); - ImGui_ImplVulkanH_CreateOrResizeWindow(g_Instance, g_PhysicalDevice, g_Device, &g_MainWindowData, g_QueueFamily, g_Allocator, fb_width, fb_height, g_MinImageCount, g_SwapChainImageUsage); + ImGui_ImplVulkanH_CreateOrResizeWindow(g_Instance, g_PhysicalDevice, g_Device, &g_MainWindowData, g_QueueFamily, g_Allocator, fb_width, fb_height, g_MinImageCount, 0); g_MainWindowData.FrameIndex = 0; g_SwapChainRebuild = false; } From 1f020e526afaf0143104fe1acf66e27a12a94e40 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 29 Sep 2025 15:06:58 +0200 Subject: [PATCH 03/10] Backends: Vulkan: ImGui_ImplVulkan_CreatePipeline() for secondary viewport always use the RenderPass created by the ImGui_ImplVulkanH_CreateOrResizeWindow(). (#8946, #8110) --- backends/imgui_impl_vulkan.cpp | 4 +--- backends/imgui_impl_vulkan.h | 4 +++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index f20a9064a..95e7d06de 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -1322,8 +1322,6 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info) IM_ASSERT(info->DescriptorPoolSize > 0); if (info->UseDynamicRendering) IM_ASSERT(info->PipelineInfoMain.RenderPass == VK_NULL_HANDLE && info->PipelineInfoForViewports.RenderPass == VK_NULL_HANDLE); - else if (info->PipelineInfoForViewports.RenderPass == NULL) - info->PipelineInfoForViewports.RenderPass = info->PipelineInfoMain.RenderPass; bd->VulkanInitInfo = *info; @@ -2014,7 +2012,7 @@ static void ImGui_ImplVulkan_CreateWindow(ImGuiViewport* viewport) } else { - IM_ASSERT(pipeline_info->RenderPass != VK_NULL_HANDLE && "Did you set ImGui_ImplVulkan_InitInfo::PipelineInfoForViewports.RenderPass?"); // Since 1.92.4 it is required. + pipeline_info->RenderPass = wd->RenderPass; } #endif bd->PipelineForViewports = ImGui_ImplVulkan_CreatePipeline(v->Device, v->Allocator, VK_NULL_HANDLE, &v->PipelineInfoForViewports); diff --git a/backends/imgui_impl_vulkan.h b/backends/imgui_impl_vulkan.h index 544f7f162..ad89cb9e4 100644 --- a/backends/imgui_impl_vulkan.h +++ b/backends/imgui_impl_vulkan.h @@ -69,8 +69,10 @@ // Specify settings to create pipeline and swapchain struct ImGui_ImplVulkan_PipelineInfo { - // For Main and Secondary viewports + // For Main viewport only VkRenderPass RenderPass; // Ignored if using dynamic rendering + + // For Main and Secondary viewports uint32_t Subpass; // VkSampleCountFlagBits MSAASamples = {}; // 0 defaults to VK_SAMPLE_COUNT_1_BIT #ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING From 217bc445ff0e26b70ab300480e1efa525a977662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Tassoux?= Date: Sat, 27 Sep 2025 00:45:10 +0200 Subject: [PATCH 04/10] Backends: DirectX12: reuse a command list and allocator for texture uploads. (#8963, #8465) --- backends/imgui_impl_dx12.cpp | 31 ++++++++++++++++++------------- docs/CHANGELOG.txt | 2 ++ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/backends/imgui_impl_dx12.cpp b/backends/imgui_impl_dx12.cpp index 86f1180ab..4a29cf529 100644 --- a/backends/imgui_impl_dx12.cpp +++ b/backends/imgui_impl_dx12.cpp @@ -20,6 +20,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2025-09-27: DirectX12: Reuse a command list and allocator for texture uploads instead of recreating them each time. // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-06-19: Fixed build on MinGW. (#8702, #4594) // 2025-06-11: DirectX12: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. @@ -97,6 +98,9 @@ struct ImGui_ImplDX12_Data ID3D12DescriptorHeap* pd3dSrvDescHeap; UINT numFramesInFlight; + ID3D12CommandAllocator* pTexCmdAllocator; + ID3D12GraphicsCommandList* pTexCmdList; + ImGui_ImplDX12_RenderBuffers* pFrameResources; UINT frameIndex; @@ -448,13 +452,12 @@ void ImGui_ImplDX12_UpdateTexture(ImTextureData* tex) props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; props.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; - // FIXME-OPT: Can upload buffer be reused? + // FIXME-OPT: Could upload buffer be kept around, reused, and grown only when needed? Would that be worth it? ID3D12Resource* uploadBuffer = nullptr; HRESULT hr = bd->pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&uploadBuffer)); IM_ASSERT(SUCCEEDED(hr)); - // Create temporary command list and execute immediately ID3D12Fence* fence = nullptr; hr = bd->pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)); IM_ASSERT(SUCCEEDED(hr)); @@ -462,15 +465,9 @@ void ImGui_ImplDX12_UpdateTexture(ImTextureData* tex) HANDLE event = ::CreateEvent(0, 0, 0, 0); IM_ASSERT(event != nullptr); - // FIXME-OPT: Create once and reuse? - ID3D12CommandAllocator* cmdAlloc = nullptr; - hr = bd->pd3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&cmdAlloc)); - IM_ASSERT(SUCCEEDED(hr)); - - // FIXME-OPT: Can be use the one from user? (pass ID3D12GraphicsCommandList* to ImGui_ImplDX12_UpdateTextures) - ID3D12GraphicsCommandList* cmdList = nullptr; - hr = bd->pd3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, cmdAlloc, nullptr, IID_PPV_ARGS(&cmdList)); - IM_ASSERT(SUCCEEDED(hr)); + bd->pTexCmdAllocator->Reset(); + bd->pTexCmdList->Reset(bd->pTexCmdAllocator, nullptr); + ID3D12GraphicsCommandList* cmdList = bd->pTexCmdList; // Copy to upload buffer void* mapped = nullptr; @@ -535,8 +532,6 @@ void ImGui_ImplDX12_UpdateTexture(ImTextureData* tex) fence->SetEventOnCompletion(1, event); ::WaitForSingleObject(event, INFINITE); - cmdList->Release(); - cmdAlloc->Release(); ::CloseHandle(event); fence->Release(); uploadBuffer->Release(); @@ -776,6 +771,14 @@ bool ImGui_ImplDX12_CreateDeviceObjects() if (result_pipeline_state != S_OK) return false; + // Create command allocator and command list for ImGui_ImplDX12_UpdateTexture() + HRESULT hr = bd->pd3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&bd->pTexCmdAllocator)); + IM_ASSERT(SUCCEEDED(hr)); + hr = bd->pd3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, bd->pTexCmdAllocator, nullptr, IID_PPV_ARGS(&bd->pTexCmdList)); + IM_ASSERT(SUCCEEDED(hr)); + hr = bd->pTexCmdList->Close(); + IM_ASSERT(SUCCEEDED(hr)); + return true; } @@ -790,6 +793,8 @@ void ImGui_ImplDX12_InvalidateDeviceObjects() bd->commandQueueOwned = false; SafeRelease(bd->pRootSignature); SafeRelease(bd->pPipelineState); + SafeRelease(bd->pTexCmdList); + SafeRelease(bd->pTexCmdAllocator); // Destroy all textures for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index d51981138..146f1de76 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -73,6 +73,8 @@ Other Changes: - Backends: all backends call ImGuiPlatformIO::ClearPlatformHandlers() and ClearRendererHandlers() on shutdown, so as not to leave function pointers which may be dangling when using backend in e.g. DLL. (#8945, #2769) +- Backends: DirectX12: reuse a command list and allocator for texture uploads instead + of recreating them each time. (#8963, #8465) [@RT2Code] - Backends: OpenGL3: fixed GL loader to work on Haiku OS which does not support `RTLD_NOLOAD`. (#8952) [@Xottab-DUTY, @threedeyes] - Backends: SDL2,SDL3: avoid using the SDL_GetGlobalMouseState() path when one of our From e459e5bf0a6db936d4de2c5978ee59c7e8ce7306 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 29 Sep 2025 15:45:43 +0200 Subject: [PATCH 05/10] Backends: GLFW: fixed build on platform that are neither Windows, macOS or known Unixes. (#8969, #8920, #8921) Fix 10d0162. x --- backends/imgui_impl_glfw.cpp | 14 +++++++------- docs/CHANGELOG.txt | 2 ++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/backends/imgui_impl_glfw.cpp b/backends/imgui_impl_glfw.cpp index cfe5dc6d3..d98248acc 100644 --- a/backends/imgui_impl_glfw.cpp +++ b/backends/imgui_impl_glfw.cpp @@ -105,6 +105,11 @@ #endif // GLFW +#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) +#define GLFW_HAS_X11_OR_WAYLAND 1 +#else +#define GLFW_HAS_X11_OR_WAYLAND 0 +#endif #include #ifdef _WIN32 #undef APIENTRY @@ -117,8 +122,8 @@ #define GLFW_EXPOSE_NATIVE_COCOA #endif #include -#elif !defined(__EMSCRIPTEN__) -#ifndef GLFW_EXPOSE_NATIVE_X11 // for glfwGetX11Window() on Freedesktop (Linux, BSD, etc.) +#elif GLFW_HAS_X11_OR_WAYLAND +#ifndef GLFW_EXPOSE_NATIVE_X11 // for glfwGetX11Display(), glfwGetX11Window() on Freedesktop (Linux, BSD, etc.) #define GLFW_EXPOSE_NATIVE_X11 #endif #ifndef GLFW_EXPOSE_NATIVE_WAYLAND @@ -154,11 +159,6 @@ #define GLFW_HAS_GETKEYNAME (GLFW_VERSION_COMBINED >= 3200) // 3.2+ glfwGetKeyName() #define GLFW_HAS_GETERROR (GLFW_VERSION_COMBINED >= 3300) // 3.3+ glfwGetError() #define GLFW_HAS_GETPLATFORM (GLFW_VERSION_COMBINED >= 3400) // 3.4+ glfwGetPlatform() -#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) -#define GLFW_HAS_X11_OR_WAYLAND 1 -#else -#define GLFW_HAS_X11_OR_WAYLAND 0 -#endif // Map GLFWWindow* to ImGuiContext*. // - Would be simpler if we could use glfwSetWindowUserPointer()/glfwGetWindowUserPointer(), but this is a single and shared resource. diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 146f1de76..5dc538ec9 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -77,6 +77,8 @@ Other Changes: of recreating them each time. (#8963, #8465) [@RT2Code] - Backends: OpenGL3: fixed GL loader to work on Haiku OS which does not support `RTLD_NOLOAD`. (#8952) [@Xottab-DUTY, @threedeyes] +- Backends: GLFW: fixed build on platform that are neither Windows, macOS or + known Unixes (Regression in 1.92.3). (#8969, #8920, #8921) [@oktonion] - Backends: SDL2,SDL3: avoid using the SDL_GetGlobalMouseState() path when one of our window is hovered, as the event data is reliable and enough in this case. - Fix mouse coordinates issue in fullscreen apps with macOS notch. (#7919, #7786) From 3ff195f70216cd61c19877e0079bfe3c04491e18 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 29 Sep 2025 16:00:17 +0200 Subject: [PATCH 06/10] Backends: DX12: let bd->FrameIndex start at 0 to match docking. (#8961) --- backends/imgui_impl_dx12.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/imgui_impl_dx12.cpp b/backends/imgui_impl_dx12.cpp index 4a29cf529..fa58f3e5d 100644 --- a/backends/imgui_impl_dx12.cpp +++ b/backends/imgui_impl_dx12.cpp @@ -106,7 +106,7 @@ struct ImGui_ImplDX12_Data bool LegacySingleDescriptorUsed; - ImGui_ImplDX12_Data() { memset((void*)this, 0, sizeof(*this)); frameIndex = UINT_MAX; } + ImGui_ImplDX12_Data() { memset((void*)this, 0, sizeof(*this)); } }; // Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts From bab3ebec14c46835de8034f238f7723621c24f1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Tassoux?= Date: Fri, 26 Sep 2025 16:07:38 +0200 Subject: [PATCH 07/10] Backends: DX12: Rework synchronization logic. (master) (#8961) --- docs/CHANGELOG.txt | 1 + examples/example_win32_directx12/main.cpp | 61 ++++++++--------------- 2 files changed, 22 insertions(+), 40 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 5dc538ec9..c8424cf5d 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -91,6 +91,7 @@ Other Changes: - Examples: SDL2+DirectX11: Try WARP software driver if hardware driver is not available. (#5924, #5562) - Examples: SDL3+DirectX11: Added SDL3+DirectX11 example. (#8956, #8957) [@tomaz82] +- Examples: Win32+DirectX12: Rework synchronization logic. (#8961) [@RT2Code] - Examples: made examples's main.cpp consistent with returning 1 on error. diff --git a/examples/example_win32_directx12/main.cpp b/examples/example_win32_directx12/main.cpp index a5396d31b..665693462 100644 --- a/examples/example_win32_directx12/main.cpp +++ b/examples/example_win32_directx12/main.cpp @@ -102,8 +102,8 @@ bool CreateDeviceD3D(HWND hWnd); void CleanupDeviceD3D(); void CreateRenderTarget(); void CleanupRenderTarget(); -void WaitForLastSubmittedFrame(); -FrameContext* WaitForNextFrameResources(); +void WaitForPendingOperations(); +FrameContext* WaitForNextFrameContext(); LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // Main code @@ -256,7 +256,7 @@ int main(int, char**) // Rendering ImGui::Render(); - FrameContext* frameCtx = WaitForNextFrameResources(); + FrameContext* frameCtx = WaitForNextFrameContext(); UINT backBufferIdx = g_pSwapChain->GetCurrentBackBufferIndex(); frameCtx->CommandAllocator->Reset(); @@ -282,19 +282,17 @@ int main(int, char**) g_pd3dCommandList->Close(); g_pd3dCommandQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)&g_pd3dCommandList); + g_pd3dCommandQueue->Signal(g_fence, ++g_fenceLastSignaledValue); + frameCtx->FenceValue = g_fenceLastSignaledValue; // Present HRESULT hr = g_pSwapChain->Present(1, 0); // Present with vsync //HRESULT hr = g_pSwapChain->Present(0, 0); // Present without vsync g_SwapChainOccluded = (hr == DXGI_STATUS_OCCLUDED); - - UINT64 fenceValue = g_fenceLastSignaledValue + 1; - g_pd3dCommandQueue->Signal(g_fence, fenceValue); - g_fenceLastSignaledValue = fenceValue; - frameCtx->FenceValue = fenceValue; + g_frameIndex++; } - WaitForLastSubmittedFrame(); + WaitForPendingOperations(); // Cleanup ImGui_ImplDX12_Shutdown(); @@ -465,49 +463,33 @@ void CreateRenderTarget() void CleanupRenderTarget() { - WaitForLastSubmittedFrame(); + WaitForPendingOperations(); for (UINT i = 0; i < APP_NUM_BACK_BUFFERS; i++) if (g_mainRenderTargetResource[i]) { g_mainRenderTargetResource[i]->Release(); g_mainRenderTargetResource[i] = nullptr; } } -void WaitForLastSubmittedFrame() +void WaitForPendingOperations() { - FrameContext* frameCtx = &g_frameContext[g_frameIndex % APP_NUM_FRAMES_IN_FLIGHT]; + g_pd3dCommandQueue->Signal(g_fence, ++g_fenceLastSignaledValue); - UINT64 fenceValue = frameCtx->FenceValue; - if (fenceValue == 0) - return; // No fence was signaled - - frameCtx->FenceValue = 0; - if (g_fence->GetCompletedValue() >= fenceValue) - return; - - g_fence->SetEventOnCompletion(fenceValue, g_fenceEvent); - WaitForSingleObject(g_fenceEvent, INFINITE); + g_fence->SetEventOnCompletion(g_fenceLastSignaledValue, g_fenceEvent); + ::WaitForSingleObject(g_fenceEvent, INFINITE); } -FrameContext* WaitForNextFrameResources() +FrameContext* WaitForNextFrameContext() { - UINT nextFrameIndex = g_frameIndex + 1; - g_frameIndex = nextFrameIndex; - - HANDLE waitableObjects[] = { g_hSwapChainWaitableObject, nullptr }; - DWORD numWaitableObjects = 1; - - FrameContext* frameCtx = &g_frameContext[nextFrameIndex % APP_NUM_FRAMES_IN_FLIGHT]; - UINT64 fenceValue = frameCtx->FenceValue; - if (fenceValue != 0) // means no fence was signaled + FrameContext* frame_context = &g_frameContext[g_frameIndex % APP_NUM_FRAMES_IN_FLIGHT]; + if (g_fence->GetCompletedValue() < frame_context->FenceValue) { - frameCtx->FenceValue = 0; - g_fence->SetEventOnCompletion(fenceValue, g_fenceEvent); - waitableObjects[1] = g_fenceEvent; - numWaitableObjects = 2; + g_fence->SetEventOnCompletion(frame_context->FenceValue, g_fenceEvent); + HANDLE waitableObjects[] = { g_hSwapChainWaitableObject, g_fenceEvent }; + ::WaitForMultipleObjects(2, waitableObjects, TRUE, INFINITE); } + else + ::WaitForSingleObject(g_hSwapChainWaitableObject, INFINITE); - WaitForMultipleObjects(numWaitableObjects, waitableObjects, TRUE, INFINITE); - - return frameCtx; + return frame_context; } // Forward declare message handler from imgui_impl_win32.cpp @@ -528,7 +510,6 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) case WM_SIZE: if (g_pd3dDevice != nullptr && wParam != SIZE_MINIMIZED) { - WaitForLastSubmittedFrame(); CleanupRenderTarget(); HRESULT result = g_pSwapChain->ResizeBuffers(0, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam), DXGI_FORMAT_UNKNOWN, DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT); assert(SUCCEEDED(result) && "Failed to resize swapchain."); From 5ce903af6e04e0a0bf044967a902aaffad5e82cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Tassoux?= Date: Sat, 27 Sep 2025 00:12:36 +0200 Subject: [PATCH 08/10] Backends: DX12: Use one fence instead of one by viewport. (master) (#8961) --- backends/imgui_impl_dx12.cpp | 30 +++++++++++++++++------------- docs/CHANGELOG.txt | 1 + 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/backends/imgui_impl_dx12.cpp b/backends/imgui_impl_dx12.cpp index fa58f3e5d..a27d34ba2 100644 --- a/backends/imgui_impl_dx12.cpp +++ b/backends/imgui_impl_dx12.cpp @@ -20,7 +20,8 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) -// 2025-09-27: DirectX12: Reuse a command list and allocator for texture uploads instead of recreating them each time. +// 2025-09-29: DirectX12: Rework synchronization logic. (#8961) +// 2025-09-29: DirectX12: Reuse a command list and allocator for texture uploads instead of recreating them each time. // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-06-19: Fixed build on MinGW. (#8702, #4594) // 2025-06-11: DirectX12: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. @@ -96,6 +97,9 @@ struct ImGui_ImplDX12_Data DXGI_FORMAT RTVFormat; DXGI_FORMAT DSVFormat; ID3D12DescriptorHeap* pd3dSrvDescHeap; + ID3D12Fence* Fence; + UINT64 FenceLastSignaledValue; + HANDLE FenceEvent; UINT numFramesInFlight; ID3D12CommandAllocator* pTexCmdAllocator; @@ -458,13 +462,6 @@ void ImGui_ImplDX12_UpdateTexture(ImTextureData* tex) D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&uploadBuffer)); IM_ASSERT(SUCCEEDED(hr)); - ID3D12Fence* fence = nullptr; - hr = bd->pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)); - IM_ASSERT(SUCCEEDED(hr)); - - HANDLE event = ::CreateEvent(0, 0, 0, 0); - IM_ASSERT(event != nullptr); - bd->pTexCmdAllocator->Reset(); bd->pTexCmdList->Reset(bd->pTexCmdAllocator, nullptr); ID3D12GraphicsCommandList* cmdList = bd->pTexCmdList; @@ -522,18 +519,16 @@ void ImGui_ImplDX12_UpdateTexture(ImTextureData* tex) ID3D12CommandQueue* cmdQueue = bd->pCommandQueue; cmdQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)&cmdList); - hr = cmdQueue->Signal(fence, 1); + hr = cmdQueue->Signal(bd->Fence, ++bd->FenceLastSignaledValue); IM_ASSERT(SUCCEEDED(hr)); // FIXME-OPT: Suboptimal? // - To remove this may need to create NumFramesInFlight x ImGui_ImplDX12_FrameContext in backend data (mimick docking version) // - Store per-frame in flight: upload buffer? // - Where do cmdList and cmdAlloc fit? - fence->SetEventOnCompletion(1, event); - ::WaitForSingleObject(event, INFINITE); + bd->Fence->SetEventOnCompletion(bd->FenceLastSignaledValue, bd->FenceEvent); + ::WaitForSingleObject(bd->FenceEvent, INFINITE); - ::CloseHandle(event); - fence->Release(); uploadBuffer->Release(); tex->SetStatus(ImTextureStatus_OK); } @@ -779,6 +774,12 @@ bool ImGui_ImplDX12_CreateDeviceObjects() hr = bd->pTexCmdList->Close(); IM_ASSERT(SUCCEEDED(hr)); + // Create fence. + hr = bd->pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&bd->Fence)); + IM_ASSERT(hr == S_OK); + bd->FenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + IM_ASSERT(bd->FenceEvent != nullptr); + return true; } @@ -795,6 +796,9 @@ void ImGui_ImplDX12_InvalidateDeviceObjects() SafeRelease(bd->pPipelineState); SafeRelease(bd->pTexCmdList); SafeRelease(bd->pTexCmdAllocator); + SafeRelease(bd->Fence); + CloseHandle(bd->FenceEvent); + bd->FenceEvent = nullptr; // Destroy all textures for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index c8424cf5d..b1579715e 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -75,6 +75,7 @@ Other Changes: which may be dangling when using backend in e.g. DLL. (#8945, #2769) - Backends: DirectX12: reuse a command list and allocator for texture uploads instead of recreating them each time. (#8963, #8465) [@RT2Code] +- Backends: DirectX12: Rework synchronization logic. (#8961) [@RT2Code] - Backends: OpenGL3: fixed GL loader to work on Haiku OS which does not support `RTLD_NOLOAD`. (#8952) [@Xottab-DUTY, @threedeyes] - Backends: GLFW: fixed build on platform that are neither Windows, macOS or From 778aadca6586da13abba2364ac09cff8156d6218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Tassoux?= Date: Mon, 29 Sep 2025 16:23:50 +0200 Subject: [PATCH 09/10] Backends: DX12: Rework synchronization logic. (docking) (#8961) --- backends/imgui_impl_dx12.cpp | 76 ++++++++++++++--------- examples/example_win32_directx12/main.cpp | 5 +- 2 files changed, 51 insertions(+), 30 deletions(-) diff --git a/backends/imgui_impl_dx12.cpp b/backends/imgui_impl_dx12.cpp index f16bd8cd0..9fbdc4426 100644 --- a/backends/imgui_impl_dx12.cpp +++ b/backends/imgui_impl_dx12.cpp @@ -135,6 +135,7 @@ struct ImGui_ImplDX12_RenderBuffers // Buffers used for secondary viewports created by the multi-viewports systems struct ImGui_ImplDX12_FrameContext { + UINT64 FenceValue; ID3D12CommandAllocator* CommandAllocator; ID3D12Resource* RenderTarget; D3D12_CPU_DESCRIPTOR_HANDLE RenderTargetCpuDescriptors; @@ -150,8 +151,9 @@ struct ImGui_ImplDX12_ViewportData ID3D12GraphicsCommandList* CommandList; ID3D12DescriptorHeap* RtvDescHeap; IDXGISwapChain3* SwapChain; + HANDLE SwapChainWaitableObject; ID3D12Fence* Fence; - UINT64 FenceSignaledValue; + UINT64 FenceLastSignaledValue; HANDLE FenceEvent; UINT NumFramesInFlight; ImGui_ImplDX12_FrameContext* FrameCtx; @@ -166,16 +168,18 @@ struct ImGui_ImplDX12_ViewportData CommandList = nullptr; RtvDescHeap = nullptr; SwapChain = nullptr; + SwapChainWaitableObject = nullptr; Fence = nullptr; - FenceSignaledValue = 0; + FenceLastSignaledValue = 0; FenceEvent = nullptr; NumFramesInFlight = num_frames_in_flight; FrameCtx = new ImGui_ImplDX12_FrameContext[NumFramesInFlight]; - FrameIndex = UINT_MAX; + FrameIndex = 0; FrameRenderBuffers = new ImGui_ImplDX12_RenderBuffers[NumFramesInFlight]; for (UINT i = 0; i < NumFramesInFlight; ++i) { + FrameCtx[i].FenceValue = 0; FrameCtx[i].CommandAllocator = nullptr; FrameCtx[i].RenderTarget = nullptr; @@ -191,6 +195,7 @@ struct ImGui_ImplDX12_ViewportData IM_ASSERT(CommandQueue == nullptr && CommandList == nullptr); IM_ASSERT(RtvDescHeap == nullptr); IM_ASSERT(SwapChain == nullptr); + IM_ASSERT(SwapChainWaitableObject == nullptr); IM_ASSERT(Fence == nullptr); IM_ASSERT(FenceEvent == nullptr); @@ -1043,18 +1048,12 @@ static void ImGui_ImplDX12_CreateWindow(ImGuiViewport* viewport) HWND hwnd = viewport->PlatformHandleRaw ? (HWND)viewport->PlatformHandleRaw : (HWND)viewport->PlatformHandle; IM_ASSERT(hwnd != 0); - vd->FrameIndex = UINT_MAX; - - // Create command queue. - D3D12_COMMAND_QUEUE_DESC queue_desc = {}; - queue_desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; - queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; - - HRESULT res = S_OK; - res = bd->pd3dDevice->CreateCommandQueue(&queue_desc, IID_PPV_ARGS(&vd->CommandQueue)); - IM_ASSERT(res == S_OK); + // Use shared command queue from init info + vd->FrameIndex = 0; + vd->CommandQueue = bd->pCommandQueue; // Create command allocator. + HRESULT res = S_OK; for (UINT i = 0; i < bd->numFramesInFlight; ++i) { res = bd->pd3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&vd->FrameCtx[i].CommandAllocator)); @@ -1088,6 +1087,7 @@ static void ImGui_ImplDX12_CreateWindow(ImGuiViewport* viewport) sd1.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; sd1.Scaling = DXGI_SCALING_NONE; sd1.Stereo = FALSE; + sd1.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; IDXGIFactory4* dxgi_factory = nullptr; res = ::CreateDXGIFactory1(IID_PPV_ARGS(&dxgi_factory)); @@ -1106,7 +1106,7 @@ static void ImGui_ImplDX12_CreateWindow(ImGuiViewport* viewport) swap_chain->QueryInterface(IID_PPV_ARGS(&vd->SwapChain)); swap_chain->Release(); - // Create the render targets + // Create the render targets and waitable object if (vd->SwapChain) { D3D12_DESCRIPTOR_HEAP_DESC desc = {}; @@ -1134,6 +1134,10 @@ static void ImGui_ImplDX12_CreateWindow(ImGuiViewport* viewport) bd->pd3dDevice->CreateRenderTargetView(back_buffer, nullptr, vd->FrameCtx[i].RenderTargetCpuDescriptors); vd->FrameCtx[i].RenderTarget = back_buffer; } + + hr = vd->SwapChain->SetMaximumFrameLatency(bd->numFramesInFlight); + IM_ASSERT(hr == S_OK); + vd->SwapChainWaitableObject = vd->SwapChain->GetFrameLatencyWaitableObject(); } for (UINT i = 0; i < bd->numFramesInFlight; i++) @@ -1142,16 +1146,29 @@ static void ImGui_ImplDX12_CreateWindow(ImGuiViewport* viewport) static void ImGui_WaitForPendingOperations(ImGui_ImplDX12_ViewportData* vd) { - HRESULT hr = S_FALSE; - if (vd && vd->CommandQueue && vd->Fence && vd->FenceEvent) + HRESULT hr = vd->CommandQueue->Signal(vd->Fence, ++vd->FenceLastSignaledValue); + IM_ASSERT(hr == S_OK); + + hr = vd->Fence->SetEventOnCompletion(vd->FenceLastSignaledValue, vd->FenceEvent); + IM_ASSERT(hr == S_OK); + ::WaitForSingleObject(vd->FenceEvent, INFINITE); +} + +static ImGui_ImplDX12_FrameContext* ImGui_WaitForNextFrameContext(ImGui_ImplDX12_ViewportData* vd) +{ + ImGui_ImplDX12_FrameContext* frame_context = &vd->FrameCtx[vd->FrameIndex % vd->NumFramesInFlight]; + if (vd->Fence->GetCompletedValue() < frame_context->FenceValue) { - hr = vd->CommandQueue->Signal(vd->Fence, ++vd->FenceSignaledValue); + HRESULT hr = vd->Fence->SetEventOnCompletion(frame_context->FenceValue, vd->FenceEvent); IM_ASSERT(hr == S_OK); - ::WaitForSingleObject(vd->FenceEvent, 0); // Reset any forgotten waits - hr = vd->Fence->SetEventOnCompletion(vd->FenceSignaledValue, vd->FenceEvent); - IM_ASSERT(hr == S_OK); - ::WaitForSingleObject(vd->FenceEvent, INFINITE); + HANDLE waitableObjects[] = { vd->SwapChainWaitableObject, vd->FenceEvent }; + ::WaitForMultipleObjects(2, waitableObjects, TRUE, INFINITE); } + else + { + ::WaitForSingleObject(vd->SwapChainWaitableObject, INFINITE); + } + return frame_context; } static void ImGui_ImplDX12_DestroyWindow(ImGuiViewport* viewport) @@ -1162,7 +1179,9 @@ static void ImGui_ImplDX12_DestroyWindow(ImGuiViewport* viewport) { ImGui_WaitForPendingOperations(vd); - SafeRelease(vd->CommandQueue); + vd->CommandQueue = nullptr; + ::CloseHandle(vd->SwapChainWaitableObject); + vd->SwapChainWaitableObject = nullptr; SafeRelease(vd->CommandList); SafeRelease(vd->SwapChain); SafeRelease(vd->RtvDescHeap); @@ -1194,7 +1213,7 @@ static void ImGui_ImplDX12_SetWindowSize(ImGuiViewport* viewport, ImVec2 size) if (vd->SwapChain) { ID3D12Resource* back_buffer = nullptr; - vd->SwapChain->ResizeBuffers(0, (UINT)size.x, (UINT)size.y, DXGI_FORMAT_UNKNOWN, 0); + vd->SwapChain->ResizeBuffers(0, (UINT)size.x, (UINT)size.y, DXGI_FORMAT_UNKNOWN, DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT); for (UINT i = 0; i < bd->numFramesInFlight; i++) { vd->SwapChain->GetBuffer(i, IID_PPV_ARGS(&back_buffer)); @@ -1209,7 +1228,7 @@ static void ImGui_ImplDX12_RenderWindow(ImGuiViewport* viewport, void*) ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); ImGui_ImplDX12_ViewportData* vd = (ImGui_ImplDX12_ViewportData*)viewport->RendererUserData; - ImGui_ImplDX12_FrameContext* frame_context = &vd->FrameCtx[vd->FrameIndex % bd->numFramesInFlight]; + ImGui_ImplDX12_FrameContext* frame_context = ImGui_WaitForNextFrameContext(vd); UINT back_buffer_idx = vd->SwapChain->GetCurrentBackBufferIndex(); const ImVec4 clear_color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); @@ -1239,9 +1258,11 @@ static void ImGui_ImplDX12_RenderWindow(ImGuiViewport* viewport, void*) cmd_list->ResourceBarrier(1, &barrier); cmd_list->Close(); - vd->CommandQueue->Wait(vd->Fence, vd->FenceSignaledValue); vd->CommandQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)&cmd_list); - vd->CommandQueue->Signal(vd->Fence, ++vd->FenceSignaledValue); + + HRESULT hr = vd->CommandQueue->Signal(vd->Fence, ++vd->FenceLastSignaledValue); + IM_ASSERT(hr == S_OK); + frame_context->FenceValue = vd->FenceLastSignaledValue; } static void ImGui_ImplDX12_SwapBuffers(ImGuiViewport* viewport, void*) @@ -1249,8 +1270,7 @@ static void ImGui_ImplDX12_SwapBuffers(ImGuiViewport* viewport, void*) ImGui_ImplDX12_ViewportData* vd = (ImGui_ImplDX12_ViewportData*)viewport->RendererUserData; vd->SwapChain->Present(0, 0); - while (vd->Fence->GetCompletedValue() < vd->FenceSignaledValue) - ::SwitchToThread(); + vd->FrameIndex++; } void ImGui_ImplDX12_InitMultiViewportSupport() diff --git a/examples/example_win32_directx12/main.cpp b/examples/example_win32_directx12/main.cpp index 68cb68a07..4fdf5da9e 100644 --- a/examples/example_win32_directx12/main.cpp +++ b/examples/example_win32_directx12/main.cpp @@ -295,8 +295,6 @@ int main(int, char**) g_pd3dCommandList->Close(); g_pd3dCommandQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)&g_pd3dCommandList); - g_pd3dCommandQueue->Signal(g_fence, ++g_fenceLastSignaledValue); - frameCtx->FenceValue = g_fenceLastSignaledValue; // Update and Render additional Platform Windows if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) @@ -305,6 +303,9 @@ int main(int, char**) ImGui::RenderPlatformWindowsDefault(); } + g_pd3dCommandQueue->Signal(g_fence, ++g_fenceLastSignaledValue); + frameCtx->FenceValue = g_fenceLastSignaledValue; + // Present HRESULT hr = g_pSwapChain->Present(1, 0); // Present with vsync //HRESULT hr = g_pSwapChain->Present(0, 0); // Present without vsync From bd9a37bc600a4281d74ef429fb23ee8e002b217d Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 29 Sep 2025 16:27:12 +0200 Subject: [PATCH 10/10] Backends: DX12: Use one fence instead of one by viewport. (docking) (#8961) --- backends/imgui_impl_dx12.cpp | 36 ++++++++++-------------------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/backends/imgui_impl_dx12.cpp b/backends/imgui_impl_dx12.cpp index 9fbdc4426..68e379b03 100644 --- a/backends/imgui_impl_dx12.cpp +++ b/backends/imgui_impl_dx12.cpp @@ -152,9 +152,6 @@ struct ImGui_ImplDX12_ViewportData ID3D12DescriptorHeap* RtvDescHeap; IDXGISwapChain3* SwapChain; HANDLE SwapChainWaitableObject; - ID3D12Fence* Fence; - UINT64 FenceLastSignaledValue; - HANDLE FenceEvent; UINT NumFramesInFlight; ImGui_ImplDX12_FrameContext* FrameCtx; @@ -169,9 +166,6 @@ struct ImGui_ImplDX12_ViewportData RtvDescHeap = nullptr; SwapChain = nullptr; SwapChainWaitableObject = nullptr; - Fence = nullptr; - FenceLastSignaledValue = 0; - FenceEvent = nullptr; NumFramesInFlight = num_frames_in_flight; FrameCtx = new ImGui_ImplDX12_FrameContext[NumFramesInFlight]; FrameIndex = 0; @@ -196,8 +190,6 @@ struct ImGui_ImplDX12_ViewportData IM_ASSERT(RtvDescHeap == nullptr); IM_ASSERT(SwapChain == nullptr); IM_ASSERT(SwapChainWaitableObject == nullptr); - IM_ASSERT(Fence == nullptr); - IM_ASSERT(FenceEvent == nullptr); for (UINT i = 0; i < NumFramesInFlight; ++i) { @@ -1065,13 +1057,6 @@ static void ImGui_ImplDX12_CreateWindow(ImGuiViewport* viewport) IM_ASSERT(res == S_OK); vd->CommandList->Close(); - // Create fence. - res = bd->pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&vd->Fence)); - IM_ASSERT(res == S_OK); - - vd->FenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); - IM_ASSERT(vd->FenceEvent != nullptr); - // Create swap chain // FIXME-VIEWPORT: May want to copy/inherit swap chain settings from the user/application. DXGI_SWAP_CHAIN_DESC1 sd1; @@ -1146,22 +1131,24 @@ static void ImGui_ImplDX12_CreateWindow(ImGuiViewport* viewport) static void ImGui_WaitForPendingOperations(ImGui_ImplDX12_ViewportData* vd) { - HRESULT hr = vd->CommandQueue->Signal(vd->Fence, ++vd->FenceLastSignaledValue); + ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); + HRESULT hr = vd->CommandQueue->Signal(bd->Fence, ++bd->FenceLastSignaledValue); IM_ASSERT(hr == S_OK); - hr = vd->Fence->SetEventOnCompletion(vd->FenceLastSignaledValue, vd->FenceEvent); + hr = bd->Fence->SetEventOnCompletion(bd->FenceLastSignaledValue, bd->FenceEvent); IM_ASSERT(hr == S_OK); - ::WaitForSingleObject(vd->FenceEvent, INFINITE); + ::WaitForSingleObject(bd->FenceEvent, INFINITE); } static ImGui_ImplDX12_FrameContext* ImGui_WaitForNextFrameContext(ImGui_ImplDX12_ViewportData* vd) { + ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); ImGui_ImplDX12_FrameContext* frame_context = &vd->FrameCtx[vd->FrameIndex % vd->NumFramesInFlight]; - if (vd->Fence->GetCompletedValue() < frame_context->FenceValue) + if (bd->Fence->GetCompletedValue() < frame_context->FenceValue) { - HRESULT hr = vd->Fence->SetEventOnCompletion(frame_context->FenceValue, vd->FenceEvent); + HRESULT hr = bd->Fence->SetEventOnCompletion(frame_context->FenceValue, bd->FenceEvent); IM_ASSERT(hr == S_OK); - HANDLE waitableObjects[] = { vd->SwapChainWaitableObject, vd->FenceEvent }; + HANDLE waitableObjects[] = { vd->SwapChainWaitableObject, bd->FenceEvent }; ::WaitForMultipleObjects(2, waitableObjects, TRUE, INFINITE); } else @@ -1185,9 +1172,6 @@ static void ImGui_ImplDX12_DestroyWindow(ImGuiViewport* viewport) SafeRelease(vd->CommandList); SafeRelease(vd->SwapChain); SafeRelease(vd->RtvDescHeap); - SafeRelease(vd->Fence); - ::CloseHandle(vd->FenceEvent); - vd->FenceEvent = nullptr; for (UINT i = 0; i < bd->numFramesInFlight; i++) { @@ -1260,9 +1244,9 @@ static void ImGui_ImplDX12_RenderWindow(ImGuiViewport* viewport, void*) vd->CommandQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)&cmd_list); - HRESULT hr = vd->CommandQueue->Signal(vd->Fence, ++vd->FenceLastSignaledValue); + HRESULT hr = vd->CommandQueue->Signal(bd->Fence, ++bd->FenceLastSignaledValue); IM_ASSERT(hr == S_OK); - frame_context->FenceValue = vd->FenceLastSignaledValue; + frame_context->FenceValue = bd->FenceLastSignaledValue; } static void ImGui_ImplDX12_SwapBuffers(ImGuiViewport* viewport, void*)