From a3ba08bb990386ee04a25e673b94b9ad9e791721 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sat, 4 Apr 2026 13:48:13 +0800 Subject: [PATCH] Share builtin pass layout assembly utilities --- docs/plan/Shader与Material系统下一阶段计划.md | 13 +- .../Rendering/Passes/BuiltinObjectIdPass.h | 1 + .../Pipelines/BuiltinForwardPipeline.h | 17 +- .../Rendering/RenderMaterialUtility.h | 154 ++++++++++++++++++ .../Rendering/Passes/BuiltinObjectIdPass.cpp | 58 ++++--- .../Pipelines/BuiltinForwardPipeline.cpp | 146 ++--------------- .../unit/test_builtin_forward_pipeline.cpp | 119 +++++++++++++- 7 files changed, 328 insertions(+), 180 deletions(-) diff --git a/docs/plan/Shader与Material系统下一阶段计划.md b/docs/plan/Shader与Material系统下一阶段计划.md index 3a55c7e5..72cb9412 100644 --- a/docs/plan/Shader与Material系统下一阶段计划.md +++ b/docs/plan/Shader与Material系统下一阶段计划.md @@ -336,7 +336,7 @@ Unity-like Shader Authoring (.shader) - 至少 `ForwardLit + Unlit + ObjectId` 共用同一套 shader/material 执行边界 - 新增 pass 不再默认要求先写一套新的硬编码 binding 路径 -当前进展(`2026-04-03`): +当前进展(`2026-04-04`): - 已完成:builtin `ObjectId` pass 接入通用 pass binding plan - builtin object-id shader 已显式声明 `PerObject` 资源合约 @@ -346,8 +346,15 @@ Unity-like Shader Authoring (.shader) - 新增 builtin `unlit` shader 资产与 `BuiltinResources` 入口 - `BuiltinForwardPipeline` 现在会在 `ForwardLit + Unlit` 之间按 material/shader metadata 解析目标 pass - `Unlit` 与 `ForwardLit` 现在共用同一套 input layout、material schema、binding plan 与 descriptor 组装路径 -- 已验证:`rendering_unit_tests` 61/61,`shader_tests` 27/27,`material_tests` 51/51 -- 下一步:评估是否抽出 `ForwardLit / Unlit / ObjectId` 共用的 pass layout 构建与 descriptor set 组装骨架,并继续推进 `DepthOnly / ShadowCaster` +- 已完成:抽出 `ForwardLit / Unlit / ObjectId` 共用的 pass layout / descriptor set 组装骨架 + - `RenderMaterialUtility` 现在统一提供 `BuiltinPassSetLayoutMetadata` 与 `TryBuildBuiltinPassSetLayouts(...)` + - `BuiltinForwardPipeline` 与 `BuiltinObjectIdPass` 现在都复用同一套 `binding plan -> set layout -> pipeline layout` 组装路径 + - forward 侧只保留 set0 compatibility fallback 与 draw/update 逻辑;object-id 侧只保留自身约束校验与 per-object 常量写入 +- 已验证:`rendering_unit_tests` 65/65 +- 已验证:`rendering_integration_textured_quad_scene` 3/3(D3D12 / OpenGL / Vulkan) +- 已验证:`rendering_integration_unlit_scene` 3/3(D3D12 / OpenGL / Vulkan) +- 已验证:`rendering_integration_object_id_scene` 3/3(D3D12 / OpenGL / Vulkan) +- 下一步:如果继续沿 renderer 主线收口,优先把 `DepthOnly / ShadowCaster` 接到同一套 shared pass-layout skeleton;如果先控制范围,当前 `ForwardLit / Unlit / ObjectId` 已可以视为这一小阶段完成 ### 阶段 D:扩展 AssetDatabase / Library Artifact 能力 diff --git a/engine/include/XCEngine/Rendering/Passes/BuiltinObjectIdPass.h b/engine/include/XCEngine/Rendering/Passes/BuiltinObjectIdPass.h index fb42762c..462fbc3f 100644 --- a/engine/include/XCEngine/Rendering/Passes/BuiltinObjectIdPass.h +++ b/engine/include/XCEngine/Rendering/Passes/BuiltinObjectIdPass.h @@ -56,6 +56,7 @@ private: RHI::RHIPipelineLayout* m_pipelineLayout = nullptr; RHI::RHIPipelineState* m_pipelineState = nullptr; PassResourceBindingLocation m_perObjectBinding = {}; + BuiltinPassSetLayoutMetadata m_perObjectSetLayout = {}; Core::uint32 m_firstDescriptorSet = 0; Resources::ResourceHandle m_builtinObjectIdShader; RenderResourceCache m_resourceCache; diff --git a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h index 2ba7d952..394dfa79 100644 --- a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h +++ b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h @@ -89,22 +89,11 @@ private: } }; - 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 setLayouts; std::vector staticDescriptorSets; PassResourceBindingLocation perObject = {}; PassResourceBindingLocation material = {}; @@ -188,7 +177,7 @@ private: const RenderContext& context, const Resources::Material* material); bool CreateOwnedDescriptorSet( - const PassSetLayoutMetadata& setLayout, + const BuiltinPassSetLayoutMetadata& setLayout, OwnedDescriptorSet& descriptorSet); RHI::RHIDescriptorSet* GetOrCreateStaticDescriptorSet( PassResourceLayout& passLayout, @@ -196,7 +185,7 @@ private: CachedDescriptorSet* GetOrCreateDynamicDescriptorSet( const PassLayoutKey& passLayoutKey, const PassResourceLayout& passLayout, - const PassSetLayoutMetadata& setLayout, + const BuiltinPassSetLayoutMetadata& setLayout, Core::uint32 setIndex, Core::uint64 objectId, const Resources::Material* material, diff --git a/engine/include/XCEngine/Rendering/RenderMaterialUtility.h b/engine/include/XCEngine/Rendering/RenderMaterialUtility.h index cb301288..49cd944c 100644 --- a/engine/include/XCEngine/Rendering/RenderMaterialUtility.h +++ b/engine/include/XCEngine/Rendering/RenderMaterialUtility.h @@ -10,6 +10,7 @@ #include #include #include +#include namespace XCEngine { namespace Rendering { @@ -70,6 +71,17 @@ struct BuiltinPassResourceBindingPlan { } }; +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 usesTexture = false; + bool usesSampler = false; +}; + inline Containers::String NormalizeBuiltinPassMetadataValue(const Containers::String& value) { return value.Trim().ToLower(); } @@ -356,6 +368,148 @@ inline bool TryBuildBuiltinPassResourceBindingPlan( 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::BaseColorTexture: + setLayout.usesTexture = true; + break; + case BuiltinPassResourceSemantic::LinearClampSampler: + setLayout.usesSampler = 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(); }; diff --git a/engine/src/Rendering/Passes/BuiltinObjectIdPass.cpp b/engine/src/Rendering/Passes/BuiltinObjectIdPass.cpp index c0981131..c10f78b6 100644 --- a/engine/src/Rendering/Passes/BuiltinObjectIdPass.cpp +++ b/engine/src/Rendering/Passes/BuiltinObjectIdPass.cpp @@ -274,24 +274,33 @@ bool BuiltinObjectIdPass::CreateResources(const RenderContext& context) { m_perObjectBinding = bindingPlan.perObject; m_firstDescriptorSet = bindingPlan.firstDescriptorSet; - std::vector> setBindingStorage( - static_cast(bindingPlan.maxSetIndex) + 1u); - RHI::DescriptorSetLayoutBinding constantBinding = {}; - constantBinding.binding = m_perObjectBinding.binding; - constantBinding.type = static_cast(RHI::DescriptorType::CBV); - constantBinding.count = 1; - setBindingStorage[m_perObjectBinding.set].push_back(constantBinding); + std::vector setLayouts; + Containers::String setLayoutError; + if (!TryBuildBuiltinPassSetLayouts(bindingPlan, setLayouts, &setLayoutError)) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + (Containers::String("BuiltinObjectIdPass failed to build descriptor set layouts: ") + setLayoutError).CStr()); + DestroyResources(); + return false; + } + if (m_perObjectBinding.set >= setLayouts.size()) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinObjectIdPass produced an invalid PerObject descriptor set index"); + DestroyResources(); + return false; + } + m_perObjectSetLayout = setLayouts[m_perObjectBinding.set]; + RefreshBuiltinPassSetLayoutMetadata(m_perObjectSetLayout); - std::vector setLayouts(setBindingStorage.size()); - for (size_t setIndex = 0; setIndex < setBindingStorage.size(); ++setIndex) { - setLayouts[setIndex].bindings = - setBindingStorage[setIndex].empty() ? nullptr : setBindingStorage[setIndex].data(); - setLayouts[setIndex].bindingCount = static_cast(setBindingStorage[setIndex].size()); + std::vector nativeSetLayouts(setLayouts.size()); + for (size_t setIndex = 0; setIndex < setLayouts.size(); ++setIndex) { + nativeSetLayouts[setIndex] = setLayouts[setIndex].layout; } RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {}; - pipelineLayoutDesc.setLayouts = setLayouts.empty() ? nullptr : setLayouts.data(); - pipelineLayoutDesc.setLayoutCount = static_cast(setLayouts.size()); + pipelineLayoutDesc.setLayouts = nativeSetLayouts.empty() ? nullptr : nativeSetLayouts.data(); + pipelineLayoutDesc.setLayoutCount = static_cast(nativeSetLayouts.size()); m_pipelineLayout = m_device->CreatePipelineLayout(pipelineLayoutDesc); if (m_pipelineLayout == nullptr) { DestroyResources(); @@ -340,12 +349,14 @@ void BuiltinObjectIdPass::DestroyResources() { m_device = nullptr; m_backendType = RHI::RHIType::D3D12; m_perObjectBinding = {}; + m_perObjectSetLayout = {}; m_firstDescriptorSet = 0; m_builtinObjectIdShader.Reset(); } RHI::RHIDescriptorSet* BuiltinObjectIdPass::GetOrCreatePerObjectSet(uint64_t objectId) { - if (m_perObjectBinding.IsValid() == false) { + if (m_perObjectBinding.IsValid() == false || + m_perObjectSetLayout.layout.bindingCount == 0) { return nullptr; } @@ -355,9 +366,10 @@ RHI::RHIDescriptorSet* BuiltinObjectIdPass::GetOrCreatePerObjectSet(uint64_t obj } RHI::DescriptorPoolDesc poolDesc = {}; - poolDesc.type = RHI::DescriptorHeapType::CBV_SRV_UAV; - poolDesc.descriptorCount = 1; - poolDesc.shaderVisible = false; + poolDesc.type = m_perObjectSetLayout.heapType; + poolDesc.descriptorCount = + CountBuiltinPassHeapDescriptors(m_perObjectSetLayout.heapType, m_perObjectSetLayout.bindings); + poolDesc.shaderVisible = m_perObjectSetLayout.shaderVisible; OwnedDescriptorSet descriptorSet = {}; descriptorSet.pool = m_device->CreateDescriptorPool(poolDesc); @@ -365,15 +377,7 @@ RHI::RHIDescriptorSet* BuiltinObjectIdPass::GetOrCreatePerObjectSet(uint64_t obj return nullptr; } - RHI::DescriptorSetLayoutBinding binding = {}; - binding.binding = m_perObjectBinding.binding; - 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(m_perObjectSetLayout.layout); if (descriptorSet.set == nullptr) { DestroyOwnedDescriptorSet(descriptorSet); return nullptr; diff --git a/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp b/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp index f755ecd3..cc0e8bbb 100644 --- a/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp +++ b/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp @@ -52,72 +52,6 @@ private: namespace { -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; -} - bool TryResolveSurfacePassType( const Resources::Material* material, BuiltinMaterialPass& outPass) { @@ -582,7 +516,10 @@ BuiltinForwardPipeline::PassResourceLayout* BuiltinForwardPipeline::GetOrCreateP const bool hasAnyResource = !bindingPlan.bindings.Empty(); if (hasAnyResource) { - passLayout.setLayouts.resize(static_cast(bindingPlan.maxSetIndex) + 1u); + Containers::String setLayoutError; + if (!TryBuildBuiltinPassSetLayouts(bindingPlan, passLayout.setLayouts, &setLayoutError)) { + return failLayout(setLayoutError.CStr()); + } passLayout.staticDescriptorSets.resize(passLayout.setLayouts.size()); passLayout.firstDescriptorSet = bindingPlan.firstDescriptorSet; passLayout.descriptorSetCount = bindingPlan.descriptorSetCount; @@ -593,51 +530,6 @@ BuiltinForwardPipeline::PassResourceLayout* BuiltinForwardPipeline::GetOrCreateP passLayout.baseColorTexture = bindingPlan.baseColorTexture; passLayout.linearClampSampler = bindingPlan.linearClampSampler; - for (const BuiltinPassResourceBindingDesc& binding : bindingPlan.bindings) { - if (binding.location.set >= passLayout.setLayouts.size()) { - return failLayout("BuiltinForwardPipeline encountered an invalid forward shader resource set"); - } - - const RHI::DescriptorType descriptorType = ToDescriptorType(binding.resourceType); - const RHI::DescriptorHeapType heapType = ResolveDescriptorHeapType(binding.resourceType); - PassSetLayoutMetadata& setLayout = passLayout.setLayouts[binding.location.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.location.binding)) { - return failLayout("BuiltinForwardPipeline 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::BaseColorTexture: - setLayout.usesTexture = true; - break; - case BuiltinPassResourceSemantic::LinearClampSampler: - 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"); } @@ -646,7 +538,7 @@ BuiltinForwardPipeline::PassResourceLayout* BuiltinForwardPipeline::GetOrCreateP passLayout.firstDescriptorSet > 0 && !passLayout.setLayouts.empty() && passLayout.setLayouts[0].bindings.empty()) { - PassSetLayoutMetadata& compatibilitySet = passLayout.setLayouts[0]; + BuiltinPassSetLayoutMetadata& compatibilitySet = passLayout.setLayouts[0]; if (bindingPlan.usesConstantBuffers) { compatibilitySet.bindings.push_back({ 0, @@ -671,20 +563,7 @@ BuiltinForwardPipeline::PassResourceLayout* BuiltinForwardPipeline::GetOrCreateP 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()); + RefreshBuiltinPassSetLayoutMetadata(compatibilitySet); } std::vector nativeSetLayouts(passLayout.setLayouts.size()); @@ -702,10 +581,7 @@ BuiltinForwardPipeline::PassResourceLayout* BuiltinForwardPipeline::GetOrCreateP 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()); - } + RefreshBuiltinPassSetLayouts(storedPassLayout.setLayouts); return &storedPassLayout; } @@ -760,11 +636,11 @@ RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState( } bool BuiltinForwardPipeline::CreateOwnedDescriptorSet( - const PassSetLayoutMetadata& setLayout, + const BuiltinPassSetLayoutMetadata& setLayout, OwnedDescriptorSet& descriptorSet) { RHI::DescriptorPoolDesc poolDesc = {}; poolDesc.type = setLayout.heapType; - poolDesc.descriptorCount = CountHeapDescriptors(setLayout.heapType, setLayout.bindings); + poolDesc.descriptorCount = CountBuiltinPassHeapDescriptors(setLayout.heapType, setLayout.bindings); poolDesc.shaderVisible = setLayout.shaderVisible; descriptorSet.pool = m_device->CreateDescriptorPool(poolDesc); @@ -813,7 +689,7 @@ RHI::RHIDescriptorSet* BuiltinForwardPipeline::GetOrCreateStaticDescriptorSet( BuiltinForwardPipeline::CachedDescriptorSet* BuiltinForwardPipeline::GetOrCreateDynamicDescriptorSet( const PassLayoutKey& passLayoutKey, const PassResourceLayout& passLayout, - const PassSetLayoutMetadata& setLayout, + const BuiltinPassSetLayoutMetadata& setLayout, Core::uint32 setIndex, Core::uint64 objectId, const Resources::Material* material, @@ -1008,7 +884,7 @@ bool BuiltinForwardPipeline::DrawVisibleItem( return false; } - const PassSetLayoutMetadata& setLayout = passLayout->setLayouts[setIndex]; + const BuiltinPassSetLayoutMetadata& setLayout = passLayout->setLayouts[setIndex]; RHI::RHIDescriptorSet* descriptorSet = nullptr; if (setLayout.usesPerObject || setLayout.usesMaterial || setLayout.usesTexture) { diff --git a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp index e2630fc0..9edc587a 100644 --- a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp +++ b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp @@ -184,6 +184,39 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLegacy EXPECT_EQ(plan.linearClampSampler.set, 4u); } +TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromLegacyForwardResources) { + const Array bindings = BuildLegacyBuiltinForwardPassResourceBindings(); + + BuiltinPassResourceBindingPlan plan = {}; + String error; + ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(bindings, plan, &error)) << error.CStr(); + + std::vector setLayouts; + ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr(); + ASSERT_EQ(setLayouts.size(), 5u); + + EXPECT_EQ(setLayouts[0].layout.bindingCount, 0u); + + EXPECT_EQ(setLayouts[1].layout.bindingCount, 1u); + EXPECT_FALSE(setLayouts[1].shaderVisible); + EXPECT_TRUE(setLayouts[1].usesPerObject); + EXPECT_FALSE(setLayouts[1].usesMaterial); + + EXPECT_EQ(setLayouts[2].layout.bindingCount, 1u); + EXPECT_TRUE(setLayouts[2].usesMaterial); + EXPECT_FALSE(setLayouts[2].shaderVisible); + + EXPECT_EQ(setLayouts[3].layout.bindingCount, 1u); + EXPECT_TRUE(setLayouts[3].usesTexture); + EXPECT_TRUE(setLayouts[3].shaderVisible); + EXPECT_EQ(setLayouts[3].heapType, DescriptorHeapType::CBV_SRV_UAV); + + EXPECT_EQ(setLayouts[4].layout.bindingCount, 1u); + EXPECT_TRUE(setLayouts[4].usesSampler); + EXPECT_TRUE(setLayouts[4].shaderVisible); + EXPECT_EQ(setLayouts[4].heapType, DescriptorHeapType::Sampler); +} + TEST(BuiltinObjectIdPass_Test, BuiltinObjectIdShaderDeclaresExplicitPerObjectResourceContract) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinObjectIdShaderPath()); @@ -227,7 +260,7 @@ TEST(BuiltinObjectIdPass_Test, BuildsBuiltinPassResourceBindingPlanFromLegacyFal TEST(BuiltinObjectIdPass_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) { const InputLayoutDesc inputLayout = BuiltinObjectIdPass::BuildInputLayout(); - ASSERT_EQ(inputLayout.elements.size(), 1u); + ASSERT_EQ(inputLayout.elements.size(), 3u); const InputElementDesc& position = inputLayout.elements[0]; EXPECT_EQ(position.semanticName, "POSITION"); @@ -235,4 +268,88 @@ TEST(BuiltinObjectIdPass_Test, UsesFloat3PositionInputLayoutForStaticMeshVertice EXPECT_EQ(position.format, static_cast(Format::R32G32B32_Float)); EXPECT_EQ(position.inputSlot, 0u); EXPECT_EQ(position.alignedByteOffset, static_cast(offsetof(StaticMeshVertex, position))); + + const InputElementDesc& normal = inputLayout.elements[1]; + EXPECT_EQ(normal.semanticName, "NORMAL"); + EXPECT_EQ(normal.semanticIndex, 0u); + EXPECT_EQ(normal.format, static_cast(Format::R32G32B32_Float)); + EXPECT_EQ(normal.inputSlot, 0u); + EXPECT_EQ(normal.alignedByteOffset, static_cast(offsetof(StaticMeshVertex, normal))); + + const InputElementDesc& texcoord = inputLayout.elements[2]; + EXPECT_EQ(texcoord.semanticName, "TEXCOORD"); + EXPECT_EQ(texcoord.semanticIndex, 0u); + EXPECT_EQ(texcoord.format, static_cast(Format::R32G32_Float)); + EXPECT_EQ(texcoord.inputSlot, 0u); + EXPECT_EQ(texcoord.alignedByteOffset, static_cast(offsetof(StaticMeshVertex, uv0))); +} + +TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromLegacyObjectIdResources) { + const Array bindings = BuildLegacyBuiltinObjectIdPassResourceBindings(); + + BuiltinPassResourceBindingPlan plan = {}; + String error; + ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(bindings, plan, &error)) << error.CStr(); + + std::vector setLayouts; + ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr(); + ASSERT_EQ(setLayouts.size(), 1u); + EXPECT_EQ(setLayouts[0].layout.bindingCount, 1u); + EXPECT_TRUE(setLayouts[0].usesPerObject); + EXPECT_FALSE(setLayouts[0].usesMaterial); + EXPECT_FALSE(setLayouts[0].usesTexture); + EXPECT_FALSE(setLayouts[0].usesSampler); + EXPECT_FALSE(setLayouts[0].shaderVisible); + EXPECT_EQ(setLayouts[0].heapType, DescriptorHeapType::CBV_SRV_UAV); +} + +TEST(BuiltinPassLayout_Test, RejectsMixedSamplerAndNonSamplerBindingsInOneSet) { + Array bindings; + bindings.Resize(2); + + bindings[0].name = "BaseColorTexture"; + bindings[0].type = ShaderResourceType::Texture2D; + bindings[0].set = 0; + bindings[0].binding = 0; + bindings[0].semantic = "BaseColorTexture"; + + bindings[1].name = "LinearClampSampler"; + bindings[1].type = ShaderResourceType::Sampler; + bindings[1].set = 0; + bindings[1].binding = 1; + bindings[1].semantic = "LinearClampSampler"; + + BuiltinPassResourceBindingPlan plan = {}; + String error; + ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(bindings, plan, &error)) << error.CStr(); + + std::vector setLayouts; + EXPECT_FALSE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)); + EXPECT_EQ(error, "Builtin pass does not support mixing sampler and non-sampler bindings in one set"); +} + +TEST(BuiltinPassLayout_Test, RejectsDuplicateBindingsInOneSet) { + BuiltinPassResourceBindingPlan plan = {}; + BuiltinPassResourceBindingDesc perObjectBinding = {}; + perObjectBinding.semantic = BuiltinPassResourceSemantic::PerObject; + perObjectBinding.resourceType = ShaderResourceType::ConstantBuffer; + perObjectBinding.location = { 0, 0 }; + plan.bindings.PushBack(perObjectBinding); + + BuiltinPassResourceBindingDesc materialBinding = {}; + materialBinding.semantic = BuiltinPassResourceSemantic::Material; + materialBinding.resourceType = ShaderResourceType::ConstantBuffer; + materialBinding.location = { 0, 0 }; + plan.bindings.PushBack(materialBinding); + plan.maxSetIndex = 0; + plan.firstDescriptorSet = 0; + plan.descriptorSetCount = 1; + plan.usesConstantBuffers = true; + plan.perObject = { 0, 0 }; + plan.material = { 0, 0 }; + + std::vector setLayouts; + String error; + EXPECT_FALSE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)); + EXPECT_EQ(error, "Builtin pass encountered duplicate bindings inside one descriptor set"); }