Decouple editor render runtime factory

This commit is contained in:
2026-04-26 19:21:38 +08:00
parent caa54b7878
commit fa4fcbe95b
15 changed files with 120 additions and 46 deletions

View File

@@ -54,9 +54,10 @@ Use these ownership boundaries when changing code:
- `editor/app/Platform/Win32`: native window, message dispatch, input,
lifecycle, chrome, HWND ownership, and native host adapter behavior.
- `editor/app/Rendering/Host`: app rendering contracts consumed by app
windowing and app content.
windowing and app content, including the render-runtime factory interface.
- `editor/app/Rendering/D3D12`: concrete D3D12 window renderer, UI renderer,
texture host, text system, render loop, and render-runtime adapter.
texture host, text system, render loop, render-runtime adapter, and
render-runtime factory.
The semantic dependency direction should remain:
@@ -64,7 +65,7 @@ The semantic dependency direction should remain:
XCEditor framework
<- editor app semantics
<- app windowing runtime / rendering host contracts
<- Win32 host / concrete rendering adapters
<- application composition root / concrete Win32 and rendering adapters
```
## Startup Flow
@@ -85,7 +86,7 @@ EditorContext::BuildWorkspaceController()
-> EditorWindowSystem::BootstrapPrimaryWindow(...)
-> EditorWindowManager::CreateWorkspaceWindow(...)
-> EditorWindowContentFactory::CreateWorkspaceContentController(...)
-> EditorWindowHostRuntime::CreateWindowRenderRuntime()
-> EditorWindowRenderRuntimeFactory::CreateWindowRenderRuntime()
-> EditorWindowRuntimeController(EditorContext, contentController, renderRuntime)
-> EditorWindowHostRuntime::CreateHostWindow(runtimeController, ...)
```
@@ -107,18 +108,21 @@ 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.
Concrete renderer selection is composed above both app windowing and the native
host. `Application` creates the current
`D3D12EditorWindowRenderRuntimeFactory` and passes it into
`EditorWindowManager`; app windowing asks the factory for backend-neutral
`Rendering::Host::EditorWindowRenderRuntime` instances. The Win32 host receives
runtime controllers that already contain their render runtime; it must not
create concrete render backends. `EditorWindowRuntimeController` may call
`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
-> ask EditorWindowRenderRuntimeFactory for EditorWindowRenderRuntime
-> create EditorWindowRuntimeController
-> EditorWindowHostRuntime::CreateHostWindow(runtimeController, ...)
-> Win32 EditorWindow owns HWND and delegates frame work to the runtime
@@ -215,18 +219,22 @@ logic.
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.
windowing or app content may consume, including
`EditorWindowRenderRuntimeFactory`.
- 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.
D3D12 `EditorWindowRenderRuntime` implementation and factory.
- 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`.
must not own the editor frame loop, expose `EditorContext`, or create
concrete render backends.
- Do not let `editor/app/Windowing` include `Platform/Win32` 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`.
`Rendering::Host::EditorWindowRenderRuntime` and
`Rendering::Host::EditorWindowRenderRuntimeFactory`.
- Do not let `editor/app/Platform/Win32` include `Rendering/D3D12` headers or
implement render-runtime factory methods on the native host interfaces.
- 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.
@@ -262,8 +270,9 @@ does not expose `GetEditorContext()`, and the native host receives an
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`.
`Rendering::Host::EditorWindowRenderRuntime`, and `EditorWindowManager` receives
a backend-neutral `Rendering::Host::EditorWindowRenderRuntimeFactory`. The
current D3D12 factory is composed in `Application`, not in the Win32 host.
The remaining promotion debt is the app runtime surface itself.
`XCUIEditorAppWindowing` is still app-internal and may depend on app semantics
@@ -271,15 +280,17 @@ 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`.
back into `editor/app/Platform/Win32`, do not move concrete renderer ownership
back into `editor/app/Windowing`, and do not move renderer factory ownership
back into the Win32 host.
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.
`Rendering/D3D12`, `windows.h`, and HWND types, and keep
`editor/app/Platform/Win32` free of `Rendering/D3D12`. 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
@@ -353,8 +364,11 @@ Start with these files for editor/windowing work:
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.
`Rendering::Host::EditorWindowRenderRuntime` and
`Rendering::Host::EditorWindowRenderRuntimeFactory`, while the current D3D12
implementation and factory live under `editor/app/Rendering/D3D12`. The
factory is composed in `Application`; `editor/app/Windowing` should stay free
of `Rendering/D3D12`, `windows.h`, and HWND types, and
`editor/app/Platform/Win32` should stay free of `Rendering/D3D12`.
- Default validation remains the editor app build plus the 12-second smoke run;
run broader windowing/unit targets only for targeted coverage.

View File

@@ -8,6 +8,7 @@
#include "Platform/Win32/Windowing/EditorWindowHostConfig.h"
#include "Platform/Win32/Windowing/EditorWindowHostRuntime.h"
#include "Platform/Win32/Windowing/EditorWindowMessageDispatcher.h"
#include "Rendering/D3D12/D3D12EditorWindowRenderRuntime.h"
#include "Support/EnvironmentFlags.h"
#include "Support/ExecutablePath.h"
@@ -144,9 +145,12 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) {
hostConfig,
m_repoRoot,
m_editorContext->GetShellAsset().captureRootPath);
m_renderRuntimeFactory =
std::make_unique<Host::D3D12EditorWindowRenderRuntimeFactory>();
m_windowManager = std::make_unique<App::EditorWindowManager>(
*m_editorContext,
*m_windowSystem,
*m_renderRuntimeFactory,
*m_windowHostRuntime);
m_editorContext->SetExitRequestHandler([this]() {
@@ -223,6 +227,7 @@ void Application::Shutdown() {
m_windowManager->Shutdown();
m_windowManager.reset();
}
m_renderRuntimeFactory.reset();
m_windowHostRuntime.reset();
m_windowSystem.reset();

View File

@@ -19,6 +19,10 @@ class EditorWindowManager;
class EditorWindowHostRuntime;
}
namespace Host {
class D3D12EditorWindowRenderRuntimeFactory;
}
namespace System {
class SystemInteractionService;
}
@@ -55,6 +59,7 @@ private:
std::unique_ptr<App::EditorContext> m_editorContext = {};
std::unique_ptr<EditorWindowSystem> m_windowSystem = {};
std::unique_ptr<App::EditorWindowHostRuntime> m_windowHostRuntime = {};
std::unique_ptr<Host::D3D12EditorWindowRenderRuntimeFactory> m_renderRuntimeFactory = {};
std::unique_ptr<App::EditorWindowManager> m_windowManager = {};
std::unique_ptr<System::SystemInteractionService> m_systemInteractionHost = {};
int m_smokeTestFrameLimit = 0;

View File

@@ -4,7 +4,6 @@
#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"
@@ -62,11 +61,6 @@ EditorWindowHostRuntime::EditorWindowHostRuntime(
EditorWindowHostRuntime::~EditorWindowHostRuntime() = default;
std::unique_ptr<Rendering::Host::EditorWindowRenderRuntime>
EditorWindowHostRuntime::CreateWindowRenderRuntime() {
return std::make_unique<Host::D3D12EditorWindowRenderRuntime>();
}
EditorWindow* EditorWindowHostRuntime::CreateHostWindow(
std::unique_ptr<EditorWindowRuntimeController> runtimeController,
const EditorWindowCreateParams& params) {

View File

@@ -23,8 +23,6 @@ public:
std::filesystem::path captureRoot);
~EditorWindowHostRuntime();
std::unique_ptr<Rendering::Host::EditorWindowRenderRuntime>
CreateWindowRenderRuntime() override;
EditorWindow* CreateHostWindow(
std::unique_ptr<EditorWindowRuntimeController> runtimeController,
const EditorWindowCreateParams& params) override;

View File

@@ -1,6 +1,7 @@
#include "Rendering/D3D12/D3D12EditorWindowRenderRuntime.h"
#include <algorithm>
#include <memory>
#include <windows.h>
@@ -18,6 +19,11 @@ using Rendering::Host::EditorWindowRenderRuntimeSurface;
D3D12EditorWindowRenderRuntime::~D3D12EditorWindowRenderRuntime() = default;
std::unique_ptr<Rendering::Host::EditorWindowRenderRuntime>
D3D12EditorWindowRenderRuntimeFactory::CreateWindowRenderRuntime() {
return std::make_unique<D3D12EditorWindowRenderRuntime>();
}
bool D3D12EditorWindowRenderRuntime::IsReady() const {
return m_ready;
}

View File

@@ -11,6 +11,8 @@
#include "Rendering/D3D12/D3D12WindowRenderer.h"
#include "Rendering/Host/EditorWindowRenderRuntime.h"
#include <memory>
namespace XCEngine::UI::Editor::Host {
class D3D12EditorWindowRenderRuntime final
@@ -53,4 +55,22 @@ private:
bool m_ready = false;
};
class D3D12EditorWindowRenderRuntimeFactory final
: public ::XCEngine::UI::Editor::Rendering::Host::EditorWindowRenderRuntimeFactory {
public:
D3D12EditorWindowRenderRuntimeFactory() = default;
~D3D12EditorWindowRenderRuntimeFactory() override = default;
D3D12EditorWindowRenderRuntimeFactory(
const D3D12EditorWindowRenderRuntimeFactory&) = delete;
D3D12EditorWindowRenderRuntimeFactory& operator=(
const D3D12EditorWindowRenderRuntimeFactory&) = delete;
D3D12EditorWindowRenderRuntimeFactory(D3D12EditorWindowRenderRuntimeFactory&&) = delete;
D3D12EditorWindowRenderRuntimeFactory& operator=(
D3D12EditorWindowRenderRuntimeFactory&&) = delete;
std::unique_ptr<Rendering::Host::EditorWindowRenderRuntime>
CreateWindowRenderRuntime() override;
};
} // namespace XCEngine::UI::Editor::Host

View File

@@ -9,6 +9,7 @@
#include <cstdint>
#include <filesystem>
#include <memory>
#include <string>
namespace XCEngine::UI::Editor::Rendering::Host {
@@ -68,4 +69,11 @@ public:
const std::filesystem::path* captureOutputPath) = 0;
};
class EditorWindowRenderRuntimeFactory {
public:
virtual ~EditorWindowRenderRuntimeFactory() = default;
virtual std::unique_ptr<EditorWindowRenderRuntime> CreateWindowRenderRuntime() = 0;
};
} // namespace XCEngine::UI::Editor::Rendering::Host

View File

@@ -6,6 +6,8 @@
#include "Windowing/Coordinator/EditorWindowLifecycleCoordinator.h"
#include "Windowing/Runtime/EditorWindowRuntimeController.h"
#include <Rendering/Host/EditorWindowRenderRuntime.h>
#include <cmath>
#include <memory>
@@ -30,9 +32,11 @@ int ResolveOuterDimension(float value, int fallback) {
EditorUtilityWindowCoordinator::EditorUtilityWindowCoordinator(
EditorContext& editorContext,
EditorWindowHost& hostRuntime,
Rendering::Host::EditorWindowRenderRuntimeFactory& renderRuntimeFactory,
EditorWindowContentFactory& contentFactory)
: m_hostRuntime(hostRuntime),
m_editorContext(editorContext),
m_renderRuntimeFactory(renderRuntimeFactory),
m_contentFactory(contentFactory) {}
EditorUtilityWindowCoordinator::~EditorUtilityWindowCoordinator() = default;
@@ -129,7 +133,7 @@ bool EditorUtilityWindowCoordinator::TryProcessOpenUtilityWindowRequest(
std::make_unique<EditorWindowRuntimeController>(
m_editorContext,
m_contentFactory.CreateUtilityContentController(*descriptor),
m_hostRuntime.CreateWindowRenderRuntime()),
m_renderRuntimeFactory.CreateWindowRenderRuntime()),
createParams);
if (utilityWindow == nullptr) {
LogRuntimeTrace("utility", "failed to create utility window");

View File

@@ -5,6 +5,10 @@
#include <string_view>
namespace XCEngine::UI::Editor::Rendering::Host {
class EditorWindowRenderRuntimeFactory;
}
namespace XCEngine::UI::Editor::App {
class EditorContext;
@@ -16,6 +20,7 @@ public:
EditorUtilityWindowCoordinator(
EditorContext& editorContext,
EditorWindowHost& hostRuntime,
Rendering::Host::EditorWindowRenderRuntimeFactory& renderRuntimeFactory,
EditorWindowContentFactory& contentFactory);
~EditorUtilityWindowCoordinator();
@@ -32,6 +37,7 @@ private:
EditorWindowHost& m_hostRuntime;
EditorContext& m_editorContext;
Rendering::Host::EditorWindowRenderRuntimeFactory& m_renderRuntimeFactory;
EditorWindowContentFactory& m_contentFactory;
EditorWindowLifecycleCoordinator* m_lifecycleCoordinator = nullptr;
};

View File

@@ -5,6 +5,8 @@
#include "Windowing/Coordinator/EditorWindowLifecycleCoordinator.h"
#include "Windowing/Runtime/EditorWindowRuntimeController.h"
#include <Rendering/Host/EditorWindowRenderRuntime.h>
#include <XCEditor/Docking/UIEditorDockHostTransfer.h>
#include <XCEditor/Windowing/Presentation/EditorWindowPresentationPolicy.h>
#include <XCEditor/Windowing/System/EditorWindowSystem.h>
@@ -93,10 +95,12 @@ EditorWindowWorkspaceCoordinator::EditorWindowWorkspaceCoordinator(
EditorContext& editorContext,
EditorWindowHost& hostRuntime,
EditorWindowSystem& windowSystem,
Rendering::Host::EditorWindowRenderRuntimeFactory& renderRuntimeFactory,
EditorWindowContentFactory& contentFactory)
: m_hostRuntime(hostRuntime),
m_editorContext(editorContext),
m_windowSystem(windowSystem),
m_renderRuntimeFactory(renderRuntimeFactory),
m_contentFactory(contentFactory) {}
EditorWindowWorkspaceCoordinator::~EditorWindowWorkspaceCoordinator() = default;
@@ -372,7 +376,7 @@ bool EditorWindowWorkspaceCoordinator::ApplySynchronizationPlan(
std::make_unique<EditorWindowRuntimeController>(
m_editorContext,
m_contentFactory.CreateWorkspaceContentController(action.create.windowState),
m_hostRuntime.CreateWindowRenderRuntime()),
m_renderRuntimeFactory.CreateWindowRenderRuntime()),
createParams);
if (createdWindow == nullptr) {
for (const ExistingWindowSnapshot& snapshot : existingWindowSnapshots) {

View File

@@ -18,6 +18,10 @@
namespace XCEngine::UI::Editor {
class EditorWindowSystem;
namespace Rendering::Host {
class EditorWindowRenderRuntimeFactory;
}
}
namespace XCEngine::UI::Editor::App {
@@ -32,6 +36,7 @@ public:
EditorContext& editorContext,
EditorWindowHost& hostRuntime,
EditorWindowSystem& windowSystem,
Rendering::Host::EditorWindowRenderRuntimeFactory& renderRuntimeFactory,
EditorWindowContentFactory& contentFactory);
~EditorWindowWorkspaceCoordinator();
@@ -118,6 +123,7 @@ private:
EditorWindowHost& m_hostRuntime;
EditorContext& m_editorContext;
EditorWindowSystem& m_windowSystem;
Rendering::Host::EditorWindowRenderRuntimeFactory& m_renderRuntimeFactory;
EditorWindowContentFactory& m_contentFactory;
EditorWindowLifecycleCoordinator* m_lifecycleCoordinator = nullptr;
GlobalTabDragSession m_globalTabDragSession = {};

View File

@@ -7,6 +7,8 @@
#include "Windowing/Coordinator/EditorWindowWorkspaceCoordinator.h"
#include "Windowing/Runtime/EditorWindowRuntimeController.h"
#include <Rendering/Host/EditorWindowRenderRuntime.h>
#include <XCEditor/Workspace/UIEditorWindowWorkspaceModel.h>
#include <memory>
@@ -18,8 +20,10 @@ namespace XCEngine::UI::Editor::App {
EditorWindowManager::EditorWindowManager(
EditorContext& editorContext,
EditorWindowSystem& windowSystem,
Rendering::Host::EditorWindowRenderRuntimeFactory& renderRuntimeFactory,
EditorWindowHostRuntimeServices& hostRuntime)
: m_editorContext(editorContext)
, m_renderRuntimeFactory(renderRuntimeFactory)
, m_hostRuntime(hostRuntime) {
m_contentFactory = CreateDefaultEditorWindowContentFactory(windowSystem);
m_workspaceCoordinator =
@@ -27,11 +31,13 @@ EditorWindowManager::EditorWindowManager(
m_editorContext,
m_hostRuntime,
windowSystem,
m_renderRuntimeFactory,
*m_contentFactory);
m_utilityCoordinator =
std::make_unique<EditorUtilityWindowCoordinator>(
m_editorContext,
m_hostRuntime,
m_renderRuntimeFactory,
*m_contentFactory);
m_lifecycleCoordinator = std::make_unique<EditorWindowLifecycleCoordinator>(
m_hostRuntime,
@@ -60,7 +66,7 @@ EditorHostWindow* EditorWindowManager::CreateWorkspaceWindow(
std::make_unique<EditorWindowRuntimeController>(
m_editorContext,
m_contentFactory->CreateWorkspaceContentController(windowState),
m_hostRuntime.CreateWindowRenderRuntime()),
m_renderRuntimeFactory.CreateWindowRenderRuntime()),
params);
if (window != nullptr) {
m_workspaceCoordinator->RegisterExistingWindow(*window);
@@ -79,7 +85,7 @@ EditorHostWindow* EditorWindowManager::CreateUtilityWindow(
std::make_unique<EditorWindowRuntimeController>(
m_editorContext,
m_contentFactory->CreateUtilityContentController(descriptor),
m_hostRuntime.CreateWindowRenderRuntime()),
m_renderRuntimeFactory.CreateWindowRenderRuntime()),
params);
if (window != nullptr) {
m_workspaceCoordinator->RegisterExistingWindow(*window);

View File

@@ -15,6 +15,10 @@ namespace XCEngine::UI::Editor {
class EditorWindowSystem;
struct UIEditorWindowWorkspaceState;
namespace Rendering::Host {
class EditorWindowRenderRuntimeFactory;
}
} // namespace XCEngine::UI::Editor
namespace XCEngine::UI::Editor::App {
@@ -33,6 +37,7 @@ public:
EditorWindowManager(
EditorContext& editorContext,
EditorWindowSystem& windowSystem,
Rendering::Host::EditorWindowRenderRuntimeFactory& renderRuntimeFactory,
EditorWindowHostRuntimeServices& hostRuntime);
~EditorWindowManager();
@@ -80,6 +85,7 @@ private:
bool requestSkipNextSteadyStateFrame);
EditorContext& m_editorContext;
Rendering::Host::EditorWindowRenderRuntimeFactory& m_renderRuntimeFactory;
EditorWindowHostRuntimeServices& m_hostRuntime;
std::unique_ptr<EditorWindowContentFactory> m_contentFactory = {};
std::unique_ptr<EditorWindowLifecycleCoordinator> m_lifecycleCoordinator = {};

View File

@@ -26,12 +26,6 @@ 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;
@@ -100,8 +94,6 @@ class EditorWindowHost {
public:
virtual ~EditorWindowHost() = default;
virtual std::unique_ptr<Rendering::Host::EditorWindowRenderRuntime>
CreateWindowRenderRuntime() = 0;
virtual EditorHostWindow* CreateHostWindow(
std::unique_ptr<EditorWindowRuntimeController> runtimeController,
const EditorWindowCreateParams& params) = 0;