diff --git a/editor/AGENTS.md b/editor/AGENTS.md index 34dcefb5..249e080a 100644 --- a/editor/AGENTS.md +++ b/editor/AGENTS.md @@ -17,7 +17,9 @@ The important production targets are: `XCEditor/Windowing`. - `XCUIEditorAppWindowing`: app-internal static library for the current host contracts, content controllers, coordinators, frame transfer flow, - runtime controllers, and window manager. + backend-neutral runtime controllers, and window manager. It consumes render + services through `editor/app/Rendering/Host` interfaces; it must not include + concrete D3D12 or Win32 rendering implementation headers. - `XCUIEditorApp`: concrete editor executable. Its output name is `XCEngine`. Do not invent target boundaries that are not present in `editor/CMakeLists.txt`. @@ -27,7 +29,9 @@ are not current production library boundaries in this checkout. There is now a narrow public `editor/include/XCEditor/Windowing` layer for the generic window authority model, synchronization plan/planner, and presentation projection. App-specific window orchestration remains under -`editor/app/Windowing`, and the concrete Win32 host remains under +`editor/app/Windowing`, render host contracts remain under +`editor/app/Rendering/Host`, the concrete D3D12 renderer remains under +`editor/app/Rendering/D3D12`, and the concrete Win32 host remains under `editor/app/Platform/Win32`. ## Layering @@ -45,18 +49,22 @@ Use these ownership boundaries when changing code: `State`, `System`, `UtilityWindows`: editor product semantics. - `editor/app/Windowing/Content`, `Coordinator`, `Frame`, `Host`, `Runtime`: app window orchestration, content ownership, frame driving, frame transfer, - and per-window runtime state. + and per-window runtime state. Keep this layer backend-neutral; it may depend + on `editor/app/Rendering/Host` contracts, but not concrete render backends. - `editor/app/Platform/Win32`: native window, message dispatch, input, lifecycle, chrome, HWND ownership, and native host adapter behavior. -- `editor/app/Rendering`: editor rendering host and D3D12 integration. +- `editor/app/Rendering/Host`: app rendering contracts consumed by app + windowing and app content. +- `editor/app/Rendering/D3D12`: concrete D3D12 window renderer, UI renderer, + texture host, text system, render loop, and render-runtime adapter. The semantic dependency direction should remain: ```text XCEditor framework <- editor app semantics - <- app windowing runtime - <- Win32 host / rendering host + <- app windowing runtime / rendering host contracts + <- Win32 host / concrete rendering adapters ``` ## Startup Flow @@ -77,7 +85,8 @@ EditorContext::BuildWorkspaceController() -> EditorWindowSystem::BootstrapPrimaryWindow(...) -> EditorWindowManager::CreateWorkspaceWindow(...) -> EditorWindowContentFactory::CreateWorkspaceContentController(...) - -> EditorWindowRuntimeController(EditorContext, contentController) + -> EditorWindowHostRuntime::CreateWindowRenderRuntime() + -> EditorWindowRuntimeController(EditorContext, contentController, renderRuntime) -> EditorWindowHostRuntime::CreateHostWindow(runtimeController, ...) ``` @@ -93,14 +102,23 @@ must not own the editor frame loop or fetch `EditorContext`. `EditorWindowRuntimeController` lives under `editor/app/Windowing/Runtime`. App windowing creates it from `EditorContext` plus a workspace or utility -content controller, then passes it to the native host. The Win32 host stores and -calls the runtime controller, but it does not create content controllers and it -does not receive `EditorContext`. +content controller plus a backend-neutral `EditorWindowRenderRuntime`, then +passes it to the native host. The Win32 host stores and calls the runtime +controller, but it does not create content controllers and it does not receive +`EditorContext`. + +Concrete renderer ownership lives below the app windowing boundary. The Win32 +host supplies the current D3D12 implementation through +`EditorWindowHost::CreateWindowRenderRuntime()`. `EditorWindowRuntimeController` +may call `Rendering::Host::EditorWindowRenderRuntime`, `UiTextureHost`, and +`ViewportRenderHost`, but it must not include `Rendering/D3D12` headers, +`windows.h`, or HWND types. The host contract direction is: ```text EditorWindowManager / coordinators + -> ask host for EditorWindowRenderRuntime -> create EditorWindowRuntimeController -> EditorWindowHostRuntime::CreateHostWindow(runtimeController, ...) -> Win32 EditorWindow owns HWND and delegates frame work to the runtime @@ -196,13 +214,21 @@ logic. - Use `editor/app/Windowing` for app-specific window runtime semantics, content, frame transfer, host contracts, runtime controllers, and coordinators. +- Use `editor/app/Rendering/Host` for renderer-facing contracts that app + windowing or app content may consume. +- Use `editor/app/Rendering/D3D12` for concrete D3D12 renderer ownership, + swap-chain/present/capture details, UI texture/text/render systems, and the + D3D12 `EditorWindowRenderRuntime` implementation. - Use `editor/app/Platform/Win32` only for native host behavior and message integration. Win32 code may request frames through the host coordinator; it must not own the editor frame loop or expose `EditorContext`. - Do not let `editor/app/Windowing` include `Platform/Win32` headers. -- Keep concrete rendering details inside `editor/app/Windowing/Runtime` or - `editor/app/Rendering`; do not spread D3D12 host types into content, - coordinator, frame-transfer, or public host-contract headers. +- Do not let `editor/app/Windowing` include `Rendering/D3D12` headers, + `windows.h`, or HWND types. It should consume concrete rendering only through + `Rendering::Host::EditorWindowRenderRuntime` and the host interfaces in + `editor/app/Rendering/Host`. +- Do not spread D3D12 host types into app windowing content, coordinator, + frame-transfer, runtime-controller, or public host-contract headers. - Do not let Win32 host code create workspace or utility content directly. It should receive an `EditorWindowRuntimeController` created by app windowing. - Use direct authoritative workspace binding for ordinary in-window workspace @@ -233,13 +259,27 @@ frame iteration, immediate frame driving, content-controller construction, and does not expose `GetEditorContext()`, and the native host receives an `EditorWindowRuntimeController` rather than raw workspace/utility content. +The renderer ownership cut has also been made: `XCUIEditorAppWindowing` no +longer owns the concrete D3D12 window render loop or Win32 surface setup. +`EditorWindowRuntimeController` consumes a backend-neutral +`Rendering::Host::EditorWindowRenderRuntime`, and the Win32 host supplies the +current `D3D12EditorWindowRenderRuntime`. + The remaining promotion debt is the app runtime surface itself. `XCUIEditorAppWindowing` is still app-internal and may depend on app semantics -such as `EditorContext`, `EditorShellRuntime`, utility window descriptors, -product-specific content, and the current D3D12 window render loop. Do not -promote host interfaces, frame transfer, runtime controllers, or content -controllers to `XCEditor` until they are generic enough to expose. Do not move -frame ownership back into `editor/app/Platform/Win32`. +such as `EditorContext`, `EditorShellRuntime`, utility window descriptors, and +product-specific content. Do not promote host interfaces, frame transfer, +runtime controllers, content controllers, or render-runtime contracts to +`XCEditor` until they are generic enough to expose. Do not move frame ownership +back into `editor/app/Platform/Win32`, and do not move concrete renderer +ownership back into `editor/app/Windowing`. + +Do not take another architecture cut just to keep carving this area. After the +render-runtime boundary, the next default improvement should be boundary +guardrails, such as checks that keep `editor/app/Windowing` free of +`Rendering/D3D12`, `windows.h`, and HWND types. Consider a second structural cut +only when there is concrete pressure, such as another native host, another render +backend, or headless editor/window tests. ## Validation @@ -288,6 +328,8 @@ Start with these files for editor/windowing work: - `editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.*` - `editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.*` - `editor/app/Windowing/EditorWindowManager.*` +- `editor/app/Rendering/Host/EditorWindowRenderRuntime.h` +- `editor/app/Rendering/D3D12/D3D12EditorWindowRenderRuntime.*` - `editor/app/Platform/Win32/Windowing/EditorWindow.*` - `editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.*` - `editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.*` @@ -310,5 +352,9 @@ Start with these files for editor/windowing work: `EditorWindowRuntimeController`, frame driving, frame transfer, host contracts, and `EditorWindowManager` are under `editor/app/Windowing`; Win32 remains the native adapter and no longer exposes `EditorContext`. +- The concrete renderer cut is sealed: app windowing consumes + `Rendering::Host::EditorWindowRenderRuntime`, while the current D3D12 + implementation lives under `editor/app/Rendering/D3D12`; `editor/app/Windowing` + should stay free of `Rendering/D3D12`, `windows.h`, and HWND types. - Default validation remains the editor app build plus the 12-second smoke run; run broader windowing/unit targets only for targeted coverage. diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 6e3efd8a..f6fd2aa6 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -210,6 +210,7 @@ set(XCUI_EDITOR_HOST_PLATFORM_SOURCES ) set(XCUI_EDITOR_HOST_RENDERING_SOURCES + app/Rendering/D3D12/D3D12EditorWindowRenderRuntime.cpp app/Rendering/D3D12/D3D12HostDevice.cpp app/Rendering/D3D12/D3D12WindowCapture.cpp app/Rendering/D3D12/D3D12UiRenderer.cpp diff --git a/editor/app/Platform/Win32/Windowing/EditorWindow.cpp b/editor/app/Platform/Win32/Windowing/EditorWindow.cpp index 830d5524..b5ac0e3c 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindow.cpp +++ b/editor/app/Platform/Win32/Windowing/EditorWindow.cpp @@ -242,8 +242,18 @@ bool EditorWindow::InitializeRuntime( LogRuntimeTrace("window", dpiTrace.str()); MarkInitializing(); + UINT clientWidth = 0u; + UINT clientHeight = 0u; + if (!QueryCurrentClientPixelSize(clientWidth, clientHeight)) { + clientWidth = 1u; + clientHeight = 1u; + } const bool initialized = m_runtime->Initialize( - m_session->GetHwnd(), + Rendering::Host::EditorWindowRenderRuntimeSurface{ + .nativeWindowHandle = m_session->GetHwnd(), + .widthPixels = clientWidth, + .heightPixels = clientHeight, + }, params.repoRoot, params.captureRoot, params.autoCaptureOnStartup); @@ -641,7 +651,7 @@ EditorWindowFrameTransferRequests EditorWindow::RenderHostFrame( UIDrawList& windowChromeDrawList = drawData.EmplaceDrawList("XCEditorWindow.Chrome"); m_chromeController->AppendChrome(*this, windowChromeDrawList, width); - const Host::D3D12WindowRenderLoopPresentResult presentResult = m_runtime->Present(drawData); + const auto presentResult = m_runtime->Present(drawData); if (!presentResult.warning.empty()) { LogRuntimeTrace("present", presentResult.warning); } @@ -699,7 +709,7 @@ EditorWindowFrameTransferRequests EditorWindow::RenderRuntimeFrame( const bool useDetachedTitleBarTabStrip = m_chromeController->ShouldUseDetachedTitleBarTabStrip(*this); m_runtime->PrepareEditorContext(); - const Host::D3D12WindowRenderLoopFrameContext frameContext = m_runtime->BeginFrame(); + const auto frameContext = m_runtime->BeginFrame(); if (!frameContext.warning.empty()) { LogRuntimeTrace("viewport", frameContext.warning); } diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp b/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp index c6463ed9..a665f9e0 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp +++ b/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp @@ -4,6 +4,7 @@ #include "Platform/Win32/Chrome/EditorWindowChromeController.h" #include "Platform/Win32/Windowing/EditorFloatingWindowPlacement.h" #include "Platform/Win32/Windowing/EditorWindow.h" +#include "Rendering/D3D12/D3D12EditorWindowRenderRuntime.h" #include "Windowing/Host/EditorWindowHostCoordinator.h" #include "Windowing/Runtime/EditorWindowRuntimeController.h" @@ -61,6 +62,11 @@ EditorWindowHostRuntime::EditorWindowHostRuntime( EditorWindowHostRuntime::~EditorWindowHostRuntime() = default; +std::unique_ptr +EditorWindowHostRuntime::CreateWindowRenderRuntime() { + return std::make_unique(); +} + EditorWindow* EditorWindowHostRuntime::CreateHostWindow( std::unique_ptr runtimeController, const EditorWindowCreateParams& params) { diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.h b/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.h index 71c8dc7c..8f70a473 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.h +++ b/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.h @@ -23,6 +23,8 @@ public: std::filesystem::path captureRoot); ~EditorWindowHostRuntime(); + std::unique_ptr + CreateWindowRenderRuntime() override; EditorWindow* CreateHostWindow( std::unique_ptr runtimeController, const EditorWindowCreateParams& params) override; diff --git a/editor/app/Rendering/D3D12/D3D12EditorWindowRenderRuntime.cpp b/editor/app/Rendering/D3D12/D3D12EditorWindowRenderRuntime.cpp new file mode 100644 index 00000000..ca70157d --- /dev/null +++ b/editor/app/Rendering/D3D12/D3D12EditorWindowRenderRuntime.cpp @@ -0,0 +1,158 @@ +#include "Rendering/D3D12/D3D12EditorWindowRenderRuntime.h" + +#include + +#include + +namespace XCEngine::UI::Editor::Host { + +namespace { + +using Rendering::Host::EditorWindowRenderRuntimeFrameContext; +using Rendering::Host::EditorWindowRenderRuntimeInitializeResult; +using Rendering::Host::EditorWindowRenderRuntimePresentResult; +using Rendering::Host::EditorWindowRenderRuntimeResizeResult; +using Rendering::Host::EditorWindowRenderRuntimeSurface; + +} // namespace + +D3D12EditorWindowRenderRuntime::~D3D12EditorWindowRenderRuntime() = default; + +bool D3D12EditorWindowRenderRuntime::IsReady() const { + return m_ready; +} + +void D3D12EditorWindowRenderRuntime::SetDpiScale(float dpiScale) { + m_dpiScale = dpiScale > 0.0f ? dpiScale : 1.0f; + m_textSystem.SetDpiScale(m_dpiScale); + m_uiRenderer.SetDpiScale(m_dpiScale); +} + +UIEditorTextMeasurer& D3D12EditorWindowRenderRuntime::GetTextMeasurer() { + return m_textSystem; +} + +const UIEditorTextMeasurer& D3D12EditorWindowRenderRuntime::GetTextMeasurer() const { + return m_textSystem; +} + +Rendering::Host::UiTextureHost& D3D12EditorWindowRenderRuntime::GetTextureHost() { + return m_textureHost; +} + +Rendering::Host::ViewportRenderHost& D3D12EditorWindowRenderRuntime::GetViewportRenderHost() { + return m_windowRenderer; +} + +EditorWindowRenderRuntimeInitializeResult D3D12EditorWindowRenderRuntime::Initialize( + const EditorWindowRenderRuntimeSurface& surface) { + EditorWindowRenderRuntimeInitializeResult result = {}; + + HWND hwnd = static_cast(surface.nativeWindowHandle); + if (hwnd == nullptr) { + result.errorMessage = "window initialize skipped: hwnd is null"; + return result; + } + + const int clientWidth = (std::max)(static_cast(surface.widthPixels), 1); + const int clientHeight = (std::max)(static_cast(surface.heightPixels), 1); + if (!m_windowRenderer.Initialize(hwnd, clientWidth, clientHeight)) { + result.errorMessage = "d3d12 window renderer initialization failed"; + return result; + } + + if (!m_textureHost.Initialize(m_windowRenderer)) { + result.errorMessage = "d3d12 ui texture host initialization failed"; + m_windowRenderer.Shutdown(); + return result; + } + + if (!m_textSystem.Initialize()) { + result.errorMessage = "d3d12 ui text system initialization failed"; + m_textureHost.Shutdown(); + m_windowRenderer.Shutdown(); + return result; + } + m_textSystem.SetDpiScale(m_dpiScale); + + if (!m_uiRenderer.Initialize(m_windowRenderer, m_textureHost, m_textSystem)) { + result.errorMessage = "d3d12 ui renderer initialization failed"; + m_textSystem.Shutdown(); + m_textureHost.Shutdown(); + m_windowRenderer.Shutdown(); + return result; + } + m_uiRenderer.SetDpiScale(m_dpiScale); + + const D3D12WindowRenderLoopAttachResult attachResult = + m_windowRenderLoop.Attach(m_uiRenderer, m_windowRenderer); + result.success = true; + result.hasViewportSurfacePresentation = attachResult.hasViewportSurfacePresentation; + result.warning = attachResult.warning; + m_ready = true; + return result; +} + +void D3D12EditorWindowRenderRuntime::WaitForGpuIdle() { + m_windowRenderer.WaitForGpuIdle(); +} + +void D3D12EditorWindowRenderRuntime::Shutdown() { + m_ready = false; + m_windowRenderLoop.Detach(); + m_uiRenderer.Shutdown(); + m_textSystem.Shutdown(); + m_textureHost.Shutdown(); + m_windowRenderer.Shutdown(); + m_dpiScale = 1.0f; +} + +EditorWindowRenderRuntimeResizeResult D3D12EditorWindowRenderRuntime::ApplyResize( + std::uint32_t width, + std::uint32_t height) { + EditorWindowRenderRuntimeResizeResult result = {}; + if (!m_ready || width == 0u || height == 0u) { + return result; + } + + const D3D12WindowRenderLoopResizeResult resizeResult = + m_windowRenderLoop.ApplyResize( + static_cast(width), + static_cast(height)); + result.hasViewportSurfacePresentation = resizeResult.hasViewportSurfacePresentation; + result.warning = resizeResult.windowRendererWarning; + return result; +} + +EditorWindowRenderRuntimeFrameContext D3D12EditorWindowRenderRuntime::BeginFrame() { + EditorWindowRenderRuntimeFrameContext result = {}; + if (!m_ready) { + return result; + } + + const D3D12WindowRenderLoopFrameContext frameContext = m_windowRenderLoop.BeginFrame(); + result.canRenderViewports = frameContext.canRenderViewports; + result.renderContext = frameContext.renderContext; + result.warning = frameContext.warning; + return result; +} + +EditorWindowRenderRuntimePresentResult D3D12EditorWindowRenderRuntime::Present( + const ::XCEngine::UI::UIDrawData& drawData, + const std::filesystem::path* captureOutputPath) { + EditorWindowRenderRuntimePresentResult result = {}; + if (!m_ready) { + result.warning = "window render runtime is not ready."; + return result; + } + + const D3D12WindowRenderLoopPresentResult presentResult = + m_windowRenderLoop.Present(drawData, captureOutputPath); + result.framePresented = presentResult.framePresented; + result.captureSucceeded = presentResult.captureSucceeded; + result.captureError = presentResult.captureError; + result.warning = presentResult.warning; + return result; +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/editor/app/Rendering/D3D12/D3D12EditorWindowRenderRuntime.h b/editor/app/Rendering/D3D12/D3D12EditorWindowRenderRuntime.h new file mode 100644 index 00000000..216f9cf8 --- /dev/null +++ b/editor/app/Rendering/D3D12/D3D12EditorWindowRenderRuntime.h @@ -0,0 +1,56 @@ +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include "Rendering/D3D12/D3D12UiRenderer.h" +#include "Rendering/D3D12/D3D12UiTextSystem.h" +#include "Rendering/D3D12/D3D12UiTextureHost.h" +#include "Rendering/D3D12/D3D12WindowRenderLoop.h" +#include "Rendering/D3D12/D3D12WindowRenderer.h" +#include "Rendering/Host/EditorWindowRenderRuntime.h" + +namespace XCEngine::UI::Editor::Host { + +class D3D12EditorWindowRenderRuntime final + : public ::XCEngine::UI::Editor::Rendering::Host::EditorWindowRenderRuntime { +public: + D3D12EditorWindowRenderRuntime() = default; + ~D3D12EditorWindowRenderRuntime() override; + + D3D12EditorWindowRenderRuntime(const D3D12EditorWindowRenderRuntime&) = delete; + D3D12EditorWindowRenderRuntime& operator=(const D3D12EditorWindowRenderRuntime&) = delete; + D3D12EditorWindowRenderRuntime(D3D12EditorWindowRenderRuntime&&) = delete; + D3D12EditorWindowRenderRuntime& operator=(D3D12EditorWindowRenderRuntime&&) = delete; + + bool IsReady() const override; + void SetDpiScale(float dpiScale) override; + UIEditorTextMeasurer& GetTextMeasurer() override; + const UIEditorTextMeasurer& GetTextMeasurer() const override; + Rendering::Host::UiTextureHost& GetTextureHost() override; + Rendering::Host::ViewportRenderHost& GetViewportRenderHost() override; + + Rendering::Host::EditorWindowRenderRuntimeInitializeResult Initialize( + const Rendering::Host::EditorWindowRenderRuntimeSurface& surface) override; + void WaitForGpuIdle() override; + void Shutdown() override; + Rendering::Host::EditorWindowRenderRuntimeResizeResult ApplyResize( + std::uint32_t width, + std::uint32_t height) override; + Rendering::Host::EditorWindowRenderRuntimeFrameContext BeginFrame() override; + Rendering::Host::EditorWindowRenderRuntimePresentResult Present( + const ::XCEngine::UI::UIDrawData& drawData, + const std::filesystem::path* captureOutputPath) override; + +private: + D3D12WindowRenderer m_windowRenderer = {}; + D3D12UiTextureHost m_textureHost = {}; + D3D12UiTextSystem m_textSystem = {}; + D3D12UiRenderer m_uiRenderer = {}; + D3D12WindowRenderLoop m_windowRenderLoop = {}; + float m_dpiScale = 1.0f; + bool m_ready = false; +}; + +} // namespace XCEngine::UI::Editor::Host diff --git a/editor/app/Rendering/Host/EditorWindowRenderRuntime.h b/editor/app/Rendering/Host/EditorWindowRenderRuntime.h new file mode 100644 index 00000000..91ae9e9b --- /dev/null +++ b/editor/app/Rendering/Host/EditorWindowRenderRuntime.h @@ -0,0 +1,71 @@ +#pragma once + +#include "Rendering/Host/UiTextureHost.h" +#include "Rendering/Host/ViewportRenderHost.h" + +#include +#include +#include + +#include +#include +#include + +namespace XCEngine::UI::Editor::Rendering::Host { + +struct EditorWindowRenderRuntimeSurface { + void* nativeWindowHandle = nullptr; + std::uint32_t widthPixels = 0u; + std::uint32_t heightPixels = 0u; +}; + +struct EditorWindowRenderRuntimeInitializeResult { + bool success = false; + bool hasViewportSurfacePresentation = false; + std::string errorMessage = {}; + std::string warning = {}; +}; + +struct EditorWindowRenderRuntimeResizeResult { + bool hasViewportSurfacePresentation = false; + std::string warning = {}; +}; + +struct EditorWindowRenderRuntimeFrameContext { + bool canRenderViewports = false; + ::XCEngine::Rendering::RenderContext renderContext = {}; + std::string warning = {}; +}; + +struct EditorWindowRenderRuntimePresentResult { + bool framePresented = false; + bool captureSucceeded = false; + std::string captureError = {}; + std::string warning = {}; +}; + +class EditorWindowRenderRuntime { +public: + virtual ~EditorWindowRenderRuntime() = default; + + virtual bool IsReady() const = 0; + virtual void SetDpiScale(float dpiScale) = 0; + virtual UIEditorTextMeasurer& GetTextMeasurer() = 0; + virtual const UIEditorTextMeasurer& GetTextMeasurer() const = 0; + virtual UiTextureHost& GetTextureHost() = 0; + virtual ViewportRenderHost& GetViewportRenderHost() = 0; + + virtual EditorWindowRenderRuntimeInitializeResult Initialize( + const EditorWindowRenderRuntimeSurface& surface) = 0; + virtual void WaitForGpuIdle() = 0; + virtual void Shutdown() = 0; + virtual EditorWindowRenderRuntimeResizeResult ApplyResize( + std::uint32_t width, + std::uint32_t height) = 0; + virtual EditorWindowRenderRuntimeFrameContext BeginFrame() = 0; + virtual EditorWindowRenderRuntimePresentResult Present( + const ::XCEngine::UI::UIDrawData& drawData, + const std::filesystem::path* captureOutputPath) = 0; +}; + +} // namespace XCEngine::UI::Editor::Rendering::Host diff --git a/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.cpp b/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.cpp index f2312617..adb28c96 100644 --- a/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.cpp +++ b/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.cpp @@ -128,7 +128,8 @@ bool EditorUtilityWindowCoordinator::TryProcessOpenUtilityWindowRequest( EditorHostWindow* utilityWindow = m_hostRuntime.CreateHostWindow( std::make_unique( m_editorContext, - m_contentFactory.CreateUtilityContentController(*descriptor)), + m_contentFactory.CreateUtilityContentController(*descriptor), + m_hostRuntime.CreateWindowRenderRuntime()), createParams); if (utilityWindow == nullptr) { LogRuntimeTrace("utility", "failed to create utility window"); diff --git a/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.cpp b/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.cpp index 08dc4a63..df04d4d2 100644 --- a/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.cpp +++ b/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.cpp @@ -371,7 +371,8 @@ bool EditorWindowWorkspaceCoordinator::ApplySynchronizationPlan( EditorHostWindow* const createdWindow = m_hostRuntime.CreateHostWindow( std::make_unique( m_editorContext, - m_contentFactory.CreateWorkspaceContentController(action.create.windowState)), + m_contentFactory.CreateWorkspaceContentController(action.create.windowState), + m_hostRuntime.CreateWindowRenderRuntime()), createParams); if (createdWindow == nullptr) { for (const ExistingWindowSnapshot& snapshot : existingWindowSnapshots) { diff --git a/editor/app/Windowing/EditorWindowManager.cpp b/editor/app/Windowing/EditorWindowManager.cpp index 32fc86e1..478173e8 100644 --- a/editor/app/Windowing/EditorWindowManager.cpp +++ b/editor/app/Windowing/EditorWindowManager.cpp @@ -59,7 +59,8 @@ EditorHostWindow* EditorWindowManager::CreateWorkspaceWindow( EditorHostWindow* const window = m_hostRuntime.CreateHostWindow( std::make_unique( m_editorContext, - m_contentFactory->CreateWorkspaceContentController(windowState)), + m_contentFactory->CreateWorkspaceContentController(windowState), + m_hostRuntime.CreateWindowRenderRuntime()), params); if (window != nullptr) { m_workspaceCoordinator->RegisterExistingWindow(*window); @@ -77,7 +78,8 @@ EditorHostWindow* EditorWindowManager::CreateUtilityWindow( EditorHostWindow* const window = m_hostRuntime.CreateHostWindow( std::make_unique( m_editorContext, - m_contentFactory->CreateUtilityContentController(descriptor)), + m_contentFactory->CreateUtilityContentController(descriptor), + m_hostRuntime.CreateWindowRenderRuntime()), params); if (window != nullptr) { m_workspaceCoordinator->RegisterExistingWindow(*window); diff --git a/editor/app/Windowing/Host/EditorWindowHostInterfaces.h b/editor/app/Windowing/Host/EditorWindowHostInterfaces.h index 6f618b06..9bb33ce2 100644 --- a/editor/app/Windowing/Host/EditorWindowHostInterfaces.h +++ b/editor/app/Windowing/Host/EditorWindowHostInterfaces.h @@ -26,6 +26,12 @@ struct UIEditorDockHostTabDropTarget; } // namespace XCEngine::UI::Editor +namespace XCEngine::UI::Editor::Rendering::Host { + +class EditorWindowRenderRuntime; + +} // namespace XCEngine::UI::Editor::Rendering::Host + namespace XCEngine::UI::Editor::App { class EditorWindowHostCoordinator; @@ -94,6 +100,8 @@ class EditorWindowHost { public: virtual ~EditorWindowHost() = default; + virtual std::unique_ptr + CreateWindowRenderRuntime() = 0; virtual EditorHostWindow* CreateHostWindow( std::unique_ptr runtimeController, const EditorWindowCreateParams& params) = 0; diff --git a/editor/app/Windowing/Runtime/EditorWindowRuntimeController.cpp b/editor/app/Windowing/Runtime/EditorWindowRuntimeController.cpp index 3468ca36..5d869b87 100644 --- a/editor/app/Windowing/Runtime/EditorWindowRuntimeController.cpp +++ b/editor/app/Windowing/Runtime/EditorWindowRuntimeController.cpp @@ -15,8 +15,6 @@ #include #include -#include - namespace XCEngine::UI::Editor::App { using App::LoadEmbeddedPngTexture; @@ -35,14 +33,16 @@ void LogRuntimeTrace(std::string_view channel, std::string_view message) { EditorWindowRuntimeController::EditorWindowRuntimeController( EditorContext& editorContext, - std::unique_ptr contentController) + std::unique_ptr contentController, + std::unique_ptr renderRuntime) : m_editorContext(editorContext) + , m_renderRuntime(std::move(renderRuntime)) , m_contentController(std::move(contentController)) {} EditorWindowRuntimeController::~EditorWindowRuntimeController() = default; bool EditorWindowRuntimeController::IsReady() const { - return m_ready; + return m_ready && m_renderRuntime != nullptr && m_renderRuntime->IsReady(); } EditorWindowContentCapabilities EditorWindowRuntimeController::GetCapabilities() const { @@ -106,17 +106,20 @@ const UIEditorShellInteractionState& EditorWindowRuntimeController::GetShellInte void EditorWindowRuntimeController::SetDpiScale(float dpiScale) { m_dpiScale = dpiScale > 0.0f ? dpiScale : 1.0f; - m_textSystem.SetDpiScale(m_dpiScale); - m_uiRenderer.SetDpiScale(m_dpiScale); + if (m_renderRuntime != nullptr) { + m_renderRuntime->SetDpiScale(m_dpiScale); + } } ::XCEngine::UI::Editor::UIEditorTextMeasurer& EditorWindowRuntimeController::GetTextMeasurer() { - return m_textSystem; + assert(m_renderRuntime != nullptr); + return m_renderRuntime->GetTextMeasurer(); } const ::XCEngine::UI::Editor::UIEditorTextMeasurer& EditorWindowRuntimeController::GetTextMeasurer() const { - return m_textSystem; + assert(m_renderRuntime != nullptr); + return m_renderRuntime->GetTextMeasurer(); } const ::XCEngine::UI::UITextureHandle& EditorWindowRuntimeController::GetTitleBarLogoIcon() const { @@ -124,69 +127,44 @@ const ::XCEngine::UI::UITextureHandle& EditorWindowRuntimeController::GetTitleBa } bool EditorWindowRuntimeController::Initialize( - void* nativeWindowHandle, + const Rendering::Host::EditorWindowRenderRuntimeSurface& renderSurface, const std::filesystem::path& repoRoot, const std::filesystem::path& captureRoot, bool autoCaptureOnStartup) { - HWND hwnd = static_cast(nativeWindowHandle); - if (hwnd == nullptr) { - LogRuntimeTrace("app", "window initialize skipped: hwnd is null"); + if (m_renderRuntime == nullptr) { + LogRuntimeTrace("app", "window initialize failed: render runtime is null"); return false; } - RECT clientRect = {}; - GetClientRect(hwnd, &clientRect); - const int clientWidth = (std::max)(clientRect.right - clientRect.left, 1L); - const int clientHeight = (std::max)(clientRect.bottom - clientRect.top, 1L); - if (!m_windowRenderer.Initialize(hwnd, clientWidth, clientHeight)) { - LogRuntimeTrace("app", "d3d12 window renderer initialization failed"); + const Rendering::Host::EditorWindowRenderRuntimeInitializeResult initializeResult = + m_renderRuntime->Initialize(renderSurface); + if (!initializeResult.success) { + LogRuntimeTrace("app", initializeResult.errorMessage.empty() + ? "window render runtime initialization failed" + : initializeResult.errorMessage); return false; } - - if (!m_textureHost.Initialize(m_windowRenderer)) { - LogRuntimeTrace("app", "d3d12 ui texture host initialization failed"); - m_windowRenderer.Shutdown(); - return false; - } - - if (!m_textSystem.Initialize()) { - LogRuntimeTrace("app", "d3d12 ui text system initialization failed"); - m_textureHost.Shutdown(); - m_windowRenderer.Shutdown(); - return false; - } - m_textSystem.SetDpiScale(m_dpiScale); - - if (!m_uiRenderer.Initialize(m_windowRenderer, m_textureHost, m_textSystem)) { - LogRuntimeTrace("app", "d3d12 ui renderer initialization failed"); - m_textSystem.Shutdown(); - m_textureHost.Shutdown(); - m_windowRenderer.Shutdown(); - return false; - } - m_uiRenderer.SetDpiScale(m_dpiScale); - - const Host::D3D12WindowRenderLoopAttachResult attachResult = - m_windowRenderLoop.Attach(m_uiRenderer, m_windowRenderer); - if (!attachResult.warning.empty()) { - LogRuntimeTrace("app", attachResult.warning); + if (!initializeResult.warning.empty()) { + LogRuntimeTrace("app", initializeResult.warning); } assert(m_contentController != nullptr); - m_contentController->PrepareEditorContext(m_editorContext, m_textSystem); + m_contentController->PrepareEditorContext( + m_editorContext, + m_renderRuntime->GetTextMeasurer()); m_contentController->Initialize(EditorWindowContentInitializationContext{ .repoRoot = repoRoot, .editorContext = m_editorContext, - .textureHost = m_textureHost, - .textMeasurer = m_textSystem, - .viewportRenderer = m_windowRenderer, + .textureHost = m_renderRuntime->GetTextureHost(), + .textMeasurer = m_renderRuntime->GetTextMeasurer(), + .viewportRenderer = m_renderRuntime->GetViewportRenderHost(), }); m_contentController->SetViewportSurfacePresentationEnabled( - attachResult.hasViewportSurfacePresentation); + initializeResult.hasViewportSurfacePresentation); std::string titleBarLogoError = {}; if (!LoadEmbeddedPngTexture( - m_textureHost, + m_renderRuntime->GetTextureHost(), IDR_PNG_LOGO_ICON, m_titleBarLogoIcon, titleBarLogoError)) { @@ -218,7 +196,9 @@ void EditorWindowRuntimeController::Shutdown() { m_ready = false; ResetFrameTiming(); LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=WaitForGpuIdle"); - m_windowRenderer.WaitForGpuIdle(); + if (m_renderRuntime != nullptr) { + m_renderRuntime->WaitForGpuIdle(); + } LogRuntimeTrace( "window-close", "EditorWindowRuntimeController::Shutdown stage=ScreenshotController"); @@ -227,18 +207,12 @@ void EditorWindowRuntimeController::Shutdown() { if (m_contentController != nullptr) { m_contentController->Shutdown(); } - LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=RenderLoopDetach"); - m_windowRenderLoop.Detach(); - LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=UiRenderer"); - m_uiRenderer.Shutdown(); - LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=TextSystem"); - m_textSystem.Shutdown(); LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=TitleBarLogo"); - m_textureHost.ReleaseTexture(m_titleBarLogoIcon); - LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=TextureHost"); - m_textureHost.Shutdown(); - LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=WindowRenderer"); - m_windowRenderer.Shutdown(); + if (m_renderRuntime != nullptr) { + m_renderRuntime->GetTextureHost().ReleaseTexture(m_titleBarLogoIcon); + LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=RenderRuntime"); + m_renderRuntime->Shutdown(); + } m_dpiScale = 1.0f; LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown end"); } @@ -251,27 +225,29 @@ void EditorWindowRuntimeController::ResetInteractionState() { } bool EditorWindowRuntimeController::ApplyResize(std::uint32_t width, std::uint32_t height) { - if (!m_ready || width == 0u || height == 0u) { + if (!IsReady() || width == 0u || height == 0u) { return false; } - const Host::D3D12WindowRenderLoopResizeResult resizeResult = - m_windowRenderLoop.ApplyResize(width, height); + const Rendering::Host::EditorWindowRenderRuntimeResizeResult resizeResult = + m_renderRuntime->ApplyResize(width, height); if (m_contentController != nullptr) { m_contentController->SetViewportSurfacePresentationEnabled( resizeResult.hasViewportSurfacePresentation); } - if (!resizeResult.windowRendererWarning.empty()) { - LogRuntimeTrace("present", resizeResult.windowRendererWarning); + if (!resizeResult.warning.empty()) { + LogRuntimeTrace("present", resizeResult.warning); } return resizeResult.hasViewportSurfacePresentation; } void EditorWindowRuntimeController::PrepareEditorContext() { - if (m_contentController != nullptr) { - m_contentController->PrepareEditorContext(m_editorContext, m_textSystem); + if (m_contentController != nullptr && m_renderRuntime != nullptr) { + m_contentController->PrepareEditorContext( + m_editorContext, + m_renderRuntime->GetTextMeasurer()); } } @@ -287,18 +263,26 @@ void EditorWindowRuntimeController::AppendInvalidFrame( } } -Host::D3D12WindowRenderLoopFrameContext EditorWindowRuntimeController::BeginFrame() { +Rendering::Host::EditorWindowRenderRuntimeFrameContext EditorWindowRuntimeController::BeginFrame() { UpdateFrameTiming(); - return m_windowRenderLoop.BeginFrame(); + return m_renderRuntime != nullptr + ? m_renderRuntime->BeginFrame() + : Rendering::Host::EditorWindowRenderRuntimeFrameContext{}; } -Host::D3D12WindowRenderLoopPresentResult EditorWindowRuntimeController::Present( +Rendering::Host::EditorWindowRenderRuntimePresentResult EditorWindowRuntimeController::Present( const ::XCEngine::UI::UIDrawData& drawData) { + if (m_renderRuntime == nullptr) { + Rendering::Host::EditorWindowRenderRuntimePresentResult result = {}; + result.warning = "window render runtime is null."; + return result; + } + std::filesystem::path capturePath = {}; const bool captureRequested = m_screenshotController.TryBeginCapture(capturePath); - Host::D3D12WindowRenderLoopPresentResult result = - m_windowRenderLoop.Present( + Rendering::Host::EditorWindowRenderRuntimePresentResult result = + m_renderRuntime->Present( drawData, captureRequested ? &capturePath : nullptr); @@ -306,7 +290,7 @@ Host::D3D12WindowRenderLoopPresentResult EditorWindowRuntimeController::Present( const bool captureSucceeded = result.framePresented && result.captureSucceeded; if (captureSucceeded) { m_screenshotController.CompleteCaptureSuccess(capturePath); - LogRuntimeTrace("capture", "native d3d12 capture succeeded: " + capturePath.string()); + LogRuntimeTrace("capture", "native capture succeeded: " + capturePath.string()); } else { std::string captureError = result.captureError; if (captureError.empty()) { @@ -317,7 +301,7 @@ Host::D3D12WindowRenderLoopPresentResult EditorWindowRuntimeController::Present( m_screenshotController.CompleteCaptureFailure(std::move(captureError)); LogRuntimeTrace( "capture", - "native d3d12 capture failed: " + m_screenshotController.GetLastCaptureError()); + "native capture failed: " + m_screenshotController.GetLastCaptureError()); } } diff --git a/editor/app/Windowing/Runtime/EditorWindowRuntimeController.h b/editor/app/Windowing/Runtime/EditorWindowRuntimeController.h index 437f5367..050cfa0b 100644 --- a/editor/app/Windowing/Runtime/EditorWindowRuntimeController.h +++ b/editor/app/Windowing/Runtime/EditorWindowRuntimeController.h @@ -7,11 +7,7 @@ #include "Windowing/Content/EditorWindowContentController.h" #include "Windowing/Runtime/EditorWindowScreenshotController.h" -#include -#include -#include -#include -#include +#include #include @@ -37,7 +33,8 @@ class EditorWindowRuntimeController final { public: EditorWindowRuntimeController( EditorContext& editorContext, - std::unique_ptr contentController); + std::unique_ptr contentController, + std::unique_ptr renderRuntime); ~EditorWindowRuntimeController(); EditorWindowRuntimeController(const EditorWindowRuntimeController&) = delete; @@ -65,7 +62,7 @@ public: const ::XCEngine::UI::UITextureHandle& GetTitleBarLogoIcon() const; bool Initialize( - void* nativeWindowHandle, + const Rendering::Host::EditorWindowRenderRuntimeSurface& renderSurface, const std::filesystem::path& repoRoot, const std::filesystem::path& captureRoot, bool autoCaptureOnStartup); @@ -76,8 +73,8 @@ public: void PrepareEditorContext(); bool IsEditorContextValid() const; void AppendInvalidFrame(::XCEngine::UI::UIDrawList& drawList) const; - Host::D3D12WindowRenderLoopFrameContext BeginFrame(); - Host::D3D12WindowRenderLoopPresentResult Present( + Rendering::Host::EditorWindowRenderRuntimeFrameContext BeginFrame(); + Rendering::Host::EditorWindowRenderRuntimePresentResult Present( const ::XCEngine::UI::UIDrawData& drawData); EditorWindowFrameTransferRequests UpdateAndAppend( const ::XCEngine::UI::UIRect& bounds, @@ -100,11 +97,7 @@ private: void RefreshDisplayedFrameStats(); EditorContext& m_editorContext; - Host::D3D12WindowRenderer m_windowRenderer = {}; - Host::D3D12UiTextureHost m_textureHost = {}; - Host::D3D12UiTextSystem m_textSystem = {}; - Host::D3D12UiRenderer m_uiRenderer = {}; - Host::D3D12WindowRenderLoop m_windowRenderLoop = {}; + std::unique_ptr m_renderRuntime = {}; EditorWindowScreenshotController m_screenshotController = {}; ::XCEngine::UI::UITextureHandle m_titleBarLogoIcon = {}; std::unique_ptr m_contentController = {};