docs: sync rendering pass execution docs

This commit is contained in:
2026-04-03 15:10:37 +08:00
parent d4afa022c1
commit 19bd38ab15
59 changed files with 1544 additions and 420 deletions

View File

@@ -0,0 +1,53 @@
# ApplySceneViewportRenderPlan
**命名空间**: `XCEngine::Editor`
**类型**: `function`
**源文件**: `editor/src/Viewport/SceneViewportRenderPlan.h`
## 签名
```cpp
void ApplySceneViewportRenderPlan(
const ViewportRenderTargets& targets,
SceneViewportRenderPlan& plan,
Rendering::CameraRenderRequest& request);
```
## 作用
把已经构建好的 `SceneViewportRenderPlan` 写入目标 `CameraRenderRequest`
## 当前实现行为
当前函数会先调用:
```cpp
ApplySceneViewportRenderRequestSetup(
targets,
&plan.postScenePasses,
request);
```
然后继续做两件事:
1. 如果 `plan.HasOverlayPasses()` 为真,把 `request.overlayPasses` 指向 `plan.overlayPasses`
2. 无条件把 `plan.hasClearColorOverride``plan.clearColorOverride` 复制到 request
## 当前语义边界
- object-id surface 和 `postScenePasses` 的挂接规则,仍由 `ApplySceneViewportRenderRequestSetup(...)` 定义。
- 该函数不负责生成 `request.surface`,也不负责构建 plan只负责“把 plan 应用到 request”。
- `overlayPasses` 只有在非空时才会挂到 request空序列不会暴露出去。
## 测试锚点
- `ApplySceneViewportRenderPlanAttachesPlannedPassesAndClearState`
## 相关文档
- [SceneViewportRenderPlan](SceneViewportRenderPlan.md)
- [HasOverlayPasses](HasOverlayPasses.md)
- [BuildSceneViewportRenderPlan](BuildSceneViewportRenderPlan.md)
- [ViewportHostRenderFlowUtils](../ViewportHostRenderFlowUtils/ViewportHostRenderFlowUtils.md)

View File

@@ -0,0 +1,68 @@
# BuildSceneViewportRenderPlan
**命名空间**: `XCEngine::Editor`
**类型**: `function`
**源文件**: `editor/src/Viewport/SceneViewportRenderPlan.h`
## 签名
```cpp
SceneViewportRenderPlanBuildResult BuildSceneViewportRenderPlan(
const ViewportRenderTargets& targets,
const SceneViewportOverlayData& overlay,
const std::vector<uint64_t>& selectedObjectIds,
const SceneViewportOverlayFrameData& editorOverlayFrameData,
const SceneViewportOverlayFrameData& transientOverlayFrameData,
const SceneViewportGridPassFactory& gridPassFactory,
const SceneViewportSelectionOutlinePassFactory& selectionOutlinePassFactory,
const SceneViewportOverlayPassFactory& overlayPassFactory,
bool debugSelectionMask = false);
```
## 作用
根据当前 Scene View 的 overlay、选中对象和视口 render targets构建一份可直接应用到 `CameraRenderRequest` 的 render plan。
## 当前实现行为
当前函数会按下面顺序执行:
1.`overlay.valid == false`,直接返回默认结果。
2. 调用 `BuildSceneViewportGridPassData(...)` 生成 grid 参数。
3.`gridPassData.valid == true``gridPassFactory` 非空,则创建 grid pass 并加入 `result.plan.postScenePasses`
4.`selectedObjectIds` 非空,并且 `targets.objectIdShaderView``selectionOutlinePassFactory` 都可用,则创建 selection outline pass 并加入 `result.plan.postScenePasses`
5. 若选中了对象,但缺少 `objectIdShaderView`,且 `debugSelectionMask == false`,则写入警告文案 `Scene object id shader view is unavailable`
6.`editorOverlayFrameData``transientOverlayFrameData` 合并成一份最终 overlay frame data。
7. 若合成后的 frame data 含有 overlay primitive`overlayPassFactory` 非空,则调用 factory 创建 overlay pass。
8. 若 factory 返回非空 pass则把它加入 `result.plan.overlayPasses`
## 当前返回值语义
- `result.plan.postScenePasses`
- 当前承载 Scene View 的 grid 和 selection outline 两类附加 pass。
- `result.plan.overlayPasses`
- 承载 editor overlay / transient overlay 合并后的最终叠加层。
- `result.warningStatusText`
- 当前主要用于提示“Scene object id shader view is unavailable”这类可降级的 Scene View 缺口。
## 测试锚点
- `BuildSceneViewportRenderPlanCollectsPostSceneAndOverlayPasses`
- `BuildSceneViewportRenderPlanWarnsWhenSelectionOutlineCannotAccessObjectIdTexture`
这些测试确认了:
- grid、selection outline 和 overlay pass 的规划入口已经拆开
- overlay frame data 会先合并,再交给 overlay factory
- object-id SRV 缺失时会返回警告而不是直接崩掉整条 Scene View 组装链
## 相关文档
- [SceneViewportRenderPlan](SceneViewportRenderPlan.md)
- [SceneViewportRenderPlanBuildResult](SceneViewportRenderPlanBuildResult.md)
- [SceneViewportGridPassFactory](SceneViewportGridPassFactory.md)
- [SceneViewportSelectionOutlinePassFactory](SceneViewportSelectionOutlinePassFactory.md)
- [SceneViewportOverlayPassFactory](SceneViewportOverlayPassFactory.md)
- [ApplySceneViewportRenderPlan](ApplySceneViewportRenderPlan.md)

View File

@@ -0,0 +1,33 @@
# SceneViewportRenderPlan::HasOverlayPasses
**命名空间**: `XCEngine::Editor`
**类型**: `method`
**源文件**: `editor/src/Viewport/SceneViewportRenderPlan.h`
## 签名
```cpp
bool HasOverlayPasses() const;
```
## 作用
判断当前 render plan 是否真的持有 overlay pass。
## 当前实现行为
当前只是返回:
```cpp
overlayPasses.GetPassCount() > 0
```
这也是 [ApplySceneViewportRenderPlan](ApplySceneViewportRenderPlan.md) 决定是否把 `request.overlayPasses` 指向 `plan.overlayPasses` 的直接依据。
## 相关文档
- [SceneViewportRenderPlan](SceneViewportRenderPlan.md)
- [HasPostScenePasses](HasPostScenePasses.md)
- [ApplySceneViewportRenderPlan](ApplySceneViewportRenderPlan.md)

View File

@@ -0,0 +1,33 @@
# SceneViewportRenderPlan::HasPostScenePasses
**命名空间**: `XCEngine::Editor`
**类型**: `method`
**源文件**: `editor/src/Viewport/SceneViewportRenderPlan.h`
## 签名
```cpp
bool HasPostScenePasses() const;
```
## 作用
判断当前 render plan 是否真的持有 post-scene pass。
## 当前实现行为
当前只是返回:
```cpp
postScenePasses.GetPassCount() > 0
```
因此空的 `RenderPassSequence` 不会被视为“已配置 post-scene pass”。
## 相关文档
- [SceneViewportRenderPlan](SceneViewportRenderPlan.md)
- [HasOverlayPasses](HasOverlayPasses.md)
- [ApplySceneViewportRenderPlan](ApplySceneViewportRenderPlan.md)

View File

@@ -0,0 +1,29 @@
# SceneViewportGridPassFactory
**命名空间**: `XCEngine::Editor`
**类型**: `type alias`
**源文件**: `editor/src/Viewport/SceneViewportRenderPlan.h`
## 定义
```cpp
using SceneViewportGridPassFactory =
std::function<std::unique_ptr<Rendering::RenderPass>(const Rendering::Passes::InfiniteGridPassData&)>;
```
## 作用
描述一个“根据 Scene View 相机数据创建网格 pass”的工厂回调。
## 当前语义
- 输入是一份已经由 `BuildSceneViewportGridPassData(...)` 组装好的 `InfiniteGridPassData`
- 输出是要追加到 `SceneViewportRenderPlan::postScenePasses` 的一个 `RenderPass`
- 返回 `nullptr` 时,本帧不会追加网格 pass。
## 相关文档
- [SceneViewportRenderPlan](SceneViewportRenderPlan.md)
- [BuildSceneViewportRenderPlan](BuildSceneViewportRenderPlan.md)

View File

@@ -0,0 +1,31 @@
# SceneViewportOverlayPassFactory
**命名空间**: `XCEngine::Editor`
**类型**: `type alias`
**源文件**: `editor/src/Viewport/SceneViewportRenderPlan.h`
## 定义
```cpp
using SceneViewportOverlayPassFactory =
std::function<std::unique_ptr<Rendering::RenderPass>(const SceneViewportOverlayFrameData&)>;
```
## 作用
描述一个“由合成后的 overlay frame data 生成 GPU overlay pass”的工厂回调。
## 当前语义
- 输入是已经合并好的 `SceneViewportOverlayFrameData`
- 输出是要追加到 `SceneViewportRenderPlan::overlayPasses` 的一个 `RenderPass`
- 返回 `nullptr` 时,当前帧不会追加 overlay pass
这让 [BuildSceneViewportRenderPlan](BuildSceneViewportRenderPlan.md) 自己不依赖具体 pass 类型,只负责决定“何时创建 overlay pass、何时跳过”。
## 相关文档
- [SceneViewportRenderPlan](SceneViewportRenderPlan.md)
- [BuildSceneViewportRenderPlan](BuildSceneViewportRenderPlan.md)

View File

@@ -0,0 +1,69 @@
# SceneViewportRenderPlan
**命名空间**: `XCEngine::Editor`
**类型**: `structs + aliases + free functions`
**源文件**: `editor/src/Viewport/SceneViewportRenderPlan.h`
**描述**: 把 Scene View 一次渲染提交所需的 post-scene pass、overlay pass 和清屏色覆盖收口成可复用的轻量 request plan。
## 概览
`SceneViewportRenderPlan.h` 处在 [ViewportHostRenderFlowUtils](../ViewportHostRenderFlowUtils/ViewportHostRenderFlowUtils.md) 和 [ViewportHostService](../ViewportHostService/ViewportHostService.md) 之间。
它解决的不是“如何渲染 Scene View”而是“如何把这一帧 Scene View 额外挂到 `CameraRenderRequest` 上的东西组织好”:
- post-scene pass 序列
- overlay pass 序列
- 默认 clear-color override
这样 `ViewportHostService` 的 Scene View 路径可以更像“构建 request -> 执行 request”而不是把所有额外 pass 拼装细节都塞在一个方法里。
## 公开类型与函数
| 成员 | 说明 |
|------|------|
| [SceneViewportGridPassFactory](SceneViewportGridPassFactory.md) | 根据 `InfiniteGridPassData` 生成 grid `RenderPass` 的工厂回调。 |
| [SceneViewportSelectionOutlinePassFactory](SceneViewportSelectionOutlinePassFactory.md) | 根据 object-id SRV、选中对象和样式生成 selection outline `RenderPass` 的工厂回调。 |
| [SceneViewportOverlayPassFactory](SceneViewportOverlayPassFactory.md) | 根据合成后的 overlay frame data 生成 GPU overlay pass 的工厂回调。 |
| [SceneViewportRenderPlanBuildResult](SceneViewportRenderPlanBuildResult.md) | 返回 render plan 与可选警告文案。 |
| [HasPostScenePasses](HasPostScenePasses.md) | 判断当前计划是否包含 post-scene pass。 |
| [HasOverlayPasses](HasOverlayPasses.md) | 判断当前计划是否包含 overlay pass。 |
| [BuildSceneViewportRenderPlan](BuildSceneViewportRenderPlan.md) | 根据 targets、overlay 和当前帧 overlay 数据构建 render plan。 |
| [ApplySceneViewportRenderPlan](ApplySceneViewportRenderPlan.md) | 把 render plan 写回 `CameraRenderRequest`。 |
## 当前实现行为
`SceneViewportRenderPlan.h``tests/editor/test_viewport_render_flow_utils.cpp` 的当前实现:
- `BuildSceneViewportRenderPlan(...)``overlay.valid == false` 时直接返回默认结果。
- grid pass 由 `BuildSceneViewportGridPassData(...)` + `SceneViewportGridPassFactory` 决定是否加入 `plan.postScenePasses`
- selection outline pass 由 `targets.objectIdShaderView``selectedObjectIds``SceneViewportSelectionOutlinePassFactory` 决定是否加入 `plan.postScenePasses`
- 如果选中了对象但缺少 `objectIdShaderView`,且不是 debug mask 模式,则会返回警告文案 `Scene object id shader view is unavailable`
- overlay pass 的输入不是单独一份 editor overlay而是 `editorOverlayFrameData + transientOverlayFrameData` 合并后的结果。
- 只有合成后的 overlay frame data 确实含有 primitive且 factory 非空、factory 返回值也非空时,才会向 `overlayPasses` 里追加 pass。
- `ApplySceneViewportRenderPlan(...)` 会继续复用 `ApplySceneViewportRenderRequestSetup(...)` 处理 object-id surface 与 `postScenePasses`,然后再补上 `overlayPasses` 和 clear-color override。
## 测试锚点
当前至少有两条直接测试覆盖:
- `BuildSceneViewportRenderPlanCollectsPostSceneAndOverlayPasses`
- `ApplySceneViewportRenderPlanAttachesPlannedPassesAndClearState`
## 设计说明
把 Scene View request 组装拆成“构建计划”和“应用计划”两段,有两个现实收益:
- 可以单独测试 plan 的拼装逻辑,而不必把整个 `ViewportHostService` 拉起来。
- 后续如果 Scene View 再增加 post-scene pass 或 overlay pass扩写点会集中在这一层 helper而不是继续膨胀 `RenderSceneViewportEntry(...)`
## 相关文档
- [Viewport](../Viewport.md)
- [ViewportHostRenderFlowUtils](../ViewportHostRenderFlowUtils/ViewportHostRenderFlowUtils.md)
- [ViewportHostService](../ViewportHostService/ViewportHostService.md)
- [SceneViewportGridPassFactory](SceneViewportGridPassFactory.md)
- [SceneViewportSelectionOutlinePassFactory](SceneViewportSelectionOutlinePassFactory.md)
- [SceneViewportEditorOverlayPass](../Passes/SceneViewportEditorOverlayPass/SceneViewportEditorOverlayPass.md)

View File

@@ -0,0 +1,34 @@
# SceneViewportRenderPlanBuildResult
**命名空间**: `XCEngine::Editor`
**类型**: `struct`
**源文件**: `editor/src/Viewport/SceneViewportRenderPlan.h`
## 定义
```cpp
struct SceneViewportRenderPlanBuildResult {
SceneViewportRenderPlan plan = {};
const char* warningStatusText = nullptr;
};
```
## 字段
| 字段 | 类型 | 说明 |
|------|------|------|
| `plan` | `SceneViewportRenderPlan` | 当前帧构建出的渲染计划。 |
| `warningStatusText` | `const char*` | 可选警告文案,用于提示可降级但不必直接终止 Scene View 渲染的缺口。 |
## 当前实现语义
- 即使存在 `warningStatusText``plan` 也仍可能是可执行的有效计划。
- 当前最典型的警告来源,是 Scene View 有选中对象、需要 selection outline`objectIdShaderView` 不可用时返回的 `Scene object id shader view is unavailable`
## 相关文档
- [SceneViewportRenderPlan](SceneViewportRenderPlan.md)
- [BuildSceneViewportRenderPlan](BuildSceneViewportRenderPlan.md)
- [ViewportHostRenderFlowUtils](../ViewportHostRenderFlowUtils/ViewportHostRenderFlowUtils.md)

View File

@@ -0,0 +1,31 @@
# SceneViewportSelectionOutlinePassFactory
**命名空间**: `XCEngine::Editor`
**类型**: `type alias`
**源文件**: `editor/src/Viewport/SceneViewportRenderPlan.h`
## 定义
```cpp
using SceneViewportSelectionOutlinePassFactory = std::function<std::unique_ptr<Rendering::RenderPass>(
RHI::RHIResourceView*,
const std::vector<uint64_t>&,
const Rendering::Passes::ObjectIdOutlineStyle&)>;
```
## 作用
描述一个“根据 object-id SRV、选中对象列表和 style 创建 selection outline pass”的工厂回调。
## 当前语义
- 输入 object-id SRV 必须由上层 render target 流程准备好。
- 输出 pass 会追加到 `SceneViewportRenderPlan::postScenePasses`
- 返回 `nullptr` 时,本帧不会追加 selection outline pass。
## 相关文档
- [SceneViewportRenderPlan](SceneViewportRenderPlan.md)
- [BuildSceneViewportRenderPlan](BuildSceneViewportRenderPlan.md)

View File

@@ -1,40 +0,0 @@
# BuiltinPostProcessRequest
**命名空间**: `XCEngine::Rendering`
**类型**: `struct`
**头文件**: `XCEngine/Rendering/CameraRenderRequest.h`
**描述**: 描述 scene viewport 一类内建后处理请求,包括无限网格、选中轮廓和调试遮罩所需输入。
## 概述
`BuiltinPostProcessRequest` 是 [CameraRenderRequest](../CameraRenderRequest.md) 里的内建后处理子请求。它不直接决定执行哪些 GPU 步骤,而是把原始输入交给 `BuiltinPostProcessPassSequenceBuilder` 去规划。
## 字段
| 字段 | 说明 |
|------|------|
| `gridPassData` | 控制是否绘制 scene viewport 的无限网格。 |
| `objectIdTextureView` | 供 outline / debug mask 读取的 object-id 纹理 SRV。 |
| `selectedObjectIds` | 当前需要描边或调试的对象 ID 集合。 |
| `outlineStyle` | 轮廓颜色、宽度和调试遮罩开关。 |
## 关键语义
- [IsRequested](IsRequested.md) 当前只看 `gridPassData.valid``selectedObjectIds`,不会因为单独设置了 `objectIdTextureView``outlineStyle.debugSelectionMask` 就自动变成“已请求”。
- 这意味着 `objectIdTextureView` 更像是 outline / debug 路径的依赖输入,而不是请求开关本身。
- 更细的回退策略和步骤顺序由 `BuiltinPostProcessPassSequenceBuilder``BuiltinPostProcessPassPlan` 决定。
## 真实使用位置
- 编辑器 scene viewport 会构造这份请求,把无限网格、选中实体 ID 和 outline style 传进 `CameraRenderer`
- `CameraRenderer::Render()` 在主场景和 `postScenePasses` 之后,调用 builtin post-process builder 处理这里的内容。
## 相关文档
- [CameraRenderRequest](../CameraRenderRequest.md)
- [IsRequested](IsRequested.md)
- [Passes](../../Passes/Passes.md)
- [BuiltinPostProcessPassSequenceBuilder](../../Passes/BuiltinPostProcessPassSequenceBuilder/BuiltinPostProcessPassSequenceBuilder.md)

View File

@@ -1,32 +0,0 @@
# BuiltinPostProcessRequest::IsRequested
检查当前是否请求了 builtin 后处理。
```cpp
bool IsRequested() const;
```
## 行为说明
当前头文件内联实现只有一条规则:
```cpp
return gridPassData.valid || !selectedObjectIds.empty();
```
## 返回值
-`gridPassData.valid` 为真,或 `selectedObjectIds` 非空时,返回 `true`
- 否则返回 `false`
## 注意事项
- 单独设置 `objectIdTextureView` 不会让请求生效。
- 单独把 `outlineStyle.debugSelectionMask` 设成 `true` 也不会让请求生效;仍然需要 grid 或 selection 至少有一个真正开启。
- 这也是为什么 scene viewport 必须显式提供选中对象 IDoutline / debug mask 路径才会进入 builder。
## 相关文档
- [返回类型总览](BuiltinPostProcessRequest.md)
- [CameraRenderRequest](../CameraRenderRequest.md)
- [BuiltinPostProcessPassSequenceBuilder](../../Passes/BuiltinPostProcessPassSequenceBuilder/BuiltinPostProcessPassSequenceBuilder.md)

View File

@@ -6,19 +6,18 @@
**头文件**: `XCEngine/Rendering/CameraRenderRequest.h`
**描述**: 把一次单相机渲染提交所需的场景、目标表面、object-id 输出和 builtin 后处理配置打包成显式请求对象。
**描述**: 把一次单相机提交所需的场景、目标表面、object-id 输出、清屏覆盖和可选 pass 序列打包成显式请求对象。
## 概览
`CameraRenderRequest` 是 [SceneRenderer](../SceneRenderer/SceneRenderer.md) 和 [CameraRenderer](../CameraRenderer/CameraRenderer.md) 之间的主要数据契约。它不负责自己执行绘制,而是把这次提交要用到的输入集中起来
`CameraRenderRequest` 是 [SceneRenderer](../SceneRenderer/SceneRenderer.md) 和 [CameraRenderer](../CameraRenderer/CameraRenderer.md) 之间的主要数据契约。它本身不执行渲染,只负责描述“这一台相机这次要怎么提交”
- 主场景渲染的 `scene``camera``context``surface`
- 主场景渲染所需`scene``camera``context``surface`
- 可选的 object-id 辅助输出 `objectId`
- 可选的 builtin 后处理 `builtinPostProcess`
- 多相机排序与清屏控制字段
- 在主场景前后插入自定义 pass 的钩子
- 多相机排序和清屏覆盖字段
- 主场景前后与最终叠加阶段要执行的可选 pass 序列
相比把这些参数散落在多个函数签名里,显式请求对象更适合表达“这次 camera submission 的完整意图”,也更利于测试和编辑器视口复用
Editor Scene View 的网格和选中轮廓,当前由 editor 侧先组装进 `postScenePasses` / `overlayPasses`,再随 request 一起交给 [CameraRenderer](../CameraRenderer/CameraRenderer.md) 执行
## 核心字段
@@ -28,59 +27,62 @@
| `camera` | 本次渲染使用的相机组件。 |
| `context` | 当前帧的 RHI 上下文。 |
| `surface` | 主颜色输出目标及其 render area。 |
| `objectId` | 可选的 object-id 辅助输出。 |
| `cameraDepth` | 多相机排序辅助值。 |
| `cameraStackOrder` | base / overlay camera stack 的附加排序键。 |
| `clearFlags` | `RenderSceneData::cameraData.clearFlags` 的覆盖值。 |
| `hasClearColorOverride` / `clearColorOverride` | 是否覆盖提取的相机清屏色。 |
| `preScenePasses` | 主管线执行前的可选 pass 序列。 |
| `postScenePasses` | 主管线与 builtin post-process 之间的可选 pass 序列。 |
| `overlayPasses` | builtin post-process 之后的可选叠加 pass 序列。 |
| `cameraStackOrder` | `Base` / `Overlay` camera stack 的附加排序键。 |
| `clearFlags` | 回写到 `RenderSceneData::cameraData.clearFlags` 的覆盖值。 |
| `hasClearColorOverride` / `clearColorOverride` | 是否覆盖提取的相机清屏色。 |
| `preScenePasses` | 主管线执行前的可选 `RenderPassSequence*`。 |
| `postScenePasses` | 主场景和 object-id 之后、overlay 之前执行的可选 `RenderPassSequence*`。 |
| `overlayPasses` | 最后叠加到主颜色目标上的可选 `RenderPassSequence*`。 |
## object-id 与 builtin 后处理
## `objectId`
### `objectId`
`objectId` 是一个 [ObjectIdRenderRequest](ObjectIdRenderRequest/ObjectIdRenderRequest.md),用于描述“是否要额外输出一张 object-id 辅助表面”。
`objectId` 是一个 [ObjectIdRenderRequest](ObjectIdRenderRequest/ObjectIdRenderRequest.md),用于描述“是否要额外渲染一张 object-id 辅助纹理”
- `IsRequested()` 只检查 `surface` 是否挂了颜色附件。
- `IsRequested()` 只检查 `surface` 是否真的挂了颜色附件
- `IsValid()` 进一步要求:
- 第一颜色附件非空
- 第一颜色附件非空
- 深度附件非空
- render area 宽高大于 `0`
`CameraRenderer::Render()` 当前会在主场景之后、`postScenePasses` 之前执行 object-id pass
`engine/src/Rendering/CameraRenderer.cpp` 的当前实现,`CameraRenderer::Render()` 会在主场景主管线之后、`postScenePasses` 之前执行当前的 [ObjectIdPass](../ObjectIdPass/ObjectIdPass.md)
### `builtinPostProcess`
## pass 序列字段
`builtinPostProcess` 是一个 [BuiltinPostProcessRequest](BuiltinPostProcessRequest/BuiltinPostProcessRequest.md),用于描述 scene viewport 一类内建附加效果:
`preScenePasses``postScenePasses``overlayPasses` 都是非拥有指针。调用方需要保证这些序列在本次 `Render()` 完成前保持有效。
- `gridPassData` 控制无限网格
- `objectIdTextureView` 把 object-id 纹理作为 SRV 传给后处理
- `selectedObjectIds` 决定是否需要 selection outline / mask
- `outlineStyle` 控制轮廓颜色、宽度与调试模式
- `preScenePasses` 适合放主场景之前的准备阶段。
- `postScenePasses` 适合放主场景和 object-id 之后的附加绘制。
- `overlayPasses` 适合放最后叠加到主颜色目标上的编辑器覆盖层。
它的 `IsRequested()` 规则很宽松:只要 `gridPassData.valid` 为真,或者 `selectedObjectIds` 非空,就会被视为请求了 builtin post-process。
Editor Scene View 当前会先通过 [SceneViewportRenderPlan](../../Editor/Viewport/SceneViewportRenderPlan/SceneViewportRenderPlan.md) 组装一份 plan再由 [ApplySceneViewportRenderPlan](../../Editor/Viewport/SceneViewportRenderPlan/ApplySceneViewportRenderPlan.md) 和 [ViewportHostRenderFlowUtils](../../Editor/Viewport/ViewportHostRenderFlowUtils/ViewportHostRenderFlowUtils.md) 把:
- `objectId`
- `postScenePasses`
- `overlayPasses`
- clear-color override
写回到这份 request。
## 当前数据契约
- [IsValid](IsValid.md) 只检查 `scene``camera``context`,不会验证 `surface``objectId``builtinPostProcess` 是否完整。
- [IsValid](IsValid.md) 只检查 `scene``camera``context`,不会验证 `surface``objectId` 是否完整。
- `CameraRenderer::Render()` 会额外拒绝:
- `surface` 的 render area 宽高为 `0`
- `objectId.IsRequested()``objectId.IsValid()` 为假
- `builtinPostProcess.objectIdTextureView` 非空,但没有请求 `objectId` 输出
- builtin post-process 更细的回退逻辑由 `BuiltinPostProcessPassSequenceBuilder` 决定,而不是在 `CameraRenderRequest` 自身完成。
- `SceneRenderer::Render(const std::vector<CameraRenderRequest>&)` 会先检查每条 request 的 `IsValid()`,再按 `cameraStackOrder``cameraDepth` 排序后逐条提交。
## 真实使用位置
- `SceneRenderer` 负责构建和排序多个 `CameraRenderRequest`
- 编辑器 scene viewport 会在 `ViewportHostRenderFlowUtils.h` 里填充 `objectId``builtinPostProcess`
- `tests/Rendering/unit/test_camera_scene_renderer.cpp` 验证了 render area、clear override、object-id pass 与 builtin post-process 的真实接入时序
- Editor Scene View 当前通过 [SceneViewportRenderPlan](../../Editor/Viewport/SceneViewportRenderPlan/SceneViewportRenderPlan.md) 组装 `postScenePasses``overlayPasses` 与 clear override并通过 [ViewportHostRenderFlowUtils](../../Editor/Viewport/ViewportHostRenderFlowUtils/ViewportHostRenderFlowUtils.md) 挂入 object-id surface
- `tests/Rendering/unit/test_camera_scene_renderer.cpp` 明确验证了 `preScenePasses -> pipeline -> objectId -> postScenePasses` 的真实执行顺序,以及 request clear override / 排序规则
## 相关文档
- [IsValid](IsValid.md)
- [ObjectIdRenderRequest](ObjectIdRenderRequest/ObjectIdRenderRequest.md)
- [BuiltinPostProcessRequest](BuiltinPostProcessRequest/BuiltinPostProcessRequest.md)
- [CameraRenderer](../CameraRenderer/CameraRenderer.md)
- [SceneRenderer](../SceneRenderer/SceneRenderer.md)
- [RenderContext](../RenderContext/RenderContext.md)

View File

@@ -29,7 +29,7 @@ return scene != nullptr &&
## 这意味着
- `surface` 不参与这里的校验。
- `objectId``builtinPostProcess` 的完整性也不在这里检查。
- `objectId` 的完整性也不在这里检查。
- `cameraDepth``cameraStackOrder``clearFlags` 和 clear color override 都不会影响这里的结果。
因此它只是“最小可进入渲染器”的校验,而不是“这次请求一定能完整跑通”的保证。
@@ -40,7 +40,6 @@ return scene != nullptr &&
- `surface.GetRenderAreaWidth()` / `GetRenderAreaHeight()` 是否大于 `0`
- `objectId.IsRequested()``objectId.IsValid()` 是否为真
- `builtinPostProcess.objectIdTextureView` 非空时,是否同时请求了 `objectId`
## 返回值

View File

@@ -6,44 +6,43 @@
**头文件**: `XCEngine/Rendering/CameraRenderer.h`
**描述**: 单个 `CameraRenderRequest` 的执行器。它负责在一次相机提交里完成场景提取、主管线渲染、object-id 输出,以及 builtin post-process / overlay pass 串接。
**描述**: 单个 `CameraRenderRequest` 的执行器。它负责在一次相机提交里完成场景提取、主管线渲染、object-id 输出,以及调用方注入的 post / overlay pass 串接。
## 概览
`CameraRenderer` 是当前渲染模块里的“单请求执行层”。和 [SceneRenderer](../SceneRenderer/SceneRenderer.md) 的分工是:
- `SceneRenderer` 负责规划和排序 `CameraRenderRequest`
- `CameraRenderer` 负责把一份 request 真正跑完
- `SceneRenderer` 负责规划和排序 `CameraRenderRequest`
- `CameraRenderer` 负责把一份 request 真正跑完
因此这里关心的不是“该渲染哪些相机”,而是“这一相机请求如何被执行”。
因此这里关心的不是“该渲染哪些相机”,而是“这一相机请求如何被执行”。
## 当前持有的运行时对象
- `m_sceneExtractor`
使用 `request.surface` 的 render-area 尺寸调用 `ExtractForCamera(...)`,生成这次提交要用的 `RenderSceneData`
使用 `request.surface` 的 render area 尺寸调用 `ExtractForCamera(...)`,生成这次提交要用的 `RenderSceneData`
- `m_pipelineAsset`
当前主管线的来源工厂;可能为空。
- `m_pipeline`
当前实际执行主场景绘制的 runtime [RenderPipeline](../RenderPipeline/RenderPipeline.md)。
- `m_objectIdPass`
主场景之后的 object-id pass默认是 [Passes::BuiltinObjectIdPass](../Passes/BuiltinObjectIdPass/BuiltinObjectIdPass.md)。
- `m_builtinPostProcessBuilder`
把 builtin grid、selection outline、debug mask 请求翻译成临时 `RenderPassSequence`
这意味着 `CameraRenderer` 本身不负责替调用方生成 Scene View 专用效果;它只按 request 已给出的 `postScenePasses` / `overlayPasses` 顺序执行
## 当前执行顺序
对一份有效 request当前 `Render()` 的主顺序是:
1. 校验 request、自身主管线、render-areaobject-id 请求和 builtin post-process 前置条件。
1. 校验 request、自身主管线、render-areaobject-id 请求前置条件。
2.`m_sceneExtractor.ExtractForCamera(...)` 生成 `RenderSceneData`
3. 用 request 覆盖 `sceneData.cameraData.clearFlags`,必要时再覆盖 `clearColor`
4. 执行 `preScenePasses`
5. 执行 `m_pipeline->Render(...)`
6. 如请求了 object-id执行 `m_objectIdPass->Render(...)`
7. 执行 `postScenePasses`
8. 如请求了 builtin post-process先由 builder 生成临时 sequence再执行它
9. 执行 `overlayPasses`
10. 按已初始化的 sequence 逆向 `Shutdown()`
8. 执行 `overlayPasses`
9. 按已初始化的 sequence 逆向 `Shutdown()`
这条顺序被 `tests/Rendering/unit/test_camera_scene_renderer.cpp` 明确覆盖。
@@ -59,24 +58,23 @@
`Render()` 目前会显式拒绝:
- `request.IsValid()` 为假
- 当前 `m_pipeline == nullptr`
- `request.surface` 的 render-area 宽或高为 `0`
- 请求了 object-id`request.objectId.IsValid()` 为假
- 请求了 builtin post-process且提供了 `objectIdTextureView`,但这次 request 并没有真正请求 fresh object-id 输出。
- `request.IsValid()` 为假
- 当前 `m_pipeline == nullptr`
- `request.surface` 的 render-area 宽或高为 `0`
- 请求了 object-id`request.objectId.IsValid()` 为假
## 当前实现边界
- 只执行单个 request多相机排序、stack 顺序和 clear 规则在 `SceneRenderer` / `SceneRenderRequestPlanner` 层完成。
- 不直接决定“默认主管线是什么”,而是通过 pipeline asset seam 解决。
- builtin post-process 仍然是顺序执行的 pass sequence不是 render graph。
- post / overlay 阶段只是顺序执行的 `RenderPassSequence`,不是 render graph。
## 公开方法
| 方法 | 说明 |
|------|------|
| [Constructor](Constructor.md) | 构造 `CameraRenderer`,并建立默认或注入的主管线 / object-id pass。 |
| [Destructor](Destructor.md) | 关闭当前主管线object-id pass 和 builtin post-process builder。 |
| [Destructor](Destructor.md) | 关闭当前主管线object-id pass。 |
| [SetPipeline](SetPipeline.md) | 手动替换当前 runtime pipeline。 |
| [SetPipelineAsset](SetPipelineAsset.md) | 通过 `RenderPipelineAsset` 重建当前 runtime pipeline。 |
| [SetObjectIdPass](SetObjectIdPass.md) | 替换当前 object-id pass。 |
@@ -92,4 +90,5 @@
- [CameraRenderRequest](../CameraRenderRequest/CameraRenderRequest.md)
- [RenderSceneExtractor](../RenderSceneExtractor/RenderSceneExtractor.md)
- [RenderPipeline](../RenderPipeline/RenderPipeline.md)
- [ObjectIdPass](../ObjectIdPass/ObjectIdPass.md)
- [Scene Extraction And Builtin Forward Pipeline](../../../_guides/Rendering/Scene-Extraction-And-Builtin-Forward-Pipeline.md)

View File

@@ -14,7 +14,7 @@
## 作用
在对象销毁前关闭当前持有的主管线object-id pass 和 builtin post-process builder
在对象销毁前关闭当前持有的主管线object-id pass。
## 当前实现行为
@@ -22,14 +22,13 @@
1. 如果 `m_pipeline` 非空,调用 `m_pipeline->Shutdown()`
2. 如果 `m_objectIdPass` 非空,调用 `m_objectIdPass->Shutdown()`
3. 调用 `m_builtinPostProcessBuilder.Shutdown()`
随后由 `unique_ptr` 和成员对象自身析构负责释放内存。
## 设计含义
- 主管线和 object-id pass 的 GPU 资源回收路径是显式 `Shutdown()`,而不是只依赖 C++ 析构。
- builtin post-process builder 会代为清理它内部缓存的 builtin grid / outline pass
- 调用方注入到 `CameraRenderRequest` 里的 `RenderPassSequence` 并不由这里持有,因此也不在这里做统一回收
## 注意事项

View File

@@ -6,41 +6,39 @@ bool Render(const CameraRenderRequest& request);
## 行为说明
执行一次完整的单相机提交。当前实现会把主场景、object-id、注入 pass 和 builtin post-process 串成一条固定顺序的执行链。
执行一次完整的单相机提交。当前实现会把主场景、object-id、调用方注入的 post passesoverlay passes 串成一条固定顺序的执行链。
## 当前流程
1. 校验 `request.IsValid()`,并确认 `m_pipeline` 非空。
2. 拒绝 render-area 宽高为 `0` 的 surface。
2. 拒绝 render-area 宽高为 `0``surface`
3. 如果请求了 object-id要求 `request.objectId.IsValid()` 为真。
4. 如果 builtin post-process 依赖 `objectIdTextureView`,则要求本次 request 真的请求了 fresh object-id 输出
5. `request.surface.GetRenderAreaWidth()` / `GetRenderAreaHeight()` 调用 `m_sceneExtractor.ExtractForCamera(...)`
6. `sceneData.HasCamera()` 为假,则返回 `false`
7. 用 request 覆盖 `sceneData.cameraData.clearFlags`;如果 request 提供了 clear-color override则继续覆盖 `sceneData.cameraData.clearColor`
8. 依次初始化并执行:
4. `request.surface.GetRenderAreaWidth()` / `GetRenderAreaHeight()` 调用 `m_sceneExtractor.ExtractForCamera(...)`
5. `sceneData.HasCamera()` 为假,则返回 `false`
6. 用 request 覆盖 `sceneData.cameraData.clearFlags`;如果 request 提供了 clear-color override则继续覆盖 `sceneData.cameraData.clearColor`
7. 依次初始化并执行:
- `preScenePasses`
- `m_pipeline->Render(...)`
- `m_objectIdPass->Render(...)`
- `postScenePasses`
- builtin post-process 临时 sequence
- `overlayPasses`
9. 按已经成功初始化的 sequence 做逆向 `Shutdown()`
8. 按已经成功初始化的 sequence 做逆向 `Shutdown()`
## builtin post-process 接入点
## `postScenePasses` / `overlayPasses` 接入点
`request.builtinPostProcess.IsRequested()` 为真时,当前实现会:
`CameraRenderer` 只消费调用方传入的 pass 序列,不再额外构建内部后处理层。
1. 调用 `m_builtinPostProcessBuilder.Build(...)` 生成一条临时 `RenderPassSequence`
2. 对这条临时 sequence 调用 `Initialize(context)`
3.`postScenePasses` 之后、`overlayPasses` 之前执行它。
- `postScenePasses` 直接来自调用方提供的 `RenderPassSequence`
- `overlayPasses` 也是同样的注入点
- 编辑器 Scene View 当前会先通过 [SceneViewportRenderPlan](../../Editor/Viewport/SceneViewportRenderPlan/SceneViewportRenderPlan.md) 组装这些序列,再调用 `ApplySceneViewportRenderPlan(...)` 挂到 request
这意味着 scene viewport 的 builtin grid、selection outline 和 debug mask 都是主场景之后叠加的
这意味着 Scene View 的无限网格、选中轮廓和编辑器 overlay已经被上移到 request 组装层,不再由 `CameraRenderer` 内部再做一次翻译
## 失败与清理语义
- 任一验证失败会直接返回 `false`
- 任一 sequence 初始化失败会立执行对应 `Shutdown()` 回滚。
- 主主管线、object-id pass、post-process 或 overlay 任一阶段失败,都会按已经初始化过的 sequence 做清理再返回 `false`
- 任一 sequence 初始化失败会立执行对应 `Shutdown()` 回滚。
- 主主管线、object-id pass、post 或 overlay 任一阶段失败,都会按已经初始化过的 sequence 做清理再返回 `false`
## 参数
@@ -56,9 +54,9 @@ bool Render(const CameraRenderRequest& request);
`tests/Rendering/unit/test_camera_scene_renderer.cpp` 当前验证了:
- render-area 会影响提取到的相机 viewport 尺寸。
- clear-flags clear-color override 会写回 `sceneData.cameraData`
- pre / pipeline / object-id / post / builtin / overlay 的执行顺序。
- object-id 失败post-pass 初始化失败和 builtin post-process 验证失败时的回滚路径。
- clear-flags clear-color override 会写回 `sceneData.cameraData`
- `pre / pipeline / object-id / post / overlay` 的执行顺序。
- object-id 失败post-pass 初始化失败时的回滚路径。
## 相关文档

View File

@@ -0,0 +1,71 @@
# ObjectIdPass
**命名空间**: `XCEngine::Rendering`
**类型**: `interface`
**头文件**: `XCEngine/Rendering/ObjectIdPass.h`
**描述**: 定义把 `RenderSceneData` 渲染到 object-id 目标表面的抽象接口,供 `CameraRenderer` 注入具体实现。
## 概览
`ObjectIdPass``CameraRenderer` 主链路里的一个可替换插槽。
它的职责很明确:
- 输入已经提取好的 `RenderSceneData`
- 输出一张 object-id 颜色目标
- 不参与主管线本身的颜色绘制
当前默认实现是 [BuiltinObjectIdPass](../Passes/BuiltinObjectIdPass/BuiltinObjectIdPass.md),但 `CameraRenderer` 允许调用方替换成别的实现,测试里也正是通过 mock pass 验证执行时序和失败回滚。
## 接口契约
### `Render()`
调用方会把:
- 当前帧的 `RenderContext`
- object-id 输出目标 `RenderSurface`
- 已经提取完成的 `RenderSceneData`
一起传进来。返回 `false` 表示本次 object-id 渲染失败,`CameraRenderer::Render()` 会直接终止本次相机提交。
### `Shutdown()`
默认实现为空,用于给具体 pass 释放缓存的 GPU 资源或内部状态。
`CameraRenderer` 会在以下时机调用它:
- 替换 object-id pass 时
- `CameraRenderer` 自身析构时
## 当前接入位置
在 [CameraRenderer](../CameraRenderer/CameraRenderer.md) 里object-id pass 的执行顺序是:
1. 执行 pre-scene pass
2. 绘制主 `RenderPipeline`
3. 如果 `request.objectId.IsRequested()`,执行 `ObjectIdPass`
4. 再执行 `postScenePasses`
5. 最后执行 `overlayPasses`
## 当前实现边界
- `ObjectIdPass` 只定义单次 object-id 绘制接口,不负责请求是否合法;这部分先由 `CameraRenderRequest``CameraRenderer` 做基础校验。
- 接口没有显式的 `Initialize()` 阶段,具体实现通常在第一次 `Render()` 时懒初始化,或在构造期准备资源。
- 当前引擎默认只维护一个 object-id pass 实例,由 `CameraRenderer` 持有。
## 真实使用位置
- `engine/src/Rendering/CameraRenderer.cpp`
- `engine/src/Rendering/Passes/BuiltinObjectIdPass.cpp`
- `tests/Rendering/unit/test_camera_scene_renderer.cpp`
## 相关文档
- [当前模块](../Rendering.md)
- [CameraRenderer](../CameraRenderer/CameraRenderer.md)
- [ObjectIdRenderRequest](../CameraRenderRequest/ObjectIdRenderRequest/ObjectIdRenderRequest.md)
- [BuiltinObjectIdPass](../Passes/BuiltinObjectIdPass/BuiltinObjectIdPass.md)

View File

@@ -0,0 +1,37 @@
# BuildInfiniteGridParameters
```cpp
InfiniteGridParameters BuildInfiniteGridParameters(const InfiniteGridPassData& data);
```
## 行为说明
当前实现会:
1. 拒绝 `data.valid == false` 的输入。
2. 以相机高度为主,计算目标网格间距。
3. 把目标间距吸附到最近的十进制尺度 `10^n`
4. 计算向下一档网格过渡时的 `transitionBlend`
5. 结合网格尺度和视线到地面距离,生成 `fadeDistance`
## 当前特征
- 水平平移不会改变结果。
- 相机高度跨过阈值时,`baseScale` 会从 `1 -> 10 -> 100` 这类十进制级别跳变。
- `orbitDistance` 当前不参与参数推导。
## 测试覆盖
`tests/Editor/test_scene_viewport_overlay_renderer.cpp` 当前验证了:
- 间距始终落在十进制尺度上。
- 水平平移不影响参数。
- 相机高度升高会扩大网格尺度和淡出距离。
- `transitionBlend` 在阈值前后平滑过渡。
- `orbitDistance` 变化不会影响结果。
## 相关文档
- [BuiltinInfiniteGridPass](BuiltinInfiniteGridPass.md)
- [InfiniteGridPassData](InfiniteGridPassData.md)
- [InfiniteGridParameters](InfiniteGridParameters.md)

View File

@@ -10,14 +10,14 @@
## 概述
`BuiltinInfiniteGridPass` 是当前 scene viewport 里“地面参考网格”效果的实际执行者。
`BuiltinInfiniteGridPass` 是当前 Scene View 里“地面参考网格”效果的实际执行者。
它消费一份相机姿态数据 `InfiniteGridPassData`,在运行时先推导网格尺度和过渡参数,再用一张 builtin shader 把网格直接混合到目标颜色附件上。
它消费一份 [InfiniteGridPassData](InfiniteGridPassData.md),在运行时先推导 [InfiniteGridParameters](InfiniteGridParameters.md),再用 builtin shader 把网格直接混合到目标颜色附件上。
## 关键输入
- `InfiniteGridPassData` 决定相机位置、朝向、FOV、裁剪面和轨道距离。
- `BuildInfiniteGridParameters()` 会把这些数据折算成 `baseScale``transitionBlend``fadeDistance`
- [InfiniteGridPassData](InfiniteGridPassData.md) 决定相机位置、朝向、FOV、裁剪面和轨道距离。
- [BuildInfiniteGridParameters](BuildInfiniteGridParameters.md) 会把这些数据折算成 `baseScale``transitionBlend``fadeDistance`
- `RenderSurface` 需要同时提供颜色附件和深度附件;当前 pass 会开启深度测试但关闭深度写入。
## 当前实现流程
@@ -35,14 +35,26 @@
- 视口和裁剪矩形使用 `surface.GetWidth()` / `GetHeight()`,不是自定义 render area。
- 网格参数完全由当前相机高度与朝向启发式推导,还没有暴露成更完整的编辑器样式配置。
## 公开方法与相关类型
| 成员 | 说明 |
|------|------|
| [InfiniteGridPassData](InfiniteGridPassData.md) | 输入相机数据。 |
| [InfiniteGridParameters](InfiniteGridParameters.md) | 推导后的网格尺度参数。 |
| [BuildInfiniteGridParameters](BuildInfiniteGridParameters.md) | 从输入相机数据生成当前网格参数。 |
| [Render](Render.md) | 在当前颜色目标上提交网格覆盖层。 |
| [Shutdown](Shutdown.md) | 销毁内部 pipeline、descriptor 和 shader handle。 |
## 真实使用位置
- `BuiltinPostProcessPassSequenceBuilder::AddInfiniteGridPass()` 会把它包装进 builtin post-process 序列
- `CameraRenderer` 在执行 `builtinPostProcess` 请求时,最终通过 builder 间接调用这里
- `tests/Rendering/unit/test_builtin_post_process_pass_sequence_builder.cpp` 覆盖了 grid-only 和 grid+selection 两类序列构建分支
- `editor/src/Viewport/Passes/SceneViewportGridPass.cpp``SceneViewportGridPassRenderer` 包装它,并把它挂进 Scene View 的 `postScenePasses`
- [SceneViewportRenderPlan](../../../Editor/Viewport/SceneViewportRenderPlan/SceneViewportRenderPlan.md) 决定当前帧是否需要创建这条 pass
- `tests/Editor/test_scene_viewport_overlay_renderer.cpp` 固定了网格参数推导规则
## 相关文档
- [Passes](../Passes.md)
- [BuiltinPostProcessPassSequenceBuilder](../BuiltinPostProcessPassSequenceBuilder/BuiltinPostProcessPassSequenceBuilder.md)
- [CameraRenderer](../../CameraRenderer/CameraRenderer.md)
- [InfiniteGridPassData](InfiniteGridPassData.md)
- [InfiniteGridParameters](InfiniteGridParameters.md)
- [BuildInfiniteGridParameters](BuildInfiniteGridParameters.md)
- [SceneViewportRenderPlan](../../../Editor/Viewport/SceneViewportRenderPlan/SceneViewportRenderPlan.md)

View File

@@ -0,0 +1,27 @@
# InfiniteGridParameters
**命名空间**: `XCEngine::Rendering::Passes`
**类型**: `struct`
**头文件**: `XCEngine/Rendering/Passes/BuiltinInfiniteGridPass.h`
## 字段
| 字段 | 说明 |
|------|------|
| `valid` | 当前参数是否可用于真正绘制网格。 |
| `baseScale` | 当前十进制主网格间距。 |
| `transitionBlend` | 向下一档网格密度过渡的平滑插值。 |
| `fadeDistance` | 当前网格淡出距离。 |
## 当前语义
- `baseScale` 始终是 `10^n` 这一类十进制尺度。
- `transitionBlend` 范围在 `[0, 1]`
- `fadeDistance` 会同时参考网格尺度和视线到地面平面的距离。
## 相关文档
- [BuiltinInfiniteGridPass](BuiltinInfiniteGridPass.md)
- [BuildInfiniteGridParameters](BuildInfiniteGridParameters.md)

View File

@@ -0,0 +1,31 @@
# InfiniteGridPassData
**命名空间**: `XCEngine::Rendering::Passes`
**类型**: `struct`
**头文件**: `XCEngine/Rendering/Passes/BuiltinInfiniteGridPass.h`
## 字段
| 字段 | 说明 |
|------|------|
| `valid` | 当前输入是否可用于绘制网格。 |
| `cameraPosition` | 当前 Scene View 相机世界坐标。 |
| `cameraForward` | 相机 forward 方向。 |
| `cameraRight` | 相机 right 方向。 |
| `cameraUp` | 相机 up 方向。 |
| `verticalFovDegrees` | 垂直视场角。 |
| `nearClipPlane` | 近裁剪面。 |
| `farClipPlane` | 远裁剪面。 |
| `orbitDistance` | 当前 orbit 距离;现实现里不参与参数推导。 |
## 当前使用位置
- `editor/src/Viewport/ViewportHostRenderFlowUtils.h` 里的 `BuildSceneViewportGridPassData(...)` 会从 overlay 数据组装它。
- [BuildInfiniteGridParameters](BuildInfiniteGridParameters.md) 和 [Render](Render.md) 都消费它。
## 相关文档
- [BuiltinInfiniteGridPass](BuiltinInfiniteGridPass.md)
- [BuildInfiniteGridParameters](BuildInfiniteGridParameters.md)

View File

@@ -0,0 +1,32 @@
# BuiltinInfiniteGridPass::Render
```cpp
bool Render(
const RenderContext& renderContext,
const RenderSurface& surface,
const InfiniteGridPassData& data);
```
## 行为说明
当前实现会:
1. 要求 `data.valid``renderContext.IsValid()`,且后端必须是 `D3D12`
2. 惰性初始化 shader、pipeline layout、pipeline state 和常量 descriptor。
3. 校验颜色附件与深度附件。
4. 调用 [BuildInfiniteGridParameters](BuildInfiniteGridParameters.md) 生成网格参数。
5. 构建 view-projection 常量并写入 descriptor set。
6. 绑定第一个颜色附件和深度附件。
7. 以全屏三角形方式执行一次 `Draw(3, 1, 0, 0)`
## 当前限制
- 视口与 scissor 总是覆盖整张 surface不读取 `surface.GetRenderArea()`
- pass 本身不做资源状态切换。
- 只写第一个颜色附件。
## 相关文档
- [BuiltinInfiniteGridPass](BuiltinInfiniteGridPass.md)
- [InfiniteGridPassData](InfiniteGridPassData.md)
- [BuildInfiniteGridParameters](BuildInfiniteGridParameters.md)

View File

@@ -0,0 +1,19 @@
# BuiltinInfiniteGridPass::Shutdown
```cpp
void Shutdown();
```
## 行为说明
当前实现直接销毁内部缓存的:
- pipeline state
- 常量 descriptor pool / set
- pipeline layout
- builtin infinite-grid shader handle
## 相关文档
- [BuiltinInfiniteGridPass](BuiltinInfiniteGridPass.md)
- [Render](Render.md)

View File

@@ -13,8 +13,8 @@
`BuiltinObjectIdOutlinePass` 处在“object-id 已经生成,但主场景也已经画完”的阶段。它本身不负责提取可见物体,也不负责生成 object-id 纹理,而是消费这些上游结果:
- object-id 纹理通常来自 `BuiltinObjectIdPass`
- 选中对象列表来自 [CameraRenderRequest](../../CameraRenderRequest/CameraRenderRequest.md) 的 `builtinPostProcess.selectedObjectIds`
- 调用时机由 `BuiltinPostProcessPassSequenceBuilder` 决定,并最终 [CameraRenderer](../../CameraRenderer/CameraRenderer.md) 在主场景之后提交。
- 选中对象列表和 style 当前通常由 editor 侧的 Scene View render plan 提供
- 调用时机当前`editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp` 这类包装 pass 决定,并最终通过 [CameraRenderer](../../CameraRenderer/CameraRenderer.md) `postScenePasses` 提交。
因此它更接近一个“builtin selection feedback compositor”而不是一个通用 post-process 框架。
@@ -40,41 +40,19 @@
5. 绑定一个 CBV set 和一个 SRV set设置目标为 `surface` 的第一个颜色附件。
6. 以全屏三角形方式执行 `Draw(3, 1, 0, 0)`
shader 侧的行为是:
- 正常模式下,如果当前像素本身属于选中对象,则直接 `discard`,只保留“选中物体边缘外侧”的轮廓。
- outline 强度来自周围邻域是否出现选中 object-id并按像素距离做一次简单衰减。
- debug 模式下,不做轮廓混合,而是把选中像素直接输出为白色,未选中输出为黑色。
## 依赖资源
- builtin shader 资源路径固定为 `builtin://shaders/object-id-outline`
- pass 优先查找 shader pass 名称 `ObjectIdOutline`,其次是 `EditorObjectIdOutline`,再退回到第一个可用的 graphics pass。
- 内部维护:
- 一个 pipeline layout
- 一个 pipeline state
- 一个常量 descriptor pool / set
- 一个纹理 descriptor pool / set
这些资源都按 device/backend 缓存,直到 [Shutdown](Shutdown.md) 或下次 [Render](Render.md) 发现上下文不匹配时重建。
## 生命周期
- [Constructor](Constructor.md) 只做状态清零,不会立刻创建 GPU 资源。
- 第一次 [Render](Render.md) 成功进入初始化路径时,才会真正创建 descriptor 和 PSO。
- [Shutdown](Shutdown.md) 会显式销毁所有内部缓存对象。
- [Destructor](Destructor.md) 当前是默认析构,因此如果脱离 `BuiltinPostProcessPassSequenceBuilder` 单独使用,调用方需要自己保证在销毁前执行 `Shutdown()`
- [Destructor](Destructor.md) 当前是默认析构,因此如果把它作为长期 renderer 成员使用,调用方仍需在销毁前显式执行 `Shutdown()`
## 当前实现边界
- 只接受 `D3D12`,其它后端直接返回 `false`
- 图形管线输出格式固定为 `R8G8B8A8_UNorm`,没有暴露可配置的 render target format
- 不使用深度测试和深度写入,也不读取 `surface` 的深度附件。
- 不处理资源状态切换,也不负责清屏;这些由 builder 或更外层调用方保证。
- 不处理资源状态切换,也不负责清屏;这些由调用方保证
- 当前完全忽略 `surface.GetRenderArea()`viewport 和 scissor 都直接取整张 surface 的宽高。
- builtin shader 只采样 `[-2, 2]` 邻域,因此 `outlineWidthPixels` 大于约 `2` 像素时不会继续带来更宽的轮廓。
- 只使用第一个颜色附件;多 render target 场景不会同步写其它附件。
- object-id 颜色编码只保留 `uint64_t` 对象 ID 的低 `32` 位。
## 公开方法
@@ -87,16 +65,14 @@ shader 侧的行为是:
## 真实使用位置
- `BuiltinPostProcessPassSequenceBuilder::AddSelectionOutlinePass()` 会把它包装成 `SceneSelectionOutline` render pass
- `BuiltinPostProcessPassSequenceBuilder::AddSelectionMaskDebugPass()` 会在清空目标后复用同一个 pass 输出 debug mask
- [CameraRenderer](../../CameraRenderer/CameraRenderer.md) 在主场景与 object-id pass 之后执行 builtin post-process 序列
- `editor/src/Viewport/ViewportHostRenderFlowUtils.h` 当前为 scene viewport 组装默认橙色、`2px` 的 outline style。
- `tests/Rendering/unit/test_builtin_post_process_pass_sequence_builder.cpp``tests/Rendering/unit/test_camera_scene_renderer.cpp``tests/Editor/test_viewport_render_flow_utils.cpp` 覆盖了它的主要接入链路。
- `editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp``SceneViewportSelectionOutlinePassRenderer` 包装它
- [SceneViewportRenderPlan](../../../Editor/Viewport/SceneViewportRenderPlan/SceneViewportRenderPlan.md) 决定当前帧是否要创建 selection outline pass
- [CameraRenderer](../../CameraRenderer/CameraRenderer.md) 在主场景与 object-id pass 之后执行这些 `postScenePasses`
- `editor/src/Viewport/ViewportHostRenderFlowUtils.h` 当前为 Scene View 组装默认橙色、`2px` 的 outline style。
## 相关文档
- [Passes](../Passes.md)
- [ObjectIdOutlineStyle](../ObjectIdOutlineStyle/ObjectIdOutlineStyle.md)
- [CameraRenderRequest](../../CameraRenderRequest/CameraRenderRequest.md)
- [CameraRenderer](../../CameraRenderer/CameraRenderer.md)
- [RenderSurface](../../RenderSurface/RenderSurface.md)

View File

@@ -25,8 +25,8 @@
## 注意事项
- 正常生命周期应在销毁前先调用 [Shutdown](Shutdown.md)。
- 这也是 `BuiltinPostProcessPassSequenceBuilder::Shutdown()` 会主动转调 `m_outlinePass.Shutdown()` 的原因
- 如果把这个类单独作为局部对象或成员对象使用,而不是放在已有的 builder 生命周期里,析构前漏掉 `Shutdown()` 会造成资源回收路径缺失。
- `editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.h` 里的 renderer 包装器也会把 `Shutdown()` 责任显式留给上层 host service
- 如果把这个类单独作为局部对象或成员对象使用,析构前漏掉 `Shutdown()` 会造成资源回收路径缺失。
## 相关文档

View File

@@ -23,54 +23,27 @@ bool Render(
## 当前实现流程
`engine/src/Rendering/Passes/BuiltinObjectIdOutlinePass.cpp`,当前执行顺序是:
1. 如果 `renderContext` 无效、后端不是 `D3D12``objectIdTextureView == nullptr``selectedObjectIds` 为空,直接返回 `false`
2. 调用 `EnsureInitialized(renderContext)`;如果当前 device/backend 变化,会先销毁旧资源再重建。
3. 检查 `surface.GetColorAttachments()`,要求至少存在一个有效颜色附件。
4. 构建 `OutlineConstants`
- `viewportSizeAndTexelSize` 来自 `surface.GetWidth()` / `GetHeight()`
- `outlineColor` 来自 `style.outlineColor`
- `selectedInfo.x` 写入实际使用的选中对象数量
- `selectedInfo.y` 表示是否启用 debug mask
- `selectedInfo.z` 写入 `style.outlineWidthPixels`
- `selectedObjectColors` 写入最多 `256` 个编码后的 object-id 颜色
4. 构建 `OutlineConstants`,写入 viewport 尺寸、轮廓颜色、调试开关、像素宽度和最多 `256` 个选中对象编码颜色。
5. 把常量缓冲写到 `m_constantSet`,把 `objectIdTextureView` 绑定到 `m_textureSet`
6. 设置渲染目标为 `surface` 的第一个颜色附件。
7. 设置全屏 viewport / scissor、三角形图元、pipeline state 和两个 graphics descriptor set。
8. 提交一次 `Draw(3, 1, 0, 0)`
## 参数
| 参数 | 说明 |
|------|------|
| `renderContext` | 当前帧的 RHI 设备、命令队列和命令列表。 |
| `surface` | 要写回轮廓结果的目标表面。 |
| `objectIdTextureView` | object-id 纹理的 SRV。 |
| `selectedObjectIds` | 需要高亮的对象 ID 列表。 |
| `style` | 轮廓颜色、宽度和 debug mask 开关。 |
## 返回值
- 返回 `true`:校验通过、资源初始化成功,并且 draw call 已经提交到命令列表。
- 返回 `false`:输入无效、资源创建失败,或目标颜色附件不可用。
6. 设置渲染目标为 `surface` 的第一个颜色附件,并提交一次全屏三角形 `Draw(3, 1, 0, 0)`
## 关键语义
- 当前只使用 `surface` 的第一个颜色附件,其它附件会被忽略
- 当前只使用 `surface` 的第一个颜色附件。
- pass 自己不会做 render target 清空;如果要输出 debug mask 这种“整张替换”的结果,需要由调用方先清空目标。
- pass 自己也不负责资源状态切换;调用前应保证颜色目标已经在 `RenderTarget` 状态、object-id SRV 已经可读。
- 当前 viewport 和 scissor 始终覆盖整张 surface而不是 `surface.GetRenderArea()`
- 选中对象列表超过 `BuiltinObjectIdOutlinePass::kMaxSelectedObjectCount` 时,后面的对象会被静默截断。
## 典型使用位置
- `BuiltinPostProcessPassSequenceBuilder::AddSelectionOutlinePass()`:正常轮廓模式。
- `BuiltinPostProcessPassSequenceBuilder::AddSelectionMaskDebugPass()`debug mask 模式,调用前会先清空颜色目标
- `editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp`:正常 Scene View 轮廓模式。
- debug mask 仍然通过同一条 `Render(...)` 路径完成,只是 `style.debugSelectionMask = true`
## 相关文档
- [BuiltinObjectIdOutlinePass](BuiltinObjectIdOutlinePass.md)
- [ObjectIdOutlineStyle](../ObjectIdOutlineStyle/ObjectIdOutlineStyle.md)
- [RenderContext](../../RenderContext/RenderContext.md)
- [RenderSurface](../../RenderSurface/RenderSurface.md)

View File

@@ -0,0 +1,24 @@
# BuiltinObjectIdPass::BuildInputLayout
```cpp
static RHI::InputLayoutDesc BuildInputLayout();
```
## 行为说明
当前实现只声明一个顶点元素:
- `POSITION`
- `semanticIndex = 0`
- `Format::R32G32B32_Float`
- 偏移取自 `Resources::StaticMeshVertex::position`
## 当前语义
- object-id pass 只依赖顶点位置不读取法线、UV、切线或颜色。
- 这和当前 shader 语义一致,因为它只需要做几何覆盖并输出 object-id 颜色。
## 相关文档
- [BuiltinObjectIdPass](BuiltinObjectIdPass.md)
- [Render](Render.md)

View File

@@ -39,16 +39,29 @@
- 这条 pass 依赖调用方先准备好 `RenderSceneData`,自己不做 scene extraction。
- 只处理第一个颜色附件和一个深度附件。
- 当前 object-id 绘制仍是逐物体提交,没有更高级的 batching。
- viewport 与 scissor 使用 `surface.GetRenderArea()`,因此 object-id 输出会跟随当前 request 的子视口。
- 是否支持某个后端,取决于 builtin object-id shader 是否提供对应 graphics variant现有 shader loader 测试覆盖了 D3D12、OpenGL 和 Vulkan 三种 builtin variant。
## 公开方法
| 方法 | 说明 |
|------|------|
| [Destructor](Destructor.md) | 析构时关闭内部缓存的 pipeline、descriptor set 和 shader handle。 |
| [BuildInputLayout](BuildInputLayout.md) | 生成 object-id pass 当前使用的顶点布局。 |
| [Render](Render.md) | 把 `visibleItems` 再绘制一遍到 object-id 目标。 |
| [Shutdown](Shutdown.md) | 主动销毁当前缓存的 GPU 资源。 |
## 真实使用位置
- `CameraRenderer::Render()` 在主场景渲染后、builtin post-process 前调用 `m_objectIdPass->Render(...)`
- `CameraRenderer::Render()` 在主场景渲染后、`postScenePasses``overlayPasses` 前调用 `m_objectIdPass->Render(...)`
- `tests/Editor/test_viewport_render_flow_utils.cpp` 验证了 scene viewport 会为 `CameraRenderRequest::objectId` 正确挂接颜色/深度附件和 shader view。
- `tests/Resources/Shader/test_shader_loader.cpp` 验证了 builtin object-id shader 的 pass tag 和多后端 variant 是否存在。
## 相关文档
- [Passes](../Passes.md)
- [BuildInputLayout](BuildInputLayout.md)
- [Render](Render.md)
- [Shutdown](Shutdown.md)
- [BuiltinObjectIdOutlinePass](../BuiltinObjectIdOutlinePass/BuiltinObjectIdOutlinePass.md)
- [CameraRenderer](../../CameraRenderer/CameraRenderer.md)

View File

@@ -0,0 +1,20 @@
# BuiltinObjectIdPass::~BuiltinObjectIdPass
```cpp
~BuiltinObjectIdPass() override;
```
## 行为说明
当前析构函数只有一个动作:
```cpp
Shutdown();
```
因此它会在对象销毁前显式释放当前缓存的 pipeline state、pipeline layout、每对象 descriptor set、shader handle 和 `RenderResourceCache`
## 相关文档
- [BuiltinObjectIdPass](BuiltinObjectIdPass.md)
- [Shutdown](Shutdown.md)

View File

@@ -0,0 +1,37 @@
# BuiltinObjectIdPass::Render
```cpp
bool Render(
const RenderContext& context,
const RenderSurface& surface,
const RenderSceneData& sceneData) override;
```
## 行为说明
当前实现会:
1. 校验 `context`、颜色附件、深度附件和 `surface.GetRenderArea()`
2. 调用惰性初始化,必要时创建 shader、pipeline layout 和 pipeline state。
3. 如果 `surface` 开启自动状态切换,把第一个颜色附件切到 `RenderTarget`
4. 把当前 render area 清成全零 object-id 颜色。
5. 遍历 `sceneData.visibleItems`,为每个 `gameObject` 编码 object-id 并发出 draw call。
6. 如果开启自动状态切换,再把颜色附件切回 `surface.GetColorStateAfter()`
## 当前细节
- viewport 与 scissor 使用 `surface.GetRenderArea()`,不是整张 surface。
- 每个 object ID 会缓存一套独立常量 descriptor set。
- section mesh 会按 `VisibleRenderItem::sectionIndex` 绘制对应 section否则退回整 mesh。
## 返回值
- 成功完成本次 object-id 提交时返回 `true`
- 输入无效、资源初始化失败,或某个可见项缺少必要 mesh/buffer 时返回 `false`
## 相关文档
- [BuiltinObjectIdPass](BuiltinObjectIdPass.md)
- [BuildInputLayout](BuildInputLayout.md)
- [ObjectIdEncoding](../../ObjectIdEncoding/ObjectIdEncoding.md)
- [CameraRenderer::Render](../../CameraRenderer/Render.md)

View File

@@ -0,0 +1,20 @@
# BuiltinObjectIdPass::Shutdown
```cpp
void Shutdown() override;
```
## 行为说明
当前实现直接转调内部 `DestroyResources()`,并释放:
- `RenderResourceCache`
- 每对象 descriptor pool / set
- pipeline state
- pipeline layout
- builtin object-id shader handle
## 相关文档
- [BuiltinObjectIdPass](BuiltinObjectIdPass.md)
- [Destructor](Destructor.md)

View File

@@ -1,45 +0,0 @@
# BuiltinPostProcessPassPlan
**命名空间**: `XCEngine::Rendering::Passes`
**类型**: `struct`
**头文件**: `XCEngine/Rendering/Passes/BuiltinPostProcessPassPlan.h`
**描述**: 根据 grid、selection 和 debug mask 请求,决定 builtin post-process 应执行哪些步骤以及顺序。
## 概述
`BuiltinPostProcessPassPlan` 本身只是一个轻量数据块:
- `valid` 表示当前输入组合是否能生成可执行序列。
- `steps` 保存真正要执行的 `BuiltinPostProcessPassStep` 顺序。
真正的规则写在同一个头文件里的 `BuildBuiltinPostProcessPassPlan()` 内联函数里,`BuiltinPostProcessPassSequenceBuilder` 只是把这个计划翻译成 `RenderPassSequence`
## 当前步骤规则
| 输入组合 | 当前 steps |
|---------|-----------|
| `hasGridOverlay && hasSelection && hasObjectIdShaderView` | `ColorToRenderTarget -> InfiniteGrid -> SelectionOutline -> ColorToShaderResource` |
| `hasGridOverlay` | `ColorToRenderTarget -> InfiniteGrid -> ColorToShaderResource` |
| `hasSelection && hasObjectIdShaderView` | `ColorToRenderTarget -> SelectionOutline -> ColorToShaderResource` |
| `debugSelectionMask && hasSelection && hasObjectIdShaderView` | `ColorToRenderTarget -> SelectionMaskDebug -> ColorToShaderResource` |
## 当前实现边界
- 没有任何 builtin 效果请求时,计划为无效。
- 只有 selection 但没有 object-id shader view 时,计划也会无效。
- debug mask 模式是排他路径,不会和正常 outline 混用。
- 当前一个比较明显的实现特征是:如果 `debugSelectionMask == true` 且只有 grid没有 selection计划虽然返回有效但只会做颜色状态切换不会真正绘制网格。
## 真实使用位置
- `BuiltinPostProcessPassSequenceBuilder::Build()` 先构造这个 plan再逐步转换为 lambda render pass。
- `tests/Rendering/unit/test_builtin_post_process_pass_sequence_builder.cpp` 覆盖了 plan 最终影响到的主要输入组合。
## 相关文档
- [BuiltinPostProcessPassSequenceBuilder](../BuiltinPostProcessPassSequenceBuilder/BuiltinPostProcessPassSequenceBuilder.md)
- [BuiltinInfiniteGridPass](../BuiltinInfiniteGridPass/BuiltinInfiniteGridPass.md)
- [BuiltinObjectIdOutlinePass](../BuiltinObjectIdOutlinePass/BuiltinObjectIdOutlinePass.md)

View File

@@ -1,57 +0,0 @@
# BuiltinPostProcessPassSequenceBuilder
**命名空间**: `XCEngine::Rendering::Passes`
**类型**: `class`
**头文件**: `XCEngine/Rendering/Passes/BuiltinPostProcessPassSequenceBuilder.h`
**描述**: 把 builtin grid、selection outline 和 debug mask 请求翻译成一条可执行的 `RenderPassSequence`
## 概述
`BuiltinPostProcessPassSequenceBuilder` 处在一个很实用的中间层位置:
- 上游给它的是一份数据请求grid 要不要画、有没有 object-id 纹理、当前选中了哪些对象、是否进入 debug mask。
- 下游 `CameraRenderer` 需要的是一条真正可执行的 `RenderPassSequence`
builder 的作用,就是把“想要什么效果”转成“要按什么顺序执行哪些 pass”。
## 当前实现流程
1. 先检查当前是否存在 selection但缺少 object-id shader view如果是会在结果里把 `missingObjectIdTextureViewForSelection` 置为 `true`
2. 调用 [BuiltinPostProcessPassPlan](../BuiltinPostProcessPassPlan/BuiltinPostProcessPassPlan.md) 决定步骤顺序。
3. 按步骤向 `RenderPassSequence` 追加 lambda pass
- `ColorToRenderTarget`
- `InfiniteGrid`
- `SelectionOutline`
- `ColorToShaderResource`
- `SelectionMaskDebug`
4. 这些 lambda pass 会复用 builder 内部持有的 [BuiltinInfiniteGridPass](../BuiltinInfiniteGridPass/BuiltinInfiniteGridPass.md) 和 [BuiltinObjectIdOutlinePass](../BuiltinObjectIdOutlinePass/BuiltinObjectIdOutlinePass.md)。
## 关键行为
- `Build()` 不直接执行任何 GPU 命令,只是组装序列。
- `Shutdown()` 会显式关闭内部复用的 grid/outline pass 资源。
- debug mask 路径会先把主颜色目标清成黑色,再调用 outline pass 的掩码模式。
- 无论是 grid 还是 outlinebuilder 都会在前后插入颜色目标到 `RenderTarget` / `ShaderResource` 的状态切换 pass。
## 当前实现边界
- builder 只处理 builtin post-process不处理主场景渲染、object-id 预通道或 overlay pass。
- sequence 里的每一步当前都是 lambda render pass没有独立类型名和更细的生命周期控制。
- 如果 selection 请求缺少 object-id shader viewbuilder 只负责报告问题;最终是否退化成 grid-only 或直接失败,取决于输入组合和 plan 结果。
## 真实使用位置
- `CameraRenderer::Render()``request.builtinPostProcess.IsRequested()` 为真时创建临时 `RenderPassSequence`,然后调用这里的 `Build()`
- `tests/Rendering/unit/test_builtin_post_process_pass_sequence_builder.cpp` 是当前最直接的行为覆盖来源。
- `tests/Editor/test_viewport_render_flow_utils.cpp` 验证了编辑器 scene viewport 如何构造能被这里消费的 builtin post-process 请求。
## 相关文档
- [Passes](../Passes.md)
- [BuiltinPostProcessPassPlan](../BuiltinPostProcessPassPlan/BuiltinPostProcessPassPlan.md)
- [BuiltinInfiniteGridPass](../BuiltinInfiniteGridPass/BuiltinInfiniteGridPass.md)
- [BuiltinObjectIdOutlinePass](../BuiltinObjectIdOutlinePass/BuiltinObjectIdOutlinePass.md)
- [CameraRenderer](../../CameraRenderer/CameraRenderer.md)

View File

@@ -12,7 +12,7 @@
`ObjectIdOutlineStyle` 是 [BuiltinObjectIdOutlinePass](../BuiltinObjectIdOutlinePass/BuiltinObjectIdOutlinePass.md) 的轻量配置结构。它不拥有 GPU 资源,也不参与生命周期管理,只负责把“这次要画什么样的轮廓”显式传给 pass。
当前它也被 [CameraRenderRequest](../../CameraRenderRequest/CameraRenderRequest.md) 的 `builtinPostProcess.outlineStyle` 直接持有,因此调用方通常在构建渲染请求时就把选中反馈样式一并确定下来
当前它主要由 editor 侧的 Scene View 组装流程创建,再传给 `SceneViewportSelectionOutlinePassRenderer`,最终落到 `BuiltinObjectIdOutlinePass::Render(...)`
## 默认值
@@ -24,8 +24,6 @@
| `outlineWidthPixels` | `2.0f` | 轮廓宽度,单位是像素。 |
| `debugSelectionMask` | `false` | 默认输出正常轮廓,而不是调试 mask。 |
编辑器 scene viewport 当前也沿用了这组默认值。
## 字段语义
### `outlineColor`
@@ -41,7 +39,7 @@
### `debugSelectionMask`
-`true`pass 不再输出橙色轮廓,而是把“当前像素是否属于被选中对象”可视化为白底/黑底 mask。
- 这个模式通常配合 `BuiltinPostProcessPassSequenceBuilder::AddSelectionMaskDebugPass()` 使用builder 会先清空目标,再调用 outline pass 进行覆盖
- Scene View 当前也复用同一组 style 字段控制这个模式,只是由 editor 侧决定是否创建对应 pass
## 当前实现边界
@@ -52,5 +50,5 @@
## 相关文档
- [BuiltinObjectIdOutlinePass](../BuiltinObjectIdOutlinePass/BuiltinObjectIdOutlinePass.md)
- [CameraRenderRequest](../../CameraRenderRequest/CameraRenderRequest.md)
- [SceneViewportRenderPlan](../../../Editor/Viewport/SceneViewportRenderPlan/SceneViewportRenderPlan.md)
- [Passes](../Passes.md)

View File

@@ -4,58 +4,58 @@
**类型**: `submodule`
**描述**: 承载 builtin object-id、outline、infinite grid 等补充型渲染 pass以及这些 pass 串成后处理序列的辅助类型。
**描述**: 承载 builtin object-id、selection outline、infinite grid 等补充型渲染 pass以及这些 pass 复用的轻量样式/参数类型。
## 概述
`Rendering::Passes` 不是用来替代 [RenderPipeline](../RenderPipeline/RenderPipeline.md) 的第二套主渲染框架,而是承载“主场景已经画完之后,还需要补一层额外结果”的 builtin pass
- `BuiltinObjectIdPass` 把可见物体编码成 object-id 颜色,写到辅助渲染目标,供拾取和选中反馈使用。
- [BuiltinObjectIdPass](BuiltinObjectIdPass/BuiltinObjectIdPass.md) 把可见物体编码成 object-id 颜色,写到辅助渲染目标,供拾取和选中反馈使用。
- [BuiltinObjectIdOutlinePass](BuiltinObjectIdOutlinePass/BuiltinObjectIdOutlinePass.md) 读取 object-id 纹理和选中对象列表,把轮廓或调试 mask 叠加回场景颜色。
- `BuiltinInfiniteGridPass` 给 Scene View 一类编辑器视口叠加无限网格。
- `BuiltinPostProcessPassSequenceBuilder``BuiltinPostProcessPassPlan` 负责把这些 pass 排成一个可执行的 `RenderPassSequence`,并补上前后状态切换。
- [BuiltinInfiniteGridPass](BuiltinInfiniteGridPass/BuiltinInfiniteGridPass.md) 给 Scene View 一类编辑器视口叠加无限网格。
这层设计让 [CameraRenderer](../CameraRenderer/CameraRenderer.md) 仍然只关心“先画主场景,再按请求插入补充 pass”而不必直接知道每个 editor-only 效果的 shader、descriptor 和执行顺序细节
当前这些 builtin pass 更多是“底层执行单元”。真正决定 Scene View 要不要画网格、要不要叠选中轮廓、这些效果要挂到 `postScenePasses` 还是 `overlayPasses` 上的逻辑,已经迁移到 editor 侧的 [SceneViewportRenderPlan](../../Editor/Viewport/SceneViewportRenderPlan/SceneViewportRenderPlan.md)
## 典型链路
当前 object-id 轮廓相关链路大致是:
当前 Scene View 相关链路大致是:
1. [CameraRenderer](../CameraRenderer/CameraRenderer.md) 先执行主 `RenderPipeline`
2. 如果 [CameraRenderRequest](../CameraRenderRequest/CameraRenderRequest.md) 里请求了 `objectId.surface`,则额外执行 `BuiltinObjectIdPass`,把每个可见物体的 `GameObject` ID 编码进辅助纹理。
3. 如果 `builtinPostProcess` 请求了网格、选中轮廓或调试 mask`BuiltinPostProcessPassSequenceBuilder` 会构建一个后处理 pass 序列
4. [BuiltinObjectIdOutlinePass](BuiltinObjectIdOutlinePass/BuiltinObjectIdOutlinePass.md) 在这个序列里读取 object-id SRV并把结果写回主颜色目标
编辑器场景视口当前正是沿着这条链路工作:`ViewportHostRenderFlowUtils.h` 先组装 `BuiltinPostProcessRequest`,再由 `CameraRenderer::Render()` 真正提交。
2. 如果 [CameraRenderRequest](../CameraRenderRequest/CameraRenderRequest.md) 里请求了 `objectId.surface`,则额外执行 [BuiltinObjectIdPass](BuiltinObjectIdPass/BuiltinObjectIdPass.md),把每个可见物体的 `GameObject` ID 编码进辅助纹理。
3. editor 侧的 [SceneViewportRenderPlan](../../Editor/Viewport/SceneViewportRenderPlan/SceneViewportRenderPlan.md) 会根据 overlay、选中对象和 render targets 同时构建 `postScenePasses``overlayPasses`
4. 其中 `postScenePasses` 里的 `SceneViewportGridPass` / `SceneViewportSelectionOutlinePass` 包装器,最终分别调用 [BuiltinInfiniteGridPass](BuiltinInfiniteGridPass/BuiltinInfiniteGridPass.md) 与 [BuiltinObjectIdOutlinePass](BuiltinObjectIdOutlinePass/BuiltinObjectIdOutlinePass.md)`overlayPasses` 则保留给 editor overlay 的最终叠加层
## 当前公开概念
| 类型 / 头文件 | 角色 |
|------|------|
| `BuiltinObjectIdPass` | 生成 object-id 颜色缓冲,给拾取与 outline 作为输入。 |
| [BuiltinObjectIdPass](BuiltinObjectIdPass/BuiltinObjectIdPass.md) | 生成 object-id 颜色缓冲,给拾取与 outline 作为输入。 |
| [BuiltinObjectIdOutlinePass](BuiltinObjectIdOutlinePass/BuiltinObjectIdOutlinePass.md) | 读取 object-id 纹理,在主颜色目标上合成选中轮廓或调试 mask。 |
| [ObjectIdOutlineStyle](ObjectIdOutlineStyle/ObjectIdOutlineStyle.md) | 轮廓颜色、像素宽度、调试模式的配置载体。 |
| `BuiltinInfiniteGridPass` | Scene View 网格覆盖层。 |
| `BuiltinPostProcessPassSequenceBuilder` | 按请求条件拼出 grid / outline / debug mask 所需的 pass 序列。 |
| `BuiltinPostProcessPassPlan` | 一个纯规划 helper决定后处理步骤顺序与回退策略。 |
| [BuiltinInfiniteGridPass](BuiltinInfiniteGridPass/BuiltinInfiniteGridPass.md) | Scene View 网格覆盖层的底层执行 pass。 |
| [InfiniteGridPassData](BuiltinInfiniteGridPass/InfiniteGridPassData.md) | 网格 pass 使用的相机姿态与裁剪参数。 |
| [InfiniteGridParameters](BuiltinInfiniteGridPass/InfiniteGridParameters.md) | 由相机状态推导出的网格尺度、过渡和淡出参数。 |
| [BuildInfiniteGridParameters](BuiltinInfiniteGridPass/BuildInfiniteGridParameters.md) | 把 Scene View 相机数据折算成稳定的十进制网格参数。 |
## 测试与真实调用点
- `tests/Rendering/unit/test_builtin_post_process_pass_plan.cpp` 固定了 outline、debug mask 和 grid 的排序规则
- `tests/Rendering/unit/test_builtin_post_process_pass_sequence_builder.cpp` 验证了 object-id SRV 缺失时的回退和报错行为
- `tests/Rendering/unit/test_camera_scene_renderer.cpp` 验证了 `CameraRenderer` 中 object-id pass 与 builtin post-process 的接入时机
- `tests/Editor/test_viewport_render_flow_utils.cpp` 验证了 scene viewport 如何组装 `BuiltinPostProcessRequest`,以及 object-id 目标和 SRV 缺失时的降级逻辑。
- `tests/Rendering/unit/test_camera_scene_renderer.cpp` 验证了 `CameraRenderer` 中 object-id pass 与可选 pass sequence 的接入时机
- `tests/Editor/test_scene_viewport_overlay_renderer.cpp` 固定了 `BuildInfiniteGridParameters(...)` 的十进制尺度、过渡和淡出语义
- `tests/Editor/test_viewport_render_flow_utils.cpp` 验证了 Scene View render plan 如何组装 grid / selection outline / overlay pass以及 object-id SRV 缺失时的警告路径
## 当前实现边界
- 这一层目前是 builtin、轻量、偏 editor/工具链导向的 pass 集合,还不是通用 render graph。
- object-id 相关流程依赖单独的辅助 render target / shader resource view而不是直接从主颜色结果反推。
- 具体 pass 的资源状态切换大多由外层 builder 或调用方保证,单个 pass 本身往往只做最小提交逻辑。
- 具体 pass 的资源状态切换大多由调用方保证,单个 pass 本身往往只做最小提交逻辑。
## 相关文档
- [BuiltinObjectIdPass](BuiltinObjectIdPass/BuiltinObjectIdPass.md)
- [BuiltinInfiniteGridPass](BuiltinInfiniteGridPass/BuiltinInfiniteGridPass.md)
- [BuiltinObjectIdOutlinePass](BuiltinObjectIdOutlinePass/BuiltinObjectIdOutlinePass.md)
- [ObjectIdOutlineStyle](ObjectIdOutlineStyle/ObjectIdOutlineStyle.md)
- [SceneViewportRenderPlan](../../Editor/Viewport/SceneViewportRenderPlan/SceneViewportRenderPlan.md)
- [CameraRenderRequest](../CameraRenderRequest/CameraRenderRequest.md)
- [CameraRenderer](../CameraRenderer/CameraRenderer.md)
- [当前模块](../Rendering.md)

View File

@@ -1,16 +1,23 @@
# RenderPipeline::~RenderPipeline
销毁渲染管线对象。
销毁渲染管线基类对象。
```cpp
virtual ~RenderPipeline() = default;
```
## 行为说明
## 当前语义
这是一个虚析构函数,保通过 `RenderPipeline*` 删除派生对象时行为正确。
- 这是一个虚析构函数,保通过 `RenderPipeline*` 删除派生对象时行为正确。
- 析构函数本身没有定义额外 shutdown 流程;资源释放语义仍应由 [Shutdown](Shutdown.md) 和派生类自己的析构实现共同承担。
## 当前所有权模型
按当前渲染主链路runtime pipeline 通常由 [CameraRenderer](../CameraRenderer/CameraRenderer.md) 以 `std::unique_ptr<RenderPipeline>` 独占持有。
这也是为什么基类必须提供虚析构,而不是让调用方了解每个 concrete pipeline 的真实类型。
## 相关文档
- [返回类型总览](RenderPipeline.md)
- [RenderPipeline::Shutdown](Shutdown.md)
- [BuiltinForwardPipeline](../Pipelines/BuiltinForwardPipeline/BuiltinForwardPipeline.md)

View File

@@ -1,26 +1,34 @@
# RenderPipeline::Initialize
初始化渲染管线资源。
初始化这条主场景 runtime 管线所需的底层资源。
```cpp
virtual bool Initialize(const RenderContext& context) = 0;
```
## 行为说明
## 当前语义
派生类应在这里创建 shader、descriptor set、pipeline state、sampler 等与具体管线相关的资源。
- 这是一个“显式预热”入口,用来让具体管线提前准备 shader、pipeline state、descriptor set、采样器等资源。
- 上层当前并不保证一定会在首次 [Render](Render.md) 前先调用这里。按 `engine/src/Rendering/CameraRenderer.cpp` 的现状,`CameraRenderer` 会直接进入 `m_pipeline->Render(...)`
- 因此调用方只能依赖“成功后这条管线进入可渲染状态”这一契约,不能依赖具体初始化时机。
## 当前实现弹性
- 具体实现可以把初始化全部放在这里一次性完成。
- 也可以像 [BuiltinForwardPipeline](../Pipelines/BuiltinForwardPipeline/BuiltinForwardPipeline.md) 一样,在 [Render](Render.md) 里按需触发或复用内部初始化逻辑。
## 参数
- `context` - 当前渲染上下文。
- `context` - 当前帧的底层渲染上下文。
## 返回值
- 初始化成功返回 `true`
- 否则返回 `false`
- 返回 `true`:初始化成功,后续可进入 [Render](Render.md)
- 返回 `false`:初始化失败;调用方不应假定这条管线已经进入可用状态
## 相关文档
- [返回类型总览](RenderPipeline.md)
- [RenderPipeline::Render](Render.md)
- [RenderContext](../RenderContext/RenderContext.md)
- [BuiltinForwardPipeline::Initialize](../Pipelines/BuiltinForwardPipeline/Initialize.md)

View File

@@ -1,6 +1,6 @@
# RenderPipeline::Render
执行一次渲染
执行一次主场景 runtime 绘制
```cpp
virtual bool Render(
@@ -9,23 +9,53 @@ virtual bool Render(
const RenderSceneData& sceneData) = 0;
```
## 行为说明
## 当前职责边界
派生类应在这里把已经提取好的 `sceneData` 绘制到 `surface`
这个接口只负责把已经提取好的主场景数据画到目标 [RenderSurface](../RenderSurface/RenderSurface.md)
- 消费 `sceneData.cameraData`
- 遍历 `sceneData.visibleItems`
- 读写 `surface` 的颜色/深度附件与 render-area
它不负责:
- 多相机 request 规划与排序
- object-id pass
- `preScenePasses` / `postScenePasses` / `overlayPasses` 编排
这些外层编排由 [CameraRenderer](../CameraRenderer/CameraRenderer.md) 负责。
## 当前调用位置
`engine/src/Rendering/CameraRenderer.cpp`,当前主链路顺序是:
1. `preScenePasses`
2. `m_pipeline->Render(...)`
3. `ObjectIdPass`
4. `postScenePasses`
5. `overlayPasses`
因此这里代表的是“主场景绘制槽位”,不是整次相机提交的全部流程。
## 当前实现弹性
- 具体实现可以假定 `sceneData` 已经由 [RenderSceneExtractor](../RenderSceneExtractor/RenderSceneExtractor.md) 产出。
- 具体实现也可以在这里内部做惰性初始化,只要对调用方维持一致的成败语义。
## 参数
- `context` - 当前渲染上下文。
- `surface` - 本次渲染的目标表面
- `sceneData` - 已提取好的相机和可见对象数据。
- `context` - 当前帧的底层渲染上下文。
- `surface` - 本次主场景绘制的目标表面与 render-area
- `sceneData` - 已提取好的相机、光照与可绘制项数据。
## 返回值
- 成功返回 `true`
- 失败返回 `false`
- 返回 `true`:主场景绘制成功
- 返回 `false`:主场景绘制失败;外层 `CameraRenderer` 会终止这次提交并按已初始化阶段做清理
## 相关文档
- [返回类型总览](RenderPipeline.md)
- [RenderSurface](../RenderSurface/RenderSurface.md)
- [CameraRenderer::Render](../CameraRenderer/Render.md)
- [RenderSceneExtractor](../RenderSceneExtractor/RenderSceneExtractor.md)
- [RenderSurface](../RenderSurface/RenderSurface.md)

View File

@@ -6,47 +6,72 @@
**头文件**: `XCEngine/Rendering/RenderPipeline.h`
**描述**: 定义渲染管线的统一接口,负责初始化、关闭和把 `RenderSceneData` 绘制到 `RenderSurface`
**描述**: 主场景 runtime 管线的统一接口,负责初始化、关闭,并把一份 `RenderSceneData` 绘制到目标 `RenderSurface`
## 概
## 概
`RenderPipeline` 是当前渲染模块最关键的扩展点之一。它把真正的绘制实现抽象成三个阶段:
`RenderPipeline` 是当前渲染模块里最核心的 runtime 扩展点之一。它把真正的主场景绘制”抽象成三个阶段:
- 初始化管线资源
- 关闭并释放资源
- 执行一次渲染
- `Initialize(const RenderContext&)`
- `Shutdown()`
- `Render(const RenderContext&, const RenderSurface&, const RenderSceneData&)`
这类接口和商业引擎里常见的 render pipeline abstraction 很接近,因为上层的 scene renderer 不应该知道具体是前向、延迟还是自定义渲染路径
上游不会直接依赖某个具体 builtin 管线类型,而是只依赖这个抽象接口
## 当前实现
## 当前接入方式
当前公开的具体实现是:
默认情况下,具体 runtime pipeline 不是由 `SceneRenderer` 直接构造,而是由 `CameraRenderer` 通过 [RenderPipelineAsset](../RenderPipelineAsset/RenderPipelineAsset.md) 创建并持有。
当前公开的默认实现是:
- [BuiltinForwardPipeline](../Pipelines/BuiltinForwardPipeline/BuiltinForwardPipeline.md)
`SceneRenderer` 默认也会创建这个内建前向管线。
默认工厂则是:
- [BuiltinForwardPipelineAsset](../Pipelines/BuiltinForwardPipelineAsset/BuiltinForwardPipelineAsset.md)
## 当前职责边界
`RenderPipeline` 只负责主场景绘制本身,不负责:
- 多相机 request 规划和排序
- object-id pass 与 object-id 输出目标
- `preScenePasses` / `postScenePasses` / `overlayPasses` 的外层编排
这些都由 [CameraRenderer](../CameraRenderer/CameraRenderer.md) 在外层编排。
## 当前实现弹性
接口本身并不强制初始化策略。具体实现可以:
-`Initialize()` 中一次性准备资源。
- 或者像 `BuiltinForwardPipeline` 一样,在 `Render()` 中按需触发或复用初始化逻辑。
因此调用方只依赖语义契约,而不依赖某个具体实现的资源建立时机。
## 公开方法
| 方法 | 说明 |
|------|------|
| [Destructor](Destructor.md) | 虚析构函数。 |
| [Initialize](Initialize.md) | 初始化这条渲染管线需要的底层资源。 |
| [Shutdown](Shutdown.md) | 释放管线资源。 |
| [Render](Render.md) | 把场景数据绘制到指定表面。 |
| [Initialize](Initialize.md) | 初始化这条主管线所需的底层资源。 |
| [Shutdown](Shutdown.md) | 释放管线资源。 |
| [Render](Render.md) | 把一份 `RenderSceneData` 绘制到指定 `RenderSurface`。 |
## 设计说明
`RenderContext``RenderSurface``RenderSceneData` 分开传入,是一个很好的职责拆分:
`RenderContext``RenderSurface``RenderSceneData` 分开传入,是当前很关键的职责拆分:
- `RenderContext` 代表底层执行环境。
- `RenderSurface` 代表当前目标表面
- `RenderSceneData` 表已经提取好的高层场景数据
- `RenderContext` 表示底层设备与命令环境。
- `RenderSurface` 表示这次提交的目标与 render-area
- `RenderSceneData`已经提取好的相机、光照和可绘制项
这样做能让同一条管线更容易在不同目标和不同场景上复用。
这样同一条 runtime pipeline 才能在不同 surface、不同 request 和不同场景提取结果之间复用。
## 相关文档
- [当前模块](../Rendering.md)
- [Rendering](../Rendering.md)
- [CameraRenderer](../CameraRenderer/CameraRenderer.md)
- [RenderPipelineAsset](../RenderPipelineAsset/RenderPipelineAsset.md)
- [BuiltinForwardPipelineAsset](../Pipelines/BuiltinForwardPipelineAsset/BuiltinForwardPipelineAsset.md)
- [SceneRenderer](../SceneRenderer/SceneRenderer.md)

View File

@@ -1,16 +1,29 @@
# RenderPipeline::Shutdown
关闭渲染管线并释放其资源。
关闭这条主场景 runtime 管线并释放其持有资源。
```cpp
virtual void Shutdown() = 0;
```
## 行为说明
## 当前调用语义
派生类应在这里释放这条管线持有的 RHI 资源、缓存和临时对象。
`engine/src/Rendering/CameraRenderer.cpp` 可以看到,当前调用方会在这些时机直接调用它:
- `CameraRenderer` 析构时
- `CameraRenderer::ResetPipeline()` 替换旧主管线时
调用方不会额外区分“这条管线是否做过显式 [Initialize](Initialize.md)”。
## 对实现的要求
- 应释放这条 runtime pipeline 自己持有的 RHI 资源、缓存和临时对象。
- 最好能容忍“尚未显式初始化就被关闭”或“曾经在 [Render](Render.md) 内部惰性初始化过”的调用路径。
- 不应把 object-id pass 或外层 request / `RenderPassSequence` 编排状态混进这里。
## 相关文档
- [返回类型总览](RenderPipeline.md)
- [RenderPipeline::Initialize](Initialize.md)
- [CameraRenderer](../CameraRenderer/CameraRenderer.md)
- [BuiltinForwardPipeline::Shutdown](../Pipelines/BuiltinForwardPipeline/Shutdown.md)

View File

@@ -51,7 +51,7 @@
## 当前实现边界
- 它只规划相机请求,不处理 object-id 或 builtin post-process 的具体填充;那部分通常由更上层调用方补充。
- 它只规划相机请求,不处理 `objectId` 或各类 `RenderPassSequence` 的具体填充;那部分通常由更上层调用方补充。
- 当前没有 camera stacking 的更复杂依赖分析,只有 base / overlay 与 depth 的线性排序。
- `BuildRequests()` 只返回成功构建的请求,不单独报告被过滤掉的相机原因。

View File

@@ -79,7 +79,7 @@
- 这些 helper 是纯局部规则,不处理多表面、多窗口或跨帧状态。
- clear 推导只依赖当前计数,不关心更复杂的 camera stack 依赖关系。
- request 组装阶段只填基础字段object-id 与 builtin post-process 仍需上层补全。
- request 组装阶段只填基础字段`objectId``preScenePasses``postScenePasses``overlayPasses` 仍可由上层继续补全。
## 相关文档

View File

@@ -8,7 +8,7 @@
`SceneRenderer` 自身没有自定义析构逻辑。真正的资源关闭发生在成员析构阶段:
- `m_cameraRenderer` 会在自身析构里关闭当前主管线object-id pass 和 builtin post-process builder
- `m_cameraRenderer` 会在自身析构里关闭当前主管线object-id passrequest 级 `RenderPassSequence` 的生命周期则由每次 `Render()` 调用自行初始化和回收
- `m_requestPlanner` 按普通值成员销毁。
因此当前析构语义是“把 teardown 责任完全交给内部 `CameraRenderer`”,而不是在 `SceneRenderer` 层重复写一套 shutdown 流程。

View File

@@ -13,7 +13,7 @@
`SceneRenderer` 当前不再自己持有 `RenderSceneExtractor` 或直接驱动主管线。它只维护两块运行时对象:
- `m_requestPlanner`:负责从 `Scene` 和可选 override camera 生成 `CameraRenderRequest` 列表。
- `m_cameraRenderer`负责真正执行单个相机请求包括场景提取、主管线提交、object-id pass 和 builtin post-process
- `m_cameraRenderer`负责真正执行单个相机请求包括场景提取、主管线提交、object-id pass,以及 request 上挂接的 pre/post/overlay pass sequence
这意味着当前的主链路已经拆成两层:
@@ -48,6 +48,8 @@ return Render(BuildRenderRequests(scene, overrideCamera, context, surface));
4. 依次调用 `m_cameraRenderer.Render(request)`
5. 任一请求失败就立即返回 `false`
这里不会额外改写 request 里的 `objectId` 或各类 pass sequence如果上层已经补好了这些字段`SceneRenderer` 只负责排序后转交执行。
### `Render(const CameraRenderRequest&)`
这是最薄的一层,直接转发到 `m_cameraRenderer.Render(request)`
@@ -64,6 +66,7 @@ return Render(BuildRenderRequests(scene, overrideCamera, context, surface));
- 不直接做 scene extraction真正的 `RenderSceneExtractor` 调用发生在 `CameraRenderer` 里。
- 不直接操作 `RenderPipeline` 的初始化或渲染细节;它只负责请求规划和排序。
- 不负责替 request 自动补 `objectId` 或各类 `RenderPassSequence`;这类附加阶段通常由 Editor 或其它上层调用方在提交前写入。
- 对手工提交的请求数组,会再次按相机优先级稳定排序,而不是按调用方原始顺序盲目执行。
## 测试覆盖

View File

@@ -15,6 +15,8 @@
同时,这里也统一收纳编辑器侧 API 文档入口 [Editor](Editor/Editor.md),用于描述与引擎并行协作的编辑器应用层接口。
本轮收口之后,`Core/Asset``Rendering``Resources``Scene` 这几个模块已经把基础 helper / 协议层 header 也纳入了 canonical 文档,而不只覆盖高层类和模块页。
## 设计原则
- 目录结构与源码平行,避免“文档是一套世界,源码又是一套世界”。
@@ -38,6 +40,13 @@
- [Scripting](Scripting/Scripting.md)
- [Threading](Threading/Threading.md)
## 重点入口
- [Core / Asset](Core/Asset/Asset.md) - 项目资产身份、artifact 缓存与运行时资源加载基础设施。
- [Rendering](Rendering/Rendering.md) - 相机请求规划、场景提取、主管线、object-id 与显式 `RenderPassSequence` 主链路。
- [Resources](Resources/Resources.md) - 具体资源类型以及 `builtin://` 内置资源入口。
- [Scene](Scene/Scene.md) - 场景容器、运行态执行器与时间推进控制。
## 相关文档
- [API 总索引](../main.md)

View File

@@ -1,27 +1,35 @@
# API 文档重构状态
**生成时间**: `2026-03-28 15:13:19`
**生成时间**: `2026-04-03 15:07:08`
**来源**: `docs/api/_tools/audit_api_docs.py`
## 摘要
- Markdown 页面数(全部): `2881`
- Markdown 页面数canonical: `2860`
- Public headers 数: `227`
- 有效头文件引用数(全部): `227`
- 有效头文件引用数(canonical: `227`
- Markdown 页面数(全部): `3327`
- Markdown 页面数canonical: `3298`
- Public headers 数: `244`
- Editor source headers 数: `124`
- 有效头文件引用数(全部: `244`
- 有效头文件引用数canonical: `244`
- 无效头文件引用数: `0`
- 有效源文件引用数(全部): `124`
- 有效源文件引用数Editor canonical: `124`
- 无效源文件引用数: `0`
- 失效 `.md` 链接数: `0`
-`.md` 相对链接数: `0`
- 旧模板页面数: `0`
- 扁平 header 页面数: `0`
- Canonical 显式过期符号残留数: `0`
- Editor 显式过期符号残留数: `0`
- Editor 残留 canonical 旧页面数: `0`
- Editor 高风险单页目录数: `0`
## 平行目录
- Canonical 根目录: `XCEngine`
- 源码目录节点数: `29`
- 已生成目录总览页节点数: `29`
- 源码目录节点数: `30`
- 已生成目录总览页节点数: `30`
- 缺失目录总览页节点数: `0`
- 支撑目录: `_meta, _tools`
@@ -31,23 +39,43 @@
|------|----------------|--------|--------|
| `Audio` | `11` | `11` | `0` |
| `Components` | `10` | `10` | `0` |
| `Core` | `42` | `42` | `0` |
| `Core` | `48` | `48` | `0` |
| `Debug` | `10` | `10` | `0` |
| `Input` | `5` | `5` | `0` |
| `Memory` | `5` | `5` | `0` |
| `Platform` | `11` | `11` | `0` |
| `RHI` | `87` | `87` | `0` |
| `Rendering` | `13` | `13` | `0` |
| `Resources` | `13` | `13` | `0` |
| `Scene` | `3` | `3` | `0` |
| `Rendering` | `22` | `22` | `0` |
| `Resources` | `14` | `14` | `0` |
| `Scene` | `4` | `4` | `0` |
| `Scripting` | `7` | `7` | `0` |
| `Threading` | `10` | `10` | `0` |
## Editor 源文件页覆盖
| 模块 | Source headers | 已覆盖 | 未覆盖 |
|------|----------------|--------|--------|
| `(root)` | `3` | `3` | `0` |
| `Actions` | `9` | `9` | `0` |
| `Commands` | `4` | `4` | `0` |
| `ComponentEditors` | `10` | `10` | `0` |
| `Core` | `20` | `20` | `0` |
| `Layers` | `1` | `1` | `0` |
| `Layout` | `1` | `1` | `0` |
| `Managers` | `3` | `3` | `0` |
| `Platform` | `4` | `4` | `0` |
| `Scripting` | `3` | `3` | `0` |
| `UI` | `29` | `29` | `0` |
| `Utils` | `4` | `4` | `0` |
| `Viewport` | `23` | `23` | `0` |
| `panels` | `10` | `10` | `0` |
## 元信息覆盖
| 字段 | 页面数 |
|------|--------|
| `命名空间` | `449` |
| `类型` | `449` |
| `描述` | `344` |
| `头文件` | `332` |
| `命名空间` | `891` |
| `类型` | `891` |
| `描述` | `417` |
| `头文件` | `502` |
| `源文件` | `348` |

View File

@@ -0,0 +1,248 @@
# API 文档实时同步任务池2026-04-03第二轮
## 文档定位
这份任务池接替已归档的第一轮计划:
- `docs/plan/used/API文档实时同步任务池_2026-04-03_第一轮归档.md`
第一轮已经解决的重点是:
- canonical 目录结构收口
- 历史缺页补齐
- 第一轮大规模内容重写
本轮重点不再是“补结构”,而是:
- 重新对照当前工作树源码与测试
- 清理最近重构后重新出现的内容级失配
- 继续维护一份适合多人并行认领的增量同步清单
## 当前复核快照
- 最近一次结构审计时间:`2026-04-03 15:07:08`
- 审计命令:`python -B docs/api/_tools/audit_api_docs.py`
- 当前结果:
- `Public headers: 244`
- `Editor source headers: 124`
- `Invalid header refs: 0`
- `Invalid source refs: 0`
- `Broken .md links: 0`
- `Stale canonical doc tokens: 0`
- `Stale editor doc tokens: 0`
- `Stale editor canonical pages: 0`
这说明当前结构层面已经恢复全绿;本轮后续工作以内容级持续同步为主。
## 认领规则
- 一次只认领 `1` 个任务块。
- 先把 `状态` 改成 `DOING`,再写 `认领人`
- 只能改自己任务块的 `写入范围`
- 所有改动都必须以“当前源码 + 当前测试 + 当前真实调用链”为依据,不允许按旧文档续写旧行为。
- 如果清理了过期 API 页面,必须同时清理交叉链接。
## 任务池
## T01 Editor / Viewport 渲染计划与宿主流程内容同步
- 状态: `DONE`
- 认领人: `Codex`
- 优先级: `P0`
- 写入范围:
- `docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/**`
- `docs/api/XCEngine/Editor/Viewport/ViewportHostRenderFlowUtils/**`
- `docs/api/XCEngine/Editor/Viewport/ViewportHostService/**`
- 必要时 `docs/api/XCEngine/Editor/Viewport/IViewportHostService/**`
- 主要源码依据:
- `editor/src/Viewport/SceneViewportRenderPlan.h`
- `editor/src/Viewport/ViewportHostRenderFlowUtils.h`
- `editor/src/Viewport/ViewportHostService.h`
- `tests/editor/test_viewport_render_flow_utils.cpp`
- 已关闭问题:
- 旧文档仍把 Scene View 当前主路径写成 builtin post-process 主导
- 没写清 grid / selection outline 现在已转为显式 `postScenePasses`
- 没写清 overlay pass 是 `editorOverlayFrameData + transientOverlayFrameData` 合并后再创建
- 没写清 `Scene object id shader view is unavailable` 是局部降级警告,不是整帧失败
- 完成记录:
- 已重写 `SceneViewportRenderPlan.md``BuildSceneViewportRenderPlan.md``ApplySceneViewportRenderPlan.md`
- 已同步 `ViewportHostRenderFlowUtils.md``ViewportHostService.md``RenderRequestedViewports.md`
- 已清理 `IViewportHostService` 中残留的旧表述
## T02 Core / Asset 缓存接口改名与语义重构同步
- 状态: `DONE`
- 认领人: `Codex`
- 优先级: `P0`
- 写入范围:
- `docs/api/XCEngine/Core/Asset/ResourceManager/**`
- `docs/api/XCEngine/Core/Asset/AssetImportService/**`
- `docs/api/XCEngine/Core/Asset/ProjectAssetIndex/**`
- 必要时 `docs/api/XCEngine/Core/Asset/Asset.md`
- 必要时 `docs/api/XCEngine/Core/Asset/ArtifactFormats/**`
- 主要源码依据:
- `engine/include/XCEngine/Core/Asset/ResourceManager.h`
- `engine/src/Core/Asset/ResourceManager.cpp`
- `engine/include/XCEngine/Core/Asset/AssetImportService.h`
- `engine/src/Core/Asset/AssetImportService.cpp`
- `engine/include/XCEngine/Core/Asset/ProjectAssetIndex.h`
- `engine/src/Core/Asset/ProjectAssetIndex.cpp`
- `tests/core/Asset/test_resource_manager.cpp`
- 已关闭问题:
- `RefreshAssetDatabase` 已经不存在,但旧文档仍在沿用
- `RefreshProjectAssets` / `RebuildProjectAssetCache` / `GetProjectLibraryRoot` 缺页或未写清
- `AssetImportService::EnsureArtifact()` 旧文档仍把输出写成 `ResolvedAsset`
- `BuildLookupSnapshot()` 旧文档仍按“双 map 出参”描述,而不是 `LookupSnapshot`
- `ImportedAsset::runtimeLoadPath` 语义未同步到上层文档
- 完成记录:
- 已删除过期页 `ResourceManager/RefreshAssetDatabase.md`
- 已补齐 `RefreshProjectAssets.md``RebuildProjectAssetCache.md``GetProjectLibraryRoot.md`
- 已补齐 `LookupSnapshot.md``ImportedAsset.md``GetLibraryRoot.md``RebuildLibraryCache.md`
- 已同步 `Asset.md``ArtifactFormats.md``ResourceManager/Load.md``runtimeLoadPath` 口径
## T03 Scripting / Mono 托管销毁入口同步
- 状态: `DONE`
- 认领人: `Codex`
- 优先级: `P1`
- 写入范围:
- `docs/api/XCEngine/Scripting/Mono/MonoScriptRuntime/**`
- 主要源码依据:
- `engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h`
- `engine/src/Scripting/Mono/MonoScriptRuntime.cpp`
- `tests/scripting/test_mono_script_runtime.cpp`
- 已关闭问题:
- `DestroyManagedObject(MonoObject*)` 已进入头文件与测试,但文档树没有对应页面
- `MonoScriptRuntime.md` 没写清 `Object.Destroy(...)` 会回落到原生对象 / 组件销毁
- 完成记录:
- 已新增 `DestroyManagedObject.md`
- 已更新 `MonoScriptRuntime.md` 的 internal call 说明与方法总表
## T04 Cross-Module / 教程层与模块总览口径持续复核
- 状态: `DONE`
- 认领人: `Codex`
- 优先级: `P2`
- 写入范围:
- `docs/api/_guides/**`
- `docs/api/XCEngine/*/*.md`
- 仅限与本轮已确认 API 变更直接相关的总览页
- 任务目标:
- 继续检查教程页、模块总览页是否仍在传播旧心智模型
- 尤其关注:
- Scene View 仍被写成 builtin post-process 主导
- 资源导入链仍被写成 `artifactMainPath` / `RefreshAssetDatabase` 时代的口径
- 托管对象销毁路径未在教程层被解释
- 产出要求:
- 只修正已确认失配的 guide / overview 页面
- 不做与当前源码无关的泛化扩写
## T05 增量变更监控 / 新一轮差异发现
- 状态: `OPEN`
- 认领人: ``
- 优先级: `P2`
- 写入范围:
- 只读检查 `engine/include/**``engine/src/**``editor/src/**``tests/**`
- 必要时只向本任务池追加新任务块
- 任务目标:
- 继续结合工作树最新改动,找出新的“源码已变但文档还没跟上”的内容级失配
- 优先检查:
- 最近改动过的 public headers
- 最近改动过的 Editor source headers
- 最近新增或更新过的测试
- 产出要求:
- 只记录已确认的问题
- 每条新任务都要写明:
- 受影响文档
- 主要源码依据
- 真实失配点
- 建议写入范围
## T06 Rendering / Camera request、Passes 与执行链旧口径清理
- 状态: `DONE`
- 认领人: `Codex`
- 优先级: `P1`
- 写入范围:
- `docs/api/XCEngine/Rendering/CameraRenderRequest/**`
- `docs/api/XCEngine/Rendering/CameraRenderer/**`
- `docs/api/XCEngine/Rendering/Passes/**`
- `docs/api/XCEngine/Rendering/ObjectIdPass/**`
- `docs/api/XCEngine/Rendering/RenderPipeline/**`
- `docs/api/XCEngine/Rendering/SceneRenderer/**`
- `docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/**`
- 必要时 `docs/api/XCEngine/XCEngine.md`
- 必要时 `docs/api/_tools/audit_api_docs.py`
- 主要源码依据:
- `engine/include/XCEngine/Rendering/CameraRenderRequest.h`
- `engine/include/XCEngine/Rendering/CameraRenderer.h`
- `engine/include/XCEngine/Rendering/Passes/BuiltinObjectIdPass.h`
- `engine/include/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass.h`
- `engine/include/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass.h`
- `engine/include/XCEngine/Rendering/ObjectIdPass.h`
- `engine/include/XCEngine/Rendering/RenderPipeline.h`
- `engine/include/XCEngine/Rendering/SceneRenderer.h`
- `engine/src/Rendering/CameraRenderer.cpp`
- `engine/src/Rendering/SceneRenderer.cpp`
- `tests/Rendering/unit/test_camera_scene_renderer.cpp`
- `editor/src/Viewport/SceneViewportRenderPlan.h`
- `editor/src/Viewport/Passes/SceneViewportGridPass.cpp`
- `editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp`
- `editor/src/Viewport/ViewportHostRenderFlowUtils.h`
- `tests/Editor/test_viewport_render_flow_utils.cpp`
- `tests/Editor/test_scene_viewport_overlay_renderer.cpp`
- 已关闭问题:
- `CameraRenderRequest.md` 仍描述已删除的 `builtinPostProcess` 子请求
- 页面仍链接到不存在的 `BuiltinPostProcessRequest/BuiltinPostProcessRequest.md`
- Scene View 已改为通过 `postScenePasses` / `overlayPasses` 写回 request但该页口径未同步
- `CameraRenderer.md` 仍保留已删除的 `m_builtinPostProcessBuilder` 历史口径
- `Passes.md` 的典型链路只写了 `postScenePasses`,遗漏当前 `overlayPasses` 组装路径
- `Rendering/Passes` 下仍残留已删除的 `BuiltinPostProcessPassPlan` / `BuiltinPostProcessPassSequenceBuilder` 页面
- `BuiltinObjectIdPass``BuiltinInfiniteGridPass` 多个公开入口缺页
- `ObjectIdPass.md``RenderPipeline.md``SceneRenderer.md` 与顶层 `XCEngine.md` 仍在传播 builtin-post-process 心智模型
- 完成记录:
- 已重写 `CameraRenderRequest.md`
- 已清理 `2` 个失效 `.md` 链接
- 已同步 `CameraRenderer.md``Passes.md` 的当前执行链路表述
- 已删除 `BuiltinPostProcessPassPlan.md``BuiltinPostProcessPassSequenceBuilder.md`
- 已补齐 `BuiltinObjectIdPass` / `BuiltinInfiniteGridPass` 缺失页面,并补充 `SceneViewportRenderPlan` 下 grid / selection outline pass factory 页面
- 已同步 `ObjectIdPass.md``RenderPipeline.md``SceneRenderer.md``XCEngine.md` 的当前口径
- 已补充 Rendering guide、`CameraRenderer::Render``ViewportHostService::RenderRequestedViewports` 的 request 级 pass 注入说明
- 已为 `builtinPostProcess` / `BuiltinPostProcessRequest` / `m_builtinPostProcessBuilder` 增加 canonical 过期符号审计
- 已复跑结构审计并确认 `Broken .md links: 0`
## T07 Editor / Core `EditorConsoleSink` 生命周期说明同步
- 状态: `DONE`
- 认领人: `Codex`
- 优先级: `P1`
- 写入范围:
- `docs/api/XCEngine/Editor/Core/EditorConsoleSink/**`
- 必要时 `docs/api/XCEngine/Editor/Managers/Managers.md`
- 必要时 `docs/api/_tools/audit_api_docs.py`
- 主要源码依据:
- `editor/src/Core/EditorConsoleSink.h`
- `editor/src/Core/EditorConsoleSink.cpp`
- `editor/src/panels/ConsolePanel.cpp`
- `editor/src/Core/EditorLoggingSetup.h`
- `tests/Editor/test_editor_console_sink.cpp`
- 已关闭问题:
- `EditorConsoleSink::GetInstance()` 旧文档仍把它写成会返回 fallback 实例
- 相关 overview 页面没有写清活动 sink 销毁后会返回 `nullptr`
- 审计脚本此前无法自动拦截这类旧生命周期表述
- 完成记录:
- 已同步 `EditorConsoleSink.md``GetInstance.md``Destructor.md`
- 已同步 `Managers.md` 中对控制台 sink 生命周期的引用口径
- 已为 `fallback 实例` 与“不会返回空指针”旧表述增加定向审计
## 当前结论
- 本轮已经关掉五组明确的内容级失配:
- `Viewport` 渲染计划与宿主流程
- `Core/Asset` 缓存接口与导入服务语义
- `MonoScriptRuntime` 托管销毁入口
- `Rendering` 相机请求、Passes 与单相机执行链旧口径
- `EditorConsoleSink` 生命周期与空指针返回语义
- 当前结构审计为全绿。
- 当前仍建议保留 `T04``T05` 作为持续性并行入口,用来承接你后续重构带来的新文档漂移。

View File

@@ -259,7 +259,10 @@ C# 脚本模块已经完成了第一阶段的核心闭环,不再是“从 0
- 已新增运行时回归用例验证层级查找、自身命中、组件销毁、GameObject 销毁。
- 已通过 `MonoScriptRuntimeTest.*``ProjectScriptAssemblyTest.*` 相关整组验证。
- 下一步建议继续做第二批 Unity API 对齐:
- 已完成第二步第一小项:`GetComponents<T>()`
- 下一步候选:`Object.Instantiate`
- 下一步候选:`tag / CompareTag / layer`
- 已完成第二步第二小项:`tag / CompareTag / layer`
- `GameObject``Component``MonoBehaviour` 已提供 Unity 风格 `tag``layer``CompareTag(string)` 入口。
- native `GameObject`、场景查找、序列化与反序列化已同步收口,`FindGameObjectWithTag` / `FindGameObjectsWithTag` 已改为基于 tag。
- 已新增 `TagLayerProbe` 与对应 C++ / Mono 回归测试,覆盖默认 tag、layer 裁剪、脚本侧读写与场景 roundtrip。
- 相关定向测试全部通过;完整 `MonoScriptRuntimeTest.*:ProjectScriptAssemblyTest.*` 输出全绿,当前仍存在历史性的 `exit code 3` 异常,需后续单独跟踪。
- 下一步建议继续做第三小项:`Object.Instantiate`

View File

@@ -30,6 +30,9 @@ public:
uint64_t GetUUID() const { return m_uuid; }
const std::string& GetName() const { return m_name; }
void SetName(const std::string& name) { m_name = name; }
const std::string& GetTag() const { return m_tag; }
void SetTag(const std::string& tag) { m_tag = tag.empty() ? std::string("Untagged") : tag; }
bool CompareTag(const std::string& tag) const { return m_tag == tag; }
uint8_t GetLayer() const { return m_layer; }
void SetLayer(uint8_t layer) { m_layer = std::min<uint8_t>(layer, 31u); }
@@ -217,6 +220,7 @@ private:
ID m_id = INVALID_ID;
uint64_t m_uuid = 0;
std::string m_name;
std::string m_tag = "Untagged";
bool m_activeSelf = true;
bool m_started = false;
uint8_t m_layer = 0;

View File

@@ -8,7 +8,14 @@ namespace Components {
GameObject::ID GameObject::s_nextID = 1;
GameObject::GameObject()
: m_name("GameObject") {
: m_name("GameObject")
, m_tag("Untagged")
, m_activeSelf(true)
, m_started(false)
, m_layer(0)
, m_parent(nullptr)
, m_scene(nullptr)
, m_transform(nullptr) {
m_id = s_nextID++;
static std::random_device rd;
static std::mt19937_64 gen(rd());
@@ -19,7 +26,14 @@ GameObject::GameObject()
}
GameObject::GameObject(const std::string& name)
: m_name(name) {
: m_name(name)
, m_tag("Untagged")
, m_activeSelf(true)
, m_started(false)
, m_layer(0)
, m_parent(nullptr)
, m_scene(nullptr)
, m_transform(nullptr) {
m_id = s_nextID++;
static std::random_device rd;
static std::mt19937_64 gen(rd());
@@ -212,7 +226,7 @@ std::vector<GameObject*> GameObject::FindGameObjectsWithTag(const std::string& t
auto& registry = GetGlobalRegistry();
std::vector<GameObject*> result;
for (auto& pair : registry) {
if (pair.second->GetName() == tag) {
if (pair.second->CompareTag(tag)) {
result.push_back(pair.second);
}
}
@@ -316,6 +330,7 @@ void GameObject::Destroy() {
void GameObject::Serialize(std::ostream& os) const {
os << "name=" << m_name << ";";
os << "tag=" << m_tag << ";";
os << "active=" << (m_activeSelf ? "1" : "0") << ";";
os << "layer=" << static_cast<uint32_t>(m_layer) << ";";
os << "id=" << m_id << ";";
@@ -342,6 +357,10 @@ void GameObject::Deserialize(std::istream& is) {
if (strcmp(key, "name") == 0) {
std::getline(is, m_name, ';');
} else if (strcmp(key, "tag") == 0) {
std::string tag;
std::getline(is, tag, ';');
SetTag(tag);
} else if (strcmp(key, "active") == 0) {
char val;
is.get(val);

View File

@@ -31,7 +31,9 @@ struct PendingGameObjectData {
GameObject::ID id = GameObject::INVALID_ID;
uint64_t uuid = 0;
std::string name = "GameObject";
std::string tag = "Untagged";
bool active = true;
uint8_t layer = 0;
GameObject::ID parentId = GameObject::INVALID_ID;
std::string transformPayload;
std::vector<PendingComponentData> components;
@@ -75,6 +77,28 @@ std::string UnescapeString(const std::string& value) {
return unescaped;
}
GameObject* FindInChildrenByTag(GameObject* parent, const std::string& tag) {
if (!parent) {
return nullptr;
}
for (GameObject* child : parent->GetChildren()) {
if (!child) {
continue;
}
if (child->CompareTag(tag)) {
return child;
}
if (GameObject* found = FindInChildrenByTag(child, tag)) {
return found;
}
}
return nullptr;
}
void SerializeGameObjectRecursive(std::ostream& os, GameObject* gameObject) {
if (!gameObject) {
return;
@@ -84,7 +108,9 @@ void SerializeGameObjectRecursive(std::ostream& os, GameObject* gameObject) {
os << "id=" << gameObject->GetID() << "\n";
os << "uuid=" << gameObject->GetUUID() << "\n";
os << "name=" << EscapeString(gameObject->GetName()) << "\n";
os << "tag=" << EscapeString(gameObject->GetTag()) << "\n";
os << "active=" << (gameObject->IsActive() ? 1 : 0) << "\n";
os << "layer=" << static_cast<uint32_t>(gameObject->GetLayer()) << "\n";
os << "parent=" << (gameObject->GetParent() ? gameObject->GetParent()->GetID() : GameObject::INVALID_ID) << "\n";
os << "transform=";
gameObject->GetTransform()->Serialize(os);
@@ -212,11 +238,11 @@ GameObject* Scene::FindInChildren(GameObject* parent, const std::string& name) c
GameObject* Scene::FindGameObjectWithTag(const std::string& tag) const {
for (auto* go : GetRootGameObjects()) {
if (go->GetName() == tag) {
if (go->CompareTag(tag)) {
return go;
}
GameObject* found = FindInChildren(go, tag);
if (found) {
if (GameObject* found = FindInChildrenByTag(go, tag)) {
return found;
}
}
@@ -312,8 +338,12 @@ void Scene::DeserializeFromString(const std::string& data) {
currentObject->uuid = std::stoull(value);
} else if (key == "name") {
currentObject->name = UnescapeString(value);
} else if (key == "tag") {
currentObject->tag = UnescapeString(value);
} else if (key == "active") {
currentObject->active = (value == "1");
} else if (key == "layer") {
currentObject->layer = static_cast<uint8_t>(std::min<uint32_t>(std::stoul(value), 31u));
} else if (key == "parent") {
currentObject->parentId = static_cast<GameObject::ID>(std::stoull(value));
} else if (key == "transform") {
@@ -340,7 +370,9 @@ void Scene::DeserializeFromString(const std::string& data) {
if (pending.uuid != 0) {
go->m_uuid = pending.uuid;
}
go->SetTag(pending.tag);
go->m_activeSelf = pending.active;
go->SetLayer(pending.layer);
go->m_scene = this;
if (!pending.transformPayload.empty()) {

View File

@@ -572,6 +572,41 @@ void InternalCall_GameObject_SetName(uint64_t gameObjectUUID, MonoString* name)
gameObject->SetName(MonoStringToUtf8(name));
}
MonoString* InternalCall_GameObject_GetTag(uint64_t gameObjectUUID) {
Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID);
return mono_string_new(
mono_domain_get(),
gameObject ? gameObject->GetTag().c_str() : "");
}
void InternalCall_GameObject_SetTag(uint64_t gameObjectUUID, MonoString* tag) {
Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID);
if (!gameObject) {
return;
}
gameObject->SetTag(MonoStringToUtf8(tag));
}
mono_bool InternalCall_GameObject_CompareTag(uint64_t gameObjectUUID, MonoString* tag) {
Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID);
return (gameObject && gameObject->CompareTag(MonoStringToUtf8(tag))) ? 1 : 0;
}
int32_t InternalCall_GameObject_GetLayer(uint64_t gameObjectUUID) {
Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID);
return gameObject ? static_cast<int32_t>(gameObject->GetLayer()) : 0;
}
void InternalCall_GameObject_SetLayer(uint64_t gameObjectUUID, int32_t layer) {
Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID);
if (!gameObject) {
return;
}
gameObject->SetLayer(static_cast<uint8_t>(std::clamp(layer, 0, 31)));
}
mono_bool InternalCall_GameObject_GetActiveSelf(uint64_t gameObjectUUID) {
Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID);
return (gameObject && gameObject->IsActive()) ? 1 : 0;
@@ -1492,6 +1527,11 @@ void RegisterInternalCalls() {
mono_add_internal_call("XCEngine.InternalCalls::Input_GetMouseScrollDelta", reinterpret_cast<const void*>(&InternalCall_Input_GetMouseScrollDelta));
mono_add_internal_call("XCEngine.InternalCalls::GameObject_GetName", reinterpret_cast<const void*>(&InternalCall_GameObject_GetName));
mono_add_internal_call("XCEngine.InternalCalls::GameObject_SetName", reinterpret_cast<const void*>(&InternalCall_GameObject_SetName));
mono_add_internal_call("XCEngine.InternalCalls::GameObject_GetTag", reinterpret_cast<const void*>(&InternalCall_GameObject_GetTag));
mono_add_internal_call("XCEngine.InternalCalls::GameObject_SetTag", reinterpret_cast<const void*>(&InternalCall_GameObject_SetTag));
mono_add_internal_call("XCEngine.InternalCalls::GameObject_CompareTag", reinterpret_cast<const void*>(&InternalCall_GameObject_CompareTag));
mono_add_internal_call("XCEngine.InternalCalls::GameObject_GetLayer", reinterpret_cast<const void*>(&InternalCall_GameObject_GetLayer));
mono_add_internal_call("XCEngine.InternalCalls::GameObject_SetLayer", reinterpret_cast<const void*>(&InternalCall_GameObject_SetLayer));
mono_add_internal_call("XCEngine.InternalCalls::GameObject_GetActiveSelf", reinterpret_cast<const void*>(&InternalCall_GameObject_GetActiveSelf));
mono_add_internal_call("XCEngine.InternalCalls::GameObject_GetActiveInHierarchy", reinterpret_cast<const void*>(&InternalCall_GameObject_GetActiveInHierarchy));
mono_add_internal_call("XCEngine.InternalCalls::GameObject_SetActive", reinterpret_cast<const void*>(&InternalCall_GameObject_SetActive));

View File

@@ -110,6 +110,7 @@ set(XCENGINE_GAME_SCRIPT_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/MeshComponentProbe.cs
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/MeshRendererEdgeCaseProbe.cs
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/ObjectApiProbe.cs
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/TagLayerProbe.cs
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/TickLogProbe.cs
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/TransformConversionProbe.cs
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/TransformMotionProbe.cs

View File

@@ -0,0 +1,35 @@
using XCEngine;
namespace Gameplay
{
public sealed class TagLayerProbe : MonoBehaviour
{
public string ObservedInitialTag = string.Empty;
public int ObservedInitialLayer = -1;
public bool ObservedInitialCompareTag;
public bool ObservedGameObjectRouteMatches;
public string ObservedUpdatedTag = string.Empty;
public int ObservedUpdatedLayer = -1;
public bool ObservedUpdatedCompareTag;
public bool ObservedOriginalTagRejected;
public bool ObservedEmptyTagRejected;
public void Start()
{
ObservedInitialTag = tag;
ObservedInitialLayer = layer;
ObservedInitialCompareTag = CompareTag("Enemy");
ObservedGameObjectRouteMatches = gameObject.tag == tag
&& gameObject.layer == layer;
tag = "Player";
layer = 33;
ObservedUpdatedTag = gameObject.tag;
ObservedUpdatedLayer = gameObject.layer;
ObservedUpdatedCompareTag = gameObject.CompareTag("Player");
ObservedOriginalTagRejected = !CompareTag("Enemy");
ObservedEmptyTagRejected = !CompareTag(string.Empty);
}
}
}

View File

@@ -24,6 +24,30 @@ namespace XCEngine
public Transform Transform => GameObject.Transform;
public Transform transform => Transform;
public string Tag
{
get => GameObject.tag;
set => GameObject.tag = value;
}
public string tag
{
get => Tag;
set => Tag = value;
}
public int Layer
{
get => GameObject.layer;
set => GameObject.layer = value;
}
public int layer
{
get => Layer;
set => Layer = value;
}
public bool HasComponent<T>() where T : Component
{
return GameObject.HasComponent<T>();
@@ -60,6 +84,11 @@ namespace XCEngine
return component != null;
}
public bool CompareTag(string tag)
{
return GameObject.CompareTag(tag);
}
internal static T Create<T>(ulong gameObjectUUID) where T : Component
{
return Create(typeof(T), gameObjectUUID) as T;

View File

@@ -40,6 +40,30 @@ namespace XCEngine
set => Name = value;
}
public string Tag
{
get => InternalCalls.GameObject_GetTag(UUID) ?? string.Empty;
set => InternalCalls.GameObject_SetTag(UUID, value ?? string.Empty);
}
public string tag
{
get => Tag;
set => Tag = value;
}
public int Layer
{
get => InternalCalls.GameObject_GetLayer(UUID);
set => InternalCalls.GameObject_SetLayer(UUID, value);
}
public int layer
{
get => Layer;
set => Layer = value;
}
public bool activeSelf => InternalCalls.GameObject_GetActiveSelf(UUID);
public bool activeInHierarchy => InternalCalls.GameObject_GetActiveInHierarchy(UUID);
@@ -54,6 +78,11 @@ namespace XCEngine
InternalCalls.GameObject_Destroy(UUID);
}
public bool CompareTag(string tag)
{
return InternalCalls.GameObject_CompareTag(UUID, tag ?? string.Empty);
}
public Transform Transform => GetComponent<Transform>();
public Transform transform => Transform;

View File

@@ -71,6 +71,21 @@ namespace XCEngine
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void GameObject_SetName(ulong gameObjectUUID, string name);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern string GameObject_GetTag(ulong gameObjectUUID);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void GameObject_SetTag(ulong gameObjectUUID, string tag);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool GameObject_CompareTag(ulong gameObjectUUID, string tag);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern int GameObject_GetLayer(ulong gameObjectUUID);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void GameObject_SetLayer(ulong gameObjectUUID, int layer);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool GameObject_GetActiveSelf(ulong gameObjectUUID);

View File

@@ -56,6 +56,8 @@ TEST(GameObject_Test, DefaultConstructor_DefaultValues) {
GameObject go;
EXPECT_EQ(go.GetName(), "GameObject");
EXPECT_EQ(go.GetTag(), "Untagged");
EXPECT_TRUE(go.CompareTag("Untagged"));
EXPECT_TRUE(go.IsActive());
EXPECT_NE(go.GetID(), GameObject::INVALID_ID);
}
@@ -68,6 +70,7 @@ TEST(GameObject_Test, NamedConstructor) {
TEST(GameObject_Test, Layer_GetSetAndSerializeRoundTrip) {
GameObject source("LayeredObject");
source.SetTag("Player");
source.SetLayer(7);
std::stringstream stream;
@@ -76,7 +79,9 @@ TEST(GameObject_Test, Layer_GetSetAndSerializeRoundTrip) {
GameObject target;
target.Deserialize(stream);
EXPECT_EQ(source.GetTag(), "Player");
EXPECT_EQ(source.GetLayer(), 7u);
EXPECT_EQ(target.GetTag(), "Player");
EXPECT_EQ(target.GetLayer(), 7u);
}

View File

@@ -1828,6 +1828,53 @@ TEST_F(MonoScriptRuntimeTest, GameObjectRuntimeApiCreatesFindsAndDestroysSceneOb
EXPECT_EQ(meshRenderer->GetRenderLayer(), 4u);
}
TEST_F(MonoScriptRuntimeTest, GameObjectTagAndLayerApiExposeUnityStylePropertiesAndCompareTag) {
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
GameObject* host = runtimeScene->CreateGameObject("Host");
host->SetTag("Enemy");
host->SetLayer(7);
ScriptComponent* component = AddScript(host, "Gameplay", "TagLayerProbe");
engine->OnRuntimeStart(runtimeScene);
engine->OnUpdate(0.016f);
std::string observedInitialTag;
int32_t observedInitialLayer = -1;
bool observedInitialCompareTag = false;
bool observedGameObjectRouteMatches = false;
std::string observedUpdatedTag;
int32_t observedUpdatedLayer = -1;
bool observedUpdatedCompareTag = false;
bool observedOriginalTagRejected = false;
bool observedEmptyTagRejected = false;
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialTag", observedInitialTag));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialLayer", observedInitialLayer));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialCompareTag", observedInitialCompareTag));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedGameObjectRouteMatches", observedGameObjectRouteMatches));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdatedTag", observedUpdatedTag));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdatedLayer", observedUpdatedLayer));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdatedCompareTag", observedUpdatedCompareTag));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedOriginalTagRejected", observedOriginalTagRejected));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedEmptyTagRejected", observedEmptyTagRejected));
EXPECT_EQ(observedInitialTag, "Enemy");
EXPECT_EQ(observedInitialLayer, 7);
EXPECT_TRUE(observedInitialCompareTag);
EXPECT_TRUE(observedGameObjectRouteMatches);
EXPECT_EQ(observedUpdatedTag, "Player");
EXPECT_EQ(observedUpdatedLayer, 31);
EXPECT_TRUE(observedUpdatedCompareTag);
EXPECT_TRUE(observedOriginalTagRejected);
EXPECT_TRUE(observedEmptyTagRejected);
EXPECT_EQ(host->GetTag(), "Player");
EXPECT_EQ(host->GetLayer(), 31u);
EXPECT_EQ(runtimeScene->FindGameObjectWithTag("Player"), host);
EXPECT_EQ(runtimeScene->FindGameObjectWithTag("Enemy"), nullptr);
}
TEST_F(MonoScriptRuntimeTest, RuntimeCreatedScriptComponentCreatesManagedInstanceAfterClassAssignment) {
Scene* runtimeScene = CreateScene("MonoRuntimeScene");