Fix new editor window resize presentation
This commit is contained in:
@@ -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<DWORD>(std::size(buffer)));
|
||||
return length > 0u && buffer[0] != L'0';
|
||||
}
|
||||
|
||||
Application* GetApplicationFromWindow(HWND hwnd) {
|
||||
return reinterpret_cast<Application*>(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<UIInputEvent> 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<UIInputEvent>& 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<int>(width), static_cast<int>(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<int>(width), static_cast<int>(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<UINT>(width);
|
||||
outHeight = static_cast<UINT>(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<UINT>(LOWORD(lParam)), static_cast<UINT>(HIWORD(lParam)));
|
||||
application->OnResize();
|
||||
application->RenderFrame();
|
||||
}
|
||||
return 0;
|
||||
case WM_PAINT:
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <XCEngine/RHI/RHIFactory.h>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
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<std::uint32_t>(::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<std::uint32_t>(::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<D3D12DescriptorHeap*>(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<std::uint32_t>(width), static_cast<std::uint32_t>(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<std::uint32_t>(width),
|
||||
static_cast<std::uint32_t>(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<unsigned long>(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<int>(backBufferTexture->GetWidth());
|
||||
m_height = static_cast<int>(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<D3D12CommandList*>(m_commandList) : nullptr;
|
||||
::XCEngine::RHI::RHICommandList* commandList = GetCurrentCommandList();
|
||||
return commandList != nullptr ? static_cast<D3D12CommandList*>(commandList) : nullptr;
|
||||
}
|
||||
|
||||
D3D12SwapChain* D3D12WindowRenderer::GetD3D12SwapChain() const {
|
||||
return m_swapChain != nullptr ? static_cast<D3D12SwapChain*>(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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "NativeRenderer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
@@ -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<std::uintptr_t> 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<void**>(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<ID3D11Resource*> 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<UINT>(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<UINT>(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<D3D_FEATURE_LEVEL, 4> featureLevels = {
|
||||
D3D_FEATURE_LEVEL_12_1,
|
||||
D3D_FEATURE_LEVEL_12_0,
|
||||
D3D_FEATURE_LEVEL_11_1,
|
||||
D3D_FEATURE_LEVEL_11_0
|
||||
};
|
||||
const std::array<IUnknown*, 1> commandQueues = {
|
||||
reinterpret_cast<IUnknown*>(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<UINT>(featureLevels.size()),
|
||||
commandQueues.data(),
|
||||
static_cast<UINT>(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<UINT>(featureLevels.size()),
|
||||
commandQueues.data(),
|
||||
static_cast<UINT>(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<IDXGIDevice> 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<IDXGISurface> 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<IDXGISurface> 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<ID2D1Bitmap>& 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<NativeTextureResource*>(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<ID2D1Bitmap> 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<NativeTextureResource*>(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<float>(texture->width) * std::clamp(command.uvMin.x, 0.0f, 1.0f);
|
||||
const float sourceTop = static_cast<float>(texture->height) * std::clamp(command.uvMin.y, 0.0f, 1.0f);
|
||||
const float sourceRight = static_cast<float>(texture->width) * std::clamp(command.uvMax.x, 0.0f, 1.0f);
|
||||
const float sourceBottom = static_cast<float>(texture->height) * std::clamp(command.uvMax.y, 0.0f, 1.0f);
|
||||
const float sourceWidth = static_cast<float>(command.texture.width);
|
||||
const float sourceHeight = static_cast<float>(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,
|
||||
|
||||
Reference in New Issue
Block a user