From 92d5cc61cf8e64103ef6fdf9a5aad0f2cf238e98 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sat, 11 Apr 2026 07:07:21 +0800 Subject: [PATCH] Define gaussian splat chunk data contract --- .../Resources/GaussianSplat/GaussianSplat.h | 20 ++++++ .../unit/test_render_resource_cache.cpp | 72 ++++++++++++++----- .../test_gaussian_splat_loader.cpp | 36 +++++++++- 3 files changed, 108 insertions(+), 20 deletions(-) diff --git a/engine/include/XCEngine/Resources/GaussianSplat/GaussianSplat.h b/engine/include/XCEngine/Resources/GaussianSplat/GaussianSplat.h index cf5c504f..07c7f532 100644 --- a/engine/include/XCEngine/Resources/GaussianSplat/GaussianSplat.h +++ b/engine/include/XCEngine/Resources/GaussianSplat/GaussianSplat.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +46,7 @@ enum class GaussianSplatSectionFormat : Core::uint32 { constexpr Core::uint32 kGaussianSplatSHCoefficientCount = 45; constexpr Core::uint32 kGaussianSplatSHColorChannelCount = 3; constexpr Core::uint32 kGaussianSplatMaxSHOrder = 3; +constexpr Core::uint32 kGaussianSplatChunkSize = 256; Core::uint32 ResolveGaussianSplatSHOrderFromCoefficientCount(Core::uint32 coefficientCount); Core::uint32 ResolveGaussianSplatSHOrderFromSectionStride(Core::uint32 elementStride); @@ -67,6 +69,24 @@ struct GaussianSplatSHRecord { float coefficients[kGaussianSplatSHCoefficientCount] = {}; }; +struct GaussianSplatChunkRecord { + Core::uint32 colR = 0u; + Core::uint32 colG = 0u; + Core::uint32 colB = 0u; + Core::uint32 colA = 0u; + Math::Vector2 posX = Math::Vector2::Zero(); + Math::Vector2 posY = Math::Vector2::Zero(); + Math::Vector2 posZ = Math::Vector2::Zero(); + Core::uint32 sclX = 0u; + Core::uint32 sclY = 0u; + Core::uint32 sclZ = 0u; + Core::uint32 shR = 0u; + Core::uint32 shG = 0u; + Core::uint32 shB = 0u; +}; + +static_assert(sizeof(GaussianSplatChunkRecord) == 64u, "GaussianSplatChunkRecord layout must stay Unity-compatible"); + struct GaussianSplatSection { GaussianSplatSectionType type = GaussianSplatSectionType::Unknown; GaussianSplatSectionFormat format = GaussianSplatSectionFormat::Unknown; diff --git a/tests/Rendering/unit/test_render_resource_cache.cpp b/tests/Rendering/unit/test_render_resource_cache.cpp index 2c35d3ed..4512b813 100644 --- a/tests/Rendering/unit/test_render_resource_cache.cpp +++ b/tests/Rendering/unit/test_render_resource_cache.cpp @@ -295,16 +295,36 @@ SampleArtifactData BuildSampleArtifactData() { sh[1].coefficients[index] = -0.02f * static_cast(index + 1u); } + const GaussianSplatChunkRecord chunks[1] = { + { + 0x00010002u, + 0x00030004u, + 0x00050006u, + 0x00070008u, + Vector2(-1.0f, 2.0f), + Vector2(-3.0f, 4.0f), + Vector2(-5.0f, 6.0f), + 0x0009000Au, + 0x000B000Cu, + 0x000D000Eu, + 0x000F0010u, + 0x00110012u, + 0x00130014u + } + }; + SampleArtifactData sample; sample.metadata.contentVersion = 1u; sample.metadata.splatCount = 2u; + sample.metadata.chunkCount = 1u; sample.metadata.bounds.SetMinMax(Vector3(-2.0f, -1.0f, -3.0f), Vector3(5.0f, 4.0f, 6.0f)); sample.metadata.positionFormat = GaussianSplatSectionFormat::VectorFloat32; sample.metadata.otherFormat = GaussianSplatSectionFormat::OtherFloat32; sample.metadata.colorFormat = GaussianSplatSectionFormat::ColorRGBA32F; sample.metadata.shFormat = GaussianSplatSectionFormat::SHFloat32; + sample.metadata.chunkFormat = GaussianSplatSectionFormat::ChunkFloat32; - sample.sections.Reserve(4u); + sample.sections.Reserve(5u); size_t payloadOffset = 0u; auto appendSection = [&](GaussianSplatSectionType type, GaussianSplatSectionFormat format, @@ -355,6 +375,13 @@ SampleArtifactData BuildSampleArtifactData() { sizeof(sh), 2u, sizeof(GaussianSplatSHRecord)); + appendSection( + GaussianSplatSectionType::Chunks, + GaussianSplatSectionFormat::ChunkFloat32, + chunks, + sizeof(chunks), + 1u, + sizeof(GaussianSplatChunkRecord)); return sample; } @@ -392,41 +419,49 @@ TEST(RenderResourceCacheTest, GetOrCreateGaussianSplatUploadsStructuredSectionsA EXPECT_EQ(cached->residencyState, RenderResourceCache::GaussianSplatResidencyState::GpuReady); EXPECT_EQ(cached->contentVersion, 1u); EXPECT_EQ(cached->splatCount, 2u); - EXPECT_EQ(cached->chunkCount, 0u); - EXPECT_EQ(cached->positions.elementStride, sizeof(GaussianSplatPositionRecord)); + EXPECT_EQ(cached->chunkCount, 1u); + EXPECT_EQ(cached->positions.elementStride, sizeof(float) * 4u); EXPECT_EQ(cached->other.elementStride, sizeof(GaussianSplatOtherRecord)); EXPECT_EQ(cached->color.elementStride, sizeof(GaussianSplatColorRecord)); EXPECT_EQ(cached->sh.elementStride, sizeof(GaussianSplatSHRecord)); + EXPECT_EQ(cached->chunks.elementStride, sizeof(GaussianSplatChunkRecord)); EXPECT_EQ(cached->positions.elementCount, 2u); EXPECT_EQ(cached->other.elementCount, 2u); EXPECT_EQ(cached->color.elementCount, 2u); EXPECT_EQ(cached->sh.elementCount, 2u); + EXPECT_EQ(cached->chunks.elementCount, 1u); ASSERT_NE(cached->positions.buffer, nullptr); ASSERT_NE(cached->positions.shaderResourceView, nullptr); ASSERT_NE(cached->color.buffer, nullptr); ASSERT_NE(cached->sh.buffer, nullptr); + ASSERT_NE(cached->chunks.buffer, nullptr); const auto* uploadedPositions = static_cast(cached->positions.buffer); - ASSERT_GE(uploadedPositions->GetBytes().size(), sizeof(GaussianSplatPositionRecord) * 2u); - const auto* uploadedPositionRecords = reinterpret_cast( - uploadedPositions->GetBytes().data()); - EXPECT_EQ(uploadedPositionRecords[0].position, Vector3(0.0f, 1.0f, 2.0f)); - EXPECT_EQ(uploadedPositionRecords[1].position, Vector3(3.0f, 4.0f, 5.0f)); + ASSERT_GE(uploadedPositions->GetBytes().size(), sizeof(float) * 4u * 2u); + const auto* uploadedPositionWords = reinterpret_cast(uploadedPositions->GetBytes().data()); + EXPECT_FLOAT_EQ(uploadedPositionWords[0], 0.0f); + EXPECT_FLOAT_EQ(uploadedPositionWords[1], 1.0f); + EXPECT_FLOAT_EQ(uploadedPositionWords[2], 2.0f); + EXPECT_FLOAT_EQ(uploadedPositionWords[3], 0.0f); + EXPECT_FLOAT_EQ(uploadedPositionWords[4], 3.0f); + EXPECT_FLOAT_EQ(uploadedPositionWords[5], 4.0f); + EXPECT_FLOAT_EQ(uploadedPositionWords[6], 5.0f); + EXPECT_FLOAT_EQ(uploadedPositionWords[7], 0.0f); - EXPECT_EQ(state->createBufferCalls, 4); - EXPECT_EQ(state->createShaderViewCalls, 4); + EXPECT_EQ(state->createBufferCalls, 5); + EXPECT_EQ(state->createShaderViewCalls, 5); const RenderResourceCache::CachedGaussianSplat* cachedAgain = cache.GetOrCreateGaussianSplat(&device, &gaussianSplat); EXPECT_EQ(cachedAgain, cached); - EXPECT_EQ(state->createBufferCalls, 4); - EXPECT_EQ(state->createShaderViewCalls, 4); + EXPECT_EQ(state->createBufferCalls, 5); + EXPECT_EQ(state->createShaderViewCalls, 5); cache.Shutdown(); - EXPECT_EQ(state->shutdownBufferCalls, 4); - EXPECT_EQ(state->destroyBufferCalls, 4); - EXPECT_EQ(state->shutdownShaderViewCalls, 4); - EXPECT_EQ(state->destroyShaderViewCalls, 4); + EXPECT_EQ(state->shutdownBufferCalls, 5); + EXPECT_EQ(state->destroyBufferCalls, 5); + EXPECT_EQ(state->shutdownShaderViewCalls, 5); + EXPECT_EQ(state->destroyShaderViewCalls, 5); } TEST(RenderResourceCacheTest, GetOrCreateGaussianSplatSupportsArtifactRuntimeLoadPath) { @@ -459,8 +494,9 @@ TEST(RenderResourceCacheTest, GetOrCreateGaussianSplatSupportsArtifactRuntimeLoa ASSERT_NE(cached, nullptr); EXPECT_EQ(cached->residencyState, RenderResourceCache::GaussianSplatResidencyState::GpuReady); EXPECT_EQ(cached->splatCount, 2u); - EXPECT_EQ(state->createBufferCalls, 4); - EXPECT_EQ(state->createShaderViewCalls, 4); + EXPECT_EQ(cached->chunkCount, 1u); + EXPECT_EQ(state->createBufferCalls, 5); + EXPECT_EQ(state->createShaderViewCalls, 5); } manager.UnloadAll(); diff --git a/tests/Resources/GaussianSplat/test_gaussian_splat_loader.cpp b/tests/Resources/GaussianSplat/test_gaussian_splat_loader.cpp index 4d4b4b76..64e5c4c0 100644 --- a/tests/Resources/GaussianSplat/test_gaussian_splat_loader.cpp +++ b/tests/Resources/GaussianSplat/test_gaussian_splat_loader.cpp @@ -163,16 +163,36 @@ SampleArtifactData BuildSampleArtifactData() { sh[1].coefficients[index] = -0.02f * static_cast(index + 1u); } + const GaussianSplatChunkRecord chunks[1] = { + { + 0x00010002u, + 0x00030004u, + 0x00050006u, + 0x00070008u, + Vector2(-1.0f, 2.0f), + Vector2(-3.0f, 4.0f), + Vector2(-5.0f, 6.0f), + 0x0009000Au, + 0x000B000Cu, + 0x000D000Eu, + 0x000F0010u, + 0x00110012u, + 0x00130014u + } + }; + SampleArtifactData sample; sample.metadata.contentVersion = 3u; sample.metadata.splatCount = 2u; + sample.metadata.chunkCount = 1u; sample.metadata.bounds.SetMinMax(Vector3(-2.0f, -1.0f, -3.0f), Vector3(5.0f, 4.0f, 6.0f)); sample.metadata.positionFormat = GaussianSplatSectionFormat::VectorFloat32; sample.metadata.otherFormat = GaussianSplatSectionFormat::OtherFloat32; sample.metadata.colorFormat = GaussianSplatSectionFormat::ColorRGBA32F; sample.metadata.shFormat = GaussianSplatSectionFormat::SHFloat32; + sample.metadata.chunkFormat = GaussianSplatSectionFormat::ChunkFloat32; - sample.sections.Reserve(4u); + sample.sections.Reserve(5u); size_t payloadOffset = 0u; auto appendSection = [&](GaussianSplatSectionType type, GaussianSplatSectionFormat format, @@ -223,6 +243,13 @@ SampleArtifactData BuildSampleArtifactData() { sizeof(sh), 2u, sizeof(GaussianSplatSHRecord)); + appendSection( + GaussianSplatSectionType::Chunks, + GaussianSplatSectionFormat::ChunkFloat32, + chunks, + sizeof(chunks), + 1u, + sizeof(GaussianSplatChunkRecord)); return sample; } @@ -317,13 +344,18 @@ TEST(GaussianSplatLoader, WritesAndLoadsArtifact) { ASSERT_NE(gaussianSplat, nullptr); EXPECT_EQ(gaussianSplat->GetContentVersion(), 3u); EXPECT_EQ(gaussianSplat->GetSplatCount(), 2u); + EXPECT_EQ(gaussianSplat->GetChunkCount(), 1u); EXPECT_EQ(gaussianSplat->GetBounds().GetMin(), Vector3(-2.0f, -1.0f, -3.0f)); EXPECT_EQ(gaussianSplat->GetBounds().GetMax(), Vector3(5.0f, 4.0f, 6.0f)); - ASSERT_EQ(gaussianSplat->GetSections().Size(), 4u); + ASSERT_EQ(gaussianSplat->GetSections().Size(), 5u); const GaussianSplatSection* shSection = gaussianSplat->FindSection(GaussianSplatSectionType::SH); ASSERT_NE(shSection, nullptr); EXPECT_EQ(shSection->elementCount, 2u); EXPECT_EQ(shSection->elementStride, sizeof(GaussianSplatSHRecord)); + const GaussianSplatSection* chunkSection = gaussianSplat->FindSection(GaussianSplatSectionType::Chunks); + ASSERT_NE(chunkSection, nullptr); + EXPECT_EQ(chunkSection->elementCount, 1u); + EXPECT_EQ(chunkSection->elementStride, sizeof(GaussianSplatChunkRecord)); ASSERT_NE(gaussianSplat->GetColorRecords(), nullptr); EXPECT_EQ(gaussianSplat->GetColorRecords()[1].colorOpacity, Vector4(0.0f, 1.0f, 0.0f, 0.75f));