#include #include #include #include #include #include #include #include #include #include #include using namespace XCEngine::Resources; using namespace XCEngine::Math; namespace { class MockMaterialBuffer final : public XCEngine::RHI::RHIBuffer { public: explicit MockMaterialBuffer( uint64_t size = 256u, uint32_t stride = 16u, XCEngine::RHI::BufferType type = XCEngine::RHI::BufferType::Storage) : m_size(size) , m_stride(stride) , m_type(type) { } void* Map() override { return nullptr; } void Unmap() override {} void SetData(const void*, size_t, size_t) override {} uint64_t GetSize() const override { return m_size; } XCEngine::RHI::BufferType GetBufferType() const override { return m_type; } void SetBufferType(XCEngine::RHI::BufferType type) override { m_type = type; } uint32_t GetStride() const override { return m_stride; } void SetStride(uint32_t stride) override { m_stride = stride; } void* GetNativeHandle() override { return nullptr; } XCEngine::RHI::ResourceStates GetState() const override { return m_state; } void SetState(XCEngine::RHI::ResourceStates state) override { m_state = state; } const std::string& GetName() const override { return m_name; } void SetName(const std::string& name) override { m_name = name; } void Shutdown() override {} private: uint64_t m_size = 0; uint32_t m_stride = 0; XCEngine::RHI::BufferType m_type = XCEngine::RHI::BufferType::Storage; XCEngine::RHI::ResourceStates m_state = XCEngine::RHI::ResourceStates::Common; std::string m_name; }; Shader* CreateMaterialSchemaShader() { auto* shader = new Shader(); ShaderPropertyDesc baseColor = {}; baseColor.name = "_BaseColor"; baseColor.displayName = "Base Color"; baseColor.type = ShaderPropertyType::Color; baseColor.defaultValue = "(1.0,0.5,0.25,1.0)"; baseColor.semantic = "BaseColor"; shader->AddProperty(baseColor); ShaderPropertyDesc metallic = {}; metallic.name = "_Metallic"; metallic.displayName = "Metallic"; metallic.type = ShaderPropertyType::Float; metallic.defaultValue = "0.7"; shader->AddProperty(metallic); ShaderPropertyDesc baseMap = {}; baseMap.name = "_MainTex"; baseMap.displayName = "Base Map"; baseMap.type = ShaderPropertyType::Texture2D; baseMap.defaultValue = "white"; baseMap.semantic = "BaseColorTexture"; shader->AddProperty(baseMap); return shader; } Shader* CreateMaterialKeywordShader(std::initializer_list keywords) { auto* shader = new Shader(); ShaderPass pass = {}; pass.name = "ForwardLit"; shader->AddPass(pass); ShaderKeywordDeclaration declaration = {}; declaration.type = ShaderKeywordDeclarationType::ShaderFeatureLocal; declaration.options.PushBack("_"); for (const char* keyword : keywords) { declaration.options.PushBack(keyword); } shader->AddPassKeywordDeclaration("ForwardLit", declaration); return shader; } TEST(Material, DefaultConstructor) { Material material; EXPECT_EQ(material.GetType(), ResourceType::Material); EXPECT_FALSE(material.IsValid()); EXPECT_EQ(material.GetMemorySize(), 0u); } TEST(Material, GetType) { Material material; EXPECT_EQ(material.GetType(), ResourceType::Material); } TEST(Material, SetGetShader) { Material material; Shader* shader = new Shader(); ResourceHandle handle(shader); material.SetShader(handle); EXPECT_EQ(material.GetShader(), shader); } TEST(Material, DefaultRenderMetadata) { Material material; EXPECT_EQ(material.GetRenderQueue(), static_cast(MaterialRenderQueue::Geometry)); EXPECT_EQ(material.GetRenderState().cullMode, MaterialCullMode::None); EXPECT_FALSE(material.GetRenderState().blendEnable); EXPECT_TRUE(material.GetRenderState().depthTestEnable); EXPECT_TRUE(material.GetRenderState().depthWriteEnable); EXPECT_EQ(material.GetRenderState().depthFunc, MaterialComparisonFunc::Less); EXPECT_EQ(material.GetTagCount(), 0u); } TEST(Material, SetGetRenderQueue) { Material material; material.SetRenderQueue(MaterialRenderQueue::Transparent); EXPECT_EQ(material.GetRenderQueue(), static_cast(MaterialRenderQueue::Transparent)); material.SetRenderQueue(2600); EXPECT_EQ(material.GetRenderQueue(), 2600); } TEST(Material, SetGetRenderState) { Material material; MaterialRenderState renderState; renderState.cullMode = MaterialCullMode::Back; renderState.blendEnable = true; renderState.srcBlend = MaterialBlendFactor::SrcAlpha; renderState.dstBlend = MaterialBlendFactor::InvSrcAlpha; renderState.srcBlendAlpha = MaterialBlendFactor::One; renderState.dstBlendAlpha = MaterialBlendFactor::InvSrcAlpha; renderState.blendOp = MaterialBlendOp::Add; renderState.blendOpAlpha = MaterialBlendOp::Add; renderState.depthTestEnable = true; renderState.depthWriteEnable = false; renderState.depthFunc = MaterialComparisonFunc::LessEqual; renderState.colorWriteMask = 0x7; renderState.depthBiasFactor = 1.5f; renderState.depthBiasUnits = 2; renderState.stencil.enabled = true; renderState.stencil.reference = 3; renderState.stencil.readMask = 0x0F; renderState.stencil.writeMask = 0xF0; renderState.stencil.front.func = MaterialComparisonFunc::Equal; renderState.stencil.front.passOp = MaterialStencilOp::Replace; renderState.stencil.front.failOp = MaterialStencilOp::Keep; renderState.stencil.front.depthFailOp = MaterialStencilOp::IncrSat; renderState.stencil.back.func = MaterialComparisonFunc::NotEqual; renderState.stencil.back.passOp = MaterialStencilOp::DecrWrap; renderState.stencil.back.failOp = MaterialStencilOp::Invert; renderState.stencil.back.depthFailOp = MaterialStencilOp::Zero; material.SetRenderState(renderState); const MaterialRenderState& result = material.GetRenderState(); EXPECT_EQ(result.cullMode, MaterialCullMode::Back); EXPECT_TRUE(result.blendEnable); EXPECT_EQ(result.srcBlend, MaterialBlendFactor::SrcAlpha); EXPECT_EQ(result.dstBlend, MaterialBlendFactor::InvSrcAlpha); EXPECT_EQ(result.srcBlendAlpha, MaterialBlendFactor::One); EXPECT_EQ(result.dstBlendAlpha, MaterialBlendFactor::InvSrcAlpha); EXPECT_EQ(result.blendOp, MaterialBlendOp::Add); EXPECT_EQ(result.blendOpAlpha, MaterialBlendOp::Add); EXPECT_TRUE(result.depthTestEnable); EXPECT_FALSE(result.depthWriteEnable); EXPECT_EQ(result.depthFunc, MaterialComparisonFunc::LessEqual); EXPECT_EQ(result.colorWriteMask, 0x7); EXPECT_FLOAT_EQ(result.depthBiasFactor, 1.5f); EXPECT_EQ(result.depthBiasUnits, 2); EXPECT_TRUE(result.stencil.enabled); EXPECT_EQ(result.stencil.reference, 3u); EXPECT_EQ(result.stencil.readMask, 0x0Fu); EXPECT_EQ(result.stencil.writeMask, 0xF0u); EXPECT_EQ(result.stencil.front.func, MaterialComparisonFunc::Equal); EXPECT_EQ(result.stencil.front.passOp, MaterialStencilOp::Replace); EXPECT_EQ(result.stencil.front.depthFailOp, MaterialStencilOp::IncrSat); EXPECT_EQ(result.stencil.back.func, MaterialComparisonFunc::NotEqual); EXPECT_EQ(result.stencil.back.passOp, MaterialStencilOp::DecrWrap); EXPECT_EQ(result.stencil.back.failOp, MaterialStencilOp::Invert); EXPECT_EQ(result.stencil.back.depthFailOp, MaterialStencilOp::Zero); } TEST(Material, SetGetTags) { Material material; material.SetTag("LightMode", "ForwardBase"); material.SetTag("RenderType", "Opaque"); EXPECT_TRUE(material.HasTag("LightMode")); EXPECT_EQ(material.GetTag("LightMode"), "ForwardBase"); EXPECT_EQ(material.GetTag("RenderType"), "Opaque"); EXPECT_EQ(material.GetTagCount(), 2u); } TEST(Material, SetTagReplacesExistingValue) { Material material; material.SetTag("LightMode", "ForwardBase"); material.SetTag("LightMode", "ShadowCaster"); EXPECT_EQ(material.GetTagCount(), 1u); EXPECT_EQ(material.GetTag("LightMode"), "ShadowCaster"); } TEST(Material, RemoveTag) { Material material; material.SetTag("LightMode", "ForwardBase"); EXPECT_TRUE(material.HasTag("LightMode")); material.RemoveTag("LightMode"); EXPECT_FALSE(material.HasTag("LightMode")); EXPECT_TRUE(material.GetTag("LightMode").Empty()); } TEST(Material, ClearTags) { Material material; material.SetTag("LightMode", "ForwardBase"); material.SetTag("RenderType", "Opaque"); ASSERT_EQ(material.GetTagCount(), 2u); material.ClearTags(); EXPECT_EQ(material.GetTagCount(), 0u); EXPECT_FALSE(material.HasTag("LightMode")); EXPECT_FALSE(material.HasTag("RenderType")); } TEST(Material, SetGetFloat) { Material material; material.SetFloat("uTime", 1.5f); EXPECT_FLOAT_EQ(material.GetFloat("uTime"), 1.5f); } TEST(Material, SetGetFloat2) { Material material; Vector2 value(1.0f, 2.0f); material.SetFloat2("uUV", value); Vector2 result = material.GetFloat2("uUV"); EXPECT_FLOAT_EQ(result.x, 1.0f); EXPECT_FLOAT_EQ(result.y, 2.0f); } TEST(Material, SetGetFloat3) { Material material; Vector3 value(1.0f, 2.0f, 3.0f); material.SetFloat3("uPosition", value); Vector3 result = material.GetFloat3("uPosition"); EXPECT_FLOAT_EQ(result.x, 1.0f); EXPECT_FLOAT_EQ(result.y, 2.0f); EXPECT_FLOAT_EQ(result.z, 3.0f); } TEST(Material, SetGetFloat4) { Material material; Vector4 value(1.0f, 2.0f, 3.0f, 4.0f); material.SetFloat4("uColor", value); Vector4 result = material.GetFloat4("uColor"); EXPECT_FLOAT_EQ(result.x, 1.0f); EXPECT_FLOAT_EQ(result.y, 2.0f); EXPECT_FLOAT_EQ(result.z, 3.0f); EXPECT_FLOAT_EQ(result.w, 4.0f); } TEST(Material, SetGetInt) { Material material; material.SetInt("uIndex", 42); EXPECT_EQ(material.GetInt("uIndex"), 42); } TEST(Material, SetGetBool) { Material material; material.SetBool("uEnabled", true); EXPECT_TRUE(material.GetBool("uEnabled")); material.SetBool("uEnabled", false); EXPECT_FALSE(material.GetBool("uEnabled")); } TEST(Material, SetGetTexture) { Material material; Texture* texture = new Texture(); ResourceHandle handle(texture); material.SetTexture("uDiffuse", handle); EXPECT_EQ(material.GetTexture("uDiffuse").Get(), texture); } TEST(Material, SetTextureReplacesExistingBinding) { Material material; Texture* firstTexture = new Texture(); Texture* secondTexture = new Texture(); material.SetTexture("uDiffuse", ResourceHandle(firstTexture)); material.SetTexture("uDiffuse", ResourceHandle(secondTexture)); EXPECT_EQ(material.GetTextureBindingCount(), 1u); EXPECT_EQ(material.GetTexture("uDiffuse").Get(), secondTexture); } TEST(Material, SetTextureAssetRefStoresStableBindingMetadata) { Material material; AssetRef textureRef; textureRef.assetGuid = AssetGUID(1, 2); textureRef.localID = kMainAssetLocalID; textureRef.resourceType = ResourceType::Texture; material.SetTextureAssetRef("uDiffuse", textureRef, "Assets/diffuse.bmp"); ASSERT_EQ(material.GetTextureBindingCount(), 1u); EXPECT_TRUE(material.HasProperty("uDiffuse")); EXPECT_EQ(material.GetTextureBindingName(0), "uDiffuse"); EXPECT_EQ(material.GetTextureBindingPath(0), "Assets/diffuse.bmp"); const AssetRef storedRef = material.GetTextureBindingAssetRef(0); EXPECT_EQ(storedRef.assetGuid, textureRef.assetGuid); EXPECT_EQ(storedRef.localID, textureRef.localID); EXPECT_EQ(storedRef.resourceType, textureRef.resourceType); EXPECT_FALSE(material.GetTextureBindingLoadedTexture(0).IsValid()); } TEST(Material, ChangeVersionIncrementsWhenMaterialMutates) { Material material; const XCEngine::Core::uint64 initialVersion = material.GetChangeVersion(); material.SetFloat("uTime", 1.0f); const XCEngine::Core::uint64 afterFloatVersion = material.GetChangeVersion(); EXPECT_GT(afterFloatVersion, initialVersion); material.SetTexture("uDiffuse", ResourceHandle(new Texture())); EXPECT_GT(material.GetChangeVersion(), afterFloatVersion); } TEST(Material, SetBufferStoresRuntimeOnlyBindingMetadata) { Material material; MockMaterialBuffer buffer(512u, 32u); MaterialBufferBindingViewDesc viewDesc = {}; viewDesc.firstElement = 4u; viewDesc.elementCount = 8u; viewDesc.structureByteStride = 32u; material.SetBuffer("VolumeNodes", &buffer, viewDesc); ASSERT_EQ(material.GetBufferBindingCount(), 1u); EXPECT_EQ(material.GetBuffer("VolumeNodes"), &buffer); const MaterialBufferBinding* binding = material.FindBufferBinding("VolumeNodes"); ASSERT_NE(binding, nullptr); EXPECT_EQ(binding->name, "VolumeNodes"); EXPECT_EQ(binding->buffer, &buffer); EXPECT_EQ(binding->viewDesc.firstElement, 4u); EXPECT_EQ(binding->viewDesc.elementCount, 8u); EXPECT_EQ(binding->viewDesc.structureByteStride, 32u); EXPECT_FALSE(material.HasProperty("VolumeNodes")); } TEST(Material, SetBufferNullRemovesRuntimeBinding) { Material material; MockMaterialBuffer buffer; material.SetBuffer("VolumeNodes", &buffer); ASSERT_EQ(material.GetBufferBindingCount(), 1u); material.SetBuffer("VolumeNodes", nullptr); EXPECT_EQ(material.GetBufferBindingCount(), 0u); EXPECT_EQ(material.GetBuffer("VolumeNodes"), nullptr); } TEST(Material, ClearAllPropertiesDoesNotRemoveRuntimeBufferBindings) { Material material; MockMaterialBuffer buffer; material.SetFloat("uTime", 1.0f); material.SetBuffer("VolumeNodes", &buffer); ASSERT_TRUE(material.HasProperty("uTime")); ASSERT_EQ(material.GetBufferBindingCount(), 1u); material.ClearAllProperties(); EXPECT_FALSE(material.HasProperty("uTime")); EXPECT_EQ(material.GetBufferBindingCount(), 1u); EXPECT_EQ(material.GetBuffer("VolumeNodes"), &buffer); } TEST(Material, UpdateConstantBufferPacksNumericPropertiesIntoStableSlots) { Material material; material.SetFloat("alpha", 3.5f); material.SetFloat4("beta", Vector4(1.0f, 2.0f, 3.0f, 4.0f)); material.SetInt("gamma", 7); const auto& constantLayout = material.GetConstantLayout(); ASSERT_EQ(constantLayout.Size(), 3u); EXPECT_EQ(constantLayout[0].name, "alpha"); EXPECT_EQ(constantLayout[0].offset, 0u); EXPECT_EQ(constantLayout[0].size, 4u); EXPECT_EQ(constantLayout[0].alignedSize, 16u); EXPECT_EQ(constantLayout[1].name, "beta"); EXPECT_EQ(constantLayout[1].offset, 16u); EXPECT_EQ(constantLayout[1].size, 16u); EXPECT_EQ(constantLayout[2].name, "gamma"); EXPECT_EQ(constantLayout[2].offset, 32u); EXPECT_EQ(constantLayout[2].size, 4u); const MaterialConstantFieldDesc* betaField = material.FindConstantField("beta"); ASSERT_NE(betaField, nullptr); EXPECT_EQ(betaField->offset, 16u); EXPECT_EQ(betaField->alignedSize, 16u); const auto& constantBufferData = material.GetConstantBufferData(); ASSERT_EQ(constantBufferData.Size(), 48u); const float* alphaSlot = reinterpret_cast(constantBufferData.Data()); EXPECT_FLOAT_EQ(alphaSlot[0], 3.5f); EXPECT_FLOAT_EQ(alphaSlot[1], 0.0f); EXPECT_FLOAT_EQ(alphaSlot[2], 0.0f); EXPECT_FLOAT_EQ(alphaSlot[3], 0.0f); const float* betaSlot = reinterpret_cast(constantBufferData.Data() + 16); EXPECT_FLOAT_EQ(betaSlot[0], 1.0f); EXPECT_FLOAT_EQ(betaSlot[1], 2.0f); EXPECT_FLOAT_EQ(betaSlot[2], 3.0f); EXPECT_FLOAT_EQ(betaSlot[3], 4.0f); const XCEngine::Core::int32* gammaSlot = reinterpret_cast(constantBufferData.Data() + 32); EXPECT_EQ(gammaSlot[0], 7); EXPECT_EQ(gammaSlot[1], 0); EXPECT_EQ(gammaSlot[2], 0); EXPECT_EQ(gammaSlot[3], 0); } TEST(Material, RemoveTexturePropertyAlsoRemovesTextureBinding) { Material material; Texture* texture = new Texture(); material.SetTexture("uDiffuse", ResourceHandle(texture)); ASSERT_EQ(material.GetTextureBindingCount(), 1u); material.RemoveProperty("uDiffuse"); EXPECT_FALSE(material.HasProperty("uDiffuse")); EXPECT_EQ(material.GetTextureBindingCount(), 0u); EXPECT_EQ(material.GetTexture("uDiffuse").Get(), nullptr); } TEST(Material, ReplacingTexturePropertyWithScalarRemovesTextureBinding) { Material material; Texture* texture = new Texture(); material.SetTexture("uMain", ResourceHandle(texture)); ASSERT_EQ(material.GetTextureBindingCount(), 1u); material.SetFloat("uMain", 2.0f); EXPECT_EQ(material.GetTextureBindingCount(), 0u); EXPECT_EQ(material.GetTexture("uMain").Get(), nullptr); EXPECT_FLOAT_EQ(material.GetFloat("uMain"), 2.0f); } TEST(Material, HasProperty) { Material material; EXPECT_FALSE(material.HasProperty("uTime")); material.SetFloat("uTime", 1.0f); EXPECT_TRUE(material.HasProperty("uTime")); } TEST(Material, RemoveProperty) { Material material; material.SetFloat("uTime", 1.0f); EXPECT_TRUE(material.HasProperty("uTime")); material.RemoveProperty("uTime"); EXPECT_FALSE(material.HasProperty("uTime")); } TEST(Material, ClearAllProperties) { Material material; material.SetFloat("uTime", 1.0f); material.SetFloat3("uPosition", Vector3(1.0f, 2.0f, 3.0f)); material.SetInt("uIndex", 1); EXPECT_TRUE(material.HasProperty("uTime")); EXPECT_TRUE(material.HasProperty("uPosition")); EXPECT_TRUE(material.HasProperty("uIndex")); material.ClearAllProperties(); EXPECT_FALSE(material.HasProperty("uTime")); EXPECT_FALSE(material.HasProperty("uPosition")); EXPECT_FALSE(material.HasProperty("uIndex")); } TEST(Material, SetShaderSeedsDefaultsAndRemovePropertyRestoresShaderDefault) { Material material; Shader* shader = CreateMaterialSchemaShader(); material.SetShader(ResourceHandle(shader)); EXPECT_TRUE(material.HasProperty("_BaseColor")); EXPECT_TRUE(material.HasProperty("_Metallic")); EXPECT_TRUE(material.HasProperty("_MainTex")); EXPECT_EQ(material.GetFloat4("_BaseColor"), Vector4(1.0f, 0.5f, 0.25f, 1.0f)); EXPECT_FLOAT_EQ(material.GetFloat("_Metallic"), 0.7f); EXPECT_EQ(material.GetTextureBindingCount(), 0u); material.SetFloat4("_BaseColor", Vector4(0.2f, 0.3f, 0.4f, 0.5f)); EXPECT_EQ(material.GetFloat4("_BaseColor"), Vector4(0.2f, 0.3f, 0.4f, 0.5f)); material.RemoveProperty("_BaseColor"); EXPECT_EQ(material.GetFloat4("_BaseColor"), Vector4(1.0f, 0.5f, 0.25f, 1.0f)); } TEST(Material, ClearAllPropertiesWithShaderRestoresSchemaDefaults) { Material material; Shader* shader = CreateMaterialSchemaShader(); material.SetShader(ResourceHandle(shader)); material.SetFloat("_Metallic", 0.15f); material.SetTexture("_MainTex", ResourceHandle(new Texture())); ASSERT_EQ(material.GetTextureBindingCount(), 1u); material.ClearAllProperties(); EXPECT_TRUE(material.HasProperty("_BaseColor")); EXPECT_TRUE(material.HasProperty("_Metallic")); EXPECT_TRUE(material.HasProperty("_MainTex")); EXPECT_EQ(material.GetFloat4("_BaseColor"), Vector4(1.0f, 0.5f, 0.25f, 1.0f)); EXPECT_FLOAT_EQ(material.GetFloat("_Metallic"), 0.7f); EXPECT_EQ(material.GetTextureBindingCount(), 0u); } TEST(Material, ShaderSchemaRejectsUnknownAndTypeMismatchedAssignments) { Material material; Shader* shader = CreateMaterialSchemaShader(); material.SetShader(ResourceHandle(shader)); const Vector4 defaultBaseColor = material.GetFloat4("_BaseColor"); const float defaultMetallic = material.GetFloat("_Metallic"); material.SetFloat("_BaseColor", 0.1f); material.SetFloat4("_Metallic", Vector4(1.0f, 2.0f, 3.0f, 4.0f)); material.SetFloat("UnknownProperty", 5.0f); EXPECT_EQ(material.GetFloat4("_BaseColor"), defaultBaseColor); EXPECT_FLOAT_EQ(material.GetFloat("_Metallic"), defaultMetallic); EXPECT_FALSE(material.HasProperty("UnknownProperty")); } TEST(Material, SwitchingShaderResyncsPropertiesAgainstNewSchema) { Material material; auto* shaderA = new Shader(); ShaderPropertyDesc sharedA = {}; sharedA.name = "Shared"; sharedA.type = ShaderPropertyType::Float; sharedA.defaultValue = "1.0"; shaderA->AddProperty(sharedA); ShaderPropertyDesc onlyA = {}; onlyA.name = "OnlyA"; onlyA.type = ShaderPropertyType::Float; onlyA.defaultValue = "2.0"; shaderA->AddProperty(onlyA); auto* shaderB = new Shader(); ShaderPropertyDesc sharedB = {}; sharedB.name = "Shared"; sharedB.type = ShaderPropertyType::Float; sharedB.defaultValue = "4.0"; shaderB->AddProperty(sharedB); ShaderPropertyDesc onlyB = {}; onlyB.name = "OnlyB"; onlyB.type = ShaderPropertyType::Color; onlyB.defaultValue = "(0.1,0.2,0.3,0.4)"; shaderB->AddProperty(onlyB); material.SetFloat("Legacy", 9.0f); material.SetShader(ResourceHandle(shaderA)); material.SetFloat("Shared", 5.0f); material.SetFloat("OnlyA", 8.0f); material.SetShader(ResourceHandle(shaderB)); EXPECT_FALSE(material.HasProperty("Legacy")); EXPECT_FALSE(material.HasProperty("OnlyA")); EXPECT_TRUE(material.HasProperty("Shared")); EXPECT_TRUE(material.HasProperty("OnlyB")); EXPECT_FLOAT_EQ(material.GetFloat("Shared"), 5.0f); EXPECT_EQ(material.GetFloat4("OnlyB"), Vector4(0.1f, 0.2f, 0.3f, 0.4f)); } TEST(Material, SwitchingShaderDropsUnknownRuntimeBufferBindings) { Material material; auto* shaderA = new Shader(); ShaderPass passA = {}; passA.name = "ForwardLit"; ShaderResourceBindingDesc nodesBinding = {}; nodesBinding.name = "VolumeNodes"; nodesBinding.type = ShaderResourceType::StructuredBuffer; nodesBinding.set = 2u; nodesBinding.binding = 0u; passA.resources.PushBack(nodesBinding); shaderA->AddPass(passA); auto* shaderB = new Shader(); ShaderPass passB = {}; passB.name = "ForwardLit"; ShaderResourceBindingDesc bricksBinding = {}; bricksBinding.name = "VolumeBricks"; bricksBinding.type = ShaderResourceType::StructuredBuffer; bricksBinding.set = 2u; bricksBinding.binding = 0u; passB.resources.PushBack(bricksBinding); shaderB->AddPass(passB); MockMaterialBuffer nodesBuffer; MockMaterialBuffer bricksBuffer; material.SetShader(ResourceHandle(shaderA)); material.SetBuffer("VolumeNodes", &nodesBuffer); material.SetBuffer("VolumeBricks", &bricksBuffer); material.SetShader(ResourceHandle(shaderB)); EXPECT_EQ(material.GetBuffer("VolumeNodes"), nullptr); EXPECT_EQ(material.GetBuffer("VolumeBricks"), &bricksBuffer); EXPECT_EQ(material.GetBufferBindingCount(), 1u); } TEST(Material, UpdateConstantBufferFollowsShaderSchemaOrderInsteadOfAlphabeticalOrder) { Material material; auto* shader = new Shader(); ShaderPropertyDesc beta = {}; beta.name = "beta"; beta.type = ShaderPropertyType::Float; beta.defaultValue = "0.0"; shader->AddProperty(beta); ShaderPropertyDesc alpha = {}; alpha.name = "alpha"; alpha.type = ShaderPropertyType::Float; alpha.defaultValue = "0.0"; shader->AddProperty(alpha); material.SetShader(ResourceHandle(shader)); material.SetFloat("alpha", 10.0f); material.SetFloat("beta", 20.0f); const auto& constantLayout = material.GetConstantLayout(); ASSERT_EQ(constantLayout.Size(), 2u); EXPECT_EQ(constantLayout[0].name, "beta"); EXPECT_EQ(constantLayout[0].offset, 0u); EXPECT_EQ(constantLayout[1].name, "alpha"); EXPECT_EQ(constantLayout[1].offset, 16u); const auto& constantBufferData = material.GetConstantBufferData(); ASSERT_EQ(constantBufferData.Size(), 32u); const float* firstSlot = reinterpret_cast(constantBufferData.Data()); const float* secondSlot = reinterpret_cast(constantBufferData.Data() + 16); EXPECT_FLOAT_EQ(firstSlot[0], 20.0f); EXPECT_FLOAT_EQ(secondSlot[0], 10.0f); } TEST(Material, KeywordsFormStableValidatedSet) { Material material; Shader* shader = CreateMaterialKeywordShader({"XC_MAIN_LIGHT_SHADOWS", "XC_ALPHA_TEST"}); material.SetShader(ResourceHandle(shader)); material.EnableKeyword("XC_MAIN_LIGHT_SHADOWS"); material.EnableKeyword("XC_ALPHA_TEST"); material.EnableKeyword("XC_ALPHA_TEST"); material.EnableKeyword("_"); material.EnableKeyword("XC_UNKNOWN"); ASSERT_EQ(material.GetKeywordCount(), 2u); EXPECT_EQ(material.GetKeyword(0), "XC_ALPHA_TEST"); EXPECT_EQ(material.GetKeyword(1), "XC_MAIN_LIGHT_SHADOWS"); EXPECT_TRUE(material.IsKeywordEnabled("XC_ALPHA_TEST")); EXPECT_TRUE(material.IsKeywordEnabled("XC_MAIN_LIGHT_SHADOWS")); EXPECT_FALSE(material.IsKeywordEnabled("XC_UNKNOWN")); material.DisableKeyword("XC_ALPHA_TEST"); ASSERT_EQ(material.GetKeywordCount(), 1u); EXPECT_EQ(material.GetKeyword(0), "XC_MAIN_LIGHT_SHADOWS"); material.ClearKeywords(); EXPECT_EQ(material.GetKeywordCount(), 0u); } TEST(Material, SwitchingShaderDropsUndeclaredKeywords) { Material material; Shader* shaderA = CreateMaterialKeywordShader({"XC_MAIN_LIGHT_SHADOWS", "XC_ALPHA_TEST"}); Shader* shaderB = CreateMaterialKeywordShader({"XC_ALPHA_TEST", "XC_EMISSION"}); material.SetShader(ResourceHandle(shaderA)); material.EnableKeyword("XC_MAIN_LIGHT_SHADOWS"); material.EnableKeyword("XC_ALPHA_TEST"); ASSERT_EQ(material.GetKeywordCount(), 2u); material.SetShader(ResourceHandle(shaderB)); ASSERT_EQ(material.GetKeywordCount(), 1u); EXPECT_FALSE(material.IsKeywordEnabled("XC_MAIN_LIGHT_SHADOWS")); EXPECT_TRUE(material.IsKeywordEnabled("XC_ALPHA_TEST")); material.EnableKeyword("XC_EMISSION"); ASSERT_EQ(material.GetKeywordCount(), 2u); EXPECT_EQ(material.GetKeyword(0), "XC_ALPHA_TEST"); EXPECT_EQ(material.GetKeyword(1), "XC_EMISSION"); } } // namespace