Fix new editor window resize presentation

This commit is contained in:
2026-04-13 12:20:25 +08:00
parent adb6fe4659
commit 0cc3d6da46
5 changed files with 1019 additions and 78 deletions

View File

@@ -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,