Centralize render-graph recording context builders

This commit is contained in:
2026-04-15 01:18:15 +08:00
parent 65b3078c7f
commit d0ce2d7883
7 changed files with 331 additions and 67 deletions

View File

@@ -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 / passnative 侧负责承载与执行”的正式宿主层。
### 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 firstdeferred later
正确顺序仍然是:
1. `SRP Host v1`
2. `URP-like builtin package`
3. `Deferred renderer`
4. 更复杂的 feature 生态

View File

@@ -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<RenderGraphTextureHandle>{ 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<RenderGraphTextureHandle>{ 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,

View File

@@ -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<RenderGraphTextureHandle>{ passOutputColor },
{},
&stageExecutionSucceeded,
beginSequencePass,
{},
&blackboard
};
const RenderPassRenderGraphContext passContext =
Internal::BuildRenderPassRenderGraphContext(
commonContext,
beginSequencePass);
if (!RecordSequencePass(
*pass,
passContext,

View File

@@ -0,0 +1,118 @@
#pragma once
#include <XCEngine/Rendering/RenderPipeline.h>
#include <XCEngine/Rendering/SceneRenderFeaturePass.h>
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<RenderGraphTextureHandle> 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

View File

@@ -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<RenderGraphTextureHandle>{ mainDirectionalShadowTexture }
: std::vector<RenderGraphTextureHandle>{};
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,
{

View File

@@ -1,6 +1,7 @@
#include "Rendering/SceneRenderFeatureHost.h"
#include "Debug/Logger.h"
#include "Rendering/Internal/RenderGraphRecordingContextBuilders.h"
#include <string>
@@ -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,