Share builtin pass layout assembly utilities
This commit is contained in:
@@ -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 能力
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user