Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DirectComposition for DX12 backend (for transparency) #7462

Open
wants to merge 3 commits into
base: docking
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
86 changes: 86 additions & 0 deletions backends/imgui_impl_dx12.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@
#pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below.
#endif

#ifdef DCOMP
#include <dcomp.h>
#endif

// DirectX data
struct ImGui_ImplDX12_Data
{
Expand All @@ -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)); }
};

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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()
{
Expand All @@ -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;
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -907,8 +944,48 @@ static void ImGui_ImplDX12_CreateWindow(ImGuiViewport* viewport)
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();

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 8 additions & 0 deletions backends/imgui_impl_dx12.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 6 additions & 0 deletions backends/imgui_impl_win32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
24 changes: 23 additions & 1 deletion examples/example_win32_directx12/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
#pragma comment(lib, "dxguid.lib")
#endif

#ifdef DCOMP
#include <dcomp.h>
#pragma comment(lib, "dcomp.lib")
#endif

struct FrameContext
{
ID3D12CommandAllocator* CommandAllocator;
Expand All @@ -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();
Expand All @@ -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();
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -391,6 +409,10 @@ void CleanupDeviceD3D()
pDebug->Release();
}
#endif

#ifdef DCOMP
if (g_pDCompDevice) { g_pDCompDevice->Release(); g_pDCompDevice = NULL; }
#endif
}

void CreateRenderTarget()
Expand Down
6 changes: 4 additions & 2 deletions imgui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 5 additions & 3 deletions imgui.h
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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.
Expand Down