Add runtime material buffer bindings
This commit is contained in:
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user