From f3fc34898aa39f7defe679d31566174c3dc2b403 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Mon, 13 Apr 2026 19:37:10 +0800 Subject: [PATCH] Refactor new editor host orchestration --- .../plan/NewEditor_宿主重构计划_2026-04-13.md | 178 +++++++++++++ new_editor/CMakeLists.txt | 1 + new_editor/app/Application.cpp | 243 ++++++------------ new_editor/app/Application.h | 20 +- new_editor/app/Host/D3D12WindowRenderLoop.cpp | 138 +++++++--- new_editor/app/Host/D3D12WindowRenderLoop.h | 53 +++- new_editor/app/Host/HostRuntimeState.h | 89 +++++++ .../app/Host/WindowMessageDispatcher.cpp | 115 +++++++++ new_editor/app/Host/WindowMessageDispatcher.h | 34 +++ 9 files changed, 648 insertions(+), 223 deletions(-) create mode 100644 docs/plan/NewEditor_宿主重构计划_2026-04-13.md create mode 100644 new_editor/app/Host/HostRuntimeState.h create mode 100644 new_editor/app/Host/WindowMessageDispatcher.cpp create mode 100644 new_editor/app/Host/WindowMessageDispatcher.h diff --git a/docs/plan/NewEditor_宿主重构计划_2026-04-13.md b/docs/plan/NewEditor_宿主重构计划_2026-04-13.md new file mode 100644 index 00000000..66ce9fc3 --- /dev/null +++ b/docs/plan/NewEditor_宿主重构计划_2026-04-13.md @@ -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 手感继续改善。 + +## 阶段 6:viewport 生命周期收口 + +### 目标 + +让 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` 中收出去。 diff --git a/new_editor/CMakeLists.txt b/new_editor/CMakeLists.txt index 8414f187..8deccc67 100644 --- a/new_editor/CMakeLists.txt +++ b/new_editor/CMakeLists.txt @@ -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 diff --git a/new_editor/app/Application.cpp b/new_editor/app/Application.cpp index aac5f746..eccbe0be 100644 --- a/new_editor/app/Application.cpp +++ b/new_editor/app/Application.cpp @@ -1,5 +1,7 @@ #include "Application.h" +#include + #include #include @@ -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(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( - 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(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(width), static_cast(height)); - const bool hasD3D12WindowInterop = resizedWindowRenderer && - m_renderer.AttachWindowRenderer(m_windowRenderer); - const bool hasHealthyD3D12WindowInterop = - resizedWindowRenderer && - hasD3D12WindowInterop; - m_editorWorkspace.SetViewportSurfacePresentationEnabled(hasHealthyD3D12WindowInterop); - - if (!resizedWindowRenderer || !m_windowRenderer.GetLastError().empty()) { - LogRuntimeTrace( - "present", - "window renderer resize warning: " + m_windowRenderer.GetLastError()); + if (!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(lParam); - auto* application = reinterpret_cast(createStruct->lpCreateParams); - SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast(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(LOWORD(wParam)), - *reinterpret_cast(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) { diff --git a/new_editor/app/Application.h b/new_editor/app/Application.h index c5183079..bfe44319 100644 --- a/new_editor/app/Application.h +++ b/new_editor/app/Application.h @@ -6,6 +6,8 @@ #include #include +#include +#include #include #include @@ -23,6 +25,10 @@ #include #include +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); diff --git a/new_editor/app/Host/D3D12WindowRenderLoop.cpp b/new_editor/app/Host/D3D12WindowRenderLoop.cpp index eadd1c62..6b23dc4a 100644 --- a/new_editor/app/Host/D3D12WindowRenderLoop.cpp +++ b/new_editor/app/Host/D3D12WindowRenderLoop.cpp @@ -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(width), static_cast(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 diff --git a/new_editor/app/Host/D3D12WindowRenderLoop.h b/new_editor/app/Host/D3D12WindowRenderLoop.h index 7b8b204c..944cbd77 100644 --- a/new_editor/app/Host/D3D12WindowRenderLoop.h +++ b/new_editor/app/Host/D3D12WindowRenderLoop.h @@ -1,20 +1,53 @@ #pragma once #include "D3D12WindowRenderer.h" +#include "NativeRenderer.h" -#include +#include + +#include namespace XCEngine::UI::Editor::Host { -using D3D12WindowRenderCallback = - std::function; +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 diff --git a/new_editor/app/Host/HostRuntimeState.h b/new_editor/app/Host/HostRuntimeState.h new file mode 100644 index 00000000..25554f9b --- /dev/null +++ b/new_editor/app/Host/HostRuntimeState.h @@ -0,0 +1,89 @@ +#pragma once + +#include + +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(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 diff --git a/new_editor/app/Host/WindowMessageDispatcher.cpp b/new_editor/app/Host/WindowMessageDispatcher.cpp new file mode 100644 index 00000000..da6ec629 --- /dev/null +++ b/new_editor/app/Host/WindowMessageDispatcher.cpp @@ -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( + GetProcAddress(user32, "EnableNonClientDpiScaling")); + if (enableNonClientDpiScaling != nullptr) { + enableNonClientDpiScaling(hwnd); + } +} + +} // namespace + +Application* WindowMessageDispatcher::GetApplicationFromWindow(HWND hwnd) { + return reinterpret_cast(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(lParam); + auto* application = reinterpret_cast(createStruct->lpCreateParams); + SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast(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(LOWORD(wParam)), + *reinterpret_cast(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 diff --git a/new_editor/app/Host/WindowMessageDispatcher.h b/new_editor/app/Host/WindowMessageDispatcher.h new file mode 100644 index 00000000..1f48c1b7 --- /dev/null +++ b/new_editor/app/Host/WindowMessageDispatcher.h @@ -0,0 +1,34 @@ +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include + +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