Files
XCEngine/tests/NewEditor/test_xcui_rhi_render_backend.cpp

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);
}