diff --git a/docs/plan/Renderer_C++层第三阶段计划_RenderGraph骨架与资源调度_2026-04-14.md b/docs/plan/Renderer_C++层第三阶段计划_RenderGraph骨架与资源调度_2026-04-14.md new file mode 100644 index 00000000..d5a37011 --- /dev/null +++ b/docs/plan/Renderer_C++层第三阶段计划_RenderGraph骨架与资源调度_2026-04-14.md @@ -0,0 +1,505 @@ +# Renderer C++层第三阶段计划:Render Graph 骨架与资源调度 + +日期:`2026-04-14` + +## 1. 文档定位 + +第二阶段已经收口,当前 native 渲染层已经完成了两件关键事情: + +- `request -> frame plan -> execution` 主链基本收干净 +- `BuiltinForwardPipeline` 已经从“大杂烩”开始拆成更清晰的 builtin renderer 雏形 + +所以下一阶段不应该继续围着 `BuiltinForwardPipeline` 做零散瘦身,而是要正式进入: + +`Render Graph` + +这一阶段的目标不是立刻做完整 `Deferred Renderer`,也不是立刻做 `C# SRP`,而是先在 `C++ native` 层建立真正可长期演进的图式调度内核。 + +--- + +## 2. 什么是 Render Graph + +Render Graph 不是“一个新 pass 管理器”,也不是“把现有 stage 改个名字”。 + +它本质上是: + +- 一套按帧构建的 pass / resource 依赖描述 +- 一套根据读写关系编译执行顺序的系统 +- 一套统一管理 transient 资源、资源生命周期和 barrier 的系统 + +也就是说,pass 不再手写: + +- 我自己创建哪张中间 RT +- 我自己决定什么时候切状态 +- 我自己告诉下一个 pass 用哪张 source/destination surface + +而是改成声明: + +- 我读什么 +- 我写什么 +- 我输出到哪类 attachment +- 我需要什么输入上下文 + +然后由 graph 编译器统一决定: + +- 顺序 +- 资源创建/复用 +- barrier +- 最终执行计划 + +一句话说,当前是“手写命令式渲染编排”,Render Graph 是“声明式依赖编排 + 编译执行”。 + +--- + +## 3. 为什么现在该做 + +当前这套渲染层已经到了一个临界点: + +- `CameraFramePlan` 已经把 camera 级执行入口整理出来了 +- `SceneRenderFeatureHost` 已经把 feature 注入点整理出来了 +- `BuiltinForwardPipeline` 已经拆出 frame / lifecycle / resources / scene phases / surface + +这意味着“谁来做什么”已经比之前清楚很多了。 + +但现在仍然有三个核心问题没解决: + +### 3.1 资源流还是手工传递 + +现在很多阶段仍然是: + +- `RenderSurface` +- `sourceColorView` +- `sourceSurface` +- `destinationSurface` + +靠手工在 `CameraFramePlan` 和 `CameraRenderer` 之间传。 + +这对少量固定阶段还能忍,但一旦要上: + +- 更长的 post-process 链 +- object id / overlay / outline 混合 +- deferred gbuffer +- SSAO / SSR / TAA / bloom / DoF +- feature 自定义中间纹理 + +就会越来越乱。 + +### 3.2 依赖关系还是隐式的 + +当前 feature 注入点已经有了,但依赖还是隐式的。 + +例如一个 feature pass 到底: + +- 读 scene color +- 读 depth +- 读 shadow map +- 写 camera color +- 写独立 intermediate + +现在不是 native 核心显式知道的,而是散落在调用代码里。 + +### 3.3 Deferred 还没有合适宿主 + +Deferred 不应该直接加在现在这套手工 surface 编排上。 + +因为 deferred 天生会带来: + +- gbuffer A/B/C/depth +- lighting buffer +- transparent / skybox / post-process 组合 +- optional prepass / depth pyramid / SSAO + +这些东西如果没有 Render Graph,代码很快又会炸回“大 pipeline 手工串 stage”的旧路。 + +所以顺序必须是: + +1. 先做 Render Graph +2. 再在 graph 上接 deferred + +--- + +## 4. 这一阶段的总目标 + +这一阶段结束后,native 层要达到下面这个结构: + +`RHI -> Render Graph Kernel -> Builtin Renderer Graph Builder -> Feature Injection -> Camera Execution` + +更具体一点: + +- `RHI` 继续负责底层资源、命令列表、PSO、descriptor、render pass +- `Render Graph Kernel` 负责 pass/resource/依赖/barrier/lifetime +- `Builtin Renderer` 负责“这一帧要往 graph 里注册哪些 scene pass” +- `Feature` 负责在指定注入点向 graph 追加 pass +- `CameraRenderer` 只负责 build scene data -> build graph -> execute graph + +--- + +## 5. 和你当前代码怎么对接 + +### 5.1 先保留 CameraFramePlan,但降级为“高层帧意图” + +现在的 `CameraFramePlan` 里还带着不少具体 surface/sourceColor 细节。 + +Render Graph 上来之后,`CameraFramePlan` 更应该表达: + +- 这台 camera 本帧需不需要 shadow +- 需不需要 object id +- 需不需要 post-process +- 需不需要 final output +- 需不需要 overlay +- 允许哪些 feature 注入 + +而不是继续手工保存一串中间 surface 流。 + +也就是说: + +- `CameraFramePlan` 保留 +- 但它从“半执行脚本”收成“高层执行意图” + +### 5.2 CameraRenderer 改成 build graph,而不是手工串 stage + +当前 `CameraRenderer` 还是在做: + +- build scene data +- 执行 shadow +- 执行 main scene +- 执行 post-process +- 执行 final output +- 执行 object id / overlay + +下一阶段要改成: + +1. `BuildSceneData` +2. `BuildRendererLists` +3. `BuildCameraRenderGraph` +4. `ExecuteRenderGraph` + +也就是说它变成 graph 驱动器,而不是手工编排器。 + +### 5.3 BuiltinForwardPipeline 不再直接代表“一整帧” + +这一步很关键。 + +当前 `BuiltinForwardPipeline` 虽然已经清爽很多,但本质上还是: + +- 一个 camera main scene renderer + +Render Graph 之后,它更应该变成: + +- 一个向 graph 注册 forward scene pass 的 builtin renderer + +也就是它的职责改成: + +- 注册 opaque pass +- 注册 skybox pass +- 注册 transparent pass +- 在合适注入点让 feature host 往 graph 塞 pass + +而不是自己继续拥有整帧级别的中间表面和编排职责。 + +### 5.4 SceneRenderFeatureHost 保留,但执行方式改掉 + +`SceneRenderFeatureHost` 现在是直接 `Prepare/Execute`。 + +上 Render Graph 后,它更应该变成: + +- 在 `BeforeOpaque / AfterOpaque / BeforeTransparent / AfterTransparent` 等注入点 +- 由 feature 向 graph builder 注册 pass + +所以 concept 保留,执行方式要改成: + +- 现在:`feature.Execute(passContext)` +- 以后:`feature.Record(renderGraphBuilder, injectionPoint, context)` + +这会非常接近 Unity URP 的 `RendererFeature -> AddRenderPasses` 那个味道。 + +### 5.5 RenderSurface 只保留 external/import 场景 + +Graph 上来之后,不应该继续让内部 pass 大量互相传裸 `RenderSurface`。 + +`RenderSurface` 主要应该用于: + +- swapchain backbuffer +- editor viewport offscreen target +- object id target +- 已有外部资源 import 进 graph + +而 graph 内部中间纹理应该改成: + +- graph texture handle +- graph imported texture +- graph transient texture + +--- + +## 6. 这一阶段的实现策略 + +### 6.1 先做最小可用 Render Graph,不求一步到位 + +第一版 graph 只做下面这些: + +- texture resource +- imported texture +- transient texture +- raster pass +- compute pass +- read/write dependency +- 简单拓扑排序 +- 简单 barrier 推导 +- 按帧创建与释放 transient 资源 + +第一版明确先不做: + +- 复杂 aliasing +- pass merge +- async compute 调度 +- memory budget 优化器 +- 跨 queue graph + +先把“能正确替代手工 surface 编排”做出来。 + +### 6.2 先 graph 化 camera 级 pass,不先 graph 化单个 draw call + +这一步也很重要。 + +先 graph 化的是: + +- shadow caster +- main scene opaque/skybox/transparent +- object id +- post-process +- final output +- overlay / outline / grid + +不是一上来就把 mesh draw 拆成图节点。 + +也就是说,graph 的粒度先是“渲染 pass 级”,不是“每个物体 draw 级”。 + +这是成本最低、风险最小、收益最大的切法。 + +### 6.3 先替掉中间 surface 管理,再替掉 feature 执行模型 + +推荐顺序: + +1. 先让 post-process / final output / object-id / overlay 改成 graph 资源流 +2. 再让 builtin forward main scene 改成 graph pass +3. 最后让 feature host 改成 graph 注入 + +原因很简单: + +- 最乱的其实是中间资源流 +- 资源流一旦 graph 化,后面的 feature / deferred 才有稳定宿主 + +--- + +## 7. 建议新增的 native 模块 + +建议直接新开一个 `Rendering/Graph/` 模块。 + +第一版建议至少有: + +- `RenderGraph.h` +- `RenderGraphBuilder.h` +- `RenderGraphPass.h` +- `RenderGraphResource.h` +- `RenderGraphTypes.h` +- `RenderGraphCompiler.h` +- `RenderGraphExecutor.h` +- `RenderGraphBlackboard.h` + +第一版资源类型建议先只做: + +- `RenderGraphTextureHandle` +- `ImportedTextureDesc` +- `TransientTextureDesc` + +第一版 pass builder 建议至少支持: + +- `ReadTexture` +- `WriteColor` +- `WriteDepth` +- `ReadDepth` +- `CreateTransientTexture` +- `ImportTexture` +- `SetExecuteCallback` + +--- + +## 8. 对当前文件结构的具体改造方向 + +### 8.1 CameraRenderer + +从: + +- 手工执行 frame stage + +改成: + +- build scene data +- build renderer lists +- build render graph +- execute render graph + +### 8.2 CameraFramePlan + +从: + +- 带大量具体 source/destination surface 细节 + +改成: + +- 表达这一帧需要哪些逻辑阶段和输出能力 + +### 8.3 BuiltinForwardPipeline + +从: + +- 一个直接渲染 camera main scene 的 pipeline + +改成: + +- 一个 forward renderer graph builder / pass registrar + +### 8.4 FullscreenPassSurfaceCache + +从: + +- runtime 手工中间表面缓存 + +改成: + +- graph import/export 辅助 +- 后续逐步淡出 + +### 8.5 SceneRenderFeaturePass / Host + +从: + +- runtime execute + +改成: + +- graph record / graph contribute + +--- + +## 9. 分阶段执行 + +## 阶段 A:Render Graph Core + +目标: + +- 建立 graph 基础数据结构和最小编译/执行器 + +交付: + +- pass/resource/handle/builder/compiler/executor +- 支持 imported + transient texture +- 支持 raster + compute pass + +验收: + +- 有独立 unit tests +- 能构造最小 graph 并正确执行顺序与 barrier + +## 阶段 B:Camera 级资源流 graph 化 + +目标: + +- 把 post-process / final output / object id / overlay 的中间 surface 手工管理替成 graph + +交付: + +- `CameraRenderer` 开始 build graph +- `FullscreenPassSurfaceCache` 职责收缩 + +验收: + +- 现有 viewport / object id / final color 路径不回退 + +## 阶段 C:Builtin Forward Main Scene graph 化 + +目标: + +- 把 main scene 从手工 frame 执行切到 graph raster pass + +交付: + +- opaque / skybox / transparent 作为 graph pass 注册 +- forward scene 仍保留当前 renderer list / draw settings 逻辑 + +验收: + +- 当前 forward 主链功能不回退 +- `BuiltinForwardPipeline` 不再继续持有整帧调度职责 + +## 阶段 D:Feature 注入 graph 化 + +目标: + +- 让 `SceneRenderFeatureHost` 从直接执行改成向 graph 注入 + +交付: + +- feature pass graph record contract +- 注入点继续沿用当前 `SceneRenderInjectionPoint` + +验收: + +- gaussian / volumetric / editor feature 能继续挂入 + +## 阶段 E:为 Deferred 做宿主准备 + +目标: + +- 不做 deferred 本身,只把 graph 宿主和 renderer contract 整理到可以接 deferred + +交付: + +- scene color/depth/object id/shadow/final output 都成为正式 graph resource +- renderer feature 和 builtin renderer 关系稳定 + +验收: + +- 下一阶段可以直接进入 deferred / gbuffer 设计 + +--- + +## 10. 本阶段明确不做 + +- 不在这一阶段直接落完整 deferred +- 不在这一阶段做 C# SRP API +- 不在这一阶段做全量资源 aliasing 优化 +- 不在这一阶段做 async compute graph +- 不在这一阶段推倒重写 RHI + +--- + +## 11. 这一阶段完成后的状态 + +如果这一阶段做完,native 渲染层应该变成下面这样: + +- `CameraFramePlan` 负责高层帧意图 +- `CameraRenderer` 负责 graph build + execute +- `RenderGraph` 负责资源生命周期与依赖调度 +- `BuiltinForwardRenderer` 负责向 graph 注册 forward scene pass +- `FeatureHost` 负责在 injection point 向 graph 贡献 pass + +到这一步,再做: + +- deferred +- universal renderer +- C# 自定义 renderer feature +- C# 自定义 pipeline + +才是顺的。 + +--- + +## 12. 一句话结论 + +下一阶段不是继续修补 `BuiltinForwardPipeline`,而是要把你现在这套已经整理好的 native 渲染主链,正式提升成: + +`Render Graph 驱动的 Render Kernel` + +这一步一旦做实,后面的 deferred 和 Unity 式 SRP/URP 才有真正稳定的底座。 diff --git a/docs/plan/used/Renderer_C++层第二阶段计划_BuiltinRenderer与FeatureContract_2026-04-14.md b/docs/plan/used/Renderer_C++层第二阶段计划_BuiltinRenderer与FeatureContract_2026-04-14.md new file mode 100644 index 00000000..373158ea --- /dev/null +++ b/docs/plan/used/Renderer_C++层第二阶段计划_BuiltinRenderer与FeatureContract_2026-04-14.md @@ -0,0 +1,211 @@ +# Renderer C++层第二阶段计划:Builtin Renderer 与 Feature Contract + +日期:`2026-04-14` + +## 1. 文档定位 + +第一阶段已经把最乱的执行契约收干净了: + +- `request -> frame plan -> execution` 已经分层 +- `SceneRenderer / CameraRenderer` 的公开执行入口已经收敛到 `CameraFramePlan` + +第二阶段不做 `Render Graph`,也不做 `Deferred Renderer`。 +第二阶段要做的是: + +`先把 native C++ 层整理成一个真正能承接 URP-like 包层的内核` + +也就是先把“内建 renderer、feature 注入点、pass 编排契约”做对。 + +## 2. 为什么下一步不是先搞 Shadow + +`Shadow` 后面当然会进入 `URP-like` 层参与编排,但那说的是: + +- cascade 策略 +- filter 策略 +- screen-space shadow +- additional light shadow +- debug / feature 开关 + +这些“策略和管线组织”应该放在包层。 + +但 native C++ 层里仍然必须保留最小阴影执行能力: + +- shadow map / atlas 资源 +- shadow caster pass 执行 +- light-space 数据布局 +- resource state / barrier / cache +- 主场景采样阴影所依赖的底层 contract + +所以现在如果先把 `shadow` 当第一优先去扩,会把顺序做反。 +更正确的路线是: + +1. 先把 `renderer / feature / pass` 契约做稳定 +2. 再让 `shadow` 挂到这个契约上 +3. 再往 `URP-like` 层放阴影策略 + +## 3. 本阶段总目标 + +把当前 `BuiltinForwardPipeline + SceneRenderer + editor 注入点` 这一坨, +整理成更接近下面这条线的形态: + +`RHI -> Native Render Kernel -> Builtin Renderer Contract -> URP-like Renderer Layer` + +这一阶段结束后,native 层要能回答清楚三件事: + +- 一个 builtin renderer 的正式输入是什么 +- 一个 renderer feature 怎么注入 pass +- 哪些东西永远属于 native 执行层,哪些东西以后应该上 URP-like 包层 + +## 4. 这一阶段真正该做什么 + +### 4.1 先正式化 Builtin Renderer Contract + +目标: + +- 不再让 `BuiltinForwardPipeline` 既像 pipeline、又像 renderer、又像 feature 垃圾桶 + +这一轮要先说清楚: + +- renderer runtime 的输入 contract +- main scene phase 的组织方式 +- fullscreen / object-id / editor extension 的挂接边界 +- feature pass 和 renderer 自身职责边界 + +预期结果: + +- `BuiltinForwardPipeline` 更像一个内建 renderer 雏形 +- 后面无论叫 `UniversalRenderer` 还是别的名字,都有 native 对照物 + +### 4.2 正式化 Feature / Pass 注入契约 + +目标: + +- 把现在已经存在的各种 pass 注入点,收成一个以后能类比 `URP Renderer Feature` 的 native contract + +这一轮重点不是把所有功能迁走,而是先把“怎么挂”定义清楚。 + +至少要覆盖: + +- pre-scene +- main-scene 前后扩展点 +- object-id +- post-process +- final-output +- editor overlay / outline / grid + +预期结果: + +- feature 注入点不再依赖隐式约定 +- `CameraFramePlan` 对阶段的描述更稳定 +- 以后 C# 包层要做 renderer feature 时,native 侧已经有对应的宿主契约 + +### 4.3 BuiltinForwardPipeline 继续瘦身,但方向改成“内建 renderer 化” + +目标: + +- 不是继续散着拆 helper +- 而是按“renderer 自身 / feature / 资源绑定 / scene draw”这几个方向收 + +这一轮优先处理: + +- scene phase 调度 +- feature pass 调度 +- renderer 级资源绑定 +- 与 shadow / skybox / gaussian / volumetric 的宿主边界 + +预期结果: + +- `BuiltinForwardPipeline` 主类继续变薄 +- 新增渲染能力时,优先新增 feature 或独立模块,而不是继续堆进主 cpp + +### 4.4 Shadow 在本阶段只保留“最小 native service” + +目标: + +- 先不扩 shadow 策略层 +- 只把 native 必需能力收成清晰 service + +也就是只做这些: + +- shadow resource ownership +- shadow caster execution contract +- 主场景采样所需共享数据 +- 与 builtin renderer 的边界 + +明确不做这些: + +- cascade 新策略 +- additional light shadows +- screen-space shadow +- 各种阴影 feature 开关扩展 + +这些以后都应该挂到 `URP-like` 层。 + +### 4.5 Rendering 目录第一轮低风险收口 + +目标: + +- 只在职责边界已经清楚的前提下收目录 + +优先考虑: + +- 把 `BuiltinForwardPipeline*` 相关实现收成更明确的 builtin renderer 子目录 +- 让 `Passes` 不再无限平铺 +- 给 feature / editor / fullscreen 形成更清晰的落点 + +原则: + +- 先拆职责,再收目录 +- 不为“好看”做纯搬家 +- 每次目录调整都必须带编译验证 + +## 5. 明确不做的事 + +这一阶段明确不做: + +- 不引入真正运行时 `Render Graph` +- 不做完整 `Deferred Renderer` +- 不做 `C# SRP` 暴露 +- 不把 Gaussian / Volumetric / Shadow 整块直接搬到包层 +- 不为了目录整齐做大搬家 + +## 6. 推荐执行顺序 + +1. 先做 `Builtin Renderer Contract` +2. 再做 `Feature / Pass 注入契约` +3. 再做 `BuiltinForwardPipeline` 的 renderer 化瘦身 +4. 同步把 `Shadow` 收成最小 native service +5. 最后做第一轮目录收口 + +这个顺序的原因很简单: + +- 没有 renderer contract,feature 就没有宿主 +- 没有 feature contract,URP-like 层以后只能继续硬绑 native 细节 +- 没有 builtin renderer 化,forward pipeline 还会继续长歪 +- shadow 应该挂在契约上收,而不是反过来主导这一阶段 + +## 7. 交付标准 + +满足下面这些条件,就可以认为第二阶段基本收口: + +- builtin renderer 的输入和执行边界能一句话说清 +- feature / pass 注入点形成稳定 native contract +- `BuiltinForwardPipeline` 更像 builtin renderer,而不是继续当大杂烩 +- shadow 留在 native 层的部分被收成最小 service +- 目录结构开始反映真实模块边界 +- `XCEditor`、`rendering_unit_tests`、`editor_tests`、相关 rendering integration target 持续通过 + +## 8. 这一阶段完成后的下一步 + +当这一阶段做完,下一步才适合正式进入: + +`C++ Render Graph + URP-like Renderer Layer` + +到那时再接: + +- `Render Graph` +- `Universal Renderer` +- `Renderer Feature` +- `C# Custom Pipeline` + +顺序才是对的。 diff --git a/engine/include/XCEngine/Rendering/Execution/ScenePhase.h b/engine/include/XCEngine/Rendering/Execution/ScenePhase.h index 08c63745..835a4004 100644 --- a/engine/include/XCEngine/Rendering/Execution/ScenePhase.h +++ b/engine/include/XCEngine/Rendering/Execution/ScenePhase.h @@ -8,7 +8,6 @@ namespace Rendering { enum class ScenePhase : Core::uint32 { Opaque = 0, Skybox = 1, - Feature = 2, Transparent = 3, EditorExtension = 4, PostProcess = 5, @@ -21,8 +20,6 @@ inline const char* ToString(ScenePhase scenePhase) { return "Opaque"; case ScenePhase::Skybox: return "Skybox"; - case ScenePhase::Feature: - return "Feature"; case ScenePhase::Transparent: return "Transparent"; case ScenePhase::EditorExtension: diff --git a/engine/include/XCEngine/Rendering/Execution/SceneRenderInjectionPoint.h b/engine/include/XCEngine/Rendering/Execution/SceneRenderInjectionPoint.h new file mode 100644 index 00000000..6801a76f --- /dev/null +++ b/engine/include/XCEngine/Rendering/Execution/SceneRenderInjectionPoint.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +namespace XCEngine { +namespace Rendering { + +enum class SceneRenderInjectionPoint : Core::uint32 { + BeforeOpaque = 0, + AfterOpaque = 1, + BeforeSkybox = 2, + AfterSkybox = 3, + BeforeTransparent = 4, + AfterTransparent = 5 +}; + +inline const char* ToString(SceneRenderInjectionPoint injectionPoint) { + switch (injectionPoint) { + case SceneRenderInjectionPoint::BeforeOpaque: + return "BeforeOpaque"; + case SceneRenderInjectionPoint::AfterOpaque: + return "AfterOpaque"; + case SceneRenderInjectionPoint::BeforeSkybox: + return "BeforeSkybox"; + case SceneRenderInjectionPoint::AfterSkybox: + return "AfterSkybox"; + case SceneRenderInjectionPoint::BeforeTransparent: + return "BeforeTransparent"; + case SceneRenderInjectionPoint::AfterTransparent: + return "AfterTransparent"; + } + + return "Unknown"; +} + +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h b/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h index 4f9c2f23..5d533f1f 100644 --- a/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h +++ b/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -31,21 +30,12 @@ public: RenderPipeline* GetPipeline() const { return m_cameraRenderer.GetPipeline(); } const RenderPipelineAsset* GetPipelineAsset() const { return m_cameraRenderer.GetPipelineAsset(); } - // Legacy compatibility adapters retained for callers that still consume CameraRenderRequest. - std::vector BuildRenderRequests( - const Components::Scene& scene, - Components::CameraComponent* overrideCamera, - const RenderContext& context, - const RenderSurface& surface); std::vector BuildFramePlans( const Components::Scene& scene, Components::CameraComponent* overrideCamera, const RenderContext& context, const RenderSurface& surface); - // Legacy compatibility adapters retained for callers that still submit CameraRenderRequest. - bool Render(const CameraRenderRequest& request); - bool Render(const std::vector& requests); bool Render(const CameraFramePlan& plan); bool Render(const std::vector& plans); bool Render( @@ -54,10 +44,6 @@ public: const RenderContext& context, const RenderSurface& surface); -private: - std::vector CreateFramePlansFromLegacyRequests( - const std::vector& requests) const; - SceneRenderRequestPlanner m_requestPlanner; CameraRenderer m_cameraRenderer; std::unique_ptr m_framePlanBuilder; diff --git a/engine/include/XCEngine/Rendering/Passes/BuiltinGaussianSplatPass.h b/engine/include/XCEngine/Rendering/Features/BuiltinGaussianSplatPass.h similarity index 98% rename from engine/include/XCEngine/Rendering/Passes/BuiltinGaussianSplatPass.h rename to engine/include/XCEngine/Rendering/Features/BuiltinGaussianSplatPass.h index 606c879e..fee841d6 100644 --- a/engine/include/XCEngine/Rendering/Passes/BuiltinGaussianSplatPass.h +++ b/engine/include/XCEngine/Rendering/Features/BuiltinGaussianSplatPass.h @@ -32,19 +32,22 @@ class Shader; namespace Rendering { struct VisibleGaussianSplatItem; -namespace Passes { +namespace Features { namespace Internal { class BuiltinGaussianSplatPassResources; } // namespace Internal -} // namespace Passes +} // namespace Features -namespace Passes { +namespace Features { class BuiltinGaussianSplatPass final : public SceneRenderFeaturePass { public: ~BuiltinGaussianSplatPass() override; const char* GetName() const override; + SceneRenderInjectionPoint GetInjectionPoint() const override { + return SceneRenderInjectionPoint::BeforeTransparent; + } bool Initialize(const RenderContext& context) override; bool IsActive(const RenderSceneData& sceneData) const override; bool Prepare( @@ -304,6 +307,6 @@ private: std::unordered_map m_dynamicDescriptorSets; }; -} // namespace Passes +} // namespace Features } // namespace Rendering } // namespace XCEngine diff --git a/engine/include/XCEngine/Rendering/Passes/BuiltinVolumetricPass.h b/engine/include/XCEngine/Rendering/Features/BuiltinVolumetricPass.h similarity index 98% rename from engine/include/XCEngine/Rendering/Passes/BuiltinVolumetricPass.h rename to engine/include/XCEngine/Rendering/Features/BuiltinVolumetricPass.h index 95c86466..dc03a88a 100644 --- a/engine/include/XCEngine/Rendering/Passes/BuiltinVolumetricPass.h +++ b/engine/include/XCEngine/Rendering/Features/BuiltinVolumetricPass.h @@ -31,7 +31,7 @@ namespace Rendering { struct VisibleVolumeItem; struct RenderLightingData; -namespace Passes { +namespace Features { class BuiltinVolumetricPass final : public SceneRenderFeaturePass { public: @@ -40,6 +40,9 @@ public: static RHI::InputLayoutDesc BuildInputLayout(); const char* GetName() const override; + SceneRenderInjectionPoint GetInjectionPoint() const override { + return SceneRenderInjectionPoint::BeforeTransparent; + } bool Initialize(const RenderContext& context) override; bool IsActive(const RenderSceneData& sceneData) const override; bool Prepare( @@ -226,6 +229,6 @@ private: std::unordered_map m_dynamicDescriptorSets; }; -} // namespace Passes +} // namespace Features } // namespace Rendering } // namespace XCEngine diff --git a/engine/src/Rendering/Passes/BuiltinGaussianSplatPass.cpp b/engine/src/Rendering/Features/BuiltinGaussianSplatPass.cpp similarity index 99% rename from engine/src/Rendering/Passes/BuiltinGaussianSplatPass.cpp rename to engine/src/Rendering/Features/BuiltinGaussianSplatPass.cpp index 7715832c..347dc8c2 100644 --- a/engine/src/Rendering/Passes/BuiltinGaussianSplatPass.cpp +++ b/engine/src/Rendering/Features/BuiltinGaussianSplatPass.cpp @@ -1,4 +1,4 @@ -#include "Rendering/Passes/BuiltinGaussianSplatPass.h" +#include "Rendering/Features/BuiltinGaussianSplatPass.h" #include "Components/GameObject.h" #include "Debug/Logger.h" @@ -9,7 +9,7 @@ #include "Rendering/FrameData/VisibleGaussianSplatItem.h" #include "Rendering/Internal/RenderSurfacePipelineUtils.h" #include "Rendering/Internal/ShaderVariantUtils.h" -#include "Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.h" +#include "Rendering/Features/Internal/BuiltinGaussianSplatPassResources.h" #include "Rendering/RenderSurface.h" #include "Resources/BuiltinResources.h" #include "Resources/GaussianSplat/GaussianSplat.h" @@ -18,7 +18,7 @@ namespace XCEngine { namespace Rendering { -namespace Passes { +namespace Features { namespace { @@ -1789,6 +1789,6 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat( return true; } -} // namespace Passes +} // namespace Features } // namespace Rendering } // namespace XCEngine diff --git a/engine/src/Rendering/Passes/BuiltinVolumetricPass.cpp b/engine/src/Rendering/Features/BuiltinVolumetricPass.cpp similarity index 99% rename from engine/src/Rendering/Passes/BuiltinVolumetricPass.cpp rename to engine/src/Rendering/Features/BuiltinVolumetricPass.cpp index 61c696ff..21c15476 100644 --- a/engine/src/Rendering/Passes/BuiltinVolumetricPass.cpp +++ b/engine/src/Rendering/Features/BuiltinVolumetricPass.cpp @@ -1,4 +1,4 @@ -#include "Rendering/Passes/BuiltinVolumetricPass.h" +#include "Rendering/Features/BuiltinVolumetricPass.h" #include "Components/GameObject.h" #include "Core/Asset/ResourceManager.h" @@ -23,7 +23,7 @@ namespace XCEngine { namespace Rendering { -namespace Passes { +namespace Features { namespace { @@ -811,6 +811,6 @@ bool BuiltinVolumetricPass::DrawVisibleVolume( return true; } -} // namespace Passes +} // namespace Features } // namespace Rendering } // namespace XCEngine diff --git a/engine/src/Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.cpp b/engine/src/Rendering/Features/Internal/BuiltinGaussianSplatPassResources.cpp similarity index 99% rename from engine/src/Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.cpp rename to engine/src/Rendering/Features/Internal/BuiltinGaussianSplatPassResources.cpp index bfa1e0f4..098f7386 100644 --- a/engine/src/Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.cpp +++ b/engine/src/Rendering/Features/Internal/BuiltinGaussianSplatPassResources.cpp @@ -1,4 +1,4 @@ -#include "Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.h" +#include "Rendering/Features/Internal/BuiltinGaussianSplatPassResources.h" #include "Components/GaussianSplatRendererComponent.h" #include "Debug/Logger.h" @@ -6,7 +6,7 @@ namespace XCEngine { namespace Rendering { -namespace Passes { +namespace Features { namespace Internal { namespace { @@ -391,6 +391,6 @@ Core::uint32 BuiltinGaussianSplatPassResources::ComputeSortCapacity(Core::uint32 } } // namespace Internal -} // namespace Passes +} // namespace Features } // namespace Rendering } // namespace XCEngine diff --git a/engine/src/Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.h b/engine/src/Rendering/Features/Internal/BuiltinGaussianSplatPassResources.h similarity index 98% rename from engine/src/Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.h rename to engine/src/Rendering/Features/Internal/BuiltinGaussianSplatPassResources.h index 824c6999..c1630b38 100644 --- a/engine/src/Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.h +++ b/engine/src/Rendering/Features/Internal/BuiltinGaussianSplatPassResources.h @@ -11,7 +11,7 @@ namespace XCEngine { namespace Rendering { -namespace Passes { +namespace Features { namespace Internal { struct GaussianSplatViewData { @@ -106,6 +106,6 @@ private: }; } // namespace Internal -} // namespace Passes +} // namespace Features } // namespace Rendering } // namespace XCEngine diff --git a/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp b/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp index 0a5048ac..735ed316 100644 --- a/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp +++ b/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp @@ -1,676 +1,36 @@ #include "Rendering/Pipelines/BuiltinForwardPipeline.h" -#include "Debug/Logger.h" -#include "Core/Asset/ResourceManager.h" -#include "RHI/RHICommandList.h" -#include "Rendering/Internal/RenderSurfacePipelineUtils.h" -#include "Rendering/Passes/BuiltinGaussianSplatPass.h" -#include "Rendering/Materials/RenderMaterialResolve.h" -#include "Rendering/Passes/BuiltinVolumetricPass.h" -#include "Rendering/RenderSurface.h" -#include "Resources/BuiltinResources.h" - -#include -#include +#include "Rendering/Pipelines/Internal/BuiltinForwardSceneSetup.h" namespace XCEngine { namespace Rendering { namespace Pipelines { -namespace { - -bool IsDepthFormat(RHI::Format format) { - return format == RHI::Format::D24_UNorm_S8_UInt || - format == RHI::Format::D32_Float; -} - -bool ShouldSampleMainDirectionalShadowMap(const RenderSceneData& sceneData) { - return sceneData.lighting.HasMainDirectionalShadow() && - sceneData.lighting.mainDirectionalShadow.shadowMap != nullptr && - IsDepthFormat(sceneData.lighting.mainDirectionalShadow.shadowMap->GetFormat()); -} - -void TransitionMainDirectionalShadowForSampling( - const RenderContext& context, - const RenderSceneData& sceneData) { - context.commandList->TransitionBarrier( - sceneData.lighting.mainDirectionalShadow.shadowMap, - RHI::ResourceStates::DepthWrite, - RHI::ResourceStates::PixelShaderResource); -} - -void RestoreMainDirectionalShadowAfterSampling( - const RenderContext& context, - const RenderSceneData& sceneData) { - context.commandList->TransitionBarrier( - sceneData.lighting.mainDirectionalShadow.shadowMap, - RHI::ResourceStates::PixelShaderResource, - RHI::ResourceStates::DepthWrite); -} - -std::vector CollectSurfaceColorAttachments(const RenderSurface& surface) { - std::vector renderTargets; - const Core::uint32 colorAttachmentCount = - ::XCEngine::Rendering::Internal::ResolveSurfaceColorAttachmentCount(surface); - renderTargets.reserve(colorAttachmentCount); - for (Core::uint32 attachmentIndex = 0; attachmentIndex < colorAttachmentCount; ++attachmentIndex) { - RHI::RHIResourceView* renderTarget = surface.GetColorAttachments()[attachmentIndex]; - if (renderTarget == nullptr) { - break; - } - - renderTargets.push_back(renderTarget); - } - - return renderTargets; -} - -} // namespace BuiltinForwardPipeline::BuiltinForwardPipeline() { - m_gaussianSplatPass = std::make_unique(); - m_volumetricPass = std::make_unique(); + Internal::RegisterBuiltinForwardSceneFeatures(m_forwardSceneFeatureHost); } BuiltinForwardPipeline::~BuiltinForwardPipeline() { Shutdown(); } +void BuiltinForwardPipeline::AddForwardSceneFeaturePass( + std::unique_ptr featurePass) { + m_forwardSceneFeatureHost.AddFeaturePass(std::move(featurePass)); +} + +size_t BuiltinForwardPipeline::GetForwardSceneFeaturePassCount() const { + return m_forwardSceneFeatureHost.GetFeaturePassCount(); +} + +SceneRenderFeaturePass* BuiltinForwardPipeline::GetForwardSceneFeaturePass(size_t index) const { + return m_forwardSceneFeatureHost.GetFeaturePass(index); +} + std::unique_ptr BuiltinForwardPipelineAsset::CreatePipeline() const { return std::make_unique(); } -RHI::InputLayoutDesc BuiltinForwardPipeline::BuildInputLayout() { - RHI::InputLayoutDesc inputLayout = {}; - - RHI::InputElementDesc position = {}; - position.semanticName = "POSITION"; - position.semanticIndex = 0; - position.format = static_cast(RHI::Format::R32G32B32_Float); - position.inputSlot = 0; - position.alignedByteOffset = 0; - inputLayout.elements.push_back(position); - - RHI::InputElementDesc normal = {}; - normal.semanticName = "NORMAL"; - normal.semanticIndex = 0; - normal.format = static_cast(RHI::Format::R32G32B32_Float); - normal.inputSlot = 0; - normal.alignedByteOffset = static_cast(offsetof(Resources::StaticMeshVertex, normal)); - inputLayout.elements.push_back(normal); - - RHI::InputElementDesc texcoord = {}; - texcoord.semanticName = "TEXCOORD"; - texcoord.semanticIndex = 0; - texcoord.format = static_cast(RHI::Format::R32G32_Float); - texcoord.inputSlot = 0; - texcoord.alignedByteOffset = static_cast(offsetof(Resources::StaticMeshVertex, uv0)); - inputLayout.elements.push_back(texcoord); - - RHI::InputElementDesc backTexcoord = {}; - backTexcoord.semanticName = "TEXCOORD"; - backTexcoord.semanticIndex = 1; - backTexcoord.format = static_cast(RHI::Format::R32G32_Float); - backTexcoord.inputSlot = 0; - backTexcoord.alignedByteOffset = static_cast(offsetof(Resources::StaticMeshVertex, uv1)); - inputLayout.elements.push_back(backTexcoord); - - RHI::InputElementDesc tangent = {}; - tangent.semanticName = "TEXCOORD"; - tangent.semanticIndex = 2; - tangent.format = static_cast(RHI::Format::R32G32B32_Float); - tangent.inputSlot = 0; - tangent.alignedByteOffset = static_cast(offsetof(Resources::StaticMeshVertex, tangent)); - inputLayout.elements.push_back(tangent); - - RHI::InputElementDesc bitangent = {}; - bitangent.semanticName = "TEXCOORD"; - bitangent.semanticIndex = 3; - bitangent.format = static_cast(RHI::Format::R32G32B32_Float); - bitangent.inputSlot = 0; - bitangent.alignedByteOffset = static_cast(offsetof(Resources::StaticMeshVertex, bitangent)); - inputLayout.elements.push_back(bitangent); - - RHI::InputElementDesc color = {}; - color.semanticName = "COLOR"; - color.semanticIndex = 0; - color.format = static_cast(RHI::Format::R32G32B32A32_Float); - color.inputSlot = 0; - color.alignedByteOffset = static_cast(offsetof(Resources::StaticMeshVertex, color)); - inputLayout.elements.push_back(color); - - return inputLayout; -} - -bool BuiltinForwardPipeline::Initialize(const RenderContext& context) { - return EnsureInitialized(context) && - InitializeForwardSceneFeaturePasses(context); -} - -void BuiltinForwardPipeline::Shutdown() { - ShutdownForwardSceneFeaturePasses(); - DestroyPipelineResources(); -} - -bool BuiltinForwardPipeline::Render( - const FrameExecutionContext& executionContext) { - if (!Initialize(executionContext.renderContext)) { - Debug::Logger::Get().Error( - Debug::LogCategory::Rendering, - "BuiltinForwardPipeline::Render failed: Initialize returned false"); - return false; - } - - if (!PrepareForwardSceneFeaturePasses(executionContext)) { - Debug::Logger::Get().Error( - Debug::LogCategory::Rendering, - "BuiltinForwardPipeline::Render failed: PrepareForwardSceneFeaturePasses returned false"); - return false; - } - - const RenderPassContext passContext = BuildRenderPassContext(executionContext); - - if (!BeginForwardScenePass(passContext)) { - Debug::Logger::Get().Error( - Debug::LogCategory::Rendering, - "BuiltinForwardPipeline::Render failed: BeginForwardScenePass returned false"); - return false; - } - - const bool sampledDirectionalShadow = - ShouldSampleMainDirectionalShadowMap(executionContext.sceneData); - if (sampledDirectionalShadow) { - TransitionMainDirectionalShadowForSampling( - executionContext.renderContext, - executionContext.sceneData); - } - - const bool renderResult = ExecuteForwardScene(executionContext); - - if (sampledDirectionalShadow) { - RestoreMainDirectionalShadowAfterSampling( - executionContext.renderContext, - executionContext.sceneData); - } - EndForwardScenePass(passContext); - - return renderResult; -} - -bool BuiltinForwardPipeline::Render( - const RenderContext& context, - const RenderSurface& surface, - const RenderSceneData& sceneData) { - return Render(FrameExecutionContext(context, surface, sceneData)); -} - -bool BuiltinForwardPipeline::BeginForwardScenePass(const RenderPassContext& passContext) { - const RenderContext& context = passContext.renderContext; - const RenderSurface& surface = passContext.surface; - const RenderSceneData& sceneData = passContext.sceneData; - RHI::RHIResourceView* depthAttachment = surface.GetDepthAttachment(); - - std::vector renderTargets = CollectSurfaceColorAttachments(surface); - if (renderTargets.empty()) { - return false; - } - - const Math::RectInt renderArea = surface.GetRenderArea(); - if (renderArea.width <= 0 || renderArea.height <= 0) { - return false; - } - - RHI::RHICommandList* commandList = context.commandList; - if (surface.IsAutoTransitionEnabled()) { - for (RHI::RHIResourceView* renderTarget : renderTargets) { - if (renderTarget != nullptr) { - commandList->TransitionBarrier( - renderTarget, - surface.GetColorStateBefore(), - RHI::ResourceStates::RenderTarget); - } - } - - if (depthAttachment != nullptr) { - commandList->TransitionBarrier( - depthAttachment, - surface.GetDepthStateBefore(), - RHI::ResourceStates::DepthWrite); - } - } - - commandList->SetRenderTargets( - static_cast(renderTargets.size()), - renderTargets.data(), - depthAttachment); - - const RHI::Viewport viewport = { - static_cast(renderArea.x), - static_cast(renderArea.y), - static_cast(renderArea.width), - static_cast(renderArea.height), - 0.0f, - 1.0f - }; - const RHI::Rect scissorRect = { - renderArea.x, - renderArea.y, - renderArea.x + renderArea.width, - renderArea.y + renderArea.height - }; - const RHI::Rect clearRects[] = { scissorRect }; - commandList->SetViewport(viewport); - commandList->SetScissorRect(scissorRect); - - const Math::Color clearColor = surface.HasClearColorOverride() - ? surface.GetClearColorOverride() - : sceneData.cameraData.clearColor; - const float clearValues[4] = { clearColor.r, clearColor.g, clearColor.b, clearColor.a }; - if (HasRenderClearFlag(sceneData.cameraData.clearFlags, RenderClearFlags::Color) && - !HasSkybox(sceneData)) { - for (RHI::RHIResourceView* renderTarget : renderTargets) { - if (renderTarget != nullptr) { - commandList->ClearRenderTarget(renderTarget, clearValues, 1, clearRects); - } - } - } - if (depthAttachment != nullptr && - HasRenderClearFlag(sceneData.cameraData.clearFlags, RenderClearFlags::Depth)) { - commandList->ClearDepthStencil(depthAttachment, 1.0f, 0, 1, clearRects); - } - - commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList); - return true; -} - -void BuiltinForwardPipeline::EndForwardScenePass(const RenderPassContext& passContext) { - const RenderContext& context = passContext.renderContext; - const RenderSurface& surface = passContext.surface; - RHI::RHIResourceView* depthAttachment = surface.GetDepthAttachment(); - std::vector renderTargets = CollectSurfaceColorAttachments(surface); - RHI::RHICommandList* commandList = context.commandList; - - commandList->EndRenderPass(); - if (!surface.IsAutoTransitionEnabled()) { - return; - } - - for (RHI::RHIResourceView* renderTarget : renderTargets) { - if (renderTarget != nullptr) { - commandList->TransitionBarrier( - renderTarget, - RHI::ResourceStates::RenderTarget, - surface.GetColorStateAfter()); - } - } - - if (depthAttachment != nullptr) { - commandList->TransitionBarrier( - depthAttachment, - RHI::ResourceStates::DepthWrite, - surface.GetDepthStateAfter()); - } -} - -bool BuiltinForwardPipeline::ExecuteForwardOpaquePass( - const ScenePhaseExecutionContext& executionContext) { - return DrawVisibleItems( - executionContext.frameContext, - BuildDrawSettings(executionContext.scenePhase)); -} - -bool BuiltinForwardPipeline::ExecuteForwardTransparentPass( - const ScenePhaseExecutionContext& executionContext) { - return DrawVisibleItems( - executionContext.frameContext, - BuildDrawSettings(executionContext.scenePhase)); -} - -bool BuiltinForwardPipeline::EnsureInitialized(const RenderContext& context) { - if (!context.IsValid()) { - return false; - } - - if (m_initialized && - m_device == context.device && - m_backendType == context.backendType) { - return true; - } - - DestroyPipelineResources(); - m_device = context.device; - m_backendType = context.backendType; - m_initialized = CreatePipelineResources(context); - return m_initialized; -} - -BuiltinForwardPipeline::ForwardSceneFeaturePassArray -BuiltinForwardPipeline::CollectForwardSceneFeaturePasses() const { - return { - m_gaussianSplatPass.get(), - m_volumetricPass.get() - }; -} - -bool BuiltinForwardPipeline::InitializeForwardSceneFeaturePasses(const RenderContext& context) { - for (SceneRenderFeaturePass* featurePass : CollectForwardSceneFeaturePasses()) { - if (featurePass == nullptr || !featurePass->Initialize(context)) { - return false; - } - } - - return true; -} - -void BuiltinForwardPipeline::ShutdownForwardSceneFeaturePasses() { - for (SceneRenderFeaturePass* featurePass : CollectForwardSceneFeaturePasses()) { - if (featurePass != nullptr) { - featurePass->Shutdown(); - } - } -} - -bool BuiltinForwardPipeline::PrepareForwardSceneFeaturePasses( - const FrameExecutionContext& executionContext) const { - for (SceneRenderFeaturePass* featurePass : CollectForwardSceneFeaturePasses()) { - if (featurePass == nullptr || !featurePass->IsActive(executionContext.sceneData)) { - continue; - } - - if (!featurePass->Prepare( - executionContext.renderContext, - executionContext.sceneData)) { - Debug::Logger::Get().Error( - Debug::LogCategory::Rendering, - (Containers::String("BuiltinForwardPipeline feature prepare failed: ") + - featurePass->GetName()).CStr()); - return false; - } - } - - return true; -} - -bool BuiltinForwardPipeline::ExecuteForwardSceneFeaturePasses( - const ScenePhaseExecutionContext& executionContext) const { - const RenderPassContext passContext = BuildRenderPassContext(executionContext); - - for (SceneRenderFeaturePass* featurePass : CollectForwardSceneFeaturePasses()) { - if (featurePass == nullptr || - !featurePass->IsActive(executionContext.frameContext.sceneData)) { - continue; - } - - if (!featurePass->Execute(passContext)) { - Debug::Logger::Get().Error( - Debug::LogCategory::Rendering, - (Containers::String("BuiltinForwardPipeline feature execute failed: ") + - featurePass->GetName()).CStr()); - return false; - } - } - - return true; -} - -ScenePhaseExecutionContext BuiltinForwardPipeline::BuildScenePhaseExecutionContext( - const FrameExecutionContext& executionContext, - ScenePhase scenePhase) const { - return ScenePhaseExecutionContext( - executionContext, - scenePhase, - ShouldSampleMainDirectionalShadowMap(executionContext.sceneData)); -} - -DrawSettings BuiltinForwardPipeline::BuildDrawSettings(ScenePhase scenePhase) const { - DrawSettings drawSettings = {}; - drawSettings.scenePhase = scenePhase; - switch (scenePhase) { - case ScenePhase::Opaque: - drawSettings.rendererListType = RendererListType::Opaque; - break; - case ScenePhase::Transparent: - drawSettings.rendererListType = RendererListType::Transparent; - break; - default: - drawSettings.rendererListType = RendererListType::AllVisible; - break; - } - - return drawSettings; -} - -bool BuiltinForwardPipeline::ExecuteScenePhase( - const ScenePhaseExecutionContext& executionContext) { - switch (executionContext.scenePhase) { - case ScenePhase::Opaque: - return ExecuteForwardOpaquePass(executionContext); - case ScenePhase::Skybox: - return ExecuteForwardSkyboxPass(BuildRenderPassContext(executionContext)); - case ScenePhase::Feature: - return ExecuteForwardSceneFeaturePasses(executionContext); - case ScenePhase::Transparent: - return ExecuteForwardTransparentPass(executionContext); - default: - Debug::Logger::Get().Error( - Debug::LogCategory::Rendering, - (Containers::String("BuiltinForwardPipeline::ExecuteScenePhase does not support scene phase: ") + - ToString(executionContext.scenePhase)).CStr()); - return false; - } -} - -bool BuiltinForwardPipeline::ExecuteForwardScene( - const FrameExecutionContext& executionContext) { - static constexpr ScenePhase kForwardScenePhases[] = { - ScenePhase::Opaque, - ScenePhase::Skybox, - ScenePhase::Feature, - ScenePhase::Transparent - }; - - for (ScenePhase scenePhase : kForwardScenePhases) { - const ScenePhaseExecutionContext scenePhaseExecutionContext = - BuildScenePhaseExecutionContext(executionContext, scenePhase); - if (!ExecuteScenePhase(scenePhaseExecutionContext)) { - Debug::Logger::Get().Error( - Debug::LogCategory::Rendering, - (Containers::String("BuiltinForwardPipeline::ExecuteForwardScene failed during phase: ") + - ToString(scenePhase)).CStr()); - return false; - } - } - - return true; -} - -bool BuiltinForwardPipeline::CreatePipelineResources(const RenderContext& context) { - m_builtinForwardShader = Resources::ResourceManager::Get().Load( - Resources::GetBuiltinForwardLitShaderPath()); - if (!m_builtinForwardShader.IsValid()) { - Debug::Logger::Get().Error( - Debug::LogCategory::Rendering, - "BuiltinForwardPipeline failed to load builtin forward shader resource"); - return false; - } - - m_builtinUnlitShader = Resources::ResourceManager::Get().Load( - Resources::GetBuiltinUnlitShaderPath()); - if (!m_builtinUnlitShader.IsValid()) { - Debug::Logger::Get().Error( - Debug::LogCategory::Rendering, - "BuiltinForwardPipeline failed to load builtin unlit shader resource"); - return false; - } - - m_builtinSkyboxShader = Resources::ResourceManager::Get().Load( - Resources::GetBuiltinSkyboxShaderPath()); - if (!m_builtinSkyboxShader.IsValid()) { - Debug::Logger::Get().Error( - Debug::LogCategory::Rendering, - "BuiltinForwardPipeline failed to load builtin skybox shader resource"); - return false; - } - - RHI::SamplerDesc samplerDesc = {}; - samplerDesc.filter = static_cast(RHI::FilterMode::Linear); - samplerDesc.addressU = static_cast(RHI::TextureAddressMode::Clamp); - samplerDesc.addressV = static_cast(RHI::TextureAddressMode::Clamp); - samplerDesc.addressW = static_cast(RHI::TextureAddressMode::Clamp); - samplerDesc.mipLodBias = 0.0f; - samplerDesc.maxAnisotropy = 1; - samplerDesc.comparisonFunc = static_cast(RHI::ComparisonFunc::Always); - samplerDesc.minLod = 0.0f; - samplerDesc.maxLod = 1000.0f; - m_sampler = context.device->CreateSampler(samplerDesc); - if (m_sampler == nullptr) { - return false; - } - - RHI::SamplerDesc shadowSamplerDesc = samplerDesc; - shadowSamplerDesc.filter = static_cast(RHI::FilterMode::Point); - m_shadowSampler = context.device->CreateSampler(shadowSamplerDesc); - if (m_shadowSampler == nullptr) { - return false; - } - - const unsigned char whitePixels2D[4] = { - 255, 255, 255, 255 - }; - RHI::TextureDesc fallback2DDesc = {}; - fallback2DDesc.width = 1; - fallback2DDesc.height = 1; - fallback2DDesc.depth = 1; - fallback2DDesc.mipLevels = 1; - fallback2DDesc.arraySize = 1; - fallback2DDesc.format = static_cast(RHI::Format::R8G8B8A8_UNorm); - fallback2DDesc.textureType = static_cast(RHI::TextureType::Texture2D); - fallback2DDesc.sampleCount = 1; - fallback2DDesc.sampleQuality = 0; - fallback2DDesc.flags = 0; - m_fallbackTexture2D = context.device->CreateTexture( - fallback2DDesc, - whitePixels2D, - sizeof(whitePixels2D), - sizeof(whitePixels2D)); - if (m_fallbackTexture2D == nullptr) { - return false; - } - - RHI::ResourceViewDesc fallback2DViewDesc = {}; - fallback2DViewDesc.format = static_cast(RHI::Format::R8G8B8A8_UNorm); - fallback2DViewDesc.dimension = RHI::ResourceViewDimension::Texture2D; - fallback2DViewDesc.mipLevel = 0; - m_fallbackTexture2DView = context.device->CreateShaderResourceView(m_fallbackTexture2D, fallback2DViewDesc); - if (m_fallbackTexture2DView == nullptr) { - return false; - } - - const unsigned char whitePixelsCube[6 * 4] = { - 255, 255, 255, 255, - 255, 255, 255, 255, - 255, 255, 255, 255, - 255, 255, 255, 255, - 255, 255, 255, 255, - 255, 255, 255, 255 - }; - RHI::TextureDesc fallbackCubeDesc = {}; - fallbackCubeDesc.width = 1; - fallbackCubeDesc.height = 1; - fallbackCubeDesc.depth = 1; - fallbackCubeDesc.mipLevels = 1; - fallbackCubeDesc.arraySize = 6; - fallbackCubeDesc.format = static_cast(RHI::Format::R8G8B8A8_UNorm); - fallbackCubeDesc.textureType = static_cast(RHI::TextureType::TextureCube); - fallbackCubeDesc.sampleCount = 1; - fallbackCubeDesc.sampleQuality = 0; - fallbackCubeDesc.flags = 0; - m_fallbackTextureCube = context.device->CreateTexture( - fallbackCubeDesc, - whitePixelsCube, - sizeof(whitePixelsCube), - 4); - if (m_fallbackTextureCube == nullptr) { - return false; - } - - RHI::ResourceViewDesc fallbackCubeViewDesc = {}; - fallbackCubeViewDesc.format = static_cast(RHI::Format::R8G8B8A8_UNorm); - fallbackCubeViewDesc.dimension = RHI::ResourceViewDimension::TextureCube; - fallbackCubeViewDesc.mipLevel = 0; - m_fallbackTextureCubeView = context.device->CreateShaderResourceView(m_fallbackTextureCube, fallbackCubeViewDesc); - if (m_fallbackTextureCubeView == nullptr) { - return false; - } - - return true; -} - -void BuiltinForwardPipeline::DestroyPipelineResources() { - m_resourceCache.Shutdown(); - - for (auto& pipelinePair : m_pipelineStates) { - if (pipelinePair.second != nullptr) { - pipelinePair.second->Shutdown(); - delete pipelinePair.second; - } - } - m_pipelineStates.clear(); - - for (auto& descriptorSetPair : m_dynamicDescriptorSets) { - DestroyOwnedDescriptorSet(descriptorSetPair.second.descriptorSet); - } - m_dynamicDescriptorSets.clear(); - - for (auto& passLayoutPair : m_passResourceLayouts) { - DestroyPassResourceLayout(passLayoutPair.second); - } - m_passResourceLayouts.clear(); - - DestroySkyboxResources(); - - if (m_fallbackTextureCubeView != nullptr) { - m_fallbackTextureCubeView->Shutdown(); - delete m_fallbackTextureCubeView; - m_fallbackTextureCubeView = nullptr; - } - - if (m_fallbackTextureCube != nullptr) { - m_fallbackTextureCube->Shutdown(); - delete m_fallbackTextureCube; - m_fallbackTextureCube = nullptr; - } - - if (m_fallbackTexture2DView != nullptr) { - m_fallbackTexture2DView->Shutdown(); - delete m_fallbackTexture2DView; - m_fallbackTexture2DView = nullptr; - } - - if (m_fallbackTexture2D != nullptr) { - m_fallbackTexture2D->Shutdown(); - delete m_fallbackTexture2D; - m_fallbackTexture2D = nullptr; - } - - if (m_sampler != nullptr) { - m_sampler->Shutdown(); - delete m_sampler; - m_sampler = nullptr; - } - - if (m_shadowSampler != nullptr) { - m_shadowSampler->Shutdown(); - delete m_shadowSampler; - m_shadowSampler = nullptr; - } - - m_device = nullptr; - m_initialized = false; - m_builtinForwardShader.Reset(); - m_builtinUnlitShader.Reset(); - m_builtinSkyboxShader.Reset(); -} - } // namespace Pipelines } // namespace Rendering } // namespace XCEngine diff --git a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineLifecycle.cpp b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineLifecycle.cpp new file mode 100644 index 00000000..10172e6b --- /dev/null +++ b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineLifecycle.cpp @@ -0,0 +1,229 @@ +#include "Rendering/Pipelines/BuiltinForwardPipeline.h" + +#include "Debug/Logger.h" +#include "Core/Asset/ResourceManager.h" +#include "Resources/BuiltinResources.h" + +namespace XCEngine { +namespace Rendering { +namespace Pipelines { + +bool BuiltinForwardPipeline::Initialize(const RenderContext& context) { + return EnsureInitialized(context) && + m_forwardSceneFeatureHost.Initialize(context); +} + +void BuiltinForwardPipeline::Shutdown() { + m_forwardSceneFeatureHost.Shutdown(); + DestroyPipelineResources(); +} + +bool BuiltinForwardPipeline::EnsureInitialized(const RenderContext& context) { + if (!context.IsValid()) { + return false; + } + + if (m_initialized && + m_device == context.device && + m_backendType == context.backendType) { + return true; + } + + DestroyPipelineResources(); + m_device = context.device; + m_backendType = context.backendType; + m_initialized = CreatePipelineResources(context); + return m_initialized; +} + +bool BuiltinForwardPipeline::CreatePipelineResources(const RenderContext& context) { + m_builtinForwardShader = Resources::ResourceManager::Get().Load( + Resources::GetBuiltinForwardLitShaderPath()); + if (!m_builtinForwardShader.IsValid()) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinForwardPipeline failed to load builtin forward shader resource"); + return false; + } + + m_builtinUnlitShader = Resources::ResourceManager::Get().Load( + Resources::GetBuiltinUnlitShaderPath()); + if (!m_builtinUnlitShader.IsValid()) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinForwardPipeline failed to load builtin unlit shader resource"); + return false; + } + + m_builtinSkyboxShader = Resources::ResourceManager::Get().Load( + Resources::GetBuiltinSkyboxShaderPath()); + if (!m_builtinSkyboxShader.IsValid()) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinForwardPipeline failed to load builtin skybox shader resource"); + return false; + } + + RHI::SamplerDesc samplerDesc = {}; + samplerDesc.filter = static_cast(RHI::FilterMode::Linear); + samplerDesc.addressU = static_cast(RHI::TextureAddressMode::Clamp); + samplerDesc.addressV = static_cast(RHI::TextureAddressMode::Clamp); + samplerDesc.addressW = static_cast(RHI::TextureAddressMode::Clamp); + samplerDesc.mipLodBias = 0.0f; + samplerDesc.maxAnisotropy = 1; + samplerDesc.comparisonFunc = static_cast(RHI::ComparisonFunc::Always); + samplerDesc.minLod = 0.0f; + samplerDesc.maxLod = 1000.0f; + m_sampler = context.device->CreateSampler(samplerDesc); + if (m_sampler == nullptr) { + return false; + } + + RHI::SamplerDesc shadowSamplerDesc = samplerDesc; + shadowSamplerDesc.filter = static_cast(RHI::FilterMode::Point); + m_shadowSampler = context.device->CreateSampler(shadowSamplerDesc); + if (m_shadowSampler == nullptr) { + return false; + } + + const unsigned char whitePixels2D[4] = { + 255, 255, 255, 255 + }; + RHI::TextureDesc fallback2DDesc = {}; + fallback2DDesc.width = 1; + fallback2DDesc.height = 1; + fallback2DDesc.depth = 1; + fallback2DDesc.mipLevels = 1; + fallback2DDesc.arraySize = 1; + fallback2DDesc.format = static_cast(RHI::Format::R8G8B8A8_UNorm); + fallback2DDesc.textureType = static_cast(RHI::TextureType::Texture2D); + fallback2DDesc.sampleCount = 1; + fallback2DDesc.sampleQuality = 0; + fallback2DDesc.flags = 0; + m_fallbackTexture2D = context.device->CreateTexture( + fallback2DDesc, + whitePixels2D, + sizeof(whitePixels2D), + sizeof(whitePixels2D)); + if (m_fallbackTexture2D == nullptr) { + return false; + } + + RHI::ResourceViewDesc fallback2DViewDesc = {}; + fallback2DViewDesc.format = static_cast(RHI::Format::R8G8B8A8_UNorm); + fallback2DViewDesc.dimension = RHI::ResourceViewDimension::Texture2D; + fallback2DViewDesc.mipLevel = 0; + m_fallbackTexture2DView = context.device->CreateShaderResourceView(m_fallbackTexture2D, fallback2DViewDesc); + if (m_fallbackTexture2DView == nullptr) { + return false; + } + + const unsigned char whitePixelsCube[6 * 4] = { + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255 + }; + RHI::TextureDesc fallbackCubeDesc = {}; + fallbackCubeDesc.width = 1; + fallbackCubeDesc.height = 1; + fallbackCubeDesc.depth = 1; + fallbackCubeDesc.mipLevels = 1; + fallbackCubeDesc.arraySize = 6; + fallbackCubeDesc.format = static_cast(RHI::Format::R8G8B8A8_UNorm); + fallbackCubeDesc.textureType = static_cast(RHI::TextureType::TextureCube); + fallbackCubeDesc.sampleCount = 1; + fallbackCubeDesc.sampleQuality = 0; + fallbackCubeDesc.flags = 0; + m_fallbackTextureCube = context.device->CreateTexture( + fallbackCubeDesc, + whitePixelsCube, + sizeof(whitePixelsCube), + 4); + if (m_fallbackTextureCube == nullptr) { + return false; + } + + RHI::ResourceViewDesc fallbackCubeViewDesc = {}; + fallbackCubeViewDesc.format = static_cast(RHI::Format::R8G8B8A8_UNorm); + fallbackCubeViewDesc.dimension = RHI::ResourceViewDimension::TextureCube; + fallbackCubeViewDesc.mipLevel = 0; + m_fallbackTextureCubeView = context.device->CreateShaderResourceView(m_fallbackTextureCube, fallbackCubeViewDesc); + if (m_fallbackTextureCubeView == nullptr) { + return false; + } + + return true; +} + +void BuiltinForwardPipeline::DestroyPipelineResources() { + m_resourceCache.Shutdown(); + + for (auto& pipelinePair : m_pipelineStates) { + if (pipelinePair.second != nullptr) { + pipelinePair.second->Shutdown(); + delete pipelinePair.second; + } + } + m_pipelineStates.clear(); + + for (auto& descriptorSetPair : m_dynamicDescriptorSets) { + DestroyOwnedDescriptorSet(descriptorSetPair.second.descriptorSet); + } + m_dynamicDescriptorSets.clear(); + + for (auto& passLayoutPair : m_passResourceLayouts) { + DestroyPassResourceLayout(passLayoutPair.second); + } + m_passResourceLayouts.clear(); + + DestroySkyboxResources(); + + if (m_fallbackTextureCubeView != nullptr) { + m_fallbackTextureCubeView->Shutdown(); + delete m_fallbackTextureCubeView; + m_fallbackTextureCubeView = nullptr; + } + + if (m_fallbackTextureCube != nullptr) { + m_fallbackTextureCube->Shutdown(); + delete m_fallbackTextureCube; + m_fallbackTextureCube = nullptr; + } + + if (m_fallbackTexture2DView != nullptr) { + m_fallbackTexture2DView->Shutdown(); + delete m_fallbackTexture2DView; + m_fallbackTexture2DView = nullptr; + } + + if (m_fallbackTexture2D != nullptr) { + m_fallbackTexture2D->Shutdown(); + delete m_fallbackTexture2D; + m_fallbackTexture2D = nullptr; + } + + if (m_sampler != nullptr) { + m_sampler->Shutdown(); + delete m_sampler; + m_sampler = nullptr; + } + + if (m_shadowSampler != nullptr) { + m_shadowSampler->Shutdown(); + delete m_shadowSampler; + m_shadowSampler = nullptr; + } + + m_device = nullptr; + m_initialized = false; + m_builtinForwardShader.Reset(); + m_builtinUnlitShader.Reset(); + m_builtinSkyboxShader.Reset(); +} + +} // namespace Pipelines +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineResources.cpp b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineResources.cpp index 6c03ea39..d4ac74dd 100644 --- a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineResources.cpp +++ b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineResources.cpp @@ -1,156 +1,16 @@ #include "Rendering/Pipelines/BuiltinForwardPipeline.h" -#include "Components/GameObject.h" #include "Debug/Logger.h" -#include "RHI/RHICommandList.h" #include "RHI/RHIDevice.h" #include "Rendering/Builtin/BuiltinPassLayoutUtils.h" -#include "Rendering/FrameData/RendererListUtils.h" -#include "Rendering/Internal/RenderSurfacePipelineUtils.h" -#include "Rendering/Internal/ShaderVariantUtils.h" #include "Rendering/Materials/RenderMaterialResolve.h" -#include "Rendering/Materials/RenderMaterialStateUtils.h" #include "Resources/Material/Material.h" #include "Resources/Shader/Shader.h" #include "Resources/Texture/Texture.h" -#include -#include - namespace XCEngine { namespace Rendering { namespace Pipelines { -namespace { - -constexpr float kForwardAmbientIntensity = 0.28f; -constexpr float kSpotInnerAngleRatio = 0.8f; - -Resources::ShaderKeywordSet ResolvePassKeywordSet( - const RenderSceneData& sceneData, - const Resources::Material* material) { - return Resources::CombineShaderKeywordSets( - sceneData.globalShaderKeywords, - material != nullptr ? material->GetKeywordSet() : Resources::ShaderKeywordSet()); -} - -const Resources::ShaderPass* FindCompatibleSurfacePass( - const Resources::Shader& shader, - const RenderSceneData& sceneData, - const Resources::Material* material, - BuiltinMaterialPass pass, - Resources::ShaderBackend backend) { - const Resources::ShaderKeywordSet keywordSet = ResolvePassKeywordSet(sceneData, material); - - for (const Resources::ShaderPass& shaderPass : shader.GetPasses()) { - if (ShaderPassMatchesBuiltinPass(shaderPass, pass) && - ::XCEngine::Rendering::Internal::ShaderPassHasGraphicsVariants( - shader, - shaderPass.name, - backend, - keywordSet)) { - return &shaderPass; - } - } - - return nullptr; -} - -RHI::GraphicsPipelineDesc CreatePipelineDesc( - RHI::RHIType backendType, - RHI::RHIPipelineLayout* pipelineLayout, - const Resources::Shader& shader, - const Resources::ShaderPass& shaderPass, - const Containers::String& passName, - const Resources::ShaderKeywordSet& keywordSet, - const Resources::Material* material, - const RenderSurface& surface) { - RHI::GraphicsPipelineDesc pipelineDesc = {}; - pipelineDesc.pipelineLayout = pipelineLayout; - pipelineDesc.topologyType = static_cast(RHI::PrimitiveTopologyType::Triangle); - ::XCEngine::Rendering::Internal::ApplySurfacePropertiesToGraphicsPipelineDesc(surface, pipelineDesc); - ApplyResolvedRenderState(&shaderPass, material, pipelineDesc); - - pipelineDesc.inputLayout = BuiltinForwardPipeline::BuildInputLayout(); - - const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(backendType); - const Resources::ShaderStageVariant* vertexVariant = - shader.FindVariant(passName, Resources::ShaderType::Vertex, backend, keywordSet); - const Resources::ShaderStageVariant* fragmentVariant = - shader.FindVariant(passName, Resources::ShaderType::Fragment, backend, keywordSet); - if (vertexVariant != nullptr) { - ::XCEngine::Rendering::Internal::ApplyShaderStageVariant( - shader.GetPath(), - shaderPass, - backend, - *vertexVariant, - pipelineDesc.vertexShader); - } - if (fragmentVariant != nullptr) { - ::XCEngine::Rendering::Internal::ApplyShaderStageVariant( - shader.GetPath(), - shaderPass, - backend, - *fragmentVariant, - pipelineDesc.fragmentShader); - } - - return pipelineDesc; -} - -} // namespace - -bool BuiltinForwardPipeline::TryResolveSurfacePassType( - const Resources::Material* material, - BuiltinMaterialPass& outPass) { - if (MatchesBuiltinPass(material, BuiltinMaterialPass::Unlit)) { - outPass = BuiltinMaterialPass::Unlit; - return true; - } - - if (MatchesBuiltinPass(material, BuiltinMaterialPass::ForwardLit)) { - outPass = BuiltinMaterialPass::ForwardLit; - return true; - } - - return false; -} - -BuiltinForwardPipeline::ResolvedShaderPass BuiltinForwardPipeline::ResolveSurfaceShaderPass( - const RenderSceneData& sceneData, - const Resources::Material* material) const { - ResolvedShaderPass resolved = {}; - BuiltinMaterialPass pass = BuiltinMaterialPass::ForwardLit; - if (!TryResolveSurfacePassType(material, pass)) { - return resolved; - } - - const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(m_backendType); - - if (material != nullptr && material->GetShader() != nullptr) { - const Resources::Shader* materialShader = material->GetShader(); - if (const Resources::ShaderPass* shaderPass = - FindCompatibleSurfacePass(*materialShader, sceneData, material, pass, backend)) { - resolved.shader = materialShader; - resolved.pass = shaderPass; - resolved.passName = shaderPass->name; - return resolved; - } - } - - const Resources::ResourceHandle* builtinShaderHandle = - pass == BuiltinMaterialPass::Unlit ? &m_builtinUnlitShader : &m_builtinForwardShader; - if (builtinShaderHandle->IsValid()) { - const Resources::Shader* builtinShader = builtinShaderHandle->Get(); - if (const Resources::ShaderPass* shaderPass = - FindCompatibleSurfacePass(*builtinShader, sceneData, material, pass, backend)) { - resolved.shader = builtinShader; - resolved.pass = shaderPass; - resolved.passName = shaderPass->name; - } - } - - return resolved; -} BuiltinForwardPipeline::PassResourceLayout* BuiltinForwardPipeline::GetOrCreatePassResourceLayout( const RenderContext& context, @@ -234,71 +94,6 @@ BuiltinForwardPipeline::PassResourceLayout* BuiltinForwardPipeline::GetOrCreateP return &storedPassLayout; } -RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState( - const RenderContext& context, - const RenderSurface& surface, - const RenderSceneData& sceneData, - const Resources::Material* material) { - const Resources::ShaderKeywordSet keywordSet = ResolvePassKeywordSet(sceneData, material); - const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(sceneData, material); - if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { - Debug::Logger::Get().Error( - Debug::LogCategory::Rendering, - "BuiltinForwardPipeline could not resolve a valid surface shader pass"); - return nullptr; - } - - PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass); - if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) { - return nullptr; - } - - PipelineStateKey pipelineKey = {}; - pipelineKey.renderState = - BuildStaticPipelineRenderStateKey(ResolveEffectiveRenderState(resolvedShaderPass.pass, material)); - pipelineKey.shader = resolvedShaderPass.shader; - pipelineKey.passName = resolvedShaderPass.passName; - pipelineKey.keywordSignature = ::XCEngine::Rendering::Internal::BuildShaderKeywordSignature(keywordSet); - pipelineKey.renderTargetCount = - ::XCEngine::Rendering::Internal::ResolveSurfaceColorAttachmentCount(surface); - pipelineKey.renderTargetFormats = - ::XCEngine::Rendering::Internal::ResolveSurfaceColorFormats(surface); - pipelineKey.depthStencilFormat = - static_cast(::XCEngine::Rendering::Internal::ResolveSurfaceDepthFormat(surface)); - pipelineKey.sampleCount = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(surface); - pipelineKey.sampleQuality = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleQuality(surface); - - const auto existing = m_pipelineStates.find(pipelineKey); - if (existing != m_pipelineStates.end()) { - return existing->second; - } - - const RHI::GraphicsPipelineDesc pipelineDesc = - CreatePipelineDesc( - context.backendType, - passLayout->pipelineLayout, - *resolvedShaderPass.shader, - *resolvedShaderPass.pass, - resolvedShaderPass.passName, - keywordSet, - material, - surface); - RHI::RHIPipelineState* pipelineState = context.device->CreatePipelineState(pipelineDesc); - if (pipelineState == nullptr || !pipelineState->IsValid()) { - Debug::Logger::Get().Error( - Debug::LogCategory::Rendering, - "BuiltinForwardPipeline failed to create pipeline state"); - if (pipelineState != nullptr) { - pipelineState->Shutdown(); - delete pipelineState; - } - return nullptr; - } - - m_pipelineStates.emplace(pipelineKey, pipelineState); - return pipelineState; -} - bool BuiltinForwardPipeline::CreateOwnedDescriptorSet( const BuiltinPassSetLayoutMetadata& setLayout, OwnedDescriptorSet& descriptorSet) { @@ -562,32 +357,6 @@ void BuiltinForwardPipeline::DestroyPassResourceLayout(PassResourceLayout& passL passLayout.shadowMapSampler = {}; } -const Resources::Texture* BuiltinForwardPipeline::ResolveTexture(const Resources::Material* material) const { - return ResolveBuiltinBaseColorTexture(material); -} - -RHI::RHIResourceView* BuiltinForwardPipeline::ResolveTextureView( - const Resources::Texture* texture) { - if (texture == nullptr) { - return nullptr; - } - - const RenderResourceCache::CachedTexture* cachedTexture = m_resourceCache.GetOrCreateTexture(m_device, texture); - if (cachedTexture != nullptr && cachedTexture->shaderResourceView != nullptr) { - return cachedTexture->shaderResourceView; - } - - return nullptr; -} - -RHI::RHIResourceView* BuiltinForwardPipeline::ResolveTextureView( - const VisibleRenderItem& visibleItem) { - const Resources::Material* material = ResolveMaterial(visibleItem); - const Resources::Texture* texture = ResolveTexture(material); - RHI::RHIResourceView* textureView = ResolveTextureView(texture); - return textureView != nullptr ? textureView : m_fallbackTexture2DView; -} - RHI::RHIResourceView* BuiltinForwardPipeline::ResolveMaterialTextureView( const Resources::Material* material, const BuiltinPassResourceBindingDesc& binding) { @@ -615,287 +384,6 @@ RHI::RHIResourceView* BuiltinForwardPipeline::ResolveMaterialTextureView( return textureView != nullptr ? textureView : fallbackView; } -BuiltinForwardPipeline::LightingConstants BuiltinForwardPipeline::BuildLightingConstants( - const RenderLightingData& lightingData) { - LightingConstants lightingConstants = {}; - - if (lightingData.HasMainDirectionalLight()) { - lightingConstants.mainLightDirectionAndIntensity = Math::Vector4( - lightingData.mainDirectionalLight.direction.x, - lightingData.mainDirectionalLight.direction.y, - lightingData.mainDirectionalLight.direction.z, - lightingData.mainDirectionalLight.intensity); - lightingConstants.mainLightColorAndFlags = Math::Vector4( - lightingData.mainDirectionalLight.color.r, - lightingData.mainDirectionalLight.color.g, - lightingData.mainDirectionalLight.color.b, - 1.0f); - } - - const Core::uint32 additionalLightCount = std::min( - lightingData.additionalLightCount, - kMaxLightingAdditionalLightCount); - lightingConstants.lightingParams = Math::Vector4( - static_cast(additionalLightCount), - kForwardAmbientIntensity, - 0.0f, - 0.0f); - - for (Core::uint32 index = 0; index < additionalLightCount; ++index) { - lightingConstants.additionalLights[index] = - BuildAdditionalLightConstants(lightingData.additionalLights[index]); - } - - return lightingConstants; -} - -BuiltinForwardPipeline::AdditionalLightConstants BuiltinForwardPipeline::BuildAdditionalLightConstants( - const RenderAdditionalLightData& lightData) { - AdditionalLightConstants constants = {}; - constants.colorAndIntensity = Math::Vector4( - lightData.color.r, - lightData.color.g, - lightData.color.b, - lightData.intensity); - constants.positionAndRange = Math::Vector4( - lightData.position.x, - lightData.position.y, - lightData.position.z, - lightData.range); - constants.directionAndType = Math::Vector4( - lightData.direction.x, - lightData.direction.y, - lightData.direction.z, - static_cast(lightData.type)); - - if (lightData.type == RenderLightType::Spot) { - const float outerHalfAngleRadians = Math::Radians(lightData.spotAngle * 0.5f); - const float innerHalfAngleRadians = outerHalfAngleRadians * kSpotInnerAngleRatio; - constants.spotAnglesAndFlags = Math::Vector4( - std::cos(outerHalfAngleRadians), - std::cos(innerHalfAngleRadians), - lightData.castsShadows ? 1.0f : 0.0f, - 0.0f); - } else { - constants.spotAnglesAndFlags = Math::Vector4( - 0.0f, - 0.0f, - lightData.castsShadows ? 1.0f : 0.0f, - 0.0f); - } - - return constants; -} - -bool BuiltinForwardPipeline::DrawVisibleItem( - const FrameExecutionContext& executionContext, - const VisibleRenderItem& visibleItem) { - const RenderContext& context = executionContext.renderContext; - const RenderSurface& surface = executionContext.surface; - const RenderSceneData& sceneData = executionContext.sceneData; - - (void)surface; - const RenderResourceCache::CachedMesh* cachedMesh = m_resourceCache.GetOrCreateMesh(m_device, visibleItem.mesh); - if (cachedMesh == nullptr || cachedMesh->vertexBufferView == nullptr) { - return false; - } - - RHI::RHICommandList* commandList = context.commandList; - - RHI::RHIResourceView* vertexBuffers[] = { cachedMesh->vertexBufferView }; - const uint64_t offsets[] = { 0 }; - const uint32_t strides[] = { cachedMesh->vertexStride }; - commandList->SetVertexBuffers(0, 1, vertexBuffers, offsets, strides); - if (cachedMesh->indexBufferView != nullptr) { - commandList->SetIndexBuffer(cachedMesh->indexBufferView, 0); - } - - const PerObjectConstants constants = { - sceneData.cameraData.projection, - sceneData.cameraData.view, - visibleItem.localToWorld.Transpose(), - visibleItem.localToWorld.Inverse() - }; - const LightingConstants lightingConstants = BuildLightingConstants(sceneData.lighting); - ShadowReceiverConstants shadowReceiverConstants = {}; - if (sceneData.lighting.HasMainDirectionalShadow()) { - shadowReceiverConstants.worldToShadow = sceneData.lighting.mainDirectionalShadow.viewProjection; - shadowReceiverConstants.shadowMapMetrics = sceneData.lighting.mainDirectionalShadow.mapMetrics; - shadowReceiverConstants.shadowSampling = sceneData.lighting.mainDirectionalShadow.sampling; - } - - const Resources::Material* material = ResolveMaterial(visibleItem); - const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(sceneData, material); - if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { - return false; - } - const Resources::MaterialRenderState effectiveRenderState = - ResolveEffectiveRenderState(resolvedShaderPass.pass, material); - - PassLayoutKey passLayoutKey = {}; - passLayoutKey.shader = resolvedShaderPass.shader; - passLayoutKey.passName = resolvedShaderPass.passName; - - PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass); - if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) { - return false; - } - - RHI::RHIResourceView* baseColorTextureView = ResolveTextureView(visibleItem); - if (passLayout->baseColorTexture.IsValid() && baseColorTextureView == nullptr) { - return false; - } - - RHI::RHIResourceView* shadowMapTextureView = sceneData.lighting.HasMainDirectionalShadow() - ? sceneData.lighting.mainDirectionalShadow.shadowMap - : m_fallbackTexture2DView; - if (passLayout->shadowMapTexture.IsValid() && shadowMapTextureView == nullptr) { - return false; - } - - MaterialConstantPayloadView materialConstants = ResolveSchemaMaterialConstantPayload(material); - if (passLayout->material.IsValid() && !materialConstants.IsValid()) { - Debug::Logger::Get().Error( - Debug::LogCategory::Rendering, - "BuiltinForwardPipeline requires a schema-backed material constant payload"); - return false; - } - - if (passLayout->descriptorSetCount > 0) { - std::vector descriptorSets(passLayout->descriptorSetCount, nullptr); - - for (Core::uint32 descriptorOffset = 0; descriptorOffset < passLayout->descriptorSetCount; ++descriptorOffset) { - const Core::uint32 setIndex = passLayout->firstDescriptorSet + descriptorOffset; - if (setIndex >= passLayout->setLayouts.size()) { - return false; - } - - const BuiltinPassSetLayoutMetadata& setLayout = passLayout->setLayouts[setIndex]; - RHI::RHIDescriptorSet* descriptorSet = nullptr; - - if (setLayout.usesPerObject || - setLayout.usesLighting || - setLayout.usesMaterial || - setLayout.usesShadowReceiver || - setLayout.usesTexture || - setLayout.usesMaterialBuffers) { - const Core::uint64 objectId = - (setLayout.usesPerObject && visibleItem.gameObject != nullptr) - ? visibleItem.gameObject->GetID() - : 0; - const Resources::Material* materialKey = - (setLayout.usesMaterial || - setLayout.usesBaseColorTexture || - setLayout.usesMaterialBuffers || - setLayout.usesMaterialTextures) - ? material - : nullptr; - - CachedDescriptorSet* cachedDescriptorSet = GetOrCreateDynamicDescriptorSet( - passLayoutKey, - *passLayout, - setLayout, - setIndex, - objectId, - materialKey, - materialConstants, - lightingConstants, - shadowReceiverConstants, - baseColorTextureView, - shadowMapTextureView); - if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) { - return false; - } - - descriptorSet = cachedDescriptorSet->descriptorSet.set; - if (setLayout.usesPerObject) { - if (!passLayout->perObject.IsValid() || passLayout->perObject.set != setIndex) { - return false; - } - - descriptorSet->WriteConstant( - passLayout->perObject.binding, - &constants, - sizeof(constants)); - } - } else { - descriptorSet = GetOrCreateStaticDescriptorSet(*passLayout, setIndex); - if (descriptorSet == nullptr) { - return false; - } - } - - descriptorSets[descriptorOffset] = descriptorSet; - } - - commandList->SetGraphicsDescriptorSets( - passLayout->firstDescriptorSet, - passLayout->descriptorSetCount, - descriptorSets.data(), - passLayout->pipelineLayout); - } - - ApplyDynamicRenderState(effectiveRenderState, *commandList); - - if (visibleItem.hasSection) { - const Containers::Array& sections = visibleItem.mesh->GetSections(); - if (visibleItem.sectionIndex >= sections.Size()) { - return false; - } - - const Resources::MeshSection& section = sections[visibleItem.sectionIndex]; - if (cachedMesh->indexBufferView != nullptr && section.indexCount > 0) { - // MeshLoader flattens section indices into a single global index buffer. - commandList->DrawIndexed(section.indexCount, 1, section.startIndex, 0, 0); - } else if (section.vertexCount > 0) { - commandList->Draw(section.vertexCount, 1, section.baseVertex, 0); - } - - return true; - } - - if (cachedMesh->indexBufferView != nullptr && cachedMesh->indexCount > 0) { - commandList->DrawIndexed(cachedMesh->indexCount, 1, 0, 0, 0); - } else if (cachedMesh->vertexCount > 0) { - commandList->Draw(cachedMesh->vertexCount, 1, 0, 0); - } - - return true; -} - -bool BuiltinForwardPipeline::DrawVisibleItems( - const FrameExecutionContext& executionContext, - const DrawSettings& drawSettings) { - const RenderContext& context = executionContext.renderContext; - const RenderSurface& surface = executionContext.surface; - const RenderSceneData& sceneData = executionContext.sceneData; - - RHI::RHICommandList* commandList = context.commandList; - RHI::RHIPipelineState* currentPipelineState = nullptr; - - auto drawVisibleItem = [&](const VisibleRenderItem& visibleItem) { - const Resources::Material* material = ResolveMaterial(visibleItem); - BuiltinMaterialPass pass = BuiltinMaterialPass::ForwardLit; - if (!TryResolveSurfacePassType(material, pass)) { - return; - } - - RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, surface, sceneData, material); - if (pipelineState == nullptr) { - return; - } - if (pipelineState != currentPipelineState) { - commandList->SetPipelineState(pipelineState); - currentPipelineState = pipelineState; - } - - DrawVisibleItem(executionContext, visibleItem); - }; - - VisitRendererListVisibleItems(sceneData, drawSettings.rendererListType, drawVisibleItem); - return true; -} - } // namespace Pipelines } // namespace Rendering } // namespace XCEngine diff --git a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineSurface.cpp b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineSurface.cpp new file mode 100644 index 00000000..32e0c297 --- /dev/null +++ b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineSurface.cpp @@ -0,0 +1,615 @@ +#include "Rendering/Pipelines/BuiltinForwardPipeline.h" + +#include "Components/GameObject.h" +#include "Debug/Logger.h" +#include "RHI/RHICommandList.h" +#include "RHI/RHIDevice.h" +#include "Rendering/FrameData/RendererListUtils.h" +#include "Rendering/Internal/RenderSurfacePipelineUtils.h" +#include "Rendering/Internal/ShaderVariantUtils.h" +#include "Rendering/Materials/RenderMaterialResolve.h" +#include "Rendering/Materials/RenderMaterialStateUtils.h" +#include "Resources/Material/Material.h" +#include "Resources/Shader/Shader.h" +#include "Resources/Texture/Texture.h" + +#include +#include +#include + +namespace XCEngine { +namespace Rendering { +namespace Pipelines { +namespace { + +constexpr float kForwardAmbientIntensity = 0.28f; +constexpr float kSpotInnerAngleRatio = 0.8f; + +Resources::ShaderKeywordSet ResolvePassKeywordSet( + const RenderSceneData& sceneData, + const Resources::Material* material) { + return Resources::CombineShaderKeywordSets( + sceneData.globalShaderKeywords, + material != nullptr ? material->GetKeywordSet() : Resources::ShaderKeywordSet()); +} + +const Resources::ShaderPass* FindCompatibleSurfacePass( + const Resources::Shader& shader, + const RenderSceneData& sceneData, + const Resources::Material* material, + BuiltinMaterialPass pass, + Resources::ShaderBackend backend) { + const Resources::ShaderKeywordSet keywordSet = ResolvePassKeywordSet(sceneData, material); + + for (const Resources::ShaderPass& shaderPass : shader.GetPasses()) { + if (ShaderPassMatchesBuiltinPass(shaderPass, pass) && + ::XCEngine::Rendering::Internal::ShaderPassHasGraphicsVariants( + shader, + shaderPass.name, + backend, + keywordSet)) { + return &shaderPass; + } + } + + return nullptr; +} + +RHI::GraphicsPipelineDesc CreatePipelineDesc( + RHI::RHIType backendType, + RHI::RHIPipelineLayout* pipelineLayout, + const Resources::Shader& shader, + const Resources::ShaderPass& shaderPass, + const Containers::String& passName, + const Resources::ShaderKeywordSet& keywordSet, + const Resources::Material* material, + const RenderSurface& surface) { + RHI::GraphicsPipelineDesc pipelineDesc = {}; + pipelineDesc.pipelineLayout = pipelineLayout; + pipelineDesc.topologyType = static_cast(RHI::PrimitiveTopologyType::Triangle); + ::XCEngine::Rendering::Internal::ApplySurfacePropertiesToGraphicsPipelineDesc(surface, pipelineDesc); + ApplyResolvedRenderState(&shaderPass, material, pipelineDesc); + + pipelineDesc.inputLayout = BuiltinForwardPipeline::BuildInputLayout(); + + const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(backendType); + const Resources::ShaderStageVariant* vertexVariant = + shader.FindVariant(passName, Resources::ShaderType::Vertex, backend, keywordSet); + const Resources::ShaderStageVariant* fragmentVariant = + shader.FindVariant(passName, Resources::ShaderType::Fragment, backend, keywordSet); + if (vertexVariant != nullptr) { + ::XCEngine::Rendering::Internal::ApplyShaderStageVariant( + shader.GetPath(), + shaderPass, + backend, + *vertexVariant, + pipelineDesc.vertexShader); + } + if (fragmentVariant != nullptr) { + ::XCEngine::Rendering::Internal::ApplyShaderStageVariant( + shader.GetPath(), + shaderPass, + backend, + *fragmentVariant, + pipelineDesc.fragmentShader); + } + + return pipelineDesc; +} + +bool UsesDynamicSurfaceDescriptorSet(const BuiltinPassSetLayoutMetadata& setLayout) { + return setLayout.usesPerObject || + setLayout.usesLighting || + setLayout.usesMaterial || + setLayout.usesShadowReceiver || + setLayout.usesTexture || + setLayout.usesMaterialBuffers; +} + +} // namespace + +RHI::InputLayoutDesc BuiltinForwardPipeline::BuildInputLayout() { + RHI::InputLayoutDesc inputLayout = {}; + + RHI::InputElementDesc position = {}; + position.semanticName = "POSITION"; + position.semanticIndex = 0; + position.format = static_cast(RHI::Format::R32G32B32_Float); + position.inputSlot = 0; + position.alignedByteOffset = 0; + inputLayout.elements.push_back(position); + + RHI::InputElementDesc normal = {}; + normal.semanticName = "NORMAL"; + normal.semanticIndex = 0; + normal.format = static_cast(RHI::Format::R32G32B32_Float); + normal.inputSlot = 0; + normal.alignedByteOffset = static_cast(offsetof(Resources::StaticMeshVertex, normal)); + inputLayout.elements.push_back(normal); + + RHI::InputElementDesc texcoord = {}; + texcoord.semanticName = "TEXCOORD"; + texcoord.semanticIndex = 0; + texcoord.format = static_cast(RHI::Format::R32G32_Float); + texcoord.inputSlot = 0; + texcoord.alignedByteOffset = static_cast(offsetof(Resources::StaticMeshVertex, uv0)); + inputLayout.elements.push_back(texcoord); + + RHI::InputElementDesc backTexcoord = {}; + backTexcoord.semanticName = "TEXCOORD"; + backTexcoord.semanticIndex = 1; + backTexcoord.format = static_cast(RHI::Format::R32G32_Float); + backTexcoord.inputSlot = 0; + backTexcoord.alignedByteOffset = static_cast(offsetof(Resources::StaticMeshVertex, uv1)); + inputLayout.elements.push_back(backTexcoord); + + RHI::InputElementDesc tangent = {}; + tangent.semanticName = "TEXCOORD"; + tangent.semanticIndex = 2; + tangent.format = static_cast(RHI::Format::R32G32B32_Float); + tangent.inputSlot = 0; + tangent.alignedByteOffset = static_cast(offsetof(Resources::StaticMeshVertex, tangent)); + inputLayout.elements.push_back(tangent); + + RHI::InputElementDesc bitangent = {}; + bitangent.semanticName = "TEXCOORD"; + bitangent.semanticIndex = 3; + bitangent.format = static_cast(RHI::Format::R32G32B32_Float); + bitangent.inputSlot = 0; + bitangent.alignedByteOffset = static_cast(offsetof(Resources::StaticMeshVertex, bitangent)); + inputLayout.elements.push_back(bitangent); + + RHI::InputElementDesc color = {}; + color.semanticName = "COLOR"; + color.semanticIndex = 0; + color.format = static_cast(RHI::Format::R32G32B32A32_Float); + color.inputSlot = 0; + color.alignedByteOffset = static_cast(offsetof(Resources::StaticMeshVertex, color)); + inputLayout.elements.push_back(color); + + return inputLayout; +} + +bool BuiltinForwardPipeline::TryResolveSurfacePassType( + const Resources::Material* material, + BuiltinMaterialPass& outPass) { + if (MatchesBuiltinPass(material, BuiltinMaterialPass::Unlit)) { + outPass = BuiltinMaterialPass::Unlit; + return true; + } + + if (MatchesBuiltinPass(material, BuiltinMaterialPass::ForwardLit)) { + outPass = BuiltinMaterialPass::ForwardLit; + return true; + } + + return false; +} + +BuiltinForwardPipeline::ResolvedShaderPass BuiltinForwardPipeline::ResolveSurfaceShaderPass( + const RenderSceneData& sceneData, + const Resources::Material* material) const { + ResolvedShaderPass resolved = {}; + BuiltinMaterialPass pass = BuiltinMaterialPass::ForwardLit; + if (!TryResolveSurfacePassType(material, pass)) { + return resolved; + } + + const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(m_backendType); + + if (material != nullptr && material->GetShader() != nullptr) { + const Resources::Shader* materialShader = material->GetShader(); + if (const Resources::ShaderPass* shaderPass = + FindCompatibleSurfacePass(*materialShader, sceneData, material, pass, backend)) { + resolved.shader = materialShader; + resolved.pass = shaderPass; + resolved.passName = shaderPass->name; + return resolved; + } + } + + const Resources::ResourceHandle* builtinShaderHandle = + pass == BuiltinMaterialPass::Unlit ? &m_builtinUnlitShader : &m_builtinForwardShader; + if (builtinShaderHandle->IsValid()) { + const Resources::Shader* builtinShader = builtinShaderHandle->Get(); + if (const Resources::ShaderPass* shaderPass = + FindCompatibleSurfacePass(*builtinShader, sceneData, material, pass, backend)) { + resolved.shader = builtinShader; + resolved.pass = shaderPass; + resolved.passName = shaderPass->name; + } + } + + return resolved; +} + +RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState( + const RenderContext& context, + const RenderSurface& surface, + const RenderSceneData& sceneData, + const Resources::Material* material) { + const Resources::ShaderKeywordSet keywordSet = ResolvePassKeywordSet(sceneData, material); + const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(sceneData, material); + if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinForwardPipeline could not resolve a valid surface shader pass"); + return nullptr; + } + + PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass); + if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) { + return nullptr; + } + + PipelineStateKey pipelineKey = {}; + pipelineKey.renderState = + BuildStaticPipelineRenderStateKey(ResolveEffectiveRenderState(resolvedShaderPass.pass, material)); + pipelineKey.shader = resolvedShaderPass.shader; + pipelineKey.passName = resolvedShaderPass.passName; + pipelineKey.keywordSignature = ::XCEngine::Rendering::Internal::BuildShaderKeywordSignature(keywordSet); + pipelineKey.renderTargetCount = + ::XCEngine::Rendering::Internal::ResolveSurfaceColorAttachmentCount(surface); + pipelineKey.renderTargetFormats = + ::XCEngine::Rendering::Internal::ResolveSurfaceColorFormats(surface); + pipelineKey.depthStencilFormat = + static_cast(::XCEngine::Rendering::Internal::ResolveSurfaceDepthFormat(surface)); + pipelineKey.sampleCount = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(surface); + pipelineKey.sampleQuality = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleQuality(surface); + + const auto existing = m_pipelineStates.find(pipelineKey); + if (existing != m_pipelineStates.end()) { + return existing->second; + } + + const RHI::GraphicsPipelineDesc pipelineDesc = + CreatePipelineDesc( + context.backendType, + passLayout->pipelineLayout, + *resolvedShaderPass.shader, + *resolvedShaderPass.pass, + resolvedShaderPass.passName, + keywordSet, + material, + surface); + RHI::RHIPipelineState* pipelineState = context.device->CreatePipelineState(pipelineDesc); + if (pipelineState == nullptr || !pipelineState->IsValid()) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinForwardPipeline failed to create pipeline state"); + if (pipelineState != nullptr) { + pipelineState->Shutdown(); + delete pipelineState; + } + return nullptr; + } + + m_pipelineStates.emplace(pipelineKey, pipelineState); + return pipelineState; +} + +const Resources::Texture* BuiltinForwardPipeline::ResolveTexture(const Resources::Material* material) const { + return ResolveBuiltinBaseColorTexture(material); +} + +RHI::RHIResourceView* BuiltinForwardPipeline::ResolveTextureView( + const Resources::Texture* texture) { + if (texture == nullptr) { + return nullptr; + } + + const RenderResourceCache::CachedTexture* cachedTexture = m_resourceCache.GetOrCreateTexture(m_device, texture); + if (cachedTexture != nullptr && cachedTexture->shaderResourceView != nullptr) { + return cachedTexture->shaderResourceView; + } + + return nullptr; +} + +RHI::RHIResourceView* BuiltinForwardPipeline::ResolveTextureView( + const VisibleRenderItem& visibleItem) { + const Resources::Material* material = ResolveMaterial(visibleItem); + const Resources::Texture* texture = ResolveTexture(material); + RHI::RHIResourceView* textureView = ResolveTextureView(texture); + return textureView != nullptr ? textureView : m_fallbackTexture2DView; +} + +BuiltinForwardPipeline::LightingConstants BuiltinForwardPipeline::BuildLightingConstants( + const RenderLightingData& lightingData) { + LightingConstants lightingConstants = {}; + + if (lightingData.HasMainDirectionalLight()) { + lightingConstants.mainLightDirectionAndIntensity = Math::Vector4( + lightingData.mainDirectionalLight.direction.x, + lightingData.mainDirectionalLight.direction.y, + lightingData.mainDirectionalLight.direction.z, + lightingData.mainDirectionalLight.intensity); + lightingConstants.mainLightColorAndFlags = Math::Vector4( + lightingData.mainDirectionalLight.color.r, + lightingData.mainDirectionalLight.color.g, + lightingData.mainDirectionalLight.color.b, + 1.0f); + } + + const Core::uint32 additionalLightCount = std::min( + lightingData.additionalLightCount, + kMaxLightingAdditionalLightCount); + lightingConstants.lightingParams = Math::Vector4( + static_cast(additionalLightCount), + kForwardAmbientIntensity, + 0.0f, + 0.0f); + + for (Core::uint32 index = 0; index < additionalLightCount; ++index) { + lightingConstants.additionalLights[index] = + BuildAdditionalLightConstants(lightingData.additionalLights[index]); + } + + return lightingConstants; +} + +BuiltinForwardPipeline::AdditionalLightConstants BuiltinForwardPipeline::BuildAdditionalLightConstants( + const RenderAdditionalLightData& lightData) { + AdditionalLightConstants constants = {}; + constants.colorAndIntensity = Math::Vector4( + lightData.color.r, + lightData.color.g, + lightData.color.b, + lightData.intensity); + constants.positionAndRange = Math::Vector4( + lightData.position.x, + lightData.position.y, + lightData.position.z, + lightData.range); + constants.directionAndType = Math::Vector4( + lightData.direction.x, + lightData.direction.y, + lightData.direction.z, + static_cast(lightData.type)); + + if (lightData.type == RenderLightType::Spot) { + const float outerHalfAngleRadians = Math::Radians(lightData.spotAngle * 0.5f); + const float innerHalfAngleRadians = outerHalfAngleRadians * kSpotInnerAngleRatio; + constants.spotAnglesAndFlags = Math::Vector4( + std::cos(outerHalfAngleRadians), + std::cos(innerHalfAngleRadians), + lightData.castsShadows ? 1.0f : 0.0f, + 0.0f); + } else { + constants.spotAnglesAndFlags = Math::Vector4( + 0.0f, + 0.0f, + lightData.castsShadows ? 1.0f : 0.0f, + 0.0f); + } + + return constants; +} + +bool BuiltinForwardPipeline::ExecuteForwardOpaquePass( + const ScenePhaseExecutionContext& executionContext) { + return DrawVisibleItems( + executionContext.frameContext, + BuildDrawSettings(executionContext.scenePhase)); +} + +bool BuiltinForwardPipeline::ExecuteForwardTransparentPass( + const ScenePhaseExecutionContext& executionContext) { + return DrawVisibleItems( + executionContext.frameContext, + BuildDrawSettings(executionContext.scenePhase)); +} + +bool BuiltinForwardPipeline::DrawVisibleItem( + const FrameExecutionContext& executionContext, + const VisibleRenderItem& visibleItem) { + const RenderContext& context = executionContext.renderContext; + const RenderSceneData& sceneData = executionContext.sceneData; + + const RenderResourceCache::CachedMesh* cachedMesh = m_resourceCache.GetOrCreateMesh(m_device, visibleItem.mesh); + if (cachedMesh == nullptr || cachedMesh->vertexBufferView == nullptr) { + return false; + } + + RHI::RHICommandList* commandList = context.commandList; + + RHI::RHIResourceView* vertexBuffers[] = { cachedMesh->vertexBufferView }; + const uint64_t offsets[] = { 0 }; + const uint32_t strides[] = { cachedMesh->vertexStride }; + commandList->SetVertexBuffers(0, 1, vertexBuffers, offsets, strides); + if (cachedMesh->indexBufferView != nullptr) { + commandList->SetIndexBuffer(cachedMesh->indexBufferView, 0); + } + + const PerObjectConstants constants = { + sceneData.cameraData.projection, + sceneData.cameraData.view, + visibleItem.localToWorld.Transpose(), + visibleItem.localToWorld.Inverse() + }; + const LightingConstants lightingConstants = BuildLightingConstants(sceneData.lighting); + ShadowReceiverConstants shadowReceiverConstants = {}; + if (sceneData.lighting.HasMainDirectionalShadow()) { + shadowReceiverConstants.worldToShadow = sceneData.lighting.mainDirectionalShadow.viewProjection; + shadowReceiverConstants.shadowMapMetrics = sceneData.lighting.mainDirectionalShadow.mapMetrics; + shadowReceiverConstants.shadowSampling = sceneData.lighting.mainDirectionalShadow.sampling; + } + + const Resources::Material* material = ResolveMaterial(visibleItem); + const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(sceneData, material); + if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { + return false; + } + const Resources::MaterialRenderState effectiveRenderState = + ResolveEffectiveRenderState(resolvedShaderPass.pass, material); + + PassLayoutKey passLayoutKey = {}; + passLayoutKey.shader = resolvedShaderPass.shader; + passLayoutKey.passName = resolvedShaderPass.passName; + + PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass); + if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) { + return false; + } + + RHI::RHIResourceView* baseColorTextureView = ResolveTextureView(visibleItem); + if (passLayout->baseColorTexture.IsValid() && baseColorTextureView == nullptr) { + return false; + } + + RHI::RHIResourceView* shadowMapTextureView = sceneData.lighting.HasMainDirectionalShadow() + ? sceneData.lighting.mainDirectionalShadow.shadowMap + : m_fallbackTexture2DView; + if (passLayout->shadowMapTexture.IsValid() && shadowMapTextureView == nullptr) { + return false; + } + + MaterialConstantPayloadView materialConstants = ResolveSchemaMaterialConstantPayload(material); + if (passLayout->material.IsValid() && !materialConstants.IsValid()) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinForwardPipeline requires a schema-backed material constant payload"); + return false; + } + + if (passLayout->descriptorSetCount > 0) { + std::vector descriptorSets(passLayout->descriptorSetCount, nullptr); + + for (Core::uint32 descriptorOffset = 0; descriptorOffset < passLayout->descriptorSetCount; ++descriptorOffset) { + const Core::uint32 setIndex = passLayout->firstDescriptorSet + descriptorOffset; + if (setIndex >= passLayout->setLayouts.size()) { + return false; + } + + const BuiltinPassSetLayoutMetadata& setLayout = passLayout->setLayouts[setIndex]; + RHI::RHIDescriptorSet* descriptorSet = nullptr; + + if (UsesDynamicSurfaceDescriptorSet(setLayout)) { + const Core::uint64 objectId = + (setLayout.usesPerObject && visibleItem.gameObject != nullptr) + ? visibleItem.gameObject->GetID() + : 0; + const Resources::Material* materialKey = + (setLayout.usesMaterial || + setLayout.usesBaseColorTexture || + setLayout.usesMaterialBuffers || + setLayout.usesMaterialTextures) + ? material + : nullptr; + + CachedDescriptorSet* cachedDescriptorSet = GetOrCreateDynamicDescriptorSet( + passLayoutKey, + *passLayout, + setLayout, + setIndex, + objectId, + materialKey, + materialConstants, + lightingConstants, + shadowReceiverConstants, + baseColorTextureView, + shadowMapTextureView); + if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) { + return false; + } + + descriptorSet = cachedDescriptorSet->descriptorSet.set; + if (setLayout.usesPerObject) { + if (!passLayout->perObject.IsValid() || passLayout->perObject.set != setIndex) { + return false; + } + + descriptorSet->WriteConstant( + passLayout->perObject.binding, + &constants, + sizeof(constants)); + } + } else { + descriptorSet = GetOrCreateStaticDescriptorSet(*passLayout, setIndex); + if (descriptorSet == nullptr) { + return false; + } + } + + descriptorSets[descriptorOffset] = descriptorSet; + } + + commandList->SetGraphicsDescriptorSets( + passLayout->firstDescriptorSet, + passLayout->descriptorSetCount, + descriptorSets.data(), + passLayout->pipelineLayout); + } + + ApplyDynamicRenderState(effectiveRenderState, *commandList); + + if (visibleItem.hasSection) { + const Containers::Array& sections = visibleItem.mesh->GetSections(); + if (visibleItem.sectionIndex >= sections.Size()) { + return false; + } + + const Resources::MeshSection& section = sections[visibleItem.sectionIndex]; + if (cachedMesh->indexBufferView != nullptr && section.indexCount > 0) { + // MeshLoader flattens section indices into a single global index buffer. + commandList->DrawIndexed(section.indexCount, 1, section.startIndex, 0, 0); + } else if (section.vertexCount > 0) { + commandList->Draw(section.vertexCount, 1, section.baseVertex, 0); + } + + return true; + } + + if (cachedMesh->indexBufferView != nullptr && cachedMesh->indexCount > 0) { + commandList->DrawIndexed(cachedMesh->indexCount, 1, 0, 0, 0); + } else if (cachedMesh->vertexCount > 0) { + commandList->Draw(cachedMesh->vertexCount, 1, 0, 0); + } + + return true; +} + +bool BuiltinForwardPipeline::DrawVisibleItems( + const FrameExecutionContext& executionContext, + const DrawSettings& drawSettings) { + const RenderContext& context = executionContext.renderContext; + const RenderSurface& surface = executionContext.surface; + const RenderSceneData& sceneData = executionContext.sceneData; + + RHI::RHICommandList* commandList = context.commandList; + RHI::RHIPipelineState* currentPipelineState = nullptr; + bool drawFailed = false; + + auto drawVisibleItem = [&](const VisibleRenderItem& visibleItem) { + if (drawFailed) { + return; + } + + const Resources::Material* material = ResolveMaterial(visibleItem); + BuiltinMaterialPass pass = BuiltinMaterialPass::ForwardLit; + if (!TryResolveSurfacePassType(material, pass)) { + return; + } + + RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, surface, sceneData, material); + if (pipelineState == nullptr) { + drawFailed = true; + return; + } + if (pipelineState != currentPipelineState) { + commandList->SetPipelineState(pipelineState); + currentPipelineState = pipelineState; + } + + if (!DrawVisibleItem(executionContext, visibleItem)) { + drawFailed = true; + } + }; + + VisitRendererListVisibleItems(sceneData, drawSettings.rendererListType, drawVisibleItem); + return !drawFailed; +} + +} // namespace Pipelines +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardSceneSetup.cpp b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardSceneSetup.cpp new file mode 100644 index 00000000..c2a23a84 --- /dev/null +++ b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardSceneSetup.cpp @@ -0,0 +1,54 @@ +#include "Rendering/Pipelines/Internal/BuiltinForwardSceneSetup.h" + +#include "Rendering/Features/BuiltinGaussianSplatPass.h" +#include "Rendering/Features/BuiltinVolumetricPass.h" +#include "Rendering/SceneRenderFeatureHost.h" + +#include + +namespace XCEngine { +namespace Rendering { +namespace Pipelines { +namespace Internal { +namespace { + +constexpr ForwardSceneStep MakeForwardSceneInjectionStep(SceneRenderInjectionPoint injectionPoint) { + ForwardSceneStep step = {}; + step.type = ForwardSceneStepType::InjectionPoint; + step.injectionPoint = injectionPoint; + return step; +} + +constexpr ForwardSceneStep MakeForwardSceneBuiltinPhaseStep(ScenePhase scenePhase) { + ForwardSceneStep step = {}; + step.type = ForwardSceneStepType::BuiltinPhase; + step.scenePhase = scenePhase; + return step; +} + +} // namespace + +const std::array& GetBuiltinForwardSceneSteps() { + static constexpr std::array kForwardSceneSteps = { + MakeForwardSceneInjectionStep(SceneRenderInjectionPoint::BeforeOpaque), + MakeForwardSceneBuiltinPhaseStep(ScenePhase::Opaque), + MakeForwardSceneInjectionStep(SceneRenderInjectionPoint::AfterOpaque), + MakeForwardSceneInjectionStep(SceneRenderInjectionPoint::BeforeSkybox), + MakeForwardSceneBuiltinPhaseStep(ScenePhase::Skybox), + MakeForwardSceneInjectionStep(SceneRenderInjectionPoint::AfterSkybox), + MakeForwardSceneInjectionStep(SceneRenderInjectionPoint::BeforeTransparent), + MakeForwardSceneBuiltinPhaseStep(ScenePhase::Transparent), + MakeForwardSceneInjectionStep(SceneRenderInjectionPoint::AfterTransparent) + }; + return kForwardSceneSteps; +} + +void RegisterBuiltinForwardSceneFeatures(SceneRenderFeatureHost& featureHost) { + featureHost.AddFeaturePass(std::make_unique()); + featureHost.AddFeaturePass(std::make_unique()); +} + +} // namespace Internal +} // namespace Pipelines +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardSceneSetup.h b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardSceneSetup.h new file mode 100644 index 00000000..a855ce70 --- /dev/null +++ b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardSceneSetup.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include + +#include + +namespace XCEngine { +namespace Rendering { + +class SceneRenderFeatureHost; + +namespace Pipelines { +namespace Internal { + +enum class ForwardSceneStepType : Core::uint8 { + InjectionPoint, + BuiltinPhase +}; + +struct ForwardSceneStep { + ForwardSceneStepType type = ForwardSceneStepType::BuiltinPhase; + SceneRenderInjectionPoint injectionPoint = SceneRenderInjectionPoint::BeforeOpaque; + ScenePhase scenePhase = ScenePhase::Opaque; +}; + +const std::array& GetBuiltinForwardSceneSteps(); +void RegisterBuiltinForwardSceneFeatures(SceneRenderFeatureHost& featureHost); + +} // namespace Internal +} // namespace Pipelines +} // namespace Rendering +} // namespace XCEngine diff --git a/tests/Rendering/unit/CMakeLists.txt b/tests/Rendering/unit/CMakeLists.txt index f7c1b79e..e6cf5cf8 100644 --- a/tests/Rendering/unit/CMakeLists.txt +++ b/tests/Rendering/unit/CMakeLists.txt @@ -13,6 +13,7 @@ set(RENDERING_UNIT_TEST_SOURCES test_render_scene_utility.cpp test_render_scene_extractor.cpp test_render_resource_cache.cpp + test_render_graph.cpp ) add_executable(rendering_unit_tests ${RENDERING_UNIT_TEST_SOURCES}) diff --git a/tests/Rendering/unit/test_builtin_gaussian_splat_pass_resources.cpp b/tests/Rendering/unit/test_builtin_gaussian_splat_pass_resources.cpp index b6163865..ce967f50 100644 --- a/tests/Rendering/unit/test_builtin_gaussian_splat_pass_resources.cpp +++ b/tests/Rendering/unit/test_builtin_gaussian_splat_pass_resources.cpp @@ -7,7 +7,7 @@ #include #include -#include "Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.h" +#include "Rendering/Features/Internal/BuiltinGaussianSplatPassResources.h" #include #include @@ -15,7 +15,7 @@ using namespace XCEngine::Components; using namespace XCEngine::Rendering; -using namespace XCEngine::Rendering::Passes::Internal; +using namespace XCEngine::Rendering::Features::Internal; using namespace XCEngine::Resources; namespace {