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

@@ -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;