Publish native hosted preview textures through XCUI compositor

This commit is contained in:
2026-04-05 14:36:02 +08:00
parent 231df6ee36
commit b05e76de0c
7 changed files with 605 additions and 36 deletions

View File

@@ -20,6 +20,8 @@ Old `editor` replacement is explicitly out of scope for this phase.
- `NativeWindowUICompositor` is now buildable alongside the legacy ImGui compositor - `NativeWindowUICompositor` is now buildable alongside the legacy ImGui compositor
- `Application` now defaults to a native XCUI host path instead of creating the ImGui shell by default - `Application` now defaults to a native XCUI host path instead of creating the ImGui shell by default
- the native default path now drives `XCUIDemoRuntime` and `XCUILayoutLabRuntime` directly, composes their `UIDrawData`, and submits one swapchain packet through the native compositor - the native default path now drives `XCUIDemoRuntime` and `XCUILayoutLabRuntime` directly, composes their `UIDrawData`, and submits one swapchain packet through the native compositor
- the native compositor now publishes hosted-preview textures as SRV-backed `UITextureRegistration` / `UITextureHandle` values instead of relying on ImGui-only descriptor semantics
- the native shell now begins the hosted-preview queue/registry lifecycle each frame, queues native preview frames, drains them during the native render pass, and consumes published hosted-surface images directly in panel cards with live/warming placeholder states
- the old ImGui shell path remains present as an explicit compatibility host instead of the default host - the old ImGui shell path remains present as an explicit compatibility host instead of the default host
- `XCUIInputBridge.h` no longer drags `imgui.h` through the public XCUI input seam - `XCUIInputBridge.h` no longer drags `imgui.h` through the public XCUI input seam
- Old `editor` replacement remains deferred; all active execution still stays inside XCUI shared code and `new_editor`. - Old `editor` replacement remains deferred; all active execution still stays inside XCUI shared code and `new_editor`.
@@ -70,7 +72,7 @@ Current gap:
### 3. Editor Layer ### 3. Editor Layer
- `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 now working end-to-end as `RHI offscreen surface -> SRV-backed publication -> hosted surface image present` through the native shell 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. - 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.
@@ -108,13 +110,14 @@ Current gap:
- 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, and it now emits native `Image` draw commands for hosted surface-image previews while preserving per-surface UVs. - `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.
- `NativeWindowUICompositor` now creates and frees SRV-backed texture registrations for hosted preview surfaces, so native publication no longer depends on ImGui descriptor handles.
- `Application` now runs the hosted-preview lifecycle in both legacy and native frame paths, treats published textures as XCUI-owned `UITextureHandle` state, queues native preview frames from `BuildNativeShellDrawData(...)`, and drains them during native rendering before shell chrome overlays.
- `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`.
Current gap: Current gap:
- The default shell host is now native, but the legacy ImGui shell and panel path still exists as a compatibility host and is still compiled into `new_editor`. - The default shell host is now native, but the legacy ImGui shell and panel path still exists as a compatibility host and is still compiled into `new_editor`.
- 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.
- 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. - 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.
@@ -130,9 +133,9 @@ Current gap:
- `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`
- `new_editor_native_window_ui_compositor_tests`: `5/5` - `new_editor_native_window_ui_compositor_tests`: `8/8`
- `new_editor_xcui_editor_command_router_tests`: `5/5` - `new_editor_xcui_editor_command_router_tests`: `5/5`
- `new_editor_application_shell_command_bindings_tests`: `6/6` - `new_editor_application_shell_command_bindings_tests`: `8/8`
- `new_editor_xcui_shell_chrome_state_tests`: `11/11` - `new_editor_xcui_shell_chrome_state_tests`: `11/11`
- `new_editor_xcui_panel_canvas_host_tests`: `4/4` - `new_editor_xcui_panel_canvas_host_tests`: `4/4`
- `new_editor_imgui_xcui_panel_canvas_host_tests`: `1/1` - `new_editor_imgui_xcui_panel_canvas_host_tests`: `1/1`
@@ -250,6 +253,12 @@ Current gap:
- generic preview frame submission no longer carries an ImGui draw-list pointer - generic preview frame submission no longer carries an ImGui draw-list pointer
- the ImGui presenter now resolves inline draw targets through an explicit ImGui-only binding seam - the ImGui presenter now resolves inline draw targets through an explicit ImGui-only binding seam
- panel/runtime callers still preserve the same legacy and native-preview behavior - panel/runtime callers still preserve the same legacy and native-preview behavior
- Native hosted-preview publication is now wired through the default shell path:
- `NativeWindowUICompositor` publishes hosted preview textures as SRV-backed XCUI registrations and frees them through the compositor seam
- `Application::BeginHostedPreviewFrameLifecycle(...)` now resets queue/registry state for both legacy and native frame paths
- hosted-preview surface readiness now keys on published texture availability instead of ImGui-style descriptor validity
- `BuildNativeShellDrawData(...)` now queues native preview frames for `XCUI Demo` / `LayoutLab`, while shell cards consume the previously published hosted-surface image with warming/live placeholder text
- native compositor and shell-command tests now cover the new publication / lifecycle guards
- `LayoutLab` now resolves editor collection widget taxonomy and metrics through shared `UIEditorCollectionPrimitives` helpers instead of duplicating the same tag and metric rules inside the sandbox runtime. - `LayoutLab` now resolves editor collection widget taxonomy and metrics through shared `UIEditorCollectionPrimitives` helpers instead of duplicating the same tag and metric rules inside the sandbox runtime.
- `LayoutLab` runtime now consumes shared keyboard-navigation semantics for list/tree/property traversal, while the remaining panel-level key mapping is tracked as an editor-host integration gap rather than a runtime gap. - `LayoutLab` runtime now consumes shared keyboard-navigation semantics for list/tree/property traversal, while the remaining panel-level key mapping is tracked as an editor-host integration gap rather than a runtime gap.
- `LayoutLab` panel now maps concrete arrow/home/end keys into the shared navigation model, with dedicated panel-level coverage proving that the sandbox UI can actually drive the runtime navigation seam end-to-end. - `LayoutLab` panel now maps concrete arrow/home/end keys into the shared navigation model, with dedicated panel-level coverage proving that the sandbox UI can actually drive the runtime navigation seam end-to-end.
@@ -270,6 +279,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. Finish native compositor texture registration and hosted-preview surface publication so the native path no longer depends on ImGui-centric descriptor registration. 3. 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.
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. 4. Promote the native shell chrome and card layout out of bespoke `Application` code into a shared XCUI/editor-layer shell model or authored shell document.
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

@@ -591,7 +591,7 @@ void Application::ShutdownRenderer() {
void Application::DestroyHostedPreviewSurfaces() { void Application::DestroyHostedPreviewSurfaces() {
for (HostedPreviewOffscreenSurface& previewSurface : m_hostedPreviewSurfaces) { for (HostedPreviewOffscreenSurface& previewSurface : m_hostedPreviewSurfaces) {
if (previewSurface.textureRegistration.cpuHandle.ptr != 0) { if (Application::HasHostedPreviewTextureRegistration(previewSurface.textureRegistration)) {
if (m_windowCompositor != nullptr) { if (m_windowCompositor != nullptr) {
m_windowCompositor->FreeTextureDescriptor(previewSurface.textureRegistration); m_windowCompositor->FreeTextureDescriptor(previewSurface.textureRegistration);
} }
@@ -698,7 +698,7 @@ bool Application::EnsureHostedPreviewSurface(
return true; return true;
} }
if (previewSurface.textureRegistration.cpuHandle.ptr != 0) { if (Application::HasHostedPreviewTextureRegistration(previewSurface.textureRegistration)) {
if (m_windowCompositor != nullptr) { if (m_windowCompositor != nullptr) {
m_windowCompositor->FreeTextureDescriptor(previewSurface.textureRegistration); m_windowCompositor->FreeTextureDescriptor(previewSurface.textureRegistration);
} }
@@ -980,6 +980,35 @@ bool Application::RenderHostedPreviewOffscreenSurface(
for (const NativeShellPanelLayout& panelLayout : panelLayouts) { for (const NativeShellPanelLayout& panelLayout : panelLayouts) {
if (panelLayout.panelId == ShellPanelId::XCUIDemo) { if (panelLayout.panelId == ShellPanelId::XCUIDemo) {
const ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUIDemo);
const bool nativeHostedPreview =
panelState != nullptr &&
panelState->hostedPreviewEnabled &&
IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo);
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor hostedSurfaceDescriptor = {};
const bool hasHostedSurfaceDescriptor =
nativeHostedPreview &&
panelState != nullptr &&
!panelState->previewDebugName.empty() &&
m_hostedPreviewSurfaceRegistry.TryGetSurfaceDescriptor(
panelState->previewDebugName.data(),
hostedSurfaceDescriptor);
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage hostedSurfaceImage = {};
const bool showHostedSurfaceImage =
nativeHostedPreview &&
panelState != nullptr &&
!panelState->previewDebugName.empty() &&
m_hostedPreviewSurfaceRegistry.TryGetSurfaceImage(
panelState->previewDebugName.data(),
hostedSurfaceImage);
const NativeHostedPreviewConsumption previewConsumption =
Application::ResolveNativeHostedPreviewConsumption(
nativeHostedPreview,
hasHostedSurfaceDescriptor,
showHostedSurfaceImage,
"Native XCUI preview pending",
"Waiting for queued native preview output to publish into the shell card.");
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = {}; ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = {};
canvasSession.hostRect = panelLayout.panelRect; canvasSession.hostRect = panelLayout.panelRect;
canvasSession.canvasRect = panelLayout.canvasRect; canvasSession.canvasRect = panelLayout.canvasRect;
@@ -994,6 +1023,12 @@ bool Application::RenderHostedPreviewOffscreenSurface(
canvasRequest.height = panelLayout.panelRect.height; canvasRequest.height = panelLayout.panelRect.height;
canvasRequest.topInset = panelLayout.canvasRect.y - panelLayout.panelRect.y; canvasRequest.topInset = panelLayout.canvasRect.y - panelLayout.panelRect.y;
canvasRequest.drawPreviewFrame = false; canvasRequest.drawPreviewFrame = false;
canvasRequest.showSurfaceImage = previewConsumption.showSurfaceImage;
canvasRequest.surfaceImage = hostedSurfaceImage;
canvasRequest.placeholderTitle =
previewConsumption.placeholderTitle.empty() ? nullptr : previewConsumption.placeholderTitle.data();
canvasRequest.placeholderSubtitle =
previewConsumption.placeholderSubtitle.empty() ? nullptr : previewConsumption.placeholderSubtitle.data();
const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession resolvedSession = const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession resolvedSession =
m_nativeDemoCanvasHost.BeginCanvas(canvasRequest); m_nativeDemoCanvasHost.BeginCanvas(canvasRequest);
@@ -1018,7 +1053,21 @@ bool Application::RenderHostedPreviewOffscreenSurface(
input.events = panelFrameDelta.events; input.events = panelFrameDelta.events;
const auto& frame = m_nativeDemoRuntime.Update(input); const auto& frame = m_nativeDemoRuntime.Update(input);
if (previewConsumption.queueRuntimeFrame) {
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame previewFrame = {};
previewFrame.drawData = &frame.drawData;
previewFrame.canvasRect = resolvedSession.canvasRect;
previewFrame.logicalSize = UI::UISize(
resolvedSession.canvasRect.width,
resolvedSession.canvasRect.height);
previewFrame.debugName =
panelState != nullptr ? panelState->previewDebugName.data() : nullptr;
previewFrame.debugSource =
panelState != nullptr ? panelState->previewDebugSource.data() : nullptr;
m_hostedPreviewQueue.Submit(previewFrame);
} else if (previewConsumption.appendRuntimeDrawDataToShell) {
AppendDrawData(composedDrawData, frame.drawData); AppendDrawData(composedDrawData, frame.drawData);
}
if (IsShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud)) { if (IsShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud)) {
const auto& stats = frame.stats; const auto& stats = frame.stats;
@@ -1049,8 +1098,9 @@ bool Application::RenderHostedPreviewOffscreenSurface(
textMuted); textMuted);
} }
if (previewConsumption.drawRuntimeDebugRects) {
const auto drawDebugRect = const auto drawDebugRect =
[this, &stats](const std::string& elementId, const UI::UIColor& color, const char* label) { [this](const std::string& elementId, const UI::UIColor& color, const char* label) {
if (elementId.empty()) { if (elementId.empty()) {
return; return;
} }
@@ -1060,11 +1110,21 @@ bool Application::RenderHostedPreviewOffscreenSurface(
} }
m_nativeDemoCanvasHost.DrawOutlineRect(rect, color, 2.0f, 6.0f); m_nativeDemoCanvasHost.DrawOutlineRect(rect, color, 2.0f, 6.0f);
if (label != nullptr && label[0] != '\0') { if (label != nullptr && label[0] != '\0') {
m_nativeDemoCanvasHost.DrawText(UI::UIPoint(rect.x + 4.0f, rect.y + 4.0f), label, color); m_nativeDemoCanvasHost.DrawText(
UI::UIPoint(rect.x + 4.0f, rect.y + 4.0f),
label,
color);
} }
}; };
drawDebugRect(stats.hoveredElementId, UI::UIColor(1.0f, 195.0f / 255.0f, 64.0f / 255.0f, 1.0f), "hover"); drawDebugRect(
drawDebugRect(stats.focusedElementId, UI::UIColor(64.0f / 255.0f, 214.0f / 255.0f, 1.0f, 1.0f), "focus"); stats.hoveredElementId,
UI::UIColor(1.0f, 195.0f / 255.0f, 64.0f / 255.0f, 1.0f),
"hover");
drawDebugRect(
stats.focusedElementId,
UI::UIColor(64.0f / 255.0f, 214.0f / 255.0f, 1.0f, 1.0f),
"focus");
}
} }
m_nativeDemoCanvasHost.EndCanvas(); m_nativeDemoCanvasHost.EndCanvas();
@@ -1072,7 +1132,9 @@ bool Application::RenderHostedPreviewOffscreenSurface(
NativePanelFrameSummary summary = {}; NativePanelFrameSummary summary = {};
summary.layout = panelLayout; summary.layout = panelLayout;
summary.lineA = m_nativeDemoReloadSucceeded summary.lineA = m_nativeDemoReloadSucceeded
? frame.stats.statusMessage ? Application::ComposeNativeHostedPreviewStatusLine(
previewConsumption,
frame.stats.statusMessage)
: "Document reload failed; showing last retained runtime state."; : "Document reload failed; showing last retained runtime state.";
summary.lineB = summary.lineB =
std::string(panelLayout.active ? "Active" : "Passive") + std::string(panelLayout.active ? "Active" : "Passive") +
@@ -1085,6 +1147,35 @@ bool Application::RenderHostedPreviewOffscreenSurface(
continue; continue;
} }
const ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab);
const bool nativeHostedPreview =
panelState != nullptr &&
panelState->hostedPreviewEnabled &&
IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab);
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor hostedSurfaceDescriptor = {};
const bool hasHostedSurfaceDescriptor =
nativeHostedPreview &&
panelState != nullptr &&
!panelState->previewDebugName.empty() &&
m_hostedPreviewSurfaceRegistry.TryGetSurfaceDescriptor(
panelState->previewDebugName.data(),
hostedSurfaceDescriptor);
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage hostedSurfaceImage = {};
const bool showHostedSurfaceImage =
nativeHostedPreview &&
panelState != nullptr &&
!panelState->previewDebugName.empty() &&
m_hostedPreviewSurfaceRegistry.TryGetSurfaceImage(
panelState->previewDebugName.data(),
hostedSurfaceImage);
const NativeHostedPreviewConsumption previewConsumption =
Application::ResolveNativeHostedPreviewConsumption(
nativeHostedPreview,
hasHostedSurfaceDescriptor,
showHostedSurfaceImage,
"Native layout preview pending",
"Waiting for queued native preview output to publish into the layout card.");
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = {}; ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = {};
canvasSession.hostRect = panelLayout.panelRect; canvasSession.hostRect = panelLayout.panelRect;
canvasSession.canvasRect = panelLayout.canvasRect; canvasSession.canvasRect = panelLayout.canvasRect;
@@ -1099,6 +1190,12 @@ bool Application::RenderHostedPreviewOffscreenSurface(
canvasRequest.height = panelLayout.panelRect.height; canvasRequest.height = panelLayout.panelRect.height;
canvasRequest.topInset = panelLayout.canvasRect.y - panelLayout.panelRect.y; canvasRequest.topInset = panelLayout.canvasRect.y - panelLayout.panelRect.y;
canvasRequest.drawPreviewFrame = false; canvasRequest.drawPreviewFrame = false;
canvasRequest.showSurfaceImage = previewConsumption.showSurfaceImage;
canvasRequest.surfaceImage = hostedSurfaceImage;
canvasRequest.placeholderTitle =
previewConsumption.placeholderTitle.empty() ? nullptr : previewConsumption.placeholderTitle.data();
canvasRequest.placeholderSubtitle =
previewConsumption.placeholderSubtitle.empty() ? nullptr : previewConsumption.placeholderSubtitle.data();
const auto resolvedSession = m_nativeLayoutCanvasHost.BeginCanvas(canvasRequest); const auto resolvedSession = m_nativeLayoutCanvasHost.BeginCanvas(canvasRequest);
const bool wantsKeyboard = panelLayout.active; const bool wantsKeyboard = panelLayout.active;
@@ -1119,13 +1216,29 @@ bool Application::RenderHostedPreviewOffscreenSurface(
panelLayout.active && ShouldCaptureKeyboardNavigation(resolvedSession, m_nativeLayoutRuntime.GetFrameResult())); panelLayout.active && ShouldCaptureKeyboardNavigation(resolvedSession, m_nativeLayoutRuntime.GetFrameResult()));
const auto& frame = m_nativeLayoutRuntime.Update(input); const auto& frame = m_nativeLayoutRuntime.Update(input);
if (previewConsumption.queueRuntimeFrame) {
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame previewFrame = {};
previewFrame.drawData = &frame.drawData;
previewFrame.canvasRect = resolvedSession.canvasRect;
previewFrame.logicalSize = UI::UISize(
resolvedSession.canvasRect.width,
resolvedSession.canvasRect.height);
previewFrame.debugName =
panelState != nullptr ? panelState->previewDebugName.data() : nullptr;
previewFrame.debugSource =
panelState != nullptr ? panelState->previewDebugSource.data() : nullptr;
m_hostedPreviewQueue.Submit(previewFrame);
} else if (previewConsumption.appendRuntimeDrawDataToShell) {
AppendDrawData(composedDrawData, frame.drawData); AppendDrawData(composedDrawData, frame.drawData);
}
m_nativeLayoutCanvasHost.EndCanvas(); m_nativeLayoutCanvasHost.EndCanvas();
NativePanelFrameSummary summary = {}; NativePanelFrameSummary summary = {};
summary.layout = panelLayout; summary.layout = panelLayout;
summary.lineA = m_nativeLayoutReloadSucceeded summary.lineA = m_nativeLayoutReloadSucceeded
? frame.stats.statusMessage ? Application::ComposeNativeHostedPreviewStatusLine(
previewConsumption,
frame.stats.statusMessage)
: "Layout lab reload failed; showing last retained runtime state."; : "Layout lab reload failed; showing last retained runtime state.";
summary.lineB = summary.lineB =
std::to_string(frame.stats.rowCount) + " rows | " + std::to_string(frame.stats.rowCount) + " rows | " +
@@ -1197,8 +1310,9 @@ bool Application::RenderHostedPreviewOffscreenSurface(
} }
void Application::FrameLegacyImGuiHost() { void Application::FrameLegacyImGuiHost() {
m_hostedPreviewQueue.BeginFrame(); Application::BeginHostedPreviewFrameLifecycle(
m_hostedPreviewSurfaceRegistry.BeginFrame(); m_hostedPreviewQueue,
m_hostedPreviewSurfaceRegistry);
SyncHostedPreviewSurfaces(); SyncHostedPreviewSurfaces();
if (m_windowCompositor == nullptr) { if (m_windowCompositor == nullptr) {
m_xcuiInputSource.ClearFrameTransients(); m_xcuiInputSource.ClearFrameTransients();
@@ -1273,6 +1387,11 @@ void Application::FrameLegacyImGuiHost() {
} }
void Application::FrameNativeXCUIHost() { void Application::FrameNativeXCUIHost() {
Application::BeginHostedPreviewFrameLifecycle(
m_hostedPreviewQueue,
m_hostedPreviewSurfaceRegistry);
SyncHostedPreviewSurfaces();
auto* nativeCompositor = auto* nativeCompositor =
dynamic_cast<::XCEngine::Editor::XCUIBackend::NativeWindowUICompositor*>(m_windowCompositor.get()); dynamic_cast<::XCEngine::Editor::XCUIBackend::NativeWindowUICompositor*>(m_windowCompositor.get());
if (nativeCompositor == nullptr) { if (nativeCompositor == nullptr) {
@@ -1287,6 +1406,7 @@ void Application::FrameNativeXCUIHost() {
const auto shellFrameDelta = DispatchShellShortcuts(shellSnapshot); const auto shellFrameDelta = DispatchShellShortcuts(shellSnapshot);
::XCEngine::UI::UIDrawData nativeShellDrawData = BuildNativeShellDrawData(shellSnapshot, shellFrameDelta); ::XCEngine::UI::UIDrawData nativeShellDrawData = BuildNativeShellDrawData(shellSnapshot, shellFrameDelta);
nativeCompositor->SubmitRenderPacket(nativeShellDrawData, &m_hostedPreviewTextAtlasProvider); nativeCompositor->SubmitRenderPacket(nativeShellDrawData, &m_hostedPreviewTextAtlasProvider);
SyncHostedPreviewSurfaces();
m_windowCompositor->RenderFrame( m_windowCompositor->RenderFrame(
kClearColor, kClearColor,
@@ -1294,6 +1414,8 @@ void Application::FrameNativeXCUIHost() {
[this]( [this](
const ::XCEngine::Rendering::RenderContext& renderContext, const ::XCEngine::Rendering::RenderContext& renderContext,
const ::XCEngine::Rendering::RenderSurface& surface) { const ::XCEngine::Rendering::RenderSurface& surface) {
RenderQueuedHostedPreviews(renderContext, surface);
MainWindowNativeBackdropRenderer::FrameState frameState = {}; MainWindowNativeBackdropRenderer::FrameState frameState = {};
frameState.elapsedSeconds = static_cast<float>( frameState.elapsedSeconds = static_cast<float>(
std::chrono::duration<double>(std::chrono::steady_clock::now() - m_startTime).count()); std::chrono::duration<double>(std::chrono::steady_clock::now() - m_startTime).count());

View File

@@ -24,6 +24,7 @@
#include <initializer_list> #include <initializer_list>
#include <memory> #include <memory>
#include <string> #include <string>
#include <string_view>
#include <vector> #include <vector>
#include <windows.h> #include <windows.h>
@@ -86,6 +87,23 @@ public:
std::function<void()> onHostedPreviewModeChanged = {}; std::function<void()> onHostedPreviewModeChanged = {};
}; };
enum class NativeHostedPreviewSurfaceState : std::uint8_t {
Disabled = 0,
AwaitingSubmit,
Warming,
Live
};
struct NativeHostedPreviewConsumption {
NativeHostedPreviewSurfaceState surfaceState = NativeHostedPreviewSurfaceState::Disabled;
bool queueRuntimeFrame = false;
bool appendRuntimeDrawDataToShell = true;
bool showSurfaceImage = false;
bool drawRuntimeDebugRects = true;
std::string_view placeholderTitle = {};
std::string_view placeholderSubtitle = {};
};
static ::XCEngine::Editor::XCUIBackend::XCUIEditorCommandInputSnapshot BuildShellShortcutSnapshot( static ::XCEngine::Editor::XCUIBackend::XCUIEditorCommandInputSnapshot BuildShellShortcutSnapshot(
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta& frameDelta) { const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta& frameDelta) {
::XCEngine::Editor::XCUIBackend::XCUIEditorCommandInputSnapshot snapshot = {}; ::XCEngine::Editor::XCUIBackend::XCUIEditorCommandInputSnapshot snapshot = {};
@@ -251,6 +269,69 @@ public:
bindings.onHostedPreviewModeChanged); bindings.onHostedPreviewModeChanged);
} }
static void BeginHostedPreviewFrameLifecycle(
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewQueue& previewQueue,
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceRegistry& surfaceRegistry) {
previewQueue.BeginFrame();
surfaceRegistry.BeginFrame();
}
static bool HasHostedPreviewTextureRegistration(
const ::XCEngine::Editor::XCUIBackend::UITextureRegistration& registration) {
return registration.texture.IsValid() ||
registration.cpuHandle.ptr != 0u ||
registration.gpuHandle.ptr != 0u;
}
static bool HasHostedPreviewPublishedTexture(
const ::XCEngine::Editor::XCUIBackend::UITextureRegistration& registration) {
return registration.texture.IsValid();
}
static NativeHostedPreviewConsumption ResolveNativeHostedPreviewConsumption(
bool nativeHostedPreview,
bool hasHostedSurfaceDescriptor,
bool showHostedSurfaceImage,
std::string_view pendingTitle = {},
std::string_view pendingSubtitle = {}) {
NativeHostedPreviewConsumption consumption = {};
if (!nativeHostedPreview) {
return consumption;
}
consumption.queueRuntimeFrame = true;
consumption.appendRuntimeDrawDataToShell = false;
consumption.showSurfaceImage = showHostedSurfaceImage;
consumption.drawRuntimeDebugRects = showHostedSurfaceImage;
consumption.surfaceState = showHostedSurfaceImage
? NativeHostedPreviewSurfaceState::Live
: (hasHostedSurfaceDescriptor
? NativeHostedPreviewSurfaceState::Warming
: NativeHostedPreviewSurfaceState::AwaitingSubmit);
if (!showHostedSurfaceImage) {
consumption.placeholderTitle = pendingTitle;
consumption.placeholderSubtitle = pendingSubtitle;
}
return consumption;
}
static std::string ComposeNativeHostedPreviewStatusLine(
const NativeHostedPreviewConsumption& consumption,
std::string_view status) {
switch (consumption.surfaceState) {
case NativeHostedPreviewSurfaceState::AwaitingSubmit:
return std::string("Native surface awaiting submit | ") + std::string(status);
case NativeHostedPreviewSurfaceState::Warming:
return std::string("Native surface warming | ") + std::string(status);
case NativeHostedPreviewSurfaceState::Live:
return std::string("Native surface live | ") + std::string(status);
case NativeHostedPreviewSurfaceState::Disabled:
default:
return std::string(status);
}
}
int Run(HINSTANCE instance, int nCmdShow); int Run(HINSTANCE instance, int nCmdShow);
private: private:
@@ -291,7 +372,7 @@ private:
return !debugName.empty() && return !debugName.empty() &&
colorTexture != nullptr && colorTexture != nullptr &&
colorView != nullptr && colorView != nullptr &&
textureRegistration.IsValid() && Application::HasHostedPreviewPublishedTexture(textureRegistration) &&
width > 0u && width > 0u &&
height > 0u; height > 0u;
} }

View File

@@ -13,6 +13,26 @@ namespace XCUIBackend {
namespace { namespace {
::XCEngine::RHI::ResourceViewDimension ResolveShaderResourceDimension(
::XCEngine::RHI::TextureType textureType) {
switch (textureType) {
case ::XCEngine::RHI::TextureType::Texture1D:
return ::XCEngine::RHI::ResourceViewDimension::Texture1D;
case ::XCEngine::RHI::TextureType::Texture2D:
return ::XCEngine::RHI::ResourceViewDimension::Texture2D;
case ::XCEngine::RHI::TextureType::Texture2DArray:
return ::XCEngine::RHI::ResourceViewDimension::Texture2DArray;
case ::XCEngine::RHI::TextureType::Texture3D:
return ::XCEngine::RHI::ResourceViewDimension::Texture3D;
case ::XCEngine::RHI::TextureType::TextureCube:
return ::XCEngine::RHI::ResourceViewDimension::TextureCube;
case ::XCEngine::RHI::TextureType::TextureCubeArray:
return ::XCEngine::RHI::ResourceViewDimension::TextureCubeArray;
default:
return ::XCEngine::RHI::ResourceViewDimension::Texture2D;
}
}
bool PrepareSwapChainRender( bool PrepareSwapChainRender(
::XCEngine::Editor::Platform::D3D12WindowRenderer& windowRenderer, ::XCEngine::Editor::Platform::D3D12WindowRenderer& windowRenderer,
const float clearColor[4], const float clearColor[4],
@@ -172,14 +192,56 @@ bool NativeWindowUICompositor::CreateTextureDescriptor(
::XCEngine::RHI::RHIDevice* device, ::XCEngine::RHI::RHIDevice* device,
::XCEngine::RHI::RHITexture* texture, ::XCEngine::RHI::RHITexture* texture,
UITextureRegistration& outRegistration) { UITextureRegistration& outRegistration) {
(void)device; outRegistration = {};
(void)texture; if (device == nullptr || texture == nullptr) {
return false;
}
::XCEngine::RHI::ResourceViewDesc viewDesc = {};
viewDesc.format = static_cast<std::uint32_t>(texture->GetFormat());
viewDesc.dimension = ResolveShaderResourceDimension(texture->GetTextureType());
viewDesc.mipLevel = 0u;
::XCEngine::RHI::RHIResourceView* shaderResourceView = device->CreateShaderResourceView(texture, viewDesc);
if (shaderResourceView == nullptr) {
return false;
}
if (!shaderResourceView->IsValid() ||
shaderResourceView->GetViewType() != ::XCEngine::RHI::ResourceViewType::ShaderResource) {
shaderResourceView->Shutdown();
delete shaderResourceView;
return false;
}
outRegistration.cpuHandle.ptr =
reinterpret_cast<std::uintptr_t>(shaderResourceView->GetNativeHandle());
outRegistration.texture.nativeHandle =
reinterpret_cast<std::uintptr_t>(shaderResourceView);
outRegistration.texture.width = texture->GetWidth();
outRegistration.texture.height = texture->GetHeight();
outRegistration.texture.kind = ::XCEngine::UI::UITextureHandleKind::ShaderResourceView;
if (!outRegistration.IsValid()) {
shaderResourceView->Shutdown();
delete shaderResourceView;
outRegistration = {}; outRegistration = {};
return false; return false;
} }
return true;
}
void NativeWindowUICompositor::FreeTextureDescriptor(const UITextureRegistration& registration) { void NativeWindowUICompositor::FreeTextureDescriptor(const UITextureRegistration& registration) {
(void)registration; if (registration.texture.kind != ::XCEngine::UI::UITextureHandleKind::ShaderResourceView ||
registration.texture.nativeHandle == 0u) {
return;
}
auto* shaderResourceView =
reinterpret_cast<::XCEngine::RHI::RHIResourceView*>(registration.texture.nativeHandle);
shaderResourceView->Shutdown();
delete shaderResourceView;
} }
void NativeWindowUICompositor::SubmitRenderPacket(const XCUINativeWindowRenderPacket& packet) { void NativeWindowUICompositor::SubmitRenderPacket(const XCUINativeWindowRenderPacket& packet) {

View File

@@ -14,7 +14,15 @@ struct UITextureRegistration {
::XCEngine::UI::UITextureHandle texture = {}; ::XCEngine::UI::UITextureHandle texture = {};
bool IsValid() const { bool IsValid() const {
return cpuHandle.ptr != 0u && gpuHandle.ptr != 0u && texture.IsValid(); if (!texture.IsValid()) {
return false;
}
if (texture.kind == ::XCEngine::UI::UITextureHandleKind::ShaderResourceView) {
return cpuHandle.ptr != 0u;
}
return cpuHandle.ptr != 0u && gpuHandle.ptr != 0u;
} }
}; };

View File

@@ -7,9 +7,20 @@
namespace { namespace {
using XCEngine::Editor::XCUIBackend::XCUIEditorCommandRouter; using XCEngine::Editor::XCUIBackend::XCUIEditorCommandRouter;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewQueue;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceRegistry;
using XCEngine::Editor::XCUIBackend::UITextureRegistration;
using XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta; using XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta;
using XCEngine::Input::KeyCode; using XCEngine::Input::KeyCode;
using XCEngine::NewEditor::Application; using XCEngine::NewEditor::Application;
using XCEngine::UI::UIColor;
using XCEngine::UI::UIDrawData;
using XCEngine::UI::UIDrawList;
using XCEngine::UI::UIRect;
using XCEngine::UI::UISize;
using XCEngine::UI::UITextureHandle;
using XCEngine::UI::UITextureHandleKind;
constexpr std::size_t ToPanelIndex(Application::ShellPanelId panelId) { constexpr std::size_t ToPanelIndex(Application::ShellPanelId panelId) {
return static_cast<std::size_t>(panelId); return static_cast<std::size_t>(panelId);
@@ -265,4 +276,47 @@ TEST(ApplicationShellCommandBindingsTest, PreviewShortcutInvokesCommandHandlerAn
EXPECT_EQ(harness.hostedPreviewReconfigureCount, 1); EXPECT_EQ(harness.hostedPreviewReconfigureCount, 1);
} }
TEST(ApplicationShellCommandBindingsTest, BeginHostedPreviewFrameLifecycleClearsQueueAndResetsRegistryFlags) {
XCUIHostedPreviewQueue queue = {};
XCUIHostedPreviewSurfaceRegistry registry = {};
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("HostedPreview");
drawList.AddFilledRect(UIRect(4.0f, 6.0f, 24.0f, 16.0f), UIColor(1.0f, 1.0f, 1.0f, 1.0f));
XCUIHostedPreviewFrame frame = {};
frame.drawData = &drawData;
frame.canvasRect = UIRect(0.0f, 0.0f, 160.0f, 90.0f);
frame.logicalSize = UISize(160.0f, 90.0f);
frame.debugName = "HostedPreview";
frame.debugSource = "tests.application";
ASSERT_TRUE(queue.Submit(frame));
ASSERT_EQ(queue.GetQueuedFrames().size(), 1u);
registry.RecordQueuedFrame(queue.GetQueuedFrames().front(), 0u);
ASSERT_EQ(registry.GetDescriptors().size(), 1u);
EXPECT_TRUE(registry.GetDescriptors().front().queuedThisFrame);
Application::BeginHostedPreviewFrameLifecycle(queue, registry);
EXPECT_TRUE(queue.GetQueuedFrames().empty());
ASSERT_EQ(registry.GetDescriptors().size(), 1u);
EXPECT_FALSE(registry.GetDescriptors().front().queuedThisFrame);
}
TEST(ApplicationShellCommandBindingsTest, HostedPreviewRegistrationGuardsAllowTextureOnlyNativePublication) {
UITextureRegistration registration = {};
registration.texture = UITextureHandle{ 99u, 256u, 128u, UITextureHandleKind::ShaderResourceView };
EXPECT_TRUE(Application::HasHostedPreviewTextureRegistration(registration));
EXPECT_TRUE(Application::HasHostedPreviewPublishedTexture(registration));
registration = {};
registration.cpuHandle.ptr = 11u;
registration.gpuHandle.ptr = 29u;
EXPECT_TRUE(Application::HasHostedPreviewTextureRegistration(registration));
EXPECT_FALSE(Application::HasHostedPreviewPublishedTexture(registration));
}
} // namespace } // namespace

View File

@@ -4,11 +4,16 @@
#include "XCUIBackend/NativeWindowUICompositor.h" #include "XCUIBackend/NativeWindowUICompositor.h"
#include "XCUIBackend/UITextureRegistration.h" #include "XCUIBackend/UITextureRegistration.h"
#include <XCEngine/RHI/RHICapabilities.h>
#include <XCEngine/RHI/RHIDevice.h>
#include <XCEngine/RHI/RHIResourceView.h>
#include <XCEngine/RHI/RHITexture.h>
#include <XCEngine/UI/DrawData.h> #include <XCEngine/UI/DrawData.h>
#include <array> #include <array>
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <string>
namespace { namespace {
@@ -20,6 +25,16 @@ using XCEngine::Editor::XCUIBackend::NativeWindowUICompositor;
using XCEngine::Editor::XCUIBackend::UITextureRegistration; using XCEngine::Editor::XCUIBackend::UITextureRegistration;
using XCEngine::Editor::XCUIBackend::XCUINativeWindowPresentStats; using XCEngine::Editor::XCUIBackend::XCUINativeWindowPresentStats;
using XCEngine::Editor::XCUIBackend::XCUINativeWindowRenderPacket; using XCEngine::Editor::XCUIBackend::XCUINativeWindowRenderPacket;
using XCEngine::RHI::Format;
using XCEngine::RHI::ResourceStates;
using XCEngine::RHI::ResourceViewDesc;
using XCEngine::RHI::ResourceViewDimension;
using XCEngine::RHI::ResourceViewType;
using XCEngine::RHI::RHIDevice;
using XCEngine::RHI::RHIResourceView;
using XCEngine::RHI::RHITexture;
using XCEngine::RHI::TextureType;
using XCEngine::UI::UITextureHandleKind;
HWND MakeFakeHwnd() { HWND MakeFakeHwnd() {
return reinterpret_cast<HWND>(static_cast<std::uintptr_t>(0x2345u)); return reinterpret_cast<HWND>(static_cast<std::uintptr_t>(0x2345u));
@@ -76,6 +91,162 @@ XCEngine::UI::UIDrawData MakeDrawData() {
return drawData; return drawData;
} }
struct TrackingShaderViewState {
int shutdownCount = 0;
int destructorCount = 0;
};
class TrackingShaderResourceView final : public XCEngine::RHI::RHIShaderResourceView {
public:
TrackingShaderResourceView(
TrackingShaderViewState& state,
bool valid,
ResourceViewDimension dimension = ResourceViewDimension::Texture2D,
Format format = Format::R8G8B8A8_UNorm)
: m_state(state)
, m_valid(valid)
, m_dimension(dimension)
, m_format(format) {
}
~TrackingShaderResourceView() override {
++m_state.destructorCount;
}
void Shutdown() override {
++m_state.shutdownCount;
m_valid = false;
}
void* GetNativeHandle() override {
return this;
}
bool IsValid() const override {
return m_valid;
}
ResourceViewType GetViewType() const override {
return ResourceViewType::ShaderResource;
}
ResourceViewDimension GetDimension() const override {
return m_dimension;
}
Format GetFormat() const override {
return m_format;
}
private:
TrackingShaderViewState& m_state;
bool m_valid = true;
ResourceViewDimension m_dimension = ResourceViewDimension::Texture2D;
Format m_format = Format::R8G8B8A8_UNorm;
};
class FakeTexture final : public RHITexture {
public:
FakeTexture(
std::uint32_t width,
std::uint32_t height,
Format format = Format::R8G8B8A8_UNorm,
TextureType textureType = TextureType::Texture2D)
: m_width(width)
, m_height(height)
, m_format(format)
, m_textureType(textureType) {
}
std::uint32_t GetWidth() const override { return m_width; }
std::uint32_t GetHeight() const override { return m_height; }
std::uint32_t GetDepth() const override { return 1u; }
std::uint32_t GetMipLevels() const override { return 1u; }
Format GetFormat() const override { return m_format; }
TextureType GetTextureType() const override { return m_textureType; }
ResourceStates GetState() const override { return m_state; }
void SetState(ResourceStates state) override { m_state = state; }
void* GetNativeHandle() override { return this; }
const std::string& GetName() const override { return m_name; }
void SetName(const std::string& name) override { m_name = name; }
void Shutdown() override { m_shutdownCalled = true; }
bool shutdownCalled() const { return m_shutdownCalled; }
private:
std::uint32_t m_width = 0u;
std::uint32_t m_height = 0u;
Format m_format = Format::Unknown;
TextureType m_textureType = TextureType::Texture2D;
ResourceStates m_state = ResourceStates::Common;
std::string m_name = {};
bool m_shutdownCalled = false;
};
class RecordingDevice final : public RHIDevice {
public:
TrackingShaderViewState* nextShaderViewState = nullptr;
bool createShaderViewValid = true;
int createShaderViewCount = 0;
RHITexture* lastShaderViewTexture = nullptr;
ResourceViewDesc lastShaderViewDesc = {};
bool Initialize(const XCEngine::RHI::RHIDeviceDesc&) override { return true; }
void Shutdown() override {}
XCEngine::RHI::RHIBuffer* CreateBuffer(const XCEngine::RHI::BufferDesc&) override { return nullptr; }
XCEngine::RHI::RHITexture* CreateTexture(const XCEngine::RHI::TextureDesc&) override { return nullptr; }
XCEngine::RHI::RHITexture* CreateTexture(const XCEngine::RHI::TextureDesc&, const void*, size_t, std::uint32_t) override { return nullptr; }
XCEngine::RHI::RHISwapChain* CreateSwapChain(const XCEngine::RHI::SwapChainDesc&, XCEngine::RHI::RHICommandQueue*) override { return nullptr; }
XCEngine::RHI::RHICommandList* CreateCommandList(const XCEngine::RHI::CommandListDesc&) override { return nullptr; }
XCEngine::RHI::RHICommandQueue* CreateCommandQueue(const XCEngine::RHI::CommandQueueDesc&) override { return nullptr; }
XCEngine::RHI::RHIShader* CreateShader(const XCEngine::RHI::ShaderCompileDesc&) override { return nullptr; }
XCEngine::RHI::RHIPipelineState* CreatePipelineState(const XCEngine::RHI::GraphicsPipelineDesc&) override { return nullptr; }
XCEngine::RHI::RHIPipelineLayout* CreatePipelineLayout(const XCEngine::RHI::RHIPipelineLayoutDesc&) override { return nullptr; }
XCEngine::RHI::RHIFence* CreateFence(const XCEngine::RHI::FenceDesc&) override { return nullptr; }
XCEngine::RHI::RHISampler* CreateSampler(const XCEngine::RHI::SamplerDesc&) override { return nullptr; }
XCEngine::RHI::RHIRenderPass* CreateRenderPass(
std::uint32_t,
const XCEngine::RHI::AttachmentDesc*,
const XCEngine::RHI::AttachmentDesc*) override { return nullptr; }
XCEngine::RHI::RHIFramebuffer* CreateFramebuffer(
XCEngine::RHI::RHIRenderPass*,
std::uint32_t,
std::uint32_t,
std::uint32_t,
RHIResourceView**,
RHIResourceView*) override { return nullptr; }
XCEngine::RHI::RHIDescriptorPool* CreateDescriptorPool(const XCEngine::RHI::DescriptorPoolDesc&) override { return nullptr; }
XCEngine::RHI::RHIDescriptorSet* CreateDescriptorSet(
XCEngine::RHI::RHIDescriptorPool*,
const XCEngine::RHI::DescriptorSetLayoutDesc&) override { return nullptr; }
RHIResourceView* CreateVertexBufferView(XCEngine::RHI::RHIBuffer*, const ResourceViewDesc&) override { return nullptr; }
RHIResourceView* CreateIndexBufferView(XCEngine::RHI::RHIBuffer*, const ResourceViewDesc&) override { return nullptr; }
RHIResourceView* CreateRenderTargetView(XCEngine::RHI::RHITexture*, const ResourceViewDesc&) override { return nullptr; }
RHIResourceView* CreateDepthStencilView(XCEngine::RHI::RHITexture*, const ResourceViewDesc&) override { return nullptr; }
RHIResourceView* CreateShaderResourceView(XCEngine::RHI::RHITexture* texture, const ResourceViewDesc& desc) override {
++createShaderViewCount;
lastShaderViewTexture = texture;
lastShaderViewDesc = desc;
if (nextShaderViewState == nullptr) {
return nullptr;
}
return new TrackingShaderResourceView(
*nextShaderViewState,
createShaderViewValid,
desc.dimension != ResourceViewDimension::Unknown ? desc.dimension : ResourceViewDimension::Texture2D,
desc.format != 0u ? static_cast<Format>(desc.format) : Format::R8G8B8A8_UNorm);
}
RHIResourceView* CreateUnorderedAccessView(XCEngine::RHI::RHITexture*, const ResourceViewDesc&) override { return nullptr; }
const XCEngine::RHI::RHICapabilities& GetCapabilities() const override { return m_capabilities; }
const XCEngine::RHI::RHIDeviceInfo& GetDeviceInfo() const override { return m_deviceInfo; }
void* GetNativeDevice() override { return this; }
private:
XCEngine::RHI::RHICapabilities m_capabilities = {};
XCEngine::RHI::RHIDeviceInfo m_deviceInfo = {};
};
TEST(NativeWindowUICompositorTest, RenderPacketReportsDrawDataPresenceAndClearResetsPayload) { TEST(NativeWindowUICompositorTest, RenderPacketReportsDrawDataPresenceAndClearResetsPayload) {
XCUINativeWindowRenderPacket packet = {}; XCUINativeWindowRenderPacket packet = {};
EXPECT_FALSE(packet.HasDrawData()); EXPECT_FALSE(packet.HasDrawData());
@@ -209,4 +380,66 @@ TEST(NativeWindowUICompositorTest, InterfaceFactoryReturnsSafeNativeCompositorDe
compositor->Shutdown(); compositor->Shutdown();
} }
TEST(NativeWindowUICompositorTest, ShaderResourceViewRegistrationsStayValidWithoutGpuDescriptorHandle) {
UITextureRegistration registration = {};
registration.cpuHandle.ptr = 17u;
registration.texture.nativeHandle = 33u;
registration.texture.width = 64u;
registration.texture.height = 32u;
registration.texture.kind = UITextureHandleKind::ShaderResourceView;
EXPECT_TRUE(registration.IsValid());
registration.texture.kind = UITextureHandleKind::ImGuiDescriptor;
EXPECT_FALSE(registration.IsValid());
registration.gpuHandle.ptr = 19u;
EXPECT_TRUE(registration.IsValid());
}
TEST(NativeWindowUICompositorTest, CreateTextureDescriptorPublishesShaderResourceViewAndFreeReleasesIt) {
NativeWindowUICompositor compositor = {};
RecordingDevice device = {};
TrackingShaderViewState viewState = {};
device.nextShaderViewState = &viewState;
FakeTexture texture(256u, 128u, Format::R8G8B8A8_UNorm, TextureType::Texture2DArray);
UITextureRegistration registration = {};
ASSERT_TRUE(compositor.CreateTextureDescriptor(&device, &texture, registration));
EXPECT_EQ(device.createShaderViewCount, 1);
EXPECT_EQ(device.lastShaderViewTexture, &texture);
EXPECT_EQ(device.lastShaderViewDesc.format, static_cast<std::uint32_t>(Format::R8G8B8A8_UNorm));
EXPECT_EQ(device.lastShaderViewDesc.dimension, ResourceViewDimension::Texture2DArray);
EXPECT_TRUE(registration.IsValid());
EXPECT_NE(registration.cpuHandle.ptr, 0u);
EXPECT_EQ(registration.gpuHandle.ptr, 0u);
EXPECT_EQ(registration.texture.width, 256u);
EXPECT_EQ(registration.texture.height, 128u);
EXPECT_EQ(registration.texture.kind, UITextureHandleKind::ShaderResourceView);
EXPECT_EQ(registration.cpuHandle.ptr, registration.texture.nativeHandle);
compositor.FreeTextureDescriptor(registration);
EXPECT_EQ(viewState.shutdownCount, 1);
EXPECT_EQ(viewState.destructorCount, 1);
}
TEST(NativeWindowUICompositorTest, CreateTextureDescriptorRejectsInvalidShaderResourceViewAndCleansItUp) {
NativeWindowUICompositor compositor = {};
RecordingDevice device = {};
TrackingShaderViewState viewState = {};
device.nextShaderViewState = &viewState;
device.createShaderViewValid = false;
FakeTexture texture(96u, 64u);
UITextureRegistration registration = {};
EXPECT_FALSE(compositor.CreateTextureDescriptor(&device, &texture, registration));
EXPECT_EQ(device.createShaderViewCount, 1);
EXPECT_FALSE(registration.IsValid());
EXPECT_EQ(registration.texture.nativeHandle, 0u);
EXPECT_EQ(viewState.shutdownCount, 1);
EXPECT_EQ(viewState.destructorCount, 1);
}
} // namespace } // namespace