checkpoint(new_editor): native d3d12 ui path
Key node 1: move main-window UI presentation onto the D3D12 render loop with native UI renderer, text system, and texture host. Key node 2: wire frame timing/FPS display, window runtime, swapchain presentation, and native screenshot capture around the new path. Key node 3: carry editor shell/workspace/viewport/panel interaction updates needed by the new renderer and detached window flow. Key node 4: pump async resource loads and scene bridge follow-up needed for scene content visibility in new_editor.
This commit is contained in:
@@ -18,27 +18,9 @@ bool NativeRenderer::Initialize(HWND hwnd) {
|
||||
}
|
||||
|
||||
m_hwnd = hwnd;
|
||||
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();
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = DWriteCreateFactory(
|
||||
DWRITE_FACTORY_TYPE_SHARED,
|
||||
__uuidof(IDWriteFactory),
|
||||
reinterpret_cast<IUnknown**>(m_dwriteFactory.ReleaseAndGetAddressOf()));
|
||||
if (FAILED(hr)) {
|
||||
m_lastRenderError = HrToString("DWriteCreateFactory", hr);
|
||||
std::string error = {};
|
||||
if (!EnsureCoreFactories(error)) {
|
||||
m_lastRenderError = error;
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
@@ -48,7 +30,7 @@ bool NativeRenderer::Initialize(HWND hwnd) {
|
||||
}
|
||||
|
||||
void NativeRenderer::Shutdown() {
|
||||
DetachWindowRenderer();
|
||||
m_windowInterop.Detach();
|
||||
while (!m_liveTextures.empty()) {
|
||||
auto it = m_liveTextures.begin();
|
||||
delete *it;
|
||||
@@ -141,121 +123,42 @@ 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.";
|
||||
bool NativeRenderer::EnsureCoreFactories(std::string& outError) {
|
||||
outError.clear();
|
||||
|
||||
if (m_d2dFactory != nullptr && m_dwriteFactory != nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_d2dFactory == nullptr) {
|
||||
D2D1_FACTORY_OPTIONS factoryOptions = {};
|
||||
#ifdef _DEBUG
|
||||
factoryOptions.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
|
||||
#endif
|
||||
const HRESULT factoryHr = D2D1CreateFactory(
|
||||
D2D1_FACTORY_TYPE_SINGLE_THREADED,
|
||||
__uuidof(ID2D1Factory1),
|
||||
&factoryOptions,
|
||||
reinterpret_cast<void**>(m_d2dFactory.ReleaseAndGetAddressOf()));
|
||||
if (FAILED(factoryHr) || m_d2dFactory == nullptr) {
|
||||
outError = HrToString("D2D1CreateFactory", factoryHr);
|
||||
m_d2dFactory.Reset();
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_windowInterop.HasBackBufferTargets() &&
|
||||
!m_windowInterop.RebuildBackBufferTargets()) {
|
||||
if (m_lastRenderError.empty()) {
|
||||
m_lastRenderError = "Window renderer back buffer interop targets are unavailable.";
|
||||
if (m_dwriteFactory == nullptr) {
|
||||
const HRESULT writeHr = DWriteCreateFactory(
|
||||
DWRITE_FACTORY_TYPE_SHARED,
|
||||
__uuidof(IDWriteFactory),
|
||||
reinterpret_cast<IUnknown**>(m_dwriteFactory.ReleaseAndGetAddressOf()));
|
||||
if (FAILED(writeHr) || m_dwriteFactory == nullptr) {
|
||||
outError = HrToString("DWriteCreateFactory", writeHr);
|
||||
m_dwriteFactory.Reset();
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ID3D11On12Device* d3d11On12Device = m_windowInterop.GetD3D11On12Device();
|
||||
ID3D11DeviceContext* d3d11DeviceContext = m_windowInterop.GetD3D11DeviceContext();
|
||||
ID2D1DeviceContext* d2dDeviceContext = m_windowInterop.GetD2DDeviceContext();
|
||||
ID2D1SolidColorBrush* interopBrush = m_windowInterop.GetInteropBrush();
|
||||
if (d3d11On12Device == nullptr ||
|
||||
d3d11DeviceContext == nullptr ||
|
||||
d2dDeviceContext == nullptr ||
|
||||
interopBrush == nullptr) {
|
||||
m_lastRenderError = "Window renderer interop resources are incomplete.";
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::uint32_t backBufferIndex = m_windowInterop.GetCurrentBackBufferIndex();
|
||||
if (m_windowInterop.GetWrappedBackBufferResource(backBufferIndex) == nullptr ||
|
||||
m_windowInterop.GetBackBufferTargetBitmap(backBufferIndex) == nullptr) {
|
||||
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 (!m_windowInterop.PrepareSourceTextures(drawData)) {
|
||||
ID3D11Resource* backBufferResource =
|
||||
m_windowInterop.GetWrappedBackBufferResource(backBufferIndex);
|
||||
if (backBufferResource != nullptr) {
|
||||
d3d11On12Device->AcquireWrappedResources(&backBufferResource, 1u);
|
||||
d3d11On12Device->ReleaseWrappedResources(&backBufferResource, 1u);
|
||||
}
|
||||
d3d11DeviceContext->Flush();
|
||||
m_windowInterop.ClearSourceTextures();
|
||||
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 = {};
|
||||
m_windowInterop.BuildAcquiredResources(backBufferIndex, acquiredResources);
|
||||
if (acquiredResources.empty()) {
|
||||
m_lastRenderError = "No wrapped interop resources were prepared for UI composition.";
|
||||
return false;
|
||||
}
|
||||
|
||||
d3d11On12Device->AcquireWrappedResources(
|
||||
acquiredResources.data(),
|
||||
static_cast<UINT>(acquiredResources.size()));
|
||||
|
||||
d2dDeviceContext->SetTarget(m_windowInterop.GetBackBufferTargetBitmap(backBufferIndex));
|
||||
const bool rendered = RenderToTarget(*d2dDeviceContext, *interopBrush, drawData);
|
||||
const HRESULT hr = d2dDeviceContext->EndDraw();
|
||||
|
||||
d3d11On12Device->ReleaseWrappedResources(
|
||||
acquiredResources.data(),
|
||||
static_cast<UINT>(acquiredResources.size()));
|
||||
d3d11DeviceContext->Flush();
|
||||
d2dDeviceContext->SetTarget(nullptr);
|
||||
m_windowInterop.ClearSourceTextures();
|
||||
|
||||
if (!rendered || FAILED(hr)) {
|
||||
m_lastRenderError = FAILED(hr)
|
||||
? HrToString("ID2D1DeviceContext::EndDraw", hr)
|
||||
: "RenderToTarget failed during D3D11On12 composition.";
|
||||
const bool signaled = m_windowRenderer->SignalFrameCompletion();
|
||||
if (hr == D2DERR_RECREATE_TARGET) {
|
||||
ReleaseWindowRendererBackBufferTargets();
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -688,52 +591,6 @@ namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
using namespace NativeRendererHelpers;
|
||||
|
||||
float NativeRenderer::MeasureTextWidth(
|
||||
const ::XCEngine::UI::Editor::UIEditorTextMeasureRequest& request) const {
|
||||
if (!m_dwriteFactory || request.text.empty()) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
const std::wstring text = Utf8ToWide(request.text);
|
||||
if (text.empty()) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
const float dpiScale = ClampDpiScale(m_dpiScale);
|
||||
const float scaledFontSize = ResolveFontSize(request.fontSize) * dpiScale;
|
||||
IDWriteTextFormat* textFormat = GetTextFormat(scaledFontSize);
|
||||
if (textFormat == nullptr) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<IDWriteTextLayout> textLayout;
|
||||
HRESULT hr = m_dwriteFactory->CreateTextLayout(
|
||||
text.c_str(),
|
||||
static_cast<UINT32>(text.size()),
|
||||
textFormat,
|
||||
4096.0f,
|
||||
scaledFontSize * 2.0f,
|
||||
textLayout.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr) || !textLayout) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
DWRITE_TEXT_METRICS textMetrics = {};
|
||||
hr = textLayout->GetMetrics(&textMetrics);
|
||||
if (FAILED(hr)) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
DWRITE_OVERHANG_METRICS overhangMetrics = {};
|
||||
float width = textMetrics.widthIncludingTrailingWhitespace;
|
||||
if (SUCCEEDED(textLayout->GetOverhangMetrics(&overhangMetrics))) {
|
||||
width += (std::max)(overhangMetrics.left, 0.0f);
|
||||
width += (std::max)(overhangMetrics.right, 0.0f);
|
||||
}
|
||||
|
||||
return std::ceil(width) / dpiScale;
|
||||
}
|
||||
|
||||
IDWriteTextFormat* NativeRenderer::GetTextFormat(float fontSize) const {
|
||||
if (!m_dwriteFactory) {
|
||||
return nullptr;
|
||||
@@ -1121,78 +978,8 @@ namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
using namespace NativeRendererHelpers;
|
||||
|
||||
bool NativeRenderer::AttachWindowRenderer(D3D12WindowRenderer& windowRenderer) {
|
||||
if (m_windowRenderer != &windowRenderer) {
|
||||
ReleaseWindowRendererInterop();
|
||||
m_windowRenderer = &windowRenderer;
|
||||
}
|
||||
|
||||
if (!EnsureWindowRendererInterop()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DiscardRenderTarget();
|
||||
|
||||
if (m_windowInterop.HasBackBufferTargets()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return m_windowInterop.RebuildBackBufferTargets();
|
||||
}
|
||||
|
||||
void NativeRenderer::DetachWindowRenderer() {
|
||||
ReleaseWindowRendererInterop();
|
||||
m_windowRenderer = nullptr;
|
||||
}
|
||||
|
||||
void NativeRenderer::ReleaseWindowRendererBackBufferTargets() {
|
||||
m_windowInterop.ReleaseBackBufferTargets();
|
||||
}
|
||||
|
||||
bool NativeRenderer::RebuildWindowRendererBackBufferTargets() {
|
||||
if (!EnsureWindowRendererInterop()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DiscardRenderTarget();
|
||||
ReleaseWindowRendererBackBufferTargets();
|
||||
return m_windowInterop.RebuildBackBufferTargets();
|
||||
}
|
||||
|
||||
bool NativeRenderer::HasAttachedWindowRenderer() const {
|
||||
return m_windowInterop.HasAttachedWindowRenderer();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const bool attached = m_windowInterop.Attach(*m_windowRenderer, *m_d2dFactory.Get());
|
||||
if (!attached) {
|
||||
m_lastRenderError = m_windowInterop.GetLastError();
|
||||
} else {
|
||||
m_lastRenderError.clear();
|
||||
}
|
||||
return attached;
|
||||
}
|
||||
|
||||
void NativeRenderer::ReleaseWindowRendererInterop() {
|
||||
m_windowInterop.Detach();
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Host
|
||||
|
||||
namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
using namespace NativeRendererHelpers;
|
||||
|
||||
bool NativeRenderer::CaptureToPng(
|
||||
D3D12WindowRenderer* windowRenderer,
|
||||
const ::XCEngine::UI::UIDrawData& drawData,
|
||||
UINT width,
|
||||
UINT height,
|
||||
@@ -1204,8 +991,7 @@ bool NativeRenderer::CaptureToPng(
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_d2dFactory || !m_dwriteFactory) {
|
||||
outError = "CaptureToPng requires an initialized NativeRenderer.";
|
||||
if (!EnsureCoreFactories(outError)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1213,10 +999,34 @@ bool NativeRenderer::CaptureToPng(
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<::XCEngine::UI::UITextureHandle> interopTextures = {};
|
||||
CollectInteropTextureHandles(drawData, interopTextures);
|
||||
const bool requiresInterop = !interopTextures.empty();
|
||||
if (requiresInterop) {
|
||||
if (windowRenderer == nullptr) {
|
||||
outError =
|
||||
"CaptureToPng requires a D3D12 window renderer to resolve GPU UI textures.";
|
||||
return false;
|
||||
}
|
||||
if (!m_windowInterop.Attach(*windowRenderer, *m_d2dFactory.Get())) {
|
||||
outError = m_windowInterop.GetLastError();
|
||||
return false;
|
||||
}
|
||||
if (!m_windowInterop.PrepareSourceTextures(drawData)) {
|
||||
outError = m_windowInterop.GetLastError();
|
||||
m_windowInterop.Detach();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::error_code errorCode = {};
|
||||
std::filesystem::create_directories(outputPath.parent_path(), errorCode);
|
||||
if (errorCode) {
|
||||
outError = "Failed to create screenshot directory: " + outputPath.parent_path().string();
|
||||
if (requiresInterop) {
|
||||
m_windowInterop.ClearSourceTextures();
|
||||
m_windowInterop.Detach();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1245,6 +1055,10 @@ bool NativeRenderer::CaptureToPng(
|
||||
offscreenRenderTarget.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr)) {
|
||||
outError = HrToString("ID2D1Factory::CreateWicBitmapRenderTarget", hr);
|
||||
if (requiresInterop) {
|
||||
m_windowInterop.ClearSourceTextures();
|
||||
m_windowInterop.Detach();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1254,11 +1068,19 @@ bool NativeRenderer::CaptureToPng(
|
||||
offscreenBrush.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr)) {
|
||||
outError = HrToString("ID2D1RenderTarget::CreateSolidColorBrush", hr);
|
||||
if (requiresInterop) {
|
||||
m_windowInterop.ClearSourceTextures();
|
||||
m_windowInterop.Detach();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool rendered = RenderToTarget(*offscreenRenderTarget.Get(), *offscreenBrush.Get(), drawData);
|
||||
hr = offscreenRenderTarget->EndDraw();
|
||||
if (requiresInterop) {
|
||||
m_windowInterop.ClearSourceTextures();
|
||||
m_windowInterop.Detach();
|
||||
}
|
||||
if (!rendered || FAILED(hr)) {
|
||||
outError = HrToString("ID2D1RenderTarget::EndDraw", hr);
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user