#pragma once #include #include #include #include #include #include #include #include #include #include namespace XCEngine { namespace Rendering { enum class BuiltinMaterialPass : Core::uint32 { ForwardLit = 0, Unlit, DepthOnly, ShadowCaster, ObjectId, Forward = ForwardLit }; struct PassResourceBindingLocation { Core::uint32 set = UINT32_MAX; Core::uint32 binding = UINT32_MAX; bool IsValid() const { return set != UINT32_MAX && binding != UINT32_MAX; } }; enum class BuiltinPassResourceSemantic : Core::uint8 { Unknown = 0, PerObject, Material, Lighting, ShadowReceiver, BaseColorTexture, ShadowMapTexture, LinearClampSampler, ShadowMapSampler }; struct BuiltinPassResourceBindingDesc { BuiltinPassResourceSemantic semantic = BuiltinPassResourceSemantic::Unknown; Resources::ShaderResourceType resourceType = Resources::ShaderResourceType::ConstantBuffer; PassResourceBindingLocation location = {}; }; struct BuiltinPassResourceBindingPlan { Containers::Array bindings; Core::uint32 maxSetIndex = 0; Core::uint32 firstDescriptorSet = 0; Core::uint32 descriptorSetCount = 0; bool usesConstantBuffers = false; bool usesTextures = false; bool usesSamplers = false; PassResourceBindingLocation perObject = {}; PassResourceBindingLocation material = {}; PassResourceBindingLocation lighting = {}; PassResourceBindingLocation shadowReceiver = {}; PassResourceBindingLocation baseColorTexture = {}; PassResourceBindingLocation linearClampSampler = {}; PassResourceBindingLocation shadowMapTexture = {}; PassResourceBindingLocation shadowMapSampler = {}; const BuiltinPassResourceBindingDesc* FindBinding(BuiltinPassResourceSemantic semantic) const { for (const BuiltinPassResourceBindingDesc& binding : bindings) { if (binding.semantic == semantic) { return &binding; } } return nullptr; } }; struct BuiltinPassSetLayoutMetadata { std::vector bindings; RHI::DescriptorSetLayoutDesc layout = {}; RHI::DescriptorHeapType heapType = RHI::DescriptorHeapType::CBV_SRV_UAV; bool shaderVisible = false; bool usesPerObject = false; bool usesMaterial = false; bool usesLighting = false; bool usesShadowReceiver = false; bool usesTexture = false; bool usesBaseColorTexture = false; bool usesShadowMapTexture = false; bool usesSampler = false; bool usesLinearClampSampler = false; bool usesShadowMapSampler = false; }; inline Containers::String NormalizeBuiltinPassMetadataValue(const Containers::String& value) { return value.Trim().ToLower(); } inline bool IsForwardPassName(const Containers::String& value) { const Containers::String normalized = NormalizeBuiltinPassMetadataValue(value); return normalized.Empty() || normalized == Containers::String("forward") || normalized == Containers::String("forwardbase") || normalized == Containers::String("forwardlit") || normalized == Containers::String("forwardonly"); } inline bool IsUnlitPassName(const Containers::String& value) { const Containers::String normalized = NormalizeBuiltinPassMetadataValue(value); return normalized == Containers::String("unlit") || normalized == Containers::String("forwardunlit") || normalized == Containers::String("srpdefaultunlit"); } inline bool IsDepthOnlyPassName(const Containers::String& value) { const Containers::String normalized = NormalizeBuiltinPassMetadataValue(value); return normalized == Containers::String("depthonly") || normalized == Containers::String("depth"); } inline bool IsShadowCasterPassName(const Containers::String& value) { const Containers::String normalized = NormalizeBuiltinPassMetadataValue(value); return normalized == Containers::String("shadowcaster") || normalized == Containers::String("shadow"); } inline bool IsObjectIdPassName(const Containers::String& value) { const Containers::String normalized = value.Trim().ToLower(); return normalized == Containers::String("objectid") || normalized == Containers::String("editorobjectid"); } inline bool MatchesBuiltinPassName( const Containers::String& value, BuiltinMaterialPass pass) { switch (pass) { case BuiltinMaterialPass::ForwardLit: return IsForwardPassName(value); case BuiltinMaterialPass::Unlit: return IsUnlitPassName(value); case BuiltinMaterialPass::DepthOnly: return IsDepthOnlyPassName(value); case BuiltinMaterialPass::ShadowCaster: return IsShadowCasterPassName(value); case BuiltinMaterialPass::ObjectId: return IsObjectIdPassName(value); default: return false; } } inline bool ShaderPassHasExplicitBuiltinMetadata(const Resources::ShaderPass& shaderPass) { if (!shaderPass.name.Empty() && shaderPass.name != Containers::String("Default")) { return true; } for (const Resources::ShaderPassTagEntry& tag : shaderPass.tags) { if (NormalizeBuiltinPassMetadataValue(tag.name) == Containers::String("lightmode")) { return true; } } return false; } inline bool ShaderPassMatchesBuiltinPass( const Resources::ShaderPass& shaderPass, BuiltinMaterialPass pass) { bool hasMetadata = false; if (!shaderPass.name.Empty() && shaderPass.name != Containers::String("Default")) { hasMetadata = true; if (!MatchesBuiltinPassName(shaderPass.name, pass)) { return false; } } for (const Resources::ShaderPassTagEntry& tag : shaderPass.tags) { if (NormalizeBuiltinPassMetadataValue(tag.name) != Containers::String("lightmode")) { continue; } hasMetadata = true; if (!MatchesBuiltinPassName(tag.value, pass)) { return false; } } return hasMetadata; } inline BuiltinPassResourceSemantic ResolveBuiltinPassResourceSemantic( const Resources::ShaderResourceBindingDesc& binding) { Containers::String semantic = NormalizeBuiltinPassMetadataValue(binding.semantic); if (semantic.Empty()) { semantic = NormalizeBuiltinPassMetadataValue(binding.name); } if (semantic == Containers::String("perobject") || semantic == Containers::String("perobjectconstants")) { return BuiltinPassResourceSemantic::PerObject; } if (semantic == Containers::String("material") || semantic == Containers::String("materialconstants")) { return BuiltinPassResourceSemantic::Material; } if (semantic == Containers::String("lighting") || semantic == Containers::String("lightingconstants")) { return BuiltinPassResourceSemantic::Lighting; } if (semantic == Containers::String("shadowreceiver") || semantic == Containers::String("shadowreceiverconstants")) { return BuiltinPassResourceSemantic::ShadowReceiver; } if (semantic == Containers::String("basecolortexture") || semantic == Containers::String("maintex")) { return BuiltinPassResourceSemantic::BaseColorTexture; } if (semantic == Containers::String("shadowmaptexture") || semantic == Containers::String("shadowmap")) { return BuiltinPassResourceSemantic::ShadowMapTexture; } if (semantic == Containers::String("linearclampsampler")) { return BuiltinPassResourceSemantic::LinearClampSampler; } if (semantic == Containers::String("shadowmapsampler") || semantic == Containers::String("shadowsampler")) { return BuiltinPassResourceSemantic::ShadowMapSampler; } return BuiltinPassResourceSemantic::Unknown; } inline bool IsBuiltinPassResourceTypeCompatible( BuiltinPassResourceSemantic semantic, Resources::ShaderResourceType type) { switch (semantic) { case BuiltinPassResourceSemantic::PerObject: case BuiltinPassResourceSemantic::Material: case BuiltinPassResourceSemantic::Lighting: case BuiltinPassResourceSemantic::ShadowReceiver: return type == Resources::ShaderResourceType::ConstantBuffer; case BuiltinPassResourceSemantic::BaseColorTexture: case BuiltinPassResourceSemantic::ShadowMapTexture: return type == Resources::ShaderResourceType::Texture2D || type == Resources::ShaderResourceType::TextureCube; case BuiltinPassResourceSemantic::LinearClampSampler: case BuiltinPassResourceSemantic::ShadowMapSampler: return type == Resources::ShaderResourceType::Sampler; case BuiltinPassResourceSemantic::Unknown: default: return false; } } inline bool TryBuildBuiltinPassResourceBindingPlan( const Containers::Array& bindings, BuiltinPassResourceBindingPlan& outPlan, Containers::String* outError = nullptr) { outPlan = {}; auto fail = [&outError](const char* message) { if (outError != nullptr) { *outError = message; } return false; }; if (bindings.Empty()) { return true; } outPlan.bindings.Reserve(bindings.Size()); Core::uint32 minBoundSet = UINT32_MAX; Core::uint32 maxBoundSet = 0; for (const Resources::ShaderResourceBindingDesc& binding : bindings) { const BuiltinPassResourceSemantic semantic = ResolveBuiltinPassResourceSemantic(binding); if (semantic == BuiltinPassResourceSemantic::Unknown) { return fail("Unsupported builtin pass resource semantic"); } if (!IsBuiltinPassResourceTypeCompatible(semantic, binding.type)) { return fail("Builtin pass resource semantic/type combination is invalid"); } PassResourceBindingLocation* location = nullptr; switch (semantic) { case BuiltinPassResourceSemantic::PerObject: location = &outPlan.perObject; break; case BuiltinPassResourceSemantic::Material: location = &outPlan.material; break; case BuiltinPassResourceSemantic::Lighting: location = &outPlan.lighting; break; case BuiltinPassResourceSemantic::ShadowReceiver: location = &outPlan.shadowReceiver; break; case BuiltinPassResourceSemantic::BaseColorTexture: location = &outPlan.baseColorTexture; break; case BuiltinPassResourceSemantic::LinearClampSampler: location = &outPlan.linearClampSampler; break; case BuiltinPassResourceSemantic::ShadowMapTexture: location = &outPlan.shadowMapTexture; break; case BuiltinPassResourceSemantic::ShadowMapSampler: location = &outPlan.shadowMapSampler; break; case BuiltinPassResourceSemantic::Unknown: default: break; } if (location == nullptr) { return fail("Builtin pass resource semantic could not be mapped"); } if (location->IsValid()) { return fail("Builtin pass resource semantic appears more than once"); } for (const BuiltinPassResourceBindingDesc& existingBinding : outPlan.bindings) { if (existingBinding.location.set == binding.set && existingBinding.location.binding == binding.binding) { return fail("Builtin pass resource set/binding pair appears more than once"); } } *location = { binding.set, binding.binding }; BuiltinPassResourceBindingDesc resolvedBinding = {}; resolvedBinding.semantic = semantic; resolvedBinding.resourceType = binding.type; resolvedBinding.location = *location; outPlan.bindings.PushBack(resolvedBinding); outPlan.maxSetIndex = std::max(outPlan.maxSetIndex, binding.set); minBoundSet = std::min(minBoundSet, binding.set); maxBoundSet = std::max(maxBoundSet, binding.set); switch (binding.type) { case Resources::ShaderResourceType::ConstantBuffer: outPlan.usesConstantBuffers = true; break; case Resources::ShaderResourceType::Texture2D: case Resources::ShaderResourceType::TextureCube: outPlan.usesTextures = true; break; case Resources::ShaderResourceType::Sampler: outPlan.usesSamplers = true; break; default: break; } } outPlan.firstDescriptorSet = minBoundSet; outPlan.descriptorSetCount = maxBoundSet - minBoundSet + 1u; return true; } inline RHI::DescriptorType ToBuiltinPassDescriptorType(Resources::ShaderResourceType type) { switch (type) { case Resources::ShaderResourceType::ConstantBuffer: return RHI::DescriptorType::CBV; case Resources::ShaderResourceType::Texture2D: case Resources::ShaderResourceType::TextureCube: return RHI::DescriptorType::SRV; case Resources::ShaderResourceType::Sampler: return RHI::DescriptorType::Sampler; default: return RHI::DescriptorType::CBV; } } inline RHI::DescriptorHeapType ResolveBuiltinPassDescriptorHeapType(Resources::ShaderResourceType type) { return type == Resources::ShaderResourceType::Sampler ? RHI::DescriptorHeapType::Sampler : RHI::DescriptorHeapType::CBV_SRV_UAV; } inline bool IsBuiltinPassShaderVisibleSet(const std::vector& bindings) { for (const RHI::DescriptorSetLayoutBinding& binding : bindings) { const RHI::DescriptorType descriptorType = static_cast(binding.type); if (descriptorType == RHI::DescriptorType::SRV || descriptorType == RHI::DescriptorType::UAV || descriptorType == RHI::DescriptorType::Sampler) { return true; } } return false; } inline Core::uint32 CountBuiltinPassHeapDescriptors( RHI::DescriptorHeapType heapType, const std::vector& bindings) { Core::uint32 descriptorCount = 0; for (const RHI::DescriptorSetLayoutBinding& binding : bindings) { const RHI::DescriptorType descriptorType = static_cast(binding.type); if (heapType == RHI::DescriptorHeapType::Sampler) { if (descriptorType == RHI::DescriptorType::Sampler) { descriptorCount += binding.count > 0 ? binding.count : 1u; } continue; } if (descriptorType != RHI::DescriptorType::Sampler) { descriptorCount += binding.count > 0 ? binding.count : 1u; } } return descriptorCount > 0 ? descriptorCount : 1u; } inline void RefreshBuiltinPassSetLayoutMetadata(BuiltinPassSetLayoutMetadata& setLayout) { std::sort( setLayout.bindings.begin(), setLayout.bindings.end(), [](const RHI::DescriptorSetLayoutBinding& left, const RHI::DescriptorSetLayoutBinding& right) { return left.binding < right.binding; }); setLayout.shaderVisible = IsBuiltinPassShaderVisibleSet(setLayout.bindings); setLayout.layout.bindings = setLayout.bindings.empty() ? nullptr : setLayout.bindings.data(); setLayout.layout.bindingCount = static_cast(setLayout.bindings.size()); } inline void RefreshBuiltinPassSetLayouts(std::vector& setLayouts) { for (BuiltinPassSetLayoutMetadata& setLayout : setLayouts) { RefreshBuiltinPassSetLayoutMetadata(setLayout); } } inline bool TryBuildBuiltinPassSetLayouts( const BuiltinPassResourceBindingPlan& bindingPlan, std::vector& outSetLayouts, Containers::String* outError = nullptr) { outSetLayouts.clear(); auto fail = [&outError](const char* message) { if (outError != nullptr) { *outError = message; } return false; }; if (bindingPlan.bindings.Empty()) { return true; } outSetLayouts.resize(static_cast(bindingPlan.maxSetIndex) + 1u); for (const BuiltinPassResourceBindingDesc& binding : bindingPlan.bindings) { if (binding.location.set >= outSetLayouts.size()) { return fail("Builtin pass encountered an invalid descriptor set index"); } const RHI::DescriptorType descriptorType = ToBuiltinPassDescriptorType(binding.resourceType); const RHI::DescriptorHeapType heapType = ResolveBuiltinPassDescriptorHeapType(binding.resourceType); BuiltinPassSetLayoutMetadata& setLayout = outSetLayouts[binding.location.set]; if (!setLayout.bindings.empty() && setLayout.heapType != heapType) { return fail("Builtin pass does not support mixing sampler and non-sampler bindings in one set"); } for (const RHI::DescriptorSetLayoutBinding& existingBinding : setLayout.bindings) { if (existingBinding.binding == binding.location.binding) { return fail("Builtin pass encountered duplicate bindings inside one descriptor set"); } } if (setLayout.bindings.empty()) { setLayout.heapType = heapType; } RHI::DescriptorSetLayoutBinding layoutBinding = {}; layoutBinding.binding = binding.location.binding; layoutBinding.type = static_cast(descriptorType); layoutBinding.count = 1; setLayout.bindings.push_back(layoutBinding); switch (binding.semantic) { case BuiltinPassResourceSemantic::PerObject: setLayout.usesPerObject = true; break; case BuiltinPassResourceSemantic::Material: setLayout.usesMaterial = true; break; case BuiltinPassResourceSemantic::Lighting: setLayout.usesLighting = true; break; case BuiltinPassResourceSemantic::ShadowReceiver: setLayout.usesShadowReceiver = true; break; case BuiltinPassResourceSemantic::BaseColorTexture: setLayout.usesTexture = true; setLayout.usesBaseColorTexture = true; break; case BuiltinPassResourceSemantic::ShadowMapTexture: setLayout.usesTexture = true; setLayout.usesShadowMapTexture = true; break; case BuiltinPassResourceSemantic::LinearClampSampler: setLayout.usesSampler = true; setLayout.usesLinearClampSampler = true; break; case BuiltinPassResourceSemantic::ShadowMapSampler: setLayout.usesSampler = true; setLayout.usesShadowMapSampler = true; break; case BuiltinPassResourceSemantic::Unknown: default: return fail("Builtin pass encountered an unsupported resource semantic"); } } RefreshBuiltinPassSetLayouts(outSetLayouts); return true; } struct BuiltinForwardMaterialData { Math::Vector4 baseColorFactor = Math::Vector4::One(); }; struct MaterialConstantLayoutView { const Resources::MaterialConstantFieldDesc* fields = nullptr; size_t count = 0; size_t size = 0; bool IsValid() const { return fields != nullptr && count > 0 && size > 0; } }; struct MaterialConstantPayloadView { const void* data = nullptr; size_t size = 0; MaterialConstantLayoutView layout = {}; bool IsValid() const { return data != nullptr && size > 0 && layout.IsValid() && layout.size == size; } }; inline const Resources::ShaderPropertyDesc* FindShaderPropertyBySemantic( const Resources::Material* material, const Containers::String& semantic) { if (material == nullptr || material->GetShader() == nullptr) { return nullptr; } const Containers::String normalizedSemantic = NormalizeBuiltinPassMetadataValue(semantic); for (const Resources::ShaderPropertyDesc& property : material->GetShader()->GetProperties()) { if (NormalizeBuiltinPassMetadataValue(property.semantic) == normalizedSemantic) { return &property; } } return nullptr; } inline Math::Vector4 ResolveBuiltinBaseColorFactor(const Resources::Material* material) { if (material == nullptr) { return Math::Vector4::One(); } if (const Resources::ShaderPropertyDesc* property = FindShaderPropertyBySemantic(material, "BaseColor")) { if (material->HasProperty(property->name) && (property->type == Resources::ShaderPropertyType::Color || property->type == Resources::ShaderPropertyType::Vector)) { return material->GetFloat4(property->name); } } static const char* kBaseColorPropertyNames[] = { "baseColor", "_BaseColor", "color", "_Color" }; for (const char* propertyName : kBaseColorPropertyNames) { if (material->HasProperty(Containers::String(propertyName))) { return material->GetFloat4(Containers::String(propertyName)); } } Math::Vector4 baseColor = Math::Vector4::One(); static const char* kOpacityPropertyNames[] = { "opacity", "_Opacity", "alpha", "_Alpha" }; for (const char* propertyName : kOpacityPropertyNames) { if (material->HasProperty(Containers::String(propertyName))) { baseColor.w = material->GetFloat(Containers::String(propertyName)); break; } } return baseColor; } inline const Resources::Texture* ResolveBuiltinBaseColorTexture(const Resources::Material* material) { if (material == nullptr) { return nullptr; } if (const Resources::ShaderPropertyDesc* property = FindShaderPropertyBySemantic(material, "BaseColorTexture")) { const Resources::ResourceHandle textureHandle = material->GetTexture(property->name); if (textureHandle.Get() != nullptr && textureHandle->IsValid()) { return textureHandle.Get(); } } static const char* kTextureNames[] = { "baseColorTexture", "_BaseColorTexture", "_MainTex", "albedoTexture", "mainTexture", "texture" }; for (const char* textureName : kTextureNames) { const Resources::ResourceHandle textureHandle = material->GetTexture(Containers::String(textureName)); if (textureHandle.Get() != nullptr && textureHandle->IsValid()) { return textureHandle.Get(); } } return nullptr; } inline BuiltinForwardMaterialData BuildBuiltinForwardMaterialData(const Resources::Material* material) { BuiltinForwardMaterialData data = {}; data.baseColorFactor = ResolveBuiltinBaseColorFactor(material); return data; } inline MaterialConstantPayloadView ResolveSchemaMaterialConstantPayload(const Resources::Material* material) { if (material == nullptr || material->GetShader() == nullptr) { return {}; } const Containers::Array& constantLayout = material->GetConstantLayout(); const Containers::Array& constantBufferData = material->GetConstantBufferData(); if (constantLayout.Empty() || constantBufferData.Empty()) { return {}; } MaterialConstantLayoutView layoutView = {}; layoutView.fields = constantLayout.Data(); layoutView.count = constantLayout.Size(); layoutView.size = constantBufferData.Size(); return { constantBufferData.Data(), constantBufferData.Size(), layoutView }; } inline const Resources::Material* ResolveMaterial( const Components::MeshRendererComponent* meshRenderer, const Resources::Mesh* mesh, Core::uint32 materialIndex) { if (meshRenderer != nullptr && materialIndex < meshRenderer->GetMaterialCount()) { if (const Resources::Material* material = meshRenderer->GetMaterial(materialIndex)) { return material; } } if (mesh != nullptr && materialIndex < mesh->GetMaterials().Size()) { if (const Resources::Material* material = mesh->GetMaterials()[materialIndex]) { return material; } } if (meshRenderer != nullptr && meshRenderer->GetMaterialCount() > 0) { if (const Resources::Material* material = meshRenderer->GetMaterial(0)) { return material; } } if (mesh != nullptr && mesh->GetMaterials().Size() > 0) { return mesh->GetMaterials()[0]; } return nullptr; } inline const Resources::Material* ResolveMaterial(const VisibleRenderItem& visibleItem) { if (visibleItem.material != nullptr) { return visibleItem.material; } return ResolveMaterial(visibleItem.meshRenderer, visibleItem.mesh, visibleItem.materialIndex); } inline Core::int32 ResolveMaterialRenderQueue(const Resources::Material* material) { return material != nullptr ? material->GetRenderQueue() : static_cast(Resources::MaterialRenderQueue::Geometry); } inline bool IsTransparentRenderQueue(Core::int32 renderQueue) { return renderQueue >= static_cast(Resources::MaterialRenderQueue::Transparent); } inline bool MatchesBuiltinPass(const Resources::Material* material, BuiltinMaterialPass pass) { if (material == nullptr) { return pass == BuiltinMaterialPass::ForwardLit; } const Containers::String shaderPass = material->GetShaderPass(); const Containers::String lightMode = material->GetTag("LightMode"); const bool hasMaterialShaderPass = !NormalizeBuiltinPassMetadataValue(shaderPass).Empty(); const bool hasMaterialLightMode = !NormalizeBuiltinPassMetadataValue(lightMode).Empty(); if (hasMaterialShaderPass || hasMaterialLightMode) { if (hasMaterialShaderPass && !MatchesBuiltinPassName(shaderPass, pass)) { return false; } if (hasMaterialLightMode && !MatchesBuiltinPassName(lightMode, pass)) { return false; } return true; } const Resources::Shader* shader = material->GetShader(); if (shader != nullptr) { bool shaderHasExplicitBuiltinMetadata = false; for (const Resources::ShaderPass& shaderPassEntry : shader->GetPasses()) { if (ShaderPassMatchesBuiltinPass(shaderPassEntry, pass)) { return true; } if (ShaderPassHasExplicitBuiltinMetadata(shaderPassEntry)) { shaderHasExplicitBuiltinMetadata = true; } } if (shaderHasExplicitBuiltinMetadata) { return false; } } return pass == BuiltinMaterialPass::ForwardLit; } inline RHI::CullMode ToRHICullMode(Resources::MaterialCullMode mode) { switch (mode) { case Resources::MaterialCullMode::Front: return RHI::CullMode::Front; case Resources::MaterialCullMode::Back: return RHI::CullMode::Back; case Resources::MaterialCullMode::None: default: return RHI::CullMode::None; } } inline RHI::ComparisonFunc ToRHIComparisonFunc(Resources::MaterialComparisonFunc func) { switch (func) { case Resources::MaterialComparisonFunc::Never: return RHI::ComparisonFunc::Never; case Resources::MaterialComparisonFunc::Equal: return RHI::ComparisonFunc::Equal; case Resources::MaterialComparisonFunc::LessEqual: return RHI::ComparisonFunc::LessEqual; case Resources::MaterialComparisonFunc::Greater: return RHI::ComparisonFunc::Greater; case Resources::MaterialComparisonFunc::NotEqual: return RHI::ComparisonFunc::NotEqual; case Resources::MaterialComparisonFunc::GreaterEqual: return RHI::ComparisonFunc::GreaterEqual; case Resources::MaterialComparisonFunc::Always: return RHI::ComparisonFunc::Always; case Resources::MaterialComparisonFunc::Less: default: return RHI::ComparisonFunc::Less; } } inline RHI::BlendFactor ToRHIBlendFactor(Resources::MaterialBlendFactor factor) { switch (factor) { case Resources::MaterialBlendFactor::Zero: return RHI::BlendFactor::Zero; case Resources::MaterialBlendFactor::SrcColor: return RHI::BlendFactor::SrcColor; case Resources::MaterialBlendFactor::InvSrcColor: return RHI::BlendFactor::InvSrcColor; case Resources::MaterialBlendFactor::SrcAlpha: return RHI::BlendFactor::SrcAlpha; case Resources::MaterialBlendFactor::InvSrcAlpha: return RHI::BlendFactor::InvSrcAlpha; case Resources::MaterialBlendFactor::DstAlpha: return RHI::BlendFactor::DstAlpha; case Resources::MaterialBlendFactor::InvDstAlpha: return RHI::BlendFactor::InvDstAlpha; case Resources::MaterialBlendFactor::DstColor: return RHI::BlendFactor::DstColor; case Resources::MaterialBlendFactor::InvDstColor: return RHI::BlendFactor::InvDstColor; case Resources::MaterialBlendFactor::SrcAlphaSat: return RHI::BlendFactor::SrcAlphaSat; case Resources::MaterialBlendFactor::BlendFactor: return RHI::BlendFactor::BlendFactor; case Resources::MaterialBlendFactor::InvBlendFactor: return RHI::BlendFactor::InvBlendFactor; case Resources::MaterialBlendFactor::Src1Color: return RHI::BlendFactor::Src1Color; case Resources::MaterialBlendFactor::InvSrc1Color: return RHI::BlendFactor::InvSrc1Color; case Resources::MaterialBlendFactor::Src1Alpha: return RHI::BlendFactor::Src1Alpha; case Resources::MaterialBlendFactor::InvSrc1Alpha: return RHI::BlendFactor::InvSrc1Alpha; case Resources::MaterialBlendFactor::One: default: return RHI::BlendFactor::One; } } inline RHI::BlendOp ToRHIBlendOp(Resources::MaterialBlendOp op) { switch (op) { case Resources::MaterialBlendOp::Subtract: return RHI::BlendOp::Subtract; case Resources::MaterialBlendOp::ReverseSubtract: return RHI::BlendOp::ReverseSubtract; case Resources::MaterialBlendOp::Min: return RHI::BlendOp::Min; case Resources::MaterialBlendOp::Max: return RHI::BlendOp::Max; case Resources::MaterialBlendOp::Add: default: return RHI::BlendOp::Add; } } inline RHI::RasterizerDesc BuildRasterizerState(const Resources::Material* material) { RHI::RasterizerDesc desc = {}; desc.fillMode = static_cast(RHI::FillMode::Solid); desc.cullMode = static_cast(RHI::CullMode::None); desc.frontFace = static_cast(RHI::FrontFace::CounterClockwise); desc.depthClipEnable = true; if (material != nullptr) { const Resources::MaterialRenderState& renderState = material->GetRenderState(); desc.cullMode = static_cast(ToRHICullMode(renderState.cullMode)); } return desc; } inline RHI::BlendDesc BuildBlendState(const Resources::Material* material) { RHI::BlendDesc desc = {}; if (material != nullptr) { const Resources::MaterialRenderState& renderState = material->GetRenderState(); desc.blendEnable = renderState.blendEnable; desc.srcBlend = static_cast(ToRHIBlendFactor(renderState.srcBlend)); desc.dstBlend = static_cast(ToRHIBlendFactor(renderState.dstBlend)); desc.srcBlendAlpha = static_cast(ToRHIBlendFactor(renderState.srcBlendAlpha)); desc.dstBlendAlpha = static_cast(ToRHIBlendFactor(renderState.dstBlendAlpha)); desc.blendOp = static_cast(ToRHIBlendOp(renderState.blendOp)); desc.blendOpAlpha = static_cast(ToRHIBlendOp(renderState.blendOpAlpha)); desc.colorWriteMask = renderState.colorWriteMask; } return desc; } inline RHI::DepthStencilStateDesc BuildDepthStencilState(const Resources::Material* material) { RHI::DepthStencilStateDesc desc = {}; desc.depthTestEnable = true; desc.depthWriteEnable = true; desc.depthFunc = static_cast(RHI::ComparisonFunc::Less); desc.stencilEnable = false; if (material != nullptr) { const Resources::MaterialRenderState& renderState = material->GetRenderState(); desc.depthTestEnable = renderState.depthTestEnable; desc.depthWriteEnable = renderState.depthWriteEnable; desc.depthFunc = static_cast(ToRHIComparisonFunc(renderState.depthFunc)); } return desc; } inline void ApplyMaterialRenderState(const Resources::Material* material, RHI::GraphicsPipelineDesc& pipelineDesc) { pipelineDesc.rasterizerState = BuildRasterizerState(material); pipelineDesc.blendState = BuildBlendState(material); pipelineDesc.depthStencilState = BuildDepthStencilState(material); } struct MaterialRenderStateHash { size_t operator()(const Resources::MaterialRenderState& state) const noexcept { size_t hash = 2166136261u; auto combine = [&hash](size_t value) { hash ^= value + 0x9e3779b9u + (hash << 6) + (hash >> 2); }; combine(static_cast(state.blendEnable)); combine(static_cast(state.srcBlend)); combine(static_cast(state.dstBlend)); combine(static_cast(state.srcBlendAlpha)); combine(static_cast(state.dstBlendAlpha)); combine(static_cast(state.blendOp)); combine(static_cast(state.blendOpAlpha)); combine(static_cast(state.colorWriteMask)); combine(static_cast(state.depthTestEnable)); combine(static_cast(state.depthWriteEnable)); combine(static_cast(state.depthFunc)); combine(static_cast(state.cullMode)); return hash; } }; } // namespace Rendering } // namespace XCEngine