Publish native hosted preview textures through XCUI compositor
This commit is contained in:
@@ -4,11 +4,16 @@
|
||||
#include "XCUIBackend/NativeWindowUICompositor.h"
|
||||
#include "XCUIBackend/UITextureRegistration.h"
|
||||
|
||||
#include <XCEngine/RHI/RHICapabilities.h>
|
||||
#include <XCEngine/RHI/RHIDevice.h>
|
||||
#include <XCEngine/RHI/RHIResourceView.h>
|
||||
#include <XCEngine/RHI/RHITexture.h>
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -20,6 +25,16 @@ using XCEngine::Editor::XCUIBackend::NativeWindowUICompositor;
|
||||
using XCEngine::Editor::XCUIBackend::UITextureRegistration;
|
||||
using XCEngine::Editor::XCUIBackend::XCUINativeWindowPresentStats;
|
||||
using XCEngine::Editor::XCUIBackend::XCUINativeWindowRenderPacket;
|
||||
using XCEngine::RHI::Format;
|
||||
using XCEngine::RHI::ResourceStates;
|
||||
using XCEngine::RHI::ResourceViewDesc;
|
||||
using XCEngine::RHI::ResourceViewDimension;
|
||||
using XCEngine::RHI::ResourceViewType;
|
||||
using XCEngine::RHI::RHIDevice;
|
||||
using XCEngine::RHI::RHIResourceView;
|
||||
using XCEngine::RHI::RHITexture;
|
||||
using XCEngine::RHI::TextureType;
|
||||
using XCEngine::UI::UITextureHandleKind;
|
||||
|
||||
HWND MakeFakeHwnd() {
|
||||
return reinterpret_cast<HWND>(static_cast<std::uintptr_t>(0x2345u));
|
||||
@@ -76,6 +91,162 @@ XCEngine::UI::UIDrawData MakeDrawData() {
|
||||
return drawData;
|
||||
}
|
||||
|
||||
struct TrackingShaderViewState {
|
||||
int shutdownCount = 0;
|
||||
int destructorCount = 0;
|
||||
};
|
||||
|
||||
class TrackingShaderResourceView final : public XCEngine::RHI::RHIShaderResourceView {
|
||||
public:
|
||||
TrackingShaderResourceView(
|
||||
TrackingShaderViewState& state,
|
||||
bool valid,
|
||||
ResourceViewDimension dimension = ResourceViewDimension::Texture2D,
|
||||
Format format = Format::R8G8B8A8_UNorm)
|
||||
: m_state(state)
|
||||
, m_valid(valid)
|
||||
, m_dimension(dimension)
|
||||
, m_format(format) {
|
||||
}
|
||||
|
||||
~TrackingShaderResourceView() override {
|
||||
++m_state.destructorCount;
|
||||
}
|
||||
|
||||
void Shutdown() override {
|
||||
++m_state.shutdownCount;
|
||||
m_valid = false;
|
||||
}
|
||||
|
||||
void* GetNativeHandle() override {
|
||||
return this;
|
||||
}
|
||||
|
||||
bool IsValid() const override {
|
||||
return m_valid;
|
||||
}
|
||||
|
||||
ResourceViewType GetViewType() const override {
|
||||
return ResourceViewType::ShaderResource;
|
||||
}
|
||||
|
||||
ResourceViewDimension GetDimension() const override {
|
||||
return m_dimension;
|
||||
}
|
||||
|
||||
Format GetFormat() const override {
|
||||
return m_format;
|
||||
}
|
||||
|
||||
private:
|
||||
TrackingShaderViewState& m_state;
|
||||
bool m_valid = true;
|
||||
ResourceViewDimension m_dimension = ResourceViewDimension::Texture2D;
|
||||
Format m_format = Format::R8G8B8A8_UNorm;
|
||||
};
|
||||
|
||||
class FakeTexture final : public RHITexture {
|
||||
public:
|
||||
FakeTexture(
|
||||
std::uint32_t width,
|
||||
std::uint32_t height,
|
||||
Format format = Format::R8G8B8A8_UNorm,
|
||||
TextureType textureType = TextureType::Texture2D)
|
||||
: m_width(width)
|
||||
, m_height(height)
|
||||
, m_format(format)
|
||||
, m_textureType(textureType) {
|
||||
}
|
||||
|
||||
std::uint32_t GetWidth() const override { return m_width; }
|
||||
std::uint32_t GetHeight() const override { return m_height; }
|
||||
std::uint32_t GetDepth() const override { return 1u; }
|
||||
std::uint32_t GetMipLevels() const override { return 1u; }
|
||||
Format GetFormat() const override { return m_format; }
|
||||
TextureType GetTextureType() const override { return m_textureType; }
|
||||
ResourceStates GetState() const override { return m_state; }
|
||||
void SetState(ResourceStates state) override { m_state = state; }
|
||||
void* GetNativeHandle() override { return this; }
|
||||
const std::string& GetName() const override { return m_name; }
|
||||
void SetName(const std::string& name) override { m_name = name; }
|
||||
void Shutdown() override { m_shutdownCalled = true; }
|
||||
|
||||
bool shutdownCalled() const { return m_shutdownCalled; }
|
||||
|
||||
private:
|
||||
std::uint32_t m_width = 0u;
|
||||
std::uint32_t m_height = 0u;
|
||||
Format m_format = Format::Unknown;
|
||||
TextureType m_textureType = TextureType::Texture2D;
|
||||
ResourceStates m_state = ResourceStates::Common;
|
||||
std::string m_name = {};
|
||||
bool m_shutdownCalled = false;
|
||||
};
|
||||
|
||||
class RecordingDevice final : public RHIDevice {
|
||||
public:
|
||||
TrackingShaderViewState* nextShaderViewState = nullptr;
|
||||
bool createShaderViewValid = true;
|
||||
int createShaderViewCount = 0;
|
||||
RHITexture* lastShaderViewTexture = nullptr;
|
||||
ResourceViewDesc lastShaderViewDesc = {};
|
||||
|
||||
bool Initialize(const XCEngine::RHI::RHIDeviceDesc&) override { return true; }
|
||||
void Shutdown() override {}
|
||||
XCEngine::RHI::RHIBuffer* CreateBuffer(const XCEngine::RHI::BufferDesc&) override { return nullptr; }
|
||||
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) override { return nullptr; }
|
||||
XCEngine::RHI::RHISwapChain* CreateSwapChain(const XCEngine::RHI::SwapChainDesc&, XCEngine::RHI::RHICommandQueue*) override { return nullptr; }
|
||||
XCEngine::RHI::RHICommandList* CreateCommandList(const XCEngine::RHI::CommandListDesc&) override { return nullptr; }
|
||||
XCEngine::RHI::RHICommandQueue* CreateCommandQueue(const XCEngine::RHI::CommandQueueDesc&) override { return nullptr; }
|
||||
XCEngine::RHI::RHIShader* CreateShader(const XCEngine::RHI::ShaderCompileDesc&) override { return nullptr; }
|
||||
XCEngine::RHI::RHIPipelineState* CreatePipelineState(const XCEngine::RHI::GraphicsPipelineDesc&) override { return nullptr; }
|
||||
XCEngine::RHI::RHIPipelineLayout* CreatePipelineLayout(const XCEngine::RHI::RHIPipelineLayoutDesc&) override { return nullptr; }
|
||||
XCEngine::RHI::RHIFence* CreateFence(const XCEngine::RHI::FenceDesc&) override { return nullptr; }
|
||||
XCEngine::RHI::RHISampler* CreateSampler(const XCEngine::RHI::SamplerDesc&) override { return nullptr; }
|
||||
XCEngine::RHI::RHIRenderPass* CreateRenderPass(
|
||||
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; }
|
||||
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; }
|
||||
RHIResourceView* CreateVertexBufferView(XCEngine::RHI::RHIBuffer*, const ResourceViewDesc&) override { return nullptr; }
|
||||
RHIResourceView* CreateIndexBufferView(XCEngine::RHI::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* texture, const ResourceViewDesc& desc) override {
|
||||
++createShaderViewCount;
|
||||
lastShaderViewTexture = texture;
|
||||
lastShaderViewDesc = desc;
|
||||
if (nextShaderViewState == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new TrackingShaderResourceView(
|
||||
*nextShaderViewState,
|
||||
createShaderViewValid,
|
||||
desc.dimension != ResourceViewDimension::Unknown ? desc.dimension : ResourceViewDimension::Texture2D,
|
||||
desc.format != 0u ? static_cast<Format>(desc.format) : Format::R8G8B8A8_UNorm);
|
||||
}
|
||||
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 = {};
|
||||
};
|
||||
|
||||
TEST(NativeWindowUICompositorTest, RenderPacketReportsDrawDataPresenceAndClearResetsPayload) {
|
||||
XCUINativeWindowRenderPacket packet = {};
|
||||
EXPECT_FALSE(packet.HasDrawData());
|
||||
@@ -209,4 +380,66 @@ TEST(NativeWindowUICompositorTest, InterfaceFactoryReturnsSafeNativeCompositorDe
|
||||
compositor->Shutdown();
|
||||
}
|
||||
|
||||
TEST(NativeWindowUICompositorTest, ShaderResourceViewRegistrationsStayValidWithoutGpuDescriptorHandle) {
|
||||
UITextureRegistration registration = {};
|
||||
registration.cpuHandle.ptr = 17u;
|
||||
registration.texture.nativeHandle = 33u;
|
||||
registration.texture.width = 64u;
|
||||
registration.texture.height = 32u;
|
||||
registration.texture.kind = UITextureHandleKind::ShaderResourceView;
|
||||
|
||||
EXPECT_TRUE(registration.IsValid());
|
||||
|
||||
registration.texture.kind = UITextureHandleKind::ImGuiDescriptor;
|
||||
EXPECT_FALSE(registration.IsValid());
|
||||
|
||||
registration.gpuHandle.ptr = 19u;
|
||||
EXPECT_TRUE(registration.IsValid());
|
||||
}
|
||||
|
||||
TEST(NativeWindowUICompositorTest, CreateTextureDescriptorPublishesShaderResourceViewAndFreeReleasesIt) {
|
||||
NativeWindowUICompositor compositor = {};
|
||||
RecordingDevice device = {};
|
||||
TrackingShaderViewState viewState = {};
|
||||
device.nextShaderViewState = &viewState;
|
||||
|
||||
FakeTexture texture(256u, 128u, Format::R8G8B8A8_UNorm, TextureType::Texture2DArray);
|
||||
UITextureRegistration registration = {};
|
||||
|
||||
ASSERT_TRUE(compositor.CreateTextureDescriptor(&device, &texture, registration));
|
||||
EXPECT_EQ(device.createShaderViewCount, 1);
|
||||
EXPECT_EQ(device.lastShaderViewTexture, &texture);
|
||||
EXPECT_EQ(device.lastShaderViewDesc.format, static_cast<std::uint32_t>(Format::R8G8B8A8_UNorm));
|
||||
EXPECT_EQ(device.lastShaderViewDesc.dimension, ResourceViewDimension::Texture2DArray);
|
||||
EXPECT_TRUE(registration.IsValid());
|
||||
EXPECT_NE(registration.cpuHandle.ptr, 0u);
|
||||
EXPECT_EQ(registration.gpuHandle.ptr, 0u);
|
||||
EXPECT_EQ(registration.texture.width, 256u);
|
||||
EXPECT_EQ(registration.texture.height, 128u);
|
||||
EXPECT_EQ(registration.texture.kind, UITextureHandleKind::ShaderResourceView);
|
||||
EXPECT_EQ(registration.cpuHandle.ptr, registration.texture.nativeHandle);
|
||||
|
||||
compositor.FreeTextureDescriptor(registration);
|
||||
EXPECT_EQ(viewState.shutdownCount, 1);
|
||||
EXPECT_EQ(viewState.destructorCount, 1);
|
||||
}
|
||||
|
||||
TEST(NativeWindowUICompositorTest, CreateTextureDescriptorRejectsInvalidShaderResourceViewAndCleansItUp) {
|
||||
NativeWindowUICompositor compositor = {};
|
||||
RecordingDevice device = {};
|
||||
TrackingShaderViewState viewState = {};
|
||||
device.nextShaderViewState = &viewState;
|
||||
device.createShaderViewValid = false;
|
||||
|
||||
FakeTexture texture(96u, 64u);
|
||||
UITextureRegistration registration = {};
|
||||
|
||||
EXPECT_FALSE(compositor.CreateTextureDescriptor(&device, &texture, registration));
|
||||
EXPECT_EQ(device.createShaderViewCount, 1);
|
||||
EXPECT_FALSE(registration.IsValid());
|
||||
EXPECT_EQ(registration.texture.nativeHandle, 0u);
|
||||
EXPECT_EQ(viewState.shutdownCount, 1);
|
||||
EXPECT_EQ(viewState.destructorCount, 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user