Publish native hosted preview textures through XCUI compositor
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user