From 4ba7f54031282a631c33251e2381b5e7481a9b3a Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Tue, 2 Apr 2024 21:18:03 +0900 Subject: [PATCH 1/3] Add DirectComposition for DX12 backend --- backends/imgui_impl_dx12.cpp | 88 ++++++++++++++++++++++- backends/imgui_impl_dx12.h | 8 +++ backends/imgui_impl_win32.cpp | 6 ++ examples/example_win32_directx12/main.cpp | 24 ++++++- imgui.cpp | 6 +- imgui.h | 8 ++- 6 files changed, 133 insertions(+), 7 deletions(-) diff --git a/backends/imgui_impl_dx12.cpp b/backends/imgui_impl_dx12.cpp index 31ee73727261..5a429ac1171f 100644 --- a/backends/imgui_impl_dx12.cpp +++ b/backends/imgui_impl_dx12.cpp @@ -56,6 +56,10 @@ #pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below. #endif +#ifdef DCOMP +#include +#endif + // DirectX data struct ImGui_ImplDX12_Data { @@ -69,6 +73,10 @@ struct ImGui_ImplDX12_Data ID3D12DescriptorHeap* pd3dSrvDescHeap; UINT numFramesInFlight; +#ifdef DCOMP + IDCompositionDevice* pDCompDevice; +#endif + ImGui_ImplDX12_Data() { memset((void*)this, 0, sizeof(*this)); } }; @@ -112,6 +120,11 @@ struct ImGui_ImplDX12_ViewportData UINT NumFramesInFlight; ImGui_ImplDX12_FrameContext* FrameCtx; +#ifdef DCOMP + IDCompositionVisual* DCompVisual; + IDCompositionTarget* DCompTarget; +#endif + // Render buffers UINT FrameIndex; ImGui_ImplDX12_RenderBuffers* FrameRenderBuffers; @@ -141,6 +154,11 @@ struct ImGui_ImplDX12_ViewportData FrameRenderBuffers[i].VertexBufferSize = 5000; FrameRenderBuffers[i].IndexBufferSize = 10000; } + +#ifdef DCOMP + DCompVisual = nullptr; + DCompTarget = nullptr; +#endif } ~ImGui_ImplDX12_ViewportData() { @@ -156,6 +174,11 @@ struct ImGui_ImplDX12_ViewportData IM_ASSERT(FrameRenderBuffers[i].IndexBuffer == nullptr && FrameRenderBuffers[i].VertexBuffer == nullptr); } +#ifdef DCOMP + IM_ASSERT(DCompVisual == nullptr); + IM_ASSERT(DCompTarget == nullptr); +#endif + delete[] FrameCtx; FrameCtx = nullptr; delete[] FrameRenderBuffers; FrameRenderBuffers = nullptr; } @@ -803,6 +826,20 @@ bool ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames_in_flight, DXGI_FO return true; } +#ifdef DCOMP +bool ImGui_ImplDX12_InitDComp(IDCompositionDevice* device) { + ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.BackendRendererUserData != nullptr && "InitDComp expects standard Init to have been called."); + + io.BackendFlags |= ImGuiBackendFlags_RendererHasTransparentViewports; + + ImGui_ImplDX12_Data* bd = (ImGui_ImplDX12_Data*)io.BackendRendererUserData; + bd->pDCompDevice = device; + + return true; +} +#endif + void ImGui_ImplDX12_Shutdown() { ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); @@ -903,12 +940,52 @@ static void ImGui_ImplDX12_CreateWindow(ImGuiViewport* viewport) sd1.Stereo = FALSE; IDXGIFactory4* dxgi_factory = nullptr; - res = ::CreateDXGIFactory1(IID_PPV_ARGS(&dxgi_factory)); + res = ::CreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG, IID_PPV_ARGS(&dxgi_factory)); IM_ASSERT(res == S_OK); IDXGISwapChain1* swap_chain = nullptr; +#ifndef DCOMP res = dxgi_factory->CreateSwapChainForHwnd(vd->CommandQueue, hwnd, &sd1, nullptr, nullptr, &swap_chain); IM_ASSERT(res == S_OK); +#else + IM_ASSERT(bd->pDCompDevice != nullptr && "Call ImGui_ImplDX12_InitDComp after ImGui_ImplDX12_Init."); + + // Microsoft Learn: + // You must specify the DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL value in the SwapEffect member of + // DXGI_SWAP_CHAIN_DESC1 because CreateSwapChainForComposition supports only flip presentation model. + sd1.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; + + // DXGI debug output: + // Composition SwapChains do not support the DXGI_ALPHA_MODE_STRAIGHT AlphaMode. + sd1.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED; + + // DXGI debug output: + // Composition SwapChains only support the DXGI_SCALING_STRETCH Scaling. + sd1.Scaling = DXGI_SCALING_STRETCH; + + res = dxgi_factory->CreateSwapChainForComposition(vd->CommandQueue, &sd1, nullptr, &swap_chain); + IM_ASSERT(res == S_OK); + + // Turn a window into a DirectComposition draw target. + res = bd->pDCompDevice->CreateTargetForHwnd(hwnd, TRUE, &vd->DCompTarget); + IM_ASSERT(res == S_OK); + + // Create a "Visual Object" that causes the contents of a window to change. + res = bd->pDCompDevice->CreateVisual(&vd->DCompVisual); + IM_ASSERT(res == S_OK); + + // The visual must be added as a child of another visual, or as the root of a composition target, + // before it can affect the appearance of a window. + res = vd->DCompTarget->SetRoot(vd->DCompVisual); + IM_ASSERT(res == S_OK); + + // Make the visual host a swap chain created from above. + res = vd->DCompVisual->SetContent(swap_chain); + IM_ASSERT(res == S_OK); + + res = bd->pDCompDevice->Commit(); + IM_ASSERT(res == S_OK); +#endif dxgi_factory->Release(); @@ -981,6 +1058,11 @@ static void ImGui_ImplDX12_DestroyWindow(ImGuiViewport* viewport) ::CloseHandle(vd->FenceEvent); vd->FenceEvent = nullptr; +#ifdef DCOMP + SafeRelease(vd->DCompTarget); + SafeRelease(vd->DCompVisual); +#endif + for (UINT i = 0; i < bd->numFramesInFlight; i++) { SafeRelease(vd->FrameCtx[i].RenderTarget); @@ -1023,7 +1105,11 @@ static void ImGui_ImplDX12_RenderWindow(ImGuiViewport* viewport, void*) ImGui_ImplDX12_FrameContext* frame_context = &vd->FrameCtx[vd->FrameIndex % bd->numFramesInFlight]; UINT back_buffer_idx = vd->SwapChain->GetCurrentBackBufferIndex(); +#ifndef DCOMP const ImVec4 clear_color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); +#else + const ImVec4 clear_color = ImVec4(0.0f, 0.0f, 0.0f, 0.0f); +#endif D3D12_RESOURCE_BARRIER barrier = {}; barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; diff --git a/backends/imgui_impl_dx12.h b/backends/imgui_impl_dx12.h index f304cca15942..21585da6e577 100644 --- a/backends/imgui_impl_dx12.h +++ b/backends/imgui_impl_dx12.h @@ -42,4 +42,12 @@ IMGUI_IMPL_API void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3 IMGUI_IMPL_API void ImGui_ImplDX12_InvalidateDeviceObjects(); IMGUI_IMPL_API bool ImGui_ImplDX12_CreateDeviceObjects(); +// Extras if you want transparent backgrounds on viewport windows. +#ifdef DCOMP +struct IDCompositionDevice; + +// Call after calling ImGui_ImplDX12_Init. +IMGUI_IMPL_API bool ImGui_ImplDX12_InitDComp(IDCompositionDevice* device); +#endif + #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_win32.cpp b/backends/imgui_impl_win32.cpp index 8cc0d9a11f8b..4e8b990e4005 100644 --- a/backends/imgui_impl_win32.cpp +++ b/backends/imgui_impl_win32.cpp @@ -1013,6 +1013,12 @@ static void ImGui_ImplWin32_GetWin32StyleFromViewportFlags(ImGuiViewportFlags fl if (flags & ImGuiViewportFlags_TopMost) *out_ex_style |= WS_EX_TOPMOST; + + // "Redirection bitmap" can be understood as a copy of painted window content held by DWM. + // By making DWM not hold a copy, which happens to not support transparency, this enables using + // transparent window contents, without having to use uniform transparency for the whole window. + if (flags & ImGuiViewportFlags_TransparencySupport) + *out_ex_style |= WS_EX_NOREDIRECTIONBITMAP; } static HWND ImGui_ImplWin32_GetHwndFromViewportID(ImGuiID viewport_id) diff --git a/examples/example_win32_directx12/main.cpp b/examples/example_win32_directx12/main.cpp index 1ef5ae82ba63..6e3e9c1d2fbf 100644 --- a/examples/example_win32_directx12/main.cpp +++ b/examples/example_win32_directx12/main.cpp @@ -26,6 +26,11 @@ #pragma comment(lib, "dxguid.lib") #endif +#ifdef DCOMP +#include +#pragma comment(lib, "dcomp.lib") +#endif + struct FrameContext { ID3D12CommandAllocator* CommandAllocator; @@ -51,6 +56,10 @@ static HANDLE g_hSwapChainWaitableObject = nullptr; static ID3D12Resource* g_mainRenderTargetResource[NUM_BACK_BUFFERS] = {}; static D3D12_CPU_DESCRIPTOR_HANDLE g_mainRenderTargetDescriptor[NUM_BACK_BUFFERS] = {}; +#ifdef DCOMP +static IDCompositionDevice* g_pDCompDevice = NULL; +#endif + // Forward declarations of helper functions bool CreateDeviceD3D(HWND hWnd); void CleanupDeviceD3D(); @@ -69,7 +78,7 @@ int main(int, char**) ::RegisterClassExW(&wc); HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui DirectX12 Example", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, nullptr, nullptr, wc.hInstance, nullptr); - // Initialize Direct3D + // Initialize Direct3D (and DComposition, if enabled) if (!CreateDeviceD3D(hwnd)) { CleanupDeviceD3D(); @@ -111,6 +120,10 @@ int main(int, char**) g_pd3dSrvDescHeap->GetCPUDescriptorHandleForHeapStart(), g_pd3dSrvDescHeap->GetGPUDescriptorHandleForHeapStart()); +#ifdef DCOMP + ImGui_ImplDX12_InitDComp(g_pDCompDevice); +#endif + // 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. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. @@ -364,6 +377,11 @@ bool CreateDeviceD3D(HWND hWnd) g_hSwapChainWaitableObject = g_pSwapChain->GetFrameLatencyWaitableObject(); } +#ifdef DCOMP + if (DCompositionCreateDevice(NULL, IID_PPV_ARGS(&g_pDCompDevice)) != S_OK) + return false; +#endif + CreateRenderTarget(); return true; } @@ -391,6 +409,10 @@ void CleanupDeviceD3D() pDebug->Release(); } #endif + +#ifdef DCOMP + if (g_pDCompDevice) { g_pDCompDevice->Release(); g_pDCompDevice = NULL; } +#endif } void CreateRenderTarget() diff --git a/imgui.cpp b/imgui.cpp index 21cfbbcf805b..b57b9c77c1ac 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6485,7 +6485,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar is_docking_transparent_payload = true; ImU32 bg_col = GetColorU32(GetWindowBgColorIdx(window)); - if (window->ViewportOwned) + if (window->ViewportOwned && !(g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTransparentViewports)) { bg_col |= IM_COL32_A_MASK; // No alpha if (is_docking_transparent_payload) @@ -15255,7 +15255,9 @@ void ImGui::WindowSyncOwnedViewport(ImGuiWindow* window, ImGuiWindow* parent_win // We can also tell the backend that clearing the platform window won't be necessary, // as our window background is filling the viewport and we have disabled BgAlpha. // FIXME: Work on support for per-viewport transparency (#2766) - if (!(window_flags & ImGuiWindowFlags_NoBackground)) + if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTransparentViewports) + viewport_flags |= ImGuiViewportFlags_TransparencySupport; + else if (!(window_flags & ImGuiWindowFlags_NoBackground)) viewport_flags |= ImGuiViewportFlags_NoRendererClear; window->Viewport->Flags = viewport_flags; diff --git a/imgui.h b/imgui.h index 0941a2ed36c9..fcaa3686ef94 100644 --- a/imgui.h +++ b/imgui.h @@ -1559,7 +1559,8 @@ enum ImGuiBackendFlags_ // [BETA] Viewports ImGuiBackendFlags_PlatformHasViewports = 1 << 10, // Backend Platform supports multiple viewports. ImGuiBackendFlags_HasMouseHoveredViewport=1 << 11, // Backend Platform supports calling io.AddMouseViewportEvent() with the viewport under the mouse. IF POSSIBLE, ignore viewports with the ImGuiViewportFlags_NoInputs flag (Win32 backend, GLFW 3.30+ backend can do this, SDL backend cannot). If this cannot be done, Dear ImGui needs to use a flawed heuristic to find the viewport under. - ImGuiBackendFlags_RendererHasViewports = 1 << 12, // Backend Renderer supports multiple viewports. + ImGuiBackendFlags_RendererHasViewports = 1 << 12, // Backend Renderer supports multiple viewports. + ImGuiBackendFlags_RendererHasTransparentViewports = 1 << 13 // Backend Renderer supports transparent viewport content. }; // Enumeration for PushStyleColor() / PopStyleColor() @@ -3257,10 +3258,11 @@ enum ImGuiViewportFlags_ ImGuiViewportFlags_NoAutoMerge = 1 << 9, // Platform Window: Avoid merging this window into another host window. This can only be set via ImGuiWindowClass viewport flags override (because we need to now ahead if we are going to create a viewport in the first place!). ImGuiViewportFlags_TopMost = 1 << 10, // Platform Window: Display on top (for tooltips only). ImGuiViewportFlags_CanHostOtherWindows = 1 << 11, // Viewport can host multiple imgui windows (secondary viewports are associated to a single window). // FIXME: In practice there's still probably code making the assumption that this is always and only on the MainViewport. Will fix once we add support for "no main viewport". + ImGuiViewportFlags_TransparencySupport = 1 << 12, // Platform Window: Transparent content should be handled by system. // Output status flags (from Platform) - ImGuiViewportFlags_IsMinimized = 1 << 12, // Platform Window: Window is minimized, can skip render. When minimized we tend to avoid using the viewport pos/size for clipping window or testing if they are contained in the viewport. - ImGuiViewportFlags_IsFocused = 1 << 13, // Platform Window: Window is focused (last call to Platform_GetWindowFocus() returned true) + ImGuiViewportFlags_IsMinimized = 1 << 13, // Platform Window: Window is minimized, can skip render. When minimized we tend to avoid using the viewport pos/size for clipping window or testing if they are contained in the viewport. + ImGuiViewportFlags_IsFocused = 1 << 14, // Platform Window: Window is focused (last call to Platform_GetWindowFocus() returned true) }; // - Currently represents the Platform Window created by the application which is hosting our Dear ImGui windows. From b35d0a7775d9a342031928636cc04c994e6278db Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Wed, 3 Apr 2024 11:17:40 +0900 Subject: [PATCH 2/3] Revert CreateDXGIFactory2 debugging --- backends/imgui_impl_dx12.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/imgui_impl_dx12.cpp b/backends/imgui_impl_dx12.cpp index 5a429ac1171f..8578d862120f 100644 --- a/backends/imgui_impl_dx12.cpp +++ b/backends/imgui_impl_dx12.cpp @@ -940,7 +940,7 @@ static void ImGui_ImplDX12_CreateWindow(ImGuiViewport* viewport) sd1.Stereo = FALSE; IDXGIFactory4* dxgi_factory = nullptr; - res = ::CreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG, IID_PPV_ARGS(&dxgi_factory)); + res = ::CreateDXGIFactory1(IID_PPV_ARGS(&dxgi_factory)); IM_ASSERT(res == S_OK); IDXGISwapChain1* swap_chain = nullptr; From bbdfb362b4592f4ca3d3c4e6c900c1e4de5ab1e6 Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Wed, 3 Apr 2024 11:19:34 +0900 Subject: [PATCH 3/3] trailing comma consistency --- imgui.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui.h b/imgui.h index fcaa3686ef94..0171cebb6342 100644 --- a/imgui.h +++ b/imgui.h @@ -1560,7 +1560,7 @@ enum ImGuiBackendFlags_ ImGuiBackendFlags_PlatformHasViewports = 1 << 10, // Backend Platform supports multiple viewports. ImGuiBackendFlags_HasMouseHoveredViewport=1 << 11, // Backend Platform supports calling io.AddMouseViewportEvent() with the viewport under the mouse. IF POSSIBLE, ignore viewports with the ImGuiViewportFlags_NoInputs flag (Win32 backend, GLFW 3.30+ backend can do this, SDL backend cannot). If this cannot be done, Dear ImGui needs to use a flawed heuristic to find the viewport under. ImGuiBackendFlags_RendererHasViewports = 1 << 12, // Backend Renderer supports multiple viewports. - ImGuiBackendFlags_RendererHasTransparentViewports = 1 << 13 // Backend Renderer supports transparent viewport content. + ImGuiBackendFlags_RendererHasTransparentViewports = 1 << 13, // Backend Renderer supports transparent viewport content. }; // Enumeration for PushStyleColor() / PopStyleColor()