diff --git a/backends/imgui_impl_dx12.cpp b/backends/imgui_impl_dx12.cpp index a224469d8..f16bd8cd0 100644 --- a/backends/imgui_impl_dx12.cpp +++ b/backends/imgui_impl_dx12.cpp @@ -23,6 +23,8 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. +// 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. @@ -98,8 +100,14 @@ struct ImGui_ImplDX12_Data DXGI_FORMAT RTVFormat; DXGI_FORMAT DSVFormat; ID3D12DescriptorHeap* pd3dSrvDescHeap; + ID3D12Fence* Fence; + UINT64 FenceLastSignaledValue; + HANDLE FenceEvent; UINT numFramesInFlight; + ID3D12CommandAllocator* pTexCmdAllocator; + ID3D12GraphicsCommandList* pTexCmdList; + ImGui_ImplDX12_RenderBuffers* pFrameResources; UINT frameIndex; @@ -529,29 +537,15 @@ 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)); - - 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; @@ -606,20 +600,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); - cmdList->Release(); - cmdAlloc->Release(); - ::CloseHandle(event); - fence->Release(); uploadBuffer->Release(); tex->SetStatus(ImTextureStatus_OK); } @@ -857,6 +847,20 @@ 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)); + + // 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; } @@ -878,6 +882,11 @@ void ImGui_ImplDX12_InvalidateDeviceObjects() bd->commandQueueOwned = false; SafeRelease(bd->pRootSignature); 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/backends/imgui_impl_glfw.cpp b/backends/imgui_impl_glfw.cpp index 905f86488..31da48289 100644 --- a/backends/imgui_impl_glfw.cpp +++ b/backends/imgui_impl_glfw.cpp @@ -116,6 +116,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 @@ -128,8 +133,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 #include #endif @@ -184,11 +189,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 0bea2cf2e..e4f190071 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -66,6 +66,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, @@ -75,8 +77,13 @@ 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: 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 + 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) @@ -89,6 +96,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. Docking+Viewports Branch: diff --git a/examples/example_win32_directx12/main.cpp b/examples/example_win32_directx12/main.cpp index 0cd96e2df..68cb68a07 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 @@ -269,7 +269,7 @@ int main(int, char**) // Rendering ImGui::Render(); - FrameContext* frameCtx = WaitForNextFrameResources(); + FrameContext* frameCtx = WaitForNextFrameContext(); UINT backBufferIdx = g_pSwapChain->GetCurrentBackBufferIndex(); frameCtx->CommandAllocator->Reset(); @@ -295,6 +295,8 @@ 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) @@ -307,14 +309,10 @@ int main(int, char**) 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(); @@ -484,49 +482,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 @@ -547,7 +529,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."); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 0f7ce29d3..c5cafbeb4 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -5531,7 +5531,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)