diff --git a/backends/imgui_impl_dx12.cpp b/backends/imgui_impl_dx12.cpp index 776a7b01f..3c4f5beee 100644 --- a/backends/imgui_impl_dx12.cpp +++ b/backends/imgui_impl_dx12.cpp @@ -22,6 +22,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2025-09-26: DirectX12: Rework and unify synchronization logic. // 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. // 2025-06-19: Fixed build on MinGW. (#8702, #4594) // 2025-06-11: DirectX12: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. @@ -126,6 +127,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; @@ -141,8 +143,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; @@ -157,16 +160,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; @@ -182,6 +187,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); @@ -1032,16 +1038,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; + vd->FrameIndex = 0; - // 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; + // Use shared command queue from init info + vd->CommandQueue = bd->pCommandQueue; HRESULT res = S_OK; - res = bd->pd3dDevice->CreateCommandQueue(&queue_desc, IID_PPV_ARGS(&vd->CommandQueue)); - IM_ASSERT(res == S_OK); // Create command allocator. for (UINT i = 0; i < bd->numFramesInFlight; ++i) @@ -1077,6 +1079,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)); @@ -1093,7 +1096,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 = {}; @@ -1121,6 +1124,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++) @@ -1129,16 +1136,28 @@ 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) @@ -1149,7 +1168,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); @@ -1181,7 +1202,8 @@ 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)); @@ -1196,7 +1218,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); @@ -1226,9 +1248,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*) @@ -1236,8 +1260,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_InitPlatformInterface() diff --git a/examples/example_win32_directx12/main.cpp b/examples/example_win32_directx12/main.cpp index 60b952143..462c8f0d4 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(); @@ -303,18 +303,18 @@ 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 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 +484,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 +531,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.");