From 6bf9203eecff554498492f578d075c9fbd1ff3b1 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Wed, 8 Apr 2026 19:18:07 +0800 Subject: [PATCH] Add runtime material buffer bindings --- .../Builtin/BuiltinPassLayoutUtils.h | 22 ++- .../Builtin/BuiltinPassMetadataUtils.h | 21 +++ .../Rendering/Builtin/BuiltinPassTypes.h | 6 + .../Rendering/Caches/RenderResourceCache.h | 42 ++++++ .../Materials/RenderMaterialResolve.h | 84 ++++++++++++ .../XCEngine/Resources/Material/Material.h | 28 ++++ .../Rendering/Caches/RenderResourceCache.cpp | 84 ++++++++++++ .../BuiltinDepthStylePassBaseResources.cpp | 93 ++++++++++--- .../BuiltinForwardPipelineResources.cpp | 38 +++++- engine/src/Resources/Material/Material.cpp | 129 ++++++++++++++++++ .../unit/test_builtin_forward_pipeline.cpp | 77 +++++++++++ .../unit/test_render_scene_extractor.cpp | 55 ++++++++ tests/Resources/Material/test_material.cpp | 124 +++++++++++++++++ 13 files changed, 781 insertions(+), 22 deletions(-) diff --git a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h index 424c072f..4b467320 100644 --- a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h +++ b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h @@ -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; diff --git a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h index 4c690268..a5613e8c 100644 --- a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h +++ b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h @@ -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: diff --git a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassTypes.h b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassTypes.h index fbd77c4a..4bd9b29b 100644 --- a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassTypes.h +++ b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassTypes.h @@ -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 bindings; + Containers::Array 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 bindings; + std::vector 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; diff --git a/engine/include/XCEngine/Rendering/Caches/RenderResourceCache.h b/engine/include/XCEngine/Rendering/Caches/RenderResourceCache.h index af705e23..946f4753 100644 --- a/engine/include/XCEngine/Rendering/Caches/RenderResourceCache.h +++ b/engine/include/XCEngine/Rendering/Caches/RenderResourceCache.h @@ -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 m_meshCache; std::unordered_map m_textureCache; + std::unordered_map m_bufferViewCache; }; } // namespace Rendering diff --git a/engine/include/XCEngine/Rendering/Materials/RenderMaterialResolve.h b/engine/include/XCEngine/Rendering/Materials/RenderMaterialResolve.h index d1860a76..3f7e6b1b 100644 --- a/engine/include/XCEngine/Rendering/Materials/RenderMaterialResolve.h +++ b/engine/include/XCEngine/Rendering/Materials/RenderMaterialResolve.h @@ -2,6 +2,9 @@ #include #include +#include +#include +#include #include #include #include @@ -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, diff --git a/engine/include/XCEngine/Resources/Material/Material.h b/engine/include/XCEngine/Resources/Material/Material.h index 7883e8b7..67ca7f58 100644 --- a/engine/include/XCEngine/Resources/Material/Material.h +++ b/engine/include/XCEngine/Resources/Material/Material.h @@ -16,6 +16,9 @@ #include namespace XCEngine { +namespace RHI { +class RHIBuffer; +} namespace Resources { enum class MaterialRenderQueue : Core::int32 { @@ -77,6 +80,18 @@ struct MaterialTextureBinding { std::shared_ptr 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); + 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 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(m_textureBindings.Size()); } + Core::uint32 GetBufferBindingCount() const { return static_cast(m_bufferBindings.Size()); } Containers::String GetTextureBindingName(Core::uint32 index) const; AssetRef GetTextureBindingAssetRef(Core::uint32 index) const; Containers::String GetTextureBindingPath(Core::uint32 index) const; ResourceHandle GetTextureBindingLoadedTexture(Core::uint32 index) const; ResourceHandle GetTextureBindingTexture(Core::uint32 index) const; const Containers::Array& GetTextureBindings() const { return m_textureBindings; } + const Containers::Array& GetBufferBindings() const { return m_bufferBindings; } std::vector GetProperties() const; const Containers::Array& 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 m_constantLayout; Containers::Array m_constantBufferData; Containers::Array m_textureBindings; + Containers::Array m_bufferBindings; Core::uint64 m_changeVersion = 1; }; diff --git a/engine/src/Rendering/Caches/RenderResourceCache.cpp b/engine/src/Rendering/Caches/RenderResourceCache.cpp index 74629fb4..fa7ea0a2 100644 --- a/engine/src/Rendering/Caches/RenderResourceCache.cpp +++ b/engine/src/Rendering/Caches/RenderResourceCache.cpp @@ -3,12 +3,17 @@ #include #include +#include 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{}(static_cast(key.buffer)); + HashCombine(hash, std::hash{}(static_cast(key.viewType))); + HashCombine(hash, std::hash{}(key.format)); + HashCombine(hash, std::hash{}(static_cast(key.dimension))); + HashCombine(hash, std::hash{}(key.bufferLocation)); + HashCombine(hash, std::hash{}(key.firstElement)); + HashCombine(hash, std::hash{}(key.elementCount)); + HashCombine(hash, std::hash{}(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 diff --git a/engine/src/Rendering/Passes/BuiltinDepthStylePassBaseResources.cpp b/engine/src/Rendering/Passes/BuiltinDepthStylePassBaseResources.cpp index f0f3e09b..09a624b7 100644 --- a/engine/src/Rendering/Passes/BuiltinDepthStylePassBaseResources.cpp +++ b/engine/src/Rendering/Passes/BuiltinDepthStylePassBaseResources.cpp @@ -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, diff --git a/engine/src/Rendering/Pipelines/BuiltinForwardPipelineResources.cpp b/engine/src/Rendering/Pipelines/BuiltinForwardPipelineResources.cpp index b256a5c9..5099b767 100644 --- a/engine/src/Rendering/Pipelines/BuiltinForwardPipelineResources.cpp +++ b/engine/src/Rendering/Pipelines/BuiltinForwardPipelineResources.cpp @@ -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, diff --git a/engine/src/Resources/Material/Material.cpp b/engine/src/Resources/Material/Material.cpp index 033ac46d..7b3414de 100644 --- a/engine/src/Resources/Material/Material.cpp +++ b/engine/src/Resources/Material/Material.cpp @@ -81,6 +81,20 @@ void RemoveTextureBindingByName( } } +void RemoveBufferBindingByName( + Containers::Array& 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& 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) { m_shader = shader; SyncShaderSchemaProperties(true); + SyncShaderRuntimeBufferBindings(true); SyncShaderSchemaKeywords(true); MarkChanged(true); } @@ -643,6 +659,36 @@ void Material::SetTexture(const Containers::String& name, const ResourceHandle Material::GetTexture(const Containers::String& name) con return ResourceHandle(); } +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 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 diff --git a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp index 43605267..7464b2e8 100644 --- a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp +++ b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp @@ -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 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 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(setLayouts[2].bindings[0].type), + DescriptorType::SRV); + EXPECT_EQ(setLayouts[2].bindings[0].resourceDimension, ResourceViewDimension::StructuredBuffer); +} + +TEST(BuiltinPassLayout_Test, AcceptsRuntimeMaterialRawUavBufferBindings) { + Array 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 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(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(); diff --git a/tests/Rendering/unit/test_render_scene_extractor.cpp b/tests/Rendering/unit/test_render_scene_extractor.cpp index 92fca808..cd640533 100644 --- a/tests/Rendering/unit/test_render_scene_extractor.cpp +++ b/tests/Rendering/unit/test_render_scene_extractor.cpp @@ -13,12 +13,15 @@ #include #include #include +#include #include #include #include #include #include +#include + 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; diff --git a/tests/Resources/Material/test_material.cpp b/tests/Resources/Material/test_material.cpp index 3386fbf3..815ac903 100644 --- a/tests/Resources/Material/test_material.cpp +++ b/tests/Resources/Material/test_material.cpp @@ -6,14 +6,50 @@ #include #include #include +#include #include +#include 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(shaderA)); + material.SetBuffer("VolumeNodes", &nodesBuffer); + material.SetBuffer("VolumeBricks", &bricksBuffer); + + material.SetShader(ResourceHandle(shaderB)); + + EXPECT_EQ(material.GetBuffer("VolumeNodes"), nullptr); + EXPECT_EQ(material.GetBuffer("VolumeBricks"), &bricksBuffer); + EXPECT_EQ(material.GetBufferBindingCount(), 1u); +} + TEST(Material, UpdateConstantBufferFollowsShaderSchemaOrderInsteadOfAlphabeticalOrder) { Material material;