Refactor new editor host orchestration

This commit is contained in:
2026-04-13 19:37:10 +08:00
parent d2140bf5cc
commit f3fc34898a
9 changed files with 648 additions and 223 deletions

View File

@@ -0,0 +1,178 @@
# NewEditor 宿主重构计划
## 目标
`new_editor` 当前的窗口宿主从“功能可运行的过渡方案”收敛成可长期演进的 Editor 宿主架构,核心目标如下:
1. 主显示链最终统一到纯 D3D12。
2. 窗口线程只负责消息和状态,不承担 GPU 重活。
3. live resize 必须真实更新,但不能再走同步阻塞窗口线程的路径。
4. Editor shell 和 Scene/Game viewport 统一纳入宿主合成层。
5. 当前 `D3D11On12 + D2D` 只允许作为过渡路径,不能继续加深耦合。
## 当前问题
### 1. 宿主职责混在 `Application`
当前 `Application` 同时承担:
- Win32 消息调度
- 宿主运行时状态
- resize / dpi / deferred render 调度
- editor 状态更新
- present 前后的宿主控制
这会导致宿主问题难以单独定位和演进。
### 2. 主显示链过厚
当前主路径仍然依赖:
- D3D12 viewport 渲染
- swapchain backbuffer
- D3D11On12 wrapped resource
- D2D 绘制 shell
- present
这条链在 live resize、frame pacing、backbuffer 生命周期上都偏重。
### 3. resize 热路径仍然偏保守
虽然已经把 `WM_SIZE` 从直接同步 resize 调整为 deferred render 触发,但 resize 热路径里仍存在:
- backbuffer interop target 重建
- swapchain resize
- 资源生命周期收束
这还不是最终架构。
## 重构阶段
## 阶段 1宿主分层
### 目标
先把结构理顺,停止继续把宿主逻辑堆进 `Application`
### 任务
1. 拆出宿主运行时状态对象。
2. 拆出窗口消息调度与 deferred render 调度。
3.`Application` 只保留 editor 业务更新与高层协作职责。
4. 为后续 HostRenderer / HostCompositor 留清晰边界。
### 完成标准
1. resize / dpi / deferred render / interactive resize 状态不再散落在 `Application` 成员里。
2. `Application` 的宿主状态字段明显减少。
3. 现有功能与布局不回退。
## 阶段 2建立 HostRenderer / HostCompositor 边界
### 目标
把宿主渲染拆成明确两层:
1. `HostRenderer`
责任device / queue / swapchain / backbuffer / fence / present
2. `HostCompositor`
责任:把 shell draw data、viewport texture、icon/text 统一合成到 backbuffer
### 任务
1. 停止让 `NativeRenderer` 既像窗口绘制器又像过渡 compositor。
2. 把“主窗口显示”和“UI 绘制命令解释”职责显式拆开。
3. 为纯 D3D12 compositor 做接口准备。
### 完成标准
1. `HostRenderer` 不依赖 D2D 语义。
2. `HostCompositor` 成为唯一的宿主 UI 合成入口。
3. 现有 `NativeRenderer` 明确退化为过渡层或 fallback。
## 阶段 3主显示链切换到纯 D3D12
### 目标
去掉 `D3D11On12 + D2D` 在主显示链中的核心地位。
### 任务
1. shell 矩形、线条、图像、文字统一进入 D3D12 UI compositor。
2. Scene/Game viewport 作为普通 SRV 输入参与同一条 compositor pass。
3. backbuffer 只通过 D3D12 呈现。
### 完成标准
1. 主窗口显示不再依赖 D2D。
2. resize 时不再重建 D3D11On12 backbuffer interop target。
3. `new_editor` 主显示链可在没有 D3D11On12 的条件下工作。
## 阶段 4重写 resize 状态机
### 目标
做到真实 live resize但不阻塞窗口线程。
### 任务
1. `WM_SIZE` 只更新最新目标尺寸。
2. render tick 只消费最新尺寸,不处理过期尺寸。
3. resize 不允许在消息处理里做 GPU 等待。
4. resize / present / viewport surface 生命周期统一到宿主状态机。
### 完成标准
1. 拖动窗口边界时不黑屏。
2. 不出现黑白垃圾区。
3. 窗口拖动体感明显优于当前实现。
## 阶段 5去掉 resize 路径里的全队列等待
### 目标
去掉当前最重的同步点。
### 任务
1. 改成 per-backbuffer / per-frame 生命周期管理。
2. 只等待必须退休的资源代际。
3. 禁止 resize 路径里的整队列 idle 等待。
### 完成标准
1. resize 期间 CPU/GPU 同步明显减少。
2. live resize 手感继续改善。
## 阶段 6viewport 生命周期收口
### 目标
让 Scene/Game viewport 与外层宿主真正解耦。
### 任务
1. viewport render target 长期存活,不跟着宿主链大拆大建。
2. 宿主 compositor 只采样 viewport 结果,不干预其内部资源生命周期。
3. Scene/Game 在 interactive resize 期间保持稳定。
### 完成标准
1. viewport 不再因宿主 resize 逻辑出现黑屏或闪烁。
2. 宿主和 viewport 各自职责明确。
## 执行顺序
1. 先完成阶段 1。
2. 然后搭好阶段 2 的 HostRenderer / HostCompositor 边界。
3. 再推进阶段 3切主显示链到纯 D3D12。
4. 最后做阶段 4、5、6 的性能与生命周期收口。
## 当前落点
当前阶段 1 已完成,阶段 2 已开始收口:
1. 已新增 `HostRuntimeState`,把宿主 DPI / interactive resize / pending resize / deferred render 状态从 `Application` 中抽离。
2. 已新增 `WindowMessageDispatcher`,把 `WndProc` 中的宿主消息调度与 deferred render 调度拆到 `Host` 层。
3. 已把 `D3D12WindowRenderLoop` 从悬空 helper 升级为主窗口帧编排入口,开始统一 `BeginFrame / viewport render / UI present / fallback / resize interop` 这条链。
4. 下一步进入阶段 2 主体:继续拆 `NativeRenderer` 中的窗口互操作 / present 组合路径,并把 viewport render target 生命周期从 `ProductViewportHostService` 中收出去。

View File

@@ -127,6 +127,7 @@ add_library(XCUIEditorHost STATIC
app/Host/D3D12WindowRenderer.cpp
app/Host/D3D12WindowRenderLoop.cpp
app/Host/NativeRenderer.cpp
app/Host/WindowMessageDispatcher.cpp
)
target_include_directories(XCUIEditorHost

View File

@@ -1,5 +1,7 @@
#include "Application.h"
#include <Host/WindowMessageDispatcher.h>
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
#include <XCEditor/Foundation/UIEditorTheme.h>
@@ -36,7 +38,6 @@ constexpr const wchar_t* kWindowClassName = L"XCEditorShellHost";
constexpr const wchar_t* kWindowTitle = L"Main Scene * - Main.xx - XCEngine Editor";
constexpr UINT kDefaultDpi = 96u;
constexpr float kBaseDpiScale = 96.0f;
constexpr UINT kDeferredRenderMessage = WM_APP + 1u;
bool ResolveVerboseRuntimeTraceEnabled() {
wchar_t buffer[8] = {};
@@ -47,10 +48,6 @@ bool ResolveVerboseRuntimeTraceEnabled() {
return length > 0u && buffer[0] != L'0';
}
Application* GetApplicationFromWindow(HWND hwnd) {
return reinterpret_cast<Application*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
}
UINT QuerySystemDpi() {
HDC screenDc = GetDC(nullptr);
if (screenDc == nullptr) {
@@ -131,25 +128,6 @@ void EnableDpiAwareness() {
}
}
void TryEnableNonClientDpiScaling(HWND hwnd) {
if (hwnd == nullptr) {
return;
}
const HMODULE user32 = GetModuleHandleW(L"user32.dll");
if (user32 == nullptr) {
return;
}
using EnableNonClientDpiScalingFn = BOOL(WINAPI*)(HWND);
const auto enableNonClientDpiScaling =
reinterpret_cast<EnableNonClientDpiScalingFn>(
GetProcAddress(user32, "EnableNonClientDpiScaling"));
if (enableNonClientDpiScaling != nullptr) {
enableNonClientDpiScaling(hwnd);
}
}
std::string TruncateText(const std::string& text, std::size_t maxLength) {
if (text.size() <= maxLength) {
return text;
@@ -456,9 +434,9 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) {
LogRuntimeTrace("app", "window creation failed");
return false;
}
m_windowDpi = QueryWindowDpi(m_hwnd);
m_dpiScale = GetDpiScale();
m_renderer.SetDpiScale(m_dpiScale);
m_hostRuntime.Reset();
m_hostRuntime.SetWindowDpi(QueryWindowDpi(m_hwnd));
m_renderer.SetDpiScale(GetDpiScale());
m_editorContext.SetExitRequestHandler([this]() {
if (m_hwnd != nullptr) {
PostMessageW(m_hwnd, WM_CLOSE, 0, 0);
@@ -466,7 +444,7 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) {
});
std::ostringstream dpiTrace = {};
dpiTrace << "initial dpi=" << m_windowDpi << " scale=" << m_dpiScale;
dpiTrace << "initial dpi=" << m_hostRuntime.GetWindowDpi() << " scale=" << GetDpiScale();
LogRuntimeTrace("window", dpiTrace.str());
if (!m_renderer.Initialize(m_hwnd)) {
@@ -481,17 +459,18 @@ 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) {
const Host::D3D12WindowRenderLoopAttachResult attachResult =
m_windowRenderLoop.Attach(m_renderer, m_windowRenderer);
if (!attachResult.interopWarning.empty()) {
LogRuntimeTrace(
"app",
"native renderer d3d12 interop unavailable; falling back to hwnd renderer: " +
m_renderer.GetLastRenderError());
attachResult.interopWarning);
}
m_editorContext.AttachTextMeasurer(m_renderer);
m_editorWorkspace.Initialize(repoRoot, m_renderer);
m_editorWorkspace.AttachViewportWindowRenderer(m_windowRenderer);
m_editorWorkspace.SetViewportSurfacePresentationEnabled(hasD3D12WindowInterop);
m_editorWorkspace.SetViewportSurfacePresentationEnabled(
attachResult.hasViewportSurfacePresentation);
if (!m_editorWorkspace.GetBuiltInIconError().empty()) {
LogRuntimeTrace("icons", m_editorWorkspace.GetBuiltInIconError());
}
@@ -520,6 +499,7 @@ void Application::Shutdown() {
m_autoScreenshot.Shutdown();
m_editorWorkspace.Shutdown();
m_windowRenderLoop.Detach();
m_windowRenderer.Shutdown();
m_renderer.Shutdown();
@@ -570,11 +550,10 @@ void Application::RenderFrame() {
m_editorWorkspace.GetShellInteractionState()));
}
const bool canUseWindowRenderer = m_renderer.HasAttachedWindowRenderer();
const bool d3d12FrameBegun =
canUseWindowRenderer && m_windowRenderer.BeginFrame();
if (canUseWindowRenderer && !d3d12FrameBegun) {
LogRuntimeTrace("viewport", "d3d12 frame begin failed");
const Host::D3D12WindowRenderLoopFrameContext frameContext =
m_windowRenderLoop.BeginFrame();
if (!frameContext.warning.empty()) {
LogRuntimeTrace("viewport", frameContext.warning);
}
m_editorWorkspace.Update(
@@ -610,8 +589,8 @@ void Application::RenderFrame() {
ApplyHostedContentCaptureRequests();
ApplyCurrentCursor();
m_editorWorkspace.Append(drawList);
if (d3d12FrameBegun) {
m_editorWorkspace.RenderRequestedViewports(m_windowRenderer.GetRenderContext());
if (frameContext.canRenderViewports) {
m_editorWorkspace.RenderRequestedViewports(frameContext.renderContext);
}
} else {
drawList.AddText(
@@ -628,31 +607,37 @@ void Application::RenderFrame() {
12.0f);
}
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);
const Host::D3D12WindowRenderLoopPresentResult presentResult =
m_windowRenderLoop.Present(drawData);
if (!presentResult.warning.empty()) {
LogRuntimeTrace("present", presentResult.warning);
}
m_autoScreenshot.CaptureIfRequested(
m_renderer,
drawData,
pixelWidth,
pixelHeight,
framePresented);
presentResult.framePresented);
}
void Application::OnDeferredRenderMessage() {
m_hostRuntime.ClearDeferredRenderRequest();
RenderFrame();
}
void Application::OnPaintMessage() {
if (m_hwnd == nullptr) {
return;
}
PAINTSTRUCT paintStruct = {};
BeginPaint(m_hwnd, &paintStruct);
RenderFrame();
EndPaint(m_hwnd, &paintStruct);
}
float Application::GetDpiScale() const {
const UINT dpi = m_windowDpi == 0u ? kDefaultDpi : m_windowDpi;
return static_cast<float>(dpi) / kBaseDpiScale;
return m_hostRuntime.GetDpiScale(kBaseDpiScale);
}
float Application::PixelsToDips(float pixels) const {
@@ -767,22 +752,16 @@ void Application::OnResize() {
}
void Application::OnEnterSizeMove() {
m_inInteractiveResize = true;
m_hostRuntime.BeginInteractiveResize();
}
void Application::OnExitSizeMove() {
m_inInteractiveResize = false;
m_hostRuntime.EndInteractiveResize();
QueueCurrentClientResize();
}
void Application::QueueWindowResize(UINT width, UINT height) {
if (width == 0u || height == 0u) {
return;
}
m_pendingWindowResizeWidth = width;
m_pendingWindowResizeHeight = height;
m_hasPendingWindowResize = true;
m_hostRuntime.QueueWindowResize(width, height);
}
void Application::QueueCurrentClientResize() {
@@ -796,51 +775,26 @@ void Application::QueueCurrentClientResize() {
}
bool Application::ApplyPendingWindowResize() {
if (!m_hasPendingWindowResize) {
UINT width = 0u;
UINT height = 0u;
if (!m_hostRuntime.ConsumePendingWindowResize(width, height)) {
return true;
}
const UINT width = m_pendingWindowResizeWidth;
const UINT height = m_pendingWindowResizeHeight;
m_hasPendingWindowResize = false;
if (width == 0u || height == 0u) {
return false;
const Host::D3D12WindowRenderLoopResizeResult resizeResult =
m_windowRenderLoop.ApplyResize(width, height);
m_editorWorkspace.SetViewportSurfacePresentationEnabled(
resizeResult.hasViewportSurfacePresentation);
if (!resizeResult.windowRendererWarning.empty()) {
LogRuntimeTrace("present", resizeResult.windowRendererWarning);
}
m_renderer.Resize(width, height);
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 (!resizeResult.interopWarning.empty()) {
LogRuntimeTrace("present", resizeResult.interopWarning);
}
if (!hasD3D12WindowInterop) {
LogRuntimeTrace(
"present",
"failed to rebuild d3d12 window interop after resize: " +
m_renderer.GetLastRenderError());
}
return hasHealthyD3D12WindowInterop;
}
void Application::RequestDeferredRenderFrame() {
if (m_hwnd == nullptr || !IsWindow(m_hwnd) || m_renderFrameQueued) {
return;
}
m_renderFrameQueued = true;
PostMessageW(m_hwnd, kDeferredRenderMessage, 0, 0);
return resizeResult.hasViewportSurfacePresentation;
}
bool Application::QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) const {
@@ -867,9 +821,8 @@ bool Application::QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) c
}
void Application::OnDpiChanged(UINT dpi, const RECT& suggestedRect) {
m_windowDpi = dpi == 0u ? kDefaultDpi : dpi;
m_dpiScale = GetDpiScale();
m_renderer.SetDpiScale(m_dpiScale);
m_hostRuntime.SetWindowDpi(dpi == 0u ? kDefaultDpi : dpi);
m_renderer.SetDpiScale(GetDpiScale());
if (m_hwnd != nullptr) {
const LONG windowWidth = suggestedRect.right - suggestedRect.left;
const LONG windowHeight = suggestedRect.bottom - suggestedRect.top;
@@ -882,11 +835,10 @@ void Application::OnDpiChanged(UINT dpi, const RECT& suggestedRect) {
windowHeight,
SWP_NOZORDER | SWP_NOACTIVATE);
QueueCurrentClientResize();
RequestDeferredRenderFrame();
}
std::ostringstream trace = {};
trace << "dpi changed to " << m_windowDpi << " scale=" << m_dpiScale;
trace << "dpi changed to " << m_hostRuntime.GetWindowDpi() << " scale=" << GetDpiScale();
LogRuntimeTrace("window", trace.str());
}
@@ -1006,66 +958,27 @@ LONG WINAPI Application::HandleUnhandledException(EXCEPTION_POINTERS* exceptionI
}
LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
if (message == WM_NCCREATE) {
TryEnableNonClientDpiScaling(hwnd);
const auto* createStruct = reinterpret_cast<CREATESTRUCTW*>(lParam);
auto* application = reinterpret_cast<Application*>(createStruct->lpCreateParams);
SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(application));
return TRUE;
LRESULT dispatcherResult = 0;
if (Host::WindowMessageDispatcher::TryHandleNonClientCreate(
hwnd,
message,
lParam,
dispatcherResult)) {
return dispatcherResult;
}
Application* application = Host::WindowMessageDispatcher::GetApplicationFromWindow(hwnd);
if (application != nullptr &&
Host::WindowMessageDispatcher::TryDispatch(
*application,
message,
wParam,
lParam,
dispatcherResult)) {
return dispatcherResult;
}
Application* application = GetApplicationFromWindow(hwnd);
switch (message) {
case WM_SETCURSOR:
if (application != nullptr &&
LOWORD(lParam) == HTCLIENT &&
application->ApplyCurrentCursor()) {
return TRUE;
}
break;
case WM_DPICHANGED:
if (application != nullptr && lParam != 0) {
application->OnDpiChanged(
static_cast<UINT>(LOWORD(wParam)),
*reinterpret_cast<RECT*>(lParam));
return 0;
}
break;
case WM_ENTERSIZEMOVE:
if (application != nullptr) {
application->OnEnterSizeMove();
return 0;
}
break;
case WM_EXITSIZEMOVE:
if (application != nullptr) {
application->OnExitSizeMove();
application->RequestDeferredRenderFrame();
return 0;
}
break;
case WM_SIZE:
if (application != nullptr && wParam != SIZE_MINIMIZED) {
application->OnResize();
application->RequestDeferredRenderFrame();
}
return 0;
case kDeferredRenderMessage:
if (application != nullptr) {
application->m_renderFrameQueued = false;
application->RenderFrame();
return 0;
}
break;
case WM_PAINT:
if (application != nullptr) {
PAINTSTRUCT paintStruct = {};
BeginPaint(hwnd, &paintStruct);
application->RenderFrame();
EndPaint(hwnd, &paintStruct);
return 0;
}
break;
case WM_MOUSEMOVE:
if (application != nullptr) {
if (!application->m_trackingMouseLeave) {

View File

@@ -6,6 +6,8 @@
#include <Host/AutoScreenshot.h>
#include <Host/D3D12WindowRenderer.h>
#include <Host/D3D12WindowRenderLoop.h>
#include <Host/HostRuntimeState.h>
#include <Host/InputModifierTracker.h>
#include <Host/NativeRenderer.h>
@@ -23,6 +25,10 @@
#include <string_view>
#include <vector>
namespace XCEngine::UI::Editor::Host {
class WindowMessageDispatcher;
}
namespace XCEngine::UI::Editor {
class Application {
@@ -32,11 +38,15 @@ public:
int Run(HINSTANCE hInstance, int nCmdShow);
private:
friend class ::XCEngine::UI::Editor::Host::WindowMessageDispatcher;
static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
bool Initialize(HINSTANCE hInstance, int nCmdShow);
void Shutdown();
void RenderFrame();
void OnDeferredRenderMessage();
void OnPaintMessage();
void OnResize();
void OnEnterSizeMove();
void OnExitSizeMove();
@@ -44,7 +54,6 @@ private:
void QueueWindowResize(UINT width, UINT height);
void QueueCurrentClientResize();
bool ApplyPendingWindowResize();
void RequestDeferredRenderFrame();
bool QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) const;
bool IsPointerInsideClientArea() const;
bool ApplyCurrentCursor() const;
@@ -78,19 +87,14 @@ private:
ATOM m_windowClassAtom = 0;
::XCEngine::UI::Editor::Host::NativeRenderer m_renderer = {};
::XCEngine::UI::Editor::Host::D3D12WindowRenderer m_windowRenderer = {};
::XCEngine::UI::Editor::Host::D3D12WindowRenderLoop m_windowRenderLoop = {};
::XCEngine::UI::Editor::Host::AutoScreenshotController m_autoScreenshot = {};
::XCEngine::UI::Editor::Host::InputModifierTracker m_inputModifierTracker = {};
App::ProductEditorContext m_editorContext = {};
App::ProductEditorWorkspace m_editorWorkspace = {};
std::vector<::XCEngine::UI::UIInputEvent> m_pendingInputEvents = {};
bool m_trackingMouseLeave = false;
UINT m_windowDpi = 96u;
float m_dpiScale = 1.0f;
bool m_inInteractiveResize = false;
bool m_renderFrameQueued = false;
bool m_hasPendingWindowResize = false;
UINT m_pendingWindowResizeWidth = 0u;
UINT m_pendingWindowResizeHeight = 0u;
::XCEngine::UI::Editor::Host::HostRuntimeState m_hostRuntime = {};
};
int RunXCUIEditorApp(HINSTANCE hInstance, int nCmdShow);

View File

@@ -2,57 +2,115 @@
namespace XCEngine::UI::Editor::Host {
bool RenderD3D12WindowFrame(
D3D12WindowRenderer& windowRenderer,
const float clearColor[4],
const D3D12WindowRenderCallback& beforePresent,
const D3D12WindowRenderCallback& afterPresent) {
const ::XCEngine::Rendering::RenderSurface* renderSurface =
windowRenderer.GetCurrentRenderSurface();
::XCEngine::Rendering::RenderContext renderContext =
windowRenderer.GetRenderContext();
if (!renderContext.IsValid() ||
renderContext.commandList == nullptr ||
renderContext.commandQueue == nullptr ||
windowRenderer.GetSwapChain() == nullptr ||
renderSurface == nullptr) {
return false;
D3D12WindowRenderLoopAttachResult D3D12WindowRenderLoop::Attach(
NativeRenderer& uiRenderer,
D3D12WindowRenderer& windowRenderer) {
m_uiRenderer = &uiRenderer;
m_windowRenderer = &windowRenderer;
D3D12WindowRenderLoopAttachResult result = {};
result.hasViewportSurfacePresentation = m_uiRenderer->AttachWindowRenderer(*m_windowRenderer);
if (!result.hasViewportSurfacePresentation) {
const std::string& interopError = m_uiRenderer->GetLastRenderError();
result.interopWarning = interopError.empty()
? "native renderer d3d12 interop unavailable; falling back to hwnd renderer."
: "native renderer d3d12 interop unavailable; falling back to hwnd renderer: " +
interopError;
}
return result;
}
void D3D12WindowRenderLoop::Detach() {
if (m_uiRenderer != nullptr) {
m_uiRenderer->DetachWindowRenderer();
}
auto* d3d12CommandList =
static_cast<::XCEngine::RHI::D3D12CommandList*>(renderContext.commandList);
if (d3d12CommandList == nullptr) {
return false;
m_uiRenderer = nullptr;
m_windowRenderer = nullptr;
}
D3D12WindowRenderLoopFrameContext D3D12WindowRenderLoop::BeginFrame() const {
D3D12WindowRenderLoopFrameContext context = {};
if (!HasViewportSurfacePresentation()) {
return context;
}
const auto& colorAttachments = renderSurface->GetColorAttachments();
if (colorAttachments.empty() || colorAttachments[0] == nullptr) {
return false;
if (!m_windowRenderer->BeginFrame()) {
const std::string& frameError = m_windowRenderer->GetLastError();
context.warning = frameError.empty()
? "d3d12 frame begin failed"
: "d3d12 frame begin failed: " + frameError;
return context;
}
::XCEngine::RHI::RHIResourceView* renderTargetView = colorAttachments[0];
renderContext.commandList->TransitionBarrier(
renderTargetView,
::XCEngine::RHI::ResourceStates::Present,
::XCEngine::RHI::ResourceStates::RenderTarget);
renderContext.commandList->SetRenderTargets(1, &renderTargetView, nullptr);
renderContext.commandList->ClearRenderTarget(renderTargetView, clearColor);
context.canRenderViewports = true;
context.renderContext = m_windowRenderer->GetRenderContext();
return context;
}
if (beforePresent) {
beforePresent(renderContext, *renderSurface);
renderContext.commandList->SetRenderTargets(1, &renderTargetView, nullptr);
D3D12WindowRenderLoopResizeResult D3D12WindowRenderLoop::ApplyResize(UINT width, UINT height) {
D3D12WindowRenderLoopResizeResult result = {};
if (m_uiRenderer == nullptr || m_windowRenderer == nullptr) {
result.interopWarning = "window render loop is detached.";
return result;
}
if (afterPresent) {
afterPresent(renderContext, *renderSurface);
renderContext.commandList->SetRenderTargets(1, &renderTargetView, nullptr);
m_uiRenderer->Resize(width, height);
m_uiRenderer->DetachWindowRenderer();
const bool resizedWindowRenderer =
m_windowRenderer->Resize(static_cast<int>(width), static_cast<int>(height));
if (!resizedWindowRenderer || !m_windowRenderer->GetLastError().empty()) {
const std::string& resizeError = m_windowRenderer->GetLastError();
result.windowRendererWarning = resizeError.empty()
? "window renderer resize warning."
: "window renderer resize warning: " + resizeError;
}
renderContext.commandList->TransitionBarrier(
renderTargetView,
::XCEngine::RHI::ResourceStates::RenderTarget,
::XCEngine::RHI::ResourceStates::Present);
return windowRenderer.SubmitFrame(true);
if (!resizedWindowRenderer) {
return result;
}
const D3D12WindowRenderLoopAttachResult attachResult =
Attach(*m_uiRenderer, *m_windowRenderer);
result.hasViewportSurfacePresentation = attachResult.hasViewportSurfacePresentation;
result.interopWarning = attachResult.interopWarning;
return result;
}
D3D12WindowRenderLoopPresentResult D3D12WindowRenderLoop::Present(
const ::XCEngine::UI::UIDrawData& drawData) const {
D3D12WindowRenderLoopPresentResult result = {};
if (m_uiRenderer == nullptr) {
result.warning = "window render loop has no ui renderer.";
return result;
}
if (HasViewportSurfacePresentation()) {
result.framePresented = m_uiRenderer->RenderToWindowRenderer(drawData);
if (!result.framePresented) {
const std::string& composeError = m_uiRenderer->GetLastRenderError();
result.warning = composeError.empty()
? "d3d12 window composition failed, falling back to hwnd renderer."
: "d3d12 window composition failed, falling back to hwnd renderer: " +
composeError;
}
}
if (!result.framePresented) {
result.framePresented = m_uiRenderer->Render(drawData);
if (!result.framePresented && result.warning.empty()) {
result.warning = m_uiRenderer->GetLastRenderError();
}
}
return result;
}
bool D3D12WindowRenderLoop::HasViewportSurfacePresentation() const {
return m_uiRenderer != nullptr &&
m_windowRenderer != nullptr &&
m_uiRenderer->HasAttachedWindowRenderer();
}
} // namespace XCEngine::UI::Editor::Host

View File

@@ -1,20 +1,53 @@
#pragma once
#include "D3D12WindowRenderer.h"
#include "NativeRenderer.h"
#include <functional>
#include <XCEngine/UI/DrawData.h>
#include <string>
namespace XCEngine::UI::Editor::Host {
using D3D12WindowRenderCallback =
std::function<void(
const ::XCEngine::Rendering::RenderContext&,
const ::XCEngine::Rendering::RenderSurface&)>;
struct D3D12WindowRenderLoopAttachResult {
bool hasViewportSurfacePresentation = false;
std::string interopWarning = {};
};
bool RenderD3D12WindowFrame(
D3D12WindowRenderer& windowRenderer,
const float clearColor[4],
const D3D12WindowRenderCallback& beforePresent = {},
const D3D12WindowRenderCallback& afterPresent = {});
struct D3D12WindowRenderLoopFrameContext {
bool canRenderViewports = false;
::XCEngine::Rendering::RenderContext renderContext = {};
std::string warning = {};
};
struct D3D12WindowRenderLoopResizeResult {
bool hasViewportSurfacePresentation = false;
std::string windowRendererWarning = {};
std::string interopWarning = {};
};
struct D3D12WindowRenderLoopPresentResult {
bool framePresented = false;
std::string warning = {};
};
class D3D12WindowRenderLoop {
public:
D3D12WindowRenderLoopAttachResult Attach(
NativeRenderer& uiRenderer,
D3D12WindowRenderer& windowRenderer);
void Detach();
D3D12WindowRenderLoopFrameContext BeginFrame() const;
D3D12WindowRenderLoopResizeResult ApplyResize(UINT width, UINT height);
D3D12WindowRenderLoopPresentResult Present(
const ::XCEngine::UI::UIDrawData& drawData) const;
bool HasViewportSurfacePresentation() const;
private:
NativeRenderer* m_uiRenderer = nullptr;
D3D12WindowRenderer* m_windowRenderer = nullptr;
};
} // namespace XCEngine::UI::Editor::Host

View File

@@ -0,0 +1,89 @@
#pragma once
#include <windows.h>
namespace XCEngine::UI::Editor::Host {
class HostRuntimeState {
public:
void Reset() {
m_windowDpi = 96u;
m_inInteractiveResize = false;
m_renderFrameQueued = false;
m_hasPendingWindowResize = false;
m_pendingWindowResizeWidth = 0u;
m_pendingWindowResizeHeight = 0u;
}
void SetWindowDpi(UINT dpi) {
m_windowDpi = dpi == 0u ? 96u : dpi;
}
UINT GetWindowDpi() const {
return m_windowDpi;
}
float GetDpiScale(float baseDpiScale) const {
return baseDpiScale > 0.0f
? static_cast<float>(m_windowDpi) / baseDpiScale
: 1.0f;
}
void BeginInteractiveResize() {
m_inInteractiveResize = true;
}
void EndInteractiveResize() {
m_inInteractiveResize = false;
}
bool IsInteractiveResize() const {
return m_inInteractiveResize;
}
void QueueWindowResize(UINT width, UINT height) {
if (width == 0u || height == 0u) {
return;
}
m_pendingWindowResizeWidth = width;
m_pendingWindowResizeHeight = height;
m_hasPendingWindowResize = true;
}
bool ConsumePendingWindowResize(UINT& outWidth, UINT& outHeight) {
outWidth = 0u;
outHeight = 0u;
if (!m_hasPendingWindowResize) {
return false;
}
m_hasPendingWindowResize = false;
outWidth = m_pendingWindowResizeWidth;
outHeight = m_pendingWindowResizeHeight;
return outWidth > 0u && outHeight > 0u;
}
bool TryQueueDeferredRender() {
if (m_renderFrameQueued) {
return false;
}
m_renderFrameQueued = true;
return true;
}
void ClearDeferredRenderRequest() {
m_renderFrameQueued = false;
}
private:
UINT m_windowDpi = 96u;
bool m_inInteractiveResize = false;
bool m_renderFrameQueued = false;
bool m_hasPendingWindowResize = false;
UINT m_pendingWindowResizeWidth = 0u;
UINT m_pendingWindowResizeHeight = 0u;
};
} // namespace XCEngine::UI::Editor::Host

View File

@@ -0,0 +1,115 @@
#include "WindowMessageDispatcher.h"
#include "../Application.h"
namespace XCEngine::UI::Editor::Host {
namespace {
constexpr UINT kDeferredRenderMessage = WM_APP + 1u;
void TryEnableNonClientDpiScaling(HWND hwnd) {
if (hwnd == nullptr) {
return;
}
const HMODULE user32 = GetModuleHandleW(L"user32.dll");
if (user32 == nullptr) {
return;
}
using EnableNonClientDpiScalingFn = BOOL(WINAPI*)(HWND);
const auto enableNonClientDpiScaling =
reinterpret_cast<EnableNonClientDpiScalingFn>(
GetProcAddress(user32, "EnableNonClientDpiScaling"));
if (enableNonClientDpiScaling != nullptr) {
enableNonClientDpiScaling(hwnd);
}
}
} // namespace
Application* WindowMessageDispatcher::GetApplicationFromWindow(HWND hwnd) {
return reinterpret_cast<Application*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
}
bool WindowMessageDispatcher::TryHandleNonClientCreate(
HWND hwnd,
UINT message,
LPARAM lParam,
LRESULT& outResult) {
if (message != WM_NCCREATE) {
return false;
}
TryEnableNonClientDpiScaling(hwnd);
const auto* createStruct = reinterpret_cast<CREATESTRUCTW*>(lParam);
auto* application = reinterpret_cast<Application*>(createStruct->lpCreateParams);
SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(application));
outResult = TRUE;
return true;
}
bool WindowMessageDispatcher::TryDispatch(
Application& application,
UINT message,
WPARAM wParam,
LPARAM lParam,
LRESULT& outResult) {
switch (message) {
case WM_SETCURSOR:
if (LOWORD(lParam) == HTCLIENT && application.ApplyCurrentCursor()) {
outResult = TRUE;
return true;
}
return false;
case WM_DPICHANGED:
if (lParam == 0) {
return false;
}
application.OnDpiChanged(
static_cast<UINT>(LOWORD(wParam)),
*reinterpret_cast<const RECT*>(lParam));
RequestDeferredRenderFrame(application);
outResult = 0;
return true;
case WM_ENTERSIZEMOVE:
application.OnEnterSizeMove();
outResult = 0;
return true;
case WM_EXITSIZEMOVE:
application.OnExitSizeMove();
RequestDeferredRenderFrame(application);
outResult = 0;
return true;
case WM_SIZE:
if (wParam != SIZE_MINIMIZED) {
application.OnResize();
RequestDeferredRenderFrame(application);
}
outResult = 0;
return true;
case kDeferredRenderMessage:
application.OnDeferredRenderMessage();
outResult = 0;
return true;
case WM_PAINT:
application.OnPaintMessage();
outResult = 0;
return true;
default:
return false;
}
}
void WindowMessageDispatcher::RequestDeferredRenderFrame(Application& application) {
if (application.m_hwnd == nullptr ||
!IsWindow(application.m_hwnd) ||
!application.m_hostRuntime.TryQueueDeferredRender()) {
return;
}
PostMessageW(application.m_hwnd, kDeferredRenderMessage, 0, 0);
}
} // namespace XCEngine::UI::Editor::Host

View File

@@ -0,0 +1,34 @@
#pragma once
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
namespace XCEngine::UI::Editor {
class Application;
}
namespace XCEngine::UI::Editor::Host {
class WindowMessageDispatcher {
public:
static Application* GetApplicationFromWindow(HWND hwnd);
static bool TryHandleNonClientCreate(
HWND hwnd,
UINT message,
LPARAM lParam,
LRESULT& outResult);
static bool TryDispatch(
Application& application,
UINT message,
WPARAM wParam,
LPARAM lParam,
LRESULT& outResult);
private:
static void RequestDeferredRenderFrame(Application& application);
};
} // namespace XCEngine::UI::Editor::Host