From 10b092d46761e095bd5e06b03e342ab92d4478d4 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Mon, 20 Apr 2026 12:47:25 +0800 Subject: [PATCH] refactor(srp): add renderer backend registry seam --- ...注册与多Key接缝计划_完成归档_2026-04-20.md | 119 ++++++++++++++++++ .../Internal/RenderPipelineFactory.cpp | 107 ++++++++++++++-- .../Internal/RenderPipelineFactory.h | 10 +- managed/GameScripts/RenderPipelineApiProbe.cs | 26 ++++ .../unit/test_camera_scene_renderer.cpp | 57 +++++++++ tests/scripting/test_mono_script_runtime.cpp | 72 +++++++++++ 6 files changed, 382 insertions(+), 9 deletions(-) create mode 100644 docs/used/SRP_RendererBackend注册与多Key接缝计划_完成归档_2026-04-20.md diff --git a/docs/used/SRP_RendererBackend注册与多Key接缝计划_完成归档_2026-04-20.md b/docs/used/SRP_RendererBackend注册与多Key接缝计划_完成归档_2026-04-20.md new file mode 100644 index 00000000..b61f97d2 --- /dev/null +++ b/docs/used/SRP_RendererBackend注册与多Key接缝计划_完成归档_2026-04-20.md @@ -0,0 +1,119 @@ +# SRP RendererBackend 注册与多 Key 接缝计划 2026-04-20 + +## 1. 阶段目标 + +上一阶段已经把: + +`ScriptableRenderPipelineAsset -> ManagedRenderPipelineAssetRuntime` + +这一层的失效与重建接缝补齐了。 + +当前新的主矛盾不在生命周期,而在 backend ownership: + +1. managed SRP/Universal 已经能声明 backend key +2. native factory 仍然基本停留在单 key 硬编码 +3. future SRP/URP 想挂更多 native backend 时,没有正式 registry + +这一阶段的目标,就是把: + +`managed backend key -> native renderer backend asset` + +从临时硬编码改成正式 registry seam。 + +--- + +## 2. 当前问题 + +### 2.1 native factory 仍是单点硬编码 + +当前 `CreatePipelineRendererAssetByKey()` 仍然是: + +1. 只认 `BuiltinForward` +2. 直接 `if` 分支返回 + +这意味着后续每多一个 backend,都要继续往 factory 里堆硬编码。 + +### 2.2 多 key 语义还没有真正被锁住 + +managed 侧虽然已经允许不同 asset / renderer data 返回不同 key, +但 native 侧并没有一套正式的“注册、查询、失败回退”模型。 + +### 2.3 现有测试只锁住单 key 与 unknown fallback + +当前已经锁住: + +1. `BuiltinForward` 能解析 +2. missing key 会 fallback + +但还没有锁住: + +1. 可以注册额外 key +2. managed asset 返回额外 key 之后,native runtime 仍能正常解析 backend asset + +--- + +## 3. 实施方案 + +### 3.1 在 native factory 引入正式 backend registry + +新增 registry 职责: + +1. 默认注册 builtin backend +2. 支持额外 key 注册 +3. 支持通过 key 查询 backend asset factory +4. unknown key 保持安全失败 + +### 3.2 builtin forward 改为 registry 默认项 + +`BuiltinForward` 不再作为 `if` 特判逻辑散落在 factory 主流程里, +而是作为 registry 初始化时的默认注册项。 + +### 3.3 用测试 alias key 锁住多 key seam + +本阶段不强行引入第二套真实 renderer backend, +而是通过测试注册一个 alias key,映射到 builtin forward asset, +用来验证: + +1. registry 真的支持额外 key +2. managed runtime 真的沿 key seam 解析 native backend + +--- + +## 4. 实施步骤 + +### Step 1:补 native registry API + +1. 修改 `engine/src/Rendering/Internal/RenderPipelineFactory.h/.cpp` +2. 引入 backend asset factory registry +3. builtin forward 作为默认注册项 + +### Step 2:补 unit tests + +1. 修改 `tests/Rendering/unit/test_camera_scene_renderer.cpp` +2. 锁住 custom alias key 的注册、解析、native scene renderer 创建 + +### Step 3:补 scripting probe 与 runtime test + +1. 修改 `managed/GameScripts/RenderPipelineApiProbe.cs` +2. 修改 `tests/scripting/test_mono_script_runtime.cpp` +3. 锁住 managed asset 返回 alias key 后,runtime 仍能解析 builtin forward backend + +### Step 4:验证与收口 + +1. 编译 `XCEditor` +2. 运行 `rendering_unit_tests` +3. 运行 `scripting_tests` +4. 旧 editor 冒烟 10s +5. 阶段完成后归档 plan、提交推送 + +--- + +## 5. 验收标准 + +完成后应满足: + +1. native backend key 解析走正式 registry,而不是单点 `if` +2. `BuiltinForward` 作为默认注册项保留 +3. 可以额外注册 alias key 并解析出 backend asset +4. managed asset 返回 alias key 时,native runtime 仍能正常工作 +5. 编译、单测、冒烟全部通过 diff --git a/engine/src/Rendering/Internal/RenderPipelineFactory.cpp b/engine/src/Rendering/Internal/RenderPipelineFactory.cpp index cb2f3072..21dd5aaa 100644 --- a/engine/src/Rendering/Internal/RenderPipelineFactory.cpp +++ b/engine/src/Rendering/Internal/RenderPipelineFactory.cpp @@ -4,12 +4,55 @@ #include "Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h" #include "Rendering/Pipelines/ScriptableRenderPipelineHost.h" +#include +#include +#include + namespace XCEngine { namespace Rendering { namespace Internal { namespace { +std::shared_ptr +CreateBuiltinForwardPipelineRendererAsset() { + static const std::shared_ptr + s_builtinForwardPipelineAsset = + std::make_shared(); + return s_builtinForwardPipelineAsset; +} + +using PipelineRendererAssetRegistry = + std::unordered_map; + +PipelineRendererAssetRegistry& GetPipelineRendererAssetRegistry() { + static PipelineRendererAssetRegistry registry = {}; + return registry; +} + +std::unordered_set& GetBuiltinPipelineRendererAssetKeys() { + static std::unordered_set builtinKeys = {}; + return builtinKeys; +} + +std::mutex& GetPipelineRendererAssetRegistryMutex() { + static std::mutex mutex; + return mutex; +} + +void EnsureBuiltinPipelineRendererAssetRegistryInitialized() { + static const bool initialized = []() { + PipelineRendererAssetRegistry& registry = + GetPipelineRendererAssetRegistry(); + registry.emplace( + "BuiltinForward", + &CreateBuiltinForwardPipelineRendererAsset); + GetBuiltinPipelineRendererAssetKeys().insert("BuiltinForward"); + return true; + }(); + (void)initialized; +} + std::unique_ptr TryCreateNativeSceneRendererFromAsset( const std::shared_ptr& asset) { if (asset == nullptr) { @@ -41,16 +84,64 @@ std::shared_ptr CreateFallbackRenderPipelineAsset() { return std::make_shared(); } -std::shared_ptr CreatePipelineRendererAssetByKey( - const std::string& key) { - if (key == "BuiltinForward") { - static const std::shared_ptr - s_builtinForwardPipelineAsset = - std::make_shared(); - return s_builtinForwardPipelineAsset; +bool RegisterPipelineRendererAssetFactory( + const std::string& key, + PipelineRendererAssetFactory factory) { + if (key.empty() || !factory) { + return false; } - return nullptr; + EnsureBuiltinPipelineRendererAssetRegistryInitialized(); + + std::lock_guard lock( + GetPipelineRendererAssetRegistryMutex()); + PipelineRendererAssetRegistry& registry = + GetPipelineRendererAssetRegistry(); + if (registry.find(key) != registry.end()) { + return false; + } + + registry.emplace(key, std::move(factory)); + return true; +} + +bool UnregisterPipelineRendererAssetFactory(const std::string& key) { + if (key.empty()) { + return false; + } + + EnsureBuiltinPipelineRendererAssetRegistryInitialized(); + + std::lock_guard lock( + GetPipelineRendererAssetRegistryMutex()); + if (GetBuiltinPipelineRendererAssetKeys().find(key) != + GetBuiltinPipelineRendererAssetKeys().end()) { + return false; + } + + PipelineRendererAssetRegistry& registry = + GetPipelineRendererAssetRegistry(); + return registry.erase(key) != 0u; +} + +std::shared_ptr CreatePipelineRendererAssetByKey( + const std::string& key) { + if (key.empty()) { + return nullptr; + } + + EnsureBuiltinPipelineRendererAssetRegistryInitialized(); + + std::lock_guard lock( + GetPipelineRendererAssetRegistryMutex()); + const PipelineRendererAssetRegistry& registry = + GetPipelineRendererAssetRegistry(); + const auto it = registry.find(key); + if (it == registry.end() || !it->second) { + return nullptr; + } + + return it->second(); } std::shared_ptr ResolveRenderPipelineAssetOrDefault( diff --git a/engine/src/Rendering/Internal/RenderPipelineFactory.h b/engine/src/Rendering/Internal/RenderPipelineFactory.h index b2ad9c9f..b6ab6456 100644 --- a/engine/src/Rendering/Internal/RenderPipelineFactory.h +++ b/engine/src/Rendering/Internal/RenderPipelineFactory.h @@ -1,7 +1,8 @@ #pragma once -#include +#include #include +#include namespace XCEngine { namespace Rendering { @@ -12,8 +13,15 @@ class RenderPipelineAsset; namespace Internal { +using PipelineRendererAssetFactory = + std::function()>; + std::shared_ptr CreateConfiguredRenderPipelineAsset(); std::shared_ptr CreateFallbackRenderPipelineAsset(); +bool RegisterPipelineRendererAssetFactory( + const std::string& key, + PipelineRendererAssetFactory factory); +bool UnregisterPipelineRendererAssetFactory(const std::string& key); std::shared_ptr CreatePipelineRendererAssetByKey( const std::string& key); diff --git a/managed/GameScripts/RenderPipelineApiProbe.cs b/managed/GameScripts/RenderPipelineApiProbe.cs index c73d3b0b..942d31fa 100644 --- a/managed/GameScripts/RenderPipelineApiProbe.cs +++ b/managed/GameScripts/RenderPipelineApiProbe.cs @@ -878,6 +878,20 @@ namespace Gameplay } } + internal sealed class ManagedBuiltinForwardAliasProbeRendererData + : ProbeRendererData + { + protected override ScriptableRenderer CreateProbeRenderer() + { + return new ManagedRenderPipelineProbe(); + } + + protected override string GetPipelineRendererAssetKey() + { + return "BuiltinForwardAlias"; + } + } + internal sealed class ManagedRendererReuseProbeRendererData : ProbeRendererData { @@ -1235,6 +1249,18 @@ namespace Gameplay } } + public sealed class ManagedBuiltinForwardAliasRenderPipelineProbeAsset + : RendererBackedRenderPipelineAsset + { + public ManagedBuiltinForwardAliasRenderPipelineProbeAsset() + { + rendererDataList = new ScriptableRendererData[] + { + new ManagedBuiltinForwardAliasProbeRendererData() + }; + } + } + public sealed class ManagedUniversalRenderPipelineProbeAsset : UniversalRenderPipelineAsset { diff --git a/tests/Rendering/unit/test_camera_scene_renderer.cpp b/tests/Rendering/unit/test_camera_scene_renderer.cpp index dec476d3..2ccede26 100644 --- a/tests/Rendering/unit/test_camera_scene_renderer.cpp +++ b/tests/Rendering/unit/test_camera_scene_renderer.cpp @@ -50,6 +50,38 @@ CameraFrameStageSourceBinding ResolveStageSourceBinding( return ResolveCameraFrameStageSourceBinding(stage, plan); } +std::shared_ptr +CreateBuiltinForwardPipelineRendererAssetForTest() { + return std::make_shared(); +} + +class ScopedPipelineRendererAssetFactoryRegistration final { +public: + ScopedPipelineRendererAssetFactoryRegistration( + std::string key, + Internal::PipelineRendererAssetFactory factory) + : m_key(std::move(key)) + , m_registered( + Internal::RegisterPipelineRendererAssetFactory( + m_key, + std::move(factory))) { + } + + ~ScopedPipelineRendererAssetFactoryRegistration() { + if (m_registered) { + (void)Internal::UnregisterPipelineRendererAssetFactory(m_key); + } + } + + bool IsRegistered() const { + return m_registered; + } + +private: + std::string m_key; + bool m_registered = false; +}; + struct MockPipelineState { int initializeCalls = 0; int shutdownCalls = 0; @@ -4543,6 +4575,31 @@ TEST( Pipelines::ClearManagedRenderPipelineBridge(); } +TEST( + RenderPipelineFactory_Test, + RegisteredAliasKeyResolvesPipelineRendererAssetAndNativeSceneRenderer) { + ScopedPipelineRendererAssetFactoryRegistration registration( + "BuiltinForwardAlias", + &CreateBuiltinForwardPipelineRendererAssetForTest); + ASSERT_TRUE(registration.IsRegistered()); + + std::shared_ptr rendererAsset = + Internal::CreatePipelineRendererAssetByKey("BuiltinForwardAlias"); + ASSERT_NE(rendererAsset, nullptr); + + std::shared_ptr resolvedAsset = nullptr; + std::unique_ptr sceneRenderer = + Internal::CreateNativeSceneRendererFromAsset( + rendererAsset, + &resolvedAsset); + + ASSERT_NE(sceneRenderer, nullptr); + EXPECT_EQ(resolvedAsset, rendererAsset); + EXPECT_NE( + dynamic_cast(sceneRenderer.get()), + nullptr); +} + TEST( ScriptableRenderPipelineHost_Test, FallsBackToRendererWhenStageRecorderDeclinesRecording) { diff --git a/tests/scripting/test_mono_script_runtime.cpp b/tests/scripting/test_mono_script_runtime.cpp index 68d4a4be..95a1e021 100644 --- a/tests/scripting/test_mono_script_runtime.cpp +++ b/tests/scripting/test_mono_script_runtime.cpp @@ -45,6 +45,8 @@ #include #include +#include "Rendering/Internal/RenderPipelineFactory.h" + #include #include #include @@ -75,6 +77,40 @@ void ExpectVector4Near(const XCEngine::Math::Vector4& actual, const XCEngine::Ma EXPECT_NEAR(actual.w, expected.w, tolerance); } +std::shared_ptr +CreateBuiltinForwardPipelineRendererAssetForTest() { + return std::make_shared< + XCEngine::Rendering::Pipelines::BuiltinForwardPipelineAsset>(); +} + +class ScopedPipelineRendererAssetFactoryRegistration final { +public: + ScopedPipelineRendererAssetFactoryRegistration( + std::string key, + XCEngine::Rendering::Internal::PipelineRendererAssetFactory factory) + : m_key(std::move(key)) + , m_registered( + XCEngine::Rendering::Internal::RegisterPipelineRendererAssetFactory( + m_key, + std::move(factory))) { + } + + ~ScopedPipelineRendererAssetFactoryRegistration() { + if (m_registered) { + (void)XCEngine::Rendering::Internal:: + UnregisterPipelineRendererAssetFactory(m_key); + } + } + + bool IsRegistered() const { + return m_registered; + } + +private: + std::string m_key; + bool m_registered = false; +}; + class CapturingLogSink final : public XCEngine::Debug::ILogSink { public: void Log(const XCEngine::Debug::LogEntry& entry) override { @@ -2950,6 +2986,42 @@ TEST_F( nullptr); } +TEST_F( + MonoScriptRuntimeTest, + ManagedRenderPipelineBridgeResolvesRegisteredAliasBackendKey) { + ScopedPipelineRendererAssetFactoryRegistration registration( + "BuiltinForwardAlias", + &CreateBuiltinForwardPipelineRendererAssetForTest); + ASSERT_TRUE(registration.IsRegistered()); + + const auto bridge = + XCEngine::Rendering::Pipelines::GetManagedRenderPipelineBridge(); + ASSERT_NE(bridge, nullptr); + + const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor + descriptor = { + "GameScripts", + "Gameplay", + "ManagedBuiltinForwardAliasRenderPipelineProbeAsset" + }; + + std::shared_ptr + assetRuntime = bridge->CreateAssetRuntime(descriptor); + ASSERT_NE(assetRuntime, nullptr); + + const std::shared_ptr + rendererAsset = assetRuntime->GetPipelineRendererAsset(); + ASSERT_NE(rendererAsset, nullptr); + + std::unique_ptr pipeline = + rendererAsset->CreatePipeline(); + ASSERT_NE(pipeline, nullptr); + EXPECT_NE( + dynamic_cast( + pipeline.get()), + nullptr); +} + TEST_F( MonoScriptRuntimeTest, ManagedRenderPipelineBridgeUsesDefaultRendererSelectionForNativeBackendAsset) {