Formalize volume shader include context
This commit is contained in:
200
docs/plan/Renderer剩余收口与体积渲染多后端正式化计划_2026-04-10.md
Normal file
200
docs/plan/Renderer剩余收口与体积渲染多后端正式化计划_2026-04-10.md
Normal file
@@ -0,0 +1,200 @@
|
||||
# Renderer 剩余收口与体积渲染多后端正式化计划
|
||||
|
||||
日期:2026-04-10
|
||||
|
||||
## 1. 文档定位
|
||||
|
||||
旧的 `Renderer当前阶段正式收口计划_2026-04-09` 与 `NanoVDB稀疏体积渲染后续正式化计划_2026-04-09`
|
||||
已归档。
|
||||
|
||||
当前这份计划只覆盖 Rendering 主线里仍然没有真正收口的剩余问题,不再重复记录已经完成的阶段成果。
|
||||
|
||||
本轮目标有且只有四类:
|
||||
|
||||
1. 把 NanoVDB 体积渲染从 D3D12 单后端正式能力推进到 Vulkan / OpenGL 正式能力。
|
||||
2. 把 `FinalColor` 与 volume/final-output 的契约收死,消除“接口已存在但运行时半接线”的状态。
|
||||
3. 把 `ObjectId` 从“低 32 位截断可用方案”推进到清晰稳定的正式渲染 ID 契约。
|
||||
4. 把阴影规划与前向管线剩余的硬编码策略、monolith 结构继续拆干净。
|
||||
|
||||
---
|
||||
|
||||
## 2. 当前状态判断
|
||||
|
||||
当前 Rendering 已经具备稳定运行基础,但还不能称为“完全收口”,核心原因是:
|
||||
|
||||
1. `volume_scene / volume_occlusion_scene / volume_transform_scene` 目前仍只在 D3D12 上做了正式实例化与 GT 验证。
|
||||
2. `FinalColorSettings` 已经具备 pipeline/camera/volume 三层策略接口,但运行时只真正接入了 pipeline + camera。
|
||||
3. `ObjectId` 仍基于 runtime object id 低 32 位编码,不是长期正式方案。
|
||||
4. `SceneRenderRequestPlanner` 里仍保留方向光阴影尺寸与 focus 策略的硬编码。
|
||||
5. `BuiltinForwardPipeline` 功能上可用,但资源布局、PSO、descriptor、skybox、draw path 仍聚集在同一条大实现链里。
|
||||
|
||||
这五点不继续收掉,后面不管做 editor 深化还是做更高层 renderer / SRP,都会反复返工。
|
||||
|
||||
---
|
||||
|
||||
## 3. 本轮明确不做什么
|
||||
|
||||
1. 不引入 `RenderGraph`。
|
||||
2. 不提前启动 `SRP` 正式实现。
|
||||
3. 不改写现有 Shader/Material 语法主线。
|
||||
4. 不扩展新的大特性,例如 deferred、reflection probe、完整 post-processing stack。
|
||||
5. 不为了追求“结构完美”重写整个 builtin renderer。
|
||||
|
||||
本轮只做剩余收口,不做新能力扩张。
|
||||
|
||||
---
|
||||
|
||||
## 4. 执行阶段
|
||||
|
||||
### Phase 1:NanoVDB Vulkan 正式点亮
|
||||
|
||||
目标:
|
||||
|
||||
把当前 D3D12 已验证的体积渲染链路推进到 Vulkan 正式支持。
|
||||
|
||||
任务:
|
||||
|
||||
1. 审查 `BuiltinVolumetricPass` 在 Vulkan 下的 StructuredBuffer / storage buffer 绑定路径。
|
||||
2. 核对体积 shader 在 Vulkan 下的资源布局、反射、descriptor set 绑定。
|
||||
3. 让 `volume_scene`、`volume_occlusion_scene`、`volume_transform_scene` 在 Vulkan 下正式实例化并通过。
|
||||
4. 固化 Vulkan 失败日志与 capability 行为,禁止 silent fallback。
|
||||
|
||||
验收标准:
|
||||
|
||||
1. Vulkan 路线能稳定出图。
|
||||
2. 三个体积集成测试全部通过。
|
||||
3. 失败时日志能直接定位到 capability / shader / 绑定问题。
|
||||
|
||||
### Phase 2:NanoVDB OpenGL 正式点亮
|
||||
|
||||
目标:
|
||||
|
||||
把体积渲染推进到 OpenGL 正式支持,并确认 SSBO/GLSL 转译边界。
|
||||
|
||||
任务:
|
||||
|
||||
1. 审查 StructuredBuffer -> OpenGL SSBO 的运行时绑定链。
|
||||
2. 解决体积 shader 在 OpenGL 下的 layout / binding / 对齐问题。
|
||||
3. 让三组体积集成测试在 OpenGL 下正式实例化并通过。
|
||||
4. 明确 OpenGL 不支持场景下的禁用与报错策略。
|
||||
|
||||
验收标准:
|
||||
|
||||
1. OpenGL 路线能稳定出图。
|
||||
2. D3D12 / Vulkan / OpenGL 三后端都具备正式验证。
|
||||
3. 不再存在“体积渲染只是 D3D12 正式能力”的状态。
|
||||
|
||||
### Phase 3:FinalColor 契约彻底收死
|
||||
|
||||
目标:
|
||||
|
||||
让 `FinalColor` 的 API 契约、运行时行为、测试覆盖完全一致。
|
||||
|
||||
任务:
|
||||
|
||||
1. 审查 `FinalColorSettings`、camera overrides、volume overrides 的设计边界。
|
||||
2. 二选一处理 volume override:
|
||||
- 真正接入运行时解析链路;
|
||||
- 或明确从现阶段 API 中收掉,不再保留半接线接口。
|
||||
3. 补齐对应单测与场景回归测试。
|
||||
4. 明确 post-process / final-output / volume 的职责关系文档。
|
||||
|
||||
验收标准:
|
||||
|
||||
1. 不再存在“接口支持但执行层没接”的不自洽状态。
|
||||
2. `FinalColor` 行为能用测试稳定验证。
|
||||
|
||||
### Phase 4:ObjectId 正式渲染 ID 契约
|
||||
|
||||
目标:
|
||||
|
||||
把 picking / outline / selection 依赖的 ID 方案从阶段实现推进到正式契约。
|
||||
|
||||
任务:
|
||||
|
||||
1. 明确长期方案是稳定 32-bit render id,还是 runtime mapping table。
|
||||
2. 将 `ObjectIdCodec` 与 `GameObject::ID` 的关系从“低 32 位截断”调整为正式约束。
|
||||
3. 让 picking / outline / editor selection 共用同一份正式契约。
|
||||
4. 补齐边界测试与错误防护。
|
||||
|
||||
验收标准:
|
||||
|
||||
1. `ObjectId` 不再依赖模糊约定。
|
||||
2. 相关 editor 能力建立在稳定渲染 ID 契约上。
|
||||
|
||||
### Phase 5:阴影规划与前向管线剩余结构收口
|
||||
|
||||
目标:
|
||||
|
||||
继续把 planner / pipeline 里剩余的硬编码和 monolith 结构压缩到合理边界。
|
||||
|
||||
任务:
|
||||
|
||||
1. 把方向光阴影尺寸、focus/depth/padding 策略抽成正式配置点。
|
||||
2. 收紧 `SceneRenderRequestPlanner` 的职责,避免继续膨胀。
|
||||
3. 拆分 `BuiltinForwardPipeline` 剩余的大块职责:
|
||||
- pipeline state resolve
|
||||
- descriptor/layout cache
|
||||
- skybox path
|
||||
- draw submission
|
||||
4. 保持功能不回退的前提下优化代码结构与目录边界。
|
||||
|
||||
验收标准:
|
||||
|
||||
1. 阴影规划不再写死关键策略参数。
|
||||
2. `BuiltinForwardPipeline` 不再继续向单体类膨胀。
|
||||
|
||||
### Phase 6:测试、文档、归档
|
||||
|
||||
目标:
|
||||
|
||||
让本轮真正可交接、可回归、可归档。
|
||||
|
||||
任务:
|
||||
|
||||
1. 复跑关键 Rendering unit / integration。
|
||||
2. 输出阶段总结。
|
||||
3. 将本轮完成计划归档到 `docs/used`。
|
||||
|
||||
验收标准:
|
||||
|
||||
1. 关键测试全绿。
|
||||
2. 活跃 plan 只保留真正未完成事项。
|
||||
|
||||
---
|
||||
|
||||
## 5. 执行顺序
|
||||
|
||||
严格按下面顺序推进:
|
||||
|
||||
1. `Phase 1`
|
||||
2. `Phase 2`
|
||||
3. `Phase 3`
|
||||
4. `Phase 4`
|
||||
5. `Phase 5`
|
||||
6. `Phase 6`
|
||||
|
||||
原因:
|
||||
|
||||
1. 体积渲染多后端 rollout 还没完成,当前还不能宣称 renderer 全面正式支持。
|
||||
2. `FinalColor` 与 `ObjectId` 都是契约问题,必须在继续推进更高层系统前收死。
|
||||
3. planner / pipeline 结构整理必须建立在前面能力边界稳定之后。
|
||||
|
||||
---
|
||||
|
||||
## 6. 本轮完成标志
|
||||
|
||||
当下面条件同时成立时,本轮才算真正完成:
|
||||
|
||||
1. 三个体积场景已在 D3D12 / Vulkan / OpenGL 三后端正式验证。
|
||||
2. `FinalColor` 契约与执行层完全一致。
|
||||
3. `ObjectId` 已切换到正式渲染 ID 契约。
|
||||
4. 阴影规划关键策略点已正式化。
|
||||
5. `BuiltinForwardPipeline` 剩余的结构性膨胀得到明显收口。
|
||||
6. 文档与测试全部同步完成。
|
||||
|
||||
---
|
||||
|
||||
## 7. 一句话结论
|
||||
|
||||
当前 Rendering 不是“功能没做完”,而是“剩余收口项已经缩小到多后端体积渲染、运行时契约、自身结构整理三类问题”;本轮做完之后,才可以严肃地说它进入了真正可长期维护的正式状态。
|
||||
159
docs/used/NanoVDB稀疏体积渲染后续正式化计划_阶段归档_2026-04-10.md
Normal file
159
docs/used/NanoVDB稀疏体积渲染后续正式化计划_阶段归档_2026-04-10.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# NanoVDB 稀疏体积渲染后续正式化计划
|
||||
|
||||
日期:2026-04-09
|
||||
|
||||
## 1. 文档定位
|
||||
|
||||
旧的 `NanoVDB` 大计划已经归档为第一阶段完成文档。
|
||||
|
||||
当前这份计划只保留 NanoVDB 体积渲染在第一阶段落地之后,真正还没有完全收口的少数事项。
|
||||
|
||||
这意味着:
|
||||
|
||||
1. `.nvdb -> artifact -> VolumeField -> VolumeRendererComponent -> BuiltinVolumetricPass -> Editor 基本接入` 已经视为第一阶段完成。
|
||||
2. 当前不再讨论那一整套已经落地的基础链路。
|
||||
3. 当前只推进仍然明确未完成、且会影响后续长期演进的后续正式化工作。
|
||||
|
||||
---
|
||||
|
||||
## 2. 第一阶段已完成范围
|
||||
|
||||
以下内容已经不再作为活跃计划项:
|
||||
|
||||
1. `.nvdb` 资源导入与 artifact 化。
|
||||
2. `VolumeField` 运行时资源模型。
|
||||
3. `VolumeRendererComponent` 组件接入。
|
||||
4. `RenderSceneExtractor` 对体积对象的提取。
|
||||
5. `BuiltinVolumetricPass` 在内建前向链路中的正式接入。
|
||||
6. D3D12 正式链路点亮。
|
||||
7. D3D12 体积渲染集成测试与 GT 对比。
|
||||
8. Editor 中最小可用的组件面板接入。
|
||||
|
||||
这些内容已经不该继续挂在 `docs/plan` 里作为未完成主线。
|
||||
|
||||
---
|
||||
|
||||
## 3. 当前仍然活跃的真实问题
|
||||
|
||||
现在剩下的 NanoVDB 后续工作,核心只有两类。
|
||||
|
||||
### 3.1 多后端尚未完成正式 rollout
|
||||
|
||||
当前体积渲染集成测试仍然只在 D3D12 路径上正式实例化与验证。
|
||||
|
||||
这意味着下面这些工作还没完成:
|
||||
|
||||
1. Vulkan 的正式点亮与稳定验证。
|
||||
2. OpenGL 的正式点亮与稳定验证。
|
||||
3. 三后端统一 GT 基线与回归验证。
|
||||
4. capability 检测、禁用行为、错误信息是否完整。
|
||||
|
||||
### 3.2 与未来高层系统的契约还没有最终落地
|
||||
|
||||
当前底层 buffer 资源能力已经具备,但和未来高层系统的最终契约还没有必要现在就完全做死。
|
||||
|
||||
真正还需要收口的是:
|
||||
|
||||
1. 明确 NanoVDB 路线如何映射到未来 Unity 风格 `SetBuffer` 使用面。
|
||||
2. 明确当 C# / SRP 正式接入时,体积渲染不会反向逼迫底层重写。
|
||||
3. 把当前已经存在的运行时绑定约束整理成正式文档边界,而不是继续散落在实现里。
|
||||
|
||||
注意:
|
||||
|
||||
这不意味着现在要实现完整 C# 绑定层。
|
||||
本阶段只需要把边界冻结清楚。
|
||||
|
||||
---
|
||||
|
||||
## 4. 本轮明确不做什么
|
||||
|
||||
1. 不重做第一阶段已经落地的 D3D12 体积渲染主链路。
|
||||
2. 不扩展新的体积特性,例如全局体积雾、体积阴影系统、froxel fog。
|
||||
3. 不引入 `Texture3D` 替代 NanoVDB 正式路线。
|
||||
4. 不提前实现完整 C# Volume API。
|
||||
5. 不把体积渲染主线改造成依赖 `SRP` 才能继续推进。
|
||||
|
||||
---
|
||||
|
||||
## 5. 执行阶段
|
||||
|
||||
### Phase 1:Vulkan 路线正式点亮
|
||||
|
||||
目标:
|
||||
|
||||
把当前已经在 D3D12 上验证过的 NanoVDB 正式链路,推进到 Vulkan。
|
||||
|
||||
任务:
|
||||
|
||||
1. 审查 StructuredBuffer / storage buffer 绑定路径在 Vulkan 下的编译与运行时绑定。
|
||||
2. 补齐体积渲染 shader 在 Vulkan 路线上的资源布局与反射验证。
|
||||
3. 让 `volume_scene`、`volume_occlusion_scene`、`volume_transform_scene` 至少能在 Vulkan 路线上正式跑通。
|
||||
4. 固化 Vulkan 路径的 GT 对比与失败日志。
|
||||
|
||||
验收标准:
|
||||
|
||||
1. Vulkan 路线上能稳定出图。
|
||||
2. 集成测试能稳定通过。
|
||||
3. 不是“偶然能跑”,而是正式进入体积渲染支持集合。
|
||||
|
||||
### Phase 2:OpenGL 路线正式点亮
|
||||
|
||||
目标:
|
||||
|
||||
把 NanoVDB 路线推进到 OpenGL,并明确 SSBO/GLSL 转译的正式边界。
|
||||
|
||||
任务:
|
||||
|
||||
1. 审查当前 StructuredBuffer 到 OpenGL/SSBO 的转译链路。
|
||||
2. 解决体积 shader 在 OpenGL 路线上可能出现的 layout、binding、对齐问题。
|
||||
3. 让三组体积集成测试至少具备 OpenGL 正式验证能力。
|
||||
4. 明确 OpenGL 不支持场景下的禁用与报错策略,禁止 silent fallback。
|
||||
|
||||
验收标准:
|
||||
|
||||
1. OpenGL 路线能稳定出图。
|
||||
2. 三后端测试输出都可统一对同一 GT 做比较。
|
||||
3. 不再存在“D3D12 独有体积功能”的阶段性状态。
|
||||
|
||||
### Phase 3:多后端能力收口与文档冻结
|
||||
|
||||
目标:
|
||||
|
||||
在 D3D12 / Vulkan / OpenGL 三后端都跑通之后,把 NanoVDB 这条能力从“已点亮”收口成“正式能力”。
|
||||
|
||||
任务:
|
||||
|
||||
1. 明确三后端 capability 表达方式。
|
||||
2. 明确资源绑定失败、shader 编译失败、后端不支持时的统一错误行为。
|
||||
3. 冻结未来高层 `SetBuffer` 风格接口与当前底层资源绑定的对应关系。
|
||||
4. 更新文档并把这份后续计划归档。
|
||||
|
||||
验收标准:
|
||||
|
||||
1. 三后端行为边界清晰。
|
||||
2. 错误路径明确,不靠试错排查。
|
||||
3. 文档和代码边界一致。
|
||||
|
||||
---
|
||||
|
||||
## 6. 当前阶段完成标志
|
||||
|
||||
当下面条件同时成立时,这份后续计划才算真正完成:
|
||||
|
||||
1. `volume_scene`
|
||||
2. `volume_occlusion_scene`
|
||||
3. `volume_transform_scene`
|
||||
|
||||
上述三组集成测试已经在:
|
||||
|
||||
1. D3D12
|
||||
2. Vulkan
|
||||
3. OpenGL
|
||||
|
||||
三后端上完成正式验证,且对应错误处理、能力边界、文档说明都已经收口。
|
||||
|
||||
---
|
||||
|
||||
## 7. 一句话结论
|
||||
|
||||
NanoVDB 不是“还没做完”,而是“第一阶段已经完成,剩下的是多后端 rollout 与最终正式化边界”;从现在开始,这条主线不再需要一份大而全计划,只需要一份小而硬的后续收口计划。
|
||||
228
docs/used/Renderer当前阶段正式收口计划_阶段归档_2026-04-10.md
Normal file
228
docs/used/Renderer当前阶段正式收口计划_阶段归档_2026-04-10.md
Normal file
@@ -0,0 +1,228 @@
|
||||
# Renderer 当前阶段正式收口计划
|
||||
|
||||
日期:2026-04-09
|
||||
|
||||
## 1. 文档定位
|
||||
|
||||
这份计划只处理当前 `Rendering` 主线里“已经能跑,但还没有完全正式化”的问题。
|
||||
|
||||
它不是下一阶段功能规划,也不是 `SRP / RenderGraph / 后处理体系扩展` 计划。
|
||||
本轮目标很明确:
|
||||
|
||||
1. 把当前内建渲染链路收口到可长期维护的正式状态。
|
||||
2. 清掉仍然明显带有阶段性、硬编码、单后端假设的实现。
|
||||
3. 在不破坏现有 Editor/Game/Integration Test 行为的前提下,把当前渲染层的边界整理干净。
|
||||
|
||||
---
|
||||
|
||||
## 2. 当前阶段判断
|
||||
|
||||
当前 `Rendering` 已经完成了这些关键闭环:
|
||||
|
||||
1. 内建前向主链路已经能稳定驱动 `Scene / Game` 视图。
|
||||
2. 多光源、方向光阴影、天空盒、体积渲染都已经进入正式运行链路。
|
||||
3. Shader/Material 资产链路已经从旧散装方案收束到当前统一体系。
|
||||
4. Picking、Outline、Grid、Gizmo 等编辑器侧能力已经具备基本可用性。
|
||||
5. 集成测试体系已经覆盖了一批真实渲染场景,并能做 GT 对比。
|
||||
|
||||
但现在还剩下一批很典型的“阶段尾巴”:
|
||||
|
||||
1. 前向管线对颜色目标格式、深度格式、PSO 组合键仍有硬编码假设。
|
||||
2. Editor 相关 pass 仍夹带单后端假设和格式写死问题。
|
||||
3. 阴影规划层仍偏固定策略,缺少正式配置入口和更清晰的职责边界。
|
||||
4. ObjectId 方案仍是阶段版实现,和 `GameObject::ID` 的长期契约没有彻底收束。
|
||||
5. `FinalColor` 与体积渲染之间还有“接口预留已存在,但运行时接线未完全正式化”的问题。
|
||||
|
||||
这些问题现在不处理,后面做 `SRP`、Renderer 扩展、Editor 深化时会持续反噬。
|
||||
|
||||
---
|
||||
|
||||
## 3. 本轮明确不做什么
|
||||
|
||||
为了避免计划失控,本轮明确不做下面这些事:
|
||||
|
||||
1. 不引入 `RenderGraph`。
|
||||
2. 不启动 `SRP` 正式实现。
|
||||
3. 不重写现有多 pass 框架。
|
||||
4. 不扩展新的大特性,例如延迟渲染、反射探针、后处理栈。
|
||||
5. 不再重开一轮 Shader/Material 语法设计。
|
||||
|
||||
本轮只做“把当前链路做正式”。
|
||||
|
||||
---
|
||||
|
||||
## 4. 收口目标
|
||||
|
||||
本轮收口完成后,当前 `Renderer` 至少应满足下面这些条件:
|
||||
|
||||
1. 同一套内建前向链路不再偷偷依赖固定颜色格式或固定深度格式。
|
||||
2. PSO 缓存键和 pass 描述能完整反映真正影响渲染状态的维度。
|
||||
3. Editor overlay 类 pass 不再用 D3D12 风格思维写死输入输出资源契约。
|
||||
4. 阴影规划和 ObjectId 编码拥有清晰、稳定、可扩展的正式边界。
|
||||
5. `FinalColor`、体积渲染、场景颜色输出之间的职责关系明确且可测试。
|
||||
6. 对应单测与集成测试补齐,保证本轮收口不会把现有功能打坏。
|
||||
|
||||
---
|
||||
|
||||
## 5. 分阶段执行
|
||||
|
||||
### Phase 1:前向管线资源契约正式化
|
||||
|
||||
目标:
|
||||
|
||||
把当前 `BuiltinForwardPipeline` 从“能用”整理成“目标格式、深度格式、状态组合都明确受描述驱动”。
|
||||
|
||||
要做的事:
|
||||
|
||||
1. 审查并收口前向主链路里所有颜色目标格式、深度格式、MSAA、load/store、viewport/scissor 的隐式假设。
|
||||
2. 补全 PSO cache key,让真正影响管线状态的维度全部进入键值,而不是靠调用点默认一致。
|
||||
3. 把 render target/depth format 的来源统一到正式 request/context,而不是散落在 pass 内部硬编码。
|
||||
4. 检查 `Scene / Game / Editor overlay` 是否共用了不该共用的隐式默认值,逐一拆开。
|
||||
|
||||
优先关注:
|
||||
|
||||
1. `engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp`
|
||||
2. `engine/src/Rendering/Pipelines/BuiltinForwardPipelineResources.cpp`
|
||||
3. 与 camera request / pass context / pipeline key 相关的描述结构
|
||||
|
||||
验收标准:
|
||||
|
||||
1. 同一前向主链路可在不同颜色格式/深度格式输入下保持行为正确。
|
||||
2. 不再存在“只因为默认值碰巧一致所以能跑”的路径。
|
||||
3. 相关单测补齐并通过。
|
||||
|
||||
### Phase 2:Editor Overlay Pass 正式化
|
||||
|
||||
目标:
|
||||
|
||||
把 `Grid / Outline / ObjectId` 这批 Editor 侧 pass 从阶段性实现整理成正式 pass。
|
||||
|
||||
要做的事:
|
||||
|
||||
1. 审查 `BuiltinInfiniteGridPass`、`BuiltinSelectionOutlinePass`、`BuiltinObjectIdOutlinePass` 对资源格式和后端行为的假设。
|
||||
2. 把这些 pass 对 scene color/depth/object-id/mask 的输入输出契约整理成统一描述,而不是各自单独偷拿默认格式。
|
||||
3. 去掉明显的 D3D12-only 思维残留,至少保证代码层面不再把后端差异写死在 pass 逻辑里。
|
||||
4. 统一调试路径和正式路径,避免同一个 pass 同时保留多套阶段性资源接法。
|
||||
|
||||
优先关注:
|
||||
|
||||
1. `engine/src/Rendering/Passes/BuiltinInfiniteGridPass.cpp`
|
||||
2. `engine/src/Rendering/Passes/BuiltinSelectionOutlinePass.cpp`
|
||||
3. `engine/src/Rendering/Passes/BuiltinObjectIdOutlinePass.cpp`
|
||||
4. Editor 侧 Scene/Game viewport render target 管理层
|
||||
|
||||
验收标准:
|
||||
|
||||
1. Grid、Outline、Picking 对资源输入输出的依赖关系清晰可读。
|
||||
2. Editor 相关 pass 不再依赖单后端硬编码格式。
|
||||
3. 现有 Scene/Game 视图行为不回退。
|
||||
|
||||
### Phase 3:Shadow Planning 与 ObjectId 正式边界收口
|
||||
|
||||
目标:
|
||||
|
||||
把当前“能工作但还是阶段实现”的阴影规划与 ObjectId 方案整理为正式边界。
|
||||
|
||||
要做的事:
|
||||
|
||||
1. 审查方向光阴影规划层,把固定 `1024` 阴影图尺寸、全场景粗扫描等阶段策略抽成正式配置/策略点。
|
||||
2. 明确 shadow request/planning/execution 的职责边界,避免 planner 继续膨胀成杂物层。
|
||||
3. 审查 `ObjectIdCodec` 与 `GameObject::ID` 关系。
|
||||
4. 明确长期方案到底是稳定 32-bit 渲染 ID,还是正式的 runtime 映射表,不再让 64-bit 对象 ID 与 32-bit object-id target 处于半耦合状态。
|
||||
5. 为 picking、outline、editor selection 使用同一份正式 ID 契约。
|
||||
|
||||
优先关注:
|
||||
|
||||
1. `engine/src/Rendering/Planning/SceneRenderRequestPlanner.cpp`
|
||||
2. `engine/include/XCEngine/Rendering/Picking/ObjectIdCodec.h`
|
||||
3. `engine/include/XCEngine/Components/GameObject.h`
|
||||
|
||||
验收标准:
|
||||
|
||||
1. 阴影规划不再带有明显的固定阶段参数硬编码。
|
||||
2. ObjectId 编码边界清晰且有测试覆盖。
|
||||
3. Picking/Outline 不再建立在模糊 ID 约定之上。
|
||||
|
||||
### Phase 4:FinalColor 与体积渲染接线正式化
|
||||
|
||||
目标:
|
||||
|
||||
把当前 `FinalColor`、场景颜色输出、体积渲染之间的关系做实,避免继续停留在“API 预留了,但接线没完全正式化”的状态。
|
||||
|
||||
要做的事:
|
||||
|
||||
1. 复查 `FinalColorSettings`、camera request、scene renderer 执行链路中的实际生效路径。
|
||||
2. 确认 volume override / final color override 的职责边界,是正式运行时能力还是仅作为未来扩展保留。
|
||||
3. 把现在已经真实存在的行为和仍只是预留的行为区分清楚。
|
||||
4. 如果保留预留接口,就必须让默认行为、空行为、调试行为全部明确;如果决定正式启用,就把运行时接线补齐。
|
||||
|
||||
优先关注:
|
||||
|
||||
1. `engine/include/XCEngine/Rendering/Planning/FinalColorSettings.h`
|
||||
2. `engine/src/Rendering/Execution/SceneRenderer.cpp`
|
||||
3. 与 camera scene renderer / frame composition 相关的执行层
|
||||
|
||||
验收标准:
|
||||
|
||||
1. `FinalColor` 不再停留在“名字像正式能力,实际只是半接线状态”。
|
||||
2. 体积渲染与最终颜色输出关系清晰、行为稳定。
|
||||
3. 对应单测或场景回归测试补齐。
|
||||
|
||||
### Phase 5:测试、文档、归档收口
|
||||
|
||||
目标:
|
||||
|
||||
让这一轮不是“代码上看起来做完”,而是真正可交接、可回归、可归档。
|
||||
|
||||
要做的事:
|
||||
|
||||
1. 为本轮所有正式化改动补对应 unit/integration coverage。
|
||||
2. 至少复跑当前关键 Rendering 集成场景:
|
||||
- 基础 forward 场景
|
||||
- directional shadow 场景
|
||||
- skybox 场景
|
||||
- volume 场景
|
||||
3. 更新必要文档,把本轮完成的阶段计划归档到 `docs/used`。
|
||||
4. 输出一份阶段总结,明确当前 Renderer 已正式化到什么程度,下一阶段才该进入什么主题。
|
||||
|
||||
验收标准:
|
||||
|
||||
1. 关键测试全绿。
|
||||
2. 没有新增“只在某个后端偶然能过”的脆弱实现。
|
||||
3. 本轮计划可以被完整归档。
|
||||
|
||||
---
|
||||
|
||||
## 6. 执行顺序要求
|
||||
|
||||
必须按下面顺序推进:
|
||||
|
||||
1. 先做 `Phase 1`
|
||||
2. 再做 `Phase 2`
|
||||
3. 然后做 `Phase 3`
|
||||
4. 再处理 `Phase 4`
|
||||
5. 最后统一做 `Phase 5` 收口
|
||||
|
||||
原因很简单:
|
||||
|
||||
1. 前向主链路资源契约不稳,后面的 Editor pass 正式化会一直返工。
|
||||
2. ObjectId 和 shadow planning 的正式边界不先理顺,Editor 相关能力会持续建立在临时假设上。
|
||||
3. `FinalColor` 的运行时接线要建立在前面这些边界已经收紧之后。
|
||||
|
||||
---
|
||||
|
||||
## 7. 本轮完成标志
|
||||
|
||||
当下面条件同时成立时,这一轮 Renderer 收口才算真正完成:
|
||||
|
||||
1. 当前内建前向链路已摆脱明显格式硬编码和状态隐式约定。
|
||||
2. Editor overlay 类 pass 已完成正式资源契约整理。
|
||||
3. Shadow planning 与 ObjectId 契约已从阶段版实现收口为正式边界。
|
||||
4. `FinalColor` 与体积渲染关系已经明确并落地到代码与测试。
|
||||
5. 所有本轮阶段性计划文档已经归档,`docs/plan` 中不再残留已经完成的旧专项计划。
|
||||
|
||||
---
|
||||
|
||||
## 8. 一句话结论
|
||||
|
||||
当前 Renderer 已经具备“继续承接更高层系统”的能力,但在进入下一阶段之前,必须先把资源契约、Editor pass、阴影规划、ObjectId、FinalColor 这五块彻底做正式;这轮收口做完,后面再接 `SRP` 或更复杂渲染能力才不会反复返工。
|
||||
@@ -49,6 +49,7 @@ enum class ShaderLanguage : uint8_t;
|
||||
|
||||
struct ShaderCompileDesc {
|
||||
std::wstring fileName;
|
||||
std::vector<std::wstring> includeDirectories;
|
||||
std::vector<uint8_t> source;
|
||||
ShaderLanguage sourceLanguage = ShaderLanguage::Unknown;
|
||||
std::wstring entryPoint;
|
||||
@@ -323,6 +324,7 @@ struct GraphicsPipelineDesc {
|
||||
uint32_t renderTargetFormats[8] = { 0 }; // Format
|
||||
uint32_t depthStencilFormat = 0; // Format
|
||||
uint32_t sampleCount = 1;
|
||||
uint32_t sampleQuality = 0;
|
||||
};
|
||||
|
||||
struct RHIDeviceDesc {
|
||||
|
||||
@@ -603,10 +603,89 @@ std::string InjectMacrosIntoSource(const std::string& source, const std::vector<
|
||||
return macroBlock + source;
|
||||
}
|
||||
|
||||
std::filesystem::path MakeAbsoluteNormalizedPath(const std::filesystem::path& path) {
|
||||
if (path.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
const std::filesystem::path absolutePath = std::filesystem::absolute(path, ec);
|
||||
if (ec) {
|
||||
return path.lexically_normal();
|
||||
}
|
||||
|
||||
return absolutePath.lexically_normal();
|
||||
}
|
||||
|
||||
std::string NormalizeDirectoryKey(const std::filesystem::path& path) {
|
||||
std::string key = path.generic_string();
|
||||
std::transform(key.begin(), key.end(), key.begin(), [](unsigned char ch) {
|
||||
return static_cast<char>(std::tolower(ch));
|
||||
});
|
||||
return key;
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> CollectShaderIncludeDirectories(const ShaderCompileDesc& desc) {
|
||||
std::vector<std::filesystem::path> directories;
|
||||
directories.reserve(desc.includeDirectories.size() + (desc.fileName.empty() ? 0u : 1u));
|
||||
|
||||
auto appendDirectory = [&](const std::filesystem::path& candidate) {
|
||||
if (candidate.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::filesystem::path normalized = MakeAbsoluteNormalizedPath(candidate);
|
||||
if (normalized.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string key = NormalizeDirectoryKey(normalized);
|
||||
const auto existing = std::find_if(directories.begin(), directories.end(), [&](const std::filesystem::path& path) {
|
||||
return NormalizeDirectoryKey(path) == key;
|
||||
});
|
||||
if (existing == directories.end()) {
|
||||
directories.push_back(normalized);
|
||||
}
|
||||
};
|
||||
|
||||
if (!desc.fileName.empty()) {
|
||||
const std::filesystem::path sourcePath(desc.fileName);
|
||||
if (sourcePath.has_parent_path()) {
|
||||
appendDirectory(sourcePath.parent_path());
|
||||
}
|
||||
}
|
||||
|
||||
for (const std::wstring& includeDirectory : desc.includeDirectories) {
|
||||
appendDirectory(std::filesystem::path(includeDirectory));
|
||||
}
|
||||
|
||||
return directories;
|
||||
}
|
||||
|
||||
std::wstring BuildIncludeDirectoryArguments(const ShaderCompileDesc& desc) {
|
||||
std::wstring arguments;
|
||||
const std::vector<std::filesystem::path> includeDirectories = CollectShaderIncludeDirectories(desc);
|
||||
for (const std::filesystem::path& includeDirectory : includeDirectories) {
|
||||
arguments += L" -I \"" + includeDirectory.wstring() + L"\"";
|
||||
}
|
||||
|
||||
return arguments;
|
||||
}
|
||||
|
||||
std::wstring ResolveCompilerWorkingDirectory(const ShaderCompileDesc& desc) {
|
||||
const std::vector<std::filesystem::path> includeDirectories = CollectShaderIncludeDirectories(desc);
|
||||
if (!includeDirectories.empty()) {
|
||||
return includeDirectories.front().wstring();
|
||||
}
|
||||
|
||||
return std::wstring();
|
||||
}
|
||||
|
||||
bool RunProcessAndCapture(const std::wstring& executablePath,
|
||||
const std::wstring& arguments,
|
||||
DWORD& exitCode,
|
||||
std::string& output) {
|
||||
std::string& output,
|
||||
const wchar_t* workingDirectory = nullptr) {
|
||||
SECURITY_ATTRIBUTES securityAttributes = {};
|
||||
securityAttributes.nLength = sizeof(securityAttributes);
|
||||
securityAttributes.bInheritHandle = TRUE;
|
||||
@@ -638,7 +717,7 @@ bool RunProcessAndCapture(const std::wstring& executablePath,
|
||||
TRUE,
|
||||
CREATE_NO_WINDOW,
|
||||
nullptr,
|
||||
nullptr,
|
||||
workingDirectory,
|
||||
&startupInfo,
|
||||
&processInfo);
|
||||
|
||||
@@ -701,14 +780,22 @@ bool CompileGlslToSpirv(const ShaderCompileDesc& desc,
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::wstring includeArguments = BuildIncludeDirectoryArguments(desc);
|
||||
const std::wstring workingDirectory = ResolveCompilerWorkingDirectory(desc);
|
||||
const std::wstring arguments =
|
||||
SpirvTargetArguments(targetEnvironment) + L" -S " + ShaderStageArgument(type) +
|
||||
L" -e " + WidenAscii(entryPoint.c_str()) +
|
||||
includeArguments +
|
||||
L" -o \"" + tempOutputPath + L"\" \"" + tempSourcePath + L"\"";
|
||||
|
||||
DWORD exitCode = 0;
|
||||
std::string compilerOutput;
|
||||
const bool ranProcess = RunProcessAndCapture(validatorPath, arguments, exitCode, compilerOutput);
|
||||
const bool ranProcess = RunProcessAndCapture(
|
||||
validatorPath,
|
||||
arguments,
|
||||
exitCode,
|
||||
compilerOutput,
|
||||
workingDirectory.empty() ? nullptr : workingDirectory.c_str());
|
||||
|
||||
std::vector<uint8_t> bytes;
|
||||
const bool loadedOutput = ranProcess && exitCode == 0 && LoadBinaryFile(tempOutputPath, bytes);
|
||||
@@ -786,16 +873,24 @@ bool CompileHlslToSpirv(const ShaderCompileDesc& desc,
|
||||
const std::string profile = NarrowAscii(desc.profile);
|
||||
const std::wstring targetProfile =
|
||||
WidenAscii(profile.empty() ? "ps_6_0" : profile.c_str());
|
||||
const std::wstring includeArguments = BuildIncludeDirectoryArguments(desc);
|
||||
const std::wstring workingDirectory = ResolveCompilerWorkingDirectory(desc);
|
||||
|
||||
const std::wstring arguments =
|
||||
L"-spirv -Zpc -fvk-use-dx-layout -fspv-target-env=vulkan1.0 " +
|
||||
std::wstring(L"-T ") + targetProfile +
|
||||
L" -E " + WidenAscii(entryPoint.c_str()) +
|
||||
includeArguments +
|
||||
L" -Fo \"" + tempOutputPath + L"\" \"" + tempSourcePath + L"\"";
|
||||
|
||||
DWORD exitCode = 0;
|
||||
std::string compilerOutput;
|
||||
const bool ranProcess = RunProcessAndCapture(dxcPath, arguments, exitCode, compilerOutput);
|
||||
const bool ranProcess = RunProcessAndCapture(
|
||||
dxcPath,
|
||||
arguments,
|
||||
exitCode,
|
||||
compilerOutput,
|
||||
workingDirectory.empty() ? nullptr : workingDirectory.c_str());
|
||||
|
||||
std::vector<uint8_t> bytes;
|
||||
const bool loadedOutput = ranProcess && exitCode == 0 && LoadBinaryFile(tempOutputPath, bytes);
|
||||
@@ -861,15 +956,23 @@ bool CompileHlslToSpirv(const ShaderCompileDesc& desc,
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::wstring includeArguments = BuildIncludeDirectoryArguments(desc);
|
||||
const std::wstring workingDirectory = ResolveCompilerWorkingDirectory(desc);
|
||||
const std::wstring arguments =
|
||||
L"-D --auto-map-bindings " + SpirvTargetArguments(targetEnvironment) +
|
||||
L" -S " + ShaderStageArgument(type) +
|
||||
L" -e " + WidenAscii(entryPoint.c_str()) +
|
||||
includeArguments +
|
||||
L" -o \"" + tempOutputPath + L"\" \"" + tempSourcePath + L"\"";
|
||||
|
||||
DWORD exitCode = 0;
|
||||
std::string compilerOutput;
|
||||
const bool ranProcess = RunProcessAndCapture(validatorPath, arguments, exitCode, compilerOutput);
|
||||
const bool ranProcess = RunProcessAndCapture(
|
||||
validatorPath,
|
||||
arguments,
|
||||
exitCode,
|
||||
compilerOutput,
|
||||
workingDirectory.empty() ? nullptr : workingDirectory.c_str());
|
||||
|
||||
std::vector<uint8_t> bytes;
|
||||
const bool loadedOutput = ranProcess && exitCode == 0 && LoadBinaryFile(tempOutputPath, bytes);
|
||||
|
||||
448
engine/src/Rendering/Internal/ShaderVariantUtils.h
Normal file
448
engine/src/Rendering/Internal/ShaderVariantUtils.h
Normal file
@@ -0,0 +1,448 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Core/Containers/String.h>
|
||||
#include <XCEngine/RHI/RHIEnums.h>
|
||||
#include <XCEngine/RHI/RHIPipelineState.h>
|
||||
#include <XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Resources/Shader/Shader.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
namespace Internal {
|
||||
|
||||
inline Resources::ShaderBackend ToShaderBackend(RHI::RHIType backendType) {
|
||||
switch (backendType) {
|
||||
case RHI::RHIType::D3D12:
|
||||
return Resources::ShaderBackend::D3D12;
|
||||
case RHI::RHIType::Vulkan:
|
||||
return Resources::ShaderBackend::Vulkan;
|
||||
case RHI::RHIType::OpenGL:
|
||||
default:
|
||||
return Resources::ShaderBackend::OpenGL;
|
||||
}
|
||||
}
|
||||
|
||||
inline RHI::ShaderLanguage ToRHIShaderLanguage(Resources::ShaderLanguage language) {
|
||||
switch (language) {
|
||||
case Resources::ShaderLanguage::HLSL:
|
||||
return RHI::ShaderLanguage::HLSL;
|
||||
case Resources::ShaderLanguage::SPIRV:
|
||||
return RHI::ShaderLanguage::SPIRV;
|
||||
case Resources::ShaderLanguage::GLSL:
|
||||
default:
|
||||
return RHI::ShaderLanguage::GLSL;
|
||||
}
|
||||
}
|
||||
|
||||
inline std::wstring ToWideAscii(const Containers::String& value) {
|
||||
std::wstring wide;
|
||||
wide.reserve(value.Length());
|
||||
for (size_t index = 0; index < value.Length(); ++index) {
|
||||
wide.push_back(static_cast<wchar_t>(value[index]));
|
||||
}
|
||||
return wide;
|
||||
}
|
||||
|
||||
inline std::string ToStdString(const Containers::String& value) {
|
||||
return std::string(value.CStr(), value.Length());
|
||||
}
|
||||
|
||||
inline std::string EscapeRegexLiteral(const Containers::String& value) {
|
||||
std::string escaped;
|
||||
escaped.reserve(value.Length() * 2u);
|
||||
for (size_t index = 0; index < value.Length(); ++index) {
|
||||
const char ch = value[index];
|
||||
switch (ch) {
|
||||
case '\\':
|
||||
case '^':
|
||||
case '$':
|
||||
case '.':
|
||||
case '|':
|
||||
case '?':
|
||||
case '*':
|
||||
case '+':
|
||||
case '(':
|
||||
case ')':
|
||||
case '[':
|
||||
case ']':
|
||||
case '{':
|
||||
case '}':
|
||||
escaped.push_back('\\');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
escaped.push_back(ch);
|
||||
}
|
||||
|
||||
return escaped;
|
||||
}
|
||||
|
||||
inline bool TryCollectShaderPassResourceBindings(
|
||||
const Resources::ShaderPass& pass,
|
||||
Containers::Array<Resources::ShaderResourceBindingDesc>& outBindings) {
|
||||
outBindings.Clear();
|
||||
|
||||
if (pass.resources.Empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outBindings.Reserve(pass.resources.Size());
|
||||
for (const Resources::ShaderResourceBindingDesc& binding : pass.resources) {
|
||||
outBindings.PushBack(binding);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool TryRewriteHlslRegisterBindingWithName(
|
||||
std::string& sourceText,
|
||||
const Containers::String& declarationName,
|
||||
const char* registerPrefix,
|
||||
Core::uint32 bindingIndex,
|
||||
Core::uint32 setIndex,
|
||||
bool includeRegisterSpace,
|
||||
Resources::ShaderResourceType resourceType) {
|
||||
if (declarationName.Empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string registerClause =
|
||||
includeRegisterSpace
|
||||
? std::string("register(") + registerPrefix +
|
||||
std::to_string(bindingIndex) +
|
||||
", space" +
|
||||
std::to_string(setIndex) +
|
||||
")"
|
||||
: std::string("register(") + registerPrefix +
|
||||
std::to_string(bindingIndex) +
|
||||
")";
|
||||
const std::string escapedName = EscapeRegexLiteral(declarationName);
|
||||
|
||||
if (resourceType == Resources::ShaderResourceType::ConstantBuffer) {
|
||||
const std::regex pattern(
|
||||
"(cbuffer\\s+" + escapedName + "\\s*)(:\\s*register\\s*\\([^\\)]*\\))?(\\s*\\{)",
|
||||
std::regex::ECMAScript);
|
||||
const std::string rewritten =
|
||||
std::regex_replace(sourceText, pattern, "$1: " + registerClause + "$3");
|
||||
if (rewritten != sourceText) {
|
||||
sourceText = rewritten;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (resourceType == Resources::ShaderResourceType::StructuredBuffer ||
|
||||
resourceType == Resources::ShaderResourceType::RWStructuredBuffer) {
|
||||
const std::regex pattern(
|
||||
"((?:globallycoherent\\s+)?(?:StructuredBuffer|RWStructuredBuffer)\\s*<[^;\\r\\n>]+>\\s+" +
|
||||
escapedName + "\\s*)(:\\s*register\\s*\\([^\\)]*\\))?(\\s*;)",
|
||||
std::regex::ECMAScript);
|
||||
const std::string rewritten =
|
||||
std::regex_replace(sourceText, pattern, "$1: " + registerClause + "$3");
|
||||
if (rewritten != sourceText) {
|
||||
sourceText = rewritten;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (resourceType == Resources::ShaderResourceType::RawBuffer ||
|
||||
resourceType == Resources::ShaderResourceType::RWRawBuffer) {
|
||||
const std::regex pattern(
|
||||
"((?:globallycoherent\\s+)?(?:ByteAddressBuffer|RWByteAddressBuffer)\\s+" + escapedName +
|
||||
"\\s*)(:\\s*register\\s*\\([^\\)]*\\))?(\\s*;)",
|
||||
std::regex::ECMAScript);
|
||||
const std::string rewritten =
|
||||
std::regex_replace(sourceText, pattern, "$1: " + registerClause + "$3");
|
||||
if (rewritten != sourceText) {
|
||||
sourceText = rewritten;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::regex pattern(
|
||||
"((?:Texture2D|TextureCube|SamplerState|SamplerComparisonState)\\s+" + escapedName +
|
||||
"\\s*)(:\\s*register\\s*\\([^\\)]*\\))?(\\s*;)",
|
||||
std::regex::ECMAScript);
|
||||
const std::string rewritten =
|
||||
std::regex_replace(sourceText, pattern, "$1: " + registerClause + "$3");
|
||||
if (rewritten != sourceText) {
|
||||
sourceText = rewritten;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline const char* TryGetHlslRegisterPrefix(Resources::ShaderResourceType type) {
|
||||
switch (type) {
|
||||
case Resources::ShaderResourceType::ConstantBuffer:
|
||||
return "b";
|
||||
case Resources::ShaderResourceType::Texture2D:
|
||||
case Resources::ShaderResourceType::TextureCube:
|
||||
case Resources::ShaderResourceType::StructuredBuffer:
|
||||
case Resources::ShaderResourceType::RawBuffer:
|
||||
return "t";
|
||||
case Resources::ShaderResourceType::Sampler:
|
||||
return "s";
|
||||
case Resources::ShaderResourceType::RWStructuredBuffer:
|
||||
case Resources::ShaderResourceType::RWRawBuffer:
|
||||
return "u";
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool TryRewriteHlslRegisterBinding(
|
||||
std::string& sourceText,
|
||||
const Resources::ShaderResourceBindingDesc& binding,
|
||||
bool includeRegisterSpace) {
|
||||
const char* registerPrefix = TryGetHlslRegisterPrefix(binding.type);
|
||||
if (registerPrefix == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TryRewriteHlslRegisterBindingWithName(
|
||||
sourceText,
|
||||
binding.name,
|
||||
registerPrefix,
|
||||
binding.binding,
|
||||
binding.set,
|
||||
includeRegisterSpace,
|
||||
binding.type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool TryBuildRuntimeShaderBindings(
|
||||
const Resources::ShaderPass& pass,
|
||||
Resources::ShaderBackend backend,
|
||||
Containers::Array<Resources::ShaderResourceBindingDesc>& outBindings,
|
||||
bool& outIncludeRegisterSpace) {
|
||||
outBindings.Clear();
|
||||
outIncludeRegisterSpace = false;
|
||||
|
||||
if (!TryCollectShaderPassResourceBindings(pass, outBindings)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (backend == Resources::ShaderBackend::Vulkan) {
|
||||
outIncludeRegisterSpace = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (backend != Resources::ShaderBackend::D3D12 &&
|
||||
backend != Resources::ShaderBackend::OpenGL) {
|
||||
outBindings.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
Core::uint32 nextConstantBufferRegister = 0;
|
||||
Core::uint32 nextTextureRegister = 0;
|
||||
Core::uint32 nextSamplerRegister = 0;
|
||||
Core::uint32 nextUnorderedAccessRegister = 0;
|
||||
for (Resources::ShaderResourceBindingDesc& binding : outBindings) {
|
||||
binding.set = 0;
|
||||
switch (binding.type) {
|
||||
case Resources::ShaderResourceType::ConstantBuffer:
|
||||
binding.binding = nextConstantBufferRegister++;
|
||||
break;
|
||||
case Resources::ShaderResourceType::Texture2D:
|
||||
case Resources::ShaderResourceType::TextureCube:
|
||||
case Resources::ShaderResourceType::StructuredBuffer:
|
||||
case Resources::ShaderResourceType::RawBuffer:
|
||||
binding.binding = nextTextureRegister++;
|
||||
break;
|
||||
case Resources::ShaderResourceType::Sampler:
|
||||
binding.binding = nextSamplerRegister++;
|
||||
break;
|
||||
case Resources::ShaderResourceType::RWStructuredBuffer:
|
||||
case Resources::ShaderResourceType::RWRawBuffer:
|
||||
default:
|
||||
binding.binding = nextUnorderedAccessRegister++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline std::string BuildRuntimeShaderSource(
|
||||
const Resources::ShaderPass& pass,
|
||||
Resources::ShaderBackend backend,
|
||||
const Resources::ShaderStageVariant& variant) {
|
||||
std::string sourceText = ToStdString(variant.sourceCode);
|
||||
|
||||
if (variant.language != Resources::ShaderLanguage::HLSL ||
|
||||
backend == Resources::ShaderBackend::Generic) {
|
||||
return sourceText;
|
||||
}
|
||||
|
||||
Containers::Array<Resources::ShaderResourceBindingDesc> bindings;
|
||||
bool includeRegisterSpace = false;
|
||||
if (!TryBuildRuntimeShaderBindings(pass, backend, bindings, includeRegisterSpace)) {
|
||||
return sourceText;
|
||||
}
|
||||
|
||||
for (const Resources::ShaderResourceBindingDesc& binding : bindings) {
|
||||
TryRewriteHlslRegisterBinding(sourceText, binding, includeRegisterSpace);
|
||||
}
|
||||
|
||||
return sourceText;
|
||||
}
|
||||
|
||||
inline void AddShaderCompileMacro(
|
||||
RHI::ShaderCompileDesc& compileDesc,
|
||||
const wchar_t* name,
|
||||
const wchar_t* definition = L"1") {
|
||||
if (name == nullptr || *name == L'\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const RHI::ShaderCompileMacro& existingMacro : compileDesc.macros) {
|
||||
if (existingMacro.name == name) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
RHI::ShaderCompileMacro macro = {};
|
||||
macro.name = name;
|
||||
macro.definition = definition != nullptr ? definition : L"";
|
||||
compileDesc.macros.push_back(std::move(macro));
|
||||
}
|
||||
|
||||
inline void InjectShaderBackendMacros(
|
||||
Resources::ShaderBackend backend,
|
||||
RHI::ShaderCompileDesc& compileDesc) {
|
||||
switch (backend) {
|
||||
case Resources::ShaderBackend::OpenGL:
|
||||
AddShaderCompileMacro(compileDesc, L"SHADER_API_GLCORE");
|
||||
AddShaderCompileMacro(compileDesc, L"UNITY_UV_STARTS_AT_TOP", L"0");
|
||||
AddShaderCompileMacro(compileDesc, L"UNITY_NEAR_CLIP_VALUE", L"-1");
|
||||
break;
|
||||
case Resources::ShaderBackend::Vulkan:
|
||||
AddShaderCompileMacro(compileDesc, L"SHADER_API_VULKAN");
|
||||
AddShaderCompileMacro(compileDesc, L"UNITY_UV_STARTS_AT_TOP", L"1");
|
||||
AddShaderCompileMacro(compileDesc, L"UNITY_NEAR_CLIP_VALUE", L"0");
|
||||
break;
|
||||
case Resources::ShaderBackend::D3D12:
|
||||
AddShaderCompileMacro(compileDesc, L"SHADER_API_D3D12");
|
||||
AddShaderCompileMacro(compileDesc, L"UNITY_UV_STARTS_AT_TOP", L"1");
|
||||
AddShaderCompileMacro(compileDesc, L"UNITY_NEAR_CLIP_VALUE", L"0");
|
||||
break;
|
||||
case Resources::ShaderBackend::Generic:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
inline void ApplyShaderStageVariant(
|
||||
const Resources::ShaderStageVariant& variant,
|
||||
RHI::ShaderCompileDesc& compileDesc) {
|
||||
compileDesc.fileName.clear();
|
||||
compileDesc.includeDirectories.clear();
|
||||
compileDesc.source.assign(
|
||||
variant.sourceCode.CStr(),
|
||||
variant.sourceCode.CStr() + variant.sourceCode.Length());
|
||||
compileDesc.sourceLanguage = ToRHIShaderLanguage(variant.language);
|
||||
compileDesc.entryPoint = ToWideAscii(variant.entryPoint);
|
||||
compileDesc.profile = ToWideAscii(variant.profile);
|
||||
}
|
||||
|
||||
inline std::wstring ResolveRuntimeShaderSourcePath(const Containers::String& shaderPath) {
|
||||
Containers::String resolvedPath = shaderPath;
|
||||
if (resolvedPath.Empty()) {
|
||||
return std::wstring();
|
||||
}
|
||||
|
||||
if (Resources::IsBuiltinShaderPath(resolvedPath)) {
|
||||
Containers::String builtinAssetPath;
|
||||
if (!Resources::TryResolveBuiltinShaderAssetPath(resolvedPath, builtinAssetPath)) {
|
||||
return std::wstring();
|
||||
}
|
||||
resolvedPath = builtinAssetPath;
|
||||
}
|
||||
|
||||
return ToWideAscii(resolvedPath);
|
||||
}
|
||||
|
||||
inline void ApplyShaderStageVariant(
|
||||
const Containers::String& shaderPath,
|
||||
const Resources::ShaderPass& pass,
|
||||
Resources::ShaderBackend backend,
|
||||
const Resources::ShaderStageVariant& variant,
|
||||
RHI::ShaderCompileDesc& compileDesc) {
|
||||
const std::string sourceText = BuildRuntimeShaderSource(pass, backend, variant);
|
||||
compileDesc.source.assign(sourceText.begin(), sourceText.end());
|
||||
compileDesc.fileName = ResolveRuntimeShaderSourcePath(shaderPath);
|
||||
compileDesc.includeDirectories.clear();
|
||||
if (!compileDesc.fileName.empty()) {
|
||||
const std::filesystem::path shaderFilePath(compileDesc.fileName);
|
||||
if (shaderFilePath.has_parent_path()) {
|
||||
compileDesc.includeDirectories.push_back(shaderFilePath.parent_path().wstring());
|
||||
}
|
||||
}
|
||||
compileDesc.sourceLanguage = ToRHIShaderLanguage(variant.language);
|
||||
compileDesc.entryPoint = ToWideAscii(variant.entryPoint);
|
||||
compileDesc.profile = ToWideAscii(variant.profile);
|
||||
InjectShaderBackendMacros(backend, compileDesc);
|
||||
}
|
||||
|
||||
inline void ApplyShaderStageVariant(
|
||||
const Resources::ShaderPass& pass,
|
||||
Resources::ShaderBackend backend,
|
||||
const Resources::ShaderStageVariant& variant,
|
||||
RHI::ShaderCompileDesc& compileDesc) {
|
||||
ApplyShaderStageVariant(Containers::String(), pass, backend, variant, compileDesc);
|
||||
}
|
||||
|
||||
inline Containers::String BuildShaderKeywordSignature(
|
||||
const Resources::ShaderKeywordSet& keywordSet) {
|
||||
Resources::ShaderKeywordSet normalizedKeywords = keywordSet;
|
||||
Resources::NormalizeShaderKeywordSetInPlace(normalizedKeywords);
|
||||
|
||||
Containers::String signature;
|
||||
for (size_t keywordIndex = 0; keywordIndex < normalizedKeywords.enabledKeywords.Size(); ++keywordIndex) {
|
||||
if (keywordIndex > 0) {
|
||||
signature += ";";
|
||||
}
|
||||
|
||||
signature += normalizedKeywords.enabledKeywords[keywordIndex];
|
||||
}
|
||||
|
||||
return signature;
|
||||
}
|
||||
|
||||
inline bool ShaderPassHasGraphicsVariants(
|
||||
const Resources::Shader& shader,
|
||||
const Containers::String& passName,
|
||||
Resources::ShaderBackend backend,
|
||||
const Resources::ShaderKeywordSet& enabledKeywords = Resources::ShaderKeywordSet()) {
|
||||
return shader.FindVariant(
|
||||
passName,
|
||||
Resources::ShaderType::Vertex,
|
||||
backend,
|
||||
enabledKeywords) != nullptr &&
|
||||
shader.FindVariant(
|
||||
passName,
|
||||
Resources::ShaderType::Fragment,
|
||||
backend,
|
||||
enabledKeywords) != nullptr;
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -15,6 +15,20 @@ using namespace VolumeIntegrationTestUtils;
|
||||
namespace {
|
||||
|
||||
constexpr const char* kD3D12Screenshot = "volume_occlusion_scene_d3d12.ppm";
|
||||
constexpr const char* kOpenGLScreenshot = "volume_occlusion_scene_opengl.ppm";
|
||||
constexpr const char* kVulkanScreenshot = "volume_occlusion_scene_vulkan.ppm";
|
||||
|
||||
const char* GetScreenshotFilename(RHIType backendType) {
|
||||
switch (backendType) {
|
||||
case RHIType::OpenGL:
|
||||
return kOpenGLScreenshot;
|
||||
case RHIType::Vulkan:
|
||||
return kVulkanScreenshot;
|
||||
case RHIType::D3D12:
|
||||
default:
|
||||
return kD3D12Screenshot;
|
||||
}
|
||||
}
|
||||
|
||||
class VolumeOcclusionSceneTest : public VolumeIntegrationSceneFixture {
|
||||
protected:
|
||||
@@ -96,10 +110,11 @@ private:
|
||||
};
|
||||
|
||||
TEST_P(VolumeOcclusionSceneTest, RenderNanoVdbVolumeOcclusionScene) {
|
||||
RenderAndCompare(kD3D12Screenshot, 0.0f);
|
||||
const char* screenshotFilename = GetScreenshotFilename(GetBackendType());
|
||||
RenderAndCompare(screenshotFilename, 0.0f);
|
||||
|
||||
const RenderingIntegrationTestUtils::PpmImage image =
|
||||
RenderingIntegrationTestUtils::LoadPpmImage(kD3D12Screenshot);
|
||||
RenderingIntegrationTestUtils::LoadPpmImage(screenshotFilename);
|
||||
RenderingIntegrationTestUtils::ExpectPixelLuminanceAtMost(
|
||||
image,
|
||||
640u,
|
||||
@@ -117,6 +132,12 @@ TEST_P(VolumeOcclusionSceneTest, RenderNanoVdbVolumeOcclusionScene) {
|
||||
} // namespace
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(D3D12, VolumeOcclusionSceneTest, ::testing::Values(XCEngine::RHI::RHIType::D3D12));
|
||||
#if defined(XCENGINE_SUPPORT_OPENGL)
|
||||
INSTANTIATE_TEST_SUITE_P(OpenGL, VolumeOcclusionSceneTest, ::testing::Values(XCEngine::RHI::RHIType::OpenGL));
|
||||
#endif
|
||||
#if defined(XCENGINE_SUPPORT_VULKAN)
|
||||
INSTANTIATE_TEST_SUITE_P(Vulkan, VolumeOcclusionSceneTest, ::testing::Values(XCEngine::RHI::RHIType::Vulkan));
|
||||
#endif
|
||||
|
||||
GTEST_API_ int main(int argc, char** argv) {
|
||||
return RunRenderingIntegrationTestMain(argc, argv);
|
||||
|
||||
@@ -15,6 +15,20 @@ using namespace VolumeIntegrationTestUtils;
|
||||
namespace {
|
||||
|
||||
constexpr const char* kD3D12Screenshot = "volume_scene_d3d12.ppm";
|
||||
constexpr const char* kOpenGLScreenshot = "volume_scene_opengl.ppm";
|
||||
constexpr const char* kVulkanScreenshot = "volume_scene_vulkan.ppm";
|
||||
|
||||
const char* GetScreenshotFilename(RHIType backendType) {
|
||||
switch (backendType) {
|
||||
case RHIType::OpenGL:
|
||||
return kOpenGLScreenshot;
|
||||
case RHIType::Vulkan:
|
||||
return kVulkanScreenshot;
|
||||
case RHIType::D3D12:
|
||||
default:
|
||||
return kD3D12Screenshot;
|
||||
}
|
||||
}
|
||||
|
||||
class VolumeSceneTest : public VolumeIntegrationSceneFixture {
|
||||
protected:
|
||||
@@ -82,12 +96,18 @@ void VolumeSceneTest::ReleaseSceneResources() {
|
||||
}
|
||||
|
||||
TEST_P(VolumeSceneTest, RenderNanoVdbVolumeScene) {
|
||||
RenderAndCompare(kD3D12Screenshot, 0.0f);
|
||||
RenderAndCompare(GetScreenshotFilename(GetBackendType()), 0.0f);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(D3D12, VolumeSceneTest, ::testing::Values(RHIType::D3D12));
|
||||
#if defined(XCENGINE_SUPPORT_OPENGL)
|
||||
INSTANTIATE_TEST_SUITE_P(OpenGL, VolumeSceneTest, ::testing::Values(RHIType::OpenGL));
|
||||
#endif
|
||||
#if defined(XCENGINE_SUPPORT_VULKAN)
|
||||
INSTANTIATE_TEST_SUITE_P(Vulkan, VolumeSceneTest, ::testing::Values(RHIType::Vulkan));
|
||||
#endif
|
||||
|
||||
GTEST_API_ int main(int argc, char** argv) {
|
||||
return RunRenderingIntegrationTestMain(argc, argv);
|
||||
|
||||
@@ -17,6 +17,20 @@ using namespace VolumeIntegrationTestUtils;
|
||||
namespace {
|
||||
|
||||
constexpr const char* kD3D12Screenshot = "volume_transform_scene_d3d12.ppm";
|
||||
constexpr const char* kOpenGLScreenshot = "volume_transform_scene_opengl.ppm";
|
||||
constexpr const char* kVulkanScreenshot = "volume_transform_scene_vulkan.ppm";
|
||||
|
||||
const char* GetScreenshotFilename(RHIType backendType) {
|
||||
switch (backendType) {
|
||||
case RHIType::OpenGL:
|
||||
return kOpenGLScreenshot;
|
||||
case RHIType::Vulkan:
|
||||
return kVulkanScreenshot;
|
||||
case RHIType::D3D12:
|
||||
default:
|
||||
return kD3D12Screenshot;
|
||||
}
|
||||
}
|
||||
|
||||
class VolumeTransformSceneTest : public VolumeIntegrationSceneFixture {
|
||||
protected:
|
||||
@@ -122,10 +136,11 @@ private:
|
||||
};
|
||||
|
||||
TEST_P(VolumeTransformSceneTest, RenderNanoVdbVolumeTransformScene) {
|
||||
RenderAndCompare(kD3D12Screenshot, 0.0f);
|
||||
const char* screenshotFilename = GetScreenshotFilename(GetBackendType());
|
||||
RenderAndCompare(screenshotFilename, 0.0f);
|
||||
|
||||
const RenderingIntegrationTestUtils::PpmImage image =
|
||||
RenderingIntegrationTestUtils::LoadPpmImage(kD3D12Screenshot);
|
||||
RenderingIntegrationTestUtils::LoadPpmImage(screenshotFilename);
|
||||
RenderingIntegrationTestUtils::ExpectPixelLuminanceAtLeast(
|
||||
image,
|
||||
840u,
|
||||
@@ -137,6 +152,12 @@ TEST_P(VolumeTransformSceneTest, RenderNanoVdbVolumeTransformScene) {
|
||||
} // namespace
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(D3D12, VolumeTransformSceneTest, ::testing::Values(XCEngine::RHI::RHIType::D3D12));
|
||||
#if defined(XCENGINE_SUPPORT_OPENGL)
|
||||
INSTANTIATE_TEST_SUITE_P(OpenGL, VolumeTransformSceneTest, ::testing::Values(XCEngine::RHI::RHIType::OpenGL));
|
||||
#endif
|
||||
#if defined(XCENGINE_SUPPORT_VULKAN)
|
||||
INSTANTIATE_TEST_SUITE_P(Vulkan, VolumeTransformSceneTest, ::testing::Values(XCEngine::RHI::RHIType::Vulkan));
|
||||
#endif
|
||||
|
||||
GTEST_API_ int main(int argc, char** argv) {
|
||||
return RunRenderingIntegrationTestMain(argc, argv);
|
||||
|
||||
Reference in New Issue
Block a user