Add XCUI native image UV support

This commit is contained in:
2026-04-05 14:16:53 +08:00
parent daa54e0230
commit 231df6ee36
6 changed files with 68 additions and 18 deletions

View File

@@ -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. - 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. - 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 ### 2. Runtime/Game Layer
@@ -72,6 +72,7 @@ Current gap:
- `new_editor` remains the isolated XCUI sandbox. - `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. - 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. - 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` 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 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. - `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 - 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 - 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` - 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. - `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`. - `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. - 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. - 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. - 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 ## Validated This Phase
@@ -125,7 +126,7 @@ Current gap:
- `new_editor_xcui_input_bridge_tests`: `4/4` - `new_editor_xcui_input_bridge_tests`: `4/4`
- `new_editor_imgui_xcui_input_adapter_tests`: `2/2` - `new_editor_imgui_xcui_input_adapter_tests`: `2/2`
- `new_editor_xcui_layout_lab_runtime_tests`: `12/12` - `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_rhi_render_backend_tests`: `5/5`
- `new_editor_xcui_hosted_preview_presenter_tests`: `20/20` - `new_editor_xcui_hosted_preview_presenter_tests`: `20/20`
- `new_editor_imgui_window_ui_compositor_tests`: `7/7` - `new_editor_imgui_window_ui_compositor_tests`: `7/7`
@@ -203,6 +204,7 @@ Current gap:
- RHI image path improvements: - RHI image path improvements:
- clipped image UV adjustment - clipped image UV adjustment
- mirrored image UV preservation - mirrored image UV preservation
- explicit image UV/source-rect submission through `UI::UIDrawData`
- external texture binding reuse - external texture binding reuse
- per-batch scissor application - per-batch scissor application
- Editor bridge helpers now expose: - Editor bridge helpers now expose:
@@ -233,7 +235,7 @@ Current gap:
- The native host follow-up is now present in `new_editor`: - 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 - `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 - `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 - 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. - `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: - `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. - 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. - `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. - The default native text path still builds its font atlas on top of ImGui font types/internal baking helpers.
- Editor shell still depends on ImGui as host chrome.
- Hosted-preview compatibility presentation still depends on an ImGui-only inline presenter path when not using the queued native surface path. - 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. - 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. 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. 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. 3. Finish native compositor texture registration and hosted-preview surface publication so the native path no longer depends on ImGui-centric descriptor registration.
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. 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. 5. Continue phased validation, commit, push, and plan refresh after each stable batch.

View File

@@ -39,6 +39,8 @@ struct UIDrawCommand {
UIDrawCommandType type = UIDrawCommandType::FilledRect; UIDrawCommandType type = UIDrawCommandType::FilledRect;
UIRect rect = {}; UIRect rect = {};
UIPoint position = {}; UIPoint position = {};
UIPoint uvMin = {};
UIPoint uvMax = UIPoint(1.0f, 1.0f);
UIColor color = {}; UIColor color = {};
float thickness = 1.0f; float thickness = 1.0f;
float rounding = 0.0f; float rounding = 0.0f;
@@ -122,12 +124,16 @@ public:
UIDrawCommand& AddImage( UIDrawCommand& AddImage(
const UIRect& rect, const UIRect& rect,
const UITextureHandle& texture, const UITextureHandle& texture,
const UIColor& tintColor = {}) { const UIColor& tintColor = {},
const UIPoint& uvMin = {},
const UIPoint& uvMax = UIPoint(1.0f, 1.0f)) {
UIDrawCommand command = {}; UIDrawCommand command = {};
command.type = UIDrawCommandType::Image; command.type = UIDrawCommandType::Image;
command.rect = rect; command.rect = rect;
command.texture = texture; command.texture = texture;
command.color = tintColor; command.color = tintColor;
command.uvMin = uvMin;
command.uvMax = uvMax;
return AddCommand(std::move(command)); return AddCommand(std::move(command));
} }

View File

@@ -193,6 +193,15 @@ public:
m_currentFrame.placeholderSubtitle); 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) { if (request.drawPreviewFrame) {
DrawOutlineRect( DrawOutlineRect(
m_currentFrame.session.canvasRect, m_currentFrame.session.canvasRect,

View File

@@ -502,10 +502,10 @@ void XCUIRHICommandCompiler::Compile(
compiled = AppendClippedTexturedRect( compiled = AppendClippedTexturedRect(
outCompiledDrawData.texturedVertices, outCompiledDrawData.texturedVertices,
command.rect, command.rect,
0.0f, command.uvMin.x,
0.0f, command.uvMin.y,
1.0f, command.uvMax.x,
1.0f, command.uvMax.y,
command.color, command.color,
currentClipRect); currentClipRect);
if (compiled) { if (compiled) {

View File

@@ -135,13 +135,18 @@ TEST(NativeXCUIPanelCanvasHostTest, SurfaceImagePathCapturesSurfaceAndPreviewFra
EXPECT_TRUE(snapshot.surfaceImage.IsValid()); EXPECT_TRUE(snapshot.surfaceImage.IsValid());
EXPECT_EQ(snapshot.surfaceImage.texture.nativeHandle, 17u); EXPECT_EQ(snapshot.surfaceImage.texture.nativeHandle, 17u);
EXPECT_EQ(snapshot.overlayDrawData.GetDrawListCount(), 1u); 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(); 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[0].type, XCEngine::UI::UIDrawCommandType::PushClipRect);
EXPECT_EQ(commands[1].type, XCEngine::UI::UIDrawCommandType::RectOutline); EXPECT_EQ(commands[1].type, XCEngine::UI::UIDrawCommandType::Image);
EXPECT_EQ(commands[2].type, XCEngine::UI::UIDrawCommandType::PopClipRect); 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) { TEST(NativeXCUIPanelCanvasHostTest, ClearingConfiguredSessionFallsBackToPassiveSnapshot) {

View File

@@ -239,4 +239,33 @@ TEST(XCUIRHICommandCompilerTest, CompilePreservesMirroredImageUvForNegativeRectE
EXPECT_EQ(compiled.stats.compiledCommandCount, 1u); 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 } // namespace