#include #include "Rendering/Detail/ShaderVariantUtils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace XCEngine::Rendering::Pipelines; using namespace XCEngine::Rendering::Passes; using namespace XCEngine::Rendering; using namespace XCEngine::Containers; using namespace XCEngine::Resources; using namespace XCEngine::RHI; namespace { std::string EscapeRegexLiteralForTest(const std::string& value) { std::string escaped; escaped.reserve(value.size() * 2u); for (const char ch : value) { switch (ch) { case '\\': case '^': case '$': case '.': case '|': case '?': case '*': case '+': case '(': case ')': case '[': case ']': case '{': case '}': escaped.push_back('\\'); break; default: break; } escaped.push_back(ch); } return escaped; } ::testing::AssertionResult SourceContainsRegisterBinding( const std::string& source, const std::string& declaration, const std::string& registerClause) { const std::regex pattern( EscapeRegexLiteralForTest(declaration) + "\\s*:\\s*" + EscapeRegexLiteralForTest(registerClause), std::regex::ECMAScript); if (std::regex_search(source, pattern)) { return ::testing::AssertionSuccess(); } return ::testing::AssertionFailure() << "Missing binding '" << declaration << " : " << registerClause << "' in source:\n" << source; } } // namespace TEST(BuiltinForwardPipeline_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) { const InputLayoutDesc inputLayout = BuiltinForwardPipeline::BuildInputLayout(); ASSERT_EQ(inputLayout.elements.size(), 3u); const InputElementDesc& position = inputLayout.elements[0]; EXPECT_EQ(position.semanticName, "POSITION"); EXPECT_EQ(position.semanticIndex, 0u); EXPECT_EQ(position.format, static_cast(Format::R32G32B32_Float)); EXPECT_EQ(position.inputSlot, 0u); EXPECT_EQ(position.alignedByteOffset, 0u); const InputElementDesc& normal = inputLayout.elements[1]; EXPECT_EQ(normal.semanticName, "NORMAL"); EXPECT_EQ(normal.semanticIndex, 0u); EXPECT_EQ(normal.format, static_cast(Format::R32G32B32_Float)); EXPECT_EQ(normal.inputSlot, 0u); EXPECT_EQ(normal.alignedByteOffset, static_cast(offsetof(StaticMeshVertex, normal))); const InputElementDesc& texcoord = inputLayout.elements[2]; EXPECT_EQ(texcoord.semanticName, "TEXCOORD"); EXPECT_EQ(texcoord.semanticIndex, 0u); EXPECT_EQ(texcoord.format, static_cast(Format::R32G32_Float)); EXPECT_EQ(texcoord.inputSlot, 0u); EXPECT_EQ(texcoord.alignedByteOffset, static_cast(offsetof(StaticMeshVertex, uv0))); } TEST(BuiltinForwardPipeline_Test, SplitsSceneItemsIntoOpaqueAndTransparentQueueRanges) { EXPECT_FALSE(IsTransparentRenderQueue(static_cast(MaterialRenderQueue::Background))); EXPECT_FALSE(IsTransparentRenderQueue(static_cast(MaterialRenderQueue::Geometry))); EXPECT_FALSE(IsTransparentRenderQueue(static_cast(MaterialRenderQueue::AlphaTest))); EXPECT_TRUE(IsTransparentRenderQueue(static_cast(MaterialRenderQueue::Transparent))); EXPECT_TRUE(IsTransparentRenderQueue(static_cast(MaterialRenderQueue::Overlay))); } TEST(BuiltinForwardPipeline_Test, BuiltinForwardShaderUsesAuthoringSurfaceContract) { 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 ShaderPropertyDesc* cutoff = shader->FindProperty("_Cutoff"); ASSERT_NE(cutoff, nullptr); EXPECT_EQ(cutoff->type, ShaderPropertyType::Range); EXPECT_EQ(cutoff->semantic, "AlphaCutoff"); const ShaderPass* pass = shader->FindPass("ForwardLit"); ASSERT_NE(pass, nullptr); 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); const ShaderStageVariant* fragmentVariant = shader->FindVariant( "ForwardLit", XCEngine::Resources::ShaderType::Fragment, XCEngine::Resources::ShaderBackend::D3D12); ASSERT_NE(fragmentVariant, nullptr); EXPECT_EQ( std::string(fragmentVariant->sourceCode.CStr()).find("register("), std::string::npos); delete shader; } TEST(BuiltinForwardPipeline_Test, BuiltinUnlitShaderUsesAuthoringSurfaceContract) { 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); 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); const ShaderStageVariant* fragmentVariant = shader->FindVariant( "Unlit", XCEngine::Resources::ShaderType::Fragment, XCEngine::Resources::ShaderBackend::D3D12); ASSERT_NE(fragmentVariant, nullptr); EXPECT_EQ( std::string(fragmentVariant->sourceCode.CStr()).find("register("), std::string::npos); delete shader; } TEST(BuiltinForwardPipeline_Test, BuiltinUnlitShaderBuildsVulkanRuntimeSourceWithResolvedBindings) { 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* 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_TRUE(SourceContainsRegisterBinding( runtimeSource, "cbuffer MaterialConstants", "register(b0, space1)")); 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, VulkanRuntimeCompileDescRewritesAuthoringUnlitBindingsToDescriptorSpaces) { 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_TRUE(SourceContainsRegisterBinding( d3d12Source, "cbuffer MaterialConstants", "register(b1)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "Texture2D BaseColorTexture", "register(t0)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "SamplerState LinearClampSampler", "register(s0)")); 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_TRUE(SourceContainsRegisterBinding( vulkanSource, "cbuffer MaterialConstants", "register(b0, space1)")); 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, VulkanRuntimeCompileDescRewritesAuthoringForwardBindingsToDescriptorSpaces) { 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_TRUE(SourceContainsRegisterBinding( d3d12Source, "cbuffer PerObjectConstants", "register(b0)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "cbuffer LightingConstants", "register(b1)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "cbuffer MaterialConstants", "register(b2)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "cbuffer ShadowReceiverConstants", "register(b3)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "Texture2D BaseColorTexture", "register(t0)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "SamplerState LinearClampSampler", "register(s0)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "Texture2D ShadowMapTexture", "register(t1)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "SamplerState ShadowMapSampler", "register(s1)")); EXPECT_EQ(d3d12Source.find("space0"), std::string::npos); EXPECT_EQ(d3d12Source.find("space1"), 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_TRUE(SourceContainsRegisterBinding( vulkanSource, "cbuffer PerObjectConstants", "register(b0, space0)")); EXPECT_TRUE(SourceContainsRegisterBinding( vulkanSource, "cbuffer LightingConstants", "register(b0, space1)")); EXPECT_TRUE(SourceContainsRegisterBinding( vulkanSource, "cbuffer MaterialConstants", "register(b0, space2)")); EXPECT_TRUE(SourceContainsRegisterBinding( vulkanSource, "cbuffer ShadowReceiverConstants", "register(b0, space3)")); EXPECT_TRUE(SourceContainsRegisterBinding( vulkanSource, "Texture2D BaseColorTexture", "register(t0, space4)")); EXPECT_TRUE(SourceContainsRegisterBinding( vulkanSource, "SamplerState LinearClampSampler", "register(s0, space5)")); EXPECT_TRUE(SourceContainsRegisterBinding( vulkanSource, "Texture2D ShadowMapTexture", "register(t0, space6)")); EXPECT_TRUE(SourceContainsRegisterBinding( vulkanSource, "SamplerState ShadowMapSampler", "register(s0, space7)")); 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; } TEST(BuiltinForwardPipeline_Test, BuiltinSkyboxShaderUsesAuthoringContract) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinSkyboxShaderPath()); ASSERT_TRUE(result); ASSERT_NE(result.resource, nullptr); Shader* shader = static_cast(result.resource); ASSERT_NE(shader, nullptr); const ShaderPass* pass = shader->FindPass("Skybox"); ASSERT_NE(pass, nullptr); EXPECT_TRUE(pass->resources.Empty()); EXPECT_TRUE(pass->hasFixedFunctionState); EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::None); EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable); EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual); const ShaderPropertyDesc* tint = shader->FindProperty("_Tint"); ASSERT_NE(tint, nullptr); EXPECT_EQ(tint->type, ShaderPropertyType::Color); EXPECT_EQ(tint->semantic, "Tint"); const ShaderPropertyDesc* exposure = shader->FindProperty("_Exposure"); ASSERT_NE(exposure, nullptr); EXPECT_EQ(exposure->type, ShaderPropertyType::Float); EXPECT_EQ(exposure->semantic, "Exposure"); const ShaderPropertyDesc* rotation = shader->FindProperty("_Rotation"); ASSERT_NE(rotation, nullptr); EXPECT_EQ(rotation->type, ShaderPropertyType::Float); EXPECT_EQ(rotation->semantic, "Rotation"); const ShaderPropertyDesc* panoramic = shader->FindProperty("_MainTex"); ASSERT_NE(panoramic, nullptr); EXPECT_EQ(panoramic->type, ShaderPropertyType::Texture2D); EXPECT_EQ(panoramic->semantic, "SkyboxPanoramicTexture"); const ShaderPropertyDesc* cubemap = shader->FindProperty("_Tex"); ASSERT_NE(cubemap, nullptr); EXPECT_EQ(cubemap->type, ShaderPropertyType::TextureCube); EXPECT_EQ(cubemap->semantic, "SkyboxTexture"); delete shader; } TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromImplicitSkyboxShaderContract) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinSkyboxShaderPath()); ASSERT_TRUE(result); ASSERT_NE(result.resource, nullptr); Shader* shader = static_cast(result.resource); ASSERT_NE(shader, nullptr); const ShaderPass* pass = shader->FindPass("Skybox"); ASSERT_NE(pass, nullptr); BuiltinPassResourceBindingPlan plan = {}; String error; EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr(); ASSERT_EQ(plan.bindings.Size(), 5u); EXPECT_TRUE(plan.environment.IsValid()); EXPECT_TRUE(plan.material.IsValid()); EXPECT_TRUE(plan.skyboxPanoramicTexture.IsValid()); EXPECT_TRUE(plan.skyboxTexture.IsValid()); EXPECT_TRUE(plan.linearClampSampler.IsValid()); EXPECT_EQ(plan.firstDescriptorSet, 0u); EXPECT_EQ(plan.descriptorSetCount, 5u); delete shader; } TEST(BuiltinForwardPipeline_Test, BuiltinFinalColorShaderUsesAuthoringContract) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinFinalColorShaderPath()); ASSERT_TRUE(result); ASSERT_NE(result.resource, nullptr); Shader* shader = static_cast(result.resource); ASSERT_NE(shader, nullptr); const ShaderPass* pass = shader->FindPass("FinalColor"); ASSERT_NE(pass, nullptr); EXPECT_TRUE(pass->resources.Empty()); EXPECT_TRUE(pass->hasFixedFunctionState); EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::None); EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable); EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::Always); const ShaderPropertyDesc* colorScale = shader->FindProperty("_ColorScale"); ASSERT_NE(colorScale, nullptr); EXPECT_EQ(colorScale->type, ShaderPropertyType::Color); const ShaderPropertyDesc* exposure = shader->FindProperty("_Exposure"); ASSERT_NE(exposure, nullptr); EXPECT_EQ(exposure->type, ShaderPropertyType::Float); const ShaderPropertyDesc* outputTransferMode = shader->FindProperty("_OutputTransferMode"); ASSERT_NE(outputTransferMode, nullptr); EXPECT_EQ(outputTransferMode->type, ShaderPropertyType::Float); const ShaderPropertyDesc* toneMappingMode = shader->FindProperty("_ToneMappingMode"); ASSERT_NE(toneMappingMode, nullptr); EXPECT_EQ(toneMappingMode->type, ShaderPropertyType::Float); delete shader; } TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromImplicitFinalColorShaderContract) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinFinalColorShaderPath()); ASSERT_TRUE(result); ASSERT_NE(result.resource, nullptr); Shader* shader = static_cast(result.resource); ASSERT_NE(shader, nullptr); const ShaderPass* pass = shader->FindPass("FinalColor"); ASSERT_NE(pass, nullptr); BuiltinPassResourceBindingPlan plan = {}; String error; EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr(); ASSERT_EQ(plan.bindings.Size(), 3u); EXPECT_TRUE(plan.passConstants.IsValid()); EXPECT_TRUE(plan.sourceColorTexture.IsValid()); EXPECT_TRUE(plan.linearClampSampler.IsValid()); EXPECT_EQ(plan.firstDescriptorSet, 0u); EXPECT_EQ(plan.descriptorSetCount, 3u); delete shader; } TEST(BuiltinForwardPipeline_Test, BuiltinColorScalePostProcessShaderUsesAuthoringContract) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinColorScalePostProcessShaderPath()); ASSERT_TRUE(result); ASSERT_NE(result.resource, nullptr); Shader* shader = static_cast(result.resource); ASSERT_NE(shader, nullptr); const ShaderPass* pass = shader->FindPass("ColorScale"); ASSERT_NE(pass, nullptr); EXPECT_TRUE(pass->resources.Empty()); EXPECT_TRUE(pass->hasFixedFunctionState); EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::None); EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable); EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::Always); const ShaderPropertyDesc* colorScale = shader->FindProperty("_ColorScale"); ASSERT_NE(colorScale, nullptr); EXPECT_EQ(colorScale->type, ShaderPropertyType::Color); delete shader; } TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromImplicitColorScalePostProcessShaderContract) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinColorScalePostProcessShaderPath()); ASSERT_TRUE(result); ASSERT_NE(result.resource, nullptr); Shader* shader = static_cast(result.resource); ASSERT_NE(shader, nullptr); const ShaderPass* pass = shader->FindPass("ColorScale"); ASSERT_NE(pass, nullptr); BuiltinPassResourceBindingPlan plan = {}; String error; EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr(); ASSERT_EQ(plan.bindings.Size(), 3u); EXPECT_TRUE(plan.passConstants.IsValid()); EXPECT_TRUE(plan.sourceColorTexture.IsValid()); EXPECT_TRUE(plan.linearClampSampler.IsValid()); EXPECT_EQ(plan.firstDescriptorSet, 0u); EXPECT_EQ(plan.descriptorSetCount, 3u); delete shader; } TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringFinalColorBindingsToDescriptorSpaces) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinFinalColorShaderPath()); ASSERT_TRUE(result); ASSERT_NE(result.resource, nullptr); Shader* shader = static_cast(result.resource); ASSERT_NE(shader, nullptr); const ShaderPass* pass = shader->FindPass("FinalColor"); ASSERT_NE(pass, nullptr); const ShaderStageVariant* d3d12Fragment = shader->FindVariant( "FinalColor", 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_TRUE(SourceContainsRegisterBinding( d3d12Source, "cbuffer FinalColorConstants", "register(b0)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "Texture2D gSourceColorTexture", "register(t0)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "SamplerState gLinearClampSampler", "register(s0)")); EXPECT_EQ(d3d12Source.find("space0"), std::string::npos); EXPECT_EQ(d3d12Source.find("space1"), std::string::npos); const ShaderStageVariant* vulkanFragment = shader->FindVariant( "FinalColor", XCEngine::Resources::ShaderType::Fragment, XCEngine::Resources::ShaderBackend::Vulkan); ASSERT_NE(vulkanFragment, nullptr); const std::string runtimeSource = ::XCEngine::Rendering::Detail::BuildRuntimeShaderSource( *pass, XCEngine::Resources::ShaderBackend::Vulkan, *vulkanFragment); EXPECT_TRUE(SourceContainsRegisterBinding( runtimeSource, "cbuffer FinalColorConstants", "register(b0, space0)")); EXPECT_TRUE(SourceContainsRegisterBinding( runtimeSource, "Texture2D gSourceColorTexture", "register(t0, space1)")); EXPECT_TRUE(SourceContainsRegisterBinding( runtimeSource, "SamplerState gLinearClampSampler", "register(s0, space2)")); 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("ApplyLinearToSRGB"), std::string::npos); EXPECT_NE(vulkanSource.find("ApplyToneMapping"), std::string::npos); delete shader; } TEST(BuiltinForwardPipeline_Test, OpenGLRuntimeTranspilesFinalColorVariantToCombinedSourceSampler) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinFinalColorShaderPath()); ASSERT_TRUE(result); ASSERT_NE(result.resource, nullptr); Shader* shader = static_cast(result.resource); ASSERT_NE(shader, nullptr); const ShaderPass* pass = shader->FindPass("FinalColor"); ASSERT_NE(pass, nullptr); const ShaderStageVariant* openGLFragment = shader->FindVariant( "FinalColor", XCEngine::Resources::ShaderType::Fragment, XCEngine::Resources::ShaderBackend::OpenGL); ASSERT_NE(openGLFragment, nullptr); ShaderCompileDesc compileDesc = {}; ::XCEngine::Rendering::Detail::ApplyShaderStageVariant( *pass, XCEngine::Resources::ShaderBackend::OpenGL, *openGLFragment, compileDesc); 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_Combined"), std::string::npos); EXPECT_NE(glslSource.find("texture(SPIRV_Cross_Combined"), std::string::npos); EXPECT_NE(glslSource.find("FinalColorConstants"), std::string::npos); delete shader; } TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringColorScaleBindingsToDescriptorSpaces) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinColorScalePostProcessShaderPath()); ASSERT_TRUE(result); ASSERT_NE(result.resource, nullptr); Shader* shader = static_cast(result.resource); ASSERT_NE(shader, nullptr); const ShaderPass* pass = shader->FindPass("ColorScale"); ASSERT_NE(pass, nullptr); const ShaderStageVariant* d3d12Fragment = shader->FindVariant( "ColorScale", 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_TRUE(SourceContainsRegisterBinding( d3d12Source, "cbuffer PostProcessConstants", "register(b0)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "Texture2D gSourceColorTexture", "register(t0)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "SamplerState gLinearClampSampler", "register(s0)")); EXPECT_EQ(d3d12Source.find("space0"), std::string::npos); EXPECT_EQ(d3d12Source.find("space1"), std::string::npos); const ShaderStageVariant* vulkanFragment = shader->FindVariant( "ColorScale", XCEngine::Resources::ShaderType::Fragment, XCEngine::Resources::ShaderBackend::Vulkan); ASSERT_NE(vulkanFragment, nullptr); const std::string runtimeSource = ::XCEngine::Rendering::Detail::BuildRuntimeShaderSource( *pass, XCEngine::Resources::ShaderBackend::Vulkan, *vulkanFragment); EXPECT_TRUE(SourceContainsRegisterBinding( runtimeSource, "cbuffer PostProcessConstants", "register(b0, space0)")); EXPECT_TRUE(SourceContainsRegisterBinding( runtimeSource, "Texture2D gSourceColorTexture", "register(t0, space1)")); EXPECT_TRUE(SourceContainsRegisterBinding( runtimeSource, "SamplerState gLinearClampSampler", "register(s0, space2)")); 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("gSourceColorTexture.Sample"), std::string::npos); EXPECT_NE(vulkanSource.find("gColorScale"), std::string::npos); delete shader; } TEST(BuiltinForwardPipeline_Test, OpenGLRuntimeTranspilesColorScaleVariantToCombinedSourceSampler) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinColorScalePostProcessShaderPath()); ASSERT_TRUE(result); ASSERT_NE(result.resource, nullptr); Shader* shader = static_cast(result.resource); ASSERT_NE(shader, nullptr); const ShaderPass* pass = shader->FindPass("ColorScale"); ASSERT_NE(pass, nullptr); const ShaderStageVariant* openGLFragment = shader->FindVariant( "ColorScale", XCEngine::Resources::ShaderType::Fragment, XCEngine::Resources::ShaderBackend::OpenGL); ASSERT_NE(openGLFragment, nullptr); ShaderCompileDesc compileDesc = {}; ::XCEngine::Rendering::Detail::ApplyShaderStageVariant( *pass, XCEngine::Resources::ShaderBackend::OpenGL, *openGLFragment, compileDesc); 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_Combined"), std::string::npos); EXPECT_NE(glslSource.find("texture(SPIRV_Cross_Combined"), std::string::npos); EXPECT_NE(glslSource.find("PostProcessConstants"), std::string::npos); delete shader; } TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoadedImplicitForwardShader) { 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); BuiltinPassResourceBindingPlan plan = {}; String error; EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr(); EXPECT_TRUE(plan.perObject.IsValid()); EXPECT_TRUE(plan.lighting.IsValid()); EXPECT_TRUE(plan.material.IsValid()); EXPECT_TRUE(plan.shadowReceiver.IsValid()); EXPECT_TRUE(plan.baseColorTexture.IsValid()); EXPECT_TRUE(plan.linearClampSampler.IsValid()); EXPECT_TRUE(plan.shadowMapTexture.IsValid()); EXPECT_TRUE(plan.shadowMapSampler.IsValid()); EXPECT_EQ(plan.firstDescriptorSet, 0u); EXPECT_EQ(plan.descriptorSetCount, 8u); EXPECT_TRUE(plan.usesConstantBuffers); EXPECT_TRUE(plan.usesTextures); EXPECT_TRUE(plan.usesSamplers); delete shader; } TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromBuiltinForwardShaderContract) { ShaderPass pass = {}; pass.name = "ForwardLit"; ShaderPassTagEntry tag = {}; tag.name = "LightMode"; tag.value = "ForwardBase"; pass.tags.PushBack(tag); BuiltinPassResourceBindingPlan plan = {}; String error; EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass, plan, &error)) << error.CStr(); EXPECT_TRUE(plan.perObject.IsValid()); EXPECT_TRUE(plan.lighting.IsValid()); EXPECT_TRUE(plan.material.IsValid()); EXPECT_TRUE(plan.shadowReceiver.IsValid()); EXPECT_TRUE(plan.baseColorTexture.IsValid()); EXPECT_TRUE(plan.linearClampSampler.IsValid()); EXPECT_TRUE(plan.shadowMapTexture.IsValid()); EXPECT_TRUE(plan.shadowMapSampler.IsValid()); EXPECT_EQ(plan.firstDescriptorSet, 0u); EXPECT_EQ(plan.descriptorSetCount, 8u); } TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromImplicitUnlitContract) { 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, plan, &error)) << error.CStr(); EXPECT_TRUE(plan.perObject.IsValid()); EXPECT_TRUE(plan.material.IsValid()); EXPECT_TRUE(plan.baseColorTexture.IsValid()); EXPECT_TRUE(plan.linearClampSampler.IsValid()); EXPECT_EQ(plan.firstDescriptorSet, 0u); EXPECT_EQ(plan.descriptorSetCount, 4u); EXPECT_TRUE(plan.usesConstantBuffers); EXPECT_TRUE(plan.usesTextures); EXPECT_TRUE(plan.usesSamplers); delete shader; } TEST(BuiltinForwardPipeline_Test, UsesNormalizedImplicitSetIndicesForForwardSurfaceResources) { 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); EXPECT_TRUE(pass->resources.Empty()); BuiltinPassResourceBindingPlan plan = {}; String error; 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); EXPECT_EQ(plan.material.set, 2u); EXPECT_EQ(plan.shadowReceiver.set, 3u); EXPECT_EQ(plan.baseColorTexture.set, 4u); EXPECT_EQ(plan.linearClampSampler.set, 5u); EXPECT_EQ(plan.shadowMapTexture.set, 6u); EXPECT_EQ(plan.shadowMapSampler.set, 7u); delete shader; } TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromImplicitForwardResources) { 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); EXPECT_TRUE(pass->resources.Empty()); BuiltinPassResourceBindingPlan plan = {}; String error; ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr(); std::vector setLayouts; ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr(); ASSERT_EQ(setLayouts.size(), 8u); EXPECT_EQ(setLayouts[0].layout.bindingCount, 1u); EXPECT_FALSE(setLayouts[0].shaderVisible); EXPECT_TRUE(setLayouts[0].usesPerObject); EXPECT_FALSE(setLayouts[0].usesMaterial); EXPECT_EQ(setLayouts[1].layout.bindingCount, 1u); EXPECT_TRUE(setLayouts[1].usesLighting); EXPECT_FALSE(setLayouts[1].shaderVisible); EXPECT_EQ(setLayouts[2].layout.bindingCount, 1u); EXPECT_TRUE(setLayouts[2].usesMaterial); EXPECT_FALSE(setLayouts[2].shaderVisible); EXPECT_EQ(setLayouts[3].layout.bindingCount, 1u); EXPECT_TRUE(setLayouts[3].usesShadowReceiver); EXPECT_FALSE(setLayouts[3].shaderVisible); EXPECT_EQ(setLayouts[4].layout.bindingCount, 1u); EXPECT_TRUE(setLayouts[4].usesTexture); EXPECT_TRUE(setLayouts[4].shaderVisible); EXPECT_EQ(setLayouts[4].heapType, DescriptorHeapType::CBV_SRV_UAV); EXPECT_EQ(setLayouts[5].layout.bindingCount, 1u); EXPECT_TRUE(setLayouts[5].usesSampler); EXPECT_TRUE(setLayouts[5].shaderVisible); EXPECT_EQ(setLayouts[5].heapType, DescriptorHeapType::Sampler); EXPECT_EQ(setLayouts[6].layout.bindingCount, 1u); EXPECT_TRUE(setLayouts[6].usesTexture); EXPECT_TRUE(setLayouts[6].shaderVisible); EXPECT_EQ(setLayouts[7].layout.bindingCount, 1u); EXPECT_TRUE(setLayouts[7].usesSampler); EXPECT_TRUE(setLayouts[7].shaderVisible); EXPECT_EQ(setLayouts[7].heapType, DescriptorHeapType::Sampler); delete shader; } TEST(BuiltinDepthStylePass_Test, BuiltinDepthOnlyShaderUsesAuthoringContract) { 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); 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, BuiltinShadowCasterShaderUsesAuthoringContract) { 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); 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(BuiltinObjectIdPass_Test, BuiltinObjectIdShaderUsesAuthoringContract) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinObjectIdShaderPath()); ASSERT_TRUE(result); ASSERT_NE(result.resource, nullptr); Shader* shader = static_cast(result.resource); ASSERT_NE(shader, nullptr); const ShaderPass* pass = shader->FindPass("ObjectId"); ASSERT_NE(pass, nullptr); 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(BuiltinObjectIdPass_Test, BuildsBuiltinPassResourceBindingPlanFromBuiltinObjectIdShaderContract) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinObjectIdShaderPath()); ASSERT_TRUE(result); ASSERT_NE(result.resource, nullptr); Shader* shader = static_cast(result.resource); ASSERT_NE(shader, nullptr); const ShaderPass* pass = shader->FindPass("ObjectId"); ASSERT_NE(pass, nullptr); BuiltinPassResourceBindingPlan plan = {}; String error; EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr(); ASSERT_EQ(plan.bindings.Size(), 1u); EXPECT_TRUE(plan.perObject.IsValid()); EXPECT_FALSE(plan.material.IsValid()); EXPECT_FALSE(plan.baseColorTexture.IsValid()); EXPECT_FALSE(plan.linearClampSampler.IsValid()); EXPECT_EQ(plan.firstDescriptorSet, 0u); EXPECT_EQ(plan.descriptorSetCount, 1u); EXPECT_TRUE(plan.usesConstantBuffers); EXPECT_FALSE(plan.usesTextures); EXPECT_FALSE(plan.usesSamplers); delete shader; } TEST(BuiltinObjectIdPass_Test, BuildsBuiltinPassResourceBindingPlanFromImplicitObjectIdContract) { ShaderPass pass = {}; pass.name = "ObjectId"; ShaderPassTagEntry tag = {}; tag.name = "LightMode"; tag.value = "ObjectId"; pass.tags.PushBack(tag); BuiltinPassResourceBindingPlan plan = {}; String error; EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass, plan, &error)) << error.CStr(); ASSERT_EQ(plan.bindings.Size(), 1u); EXPECT_TRUE(plan.perObject.IsValid()); EXPECT_FALSE(plan.material.IsValid()); EXPECT_EQ(plan.firstDescriptorSet, 0u); EXPECT_EQ(plan.descriptorSetCount, 1u); } TEST(BuiltinDepthStylePass_Test, BuildsBuiltinPassResourceBindingPlanFromImplicitDepthOnlyContract) { ShaderPass pass = {}; pass.name = "DepthOnly"; ShaderPassTagEntry tag = {}; tag.name = "LightMode"; tag.value = "DepthOnly"; pass.tags.PushBack(tag); BuiltinPassResourceBindingPlan plan = {}; String error; EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass, plan, &error)) << error.CStr(); EXPECT_TRUE(plan.perObject.IsValid()); EXPECT_TRUE(plan.material.IsValid()); EXPECT_TRUE(plan.baseColorTexture.IsValid()); EXPECT_TRUE(plan.linearClampSampler.IsValid()); EXPECT_EQ(plan.firstDescriptorSet, 0u); EXPECT_EQ(plan.descriptorSetCount, 4u); } TEST(BuiltinObjectIdPass_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) { const InputLayoutDesc inputLayout = BuiltinObjectIdPass::BuildInputLayout(); ASSERT_EQ(inputLayout.elements.size(), 3u); const InputElementDesc& position = inputLayout.elements[0]; EXPECT_EQ(position.semanticName, "POSITION"); EXPECT_EQ(position.semanticIndex, 0u); EXPECT_EQ(position.format, static_cast(Format::R32G32B32_Float)); EXPECT_EQ(position.inputSlot, 0u); EXPECT_EQ(position.alignedByteOffset, static_cast(offsetof(StaticMeshVertex, position))); const InputElementDesc& normal = inputLayout.elements[1]; EXPECT_EQ(normal.semanticName, "NORMAL"); EXPECT_EQ(normal.semanticIndex, 0u); EXPECT_EQ(normal.format, static_cast(Format::R32G32B32_Float)); EXPECT_EQ(normal.inputSlot, 0u); EXPECT_EQ(normal.alignedByteOffset, static_cast(offsetof(StaticMeshVertex, normal))); const InputElementDesc& texcoord = inputLayout.elements[2]; EXPECT_EQ(texcoord.semanticName, "TEXCOORD"); EXPECT_EQ(texcoord.semanticIndex, 0u); EXPECT_EQ(texcoord.format, static_cast(Format::R32G32_Float)); EXPECT_EQ(texcoord.inputSlot, 0u); EXPECT_EQ(texcoord.alignedByteOffset, static_cast(offsetof(StaticMeshVertex, uv0))); } TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromExplicitObjectIdResources) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinObjectIdShaderPath()); ASSERT_TRUE(result); ASSERT_NE(result.resource, nullptr); Shader* shader = static_cast(result.resource); ASSERT_NE(shader, nullptr); const ShaderPass* pass = shader->FindPass("ObjectId"); ASSERT_NE(pass, nullptr); BuiltinPassResourceBindingPlan plan = {}; String error; ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr(); std::vector setLayouts; ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr(); ASSERT_EQ(setLayouts.size(), 1u); EXPECT_EQ(setLayouts[0].layout.bindingCount, 1u); EXPECT_TRUE(setLayouts[0].usesPerObject); EXPECT_FALSE(setLayouts[0].usesMaterial); EXPECT_FALSE(setLayouts[0].usesTexture); EXPECT_FALSE(setLayouts[0].usesSampler); EXPECT_FALSE(setLayouts[0].shaderVisible); EXPECT_EQ(setLayouts[0].heapType, DescriptorHeapType::CBV_SRV_UAV); delete shader; } TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromBuiltinDepthOnlyShaderContract) { 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); BuiltinPassResourceBindingPlan plan = {}; String error; ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr(); EXPECT_TRUE(plan.perObject.IsValid()); EXPECT_TRUE(plan.material.IsValid()); EXPECT_TRUE(plan.baseColorTexture.IsValid()); EXPECT_TRUE(plan.linearClampSampler.IsValid()); EXPECT_EQ(plan.firstDescriptorSet, 0u); EXPECT_EQ(plan.descriptorSetCount, 4u); EXPECT_TRUE(plan.usesConstantBuffers); EXPECT_TRUE(plan.usesTextures); EXPECT_TRUE(plan.usesSamplers); std::vector setLayouts; ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr(); ASSERT_EQ(setLayouts.size(), 4u); EXPECT_EQ(setLayouts[0].layout.bindingCount, 1u); EXPECT_TRUE(setLayouts[0].usesPerObject); EXPECT_FALSE(setLayouts[0].usesMaterial); EXPECT_FALSE(setLayouts[0].usesTexture); EXPECT_FALSE(setLayouts[0].usesSampler); EXPECT_EQ(setLayouts[1].layout.bindingCount, 1u); EXPECT_FALSE(setLayouts[1].usesPerObject); EXPECT_TRUE(setLayouts[1].usesMaterial); EXPECT_FALSE(setLayouts[1].usesTexture); EXPECT_FALSE(setLayouts[1].usesSampler); EXPECT_EQ(setLayouts[2].layout.bindingCount, 1u); EXPECT_FALSE(setLayouts[2].usesPerObject); EXPECT_FALSE(setLayouts[2].usesMaterial); EXPECT_TRUE(setLayouts[2].usesTexture); EXPECT_FALSE(setLayouts[2].usesSampler); EXPECT_TRUE(setLayouts[2].shaderVisible); EXPECT_EQ(setLayouts[3].layout.bindingCount, 1u); EXPECT_FALSE(setLayouts[3].usesPerObject); EXPECT_FALSE(setLayouts[3].usesMaterial); EXPECT_FALSE(setLayouts[3].usesTexture); EXPECT_TRUE(setLayouts[3].usesSampler); EXPECT_TRUE(setLayouts[3].shaderVisible); EXPECT_EQ(setLayouts[3].heapType, DescriptorHeapType::Sampler); delete shader; } TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromBuiltinShadowCasterShaderContract) { 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); BuiltinPassResourceBindingPlan plan = {}; String error; ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr(); EXPECT_TRUE(plan.perObject.IsValid()); EXPECT_TRUE(plan.material.IsValid()); EXPECT_TRUE(plan.baseColorTexture.IsValid()); EXPECT_TRUE(plan.linearClampSampler.IsValid()); EXPECT_EQ(plan.firstDescriptorSet, 0u); EXPECT_EQ(plan.descriptorSetCount, 4u); EXPECT_TRUE(plan.usesConstantBuffers); EXPECT_TRUE(plan.usesTextures); EXPECT_TRUE(plan.usesSamplers); std::vector setLayouts; ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr(); ASSERT_EQ(setLayouts.size(), 4u); EXPECT_EQ(setLayouts[0].layout.bindingCount, 1u); EXPECT_TRUE(setLayouts[0].usesPerObject); EXPECT_FALSE(setLayouts[0].usesMaterial); EXPECT_FALSE(setLayouts[0].usesTexture); EXPECT_FALSE(setLayouts[0].usesSampler); EXPECT_EQ(setLayouts[1].layout.bindingCount, 1u); EXPECT_FALSE(setLayouts[1].usesPerObject); EXPECT_TRUE(setLayouts[1].usesMaterial); EXPECT_FALSE(setLayouts[1].usesTexture); EXPECT_FALSE(setLayouts[1].usesSampler); EXPECT_EQ(setLayouts[2].layout.bindingCount, 1u); EXPECT_FALSE(setLayouts[2].usesPerObject); EXPECT_FALSE(setLayouts[2].usesMaterial); EXPECT_TRUE(setLayouts[2].usesTexture); EXPECT_FALSE(setLayouts[2].usesSampler); EXPECT_TRUE(setLayouts[2].shaderVisible); EXPECT_EQ(setLayouts[3].layout.bindingCount, 1u); EXPECT_FALSE(setLayouts[3].usesPerObject); EXPECT_FALSE(setLayouts[3].usesMaterial); EXPECT_FALSE(setLayouts[3].usesTexture); EXPECT_TRUE(setLayouts[3].usesSampler); EXPECT_TRUE(setLayouts[3].shaderVisible); EXPECT_EQ(setLayouts[3].heapType, DescriptorHeapType::Sampler); delete shader; } TEST(BuiltinPassLayout_Test, RejectsMixedSamplerAndNonSamplerBindingsInOneSet) { Array bindings; bindings.Resize(2); bindings[0].name = "BaseColorTexture"; bindings[0].type = ShaderResourceType::Texture2D; bindings[0].set = 0; bindings[0].binding = 0; bindings[0].semantic = "BaseColorTexture"; bindings[1].name = "LinearClampSampler"; bindings[1].type = ShaderResourceType::Sampler; bindings[1].set = 0; bindings[1].binding = 1; bindings[1].semantic = "LinearClampSampler"; BuiltinPassResourceBindingPlan plan = {}; String error; ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(bindings, plan, &error)) << error.CStr(); std::vector setLayouts; EXPECT_FALSE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)); EXPECT_EQ(error, "Builtin pass does not support mixing sampler and non-sampler bindings in one set"); } TEST(BuiltinPassLayout_Test, RejectsDuplicateBindingsInOneSet) { BuiltinPassResourceBindingPlan plan = {}; BuiltinPassResourceBindingDesc perObjectBinding = {}; perObjectBinding.semantic = BuiltinPassResourceSemantic::PerObject; perObjectBinding.resourceType = ShaderResourceType::ConstantBuffer; perObjectBinding.location = { 0, 0 }; plan.bindings.PushBack(perObjectBinding); BuiltinPassResourceBindingDesc materialBinding = {}; materialBinding.semantic = BuiltinPassResourceSemantic::Material; materialBinding.resourceType = ShaderResourceType::ConstantBuffer; materialBinding.location = { 0, 0 }; plan.bindings.PushBack(materialBinding); plan.maxSetIndex = 0; plan.firstDescriptorSet = 0; plan.descriptorSetCount = 1; plan.usesConstantBuffers = true; plan.perObject = { 0, 0 }; plan.material = { 0, 0 }; std::vector setLayouts; String error; EXPECT_FALSE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)); EXPECT_EQ(error, "Builtin pass encountered duplicate bindings inside one descriptor set"); } TEST(BuiltinDepthOnlyPass_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) { const InputLayoutDesc inputLayout = BuiltinDepthOnlyPass::BuildInputLayout(); ASSERT_EQ(inputLayout.elements.size(), 3u); EXPECT_EQ(inputLayout.elements[0].semanticName, "POSITION"); EXPECT_EQ(inputLayout.elements[1].semanticName, "NORMAL"); EXPECT_EQ(inputLayout.elements[2].semanticName, "TEXCOORD"); EXPECT_EQ(inputLayout.elements[0].alignedByteOffset, static_cast(offsetof(StaticMeshVertex, position))); EXPECT_EQ(inputLayout.elements[1].alignedByteOffset, static_cast(offsetof(StaticMeshVertex, normal))); EXPECT_EQ(inputLayout.elements[2].alignedByteOffset, static_cast(offsetof(StaticMeshVertex, uv0))); } TEST(BuiltinShadowCasterPass_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) { const InputLayoutDesc inputLayout = BuiltinShadowCasterPass::BuildInputLayout(); ASSERT_EQ(inputLayout.elements.size(), 3u); EXPECT_EQ(inputLayout.elements[0].semanticName, "POSITION"); EXPECT_EQ(inputLayout.elements[1].semanticName, "NORMAL"); EXPECT_EQ(inputLayout.elements[2].semanticName, "TEXCOORD"); EXPECT_EQ(inputLayout.elements[0].alignedByteOffset, static_cast(offsetof(StaticMeshVertex, position))); EXPECT_EQ(inputLayout.elements[1].alignedByteOffset, static_cast(offsetof(StaticMeshVertex, normal))); EXPECT_EQ(inputLayout.elements[2].alignedByteOffset, static_cast(offsetof(StaticMeshVertex, uv0))); }