refactor(srp): add renderer backend registry seam
This commit is contained in:
119
docs/used/SRP_RendererBackend注册与多Key接缝计划_完成归档_2026-04-20.md
Normal file
119
docs/used/SRP_RendererBackend注册与多Key接缝计划_完成归档_2026-04-20.md
Normal file
@@ -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. 编译、单测、冒烟全部通过
|
||||
@@ -4,12 +4,55 @@
|
||||
#include "Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h"
|
||||
#include "Rendering/Pipelines/ScriptableRenderPipelineHost.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
namespace Internal {
|
||||
|
||||
namespace {
|
||||
|
||||
std::shared_ptr<const RenderPipelineAsset>
|
||||
CreateBuiltinForwardPipelineRendererAsset() {
|
||||
static const std::shared_ptr<const RenderPipelineAsset>
|
||||
s_builtinForwardPipelineAsset =
|
||||
std::make_shared<Pipelines::BuiltinForwardPipelineAsset>();
|
||||
return s_builtinForwardPipelineAsset;
|
||||
}
|
||||
|
||||
using PipelineRendererAssetRegistry =
|
||||
std::unordered_map<std::string, PipelineRendererAssetFactory>;
|
||||
|
||||
PipelineRendererAssetRegistry& GetPipelineRendererAssetRegistry() {
|
||||
static PipelineRendererAssetRegistry registry = {};
|
||||
return registry;
|
||||
}
|
||||
|
||||
std::unordered_set<std::string>& GetBuiltinPipelineRendererAssetKeys() {
|
||||
static std::unordered_set<std::string> 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<NativeSceneRenderer> TryCreateNativeSceneRendererFromAsset(
|
||||
const std::shared_ptr<const RenderPipelineAsset>& asset) {
|
||||
if (asset == nullptr) {
|
||||
@@ -41,16 +84,64 @@ std::shared_ptr<const RenderPipelineAsset> CreateFallbackRenderPipelineAsset() {
|
||||
return std::make_shared<Pipelines::ScriptableRenderPipelineHostAsset>();
|
||||
}
|
||||
|
||||
std::shared_ptr<const RenderPipelineAsset> CreatePipelineRendererAssetByKey(
|
||||
const std::string& key) {
|
||||
if (key == "BuiltinForward") {
|
||||
static const std::shared_ptr<const RenderPipelineAsset>
|
||||
s_builtinForwardPipelineAsset =
|
||||
std::make_shared<Pipelines::BuiltinForwardPipelineAsset>();
|
||||
return s_builtinForwardPipelineAsset;
|
||||
bool RegisterPipelineRendererAssetFactory(
|
||||
const std::string& key,
|
||||
PipelineRendererAssetFactory factory) {
|
||||
if (key.empty() || !factory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
EnsureBuiltinPipelineRendererAssetRegistryInitialized();
|
||||
|
||||
std::lock_guard<std::mutex> 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<std::mutex> lock(
|
||||
GetPipelineRendererAssetRegistryMutex());
|
||||
if (GetBuiltinPipelineRendererAssetKeys().find(key) !=
|
||||
GetBuiltinPipelineRendererAssetKeys().end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PipelineRendererAssetRegistry& registry =
|
||||
GetPipelineRendererAssetRegistry();
|
||||
return registry.erase(key) != 0u;
|
||||
}
|
||||
|
||||
std::shared_ptr<const RenderPipelineAsset> CreatePipelineRendererAssetByKey(
|
||||
const std::string& key) {
|
||||
if (key.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
EnsureBuiltinPipelineRendererAssetRegistryInitialized();
|
||||
|
||||
std::lock_guard<std::mutex> 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<const RenderPipelineAsset> ResolveRenderPipelineAssetOrDefault(
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
@@ -12,8 +13,15 @@ class RenderPipelineAsset;
|
||||
|
||||
namespace Internal {
|
||||
|
||||
using PipelineRendererAssetFactory =
|
||||
std::function<std::shared_ptr<const RenderPipelineAsset>()>;
|
||||
|
||||
std::shared_ptr<const RenderPipelineAsset> CreateConfiguredRenderPipelineAsset();
|
||||
std::shared_ptr<const RenderPipelineAsset> CreateFallbackRenderPipelineAsset();
|
||||
bool RegisterPipelineRendererAssetFactory(
|
||||
const std::string& key,
|
||||
PipelineRendererAssetFactory factory);
|
||||
bool UnregisterPipelineRendererAssetFactory(const std::string& key);
|
||||
std::shared_ptr<const RenderPipelineAsset> CreatePipelineRendererAssetByKey(
|
||||
const std::string& key);
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -50,6 +50,38 @@ CameraFrameStageSourceBinding ResolveStageSourceBinding(
|
||||
return ResolveCameraFrameStageSourceBinding(stage, plan);
|
||||
}
|
||||
|
||||
std::shared_ptr<const RenderPipelineAsset>
|
||||
CreateBuiltinForwardPipelineRendererAssetForTest() {
|
||||
return std::make_shared<Pipelines::BuiltinForwardPipelineAsset>();
|
||||
}
|
||||
|
||||
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<const RenderPipelineAsset> rendererAsset =
|
||||
Internal::CreatePipelineRendererAssetByKey("BuiltinForwardAlias");
|
||||
ASSERT_NE(rendererAsset, nullptr);
|
||||
|
||||
std::shared_ptr<const RenderPipelineAsset> resolvedAsset = nullptr;
|
||||
std::unique_ptr<NativeSceneRenderer> sceneRenderer =
|
||||
Internal::CreateNativeSceneRendererFromAsset(
|
||||
rendererAsset,
|
||||
&resolvedAsset);
|
||||
|
||||
ASSERT_NE(sceneRenderer, nullptr);
|
||||
EXPECT_EQ(resolvedAsset, rendererAsset);
|
||||
EXPECT_NE(
|
||||
dynamic_cast<Pipelines::BuiltinForwardPipeline*>(sceneRenderer.get()),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
TEST(
|
||||
ScriptableRenderPipelineHost_Test,
|
||||
FallsBackToRendererWhenStageRecorderDeclinesRecording) {
|
||||
|
||||
@@ -45,6 +45,8 @@
|
||||
#include <XCEngine/Scripting/ScriptComponent.h>
|
||||
#include <XCEngine/Scripting/ScriptEngine.h>
|
||||
|
||||
#include "Rendering/Internal/RenderPipelineFactory.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
@@ -75,6 +77,40 @@ void ExpectVector4Near(const XCEngine::Math::Vector4& actual, const XCEngine::Ma
|
||||
EXPECT_NEAR(actual.w, expected.w, tolerance);
|
||||
}
|
||||
|
||||
std::shared_ptr<const XCEngine::Rendering::RenderPipelineAsset>
|
||||
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<const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetRuntime>
|
||||
assetRuntime = bridge->CreateAssetRuntime(descriptor);
|
||||
ASSERT_NE(assetRuntime, nullptr);
|
||||
|
||||
const std::shared_ptr<const XCEngine::Rendering::RenderPipelineAsset>
|
||||
rendererAsset = assetRuntime->GetPipelineRendererAsset();
|
||||
ASSERT_NE(rendererAsset, nullptr);
|
||||
|
||||
std::unique_ptr<XCEngine::Rendering::RenderPipeline> pipeline =
|
||||
rendererAsset->CreatePipeline();
|
||||
ASSERT_NE(pipeline, nullptr);
|
||||
EXPECT_NE(
|
||||
dynamic_cast<XCEngine::Rendering::Pipelines::BuiltinForwardPipeline*>(
|
||||
pipeline.get()),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
MonoScriptRuntimeTest,
|
||||
ManagedRenderPipelineBridgeUsesDefaultRendererSelectionForNativeBackendAsset) {
|
||||
|
||||
Reference in New Issue
Block a user