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.
- 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.

View File

@@ -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));
}

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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