Precompute gaussian splat chunk visibility

This commit is contained in:
2026-04-11 16:32:40 +08:00
parent 0a2bdedc59
commit c03c7379c8
11 changed files with 583 additions and 62 deletions

View File

@@ -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<float>(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<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),
static_cast<float>(workingSet->sortCapacity),
shOrder,
static_cast<float>(cachedGaussianSplat->chunkCount))
};
if (passLayout->descriptorSetCount > 0u) {
std::vector<RHI::RHIDescriptorSet*> 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");