711 lines
29 KiB
C++
711 lines
29 KiB
C++
|
|
#include <gtest/gtest.h>
|
||
|
|
|
||
|
|
#include "XCUIBackend/XCUIRHIRenderBackend.h"
|
||
|
|
|
||
|
|
#include <XCEngine/Rendering/RenderContext.h>
|
||
|
|
#include <XCEngine/Rendering/RenderSurface.h>
|
||
|
|
#include <XCEngine/RHI/RHIBuffer.h>
|
||
|
|
#include <XCEngine/RHI/RHICapabilities.h>
|
||
|
|
#include <XCEngine/RHI/RHICommandList.h>
|
||
|
|
#include <XCEngine/RHI/RHICommandQueue.h>
|
||
|
|
#include <XCEngine/RHI/RHIDevice.h>
|
||
|
|
#include <XCEngine/RHI/RHIDescriptorPool.h>
|
||
|
|
#include <XCEngine/RHI/RHIDescriptorSet.h>
|
||
|
|
#include <XCEngine/RHI/RHIPipelineLayout.h>
|
||
|
|
#include <XCEngine/RHI/RHIPipelineState.h>
|
||
|
|
#include <XCEngine/RHI/RHIResourceView.h>
|
||
|
|
#include <XCEngine/RHI/RHISampler.h>
|
||
|
|
#include <XCEngine/UI/DrawData.h>
|
||
|
|
|
||
|
|
#include <array>
|
||
|
|
#include <cstdint>
|
||
|
|
#include <cstring>
|
||
|
|
#include <type_traits>
|
||
|
|
#include <vector>
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
using XCEngine::Editor::XCUIBackend::IXCUITextAtlasProvider;
|
||
|
|
using XCEngine::Editor::XCUIBackend::XCUIRHIRenderBackend;
|
||
|
|
using XCEngine::Rendering::RenderContext;
|
||
|
|
using XCEngine::Rendering::RenderSurface;
|
||
|
|
using XCEngine::RHI::BlendDesc;
|
||
|
|
using XCEngine::RHI::BufferDesc;
|
||
|
|
using XCEngine::RHI::BufferType;
|
||
|
|
using XCEngine::RHI::CommandQueueType;
|
||
|
|
using XCEngine::RHI::ComparisonFunc;
|
||
|
|
using XCEngine::RHI::DepthStencilStateDesc;
|
||
|
|
using XCEngine::RHI::DescriptorHeapType;
|
||
|
|
using XCEngine::RHI::DescriptorPoolDesc;
|
||
|
|
using XCEngine::RHI::DescriptorSetLayoutBinding;
|
||
|
|
using XCEngine::RHI::DescriptorSetLayoutDesc;
|
||
|
|
using XCEngine::RHI::Format;
|
||
|
|
using XCEngine::RHI::GraphicsPipelineDesc;
|
||
|
|
using XCEngine::RHI::InputLayoutDesc;
|
||
|
|
using XCEngine::RHI::PipelineStateHash;
|
||
|
|
using XCEngine::RHI::PipelineType;
|
||
|
|
using XCEngine::RHI::PrimitiveTopology;
|
||
|
|
using XCEngine::RHI::RasterizerDesc;
|
||
|
|
using XCEngine::RHI::Rect;
|
||
|
|
using XCEngine::RHI::ResourceStates;
|
||
|
|
using XCEngine::RHI::ResourceViewDesc;
|
||
|
|
using XCEngine::RHI::ResourceViewDimension;
|
||
|
|
using XCEngine::RHI::ResourceViewType;
|
||
|
|
using XCEngine::RHI::RHICommandList;
|
||
|
|
using XCEngine::RHI::RHICommandQueue;
|
||
|
|
using XCEngine::RHI::RHIDescriptorPool;
|
||
|
|
using XCEngine::RHI::RHIDescriptorSet;
|
||
|
|
using XCEngine::RHI::RHIDevice;
|
||
|
|
using XCEngine::RHI::RHIPipelineLayout;
|
||
|
|
using XCEngine::RHI::RHIPipelineState;
|
||
|
|
using XCEngine::RHI::RHIResourceView;
|
||
|
|
using XCEngine::RHI::RHISampler;
|
||
|
|
using XCEngine::RHI::RHIBuffer;
|
||
|
|
using XCEngine::RHI::RHIShader;
|
||
|
|
using XCEngine::RHI::RHIType;
|
||
|
|
using XCEngine::RHI::SamplerDesc;
|
||
|
|
using XCEngine::RHI::Viewport;
|
||
|
|
using XCEngine::UI::UIColor;
|
||
|
|
using XCEngine::UI::UIDrawData;
|
||
|
|
using XCEngine::UI::UIDrawList;
|
||
|
|
using XCEngine::UI::UIRect;
|
||
|
|
using XCEngine::UI::UITextureHandle;
|
||
|
|
using XCEngine::UI::UITextureHandleKind;
|
||
|
|
|
||
|
|
class FakeResourceViewBase {
|
||
|
|
public:
|
||
|
|
explicit FakeResourceViewBase(
|
||
|
|
ResourceViewType viewType,
|
||
|
|
ResourceViewDimension dimension = ResourceViewDimension::Texture2D,
|
||
|
|
Format format = Format::R8G8B8A8_UNorm)
|
||
|
|
: m_viewType(viewType)
|
||
|
|
, m_dimension(dimension)
|
||
|
|
, m_format(format) {
|
||
|
|
}
|
||
|
|
|
||
|
|
void ShutdownBase() {
|
||
|
|
m_valid = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
void* GetNativeHandleBase() {
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool IsValidBase() const {
|
||
|
|
return m_valid;
|
||
|
|
}
|
||
|
|
|
||
|
|
ResourceViewType GetViewTypeBase() const {
|
||
|
|
return m_viewType;
|
||
|
|
}
|
||
|
|
|
||
|
|
ResourceViewDimension GetDimensionBase() const {
|
||
|
|
return m_dimension;
|
||
|
|
}
|
||
|
|
|
||
|
|
Format GetFormatBase() const {
|
||
|
|
return m_format;
|
||
|
|
}
|
||
|
|
|
||
|
|
void SetValid(bool valid) {
|
||
|
|
m_valid = valid;
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
ResourceViewType m_viewType = ResourceViewType::ShaderResource;
|
||
|
|
ResourceViewDimension m_dimension = ResourceViewDimension::Texture2D;
|
||
|
|
Format m_format = Format::R8G8B8A8_UNorm;
|
||
|
|
bool m_valid = true;
|
||
|
|
};
|
||
|
|
|
||
|
|
class FakeShaderResourceView final : public XCEngine::RHI::RHIShaderResourceView {
|
||
|
|
public:
|
||
|
|
explicit FakeShaderResourceView(bool valid = true)
|
||
|
|
: m_base(ResourceViewType::ShaderResource) {
|
||
|
|
m_base.SetValid(valid);
|
||
|
|
}
|
||
|
|
|
||
|
|
void Shutdown() override { m_base.ShutdownBase(); }
|
||
|
|
void* GetNativeHandle() override { return m_base.GetNativeHandleBase(); }
|
||
|
|
bool IsValid() const override { return m_base.IsValidBase(); }
|
||
|
|
ResourceViewType GetViewType() const override { return m_base.GetViewTypeBase(); }
|
||
|
|
ResourceViewDimension GetDimension() const override { return m_base.GetDimensionBase(); }
|
||
|
|
Format GetFormat() const override { return m_base.GetFormatBase(); }
|
||
|
|
|
||
|
|
private:
|
||
|
|
FakeResourceViewBase m_base;
|
||
|
|
};
|
||
|
|
|
||
|
|
class FakeRenderTargetView final : public XCEngine::RHI::RHIRenderTargetView {
|
||
|
|
public:
|
||
|
|
FakeRenderTargetView()
|
||
|
|
: m_base(ResourceViewType::RenderTarget) {
|
||
|
|
}
|
||
|
|
|
||
|
|
void Shutdown() override { m_base.ShutdownBase(); }
|
||
|
|
void* GetNativeHandle() override { return m_base.GetNativeHandleBase(); }
|
||
|
|
bool IsValid() const override { return m_base.IsValidBase(); }
|
||
|
|
ResourceViewType GetViewType() const override { return m_base.GetViewTypeBase(); }
|
||
|
|
ResourceViewDimension GetDimension() const override { return m_base.GetDimensionBase(); }
|
||
|
|
Format GetFormat() const override { return m_base.GetFormatBase(); }
|
||
|
|
|
||
|
|
private:
|
||
|
|
FakeResourceViewBase m_base;
|
||
|
|
};
|
||
|
|
|
||
|
|
class FakeVertexBufferView final : public XCEngine::RHI::RHIVertexBufferView {
|
||
|
|
public:
|
||
|
|
explicit FakeVertexBufferView(std::uint32_t size, std::uint32_t stride)
|
||
|
|
: m_size(size)
|
||
|
|
, m_stride(stride)
|
||
|
|
, m_base(ResourceViewType::VertexBuffer, ResourceViewDimension::Buffer, Format::Unknown) {
|
||
|
|
}
|
||
|
|
|
||
|
|
void Shutdown() override { m_base.ShutdownBase(); }
|
||
|
|
void* GetNativeHandle() override { return m_base.GetNativeHandleBase(); }
|
||
|
|
bool IsValid() const override { return m_base.IsValidBase(); }
|
||
|
|
ResourceViewType GetViewType() const override { return m_base.GetViewTypeBase(); }
|
||
|
|
ResourceViewDimension GetDimension() const override { return m_base.GetDimensionBase(); }
|
||
|
|
Format GetFormat() const override { return m_base.GetFormatBase(); }
|
||
|
|
std::uint64_t GetBufferAddress() const override { return 0u; }
|
||
|
|
std::uint32_t GetSize() const override { return m_size; }
|
||
|
|
std::uint32_t GetStride() const override { return m_stride; }
|
||
|
|
|
||
|
|
private:
|
||
|
|
std::uint32_t m_size = 0;
|
||
|
|
std::uint32_t m_stride = 0;
|
||
|
|
FakeResourceViewBase m_base;
|
||
|
|
};
|
||
|
|
|
||
|
|
class FakeBuffer final : public RHIBuffer {
|
||
|
|
public:
|
||
|
|
explicit FakeBuffer(const BufferDesc& desc)
|
||
|
|
: m_size(desc.size)
|
||
|
|
, m_stride(desc.stride)
|
||
|
|
, m_type(static_cast<BufferType>(desc.bufferType))
|
||
|
|
, m_storage(static_cast<std::size_t>(desc.size), 0u) {
|
||
|
|
}
|
||
|
|
|
||
|
|
void* Map() override { return m_storage.data(); }
|
||
|
|
void Unmap() override {}
|
||
|
|
|
||
|
|
void SetData(const void* data, size_t size, size_t offset = 0) override {
|
||
|
|
if (data == nullptr || offset + size > m_storage.size()) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
std::memcpy(m_storage.data() + offset, data, size);
|
||
|
|
}
|
||
|
|
|
||
|
|
std::uint64_t GetSize() const override { return m_size; }
|
||
|
|
BufferType GetBufferType() const override { return m_type; }
|
||
|
|
void SetBufferType(BufferType type) override { m_type = type; }
|
||
|
|
std::uint32_t GetStride() const override { return m_stride; }
|
||
|
|
void SetStride(std::uint32_t stride) override { m_stride = stride; }
|
||
|
|
void* GetNativeHandle() override { return this; }
|
||
|
|
ResourceStates GetState() const override { return m_state; }
|
||
|
|
void SetState(ResourceStates state) override { m_state = state; }
|
||
|
|
const std::string& GetName() const override { return m_name; }
|
||
|
|
void SetName(const std::string& name) override { m_name = name; }
|
||
|
|
void Shutdown() override { m_storage.clear(); }
|
||
|
|
|
||
|
|
private:
|
||
|
|
std::uint64_t m_size = 0;
|
||
|
|
std::uint32_t m_stride = 0;
|
||
|
|
BufferType m_type = BufferType::Vertex;
|
||
|
|
ResourceStates m_state = ResourceStates::Common;
|
||
|
|
std::string m_name = {};
|
||
|
|
std::vector<std::uint8_t> m_storage = {};
|
||
|
|
};
|
||
|
|
|
||
|
|
class FakeDescriptorSet final : public RHIDescriptorSet {
|
||
|
|
public:
|
||
|
|
explicit FakeDescriptorSet(const DescriptorSetLayoutDesc& layout) {
|
||
|
|
if (layout.bindings != nullptr && layout.bindingCount > 0u) {
|
||
|
|
m_bindings.assign(layout.bindings, layout.bindings + layout.bindingCount);
|
||
|
|
m_views.resize(layout.bindingCount, nullptr);
|
||
|
|
m_samplers.resize(layout.bindingCount, nullptr);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void Shutdown() override {
|
||
|
|
m_views.clear();
|
||
|
|
m_samplers.clear();
|
||
|
|
m_constantBuffer.clear();
|
||
|
|
m_constantDirty = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
void Bind() override {}
|
||
|
|
void Unbind() override {}
|
||
|
|
|
||
|
|
void Update(std::uint32_t offset, RHIResourceView* view) override {
|
||
|
|
if (offset >= m_views.size()) {
|
||
|
|
m_views.resize(offset + 1u, nullptr);
|
||
|
|
}
|
||
|
|
m_views[offset] = view;
|
||
|
|
}
|
||
|
|
|
||
|
|
void UpdateSampler(std::uint32_t offset, RHISampler* sampler) override {
|
||
|
|
if (offset >= m_samplers.size()) {
|
||
|
|
m_samplers.resize(offset + 1u, nullptr);
|
||
|
|
}
|
||
|
|
m_samplers[offset] = sampler;
|
||
|
|
}
|
||
|
|
|
||
|
|
void WriteConstant(std::uint32_t, const void* data, size_t size, size_t offset = 0) override {
|
||
|
|
if (data == nullptr) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (m_constantBuffer.size() < offset + size) {
|
||
|
|
m_constantBuffer.resize(offset + size, 0u);
|
||
|
|
}
|
||
|
|
std::memcpy(m_constantBuffer.data() + offset, data, size);
|
||
|
|
m_constantDirty = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::uint32_t GetBindingCount() const override {
|
||
|
|
return static_cast<std::uint32_t>(m_bindings.size());
|
||
|
|
}
|
||
|
|
|
||
|
|
const DescriptorSetLayoutBinding* GetBindings() const override {
|
||
|
|
return m_bindings.empty() ? nullptr : m_bindings.data();
|
||
|
|
}
|
||
|
|
|
||
|
|
void* GetConstantBufferData() override {
|
||
|
|
return m_constantBuffer.empty() ? nullptr : m_constantBuffer.data();
|
||
|
|
}
|
||
|
|
|
||
|
|
size_t GetConstantBufferSize() const override {
|
||
|
|
return m_constantBuffer.size();
|
||
|
|
}
|
||
|
|
|
||
|
|
bool IsConstantDirty() const override {
|
||
|
|
return m_constantDirty;
|
||
|
|
}
|
||
|
|
|
||
|
|
void MarkConstantClean() override {
|
||
|
|
m_constantDirty = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
std::vector<DescriptorSetLayoutBinding> m_bindings = {};
|
||
|
|
std::vector<RHIResourceView*> m_views = {};
|
||
|
|
std::vector<RHISampler*> m_samplers = {};
|
||
|
|
std::vector<std::uint8_t> m_constantBuffer = {};
|
||
|
|
bool m_constantDirty = false;
|
||
|
|
};
|
||
|
|
|
||
|
|
class FakeDescriptorPool final : public RHIDescriptorPool {
|
||
|
|
public:
|
||
|
|
explicit FakeDescriptorPool(const DescriptorPoolDesc& desc)
|
||
|
|
: m_desc(desc) {
|
||
|
|
}
|
||
|
|
|
||
|
|
bool Initialize(const DescriptorPoolDesc& desc) override {
|
||
|
|
m_desc = desc;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void Shutdown() override {}
|
||
|
|
void* GetNativeHandle() override { return this; }
|
||
|
|
std::uint32_t GetDescriptorCount() const override { return m_desc.descriptorCount; }
|
||
|
|
DescriptorHeapType GetType() const override { return m_desc.type; }
|
||
|
|
|
||
|
|
RHIDescriptorSet* AllocateSet(const DescriptorSetLayoutDesc& layout) override {
|
||
|
|
++m_allocationCount;
|
||
|
|
return new FakeDescriptorSet(layout);
|
||
|
|
}
|
||
|
|
|
||
|
|
void FreeSet(RHIDescriptorSet* set) override {
|
||
|
|
delete set;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::uint32_t GetAllocationCount() const {
|
||
|
|
return m_allocationCount;
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
DescriptorPoolDesc m_desc = {};
|
||
|
|
std::uint32_t m_allocationCount = 0;
|
||
|
|
};
|
||
|
|
|
||
|
|
class FakePipelineLayout final : public RHIPipelineLayout {
|
||
|
|
public:
|
||
|
|
bool Initialize(const XCEngine::RHI::RHIPipelineLayoutDesc& desc) override {
|
||
|
|
m_desc = desc;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void Shutdown() override {}
|
||
|
|
void* GetNativeHandle() override { return this; }
|
||
|
|
|
||
|
|
private:
|
||
|
|
XCEngine::RHI::RHIPipelineLayoutDesc m_desc = {};
|
||
|
|
};
|
||
|
|
|
||
|
|
class FakePipelineState final : public RHIPipelineState {
|
||
|
|
public:
|
||
|
|
explicit FakePipelineState(const GraphicsPipelineDesc& desc)
|
||
|
|
: m_inputLayout(desc.inputLayout)
|
||
|
|
, m_rasterizer(desc.rasterizerState)
|
||
|
|
, m_blend(desc.blendState)
|
||
|
|
, m_depthStencil(desc.depthStencilState) {
|
||
|
|
}
|
||
|
|
|
||
|
|
void SetInputLayout(const InputLayoutDesc& layout) override { m_inputLayout = layout; }
|
||
|
|
void SetRasterizerState(const RasterizerDesc& state) override { m_rasterizer = state; }
|
||
|
|
void SetBlendState(const BlendDesc& state) override { m_blend = state; }
|
||
|
|
void SetDepthStencilState(const DepthStencilStateDesc& state) override { m_depthStencil = state; }
|
||
|
|
void SetTopology(std::uint32_t topologyType) override { m_topologyType = topologyType; }
|
||
|
|
void SetRenderTargetFormats(std::uint32_t, const std::uint32_t*, std::uint32_t) override {}
|
||
|
|
void SetSampleCount(std::uint32_t count) override { m_sampleCount = count; }
|
||
|
|
void SetComputeShader(RHIShader* shader) override { m_computeShader = shader; }
|
||
|
|
const RasterizerDesc& GetRasterizerState() const override { return m_rasterizer; }
|
||
|
|
const BlendDesc& GetBlendState() const override { return m_blend; }
|
||
|
|
const DepthStencilStateDesc& GetDepthStencilState() const override { return m_depthStencil; }
|
||
|
|
const InputLayoutDesc& GetInputLayout() const override { return m_inputLayout; }
|
||
|
|
PipelineStateHash GetHash() const override { return {}; }
|
||
|
|
RHIShader* GetComputeShader() const override { return m_computeShader; }
|
||
|
|
bool HasComputeShader() const override { return m_computeShader != nullptr; }
|
||
|
|
bool IsValid() const override { return true; }
|
||
|
|
void EnsureValid() override {}
|
||
|
|
void Shutdown() override {}
|
||
|
|
void Bind() override {}
|
||
|
|
void Unbind() override {}
|
||
|
|
void* GetNativeHandle() override { return this; }
|
||
|
|
PipelineType GetType() const override { return PipelineType::Graphics; }
|
||
|
|
|
||
|
|
private:
|
||
|
|
InputLayoutDesc m_inputLayout = {};
|
||
|
|
RasterizerDesc m_rasterizer = {};
|
||
|
|
BlendDesc m_blend = {};
|
||
|
|
DepthStencilStateDesc m_depthStencil = {};
|
||
|
|
std::uint32_t m_topologyType = 0u;
|
||
|
|
std::uint32_t m_sampleCount = 1u;
|
||
|
|
RHIShader* m_computeShader = nullptr;
|
||
|
|
};
|
||
|
|
|
||
|
|
class FakeSampler final : public RHISampler {
|
||
|
|
public:
|
||
|
|
void Shutdown() override {}
|
||
|
|
void Bind(unsigned int) override {}
|
||
|
|
void Unbind(unsigned int) override {}
|
||
|
|
void* GetNativeHandle() override { return this; }
|
||
|
|
unsigned int GetID() override { return 1u; }
|
||
|
|
};
|
||
|
|
|
||
|
|
class FakeCommandQueue final : public RHICommandQueue {
|
||
|
|
public:
|
||
|
|
void Shutdown() override {}
|
||
|
|
void ExecuteCommandLists(std::uint32_t, void**) override {}
|
||
|
|
void Signal(XCEngine::RHI::RHIFence*, std::uint64_t) override {}
|
||
|
|
void Wait(XCEngine::RHI::RHIFence*, std::uint64_t) override {}
|
||
|
|
std::uint64_t GetCompletedValue() override { return 0u; }
|
||
|
|
void WaitForIdle() override {}
|
||
|
|
CommandQueueType GetType() const override { return CommandQueueType::Direct; }
|
||
|
|
std::uint64_t GetTimestampFrequency() const override { return 0u; }
|
||
|
|
void* GetNativeHandle() override { return this; }
|
||
|
|
void WaitForPreviousFrame() override {}
|
||
|
|
std::uint64_t GetCurrentFrame() const override { return 0u; }
|
||
|
|
};
|
||
|
|
|
||
|
|
class FakeCommandList final : public RHICommandList {
|
||
|
|
public:
|
||
|
|
void Shutdown() override {}
|
||
|
|
void Reset() override {}
|
||
|
|
void Close() override {}
|
||
|
|
void TransitionBarrier(RHIResourceView*, ResourceStates, ResourceStates) override {}
|
||
|
|
void BeginRenderPass(XCEngine::RHI::RHIRenderPass*, XCEngine::RHI::RHIFramebuffer*, const Rect&, std::uint32_t, const XCEngine::RHI::ClearValue*) override {}
|
||
|
|
void EndRenderPass() override {}
|
||
|
|
void SetShader(RHIShader*) override {}
|
||
|
|
|
||
|
|
void SetPipelineState(RHIPipelineState* pso) override {
|
||
|
|
pipelineStateHistory.push_back(pso);
|
||
|
|
}
|
||
|
|
|
||
|
|
void SetGraphicsDescriptorSets(
|
||
|
|
std::uint32_t,
|
||
|
|
std::uint32_t count,
|
||
|
|
RHIDescriptorSet** descriptorSets,
|
||
|
|
RHIPipelineLayout*) override {
|
||
|
|
if (count >= 2u && descriptorSets != nullptr) {
|
||
|
|
textureDescriptorSetHistory.push_back(descriptorSets[1]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void SetComputeDescriptorSets(std::uint32_t, std::uint32_t, RHIDescriptorSet**, RHIPipelineLayout*) override {}
|
||
|
|
void SetPrimitiveTopology(PrimitiveTopology topology) override { primitiveTopologyHistory.push_back(topology); }
|
||
|
|
void SetViewport(const Viewport& viewport) override { viewportHistory.push_back(viewport); }
|
||
|
|
void SetViewports(std::uint32_t count, const Viewport* viewports) override {
|
||
|
|
for (std::uint32_t index = 0; index < count; ++index) {
|
||
|
|
viewportHistory.push_back(viewports[index]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
void SetScissorRect(const Rect& rect) override { scissorHistory.push_back(rect); }
|
||
|
|
void SetScissorRects(std::uint32_t count, const Rect* rects) override {
|
||
|
|
for (std::uint32_t index = 0; index < count; ++index) {
|
||
|
|
scissorHistory.push_back(rects[index]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
void SetRenderTargets(std::uint32_t count, RHIResourceView**, RHIResourceView* = nullptr) override {
|
||
|
|
renderTargetBindHistory.push_back(count);
|
||
|
|
}
|
||
|
|
void SetStencilRef(std::uint8_t) override {}
|
||
|
|
void SetBlendFactor(const float[4]) override {}
|
||
|
|
void SetVertexBuffers(std::uint32_t, std::uint32_t, RHIResourceView**, const std::uint64_t*, const std::uint32_t*) override {}
|
||
|
|
void SetIndexBuffer(RHIResourceView*, std::uint64_t) override {}
|
||
|
|
|
||
|
|
void Draw(std::uint32_t vertexCount, std::uint32_t, std::uint32_t startVertex, std::uint32_t) override {
|
||
|
|
drawVertexCounts.push_back(vertexCount);
|
||
|
|
drawStartVertices.push_back(startVertex);
|
||
|
|
}
|
||
|
|
|
||
|
|
void DrawIndexed(std::uint32_t, std::uint32_t, std::uint32_t, std::int32_t, std::uint32_t) override {}
|
||
|
|
void Clear(float, float, float, float, std::uint32_t) override {}
|
||
|
|
void ClearRenderTarget(RHIResourceView*, const float[4], std::uint32_t = 0, const Rect* = nullptr) override {}
|
||
|
|
void ClearDepthStencil(RHIResourceView*, float, std::uint8_t, std::uint32_t = 0, const Rect* = nullptr) override {}
|
||
|
|
void CopyResource(RHIResourceView*, RHIResourceView*) override {}
|
||
|
|
void Dispatch(std::uint32_t, std::uint32_t, std::uint32_t) override {}
|
||
|
|
void* GetNativeHandle() override { return this; }
|
||
|
|
|
||
|
|
std::vector<RHIPipelineState*> pipelineStateHistory = {};
|
||
|
|
std::vector<PrimitiveTopology> primitiveTopologyHistory = {};
|
||
|
|
std::vector<Viewport> viewportHistory = {};
|
||
|
|
std::vector<Rect> scissorHistory = {};
|
||
|
|
std::vector<std::uint32_t> renderTargetBindHistory = {};
|
||
|
|
std::vector<RHIDescriptorSet*> textureDescriptorSetHistory = {};
|
||
|
|
std::vector<std::uint32_t> drawVertexCounts = {};
|
||
|
|
std::vector<std::uint32_t> drawStartVertices = {};
|
||
|
|
};
|
||
|
|
|
||
|
|
class FakeDevice final : public RHIDevice {
|
||
|
|
public:
|
||
|
|
bool Initialize(const XCEngine::RHI::RHIDeviceDesc&) override { return true; }
|
||
|
|
void Shutdown() override {}
|
||
|
|
RHIBuffer* CreateBuffer(const BufferDesc& desc) override { return new FakeBuffer(desc); }
|
||
|
|
XCEngine::RHI::RHITexture* CreateTexture(const XCEngine::RHI::TextureDesc&) override { return nullptr; }
|
||
|
|
XCEngine::RHI::RHITexture* CreateTexture(const XCEngine::RHI::TextureDesc&, const void*, size_t, std::uint32_t = 0) override { return nullptr; }
|
||
|
|
XCEngine::RHI::RHISwapChain* CreateSwapChain(const XCEngine::RHI::SwapChainDesc&, RHICommandQueue*) override { return nullptr; }
|
||
|
|
RHICommandList* CreateCommandList(const XCEngine::RHI::CommandListDesc&) override { return nullptr; }
|
||
|
|
RHICommandQueue* CreateCommandQueue(const XCEngine::RHI::CommandQueueDesc&) override { return nullptr; }
|
||
|
|
RHIShader* CreateShader(const XCEngine::RHI::ShaderCompileDesc&) override { return nullptr; }
|
||
|
|
RHIPipelineState* CreatePipelineState(const GraphicsPipelineDesc& desc) override { return new FakePipelineState(desc); }
|
||
|
|
RHIPipelineLayout* CreatePipelineLayout(const XCEngine::RHI::RHIPipelineLayoutDesc&) override { return new FakePipelineLayout(); }
|
||
|
|
XCEngine::RHI::RHIFence* CreateFence(const XCEngine::RHI::FenceDesc&) override { return nullptr; }
|
||
|
|
RHISampler* CreateSampler(const SamplerDesc&) override { return new FakeSampler(); }
|
||
|
|
XCEngine::RHI::RHIRenderPass* CreateRenderPass(std::uint32_t, const XCEngine::RHI::AttachmentDesc*, const XCEngine::RHI::AttachmentDesc*) override { return nullptr; }
|
||
|
|
XCEngine::RHI::RHIFramebuffer* CreateFramebuffer(XCEngine::RHI::RHIRenderPass*, std::uint32_t, std::uint32_t, std::uint32_t, RHIResourceView**, RHIResourceView*) override { return nullptr; }
|
||
|
|
RHIDescriptorPool* CreateDescriptorPool(const DescriptorPoolDesc& desc) override { return new FakeDescriptorPool(desc); }
|
||
|
|
RHIDescriptorSet* CreateDescriptorSet(RHIDescriptorPool*, const DescriptorSetLayoutDesc&) override { return nullptr; }
|
||
|
|
RHIResourceView* CreateVertexBufferView(RHIBuffer* buffer, const ResourceViewDesc& desc) override {
|
||
|
|
return new FakeVertexBufferView(static_cast<std::uint32_t>(buffer != nullptr ? buffer->GetSize() : 0u), desc.structureByteStride);
|
||
|
|
}
|
||
|
|
RHIResourceView* CreateIndexBufferView(RHIBuffer*, const ResourceViewDesc&) override { return nullptr; }
|
||
|
|
RHIResourceView* CreateRenderTargetView(XCEngine::RHI::RHITexture*, const ResourceViewDesc&) override { return nullptr; }
|
||
|
|
RHIResourceView* CreateDepthStencilView(XCEngine::RHI::RHITexture*, const ResourceViewDesc&) override { return nullptr; }
|
||
|
|
RHIResourceView* CreateShaderResourceView(XCEngine::RHI::RHITexture*, const ResourceViewDesc&) override { return nullptr; }
|
||
|
|
RHIResourceView* CreateUnorderedAccessView(XCEngine::RHI::RHITexture*, const 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 this; }
|
||
|
|
|
||
|
|
private:
|
||
|
|
XCEngine::RHI::RHICapabilities m_capabilities = {};
|
||
|
|
XCEngine::RHI::RHIDeviceInfo m_deviceInfo = {};
|
||
|
|
};
|
||
|
|
|
||
|
|
UITextureHandle MakeExternalTextureHandle(FakeShaderResourceView& shaderView, std::uint32_t width = 32u, std::uint32_t height = 32u) {
|
||
|
|
UITextureHandle texture = {};
|
||
|
|
texture.nativeHandle = reinterpret_cast<std::uintptr_t>(&shaderView);
|
||
|
|
texture.width = width;
|
||
|
|
texture.height = height;
|
||
|
|
texture.kind = UITextureHandleKind::ShaderResourceView;
|
||
|
|
return texture;
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
TEST(XCUIRHIRenderBackendTest, ExposesNativeOverlayEntryAndAtlasInjection) {
|
||
|
|
static_assert(std::is_same_v<
|
||
|
|
decltype(std::declval<XCUIRHIRenderBackend&>().Render(
|
||
|
|
std::declval<const RenderContext&>(),
|
||
|
|
std::declval<const RenderSurface&>(),
|
||
|
|
std::declval<const UIDrawData&>())),
|
||
|
|
bool>);
|
||
|
|
static_assert(std::is_same_v<
|
||
|
|
decltype(std::declval<XCUIRHIRenderBackend&>().SetTextAtlasProvider(
|
||
|
|
std::declval<const IXCUITextAtlasProvider*>())) ,
|
||
|
|
void>);
|
||
|
|
static_assert(std::is_same_v<
|
||
|
|
decltype(std::declval<const XCUIRHIRenderBackend&>().GetLastOverlayStats().drawListCount),
|
||
|
|
std::size_t>);
|
||
|
|
|
||
|
|
SUCCEED();
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(XCUIRHIRenderBackendTest, RenderRejectsInvalidContextAndLeavesStatsClear) {
|
||
|
|
XCUIRHIRenderBackend backend = {};
|
||
|
|
RenderSurface surface(128, 72);
|
||
|
|
UIDrawData drawData = {};
|
||
|
|
|
||
|
|
EXPECT_FALSE(backend.Render(RenderContext(), surface, drawData));
|
||
|
|
EXPECT_EQ(backend.GetLastOverlayStats().commandCount, 0u);
|
||
|
|
EXPECT_EQ(backend.GetLastOverlayStats().renderedCommandCount, 0u);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(XCUIRHIRenderBackendTest, SetTextAtlasProviderStoresPointerAndResetStatsClearsCounters) {
|
||
|
|
XCUIRHIRenderBackend backend = {};
|
||
|
|
|
||
|
|
class StubAtlasProvider final : public IXCUITextAtlasProvider {
|
||
|
|
public:
|
||
|
|
bool GetAtlasTextureView(PixelFormat, AtlasTextureView& outView) const override {
|
||
|
|
outView = {};
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
std::size_t GetFontCount() const override { return 0u; }
|
||
|
|
FontHandle GetFont(std::size_t) const override { return {}; }
|
||
|
|
FontHandle GetDefaultFont() const override { return {}; }
|
||
|
|
bool GetFontInfo(FontHandle, FontInfo& outInfo) const override {
|
||
|
|
outInfo = {};
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
bool GetBakedFontInfo(FontHandle, float, BakedFontInfo& outInfo) const override {
|
||
|
|
outInfo = {};
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
bool FindGlyph(FontHandle, float, std::uint32_t, GlyphInfo& outInfo) const override {
|
||
|
|
outInfo = {};
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
} atlasProvider;
|
||
|
|
|
||
|
|
backend.SetTextAtlasProvider(&atlasProvider);
|
||
|
|
EXPECT_EQ(backend.GetTextAtlasProvider(), &atlasProvider);
|
||
|
|
|
||
|
|
backend.SetTextAtlasProvider(nullptr);
|
||
|
|
EXPECT_EQ(backend.GetTextAtlasProvider(), nullptr);
|
||
|
|
|
||
|
|
backend.ResetStats();
|
||
|
|
EXPECT_EQ(backend.GetLastStats().drawListCount, 0u);
|
||
|
|
EXPECT_EQ(backend.GetLastStats().commandCount, 0u);
|
||
|
|
EXPECT_EQ(backend.GetLastStats().batchCount, 0u);
|
||
|
|
EXPECT_EQ(backend.GetLastStats().renderedCommandCount, 0u);
|
||
|
|
EXPECT_EQ(backend.GetLastStats().skippedCommandCount, 0u);
|
||
|
|
EXPECT_EQ(backend.GetLastStats().textureResolveCount, 0u);
|
||
|
|
EXPECT_EQ(backend.GetLastStats().textureCacheHitCount, 0u);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(XCUIRHIRenderBackendTest, RenderReusesExternalTextureBindingsAcrossTexturedBatches) {
|
||
|
|
FakeDevice device = {};
|
||
|
|
FakeCommandList commandList = {};
|
||
|
|
FakeCommandQueue commandQueue = {};
|
||
|
|
FakeRenderTargetView renderTarget = {};
|
||
|
|
FakeShaderResourceView externalTextureView(true);
|
||
|
|
|
||
|
|
RenderContext context = {};
|
||
|
|
context.device = &device;
|
||
|
|
context.commandList = &commandList;
|
||
|
|
context.commandQueue = &commandQueue;
|
||
|
|
context.backendType = RHIType::D3D12;
|
||
|
|
|
||
|
|
RenderSurface surface(64, 48);
|
||
|
|
surface.SetColorAttachment(&renderTarget);
|
||
|
|
|
||
|
|
UIDrawData drawData = {};
|
||
|
|
UIDrawList& firstDrawList = drawData.EmplaceDrawList("FirstTexturedBatch");
|
||
|
|
firstDrawList.PushClipRect(UIRect(4.0f, 6.0f, 12.0f, 10.0f));
|
||
|
|
firstDrawList.AddImage(
|
||
|
|
UIRect(0.0f, 0.0f, 18.0f, 18.0f),
|
||
|
|
MakeExternalTextureHandle(externalTextureView),
|
||
|
|
UIColor(1.0f, 0.8f, 0.6f, 1.0f));
|
||
|
|
firstDrawList.PopClipRect();
|
||
|
|
|
||
|
|
UIDrawList& secondDrawList = drawData.EmplaceDrawList("SecondTexturedBatch");
|
||
|
|
secondDrawList.PushClipRect(UIRect(20.0f, 8.0f, 10.0f, 12.0f));
|
||
|
|
secondDrawList.AddImage(
|
||
|
|
UIRect(18.0f, 4.0f, 16.0f, 16.0f),
|
||
|
|
MakeExternalTextureHandle(externalTextureView),
|
||
|
|
UIColor(0.7f, 0.9f, 1.0f, 1.0f));
|
||
|
|
secondDrawList.PopClipRect();
|
||
|
|
|
||
|
|
XCUIRHIRenderBackend backend = {};
|
||
|
|
ASSERT_TRUE(backend.Render(context, surface, drawData));
|
||
|
|
|
||
|
|
const XCUIRHIRenderBackend::OverlayStats& stats = backend.GetLastOverlayStats();
|
||
|
|
EXPECT_EQ(stats.drawListCount, 2u);
|
||
|
|
EXPECT_EQ(stats.commandCount, 6u);
|
||
|
|
EXPECT_EQ(stats.batchCount, 2u);
|
||
|
|
EXPECT_EQ(stats.colorBatchCount, 0u);
|
||
|
|
EXPECT_EQ(stats.texturedBatchCount, 2u);
|
||
|
|
EXPECT_EQ(stats.scissoredBatchCount, 2u);
|
||
|
|
EXPECT_EQ(stats.renderedCommandCount, 2u);
|
||
|
|
EXPECT_EQ(stats.skippedCommandCount, 0u);
|
||
|
|
EXPECT_EQ(stats.textureResolveCount, 2u);
|
||
|
|
EXPECT_EQ(stats.textureCacheHitCount, 1u);
|
||
|
|
EXPECT_EQ(stats.vertexCount, 12u);
|
||
|
|
EXPECT_EQ(stats.triangleCount, 4u);
|
||
|
|
|
||
|
|
ASSERT_EQ(commandList.renderTargetBindHistory.size(), 1u);
|
||
|
|
ASSERT_EQ(commandList.viewportHistory.size(), 1u);
|
||
|
|
ASSERT_EQ(commandList.drawVertexCounts.size(), 2u);
|
||
|
|
EXPECT_EQ(commandList.drawVertexCounts[0], 6u);
|
||
|
|
EXPECT_EQ(commandList.drawVertexCounts[1], 6u);
|
||
|
|
ASSERT_EQ(commandList.textureDescriptorSetHistory.size(), 2u);
|
||
|
|
EXPECT_EQ(commandList.textureDescriptorSetHistory[0], commandList.textureDescriptorSetHistory[1]);
|
||
|
|
|
||
|
|
ASSERT_EQ(commandList.scissorHistory.size(), 4u);
|
||
|
|
EXPECT_EQ(commandList.scissorHistory[0].left, 0);
|
||
|
|
EXPECT_EQ(commandList.scissorHistory[0].top, 0);
|
||
|
|
EXPECT_EQ(commandList.scissorHistory[0].right, 64);
|
||
|
|
EXPECT_EQ(commandList.scissorHistory[0].bottom, 48);
|
||
|
|
EXPECT_EQ(commandList.scissorHistory[1].left, 4);
|
||
|
|
EXPECT_EQ(commandList.scissorHistory[1].top, 6);
|
||
|
|
EXPECT_EQ(commandList.scissorHistory[1].right, 16);
|
||
|
|
EXPECT_EQ(commandList.scissorHistory[1].bottom, 16);
|
||
|
|
EXPECT_EQ(commandList.scissorHistory[2].left, 20);
|
||
|
|
EXPECT_EQ(commandList.scissorHistory[2].top, 8);
|
||
|
|
EXPECT_EQ(commandList.scissorHistory[2].right, 30);
|
||
|
|
EXPECT_EQ(commandList.scissorHistory[2].bottom, 20);
|
||
|
|
EXPECT_EQ(commandList.scissorHistory[3].left, 0);
|
||
|
|
EXPECT_EQ(commandList.scissorHistory[3].top, 0);
|
||
|
|
EXPECT_EQ(commandList.scissorHistory[3].right, 64);
|
||
|
|
EXPECT_EQ(commandList.scissorHistory[3].bottom, 48);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(XCUIRHIRenderBackendTest, RenderSkipsTexturedBatchWhenExternalTextureViewIsInvalid) {
|
||
|
|
FakeDevice device = {};
|
||
|
|
FakeCommandList commandList = {};
|
||
|
|
FakeCommandQueue commandQueue = {};
|
||
|
|
FakeRenderTargetView renderTarget = {};
|
||
|
|
FakeShaderResourceView invalidTextureView(false);
|
||
|
|
|
||
|
|
RenderContext context = {};
|
||
|
|
context.device = &device;
|
||
|
|
context.commandList = &commandList;
|
||
|
|
context.commandQueue = &commandQueue;
|
||
|
|
context.backendType = RHIType::D3D12;
|
||
|
|
|
||
|
|
RenderSurface surface(32, 32);
|
||
|
|
surface.SetColorAttachment(&renderTarget);
|
||
|
|
|
||
|
|
UIDrawData drawData = {};
|
||
|
|
UIDrawList& drawList = drawData.EmplaceDrawList("InvalidExternalTexture");
|
||
|
|
drawList.AddImage(
|
||
|
|
UIRect(2.0f, 4.0f, 12.0f, 8.0f),
|
||
|
|
MakeExternalTextureHandle(invalidTextureView),
|
||
|
|
UIColor(1.0f, 1.0f, 1.0f, 1.0f));
|
||
|
|
|
||
|
|
XCUIRHIRenderBackend backend = {};
|
||
|
|
ASSERT_TRUE(backend.Render(context, surface, drawData));
|
||
|
|
|
||
|
|
const XCUIRHIRenderBackend::OverlayStats& stats = backend.GetLastOverlayStats();
|
||
|
|
EXPECT_EQ(stats.commandCount, 1u);
|
||
|
|
EXPECT_EQ(stats.batchCount, 1u);
|
||
|
|
EXPECT_EQ(stats.texturedBatchCount, 1u);
|
||
|
|
EXPECT_EQ(stats.renderedCommandCount, 0u);
|
||
|
|
EXPECT_EQ(stats.skippedCommandCount, 1u);
|
||
|
|
EXPECT_EQ(stats.textureResolveCount, 1u);
|
||
|
|
EXPECT_EQ(stats.textureCacheHitCount, 0u);
|
||
|
|
EXPECT_EQ(commandList.drawVertexCounts.size(), 0u);
|
||
|
|
EXPECT_EQ(commandList.textureDescriptorSetHistory.size(), 0u);
|
||
|
|
}
|