From 9525053624d6d08f10b8b1d7cf8a6159eb88bc02 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sun, 5 Apr 2026 06:34:15 +0800 Subject: [PATCH] Split XCUI hosted preview ImGui presenter seam --- docs/plan/XCUI_Phase_Status_2026-04-05.md | 8 +++ new_editor/src/Application.cpp | 10 ++- .../ImGuiXCUIHostedPreviewPresenter.h | 45 +++++++++++++ .../XCUIBackend/XCUIHostedPreviewPresenter.h | 65 +++++-------------- new_editor/src/panels/XCUIDemoPanel.cpp | 17 ++++- new_editor/src/panels/XCUILayoutLabPanel.cpp | 17 ++++- .../test_xcui_hosted_preview_presenter.cpp | 63 +++++++++--------- 7 files changed, 142 insertions(+), 83 deletions(-) create mode 100644 new_editor/src/XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h diff --git a/docs/plan/XCUI_Phase_Status_2026-04-05.md b/docs/plan/XCUI_Phase_Status_2026-04-05.md index 15bf2550..40a1e6d8 100644 --- a/docs/plan/XCUI_Phase_Status_2026-04-05.md +++ b/docs/plan/XCUI_Phase_Status_2026-04-05.md @@ -56,6 +56,7 @@ Current gap: - `new_editor` remains the isolated XCUI sandbox. - Native hosted preview is working as `RHI offscreen surface -> ImGui shell texture embed`. +- Hosted preview surface descriptors now stay on XCUI-owned value types (`UITextureHandle`, `UIPoint`, `UIRect`) instead of exposing ImGui texture/UV types through the generic preview contract. - `XCUI Demo` remains the long-lived effect and behavior testbed. - `XCUI Demo` now covers both single-line and multiline text authoring behavior, including click caret placement, delete/backspace, tab indentation, and optional text-area line numbers. - `XCUI Demo` now consumes the shared `UITextInputController` path for text editing instead of carrying a private key-handling state machine. @@ -64,11 +65,13 @@ Current gap: - Panel diagnostics were expanded to clearly separate preview/runtime/input state and native vs legacy paths. - The editor bridge layer now has smoke coverage for swapchain after-UI rendering hooks and SRV-backed ImGui texture descriptor registration. - `Application` no longer owns the ImGui backend directly; window presentation now routes through `IWindowUICompositor` with an `ImGuiWindowUICompositor` implementation, which currently delegates to `IEditorHostCompositor` / `ImGuiHostCompositor`. +- The generic hosted-preview presenter contract no longer owns `ImGuiTransitionBackend`; the ImGui presenter now sits in a separate `ImGuiXCUIHostedPreviewPresenter` header while the native queue/surface registry remains XCUI-generic. - `XCNewEditor` builds successfully to `build/new_editor/bin/Debug/XCNewEditor.exe`. Current gap: - The shell is still ImGui-hosted. +- The hosted-preview frame submission contract still carries an ImGui draw-list target for the legacy inline presenter path. - Editor-specialized widgets are still incomplete at the shared-module level: the authored prototypes exist, but virtualization, selection models, command routing, property editing models, toolbar/menu chrome, and icon-atlas widgets are not yet extracted into reusable XCUI modules. ## Validated This Phase @@ -77,6 +80,7 @@ Current gap: - `new_editor_xcui_layout_lab_runtime_tests`: `6/6` - `new_editor_xcui_rhi_command_compiler_tests`: `6/6` - `new_editor_xcui_rhi_render_backend_tests`: `5/5` +- `new_editor_xcui_hosted_preview_presenter_tests`: `12/12` - `XCNewEditor` Debug target builds successfully - `core_ui_tests`: `26/26` - `core_ui_style_tests`: `5/5` @@ -129,6 +133,10 @@ Current gap: - `IEditorHostCompositor` - `ImGuiHostCompositor` - `Application` frame/present flow routed through the compositor instead of direct `m_imguiBackend` ownership +- Hosted preview contracts were tightened again: + - generic preview surface metadata stays on XCUI-owned value types + - `ImGuiTransitionBackend` moved behind `ImGuiXCUIHostedPreviewPresenter` + - panel/runtime callers still preserve the same legacy and native-preview behavior - `new_editor` panel/shell diagnostics improvements for hosted preview state. - XCUI asset document loading changed to prefer direct source compilation before `ResourceManager` fallback for the sandbox path, fixing the LayoutLab crash. - `UIDocumentCompiler.cpp` repaired enough to restore full local builds after the duplicated schema-helper regression. diff --git a/new_editor/src/Application.cpp b/new_editor/src/Application.cpp index 05e73254..bff9c552 100644 --- a/new_editor/src/Application.cpp +++ b/new_editor/src/Application.cpp @@ -1,5 +1,6 @@ #include "Application.h" +#include "XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h" #include "XCUIBackend/ImGuiWindowUICompositor.h" #include @@ -848,11 +849,14 @@ void Application::RenderQueuedHostedPreviews( drainStats.renderedDrawListCount += overlayStats.drawListCount; drainStats.renderedCommandCount += overlayStats.renderedCommandCount; drainStats.skippedCommandCount += overlayStats.skippedCommandCount; + ::XCEngine::UI::UITextureHandle previewTexture = {}; + previewTexture.nativeHandle = static_cast(previewSurface.imguiTextureId); + previewTexture.width = previewSurface.width; + previewTexture.height = previewSurface.height; + previewTexture.kind = ::XCEngine::UI::UITextureHandleKind::ImGuiDescriptor; m_hostedPreviewSurfaceRegistry.UpdateSurface( queuedFrame.debugName, - previewSurface.imguiTextureId, - previewSurface.width, - previewSurface.height, + previewTexture, ::XCEngine::UI::UIRect( 0.0f, 0.0f, diff --git a/new_editor/src/XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h b/new_editor/src/XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h new file mode 100644 index 00000000..09150c8e --- /dev/null +++ b/new_editor/src/XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h @@ -0,0 +1,45 @@ +#pragma once + +#include "XCUIBackend/ImGuiTransitionBackend.h" +#include "XCUIBackend/XCUIHostedPreviewPresenter.h" + +#include + +namespace XCEngine { +namespace Editor { +namespace XCUIBackend { + +class ImGuiXCUIHostedPreviewPresenter final : public IXCUIHostedPreviewPresenter { +public: + bool Present(const XCUIHostedPreviewFrame& frame) override { + m_lastStats = {}; + if (frame.drawData == nullptr) { + return false; + } + + m_backend.BeginFrame(); + m_backend.Submit(*frame.drawData); + m_lastStats.submittedDrawListCount = m_backend.GetPendingDrawListCount(); + m_lastStats.submittedCommandCount = m_backend.GetPendingCommandCount(); + m_lastStats.presented = m_backend.EndFrame(frame.targetDrawList); + m_lastStats.flushedDrawListCount = m_backend.GetLastFlushedDrawListCount(); + m_lastStats.flushedCommandCount = m_backend.GetLastFlushedCommandCount(); + return m_lastStats.presented; + } + + const XCUIHostedPreviewStats& GetLastStats() const override { + return m_lastStats; + } + +private: + ImGuiTransitionBackend m_backend = {}; + XCUIHostedPreviewStats m_lastStats = {}; +}; + +inline std::unique_ptr CreateImGuiXCUIHostedPreviewPresenter() { + return std::make_unique(); +} + +} // namespace XCUIBackend +} // namespace Editor +} // namespace XCEngine diff --git a/new_editor/src/XCUIBackend/XCUIHostedPreviewPresenter.h b/new_editor/src/XCUIBackend/XCUIHostedPreviewPresenter.h index 8abe6eae..43698572 100644 --- a/new_editor/src/XCUIBackend/XCUIHostedPreviewPresenter.h +++ b/new_editor/src/XCUIBackend/XCUIHostedPreviewPresenter.h @@ -1,7 +1,5 @@ #pragma once -#include "XCUIBackend/ImGuiTransitionBackend.h" - #include #include @@ -45,15 +43,15 @@ struct XCUIHostedPreviewQueuedFrame { }; struct XCUIHostedPreviewSurfaceImage { - ImTextureID textureId = {}; - ImVec2 uvMin = ImVec2(0.0f, 0.0f); - ImVec2 uvMax = ImVec2(1.0f, 1.0f); + ::XCEngine::UI::UITextureHandle texture = {}; + ::XCEngine::UI::UIPoint uvMin = {}; + ::XCEngine::UI::UIPoint uvMax = ::XCEngine::UI::UIPoint(1.0f, 1.0f); ::XCEngine::UI::UIRect renderedCanvasRect = {}; std::uint32_t surfaceWidth = 0; std::uint32_t surfaceHeight = 0; bool IsValid() const { - return textureId != ImTextureID{} && surfaceWidth > 0u && surfaceHeight > 0u; + return texture.IsValid() && surfaceWidth > 0u && surfaceHeight > 0u; } }; @@ -114,11 +112,9 @@ public: void UpdateSurface( const std::string& debugName, - ImTextureID textureId, - std::uint32_t surfaceWidth, - std::uint32_t surfaceHeight, + const ::XCEngine::UI::UITextureHandle& texture, const ::XCEngine::UI::UIRect& renderedCanvasRect) { - if (debugName.empty() || textureId == ImTextureID{} || surfaceWidth == 0u || surfaceHeight == 0u) { + if (debugName.empty() || !texture.IsValid()) { return; } @@ -130,16 +126,16 @@ public: descriptor = &m_descriptors.back(); } - descriptor->image.textureId = textureId; - descriptor->image.surfaceWidth = surfaceWidth; - descriptor->image.surfaceHeight = surfaceHeight; + descriptor->image.texture = texture; + descriptor->image.surfaceWidth = texture.width; + descriptor->image.surfaceHeight = texture.height; descriptor->image.renderedCanvasRect = renderedCanvasRect; - descriptor->image.uvMin = ImVec2( - renderedCanvasRect.x / static_cast(surfaceWidth), - renderedCanvasRect.y / static_cast(surfaceHeight)); - descriptor->image.uvMax = ImVec2( - (renderedCanvasRect.x + renderedCanvasRect.width) / static_cast(surfaceWidth), - (renderedCanvasRect.y + renderedCanvasRect.height) / static_cast(surfaceHeight)); + descriptor->image.uvMin = ::XCEngine::UI::UIPoint( + renderedCanvasRect.x / static_cast(texture.width), + renderedCanvasRect.y / static_cast(texture.height)); + descriptor->image.uvMax = ::XCEngine::UI::UIPoint( + (renderedCanvasRect.x + renderedCanvasRect.width) / static_cast(texture.width), + (renderedCanvasRect.y + renderedCanvasRect.height) / static_cast(texture.height)); } bool TryGetSurfaceDescriptor( @@ -267,33 +263,6 @@ public: } }; -class ImGuiXCUIHostedPreviewPresenter final : public IXCUIHostedPreviewPresenter { -public: - bool Present(const XCUIHostedPreviewFrame& frame) override { - m_lastStats = {}; - if (frame.drawData == nullptr) { - return false; - } - - m_backend.BeginFrame(); - m_backend.Submit(*frame.drawData); - m_lastStats.submittedDrawListCount = m_backend.GetPendingDrawListCount(); - m_lastStats.submittedCommandCount = m_backend.GetPendingCommandCount(); - m_lastStats.presented = m_backend.EndFrame(frame.targetDrawList); - m_lastStats.flushedDrawListCount = m_backend.GetLastFlushedDrawListCount(); - m_lastStats.flushedCommandCount = m_backend.GetLastFlushedCommandCount(); - return m_lastStats.presented; - } - - const XCUIHostedPreviewStats& GetLastStats() const override { - return m_lastStats; - } - -private: - ImGuiTransitionBackend m_backend = {}; - XCUIHostedPreviewStats m_lastStats = {}; -}; - class QueuedNativeXCUIHostedPreviewPresenter final : public IXCUIHostedPreviewPresenter { public: QueuedNativeXCUIHostedPreviewPresenter( @@ -334,9 +303,7 @@ private: XCUIHostedPreviewStats m_lastStats = {}; }; -inline std::unique_ptr CreateImGuiXCUIHostedPreviewPresenter() { - return std::make_unique(); -} +std::unique_ptr CreateImGuiXCUIHostedPreviewPresenter(); inline std::unique_ptr CreateQueuedNativeXCUIHostedPreviewPresenter( XCUIHostedPreviewQueue& queue, diff --git a/new_editor/src/panels/XCUIDemoPanel.cpp b/new_editor/src/panels/XCUIDemoPanel.cpp index 7498724f..52a48f20 100644 --- a/new_editor/src/panels/XCUIDemoPanel.cpp +++ b/new_editor/src/panels/XCUIDemoPanel.cpp @@ -1,5 +1,6 @@ #include "XCUIDemoPanel.h" +#include "XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h" #include "XCUIBackend/ImGuiXCUIInputAdapter.h" #include @@ -30,6 +31,16 @@ UI::UIRect ToUIRect(const ImVec2& minPoint, const ImVec2& size) { return UI::UIRect(minPoint.x, minPoint.y, size.x, size.y); } +ImTextureID ToImTextureId(const UI::UITextureHandle& texture) { + return texture.IsValid() + ? static_cast(texture.nativeHandle) + : ImTextureID{}; +} + +ImVec2 ToImVec2(const UI::UIPoint& point) { + return ImVec2(point.x, point.y); +} + bool ContainsPoint(const UI::UIRect& rect, const UI::UIPoint& point) { return point.x >= rect.x && point.y >= rect.y && @@ -216,7 +227,11 @@ void XCUIDemoPanel::Render() { if (validCanvas) { ImGui::SetCursorScreenPos(canvasMin); if (showHostedSurfaceImage) { - ImGui::Image(hostedSurfaceImage.textureId, canvasSize, hostedSurfaceImage.uvMin, hostedSurfaceImage.uvMax); + ImGui::Image( + ToImTextureId(hostedSurfaceImage.texture), + canvasSize, + ToImVec2(hostedSurfaceImage.uvMin), + ToImVec2(hostedSurfaceImage.uvMax)); DrawHostedPreviewFrame(drawList, canvasMin, canvasSize); } else { ImGui::InvisibleButton("##XCUIDemoCanvas", canvasSize); diff --git a/new_editor/src/panels/XCUILayoutLabPanel.cpp b/new_editor/src/panels/XCUILayoutLabPanel.cpp index fa7283cd..35413586 100644 --- a/new_editor/src/panels/XCUILayoutLabPanel.cpp +++ b/new_editor/src/panels/XCUILayoutLabPanel.cpp @@ -1,5 +1,6 @@ #include "XCUILayoutLabPanel.h" +#include "XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h" #include #include @@ -23,6 +24,16 @@ UI::UIRect ToUIRect(const ImVec2& minPoint, const ImVec2& size) { return UI::UIRect(minPoint.x, minPoint.y, size.x, size.y); } +ImTextureID ToImTextureId(const UI::UITextureHandle& texture) { + return texture.IsValid() + ? static_cast(texture.nativeHandle) + : ImTextureID{}; +} + +ImVec2 ToImVec2(const UI::UIPoint& point) { + return ImVec2(point.x, point.y); +} + bool ContainsPoint(const UI::UIRect& rect, const UI::UIPoint& point) { return point.x >= rect.x && point.y >= rect.y && @@ -197,7 +208,11 @@ void XCUILayoutLabPanel::Render() { if (validCanvas) { ImGui::SetCursorScreenPos(canvasMin); if (showHostedSurfaceImage) { - ImGui::Image(hostedSurfaceImage.textureId, canvasSize, hostedSurfaceImage.uvMin, hostedSurfaceImage.uvMax); + ImGui::Image( + ToImTextureId(hostedSurfaceImage.texture), + canvasSize, + ToImVec2(hostedSurfaceImage.uvMin), + ToImVec2(hostedSurfaceImage.uvMax)); DrawHostedPreviewFrame(drawList, canvasMin, canvasSize); } else { ImGui::InvisibleButton("##XCUILayoutLabCanvas", canvasSize); diff --git a/tests/NewEditor/test_xcui_hosted_preview_presenter.cpp b/tests/NewEditor/test_xcui_hosted_preview_presenter.cpp index ec085886..b835072a 100644 --- a/tests/NewEditor/test_xcui_hosted_preview_presenter.cpp +++ b/tests/NewEditor/test_xcui_hosted_preview_presenter.cpp @@ -1,5 +1,6 @@ #include +#include "XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h" #include "XCUIBackend/XCUIHostedPreviewPresenter.h" #include @@ -46,6 +47,18 @@ void PrepareImGui(float width = 1024.0f, float height = 768.0f) { io.Fonts->SetTexID(static_cast(1)); } +XCEngine::UI::UITextureHandle MakeHostedPreviewTextureHandle( + std::uintptr_t nativeHandle, + std::uint32_t width, + std::uint32_t height) { + XCEngine::UI::UITextureHandle texture = {}; + texture.nativeHandle = nativeHandle; + texture.width = width; + texture.height = height; + texture.kind = XCEngine::UI::UITextureHandleKind::ImGuiDescriptor; + return texture; +} + TEST(XCUIHostedPreviewPresenterTest, PresentReturnsFalseAndClearsStatsWhenFrameHasNoDrawData) { ImGuiContextScope contextScope; PrepareImGui(); @@ -216,14 +229,14 @@ TEST(XCUIHostedPreviewPresenterTest, QueuedNativePresenterFallsBackLogicalSizeTo surfaceRegistry.UpdateSurface( "XCUI Demo", - static_cast(static_cast(11)), - 640u, - 360u, + MakeHostedPreviewTextureHandle(11u, 640u, 360u), XCEngine::UI::UIRect(0.0f, 0.0f, 320.0f, 180.0f)); ASSERT_TRUE(presenter->TryGetSurfaceImage("XCUI Demo", image)); EXPECT_TRUE(image.IsValid()); - EXPECT_EQ(image.textureId, static_cast(static_cast(11))); + EXPECT_EQ(image.texture.nativeHandle, 11u); + EXPECT_EQ(image.texture.width, 640u); + EXPECT_EQ(image.texture.height, 360u); EXPECT_FLOAT_EQ(image.uvMin.x, 0.0f); EXPECT_FLOAT_EQ(image.uvMin.y, 0.0f); EXPECT_FLOAT_EQ(image.uvMax.x, 0.5f); @@ -324,9 +337,7 @@ TEST(XCUIHostedPreviewPresenterTest, SurfaceRegistryIgnoresUnnamedQueuedFramesAn surfaceRegistry.UpdateSurface( "XCUI Demo", - static_cast(static_cast(13)), - 800u, - 600u, + MakeHostedPreviewTextureHandle(13u, 800u, 600u), XCEngine::UI::UIRect(0.0f, 0.0f, 400.0f, 300.0f)); ASSERT_EQ(surfaceRegistry.GetDescriptors().size(), 1u); @@ -342,14 +353,14 @@ TEST(XCUIHostedPreviewPresenterTest, SurfaceRegistryExposesImageUvForRenderedCan surfaceRegistry.UpdateSurface( "XCUI Demo", - static_cast(static_cast(7)), - 1024u, - 768u, + MakeHostedPreviewTextureHandle(7u, 1024u, 768u), XCEngine::UI::UIRect(128.0f, 96.0f, 512.0f, 384.0f)); ASSERT_TRUE(surfaceRegistry.TryGetSurfaceImage("XCUI Demo", image)); EXPECT_TRUE(image.IsValid()); - EXPECT_EQ(image.textureId, static_cast(static_cast(7))); + EXPECT_EQ(image.texture.nativeHandle, 7u); + EXPECT_EQ(image.texture.width, 1024u); + EXPECT_EQ(image.texture.height, 768u); EXPECT_EQ(image.surfaceWidth, 1024u); EXPECT_EQ(image.surfaceHeight, 768u); EXPECT_FLOAT_EQ(image.uvMin.x, 0.125f); @@ -400,15 +411,15 @@ TEST(XCUIHostedPreviewPresenterTest, SurfaceRegistryTracksQueuedFrameMetadataAlo surfaceRegistry.UpdateSurface( "XCUI Demo", - static_cast(static_cast(9)), - 1024u, - 512u, + MakeHostedPreviewTextureHandle(9u, 1024u, 512u), XCEngine::UI::UIRect(128.0f, 64.0f, 320.0f, 160.0f)); ASSERT_TRUE(surfaceRegistry.TryGetSurfaceDescriptor("XCUI Demo", descriptor)); EXPECT_TRUE(descriptor.queuedThisFrame); EXPECT_TRUE(descriptor.image.IsValid()); - EXPECT_EQ(descriptor.image.textureId, static_cast(static_cast(9))); + EXPECT_EQ(descriptor.image.texture.nativeHandle, 9u); + EXPECT_EQ(descriptor.image.texture.width, 1024u); + EXPECT_EQ(descriptor.image.texture.height, 512u); EXPECT_FLOAT_EQ(descriptor.image.uvMin.x, 0.125f); EXPECT_FLOAT_EQ(descriptor.image.uvMin.y, 0.125f); EXPECT_FLOAT_EQ(descriptor.image.uvMax.x, 0.4375f); @@ -427,9 +438,7 @@ TEST(XCUIHostedPreviewPresenterTest, SurfaceRegistryRejectsInvalidSurfaceUpdates surfaceRegistry.UpdateSurface( "XCUI Demo", - static_cast(static_cast(17)), - 512u, - 256u, + MakeHostedPreviewTextureHandle(17u, 512u, 256u), XCEngine::UI::UIRect(64.0f, 32.0f, 256.0f, 128.0f)); ASSERT_TRUE(surfaceRegistry.TryGetSurfaceDescriptor("XCUI Demo", descriptor)); @@ -438,25 +447,21 @@ TEST(XCUIHostedPreviewPresenterTest, SurfaceRegistryRejectsInvalidSurfaceUpdates surfaceRegistry.UpdateSurface( "XCUI Demo", - ImTextureID{}, - 512u, - 256u, + XCEngine::UI::UITextureHandle{}, XCEngine::UI::UIRect(0.0f, 0.0f, 128.0f, 64.0f)); surfaceRegistry.UpdateSurface( "", - static_cast(static_cast(19)), - 512u, - 256u, + MakeHostedPreviewTextureHandle(19u, 512u, 256u), XCEngine::UI::UIRect(0.0f, 0.0f, 128.0f, 64.0f)); surfaceRegistry.UpdateSurface( "XCUI Demo", - static_cast(static_cast(21)), - 0u, - 256u, + MakeHostedPreviewTextureHandle(21u, 0u, 256u), XCEngine::UI::UIRect(0.0f, 0.0f, 128.0f, 64.0f)); ASSERT_TRUE(surfaceRegistry.TryGetSurfaceDescriptor("XCUI Demo", descriptor)); - EXPECT_EQ(descriptor.image.textureId, originalImage.textureId); + EXPECT_EQ(descriptor.image.texture.nativeHandle, originalImage.texture.nativeHandle); + EXPECT_EQ(descriptor.image.texture.width, originalImage.texture.width); + EXPECT_EQ(descriptor.image.texture.height, originalImage.texture.height); EXPECT_EQ(descriptor.image.surfaceWidth, originalImage.surfaceWidth); EXPECT_EQ(descriptor.image.surfaceHeight, originalImage.surfaceHeight); EXPECT_FLOAT_EQ(descriptor.image.uvMin.x, originalImage.uvMin.x); @@ -504,7 +509,7 @@ TEST(XCUIHostedPreviewPresenterTest, SurfaceRegistryQueriesClearOutputForMissing descriptor.queuedThisFrame = true; XCUIHostedPreviewSurfaceImage image = {}; - image.textureId = static_cast(static_cast(23)); + image.texture = MakeHostedPreviewTextureHandle(23u, 64u, 64u); image.surfaceWidth = 64u; image.surfaceHeight = 64u;