diff --git a/docs/used/SRP_RendererSetup与ContextFirst执行接缝计划_2026-04-20.md b/docs/used/SRP_RendererSetup与ContextFirst执行接缝计划_2026-04-20.md new file mode 100644 index 00000000..c3b174d8 --- /dev/null +++ b/docs/used/SRP_RendererSetup与ContextFirst执行接缝计划_2026-04-20.md @@ -0,0 +1,133 @@ +# SRP Renderer Setup 与 Context-First 执行接缝计划 2026-04-20 + +## 1. 阶段目标 + +上一阶段已经把 renderer-driven 主骨架立起来了: + +1. `RendererDrivenRenderPipeline` +2. `RendererBackedRenderPipeline` +3. `RendererBackedRenderPipelineAsset` +4. `RendererRecordingContext / RendererCameraRequestContext` + +但当前 `RendererRecordingContext` 还没有真正成为 renderer 的正式 override 面。 +现在 `ScriptableRenderer` 里仍然主要靠: + +1. `SupportsStageRenderGraph(CameraFrameStage)` +2. `RecordStageRenderGraph(ScriptableRenderContext)` + +而 `RendererRecordingContext` 只是被外层包了一层。 + +这一阶段的目标就是把 Universal 包内的 renderer 执行接缝正式切成 context-first: + +1. 让 `ScriptableRenderer` 以 `RendererRecordingContext` 为正式支持/录制入口 +2. 让 `RendererDrivenRenderPipeline` 直接走这条正式 seam +3. 让 probe / 测试改成覆盖这条新 seam +4. 锁住“新 seam 存在,但不额外扩 public surface” + +--- + +## 2. 当前问题 + +### 2.1 `RendererRecordingContext` 还只是包装,不是正式 contract + +当前 `RendererDrivenRenderPipeline` 虽然已经把 stage/context 转成了 +`RendererRecordingContext`,但 `ScriptableRenderer` 仍然是: + +1. `SupportsRendererRecording(context)` 内部转回 `SupportsStageRenderGraph(stage)` +2. `RecordRenderer(context)` 内部转回 `RecordStageRenderGraph(context.renderContext)` + +这说明真正的 override 面还没变。 + +### 2.2 support path 和 record path 还没真正共享同一组上下文入口 + +现在 support 和 record 的主入口签名不同: + +1. support:`CameraFrameStage` +2. record:`ScriptableRenderContext` + +虽然外层能桥接,但 renderer 内部并没有一个统一的 context-first contract。 + +### 2.3 后续想扩 renderer setup 数据会继续卡在旧签名 + +未来如果要把更多 setup 数据挂进 renderer 执行面: + +1. stage planning 结果 +2. camera-specific flags +3. renderer-local setup state + +那就应该沿着 `RendererRecordingContext` 往里加,而不是继续扩旧签名。 + +--- + +## 3. 实施方案 + +### 3.1 让 `ScriptableRenderer` 正式采用 context-first seam + +调整为: + +1. `SupportsRendererRecording(RendererRecordingContext)` 成为主 support seam +2. `RecordRenderer(RendererRecordingContext)` 成为主 record seam +3. 旧的 `SupportsStageRenderGraph/RecordStageRenderGraph` 只保留为 native bridge 适配入口 + +### 3.2 probe renderers 迁移到新 seam + +把目前那些会观察 support/record 调用次数的 probe renderer: + +1. `ManagedRenderPipelineProbe` +2. `ManagedUniversalRenderPipelineProbe` +3. `ManagedLifecycleProbeRenderer` + +改成 override 新 seam。 + +### 3.3 用脚本 probe 锁住 seam + +新增/更新 API probe,确认: + +1. `RendererRecordingContext` 类型存在 +2. `ScriptableRenderer` 存在非 public 的 context-first 方法 +3. 旧的 stage-based renderer 方法仍然不是 public + +--- + +## 4. 实施步骤 + +### Step 1:重构 `ScriptableRenderer` + +目标: + +1. 把 context-first 方法变成正式 virtual seam +2. 旧入口退回到适配层 + +### Step 2:迁移 probe renderers + +目标: + +1. 改写 probe renderer overrides +2. 保持现有行为和计数不回退 + +### Step 3:补 probe 与测试 + +目标: + +1. 更新 `ScriptableRenderContextApiSurfaceProbe.cs` +2. 更新 `test_mono_script_runtime.cpp` + +### Step 4:完整验证 + +目标: + +1. 编译 `rendering_unit_tests`、`scripting_tests`、`XCEditor` +2. 跑测试 +3. old editor 10s 冒烟并检查最新 `SceneReady` + +--- + +## 5. 验收标准 + +完成后应满足: + +1. `ScriptableRenderer` 的正式 support/record seam 是 context-first +2. `RendererDrivenRenderPipeline` 直接走正式 seam +3. probe renderers 与脚本测试已经迁移并锁住新结构 +4. 不扩大 public renderer 录制表面 +5. 编译、测试、old editor 冒烟全部通过 diff --git a/managed/GameScripts/RenderPipelineApiProbe.cs b/managed/GameScripts/RenderPipelineApiProbe.cs index d49419e4..cb7fab71 100644 --- a/managed/GameScripts/RenderPipelineApiProbe.cs +++ b/managed/GameScripts/RenderPipelineApiProbe.cs @@ -999,14 +999,15 @@ namespace Gameplay } } - protected override bool SupportsStageRenderGraph( - CameraFrameStage stage) + protected override bool SupportsRendererRecording( + RendererRecordingContext context) { - return stage == CameraFrameStage.MainScene; + return context != null && + context.stage == CameraFrameStage.MainScene; } - protected override bool RecordStageRenderGraph( - ScriptableRenderContext context) + protected override bool RecordRenderer( + RendererRecordingContext context) { return context != null; } @@ -1384,18 +1385,18 @@ namespace Gameplay public static int SupportsStageCallCount; public static int RecordStageCallCount; - protected override bool SupportsStageRenderGraph( - CameraFrameStage stage) + protected override bool SupportsRendererRecording( + RendererRecordingContext context) { SupportsStageCallCount++; - return base.SupportsStageRenderGraph(stage); + return base.SupportsRendererRecording(context); } - protected override bool RecordStageRenderGraph( - ScriptableRenderContext context) + protected override bool RecordRenderer( + RendererRecordingContext context) { RecordStageCallCount++; - return base.RecordStageRenderGraph(context); + return base.RecordRenderer(context); } } @@ -1431,23 +1432,28 @@ namespace Gameplay m_postProcessScale = postProcessScale; } - protected override bool SupportsStageRenderGraph( - CameraFrameStage stage) + protected override bool SupportsRendererRecording( + RendererRecordingContext context) { - if (stage == CameraFrameStage.MainScene) + if (context == null) + { + return false; + } + + if (context.stage == CameraFrameStage.MainScene) { SupportsMainSceneCallCount++; } - else if (stage == CameraFrameStage.PostProcess) + else if (context.stage == CameraFrameStage.PostProcess) { SupportsPostProcessCallCount++; } - return base.SupportsStageRenderGraph(stage); + return base.SupportsRendererRecording(context); } - protected override bool RecordStageRenderGraph( - ScriptableRenderContext context) + protected override bool RecordRenderer( + RendererRecordingContext context) { if (context == null) { @@ -1464,7 +1470,7 @@ namespace Gameplay LastPostProcessScale = m_postProcessScale; } - return base.RecordStageRenderGraph(context); + return base.RecordRenderer(context); } private static void OnOpaqueRecorded() diff --git a/managed/GameScripts/ScriptableRenderContextApiSurfaceProbe.cs b/managed/GameScripts/ScriptableRenderContextApiSurfaceProbe.cs index c0058519..b9d09c12 100644 --- a/managed/GameScripts/ScriptableRenderContextApiSurfaceProbe.cs +++ b/managed/GameScripts/ScriptableRenderContextApiSurfaceProbe.cs @@ -34,6 +34,8 @@ namespace Gameplay public bool HasRendererBackedRenderPipelineAssetType; public bool HasRendererBackedRenderPipelineType; public bool HasRendererDrivenRenderPipelineType; + public bool HasRendererSupportsRendererRecording; + public bool HasRendererRecordRenderer; public bool HasPublicRendererSupportsStageRenderGraph; public bool HasPublicRendererRecordStageRenderGraph; @@ -170,6 +172,16 @@ namespace Gameplay HasRendererDrivenRenderPipelineType = System.Type.GetType( "XCEngine.Rendering.Universal.RendererDrivenRenderPipeline, XCEngine.RenderPipelines.Universal") != null; + HasRendererSupportsRendererRecording = + rendererType.GetMethod( + "SupportsRendererRecording", + BindingFlags.Instance | + BindingFlags.NonPublic) != null; + HasRendererRecordRenderer = + rendererType.GetMethod( + "RecordRenderer", + BindingFlags.Instance | + BindingFlags.NonPublic) != null; HasPublicRendererSupportsStageRenderGraph = rendererType.GetMethod( "SupportsStageRenderGraph", diff --git a/managed/XCEngine.ScriptCore/Rendering/Universal/RendererDrivenRenderPipeline.cs b/managed/XCEngine.ScriptCore/Rendering/Universal/RendererDrivenRenderPipeline.cs index 9c9ec618..fbae4e7b 100644 --- a/managed/XCEngine.ScriptCore/Rendering/Universal/RendererDrivenRenderPipeline.cs +++ b/managed/XCEngine.ScriptCore/Rendering/Universal/RendererDrivenRenderPipeline.cs @@ -35,7 +35,7 @@ namespace XCEngine.Rendering.Universal ScriptableRenderer renderer = ResolveRenderer(context); return renderer != null && - renderer.SupportsRendererRecording(context); + renderer.SupportsRendererRecordingInstance(context); } protected virtual bool RecordRenderer( @@ -44,7 +44,7 @@ namespace XCEngine.Rendering.Universal ScriptableRenderer renderer = ResolveRenderer(context); return renderer != null && - renderer.RecordRenderer(context); + renderer.RecordRendererInstance(context); } protected abstract ScriptableRenderer ResolveRenderer( diff --git a/managed/XCEngine.ScriptCore/Rendering/Universal/ScriptableRenderer.cs b/managed/XCEngine.ScriptCore/Rendering/Universal/ScriptableRenderer.cs index fbdc67fe..fc310f6b 100644 --- a/managed/XCEngine.ScriptCore/Rendering/Universal/ScriptableRenderer.cs +++ b/managed/XCEngine.ScriptCore/Rendering/Universal/ScriptableRenderer.cs @@ -77,32 +77,34 @@ namespace XCEngine.Rendering.Universal { } - internal bool SupportsRendererRecording( + internal bool SupportsRendererRecordingInstance( RendererRecordingContext context) { - return context != null && - SupportsStageRenderGraph(context.stage); + return SupportsRendererRecording(context); } - internal bool RecordRenderer( + internal bool RecordRendererInstance( RendererRecordingContext context) { - return context != null && - context.renderContext != null && - RecordStageRenderGraph( - context.renderContext); + return RecordRenderer(context); } - protected internal virtual bool SupportsStageRenderGraph( - CameraFrameStage stage) + protected virtual bool SupportsRendererRecording( + RendererRecordingContext context) { - RenderingData renderingData = new RenderingData(stage); + if (context == null) + { + return false; + } + + RenderingData renderingData = + context.renderingData; BuildPassQueue(renderingData); for (int i = 0; i < m_activePassQueue.Count; ++i) { ScriptableRenderPass renderPass = m_activePassQueue[i]; if (renderPass != null && - renderPass.SupportsStage(stage)) + renderPass.SupportsStage(context.stage)) { return true; } @@ -111,16 +113,17 @@ namespace XCEngine.Rendering.Universal return false; } - protected internal virtual bool RecordStageRenderGraph( - ScriptableRenderContext context) + protected virtual bool RecordRenderer( + RendererRecordingContext context) { - if (context == null) + if (context == null || + context.renderContext == null) { return false; } RenderingData renderingData = - new RenderingData(context); + context.renderingData; BuildPassQueue(renderingData); bool recordedAnyPass = false; @@ -134,7 +137,7 @@ namespace XCEngine.Rendering.Universal } if (!renderPass.Record( - context, + context.renderContext, renderingData)) { return false; @@ -146,6 +149,21 @@ namespace XCEngine.Rendering.Universal return recordedAnyPass; } + protected internal virtual bool SupportsStageRenderGraph( + CameraFrameStage stage) + { + return SupportsRendererRecording( + new RendererRecordingContext(stage)); + } + + protected internal virtual bool RecordStageRenderGraph( + ScriptableRenderContext context) + { + return context != null && + RecordRenderer( + new RendererRecordingContext(context)); + } + private void BuildPassQueue( RenderingData renderingData) { diff --git a/tests/scripting/test_mono_script_runtime.cpp b/tests/scripting/test_mono_script_runtime.cpp index e4871780..1c439fc1 100644 --- a/tests/scripting/test_mono_script_runtime.cpp +++ b/tests/scripting/test_mono_script_runtime.cpp @@ -1178,6 +1178,8 @@ TEST_F( bool hasRendererBackedRenderPipelineAssetType = false; bool hasRendererBackedRenderPipelineType = false; bool hasRendererDrivenRenderPipelineType = false; + bool hasRendererSupportsRendererRecording = false; + bool hasRendererRecordRenderer = false; bool hasPublicRendererSupportsStageRenderGraph = false; bool hasPublicRendererRecordStageRenderGraph = false; @@ -1285,6 +1287,14 @@ TEST_F( selectionScript, "HasRendererDrivenRenderPipelineType", hasRendererDrivenRenderPipelineType)); + EXPECT_TRUE(runtime->TryGetFieldValue( + selectionScript, + "HasRendererSupportsRendererRecording", + hasRendererSupportsRendererRecording)); + EXPECT_TRUE(runtime->TryGetFieldValue( + selectionScript, + "HasRendererRecordRenderer", + hasRendererRecordRenderer)); EXPECT_TRUE(runtime->TryGetFieldValue( selectionScript, "HasPublicRendererSupportsStageRenderGraph", @@ -1320,6 +1330,8 @@ TEST_F( EXPECT_TRUE(hasRendererBackedRenderPipelineAssetType); EXPECT_TRUE(hasRendererBackedRenderPipelineType); EXPECT_TRUE(hasRendererDrivenRenderPipelineType); + EXPECT_TRUE(hasRendererSupportsRendererRecording); + EXPECT_TRUE(hasRendererRecordRenderer); EXPECT_FALSE(hasPublicRendererSupportsStageRenderGraph); EXPECT_FALSE(hasPublicRendererRecordStageRenderGraph); }