From 0cc3d6da4616c25d6d8f3883f988e1de291ab780 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Mon, 13 Apr 2026 12:20:25 +0800 Subject: [PATCH] Fix new editor window resize presentation --- .../XCEngine/RHI/D3D12/D3D12SwapChain.h | 6 + engine/src/RHI/D3D12/D3D12SwapChain.cpp | 39 +- new_editor/app/Application.cpp | 157 +++++- new_editor/app/Host/D3D12WindowRenderer.cpp | 374 +++++++++++-- new_editor/app/Host/NativeRenderer.cpp | 521 +++++++++++++++++- 5 files changed, 1019 insertions(+), 78 deletions(-) diff --git a/engine/include/XCEngine/RHI/D3D12/D3D12SwapChain.h b/engine/include/XCEngine/RHI/D3D12/D3D12SwapChain.h index d91ffd05..de8300a4 100644 --- a/engine/include/XCEngine/RHI/D3D12/D3D12SwapChain.h +++ b/engine/include/XCEngine/RHI/D3D12/D3D12SwapChain.h @@ -25,12 +25,17 @@ public: uint32_t GetCurrentBackBufferIndex() const override; RHITexture* GetCurrentBackBuffer() override; + D3D12Texture* TryGetBackBuffer(uint32_t index); + const D3D12Texture* TryGetBackBuffer(uint32_t index) const; D3D12Texture& GetBackBuffer(uint32_t index); const D3D12Texture& GetBackBuffer(uint32_t index) const; ID3D12CommandQueue* GetNativeCommandQueue() const { return m_commandQueue.Get(); } void Present(uint32_t syncInterval = 1, uint32_t flags = 0) override; void Resize(uint32_t width, uint32_t height) override; void* GetNativeHandle() override; + HRESULT GetLastResizeResult() const { return m_lastResizeResult; } + uint32_t GetWidth() const { return m_width; } + uint32_t GetHeight() const { return m_height; } private: bool RefreshBackBuffers(); @@ -42,6 +47,7 @@ private: uint32_t m_height; uint32_t m_bufferCount; std::vector m_backBuffers; + HRESULT m_lastResizeResult = S_OK; }; } // namespace RHI diff --git a/engine/src/RHI/D3D12/D3D12SwapChain.cpp b/engine/src/RHI/D3D12/D3D12SwapChain.cpp index eae7b168..32feaff7 100644 --- a/engine/src/RHI/D3D12/D3D12SwapChain.cpp +++ b/engine/src/RHI/D3D12/D3D12SwapChain.cpp @@ -42,6 +42,7 @@ bool D3D12SwapChain::Initialize(IDXGIFactory4* factory, ID3D12CommandQueue* comm m_width = width; m_height = height; m_bufferCount = bufferCount; + m_lastResizeResult = S_OK; return RefreshBackBuffers(); } @@ -61,6 +62,7 @@ bool D3D12SwapChain::Initialize(IDXGISwapChain* swapChain, uint32_t width, uint3 m_width = width; m_height = height; m_bufferCount = desc.BufferCount; + m_lastResizeResult = S_OK; return RefreshBackBuffers(); } @@ -71,20 +73,31 @@ void D3D12SwapChain::Shutdown() { m_swapChain.Reset(); m_width = 0; m_height = 0; + m_lastResizeResult = S_OK; } uint32_t D3D12SwapChain::GetCurrentBackBufferIndex() const { return m_swapChain->GetCurrentBackBufferIndex(); } +D3D12Texture* D3D12SwapChain::TryGetBackBuffer(uint32_t index) { + return index < m_backBuffers.size() ? &m_backBuffers[index] : nullptr; +} + +const D3D12Texture* D3D12SwapChain::TryGetBackBuffer(uint32_t index) const { + return index < m_backBuffers.size() ? &m_backBuffers[index] : nullptr; +} + D3D12Texture& D3D12SwapChain::GetBackBuffer(uint32_t index) { - assert(index < m_backBuffers.size() && "BackBuffer index out of range"); - return m_backBuffers[index]; + D3D12Texture* backBuffer = TryGetBackBuffer(index); + assert(backBuffer != nullptr && "BackBuffer index out of range"); + return *backBuffer; } const D3D12Texture& D3D12SwapChain::GetBackBuffer(uint32_t index) const { - assert(index < m_backBuffers.size() && "BackBuffer index out of range"); - return m_backBuffers[index]; + const D3D12Texture* backBuffer = TryGetBackBuffer(index); + assert(backBuffer != nullptr && "BackBuffer index out of range"); + return *backBuffer; } void D3D12SwapChain::Present(uint32_t syncInterval, uint32_t flags) { @@ -92,10 +105,26 @@ void D3D12SwapChain::Present(uint32_t syncInterval, uint32_t flags) { } void D3D12SwapChain::Resize(uint32_t width, uint32_t height) { + if (!m_swapChain) { + m_lastResizeResult = E_POINTER; + return; + } + + const uint32_t previousWidth = m_width; + const uint32_t previousHeight = m_height; ReleaseBackBuffers(); - const HRESULT hResult = m_swapChain->ResizeBuffers(m_bufferCount, width, height, DXGI_FORMAT_R8G8B8A8_UNORM, 0); + const HRESULT hResult = m_swapChain->ResizeBuffers( + 0, + width, + height, + DXGI_FORMAT_UNKNOWN, + 0); + m_lastResizeResult = hResult; if (FAILED(hResult)) { + m_width = previousWidth; + m_height = previousHeight; + RefreshBackBuffers(); return; } diff --git a/new_editor/app/Application.cpp b/new_editor/app/Application.cpp index ed459a06..8be4f23a 100644 --- a/new_editor/app/Application.cpp +++ b/new_editor/app/Application.cpp @@ -37,6 +37,15 @@ constexpr const wchar_t* kWindowTitle = L"Main Scene * - Main.xx - XCEngine Edit constexpr UINT kDefaultDpi = 96u; constexpr float kBaseDpiScale = 96.0f; +bool ResolveVerboseRuntimeTraceEnabled() { + wchar_t buffer[8] = {}; + const DWORD length = GetEnvironmentVariableW( + L"XCUIEDITOR_VERBOSE_TRACE", + buffer, + static_cast(std::size(buffer))); + return length > 0u && buffer[0] != L'0'; +} + Application* GetApplicationFromWindow(HWND hwnd) { return reinterpret_cast(GetWindowLongPtrW(hwnd, GWLP_USERDATA)); } @@ -394,7 +403,6 @@ int Application::Run(HINSTANCE hInstance, int nCmdShow) { } RenderFrame(); - Sleep(8); } Shutdown(); @@ -472,10 +480,17 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { LogRuntimeTrace("app", "d3d12 window renderer initialization failed"); return false; } + const bool hasD3D12WindowInterop = m_renderer.AttachWindowRenderer(m_windowRenderer); + if (!hasD3D12WindowInterop) { + LogRuntimeTrace( + "app", + "native renderer d3d12 interop unavailable; falling back to hwnd renderer: " + + m_renderer.GetLastRenderError()); + } m_editorContext.AttachTextMeasurer(m_renderer); m_editorWorkspace.Initialize(repoRoot, m_renderer); m_editorWorkspace.AttachViewportWindowRenderer(m_windowRenderer); - m_editorWorkspace.SetViewportSurfacePresentationEnabled(false); + m_editorWorkspace.SetViewportSurfacePresentationEnabled(hasD3D12WindowInterop); if (!m_editorWorkspace.GetBuiltInIconError().empty()) { LogRuntimeTrace("icons", m_editorWorkspace.GetBuiltInIconError()); } @@ -544,7 +559,7 @@ void Application::RenderFrame() { if (m_editorContext.IsValid()) { std::vector frameEvents = std::move(m_pendingInputEvents); m_pendingInputEvents.clear(); - if (!frameEvents.empty()) { + if (!frameEvents.empty() && IsVerboseRuntimeTraceEnabled()) { LogRuntimeTrace( "input", DescribeInputEvents(frameEvents) + " | " + @@ -552,8 +567,10 @@ void Application::RenderFrame() { m_editorWorkspace.GetShellInteractionState())); } - const bool d3d12FrameBegun = m_windowRenderer.BeginFrame(); - if (!d3d12FrameBegun) { + const bool canUseWindowRenderer = m_renderer.HasAttachedWindowRenderer(); + const bool d3d12FrameBegun = + canUseWindowRenderer && m_windowRenderer.BeginFrame(); + if (canUseWindowRenderer && !d3d12FrameBegun) { LogRuntimeTrace("viewport", "d3d12 frame begin failed"); } @@ -562,17 +579,12 @@ void Application::RenderFrame() { UIRect(0.0f, 0.0f, width, height), frameEvents, BuildCaptureStatusText()); - if (d3d12FrameBegun) { - m_editorWorkspace.RenderRequestedViewports(m_windowRenderer.GetRenderContext()); - if (!m_windowRenderer.SubmitFrame(false)) { - LogRuntimeTrace("viewport", "d3d12 offscreen frame submit failed"); - } - } const UIEditorShellInteractionFrame& shellFrame = m_editorWorkspace.GetShellFrame(); - if (!frameEvents.empty() || - shellFrame.result.workspaceResult.dockHostResult.layoutChanged || - shellFrame.result.workspaceResult.dockHostResult.commandExecuted) { + if (IsVerboseRuntimeTraceEnabled() && + (!frameEvents.empty() || + shellFrame.result.workspaceResult.dockHostResult.layoutChanged || + shellFrame.result.workspaceResult.dockHostResult.commandExecuted)) { std::ostringstream frameTrace = {}; frameTrace << "result consumed=" << (shellFrame.result.consumed ? "true" : "false") @@ -595,6 +607,9 @@ void Application::RenderFrame() { ApplyHostedContentCaptureRequests(); ApplyCurrentCursor(); m_editorWorkspace.Append(drawList); + if (d3d12FrameBegun && !m_inInteractiveResize) { + m_editorWorkspace.RenderRequestedViewports(m_windowRenderer.GetRenderContext()); + } } else { drawList.AddText( UIPoint(28.0f, 28.0f), @@ -610,7 +625,20 @@ void Application::RenderFrame() { 12.0f); } - const bool framePresented = m_renderer.Render(drawData); + bool framePresented = false; + if (m_renderer.HasAttachedWindowRenderer()) { + framePresented = m_renderer.RenderToWindowRenderer(drawData); + if (!framePresented) { + LogRuntimeTrace( + "present", + "d3d12 window composition failed, falling back to hwnd renderer: " + + m_renderer.GetLastRenderError()); + } + } + + if (!framePresented) { + framePresented = m_renderer.Render(drawData); + } m_autoScreenshot.CaptureIfRequested( m_renderer, drawData, @@ -707,6 +735,11 @@ void Application::LogRuntimeTrace( AppendUIEditorRuntimeTrace(channel, message); } +bool Application::IsVerboseRuntimeTraceEnabled() { + static const bool s_enabled = ResolveVerboseRuntimeTraceEnabled(); + return s_enabled; +} + std::string Application::DescribeInputEvents( const std::vector& events) const { std::ostringstream stream = {}; @@ -726,13 +759,84 @@ std::string Application::DescribeInputEvents( return stream.str(); } -void Application::OnResize(UINT width, UINT height) { - if (width == 0 || height == 0) { +void Application::OnResize() { + UINT width = 0u; + UINT height = 0u; + if (!QueryCurrentClientPixelSize(width, height)) { return; } m_renderer.Resize(width, height); - m_windowRenderer.Resize(static_cast(width), static_cast(height)); + CommitWindowResize(); + if (m_inInteractiveResize) { + m_editorWorkspace.SetViewportSurfacePresentationEnabled(false); + } +} + +void Application::OnEnterSizeMove() { + m_inInteractiveResize = true; + m_editorWorkspace.SetViewportSurfacePresentationEnabled(false); +} + +void Application::OnExitSizeMove() { + m_inInteractiveResize = false; + CommitWindowResize(); +} + +bool Application::CommitWindowResize() { + UINT width = 0u; + UINT height = 0u; + if (!QueryCurrentClientPixelSize(width, height)) { + return false; + } + + m_renderer.DetachWindowRenderer(); + const bool resizedWindowRenderer = + m_windowRenderer.Resize(static_cast(width), static_cast(height)); + const bool hasD3D12WindowInterop = resizedWindowRenderer && + m_renderer.AttachWindowRenderer(m_windowRenderer); + const bool hasHealthyD3D12WindowInterop = + resizedWindowRenderer && + hasD3D12WindowInterop; + m_editorWorkspace.SetViewportSurfacePresentationEnabled(hasHealthyD3D12WindowInterop); + + if (!resizedWindowRenderer || !m_windowRenderer.GetLastError().empty()) { + LogRuntimeTrace( + "present", + "window renderer resize warning: " + m_windowRenderer.GetLastError()); + } + + if (!hasD3D12WindowInterop) { + LogRuntimeTrace( + "present", + "failed to rebuild d3d12 window interop after resize: " + + m_renderer.GetLastRenderError()); + } + + return hasHealthyD3D12WindowInterop; +} + +bool Application::QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) const { + outWidth = 0u; + outHeight = 0u; + if (m_hwnd == nullptr || !IsWindow(m_hwnd)) { + return false; + } + + RECT clientRect = {}; + if (!GetClientRect(m_hwnd, &clientRect)) { + return false; + } + + const LONG width = clientRect.right - clientRect.left; + const LONG height = clientRect.bottom - clientRect.top; + if (width <= 0 || height <= 0) { + return false; + } + + outWidth = static_cast(width); + outHeight = static_cast(height); + return true; } void Application::OnDpiChanged(UINT dpi, const RECT& suggestedRect) { @@ -748,6 +852,7 @@ void Application::OnDpiChanged(UINT dpi, const RECT& suggestedRect) { suggestedRect.right - suggestedRect.left, suggestedRect.bottom - suggestedRect.top, SWP_NOZORDER | SWP_NOACTIVATE); + CommitWindowResize(); InvalidateRect(m_hwnd, nullptr, FALSE); } @@ -897,9 +1002,23 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP return 0; } break; + case WM_ENTERSIZEMOVE: + if (application != nullptr) { + application->OnEnterSizeMove(); + return 0; + } + break; + case WM_EXITSIZEMOVE: + if (application != nullptr) { + application->OnExitSizeMove(); + application->RenderFrame(); + return 0; + } + break; case WM_SIZE: if (application != nullptr && wParam != SIZE_MINIMIZED) { - application->OnResize(static_cast(LOWORD(lParam)), static_cast(HIWORD(lParam))); + application->OnResize(); + application->RenderFrame(); } return 0; case WM_PAINT: diff --git a/new_editor/app/Host/D3D12WindowRenderer.cpp b/new_editor/app/Host/D3D12WindowRenderer.cpp index fd0cfa5d..117e5f7d 100644 --- a/new_editor/app/Host/D3D12WindowRenderer.cpp +++ b/new_editor/app/Host/D3D12WindowRenderer.cpp @@ -2,6 +2,8 @@ #include +#include + namespace XCEngine::UI::Editor::Host { using ::XCEngine::RHI::CommandListDesc; @@ -31,6 +33,7 @@ bool D3D12WindowRenderer::Initialize(HWND hwnd, int width, int height) { Shutdown(); if (hwnd == nullptr || width <= 0 || height <= 0) { + m_lastError = "Initialize rejected an invalid hwnd or size."; return false; } @@ -40,6 +43,7 @@ bool D3D12WindowRenderer::Initialize(HWND hwnd, int width, int height) { m_device = RHIFactory::CreateRHIDevice(RHIType::D3D12); if (m_device == nullptr) { + m_lastError = "Failed to create the D3D12 RHI device."; Shutdown(); return false; } @@ -50,6 +54,7 @@ bool D3D12WindowRenderer::Initialize(HWND hwnd, int width, int height) { deviceDesc.enableGPUValidation = true; #endif if (!m_device->Initialize(deviceDesc)) { + m_lastError = "Failed to initialize the D3D12 RHI device."; Shutdown(); return false; } @@ -58,18 +63,31 @@ bool D3D12WindowRenderer::Initialize(HWND hwnd, int width, int height) { queueDesc.queueType = static_cast(::XCEngine::RHI::CommandQueueType::Direct); m_commandQueue = m_device->CreateCommandQueue(queueDesc); if (m_commandQueue == nullptr || GetD3D12CommandQueue() == nullptr) { + m_lastError = "Failed to create the D3D12 command queue."; Shutdown(); return false; } CommandListDesc commandListDesc = {}; commandListDesc.commandListType = static_cast(::XCEngine::RHI::CommandQueueType::Direct); - m_commandList = m_device->CreateCommandList(commandListDesc); - if (m_commandList == nullptr || GetD3D12CommandList() == nullptr) { - Shutdown(); - return false; + for (std::uint32_t commandListIndex = 0; commandListIndex < kSwapChainBufferCount; ++commandListIndex) { + m_commandLists[commandListIndex] = m_device->CreateCommandList(commandListDesc); + if (m_commandLists[commandListIndex] == nullptr) { + m_lastError = "Failed to create the D3D12 command list."; + Shutdown(); + return false; + } + + m_activeBackBufferIndex = commandListIndex; + if (GetD3D12CommandList() == nullptr) { + m_lastError = "Failed to resolve the D3D12 command list."; + Shutdown(); + return false; + } + + m_commandLists[commandListIndex]->Close(); } - m_commandList->Close(); + m_activeBackBufferIndex = 0u; SwapChainDesc swapChainDesc = {}; swapChainDesc.windowHandle = hwnd; @@ -78,6 +96,7 @@ bool D3D12WindowRenderer::Initialize(HWND hwnd, int width, int height) { swapChainDesc.bufferCount = kSwapChainBufferCount; m_swapChain = m_device->CreateSwapChain(swapChainDesc, m_commandQueue); if (m_swapChain == nullptr || GetD3D12SwapChain() == nullptr) { + m_lastError = "Failed to create the D3D12 swap chain."; Shutdown(); return false; } @@ -89,6 +108,7 @@ bool D3D12WindowRenderer::Initialize(HWND hwnd, int width, int height) { m_srvPool = m_device->CreateDescriptorPool(srvPoolDesc); m_srvHeap = dynamic_cast(m_srvPool); if (m_srvPool == nullptr || m_srvHeap == nullptr) { + m_lastError = "Failed to create the D3D12 SRV descriptor heap."; Shutdown(); return false; } @@ -96,15 +116,25 @@ bool D3D12WindowRenderer::Initialize(HWND hwnd, int width, int height) { m_srvDescriptorSize = m_srvHeap->GetDescriptorSize(); m_srvUsage.assign(kSrvDescriptorCount, false); if (!RecreateBackBufferViews()) { + m_lastError = "Failed to create swap chain back buffer views."; Shutdown(); return false; } + if (!InitializeFrameCompletionFence()) { + m_lastError = "Failed to create the host frame completion fence."; + Shutdown(); + return false; + } + + m_backBufferFenceValues.fill(0u); + m_lastError.clear(); return true; } void D3D12WindowRenderer::Shutdown() { WaitForGpuIdle(); + ReleaseFrameCompletionFence(); ReleaseBackBufferViews(); if (m_srvPool != nullptr) { @@ -121,10 +151,12 @@ void D3D12WindowRenderer::Shutdown() { m_swapChain = nullptr; } - if (m_commandList != nullptr) { - m_commandList->Shutdown(); - delete m_commandList; - m_commandList = nullptr; + for (auto*& commandList : m_commandLists) { + if (commandList != nullptr) { + commandList->Shutdown(); + delete commandList; + commandList = nullptr; + } } if (m_commandQueue != nullptr) { @@ -142,53 +174,179 @@ void D3D12WindowRenderer::Shutdown() { m_hwnd = nullptr; m_width = 0; m_height = 0; + m_activeBackBufferIndex = 0u; m_srvDescriptorSize = 0; + m_lastError.clear(); } -void D3D12WindowRenderer::Resize(int width, int height) { +bool D3D12WindowRenderer::Resize(int width, int height) { if (width <= 0 || height <= 0 || m_swapChain == nullptr) { - return; - } - - WaitForGpuIdle(); - ReleaseBackBufferViews(); - - m_swapChain->Resize(static_cast(width), static_cast(height)); - m_width = width; - m_height = height; - RecreateBackBufferViews(); -} - -bool D3D12WindowRenderer::BeginFrame() { - D3D12CommandQueue* d3d12Queue = GetD3D12CommandQueue(); - D3D12CommandList* d3d12CommandList = GetD3D12CommandList(); - if (m_swapChain == nullptr || - d3d12Queue == nullptr || - d3d12CommandList == nullptr || - m_srvHeap == nullptr) { return false; } - d3d12Queue->WaitForPreviousFrame(); - m_commandList->Reset(); + if (m_width == width && m_height == height) { + m_lastError.clear(); + return true; + } + + WaitForGpuIdle(); + ReleaseBackBufferCommandReferences(); + ReleaseBackBufferViews(); + D3D12SwapChain* d3d12SwapChain = GetD3D12SwapChain(); + if (d3d12SwapChain == nullptr) { + m_lastError = "Resize could not resolve the native D3D12 swap chain."; + return false; + } + + d3d12SwapChain->Resize( + static_cast(width), + static_cast(height)); + const HRESULT resizeHr = d3d12SwapChain->GetLastResizeResult(); + if (FAILED(resizeHr)) { + std::ostringstream error = {}; + error << "ResizeBuffers failed. requested=" + << width + << 'x' + << height + << " hr=0x" + << std::uppercase + << std::hex + << static_cast(resizeHr); + m_lastError = error.str(); + return false; + } + + const D3D12Texture* backBufferTexture = GetBackBufferTexture(0u); + if (backBufferTexture == nullptr) { + m_lastError = "Resize failed to restore swap chain back buffers after ResizeBuffers."; + return false; + } + + m_width = static_cast(backBufferTexture->GetWidth()); + m_height = static_cast(backBufferTexture->GetHeight()); + m_activeBackBufferIndex = m_swapChain->GetCurrentBackBufferIndex(); + m_backBufferFenceValues.fill(0u); + if (!RecreateBackBufferViews()) { + m_lastError = "Resize failed to recreate swap chain back buffer views."; + return false; + } + + if (m_width != width || m_height != height) { + std::ostringstream warning = {}; + warning << "Resize applied the current swap chain size. requested=" + << width + << 'x' + << height + << " actual=" + << m_width + << 'x' + << m_height; + m_lastError = warning.str(); + return true; + } + + m_lastError.clear(); + return true; +} + +bool D3D12WindowRenderer::BeginFrame() { + if (m_swapChain == nullptr || m_srvHeap == nullptr) { + m_lastError = "BeginFrame requires a swap chain, command list, and SRV heap."; + return false; + } + + m_activeBackBufferIndex = m_swapChain->GetCurrentBackBufferIndex(); + WaitForBackBufferFrame(m_activeBackBufferIndex); + ::XCEngine::RHI::RHICommandList* commandList = GetCurrentCommandList(); + D3D12CommandList* d3d12CommandList = GetD3D12CommandList(); + if (commandList == nullptr || d3d12CommandList == nullptr) { + m_lastError = "BeginFrame could not resolve the active command list."; + return false; + } + + commandList->Reset(); + m_lastError.clear(); + return true; +} + +bool D3D12WindowRenderer::PreparePresentSurface() { + ::XCEngine::Rendering::RenderContext renderContext = GetRenderContext(); + if (!renderContext.IsValid() || renderContext.commandList == nullptr) { + m_lastError = "PreparePresentSurface requires a valid render context."; + return false; + } + + if (m_swapChain == nullptr) { + m_lastError = "PreparePresentSurface requires an initialized swap chain."; + return false; + } + + const std::uint32_t backBufferIndex = m_swapChain->GetCurrentBackBufferIndex(); + if (backBufferIndex >= m_backBufferViews.size() || + m_backBufferViews[backBufferIndex] == nullptr) { + std::ostringstream error = {}; + error << "PreparePresentSurface could not find the current swap chain RTV. index=" + << backBufferIndex + << " rtvCount=" + << m_backBufferViews.size(); + m_lastError = error.str(); + return false; + } + + renderContext.commandList->TransitionBarrier( + m_backBufferViews[backBufferIndex], + ::XCEngine::RHI::ResourceStates::Present, + ::XCEngine::RHI::ResourceStates::RenderTarget); + m_lastError.clear(); return true; } bool D3D12WindowRenderer::SubmitFrame(bool presentSwapChain) { - if (m_commandList == nullptr || m_commandQueue == nullptr) { + ::XCEngine::RHI::RHICommandList* commandList = GetCurrentCommandList(); + if (commandList == nullptr || m_commandQueue == nullptr) { + m_lastError = "SubmitFrame requires an initialized command list and queue."; return false; } if (presentSwapChain && m_swapChain == nullptr) { + m_lastError = "SubmitFrame requested present without a swap chain."; return false; } - m_commandList->Close(); - void* commandLists[] = { m_commandList }; + commandList->Close(); + void* commandLists[] = { commandList }; m_commandQueue->ExecuteCommandLists(1, commandLists); if (presentSwapChain) { m_swapChain->Present(1, 0); } + m_lastError.clear(); + return true; +} + +bool D3D12WindowRenderer::SignalFrameCompletion() { + ID3D12CommandQueue* commandQueue = GetCommandQueue(); + if (commandQueue == nullptr || m_frameCompletionFence == nullptr) { + return false; + } + + ++m_lastSubmittedFrameValue; + const HRESULT hr = commandQueue->Signal( + m_frameCompletionFence.Get(), + m_lastSubmittedFrameValue); + if (SUCCEEDED(hr) && m_activeBackBufferIndex < m_backBufferFenceValues.size()) { + m_backBufferFenceValues[m_activeBackBufferIndex] = m_lastSubmittedFrameValue; + } + return SUCCEEDED(hr); +} + +bool D3D12WindowRenderer::PresentFrame() { + if (m_swapChain == nullptr) { + m_lastError = "PresentFrame requires an initialized swap chain."; + return false; + } + + m_swapChain->Present(1, 0); + m_lastError.clear(); return true; } @@ -206,6 +364,10 @@ ID3D12CommandQueue* D3D12WindowRenderer::GetCommandQueue() const { return queue != nullptr ? queue->GetCommandQueue() : nullptr; } +const std::string& D3D12WindowRenderer::GetLastError() const { + return m_lastError; +} + void D3D12WindowRenderer::AllocateShaderResourceDescriptor( D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle, D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle) { @@ -289,10 +451,31 @@ const ::XCEngine::Rendering::RenderSurface* D3D12WindowRenderer::GetCurrentRende return &m_backBufferSurfaces[backBufferIndex]; } +const D3D12Texture* D3D12WindowRenderer::GetCurrentBackBufferTexture() const { + if (m_swapChain == nullptr) { + return nullptr; + } + + return GetBackBufferTexture(m_swapChain->GetCurrentBackBufferIndex()); +} + +const D3D12Texture* D3D12WindowRenderer::GetBackBufferTexture(std::uint32_t index) const { + const D3D12SwapChain* d3d12SwapChain = GetD3D12SwapChain(); + if (d3d12SwapChain == nullptr) { + return nullptr; + } + + return d3d12SwapChain->TryGetBackBuffer(index); +} + +std::uint32_t D3D12WindowRenderer::GetBackBufferCount() const { + return kSwapChainBufferCount; +} + ::XCEngine::Rendering::RenderContext D3D12WindowRenderer::GetRenderContext() const { ::XCEngine::Rendering::RenderContext context = {}; context.device = m_device; - context.commandList = m_commandList; + context.commandList = GetCurrentCommandList(); context.commandQueue = m_commandQueue; context.backendType = RHIType::D3D12; return context; @@ -307,13 +490,20 @@ D3D12CommandQueue* D3D12WindowRenderer::GetD3D12CommandQueue() const { } D3D12CommandList* D3D12WindowRenderer::GetD3D12CommandList() const { - return m_commandList != nullptr ? static_cast(m_commandList) : nullptr; + ::XCEngine::RHI::RHICommandList* commandList = GetCurrentCommandList(); + return commandList != nullptr ? static_cast(commandList) : nullptr; } D3D12SwapChain* D3D12WindowRenderer::GetD3D12SwapChain() const { return m_swapChain != nullptr ? static_cast(m_swapChain) : nullptr; } +::XCEngine::RHI::RHICommandList* D3D12WindowRenderer::GetCurrentCommandList() const { + return m_activeBackBufferIndex < m_commandLists.size() + ? m_commandLists[m_activeBackBufferIndex] + : nullptr; +} + void D3D12WindowRenderer::AllocateShaderResourceDescriptorInternal( D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle, D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle) { @@ -363,9 +553,105 @@ void D3D12WindowRenderer::FreeShaderResourceDescriptorInternal( } } +bool D3D12WindowRenderer::InitializeFrameCompletionFence() { + ReleaseFrameCompletionFence(); + + ID3D12Device* device = GetDevice(); + if (device == nullptr) { + return false; + } + + const HRESULT hr = device->CreateFence( + 0u, + D3D12_FENCE_FLAG_NONE, + IID_PPV_ARGS(m_frameCompletionFence.ReleaseAndGetAddressOf())); + if (FAILED(hr)) { + return false; + } + + m_frameCompletionEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); + if (m_frameCompletionEvent == nullptr) { + m_frameCompletionFence.Reset(); + return false; + } + + m_lastSubmittedFrameValue = 0u; + return true; +} + +void D3D12WindowRenderer::ReleaseFrameCompletionFence() { + if (m_frameCompletionEvent != nullptr) { + CloseHandle(m_frameCompletionEvent); + m_frameCompletionEvent = nullptr; + } + + m_frameCompletionFence.Reset(); + m_backBufferFenceValues.fill(0u); + m_activeBackBufferIndex = 0u; + m_lastSubmittedFrameValue = 0u; +} + +void D3D12WindowRenderer::WaitForBackBufferFrame(std::uint32_t backBufferIndex) { + if (m_frameCompletionFence == nullptr || + m_frameCompletionEvent == nullptr || + backBufferIndex >= m_backBufferFenceValues.size()) { + return; + } + + const std::uint64_t fenceValue = m_backBufferFenceValues[backBufferIndex]; + if (fenceValue == 0u || + m_frameCompletionFence->GetCompletedValue() >= fenceValue) { + return; + } + + const HRESULT hr = m_frameCompletionFence->SetEventOnCompletion( + fenceValue, + m_frameCompletionEvent); + if (SUCCEEDED(hr)) { + WaitForSingleObject(m_frameCompletionEvent, INFINITE); + } +} + void D3D12WindowRenderer::WaitForGpuIdle() { - if (m_commandQueue != nullptr) { - m_commandQueue->WaitForIdle(); + ID3D12CommandQueue* commandQueue = GetCommandQueue(); + if (commandQueue == nullptr || + m_frameCompletionFence == nullptr || + m_frameCompletionEvent == nullptr) { + if (m_commandQueue != nullptr) { + m_commandQueue->WaitForIdle(); + } + return; + } + + ++m_lastSubmittedFrameValue; + const std::uint64_t fenceValue = m_lastSubmittedFrameValue; + const HRESULT signalHr = commandQueue->Signal( + m_frameCompletionFence.Get(), + fenceValue); + if (FAILED(signalHr)) { + return; + } + + if (m_frameCompletionFence->GetCompletedValue() >= fenceValue) { + return; + } + + const HRESULT waitHr = m_frameCompletionFence->SetEventOnCompletion( + fenceValue, + m_frameCompletionEvent); + if (SUCCEEDED(waitHr)) { + WaitForSingleObject(m_frameCompletionEvent, INFINITE); + } +} + +void D3D12WindowRenderer::ReleaseBackBufferCommandReferences() { + for (auto* commandList : m_commandLists) { + if (commandList == nullptr) { + continue; + } + + commandList->Reset(); + commandList->Close(); } } @@ -394,11 +680,21 @@ bool D3D12WindowRenderer::RecreateBackBufferViews() { viewDesc.dimension = ResourceViewDimension::Texture2D; for (std::uint32_t backBufferIndex = 0; backBufferIndex < kSwapChainBufferCount; ++backBufferIndex) { + D3D12Texture* backBufferTexture = d3d12SwapChain->TryGetBackBuffer(backBufferIndex); + if (backBufferTexture == nullptr) { + ReleaseBackBufferViews(); + m_lastError = "RecreateBackBufferViews could not resolve swap chain back buffer " + + std::to_string(backBufferIndex) + "."; + return false; + } + m_backBufferViews[backBufferIndex] = m_device->CreateRenderTargetView( - &d3d12SwapChain->GetBackBuffer(backBufferIndex), + backBufferTexture, viewDesc); if (m_backBufferViews[backBufferIndex] == nullptr) { ReleaseBackBufferViews(); + m_lastError = "RecreateBackBufferViews failed to create RTV for swap chain back buffer " + + std::to_string(backBufferIndex) + "."; return false; } diff --git a/new_editor/app/Host/NativeRenderer.cpp b/new_editor/app/Host/NativeRenderer.cpp index 6a1d728f..6017d586 100644 --- a/new_editor/app/Host/NativeRenderer.cpp +++ b/new_editor/app/Host/NativeRenderer.cpp @@ -1,6 +1,7 @@ #include "NativeRenderer.h" #include +#include #include #include #include @@ -53,6 +54,41 @@ float ResolveStrokePixelOffset(float thickness) { return std::fmod(roundedThickness, 2.0f) == 1.0f ? 0.5f : 0.0f; } +D2D1_BITMAP_PROPERTIES1 BuildD2DBitmapProperties( + DXGI_FORMAT format, + D2D1_BITMAP_OPTIONS options, + D2D1_ALPHA_MODE alphaMode = D2D1_ALPHA_MODE_IGNORE) { + return D2D1::BitmapProperties1( + options, + D2D1::PixelFormat(format, alphaMode), + kBaseDpi, + kBaseDpi); +} + +bool IsInteropTextureHandle(const ::XCEngine::UI::UITextureHandle& texture) { + return texture.kind == ::XCEngine::UI::UITextureHandleKind::ShaderResourceView && + texture.resourceHandle != 0u; +} + +bool CollectInteropTextureHandles( + const ::XCEngine::UI::UIDrawData& drawData, + std::vector<::XCEngine::UI::UITextureHandle>& outTextures) { + outTextures.clear(); + std::unordered_set seenKeys = {}; + for (const ::XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) { + for (const ::XCEngine::UI::UIDrawCommand& command : drawList.GetCommands()) { + if (!IsInteropTextureHandle(command.texture) || + !seenKeys.insert(command.texture.resourceHandle).second) { + continue; + } + + outTextures.push_back(command.texture); + } + } + + return true; +} + } // namespace bool NativeRenderer::Initialize(HWND hwnd) { @@ -64,7 +100,15 @@ bool NativeRenderer::Initialize(HWND hwnd) { } m_hwnd = hwnd; - HRESULT hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, m_d2dFactory.ReleaseAndGetAddressOf()); + D2D1_FACTORY_OPTIONS factoryOptions = {}; +#ifdef _DEBUG + factoryOptions.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION; +#endif + HRESULT hr = D2D1CreateFactory( + D2D1_FACTORY_TYPE_SINGLE_THREADED, + __uuidof(ID2D1Factory1), + &factoryOptions, + reinterpret_cast(m_d2dFactory.ReleaseAndGetAddressOf())); if (FAILED(hr)) { m_lastRenderError = HrToString("D2D1CreateFactory", hr); Shutdown(); @@ -82,10 +126,11 @@ bool NativeRenderer::Initialize(HWND hwnd) { } m_lastRenderError.clear(); - return EnsureRenderTarget(); + return true; } void NativeRenderer::Shutdown() { + DetachWindowRenderer(); while (!m_liveTextures.empty()) { auto it = m_liveTextures.begin(); delete *it; @@ -126,6 +171,64 @@ void NativeRenderer::Resize(UINT width, UINT height) { } } +bool NativeRenderer::AttachWindowRenderer(D3D12WindowRenderer& windowRenderer) { + if (m_windowRenderer != &windowRenderer) { + ReleaseWindowRendererInterop(); + m_windowRenderer = &windowRenderer; + } + + if (!EnsureWindowRendererInterop()) { + return false; + } + + // Keep a single real window presentation path. The hwnd render target is + // fallback-only and should not stay alive while D3D11On12 interop is healthy. + DiscardRenderTarget(); + + if (!m_backBufferInteropTargets.empty()) { + return true; + } + + return RebuildBackBufferInteropTargets(); +} + +void NativeRenderer::DetachWindowRenderer() { + ReleaseWindowRendererInterop(); + m_windowRenderer = nullptr; +} + +void NativeRenderer::ReleaseWindowRendererBackBufferTargets() { + ClearActiveInteropSourceTextures(); + if (m_d2dDeviceContext != nullptr) { + m_d2dDeviceContext->SetTarget(nullptr); + D2D1_TAG firstTag = 0u; + D2D1_TAG secondTag = 0u; + m_d2dDeviceContext->Flush(&firstTag, &secondTag); + } + if (m_d3d11DeviceContext != nullptr) { + m_d3d11DeviceContext->ClearState(); + m_d3d11DeviceContext->Flush(); + } + m_backBufferInteropTargets.clear(); +} + +bool NativeRenderer::RebuildWindowRendererBackBufferTargets() { + if (!EnsureWindowRendererInterop()) { + return false; + } + + DiscardRenderTarget(); + ReleaseWindowRendererBackBufferTargets(); + return RebuildBackBufferInteropTargets(); +} + +bool NativeRenderer::HasAttachedWindowRenderer() const { + return m_windowRenderer != nullptr && + m_d3d11On12Device != nullptr && + m_d2dDeviceContext != nullptr && + !m_backBufferInteropTargets.empty(); +} + bool NativeRenderer::Render(const ::XCEngine::UI::UIDrawData& drawData) { if (!EnsureRenderTarget()) { if (m_lastRenderError.empty()) { @@ -151,6 +254,110 @@ bool NativeRenderer::Render(const ::XCEngine::UI::UIDrawData& drawData) { return true; } +bool NativeRenderer::RenderToWindowRenderer(const ::XCEngine::UI::UIDrawData& drawData) { + if (!EnsureWindowRendererInterop()) { + if (m_lastRenderError.empty()) { + m_lastRenderError = "Window renderer interop is not available."; + } + return false; + } + + if (m_backBufferInteropTargets.empty() && + !RebuildBackBufferInteropTargets()) { + if (m_lastRenderError.empty()) { + m_lastRenderError = "Window renderer back buffer interop targets are unavailable."; + } + return false; + } + + const std::uint32_t backBufferIndex = + m_windowRenderer->GetSwapChain() != nullptr + ? m_windowRenderer->GetSwapChain()->GetCurrentBackBufferIndex() + : 0u; + if (backBufferIndex >= m_backBufferInteropTargets.size()) { + m_lastRenderError = "Back buffer interop target index is out of range."; + return false; + } + + if (!m_windowRenderer->PreparePresentSurface()) { + m_lastRenderError = "Failed to prepare the D3D12 present surface: " + + m_windowRenderer->GetLastError(); + return false; + } + + if (!m_windowRenderer->SubmitFrame(false)) { + m_lastRenderError = "Failed to submit the D3D12 frame before UI composition."; + return false; + } + + if (!PrepareActiveInteropSourceTextures(drawData)) { + ID3D11Resource* backBufferResource = + m_backBufferInteropTargets[backBufferIndex].wrappedResource.Get(); + if (backBufferResource != nullptr) { + m_d3d11On12Device->AcquireWrappedResources(&backBufferResource, 1u); + m_d3d11On12Device->ReleaseWrappedResources(&backBufferResource, 1u); + } + m_d3d11DeviceContext->Flush(); + ClearActiveInteropSourceTextures(); + const bool signaled = m_windowRenderer->SignalFrameCompletion(); + ReleaseWindowRendererInterop(); + if (!signaled) { + m_lastRenderError = + "Failed to signal D3D12 frame completion after interop preparation failed."; + } + return false; + } + + std::vector acquiredResources = {}; + acquiredResources.reserve(1u + m_activeInteropSourceTextures.size()); + acquiredResources.push_back(m_backBufferInteropTargets[backBufferIndex].wrappedResource.Get()); + for (const D3D12SourceTextureInteropResource& texture : m_activeInteropSourceTextures) { + acquiredResources.push_back(texture.wrappedResource.Get()); + } + + m_d3d11On12Device->AcquireWrappedResources( + acquiredResources.data(), + static_cast(acquiredResources.size())); + + m_d2dDeviceContext->SetTarget(m_backBufferInteropTargets[backBufferIndex].targetBitmap.Get()); + const bool rendered = RenderToTarget(*m_d2dDeviceContext.Get(), *m_interopBrush.Get(), drawData); + const HRESULT hr = m_d2dDeviceContext->EndDraw(); + + m_d3d11On12Device->ReleaseWrappedResources( + acquiredResources.data(), + static_cast(acquiredResources.size())); + m_d3d11DeviceContext->Flush(); + m_d2dDeviceContext->SetTarget(nullptr); + ClearActiveInteropSourceTextures(); + + if (!rendered || FAILED(hr)) { + m_lastRenderError = FAILED(hr) + ? HrToString("ID2D1DeviceContext::EndDraw", hr) + : "RenderToTarget failed during D3D11On12 composition."; + const bool signaled = m_windowRenderer->SignalFrameCompletion(); + ReleaseWindowRendererInterop(); + if (!signaled) { + m_lastRenderError = + "Failed to signal D3D12 frame completion after UI composition failed."; + } + return false; + } + + if (!m_windowRenderer->SignalFrameCompletion()) { + m_lastRenderError = "Failed to signal D3D12 frame completion after UI composition."; + ReleaseWindowRendererInterop(); + return false; + } + if (!m_windowRenderer->PresentFrame()) { + m_lastRenderError = "Failed to present the D3D12 swap chain."; + ReleaseWindowRendererInterop(); + return false; + } + + m_lastRenderError.clear(); + return true; +} + const std::string& NativeRenderer::GetLastRenderError() const { return m_lastRenderError; } @@ -388,6 +595,263 @@ bool NativeRenderer::CaptureToPng( return true; } +bool NativeRenderer::EnsureWindowRendererInterop() { + if (m_windowRenderer == nullptr) { + m_lastRenderError = "EnsureWindowRendererInterop requires an attached D3D12 window renderer."; + return false; + } + if (m_d2dFactory == nullptr || m_dwriteFactory == nullptr) { + m_lastRenderError = "EnsureWindowRendererInterop requires initialized D2D and DWrite factories."; + return false; + } + if (m_d3d11On12Device != nullptr && + m_d2dDeviceContext != nullptr && + m_interopBrush != nullptr) { + return true; + } + + ReleaseWindowRendererInterop(); + + ID3D12Device* d3d12Device = m_windowRenderer->GetDevice(); + ID3D12CommandQueue* d3d12CommandQueue = m_windowRenderer->GetCommandQueue(); + if (d3d12Device == nullptr || d3d12CommandQueue == nullptr) { + m_lastRenderError = "The attached D3D12 window renderer does not expose a native device/queue."; + return false; + } + + const std::array featureLevels = { + D3D_FEATURE_LEVEL_12_1, + D3D_FEATURE_LEVEL_12_0, + D3D_FEATURE_LEVEL_11_1, + D3D_FEATURE_LEVEL_11_0 + }; + const std::array commandQueues = { + reinterpret_cast(d3d12CommandQueue) + }; + + UINT createFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; +#ifdef _DEBUG + createFlags |= D3D11_CREATE_DEVICE_DEBUG; +#endif + + D3D_FEATURE_LEVEL actualFeatureLevel = D3D_FEATURE_LEVEL_11_0; + HRESULT hr = D3D11On12CreateDevice( + d3d12Device, + createFlags, + featureLevels.data(), + static_cast(featureLevels.size()), + commandQueues.data(), + static_cast(commandQueues.size()), + 0u, + m_d3d11Device.ReleaseAndGetAddressOf(), + m_d3d11DeviceContext.ReleaseAndGetAddressOf(), + &actualFeatureLevel); +#ifdef _DEBUG + if (FAILED(hr)) { + createFlags &= ~D3D11_CREATE_DEVICE_DEBUG; + hr = D3D11On12CreateDevice( + d3d12Device, + createFlags, + featureLevels.data(), + static_cast(featureLevels.size()), + commandQueues.data(), + static_cast(commandQueues.size()), + 0u, + m_d3d11Device.ReleaseAndGetAddressOf(), + m_d3d11DeviceContext.ReleaseAndGetAddressOf(), + &actualFeatureLevel); + } +#endif + if (FAILED(hr) || m_d3d11Device == nullptr || m_d3d11DeviceContext == nullptr) { + m_lastRenderError = HrToString("D3D11On12CreateDevice", hr); + ReleaseWindowRendererInterop(); + return false; + } + + hr = m_d3d11Device.As(&m_d3d11On12Device); + if (FAILED(hr) || m_d3d11On12Device == nullptr) { + m_lastRenderError = HrToString("ID3D11Device::QueryInterface(ID3D11On12Device)", hr); + ReleaseWindowRendererInterop(); + return false; + } + + Microsoft::WRL::ComPtr dxgiDevice; + hr = m_d3d11Device.As(&dxgiDevice); + if (FAILED(hr) || dxgiDevice == nullptr) { + m_lastRenderError = HrToString("ID3D11Device::QueryInterface(IDXGIDevice)", hr); + ReleaseWindowRendererInterop(); + return false; + } + + hr = m_d2dFactory->CreateDevice(dxgiDevice.Get(), m_d2dDevice.ReleaseAndGetAddressOf()); + if (FAILED(hr) || m_d2dDevice == nullptr) { + m_lastRenderError = HrToString("ID2D1Factory1::CreateDevice", hr); + ReleaseWindowRendererInterop(); + return false; + } + + hr = m_d2dDevice->CreateDeviceContext( + D2D1_DEVICE_CONTEXT_OPTIONS_NONE, + m_d2dDeviceContext.ReleaseAndGetAddressOf()); + if (FAILED(hr) || m_d2dDeviceContext == nullptr) { + m_lastRenderError = HrToString("ID2D1Device::CreateDeviceContext", hr); + ReleaseWindowRendererInterop(); + return false; + } + + hr = m_d2dDeviceContext->CreateSolidColorBrush( + D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f), + m_interopBrush.ReleaseAndGetAddressOf()); + if (FAILED(hr) || m_interopBrush == nullptr) { + m_lastRenderError = HrToString("ID2D1DeviceContext::CreateSolidColorBrush", hr); + ReleaseWindowRendererInterop(); + return false; + } + + m_d2dDeviceContext->SetDpi(kBaseDpi, kBaseDpi); + m_d2dDeviceContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE); + return RebuildBackBufferInteropTargets(); +} + +void NativeRenderer::ReleaseWindowRendererInterop() { + ReleaseWindowRendererBackBufferTargets(); + m_interopBrush.Reset(); + m_d2dDeviceContext.Reset(); + m_d2dDevice.Reset(); + m_d3d11On12Device.Reset(); + m_d3d11DeviceContext.Reset(); + m_d3d11Device.Reset(); +} + +bool NativeRenderer::RebuildBackBufferInteropTargets() { + m_backBufferInteropTargets.clear(); + if (m_windowRenderer == nullptr || m_d3d11On12Device == nullptr || m_d2dDeviceContext == nullptr) { + return false; + } + + const std::uint32_t backBufferCount = m_windowRenderer->GetBackBufferCount(); + m_backBufferInteropTargets.resize(backBufferCount); + for (std::uint32_t index = 0; index < backBufferCount; ++index) { + const ::XCEngine::RHI::D3D12Texture* backBufferTexture = + m_windowRenderer->GetBackBufferTexture(index); + if (backBufferTexture == nullptr || backBufferTexture->GetResource() == nullptr) { + m_lastRenderError = "Failed to resolve a D3D12 swap chain back buffer."; + m_backBufferInteropTargets.clear(); + return false; + } + + D3D11_RESOURCE_FLAGS resourceFlags = {}; + resourceFlags.BindFlags = D3D11_BIND_RENDER_TARGET; + HRESULT hr = m_d3d11On12Device->CreateWrappedResource( + backBufferTexture->GetResource(), + &resourceFlags, + D3D12_RESOURCE_STATE_RENDER_TARGET, + D3D12_RESOURCE_STATE_PRESENT, + IID_PPV_ARGS(m_backBufferInteropTargets[index].wrappedResource.ReleaseAndGetAddressOf())); + if (FAILED(hr) || m_backBufferInteropTargets[index].wrappedResource == nullptr) { + m_lastRenderError = HrToString("ID3D11On12Device::CreateWrappedResource(backbuffer)", hr); + m_backBufferInteropTargets.clear(); + return false; + } + + Microsoft::WRL::ComPtr dxgiSurface; + hr = m_backBufferInteropTargets[index].wrappedResource.As(&dxgiSurface); + if (FAILED(hr) || dxgiSurface == nullptr) { + m_lastRenderError = HrToString("ID3D11Resource::QueryInterface(IDXGISurface)", hr); + m_backBufferInteropTargets.clear(); + return false; + } + + const D2D1_BITMAP_PROPERTIES1 bitmapProperties = + BuildD2DBitmapProperties( + backBufferTexture->GetDesc().Format, + D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW); + hr = m_d2dDeviceContext->CreateBitmapFromDxgiSurface( + dxgiSurface.Get(), + &bitmapProperties, + m_backBufferInteropTargets[index].targetBitmap.ReleaseAndGetAddressOf()); + if (FAILED(hr) || m_backBufferInteropTargets[index].targetBitmap == nullptr) { + m_lastRenderError = HrToString("ID2D1DeviceContext::CreateBitmapFromDxgiSurface(backbuffer)", hr); + m_backBufferInteropTargets.clear(); + return false; + } + } + + return true; +} + +void NativeRenderer::ClearActiveInteropSourceTextures() { + m_activeInteropBitmaps.clear(); + m_activeInteropSourceTextures.clear(); +} + +bool NativeRenderer::PrepareActiveInteropSourceTextures( + const ::XCEngine::UI::UIDrawData& drawData) { + ClearActiveInteropSourceTextures(); + if (m_windowRenderer == nullptr || m_d3d11On12Device == nullptr || m_d2dDeviceContext == nullptr) { + return false; + } + + std::vector<::XCEngine::UI::UITextureHandle> textureHandles = {}; + CollectInteropTextureHandles(drawData, textureHandles); + m_activeInteropSourceTextures.reserve(textureHandles.size()); + + for (const ::XCEngine::UI::UITextureHandle& textureHandle : textureHandles) { + auto* texture = + reinterpret_cast<::XCEngine::RHI::RHITexture*>(textureHandle.resourceHandle); + auto* nativeTexture = dynamic_cast<::XCEngine::RHI::D3D12Texture*>(texture); + if (nativeTexture == nullptr || nativeTexture->GetResource() == nullptr) { + m_lastRenderError = "Failed to resolve a D3D12 source texture for UI composition."; + ClearActiveInteropSourceTextures(); + return false; + } + + D3D11_RESOURCE_FLAGS resourceFlags = {}; + resourceFlags.BindFlags = D3D11_BIND_SHADER_RESOURCE; + + D3D12SourceTextureInteropResource resource = {}; + resource.key = textureHandle.resourceHandle; + HRESULT hr = m_d3d11On12Device->CreateWrappedResource( + nativeTexture->GetResource(), + &resourceFlags, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, + IID_PPV_ARGS(resource.wrappedResource.ReleaseAndGetAddressOf())); + if (FAILED(hr) || resource.wrappedResource == nullptr) { + m_lastRenderError = HrToString("ID3D11On12Device::CreateWrappedResource(source)", hr); + ClearActiveInteropSourceTextures(); + return false; + } + + Microsoft::WRL::ComPtr dxgiSurface; + hr = resource.wrappedResource.As(&dxgiSurface); + if (FAILED(hr) || dxgiSurface == nullptr) { + m_lastRenderError = HrToString("ID3D11Resource::QueryInterface(IDXGISurface)", hr); + ClearActiveInteropSourceTextures(); + return false; + } + + const D2D1_BITMAP_PROPERTIES1 bitmapProperties = + BuildD2DBitmapProperties( + nativeTexture->GetDesc().Format, + D2D1_BITMAP_OPTIONS_NONE); + hr = m_d2dDeviceContext->CreateBitmapFromDxgiSurface( + dxgiSurface.Get(), + &bitmapProperties, + resource.bitmap.ReleaseAndGetAddressOf()); + if (FAILED(hr) || resource.bitmap == nullptr) { + m_lastRenderError = HrToString("ID2D1DeviceContext::CreateBitmapFromDxgiSurface(source)", hr); + ClearActiveInteropSourceTextures(); + return false; + } + + m_activeInteropBitmaps.emplace(resource.key, resource.bitmap); + m_activeInteropSourceTextures.push_back(std::move(resource)); + } + + return true; +} + bool NativeRenderer::EnsureRenderTarget() { if (!m_hwnd || !m_d2dFactory || !m_dwriteFactory) { m_lastRenderError = "EnsureRenderTarget requires hwnd, D2D factory, and DWrite factory."; @@ -601,6 +1065,23 @@ bool NativeRenderer::ResolveTextureBitmap( return true; } +bool NativeRenderer::ResolveInteropBitmap( + const ::XCEngine::UI::UITextureHandle& texture, + Microsoft::WRL::ComPtr& outBitmap) const { + outBitmap.Reset(); + if (!IsInteropTextureHandle(texture)) { + return false; + } + + const auto found = m_activeInteropBitmaps.find(texture.resourceHandle); + if (found == m_activeInteropBitmaps.end() || found->second == nullptr) { + return false; + } + + outBitmap = found->second; + return true; +} + bool NativeRenderer::RenderToTarget( ID2D1RenderTarget& renderTarget, ID2D1SolidColorBrush& solidBrush, @@ -798,23 +1279,33 @@ void NativeRenderer::RenderCommand( break; } - auto* texture = reinterpret_cast(command.texture.nativeHandle); - if (texture == nullptr || m_liveTextures.find(texture) == m_liveTextures.end()) { - const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); - renderTarget.DrawRectangle(rect, &solidBrush, 1.0f); - break; - } - Microsoft::WRL::ComPtr bitmap; - if (!ResolveTextureBitmap(renderTarget, *texture, bitmap) || !bitmap) { - break; + if (command.texture.kind == ::XCEngine::UI::UITextureHandleKind::ShaderResourceView) { + if (!ResolveInteropBitmap(command.texture, bitmap) || !bitmap) { + const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); + renderTarget.DrawRectangle(rect, &solidBrush, 1.0f); + break; + } + } else { + auto* texture = reinterpret_cast(command.texture.nativeHandle); + if (texture == nullptr || m_liveTextures.find(texture) == m_liveTextures.end()) { + const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); + renderTarget.DrawRectangle(rect, &solidBrush, 1.0f); + break; + } + + if (!ResolveTextureBitmap(renderTarget, *texture, bitmap) || !bitmap) { + break; + } } const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); - const float sourceLeft = static_cast(texture->width) * std::clamp(command.uvMin.x, 0.0f, 1.0f); - const float sourceTop = static_cast(texture->height) * std::clamp(command.uvMin.y, 0.0f, 1.0f); - const float sourceRight = static_cast(texture->width) * std::clamp(command.uvMax.x, 0.0f, 1.0f); - const float sourceBottom = static_cast(texture->height) * std::clamp(command.uvMax.y, 0.0f, 1.0f); + const float sourceWidth = static_cast(command.texture.width); + const float sourceHeight = static_cast(command.texture.height); + const float sourceLeft = sourceWidth * std::clamp(command.uvMin.x, 0.0f, 1.0f); + const float sourceTop = sourceHeight * std::clamp(command.uvMin.y, 0.0f, 1.0f); + const float sourceRight = sourceWidth * std::clamp(command.uvMax.x, 0.0f, 1.0f); + const float sourceBottom = sourceHeight * std::clamp(command.uvMax.y, 0.0f, 1.0f); renderTarget.DrawBitmap( bitmap.Get(), rect,