Share builtin pass layout assembly utilities

This commit is contained in:
2026-04-04 13:48:13 +08:00
parent 0ebd2d4979
commit a3ba08bb99
7 changed files with 328 additions and 180 deletions

View File

@@ -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/3D3D12 / OpenGL / Vulkan
- 已验证:`rendering_integration_unlit_scene` 3/3D3D12 / OpenGL / Vulkan
- 已验证:`rendering_integration_object_id_scene` 3/3D3D12 / OpenGL / Vulkan
- 下一步:如果继续沿 renderer 主线收口,优先把 `DepthOnly / ShadowCaster` 接到同一套 shared pass-layout skeleton如果先控制范围当前 `ForwardLit / Unlit / ObjectId` 已可以视为这一小阶段完成
### 阶段 D扩展 AssetDatabase / Library Artifact 能力

View File

@@ -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<Resources::Shader> m_builtinObjectIdShader;
RenderResourceCache m_resourceCache;

View File

@@ -89,22 +89,11 @@ private:
}
};
struct PassSetLayoutMetadata {
std::vector<RHI::DescriptorSetLayoutBinding> 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<PassSetLayoutMetadata> setLayouts;
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
std::vector<OwnedDescriptorSet> 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,

View File

@@ -10,6 +10,7 @@
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <vector>
namespace XCEngine {
namespace Rendering {
@@ -70,6 +71,17 @@ struct BuiltinPassResourceBindingPlan {
}
};
struct BuiltinPassSetLayoutMetadata {
std::vector<RHI::DescriptorSetLayoutBinding> 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<RHI::DescriptorSetLayoutBinding>& bindings) {
for (const RHI::DescriptorSetLayoutBinding& binding : bindings) {
const RHI::DescriptorType descriptorType = static_cast<RHI::DescriptorType>(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<RHI::DescriptorSetLayoutBinding>& bindings) {
Core::uint32 descriptorCount = 0;
for (const RHI::DescriptorSetLayoutBinding& binding : bindings) {
const RHI::DescriptorType descriptorType = static_cast<RHI::DescriptorType>(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<Core::uint32>(setLayout.bindings.size());
}
inline void RefreshBuiltinPassSetLayouts(std::vector<BuiltinPassSetLayoutMetadata>& setLayouts) {
for (BuiltinPassSetLayoutMetadata& setLayout : setLayouts) {
RefreshBuiltinPassSetLayoutMetadata(setLayout);
}
}
inline bool TryBuildBuiltinPassSetLayouts(
const BuiltinPassResourceBindingPlan& bindingPlan,
std::vector<BuiltinPassSetLayoutMetadata>& 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<size_t>(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<Core::uint32>(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();
};

View File

@@ -274,24 +274,33 @@ bool BuiltinObjectIdPass::CreateResources(const RenderContext& context) {
m_perObjectBinding = bindingPlan.perObject;
m_firstDescriptorSet = bindingPlan.firstDescriptorSet;
std::vector<std::vector<RHI::DescriptorSetLayoutBinding>> setBindingStorage(
static_cast<size_t>(bindingPlan.maxSetIndex) + 1u);
RHI::DescriptorSetLayoutBinding constantBinding = {};
constantBinding.binding = m_perObjectBinding.binding;
constantBinding.type = static_cast<uint32_t>(RHI::DescriptorType::CBV);
constantBinding.count = 1;
setBindingStorage[m_perObjectBinding.set].push_back(constantBinding);
std::vector<BuiltinPassSetLayoutMetadata> 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<RHI::DescriptorSetLayoutDesc> 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<uint32_t>(setBindingStorage[setIndex].size());
std::vector<RHI::DescriptorSetLayoutDesc> 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<uint32_t>(setLayouts.size());
pipelineLayoutDesc.setLayouts = nativeSetLayouts.empty() ? nullptr : nativeSetLayouts.data();
pipelineLayoutDesc.setLayoutCount = static_cast<uint32_t>(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<uint32_t>(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;

View File

@@ -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<RHI::DescriptorSetLayoutBinding>& bindings) {
for (const RHI::DescriptorSetLayoutBinding& binding : bindings) {
const RHI::DescriptorType descriptorType = static_cast<RHI::DescriptorType>(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<RHI::DescriptorSetLayoutBinding>& bindings) {
uint32_t descriptorCount = 0;
for (const RHI::DescriptorSetLayoutBinding& binding : bindings) {
const RHI::DescriptorType descriptorType = static_cast<RHI::DescriptorType>(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<RHI::DescriptorSetLayoutBinding>& 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<size_t>(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<uint32_t>(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<uint32_t>(setLayout.bindings.size());
RefreshBuiltinPassSetLayoutMetadata(compatibilitySet);
}
std::vector<RHI::DescriptorSetLayoutDesc> 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<uint32_t>(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) {

View File

@@ -184,6 +184,39 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLegacy
EXPECT_EQ(plan.linearClampSampler.set, 4u);
}
TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromLegacyForwardResources) {
const Array<ShaderResourceBindingDesc> bindings = BuildLegacyBuiltinForwardPassResourceBindings();
BuiltinPassResourceBindingPlan plan = {};
String error;
ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(bindings, plan, &error)) << error.CStr();
std::vector<BuiltinPassSetLayoutMetadata> 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<uint32_t>(Format::R32G32B32_Float));
EXPECT_EQ(position.inputSlot, 0u);
EXPECT_EQ(position.alignedByteOffset, static_cast<uint32_t>(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<uint32_t>(Format::R32G32B32_Float));
EXPECT_EQ(normal.inputSlot, 0u);
EXPECT_EQ(normal.alignedByteOffset, static_cast<uint32_t>(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<uint32_t>(Format::R32G32_Float));
EXPECT_EQ(texcoord.inputSlot, 0u);
EXPECT_EQ(texcoord.alignedByteOffset, static_cast<uint32_t>(offsetof(StaticMeshVertex, uv0)));
}
TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromLegacyObjectIdResources) {
const Array<ShaderResourceBindingDesc> bindings = BuildLegacyBuiltinObjectIdPassResourceBindings();
BuiltinPassResourceBindingPlan plan = {};
String error;
ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(bindings, plan, &error)) << error.CStr();
std::vector<BuiltinPassSetLayoutMetadata> 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<ShaderResourceBindingDesc> 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<BuiltinPassSetLayoutMetadata> 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<BuiltinPassSetLayoutMetadata> setLayouts;
String error;
EXPECT_FALSE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error));
EXPECT_EQ(error, "Builtin pass encountered duplicate bindings inside one descriptor set");
}