(Breaking) Backends: DX12: changed ImGui_ImplDX12_Init() signature. Added ImGui_ImplDX12_InitInfo. Added support for Srv allocators.

Ref 7708
This commit is contained in:
ocornut 2024-11-15 19:02:26 +01:00
parent 3260ea6954
commit 40b2286d16
4 changed files with 155 additions and 28 deletions

View File

@ -19,6 +19,8 @@
// CHANGELOG // CHANGELOG
// (minor and older changes stripped away, please see git history for details) // (minor and older changes stripped away, please see git history for details)
// 2024-11-15: DirectX12: *BREAKING CHANGE* Changed ImGui_ImplDX12_Init() signature to take a ImGui_ImplDX12_InitInfo struct. Legacy ImGui_ImplDX12_Init() signature is still supported (will obsolete).
// 2024-11-15: DirectX12: *BREAKING CHANGE* User is now required to pass function pointers to allocate/free SRV Descriptors. We provide convenience legacy fields to pass a single descriptor, matching the old API, but upcoming features will want multiple.
// 2024-10-23: DirectX12: Unmap() call specify written range. The range is informational and may be used by debug tools. // 2024-10-23: DirectX12: Unmap() call specify written range. The range is informational and may be used by debug tools.
// 2024-10-07: DirectX12: Changed default texture sampler to Clamp instead of Repeat/Wrap. // 2024-10-07: DirectX12: Changed default texture sampler to Clamp instead of Repeat/Wrap.
// 2024-10-07: DirectX12: Expose selected render state in ImGui_ImplDX12_RenderState, which you can access in 'void* platform_io.Renderer_RenderState' during draw callbacks. // 2024-10-07: DirectX12: Expose selected render state in ImGui_ImplDX12_RenderState, which you can access in 'void* platform_io.Renderer_RenderState' during draw callbacks.
@ -57,6 +59,7 @@
struct ImGui_ImplDX12_RenderBuffers; struct ImGui_ImplDX12_RenderBuffers;
struct ImGui_ImplDX12_Data struct ImGui_ImplDX12_Data
{ {
ImGui_ImplDX12_InitInfo InitInfo;
ID3D12Device* pd3dDevice; ID3D12Device* pd3dDevice;
ID3D12RootSignature* pRootSignature; ID3D12RootSignature* pRootSignature;
ID3D12PipelineState* pPipelineState; ID3D12PipelineState* pPipelineState;
@ -695,8 +698,14 @@ void ImGui_ImplDX12_InvalidateDeviceObjects()
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
SafeRelease(bd->pRootSignature); SafeRelease(bd->pRootSignature);
SafeRelease(bd->pPipelineState); SafeRelease(bd->pPipelineState);
// Free SRV descriptor used by texture
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
if (bd->InitInfo.SrvDescriptorFreeFn != NULL)
#endif
bd->InitInfo.SrvDescriptorFreeFn(&bd->InitInfo, bd->hFontSrvCpuDescHandle, bd->hFontSrvGpuDescHandle);
SafeRelease(bd->pFontTextureResource); SafeRelease(bd->pFontTextureResource);
io.Fonts->SetTexID(0); // We copied bd->pFontTextureView to io.Fonts->TexID so let's clear that as well. io.Fonts->SetTexID(0); // We copied bd->hFontSrvGpuDescHandle to io.Fonts->TexID so let's clear that as well.
for (UINT i = 0; i < bd->numFramesInFlight; i++) for (UINT i = 0; i < bd->numFramesInFlight; i++)
{ {
@ -706,8 +715,7 @@ void ImGui_ImplDX12_InvalidateDeviceObjects()
} }
} }
bool ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames_in_flight, DXGI_FORMAT rtv_format, ID3D12DescriptorHeap* cbv_srv_heap, bool ImGui_ImplDX12_Init(ImGui_ImplDX12_InitInfo* init_info)
D3D12_CPU_DESCRIPTOR_HANDLE font_srv_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE font_srv_gpu_desc_handle)
{ {
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
IMGUI_CHECKVERSION(); IMGUI_CHECKVERSION();
@ -715,21 +723,39 @@ bool ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames_in_flight, DXGI_FO
// Setup backend capabilities flags // Setup backend capabilities flags
ImGui_ImplDX12_Data* bd = IM_NEW(ImGui_ImplDX12_Data)(); ImGui_ImplDX12_Data* bd = IM_NEW(ImGui_ImplDX12_Data)();
bd->InitInfo = *init_info; // Deep copy
bd->pd3dDevice = init_info->Device;
bd->RTVFormat = init_info->RTVFormat;
bd->numFramesInFlight = init_info->NumFramesInFlight;
bd->pd3dSrvDescHeap = init_info->SrvDescriptorHeap;
io.BackendRendererUserData = (void*)bd; io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_dx12"; io.BackendRendererName = "imgui_impl_dx12";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
bd->pd3dDevice = device; // Allocate 1 SRV descriptor for the font texture
bd->RTVFormat = rtv_format; if (init_info->SrvDescriptorAllocFn != NULL)
bd->hFontSrvCpuDescHandle = font_srv_cpu_desc_handle; {
bd->hFontSrvGpuDescHandle = font_srv_gpu_desc_handle; IM_ASSERT(init_info->SrvDescriptorFreeFn != NULL);
bd->pFrameResources = new ImGui_ImplDX12_RenderBuffers[num_frames_in_flight]; init_info->SrvDescriptorAllocFn(&bd->InitInfo, &bd->hFontSrvCpuDescHandle, &bd->hFontSrvGpuDescHandle);
bd->numFramesInFlight = num_frames_in_flight; }
bd->pd3dSrvDescHeap = cbv_srv_heap; else
bd->frameIndex = UINT_MAX; {
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
IM_ASSERT(init_info->LegacySingleSrvCpuDescriptor.ptr != 0 && init_info->LegacySingleSrvGpuDescriptor.ptr != 0);
bd->hFontSrvCpuDescHandle = init_info->LegacySingleSrvCpuDescriptor;
bd->hFontSrvGpuDescHandle = init_info->LegacySingleSrvGpuDescriptor;
#else
IM_ASSERT(init_info->SrvDescriptorAllocFn != NULL);
IM_ASSERT(init_info->SrvDescriptorFreeFn != NULL);
#endif
}
// Create buffers with a default size (they will later be grown as needed) // Create buffers with a default size (they will later be grown as needed)
for (int i = 0; i < num_frames_in_flight; i++) bd->frameIndex = UINT_MAX;
bd->pFrameResources = new ImGui_ImplDX12_RenderBuffers[bd->numFramesInFlight];
for (int i = 0; i < (int)bd->numFramesInFlight; i++)
{ {
ImGui_ImplDX12_RenderBuffers* fr = &bd->pFrameResources[i]; ImGui_ImplDX12_RenderBuffers* fr = &bd->pFrameResources[i];
fr->IndexBuffer = nullptr; fr->IndexBuffer = nullptr;
@ -741,6 +767,22 @@ bool ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames_in_flight, DXGI_FO
return true; return true;
} }
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
// Legacy initialization API Obsoleted in 1.91.5
// font_srv_cpu_desc_handle and font_srv_gpu_desc_handle are handles to a single SRV descriptor to use for the internal font texture, they must be in 'srv_descriptor_heap'
bool ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames_in_flight, DXGI_FORMAT rtv_format, ID3D12DescriptorHeap* srv_descriptor_heap, D3D12_CPU_DESCRIPTOR_HANDLE font_srv_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE font_srv_gpu_desc_handle)
{
ImGui_ImplDX12_InitInfo init_info;
init_info.Device = device;
init_info.NumFramesInFlight = num_frames_in_flight;
init_info.RTVFormat = rtv_format;
init_info.SrvDescriptorHeap = srv_descriptor_heap;
init_info.LegacySingleSrvCpuDescriptor = font_srv_cpu_desc_handle;
init_info.LegacySingleSrvGpuDescriptor = font_srv_gpu_desc_handle;;
return ImGui_ImplDX12_Init(&init_info);
}
#endif
void ImGui_ImplDX12_Shutdown() void ImGui_ImplDX12_Shutdown()
{ {
ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
@ -750,6 +792,7 @@ void ImGui_ImplDX12_Shutdown()
// Clean up windows and device objects // Clean up windows and device objects
ImGui_ImplDX12_InvalidateDeviceObjects(); ImGui_ImplDX12_InvalidateDeviceObjects();
delete[] bd->pFrameResources; delete[] bd->pFrameResources;
io.BackendRendererName = nullptr; io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr; io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;

View File

@ -21,24 +21,42 @@
#include "imgui.h" // IMGUI_IMPL_API #include "imgui.h" // IMGUI_IMPL_API
#ifndef IMGUI_DISABLE #ifndef IMGUI_DISABLE
#include <dxgiformat.h> // DXGI_FORMAT #include <dxgiformat.h> // DXGI_FORMAT
#include <d3d12.h> // D3D12_CPU_DESCRIPTOR_HANDLE
struct ID3D12Device; // Initialization data, for ImGui_ImplDX12_Init()
struct ID3D12DescriptorHeap; struct ImGui_ImplDX12_InitInfo
struct ID3D12GraphicsCommandList; {
struct D3D12_CPU_DESCRIPTOR_HANDLE; ID3D12Device* Device;
struct D3D12_GPU_DESCRIPTOR_HANDLE; ID3D12CommandQueue* CommandQueue;
int NumFramesInFlight;
DXGI_FORMAT RTVFormat;
void* UserData;
// Allocating SRV descriptors for textures is up to the application, so we provide callbacks.
// (current version of the backend will only allocate one descriptor, future versions will need to allocate more)
ID3D12DescriptorHeap* SrvDescriptorHeap;
void (*SrvDescriptorAllocFn)(ImGui_ImplDX12_InitInfo* info, D3D12_CPU_DESCRIPTOR_HANDLE* out_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE* out_gpu_desc_handle);
void (*SrvDescriptorFreeFn)(ImGui_ImplDX12_InitInfo* info, D3D12_CPU_DESCRIPTOR_HANDLE cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE gpu_desc_handle);
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
D3D12_CPU_DESCRIPTOR_HANDLE LegacySingleSrvCpuDescriptor; // To facilitate transition from single descriptor to allocator callback, you may use those.
D3D12_GPU_DESCRIPTOR_HANDLE LegacySingleSrvGpuDescriptor;
#endif
ImGui_ImplDX12_InitInfo() { memset(this, 0, sizeof(*this)); }
};
// Follow "Getting Started" link and check examples/ folder to learn about using backends! // Follow "Getting Started" link and check examples/ folder to learn about using backends!
IMGUI_IMPL_API bool ImGui_ImplDX12_Init(ImGui_ImplDX12_InitInfo* info);
// Before calling the render function, caller must prepare the command list by resetting it and setting the appropriate
// render target and descriptor heap that contains font_srv_cpu_desc_handle/font_srv_gpu_desc_handle.
// font_srv_cpu_desc_handle and font_srv_gpu_desc_handle are handles to a single SRV descriptor to use for the internal font texture.
IMGUI_IMPL_API bool ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames_in_flight, DXGI_FORMAT rtv_format, ID3D12DescriptorHeap* cbv_srv_heap,
D3D12_CPU_DESCRIPTOR_HANDLE font_srv_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE font_srv_gpu_desc_handle);
IMGUI_IMPL_API void ImGui_ImplDX12_Shutdown(); IMGUI_IMPL_API void ImGui_ImplDX12_Shutdown();
IMGUI_IMPL_API void ImGui_ImplDX12_NewFrame(); IMGUI_IMPL_API void ImGui_ImplDX12_NewFrame();
IMGUI_IMPL_API void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandList* graphics_command_list); IMGUI_IMPL_API void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandList* graphics_command_list);
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
// Legacy initialization API Obsoleted in 1.91.5
// font_srv_cpu_desc_handle and font_srv_gpu_desc_handle are handles to a single SRV descriptor to use for the internal font texture, they must be in 'srv_descriptor_heap'
IMGUI_IMPL_API bool ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames_in_flight, DXGI_FORMAT rtv_format, ID3D12DescriptorHeap* srv_descriptor_heap, D3D12_CPU_DESCRIPTOR_HANDLE font_srv_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE font_srv_gpu_desc_handle);
#endif
// Use if you want to reset your rendering device without losing Dear ImGui state. // Use if you want to reset your rendering device without losing Dear ImGui state.
IMGUI_IMPL_API bool ImGui_ImplDX12_CreateDeviceObjects(); IMGUI_IMPL_API bool ImGui_ImplDX12_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplDX12_InvalidateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplDX12_InvalidateDeviceObjects();

View File

@ -41,10 +41,20 @@ HOW TO UPDATE?
Breaking changes: Breaking changes:
- Backends: DX12: Changed ImGui_ImplDX12_Init() signature to take a
ImGui_ImplDX12_InitInfo struct.
- Using the new API, application is now required to pass function pointers
to allocate/free SRV Descriptors.
- We provide convenience legacy fields to pass a single descriptor,
matching the old API, but upcoming features will want multiple.
- Legacy ImGui_ImplDX12_Init() signature is still supported (will obsolete).
Other changes: Other changes:
- Error Handling: fixed cases where recoverable error handling would crash when - Error Handling: fixed cases where recoverable error handling would crash when
processing errors outside of the NewFrame()..EndFrame() scope. (#1651) processing errors outside of the NewFrame()..EndFrame() scope. (#1651)
- Examples: Win32+DX12: Using a basic free-list allocator to manage multiple
SRV descriptors.
----------------------------------------------------------------------- -----------------------------------------------------------------------

View File

@ -25,6 +25,7 @@
// Config for example app // Config for example app
static const int APP_NUM_FRAMES_IN_FLIGHT = 3; static const int APP_NUM_FRAMES_IN_FLIGHT = 3;
static const int APP_NUM_BACK_BUFFERS = 3; static const int APP_NUM_BACK_BUFFERS = 3;
static const int APP_SRV_HEAP_SIZE = 64;
struct FrameContext struct FrameContext
{ {
@ -32,6 +33,51 @@ struct FrameContext
UINT64 FenceValue; UINT64 FenceValue;
}; };
// Simple free list based allocator
struct ExampleDescriptorHeapAllocator
{
ID3D12DescriptorHeap* Heap = nullptr;
D3D12_DESCRIPTOR_HEAP_TYPE HeapType = D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES;
D3D12_CPU_DESCRIPTOR_HANDLE HeapStartCpu;
D3D12_GPU_DESCRIPTOR_HANDLE HeapStartGpu;
UINT HeapHandleIncrement;
ImVector<int> FreeIndices;
void Create(ID3D12Device* device, ID3D12DescriptorHeap* heap)
{
IM_ASSERT(Heap == nullptr && FreeIndices.empty());
Heap = heap;
D3D12_DESCRIPTOR_HEAP_DESC desc = heap->GetDesc();
HeapType = desc.Type;
HeapStartCpu = Heap->GetCPUDescriptorHandleForHeapStart();
HeapStartGpu = Heap->GetGPUDescriptorHandleForHeapStart();
HeapHandleIncrement = device->GetDescriptorHandleIncrementSize(HeapType);
FreeIndices.reserve((int)desc.NumDescriptors);
for (int n = desc.NumDescriptors; n > 0; n--)
FreeIndices.push_back(n);
}
void Destroy()
{
Heap = NULL;
FreeIndices.clear();
}
void Alloc(D3D12_CPU_DESCRIPTOR_HANDLE* out_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE* out_gpu_desc_handle)
{
IM_ASSERT(FreeIndices.Size > 0);
int idx = FreeIndices.back();
FreeIndices.pop_back();
out_cpu_desc_handle->ptr = HeapStartCpu.ptr + (idx * HeapHandleIncrement);
out_gpu_desc_handle->ptr = HeapStartGpu.ptr + (idx * HeapHandleIncrement);
}
void Free(D3D12_CPU_DESCRIPTOR_HANDLE out_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE out_gpu_desc_handle)
{
int cpu_idx = (int)((out_cpu_desc_handle.ptr - HeapStartCpu.ptr) / HeapHandleIncrement);
int gpu_idx = (int)((out_gpu_desc_handle.ptr - HeapStartGpu.ptr) / HeapHandleIncrement);
IM_ASSERT(cpu_idx == gpu_idx);
FreeIndices.push_back(cpu_idx);
}
};
// Data // Data
static FrameContext g_frameContext[APP_NUM_FRAMES_IN_FLIGHT] = {}; static FrameContext g_frameContext[APP_NUM_FRAMES_IN_FLIGHT] = {};
static UINT g_frameIndex = 0; static UINT g_frameIndex = 0;
@ -39,6 +85,7 @@ static UINT g_frameIndex = 0;
static ID3D12Device* g_pd3dDevice = nullptr; static ID3D12Device* g_pd3dDevice = nullptr;
static ID3D12DescriptorHeap* g_pd3dRtvDescHeap = nullptr; static ID3D12DescriptorHeap* g_pd3dRtvDescHeap = nullptr;
static ID3D12DescriptorHeap* g_pd3dSrvDescHeap = nullptr; static ID3D12DescriptorHeap* g_pd3dSrvDescHeap = nullptr;
static ExampleDescriptorHeapAllocator g_pd3dSrvDescHeapAlloc;
static ID3D12CommandQueue* g_pd3dCommandQueue = nullptr; static ID3D12CommandQueue* g_pd3dCommandQueue = nullptr;
static ID3D12GraphicsCommandList* g_pd3dCommandList = nullptr; static ID3D12GraphicsCommandList* g_pd3dCommandList = nullptr;
static ID3D12Fence* g_fence = nullptr; static ID3D12Fence* g_fence = nullptr;
@ -93,10 +140,18 @@ int main(int, char**)
// Setup Platform/Renderer backends // Setup Platform/Renderer backends
ImGui_ImplWin32_Init(hwnd); ImGui_ImplWin32_Init(hwnd);
ImGui_ImplDX12_Init(g_pd3dDevice, NUM_FRAMES_IN_FLIGHT,
DXGI_FORMAT_R8G8B8A8_UNORM, g_pd3dSrvDescHeap, ImGui_ImplDX12_InitInfo init_info = {};
g_pd3dSrvDescHeap->GetCPUDescriptorHandleForHeapStart(), init_info.Device = g_pd3dDevice;
g_pd3dSrvDescHeap->GetGPUDescriptorHandleForHeapStart()); init_info.CommandQueue = g_pd3dCommandQueue;
init_info.NumFramesInFlight = APP_NUM_FRAMES_IN_FLIGHT;
init_info.RTVFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
// Allocating SRV descriptors (for textures) is up to the application, so we provide callbacks.
// (current version of the backend will only allocate one descriptor, future versions will need to allocate more)
init_info.SrvDescriptorHeap = g_pd3dSrvDescHeap;
init_info.SrvDescriptorAllocFn = [](ImGui_ImplDX12_InitInfo*, D3D12_CPU_DESCRIPTOR_HANDLE* out_cpu_handle, D3D12_GPU_DESCRIPTOR_HANDLE* out_gpu_handle) { return g_pd3dSrvDescHeapAlloc.Alloc(out_cpu_handle, out_gpu_handle); };
init_info.SrvDescriptorFreeFn = [](ImGui_ImplDX12_InitInfo*, D3D12_CPU_DESCRIPTOR_HANDLE cpu_handle, D3D12_GPU_DESCRIPTOR_HANDLE gpu_handle) { return g_pd3dSrvDescHeapAlloc.Free(cpu_handle, gpu_handle); };
ImGui_ImplDX12_Init(&init_info);
// Load Fonts // Load Fonts
// - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them.
@ -310,10 +365,11 @@ bool CreateDeviceD3D(HWND hWnd)
{ {
D3D12_DESCRIPTOR_HEAP_DESC desc = {}; D3D12_DESCRIPTOR_HEAP_DESC desc = {};
desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
desc.NumDescriptors = 1; desc.NumDescriptors = APP_SRV_HEAP_SIZE;
desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
if (g_pd3dDevice->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&g_pd3dSrvDescHeap)) != S_OK) if (g_pd3dDevice->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&g_pd3dSrvDescHeap)) != S_OK)
return false; return false;
g_pd3dSrvDescHeapAlloc.Create(g_pd3dDevice, g_pd3dSrvDescHeap);
} }
{ {