diff --git a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h index fbefce92..773baeb9 100644 --- a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h +++ b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h @@ -16,6 +16,7 @@ #include #include +#include namespace XCEngine { namespace Components { @@ -71,9 +72,85 @@ private: Math::Vector4 baseColorFactor = Math::Vector4::One(); }; - struct CachedMaterialBindings { - OwnedDescriptorSet constantSet = {}; - OwnedDescriptorSet textureSet = {}; + struct DescriptorBindingLocation { + Core::uint32 set = UINT32_MAX; + Core::uint32 binding = UINT32_MAX; + + bool IsValid() const { + return set != UINT32_MAX && binding != UINT32_MAX; + } + }; + + struct PassLayoutKey { + const Resources::Shader* shader = nullptr; + Containers::String passName; + + bool operator==(const PassLayoutKey& other) const { + return shader == other.shader && passName == other.passName; + } + }; + + struct PassLayoutKeyHash { + size_t operator()(const PassLayoutKey& key) const noexcept { + size_t hash = reinterpret_cast(key.shader); + hash ^= std::hash{}(key.passName) + 0x9e3779b9u + (hash << 6) + (hash >> 2); + return hash; + } + }; + + struct PassSetLayoutMetadata { + std::vector bindings; + RHI::DescriptorSetLayoutDesc layout = {}; + RHI::DescriptorHeapType heapType = RHI::DescriptorHeapType::CBV_SRV_UAV; + bool shaderVisible = false; + bool usesPerObject = false; + bool usesMaterial = false; + bool usesTexture = false; + bool usesSampler = false; + }; + + struct PassResourceLayout { + RHI::RHIPipelineLayout* pipelineLayout = nullptr; + Core::uint32 firstDescriptorSet = 0; + Core::uint32 descriptorSetCount = 0; + std::vector setLayouts; + std::vector staticDescriptorSets; + DescriptorBindingLocation perObject = {}; + DescriptorBindingLocation material = {}; + DescriptorBindingLocation baseColorTexture = {}; + DescriptorBindingLocation linearClampSampler = {}; + }; + + struct DynamicDescriptorSetKey { + PassLayoutKey passLayout = {}; + Core::uint32 setIndex = 0; + Core::uint64 objectId = 0; + const Resources::Material* material = nullptr; + + bool operator==(const DynamicDescriptorSetKey& other) const { + return passLayout == other.passLayout && + setIndex == other.setIndex && + objectId == other.objectId && + material == other.material; + } + }; + + struct DynamicDescriptorSetKeyHash { + size_t operator()(const DynamicDescriptorSetKey& key) const noexcept { + size_t hash = PassLayoutKeyHash()(key.passLayout); + auto combine = [&hash](size_t value) { + hash ^= value + 0x9e3779b9u + (hash << 6) + (hash >> 2); + }; + + combine(std::hash{}(key.setIndex)); + combine(std::hash{}(key.objectId)); + combine(reinterpret_cast(key.material)); + return hash; + } + }; + + struct CachedDescriptorSet { + OwnedDescriptorSet descriptorSet = {}; Core::uint64 materialVersion = 0; RHI::RHIResourceView* textureView = nullptr; }; @@ -113,14 +190,29 @@ private: bool CreatePipelineResources(const RenderContext& context); void DestroyPipelineResources(); ResolvedShaderPass ResolveForwardShaderPass(const Resources::Material* material) const; + PassResourceLayout* GetOrCreatePassResourceLayout( + const RenderContext& context, + const ResolvedShaderPass& resolvedShaderPass); RHI::RHIPipelineState* GetOrCreatePipelineState( const RenderContext& context, const Resources::Material* material); - RHI::RHIDescriptorSet* GetOrCreatePerObjectSet(Core::uint64 objectId); - CachedMaterialBindings* GetOrCreateMaterialBindings( + bool CreateOwnedDescriptorSet( + const PassSetLayoutMetadata& setLayout, + OwnedDescriptorSet& descriptorSet); + RHI::RHIDescriptorSet* GetOrCreateStaticDescriptorSet( + PassResourceLayout& passLayout, + Core::uint32 setIndex); + CachedDescriptorSet* GetOrCreateDynamicDescriptorSet( + const PassLayoutKey& passLayoutKey, + const PassResourceLayout& passLayout, + const PassSetLayoutMetadata& setLayout, + Core::uint32 setIndex, + Core::uint64 objectId, const Resources::Material* material, + const PerMaterialConstants& materialConstants, RHI::RHIResourceView* textureView); void DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet); + void DestroyPassResourceLayout(PassResourceLayout& passLayout); const Resources::Texture* ResolveTexture(const Resources::Material* material) const; RHI::RHIResourceView* ResolveTextureView(const VisibleRenderItem& visibleItem); @@ -137,12 +229,9 @@ private: RenderResourceCache m_resourceCache; - RHI::RHIDescriptorPool* m_samplerPool = nullptr; - RHI::RHIDescriptorSet* m_samplerSet = nullptr; - RHI::RHIPipelineLayout* m_pipelineLayout = nullptr; + std::unordered_map m_passResourceLayouts; std::unordered_map m_pipelineStates; - std::unordered_map m_perObjectSets; - std::unordered_map m_materialBindings; + std::unordered_map m_dynamicDescriptorSets; RHI::RHISampler* m_sampler = nullptr; RHI::RHITexture* m_fallbackTexture = nullptr; RHI::RHIResourceView* m_fallbackTextureView = nullptr; diff --git a/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp b/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp index 75804b6d..ba0befa8 100644 --- a/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp +++ b/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp @@ -14,6 +14,7 @@ #include "Resources/Shader/Shader.h" #include "Resources/Texture/Texture.h" +#include #include namespace XCEngine { @@ -51,8 +52,137 @@ private: namespace { -constexpr uint32_t kDescriptorFirstSet = 1; -constexpr uint32_t kDescriptorSetCount = 5; +enum class ForwardPassSemantic : uint8_t { + Unknown = 0, + PerObject, + Material, + BaseColorTexture, + LinearClampSampler +}; + +ForwardPassSemantic ResolveForwardPassSemantic(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 ForwardPassSemantic::PerObject; + } + + if (semantic == Containers::String("material") || + semantic == Containers::String("materialconstants")) { + return ForwardPassSemantic::Material; + } + + if (semantic == Containers::String("basecolortexture") || + semantic == Containers::String("maintex")) { + return ForwardPassSemantic::BaseColorTexture; + } + + if (semantic == Containers::String("linearclampsampler")) { + return ForwardPassSemantic::LinearClampSampler; + } + + return ForwardPassSemantic::Unknown; +} + +RHI::DescriptorType ToDescriptorType(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; + } +} + +RHI::DescriptorHeapType ResolveDescriptorHeapType(Resources::ShaderResourceType type) { + return type == Resources::ShaderResourceType::Sampler + ? RHI::DescriptorHeapType::Sampler + : RHI::DescriptorHeapType::CBV_SRV_UAV; +} + +bool IsShaderVisibleSet(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; +} + +uint32_t CountHeapDescriptors( + RHI::DescriptorHeapType heapType, + const std::vector& bindings) { + uint32_t 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; +} + +bool BindingNumberExists( + const std::vector& bindings, + uint32_t bindingNumber) { + for (const RHI::DescriptorSetLayoutBinding& binding : bindings) { + if (binding.binding == bindingNumber) { + return true; + } + } + + return false; +} + +std::vector BuildLegacyForwardResourceBindings() { + std::vector bindings(4); + + bindings[0].name = "PerObjectConstants"; + bindings[0].type = Resources::ShaderResourceType::ConstantBuffer; + bindings[0].set = 1; + bindings[0].binding = 0; + bindings[0].semantic = "PerObject"; + + bindings[1].name = "MaterialConstants"; + bindings[1].type = Resources::ShaderResourceType::ConstantBuffer; + bindings[1].set = 2; + bindings[1].binding = 0; + bindings[1].semantic = "Material"; + + bindings[2].name = "BaseColorTexture"; + bindings[2].type = Resources::ShaderResourceType::Texture2D; + bindings[2].set = 3; + bindings[2].binding = 0; + bindings[2].semantic = "BaseColorTexture"; + + bindings[3].name = "LinearClampSampler"; + bindings[3].type = Resources::ShaderResourceType::Sampler; + bindings[3].set = 4; + bindings[3].binding = 0; + bindings[3].semantic = "LinearClampSampler"; + + return bindings; +} const Resources::ShaderPass* FindForwardCompatiblePass( const Resources::Shader& shader, @@ -325,80 +455,6 @@ bool BuiltinForwardPipeline::CreatePipelineResources(const RenderContext& contex return false; } - RHI::DescriptorSetLayoutBinding constantBinding = {}; - constantBinding.binding = 0; - constantBinding.type = static_cast(RHI::DescriptorType::CBV); - constantBinding.count = 1; - - RHI::DescriptorSetLayoutDesc constantLayout = {}; - constantLayout.bindings = &constantBinding; - constantLayout.bindingCount = 1; - - RHI::DescriptorSetLayoutDesc materialConstantLayout = {}; - materialConstantLayout.bindings = &constantBinding; - materialConstantLayout.bindingCount = 1; - - RHI::DescriptorSetLayoutBinding textureBinding = {}; - textureBinding.binding = 0; - textureBinding.type = static_cast(RHI::DescriptorType::SRV); - textureBinding.count = 1; - - RHI::DescriptorSetLayoutDesc textureLayout = {}; - textureLayout.bindings = &textureBinding; - textureLayout.bindingCount = 1; - - RHI::DescriptorPoolDesc samplerPoolDesc = {}; - samplerPoolDesc.type = RHI::DescriptorHeapType::Sampler; - samplerPoolDesc.descriptorCount = 1; - samplerPoolDesc.shaderVisible = true; - m_samplerPool = context.device->CreateDescriptorPool(samplerPoolDesc); - if (m_samplerPool == nullptr) { - return false; - } - - RHI::DescriptorSetLayoutBinding samplerBinding = {}; - samplerBinding.binding = 0; - samplerBinding.type = static_cast(RHI::DescriptorType::Sampler); - samplerBinding.count = 1; - - RHI::DescriptorSetLayoutDesc samplerLayout = {}; - samplerLayout.bindings = &samplerBinding; - samplerLayout.bindingCount = 1; - m_samplerSet = m_samplerPool->AllocateSet(samplerLayout); - if (m_samplerSet == nullptr) { - return false; - } - - RHI::DescriptorSetLayoutBinding reservedBindings[3] = {}; - reservedBindings[0].binding = 0; - reservedBindings[0].type = static_cast(RHI::DescriptorType::CBV); - reservedBindings[0].count = 1; - reservedBindings[1].binding = 0; - reservedBindings[1].type = static_cast(RHI::DescriptorType::SRV); - reservedBindings[1].count = 1; - reservedBindings[2].binding = 0; - reservedBindings[2].type = static_cast(RHI::DescriptorType::Sampler); - reservedBindings[2].count = 1; - - RHI::DescriptorSetLayoutDesc reservedLayout = {}; - reservedLayout.bindings = reservedBindings; - reservedLayout.bindingCount = 3; - - RHI::DescriptorSetLayoutDesc setLayouts[kDescriptorSetCount] = {}; - setLayouts[0] = reservedLayout; - setLayouts[1] = constantLayout; - setLayouts[2] = materialConstantLayout; - setLayouts[3] = textureLayout; - setLayouts[4] = samplerLayout; - - RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {}; - pipelineLayoutDesc.setLayouts = setLayouts; - pipelineLayoutDesc.setLayoutCount = kDescriptorSetCount; - m_pipelineLayout = context.device->CreatePipelineLayout(pipelineLayoutDesc); - if (m_pipelineLayout == nullptr) { - return false; - } - RHI::SamplerDesc samplerDesc = {}; samplerDesc.filter = static_cast(RHI::FilterMode::Linear); samplerDesc.addressU = static_cast(RHI::TextureAddressMode::Clamp); @@ -413,7 +469,6 @@ bool BuiltinForwardPipeline::CreatePipelineResources(const RenderContext& contex if (m_sampler == nullptr) { return false; } - m_samplerSet->UpdateSampler(0, m_sampler); const unsigned char whitePixel[4] = { 255, 255, 255, 255 }; RHI::TextureDesc textureDesc = {}; @@ -455,16 +510,15 @@ void BuiltinForwardPipeline::DestroyPipelineResources() { } m_pipelineStates.clear(); - for (auto& perObjectSetPair : m_perObjectSets) { - DestroyOwnedDescriptorSet(perObjectSetPair.second); + for (auto& descriptorSetPair : m_dynamicDescriptorSets) { + DestroyOwnedDescriptorSet(descriptorSetPair.second.descriptorSet); } - m_perObjectSets.clear(); + m_dynamicDescriptorSets.clear(); - for (auto& materialBindingPair : m_materialBindings) { - DestroyOwnedDescriptorSet(materialBindingPair.second.constantSet); - DestroyOwnedDescriptorSet(materialBindingPair.second.textureSet); + for (auto& passLayoutPair : m_passResourceLayouts) { + DestroyPassResourceLayout(passLayoutPair.second); } - m_materialBindings.clear(); + m_passResourceLayouts.clear(); if (m_fallbackTextureView != nullptr) { m_fallbackTextureView->Shutdown(); @@ -484,24 +538,6 @@ void BuiltinForwardPipeline::DestroyPipelineResources() { m_sampler = nullptr; } - if (m_pipelineLayout != nullptr) { - m_pipelineLayout->Shutdown(); - delete m_pipelineLayout; - m_pipelineLayout = nullptr; - } - - if (m_samplerSet != nullptr) { - m_samplerSet->Shutdown(); - delete m_samplerSet; - m_samplerSet = nullptr; - } - - if (m_samplerPool != nullptr) { - m_samplerPool->Shutdown(); - delete m_samplerPool; - m_samplerPool = nullptr; - } - m_device = nullptr; m_initialized = false; m_builtinForwardShader.Reset(); @@ -536,6 +572,214 @@ BuiltinForwardPipeline::ResolvedShaderPass BuiltinForwardPipeline::ResolveForwar return resolved; } +BuiltinForwardPipeline::PassResourceLayout* BuiltinForwardPipeline::GetOrCreatePassResourceLayout( + const RenderContext& context, + const ResolvedShaderPass& resolvedShaderPass) { + if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { + return nullptr; + } + + PassLayoutKey passLayoutKey = {}; + passLayoutKey.shader = resolvedShaderPass.shader; + passLayoutKey.passName = resolvedShaderPass.passName; + + const auto existing = m_passResourceLayouts.find(passLayoutKey); + if (existing != m_passResourceLayouts.end()) { + return &existing->second; + } + + std::vector resourceBindings; + if (resolvedShaderPass.pass->resources.Empty()) { + resourceBindings = BuildLegacyForwardResourceBindings(); + } else { + resourceBindings.reserve(resolvedShaderPass.pass->resources.Size()); + for (const Resources::ShaderResourceBindingDesc& binding : resolvedShaderPass.pass->resources) { + resourceBindings.push_back(binding); + } + } + + PassResourceLayout passLayout = {}; + auto failLayout = [this, &passLayout](const char* message) -> PassResourceLayout* { + Debug::Logger::Get().Error(Debug::LogCategory::Rendering, message); + DestroyPassResourceLayout(passLayout); + return nullptr; + }; + + Core::uint32 minBoundSet = UINT32_MAX; + Core::uint32 maxBoundSet = 0; + Core::uint32 maxSetIndex = 0; + bool hasAnyResource = false; + bool usesConstantBuffers = false; + bool usesTextures = false; + bool usesSamplers = false; + for (const Resources::ShaderResourceBindingDesc& binding : resourceBindings) { + maxSetIndex = std::max(maxSetIndex, binding.set); + hasAnyResource = true; + minBoundSet = std::min(minBoundSet, binding.set); + maxBoundSet = std::max(maxBoundSet, binding.set); + + switch (binding.type) { + case Resources::ShaderResourceType::ConstantBuffer: + usesConstantBuffers = true; + break; + case Resources::ShaderResourceType::Texture2D: + case Resources::ShaderResourceType::TextureCube: + usesTextures = true; + break; + case Resources::ShaderResourceType::Sampler: + usesSamplers = true; + break; + default: + break; + } + } + + if (hasAnyResource) { + passLayout.setLayouts.resize(static_cast(maxSetIndex) + 1u); + passLayout.staticDescriptorSets.resize(passLayout.setLayouts.size()); + passLayout.firstDescriptorSet = minBoundSet; + passLayout.descriptorSetCount = maxBoundSet - minBoundSet + 1u; + } + + for (const Resources::ShaderResourceBindingDesc& binding : resourceBindings) { + ForwardPassSemantic semantic = ResolveForwardPassSemantic(binding); + if (semantic == ForwardPassSemantic::Unknown) { + return failLayout("BuiltinForwardPipeline encountered an unsupported forward shader resource semantic"); + } + + if (binding.set >= passLayout.setLayouts.size()) { + return failLayout("BuiltinForwardPipeline encountered an invalid forward shader resource set"); + } + + const RHI::DescriptorType descriptorType = ToDescriptorType(binding.type); + const RHI::DescriptorHeapType heapType = ResolveDescriptorHeapType(binding.type); + PassSetLayoutMetadata& setLayout = passLayout.setLayouts[binding.set]; + + if (!setLayout.bindings.empty() && setLayout.heapType != heapType) { + return failLayout("BuiltinForwardPipeline does not support mixing sampler and non-sampler bindings in one set"); + } + + if (BindingNumberExists(setLayout.bindings, binding.binding)) { + return failLayout("BuiltinForwardPipeline encountered duplicate bindings inside one descriptor set"); + } + + if (setLayout.bindings.empty()) { + setLayout.heapType = heapType; + } + + RHI::DescriptorSetLayoutBinding layoutBinding = {}; + layoutBinding.binding = binding.binding; + layoutBinding.type = static_cast(descriptorType); + layoutBinding.count = 1; + setLayout.bindings.push_back(layoutBinding); + + switch (semantic) { + case ForwardPassSemantic::PerObject: + if (binding.type != Resources::ShaderResourceType::ConstantBuffer || passLayout.perObject.IsValid()) { + return failLayout("BuiltinForwardPipeline requires a single constant-buffer PerObject resource"); + } + passLayout.perObject = { binding.set, binding.binding }; + setLayout.usesPerObject = true; + break; + case ForwardPassSemantic::Material: + if (binding.type != Resources::ShaderResourceType::ConstantBuffer || passLayout.material.IsValid()) { + return failLayout("BuiltinForwardPipeline requires a single constant-buffer Material resource"); + } + passLayout.material = { binding.set, binding.binding }; + setLayout.usesMaterial = true; + break; + case ForwardPassSemantic::BaseColorTexture: + if ((binding.type != Resources::ShaderResourceType::Texture2D && + binding.type != Resources::ShaderResourceType::TextureCube) || + passLayout.baseColorTexture.IsValid()) { + return failLayout("BuiltinForwardPipeline requires a single texture BaseColorTexture resource"); + } + passLayout.baseColorTexture = { binding.set, binding.binding }; + setLayout.usesTexture = true; + break; + case ForwardPassSemantic::LinearClampSampler: + if (binding.type != Resources::ShaderResourceType::Sampler || passLayout.linearClampSampler.IsValid()) { + return failLayout("BuiltinForwardPipeline requires a single sampler LinearClampSampler resource"); + } + passLayout.linearClampSampler = { binding.set, binding.binding }; + setLayout.usesSampler = true; + break; + default: + return failLayout("BuiltinForwardPipeline encountered an unsupported forward shader resource semantic"); + } + } + + if (!passLayout.perObject.IsValid()) { + return failLayout("BuiltinForwardPipeline requires a PerObject resource binding"); + } + + if (hasAnyResource && + passLayout.firstDescriptorSet > 0 && + !passLayout.setLayouts.empty() && + passLayout.setLayouts[0].bindings.empty()) { + PassSetLayoutMetadata& compatibilitySet = passLayout.setLayouts[0]; + if (usesConstantBuffers) { + compatibilitySet.bindings.push_back({ + 0, + static_cast(RHI::DescriptorType::CBV), + 1, + 0 + }); + } + if (usesTextures) { + compatibilitySet.bindings.push_back({ + 0, + static_cast(RHI::DescriptorType::SRV), + 1, + 0 + }); + } + if (usesSamplers) { + compatibilitySet.bindings.push_back({ + 0, + static_cast(RHI::DescriptorType::Sampler), + 1, + 0 + }); + } + compatibilitySet.shaderVisible = true; + } + + for (Core::uint32 setIndex = 0; setIndex < passLayout.setLayouts.size(); ++setIndex) { + PassSetLayoutMetadata& setLayout = passLayout.setLayouts[setIndex]; + std::sort( + setLayout.bindings.begin(), + setLayout.bindings.end(), + [](const RHI::DescriptorSetLayoutBinding& left, const RHI::DescriptorSetLayoutBinding& right) { + return left.binding < right.binding; + }); + setLayout.shaderVisible = IsShaderVisibleSet(setLayout.bindings); + setLayout.layout.bindings = setLayout.bindings.empty() ? nullptr : setLayout.bindings.data(); + setLayout.layout.bindingCount = static_cast(setLayout.bindings.size()); + } + + std::vector nativeSetLayouts(passLayout.setLayouts.size()); + for (size_t i = 0; i < passLayout.setLayouts.size(); ++i) { + nativeSetLayouts[i] = passLayout.setLayouts[i].layout; + } + + RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {}; + pipelineLayoutDesc.setLayouts = nativeSetLayouts.empty() ? nullptr : nativeSetLayouts.data(); + pipelineLayoutDesc.setLayoutCount = static_cast(nativeSetLayouts.size()); + passLayout.pipelineLayout = context.device->CreatePipelineLayout(pipelineLayoutDesc); + if (passLayout.pipelineLayout == nullptr) { + return failLayout("BuiltinForwardPipeline failed to create a pipeline layout from shader pass resources"); + } + + const auto result = m_passResourceLayouts.emplace(passLayoutKey, passLayout); + PassResourceLayout& storedPassLayout = result.first->second; + for (PassSetLayoutMetadata& setLayout : storedPassLayout.setLayouts) { + setLayout.layout.bindings = setLayout.bindings.empty() ? nullptr : setLayout.bindings.data(); + setLayout.layout.bindingCount = static_cast(setLayout.bindings.size()); + } + return &storedPassLayout; +} + RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState( const RenderContext& context, const Resources::Material* material) { @@ -547,6 +791,11 @@ RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState( return nullptr; } + PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass); + if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) { + return nullptr; + } + PipelineStateKey pipelineKey = {}; pipelineKey.renderState = material != nullptr ? material->GetRenderState() : Resources::MaterialRenderState(); @@ -561,7 +810,7 @@ RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState( const RHI::GraphicsPipelineDesc pipelineDesc = CreatePipelineDesc( context.backendType, - m_pipelineLayout, + passLayout->pipelineLayout, *resolvedShaderPass.shader, resolvedShaderPass.passName, material); @@ -581,117 +830,107 @@ RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState( return pipelineState; } -RHI::RHIDescriptorSet* BuiltinForwardPipeline::GetOrCreatePerObjectSet(Core::uint64 objectId) { - const auto existing = m_perObjectSets.find(objectId); - if (existing != m_perObjectSets.end()) { - return existing->second.set; - } - +bool BuiltinForwardPipeline::CreateOwnedDescriptorSet( + const PassSetLayoutMetadata& setLayout, + OwnedDescriptorSet& descriptorSet) { RHI::DescriptorPoolDesc poolDesc = {}; - poolDesc.type = RHI::DescriptorHeapType::CBV_SRV_UAV; - poolDesc.descriptorCount = 1; - poolDesc.shaderVisible = false; + poolDesc.type = setLayout.heapType; + poolDesc.descriptorCount = CountHeapDescriptors(setLayout.heapType, setLayout.bindings); + poolDesc.shaderVisible = setLayout.shaderVisible; - OwnedDescriptorSet descriptorSet = {}; descriptorSet.pool = m_device->CreateDescriptorPool(poolDesc); if (descriptorSet.pool == nullptr) { - return nullptr; + return false; } - RHI::DescriptorSetLayoutBinding binding = {}; - binding.binding = 0; - binding.type = static_cast(RHI::DescriptorType::CBV); - binding.count = 1; - - RHI::DescriptorSetLayoutDesc layout = {}; - layout.bindings = &binding; - layout.bindingCount = 1; - descriptorSet.set = descriptorSet.pool->AllocateSet(layout); + descriptorSet.set = descriptorSet.pool->AllocateSet(setLayout.layout); if (descriptorSet.set == nullptr) { DestroyOwnedDescriptorSet(descriptorSet); - return nullptr; + return false; } - const auto result = m_perObjectSets.emplace(objectId, descriptorSet); - return result.first->second.set; + return true; } -BuiltinForwardPipeline::CachedMaterialBindings* BuiltinForwardPipeline::GetOrCreateMaterialBindings( - const Resources::Material* material, - RHI::RHIResourceView* textureView) { - if (textureView == nullptr) { +RHI::RHIDescriptorSet* BuiltinForwardPipeline::GetOrCreateStaticDescriptorSet( + PassResourceLayout& passLayout, + Core::uint32 setIndex) { + if (setIndex >= passLayout.setLayouts.size() || + setIndex >= passLayout.staticDescriptorSets.size()) { return nullptr; } - CachedMaterialBindings& cachedBindings = m_materialBindings[material]; - - if (cachedBindings.constantSet.set == nullptr) { - RHI::DescriptorPoolDesc constantPoolDesc = {}; - constantPoolDesc.type = RHI::DescriptorHeapType::CBV_SRV_UAV; - constantPoolDesc.descriptorCount = 1; - constantPoolDesc.shaderVisible = false; - - cachedBindings.constantSet.pool = m_device->CreateDescriptorPool(constantPoolDesc); - if (cachedBindings.constantSet.pool == nullptr) { + OwnedDescriptorSet& descriptorSet = passLayout.staticDescriptorSets[setIndex]; + if (descriptorSet.set == nullptr) { + if (!CreateOwnedDescriptorSet(passLayout.setLayouts[setIndex], descriptorSet)) { return nullptr; } - RHI::DescriptorSetLayoutBinding constantBinding = {}; - constantBinding.binding = 0; - constantBinding.type = static_cast(RHI::DescriptorType::CBV); - constantBinding.count = 1; + if (passLayout.setLayouts[setIndex].usesSampler) { + if (m_sampler == nullptr || + !passLayout.linearClampSampler.IsValid() || + passLayout.linearClampSampler.set != setIndex) { + DestroyOwnedDescriptorSet(descriptorSet); + return nullptr; + } - RHI::DescriptorSetLayoutDesc constantLayout = {}; - constantLayout.bindings = &constantBinding; - constantLayout.bindingCount = 1; - cachedBindings.constantSet.set = cachedBindings.constantSet.pool->AllocateSet(constantLayout); - if (cachedBindings.constantSet.set == nullptr) { - DestroyOwnedDescriptorSet(cachedBindings.constantSet); - return nullptr; + descriptorSet.set->UpdateSampler(passLayout.linearClampSampler.binding, m_sampler); } } - if (cachedBindings.textureSet.set == nullptr) { - RHI::DescriptorPoolDesc texturePoolDesc = {}; - texturePoolDesc.type = RHI::DescriptorHeapType::CBV_SRV_UAV; - texturePoolDesc.descriptorCount = 1; - texturePoolDesc.shaderVisible = true; + return descriptorSet.set; +} - cachedBindings.textureSet.pool = m_device->CreateDescriptorPool(texturePoolDesc); - if (cachedBindings.textureSet.pool == nullptr) { - DestroyOwnedDescriptorSet(cachedBindings.constantSet); - return nullptr; - } +BuiltinForwardPipeline::CachedDescriptorSet* BuiltinForwardPipeline::GetOrCreateDynamicDescriptorSet( + const PassLayoutKey& passLayoutKey, + const PassResourceLayout& passLayout, + const PassSetLayoutMetadata& setLayout, + Core::uint32 setIndex, + Core::uint64 objectId, + const Resources::Material* material, + const PerMaterialConstants& materialConstants, + RHI::RHIResourceView* textureView) { + DynamicDescriptorSetKey key = {}; + key.passLayout = passLayoutKey; + key.setIndex = setIndex; + key.objectId = objectId; + key.material = material; - RHI::DescriptorSetLayoutBinding textureBinding = {}; - textureBinding.binding = 0; - textureBinding.type = static_cast(RHI::DescriptorType::SRV); - textureBinding.count = 1; - - RHI::DescriptorSetLayoutDesc textureLayout = {}; - textureLayout.bindings = &textureBinding; - textureLayout.bindingCount = 1; - cachedBindings.textureSet.set = cachedBindings.textureSet.pool->AllocateSet(textureLayout); - if (cachedBindings.textureSet.set == nullptr) { - DestroyOwnedDescriptorSet(cachedBindings.textureSet); + CachedDescriptorSet& cachedDescriptorSet = m_dynamicDescriptorSets[key]; + if (cachedDescriptorSet.descriptorSet.set == nullptr) { + if (!CreateOwnedDescriptorSet(setLayout, cachedDescriptorSet.descriptorSet)) { return nullptr; } } const Core::uint64 materialVersion = material != nullptr ? material->GetChangeVersion() : 0; - if (cachedBindings.materialVersion != materialVersion || cachedBindings.textureView != textureView) { - const BuiltinForwardMaterialData materialData = BuildBuiltinForwardMaterialData(material); - const PerMaterialConstants materialConstants = { - materialData.baseColorFactor - }; + if ((setLayout.usesMaterial || setLayout.usesTexture) && + (cachedDescriptorSet.materialVersion != materialVersion || + cachedDescriptorSet.textureView != textureView)) { + if (setLayout.usesMaterial) { + if (!passLayout.material.IsValid() || passLayout.material.set != setIndex) { + return nullptr; + } + cachedDescriptorSet.descriptorSet.set->WriteConstant( + passLayout.material.binding, + &materialConstants, + sizeof(materialConstants)); + } - cachedBindings.constantSet.set->WriteConstant(0, &materialConstants, sizeof(materialConstants)); - cachedBindings.textureSet.set->Update(0, textureView); - cachedBindings.materialVersion = materialVersion; - cachedBindings.textureView = textureView; + if (setLayout.usesTexture) { + if (textureView == nullptr || + !passLayout.baseColorTexture.IsValid() || + passLayout.baseColorTexture.set != setIndex) { + return nullptr; + } + cachedDescriptorSet.descriptorSet.set->Update(passLayout.baseColorTexture.binding, textureView); + } + + cachedDescriptorSet.materialVersion = materialVersion; + cachedDescriptorSet.textureView = textureView; } - return &cachedBindings; + return &cachedDescriptorSet; } void BuiltinForwardPipeline::DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet) { @@ -708,6 +947,27 @@ void BuiltinForwardPipeline::DestroyOwnedDescriptorSet(OwnedDescriptorSet& descr } } +void BuiltinForwardPipeline::DestroyPassResourceLayout(PassResourceLayout& passLayout) { + for (OwnedDescriptorSet& descriptorSet : passLayout.staticDescriptorSets) { + DestroyOwnedDescriptorSet(descriptorSet); + } + passLayout.staticDescriptorSets.clear(); + + if (passLayout.pipelineLayout != nullptr) { + passLayout.pipelineLayout->Shutdown(); + delete passLayout.pipelineLayout; + passLayout.pipelineLayout = nullptr; + } + + passLayout.setLayouts.clear(); + passLayout.firstDescriptorSet = 0; + passLayout.descriptorSetCount = 0; + passLayout.perObject = {}; + passLayout.material = {}; + passLayout.baseColorTexture = {}; + passLayout.linearClampSampler = {}; +} + const Resources::Texture* BuiltinForwardPipeline::ResolveTexture(const Resources::Material* material) const { return ResolveBuiltinBaseColorTexture(material); } @@ -766,31 +1026,91 @@ bool BuiltinForwardPipeline::DrawVisibleItem( : Math::Vector4::Zero() }; - RHI::RHIResourceView* textureView = ResolveTextureView(visibleItem); - if (textureView == nullptr) { - return false; - } - const Resources::Material* material = ResolveMaterial(visibleItem); - RHI::RHIDescriptorSet* constantSet = GetOrCreatePerObjectSet( - visibleItem.gameObject != nullptr ? visibleItem.gameObject->GetID() : 0); - CachedMaterialBindings* materialBindings = GetOrCreateMaterialBindings(material, textureView); - if (constantSet == nullptr || - materialBindings == nullptr || - materialBindings->constantSet.set == nullptr || - materialBindings->textureSet.set == nullptr || - m_samplerSet == nullptr) { + const ResolvedShaderPass resolvedShaderPass = ResolveForwardShaderPass(material); + if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { return false; } - constantSet->WriteConstant(0, &constants, sizeof(constants)); - RHI::RHIDescriptorSet* descriptorSets[] = { - constantSet, - materialBindings->constantSet.set, - materialBindings->textureSet.set, - m_samplerSet + PassLayoutKey passLayoutKey = {}; + passLayoutKey.shader = resolvedShaderPass.shader; + passLayoutKey.passName = resolvedShaderPass.passName; + + PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass); + if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) { + return false; + } + + RHI::RHIResourceView* textureView = ResolveTextureView(visibleItem); + if (passLayout->baseColorTexture.IsValid() && textureView == nullptr) { + return false; + } + + const BuiltinForwardMaterialData materialData = BuildBuiltinForwardMaterialData(material); + const PerMaterialConstants materialConstants = { + materialData.baseColorFactor }; - commandList->SetGraphicsDescriptorSets(kDescriptorFirstSet, 4, descriptorSets, m_pipelineLayout); + + if (passLayout->descriptorSetCount > 0) { + std::vector descriptorSets(passLayout->descriptorSetCount, nullptr); + + for (Core::uint32 descriptorOffset = 0; descriptorOffset < passLayout->descriptorSetCount; ++descriptorOffset) { + const Core::uint32 setIndex = passLayout->firstDescriptorSet + descriptorOffset; + if (setIndex >= passLayout->setLayouts.size()) { + return false; + } + + const PassSetLayoutMetadata& setLayout = passLayout->setLayouts[setIndex]; + RHI::RHIDescriptorSet* descriptorSet = nullptr; + + if (setLayout.usesPerObject || setLayout.usesMaterial || setLayout.usesTexture) { + const Core::uint64 objectId = + (setLayout.usesPerObject && visibleItem.gameObject != nullptr) + ? visibleItem.gameObject->GetID() + : 0; + const Resources::Material* materialKey = + (setLayout.usesMaterial || setLayout.usesTexture) ? material : nullptr; + + CachedDescriptorSet* cachedDescriptorSet = GetOrCreateDynamicDescriptorSet( + passLayoutKey, + *passLayout, + setLayout, + setIndex, + objectId, + materialKey, + materialConstants, + textureView); + if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) { + return false; + } + + descriptorSet = cachedDescriptorSet->descriptorSet.set; + if (setLayout.usesPerObject) { + if (!passLayout->perObject.IsValid() || passLayout->perObject.set != setIndex) { + return false; + } + + descriptorSet->WriteConstant( + passLayout->perObject.binding, + &constants, + sizeof(constants)); + } + } else { + descriptorSet = GetOrCreateStaticDescriptorSet(*passLayout, setIndex); + if (descriptorSet == nullptr) { + return false; + } + } + + descriptorSets[descriptorOffset] = descriptorSet; + } + + commandList->SetGraphicsDescriptorSets( + passLayout->firstDescriptorSet, + passLayout->descriptorSetCount, + descriptorSets.data(), + passLayout->pipelineLayout); + } if (visibleItem.hasSection) { const Containers::Array& sections = visibleItem.mesh->GetSections(); diff --git a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp index 376bff62..1ff0f62a 100644 --- a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp +++ b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp @@ -2,7 +2,10 @@ #include #include +#include #include +#include +#include #include using namespace XCEngine::Rendering::Pipelines; @@ -37,6 +40,42 @@ TEST(BuiltinForwardPipeline_Test, UsesFloat3PositionInputLayoutForStaticMeshVert EXPECT_EQ(texcoord.alignedByteOffset, static_cast(offsetof(StaticMeshVertex, uv0))); } +TEST(BuiltinForwardPipeline_Test, BuiltinForwardShaderDeclaresExplicitForwardResourceContract) { + 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); + ASSERT_EQ(pass->resources.Size(), 4u); + + EXPECT_EQ(pass->resources[0].semantic, "PerObject"); + EXPECT_EQ(pass->resources[0].type, ShaderResourceType::ConstantBuffer); + EXPECT_EQ(pass->resources[0].set, 1u); + EXPECT_EQ(pass->resources[0].binding, 0u); + + EXPECT_EQ(pass->resources[1].semantic, "Material"); + EXPECT_EQ(pass->resources[1].type, ShaderResourceType::ConstantBuffer); + EXPECT_EQ(pass->resources[1].set, 2u); + EXPECT_EQ(pass->resources[1].binding, 0u); + + EXPECT_EQ(pass->resources[2].semantic, "BaseColorTexture"); + EXPECT_EQ(pass->resources[2].type, ShaderResourceType::Texture2D); + EXPECT_EQ(pass->resources[2].set, 3u); + EXPECT_EQ(pass->resources[2].binding, 0u); + + EXPECT_EQ(pass->resources[3].semantic, "LinearClampSampler"); + EXPECT_EQ(pass->resources[3].type, ShaderResourceType::Sampler); + EXPECT_EQ(pass->resources[3].set, 4u); + EXPECT_EQ(pass->resources[3].binding, 0u); + + delete shader; +} + TEST(BuiltinObjectIdPass_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) { const InputLayoutDesc inputLayout = BuiltinObjectIdPass::BuildInputLayout();