From b4514ce64a62322746af99b83a53187c6761c34a Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 29 Sep 2025 18:30:31 +0200 Subject: [PATCH] Backends: DirectX12: enable swapchain tearing if available. (#8965) --- backends/imgui_impl_dx12.cpp | 39 +++++++++++++++-------- docs/CHANGELOG.txt | 1 + examples/example_win32_directx12/main.cpp | 21 +++++++++--- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/backends/imgui_impl_dx12.cpp b/backends/imgui_impl_dx12.cpp index 68e379b03..8dcd923c2 100644 --- a/backends/imgui_impl_dx12.cpp +++ b/backends/imgui_impl_dx12.cpp @@ -24,7 +24,8 @@ // (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-29: DirectX12: Enable swapchain tearing to eliminate viewports framerate throttling. (#8965) +// 2025-09-29: DirectX12: Reuse a command list and allocator for texture uploads instead of recreating them each time. (#8963) // 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. @@ -62,7 +63,7 @@ // DirectX #include -#include +#include #include #ifdef _MSC_VER #pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below. @@ -92,6 +93,7 @@ struct ImGui_ImplDX12_Texture struct ImGui_ImplDX12_Data { ImGui_ImplDX12_InitInfo InitInfo; + IDXGIFactory5* pdxgiFactory; ID3D12Device* pd3dDevice; ID3D12RootSignature* pRootSignature; ID3D12PipelineState* pPipelineState; @@ -104,6 +106,8 @@ struct ImGui_ImplDX12_Data UINT64 FenceLastSignaledValue; HANDLE FenceEvent; UINT numFramesInFlight; + bool tearingSupport; + bool LegacySingleDescriptorUsed; ID3D12CommandAllocator* pTexCmdAllocator; ID3D12GraphicsCommandList* pTexCmdList; @@ -111,8 +115,6 @@ struct ImGui_ImplDX12_Data ImGui_ImplDX12_RenderBuffers* pFrameResources; UINT frameIndex; - bool LegacySingleDescriptorUsed; - ImGui_ImplDX12_Data() { memset((void*)this, 0, sizeof(*this)); } }; @@ -623,6 +625,13 @@ bool ImGui_ImplDX12_CreateDeviceObjects() if (bd->pPipelineState) ImGui_ImplDX12_InvalidateDeviceObjects(); + HRESULT hr = ::CreateDXGIFactory1(IID_PPV_ARGS(&bd->pdxgiFactory)); + IM_ASSERT(hr == S_OK); + + BOOL allow_tearing = FALSE; + bd->pdxgiFactory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing, sizeof(allow_tearing)); + bd->tearingSupport = (allow_tearing == TRUE); + // Create the root signature { D3D12_DESCRIPTOR_RANGE descRange = {}; @@ -845,7 +854,7 @@ bool ImGui_ImplDX12_CreateDeviceObjects() 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)); + 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)); @@ -874,6 +883,7 @@ void ImGui_ImplDX12_InvalidateDeviceObjects() if (!bd || !bd->pd3dDevice) return; + SafeRelease(bd->pdxgiFactory); if (bd->commandQueueOwned) SafeRelease(bd->pCommandQueue); bd->commandQueueOwned = false; @@ -931,6 +941,7 @@ bool ImGui_ImplDX12_Init(ImGui_ImplDX12_InitInfo* init_info) bd->DSVFormat = init_info->DSVFormat; bd->numFramesInFlight = init_info->NumFramesInFlight; bd->pd3dSrvDescHeap = init_info->SrvDescriptorHeap; + bd->tearingSupport = false; io.BackendRendererUserData = (void*)bd; io.BackendRendererName = "imgui_impl_dx12"; @@ -1074,18 +1085,15 @@ static void ImGui_ImplDX12_CreateWindow(ImGuiViewport* viewport) sd1.Stereo = FALSE; sd1.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; - IDXGIFactory4* dxgi_factory = nullptr; - res = ::CreateDXGIFactory1(IID_PPV_ARGS(&dxgi_factory)); - IM_ASSERT(res == S_OK); + if (bd->tearingSupport) + sd1.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; IDXGISwapChain1* swap_chain = nullptr; - res = dxgi_factory->CreateSwapChainForHwnd(vd->CommandQueue, hwnd, &sd1, nullptr, nullptr, &swap_chain); + res = bd->pdxgiFactory->CreateSwapChainForHwnd(vd->CommandQueue, hwnd, &sd1, nullptr, nullptr, &swap_chain); IM_ASSERT(res == S_OK); - res = dxgi_factory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER | DXGI_MWA_NO_WINDOW_CHANGES); // Disable e.g. Alt+Enter + res = bd->pdxgiFactory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER | DXGI_MWA_NO_WINDOW_CHANGES); // Disable e.g. Alt+Enter IM_ASSERT(res == S_OK); - dxgi_factory->Release(); - // Or swapChain.As(&mSwapChain) IM_ASSERT(vd->SwapChain == nullptr); swap_chain->QueryInterface(IID_PPV_ARGS(&vd->SwapChain)); @@ -1197,7 +1205,9 @@ 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, DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT); + DXGI_SWAP_CHAIN_DESC1 desc = {}; + vd->SwapChain->GetDesc1(&desc); + vd->SwapChain->ResizeBuffers(0, (UINT)size.x, (UINT)size.y, desc.Format, desc.Flags); for (UINT i = 0; i < bd->numFramesInFlight; i++) { vd->SwapChain->GetBuffer(i, IID_PPV_ARGS(&back_buffer)); @@ -1251,9 +1261,10 @@ static void ImGui_ImplDX12_RenderWindow(ImGuiViewport* viewport, void*) static void ImGui_ImplDX12_SwapBuffers(ImGuiViewport* viewport, void*) { + ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); ImGui_ImplDX12_ViewportData* vd = (ImGui_ImplDX12_ViewportData*)viewport->RendererUserData; - vd->SwapChain->Present(0, 0); + vd->SwapChain->Present(0, bd->tearingSupport ? DXGI_PRESENT_ALLOW_TEARING : 0); vd->FrameIndex++; } diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index e4f190071..063a51ebb 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -80,6 +80,7 @@ Other Changes: - 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: DirectX12: Enable swapchain tearing if available. (#8965) [@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 diff --git a/examples/example_win32_directx12/main.cpp b/examples/example_win32_directx12/main.cpp index 4fdf5da9e..e94ccaee1 100644 --- a/examples/example_win32_directx12/main.cpp +++ b/examples/example_win32_directx12/main.cpp @@ -10,7 +10,7 @@ #include "imgui_impl_win32.h" #include "imgui_impl_dx12.h" #include -#include +#include #include #ifdef _DEBUG @@ -92,6 +92,7 @@ static ID3D12Fence* g_fence = nullptr; static HANDLE g_fenceEvent = nullptr; static UINT64 g_fenceLastSignaledValue = 0; static IDXGISwapChain3* g_pSwapChain = nullptr; +static bool g_SwapChainTearingSupport = false; static bool g_SwapChainOccluded = false; static HANDLE g_hSwapChainWaitableObject = nullptr; static ID3D12Resource* g_mainRenderTargetResource[APP_NUM_BACK_BUFFERS] = {}; @@ -308,7 +309,7 @@ int main(int, char**) // Present HRESULT hr = g_pSwapChain->Present(1, 0); // Present with vsync - //HRESULT hr = g_pSwapChain->Present(0, 0); // Present without vsync + //HRESULT hr = g_pSwapChain->Present(0, g_SwapChainTearingSupport ? DXGI_PRESENT_ALLOW_TEARING : 0); // Present without vsync g_SwapChainOccluded = (hr == DXGI_STATUS_OCCLUDED); g_frameIndex++; } @@ -427,14 +428,24 @@ bool CreateDeviceD3D(HWND hWnd) return false; { - IDXGIFactory4* dxgiFactory = nullptr; + IDXGIFactory5* dxgiFactory = nullptr; IDXGISwapChain1* swapChain1 = nullptr; if (CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory)) != S_OK) return false; + + BOOL allow_tearing = FALSE; + dxgiFactory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing, sizeof(allow_tearing)); + g_SwapChainTearingSupport = (allow_tearing == TRUE); + if (g_SwapChainTearingSupport) + sd.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + if (dxgiFactory->CreateSwapChainForHwnd(g_pd3dCommandQueue, hWnd, &sd, nullptr, nullptr, &swapChain1) != S_OK) return false; if (swapChain1->QueryInterface(IID_PPV_ARGS(&g_pSwapChain)) != S_OK) return false; + if (g_SwapChainTearingSupport) + dxgiFactory->MakeWindowAssociation(hWnd, DXGI_MWA_NO_ALT_ENTER); + swapChain1->Release(); dxgiFactory->Release(); g_pSwapChain->SetMaximumFrameLatency(APP_NUM_BACK_BUFFERS); @@ -531,7 +542,9 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) if (g_pd3dDevice != nullptr && wParam != SIZE_MINIMIZED) { CleanupRenderTarget(); - HRESULT result = g_pSwapChain->ResizeBuffers(0, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam), DXGI_FORMAT_UNKNOWN, DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT); + DXGI_SWAP_CHAIN_DESC1 desc = {}; + g_pSwapChain->GetDesc1(&desc); + HRESULT result = g_pSwapChain->ResizeBuffers(0, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam), desc.Format, desc.Flags); assert(SUCCEEDED(result) && "Failed to resize swapchain."); CreateRenderTarget(); }