2026-04-10 23:11:11 +08:00
|
|
|
#include "Rendering/Passes/BuiltinGaussianSplatPass.h"
|
|
|
|
|
|
|
|
|
|
#include "Components/GameObject.h"
|
|
|
|
|
#include "Debug/Logger.h"
|
|
|
|
|
#include "RHI/RHICommandList.h"
|
|
|
|
|
#include "RHI/RHIDevice.h"
|
|
|
|
|
#include "Rendering/Builtin/BuiltinPassLayoutUtils.h"
|
|
|
|
|
#include "Rendering/FrameData/RenderSceneData.h"
|
|
|
|
|
#include "Rendering/FrameData/VisibleGaussianSplatItem.h"
|
|
|
|
|
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
|
|
|
|
|
#include "Rendering/Internal/ShaderVariantUtils.h"
|
2026-04-11 03:02:30 +08:00
|
|
|
#include "Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.h"
|
2026-04-10 23:11:11 +08:00
|
|
|
#include "Rendering/RenderSurface.h"
|
|
|
|
|
#include "Resources/BuiltinResources.h"
|
|
|
|
|
#include "Resources/GaussianSplat/GaussianSplat.h"
|
|
|
|
|
#include "Resources/Material/Material.h"
|
|
|
|
|
#include "Resources/Shader/Shader.h"
|
|
|
|
|
|
|
|
|
|
namespace XCEngine {
|
|
|
|
|
namespace Rendering {
|
|
|
|
|
namespace Passes {
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
Resources::ShaderKeywordSet ResolvePassKeywordSet(
|
|
|
|
|
const RenderSceneData& sceneData,
|
|
|
|
|
const Resources::Material* material) {
|
|
|
|
|
return Resources::CombineShaderKeywordSets(
|
|
|
|
|
sceneData.globalShaderKeywords,
|
|
|
|
|
material != nullptr ? material->GetKeywordSet() : Resources::ShaderKeywordSet());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Resources::ShaderPass* FindCompatibleGaussianSplatPass(
|
|
|
|
|
const Resources::Shader& shader,
|
|
|
|
|
const RenderSceneData& sceneData,
|
|
|
|
|
const Resources::Material* material,
|
|
|
|
|
Resources::ShaderBackend backend) {
|
|
|
|
|
const Resources::ShaderKeywordSet keywordSet = ResolvePassKeywordSet(sceneData, material);
|
|
|
|
|
for (const Resources::ShaderPass& shaderPass : shader.GetPasses()) {
|
|
|
|
|
if (ShaderPassMatchesBuiltinPass(shaderPass, BuiltinMaterialPass::GaussianSplat) &&
|
|
|
|
|
::XCEngine::Rendering::Internal::ShaderPassHasGraphicsVariants(
|
|
|
|
|
shader,
|
|
|
|
|
shaderPass.name,
|
|
|
|
|
backend,
|
|
|
|
|
keywordSet)) {
|
|
|
|
|
return &shaderPass;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 03:02:30 +08:00
|
|
|
const Resources::ShaderPass* FindCompatibleComputePass(
|
|
|
|
|
const Resources::Shader& shader,
|
|
|
|
|
const Containers::String& passName,
|
|
|
|
|
const Resources::ShaderKeywordSet& keywordSet,
|
|
|
|
|
Resources::ShaderBackend backend) {
|
|
|
|
|
const Resources::ShaderPass* shaderPass = shader.FindPass(passName);
|
|
|
|
|
if (shaderPass == nullptr) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return shader.FindVariant(passName, Resources::ShaderType::Compute, backend, keywordSet) != nullptr
|
|
|
|
|
? shaderPass
|
|
|
|
|
: nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 23:11:11 +08:00
|
|
|
RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
|
|
|
|
RHI::RHIType backendType,
|
|
|
|
|
RHI::RHIPipelineLayout* pipelineLayout,
|
|
|
|
|
const Resources::Shader& shader,
|
|
|
|
|
const Resources::ShaderPass& shaderPass,
|
|
|
|
|
const Containers::String& passName,
|
|
|
|
|
const Resources::ShaderKeywordSet& keywordSet,
|
|
|
|
|
const Resources::Material* material,
|
|
|
|
|
const RenderSurface& surface) {
|
|
|
|
|
RHI::GraphicsPipelineDesc pipelineDesc = {};
|
|
|
|
|
pipelineDesc.pipelineLayout = pipelineLayout;
|
|
|
|
|
pipelineDesc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
|
|
|
|
|
::XCEngine::Rendering::Internal::ApplySingleColorAttachmentPropertiesToGraphicsPipelineDesc(surface, pipelineDesc);
|
|
|
|
|
pipelineDesc.depthStencilFormat =
|
|
|
|
|
static_cast<uint32_t>(::XCEngine::Rendering::Internal::ResolveSurfaceDepthFormat(surface));
|
|
|
|
|
ApplyResolvedRenderState(&shaderPass, material, pipelineDesc);
|
|
|
|
|
|
|
|
|
|
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(backendType);
|
|
|
|
|
if (const Resources::ShaderStageVariant* vertexVariant =
|
|
|
|
|
shader.FindVariant(passName, Resources::ShaderType::Vertex, backend, keywordSet)) {
|
|
|
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
|
|
|
|
shader.GetPath(),
|
|
|
|
|
shaderPass,
|
|
|
|
|
backend,
|
|
|
|
|
*vertexVariant,
|
|
|
|
|
pipelineDesc.vertexShader);
|
|
|
|
|
}
|
|
|
|
|
if (const Resources::ShaderStageVariant* fragmentVariant =
|
|
|
|
|
shader.FindVariant(passName, Resources::ShaderType::Fragment, backend, keywordSet)) {
|
|
|
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
|
|
|
|
shader.GetPath(),
|
|
|
|
|
shaderPass,
|
|
|
|
|
backend,
|
|
|
|
|
*fragmentVariant,
|
|
|
|
|
pipelineDesc.fragmentShader);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pipelineDesc;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 03:02:30 +08:00
|
|
|
RHI::ComputePipelineDesc CreateComputePipelineDesc(
|
|
|
|
|
RHI::RHIType backendType,
|
|
|
|
|
RHI::RHIPipelineLayout* pipelineLayout,
|
|
|
|
|
const Resources::Shader& shader,
|
|
|
|
|
const Resources::ShaderPass& shaderPass,
|
|
|
|
|
const Containers::String& passName,
|
|
|
|
|
const Resources::ShaderKeywordSet& keywordSet) {
|
|
|
|
|
RHI::ComputePipelineDesc pipelineDesc = {};
|
|
|
|
|
pipelineDesc.pipelineLayout = pipelineLayout;
|
|
|
|
|
|
|
|
|
|
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(backendType);
|
|
|
|
|
if (const Resources::ShaderStageVariant* computeVariant =
|
|
|
|
|
shader.FindVariant(passName, Resources::ShaderType::Compute, backend, keywordSet)) {
|
|
|
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
|
|
|
|
shader.GetPath(),
|
|
|
|
|
shaderPass,
|
|
|
|
|
backend,
|
|
|
|
|
*computeVariant,
|
|
|
|
|
pipelineDesc.computeShader);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pipelineDesc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const RHI::DescriptorSetLayoutBinding* FindSetLayoutBinding(
|
|
|
|
|
const BuiltinPassSetLayoutMetadata& setLayout,
|
|
|
|
|
Core::uint32 binding) {
|
|
|
|
|
for (const RHI::DescriptorSetLayoutBinding& layoutBinding : setLayout.bindings) {
|
|
|
|
|
if (layoutBinding.binding == binding) {
|
|
|
|
|
return &layoutBinding;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RHI::RHIResourceView* ResolveWorkingSetView(
|
|
|
|
|
const Internal::BuiltinGaussianSplatPassResources::CachedBufferView& bufferView,
|
|
|
|
|
const RHI::DescriptorSetLayoutBinding* layoutBinding) {
|
|
|
|
|
if (layoutBinding == nullptr) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (static_cast<RHI::DescriptorType>(layoutBinding->type)) {
|
|
|
|
|
case RHI::DescriptorType::UAV:
|
|
|
|
|
return bufferView.unorderedAccessView;
|
|
|
|
|
case RHI::DescriptorType::SRV:
|
|
|
|
|
return bufferView.shaderResourceView;
|
|
|
|
|
default:
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 06:09:53 +08:00
|
|
|
bool TransitionWorkingSetBuffer(
|
|
|
|
|
RHI::RHICommandList* commandList,
|
|
|
|
|
Internal::BuiltinGaussianSplatPassResources::CachedBufferView& bufferView,
|
|
|
|
|
RHI::ResourceStates targetState) {
|
|
|
|
|
if (commandList == nullptr || bufferView.currentState == targetState) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RHI::RHIResourceView* resourceView =
|
|
|
|
|
targetState == RHI::ResourceStates::UnorderedAccess
|
|
|
|
|
? bufferView.unorderedAccessView
|
|
|
|
|
: bufferView.shaderResourceView;
|
|
|
|
|
if (resourceView == nullptr) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
commandList->TransitionBarrier(resourceView, bufferView.currentState, targetState);
|
|
|
|
|
bufferView.currentState = targetState;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 03:02:30 +08:00
|
|
|
template <typename BindFn>
|
|
|
|
|
void BindDescriptorSetRanges(
|
|
|
|
|
Core::uint32 firstDescriptorSet,
|
|
|
|
|
std::vector<RHI::RHIDescriptorSet*>& descriptorSets,
|
|
|
|
|
BindFn&& bindFn) {
|
|
|
|
|
const Core::uint32 descriptorSetCount = static_cast<Core::uint32>(descriptorSets.size());
|
|
|
|
|
Core::uint32 rangeStart = 0u;
|
|
|
|
|
while (rangeStart < descriptorSetCount) {
|
|
|
|
|
while (rangeStart < descriptorSetCount && descriptorSets[rangeStart] == nullptr) {
|
|
|
|
|
++rangeStart;
|
|
|
|
|
}
|
|
|
|
|
if (rangeStart >= descriptorSetCount) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Core::uint32 rangeCount = 0u;
|
|
|
|
|
while ((rangeStart + rangeCount) < descriptorSetCount &&
|
|
|
|
|
descriptorSets[rangeStart + rangeCount] != nullptr) {
|
|
|
|
|
++rangeCount;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bindFn(
|
|
|
|
|
firstDescriptorSet + rangeStart,
|
|
|
|
|
rangeCount,
|
|
|
|
|
descriptorSets.data() + rangeStart);
|
|
|
|
|
rangeStart += rangeCount;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 23:11:11 +08:00
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
BuiltinGaussianSplatPass::~BuiltinGaussianSplatPass() {
|
|
|
|
|
Shutdown();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char* BuiltinGaussianSplatPass::GetName() const {
|
|
|
|
|
return "BuiltinGaussianSplatPass";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BuiltinGaussianSplatPass::Initialize(const RenderContext& context) {
|
|
|
|
|
return EnsureInitialized(context);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BuiltinGaussianSplatPass::PrepareGaussianSplatResources(
|
|
|
|
|
const RenderContext& context,
|
|
|
|
|
const RenderSceneData& sceneData) {
|
|
|
|
|
if (!EnsureInitialized(context)) {
|
2026-04-11 05:37:31 +08:00
|
|
|
Debug::Logger::Get().Error(
|
|
|
|
|
Debug::LogCategory::Rendering,
|
|
|
|
|
"BuiltinGaussianSplatPass::PrepareGaussianSplatResources failed: EnsureInitialized returned false");
|
2026-04-10 23:11:11 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const VisibleGaussianSplatItem& visibleGaussianSplat : sceneData.visibleGaussianSplats) {
|
|
|
|
|
if (visibleGaussianSplat.gaussianSplat == nullptr ||
|
|
|
|
|
!visibleGaussianSplat.gaussianSplat->IsValid()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const RenderResourceCache::CachedGaussianSplat* cachedGaussianSplat =
|
|
|
|
|
m_resourceCache.GetOrCreateGaussianSplat(m_device, visibleGaussianSplat.gaussianSplat);
|
|
|
|
|
if (cachedGaussianSplat == nullptr ||
|
|
|
|
|
cachedGaussianSplat->positions.shaderResourceView == nullptr ||
|
|
|
|
|
cachedGaussianSplat->other.shaderResourceView == nullptr ||
|
2026-04-11 06:32:38 +08:00
|
|
|
cachedGaussianSplat->color.shaderResourceView == nullptr ||
|
|
|
|
|
cachedGaussianSplat->sh.shaderResourceView == nullptr) {
|
2026-04-11 05:37:31 +08:00
|
|
|
Debug::Logger::Get().Error(
|
|
|
|
|
Debug::LogCategory::Rendering,
|
|
|
|
|
"BuiltinGaussianSplatPass::PrepareGaussianSplatResources failed: gaussian splat GPU cache incomplete");
|
2026-04-10 23:11:11 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
2026-04-11 03:02:30 +08:00
|
|
|
|
|
|
|
|
if (m_passResources == nullptr) {
|
2026-04-11 05:37:31 +08:00
|
|
|
Debug::Logger::Get().Error(
|
|
|
|
|
Debug::LogCategory::Rendering,
|
|
|
|
|
"BuiltinGaussianSplatPass::PrepareGaussianSplatResources failed: pass resources missing");
|
2026-04-11 03:02:30 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Internal::BuiltinGaussianSplatPassResources::WorkingSet* workingSet = nullptr;
|
|
|
|
|
if (!m_passResources->EnsureWorkingSet(m_device, visibleGaussianSplat, workingSet) ||
|
|
|
|
|
workingSet == nullptr ||
|
|
|
|
|
workingSet->sortDistances.unorderedAccessView == nullptr ||
|
|
|
|
|
workingSet->orderIndices.shaderResourceView == nullptr ||
|
2026-04-11 05:37:31 +08:00
|
|
|
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");
|
2026-04-11 03:02:30 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
2026-04-10 23:11:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BuiltinGaussianSplatPass::Execute(const RenderPassContext& context) {
|
|
|
|
|
if (!context.renderContext.IsValid()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (context.sceneData.visibleGaussianSplats.empty()) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::vector<RHI::RHIResourceView*>& colorAttachments = context.surface.GetColorAttachments();
|
|
|
|
|
if (!::XCEngine::Rendering::Internal::HasSingleColorAttachment(context.surface) ||
|
|
|
|
|
colorAttachments.empty() ||
|
|
|
|
|
colorAttachments[0] == nullptr) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Math::RectInt renderArea = context.surface.GetRenderArea();
|
|
|
|
|
if (renderArea.width <= 0 || renderArea.height <= 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!PrepareGaussianSplatResources(context.renderContext, context.sceneData)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RHI::RHICommandList* commandList = context.renderContext.commandList;
|
|
|
|
|
RHI::RHIResourceView* renderTarget = colorAttachments[0];
|
|
|
|
|
commandList->SetRenderTargets(1, &renderTarget, context.surface.GetDepthAttachment());
|
|
|
|
|
|
|
|
|
|
const RHI::Viewport viewport = {
|
|
|
|
|
static_cast<float>(renderArea.x),
|
|
|
|
|
static_cast<float>(renderArea.y),
|
|
|
|
|
static_cast<float>(renderArea.width),
|
|
|
|
|
static_cast<float>(renderArea.height),
|
|
|
|
|
0.0f,
|
|
|
|
|
1.0f
|
|
|
|
|
};
|
|
|
|
|
const RHI::Rect scissorRect = {
|
|
|
|
|
renderArea.x,
|
|
|
|
|
renderArea.y,
|
|
|
|
|
renderArea.x + renderArea.width,
|
|
|
|
|
renderArea.y + renderArea.height
|
|
|
|
|
};
|
|
|
|
|
commandList->SetViewport(viewport);
|
|
|
|
|
commandList->SetScissorRect(scissorRect);
|
|
|
|
|
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
|
|
|
|
|
|
|
|
|
|
for (const VisibleGaussianSplatItem& visibleGaussianSplat : context.sceneData.visibleGaussianSplats) {
|
2026-04-11 03:02:30 +08:00
|
|
|
if (!PrepareVisibleGaussianSplat(
|
|
|
|
|
context.renderContext,
|
|
|
|
|
context.sceneData,
|
|
|
|
|
visibleGaussianSplat)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 06:09:53 +08:00
|
|
|
if (!SortVisibleGaussianSplat(
|
|
|
|
|
context.renderContext,
|
|
|
|
|
context.sceneData,
|
|
|
|
|
visibleGaussianSplat)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 23:11:11 +08:00
|
|
|
if (!DrawVisibleGaussianSplat(
|
|
|
|
|
context.renderContext,
|
|
|
|
|
context.surface,
|
|
|
|
|
context.sceneData,
|
|
|
|
|
visibleGaussianSplat)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BuiltinGaussianSplatPass::Shutdown() {
|
|
|
|
|
DestroyResources();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BuiltinGaussianSplatPass::EnsureInitialized(const RenderContext& context) {
|
|
|
|
|
if (!context.IsValid()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_device == context.device &&
|
|
|
|
|
m_backendType == context.backendType &&
|
|
|
|
|
m_builtinGaussianSplatShader.IsValid() &&
|
|
|
|
|
m_builtinGaussianSplatMaterial != nullptr) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return CreateResources(context);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BuiltinGaussianSplatPass::CreateResources(const RenderContext& context) {
|
|
|
|
|
m_device = context.device;
|
|
|
|
|
m_backendType = context.backendType;
|
|
|
|
|
m_builtinGaussianSplatShader = Resources::ResourceManager::Get().Load<Resources::Shader>(
|
|
|
|
|
Resources::GetBuiltinGaussianSplatShaderPath());
|
|
|
|
|
if (!m_builtinGaussianSplatShader.IsValid()) {
|
|
|
|
|
Debug::Logger::Get().Error(
|
|
|
|
|
Debug::LogCategory::Rendering,
|
|
|
|
|
"BuiltinGaussianSplatPass failed to load builtin gaussian splat shader resource");
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 03:02:30 +08:00
|
|
|
m_builtinGaussianSplatUtilitiesShader = Resources::ResourceManager::Get().Load<Resources::Shader>(
|
|
|
|
|
Resources::GetBuiltinGaussianSplatUtilitiesShaderPath());
|
|
|
|
|
if (!m_builtinGaussianSplatUtilitiesShader.IsValid()) {
|
|
|
|
|
Debug::Logger::Get().Error(
|
|
|
|
|
Debug::LogCategory::Rendering,
|
|
|
|
|
"BuiltinGaussianSplatPass failed to load builtin gaussian splat utilities shader resource");
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 23:11:11 +08:00
|
|
|
m_builtinGaussianSplatMaterial = std::make_unique<Resources::Material>();
|
|
|
|
|
Resources::IResource::ConstructParams params = {};
|
|
|
|
|
params.name = "BuiltinGaussianSplatMaterial";
|
|
|
|
|
params.path = "builtin://materials/gaussian-splat-default";
|
|
|
|
|
params.guid = Resources::ResourceGUID::Generate(params.path);
|
|
|
|
|
m_builtinGaussianSplatMaterial->Initialize(params);
|
|
|
|
|
m_builtinGaussianSplatMaterial->SetShader(m_builtinGaussianSplatShader);
|
|
|
|
|
m_builtinGaussianSplatMaterial->SetRenderQueue(Resources::MaterialRenderQueue::Transparent);
|
2026-04-11 03:02:30 +08:00
|
|
|
m_passResources = new Internal::BuiltinGaussianSplatPassResources();
|
2026-04-10 23:11:11 +08:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BuiltinGaussianSplatPass::DestroyResources() {
|
2026-04-11 03:02:30 +08:00
|
|
|
if (m_passResources != nullptr) {
|
|
|
|
|
m_passResources->Shutdown();
|
|
|
|
|
delete m_passResources;
|
|
|
|
|
m_passResources = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 23:11:11 +08:00
|
|
|
m_resourceCache.Shutdown();
|
|
|
|
|
|
|
|
|
|
for (auto& pipelinePair : m_pipelineStates) {
|
|
|
|
|
if (pipelinePair.second != nullptr) {
|
|
|
|
|
pipelinePair.second->Shutdown();
|
|
|
|
|
delete pipelinePair.second;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
m_pipelineStates.clear();
|
|
|
|
|
|
2026-04-11 03:02:30 +08:00
|
|
|
for (auto& pipelinePair : m_computePipelineStates) {
|
|
|
|
|
if (pipelinePair.second != nullptr) {
|
|
|
|
|
pipelinePair.second->Shutdown();
|
|
|
|
|
delete pipelinePair.second;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
m_computePipelineStates.clear();
|
|
|
|
|
|
2026-04-10 23:11:11 +08:00
|
|
|
for (auto& descriptorSetPair : m_dynamicDescriptorSets) {
|
|
|
|
|
DestroyOwnedDescriptorSet(descriptorSetPair.second.descriptorSet);
|
|
|
|
|
}
|
|
|
|
|
m_dynamicDescriptorSets.clear();
|
|
|
|
|
|
|
|
|
|
for (auto& passLayoutPair : m_passResourceLayouts) {
|
|
|
|
|
DestroyPassResourceLayout(passLayoutPair.second);
|
|
|
|
|
}
|
|
|
|
|
m_passResourceLayouts.clear();
|
|
|
|
|
|
|
|
|
|
m_builtinGaussianSplatMaterial.reset();
|
2026-04-11 03:02:30 +08:00
|
|
|
m_builtinGaussianSplatUtilitiesShader.Reset();
|
2026-04-10 23:11:11 +08:00
|
|
|
m_builtinGaussianSplatShader.Reset();
|
|
|
|
|
m_device = nullptr;
|
|
|
|
|
m_backendType = RHI::RHIType::D3D12;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Resources::Material* BuiltinGaussianSplatPass::ResolveGaussianSplatMaterial(
|
|
|
|
|
const VisibleGaussianSplatItem& visibleGaussianSplat) const {
|
|
|
|
|
if (visibleGaussianSplat.material != nullptr &&
|
|
|
|
|
visibleGaussianSplat.material->GetShader() != nullptr &&
|
|
|
|
|
MatchesBuiltinPass(visibleGaussianSplat.material, BuiltinMaterialPass::GaussianSplat)) {
|
|
|
|
|
return visibleGaussianSplat.material;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return m_builtinGaussianSplatMaterial.get();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BuiltinGaussianSplatPass::ResolvedShaderPass BuiltinGaussianSplatPass::ResolveGaussianSplatShaderPass(
|
|
|
|
|
const RenderSceneData& sceneData,
|
|
|
|
|
const Resources::Material* material) const {
|
|
|
|
|
ResolvedShaderPass resolved = {};
|
|
|
|
|
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(m_backendType);
|
|
|
|
|
|
|
|
|
|
if (material != nullptr && material->GetShader() != nullptr) {
|
|
|
|
|
const Resources::Shader* shader = material->GetShader();
|
|
|
|
|
if (const Resources::ShaderPass* shaderPass =
|
|
|
|
|
FindCompatibleGaussianSplatPass(*shader, sceneData, material, backend)) {
|
|
|
|
|
resolved.shader = shader;
|
|
|
|
|
resolved.pass = shaderPass;
|
|
|
|
|
resolved.passName = shaderPass->name;
|
|
|
|
|
return resolved;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_builtinGaussianSplatShader.IsValid()) {
|
|
|
|
|
const Resources::Shader* shader = m_builtinGaussianSplatShader.Get();
|
|
|
|
|
if (const Resources::ShaderPass* shaderPass =
|
|
|
|
|
FindCompatibleGaussianSplatPass(*shader, sceneData, material, backend)) {
|
|
|
|
|
resolved.shader = shader;
|
|
|
|
|
resolved.pass = shaderPass;
|
|
|
|
|
resolved.passName = shaderPass->name;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return resolved;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 03:02:30 +08:00
|
|
|
BuiltinGaussianSplatPass::ResolvedShaderPass BuiltinGaussianSplatPass::ResolvePrepareOrderShaderPass(
|
|
|
|
|
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("GaussianSplatPrepareOrder");
|
|
|
|
|
if (const Resources::ShaderPass* shaderPass =
|
|
|
|
|
FindCompatibleComputePass(*shader, passName, sceneData.globalShaderKeywords, backend)) {
|
|
|
|
|
resolved.shader = shader;
|
|
|
|
|
resolved.pass = shaderPass;
|
|
|
|
|
resolved.passName = passName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return resolved;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 06:09:53 +08:00
|
|
|
BuiltinGaussianSplatPass::ResolvedShaderPass BuiltinGaussianSplatPass::ResolveBitonicSortShaderPass(
|
|
|
|
|
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("GaussianSplatBitonicSort");
|
|
|
|
|
if (const Resources::ShaderPass* shaderPass =
|
|
|
|
|
FindCompatibleComputePass(*shader, passName, sceneData.globalShaderKeywords, backend)) {
|
|
|
|
|
resolved.shader = shader;
|
|
|
|
|
resolved.pass = shaderPass;
|
|
|
|
|
resolved.passName = passName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return resolved;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 23:11:11 +08:00
|
|
|
BuiltinGaussianSplatPass::PassResourceLayout* BuiltinGaussianSplatPass::GetOrCreatePassResourceLayout(
|
|
|
|
|
const RenderContext& context,
|
2026-04-11 03:02:30 +08:00
|
|
|
const ResolvedShaderPass& resolvedShaderPass,
|
|
|
|
|
PassLayoutUsage usage) {
|
2026-04-10 23:11:11 +08:00
|
|
|
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PassLayoutKey passLayoutKey = {};
|
|
|
|
|
passLayoutKey.shader = resolvedShaderPass.shader;
|
|
|
|
|
passLayoutKey.passName = resolvedShaderPass.passName;
|
|
|
|
|
|
|
|
|
|
const auto existing = m_passResourceLayouts.find(passLayoutKey);
|
|
|
|
|
if (existing != m_passResourceLayouts.end()) {
|
|
|
|
|
return &existing->second;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PassResourceLayout passLayout = {};
|
|
|
|
|
auto failLayout = [this, &passLayout](const char* message) -> PassResourceLayout* {
|
|
|
|
|
Debug::Logger::Get().Error(Debug::LogCategory::Rendering, message);
|
|
|
|
|
DestroyPassResourceLayout(passLayout);
|
|
|
|
|
return nullptr;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan bindingPlan = {};
|
|
|
|
|
Containers::String bindingPlanError;
|
|
|
|
|
if (!TryBuildBuiltinPassResourceBindingPlan(*resolvedShaderPass.pass, bindingPlan, &bindingPlanError)) {
|
|
|
|
|
const Containers::String contextualError =
|
|
|
|
|
Containers::String("BuiltinGaussianSplatPass failed to resolve pass resource bindings for shader='") +
|
|
|
|
|
resolvedShaderPass.shader->GetPath() +
|
|
|
|
|
"', pass='" + resolvedShaderPass.passName +
|
|
|
|
|
"': " + bindingPlanError +
|
|
|
|
|
". Bindings: " + DescribeShaderResourceBindings(resolvedShaderPass.pass->resources);
|
|
|
|
|
return failLayout(contextualError.CStr());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Containers::String setLayoutError;
|
|
|
|
|
if (!TryBuildBuiltinPassSetLayouts(bindingPlan, passLayout.setLayouts, &setLayoutError)) {
|
|
|
|
|
return failLayout(setLayoutError.CStr());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
passLayout.firstDescriptorSet = bindingPlan.firstDescriptorSet;
|
|
|
|
|
passLayout.descriptorSetCount = bindingPlan.descriptorSetCount;
|
|
|
|
|
passLayout.perObject = bindingPlan.perObject;
|
|
|
|
|
passLayout.material = bindingPlan.material;
|
2026-04-11 03:02:30 +08:00
|
|
|
passLayout.gaussianSplatSortDistanceBuffer = bindingPlan.gaussianSplatSortDistanceBuffer;
|
|
|
|
|
passLayout.gaussianSplatOrderBuffer = bindingPlan.gaussianSplatOrderBuffer;
|
2026-04-10 23:11:11 +08:00
|
|
|
passLayout.gaussianSplatPositionBuffer = bindingPlan.gaussianSplatPositionBuffer;
|
|
|
|
|
passLayout.gaussianSplatOtherBuffer = bindingPlan.gaussianSplatOtherBuffer;
|
|
|
|
|
passLayout.gaussianSplatColorBuffer = bindingPlan.gaussianSplatColorBuffer;
|
|
|
|
|
passLayout.gaussianSplatSHBuffer = bindingPlan.gaussianSplatSHBuffer;
|
2026-04-11 03:02:30 +08:00
|
|
|
passLayout.gaussianSplatViewDataBuffer = bindingPlan.gaussianSplatViewDataBuffer;
|
2026-04-10 23:11:11 +08:00
|
|
|
|
|
|
|
|
if (!passLayout.perObject.IsValid()) {
|
|
|
|
|
return failLayout("BuiltinGaussianSplatPass requires a PerObject resource binding");
|
|
|
|
|
}
|
2026-04-11 03:02:30 +08:00
|
|
|
|
|
|
|
|
if (usage == PassLayoutUsage::Draw) {
|
|
|
|
|
if (!passLayout.material.IsValid()) {
|
|
|
|
|
return failLayout("BuiltinGaussianSplatPass requires a Material resource binding");
|
|
|
|
|
}
|
|
|
|
|
if (!passLayout.gaussianSplatOrderBuffer.IsValid() ||
|
2026-04-11 05:37:31 +08:00
|
|
|
!passLayout.gaussianSplatViewDataBuffer.IsValid()) {
|
2026-04-11 03:02:30 +08:00
|
|
|
return failLayout(
|
2026-04-11 05:37:31 +08:00
|
|
|
"BuiltinGaussianSplatPass draw pass requires order and view-data gaussian splat buffer bindings");
|
2026-04-11 03:02:30 +08:00
|
|
|
}
|
|
|
|
|
} else if (usage == PassLayoutUsage::PrepareOrder) {
|
|
|
|
|
if (!passLayout.gaussianSplatSortDistanceBuffer.IsValid() ||
|
|
|
|
|
!passLayout.gaussianSplatOrderBuffer.IsValid() ||
|
2026-04-11 05:37:31 +08:00
|
|
|
!passLayout.gaussianSplatPositionBuffer.IsValid() ||
|
|
|
|
|
!passLayout.gaussianSplatOtherBuffer.IsValid() ||
|
|
|
|
|
!passLayout.gaussianSplatColorBuffer.IsValid() ||
|
2026-04-11 06:32:38 +08:00
|
|
|
!passLayout.gaussianSplatSHBuffer.IsValid() ||
|
2026-04-11 05:37:31 +08:00
|
|
|
!passLayout.gaussianSplatViewDataBuffer.IsValid()) {
|
2026-04-11 03:02:30 +08:00
|
|
|
return failLayout(
|
2026-04-11 06:32:38 +08:00
|
|
|
"BuiltinGaussianSplatPass prepare-order pass requires sort distance, order, position, other, color, SH, and view-data gaussian splat buffer bindings");
|
2026-04-11 03:02:30 +08:00
|
|
|
}
|
2026-04-11 06:09:53 +08:00
|
|
|
} else if (usage == PassLayoutUsage::BitonicSort) {
|
|
|
|
|
if (!passLayout.gaussianSplatSortDistanceBuffer.IsValid() ||
|
|
|
|
|
!passLayout.gaussianSplatOrderBuffer.IsValid()) {
|
|
|
|
|
return failLayout(
|
|
|
|
|
"BuiltinGaussianSplatPass bitonic-sort pass requires sort distance and order gaussian splat buffer bindings");
|
|
|
|
|
}
|
2026-04-10 23:11:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<RHI::DescriptorSetLayoutDesc> nativeSetLayouts(passLayout.setLayouts.size());
|
|
|
|
|
for (size_t setIndex = 0; setIndex < passLayout.setLayouts.size(); ++setIndex) {
|
|
|
|
|
nativeSetLayouts[setIndex] = passLayout.setLayouts[setIndex].layout;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {};
|
|
|
|
|
pipelineLayoutDesc.setLayouts = nativeSetLayouts.empty() ? nullptr : nativeSetLayouts.data();
|
|
|
|
|
pipelineLayoutDesc.setLayoutCount = static_cast<Core::uint32>(nativeSetLayouts.size());
|
|
|
|
|
passLayout.pipelineLayout = context.device->CreatePipelineLayout(pipelineLayoutDesc);
|
|
|
|
|
if (passLayout.pipelineLayout == nullptr) {
|
|
|
|
|
return failLayout("BuiltinGaussianSplatPass failed to create pipeline layout from shader pass resources");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto result = m_passResourceLayouts.emplace(passLayoutKey, passLayout);
|
|
|
|
|
PassResourceLayout& storedPassLayout = result.first->second;
|
|
|
|
|
RefreshBuiltinPassSetLayouts(storedPassLayout.setLayouts);
|
|
|
|
|
return &storedPassLayout;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RHI::RHIPipelineState* BuiltinGaussianSplatPass::GetOrCreatePipelineState(
|
|
|
|
|
const RenderContext& context,
|
|
|
|
|
const RenderSurface& surface,
|
|
|
|
|
const RenderSceneData& sceneData,
|
|
|
|
|
const Resources::Material* material) {
|
|
|
|
|
const Resources::ShaderKeywordSet keywordSet = ResolvePassKeywordSet(sceneData, material);
|
|
|
|
|
const ResolvedShaderPass resolvedShaderPass = ResolveGaussianSplatShaderPass(sceneData, material);
|
|
|
|
|
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 03:02:30 +08:00
|
|
|
PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(
|
|
|
|
|
context,
|
|
|
|
|
resolvedShaderPass,
|
|
|
|
|
PassLayoutUsage::Draw);
|
2026-04-10 23:11:11 +08:00
|
|
|
if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PipelineStateKey pipelineKey = {};
|
|
|
|
|
pipelineKey.renderState =
|
|
|
|
|
BuildStaticPipelineRenderStateKey(ResolveEffectiveRenderState(resolvedShaderPass.pass, material));
|
|
|
|
|
pipelineKey.shader = resolvedShaderPass.shader;
|
|
|
|
|
pipelineKey.passName = resolvedShaderPass.passName;
|
|
|
|
|
pipelineKey.keywordSignature = ::XCEngine::Rendering::Internal::BuildShaderKeywordSignature(keywordSet);
|
|
|
|
|
pipelineKey.renderTargetCount =
|
|
|
|
|
::XCEngine::Rendering::Internal::HasSingleColorAttachment(surface) ? 1u : 0u;
|
|
|
|
|
pipelineKey.renderTargetFormat =
|
|
|
|
|
static_cast<Core::uint32>(::XCEngine::Rendering::Internal::ResolveSurfaceColorFormat(surface, 0u));
|
|
|
|
|
pipelineKey.depthStencilFormat =
|
|
|
|
|
static_cast<Core::uint32>(::XCEngine::Rendering::Internal::ResolveSurfaceDepthFormat(surface));
|
|
|
|
|
pipelineKey.sampleCount = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(surface);
|
|
|
|
|
pipelineKey.sampleQuality = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleQuality(surface);
|
|
|
|
|
|
|
|
|
|
const auto existing = m_pipelineStates.find(pipelineKey);
|
|
|
|
|
if (existing != m_pipelineStates.end()) {
|
|
|
|
|
return existing->second;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const RHI::GraphicsPipelineDesc pipelineDesc =
|
|
|
|
|
CreatePipelineDesc(
|
|
|
|
|
context.backendType,
|
|
|
|
|
passLayout->pipelineLayout,
|
|
|
|
|
*resolvedShaderPass.shader,
|
|
|
|
|
*resolvedShaderPass.pass,
|
|
|
|
|
resolvedShaderPass.passName,
|
|
|
|
|
keywordSet,
|
|
|
|
|
material,
|
|
|
|
|
surface);
|
|
|
|
|
RHI::RHIPipelineState* pipelineState = context.device->CreatePipelineState(pipelineDesc);
|
|
|
|
|
if (pipelineState == nullptr || !pipelineState->IsValid()) {
|
|
|
|
|
if (pipelineState != nullptr) {
|
|
|
|
|
pipelineState->Shutdown();
|
|
|
|
|
delete pipelineState;
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_pipelineStates.emplace(pipelineKey, pipelineState);
|
|
|
|
|
return pipelineState;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 03:02:30 +08:00
|
|
|
RHI::RHIPipelineState* BuiltinGaussianSplatPass::GetOrCreateComputePipelineState(
|
|
|
|
|
const RenderContext& context,
|
2026-04-11 06:09:53 +08:00
|
|
|
const ResolvedShaderPass& resolvedShaderPass,
|
|
|
|
|
PassLayoutUsage usage,
|
|
|
|
|
const Resources::ShaderKeywordSet& keywordSet) {
|
2026-04-11 03:02:30 +08:00
|
|
|
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(
|
|
|
|
|
context,
|
|
|
|
|
resolvedShaderPass,
|
2026-04-11 06:09:53 +08:00
|
|
|
usage);
|
2026-04-11 03:02:30 +08:00
|
|
|
if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ComputePipelineKey pipelineKey = {};
|
|
|
|
|
pipelineKey.shader = resolvedShaderPass.shader;
|
|
|
|
|
pipelineKey.passName = resolvedShaderPass.passName;
|
|
|
|
|
pipelineKey.keywordSignature =
|
2026-04-11 06:09:53 +08:00
|
|
|
::XCEngine::Rendering::Internal::BuildShaderKeywordSignature(keywordSet);
|
2026-04-11 03:02:30 +08:00
|
|
|
|
|
|
|
|
const auto existing = m_computePipelineStates.find(pipelineKey);
|
|
|
|
|
if (existing != m_computePipelineStates.end()) {
|
|
|
|
|
return existing->second;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const RHI::ComputePipelineDesc pipelineDesc =
|
|
|
|
|
CreateComputePipelineDesc(
|
|
|
|
|
context.backendType,
|
|
|
|
|
passLayout->pipelineLayout,
|
|
|
|
|
*resolvedShaderPass.shader,
|
|
|
|
|
*resolvedShaderPass.pass,
|
|
|
|
|
resolvedShaderPass.passName,
|
2026-04-11 06:09:53 +08:00
|
|
|
keywordSet);
|
2026-04-11 03:02:30 +08:00
|
|
|
RHI::RHIPipelineState* pipelineState = context.device->CreateComputePipelineState(pipelineDesc);
|
|
|
|
|
if (pipelineState == nullptr || !pipelineState->IsValid()) {
|
2026-04-11 05:37:31 +08:00
|
|
|
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());
|
2026-04-11 03:02:30 +08:00
|
|
|
if (pipelineState != nullptr) {
|
|
|
|
|
pipelineState->Shutdown();
|
|
|
|
|
delete pipelineState;
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_computePipelineStates.emplace(pipelineKey, pipelineState);
|
|
|
|
|
return pipelineState;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 23:11:11 +08:00
|
|
|
bool BuiltinGaussianSplatPass::CreateOwnedDescriptorSet(
|
|
|
|
|
const BuiltinPassSetLayoutMetadata& setLayout,
|
|
|
|
|
OwnedDescriptorSet& descriptorSet) {
|
|
|
|
|
RHI::DescriptorPoolDesc poolDesc = {};
|
|
|
|
|
poolDesc.type = setLayout.heapType;
|
|
|
|
|
poolDesc.descriptorCount = CountBuiltinPassHeapDescriptors(setLayout.heapType, setLayout.bindings);
|
|
|
|
|
poolDesc.shaderVisible = setLayout.shaderVisible;
|
|
|
|
|
|
|
|
|
|
descriptorSet.pool = m_device->CreateDescriptorPool(poolDesc);
|
|
|
|
|
if (descriptorSet.pool == nullptr) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
descriptorSet.set = descriptorSet.pool->AllocateSet(setLayout.layout);
|
|
|
|
|
if (descriptorSet.set == nullptr) {
|
|
|
|
|
DestroyOwnedDescriptorSet(descriptorSet);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BuiltinGaussianSplatPass::CachedDescriptorSet* BuiltinGaussianSplatPass::GetOrCreateDynamicDescriptorSet(
|
|
|
|
|
const PassLayoutKey& passLayoutKey,
|
|
|
|
|
const PassResourceLayout& passLayout,
|
|
|
|
|
const BuiltinPassSetLayoutMetadata& setLayout,
|
|
|
|
|
Core::uint32 setIndex,
|
|
|
|
|
Core::uint64 objectId,
|
2026-04-11 03:02:30 +08:00
|
|
|
const Components::GaussianSplatRendererComponent* gaussianSplatRenderer,
|
2026-04-10 23:11:11 +08:00
|
|
|
const Resources::Material* material,
|
|
|
|
|
const Resources::GaussianSplat* gaussianSplat,
|
|
|
|
|
const MaterialConstantPayloadView& materialConstants,
|
2026-04-11 03:02:30 +08:00
|
|
|
const RenderResourceCache::CachedGaussianSplat& cachedGaussianSplat,
|
|
|
|
|
RHI::RHIResourceView* sortDistanceView,
|
|
|
|
|
RHI::RHIResourceView* orderView,
|
|
|
|
|
RHI::RHIResourceView* viewDataView) {
|
2026-04-10 23:11:11 +08:00
|
|
|
DynamicDescriptorSetKey key = {};
|
|
|
|
|
key.passLayout = passLayoutKey;
|
|
|
|
|
key.setIndex = setIndex;
|
|
|
|
|
key.objectId = objectId;
|
2026-04-11 03:02:30 +08:00
|
|
|
key.gaussianSplatRenderer = gaussianSplatRenderer;
|
2026-04-10 23:11:11 +08:00
|
|
|
key.material = material;
|
|
|
|
|
key.gaussianSplat = gaussianSplat;
|
|
|
|
|
|
|
|
|
|
CachedDescriptorSet& cachedDescriptorSet = m_dynamicDescriptorSets[key];
|
|
|
|
|
if (cachedDescriptorSet.descriptorSet.set == nullptr) {
|
|
|
|
|
if (!CreateOwnedDescriptorSet(setLayout, cachedDescriptorSet.descriptorSet)) {
|
2026-04-11 05:37:31 +08:00
|
|
|
Debug::Logger::Get().Error(
|
|
|
|
|
Debug::LogCategory::Rendering,
|
|
|
|
|
"BuiltinGaussianSplatPass failed to allocate descriptor set");
|
2026-04-10 23:11:11 +08:00
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 03:02:30 +08:00
|
|
|
const Internal::BuiltinGaussianSplatPassResources::WorkingSet* workingSet =
|
|
|
|
|
(gaussianSplatRenderer != nullptr && m_passResources != nullptr)
|
|
|
|
|
? m_passResources->FindWorkingSet(gaussianSplatRenderer)
|
|
|
|
|
: nullptr;
|
|
|
|
|
RHI::RHIResourceView* resolvedSortDistanceView = sortDistanceView;
|
|
|
|
|
RHI::RHIResourceView* resolvedOrderView = orderView;
|
|
|
|
|
RHI::RHIResourceView* resolvedViewDataView = viewDataView;
|
|
|
|
|
|
|
|
|
|
if (setLayout.usesGaussianSplatSortDistanceBuffer && workingSet != nullptr) {
|
|
|
|
|
if (const RHI::DescriptorSetLayoutBinding* layoutBinding =
|
|
|
|
|
FindSetLayoutBinding(setLayout, passLayout.gaussianSplatSortDistanceBuffer.binding)) {
|
|
|
|
|
if (RHI::RHIResourceView* view = ResolveWorkingSetView(workingSet->sortDistances, layoutBinding)) {
|
|
|
|
|
resolvedSortDistanceView = view;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (setLayout.usesGaussianSplatOrderBuffer && workingSet != nullptr) {
|
|
|
|
|
if (const RHI::DescriptorSetLayoutBinding* layoutBinding =
|
|
|
|
|
FindSetLayoutBinding(setLayout, passLayout.gaussianSplatOrderBuffer.binding)) {
|
|
|
|
|
if (RHI::RHIResourceView* view = ResolveWorkingSetView(workingSet->orderIndices, layoutBinding)) {
|
|
|
|
|
resolvedOrderView = view;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (setLayout.usesGaussianSplatViewDataBuffer && workingSet != nullptr) {
|
|
|
|
|
if (const RHI::DescriptorSetLayoutBinding* layoutBinding =
|
|
|
|
|
FindSetLayoutBinding(setLayout, passLayout.gaussianSplatViewDataBuffer.binding)) {
|
|
|
|
|
if (RHI::RHIResourceView* view = ResolveWorkingSetView(workingSet->viewData, layoutBinding)) {
|
|
|
|
|
resolvedViewDataView = view;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 23:11:11 +08:00
|
|
|
const Core::uint64 materialVersion = material != nullptr ? material->GetChangeVersion() : 0u;
|
|
|
|
|
if (setLayout.usesMaterial) {
|
|
|
|
|
if (!passLayout.material.IsValid() ||
|
|
|
|
|
passLayout.material.set != setIndex ||
|
|
|
|
|
!materialConstants.IsValid()) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cachedDescriptorSet.materialVersion != materialVersion) {
|
|
|
|
|
cachedDescriptorSet.descriptorSet.set->WriteConstant(
|
|
|
|
|
passLayout.material.binding,
|
|
|
|
|
materialConstants.data,
|
|
|
|
|
materialConstants.size);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (setLayout.usesGaussianSplatPositionBuffer) {
|
|
|
|
|
if (cachedGaussianSplat.positions.shaderResourceView == nullptr ||
|
|
|
|
|
!passLayout.gaussianSplatPositionBuffer.IsValid() ||
|
|
|
|
|
passLayout.gaussianSplatPositionBuffer.set != setIndex) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cachedDescriptorSet.positionsView != cachedGaussianSplat.positions.shaderResourceView) {
|
|
|
|
|
cachedDescriptorSet.descriptorSet.set->Update(
|
|
|
|
|
passLayout.gaussianSplatPositionBuffer.binding,
|
|
|
|
|
cachedGaussianSplat.positions.shaderResourceView);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 03:02:30 +08:00
|
|
|
if (setLayout.usesGaussianSplatSortDistanceBuffer) {
|
|
|
|
|
if (resolvedSortDistanceView == nullptr ||
|
|
|
|
|
!passLayout.gaussianSplatSortDistanceBuffer.IsValid() ||
|
|
|
|
|
passLayout.gaussianSplatSortDistanceBuffer.set != setIndex) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cachedDescriptorSet.sortDistanceView != resolvedSortDistanceView) {
|
|
|
|
|
cachedDescriptorSet.descriptorSet.set->Update(
|
|
|
|
|
passLayout.gaussianSplatSortDistanceBuffer.binding,
|
|
|
|
|
resolvedSortDistanceView);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (setLayout.usesGaussianSplatOrderBuffer) {
|
|
|
|
|
if (resolvedOrderView == nullptr ||
|
|
|
|
|
!passLayout.gaussianSplatOrderBuffer.IsValid() ||
|
|
|
|
|
passLayout.gaussianSplatOrderBuffer.set != setIndex) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cachedDescriptorSet.orderView != resolvedOrderView) {
|
|
|
|
|
cachedDescriptorSet.descriptorSet.set->Update(
|
|
|
|
|
passLayout.gaussianSplatOrderBuffer.binding,
|
|
|
|
|
resolvedOrderView);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 23:11:11 +08:00
|
|
|
if (setLayout.usesGaussianSplatOtherBuffer) {
|
|
|
|
|
if (cachedGaussianSplat.other.shaderResourceView == nullptr ||
|
|
|
|
|
!passLayout.gaussianSplatOtherBuffer.IsValid() ||
|
|
|
|
|
passLayout.gaussianSplatOtherBuffer.set != setIndex) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cachedDescriptorSet.otherView != cachedGaussianSplat.other.shaderResourceView) {
|
|
|
|
|
cachedDescriptorSet.descriptorSet.set->Update(
|
|
|
|
|
passLayout.gaussianSplatOtherBuffer.binding,
|
|
|
|
|
cachedGaussianSplat.other.shaderResourceView);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (setLayout.usesGaussianSplatColorBuffer) {
|
|
|
|
|
if (cachedGaussianSplat.color.shaderResourceView == nullptr ||
|
|
|
|
|
!passLayout.gaussianSplatColorBuffer.IsValid() ||
|
|
|
|
|
passLayout.gaussianSplatColorBuffer.set != setIndex) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cachedDescriptorSet.colorView != cachedGaussianSplat.color.shaderResourceView) {
|
|
|
|
|
cachedDescriptorSet.descriptorSet.set->Update(
|
|
|
|
|
passLayout.gaussianSplatColorBuffer.binding,
|
|
|
|
|
cachedGaussianSplat.color.shaderResourceView);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (setLayout.usesGaussianSplatSHBuffer) {
|
|
|
|
|
if (cachedGaussianSplat.sh.shaderResourceView == nullptr ||
|
|
|
|
|
!passLayout.gaussianSplatSHBuffer.IsValid() ||
|
|
|
|
|
passLayout.gaussianSplatSHBuffer.set != setIndex) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cachedDescriptorSet.shView != cachedGaussianSplat.sh.shaderResourceView) {
|
|
|
|
|
cachedDescriptorSet.descriptorSet.set->Update(
|
|
|
|
|
passLayout.gaussianSplatSHBuffer.binding,
|
|
|
|
|
cachedGaussianSplat.sh.shaderResourceView);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 03:02:30 +08:00
|
|
|
if (setLayout.usesGaussianSplatViewDataBuffer) {
|
|
|
|
|
if (resolvedViewDataView == nullptr ||
|
|
|
|
|
!passLayout.gaussianSplatViewDataBuffer.IsValid() ||
|
|
|
|
|
passLayout.gaussianSplatViewDataBuffer.set != setIndex) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cachedDescriptorSet.viewDataView != resolvedViewDataView) {
|
|
|
|
|
cachedDescriptorSet.descriptorSet.set->Update(
|
|
|
|
|
passLayout.gaussianSplatViewDataBuffer.binding,
|
|
|
|
|
resolvedViewDataView);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 23:11:11 +08:00
|
|
|
cachedDescriptorSet.materialVersion = materialVersion;
|
2026-04-11 03:02:30 +08:00
|
|
|
cachedDescriptorSet.sortDistanceView = resolvedSortDistanceView;
|
|
|
|
|
cachedDescriptorSet.orderView = resolvedOrderView;
|
2026-04-10 23:11:11 +08:00
|
|
|
cachedDescriptorSet.positionsView = cachedGaussianSplat.positions.shaderResourceView;
|
|
|
|
|
cachedDescriptorSet.otherView = cachedGaussianSplat.other.shaderResourceView;
|
|
|
|
|
cachedDescriptorSet.colorView = cachedGaussianSplat.color.shaderResourceView;
|
|
|
|
|
cachedDescriptorSet.shView = cachedGaussianSplat.sh.shaderResourceView;
|
2026-04-11 03:02:30 +08:00
|
|
|
cachedDescriptorSet.viewDataView = resolvedViewDataView;
|
2026-04-10 23:11:11 +08:00
|
|
|
return &cachedDescriptorSet;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BuiltinGaussianSplatPass::DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet) {
|
|
|
|
|
if (descriptorSet.set != nullptr) {
|
|
|
|
|
descriptorSet.set->Shutdown();
|
|
|
|
|
delete descriptorSet.set;
|
|
|
|
|
descriptorSet.set = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (descriptorSet.pool != nullptr) {
|
|
|
|
|
descriptorSet.pool->Shutdown();
|
|
|
|
|
delete descriptorSet.pool;
|
|
|
|
|
descriptorSet.pool = nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BuiltinGaussianSplatPass::DestroyPassResourceLayout(PassResourceLayout& passLayout) {
|
|
|
|
|
if (passLayout.pipelineLayout != nullptr) {
|
|
|
|
|
passLayout.pipelineLayout->Shutdown();
|
|
|
|
|
delete passLayout.pipelineLayout;
|
|
|
|
|
passLayout.pipelineLayout = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
passLayout.setLayouts.clear();
|
|
|
|
|
passLayout.firstDescriptorSet = 0;
|
|
|
|
|
passLayout.descriptorSetCount = 0;
|
|
|
|
|
passLayout.perObject = {};
|
|
|
|
|
passLayout.material = {};
|
2026-04-11 03:02:30 +08:00
|
|
|
passLayout.gaussianSplatSortDistanceBuffer = {};
|
|
|
|
|
passLayout.gaussianSplatOrderBuffer = {};
|
2026-04-10 23:11:11 +08:00
|
|
|
passLayout.gaussianSplatPositionBuffer = {};
|
|
|
|
|
passLayout.gaussianSplatOtherBuffer = {};
|
|
|
|
|
passLayout.gaussianSplatColorBuffer = {};
|
|
|
|
|
passLayout.gaussianSplatSHBuffer = {};
|
2026-04-11 03:02:30 +08:00
|
|
|
passLayout.gaussianSplatViewDataBuffer = {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat(
|
|
|
|
|
const RenderContext& context,
|
|
|
|
|
const RenderSceneData& sceneData,
|
|
|
|
|
const VisibleGaussianSplatItem& visibleGaussianSplat) {
|
2026-04-11 05:37:31 +08:00
|
|
|
auto fail = [](const char* message) -> bool {
|
|
|
|
|
Debug::Logger::Get().Error(Debug::LogCategory::Rendering, message);
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-11 03:02:30 +08:00
|
|
|
if (visibleGaussianSplat.gameObject == nullptr ||
|
|
|
|
|
visibleGaussianSplat.gaussianSplat == nullptr ||
|
|
|
|
|
!visibleGaussianSplat.gaussianSplat->IsValid() ||
|
|
|
|
|
m_passResources == nullptr) {
|
2026-04-11 05:37:31 +08:00
|
|
|
return fail("BuiltinGaussianSplatPass prepare-order failed: invalid visible gaussian splat item");
|
2026-04-11 03:02:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const RenderResourceCache::CachedGaussianSplat* cachedGaussianSplat =
|
|
|
|
|
m_resourceCache.GetOrCreateGaussianSplat(m_device, visibleGaussianSplat.gaussianSplat);
|
2026-04-11 06:32:38 +08:00
|
|
|
if (cachedGaussianSplat == nullptr ||
|
|
|
|
|
cachedGaussianSplat->positions.shaderResourceView == nullptr ||
|
|
|
|
|
cachedGaussianSplat->other.shaderResourceView == nullptr ||
|
|
|
|
|
cachedGaussianSplat->color.shaderResourceView == nullptr ||
|
|
|
|
|
cachedGaussianSplat->sh.shaderResourceView == nullptr) {
|
2026-04-11 05:37:31 +08:00
|
|
|
return fail("BuiltinGaussianSplatPass prepare-order failed: gaussian splat GPU cache is incomplete");
|
2026-04-11 03:02:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Internal::BuiltinGaussianSplatPassResources::WorkingSet* workingSet = nullptr;
|
|
|
|
|
if (!m_passResources->EnsureWorkingSet(m_device, visibleGaussianSplat, workingSet) ||
|
|
|
|
|
workingSet == nullptr ||
|
|
|
|
|
workingSet->sortDistances.unorderedAccessView == nullptr ||
|
|
|
|
|
workingSet->orderIndices.unorderedAccessView == nullptr ||
|
2026-04-11 05:37:31 +08:00
|
|
|
workingSet->orderIndices.shaderResourceView == nullptr ||
|
|
|
|
|
workingSet->viewData.unorderedAccessView == nullptr ||
|
|
|
|
|
workingSet->viewData.shaderResourceView == nullptr) {
|
|
|
|
|
return fail("BuiltinGaussianSplatPass prepare-order failed: working set allocation is incomplete");
|
2026-04-11 03:02:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ResolvedShaderPass resolvedShaderPass = ResolvePrepareOrderShaderPass(sceneData);
|
|
|
|
|
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
|
2026-04-11 05:37:31 +08:00
|
|
|
return fail("BuiltinGaussianSplatPass prepare-order failed: utilities shader pass was not resolved");
|
2026-04-11 03:02:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PassLayoutKey passLayoutKey = {};
|
|
|
|
|
passLayoutKey.shader = resolvedShaderPass.shader;
|
|
|
|
|
passLayoutKey.passName = resolvedShaderPass.passName;
|
|
|
|
|
|
|
|
|
|
PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(
|
|
|
|
|
context,
|
|
|
|
|
resolvedShaderPass,
|
|
|
|
|
PassLayoutUsage::PrepareOrder);
|
2026-04-11 06:09:53 +08:00
|
|
|
RHI::RHIPipelineState* pipelineState = GetOrCreateComputePipelineState(
|
|
|
|
|
context,
|
|
|
|
|
resolvedShaderPass,
|
|
|
|
|
PassLayoutUsage::PrepareOrder,
|
|
|
|
|
sceneData.globalShaderKeywords);
|
2026-04-11 03:02:30 +08:00
|
|
|
if (passLayout == nullptr || pipelineState == nullptr) {
|
2026-04-11 05:37:31 +08:00
|
|
|
return fail("BuiltinGaussianSplatPass prepare-order failed: compute pipeline setup was not created");
|
2026-04-11 03:02:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RHI::RHICommandList* commandList = context.commandList;
|
2026-04-11 06:09:53 +08:00
|
|
|
if (!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");
|
2026-04-11 05:37:31 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-11 03:02:30 +08:00
|
|
|
commandList->SetPipelineState(pipelineState);
|
|
|
|
|
|
2026-04-11 06:57:47 +08:00
|
|
|
const float shOrder =
|
|
|
|
|
visibleGaussianSplat.gaussianSplat != nullptr
|
|
|
|
|
? static_cast<float>(visibleGaussianSplat.gaussianSplat->GetSHOrder())
|
|
|
|
|
: 0.0f;
|
|
|
|
|
|
2026-04-11 03:02:30 +08:00
|
|
|
const PerObjectConstants perObjectConstants = {
|
|
|
|
|
sceneData.cameraData.projection,
|
|
|
|
|
sceneData.cameraData.view,
|
|
|
|
|
visibleGaussianSplat.localToWorld.Transpose(),
|
2026-04-11 06:32:38 +08:00
|
|
|
visibleGaussianSplat.localToWorld.Inverse(),
|
2026-04-11 03:02:30 +08:00
|
|
|
Math::Vector4(sceneData.cameraData.worldRight, 0.0f),
|
2026-04-11 05:37:31 +08:00
|
|
|
Math::Vector4(sceneData.cameraData.worldUp, 0.0f),
|
2026-04-11 06:32:38 +08:00
|
|
|
Math::Vector4(sceneData.cameraData.worldPosition, 0.0f),
|
2026-04-11 05:37:31 +08:00
|
|
|
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),
|
2026-04-11 06:09:53 +08:00
|
|
|
Math::Vector4(
|
|
|
|
|
static_cast<float>(cachedGaussianSplat->splatCount),
|
|
|
|
|
static_cast<float>(workingSet->sortCapacity),
|
2026-04-11 06:57:47 +08:00
|
|
|
shOrder,
|
2026-04-11 06:09:53 +08:00
|
|
|
0.0f)
|
2026-04-11 03:02:30 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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()) {
|
2026-04-11 05:37:31 +08:00
|
|
|
return fail("BuiltinGaussianSplatPass prepare-order failed: descriptor set index overflow");
|
2026-04-11 03:02:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const BuiltinPassSetLayoutMetadata& setLayout = passLayout->setLayouts[setIndex];
|
|
|
|
|
if (setLayout.layout.bindingCount == 0u) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!(setLayout.usesPerObject ||
|
|
|
|
|
setLayout.usesGaussianSplatSortDistanceBuffer ||
|
|
|
|
|
setLayout.usesGaussianSplatOrderBuffer ||
|
2026-04-11 05:37:31 +08:00
|
|
|
setLayout.usesGaussianSplatPositionBuffer ||
|
|
|
|
|
setLayout.usesGaussianSplatOtherBuffer ||
|
|
|
|
|
setLayout.usesGaussianSplatColorBuffer ||
|
2026-04-11 06:32:38 +08:00
|
|
|
setLayout.usesGaussianSplatSHBuffer ||
|
2026-04-11 05:37:31 +08:00
|
|
|
setLayout.usesGaussianSplatViewDataBuffer)) {
|
|
|
|
|
return fail("BuiltinGaussianSplatPass prepare-order failed: unexpected descriptor set layout");
|
2026-04-11 03:02:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Core::uint64 objectId =
|
|
|
|
|
setLayout.usesPerObject ? visibleGaussianSplat.gameObject->GetID() : 0u;
|
|
|
|
|
const Resources::GaussianSplat* gaussianSplatKey =
|
|
|
|
|
(setLayout.usesGaussianSplatSortDistanceBuffer ||
|
|
|
|
|
setLayout.usesGaussianSplatOrderBuffer ||
|
2026-04-11 05:37:31 +08:00
|
|
|
setLayout.usesGaussianSplatPositionBuffer ||
|
|
|
|
|
setLayout.usesGaussianSplatOtherBuffer ||
|
|
|
|
|
setLayout.usesGaussianSplatColorBuffer ||
|
2026-04-11 06:32:38 +08:00
|
|
|
setLayout.usesGaussianSplatSHBuffer ||
|
2026-04-11 05:37:31 +08:00
|
|
|
setLayout.usesGaussianSplatViewDataBuffer)
|
2026-04-11 03:02:30 +08:00
|
|
|
? visibleGaussianSplat.gaussianSplat
|
|
|
|
|
: nullptr;
|
|
|
|
|
|
|
|
|
|
CachedDescriptorSet* cachedDescriptorSet = GetOrCreateDynamicDescriptorSet(
|
|
|
|
|
passLayoutKey,
|
|
|
|
|
*passLayout,
|
|
|
|
|
setLayout,
|
|
|
|
|
setIndex,
|
|
|
|
|
objectId,
|
|
|
|
|
visibleGaussianSplat.gaussianSplatRenderer,
|
|
|
|
|
nullptr,
|
|
|
|
|
gaussianSplatKey,
|
|
|
|
|
MaterialConstantPayloadView(),
|
|
|
|
|
*cachedGaussianSplat,
|
|
|
|
|
workingSet->sortDistances.unorderedAccessView,
|
|
|
|
|
workingSet->orderIndices.unorderedAccessView,
|
|
|
|
|
workingSet->viewData.unorderedAccessView);
|
|
|
|
|
if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) {
|
2026-04-11 05:37:31 +08:00
|
|
|
return fail("BuiltinGaussianSplatPass prepare-order failed: dynamic descriptor set resolution failed");
|
2026-04-11 03:02:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RHI::RHIDescriptorSet* descriptorSet = cachedDescriptorSet->descriptorSet.set;
|
|
|
|
|
if (setLayout.usesPerObject) {
|
|
|
|
|
if (!passLayout->perObject.IsValid() || passLayout->perObject.set != setIndex) {
|
2026-04-11 05:37:31 +08:00
|
|
|
return fail("BuiltinGaussianSplatPass prepare-order failed: per-object binding is invalid");
|
2026-04-11 03:02:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 06:09:53 +08:00
|
|
|
commandList->Dispatch((workingSet->sortCapacity + 63u) / 64u, 1u, 1u);
|
|
|
|
|
|
|
|
|
|
if (!TransitionWorkingSetBuffer(commandList, workingSet->viewData, RHI::ResourceStates::NonPixelShaderResource)) {
|
|
|
|
|
return fail("BuiltinGaussianSplatPass prepare-order failed: view-data transition failed");
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BuiltinGaussianSplatPass::SortVisibleGaussianSplat(
|
|
|
|
|
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 bitonic-sort failed: invalid visible gaussian splat item");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const RenderResourceCache::CachedGaussianSplat* cachedGaussianSplat =
|
|
|
|
|
m_resourceCache.GetOrCreateGaussianSplat(m_device, visibleGaussianSplat.gaussianSplat);
|
|
|
|
|
if (cachedGaussianSplat == nullptr) {
|
|
|
|
|
return fail("BuiltinGaussianSplatPass bitonic-sort failed: gaussian splat GPU cache is missing");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Internal::BuiltinGaussianSplatPassResources::WorkingSet* workingSet = nullptr;
|
|
|
|
|
if (!m_passResources->EnsureWorkingSet(m_device, visibleGaussianSplat, workingSet) ||
|
|
|
|
|
workingSet == nullptr ||
|
|
|
|
|
workingSet->sortDistances.shaderResourceView == nullptr ||
|
|
|
|
|
workingSet->sortDistances.unorderedAccessView == nullptr ||
|
|
|
|
|
workingSet->orderIndices.shaderResourceView == nullptr ||
|
|
|
|
|
workingSet->orderIndices.unorderedAccessView == nullptr) {
|
|
|
|
|
return fail("BuiltinGaussianSplatPass bitonic-sort failed: working set allocation is incomplete");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RHI::RHICommandList* commandList = context.commandList;
|
|
|
|
|
if (workingSet->sortCapacity <= 1u) {
|
|
|
|
|
return TransitionWorkingSetBuffer(
|
|
|
|
|
commandList,
|
|
|
|
|
workingSet->orderIndices,
|
|
|
|
|
RHI::ResourceStates::NonPixelShaderResource);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ResolvedShaderPass resolvedShaderPass = ResolveBitonicSortShaderPass(sceneData);
|
|
|
|
|
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
|
|
|
|
|
return fail("BuiltinGaussianSplatPass bitonic-sort failed: utilities shader pass was not resolved");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PassLayoutKey passLayoutKey = {};
|
|
|
|
|
passLayoutKey.shader = resolvedShaderPass.shader;
|
|
|
|
|
passLayoutKey.passName = resolvedShaderPass.passName;
|
|
|
|
|
|
|
|
|
|
PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(
|
|
|
|
|
context,
|
|
|
|
|
resolvedShaderPass,
|
|
|
|
|
PassLayoutUsage::BitonicSort);
|
|
|
|
|
RHI::RHIPipelineState* pipelineState = GetOrCreateComputePipelineState(
|
|
|
|
|
context,
|
|
|
|
|
resolvedShaderPass,
|
|
|
|
|
PassLayoutUsage::BitonicSort,
|
|
|
|
|
sceneData.globalShaderKeywords);
|
|
|
|
|
if (passLayout == nullptr || pipelineState == nullptr) {
|
|
|
|
|
return fail("BuiltinGaussianSplatPass bitonic-sort failed: compute pipeline setup was not created");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
commandList->SetPipelineState(pipelineState);
|
|
|
|
|
|
|
|
|
|
for (Core::uint32 levelMask = 2u; levelMask <= workingSet->sortCapacity; levelMask <<= 1u) {
|
|
|
|
|
for (Core::uint32 partnerMask = levelMask >> 1u; partnerMask > 0u; partnerMask >>= 1u) {
|
|
|
|
|
if (!TransitionWorkingSetBuffer(commandList, workingSet->sortDistances, RHI::ResourceStates::UnorderedAccess) ||
|
|
|
|
|
!TransitionWorkingSetBuffer(commandList, workingSet->orderIndices, RHI::ResourceStates::UnorderedAccess)) {
|
|
|
|
|
return fail("BuiltinGaussianSplatPass bitonic-sort failed: resource transition to UAV failed");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const PerObjectConstants perObjectConstants = {
|
|
|
|
|
sceneData.cameraData.projection,
|
|
|
|
|
sceneData.cameraData.view,
|
|
|
|
|
visibleGaussianSplat.localToWorld.Transpose(),
|
2026-04-11 06:32:38 +08:00
|
|
|
visibleGaussianSplat.localToWorld.Inverse(),
|
2026-04-11 06:09:53 +08:00
|
|
|
Math::Vector4(sceneData.cameraData.worldRight, 0.0f),
|
|
|
|
|
Math::Vector4(sceneData.cameraData.worldUp, 0.0f),
|
2026-04-11 06:32:38 +08:00
|
|
|
Math::Vector4(sceneData.cameraData.worldPosition, 0.0f),
|
2026-04-11 06:09:53 +08:00
|
|
|
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),
|
|
|
|
|
static_cast<float>(partnerMask),
|
|
|
|
|
static_cast<float>(levelMask))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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 bitonic-sort failed: descriptor set index overflow");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const BuiltinPassSetLayoutMetadata& setLayout = passLayout->setLayouts[setIndex];
|
|
|
|
|
if (setLayout.layout.bindingCount == 0u) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!(setLayout.usesPerObject ||
|
|
|
|
|
setLayout.usesGaussianSplatSortDistanceBuffer ||
|
|
|
|
|
setLayout.usesGaussianSplatOrderBuffer)) {
|
|
|
|
|
return fail("BuiltinGaussianSplatPass bitonic-sort failed: unexpected descriptor set layout");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Core::uint64 objectId =
|
|
|
|
|
setLayout.usesPerObject ? visibleGaussianSplat.gameObject->GetID() : 0u;
|
|
|
|
|
const Resources::GaussianSplat* gaussianSplatKey =
|
|
|
|
|
(setLayout.usesGaussianSplatSortDistanceBuffer ||
|
|
|
|
|
setLayout.usesGaussianSplatOrderBuffer)
|
|
|
|
|
? visibleGaussianSplat.gaussianSplat
|
|
|
|
|
: nullptr;
|
|
|
|
|
|
|
|
|
|
CachedDescriptorSet* cachedDescriptorSet = GetOrCreateDynamicDescriptorSet(
|
|
|
|
|
passLayoutKey,
|
|
|
|
|
*passLayout,
|
|
|
|
|
setLayout,
|
|
|
|
|
setIndex,
|
|
|
|
|
objectId,
|
|
|
|
|
visibleGaussianSplat.gaussianSplatRenderer,
|
|
|
|
|
nullptr,
|
|
|
|
|
gaussianSplatKey,
|
|
|
|
|
MaterialConstantPayloadView(),
|
|
|
|
|
*cachedGaussianSplat,
|
|
|
|
|
workingSet->sortDistances.unorderedAccessView,
|
|
|
|
|
workingSet->orderIndices.unorderedAccessView,
|
|
|
|
|
nullptr);
|
|
|
|
|
if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) {
|
|
|
|
|
return fail("BuiltinGaussianSplatPass bitonic-sort 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 bitonic-sort 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((workingSet->sortCapacity + 255u) / 256u, 1u, 1u);
|
|
|
|
|
|
|
|
|
|
if (!TransitionWorkingSetBuffer(commandList, workingSet->sortDistances, RHI::ResourceStates::NonPixelShaderResource) ||
|
|
|
|
|
!TransitionWorkingSetBuffer(commandList, workingSet->orderIndices, RHI::ResourceStates::NonPixelShaderResource)) {
|
|
|
|
|
return fail("BuiltinGaussianSplatPass bitonic-sort failed: resource transition to SRV failed");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-11 03:02:30 +08:00
|
|
|
|
|
|
|
|
return true;
|
2026-04-10 23:11:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
|
|
|
|
|
const RenderContext& context,
|
|
|
|
|
const RenderSurface& surface,
|
|
|
|
|
const RenderSceneData& sceneData,
|
|
|
|
|
const VisibleGaussianSplatItem& visibleGaussianSplat) {
|
2026-04-11 05:37:31 +08:00
|
|
|
auto fail = [](const char* message) -> bool {
|
|
|
|
|
Debug::Logger::Get().Error(Debug::LogCategory::Rendering, message);
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-10 23:11:11 +08:00
|
|
|
if (visibleGaussianSplat.gameObject == nullptr ||
|
|
|
|
|
visibleGaussianSplat.gaussianSplat == nullptr ||
|
|
|
|
|
!visibleGaussianSplat.gaussianSplat->IsValid()) {
|
2026-04-11 05:37:31 +08:00
|
|
|
return fail("BuiltinGaussianSplatPass draw failed: invalid visible gaussian splat item");
|
2026-04-10 23:11:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const RenderResourceCache::CachedGaussianSplat* cachedGaussianSplat =
|
|
|
|
|
m_resourceCache.GetOrCreateGaussianSplat(m_device, visibleGaussianSplat.gaussianSplat);
|
|
|
|
|
if (cachedGaussianSplat == nullptr ||
|
|
|
|
|
cachedGaussianSplat->positions.shaderResourceView == nullptr ||
|
|
|
|
|
cachedGaussianSplat->other.shaderResourceView == nullptr ||
|
|
|
|
|
cachedGaussianSplat->color.shaderResourceView == nullptr) {
|
2026-04-11 05:37:31 +08:00
|
|
|
return fail("BuiltinGaussianSplatPass draw failed: gaussian splat GPU cache is incomplete");
|
2026-04-10 23:11:11 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-11 03:02:30 +08:00
|
|
|
if (m_passResources == nullptr) {
|
2026-04-11 05:37:31 +08:00
|
|
|
return fail("BuiltinGaussianSplatPass draw failed: pass resources were not initialized");
|
2026-04-11 03:02:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Internal::BuiltinGaussianSplatPassResources::WorkingSet* workingSet = nullptr;
|
|
|
|
|
if (!m_passResources->EnsureWorkingSet(m_device, visibleGaussianSplat, workingSet) ||
|
|
|
|
|
workingSet == nullptr ||
|
2026-04-11 05:37:31 +08:00
|
|
|
workingSet->orderIndices.shaderResourceView == nullptr ||
|
|
|
|
|
workingSet->viewData.shaderResourceView == nullptr) {
|
|
|
|
|
return fail("BuiltinGaussianSplatPass draw failed: working set allocation is incomplete");
|
2026-04-11 03:02:30 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-10 23:11:11 +08:00
|
|
|
const Resources::Material* material = ResolveGaussianSplatMaterial(visibleGaussianSplat);
|
|
|
|
|
if (material == nullptr) {
|
2026-04-11 05:37:31 +08:00
|
|
|
return fail("BuiltinGaussianSplatPass draw failed: gaussian splat material could not be resolved");
|
2026-04-10 23:11:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ResolvedShaderPass resolvedShaderPass = ResolveGaussianSplatShaderPass(sceneData, material);
|
|
|
|
|
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
|
2026-04-11 05:37:31 +08:00
|
|
|
return fail("BuiltinGaussianSplatPass draw failed: draw shader pass was not resolved");
|
2026-04-10 23:11:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PassLayoutKey passLayoutKey = {};
|
|
|
|
|
passLayoutKey.shader = resolvedShaderPass.shader;
|
|
|
|
|
passLayoutKey.passName = resolvedShaderPass.passName;
|
|
|
|
|
|
2026-04-11 03:02:30 +08:00
|
|
|
PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(
|
|
|
|
|
context,
|
|
|
|
|
resolvedShaderPass,
|
|
|
|
|
PassLayoutUsage::Draw);
|
2026-04-10 23:11:11 +08:00
|
|
|
RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, surface, sceneData, material);
|
|
|
|
|
if (passLayout == nullptr || pipelineState == nullptr) {
|
2026-04-11 05:37:31 +08:00
|
|
|
return fail("BuiltinGaussianSplatPass draw failed: graphics pipeline setup was not created");
|
2026-04-10 23:11:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const MaterialConstantPayloadView materialConstants = ResolveSchemaMaterialConstantPayload(material);
|
|
|
|
|
if (!materialConstants.IsValid()) {
|
2026-04-11 05:37:31 +08:00
|
|
|
return fail("BuiltinGaussianSplatPass draw failed: material constant payload is invalid");
|
2026-04-10 23:11:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RHI::RHICommandList* commandList = context.commandList;
|
|
|
|
|
commandList->SetPipelineState(pipelineState);
|
|
|
|
|
|
|
|
|
|
const PerObjectConstants perObjectConstants = {
|
|
|
|
|
sceneData.cameraData.projection,
|
|
|
|
|
sceneData.cameraData.view,
|
|
|
|
|
visibleGaussianSplat.localToWorld.Transpose(),
|
2026-04-11 06:32:38 +08:00
|
|
|
visibleGaussianSplat.localToWorld.Inverse(),
|
2026-04-10 23:11:11 +08:00
|
|
|
Math::Vector4(sceneData.cameraData.worldRight, 0.0f),
|
2026-04-11 05:37:31 +08:00
|
|
|
Math::Vector4(sceneData.cameraData.worldUp, 0.0f),
|
2026-04-11 06:32:38 +08:00
|
|
|
Math::Vector4(sceneData.cameraData.worldPosition, 0.0f),
|
2026-04-11 05:37:31 +08:00
|
|
|
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),
|
2026-04-11 06:09:53 +08:00
|
|
|
Math::Vector4(
|
|
|
|
|
static_cast<float>(cachedGaussianSplat->splatCount),
|
|
|
|
|
static_cast<float>(workingSet->sortCapacity),
|
|
|
|
|
0.0f,
|
|
|
|
|
0.0f)
|
2026-04-10 23:11:11 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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()) {
|
2026-04-11 05:37:31 +08:00
|
|
|
return fail("BuiltinGaussianSplatPass draw failed: descriptor set index overflow");
|
2026-04-10 23:11:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const BuiltinPassSetLayoutMetadata& setLayout = passLayout->setLayouts[setIndex];
|
2026-04-11 03:02:30 +08:00
|
|
|
if (setLayout.layout.bindingCount == 0u) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 23:11:11 +08:00
|
|
|
if (!(setLayout.usesPerObject ||
|
|
|
|
|
setLayout.usesMaterial ||
|
2026-04-11 03:02:30 +08:00
|
|
|
setLayout.usesGaussianSplatOrderBuffer ||
|
2026-04-11 05:37:31 +08:00
|
|
|
setLayout.usesGaussianSplatViewDataBuffer)) {
|
|
|
|
|
return fail("BuiltinGaussianSplatPass draw failed: unexpected descriptor set layout");
|
2026-04-10 23:11:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Core::uint64 objectId =
|
|
|
|
|
setLayout.usesPerObject ? visibleGaussianSplat.gameObject->GetID() : 0u;
|
|
|
|
|
const Resources::Material* materialKey =
|
|
|
|
|
setLayout.usesMaterial ? material : nullptr;
|
|
|
|
|
const Resources::GaussianSplat* gaussianSplatKey =
|
2026-04-11 03:02:30 +08:00
|
|
|
(setLayout.usesGaussianSplatOrderBuffer ||
|
2026-04-11 05:37:31 +08:00
|
|
|
setLayout.usesGaussianSplatViewDataBuffer)
|
2026-04-10 23:11:11 +08:00
|
|
|
? visibleGaussianSplat.gaussianSplat
|
|
|
|
|
: nullptr;
|
|
|
|
|
|
|
|
|
|
CachedDescriptorSet* cachedDescriptorSet = GetOrCreateDynamicDescriptorSet(
|
|
|
|
|
passLayoutKey,
|
|
|
|
|
*passLayout,
|
|
|
|
|
setLayout,
|
|
|
|
|
setIndex,
|
|
|
|
|
objectId,
|
2026-04-11 03:02:30 +08:00
|
|
|
visibleGaussianSplat.gaussianSplatRenderer,
|
2026-04-10 23:11:11 +08:00
|
|
|
materialKey,
|
|
|
|
|
gaussianSplatKey,
|
|
|
|
|
materialConstants,
|
2026-04-11 03:02:30 +08:00
|
|
|
*cachedGaussianSplat,
|
|
|
|
|
nullptr,
|
|
|
|
|
workingSet->orderIndices.shaderResourceView,
|
|
|
|
|
workingSet->viewData.shaderResourceView);
|
2026-04-10 23:11:11 +08:00
|
|
|
if (cachedDescriptorSet == nullptr || cachedDescriptorSet->descriptorSet.set == nullptr) {
|
2026-04-11 05:37:31 +08:00
|
|
|
return fail("BuiltinGaussianSplatPass draw failed: dynamic descriptor set resolution failed");
|
2026-04-10 23:11:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RHI::RHIDescriptorSet* descriptorSet = cachedDescriptorSet->descriptorSet.set;
|
|
|
|
|
if (setLayout.usesPerObject) {
|
|
|
|
|
if (!passLayout->perObject.IsValid() || passLayout->perObject.set != setIndex) {
|
2026-04-11 05:37:31 +08:00
|
|
|
return fail("BuiltinGaussianSplatPass draw failed: per-object binding is invalid");
|
2026-04-10 23:11:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
descriptorSet->WriteConstant(
|
|
|
|
|
passLayout->perObject.binding,
|
|
|
|
|
&perObjectConstants,
|
|
|
|
|
sizeof(perObjectConstants));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
descriptorSets[descriptorOffset] = descriptorSet;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 03:02:30 +08:00
|
|
|
BindDescriptorSetRanges(
|
2026-04-10 23:11:11 +08:00
|
|
|
passLayout->firstDescriptorSet,
|
2026-04-11 03:02:30 +08:00
|
|
|
descriptorSets,
|
|
|
|
|
[commandList, passLayout](Core::uint32 firstSet, Core::uint32 count, RHI::RHIDescriptorSet** sets) {
|
|
|
|
|
commandList->SetGraphicsDescriptorSets(
|
|
|
|
|
firstSet,
|
|
|
|
|
count,
|
|
|
|
|
sets,
|
|
|
|
|
passLayout->pipelineLayout);
|
|
|
|
|
});
|
2026-04-10 23:11:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ApplyDynamicRenderState(ResolveEffectiveRenderState(resolvedShaderPass.pass, material), *commandList);
|
|
|
|
|
commandList->Draw(6u, cachedGaussianSplat->splatCount, 0u, 0u);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace Passes
|
|
|
|
|
} // namespace Rendering
|
|
|
|
|
} // namespace XCEngine
|