2026-04-05 07:29:27 +08:00
|
|
|
#include <gtest/gtest.h>
|
|
|
|
|
|
|
|
|
|
#include "XCUIBackend/IEditorHostCompositor.h"
|
|
|
|
|
#include "XCUIBackend/ImGuiWindowUICompositor.h"
|
|
|
|
|
#include "XCUIBackend/UITextureRegistration.h"
|
|
|
|
|
|
|
|
|
|
#include <array>
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
#include <memory>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <utility>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
using XCEngine::Editor::Platform::D3D12WindowRenderer;
|
|
|
|
|
using XCEngine::Editor::XCUIBackend::IEditorHostCompositor;
|
|
|
|
|
using XCEngine::Editor::XCUIBackend::ImGuiWindowUICompositor;
|
|
|
|
|
using XCEngine::Editor::XCUIBackend::UITextureRegistration;
|
|
|
|
|
|
|
|
|
|
class RecordingHostCompositor final : public IEditorHostCompositor {
|
|
|
|
|
public:
|
|
|
|
|
bool initializeResult = true;
|
|
|
|
|
bool handleWindowMessageResult = false;
|
|
|
|
|
bool createTextureDescriptorResult = false;
|
|
|
|
|
bool invokeConfigureFonts = false;
|
|
|
|
|
|
|
|
|
|
int initializeCount = 0;
|
|
|
|
|
int shutdownCount = 0;
|
|
|
|
|
int beginFrameCount = 0;
|
|
|
|
|
int endFrameCount = 0;
|
|
|
|
|
int handleWindowMessageCount = 0;
|
|
|
|
|
int createTextureDescriptorCount = 0;
|
|
|
|
|
int freeTextureDescriptorCount = 0;
|
|
|
|
|
|
|
|
|
|
HWND lastHwnd = nullptr;
|
|
|
|
|
UINT lastMessage = 0u;
|
|
|
|
|
WPARAM lastWParam = 0u;
|
|
|
|
|
LPARAM lastLParam = 0u;
|
|
|
|
|
|
|
|
|
|
D3D12WindowRenderer* initializedRenderer = nullptr;
|
|
|
|
|
D3D12WindowRenderer* presentedRenderer = nullptr;
|
|
|
|
|
::XCEngine::RHI::RHIDevice* lastDevice = nullptr;
|
|
|
|
|
::XCEngine::RHI::RHITexture* lastTexture = nullptr;
|
|
|
|
|
|
|
|
|
|
bool beforeUiRenderProvided = false;
|
|
|
|
|
bool afterUiRenderProvided = false;
|
|
|
|
|
|
|
|
|
|
std::array<float, 4> lastClearColor = { 0.0f, 0.0f, 0.0f, 0.0f };
|
|
|
|
|
UITextureRegistration nextRegistration = {};
|
|
|
|
|
UITextureRegistration freedRegistration = {};
|
|
|
|
|
std::vector<std::string> callOrder = {};
|
|
|
|
|
|
|
|
|
|
bool Initialize(
|
|
|
|
|
HWND hwnd,
|
|
|
|
|
D3D12WindowRenderer& windowRenderer,
|
|
|
|
|
const ConfigureFontsCallback& configureFonts) override {
|
|
|
|
|
++initializeCount;
|
|
|
|
|
lastHwnd = hwnd;
|
|
|
|
|
initializedRenderer = &windowRenderer;
|
|
|
|
|
callOrder.push_back("initialize");
|
|
|
|
|
if (invokeConfigureFonts && configureFonts) {
|
|
|
|
|
configureFonts();
|
|
|
|
|
}
|
|
|
|
|
return initializeResult;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Shutdown() override {
|
|
|
|
|
++shutdownCount;
|
|
|
|
|
callOrder.push_back("shutdown");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool HandleWindowMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) override {
|
|
|
|
|
++handleWindowMessageCount;
|
|
|
|
|
lastHwnd = hwnd;
|
|
|
|
|
lastMessage = message;
|
|
|
|
|
lastWParam = wParam;
|
|
|
|
|
lastLParam = lParam;
|
|
|
|
|
callOrder.push_back("message");
|
|
|
|
|
return handleWindowMessageResult;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BeginFrame() override {
|
|
|
|
|
++beginFrameCount;
|
|
|
|
|
callOrder.push_back("begin");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EndFrameAndPresent(
|
|
|
|
|
D3D12WindowRenderer& windowRenderer,
|
|
|
|
|
const float clearColor[4],
|
|
|
|
|
const RenderCallback& beforeUiRender,
|
|
|
|
|
const RenderCallback& afterUiRender) override {
|
|
|
|
|
++endFrameCount;
|
|
|
|
|
presentedRenderer = &windowRenderer;
|
|
|
|
|
beforeUiRenderProvided = static_cast<bool>(beforeUiRender);
|
|
|
|
|
afterUiRenderProvided = static_cast<bool>(afterUiRender);
|
|
|
|
|
for (std::size_t index = 0; index < lastClearColor.size(); ++index) {
|
|
|
|
|
lastClearColor[index] = clearColor[index];
|
|
|
|
|
}
|
|
|
|
|
callOrder.push_back("present");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CreateTextureDescriptor(
|
|
|
|
|
::XCEngine::RHI::RHIDevice* device,
|
|
|
|
|
::XCEngine::RHI::RHITexture* texture,
|
|
|
|
|
UITextureRegistration& outRegistration) override {
|
|
|
|
|
++createTextureDescriptorCount;
|
|
|
|
|
lastDevice = device;
|
|
|
|
|
lastTexture = texture;
|
|
|
|
|
if (createTextureDescriptorResult) {
|
|
|
|
|
outRegistration = nextRegistration;
|
|
|
|
|
}
|
|
|
|
|
return createTextureDescriptorResult;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FreeTextureDescriptor(const UITextureRegistration& registration) override {
|
|
|
|
|
++freeTextureDescriptorCount;
|
|
|
|
|
freedRegistration = registration;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
HWND MakeFakeHwnd() {
|
|
|
|
|
return reinterpret_cast<HWND>(static_cast<std::uintptr_t>(0x1234u));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UITextureRegistration MakeRegistration() {
|
|
|
|
|
UITextureRegistration registration = {};
|
|
|
|
|
registration.cpuHandle.ptr = 11u;
|
|
|
|
|
registration.gpuHandle.ptr = 29u;
|
|
|
|
|
registration.texture.nativeHandle = 43u;
|
|
|
|
|
registration.texture.width = 256u;
|
|
|
|
|
registration.texture.height = 128u;
|
|
|
|
|
registration.texture.kind = ::XCEngine::UI::UITextureHandleKind::ShaderResourceView;
|
|
|
|
|
return registration;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(ImGuiWindowUICompositorTest, InitializeForwardsToHostAndConfigureFontsCallback) {
|
|
|
|
|
auto host = std::make_unique<RecordingHostCompositor>();
|
|
|
|
|
RecordingHostCompositor* hostPtr = host.get();
|
|
|
|
|
hostPtr->invokeConfigureFonts = true;
|
|
|
|
|
|
|
|
|
|
ImGuiWindowUICompositor compositor(std::move(host));
|
|
|
|
|
D3D12WindowRenderer renderer = {};
|
|
|
|
|
bool configureFontsCalled = false;
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(compositor.Initialize(
|
|
|
|
|
MakeFakeHwnd(),
|
|
|
|
|
renderer,
|
|
|
|
|
[&configureFontsCalled]() { configureFontsCalled = true; }));
|
|
|
|
|
EXPECT_TRUE(configureFontsCalled);
|
|
|
|
|
ASSERT_NE(hostPtr, nullptr);
|
|
|
|
|
EXPECT_EQ(hostPtr->initializeCount, 1);
|
|
|
|
|
EXPECT_EQ(hostPtr->lastHwnd, MakeFakeHwnd());
|
|
|
|
|
EXPECT_EQ(hostPtr->initializedRenderer, &renderer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(ImGuiWindowUICompositorTest, RenderFrameCallsHostBeginUiAndPresentInOrder) {
|
|
|
|
|
auto host = std::make_unique<RecordingHostCompositor>();
|
|
|
|
|
RecordingHostCompositor* hostPtr = host.get();
|
|
|
|
|
|
|
|
|
|
ImGuiWindowUICompositor compositor(std::move(host));
|
|
|
|
|
D3D12WindowRenderer renderer = {};
|
|
|
|
|
ASSERT_TRUE(compositor.Initialize(MakeFakeHwnd(), renderer, {}));
|
|
|
|
|
hostPtr->callOrder.clear();
|
|
|
|
|
|
|
|
|
|
bool uiRendered = false;
|
|
|
|
|
constexpr float clearColor[4] = { 0.1f, 0.2f, 0.3f, 0.4f };
|
|
|
|
|
compositor.RenderFrame(
|
|
|
|
|
clearColor,
|
|
|
|
|
[&]() {
|
|
|
|
|
uiRendered = true;
|
|
|
|
|
hostPtr->callOrder.push_back("ui");
|
|
|
|
|
},
|
|
|
|
|
[](const ::XCEngine::Rendering::RenderContext&, const ::XCEngine::Rendering::RenderSurface&) {},
|
|
|
|
|
[](const ::XCEngine::Rendering::RenderContext&, const ::XCEngine::Rendering::RenderSurface&) {});
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(uiRendered);
|
|
|
|
|
EXPECT_EQ(hostPtr->beginFrameCount, 1);
|
|
|
|
|
EXPECT_EQ(hostPtr->endFrameCount, 1);
|
|
|
|
|
EXPECT_EQ(hostPtr->presentedRenderer, &renderer);
|
|
|
|
|
EXPECT_TRUE(hostPtr->beforeUiRenderProvided);
|
|
|
|
|
EXPECT_TRUE(hostPtr->afterUiRenderProvided);
|
|
|
|
|
EXPECT_EQ(hostPtr->lastClearColor[0], clearColor[0]);
|
|
|
|
|
EXPECT_EQ(hostPtr->lastClearColor[1], clearColor[1]);
|
|
|
|
|
EXPECT_EQ(hostPtr->lastClearColor[2], clearColor[2]);
|
|
|
|
|
EXPECT_EQ(hostPtr->lastClearColor[3], clearColor[3]);
|
|
|
|
|
EXPECT_EQ(
|
|
|
|
|
hostPtr->callOrder,
|
|
|
|
|
(std::vector<std::string>{ "begin", "ui", "present" }));
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 12:10:55 +08:00
|
|
|
TEST(ImGuiWindowUICompositorTest, RenderFrameWithoutUiCallbackStillBeginsAndPresents) {
|
|
|
|
|
auto host = std::make_unique<RecordingHostCompositor>();
|
|
|
|
|
RecordingHostCompositor* hostPtr = host.get();
|
|
|
|
|
|
|
|
|
|
ImGuiWindowUICompositor compositor(std::move(host));
|
|
|
|
|
D3D12WindowRenderer renderer = {};
|
|
|
|
|
ASSERT_TRUE(compositor.Initialize(MakeFakeHwnd(), renderer, {}));
|
|
|
|
|
hostPtr->callOrder.clear();
|
|
|
|
|
|
|
|
|
|
constexpr float clearColor[4] = { 0.7f, 0.6f, 0.5f, 0.4f };
|
|
|
|
|
compositor.RenderFrame(clearColor, {}, {}, {});
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(hostPtr->beginFrameCount, 1);
|
|
|
|
|
EXPECT_EQ(hostPtr->endFrameCount, 1);
|
|
|
|
|
EXPECT_EQ(hostPtr->presentedRenderer, &renderer);
|
|
|
|
|
EXPECT_FALSE(hostPtr->beforeUiRenderProvided);
|
|
|
|
|
EXPECT_FALSE(hostPtr->afterUiRenderProvided);
|
|
|
|
|
EXPECT_EQ(hostPtr->lastClearColor[0], clearColor[0]);
|
|
|
|
|
EXPECT_EQ(hostPtr->lastClearColor[1], clearColor[1]);
|
|
|
|
|
EXPECT_EQ(hostPtr->lastClearColor[2], clearColor[2]);
|
|
|
|
|
EXPECT_EQ(hostPtr->lastClearColor[3], clearColor[3]);
|
|
|
|
|
EXPECT_EQ(
|
|
|
|
|
hostPtr->callOrder,
|
|
|
|
|
(std::vector<std::string>{ "begin", "present" }));
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 07:29:27 +08:00
|
|
|
TEST(ImGuiWindowUICompositorTest, HandleWindowMessageAndTextureRegistrationForwardToHost) {
|
|
|
|
|
auto host = std::make_unique<RecordingHostCompositor>();
|
|
|
|
|
RecordingHostCompositor* hostPtr = host.get();
|
|
|
|
|
hostPtr->handleWindowMessageResult = true;
|
|
|
|
|
hostPtr->createTextureDescriptorResult = true;
|
|
|
|
|
hostPtr->nextRegistration = MakeRegistration();
|
|
|
|
|
|
|
|
|
|
ImGuiWindowUICompositor compositor(std::move(host));
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(compositor.HandleWindowMessage(MakeFakeHwnd(), WM_SIZE, 7u, 19u));
|
|
|
|
|
EXPECT_EQ(hostPtr->handleWindowMessageCount, 1);
|
|
|
|
|
EXPECT_EQ(hostPtr->lastMessage, static_cast<UINT>(WM_SIZE));
|
|
|
|
|
EXPECT_EQ(hostPtr->lastWParam, static_cast<WPARAM>(7u));
|
|
|
|
|
EXPECT_EQ(hostPtr->lastLParam, static_cast<LPARAM>(19u));
|
|
|
|
|
|
|
|
|
|
UITextureRegistration registration = {};
|
|
|
|
|
auto* fakeDevice = reinterpret_cast<::XCEngine::RHI::RHIDevice*>(static_cast<std::uintptr_t>(0x41u));
|
|
|
|
|
auto* fakeTexture = reinterpret_cast<::XCEngine::RHI::RHITexture*>(static_cast<std::uintptr_t>(0x59u));
|
|
|
|
|
EXPECT_TRUE(compositor.CreateTextureDescriptor(fakeDevice, fakeTexture, registration));
|
|
|
|
|
EXPECT_EQ(hostPtr->createTextureDescriptorCount, 1);
|
|
|
|
|
EXPECT_EQ(hostPtr->lastDevice, fakeDevice);
|
|
|
|
|
EXPECT_EQ(hostPtr->lastTexture, fakeTexture);
|
|
|
|
|
EXPECT_EQ(registration.cpuHandle.ptr, hostPtr->nextRegistration.cpuHandle.ptr);
|
|
|
|
|
EXPECT_EQ(registration.gpuHandle.ptr, hostPtr->nextRegistration.gpuHandle.ptr);
|
|
|
|
|
EXPECT_EQ(registration.texture.nativeHandle, hostPtr->nextRegistration.texture.nativeHandle);
|
|
|
|
|
|
|
|
|
|
compositor.FreeTextureDescriptor(registration);
|
|
|
|
|
EXPECT_EQ(hostPtr->freeTextureDescriptorCount, 1);
|
|
|
|
|
EXPECT_EQ(hostPtr->freedRegistration.cpuHandle.ptr, registration.cpuHandle.ptr);
|
|
|
|
|
EXPECT_EQ(hostPtr->freedRegistration.gpuHandle.ptr, registration.gpuHandle.ptr);
|
|
|
|
|
EXPECT_EQ(hostPtr->freedRegistration.texture.nativeHandle, registration.texture.nativeHandle);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 12:10:55 +08:00
|
|
|
TEST(ImGuiWindowUICompositorTest, SecondInitializeRebindsRendererForSubsequentFrames) {
|
|
|
|
|
auto host = std::make_unique<RecordingHostCompositor>();
|
|
|
|
|
RecordingHostCompositor* hostPtr = host.get();
|
|
|
|
|
|
|
|
|
|
ImGuiWindowUICompositor compositor(std::move(host));
|
|
|
|
|
D3D12WindowRenderer firstRenderer = {};
|
|
|
|
|
D3D12WindowRenderer secondRenderer = {};
|
|
|
|
|
|
|
|
|
|
ASSERT_TRUE(compositor.Initialize(MakeFakeHwnd(), firstRenderer, {}));
|
|
|
|
|
ASSERT_TRUE(compositor.Initialize(MakeFakeHwnd(), secondRenderer, {}));
|
|
|
|
|
EXPECT_EQ(hostPtr->initializeCount, 2);
|
|
|
|
|
EXPECT_EQ(hostPtr->initializedRenderer, &secondRenderer);
|
|
|
|
|
|
|
|
|
|
compositor.RenderFrame(
|
|
|
|
|
std::array<float, 4>{ 0.2f, 0.3f, 0.4f, 1.0f }.data(),
|
|
|
|
|
[]() {},
|
|
|
|
|
{},
|
|
|
|
|
{});
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(hostPtr->beginFrameCount, 1);
|
|
|
|
|
EXPECT_EQ(hostPtr->endFrameCount, 1);
|
|
|
|
|
EXPECT_EQ(hostPtr->presentedRenderer, &secondRenderer);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 07:29:27 +08:00
|
|
|
TEST(ImGuiWindowUICompositorTest, ShutdownClearsRendererBindingAndPreventsFurtherRender) {
|
|
|
|
|
auto host = std::make_unique<RecordingHostCompositor>();
|
|
|
|
|
RecordingHostCompositor* hostPtr = host.get();
|
|
|
|
|
|
|
|
|
|
ImGuiWindowUICompositor compositor(std::move(host));
|
|
|
|
|
D3D12WindowRenderer renderer = {};
|
|
|
|
|
ASSERT_TRUE(compositor.Initialize(MakeFakeHwnd(), renderer, {}));
|
|
|
|
|
|
|
|
|
|
bool firstUiRendered = false;
|
|
|
|
|
compositor.RenderFrame(
|
|
|
|
|
std::array<float, 4>{ 0.0f, 0.0f, 0.0f, 1.0f }.data(),
|
|
|
|
|
[&]() { firstUiRendered = true; },
|
|
|
|
|
{},
|
|
|
|
|
{});
|
|
|
|
|
EXPECT_TRUE(firstUiRendered);
|
|
|
|
|
EXPECT_EQ(hostPtr->beginFrameCount, 1);
|
|
|
|
|
EXPECT_EQ(hostPtr->endFrameCount, 1);
|
|
|
|
|
|
|
|
|
|
compositor.Shutdown();
|
|
|
|
|
EXPECT_EQ(hostPtr->shutdownCount, 1);
|
|
|
|
|
|
|
|
|
|
bool secondUiRendered = false;
|
|
|
|
|
compositor.RenderFrame(
|
|
|
|
|
std::array<float, 4>{ 1.0f, 0.0f, 0.0f, 1.0f }.data(),
|
|
|
|
|
[&]() { secondUiRendered = true; },
|
|
|
|
|
{},
|
|
|
|
|
{});
|
|
|
|
|
EXPECT_FALSE(secondUiRendered);
|
|
|
|
|
EXPECT_EQ(hostPtr->beginFrameCount, 1);
|
|
|
|
|
EXPECT_EQ(hostPtr->endFrameCount, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(ImGuiWindowUICompositorTest, NullHostCompositorReturnsSafeDefaults) {
|
|
|
|
|
ImGuiWindowUICompositor compositor(std::unique_ptr<IEditorHostCompositor>{});
|
|
|
|
|
D3D12WindowRenderer renderer = {};
|
|
|
|
|
|
|
|
|
|
bool configureFontsCalled = false;
|
|
|
|
|
EXPECT_FALSE(compositor.Initialize(
|
|
|
|
|
MakeFakeHwnd(),
|
|
|
|
|
renderer,
|
|
|
|
|
[&configureFontsCalled]() { configureFontsCalled = true; }));
|
|
|
|
|
EXPECT_FALSE(configureFontsCalled);
|
|
|
|
|
EXPECT_FALSE(compositor.HandleWindowMessage(MakeFakeHwnd(), WM_CLOSE, 0u, 0u));
|
|
|
|
|
|
|
|
|
|
UITextureRegistration registration = {};
|
|
|
|
|
EXPECT_FALSE(compositor.CreateTextureDescriptor(nullptr, nullptr, registration));
|
|
|
|
|
|
|
|
|
|
bool uiRendered = false;
|
|
|
|
|
compositor.RenderFrame(
|
|
|
|
|
std::array<float, 4>{ 0.0f, 0.0f, 0.0f, 1.0f }.data(),
|
|
|
|
|
[&]() { uiRendered = true; },
|
|
|
|
|
{},
|
|
|
|
|
{});
|
|
|
|
|
EXPECT_FALSE(uiRendered);
|
|
|
|
|
|
|
|
|
|
compositor.Shutdown();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|