Formalize GaussianSplat transient pass resources
This commit is contained in:
@@ -526,6 +526,7 @@ add_library(XCEngine STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinObjectIdOutlinePass.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinSelectionOutlinePass.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinVolumetricPass.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/RenderSurface.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Extraction/RenderSceneExtractor.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Extraction/RenderSceneUtility.cpp
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
#include "Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.h"
|
||||
|
||||
#include "Components/GaussianSplatRendererComponent.h"
|
||||
#include "Resources/GaussianSplat/GaussianSplat.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
namespace Passes {
|
||||
namespace Internal {
|
||||
|
||||
namespace {
|
||||
|
||||
bool CreateStructuredBufferViews(
|
||||
RHI::RHIDevice* device,
|
||||
Core::uint32 elementCount,
|
||||
Core::uint32 elementStride,
|
||||
BuiltinGaussianSplatPassResources::CachedBufferView& bufferView) {
|
||||
if (device == nullptr || elementCount == 0u || elementStride == 0u) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::BufferDesc bufferDesc = {};
|
||||
bufferDesc.size = static_cast<Core::uint64>(elementCount) * static_cast<Core::uint64>(elementStride);
|
||||
bufferDesc.stride = elementStride;
|
||||
bufferDesc.bufferType = static_cast<uint32_t>(RHI::BufferType::Storage);
|
||||
bufferDesc.flags = 0u;
|
||||
|
||||
bufferView.buffer = device->CreateBuffer(bufferDesc);
|
||||
if (bufferView.buffer == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bufferView.buffer->SetStride(elementStride);
|
||||
bufferView.buffer->SetBufferType(RHI::BufferType::Storage);
|
||||
bufferView.buffer->SetState(RHI::ResourceStates::Common);
|
||||
|
||||
RHI::ResourceViewDesc viewDesc = {};
|
||||
viewDesc.dimension = RHI::ResourceViewDimension::StructuredBuffer;
|
||||
viewDesc.firstElement = 0u;
|
||||
viewDesc.elementCount = elementCount;
|
||||
viewDesc.structureByteStride = elementStride;
|
||||
|
||||
bufferView.shaderResourceView = device->CreateShaderResourceView(bufferView.buffer, viewDesc);
|
||||
if (bufferView.shaderResourceView == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bufferView.unorderedAccessView = device->CreateUnorderedAccessView(bufferView.buffer, viewDesc);
|
||||
if (bufferView.unorderedAccessView == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bufferView.elementCount = elementCount;
|
||||
bufferView.elementStride = elementStride;
|
||||
bufferView.currentState = RHI::ResourceStates::Common;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BuiltinGaussianSplatPassResources::~BuiltinGaussianSplatPassResources() {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
bool BuiltinGaussianSplatPassResources::EnsureWorkingSet(
|
||||
RHI::RHIDevice* device,
|
||||
const VisibleGaussianSplatItem& visibleGaussianSplat,
|
||||
WorkingSet*& outWorkingSet) {
|
||||
outWorkingSet = nullptr;
|
||||
if (device == nullptr ||
|
||||
visibleGaussianSplat.gaussianSplatRenderer == nullptr ||
|
||||
visibleGaussianSplat.gaussianSplat == nullptr ||
|
||||
!visibleGaussianSplat.gaussianSplat->IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Core::uint32 splatCapacity = visibleGaussianSplat.gaussianSplat->GetSplatCount();
|
||||
if (splatCapacity == 0u) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ResetForDevice(device)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WorkingSet& workingSet = m_workingSets[visibleGaussianSplat.gaussianSplatRenderer];
|
||||
if (workingSet.splatCapacity < splatCapacity) {
|
||||
DestroyBufferView(workingSet.sortDistances);
|
||||
DestroyBufferView(workingSet.orderIndices);
|
||||
DestroyBufferView(workingSet.viewData);
|
||||
if (!RecreateWorkingSet(device, splatCapacity, workingSet)) {
|
||||
m_workingSets.erase(visibleGaussianSplat.gaussianSplatRenderer);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
workingSet.renderer = visibleGaussianSplat.gaussianSplatRenderer;
|
||||
outWorkingSet = &workingSet;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BuiltinGaussianSplatPassResources::EnsureAccumulationSurface(
|
||||
RHI::RHIDevice* device,
|
||||
Core::uint32 width,
|
||||
Core::uint32 height,
|
||||
RHI::Format format,
|
||||
AccumulationSurface*& outSurface) {
|
||||
outSurface = nullptr;
|
||||
if (device == nullptr ||
|
||||
width == 0u ||
|
||||
height == 0u ||
|
||||
format == RHI::Format::Unknown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ResetForDevice(device)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_accumulationSurface.texture == nullptr ||
|
||||
m_accumulationSurface.width != width ||
|
||||
m_accumulationSurface.height != height ||
|
||||
m_accumulationSurface.format != format) {
|
||||
if (!RecreateAccumulationSurface(device, width, height, format)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
outSurface = &m_accumulationSurface;
|
||||
return true;
|
||||
}
|
||||
|
||||
void BuiltinGaussianSplatPassResources::Shutdown() {
|
||||
for (auto& workingSetPair : m_workingSets) {
|
||||
DestroyBufferView(workingSetPair.second.sortDistances);
|
||||
DestroyBufferView(workingSetPair.second.orderIndices);
|
||||
DestroyBufferView(workingSetPair.second.viewData);
|
||||
}
|
||||
|
||||
m_workingSets.clear();
|
||||
DestroyAccumulationSurface(m_accumulationSurface);
|
||||
m_device = nullptr;
|
||||
}
|
||||
|
||||
const BuiltinGaussianSplatPassResources::WorkingSet* BuiltinGaussianSplatPassResources::FindWorkingSet(
|
||||
const Components::GaussianSplatRendererComponent* renderer) const {
|
||||
const auto workingSetIt = m_workingSets.find(renderer);
|
||||
return workingSetIt != m_workingSets.end() ? &workingSetIt->second : nullptr;
|
||||
}
|
||||
|
||||
const BuiltinGaussianSplatPassResources::AccumulationSurface* BuiltinGaussianSplatPassResources::GetAccumulationSurface() const {
|
||||
return m_accumulationSurface.texture != nullptr ? &m_accumulationSurface : nullptr;
|
||||
}
|
||||
|
||||
void BuiltinGaussianSplatPassResources::DestroyBufferView(CachedBufferView& bufferView) {
|
||||
if (bufferView.shaderResourceView != nullptr) {
|
||||
bufferView.shaderResourceView->Shutdown();
|
||||
delete bufferView.shaderResourceView;
|
||||
bufferView.shaderResourceView = nullptr;
|
||||
}
|
||||
|
||||
if (bufferView.unorderedAccessView != nullptr) {
|
||||
bufferView.unorderedAccessView->Shutdown();
|
||||
delete bufferView.unorderedAccessView;
|
||||
bufferView.unorderedAccessView = nullptr;
|
||||
}
|
||||
|
||||
if (bufferView.buffer != nullptr) {
|
||||
bufferView.buffer->Shutdown();
|
||||
delete bufferView.buffer;
|
||||
bufferView.buffer = nullptr;
|
||||
}
|
||||
|
||||
bufferView.elementCount = 0u;
|
||||
bufferView.elementStride = 0u;
|
||||
bufferView.currentState = RHI::ResourceStates::Common;
|
||||
}
|
||||
|
||||
void BuiltinGaussianSplatPassResources::DestroyAccumulationSurface(AccumulationSurface& surface) {
|
||||
if (surface.renderTargetView != nullptr) {
|
||||
surface.renderTargetView->Shutdown();
|
||||
delete surface.renderTargetView;
|
||||
surface.renderTargetView = nullptr;
|
||||
}
|
||||
|
||||
if (surface.shaderResourceView != nullptr) {
|
||||
surface.shaderResourceView->Shutdown();
|
||||
delete surface.shaderResourceView;
|
||||
surface.shaderResourceView = nullptr;
|
||||
}
|
||||
|
||||
if (surface.texture != nullptr) {
|
||||
surface.texture->Shutdown();
|
||||
delete surface.texture;
|
||||
surface.texture = nullptr;
|
||||
}
|
||||
|
||||
surface.width = 0u;
|
||||
surface.height = 0u;
|
||||
surface.format = RHI::Format::Unknown;
|
||||
surface.currentColorState = RHI::ResourceStates::Common;
|
||||
}
|
||||
|
||||
bool BuiltinGaussianSplatPassResources::ResetForDevice(RHI::RHIDevice* device) {
|
||||
if (m_device == nullptr) {
|
||||
m_device = device;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_device == device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Shutdown();
|
||||
m_device = device;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BuiltinGaussianSplatPassResources::RecreateWorkingSet(
|
||||
RHI::RHIDevice* device,
|
||||
Core::uint32 splatCapacity,
|
||||
WorkingSet& workingSet) {
|
||||
if (!CreateStructuredBufferView(device, splatCapacity, kSortDistanceStride, workingSet.sortDistances) ||
|
||||
!CreateStructuredBufferView(device, splatCapacity, kOrderIndexStride, workingSet.orderIndices) ||
|
||||
!CreateStructuredBufferView(device, splatCapacity, kViewDataStride, workingSet.viewData)) {
|
||||
DestroyBufferView(workingSet.sortDistances);
|
||||
DestroyBufferView(workingSet.orderIndices);
|
||||
DestroyBufferView(workingSet.viewData);
|
||||
workingSet.renderer = nullptr;
|
||||
workingSet.splatCapacity = 0u;
|
||||
return false;
|
||||
}
|
||||
|
||||
workingSet.splatCapacity = splatCapacity;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BuiltinGaussianSplatPassResources::CreateStructuredBufferView(
|
||||
RHI::RHIDevice* device,
|
||||
Core::uint32 elementCount,
|
||||
Core::uint32 elementStride,
|
||||
CachedBufferView& bufferView) {
|
||||
return CreateStructuredBufferViews(device, elementCount, elementStride, bufferView);
|
||||
}
|
||||
|
||||
bool BuiltinGaussianSplatPassResources::RecreateAccumulationSurface(
|
||||
RHI::RHIDevice* device,
|
||||
Core::uint32 width,
|
||||
Core::uint32 height,
|
||||
RHI::Format format) {
|
||||
DestroyAccumulationSurface(m_accumulationSurface);
|
||||
|
||||
RHI::TextureDesc textureDesc = {};
|
||||
textureDesc.width = width;
|
||||
textureDesc.height = height;
|
||||
textureDesc.depth = 1u;
|
||||
textureDesc.mipLevels = 1u;
|
||||
textureDesc.arraySize = 1u;
|
||||
textureDesc.format = static_cast<uint32_t>(format);
|
||||
textureDesc.textureType = static_cast<uint32_t>(RHI::TextureType::Texture2D);
|
||||
textureDesc.sampleCount = 1u;
|
||||
textureDesc.sampleQuality = 0u;
|
||||
textureDesc.flags = 0u;
|
||||
|
||||
m_accumulationSurface.texture = device->CreateTexture(textureDesc);
|
||||
if (m_accumulationSurface.texture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::ResourceViewDesc viewDesc = {};
|
||||
viewDesc.format = static_cast<uint32_t>(format);
|
||||
viewDesc.dimension = RHI::ResourceViewDimension::Texture2D;
|
||||
viewDesc.mipLevel = 0u;
|
||||
|
||||
m_accumulationSurface.renderTargetView = device->CreateRenderTargetView(m_accumulationSurface.texture, viewDesc);
|
||||
if (m_accumulationSurface.renderTargetView == nullptr) {
|
||||
DestroyAccumulationSurface(m_accumulationSurface);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_accumulationSurface.shaderResourceView = device->CreateShaderResourceView(m_accumulationSurface.texture, viewDesc);
|
||||
if (m_accumulationSurface.shaderResourceView == nullptr) {
|
||||
DestroyAccumulationSurface(m_accumulationSurface);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_accumulationSurface.width = width;
|
||||
m_accumulationSurface.height = height;
|
||||
m_accumulationSurface.format = format;
|
||||
m_accumulationSurface.currentColorState = RHI::ResourceStates::Common;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Passes
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -0,0 +1,104 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Core/Math/Vector4.h>
|
||||
#include <XCEngine/Core/Types.h>
|
||||
#include <XCEngine/Rendering/FrameData/VisibleGaussianSplatItem.h>
|
||||
#include <XCEngine/RHI/RHIDevice.h>
|
||||
#include <XCEngine/RHI/RHIResourceView.h>
|
||||
#include <XCEngine/RHI/RHITexture.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
namespace Passes {
|
||||
namespace Internal {
|
||||
|
||||
struct GaussianSplatViewData {
|
||||
Math::Vector4 clipCenter = Math::Vector4::Zero();
|
||||
Math::Vector4 ellipseAxisU = Math::Vector4::Zero();
|
||||
Math::Vector4 ellipseAxisV = Math::Vector4::Zero();
|
||||
Math::Vector4 colorOpacity = Math::Vector4::Zero();
|
||||
};
|
||||
|
||||
class BuiltinGaussianSplatPassResources final {
|
||||
public:
|
||||
struct CachedBufferView {
|
||||
RHI::RHIBuffer* buffer = nullptr;
|
||||
RHI::RHIResourceView* shaderResourceView = nullptr;
|
||||
RHI::RHIResourceView* unorderedAccessView = nullptr;
|
||||
Core::uint32 elementCount = 0u;
|
||||
Core::uint32 elementStride = 0u;
|
||||
RHI::ResourceStates currentState = RHI::ResourceStates::Common;
|
||||
};
|
||||
|
||||
struct WorkingSet {
|
||||
const Components::GaussianSplatRendererComponent* renderer = nullptr;
|
||||
Core::uint32 splatCapacity = 0u;
|
||||
CachedBufferView sortDistances = {};
|
||||
CachedBufferView orderIndices = {};
|
||||
CachedBufferView viewData = {};
|
||||
};
|
||||
|
||||
struct AccumulationSurface {
|
||||
Core::uint32 width = 0u;
|
||||
Core::uint32 height = 0u;
|
||||
RHI::Format format = RHI::Format::Unknown;
|
||||
RHI::RHITexture* texture = nullptr;
|
||||
RHI::RHIResourceView* renderTargetView = nullptr;
|
||||
RHI::RHIResourceView* shaderResourceView = nullptr;
|
||||
RHI::ResourceStates currentColorState = RHI::ResourceStates::Common;
|
||||
};
|
||||
|
||||
~BuiltinGaussianSplatPassResources();
|
||||
|
||||
bool EnsureWorkingSet(
|
||||
RHI::RHIDevice* device,
|
||||
const VisibleGaussianSplatItem& visibleGaussianSplat,
|
||||
WorkingSet*& outWorkingSet);
|
||||
bool EnsureAccumulationSurface(
|
||||
RHI::RHIDevice* device,
|
||||
Core::uint32 width,
|
||||
Core::uint32 height,
|
||||
RHI::Format format,
|
||||
AccumulationSurface*& outSurface);
|
||||
void Shutdown();
|
||||
|
||||
size_t GetWorkingSetCount() const { return m_workingSets.size(); }
|
||||
const WorkingSet* FindWorkingSet(
|
||||
const Components::GaussianSplatRendererComponent* renderer) const;
|
||||
const AccumulationSurface* GetAccumulationSurface() const;
|
||||
|
||||
private:
|
||||
static constexpr Core::uint32 kSortDistanceStride = sizeof(float);
|
||||
static constexpr Core::uint32 kOrderIndexStride = sizeof(Core::uint32);
|
||||
static constexpr Core::uint32 kViewDataStride = sizeof(GaussianSplatViewData);
|
||||
|
||||
static void DestroyBufferView(CachedBufferView& bufferView);
|
||||
static void DestroyAccumulationSurface(AccumulationSurface& surface);
|
||||
|
||||
bool ResetForDevice(RHI::RHIDevice* device);
|
||||
bool RecreateWorkingSet(
|
||||
RHI::RHIDevice* device,
|
||||
Core::uint32 splatCapacity,
|
||||
WorkingSet& workingSet);
|
||||
bool CreateStructuredBufferView(
|
||||
RHI::RHIDevice* device,
|
||||
Core::uint32 elementCount,
|
||||
Core::uint32 elementStride,
|
||||
CachedBufferView& bufferView);
|
||||
bool RecreateAccumulationSurface(
|
||||
RHI::RHIDevice* device,
|
||||
Core::uint32 width,
|
||||
Core::uint32 height,
|
||||
RHI::Format format);
|
||||
|
||||
RHI::RHIDevice* m_device = nullptr;
|
||||
std::unordered_map<const Components::GaussianSplatRendererComponent*, WorkingSet> m_workingSets;
|
||||
AccumulationSurface m_accumulationSurface = {};
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Passes
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -6,6 +6,7 @@ set(RENDERING_UNIT_TEST_SOURCES
|
||||
test_render_pass.cpp
|
||||
test_object_id_encoding.cpp
|
||||
test_builtin_forward_pipeline.cpp
|
||||
test_builtin_gaussian_splat_pass_resources.cpp
|
||||
test_camera_scene_renderer.cpp
|
||||
test_scene_render_request_planner.cpp
|
||||
test_scene_render_request_utils.cpp
|
||||
|
||||
@@ -0,0 +1,441 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEngine/Components/GaussianSplatRendererComponent.h>
|
||||
#include <XCEngine/Components/GameObject.h>
|
||||
#include <XCEngine/Core/Asset/IResource.h>
|
||||
#include <XCEngine/Resources/GaussianSplat/GaussianSplat.h>
|
||||
#include <XCEngine/RHI/RHIResourceView.h>
|
||||
#include <XCEngine/RHI/RHITexture.h>
|
||||
|
||||
#include "Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
using namespace XCEngine::Components;
|
||||
using namespace XCEngine::Rendering;
|
||||
using namespace XCEngine::Rendering::Passes::Internal;
|
||||
using namespace XCEngine::Resources;
|
||||
|
||||
namespace {
|
||||
|
||||
struct MockGaussianSplatResourceState {
|
||||
int createBufferCalls = 0;
|
||||
int bufferShutdownCalls = 0;
|
||||
int bufferDestroyCalls = 0;
|
||||
int createBufferShaderViewCalls = 0;
|
||||
int createBufferUavCalls = 0;
|
||||
int createTextureCalls = 0;
|
||||
int textureShutdownCalls = 0;
|
||||
int textureDestroyCalls = 0;
|
||||
int createTextureShaderViewCalls = 0;
|
||||
int createRenderTargetViewCalls = 0;
|
||||
int resourceViewShutdownCalls = 0;
|
||||
int resourceViewDestroyCalls = 0;
|
||||
};
|
||||
|
||||
class MockGaussianSplatBuffer final : public XCEngine::RHI::RHIBuffer {
|
||||
public:
|
||||
MockGaussianSplatBuffer(
|
||||
std::shared_ptr<MockGaussianSplatResourceState> state,
|
||||
const XCEngine::RHI::BufferDesc& desc)
|
||||
: m_state(std::move(state))
|
||||
, m_size(desc.size)
|
||||
, m_stride(desc.stride)
|
||||
, m_type(static_cast<XCEngine::RHI::BufferType>(desc.bufferType)) {
|
||||
}
|
||||
|
||||
~MockGaussianSplatBuffer() override {
|
||||
++m_state->bufferDestroyCalls;
|
||||
}
|
||||
|
||||
void* Map() override { return nullptr; }
|
||||
void Unmap() override {}
|
||||
void SetData(const void*, size_t, size_t) override {}
|
||||
uint64_t GetSize() const override { return m_size; }
|
||||
XCEngine::RHI::BufferType GetBufferType() const override { return m_type; }
|
||||
void SetBufferType(XCEngine::RHI::BufferType type) override { m_type = 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_resourceState; }
|
||||
void SetState(XCEngine::RHI::ResourceStates state) override { m_resourceState = 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->bufferShutdownCalls;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<MockGaussianSplatResourceState> m_state;
|
||||
uint64_t m_size = 0u;
|
||||
uint32_t m_stride = 0u;
|
||||
XCEngine::RHI::BufferType m_type = XCEngine::RHI::BufferType::Storage;
|
||||
XCEngine::RHI::ResourceStates m_resourceState = XCEngine::RHI::ResourceStates::Common;
|
||||
std::string m_name;
|
||||
};
|
||||
|
||||
class MockGaussianSplatTexture final : public XCEngine::RHI::RHITexture {
|
||||
public:
|
||||
MockGaussianSplatTexture(
|
||||
std::shared_ptr<MockGaussianSplatResourceState> state,
|
||||
const XCEngine::RHI::TextureDesc& desc)
|
||||
: m_state(std::move(state))
|
||||
, m_width(desc.width)
|
||||
, m_height(desc.height)
|
||||
, m_format(static_cast<XCEngine::RHI::Format>(desc.format))
|
||||
, m_type(static_cast<XCEngine::RHI::TextureType>(desc.textureType)) {
|
||||
}
|
||||
|
||||
~MockGaussianSplatTexture() override {
|
||||
++m_state->textureDestroyCalls;
|
||||
}
|
||||
|
||||
uint32_t GetWidth() const override { return m_width; }
|
||||
uint32_t GetHeight() const override { return m_height; }
|
||||
uint32_t GetDepth() const override { return 1u; }
|
||||
uint32_t GetMipLevels() const override { return 1u; }
|
||||
XCEngine::RHI::Format GetFormat() const override { return m_format; }
|
||||
XCEngine::RHI::TextureType GetTextureType() const override { return m_type; }
|
||||
XCEngine::RHI::ResourceStates GetState() const override { return m_resourceState; }
|
||||
void SetState(XCEngine::RHI::ResourceStates state) override { m_resourceState = state; }
|
||||
void* GetNativeHandle() override { return nullptr; }
|
||||
const std::string& GetName() const override { return m_name; }
|
||||
void SetName(const std::string& name) override { m_name = name; }
|
||||
|
||||
void Shutdown() override {
|
||||
++m_state->textureShutdownCalls;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<MockGaussianSplatResourceState> m_state;
|
||||
uint32_t m_width = 0u;
|
||||
uint32_t m_height = 0u;
|
||||
XCEngine::RHI::Format m_format = XCEngine::RHI::Format::Unknown;
|
||||
XCEngine::RHI::TextureType m_type = XCEngine::RHI::TextureType::Texture2D;
|
||||
XCEngine::RHI::ResourceStates m_resourceState = XCEngine::RHI::ResourceStates::Common;
|
||||
std::string m_name;
|
||||
};
|
||||
|
||||
class MockGaussianSplatResourceView final : public XCEngine::RHI::RHIResourceView {
|
||||
public:
|
||||
MockGaussianSplatResourceView(
|
||||
std::shared_ptr<MockGaussianSplatResourceState> state,
|
||||
XCEngine::RHI::ResourceViewType type,
|
||||
XCEngine::RHI::Format format,
|
||||
XCEngine::RHI::ResourceViewDimension dimension)
|
||||
: m_state(std::move(state))
|
||||
, m_type(type)
|
||||
, m_format(format)
|
||||
, m_dimension(dimension) {
|
||||
}
|
||||
|
||||
~MockGaussianSplatResourceView() override {
|
||||
++m_state->resourceViewDestroyCalls;
|
||||
}
|
||||
|
||||
void Shutdown() override {
|
||||
++m_state->resourceViewShutdownCalls;
|
||||
}
|
||||
|
||||
void* GetNativeHandle() override { return nullptr; }
|
||||
bool IsValid() const override { return true; }
|
||||
XCEngine::RHI::ResourceViewType GetViewType() const override { return m_type; }
|
||||
XCEngine::RHI::ResourceViewDimension GetDimension() const override { return m_dimension; }
|
||||
XCEngine::RHI::Format GetFormat() const override { return m_format; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<MockGaussianSplatResourceState> m_state;
|
||||
XCEngine::RHI::ResourceViewType m_type = XCEngine::RHI::ResourceViewType::ShaderResource;
|
||||
XCEngine::RHI::Format m_format = XCEngine::RHI::Format::Unknown;
|
||||
XCEngine::RHI::ResourceViewDimension m_dimension = XCEngine::RHI::ResourceViewDimension::Unknown;
|
||||
};
|
||||
|
||||
class MockGaussianSplatDevice final : public XCEngine::RHI::RHIDevice {
|
||||
public:
|
||||
explicit MockGaussianSplatDevice(std::shared_ptr<MockGaussianSplatResourceState> 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;
|
||||
return new MockGaussianSplatBuffer(m_state, desc);
|
||||
}
|
||||
|
||||
XCEngine::RHI::RHITexture* CreateTexture(const XCEngine::RHI::TextureDesc& desc) override {
|
||||
++m_state->createTextureCalls;
|
||||
return new MockGaussianSplatTexture(m_state, desc);
|
||||
}
|
||||
|
||||
XCEngine::RHI::RHITexture* CreateTexture(
|
||||
const XCEngine::RHI::TextureDesc& desc,
|
||||
const void*,
|
||||
size_t,
|
||||
uint32_t) override {
|
||||
return CreateTexture(desc);
|
||||
}
|
||||
|
||||
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& desc) override {
|
||||
++m_state->createRenderTargetViewCalls;
|
||||
return new MockGaussianSplatResourceView(
|
||||
m_state,
|
||||
XCEngine::RHI::ResourceViewType::RenderTarget,
|
||||
static_cast<XCEngine::RHI::Format>(desc.format),
|
||||
desc.dimension);
|
||||
}
|
||||
|
||||
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->createBufferShaderViewCalls;
|
||||
return new MockGaussianSplatResourceView(
|
||||
m_state,
|
||||
XCEngine::RHI::ResourceViewType::ShaderResource,
|
||||
static_cast<XCEngine::RHI::Format>(desc.format),
|
||||
desc.dimension);
|
||||
}
|
||||
|
||||
XCEngine::RHI::RHIResourceView* CreateShaderResourceView(
|
||||
XCEngine::RHI::RHITexture*,
|
||||
const XCEngine::RHI::ResourceViewDesc& desc) override {
|
||||
++m_state->createTextureShaderViewCalls;
|
||||
return new MockGaussianSplatResourceView(
|
||||
m_state,
|
||||
XCEngine::RHI::ResourceViewType::ShaderResource,
|
||||
static_cast<XCEngine::RHI::Format>(desc.format),
|
||||
desc.dimension);
|
||||
}
|
||||
|
||||
XCEngine::RHI::RHIResourceView* CreateUnorderedAccessView(
|
||||
XCEngine::RHI::RHIBuffer*,
|
||||
const XCEngine::RHI::ResourceViewDesc& desc) override {
|
||||
++m_state->createBufferUavCalls;
|
||||
return new MockGaussianSplatResourceView(
|
||||
m_state,
|
||||
XCEngine::RHI::ResourceViewType::UnorderedAccess,
|
||||
static_cast<XCEngine::RHI::Format>(desc.format),
|
||||
desc.dimension);
|
||||
}
|
||||
|
||||
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_info; }
|
||||
void* GetNativeDevice() override { return nullptr; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<MockGaussianSplatResourceState> m_state;
|
||||
XCEngine::RHI::RHICapabilities m_capabilities = {};
|
||||
XCEngine::RHI::RHIDeviceInfo m_info = {};
|
||||
};
|
||||
|
||||
GaussianSplat* CreateTestGaussianSplat(const char* path, XCEngine::Core::uint32 splatCount) {
|
||||
auto* gaussianSplat = new GaussianSplat();
|
||||
IResource::ConstructParams params = {};
|
||||
params.name = "TestGaussianSplat";
|
||||
params.path = path;
|
||||
params.guid = ResourceGUID::Generate(path);
|
||||
gaussianSplat->Initialize(params);
|
||||
|
||||
GaussianSplatMetadata metadata = {};
|
||||
metadata.splatCount = splatCount;
|
||||
|
||||
XCEngine::Containers::Array<GaussianSplatSection> sections;
|
||||
sections.Resize(1);
|
||||
sections[0].type = GaussianSplatSectionType::Positions;
|
||||
sections[0].format = GaussianSplatSectionFormat::VectorFloat32;
|
||||
sections[0].dataOffset = 0u;
|
||||
sections[0].dataSize = static_cast<XCEngine::Core::uint64>(splatCount) * sizeof(GaussianSplatPositionRecord);
|
||||
sections[0].elementCount = splatCount;
|
||||
sections[0].elementStride = sizeof(GaussianSplatPositionRecord);
|
||||
|
||||
XCEngine::Containers::Array<XCEngine::Core::uint8> payload;
|
||||
payload.Resize(static_cast<size_t>(sections[0].dataSize));
|
||||
for (XCEngine::Core::uint32 index = 0; index < splatCount; ++index) {
|
||||
const GaussianSplatPositionRecord positionRecord = { XCEngine::Math::Vector3(static_cast<float>(index), 0.0f, 0.0f) };
|
||||
std::memcpy(
|
||||
payload.Data() + (static_cast<size_t>(index) * sizeof(GaussianSplatPositionRecord)),
|
||||
&positionRecord,
|
||||
sizeof(positionRecord));
|
||||
}
|
||||
|
||||
EXPECT_TRUE(gaussianSplat->CreateOwned(metadata, std::move(sections), std::move(payload)));
|
||||
return gaussianSplat;
|
||||
}
|
||||
|
||||
VisibleGaussianSplatItem BuildVisibleGaussianSplatItem(
|
||||
GameObject& gameObject,
|
||||
GaussianSplatRendererComponent& renderer,
|
||||
GaussianSplat& gaussianSplat) {
|
||||
VisibleGaussianSplatItem item = {};
|
||||
item.gameObject = &gameObject;
|
||||
item.gaussianSplatRenderer = &renderer;
|
||||
item.gaussianSplat = &gaussianSplat;
|
||||
return item;
|
||||
}
|
||||
|
||||
TEST(BuiltinGaussianSplatPassResources_Test, EnsureWorkingSetAllocatesAndReusesStructuredBuffersPerRenderer) {
|
||||
auto state = std::make_shared<MockGaussianSplatResourceState>();
|
||||
MockGaussianSplatDevice device(state);
|
||||
BuiltinGaussianSplatPassResources resources;
|
||||
|
||||
GameObject gameObject("GaussianSplatObject");
|
||||
auto* renderer = gameObject.AddComponent<GaussianSplatRendererComponent>();
|
||||
std::unique_ptr<GaussianSplat> gaussianSplat(CreateTestGaussianSplat("GaussianSplats/room.xcgsplat", 8u));
|
||||
VisibleGaussianSplatItem item = BuildVisibleGaussianSplatItem(gameObject, *renderer, *gaussianSplat);
|
||||
|
||||
BuiltinGaussianSplatPassResources::WorkingSet* workingSet = nullptr;
|
||||
ASSERT_TRUE(resources.EnsureWorkingSet(&device, item, workingSet));
|
||||
ASSERT_NE(workingSet, nullptr);
|
||||
EXPECT_EQ(resources.GetWorkingSetCount(), 1u);
|
||||
EXPECT_EQ(workingSet->renderer, renderer);
|
||||
EXPECT_EQ(workingSet->splatCapacity, 8u);
|
||||
EXPECT_EQ(workingSet->sortDistances.elementStride, sizeof(float));
|
||||
EXPECT_EQ(workingSet->orderIndices.elementStride, sizeof(XCEngine::Core::uint32));
|
||||
EXPECT_EQ(workingSet->viewData.elementStride, sizeof(GaussianSplatViewData));
|
||||
EXPECT_EQ(workingSet->sortDistances.shaderResourceView->GetDimension(), XCEngine::RHI::ResourceViewDimension::StructuredBuffer);
|
||||
EXPECT_EQ(workingSet->sortDistances.unorderedAccessView->GetViewType(), XCEngine::RHI::ResourceViewType::UnorderedAccess);
|
||||
EXPECT_EQ(state->createBufferCalls, 3);
|
||||
EXPECT_EQ(state->createBufferShaderViewCalls, 3);
|
||||
EXPECT_EQ(state->createBufferUavCalls, 3);
|
||||
|
||||
BuiltinGaussianSplatPassResources::WorkingSet* reusedWorkingSet = nullptr;
|
||||
ASSERT_TRUE(resources.EnsureWorkingSet(&device, item, reusedWorkingSet));
|
||||
EXPECT_EQ(reusedWorkingSet, workingSet);
|
||||
EXPECT_EQ(state->createBufferCalls, 3);
|
||||
EXPECT_EQ(state->createBufferShaderViewCalls, 3);
|
||||
EXPECT_EQ(state->createBufferUavCalls, 3);
|
||||
}
|
||||
|
||||
TEST(BuiltinGaussianSplatPassResources_Test, EnsureWorkingSetKeepsPerRendererIsolationAndRecreatesOnCapacityGrowth) {
|
||||
auto state = std::make_shared<MockGaussianSplatResourceState>();
|
||||
MockGaussianSplatDevice device(state);
|
||||
BuiltinGaussianSplatPassResources resources;
|
||||
|
||||
GameObject firstObject("FirstGaussianSplat");
|
||||
auto* firstRenderer = firstObject.AddComponent<GaussianSplatRendererComponent>();
|
||||
std::unique_ptr<GaussianSplat> firstGaussianSplat(CreateTestGaussianSplat("GaussianSplats/first.xcgsplat", 4u));
|
||||
VisibleGaussianSplatItem firstItem = BuildVisibleGaussianSplatItem(firstObject, *firstRenderer, *firstGaussianSplat);
|
||||
|
||||
GameObject secondObject("SecondGaussianSplat");
|
||||
auto* secondRenderer = secondObject.AddComponent<GaussianSplatRendererComponent>();
|
||||
std::unique_ptr<GaussianSplat> secondGaussianSplat(CreateTestGaussianSplat("GaussianSplats/second.xcgsplat", 4u));
|
||||
VisibleGaussianSplatItem secondItem = BuildVisibleGaussianSplatItem(secondObject, *secondRenderer, *secondGaussianSplat);
|
||||
|
||||
BuiltinGaussianSplatPassResources::WorkingSet* firstWorkingSet = nullptr;
|
||||
BuiltinGaussianSplatPassResources::WorkingSet* secondWorkingSet = nullptr;
|
||||
ASSERT_TRUE(resources.EnsureWorkingSet(&device, firstItem, firstWorkingSet));
|
||||
ASSERT_TRUE(resources.EnsureWorkingSet(&device, secondItem, secondWorkingSet));
|
||||
ASSERT_NE(firstWorkingSet, nullptr);
|
||||
ASSERT_NE(secondWorkingSet, nullptr);
|
||||
EXPECT_NE(firstWorkingSet, secondWorkingSet);
|
||||
EXPECT_EQ(resources.GetWorkingSetCount(), 2u);
|
||||
EXPECT_EQ(state->createBufferCalls, 6);
|
||||
|
||||
std::unique_ptr<GaussianSplat> grownGaussianSplat(CreateTestGaussianSplat("GaussianSplats/first_grown.xcgsplat", 12u));
|
||||
firstItem.gaussianSplat = grownGaussianSplat.get();
|
||||
|
||||
BuiltinGaussianSplatPassResources::WorkingSet* grownWorkingSet = nullptr;
|
||||
ASSERT_TRUE(resources.EnsureWorkingSet(&device, firstItem, grownWorkingSet));
|
||||
ASSERT_NE(grownWorkingSet, nullptr);
|
||||
EXPECT_EQ(grownWorkingSet, resources.FindWorkingSet(firstRenderer));
|
||||
EXPECT_EQ(grownWorkingSet->splatCapacity, 12u);
|
||||
EXPECT_EQ(resources.GetWorkingSetCount(), 2u);
|
||||
EXPECT_EQ(state->createBufferCalls, 9);
|
||||
EXPECT_GE(state->bufferShutdownCalls, 3);
|
||||
EXPECT_GE(state->bufferDestroyCalls, 3);
|
||||
}
|
||||
|
||||
TEST(BuiltinGaussianSplatPassResources_Test, EnsureAccumulationSurfaceReusesCompatibleTargetAndRecreatesOnResize) {
|
||||
auto state = std::make_shared<MockGaussianSplatResourceState>();
|
||||
MockGaussianSplatDevice device(state);
|
||||
BuiltinGaussianSplatPassResources resources;
|
||||
|
||||
BuiltinGaussianSplatPassResources::AccumulationSurface* surface = nullptr;
|
||||
ASSERT_TRUE(resources.EnsureAccumulationSurface(
|
||||
&device,
|
||||
640u,
|
||||
360u,
|
||||
XCEngine::RHI::Format::R16G16B16A16_Float,
|
||||
surface));
|
||||
ASSERT_NE(surface, nullptr);
|
||||
EXPECT_EQ(surface->width, 640u);
|
||||
EXPECT_EQ(surface->height, 360u);
|
||||
EXPECT_EQ(surface->format, XCEngine::RHI::Format::R16G16B16A16_Float);
|
||||
EXPECT_EQ(state->createTextureCalls, 1);
|
||||
EXPECT_EQ(state->createRenderTargetViewCalls, 1);
|
||||
EXPECT_EQ(state->createTextureShaderViewCalls, 1);
|
||||
|
||||
BuiltinGaussianSplatPassResources::AccumulationSurface* reusedSurface = nullptr;
|
||||
ASSERT_TRUE(resources.EnsureAccumulationSurface(
|
||||
&device,
|
||||
640u,
|
||||
360u,
|
||||
XCEngine::RHI::Format::R16G16B16A16_Float,
|
||||
reusedSurface));
|
||||
EXPECT_EQ(reusedSurface, surface);
|
||||
EXPECT_EQ(state->createTextureCalls, 1);
|
||||
|
||||
BuiltinGaussianSplatPassResources::AccumulationSurface* resizedSurface = nullptr;
|
||||
ASSERT_TRUE(resources.EnsureAccumulationSurface(
|
||||
&device,
|
||||
800u,
|
||||
600u,
|
||||
XCEngine::RHI::Format::R16G16B16A16_Float,
|
||||
resizedSurface));
|
||||
ASSERT_NE(resizedSurface, nullptr);
|
||||
EXPECT_EQ(resizedSurface->width, 800u);
|
||||
EXPECT_EQ(resizedSurface->height, 600u);
|
||||
EXPECT_EQ(state->createTextureCalls, 2);
|
||||
EXPECT_GE(state->textureShutdownCalls, 1);
|
||||
EXPECT_GE(state->textureDestroyCalls, 1);
|
||||
EXPECT_GE(state->resourceViewShutdownCalls, 2);
|
||||
EXPECT_GE(state->resourceViewDestroyCalls, 2);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user