From 231df6ee367955f6ea0205ab69a6640835bb8b5d Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sun, 5 Apr 2026 14:16:53 +0800 Subject: [PATCH] Add XCUI native image UV support --- docs/plan/XCUI_Phase_Status_2026-04-05.md | 19 ++++++------ engine/include/XCEngine/UI/DrawData.h | 8 ++++- .../XCUIBackend/NativeXCUIPanelCanvasHost.h | 9 ++++++ .../XCUIBackend/XCUIRHICommandCompiler.cpp | 8 ++--- .../test_native_xcui_panel_canvas_host.cpp | 13 ++++++--- .../test_xcui_rhi_command_compiler.cpp | 29 +++++++++++++++++++ 6 files changed, 68 insertions(+), 18 deletions(-) diff --git a/docs/plan/XCUI_Phase_Status_2026-04-05.md b/docs/plan/XCUI_Phase_Status_2026-04-05.md index dc91a514..fdb41989 100644 --- a/docs/plan/XCUI_Phase_Status_2026-04-05.md +++ b/docs/plan/XCUI_Phase_Status_2026-04-05.md @@ -48,7 +48,7 @@ Current gap: - Minimal schema self-definition support is landed, including consistency checks for enum/document-only schema metadata, but schema-driven validation for `.xcui` / `.xctheme` instances is still not implemented. - Shared widget/runtime instantiation is still thin and mostly editor-side. -- Common widget primitives are still incomplete: shared text-input presentation/composition on top of the new text controller, multi-selection/focus-traversal/virtualized collection state on top of the new editor-primitive helpers, and native image/source-rect level APIs. +- Common widget primitives are still incomplete: shared text-input presentation/composition on top of the new text controller, multi-selection/focus-traversal/virtualized collection state on top of the new editor-primitive helpers, and a fully native text/font-atlas path that no longer leans on ImGui font internals. ### 2. Runtime/Game Layer @@ -72,6 +72,7 @@ Current gap: - `new_editor` remains the isolated XCUI sandbox. - Native hosted preview is working as `RHI offscreen surface -> hosted surface image present` through the current shell adapter path. - 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. +- Shared `UI::UIDrawData` image commands now carry explicit `uvMin` / `uvMax` source-rect semantics, and the native panel canvas host preserves those UVs when it records hosted surface-image preview commands. - `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. @@ -106,7 +107,7 @@ Current gap: - routes shell shortcuts through the same command router without reading ImGui capture state in the default host path - drives both sandbox runtimes directly from Win32/XCUI input snapshots instead of routing through ImGui panel hosts - composes one native `UIDrawData` packet and submits it through `NativeWindowUICompositor` -- `NativeXCUIPanelCanvasHost` now backs that direct shell path as an externally driven canvas/session host for native cards instead of assuming an ImGui child-window model. +- `NativeXCUIPanelCanvasHost` now backs that direct shell path as an externally driven canvas/session host for native cards instead of assuming an ImGui child-window model, and it now emits native `Image` draw commands for hosted surface-image previews while preserving per-surface UVs. - `XCUIInputBridge.h` no longer includes `imgui.h`, so the public XCUI input bridge seam is now host-neutral at the header boundary. - `XCNewEditor` builds successfully to `build/new_editor/bin/Debug/XCNewEditor.exe`. @@ -116,7 +117,7 @@ Current gap: - Hosted-preview offscreen surface publication is still not wired for the native compositor path because descriptor-backed texture registration is still ImGui-host-centric today. - The native shell currently proves direct runtime composition, but its shell chrome is still a bespoke `Application`-side layout rather than a fully shared XCUI-authored editor shell document. - Editor-specialized widgets are still incomplete at the shared-module level: the authored prototypes exist, but virtualization, multi-selection/focus traversal, toolbar/menu chrome, menu interaction widgets, and icon-atlas widgets are not yet extracted into reusable XCUI modules. -- Shared image support is still incomplete for full native hosted-preview parity: `UI::UIDrawData` still lacks explicit source-rect/UV-level image commands, so native surface-image composition cannot yet mirror the ImGui panel host path exactly. +- The default native path still depends on ImGui font-atlas internals through `XCUIStandaloneTextAtlasProvider`, so text rendering is not yet cleanly de-ImGuiized even though the shell/panel composition path is native by default. ## Validated This Phase @@ -125,7 +126,7 @@ Current gap: - `new_editor_xcui_input_bridge_tests`: `4/4` - `new_editor_imgui_xcui_input_adapter_tests`: `2/2` - `new_editor_xcui_layout_lab_runtime_tests`: `12/12` -- `new_editor_xcui_rhi_command_compiler_tests`: `6/6` +- `new_editor_xcui_rhi_command_compiler_tests`: `7/7` - `new_editor_xcui_rhi_render_backend_tests`: `5/5` - `new_editor_xcui_hosted_preview_presenter_tests`: `20/20` - `new_editor_imgui_window_ui_compositor_tests`: `7/7` @@ -203,6 +204,7 @@ Current gap: - RHI image path improvements: - clipped image UV adjustment - mirrored image UV preservation + - explicit image UV/source-rect submission through `UI::UIDrawData` - external texture binding reuse - per-batch scissor application - Editor bridge helpers now expose: @@ -233,7 +235,7 @@ Current gap: - The native host follow-up is now present in `new_editor`: - `NativeWindowUICompositor` provides a swapchain-native XCUI packet present path beside the legacy ImGui compositor - `Application` now defaults to that native host path and directly composes `XCUI Demo` plus `Layout Lab` into one native shell frame - - `NativeXCUIPanelCanvasHost` now drives externally configured native card sessions for that shell path + - `NativeXCUIPanelCanvasHost` now drives externally configured native card sessions for that shell path and records hosted surface-image preview commands with preserved UVs - new native compositor/native canvas-host tests now cover the new host seam - `XCUIInputBridge.h` no longer includes `imgui.h`, so XCUI input translation is no longer coupled to ImGui at the public header boundary. - `SceneRuntime` layered XCUI routing now has dedicated regression coverage for: @@ -260,8 +262,7 @@ Current gap: - Schema instance validation is still open beyond `.xcschema` self-definition and artifact round-trip coverage. - `ScrollView` is still authored/static; no wheel-driven scrolling or virtualization yet. -- `Image` widgets still do not have source-rect/atlas-subregion level API in the high-level draw command model. -- Editor shell still depends on ImGui as host chrome. +- The default native text path still builds its font atlas on top of ImGui font types/internal baking helpers. - Hosted-preview compatibility presentation still depends on an ImGui-only inline presenter path when not using the queued native surface path. - Editor widget coverage is still prototype-driven inside `LayoutLab`; it has not yet been promoted into a full reusable shared widget/runtime layer with command routing, virtualization, and property-edit transactions. @@ -269,6 +270,6 @@ Current gap: 1. Expand runtime/game-layer ownership from the current `SceneRuntime` UI context into scene-declared HUD/menu bootstrapping, draw submission, and higher-level runtime UI policies. 2. Promote the current editor-facing widget prototypes out of authored `LayoutLab` content and into reusable XCUI widget/runtime modules, then continue with toolbar/menu chrome, shell-state adoption, virtualization, and broader focus/multi-selection behavior. -3. Add a native XCUI host compositor on the existing window-level compositor seam so `new_editor` can present without going through ImGui-owned draw data. -4. Replace the remaining ImGui-only fallback seams in hosted preview and panel canvas hosting with native host implementations so ImGui can become compatibility-only instead of the default shell path. +3. Finish native compositor texture registration and hosted-preview surface publication so the native path no longer depends on ImGui-centric descriptor registration. +4. Strip the remaining default-path ImGui seams down to compatibility-only code, starting with the standalone text/font-atlas path and then the remaining hosted-preview fallback layers. 5. Continue phased validation, commit, push, and plan refresh after each stable batch. diff --git a/engine/include/XCEngine/UI/DrawData.h b/engine/include/XCEngine/UI/DrawData.h index 40dbf7b9..c6a03ad3 100644 --- a/engine/include/XCEngine/UI/DrawData.h +++ b/engine/include/XCEngine/UI/DrawData.h @@ -39,6 +39,8 @@ struct UIDrawCommand { UIDrawCommandType type = UIDrawCommandType::FilledRect; UIRect rect = {}; UIPoint position = {}; + UIPoint uvMin = {}; + UIPoint uvMax = UIPoint(1.0f, 1.0f); UIColor color = {}; float thickness = 1.0f; float rounding = 0.0f; @@ -122,12 +124,16 @@ public: UIDrawCommand& AddImage( const UIRect& rect, const UITextureHandle& texture, - const UIColor& tintColor = {}) { + const UIColor& tintColor = {}, + const UIPoint& uvMin = {}, + const UIPoint& uvMax = UIPoint(1.0f, 1.0f)) { UIDrawCommand command = {}; command.type = UIDrawCommandType::Image; command.rect = rect; command.texture = texture; command.color = tintColor; + command.uvMin = uvMin; + command.uvMax = uvMax; return AddCommand(std::move(command)); } diff --git a/new_editor/src/XCUIBackend/NativeXCUIPanelCanvasHost.h b/new_editor/src/XCUIBackend/NativeXCUIPanelCanvasHost.h index 4d3146ec..93b6a942 100644 --- a/new_editor/src/XCUIBackend/NativeXCUIPanelCanvasHost.h +++ b/new_editor/src/XCUIBackend/NativeXCUIPanelCanvasHost.h @@ -193,6 +193,15 @@ public: m_currentFrame.placeholderSubtitle); } + if (m_currentFrame.showingSurfaceImage) { + EnsureOverlayDrawList().AddImage( + m_currentFrame.session.canvasRect, + m_currentFrame.surfaceImage.texture, + ::XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f), + m_currentFrame.surfaceImage.uvMin, + m_currentFrame.surfaceImage.uvMax); + } + if (request.drawPreviewFrame) { DrawOutlineRect( m_currentFrame.session.canvasRect, diff --git a/new_editor/src/XCUIBackend/XCUIRHICommandCompiler.cpp b/new_editor/src/XCUIBackend/XCUIRHICommandCompiler.cpp index 0ab71d1d..3cd34326 100644 --- a/new_editor/src/XCUIBackend/XCUIRHICommandCompiler.cpp +++ b/new_editor/src/XCUIBackend/XCUIRHICommandCompiler.cpp @@ -502,10 +502,10 @@ void XCUIRHICommandCompiler::Compile( compiled = AppendClippedTexturedRect( outCompiledDrawData.texturedVertices, command.rect, - 0.0f, - 0.0f, - 1.0f, - 1.0f, + command.uvMin.x, + command.uvMin.y, + command.uvMax.x, + command.uvMax.y, command.color, currentClipRect); if (compiled) { diff --git a/tests/NewEditor/test_native_xcui_panel_canvas_host.cpp b/tests/NewEditor/test_native_xcui_panel_canvas_host.cpp index 7789ebf7..86d7e254 100644 --- a/tests/NewEditor/test_native_xcui_panel_canvas_host.cpp +++ b/tests/NewEditor/test_native_xcui_panel_canvas_host.cpp @@ -135,13 +135,18 @@ TEST(NativeXCUIPanelCanvasHostTest, SurfaceImagePathCapturesSurfaceAndPreviewFra EXPECT_TRUE(snapshot.surfaceImage.IsValid()); EXPECT_EQ(snapshot.surfaceImage.texture.nativeHandle, 17u); EXPECT_EQ(snapshot.overlayDrawData.GetDrawListCount(), 1u); - EXPECT_EQ(snapshot.overlayDrawData.GetTotalCommandCount(), 3u); + EXPECT_EQ(snapshot.overlayDrawData.GetTotalCommandCount(), 4u); const auto& commands = snapshot.overlayDrawData.GetDrawLists().front().GetCommands(); - ASSERT_EQ(commands.size(), 3u); + ASSERT_EQ(commands.size(), 4u); EXPECT_EQ(commands[0].type, XCEngine::UI::UIDrawCommandType::PushClipRect); - EXPECT_EQ(commands[1].type, XCEngine::UI::UIDrawCommandType::RectOutline); - EXPECT_EQ(commands[2].type, XCEngine::UI::UIDrawCommandType::PopClipRect); + EXPECT_EQ(commands[1].type, XCEngine::UI::UIDrawCommandType::Image); + EXPECT_FLOAT_EQ(commands[1].uvMin.x, 0.0f); + EXPECT_FLOAT_EQ(commands[1].uvMin.y, 0.0f); + EXPECT_FLOAT_EQ(commands[1].uvMax.x, 1.0f); + EXPECT_FLOAT_EQ(commands[1].uvMax.y, 1.0f); + EXPECT_EQ(commands[2].type, XCEngine::UI::UIDrawCommandType::RectOutline); + EXPECT_EQ(commands[3].type, XCEngine::UI::UIDrawCommandType::PopClipRect); } TEST(NativeXCUIPanelCanvasHostTest, ClearingConfiguredSessionFallsBackToPassiveSnapshot) { diff --git a/tests/NewEditor/test_xcui_rhi_command_compiler.cpp b/tests/NewEditor/test_xcui_rhi_command_compiler.cpp index 2fcdf29d..0421bebc 100644 --- a/tests/NewEditor/test_xcui_rhi_command_compiler.cpp +++ b/tests/NewEditor/test_xcui_rhi_command_compiler.cpp @@ -239,4 +239,33 @@ TEST(XCUIRHICommandCompilerTest, CompilePreservesMirroredImageUvForNegativeRectE EXPECT_EQ(compiled.stats.compiledCommandCount, 1u); } +TEST(XCUIRHICommandCompilerTest, CompileUsesExplicitImageUvRectWhenProvided) { + XCUIRHICommandCompiler compiler = {}; + UIDrawData drawData = {}; + UIDrawList& drawList = drawData.EmplaceDrawList("ExplicitUvImage"); + drawList.AddImage( + UIRect(8.0f, 6.0f, 16.0f, 12.0f), + UITextureHandle{ 123u, 64u, 64u, UITextureHandleKind::ShaderResourceView }, + UIColor(0.9f, 0.8f, 0.7f, 1.0f), + UIPoint(0.25f, 0.40f), + UIPoint(0.75f, 0.90f)); + + XCUIRHICommandCompiler::CompileConfig config = {}; + config.surfaceClipRect = UIRect(0.0f, 0.0f, 64.0f, 64.0f); + + XCUIRHICommandCompiler::CompiledDrawData compiled = {}; + compiler.Compile(drawData, config, compiled); + + ASSERT_EQ(compiled.batches.size(), 1u); + ASSERT_EQ(compiled.texturedVertices.size(), 6u); + EXPECT_FLOAT_EQ(compiled.texturedVertices[0].uv[0], 0.25f); + EXPECT_FLOAT_EQ(compiled.texturedVertices[0].uv[1], 0.40f); + EXPECT_FLOAT_EQ(compiled.texturedVertices[1].uv[0], 0.75f); + EXPECT_FLOAT_EQ(compiled.texturedVertices[1].uv[1], 0.40f); + EXPECT_FLOAT_EQ(compiled.texturedVertices[2].uv[0], 0.75f); + EXPECT_FLOAT_EQ(compiled.texturedVertices[2].uv[1], 0.90f); + EXPECT_EQ(compiled.stats.imageCommandCount, 1u); + EXPECT_EQ(compiled.stats.compiledCommandCount, 1u); +} + } // namespace