diff --git a/docs/plan/Renderer_C++层第五阶段计划_SRPHost_v1骨架与BuiltinForward复用_2026-04-15.md b/docs/plan/Renderer_C++层第五阶段计划_SRPHost_v1骨架与BuiltinForward复用_2026-04-15.md new file mode 100644 index 00000000..d9ec54a8 --- /dev/null +++ b/docs/plan/Renderer_C++层第五阶段计划_SRPHost_v1骨架与BuiltinForward复用_2026-04-15.md @@ -0,0 +1,152 @@ +# Renderer C++层第五阶段计划:SRP Host v1骨架与 Builtin Forward 复用 + +日期:`2026-04-15` + +## 1. 阶段定位 + +第四阶段已经把 `native planning -> graph build` 这条主线基本收口: + +- `CameraFramePlanBuilder` 的 fullscreen/color-chain 规划已拆出 +- `CameraFramePlan` 已从 public 头里下沉主要实现 +- `BuiltinForwardPipeline` 的 main-scene graph 录制已抽出 internal builder +- `CameraFrame` / `SceneRenderFeatureHost` / `BuiltinForward` 共用的 graph recording context 已集中到统一 builder + +现在可以正式进入下一阶段: + +`SRP Host v1` + +这一阶段不是直接做完整 URP,也不是直接开放最终用户级 API。 +这一阶段的目标是先把 native host、renderer builder、C# 管线入口三者接稳。 + +## 2. 这一阶段要解决的核心问题 + +### 2.1 目前还没有正式的 SRP host + +当前引擎已经有: + +- `SceneRenderer` +- `CameraRenderer` +- `RenderGraph` +- `BuiltinForwardPipeline` + +但还没有一个明确的“脚本侧组织 renderer / feature / pass,native 侧负责承载与执行”的正式宿主层。 + +### 2.2 当前 builtin forward 仍然是“内建管线实现”,不是“可被 SRP host 复用的 builtin renderer” + +虽然第四阶段已经把 main-scene graph builder 抽出来了,但 `BuiltinForwardPipeline` 仍然更像一个完整管线对象。 +SRP host 要求它至少能被看成: + +- 一个可复用的 native renderer +- 一个可被 host 调度的 graph contributor +- 一个默认 builtin implementation + +而不是 host 本身。 + +### 2.3 C# 入口现在还缺少正式边界 + +后面如果要像 Unity 一样让用户用 C# 组织 renderer / feature / pass,最先需要的不是完整功能,而是稳定边界: + +- C# 能创建/持有一条 render pipeline asset +- native 能向脚本暴露受控的 renderer host 入口 +- host 能回落复用 builtin forward renderer + +## 3. 本阶段总目标 + +这一阶段结束后,主线应接近: + +`C# Pipeline Asset / Native Pipeline Asset -> SRP Host -> Builtin Renderer / Renderer Feature -> RenderGraph Host -> Execute` + +也就是: + +- `RenderGraph` 仍然留在 C++ 层 +- `SRP Host` 负责组织一帧 renderer graph contribution +- `BuiltinForward` 首先作为默认 builtin renderer 被 host 复用 +- 后续 `URP-like package` 再放到更高层,不直接塞回 C++ host + +## 4. 明确边界 + +### 4.1 这一阶段明确不做 + +- 不做完整 deferred renderer +- 不做完整 URP 包 +- 不做完整 renderer feature 生态 +- 不做最终用户可自由扩展的全部 C# API + +### 4.2 这一阶段必须做成 + +- 一个正式的 `SRP Host v1` +- 一个可被 host 调度的 builtin forward renderer 入口 +- 一个最小可运行的 C# pipeline asset / host bridge +- host 能把 builtin forward 作为默认 fallback 跑通 + +## 5. 具体执行顺序 + +### 5.1 先把 builtin forward 从“管线实现”整理成“host 可复用 renderer” + +目标: + +- 明确 builtin forward 的 renderer 身份 +- 减少 host 未来直接依赖 `BuiltinForwardPipeline` 大对象内部细节 +- 为后续 deferred / custom renderer 留统一接缝 + +建议收口方向: + +- `BuiltinForwardRenderer` 或等价 internal builder host +- `RenderPipeline` 与 `Renderer Builder` 边界再清一次 + +### 5.2 引入 `SRP Host v1` native 宿主 + +目标: + +- host 持有一帧 renderer graph contribution 列表 +- host 负责组织 main scene / feature / fullscreen stages 的 graph 录制 +- 默认仍可回落到 builtin forward renderer + +第一版不追求花哨能力,先把“host 存在且稳定”做出来。 + +### 5.3 打通最小 C# 管线入口 + +目标: + +- C# 侧能描述“使用哪条 pipeline asset / renderer” +- native 侧能从脚本入口落到 `SRP Host v1` +- 至少存在一条 builtin forward 的脚本可达路径 + +这一层只做最小闭环,不追求完整编辑器 UX。 + +### 5.4 最后再决定是否开 `URP-like builtin package` + +当下面三件事成立,再开下一阶段: + +1. `SRP Host v1` 已稳定 +2. `BuiltinForward` 已经是可复用 builtin renderer,而不是一坨特殊 case +3. C# 入口已经能稳定驱动默认 renderer + +## 6. 完成标准 + +满足下面这些条件,就可以认定第五阶段收口: + +- native 已存在正式的 `SRP Host v1` +- builtin forward 已能作为 host 下的默认 builtin renderer 运行 +- 脚本侧至少能以最小路径创建并驱动默认 pipeline +- `CameraRenderer` 不会重新长成新的总调度中心 +- `rendering_unit_tests / editor_tests / XCEditor smoke` 继续稳定 + +## 7. 阶段后下一步 + +第五阶段完成后,才进入: + +`URP-like Builtin Package v1` + +也就是: + +- C# 侧 builtin package +- renderer feature / pass 组合能力 +- forward first,deferred later + +正确顺序仍然是: + +1. `SRP Host v1` +2. `URP-like builtin package` +3. `Deferred renderer` +4. 更复杂的 feature 生态 diff --git a/docs/plan/Renderer_C++层第四阶段计划_RenderGraph规划层收口与SRP启动前条件_2026-04-15.md b/docs/plan/used/Renderer_C++层第四阶段计划_RenderGraph规划层收口与SRP启动前条件_2026-04-15.md similarity index 100% rename from docs/plan/Renderer_C++层第四阶段计划_RenderGraph规划层收口与SRP启动前条件_2026-04-15.md rename to docs/plan/used/Renderer_C++层第四阶段计划_RenderGraph规划层收口与SRP启动前条件_2026-04-15.md diff --git a/engine/src/Rendering/Execution/Internal/CameraFrameRenderGraphStagePassRecording.cpp b/engine/src/Rendering/Execution/Internal/CameraFrameRenderGraphStagePassRecording.cpp index 4819837c..9394cd8e 100644 --- a/engine/src/Rendering/Execution/Internal/CameraFrameRenderGraphStagePassRecording.cpp +++ b/engine/src/Rendering/Execution/Internal/CameraFrameRenderGraphStagePassRecording.cpp @@ -8,6 +8,7 @@ #include "Rendering/Execution/Internal/CameraFrameRenderGraphStageState.h" #include "Rendering/Execution/Internal/CameraFrameRenderGraphSurfaceUtils.h" #include "Rendering/Graph/RenderGraph.h" +#include "Rendering/Internal/RenderGraphRecordingContextBuilders.h" namespace XCEngine { namespace Rendering { @@ -46,7 +47,7 @@ bool TryRecordCameraFrameStageStandaloneRenderGraphPass( return true; }; - const RenderPassRenderGraphContext standalonePassContext = { + const Internal::RenderGraphRecordingContextCommon commonContext = { session.graphBuilder, stageState.stageName, context.plan.request.context, @@ -61,10 +62,12 @@ bool TryRecordCameraFrameStageStandaloneRenderGraphPass( std::vector{ stageState.outputColor }, stageState.outputSurface.depthTexture, &session.stageExecutionSucceeded, - beginStandalonePass, - {}, &session.blackboard }; + const RenderPassRenderGraphContext standalonePassContext = + Internal::BuildRenderPassRenderGraphContext( + commonContext, + beginStandalonePass); if (!standaloneStagePass->RecordRenderGraph(standalonePassContext)) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, @@ -89,7 +92,7 @@ bool TryRecordCameraFrameMainSceneGraphPass( } handled = true; - const RenderPipelineMainSceneRenderGraphContext mainSceneContext = { + const Internal::RenderGraphRecordingContextCommon commonContext = { session.graphBuilder, stageState.stageName, context.plan.request.context, @@ -100,11 +103,15 @@ bool TryRecordCameraFrameMainSceneGraphPass( : nullptr, stageState.sourceColorView, stageState.sourceColorState, + GetPrimaryColorTexture(stageState.sourceSurface), std::vector{ stageState.outputColor }, stageState.outputSurface.depthTexture, &session.stageExecutionSucceeded, &session.blackboard }; + const RenderPipelineMainSceneRenderGraphContext mainSceneContext = + Internal::BuildRenderPipelineMainSceneRenderGraphContext( + commonContext); if (!session.executionState.pipeline->RecordMainSceneRenderGraph(mainSceneContext)) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, diff --git a/engine/src/Rendering/Execution/Internal/CameraFrameRenderGraphStageSequenceRecording.cpp b/engine/src/Rendering/Execution/Internal/CameraFrameRenderGraphStageSequenceRecording.cpp index 096bf623..d2825503 100644 --- a/engine/src/Rendering/Execution/Internal/CameraFrameRenderGraphStageSequenceRecording.cpp +++ b/engine/src/Rendering/Execution/Internal/CameraFrameRenderGraphStageSequenceRecording.cpp @@ -7,6 +7,7 @@ #include "Rendering/Execution/Internal/CameraFrameRenderGraphStageState.h" #include "Rendering/Execution/Internal/CameraFrameRenderGraphSurfaceUtils.h" #include "Rendering/Graph/RenderGraph.h" +#include "Rendering/Internal/RenderGraphRecordingContextBuilders.h" #include "Rendering/Internal/RenderPassGraphUtils.h" namespace XCEngine { @@ -103,7 +104,7 @@ bool RecordRegularPassSequenceStage( stageSequence->GetPassCount() == 1u ? stageName : BuildRenderGraphSequencePassName(stageName, passIndex); - const RenderPassRenderGraphContext passContext = { + const Internal::RenderGraphRecordingContextCommon commonContext = { graphBuilder, passName, stagePassContext.renderContext, @@ -116,10 +117,12 @@ bool RecordRegularPassSequenceStage( outputSurface.colorTextures, outputSurface.depthTexture, &stageExecutionSucceeded, - beginSequencePass, - {}, &blackboard }; + const RenderPassRenderGraphContext passContext = + Internal::BuildRenderPassRenderGraphContext( + commonContext, + beginSequencePass); if (!RecordSequencePass( *pass, passContext, @@ -206,7 +209,7 @@ bool RecordFullscreenPassSequenceStage( passIndex == 0u ? binding.sourceColorState : RHI::ResourceStates::PixelShaderResource; - const RenderPassRenderGraphContext passContext = { + const Internal::RenderGraphRecordingContextCommon commonContext = { graphBuilder, passName, stagePassContext.renderContext, @@ -219,10 +222,12 @@ bool RecordFullscreenPassSequenceStage( std::vector{ passOutputColor }, {}, &stageExecutionSucceeded, - beginSequencePass, - {}, &blackboard }; + const RenderPassRenderGraphContext passContext = + Internal::BuildRenderPassRenderGraphContext( + commonContext, + beginSequencePass); if (!RecordSequencePass( *pass, passContext, diff --git a/engine/src/Rendering/Internal/RenderGraphRecordingContextBuilders.h b/engine/src/Rendering/Internal/RenderGraphRecordingContextBuilders.h new file mode 100644 index 00000000..bd31e8c1 --- /dev/null +++ b/engine/src/Rendering/Internal/RenderGraphRecordingContextBuilders.h @@ -0,0 +1,118 @@ +#pragma once + +#include +#include + +namespace XCEngine { +namespace Rendering { +namespace Internal { + +struct RenderGraphRecordingContextCommon { + RenderGraphBuilder& graphBuilder; + Containers::String passName = {}; + const RenderContext& renderContext; + const RenderSceneData& sceneData; + RenderSurface surface = {}; + const RenderSurface* sourceSurface = nullptr; + RHI::RHIResourceView* sourceColorView = nullptr; + RHI::ResourceStates sourceColorState = RHI::ResourceStates::Common; + RenderGraphTextureHandle sourceColorTexture = {}; + std::vector colorTargets = {}; + RenderGraphTextureHandle depthTarget = {}; + bool* executionSucceeded = nullptr; + RenderGraphBlackboard* blackboard = nullptr; +}; + +inline RenderPassRenderGraphContext BuildRenderPassRenderGraphContext( + const RenderGraphRecordingContextCommon& common, + RenderPassGraphBeginCallback beginPassCallback = {}, + RenderPassGraphEndCallback endPassCallback = {}) { + return { + common.graphBuilder, + common.passName, + common.renderContext, + common.sceneData, + common.surface, + common.sourceSurface, + common.sourceColorView, + common.sourceColorState, + common.sourceColorTexture, + common.colorTargets, + common.depthTarget, + common.executionSucceeded, + beginPassCallback, + endPassCallback, + common.blackboard + }; +} + +inline SceneRenderFeaturePassRenderGraphContext BuildSceneRenderFeaturePassRenderGraphContext( + const RenderGraphRecordingContextCommon& common, + bool clearAttachments = false, + SceneRenderFeaturePassBeginCallback beginPassCallback = {}, + SceneRenderFeaturePassEndCallback endPassCallback = {}) { + return { + common.graphBuilder, + common.passName, + common.renderContext, + common.sceneData, + common.surface, + common.sourceSurface, + common.sourceColorView, + common.sourceColorState, + common.sourceColorTexture, + common.colorTargets, + common.depthTarget, + clearAttachments, + common.executionSucceeded, + beginPassCallback, + endPassCallback, + common.blackboard + }; +} + +inline RenderPipelineMainSceneRenderGraphContext BuildRenderPipelineMainSceneRenderGraphContext( + const RenderGraphRecordingContextCommon& common) { + return { + common.graphBuilder, + common.passName, + common.renderContext, + common.sceneData, + common.surface, + common.sourceSurface, + common.sourceColorView, + common.sourceColorState, + common.colorTargets, + common.depthTarget, + common.executionSucceeded, + common.blackboard + }; +} + +inline SceneRenderFeaturePassRenderGraphContext CloneSceneRenderFeaturePassRenderGraphContext( + const SceneRenderFeaturePassRenderGraphContext& context, + const Containers::String& passName, + bool clearAttachments) { + return { + context.graphBuilder, + passName, + context.renderContext, + context.sceneData, + context.surface, + context.sourceSurface, + context.sourceColorView, + context.sourceColorState, + context.sourceColorTexture, + context.colorTargets, + context.depthTarget, + clearAttachments, + context.executionSucceeded, + context.beginPassCallback, + context.endPassCallback, + context.blackboard + }; +} + +} // namespace Internal +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardMainSceneGraphBuilder.cpp b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardMainSceneGraphBuilder.cpp index 1983722d..15f9d2f4 100644 --- a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardMainSceneGraphBuilder.cpp +++ b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardMainSceneGraphBuilder.cpp @@ -3,6 +3,7 @@ #include "Debug/Logger.h" #include "Rendering/Graph/RenderGraph.h" #include "Rendering/Internal/RenderPassGraphUtils.h" +#include "Rendering/Internal/RenderGraphRecordingContextBuilders.h" #include "Rendering/Pipelines/BuiltinForwardPipeline.h" #include "Rendering/Pipelines/Internal/BuiltinForwardSceneSetup.h" #include "Rendering/RenderSurface.h" @@ -131,28 +132,31 @@ bool BuiltinForwardMainSceneGraphBuilder::Record( [&pipeline](const RenderPassContext& passContext) { pipeline.EndForwardScenePass(passContext); }; + const ::XCEngine::Rendering::Internal::RenderGraphRecordingContextCommon commonContext = { + context.graphBuilder, + passName, + renderContext, + *sceneData, + surfaceTemplate, + hasSourceSurface ? &sourceSurface : nullptr, + sourceColorView, + sourceColorState, + {}, + colorTargets, + depthTarget, + executionSucceeded, + context.blackboard + }; bool clearAttachments = true; for (const ForwardSceneStep& step : GetBuiltinForwardSceneSteps()) { if (step.type == ForwardSceneStepType::InjectionPoint) { - const SceneRenderFeaturePassRenderGraphContext featureContext = { - context.graphBuilder, - passName, - renderContext, - *sceneData, - surfaceTemplate, - hasSourceSurface ? &sourceSurface : nullptr, - sourceColorView, - sourceColorState, - {}, - colorTargets, - depthTarget, - clearAttachments, - executionSucceeded, - beginRecordedPass, - endRecordedPass, - context.blackboard - }; + const SceneRenderFeaturePassRenderGraphContext featureContext = + ::XCEngine::Rendering::Internal::BuildSceneRenderFeaturePassRenderGraphContext( + commonContext, + clearAttachments, + beginRecordedPass, + endRecordedPass); bool recordedAnyPass = false; if (!pipeline.m_forwardSceneFeatureHost.Record( featureContext, @@ -178,23 +182,13 @@ bool BuiltinForwardMainSceneGraphBuilder::Record( mainDirectionalShadowTexture.IsValid() ? std::vector{ mainDirectionalShadowTexture } : std::vector{}; - const RenderPassRenderGraphContext phaseContext = { - context.graphBuilder, - phasePassName, - renderContext, - *sceneData, - surfaceTemplate, - hasSourceSurface ? &sourceSurface : nullptr, - sourceColorView, - sourceColorState, - {}, - colorTargets, - depthTarget, - executionSucceeded, - beginPhasePass, - endRecordedPass, - context.blackboard - }; + ::XCEngine::Rendering::Internal::RenderGraphRecordingContextCommon phaseCommonContext = commonContext; + phaseCommonContext.passName = phasePassName; + const RenderPassRenderGraphContext phaseContext = + ::XCEngine::Rendering::Internal::BuildRenderPassRenderGraphContext( + phaseCommonContext, + beginPhasePass, + endRecordedPass); if (!::XCEngine::Rendering::Internal::RecordCallbackRasterRenderPass( phaseContext, { diff --git a/engine/src/Rendering/SceneRenderFeatureHost.cpp b/engine/src/Rendering/SceneRenderFeatureHost.cpp index 4dd82588..14dd196b 100644 --- a/engine/src/Rendering/SceneRenderFeatureHost.cpp +++ b/engine/src/Rendering/SceneRenderFeatureHost.cpp @@ -1,6 +1,7 @@ #include "Rendering/SceneRenderFeatureHost.h" #include "Debug/Logger.h" +#include "Rendering/Internal/RenderGraphRecordingContextBuilders.h" #include @@ -118,28 +119,15 @@ bool SceneRenderFeatureHost::Record( continue; } - const SceneRenderFeaturePassRenderGraphContext featureContext = { - context.graphBuilder, - BuildFeatureGraphPassName( - context.passName, - injectionPoint, - *featurePass, - featureIndex), - context.renderContext, - context.sceneData, - context.surface, - context.sourceSurface, - context.sourceColorView, - context.sourceColorState, - context.sourceColorTexture, - context.colorTargets, - context.depthTarget, - clearAttachments, - context.executionSucceeded, - context.beginPassCallback, - context.endPassCallback, - context.blackboard - }; + const SceneRenderFeaturePassRenderGraphContext featureContext = + Internal::CloneSceneRenderFeaturePassRenderGraphContext( + context, + BuildFeatureGraphPassName( + context.passName, + injectionPoint, + *featurePass, + featureIndex), + clearAttachments); if (!featurePass->RecordRenderGraph(featureContext)) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering,