diff --git a/engine/assets/builtin/shaders/depth-only/depth-only.frag.glsl b/engine/assets/builtin/shaders/depth-only/depth-only.frag.glsl index 17c196d7..64ba1d2f 100644 --- a/engine/assets/builtin/shaders/depth-only/depth-only.frag.glsl +++ b/engine/assets/builtin/shaders/depth-only/depth-only.frag.glsl @@ -1,7 +1,20 @@ // XC_BUILTIN_DEPTH_ONLY_OPENGL_PS #version 430 -layout(location = 0) out vec4 fragColor; + +layout(binding = 0) uniform sampler2D uBaseColorTexture; + +layout(std140, binding = 1) uniform MaterialConstants { + vec4 gBaseColorFactor; + vec4 gAlphaCutoffParams; +}; + +in vec2 vTexCoord; void main() { - fragColor = vec4(0.0); +#ifdef XC_ALPHA_TEST + vec4 baseColor = texture(uBaseColorTexture, vTexCoord) * gBaseColorFactor; + if (baseColor.a < gAlphaCutoffParams.x) { + discard; + } +#endif } 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 index 47b4ba21..cefdfb7b 100644 --- a/engine/assets/builtin/shaders/depth-only/depth-only.frag.vk.glsl +++ b/engine/assets/builtin/shaders/depth-only/depth-only.frag.vk.glsl @@ -1,7 +1,21 @@ // XC_BUILTIN_DEPTH_ONLY_VULKAN_PS #version 450 -layout(location = 0) out vec4 fragColor; + +layout(set = 2, binding = 0) uniform texture2D uBaseColorTexture; +layout(set = 3, binding = 0) uniform sampler uLinearSampler; + +layout(set = 1, binding = 0, std140) uniform MaterialConstants { + vec4 gBaseColorFactor; + vec4 gAlphaCutoffParams; +}; + +layout(location = 0) in vec2 vTexCoord; void main() { - fragColor = vec4(0.0); +#ifdef XC_ALPHA_TEST + vec4 baseColor = texture(sampler2D(uBaseColorTexture, uLinearSampler), vTexCoord) * gBaseColorFactor; + if (baseColor.a < gAlphaCutoffParams.x) { + discard; + } +#endif } diff --git a/engine/assets/builtin/shaders/depth-only/depth-only.ps.hlsl b/engine/assets/builtin/shaders/depth-only/depth-only.ps.hlsl index 97468462..be395f32 100644 --- a/engine/assets/builtin/shaders/depth-only/depth-only.ps.hlsl +++ b/engine/assets/builtin/shaders/depth-only/depth-only.ps.hlsl @@ -1,8 +1,20 @@ // XC_BUILTIN_DEPTH_ONLY_D3D12_PS -struct PSInput { - float4 position : SV_POSITION; +Texture2D gBaseColorTexture : register(t0); +SamplerState gLinearSampler : register(s0); + +cbuffer MaterialConstants : register(b1) { + float4 gBaseColorFactor; + float4 gAlphaCutoffParams; }; -float4 MainPS(PSInput input) : SV_TARGET { - return float4(0.0, 0.0, 0.0, 0.0); +struct PSInput { + float4 position : SV_POSITION; + float2 texcoord : TEXCOORD0; +}; + +void MainPS(PSInput input) { +#ifdef XC_ALPHA_TEST + float4 baseColor = gBaseColorTexture.Sample(gLinearSampler, input.texcoord) * gBaseColorFactor; + clip(baseColor.a - gAlphaCutoffParams.x); +#endif } diff --git a/engine/assets/builtin/shaders/depth-only/depth-only.shader b/engine/assets/builtin/shaders/depth-only/depth-only.shader index a098fdff..1a6692c4 100644 --- a/engine/assets/builtin/shaders/depth-only/depth-only.shader +++ b/engine/assets/builtin/shaders/depth-only/depth-only.shader @@ -1,5 +1,11 @@ Shader "Builtin Depth Only" { + Properties + { + _BaseColor ("Base Color", Color) = (1,1,1,1) [Semantic(BaseColor)] + _Cutoff ("Alpha Cutoff", Range) = 0.5 [Semantic(AlphaCutoff)] + _MainTex ("Base Map", 2D) = "white" [Semantic(BaseColorTexture)] + } SubShader { Pass @@ -9,10 +15,14 @@ Shader "Builtin Depth Only" Resources { PerObjectConstants (ConstantBuffer, 0, 0) [Semantic(PerObject)] + MaterialConstants (ConstantBuffer, 1, 0) [Semantic(Material)] + BaseColorTexture (Texture2D, 2, 0) [Semantic(BaseColorTexture)] + LinearClampSampler (Sampler, 3, 0) [Semantic(LinearClampSampler)] } HLSLPROGRAM #pragma vertex MainVS #pragma fragment MainPS + #pragma shader_feature_local _ XC_ALPHA_TEST #pragma backend D3D12 HLSL "depth-only.vs.hlsl" "depth-only.ps.hlsl" vs_5_0 ps_5_0 #pragma backend OpenGL GLSL "depth-only.vert.glsl" "depth-only.frag.glsl" #pragma backend Vulkan GLSL "depth-only.vert.vk.glsl" "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 index 3f6ce2f7..d72d9fda 100644 --- a/engine/assets/builtin/shaders/depth-only/depth-only.vert.glsl +++ b/engine/assets/builtin/shaders/depth-only/depth-only.vert.glsl @@ -1,6 +1,7 @@ // XC_BUILTIN_DEPTH_ONLY_OPENGL_VS #version 430 layout(location = 0) in vec3 aPosition; +layout(location = 2) in vec2 aTexCoord; layout(std140, binding = 0) uniform PerObjectConstants { mat4 gProjectionMatrix; @@ -8,8 +9,11 @@ layout(std140, binding = 0) uniform PerObjectConstants { mat4 gModelMatrix; }; +out vec2 vTexCoord; + void main() { vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0); vec4 positionVS = gViewMatrix * positionWS; gl_Position = gProjectionMatrix * positionVS; + vTexCoord = aTexCoord; } 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 index 5d9ac23f..301093bd 100644 --- a/engine/assets/builtin/shaders/depth-only/depth-only.vert.vk.glsl +++ b/engine/assets/builtin/shaders/depth-only/depth-only.vert.vk.glsl @@ -1,6 +1,7 @@ // XC_BUILTIN_DEPTH_ONLY_VULKAN_VS #version 450 layout(location = 0) in vec3 aPosition; +layout(location = 2) in vec2 aTexCoord; layout(set = 0, binding = 0, std140) uniform PerObjectConstants { mat4 gProjectionMatrix; @@ -8,8 +9,11 @@ layout(set = 0, binding = 0, std140) uniform PerObjectConstants { mat4 gModelMatrix; }; +layout(location = 0) out vec2 vTexCoord; + void main() { vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0); vec4 positionVS = gViewMatrix * positionWS; gl_Position = gProjectionMatrix * positionVS; + vTexCoord = aTexCoord; } diff --git a/engine/assets/builtin/shaders/depth-only/depth-only.vs.hlsl b/engine/assets/builtin/shaders/depth-only/depth-only.vs.hlsl index e1bd2ca4..7f471127 100644 --- a/engine/assets/builtin/shaders/depth-only/depth-only.vs.hlsl +++ b/engine/assets/builtin/shaders/depth-only/depth-only.vs.hlsl @@ -7,10 +7,12 @@ cbuffer PerObjectConstants : register(b0) { struct VSInput { float3 position : POSITION; + float2 texcoord : TEXCOORD0; }; struct PSInput { float4 position : SV_POSITION; + float2 texcoord : TEXCOORD0; }; PSInput MainVS(VSInput input) { @@ -18,5 +20,6 @@ PSInput MainVS(VSInput input) { float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0)); float4 positionVS = mul(gViewMatrix, positionWS); output.position = mul(gProjectionMatrix, positionVS); + output.texcoord = input.texcoord; return output; } diff --git a/engine/assets/builtin/shaders/forward-lit/forward-lit.frag.glsl b/engine/assets/builtin/shaders/forward-lit/forward-lit.frag.glsl index 82f66bb4..f7d7139c 100644 --- a/engine/assets/builtin/shaders/forward-lit/forward-lit.frag.glsl +++ b/engine/assets/builtin/shaders/forward-lit/forward-lit.frag.glsl @@ -28,6 +28,7 @@ layout(std140, binding = 1) uniform LightingConstants { layout(std140, binding = 2) uniform MaterialConstants { vec4 gBaseColorFactor; + vec4 gAlphaCutoffParams; }; layout(std140, binding = 3) uniform ShadowReceiverConstants { @@ -137,6 +138,11 @@ vec3 EvaluateAdditionalLight(AdditionalLightData light, vec3 normalWS, vec3 posi void main() { vec4 baseColor = texture(uBaseColorTexture, vTexCoord) * gBaseColorFactor; +#ifdef XC_ALPHA_TEST + if (baseColor.a < gAlphaCutoffParams.x) { + discard; + } +#endif int additionalLightCount = min(int(gLightingParams.x + 0.5), XC_MAX_ADDITIONAL_LIGHTS); if (gMainLightColorAndFlags.w < 0.5 && additionalLightCount == 0) { fragColor = baseColor; diff --git a/engine/assets/builtin/shaders/forward-lit/forward-lit.frag.vk.glsl b/engine/assets/builtin/shaders/forward-lit/forward-lit.frag.vk.glsl index d7824046..98a9dbb8 100644 --- a/engine/assets/builtin/shaders/forward-lit/forward-lit.frag.vk.glsl +++ b/engine/assets/builtin/shaders/forward-lit/forward-lit.frag.vk.glsl @@ -30,6 +30,7 @@ layout(set = 1, binding = 0, std140) uniform LightingConstants { layout(set = 2, binding = 0, std140) uniform MaterialConstants { vec4 gBaseColorFactor; + vec4 gAlphaCutoffParams; }; layout(set = 3, binding = 0, std140) uniform ShadowReceiverConstants { @@ -139,6 +140,11 @@ vec3 EvaluateAdditionalLight(AdditionalLightData light, vec3 normalWS, vec3 posi void main() { vec4 baseColor = texture(sampler2D(uBaseColorTexture, uLinearSampler), vTexCoord) * gBaseColorFactor; +#ifdef XC_ALPHA_TEST + if (baseColor.a < gAlphaCutoffParams.x) { + discard; + } +#endif int additionalLightCount = min(int(gLightingParams.x + 0.5), XC_MAX_ADDITIONAL_LIGHTS); if (gMainLightColorAndFlags.w < 0.5 && additionalLightCount == 0) { fragColor = baseColor; diff --git a/engine/assets/builtin/shaders/forward-lit/forward-lit.ps.hlsl b/engine/assets/builtin/shaders/forward-lit/forward-lit.ps.hlsl index 4aef0f97..592aff20 100644 --- a/engine/assets/builtin/shaders/forward-lit/forward-lit.ps.hlsl +++ b/engine/assets/builtin/shaders/forward-lit/forward-lit.ps.hlsl @@ -29,6 +29,7 @@ cbuffer LightingConstants : register(b1) { cbuffer MaterialConstants : register(b2) { float4 gBaseColorFactor; + float4 gAlphaCutoffParams; }; cbuffer ShadowReceiverConstants : register(b3) { @@ -139,6 +140,9 @@ float3 EvaluateAdditionalLight(AdditionalLightData light, float3 normalWS, float float4 MainPS(PSInput input) : SV_TARGET { float4 baseColor = gBaseColorTexture.Sample(gLinearSampler, input.texcoord) * gBaseColorFactor; +#ifdef XC_ALPHA_TEST + clip(baseColor.a - gAlphaCutoffParams.x); +#endif const int additionalLightCount = min((int)gLightingParams.x, XC_MAX_ADDITIONAL_LIGHTS); if (gMainLightColorAndFlags.a < 0.5f && additionalLightCount == 0) { return baseColor; diff --git a/engine/assets/builtin/shaders/forward-lit/forward-lit.shader b/engine/assets/builtin/shaders/forward-lit/forward-lit.shader index 84f0dc9d..06891ddb 100644 --- a/engine/assets/builtin/shaders/forward-lit/forward-lit.shader +++ b/engine/assets/builtin/shaders/forward-lit/forward-lit.shader @@ -3,6 +3,7 @@ Shader "Builtin Forward Lit" Properties { _BaseColor ("Base Color", Color) = (1,1,1,1) [Semantic(BaseColor)] + _Cutoff ("Alpha Cutoff", Range) = 0.5 [Semantic(AlphaCutoff)] _MainTex ("Base Map", 2D) = "white" [Semantic(BaseColorTexture)] } SubShader @@ -26,6 +27,7 @@ Shader "Builtin Forward Lit" #pragma vertex MainVS #pragma fragment MainPS #pragma multi_compile _ XC_MAIN_LIGHT_SHADOWS + #pragma shader_feature_local _ XC_ALPHA_TEST #pragma backend D3D12 HLSL "forward-lit.vs.hlsl" "forward-lit.ps.hlsl" vs_5_0 ps_5_0 #pragma backend OpenGL GLSL "forward-lit.vert.glsl" "forward-lit.frag.glsl" #pragma backend Vulkan GLSL "forward-lit.vert.vk.glsl" "forward-lit.frag.vk.glsl" diff --git a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.frag.glsl b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.frag.glsl index 5d27dd56..c28bbf29 100644 --- a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.frag.glsl +++ b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.frag.glsl @@ -1,7 +1,21 @@ // XC_BUILTIN_SHADOW_CASTER_OPENGL_PS #version 430 -layout(location = 0) out vec4 fragColor; + +layout(binding = 0) uniform sampler2D uBaseColorTexture; + +layout(std140, binding = 1) uniform MaterialConstants { + vec4 gBaseColorFactor; + vec4 gAlphaCutoffParams; +}; + +in vec2 vTexCoord; void main() { - fragColor = vec4(0.0); +#ifdef XC_ALPHA_TEST + vec4 baseColor = texture(uBaseColorTexture, vTexCoord) * gBaseColorFactor; + if (baseColor.a < gAlphaCutoffParams.x) { + discard; + } +#endif + gl_FragDepth = gl_FragCoord.z; } 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 index 9417195d..5f441c88 100644 --- a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.frag.vk.glsl +++ b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.frag.vk.glsl @@ -1,7 +1,22 @@ // XC_BUILTIN_SHADOW_CASTER_VULKAN_PS #version 450 -layout(location = 0) out vec4 fragColor; + +layout(set = 2, binding = 0) uniform texture2D uBaseColorTexture; +layout(set = 3, binding = 0) uniform sampler uLinearSampler; + +layout(set = 1, binding = 0, std140) uniform MaterialConstants { + vec4 gBaseColorFactor; + vec4 gAlphaCutoffParams; +}; + +layout(location = 0) in vec2 vTexCoord; void main() { - fragColor = vec4(0.0); +#ifdef XC_ALPHA_TEST + vec4 baseColor = texture(sampler2D(uBaseColorTexture, uLinearSampler), vTexCoord) * gBaseColorFactor; + if (baseColor.a < gAlphaCutoffParams.x) { + discard; + } +#endif + gl_FragDepth = gl_FragCoord.z; } diff --git a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.ps.hlsl b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.ps.hlsl index e3f3fa1d..6095a2ba 100644 --- a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.ps.hlsl +++ b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.ps.hlsl @@ -1,8 +1,21 @@ // XC_BUILTIN_SHADOW_CASTER_D3D12_PS -struct PSInput { - float4 position : SV_POSITION; +Texture2D gBaseColorTexture : register(t0); +SamplerState gLinearSampler : register(s0); + +cbuffer MaterialConstants : register(b1) { + float4 gBaseColorFactor; + float4 gAlphaCutoffParams; }; -float4 MainPS(PSInput input) : SV_TARGET { - return float4(0.0, 0.0, 0.0, 0.0); +struct PSInput { + float4 position : SV_POSITION; + float2 texcoord : TEXCOORD0; +}; + +float MainPS(PSInput input) : SV_Depth { +#ifdef XC_ALPHA_TEST + float4 baseColor = gBaseColorTexture.Sample(gLinearSampler, input.texcoord) * gBaseColorFactor; + clip(baseColor.a - gAlphaCutoffParams.x); +#endif + return input.position.z; } diff --git a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.shader b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.shader index d5a0244a..b80daf21 100644 --- a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.shader +++ b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.shader @@ -1,5 +1,11 @@ Shader "Builtin Shadow Caster" { + Properties + { + _BaseColor ("Base Color", Color) = (1,1,1,1) [Semantic(BaseColor)] + _Cutoff ("Alpha Cutoff", Range) = 0.5 [Semantic(AlphaCutoff)] + _MainTex ("Base Map", 2D) = "white" [Semantic(BaseColorTexture)] + } SubShader { Pass @@ -9,10 +15,14 @@ Shader "Builtin Shadow Caster" Resources { PerObjectConstants (ConstantBuffer, 0, 0) [Semantic(PerObject)] + MaterialConstants (ConstantBuffer, 1, 0) [Semantic(Material)] + BaseColorTexture (Texture2D, 2, 0) [Semantic(BaseColorTexture)] + LinearClampSampler (Sampler, 3, 0) [Semantic(LinearClampSampler)] } HLSLPROGRAM #pragma vertex MainVS #pragma fragment MainPS + #pragma shader_feature_local _ XC_ALPHA_TEST #pragma backend D3D12 HLSL "shadow-caster.vs.hlsl" "shadow-caster.ps.hlsl" vs_5_0 ps_5_0 #pragma backend OpenGL GLSL "shadow-caster.vert.glsl" "shadow-caster.frag.glsl" #pragma backend Vulkan GLSL "shadow-caster.vert.vk.glsl" "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 index 3e0c614b..7cc469bb 100644 --- a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.vert.glsl +++ b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.vert.glsl @@ -1,6 +1,7 @@ // XC_BUILTIN_SHADOW_CASTER_OPENGL_VS #version 430 layout(location = 0) in vec3 aPosition; +layout(location = 2) in vec2 aTexCoord; layout(std140, binding = 0) uniform PerObjectConstants { mat4 gProjectionMatrix; @@ -8,8 +9,11 @@ layout(std140, binding = 0) uniform PerObjectConstants { mat4 gModelMatrix; }; +out vec2 vTexCoord; + void main() { vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0); vec4 positionVS = gViewMatrix * positionWS; gl_Position = gProjectionMatrix * positionVS; + vTexCoord = aTexCoord; } 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 index 46fe8a19..31ec106e 100644 --- a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.vert.vk.glsl +++ b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.vert.vk.glsl @@ -1,6 +1,7 @@ // XC_BUILTIN_SHADOW_CASTER_VULKAN_VS #version 450 layout(location = 0) in vec3 aPosition; +layout(location = 2) in vec2 aTexCoord; layout(set = 0, binding = 0, std140) uniform PerObjectConstants { mat4 gProjectionMatrix; @@ -8,8 +9,11 @@ layout(set = 0, binding = 0, std140) uniform PerObjectConstants { mat4 gModelMatrix; }; +layout(location = 0) out vec2 vTexCoord; + void main() { vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0); vec4 positionVS = gViewMatrix * positionWS; gl_Position = gProjectionMatrix * positionVS; + vTexCoord = aTexCoord; } diff --git a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.vs.hlsl b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.vs.hlsl index d7a5bc23..a93fc8a8 100644 --- a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.vs.hlsl +++ b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.vs.hlsl @@ -7,16 +7,19 @@ cbuffer PerObjectConstants : register(b0) { struct VSInput { float3 position : POSITION; + float2 texcoord : TEXCOORD0; }; struct PSInput { float4 position : SV_POSITION; + float2 texcoord : TEXCOORD0; }; PSInput MainVS(VSInput input) { PSInput output; - float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0)); + float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0f)); float4 positionVS = mul(gViewMatrix, positionWS); output.position = mul(gProjectionMatrix, positionVS); + output.texcoord = input.texcoord; return output; } diff --git a/engine/include/XCEngine/Rendering/Materials/RenderMaterialResolve.h b/engine/include/XCEngine/Rendering/Materials/RenderMaterialResolve.h index fe15e071..66b9bbe0 100644 --- a/engine/include/XCEngine/Rendering/Materials/RenderMaterialResolve.h +++ b/engine/include/XCEngine/Rendering/Materials/RenderMaterialResolve.h @@ -14,6 +14,7 @@ namespace Rendering { struct BuiltinForwardMaterialData { Math::Vector4 baseColorFactor = Math::Vector4::One(); + float alphaCutoff = 0.5f; }; enum class BuiltinSkyboxTextureMode : Core::uint8 { @@ -141,9 +142,39 @@ inline const Resources::Texture* ResolveBuiltinBaseColorTexture(const Resources: return nullptr; } +inline float ResolveBuiltinAlphaCutoff(const Resources::Material* material) { + if (material == nullptr) { + return 0.5f; + } + + if (const Resources::ShaderPropertyDesc* property = FindShaderPropertyBySemantic(material, "AlphaCutoff")) { + if (material->HasProperty(property->name) && + (property->type == Resources::ShaderPropertyType::Float || + property->type == Resources::ShaderPropertyType::Range)) { + return material->GetFloat(property->name); + } + } + + static const char* kCutoffPropertyNames[] = { + "_Cutoff", + "cutoff", + "_AlphaCutoff", + "alphaCutoff" + }; + + for (const char* propertyName : kCutoffPropertyNames) { + if (material->HasProperty(Containers::String(propertyName))) { + return material->GetFloat(Containers::String(propertyName)); + } + } + + return 0.5f; +} + inline BuiltinForwardMaterialData BuildBuiltinForwardMaterialData(const Resources::Material* material) { BuiltinForwardMaterialData data = {}; data.baseColorFactor = ResolveBuiltinBaseColorFactor(material); + data.alphaCutoff = ResolveBuiltinAlphaCutoff(material); return data; } diff --git a/engine/include/XCEngine/Rendering/Passes/BuiltinDepthStylePassBase.h b/engine/include/XCEngine/Rendering/Passes/BuiltinDepthStylePassBase.h index a1599d43..fd4a6b12 100644 --- a/engine/include/XCEngine/Rendering/Passes/BuiltinDepthStylePassBase.h +++ b/engine/include/XCEngine/Rendering/Passes/BuiltinDepthStylePassBase.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -10,14 +11,19 @@ #include #include #include +#include +#include +#include #include #include +#include namespace XCEngine { namespace Resources { class Material; class Shader; +class Texture; } // namespace Resources namespace Rendering { @@ -54,6 +60,11 @@ private: RHI::RHIDescriptorSet* set = nullptr; }; + struct FallbackPerMaterialConstants { + Math::Vector4 baseColorFactor = Math::Vector4::One(); + Math::Vector4 alphaCutoffParams = Math::Vector4(0.5f, 0.0f, 0.0f, 0.0f); + }; + struct PassLayoutKey { const Resources::Shader* shader = nullptr; Containers::String passName; @@ -73,29 +84,46 @@ private: struct PassResourceLayout { RHI::RHIPipelineLayout* pipelineLayout = nullptr; - PassResourceBindingLocation perObject = {}; - BuiltinPassSetLayoutMetadata perObjectSetLayout = {}; Core::uint32 firstDescriptorSet = 0; + Core::uint32 descriptorSetCount = 0; + std::vector setLayouts; + std::vector staticDescriptorSets; + PassResourceBindingLocation perObject = {}; + PassResourceBindingLocation material = {}; + PassResourceBindingLocation baseColorTexture = {}; + PassResourceBindingLocation linearClampSampler = {}; }; - struct PerObjectSetKey { + struct DynamicDescriptorSetKey { PassLayoutKey passLayout = {}; + Core::uint32 setIndex = 0; Core::uint64 objectId = 0; + const Resources::Material* material = nullptr; - bool operator==(const PerObjectSetKey& other) const { + bool operator==(const DynamicDescriptorSetKey& other) const { return passLayout == other.passLayout && + setIndex == other.setIndex && + material == other.material && objectId == other.objectId; } }; - struct PerObjectSetKeyHash { - size_t operator()(const PerObjectSetKey& key) const noexcept { + struct DynamicDescriptorSetKeyHash { + size_t operator()(const DynamicDescriptorSetKey& key) const noexcept { size_t hash = PassLayoutKeyHash()(key.passLayout); + hash ^= std::hash{}(key.setIndex) + 0x9e3779b9u + (hash << 6) + (hash >> 2); hash ^= std::hash{}(key.objectId) + 0x9e3779b9u + (hash << 6) + (hash >> 2); + hash ^= reinterpret_cast(key.material) + 0x9e3779b9u + (hash << 6) + (hash >> 2); return hash; } }; + struct CachedDescriptorSet { + OwnedDescriptorSet descriptorSet = {}; + Core::uint64 materialVersion = 0; + RHI::RHIResourceView* baseColorTextureView = nullptr; + }; + struct ResolvedShaderPass { const Resources::Shader* shader = nullptr; const Resources::ShaderPass* pass = nullptr; @@ -157,12 +185,23 @@ private: bool CreateOwnedDescriptorSet( const BuiltinPassSetLayoutMetadata& setLayout, OwnedDescriptorSet& descriptorSet); - RHI::RHIDescriptorSet* GetOrCreatePerObjectSet( + RHI::RHIDescriptorSet* GetOrCreateStaticDescriptorSet( + const PassLayoutKey& passLayoutKey, + PassResourceLayout& passLayout, + Core::uint32 setIndex); + CachedDescriptorSet* GetOrCreateDynamicDescriptorSet( const PassLayoutKey& passLayoutKey, const PassResourceLayout& passLayout, - Core::uint64 objectId); + const BuiltinPassSetLayoutMetadata& setLayout, + Core::uint32 setIndex, + Core::uint64 objectId, + const Resources::Material* material, + const MaterialConstantPayloadView& materialConstants, + RHI::RHIResourceView* baseColorTextureView); void DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet); void DestroyPassResourceLayout(PassResourceLayout& passLayout); + RHI::RHIResourceView* ResolveTextureView(const Resources::Texture* texture); + RHI::RHIResourceView* ResolveTextureView(const VisibleRenderItem& visibleItem); bool DrawVisibleItem( const RenderContext& context, const RenderSurface& surface, @@ -178,7 +217,10 @@ private: std::unordered_map m_passResourceLayouts; std::unordered_map m_pipelineStates; - std::unordered_map m_perObjectSets; + std::unordered_map m_dynamicDescriptorSets; + RHI::RHISampler* m_sampler = nullptr; + RHI::RHITexture* m_fallbackTexture2D = nullptr; + RHI::RHIResourceView* m_fallbackTexture2DView = nullptr; }; } // namespace Passes diff --git a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h index 5c142235..62869036 100644 --- a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h +++ b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h @@ -98,6 +98,7 @@ private: struct FallbackPerMaterialConstants { Math::Vector4 baseColorFactor = Math::Vector4::One(); + Math::Vector4 alphaCutoffParams = Math::Vector4(0.5f, 0.0f, 0.0f, 0.0f); }; struct SkyboxConstants { diff --git a/engine/src/Rendering/Passes/BuiltinDepthStylePassBaseResources.cpp b/engine/src/Rendering/Passes/BuiltinDepthStylePassBaseResources.cpp index 8206e086..0c308fa4 100644 --- a/engine/src/Rendering/Passes/BuiltinDepthStylePassBaseResources.cpp +++ b/engine/src/Rendering/Passes/BuiltinDepthStylePassBaseResources.cpp @@ -11,7 +11,9 @@ #include "Rendering/RenderSurface.h" #include "Resources/Material/Material.h" #include "Resources/Mesh/Mesh.h" +#include "Resources/Texture/Texture.h" +#include #include namespace XCEngine { @@ -37,6 +39,40 @@ bool IsSupportedPerObjectOnlyBindingPlan(const BuiltinPassResourceBindingPlan& b !bindingPlan.usesSamplers; } +bool UsesContiguousDescriptorSets(const BuiltinPassResourceBindingPlan& bindingPlan) { + if (bindingPlan.bindings.Empty()) { + return true; + } + + std::vector usedSets(static_cast(bindingPlan.maxSetIndex) + 1u, false); + Core::uint32 minSet = UINT32_MAX; + Core::uint32 maxSet = 0; + for (const BuiltinPassResourceBindingDesc& binding : bindingPlan.bindings) { + usedSets[binding.location.set] = true; + minSet = std::min(minSet, binding.location.set); + maxSet = std::max(maxSet, binding.location.set); + } + + for (Core::uint32 setIndex = minSet; setIndex <= maxSet; ++setIndex) { + if (!usedSets[setIndex]) { + return false; + } + } + + return true; +} + +bool IsSupportedAlphaTestBindingPlan(const BuiltinPassResourceBindingPlan& bindingPlan) { + return bindingPlan.perObject.IsValid() && + bindingPlan.material.IsValid() && + bindingPlan.baseColorTexture.IsValid() && + bindingPlan.linearClampSampler.IsValid() && + !bindingPlan.lighting.IsValid() && + !bindingPlan.shadowReceiver.IsValid() && + !bindingPlan.shadowMapTexture.IsValid() && + !bindingPlan.shadowMapSampler.IsValid(); +} + uint32_t ResolveSurfaceColorAttachmentCount(const RenderSurface& surface) { const std::vector& colorAttachments = surface.GetColorAttachments(); return (!colorAttachments.empty() && colorAttachments[0] != nullptr) ? 1u : 0u; @@ -131,16 +167,66 @@ bool BuiltinDepthStylePassBase::CreateResources(const RenderContext& context) { 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) { + DestroyResources(); + 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) { + DestroyResources(); + 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) { + DestroyResources(); + return false; + } + return true; } void BuiltinDepthStylePassBase::DestroyResources() { m_resourceCache.Shutdown(); - for (auto& descriptorSetEntry : m_perObjectSets) { - DestroyOwnedDescriptorSet(descriptorSetEntry.second); + for (auto& descriptorSetEntry : m_dynamicDescriptorSets) { + DestroyOwnedDescriptorSet(descriptorSetEntry.second.descriptorSet); } - m_perObjectSets.clear(); + m_dynamicDescriptorSets.clear(); for (auto& pipelineEntry : m_pipelineStates) { if (pipelineEntry.second != nullptr) { @@ -155,6 +241,24 @@ void BuiltinDepthStylePassBase::DestroyResources() { } m_passResourceLayouts.clear(); + 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; + } + m_device = nullptr; m_backendType = RHI::RHIType::D3D12; m_builtinShader.Reset(); @@ -235,7 +339,7 @@ BuiltinDepthStylePassBase::ResolvedShaderPass BuiltinDepthStylePassBase::Resolve } if (m_builtinShader.IsValid() && - tryResolveFromShader(m_builtinShader.Get(), nullptr)) { + tryResolveFromShader(m_builtinShader.Get(), material)) { return resolved; } @@ -259,9 +363,19 @@ bool BuiltinDepthStylePassBase::TryBuildSupportedBindingPlan( return false; } - if (!IsSupportedPerObjectOnlyBindingPlan(outPlan)) { + if (!UsesContiguousDescriptorSets(outPlan)) { if (outError != nullptr) { - *outError = "Builtin depth-style pass currently requires exactly one PerObject constant-buffer binding"; + *outError = "Builtin depth-style pass requires contiguous descriptor set indices"; + } + return false; + } + + if (!IsSupportedPerObjectOnlyBindingPlan(outPlan) && + !IsSupportedAlphaTestBindingPlan(outPlan)) { + if (outError != nullptr) { + *outError = + "Builtin depth-style pass requires either PerObject-only bindings or the alpha-test contract " + "(PerObject + Material + BaseColorTexture + LinearClampSampler)"; } return false; } @@ -304,24 +418,26 @@ BuiltinDepthStylePassBase::PassResourceLayout* BuiltinDepthStylePassBase::GetOrC return failLayout(contextualError.CStr()); } - std::vector setLayouts; Containers::String setLayoutError; - if (!TryBuildBuiltinPassSetLayouts(bindingPlan, setLayouts, &setLayoutError)) { + if (!TryBuildBuiltinPassSetLayouts(bindingPlan, passLayout.setLayouts, &setLayoutError)) { return failLayout(setLayoutError.CStr()); } - if (bindingPlan.perObject.set >= setLayouts.size()) { + if (bindingPlan.perObject.set >= passLayout.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); + passLayout.descriptorSetCount = bindingPlan.descriptorSetCount; + passLayout.staticDescriptorSets.resize(passLayout.setLayouts.size()); + passLayout.perObject = bindingPlan.perObject; + passLayout.material = bindingPlan.material; + passLayout.baseColorTexture = bindingPlan.baseColorTexture; + passLayout.linearClampSampler = bindingPlan.linearClampSampler; - std::vector nativeSetLayouts(setLayouts.size()); - for (size_t setIndex = 0; setIndex < setLayouts.size(); ++setIndex) { - nativeSetLayouts[setIndex] = setLayouts[setIndex].layout; + std::vector nativeSetLayouts(passLayout.setLayouts.size()); + for (size_t setIndex = 0; setIndex < passLayout.setLayouts.size(); ++setIndex) { + nativeSetLayouts[setIndex] = passLayout.setLayouts[setIndex].layout; } RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {}; @@ -333,7 +449,7 @@ BuiltinDepthStylePassBase::PassResourceLayout* BuiltinDepthStylePassBase::GetOrC } const auto result = m_passResourceLayouts.emplace(passLayoutKey, std::move(passLayout)); - RefreshBuiltinPassSetLayoutMetadata(result.first->second.perObjectSetLayout); + RefreshBuiltinPassSetLayouts(result.first->second.setLayouts); return &result.first->second; } @@ -412,31 +528,98 @@ bool BuiltinDepthStylePassBase::CreateOwnedDescriptorSet( return true; } -RHI::RHIDescriptorSet* BuiltinDepthStylePassBase::GetOrCreatePerObjectSet( +RHI::RHIDescriptorSet* BuiltinDepthStylePassBase::GetOrCreateStaticDescriptorSet( + const PassLayoutKey& passLayoutKey, + PassResourceLayout& passLayout, + Core::uint32 setIndex) { + (void)passLayoutKey; + + if (setIndex >= passLayout.setLayouts.size() || + setIndex >= passLayout.staticDescriptorSets.size()) { + return nullptr; + } + + const BuiltinPassSetLayoutMetadata& setLayout = passLayout.setLayouts[setIndex]; + if (setLayout.layout.bindingCount == 0) { + return nullptr; + } + + OwnedDescriptorSet& descriptorSet = passLayout.staticDescriptorSets[setIndex]; + if (descriptorSet.set == nullptr) { + if (!CreateOwnedDescriptorSet(setLayout, descriptorSet)) { + return nullptr; + } + + if (setLayout.usesLinearClampSampler) { + if (!passLayout.linearClampSampler.IsValid() || + passLayout.linearClampSampler.set != setIndex || + m_sampler == nullptr) { + DestroyOwnedDescriptorSet(descriptorSet); + return nullptr; + } + + descriptorSet.set->UpdateSampler(passLayout.linearClampSampler.binding, m_sampler); + } + } + + return descriptorSet.set; +} + +BuiltinDepthStylePassBase::CachedDescriptorSet* BuiltinDepthStylePassBase::GetOrCreateDynamicDescriptorSet( const PassLayoutKey& passLayoutKey, const PassResourceLayout& passLayout, - Core::uint64 objectId) { - if (!passLayout.perObject.IsValid() || - passLayout.perObjectSetLayout.layout.bindingCount == 0) { - return nullptr; - } - - PerObjectSetKey key = {}; + const BuiltinPassSetLayoutMetadata& setLayout, + Core::uint32 setIndex, + Core::uint64 objectId, + const Resources::Material* material, + const MaterialConstantPayloadView& materialConstants, + RHI::RHIResourceView* baseColorTextureView) { + DynamicDescriptorSetKey key = {}; key.passLayout = passLayoutKey; + key.setIndex = setIndex; key.objectId = objectId; + key.material = material; - const auto existing = m_perObjectSets.find(key); - if (existing != m_perObjectSets.end()) { - return existing->second.set; + CachedDescriptorSet& cachedDescriptorSet = m_dynamicDescriptorSets[key]; + if (cachedDescriptorSet.descriptorSet.set == nullptr) { + if (!CreateOwnedDescriptorSet(setLayout, cachedDescriptorSet.descriptorSet)) { + return nullptr; + } } - OwnedDescriptorSet descriptorSet = {}; - if (!CreateOwnedDescriptorSet(passLayout.perObjectSetLayout, descriptorSet)) { - return nullptr; + const Core::uint64 materialVersion = material != nullptr ? material->GetChangeVersion() : 0; + if (setLayout.usesMaterial) { + if (!passLayout.material.IsValid() || + passLayout.material.set != setIndex || + !materialConstants.IsValid()) { + return nullptr; + } + + if (cachedDescriptorSet.materialVersion != materialVersion) { + cachedDescriptorSet.descriptorSet.set->WriteConstant( + passLayout.material.binding, + materialConstants.data, + materialConstants.size); + } } - const auto result = m_perObjectSets.emplace(key, descriptorSet); - return result.first->second.set; + if (setLayout.usesBaseColorTexture) { + if (baseColorTextureView == nullptr || + !passLayout.baseColorTexture.IsValid() || + passLayout.baseColorTexture.set != setIndex) { + return nullptr; + } + + if (cachedDescriptorSet.baseColorTextureView != baseColorTextureView) { + cachedDescriptorSet.descriptorSet.set->Update( + passLayout.baseColorTexture.binding, + baseColorTextureView); + } + } + + cachedDescriptorSet.materialVersion = materialVersion; + cachedDescriptorSet.baseColorTextureView = baseColorTextureView; + return &cachedDescriptorSet; } void BuiltinDepthStylePassBase::DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet) { @@ -454,15 +637,46 @@ void BuiltinDepthStylePassBase::DestroyOwnedDescriptorSet(OwnedDescriptorSet& de } void BuiltinDepthStylePassBase::DestroyPassResourceLayout(PassResourceLayout& passLayout) { + for (OwnedDescriptorSet& descriptorSet : passLayout.staticDescriptorSets) { + DestroyOwnedDescriptorSet(descriptorSet); + } + passLayout.staticDescriptorSets.clear(); + if (passLayout.pipelineLayout != nullptr) { passLayout.pipelineLayout->Shutdown(); delete passLayout.pipelineLayout; passLayout.pipelineLayout = nullptr; } + passLayout.setLayouts.clear(); passLayout.perObject = {}; - passLayout.perObjectSetLayout = {}; + passLayout.material = {}; + passLayout.baseColorTexture = {}; + passLayout.linearClampSampler = {}; passLayout.firstDescriptorSet = 0; + passLayout.descriptorSetCount = 0; +} + +RHI::RHIResourceView* BuiltinDepthStylePassBase::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* BuiltinDepthStylePassBase::ResolveTextureView( + const VisibleRenderItem& visibleItem) { + const Resources::Material* material = ResolveMaterial(visibleItem); + const Resources::Texture* texture = ResolveBuiltinBaseColorTexture(material); + RHI::RHIResourceView* textureView = ResolveTextureView(texture); + return textureView != nullptr ? textureView : m_fallbackTexture2DView; } bool BuiltinDepthStylePassBase::DrawVisibleItem( @@ -531,18 +745,6 @@ bool BuiltinDepthStylePassBase::DrawVisibleItem( commandList->SetIndexBuffer(cachedMesh->indexBufferView, 0); } - RHI::RHIDescriptorSet* constantSet = GetOrCreatePerObjectSet( - passLayoutKey, - *passLayout, - visibleItem.gameObject->GetID()); - if (constantSet == nullptr) { - Debug::Logger::Get().Error( - Debug::LogCategory::Rendering, - (Containers::String("BuiltinDepthStylePassBase failed to allocate descriptor set for ") + - visibleItem.gameObject->GetName().c_str()).CStr()); - return false; - } - const Math::Matrix4x4 projectionMatrix = m_passType == BuiltinMaterialPass::ShadowCaster ? sceneData.cameraData.viewProjection @@ -556,14 +758,122 @@ bool BuiltinDepthStylePassBase::DrawVisibleItem( viewMatrix, visibleItem.localToWorld.Transpose() }; - constantSet->WriteConstant(passLayout->perObject.binding, &constants, sizeof(constants)); - RHI::RHIDescriptorSet* descriptorSets[] = { constantSet }; - commandList->SetGraphicsDescriptorSets( - passLayout->firstDescriptorSet, - 1, - descriptorSets, - passLayout->pipelineLayout); + MaterialConstantPayloadView materialConstants = ResolveSchemaMaterialConstantPayload(material); + FallbackPerMaterialConstants fallbackMaterialConstants = {}; + if (!materialConstants.IsValid()) { + const BuiltinForwardMaterialData materialData = BuildBuiltinForwardMaterialData(material); + fallbackMaterialConstants.baseColorFactor = materialData.baseColorFactor; + fallbackMaterialConstants.alphaCutoffParams = Math::Vector4( + materialData.alphaCutoff, + 0.0f, + 0.0f, + 0.0f); + static const Resources::MaterialConstantFieldDesc kFallbackMaterialConstantFields[] = { + { + Containers::String("_BaseColor"), + Resources::MaterialPropertyType::Float4, + 0, + sizeof(Math::Vector4), + sizeof(Math::Vector4) + }, + { + Containers::String("_Cutoff"), + Resources::MaterialPropertyType::Float, + sizeof(Math::Vector4), + sizeof(float), + sizeof(Math::Vector4) + } + }; + materialConstants.data = &fallbackMaterialConstants; + materialConstants.size = sizeof(fallbackMaterialConstants); + materialConstants.layout = { + kFallbackMaterialConstantFields, + 2, + sizeof(fallbackMaterialConstants) + }; + } + + RHI::RHIResourceView* baseColorTextureView = ResolveTextureView(visibleItem); + if (passLayout->baseColorTexture.IsValid() && baseColorTextureView == nullptr) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + (Containers::String("BuiltinDepthStylePassBase failed to resolve base color texture for ") + + visibleItem.gameObject->GetName().c_str()).CStr()); + 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]; + if (setLayout.layout.bindingCount == 0) { + return false; + } + + RHI::RHIDescriptorSet* descriptorSet = nullptr; + if (setLayout.usesPerObject || + setLayout.usesMaterial || + setLayout.usesBaseColorTexture) { + const Core::uint64 objectId = + setLayout.usesPerObject ? visibleItem.gameObject->GetID() : 0; + const Resources::Material* materialKey = + (setLayout.usesMaterial || setLayout.usesBaseColorTexture) ? material : nullptr; + + CachedDescriptorSet* cachedDescriptorSet = GetOrCreateDynamicDescriptorSet( + passLayoutKey, + *passLayout, + setLayout, + setIndex, + objectId, + materialKey, + materialConstants, + baseColorTextureView); + if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + (Containers::String("BuiltinDepthStylePassBase failed to allocate dynamic descriptor set for ") + + visibleItem.gameObject->GetName().c_str()).CStr()); + 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(passLayoutKey, *passLayout, setIndex); + if (descriptorSet == nullptr) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + (Containers::String("BuiltinDepthStylePassBase failed to allocate static descriptor set for ") + + visibleItem.gameObject->GetName().c_str()).CStr()); + return false; + } + } + + descriptorSets[descriptorOffset] = descriptorSet; + } + + commandList->SetGraphicsDescriptorSets( + passLayout->firstDescriptorSet, + passLayout->descriptorSetCount, + descriptorSets.data(), + passLayout->pipelineLayout); + } if (visibleItem.hasSection) { const Containers::Array& sections = visibleItem.mesh->GetSections(); diff --git a/engine/src/Rendering/Pipelines/BuiltinForwardPipelineResources.cpp b/engine/src/Rendering/Pipelines/BuiltinForwardPipelineResources.cpp index 48934cb9..d622bf4e 100644 --- a/engine/src/Rendering/Pipelines/BuiltinForwardPipelineResources.cpp +++ b/engine/src/Rendering/Pipelines/BuiltinForwardPipelineResources.cpp @@ -183,7 +183,7 @@ BuiltinForwardPipeline::ResolvedShaderPass BuiltinForwardPipeline::ResolveSurfac if (builtinShaderHandle->IsValid()) { const Resources::Shader* builtinShader = builtinShaderHandle->Get(); if (const Resources::ShaderPass* shaderPass = - FindCompatibleSurfacePass(*builtinShader, sceneData, nullptr, pass, backend)) { + FindCompatibleSurfacePass(*builtinShader, sceneData, material, pass, backend)) { resolved.shader = builtinShader; resolved.pass = shaderPass; resolved.passName = shaderPass->name; @@ -705,18 +705,32 @@ bool BuiltinForwardPipeline::DrawVisibleItem( if (!materialConstants.IsValid()) { const BuiltinForwardMaterialData materialData = BuildBuiltinForwardMaterialData(material); fallbackMaterialConstants.baseColorFactor = materialData.baseColorFactor; - static const Resources::MaterialConstantFieldDesc kFallbackMaterialConstantField = { - Containers::String("baseColorFactor"), - Resources::MaterialPropertyType::Float4, - 0, - sizeof(FallbackPerMaterialConstants), - sizeof(FallbackPerMaterialConstants) + fallbackMaterialConstants.alphaCutoffParams = Math::Vector4( + materialData.alphaCutoff, + 0.0f, + 0.0f, + 0.0f); + static const Resources::MaterialConstantFieldDesc kFallbackMaterialConstantFields[] = { + { + Containers::String("_BaseColor"), + Resources::MaterialPropertyType::Float4, + 0, + sizeof(Math::Vector4), + sizeof(Math::Vector4) + }, + { + Containers::String("_Cutoff"), + Resources::MaterialPropertyType::Float, + sizeof(Math::Vector4), + sizeof(float), + sizeof(Math::Vector4) + } }; materialConstants.data = &fallbackMaterialConstants; materialConstants.size = sizeof(fallbackMaterialConstants); materialConstants.layout = { - &kFallbackMaterialConstantField, - 1, + kFallbackMaterialConstantFields, + 2, sizeof(fallbackMaterialConstants) }; } diff --git a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp index 3b0cdfec..e6ced0c6 100644 --- a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp +++ b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp @@ -62,6 +62,11 @@ TEST(BuiltinForwardPipeline_Test, BuiltinForwardShaderDeclaresExplicitForwardRes Shader* shader = static_cast(result.resource); ASSERT_NE(shader, nullptr); + const ShaderPropertyDesc* cutoff = shader->FindProperty("_Cutoff"); + ASSERT_NE(cutoff, nullptr); + EXPECT_EQ(cutoff->type, ShaderPropertyType::Range); + EXPECT_EQ(cutoff->semantic, "AlphaCutoff"); + const ShaderPass* pass = shader->FindPass("ForwardLit"); ASSERT_NE(pass, nullptr); ASSERT_EQ(pass->resources.Size(), 8u); @@ -418,12 +423,24 @@ TEST(BuiltinDepthStylePass_Test, BuiltinDepthOnlyShaderDeclaresExplicitPerObject const ShaderPass* pass = shader->FindPass("DepthOnly"); ASSERT_NE(pass, nullptr); - ASSERT_EQ(pass->resources.Size(), 1u); + ASSERT_EQ(pass->resources.Size(), 4u); 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); + EXPECT_EQ(pass->resources[1].semantic, "Material"); + EXPECT_EQ(pass->resources[1].type, ShaderResourceType::ConstantBuffer); + EXPECT_EQ(pass->resources[1].set, 1u); + EXPECT_EQ(pass->resources[1].binding, 0u); + EXPECT_EQ(pass->resources[2].semantic, "BaseColorTexture"); + EXPECT_EQ(pass->resources[2].type, ShaderResourceType::Texture2D); + EXPECT_EQ(pass->resources[2].set, 2u); + EXPECT_EQ(pass->resources[2].binding, 0u); + EXPECT_EQ(pass->resources[3].semantic, "LinearClampSampler"); + EXPECT_EQ(pass->resources[3].type, ShaderResourceType::Sampler); + EXPECT_EQ(pass->resources[3].set, 3u); + EXPECT_EQ(pass->resources[3].binding, 0u); delete shader; } @@ -439,12 +456,24 @@ TEST(BuiltinDepthStylePass_Test, BuiltinShadowCasterShaderDeclaresExplicitPerObj const ShaderPass* pass = shader->FindPass("ShadowCaster"); ASSERT_NE(pass, nullptr); - ASSERT_EQ(pass->resources.Size(), 1u); + ASSERT_EQ(pass->resources.Size(), 4u); 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); + EXPECT_EQ(pass->resources[1].semantic, "Material"); + EXPECT_EQ(pass->resources[1].type, ShaderResourceType::ConstantBuffer); + EXPECT_EQ(pass->resources[1].set, 1u); + EXPECT_EQ(pass->resources[1].binding, 0u); + EXPECT_EQ(pass->resources[2].semantic, "BaseColorTexture"); + EXPECT_EQ(pass->resources[2].type, ShaderResourceType::Texture2D); + EXPECT_EQ(pass->resources[2].set, 2u); + EXPECT_EQ(pass->resources[2].binding, 0u); + EXPECT_EQ(pass->resources[3].semantic, "LinearClampSampler"); + EXPECT_EQ(pass->resources[3].type, ShaderResourceType::Sampler); + EXPECT_EQ(pass->resources[3].set, 3u); + EXPECT_EQ(pass->resources[3].binding, 0u); delete shader; } @@ -569,20 +598,47 @@ TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromExplicitDepthOnlyResource const ShaderPass* pass = shader->FindPass("DepthOnly"); ASSERT_NE(pass, nullptr); - ASSERT_EQ(pass->resources.Size(), 1u); + ASSERT_EQ(pass->resources.Size(), 4u); BuiltinPassResourceBindingPlan plan = {}; String error; ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass->resources, plan, &error)) << error.CStr(); + EXPECT_TRUE(plan.perObject.IsValid()); + EXPECT_TRUE(plan.material.IsValid()); + EXPECT_TRUE(plan.baseColorTexture.IsValid()); + EXPECT_TRUE(plan.linearClampSampler.IsValid()); + EXPECT_EQ(plan.firstDescriptorSet, 0u); + EXPECT_EQ(plan.descriptorSetCount, 4u); + EXPECT_TRUE(plan.usesConstantBuffers); + EXPECT_TRUE(plan.usesTextures); + EXPECT_TRUE(plan.usesSamplers); std::vector setLayouts; ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr(); - ASSERT_EQ(setLayouts.size(), 1u); + ASSERT_EQ(setLayouts.size(), 4u); 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); + EXPECT_EQ(setLayouts[1].layout.bindingCount, 1u); + EXPECT_FALSE(setLayouts[1].usesPerObject); + EXPECT_TRUE(setLayouts[1].usesMaterial); + EXPECT_FALSE(setLayouts[1].usesTexture); + EXPECT_FALSE(setLayouts[1].usesSampler); + EXPECT_EQ(setLayouts[2].layout.bindingCount, 1u); + EXPECT_FALSE(setLayouts[2].usesPerObject); + EXPECT_FALSE(setLayouts[2].usesMaterial); + EXPECT_TRUE(setLayouts[2].usesTexture); + EXPECT_FALSE(setLayouts[2].usesSampler); + EXPECT_TRUE(setLayouts[2].shaderVisible); + EXPECT_EQ(setLayouts[3].layout.bindingCount, 1u); + EXPECT_FALSE(setLayouts[3].usesPerObject); + EXPECT_FALSE(setLayouts[3].usesMaterial); + EXPECT_FALSE(setLayouts[3].usesTexture); + EXPECT_TRUE(setLayouts[3].usesSampler); + EXPECT_TRUE(setLayouts[3].shaderVisible); + EXPECT_EQ(setLayouts[3].heapType, DescriptorHeapType::Sampler); delete shader; } @@ -598,20 +654,47 @@ TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromExplicitShadowCasterResou const ShaderPass* pass = shader->FindPass("ShadowCaster"); ASSERT_NE(pass, nullptr); - ASSERT_EQ(pass->resources.Size(), 1u); + ASSERT_EQ(pass->resources.Size(), 4u); BuiltinPassResourceBindingPlan plan = {}; String error; ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass->resources, plan, &error)) << error.CStr(); + EXPECT_TRUE(plan.perObject.IsValid()); + EXPECT_TRUE(plan.material.IsValid()); + EXPECT_TRUE(plan.baseColorTexture.IsValid()); + EXPECT_TRUE(plan.linearClampSampler.IsValid()); + EXPECT_EQ(plan.firstDescriptorSet, 0u); + EXPECT_EQ(plan.descriptorSetCount, 4u); + EXPECT_TRUE(plan.usesConstantBuffers); + EXPECT_TRUE(plan.usesTextures); + EXPECT_TRUE(plan.usesSamplers); std::vector setLayouts; ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr(); - ASSERT_EQ(setLayouts.size(), 1u); + ASSERT_EQ(setLayouts.size(), 4u); 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); + EXPECT_EQ(setLayouts[1].layout.bindingCount, 1u); + EXPECT_FALSE(setLayouts[1].usesPerObject); + EXPECT_TRUE(setLayouts[1].usesMaterial); + EXPECT_FALSE(setLayouts[1].usesTexture); + EXPECT_FALSE(setLayouts[1].usesSampler); + EXPECT_EQ(setLayouts[2].layout.bindingCount, 1u); + EXPECT_FALSE(setLayouts[2].usesPerObject); + EXPECT_FALSE(setLayouts[2].usesMaterial); + EXPECT_TRUE(setLayouts[2].usesTexture); + EXPECT_FALSE(setLayouts[2].usesSampler); + EXPECT_TRUE(setLayouts[2].shaderVisible); + EXPECT_EQ(setLayouts[3].layout.bindingCount, 1u); + EXPECT_FALSE(setLayouts[3].usesPerObject); + EXPECT_FALSE(setLayouts[3].usesMaterial); + EXPECT_FALSE(setLayouts[3].usesTexture); + EXPECT_TRUE(setLayouts[3].usesSampler); + EXPECT_TRUE(setLayouts[3].shaderVisible); + EXPECT_EQ(setLayouts[3].heapType, DescriptorHeapType::Sampler); delete shader; } diff --git a/tests/Rendering/unit/test_render_scene_extractor.cpp b/tests/Rendering/unit/test_render_scene_extractor.cpp index e12e8d43..6921668d 100644 --- a/tests/Rendering/unit/test_render_scene_extractor.cpp +++ b/tests/Rendering/unit/test_render_scene_extractor.cpp @@ -574,10 +574,13 @@ TEST(RenderMaterialUtility_Test, ShadersWithoutBuiltinMetadataKeepImplicitForwar TEST(RenderMaterialUtility_Test, ResolvesBuiltinForwardMaterialContractFromCanonicalNamesAndAliases) { Material canonicalMaterial; canonicalMaterial.SetFloat4("baseColor", Vector4(0.2f, 0.4f, 0.6f, 0.8f)); + canonicalMaterial.SetFloat("_Cutoff", 0.3f); EXPECT_EQ(ResolveBuiltinBaseColorFactor(&canonicalMaterial), Vector4(0.2f, 0.4f, 0.6f, 0.8f)); + EXPECT_FLOAT_EQ(ResolveBuiltinAlphaCutoff(&canonicalMaterial), 0.3f); Material aliasMaterial; aliasMaterial.SetFloat4("_BaseColor", Vector4(0.7f, 0.6f, 0.5f, 0.4f)); + aliasMaterial.SetFloat("_Cutoff", 0.42f); Texture* baseColorTexture = new Texture(); IResource::ConstructParams textureParams = {}; textureParams.name = "AliasBaseColor"; @@ -588,6 +591,7 @@ TEST(RenderMaterialUtility_Test, ResolvesBuiltinForwardMaterialContractFromCanon const BuiltinForwardMaterialData materialData = BuildBuiltinForwardMaterialData(&aliasMaterial); EXPECT_EQ(materialData.baseColorFactor, Vector4(0.7f, 0.6f, 0.5f, 0.4f)); + EXPECT_FLOAT_EQ(materialData.alphaCutoff, 0.42f); EXPECT_EQ(ResolveBuiltinBaseColorTexture(&aliasMaterial), baseColorTexture); } @@ -608,9 +612,18 @@ TEST(RenderMaterialUtility_Test, ResolvesBuiltinForwardMaterialContractFromShade textureProperty.semantic = "BaseColorTexture"; shader->AddProperty(textureProperty); + ShaderPropertyDesc cutoffProperty = {}; + cutoffProperty.name = "AlphaClipThreshold"; + cutoffProperty.displayName = "Alpha Clip"; + cutoffProperty.type = ShaderPropertyType::Range; + cutoffProperty.defaultValue = "0.5"; + cutoffProperty.semantic = "AlphaCutoff"; + shader->AddProperty(cutoffProperty); + Material material; material.SetShader(ResourceHandle(shader)); material.SetFloat4("TintColor", Vector4(0.3f, 0.5f, 0.7f, 0.9f)); + material.SetFloat("AlphaClipThreshold", 0.61f); Texture* texture = new Texture(); IResource::ConstructParams textureParams = {}; @@ -622,6 +635,7 @@ TEST(RenderMaterialUtility_Test, ResolvesBuiltinForwardMaterialContractFromShade EXPECT_EQ(ResolveBuiltinBaseColorFactor(&material), Vector4(0.3f, 0.5f, 0.7f, 0.9f)); EXPECT_EQ(ResolveBuiltinBaseColorTexture(&material), texture); + EXPECT_FLOAT_EQ(ResolveBuiltinAlphaCutoff(&material), 0.61f); } TEST(RenderMaterialUtility_Test, ResolvesBuiltinForwardMaterialContractFromShaderSemanticDefaults) { @@ -635,10 +649,18 @@ TEST(RenderMaterialUtility_Test, ResolvesBuiltinForwardMaterialContractFromShade colorProperty.semantic = "BaseColor"; shader->AddProperty(colorProperty); + ShaderPropertyDesc cutoffProperty = {}; + cutoffProperty.name = "_Cutoff"; + cutoffProperty.type = ShaderPropertyType::Range; + cutoffProperty.defaultValue = "0.37"; + cutoffProperty.semantic = "AlphaCutoff"; + shader->AddProperty(cutoffProperty); + Material material; material.SetShader(ResourceHandle(shader)); EXPECT_EQ(ResolveBuiltinBaseColorFactor(&material), Vector4(0.11f, 0.22f, 0.33f, 0.44f)); + EXPECT_FLOAT_EQ(ResolveBuiltinAlphaCutoff(&material), 0.37f); EXPECT_EQ(ResolveBuiltinBaseColorTexture(&material), nullptr); } @@ -652,25 +674,38 @@ TEST(RenderMaterialUtility_Test, ExposesSchemaDrivenMaterialConstantPayload) { colorProperty.semantic = "BaseColor"; shader->AddProperty(colorProperty); + ShaderPropertyDesc cutoffProperty = {}; + cutoffProperty.name = "_Cutoff"; + cutoffProperty.type = ShaderPropertyType::Range; + cutoffProperty.defaultValue = "0.6"; + cutoffProperty.semantic = "AlphaCutoff"; + shader->AddProperty(cutoffProperty); + Material material; material.SetShader(ResourceHandle(shader)); const MaterialConstantPayloadView payload = ResolveSchemaMaterialConstantPayload(&material); ASSERT_TRUE(payload.IsValid()); - ASSERT_EQ(payload.size, 16u); + ASSERT_EQ(payload.size, 32u); ASSERT_TRUE(payload.layout.IsValid()); - ASSERT_EQ(payload.layout.count, 1u); - EXPECT_EQ(payload.layout.size, 16u); + ASSERT_EQ(payload.layout.count, 2u); + EXPECT_EQ(payload.layout.size, 32u); EXPECT_EQ(payload.layout.fields[0].name, "_BaseColor"); EXPECT_EQ(payload.layout.fields[0].offset, 0u); EXPECT_EQ(payload.layout.fields[0].size, 16u); EXPECT_EQ(payload.layout.fields[0].alignedSize, 16u); + EXPECT_EQ(payload.layout.fields[1].name, "_Cutoff"); + EXPECT_EQ(payload.layout.fields[1].offset, 16u); + EXPECT_EQ(payload.layout.fields[1].size, 4u); + EXPECT_EQ(payload.layout.fields[1].alignedSize, 16u); const float* values = static_cast(payload.data); EXPECT_FLOAT_EQ(values[0], 0.25f); EXPECT_FLOAT_EQ(values[1], 0.5f); EXPECT_FLOAT_EQ(values[2], 0.75f); EXPECT_FLOAT_EQ(values[3], 1.0f); + const float* cutoffValues = static_cast(payload.data) + 4; + EXPECT_FLOAT_EQ(cutoffValues[0], 0.6f); } TEST(RenderMaterialUtility_Test, UsesOpacityOnlyWhenBaseColorFactorIsMissing) { diff --git a/tests/Resources/Shader/test_shader_loader.cpp b/tests/Resources/Shader/test_shader_loader.cpp index ff89ae87..154bd625 100644 --- a/tests/Resources/Shader/test_shader_loader.cpp +++ b/tests/Resources/Shader/test_shader_loader.cpp @@ -1334,8 +1334,8 @@ TEST(ShaderLoader, LoadBuiltinForwardLitShaderBuildsBackendVariants) { const ShaderPass* pass = shader->FindPass("ForwardLit"); ASSERT_NE(pass, nullptr); - ASSERT_EQ(shader->GetProperties().Size(), 2u); - ASSERT_EQ(pass->variants.Size(), 12u); + ASSERT_EQ(shader->GetProperties().Size(), 3u); + ASSERT_EQ(pass->variants.Size(), 24u); ASSERT_EQ(pass->tags.Size(), 1u); ASSERT_EQ(pass->resources.Size(), 8u); EXPECT_EQ(pass->tags[0].name, "LightMode"); @@ -1351,6 +1351,11 @@ TEST(ShaderLoader, LoadBuiltinForwardLitShaderBuildsBackendVariants) { EXPECT_EQ(baseMapProperty->type, ShaderPropertyType::Texture2D); EXPECT_EQ(baseMapProperty->semantic, "BaseColorTexture"); + const ShaderPropertyDesc* cutoffProperty = shader->FindProperty("_Cutoff"); + ASSERT_NE(cutoffProperty, nullptr); + EXPECT_EQ(cutoffProperty->type, ShaderPropertyType::Range); + EXPECT_EQ(cutoffProperty->semantic, "AlphaCutoff"); + const ShaderResourceBindingDesc* perObjectBinding = shader->FindPassResourceBinding("ForwardLit", "PerObjectConstants"); ASSERT_NE(perObjectBinding, nullptr); @@ -1390,13 +1395,18 @@ TEST(ShaderLoader, LoadBuiltinForwardLitShaderBuildsBackendVariants) { EXPECT_EQ(shadowTextureBinding->set, 6u); EXPECT_EQ(shadowTextureBinding->binding, 0u); EXPECT_EQ(shadowTextureBinding->semantic, "ShadowMapTexture"); - ASSERT_EQ(pass->keywordDeclarations.Size(), 1u); + ASSERT_EQ(pass->keywordDeclarations.Size(), 2u); EXPECT_EQ(pass->keywordDeclarations[0].type, ShaderKeywordDeclarationType::MultiCompile); ASSERT_EQ(pass->keywordDeclarations[0].options.Size(), 2u); EXPECT_EQ(pass->keywordDeclarations[0].options[0], "_"); EXPECT_EQ(pass->keywordDeclarations[0].options[1], "XC_MAIN_LIGHT_SHADOWS"); + EXPECT_EQ(pass->keywordDeclarations[1].type, ShaderKeywordDeclarationType::ShaderFeatureLocal); + ASSERT_EQ(pass->keywordDeclarations[1].options.Size(), 2u); + EXPECT_EQ(pass->keywordDeclarations[1].options[0], "_"); + EXPECT_EQ(pass->keywordDeclarations[1].options[1], "XC_ALPHA_TEST"); EXPECT_TRUE(shader->PassDeclaresKeyword("ForwardLit", "XC_MAIN_LIGHT_SHADOWS")); - ASSERT_EQ(pass->variants.Size(), 12u); + EXPECT_TRUE(shader->PassDeclaresKeyword("ForwardLit", "XC_ALPHA_TEST")); + ASSERT_EQ(pass->variants.Size(), 24u); EXPECT_NE(shader->FindVariant("ForwardLit", ShaderType::Vertex, ShaderBackend::D3D12), nullptr); EXPECT_NE(shader->FindVariant("ForwardLit", ShaderType::Fragment, ShaderBackend::D3D12), nullptr); @@ -1437,6 +1447,23 @@ TEST(ShaderLoader, LoadBuiltinForwardLitShaderBuildsBackendVariants) { std::string(shadowD3D12Fragment->sourceCode.CStr()).find("gShadowMapTexture.Sample"), std::string::npos); + ShaderKeywordSet alphaShadowKeywords = {}; + alphaShadowKeywords.enabledKeywords.PushBack("XC_ALPHA_TEST"); + alphaShadowKeywords.enabledKeywords.PushBack("XC_MAIN_LIGHT_SHADOWS"); + + const ShaderStageVariant* alphaShadowD3D12Fragment = shader->FindVariant( + "ForwardLit", + ShaderType::Fragment, + ShaderBackend::D3D12, + alphaShadowKeywords); + ASSERT_NE(alphaShadowD3D12Fragment, nullptr); + EXPECT_NE( + std::string(alphaShadowD3D12Fragment->sourceCode.CStr()).find("#define XC_ALPHA_TEST 1"), + std::string::npos); + EXPECT_NE( + std::string(alphaShadowD3D12Fragment->sourceCode.CStr()).find("gAlphaCutoffParams"), + std::string::npos); + const ShaderStageVariant* openglFragment = shader->FindVariant( "ForwardLit", ShaderType::Fragment, @@ -1591,15 +1618,33 @@ TEST(ShaderLoader, LoadBuiltinDepthOnlyShaderBuildsBackendVariants) { const ShaderPass* pass = shader->FindPass("DepthOnly"); ASSERT_NE(pass, nullptr); - ASSERT_EQ(pass->resources.Size(), 1u); + ASSERT_EQ(shader->GetProperties().Size(), 3u); + ASSERT_EQ(pass->resources.Size(), 4u); 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); - ASSERT_EQ(pass->variants.Size(), 6u); + EXPECT_EQ(pass->resources[1].semantic, "Material"); + EXPECT_EQ(pass->resources[1].type, ShaderResourceType::ConstantBuffer); + EXPECT_EQ(pass->resources[1].set, 1u); + EXPECT_EQ(pass->resources[1].binding, 0u); + EXPECT_EQ(pass->resources[2].semantic, "BaseColorTexture"); + EXPECT_EQ(pass->resources[2].type, ShaderResourceType::Texture2D); + EXPECT_EQ(pass->resources[2].set, 2u); + EXPECT_EQ(pass->resources[2].binding, 0u); + EXPECT_EQ(pass->resources[3].semantic, "LinearClampSampler"); + EXPECT_EQ(pass->resources[3].type, ShaderResourceType::Sampler); + EXPECT_EQ(pass->resources[3].set, 3u); + EXPECT_EQ(pass->resources[3].binding, 0u); + ASSERT_EQ(pass->variants.Size(), 12u); ASSERT_EQ(pass->tags.Size(), 1u); EXPECT_EQ(pass->tags[0].name, "LightMode"); EXPECT_EQ(pass->tags[0].value, "DepthOnly"); + ASSERT_EQ(pass->keywordDeclarations.Size(), 1u); + EXPECT_EQ(pass->keywordDeclarations[0].type, ShaderKeywordDeclarationType::ShaderFeatureLocal); + ASSERT_EQ(pass->keywordDeclarations[0].options.Size(), 2u); + EXPECT_EQ(pass->keywordDeclarations[0].options[0], "_"); + EXPECT_EQ(pass->keywordDeclarations[0].options[1], "XC_ALPHA_TEST"); EXPECT_NE(shader->FindVariant("DepthOnly", ShaderType::Vertex, ShaderBackend::D3D12), nullptr); EXPECT_NE(shader->FindVariant("DepthOnly", ShaderType::Fragment, ShaderBackend::D3D12), nullptr); @@ -1629,6 +1674,22 @@ TEST(ShaderLoader, LoadBuiltinDepthOnlyShaderBuildsBackendVariants) { ASSERT_NE(vulkanFragment, nullptr); EXPECT_NE(std::string(vulkanFragment->sourceCode.CStr()).find("XC_BUILTIN_DEPTH_ONLY_VULKAN_PS"), std::string::npos); + ShaderKeywordSet alphaKeywords = {}; + alphaKeywords.enabledKeywords.PushBack("XC_ALPHA_TEST"); + + const ShaderStageVariant* alphaD3D12Fragment = shader->FindVariant( + "DepthOnly", + ShaderType::Fragment, + ShaderBackend::D3D12, + alphaKeywords); + ASSERT_NE(alphaD3D12Fragment, nullptr); + EXPECT_NE( + std::string(alphaD3D12Fragment->sourceCode.CStr()).find("#define XC_ALPHA_TEST 1"), + std::string::npos); + EXPECT_NE( + std::string(alphaD3D12Fragment->sourceCode.CStr()).find("gAlphaCutoffParams"), + std::string::npos); + delete shader; } @@ -1644,15 +1705,33 @@ TEST(ShaderLoader, LoadBuiltinShadowCasterShaderBuildsBackendVariants) { const ShaderPass* pass = shader->FindPass("ShadowCaster"); ASSERT_NE(pass, nullptr); - ASSERT_EQ(pass->resources.Size(), 1u); + ASSERT_EQ(shader->GetProperties().Size(), 3u); + ASSERT_EQ(pass->resources.Size(), 4u); 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); - ASSERT_EQ(pass->variants.Size(), 6u); + EXPECT_EQ(pass->resources[1].semantic, "Material"); + EXPECT_EQ(pass->resources[1].type, ShaderResourceType::ConstantBuffer); + EXPECT_EQ(pass->resources[1].set, 1u); + EXPECT_EQ(pass->resources[1].binding, 0u); + EXPECT_EQ(pass->resources[2].semantic, "BaseColorTexture"); + EXPECT_EQ(pass->resources[2].type, ShaderResourceType::Texture2D); + EXPECT_EQ(pass->resources[2].set, 2u); + EXPECT_EQ(pass->resources[2].binding, 0u); + EXPECT_EQ(pass->resources[3].semantic, "LinearClampSampler"); + EXPECT_EQ(pass->resources[3].type, ShaderResourceType::Sampler); + EXPECT_EQ(pass->resources[3].set, 3u); + EXPECT_EQ(pass->resources[3].binding, 0u); + ASSERT_EQ(pass->variants.Size(), 12u); ASSERT_EQ(pass->tags.Size(), 1u); EXPECT_EQ(pass->tags[0].name, "LightMode"); EXPECT_EQ(pass->tags[0].value, "ShadowCaster"); + ASSERT_EQ(pass->keywordDeclarations.Size(), 1u); + EXPECT_EQ(pass->keywordDeclarations[0].type, ShaderKeywordDeclarationType::ShaderFeatureLocal); + ASSERT_EQ(pass->keywordDeclarations[0].options.Size(), 2u); + EXPECT_EQ(pass->keywordDeclarations[0].options[0], "_"); + EXPECT_EQ(pass->keywordDeclarations[0].options[1], "XC_ALPHA_TEST"); EXPECT_NE(shader->FindVariant("ShadowCaster", ShaderType::Vertex, ShaderBackend::D3D12), nullptr); EXPECT_NE(shader->FindVariant("ShadowCaster", ShaderType::Fragment, ShaderBackend::D3D12), nullptr); @@ -1688,6 +1767,22 @@ TEST(ShaderLoader, LoadBuiltinShadowCasterShaderBuildsBackendVariants) { std::string(vulkanFragment->sourceCode.CStr()).find("XC_BUILTIN_SHADOW_CASTER_VULKAN_PS"), std::string::npos); + ShaderKeywordSet alphaKeywords = {}; + alphaKeywords.enabledKeywords.PushBack("XC_ALPHA_TEST"); + + const ShaderStageVariant* alphaD3D12Fragment = shader->FindVariant( + "ShadowCaster", + ShaderType::Fragment, + ShaderBackend::D3D12, + alphaKeywords); + ASSERT_NE(alphaD3D12Fragment, nullptr); + EXPECT_NE( + std::string(alphaD3D12Fragment->sourceCode.CStr()).find("#define XC_ALPHA_TEST 1"), + std::string::npos); + EXPECT_NE( + std::string(alphaD3D12Fragment->sourceCode.CStr()).find("gAlphaCutoffParams"), + std::string::npos); + delete shader; }