Extract camera frame fullscreen stage planner

This commit is contained in:
2026-04-15 00:53:51 +08:00
parent 0afcaa0b3b
commit f064d6ed68
10 changed files with 284 additions and 54 deletions

View File

@@ -0,0 +1,180 @@
# Renderer C++层第四阶段计划RenderGraph规划层收口与SRP启动前条件
日期:`2026-04-15`
## 1. 文档定位
第三阶段已经把 RenderGraph native host 的骨架、阶段上下文、运行时分发、stage surface/source 解析、fullscreen color chain 意图、internal policy 边界,连续收了一轮。
这一阶段不直接开做面向用户的 `C# SRP`
这一阶段要做的是:
-`CameraFramePlanBuilder` 和 planning 层继续收干净。
-`CameraFramePlan` 更像高层 frame intent而不是执行细节载体。
- 给后面的 `native renderer builder / SRP host seam` 做稳定落点。
- 明确“什么时候可以正式开始做 SRP”。
一句话:
先把 `native planning -> graph build` 这条线收口,再开 SRP。
## 2. 当前进展基线
截至当前主线,已经完成并推送:
- `8232f59` `Extract camera frame render-graph recording session`
- `d92afa2` `Extract camera frame render-graph stage record context`
- `5edc4ed` `Extract camera frame render-graph stage pass runtime`
- `ac836ae` `Extract camera frame stage surface resolver`
- `00fa6ff` `Group camera frame fullscreen color chain intent`
- `0afcaa0` `Move render-graph stage policy into internal host`
当前验证基线:
- `rendering_unit_tests`: `209 passed / 1 failed`
- 唯一剩余失败:`BuiltinForwardPipeline_Test.OpenGLRuntimeTranspilesForwardShadowVariantToLegacyClipConventions`
- `editor_tests`: `191 passed`
- `XCEditor` 12s smoke通过
## 3. 当前还没收口的核心问题
### 3.1 `CameraFramePlanBuilder` 仍然过重
现在 `AttachFullscreenStageRequests` 还在同时做:
- camera 后处理/最终输出需求判断
- graph-managed color chain 规划
- sequence 绑定
- destination/source 语义拼装
这说明 planning 还是有点像“半执行期拼装器”。
### 3.2 `CameraFramePlan` 还没有完全变成高层意图对象
虽然已经收掉了一批低层 getter也把 fullscreen chain 收成了 `colorChain`,但 plan 里仍然保留着一些偏执行期的结构和默认推导痕迹。
后面如果直接在这上面做 SRP暴露出去的 API 很容易绑定到当前 native 内部实现细节。
### 3.3 还缺一个稳定的 native renderer builder 接缝
现在的 RenderGraph host 已经能工作,但还没有一个非常明确的 “renderer 向 graph 注册本帧 pass” 的正式层。
这层不稳定,后面:
- forward renderer
- deferred renderer
- feature injection
- C# SRP host
都会继续直接碰当前 host 细节。
## 4. 本阶段总目标
这一阶段结束后native 渲染主线要更接近:
`Scene/Camera Planning -> CameraFramePlan -> Native Renderer Builder -> RenderGraph Host -> Execute`
也就是:
- `Planning` 只负责“这一帧需要什么”。
- `Renderer Builder` 只负责“把这些意图翻译成 graph passes/resources”。
- `RenderGraph Host` 只负责“记录、编译、执行图”。
## 5. 具体执行顺序
### 5.1 先拆 `CameraFramePlanBuilder`
优先拆出与 fullscreen chain 相关的独立 planner / helper目标是让 `SceneRenderer``CameraFramePlanBuilder` 不再直接持有一坨后处理/最终输出拼装逻辑。
建议收口方向:
- `CameraFrameFullscreenStagePlanner`
- `CameraFrameColorChainPlanner`
- `CameraFramePlanValidation` 或等价 helper
### 5.2 再把 plan 进一步高层化
继续减少 plan 里和具体 surface/source/sequence 拼装绑定过深的部分,让 plan 更像:
- 有哪些 stage
- stage 之间是什么来源关系
- 哪些输出是 graph-managed
- 哪些是 external/imported target
而不是继续堆更多执行期细节。
### 5.3 引入 native renderer builder 接缝
在 native 侧明确一层“renderer 向 RenderGraph 贡献 pass”的正式接口。
这一层的目标不是立刻做成用户 API而是先给
- `BuiltinForwardPipeline`
- 未来 `DeferredRenderer`
- 未来 `RendererFeature`
提供统一宿主。
### 5.4 最后判断是否进入 SRP
当且仅当下面三件事成立,才正式开始做 SRP
- `Planning``Execution` 边界稳定。
- `Renderer Builder` 这一层稳定。
- 新增渲染能力时,不需要再直接改 `CameraRenderer` 主线大函数。
## 6. 何时可以开始做 SRP
当前结论仍然是:
还不能正式开始做面向用户的 `C# SRP`
可以开始 SRP 的条件不是“已经有 RenderGraph 了”,而是:
1. `CameraFramePlanBuilder` 已收口。
2. `CameraFramePlan` 已足够高层稳定。
3. native `renderer builder` 接缝已存在。
4. `BuiltinForwardPipeline` 已经可以被看成一个 builtin renderer而不是一坨 host 逻辑。
满足这四条之后,就可以开始第一版 SRP。
我的判断是:
- 如果第四阶段顺利SRP 就已经进入可启动状态。
- 也就是说SRP 不是现在开。
- SRP 是这一个 native 收口阶段之后开。
## 7. 本阶段完成标准
满足下面这些条件,就可以认定第四阶段收口:
- `CameraFramePlanBuilder` 明显变薄fullscreen/color-chain planning 被拆出。
- `CameraFramePlan` 不再继续回流低层执行细节。
- native 出现稳定的 renderer builder 接缝。
- `CameraRenderer` 继续变薄,没有重新长回大调度函数。
- `rendering_unit_tests / editor_tests / XCEditor smoke` 继续稳定通过。
## 8. 阶段后下一步
第四阶段完成后,下一步就是正式开:
`SRP Host v1`
但第一版 SRP 仍然只建议做到:
- native host + C# 管线入口
- C# 侧可组织 renderer / feature / pass
- 先复用 builtin forward renderer
不建议一上来就同时做:
- 完整 deferred
- 完整 URP 包
- 全量 renderer feature 生态
正确顺序仍然是:
1. 先 native 收口
2. 再 SRP host
3. 再 URP-like builtin package
4. 最后再 deferred / 更复杂 feature

View File

@@ -583,6 +583,8 @@ add_library(XCEngine STATIC
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Planning/CameraFramePlanBuilder.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Planning/CameraFramePlanBuilder.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Planning/SceneRenderRequestPlanner.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Planning/Internal/CameraFrameFullscreenStagePlanner.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Planning/Internal/CameraFrameFullscreenStagePlanner.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Planning/Internal/DirectionalShadowPlanning.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Planning/Internal/DirectionalShadowPlanning.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Shadow/DirectionalShadowData.cpp

View File

@@ -1,9 +1,7 @@
#include "Rendering/Planning/CameraFramePlanBuilder.h"
#include "Components/CameraComponent.h"
#include "Debug/Logger.h"
#include "Rendering/Planning/CameraPostProcessPassFactory.h"
#include "Rendering/Planning/FinalColorPassFactory.h"
#include "Rendering/Planning/Internal/CameraFrameFullscreenStagePlanner.h"
#include "Rendering/RenderPipelineAsset.h"
namespace XCEngine {
@@ -55,57 +53,10 @@ void CameraFramePlanBuilder::AttachFullscreenStageRequests(
for (size_t index = 0; index < plans.size(); ++index) {
CameraFramePlan& plan = plans[index];
if (plan.request.camera == nullptr ||
plan.request.context.device == nullptr ||
!HasValidColorTarget(plan.request.surface)) {
continue;
}
std::unique_ptr<RenderPassSequence> postProcessSequence =
BuildCameraPostProcessPassSequence(plan.request.camera->GetPostProcessPasses());
std::unique_ptr<RenderPassSequence> finalOutputSequence =
BuildFinalColorPassSequence(plan.finalColorPolicy);
const bool hasPostProcess =
postProcessSequence != nullptr && postProcessSequence->GetPassCount() > 0u;
const bool hasFinalOutput =
finalOutputSequence != nullptr && finalOutputSequence->GetPassCount() > 0u;
if (!hasPostProcess && !hasFinalOutput) {
continue;
}
if (plan.request.surface.GetSampleCount() > 1u) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
"SceneRenderer fullscreen post-process/final-output chain currently requires a single-sample main scene surface");
continue;
}
if (hasPostProcess) {
m_ownedPostProcessSequences[index] = std::move(postProcessSequence);
plan.postProcess.passes = m_ownedPostProcessSequences[index].get();
plan.colorChain.usesGraphManagedMainSceneColor = true;
plan.colorChain.postProcess.source = CameraFrameColorSource::MainSceneColor;
plan.colorChain.postProcess.usesGraphManagedOutputColor = hasFinalOutput;
if (!hasFinalOutput) {
plan.postProcess.destinationSurface = plan.request.surface;
}
}
if (hasFinalOutput) {
m_ownedFinalOutputSequences[index] = std::move(finalOutputSequence);
plan.finalOutput.passes = m_ownedFinalOutputSequences[index].get();
plan.colorChain.usesGraphManagedMainSceneColor = true;
plan.colorChain.finalOutput.source =
hasPostProcess
? CameraFrameColorSource::PostProcessColor
: CameraFrameColorSource::MainSceneColor;
plan.finalOutput.destinationSurface = plan.request.surface;
}
if (plan.UsesGraphManagedMainSceneColor()) {
plan.ConfigureGraphManagedMainSceneSurface();
}
Internal::PlanCameraFrameFullscreenStages(
plan,
m_ownedPostProcessSequences[index],
m_ownedFinalOutputSequences[index]);
}
}

View File

@@ -0,0 +1,76 @@
#include "Rendering/Planning/Internal/CameraFrameFullscreenStagePlanner.h"
#include "Components/CameraComponent.h"
#include "Debug/Logger.h"
#include <XCEngine/Rendering/Execution/CameraFramePlan.h>
#include <XCEngine/Rendering/Planning/CameraPostProcessPassFactory.h>
#include <XCEngine/Rendering/Planning/FinalColorPassFactory.h>
namespace XCEngine {
namespace Rendering {
namespace Internal {
void PlanCameraFrameFullscreenStages(
CameraFramePlan& plan,
std::unique_ptr<RenderPassSequence>& ownedPostProcessSequence,
std::unique_ptr<RenderPassSequence>& ownedFinalOutputSequence) {
ownedPostProcessSequence.reset();
ownedFinalOutputSequence.reset();
if (plan.request.camera == nullptr ||
plan.request.context.device == nullptr ||
!HasValidColorTarget(plan.request.surface)) {
return;
}
std::unique_ptr<RenderPassSequence> postProcessSequence =
BuildCameraPostProcessPassSequence(plan.request.camera->GetPostProcessPasses());
std::unique_ptr<RenderPassSequence> finalOutputSequence =
BuildFinalColorPassSequence(plan.finalColorPolicy);
const bool hasPostProcess =
postProcessSequence != nullptr && postProcessSequence->GetPassCount() > 0u;
const bool hasFinalOutput =
finalOutputSequence != nullptr && finalOutputSequence->GetPassCount() > 0u;
if (!hasPostProcess && !hasFinalOutput) {
return;
}
if (plan.request.surface.GetSampleCount() > 1u) {
Debug::Logger::Get().Error(
Debug::LogCategory::Rendering,
"SceneRenderer fullscreen post-process/final-output chain currently requires a single-sample main scene surface");
return;
}
if (hasPostProcess) {
ownedPostProcessSequence = std::move(postProcessSequence);
plan.postProcess.passes = ownedPostProcessSequence.get();
plan.colorChain.usesGraphManagedMainSceneColor = true;
plan.colorChain.postProcess.source = CameraFrameColorSource::MainSceneColor;
plan.colorChain.postProcess.usesGraphManagedOutputColor = hasFinalOutput;
if (!hasFinalOutput) {
plan.postProcess.destinationSurface = plan.request.surface;
}
}
if (hasFinalOutput) {
ownedFinalOutputSequence = std::move(finalOutputSequence);
plan.finalOutput.passes = ownedFinalOutputSequence.get();
plan.colorChain.usesGraphManagedMainSceneColor = true;
plan.colorChain.finalOutput.source =
hasPostProcess
? CameraFrameColorSource::PostProcessColor
: CameraFrameColorSource::MainSceneColor;
plan.finalOutput.destinationSurface = plan.request.surface;
}
if (plan.UsesGraphManagedMainSceneColor()) {
plan.ConfigureGraphManagedMainSceneSurface();
}
}
} // namespace Internal
} // namespace Rendering
} // namespace XCEngine

View File

@@ -0,0 +1,21 @@
#pragma once
#include <XCEngine/Rendering/RenderPass.h>
#include <memory>
namespace XCEngine {
namespace Rendering {
struct CameraFramePlan;
namespace Internal {
void PlanCameraFrameFullscreenStages(
CameraFramePlan& plan,
std::unique_ptr<RenderPassSequence>& ownedPostProcessSequence,
std::unique_ptr<RenderPassSequence>& ownedFinalOutputSequence);
} // namespace Internal
} // namespace Rendering
} // namespace XCEngine