Formalize GaussianSplat render cache
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
#include <XCEngine/RHI/RHIDevice.h>
|
#include <XCEngine/RHI/RHIDevice.h>
|
||||||
#include <XCEngine/RHI/RHIResourceView.h>
|
#include <XCEngine/RHI/RHIResourceView.h>
|
||||||
#include <XCEngine/RHI/RHITexture.h>
|
#include <XCEngine/RHI/RHITexture.h>
|
||||||
|
#include <XCEngine/Resources/GaussianSplat/GaussianSplat.h>
|
||||||
#include <XCEngine/Resources/Mesh/Mesh.h>
|
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||||
#include <XCEngine/Resources/Texture/Texture.h>
|
#include <XCEngine/Resources/Texture/Texture.h>
|
||||||
#include <XCEngine/Resources/Volume/VolumeField.h>
|
#include <XCEngine/Resources/Volume/VolumeField.h>
|
||||||
@@ -15,6 +16,14 @@ namespace Rendering {
|
|||||||
|
|
||||||
class RenderResourceCache {
|
class RenderResourceCache {
|
||||||
public:
|
public:
|
||||||
|
enum class GaussianSplatResidencyState {
|
||||||
|
Uninitialized = 0,
|
||||||
|
CpuReady,
|
||||||
|
GpuUploading,
|
||||||
|
GpuReady,
|
||||||
|
Failed
|
||||||
|
};
|
||||||
|
|
||||||
struct CachedMesh {
|
struct CachedMesh {
|
||||||
RHI::RHIBuffer* vertexBuffer = nullptr;
|
RHI::RHIBuffer* vertexBuffer = nullptr;
|
||||||
RHI::RHIResourceView* vertexBufferView = nullptr;
|
RHI::RHIResourceView* vertexBufferView = nullptr;
|
||||||
@@ -46,6 +55,30 @@ public:
|
|||||||
Resources::VolumeStorageKind storageKind = Resources::VolumeStorageKind::Unknown;
|
Resources::VolumeStorageKind storageKind = Resources::VolumeStorageKind::Unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct CachedGaussianSplatSection {
|
||||||
|
RHI::RHIBuffer* buffer = nullptr;
|
||||||
|
RHI::RHIResourceView* shaderResourceView = nullptr;
|
||||||
|
Resources::GaussianSplatSectionType type = Resources::GaussianSplatSectionType::Unknown;
|
||||||
|
Resources::GaussianSplatSectionFormat format = Resources::GaussianSplatSectionFormat::Unknown;
|
||||||
|
uint32_t elementStride = 0;
|
||||||
|
uint32_t elementCount = 0;
|
||||||
|
uint64_t payloadSize = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CachedGaussianSplat {
|
||||||
|
GaussianSplatResidencyState residencyState = GaussianSplatResidencyState::Uninitialized;
|
||||||
|
uint32_t contentVersion = 0;
|
||||||
|
uint32_t splatCount = 0;
|
||||||
|
uint32_t chunkCount = 0;
|
||||||
|
uint32_t cameraCount = 0;
|
||||||
|
Math::Bounds bounds;
|
||||||
|
CachedGaussianSplatSection positions;
|
||||||
|
CachedGaussianSplatSection other;
|
||||||
|
CachedGaussianSplatSection color;
|
||||||
|
CachedGaussianSplatSection sh;
|
||||||
|
CachedGaussianSplatSection chunks;
|
||||||
|
};
|
||||||
|
|
||||||
~RenderResourceCache();
|
~RenderResourceCache();
|
||||||
|
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
@@ -55,6 +88,9 @@ public:
|
|||||||
const CachedVolumeField* GetOrCreateVolumeField(
|
const CachedVolumeField* GetOrCreateVolumeField(
|
||||||
RHI::RHIDevice* device,
|
RHI::RHIDevice* device,
|
||||||
const Resources::VolumeField* volumeField);
|
const Resources::VolumeField* volumeField);
|
||||||
|
const CachedGaussianSplat* GetOrCreateGaussianSplat(
|
||||||
|
RHI::RHIDevice* device,
|
||||||
|
const Resources::GaussianSplat* gaussianSplat);
|
||||||
const CachedBufferView* GetOrCreateBufferView(
|
const CachedBufferView* GetOrCreateBufferView(
|
||||||
RHI::RHIDevice* device,
|
RHI::RHIDevice* device,
|
||||||
RHI::RHIBuffer* buffer,
|
RHI::RHIBuffer* buffer,
|
||||||
@@ -94,6 +130,18 @@ private:
|
|||||||
RHI::RHIDevice* device,
|
RHI::RHIDevice* device,
|
||||||
const Resources::VolumeField* volumeField,
|
const Resources::VolumeField* volumeField,
|
||||||
CachedVolumeField& cachedVolumeField);
|
CachedVolumeField& cachedVolumeField);
|
||||||
|
bool UploadGaussianSplat(
|
||||||
|
RHI::RHIDevice* device,
|
||||||
|
const Resources::GaussianSplat* gaussianSplat,
|
||||||
|
CachedGaussianSplat& cachedGaussianSplat);
|
||||||
|
bool UploadGaussianSplatSection(
|
||||||
|
RHI::RHIDevice* device,
|
||||||
|
const Resources::GaussianSplat* gaussianSplat,
|
||||||
|
Resources::GaussianSplatSectionType sectionType,
|
||||||
|
Resources::GaussianSplatSectionFormat requiredFormat,
|
||||||
|
uint32_t requiredStride,
|
||||||
|
bool requiredSection,
|
||||||
|
CachedGaussianSplatSection& cachedSection);
|
||||||
bool CreateBufferView(
|
bool CreateBufferView(
|
||||||
RHI::RHIDevice* device,
|
RHI::RHIDevice* device,
|
||||||
RHI::RHIBuffer* buffer,
|
RHI::RHIBuffer* buffer,
|
||||||
@@ -104,6 +152,7 @@ private:
|
|||||||
std::unordered_map<const Resources::Mesh*, CachedMesh> m_meshCache;
|
std::unordered_map<const Resources::Mesh*, CachedMesh> m_meshCache;
|
||||||
std::unordered_map<const Resources::Texture*, CachedTexture> m_textureCache;
|
std::unordered_map<const Resources::Texture*, CachedTexture> m_textureCache;
|
||||||
std::unordered_map<const Resources::VolumeField*, CachedVolumeField> m_volumeFieldCache;
|
std::unordered_map<const Resources::VolumeField*, CachedVolumeField> m_volumeFieldCache;
|
||||||
|
std::unordered_map<const Resources::GaussianSplat*, CachedGaussianSplat> m_gaussianSplatCache;
|
||||||
std::unordered_map<BufferViewCacheKey, CachedBufferView, BufferViewCacheKeyHash> m_bufferViewCache;
|
std::unordered_map<BufferViewCacheKey, CachedBufferView, BufferViewCacheKeyHash> m_bufferViewCache;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
#include "Rendering/Caches/RenderResourceCache.h"
|
#include "Rendering/Caches/RenderResourceCache.h"
|
||||||
|
|
||||||
|
#include "Debug/Logger.h"
|
||||||
|
|
||||||
#include <XCEngine/RHI/RHIEnums.h>
|
#include <XCEngine/RHI/RHIEnums.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <limits>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
@@ -13,6 +17,21 @@ namespace Rendering {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
constexpr size_t kVolumeWordStride = sizeof(uint32_t);
|
||||||
|
|
||||||
|
uint64_t GetVolumeTraceSteadyMs() {
|
||||||
|
using Clock = std::chrono::steady_clock;
|
||||||
|
static const Clock::time_point s_start = Clock::now();
|
||||||
|
return static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
Clock::now() - s_start).count());
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogVolumeTraceRendering(const std::string& message) {
|
||||||
|
Containers::String entry("[VolumeTrace] ");
|
||||||
|
entry += message.c_str();
|
||||||
|
Debug::Logger::Get().Info(Debug::LogCategory::Rendering, entry);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename TValue>
|
template <typename TValue>
|
||||||
TValue AlignUp(TValue value, TValue alignment) {
|
TValue AlignUp(TValue value, TValue alignment) {
|
||||||
if (alignment == 0) {
|
if (alignment == 0) {
|
||||||
@@ -165,6 +184,58 @@ void ShutdownVolumeField(RenderResourceCache::CachedVolumeField& cachedVolumeFie
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ShutdownGaussianSplatSection(RenderResourceCache::CachedGaussianSplatSection& cachedSection) {
|
||||||
|
if (cachedSection.shaderResourceView != nullptr) {
|
||||||
|
cachedSection.shaderResourceView->Shutdown();
|
||||||
|
delete cachedSection.shaderResourceView;
|
||||||
|
cachedSection.shaderResourceView = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cachedSection.buffer != nullptr) {
|
||||||
|
cachedSection.buffer->Shutdown();
|
||||||
|
delete cachedSection.buffer;
|
||||||
|
cachedSection.buffer = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedSection.type = Resources::GaussianSplatSectionType::Unknown;
|
||||||
|
cachedSection.format = Resources::GaussianSplatSectionFormat::Unknown;
|
||||||
|
cachedSection.elementStride = 0u;
|
||||||
|
cachedSection.elementCount = 0u;
|
||||||
|
cachedSection.payloadSize = 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShutdownGaussianSplat(RenderResourceCache::CachedGaussianSplat& cachedGaussianSplat) {
|
||||||
|
ShutdownGaussianSplatSection(cachedGaussianSplat.positions);
|
||||||
|
ShutdownGaussianSplatSection(cachedGaussianSplat.other);
|
||||||
|
ShutdownGaussianSplatSection(cachedGaussianSplat.color);
|
||||||
|
ShutdownGaussianSplatSection(cachedGaussianSplat.sh);
|
||||||
|
ShutdownGaussianSplatSection(cachedGaussianSplat.chunks);
|
||||||
|
cachedGaussianSplat.contentVersion = 0u;
|
||||||
|
cachedGaussianSplat.splatCount = 0u;
|
||||||
|
cachedGaussianSplat.chunkCount = 0u;
|
||||||
|
cachedGaussianSplat.cameraCount = 0u;
|
||||||
|
cachedGaussianSplat.bounds = Math::Bounds();
|
||||||
|
cachedGaussianSplat.residencyState = RenderResourceCache::GaussianSplatResidencyState::Uninitialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ValidateGaussianSplatSectionLayout(
|
||||||
|
const Resources::GaussianSplatSection& section,
|
||||||
|
Resources::GaussianSplatSectionFormat requiredFormat,
|
||||||
|
uint32_t requiredStride,
|
||||||
|
uint32_t expectedElementCount) {
|
||||||
|
if (section.format != requiredFormat || section.elementStride != requiredStride) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section.elementCount != expectedElementCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint64_t expectedDataSize =
|
||||||
|
static_cast<uint64_t>(section.elementCount) * static_cast<uint64_t>(section.elementStride);
|
||||||
|
return expectedDataSize == section.dataSize;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
RenderResourceCache::~RenderResourceCache() {
|
RenderResourceCache::~RenderResourceCache() {
|
||||||
@@ -187,6 +258,11 @@ void RenderResourceCache::Shutdown() {
|
|||||||
}
|
}
|
||||||
m_volumeFieldCache.clear();
|
m_volumeFieldCache.clear();
|
||||||
|
|
||||||
|
for (auto& entry : m_gaussianSplatCache) {
|
||||||
|
ShutdownGaussianSplat(entry.second);
|
||||||
|
}
|
||||||
|
m_gaussianSplatCache.clear();
|
||||||
|
|
||||||
for (auto& entry : m_bufferViewCache) {
|
for (auto& entry : m_bufferViewCache) {
|
||||||
ShutdownBufferView(entry.second);
|
ShutdownBufferView(entry.second);
|
||||||
}
|
}
|
||||||
@@ -263,6 +339,37 @@ const RenderResourceCache::CachedVolumeField* RenderResourceCache::GetOrCreateVo
|
|||||||
return &result.first->second;
|
return &result.first->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const RenderResourceCache::CachedGaussianSplat* RenderResourceCache::GetOrCreateGaussianSplat(
|
||||||
|
RHI::RHIDevice* device,
|
||||||
|
const Resources::GaussianSplat* gaussianSplat) {
|
||||||
|
if (device == nullptr ||
|
||||||
|
gaussianSplat == nullptr ||
|
||||||
|
!gaussianSplat->IsValid() ||
|
||||||
|
gaussianSplat->GetSplatCount() == 0u ||
|
||||||
|
gaussianSplat->GetPayloadData() == nullptr ||
|
||||||
|
gaussianSplat->GetPayloadSize() == 0u) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto existing = m_gaussianSplatCache.find(gaussianSplat);
|
||||||
|
if (existing != m_gaussianSplatCache.end()) {
|
||||||
|
return existing->second.residencyState == GaussianSplatResidencyState::GpuReady
|
||||||
|
? &existing->second
|
||||||
|
: nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto result = m_gaussianSplatCache.emplace(gaussianSplat, CachedGaussianSplat{});
|
||||||
|
CachedGaussianSplat& cachedGaussianSplat = result.first->second;
|
||||||
|
cachedGaussianSplat.residencyState = GaussianSplatResidencyState::CpuReady;
|
||||||
|
if (!UploadGaussianSplat(device, gaussianSplat, cachedGaussianSplat)) {
|
||||||
|
ShutdownGaussianSplat(cachedGaussianSplat);
|
||||||
|
cachedGaussianSplat.residencyState = GaussianSplatResidencyState::Failed;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cachedGaussianSplat;
|
||||||
|
}
|
||||||
|
|
||||||
const RenderResourceCache::CachedBufferView* RenderResourceCache::GetOrCreateBufferView(
|
const RenderResourceCache::CachedBufferView* RenderResourceCache::GetOrCreateBufferView(
|
||||||
RHI::RHIDevice* device,
|
RHI::RHIDevice* device,
|
||||||
RHI::RHIBuffer* buffer,
|
RHI::RHIBuffer* buffer,
|
||||||
@@ -436,29 +543,37 @@ bool RenderResourceCache::UploadVolumeField(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr uint32_t kVolumeWordStride = sizeof(uint32_t);
|
|
||||||
const size_t alignedPayloadSize = AlignUp(volumeField->GetPayloadSize(), static_cast<size_t>(kVolumeWordStride));
|
const size_t alignedPayloadSize = AlignUp(volumeField->GetPayloadSize(), static_cast<size_t>(kVolumeWordStride));
|
||||||
if (alignedPayloadSize == 0u || alignedPayloadSize > static_cast<size_t>(UINT64_MAX)) {
|
if (alignedPayloadSize == 0u || alignedPayloadSize > static_cast<size_t>(UINT64_MAX)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uint64_t uploadStartMs = GetVolumeTraceSteadyMs();
|
||||||
|
LogVolumeTraceRendering(
|
||||||
|
"UploadVolumeField begin path=" + std::string(volumeField->GetPath().CStr()) +
|
||||||
|
" steady_ms=" + std::to_string(uploadStartMs) +
|
||||||
|
" payload_bytes=" + std::to_string(volumeField->GetPayloadSize()) +
|
||||||
|
" aligned_bytes=" + std::to_string(alignedPayloadSize));
|
||||||
|
|
||||||
RHI::BufferDesc bufferDesc = {};
|
RHI::BufferDesc bufferDesc = {};
|
||||||
bufferDesc.size = static_cast<uint64_t>(alignedPayloadSize);
|
bufferDesc.size = static_cast<uint64_t>(alignedPayloadSize);
|
||||||
bufferDesc.stride = kVolumeWordStride;
|
bufferDesc.stride = kVolumeWordStride;
|
||||||
bufferDesc.bufferType = static_cast<uint32_t>(RHI::BufferType::Storage);
|
bufferDesc.bufferType = static_cast<uint32_t>(RHI::BufferType::Storage);
|
||||||
bufferDesc.flags = 0;
|
bufferDesc.flags = 0;
|
||||||
|
|
||||||
cachedVolumeField.payloadBuffer = device->CreateBuffer(bufferDesc);
|
const uint64_t createBufferStartMs = GetVolumeTraceSteadyMs();
|
||||||
|
cachedVolumeField.payloadBuffer = device->CreateBuffer(
|
||||||
|
bufferDesc,
|
||||||
|
volumeField->GetPayloadData(),
|
||||||
|
volumeField->GetPayloadSize(),
|
||||||
|
RHI::ResourceStates::GenericRead);
|
||||||
if (cachedVolumeField.payloadBuffer == nullptr) {
|
if (cachedVolumeField.payloadBuffer == nullptr) {
|
||||||
|
LogVolumeTraceRendering(
|
||||||
|
"UploadVolumeField failed path=" + std::string(volumeField->GetPath().CStr()) +
|
||||||
|
" stage=create_buffer");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const uint64_t createBufferEndMs = GetVolumeTraceSteadyMs();
|
||||||
std::vector<uint8_t> uploadData(alignedPayloadSize, 0u);
|
|
||||||
std::memcpy(
|
|
||||||
uploadData.data(),
|
|
||||||
volumeField->GetPayloadData(),
|
|
||||||
volumeField->GetPayloadSize());
|
|
||||||
cachedVolumeField.payloadBuffer->SetData(uploadData.data(), uploadData.size());
|
|
||||||
cachedVolumeField.payloadBuffer->SetStride(kVolumeWordStride);
|
cachedVolumeField.payloadBuffer->SetStride(kVolumeWordStride);
|
||||||
cachedVolumeField.payloadBuffer->SetBufferType(RHI::BufferType::Storage);
|
cachedVolumeField.payloadBuffer->SetBufferType(RHI::BufferType::Storage);
|
||||||
|
|
||||||
@@ -471,8 +586,17 @@ bool RenderResourceCache::UploadVolumeField(
|
|||||||
cachedVolumeField.shaderResourceView =
|
cachedVolumeField.shaderResourceView =
|
||||||
device->CreateShaderResourceView(cachedVolumeField.payloadBuffer, viewDesc);
|
device->CreateShaderResourceView(cachedVolumeField.payloadBuffer, viewDesc);
|
||||||
if (cachedVolumeField.shaderResourceView == nullptr) {
|
if (cachedVolumeField.shaderResourceView == nullptr) {
|
||||||
|
LogVolumeTraceRendering(
|
||||||
|
"UploadVolumeField failed path=" + std::string(volumeField->GetPath().CStr()) +
|
||||||
|
" stage=create_srv");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const uint64_t uploadEndMs = GetVolumeTraceSteadyMs();
|
||||||
|
LogVolumeTraceRendering(
|
||||||
|
"UploadVolumeField end path=" + std::string(volumeField->GetPath().CStr()) +
|
||||||
|
" steady_ms=" + std::to_string(uploadEndMs) +
|
||||||
|
" create_buffer_ms=" + std::to_string(createBufferEndMs - createBufferStartMs) +
|
||||||
|
" total_ms=" + std::to_string(uploadEndMs - uploadStartMs));
|
||||||
|
|
||||||
cachedVolumeField.elementStride = kVolumeWordStride;
|
cachedVolumeField.elementStride = kVolumeWordStride;
|
||||||
cachedVolumeField.elementCount = viewDesc.elementCount;
|
cachedVolumeField.elementCount = viewDesc.elementCount;
|
||||||
@@ -481,6 +605,147 @@ bool RenderResourceCache::UploadVolumeField(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool RenderResourceCache::UploadGaussianSplat(
|
||||||
|
RHI::RHIDevice* device,
|
||||||
|
const Resources::GaussianSplat* gaussianSplat,
|
||||||
|
CachedGaussianSplat& cachedGaussianSplat) {
|
||||||
|
if (device == nullptr || gaussianSplat == nullptr || gaussianSplat->GetSplatCount() == 0u) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedGaussianSplat.residencyState = GaussianSplatResidencyState::GpuUploading;
|
||||||
|
cachedGaussianSplat.contentVersion = gaussianSplat->GetContentVersion();
|
||||||
|
cachedGaussianSplat.splatCount = gaussianSplat->GetSplatCount();
|
||||||
|
cachedGaussianSplat.chunkCount = gaussianSplat->GetChunkCount();
|
||||||
|
cachedGaussianSplat.cameraCount = gaussianSplat->GetCameraCount();
|
||||||
|
cachedGaussianSplat.bounds = gaussianSplat->GetBounds();
|
||||||
|
|
||||||
|
if (!UploadGaussianSplatSection(
|
||||||
|
device,
|
||||||
|
gaussianSplat,
|
||||||
|
Resources::GaussianSplatSectionType::Positions,
|
||||||
|
Resources::GaussianSplatSectionFormat::VectorFloat32,
|
||||||
|
sizeof(Resources::GaussianSplatPositionRecord),
|
||||||
|
true,
|
||||||
|
cachedGaussianSplat.positions) ||
|
||||||
|
!UploadGaussianSplatSection(
|
||||||
|
device,
|
||||||
|
gaussianSplat,
|
||||||
|
Resources::GaussianSplatSectionType::Other,
|
||||||
|
Resources::GaussianSplatSectionFormat::OtherFloat32,
|
||||||
|
sizeof(Resources::GaussianSplatOtherRecord),
|
||||||
|
true,
|
||||||
|
cachedGaussianSplat.other) ||
|
||||||
|
!UploadGaussianSplatSection(
|
||||||
|
device,
|
||||||
|
gaussianSplat,
|
||||||
|
Resources::GaussianSplatSectionType::Color,
|
||||||
|
Resources::GaussianSplatSectionFormat::ColorRGBA32F,
|
||||||
|
sizeof(Resources::GaussianSplatColorRecord),
|
||||||
|
true,
|
||||||
|
cachedGaussianSplat.color) ||
|
||||||
|
!UploadGaussianSplatSection(
|
||||||
|
device,
|
||||||
|
gaussianSplat,
|
||||||
|
Resources::GaussianSplatSectionType::SH,
|
||||||
|
Resources::GaussianSplatSectionFormat::SHFloat32,
|
||||||
|
sizeof(Resources::GaussianSplatSHRecord),
|
||||||
|
true,
|
||||||
|
cachedGaussianSplat.sh) ||
|
||||||
|
!UploadGaussianSplatSection(
|
||||||
|
device,
|
||||||
|
gaussianSplat,
|
||||||
|
Resources::GaussianSplatSectionType::Chunks,
|
||||||
|
Resources::GaussianSplatSectionFormat::ChunkFloat32,
|
||||||
|
0u,
|
||||||
|
gaussianSplat->GetChunkCount() > 0u,
|
||||||
|
cachedGaussianSplat.chunks)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedGaussianSplat.residencyState = GaussianSplatResidencyState::GpuReady;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderResourceCache::UploadGaussianSplatSection(
|
||||||
|
RHI::RHIDevice* device,
|
||||||
|
const Resources::GaussianSplat* gaussianSplat,
|
||||||
|
Resources::GaussianSplatSectionType sectionType,
|
||||||
|
Resources::GaussianSplatSectionFormat requiredFormat,
|
||||||
|
uint32_t requiredStride,
|
||||||
|
bool requiredSection,
|
||||||
|
CachedGaussianSplatSection& cachedSection) {
|
||||||
|
if (device == nullptr || gaussianSplat == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Resources::GaussianSplatSection* section = gaussianSplat->FindSection(sectionType);
|
||||||
|
if (section == nullptr || section->dataSize == 0u || section->elementCount == 0u) {
|
||||||
|
if (requiredSection) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedSection.type = sectionType;
|
||||||
|
cachedSection.format = section != nullptr ? section->format : requiredFormat;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t expectedElementCount =
|
||||||
|
sectionType == Resources::GaussianSplatSectionType::Chunks
|
||||||
|
? gaussianSplat->GetChunkCount()
|
||||||
|
: gaussianSplat->GetSplatCount();
|
||||||
|
const uint32_t resolvedStride =
|
||||||
|
requiredStride != 0u ? requiredStride : section->elementStride;
|
||||||
|
if (!ValidateGaussianSplatSectionLayout(*section, requiredFormat, resolvedStride, expectedElementCount)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const void* sectionData = gaussianSplat->GetSectionData(sectionType);
|
||||||
|
if (sectionData == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section->dataSize > static_cast<uint64_t>(std::numeric_limits<size_t>::max())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHI::BufferDesc bufferDesc = {};
|
||||||
|
bufferDesc.size = section->dataSize;
|
||||||
|
bufferDesc.stride = resolvedStride;
|
||||||
|
bufferDesc.bufferType = static_cast<uint32_t>(RHI::BufferType::Storage);
|
||||||
|
bufferDesc.flags = 0u;
|
||||||
|
|
||||||
|
cachedSection.buffer = device->CreateBuffer(
|
||||||
|
bufferDesc,
|
||||||
|
sectionData,
|
||||||
|
static_cast<size_t>(section->dataSize),
|
||||||
|
RHI::ResourceStates::GenericRead);
|
||||||
|
if (cachedSection.buffer == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedSection.buffer->SetStride(resolvedStride);
|
||||||
|
cachedSection.buffer->SetBufferType(RHI::BufferType::Storage);
|
||||||
|
|
||||||
|
RHI::ResourceViewDesc viewDesc = {};
|
||||||
|
viewDesc.dimension = RHI::ResourceViewDimension::StructuredBuffer;
|
||||||
|
viewDesc.firstElement = 0u;
|
||||||
|
viewDesc.elementCount = section->elementCount;
|
||||||
|
viewDesc.structureByteStride = resolvedStride;
|
||||||
|
|
||||||
|
cachedSection.shaderResourceView = device->CreateShaderResourceView(cachedSection.buffer, viewDesc);
|
||||||
|
if (cachedSection.shaderResourceView == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedSection.type = sectionType;
|
||||||
|
cachedSection.format = section->format;
|
||||||
|
cachedSection.elementStride = resolvedStride;
|
||||||
|
cachedSection.elementCount = section->elementCount;
|
||||||
|
cachedSection.payloadSize = section->dataSize;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool RenderResourceCache::CreateBufferView(
|
bool RenderResourceCache::CreateBufferView(
|
||||||
RHI::RHIDevice* device,
|
RHI::RHIDevice* device,
|
||||||
RHI::RHIBuffer* buffer,
|
RHI::RHIBuffer* buffer,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ set(RENDERING_UNIT_TEST_SOURCES
|
|||||||
test_scene_render_request_utils.cpp
|
test_scene_render_request_utils.cpp
|
||||||
test_render_scene_utility.cpp
|
test_render_scene_utility.cpp
|
||||||
test_render_scene_extractor.cpp
|
test_render_scene_extractor.cpp
|
||||||
|
test_render_resource_cache.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(rendering_unit_tests ${RENDERING_UNIT_TEST_SOURCES})
|
add_executable(rendering_unit_tests ${RENDERING_UNIT_TEST_SOURCES})
|
||||||
@@ -30,6 +31,7 @@ target_link_libraries(rendering_unit_tests PRIVATE
|
|||||||
|
|
||||||
target_include_directories(rendering_unit_tests PRIVATE
|
target_include_directories(rendering_unit_tests PRIVATE
|
||||||
${CMAKE_SOURCE_DIR}/engine/include
|
${CMAKE_SOURCE_DIR}/engine/include
|
||||||
|
${CMAKE_SOURCE_DIR}/engine/src
|
||||||
)
|
)
|
||||||
|
|
||||||
include(GoogleTest)
|
include(GoogleTest)
|
||||||
|
|||||||
538
tests/Rendering/unit/test_render_resource_cache.cpp
Normal file
538
tests/Rendering/unit/test_render_resource_cache.cpp
Normal file
@@ -0,0 +1,538 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||||
|
#include <XCEngine/Core/Math/Bounds.h>
|
||||||
|
#include <XCEngine/RHI/RHIBuffer.h>
|
||||||
|
#include <XCEngine/RHI/RHIDevice.h>
|
||||||
|
#include <XCEngine/RHI/RHIResourceView.h>
|
||||||
|
#include <XCEngine/Rendering/Caches/RenderResourceCache.h>
|
||||||
|
#include <XCEngine/Resources/GaussianSplat/GaussianSplat.h>
|
||||||
|
#include <XCEngine/Resources/GaussianSplat/GaussianSplatArtifactIO.h>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace XCEngine::Math;
|
||||||
|
using namespace XCEngine::Rendering;
|
||||||
|
using namespace XCEngine::Resources;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct SyntheticGaussianSplatVertex {
|
||||||
|
Vector3 position = Vector3::Zero();
|
||||||
|
Vector3 dc0 = Vector3::Zero();
|
||||||
|
float opacity = 0.0f;
|
||||||
|
Vector3 scaleLog = Vector3::Zero();
|
||||||
|
float rotationWXYZ[4] = { 1.0f, 0.0f, 0.0f, 0.0f };
|
||||||
|
float sh[kGaussianSplatSHCoefficientCount] = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SampleArtifactData {
|
||||||
|
GaussianSplatMetadata metadata;
|
||||||
|
XCEngine::Containers::Array<GaussianSplatSection> sections;
|
||||||
|
XCEngine::Containers::Array<XCEngine::Core::uint8> payload;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MockCacheAllocationState {
|
||||||
|
int createBufferCalls = 0;
|
||||||
|
int shutdownBufferCalls = 0;
|
||||||
|
int destroyBufferCalls = 0;
|
||||||
|
int createShaderViewCalls = 0;
|
||||||
|
int shutdownShaderViewCalls = 0;
|
||||||
|
int destroyShaderViewCalls = 0;
|
||||||
|
std::vector<XCEngine::RHI::BufferDesc> bufferDescs;
|
||||||
|
std::vector<XCEngine::RHI::ResourceViewDesc> viewDescs;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MockCacheBuffer final : public XCEngine::RHI::RHIBuffer {
|
||||||
|
public:
|
||||||
|
MockCacheBuffer(std::shared_ptr<MockCacheAllocationState> state, const XCEngine::RHI::BufferDesc& desc)
|
||||||
|
: m_state(std::move(state))
|
||||||
|
, m_size(desc.size)
|
||||||
|
, m_stride(desc.stride)
|
||||||
|
, m_bufferType(static_cast<XCEngine::RHI::BufferType>(desc.bufferType))
|
||||||
|
, m_data(static_cast<size_t>(desc.size), 0u) {
|
||||||
|
}
|
||||||
|
|
||||||
|
~MockCacheBuffer() override {
|
||||||
|
++m_state->destroyBufferCalls;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* Map() override {
|
||||||
|
return m_data.empty() ? nullptr : m_data.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Unmap() override {}
|
||||||
|
|
||||||
|
void SetData(const void* data, size_t size, size_t offset = 0) override {
|
||||||
|
ASSERT_NE(data, nullptr);
|
||||||
|
ASSERT_LE(offset + size, m_data.size());
|
||||||
|
std::memcpy(m_data.data() + offset, data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t GetSize() const override { return m_size; }
|
||||||
|
XCEngine::RHI::BufferType GetBufferType() const override { return m_bufferType; }
|
||||||
|
void SetBufferType(XCEngine::RHI::BufferType type) override { m_bufferType = type; }
|
||||||
|
uint32_t GetStride() const override { return m_stride; }
|
||||||
|
void SetStride(uint32_t stride) override { m_stride = stride; }
|
||||||
|
void* GetNativeHandle() override { return nullptr; }
|
||||||
|
XCEngine::RHI::ResourceStates GetState() const override { return m_stateValue; }
|
||||||
|
void SetState(XCEngine::RHI::ResourceStates state) override { m_stateValue = state; }
|
||||||
|
const std::string& GetName() const override { return m_name; }
|
||||||
|
void SetName(const std::string& name) override { m_name = name; }
|
||||||
|
|
||||||
|
void Shutdown() override {
|
||||||
|
++m_state->shutdownBufferCalls;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<XCEngine::Core::uint8>& GetBytes() const { return m_data; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<MockCacheAllocationState> m_state;
|
||||||
|
uint64_t m_size = 0u;
|
||||||
|
uint32_t m_stride = 0u;
|
||||||
|
XCEngine::RHI::BufferType m_bufferType = XCEngine::RHI::BufferType::Storage;
|
||||||
|
XCEngine::RHI::ResourceStates m_stateValue = XCEngine::RHI::ResourceStates::Common;
|
||||||
|
std::string m_name;
|
||||||
|
std::vector<XCEngine::Core::uint8> m_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MockCacheView final : public XCEngine::RHI::RHIResourceView {
|
||||||
|
public:
|
||||||
|
MockCacheView(
|
||||||
|
std::shared_ptr<MockCacheAllocationState> state,
|
||||||
|
XCEngine::RHI::ResourceViewType viewType,
|
||||||
|
const XCEngine::RHI::ResourceViewDesc& desc)
|
||||||
|
: m_state(std::move(state))
|
||||||
|
, m_viewType(viewType)
|
||||||
|
, m_dimension(desc.dimension)
|
||||||
|
, m_format(static_cast<XCEngine::RHI::Format>(desc.format)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
~MockCacheView() override {
|
||||||
|
++m_state->destroyShaderViewCalls;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shutdown() override {
|
||||||
|
++m_state->shutdownShaderViewCalls;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* GetNativeHandle() override { return nullptr; }
|
||||||
|
bool IsValid() const override { return true; }
|
||||||
|
XCEngine::RHI::ResourceViewType GetViewType() const override { return m_viewType; }
|
||||||
|
XCEngine::RHI::ResourceViewDimension GetDimension() const override { return m_dimension; }
|
||||||
|
XCEngine::RHI::Format GetFormat() const override { return m_format; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<MockCacheAllocationState> m_state;
|
||||||
|
XCEngine::RHI::ResourceViewType m_viewType = XCEngine::RHI::ResourceViewType::ShaderResource;
|
||||||
|
XCEngine::RHI::ResourceViewDimension m_dimension = XCEngine::RHI::ResourceViewDimension::Unknown;
|
||||||
|
XCEngine::RHI::Format m_format = XCEngine::RHI::Format::Unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MockCacheDevice final : public XCEngine::RHI::RHIDevice {
|
||||||
|
public:
|
||||||
|
explicit MockCacheDevice(std::shared_ptr<MockCacheAllocationState> state)
|
||||||
|
: m_state(std::move(state)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Initialize(const XCEngine::RHI::RHIDeviceDesc&) override { return true; }
|
||||||
|
void Shutdown() override {}
|
||||||
|
|
||||||
|
XCEngine::RHI::RHIBuffer* CreateBuffer(const XCEngine::RHI::BufferDesc& desc) override {
|
||||||
|
++m_state->createBufferCalls;
|
||||||
|
m_state->bufferDescs.push_back(desc);
|
||||||
|
return new MockCacheBuffer(m_state, desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
XCEngine::RHI::RHITexture* CreateTexture(const XCEngine::RHI::TextureDesc&) override { return nullptr; }
|
||||||
|
XCEngine::RHI::RHITexture* CreateTexture(
|
||||||
|
const XCEngine::RHI::TextureDesc&,
|
||||||
|
const void*,
|
||||||
|
size_t,
|
||||||
|
uint32_t) override { return nullptr; }
|
||||||
|
XCEngine::RHI::RHISwapChain* CreateSwapChain(
|
||||||
|
const XCEngine::RHI::SwapChainDesc&,
|
||||||
|
XCEngine::RHI::RHICommandQueue*) override { return nullptr; }
|
||||||
|
XCEngine::RHI::RHICommandList* CreateCommandList(const XCEngine::RHI::CommandListDesc&) override { return nullptr; }
|
||||||
|
XCEngine::RHI::RHICommandQueue* CreateCommandQueue(const XCEngine::RHI::CommandQueueDesc&) override { return nullptr; }
|
||||||
|
XCEngine::RHI::RHIShader* CreateShader(const XCEngine::RHI::ShaderCompileDesc&) override { return nullptr; }
|
||||||
|
XCEngine::RHI::RHIPipelineState* CreatePipelineState(const XCEngine::RHI::GraphicsPipelineDesc&) override { return nullptr; }
|
||||||
|
XCEngine::RHI::RHIPipelineLayout* CreatePipelineLayout(const XCEngine::RHI::RHIPipelineLayoutDesc&) override { return nullptr; }
|
||||||
|
XCEngine::RHI::RHIFence* CreateFence(const XCEngine::RHI::FenceDesc&) override { return nullptr; }
|
||||||
|
XCEngine::RHI::RHISampler* CreateSampler(const XCEngine::RHI::SamplerDesc&) override { return nullptr; }
|
||||||
|
XCEngine::RHI::RHIRenderPass* CreateRenderPass(
|
||||||
|
uint32_t,
|
||||||
|
const XCEngine::RHI::AttachmentDesc*,
|
||||||
|
const XCEngine::RHI::AttachmentDesc*) override { return nullptr; }
|
||||||
|
XCEngine::RHI::RHIFramebuffer* CreateFramebuffer(
|
||||||
|
XCEngine::RHI::RHIRenderPass*,
|
||||||
|
uint32_t,
|
||||||
|
uint32_t,
|
||||||
|
uint32_t,
|
||||||
|
XCEngine::RHI::RHIResourceView**,
|
||||||
|
XCEngine::RHI::RHIResourceView*) override { return nullptr; }
|
||||||
|
XCEngine::RHI::RHIDescriptorPool* CreateDescriptorPool(const XCEngine::RHI::DescriptorPoolDesc&) override { return nullptr; }
|
||||||
|
XCEngine::RHI::RHIDescriptorSet* CreateDescriptorSet(
|
||||||
|
XCEngine::RHI::RHIDescriptorPool*,
|
||||||
|
const XCEngine::RHI::DescriptorSetLayoutDesc&) override { return nullptr; }
|
||||||
|
XCEngine::RHI::RHIResourceView* CreateVertexBufferView(
|
||||||
|
XCEngine::RHI::RHIBuffer*,
|
||||||
|
const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; }
|
||||||
|
XCEngine::RHI::RHIResourceView* CreateIndexBufferView(
|
||||||
|
XCEngine::RHI::RHIBuffer*,
|
||||||
|
const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; }
|
||||||
|
XCEngine::RHI::RHIResourceView* CreateRenderTargetView(
|
||||||
|
XCEngine::RHI::RHITexture*,
|
||||||
|
const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; }
|
||||||
|
XCEngine::RHI::RHIResourceView* CreateDepthStencilView(
|
||||||
|
XCEngine::RHI::RHITexture*,
|
||||||
|
const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; }
|
||||||
|
|
||||||
|
XCEngine::RHI::RHIResourceView* CreateShaderResourceView(
|
||||||
|
XCEngine::RHI::RHIBuffer*,
|
||||||
|
const XCEngine::RHI::ResourceViewDesc& desc) override {
|
||||||
|
++m_state->createShaderViewCalls;
|
||||||
|
m_state->viewDescs.push_back(desc);
|
||||||
|
return new MockCacheView(m_state, XCEngine::RHI::ResourceViewType::ShaderResource, desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
XCEngine::RHI::RHIResourceView* CreateShaderResourceView(
|
||||||
|
XCEngine::RHI::RHITexture*,
|
||||||
|
const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; }
|
||||||
|
XCEngine::RHI::RHIResourceView* CreateUnorderedAccessView(
|
||||||
|
XCEngine::RHI::RHIBuffer*,
|
||||||
|
const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; }
|
||||||
|
XCEngine::RHI::RHIResourceView* CreateUnorderedAccessView(
|
||||||
|
XCEngine::RHI::RHITexture*,
|
||||||
|
const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; }
|
||||||
|
|
||||||
|
const XCEngine::RHI::RHICapabilities& GetCapabilities() const override { return m_capabilities; }
|
||||||
|
const XCEngine::RHI::RHIDeviceInfo& GetDeviceInfo() const override { return m_deviceInfo; }
|
||||||
|
void* GetNativeDevice() override { return nullptr; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<MockCacheAllocationState> m_state;
|
||||||
|
XCEngine::RHI::RHICapabilities m_capabilities = {};
|
||||||
|
XCEngine::RHI::RHIDeviceInfo m_deviceInfo = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
void WriteBinaryFloat(std::ofstream& output, float value) {
|
||||||
|
output.write(reinterpret_cast<const char*>(&value), sizeof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteSyntheticGaussianSplatPly(
|
||||||
|
const std::filesystem::path& path,
|
||||||
|
const std::vector<SyntheticGaussianSplatVertex>& vertices) {
|
||||||
|
std::ofstream output(path, std::ios::binary | std::ios::trunc);
|
||||||
|
ASSERT_TRUE(output.is_open());
|
||||||
|
|
||||||
|
output << "ply\n";
|
||||||
|
output << "format binary_little_endian 1.0\n";
|
||||||
|
output << "element vertex " << vertices.size() << "\n";
|
||||||
|
output << "property float opacity\n";
|
||||||
|
output << "property float y\n";
|
||||||
|
output << "property float scale_2\n";
|
||||||
|
output << "property float rot_3\n";
|
||||||
|
output << "property float f_dc_1\n";
|
||||||
|
output << "property float x\n";
|
||||||
|
output << "property float scale_0\n";
|
||||||
|
output << "property float rot_1\n";
|
||||||
|
output << "property float f_dc_2\n";
|
||||||
|
output << "property float z\n";
|
||||||
|
output << "property float scale_1\n";
|
||||||
|
output << "property float rot_0\n";
|
||||||
|
output << "property float f_dc_0\n";
|
||||||
|
output << "property float rot_2\n";
|
||||||
|
for (XCEngine::Core::uint32 index = 0; index < kGaussianSplatSHCoefficientCount; ++index) {
|
||||||
|
output << "property float f_rest_" << index << "\n";
|
||||||
|
}
|
||||||
|
output << "end_header\n";
|
||||||
|
|
||||||
|
for (const SyntheticGaussianSplatVertex& vertex : vertices) {
|
||||||
|
WriteBinaryFloat(output, vertex.opacity);
|
||||||
|
WriteBinaryFloat(output, vertex.position.y);
|
||||||
|
WriteBinaryFloat(output, vertex.scaleLog.z);
|
||||||
|
WriteBinaryFloat(output, vertex.rotationWXYZ[3]);
|
||||||
|
WriteBinaryFloat(output, vertex.dc0.y);
|
||||||
|
WriteBinaryFloat(output, vertex.position.x);
|
||||||
|
WriteBinaryFloat(output, vertex.scaleLog.x);
|
||||||
|
WriteBinaryFloat(output, vertex.rotationWXYZ[1]);
|
||||||
|
WriteBinaryFloat(output, vertex.dc0.z);
|
||||||
|
WriteBinaryFloat(output, vertex.position.z);
|
||||||
|
WriteBinaryFloat(output, vertex.scaleLog.y);
|
||||||
|
WriteBinaryFloat(output, vertex.rotationWXYZ[0]);
|
||||||
|
WriteBinaryFloat(output, vertex.dc0.x);
|
||||||
|
WriteBinaryFloat(output, vertex.rotationWXYZ[2]);
|
||||||
|
for (float coefficient : vertex.sh) {
|
||||||
|
WriteBinaryFloat(output, coefficient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SampleArtifactData BuildSampleArtifactData() {
|
||||||
|
const GaussianSplatPositionRecord positions[2] = {
|
||||||
|
{ Vector3(0.0f, 1.0f, 2.0f) },
|
||||||
|
{ Vector3(3.0f, 4.0f, 5.0f) }
|
||||||
|
};
|
||||||
|
const GaussianSplatOtherRecord other[2] = {
|
||||||
|
{ Quaternion::Identity(), Vector3(1.0f, 1.0f, 1.0f), 0.0f },
|
||||||
|
{ Quaternion(0.0f, 0.5f, 0.0f, 0.8660254f), Vector3(2.0f, 2.0f, 2.0f), 0.0f }
|
||||||
|
};
|
||||||
|
const GaussianSplatColorRecord colors[2] = {
|
||||||
|
{ Vector4(1.0f, 0.0f, 0.0f, 0.25f) },
|
||||||
|
{ Vector4(0.0f, 1.0f, 0.0f, 0.75f) }
|
||||||
|
};
|
||||||
|
|
||||||
|
GaussianSplatSHRecord sh[2];
|
||||||
|
for (XCEngine::Core::uint32 index = 0; index < kGaussianSplatSHCoefficientCount; ++index) {
|
||||||
|
sh[0].coefficients[index] = 0.01f * static_cast<float>(index + 1u);
|
||||||
|
sh[1].coefficients[index] = -0.02f * static_cast<float>(index + 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
SampleArtifactData sample;
|
||||||
|
sample.metadata.contentVersion = 1u;
|
||||||
|
sample.metadata.splatCount = 2u;
|
||||||
|
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.sections.Reserve(4u);
|
||||||
|
size_t payloadOffset = 0u;
|
||||||
|
auto appendSection = [&](GaussianSplatSectionType type,
|
||||||
|
GaussianSplatSectionFormat format,
|
||||||
|
const void* data,
|
||||||
|
size_t dataSize,
|
||||||
|
XCEngine::Core::uint32 elementCount,
|
||||||
|
XCEngine::Core::uint32 elementStride) {
|
||||||
|
GaussianSplatSection section;
|
||||||
|
section.type = type;
|
||||||
|
section.format = format;
|
||||||
|
section.dataOffset = payloadOffset;
|
||||||
|
section.dataSize = dataSize;
|
||||||
|
section.elementCount = elementCount;
|
||||||
|
section.elementStride = elementStride;
|
||||||
|
sample.sections.PushBack(section);
|
||||||
|
|
||||||
|
const size_t newPayloadSize = sample.payload.Size() + dataSize;
|
||||||
|
sample.payload.Resize(newPayloadSize);
|
||||||
|
std::memcpy(sample.payload.Data() + payloadOffset, data, dataSize);
|
||||||
|
payloadOffset = newPayloadSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
appendSection(
|
||||||
|
GaussianSplatSectionType::Positions,
|
||||||
|
GaussianSplatSectionFormat::VectorFloat32,
|
||||||
|
positions,
|
||||||
|
sizeof(positions),
|
||||||
|
2u,
|
||||||
|
sizeof(GaussianSplatPositionRecord));
|
||||||
|
appendSection(
|
||||||
|
GaussianSplatSectionType::Other,
|
||||||
|
GaussianSplatSectionFormat::OtherFloat32,
|
||||||
|
other,
|
||||||
|
sizeof(other),
|
||||||
|
2u,
|
||||||
|
sizeof(GaussianSplatOtherRecord));
|
||||||
|
appendSection(
|
||||||
|
GaussianSplatSectionType::Color,
|
||||||
|
GaussianSplatSectionFormat::ColorRGBA32F,
|
||||||
|
colors,
|
||||||
|
sizeof(colors),
|
||||||
|
2u,
|
||||||
|
sizeof(GaussianSplatColorRecord));
|
||||||
|
appendSection(
|
||||||
|
GaussianSplatSectionType::SH,
|
||||||
|
GaussianSplatSectionFormat::SHFloat32,
|
||||||
|
sh,
|
||||||
|
sizeof(sh),
|
||||||
|
2u,
|
||||||
|
sizeof(GaussianSplatSHRecord));
|
||||||
|
|
||||||
|
return sample;
|
||||||
|
}
|
||||||
|
|
||||||
|
GaussianSplat BuildSampleGaussianSplat(const char* artifactPath) {
|
||||||
|
SampleArtifactData sample = BuildSampleArtifactData();
|
||||||
|
|
||||||
|
GaussianSplat gaussianSplat;
|
||||||
|
XCEngine::Resources::IResource::ConstructParams params;
|
||||||
|
params.name = "sample.xcgsplat";
|
||||||
|
params.path = artifactPath;
|
||||||
|
params.guid = ResourceGUID::Generate(params.path);
|
||||||
|
gaussianSplat.Initialize(params);
|
||||||
|
EXPECT_TRUE(gaussianSplat.CreateOwned(
|
||||||
|
sample.metadata,
|
||||||
|
std::move(sample.sections),
|
||||||
|
std::move(sample.payload)));
|
||||||
|
return gaussianSplat;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path CreateTestProjectRoot(const char* folderName) {
|
||||||
|
return std::filesystem::current_path() / "__xc_gaussian_splat_cache_test_runtime" / folderName;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RenderResourceCacheTest, GetOrCreateGaussianSplatUploadsStructuredSectionsAndReusesEntry) {
|
||||||
|
GaussianSplat gaussianSplat = BuildSampleGaussianSplat("sample.xcgsplat");
|
||||||
|
|
||||||
|
auto state = std::make_shared<MockCacheAllocationState>();
|
||||||
|
MockCacheDevice device(state);
|
||||||
|
RenderResourceCache cache;
|
||||||
|
|
||||||
|
const RenderResourceCache::CachedGaussianSplat* cached =
|
||||||
|
cache.GetOrCreateGaussianSplat(&device, &gaussianSplat);
|
||||||
|
ASSERT_NE(cached, nullptr);
|
||||||
|
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->other.elementStride, sizeof(GaussianSplatOtherRecord));
|
||||||
|
EXPECT_EQ(cached->color.elementStride, sizeof(GaussianSplatColorRecord));
|
||||||
|
EXPECT_EQ(cached->sh.elementStride, sizeof(GaussianSplatSHRecord));
|
||||||
|
EXPECT_EQ(cached->positions.elementCount, 2u);
|
||||||
|
EXPECT_EQ(cached->other.elementCount, 2u);
|
||||||
|
EXPECT_EQ(cached->color.elementCount, 2u);
|
||||||
|
EXPECT_EQ(cached->sh.elementCount, 2u);
|
||||||
|
ASSERT_NE(cached->positions.buffer, nullptr);
|
||||||
|
ASSERT_NE(cached->positions.shaderResourceView, nullptr);
|
||||||
|
ASSERT_NE(cached->color.buffer, nullptr);
|
||||||
|
ASSERT_NE(cached->sh.buffer, nullptr);
|
||||||
|
|
||||||
|
const auto* uploadedPositions = static_cast<const MockCacheBuffer*>(cached->positions.buffer);
|
||||||
|
ASSERT_GE(uploadedPositions->GetBytes().size(), sizeof(GaussianSplatPositionRecord) * 2u);
|
||||||
|
const auto* uploadedPositionRecords = reinterpret_cast<const GaussianSplatPositionRecord*>(
|
||||||
|
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));
|
||||||
|
|
||||||
|
EXPECT_EQ(state->createBufferCalls, 4);
|
||||||
|
EXPECT_EQ(state->createShaderViewCalls, 4);
|
||||||
|
|
||||||
|
const RenderResourceCache::CachedGaussianSplat* cachedAgain =
|
||||||
|
cache.GetOrCreateGaussianSplat(&device, &gaussianSplat);
|
||||||
|
EXPECT_EQ(cachedAgain, cached);
|
||||||
|
EXPECT_EQ(state->createBufferCalls, 4);
|
||||||
|
EXPECT_EQ(state->createShaderViewCalls, 4);
|
||||||
|
|
||||||
|
cache.Shutdown();
|
||||||
|
EXPECT_EQ(state->shutdownBufferCalls, 4);
|
||||||
|
EXPECT_EQ(state->destroyBufferCalls, 4);
|
||||||
|
EXPECT_EQ(state->shutdownShaderViewCalls, 4);
|
||||||
|
EXPECT_EQ(state->destroyShaderViewCalls, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RenderResourceCacheTest, GetOrCreateGaussianSplatSupportsArtifactRuntimeLoadPath) {
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
const fs::path tempDir = fs::temp_directory_path() / "xc_gaussian_splat_render_cache_artifact";
|
||||||
|
const fs::path artifactPath = tempDir / "sample.xcgsplat";
|
||||||
|
|
||||||
|
fs::remove_all(tempDir);
|
||||||
|
fs::create_directories(tempDir);
|
||||||
|
|
||||||
|
const GaussianSplat source = BuildSampleGaussianSplat(artifactPath.string().c_str());
|
||||||
|
XCEngine::Containers::String errorMessage;
|
||||||
|
ASSERT_TRUE(WriteGaussianSplatArtifactFile(artifactPath.string().c_str(), source, &errorMessage))
|
||||||
|
<< errorMessage.CStr();
|
||||||
|
|
||||||
|
ResourceManager& manager = ResourceManager::Get();
|
||||||
|
manager.Initialize();
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto handle = manager.Load<GaussianSplat>(artifactPath.string().c_str());
|
||||||
|
ASSERT_TRUE(handle.IsValid());
|
||||||
|
|
||||||
|
auto state = std::make_shared<MockCacheAllocationState>();
|
||||||
|
MockCacheDevice device(state);
|
||||||
|
RenderResourceCache cache;
|
||||||
|
|
||||||
|
const RenderResourceCache::CachedGaussianSplat* cached =
|
||||||
|
cache.GetOrCreateGaussianSplat(&device, handle.Get());
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.UnloadAll();
|
||||||
|
manager.Shutdown();
|
||||||
|
fs::remove_all(tempDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RenderResourceCacheTest, GetOrCreateGaussianSplatSupportsSourceAssetImportPath) {
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
const fs::path projectRoot = CreateTestProjectRoot("source_asset_import");
|
||||||
|
const fs::path assetsDir = projectRoot / "Assets";
|
||||||
|
const fs::path sourcePath = assetsDir / "sample.ply";
|
||||||
|
|
||||||
|
fs::remove_all(projectRoot);
|
||||||
|
fs::create_directories(assetsDir);
|
||||||
|
|
||||||
|
std::vector<SyntheticGaussianSplatVertex> vertices(2);
|
||||||
|
vertices[0].position = Vector3(1.0f, 2.0f, 3.0f);
|
||||||
|
vertices[0].dc0 = Vector3(0.2f, -0.1f, 0.0f);
|
||||||
|
vertices[0].opacity = 0.25f;
|
||||||
|
vertices[0].scaleLog = Vector3(0.0f, std::log(2.0f), std::log(4.0f));
|
||||||
|
vertices[0].rotationWXYZ[0] = 1.0f;
|
||||||
|
for (XCEngine::Core::uint32 index = 0; index < kGaussianSplatSHCoefficientCount; ++index) {
|
||||||
|
vertices[0].sh[index] = 0.01f * static_cast<float>(index + 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
vertices[1].position = Vector3(-4.0f, -5.0f, -6.0f);
|
||||||
|
vertices[1].dc0 = Vector3(1.0f, 0.5f, -0.5f);
|
||||||
|
vertices[1].opacity = -1.0f;
|
||||||
|
vertices[1].scaleLog = Vector3(std::log(0.5f), 0.0f, std::log(3.0f));
|
||||||
|
vertices[1].rotationWXYZ[2] = 3.0f;
|
||||||
|
vertices[1].rotationWXYZ[3] = 4.0f;
|
||||||
|
for (XCEngine::Core::uint32 index = 0; index < kGaussianSplatSHCoefficientCount; ++index) {
|
||||||
|
vertices[1].sh[index] = -0.02f * static_cast<float>(index + 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteSyntheticGaussianSplatPly(sourcePath, vertices);
|
||||||
|
|
||||||
|
ResourceManager& manager = ResourceManager::Get();
|
||||||
|
manager.Initialize();
|
||||||
|
manager.SetResourceRoot(projectRoot.string().c_str());
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto handle = manager.Load<GaussianSplat>("Assets/sample.ply");
|
||||||
|
ASSERT_TRUE(handle.IsValid());
|
||||||
|
EXPECT_EQ(handle->GetSplatCount(), 2u);
|
||||||
|
|
||||||
|
auto state = std::make_shared<MockCacheAllocationState>();
|
||||||
|
MockCacheDevice device(state);
|
||||||
|
RenderResourceCache cache;
|
||||||
|
|
||||||
|
const RenderResourceCache::CachedGaussianSplat* cached =
|
||||||
|
cache.GetOrCreateGaussianSplat(&device, handle.Get());
|
||||||
|
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);
|
||||||
|
|
||||||
|
const auto handleAgain = manager.Load<GaussianSplat>("Assets/sample.ply");
|
||||||
|
ASSERT_TRUE(handleAgain.IsValid());
|
||||||
|
const RenderResourceCache::CachedGaussianSplat* cachedAgain =
|
||||||
|
cache.GetOrCreateGaussianSplat(&device, handleAgain.Get());
|
||||||
|
EXPECT_EQ(cachedAgain, cached);
|
||||||
|
EXPECT_EQ(state->createBufferCalls, 4);
|
||||||
|
EXPECT_EQ(state->createShaderViewCalls, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.UnloadAll();
|
||||||
|
manager.Shutdown();
|
||||||
|
fs::remove_all(projectRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
Reference in New Issue
Block a user