#include #include "Platform/D3D12WindowRenderer.h" #include "XCUIBackend/IEditorHostCompositor.h" #include "XCUIBackend/ImGuiWindowUICompositor.h" #include "XCUIBackend/UITextureRegistration.h" #include #include #include #include #include #include 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 lastClearColor = { 0.0f, 0.0f, 0.0f, 0.0f }; UITextureRegistration nextRegistration = {}; UITextureRegistration freedRegistration = {}; std::vector 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(beforeUiRender); afterUiRenderProvided = static_cast(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(static_cast(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* 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* 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{ "begin", "ui", "present" })); } TEST(ImGuiWindowUICompositorTest, RenderFrameWithoutUiCallbackStillBeginsAndPresents) { auto host = std::make_unique(); 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{ "begin", "present" })); } TEST(ImGuiWindowUICompositorTest, HandleWindowMessageAndTextureRegistrationForwardToHost) { auto host = std::make_unique(); 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(WM_SIZE)); EXPECT_EQ(hostPtr->lastWParam, static_cast(7u)); EXPECT_EQ(hostPtr->lastLParam, static_cast(19u)); UITextureRegistration registration = {}; auto* fakeDevice = reinterpret_cast<::XCEngine::RHI::RHIDevice*>(static_cast(0x41u)); auto* fakeTexture = reinterpret_cast<::XCEngine::RHI::RHITexture*>(static_cast(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); } TEST(ImGuiWindowUICompositorTest, SecondInitializeRebindsRendererForSubsequentFrames) { auto host = std::make_unique(); 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{ 0.2f, 0.3f, 0.4f, 1.0f }.data(), []() {}, {}, {}); EXPECT_EQ(hostPtr->beginFrameCount, 1); EXPECT_EQ(hostPtr->endFrameCount, 1); EXPECT_EQ(hostPtr->presentedRenderer, &secondRenderer); } TEST(ImGuiWindowUICompositorTest, ShutdownClearsRendererBindingAndPreventsFurtherRender) { auto host = std::make_unique(); RecordingHostCompositor* hostPtr = host.get(); ImGuiWindowUICompositor compositor(std::move(host)); D3D12WindowRenderer renderer = {}; ASSERT_TRUE(compositor.Initialize(MakeFakeHwnd(), renderer, {})); bool firstUiRendered = false; compositor.RenderFrame( std::array{ 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{ 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{}); 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{ 0.0f, 0.0f, 0.0f, 1.0f }.data(), [&]() { uiRendered = true; }, {}, {}); EXPECT_FALSE(uiRendered); compositor.Shutdown(); } } // namespace