From 9e8810e593432c79c9db1c89355a13e3b8f80612 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sat, 4 Apr 2026 14:27:44 +0800 Subject: [PATCH] Add depth-only and shadow-caster pass skeletons --- docs/plan/Shader与Material系统下一阶段计划.md | 9 +- engine/CMakeLists.txt | 4 + .../shaders/depth-only/depth-only.frag.glsl | 7 + .../depth-only/depth-only.frag.vk.glsl | 7 + .../shaders/depth-only/depth-only.ps.hlsl | 8 + .../shaders/depth-only/depth-only.shader | 62 ++ .../shaders/depth-only/depth-only.vert.glsl | 15 + .../depth-only/depth-only.vert.vk.glsl | 15 + .../shaders/depth-only/depth-only.vs.hlsl | 22 + .../shadow-caster/shadow-caster.frag.glsl | 7 + .../shadow-caster/shadow-caster.frag.vk.glsl | 7 + .../shadow-caster/shadow-caster.ps.hlsl | 8 + .../shadow-caster/shadow-caster.shader | 62 ++ .../shadow-caster/shadow-caster.vert.glsl | 15 + .../shadow-caster/shadow-caster.vert.vk.glsl | 15 + .../shadow-caster/shadow-caster.vs.hlsl | 22 + .../Rendering/Passes/BuiltinDepthOnlyPass.h | 21 + .../Passes/BuiltinDepthStylePassBase.h | 165 +++++ .../Passes/BuiltinShadowCasterPass.h | 24 + .../Rendering/RenderMaterialUtility.h | 26 + .../XCEngine/Resources/BuiltinResources.h | 2 + .../Passes/BuiltinDepthStylePassBase.cpp | 667 ++++++++++++++++++ engine/src/Resources/BuiltinResources.cpp | 32 + .../unit/test_builtin_forward_pipeline.cpp | 102 +++ .../unit/test_render_scene_extractor.cpp | 1 + 25 files changed, 1323 insertions(+), 2 deletions(-) create mode 100644 engine/assets/builtin/shaders/depth-only/depth-only.frag.glsl create mode 100644 engine/assets/builtin/shaders/depth-only/depth-only.frag.vk.glsl create mode 100644 engine/assets/builtin/shaders/depth-only/depth-only.ps.hlsl create mode 100644 engine/assets/builtin/shaders/depth-only/depth-only.shader create mode 100644 engine/assets/builtin/shaders/depth-only/depth-only.vert.glsl create mode 100644 engine/assets/builtin/shaders/depth-only/depth-only.vert.vk.glsl create mode 100644 engine/assets/builtin/shaders/depth-only/depth-only.vs.hlsl create mode 100644 engine/assets/builtin/shaders/shadow-caster/shadow-caster.frag.glsl create mode 100644 engine/assets/builtin/shaders/shadow-caster/shadow-caster.frag.vk.glsl create mode 100644 engine/assets/builtin/shaders/shadow-caster/shadow-caster.ps.hlsl create mode 100644 engine/assets/builtin/shaders/shadow-caster/shadow-caster.shader create mode 100644 engine/assets/builtin/shaders/shadow-caster/shadow-caster.vert.glsl create mode 100644 engine/assets/builtin/shaders/shadow-caster/shadow-caster.vert.vk.glsl create mode 100644 engine/assets/builtin/shaders/shadow-caster/shadow-caster.vs.hlsl create mode 100644 engine/include/XCEngine/Rendering/Passes/BuiltinDepthOnlyPass.h create mode 100644 engine/include/XCEngine/Rendering/Passes/BuiltinDepthStylePassBase.h create mode 100644 engine/include/XCEngine/Rendering/Passes/BuiltinShadowCasterPass.h create mode 100644 engine/src/Rendering/Passes/BuiltinDepthStylePassBase.cpp diff --git a/docs/plan/Shader与Material系统下一阶段计划.md b/docs/plan/Shader与Material系统下一阶段计划.md index 72cb9412..3c708ca7 100644 --- a/docs/plan/Shader与Material系统下一阶段计划.md +++ b/docs/plan/Shader与Material系统下一阶段计划.md @@ -350,11 +350,16 @@ Unity-like Shader Authoring (.shader) - `RenderMaterialUtility` 现在统一提供 `BuiltinPassSetLayoutMetadata` 与 `TryBuildBuiltinPassSetLayouts(...)` - `BuiltinForwardPipeline` 与 `BuiltinObjectIdPass` 现在都复用同一套 `binding plan -> set layout -> pipeline layout` 组装路径 - forward 侧只保留 set0 compatibility fallback 与 draw/update 逻辑;object-id 侧只保留自身约束校验与 per-object 常量写入 -- 已验证:`rendering_unit_tests` 65/65 +- 已完成:builtin `DepthOnly / ShadowCaster` shader 与独立 pass skeleton 落地 + - 新增 builtin `depth-only` 与 `shadow-caster` shader 资产,以及 `BuiltinResources` 对应入口 + - 新增 `BuiltinDepthOnlyPass / BuiltinShadowCasterPass`,作为独立 `RenderPass` 复用同一套 shared pass-layout skeleton + - 两个 pass 当前先收敛到 `PerObject` 常量路径;opaque 物体走 builtin fallback,`ShadowCaster` 额外尊重 `MeshRenderer.castShadows` + - 这一步解决的是“pass contract 与执行骨架缺失”,还没有把 shadow map / light-space request flow 一次性做完 +- 已验证:`rendering_unit_tests` 71/71 - 已验证:`rendering_integration_textured_quad_scene` 3/3(D3D12 / OpenGL / Vulkan) - 已验证:`rendering_integration_unlit_scene` 3/3(D3D12 / OpenGL / Vulkan) - 已验证:`rendering_integration_object_id_scene` 3/3(D3D12 / OpenGL / Vulkan) -- 下一步:如果继续沿 renderer 主线收口,优先把 `DepthOnly / ShadowCaster` 接到同一套 shared pass-layout skeleton;如果先控制范围,当前 `ForwardLit / Unlit / ObjectId` 已可以视为这一小阶段完成 +- 下一步:如果继续沿 renderer 主线收口,优先把 `DepthOnly / ShadowCaster` 真正接到 request / light-space 渲染流程,并补对应 integration coverage;如果先控制范围,当前 shader/material/pass contract 这一小阶段已经接近收口 ### 阶段 D:扩展 AssetDatabase / Library Artifact 能力 diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index ed8cf59c..53fe8a3e 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -359,11 +359,15 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/SceneRenderRequestPlanner.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/SceneRenderRequestUtils.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/SceneRenderer.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinDepthStylePassBase.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinDepthOnlyPass.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinShadowCasterPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinObjectIdPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/CameraRenderer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinDepthStylePassBase.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinObjectIdPass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinInfiniteGridPass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinObjectIdOutlinePass.cpp diff --git a/engine/assets/builtin/shaders/depth-only/depth-only.frag.glsl b/engine/assets/builtin/shaders/depth-only/depth-only.frag.glsl new file mode 100644 index 00000000..17c196d7 --- /dev/null +++ b/engine/assets/builtin/shaders/depth-only/depth-only.frag.glsl @@ -0,0 +1,7 @@ +// XC_BUILTIN_DEPTH_ONLY_OPENGL_PS +#version 430 +layout(location = 0) out vec4 fragColor; + +void main() { + fragColor = vec4(0.0); +} diff --git a/engine/assets/builtin/shaders/depth-only/depth-only.frag.vk.glsl b/engine/assets/builtin/shaders/depth-only/depth-only.frag.vk.glsl new file mode 100644 index 00000000..47b4ba21 --- /dev/null +++ b/engine/assets/builtin/shaders/depth-only/depth-only.frag.vk.glsl @@ -0,0 +1,7 @@ +// XC_BUILTIN_DEPTH_ONLY_VULKAN_PS +#version 450 +layout(location = 0) out vec4 fragColor; + +void main() { + fragColor = vec4(0.0); +} diff --git a/engine/assets/builtin/shaders/depth-only/depth-only.ps.hlsl b/engine/assets/builtin/shaders/depth-only/depth-only.ps.hlsl new file mode 100644 index 00000000..97468462 --- /dev/null +++ b/engine/assets/builtin/shaders/depth-only/depth-only.ps.hlsl @@ -0,0 +1,8 @@ +// XC_BUILTIN_DEPTH_ONLY_D3D12_PS +struct PSInput { + float4 position : SV_POSITION; +}; + +float4 MainPS(PSInput input) : SV_TARGET { + return float4(0.0, 0.0, 0.0, 0.0); +} diff --git a/engine/assets/builtin/shaders/depth-only/depth-only.shader b/engine/assets/builtin/shaders/depth-only/depth-only.shader new file mode 100644 index 00000000..a94f1584 --- /dev/null +++ b/engine/assets/builtin/shaders/depth-only/depth-only.shader @@ -0,0 +1,62 @@ +{ + "name": "Builtin Depth Only", + "passes": [ + { + "name": "DepthOnly", + "tags": { + "LightMode": "DepthOnly" + }, + "resources": [ + { + "name": "PerObjectConstants", + "type": "ConstantBuffer", + "set": 0, + "binding": 0, + "semantic": "PerObject" + } + ], + "variants": [ + { + "stage": "Vertex", + "backend": "D3D12", + "language": "HLSL", + "source": "depth-only.vs.hlsl", + "entryPoint": "MainVS", + "profile": "vs_5_0" + }, + { + "stage": "Fragment", + "backend": "D3D12", + "language": "HLSL", + "source": "depth-only.ps.hlsl", + "entryPoint": "MainPS", + "profile": "ps_5_0" + }, + { + "stage": "Vertex", + "backend": "OpenGL", + "language": "GLSL", + "source": "depth-only.vert.glsl" + }, + { + "stage": "Fragment", + "backend": "OpenGL", + "language": "GLSL", + "source": "depth-only.frag.glsl" + }, + { + "stage": "Vertex", + "backend": "Vulkan", + "language": "GLSL", + "source": "depth-only.vert.vk.glsl" + }, + { + "stage": "Fragment", + "backend": "Vulkan", + "language": "GLSL", + "source": "depth-only.frag.vk.glsl" + } + ] + } + ] +} diff --git a/engine/assets/builtin/shaders/depth-only/depth-only.vert.glsl b/engine/assets/builtin/shaders/depth-only/depth-only.vert.glsl new file mode 100644 index 00000000..3f6ce2f7 --- /dev/null +++ b/engine/assets/builtin/shaders/depth-only/depth-only.vert.glsl @@ -0,0 +1,15 @@ +// XC_BUILTIN_DEPTH_ONLY_OPENGL_VS +#version 430 +layout(location = 0) in vec3 aPosition; + +layout(std140, binding = 0) uniform PerObjectConstants { + mat4 gProjectionMatrix; + mat4 gViewMatrix; + mat4 gModelMatrix; +}; + +void main() { + vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0); + vec4 positionVS = gViewMatrix * positionWS; + gl_Position = gProjectionMatrix * positionVS; +} diff --git a/engine/assets/builtin/shaders/depth-only/depth-only.vert.vk.glsl b/engine/assets/builtin/shaders/depth-only/depth-only.vert.vk.glsl new file mode 100644 index 00000000..5d9ac23f --- /dev/null +++ b/engine/assets/builtin/shaders/depth-only/depth-only.vert.vk.glsl @@ -0,0 +1,15 @@ +// XC_BUILTIN_DEPTH_ONLY_VULKAN_VS +#version 450 +layout(location = 0) in vec3 aPosition; + +layout(set = 0, binding = 0, std140) uniform PerObjectConstants { + mat4 gProjectionMatrix; + mat4 gViewMatrix; + mat4 gModelMatrix; +}; + +void main() { + vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0); + vec4 positionVS = gViewMatrix * positionWS; + gl_Position = gProjectionMatrix * positionVS; +} diff --git a/engine/assets/builtin/shaders/depth-only/depth-only.vs.hlsl b/engine/assets/builtin/shaders/depth-only/depth-only.vs.hlsl new file mode 100644 index 00000000..e1bd2ca4 --- /dev/null +++ b/engine/assets/builtin/shaders/depth-only/depth-only.vs.hlsl @@ -0,0 +1,22 @@ +// XC_BUILTIN_DEPTH_ONLY_D3D12_VS +cbuffer PerObjectConstants : register(b0) { + float4x4 gProjectionMatrix; + float4x4 gViewMatrix; + float4x4 gModelMatrix; +}; + +struct VSInput { + float3 position : POSITION; +}; + +struct PSInput { + float4 position : SV_POSITION; +}; + +PSInput MainVS(VSInput input) { + PSInput output; + float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0)); + float4 positionVS = mul(gViewMatrix, positionWS); + output.position = mul(gProjectionMatrix, positionVS); + return output; +} diff --git a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.frag.glsl b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.frag.glsl new file mode 100644 index 00000000..5d27dd56 --- /dev/null +++ b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.frag.glsl @@ -0,0 +1,7 @@ +// XC_BUILTIN_SHADOW_CASTER_OPENGL_PS +#version 430 +layout(location = 0) out vec4 fragColor; + +void main() { + fragColor = vec4(0.0); +} diff --git a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.frag.vk.glsl b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.frag.vk.glsl new file mode 100644 index 00000000..9417195d --- /dev/null +++ b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.frag.vk.glsl @@ -0,0 +1,7 @@ +// XC_BUILTIN_SHADOW_CASTER_VULKAN_PS +#version 450 +layout(location = 0) out vec4 fragColor; + +void main() { + fragColor = vec4(0.0); +} diff --git a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.ps.hlsl b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.ps.hlsl new file mode 100644 index 00000000..e3f3fa1d --- /dev/null +++ b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.ps.hlsl @@ -0,0 +1,8 @@ +// XC_BUILTIN_SHADOW_CASTER_D3D12_PS +struct PSInput { + float4 position : SV_POSITION; +}; + +float4 MainPS(PSInput input) : SV_TARGET { + return float4(0.0, 0.0, 0.0, 0.0); +} diff --git a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.shader b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.shader new file mode 100644 index 00000000..34f514d4 --- /dev/null +++ b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.shader @@ -0,0 +1,62 @@ +{ + "name": "Builtin Shadow Caster", + "passes": [ + { + "name": "ShadowCaster", + "tags": { + "LightMode": "ShadowCaster" + }, + "resources": [ + { + "name": "PerObjectConstants", + "type": "ConstantBuffer", + "set": 0, + "binding": 0, + "semantic": "PerObject" + } + ], + "variants": [ + { + "stage": "Vertex", + "backend": "D3D12", + "language": "HLSL", + "source": "shadow-caster.vs.hlsl", + "entryPoint": "MainVS", + "profile": "vs_5_0" + }, + { + "stage": "Fragment", + "backend": "D3D12", + "language": "HLSL", + "source": "shadow-caster.ps.hlsl", + "entryPoint": "MainPS", + "profile": "ps_5_0" + }, + { + "stage": "Vertex", + "backend": "OpenGL", + "language": "GLSL", + "source": "shadow-caster.vert.glsl" + }, + { + "stage": "Fragment", + "backend": "OpenGL", + "language": "GLSL", + "source": "shadow-caster.frag.glsl" + }, + { + "stage": "Vertex", + "backend": "Vulkan", + "language": "GLSL", + "source": "shadow-caster.vert.vk.glsl" + }, + { + "stage": "Fragment", + "backend": "Vulkan", + "language": "GLSL", + "source": "shadow-caster.frag.vk.glsl" + } + ] + } + ] +} diff --git a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.vert.glsl b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.vert.glsl new file mode 100644 index 00000000..3e0c614b --- /dev/null +++ b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.vert.glsl @@ -0,0 +1,15 @@ +// XC_BUILTIN_SHADOW_CASTER_OPENGL_VS +#version 430 +layout(location = 0) in vec3 aPosition; + +layout(std140, binding = 0) uniform PerObjectConstants { + mat4 gProjectionMatrix; + mat4 gViewMatrix; + mat4 gModelMatrix; +}; + +void main() { + vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0); + vec4 positionVS = gViewMatrix * positionWS; + gl_Position = gProjectionMatrix * positionVS; +} diff --git a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.vert.vk.glsl b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.vert.vk.glsl new file mode 100644 index 00000000..46fe8a19 --- /dev/null +++ b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.vert.vk.glsl @@ -0,0 +1,15 @@ +// XC_BUILTIN_SHADOW_CASTER_VULKAN_VS +#version 450 +layout(location = 0) in vec3 aPosition; + +layout(set = 0, binding = 0, std140) uniform PerObjectConstants { + mat4 gProjectionMatrix; + mat4 gViewMatrix; + mat4 gModelMatrix; +}; + +void main() { + vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0); + vec4 positionVS = gViewMatrix * positionWS; + gl_Position = gProjectionMatrix * positionVS; +} diff --git a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.vs.hlsl b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.vs.hlsl new file mode 100644 index 00000000..d7a5bc23 --- /dev/null +++ b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.vs.hlsl @@ -0,0 +1,22 @@ +// XC_BUILTIN_SHADOW_CASTER_D3D12_VS +cbuffer PerObjectConstants : register(b0) { + float4x4 gProjectionMatrix; + float4x4 gViewMatrix; + float4x4 gModelMatrix; +}; + +struct VSInput { + float3 position : POSITION; +}; + +struct PSInput { + float4 position : SV_POSITION; +}; + +PSInput MainVS(VSInput input) { + PSInput output; + float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0)); + float4 positionVS = mul(gViewMatrix, positionWS); + output.position = mul(gProjectionMatrix, positionVS); + return output; +} diff --git a/engine/include/XCEngine/Rendering/Passes/BuiltinDepthOnlyPass.h b/engine/include/XCEngine/Rendering/Passes/BuiltinDepthOnlyPass.h new file mode 100644 index 00000000..33ecb2ea --- /dev/null +++ b/engine/include/XCEngine/Rendering/Passes/BuiltinDepthOnlyPass.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +namespace XCEngine { +namespace Rendering { +namespace Passes { + +class BuiltinDepthOnlyPass final : public BuiltinDepthStylePassBase { +public: + BuiltinDepthOnlyPass(); + ~BuiltinDepthOnlyPass() override = default; + + static RHI::InputLayoutDesc BuildInputLayout(); + + const char* GetName() const override; +}; + +} // namespace Passes +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/include/XCEngine/Rendering/Passes/BuiltinDepthStylePassBase.h b/engine/include/XCEngine/Rendering/Passes/BuiltinDepthStylePassBase.h new file mode 100644 index 00000000..69c9e6c7 --- /dev/null +++ b/engine/include/XCEngine/Rendering/Passes/BuiltinDepthStylePassBase.h @@ -0,0 +1,165 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace XCEngine { +namespace Resources { +class Material; +class Shader; +} // namespace Resources + +namespace Rendering { +namespace Passes { + +class BuiltinDepthStylePassBase : public RenderPass { +public: + ~BuiltinDepthStylePassBase() override; + + static RHI::InputLayoutDesc BuildCommonInputLayout(); + + bool Initialize(const RenderContext& context) override; + void Shutdown() override; + bool Execute(const RenderPassContext& context) override; + +protected: + BuiltinDepthStylePassBase( + BuiltinMaterialPass passType, + Containers::String builtinShaderPath); + + virtual bool ShouldRenderVisibleItem(const VisibleRenderItem& visibleItem) const; + +private: + struct PerObjectConstants { + Math::Matrix4x4 projection = Math::Matrix4x4::Identity(); + Math::Matrix4x4 view = Math::Matrix4x4::Identity(); + Math::Matrix4x4 model = Math::Matrix4x4::Identity(); + }; + + struct OwnedDescriptorSet { + RHI::RHIDescriptorPool* pool = nullptr; + RHI::RHIDescriptorSet* set = nullptr; + }; + + struct PassLayoutKey { + const Resources::Shader* shader = nullptr; + Containers::String passName; + + bool operator==(const PassLayoutKey& other) const { + return shader == other.shader && passName == other.passName; + } + }; + + struct PassLayoutKeyHash { + size_t operator()(const PassLayoutKey& key) const noexcept { + size_t hash = reinterpret_cast(key.shader); + hash ^= std::hash{}(key.passName) + 0x9e3779b9u + (hash << 6) + (hash >> 2); + return hash; + } + }; + + struct PassResourceLayout { + RHI::RHIPipelineLayout* pipelineLayout = nullptr; + PassResourceBindingLocation perObject = {}; + BuiltinPassSetLayoutMetadata perObjectSetLayout = {}; + Core::uint32 firstDescriptorSet = 0; + }; + + struct PerObjectSetKey { + PassLayoutKey passLayout = {}; + Core::uint64 objectId = 0; + + bool operator==(const PerObjectSetKey& other) const { + return passLayout == other.passLayout && + objectId == other.objectId; + } + }; + + struct PerObjectSetKeyHash { + size_t operator()(const PerObjectSetKey& key) const noexcept { + size_t hash = PassLayoutKeyHash()(key.passLayout); + hash ^= std::hash{}(key.objectId) + 0x9e3779b9u + (hash << 6) + (hash >> 2); + return hash; + } + }; + + struct ResolvedShaderPass { + const Resources::Shader* shader = nullptr; + const Resources::ShaderPass* pass = nullptr; + Containers::String passName; + }; + + struct PipelineStateKey { + Resources::MaterialRenderState renderState; + const Resources::Shader* shader = nullptr; + Containers::String passName; + + bool operator==(const PipelineStateKey& other) const { + return renderState == other.renderState && + shader == other.shader && + passName == other.passName; + } + }; + + struct PipelineStateKeyHash { + size_t operator()(const PipelineStateKey& key) const noexcept { + size_t hash = MaterialRenderStateHash()(key.renderState); + hash ^= reinterpret_cast(key.shader) + 0x9e3779b9u + (hash << 6) + (hash >> 2); + hash ^= std::hash{}(key.passName) + 0x9e3779b9u + (hash << 6) + (hash >> 2); + return hash; + } + }; + + bool EnsureInitialized(const RenderContext& context); + bool CreateResources(const RenderContext& context); + void DestroyResources(); + + ResolvedShaderPass ResolveSurfaceShaderPass(const Resources::Material* material) const; + bool TryBuildSupportedBindingPlan( + const Resources::ShaderPass& shaderPass, + BuiltinPassResourceBindingPlan& outPlan, + Containers::String* outError = nullptr) const; + PassResourceLayout* GetOrCreatePassResourceLayout( + const RenderContext& context, + const ResolvedShaderPass& resolvedShaderPass); + RHI::RHIPipelineState* GetOrCreatePipelineState( + const RenderContext& context, + const Resources::Material* material); + bool CreateOwnedDescriptorSet( + const BuiltinPassSetLayoutMetadata& setLayout, + OwnedDescriptorSet& descriptorSet); + RHI::RHIDescriptorSet* GetOrCreatePerObjectSet( + const PassLayoutKey& passLayoutKey, + const PassResourceLayout& passLayout, + Core::uint64 objectId); + void DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet); + void DestroyPassResourceLayout(PassResourceLayout& passLayout); + bool DrawVisibleItem( + const RenderContext& context, + const RenderSceneData& sceneData, + const VisibleRenderItem& visibleItem); + + RHI::RHIDevice* m_device = nullptr; + RHI::RHIType m_backendType = RHI::RHIType::D3D12; + BuiltinMaterialPass m_passType = BuiltinMaterialPass::DepthOnly; + Containers::String m_builtinShaderPath; + Resources::ResourceHandle m_builtinShader; + RenderResourceCache m_resourceCache; + + std::unordered_map m_passResourceLayouts; + std::unordered_map m_pipelineStates; + std::unordered_map m_perObjectSets; +}; + +} // namespace Passes +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/include/XCEngine/Rendering/Passes/BuiltinShadowCasterPass.h b/engine/include/XCEngine/Rendering/Passes/BuiltinShadowCasterPass.h new file mode 100644 index 00000000..3d014795 --- /dev/null +++ b/engine/include/XCEngine/Rendering/Passes/BuiltinShadowCasterPass.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace XCEngine { +namespace Rendering { +namespace Passes { + +class BuiltinShadowCasterPass final : public BuiltinDepthStylePassBase { +public: + BuiltinShadowCasterPass(); + ~BuiltinShadowCasterPass() override = default; + + static RHI::InputLayoutDesc BuildInputLayout(); + + const char* GetName() const override; + +protected: + bool ShouldRenderVisibleItem(const VisibleRenderItem& visibleItem) const override; +}; + +} // namespace Passes +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/include/XCEngine/Rendering/RenderMaterialUtility.h b/engine/include/XCEngine/Rendering/RenderMaterialUtility.h index 49cd944c..0d3423fa 100644 --- a/engine/include/XCEngine/Rendering/RenderMaterialUtility.h +++ b/engine/include/XCEngine/Rendering/RenderMaterialUtility.h @@ -254,6 +254,32 @@ inline Containers::Array BuildLegacyBuilti return bindings; } +inline Containers::Array BuildLegacyBuiltinDepthOnlyPassResourceBindings() { + Containers::Array bindings; + bindings.Resize(1); + + bindings[0].name = "PerObjectConstants"; + bindings[0].type = Resources::ShaderResourceType::ConstantBuffer; + bindings[0].set = 0; + bindings[0].binding = 0; + bindings[0].semantic = "PerObject"; + + return bindings; +} + +inline Containers::Array BuildLegacyBuiltinShadowCasterPassResourceBindings() { + Containers::Array bindings; + bindings.Resize(1); + + bindings[0].name = "PerObjectConstants"; + bindings[0].type = Resources::ShaderResourceType::ConstantBuffer; + bindings[0].set = 0; + bindings[0].binding = 0; + bindings[0].semantic = "PerObject"; + + return bindings; +} + inline bool IsBuiltinPassResourceTypeCompatible( BuiltinPassResourceSemantic semantic, Resources::ShaderResourceType type) { diff --git a/engine/include/XCEngine/Resources/BuiltinResources.h b/engine/include/XCEngine/Resources/BuiltinResources.h index 93a5961d..2190c690 100644 --- a/engine/include/XCEngine/Resources/BuiltinResources.h +++ b/engine/include/XCEngine/Resources/BuiltinResources.h @@ -25,6 +25,8 @@ Containers::String GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType primitiveTyp Containers::String GetBuiltinDefaultPrimitiveMaterialPath(); Containers::String GetBuiltinForwardLitShaderPath(); Containers::String GetBuiltinUnlitShaderPath(); +Containers::String GetBuiltinDepthOnlyShaderPath(); +Containers::String GetBuiltinShadowCasterShaderPath(); Containers::String GetBuiltinObjectIdShaderPath(); Containers::String GetBuiltinDefaultPrimitiveTexturePath(); diff --git a/engine/src/Rendering/Passes/BuiltinDepthStylePassBase.cpp b/engine/src/Rendering/Passes/BuiltinDepthStylePassBase.cpp new file mode 100644 index 00000000..e10d8fc8 --- /dev/null +++ b/engine/src/Rendering/Passes/BuiltinDepthStylePassBase.cpp @@ -0,0 +1,667 @@ +#include "Rendering/Passes/BuiltinDepthStylePassBase.h" +#include "Rendering/Passes/BuiltinDepthOnlyPass.h" +#include "Rendering/Passes/BuiltinShadowCasterPass.h" + +#include "Components/GameObject.h" +#include "Components/MeshRendererComponent.h" +#include "Core/Asset/ResourceManager.h" +#include "Debug/Logger.h" +#include "RHI/RHICommandList.h" +#include "Rendering/Detail/ShaderVariantUtils.h" +#include "Rendering/RenderSceneExtractor.h" +#include "Rendering/RenderSurface.h" +#include "Resources/BuiltinResources.h" +#include "Resources/Material/Material.h" +#include "Resources/Mesh/Mesh.h" + +#include +#include +#include + +namespace XCEngine { +namespace Rendering { +namespace Passes { + +namespace { + +Containers::Array BuildLegacyBuiltinDepthStylePassResourceBindings( + BuiltinMaterialPass passType) { + switch (passType) { + case BuiltinMaterialPass::DepthOnly: + return BuildLegacyBuiltinDepthOnlyPassResourceBindings(); + case BuiltinMaterialPass::ShadowCaster: + return BuildLegacyBuiltinShadowCasterPassResourceBindings(); + default: + return {}; + } +} + +bool IsSupportedPerObjectOnlyBindingPlan(const BuiltinPassResourceBindingPlan& bindingPlan) { + return bindingPlan.perObject.IsValid() && + bindingPlan.bindings.Size() == 1u && + bindingPlan.descriptorSetCount == 1u && + bindingPlan.usesConstantBuffers && + !bindingPlan.usesTextures && + !bindingPlan.usesSamplers; +} + +RHI::GraphicsPipelineDesc CreatePipelineDesc( + RHI::RHIType backendType, + RHI::RHIPipelineLayout* pipelineLayout, + const Resources::Shader& shader, + const Containers::String& passName, + const Resources::Material* material, + const RHI::InputLayoutDesc& inputLayout) { + RHI::GraphicsPipelineDesc pipelineDesc = {}; + pipelineDesc.pipelineLayout = pipelineLayout; + pipelineDesc.topologyType = static_cast(RHI::PrimitiveTopologyType::Triangle); + pipelineDesc.renderTargetCount = 1; + pipelineDesc.renderTargetFormats[0] = static_cast(RHI::Format::R8G8B8A8_UNorm); + pipelineDesc.depthStencilFormat = static_cast(RHI::Format::D24_UNorm_S8_UInt); + pipelineDesc.sampleCount = 1; + pipelineDesc.inputLayout = inputLayout; + ApplyMaterialRenderState(material, pipelineDesc); + + pipelineDesc.blendState.blendEnable = false; + pipelineDesc.blendState.colorWriteMask = 0; + pipelineDesc.depthStencilState.depthTestEnable = true; + pipelineDesc.depthStencilState.depthWriteEnable = true; + pipelineDesc.depthStencilState.depthFunc = static_cast(RHI::ComparisonFunc::LessEqual); + + const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType); + if (const Resources::ShaderStageVariant* vertexVariant = + shader.FindVariant(passName, Resources::ShaderType::Vertex, backend)) { + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*vertexVariant, pipelineDesc.vertexShader); + } + if (const Resources::ShaderStageVariant* fragmentVariant = + shader.FindVariant(passName, Resources::ShaderType::Fragment, backend)) { + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*fragmentVariant, pipelineDesc.fragmentShader); + } + + return pipelineDesc; +} + +} // namespace + +BuiltinDepthStylePassBase::BuiltinDepthStylePassBase( + BuiltinMaterialPass passType, + Containers::String builtinShaderPath) + : m_passType(passType) + , m_builtinShaderPath(std::move(builtinShaderPath)) { +} + +BuiltinDepthStylePassBase::~BuiltinDepthStylePassBase() { + Shutdown(); +} + +RHI::InputLayoutDesc BuiltinDepthStylePassBase::BuildCommonInputLayout() { + 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 = static_cast(offsetof(Resources::StaticMeshVertex, position)); + 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); + + return inputLayout; +} + +bool BuiltinDepthStylePassBase::Initialize(const RenderContext& context) { + return EnsureInitialized(context); +} + +void BuiltinDepthStylePassBase::Shutdown() { + DestroyResources(); +} + +bool BuiltinDepthStylePassBase::Execute(const RenderPassContext& context) { + if (!context.renderContext.IsValid()) { + return false; + } + + const std::vector& colorAttachments = context.surface.GetColorAttachments(); + if (colorAttachments.empty() || + colorAttachments[0] == nullptr || + context.surface.GetDepthAttachment() == nullptr) { + return false; + } + + const Math::RectInt renderArea = context.surface.GetRenderArea(); + if (renderArea.width <= 0 || renderArea.height <= 0) { + return false; + } + + if (!EnsureInitialized(context.renderContext)) { + return false; + } + + RHI::RHICommandList* commandList = context.renderContext.commandList; + RHI::RHIResourceView* renderTarget = colorAttachments[0]; + + if (context.surface.IsAutoTransitionEnabled()) { + commandList->TransitionBarrier( + renderTarget, + context.surface.GetColorStateBefore(), + RHI::ResourceStates::RenderTarget); + } + + commandList->SetRenderTargets(1, &renderTarget, context.surface.GetDepthAttachment()); + + 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 + }; + commandList->SetViewport(viewport); + commandList->SetScissorRect(scissorRect); + commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList); + + for (const VisibleRenderItem& visibleItem : context.sceneData.visibleItems) { + if (!ShouldRenderVisibleItem(visibleItem)) { + continue; + } + + DrawVisibleItem(context.renderContext, context.sceneData, visibleItem); + } + + commandList->EndRenderPass(); + + if (context.surface.IsAutoTransitionEnabled()) { + commandList->TransitionBarrier( + renderTarget, + RHI::ResourceStates::RenderTarget, + context.surface.GetColorStateAfter()); + } + + return true; +} + +bool BuiltinDepthStylePassBase::ShouldRenderVisibleItem(const VisibleRenderItem& visibleItem) const { + (void)visibleItem; + return true; +} + +bool BuiltinDepthStylePassBase::EnsureInitialized(const RenderContext& context) { + if (!context.IsValid()) { + return false; + } + + if (m_device == context.device && + m_backendType == context.backendType && + m_builtinShader.IsValid()) { + return true; + } + + DestroyResources(); + return CreateResources(context); +} + +bool BuiltinDepthStylePassBase::CreateResources(const RenderContext& context) { + m_device = context.device; + m_backendType = context.backendType; + m_builtinShader = Resources::ResourceManager::Get().Load(m_builtinShaderPath); + if (!m_builtinShader.IsValid()) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + (Containers::String("BuiltinDepthStylePassBase failed to load builtin shader resource: ") + m_builtinShaderPath).CStr()); + DestroyResources(); + return false; + } + + return true; +} + +void BuiltinDepthStylePassBase::DestroyResources() { + m_resourceCache.Shutdown(); + + for (auto& descriptorSetEntry : m_perObjectSets) { + DestroyOwnedDescriptorSet(descriptorSetEntry.second); + } + m_perObjectSets.clear(); + + for (auto& pipelineEntry : m_pipelineStates) { + if (pipelineEntry.second != nullptr) { + pipelineEntry.second->Shutdown(); + delete pipelineEntry.second; + } + } + m_pipelineStates.clear(); + + for (auto& layoutEntry : m_passResourceLayouts) { + DestroyPassResourceLayout(layoutEntry.second); + } + m_passResourceLayouts.clear(); + + m_device = nullptr; + m_backendType = RHI::RHIType::D3D12; + m_builtinShader.Reset(); +} + +BuiltinDepthStylePassBase::ResolvedShaderPass BuiltinDepthStylePassBase::ResolveSurfaceShaderPass( + const Resources::Material* material) const { + ResolvedShaderPass resolved = {}; + const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(m_backendType); + + auto tryResolveFromShader = + [this, backend, &resolved]( + const Resources::Shader* shader, + const Resources::Material* ownerMaterial) -> bool { + if (shader == nullptr) { + return false; + } + + auto tryAcceptPass = + [this, shader, &resolved](const Resources::ShaderPass& shaderPass) -> bool { + BuiltinPassResourceBindingPlan bindingPlan = {}; + Containers::String error; + if (!TryBuildSupportedBindingPlan(shaderPass, bindingPlan, &error)) { + return false; + } + + resolved.shader = shader; + resolved.pass = &shaderPass; + resolved.passName = shaderPass.name; + return true; + }; + + if (ownerMaterial != nullptr && !ownerMaterial->GetShaderPass().Empty()) { + const Resources::ShaderPass* explicitPass = shader->FindPass(ownerMaterial->GetShaderPass()); + if (explicitPass != nullptr && + ShaderPassMatchesBuiltinPass(*explicitPass, m_passType) && + ::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants( + *shader, + explicitPass->name, + backend) && + tryAcceptPass(*explicitPass)) { + return true; + } + } + + for (const Resources::ShaderPass& shaderPass : shader->GetPasses()) { + if (!ShaderPassMatchesBuiltinPass(shaderPass, m_passType) || + !::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(*shader, shaderPass.name, backend)) { + continue; + } + + if (tryAcceptPass(shaderPass)) { + return true; + } + } + + return false; + }; + + if (material != nullptr && + material->GetShader() != nullptr && + tryResolveFromShader(material->GetShader(), material)) { + return resolved; + } + + if (material != nullptr && IsTransparentRenderQueue(ResolveMaterialRenderQueue(material))) { + return {}; + } + + if (m_builtinShader.IsValid() && + tryResolveFromShader(m_builtinShader.Get(), nullptr)) { + return resolved; + } + + return {}; +} + +bool BuiltinDepthStylePassBase::TryBuildSupportedBindingPlan( + const Resources::ShaderPass& shaderPass, + BuiltinPassResourceBindingPlan& outPlan, + Containers::String* outError) const { + Containers::Array resourceBindings = shaderPass.resources; + if (resourceBindings.Empty()) { + resourceBindings = BuildLegacyBuiltinDepthStylePassResourceBindings(m_passType); + } + + if (!TryBuildBuiltinPassResourceBindingPlan(resourceBindings, outPlan, outError)) { + return false; + } + + if (!IsSupportedPerObjectOnlyBindingPlan(outPlan)) { + if (outError != nullptr) { + *outError = "Builtin depth-style pass currently requires exactly one PerObject constant-buffer binding"; + } + return false; + } + + return true; +} + +BuiltinDepthStylePassBase::PassResourceLayout* BuiltinDepthStylePassBase::GetOrCreatePassResourceLayout( + const RenderContext& context, + const ResolvedShaderPass& resolvedShaderPass) { + if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { + return nullptr; + } + + PassLayoutKey passLayoutKey = {}; + passLayoutKey.shader = resolvedShaderPass.shader; + passLayoutKey.passName = resolvedShaderPass.passName; + + const auto existing = m_passResourceLayouts.find(passLayoutKey); + if (existing != m_passResourceLayouts.end()) { + return &existing->second; + } + + PassResourceLayout passLayout = {}; + auto failLayout = [this, &passLayout](const char* message) -> PassResourceLayout* { + Debug::Logger::Get().Error(Debug::LogCategory::Rendering, message); + DestroyPassResourceLayout(passLayout); + return nullptr; + }; + + BuiltinPassResourceBindingPlan bindingPlan = {}; + Containers::String bindingPlanError; + if (!TryBuildSupportedBindingPlan(*resolvedShaderPass.pass, bindingPlan, &bindingPlanError)) { + return failLayout(bindingPlanError.CStr()); + } + + std::vector setLayouts; + Containers::String setLayoutError; + if (!TryBuildBuiltinPassSetLayouts(bindingPlan, setLayouts, &setLayoutError)) { + return failLayout(setLayoutError.CStr()); + } + + if (bindingPlan.perObject.set >= setLayouts.size()) { + return failLayout("Builtin depth-style pass produced an invalid PerObject descriptor set index"); + } + + passLayout.perObject = bindingPlan.perObject; + passLayout.firstDescriptorSet = bindingPlan.firstDescriptorSet; + passLayout.perObjectSetLayout = setLayouts[bindingPlan.perObject.set]; + RefreshBuiltinPassSetLayoutMetadata(passLayout.perObjectSetLayout); + + std::vector nativeSetLayouts(setLayouts.size()); + for (size_t setIndex = 0; setIndex < setLayouts.size(); ++setIndex) { + nativeSetLayouts[setIndex] = setLayouts[setIndex].layout; + } + + RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {}; + pipelineLayoutDesc.setLayouts = nativeSetLayouts.empty() ? nullptr : nativeSetLayouts.data(); + pipelineLayoutDesc.setLayoutCount = static_cast(nativeSetLayouts.size()); + passLayout.pipelineLayout = context.device->CreatePipelineLayout(pipelineLayoutDesc); + if (passLayout.pipelineLayout == nullptr) { + return failLayout("Builtin depth-style pass failed to create pipeline layout"); + } + + const auto result = m_passResourceLayouts.emplace(passLayoutKey, passLayout); + return &result.first->second; +} + +RHI::RHIPipelineState* BuiltinDepthStylePassBase::GetOrCreatePipelineState( + const RenderContext& context, + const Resources::Material* material) { + const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(material); + if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { + return nullptr; + } + + PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass); + if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) { + return nullptr; + } + + PipelineStateKey pipelineKey = {}; + pipelineKey.renderState = + material != nullptr ? material->GetRenderState() : Resources::MaterialRenderState(); + pipelineKey.shader = resolvedShaderPass.shader; + pipelineKey.passName = resolvedShaderPass.passName; + + 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.passName, + material, + BuildCommonInputLayout()); + RHI::RHIPipelineState* pipelineState = context.device->CreatePipelineState(pipelineDesc); + if (pipelineState == nullptr || !pipelineState->IsValid()) { + if (pipelineState != nullptr) { + pipelineState->Shutdown(); + delete pipelineState; + } + return nullptr; + } + + m_pipelineStates.emplace(pipelineKey, pipelineState); + return pipelineState; +} + +bool BuiltinDepthStylePassBase::CreateOwnedDescriptorSet( + const BuiltinPassSetLayoutMetadata& setLayout, + OwnedDescriptorSet& descriptorSet) { + RHI::DescriptorPoolDesc poolDesc = {}; + poolDesc.type = setLayout.heapType; + poolDesc.descriptorCount = CountBuiltinPassHeapDescriptors(setLayout.heapType, setLayout.bindings); + poolDesc.shaderVisible = setLayout.shaderVisible; + + descriptorSet.pool = m_device->CreateDescriptorPool(poolDesc); + if (descriptorSet.pool == nullptr) { + return false; + } + + descriptorSet.set = descriptorSet.pool->AllocateSet(setLayout.layout); + if (descriptorSet.set == nullptr) { + DestroyOwnedDescriptorSet(descriptorSet); + return false; + } + + return true; +} + +RHI::RHIDescriptorSet* BuiltinDepthStylePassBase::GetOrCreatePerObjectSet( + const PassLayoutKey& passLayoutKey, + const PassResourceLayout& passLayout, + Core::uint64 objectId) { + if (!passLayout.perObject.IsValid() || + passLayout.perObjectSetLayout.layout.bindingCount == 0) { + return nullptr; + } + + PerObjectSetKey key = {}; + key.passLayout = passLayoutKey; + key.objectId = objectId; + + const auto existing = m_perObjectSets.find(key); + if (existing != m_perObjectSets.end()) { + return existing->second.set; + } + + OwnedDescriptorSet descriptorSet = {}; + if (!CreateOwnedDescriptorSet(passLayout.perObjectSetLayout, descriptorSet)) { + return nullptr; + } + + const auto result = m_perObjectSets.emplace(key, descriptorSet); + return result.first->second.set; +} + +void BuiltinDepthStylePassBase::DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet) { + if (descriptorSet.set != nullptr) { + descriptorSet.set->Shutdown(); + delete descriptorSet.set; + descriptorSet.set = nullptr; + } + + if (descriptorSet.pool != nullptr) { + descriptorSet.pool->Shutdown(); + delete descriptorSet.pool; + descriptorSet.pool = nullptr; + } +} + +void BuiltinDepthStylePassBase::DestroyPassResourceLayout(PassResourceLayout& passLayout) { + if (passLayout.pipelineLayout != nullptr) { + passLayout.pipelineLayout->Shutdown(); + delete passLayout.pipelineLayout; + passLayout.pipelineLayout = nullptr; + } + + passLayout.perObject = {}; + passLayout.perObjectSetLayout = {}; + passLayout.firstDescriptorSet = 0; +} + +bool BuiltinDepthStylePassBase::DrawVisibleItem( + const RenderContext& context, + const RenderSceneData& sceneData, + const VisibleRenderItem& visibleItem) { + if (visibleItem.mesh == nullptr || visibleItem.gameObject == nullptr) { + return false; + } + + const RenderResourceCache::CachedMesh* cachedMesh = + m_resourceCache.GetOrCreateMesh(m_device, visibleItem.mesh); + if (cachedMesh == nullptr || cachedMesh->vertexBufferView == nullptr) { + return false; + } + + const Resources::Material* material = ResolveMaterial(visibleItem); + const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(material); + if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { + return false; + } + + PassLayoutKey passLayoutKey = {}; + passLayoutKey.shader = resolvedShaderPass.shader; + passLayoutKey.passName = resolvedShaderPass.passName; + + PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass); + if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) { + return false; + } + + RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, material); + if (pipelineState == nullptr) { + return false; + } + + RHI::RHICommandList* commandList = context.commandList; + commandList->SetPipelineState(pipelineState); + + 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); + } + + RHI::RHIDescriptorSet* constantSet = GetOrCreatePerObjectSet( + passLayoutKey, + *passLayout, + visibleItem.gameObject->GetID()); + if (constantSet == nullptr) { + return false; + } + + const PerObjectConstants constants = { + sceneData.cameraData.projection, + sceneData.cameraData.view, + visibleItem.localToWorld.Transpose() + }; + constantSet->WriteConstant(passLayout->perObject.binding, &constants, sizeof(constants)); + + RHI::RHIDescriptorSet* descriptorSets[] = { constantSet }; + commandList->SetGraphicsDescriptorSets( + passLayout->firstDescriptorSet, + 1, + descriptorSets, + passLayout->pipelineLayout); + + 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) { + 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; +} + +BuiltinDepthOnlyPass::BuiltinDepthOnlyPass() + : BuiltinDepthStylePassBase( + BuiltinMaterialPass::DepthOnly, + Resources::GetBuiltinDepthOnlyShaderPath()) { +} + +RHI::InputLayoutDesc BuiltinDepthOnlyPass::BuildInputLayout() { + return BuildCommonInputLayout(); +} + +const char* BuiltinDepthOnlyPass::GetName() const { + return "BuiltinDepthOnlyPass"; +} + +BuiltinShadowCasterPass::BuiltinShadowCasterPass() + : BuiltinDepthStylePassBase( + BuiltinMaterialPass::ShadowCaster, + Resources::GetBuiltinShadowCasterShaderPath()) { +} + +RHI::InputLayoutDesc BuiltinShadowCasterPass::BuildInputLayout() { + return BuildCommonInputLayout(); +} + +const char* BuiltinShadowCasterPass::GetName() const { + return "BuiltinShadowCasterPass"; +} + +bool BuiltinShadowCasterPass::ShouldRenderVisibleItem(const VisibleRenderItem& visibleItem) const { + return visibleItem.meshRenderer == nullptr || visibleItem.meshRenderer->GetCastShadows(); +} + +} // namespace Passes +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/src/Resources/BuiltinResources.cpp b/engine/src/Resources/BuiltinResources.cpp index 7af645e6..2531bb8b 100644 --- a/engine/src/Resources/BuiltinResources.cpp +++ b/engine/src/Resources/BuiltinResources.cpp @@ -29,6 +29,8 @@ constexpr const char* kBuiltinTexturePrefix = "builtin://textures/"; constexpr const char* kBuiltinDefaultPrimitiveMaterialPath = "builtin://materials/default-primitive"; constexpr const char* kBuiltinForwardLitShaderPath = "builtin://shaders/forward-lit"; constexpr const char* kBuiltinUnlitShaderPath = "builtin://shaders/unlit"; +constexpr const char* kBuiltinDepthOnlyShaderPath = "builtin://shaders/depth-only"; +constexpr const char* kBuiltinShadowCasterShaderPath = "builtin://shaders/shadow-caster"; constexpr const char* kBuiltinObjectIdShaderPath = "builtin://shaders/object-id"; constexpr const char* kBuiltinDefaultPrimitiveTexturePath = "builtin://textures/default-primitive-albedo"; constexpr float kPi = 3.14159265358979323846f; @@ -44,6 +46,10 @@ constexpr const char* kBuiltinForwardLitShaderManifestRelativePath = "engine/assets/builtin/shaders/forward-lit/forward-lit.shader"; constexpr const char* kBuiltinUnlitShaderManifestRelativePath = "engine/assets/builtin/shaders/unlit/unlit.shader"; +constexpr const char* kBuiltinDepthOnlyShaderManifestRelativePath = + "engine/assets/builtin/shaders/depth-only/depth-only.shader"; +constexpr const char* kBuiltinShadowCasterShaderManifestRelativePath = + "engine/assets/builtin/shaders/shadow-caster/shadow-caster.shader"; constexpr const char* kBuiltinObjectIdShaderManifestRelativePath = "engine/assets/builtin/shaders/object-id/object-id.shader"; @@ -115,6 +121,12 @@ const char* GetBuiltinShaderManifestRelativePath(const Containers::String& built if (builtinShaderPath == Containers::String(kBuiltinUnlitShaderPath)) { return kBuiltinUnlitShaderManifestRelativePath; } + if (builtinShaderPath == Containers::String(kBuiltinDepthOnlyShaderPath)) { + return kBuiltinDepthOnlyShaderManifestRelativePath; + } + if (builtinShaderPath == Containers::String(kBuiltinShadowCasterShaderPath)) { + return kBuiltinShadowCasterShaderManifestRelativePath; + } if (builtinShaderPath == Containers::String(kBuiltinObjectIdShaderPath)) { return kBuiltinObjectIdShaderManifestRelativePath; } @@ -646,6 +658,14 @@ Shader* BuildBuiltinUnlitShader(const Containers::String& path) { return TryLoadBuiltinShaderFromManifest(path); } +Shader* BuildBuiltinDepthOnlyShader(const Containers::String& path) { + return TryLoadBuiltinShaderFromManifest(path); +} + +Shader* BuildBuiltinShadowCasterShader(const Containers::String& path) { + return TryLoadBuiltinShaderFromManifest(path); +} + Shader* BuildBuiltinObjectIdShader(const Containers::String& path) { return TryLoadBuiltinShaderFromManifest(path); } @@ -756,6 +776,14 @@ Containers::String GetBuiltinUnlitShaderPath() { return Containers::String(kBuiltinUnlitShaderPath); } +Containers::String GetBuiltinDepthOnlyShaderPath() { + return Containers::String(kBuiltinDepthOnlyShaderPath); +} + +Containers::String GetBuiltinShadowCasterShaderPath() { + return Containers::String(kBuiltinShadowCasterShaderPath); +} + Containers::String GetBuiltinObjectIdShaderPath() { return Containers::String(kBuiltinObjectIdShaderPath); } @@ -852,6 +880,10 @@ LoadResult CreateBuiltinShaderResource(const Containers::String& path) { shader = BuildBuiltinForwardLitShader(path); } else if (path == GetBuiltinUnlitShaderPath()) { shader = BuildBuiltinUnlitShader(path); + } else if (path == GetBuiltinDepthOnlyShaderPath()) { + shader = BuildBuiltinDepthOnlyShader(path); + } else if (path == GetBuiltinShadowCasterShaderPath()) { + shader = BuildBuiltinShadowCasterShader(path); } else if (path == GetBuiltinObjectIdShaderPath()) { shader = BuildBuiltinObjectIdShader(path); } else { diff --git a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp index 9edc587a..c8fc42ef 100644 --- a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp +++ b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp @@ -1,6 +1,8 @@ #include +#include #include +#include #include #include #include @@ -217,6 +219,48 @@ TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromLegacyForwardResources) { EXPECT_EQ(setLayouts[4].heapType, DescriptorHeapType::Sampler); } +TEST(BuiltinDepthStylePass_Test, BuiltinDepthOnlyShaderDeclaresExplicitPerObjectResourceContract) { + ShaderLoader loader; + LoadResult result = loader.Load(GetBuiltinDepthOnlyShaderPath()); + ASSERT_TRUE(result); + ASSERT_NE(result.resource, nullptr); + + Shader* shader = static_cast(result.resource); + ASSERT_NE(shader, nullptr); + + const ShaderPass* pass = shader->FindPass("DepthOnly"); + ASSERT_NE(pass, nullptr); + ASSERT_EQ(pass->resources.Size(), 1u); + + EXPECT_EQ(pass->resources[0].semantic, "PerObject"); + EXPECT_EQ(pass->resources[0].type, ShaderResourceType::ConstantBuffer); + EXPECT_EQ(pass->resources[0].set, 0u); + EXPECT_EQ(pass->resources[0].binding, 0u); + + delete shader; +} + +TEST(BuiltinDepthStylePass_Test, BuiltinShadowCasterShaderDeclaresExplicitPerObjectResourceContract) { + ShaderLoader loader; + LoadResult result = loader.Load(GetBuiltinShadowCasterShaderPath()); + ASSERT_TRUE(result); + ASSERT_NE(result.resource, nullptr); + + Shader* shader = static_cast(result.resource); + ASSERT_NE(shader, nullptr); + + const ShaderPass* pass = shader->FindPass("ShadowCaster"); + ASSERT_NE(pass, nullptr); + ASSERT_EQ(pass->resources.Size(), 1u); + + EXPECT_EQ(pass->resources[0].semantic, "PerObject"); + EXPECT_EQ(pass->resources[0].type, ShaderResourceType::ConstantBuffer); + EXPECT_EQ(pass->resources[0].set, 0u); + EXPECT_EQ(pass->resources[0].binding, 0u); + + delete shader; +} + TEST(BuiltinObjectIdPass_Test, BuiltinObjectIdShaderDeclaresExplicitPerObjectResourceContract) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinObjectIdShaderPath()); @@ -303,6 +347,40 @@ TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromLegacyObjectIdResources) EXPECT_EQ(setLayouts[0].heapType, DescriptorHeapType::CBV_SRV_UAV); } +TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromLegacyDepthOnlyResources) { + const Array bindings = BuildLegacyBuiltinDepthOnlyPassResourceBindings(); + + BuiltinPassResourceBindingPlan plan = {}; + String error; + ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(bindings, plan, &error)) << error.CStr(); + + std::vector setLayouts; + ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr(); + ASSERT_EQ(setLayouts.size(), 1u); + EXPECT_EQ(setLayouts[0].layout.bindingCount, 1u); + EXPECT_TRUE(setLayouts[0].usesPerObject); + EXPECT_FALSE(setLayouts[0].usesMaterial); + EXPECT_FALSE(setLayouts[0].usesTexture); + EXPECT_FALSE(setLayouts[0].usesSampler); +} + +TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromLegacyShadowCasterResources) { + const Array bindings = BuildLegacyBuiltinShadowCasterPassResourceBindings(); + + BuiltinPassResourceBindingPlan plan = {}; + String error; + ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(bindings, plan, &error)) << error.CStr(); + + std::vector setLayouts; + ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr(); + ASSERT_EQ(setLayouts.size(), 1u); + EXPECT_EQ(setLayouts[0].layout.bindingCount, 1u); + EXPECT_TRUE(setLayouts[0].usesPerObject); + EXPECT_FALSE(setLayouts[0].usesMaterial); + EXPECT_FALSE(setLayouts[0].usesTexture); + EXPECT_FALSE(setLayouts[0].usesSampler); +} + TEST(BuiltinPassLayout_Test, RejectsMixedSamplerAndNonSamplerBindingsInOneSet) { Array bindings; bindings.Resize(2); @@ -353,3 +431,27 @@ TEST(BuiltinPassLayout_Test, RejectsDuplicateBindingsInOneSet) { EXPECT_FALSE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)); EXPECT_EQ(error, "Builtin pass encountered duplicate bindings inside one descriptor set"); } + +TEST(BuiltinDepthOnlyPass_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) { + const InputLayoutDesc inputLayout = BuiltinDepthOnlyPass::BuildInputLayout(); + + ASSERT_EQ(inputLayout.elements.size(), 3u); + EXPECT_EQ(inputLayout.elements[0].semanticName, "POSITION"); + EXPECT_EQ(inputLayout.elements[1].semanticName, "NORMAL"); + EXPECT_EQ(inputLayout.elements[2].semanticName, "TEXCOORD"); + EXPECT_EQ(inputLayout.elements[0].alignedByteOffset, static_cast(offsetof(StaticMeshVertex, position))); + EXPECT_EQ(inputLayout.elements[1].alignedByteOffset, static_cast(offsetof(StaticMeshVertex, normal))); + EXPECT_EQ(inputLayout.elements[2].alignedByteOffset, static_cast(offsetof(StaticMeshVertex, uv0))); +} + +TEST(BuiltinShadowCasterPass_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) { + const InputLayoutDesc inputLayout = BuiltinShadowCasterPass::BuildInputLayout(); + + ASSERT_EQ(inputLayout.elements.size(), 3u); + EXPECT_EQ(inputLayout.elements[0].semanticName, "POSITION"); + EXPECT_EQ(inputLayout.elements[1].semanticName, "NORMAL"); + EXPECT_EQ(inputLayout.elements[2].semanticName, "TEXCOORD"); + EXPECT_EQ(inputLayout.elements[0].alignedByteOffset, static_cast(offsetof(StaticMeshVertex, position))); + EXPECT_EQ(inputLayout.elements[1].alignedByteOffset, static_cast(offsetof(StaticMeshVertex, normal))); + EXPECT_EQ(inputLayout.elements[2].alignedByteOffset, static_cast(offsetof(StaticMeshVertex, uv0))); +} diff --git a/tests/Rendering/unit/test_render_scene_extractor.cpp b/tests/Rendering/unit/test_render_scene_extractor.cpp index ab09f03a..6c928430 100644 --- a/tests/Rendering/unit/test_render_scene_extractor.cpp +++ b/tests/Rendering/unit/test_render_scene_extractor.cpp @@ -373,6 +373,7 @@ TEST(RenderMaterialUtility_Test, MatchesBuiltinForwardLitPassMetadata) { Material shadowMaterial; shadowMaterial.SetShaderPass("ShadowCaster"); EXPECT_FALSE(MatchesBuiltinPass(&shadowMaterial, BuiltinMaterialPass::ForwardLit)); + EXPECT_TRUE(MatchesBuiltinPass(&shadowMaterial, BuiltinMaterialPass::ShadowCaster)); Material depthOnlyMaterial; depthOnlyMaterial.SetTag("LightMode", "DepthOnly");