diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 930f2cf8..1d7ea993 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -431,6 +431,7 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderCameraData.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/CameraRenderRequest.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/VisibleRenderObject.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/BuiltinPassContract.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderSceneUtility.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderSceneExtractor.h diff --git a/engine/include/XCEngine/Rendering/BuiltinPassContract.h b/engine/include/XCEngine/Rendering/BuiltinPassContract.h new file mode 100644 index 00000000..3ad0f9b2 --- /dev/null +++ b/engine/include/XCEngine/Rendering/BuiltinPassContract.h @@ -0,0 +1,600 @@ +#pragma once + +#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 const char* BuiltinPassResourceSemanticToString(BuiltinPassResourceSemantic semantic) { + switch (semantic) { + case BuiltinPassResourceSemantic::PerObject: + return "PerObject"; + case BuiltinPassResourceSemantic::Material: + return "Material"; + case BuiltinPassResourceSemantic::Lighting: + return "Lighting"; + case BuiltinPassResourceSemantic::ShadowReceiver: + return "ShadowReceiver"; + case BuiltinPassResourceSemantic::BaseColorTexture: + return "BaseColorTexture"; + case BuiltinPassResourceSemantic::ShadowMapTexture: + return "ShadowMapTexture"; + case BuiltinPassResourceSemantic::LinearClampSampler: + return "LinearClampSampler"; + case BuiltinPassResourceSemantic::ShadowMapSampler: + return "ShadowMapSampler"; + case BuiltinPassResourceSemantic::Unknown: + default: + return "Unknown"; + } +} + +inline const char* ShaderResourceTypeToString(Resources::ShaderResourceType type) { + switch (type) { + case Resources::ShaderResourceType::ConstantBuffer: + return "ConstantBuffer"; + case Resources::ShaderResourceType::Texture2D: + return "Texture2D"; + case Resources::ShaderResourceType::TextureCube: + return "TextureCube"; + case Resources::ShaderResourceType::Sampler: + return "Sampler"; + default: + return "Unknown"; + } +} + +inline Containers::String DescribeShaderResourceBinding( + const Resources::ShaderResourceBindingDesc& binding) { + const BuiltinPassResourceSemantic resolvedSemantic = ResolveBuiltinPassResourceSemantic(binding); + return Containers::String("name=") + binding.name + + ", semantic=" + binding.semantic + + ", resolvedSemantic=" + Containers::String(BuiltinPassResourceSemanticToString(resolvedSemantic)) + + ", type=" + Containers::String(ShaderResourceTypeToString(binding.type)) + + ", set=" + Containers::String(std::to_string(binding.set).c_str()) + + ", binding=" + Containers::String(std::to_string(binding.binding).c_str()); +} + +inline Containers::String DescribeShaderResourceBindings( + const Containers::Array& bindings) { + Containers::String description; + for (size_t bindingIndex = 0; bindingIndex < bindings.Size(); ++bindingIndex) { + if (!description.Empty()) { + description += " | "; + } + + description += "[" + Containers::String(std::to_string(bindingIndex).c_str()) + "] "; + description += DescribeShaderResourceBinding(bindings[bindingIndex]); + } + + return description; +} + +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; +} + +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/include/XCEngine/Rendering/RenderMaterialUtility.h b/engine/include/XCEngine/Rendering/RenderMaterialUtility.h index 59dfddd2..d85a59d3 100644 --- a/engine/include/XCEngine/Rendering/RenderMaterialUtility.h +++ b/engine/include/XCEngine/Rendering/RenderMaterialUtility.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -16,588 +17,6 @@ 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 const char* BuiltinPassResourceSemanticToString(BuiltinPassResourceSemantic semantic) { - switch (semantic) { - case BuiltinPassResourceSemantic::PerObject: - return "PerObject"; - case BuiltinPassResourceSemantic::Material: - return "Material"; - case BuiltinPassResourceSemantic::Lighting: - return "Lighting"; - case BuiltinPassResourceSemantic::ShadowReceiver: - return "ShadowReceiver"; - case BuiltinPassResourceSemantic::BaseColorTexture: - return "BaseColorTexture"; - case BuiltinPassResourceSemantic::ShadowMapTexture: - return "ShadowMapTexture"; - case BuiltinPassResourceSemantic::LinearClampSampler: - return "LinearClampSampler"; - case BuiltinPassResourceSemantic::ShadowMapSampler: - return "ShadowMapSampler"; - case BuiltinPassResourceSemantic::Unknown: - default: - return "Unknown"; - } -} - -inline const char* ShaderResourceTypeToString(Resources::ShaderResourceType type) { - switch (type) { - case Resources::ShaderResourceType::ConstantBuffer: - return "ConstantBuffer"; - case Resources::ShaderResourceType::Texture2D: - return "Texture2D"; - case Resources::ShaderResourceType::TextureCube: - return "TextureCube"; - case Resources::ShaderResourceType::Sampler: - return "Sampler"; - default: - return "Unknown"; - } -} - -inline Containers::String DescribeShaderResourceBinding( - const Resources::ShaderResourceBindingDesc& binding) { - const BuiltinPassResourceSemantic resolvedSemantic = ResolveBuiltinPassResourceSemantic(binding); - return Containers::String("name=") + binding.name + - ", semantic=" + binding.semantic + - ", resolvedSemantic=" + Containers::String(BuiltinPassResourceSemanticToString(resolvedSemantic)) + - ", type=" + Containers::String(ShaderResourceTypeToString(binding.type)) + - ", set=" + Containers::String(std::to_string(binding.set).c_str()) + - ", binding=" + Containers::String(std::to_string(binding.binding).c_str()); -} - -inline Containers::String DescribeShaderResourceBindings( - const Containers::Array& bindings) { - Containers::String description; - for (size_t bindingIndex = 0; bindingIndex < bindings.Size(); ++bindingIndex) { - if (!description.Empty()) { - description += " | "; - } - - description += "[" + Containers::String(std::to_string(bindingIndex).c_str()) + "] "; - description += DescribeShaderResourceBinding(bindings[bindingIndex]); - } - - return description; -} - -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(); };