Backends: DirectX12: enable swapchain tearing if available. (#8965)

This commit is contained in:
ocornut
2025-09-29 18:30:31 +02:00
parent bd9a37bc60
commit b4514ce64a
3 changed files with 43 additions and 18 deletions

View File

@@ -24,7 +24,8 @@
// (minor and older changes stripped away, please see git history for details) // (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-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2025-09-29: DirectX12: Rework synchronization logic. (#8961) // 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-09-18: Call platform_io.ClearRendererHandlers() on shutdown.
// 2025-06-19: Fixed build on MinGW. (#8702, #4594) // 2025-06-19: Fixed build on MinGW. (#8702, #4594)
// 2025-06-11: DirectX12: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. // 2025-06-11: DirectX12: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas.
@@ -62,7 +63,7 @@
// DirectX // DirectX
#include <d3d12.h> #include <d3d12.h>
#include <dxgi1_4.h> #include <dxgi1_5.h>
#include <d3dcompiler.h> #include <d3dcompiler.h>
#ifdef _MSC_VER #ifdef _MSC_VER
#pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below. #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 struct ImGui_ImplDX12_Data
{ {
ImGui_ImplDX12_InitInfo InitInfo; ImGui_ImplDX12_InitInfo InitInfo;
IDXGIFactory5* pdxgiFactory;
ID3D12Device* pd3dDevice; ID3D12Device* pd3dDevice;
ID3D12RootSignature* pRootSignature; ID3D12RootSignature* pRootSignature;
ID3D12PipelineState* pPipelineState; ID3D12PipelineState* pPipelineState;
@@ -104,6 +106,8 @@ struct ImGui_ImplDX12_Data
UINT64 FenceLastSignaledValue; UINT64 FenceLastSignaledValue;
HANDLE FenceEvent; HANDLE FenceEvent;
UINT numFramesInFlight; UINT numFramesInFlight;
bool tearingSupport;
bool LegacySingleDescriptorUsed;
ID3D12CommandAllocator* pTexCmdAllocator; ID3D12CommandAllocator* pTexCmdAllocator;
ID3D12GraphicsCommandList* pTexCmdList; ID3D12GraphicsCommandList* pTexCmdList;
@@ -111,8 +115,6 @@ struct ImGui_ImplDX12_Data
ImGui_ImplDX12_RenderBuffers* pFrameResources; ImGui_ImplDX12_RenderBuffers* pFrameResources;
UINT frameIndex; UINT frameIndex;
bool LegacySingleDescriptorUsed;
ImGui_ImplDX12_Data() { memset((void*)this, 0, sizeof(*this)); } ImGui_ImplDX12_Data() { memset((void*)this, 0, sizeof(*this)); }
}; };
@@ -623,6 +625,13 @@ bool ImGui_ImplDX12_CreateDeviceObjects()
if (bd->pPipelineState) if (bd->pPipelineState)
ImGui_ImplDX12_InvalidateDeviceObjects(); 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 // Create the root signature
{ {
D3D12_DESCRIPTOR_RANGE descRange = {}; D3D12_DESCRIPTOR_RANGE descRange = {};
@@ -845,7 +854,7 @@ bool ImGui_ImplDX12_CreateDeviceObjects()
return false; return false;
// Create command allocator and command list for ImGui_ImplDX12_UpdateTexture() // 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)); IM_ASSERT(SUCCEEDED(hr));
hr = bd->pd3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, bd->pTexCmdAllocator, nullptr, IID_PPV_ARGS(&bd->pTexCmdList)); hr = bd->pd3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, bd->pTexCmdAllocator, nullptr, IID_PPV_ARGS(&bd->pTexCmdList));
IM_ASSERT(SUCCEEDED(hr)); IM_ASSERT(SUCCEEDED(hr));
@@ -874,6 +883,7 @@ void ImGui_ImplDX12_InvalidateDeviceObjects()
if (!bd || !bd->pd3dDevice) if (!bd || !bd->pd3dDevice)
return; return;
SafeRelease(bd->pdxgiFactory);
if (bd->commandQueueOwned) if (bd->commandQueueOwned)
SafeRelease(bd->pCommandQueue); SafeRelease(bd->pCommandQueue);
bd->commandQueueOwned = false; bd->commandQueueOwned = false;
@@ -931,6 +941,7 @@ bool ImGui_ImplDX12_Init(ImGui_ImplDX12_InitInfo* init_info)
bd->DSVFormat = init_info->DSVFormat; bd->DSVFormat = init_info->DSVFormat;
bd->numFramesInFlight = init_info->NumFramesInFlight; bd->numFramesInFlight = init_info->NumFramesInFlight;
bd->pd3dSrvDescHeap = init_info->SrvDescriptorHeap; bd->pd3dSrvDescHeap = init_info->SrvDescriptorHeap;
bd->tearingSupport = false;
io.BackendRendererUserData = (void*)bd; io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_dx12"; io.BackendRendererName = "imgui_impl_dx12";
@@ -1074,18 +1085,15 @@ static void ImGui_ImplDX12_CreateWindow(ImGuiViewport* viewport)
sd1.Stereo = FALSE; sd1.Stereo = FALSE;
sd1.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; sd1.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
IDXGIFactory4* dxgi_factory = nullptr; if (bd->tearingSupport)
res = ::CreateDXGIFactory1(IID_PPV_ARGS(&dxgi_factory)); sd1.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
IM_ASSERT(res == S_OK);
IDXGISwapChain1* swap_chain = nullptr; 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); 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); IM_ASSERT(res == S_OK);
dxgi_factory->Release();
// Or swapChain.As(&mSwapChain) // Or swapChain.As(&mSwapChain)
IM_ASSERT(vd->SwapChain == nullptr); IM_ASSERT(vd->SwapChain == nullptr);
swap_chain->QueryInterface(IID_PPV_ARGS(&vd->SwapChain)); swap_chain->QueryInterface(IID_PPV_ARGS(&vd->SwapChain));
@@ -1197,7 +1205,9 @@ static void ImGui_ImplDX12_SetWindowSize(ImGuiViewport* viewport, ImVec2 size)
if (vd->SwapChain) if (vd->SwapChain)
{ {
ID3D12Resource* back_buffer = nullptr; 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++) for (UINT i = 0; i < bd->numFramesInFlight; i++)
{ {
vd->SwapChain->GetBuffer(i, IID_PPV_ARGS(&back_buffer)); 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*) static void ImGui_ImplDX12_SwapBuffers(ImGuiViewport* viewport, void*)
{ {
ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
ImGui_ImplDX12_ViewportData* vd = (ImGui_ImplDX12_ViewportData*)viewport->RendererUserData; 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++; vd->FrameIndex++;
} }

View File

@@ -80,6 +80,7 @@ Other Changes:
- Backends: DirectX12: reuse a command list and allocator for texture uploads instead - Backends: DirectX12: reuse a command list and allocator for texture uploads instead
of recreating them each time. (#8963, #8465) [@RT2Code] of recreating them each time. (#8963, #8465) [@RT2Code]
- Backends: DirectX12: Rework synchronization logic. (#8961) [@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 - Backends: OpenGL3: fixed GL loader to work on Haiku OS which does not support
`RTLD_NOLOAD`. (#8952) [@Xottab-DUTY, @threedeyes] `RTLD_NOLOAD`. (#8952) [@Xottab-DUTY, @threedeyes]
- Backends: GLFW: fixed build on platform that are neither Windows, macOS or - Backends: GLFW: fixed build on platform that are neither Windows, macOS or

View File

@@ -10,7 +10,7 @@
#include "imgui_impl_win32.h" #include "imgui_impl_win32.h"
#include "imgui_impl_dx12.h" #include "imgui_impl_dx12.h"
#include <d3d12.h> #include <d3d12.h>
#include <dxgi1_4.h> #include <dxgi1_5.h>
#include <tchar.h> #include <tchar.h>
#ifdef _DEBUG #ifdef _DEBUG
@@ -92,6 +92,7 @@ static ID3D12Fence* g_fence = nullptr;
static HANDLE g_fenceEvent = nullptr; static HANDLE g_fenceEvent = nullptr;
static UINT64 g_fenceLastSignaledValue = 0; static UINT64 g_fenceLastSignaledValue = 0;
static IDXGISwapChain3* g_pSwapChain = nullptr; static IDXGISwapChain3* g_pSwapChain = nullptr;
static bool g_SwapChainTearingSupport = false;
static bool g_SwapChainOccluded = false; static bool g_SwapChainOccluded = false;
static HANDLE g_hSwapChainWaitableObject = nullptr; static HANDLE g_hSwapChainWaitableObject = nullptr;
static ID3D12Resource* g_mainRenderTargetResource[APP_NUM_BACK_BUFFERS] = {}; static ID3D12Resource* g_mainRenderTargetResource[APP_NUM_BACK_BUFFERS] = {};
@@ -308,7 +309,7 @@ int main(int, char**)
// Present // Present
HRESULT hr = g_pSwapChain->Present(1, 0); // Present with vsync 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_SwapChainOccluded = (hr == DXGI_STATUS_OCCLUDED);
g_frameIndex++; g_frameIndex++;
} }
@@ -427,14 +428,24 @@ bool CreateDeviceD3D(HWND hWnd)
return false; return false;
{ {
IDXGIFactory4* dxgiFactory = nullptr; IDXGIFactory5* dxgiFactory = nullptr;
IDXGISwapChain1* swapChain1 = nullptr; IDXGISwapChain1* swapChain1 = nullptr;
if (CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory)) != S_OK) if (CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory)) != S_OK)
return false; 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) if (dxgiFactory->CreateSwapChainForHwnd(g_pd3dCommandQueue, hWnd, &sd, nullptr, nullptr, &swapChain1) != S_OK)
return false; return false;
if (swapChain1->QueryInterface(IID_PPV_ARGS(&g_pSwapChain)) != S_OK) if (swapChain1->QueryInterface(IID_PPV_ARGS(&g_pSwapChain)) != S_OK)
return false; return false;
if (g_SwapChainTearingSupport)
dxgiFactory->MakeWindowAssociation(hWnd, DXGI_MWA_NO_ALT_ENTER);
swapChain1->Release(); swapChain1->Release();
dxgiFactory->Release(); dxgiFactory->Release();
g_pSwapChain->SetMaximumFrameLatency(APP_NUM_BACK_BUFFERS); 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) if (g_pd3dDevice != nullptr && wParam != SIZE_MINIMIZED)
{ {
CleanupRenderTarget(); 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."); assert(SUCCEEDED(result) && "Failed to resize swapchain.");
CreateRenderTarget(); CreateRenderTarget();
} }