diff --git a/engine/assets/builtin/shaders/gaussian-splat-utilities.shader b/engine/assets/builtin/shaders/gaussian-splat-utilities.shader new file mode 100644 index 00000000..81f1daed --- /dev/null +++ b/engine/assets/builtin/shaders/gaussian-splat-utilities.shader @@ -0,0 +1,56 @@ +Shader "Builtin Gaussian Splat Utilities" +{ + HLSLINCLUDE + uint FloatToSortableUint(float value) + { + const uint rawValue = asuint(value); + const uint mask = (rawValue & 0x80000000u) != 0u ? 0xffffffffu : 0x80000000u; + return rawValue ^ mask; + } + ENDHLSL + + SubShader + { + Pass + { + Name "GaussianSplatPrepareOrder" + HLSLPROGRAM + #pragma target 4.5 + #pragma compute GaussianSplatPrepareOrderCS + + cbuffer PerObjectConstants + { + float4x4 gProjectionMatrix; + float4x4 gViewMatrix; + float4x4 gModelMatrix; + float4 gCameraRight; + float4 gCameraUp; + }; + + StructuredBuffer GaussianSplatPositions; + RWStructuredBuffer GaussianSplatSortDistances; + RWStructuredBuffer GaussianSplatOrderBuffer; + + [numthreads(64, 1, 1)] + void GaussianSplatPrepareOrderCS(uint3 dispatchThreadId : SV_DispatchThreadID) + { + uint splatCount = 0u; + GaussianSplatOrderBuffer.GetDimensions(splatCount); + + const uint index = dispatchThreadId.x; + if (index >= splatCount) + { + return; + } + + GaussianSplatOrderBuffer[index] = index; + + const float3 localCenter = GaussianSplatPositions[index]; + const float3 viewCenter = + mul(gViewMatrix, mul(gModelMatrix, float4(localCenter, 1.0))).xyz; + GaussianSplatSortDistances[index] = FloatToSortableUint(viewCenter.z); + } + ENDHLSL + } + } +} diff --git a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h index 41c4bf5f..2acb6964 100644 --- a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h +++ b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h @@ -62,6 +62,12 @@ inline bool TryBuildBuiltinPassResourceBindingPlan( case BuiltinPassResourceSemantic::VolumeField: location = &outPlan.volumeField; break; + case BuiltinPassResourceSemantic::GaussianSplatSortDistanceBuffer: + location = &outPlan.gaussianSplatSortDistanceBuffer; + break; + case BuiltinPassResourceSemantic::GaussianSplatOrderBuffer: + location = &outPlan.gaussianSplatOrderBuffer; + break; case BuiltinPassResourceSemantic::GaussianSplatPositionBuffer: location = &outPlan.gaussianSplatPositionBuffer; break; @@ -74,6 +80,9 @@ inline bool TryBuildBuiltinPassResourceBindingPlan( case BuiltinPassResourceSemantic::GaussianSplatSHBuffer: location = &outPlan.gaussianSplatSHBuffer; break; + case BuiltinPassResourceSemantic::GaussianSplatViewDataBuffer: + location = &outPlan.gaussianSplatViewDataBuffer; + break; case BuiltinPassResourceSemantic::BaseColorTexture: location = &outPlan.baseColorTexture; break; @@ -600,6 +609,12 @@ inline bool TryBuildBuiltinPassSetLayouts( case BuiltinPassResourceSemantic::VolumeField: setLayout.usesVolumeField = true; break; + case BuiltinPassResourceSemantic::GaussianSplatSortDistanceBuffer: + setLayout.usesGaussianSplatSortDistanceBuffer = true; + break; + case BuiltinPassResourceSemantic::GaussianSplatOrderBuffer: + setLayout.usesGaussianSplatOrderBuffer = true; + break; case BuiltinPassResourceSemantic::GaussianSplatPositionBuffer: setLayout.usesGaussianSplatPositionBuffer = true; break; @@ -612,6 +627,9 @@ inline bool TryBuildBuiltinPassSetLayouts( case BuiltinPassResourceSemantic::GaussianSplatSHBuffer: setLayout.usesGaussianSplatSHBuffer = true; break; + case BuiltinPassResourceSemantic::GaussianSplatViewDataBuffer: + setLayout.usesGaussianSplatViewDataBuffer = true; + break; case BuiltinPassResourceSemantic::BaseColorTexture: setLayout.usesTexture = true; setLayout.usesBaseColorTexture = true; diff --git a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h index b99875be..553769c0 100644 --- a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h +++ b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h @@ -147,6 +147,18 @@ inline BuiltinPassResourceSemantic ResolveBuiltinPassResourceSemantic( return BuiltinPassResourceSemantic::VolumeField; } + if (semantic == Containers::String("gaussiansplatsortdistancebuffer") || + semantic == Containers::String("gaussiansplatsortdistances") || + semantic == Containers::String("splatsortdistances")) { + return BuiltinPassResourceSemantic::GaussianSplatSortDistanceBuffer; + } + + if (semantic == Containers::String("gaussiansplatorderbuffer") || + semantic == Containers::String("gaussiansplatorder") || + semantic == Containers::String("orderbuffer")) { + return BuiltinPassResourceSemantic::GaussianSplatOrderBuffer; + } + if (semantic == Containers::String("gaussiansplatpositionbuffer") || semantic == Containers::String("gaussiansplatpositions") || semantic == Containers::String("splatpositions")) { @@ -171,6 +183,12 @@ inline BuiltinPassResourceSemantic ResolveBuiltinPassResourceSemantic( return BuiltinPassResourceSemantic::GaussianSplatSHBuffer; } + if (semantic == Containers::String("gaussiansplatviewdatabuffer") || + semantic == Containers::String("gaussiansplatviewdata") || + semantic == Containers::String("splatviewdata")) { + return BuiltinPassResourceSemantic::GaussianSplatViewDataBuffer; + } + if (semantic == Containers::String("basecolortexture") || semantic == Containers::String("maintex")) { return BuiltinPassResourceSemantic::BaseColorTexture; @@ -233,6 +251,10 @@ inline const char* BuiltinPassResourceSemanticToString(BuiltinPassResourceSemant return "PassConstants"; case BuiltinPassResourceSemantic::VolumeField: return "VolumeField"; + case BuiltinPassResourceSemantic::GaussianSplatSortDistanceBuffer: + return "GaussianSplatSortDistanceBuffer"; + case BuiltinPassResourceSemantic::GaussianSplatOrderBuffer: + return "GaussianSplatOrderBuffer"; case BuiltinPassResourceSemantic::GaussianSplatPositionBuffer: return "GaussianSplatPositionBuffer"; case BuiltinPassResourceSemantic::GaussianSplatOtherBuffer: @@ -241,6 +263,8 @@ inline const char* BuiltinPassResourceSemanticToString(BuiltinPassResourceSemant return "GaussianSplatColorBuffer"; case BuiltinPassResourceSemantic::GaussianSplatSHBuffer: return "GaussianSplatSHBuffer"; + case BuiltinPassResourceSemantic::GaussianSplatViewDataBuffer: + return "GaussianSplatViewDataBuffer"; case BuiltinPassResourceSemantic::BaseColorTexture: return "BaseColorTexture"; case BuiltinPassResourceSemantic::SourceColorTexture: @@ -329,12 +353,17 @@ inline bool IsBuiltinPassResourceTypeCompatible( case BuiltinPassResourceSemantic::VolumeField: return type == Resources::ShaderResourceType::StructuredBuffer || type == Resources::ShaderResourceType::RawBuffer; + case BuiltinPassResourceSemantic::GaussianSplatSortDistanceBuffer: + case BuiltinPassResourceSemantic::GaussianSplatOrderBuffer: case BuiltinPassResourceSemantic::GaussianSplatPositionBuffer: case BuiltinPassResourceSemantic::GaussianSplatOtherBuffer: case BuiltinPassResourceSemantic::GaussianSplatColorBuffer: case BuiltinPassResourceSemantic::GaussianSplatSHBuffer: + case BuiltinPassResourceSemantic::GaussianSplatViewDataBuffer: return type == Resources::ShaderResourceType::StructuredBuffer || - type == Resources::ShaderResourceType::RawBuffer; + 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 f40bcde0..95295672 100644 --- a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassTypes.h +++ b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassTypes.h @@ -46,11 +46,13 @@ enum class BuiltinPassResourceSemantic : Core::uint8 { Environment, PassConstants, VolumeField, + GaussianSplatSortDistanceBuffer, GaussianSplatOrderBuffer, GaussianSplatPositionBuffer, GaussianSplatOtherBuffer, GaussianSplatColorBuffer, GaussianSplatSHBuffer, + GaussianSplatViewDataBuffer, BaseColorTexture, SourceColorTexture, SkyboxPanoramicTexture, @@ -84,11 +86,13 @@ struct BuiltinPassResourceBindingPlan { PassResourceBindingLocation shadowReceiver = {}; PassResourceBindingLocation environment = {}; PassResourceBindingLocation passConstants = {}; + PassResourceBindingLocation gaussianSplatSortDistanceBuffer = {}; PassResourceBindingLocation gaussianSplatOrderBuffer = {}; PassResourceBindingLocation gaussianSplatPositionBuffer = {}; PassResourceBindingLocation gaussianSplatOtherBuffer = {}; PassResourceBindingLocation gaussianSplatColorBuffer = {}; PassResourceBindingLocation gaussianSplatSHBuffer = {}; + PassResourceBindingLocation gaussianSplatViewDataBuffer = {}; PassResourceBindingLocation baseColorTexture = {}; PassResourceBindingLocation sourceColorTexture = {}; PassResourceBindingLocation skyboxPanoramicTexture = {}; @@ -122,11 +126,13 @@ struct BuiltinPassSetLayoutMetadata { bool usesPassConstants = false; bool usesMaterialBuffers = false; bool usesVolumeField = false; + bool usesGaussianSplatSortDistanceBuffer = false; bool usesGaussianSplatOrderBuffer = false; bool usesGaussianSplatPositionBuffer = false; bool usesGaussianSplatOtherBuffer = false; bool usesGaussianSplatColorBuffer = false; bool usesGaussianSplatSHBuffer = false; + bool usesGaussianSplatViewDataBuffer = false; bool usesTexture = false; bool usesBaseColorTexture = false; bool usesSourceColorTexture = false; diff --git a/engine/include/XCEngine/Resources/BuiltinResources.h b/engine/include/XCEngine/Resources/BuiltinResources.h index 1e1c6ed4..72514396 100644 --- a/engine/include/XCEngine/Resources/BuiltinResources.h +++ b/engine/include/XCEngine/Resources/BuiltinResources.h @@ -39,6 +39,7 @@ Containers::String GetBuiltinSelectionMaskShaderPath(); Containers::String GetBuiltinSelectionOutlineShaderPath(); Containers::String GetBuiltinSkyboxShaderPath(); Containers::String GetBuiltinGaussianSplatShaderPath(); +Containers::String GetBuiltinGaussianSplatUtilitiesShaderPath(); Containers::String GetBuiltinVolumetricShaderPath(); Containers::String GetBuiltinColorScalePostProcessShaderPath(); Containers::String GetBuiltinFinalColorShaderPath(); diff --git a/engine/src/Resources/BuiltinResources.cpp b/engine/src/Resources/BuiltinResources.cpp index 73ce3106..19a2e60b 100644 --- a/engine/src/Resources/BuiltinResources.cpp +++ b/engine/src/Resources/BuiltinResources.cpp @@ -37,6 +37,8 @@ constexpr const char* kBuiltinSelectionMaskShaderPath = "builtin://shaders/selec constexpr const char* kBuiltinSelectionOutlineShaderPath = "builtin://shaders/selection-outline"; constexpr const char* kBuiltinSkyboxShaderPath = "builtin://shaders/skybox"; constexpr const char* kBuiltinGaussianSplatShaderPath = "builtin://shaders/gaussian-splat"; +constexpr const char* kBuiltinGaussianSplatUtilitiesShaderPath = + "builtin://shaders/gaussian-splat-utilities"; constexpr const char* kBuiltinVolumetricShaderPath = "builtin://shaders/volumetric"; constexpr const char* kBuiltinColorScalePostProcessShaderPath = "builtin://shaders/color-scale-post-process"; @@ -71,6 +73,8 @@ constexpr const char* kBuiltinSkyboxShaderAssetRelativePath = "engine/assets/builtin/shaders/skybox.shader"; constexpr const char* kBuiltinGaussianSplatShaderAssetRelativePath = "engine/assets/builtin/shaders/gaussian-splat.shader"; +constexpr const char* kBuiltinGaussianSplatUtilitiesShaderAssetRelativePath = + "engine/assets/builtin/shaders/gaussian-splat-utilities.shader"; constexpr const char* kBuiltinVolumetricShaderAssetRelativePath = "engine/assets/builtin/shaders/volumetric.shader"; constexpr const char* kBuiltinColorScalePostProcessShaderAssetRelativePath = @@ -170,6 +174,9 @@ const char* GetBuiltinShaderAssetRelativePath(const Containers::String& builtinS if (builtinShaderPath == Containers::String(kBuiltinGaussianSplatShaderPath)) { return kBuiltinGaussianSplatShaderAssetRelativePath; } + if (builtinShaderPath == Containers::String(kBuiltinGaussianSplatUtilitiesShaderPath)) { + return kBuiltinGaussianSplatUtilitiesShaderAssetRelativePath; + } if (builtinShaderPath == Containers::String(kBuiltinVolumetricShaderPath)) { return kBuiltinVolumetricShaderAssetRelativePath; } @@ -698,6 +705,9 @@ size_t CalculateBuiltinShaderMemorySize(const Shader& shader) { memorySize += variant.profile.Length(); memorySize += variant.sourceCode.Length(); memorySize += variant.compiledBinary.Size(); + for (const ShaderBackendCompiledBinary& record : variant.backendCompiledBinaries) { + memorySize += record.payload.Size(); + } } } @@ -866,6 +876,10 @@ bool TryGetBuiltinShaderPathByShaderName( outPath = GetBuiltinGaussianSplatShaderPath(); return true; } + if (shaderName == "Builtin Gaussian Splat Utilities") { + outPath = GetBuiltinGaussianSplatUtilitiesShaderPath(); + return true; + } if (shaderName == "Builtin Volumetric") { outPath = GetBuiltinVolumetricShaderPath(); return true; @@ -957,6 +971,10 @@ Containers::String GetBuiltinGaussianSplatShaderPath() { return Containers::String(kBuiltinGaussianSplatShaderPath); } +Containers::String GetBuiltinGaussianSplatUtilitiesShaderPath() { + return Containers::String(kBuiltinGaussianSplatUtilitiesShaderPath); +} + Containers::String GetBuiltinVolumetricShaderPath() { return Containers::String(kBuiltinVolumetricShaderPath); } @@ -1077,6 +1095,8 @@ LoadResult CreateBuiltinShaderResource(const Containers::String& path) { shader = BuildBuiltinSkyboxShader(path); } else if (path == GetBuiltinGaussianSplatShaderPath()) { shader = BuildBuiltinGaussianSplatShader(path); + } else if (path == GetBuiltinGaussianSplatUtilitiesShaderPath()) { + shader = TryLoadBuiltinShaderFromAsset(path); } else if (path == GetBuiltinVolumetricShaderPath()) { shader = BuildBuiltinVolumetricShader(path); } else if (path == GetBuiltinColorScalePostProcessShaderPath()) { diff --git a/engine/src/Resources/Shader/Internal/ShaderRuntimeBuildUtils.cpp b/engine/src/Resources/Shader/Internal/ShaderRuntimeBuildUtils.cpp index e7008726..741331e0 100644 --- a/engine/src/Resources/Shader/Internal/ShaderRuntimeBuildUtils.cpp +++ b/engine/src/Resources/Shader/Internal/ShaderRuntimeBuildUtils.cpp @@ -192,8 +192,9 @@ void ImportConcretePass(Shader& shader, const ShaderPass& sourcePass) { shader.AddPass(sourcePass); } -bool IsBufferShaderResourceType(ShaderResourceType type) { - return type == ShaderResourceType::StructuredBuffer || +bool IsScannedAuthoringResourceType(ShaderResourceType type) { + return type == ShaderResourceType::ConstantBuffer || + type == ShaderResourceType::StructuredBuffer || type == ShaderResourceType::RawBuffer || type == ShaderResourceType::RWStructuredBuffer || type == ShaderResourceType::RWRawBuffer; @@ -239,10 +240,13 @@ Core::uint32 FindNextBindingInSet( return nextBinding; } -bool TryParseHlslBufferResourceLine( +bool TryParseHlslAuthoringResourceLine( const std::string& line, ShaderResourceType& outType, Containers::String& outName) { + static const std::regex kConstantBufferPattern( + R"(^\s*cbuffer\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?::\s*register\s*\([^\)]*\))?\s*(?:\{\s*)?$)", + std::regex::ECMAScript); static const std::regex kStructuredPattern( R"(^\s*(?:globallycoherent\s+)?(RWStructuredBuffer|StructuredBuffer)\s*<[^;\r\n>]+>\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?::\s*register\s*\([^\)]*\))?\s*;)", std::regex::ECMAScript); @@ -251,6 +255,12 @@ bool TryParseHlslBufferResourceLine( std::regex::ECMAScript); std::smatch match; + if (std::regex_match(line, match, kConstantBufferPattern) && match.size() >= 2u) { + outType = ShaderResourceType::ConstantBuffer; + outName = match[1].str().c_str(); + return true; + } + if (std::regex_match(line, match, kStructuredPattern) && match.size() >= 3u) { outType = match[1].str() == "RWStructuredBuffer" @@ -272,7 +282,7 @@ bool TryParseHlslBufferResourceLine( return false; } -void AppendScannedBufferResourceBindings( +void AppendScannedAuthoringResourceBindings( const Containers::String& sourceText, Containers::Array& ioBindings) { if (sourceText.Empty()) { @@ -286,11 +296,11 @@ void AppendScannedBufferResourceBindings( const std::string line = Internal::StripAuthoringLineComment(rawLine); ShaderResourceType resourceType = ShaderResourceType::ConstantBuffer; Containers::String resourceName; - if (!TryParseHlslBufferResourceLine(line, resourceType, resourceName)) { + if (!TryParseHlslAuthoringResourceLine(line, resourceType, resourceName)) { continue; } - if (!IsBufferShaderResourceType(resourceType) || + if (!IsScannedAuthoringResourceType(resourceType) || resourceName.Empty() || HasResourceBindingNamed(ioBindings, resourceName)) { continue; @@ -354,7 +364,7 @@ ShaderPass BuildConcretePass( } } } - AppendScannedBufferResourceBindings(strippedCombinedSource, shaderPass.resources); + AppendScannedAuthoringResourceBindings(strippedCombinedSource, shaderPass.resources); const std::vector keywordSets = BuildShaderKeywordVariantSets(pass.keywordDeclarations); @@ -615,6 +625,9 @@ size_t CalculateShaderMemorySize(const Shader& shader) { memorySize += variant.profile.Length(); memorySize += variant.sourceCode.Length(); memorySize += variant.compiledBinary.Size(); + for (const ShaderBackendCompiledBinary& record : variant.backendCompiledBinaries) { + memorySize += record.payload.Size(); + } } } diff --git a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp index d538ff0b..11c2d317 100644 --- a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp +++ b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp @@ -1087,6 +1087,201 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringGauss delete shader; } +TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatUtilitiesShaderUsesComputeAuthoringContract) { + ShaderLoader loader; + LoadResult result = loader.Load(GetBuiltinGaussianSplatUtilitiesShaderPath()); + ASSERT_TRUE(result); + ASSERT_NE(result.resource, nullptr); + + Shader* shader = static_cast(result.resource); + ASSERT_NE(shader, nullptr); + + const ShaderPass* pass = shader->FindPass("GaussianSplatPrepareOrder"); + ASSERT_NE(pass, nullptr); + EXPECT_EQ(pass->resources.Size(), 4u); + + const ShaderResourceBindingDesc* perObject = + shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "PerObjectConstants"); + ASSERT_NE(perObject, nullptr); + EXPECT_EQ(perObject->type, ShaderResourceType::ConstantBuffer); + EXPECT_EQ(perObject->set, 0u); + EXPECT_EQ(perObject->binding, 0u); + EXPECT_EQ( + ResolveBuiltinPassResourceSemantic(*perObject), + BuiltinPassResourceSemantic::PerObject); + + const ShaderResourceBindingDesc* positions = + shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "GaussianSplatPositions"); + ASSERT_NE(positions, nullptr); + EXPECT_EQ(positions->type, ShaderResourceType::StructuredBuffer); + EXPECT_EQ(positions->set, 2u); + EXPECT_EQ(positions->binding, 0u); + EXPECT_EQ( + ResolveBuiltinPassResourceSemantic(*positions), + BuiltinPassResourceSemantic::GaussianSplatPositionBuffer); + + const ShaderResourceBindingDesc* sortDistances = + shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "GaussianSplatSortDistances"); + ASSERT_NE(sortDistances, nullptr); + EXPECT_EQ(sortDistances->type, ShaderResourceType::RWStructuredBuffer); + EXPECT_EQ(sortDistances->set, 4u); + EXPECT_EQ(sortDistances->binding, 0u); + EXPECT_EQ( + ResolveBuiltinPassResourceSemantic(*sortDistances), + BuiltinPassResourceSemantic::GaussianSplatSortDistanceBuffer); + + const ShaderResourceBindingDesc* orderBuffer = + shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "GaussianSplatOrderBuffer"); + ASSERT_NE(orderBuffer, nullptr); + EXPECT_EQ(orderBuffer->type, ShaderResourceType::RWStructuredBuffer); + EXPECT_EQ(orderBuffer->set, 4u); + EXPECT_EQ(orderBuffer->binding, 1u); + EXPECT_EQ( + ResolveBuiltinPassResourceSemantic(*orderBuffer), + BuiltinPassResourceSemantic::GaussianSplatOrderBuffer); + + const ShaderStageVariant* computeVariant = shader->FindVariant( + "GaussianSplatPrepareOrder", + XCEngine::Resources::ShaderType::Compute, + XCEngine::Resources::ShaderBackend::D3D12); + ASSERT_NE(computeVariant, nullptr); + EXPECT_EQ(computeVariant->entryPoint, "GaussianSplatPrepareOrderCS"); + + delete shader; +} + +TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoadedGaussianSplatUtilitiesShaderContract) { + ShaderLoader loader; + LoadResult result = loader.Load(GetBuiltinGaussianSplatUtilitiesShaderPath()); + ASSERT_TRUE(result); + ASSERT_NE(result.resource, nullptr); + + Shader* shader = static_cast(result.resource); + ASSERT_NE(shader, nullptr); + + const ShaderPass* pass = shader->FindPass("GaussianSplatPrepareOrder"); + ASSERT_NE(pass, nullptr); + + BuiltinPassResourceBindingPlan plan = {}; + String error; + EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr(); + ASSERT_EQ(plan.bindings.Size(), 4u); + EXPECT_TRUE(plan.perObject.IsValid()); + EXPECT_TRUE(plan.gaussianSplatPositionBuffer.IsValid()); + EXPECT_TRUE(plan.gaussianSplatSortDistanceBuffer.IsValid()); + EXPECT_TRUE(plan.gaussianSplatOrderBuffer.IsValid()); + EXPECT_EQ(plan.perObject.set, 0u); + EXPECT_EQ(plan.gaussianSplatPositionBuffer.set, 2u); + EXPECT_EQ(plan.gaussianSplatSortDistanceBuffer.set, 4u); + EXPECT_EQ(plan.gaussianSplatSortDistanceBuffer.binding, 0u); + EXPECT_EQ(plan.gaussianSplatOrderBuffer.set, 4u); + EXPECT_EQ(plan.gaussianSplatOrderBuffer.binding, 1u); + EXPECT_EQ(plan.firstDescriptorSet, 0u); + EXPECT_EQ(plan.descriptorSetCount, 5u); + + std::vector setLayouts; + ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr(); + ASSERT_EQ(setLayouts.size(), 5u); + EXPECT_TRUE(setLayouts[0].usesPerObject); + EXPECT_TRUE(setLayouts[2].usesGaussianSplatPositionBuffer); + EXPECT_TRUE(setLayouts[4].usesGaussianSplatSortDistanceBuffer); + EXPECT_TRUE(setLayouts[4].usesGaussianSplatOrderBuffer); + ASSERT_EQ(setLayouts[4].bindings.size(), 2u); + EXPECT_EQ( + static_cast(setLayouts[4].bindings[0].type), + DescriptorType::UAV); + EXPECT_EQ( + static_cast(setLayouts[4].bindings[1].type), + DescriptorType::UAV); + EXPECT_EQ( + setLayouts[4].bindings[0].resourceDimension, + ResourceViewDimension::StructuredBuffer); + EXPECT_EQ( + setLayouts[4].bindings[1].resourceDimension, + ResourceViewDimension::StructuredBuffer); + + delete shader; +} + +TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesGaussianSplatUtilitiesComputeBindingsToDescriptorSpaces) { + ShaderLoader loader; + LoadResult result = loader.Load(GetBuiltinGaussianSplatUtilitiesShaderPath()); + ASSERT_TRUE(result); + ASSERT_NE(result.resource, nullptr); + + Shader* shader = static_cast(result.resource); + ASSERT_NE(shader, nullptr); + + const ShaderPass* pass = shader->FindPass("GaussianSplatPrepareOrder"); + ASSERT_NE(pass, nullptr); + + const ShaderStageVariant* d3d12Compute = shader->FindVariant( + "GaussianSplatPrepareOrder", + XCEngine::Resources::ShaderType::Compute, + XCEngine::Resources::ShaderBackend::D3D12); + ASSERT_NE(d3d12Compute, nullptr); + + ShaderCompileDesc d3d12CompileDesc = {}; + ::XCEngine::Rendering::Internal::ApplyShaderStageVariant( + *pass, + XCEngine::Resources::ShaderBackend::D3D12, + *d3d12Compute, + d3d12CompileDesc); + const std::string d3d12Source( + reinterpret_cast(d3d12CompileDesc.source.data()), + d3d12CompileDesc.source.size()); + EXPECT_TRUE(SourceContainsRegisterBinding( + d3d12Source, + "cbuffer PerObjectConstants", + "register(b0)")); + EXPECT_TRUE(SourceContainsRegisterBinding( + d3d12Source, + "StructuredBuffer GaussianSplatPositions", + "register(t0)")); + EXPECT_TRUE(SourceContainsRegisterBinding( + d3d12Source, + "RWStructuredBuffer GaussianSplatSortDistances", + "register(u0)")); + EXPECT_TRUE(SourceContainsRegisterBinding( + d3d12Source, + "RWStructuredBuffer GaussianSplatOrderBuffer", + "register(u1)")); + + const ShaderStageVariant* vulkanCompute = shader->FindVariant( + "GaussianSplatPrepareOrder", + XCEngine::Resources::ShaderType::Compute, + XCEngine::Resources::ShaderBackend::Vulkan); + ASSERT_NE(vulkanCompute, nullptr); + + ShaderCompileDesc vulkanCompileDesc = {}; + ::XCEngine::Rendering::Internal::ApplyShaderStageVariant( + *pass, + XCEngine::Resources::ShaderBackend::Vulkan, + *vulkanCompute, + vulkanCompileDesc); + const std::string vulkanSource( + reinterpret_cast(vulkanCompileDesc.source.data()), + vulkanCompileDesc.source.size()); + EXPECT_TRUE(SourceContainsRegisterBinding( + vulkanSource, + "cbuffer PerObjectConstants", + "register(b0, space0)")); + EXPECT_TRUE(SourceContainsRegisterBinding( + vulkanSource, + "StructuredBuffer GaussianSplatPositions", + "register(t0, space2)")); + EXPECT_TRUE(SourceContainsRegisterBinding( + vulkanSource, + "RWStructuredBuffer GaussianSplatSortDistances", + "register(u0, space4)")); + EXPECT_TRUE(SourceContainsRegisterBinding( + vulkanSource, + "RWStructuredBuffer GaussianSplatOrderBuffer", + "register(u1, space4)")); + + delete shader; +} + TEST(BuiltinForwardPipeline_Test, BuiltinVolumetricShaderUsesAuthoringContract) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinVolumetricShaderPath()); diff --git a/tests/Resources/Shader/test_shader_loader.cpp b/tests/Resources/Shader/test_shader_loader.cpp index e0c9b29b..ffd7db6b 100644 --- a/tests/Resources/Shader/test_shader_loader.cpp +++ b/tests/Resources/Shader/test_shader_loader.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include using namespace XCEngine::Resources; @@ -71,6 +72,7 @@ TEST(ShaderLoader, CanLoad) { EXPECT_TRUE(loader.CanLoad(GetBuiltinObjectIdOutlineShaderPath())); EXPECT_TRUE(loader.CanLoad(GetBuiltinSelectionMaskShaderPath())); EXPECT_TRUE(loader.CanLoad(GetBuiltinSelectionOutlineShaderPath())); + EXPECT_TRUE(loader.CanLoad(GetBuiltinGaussianSplatUtilitiesShaderPath())); EXPECT_FALSE(loader.CanLoad("test.vert")); EXPECT_FALSE(loader.CanLoad("test.frag")); EXPECT_FALSE(loader.CanLoad("test.glsl")); @@ -603,6 +605,100 @@ TEST(ShaderLoader, LoadShaderAuthoringBuildsComputeOnlyPassVariant) { fs::remove_all(shaderRoot); } +TEST(ShaderLoader, LoadShaderAuthoringBuildsComputeOnlyPassConstantBufferBindings) { + namespace fs = std::filesystem; + + const fs::path shaderRoot = fs::temp_directory_path() / "xc_shader_authoring_compute_constants"; + const fs::path shaderPath = shaderRoot / "compute_constants.shader"; + + fs::remove_all(shaderRoot); + fs::create_directories(shaderRoot); + + WriteTextFile( + shaderPath, + R"(Shader "ComputeConstantsShader" +{ + SubShader + { + Pass + { + Name "Prepare" + HLSLPROGRAM + #pragma target 4.5 + #pragma compute PrepareCS + cbuffer PerObjectConstants + { + float4x4 ObjectToWorld; + }; + StructuredBuffer InputData; + RWStructuredBuffer OutputOrder; + [numthreads(64, 1, 1)] + void PrepareCS(uint3 dispatchThreadId : SV_DispatchThreadID) + { + OutputOrder[dispatchThreadId.x] = + (uint)mul(ObjectToWorld, InputData[dispatchThreadId.x]).x; + } + ENDHLSL + } + } +} +)"); + + ShaderLoader loader; + LoadResult result = loader.Load(shaderPath.string().c_str()); + ASSERT_TRUE(result); + ASSERT_NE(result.resource, nullptr); + + Shader* shader = static_cast(result.resource); + ASSERT_NE(shader, nullptr); + + const ShaderPass* pass = shader->FindPass("Prepare"); + ASSERT_NE(pass, nullptr); + EXPECT_EQ(pass->resources.Size(), 3u); + + const ShaderResourceBindingDesc* perObject = + shader->FindPassResourceBinding("Prepare", "PerObjectConstants"); + ASSERT_NE(perObject, nullptr); + EXPECT_EQ(perObject->type, ShaderResourceType::ConstantBuffer); + EXPECT_EQ(perObject->set, 0u); + EXPECT_EQ(perObject->binding, 0u); + + const ShaderStageVariant* computeVariant = + shader->FindVariant("Prepare", ShaderType::Compute, ShaderBackend::D3D12); + ASSERT_NE(computeVariant, nullptr); + + XCEngine::RHI::ShaderCompileDesc d3d12CompileDesc = {}; + ::XCEngine::Rendering::Internal::ApplyShaderStageVariant( + *pass, + ShaderBackend::D3D12, + *computeVariant, + d3d12CompileDesc); + const std::string d3d12Source( + reinterpret_cast(d3d12CompileDesc.source.data()), + d3d12CompileDesc.source.size()); + EXPECT_TRUE(std::regex_search( + d3d12Source, + std::regex(R"(cbuffer\s+PerObjectConstants\s*:\s*register\(b0\))", std::regex::ECMAScript))); + + XCEngine::RHI::ShaderCompileDesc vulkanCompileDesc = {}; + ::XCEngine::Rendering::Internal::ApplyShaderStageVariant( + *pass, + ShaderBackend::Vulkan, + *computeVariant, + vulkanCompileDesc); + const std::string vulkanSource( + reinterpret_cast(vulkanCompileDesc.source.data()), + vulkanCompileDesc.source.size()); + EXPECT_TRUE(std::regex_search( + vulkanSource, + std::regex( + R"(cbuffer\s+PerObjectConstants\s*:\s*register\(b0,\s*space0\))", + std::regex::ECMAScript))); + + delete shader; + fs::remove_all(shaderRoot); +} + TEST(ShaderLoader, LoadShaderAuthoringRejectsPassMixingComputeAndGraphicsPragmas) { namespace fs = std::filesystem;