Add gaussian splat integration baseline

This commit is contained in:
2026-04-11 05:37:31 +08:00
parent 3622bf3aa2
commit 39632e1a04
11 changed files with 6813 additions and 175 deletions

View File

@@ -24,3 +24,4 @@ add_subdirectory(alpha_cutout_scene)
add_subdirectory(volume_scene)
add_subdirectory(volume_occlusion_scene)
add_subdirectory(volume_transform_scene)
add_subdirectory(gaussian_splat_scene)

View File

@@ -0,0 +1,64 @@
cmake_minimum_required(VERSION 3.15)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
project(rendering_integration_gaussian_splat_scene)
set(ENGINE_ROOT_DIR ${CMAKE_SOURCE_DIR}/engine)
set(PACKAGE_DIR ${CMAKE_SOURCE_DIR}/mvs/OpenGL/package)
get_filename_component(PROJECT_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../.. ABSOLUTE)
find_package(Vulkan QUIET)
add_executable(rendering_integration_gaussian_splat_scene
main.cpp
${CMAKE_SOURCE_DIR}/tests/RHI/integration/fixtures/RHIIntegrationFixture.cpp
${PACKAGE_DIR}/src/glad.c
)
target_include_directories(rendering_integration_gaussian_splat_scene PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/tests/RHI/integration/fixtures
${ENGINE_ROOT_DIR}/include
${PACKAGE_DIR}/include
${PROJECT_ROOT_DIR}/engine/src
)
target_link_libraries(rendering_integration_gaussian_splat_scene PRIVATE
d3d12
dxgi
d3dcompiler
winmm
opengl32
XCEngine
GTest::gtest
)
if(TARGET Vulkan::Vulkan)
target_link_libraries(rendering_integration_gaussian_splat_scene PRIVATE Vulkan::Vulkan)
target_compile_definitions(rendering_integration_gaussian_splat_scene PRIVATE XCENGINE_SUPPORT_VULKAN)
endif()
target_compile_definitions(rendering_integration_gaussian_splat_scene PRIVATE
UNICODE
_UNICODE
XCENGINE_SUPPORT_OPENGL
XCENGINE_SUPPORT_D3D12
XCENGINE_TEST_ROOM_PLY_PATH="${CMAKE_SOURCE_DIR}/mvs/3DGS-Unity/room.ply"
)
add_custom_command(TARGET rendering_integration_gaussian_splat_scene POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_SOURCE_DIR}/tests/RHI/integration/compare_ppm.py
$<TARGET_FILE_DIR:rendering_integration_gaussian_splat_scene>/
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_CURRENT_SOURCE_DIR}/GT.ppm
$<TARGET_FILE_DIR:rendering_integration_gaussian_splat_scene>/GT.ppm
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${ENGINE_ROOT_DIR}/third_party/renderdoc/renderdoc.dll
$<TARGET_FILE_DIR:rendering_integration_gaussian_splat_scene>/
)
include(GoogleTest)
gtest_discover_tests(rendering_integration_gaussian_splat_scene)

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,485 @@
#define NOMINMAX
#include <windows.h>
#include <gtest/gtest.h>
#include "../RenderingIntegrationMain.h"
#include "../RenderingIntegrationImageAssert.h"
#include <XCEngine/Components/CameraComponent.h>
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/GaussianSplatRendererComponent.h>
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Math/Color.h>
#include <XCEngine/Core/Math/Vector3.h>
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
#include <XCEngine/Rendering/RenderContext.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Resources/GaussianSplat/GaussianSplat.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Shader/Shader.h>
#include <XCEngine/RHI/RHITexture.h>
#include <XCEngine/Scene/Scene.h>
#include "../../../RHI/integration/fixtures/RHIIntegrationFixture.h"
#include <algorithm>
#include <cstring>
#include <filesystem>
#include <memory>
#include <system_error>
#include <vector>
using namespace XCEngine::Components;
using namespace XCEngine::Math;
using namespace XCEngine::Rendering;
using namespace XCEngine::Resources;
using namespace XCEngine::RHI;
using namespace XCEngine::RHI::Integration;
using namespace RenderingIntegrationTestUtils;
namespace {
constexpr const char* kD3D12Screenshot = "gaussian_splat_scene_d3d12.ppm";
constexpr const char* kOpenGLScreenshot = "gaussian_splat_scene_opengl.ppm";
constexpr const char* kVulkanScreenshot = "gaussian_splat_scene_vulkan.ppm";
constexpr uint32_t kFrameWidth = 1280;
constexpr uint32_t kFrameHeight = 720;
constexpr uint32_t kBaselineSubsetSplatCount = 65536u;
std::filesystem::path GetRoomPlyPath() {
return std::filesystem::path(XCENGINE_TEST_ROOM_PLY_PATH);
}
std::filesystem::path CreateRuntimeProjectRoot() {
return GetExecutableDirectory() /
("__xc_gaussian_splat_scene_runtime_" + std::to_string(static_cast<unsigned long>(::GetCurrentProcessId())));
}
void LinkOrCopyFixture(const std::filesystem::path& sourcePath, const std::filesystem::path& destinationPath) {
std::error_code ec;
std::filesystem::create_directories(destinationPath.parent_path(), ec);
ec.clear();
std::filesystem::create_hard_link(sourcePath, destinationPath, ec);
if (ec) {
ec.clear();
std::filesystem::copy_file(
sourcePath,
destinationPath,
std::filesystem::copy_options::overwrite_existing,
ec);
}
ASSERT_FALSE(ec) << ec.message();
}
const char* GetScreenshotFilename(RHIType backendType) {
switch (backendType) {
case RHIType::D3D12:
return kD3D12Screenshot;
case RHIType::Vulkan:
return kVulkanScreenshot;
case RHIType::OpenGL:
default:
return kOpenGLScreenshot;
}
}
GaussianSplat* CreateGaussianSplatSubset(
const GaussianSplat& source,
uint32_t maxSplatCount,
const char* path) {
const uint32_t sourceSplatCount = source.GetSplatCount();
const uint32_t subsetSplatCount = std::min(sourceSplatCount, maxSplatCount);
if (subsetSplatCount == 0u) {
return nullptr;
}
const GaussianSplatPositionRecord* positions = source.GetPositionRecords();
const GaussianSplatOtherRecord* other = source.GetOtherRecords();
const GaussianSplatColorRecord* colors = source.GetColorRecords();
const GaussianSplatSHRecord* sh = source.GetSHRecords();
const GaussianSplatSection* positionsSection = source.FindSection(GaussianSplatSectionType::Positions);
const GaussianSplatSection* otherSection = source.FindSection(GaussianSplatSectionType::Other);
const GaussianSplatSection* colorSection = source.FindSection(GaussianSplatSectionType::Color);
const GaussianSplatSection* shSection = source.FindSection(GaussianSplatSectionType::SH);
if (positions == nullptr ||
other == nullptr ||
colors == nullptr ||
positionsSection == nullptr ||
otherSection == nullptr ||
colorSection == nullptr) {
return nullptr;
}
std::vector<uint32_t> selectedIndices(subsetSplatCount, 0u);
for (uint32_t subsetIndex = 0u; subsetIndex < subsetSplatCount; ++subsetIndex) {
const uint64_t scaledIndex =
(static_cast<uint64_t>(subsetIndex) * static_cast<uint64_t>(sourceSplatCount)) /
static_cast<uint64_t>(subsetSplatCount);
selectedIndices[subsetIndex] =
static_cast<uint32_t>(std::min<uint64_t>(scaledIndex, static_cast<uint64_t>(sourceSplatCount - 1u)));
}
std::vector<GaussianSplatPositionRecord> subsetPositions(subsetSplatCount);
std::vector<GaussianSplatOtherRecord> subsetOther(subsetSplatCount);
std::vector<GaussianSplatColorRecord> subsetColors(subsetSplatCount);
std::vector<GaussianSplatSHRecord> subsetSh(shSection != nullptr && sh != nullptr ? subsetSplatCount : 0u);
for (uint32_t subsetIndex = 0u; subsetIndex < subsetSplatCount; ++subsetIndex) {
const uint32_t sourceIndex = selectedIndices[subsetIndex];
subsetPositions[subsetIndex] = positions[sourceIndex];
subsetOther[subsetIndex] = other[sourceIndex];
subsetColors[subsetIndex] = colors[sourceIndex];
if (!subsetSh.empty()) {
subsetSh[subsetIndex] = sh[sourceIndex];
}
}
XCEngine::Containers::Array<GaussianSplatSection> sections;
XCEngine::Containers::Array<XCEngine::Core::uint8> payload;
sections.Reserve(shSection != nullptr && sh != nullptr ? 4u : 3u);
size_t payloadOffset = 0u;
auto appendSection = [&](const GaussianSplatSection& sourceSection,
const void* sourceData,
uint32_t elementCount,
uint32_t elementStride) {
GaussianSplatSection section = sourceSection;
section.dataOffset = payloadOffset;
section.elementCount = elementCount;
section.elementStride = elementStride;
section.dataSize = static_cast<XCEngine::Core::uint64>(elementCount) * elementStride;
sections.PushBack(section);
const size_t newPayloadSize = payload.Size() + static_cast<size_t>(section.dataSize);
payload.Resize(newPayloadSize);
std::memcpy(payload.Data() + payloadOffset, sourceData, static_cast<size_t>(section.dataSize));
payloadOffset = newPayloadSize;
};
appendSection(
*positionsSection,
subsetPositions.data(),
subsetSplatCount,
sizeof(GaussianSplatPositionRecord));
appendSection(
*otherSection,
subsetOther.data(),
subsetSplatCount,
sizeof(GaussianSplatOtherRecord));
appendSection(
*colorSection,
subsetColors.data(),
subsetSplatCount,
sizeof(GaussianSplatColorRecord));
if (shSection != nullptr && sh != nullptr) {
appendSection(
*shSection,
subsetSh.data(),
subsetSplatCount,
sizeof(GaussianSplatSHRecord));
}
GaussianSplatMetadata metadata = source.GetMetadata();
metadata.splatCount = subsetSplatCount;
metadata.bounds = source.GetBounds();
metadata.chunkCount = 0u;
metadata.cameraCount = 0u;
metadata.chunkFormat = GaussianSplatSectionFormat::Unknown;
metadata.cameraFormat = GaussianSplatSectionFormat::Unknown;
auto* gaussianSplat = new GaussianSplat();
IResource::ConstructParams params = {};
params.name = "GaussianSplatSceneSubset";
params.path = path;
params.guid = ResourceGUID::Generate(params.path);
gaussianSplat->Initialize(params);
if (!gaussianSplat->CreateOwned(metadata, std::move(sections), std::move(payload))) {
delete gaussianSplat;
return nullptr;
}
return gaussianSplat;
}
class GaussianSplatSceneTest : public RHIIntegrationFixture {
protected:
void SetUp() override;
void TearDown() override;
void RenderFrame() override;
private:
void PrepareRuntimeProject();
void BuildScene();
RHIResourceView* GetCurrentBackBufferView();
std::filesystem::path m_projectRoot;
std::unique_ptr<Scene> m_scene;
std::unique_ptr<SceneRenderer> m_sceneRenderer;
std::vector<RHIResourceView*> m_backBufferViews;
RHITexture* m_depthTexture = nullptr;
RHIResourceView* m_depthView = nullptr;
ResourceHandle<GaussianSplat> m_gaussianSplat;
GaussianSplat* m_subsetGaussianSplat = nullptr;
Material* m_material = nullptr;
};
void GaussianSplatSceneTest::SetUp() {
RHIIntegrationFixture::SetUp();
PrepareRuntimeProject();
m_sceneRenderer = std::make_unique<SceneRenderer>();
m_scene = std::make_unique<Scene>("GaussianSplatScene");
BuildScene();
TextureDesc depthDesc = {};
depthDesc.width = kFrameWidth;
depthDesc.height = kFrameHeight;
depthDesc.depth = 1;
depthDesc.mipLevels = 1;
depthDesc.arraySize = 1;
depthDesc.format = static_cast<uint32_t>(Format::D24_UNorm_S8_UInt);
depthDesc.textureType = static_cast<uint32_t>(XCEngine::RHI::TextureType::Texture2D);
depthDesc.sampleCount = 1;
depthDesc.sampleQuality = 0;
depthDesc.flags = 0;
m_depthTexture = GetDevice()->CreateTexture(depthDesc);
ASSERT_NE(m_depthTexture, nullptr);
ResourceViewDesc depthViewDesc = {};
depthViewDesc.format = static_cast<uint32_t>(Format::D24_UNorm_S8_UInt);
depthViewDesc.dimension = ResourceViewDimension::Texture2D;
depthViewDesc.mipLevel = 0;
m_depthView = GetDevice()->CreateDepthStencilView(m_depthTexture, depthViewDesc);
ASSERT_NE(m_depthView, nullptr);
m_backBufferViews.resize(2, nullptr);
}
void GaussianSplatSceneTest::TearDown() {
m_sceneRenderer.reset();
if (m_depthView != nullptr) {
m_depthView->Shutdown();
delete m_depthView;
m_depthView = nullptr;
}
if (m_depthTexture != nullptr) {
m_depthTexture->Shutdown();
delete m_depthTexture;
m_depthTexture = nullptr;
}
for (RHIResourceView*& backBufferView : m_backBufferViews) {
if (backBufferView != nullptr) {
backBufferView->Shutdown();
delete backBufferView;
backBufferView = nullptr;
}
}
m_backBufferViews.clear();
m_scene.reset();
delete m_material;
m_material = nullptr;
delete m_subsetGaussianSplat;
m_subsetGaussianSplat = nullptr;
m_gaussianSplat.Reset();
ResourceManager& manager = ResourceManager::Get();
manager.UnloadAll();
manager.SetResourceRoot("");
manager.Shutdown();
if (!m_projectRoot.empty()) {
std::error_code ec;
std::filesystem::remove_all(m_projectRoot, ec);
}
RHIIntegrationFixture::TearDown();
}
void GaussianSplatSceneTest::PrepareRuntimeProject() {
const std::filesystem::path roomPlyPath = GetRoomPlyPath();
ASSERT_TRUE(std::filesystem::exists(roomPlyPath)) << roomPlyPath.string();
m_projectRoot = CreateRuntimeProjectRoot();
const std::filesystem::path assetsDir = m_projectRoot / "Assets";
const std::filesystem::path runtimeRoomPath = assetsDir / "room.ply";
std::error_code ec;
std::filesystem::remove_all(m_projectRoot, ec);
std::filesystem::create_directories(assetsDir, ec);
ASSERT_FALSE(ec) << ec.message();
LinkOrCopyFixture(roomPlyPath, runtimeRoomPath);
ResourceManager& manager = ResourceManager::Get();
manager.Initialize();
manager.SetResourceRoot(m_projectRoot.string().c_str());
m_gaussianSplat = manager.Load<GaussianSplat>("Assets/room.ply");
ASSERT_TRUE(m_gaussianSplat.IsValid());
ASSERT_NE(m_gaussianSplat.Get(), nullptr);
ASSERT_TRUE(m_gaussianSplat->IsValid());
ASSERT_GT(m_gaussianSplat->GetSplatCount(), 0u);
m_subsetGaussianSplat = CreateGaussianSplatSubset(
*m_gaussianSplat.Get(),
kBaselineSubsetSplatCount,
"Tests/Rendering/GaussianSplatScene/RoomSubset.xcgsplat");
ASSERT_NE(m_subsetGaussianSplat, nullptr);
ASSERT_TRUE(m_subsetGaussianSplat->IsValid());
ASSERT_GT(m_subsetGaussianSplat->GetSplatCount(), 0u);
}
void GaussianSplatSceneTest::BuildScene() {
ASSERT_NE(m_scene, nullptr);
ASSERT_NE(m_subsetGaussianSplat, nullptr);
m_material = new Material();
IResource::ConstructParams params = {};
params.name = "GaussianSplatSceneMaterial";
params.path = "Tests/Rendering/GaussianSplatScene/Room.material";
params.guid = ResourceGUID::Generate(params.path);
m_material->Initialize(params);
m_material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinGaussianSplatShaderPath()));
m_material->SetRenderQueue(MaterialRenderQueue::Transparent);
m_material->SetFloat("_PointScale", 1.0f);
m_material->SetFloat("_OpacityScale", 1.0f);
GameObject* cameraObject = m_scene->CreateGameObject("MainCamera");
auto* camera = cameraObject->AddComponent<CameraComponent>();
camera->SetPrimary(true);
camera->SetFieldOfView(45.0f);
camera->SetNearClipPlane(0.1f);
camera->SetFarClipPlane(500.0f);
camera->SetClearColor(XCEngine::Math::Color(0.02f, 0.025f, 0.035f, 1.0f));
const Bounds& bounds = m_subsetGaussianSplat->GetBounds();
const Vector3 boundsMin = bounds.GetMin();
const Vector3 boundsMax = bounds.GetMax();
const Vector3 center(
(boundsMin.x + boundsMax.x) * 0.5f,
(boundsMin.y + boundsMax.y) * 0.5f,
(boundsMin.z + boundsMax.z) * 0.5f);
const float sizeX = std::max(boundsMax.x - boundsMin.x, 0.001f);
const float sizeY = std::max(boundsMax.y - boundsMin.y, 0.001f);
const float sizeZ = std::max(boundsMax.z - boundsMin.z, 0.001f);
const float maxExtent = std::max(sizeX, std::max(sizeY, sizeZ));
const float uniformScale = 3.0f / maxExtent;
GameObject* root = m_scene->CreateGameObject("GaussianSplatRoot");
root->GetTransform()->SetLocalPosition(Vector3(0.0f, -0.35f, 3.2f));
root->GetTransform()->SetLocalScale(Vector3(uniformScale, uniformScale, uniformScale));
GameObject* splatObject = m_scene->CreateGameObject("RoomGaussianSplat");
splatObject->SetParent(root, false);
splatObject->GetTransform()->SetLocalPosition(Vector3(-center.x, -center.y, -center.z));
auto* splatRenderer = splatObject->AddComponent<GaussianSplatRendererComponent>();
splatRenderer->SetGaussianSplat(m_subsetGaussianSplat);
splatRenderer->SetMaterial(m_material);
splatRenderer->SetCastShadows(false);
splatRenderer->SetReceiveShadows(false);
}
RHIResourceView* GaussianSplatSceneTest::GetCurrentBackBufferView() {
const int backBufferIndex = GetCurrentBackBufferIndex();
if (backBufferIndex < 0) {
return nullptr;
}
if (static_cast<size_t>(backBufferIndex) >= m_backBufferViews.size()) {
m_backBufferViews.resize(static_cast<size_t>(backBufferIndex) + 1, nullptr);
}
if (m_backBufferViews[backBufferIndex] == nullptr) {
ResourceViewDesc viewDesc = {};
viewDesc.format = static_cast<uint32_t>(Format::R8G8B8A8_UNorm);
viewDesc.dimension = ResourceViewDimension::Texture2D;
viewDesc.mipLevel = 0;
m_backBufferViews[backBufferIndex] =
GetDevice()->CreateRenderTargetView(GetCurrentBackBuffer(), viewDesc);
}
return m_backBufferViews[backBufferIndex];
}
void GaussianSplatSceneTest::RenderFrame() {
ASSERT_NE(m_scene, nullptr);
ASSERT_NE(m_sceneRenderer, nullptr);
RHICommandList* commandList = GetCommandList();
ASSERT_NE(commandList, nullptr);
commandList->Reset();
RenderSurface surface(kFrameWidth, kFrameHeight);
surface.SetColorAttachment(GetCurrentBackBufferView());
surface.SetDepthAttachment(m_depthView);
RenderContext renderContext = {};
renderContext.device = GetDevice();
renderContext.commandList = commandList;
renderContext.commandQueue = GetCommandQueue();
renderContext.backendType = GetBackendType();
ASSERT_TRUE(m_sceneRenderer->Render(*m_scene, nullptr, renderContext, surface));
commandList->Close();
void* commandLists[] = { commandList };
GetCommandQueue()->ExecuteCommandLists(1, commandLists);
}
TEST_P(GaussianSplatSceneTest, RenderRoomGaussianSplatScene) {
RHICommandQueue* commandQueue = GetCommandQueue();
RHISwapChain* swapChain = GetSwapChain();
ASSERT_NE(commandQueue, nullptr);
ASSERT_NE(swapChain, nullptr);
constexpr int kTargetFrameCount = 1;
const char* screenshotFilename = GetScreenshotFilename(GetBackendType());
for (int frameCount = 0; frameCount <= kTargetFrameCount; ++frameCount) {
if (frameCount > 0) {
commandQueue->WaitForPreviousFrame();
}
BeginRender();
RenderFrame();
if (frameCount >= kTargetFrameCount) {
commandQueue->WaitForIdle();
ASSERT_TRUE(TakeScreenshot(screenshotFilename));
const std::filesystem::path gtPath = ResolveRuntimePath("GT.ppm");
if (!std::filesystem::exists(gtPath)) {
GTEST_SKIP() << "GT.ppm missing, screenshot captured for baseline generation: " << screenshotFilename;
}
ASSERT_TRUE(CompareWithGoldenTemplate(screenshotFilename, "GT.ppm", 8.0f));
break;
}
swapChain->Present(0, 0);
}
}
} // namespace
INSTANTIATE_TEST_SUITE_P(D3D12, GaussianSplatSceneTest, ::testing::Values(RHIType::D3D12));
#if defined(XCENGINE_SUPPORT_OPENGL)
INSTANTIATE_TEST_SUITE_P(OpenGL, GaussianSplatSceneTest, ::testing::Values(RHIType::OpenGL));
#endif
#if defined(XCENGINE_SUPPORT_VULKAN)
INSTANTIATE_TEST_SUITE_P(Vulkan, GaussianSplatSceneTest, ::testing::Values(RHIType::Vulkan));
#endif
GTEST_API_ int main(int argc, char** argv) {
return RunRenderingIntegrationTestMain(argc, argv);
}

View File

@@ -14,6 +14,7 @@
#include <XCEngine/Resources/Shader/Shader.h>
#include <XCEngine/Resources/Shader/ShaderLoader.h>
#include <XCEngine/RHI/RHIEnums.h>
#include <XCEngine/RHI/OpenGL/OpenGLPipelineLayout.h>
#include <XCEngine/RHI/ShaderCompiler/SpirvShaderCompiler.h>
#include <fstream>
@@ -885,7 +886,7 @@ TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatShaderUsesAuthoringContrac
const ShaderPass* pass = shader->FindPass("GaussianSplat");
ASSERT_NE(pass, nullptr);
EXPECT_EQ(pass->resources.Size(), 6u);
EXPECT_EQ(pass->resources.Size(), 4u);
EXPECT_TRUE(pass->hasFixedFunctionState);
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::None);
EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable);
@@ -910,35 +911,15 @@ TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatShaderUsesAuthoringContrac
ResolveBuiltinPassResourceSemantic(*order),
BuiltinPassResourceSemantic::GaussianSplatOrderBuffer);
const ShaderResourceBindingDesc* positions =
shader->FindPassResourceBinding("GaussianSplat", "GaussianSplatPositions");
ASSERT_NE(positions, nullptr);
EXPECT_EQ(positions->type, ShaderResourceType::StructuredBuffer);
EXPECT_EQ(positions->set, 2u);
EXPECT_EQ(positions->binding, 1u);
const ShaderResourceBindingDesc* viewData =
shader->FindPassResourceBinding("GaussianSplat", "GaussianSplatViewDataBuffer");
ASSERT_NE(viewData, nullptr);
EXPECT_EQ(viewData->type, ShaderResourceType::StructuredBuffer);
EXPECT_EQ(viewData->set, 2u);
EXPECT_EQ(viewData->binding, 1u);
EXPECT_EQ(
ResolveBuiltinPassResourceSemantic(*positions),
BuiltinPassResourceSemantic::GaussianSplatPositionBuffer);
const ShaderResourceBindingDesc* other =
shader->FindPassResourceBinding("GaussianSplat", "GaussianSplatOther");
ASSERT_NE(other, nullptr);
EXPECT_EQ(other->type, ShaderResourceType::StructuredBuffer);
EXPECT_EQ(other->set, 2u);
EXPECT_EQ(other->binding, 2u);
EXPECT_EQ(
ResolveBuiltinPassResourceSemantic(*other),
BuiltinPassResourceSemantic::GaussianSplatOtherBuffer);
const ShaderResourceBindingDesc* color =
shader->FindPassResourceBinding("GaussianSplat", "GaussianSplatColor");
ASSERT_NE(color, nullptr);
EXPECT_EQ(color->type, ShaderResourceType::StructuredBuffer);
EXPECT_EQ(color->set, 2u);
EXPECT_EQ(color->binding, 3u);
EXPECT_EQ(
ResolveBuiltinPassResourceSemantic(*color),
BuiltinPassResourceSemantic::GaussianSplatColorBuffer);
ResolveBuiltinPassResourceSemantic(*viewData),
BuiltinPassResourceSemantic::GaussianSplatViewDataBuffer);
delete shader;
}
@@ -958,24 +939,21 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoaded
BuiltinPassResourceBindingPlan plan = {};
String error;
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
ASSERT_EQ(plan.bindings.Size(), 6u);
ASSERT_EQ(plan.bindings.Size(), 4u);
EXPECT_TRUE(plan.perObject.IsValid());
EXPECT_TRUE(plan.material.IsValid());
EXPECT_TRUE(plan.gaussianSplatOrderBuffer.IsValid());
EXPECT_TRUE(plan.gaussianSplatPositionBuffer.IsValid());
EXPECT_TRUE(plan.gaussianSplatOtherBuffer.IsValid());
EXPECT_TRUE(plan.gaussianSplatColorBuffer.IsValid());
EXPECT_TRUE(plan.gaussianSplatViewDataBuffer.IsValid());
EXPECT_FALSE(plan.gaussianSplatPositionBuffer.IsValid());
EXPECT_FALSE(plan.gaussianSplatOtherBuffer.IsValid());
EXPECT_FALSE(plan.gaussianSplatColorBuffer.IsValid());
EXPECT_FALSE(plan.gaussianSplatSHBuffer.IsValid());
EXPECT_EQ(plan.perObject.set, 0u);
EXPECT_EQ(plan.material.set, 1u);
EXPECT_EQ(plan.gaussianSplatOrderBuffer.set, 2u);
EXPECT_EQ(plan.gaussianSplatOrderBuffer.binding, 0u);
EXPECT_EQ(plan.gaussianSplatPositionBuffer.set, 2u);
EXPECT_EQ(plan.gaussianSplatPositionBuffer.binding, 1u);
EXPECT_EQ(plan.gaussianSplatOtherBuffer.set, 2u);
EXPECT_EQ(plan.gaussianSplatOtherBuffer.binding, 2u);
EXPECT_EQ(plan.gaussianSplatColorBuffer.set, 2u);
EXPECT_EQ(plan.gaussianSplatColorBuffer.binding, 3u);
EXPECT_EQ(plan.gaussianSplatViewDataBuffer.set, 2u);
EXPECT_EQ(plan.gaussianSplatViewDataBuffer.binding, 1u);
EXPECT_EQ(plan.firstDescriptorSet, 0u);
EXPECT_EQ(plan.descriptorSetCount, 3u);
@@ -985,34 +963,23 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoaded
EXPECT_TRUE(setLayouts[0].usesPerObject);
EXPECT_TRUE(setLayouts[1].usesMaterial);
EXPECT_TRUE(setLayouts[2].usesGaussianSplatOrderBuffer);
EXPECT_TRUE(setLayouts[2].usesGaussianSplatPositionBuffer);
EXPECT_TRUE(setLayouts[2].usesGaussianSplatOtherBuffer);
EXPECT_TRUE(setLayouts[2].usesGaussianSplatColorBuffer);
ASSERT_EQ(setLayouts[2].bindings.size(), 4u);
EXPECT_TRUE(setLayouts[2].usesGaussianSplatViewDataBuffer);
EXPECT_FALSE(setLayouts[2].usesGaussianSplatPositionBuffer);
EXPECT_FALSE(setLayouts[2].usesGaussianSplatOtherBuffer);
EXPECT_FALSE(setLayouts[2].usesGaussianSplatColorBuffer);
ASSERT_EQ(setLayouts[2].bindings.size(), 2u);
EXPECT_EQ(
static_cast<DescriptorType>(setLayouts[2].bindings[0].type),
DescriptorType::SRV);
EXPECT_EQ(
static_cast<DescriptorType>(setLayouts[2].bindings[1].type),
DescriptorType::SRV);
EXPECT_EQ(
static_cast<DescriptorType>(setLayouts[2].bindings[2].type),
DescriptorType::SRV);
EXPECT_EQ(
static_cast<DescriptorType>(setLayouts[2].bindings[3].type),
DescriptorType::SRV);
EXPECT_EQ(
setLayouts[2].bindings[0].resourceDimension,
ResourceViewDimension::StructuredBuffer);
EXPECT_EQ(
setLayouts[2].bindings[1].resourceDimension,
ResourceViewDimension::StructuredBuffer);
EXPECT_EQ(
setLayouts[2].bindings[2].resourceDimension,
ResourceViewDimension::StructuredBuffer);
EXPECT_EQ(
setLayouts[2].bindings[3].resourceDimension,
ResourceViewDimension::StructuredBuffer);
delete shader;
}
@@ -1052,22 +1019,16 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringGauss
d3d12Source,
"cbuffer MaterialConstants",
"register(b1)"));
EXPECT_NE(d3d12Source.find("float4 gPointScaleParams;"), std::string::npos);
EXPECT_NE(d3d12Source.find("float4 gOpacityScaleParams;"), std::string::npos);
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"StructuredBuffer<uint> GaussianSplatOrderBuffer",
"register(t0)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"StructuredBuffer<float3> GaussianSplatPositions",
"StructuredBuffer<GaussianSplatViewData> GaussianSplatViewDataBuffer",
"register(t1)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther",
"register(t2)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"StructuredBuffer<float4> GaussianSplatColor",
"register(t3)"));
EXPECT_EQ(d3d12Source.find("space0"), std::string::npos);
EXPECT_EQ(d3d12Source.find("space1"), std::string::npos);
EXPECT_EQ(d3d12Source.find("space2"), std::string::npos);
@@ -1095,22 +1056,16 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringGauss
vulkanSource,
"cbuffer MaterialConstants",
"register(b0, space1)"));
EXPECT_NE(vulkanSource.find("float4 gPointScaleParams;"), std::string::npos);
EXPECT_NE(vulkanSource.find("float4 gOpacityScaleParams;"), std::string::npos);
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"StructuredBuffer<uint> GaussianSplatOrderBuffer",
"register(t0, space2)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"StructuredBuffer<float3> GaussianSplatPositions",
"StructuredBuffer<GaussianSplatViewData> GaussianSplatViewDataBuffer",
"register(t1, space2)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther",
"register(t2, space2)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"StructuredBuffer<float4> GaussianSplatColor",
"register(t3, space2)"));
delete shader;
}
@@ -1126,7 +1081,7 @@ TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatUtilitiesShaderUsesCompute
const ShaderPass* pass = shader->FindPass("GaussianSplatPrepareOrder");
ASSERT_NE(pass, nullptr);
EXPECT_EQ(pass->resources.Size(), 4u);
EXPECT_EQ(pass->resources.Size(), 7u);
const ShaderResourceBindingDesc* perObject =
shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "PerObjectConstants");
@@ -1148,6 +1103,26 @@ TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatUtilitiesShaderUsesCompute
ResolveBuiltinPassResourceSemantic(*positions),
BuiltinPassResourceSemantic::GaussianSplatPositionBuffer);
const ShaderResourceBindingDesc* other =
shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "GaussianSplatOther");
ASSERT_NE(other, nullptr);
EXPECT_EQ(other->type, ShaderResourceType::StructuredBuffer);
EXPECT_EQ(other->set, 2u);
EXPECT_EQ(other->binding, 1u);
EXPECT_EQ(
ResolveBuiltinPassResourceSemantic(*other),
BuiltinPassResourceSemantic::GaussianSplatOtherBuffer);
const ShaderResourceBindingDesc* color =
shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "GaussianSplatColor");
ASSERT_NE(color, nullptr);
EXPECT_EQ(color->type, ShaderResourceType::StructuredBuffer);
EXPECT_EQ(color->set, 2u);
EXPECT_EQ(color->binding, 2u);
EXPECT_EQ(
ResolveBuiltinPassResourceSemantic(*color),
BuiltinPassResourceSemantic::GaussianSplatColorBuffer);
const ShaderResourceBindingDesc* sortDistances =
shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "GaussianSplatSortDistances");
ASSERT_NE(sortDistances, nullptr);
@@ -1168,6 +1143,16 @@ TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatUtilitiesShaderUsesCompute
ResolveBuiltinPassResourceSemantic(*orderBuffer),
BuiltinPassResourceSemantic::GaussianSplatOrderBuffer);
const ShaderResourceBindingDesc* viewData =
shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "GaussianSplatViewDataBuffer");
ASSERT_NE(viewData, nullptr);
EXPECT_EQ(viewData->type, ShaderResourceType::RWStructuredBuffer);
EXPECT_EQ(viewData->set, 4u);
EXPECT_EQ(viewData->binding, 2u);
EXPECT_EQ(
ResolveBuiltinPassResourceSemantic(*viewData),
BuiltinPassResourceSemantic::GaussianSplatViewDataBuffer);
const ShaderStageVariant* computeVariant = shader->FindVariant(
"GaussianSplatPrepareOrder",
XCEngine::Resources::ShaderType::Compute,
@@ -1193,17 +1178,26 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoaded
BuiltinPassResourceBindingPlan plan = {};
String error;
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
ASSERT_EQ(plan.bindings.Size(), 4u);
ASSERT_EQ(plan.bindings.Size(), 7u);
EXPECT_TRUE(plan.perObject.IsValid());
EXPECT_TRUE(plan.gaussianSplatPositionBuffer.IsValid());
EXPECT_TRUE(plan.gaussianSplatOtherBuffer.IsValid());
EXPECT_TRUE(plan.gaussianSplatColorBuffer.IsValid());
EXPECT_TRUE(plan.gaussianSplatSortDistanceBuffer.IsValid());
EXPECT_TRUE(plan.gaussianSplatOrderBuffer.IsValid());
EXPECT_TRUE(plan.gaussianSplatViewDataBuffer.IsValid());
EXPECT_EQ(plan.perObject.set, 0u);
EXPECT_EQ(plan.gaussianSplatPositionBuffer.set, 2u);
EXPECT_EQ(plan.gaussianSplatOtherBuffer.set, 2u);
EXPECT_EQ(plan.gaussianSplatOtherBuffer.binding, 1u);
EXPECT_EQ(plan.gaussianSplatColorBuffer.set, 2u);
EXPECT_EQ(plan.gaussianSplatColorBuffer.binding, 2u);
EXPECT_EQ(plan.gaussianSplatSortDistanceBuffer.set, 4u);
EXPECT_EQ(plan.gaussianSplatSortDistanceBuffer.binding, 0u);
EXPECT_EQ(plan.gaussianSplatOrderBuffer.set, 4u);
EXPECT_EQ(plan.gaussianSplatOrderBuffer.binding, 1u);
EXPECT_EQ(plan.gaussianSplatViewDataBuffer.set, 4u);
EXPECT_EQ(plan.gaussianSplatViewDataBuffer.binding, 2u);
EXPECT_EQ(plan.firstDescriptorSet, 0u);
EXPECT_EQ(plan.descriptorSetCount, 5u);
@@ -1212,25 +1206,98 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoaded
ASSERT_EQ(setLayouts.size(), 5u);
EXPECT_TRUE(setLayouts[0].usesPerObject);
EXPECT_TRUE(setLayouts[2].usesGaussianSplatPositionBuffer);
EXPECT_TRUE(setLayouts[2].usesGaussianSplatOtherBuffer);
EXPECT_TRUE(setLayouts[2].usesGaussianSplatColorBuffer);
EXPECT_TRUE(setLayouts[4].usesGaussianSplatSortDistanceBuffer);
EXPECT_TRUE(setLayouts[4].usesGaussianSplatOrderBuffer);
ASSERT_EQ(setLayouts[4].bindings.size(), 2u);
EXPECT_TRUE(setLayouts[4].usesGaussianSplatViewDataBuffer);
ASSERT_EQ(setLayouts[2].bindings.size(), 3u);
ASSERT_EQ(setLayouts[4].bindings.size(), 3u);
EXPECT_EQ(
static_cast<DescriptorType>(setLayouts[2].bindings[0].type),
DescriptorType::SRV);
EXPECT_EQ(
static_cast<DescriptorType>(setLayouts[2].bindings[1].type),
DescriptorType::SRV);
EXPECT_EQ(
static_cast<DescriptorType>(setLayouts[2].bindings[2].type),
DescriptorType::SRV);
EXPECT_EQ(
static_cast<DescriptorType>(setLayouts[4].bindings[0].type),
DescriptorType::UAV);
EXPECT_EQ(
static_cast<DescriptorType>(setLayouts[4].bindings[1].type),
DescriptorType::UAV);
EXPECT_EQ(
static_cast<DescriptorType>(setLayouts[4].bindings[2].type),
DescriptorType::UAV);
EXPECT_EQ(
setLayouts[2].bindings[0].resourceDimension,
ResourceViewDimension::StructuredBuffer);
EXPECT_EQ(
setLayouts[2].bindings[1].resourceDimension,
ResourceViewDimension::StructuredBuffer);
EXPECT_EQ(
setLayouts[2].bindings[2].resourceDimension,
ResourceViewDimension::StructuredBuffer);
EXPECT_EQ(
setLayouts[4].bindings[0].resourceDimension,
ResourceViewDimension::StructuredBuffer);
EXPECT_EQ(
setLayouts[4].bindings[1].resourceDimension,
ResourceViewDimension::StructuredBuffer);
EXPECT_EQ(
setLayouts[4].bindings[2].resourceDimension,
ResourceViewDimension::StructuredBuffer);
delete shader;
}
TEST(BuiltinForwardPipeline_Test, OpenGLPipelineLayoutUsesUnifiedStorageBufferBindingsForGaussianSplatUtilities) {
ShaderLoader loader;
LoadResult result = loader.Load(GetBuiltinGaussianSplatUtilitiesShaderPath());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
Shader* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
const ShaderPass* pass = shader->FindPass("GaussianSplatPrepareOrder");
ASSERT_NE(pass, nullptr);
BuiltinPassResourceBindingPlan plan = {};
String error;
ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr();
ASSERT_EQ(setLayouts.size(), 5u);
RHIPipelineLayoutDesc layoutDesc = {};
layoutDesc.setLayoutCount = static_cast<uint32_t>(setLayouts.size());
std::vector<DescriptorSetLayoutDesc> layoutViews(setLayouts.size());
for (size_t setIndex = 0; setIndex < setLayouts.size(); ++setIndex) {
layoutViews[setIndex].bindingCount = static_cast<uint32_t>(setLayouts[setIndex].bindings.size());
layoutViews[setIndex].bindings =
setLayouts[setIndex].bindings.empty() ? nullptr : setLayouts[setIndex].bindings.data();
}
layoutDesc.setLayouts = layoutViews.data();
OpenGLPipelineLayout pipelineLayout;
ASSERT_TRUE(pipelineLayout.Initialize(layoutDesc));
EXPECT_EQ(pipelineLayout.GetConstantBufferBindingPoint(0u, 0u), 0u);
EXPECT_EQ(pipelineLayout.GetShaderResourceBindingPoint(2u, 0u), 0u);
EXPECT_EQ(pipelineLayout.GetShaderResourceBindingPoint(2u, 1u), 1u);
EXPECT_EQ(pipelineLayout.GetShaderResourceBindingPoint(2u, 2u), 2u);
EXPECT_EQ(pipelineLayout.GetUnorderedAccessBindingPoint(4u, 0u), 3u);
EXPECT_EQ(pipelineLayout.GetUnorderedAccessBindingPoint(4u, 1u), 4u);
EXPECT_EQ(pipelineLayout.GetUnorderedAccessBindingPoint(4u, 2u), 5u);
pipelineLayout.Shutdown();
delete shader;
}
TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesGaussianSplatUtilitiesComputeBindingsToDescriptorSpaces) {
ShaderLoader loader;
LoadResult result = loader.Load(GetBuiltinGaussianSplatUtilitiesShaderPath());
@@ -1264,8 +1331,16 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesGaussianSplatU
"register(b0)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"StructuredBuffer<float3> GaussianSplatPositions",
"StructuredBuffer<float4> GaussianSplatPositions",
"register(t0)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther",
"register(t1)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"StructuredBuffer<float4> GaussianSplatColor",
"register(t2)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"RWStructuredBuffer<uint> GaussianSplatSortDistances",
@@ -1274,6 +1349,10 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesGaussianSplatU
d3d12Source,
"RWStructuredBuffer<uint> GaussianSplatOrderBuffer",
"register(u1)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"RWStructuredBuffer<GaussianSplatViewData> GaussianSplatViewDataBuffer",
"register(u2)"));
const ShaderStageVariant* vulkanCompute = shader->FindVariant(
"GaussianSplatPrepareOrder",
@@ -1296,8 +1375,16 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesGaussianSplatU
"register(b0, space0)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"StructuredBuffer<float3> GaussianSplatPositions",
"StructuredBuffer<float4> GaussianSplatPositions",
"register(t0, space2)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther",
"register(t1, space2)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"StructuredBuffer<float4> GaussianSplatColor",
"register(t2, space2)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"RWStructuredBuffer<uint> GaussianSplatSortDistances",
@@ -1306,6 +1393,72 @@ TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesGaussianSplatU
vulkanSource,
"RWStructuredBuffer<uint> GaussianSplatOrderBuffer",
"register(u1, space4)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"RWStructuredBuffer<GaussianSplatViewData> GaussianSplatViewDataBuffer",
"register(u2, space4)"));
delete shader;
}
TEST(BuiltinForwardPipeline_Test, OpenGLRuntimeTranspilesGaussianSplatUtilitiesComputeVariant) {
ShaderLoader loader;
LoadResult result = loader.Load(GetBuiltinGaussianSplatUtilitiesShaderPath());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
Shader* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
const ShaderPass* pass = shader->FindPass("GaussianSplatPrepareOrder");
ASSERT_NE(pass, nullptr);
const ShaderStageVariant* openGLCompute = shader->FindVariant(
"GaussianSplatPrepareOrder",
XCEngine::Resources::ShaderType::Compute,
XCEngine::Resources::ShaderBackend::OpenGL);
ASSERT_NE(openGLCompute, nullptr);
ShaderCompileDesc compileDesc = {};
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*pass,
XCEngine::Resources::ShaderBackend::OpenGL,
*openGLCompute,
compileDesc);
XCEngine::RHI::CompiledSpirvShader spirvShader = {};
std::string errorMessage;
ASSERT_TRUE(
XCEngine::RHI::CompileSpirvShader(
compileDesc,
XCEngine::RHI::SpirvTargetEnvironment::Vulkan,
spirvShader,
&errorMessage))
<< errorMessage;
std::string glslSource;
ASSERT_TRUE(XCEngine::RHI::TranspileSpirvToOpenGLGLSL(spirvShader, glslSource, &errorMessage))
<< errorMessage;
ASSERT_FALSE(glslSource.empty());
EXPECT_NE(
glslSource.find("layout(binding = 0, std430) readonly buffer type_StructuredBuffer_v4float"),
std::string::npos);
EXPECT_NE(
glslSource.find("layout(binding = 1, std430) readonly buffer type_StructuredBuffer_GaussianSplatOtherData"),
std::string::npos);
EXPECT_NE(
glslSource.find("layout(binding = 2, std430) readonly buffer GaussianSplatColor"),
std::string::npos);
EXPECT_NE(
glslSource.find("layout(binding = 3, std430) buffer type_RWStructuredBuffer_uint"),
std::string::npos);
EXPECT_NE(
glslSource.find("layout(binding = 4, std430) buffer GaussianSplatOrderBuffer"),
std::string::npos);
EXPECT_NE(
glslSource.find("layout(binding = 5, std430) buffer type_RWStructuredBuffer_GaussianSplatViewData"),
std::string::npos);
delete shader;
}