From 308b3b061c463674160baf66573351ac12e56dc1 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Fri, 3 Apr 2026 17:18:46 +0800 Subject: [PATCH] Add builtin unlit surface path --- docs/plan/Shader与Material系统下一阶段计划.md | 8 +- .../builtin/shaders/unlit/unlit.frag.glsl | 24 +++++ .../builtin/shaders/unlit/unlit.frag.vk.glsl | 25 +++++ .../builtin/shaders/unlit/unlit.ps.hlsl | 25 +++++ .../assets/builtin/shaders/unlit/unlit.shader | 99 +++++++++++++++++++ .../builtin/shaders/unlit/unlit.vert.glsl | 23 +++++ .../builtin/shaders/unlit/unlit.vert.vk.glsl | 23 +++++ .../builtin/shaders/unlit/unlit.vs.hlsl | 29 ++++++ .../Pipelines/BuiltinForwardPipeline.h | 3 +- .../XCEngine/Resources/BuiltinResources.h | 1 + .../Pipelines/BuiltinForwardPipeline.cpp | 61 +++++++++--- engine/src/Resources/BuiltinResources.cpp | 16 +++ .../unit/test_builtin_forward_pipeline.cpp | 64 ++++++++++++ tests/Resources/Shader/test_shader_loader.cpp | 79 +++++++++++++++ 14 files changed, 466 insertions(+), 14 deletions(-) create mode 100644 engine/assets/builtin/shaders/unlit/unlit.frag.glsl create mode 100644 engine/assets/builtin/shaders/unlit/unlit.frag.vk.glsl create mode 100644 engine/assets/builtin/shaders/unlit/unlit.ps.hlsl create mode 100644 engine/assets/builtin/shaders/unlit/unlit.shader create mode 100644 engine/assets/builtin/shaders/unlit/unlit.vert.glsl create mode 100644 engine/assets/builtin/shaders/unlit/unlit.vert.vk.glsl create mode 100644 engine/assets/builtin/shaders/unlit/unlit.vs.hlsl diff --git a/docs/plan/Shader与Material系统下一阶段计划.md b/docs/plan/Shader与Material系统下一阶段计划.md index 46d47149..861cb48e 100644 --- a/docs/plan/Shader与Material系统下一阶段计划.md +++ b/docs/plan/Shader与Material系统下一阶段计划.md @@ -342,8 +342,12 @@ Unity-like Shader Authoring (.shader) - builtin object-id shader 已显式声明 `PerObject` 资源合约 - `BuiltinObjectIdPass` 已改为消费通用 binding plan,不再硬编码 `set0/binding0` 常量布局 - 显式 shader `resources` 与 legacy object-id fallback 现在走同一套解析与校验路径 -- 已验证:`rendering_unit_tests` 59/59,`shader_tests` 26/26 -- 下一步:把同一套共享执行边界继续推到 `Unlit`,并评估是否抽出 forward/object-id 共用的 pass layout 构建与 descriptor set 组装骨架 +- 已完成:builtin `Unlit` shader / pipeline 主线接入共享执行边界 + - 新增 builtin `unlit` shader 资产与 `BuiltinResources` 入口 + - `BuiltinForwardPipeline` 现在会在 `ForwardLit + Unlit` 之间按 material/shader metadata 解析目标 pass + - `Unlit` 与 `ForwardLit` 现在共用同一套 input layout、material schema、binding plan 与 descriptor 组装路径 +- 已验证:`rendering_unit_tests` 61/61,`shader_tests` 27/27,`material_tests` 51/51 +- 下一步:评估是否抽出 `ForwardLit / Unlit / ObjectId` 共用的 pass layout 构建与 descriptor set 组装骨架,并继续推进 `DepthOnly / ShadowCaster` ### 阶段 D:扩展 AssetDatabase / Library Artifact 能力 diff --git a/engine/assets/builtin/shaders/unlit/unlit.frag.glsl b/engine/assets/builtin/shaders/unlit/unlit.frag.glsl new file mode 100644 index 00000000..a21c239e --- /dev/null +++ b/engine/assets/builtin/shaders/unlit/unlit.frag.glsl @@ -0,0 +1,24 @@ +// XC_BUILTIN_UNLIT_OPENGL_PS +#version 430 +layout(binding = 1) uniform sampler2D uBaseColorTexture; + +layout(std140, binding = 1) uniform PerObjectConstants { + mat4 gProjectionMatrix; + mat4 gViewMatrix; + mat4 gModelMatrix; + mat4 gNormalMatrix; + vec4 gMainLightDirectionAndIntensity; + vec4 gMainLightColorAndFlags; +}; + +layout(std140, binding = 2) uniform MaterialConstants { + vec4 gBaseColorFactor; +}; + +in vec2 vTexCoord; + +layout(location = 0) out vec4 fragColor; + +void main() { + fragColor = texture(uBaseColorTexture, vTexCoord) * gBaseColorFactor; +} diff --git a/engine/assets/builtin/shaders/unlit/unlit.frag.vk.glsl b/engine/assets/builtin/shaders/unlit/unlit.frag.vk.glsl new file mode 100644 index 00000000..87e6c3a3 --- /dev/null +++ b/engine/assets/builtin/shaders/unlit/unlit.frag.vk.glsl @@ -0,0 +1,25 @@ +// XC_BUILTIN_UNLIT_VULKAN_PS +#version 450 +layout(set = 3, binding = 0) uniform texture2D uBaseColorTexture; +layout(set = 4, binding = 0) uniform sampler uLinearSampler; + +layout(set = 1, binding = 0, std140) uniform PerObjectConstants { + mat4 gProjectionMatrix; + mat4 gViewMatrix; + mat4 gModelMatrix; + mat4 gNormalMatrix; + vec4 gMainLightDirectionAndIntensity; + vec4 gMainLightColorAndFlags; +}; + +layout(set = 2, binding = 0, std140) uniform MaterialConstants { + vec4 gBaseColorFactor; +}; + +layout(location = 0) in vec2 vTexCoord; + +layout(location = 0) out vec4 fragColor; + +void main() { + fragColor = texture(sampler2D(uBaseColorTexture, uLinearSampler), vTexCoord) * gBaseColorFactor; +} diff --git a/engine/assets/builtin/shaders/unlit/unlit.ps.hlsl b/engine/assets/builtin/shaders/unlit/unlit.ps.hlsl new file mode 100644 index 00000000..4892ba37 --- /dev/null +++ b/engine/assets/builtin/shaders/unlit/unlit.ps.hlsl @@ -0,0 +1,25 @@ +// XC_BUILTIN_UNLIT_D3D12_PS +Texture2D gBaseColorTexture : register(t1); +SamplerState gLinearSampler : register(s1); + +cbuffer PerObjectConstants : register(b1) { + float4x4 gProjectionMatrix; + float4x4 gViewMatrix; + float4x4 gModelMatrix; + float4x4 gNormalMatrix; + float4 gMainLightDirectionAndIntensity; + float4 gMainLightColorAndFlags; +}; + +cbuffer MaterialConstants : register(b2) { + float4 gBaseColorFactor; +}; + +struct PSInput { + float4 position : SV_POSITION; + float2 texcoord : TEXCOORD0; +}; + +float4 MainPS(PSInput input) : SV_TARGET { + return gBaseColorTexture.Sample(gLinearSampler, input.texcoord) * gBaseColorFactor; +} diff --git a/engine/assets/builtin/shaders/unlit/unlit.shader b/engine/assets/builtin/shaders/unlit/unlit.shader new file mode 100644 index 00000000..d54ea442 --- /dev/null +++ b/engine/assets/builtin/shaders/unlit/unlit.shader @@ -0,0 +1,99 @@ +{ + "name": "Builtin Unlit", + "properties": [ + { + "name": "_BaseColor", + "displayName": "Base Color", + "type": "Color", + "defaultValue": "(1,1,1,1)", + "semantic": "BaseColor" + }, + { + "name": "_MainTex", + "displayName": "Base Map", + "type": "2D", + "defaultValue": "white", + "semantic": "BaseColorTexture" + } + ], + "passes": [ + { + "name": "Unlit", + "tags": { + "LightMode": "Unlit" + }, + "resources": [ + { + "name": "PerObjectConstants", + "type": "ConstantBuffer", + "set": 1, + "binding": 0, + "semantic": "PerObject" + }, + { + "name": "MaterialConstants", + "type": "ConstantBuffer", + "set": 2, + "binding": 0, + "semantic": "Material" + }, + { + "name": "BaseColorTexture", + "type": "Texture2D", + "set": 3, + "binding": 0, + "semantic": "BaseColorTexture" + }, + { + "name": "LinearClampSampler", + "type": "Sampler", + "set": 4, + "binding": 0, + "semantic": "LinearClampSampler" + } + ], + "variants": [ + { + "stage": "Vertex", + "backend": "D3D12", + "language": "HLSL", + "source": "unlit.vs.hlsl", + "entryPoint": "MainVS", + "profile": "vs_5_0" + }, + { + "stage": "Fragment", + "backend": "D3D12", + "language": "HLSL", + "source": "unlit.ps.hlsl", + "entryPoint": "MainPS", + "profile": "ps_5_0" + }, + { + "stage": "Vertex", + "backend": "OpenGL", + "language": "GLSL", + "source": "unlit.vert.glsl" + }, + { + "stage": "Fragment", + "backend": "OpenGL", + "language": "GLSL", + "source": "unlit.frag.glsl" + }, + { + "stage": "Vertex", + "backend": "Vulkan", + "language": "GLSL", + "source": "unlit.vert.vk.glsl" + }, + { + "stage": "Fragment", + "backend": "Vulkan", + "language": "GLSL", + "source": "unlit.frag.vk.glsl" + } + ] + } + ] +} diff --git a/engine/assets/builtin/shaders/unlit/unlit.vert.glsl b/engine/assets/builtin/shaders/unlit/unlit.vert.glsl new file mode 100644 index 00000000..eeb7f0fb --- /dev/null +++ b/engine/assets/builtin/shaders/unlit/unlit.vert.glsl @@ -0,0 +1,23 @@ +// XC_BUILTIN_UNLIT_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 = 1) uniform PerObjectConstants { + mat4 gProjectionMatrix; + mat4 gViewMatrix; + mat4 gModelMatrix; + mat4 gNormalMatrix; + vec4 gMainLightDirectionAndIntensity; + vec4 gMainLightColorAndFlags; +}; + +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/unlit/unlit.vert.vk.glsl b/engine/assets/builtin/shaders/unlit/unlit.vert.vk.glsl new file mode 100644 index 00000000..c4e23e6f --- /dev/null +++ b/engine/assets/builtin/shaders/unlit/unlit.vert.vk.glsl @@ -0,0 +1,23 @@ +// XC_BUILTIN_UNLIT_VULKAN_VS +#version 450 +layout(location = 0) in vec3 aPosition; +layout(location = 1) in vec3 aNormal; +layout(location = 2) in vec2 aTexCoord; + +layout(set = 1, binding = 0, std140) uniform PerObjectConstants { + mat4 gProjectionMatrix; + mat4 gViewMatrix; + mat4 gModelMatrix; + mat4 gNormalMatrix; + vec4 gMainLightDirectionAndIntensity; + vec4 gMainLightColorAndFlags; +}; + +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/unlit/unlit.vs.hlsl b/engine/assets/builtin/shaders/unlit/unlit.vs.hlsl new file mode 100644 index 00000000..d7e322d5 --- /dev/null +++ b/engine/assets/builtin/shaders/unlit/unlit.vs.hlsl @@ -0,0 +1,29 @@ +// XC_BUILTIN_UNLIT_D3D12_VS +cbuffer PerObjectConstants : register(b1) { + float4x4 gProjectionMatrix; + float4x4 gViewMatrix; + float4x4 gModelMatrix; + float4x4 gNormalMatrix; + float4 gMainLightDirectionAndIntensity; + float4 gMainLightColorAndFlags; +}; + +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; + float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0f)); + float4 positionVS = mul(gViewMatrix, positionWS); + output.position = mul(gProjectionMatrix, positionVS); + output.texcoord = input.texcoord; + return output; +} diff --git a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h index 324ebccd..2ba7d952 100644 --- a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h +++ b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h @@ -180,7 +180,7 @@ private: bool EnsureInitialized(const RenderContext& context); bool CreatePipelineResources(const RenderContext& context); void DestroyPipelineResources(); - ResolvedShaderPass ResolveForwardShaderPass(const Resources::Material* material) const; + ResolvedShaderPass ResolveSurfaceShaderPass(const Resources::Material* material) const; PassResourceLayout* GetOrCreatePassResourceLayout( const RenderContext& context, const ResolvedShaderPass& resolvedShaderPass); @@ -217,6 +217,7 @@ private: RHI::RHIType m_backendType = RHI::RHIType::D3D12; bool m_initialized = false; Resources::ResourceHandle m_builtinForwardShader; + Resources::ResourceHandle m_builtinUnlitShader; RenderResourceCache m_resourceCache; diff --git a/engine/include/XCEngine/Resources/BuiltinResources.h b/engine/include/XCEngine/Resources/BuiltinResources.h index 59853b6b..93a5961d 100644 --- a/engine/include/XCEngine/Resources/BuiltinResources.h +++ b/engine/include/XCEngine/Resources/BuiltinResources.h @@ -24,6 +24,7 @@ const char* GetBuiltinPrimitiveDisplayName(BuiltinPrimitiveType primitiveType); Containers::String GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType primitiveType); Containers::String GetBuiltinDefaultPrimitiveMaterialPath(); Containers::String GetBuiltinForwardLitShaderPath(); +Containers::String GetBuiltinUnlitShaderPath(); Containers::String GetBuiltinObjectIdShaderPath(); Containers::String GetBuiltinDefaultPrimitiveTexturePath(); diff --git a/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp b/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp index b806c7de..04f81c09 100644 --- a/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp +++ b/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp @@ -118,9 +118,26 @@ bool BindingNumberExists( return false; } -const Resources::ShaderPass* FindForwardCompatiblePass( +bool TryResolveSurfacePassType( + const Resources::Material* material, + BuiltinMaterialPass& outPass) { + if (MatchesBuiltinPass(material, BuiltinMaterialPass::Unlit)) { + outPass = BuiltinMaterialPass::Unlit; + return true; + } + + if (MatchesBuiltinPass(material, BuiltinMaterialPass::ForwardLit)) { + outPass = BuiltinMaterialPass::ForwardLit; + return true; + } + + return false; +} + +const Resources::ShaderPass* FindCompatibleSurfacePass( const Resources::Shader& shader, const Resources::Material* material, + BuiltinMaterialPass pass, Resources::ShaderBackend backend) { if (material != nullptr && !material->GetShaderPass().Empty()) { const Resources::ShaderPass* explicitPass = shader.FindPass(material->GetShaderPass()); @@ -131,12 +148,16 @@ const Resources::ShaderPass* FindForwardCompatiblePass( } for (const Resources::ShaderPass& shaderPass : shader.GetPasses()) { - if (ShaderPassMatchesBuiltinPass(shaderPass, BuiltinMaterialPass::ForwardLit) && + if (ShaderPassMatchesBuiltinPass(shaderPass, pass) && ::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, shaderPass.name, backend)) { return &shaderPass; } } + if (pass != BuiltinMaterialPass::ForwardLit) { + return nullptr; + } + const Resources::ShaderPass* defaultPass = shader.FindPass("ForwardLit"); if (defaultPass != nullptr && ::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, defaultPass->name, backend)) { @@ -331,7 +352,8 @@ bool BuiltinForwardPipeline::ExecuteForwardOpaquePass(const RenderPassContext& p RHI::RHIPipelineState* currentPipelineState = nullptr; for (const VisibleRenderItem& visibleItem : sceneData.visibleItems) { const Resources::Material* material = ResolveMaterial(visibleItem); - if (!MatchesBuiltinPass(material, BuiltinMaterialPass::ForwardLit)) { + BuiltinMaterialPass pass = BuiltinMaterialPass::ForwardLit; + if (!TryResolveSurfacePassType(material, pass)) { continue; } @@ -389,6 +411,15 @@ bool BuiltinForwardPipeline::CreatePipelineResources(const RenderContext& contex return false; } + m_builtinUnlitShader = Resources::ResourceManager::Get().Load( + Resources::GetBuiltinUnlitShaderPath()); + if (!m_builtinUnlitShader.IsValid()) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinForwardPipeline failed to load builtin unlit shader resource"); + return false; + } + RHI::SamplerDesc samplerDesc = {}; samplerDesc.filter = static_cast(RHI::FilterMode::Linear); samplerDesc.addressU = static_cast(RHI::TextureAddressMode::Clamp); @@ -475,17 +506,23 @@ void BuiltinForwardPipeline::DestroyPipelineResources() { m_device = nullptr; m_initialized = false; m_builtinForwardShader.Reset(); + m_builtinUnlitShader.Reset(); } -BuiltinForwardPipeline::ResolvedShaderPass BuiltinForwardPipeline::ResolveForwardShaderPass( +BuiltinForwardPipeline::ResolvedShaderPass BuiltinForwardPipeline::ResolveSurfaceShaderPass( const Resources::Material* material) const { ResolvedShaderPass resolved = {}; + BuiltinMaterialPass pass = BuiltinMaterialPass::ForwardLit; + if (!TryResolveSurfacePassType(material, pass)) { + return resolved; + } + const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(m_backendType); if (material != nullptr && material->GetShader() != nullptr) { const Resources::Shader* materialShader = material->GetShader(); if (const Resources::ShaderPass* shaderPass = - FindForwardCompatiblePass(*materialShader, material, backend)) { + FindCompatibleSurfacePass(*materialShader, material, pass, backend)) { resolved.shader = materialShader; resolved.pass = shaderPass; resolved.passName = shaderPass->name; @@ -493,10 +530,12 @@ BuiltinForwardPipeline::ResolvedShaderPass BuiltinForwardPipeline::ResolveForwar } } - if (m_builtinForwardShader.IsValid()) { - const Resources::Shader* builtinShader = m_builtinForwardShader.Get(); + const Resources::ResourceHandle* builtinShaderHandle = + pass == BuiltinMaterialPass::Unlit ? &m_builtinUnlitShader : &m_builtinForwardShader; + if (builtinShaderHandle->IsValid()) { + const Resources::Shader* builtinShader = builtinShaderHandle->Get(); if (const Resources::ShaderPass* shaderPass = - FindForwardCompatiblePass(*builtinShader, nullptr, backend)) { + FindCompatibleSurfacePass(*builtinShader, nullptr, pass, backend)) { resolved.shader = builtinShader; resolved.pass = shaderPass; resolved.passName = shaderPass->name; @@ -672,11 +711,11 @@ BuiltinForwardPipeline::PassResourceLayout* BuiltinForwardPipeline::GetOrCreateP RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState( const RenderContext& context, const Resources::Material* material) { - const ResolvedShaderPass resolvedShaderPass = ResolveForwardShaderPass(material); + const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(material); if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, - "BuiltinForwardPipeline could not resolve a valid ForwardLit shader pass"); + "BuiltinForwardPipeline could not resolve a valid surface shader pass"); return nullptr; } @@ -919,7 +958,7 @@ bool BuiltinForwardPipeline::DrawVisibleItem( }; const Resources::Material* material = ResolveMaterial(visibleItem); - const ResolvedShaderPass resolvedShaderPass = ResolveForwardShaderPass(material); + const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(material); if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { return false; } diff --git a/engine/src/Resources/BuiltinResources.cpp b/engine/src/Resources/BuiltinResources.cpp index ef8a48c6..7af645e6 100644 --- a/engine/src/Resources/BuiltinResources.cpp +++ b/engine/src/Resources/BuiltinResources.cpp @@ -28,6 +28,7 @@ constexpr const char* kBuiltinShaderPrefix = "builtin://shaders/"; constexpr const char* kBuiltinTexturePrefix = "builtin://textures/"; constexpr const char* kBuiltinDefaultPrimitiveMaterialPath = "builtin://materials/default-primitive"; constexpr const char* kBuiltinForwardLitShaderPath = "builtin://shaders/forward-lit"; +constexpr const char* kBuiltinUnlitShaderPath = "builtin://shaders/unlit"; constexpr const char* kBuiltinObjectIdShaderPath = "builtin://shaders/object-id"; constexpr const char* kBuiltinDefaultPrimitiveTexturePath = "builtin://textures/default-primitive-albedo"; constexpr float kPi = 3.14159265358979323846f; @@ -41,6 +42,8 @@ size_t CalculateBuiltinShaderMemorySize(const Shader& shader); constexpr const char* kBuiltinForwardLitShaderManifestRelativePath = "engine/assets/builtin/shaders/forward-lit/forward-lit.shader"; +constexpr const char* kBuiltinUnlitShaderManifestRelativePath = + "engine/assets/builtin/shaders/unlit/unlit.shader"; constexpr const char* kBuiltinObjectIdShaderManifestRelativePath = "engine/assets/builtin/shaders/object-id/object-id.shader"; @@ -109,6 +112,9 @@ const char* GetBuiltinShaderManifestRelativePath(const Containers::String& built if (builtinShaderPath == Containers::String(kBuiltinForwardLitShaderPath)) { return kBuiltinForwardLitShaderManifestRelativePath; } + if (builtinShaderPath == Containers::String(kBuiltinUnlitShaderPath)) { + return kBuiltinUnlitShaderManifestRelativePath; + } if (builtinShaderPath == Containers::String(kBuiltinObjectIdShaderPath)) { return kBuiltinObjectIdShaderManifestRelativePath; } @@ -636,6 +642,10 @@ Shader* BuildBuiltinForwardLitShader(const Containers::String& path) { return TryLoadBuiltinShaderFromManifest(path); } +Shader* BuildBuiltinUnlitShader(const Containers::String& path) { + return TryLoadBuiltinShaderFromManifest(path); +} + Shader* BuildBuiltinObjectIdShader(const Containers::String& path) { return TryLoadBuiltinShaderFromManifest(path); } @@ -742,6 +752,10 @@ Containers::String GetBuiltinForwardLitShaderPath() { return Containers::String(kBuiltinForwardLitShaderPath); } +Containers::String GetBuiltinUnlitShaderPath() { + return Containers::String(kBuiltinUnlitShaderPath); +} + Containers::String GetBuiltinObjectIdShaderPath() { return Containers::String(kBuiltinObjectIdShaderPath); } @@ -836,6 +850,8 @@ LoadResult CreateBuiltinShaderResource(const Containers::String& path) { Shader* shader = nullptr; if (path == GetBuiltinForwardLitShaderPath()) { shader = BuildBuiltinForwardLitShader(path); + } else if (path == GetBuiltinUnlitShaderPath()) { + shader = BuildBuiltinUnlitShader(path); } else if (path == GetBuiltinObjectIdShaderPath()) { shader = BuildBuiltinObjectIdShader(path); } else { diff --git a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp index 9e098c60..e2630fc0 100644 --- a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp +++ b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp @@ -78,6 +78,42 @@ TEST(BuiltinForwardPipeline_Test, BuiltinForwardShaderDeclaresExplicitForwardRes delete shader; } +TEST(BuiltinForwardPipeline_Test, BuiltinUnlitShaderDeclaresExplicitSurfaceResourceContract) { + 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); + 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, 1u); + 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, 2u); + 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, 3u); + 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, 4u); + EXPECT_EQ(pass->resources[3].binding, 0u); + + delete shader; +} + TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromExplicitForwardResources) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinForwardLitShaderPath()); @@ -106,6 +142,34 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromExplic delete shader; } +TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromExplicitUnlitResources) { + 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); + + BuiltinPassResourceBindingPlan plan = {}; + String error; + EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass->resources, plan, &error)) << error.CStr(); + EXPECT_TRUE(plan.perObject.IsValid()); + EXPECT_TRUE(plan.material.IsValid()); + EXPECT_TRUE(plan.baseColorTexture.IsValid()); + EXPECT_TRUE(plan.linearClampSampler.IsValid()); + EXPECT_EQ(plan.firstDescriptorSet, 1u); + EXPECT_EQ(plan.descriptorSetCount, 4u); + EXPECT_TRUE(plan.usesConstantBuffers); + EXPECT_TRUE(plan.usesTextures); + EXPECT_TRUE(plan.usesSamplers); + + delete shader; +} + TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLegacyFallbackResources) { const Array bindings = BuildLegacyBuiltinForwardPassResourceBindings(); ASSERT_EQ(bindings.Size(), 4u); diff --git a/tests/Resources/Shader/test_shader_loader.cpp b/tests/Resources/Shader/test_shader_loader.cpp index c3cf9de1..dfe23c38 100644 --- a/tests/Resources/Shader/test_shader_loader.cpp +++ b/tests/Resources/Shader/test_shader_loader.cpp @@ -43,6 +43,7 @@ TEST(ShaderLoader, CanLoad) { EXPECT_TRUE(loader.CanLoad("test.hlsl")); EXPECT_TRUE(loader.CanLoad("test.xcshader")); EXPECT_TRUE(loader.CanLoad(GetBuiltinForwardLitShaderPath())); + EXPECT_TRUE(loader.CanLoad(GetBuiltinUnlitShaderPath())); EXPECT_TRUE(loader.CanLoad(GetBuiltinObjectIdShaderPath())); EXPECT_FALSE(loader.CanLoad("test.txt")); EXPECT_FALSE(loader.CanLoad("test.png")); @@ -495,6 +496,74 @@ TEST(ShaderLoader, LoadBuiltinForwardLitShaderBuildsBackendVariants) { delete shader; } +TEST(ShaderLoader, LoadBuiltinUnlitShaderBuildsBackendVariants) { + 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); + ASSERT_TRUE(shader->IsValid()); + + 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->tags.Size(), 1u); + ASSERT_EQ(pass->resources.Size(), 4u); + EXPECT_EQ(pass->tags[0].name, "LightMode"); + EXPECT_EQ(pass->tags[0].value, "Unlit"); + + const ShaderPropertyDesc* baseColorProperty = shader->FindProperty("_BaseColor"); + ASSERT_NE(baseColorProperty, nullptr); + EXPECT_EQ(baseColorProperty->type, ShaderPropertyType::Color); + EXPECT_EQ(baseColorProperty->semantic, "BaseColor"); + + const ShaderPropertyDesc* baseMapProperty = shader->FindProperty("_MainTex"); + ASSERT_NE(baseMapProperty, nullptr); + 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, 1u); + EXPECT_EQ(perObjectBinding->binding, 0u); + EXPECT_EQ(perObjectBinding->semantic, "PerObject"); + + EXPECT_NE(shader->FindVariant("Unlit", ShaderType::Vertex, ShaderBackend::D3D12), nullptr); + EXPECT_NE(shader->FindVariant("Unlit", ShaderType::Fragment, ShaderBackend::D3D12), nullptr); + EXPECT_NE(shader->FindVariant("Unlit", ShaderType::Vertex, ShaderBackend::OpenGL), nullptr); + EXPECT_NE(shader->FindVariant("Unlit", ShaderType::Fragment, ShaderBackend::OpenGL), nullptr); + EXPECT_NE(shader->FindVariant("Unlit", ShaderType::Vertex, ShaderBackend::Vulkan), nullptr); + EXPECT_NE(shader->FindVariant("Unlit", ShaderType::Fragment, ShaderBackend::Vulkan), nullptr); + + const ShaderStageVariant* d3d12Vertex = shader->FindVariant( + "Unlit", + ShaderType::Vertex, + ShaderBackend::D3D12); + ASSERT_NE(d3d12Vertex, nullptr); + EXPECT_NE(std::string(d3d12Vertex->sourceCode.CStr()).find("XC_BUILTIN_UNLIT_D3D12_VS"), 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); + + 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); + + delete shader; +} + TEST(ShaderLoader, LoadBuiltinObjectIdShaderBuildsBackendVariants) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinObjectIdShaderPath()); @@ -557,6 +626,10 @@ TEST(ShaderLoader, ResourceManagerLazilyLoadsBuiltinForwardLitShader) { ASSERT_TRUE(shaderHandle.IsValid()); ASSERT_NE(shaderHandle->FindPass("ForwardLit"), nullptr); + ResourceHandle unlitShaderHandle = manager.Load(GetBuiltinUnlitShaderPath()); + ASSERT_TRUE(unlitShaderHandle.IsValid()); + ASSERT_NE(unlitShaderHandle->FindPass("Unlit"), nullptr); + ResourceHandle objectIdShaderHandle = manager.Load(GetBuiltinObjectIdShaderPath()); ASSERT_TRUE(objectIdShaderHandle.IsValid()); ASSERT_NE(objectIdShaderHandle->FindPass("ObjectId"), nullptr); @@ -604,6 +677,12 @@ TEST(ShaderLoader, ResourceManagerLoadsBuiltinShadersOutsideProjectWorkingDirect ShaderType::Vertex, ShaderBackend::D3D12, "XC_BUILTIN_FORWARD_LIT_D3D12_VS"); + expectBuiltinShader( + GetBuiltinUnlitShaderPath(), + "Unlit", + ShaderType::Fragment, + ShaderBackend::OpenGL, + "XC_BUILTIN_UNLIT_OPENGL_PS"); expectBuiltinShader( GetBuiltinObjectIdShaderPath(), "ObjectId",