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

@@ -45,6 +45,8 @@ inline bool TryBuildBuiltinPassResourceBindingPlan(
case BuiltinPassResourceSemantic::Material:
location = &outPlan.material;
break;
case BuiltinPassResourceSemantic::MaterialBuffer:
break;
case BuiltinPassResourceSemantic::Lighting:
location = &outPlan.lighting;
break;
@@ -83,10 +85,10 @@ inline bool TryBuildBuiltinPassResourceBindingPlan(
break;
}
if (location == nullptr) {
if (semantic != BuiltinPassResourceSemantic::MaterialBuffer && location == nullptr) {
return fail("Builtin pass resource semantic could not be mapped");
}
if (location->IsValid()) {
if (location != nullptr && location->IsValid()) {
return fail("Builtin pass resource semantic appears more than once");
}
@@ -97,13 +99,21 @@ inline bool TryBuildBuiltinPassResourceBindingPlan(
}
}
*location = { binding.set, binding.binding };
const PassResourceBindingLocation resolvedLocation = { binding.set, binding.binding };
if (location != nullptr) {
*location = resolvedLocation;
}
BuiltinPassResourceBindingDesc resolvedBinding = {};
resolvedBinding.name = binding.name;
resolvedBinding.semantic = semantic;
resolvedBinding.resourceType = binding.type;
resolvedBinding.location = *location;
resolvedBinding.location = resolvedLocation;
outPlan.bindings.PushBack(resolvedBinding);
if (semantic == BuiltinPassResourceSemantic::MaterialBuffer) {
outPlan.materialBufferBindings.PushBack(resolvedBinding);
outPlan.usesMaterialBuffers = true;
}
outPlan.maxSetIndex = std::max(outPlan.maxSetIndex, binding.set);
minBoundSet = std::min(minBoundSet, binding.set);
@@ -512,6 +522,10 @@ inline bool TryBuildBuiltinPassSetLayouts(
case BuiltinPassResourceSemantic::Material:
setLayout.usesMaterial = true;
break;
case BuiltinPassResourceSemantic::MaterialBuffer:
setLayout.usesMaterialBuffers = true;
setLayout.materialBufferBindings.push_back(binding);
break;
case BuiltinPassResourceSemantic::Lighting:
setLayout.usesLighting = true;
break;

View File

@@ -111,6 +111,10 @@ inline BuiltinPassResourceSemantic ResolveBuiltinPassResourceSemantic(
return BuiltinPassResourceSemantic::Material;
}
if (semantic == Containers::String("materialbuffer")) {
return BuiltinPassResourceSemantic::MaterialBuffer;
}
if (semantic == Containers::String("lighting") ||
semantic == Containers::String("lightingconstants")) {
return BuiltinPassResourceSemantic::Lighting;
@@ -163,6 +167,16 @@ inline BuiltinPassResourceSemantic ResolveBuiltinPassResourceSemantic(
return BuiltinPassResourceSemantic::ShadowMapSampler;
}
switch (binding.type) {
case Resources::ShaderResourceType::StructuredBuffer:
case Resources::ShaderResourceType::RawBuffer:
case Resources::ShaderResourceType::RWStructuredBuffer:
case Resources::ShaderResourceType::RWRawBuffer:
return BuiltinPassResourceSemantic::MaterialBuffer;
default:
break;
}
return BuiltinPassResourceSemantic::Unknown;
}
@@ -172,6 +186,8 @@ inline const char* BuiltinPassResourceSemanticToString(BuiltinPassResourceSemant
return "PerObject";
case BuiltinPassResourceSemantic::Material:
return "Material";
case BuiltinPassResourceSemantic::MaterialBuffer:
return "MaterialBuffer";
case BuiltinPassResourceSemantic::Lighting:
return "Lighting";
case BuiltinPassResourceSemantic::ShadowReceiver:
@@ -260,6 +276,11 @@ inline bool IsBuiltinPassResourceTypeCompatible(
case BuiltinPassResourceSemantic::Environment:
case BuiltinPassResourceSemantic::PassConstants:
return type == Resources::ShaderResourceType::ConstantBuffer;
case BuiltinPassResourceSemantic::MaterialBuffer:
return type == Resources::ShaderResourceType::StructuredBuffer ||
type == Resources::ShaderResourceType::RawBuffer ||
type == Resources::ShaderResourceType::RWStructuredBuffer ||
type == Resources::ShaderResourceType::RWRawBuffer;
case BuiltinPassResourceSemantic::BaseColorTexture:
case BuiltinPassResourceSemantic::SourceColorTexture:
case BuiltinPassResourceSemantic::SkyboxPanoramicTexture:

View File

@@ -37,6 +37,7 @@ enum class BuiltinPassResourceSemantic : Core::uint8 {
Unknown = 0,
PerObject,
Material,
MaterialBuffer,
Lighting,
ShadowReceiver,
Environment,
@@ -51,6 +52,7 @@ enum class BuiltinPassResourceSemantic : Core::uint8 {
};
struct BuiltinPassResourceBindingDesc {
Containers::String name;
BuiltinPassResourceSemantic semantic = BuiltinPassResourceSemantic::Unknown;
Resources::ShaderResourceType resourceType = Resources::ShaderResourceType::ConstantBuffer;
PassResourceBindingLocation location = {};
@@ -58,12 +60,14 @@ struct BuiltinPassResourceBindingDesc {
struct BuiltinPassResourceBindingPlan {
Containers::Array<BuiltinPassResourceBindingDesc> bindings;
Containers::Array<BuiltinPassResourceBindingDesc> materialBufferBindings;
Core::uint32 maxSetIndex = 0;
Core::uint32 firstDescriptorSet = 0;
Core::uint32 descriptorSetCount = 0;
bool usesConstantBuffers = false;
bool usesTextures = false;
bool usesSamplers = false;
bool usesMaterialBuffers = false;
PassResourceBindingLocation perObject = {};
PassResourceBindingLocation material = {};
PassResourceBindingLocation lighting = {};
@@ -91,6 +95,7 @@ struct BuiltinPassResourceBindingPlan {
struct BuiltinPassSetLayoutMetadata {
std::vector<RHI::DescriptorSetLayoutBinding> bindings;
std::vector<BuiltinPassResourceBindingDesc> materialBufferBindings;
RHI::DescriptorSetLayoutDesc layout = {};
RHI::DescriptorHeapType heapType = RHI::DescriptorHeapType::CBV_SRV_UAV;
bool shaderVisible = false;
@@ -100,6 +105,7 @@ struct BuiltinPassSetLayoutMetadata {
bool usesShadowReceiver = false;
bool usesEnvironment = false;
bool usesPassConstants = false;
bool usesMaterialBuffers = false;
bool usesTexture = false;
bool usesBaseColorTexture = false;
bool usesSourceColorTexture = false;

View File

@@ -32,19 +32,61 @@ public:
uint32_t height = 0;
};
struct CachedBufferView {
RHI::RHIResourceView* resourceView = nullptr;
};
~RenderResourceCache();
void Shutdown();
const CachedMesh* GetOrCreateMesh(RHI::RHIDevice* device, const Resources::Mesh* mesh);
const CachedTexture* GetOrCreateTexture(RHI::RHIDevice* device, const Resources::Texture* texture);
const CachedBufferView* GetOrCreateBufferView(
RHI::RHIDevice* device,
RHI::RHIBuffer* buffer,
RHI::ResourceViewType viewType,
const RHI::ResourceViewDesc& viewDesc);
private:
struct BufferViewCacheKey {
const RHI::RHIBuffer* buffer = nullptr;
RHI::ResourceViewType viewType = RHI::ResourceViewType::ShaderResource;
uint32_t format = 0;
RHI::ResourceViewDimension dimension = RHI::ResourceViewDimension::Unknown;
uint64_t bufferLocation = 0;
uint32_t firstElement = 0;
uint32_t elementCount = 0;
uint32_t structureByteStride = 0;
bool operator==(const BufferViewCacheKey& other) const {
return buffer == other.buffer &&
viewType == other.viewType &&
format == other.format &&
dimension == other.dimension &&
bufferLocation == other.bufferLocation &&
firstElement == other.firstElement &&
elementCount == other.elementCount &&
structureByteStride == other.structureByteStride;
}
};
struct BufferViewCacheKeyHash {
size_t operator()(const BufferViewCacheKey& key) const noexcept;
};
bool UploadMesh(RHI::RHIDevice* device, const Resources::Mesh* mesh, CachedMesh& cachedMesh);
bool UploadTexture(RHI::RHIDevice* device, const Resources::Texture* texture, CachedTexture& cachedTexture);
bool CreateBufferView(
RHI::RHIDevice* device,
RHI::RHIBuffer* buffer,
RHI::ResourceViewType viewType,
const RHI::ResourceViewDesc& viewDesc,
CachedBufferView& cachedBufferView);
std::unordered_map<const Resources::Mesh*, CachedMesh> m_meshCache;
std::unordered_map<const Resources::Texture*, CachedTexture> m_textureCache;
std::unordered_map<BufferViewCacheKey, CachedBufferView, BufferViewCacheKeyHash> m_bufferViewCache;
};
} // namespace Rendering

View File

@@ -2,6 +2,9 @@
#include <XCEngine/Components/MeshRendererComponent.h>
#include <XCEngine/Core/Types.h>
#include <XCEngine/RHI/RHIBuffer.h>
#include <XCEngine/RHI/RHIEnums.h>
#include <XCEngine/RHI/RHITypes.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h>
@@ -46,6 +49,31 @@ struct MaterialConstantPayloadView {
}
};
struct MaterialBufferResourceView {
RHI::RHIBuffer* buffer = nullptr;
RHI::ResourceViewType viewType = RHI::ResourceViewType::ShaderResource;
RHI::ResourceViewDesc viewDesc = {};
bool IsValid() const {
return buffer != nullptr &&
(viewType == RHI::ResourceViewType::ShaderResource ||
viewType == RHI::ResourceViewType::UnorderedAccess) &&
viewDesc.dimension != RHI::ResourceViewDimension::Unknown;
}
};
inline bool IsMaterialBufferResourceType(Resources::ShaderResourceType type) {
switch (type) {
case Resources::ShaderResourceType::StructuredBuffer:
case Resources::ShaderResourceType::RawBuffer:
case Resources::ShaderResourceType::RWStructuredBuffer:
case Resources::ShaderResourceType::RWRawBuffer:
return true;
default:
return false;
}
}
inline const Resources::ShaderPropertyDesc* FindShaderPropertyBySemantic(
const Resources::Material* material,
const Containers::String& semantic) {
@@ -249,6 +277,62 @@ inline MaterialConstantPayloadView ResolveSchemaMaterialConstantPayload(const Re
return { constantBufferData.Data(), constantBufferData.Size(), layoutView };
}
inline bool TryResolveMaterialBufferResourceView(
const Resources::Material* material,
const BuiltinPassResourceBindingDesc& binding,
MaterialBufferResourceView& outView) {
outView = {};
if (material == nullptr || !IsMaterialBufferResourceType(binding.resourceType)) {
return false;
}
const Resources::MaterialBufferBinding* materialBinding = material->FindBufferBinding(binding.name);
if (materialBinding == nullptr || materialBinding->buffer == nullptr) {
return false;
}
outView.buffer = materialBinding->buffer;
outView.viewDesc.firstElement = materialBinding->viewDesc.firstElement;
outView.viewDesc.elementCount = materialBinding->viewDesc.elementCount;
switch (binding.resourceType) {
case Resources::ShaderResourceType::StructuredBuffer:
outView.viewType = RHI::ResourceViewType::ShaderResource;
outView.viewDesc.dimension = RHI::ResourceViewDimension::StructuredBuffer;
outView.viewDesc.structureByteStride =
materialBinding->viewDesc.structureByteStride > 0
? materialBinding->viewDesc.structureByteStride
: materialBinding->buffer->GetStride();
break;
case Resources::ShaderResourceType::RawBuffer:
outView.viewType = RHI::ResourceViewType::ShaderResource;
outView.viewDesc.dimension = RHI::ResourceViewDimension::RawBuffer;
break;
case Resources::ShaderResourceType::RWStructuredBuffer:
outView.viewType = RHI::ResourceViewType::UnorderedAccess;
outView.viewDesc.dimension = RHI::ResourceViewDimension::StructuredBuffer;
outView.viewDesc.structureByteStride =
materialBinding->viewDesc.structureByteStride > 0
? materialBinding->viewDesc.structureByteStride
: materialBinding->buffer->GetStride();
break;
case Resources::ShaderResourceType::RWRawBuffer:
outView.viewType = RHI::ResourceViewType::UnorderedAccess;
outView.viewDesc.dimension = RHI::ResourceViewDimension::RawBuffer;
break;
default:
return false;
}
if (outView.viewDesc.dimension == RHI::ResourceViewDimension::StructuredBuffer &&
outView.viewDesc.structureByteStride == 0) {
outView = {};
return false;
}
return outView.IsValid();
}
inline const Resources::Material* ResolveMaterial(
const Components::MeshRendererComponent* meshRenderer,
const Resources::Mesh* mesh,

View File

@@ -16,6 +16,9 @@
#include <vector>
namespace XCEngine {
namespace RHI {
class RHIBuffer;
}
namespace Resources {
enum class MaterialRenderQueue : Core::int32 {
@@ -77,6 +80,18 @@ struct MaterialTextureBinding {
std::shared_ptr<PendingTextureLoadState> pendingLoad;
};
struct MaterialBufferBindingViewDesc {
Core::uint32 firstElement = 0;
Core::uint32 elementCount = 0;
Core::uint32 structureByteStride = 0;
};
struct MaterialBufferBinding {
Containers::String name;
RHI::RHIBuffer* buffer = nullptr;
MaterialBufferBindingViewDesc viewDesc = {};
};
class Material : public IResource {
public:
Material();
@@ -128,6 +143,11 @@ public:
void SetInt(const Containers::String& name, Core::int32 value);
void SetBool(const Containers::String& name, bool value);
void SetTexture(const Containers::String& name, const ResourceHandle<Texture>& texture);
void SetBuffer(const Containers::String& name, RHI::RHIBuffer* buffer);
void SetBuffer(
const Containers::String& name,
RHI::RHIBuffer* buffer,
const MaterialBufferBindingViewDesc& viewDesc);
void SetTextureAssetRef(const Containers::String& name,
const AssetRef& textureRef,
const Containers::String& texturePath = Containers::String());
@@ -140,13 +160,17 @@ public:
Core::int32 GetInt(const Containers::String& name) const;
bool GetBool(const Containers::String& name) const;
ResourceHandle<Texture> GetTexture(const Containers::String& name) const;
RHI::RHIBuffer* GetBuffer(const Containers::String& name) const;
const MaterialBufferBinding* FindBufferBinding(const Containers::String& name) const;
Core::uint32 GetTextureBindingCount() const { return static_cast<Core::uint32>(m_textureBindings.Size()); }
Core::uint32 GetBufferBindingCount() const { return static_cast<Core::uint32>(m_bufferBindings.Size()); }
Containers::String GetTextureBindingName(Core::uint32 index) const;
AssetRef GetTextureBindingAssetRef(Core::uint32 index) const;
Containers::String GetTextureBindingPath(Core::uint32 index) const;
ResourceHandle<Texture> GetTextureBindingLoadedTexture(Core::uint32 index) const;
ResourceHandle<Texture> GetTextureBindingTexture(Core::uint32 index) const;
const Containers::Array<MaterialTextureBinding>& GetTextureBindings() const { return m_textureBindings; }
const Containers::Array<MaterialBufferBinding>& GetBufferBindings() const { return m_bufferBindings; }
std::vector<MaterialProperty> GetProperties() const;
const Containers::Array<Core::uint8>& GetConstantBufferData() const { return m_constantBufferData; }
@@ -159,12 +183,15 @@ public:
bool HasProperty(const Containers::String& name) const;
void RemoveProperty(const Containers::String& name);
void ClearAllProperties();
void RemoveBufferBinding(const Containers::String& name);
void ClearBufferBindings();
private:
const ShaderPropertyDesc* FindShaderPropertyDesc(const Containers::String& name) const;
bool CanAssignPropertyType(const Containers::String& name, MaterialPropertyType type) const;
bool ResetPropertyToShaderDefault(const Containers::String& name);
void SyncShaderSchemaProperties(bool removeUnknownProperties);
void SyncShaderRuntimeBufferBindings(bool removeUnknownBindings);
void BeginAsyncTextureLoad(Core::uint32 index);
void ResolvePendingTextureBinding(Core::uint32 index);
void ResolvePendingTextureBindings();
@@ -182,6 +209,7 @@ private:
Containers::Array<MaterialConstantFieldDesc> m_constantLayout;
Containers::Array<Core::uint8> m_constantBufferData;
Containers::Array<MaterialTextureBinding> m_textureBindings;
Containers::Array<MaterialBufferBinding> m_bufferBindings;
Core::uint64 m_changeVersion = 1;
};

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

View File

@@ -1560,6 +1560,83 @@ TEST(BuiltinPassLayout_Test, RejectsDuplicateBindingsInOneSet) {
EXPECT_EQ(error, "Builtin pass encountered duplicate bindings inside one descriptor set");
}
TEST(BuiltinPassLayout_Test, AcceptsRuntimeMaterialBufferBindingsWithoutBuiltinSemanticMetadata) {
Array<ShaderResourceBindingDesc> bindings;
ShaderResourceBindingDesc perObjectBinding = {};
perObjectBinding.name = "PerObjectConstants";
perObjectBinding.type = ShaderResourceType::ConstantBuffer;
perObjectBinding.set = 0u;
perObjectBinding.binding = 0u;
perObjectBinding.semantic = "PerObject";
bindings.PushBack(perObjectBinding);
ShaderResourceBindingDesc materialBinding = {};
materialBinding.name = "MaterialConstants";
materialBinding.type = ShaderResourceType::ConstantBuffer;
materialBinding.set = 1u;
materialBinding.binding = 0u;
materialBinding.semantic = "Material";
bindings.PushBack(materialBinding);
ShaderResourceBindingDesc bufferBinding = {};
bufferBinding.name = "VolumeNodes";
bufferBinding.type = ShaderResourceType::StructuredBuffer;
bufferBinding.set = 2u;
bufferBinding.binding = 0u;
bindings.PushBack(bufferBinding);
BuiltinPassResourceBindingPlan plan = {};
String error;
ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(bindings, plan, &error)) << error.CStr();
EXPECT_TRUE(plan.usesMaterialBuffers);
ASSERT_EQ(plan.materialBufferBindings.Size(), 1u);
EXPECT_EQ(plan.materialBufferBindings[0].name, "VolumeNodes");
EXPECT_EQ(plan.materialBufferBindings[0].semantic, BuiltinPassResourceSemantic::MaterialBuffer);
EXPECT_EQ(plan.materialBufferBindings[0].resourceType, ShaderResourceType::StructuredBuffer);
EXPECT_EQ(plan.materialBufferBindings[0].location.set, 2u);
EXPECT_EQ(plan.materialBufferBindings[0].location.binding, 0u);
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr();
ASSERT_EQ(setLayouts.size(), 3u);
EXPECT_TRUE(setLayouts[2].usesMaterialBuffers);
ASSERT_EQ(setLayouts[2].materialBufferBindings.size(), 1u);
EXPECT_EQ(setLayouts[2].materialBufferBindings[0].name, "VolumeNodes");
ASSERT_EQ(setLayouts[2].bindings.size(), 1u);
EXPECT_EQ(
static_cast<DescriptorType>(setLayouts[2].bindings[0].type),
DescriptorType::SRV);
EXPECT_EQ(setLayouts[2].bindings[0].resourceDimension, ResourceViewDimension::StructuredBuffer);
}
TEST(BuiltinPassLayout_Test, AcceptsRuntimeMaterialRawUavBufferBindings) {
Array<ShaderResourceBindingDesc> bindings;
ShaderResourceBindingDesc bufferBinding = {};
bufferBinding.name = "VolumeCounters";
bufferBinding.type = ShaderResourceType::RWRawBuffer;
bufferBinding.set = 4u;
bufferBinding.binding = 3u;
bindings.PushBack(bufferBinding);
BuiltinPassResourceBindingPlan plan = {};
String error;
ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(bindings, plan, &error)) << error.CStr();
ASSERT_EQ(plan.materialBufferBindings.Size(), 1u);
EXPECT_EQ(plan.materialBufferBindings[0].semantic, BuiltinPassResourceSemantic::MaterialBuffer);
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr();
ASSERT_EQ(setLayouts.size(), 5u);
ASSERT_EQ(setLayouts[4].bindings.size(), 1u);
EXPECT_EQ(
static_cast<DescriptorType>(setLayouts[4].bindings[0].type),
DescriptorType::UAV);
EXPECT_EQ(setLayouts[4].bindings[0].resourceDimension, ResourceViewDimension::RawBuffer);
EXPECT_TRUE(setLayouts[4].usesMaterialBuffers);
}
TEST(BuiltinDepthOnlyPass_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) {
const InputLayoutDesc inputLayout = BuiltinDepthOnlyPass::BuildInputLayout();

View File

@@ -13,12 +13,15 @@
#include <XCEngine/Rendering/Materials/RenderMaterialResolve.h>
#include <XCEngine/Rendering/Materials/RenderMaterialStateUtils.h>
#include <XCEngine/Rendering/Extraction/RenderSceneExtractor.h>
#include <XCEngine/RHI/RHIBuffer.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Shader/Shader.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <XCEngine/Scene/Scene.h>
#include <string>
using namespace XCEngine::Components;
using namespace XCEngine::Core;
using namespace XCEngine::Math;
@@ -27,6 +30,34 @@ using namespace XCEngine::Resources;
namespace {
class MockRenderBuffer final : public XCEngine::RHI::RHIBuffer {
public:
explicit MockRenderBuffer(uint64_t size = 512u, uint32_t stride = 16u)
: m_size(size)
, m_stride(stride) {
}
void* Map() override { return nullptr; }
void Unmap() override {}
void SetData(const void*, size_t, size_t) override {}
uint64_t GetSize() const override { return m_size; }
XCEngine::RHI::BufferType GetBufferType() const override { return XCEngine::RHI::BufferType::Storage; }
void SetBufferType(XCEngine::RHI::BufferType) override {}
uint32_t GetStride() const override { return m_stride; }
void SetStride(uint32_t stride) override { m_stride = stride; }
void* GetNativeHandle() override { return nullptr; }
XCEngine::RHI::ResourceStates GetState() const override { return XCEngine::RHI::ResourceStates::Common; }
void SetState(XCEngine::RHI::ResourceStates) override {}
const std::string& GetName() const override { return m_name; }
void SetName(const std::string& name) override { m_name = name; }
void Shutdown() override {}
private:
uint64_t m_size = 0;
uint32_t m_stride = 0;
std::string m_name;
};
Mesh* CreateTestMesh(const char* path) {
auto* mesh = new Mesh();
IResource::ConstructParams params = {};
@@ -853,6 +884,30 @@ TEST(RenderMaterialUtility_Test, DoesNotUseOpacityFallbackWithoutFormalShaderSem
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&material), Vector4(1.0f, 1.0f, 1.0f, 1.0f));
}
TEST(RenderMaterialUtility_Test, ResolvesRuntimeStructuredBufferIntoBufferViewMetadata) {
Material material;
MockRenderBuffer buffer(1024u, 32u);
MaterialBufferBindingViewDesc viewDesc = {};
viewDesc.firstElement = 2u;
viewDesc.elementCount = 6u;
material.SetBuffer("VolumeNodes", &buffer, viewDesc);
BuiltinPassResourceBindingDesc binding = {};
binding.name = "VolumeNodes";
binding.semantic = BuiltinPassResourceSemantic::MaterialBuffer;
binding.resourceType = ShaderResourceType::StructuredBuffer;
binding.location = { 2u, 1u };
MaterialBufferResourceView resolvedView = {};
ASSERT_TRUE(TryResolveMaterialBufferResourceView(&material, binding, resolvedView));
EXPECT_EQ(resolvedView.buffer, &buffer);
EXPECT_EQ(resolvedView.viewType, XCEngine::RHI::ResourceViewType::ShaderResource);
EXPECT_EQ(resolvedView.viewDesc.dimension, XCEngine::RHI::ResourceViewDimension::StructuredBuffer);
EXPECT_EQ(resolvedView.viewDesc.firstElement, 2u);
EXPECT_EQ(resolvedView.viewDesc.elementCount, 6u);
EXPECT_EQ(resolvedView.viewDesc.structureByteStride, 32u);
}
TEST(RenderMaterialUtility_Test, MapsMaterialRenderStateToRhiDescriptors) {
Material material;
MaterialRenderState renderState;

View File

@@ -6,14 +6,50 @@
#include <XCEngine/Core/Math/Vector2.h>
#include <XCEngine/Core/Math/Vector3.h>
#include <XCEngine/Core/Math/Vector4.h>
#include <XCEngine/RHI/RHIBuffer.h>
#include <initializer_list>
#include <string>
using namespace XCEngine::Resources;
using namespace XCEngine::Math;
namespace {
class MockMaterialBuffer final : public XCEngine::RHI::RHIBuffer {
public:
explicit MockMaterialBuffer(
uint64_t size = 256u,
uint32_t stride = 16u,
XCEngine::RHI::BufferType type = XCEngine::RHI::BufferType::Storage)
: m_size(size)
, m_stride(stride)
, m_type(type) {
}
void* Map() override { return nullptr; }
void Unmap() override {}
void SetData(const void*, size_t, size_t) override {}
uint64_t GetSize() const override { return m_size; }
XCEngine::RHI::BufferType GetBufferType() const override { return m_type; }
void SetBufferType(XCEngine::RHI::BufferType type) override { m_type = type; }
uint32_t GetStride() const override { return m_stride; }
void SetStride(uint32_t stride) override { m_stride = stride; }
void* GetNativeHandle() override { return nullptr; }
XCEngine::RHI::ResourceStates GetState() const override { return m_state; }
void SetState(XCEngine::RHI::ResourceStates state) override { m_state = state; }
const std::string& GetName() const override { return m_name; }
void SetName(const std::string& name) override { m_name = name; }
void Shutdown() override {}
private:
uint64_t m_size = 0;
uint32_t m_stride = 0;
XCEngine::RHI::BufferType m_type = XCEngine::RHI::BufferType::Storage;
XCEngine::RHI::ResourceStates m_state = XCEngine::RHI::ResourceStates::Common;
std::string m_name;
};
Shader* CreateMaterialSchemaShader() {
auto* shader = new Shader();
@@ -326,6 +362,56 @@ TEST(Material, ChangeVersionIncrementsWhenMaterialMutates) {
EXPECT_GT(material.GetChangeVersion(), afterFloatVersion);
}
TEST(Material, SetBufferStoresRuntimeOnlyBindingMetadata) {
Material material;
MockMaterialBuffer buffer(512u, 32u);
MaterialBufferBindingViewDesc viewDesc = {};
viewDesc.firstElement = 4u;
viewDesc.elementCount = 8u;
viewDesc.structureByteStride = 32u;
material.SetBuffer("VolumeNodes", &buffer, viewDesc);
ASSERT_EQ(material.GetBufferBindingCount(), 1u);
EXPECT_EQ(material.GetBuffer("VolumeNodes"), &buffer);
const MaterialBufferBinding* binding = material.FindBufferBinding("VolumeNodes");
ASSERT_NE(binding, nullptr);
EXPECT_EQ(binding->name, "VolumeNodes");
EXPECT_EQ(binding->buffer, &buffer);
EXPECT_EQ(binding->viewDesc.firstElement, 4u);
EXPECT_EQ(binding->viewDesc.elementCount, 8u);
EXPECT_EQ(binding->viewDesc.structureByteStride, 32u);
EXPECT_FALSE(material.HasProperty("VolumeNodes"));
}
TEST(Material, SetBufferNullRemovesRuntimeBinding) {
Material material;
MockMaterialBuffer buffer;
material.SetBuffer("VolumeNodes", &buffer);
ASSERT_EQ(material.GetBufferBindingCount(), 1u);
material.SetBuffer("VolumeNodes", nullptr);
EXPECT_EQ(material.GetBufferBindingCount(), 0u);
EXPECT_EQ(material.GetBuffer("VolumeNodes"), nullptr);
}
TEST(Material, ClearAllPropertiesDoesNotRemoveRuntimeBufferBindings) {
Material material;
MockMaterialBuffer buffer;
material.SetFloat("uTime", 1.0f);
material.SetBuffer("VolumeNodes", &buffer);
ASSERT_TRUE(material.HasProperty("uTime"));
ASSERT_EQ(material.GetBufferBindingCount(), 1u);
material.ClearAllProperties();
EXPECT_FALSE(material.HasProperty("uTime"));
EXPECT_EQ(material.GetBufferBindingCount(), 1u);
EXPECT_EQ(material.GetBuffer("VolumeNodes"), &buffer);
}
TEST(Material, UpdateConstantBufferPacksNumericPropertiesIntoStableSlots) {
Material material;
material.SetFloat("alpha", 3.5f);
@@ -536,6 +622,44 @@ TEST(Material, SwitchingShaderResyncsPropertiesAgainstNewSchema) {
EXPECT_EQ(material.GetFloat4("OnlyB"), Vector4(0.1f, 0.2f, 0.3f, 0.4f));
}
TEST(Material, SwitchingShaderDropsUnknownRuntimeBufferBindings) {
Material material;
auto* shaderA = new Shader();
ShaderPass passA = {};
passA.name = "ForwardLit";
ShaderResourceBindingDesc nodesBinding = {};
nodesBinding.name = "VolumeNodes";
nodesBinding.type = ShaderResourceType::StructuredBuffer;
nodesBinding.set = 2u;
nodesBinding.binding = 0u;
passA.resources.PushBack(nodesBinding);
shaderA->AddPass(passA);
auto* shaderB = new Shader();
ShaderPass passB = {};
passB.name = "ForwardLit";
ShaderResourceBindingDesc bricksBinding = {};
bricksBinding.name = "VolumeBricks";
bricksBinding.type = ShaderResourceType::StructuredBuffer;
bricksBinding.set = 2u;
bricksBinding.binding = 0u;
passB.resources.PushBack(bricksBinding);
shaderB->AddPass(passB);
MockMaterialBuffer nodesBuffer;
MockMaterialBuffer bricksBuffer;
material.SetShader(ResourceHandle<Shader>(shaderA));
material.SetBuffer("VolumeNodes", &nodesBuffer);
material.SetBuffer("VolumeBricks", &bricksBuffer);
material.SetShader(ResourceHandle<Shader>(shaderB));
EXPECT_EQ(material.GetBuffer("VolumeNodes"), nullptr);
EXPECT_EQ(material.GetBuffer("VolumeBricks"), &bricksBuffer);
EXPECT_EQ(material.GetBufferBindingCount(), 1u);
}
TEST(Material, UpdateConstantBufferFollowsShaderSchemaOrderInsteadOfAlphabeticalOrder) {
Material material;