diff --git a/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/ApplySceneViewportRenderPlan.md b/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/ApplySceneViewportRenderPlan.md new file mode 100644 index 00000000..cd61206e --- /dev/null +++ b/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/ApplySceneViewportRenderPlan.md @@ -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) diff --git a/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/BuildSceneViewportRenderPlan.md b/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/BuildSceneViewportRenderPlan.md new file mode 100644 index 00000000..dcea8fe5 --- /dev/null +++ b/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/BuildSceneViewportRenderPlan.md @@ -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& 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) diff --git a/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/HasOverlayPasses.md b/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/HasOverlayPasses.md new file mode 100644 index 00000000..ba6a09c5 --- /dev/null +++ b/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/HasOverlayPasses.md @@ -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) diff --git a/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/HasPostScenePasses.md b/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/HasPostScenePasses.md new file mode 100644 index 00000000..62ed095b --- /dev/null +++ b/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/HasPostScenePasses.md @@ -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) diff --git a/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/SceneViewportGridPassFactory.md b/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/SceneViewportGridPassFactory.md new file mode 100644 index 00000000..5b1bc7dd --- /dev/null +++ b/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/SceneViewportGridPassFactory.md @@ -0,0 +1,29 @@ +# SceneViewportGridPassFactory + +**命名空间**: `XCEngine::Editor` + +**类型**: `type alias` + +**源文件**: `editor/src/Viewport/SceneViewportRenderPlan.h` + +## 定义 + +```cpp +using SceneViewportGridPassFactory = + std::function(const Rendering::Passes::InfiniteGridPassData&)>; +``` + +## 作用 + +描述一个“根据 Scene View 相机数据创建网格 pass”的工厂回调。 + +## 当前语义 + +- 输入是一份已经由 `BuildSceneViewportGridPassData(...)` 组装好的 `InfiniteGridPassData`。 +- 输出是要追加到 `SceneViewportRenderPlan::postScenePasses` 的一个 `RenderPass`。 +- 返回 `nullptr` 时,本帧不会追加网格 pass。 + +## 相关文档 + +- [SceneViewportRenderPlan](SceneViewportRenderPlan.md) +- [BuildSceneViewportRenderPlan](BuildSceneViewportRenderPlan.md) diff --git a/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/SceneViewportOverlayPassFactory.md b/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/SceneViewportOverlayPassFactory.md new file mode 100644 index 00000000..2fe52bb9 --- /dev/null +++ b/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/SceneViewportOverlayPassFactory.md @@ -0,0 +1,31 @@ +# SceneViewportOverlayPassFactory + +**命名空间**: `XCEngine::Editor` + +**类型**: `type alias` + +**源文件**: `editor/src/Viewport/SceneViewportRenderPlan.h` + +## 定义 + +```cpp +using SceneViewportOverlayPassFactory = + std::function(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) diff --git a/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/SceneViewportRenderPlan.md b/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/SceneViewportRenderPlan.md new file mode 100644 index 00000000..49a91350 --- /dev/null +++ b/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/SceneViewportRenderPlan.md @@ -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) diff --git a/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/SceneViewportRenderPlanBuildResult.md b/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/SceneViewportRenderPlanBuildResult.md new file mode 100644 index 00000000..98573639 --- /dev/null +++ b/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/SceneViewportRenderPlanBuildResult.md @@ -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) diff --git a/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/SceneViewportSelectionOutlinePassFactory.md b/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/SceneViewportSelectionOutlinePassFactory.md new file mode 100644 index 00000000..8fe97435 --- /dev/null +++ b/docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/SceneViewportSelectionOutlinePassFactory.md @@ -0,0 +1,31 @@ +# SceneViewportSelectionOutlinePassFactory + +**命名空间**: `XCEngine::Editor` + +**类型**: `type alias` + +**源文件**: `editor/src/Viewport/SceneViewportRenderPlan.h` + +## 定义 + +```cpp +using SceneViewportSelectionOutlinePassFactory = std::function( + RHI::RHIResourceView*, + const std::vector&, + 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) diff --git a/docs/api/XCEngine/Rendering/CameraRenderRequest/BuiltinPostProcessRequest/BuiltinPostProcessRequest.md b/docs/api/XCEngine/Rendering/CameraRenderRequest/BuiltinPostProcessRequest/BuiltinPostProcessRequest.md deleted file mode 100644 index 325f9157..00000000 --- a/docs/api/XCEngine/Rendering/CameraRenderRequest/BuiltinPostProcessRequest/BuiltinPostProcessRequest.md +++ /dev/null @@ -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) diff --git a/docs/api/XCEngine/Rendering/CameraRenderRequest/BuiltinPostProcessRequest/IsRequested.md b/docs/api/XCEngine/Rendering/CameraRenderRequest/BuiltinPostProcessRequest/IsRequested.md deleted file mode 100644 index 72423d3f..00000000 --- a/docs/api/XCEngine/Rendering/CameraRenderRequest/BuiltinPostProcessRequest/IsRequested.md +++ /dev/null @@ -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 必须显式提供选中对象 ID,outline / debug mask 路径才会进入 builder。 - -## 相关文档 - -- [返回类型总览](BuiltinPostProcessRequest.md) -- [CameraRenderRequest](../CameraRenderRequest.md) -- [BuiltinPostProcessPassSequenceBuilder](../../Passes/BuiltinPostProcessPassSequenceBuilder/BuiltinPostProcessPassSequenceBuilder.md) diff --git a/docs/api/XCEngine/Rendering/CameraRenderRequest/CameraRenderRequest.md b/docs/api/XCEngine/Rendering/CameraRenderRequest/CameraRenderRequest.md index c352e259..d3c6cda5 100644 --- a/docs/api/XCEngine/Rendering/CameraRenderRequest/CameraRenderRequest.md +++ b/docs/api/XCEngine/Rendering/CameraRenderRequest/CameraRenderRequest.md @@ -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&)` 会先检查每条 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) diff --git a/docs/api/XCEngine/Rendering/CameraRenderRequest/IsValid.md b/docs/api/XCEngine/Rendering/CameraRenderRequest/IsValid.md index fe86a142..aedb6488 100644 --- a/docs/api/XCEngine/Rendering/CameraRenderRequest/IsValid.md +++ b/docs/api/XCEngine/Rendering/CameraRenderRequest/IsValid.md @@ -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` ## 返回值 diff --git a/docs/api/XCEngine/Rendering/CameraRenderer/CameraRenderer.md b/docs/api/XCEngine/Rendering/CameraRenderer/CameraRenderer.md index b144f7c4..3d26a530 100644 --- a/docs/api/XCEngine/Rendering/CameraRenderer/CameraRenderer.md +++ b/docs/api/XCEngine/Rendering/CameraRenderer/CameraRenderer.md @@ -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-area、object-id 请求和 builtin post-process 前置条件。 +1. 校验 request、自身主管线、render-area 和 object-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) diff --git a/docs/api/XCEngine/Rendering/CameraRenderer/Destructor.md b/docs/api/XCEngine/Rendering/CameraRenderer/Destructor.md index ee271a2f..8890f684 100644 --- a/docs/api/XCEngine/Rendering/CameraRenderer/Destructor.md +++ b/docs/api/XCEngine/Rendering/CameraRenderer/Destructor.md @@ -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` 并不由这里持有,因此也不在这里做统一回收。 ## 注意事项 diff --git a/docs/api/XCEngine/Rendering/CameraRenderer/Render.md b/docs/api/XCEngine/Rendering/CameraRenderer/Render.md index 87714876..c7ff6c1b 100644 --- a/docs/api/XCEngine/Rendering/CameraRenderer/Render.md +++ b/docs/api/XCEngine/Rendering/CameraRenderer/Render.md @@ -6,41 +6,39 @@ bool Render(const CameraRenderRequest& request); ## 行为说明 -执行一次完整的单相机提交。当前实现会把主场景、object-id、注入 pass 和 builtin post-process 串成一条固定顺序的执行链。 +执行一次完整的单相机提交。当前实现会把主场景、object-id、调用方注入的 post passes 和 overlay 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 初始化失败时的回滚路径。 ## 相关文档 diff --git a/docs/api/XCEngine/Rendering/ObjectIdPass/ObjectIdPass.md b/docs/api/XCEngine/Rendering/ObjectIdPass/ObjectIdPass.md new file mode 100644 index 00000000..1844c8ef --- /dev/null +++ b/docs/api/XCEngine/Rendering/ObjectIdPass/ObjectIdPass.md @@ -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) diff --git a/docs/api/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass/BuildInfiniteGridParameters.md b/docs/api/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass/BuildInfiniteGridParameters.md new file mode 100644 index 00000000..40aabba6 --- /dev/null +++ b/docs/api/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass/BuildInfiniteGridParameters.md @@ -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) diff --git a/docs/api/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass/BuiltinInfiniteGridPass.md b/docs/api/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass/BuiltinInfiniteGridPass.md index ce52d484..b904dbb1 100644 --- a/docs/api/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass/BuiltinInfiniteGridPass.md +++ b/docs/api/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass/BuiltinInfiniteGridPass.md @@ -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) diff --git a/docs/api/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass/InfiniteGridParameters.md b/docs/api/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass/InfiniteGridParameters.md new file mode 100644 index 00000000..1b9e09e4 --- /dev/null +++ b/docs/api/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass/InfiniteGridParameters.md @@ -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) diff --git a/docs/api/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass/InfiniteGridPassData.md b/docs/api/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass/InfiniteGridPassData.md new file mode 100644 index 00000000..60687f00 --- /dev/null +++ b/docs/api/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass/InfiniteGridPassData.md @@ -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) diff --git a/docs/api/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass/Render.md b/docs/api/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass/Render.md new file mode 100644 index 00000000..eeabece4 --- /dev/null +++ b/docs/api/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass/Render.md @@ -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) diff --git a/docs/api/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass/Shutdown.md b/docs/api/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass/Shutdown.md new file mode 100644 index 00000000..3da5ae39 --- /dev/null +++ b/docs/api/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass/Shutdown.md @@ -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) diff --git a/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass/BuiltinObjectIdOutlinePass.md b/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass/BuiltinObjectIdOutlinePass.md index 71c23f79..a341dc91 100644 --- a/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass/BuiltinObjectIdOutlinePass.md +++ b/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass/BuiltinObjectIdOutlinePass.md @@ -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) diff --git a/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass/Destructor.md b/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass/Destructor.md index cbdf8adf..f713d548 100644 --- a/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass/Destructor.md +++ b/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass/Destructor.md @@ -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()` 会造成资源回收路径缺失。 ## 相关文档 diff --git a/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass/Render.md b/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass/Render.md index 6b75e3ca..0fa72d8a 100644 --- a/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass/Render.md +++ b/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass/Render.md @@ -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) diff --git a/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdPass/BuildInputLayout.md b/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdPass/BuildInputLayout.md new file mode 100644 index 00000000..fc404c63 --- /dev/null +++ b/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdPass/BuildInputLayout.md @@ -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) diff --git a/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdPass/BuiltinObjectIdPass.md b/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdPass/BuiltinObjectIdPass.md index 954d6322..51da696b 100644 --- a/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdPass/BuiltinObjectIdPass.md +++ b/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdPass/BuiltinObjectIdPass.md @@ -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) diff --git a/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdPass/Destructor.md b/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdPass/Destructor.md new file mode 100644 index 00000000..7f12ceab --- /dev/null +++ b/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdPass/Destructor.md @@ -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) diff --git a/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdPass/Render.md b/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdPass/Render.md new file mode 100644 index 00000000..ba48a39b --- /dev/null +++ b/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdPass/Render.md @@ -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) diff --git a/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdPass/Shutdown.md b/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdPass/Shutdown.md new file mode 100644 index 00000000..bb068d6d --- /dev/null +++ b/docs/api/XCEngine/Rendering/Passes/BuiltinObjectIdPass/Shutdown.md @@ -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) diff --git a/docs/api/XCEngine/Rendering/Passes/BuiltinPostProcessPassPlan/BuiltinPostProcessPassPlan.md b/docs/api/XCEngine/Rendering/Passes/BuiltinPostProcessPassPlan/BuiltinPostProcessPassPlan.md deleted file mode 100644 index 5a37e7b2..00000000 --- a/docs/api/XCEngine/Rendering/Passes/BuiltinPostProcessPassPlan/BuiltinPostProcessPassPlan.md +++ /dev/null @@ -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) diff --git a/docs/api/XCEngine/Rendering/Passes/BuiltinPostProcessPassSequenceBuilder/BuiltinPostProcessPassSequenceBuilder.md b/docs/api/XCEngine/Rendering/Passes/BuiltinPostProcessPassSequenceBuilder/BuiltinPostProcessPassSequenceBuilder.md deleted file mode 100644 index f5bbd01b..00000000 --- a/docs/api/XCEngine/Rendering/Passes/BuiltinPostProcessPassSequenceBuilder/BuiltinPostProcessPassSequenceBuilder.md +++ /dev/null @@ -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 还是 outline,builder 都会在前后插入颜色目标到 `RenderTarget` / `ShaderResource` 的状态切换 pass。 - -## 当前实现边界 - -- builder 只处理 builtin post-process,不处理主场景渲染、object-id 预通道或 overlay pass。 -- sequence 里的每一步当前都是 lambda render pass,没有独立类型名和更细的生命周期控制。 -- 如果 selection 请求缺少 object-id shader view,builder 只负责报告问题;最终是否退化成 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) diff --git a/docs/api/XCEngine/Rendering/Passes/ObjectIdOutlineStyle/ObjectIdOutlineStyle.md b/docs/api/XCEngine/Rendering/Passes/ObjectIdOutlineStyle/ObjectIdOutlineStyle.md index 56ca53f6..18520f9c 100644 --- a/docs/api/XCEngine/Rendering/Passes/ObjectIdOutlineStyle/ObjectIdOutlineStyle.md +++ b/docs/api/XCEngine/Rendering/Passes/ObjectIdOutlineStyle/ObjectIdOutlineStyle.md @@ -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) diff --git a/docs/api/XCEngine/Rendering/Passes/Passes.md b/docs/api/XCEngine/Rendering/Passes/Passes.md index 3cd109cc..a35a7d5f 100644 --- a/docs/api/XCEngine/Rendering/Passes/Passes.md +++ b/docs/api/XCEngine/Rendering/Passes/Passes.md @@ -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) diff --git a/docs/api/XCEngine/Rendering/RenderPipeline/Destructor.md b/docs/api/XCEngine/Rendering/RenderPipeline/Destructor.md index db8d1ff9..6a7b32f6 100644 --- a/docs/api/XCEngine/Rendering/RenderPipeline/Destructor.md +++ b/docs/api/XCEngine/Rendering/RenderPipeline/Destructor.md @@ -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` 独占持有。 +这也是为什么基类必须提供虚析构,而不是让调用方了解每个 concrete pipeline 的真实类型。 ## 相关文档 - [返回类型总览](RenderPipeline.md) +- [RenderPipeline::Shutdown](Shutdown.md) - [BuiltinForwardPipeline](../Pipelines/BuiltinForwardPipeline/BuiltinForwardPipeline.md) diff --git a/docs/api/XCEngine/Rendering/RenderPipeline/Initialize.md b/docs/api/XCEngine/Rendering/RenderPipeline/Initialize.md index 76a8a4a5..4fa580bd 100644 --- a/docs/api/XCEngine/Rendering/RenderPipeline/Initialize.md +++ b/docs/api/XCEngine/Rendering/RenderPipeline/Initialize.md @@ -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) diff --git a/docs/api/XCEngine/Rendering/RenderPipeline/Render.md b/docs/api/XCEngine/Rendering/RenderPipeline/Render.md index 0c932973..60ed5f15 100644 --- a/docs/api/XCEngine/Rendering/RenderPipeline/Render.md +++ b/docs/api/XCEngine/Rendering/RenderPipeline/Render.md @@ -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) diff --git a/docs/api/XCEngine/Rendering/RenderPipeline/RenderPipeline.md b/docs/api/XCEngine/Rendering/RenderPipeline/RenderPipeline.md index 1910f9ef..2b3aca57 100644 --- a/docs/api/XCEngine/Rendering/RenderPipeline/RenderPipeline.md +++ b/docs/api/XCEngine/Rendering/RenderPipeline/RenderPipeline.md @@ -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) diff --git a/docs/api/XCEngine/Rendering/RenderPipeline/Shutdown.md b/docs/api/XCEngine/Rendering/RenderPipeline/Shutdown.md index 9377f4d7..5ae2b786 100644 --- a/docs/api/XCEngine/Rendering/RenderPipeline/Shutdown.md +++ b/docs/api/XCEngine/Rendering/RenderPipeline/Shutdown.md @@ -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) diff --git a/docs/api/XCEngine/Rendering/SceneRenderRequestPlanner/SceneRenderRequestPlanner.md b/docs/api/XCEngine/Rendering/SceneRenderRequestPlanner/SceneRenderRequestPlanner.md index d85a821d..7817c7ee 100644 --- a/docs/api/XCEngine/Rendering/SceneRenderRequestPlanner/SceneRenderRequestPlanner.md +++ b/docs/api/XCEngine/Rendering/SceneRenderRequestPlanner/SceneRenderRequestPlanner.md @@ -51,7 +51,7 @@ ## 当前实现边界 -- 它只规划相机请求,不处理 object-id 或 builtin post-process 的具体填充;那部分通常由更上层调用方补充。 +- 它只规划相机请求,不处理 `objectId` 或各类 `RenderPassSequence` 的具体填充;那部分通常由更上层调用方补充。 - 当前没有 camera stacking 的更复杂依赖分析,只有 base / overlay 与 depth 的线性排序。 - `BuildRequests()` 只返回成功构建的请求,不单独报告被过滤掉的相机原因。 diff --git a/docs/api/XCEngine/Rendering/SceneRenderRequestUtils/SceneRenderRequestUtils.md b/docs/api/XCEngine/Rendering/SceneRenderRequestUtils/SceneRenderRequestUtils.md index 81fa7452..547019e9 100644 --- a/docs/api/XCEngine/Rendering/SceneRenderRequestUtils/SceneRenderRequestUtils.md +++ b/docs/api/XCEngine/Rendering/SceneRenderRequestUtils/SceneRenderRequestUtils.md @@ -79,7 +79,7 @@ - 这些 helper 是纯局部规则,不处理多表面、多窗口或跨帧状态。 - clear 推导只依赖当前计数,不关心更复杂的 camera stack 依赖关系。 -- request 组装阶段只填基础字段,object-id 与 builtin post-process 仍需上层补全。 +- request 组装阶段只填基础字段;`objectId`、`preScenePasses`、`postScenePasses` 与 `overlayPasses` 仍可由上层继续补全。 ## 相关文档 diff --git a/docs/api/XCEngine/Rendering/SceneRenderer/Destructor.md b/docs/api/XCEngine/Rendering/SceneRenderer/Destructor.md index 4d0c2a8c..02813e1c 100644 --- a/docs/api/XCEngine/Rendering/SceneRenderer/Destructor.md +++ b/docs/api/XCEngine/Rendering/SceneRenderer/Destructor.md @@ -8,7 +8,7 @@ `SceneRenderer` 自身没有自定义析构逻辑。真正的资源关闭发生在成员析构阶段: -- `m_cameraRenderer` 会在自身析构里关闭当前主管线、object-id pass 和 builtin post-process builder。 +- `m_cameraRenderer` 会在自身析构里关闭当前主管线和 object-id pass;request 级 `RenderPassSequence` 的生命周期则由每次 `Render()` 调用自行初始化和回收。 - `m_requestPlanner` 按普通值成员销毁。 因此当前析构语义是“把 teardown 责任完全交给内部 `CameraRenderer`”,而不是在 `SceneRenderer` 层重复写一套 shutdown 流程。 diff --git a/docs/api/XCEngine/Rendering/SceneRenderer/SceneRenderer.md b/docs/api/XCEngine/Rendering/SceneRenderer/SceneRenderer.md index 58d39d32..2851c447 100644 --- a/docs/api/XCEngine/Rendering/SceneRenderer/SceneRenderer.md +++ b/docs/api/XCEngine/Rendering/SceneRenderer/SceneRenderer.md @@ -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 或其它上层调用方在提交前写入。 - 对手工提交的请求数组,会再次按相机优先级稳定排序,而不是按调用方原始顺序盲目执行。 ## 测试覆盖 diff --git a/docs/api/XCEngine/XCEngine.md b/docs/api/XCEngine/XCEngine.md index 52bf8aaf..cd9bbe11 100644 --- a/docs/api/XCEngine/XCEngine.md +++ b/docs/api/XCEngine/XCEngine.md @@ -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) diff --git a/docs/api/_meta/rebuild-status.md b/docs/api/_meta/rebuild-status.md index fc654a38..256d877d 100644 --- a/docs/api/_meta/rebuild-status.md +++ b/docs/api/_meta/rebuild-status.md @@ -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` | diff --git a/docs/plan/API文档实时同步任务池_2026-04-03.md b/docs/plan/API文档实时同步任务池_2026-04-03.md new file mode 100644 index 00000000..da6c9209 --- /dev/null +++ b/docs/plan/API文档实时同步任务池_2026-04-03.md @@ -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` 作为持续性并行入口,用来承接你后续重构带来的新文档漂移。 diff --git a/docs/plan/C#脚本模块下一阶段计划.md b/docs/plan/C#脚本模块下一阶段计划.md index 2361ee5e..2dd6d746 100644 --- a/docs/plan/C#脚本模块下一阶段计划.md +++ b/docs/plan/C#脚本模块下一阶段计划.md @@ -259,7 +259,10 @@ C# 脚本模块已经完成了第一阶段的核心闭环,不再是“从 0 - 已新增运行时回归用例,验证层级查找、自身命中、组件销毁、GameObject 销毁。 - 已通过 `MonoScriptRuntimeTest.*` 与 `ProjectScriptAssemblyTest.*` 相关整组验证。 -- 下一步建议继续做第二批 Unity API 对齐: - 已完成第二步第一小项:`GetComponents()` -- 下一步候选:`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` diff --git a/engine/include/XCEngine/Components/GameObject.h b/engine/include/XCEngine/Components/GameObject.h index 3f9c5b6d..011ab417 100644 --- a/engine/include/XCEngine/Components/GameObject.h +++ b/engine/include/XCEngine/Components/GameObject.h @@ -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(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; diff --git a/engine/src/Components/GameObject.cpp b/engine/src/Components/GameObject.cpp index ad4ead47..ac4acc9e 100644 --- a/engine/src/Components/GameObject.cpp +++ b/engine/src/Components/GameObject.cpp @@ -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::FindGameObjectsWithTag(const std::string& t auto& registry = GetGlobalRegistry(); std::vector 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(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); diff --git a/engine/src/Scene/Scene.cpp b/engine/src/Scene/Scene.cpp index 149b682f..f9fe1c80 100644 --- a/engine/src/Scene/Scene.cpp +++ b/engine/src/Scene/Scene.cpp @@ -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 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(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(std::min(std::stoul(value), 31u)); } else if (key == "parent") { currentObject->parentId = static_cast(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()) { diff --git a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp index 7e88edc1..dd5d15bf 100644 --- a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp +++ b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp @@ -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(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(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(&InternalCall_Input_GetMouseScrollDelta)); mono_add_internal_call("XCEngine.InternalCalls::GameObject_GetName", reinterpret_cast(&InternalCall_GameObject_GetName)); mono_add_internal_call("XCEngine.InternalCalls::GameObject_SetName", reinterpret_cast(&InternalCall_GameObject_SetName)); + mono_add_internal_call("XCEngine.InternalCalls::GameObject_GetTag", reinterpret_cast(&InternalCall_GameObject_GetTag)); + mono_add_internal_call("XCEngine.InternalCalls::GameObject_SetTag", reinterpret_cast(&InternalCall_GameObject_SetTag)); + mono_add_internal_call("XCEngine.InternalCalls::GameObject_CompareTag", reinterpret_cast(&InternalCall_GameObject_CompareTag)); + mono_add_internal_call("XCEngine.InternalCalls::GameObject_GetLayer", reinterpret_cast(&InternalCall_GameObject_GetLayer)); + mono_add_internal_call("XCEngine.InternalCalls::GameObject_SetLayer", reinterpret_cast(&InternalCall_GameObject_SetLayer)); mono_add_internal_call("XCEngine.InternalCalls::GameObject_GetActiveSelf", reinterpret_cast(&InternalCall_GameObject_GetActiveSelf)); mono_add_internal_call("XCEngine.InternalCalls::GameObject_GetActiveInHierarchy", reinterpret_cast(&InternalCall_GameObject_GetActiveInHierarchy)); mono_add_internal_call("XCEngine.InternalCalls::GameObject_SetActive", reinterpret_cast(&InternalCall_GameObject_SetActive)); diff --git a/managed/CMakeLists.txt b/managed/CMakeLists.txt index da09bc02..2b0dda74 100644 --- a/managed/CMakeLists.txt +++ b/managed/CMakeLists.txt @@ -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 diff --git a/managed/GameScripts/TagLayerProbe.cs b/managed/GameScripts/TagLayerProbe.cs new file mode 100644 index 00000000..5921d480 --- /dev/null +++ b/managed/GameScripts/TagLayerProbe.cs @@ -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); + } + } +} diff --git a/managed/XCEngine.ScriptCore/Component.cs b/managed/XCEngine.ScriptCore/Component.cs index 970c951a..33314fbb 100644 --- a/managed/XCEngine.ScriptCore/Component.cs +++ b/managed/XCEngine.ScriptCore/Component.cs @@ -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() where T : Component { return GameObject.HasComponent(); @@ -60,6 +84,11 @@ namespace XCEngine return component != null; } + public bool CompareTag(string tag) + { + return GameObject.CompareTag(tag); + } + internal static T Create(ulong gameObjectUUID) where T : Component { return Create(typeof(T), gameObjectUUID) as T; diff --git a/managed/XCEngine.ScriptCore/GameObject.cs b/managed/XCEngine.ScriptCore/GameObject.cs index f2269a0d..cf432b1d 100644 --- a/managed/XCEngine.ScriptCore/GameObject.cs +++ b/managed/XCEngine.ScriptCore/GameObject.cs @@ -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(); public Transform transform => Transform; diff --git a/managed/XCEngine.ScriptCore/InternalCalls.cs b/managed/XCEngine.ScriptCore/InternalCalls.cs index b2456791..26d822bc 100644 --- a/managed/XCEngine.ScriptCore/InternalCalls.cs +++ b/managed/XCEngine.ScriptCore/InternalCalls.cs @@ -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); diff --git a/tests/Components/test_game_object.cpp b/tests/Components/test_game_object.cpp index b41ba02f..b868903e 100644 --- a/tests/Components/test_game_object.cpp +++ b/tests/Components/test_game_object.cpp @@ -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); } diff --git a/tests/scripting/test_mono_script_runtime.cpp b/tests/scripting/test_mono_script_runtime.cpp index ff51c6e1..28ff863a 100644 --- a/tests/scripting/test_mono_script_runtime.cpp +++ b/tests/scripting/test_mono_script_runtime.cpp @@ -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");