From beaf5809d52b6f3d0f439eb5f16ffa0b0322634c Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Mon, 20 Apr 2026 00:44:09 +0800 Subject: [PATCH] refactor(srp): unify managed pipeline instance ownership Move shared managed ScriptableRenderPipeline instance ownership into MonoManagedRenderPipelineAssetRuntime. Make stage recorders borrow the runtime-owned pipeline instead of creating and destroying private handles. Add a regression test that locks one CreatePipeline call across multiple stage recorders. --- ...ine实例归属统一计划_完成归档_2026-04-20.md | 137 ++++++++++++++++++ .../src/Scripting/Mono/MonoScriptRuntime.cpp | 88 ++++++----- managed/GameScripts/RenderPipelineApiProbe.cs | 17 +++ tests/scripting/test_mono_script_runtime.cpp | 67 +++++++++ 4 files changed, 272 insertions(+), 37 deletions(-) create mode 100644 docs/used/SRP_ManagedPipeline实例归属统一计划_完成归档_2026-04-20.md diff --git a/docs/used/SRP_ManagedPipeline实例归属统一计划_完成归档_2026-04-20.md b/docs/used/SRP_ManagedPipeline实例归属统一计划_完成归档_2026-04-20.md new file mode 100644 index 00000000..bfeb9b6c --- /dev/null +++ b/docs/used/SRP_ManagedPipeline实例归属统一计划_完成归档_2026-04-20.md @@ -0,0 +1,137 @@ +# SRP ManagedPipeline实例归属统一计划 2026-04-20 + +## 1. 阶段目标 + +当前 Universal/managed SRP 主线里还有一个关键 ownership 没收干净: + +1. `ManagedRenderPipelineAssetRuntime` 表示的是“一个 active managed pipeline asset” +2. 但真正的 `ScriptableRenderPipeline` managed instance 仍然是每个 stage recorder 自己去创建 +3. 这不符合 Unity 风格的 `RenderPipelineAsset -> one active RenderPipeline instance` + +本阶段目标就是把这个 ownership 统一回来: + +`MonoManagedRenderPipelineAssetRuntime` + +负责缓存并持有 managed pipeline instance, + +`MonoManagedRenderPipelineStageRecorder` + +只借用,不再各自创建和销毁。 + +--- + +## 2. 当前问题 + +### 2.1 pipeline instance 被 recorder 私有化了 + +现在 recorder 内部自己维护: + +1. `m_pipelineHandle` +2. `m_pipelineCreationAttempted` +3. `ReleaseManagedObjects()` 时销毁 pipeline object + +这意味着: + +1. 同一个 asset runtime 可以派生多个 managed pipeline instance +2. `CreatePipeline()` 调用次数取决于 recorder 数量,而不是 asset 生命周期 +3. pipeline lifecycle 没有真正挂在 asset/runtime 上 + +### 2.2 Unity 风格的 active pipeline 语义还没成立 + +Unity 的思路更接近: + +1. `RenderPipelineAsset` 决定并创建 pipeline +2. active pipeline 生命周期挂在 asset/runtime 这一侧 +3. 录制器、上下文、渲染阶段都围着这一个 pipeline 工作 + +我们现在虽然有 `ManagedRenderPipelineAssetRuntime`,但它还没真正拥有 pipeline instance。 + +### 2.3 当前 create count 还可能被 recorder 数量放大 + +前几步已经收了: + +1. backend ownership +2. renderer data ownership +3. renderer instance cache + +如果 pipeline instance ownership 不收,后面依然会留下: + +`asset -> runtime -> recorder -> pipeline` + +这条倒挂关系。 + +--- + +## 3. 本阶段方案 + +### 方案核心 + +把 managed pipeline instance cache 从: + +`MonoManagedRenderPipelineStageRecorder` + +上移到: + +`MonoManagedRenderPipelineAssetRuntime` + +形成: + +1. runtime 负责确保 pipeline instance 存在 +2. runtime 负责释放 pipeline instance +3. recorder 只查询/借用 runtime 持有的 pipeline object + +### 预期结构 + +1. `MonoManagedRenderPipelineAssetRuntime` + - 持有 shared managed pipeline handle + - 负责首次创建和最终释放 +2. `MonoManagedRenderPipelineStageRecorder` + - 不再拥有/销毁 pipeline handle + - 只缓存 method 指针和录制期状态 + +--- + +## 4. 实施步骤 + +### Step 1:把 pipeline handle/cache 上移到 asset runtime + +目标: + +1. runtime 增加 shared pipeline handle/cache +2. `CreateManagedPipeline()` 改成 shared semantics +3. `ReleaseManagedAsset()` 一并释放 shared pipeline + +### Step 2:清理 recorder 自己的 pipeline ownership + +目标: + +1. 移除 recorder 的 pipeline handle owner 角色 +2. `Shutdown()` 不再销毁 shared pipeline +3. `SupportsStageRenderGraph()` / `RecordStageRenderGraph()` 通过 runtime 借用 pipeline object + +### Step 3:补回归测试 + +目标: + +1. 同一个 managed asset runtime 创建多个 recorder 时,`CreatePipeline()` 只调用一次 +2. 原有 main scene / post process / fallback 语义不回归 + +### Step 4:验证与收口 + +目标: + +1. 编译 `rendering_unit_tests`、`scripting_tests`、`XCEditor` +2. 运行相关测试 +3. 运行旧版 `XCEditor` 10 秒冒烟并验证新 `SceneReady` +4. 归档 plan、提交、推送 + +--- + +## 5. 验收标准 + +本阶段完成后应满足: + +1. managed pipeline instance ownership 归位到 asset runtime +2. recorder 不再各自创建/销毁 pipeline object +3. 同 asset runtime 多 recorder 不会放大 `CreatePipeline()` 调用次数 +4. 当前 SRP 结构更接近 Unity 的 active pipeline 生命周期模型 diff --git a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp index cea9881b..956c5fe2 100644 --- a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp +++ b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp @@ -731,10 +731,11 @@ public: m_runtime->m_initialized; } - bool CreateManagedPipeline(uint32_t& outPipelineHandle) const; + bool AcquireManagedPipelineHandle(uint32_t& outPipelineHandle) const; private: bool EnsureManagedAsset() const; + void ReleaseManagedPipeline() const; void ReleaseManagedAsset() const; MonoObject* GetManagedAssetObject() const; MonoMethod* ResolveCreatePipelineMethod(MonoObject* assetObject) const; @@ -755,6 +756,8 @@ private: mutable MonoMethod* m_getPipelineRendererAssetKeyMethod = nullptr; mutable bool m_ownsManagedAssetHandle = false; mutable bool m_assetCreationAttempted = false; + mutable uint32_t m_pipelineHandle = 0; + mutable bool m_pipelineCreationAttempted = false; mutable bool m_pipelineRendererAssetResolved = false; mutable std::shared_ptr m_pipelineRendererAsset = nullptr; @@ -777,7 +780,7 @@ public: } bool Initialize(const Rendering::RenderContext&) override { - return EnsureManagedPipeline(); + return GetManagedPipelineObject() != nullptr; } void Shutdown() override { @@ -791,10 +794,8 @@ public: if (m_ownedSceneRenderer != nullptr) { m_ownedSceneRenderer->Shutdown(); } - ReleaseManagedObjects(); m_supportsStageMethod = nullptr; m_recordStageMethod = nullptr; - m_pipelineCreationAttempted = false; m_boundSceneRenderer = nullptr; } @@ -806,12 +807,11 @@ public: bool SupportsStageRenderGraph(Rendering::CameraFrameStage stage) const override { if (!SupportsManagedRenderPipelineStageGraphRecording(stage) || - !EnsureManagedPipeline()) { + !IsRuntimeAlive()) { return false; } - MonoObject* const pipelineObject = - m_runtime->GetManagedObject(m_pipelineHandle); + MonoObject* const pipelineObject = GetManagedPipelineObject(); MonoMethod* const method = ResolveSupportsStageMethod(pipelineObject); if (!pipelineObject || !method) { return false; @@ -834,12 +834,11 @@ public: bool RecordStageRenderGraph( const Rendering::RenderPipelineStageRenderGraphContext& context) override { - if (!EnsureManagedPipeline()) { + if (!IsRuntimeAlive()) { return false; } - MonoObject* const pipelineObject = - m_runtime->GetManagedObject(m_pipelineHandle); + MonoObject* const pipelineObject = GetManagedPipelineObject(); MonoMethod* const method = ResolveRecordStageMethod(pipelineObject); if (!pipelineObject || !method) { return false; @@ -895,30 +894,18 @@ private: m_runtime != nullptr; } - bool EnsureManagedPipeline() const { - if (m_pipelineHandle != 0) { - return true; - } - if (m_pipelineCreationAttempted || !IsRuntimeAlive()) { - return false; - } - - m_pipelineCreationAttempted = true; - - return m_assetRuntime->CreateManagedPipeline(m_pipelineHandle) && - m_pipelineHandle != 0; - } - - void ReleaseManagedObjects() { + MonoObject* GetManagedPipelineObject() const { if (!IsRuntimeAlive()) { - m_pipelineHandle = 0; - return; + return nullptr; } - if (m_pipelineHandle != 0) { - m_runtime->DestroyExternalManagedObject(m_pipelineHandle); - m_pipelineHandle = 0; + uint32_t pipelineHandle = 0; + if (!m_assetRuntime->AcquireManagedPipelineHandle(pipelineHandle) || + pipelineHandle == 0) { + return nullptr; } + + return m_runtime->GetManagedObject(pipelineHandle); } MonoMethod* ResolveSupportsStageMethod(MonoObject* pipelineObject) const { @@ -998,10 +985,8 @@ private: std::shared_ptr m_assetRuntime; MonoScriptRuntime* m_runtime = nullptr; - mutable uint32_t m_pipelineHandle = 0; mutable MonoMethod* m_supportsStageMethod = nullptr; mutable MonoMethod* m_recordStageMethod = nullptr; - mutable bool m_pipelineCreationAttempted = false; std::vector> m_fullscreenPassPool = {}; Rendering::NativeSceneRenderer* m_boundSceneRenderer = nullptr; std::unique_ptr m_ownedSceneRenderer = @@ -1131,17 +1116,28 @@ MonoManagedRenderPipelineAssetRuntime::GetPipelineRendererAsset() const { return m_pipelineRendererAsset; } -bool MonoManagedRenderPipelineAssetRuntime::CreateManagedPipeline( +bool MonoManagedRenderPipelineAssetRuntime::AcquireManagedPipelineHandle( uint32_t& outPipelineHandle) const { - outPipelineHandle = 0; - if (!EnsureManagedAsset()) { + if (m_pipelineHandle != 0) { + outPipelineHandle = m_pipelineHandle; + return true; + } + if (m_pipelineCreationAttempted) { + outPipelineHandle = 0; return false; } + if (!EnsureManagedAsset()) { + outPipelineHandle = 0; + return false; + } + + m_pipelineCreationAttempted = true; MonoObject* const assetObject = GetManagedAssetObject(); MonoMethod* const createPipelineMethod = ResolveCreatePipelineMethod(assetObject); if (assetObject == nullptr || createPipelineMethod == nullptr) { + outPipelineHandle = 0; return false; } @@ -1152,6 +1148,7 @@ bool MonoManagedRenderPipelineAssetRuntime::CreateManagedPipeline( nullptr, &pipelineObject) || pipelineObject == nullptr) { + outPipelineHandle = 0; return false; } @@ -1161,11 +1158,13 @@ bool MonoManagedRenderPipelineAssetRuntime::CreateManagedPipeline( m_runtime->SetError( "Managed render pipeline asset returned a non-ScriptableRenderPipeline instance: " + m_descriptor.GetFullName() + "."); + outPipelineHandle = 0; return false; } - outPipelineHandle = m_runtime->RetainExternalManagedObject(pipelineObject); - return outPipelineHandle != 0; + m_pipelineHandle = m_runtime->RetainExternalManagedObject(pipelineObject); + outPipelineHandle = m_pipelineHandle; + return m_pipelineHandle != 0; } bool MonoManagedRenderPipelineAssetRuntime::EnsureManagedAsset() const { @@ -1232,7 +1231,22 @@ bool MonoManagedRenderPipelineAssetRuntime::EnsureManagedAsset() const { return m_ownsManagedAssetHandle; } +void MonoManagedRenderPipelineAssetRuntime::ReleaseManagedPipeline() const { + m_pipelineCreationAttempted = false; + + if (!IsRuntimeAlive()) { + m_pipelineHandle = 0; + return; + } + + if (m_pipelineHandle != 0) { + m_runtime->DestroyExternalManagedObject(m_pipelineHandle); + m_pipelineHandle = 0; + } +} + void MonoManagedRenderPipelineAssetRuntime::ReleaseManagedAsset() const { + ReleaseManagedPipeline(); m_createPipelineMethod = nullptr; m_configureCameraRenderRequestMethod = nullptr; m_getDefaultFinalColorSettingsMethod = nullptr; diff --git a/managed/GameScripts/RenderPipelineApiProbe.cs b/managed/GameScripts/RenderPipelineApiProbe.cs index 73480a9a..704e04a7 100644 --- a/managed/GameScripts/RenderPipelineApiProbe.cs +++ b/managed/GameScripts/RenderPipelineApiProbe.cs @@ -1306,6 +1306,23 @@ namespace Gameplay } } + public sealed class ManagedRenderPipelineCreateCountObservationProbe + : MonoBehaviour + { + public int ObservedCreatePipelineCallCount; + + public void Start() + { + ManagedRenderPipelineProbeAsset.CreatePipelineCallCount = 0; + } + + public void Update() + { + ObservedCreatePipelineCallCount = + ManagedRenderPipelineProbeAsset.CreatePipelineCallCount; + } + } + public sealed class ScriptCoreUniversalRenderPipelineSelectionProbe : MonoBehaviour { diff --git a/tests/scripting/test_mono_script_runtime.cpp b/tests/scripting/test_mono_script_runtime.cpp index 6133db50..be7fc51b 100644 --- a/tests/scripting/test_mono_script_runtime.cpp +++ b/tests/scripting/test_mono_script_runtime.cpp @@ -2432,6 +2432,73 @@ TEST_F( recorder->Shutdown(); } +TEST_F( + MonoScriptRuntimeTest, + ManagedRenderPipelineBridgeSharesManagedPipelineInstanceAcrossStageRecorders) { + Scene* runtimeScene = + CreateScene("ManagedPipelineCreateCountObservationScene"); + GameObject* scriptObject = + runtimeScene->CreateGameObject("ManagedPipelineCreateCountObservationProbe"); + ScriptComponent* script = + AddScript( + scriptObject, + "Gameplay", + "ManagedRenderPipelineCreateCountObservationProbe"); + ASSERT_NE(script, nullptr); + + engine->OnRuntimeStart(runtimeScene); + engine->OnUpdate(0.016f); + + const auto bridge = + XCEngine::Rendering::Pipelines::GetManagedRenderPipelineBridge(); + ASSERT_NE(bridge, nullptr); + + const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = { + "GameScripts", + "Gameplay", + "ManagedRenderPipelineProbeAsset" + }; + + std::shared_ptr + assetRuntime = bridge->CreateAssetRuntime(descriptor); + ASSERT_NE(assetRuntime, nullptr); + + const XCEngine::Rendering::RenderContext context = {}; + + std::unique_ptr + firstRecorder = assetRuntime->CreateStageRecorder(); + ASSERT_NE(firstRecorder, nullptr); + ASSERT_TRUE(firstRecorder->Initialize(context)); + EXPECT_TRUE( + firstRecorder->SupportsStageRenderGraph( + XCEngine::Rendering::CameraFrameStage::MainScene)); + + std::unique_ptr + secondRecorder = assetRuntime->CreateStageRecorder(); + ASSERT_NE(secondRecorder, nullptr); + ASSERT_TRUE(secondRecorder->Initialize(context)); + EXPECT_TRUE( + secondRecorder->SupportsStageRenderGraph( + XCEngine::Rendering::CameraFrameStage::MainScene)); + + firstRecorder->Shutdown(); + + EXPECT_TRUE( + secondRecorder->SupportsStageRenderGraph( + XCEngine::Rendering::CameraFrameStage::MainScene)); + + engine->OnUpdate(0.016f); + + int observedCreatePipelineCallCount = 0; + EXPECT_TRUE(runtime->TryGetFieldValue( + script, + "ObservedCreatePipelineCallCount", + observedCreatePipelineCallCount)); + EXPECT_EQ(observedCreatePipelineCallCount, 1); + + secondRecorder->Shutdown(); +} + TEST_F( MonoScriptRuntimeTest, ManagedRenderPipelineBridgeRuntimeExposesBuiltinForwardRendererAsset) {