diff --git a/engine/assets/builtin/shaders/gaussian-splat-utilities.shader b/engine/assets/builtin/shaders/gaussian-splat-utilities.shader index 591a6776..708f1f4b 100644 --- a/engine/assets/builtin/shaders/gaussian-splat-utilities.shader +++ b/engine/assets/builtin/shaders/gaussian-splat-utilities.shader @@ -302,6 +302,60 @@ Shader "Builtin Gaussian Splat Utilities" SubShader { + Pass + { + Name "GaussianSplatMarkVisibleChunks" + HLSLPROGRAM + #pragma target 4.5 + #pragma compute GaussianSplatMarkVisibleChunksCS + + cbuffer PerObjectConstants + { + float4x4 gProjectionMatrix; + float4x4 gViewMatrix; + float4x4 gModelMatrix; + float4x4 gWorldToObjectMatrix; + float4 gCameraRight; + float4 gCameraUp; + float4 gCameraWorldPos; + float4 gScreenParams; + float4 gSplatParams; + }; + + StructuredBuffer GaussianSplatChunks; + RWStructuredBuffer GaussianSplatVisibleChunks; + + [numthreads(64, 1, 1)] + void GaussianSplatMarkVisibleChunksCS(uint3 dispatchThreadId : SV_DispatchThreadID) + { + const uint chunkCount = (uint)gSplatParams.w; + const uint chunkIndex = dispatchThreadId.x; + if (chunkIndex >= chunkCount) + { + return; + } + + const GaussianSplatChunkData chunkData = GaussianSplatChunks[chunkIndex]; + uint visibleFlag = 1u; + if (chunkData.posX.x > chunkData.posX.y || + chunkData.posY.x > chunkData.posY.y || + chunkData.posZ.x > chunkData.posZ.y || + IsChunkDefinitelyOutsideFrustum( + chunkData, + chunkCount, + chunkIndex, + gModelMatrix, + gViewMatrix, + gProjectionMatrix)) + { + visibleFlag = 0u; + } + + GaussianSplatVisibleChunks[chunkIndex] = visibleFlag; + } + ENDHLSL + } + Pass { Name "GaussianSplatPrepareOrder" @@ -326,7 +380,7 @@ Shader "Builtin Gaussian Splat Utilities" StructuredBuffer GaussianSplatOther; StructuredBuffer GaussianSplatColor; StructuredBuffer GaussianSplatSH; - StructuredBuffer GaussianSplatChunks; + StructuredBuffer GaussianSplatVisibleChunks : register(t0, space3); RWStructuredBuffer GaussianSplatSortDistances; RWStructuredBuffer GaussianSplatOrderBuffer; RWStructuredBuffer GaussianSplatViewDataBuffer; @@ -358,23 +412,8 @@ Shader "Builtin Gaussian Splat Utilities" const GaussianSplatSHData shData = GaussianSplatSH[index]; const uint chunkIndex = index / GAUSSIAN_SPLAT_CHUNK_SIZE; const uint chunkCount = (uint)gSplatParams.w; - const GaussianSplatChunkData chunkData = GaussianSplatChunks[chunkIndex]; const uint shOrder = min((uint)gSplatParams.z, 3u); - if (chunkData.posX.x > chunkData.posX.y || - chunkData.posY.x > chunkData.posY.y || - chunkData.posZ.x > chunkData.posZ.y) - { - GaussianSplatSortDistances[index] = 0xffffffffu; - GaussianSplatViewDataBuffer[index] = viewData; - return; - } - if (IsChunkDefinitelyOutsideFrustum( - chunkData, - chunkCount, - chunkIndex, - gModelMatrix, - gViewMatrix, - gProjectionMatrix)) + if (chunkIndex >= chunkCount || GaussianSplatVisibleChunks[chunkIndex] == 0u) { GaussianSplatSortDistances[index] = 0xffffffffu; GaussianSplatViewDataBuffer[index] = viewData; diff --git a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h index 2da253c8..22f65d45 100644 --- a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h +++ b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h @@ -84,6 +84,9 @@ inline bool TryBuildBuiltinPassResourceBindingPlan( case BuiltinPassResourceSemantic::GaussianSplatChunkBuffer: location = &outPlan.gaussianSplatChunkBuffer; break; + case BuiltinPassResourceSemantic::GaussianSplatVisibleChunkBuffer: + location = &outPlan.gaussianSplatVisibleChunkBuffer; + break; case BuiltinPassResourceSemantic::GaussianSplatViewDataBuffer: location = &outPlan.gaussianSplatViewDataBuffer; break; @@ -643,6 +646,9 @@ inline bool TryBuildBuiltinPassSetLayouts( case BuiltinPassResourceSemantic::GaussianSplatChunkBuffer: setLayout.usesGaussianSplatChunkBuffer = true; break; + case BuiltinPassResourceSemantic::GaussianSplatVisibleChunkBuffer: + setLayout.usesGaussianSplatVisibleChunkBuffer = true; + break; case BuiltinPassResourceSemantic::GaussianSplatViewDataBuffer: setLayout.usesGaussianSplatViewDataBuffer = true; break; diff --git a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h index 31d73783..b808254b 100644 --- a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h +++ b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h @@ -189,6 +189,12 @@ inline BuiltinPassResourceSemantic ResolveBuiltinPassResourceSemantic( return BuiltinPassResourceSemantic::GaussianSplatChunkBuffer; } + if (semantic == Containers::String("gaussiansplatvisiblechunkbuffer") || + semantic == Containers::String("gaussiansplatvisiblechunks") || + semantic == Containers::String("splatvisiblechunks")) { + return BuiltinPassResourceSemantic::GaussianSplatVisibleChunkBuffer; + } + if (semantic == Containers::String("gaussiansplatviewdatabuffer") || semantic == Containers::String("gaussiansplatviewdata") || semantic == Containers::String("splatviewdata")) { @@ -276,6 +282,8 @@ inline const char* BuiltinPassResourceSemanticToString(BuiltinPassResourceSemant return "GaussianSplatSHBuffer"; case BuiltinPassResourceSemantic::GaussianSplatChunkBuffer: return "GaussianSplatChunkBuffer"; + case BuiltinPassResourceSemantic::GaussianSplatVisibleChunkBuffer: + return "GaussianSplatVisibleChunkBuffer"; case BuiltinPassResourceSemantic::GaussianSplatViewDataBuffer: return "GaussianSplatViewDataBuffer"; case BuiltinPassResourceSemantic::BaseColorTexture: @@ -376,6 +384,7 @@ inline bool IsBuiltinPassResourceTypeCompatible( case BuiltinPassResourceSemantic::GaussianSplatColorBuffer: case BuiltinPassResourceSemantic::GaussianSplatSHBuffer: case BuiltinPassResourceSemantic::GaussianSplatChunkBuffer: + case BuiltinPassResourceSemantic::GaussianSplatVisibleChunkBuffer: case BuiltinPassResourceSemantic::GaussianSplatViewDataBuffer: return type == Resources::ShaderResourceType::StructuredBuffer || type == Resources::ShaderResourceType::RawBuffer || diff --git a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassTypes.h b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassTypes.h index d5efeaa6..d4b835c9 100644 --- a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassTypes.h +++ b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassTypes.h @@ -55,6 +55,7 @@ enum class BuiltinPassResourceSemantic : Core::uint8 { GaussianSplatColorBuffer, GaussianSplatSHBuffer, GaussianSplatChunkBuffer, + GaussianSplatVisibleChunkBuffer, GaussianSplatViewDataBuffer, BaseColorTexture, SourceColorTexture, @@ -98,6 +99,7 @@ struct BuiltinPassResourceBindingPlan { PassResourceBindingLocation gaussianSplatColorBuffer = {}; PassResourceBindingLocation gaussianSplatSHBuffer = {}; PassResourceBindingLocation gaussianSplatChunkBuffer = {}; + PassResourceBindingLocation gaussianSplatVisibleChunkBuffer = {}; PassResourceBindingLocation gaussianSplatViewDataBuffer = {}; PassResourceBindingLocation baseColorTexture = {}; PassResourceBindingLocation sourceColorTexture = {}; @@ -141,6 +143,7 @@ struct BuiltinPassSetLayoutMetadata { bool usesGaussianSplatColorBuffer = false; bool usesGaussianSplatSHBuffer = false; bool usesGaussianSplatChunkBuffer = false; + bool usesGaussianSplatVisibleChunkBuffer = false; bool usesGaussianSplatViewDataBuffer = false; bool usesTexture = false; bool usesBaseColorTexture = false; @@ -221,6 +224,7 @@ private: usesGaussianSplatColorBuffer = other.usesGaussianSplatColorBuffer; usesGaussianSplatSHBuffer = other.usesGaussianSplatSHBuffer; usesGaussianSplatChunkBuffer = other.usesGaussianSplatChunkBuffer; + usesGaussianSplatVisibleChunkBuffer = other.usesGaussianSplatVisibleChunkBuffer; usesGaussianSplatViewDataBuffer = other.usesGaussianSplatViewDataBuffer; usesTexture = other.usesTexture; usesBaseColorTexture = other.usesBaseColorTexture; diff --git a/engine/include/XCEngine/Rendering/Passes/BuiltinGaussianSplatPass.h b/engine/include/XCEngine/Rendering/Passes/BuiltinGaussianSplatPass.h index 65da2e33..22decc77 100644 --- a/engine/include/XCEngine/Rendering/Passes/BuiltinGaussianSplatPass.h +++ b/engine/include/XCEngine/Rendering/Passes/BuiltinGaussianSplatPass.h @@ -101,6 +101,7 @@ private: PassResourceBindingLocation gaussianSplatColorBuffer = {}; PassResourceBindingLocation gaussianSplatSHBuffer = {}; PassResourceBindingLocation gaussianSplatChunkBuffer = {}; + PassResourceBindingLocation gaussianSplatVisibleChunkBuffer = {}; PassResourceBindingLocation gaussianSplatViewDataBuffer = {}; }; @@ -144,6 +145,7 @@ private: RHI::RHIResourceView* colorView = nullptr; RHI::RHIResourceView* shView = nullptr; RHI::RHIResourceView* chunkView = nullptr; + RHI::RHIResourceView* visibleChunkView = nullptr; RHI::RHIResourceView* viewDataView = nullptr; }; @@ -215,6 +217,7 @@ private: enum class PassLayoutUsage : Core::uint8 { Draw, + MarkVisibleChunks, PrepareOrder, BitonicSort }; @@ -228,6 +231,7 @@ private: ResolvedShaderPass ResolveGaussianSplatShaderPass( const RenderSceneData& sceneData, const Resources::Material* material) const; + ResolvedShaderPass ResolveMarkVisibleChunksShaderPass(const RenderSceneData& sceneData) const; ResolvedShaderPass ResolvePrepareOrderShaderPass(const RenderSceneData& sceneData) const; ResolvedShaderPass ResolveBitonicSortShaderPass(const RenderSceneData& sceneData) const; PassResourceLayout* GetOrCreatePassResourceLayout( @@ -260,9 +264,14 @@ private: const RenderResourceCache::CachedGaussianSplat& cachedGaussianSplat, RHI::RHIResourceView* sortDistanceView, RHI::RHIResourceView* orderView, + RHI::RHIResourceView* visibleChunkView, RHI::RHIResourceView* viewDataView); void DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet); void DestroyPassResourceLayout(PassResourceLayout& passLayout); + bool MarkVisibleGaussianSplatChunks( + const RenderContext& context, + const RenderSceneData& sceneData, + const VisibleGaussianSplatItem& visibleGaussianSplat); bool PrepareVisibleGaussianSplat( const RenderContext& context, const RenderSceneData& sceneData, diff --git a/engine/src/Rendering/Passes/BuiltinGaussianSplatPass.cpp b/engine/src/Rendering/Passes/BuiltinGaussianSplatPass.cpp index 369b8b1a..5eb4cdda 100644 --- a/engine/src/Rendering/Passes/BuiltinGaussianSplatPass.cpp +++ b/engine/src/Rendering/Passes/BuiltinGaussianSplatPass.cpp @@ -262,6 +262,8 @@ bool BuiltinGaussianSplatPass::PrepareGaussianSplatResources( Internal::BuiltinGaussianSplatPassResources::WorkingSet* workingSet = nullptr; if (!m_passResources->EnsureWorkingSet(m_device, visibleGaussianSplat, workingSet) || workingSet == nullptr || + workingSet->visibleChunks.shaderResourceView == nullptr || + workingSet->visibleChunks.unorderedAccessView == nullptr || workingSet->sortDistances.unorderedAccessView == nullptr || workingSet->orderIndices.shaderResourceView == nullptr || workingSet->orderIndices.unorderedAccessView == nullptr || @@ -325,6 +327,13 @@ bool BuiltinGaussianSplatPass::Execute(const RenderPassContext& context) { commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList); for (const VisibleGaussianSplatItem& visibleGaussianSplat : context.sceneData.visibleGaussianSplats) { + if (!MarkVisibleGaussianSplatChunks( + context.renderContext, + context.sceneData, + visibleGaussianSplat)) { + return false; + } + if (!PrepareVisibleGaussianSplat( context.renderContext, context.sceneData, @@ -509,6 +518,26 @@ BuiltinGaussianSplatPass::ResolvedShaderPass BuiltinGaussianSplatPass::ResolvePr return resolved; } +BuiltinGaussianSplatPass::ResolvedShaderPass BuiltinGaussianSplatPass::ResolveMarkVisibleChunksShaderPass( + const RenderSceneData& sceneData) const { + ResolvedShaderPass resolved = {}; + if (!m_builtinGaussianSplatUtilitiesShader.IsValid()) { + return resolved; + } + + const Resources::Shader* shader = m_builtinGaussianSplatUtilitiesShader.Get(); + const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(m_backendType); + const Containers::String passName("GaussianSplatMarkVisibleChunks"); + if (const Resources::ShaderPass* shaderPass = + FindCompatibleComputePass(*shader, passName, sceneData.globalShaderKeywords, backend)) { + resolved.shader = shader; + resolved.pass = shaderPass; + resolved.passName = passName; + } + + return resolved; +} + BuiltinGaussianSplatPass::ResolvedShaderPass BuiltinGaussianSplatPass::ResolveBitonicSortShaderPass( const RenderSceneData& sceneData) const { ResolvedShaderPass resolved = {}; @@ -581,6 +610,7 @@ BuiltinGaussianSplatPass::PassResourceLayout* BuiltinGaussianSplatPass::GetOrCre passLayout.gaussianSplatColorBuffer = bindingPlan.gaussianSplatColorBuffer; passLayout.gaussianSplatSHBuffer = bindingPlan.gaussianSplatSHBuffer; passLayout.gaussianSplatChunkBuffer = bindingPlan.gaussianSplatChunkBuffer; + passLayout.gaussianSplatVisibleChunkBuffer = bindingPlan.gaussianSplatVisibleChunkBuffer; passLayout.gaussianSplatViewDataBuffer = bindingPlan.gaussianSplatViewDataBuffer; if (!passLayout.perObject.IsValid()) { @@ -596,6 +626,12 @@ BuiltinGaussianSplatPass::PassResourceLayout* BuiltinGaussianSplatPass::GetOrCre return failLayout( "BuiltinGaussianSplatPass draw pass requires order and view-data gaussian splat buffer bindings"); } + } else if (usage == PassLayoutUsage::MarkVisibleChunks) { + if (!passLayout.gaussianSplatChunkBuffer.IsValid() || + !passLayout.gaussianSplatVisibleChunkBuffer.IsValid()) { + return failLayout( + "BuiltinGaussianSplatPass mark-visible-chunks pass requires chunk and visible-chunk gaussian splat buffer bindings"); + } } else if (usage == PassLayoutUsage::PrepareOrder) { if (!passLayout.gaussianSplatSortDistanceBuffer.IsValid() || !passLayout.gaussianSplatOrderBuffer.IsValid() || @@ -603,10 +639,10 @@ BuiltinGaussianSplatPass::PassResourceLayout* BuiltinGaussianSplatPass::GetOrCre !passLayout.gaussianSplatOtherBuffer.IsValid() || !passLayout.gaussianSplatColorBuffer.IsValid() || !passLayout.gaussianSplatSHBuffer.IsValid() || - !passLayout.gaussianSplatChunkBuffer.IsValid() || + !passLayout.gaussianSplatVisibleChunkBuffer.IsValid() || !passLayout.gaussianSplatViewDataBuffer.IsValid()) { return failLayout( - "BuiltinGaussianSplatPass prepare-order pass requires sort distance, order, position, other, color, SH, chunk, and view-data gaussian splat buffer bindings"); + "BuiltinGaussianSplatPass prepare-order pass requires sort distance, order, position, other, color, SH, visible-chunk, and view-data gaussian splat buffer bindings"); } } else if (usage == PassLayoutUsage::BitonicSort) { if (!passLayout.gaussianSplatSortDistanceBuffer.IsValid() || @@ -785,6 +821,7 @@ BuiltinGaussianSplatPass::CachedDescriptorSet* BuiltinGaussianSplatPass::GetOrCr const RenderResourceCache::CachedGaussianSplat& cachedGaussianSplat, RHI::RHIResourceView* sortDistanceView, RHI::RHIResourceView* orderView, + RHI::RHIResourceView* visibleChunkView, RHI::RHIResourceView* viewDataView) { DynamicDescriptorSetKey key = {}; key.passLayout = passLayoutKey; @@ -810,6 +847,7 @@ BuiltinGaussianSplatPass::CachedDescriptorSet* BuiltinGaussianSplatPass::GetOrCr : nullptr; RHI::RHIResourceView* resolvedSortDistanceView = sortDistanceView; RHI::RHIResourceView* resolvedOrderView = orderView; + RHI::RHIResourceView* resolvedVisibleChunkView = visibleChunkView; RHI::RHIResourceView* resolvedViewDataView = viewDataView; if (setLayout.usesGaussianSplatSortDistanceBuffer && workingSet != nullptr) { @@ -839,6 +877,15 @@ BuiltinGaussianSplatPass::CachedDescriptorSet* BuiltinGaussianSplatPass::GetOrCr } } + if (setLayout.usesGaussianSplatVisibleChunkBuffer && workingSet != nullptr) { + if (const RHI::DescriptorSetLayoutBinding* layoutBinding = + FindSetLayoutBinding(setLayout, passLayout.gaussianSplatVisibleChunkBuffer.binding)) { + if (RHI::RHIResourceView* view = ResolveWorkingSetView(workingSet->visibleChunks, layoutBinding)) { + resolvedVisibleChunkView = view; + } + } + } + const Core::uint64 materialVersion = material != nullptr ? material->GetChangeVersion() : 0u; if (setLayout.usesMaterial) { if (!passLayout.material.IsValid() || @@ -953,6 +1000,20 @@ BuiltinGaussianSplatPass::CachedDescriptorSet* BuiltinGaussianSplatPass::GetOrCr } } + if (setLayout.usesGaussianSplatVisibleChunkBuffer) { + if (resolvedVisibleChunkView == nullptr || + !passLayout.gaussianSplatVisibleChunkBuffer.IsValid() || + passLayout.gaussianSplatVisibleChunkBuffer.set != setIndex) { + return nullptr; + } + + if (cachedDescriptorSet.visibleChunkView != resolvedVisibleChunkView) { + cachedDescriptorSet.descriptorSet.set->Update( + passLayout.gaussianSplatVisibleChunkBuffer.binding, + resolvedVisibleChunkView); + } + } + if (setLayout.usesGaussianSplatViewDataBuffer) { if (resolvedViewDataView == nullptr || !passLayout.gaussianSplatViewDataBuffer.IsValid() || @@ -975,6 +1036,7 @@ BuiltinGaussianSplatPass::CachedDescriptorSet* BuiltinGaussianSplatPass::GetOrCr cachedDescriptorSet.colorView = cachedGaussianSplat.color.shaderResourceView; cachedDescriptorSet.shView = cachedGaussianSplat.sh.shaderResourceView; cachedDescriptorSet.chunkView = cachedGaussianSplat.chunks.shaderResourceView; + cachedDescriptorSet.visibleChunkView = resolvedVisibleChunkView; cachedDescriptorSet.viewDataView = resolvedViewDataView; return &cachedDescriptorSet; } @@ -1012,9 +1074,178 @@ void BuiltinGaussianSplatPass::DestroyPassResourceLayout(PassResourceLayout& pas passLayout.gaussianSplatColorBuffer = {}; passLayout.gaussianSplatSHBuffer = {}; passLayout.gaussianSplatChunkBuffer = {}; + passLayout.gaussianSplatVisibleChunkBuffer = {}; passLayout.gaussianSplatViewDataBuffer = {}; } +bool BuiltinGaussianSplatPass::MarkVisibleGaussianSplatChunks( + 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 fail("BuiltinGaussianSplatPass mark-visible-chunks failed: invalid visible gaussian splat item"); + } + + const RenderResourceCache::CachedGaussianSplat* cachedGaussianSplat = + m_resourceCache.GetOrCreateGaussianSplat(m_device, visibleGaussianSplat.gaussianSplat); + if (cachedGaussianSplat == nullptr || + cachedGaussianSplat->chunks.shaderResourceView == nullptr || + cachedGaussianSplat->chunkCount == 0u) { + return fail("BuiltinGaussianSplatPass mark-visible-chunks failed: gaussian splat chunk GPU cache is incomplete"); + } + + Internal::BuiltinGaussianSplatPassResources::WorkingSet* workingSet = nullptr; + if (!m_passResources->EnsureWorkingSet(m_device, visibleGaussianSplat, workingSet) || + workingSet == nullptr || + workingSet->visibleChunks.shaderResourceView == nullptr || + workingSet->visibleChunks.unorderedAccessView == nullptr) { + return fail("BuiltinGaussianSplatPass mark-visible-chunks failed: working set allocation is incomplete"); + } + + const ResolvedShaderPass resolvedShaderPass = ResolveMarkVisibleChunksShaderPass(sceneData); + if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { + return fail("BuiltinGaussianSplatPass mark-visible-chunks failed: utilities shader pass was not resolved"); + } + + PassLayoutKey passLayoutKey = {}; + passLayoutKey.shader = resolvedShaderPass.shader; + passLayoutKey.passName = resolvedShaderPass.passName; + + PassResourceLayout* passLayout = GetOrCreatePassResourceLayout( + context, + resolvedShaderPass, + PassLayoutUsage::MarkVisibleChunks); + RHI::RHIPipelineState* pipelineState = GetOrCreateComputePipelineState( + context, + resolvedShaderPass, + PassLayoutUsage::MarkVisibleChunks, + sceneData.globalShaderKeywords); + if (passLayout == nullptr || pipelineState == nullptr) { + return fail("BuiltinGaussianSplatPass mark-visible-chunks failed: compute pipeline setup was not created"); + } + + RHI::RHICommandList* commandList = context.commandList; + if (!TransitionWorkingSetBuffer(commandList, workingSet->visibleChunks, RHI::ResourceStates::UnorderedAccess)) { + return fail("BuiltinGaussianSplatPass mark-visible-chunks failed: resource transition failed"); + } + + commandList->SetPipelineState(pipelineState); + + const float shOrder = + visibleGaussianSplat.gaussianSplat != nullptr + ? static_cast(visibleGaussianSplat.gaussianSplat->GetSHOrder()) + : 0.0f; + + const PerObjectConstants perObjectConstants = { + sceneData.cameraData.projection, + sceneData.cameraData.view, + visibleGaussianSplat.localToWorld.Transpose(), + visibleGaussianSplat.localToWorld.Inverse(), + Math::Vector4(sceneData.cameraData.worldRight, 0.0f), + Math::Vector4(sceneData.cameraData.worldUp, 0.0f), + Math::Vector4(sceneData.cameraData.worldPosition, 0.0f), + Math::Vector4( + static_cast(sceneData.cameraData.viewportWidth), + static_cast(sceneData.cameraData.viewportHeight), + sceneData.cameraData.viewportWidth > 0u ? 1.0f / static_cast(sceneData.cameraData.viewportWidth) : 0.0f, + sceneData.cameraData.viewportHeight > 0u ? 1.0f / static_cast(sceneData.cameraData.viewportHeight) : 0.0f), + Math::Vector4( + static_cast(cachedGaussianSplat->splatCount), + static_cast(workingSet->sortCapacity), + shOrder, + static_cast(cachedGaussianSplat->chunkCount)) + }; + + if (passLayout->descriptorSetCount > 0u) { + std::vector descriptorSets(passLayout->descriptorSetCount, nullptr); + for (Core::uint32 descriptorOffset = 0u; descriptorOffset < passLayout->descriptorSetCount; ++descriptorOffset) { + const Core::uint32 setIndex = passLayout->firstDescriptorSet + descriptorOffset; + if (setIndex >= passLayout->setLayouts.size()) { + return fail("BuiltinGaussianSplatPass mark-visible-chunks failed: descriptor set index overflow"); + } + + const BuiltinPassSetLayoutMetadata& setLayout = passLayout->setLayouts[setIndex]; + if (setLayout.layout.bindingCount == 0u) { + continue; + } + + if (!(setLayout.usesPerObject || + setLayout.usesGaussianSplatChunkBuffer || + setLayout.usesGaussianSplatVisibleChunkBuffer)) { + return fail("BuiltinGaussianSplatPass mark-visible-chunks failed: unexpected descriptor set layout"); + } + + const Core::uint64 objectId = + setLayout.usesPerObject ? visibleGaussianSplat.gameObject->GetID() : 0u; + const Resources::GaussianSplat* gaussianSplatKey = + (setLayout.usesGaussianSplatChunkBuffer || + setLayout.usesGaussianSplatVisibleChunkBuffer) + ? visibleGaussianSplat.gaussianSplat + : nullptr; + + CachedDescriptorSet* cachedDescriptorSet = GetOrCreateDynamicDescriptorSet( + passLayoutKey, + *passLayout, + setLayout, + setIndex, + objectId, + visibleGaussianSplat.gaussianSplatRenderer, + nullptr, + gaussianSplatKey, + MaterialConstantPayloadView(), + *cachedGaussianSplat, + nullptr, + nullptr, + workingSet->visibleChunks.unorderedAccessView, + nullptr); + if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) { + return fail("BuiltinGaussianSplatPass mark-visible-chunks failed: dynamic descriptor set resolution failed"); + } + + RHI::RHIDescriptorSet* descriptorSet = cachedDescriptorSet->descriptorSet.set; + if (setLayout.usesPerObject) { + if (!passLayout->perObject.IsValid() || passLayout->perObject.set != setIndex) { + return fail("BuiltinGaussianSplatPass mark-visible-chunks failed: per-object binding is invalid"); + } + + descriptorSet->WriteConstant( + passLayout->perObject.binding, + &perObjectConstants, + sizeof(perObjectConstants)); + } + + descriptorSets[descriptorOffset] = descriptorSet; + } + + BindDescriptorSetRanges( + passLayout->firstDescriptorSet, + descriptorSets, + [commandList, passLayout](Core::uint32 firstSet, Core::uint32 count, RHI::RHIDescriptorSet** sets) { + commandList->SetComputeDescriptorSets( + firstSet, + count, + sets, + passLayout->pipelineLayout); + }); + } + + commandList->Dispatch((cachedGaussianSplat->chunkCount + 63u) / 64u, 1u, 1u); + + if (!TransitionWorkingSetBuffer(commandList, workingSet->visibleChunks, RHI::ResourceStates::NonPixelShaderResource)) { + return fail("BuiltinGaussianSplatPass mark-visible-chunks failed: visible-chunk transition failed"); + } + + return true; +} + bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat( const RenderContext& context, const RenderSceneData& sceneData, @@ -1037,14 +1268,14 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat( cachedGaussianSplat->positions.shaderResourceView == nullptr || cachedGaussianSplat->other.shaderResourceView == nullptr || cachedGaussianSplat->color.shaderResourceView == nullptr || - cachedGaussianSplat->sh.shaderResourceView == nullptr || - cachedGaussianSplat->chunks.shaderResourceView == nullptr) { + cachedGaussianSplat->sh.shaderResourceView == nullptr) { return fail("BuiltinGaussianSplatPass prepare-order failed: gaussian splat GPU cache is incomplete"); } Internal::BuiltinGaussianSplatPassResources::WorkingSet* workingSet = nullptr; if (!m_passResources->EnsureWorkingSet(m_device, visibleGaussianSplat, workingSet) || workingSet == nullptr || + workingSet->visibleChunks.shaderResourceView == nullptr || workingSet->sortDistances.unorderedAccessView == nullptr || workingSet->orderIndices.unorderedAccessView == nullptr || workingSet->orderIndices.shaderResourceView == nullptr || @@ -1076,7 +1307,8 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat( } RHI::RHICommandList* commandList = context.commandList; - if (!TransitionWorkingSetBuffer(commandList, workingSet->sortDistances, RHI::ResourceStates::UnorderedAccess) || + if (!TransitionWorkingSetBuffer(commandList, workingSet->visibleChunks, RHI::ResourceStates::NonPixelShaderResource) || + !TransitionWorkingSetBuffer(commandList, workingSet->sortDistances, RHI::ResourceStates::UnorderedAccess) || !TransitionWorkingSetBuffer(commandList, workingSet->orderIndices, RHI::ResourceStates::UnorderedAccess) || !TransitionWorkingSetBuffer(commandList, workingSet->viewData, RHI::ResourceStates::UnorderedAccess)) { return fail("BuiltinGaussianSplatPass prepare-order failed: resource transition failed"); @@ -1123,13 +1355,13 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat( } if (!(setLayout.usesPerObject || + setLayout.usesGaussianSplatVisibleChunkBuffer || setLayout.usesGaussianSplatSortDistanceBuffer || setLayout.usesGaussianSplatOrderBuffer || setLayout.usesGaussianSplatPositionBuffer || setLayout.usesGaussianSplatOtherBuffer || setLayout.usesGaussianSplatColorBuffer || setLayout.usesGaussianSplatSHBuffer || - setLayout.usesGaussianSplatChunkBuffer || setLayout.usesGaussianSplatViewDataBuffer)) { return fail("BuiltinGaussianSplatPass prepare-order failed: unexpected descriptor set layout"); } @@ -1138,12 +1370,12 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat( setLayout.usesPerObject ? visibleGaussianSplat.gameObject->GetID() : 0u; const Resources::GaussianSplat* gaussianSplatKey = (setLayout.usesGaussianSplatSortDistanceBuffer || + setLayout.usesGaussianSplatVisibleChunkBuffer || setLayout.usesGaussianSplatOrderBuffer || setLayout.usesGaussianSplatPositionBuffer || setLayout.usesGaussianSplatOtherBuffer || setLayout.usesGaussianSplatColorBuffer || setLayout.usesGaussianSplatSHBuffer || - setLayout.usesGaussianSplatChunkBuffer || setLayout.usesGaussianSplatViewDataBuffer) ? visibleGaussianSplat.gaussianSplat : nullptr; @@ -1161,6 +1393,7 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat( *cachedGaussianSplat, workingSet->sortDistances.unorderedAccessView, workingSet->orderIndices.unorderedAccessView, + workingSet->visibleChunks.shaderResourceView, workingSet->viewData.unorderedAccessView); if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) { return fail("BuiltinGaussianSplatPass prepare-order failed: dynamic descriptor set resolution failed"); @@ -1332,6 +1565,7 @@ bool BuiltinGaussianSplatPass::SortVisibleGaussianSplat( *cachedGaussianSplat, workingSet->sortDistances.unorderedAccessView, workingSet->orderIndices.unorderedAccessView, + nullptr, nullptr); if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) { return fail("BuiltinGaussianSplatPass bitonic-sort failed: dynamic descriptor set resolution failed"); @@ -1507,6 +1741,7 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat( *cachedGaussianSplat, nullptr, workingSet->orderIndices.shaderResourceView, + nullptr, workingSet->viewData.shaderResourceView); if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) { return fail("BuiltinGaussianSplatPass draw failed: dynamic descriptor set resolution failed"); diff --git a/engine/src/Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.cpp b/engine/src/Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.cpp index 303a41b4..bfa1e0f4 100644 --- a/engine/src/Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.cpp +++ b/engine/src/Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.cpp @@ -92,12 +92,16 @@ bool BuiltinGaussianSplatPassResources::EnsureWorkingSet( const Core::uint32 splatCapacity = visibleGaussianSplat.gaussianSplat->GetSplatCount(); const Core::uint32 sortCapacity = ComputeSortCapacity(splatCapacity); + Core::uint32 chunkCapacity = visibleGaussianSplat.gaussianSplat->GetChunkCount(); if (splatCapacity == 0u) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "BuiltinGaussianSplatPassResources::EnsureWorkingSet failed: splat capacity is zero"); return false; } + if (chunkCapacity == 0u) { + chunkCapacity = (splatCapacity + 255u) / 256u; + } if (!ResetForDevice(device)) { Debug::Logger::Get().Error( @@ -107,11 +111,14 @@ bool BuiltinGaussianSplatPassResources::EnsureWorkingSet( } WorkingSet& workingSet = m_workingSets[visibleGaussianSplat.gaussianSplatRenderer]; - if (workingSet.splatCapacity < splatCapacity) { + if (workingSet.splatCapacity < splatCapacity || + workingSet.sortCapacity < sortCapacity || + workingSet.chunkCapacity < chunkCapacity) { DestroyBufferView(workingSet.sortDistances); DestroyBufferView(workingSet.orderIndices); + DestroyBufferView(workingSet.visibleChunks); DestroyBufferView(workingSet.viewData); - if (!RecreateWorkingSet(device, splatCapacity, sortCapacity, workingSet)) { + if (!RecreateWorkingSet(device, splatCapacity, sortCapacity, chunkCapacity, workingSet)) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "BuiltinGaussianSplatPassResources::EnsureWorkingSet failed: RecreateWorkingSet returned false"); @@ -160,6 +167,7 @@ void BuiltinGaussianSplatPassResources::Shutdown() { for (auto& workingSetPair : m_workingSets) { DestroyBufferView(workingSetPair.second.sortDistances); DestroyBufferView(workingSetPair.second.orderIndices); + DestroyBufferView(workingSetPair.second.visibleChunks); DestroyBufferView(workingSetPair.second.viewData); } @@ -246,6 +254,7 @@ bool BuiltinGaussianSplatPassResources::RecreateWorkingSet( RHI::RHIDevice* device, Core::uint32 splatCapacity, Core::uint32 sortCapacity, + Core::uint32 chunkCapacity, WorkingSet& workingSet) { if (!CreateStructuredBufferView(device, sortCapacity, kSortDistanceStride, workingSet.sortDistances)) { Debug::Logger::Get().Error( @@ -253,10 +262,12 @@ bool BuiltinGaussianSplatPassResources::RecreateWorkingSet( "BuiltinGaussianSplatPassResources::RecreateWorkingSet failed: sort-distance buffer view creation failed"); DestroyBufferView(workingSet.sortDistances); DestroyBufferView(workingSet.orderIndices); + DestroyBufferView(workingSet.visibleChunks); DestroyBufferView(workingSet.viewData); workingSet.renderer = nullptr; workingSet.splatCapacity = 0u; workingSet.sortCapacity = 0u; + workingSet.chunkCapacity = 0u; return false; } @@ -266,10 +277,27 @@ bool BuiltinGaussianSplatPassResources::RecreateWorkingSet( "BuiltinGaussianSplatPassResources::RecreateWorkingSet failed: order-index buffer view creation failed"); DestroyBufferView(workingSet.sortDistances); DestroyBufferView(workingSet.orderIndices); + DestroyBufferView(workingSet.visibleChunks); DestroyBufferView(workingSet.viewData); workingSet.renderer = nullptr; workingSet.splatCapacity = 0u; workingSet.sortCapacity = 0u; + workingSet.chunkCapacity = 0u; + return false; + } + + if (!CreateStructuredBufferView(device, chunkCapacity, kVisibleChunkStride, workingSet.visibleChunks)) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinGaussianSplatPassResources::RecreateWorkingSet failed: visible-chunk buffer view creation failed"); + DestroyBufferView(workingSet.sortDistances); + DestroyBufferView(workingSet.orderIndices); + DestroyBufferView(workingSet.visibleChunks); + DestroyBufferView(workingSet.viewData); + workingSet.renderer = nullptr; + workingSet.splatCapacity = 0u; + workingSet.sortCapacity = 0u; + workingSet.chunkCapacity = 0u; return false; } @@ -279,15 +307,18 @@ bool BuiltinGaussianSplatPassResources::RecreateWorkingSet( "BuiltinGaussianSplatPassResources::RecreateWorkingSet failed: view-data buffer view creation failed"); DestroyBufferView(workingSet.sortDistances); DestroyBufferView(workingSet.orderIndices); + DestroyBufferView(workingSet.visibleChunks); DestroyBufferView(workingSet.viewData); workingSet.renderer = nullptr; workingSet.splatCapacity = 0u; workingSet.sortCapacity = 0u; + workingSet.chunkCapacity = 0u; return false; } workingSet.splatCapacity = splatCapacity; workingSet.sortCapacity = sortCapacity; + workingSet.chunkCapacity = chunkCapacity; return true; } diff --git a/engine/src/Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.h b/engine/src/Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.h index aea2180f..824c6999 100644 --- a/engine/src/Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.h +++ b/engine/src/Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.h @@ -36,8 +36,10 @@ public: const Components::GaussianSplatRendererComponent* renderer = nullptr; Core::uint32 splatCapacity = 0u; Core::uint32 sortCapacity = 0u; + Core::uint32 chunkCapacity = 0u; CachedBufferView sortDistances = {}; CachedBufferView orderIndices = {}; + CachedBufferView visibleChunks = {}; CachedBufferView viewData = {}; }; @@ -73,6 +75,7 @@ public: private: static constexpr Core::uint32 kSortDistanceStride = sizeof(float); static constexpr Core::uint32 kOrderIndexStride = sizeof(Core::uint32); + static constexpr Core::uint32 kVisibleChunkStride = sizeof(Core::uint32); static constexpr Core::uint32 kViewDataStride = sizeof(GaussianSplatViewData); static void DestroyBufferView(CachedBufferView& bufferView); @@ -83,6 +86,7 @@ private: RHI::RHIDevice* device, Core::uint32 splatCapacity, Core::uint32 sortCapacity, + Core::uint32 chunkCapacity, WorkingSet& workingSet); bool CreateStructuredBufferView( RHI::RHIDevice* device, diff --git a/engine/src/Resources/Shader/Internal/ShaderRuntimeBuildUtils.cpp b/engine/src/Resources/Shader/Internal/ShaderRuntimeBuildUtils.cpp index 741331e0..eba2da3f 100644 --- a/engine/src/Resources/Shader/Internal/ShaderRuntimeBuildUtils.cpp +++ b/engine/src/Resources/Shader/Internal/ShaderRuntimeBuildUtils.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -194,6 +195,8 @@ void ImportConcretePass(Shader& shader, const ShaderPass& sourcePass) { bool IsScannedAuthoringResourceType(ShaderResourceType type) { return type == ShaderResourceType::ConstantBuffer || + type == ShaderResourceType::Texture2D || + type == ShaderResourceType::TextureCube || type == ShaderResourceType::StructuredBuffer || type == ShaderResourceType::RawBuffer || type == ShaderResourceType::RWStructuredBuffer || @@ -202,6 +205,9 @@ bool IsScannedAuthoringResourceType(ShaderResourceType type) { Core::uint32 ResolveDefaultAuthoringSet(ShaderResourceType type) { switch (type) { + case ShaderResourceType::Texture2D: + case ShaderResourceType::TextureCube: + return 4u; case ShaderResourceType::StructuredBuffer: case ShaderResourceType::RawBuffer: return 2u; @@ -247,6 +253,9 @@ bool TryParseHlslAuthoringResourceLine( 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 kTexturePattern( + R"(^\s*(Texture2D|TextureCube)\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?::\s*register\s*\([^\)]*\))?\s*;)", + std::regex::ECMAScript); static const std::regex kStructuredPattern( R"(^\s*(?:globallycoherent\s+)?(RWStructuredBuffer|StructuredBuffer)\s*<[^;\r\n>]+>\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?::\s*register\s*\([^\)]*\))?\s*;)", std::regex::ECMAScript); @@ -261,6 +270,15 @@ bool TryParseHlslAuthoringResourceLine( return true; } + if (std::regex_match(line, match, kTexturePattern) && match.size() >= 3u) { + outType = + match[1].str() == "TextureCube" + ? ShaderResourceType::TextureCube + : ShaderResourceType::Texture2D; + outName = match[2].str().c_str(); + return true; + } + if (std::regex_match(line, match, kStructuredPattern) && match.size() >= 3u) { outType = match[1].str() == "RWStructuredBuffer" @@ -282,6 +300,66 @@ bool TryParseHlslAuthoringResourceLine( return false; } +const char* TryGetAuthoringRegisterPrefix(ShaderResourceType type) { + switch (type) { + case ShaderResourceType::ConstantBuffer: + return "b"; + case ShaderResourceType::Texture2D: + case ShaderResourceType::TextureCube: + case ShaderResourceType::StructuredBuffer: + case ShaderResourceType::RawBuffer: + return "t"; + case ShaderResourceType::RWStructuredBuffer: + case ShaderResourceType::RWRawBuffer: + return "u"; + case ShaderResourceType::Sampler: + return "s"; + default: + return nullptr; + } +} + +bool TryParseHlslAuthoringRegisterBinding( + const std::string& line, + ShaderResourceType type, + Core::uint32& outSet, + Core::uint32& outBinding) { + outSet = 0u; + outBinding = 0u; + + const char* expectedPrefix = TryGetAuthoringRegisterPrefix(type); + if (expectedPrefix == nullptr) { + return false; + } + + static const std::regex kRegisterPattern( + R"(register\s*\(\s*([a-zA-Z])\s*([0-9]+)\s*(?:,\s*space\s*([0-9]+)\s*)?\))", + std::regex::ECMAScript); + + std::smatch match; + if (!std::regex_search(line, match, kRegisterPattern) || match.size() < 3u) { + return false; + } + + const std::string prefix = match[1].str(); + if (prefix.size() != 1u || + static_cast(std::tolower(static_cast(prefix[0]))) != expectedPrefix[0]) { + return false; + } + + try { + outBinding = static_cast(std::stoul(match[2].str())); + outSet = + match.size() >= 4u && match[3].matched + ? static_cast(std::stoul(match[3].str())) + : 0u; + } catch (...) { + return false; + } + + return true; +} + void AppendScannedAuthoringResourceBindings( const Containers::String& sourceText, Containers::Array& ioBindings) { @@ -309,8 +387,10 @@ void AppendScannedAuthoringResourceBindings( ShaderResourceBindingDesc binding = {}; binding.name = resourceName; binding.type = resourceType; - binding.set = ResolveDefaultAuthoringSet(resourceType); - binding.binding = FindNextBindingInSet(ioBindings, binding.set); + if (!TryParseHlslAuthoringRegisterBinding(line, resourceType, binding.set, binding.binding)) { + binding.set = ResolveDefaultAuthoringSet(resourceType); + binding.binding = FindNextBindingInSet(ioBindings, binding.set); + } ioBindings.PushBack(binding); } } diff --git a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp index 250d6bc3..31a1032c 100644 --- a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp +++ b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp @@ -1147,16 +1147,6 @@ TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatUtilitiesShaderUsesCompute ResolveBuiltinPassResourceSemantic(*sh), BuiltinPassResourceSemantic::GaussianSplatSHBuffer); - const ShaderResourceBindingDesc* chunks = - shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "GaussianSplatChunks"); - ASSERT_NE(chunks, nullptr); - EXPECT_EQ(chunks->type, ShaderResourceType::StructuredBuffer); - EXPECT_EQ(chunks->set, 2u); - EXPECT_EQ(chunks->binding, 4u); - EXPECT_EQ( - ResolveBuiltinPassResourceSemantic(*chunks), - BuiltinPassResourceSemantic::GaussianSplatChunkBuffer); - const ShaderResourceBindingDesc* sortDistances = shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "GaussianSplatSortDistances"); ASSERT_NE(sortDistances, nullptr); @@ -1167,6 +1157,16 @@ TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatUtilitiesShaderUsesCompute ResolveBuiltinPassResourceSemantic(*sortDistances), BuiltinPassResourceSemantic::GaussianSplatSortDistanceBuffer); + const ShaderResourceBindingDesc* visibleChunks = + shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "GaussianSplatVisibleChunks"); + ASSERT_NE(visibleChunks, nullptr); + EXPECT_EQ(visibleChunks->type, ShaderResourceType::StructuredBuffer); + EXPECT_EQ(visibleChunks->set, 3u); + EXPECT_EQ(visibleChunks->binding, 0u); + EXPECT_EQ( + ResolveBuiltinPassResourceSemantic(*visibleChunks), + BuiltinPassResourceSemantic::GaussianSplatVisibleChunkBuffer); + const ShaderResourceBindingDesc* orderBuffer = shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "GaussianSplatOrderBuffer"); ASSERT_NE(orderBuffer, nullptr); @@ -1197,6 +1197,59 @@ TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatUtilitiesShaderUsesCompute delete shader; } +TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatMarkVisibleChunksShaderUsesComputeAuthoringContract) { + ShaderLoader loader; + LoadResult result = loader.Load(GetBuiltinGaussianSplatUtilitiesShaderPath()); + ASSERT_TRUE(result); + ASSERT_NE(result.resource, nullptr); + + Shader* shader = static_cast(result.resource); + ASSERT_NE(shader, nullptr); + + const ShaderPass* pass = shader->FindPass("GaussianSplatMarkVisibleChunks"); + ASSERT_NE(pass, nullptr); + EXPECT_EQ(pass->resources.Size(), 3u); + + const ShaderResourceBindingDesc* perObject = + shader->FindPassResourceBinding("GaussianSplatMarkVisibleChunks", "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* chunks = + shader->FindPassResourceBinding("GaussianSplatMarkVisibleChunks", "GaussianSplatChunks"); + ASSERT_NE(chunks, nullptr); + EXPECT_EQ(chunks->type, ShaderResourceType::StructuredBuffer); + EXPECT_EQ(chunks->set, 2u); + EXPECT_EQ(chunks->binding, 0u); + EXPECT_EQ( + ResolveBuiltinPassResourceSemantic(*chunks), + BuiltinPassResourceSemantic::GaussianSplatChunkBuffer); + + const ShaderResourceBindingDesc* visibleChunks = + shader->FindPassResourceBinding("GaussianSplatMarkVisibleChunks", "GaussianSplatVisibleChunks"); + ASSERT_NE(visibleChunks, nullptr); + EXPECT_EQ(visibleChunks->type, ShaderResourceType::RWStructuredBuffer); + EXPECT_EQ(visibleChunks->set, 4u); + EXPECT_EQ(visibleChunks->binding, 0u); + EXPECT_EQ( + ResolveBuiltinPassResourceSemantic(*visibleChunks), + BuiltinPassResourceSemantic::GaussianSplatVisibleChunkBuffer); + + const ShaderStageVariant* computeVariant = shader->FindVariant( + "GaussianSplatMarkVisibleChunks", + XCEngine::Resources::ShaderType::Compute, + XCEngine::Resources::ShaderBackend::D3D12); + ASSERT_NE(computeVariant, nullptr); + EXPECT_EQ(computeVariant->entryPoint, "GaussianSplatMarkVisibleChunksCS"); + + delete shader; +} + TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatBitonicSortShaderUsesComputeAuthoringContract) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinGaussianSplatUtilitiesShaderPath()); @@ -1271,7 +1324,7 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoaded EXPECT_TRUE(plan.gaussianSplatOtherBuffer.IsValid()); EXPECT_TRUE(plan.gaussianSplatColorBuffer.IsValid()); EXPECT_TRUE(plan.gaussianSplatSHBuffer.IsValid()); - EXPECT_TRUE(plan.gaussianSplatChunkBuffer.IsValid()); + EXPECT_TRUE(plan.gaussianSplatVisibleChunkBuffer.IsValid()); EXPECT_TRUE(plan.gaussianSplatSortDistanceBuffer.IsValid()); EXPECT_TRUE(plan.gaussianSplatOrderBuffer.IsValid()); EXPECT_TRUE(plan.gaussianSplatViewDataBuffer.IsValid()); @@ -1283,8 +1336,8 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoaded EXPECT_EQ(plan.gaussianSplatColorBuffer.binding, 2u); EXPECT_EQ(plan.gaussianSplatSHBuffer.set, 2u); EXPECT_EQ(plan.gaussianSplatSHBuffer.binding, 3u); - EXPECT_EQ(plan.gaussianSplatChunkBuffer.set, 2u); - EXPECT_EQ(plan.gaussianSplatChunkBuffer.binding, 4u); + EXPECT_EQ(plan.gaussianSplatVisibleChunkBuffer.set, 3u); + EXPECT_EQ(plan.gaussianSplatVisibleChunkBuffer.binding, 0u); EXPECT_EQ(plan.gaussianSplatSortDistanceBuffer.set, 4u); EXPECT_EQ(plan.gaussianSplatSortDistanceBuffer.binding, 0u); EXPECT_EQ(plan.gaussianSplatOrderBuffer.set, 4u); @@ -1302,11 +1355,12 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoaded EXPECT_TRUE(setLayouts[2].usesGaussianSplatOtherBuffer); EXPECT_TRUE(setLayouts[2].usesGaussianSplatColorBuffer); EXPECT_TRUE(setLayouts[2].usesGaussianSplatSHBuffer); - EXPECT_TRUE(setLayouts[2].usesGaussianSplatChunkBuffer); + EXPECT_TRUE(setLayouts[3].usesGaussianSplatVisibleChunkBuffer); EXPECT_TRUE(setLayouts[4].usesGaussianSplatSortDistanceBuffer); EXPECT_TRUE(setLayouts[4].usesGaussianSplatOrderBuffer); EXPECT_TRUE(setLayouts[4].usesGaussianSplatViewDataBuffer); - ASSERT_EQ(setLayouts[2].bindings.size(), 5u); + ASSERT_EQ(setLayouts[2].bindings.size(), 4u); + ASSERT_EQ(setLayouts[3].bindings.size(), 1u); ASSERT_EQ(setLayouts[4].bindings.size(), 3u); EXPECT_EQ( static_cast(setLayouts[2].bindings[0].type), @@ -1320,9 +1374,6 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoaded EXPECT_EQ( static_cast(setLayouts[2].bindings[3].type), DescriptorType::SRV); - EXPECT_EQ( - static_cast(setLayouts[2].bindings[4].type), - DescriptorType::SRV); EXPECT_EQ( static_cast(setLayouts[4].bindings[0].type), DescriptorType::UAV); @@ -1345,7 +1396,7 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoaded setLayouts[2].bindings[3].resourceDimension, ResourceViewDimension::StructuredBuffer); EXPECT_EQ( - setLayouts[2].bindings[4].resourceDimension, + setLayouts[3].bindings[0].resourceDimension, ResourceViewDimension::StructuredBuffer); EXPECT_EQ( setLayouts[4].bindings[0].resourceDimension, @@ -1360,6 +1411,51 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoaded delete shader; } +TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoadedGaussianSplatMarkVisibleChunksContract) { + ShaderLoader loader; + LoadResult result = loader.Load(GetBuiltinGaussianSplatUtilitiesShaderPath()); + ASSERT_TRUE(result); + ASSERT_NE(result.resource, nullptr); + + Shader* shader = static_cast(result.resource); + ASSERT_NE(shader, nullptr); + + const ShaderPass* pass = shader->FindPass("GaussianSplatMarkVisibleChunks"); + ASSERT_NE(pass, nullptr); + + BuiltinPassResourceBindingPlan plan = {}; + String error; + EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr(); + ASSERT_EQ(plan.bindings.Size(), 3u); + EXPECT_TRUE(plan.perObject.IsValid()); + EXPECT_TRUE(plan.gaussianSplatChunkBuffer.IsValid()); + EXPECT_TRUE(plan.gaussianSplatVisibleChunkBuffer.IsValid()); + EXPECT_EQ(plan.perObject.set, 0u); + EXPECT_EQ(plan.gaussianSplatChunkBuffer.set, 2u); + EXPECT_EQ(plan.gaussianSplatChunkBuffer.binding, 0u); + EXPECT_EQ(plan.gaussianSplatVisibleChunkBuffer.set, 4u); + EXPECT_EQ(plan.gaussianSplatVisibleChunkBuffer.binding, 0u); + EXPECT_EQ(plan.firstDescriptorSet, 0u); + EXPECT_EQ(plan.descriptorSetCount, 5u); + + std::vector setLayouts; + ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr(); + ASSERT_EQ(setLayouts.size(), 5u); + EXPECT_TRUE(setLayouts[0].usesPerObject); + EXPECT_TRUE(setLayouts[2].usesGaussianSplatChunkBuffer); + EXPECT_TRUE(setLayouts[4].usesGaussianSplatVisibleChunkBuffer); + ASSERT_EQ(setLayouts[2].bindings.size(), 1u); + ASSERT_EQ(setLayouts[4].bindings.size(), 1u); + EXPECT_EQ( + static_cast(setLayouts[2].bindings[0].type), + DescriptorType::SRV); + EXPECT_EQ( + static_cast(setLayouts[4].bindings[0].type), + DescriptorType::UAV); + + delete shader; +} + TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoadedGaussianSplatBitonicSortContract) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinGaussianSplatUtilitiesShaderPath()); @@ -1444,7 +1540,8 @@ TEST(BuiltinForwardPipeline_Test, OpenGLPipelineLayoutUsesUnifiedStorageBufferBi EXPECT_EQ(pipelineLayout.GetShaderResourceBindingPoint(2u, 1u), 1u); EXPECT_EQ(pipelineLayout.GetShaderResourceBindingPoint(2u, 2u), 2u); EXPECT_EQ(pipelineLayout.GetShaderResourceBindingPoint(2u, 3u), 3u); - EXPECT_EQ(pipelineLayout.GetShaderResourceBindingPoint(2u, 4u), 4u); + EXPECT_EQ(pipelineLayout.GetShaderResourceBindingPoint(2u, 4u), UINT32_MAX); + EXPECT_EQ(pipelineLayout.GetShaderResourceBindingPoint(3u, 0u), 4u); EXPECT_EQ(pipelineLayout.GetUnorderedAccessBindingPoint(4u, 0u), 5u); EXPECT_EQ(pipelineLayout.GetUnorderedAccessBindingPoint(4u, 1u), 6u); EXPECT_EQ(pipelineLayout.GetUnorderedAccessBindingPoint(4u, 2u), 7u); @@ -1502,7 +1599,7 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesGaussianSplatU "register(t3)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, - "StructuredBuffer GaussianSplatChunks", + "StructuredBuffer GaussianSplatVisibleChunks", "register(t4)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, @@ -1554,8 +1651,8 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesGaussianSplatU "register(t3, space2)")); EXPECT_TRUE(SourceContainsRegisterBinding( vulkanSource, - "StructuredBuffer GaussianSplatChunks", - "register(t4, space2)")); + "StructuredBuffer GaussianSplatVisibleChunks", + "register(t0, space3)")); EXPECT_TRUE(SourceContainsRegisterBinding( vulkanSource, "RWStructuredBuffer GaussianSplatSortDistances", @@ -1625,7 +1722,7 @@ TEST(BuiltinForwardPipeline_Test, OpenGLRuntimeTranspilesGaussianSplatUtilitiesC glslSource.find("layout(binding = 3, std430) readonly buffer type_StructuredBuffer_GaussianSplatSHData"), std::string::npos); EXPECT_NE( - glslSource.find("layout(binding = 4, std430) readonly buffer type_StructuredBuffer_GaussianSplatChunkData"), + glslSource.find("layout(binding = 4, std430) readonly buffer"), std::string::npos); EXPECT_NE( glslSource.find("layout(binding = 5, std430) buffer type_RWStructuredBuffer_uint"), diff --git a/tests/Rendering/unit/test_builtin_gaussian_splat_pass_resources.cpp b/tests/Rendering/unit/test_builtin_gaussian_splat_pass_resources.cpp index 9de1183d..b6163865 100644 --- a/tests/Rendering/unit/test_builtin_gaussian_splat_pass_resources.cpp +++ b/tests/Rendering/unit/test_builtin_gaussian_splat_pass_resources.cpp @@ -284,6 +284,7 @@ GaussianSplat* CreateTestGaussianSplat(const char* path, XCEngine::Core::uint32 GaussianSplatMetadata metadata = {}; metadata.splatCount = splatCount; + metadata.chunkCount = (splatCount + 255u) / 256u; XCEngine::Containers::Array sections; sections.Resize(1); @@ -336,21 +337,24 @@ TEST(BuiltinGaussianSplatPassResources_Test, EnsureWorkingSetAllocatesAndReusesS EXPECT_EQ(workingSet->renderer, renderer); EXPECT_EQ(workingSet->splatCapacity, 8u); EXPECT_EQ(workingSet->sortCapacity, 8u); + EXPECT_EQ(workingSet->chunkCapacity, 1u); EXPECT_EQ(workingSet->sortDistances.elementStride, sizeof(float)); EXPECT_EQ(workingSet->orderIndices.elementStride, sizeof(XCEngine::Core::uint32)); + EXPECT_EQ(workingSet->visibleChunks.elementStride, sizeof(XCEngine::Core::uint32)); EXPECT_EQ(workingSet->viewData.elementStride, sizeof(GaussianSplatViewData)); EXPECT_EQ(workingSet->sortDistances.shaderResourceView->GetDimension(), XCEngine::RHI::ResourceViewDimension::StructuredBuffer); EXPECT_EQ(workingSet->sortDistances.unorderedAccessView->GetViewType(), XCEngine::RHI::ResourceViewType::UnorderedAccess); - EXPECT_EQ(state->createBufferCalls, 3); - EXPECT_EQ(state->createBufferShaderViewCalls, 3); - EXPECT_EQ(state->createBufferUavCalls, 3); + EXPECT_EQ(workingSet->visibleChunks.elementCount, 1u); + EXPECT_EQ(state->createBufferCalls, 4); + EXPECT_EQ(state->createBufferShaderViewCalls, 4); + EXPECT_EQ(state->createBufferUavCalls, 4); BuiltinGaussianSplatPassResources::WorkingSet* reusedWorkingSet = nullptr; ASSERT_TRUE(resources.EnsureWorkingSet(&device, item, reusedWorkingSet)); EXPECT_EQ(reusedWorkingSet, workingSet); - EXPECT_EQ(state->createBufferCalls, 3); - EXPECT_EQ(state->createBufferShaderViewCalls, 3); - EXPECT_EQ(state->createBufferUavCalls, 3); + EXPECT_EQ(state->createBufferCalls, 4); + EXPECT_EQ(state->createBufferShaderViewCalls, 4); + EXPECT_EQ(state->createBufferUavCalls, 4); } TEST(BuiltinGaussianSplatPassResources_Test, EnsureWorkingSetKeepsPerRendererIsolationAndRecreatesOnCapacityGrowth) { @@ -376,7 +380,7 @@ TEST(BuiltinGaussianSplatPassResources_Test, EnsureWorkingSetKeepsPerRendererIso ASSERT_NE(secondWorkingSet, nullptr); EXPECT_NE(firstWorkingSet, secondWorkingSet); EXPECT_EQ(resources.GetWorkingSetCount(), 2u); - EXPECT_EQ(state->createBufferCalls, 6); + EXPECT_EQ(state->createBufferCalls, 8); std::unique_ptr grownGaussianSplat(CreateTestGaussianSplat("GaussianSplats/first_grown.xcgsplat", 12u)); firstItem.gaussianSplat = grownGaussianSplat.get(); @@ -387,10 +391,11 @@ TEST(BuiltinGaussianSplatPassResources_Test, EnsureWorkingSetKeepsPerRendererIso EXPECT_EQ(grownWorkingSet, resources.FindWorkingSet(firstRenderer)); EXPECT_EQ(grownWorkingSet->splatCapacity, 12u); EXPECT_EQ(grownWorkingSet->sortCapacity, 16u); + EXPECT_EQ(grownWorkingSet->chunkCapacity, 1u); EXPECT_EQ(resources.GetWorkingSetCount(), 2u); - EXPECT_EQ(state->createBufferCalls, 9); - EXPECT_GE(state->bufferShutdownCalls, 3); - EXPECT_GE(state->bufferDestroyCalls, 3); + EXPECT_EQ(state->createBufferCalls, 12); + EXPECT_GE(state->bufferShutdownCalls, 4); + EXPECT_GE(state->bufferDestroyCalls, 4); } TEST(BuiltinGaussianSplatPassResources_Test, EnsureWorkingSetRoundsSortBuffersUpToNextPowerOfTwo) { @@ -408,8 +413,10 @@ TEST(BuiltinGaussianSplatPassResources_Test, EnsureWorkingSetRoundsSortBuffersUp ASSERT_NE(workingSet, nullptr); EXPECT_EQ(workingSet->splatCapacity, 9u); EXPECT_EQ(workingSet->sortCapacity, 16u); + EXPECT_EQ(workingSet->chunkCapacity, 1u); EXPECT_EQ(workingSet->sortDistances.elementCount, 16u); EXPECT_EQ(workingSet->orderIndices.elementCount, 16u); + EXPECT_EQ(workingSet->visibleChunks.elementCount, 1u); EXPECT_EQ(workingSet->viewData.elementCount, 9u); }