2323// CHANGELOG
2424// (minor and older changes stripped away, please see git history for details)
2525// 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
26+ // 2025-10-25: [Docking] DirectX12: Let user specifies viewports backbuffer count by setting ImGui_ImplDX12_InitInfo::BackBuffer and add support for a single frame in flight.
2627// 2025-10-23: [Docking] DirectX12: Fixed an issue in synchronization logic improving rendering throughput for secondary viewports. (#8961, #9025)
2728// 2025-10-11: DirectX12: Reuse texture upload buffer and grow it only when necessary. (#9002)
2829// 2025-09-29: DirectX12: Rework synchronization logic. (#8961)
@@ -107,6 +108,7 @@ struct ImGui_ImplDX12_Data
107108 ID3D12Fence* Fence;
108109 UINT64 FenceLastSignaledValue;
109110 HANDLE FenceEvent;
111+ UINT numBackbuffer;
110112 UINT numFramesInFlight;
111113 bool tearingSupport;
112114 bool LegacySingleDescriptorUsed;
@@ -136,13 +138,18 @@ struct ImGui_ImplDX12_RenderBuffers
136138 int VertexBufferSize;
137139};
138140
141+ // Backbuffers used for secondary viewports created by the multi-viewports systems
142+ struct ImGui_ImplDX12_Backbuffer
143+ {
144+ ID3D12Resource* RenderTarget;
145+ D3D12_CPU_DESCRIPTOR_HANDLE RenderTargetCpuDescriptors;
146+ };
147+
139148// Buffers used for secondary viewports created by the multi-viewports systems
140149struct ImGui_ImplDX12_FrameContext
141150{
142151 UINT64 FenceValue;
143152 ID3D12CommandAllocator* CommandAllocator;
144- ID3D12Resource* RenderTarget;
145- D3D12_CPU_DESCRIPTOR_HANDLE RenderTargetCpuDescriptors;
146153};
147154
148155// Helper structure we store in the void* RendererUserData field of each ImGuiViewport to easily retrieve our backend data.
@@ -156,30 +163,35 @@ struct ImGui_ImplDX12_ViewportData
156163 ID3D12DescriptorHeap* RtvDescHeap;
157164 IDXGISwapChain3* SwapChain;
158165 HANDLE SwapChainWaitableObject;
166+ UINT NumBackBuffer;
167+ ImGui_ImplDX12_Backbuffer* BackBuffer;
159168 UINT NumFramesInFlight;
160169 ImGui_ImplDX12_FrameContext* FrameCtx;
161-
162170 // Render buffers
163171 UINT FrameIndex;
164172 ImGui_ImplDX12_RenderBuffers* FrameRenderBuffers;
165173
166- ImGui_ImplDX12_ViewportData (UINT num_frames_in_flight)
174+ ImGui_ImplDX12_ViewportData (UINT num_backbuffer, UINT num_frames_in_flight)
167175 {
168176 CommandQueue = nullptr ;
169177 CommandList = nullptr ;
170178 RtvDescHeap = nullptr ;
171179 SwapChain = nullptr ;
172180 SwapChainWaitableObject = nullptr ;
181+ NumBackBuffer = num_backbuffer;
182+ BackBuffer = new ImGui_ImplDX12_Backbuffer[num_backbuffer];
173183 NumFramesInFlight = num_frames_in_flight;
174184 FrameCtx = new ImGui_ImplDX12_FrameContext[NumFramesInFlight];
175185 FrameIndex = 0 ;
176186 FrameRenderBuffers = new ImGui_ImplDX12_RenderBuffers[NumFramesInFlight];
177187
188+ for (UINT i = 0 ; i < NumBackBuffer; ++i)
189+ BackBuffer[i].RenderTarget = nullptr ;
190+
178191 for (UINT i = 0 ; i < NumFramesInFlight; ++i)
179192 {
180193 FrameCtx[i].FenceValue = 0 ;
181194 FrameCtx[i].CommandAllocator = nullptr ;
182- FrameCtx[i].RenderTarget = nullptr ;
183195
184196 // Create buffers with a default size (they will later be grown as needed)
185197 FrameRenderBuffers[i].IndexBuffer = nullptr ;
@@ -195,12 +207,16 @@ struct ImGui_ImplDX12_ViewportData
195207 IM_ASSERT (SwapChain == nullptr );
196208 IM_ASSERT (SwapChainWaitableObject == nullptr );
197209
210+ for (UINT i = 0 ; i < NumBackBuffer; ++i)
211+ IM_ASSERT (BackBuffer[i].RenderTarget == nullptr );
212+
198213 for (UINT i = 0 ; i < NumFramesInFlight; ++i)
199214 {
200- IM_ASSERT (FrameCtx[i].CommandAllocator == nullptr && FrameCtx[i]. RenderTarget == nullptr );
215+ IM_ASSERT (FrameCtx[i].CommandAllocator == nullptr );
201216 IM_ASSERT (FrameRenderBuffers[i].IndexBuffer == nullptr && FrameRenderBuffers[i].VertexBuffer == nullptr );
202217 }
203218
219+ delete[] BackBuffer; BackBuffer = nullptr ;
204220 delete[] FrameCtx; FrameCtx = nullptr ;
205221 delete[] FrameRenderBuffers; FrameRenderBuffers = nullptr ;
206222 }
@@ -955,6 +971,7 @@ bool ImGui_ImplDX12_Init(ImGui_ImplDX12_InitInfo* init_info)
955971 bd->pCommandQueue = init_info->CommandQueue ;
956972 bd->RTVFormat = init_info->RTVFormat ;
957973 bd->DSVFormat = init_info->DSVFormat ;
974+ bd->numBackbuffer = init_info->NumBackBuffer >= 2 ? init_info->NumBackBuffer : 2 ;
958975 bd->numFramesInFlight = init_info->NumFramesInFlight ;
959976 bd->pd3dSrvDescHeap = init_info->SrvDescriptorHeap ;
960977 bd->tearingSupport = false ;
@@ -971,7 +988,7 @@ bool ImGui_ImplDX12_Init(ImGui_ImplDX12_InitInfo* init_info)
971988 // Create a dummy ImGui_ImplDX12_ViewportData holder for the main viewport,
972989 // Since this is created and managed by the application, we will only use the ->Resources[] fields.
973990 ImGuiViewport* main_viewport = ImGui::GetMainViewport ();
974- main_viewport->RendererUserData = IM_NEW (ImGui_ImplDX12_ViewportData)(bd->numFramesInFlight );
991+ main_viewport->RendererUserData = IM_NEW (ImGui_ImplDX12_ViewportData)(0 , bd->numFramesInFlight );
975992
976993#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
977994 if (init_info->SrvDescriptorAllocFn == nullptr )
@@ -1059,7 +1076,7 @@ void ImGui_ImplDX12_NewFrame()
10591076static void ImGui_ImplDX12_CreateWindow (ImGuiViewport* viewport)
10601077{
10611078 ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData ();
1062- ImGui_ImplDX12_ViewportData* vd = IM_NEW (ImGui_ImplDX12_ViewportData)(bd->numFramesInFlight );
1079+ ImGui_ImplDX12_ViewportData* vd = IM_NEW (ImGui_ImplDX12_ViewportData)(bd->numBackbuffer , bd-> numFramesInFlight );
10631080 viewport->RendererUserData = vd;
10641081
10651082 // PlatformHandleRaw should always be a HWND, whereas PlatformHandle might be a higher-level handle (e.g. GLFWWindow*, SDL's WindowID).
@@ -1088,7 +1105,7 @@ static void ImGui_ImplDX12_CreateWindow(ImGuiViewport* viewport)
10881105 // FIXME-VIEWPORT: May want to copy/inherit swap chain settings from the user/application.
10891106 DXGI_SWAP_CHAIN_DESC1 sd1;
10901107 ZeroMemory (&sd1, sizeof (sd1));
1091- sd1.BufferCount = bd->numFramesInFlight ;
1108+ sd1.BufferCount = bd->numBackbuffer ;
10921109 sd1.Width = (UINT)viewport->Size .x ;
10931110 sd1.Height = (UINT)viewport->Size .y ;
10941111 sd1.Format = bd->RTVFormat ;
@@ -1120,7 +1137,7 @@ static void ImGui_ImplDX12_CreateWindow(ImGuiViewport* viewport)
11201137 {
11211138 D3D12_DESCRIPTOR_HEAP_DESC desc = {};
11221139 desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
1123- desc.NumDescriptors = bd->numFramesInFlight ;
1140+ desc.NumDescriptors = bd->numBackbuffer ;
11241141 desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
11251142 desc.NodeMask = 1 ;
11261143
@@ -1129,22 +1146,19 @@ static void ImGui_ImplDX12_CreateWindow(ImGuiViewport* viewport)
11291146
11301147 SIZE_T rtv_descriptor_size = bd->pd3dDevice ->GetDescriptorHandleIncrementSize (D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
11311148 D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle = vd->RtvDescHeap ->GetCPUDescriptorHandleForHeapStart ();
1132- for (UINT i = 0 ; i < bd->numFramesInFlight ; i++)
1149+ ID3D12Resource* back_buffer;
1150+ for (UINT i = 0 ; i < bd->numBackbuffer ; i++)
11331151 {
1134- vd->FrameCtx [i].RenderTargetCpuDescriptors = rtv_handle;
1152+ vd->BackBuffer [i].RenderTargetCpuDescriptors = rtv_handle;
11351153 rtv_handle.ptr += rtv_descriptor_size;
1136- }
11371154
1138- ID3D12Resource* back_buffer;
1139- for (UINT i = 0 ; i < bd->numFramesInFlight ; i++)
1140- {
1141- IM_ASSERT (vd->FrameCtx [i].RenderTarget == nullptr );
1155+ IM_ASSERT (vd->BackBuffer [i].RenderTarget == nullptr );
11421156 vd->SwapChain ->GetBuffer (i, IID_PPV_ARGS (&back_buffer));
1143- bd->pd3dDevice ->CreateRenderTargetView (back_buffer, nullptr , vd->FrameCtx [i].RenderTargetCpuDescriptors );
1144- vd->FrameCtx [i].RenderTarget = back_buffer;
1157+ bd->pd3dDevice ->CreateRenderTargetView (back_buffer, nullptr , vd->BackBuffer [i].RenderTargetCpuDescriptors );
1158+ vd->BackBuffer [i].RenderTarget = back_buffer;
11451159 }
11461160
1147- hr = vd->SwapChain ->SetMaximumFrameLatency (bd->numFramesInFlight );
1161+ hr = vd->SwapChain ->SetMaximumFrameLatency (bd->numBackbuffer );
11481162 IM_ASSERT (hr == S_OK);
11491163 vd->SwapChainWaitableObject = vd->SwapChain ->GetFrameLatencyWaitableObject ();
11501164 }
@@ -1197,9 +1211,11 @@ static void ImGui_ImplDX12_DestroyWindow(ImGuiViewport* viewport)
11971211 SafeRelease (vd->SwapChain );
11981212 SafeRelease (vd->RtvDescHeap );
11991213
1214+ for (UINT i = 0 ; i < bd->numBackbuffer ; i++)
1215+ SafeRelease (vd->BackBuffer [i].RenderTarget );
1216+
12001217 for (UINT i = 0 ; i < bd->numFramesInFlight ; i++)
12011218 {
1202- SafeRelease (vd->FrameCtx [i].RenderTarget );
12031219 SafeRelease (vd->FrameCtx [i].CommandAllocator );
12041220 ImGui_ImplDX12_DestroyRenderBuffers (&vd->FrameRenderBuffers [i]);
12051221 }
@@ -1215,20 +1231,20 @@ static void ImGui_ImplDX12_SetWindowSize(ImGuiViewport* viewport, ImVec2 size)
12151231
12161232 ImGui_WaitForPendingOperations (vd);
12171233
1218- for (UINT i = 0 ; i < bd->numFramesInFlight ; i++)
1219- SafeRelease (vd->FrameCtx [i].RenderTarget );
1234+ for (UINT i = 0 ; i < bd->numBackbuffer ; i++)
1235+ SafeRelease (vd->BackBuffer [i].RenderTarget );
12201236
12211237 if (vd->SwapChain )
12221238 {
12231239 ID3D12Resource* back_buffer = nullptr ;
12241240 DXGI_SWAP_CHAIN_DESC1 desc = {};
12251241 vd->SwapChain ->GetDesc1 (&desc);
12261242 vd->SwapChain ->ResizeBuffers (0 , (UINT)size.x , (UINT)size.y , desc.Format , desc.Flags );
1227- for (UINT i = 0 ; i < bd->numFramesInFlight ; i++)
1243+ for (UINT i = 0 ; i < bd->numBackbuffer ; i++)
12281244 {
12291245 vd->SwapChain ->GetBuffer (i, IID_PPV_ARGS (&back_buffer));
1230- bd->pd3dDevice ->CreateRenderTargetView (back_buffer, nullptr , vd->FrameCtx [i].RenderTargetCpuDescriptors );
1231- vd->FrameCtx [i].RenderTarget = back_buffer;
1246+ bd->pd3dDevice ->CreateRenderTargetView (back_buffer, nullptr , vd->BackBuffer [i].RenderTargetCpuDescriptors );
1247+ vd->BackBuffer [i].RenderTarget = back_buffer;
12321248 }
12331249 }
12341250}
@@ -1245,7 +1261,7 @@ static void ImGui_ImplDX12_RenderWindow(ImGuiViewport* viewport, void*)
12451261 D3D12_RESOURCE_BARRIER barrier = {};
12461262 barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
12471263 barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
1248- barrier.Transition .pResource = vd->FrameCtx [back_buffer_idx].RenderTarget ;
1264+ barrier.Transition .pResource = vd->BackBuffer [back_buffer_idx].RenderTarget ;
12491265 barrier.Transition .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
12501266 barrier.Transition .StateBefore = D3D12_RESOURCE_STATE_PRESENT;
12511267 barrier.Transition .StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
@@ -1256,9 +1272,9 @@ static void ImGui_ImplDX12_RenderWindow(ImGuiViewport* viewport, void*)
12561272 frame_context->CommandAllocator ->Reset ();
12571273 cmd_list->Reset (frame_context->CommandAllocator , nullptr );
12581274 cmd_list->ResourceBarrier (1 , &barrier);
1259- cmd_list->OMSetRenderTargets (1 , &vd->FrameCtx [back_buffer_idx].RenderTargetCpuDescriptors , FALSE , nullptr );
1275+ cmd_list->OMSetRenderTargets (1 , &vd->BackBuffer [back_buffer_idx].RenderTargetCpuDescriptors , FALSE , nullptr );
12601276 if (!(viewport->Flags & ImGuiViewportFlags_NoRendererClear))
1261- cmd_list->ClearRenderTargetView (vd->FrameCtx [back_buffer_idx].RenderTargetCpuDescriptors , (const float *)&clear_color, 0 , nullptr );
1277+ cmd_list->ClearRenderTargetView (vd->BackBuffer [back_buffer_idx].RenderTargetCpuDescriptors , (const float *)&clear_color, 0 , nullptr );
12621278 cmd_list->SetDescriptorHeaps (1 , &bd->pd3dSrvDescHeap );
12631279
12641280 ImGui_ImplDX12_RenderDrawData (viewport->DrawData , cmd_list);
0 commit comments