diff --git a/src/d3d12/D3D12.h b/src/d3d12/D3D12.h index 0c64197b..97e94a0d 100644 --- a/src/d3d12/D3D12.h +++ b/src/d3d12/D3D12.h @@ -100,4 +100,5 @@ struct D3D12 std::array m_imguiDrawDataBuffers; std::atomic_bool m_delayedTrapInput{false}; std::atomic_bool m_delayedTrapInputState{false}; + bool m_imGuiInitialized{false}; }; diff --git a/src/d3d12/D3D12_Functions.cpp b/src/d3d12/D3D12_Functions.cpp index ca23e2d6..749df10b 100644 --- a/src/d3d12/D3D12_Functions.cpp +++ b/src/d3d12/D3D12_Functions.cpp @@ -22,11 +22,16 @@ bool D3D12::ResetState(const bool acClearDownlevelBackbuffers, const bool acDest drawData.Clear(); } - ImGui_ImplDX12_Shutdown(); - ImGui_ImplWin32_Shutdown(); + ImGui::GetIO().Fonts->Clear(); if (acDestroyContext) + { + ImGui_ImplDX12_Shutdown(); + ImGui_ImplWin32_Shutdown(); ImGui::DestroyContext(); + + m_imGuiInitialized = false; + } } m_frameContexts.clear(); @@ -278,14 +283,15 @@ bool D3D12::InitializeDownlevel(ID3D12CommandQueue* apCommandQueue, ID3D12Resour void D3D12::ReloadFonts() { std::lock_guard _(m_imguiLock); + + auto& io = ImGui::GetIO(); + if (io.Fonts->IsBuilt()) + return; // TODO - scale also by DPI const auto [resx, resy] = m_outSize; const auto scaleFromReference = std::min(static_cast(resx) / 1920.0f, static_cast(resy) / 1080.0f); - auto& io = ImGui::GetIO(); - io.Fonts->Clear(); - ImFontConfig config; const auto& fontSettings = m_options.Font; config.SizePixels = std::floorf(fontSettings.BaseSize * scaleFromReference); @@ -415,12 +421,16 @@ void D3D12::ReloadFonts() static const ImWchar icon_ranges[] = {ICON_MIN_MD, ICON_MAX_MD, 0}; auto cetIconPath = GetAbsolutePath(L"materialdesignicons.ttf", m_paths.Fonts(), false); io.Fonts->AddFontFromFileTTF(UTF16ToUTF8(cetIconPath.native()).c_str(), config.SizePixels, &config, icon_ranges); + io.Fonts->Build(); } bool D3D12::InitializeImGui(size_t aBuffersCounts) { std::lock_guard _(m_imguiLock); + if (m_imGuiInitialized) + return true; + // TODO - scale also by DPI const auto [resx, resy] = m_outSize; const auto scaleFromReference = std::min(static_cast(resx) / 1920.0f, static_cast(resy) / 1080.0f); @@ -469,9 +479,13 @@ bool D3D12::InitializeImGui(size_t aBuffersCounts) Log::Error("D3D12::InitializeImGui() - ImGui_ImplDX12_CreateDeviceObjects call failed!"); ImGui_ImplDX12_Shutdown(); ImGui_ImplWin32_Shutdown(); + + ImGui::GetIO().Fonts->Clear(); + return false; } + m_imGuiInitialized = true; return true; } @@ -482,6 +496,13 @@ void D3D12::PrepareUpdate() std::lock_guard _(m_imguiLock); + if (!ImGui::GetIO().Fonts->IsBuilt()) + { + ReloadFonts(); + + ImGui_ImplDX12_CreateDeviceObjects(m_pCommandQueue.Get()); + } + ImGui_ImplWin32_NewFrame(m_outSize); ImGui::NewFrame(); diff --git a/src/imgui_impl/dx12.cpp b/src/imgui_impl/dx12.cpp index e9deb1c1..153d994d 100644 --- a/src/imgui_impl/dx12.cpp +++ b/src/imgui_impl/dx12.cpp @@ -4,8 +4,6 @@ // Implemented features: // [X] Renderer: User texture binding. Use 'D3D12_GPU_DESCRIPTOR_HANDLE' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices. -// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. -// FIXME: The transition from removing a viewport and moving the window in an existing hosted viewport tends to flicker. // Important: to compile on 32-bit systems, this backend requires code to be compiled with '#define ImTextureID ImU64'. // This is because we need ImTextureID to carry a 64-bit value and by default ImTextureID is defined as void*. @@ -27,10 +25,10 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) -// 2024-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. -// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11. -// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). -// 2021-05-19: DirectX12: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) +// 2023-XX-XX: *BREAKING CHANGE*: DirectX12: Changed ImGui_ImplDX12_CreateFontsTexture() to ImGui_ImplDX12_UpdateFontsTexture(), which should be called at the start of the frame +// when ImGui::GetIO().Fonts->IsDirty() is true to reupload the font texture. ImGuiBackendFlags_RendererHasTexReload should be set once this is implemented. 2022-10-11: Using +// 'nullptr' instead of 'NULL' as per our switch to C++11. 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all +// g_XXXX access changed to bd->XXXX). 2021-05-19: DirectX12: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) // 2021-02-18: DirectX12: Change blending equation to preserve alpha in output buffer. // 2021-01-11: DirectX12: Improve Windows 7 compatibility (for D3D12On7) by loading d3d12.dll dynamically. // 2020-09-16: DirectX12: Avoid rendering calls with zero-sized scissor rectangle since it generates a validation layer warning. @@ -61,6 +59,7 @@ #endif // DirectX data +struct ImGui_ImplDX12_RenderBuffers; struct ImGui_ImplDX12_Data { ID3D12Device* pd3dDevice; @@ -68,12 +67,21 @@ struct ImGui_ImplDX12_Data ID3D12PipelineState* pPipelineState; DXGI_FORMAT RTVFormat; ID3D12Resource* pFontTextureResource; + int FontTextureWidth; + int FontTextureHeight; D3D12_CPU_DESCRIPTOR_HANDLE hFontSrvCpuDescHandle; D3D12_GPU_DESCRIPTOR_HANDLE hFontSrvGpuDescHandle; ID3D12DescriptorHeap* pd3dSrvDescHeap; UINT numFramesInFlight; - ImGui_ImplDX12_Data() { memset((void*)this, 0, sizeof(*this)); } + ImGui_ImplDX12_RenderBuffers* pFrameResources; + UINT frameIndex; + + ImGui_ImplDX12_Data() + { + memset((void*)this, 0, sizeof(*this)); + frameIndex = UINT_MAX; + } }; // Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts @@ -92,90 +100,11 @@ struct ImGui_ImplDX12_RenderBuffers int VertexBufferSize; }; -// Buffers used for secondary viewports created by the multi-viewports systems -struct ImGui_ImplDX12_FrameContext -{ - ID3D12CommandAllocator* CommandAllocator; - ID3D12Resource* RenderTarget; - D3D12_CPU_DESCRIPTOR_HANDLE RenderTargetCpuDescriptors; -}; - -// Helper structure we store in the void* RendererUserData field of each ImGuiViewport to easily retrieve our backend data. -// Main viewport created by application will only use the Resources field. -// Secondary viewports created by this backend will use all the fields (including Window fields), -struct ImGui_ImplDX12_ViewportData -{ - // Window - ID3D12CommandQueue* CommandQueue; - ID3D12GraphicsCommandList* CommandList; - ID3D12DescriptorHeap* RtvDescHeap; - IDXGISwapChain3* SwapChain; - ID3D12Fence* Fence; - UINT64 FenceSignaledValue; - HANDLE FenceEvent; - UINT NumFramesInFlight; - ImGui_ImplDX12_FrameContext* FrameCtx; - - // Render buffers - UINT FrameIndex; - ImGui_ImplDX12_RenderBuffers* FrameRenderBuffers; - - ImGui_ImplDX12_ViewportData(UINT num_frames_in_flight) - { - CommandQueue = nullptr; - CommandList = nullptr; - RtvDescHeap = nullptr; - SwapChain = nullptr; - Fence = nullptr; - FenceSignaledValue = 0; - FenceEvent = nullptr; - NumFramesInFlight = num_frames_in_flight; - FrameCtx = new ImGui_ImplDX12_FrameContext[NumFramesInFlight]; - FrameIndex = UINT_MAX; - FrameRenderBuffers = new ImGui_ImplDX12_RenderBuffers[NumFramesInFlight]; - - for (UINT i = 0; i < NumFramesInFlight; ++i) - { - FrameCtx[i].CommandAllocator = nullptr; - FrameCtx[i].RenderTarget = nullptr; - - // Create buffers with a default size (they will later be grown as needed) - FrameRenderBuffers[i].IndexBuffer = nullptr; - FrameRenderBuffers[i].VertexBuffer = nullptr; - FrameRenderBuffers[i].VertexBufferSize = 5000; - FrameRenderBuffers[i].IndexBufferSize = 10000; - } - } - ~ImGui_ImplDX12_ViewportData() - { - IM_ASSERT(CommandQueue == nullptr && CommandList == nullptr); - IM_ASSERT(RtvDescHeap == nullptr); - IM_ASSERT(SwapChain == nullptr); - IM_ASSERT(Fence == nullptr); - IM_ASSERT(FenceEvent == nullptr); - - for (UINT i = 0; i < NumFramesInFlight; ++i) - { - IM_ASSERT(FrameCtx[i].CommandAllocator == nullptr && FrameCtx[i].RenderTarget == nullptr); - IM_ASSERT(FrameRenderBuffers[i].IndexBuffer == nullptr && FrameRenderBuffers[i].VertexBuffer == nullptr); - } - - delete[] FrameCtx; - FrameCtx = nullptr; - delete[] FrameRenderBuffers; - FrameRenderBuffers = nullptr; - } -}; - struct VERTEX_CONSTANT_BUFFER_DX12 { float mvp[4][4]; }; -// Forward Declarations -static void ImGui_ImplDX12_InitPlatformInterface(); -static void ImGui_ImplDX12_ShutdownPlatformInterface(); - // Functions static void ImGui_ImplDX12_SetupRenderState(ImDrawData* draw_data, ID3D12GraphicsCommandList* ctx, ImGui_ImplDX12_RenderBuffers* fr) { @@ -247,10 +176,11 @@ void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandL if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f) return; + // FIXME: I'm assuming that this only gets called once per frame! + // If not, we can't just re-allocate the IB or VB, we'll have to do a proper allocator. ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); - ImGui_ImplDX12_ViewportData* vd = (ImGui_ImplDX12_ViewportData*)draw_data->OwnerViewport->RendererUserData; - vd->FrameIndex++; - ImGui_ImplDX12_RenderBuffers* fr = &vd->FrameRenderBuffers[vd->FrameIndex % bd->numFramesInFlight]; + bd->frameIndex = bd->frameIndex + 1; + ImGui_ImplDX12_RenderBuffers* fr = &bd->pFrameResources[bd->frameIndex % bd->numFramesInFlight]; // Create and grow vertex/index buffers if needed if (fr->VertexBuffer == nullptr || fr->VertexBufferSize < draw_data->TotalVtxCount) @@ -366,7 +296,7 @@ void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandL } } -static void ImGui_ImplDX12_CreateFontsTexture(ID3D12CommandQueue* apCommandQueue) +void ImGui_ImplDX12_UpdateFontsTexture(ID3D12CommandQueue* apCommandQueue) { // Build texture atlas ImGuiIO& io = ImGui::GetIO(); @@ -374,9 +304,13 @@ static void ImGui_ImplDX12_CreateFontsTexture(ID3D12CommandQueue* apCommandQueue unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + //io.Fonts->MarkClean(); + + bool need_barrier_before_copy = true; // Do we need a resource barrier before we copy new data in? - // Upload texture to graphics system + if ((!bd->pFontTextureResource) || (bd->FontTextureWidth != width) || (bd->FontTextureHeight != height)) { + // Either we have no texture or the size has changed, so (re-)create the texture D3D12_HEAP_PROPERTIES props; memset(&props, 0, sizeof(D3D12_HEAP_PROPERTIES)); props.Type = D3D12_HEAP_TYPE_DEFAULT; @@ -400,8 +334,43 @@ static void ImGui_ImplDX12_CreateFontsTexture(ID3D12CommandQueue* apCommandQueue ID3D12Resource* pTexture = nullptr; bd->pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&pTexture)); + // Create SRV + D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc; + ZeroMemory(&srvDesc, sizeof(srvDesc)); + srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + srvDesc.Texture2D.MipLevels = desc.MipLevels; + srvDesc.Texture2D.MostDetailedMip = 0; + srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, bd->hFontSrvCpuDescHandle); + SafeRelease(bd->pFontTextureResource); + bd->pFontTextureResource = pTexture; + + bd->FontTextureWidth = width; + bd->FontTextureHeight = height; + + need_barrier_before_copy = false; // Because this is a newly-created texture it will be in D3D12_RESOURCE_STATE_COMMON and thus we don't need a barrier + } + + // Store our identifier + // READ THIS IF THE STATIC_ASSERT() TRIGGERS: + // - Important: to compile on 32-bit systems, this backend requires code to be compiled with '#define ImTextureID ImU64'. + // - This is because we need ImTextureID to carry a 64-bit value and by default ImTextureID is defined as void*. + // [Solution 1] IDE/msbuild: in "Properties/C++/Preprocessor Definitions" add 'ImTextureID=ImU64' (this is what we do in the + // 'example_win32_direct12/example_win32_direct12.vcxproj' project file) [Solution 2] IDE/msbuild: in "Properties/C++/Preprocessor Definitions" add + // 'IMGUI_USER_CONFIG="my_imgui_config.h"' and inside 'my_imgui_config.h' add '#define ImTextureID ImU64' and as many other options as you like. [Solution 3] IDE/msbuild: edit + // imconfig.h and add '#define ImTextureID ImU64' (prefer solution 2 to create your own config file!) [Solution 4] command-line: add '/D ImTextureID=ImU64' to your cl.exe + // command-line (this is what we do in the example_win32_direct12/build_win32.bat file) + static_assert(sizeof(ImTextureID) >= sizeof(bd->hFontSrvGpuDescHandle.ptr), "Can't pack descriptor handle into TexID, 32-bit not supported yet."); + io.Fonts->SetTexID((ImTextureID)bd->hFontSrvGpuDescHandle.ptr); + + // Upload texture + { UINT uploadPitch = (width * 4 + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u) & ~(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u); UINT uploadSize = height * uploadPitch; + + D3D12_RESOURCE_DESC desc; + ZeroMemory(&desc, sizeof(desc)); desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; desc.Alignment = 0; desc.Width = uploadSize; @@ -414,6 +383,8 @@ static void ImGui_ImplDX12_CreateFontsTexture(ID3D12CommandQueue* apCommandQueue desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; desc.Flags = D3D12_RESOURCE_FLAG_NONE; + D3D12_HEAP_PROPERTIES props; + memset(&props, 0, sizeof(D3D12_HEAP_PROPERTIES)); props.Type = D3D12_HEAP_TYPE_UPLOAD; props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; props.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; @@ -440,17 +411,11 @@ static void ImGui_ImplDX12_CreateFontsTexture(ID3D12CommandQueue* apCommandQueue srcLocation.PlacedFootprint.Footprint.RowPitch = uploadPitch; D3D12_TEXTURE_COPY_LOCATION dstLocation = {}; - dstLocation.pResource = pTexture; + dstLocation.pResource = bd->pFontTextureResource; dstLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; dstLocation.SubresourceIndex = 0; - D3D12_RESOURCE_BARRIER barrier = {}; - barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; - barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; - barrier.Transition.pResource = pTexture; - barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; - barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; - barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + // Create temporary command list and execute immediately ID3D12Fence* fence = nullptr; hr = bd->pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)); @@ -464,10 +429,6 @@ static void ImGui_ImplDX12_CreateFontsTexture(ID3D12CommandQueue* apCommandQueue queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; queueDesc.NodeMask = 1; - //ID3D12CommandQueue* cmdQueue = nullptr; - //hr = bd->pd3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&cmdQueue)); - //IM_ASSERT(SUCCEEDED(hr)); - ID3D12CommandAllocator* cmdAlloc = nullptr; hr = bd->pd3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&cmdAlloc)); IM_ASSERT(SUCCEEDED(hr)); @@ -476,8 +437,30 @@ static void ImGui_ImplDX12_CreateFontsTexture(ID3D12CommandQueue* apCommandQueue hr = bd->pd3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, cmdAlloc, nullptr, IID_PPV_ARGS(&cmdList)); IM_ASSERT(SUCCEEDED(hr)); + if (need_barrier_before_copy) + { + D3D12_RESOURCE_BARRIER barrier = {}; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barrier.Transition.pResource = bd->pFontTextureResource; + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST; + cmdList->ResourceBarrier(1, &barrier); + } + cmdList->CopyTextureRegion(&dstLocation, 0, 0, 0, &srcLocation, nullptr); - cmdList->ResourceBarrier(1, &barrier); + + { + D3D12_RESOURCE_BARRIER barrier = {}; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barrier.Transition.pResource = bd->pFontTextureResource; + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + cmdList->ResourceBarrier(1, &barrier); + } hr = cmdList->Close(); IM_ASSERT(SUCCEEDED(hr)); @@ -491,35 +474,10 @@ static void ImGui_ImplDX12_CreateFontsTexture(ID3D12CommandQueue* apCommandQueue cmdList->Release(); cmdAlloc->Release(); - //cmdQueue->Release(); CloseHandle(event); fence->Release(); uploadBuffer->Release(); - - // Create texture view - D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc; - ZeroMemory(&srvDesc, sizeof(srvDesc)); - srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; - srvDesc.Texture2D.MipLevels = desc.MipLevels; - srvDesc.Texture2D.MostDetailedMip = 0; - srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; - bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, bd->hFontSrvCpuDescHandle); - SafeRelease(bd->pFontTextureResource); - bd->pFontTextureResource = pTexture; } - - // Store our identifier - // READ THIS IF THE STATIC_ASSERT() TRIGGERS: - // - Important: to compile on 32-bit systems, this backend requires code to be compiled with '#define ImTextureID ImU64'. - // - This is because we need ImTextureID to carry a 64-bit value and by default ImTextureID is defined as void*. - // [Solution 1] IDE/msbuild: in "Properties/C++/Preprocessor Definitions" add 'ImTextureID=ImU64' (this is what we do in the - // 'example_win32_direct12/example_win32_direct12.vcxproj' project file) [Solution 2] IDE/msbuild: in "Properties/C++/Preprocessor Definitions" add - // 'IMGUI_USER_CONFIG="my_imgui_config.h"' and inside 'my_imgui_config.h' add '#define ImTextureID ImU64' and as many other options as you like. [Solution 3] IDE/msbuild: edit - // imconfig.h and add '#define ImTextureID ImU64' (prefer solution 2 to create your own config file!) [Solution 4] command-line: add '/D ImTextureID=ImU64' to your cl.exe - // command-line (this is what we do in the example_win32_direct12/build_win32.bat file) - static_assert(sizeof(ImTextureID) >= sizeof(bd->hFontSrvGpuDescHandle.ptr), "Can't pack descriptor handle into TexID, 32-bit not supported yet."); - io.Fonts->SetTexID((ImTextureID)bd->hFontSrvGpuDescHandle.ptr); } bool ImGui_ImplDX12_CreateDeviceObjects(ID3D12CommandQueue* apCommandQueue) @@ -579,22 +537,22 @@ bool ImGui_ImplDX12_CreateDeviceObjects(ID3D12CommandQueue* apCommandQueue) // Load d3d12.dll and D3D12SerializeRootSignature() function address dynamically to facilitate using with D3D12On7. // See if any version of d3d12.dll is already loaded in the process. If so, give preference to that. - static HINSTANCE d3d12_dll = ::GetModuleHandle(_T("d3d12.dll")); + static HINSTANCE d3d12_dll = ::GetModuleHandleA("d3d12.dll"); if (d3d12_dll == nullptr) { // Attempt to load d3d12.dll from local directories. This will only succeed if // (1) the current OS is Windows 7, and // (2) there exists a version of d3d12.dll for Windows 7 (D3D12On7) in one of the following directories. // See https://github.com/ocornut/imgui/pull/3696 for details. - const TCHAR* localD3d12Paths[] = { - _T(".\\d3d12.dll"), _T(".\\d3d12on7\\d3d12.dll"), _T(".\\12on7\\d3d12.dll")}; // A. current directory, B. used by some games, C. used in Microsoft D3D12On7 sample + const char* localD3d12Paths[] = { + ".\\d3d12.dll", ".\\d3d12on7\\d3d12.dll", ".\\12on7\\d3d12.dll"}; // A. current directory, B. used by some games, C. used in Microsoft D3D12On7 sample for (int i = 0; i < IM_ARRAYSIZE(localD3d12Paths); i++) - if ((d3d12_dll = ::LoadLibrary(localD3d12Paths[i])) != nullptr) + if ((d3d12_dll = ::LoadLibraryA(localD3d12Paths[i])) != nullptr) break; // If failed, we are on Windows >= 10. if (d3d12_dll == nullptr) - d3d12_dll = ::LoadLibrary(_T("d3d12.dll")); + d3d12_dll = ::LoadLibraryA("d3d12.dll"); if (d3d12_dll == nullptr) return false; @@ -747,18 +705,11 @@ bool ImGui_ImplDX12_CreateDeviceObjects(ID3D12CommandQueue* apCommandQueue) if (result_pipeline_state != S_OK) return false; - ImGui_ImplDX12_CreateFontsTexture(apCommandQueue); + ImGui_ImplDX12_UpdateFontsTexture(apCommandQueue); return true; } -static void ImGui_ImplDX12_DestroyRenderBuffers(ImGui_ImplDX12_RenderBuffers* render_buffers) -{ - SafeRelease(render_buffers->IndexBuffer); - SafeRelease(render_buffers->VertexBuffer); - render_buffers->IndexBufferSize = render_buffers->VertexBufferSize = 0; -} - void ImGui_ImplDX12_InvalidateDeviceObjects() { ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); @@ -770,6 +721,13 @@ void ImGui_ImplDX12_InvalidateDeviceObjects() SafeRelease(bd->pPipelineState); SafeRelease(bd->pFontTextureResource); io.Fonts->SetTexID(0); // We copied bd->pFontTextureView to io.Fonts->TexID so let's clear that as well. + + for (UINT i = 0; i < bd->numFramesInFlight; i++) + { + ImGui_ImplDX12_RenderBuffers* fr = &bd->pFrameResources[i]; + SafeRelease(fr->IndexBuffer); + SafeRelease(fr->VertexBuffer); + } } bool ImGui_ImplDX12_Init( @@ -785,21 +743,27 @@ bool ImGui_ImplDX12_Init( io.BackendRendererUserData = (void*)bd; io.BackendRendererName = "imgui_impl_dx12"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. - io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) - if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) - ImGui_ImplDX12_InitPlatformInterface(); + // Note that we explicitly do *not* set ImGuiBackendFlags_RendererHasTexReload here, because in DX12 that requires support in the caller as well, so we leave setting it (or + // not) up to that code. bd->pd3dDevice = device; bd->RTVFormat = rtv_format; bd->hFontSrvCpuDescHandle = font_srv_cpu_desc_handle; bd->hFontSrvGpuDescHandle = font_srv_gpu_desc_handle; + bd->pFrameResources = new ImGui_ImplDX12_RenderBuffers[num_frames_in_flight]; bd->numFramesInFlight = num_frames_in_flight; bd->pd3dSrvDescHeap = cbv_srv_heap; + bd->frameIndex = UINT_MAX; - // Create a dummy ImGui_ImplDX12_ViewportData holder for the main viewport, - // Since this is created and managed by the application, we will only use the ->Resources[] fields. - ImGuiViewport* main_viewport = ImGui::GetMainViewport(); - main_viewport->RendererUserData = IM_NEW(ImGui_ImplDX12_ViewportData)(bd->numFramesInFlight); + // Create buffers with a default size (they will later be grown as needed) + for (int i = 0; i < num_frames_in_flight; i++) + { + ImGui_ImplDX12_RenderBuffers* fr = &bd->pFrameResources[i]; + fr->IndexBuffer = nullptr; + fr->VertexBuffer = nullptr; + fr->IndexBufferSize = 10000; + fr->VertexBufferSize = 5000; + } return true; } @@ -810,24 +774,12 @@ void ImGui_ImplDX12_Shutdown() IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); ImGuiIO& io = ImGui::GetIO(); - // Manually delete main viewport render resources in-case we haven't initialized for viewports - ImGuiViewport* main_viewport = ImGui::GetMainViewport(); - if (ImGui_ImplDX12_ViewportData* vd = (ImGui_ImplDX12_ViewportData*)main_viewport->RendererUserData) - { - // We could just call ImGui_ImplDX12_DestroyWindow(main_viewport) as a convenience but that would be misleading since we only use data->Resources[] - for (UINT i = 0; i < bd->numFramesInFlight; i++) - ImGui_ImplDX12_DestroyRenderBuffers(&vd->FrameRenderBuffers[i]); - IM_DELETE(vd); - main_viewport->RendererUserData = nullptr; - } - // Clean up windows and device objects - ImGui_ImplDX12_ShutdownPlatformInterface(); ImGui_ImplDX12_InvalidateDeviceObjects(); - + delete[] bd->pFrameResources; io.BackendRendererName = nullptr; io.BackendRendererUserData = nullptr; - io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasViewports); + io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; IM_DELETE(bd); } @@ -836,250 +788,10 @@ void ImGui_ImplDX12_NewFrame(ID3D12CommandQueue* apCommandQueue) ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX12_Init()?"); - if (!bd->pPipelineState || !ImGui::GetIO().Fonts->IsBuilt()) + if (!bd->pPipelineState) ImGui_ImplDX12_CreateDeviceObjects(apCommandQueue); } -//-------------------------------------------------------------------------------------------------------- -// MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT -// This is an _advanced_ and _optional_ feature, allowing the backend to create and handle multiple viewports simultaneously. -// If you are new to dear imgui or creating a new binding for dear imgui, it is recommended that you completely ignore this section first.. -//-------------------------------------------------------------------------------------------------------- - -static void ImGui_ImplDX12_CreateWindow(ImGuiViewport* viewport) -{ - ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); - ImGui_ImplDX12_ViewportData* vd = IM_NEW(ImGui_ImplDX12_ViewportData)(bd->numFramesInFlight); - viewport->RendererUserData = vd; - - // PlatformHandleRaw should always be a HWND, whereas PlatformHandle might be a higher-level handle (e.g. GLFWWindow*, SDL_Window*). - // Some backends will leave PlatformHandleRaw == 0, in which case we assume PlatformHandle will contain the HWND. - HWND hwnd = viewport->PlatformHandleRaw ? (HWND)viewport->PlatformHandleRaw : (HWND)viewport->PlatformHandle; - IM_ASSERT(hwnd != 0); - - vd->FrameIndex = UINT_MAX; - - // 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; - - 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) - { - res = bd->pd3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&vd->FrameCtx[i].CommandAllocator)); - IM_ASSERT(res == S_OK); - } - - // Create command list. - res = bd->pd3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, vd->FrameCtx[0].CommandAllocator, nullptr, IID_PPV_ARGS(&vd->CommandList)); - IM_ASSERT(res == S_OK); - vd->CommandList->Close(); - - // Create fence. - res = bd->pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&vd->Fence)); - IM_ASSERT(res == S_OK); - - vd->FenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); - IM_ASSERT(vd->FenceEvent != nullptr); - - // Create swap chain - // FIXME-VIEWPORT: May want to copy/inherit swap chain settings from the user/application. - DXGI_SWAP_CHAIN_DESC1 sd1; - ZeroMemory(&sd1, sizeof(sd1)); - sd1.BufferCount = bd->numFramesInFlight; - sd1.Width = (UINT)viewport->Size.x; - sd1.Height = (UINT)viewport->Size.y; - sd1.Format = bd->RTVFormat; - sd1.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - sd1.SampleDesc.Count = 1; - sd1.SampleDesc.Quality = 0; - sd1.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; - sd1.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; - sd1.Scaling = DXGI_SCALING_NONE; - sd1.Stereo = FALSE; - - IDXGIFactory4* dxgi_factory = nullptr; - res = ::CreateDXGIFactory1(IID_PPV_ARGS(&dxgi_factory)); - IM_ASSERT(res == S_OK); - - IDXGISwapChain1* swap_chain = nullptr; - res = dxgi_factory->CreateSwapChainForHwnd(vd->CommandQueue, hwnd, &sd1, nullptr, nullptr, &swap_chain); - 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)); - swap_chain->Release(); - - // Create the render targets - if (vd->SwapChain) - { - D3D12_DESCRIPTOR_HEAP_DESC desc = {}; - desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; - desc.NumDescriptors = bd->numFramesInFlight; - desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; - desc.NodeMask = 1; - - HRESULT hr = bd->pd3dDevice->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&vd->RtvDescHeap)); - IM_ASSERT(hr == S_OK); - - SIZE_T rtv_descriptor_size = bd->pd3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); - D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle = vd->RtvDescHeap->GetCPUDescriptorHandleForHeapStart(); - for (UINT i = 0; i < bd->numFramesInFlight; i++) - { - vd->FrameCtx[i].RenderTargetCpuDescriptors = rtv_handle; - rtv_handle.ptr += rtv_descriptor_size; - } - - ID3D12Resource* back_buffer; - for (UINT i = 0; i < bd->numFramesInFlight; i++) - { - IM_ASSERT(vd->FrameCtx[i].RenderTarget == nullptr); - vd->SwapChain->GetBuffer(i, IID_PPV_ARGS(&back_buffer)); - bd->pd3dDevice->CreateRenderTargetView(back_buffer, nullptr, vd->FrameCtx[i].RenderTargetCpuDescriptors); - vd->FrameCtx[i].RenderTarget = back_buffer; - } - } - - for (UINT i = 0; i < bd->numFramesInFlight; i++) - ImGui_ImplDX12_DestroyRenderBuffers(&vd->FrameRenderBuffers[i]); -} - -static void ImGui_WaitForPendingOperations(ImGui_ImplDX12_ViewportData* vd) -{ - HRESULT hr = S_FALSE; - if (vd && vd->CommandQueue && vd->Fence && vd->FenceEvent) - { - hr = vd->CommandQueue->Signal(vd->Fence, ++vd->FenceSignaledValue); - 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); - } -} - -static void ImGui_ImplDX12_DestroyWindow(ImGuiViewport* viewport) -{ - // The main viewport (owned by the application) will always have RendererUserData == 0 since we didn't create the data for it. - ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); - if (ImGui_ImplDX12_ViewportData* vd = (ImGui_ImplDX12_ViewportData*)viewport->RendererUserData) - { - ImGui_WaitForPendingOperations(vd); - - SafeRelease(vd->CommandQueue); - SafeRelease(vd->CommandList); - SafeRelease(vd->SwapChain); - SafeRelease(vd->RtvDescHeap); - SafeRelease(vd->Fence); - ::CloseHandle(vd->FenceEvent); - vd->FenceEvent = nullptr; - - for (UINT i = 0; i < bd->numFramesInFlight; i++) - { - SafeRelease(vd->FrameCtx[i].RenderTarget); - SafeRelease(vd->FrameCtx[i].CommandAllocator); - ImGui_ImplDX12_DestroyRenderBuffers(&vd->FrameRenderBuffers[i]); - } - IM_DELETE(vd); - } - viewport->RendererUserData = nullptr; -} - -static void ImGui_ImplDX12_SetWindowSize(ImGuiViewport* viewport, ImVec2 size) -{ - ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); - ImGui_ImplDX12_ViewportData* vd = (ImGui_ImplDX12_ViewportData*)viewport->RendererUserData; - - ImGui_WaitForPendingOperations(vd); - - for (UINT i = 0; i < bd->numFramesInFlight; i++) - SafeRelease(vd->FrameCtx[i].RenderTarget); - - if (vd->SwapChain) - { - ID3D12Resource* back_buffer = nullptr; - vd->SwapChain->ResizeBuffers(0, (UINT)size.x, (UINT)size.y, DXGI_FORMAT_UNKNOWN, 0); - for (UINT i = 0; i < bd->numFramesInFlight; i++) - { - vd->SwapChain->GetBuffer(i, IID_PPV_ARGS(&back_buffer)); - bd->pd3dDevice->CreateRenderTargetView(back_buffer, nullptr, vd->FrameCtx[i].RenderTargetCpuDescriptors); - vd->FrameCtx[i].RenderTarget = back_buffer; - } - } -} - -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]; - UINT back_buffer_idx = vd->SwapChain->GetCurrentBackBufferIndex(); - - const ImVec4 clear_color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); - D3D12_RESOURCE_BARRIER barrier = {}; - barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; - barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; - barrier.Transition.pResource = vd->FrameCtx[back_buffer_idx].RenderTarget; - barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; - barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; - barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; - - // Draw - ID3D12GraphicsCommandList* cmd_list = vd->CommandList; - - frame_context->CommandAllocator->Reset(); - cmd_list->Reset(frame_context->CommandAllocator, nullptr); - cmd_list->ResourceBarrier(1, &barrier); - cmd_list->OMSetRenderTargets(1, &vd->FrameCtx[back_buffer_idx].RenderTargetCpuDescriptors, FALSE, nullptr); - if (!(viewport->Flags & ImGuiViewportFlags_NoRendererClear)) - cmd_list->ClearRenderTargetView(vd->FrameCtx[back_buffer_idx].RenderTargetCpuDescriptors, (float*)&clear_color, 0, nullptr); - cmd_list->SetDescriptorHeaps(1, &bd->pd3dSrvDescHeap); - - ImGui_ImplDX12_RenderDrawData(viewport->DrawData, cmd_list); - - barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; - barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; - 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); -} - -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(); -} - -void ImGui_ImplDX12_InitPlatformInterface() -{ - ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); - platform_io.Renderer_CreateWindow = ImGui_ImplDX12_CreateWindow; - platform_io.Renderer_DestroyWindow = ImGui_ImplDX12_DestroyWindow; - platform_io.Renderer_SetWindowSize = ImGui_ImplDX12_SetWindowSize; - platform_io.Renderer_RenderWindow = ImGui_ImplDX12_RenderWindow; - platform_io.Renderer_SwapBuffers = ImGui_ImplDX12_SwapBuffers; -} - -void ImGui_ImplDX12_ShutdownPlatformInterface() -{ - ImGui::DestroyPlatformWindows(); -} - //----------------------------------------------------------------------------- #endif // #ifndef IMGUI_DISABLE diff --git a/src/imgui_impl/dx12.h b/src/imgui_impl/dx12.h index 8872029b..029c79a3 100644 --- a/src/imgui_impl/dx12.h +++ b/src/imgui_impl/dx12.h @@ -4,7 +4,6 @@ // Implemented features: // [X] Renderer: User texture binding. Use 'D3D12_GPU_DESCRIPTOR_HANDLE' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices. -// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // Important: to compile on 32-bit systems, this backend requires code to be compiled with '#define ImTextureID ImU64'. // See imgui_impl_dx12.cpp file for details. @@ -18,7 +17,7 @@ // - Introduction, links and more at the top of imgui.cpp #pragma once -#include "imgui.h" // IMGUI_IMPL_API +#include "imgui.h" // IMGUI_IMPL_API #ifndef IMGUI_DISABLE #include // DXGI_FORMAT @@ -34,14 +33,17 @@ struct D3D12_GPU_DESCRIPTOR_HANDLE; // Before calling the render function, caller must prepare cmd_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_NewFrame(ID3D12CommandQueue* apCommandQueue); -IMGUI_IMPL_API void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandList* graphics_command_list); +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_NewFrame(ID3D12CommandQueue* apCommandQueue); +IMGUI_IMPL_API void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandList* graphics_command_list); +IMGUI_IMPL_API void ImGui_ImplDX12_UpdateFontsTexture(ID3D12CommandQueue* apCommandQueue); // This should be called when the font texture needs updating (if ImGuiBackendFlags_RendererHasTexReload was set), with the + // GPU idle. It will perform the update using a temporary command list, and block internally until it completes. // Use if you want to reset your rendering device without losing Dear ImGui state. -IMGUI_IMPL_API void ImGui_ImplDX12_InvalidateDeviceObjects(); -IMGUI_IMPL_API bool ImGui_ImplDX12_CreateDeviceObjects(ID3D12CommandQueue* apCommandQueue); +IMGUI_IMPL_API void ImGui_ImplDX12_InvalidateDeviceObjects(); +IMGUI_IMPL_API bool ImGui_ImplDX12_CreateDeviceObjects(ID3D12CommandQueue* apCommandQueue); #endif // #ifndef IMGUI_DISABLE \ No newline at end of file