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

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