Add runtime material buffer bindings

This commit is contained in:
2026-04-08 19:18:07 +08:00
parent bb0f4afe7d
commit 6bf9203eec
13 changed files with 781 additions and 22 deletions

View File

@@ -3,12 +3,17 @@
#include <XCEngine/RHI/RHIEnums.h>
#include <algorithm>
#include <functional>
namespace XCEngine {
namespace Rendering {
namespace {
inline void HashCombine(size_t& seed, size_t value) {
seed ^= value + 0x9e3779b9u + (seed << 6u) + (seed >> 2u);
}
RHI::Format ToRHITextureFormat(Resources::TextureFormat format) {
switch (format) {
case Resources::TextureFormat::R8_UNORM:
@@ -125,6 +130,14 @@ void ShutdownTexture(RenderResourceCache::CachedTexture& cachedTexture) {
}
}
void ShutdownBufferView(RenderResourceCache::CachedBufferView& cachedBufferView) {
if (cachedBufferView.resourceView != nullptr) {
cachedBufferView.resourceView->Shutdown();
delete cachedBufferView.resourceView;
cachedBufferView.resourceView = nullptr;
}
}
} // namespace
RenderResourceCache::~RenderResourceCache() {
@@ -141,6 +154,11 @@ void RenderResourceCache::Shutdown() {
ShutdownTexture(entry.second);
}
m_textureCache.clear();
for (auto& entry : m_bufferViewCache) {
ShutdownBufferView(entry.second);
}
m_bufferViewCache.clear();
}
const RenderResourceCache::CachedMesh* RenderResourceCache::GetOrCreateMesh(
@@ -187,6 +205,52 @@ const RenderResourceCache::CachedTexture* RenderResourceCache::GetOrCreateTextur
return &result.first->second;
}
const RenderResourceCache::CachedBufferView* RenderResourceCache::GetOrCreateBufferView(
RHI::RHIDevice* device,
RHI::RHIBuffer* buffer,
RHI::ResourceViewType viewType,
const RHI::ResourceViewDesc& viewDesc) {
if (device == nullptr || buffer == nullptr) {
return nullptr;
}
BufferViewCacheKey key = {};
key.buffer = buffer;
key.viewType = viewType;
key.format = viewDesc.format;
key.dimension = viewDesc.dimension;
key.bufferLocation = viewDesc.bufferLocation;
key.firstElement = viewDesc.firstElement;
key.elementCount = viewDesc.elementCount;
key.structureByteStride = viewDesc.structureByteStride;
const auto existing = m_bufferViewCache.find(key);
if (existing != m_bufferViewCache.end()) {
return &existing->second;
}
CachedBufferView cachedBufferView = {};
if (!CreateBufferView(device, buffer, viewType, viewDesc, cachedBufferView)) {
ShutdownBufferView(cachedBufferView);
return nullptr;
}
const auto result = m_bufferViewCache.emplace(key, cachedBufferView);
return &result.first->second;
}
size_t RenderResourceCache::BufferViewCacheKeyHash::operator()(const BufferViewCacheKey& key) const noexcept {
size_t hash = std::hash<const void*>{}(static_cast<const void*>(key.buffer));
HashCombine(hash, std::hash<uint32_t>{}(static_cast<uint32_t>(key.viewType)));
HashCombine(hash, std::hash<uint32_t>{}(key.format));
HashCombine(hash, std::hash<uint32_t>{}(static_cast<uint32_t>(key.dimension)));
HashCombine(hash, std::hash<uint64_t>{}(key.bufferLocation));
HashCombine(hash, std::hash<uint32_t>{}(key.firstElement));
HashCombine(hash, std::hash<uint32_t>{}(key.elementCount));
HashCombine(hash, std::hash<uint32_t>{}(key.structureByteStride));
return hash;
}
bool RenderResourceCache::UploadMesh(
RHI::RHIDevice* device,
const Resources::Mesh* mesh,
@@ -303,5 +367,25 @@ bool RenderResourceCache::UploadTexture(
return true;
}
bool RenderResourceCache::CreateBufferView(
RHI::RHIDevice* device,
RHI::RHIBuffer* buffer,
RHI::ResourceViewType viewType,
const RHI::ResourceViewDesc& viewDesc,
CachedBufferView& cachedBufferView) {
switch (viewType) {
case RHI::ResourceViewType::ShaderResource:
cachedBufferView.resourceView = device->CreateShaderResourceView(buffer, viewDesc);
break;
case RHI::ResourceViewType::UnorderedAccess:
cachedBufferView.resourceView = device->CreateUnorderedAccessView(buffer, viewDesc);
break;
default:
return false;
}
return cachedBufferView.resourceView != nullptr;
}
} // namespace Rendering
} // namespace XCEngine

View File

@@ -31,12 +31,22 @@ Resources::ShaderKeywordSet ResolvePassKeywordSet(
}
bool IsSupportedPerObjectOnlyBindingPlan(const BuiltinPassResourceBindingPlan& bindingPlan) {
return bindingPlan.perObject.IsValid() &&
bindingPlan.bindings.Size() == 1u &&
bindingPlan.descriptorSetCount == 1u &&
bindingPlan.usesConstantBuffers &&
!bindingPlan.usesTextures &&
!bindingPlan.usesSamplers;
if (!(bindingPlan.perObject.IsValid() &&
bindingPlan.descriptorSetCount >= 1u &&
bindingPlan.usesConstantBuffers &&
!bindingPlan.usesTextures &&
!bindingPlan.usesSamplers)) {
return false;
}
for (const BuiltinPassResourceBindingDesc& binding : bindingPlan.bindings) {
if (binding.semantic != BuiltinPassResourceSemantic::PerObject &&
binding.semantic != BuiltinPassResourceSemantic::MaterialBuffer) {
return false;
}
}
return true;
}
bool UsesContiguousDescriptorSets(const BuiltinPassResourceBindingPlan& bindingPlan) {
@@ -63,14 +73,31 @@ bool UsesContiguousDescriptorSets(const BuiltinPassResourceBindingPlan& bindingP
}
bool IsSupportedAlphaTestBindingPlan(const BuiltinPassResourceBindingPlan& bindingPlan) {
return bindingPlan.perObject.IsValid() &&
bindingPlan.material.IsValid() &&
bindingPlan.baseColorTexture.IsValid() &&
bindingPlan.linearClampSampler.IsValid() &&
!bindingPlan.lighting.IsValid() &&
!bindingPlan.shadowReceiver.IsValid() &&
!bindingPlan.shadowMapTexture.IsValid() &&
!bindingPlan.shadowMapSampler.IsValid();
if (!(bindingPlan.perObject.IsValid() &&
bindingPlan.material.IsValid() &&
bindingPlan.baseColorTexture.IsValid() &&
bindingPlan.linearClampSampler.IsValid() &&
!bindingPlan.lighting.IsValid() &&
!bindingPlan.shadowReceiver.IsValid() &&
!bindingPlan.shadowMapTexture.IsValid() &&
!bindingPlan.shadowMapSampler.IsValid())) {
return false;
}
for (const BuiltinPassResourceBindingDesc& binding : bindingPlan.bindings) {
switch (binding.semantic) {
case BuiltinPassResourceSemantic::PerObject:
case BuiltinPassResourceSemantic::Material:
case BuiltinPassResourceSemantic::MaterialBuffer:
case BuiltinPassResourceSemantic::BaseColorTexture:
case BuiltinPassResourceSemantic::LinearClampSampler:
break;
default:
return false;
}
}
return true;
}
uint32_t ResolveSurfaceColorAttachmentCount(const RenderSurface& surface) {
@@ -592,6 +619,35 @@ BuiltinDepthStylePassBase::CachedDescriptorSet* BuiltinDepthStylePassBase::GetOr
}
}
if (setLayout.usesMaterialBuffers) {
if (material == nullptr) {
return nullptr;
}
if (cachedDescriptorSet.materialVersion != materialVersion) {
for (const BuiltinPassResourceBindingDesc& bufferBinding : setLayout.materialBufferBindings) {
MaterialBufferResourceView resolvedView = {};
if (!TryResolveMaterialBufferResourceView(material, bufferBinding, resolvedView)) {
return nullptr;
}
const RenderResourceCache::CachedBufferView* cachedBufferView =
m_resourceCache.GetOrCreateBufferView(
m_device,
resolvedView.buffer,
resolvedView.viewType,
resolvedView.viewDesc);
if (cachedBufferView == nullptr || cachedBufferView->resourceView == nullptr) {
return nullptr;
}
cachedDescriptorSet.descriptorSet.set->Update(
bufferBinding.location.binding,
cachedBufferView->resourceView);
}
}
}
if (setLayout.usesBaseColorTexture) {
if (baseColorTextureView == nullptr ||
!passLayout.baseColorTexture.IsValid() ||
@@ -785,11 +841,16 @@ bool BuiltinDepthStylePassBase::DrawVisibleItem(
RHI::RHIDescriptorSet* descriptorSet = nullptr;
if (setLayout.usesPerObject ||
setLayout.usesMaterial ||
setLayout.usesBaseColorTexture) {
setLayout.usesBaseColorTexture ||
setLayout.usesMaterialBuffers) {
const Core::uint64 objectId =
setLayout.usesPerObject ? visibleItem.gameObject->GetID() : 0;
const Resources::Material* materialKey =
(setLayout.usesMaterial || setLayout.usesBaseColorTexture) ? material : nullptr;
(setLayout.usesMaterial ||
setLayout.usesBaseColorTexture ||
setLayout.usesMaterialBuffers)
? material
: nullptr;
CachedDescriptorSet* cachedDescriptorSet = GetOrCreateDynamicDescriptorSet(
passLayoutKey,

View File

@@ -398,6 +398,35 @@ BuiltinForwardPipeline::CachedDescriptorSet* BuiltinForwardPipeline::GetOrCreate
}
}
if (setLayout.usesMaterialBuffers) {
if (material == nullptr) {
return nullptr;
}
if (cachedDescriptorSet.materialVersion != materialVersion) {
for (const BuiltinPassResourceBindingDesc& bufferBinding : setLayout.materialBufferBindings) {
MaterialBufferResourceView resolvedView = {};
if (!TryResolveMaterialBufferResourceView(material, bufferBinding, resolvedView)) {
return nullptr;
}
const RenderResourceCache::CachedBufferView* cachedBufferView =
m_resourceCache.GetOrCreateBufferView(
m_device,
resolvedView.buffer,
resolvedView.viewType,
resolvedView.viewDesc);
if (cachedBufferView == nullptr || cachedBufferView->resourceView == nullptr) {
return nullptr;
}
cachedDescriptorSet.descriptorSet.set->Update(
bufferBinding.location.binding,
cachedBufferView->resourceView);
}
}
}
if (setLayout.usesLighting) {
if (!passLayout.lighting.IsValid() || passLayout.lighting.set != setIndex) {
return nullptr;
@@ -679,13 +708,18 @@ bool BuiltinForwardPipeline::DrawVisibleItem(
setLayout.usesLighting ||
setLayout.usesMaterial ||
setLayout.usesShadowReceiver ||
setLayout.usesTexture) {
setLayout.usesTexture ||
setLayout.usesMaterialBuffers) {
const Core::uint64 objectId =
(setLayout.usesPerObject && visibleItem.gameObject != nullptr)
? visibleItem.gameObject->GetID()
: 0;
const Resources::Material* materialKey =
(setLayout.usesMaterial || setLayout.usesBaseColorTexture) ? material : nullptr;
(setLayout.usesMaterial ||
setLayout.usesBaseColorTexture ||
setLayout.usesMaterialBuffers)
? material
: nullptr;
CachedDescriptorSet* cachedDescriptorSet = GetOrCreateDynamicDescriptorSet(
passLayoutKey,

View File

@@ -81,6 +81,20 @@ void RemoveTextureBindingByName(
}
}
void RemoveBufferBindingByName(
Containers::Array<MaterialBufferBinding>& bufferBindings,
const Containers::String& name) {
for (size_t bindingIndex = 0; bindingIndex < bufferBindings.Size(); ++bindingIndex) {
if (bufferBindings[bindingIndex].name == name) {
if (bindingIndex != bufferBindings.Size() - 1) {
bufferBindings[bindingIndex] = std::move(bufferBindings.Back());
}
bufferBindings.PopBack();
break;
}
}
}
void EnsureTextureProperty(Containers::HashMap<Containers::String, MaterialProperty>& properties,
const Containers::String& name,
MaterialPropertyType type = MaterialPropertyType::Texture) {
@@ -329,6 +343,7 @@ void Material::Release() {
m_properties.Clear();
m_constantLayout.Clear();
m_textureBindings.Clear();
m_bufferBindings.Clear();
m_constantBufferData.Clear();
m_changeVersion = 1;
m_isValid = false;
@@ -338,6 +353,7 @@ void Material::Release() {
void Material::SetShader(const ResourceHandle<Shader>& shader) {
m_shader = shader;
SyncShaderSchemaProperties(true);
SyncShaderRuntimeBufferBindings(true);
SyncShaderSchemaKeywords(true);
MarkChanged(true);
}
@@ -643,6 +659,36 @@ void Material::SetTexture(const Containers::String& name, const ResourceHandle<T
MarkChanged(false);
}
void Material::SetBuffer(const Containers::String& name, RHI::RHIBuffer* buffer) {
SetBuffer(name, buffer, MaterialBufferBindingViewDesc());
}
void Material::SetBuffer(
const Containers::String& name,
RHI::RHIBuffer* buffer,
const MaterialBufferBindingViewDesc& viewDesc) {
if (buffer == nullptr) {
RemoveBufferBinding(name);
return;
}
for (MaterialBufferBinding& binding : m_bufferBindings) {
if (binding.name == name) {
binding.buffer = buffer;
binding.viewDesc = viewDesc;
MarkChanged(false);
return;
}
}
MaterialBufferBinding binding = {};
binding.name = name;
binding.buffer = buffer;
binding.viewDesc = viewDesc;
m_bufferBindings.PushBack(binding);
MarkChanged(false);
}
void Material::SetTextureAssetRef(const Containers::String& name,
const AssetRef& textureRef,
const Containers::String& texturePath) {
@@ -785,6 +831,21 @@ ResourceHandle<Texture> Material::GetTexture(const Containers::String& name) con
return ResourceHandle<Texture>();
}
RHI::RHIBuffer* Material::GetBuffer(const Containers::String& name) const {
const MaterialBufferBinding* binding = FindBufferBinding(name);
return binding != nullptr ? binding->buffer : nullptr;
}
const MaterialBufferBinding* Material::FindBufferBinding(const Containers::String& name) const {
for (const MaterialBufferBinding& binding : m_bufferBindings) {
if (binding.name == name) {
return &binding;
}
}
return nullptr;
}
Containers::String Material::GetTextureBindingName(Core::uint32 index) const {
return index < m_textureBindings.Size() ? m_textureBindings[index].name : Containers::String();
}
@@ -996,6 +1057,23 @@ void Material::ClearAllProperties() {
MarkChanged(true);
}
void Material::RemoveBufferBinding(const Containers::String& name) {
const size_t bindingCount = m_bufferBindings.Size();
RemoveBufferBindingByName(m_bufferBindings, name);
if (m_bufferBindings.Size() != bindingCount) {
MarkChanged(false);
}
}
void Material::ClearBufferBindings() {
if (m_bufferBindings.Empty()) {
return;
}
m_bufferBindings.Clear();
MarkChanged(false);
}
const ShaderPropertyDesc* Material::FindShaderPropertyDesc(const Containers::String& name) const {
if (m_shader.Get() == nullptr) {
return nullptr;
@@ -1064,6 +1142,52 @@ void Material::SyncShaderSchemaProperties(bool removeUnknownProperties) {
}
}
void Material::SyncShaderRuntimeBufferBindings(bool removeUnknownBindings) {
if (m_shader.Get() == nullptr || !removeUnknownBindings) {
return;
}
std::vector<Containers::String> unknownBindings;
unknownBindings.reserve(m_bufferBindings.Size());
for (const MaterialBufferBinding& binding : m_bufferBindings) {
bool found = false;
for (const ShaderPass& shaderPass : m_shader->GetPasses()) {
for (const ShaderResourceBindingDesc& shaderBinding : shaderPass.resources) {
if (shaderBinding.name != binding.name) {
continue;
}
switch (shaderBinding.type) {
case ShaderResourceType::StructuredBuffer:
case ShaderResourceType::RawBuffer:
case ShaderResourceType::RWStructuredBuffer:
case ShaderResourceType::RWRawBuffer:
found = true;
break;
default:
break;
}
if (found) {
break;
}
}
if (found) {
break;
}
}
if (!found) {
unknownBindings.push_back(binding.name);
}
}
for (const Containers::String& bindingName : unknownBindings) {
RemoveBufferBindingByName(m_bufferBindings, bindingName);
}
}
void Material::SyncShaderSchemaKeywords(bool removeUnknownKeywords) {
if (m_shader.Get() == nullptr || !removeUnknownKeywords) {
return;
@@ -1104,6 +1228,7 @@ void Material::UpdateMemorySize() {
m_tags.Size() * sizeof(MaterialTagEntry) +
m_keywordSet.enabledKeywords.Size() * sizeof(Containers::String) +
m_textureBindings.Size() * sizeof(MaterialTextureBinding) +
m_bufferBindings.Size() * sizeof(MaterialBufferBinding) +
m_properties.Size() * sizeof(MaterialProperty) +
m_name.Length() +
m_path.Length();
@@ -1125,6 +1250,10 @@ void Material::UpdateMemorySize() {
m_memorySize += binding.name.Length();
m_memorySize += binding.texturePath.Length();
}
for (const MaterialBufferBinding& binding : m_bufferBindings) {
m_memorySize += binding.name.Length();
}
}
} // namespace Resources