Add gaussian splat compute shader contracts

This commit is contained in:
2026-04-11 01:30:59 +08:00
parent 4080b2e5fe
commit d9bc0f1457
9 changed files with 442 additions and 8 deletions

View File

@@ -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<float3> GaussianSplatPositions;
RWStructuredBuffer<uint> GaussianSplatSortDistances;
RWStructuredBuffer<uint> 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
}
}
}

View File

@@ -62,6 +62,12 @@ inline bool TryBuildBuiltinPassResourceBindingPlan(
case BuiltinPassResourceSemantic::VolumeField: case BuiltinPassResourceSemantic::VolumeField:
location = &outPlan.volumeField; location = &outPlan.volumeField;
break; break;
case BuiltinPassResourceSemantic::GaussianSplatSortDistanceBuffer:
location = &outPlan.gaussianSplatSortDistanceBuffer;
break;
case BuiltinPassResourceSemantic::GaussianSplatOrderBuffer:
location = &outPlan.gaussianSplatOrderBuffer;
break;
case BuiltinPassResourceSemantic::GaussianSplatPositionBuffer: case BuiltinPassResourceSemantic::GaussianSplatPositionBuffer:
location = &outPlan.gaussianSplatPositionBuffer; location = &outPlan.gaussianSplatPositionBuffer;
break; break;
@@ -74,6 +80,9 @@ inline bool TryBuildBuiltinPassResourceBindingPlan(
case BuiltinPassResourceSemantic::GaussianSplatSHBuffer: case BuiltinPassResourceSemantic::GaussianSplatSHBuffer:
location = &outPlan.gaussianSplatSHBuffer; location = &outPlan.gaussianSplatSHBuffer;
break; break;
case BuiltinPassResourceSemantic::GaussianSplatViewDataBuffer:
location = &outPlan.gaussianSplatViewDataBuffer;
break;
case BuiltinPassResourceSemantic::BaseColorTexture: case BuiltinPassResourceSemantic::BaseColorTexture:
location = &outPlan.baseColorTexture; location = &outPlan.baseColorTexture;
break; break;
@@ -600,6 +609,12 @@ inline bool TryBuildBuiltinPassSetLayouts(
case BuiltinPassResourceSemantic::VolumeField: case BuiltinPassResourceSemantic::VolumeField:
setLayout.usesVolumeField = true; setLayout.usesVolumeField = true;
break; break;
case BuiltinPassResourceSemantic::GaussianSplatSortDistanceBuffer:
setLayout.usesGaussianSplatSortDistanceBuffer = true;
break;
case BuiltinPassResourceSemantic::GaussianSplatOrderBuffer:
setLayout.usesGaussianSplatOrderBuffer = true;
break;
case BuiltinPassResourceSemantic::GaussianSplatPositionBuffer: case BuiltinPassResourceSemantic::GaussianSplatPositionBuffer:
setLayout.usesGaussianSplatPositionBuffer = true; setLayout.usesGaussianSplatPositionBuffer = true;
break; break;
@@ -612,6 +627,9 @@ inline bool TryBuildBuiltinPassSetLayouts(
case BuiltinPassResourceSemantic::GaussianSplatSHBuffer: case BuiltinPassResourceSemantic::GaussianSplatSHBuffer:
setLayout.usesGaussianSplatSHBuffer = true; setLayout.usesGaussianSplatSHBuffer = true;
break; break;
case BuiltinPassResourceSemantic::GaussianSplatViewDataBuffer:
setLayout.usesGaussianSplatViewDataBuffer = true;
break;
case BuiltinPassResourceSemantic::BaseColorTexture: case BuiltinPassResourceSemantic::BaseColorTexture:
setLayout.usesTexture = true; setLayout.usesTexture = true;
setLayout.usesBaseColorTexture = true; setLayout.usesBaseColorTexture = true;

View File

@@ -147,6 +147,18 @@ inline BuiltinPassResourceSemantic ResolveBuiltinPassResourceSemantic(
return BuiltinPassResourceSemantic::VolumeField; 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") || if (semantic == Containers::String("gaussiansplatpositionbuffer") ||
semantic == Containers::String("gaussiansplatpositions") || semantic == Containers::String("gaussiansplatpositions") ||
semantic == Containers::String("splatpositions")) { semantic == Containers::String("splatpositions")) {
@@ -171,6 +183,12 @@ inline BuiltinPassResourceSemantic ResolveBuiltinPassResourceSemantic(
return BuiltinPassResourceSemantic::GaussianSplatSHBuffer; return BuiltinPassResourceSemantic::GaussianSplatSHBuffer;
} }
if (semantic == Containers::String("gaussiansplatviewdatabuffer") ||
semantic == Containers::String("gaussiansplatviewdata") ||
semantic == Containers::String("splatviewdata")) {
return BuiltinPassResourceSemantic::GaussianSplatViewDataBuffer;
}
if (semantic == Containers::String("basecolortexture") || if (semantic == Containers::String("basecolortexture") ||
semantic == Containers::String("maintex")) { semantic == Containers::String("maintex")) {
return BuiltinPassResourceSemantic::BaseColorTexture; return BuiltinPassResourceSemantic::BaseColorTexture;
@@ -233,6 +251,10 @@ inline const char* BuiltinPassResourceSemanticToString(BuiltinPassResourceSemant
return "PassConstants"; return "PassConstants";
case BuiltinPassResourceSemantic::VolumeField: case BuiltinPassResourceSemantic::VolumeField:
return "VolumeField"; return "VolumeField";
case BuiltinPassResourceSemantic::GaussianSplatSortDistanceBuffer:
return "GaussianSplatSortDistanceBuffer";
case BuiltinPassResourceSemantic::GaussianSplatOrderBuffer:
return "GaussianSplatOrderBuffer";
case BuiltinPassResourceSemantic::GaussianSplatPositionBuffer: case BuiltinPassResourceSemantic::GaussianSplatPositionBuffer:
return "GaussianSplatPositionBuffer"; return "GaussianSplatPositionBuffer";
case BuiltinPassResourceSemantic::GaussianSplatOtherBuffer: case BuiltinPassResourceSemantic::GaussianSplatOtherBuffer:
@@ -241,6 +263,8 @@ inline const char* BuiltinPassResourceSemanticToString(BuiltinPassResourceSemant
return "GaussianSplatColorBuffer"; return "GaussianSplatColorBuffer";
case BuiltinPassResourceSemantic::GaussianSplatSHBuffer: case BuiltinPassResourceSemantic::GaussianSplatSHBuffer:
return "GaussianSplatSHBuffer"; return "GaussianSplatSHBuffer";
case BuiltinPassResourceSemantic::GaussianSplatViewDataBuffer:
return "GaussianSplatViewDataBuffer";
case BuiltinPassResourceSemantic::BaseColorTexture: case BuiltinPassResourceSemantic::BaseColorTexture:
return "BaseColorTexture"; return "BaseColorTexture";
case BuiltinPassResourceSemantic::SourceColorTexture: case BuiltinPassResourceSemantic::SourceColorTexture:
@@ -329,12 +353,17 @@ inline bool IsBuiltinPassResourceTypeCompatible(
case BuiltinPassResourceSemantic::VolumeField: case BuiltinPassResourceSemantic::VolumeField:
return type == Resources::ShaderResourceType::StructuredBuffer || return type == Resources::ShaderResourceType::StructuredBuffer ||
type == Resources::ShaderResourceType::RawBuffer; type == Resources::ShaderResourceType::RawBuffer;
case BuiltinPassResourceSemantic::GaussianSplatSortDistanceBuffer:
case BuiltinPassResourceSemantic::GaussianSplatOrderBuffer:
case BuiltinPassResourceSemantic::GaussianSplatPositionBuffer: case BuiltinPassResourceSemantic::GaussianSplatPositionBuffer:
case BuiltinPassResourceSemantic::GaussianSplatOtherBuffer: case BuiltinPassResourceSemantic::GaussianSplatOtherBuffer:
case BuiltinPassResourceSemantic::GaussianSplatColorBuffer: case BuiltinPassResourceSemantic::GaussianSplatColorBuffer:
case BuiltinPassResourceSemantic::GaussianSplatSHBuffer: case BuiltinPassResourceSemantic::GaussianSplatSHBuffer:
case BuiltinPassResourceSemantic::GaussianSplatViewDataBuffer:
return type == Resources::ShaderResourceType::StructuredBuffer || 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::BaseColorTexture:
case BuiltinPassResourceSemantic::SourceColorTexture: case BuiltinPassResourceSemantic::SourceColorTexture:
case BuiltinPassResourceSemantic::SkyboxPanoramicTexture: case BuiltinPassResourceSemantic::SkyboxPanoramicTexture:

View File

@@ -46,11 +46,13 @@ enum class BuiltinPassResourceSemantic : Core::uint8 {
Environment, Environment,
PassConstants, PassConstants,
VolumeField, VolumeField,
GaussianSplatSortDistanceBuffer,
GaussianSplatOrderBuffer, GaussianSplatOrderBuffer,
GaussianSplatPositionBuffer, GaussianSplatPositionBuffer,
GaussianSplatOtherBuffer, GaussianSplatOtherBuffer,
GaussianSplatColorBuffer, GaussianSplatColorBuffer,
GaussianSplatSHBuffer, GaussianSplatSHBuffer,
GaussianSplatViewDataBuffer,
BaseColorTexture, BaseColorTexture,
SourceColorTexture, SourceColorTexture,
SkyboxPanoramicTexture, SkyboxPanoramicTexture,
@@ -84,11 +86,13 @@ struct BuiltinPassResourceBindingPlan {
PassResourceBindingLocation shadowReceiver = {}; PassResourceBindingLocation shadowReceiver = {};
PassResourceBindingLocation environment = {}; PassResourceBindingLocation environment = {};
PassResourceBindingLocation passConstants = {}; PassResourceBindingLocation passConstants = {};
PassResourceBindingLocation gaussianSplatSortDistanceBuffer = {};
PassResourceBindingLocation gaussianSplatOrderBuffer = {}; PassResourceBindingLocation gaussianSplatOrderBuffer = {};
PassResourceBindingLocation gaussianSplatPositionBuffer = {}; PassResourceBindingLocation gaussianSplatPositionBuffer = {};
PassResourceBindingLocation gaussianSplatOtherBuffer = {}; PassResourceBindingLocation gaussianSplatOtherBuffer = {};
PassResourceBindingLocation gaussianSplatColorBuffer = {}; PassResourceBindingLocation gaussianSplatColorBuffer = {};
PassResourceBindingLocation gaussianSplatSHBuffer = {}; PassResourceBindingLocation gaussianSplatSHBuffer = {};
PassResourceBindingLocation gaussianSplatViewDataBuffer = {};
PassResourceBindingLocation baseColorTexture = {}; PassResourceBindingLocation baseColorTexture = {};
PassResourceBindingLocation sourceColorTexture = {}; PassResourceBindingLocation sourceColorTexture = {};
PassResourceBindingLocation skyboxPanoramicTexture = {}; PassResourceBindingLocation skyboxPanoramicTexture = {};
@@ -122,11 +126,13 @@ struct BuiltinPassSetLayoutMetadata {
bool usesPassConstants = false; bool usesPassConstants = false;
bool usesMaterialBuffers = false; bool usesMaterialBuffers = false;
bool usesVolumeField = false; bool usesVolumeField = false;
bool usesGaussianSplatSortDistanceBuffer = false;
bool usesGaussianSplatOrderBuffer = false; bool usesGaussianSplatOrderBuffer = false;
bool usesGaussianSplatPositionBuffer = false; bool usesGaussianSplatPositionBuffer = false;
bool usesGaussianSplatOtherBuffer = false; bool usesGaussianSplatOtherBuffer = false;
bool usesGaussianSplatColorBuffer = false; bool usesGaussianSplatColorBuffer = false;
bool usesGaussianSplatSHBuffer = false; bool usesGaussianSplatSHBuffer = false;
bool usesGaussianSplatViewDataBuffer = false;
bool usesTexture = false; bool usesTexture = false;
bool usesBaseColorTexture = false; bool usesBaseColorTexture = false;
bool usesSourceColorTexture = false; bool usesSourceColorTexture = false;

View File

@@ -39,6 +39,7 @@ Containers::String GetBuiltinSelectionMaskShaderPath();
Containers::String GetBuiltinSelectionOutlineShaderPath(); Containers::String GetBuiltinSelectionOutlineShaderPath();
Containers::String GetBuiltinSkyboxShaderPath(); Containers::String GetBuiltinSkyboxShaderPath();
Containers::String GetBuiltinGaussianSplatShaderPath(); Containers::String GetBuiltinGaussianSplatShaderPath();
Containers::String GetBuiltinGaussianSplatUtilitiesShaderPath();
Containers::String GetBuiltinVolumetricShaderPath(); Containers::String GetBuiltinVolumetricShaderPath();
Containers::String GetBuiltinColorScalePostProcessShaderPath(); Containers::String GetBuiltinColorScalePostProcessShaderPath();
Containers::String GetBuiltinFinalColorShaderPath(); Containers::String GetBuiltinFinalColorShaderPath();

View File

@@ -37,6 +37,8 @@ constexpr const char* kBuiltinSelectionMaskShaderPath = "builtin://shaders/selec
constexpr const char* kBuiltinSelectionOutlineShaderPath = "builtin://shaders/selection-outline"; constexpr const char* kBuiltinSelectionOutlineShaderPath = "builtin://shaders/selection-outline";
constexpr const char* kBuiltinSkyboxShaderPath = "builtin://shaders/skybox"; constexpr const char* kBuiltinSkyboxShaderPath = "builtin://shaders/skybox";
constexpr const char* kBuiltinGaussianSplatShaderPath = "builtin://shaders/gaussian-splat"; 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* kBuiltinVolumetricShaderPath = "builtin://shaders/volumetric";
constexpr const char* kBuiltinColorScalePostProcessShaderPath = constexpr const char* kBuiltinColorScalePostProcessShaderPath =
"builtin://shaders/color-scale-post-process"; "builtin://shaders/color-scale-post-process";
@@ -71,6 +73,8 @@ constexpr const char* kBuiltinSkyboxShaderAssetRelativePath =
"engine/assets/builtin/shaders/skybox.shader"; "engine/assets/builtin/shaders/skybox.shader";
constexpr const char* kBuiltinGaussianSplatShaderAssetRelativePath = constexpr const char* kBuiltinGaussianSplatShaderAssetRelativePath =
"engine/assets/builtin/shaders/gaussian-splat.shader"; "engine/assets/builtin/shaders/gaussian-splat.shader";
constexpr const char* kBuiltinGaussianSplatUtilitiesShaderAssetRelativePath =
"engine/assets/builtin/shaders/gaussian-splat-utilities.shader";
constexpr const char* kBuiltinVolumetricShaderAssetRelativePath = constexpr const char* kBuiltinVolumetricShaderAssetRelativePath =
"engine/assets/builtin/shaders/volumetric.shader"; "engine/assets/builtin/shaders/volumetric.shader";
constexpr const char* kBuiltinColorScalePostProcessShaderAssetRelativePath = constexpr const char* kBuiltinColorScalePostProcessShaderAssetRelativePath =
@@ -170,6 +174,9 @@ const char* GetBuiltinShaderAssetRelativePath(const Containers::String& builtinS
if (builtinShaderPath == Containers::String(kBuiltinGaussianSplatShaderPath)) { if (builtinShaderPath == Containers::String(kBuiltinGaussianSplatShaderPath)) {
return kBuiltinGaussianSplatShaderAssetRelativePath; return kBuiltinGaussianSplatShaderAssetRelativePath;
} }
if (builtinShaderPath == Containers::String(kBuiltinGaussianSplatUtilitiesShaderPath)) {
return kBuiltinGaussianSplatUtilitiesShaderAssetRelativePath;
}
if (builtinShaderPath == Containers::String(kBuiltinVolumetricShaderPath)) { if (builtinShaderPath == Containers::String(kBuiltinVolumetricShaderPath)) {
return kBuiltinVolumetricShaderAssetRelativePath; return kBuiltinVolumetricShaderAssetRelativePath;
} }
@@ -698,6 +705,9 @@ size_t CalculateBuiltinShaderMemorySize(const Shader& shader) {
memorySize += variant.profile.Length(); memorySize += variant.profile.Length();
memorySize += variant.sourceCode.Length(); memorySize += variant.sourceCode.Length();
memorySize += variant.compiledBinary.Size(); memorySize += variant.compiledBinary.Size();
for (const ShaderBackendCompiledBinary& record : variant.backendCompiledBinaries) {
memorySize += record.payload.Size();
}
} }
} }
@@ -866,6 +876,10 @@ bool TryGetBuiltinShaderPathByShaderName(
outPath = GetBuiltinGaussianSplatShaderPath(); outPath = GetBuiltinGaussianSplatShaderPath();
return true; return true;
} }
if (shaderName == "Builtin Gaussian Splat Utilities") {
outPath = GetBuiltinGaussianSplatUtilitiesShaderPath();
return true;
}
if (shaderName == "Builtin Volumetric") { if (shaderName == "Builtin Volumetric") {
outPath = GetBuiltinVolumetricShaderPath(); outPath = GetBuiltinVolumetricShaderPath();
return true; return true;
@@ -957,6 +971,10 @@ Containers::String GetBuiltinGaussianSplatShaderPath() {
return Containers::String(kBuiltinGaussianSplatShaderPath); return Containers::String(kBuiltinGaussianSplatShaderPath);
} }
Containers::String GetBuiltinGaussianSplatUtilitiesShaderPath() {
return Containers::String(kBuiltinGaussianSplatUtilitiesShaderPath);
}
Containers::String GetBuiltinVolumetricShaderPath() { Containers::String GetBuiltinVolumetricShaderPath() {
return Containers::String(kBuiltinVolumetricShaderPath); return Containers::String(kBuiltinVolumetricShaderPath);
} }
@@ -1077,6 +1095,8 @@ LoadResult CreateBuiltinShaderResource(const Containers::String& path) {
shader = BuildBuiltinSkyboxShader(path); shader = BuildBuiltinSkyboxShader(path);
} else if (path == GetBuiltinGaussianSplatShaderPath()) { } else if (path == GetBuiltinGaussianSplatShaderPath()) {
shader = BuildBuiltinGaussianSplatShader(path); shader = BuildBuiltinGaussianSplatShader(path);
} else if (path == GetBuiltinGaussianSplatUtilitiesShaderPath()) {
shader = TryLoadBuiltinShaderFromAsset(path);
} else if (path == GetBuiltinVolumetricShaderPath()) { } else if (path == GetBuiltinVolumetricShaderPath()) {
shader = BuildBuiltinVolumetricShader(path); shader = BuildBuiltinVolumetricShader(path);
} else if (path == GetBuiltinColorScalePostProcessShaderPath()) { } else if (path == GetBuiltinColorScalePostProcessShaderPath()) {

View File

@@ -192,8 +192,9 @@ void ImportConcretePass(Shader& shader, const ShaderPass& sourcePass) {
shader.AddPass(sourcePass); shader.AddPass(sourcePass);
} }
bool IsBufferShaderResourceType(ShaderResourceType type) { bool IsScannedAuthoringResourceType(ShaderResourceType type) {
return type == ShaderResourceType::StructuredBuffer || return type == ShaderResourceType::ConstantBuffer ||
type == ShaderResourceType::StructuredBuffer ||
type == ShaderResourceType::RawBuffer || type == ShaderResourceType::RawBuffer ||
type == ShaderResourceType::RWStructuredBuffer || type == ShaderResourceType::RWStructuredBuffer ||
type == ShaderResourceType::RWRawBuffer; type == ShaderResourceType::RWRawBuffer;
@@ -239,10 +240,13 @@ Core::uint32 FindNextBindingInSet(
return nextBinding; return nextBinding;
} }
bool TryParseHlslBufferResourceLine( bool TryParseHlslAuthoringResourceLine(
const std::string& line, const std::string& line,
ShaderResourceType& outType, ShaderResourceType& outType,
Containers::String& outName) { 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( 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*;)", 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); std::regex::ECMAScript);
@@ -251,6 +255,12 @@ bool TryParseHlslBufferResourceLine(
std::regex::ECMAScript); std::regex::ECMAScript);
std::smatch match; 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) { if (std::regex_match(line, match, kStructuredPattern) && match.size() >= 3u) {
outType = outType =
match[1].str() == "RWStructuredBuffer" match[1].str() == "RWStructuredBuffer"
@@ -272,7 +282,7 @@ bool TryParseHlslBufferResourceLine(
return false; return false;
} }
void AppendScannedBufferResourceBindings( void AppendScannedAuthoringResourceBindings(
const Containers::String& sourceText, const Containers::String& sourceText,
Containers::Array<ShaderResourceBindingDesc>& ioBindings) { Containers::Array<ShaderResourceBindingDesc>& ioBindings) {
if (sourceText.Empty()) { if (sourceText.Empty()) {
@@ -286,11 +296,11 @@ void AppendScannedBufferResourceBindings(
const std::string line = Internal::StripAuthoringLineComment(rawLine); const std::string line = Internal::StripAuthoringLineComment(rawLine);
ShaderResourceType resourceType = ShaderResourceType::ConstantBuffer; ShaderResourceType resourceType = ShaderResourceType::ConstantBuffer;
Containers::String resourceName; Containers::String resourceName;
if (!TryParseHlslBufferResourceLine(line, resourceType, resourceName)) { if (!TryParseHlslAuthoringResourceLine(line, resourceType, resourceName)) {
continue; continue;
} }
if (!IsBufferShaderResourceType(resourceType) || if (!IsScannedAuthoringResourceType(resourceType) ||
resourceName.Empty() || resourceName.Empty() ||
HasResourceBindingNamed(ioBindings, resourceName)) { HasResourceBindingNamed(ioBindings, resourceName)) {
continue; continue;
@@ -354,7 +364,7 @@ ShaderPass BuildConcretePass(
} }
} }
} }
AppendScannedBufferResourceBindings(strippedCombinedSource, shaderPass.resources); AppendScannedAuthoringResourceBindings(strippedCombinedSource, shaderPass.resources);
const std::vector<ShaderKeywordSet> keywordSets = const std::vector<ShaderKeywordSet> keywordSets =
BuildShaderKeywordVariantSets(pass.keywordDeclarations); BuildShaderKeywordVariantSets(pass.keywordDeclarations);
@@ -615,6 +625,9 @@ size_t CalculateShaderMemorySize(const Shader& shader) {
memorySize += variant.profile.Length(); memorySize += variant.profile.Length();
memorySize += variant.sourceCode.Length(); memorySize += variant.sourceCode.Length();
memorySize += variant.compiledBinary.Size(); memorySize += variant.compiledBinary.Size();
for (const ShaderBackendCompiledBinary& record : variant.backendCompiledBinaries) {
memorySize += record.payload.Size();
}
} }
} }

View File

@@ -1087,6 +1087,201 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringGauss
delete shader; 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<Shader*>(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<Shader*>(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<BuiltinPassSetLayoutMetadata> 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<DescriptorType>(setLayouts[4].bindings[0].type),
DescriptorType::UAV);
EXPECT_EQ(
static_cast<DescriptorType>(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<Shader*>(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<const char*>(d3d12CompileDesc.source.data()),
d3d12CompileDesc.source.size());
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"cbuffer PerObjectConstants",
"register(b0)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"StructuredBuffer<float3> GaussianSplatPositions",
"register(t0)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"RWStructuredBuffer<uint> GaussianSplatSortDistances",
"register(u0)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"RWStructuredBuffer<uint> 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<const char*>(vulkanCompileDesc.source.data()),
vulkanCompileDesc.source.size());
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"cbuffer PerObjectConstants",
"register(b0, space0)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"StructuredBuffer<float3> GaussianSplatPositions",
"register(t0, space2)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"RWStructuredBuffer<uint> GaussianSplatSortDistances",
"register(u0, space4)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"RWStructuredBuffer<uint> GaussianSplatOrderBuffer",
"register(u1, space4)"));
delete shader;
}
TEST(BuiltinForwardPipeline_Test, BuiltinVolumetricShaderUsesAuthoringContract) { TEST(BuiltinForwardPipeline_Test, BuiltinVolumetricShaderUsesAuthoringContract) {
ShaderLoader loader; ShaderLoader loader;
LoadResult result = loader.Load(GetBuiltinVolumetricShaderPath()); LoadResult result = loader.Load(GetBuiltinVolumetricShaderPath());

View File

@@ -13,6 +13,7 @@
#include <cstdio> #include <cstdio>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <regex>
#include <thread> #include <thread>
using namespace XCEngine::Resources; using namespace XCEngine::Resources;
@@ -71,6 +72,7 @@ TEST(ShaderLoader, CanLoad) {
EXPECT_TRUE(loader.CanLoad(GetBuiltinObjectIdOutlineShaderPath())); EXPECT_TRUE(loader.CanLoad(GetBuiltinObjectIdOutlineShaderPath()));
EXPECT_TRUE(loader.CanLoad(GetBuiltinSelectionMaskShaderPath())); EXPECT_TRUE(loader.CanLoad(GetBuiltinSelectionMaskShaderPath()));
EXPECT_TRUE(loader.CanLoad(GetBuiltinSelectionOutlineShaderPath())); EXPECT_TRUE(loader.CanLoad(GetBuiltinSelectionOutlineShaderPath()));
EXPECT_TRUE(loader.CanLoad(GetBuiltinGaussianSplatUtilitiesShaderPath()));
EXPECT_FALSE(loader.CanLoad("test.vert")); EXPECT_FALSE(loader.CanLoad("test.vert"));
EXPECT_FALSE(loader.CanLoad("test.frag")); EXPECT_FALSE(loader.CanLoad("test.frag"));
EXPECT_FALSE(loader.CanLoad("test.glsl")); EXPECT_FALSE(loader.CanLoad("test.glsl"));
@@ -603,6 +605,100 @@ TEST(ShaderLoader, LoadShaderAuthoringBuildsComputeOnlyPassVariant) {
fs::remove_all(shaderRoot); 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<float4> InputData;
RWStructuredBuffer<uint> 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<Shader*>(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<const char*>(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<const char*>(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) { TEST(ShaderLoader, LoadShaderAuthoringRejectsPassMixingComputeAndGraphicsPragmas) {
namespace fs = std::filesystem; namespace fs = std::filesystem;