Add gaussian splat integration baseline
This commit is contained in:
@@ -1,12 +1,122 @@
|
||||
Shader "Builtin Gaussian Splat Utilities"
|
||||
{
|
||||
HLSLINCLUDE
|
||||
struct GaussianSplatOtherData
|
||||
{
|
||||
float4 rotation;
|
||||
float4 scaleReserved;
|
||||
};
|
||||
|
||||
struct GaussianSplatViewData
|
||||
{
|
||||
float4 clipCenter;
|
||||
float4 ellipseAxisU;
|
||||
float4 ellipseAxisV;
|
||||
float4 colorOpacity;
|
||||
};
|
||||
|
||||
uint FloatToSortableUint(float value)
|
||||
{
|
||||
const uint rawValue = asuint(value);
|
||||
const uint mask = (rawValue & 0x80000000u) != 0u ? 0xffffffffu : 0x80000000u;
|
||||
return rawValue ^ mask;
|
||||
}
|
||||
|
||||
float3x3 CalcMatrixFromRotationScale(float4 rotation, float3 scale)
|
||||
{
|
||||
const float x = rotation.x;
|
||||
const float y = rotation.y;
|
||||
const float z = rotation.z;
|
||||
const float w = rotation.w;
|
||||
|
||||
const float3x3 rotationMatrix = float3x3(
|
||||
1.0 - 2.0 * (y * y + z * z), 2.0 * (x * y - w * z), 2.0 * (x * z + w * y),
|
||||
2.0 * (x * y + w * z), 1.0 - 2.0 * (x * x + z * z), 2.0 * (y * z - w * x),
|
||||
2.0 * (x * z - w * y), 2.0 * (y * z + w * x), 1.0 - 2.0 * (x * x + y * y));
|
||||
const float3x3 scaleMatrix = float3x3(
|
||||
scale.x, 0.0, 0.0,
|
||||
0.0, scale.y, 0.0,
|
||||
0.0, 0.0, scale.z);
|
||||
return mul(rotationMatrix, scaleMatrix);
|
||||
}
|
||||
|
||||
void CalcCovariance3D(float3x3 rotationScaleMatrix, out float3 sigma0, out float3 sigma1)
|
||||
{
|
||||
const float3x3 sigma = mul(rotationScaleMatrix, transpose(rotationScaleMatrix));
|
||||
sigma0 = float3(sigma._m00, sigma._m01, sigma._m02);
|
||||
sigma1 = float3(sigma._m11, sigma._m12, sigma._m22);
|
||||
}
|
||||
|
||||
float3 CalcCovariance2D(
|
||||
float3 viewPosition,
|
||||
float3 covariance3D0,
|
||||
float3 covariance3D1,
|
||||
float4x4 viewMatrix,
|
||||
float4x4 projectionMatrix,
|
||||
float2 screenSize)
|
||||
{
|
||||
if (abs(viewPosition.z) <= 1.0e-5)
|
||||
{
|
||||
return float3(0.3, 0.0, 0.3);
|
||||
}
|
||||
|
||||
const float aspect = projectionMatrix[0][0] / projectionMatrix[1][1];
|
||||
const float tanFovX = rcp(projectionMatrix[0][0]);
|
||||
const float tanFovY = rcp(projectionMatrix[1][1] * aspect);
|
||||
const float limitX = 1.3 * tanFovX;
|
||||
const float limitY = 1.3 * tanFovY;
|
||||
|
||||
float3 clampedViewPosition = viewPosition;
|
||||
clampedViewPosition.x =
|
||||
clamp(clampedViewPosition.x / clampedViewPosition.z, -limitX, limitX) * clampedViewPosition.z;
|
||||
clampedViewPosition.y =
|
||||
clamp(clampedViewPosition.y / clampedViewPosition.z, -limitY, limitY) * clampedViewPosition.z;
|
||||
|
||||
const float focalLength = screenSize.x * projectionMatrix[0][0] * 0.5;
|
||||
const float3x3 jacobian = float3x3(
|
||||
focalLength / clampedViewPosition.z,
|
||||
0.0,
|
||||
-(focalLength * clampedViewPosition.x) / (clampedViewPosition.z * clampedViewPosition.z),
|
||||
0.0,
|
||||
focalLength / clampedViewPosition.z,
|
||||
-(focalLength * clampedViewPosition.y) / (clampedViewPosition.z * clampedViewPosition.z),
|
||||
0.0,
|
||||
0.0,
|
||||
0.0);
|
||||
const float3x3 worldToView = (float3x3)viewMatrix;
|
||||
const float3x3 transform = mul(jacobian, worldToView);
|
||||
const float3x3 covariance3D = float3x3(
|
||||
covariance3D0.x, covariance3D0.y, covariance3D0.z,
|
||||
covariance3D0.y, covariance3D1.x, covariance3D1.y,
|
||||
covariance3D0.z, covariance3D1.y, covariance3D1.z);
|
||||
float3x3 covariance2D = mul(transform, mul(covariance3D, transpose(transform)));
|
||||
|
||||
covariance2D._m00 += 0.3;
|
||||
covariance2D._m11 += 0.3;
|
||||
return float3(covariance2D._m00, covariance2D._m01, covariance2D._m11);
|
||||
}
|
||||
|
||||
void DecomposeCovariance(float3 covariance2D, out float2 axisU, out float2 axisV)
|
||||
{
|
||||
const float diagonal0 = covariance2D.x;
|
||||
const float diagonal1 = covariance2D.z;
|
||||
const float offDiagonal = covariance2D.y;
|
||||
const float mid = 0.5 * (diagonal0 + diagonal1);
|
||||
const float radius = length(float2((diagonal0 - diagonal1) * 0.5, offDiagonal));
|
||||
const float lambda0 = max(mid + radius, 0.1);
|
||||
const float lambda1 = max(mid - radius, 0.1);
|
||||
|
||||
float2 basis = normalize(float2(offDiagonal, lambda0 - diagonal0));
|
||||
if (all(abs(basis) < 1.0e-5))
|
||||
{
|
||||
basis = float2(1.0, 0.0);
|
||||
}
|
||||
|
||||
basis.y = -basis.y;
|
||||
const float maxAxisLength = 4096.0;
|
||||
axisU = min(sqrt(2.0 * lambda0), maxAxisLength) * basis;
|
||||
axisV = min(sqrt(2.0 * lambda1), maxAxisLength) * float2(basis.y, -basis.x);
|
||||
}
|
||||
ENDHLSL
|
||||
|
||||
SubShader
|
||||
@@ -25,18 +135,21 @@ Shader "Builtin Gaussian Splat Utilities"
|
||||
float4x4 gModelMatrix;
|
||||
float4 gCameraRight;
|
||||
float4 gCameraUp;
|
||||
float4 gScreenParams;
|
||||
float4 gSplatParams;
|
||||
};
|
||||
|
||||
StructuredBuffer<float3> GaussianSplatPositions;
|
||||
StructuredBuffer<float4> GaussianSplatPositions;
|
||||
StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther;
|
||||
StructuredBuffer<float4> GaussianSplatColor;
|
||||
RWStructuredBuffer<uint> GaussianSplatSortDistances;
|
||||
RWStructuredBuffer<uint> GaussianSplatOrderBuffer;
|
||||
RWStructuredBuffer<GaussianSplatViewData> GaussianSplatViewDataBuffer;
|
||||
|
||||
[numthreads(64, 1, 1)]
|
||||
void GaussianSplatPrepareOrderCS(uint3 dispatchThreadId : SV_DispatchThreadID)
|
||||
{
|
||||
uint splatCount = 0u;
|
||||
GaussianSplatOrderBuffer.GetDimensions(splatCount);
|
||||
|
||||
const uint splatCount = (uint)gSplatParams.x;
|
||||
const uint index = dispatchThreadId.x;
|
||||
if (index >= splatCount)
|
||||
{
|
||||
@@ -45,10 +158,46 @@ Shader "Builtin Gaussian Splat Utilities"
|
||||
|
||||
GaussianSplatOrderBuffer[index] = index;
|
||||
|
||||
const float3 localCenter = GaussianSplatPositions[index];
|
||||
const float3 viewCenter =
|
||||
mul(gViewMatrix, mul(gModelMatrix, float4(localCenter, 1.0))).xyz;
|
||||
GaussianSplatViewData viewData = (GaussianSplatViewData)0;
|
||||
const float3 localCenter = GaussianSplatPositions[index].xyz;
|
||||
const GaussianSplatOtherData otherData = GaussianSplatOther[index];
|
||||
const float4 colorOpacity = GaussianSplatColor[index];
|
||||
|
||||
const float3 worldCenter = mul(gModelMatrix, float4(localCenter, 1.0)).xyz;
|
||||
const float3 viewCenter = mul(gViewMatrix, float4(worldCenter, 1.0)).xyz;
|
||||
GaussianSplatSortDistances[index] = FloatToSortableUint(viewCenter.z);
|
||||
|
||||
const float4 clipCenter = mul(gProjectionMatrix, float4(viewCenter, 1.0));
|
||||
if (clipCenter.w > 0.0)
|
||||
{
|
||||
const float3x3 modelLinear = (float3x3)gModelMatrix;
|
||||
const float3x3 rotationScaleMatrix =
|
||||
CalcMatrixFromRotationScale(otherData.rotation, otherData.scaleReserved.xyz);
|
||||
const float3x3 worldRotationScale = mul(modelLinear, rotationScaleMatrix);
|
||||
|
||||
float3 covariance3D0 = 0.0;
|
||||
float3 covariance3D1 = 0.0;
|
||||
CalcCovariance3D(worldRotationScale, covariance3D0, covariance3D1);
|
||||
|
||||
const float3 covariance2D = CalcCovariance2D(
|
||||
viewCenter,
|
||||
covariance3D0,
|
||||
covariance3D1,
|
||||
gViewMatrix,
|
||||
gProjectionMatrix,
|
||||
gScreenParams.xy);
|
||||
|
||||
float2 axisU = 0.0;
|
||||
float2 axisV = 0.0;
|
||||
DecomposeCovariance(covariance2D, axisU, axisV);
|
||||
|
||||
viewData.clipCenter = clipCenter;
|
||||
viewData.ellipseAxisU = float4(axisU, 0.0, 0.0);
|
||||
viewData.ellipseAxisV = float4(axisV, 0.0, 0.0);
|
||||
viewData.colorOpacity = colorOpacity;
|
||||
}
|
||||
|
||||
GaussianSplatViewDataBuffer[index] = viewData;
|
||||
}
|
||||
ENDHLSL
|
||||
}
|
||||
|
||||
@@ -13,23 +13,26 @@ Shader "Builtin Gaussian Splat"
|
||||
float4x4 gModelMatrix;
|
||||
float4 gCameraRight;
|
||||
float4 gCameraUp;
|
||||
float4 gScreenParams;
|
||||
float4 gSplatParams;
|
||||
};
|
||||
|
||||
cbuffer MaterialConstants
|
||||
{
|
||||
float4 gSplatParams;
|
||||
float4 gPointScaleParams;
|
||||
float4 gOpacityScaleParams;
|
||||
};
|
||||
|
||||
struct GaussianSplatOtherData
|
||||
struct GaussianSplatViewData
|
||||
{
|
||||
float4 rotation;
|
||||
float4 scaleReserved;
|
||||
float4 clipCenter;
|
||||
float4 ellipseAxisU;
|
||||
float4 ellipseAxisV;
|
||||
float4 colorOpacity;
|
||||
};
|
||||
|
||||
StructuredBuffer<uint> GaussianSplatOrderBuffer;
|
||||
StructuredBuffer<float3> GaussianSplatPositions;
|
||||
StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther;
|
||||
StructuredBuffer<float4> GaussianSplatColor;
|
||||
StructuredBuffer<GaussianSplatViewData> GaussianSplatViewDataBuffer;
|
||||
|
||||
struct VSOutput
|
||||
{
|
||||
@@ -53,39 +56,35 @@ Shader "Builtin Gaussian Splat"
|
||||
|
||||
VSOutput MainVS(uint vertexId : SV_VertexID, uint instanceId : SV_InstanceID)
|
||||
{
|
||||
VSOutput output;
|
||||
const float2 corner = ResolveQuadCorner(vertexId);
|
||||
VSOutput output = (VSOutput)0;
|
||||
const uint splatIndex = GaussianSplatOrderBuffer[instanceId];
|
||||
const float3 localCenter = GaussianSplatPositions[splatIndex];
|
||||
const GaussianSplatOtherData otherData = GaussianSplatOther[splatIndex];
|
||||
const float4 colorOpacity = GaussianSplatColor[splatIndex];
|
||||
const GaussianSplatViewData viewData = GaussianSplatViewDataBuffer[splatIndex];
|
||||
|
||||
const float3 worldCenter = mul(gModelMatrix, float4(localCenter, 1.0)).xyz;
|
||||
const float maxAxisScale =
|
||||
max(max(otherData.scaleReserved.x, otherData.scaleReserved.y), otherData.scaleReserved.z);
|
||||
const float radius = max(maxAxisScale * gSplatParams.x, 0.0001);
|
||||
const float3 worldPosition =
|
||||
worldCenter +
|
||||
gCameraRight.xyz * (corner.x * radius) +
|
||||
gCameraUp.xyz * (corner.y * radius);
|
||||
if (viewData.clipCenter.w <= 0.0)
|
||||
{
|
||||
const float nanValue = asfloat(0x7fc00000u);
|
||||
output.position = float4(nanValue, nanValue, nanValue, nanValue);
|
||||
return output;
|
||||
}
|
||||
|
||||
output.position = mul(gProjectionMatrix, mul(gViewMatrix, float4(worldPosition, 1.0)));
|
||||
output.localUv = corner;
|
||||
output.colorOpacity = colorOpacity;
|
||||
const float2 quadPos = ResolveQuadCorner(vertexId) * 2.0;
|
||||
const float2 ellipseOffsetPixels =
|
||||
(quadPos.x * viewData.ellipseAxisU.xy + quadPos.y * viewData.ellipseAxisV.xy) * gPointScaleParams.x;
|
||||
const float2 clipOffset =
|
||||
ellipseOffsetPixels * (2.0 / max(gScreenParams.xy, float2(1.0, 1.0))) * viewData.clipCenter.w;
|
||||
|
||||
output.position = viewData.clipCenter;
|
||||
output.position.xy += clipOffset;
|
||||
output.localUv = quadPos;
|
||||
output.colorOpacity = viewData.colorOpacity;
|
||||
return output;
|
||||
}
|
||||
|
||||
float4 MainPS(VSOutput input) : SV_TARGET
|
||||
{
|
||||
const float radiusSq = dot(input.localUv, input.localUv);
|
||||
if (radiusSq > 1.0)
|
||||
{
|
||||
discard;
|
||||
}
|
||||
|
||||
const float gaussianFalloff = exp(-radiusSq * 2.5);
|
||||
const float alpha = saturate(input.colorOpacity.a * gSplatParams.y * gaussianFalloff);
|
||||
if (alpha <= 0.001)
|
||||
const float alpha =
|
||||
saturate(exp(-dot(input.localUv, input.localUv)) * input.colorOpacity.a * gOpacityScaleParams.x);
|
||||
if (alpha <= (1.0 / 255.0))
|
||||
{
|
||||
discard;
|
||||
}
|
||||
|
||||
@@ -59,6 +59,8 @@ private:
|
||||
Math::Matrix4x4 model = Math::Matrix4x4::Identity();
|
||||
Math::Vector4 cameraRight = Math::Vector4::Zero();
|
||||
Math::Vector4 cameraUp = Math::Vector4::Zero();
|
||||
Math::Vector4 screenParams = Math::Vector4::Zero();
|
||||
Math::Vector4 splatParams = Math::Vector4::Zero();
|
||||
};
|
||||
|
||||
struct OwnedDescriptorSet {
|
||||
|
||||
@@ -52,6 +52,12 @@ std::vector<const DescriptorSetLayoutBinding*> GatherBindingsOfTypeSorted(
|
||||
return bindings;
|
||||
}
|
||||
|
||||
bool UsesOpenGLStorageBufferBinding(const DescriptorSetLayoutBinding& binding) {
|
||||
return binding.resourceDimension == ResourceViewDimension::Buffer ||
|
||||
binding.resourceDimension == ResourceViewDimension::StructuredBuffer ||
|
||||
binding.resourceDimension == ResourceViewDimension::RawBuffer;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool OpenGLPipelineLayout::Initialize(const RHIPipelineLayoutDesc& desc) {
|
||||
@@ -93,6 +99,7 @@ bool OpenGLPipelineLayout::Initialize(const RHIPipelineLayoutDesc& desc) {
|
||||
|
||||
uint32_t nextCBVBindingPoint = 0;
|
||||
uint32_t nextSRVBindingPoint = 0;
|
||||
uint32_t nextStorageBufferBindingPoint = 0;
|
||||
uint32_t nextUAVBindingPoint = 0;
|
||||
uint32_t nextSamplerBindingPoint = 0;
|
||||
|
||||
@@ -100,34 +107,42 @@ bool OpenGLPipelineLayout::Initialize(const RHIPipelineLayoutDesc& desc) {
|
||||
const DescriptorSetLayoutDesc& setLayout = m_desc.setLayouts[setIndex];
|
||||
SetBindingPointMapping& mapping = m_setBindingPointMappings[setIndex];
|
||||
|
||||
const auto appendBindings =
|
||||
[&setLayout](
|
||||
DescriptorType type,
|
||||
std::unordered_map<uint32_t, uint32_t>& bindingPoints,
|
||||
uint32_t& nextBindingPoint) {
|
||||
const auto bindings = GatherBindingsOfTypeSorted(setLayout, type);
|
||||
for (const DescriptorSetLayoutBinding* binding : bindings) {
|
||||
bindingPoints[binding->binding] = nextBindingPoint;
|
||||
nextBindingPoint += binding->count > 0 ? binding->count : 1u;
|
||||
}
|
||||
};
|
||||
const auto appendBindings = [&setLayout, &nextStorageBufferBindingPoint](
|
||||
DescriptorType type,
|
||||
std::unordered_map<uint32_t, uint32_t>& bindingPoints,
|
||||
uint32_t& nextBindingPoint,
|
||||
bool useStorageBufferNamespace) {
|
||||
const auto bindings = GatherBindingsOfTypeSorted(setLayout, type);
|
||||
for (const DescriptorSetLayoutBinding* binding : bindings) {
|
||||
uint32_t& resolvedNextBindingPoint =
|
||||
useStorageBufferNamespace && UsesOpenGLStorageBufferBinding(*binding)
|
||||
? nextStorageBufferBindingPoint
|
||||
: nextBindingPoint;
|
||||
bindingPoints[binding->binding] = resolvedNextBindingPoint;
|
||||
resolvedNextBindingPoint += binding->count > 0 ? binding->count : 1u;
|
||||
}
|
||||
};
|
||||
|
||||
appendBindings(
|
||||
DescriptorType::CBV,
|
||||
mapping.constantBufferBindingPoints,
|
||||
nextCBVBindingPoint);
|
||||
nextCBVBindingPoint,
|
||||
false);
|
||||
appendBindings(
|
||||
DescriptorType::SRV,
|
||||
mapping.shaderResourceBindingPoints,
|
||||
nextSRVBindingPoint);
|
||||
nextSRVBindingPoint,
|
||||
true);
|
||||
appendBindings(
|
||||
DescriptorType::UAV,
|
||||
mapping.unorderedAccessBindingPoints,
|
||||
nextUAVBindingPoint);
|
||||
nextUAVBindingPoint,
|
||||
true);
|
||||
appendBindings(
|
||||
DescriptorType::Sampler,
|
||||
mapping.samplerBindingPoints,
|
||||
nextSamplerBindingPoint);
|
||||
nextSamplerBindingPoint,
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -268,6 +268,7 @@ inline bool TryBuildRuntimeShaderBindings(
|
||||
Core::uint32 nextTextureRegister = 0;
|
||||
Core::uint32 nextSamplerRegister = 0;
|
||||
Core::uint32 nextUnorderedAccessRegister = 0;
|
||||
Core::uint32 nextStorageBufferRegister = 0;
|
||||
for (Resources::ShaderResourceBindingDesc& binding : outBindings) {
|
||||
binding.set = 0;
|
||||
switch (binding.type) {
|
||||
@@ -276,15 +277,25 @@ inline bool TryBuildRuntimeShaderBindings(
|
||||
break;
|
||||
case Resources::ShaderResourceType::Texture2D:
|
||||
case Resources::ShaderResourceType::TextureCube:
|
||||
binding.binding = nextTextureRegister++;
|
||||
break;
|
||||
case Resources::ShaderResourceType::StructuredBuffer:
|
||||
case Resources::ShaderResourceType::RawBuffer:
|
||||
binding.binding = nextTextureRegister++;
|
||||
binding.binding =
|
||||
backend == Resources::ShaderBackend::OpenGL
|
||||
? nextStorageBufferRegister++
|
||||
: nextTextureRegister++;
|
||||
break;
|
||||
case Resources::ShaderResourceType::Sampler:
|
||||
binding.binding = nextSamplerRegister++;
|
||||
break;
|
||||
case Resources::ShaderResourceType::RWStructuredBuffer:
|
||||
case Resources::ShaderResourceType::RWRawBuffer:
|
||||
binding.binding =
|
||||
backend == Resources::ShaderBackend::OpenGL
|
||||
? nextStorageBufferRegister++
|
||||
: nextUnorderedAccessRegister++;
|
||||
break;
|
||||
default:
|
||||
binding.binding = nextUnorderedAccessRegister++;
|
||||
break;
|
||||
|
||||
@@ -205,6 +205,9 @@ bool BuiltinGaussianSplatPass::PrepareGaussianSplatResources(
|
||||
const RenderContext& context,
|
||||
const RenderSceneData& sceneData) {
|
||||
if (!EnsureInitialized(context)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinGaussianSplatPass::PrepareGaussianSplatResources failed: EnsureInitialized returned false");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -220,10 +223,16 @@ bool BuiltinGaussianSplatPass::PrepareGaussianSplatResources(
|
||||
cachedGaussianSplat->positions.shaderResourceView == nullptr ||
|
||||
cachedGaussianSplat->other.shaderResourceView == nullptr ||
|
||||
cachedGaussianSplat->color.shaderResourceView == nullptr) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinGaussianSplatPass::PrepareGaussianSplatResources failed: gaussian splat GPU cache incomplete");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_passResources == nullptr) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinGaussianSplatPass::PrepareGaussianSplatResources failed: pass resources missing");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -232,7 +241,12 @@ bool BuiltinGaussianSplatPass::PrepareGaussianSplatResources(
|
||||
workingSet == nullptr ||
|
||||
workingSet->sortDistances.unorderedAccessView == nullptr ||
|
||||
workingSet->orderIndices.shaderResourceView == nullptr ||
|
||||
workingSet->orderIndices.unorderedAccessView == nullptr) {
|
||||
workingSet->orderIndices.unorderedAccessView == nullptr ||
|
||||
workingSet->viewData.shaderResourceView == nullptr ||
|
||||
workingSet->viewData.unorderedAccessView == nullptr) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinGaussianSplatPass::PrepareGaussianSplatResources failed: working-set allocation incomplete");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -527,18 +541,19 @@ BuiltinGaussianSplatPass::PassResourceLayout* BuiltinGaussianSplatPass::GetOrCre
|
||||
return failLayout("BuiltinGaussianSplatPass requires a Material resource binding");
|
||||
}
|
||||
if (!passLayout.gaussianSplatOrderBuffer.IsValid() ||
|
||||
!passLayout.gaussianSplatPositionBuffer.IsValid() ||
|
||||
!passLayout.gaussianSplatOtherBuffer.IsValid() ||
|
||||
!passLayout.gaussianSplatColorBuffer.IsValid()) {
|
||||
!passLayout.gaussianSplatViewDataBuffer.IsValid()) {
|
||||
return failLayout(
|
||||
"BuiltinGaussianSplatPass draw pass requires order, position, other, and color gaussian splat buffer bindings");
|
||||
"BuiltinGaussianSplatPass draw pass requires order and view-data gaussian splat buffer bindings");
|
||||
}
|
||||
} else if (usage == PassLayoutUsage::PrepareOrder) {
|
||||
if (!passLayout.gaussianSplatSortDistanceBuffer.IsValid() ||
|
||||
!passLayout.gaussianSplatOrderBuffer.IsValid() ||
|
||||
!passLayout.gaussianSplatPositionBuffer.IsValid()) {
|
||||
!passLayout.gaussianSplatPositionBuffer.IsValid() ||
|
||||
!passLayout.gaussianSplatOtherBuffer.IsValid() ||
|
||||
!passLayout.gaussianSplatColorBuffer.IsValid() ||
|
||||
!passLayout.gaussianSplatViewDataBuffer.IsValid()) {
|
||||
return failLayout(
|
||||
"BuiltinGaussianSplatPass prepare-order pass requires sort distance, order, and position gaussian splat buffer bindings");
|
||||
"BuiltinGaussianSplatPass prepare-order pass requires sort distance, order, position, other, color, and view-data gaussian splat buffer bindings");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -660,6 +675,10 @@ RHI::RHIPipelineState* BuiltinGaussianSplatPass::GetOrCreateComputePipelineState
|
||||
sceneData.globalShaderKeywords);
|
||||
RHI::RHIPipelineState* pipelineState = context.device->CreateComputePipelineState(pipelineDesc);
|
||||
if (pipelineState == nullptr || !pipelineState->IsValid()) {
|
||||
const Containers::String error =
|
||||
Containers::String("BuiltinGaussianSplatPass failed to create compute pipeline state for pass '") +
|
||||
resolvedShaderPass.passName + "'";
|
||||
Debug::Logger::Get().Error(Debug::LogCategory::Rendering, error.CStr());
|
||||
if (pipelineState != nullptr) {
|
||||
pipelineState->Shutdown();
|
||||
delete pipelineState;
|
||||
@@ -718,6 +737,9 @@ BuiltinGaussianSplatPass::CachedDescriptorSet* BuiltinGaussianSplatPass::GetOrCr
|
||||
CachedDescriptorSet& cachedDescriptorSet = m_dynamicDescriptorSets[key];
|
||||
if (cachedDescriptorSet.descriptorSet.set == nullptr) {
|
||||
if (!CreateOwnedDescriptorSet(setLayout, cachedDescriptorSet.descriptorSet)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinGaussianSplatPass failed to allocate descriptor set");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -921,17 +943,22 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat(
|
||||
const RenderContext& context,
|
||||
const RenderSceneData& sceneData,
|
||||
const VisibleGaussianSplatItem& visibleGaussianSplat) {
|
||||
auto fail = [](const char* message) -> bool {
|
||||
Debug::Logger::Get().Error(Debug::LogCategory::Rendering, message);
|
||||
return false;
|
||||
};
|
||||
|
||||
if (visibleGaussianSplat.gameObject == nullptr ||
|
||||
visibleGaussianSplat.gaussianSplat == nullptr ||
|
||||
!visibleGaussianSplat.gaussianSplat->IsValid() ||
|
||||
m_passResources == nullptr) {
|
||||
return false;
|
||||
return fail("BuiltinGaussianSplatPass prepare-order failed: invalid visible gaussian splat item");
|
||||
}
|
||||
|
||||
const RenderResourceCache::CachedGaussianSplat* cachedGaussianSplat =
|
||||
m_resourceCache.GetOrCreateGaussianSplat(m_device, visibleGaussianSplat.gaussianSplat);
|
||||
if (cachedGaussianSplat == nullptr || cachedGaussianSplat->positions.shaderResourceView == nullptr) {
|
||||
return false;
|
||||
return fail("BuiltinGaussianSplatPass prepare-order failed: gaussian splat GPU cache is incomplete");
|
||||
}
|
||||
|
||||
Internal::BuiltinGaussianSplatPassResources::WorkingSet* workingSet = nullptr;
|
||||
@@ -939,13 +966,15 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat(
|
||||
workingSet == nullptr ||
|
||||
workingSet->sortDistances.unorderedAccessView == nullptr ||
|
||||
workingSet->orderIndices.unorderedAccessView == nullptr ||
|
||||
workingSet->orderIndices.shaderResourceView == nullptr) {
|
||||
return false;
|
||||
workingSet->orderIndices.shaderResourceView == nullptr ||
|
||||
workingSet->viewData.unorderedAccessView == nullptr ||
|
||||
workingSet->viewData.shaderResourceView == nullptr) {
|
||||
return fail("BuiltinGaussianSplatPass prepare-order failed: working set allocation is incomplete");
|
||||
}
|
||||
|
||||
const ResolvedShaderPass resolvedShaderPass = ResolvePrepareOrderShaderPass(sceneData);
|
||||
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
|
||||
return false;
|
||||
return fail("BuiltinGaussianSplatPass prepare-order failed: utilities shader pass was not resolved");
|
||||
}
|
||||
|
||||
PassLayoutKey passLayoutKey = {};
|
||||
@@ -958,7 +987,7 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat(
|
||||
PassLayoutUsage::PrepareOrder);
|
||||
RHI::RHIPipelineState* pipelineState = GetOrCreateComputePipelineState(context, sceneData);
|
||||
if (passLayout == nullptr || pipelineState == nullptr) {
|
||||
return false;
|
||||
return fail("BuiltinGaussianSplatPass prepare-order failed: compute pipeline setup was not created");
|
||||
}
|
||||
|
||||
RHI::RHICommandList* commandList = context.commandList;
|
||||
@@ -979,6 +1008,14 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat(
|
||||
workingSet->orderIndices.currentState = RHI::ResourceStates::UnorderedAccess;
|
||||
}
|
||||
|
||||
if (workingSet->viewData.currentState != RHI::ResourceStates::UnorderedAccess) {
|
||||
commandList->TransitionBarrier(
|
||||
workingSet->viewData.unorderedAccessView,
|
||||
workingSet->viewData.currentState,
|
||||
RHI::ResourceStates::UnorderedAccess);
|
||||
workingSet->viewData.currentState = RHI::ResourceStates::UnorderedAccess;
|
||||
}
|
||||
|
||||
commandList->SetPipelineState(pipelineState);
|
||||
|
||||
const PerObjectConstants perObjectConstants = {
|
||||
@@ -986,7 +1023,13 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat(
|
||||
sceneData.cameraData.view,
|
||||
visibleGaussianSplat.localToWorld.Transpose(),
|
||||
Math::Vector4(sceneData.cameraData.worldRight, 0.0f),
|
||||
Math::Vector4(sceneData.cameraData.worldUp, 0.0f)
|
||||
Math::Vector4(sceneData.cameraData.worldUp, 0.0f),
|
||||
Math::Vector4(
|
||||
static_cast<float>(sceneData.cameraData.viewportWidth),
|
||||
static_cast<float>(sceneData.cameraData.viewportHeight),
|
||||
sceneData.cameraData.viewportWidth > 0u ? 1.0f / static_cast<float>(sceneData.cameraData.viewportWidth) : 0.0f,
|
||||
sceneData.cameraData.viewportHeight > 0u ? 1.0f / static_cast<float>(sceneData.cameraData.viewportHeight) : 0.0f),
|
||||
Math::Vector4(static_cast<float>(cachedGaussianSplat->splatCount), 0.0f, 0.0f, 0.0f)
|
||||
};
|
||||
|
||||
if (passLayout->descriptorSetCount > 0u) {
|
||||
@@ -994,7 +1037,7 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat(
|
||||
for (Core::uint32 descriptorOffset = 0u; descriptorOffset < passLayout->descriptorSetCount; ++descriptorOffset) {
|
||||
const Core::uint32 setIndex = passLayout->firstDescriptorSet + descriptorOffset;
|
||||
if (setIndex >= passLayout->setLayouts.size()) {
|
||||
return false;
|
||||
return fail("BuiltinGaussianSplatPass prepare-order failed: descriptor set index overflow");
|
||||
}
|
||||
|
||||
const BuiltinPassSetLayoutMetadata& setLayout = passLayout->setLayouts[setIndex];
|
||||
@@ -1005,8 +1048,11 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat(
|
||||
if (!(setLayout.usesPerObject ||
|
||||
setLayout.usesGaussianSplatSortDistanceBuffer ||
|
||||
setLayout.usesGaussianSplatOrderBuffer ||
|
||||
setLayout.usesGaussianSplatPositionBuffer)) {
|
||||
return false;
|
||||
setLayout.usesGaussianSplatPositionBuffer ||
|
||||
setLayout.usesGaussianSplatOtherBuffer ||
|
||||
setLayout.usesGaussianSplatColorBuffer ||
|
||||
setLayout.usesGaussianSplatViewDataBuffer)) {
|
||||
return fail("BuiltinGaussianSplatPass prepare-order failed: unexpected descriptor set layout");
|
||||
}
|
||||
|
||||
const Core::uint64 objectId =
|
||||
@@ -1014,7 +1060,10 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat(
|
||||
const Resources::GaussianSplat* gaussianSplatKey =
|
||||
(setLayout.usesGaussianSplatSortDistanceBuffer ||
|
||||
setLayout.usesGaussianSplatOrderBuffer ||
|
||||
setLayout.usesGaussianSplatPositionBuffer)
|
||||
setLayout.usesGaussianSplatPositionBuffer ||
|
||||
setLayout.usesGaussianSplatOtherBuffer ||
|
||||
setLayout.usesGaussianSplatColorBuffer ||
|
||||
setLayout.usesGaussianSplatViewDataBuffer)
|
||||
? visibleGaussianSplat.gaussianSplat
|
||||
: nullptr;
|
||||
|
||||
@@ -1033,13 +1082,13 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat(
|
||||
workingSet->orderIndices.unorderedAccessView,
|
||||
workingSet->viewData.unorderedAccessView);
|
||||
if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) {
|
||||
return false;
|
||||
return fail("BuiltinGaussianSplatPass prepare-order failed: dynamic descriptor set resolution failed");
|
||||
}
|
||||
|
||||
RHI::RHIDescriptorSet* descriptorSet = cachedDescriptorSet->descriptorSet.set;
|
||||
if (setLayout.usesPerObject) {
|
||||
if (!passLayout->perObject.IsValid() || passLayout->perObject.set != setIndex) {
|
||||
return false;
|
||||
return fail("BuiltinGaussianSplatPass prepare-order failed: per-object binding is invalid");
|
||||
}
|
||||
|
||||
descriptorSet->WriteConstant(
|
||||
@@ -1070,6 +1119,11 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat(
|
||||
RHI::ResourceStates::UnorderedAccess,
|
||||
RHI::ResourceStates::NonPixelShaderResource);
|
||||
workingSet->orderIndices.currentState = RHI::ResourceStates::NonPixelShaderResource;
|
||||
commandList->TransitionBarrier(
|
||||
workingSet->viewData.shaderResourceView,
|
||||
RHI::ResourceStates::UnorderedAccess,
|
||||
RHI::ResourceStates::NonPixelShaderResource);
|
||||
workingSet->viewData.currentState = RHI::ResourceStates::NonPixelShaderResource;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1078,10 +1132,15 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
|
||||
const RenderSurface& surface,
|
||||
const RenderSceneData& sceneData,
|
||||
const VisibleGaussianSplatItem& visibleGaussianSplat) {
|
||||
auto fail = [](const char* message) -> bool {
|
||||
Debug::Logger::Get().Error(Debug::LogCategory::Rendering, message);
|
||||
return false;
|
||||
};
|
||||
|
||||
if (visibleGaussianSplat.gameObject == nullptr ||
|
||||
visibleGaussianSplat.gaussianSplat == nullptr ||
|
||||
!visibleGaussianSplat.gaussianSplat->IsValid()) {
|
||||
return false;
|
||||
return fail("BuiltinGaussianSplatPass draw failed: invalid visible gaussian splat item");
|
||||
}
|
||||
|
||||
const RenderResourceCache::CachedGaussianSplat* cachedGaussianSplat =
|
||||
@@ -1090,28 +1149,29 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
|
||||
cachedGaussianSplat->positions.shaderResourceView == nullptr ||
|
||||
cachedGaussianSplat->other.shaderResourceView == nullptr ||
|
||||
cachedGaussianSplat->color.shaderResourceView == nullptr) {
|
||||
return false;
|
||||
return fail("BuiltinGaussianSplatPass draw failed: gaussian splat GPU cache is incomplete");
|
||||
}
|
||||
|
||||
if (m_passResources == nullptr) {
|
||||
return false;
|
||||
return fail("BuiltinGaussianSplatPass draw failed: pass resources were not initialized");
|
||||
}
|
||||
|
||||
Internal::BuiltinGaussianSplatPassResources::WorkingSet* workingSet = nullptr;
|
||||
if (!m_passResources->EnsureWorkingSet(m_device, visibleGaussianSplat, workingSet) ||
|
||||
workingSet == nullptr ||
|
||||
workingSet->orderIndices.shaderResourceView == nullptr) {
|
||||
return false;
|
||||
workingSet->orderIndices.shaderResourceView == nullptr ||
|
||||
workingSet->viewData.shaderResourceView == nullptr) {
|
||||
return fail("BuiltinGaussianSplatPass draw failed: working set allocation is incomplete");
|
||||
}
|
||||
|
||||
const Resources::Material* material = ResolveGaussianSplatMaterial(visibleGaussianSplat);
|
||||
if (material == nullptr) {
|
||||
return false;
|
||||
return fail("BuiltinGaussianSplatPass draw failed: gaussian splat material could not be resolved");
|
||||
}
|
||||
|
||||
const ResolvedShaderPass resolvedShaderPass = ResolveGaussianSplatShaderPass(sceneData, material);
|
||||
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
|
||||
return false;
|
||||
return fail("BuiltinGaussianSplatPass draw failed: draw shader pass was not resolved");
|
||||
}
|
||||
|
||||
PassLayoutKey passLayoutKey = {};
|
||||
@@ -1124,12 +1184,12 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
|
||||
PassLayoutUsage::Draw);
|
||||
RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, surface, sceneData, material);
|
||||
if (passLayout == nullptr || pipelineState == nullptr) {
|
||||
return false;
|
||||
return fail("BuiltinGaussianSplatPass draw failed: graphics pipeline setup was not created");
|
||||
}
|
||||
|
||||
const MaterialConstantPayloadView materialConstants = ResolveSchemaMaterialConstantPayload(material);
|
||||
if (!materialConstants.IsValid()) {
|
||||
return false;
|
||||
return fail("BuiltinGaussianSplatPass draw failed: material constant payload is invalid");
|
||||
}
|
||||
|
||||
RHI::RHICommandList* commandList = context.commandList;
|
||||
@@ -1140,7 +1200,13 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
|
||||
sceneData.cameraData.view,
|
||||
visibleGaussianSplat.localToWorld.Transpose(),
|
||||
Math::Vector4(sceneData.cameraData.worldRight, 0.0f),
|
||||
Math::Vector4(sceneData.cameraData.worldUp, 0.0f)
|
||||
Math::Vector4(sceneData.cameraData.worldUp, 0.0f),
|
||||
Math::Vector4(
|
||||
static_cast<float>(sceneData.cameraData.viewportWidth),
|
||||
static_cast<float>(sceneData.cameraData.viewportHeight),
|
||||
sceneData.cameraData.viewportWidth > 0u ? 1.0f / static_cast<float>(sceneData.cameraData.viewportWidth) : 0.0f,
|
||||
sceneData.cameraData.viewportHeight > 0u ? 1.0f / static_cast<float>(sceneData.cameraData.viewportHeight) : 0.0f),
|
||||
Math::Vector4(static_cast<float>(cachedGaussianSplat->splatCount), 0.0f, 0.0f, 0.0f)
|
||||
};
|
||||
|
||||
if (passLayout->descriptorSetCount > 0u) {
|
||||
@@ -1148,7 +1214,7 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
|
||||
for (Core::uint32 descriptorOffset = 0u; descriptorOffset < passLayout->descriptorSetCount; ++descriptorOffset) {
|
||||
const Core::uint32 setIndex = passLayout->firstDescriptorSet + descriptorOffset;
|
||||
if (setIndex >= passLayout->setLayouts.size()) {
|
||||
return false;
|
||||
return fail("BuiltinGaussianSplatPass draw failed: descriptor set index overflow");
|
||||
}
|
||||
|
||||
const BuiltinPassSetLayoutMetadata& setLayout = passLayout->setLayouts[setIndex];
|
||||
@@ -1159,11 +1225,8 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
|
||||
if (!(setLayout.usesPerObject ||
|
||||
setLayout.usesMaterial ||
|
||||
setLayout.usesGaussianSplatOrderBuffer ||
|
||||
setLayout.usesGaussianSplatPositionBuffer ||
|
||||
setLayout.usesGaussianSplatOtherBuffer ||
|
||||
setLayout.usesGaussianSplatColorBuffer ||
|
||||
setLayout.usesGaussianSplatSHBuffer)) {
|
||||
return false;
|
||||
setLayout.usesGaussianSplatViewDataBuffer)) {
|
||||
return fail("BuiltinGaussianSplatPass draw failed: unexpected descriptor set layout");
|
||||
}
|
||||
|
||||
const Core::uint64 objectId =
|
||||
@@ -1172,10 +1235,7 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
|
||||
setLayout.usesMaterial ? material : nullptr;
|
||||
const Resources::GaussianSplat* gaussianSplatKey =
|
||||
(setLayout.usesGaussianSplatOrderBuffer ||
|
||||
setLayout.usesGaussianSplatPositionBuffer ||
|
||||
setLayout.usesGaussianSplatOtherBuffer ||
|
||||
setLayout.usesGaussianSplatColorBuffer ||
|
||||
setLayout.usesGaussianSplatSHBuffer)
|
||||
setLayout.usesGaussianSplatViewDataBuffer)
|
||||
? visibleGaussianSplat.gaussianSplat
|
||||
: nullptr;
|
||||
|
||||
@@ -1194,13 +1254,13 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
|
||||
workingSet->orderIndices.shaderResourceView,
|
||||
workingSet->viewData.shaderResourceView);
|
||||
if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) {
|
||||
return false;
|
||||
return fail("BuiltinGaussianSplatPass draw failed: dynamic descriptor set resolution failed");
|
||||
}
|
||||
|
||||
RHI::RHIDescriptorSet* descriptorSet = cachedDescriptorSet->descriptorSet.set;
|
||||
if (setLayout.usesPerObject) {
|
||||
if (!passLayout->perObject.IsValid() || passLayout->perObject.set != setIndex) {
|
||||
return false;
|
||||
return fail("BuiltinGaussianSplatPass draw failed: per-object binding is invalid");
|
||||
}
|
||||
|
||||
descriptorSet->WriteConstant(
|
||||
|
||||
@@ -24,3 +24,4 @@ add_subdirectory(alpha_cutout_scene)
|
||||
add_subdirectory(volume_scene)
|
||||
add_subdirectory(volume_occlusion_scene)
|
||||
add_subdirectory(volume_transform_scene)
|
||||
add_subdirectory(gaussian_splat_scene)
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
project(rendering_integration_gaussian_splat_scene)
|
||||
|
||||
set(ENGINE_ROOT_DIR ${CMAKE_SOURCE_DIR}/engine)
|
||||
set(PACKAGE_DIR ${CMAKE_SOURCE_DIR}/mvs/OpenGL/package)
|
||||
|
||||
get_filename_component(PROJECT_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../.. ABSOLUTE)
|
||||
|
||||
find_package(Vulkan QUIET)
|
||||
|
||||
add_executable(rendering_integration_gaussian_splat_scene
|
||||
main.cpp
|
||||
${CMAKE_SOURCE_DIR}/tests/RHI/integration/fixtures/RHIIntegrationFixture.cpp
|
||||
${PACKAGE_DIR}/src/glad.c
|
||||
)
|
||||
|
||||
target_include_directories(rendering_integration_gaussian_splat_scene PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_SOURCE_DIR}/tests/RHI/integration/fixtures
|
||||
${ENGINE_ROOT_DIR}/include
|
||||
${PACKAGE_DIR}/include
|
||||
${PROJECT_ROOT_DIR}/engine/src
|
||||
)
|
||||
|
||||
target_link_libraries(rendering_integration_gaussian_splat_scene PRIVATE
|
||||
d3d12
|
||||
dxgi
|
||||
d3dcompiler
|
||||
winmm
|
||||
opengl32
|
||||
XCEngine
|
||||
GTest::gtest
|
||||
)
|
||||
|
||||
if(TARGET Vulkan::Vulkan)
|
||||
target_link_libraries(rendering_integration_gaussian_splat_scene PRIVATE Vulkan::Vulkan)
|
||||
target_compile_definitions(rendering_integration_gaussian_splat_scene PRIVATE XCENGINE_SUPPORT_VULKAN)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(rendering_integration_gaussian_splat_scene PRIVATE
|
||||
UNICODE
|
||||
_UNICODE
|
||||
XCENGINE_SUPPORT_OPENGL
|
||||
XCENGINE_SUPPORT_D3D12
|
||||
XCENGINE_TEST_ROOM_PLY_PATH="${CMAKE_SOURCE_DIR}/mvs/3DGS-Unity/room.ply"
|
||||
)
|
||||
|
||||
add_custom_command(TARGET rendering_integration_gaussian_splat_scene POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${CMAKE_SOURCE_DIR}/tests/RHI/integration/compare_ppm.py
|
||||
$<TARGET_FILE_DIR:rendering_integration_gaussian_splat_scene>/
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/GT.ppm
|
||||
$<TARGET_FILE_DIR:rendering_integration_gaussian_splat_scene>/GT.ppm
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${ENGINE_ROOT_DIR}/third_party/renderdoc/renderdoc.dll
|
||||
$<TARGET_FILE_DIR:rendering_integration_gaussian_splat_scene>/
|
||||
)
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(rendering_integration_gaussian_splat_scene)
|
||||
5699
tests/Rendering/integration/gaussian_splat_scene/GT.ppm
Normal file
5699
tests/Rendering/integration/gaussian_splat_scene/GT.ppm
Normal file
File diff suppressed because one or more lines are too long
485
tests/Rendering/integration/gaussian_splat_scene/main.cpp
Normal file
485
tests/Rendering/integration/gaussian_splat_scene/main.cpp
Normal file
@@ -0,0 +1,485 @@
|
||||
#define NOMINMAX
|
||||
#include <windows.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "../RenderingIntegrationMain.h"
|
||||
#include "../RenderingIntegrationImageAssert.h"
|
||||
|
||||
#include <XCEngine/Components/CameraComponent.h>
|
||||
#include <XCEngine/Components/GameObject.h>
|
||||
#include <XCEngine/Components/GaussianSplatRendererComponent.h>
|
||||
#include <XCEngine/Core/Asset/IResource.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Core/Math/Color.h>
|
||||
#include <XCEngine/Core/Math/Vector3.h>
|
||||
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
#include <XCEngine/Resources/GaussianSplat/GaussianSplat.h>
|
||||
#include <XCEngine/Resources/Material/Material.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Resources/Shader/Shader.h>
|
||||
#include <XCEngine/RHI/RHITexture.h>
|
||||
#include <XCEngine/Scene/Scene.h>
|
||||
|
||||
#include "../../../RHI/integration/fixtures/RHIIntegrationFixture.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <system_error>
|
||||
#include <vector>
|
||||
|
||||
using namespace XCEngine::Components;
|
||||
using namespace XCEngine::Math;
|
||||
using namespace XCEngine::Rendering;
|
||||
using namespace XCEngine::Resources;
|
||||
using namespace XCEngine::RHI;
|
||||
using namespace XCEngine::RHI::Integration;
|
||||
using namespace RenderingIntegrationTestUtils;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char* kD3D12Screenshot = "gaussian_splat_scene_d3d12.ppm";
|
||||
constexpr const char* kOpenGLScreenshot = "gaussian_splat_scene_opengl.ppm";
|
||||
constexpr const char* kVulkanScreenshot = "gaussian_splat_scene_vulkan.ppm";
|
||||
constexpr uint32_t kFrameWidth = 1280;
|
||||
constexpr uint32_t kFrameHeight = 720;
|
||||
constexpr uint32_t kBaselineSubsetSplatCount = 65536u;
|
||||
|
||||
std::filesystem::path GetRoomPlyPath() {
|
||||
return std::filesystem::path(XCENGINE_TEST_ROOM_PLY_PATH);
|
||||
}
|
||||
|
||||
std::filesystem::path CreateRuntimeProjectRoot() {
|
||||
return GetExecutableDirectory() /
|
||||
("__xc_gaussian_splat_scene_runtime_" + std::to_string(static_cast<unsigned long>(::GetCurrentProcessId())));
|
||||
}
|
||||
|
||||
void LinkOrCopyFixture(const std::filesystem::path& sourcePath, const std::filesystem::path& destinationPath) {
|
||||
std::error_code ec;
|
||||
std::filesystem::create_directories(destinationPath.parent_path(), ec);
|
||||
ec.clear();
|
||||
std::filesystem::create_hard_link(sourcePath, destinationPath, ec);
|
||||
if (ec) {
|
||||
ec.clear();
|
||||
std::filesystem::copy_file(
|
||||
sourcePath,
|
||||
destinationPath,
|
||||
std::filesystem::copy_options::overwrite_existing,
|
||||
ec);
|
||||
}
|
||||
|
||||
ASSERT_FALSE(ec) << ec.message();
|
||||
}
|
||||
|
||||
const char* GetScreenshotFilename(RHIType backendType) {
|
||||
switch (backendType) {
|
||||
case RHIType::D3D12:
|
||||
return kD3D12Screenshot;
|
||||
case RHIType::Vulkan:
|
||||
return kVulkanScreenshot;
|
||||
case RHIType::OpenGL:
|
||||
default:
|
||||
return kOpenGLScreenshot;
|
||||
}
|
||||
}
|
||||
|
||||
GaussianSplat* CreateGaussianSplatSubset(
|
||||
const GaussianSplat& source,
|
||||
uint32_t maxSplatCount,
|
||||
const char* path) {
|
||||
const uint32_t sourceSplatCount = source.GetSplatCount();
|
||||
const uint32_t subsetSplatCount = std::min(sourceSplatCount, maxSplatCount);
|
||||
if (subsetSplatCount == 0u) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const GaussianSplatPositionRecord* positions = source.GetPositionRecords();
|
||||
const GaussianSplatOtherRecord* other = source.GetOtherRecords();
|
||||
const GaussianSplatColorRecord* colors = source.GetColorRecords();
|
||||
const GaussianSplatSHRecord* sh = source.GetSHRecords();
|
||||
const GaussianSplatSection* positionsSection = source.FindSection(GaussianSplatSectionType::Positions);
|
||||
const GaussianSplatSection* otherSection = source.FindSection(GaussianSplatSectionType::Other);
|
||||
const GaussianSplatSection* colorSection = source.FindSection(GaussianSplatSectionType::Color);
|
||||
const GaussianSplatSection* shSection = source.FindSection(GaussianSplatSectionType::SH);
|
||||
if (positions == nullptr ||
|
||||
other == nullptr ||
|
||||
colors == nullptr ||
|
||||
positionsSection == nullptr ||
|
||||
otherSection == nullptr ||
|
||||
colorSection == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> selectedIndices(subsetSplatCount, 0u);
|
||||
for (uint32_t subsetIndex = 0u; subsetIndex < subsetSplatCount; ++subsetIndex) {
|
||||
const uint64_t scaledIndex =
|
||||
(static_cast<uint64_t>(subsetIndex) * static_cast<uint64_t>(sourceSplatCount)) /
|
||||
static_cast<uint64_t>(subsetSplatCount);
|
||||
selectedIndices[subsetIndex] =
|
||||
static_cast<uint32_t>(std::min<uint64_t>(scaledIndex, static_cast<uint64_t>(sourceSplatCount - 1u)));
|
||||
}
|
||||
|
||||
std::vector<GaussianSplatPositionRecord> subsetPositions(subsetSplatCount);
|
||||
std::vector<GaussianSplatOtherRecord> subsetOther(subsetSplatCount);
|
||||
std::vector<GaussianSplatColorRecord> subsetColors(subsetSplatCount);
|
||||
std::vector<GaussianSplatSHRecord> subsetSh(shSection != nullptr && sh != nullptr ? subsetSplatCount : 0u);
|
||||
for (uint32_t subsetIndex = 0u; subsetIndex < subsetSplatCount; ++subsetIndex) {
|
||||
const uint32_t sourceIndex = selectedIndices[subsetIndex];
|
||||
subsetPositions[subsetIndex] = positions[sourceIndex];
|
||||
subsetOther[subsetIndex] = other[sourceIndex];
|
||||
subsetColors[subsetIndex] = colors[sourceIndex];
|
||||
if (!subsetSh.empty()) {
|
||||
subsetSh[subsetIndex] = sh[sourceIndex];
|
||||
}
|
||||
}
|
||||
|
||||
XCEngine::Containers::Array<GaussianSplatSection> sections;
|
||||
XCEngine::Containers::Array<XCEngine::Core::uint8> payload;
|
||||
sections.Reserve(shSection != nullptr && sh != nullptr ? 4u : 3u);
|
||||
|
||||
size_t payloadOffset = 0u;
|
||||
auto appendSection = [&](const GaussianSplatSection& sourceSection,
|
||||
const void* sourceData,
|
||||
uint32_t elementCount,
|
||||
uint32_t elementStride) {
|
||||
GaussianSplatSection section = sourceSection;
|
||||
section.dataOffset = payloadOffset;
|
||||
section.elementCount = elementCount;
|
||||
section.elementStride = elementStride;
|
||||
section.dataSize = static_cast<XCEngine::Core::uint64>(elementCount) * elementStride;
|
||||
sections.PushBack(section);
|
||||
|
||||
const size_t newPayloadSize = payload.Size() + static_cast<size_t>(section.dataSize);
|
||||
payload.Resize(newPayloadSize);
|
||||
std::memcpy(payload.Data() + payloadOffset, sourceData, static_cast<size_t>(section.dataSize));
|
||||
payloadOffset = newPayloadSize;
|
||||
};
|
||||
|
||||
appendSection(
|
||||
*positionsSection,
|
||||
subsetPositions.data(),
|
||||
subsetSplatCount,
|
||||
sizeof(GaussianSplatPositionRecord));
|
||||
appendSection(
|
||||
*otherSection,
|
||||
subsetOther.data(),
|
||||
subsetSplatCount,
|
||||
sizeof(GaussianSplatOtherRecord));
|
||||
appendSection(
|
||||
*colorSection,
|
||||
subsetColors.data(),
|
||||
subsetSplatCount,
|
||||
sizeof(GaussianSplatColorRecord));
|
||||
if (shSection != nullptr && sh != nullptr) {
|
||||
appendSection(
|
||||
*shSection,
|
||||
subsetSh.data(),
|
||||
subsetSplatCount,
|
||||
sizeof(GaussianSplatSHRecord));
|
||||
}
|
||||
|
||||
GaussianSplatMetadata metadata = source.GetMetadata();
|
||||
metadata.splatCount = subsetSplatCount;
|
||||
metadata.bounds = source.GetBounds();
|
||||
metadata.chunkCount = 0u;
|
||||
metadata.cameraCount = 0u;
|
||||
metadata.chunkFormat = GaussianSplatSectionFormat::Unknown;
|
||||
metadata.cameraFormat = GaussianSplatSectionFormat::Unknown;
|
||||
|
||||
auto* gaussianSplat = new GaussianSplat();
|
||||
IResource::ConstructParams params = {};
|
||||
params.name = "GaussianSplatSceneSubset";
|
||||
params.path = path;
|
||||
params.guid = ResourceGUID::Generate(params.path);
|
||||
gaussianSplat->Initialize(params);
|
||||
if (!gaussianSplat->CreateOwned(metadata, std::move(sections), std::move(payload))) {
|
||||
delete gaussianSplat;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return gaussianSplat;
|
||||
}
|
||||
|
||||
class GaussianSplatSceneTest : public RHIIntegrationFixture {
|
||||
protected:
|
||||
void SetUp() override;
|
||||
void TearDown() override;
|
||||
void RenderFrame() override;
|
||||
|
||||
private:
|
||||
void PrepareRuntimeProject();
|
||||
void BuildScene();
|
||||
RHIResourceView* GetCurrentBackBufferView();
|
||||
|
||||
std::filesystem::path m_projectRoot;
|
||||
std::unique_ptr<Scene> m_scene;
|
||||
std::unique_ptr<SceneRenderer> m_sceneRenderer;
|
||||
std::vector<RHIResourceView*> m_backBufferViews;
|
||||
RHITexture* m_depthTexture = nullptr;
|
||||
RHIResourceView* m_depthView = nullptr;
|
||||
ResourceHandle<GaussianSplat> m_gaussianSplat;
|
||||
GaussianSplat* m_subsetGaussianSplat = nullptr;
|
||||
Material* m_material = nullptr;
|
||||
};
|
||||
|
||||
void GaussianSplatSceneTest::SetUp() {
|
||||
RHIIntegrationFixture::SetUp();
|
||||
PrepareRuntimeProject();
|
||||
|
||||
m_sceneRenderer = std::make_unique<SceneRenderer>();
|
||||
m_scene = std::make_unique<Scene>("GaussianSplatScene");
|
||||
|
||||
BuildScene();
|
||||
|
||||
TextureDesc depthDesc = {};
|
||||
depthDesc.width = kFrameWidth;
|
||||
depthDesc.height = kFrameHeight;
|
||||
depthDesc.depth = 1;
|
||||
depthDesc.mipLevels = 1;
|
||||
depthDesc.arraySize = 1;
|
||||
depthDesc.format = static_cast<uint32_t>(Format::D24_UNorm_S8_UInt);
|
||||
depthDesc.textureType = static_cast<uint32_t>(XCEngine::RHI::TextureType::Texture2D);
|
||||
depthDesc.sampleCount = 1;
|
||||
depthDesc.sampleQuality = 0;
|
||||
depthDesc.flags = 0;
|
||||
m_depthTexture = GetDevice()->CreateTexture(depthDesc);
|
||||
ASSERT_NE(m_depthTexture, nullptr);
|
||||
|
||||
ResourceViewDesc depthViewDesc = {};
|
||||
depthViewDesc.format = static_cast<uint32_t>(Format::D24_UNorm_S8_UInt);
|
||||
depthViewDesc.dimension = ResourceViewDimension::Texture2D;
|
||||
depthViewDesc.mipLevel = 0;
|
||||
m_depthView = GetDevice()->CreateDepthStencilView(m_depthTexture, depthViewDesc);
|
||||
ASSERT_NE(m_depthView, nullptr);
|
||||
|
||||
m_backBufferViews.resize(2, nullptr);
|
||||
}
|
||||
|
||||
void GaussianSplatSceneTest::TearDown() {
|
||||
m_sceneRenderer.reset();
|
||||
|
||||
if (m_depthView != nullptr) {
|
||||
m_depthView->Shutdown();
|
||||
delete m_depthView;
|
||||
m_depthView = nullptr;
|
||||
}
|
||||
|
||||
if (m_depthTexture != nullptr) {
|
||||
m_depthTexture->Shutdown();
|
||||
delete m_depthTexture;
|
||||
m_depthTexture = nullptr;
|
||||
}
|
||||
|
||||
for (RHIResourceView*& backBufferView : m_backBufferViews) {
|
||||
if (backBufferView != nullptr) {
|
||||
backBufferView->Shutdown();
|
||||
delete backBufferView;
|
||||
backBufferView = nullptr;
|
||||
}
|
||||
}
|
||||
m_backBufferViews.clear();
|
||||
|
||||
m_scene.reset();
|
||||
|
||||
delete m_material;
|
||||
m_material = nullptr;
|
||||
delete m_subsetGaussianSplat;
|
||||
m_subsetGaussianSplat = nullptr;
|
||||
m_gaussianSplat.Reset();
|
||||
|
||||
ResourceManager& manager = ResourceManager::Get();
|
||||
manager.UnloadAll();
|
||||
manager.SetResourceRoot("");
|
||||
manager.Shutdown();
|
||||
|
||||
if (!m_projectRoot.empty()) {
|
||||
std::error_code ec;
|
||||
std::filesystem::remove_all(m_projectRoot, ec);
|
||||
}
|
||||
|
||||
RHIIntegrationFixture::TearDown();
|
||||
}
|
||||
|
||||
void GaussianSplatSceneTest::PrepareRuntimeProject() {
|
||||
const std::filesystem::path roomPlyPath = GetRoomPlyPath();
|
||||
ASSERT_TRUE(std::filesystem::exists(roomPlyPath)) << roomPlyPath.string();
|
||||
|
||||
m_projectRoot = CreateRuntimeProjectRoot();
|
||||
const std::filesystem::path assetsDir = m_projectRoot / "Assets";
|
||||
const std::filesystem::path runtimeRoomPath = assetsDir / "room.ply";
|
||||
|
||||
std::error_code ec;
|
||||
std::filesystem::remove_all(m_projectRoot, ec);
|
||||
std::filesystem::create_directories(assetsDir, ec);
|
||||
ASSERT_FALSE(ec) << ec.message();
|
||||
|
||||
LinkOrCopyFixture(roomPlyPath, runtimeRoomPath);
|
||||
|
||||
ResourceManager& manager = ResourceManager::Get();
|
||||
manager.Initialize();
|
||||
manager.SetResourceRoot(m_projectRoot.string().c_str());
|
||||
|
||||
m_gaussianSplat = manager.Load<GaussianSplat>("Assets/room.ply");
|
||||
ASSERT_TRUE(m_gaussianSplat.IsValid());
|
||||
ASSERT_NE(m_gaussianSplat.Get(), nullptr);
|
||||
ASSERT_TRUE(m_gaussianSplat->IsValid());
|
||||
ASSERT_GT(m_gaussianSplat->GetSplatCount(), 0u);
|
||||
m_subsetGaussianSplat = CreateGaussianSplatSubset(
|
||||
*m_gaussianSplat.Get(),
|
||||
kBaselineSubsetSplatCount,
|
||||
"Tests/Rendering/GaussianSplatScene/RoomSubset.xcgsplat");
|
||||
ASSERT_NE(m_subsetGaussianSplat, nullptr);
|
||||
ASSERT_TRUE(m_subsetGaussianSplat->IsValid());
|
||||
ASSERT_GT(m_subsetGaussianSplat->GetSplatCount(), 0u);
|
||||
}
|
||||
|
||||
void GaussianSplatSceneTest::BuildScene() {
|
||||
ASSERT_NE(m_scene, nullptr);
|
||||
ASSERT_NE(m_subsetGaussianSplat, nullptr);
|
||||
|
||||
m_material = new Material();
|
||||
IResource::ConstructParams params = {};
|
||||
params.name = "GaussianSplatSceneMaterial";
|
||||
params.path = "Tests/Rendering/GaussianSplatScene/Room.material";
|
||||
params.guid = ResourceGUID::Generate(params.path);
|
||||
m_material->Initialize(params);
|
||||
m_material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinGaussianSplatShaderPath()));
|
||||
m_material->SetRenderQueue(MaterialRenderQueue::Transparent);
|
||||
m_material->SetFloat("_PointScale", 1.0f);
|
||||
m_material->SetFloat("_OpacityScale", 1.0f);
|
||||
|
||||
GameObject* cameraObject = m_scene->CreateGameObject("MainCamera");
|
||||
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
||||
camera->SetPrimary(true);
|
||||
camera->SetFieldOfView(45.0f);
|
||||
camera->SetNearClipPlane(0.1f);
|
||||
camera->SetFarClipPlane(500.0f);
|
||||
camera->SetClearColor(XCEngine::Math::Color(0.02f, 0.025f, 0.035f, 1.0f));
|
||||
|
||||
const Bounds& bounds = m_subsetGaussianSplat->GetBounds();
|
||||
const Vector3 boundsMin = bounds.GetMin();
|
||||
const Vector3 boundsMax = bounds.GetMax();
|
||||
const Vector3 center(
|
||||
(boundsMin.x + boundsMax.x) * 0.5f,
|
||||
(boundsMin.y + boundsMax.y) * 0.5f,
|
||||
(boundsMin.z + boundsMax.z) * 0.5f);
|
||||
const float sizeX = std::max(boundsMax.x - boundsMin.x, 0.001f);
|
||||
const float sizeY = std::max(boundsMax.y - boundsMin.y, 0.001f);
|
||||
const float sizeZ = std::max(boundsMax.z - boundsMin.z, 0.001f);
|
||||
const float maxExtent = std::max(sizeX, std::max(sizeY, sizeZ));
|
||||
const float uniformScale = 3.0f / maxExtent;
|
||||
|
||||
GameObject* root = m_scene->CreateGameObject("GaussianSplatRoot");
|
||||
root->GetTransform()->SetLocalPosition(Vector3(0.0f, -0.35f, 3.2f));
|
||||
root->GetTransform()->SetLocalScale(Vector3(uniformScale, uniformScale, uniformScale));
|
||||
|
||||
GameObject* splatObject = m_scene->CreateGameObject("RoomGaussianSplat");
|
||||
splatObject->SetParent(root, false);
|
||||
splatObject->GetTransform()->SetLocalPosition(Vector3(-center.x, -center.y, -center.z));
|
||||
|
||||
auto* splatRenderer = splatObject->AddComponent<GaussianSplatRendererComponent>();
|
||||
splatRenderer->SetGaussianSplat(m_subsetGaussianSplat);
|
||||
splatRenderer->SetMaterial(m_material);
|
||||
splatRenderer->SetCastShadows(false);
|
||||
splatRenderer->SetReceiveShadows(false);
|
||||
}
|
||||
|
||||
RHIResourceView* GaussianSplatSceneTest::GetCurrentBackBufferView() {
|
||||
const int backBufferIndex = GetCurrentBackBufferIndex();
|
||||
if (backBufferIndex < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (static_cast<size_t>(backBufferIndex) >= m_backBufferViews.size()) {
|
||||
m_backBufferViews.resize(static_cast<size_t>(backBufferIndex) + 1, nullptr);
|
||||
}
|
||||
|
||||
if (m_backBufferViews[backBufferIndex] == nullptr) {
|
||||
ResourceViewDesc viewDesc = {};
|
||||
viewDesc.format = static_cast<uint32_t>(Format::R8G8B8A8_UNorm);
|
||||
viewDesc.dimension = ResourceViewDimension::Texture2D;
|
||||
viewDesc.mipLevel = 0;
|
||||
m_backBufferViews[backBufferIndex] =
|
||||
GetDevice()->CreateRenderTargetView(GetCurrentBackBuffer(), viewDesc);
|
||||
}
|
||||
|
||||
return m_backBufferViews[backBufferIndex];
|
||||
}
|
||||
|
||||
void GaussianSplatSceneTest::RenderFrame() {
|
||||
ASSERT_NE(m_scene, nullptr);
|
||||
ASSERT_NE(m_sceneRenderer, nullptr);
|
||||
|
||||
RHICommandList* commandList = GetCommandList();
|
||||
ASSERT_NE(commandList, nullptr);
|
||||
|
||||
commandList->Reset();
|
||||
|
||||
RenderSurface surface(kFrameWidth, kFrameHeight);
|
||||
surface.SetColorAttachment(GetCurrentBackBufferView());
|
||||
surface.SetDepthAttachment(m_depthView);
|
||||
|
||||
RenderContext renderContext = {};
|
||||
renderContext.device = GetDevice();
|
||||
renderContext.commandList = commandList;
|
||||
renderContext.commandQueue = GetCommandQueue();
|
||||
renderContext.backendType = GetBackendType();
|
||||
|
||||
ASSERT_TRUE(m_sceneRenderer->Render(*m_scene, nullptr, renderContext, surface));
|
||||
|
||||
commandList->Close();
|
||||
void* commandLists[] = { commandList };
|
||||
GetCommandQueue()->ExecuteCommandLists(1, commandLists);
|
||||
}
|
||||
|
||||
TEST_P(GaussianSplatSceneTest, RenderRoomGaussianSplatScene) {
|
||||
RHICommandQueue* commandQueue = GetCommandQueue();
|
||||
RHISwapChain* swapChain = GetSwapChain();
|
||||
ASSERT_NE(commandQueue, nullptr);
|
||||
ASSERT_NE(swapChain, nullptr);
|
||||
|
||||
constexpr int kTargetFrameCount = 1;
|
||||
const char* screenshotFilename = GetScreenshotFilename(GetBackendType());
|
||||
|
||||
for (int frameCount = 0; frameCount <= kTargetFrameCount; ++frameCount) {
|
||||
if (frameCount > 0) {
|
||||
commandQueue->WaitForPreviousFrame();
|
||||
}
|
||||
|
||||
BeginRender();
|
||||
RenderFrame();
|
||||
|
||||
if (frameCount >= kTargetFrameCount) {
|
||||
commandQueue->WaitForIdle();
|
||||
ASSERT_TRUE(TakeScreenshot(screenshotFilename));
|
||||
|
||||
const std::filesystem::path gtPath = ResolveRuntimePath("GT.ppm");
|
||||
if (!std::filesystem::exists(gtPath)) {
|
||||
GTEST_SKIP() << "GT.ppm missing, screenshot captured for baseline generation: " << screenshotFilename;
|
||||
}
|
||||
|
||||
ASSERT_TRUE(CompareWithGoldenTemplate(screenshotFilename, "GT.ppm", 8.0f));
|
||||
break;
|
||||
}
|
||||
|
||||
swapChain->Present(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(D3D12, GaussianSplatSceneTest, ::testing::Values(RHIType::D3D12));
|
||||
#if defined(XCENGINE_SUPPORT_OPENGL)
|
||||
INSTANTIATE_TEST_SUITE_P(OpenGL, GaussianSplatSceneTest, ::testing::Values(RHIType::OpenGL));
|
||||
#endif
|
||||
#if defined(XCENGINE_SUPPORT_VULKAN)
|
||||
INSTANTIATE_TEST_SUITE_P(Vulkan, GaussianSplatSceneTest, ::testing::Values(RHIType::Vulkan));
|
||||
#endif
|
||||
|
||||
GTEST_API_ int main(int argc, char** argv) {
|
||||
return RunRenderingIntegrationTestMain(argc, argv);
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <XCEngine/Resources/Shader/Shader.h>
|
||||
#include <XCEngine/Resources/Shader/ShaderLoader.h>
|
||||
#include <XCEngine/RHI/RHIEnums.h>
|
||||
#include <XCEngine/RHI/OpenGL/OpenGLPipelineLayout.h>
|
||||
#include <XCEngine/RHI/ShaderCompiler/SpirvShaderCompiler.h>
|
||||
|
||||
#include <fstream>
|
||||
@@ -885,7 +886,7 @@ TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatShaderUsesAuthoringContrac
|
||||
|
||||
const ShaderPass* pass = shader->FindPass("GaussianSplat");
|
||||
ASSERT_NE(pass, nullptr);
|
||||
EXPECT_EQ(pass->resources.Size(), 6u);
|
||||
EXPECT_EQ(pass->resources.Size(), 4u);
|
||||
EXPECT_TRUE(pass->hasFixedFunctionState);
|
||||
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::None);
|
||||
EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable);
|
||||
@@ -910,35 +911,15 @@ TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatShaderUsesAuthoringContrac
|
||||
ResolveBuiltinPassResourceSemantic(*order),
|
||||
BuiltinPassResourceSemantic::GaussianSplatOrderBuffer);
|
||||
|
||||
const ShaderResourceBindingDesc* positions =
|
||||
shader->FindPassResourceBinding("GaussianSplat", "GaussianSplatPositions");
|
||||
ASSERT_NE(positions, nullptr);
|
||||
EXPECT_EQ(positions->type, ShaderResourceType::StructuredBuffer);
|
||||
EXPECT_EQ(positions->set, 2u);
|
||||
EXPECT_EQ(positions->binding, 1u);
|
||||
const ShaderResourceBindingDesc* viewData =
|
||||
shader->FindPassResourceBinding("GaussianSplat", "GaussianSplatViewDataBuffer");
|
||||
ASSERT_NE(viewData, nullptr);
|
||||
EXPECT_EQ(viewData->type, ShaderResourceType::StructuredBuffer);
|
||||
EXPECT_EQ(viewData->set, 2u);
|
||||
EXPECT_EQ(viewData->binding, 1u);
|
||||
EXPECT_EQ(
|
||||
ResolveBuiltinPassResourceSemantic(*positions),
|
||||
BuiltinPassResourceSemantic::GaussianSplatPositionBuffer);
|
||||
|
||||
const ShaderResourceBindingDesc* other =
|
||||
shader->FindPassResourceBinding("GaussianSplat", "GaussianSplatOther");
|
||||
ASSERT_NE(other, nullptr);
|
||||
EXPECT_EQ(other->type, ShaderResourceType::StructuredBuffer);
|
||||
EXPECT_EQ(other->set, 2u);
|
||||
EXPECT_EQ(other->binding, 2u);
|
||||
EXPECT_EQ(
|
||||
ResolveBuiltinPassResourceSemantic(*other),
|
||||
BuiltinPassResourceSemantic::GaussianSplatOtherBuffer);
|
||||
|
||||
const ShaderResourceBindingDesc* color =
|
||||
shader->FindPassResourceBinding("GaussianSplat", "GaussianSplatColor");
|
||||
ASSERT_NE(color, nullptr);
|
||||
EXPECT_EQ(color->type, ShaderResourceType::StructuredBuffer);
|
||||
EXPECT_EQ(color->set, 2u);
|
||||
EXPECT_EQ(color->binding, 3u);
|
||||
EXPECT_EQ(
|
||||
ResolveBuiltinPassResourceSemantic(*color),
|
||||
BuiltinPassResourceSemantic::GaussianSplatColorBuffer);
|
||||
ResolveBuiltinPassResourceSemantic(*viewData),
|
||||
BuiltinPassResourceSemantic::GaussianSplatViewDataBuffer);
|
||||
|
||||
delete shader;
|
||||
}
|
||||
@@ -958,24 +939,21 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoaded
|
||||
BuiltinPassResourceBindingPlan plan = {};
|
||||
String error;
|
||||
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
|
||||
ASSERT_EQ(plan.bindings.Size(), 6u);
|
||||
ASSERT_EQ(plan.bindings.Size(), 4u);
|
||||
EXPECT_TRUE(plan.perObject.IsValid());
|
||||
EXPECT_TRUE(plan.material.IsValid());
|
||||
EXPECT_TRUE(plan.gaussianSplatOrderBuffer.IsValid());
|
||||
EXPECT_TRUE(plan.gaussianSplatPositionBuffer.IsValid());
|
||||
EXPECT_TRUE(plan.gaussianSplatOtherBuffer.IsValid());
|
||||
EXPECT_TRUE(plan.gaussianSplatColorBuffer.IsValid());
|
||||
EXPECT_TRUE(plan.gaussianSplatViewDataBuffer.IsValid());
|
||||
EXPECT_FALSE(plan.gaussianSplatPositionBuffer.IsValid());
|
||||
EXPECT_FALSE(plan.gaussianSplatOtherBuffer.IsValid());
|
||||
EXPECT_FALSE(plan.gaussianSplatColorBuffer.IsValid());
|
||||
EXPECT_FALSE(plan.gaussianSplatSHBuffer.IsValid());
|
||||
EXPECT_EQ(plan.perObject.set, 0u);
|
||||
EXPECT_EQ(plan.material.set, 1u);
|
||||
EXPECT_EQ(plan.gaussianSplatOrderBuffer.set, 2u);
|
||||
EXPECT_EQ(plan.gaussianSplatOrderBuffer.binding, 0u);
|
||||
EXPECT_EQ(plan.gaussianSplatPositionBuffer.set, 2u);
|
||||
EXPECT_EQ(plan.gaussianSplatPositionBuffer.binding, 1u);
|
||||
EXPECT_EQ(plan.gaussianSplatOtherBuffer.set, 2u);
|
||||
EXPECT_EQ(plan.gaussianSplatOtherBuffer.binding, 2u);
|
||||
EXPECT_EQ(plan.gaussianSplatColorBuffer.set, 2u);
|
||||
EXPECT_EQ(plan.gaussianSplatColorBuffer.binding, 3u);
|
||||
EXPECT_EQ(plan.gaussianSplatViewDataBuffer.set, 2u);
|
||||
EXPECT_EQ(plan.gaussianSplatViewDataBuffer.binding, 1u);
|
||||
EXPECT_EQ(plan.firstDescriptorSet, 0u);
|
||||
EXPECT_EQ(plan.descriptorSetCount, 3u);
|
||||
|
||||
@@ -985,34 +963,23 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoaded
|
||||
EXPECT_TRUE(setLayouts[0].usesPerObject);
|
||||
EXPECT_TRUE(setLayouts[1].usesMaterial);
|
||||
EXPECT_TRUE(setLayouts[2].usesGaussianSplatOrderBuffer);
|
||||
EXPECT_TRUE(setLayouts[2].usesGaussianSplatPositionBuffer);
|
||||
EXPECT_TRUE(setLayouts[2].usesGaussianSplatOtherBuffer);
|
||||
EXPECT_TRUE(setLayouts[2].usesGaussianSplatColorBuffer);
|
||||
ASSERT_EQ(setLayouts[2].bindings.size(), 4u);
|
||||
EXPECT_TRUE(setLayouts[2].usesGaussianSplatViewDataBuffer);
|
||||
EXPECT_FALSE(setLayouts[2].usesGaussianSplatPositionBuffer);
|
||||
EXPECT_FALSE(setLayouts[2].usesGaussianSplatOtherBuffer);
|
||||
EXPECT_FALSE(setLayouts[2].usesGaussianSplatColorBuffer);
|
||||
ASSERT_EQ(setLayouts[2].bindings.size(), 2u);
|
||||
EXPECT_EQ(
|
||||
static_cast<DescriptorType>(setLayouts[2].bindings[0].type),
|
||||
DescriptorType::SRV);
|
||||
EXPECT_EQ(
|
||||
static_cast<DescriptorType>(setLayouts[2].bindings[1].type),
|
||||
DescriptorType::SRV);
|
||||
EXPECT_EQ(
|
||||
static_cast<DescriptorType>(setLayouts[2].bindings[2].type),
|
||||
DescriptorType::SRV);
|
||||
EXPECT_EQ(
|
||||
static_cast<DescriptorType>(setLayouts[2].bindings[3].type),
|
||||
DescriptorType::SRV);
|
||||
EXPECT_EQ(
|
||||
setLayouts[2].bindings[0].resourceDimension,
|
||||
ResourceViewDimension::StructuredBuffer);
|
||||
EXPECT_EQ(
|
||||
setLayouts[2].bindings[1].resourceDimension,
|
||||
ResourceViewDimension::StructuredBuffer);
|
||||
EXPECT_EQ(
|
||||
setLayouts[2].bindings[2].resourceDimension,
|
||||
ResourceViewDimension::StructuredBuffer);
|
||||
EXPECT_EQ(
|
||||
setLayouts[2].bindings[3].resourceDimension,
|
||||
ResourceViewDimension::StructuredBuffer);
|
||||
|
||||
delete shader;
|
||||
}
|
||||
@@ -1052,22 +1019,16 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringGauss
|
||||
d3d12Source,
|
||||
"cbuffer MaterialConstants",
|
||||
"register(b1)"));
|
||||
EXPECT_NE(d3d12Source.find("float4 gPointScaleParams;"), std::string::npos);
|
||||
EXPECT_NE(d3d12Source.find("float4 gOpacityScaleParams;"), std::string::npos);
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
d3d12Source,
|
||||
"StructuredBuffer<uint> GaussianSplatOrderBuffer",
|
||||
"register(t0)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
d3d12Source,
|
||||
"StructuredBuffer<float3> GaussianSplatPositions",
|
||||
"StructuredBuffer<GaussianSplatViewData> GaussianSplatViewDataBuffer",
|
||||
"register(t1)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
d3d12Source,
|
||||
"StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther",
|
||||
"register(t2)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
d3d12Source,
|
||||
"StructuredBuffer<float4> GaussianSplatColor",
|
||||
"register(t3)"));
|
||||
EXPECT_EQ(d3d12Source.find("space0"), std::string::npos);
|
||||
EXPECT_EQ(d3d12Source.find("space1"), std::string::npos);
|
||||
EXPECT_EQ(d3d12Source.find("space2"), std::string::npos);
|
||||
@@ -1095,22 +1056,16 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringGauss
|
||||
vulkanSource,
|
||||
"cbuffer MaterialConstants",
|
||||
"register(b0, space1)"));
|
||||
EXPECT_NE(vulkanSource.find("float4 gPointScaleParams;"), std::string::npos);
|
||||
EXPECT_NE(vulkanSource.find("float4 gOpacityScaleParams;"), std::string::npos);
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
vulkanSource,
|
||||
"StructuredBuffer<uint> GaussianSplatOrderBuffer",
|
||||
"register(t0, space2)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
vulkanSource,
|
||||
"StructuredBuffer<float3> GaussianSplatPositions",
|
||||
"StructuredBuffer<GaussianSplatViewData> GaussianSplatViewDataBuffer",
|
||||
"register(t1, space2)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
vulkanSource,
|
||||
"StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther",
|
||||
"register(t2, space2)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
vulkanSource,
|
||||
"StructuredBuffer<float4> GaussianSplatColor",
|
||||
"register(t3, space2)"));
|
||||
|
||||
delete shader;
|
||||
}
|
||||
@@ -1126,7 +1081,7 @@ TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatUtilitiesShaderUsesCompute
|
||||
|
||||
const ShaderPass* pass = shader->FindPass("GaussianSplatPrepareOrder");
|
||||
ASSERT_NE(pass, nullptr);
|
||||
EXPECT_EQ(pass->resources.Size(), 4u);
|
||||
EXPECT_EQ(pass->resources.Size(), 7u);
|
||||
|
||||
const ShaderResourceBindingDesc* perObject =
|
||||
shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "PerObjectConstants");
|
||||
@@ -1148,6 +1103,26 @@ TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatUtilitiesShaderUsesCompute
|
||||
ResolveBuiltinPassResourceSemantic(*positions),
|
||||
BuiltinPassResourceSemantic::GaussianSplatPositionBuffer);
|
||||
|
||||
const ShaderResourceBindingDesc* other =
|
||||
shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "GaussianSplatOther");
|
||||
ASSERT_NE(other, nullptr);
|
||||
EXPECT_EQ(other->type, ShaderResourceType::StructuredBuffer);
|
||||
EXPECT_EQ(other->set, 2u);
|
||||
EXPECT_EQ(other->binding, 1u);
|
||||
EXPECT_EQ(
|
||||
ResolveBuiltinPassResourceSemantic(*other),
|
||||
BuiltinPassResourceSemantic::GaussianSplatOtherBuffer);
|
||||
|
||||
const ShaderResourceBindingDesc* color =
|
||||
shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "GaussianSplatColor");
|
||||
ASSERT_NE(color, nullptr);
|
||||
EXPECT_EQ(color->type, ShaderResourceType::StructuredBuffer);
|
||||
EXPECT_EQ(color->set, 2u);
|
||||
EXPECT_EQ(color->binding, 2u);
|
||||
EXPECT_EQ(
|
||||
ResolveBuiltinPassResourceSemantic(*color),
|
||||
BuiltinPassResourceSemantic::GaussianSplatColorBuffer);
|
||||
|
||||
const ShaderResourceBindingDesc* sortDistances =
|
||||
shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "GaussianSplatSortDistances");
|
||||
ASSERT_NE(sortDistances, nullptr);
|
||||
@@ -1168,6 +1143,16 @@ TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatUtilitiesShaderUsesCompute
|
||||
ResolveBuiltinPassResourceSemantic(*orderBuffer),
|
||||
BuiltinPassResourceSemantic::GaussianSplatOrderBuffer);
|
||||
|
||||
const ShaderResourceBindingDesc* viewData =
|
||||
shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "GaussianSplatViewDataBuffer");
|
||||
ASSERT_NE(viewData, nullptr);
|
||||
EXPECT_EQ(viewData->type, ShaderResourceType::RWStructuredBuffer);
|
||||
EXPECT_EQ(viewData->set, 4u);
|
||||
EXPECT_EQ(viewData->binding, 2u);
|
||||
EXPECT_EQ(
|
||||
ResolveBuiltinPassResourceSemantic(*viewData),
|
||||
BuiltinPassResourceSemantic::GaussianSplatViewDataBuffer);
|
||||
|
||||
const ShaderStageVariant* computeVariant = shader->FindVariant(
|
||||
"GaussianSplatPrepareOrder",
|
||||
XCEngine::Resources::ShaderType::Compute,
|
||||
@@ -1193,17 +1178,26 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoaded
|
||||
BuiltinPassResourceBindingPlan plan = {};
|
||||
String error;
|
||||
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
|
||||
ASSERT_EQ(plan.bindings.Size(), 4u);
|
||||
ASSERT_EQ(plan.bindings.Size(), 7u);
|
||||
EXPECT_TRUE(plan.perObject.IsValid());
|
||||
EXPECT_TRUE(plan.gaussianSplatPositionBuffer.IsValid());
|
||||
EXPECT_TRUE(plan.gaussianSplatOtherBuffer.IsValid());
|
||||
EXPECT_TRUE(plan.gaussianSplatColorBuffer.IsValid());
|
||||
EXPECT_TRUE(plan.gaussianSplatSortDistanceBuffer.IsValid());
|
||||
EXPECT_TRUE(plan.gaussianSplatOrderBuffer.IsValid());
|
||||
EXPECT_TRUE(plan.gaussianSplatViewDataBuffer.IsValid());
|
||||
EXPECT_EQ(plan.perObject.set, 0u);
|
||||
EXPECT_EQ(plan.gaussianSplatPositionBuffer.set, 2u);
|
||||
EXPECT_EQ(plan.gaussianSplatOtherBuffer.set, 2u);
|
||||
EXPECT_EQ(plan.gaussianSplatOtherBuffer.binding, 1u);
|
||||
EXPECT_EQ(plan.gaussianSplatColorBuffer.set, 2u);
|
||||
EXPECT_EQ(plan.gaussianSplatColorBuffer.binding, 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.gaussianSplatViewDataBuffer.set, 4u);
|
||||
EXPECT_EQ(plan.gaussianSplatViewDataBuffer.binding, 2u);
|
||||
EXPECT_EQ(plan.firstDescriptorSet, 0u);
|
||||
EXPECT_EQ(plan.descriptorSetCount, 5u);
|
||||
|
||||
@@ -1212,25 +1206,98 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoaded
|
||||
ASSERT_EQ(setLayouts.size(), 5u);
|
||||
EXPECT_TRUE(setLayouts[0].usesPerObject);
|
||||
EXPECT_TRUE(setLayouts[2].usesGaussianSplatPositionBuffer);
|
||||
EXPECT_TRUE(setLayouts[2].usesGaussianSplatOtherBuffer);
|
||||
EXPECT_TRUE(setLayouts[2].usesGaussianSplatColorBuffer);
|
||||
EXPECT_TRUE(setLayouts[4].usesGaussianSplatSortDistanceBuffer);
|
||||
EXPECT_TRUE(setLayouts[4].usesGaussianSplatOrderBuffer);
|
||||
ASSERT_EQ(setLayouts[4].bindings.size(), 2u);
|
||||
EXPECT_TRUE(setLayouts[4].usesGaussianSplatViewDataBuffer);
|
||||
ASSERT_EQ(setLayouts[2].bindings.size(), 3u);
|
||||
ASSERT_EQ(setLayouts[4].bindings.size(), 3u);
|
||||
EXPECT_EQ(
|
||||
static_cast<DescriptorType>(setLayouts[2].bindings[0].type),
|
||||
DescriptorType::SRV);
|
||||
EXPECT_EQ(
|
||||
static_cast<DescriptorType>(setLayouts[2].bindings[1].type),
|
||||
DescriptorType::SRV);
|
||||
EXPECT_EQ(
|
||||
static_cast<DescriptorType>(setLayouts[2].bindings[2].type),
|
||||
DescriptorType::SRV);
|
||||
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(
|
||||
static_cast<DescriptorType>(setLayouts[4].bindings[2].type),
|
||||
DescriptorType::UAV);
|
||||
EXPECT_EQ(
|
||||
setLayouts[2].bindings[0].resourceDimension,
|
||||
ResourceViewDimension::StructuredBuffer);
|
||||
EXPECT_EQ(
|
||||
setLayouts[2].bindings[1].resourceDimension,
|
||||
ResourceViewDimension::StructuredBuffer);
|
||||
EXPECT_EQ(
|
||||
setLayouts[2].bindings[2].resourceDimension,
|
||||
ResourceViewDimension::StructuredBuffer);
|
||||
EXPECT_EQ(
|
||||
setLayouts[4].bindings[0].resourceDimension,
|
||||
ResourceViewDimension::StructuredBuffer);
|
||||
EXPECT_EQ(
|
||||
setLayouts[4].bindings[1].resourceDimension,
|
||||
ResourceViewDimension::StructuredBuffer);
|
||||
EXPECT_EQ(
|
||||
setLayouts[4].bindings[2].resourceDimension,
|
||||
ResourceViewDimension::StructuredBuffer);
|
||||
|
||||
delete shader;
|
||||
}
|
||||
|
||||
TEST(BuiltinForwardPipeline_Test, OpenGLPipelineLayoutUsesUnifiedStorageBufferBindingsForGaussianSplatUtilities) {
|
||||
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;
|
||||
ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
|
||||
|
||||
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
|
||||
ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr();
|
||||
ASSERT_EQ(setLayouts.size(), 5u);
|
||||
|
||||
RHIPipelineLayoutDesc layoutDesc = {};
|
||||
layoutDesc.setLayoutCount = static_cast<uint32_t>(setLayouts.size());
|
||||
std::vector<DescriptorSetLayoutDesc> layoutViews(setLayouts.size());
|
||||
for (size_t setIndex = 0; setIndex < setLayouts.size(); ++setIndex) {
|
||||
layoutViews[setIndex].bindingCount = static_cast<uint32_t>(setLayouts[setIndex].bindings.size());
|
||||
layoutViews[setIndex].bindings =
|
||||
setLayouts[setIndex].bindings.empty() ? nullptr : setLayouts[setIndex].bindings.data();
|
||||
}
|
||||
layoutDesc.setLayouts = layoutViews.data();
|
||||
|
||||
OpenGLPipelineLayout pipelineLayout;
|
||||
ASSERT_TRUE(pipelineLayout.Initialize(layoutDesc));
|
||||
|
||||
EXPECT_EQ(pipelineLayout.GetConstantBufferBindingPoint(0u, 0u), 0u);
|
||||
EXPECT_EQ(pipelineLayout.GetShaderResourceBindingPoint(2u, 0u), 0u);
|
||||
EXPECT_EQ(pipelineLayout.GetShaderResourceBindingPoint(2u, 1u), 1u);
|
||||
EXPECT_EQ(pipelineLayout.GetShaderResourceBindingPoint(2u, 2u), 2u);
|
||||
EXPECT_EQ(pipelineLayout.GetUnorderedAccessBindingPoint(4u, 0u), 3u);
|
||||
EXPECT_EQ(pipelineLayout.GetUnorderedAccessBindingPoint(4u, 1u), 4u);
|
||||
EXPECT_EQ(pipelineLayout.GetUnorderedAccessBindingPoint(4u, 2u), 5u);
|
||||
|
||||
pipelineLayout.Shutdown();
|
||||
delete shader;
|
||||
}
|
||||
|
||||
TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesGaussianSplatUtilitiesComputeBindingsToDescriptorSpaces) {
|
||||
ShaderLoader loader;
|
||||
LoadResult result = loader.Load(GetBuiltinGaussianSplatUtilitiesShaderPath());
|
||||
@@ -1264,8 +1331,16 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesGaussianSplatU
|
||||
"register(b0)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
d3d12Source,
|
||||
"StructuredBuffer<float3> GaussianSplatPositions",
|
||||
"StructuredBuffer<float4> GaussianSplatPositions",
|
||||
"register(t0)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
d3d12Source,
|
||||
"StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther",
|
||||
"register(t1)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
d3d12Source,
|
||||
"StructuredBuffer<float4> GaussianSplatColor",
|
||||
"register(t2)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
d3d12Source,
|
||||
"RWStructuredBuffer<uint> GaussianSplatSortDistances",
|
||||
@@ -1274,6 +1349,10 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesGaussianSplatU
|
||||
d3d12Source,
|
||||
"RWStructuredBuffer<uint> GaussianSplatOrderBuffer",
|
||||
"register(u1)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
d3d12Source,
|
||||
"RWStructuredBuffer<GaussianSplatViewData> GaussianSplatViewDataBuffer",
|
||||
"register(u2)"));
|
||||
|
||||
const ShaderStageVariant* vulkanCompute = shader->FindVariant(
|
||||
"GaussianSplatPrepareOrder",
|
||||
@@ -1296,8 +1375,16 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesGaussianSplatU
|
||||
"register(b0, space0)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
vulkanSource,
|
||||
"StructuredBuffer<float3> GaussianSplatPositions",
|
||||
"StructuredBuffer<float4> GaussianSplatPositions",
|
||||
"register(t0, space2)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
vulkanSource,
|
||||
"StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther",
|
||||
"register(t1, space2)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
vulkanSource,
|
||||
"StructuredBuffer<float4> GaussianSplatColor",
|
||||
"register(t2, space2)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
vulkanSource,
|
||||
"RWStructuredBuffer<uint> GaussianSplatSortDistances",
|
||||
@@ -1306,6 +1393,72 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesGaussianSplatU
|
||||
vulkanSource,
|
||||
"RWStructuredBuffer<uint> GaussianSplatOrderBuffer",
|
||||
"register(u1, space4)"));
|
||||
EXPECT_TRUE(SourceContainsRegisterBinding(
|
||||
vulkanSource,
|
||||
"RWStructuredBuffer<GaussianSplatViewData> GaussianSplatViewDataBuffer",
|
||||
"register(u2, space4)"));
|
||||
|
||||
delete shader;
|
||||
}
|
||||
|
||||
TEST(BuiltinForwardPipeline_Test, OpenGLRuntimeTranspilesGaussianSplatUtilitiesComputeVariant) {
|
||||
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* openGLCompute = shader->FindVariant(
|
||||
"GaussianSplatPrepareOrder",
|
||||
XCEngine::Resources::ShaderType::Compute,
|
||||
XCEngine::Resources::ShaderBackend::OpenGL);
|
||||
ASSERT_NE(openGLCompute, nullptr);
|
||||
|
||||
ShaderCompileDesc compileDesc = {};
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
*pass,
|
||||
XCEngine::Resources::ShaderBackend::OpenGL,
|
||||
*openGLCompute,
|
||||
compileDesc);
|
||||
|
||||
XCEngine::RHI::CompiledSpirvShader spirvShader = {};
|
||||
std::string errorMessage;
|
||||
ASSERT_TRUE(
|
||||
XCEngine::RHI::CompileSpirvShader(
|
||||
compileDesc,
|
||||
XCEngine::RHI::SpirvTargetEnvironment::Vulkan,
|
||||
spirvShader,
|
||||
&errorMessage))
|
||||
<< errorMessage;
|
||||
|
||||
std::string glslSource;
|
||||
ASSERT_TRUE(XCEngine::RHI::TranspileSpirvToOpenGLGLSL(spirvShader, glslSource, &errorMessage))
|
||||
<< errorMessage;
|
||||
ASSERT_FALSE(glslSource.empty());
|
||||
|
||||
EXPECT_NE(
|
||||
glslSource.find("layout(binding = 0, std430) readonly buffer type_StructuredBuffer_v4float"),
|
||||
std::string::npos);
|
||||
EXPECT_NE(
|
||||
glslSource.find("layout(binding = 1, std430) readonly buffer type_StructuredBuffer_GaussianSplatOtherData"),
|
||||
std::string::npos);
|
||||
EXPECT_NE(
|
||||
glslSource.find("layout(binding = 2, std430) readonly buffer GaussianSplatColor"),
|
||||
std::string::npos);
|
||||
EXPECT_NE(
|
||||
glslSource.find("layout(binding = 3, std430) buffer type_RWStructuredBuffer_uint"),
|
||||
std::string::npos);
|
||||
EXPECT_NE(
|
||||
glslSource.find("layout(binding = 4, std430) buffer GaussianSplatOrderBuffer"),
|
||||
std::string::npos);
|
||||
EXPECT_NE(
|
||||
glslSource.find("layout(binding = 5, std430) buffer type_RWStructuredBuffer_GaussianSplatViewData"),
|
||||
std::string::npos);
|
||||
|
||||
delete shader;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user