refactor: drive builtin forward bindings from shader pass resources

This commit is contained in:
2026-04-03 11:51:01 +08:00
parent 2138195b69
commit b3c5fb59a0
3 changed files with 660 additions and 212 deletions

View File

@@ -16,6 +16,7 @@
#include <functional>
#include <unordered_map>
#include <vector>
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<size_t>(key.shader);
hash ^= std::hash<Containers::String>{}(key.passName) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
return hash;
}
};
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<OwnedDescriptorSet> 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<Core::uint32>{}(key.setIndex));
combine(std::hash<Core::uint64>{}(key.objectId));
combine(reinterpret_cast<size_t>(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<PassLayoutKey, PassResourceLayout, PassLayoutKeyHash> m_passResourceLayouts;
std::unordered_map<PipelineStateKey, RHI::RHIPipelineState*, PipelineStateKeyHash> m_pipelineStates;
std::unordered_map<Core::uint64, OwnedDescriptorSet> m_perObjectSets;
std::unordered_map<const Resources::Material*, CachedMaterialBindings> m_materialBindings;
std::unordered_map<DynamicDescriptorSetKey, CachedDescriptorSet, DynamicDescriptorSetKeyHash> m_dynamicDescriptorSets;
RHI::RHISampler* m_sampler = nullptr;
RHI::RHITexture* m_fallbackTexture = nullptr;
RHI::RHIResourceView* m_fallbackTextureView = nullptr;

View File

@@ -14,6 +14,7 @@
#include "Resources/Shader/Shader.h"
#include "Resources/Texture/Texture.h"
#include <algorithm>
#include <cstddef>
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<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;
}
std::vector<Resources::ShaderResourceBindingDesc> BuildLegacyForwardResourceBindings() {
std::vector<Resources::ShaderResourceBindingDesc> 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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(RHI::DescriptorType::CBV);
reservedBindings[0].count = 1;
reservedBindings[1].binding = 0;
reservedBindings[1].type = static_cast<uint32_t>(RHI::DescriptorType::SRV);
reservedBindings[1].count = 1;
reservedBindings[2].binding = 0;
reservedBindings[2].type = static_cast<uint32_t>(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<uint32_t>(RHI::FilterMode::Linear);
samplerDesc.addressU = static_cast<uint32_t>(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<Resources::ShaderResourceBindingDesc> 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<size_t>(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<uint32_t>(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<uint32_t>(RHI::DescriptorType::CBV),
1,
0
});
}
if (usesTextures) {
compatibilitySet.bindings.push_back({
0,
static_cast<uint32_t>(RHI::DescriptorType::SRV),
1,
0
});
}
if (usesSamplers) {
compatibilitySet.bindings.push_back({
0,
static_cast<uint32_t>(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<uint32_t>(setLayout.bindings.size());
}
std::vector<RHI::DescriptorSetLayoutDesc> 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<uint32_t>(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<uint32_t>(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<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(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<uint32_t>(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<uint32_t>(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<RHI::RHIDescriptorSet*> 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<Resources::MeshSection>& sections = visibleItem.mesh->GetSections();