From 5f9f3386ab4c8b03efdf4981819f3b4b81744cb7 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Tue, 7 Apr 2026 03:35:06 +0800 Subject: [PATCH] rendering: unify builtin forward and depth-style shaders --- .../shaders/depth-only/depth-only.frag.glsl | 20 - .../depth-only/depth-only.frag.vk.glsl | 21 - .../shaders/depth-only/depth-only.ps.hlsl | 20 - .../shaders/depth-only/depth-only.shader | 61 ++- .../shaders/depth-only/depth-only.vert.glsl | 19 - .../depth-only/depth-only.vert.vk.glsl | 19 - .../shaders/depth-only/depth-only.vs.hlsl | 25 - .../shaders/forward-lit/forward-lit.frag.glsl | 174 ------ .../forward-lit/forward-lit.frag.vk.glsl | 176 ------ .../shaders/forward-lit/forward-lit.ps.hlsl | 174 ------ .../shaders/forward-lit/forward-lit.shader | 237 +++++++- .../shaders/forward-lit/forward-lit.vert.glsl | 25 - .../forward-lit/forward-lit.vert.vk.glsl | 25 - .../shaders/forward-lit/forward-lit.vs.hlsl | 31 -- .../shadow-caster/shadow-caster.frag.glsl | 21 - .../shadow-caster/shadow-caster.frag.vk.glsl | 22 - .../shadow-caster/shadow-caster.ps.hlsl | 21 - .../shadow-caster/shadow-caster.shader | 61 ++- .../shadow-caster/shadow-caster.vert.glsl | 19 - .../shadow-caster/shadow-caster.vert.vk.glsl | 19 - .../shadow-caster/shadow-caster.vs.hlsl | 25 - .../include/XCEngine/RHI/D3D12/D3D12Shader.h | 9 +- engine/src/RHI/D3D12/D3D12Device.cpp | 32 +- engine/src/RHI/D3D12/D3D12Shader.cpp | 22 +- engine/src/RHI/OpenGL/OpenGLDevice.cpp | 166 ++++++ .../unit/test_builtin_forward_pipeline.cpp | 507 ++++++++++++++---- tests/Resources/Shader/test_shader_loader.cpp | 335 +++++++----- 27 files changed, 1135 insertions(+), 1151 deletions(-) delete mode 100644 engine/assets/builtin/shaders/depth-only/depth-only.frag.glsl delete mode 100644 engine/assets/builtin/shaders/depth-only/depth-only.frag.vk.glsl delete mode 100644 engine/assets/builtin/shaders/depth-only/depth-only.ps.hlsl delete mode 100644 engine/assets/builtin/shaders/depth-only/depth-only.vert.glsl delete mode 100644 engine/assets/builtin/shaders/depth-only/depth-only.vert.vk.glsl delete mode 100644 engine/assets/builtin/shaders/depth-only/depth-only.vs.hlsl delete mode 100644 engine/assets/builtin/shaders/forward-lit/forward-lit.frag.glsl delete mode 100644 engine/assets/builtin/shaders/forward-lit/forward-lit.frag.vk.glsl delete mode 100644 engine/assets/builtin/shaders/forward-lit/forward-lit.ps.hlsl delete mode 100644 engine/assets/builtin/shaders/forward-lit/forward-lit.vert.glsl delete mode 100644 engine/assets/builtin/shaders/forward-lit/forward-lit.vert.vk.glsl delete mode 100644 engine/assets/builtin/shaders/forward-lit/forward-lit.vs.hlsl delete mode 100644 engine/assets/builtin/shaders/shadow-caster/shadow-caster.frag.glsl delete mode 100644 engine/assets/builtin/shaders/shadow-caster/shadow-caster.frag.vk.glsl delete mode 100644 engine/assets/builtin/shaders/shadow-caster/shadow-caster.ps.hlsl delete mode 100644 engine/assets/builtin/shaders/shadow-caster/shadow-caster.vert.glsl delete mode 100644 engine/assets/builtin/shaders/shadow-caster/shadow-caster.vert.vk.glsl delete mode 100644 engine/assets/builtin/shaders/shadow-caster/shadow-caster.vs.hlsl diff --git a/engine/assets/builtin/shaders/depth-only/depth-only.frag.glsl b/engine/assets/builtin/shaders/depth-only/depth-only.frag.glsl deleted file mode 100644 index 64ba1d2f..00000000 --- a/engine/assets/builtin/shaders/depth-only/depth-only.frag.glsl +++ /dev/null @@ -1,20 +0,0 @@ -// XC_BUILTIN_DEPTH_ONLY_OPENGL_PS -#version 430 - -layout(binding = 0) uniform sampler2D uBaseColorTexture; - -layout(std140, binding = 1) uniform MaterialConstants { - vec4 gBaseColorFactor; - vec4 gAlphaCutoffParams; -}; - -in vec2 vTexCoord; - -void main() { -#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 deleted file mode 100644 index cefdfb7b..00000000 --- a/engine/assets/builtin/shaders/depth-only/depth-only.frag.vk.glsl +++ /dev/null @@ -1,21 +0,0 @@ -// XC_BUILTIN_DEPTH_ONLY_VULKAN_PS -#version 450 - -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() { -#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 deleted file mode 100644 index be395f32..00000000 --- a/engine/assets/builtin/shaders/depth-only/depth-only.ps.hlsl +++ /dev/null @@ -1,20 +0,0 @@ -// XC_BUILTIN_DEPTH_ONLY_D3D12_PS -Texture2D gBaseColorTexture : register(t0); -SamplerState gLinearSampler : register(s0); - -cbuffer MaterialConstants : register(b1) { - float4 gBaseColorFactor; - float4 gAlphaCutoffParams; -}; - -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 1a6692c4..3af8365e 100644 --- a/engine/assets/builtin/shaders/depth-only/depth-only.shader +++ b/engine/assets/builtin/shaders/depth-only/depth-only.shader @@ -6,26 +6,67 @@ Shader "Builtin Depth Only" _Cutoff ("Alpha Cutoff", Range) = 0.5 [Semantic(AlphaCutoff)] _MainTex ("Base Map", 2D) = "white" [Semantic(BaseColorTexture)] } + HLSLINCLUDE + cbuffer PerObjectConstants : register(b0) + { + float4x4 gProjectionMatrix; + float4x4 gViewMatrix; + float4x4 gModelMatrix; + }; + + cbuffer MaterialConstants : register(b1) + { + float4 gBaseColorFactor; + float4 gAlphaCutoffParams; + }; + + Texture2D BaseColorTexture : register(t0); + SamplerState LinearClampSampler : register(s0); + + struct VSInput + { + float3 position : POSITION; + float3 normal : NORMAL; + float2 texcoord : TEXCOORD0; + }; + + struct PSInput + { + float4 position : SV_POSITION; + float2 texcoord : TEXCOORD0; + }; + + PSInput MainVS(VSInput input) + { + PSInput output; + const float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0f)); + const float4 positionVS = mul(gViewMatrix, positionWS); + output.position = mul(gProjectionMatrix, positionVS); + output.texcoord = input.texcoord; + return output; + } + + float MainPS(PSInput input) : SV_Depth + { + #ifdef XC_ALPHA_TEST + const float4 baseColor = + BaseColorTexture.Sample(LinearClampSampler, input.texcoord) * gBaseColorFactor; + clip(baseColor.a - gAlphaCutoffParams.x); + #endif + return input.position.z; + } + ENDHLSL SubShader { Pass { Name "DepthOnly" Tags { "LightMode" = "DepthOnly" } - 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 target 4.5 #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" ENDHLSL } } diff --git a/engine/assets/builtin/shaders/depth-only/depth-only.vert.glsl b/engine/assets/builtin/shaders/depth-only/depth-only.vert.glsl deleted file mode 100644 index d72d9fda..00000000 --- a/engine/assets/builtin/shaders/depth-only/depth-only.vert.glsl +++ /dev/null @@ -1,19 +0,0 @@ -// 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; - mat4 gViewMatrix; - 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 deleted file mode 100644 index 301093bd..00000000 --- a/engine/assets/builtin/shaders/depth-only/depth-only.vert.vk.glsl +++ /dev/null @@ -1,19 +0,0 @@ -// 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; - mat4 gViewMatrix; - 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 deleted file mode 100644 index 7f471127..00000000 --- a/engine/assets/builtin/shaders/depth-only/depth-only.vs.hlsl +++ /dev/null @@ -1,25 +0,0 @@ -// XC_BUILTIN_DEPTH_ONLY_D3D12_VS -cbuffer PerObjectConstants : register(b0) { - float4x4 gProjectionMatrix; - float4x4 gViewMatrix; - float4x4 gModelMatrix; -}; - -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 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 deleted file mode 100644 index f7d7139c..00000000 --- a/engine/assets/builtin/shaders/forward-lit/forward-lit.frag.glsl +++ /dev/null @@ -1,174 +0,0 @@ -// XC_BUILTIN_FORWARD_LIT_OPENGL_PS -#version 430 -layout(binding = 0) uniform sampler2D uBaseColorTexture; -layout(binding = 1) uniform sampler2D uShadowMapTexture; - -layout(std140, binding = 0) uniform PerObjectConstants { - mat4 gProjectionMatrix; - mat4 gViewMatrix; - mat4 gModelMatrix; - mat4 gNormalMatrix; -}; - -const int XC_MAX_ADDITIONAL_LIGHTS = 8; - -struct AdditionalLightData { - vec4 colorAndIntensity; - vec4 positionAndRange; - vec4 directionAndType; - vec4 spotAnglesAndFlags; -}; - -layout(std140, binding = 1) uniform LightingConstants { - vec4 gMainLightDirectionAndIntensity; - vec4 gMainLightColorAndFlags; - vec4 gLightingParams; - AdditionalLightData gAdditionalLights[XC_MAX_ADDITIONAL_LIGHTS]; -}; - -layout(std140, binding = 2) uniform MaterialConstants { - vec4 gBaseColorFactor; - vec4 gAlphaCutoffParams; -}; - -layout(std140, binding = 3) uniform ShadowReceiverConstants { - mat4 gWorldToShadowMatrix; - vec4 gShadowBiasAndTexelSize; - vec4 gShadowOptions; -}; - -in vec3 vNormalWS; -in vec2 vTexCoord; -in vec3 vPositionWS; - -layout(location = 0) out vec4 fragColor; - -float ComputeShadowAttenuation(vec3 positionWS) { -#ifndef XC_MAIN_LIGHT_SHADOWS - return 1.0; -#else - if (gShadowOptions.x < 0.5) { - return 1.0; - } - - vec4 shadowClip = gWorldToShadowMatrix * vec4(positionWS, 1.0); - if (shadowClip.w <= 0.0) { - return 1.0; - } - - vec3 shadowNdc = shadowClip.xyz / shadowClip.w; - vec2 shadowUv = vec2( - shadowNdc.x * 0.5 + 0.5, - shadowNdc.y * 0.5 + 0.5); - if (shadowUv.x < 0.0 || shadowUv.x > 1.0 || - shadowUv.y < 0.0 || shadowUv.y > 1.0 || - shadowNdc.z < -1.0 || shadowNdc.z > 1.0) { - return 1.0; - } - - float shadowDepth = texture(uShadowMapTexture, shadowUv).r; - float receiverDepth = shadowNdc.z * 0.5 + 0.5 - gShadowBiasAndTexelSize.x; - float shadowStrength = clamp(gShadowBiasAndTexelSize.w, 0.0, 1.0); - return receiverDepth <= shadowDepth ? 1.0 : (1.0 - shadowStrength); -#endif -} - -float ComputeRangeAttenuation(float distanceSq, float range) { - if (range <= 0.0) { - return 0.0; - } - - float clampedRange = max(range, 0.0001); - float rangeSq = clampedRange * clampedRange; - if (distanceSq >= rangeSq) { - return 0.0; - } - - float distance = sqrt(max(distanceSq, 0.0)); - float normalized = clamp(1.0 - distance / clampedRange, 0.0, 1.0); - return normalized * normalized; -} - -float ComputeSpotAttenuation(AdditionalLightData light, vec3 directionToLightWS) { - float cosOuter = light.spotAnglesAndFlags.x; - float cosInner = light.spotAnglesAndFlags.y; - vec3 spotAxisToLightWS = normalize(light.directionAndType.xyz); - float cosTheta = dot(spotAxisToLightWS, directionToLightWS); - return clamp((cosTheta - cosOuter) / max(cosInner - cosOuter, 1e-4), 0.0, 1.0); -} - -vec3 EvaluateAdditionalLight(AdditionalLightData light, vec3 normalWS, vec3 positionWS) { - float lightType = light.directionAndType.w; - vec3 lightColor = light.colorAndIntensity.rgb; - float lightIntensity = light.colorAndIntensity.w; - - vec3 directionToLightWS = vec3(0.0); - float attenuation = 1.0; - - if (lightType < 0.5) { - directionToLightWS = normalize(light.directionAndType.xyz); - } else { - vec3 lightVectorWS = light.positionAndRange.xyz - positionWS; - float distanceSq = dot(lightVectorWS, lightVectorWS); - if (distanceSq <= 1e-6) { - return vec3(0.0); - } - - directionToLightWS = normalize(lightVectorWS); - attenuation = ComputeRangeAttenuation(distanceSq, light.positionAndRange.w); - if (attenuation <= 0.0) { - return vec3(0.0); - } - - if (lightType > 1.5) { - attenuation *= ComputeSpotAttenuation(light, directionToLightWS); - if (attenuation <= 0.0) { - return vec3(0.0); - } - } - } - - float diffuse = max(dot(normalWS, directionToLightWS), 0.0); - if (diffuse <= 0.0) { - return vec3(0.0); - } - - return lightColor * (diffuse * lightIntensity * attenuation); -} - -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; - return; - } - - vec3 normalWS = normalize(vNormalWS); - vec3 lighting = gLightingParams.yyy; - - if (gMainLightColorAndFlags.w >= 0.5) { - vec3 directionToLightWS = normalize(gMainLightDirectionAndIntensity.xyz); - float diffuse = max(dot(normalWS, directionToLightWS), 0.0); - float shadowAttenuation = diffuse > 0.0 - ? ComputeShadowAttenuation(vPositionWS) - : 1.0; - lighting += - gMainLightColorAndFlags.rgb * (diffuse * gMainLightDirectionAndIntensity.w * shadowAttenuation); - } - - for (int lightIndex = 0; lightIndex < XC_MAX_ADDITIONAL_LIGHTS; ++lightIndex) { - if (lightIndex >= additionalLightCount) { - break; - } - - lighting += EvaluateAdditionalLight(gAdditionalLights[lightIndex], normalWS, vPositionWS); - } - - fragColor = vec4(baseColor.rgb * lighting, baseColor.a); -} 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 deleted file mode 100644 index 98a9dbb8..00000000 --- a/engine/assets/builtin/shaders/forward-lit/forward-lit.frag.vk.glsl +++ /dev/null @@ -1,176 +0,0 @@ -// XC_BUILTIN_FORWARD_LIT_VULKAN_PS -#version 450 -layout(set = 4, binding = 0) uniform texture2D uBaseColorTexture; -layout(set = 5, binding = 0) uniform sampler uLinearSampler; -layout(set = 6, binding = 0) uniform texture2D uShadowMapTexture; -layout(set = 7, binding = 0) uniform sampler uShadowMapSampler; - -layout(set = 0, binding = 0, std140) uniform PerObjectConstants { - mat4 gProjectionMatrix; - mat4 gViewMatrix; - mat4 gModelMatrix; - mat4 gNormalMatrix; -}; - -const int XC_MAX_ADDITIONAL_LIGHTS = 8; - -struct AdditionalLightData { - vec4 colorAndIntensity; - vec4 positionAndRange; - vec4 directionAndType; - vec4 spotAnglesAndFlags; -}; - -layout(set = 1, binding = 0, std140) uniform LightingConstants { - vec4 gMainLightDirectionAndIntensity; - vec4 gMainLightColorAndFlags; - vec4 gLightingParams; - AdditionalLightData gAdditionalLights[XC_MAX_ADDITIONAL_LIGHTS]; -}; - -layout(set = 2, binding = 0, std140) uniform MaterialConstants { - vec4 gBaseColorFactor; - vec4 gAlphaCutoffParams; -}; - -layout(set = 3, binding = 0, std140) uniform ShadowReceiverConstants { - mat4 gWorldToShadowMatrix; - vec4 gShadowBiasAndTexelSize; - vec4 gShadowOptions; -}; - -layout(location = 0) in vec3 vNormalWS; -layout(location = 1) in vec2 vTexCoord; -layout(location = 2) in vec3 vPositionWS; - -layout(location = 0) out vec4 fragColor; - -float ComputeShadowAttenuation(vec3 positionWS) { -#ifndef XC_MAIN_LIGHT_SHADOWS - return 1.0; -#else - if (gShadowOptions.x < 0.5) { - return 1.0; - } - - vec4 shadowClip = gWorldToShadowMatrix * vec4(positionWS, 1.0); - if (shadowClip.w <= 0.0) { - return 1.0; - } - - vec3 shadowNdc = shadowClip.xyz / shadowClip.w; - vec2 shadowUv = vec2( - shadowNdc.x * 0.5 + 0.5, - shadowNdc.y * -0.5 + 0.5); - if (shadowUv.x < 0.0 || shadowUv.x > 1.0 || - shadowUv.y < 0.0 || shadowUv.y > 1.0 || - shadowNdc.z < 0.0 || shadowNdc.z > 1.0) { - return 1.0; - } - - float shadowDepth = texture(sampler2D(uShadowMapTexture, uShadowMapSampler), shadowUv).r; - float receiverDepth = shadowNdc.z - gShadowBiasAndTexelSize.x; - float shadowStrength = clamp(gShadowBiasAndTexelSize.w, 0.0, 1.0); - return receiverDepth <= shadowDepth ? 1.0 : (1.0 - shadowStrength); -#endif -} - -float ComputeRangeAttenuation(float distanceSq, float range) { - if (range <= 0.0) { - return 0.0; - } - - float clampedRange = max(range, 0.0001); - float rangeSq = clampedRange * clampedRange; - if (distanceSq >= rangeSq) { - return 0.0; - } - - float distance = sqrt(max(distanceSq, 0.0)); - float normalized = clamp(1.0 - distance / clampedRange, 0.0, 1.0); - return normalized * normalized; -} - -float ComputeSpotAttenuation(AdditionalLightData light, vec3 directionToLightWS) { - float cosOuter = light.spotAnglesAndFlags.x; - float cosInner = light.spotAnglesAndFlags.y; - vec3 spotAxisToLightWS = normalize(light.directionAndType.xyz); - float cosTheta = dot(spotAxisToLightWS, directionToLightWS); - return clamp((cosTheta - cosOuter) / max(cosInner - cosOuter, 1e-4), 0.0, 1.0); -} - -vec3 EvaluateAdditionalLight(AdditionalLightData light, vec3 normalWS, vec3 positionWS) { - float lightType = light.directionAndType.w; - vec3 lightColor = light.colorAndIntensity.rgb; - float lightIntensity = light.colorAndIntensity.w; - - vec3 directionToLightWS = vec3(0.0); - float attenuation = 1.0; - - if (lightType < 0.5) { - directionToLightWS = normalize(light.directionAndType.xyz); - } else { - vec3 lightVectorWS = light.positionAndRange.xyz - positionWS; - float distanceSq = dot(lightVectorWS, lightVectorWS); - if (distanceSq <= 1e-6) { - return vec3(0.0); - } - - directionToLightWS = normalize(lightVectorWS); - attenuation = ComputeRangeAttenuation(distanceSq, light.positionAndRange.w); - if (attenuation <= 0.0) { - return vec3(0.0); - } - - if (lightType > 1.5) { - attenuation *= ComputeSpotAttenuation(light, directionToLightWS); - if (attenuation <= 0.0) { - return vec3(0.0); - } - } - } - - float diffuse = max(dot(normalWS, directionToLightWS), 0.0); - if (diffuse <= 0.0) { - return vec3(0.0); - } - - return lightColor * (diffuse * lightIntensity * attenuation); -} - -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; - return; - } - - vec3 normalWS = normalize(vNormalWS); - vec3 lighting = gLightingParams.yyy; - - if (gMainLightColorAndFlags.w >= 0.5) { - vec3 directionToLightWS = normalize(gMainLightDirectionAndIntensity.xyz); - float diffuse = max(dot(normalWS, directionToLightWS), 0.0); - float shadowAttenuation = diffuse > 0.0 - ? ComputeShadowAttenuation(vPositionWS) - : 1.0; - lighting += - gMainLightColorAndFlags.rgb * (diffuse * gMainLightDirectionAndIntensity.w * shadowAttenuation); - } - - for (int lightIndex = 0; lightIndex < XC_MAX_ADDITIONAL_LIGHTS; ++lightIndex) { - if (lightIndex >= additionalLightCount) { - break; - } - - lighting += EvaluateAdditionalLight(gAdditionalLights[lightIndex], normalWS, vPositionWS); - } - - fragColor = vec4(baseColor.rgb * lighting, baseColor.a); -} diff --git a/engine/assets/builtin/shaders/forward-lit/forward-lit.ps.hlsl b/engine/assets/builtin/shaders/forward-lit/forward-lit.ps.hlsl deleted file mode 100644 index 592aff20..00000000 --- a/engine/assets/builtin/shaders/forward-lit/forward-lit.ps.hlsl +++ /dev/null @@ -1,174 +0,0 @@ -// XC_BUILTIN_FORWARD_LIT_D3D12_PS -Texture2D gBaseColorTexture : register(t0); -SamplerState gLinearSampler : register(s0); -Texture2D gShadowMapTexture : register(t1); -SamplerState gShadowMapSampler : register(s1); - -cbuffer PerObjectConstants : register(b0) { - float4x4 gProjectionMatrix; - float4x4 gViewMatrix; - float4x4 gModelMatrix; - float4x4 gNormalMatrix; -}; - -static const int XC_MAX_ADDITIONAL_LIGHTS = 8; - -struct AdditionalLightData { - float4 colorAndIntensity; - float4 positionAndRange; - float4 directionAndType; - float4 spotAnglesAndFlags; -}; - -cbuffer LightingConstants : register(b1) { - float4 gMainLightDirectionAndIntensity; - float4 gMainLightColorAndFlags; - float4 gLightingParams; - AdditionalLightData gAdditionalLights[XC_MAX_ADDITIONAL_LIGHTS]; -}; - -cbuffer MaterialConstants : register(b2) { - float4 gBaseColorFactor; - float4 gAlphaCutoffParams; -}; - -cbuffer ShadowReceiverConstants : register(b3) { - float4x4 gWorldToShadowMatrix; - float4 gShadowBiasAndTexelSize; - float4 gShadowOptions; -}; - -struct PSInput { - float4 position : SV_POSITION; - float3 normalWS : TEXCOORD0; - float2 texcoord : TEXCOORD1; - float3 positionWS : TEXCOORD2; -}; - -float ComputeShadowAttenuation(float3 positionWS) { -#ifndef XC_MAIN_LIGHT_SHADOWS - return 1.0f; -#else - if (gShadowOptions.x < 0.5f) { - return 1.0f; - } - - float4 shadowClip = mul(gWorldToShadowMatrix, float4(positionWS, 1.0f)); - if (shadowClip.w <= 0.0f) { - return 1.0f; - } - - float3 shadowNdc = shadowClip.xyz / shadowClip.w; - float2 shadowUv = float2( - shadowNdc.x * 0.5f + 0.5f, - shadowNdc.y * -0.5f + 0.5f); - if (shadowUv.x < 0.0f || shadowUv.x > 1.0f || - shadowUv.y < 0.0f || shadowUv.y > 1.0f || - shadowNdc.z < 0.0f || shadowNdc.z > 1.0f) { - return 1.0f; - } - - const float shadowDepth = gShadowMapTexture.Sample(gShadowMapSampler, shadowUv).r; - const float receiverDepth = shadowNdc.z - gShadowBiasAndTexelSize.x; - const float shadowStrength = saturate(gShadowBiasAndTexelSize.w); - return receiverDepth <= shadowDepth ? 1.0f : (1.0f - shadowStrength); -#endif -} - -float ComputeRangeAttenuation(float distanceSq, float range) { - if (range <= 0.0f) { - return 0.0f; - } - - const float clampedRange = max(range, 0.0001f); - const float rangeSq = clampedRange * clampedRange; - if (distanceSq >= rangeSq) { - return 0.0f; - } - - const float distance = sqrt(max(distanceSq, 0.0f)); - const float normalized = saturate(1.0f - distance / clampedRange); - return normalized * normalized; -} - -float ComputeSpotAttenuation(AdditionalLightData light, float3 directionToLightWS) { - const float cosOuter = light.spotAnglesAndFlags.x; - const float cosInner = light.spotAnglesAndFlags.y; - const float3 spotAxisToLightWS = normalize(light.directionAndType.xyz); - const float cosTheta = dot(spotAxisToLightWS, directionToLightWS); - return saturate((cosTheta - cosOuter) / max(cosInner - cosOuter, 1e-4f)); -} - -float3 EvaluateAdditionalLight(AdditionalLightData light, float3 normalWS, float3 positionWS) { - const float lightType = light.directionAndType.w; - const float3 lightColor = light.colorAndIntensity.rgb; - const float lightIntensity = light.colorAndIntensity.w; - - float3 directionToLightWS = float3(0.0f, 0.0f, 0.0f); - float attenuation = 1.0f; - - if (lightType < 0.5f) { - directionToLightWS = normalize(light.directionAndType.xyz); - } else { - const float3 lightVectorWS = light.positionAndRange.xyz - positionWS; - const float distanceSq = dot(lightVectorWS, lightVectorWS); - if (distanceSq <= 1e-6f) { - return 0.0f.xxx; - } - - directionToLightWS = lightVectorWS * rsqrt(distanceSq); - attenuation = ComputeRangeAttenuation(distanceSq, light.positionAndRange.w); - if (attenuation <= 0.0f) { - return 0.0f.xxx; - } - - if (lightType > 1.5f) { - attenuation *= ComputeSpotAttenuation(light, directionToLightWS); - if (attenuation <= 0.0f) { - return 0.0f.xxx; - } - } - } - - const float diffuse = saturate(dot(normalWS, directionToLightWS)); - if (diffuse <= 0.0f) { - return 0.0f.xxx; - } - - return lightColor * (diffuse * lightIntensity * attenuation); -} - -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; - } - - float3 normalWS = normalize(input.normalWS); - float3 lighting = gLightingParams.yyy; - - if (gMainLightColorAndFlags.a >= 0.5f) { - float3 directionToLightWS = normalize(gMainLightDirectionAndIntensity.xyz); - float diffuse = saturate(dot(normalWS, directionToLightWS)); - float shadowAttenuation = diffuse > 0.0f - ? ComputeShadowAttenuation(input.positionWS) - : 1.0f; - lighting += - gMainLightColorAndFlags.rgb * (diffuse * gMainLightDirectionAndIntensity.w * shadowAttenuation); - } - - [unroll] - for (int lightIndex = 0; lightIndex < XC_MAX_ADDITIONAL_LIGHTS; ++lightIndex) { - if (lightIndex >= additionalLightCount) { - break; - } - - lighting += EvaluateAdditionalLight(gAdditionalLights[lightIndex], normalWS, input.positionWS); - } - - return float4(baseColor.rgb * lighting, baseColor.a); -} diff --git a/engine/assets/builtin/shaders/forward-lit/forward-lit.shader b/engine/assets/builtin/shaders/forward-lit/forward-lit.shader index 06891ddb..46212d2e 100644 --- a/engine/assets/builtin/shaders/forward-lit/forward-lit.shader +++ b/engine/assets/builtin/shaders/forward-lit/forward-lit.shader @@ -6,31 +6,240 @@ Shader "Builtin Forward Lit" _Cutoff ("Alpha Cutoff", Range) = 0.5 [Semantic(AlphaCutoff)] _MainTex ("Base Map", 2D) = "white" [Semantic(BaseColorTexture)] } + HLSLINCLUDE + cbuffer PerObjectConstants : register(b0) + { + float4x4 gProjectionMatrix; + float4x4 gViewMatrix; + float4x4 gModelMatrix; + float4x4 gNormalMatrix; + }; + + static const int XC_MAX_ADDITIONAL_LIGHTS = 8; + + struct AdditionalLightData + { + float4 colorAndIntensity; + float4 positionAndRange; + float4 directionAndType; + float4 spotAnglesAndFlags; + }; + + cbuffer LightingConstants : register(b1) + { + float4 gMainLightDirectionAndIntensity; + float4 gMainLightColorAndFlags; + float4 gLightingParams; + AdditionalLightData gAdditionalLights[XC_MAX_ADDITIONAL_LIGHTS]; + }; + + cbuffer MaterialConstants : register(b2) + { + float4 gBaseColorFactor; + float4 gAlphaCutoffParams; + }; + + cbuffer ShadowReceiverConstants : register(b3) + { + float4x4 gWorldToShadowMatrix; + float4 gShadowBiasAndTexelSize; + float4 gShadowOptions; + }; + + Texture2D BaseColorTexture : register(t0); + SamplerState LinearClampSampler : register(s0); + Texture2D ShadowMapTexture : register(t1); + SamplerState ShadowMapSampler : register(s1); + + struct VSInput + { + float3 position : POSITION; + float3 normal : NORMAL; + float2 texcoord : TEXCOORD0; + }; + + struct PSInput + { + float4 position : SV_POSITION; + float3 normalWS : TEXCOORD0; + float2 texcoord : TEXCOORD1; + float3 positionWS : TEXCOORD2; + }; + + PSInput MainVS(VSInput input) + { + PSInput output; + const float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0f)); + const float4 positionVS = mul(gViewMatrix, positionWS); + output.position = mul(gProjectionMatrix, positionVS); + output.normalWS = mul((float3x3)gNormalMatrix, input.normal); + output.texcoord = input.texcoord; + output.positionWS = positionWS.xyz; + return output; + } + + float ComputeShadowAttenuation(float3 positionWS) + { + #ifndef XC_MAIN_LIGHT_SHADOWS + return 1.0f; + #else + if (gShadowOptions.x < 0.5f) { + return 1.0f; + } + + const float4 shadowClip = mul(gWorldToShadowMatrix, float4(positionWS, 1.0f)); + if (shadowClip.w <= 0.0f) { + return 1.0f; + } + + const float3 shadowNdc = shadowClip.xyz / shadowClip.w; + #if UNITY_UV_STARTS_AT_TOP + const float shadowUvY = shadowNdc.y * -0.5f + 0.5f; + #else + const float shadowUvY = shadowNdc.y * 0.5f + 0.5f; + #endif + const float2 shadowUv = float2(shadowNdc.x * 0.5f + 0.5f, shadowUvY); + #if UNITY_NEAR_CLIP_VALUE < 0 + if (shadowUv.x < 0.0f || shadowUv.x > 1.0f || + shadowUv.y < 0.0f || shadowUv.y > 1.0f || + shadowNdc.z < -1.0f || shadowNdc.z > 1.0f) { + return 1.0f; + } + + const float receiverDepth = shadowNdc.z * 0.5f + 0.5f - gShadowBiasAndTexelSize.x; + #else + if (shadowUv.x < 0.0f || shadowUv.x > 1.0f || + shadowUv.y < 0.0f || shadowUv.y > 1.0f || + shadowNdc.z < 0.0f || shadowNdc.z > 1.0f) { + return 1.0f; + } + + const float receiverDepth = shadowNdc.z - gShadowBiasAndTexelSize.x; + #endif + const float shadowDepth = ShadowMapTexture.Sample(ShadowMapSampler, shadowUv).r; + const float shadowStrength = saturate(gShadowBiasAndTexelSize.w); + return receiverDepth <= shadowDepth ? 1.0f : (1.0f - shadowStrength); + #endif + } + + float ComputeRangeAttenuation(float distanceSq, float range) + { + if (range <= 0.0f) { + return 0.0f; + } + + const float clampedRange = max(range, 0.0001f); + const float rangeSq = clampedRange * clampedRange; + if (distanceSq >= rangeSq) { + return 0.0f; + } + + const float distance = sqrt(max(distanceSq, 0.0f)); + const float normalized = saturate(1.0f - distance / clampedRange); + return normalized * normalized; + } + + float ComputeSpotAttenuation(AdditionalLightData light, float3 directionToLightWS) + { + const float cosOuter = light.spotAnglesAndFlags.x; + const float cosInner = light.spotAnglesAndFlags.y; + const float3 spotAxisToLightWS = normalize(light.directionAndType.xyz); + const float cosTheta = dot(spotAxisToLightWS, directionToLightWS); + return saturate((cosTheta - cosOuter) / max(cosInner - cosOuter, 1e-4f)); + } + + float3 EvaluateAdditionalLight(AdditionalLightData light, float3 normalWS, float3 positionWS) + { + const float lightType = light.directionAndType.w; + const float3 lightColor = light.colorAndIntensity.rgb; + const float lightIntensity = light.colorAndIntensity.w; + + float3 directionToLightWS = float3(0.0f, 0.0f, 0.0f); + float attenuation = 1.0f; + + if (lightType < 0.5f) { + directionToLightWS = normalize(light.directionAndType.xyz); + } else { + const float3 lightVectorWS = light.positionAndRange.xyz - positionWS; + const float distanceSq = dot(lightVectorWS, lightVectorWS); + if (distanceSq <= 1e-6f) { + return 0.0f.xxx; + } + + directionToLightWS = lightVectorWS * rsqrt(distanceSq); + attenuation = ComputeRangeAttenuation(distanceSq, light.positionAndRange.w); + if (attenuation <= 0.0f) { + return 0.0f.xxx; + } + + if (lightType > 1.5f) { + attenuation *= ComputeSpotAttenuation(light, directionToLightWS); + if (attenuation <= 0.0f) { + return 0.0f.xxx; + } + } + } + + const float diffuse = saturate(dot(normalWS, directionToLightWS)); + if (diffuse <= 0.0f) { + return 0.0f.xxx; + } + + return lightColor * (diffuse * lightIntensity * attenuation); + } + + float4 MainPS(PSInput input) : SV_TARGET + { + float4 baseColor = BaseColorTexture.Sample(LinearClampSampler, 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; + } + + const float3 normalWS = normalize(input.normalWS); + float3 lighting = gLightingParams.yyy; + + if (gMainLightColorAndFlags.a >= 0.5f) { + const float3 directionToLightWS = normalize(gMainLightDirectionAndIntensity.xyz); + const float diffuse = saturate(dot(normalWS, directionToLightWS)); + const float shadowAttenuation = + diffuse > 0.0f ? ComputeShadowAttenuation(input.positionWS) : 1.0f; + lighting += + gMainLightColorAndFlags.rgb * + (diffuse * gMainLightDirectionAndIntensity.w * shadowAttenuation); + } + + [unroll] + for (int lightIndex = 0; lightIndex < XC_MAX_ADDITIONAL_LIGHTS; ++lightIndex) { + if (lightIndex >= additionalLightCount) { + break; + } + + lighting += EvaluateAdditionalLight(gAdditionalLights[lightIndex], normalWS, input.positionWS); + } + + return float4(baseColor.rgb * lighting, baseColor.a); + } + ENDHLSL SubShader { + Cull Back + ZWrite On + ZTest LEqual Pass { Name "ForwardLit" Tags { "LightMode" = "ForwardBase" } - Resources - { - PerObjectConstants (ConstantBuffer, 0, 0) [Semantic(PerObject)] - LightingConstants (ConstantBuffer, 1, 0) [Semantic(Lighting)] - MaterialConstants (ConstantBuffer, 2, 0) [Semantic(Material)] - ShadowReceiverConstants (ConstantBuffer, 3, 0) [Semantic(ShadowReceiver)] - BaseColorTexture (Texture2D, 4, 0) [Semantic(BaseColorTexture)] - LinearClampSampler (Sampler, 5, 0) [Semantic(LinearClampSampler)] - ShadowMapTexture (Texture2D, 6, 0) [Semantic(ShadowMapTexture)] - ShadowMapSampler (Sampler, 7, 0) [Semantic(ShadowMapSampler)] - } HLSLPROGRAM + #pragma target 4.5 #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" ENDHLSL } } diff --git a/engine/assets/builtin/shaders/forward-lit/forward-lit.vert.glsl b/engine/assets/builtin/shaders/forward-lit/forward-lit.vert.glsl deleted file mode 100644 index eea455f9..00000000 --- a/engine/assets/builtin/shaders/forward-lit/forward-lit.vert.glsl +++ /dev/null @@ -1,25 +0,0 @@ -// XC_BUILTIN_FORWARD_LIT_OPENGL_VS -#version 430 -layout(location = 0) in vec3 aPosition; -layout(location = 1) in vec3 aNormal; -layout(location = 2) in vec2 aTexCoord; - -layout(std140, binding = 0) uniform PerObjectConstants { - mat4 gProjectionMatrix; - mat4 gViewMatrix; - mat4 gModelMatrix; - mat4 gNormalMatrix; -}; - -out vec3 vNormalWS; -out vec2 vTexCoord; -out vec3 vPositionWS; - -void main() { - vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0); - vec4 positionVS = gViewMatrix * positionWS; - gl_Position = gProjectionMatrix * positionVS; - vNormalWS = mat3(gNormalMatrix) * aNormal; - vTexCoord = aTexCoord; - vPositionWS = positionWS.xyz; -} diff --git a/engine/assets/builtin/shaders/forward-lit/forward-lit.vert.vk.glsl b/engine/assets/builtin/shaders/forward-lit/forward-lit.vert.vk.glsl deleted file mode 100644 index ddc84830..00000000 --- a/engine/assets/builtin/shaders/forward-lit/forward-lit.vert.vk.glsl +++ /dev/null @@ -1,25 +0,0 @@ -// XC_BUILTIN_FORWARD_LIT_VULKAN_VS -#version 450 -layout(location = 0) in vec3 aPosition; -layout(location = 1) in vec3 aNormal; -layout(location = 2) in vec2 aTexCoord; - -layout(set = 0, binding = 0, std140) uniform PerObjectConstants { - mat4 gProjectionMatrix; - mat4 gViewMatrix; - mat4 gModelMatrix; - mat4 gNormalMatrix; -}; - -layout(location = 0) out vec3 vNormalWS; -layout(location = 1) out vec2 vTexCoord; -layout(location = 2) out vec3 vPositionWS; - -void main() { - vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0); - vec4 positionVS = gViewMatrix * positionWS; - gl_Position = gProjectionMatrix * positionVS; - vNormalWS = mat3(gNormalMatrix) * aNormal; - vTexCoord = aTexCoord; - vPositionWS = positionWS.xyz; -} diff --git a/engine/assets/builtin/shaders/forward-lit/forward-lit.vs.hlsl b/engine/assets/builtin/shaders/forward-lit/forward-lit.vs.hlsl deleted file mode 100644 index fcc747a6..00000000 --- a/engine/assets/builtin/shaders/forward-lit/forward-lit.vs.hlsl +++ /dev/null @@ -1,31 +0,0 @@ -// XC_BUILTIN_FORWARD_LIT_D3D12_VS -cbuffer PerObjectConstants : register(b0) { - float4x4 gProjectionMatrix; - float4x4 gViewMatrix; - float4x4 gModelMatrix; - float4x4 gNormalMatrix; -}; - -struct VSInput { - float3 position : POSITION; - float3 normal : NORMAL; - float2 texcoord : TEXCOORD0; -}; - -struct PSInput { - float4 position : SV_POSITION; - float3 normalWS : TEXCOORD0; - float2 texcoord : TEXCOORD1; - float3 positionWS : TEXCOORD2; -}; - -PSInput MainVS(VSInput input) { - PSInput output; - float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0f)); - float4 positionVS = mul(gViewMatrix, positionWS); - output.position = mul(gProjectionMatrix, positionVS); - output.normalWS = mul((float3x3)gNormalMatrix, input.normal); - output.texcoord = input.texcoord; - output.positionWS = positionWS.xyz; - 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 deleted file mode 100644 index c28bbf29..00000000 --- a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.frag.glsl +++ /dev/null @@ -1,21 +0,0 @@ -// XC_BUILTIN_SHADOW_CASTER_OPENGL_PS -#version 430 - -layout(binding = 0) uniform sampler2D uBaseColorTexture; - -layout(std140, binding = 1) uniform MaterialConstants { - vec4 gBaseColorFactor; - vec4 gAlphaCutoffParams; -}; - -in vec2 vTexCoord; - -void main() { -#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 deleted file mode 100644 index 5f441c88..00000000 --- a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.frag.vk.glsl +++ /dev/null @@ -1,22 +0,0 @@ -// XC_BUILTIN_SHADOW_CASTER_VULKAN_PS -#version 450 - -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() { -#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 deleted file mode 100644 index 6095a2ba..00000000 --- a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.ps.hlsl +++ /dev/null @@ -1,21 +0,0 @@ -// XC_BUILTIN_SHADOW_CASTER_D3D12_PS -Texture2D gBaseColorTexture : register(t0); -SamplerState gLinearSampler : register(s0); - -cbuffer MaterialConstants : register(b1) { - float4 gBaseColorFactor; - float4 gAlphaCutoffParams; -}; - -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 b80daf21..645fa8fc 100644 --- a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.shader +++ b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.shader @@ -6,26 +6,67 @@ Shader "Builtin Shadow Caster" _Cutoff ("Alpha Cutoff", Range) = 0.5 [Semantic(AlphaCutoff)] _MainTex ("Base Map", 2D) = "white" [Semantic(BaseColorTexture)] } + HLSLINCLUDE + cbuffer PerObjectConstants : register(b0) + { + float4x4 gProjectionMatrix; + float4x4 gViewMatrix; + float4x4 gModelMatrix; + }; + + cbuffer MaterialConstants : register(b1) + { + float4 gBaseColorFactor; + float4 gAlphaCutoffParams; + }; + + Texture2D BaseColorTexture : register(t0); + SamplerState LinearClampSampler : register(s0); + + struct VSInput + { + float3 position : POSITION; + float3 normal : NORMAL; + float2 texcoord : TEXCOORD0; + }; + + struct PSInput + { + float4 position : SV_POSITION; + float2 texcoord : TEXCOORD0; + }; + + PSInput MainVS(VSInput input) + { + PSInput output; + const float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0f)); + const float4 positionVS = mul(gViewMatrix, positionWS); + output.position = mul(gProjectionMatrix, positionVS); + output.texcoord = input.texcoord; + return output; + } + + float MainPS(PSInput input) : SV_Depth + { + #ifdef XC_ALPHA_TEST + const float4 baseColor = + BaseColorTexture.Sample(LinearClampSampler, input.texcoord) * gBaseColorFactor; + clip(baseColor.a - gAlphaCutoffParams.x); + #endif + return input.position.z; + } + ENDHLSL SubShader { Pass { Name "ShadowCaster" Tags { "LightMode" = "ShadowCaster" } - 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 target 4.5 #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" ENDHLSL } } diff --git a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.vert.glsl b/engine/assets/builtin/shaders/shadow-caster/shadow-caster.vert.glsl deleted file mode 100644 index 7cc469bb..00000000 --- a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.vert.glsl +++ /dev/null @@ -1,19 +0,0 @@ -// 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; - mat4 gViewMatrix; - 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 deleted file mode 100644 index 31ec106e..00000000 --- a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.vert.vk.glsl +++ /dev/null @@ -1,19 +0,0 @@ -// 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; - mat4 gViewMatrix; - 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 deleted file mode 100644 index a93fc8a8..00000000 --- a/engine/assets/builtin/shaders/shadow-caster/shadow-caster.vs.hlsl +++ /dev/null @@ -1,25 +0,0 @@ -// XC_BUILTIN_SHADOW_CASTER_D3D12_VS -cbuffer PerObjectConstants : register(b0) { - float4x4 gProjectionMatrix; - float4x4 gViewMatrix; - float4x4 gModelMatrix; -}; - -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.0f)); - float4 positionVS = mul(gViewMatrix, positionWS); - output.position = mul(gProjectionMatrix, positionVS); - output.texcoord = input.texcoord; - return output; -} diff --git a/engine/include/XCEngine/RHI/D3D12/D3D12Shader.h b/engine/include/XCEngine/RHI/D3D12/D3D12Shader.h index 75befb19..05f3f275 100644 --- a/engine/include/XCEngine/RHI/D3D12/D3D12Shader.h +++ b/engine/include/XCEngine/RHI/D3D12/D3D12Shader.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -22,6 +23,12 @@ public: bool CompileFromFile(const wchar_t* filePath, const char* entryPoint, const char* target) override; bool Compile(const void* sourceData, size_t sourceSize, const char* entryPoint, const char* target) override; + bool Compile( + const void* sourceData, + size_t sourceSize, + const D3D_SHADER_MACRO* macros, + const char* entryPoint, + const char* target); void Shutdown() override; const D3D12_SHADER_BYTECODE GetD3D12Bytecode() const; @@ -48,4 +55,4 @@ private: }; } // namespace RHI -} // namespace XCEngine \ No newline at end of file +} // namespace XCEngine diff --git a/engine/src/RHI/D3D12/D3D12Device.cpp b/engine/src/RHI/D3D12/D3D12Device.cpp index 98946c3a..93919d6f 100644 --- a/engine/src/RHI/D3D12/D3D12Device.cpp +++ b/engine/src/RHI/D3D12/D3D12Device.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #ifdef _DEBUG #include @@ -51,7 +52,36 @@ bool CompileD3D12Shader(const ShaderCompileDesc& desc, D3D12Shader& shader) { const char* profilePtr = profile.empty() ? nullptr : profile.c_str(); if (!desc.source.empty()) { - return shader.Compile(desc.source.data(), desc.source.size(), entryPointPtr, profilePtr); + std::vector macroNames; + std::vector macroDefinitions; + std::vector macroTable; + if (!desc.macros.empty()) { + macroNames.reserve(desc.macros.size()); + macroDefinitions.reserve(desc.macros.size()); + macroTable.reserve(desc.macros.size() + 1u); + for (const ShaderCompileMacro& macro : desc.macros) { + macroNames.push_back(NarrowAscii(macro.name)); + macroDefinitions.push_back(NarrowAscii(macro.definition)); + } + + for (size_t macroIndex = 0; macroIndex < desc.macros.size(); ++macroIndex) { + D3D_SHADER_MACRO d3dMacro = {}; + d3dMacro.Name = macroNames[macroIndex].c_str(); + d3dMacro.Definition = macroDefinitions[macroIndex].empty() + ? "1" + : macroDefinitions[macroIndex].c_str(); + macroTable.push_back(d3dMacro); + } + macroTable.push_back({ nullptr, nullptr }); + } + + const D3D_SHADER_MACRO* macroPtr = macroTable.empty() ? nullptr : macroTable.data(); + return shader.Compile( + desc.source.data(), + desc.source.size(), + macroPtr, + entryPointPtr, + profilePtr); } if (!desc.fileName.empty()) { diff --git a/engine/src/RHI/D3D12/D3D12Shader.cpp b/engine/src/RHI/D3D12/D3D12Shader.cpp index 56f85ba8..892d393a 100644 --- a/engine/src/RHI/D3D12/D3D12Shader.cpp +++ b/engine/src/RHI/D3D12/D3D12Shader.cpp @@ -44,8 +44,26 @@ bool D3D12Shader::CompileFromFile(const wchar_t* filePath, const char* entryPoin } bool D3D12Shader::Compile(const void* sourceData, size_t sourceSize, const char* entryPoint, const char* target) { - HRESULT hResult = D3DCompile(sourceData, sourceSize, nullptr, nullptr, nullptr, entryPoint, target, - D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION, 0, &m_bytecode, &m_error); + return Compile(sourceData, sourceSize, nullptr, entryPoint, target); +} + +bool D3D12Shader::Compile(const void* sourceData, + size_t sourceSize, + const D3D_SHADER_MACRO* macros, + const char* entryPoint, + const char* target) { + HRESULT hResult = D3DCompile( + sourceData, + sourceSize, + nullptr, + macros, + nullptr, + entryPoint, + target, + D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION, + 0, + &m_bytecode, + &m_error); if (FAILED(hResult)) { if (m_error) { diff --git a/engine/src/RHI/OpenGL/OpenGLDevice.cpp b/engine/src/RHI/OpenGL/OpenGLDevice.cpp index 2effa80d..2a6dd776 100644 --- a/engine/src/RHI/OpenGL/OpenGLDevice.cpp +++ b/engine/src/RHI/OpenGL/OpenGLDevice.cpp @@ -29,7 +29,10 @@ #include #include #include +#include #include +#include +#include #include typedef const char* (WINAPI* PFNWGLGETEXTENSIONSSTRINGARBPROC)(HDC hdc); @@ -166,6 +169,167 @@ bool LoadShaderSourceText(const ShaderCompileDesc& desc, std::string& outSourceT return file.read(outSourceText.data(), size).good(); } +bool IsHlslShaderCompileDesc(const ShaderCompileDesc& desc) { + if (desc.sourceLanguage == ShaderLanguage::HLSL) { + return true; + } + + if (desc.fileName.empty()) { + return false; + } + + std::string extension = std::filesystem::path(desc.fileName).extension().string(); + std::transform(extension.begin(), extension.end(), extension.begin(), [](unsigned char ch) { + return static_cast(std::tolower(ch)); + }); + return extension == ".hlsl"; +} + +void CollectHlslTextureRegisterBindings( + const std::string& sourceText, + std::unordered_map& outTextureUnits, + std::unordered_set& outSamplerNames) { + static const std::regex kTexturePattern( + R"(((?:Texture2D|TextureCube)\s+([A-Za-z_][A-Za-z0-9_]*)\s*:\s*register\s*\(\s*t([0-9]+)))", + std::regex::ECMAScript); + static const std::regex kSamplerPattern( + R"(((?:SamplerState|SamplerComparisonState)\s+([A-Za-z_][A-Za-z0-9_]*)\s*:\s*register\s*\(\s*s([0-9]+)))", + std::regex::ECMAScript); + + for (std::sregex_iterator it(sourceText.begin(), sourceText.end(), kTexturePattern), end; it != end; ++it) { + const std::smatch& match = *it; + if (match.size() < 4) { + continue; + } + + try { + outTextureUnits[match[2].str()] = static_cast(std::stoi(match[3].str())); + } catch (...) { + } + } + + for (std::sregex_iterator it(sourceText.begin(), sourceText.end(), kSamplerPattern), end; it != end; ++it) { + const std::smatch& match = *it; + if (match.size() < 4) { + continue; + } + + outSamplerNames.insert(match[2].str()); + } +} + +bool TryResolveCombinedSamplerTextureUnit( + const std::string& uniformName, + const std::unordered_map& textureUnits, + const std::unordered_set& samplerNames, + GLint& outTextureUnit) { + static const std::string kCombinedPrefix = "SPIRV_Cross_Combined"; + if (uniformName.rfind(kCombinedPrefix, 0) != 0) { + return false; + } + + std::string combinedName = uniformName.substr(kCombinedPrefix.size()); + const size_t arraySuffixPos = combinedName.find('['); + if (arraySuffixPos != std::string::npos) { + combinedName = combinedName.substr(0, arraySuffixPos); + } + + std::vector> candidates(textureUnits.begin(), textureUnits.end()); + std::sort( + candidates.begin(), + candidates.end(), + [](const auto& left, const auto& right) { + return left.first.size() > right.first.size(); + }); + + for (const auto& [textureName, textureUnit] : candidates) { + if (combinedName.rfind(textureName, 0) != 0) { + continue; + } + + const std::string samplerName = combinedName.substr(textureName.size()); + if (!samplerName.empty() && + (samplerNames.empty() || samplerNames.find(samplerName) != samplerNames.end())) { + outTextureUnit = textureUnit; + return true; + } + } + + return false; +} + +void ConfigureOpenGLCombinedSamplerUniforms( + GLuint program, + const ShaderCompileDesc* shaderDescs, + size_t shaderDescCount) { + if (program == 0 || shaderDescs == nullptr || shaderDescCount == 0) { + return; + } + + std::unordered_map textureUnits; + std::unordered_set samplerNames; + for (size_t shaderIndex = 0; shaderIndex < shaderDescCount; ++shaderIndex) { + const ShaderCompileDesc& shaderDesc = shaderDescs[shaderIndex]; + if (!IsHlslShaderCompileDesc(shaderDesc)) { + continue; + } + + std::string sourceText; + if (!LoadShaderSourceText(shaderDesc, sourceText) || sourceText.empty()) { + continue; + } + + CollectHlslTextureRegisterBindings(sourceText, textureUnits, samplerNames); + } + + if (textureUnits.empty()) { + return; + } + + GLint activeUniformCount = 0; + glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &activeUniformCount); + if (activeUniformCount <= 0) { + return; + } + + GLint previousProgram = 0; + glGetIntegerv(GL_CURRENT_PROGRAM, &previousProgram); + glUseProgram(program); + + for (GLint uniformIndex = 0; uniformIndex < activeUniformCount; ++uniformIndex) { + GLsizei nameLength = 0; + GLint arraySize = 0; + GLenum uniformType = 0; + char nameBuffer[256] = {}; + glGetActiveUniform( + program, + static_cast(uniformIndex), + static_cast(std::size(nameBuffer)), + &nameLength, + &arraySize, + &uniformType, + nameBuffer); + if (nameLength <= 0) { + continue; + } + + (void)arraySize; + (void)uniformType; + + GLint textureUnit = -1; + if (!TryResolveCombinedSamplerTextureUnit(nameBuffer, textureUnits, samplerNames, textureUnit)) { + continue; + } + + const GLint location = glGetUniformLocation(program, nameBuffer); + if (location >= 0) { + glUniform1i(location, textureUnit); + } + } + + glUseProgram(static_cast(previousProgram)); +} + bool TryResolveShaderTypeFromTarget(const std::wstring& profile, ShaderType& type) { const std::string asciiProfile = NarrowAscii(profile); if (asciiProfile.find("vs_") != std::string::npos || asciiProfile == "vs") { @@ -536,6 +700,8 @@ bool BuildOpenGLProgram(const ShaderCompileDesc* shaderDescs, return false; } + ConfigureOpenGLCombinedSamplerUniforms(program, shaderDescs, shaderDescCount); + outProgram = program; return true; } diff --git a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp index 7214937e..44ee8743 100644 --- a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp +++ b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp @@ -1,5 +1,7 @@ #include +#include "Rendering/Detail/ShaderVariantUtils.h" + #include #include #include @@ -10,6 +12,10 @@ #include #include #include +#include + +#include +#include using namespace XCEngine::Rendering::Pipelines; using namespace XCEngine::Rendering::Passes; @@ -53,7 +59,7 @@ TEST(BuiltinForwardPipeline_Test, SplitsSceneItemsIntoOpaqueAndTransparentQueueR EXPECT_TRUE(IsTransparentRenderQueue(static_cast(MaterialRenderQueue::Overlay))); } -TEST(BuiltinForwardPipeline_Test, BuiltinForwardShaderDeclaresExplicitForwardResourceContract) { +TEST(BuiltinForwardPipeline_Test, BuiltinForwardShaderUsesUnityStyleSingleSourceSurfaceContract) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinForwardLitShaderPath()); ASSERT_TRUE(result); @@ -69,52 +75,16 @@ TEST(BuiltinForwardPipeline_Test, BuiltinForwardShaderDeclaresExplicitForwardRes const ShaderPass* pass = shader->FindPass("ForwardLit"); ASSERT_NE(pass, nullptr); - ASSERT_EQ(pass->resources.Size(), 8u); - - 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, "Lighting"); - 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, "Material"); - EXPECT_EQ(pass->resources[2].type, ShaderResourceType::ConstantBuffer); - EXPECT_EQ(pass->resources[2].set, 2u); - EXPECT_EQ(pass->resources[2].binding, 0u); - - EXPECT_EQ(pass->resources[3].semantic, "ShadowReceiver"); - EXPECT_EQ(pass->resources[3].type, ShaderResourceType::ConstantBuffer); - EXPECT_EQ(pass->resources[3].set, 3u); - EXPECT_EQ(pass->resources[3].binding, 0u); - - EXPECT_EQ(pass->resources[4].semantic, "BaseColorTexture"); - EXPECT_EQ(pass->resources[4].type, ShaderResourceType::Texture2D); - EXPECT_EQ(pass->resources[4].set, 4u); - EXPECT_EQ(pass->resources[4].binding, 0u); - - EXPECT_EQ(pass->resources[5].semantic, "LinearClampSampler"); - EXPECT_EQ(pass->resources[5].type, ShaderResourceType::Sampler); - EXPECT_EQ(pass->resources[5].set, 5u); - EXPECT_EQ(pass->resources[5].binding, 0u); - - EXPECT_EQ(pass->resources[6].semantic, "ShadowMapTexture"); - EXPECT_EQ(pass->resources[6].type, ShaderResourceType::Texture2D); - EXPECT_EQ(pass->resources[6].set, 6u); - EXPECT_EQ(pass->resources[6].binding, 0u); - - EXPECT_EQ(pass->resources[7].semantic, "ShadowMapSampler"); - EXPECT_EQ(pass->resources[7].type, ShaderResourceType::Sampler); - EXPECT_EQ(pass->resources[7].set, 7u); - EXPECT_EQ(pass->resources[7].binding, 0u); + EXPECT_TRUE(pass->resources.Empty()); + EXPECT_TRUE(pass->hasFixedFunctionState); + EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::Back); + EXPECT_TRUE(pass->fixedFunctionState.depthWriteEnable); + EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual); delete shader; } -TEST(BuiltinForwardPipeline_Test, BuiltinUnlitShaderDeclaresExplicitSurfaceResourceContract) { +TEST(BuiltinForwardPipeline_Test, BuiltinUnlitShaderUsesUnityStyleSingleSourceSurfaceContract) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinUnlitShaderPath()); ASSERT_TRUE(result); @@ -125,27 +95,362 @@ TEST(BuiltinForwardPipeline_Test, BuiltinUnlitShaderDeclaresExplicitSurfaceResou const ShaderPass* pass = shader->FindPass("Unlit"); ASSERT_NE(pass, nullptr); - ASSERT_EQ(pass->resources.Size(), 4u); + EXPECT_TRUE(pass->resources.Empty()); + EXPECT_TRUE(pass->hasFixedFunctionState); + EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::Back); + EXPECT_TRUE(pass->fixedFunctionState.depthWriteEnable); + EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual); - 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; +} - 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); +TEST(BuiltinForwardPipeline_Test, BuiltinUnlitShaderBuildsVulkanRuntimeSourceWithResolvedBindings) { + ShaderLoader loader; + LoadResult result = loader.Load(GetBuiltinUnlitShaderPath()); + ASSERT_TRUE(result); + ASSERT_NE(result.resource, nullptr); - 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); + Shader* shader = static_cast(result.resource); + ASSERT_NE(shader, nullptr); - 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); + const ShaderPass* pass = shader->FindPass("Unlit"); + ASSERT_NE(pass, nullptr); + + const ShaderStageVariant* fragmentVariant = + shader->FindVariant( + "Unlit", + XCEngine::Resources::ShaderType::Fragment, + XCEngine::Resources::ShaderBackend::Vulkan); + ASSERT_NE(fragmentVariant, nullptr); + + const std::string runtimeSource = + ::XCEngine::Rendering::Detail::BuildRuntimeShaderSource( + *pass, + XCEngine::Resources::ShaderBackend::Vulkan, + *fragmentVariant); + + EXPECT_NE( + runtimeSource.find("cbuffer MaterialConstants : register(b0, space1)"), + std::string::npos); + EXPECT_NE(runtimeSource.find("BaseColorTexture"), std::string::npos); + EXPECT_NE(runtimeSource.find("space2"), std::string::npos); + EXPECT_NE(runtimeSource.find("LinearClampSampler"), std::string::npos); + EXPECT_NE(runtimeSource.find("space3"), std::string::npos); + + delete shader; +} + +TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesUnityStyleUnlitBindingsToDescriptorSpaces) { + ShaderLoader loader; + LoadResult result = loader.Load(GetBuiltinUnlitShaderPath()); + ASSERT_TRUE(result); + ASSERT_NE(result.resource, nullptr); + + Shader* shader = static_cast(result.resource); + ASSERT_NE(shader, nullptr); + + const ShaderPass* pass = shader->FindPass("Unlit"); + ASSERT_NE(pass, nullptr); + + const ShaderStageVariant* d3d12Fragment = shader->FindVariant( + "Unlit", + XCEngine::Resources::ShaderType::Fragment, + XCEngine::Resources::ShaderBackend::D3D12); + ASSERT_NE(d3d12Fragment, nullptr); + + ShaderCompileDesc d3d12CompileDesc = {}; + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant( + *pass, + XCEngine::Resources::ShaderBackend::D3D12, + *d3d12Fragment, + d3d12CompileDesc); + const std::string d3d12Source( + reinterpret_cast(d3d12CompileDesc.source.data()), + d3d12CompileDesc.source.size()); + EXPECT_NE(d3d12Source.find("BaseColorTexture"), std::string::npos); + EXPECT_NE(d3d12Source.find("LinearClampSampler"), std::string::npos); + EXPECT_EQ(d3d12Source.find("space2"), std::string::npos); + EXPECT_EQ(d3d12Source.find("space3"), std::string::npos); + + const ShaderStageVariant* vulkanFragment = shader->FindVariant( + "Unlit", + XCEngine::Resources::ShaderType::Fragment, + XCEngine::Resources::ShaderBackend::Vulkan); + ASSERT_NE(vulkanFragment, nullptr); + + ShaderCompileDesc vulkanCompileDesc = {}; + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant( + *pass, + XCEngine::Resources::ShaderBackend::Vulkan, + *vulkanFragment, + vulkanCompileDesc); + const std::string vulkanSource( + reinterpret_cast(vulkanCompileDesc.source.data()), + vulkanCompileDesc.source.size()); + EXPECT_NE(vulkanSource.find("cbuffer MaterialConstants : register(b0, space1)"), std::string::npos); + EXPECT_NE(vulkanSource.find("BaseColorTexture"), std::string::npos); + EXPECT_NE(vulkanSource.find("space2"), std::string::npos); + EXPECT_NE(vulkanSource.find("LinearClampSampler"), std::string::npos); + EXPECT_NE(vulkanSource.find("space3"), std::string::npos); + + delete shader; +} + +TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesUnityStyleForwardBindingsToDescriptorSpaces) { + ShaderLoader loader; + LoadResult result = loader.Load(GetBuiltinForwardLitShaderPath()); + ASSERT_TRUE(result); + ASSERT_NE(result.resource, nullptr); + + Shader* shader = static_cast(result.resource); + ASSERT_NE(shader, nullptr); + + const ShaderPass* pass = shader->FindPass("ForwardLit"); + ASSERT_NE(pass, nullptr); + + ShaderKeywordSet shadowKeywords = {}; + shadowKeywords.enabledKeywords.PushBack("XC_MAIN_LIGHT_SHADOWS"); + + const ShaderStageVariant* d3d12Fragment = shader->FindVariant( + "ForwardLit", + XCEngine::Resources::ShaderType::Fragment, + XCEngine::Resources::ShaderBackend::D3D12, + shadowKeywords); + ASSERT_NE(d3d12Fragment, nullptr); + + ShaderCompileDesc d3d12CompileDesc = {}; + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant( + *pass, + XCEngine::Resources::ShaderBackend::D3D12, + *d3d12Fragment, + d3d12CompileDesc); + const std::string d3d12Source( + reinterpret_cast(d3d12CompileDesc.source.data()), + d3d12CompileDesc.source.size()); + EXPECT_NE(d3d12Source.find("cbuffer LightingConstants : register(b1)"), std::string::npos); + EXPECT_NE(d3d12Source.find("Texture2D ShadowMapTexture : register(t1)"), std::string::npos); + EXPECT_EQ(d3d12Source.find("space1"), std::string::npos); + EXPECT_EQ(d3d12Source.find("space6"), std::string::npos); + + const ShaderStageVariant* vulkanFragment = shader->FindVariant( + "ForwardLit", + XCEngine::Resources::ShaderType::Fragment, + XCEngine::Resources::ShaderBackend::Vulkan, + shadowKeywords); + ASSERT_NE(vulkanFragment, nullptr); + + ShaderCompileDesc vulkanCompileDesc = {}; + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant( + *pass, + XCEngine::Resources::ShaderBackend::Vulkan, + *vulkanFragment, + vulkanCompileDesc); + const std::string vulkanSource( + reinterpret_cast(vulkanCompileDesc.source.data()), + vulkanCompileDesc.source.size()); + EXPECT_NE(vulkanSource.find("cbuffer PerObjectConstants : register(b0, space0)"), std::string::npos); + EXPECT_NE(vulkanSource.find("cbuffer LightingConstants : register(b0, space1)"), std::string::npos); + EXPECT_NE(vulkanSource.find("cbuffer MaterialConstants : register(b0, space2)"), std::string::npos); + EXPECT_NE(vulkanSource.find("cbuffer ShadowReceiverConstants : register(b0, space3)"), std::string::npos); + EXPECT_NE(vulkanSource.find("Texture2D BaseColorTexture : register(t0, space4)"), std::string::npos); + EXPECT_NE(vulkanSource.find("SamplerState LinearClampSampler : register(s0, space5)"), std::string::npos); + EXPECT_NE(vulkanSource.find("Texture2D ShadowMapTexture : register(t0, space6)"), std::string::npos); + EXPECT_NE(vulkanSource.find("SamplerState ShadowMapSampler : register(s0, space7)"), std::string::npos); + + delete shader; +} + +TEST(BuiltinForwardPipeline_Test, OpenGLRuntimeTranspilesForwardShadowVariantToLegacyClipConventions) { + ShaderLoader loader; + LoadResult result = loader.Load(GetBuiltinForwardLitShaderPath()); + ASSERT_TRUE(result); + ASSERT_NE(result.resource, nullptr); + + Shader* shader = static_cast(result.resource); + ASSERT_NE(shader, nullptr); + + const ShaderPass* pass = shader->FindPass("ForwardLit"); + ASSERT_NE(pass, nullptr); + + ShaderKeywordSet shadowKeywords = {}; + shadowKeywords.enabledKeywords.PushBack("XC_MAIN_LIGHT_SHADOWS"); + + const ShaderStageVariant* openGLFragment = shader->FindVariant( + "ForwardLit", + XCEngine::Resources::ShaderType::Fragment, + XCEngine::Resources::ShaderBackend::OpenGL, + shadowKeywords); + ASSERT_NE(openGLFragment, nullptr); + + ShaderCompileDesc compileDesc = {}; + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant( + *pass, + XCEngine::Resources::ShaderBackend::OpenGL, + *openGLFragment, + compileDesc); + const std::string runtimeSource( + reinterpret_cast(compileDesc.source.data()), + compileDesc.source.size()); + EXPECT_NE(runtimeSource.find("const float shadowUvY = shadowNdc.y * 0.5f + 0.5f;"), std::string::npos); + EXPECT_NE( + runtimeSource.find("shadowNdc.z < -1.0f || shadowNdc.z > 1.0f"), + std::string::npos); + EXPECT_NE( + runtimeSource.find( + "const float receiverDepth = shadowNdc.z * 0.5f + 0.5f - gShadowBiasAndTexelSize.x;"), + std::string::npos); + + XCEngine::RHI::CompiledSpirvShader spirvShader = {}; + std::string errorMessage; + ASSERT_TRUE( + XCEngine::RHI::CompileSpirvShader( + compileDesc, + XCEngine::RHI::SpirvTargetEnvironment::Vulkan, + spirvShader, + &errorMessage)) + << errorMessage; + + std::string glslSource; + ASSERT_TRUE(XCEngine::RHI::TranspileSpirvToOpenGLGLSL(spirvShader, glslSource, &errorMessage)) + << errorMessage; + EXPECT_NE( + glslSource.find("uniform sampler2D SPIRV_Cross_CombinedBaseColorTextureLinearClampSampler;"), + std::string::npos); + EXPECT_NE( + glslSource.find("uniform sampler2D SPIRV_Cross_CombinedShadowMapTextureShadowMapSampler;"), + std::string::npos); + EXPECT_NE( + glslSource.find("spvWorkaroundRowMajor(ShadowReceiverConstants.gWorldToShadowMatrix)"), + std::string::npos); + EXPECT_NE(glslSource.find("texture(SPIRV_Cross_CombinedShadowMapTextureShadowMapSampler"), std::string::npos); + + delete shader; +} + +TEST(BuiltinDepthStylePass_Test, OpenGLRuntimeTranspilesDepthOnlyAlphaVariantWithTexcoordAtLocation2) { + 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); + + ShaderKeywordSet alphaKeywords = {}; + alphaKeywords.enabledKeywords.PushBack("XC_ALPHA_TEST"); + + const ShaderStageVariant* openGLVertex = shader->FindVariant( + "DepthOnly", + XCEngine::Resources::ShaderType::Vertex, + ShaderBackend::OpenGL, + alphaKeywords); + ASSERT_NE(openGLVertex, nullptr); + + const ShaderStageVariant* openGLFragment = shader->FindVariant( + "DepthOnly", + XCEngine::Resources::ShaderType::Fragment, + ShaderBackend::OpenGL, + alphaKeywords); + ASSERT_NE(openGLFragment, nullptr); + + auto transpileStage = [](const ShaderPass& targetPass, + const ShaderStageVariant& variant, + std::string& glslSource) { + ShaderCompileDesc compileDesc = {}; + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant( + targetPass, + XCEngine::Resources::ShaderBackend::OpenGL, + variant, + compileDesc); + + XCEngine::RHI::CompiledSpirvShader spirvShader = {}; + std::string errorMessage; + EXPECT_TRUE( + XCEngine::RHI::CompileSpirvShader( + compileDesc, + XCEngine::RHI::SpirvTargetEnvironment::Vulkan, + spirvShader, + &errorMessage)) + << errorMessage; + EXPECT_TRUE(XCEngine::RHI::TranspileSpirvToOpenGLGLSL(spirvShader, glslSource, &errorMessage)) + << errorMessage; + }; + + std::string vertexGlsl; + transpileStage(*pass, *openGLVertex, vertexGlsl); + EXPECT_NE(vertexGlsl.find("layout(location = 2) in vec2"), std::string::npos); + + std::string fragmentGlsl; + transpileStage(*pass, *openGLFragment, fragmentGlsl); + EXPECT_NE(fragmentGlsl.find("discard;"), std::string::npos); + EXPECT_NE(fragmentGlsl.find("gl_FragDepth"), std::string::npos); + + delete shader; +} + +TEST(BuiltinDepthStylePass_Test, OpenGLRuntimeTranspilesShadowCasterAlphaVariantWithTexcoordAtLocation2) { + 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); + + ShaderKeywordSet alphaKeywords = {}; + alphaKeywords.enabledKeywords.PushBack("XC_ALPHA_TEST"); + + const ShaderStageVariant* openGLVertex = shader->FindVariant( + "ShadowCaster", + XCEngine::Resources::ShaderType::Vertex, + ShaderBackend::OpenGL, + alphaKeywords); + ASSERT_NE(openGLVertex, nullptr); + + const ShaderStageVariant* openGLFragment = shader->FindVariant( + "ShadowCaster", + XCEngine::Resources::ShaderType::Fragment, + ShaderBackend::OpenGL, + alphaKeywords); + ASSERT_NE(openGLFragment, nullptr); + + auto transpileStage = [](const ShaderPass& targetPass, + const ShaderStageVariant& variant, + std::string& glslSource) { + ShaderCompileDesc compileDesc = {}; + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant( + targetPass, + XCEngine::Resources::ShaderBackend::OpenGL, + variant, + compileDesc); + + XCEngine::RHI::CompiledSpirvShader spirvShader = {}; + std::string errorMessage; + EXPECT_TRUE( + XCEngine::RHI::CompileSpirvShader( + compileDesc, + XCEngine::RHI::SpirvTargetEnvironment::Vulkan, + spirvShader, + &errorMessage)) + << errorMessage; + EXPECT_TRUE(XCEngine::RHI::TranspileSpirvToOpenGLGLSL(spirvShader, glslSource, &errorMessage)) + << errorMessage; + }; + + std::string vertexGlsl; + transpileStage(*pass, *openGLVertex, vertexGlsl); + EXPECT_NE(vertexGlsl.find("layout(location = 2) in vec2"), std::string::npos); + + std::string fragmentGlsl; + transpileStage(*pass, *openGLFragment, fragmentGlsl); + EXPECT_NE(fragmentGlsl.find("discard;"), std::string::npos); + EXPECT_NE(fragmentGlsl.find("gl_FragDepth"), std::string::npos); delete shader; } @@ -263,7 +568,7 @@ TEST(BuiltinForwardPipeline_Test, BuiltinFinalColorShaderDeclaresExplicitFullscr delete shader; } -TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromExplicitForwardResources) { +TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoadedImplicitForwardShader) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinForwardLitShaderPath()); ASSERT_TRUE(result); @@ -277,7 +582,7 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromExplic BuiltinPassResourceBindingPlan plan = {}; String error; - EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass->resources, plan, &error)) << error.CStr(); + EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr(); EXPECT_TRUE(plan.perObject.IsValid()); EXPECT_TRUE(plan.lighting.IsValid()); EXPECT_TRUE(plan.material.IsValid()); @@ -295,7 +600,7 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromExplic delete shader; } -TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromImplicitForwardContract) { +TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromBuiltinForwardShaderContract) { ShaderPass pass = {}; pass.name = "ForwardLit"; @@ -319,7 +624,7 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromImplic EXPECT_EQ(plan.descriptorSetCount, 8u); } -TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromExplicitUnlitResources) { +TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromImplicitUnlitContract) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinUnlitShaderPath()); ASSERT_TRUE(result); @@ -333,7 +638,7 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromExplic BuiltinPassResourceBindingPlan plan = {}; String error; - EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass->resources, plan, &error)) << error.CStr(); + EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr(); EXPECT_TRUE(plan.perObject.IsValid()); EXPECT_TRUE(plan.material.IsValid()); EXPECT_TRUE(plan.baseColorTexture.IsValid()); @@ -347,7 +652,7 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromExplic delete shader; } -TEST(BuiltinForwardPipeline_Test, UsesNormalizedExplicitSetIndicesForSurfaceResources) { +TEST(BuiltinForwardPipeline_Test, UsesNormalizedImplicitSetIndicesForForwardSurfaceResources) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinForwardLitShaderPath()); ASSERT_TRUE(result); @@ -358,11 +663,11 @@ TEST(BuiltinForwardPipeline_Test, UsesNormalizedExplicitSetIndicesForSurfaceReso const ShaderPass* pass = shader->FindPass("ForwardLit"); ASSERT_NE(pass, nullptr); - ASSERT_EQ(pass->resources.Size(), 8u); + EXPECT_TRUE(pass->resources.Empty()); BuiltinPassResourceBindingPlan plan = {}; String error; - EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass->resources, plan, &error)) << error.CStr(); + EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr(); ASSERT_EQ(plan.bindings.Size(), 8u); EXPECT_EQ(plan.perObject.set, 0u); EXPECT_EQ(plan.lighting.set, 1u); @@ -376,7 +681,7 @@ TEST(BuiltinForwardPipeline_Test, UsesNormalizedExplicitSetIndicesForSurfaceReso delete shader; } -TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromExplicitForwardResources) { +TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromImplicitForwardResources) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinForwardLitShaderPath()); ASSERT_TRUE(result); @@ -387,11 +692,11 @@ TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromExplicitForwardResources) const ShaderPass* pass = shader->FindPass("ForwardLit"); ASSERT_NE(pass, nullptr); - ASSERT_EQ(pass->resources.Size(), 8u); + EXPECT_TRUE(pass->resources.Empty()); BuiltinPassResourceBindingPlan plan = {}; String error; - ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass->resources, plan, &error)) << error.CStr(); + ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr(); std::vector setLayouts; ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr(); @@ -436,7 +741,7 @@ TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromExplicitForwardResources) delete shader; } -TEST(BuiltinDepthStylePass_Test, BuiltinDepthOnlyShaderDeclaresExplicitPerObjectResourceContract) { +TEST(BuiltinDepthStylePass_Test, BuiltinDepthOnlyShaderUsesUnityStyleSingleSourceContract) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinDepthOnlyShaderPath()); ASSERT_TRUE(result); @@ -447,29 +752,16 @@ TEST(BuiltinDepthStylePass_Test, BuiltinDepthOnlyShaderDeclaresExplicitPerObject const ShaderPass* pass = shader->FindPass("DepthOnly"); ASSERT_NE(pass, nullptr); - 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); + EXPECT_TRUE(pass->resources.Empty()); + EXPECT_TRUE(pass->hasFixedFunctionState); + EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::Back); + EXPECT_TRUE(pass->fixedFunctionState.depthWriteEnable); + EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual); delete shader; } -TEST(BuiltinDepthStylePass_Test, BuiltinShadowCasterShaderDeclaresExplicitPerObjectResourceContract) { +TEST(BuiltinDepthStylePass_Test, BuiltinShadowCasterShaderUsesUnityStyleSingleSourceContract) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinShadowCasterShaderPath()); ASSERT_TRUE(result); @@ -480,24 +772,11 @@ TEST(BuiltinDepthStylePass_Test, BuiltinShadowCasterShaderDeclaresExplicitPerObj const ShaderPass* pass = shader->FindPass("ShadowCaster"); ASSERT_NE(pass, nullptr); - 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); + EXPECT_TRUE(pass->resources.Empty()); + EXPECT_TRUE(pass->hasFixedFunctionState); + EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::Back); + EXPECT_TRUE(pass->fixedFunctionState.depthWriteEnable); + EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual); delete shader; } @@ -647,7 +926,7 @@ TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromExplicitObjectIdResources delete shader; } -TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromExplicitDepthOnlyResources) { +TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromBuiltinDepthOnlyShaderContract) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinDepthOnlyShaderPath()); ASSERT_TRUE(result); @@ -658,11 +937,10 @@ TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromExplicitDepthOnlyResource const ShaderPass* pass = shader->FindPass("DepthOnly"); ASSERT_NE(pass, nullptr); - ASSERT_EQ(pass->resources.Size(), 4u); BuiltinPassResourceBindingPlan plan = {}; String error; - ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass->resources, plan, &error)) << error.CStr(); + ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr(); EXPECT_TRUE(plan.perObject.IsValid()); EXPECT_TRUE(plan.material.IsValid()); EXPECT_TRUE(plan.baseColorTexture.IsValid()); @@ -703,7 +981,7 @@ TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromExplicitDepthOnlyResource delete shader; } -TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromExplicitShadowCasterResources) { +TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromBuiltinShadowCasterShaderContract) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinShadowCasterShaderPath()); ASSERT_TRUE(result); @@ -714,11 +992,10 @@ TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromExplicitShadowCasterResou const ShaderPass* pass = shader->FindPass("ShadowCaster"); ASSERT_NE(pass, nullptr); - ASSERT_EQ(pass->resources.Size(), 4u); BuiltinPassResourceBindingPlan plan = {}; String error; - ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass->resources, plan, &error)) << error.CStr(); + ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr(); EXPECT_TRUE(plan.perObject.IsValid()); EXPECT_TRUE(plan.material.IsValid()); EXPECT_TRUE(plan.baseColorTexture.IsValid()); diff --git a/tests/Resources/Shader/test_shader_loader.cpp b/tests/Resources/Shader/test_shader_loader.cpp index 26e253ed..8497a150 100644 --- a/tests/Resources/Shader/test_shader_loader.cpp +++ b/tests/Resources/Shader/test_shader_loader.cpp @@ -1547,7 +1547,7 @@ TEST(ShaderLoader, AssetDatabaseReimportsShaderWhenStageDependencyChanges) { fs::remove_all(projectRoot); } -TEST(ShaderLoader, LoadBuiltinForwardLitShaderBuildsBackendVariants) { +TEST(ShaderLoader, LoadBuiltinForwardLitShaderBuildsUnityStyleSingleSourceVariants) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinForwardLitShaderPath()); ASSERT_TRUE(result); @@ -1560,9 +1560,13 @@ TEST(ShaderLoader, LoadBuiltinForwardLitShaderBuildsBackendVariants) { const ShaderPass* pass = shader->FindPass("ForwardLit"); ASSERT_NE(pass, nullptr); ASSERT_EQ(shader->GetProperties().Size(), 3u); - ASSERT_EQ(pass->variants.Size(), 24u); + ASSERT_EQ(pass->variants.Size(), 8u); ASSERT_EQ(pass->tags.Size(), 1u); - ASSERT_EQ(pass->resources.Size(), 8u); + EXPECT_TRUE(pass->resources.Empty()); + EXPECT_TRUE(pass->hasFixedFunctionState); + EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::Back); + EXPECT_TRUE(pass->fixedFunctionState.depthWriteEnable); + EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual); EXPECT_EQ(pass->tags[0].name, "LightMode"); EXPECT_EQ(pass->tags[0].value, "ForwardBase"); @@ -1581,45 +1585,11 @@ TEST(ShaderLoader, LoadBuiltinForwardLitShaderBuildsBackendVariants) { EXPECT_EQ(cutoffProperty->type, ShaderPropertyType::Range); EXPECT_EQ(cutoffProperty->semantic, "AlphaCutoff"); - const ShaderResourceBindingDesc* perObjectBinding = - shader->FindPassResourceBinding("ForwardLit", "PerObjectConstants"); - ASSERT_NE(perObjectBinding, nullptr); - EXPECT_EQ(perObjectBinding->type, ShaderResourceType::ConstantBuffer); - EXPECT_EQ(perObjectBinding->set, 0u); - EXPECT_EQ(perObjectBinding->binding, 0u); - EXPECT_EQ(perObjectBinding->semantic, "PerObject"); - - const ShaderResourceBindingDesc* lightingBinding = - shader->FindPassResourceBinding("ForwardLit", "LightingConstants"); - ASSERT_NE(lightingBinding, nullptr); - EXPECT_EQ(lightingBinding->type, ShaderResourceType::ConstantBuffer); - EXPECT_EQ(lightingBinding->set, 1u); - EXPECT_EQ(lightingBinding->binding, 0u); - EXPECT_EQ(lightingBinding->semantic, "Lighting"); - - const ShaderResourceBindingDesc* shadowReceiverBinding = - shader->FindPassResourceBinding("ForwardLit", "ShadowReceiverConstants"); - ASSERT_NE(shadowReceiverBinding, nullptr); - EXPECT_EQ(shadowReceiverBinding->type, ShaderResourceType::ConstantBuffer); - EXPECT_EQ(shadowReceiverBinding->set, 3u); - EXPECT_EQ(shadowReceiverBinding->binding, 0u); - EXPECT_EQ(shadowReceiverBinding->semantic, "ShadowReceiver"); - - const ShaderResourceBindingDesc* baseColorBinding = - shader->FindPassResourceBinding("ForwardLit", "BaseColorTexture"); - ASSERT_NE(baseColorBinding, nullptr); - EXPECT_EQ(baseColorBinding->type, ShaderResourceType::Texture2D); - EXPECT_EQ(baseColorBinding->set, 4u); - EXPECT_EQ(baseColorBinding->binding, 0u); - EXPECT_EQ(baseColorBinding->semantic, "BaseColorTexture"); - - const ShaderResourceBindingDesc* shadowTextureBinding = - shader->FindPassResourceBinding("ForwardLit", "ShadowMapTexture"); - ASSERT_NE(shadowTextureBinding, nullptr); - EXPECT_EQ(shadowTextureBinding->type, ShaderResourceType::Texture2D); - EXPECT_EQ(shadowTextureBinding->set, 6u); - EXPECT_EQ(shadowTextureBinding->binding, 0u); - EXPECT_EQ(shadowTextureBinding->semantic, "ShadowMapTexture"); + EXPECT_EQ(shader->FindPassResourceBinding("ForwardLit", "PerObjectConstants"), nullptr); + EXPECT_EQ(shader->FindPassResourceBinding("ForwardLit", "LightingConstants"), nullptr); + EXPECT_EQ(shader->FindPassResourceBinding("ForwardLit", "ShadowReceiverConstants"), nullptr); + EXPECT_EQ(shader->FindPassResourceBinding("ForwardLit", "BaseColorTexture"), nullptr); + EXPECT_EQ(shader->FindPassResourceBinding("ForwardLit", "ShadowMapTexture"), nullptr); ASSERT_EQ(pass->keywordDeclarations.Size(), 2u); EXPECT_EQ(pass->keywordDeclarations[0].type, ShaderKeywordDeclarationType::MultiCompile); ASSERT_EQ(pass->keywordDeclarations[0].options.Size(), 2u); @@ -1631,7 +1601,7 @@ TEST(ShaderLoader, LoadBuiltinForwardLitShaderBuildsBackendVariants) { EXPECT_EQ(pass->keywordDeclarations[1].options[1], "XC_ALPHA_TEST"); EXPECT_TRUE(shader->PassDeclaresKeyword("ForwardLit", "XC_MAIN_LIGHT_SHADOWS")); EXPECT_TRUE(shader->PassDeclaresKeyword("ForwardLit", "XC_ALPHA_TEST")); - ASSERT_EQ(pass->variants.Size(), 24u); + ASSERT_EQ(pass->variants.Size(), 8u); EXPECT_NE(shader->FindVariant("ForwardLit", ShaderType::Vertex, ShaderBackend::D3D12), nullptr); EXPECT_NE(shader->FindVariant("ForwardLit", ShaderType::Fragment, ShaderBackend::D3D12), nullptr); @@ -1645,13 +1615,21 @@ TEST(ShaderLoader, LoadBuiltinForwardLitShaderBuildsBackendVariants) { ShaderType::Vertex, ShaderBackend::D3D12); ASSERT_NE(d3d12Vertex, nullptr); - EXPECT_NE(std::string(d3d12Vertex->sourceCode.CStr()).find("XC_BUILTIN_FORWARD_LIT_D3D12_VS"), std::string::npos); + EXPECT_EQ(d3d12Vertex->backend, ShaderBackend::Generic); + EXPECT_EQ(d3d12Vertex->language, ShaderLanguage::HLSL); + EXPECT_EQ(d3d12Vertex->entryPoint, "MainVS"); + EXPECT_EQ(d3d12Vertex->profile, "vs_5_0"); + EXPECT_NE(std::string(d3d12Vertex->sourceCode.CStr()).find("cbuffer LightingConstants"), std::string::npos); const ShaderStageVariant* d3d12Fragment = shader->FindVariant( "ForwardLit", ShaderType::Fragment, ShaderBackend::D3D12); ASSERT_NE(d3d12Fragment, nullptr); + EXPECT_EQ(d3d12Fragment->backend, ShaderBackend::Generic); + EXPECT_EQ(d3d12Fragment->language, ShaderLanguage::HLSL); + EXPECT_EQ(d3d12Fragment->entryPoint, "MainPS"); + EXPECT_EQ(d3d12Fragment->profile, "ps_5_0"); EXPECT_NE(std::string(d3d12Fragment->sourceCode.CStr()).find("gAdditionalLights"), std::string::npos); EXPECT_NE(std::string(d3d12Fragment->sourceCode.CStr()).find("gLightingParams"), std::string::npos); EXPECT_EQ(std::string(d3d12Fragment->sourceCode.CStr()).find("#define XC_MAIN_LIGHT_SHADOWS 1"), std::string::npos); @@ -1669,7 +1647,7 @@ TEST(ShaderLoader, LoadBuiltinForwardLitShaderBuildsBackendVariants) { std::string(shadowD3D12Fragment->sourceCode.CStr()).find("#define XC_MAIN_LIGHT_SHADOWS 1"), std::string::npos); EXPECT_NE( - std::string(shadowD3D12Fragment->sourceCode.CStr()).find("gShadowMapTexture.Sample"), + std::string(shadowD3D12Fragment->sourceCode.CStr()).find("ShadowMapTexture.Sample"), std::string::npos); ShaderKeywordSet alphaShadowKeywords = {}; @@ -1695,13 +1673,13 @@ TEST(ShaderLoader, LoadBuiltinForwardLitShaderBuildsBackendVariants) { ShaderBackend::OpenGL, alphaShadowKeywords); ASSERT_NE(alphaShadowOpenGLFragment, nullptr); + EXPECT_EQ(alphaShadowOpenGLFragment->backend, ShaderBackend::Generic); + EXPECT_EQ(alphaShadowOpenGLFragment->language, ShaderLanguage::HLSL); + EXPECT_EQ(alphaShadowOpenGLFragment->entryPoint, "MainPS"); + EXPECT_EQ(alphaShadowOpenGLFragment->profile, "ps_5_0"); const std::string alphaShadowOpenGLSource = alphaShadowOpenGLFragment->sourceCode.CStr(); - EXPECT_NE(alphaShadowOpenGLSource.find("#version 430"), std::string::npos); EXPECT_NE(alphaShadowOpenGLSource.find("#define XC_ALPHA_TEST 1"), std::string::npos); - EXPECT_LT( - alphaShadowOpenGLSource.find("#version 430"), - alphaShadowOpenGLSource.find("#define XC_ALPHA_TEST 1")); - EXPECT_NE(alphaShadowOpenGLSource.find("discard;"), std::string::npos); + EXPECT_NE(alphaShadowOpenGLSource.find("clip(baseColor.a - gAlphaCutoffParams.x);"), std::string::npos); const ShaderStageVariant* alphaShadowVulkanFragment = shader->FindVariant( "ForwardLit", @@ -1709,19 +1687,23 @@ TEST(ShaderLoader, LoadBuiltinForwardLitShaderBuildsBackendVariants) { ShaderBackend::Vulkan, alphaShadowKeywords); ASSERT_NE(alphaShadowVulkanFragment, nullptr); + EXPECT_EQ(alphaShadowVulkanFragment->backend, ShaderBackend::Generic); + EXPECT_EQ(alphaShadowVulkanFragment->language, ShaderLanguage::HLSL); + EXPECT_EQ(alphaShadowVulkanFragment->entryPoint, "MainPS"); + EXPECT_EQ(alphaShadowVulkanFragment->profile, "ps_5_0"); const std::string alphaShadowVulkanSource = alphaShadowVulkanFragment->sourceCode.CStr(); - EXPECT_NE(alphaShadowVulkanSource.find("#version 450"), std::string::npos); EXPECT_NE(alphaShadowVulkanSource.find("#define XC_ALPHA_TEST 1"), std::string::npos); - EXPECT_LT( - alphaShadowVulkanSource.find("#version 450"), - alphaShadowVulkanSource.find("#define XC_ALPHA_TEST 1")); + EXPECT_NE(alphaShadowVulkanSource.find("ShadowMapTexture.Sample"), std::string::npos); const ShaderStageVariant* openglFragment = shader->FindVariant( "ForwardLit", ShaderType::Fragment, ShaderBackend::OpenGL); ASSERT_NE(openglFragment, nullptr); - EXPECT_NE(std::string(openglFragment->sourceCode.CStr()).find("XC_BUILTIN_FORWARD_LIT_OPENGL_PS"), std::string::npos); + EXPECT_EQ(openglFragment->backend, ShaderBackend::Generic); + EXPECT_EQ(openglFragment->language, ShaderLanguage::HLSL); + EXPECT_EQ(openglFragment->entryPoint, "MainPS"); + EXPECT_EQ(openglFragment->profile, "ps_5_0"); EXPECT_NE(std::string(openglFragment->sourceCode.CStr()).find("gAdditionalLights"), std::string::npos); EXPECT_NE(std::string(openglFragment->sourceCode.CStr()).find("gLightingParams"), std::string::npos); @@ -1730,14 +1712,17 @@ TEST(ShaderLoader, LoadBuiltinForwardLitShaderBuildsBackendVariants) { ShaderType::Fragment, ShaderBackend::Vulkan); ASSERT_NE(vulkanFragment, nullptr); - EXPECT_NE(std::string(vulkanFragment->sourceCode.CStr()).find("XC_BUILTIN_FORWARD_LIT_VULKAN_PS"), std::string::npos); + EXPECT_EQ(vulkanFragment->backend, ShaderBackend::Generic); + EXPECT_EQ(vulkanFragment->language, ShaderLanguage::HLSL); + EXPECT_EQ(vulkanFragment->entryPoint, "MainPS"); + EXPECT_EQ(vulkanFragment->profile, "ps_5_0"); EXPECT_NE(std::string(vulkanFragment->sourceCode.CStr()).find("gAdditionalLights"), std::string::npos); EXPECT_NE(std::string(vulkanFragment->sourceCode.CStr()).find("gLightingParams"), std::string::npos); delete shader; } -TEST(ShaderLoader, LoadBuiltinUnlitShaderBuildsBackendVariants) { +TEST(ShaderLoader, LoadBuiltinUnlitShaderBuildsUnityStyleSingleSourceVariants) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinUnlitShaderPath()); ASSERT_TRUE(result); @@ -1750,9 +1735,13 @@ TEST(ShaderLoader, LoadBuiltinUnlitShaderBuildsBackendVariants) { const ShaderPass* pass = shader->FindPass("Unlit"); ASSERT_NE(pass, nullptr); ASSERT_EQ(shader->GetProperties().Size(), 2u); - ASSERT_EQ(pass->variants.Size(), 6u); + ASSERT_EQ(pass->variants.Size(), 2u); ASSERT_EQ(pass->tags.Size(), 1u); - ASSERT_EQ(pass->resources.Size(), 4u); + EXPECT_TRUE(pass->resources.Empty()); + EXPECT_TRUE(pass->hasFixedFunctionState); + EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::Back); + EXPECT_TRUE(pass->fixedFunctionState.depthWriteEnable); + EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual); EXPECT_EQ(pass->tags[0].name, "LightMode"); EXPECT_EQ(pass->tags[0].value, "Unlit"); @@ -1766,13 +1755,7 @@ TEST(ShaderLoader, LoadBuiltinUnlitShaderBuildsBackendVariants) { EXPECT_EQ(baseMapProperty->type, ShaderPropertyType::Texture2D); EXPECT_EQ(baseMapProperty->semantic, "BaseColorTexture"); - const ShaderResourceBindingDesc* perObjectBinding = - shader->FindPassResourceBinding("Unlit", "PerObjectConstants"); - ASSERT_NE(perObjectBinding, nullptr); - EXPECT_EQ(perObjectBinding->type, ShaderResourceType::ConstantBuffer); - EXPECT_EQ(perObjectBinding->set, 0u); - EXPECT_EQ(perObjectBinding->binding, 0u); - EXPECT_EQ(perObjectBinding->semantic, "PerObject"); + EXPECT_EQ(shader->FindPassResourceBinding("Unlit", "PerObjectConstants"), nullptr); EXPECT_NE(shader->FindVariant("Unlit", ShaderType::Vertex, ShaderBackend::D3D12), nullptr); EXPECT_NE(shader->FindVariant("Unlit", ShaderType::Fragment, ShaderBackend::D3D12), nullptr); @@ -1786,26 +1769,38 @@ TEST(ShaderLoader, LoadBuiltinUnlitShaderBuildsBackendVariants) { ShaderType::Vertex, ShaderBackend::D3D12); ASSERT_NE(d3d12Vertex, nullptr); - EXPECT_NE(std::string(d3d12Vertex->sourceCode.CStr()).find("XC_BUILTIN_UNLIT_D3D12_VS"), std::string::npos); + EXPECT_EQ(d3d12Vertex->backend, ShaderBackend::Generic); + EXPECT_EQ(d3d12Vertex->language, ShaderLanguage::HLSL); + EXPECT_EQ(d3d12Vertex->entryPoint, "MainVS"); + EXPECT_EQ(d3d12Vertex->profile, "vs_5_0"); + EXPECT_NE(std::string(d3d12Vertex->sourceCode.CStr()).find("gProjectionMatrix"), std::string::npos); const ShaderStageVariant* openglFragment = shader->FindVariant( "Unlit", ShaderType::Fragment, ShaderBackend::OpenGL); ASSERT_NE(openglFragment, nullptr); - EXPECT_NE(std::string(openglFragment->sourceCode.CStr()).find("XC_BUILTIN_UNLIT_OPENGL_PS"), std::string::npos); + EXPECT_EQ(openglFragment->backend, ShaderBackend::Generic); + EXPECT_EQ(openglFragment->language, ShaderLanguage::HLSL); + EXPECT_EQ(openglFragment->entryPoint, "MainPS"); + EXPECT_EQ(openglFragment->profile, "ps_5_0"); + EXPECT_NE(std::string(openglFragment->sourceCode.CStr()).find("BaseColorTexture.Sample"), std::string::npos); const ShaderStageVariant* vulkanFragment = shader->FindVariant( "Unlit", ShaderType::Fragment, ShaderBackend::Vulkan); ASSERT_NE(vulkanFragment, nullptr); - EXPECT_NE(std::string(vulkanFragment->sourceCode.CStr()).find("XC_BUILTIN_UNLIT_VULKAN_PS"), std::string::npos); + EXPECT_EQ(vulkanFragment->backend, ShaderBackend::Generic); + EXPECT_EQ(vulkanFragment->language, ShaderLanguage::HLSL); + EXPECT_EQ(vulkanFragment->entryPoint, "MainPS"); + EXPECT_EQ(vulkanFragment->profile, "ps_5_0"); + EXPECT_NE(std::string(vulkanFragment->sourceCode.CStr()).find("gBaseColorFactor"), std::string::npos); delete shader; } -TEST(ShaderLoader, LoadBuiltinObjectIdShaderBuildsBackendVariants) { +TEST(ShaderLoader, LoadBuiltinObjectIdShaderBuildsUnityStyleSingleSourceVariants) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinObjectIdShaderPath()); ASSERT_TRUE(result); @@ -1817,12 +1812,12 @@ TEST(ShaderLoader, LoadBuiltinObjectIdShaderBuildsBackendVariants) { const ShaderPass* pass = shader->FindPass("ObjectId"); 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); - ASSERT_EQ(pass->variants.Size(), 6u); + EXPECT_TRUE(pass->resources.Empty()); + EXPECT_TRUE(pass->hasFixedFunctionState); + EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::Back); + EXPECT_TRUE(pass->fixedFunctionState.depthWriteEnable); + EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual); + ASSERT_EQ(pass->variants.Size(), 2u); ASSERT_EQ(pass->tags.Size(), 1u); EXPECT_EQ(pass->tags[0].name, "LightMode"); EXPECT_EQ(pass->tags[0].value, "ObjectId"); @@ -1839,26 +1834,38 @@ TEST(ShaderLoader, LoadBuiltinObjectIdShaderBuildsBackendVariants) { ShaderType::Vertex, ShaderBackend::D3D12); ASSERT_NE(d3d12Vertex, nullptr); - EXPECT_NE(std::string(d3d12Vertex->sourceCode.CStr()).find("XC_BUILTIN_OBJECT_ID_D3D12_VS"), std::string::npos); + EXPECT_EQ(d3d12Vertex->backend, ShaderBackend::Generic); + EXPECT_EQ(d3d12Vertex->language, ShaderLanguage::HLSL); + EXPECT_EQ(d3d12Vertex->entryPoint, "MainVS"); + EXPECT_EQ(d3d12Vertex->profile, "vs_5_0"); + EXPECT_NE(std::string(d3d12Vertex->sourceCode.CStr()).find("gObjectIdColor"), std::string::npos); const ShaderStageVariant* openglFragment = shader->FindVariant( "ObjectId", ShaderType::Fragment, ShaderBackend::OpenGL); ASSERT_NE(openglFragment, nullptr); - EXPECT_NE(std::string(openglFragment->sourceCode.CStr()).find("XC_BUILTIN_OBJECT_ID_OPENGL_PS"), std::string::npos); + EXPECT_EQ(openglFragment->backend, ShaderBackend::Generic); + EXPECT_EQ(openglFragment->language, ShaderLanguage::HLSL); + EXPECT_EQ(openglFragment->entryPoint, "MainPS"); + EXPECT_EQ(openglFragment->profile, "ps_5_0"); + EXPECT_NE(std::string(openglFragment->sourceCode.CStr()).find("return gObjectIdColor;"), std::string::npos); const ShaderStageVariant* vulkanFragment = shader->FindVariant( "ObjectId", ShaderType::Fragment, ShaderBackend::Vulkan); ASSERT_NE(vulkanFragment, nullptr); - EXPECT_NE(std::string(vulkanFragment->sourceCode.CStr()).find("XC_BUILTIN_OBJECT_ID_VULKAN_PS"), std::string::npos); + EXPECT_EQ(vulkanFragment->backend, ShaderBackend::Generic); + EXPECT_EQ(vulkanFragment->language, ShaderLanguage::HLSL); + EXPECT_EQ(vulkanFragment->entryPoint, "MainPS"); + EXPECT_EQ(vulkanFragment->profile, "ps_5_0"); + EXPECT_NE(std::string(vulkanFragment->sourceCode.CStr()).find("gModelMatrix"), std::string::npos); delete shader; } -TEST(ShaderLoader, LoadBuiltinDepthOnlyShaderBuildsBackendVariants) { +TEST(ShaderLoader, LoadBuiltinDepthOnlyShaderBuildsUnityStyleSingleSourceVariants) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinDepthOnlyShaderPath()); ASSERT_TRUE(result); @@ -1871,24 +1878,12 @@ TEST(ShaderLoader, LoadBuiltinDepthOnlyShaderBuildsBackendVariants) { const ShaderPass* pass = shader->FindPass("DepthOnly"); ASSERT_NE(pass, nullptr); 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); - 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); + EXPECT_TRUE(pass->resources.Empty()); + EXPECT_TRUE(pass->hasFixedFunctionState); + EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::Back); + EXPECT_TRUE(pass->fixedFunctionState.depthWriteEnable); + EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual); + ASSERT_EQ(pass->variants.Size(), 4u); ASSERT_EQ(pass->tags.Size(), 1u); EXPECT_EQ(pass->tags[0].name, "LightMode"); EXPECT_EQ(pass->tags[0].value, "DepthOnly"); @@ -1897,6 +1892,27 @@ TEST(ShaderLoader, LoadBuiltinDepthOnlyShaderBuildsBackendVariants) { 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_TRUE(shader->PassDeclaresKeyword("DepthOnly", "XC_ALPHA_TEST")); + + const ShaderPropertyDesc* baseColorProperty = shader->FindProperty("_BaseColor"); + ASSERT_NE(baseColorProperty, nullptr); + EXPECT_EQ(baseColorProperty->type, ShaderPropertyType::Color); + EXPECT_EQ(baseColorProperty->semantic, "BaseColor"); + + const ShaderPropertyDesc* cutoffProperty = shader->FindProperty("_Cutoff"); + ASSERT_NE(cutoffProperty, nullptr); + EXPECT_EQ(cutoffProperty->type, ShaderPropertyType::Range); + EXPECT_EQ(cutoffProperty->semantic, "AlphaCutoff"); + + const ShaderPropertyDesc* baseMapProperty = shader->FindProperty("_MainTex"); + ASSERT_NE(baseMapProperty, nullptr); + EXPECT_EQ(baseMapProperty->type, ShaderPropertyType::Texture2D); + EXPECT_EQ(baseMapProperty->semantic, "BaseColorTexture"); + + EXPECT_EQ(shader->FindPassResourceBinding("DepthOnly", "PerObjectConstants"), nullptr); + EXPECT_EQ(shader->FindPassResourceBinding("DepthOnly", "MaterialConstants"), nullptr); + EXPECT_EQ(shader->FindPassResourceBinding("DepthOnly", "BaseColorTexture"), nullptr); + EXPECT_EQ(shader->FindPassResourceBinding("DepthOnly", "LinearClampSampler"), nullptr); EXPECT_NE(shader->FindVariant("DepthOnly", ShaderType::Vertex, ShaderBackend::D3D12), nullptr); EXPECT_NE(shader->FindVariant("DepthOnly", ShaderType::Fragment, ShaderBackend::D3D12), nullptr); @@ -1910,21 +1926,33 @@ TEST(ShaderLoader, LoadBuiltinDepthOnlyShaderBuildsBackendVariants) { ShaderType::Vertex, ShaderBackend::D3D12); ASSERT_NE(d3d12Vertex, nullptr); - EXPECT_NE(std::string(d3d12Vertex->sourceCode.CStr()).find("XC_BUILTIN_DEPTH_ONLY_D3D12_VS"), std::string::npos); + EXPECT_EQ(d3d12Vertex->backend, ShaderBackend::Generic); + EXPECT_EQ(d3d12Vertex->language, ShaderLanguage::HLSL); + EXPECT_EQ(d3d12Vertex->entryPoint, "MainVS"); + EXPECT_EQ(d3d12Vertex->profile, "vs_5_0"); + EXPECT_NE(std::string(d3d12Vertex->sourceCode.CStr()).find("gProjectionMatrix"), std::string::npos); const ShaderStageVariant* openglFragment = shader->FindVariant( "DepthOnly", ShaderType::Fragment, ShaderBackend::OpenGL); ASSERT_NE(openglFragment, nullptr); - EXPECT_NE(std::string(openglFragment->sourceCode.CStr()).find("XC_BUILTIN_DEPTH_ONLY_OPENGL_PS"), std::string::npos); + EXPECT_EQ(openglFragment->backend, ShaderBackend::Generic); + EXPECT_EQ(openglFragment->language, ShaderLanguage::HLSL); + EXPECT_EQ(openglFragment->entryPoint, "MainPS"); + EXPECT_EQ(openglFragment->profile, "ps_5_0"); + EXPECT_NE(std::string(openglFragment->sourceCode.CStr()).find("BaseColorTexture.Sample"), std::string::npos); const ShaderStageVariant* vulkanFragment = shader->FindVariant( "DepthOnly", ShaderType::Fragment, ShaderBackend::Vulkan); ASSERT_NE(vulkanFragment, nullptr); - EXPECT_NE(std::string(vulkanFragment->sourceCode.CStr()).find("XC_BUILTIN_DEPTH_ONLY_VULKAN_PS"), std::string::npos); + EXPECT_EQ(vulkanFragment->backend, ShaderBackend::Generic); + EXPECT_EQ(vulkanFragment->language, ShaderLanguage::HLSL); + EXPECT_EQ(vulkanFragment->entryPoint, "MainPS"); + EXPECT_EQ(vulkanFragment->profile, "ps_5_0"); + EXPECT_NE(std::string(vulkanFragment->sourceCode.CStr()).find("gAlphaCutoffParams"), std::string::npos); ShaderKeywordSet alphaKeywords = {}; alphaKeywords.enabledKeywords.PushBack("XC_ALPHA_TEST"); @@ -1935,6 +1963,10 @@ TEST(ShaderLoader, LoadBuiltinDepthOnlyShaderBuildsBackendVariants) { ShaderBackend::D3D12, alphaKeywords); ASSERT_NE(alphaD3D12Fragment, nullptr); + EXPECT_EQ(alphaD3D12Fragment->backend, ShaderBackend::Generic); + EXPECT_EQ(alphaD3D12Fragment->language, ShaderLanguage::HLSL); + EXPECT_EQ(alphaD3D12Fragment->entryPoint, "MainPS"); + EXPECT_EQ(alphaD3D12Fragment->profile, "ps_5_0"); EXPECT_NE( std::string(alphaD3D12Fragment->sourceCode.CStr()).find("#define XC_ALPHA_TEST 1"), std::string::npos); @@ -1948,18 +1980,18 @@ TEST(ShaderLoader, LoadBuiltinDepthOnlyShaderBuildsBackendVariants) { ShaderBackend::OpenGL, alphaKeywords); ASSERT_NE(alphaOpenGLFragment, nullptr); + EXPECT_EQ(alphaOpenGLFragment->backend, ShaderBackend::Generic); + EXPECT_EQ(alphaOpenGLFragment->language, ShaderLanguage::HLSL); + EXPECT_EQ(alphaOpenGLFragment->entryPoint, "MainPS"); + EXPECT_EQ(alphaOpenGLFragment->profile, "ps_5_0"); const std::string alphaOpenGLSource = alphaOpenGLFragment->sourceCode.CStr(); - EXPECT_NE(alphaOpenGLSource.find("#version 430"), std::string::npos); EXPECT_NE(alphaOpenGLSource.find("#define XC_ALPHA_TEST 1"), std::string::npos); - EXPECT_LT( - alphaOpenGLSource.find("#version 430"), - alphaOpenGLSource.find("#define XC_ALPHA_TEST 1")); - EXPECT_NE(alphaOpenGLSource.find("discard;"), std::string::npos); + EXPECT_NE(alphaOpenGLSource.find("clip(baseColor.a - gAlphaCutoffParams.x);"), std::string::npos); delete shader; } -TEST(ShaderLoader, LoadBuiltinShadowCasterShaderBuildsBackendVariants) { +TEST(ShaderLoader, LoadBuiltinShadowCasterShaderBuildsUnityStyleSingleSourceVariants) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinShadowCasterShaderPath()); ASSERT_TRUE(result); @@ -1972,24 +2004,12 @@ TEST(ShaderLoader, LoadBuiltinShadowCasterShaderBuildsBackendVariants) { const ShaderPass* pass = shader->FindPass("ShadowCaster"); ASSERT_NE(pass, nullptr); 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); - 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); + EXPECT_TRUE(pass->resources.Empty()); + EXPECT_TRUE(pass->hasFixedFunctionState); + EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::Back); + EXPECT_TRUE(pass->fixedFunctionState.depthWriteEnable); + EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual); + ASSERT_EQ(pass->variants.Size(), 4u); ASSERT_EQ(pass->tags.Size(), 1u); EXPECT_EQ(pass->tags[0].name, "LightMode"); EXPECT_EQ(pass->tags[0].value, "ShadowCaster"); @@ -1998,6 +2018,27 @@ TEST(ShaderLoader, LoadBuiltinShadowCasterShaderBuildsBackendVariants) { 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_TRUE(shader->PassDeclaresKeyword("ShadowCaster", "XC_ALPHA_TEST")); + + const ShaderPropertyDesc* baseColorProperty = shader->FindProperty("_BaseColor"); + ASSERT_NE(baseColorProperty, nullptr); + EXPECT_EQ(baseColorProperty->type, ShaderPropertyType::Color); + EXPECT_EQ(baseColorProperty->semantic, "BaseColor"); + + const ShaderPropertyDesc* cutoffProperty = shader->FindProperty("_Cutoff"); + ASSERT_NE(cutoffProperty, nullptr); + EXPECT_EQ(cutoffProperty->type, ShaderPropertyType::Range); + EXPECT_EQ(cutoffProperty->semantic, "AlphaCutoff"); + + const ShaderPropertyDesc* baseMapProperty = shader->FindProperty("_MainTex"); + ASSERT_NE(baseMapProperty, nullptr); + EXPECT_EQ(baseMapProperty->type, ShaderPropertyType::Texture2D); + EXPECT_EQ(baseMapProperty->semantic, "BaseColorTexture"); + + EXPECT_EQ(shader->FindPassResourceBinding("ShadowCaster", "PerObjectConstants"), nullptr); + EXPECT_EQ(shader->FindPassResourceBinding("ShadowCaster", "MaterialConstants"), nullptr); + EXPECT_EQ(shader->FindPassResourceBinding("ShadowCaster", "BaseColorTexture"), nullptr); + EXPECT_EQ(shader->FindPassResourceBinding("ShadowCaster", "LinearClampSampler"), nullptr); EXPECT_NE(shader->FindVariant("ShadowCaster", ShaderType::Vertex, ShaderBackend::D3D12), nullptr); EXPECT_NE(shader->FindVariant("ShadowCaster", ShaderType::Fragment, ShaderBackend::D3D12), nullptr); @@ -2011,27 +2052,33 @@ TEST(ShaderLoader, LoadBuiltinShadowCasterShaderBuildsBackendVariants) { ShaderType::Vertex, ShaderBackend::D3D12); ASSERT_NE(d3d12Vertex, nullptr); - EXPECT_NE( - std::string(d3d12Vertex->sourceCode.CStr()).find("XC_BUILTIN_SHADOW_CASTER_D3D12_VS"), - std::string::npos); + EXPECT_EQ(d3d12Vertex->backend, ShaderBackend::Generic); + EXPECT_EQ(d3d12Vertex->language, ShaderLanguage::HLSL); + EXPECT_EQ(d3d12Vertex->entryPoint, "MainVS"); + EXPECT_EQ(d3d12Vertex->profile, "vs_5_0"); + EXPECT_NE(std::string(d3d12Vertex->sourceCode.CStr()).find("gProjectionMatrix"), std::string::npos); const ShaderStageVariant* openglFragment = shader->FindVariant( "ShadowCaster", ShaderType::Fragment, ShaderBackend::OpenGL); ASSERT_NE(openglFragment, nullptr); - EXPECT_NE( - std::string(openglFragment->sourceCode.CStr()).find("XC_BUILTIN_SHADOW_CASTER_OPENGL_PS"), - std::string::npos); + EXPECT_EQ(openglFragment->backend, ShaderBackend::Generic); + EXPECT_EQ(openglFragment->language, ShaderLanguage::HLSL); + EXPECT_EQ(openglFragment->entryPoint, "MainPS"); + EXPECT_EQ(openglFragment->profile, "ps_5_0"); + EXPECT_NE(std::string(openglFragment->sourceCode.CStr()).find("return input.position.z;"), std::string::npos); const ShaderStageVariant* vulkanFragment = shader->FindVariant( "ShadowCaster", ShaderType::Fragment, ShaderBackend::Vulkan); ASSERT_NE(vulkanFragment, nullptr); - EXPECT_NE( - std::string(vulkanFragment->sourceCode.CStr()).find("XC_BUILTIN_SHADOW_CASTER_VULKAN_PS"), - std::string::npos); + EXPECT_EQ(vulkanFragment->backend, ShaderBackend::Generic); + EXPECT_EQ(vulkanFragment->language, ShaderLanguage::HLSL); + EXPECT_EQ(vulkanFragment->entryPoint, "MainPS"); + EXPECT_EQ(vulkanFragment->profile, "ps_5_0"); + EXPECT_NE(std::string(vulkanFragment->sourceCode.CStr()).find("gAlphaCutoffParams"), std::string::npos); ShaderKeywordSet alphaKeywords = {}; alphaKeywords.enabledKeywords.PushBack("XC_ALPHA_TEST"); @@ -2042,6 +2089,10 @@ TEST(ShaderLoader, LoadBuiltinShadowCasterShaderBuildsBackendVariants) { ShaderBackend::D3D12, alphaKeywords); ASSERT_NE(alphaD3D12Fragment, nullptr); + EXPECT_EQ(alphaD3D12Fragment->backend, ShaderBackend::Generic); + EXPECT_EQ(alphaD3D12Fragment->language, ShaderLanguage::HLSL); + EXPECT_EQ(alphaD3D12Fragment->entryPoint, "MainPS"); + EXPECT_EQ(alphaD3D12Fragment->profile, "ps_5_0"); EXPECT_NE( std::string(alphaD3D12Fragment->sourceCode.CStr()).find("#define XC_ALPHA_TEST 1"), std::string::npos); @@ -2055,13 +2106,13 @@ TEST(ShaderLoader, LoadBuiltinShadowCasterShaderBuildsBackendVariants) { ShaderBackend::OpenGL, alphaKeywords); ASSERT_NE(alphaOpenGLFragment, nullptr); + EXPECT_EQ(alphaOpenGLFragment->backend, ShaderBackend::Generic); + EXPECT_EQ(alphaOpenGLFragment->language, ShaderLanguage::HLSL); + EXPECT_EQ(alphaOpenGLFragment->entryPoint, "MainPS"); + EXPECT_EQ(alphaOpenGLFragment->profile, "ps_5_0"); const std::string alphaOpenGLSource = alphaOpenGLFragment->sourceCode.CStr(); - EXPECT_NE(alphaOpenGLSource.find("#version 430"), std::string::npos); EXPECT_NE(alphaOpenGLSource.find("#define XC_ALPHA_TEST 1"), std::string::npos); - EXPECT_LT( - alphaOpenGLSource.find("#version 430"), - alphaOpenGLSource.find("#define XC_ALPHA_TEST 1")); - EXPECT_NE(alphaOpenGLSource.find("discard;"), std::string::npos); + EXPECT_NE(alphaOpenGLSource.find("clip(baseColor.a - gAlphaCutoffParams.x);"), std::string::npos); delete shader; } @@ -2125,19 +2176,19 @@ TEST(ShaderLoader, ResourceManagerLoadsBuiltinShadersOutsideProjectWorkingDirect "ForwardLit", ShaderType::Vertex, ShaderBackend::D3D12, - "XC_BUILTIN_FORWARD_LIT_D3D12_VS"); + "cbuffer LightingConstants"); expectBuiltinShader( GetBuiltinUnlitShaderPath(), "Unlit", ShaderType::Fragment, ShaderBackend::OpenGL, - "XC_BUILTIN_UNLIT_OPENGL_PS"); + "BaseColorTexture"); expectBuiltinShader( GetBuiltinObjectIdShaderPath(), "ObjectId", ShaderType::Fragment, ShaderBackend::Vulkan, - "XC_BUILTIN_OBJECT_ID_VULKAN_PS"); + "return gObjectIdColor;"); fs::current_path(previousPath); manager.SetResourceRoot(previousResourceRoot);