2026-04-05 04:55:25 +08:00
|
|
|
#include "Application.h"
|
2026-04-05 16:38:00 +08:00
|
|
|
#include "XCUIBackend/XCUINativeShellLayout.h"
|
2026-04-05 14:05:46 +08:00
|
|
|
#include "XCUIBackend/NativeWindowUICompositor.h"
|
2026-04-05 17:41:31 +08:00
|
|
|
#include "XCUIBackend/XCUIPanelCanvasHost.h"
|
|
|
|
|
#include "panels/XCUIDemoPanel.h"
|
|
|
|
|
#include "panels/XCUILayoutLabPanel.h"
|
2026-04-05 06:15:24 +08:00
|
|
|
|
2026-04-05 04:55:25 +08:00
|
|
|
#include <XCEngine/Core/Asset/ResourceManager.h>
|
2026-04-05 14:05:46 +08:00
|
|
|
#include <XCEngine/UI/DrawData.h>
|
2026-04-05 16:38:00 +08:00
|
|
|
#include <XCEngine/UI/Widgets/UIEditorPanelChrome.h>
|
2026-04-05 04:55:25 +08:00
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <cmath>
|
2026-04-05 14:05:46 +08:00
|
|
|
#include <sstream>
|
2026-04-05 04:55:25 +08:00
|
|
|
|
2026-04-05 16:11:08 +08:00
|
|
|
#ifdef DrawText
|
|
|
|
|
#undef DrawText
|
|
|
|
|
#endif
|
|
|
|
|
|
2026-04-05 04:55:25 +08:00
|
|
|
namespace XCEngine {
|
|
|
|
|
namespace NewEditor {
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
constexpr wchar_t kWindowClassName[] = L"XCNewEditorWindowClass";
|
|
|
|
|
constexpr wchar_t kWindowTitle[] = L"XCNewEditor";
|
|
|
|
|
constexpr float kClearColor[4] = { 0.08f, 0.09f, 0.11f, 1.0f };
|
|
|
|
|
constexpr float kHostedPreviewClearColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
|
|
|
|
|
|
|
|
|
template <typename ResourceType>
|
|
|
|
|
void ShutdownAndDelete(ResourceType*& resource) {
|
|
|
|
|
if (resource == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resource->Shutdown();
|
|
|
|
|
delete resource;
|
|
|
|
|
resource = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 14:05:46 +08:00
|
|
|
std::uint64_t MakeFrameTimestampNanoseconds() {
|
|
|
|
|
return static_cast<std::uint64_t>(
|
|
|
|
|
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
|
|
|
|
std::chrono::steady_clock::now().time_since_epoch())
|
|
|
|
|
.count());
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 17:41:31 +08:00
|
|
|
class ForwardingNativePanelCanvasHost final : public ::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost {
|
|
|
|
|
public:
|
|
|
|
|
explicit ForwardingNativePanelCanvasHost(::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost& host)
|
|
|
|
|
: m_host(host) {}
|
|
|
|
|
|
|
|
|
|
const char* GetDebugName() const override {
|
|
|
|
|
return m_host.GetDebugName();
|
2026-04-05 14:05:46 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-05 17:41:31 +08:00
|
|
|
auto BeginCanvas(
|
|
|
|
|
const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest& request)
|
|
|
|
|
-> decltype(std::declval<::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost&>().BeginCanvas(request))
|
|
|
|
|
override {
|
|
|
|
|
return m_host.BeginCanvas(request);
|
|
|
|
|
}
|
2026-04-05 14:05:46 +08:00
|
|
|
|
2026-04-05 17:41:31 +08:00
|
|
|
void DrawFilledRect(
|
|
|
|
|
const ::XCEngine::UI::UIRect& rect,
|
|
|
|
|
const ::XCEngine::UI::UIColor& color,
|
|
|
|
|
float rounding) override {
|
|
|
|
|
m_host.DrawFilledRect(rect, color, rounding);
|
2026-04-05 14:05:46 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-05 17:41:31 +08:00
|
|
|
void DrawOutlineRect(
|
|
|
|
|
const ::XCEngine::UI::UIRect& rect,
|
|
|
|
|
const ::XCEngine::UI::UIColor& color,
|
|
|
|
|
float thickness,
|
|
|
|
|
float rounding) override {
|
|
|
|
|
m_host.DrawOutlineRect(rect, color, thickness, rounding);
|
|
|
|
|
}
|
2026-04-05 14:05:46 +08:00
|
|
|
|
2026-04-05 17:41:31 +08:00
|
|
|
void DrawText(
|
|
|
|
|
const ::XCEngine::UI::UIPoint& position,
|
|
|
|
|
std::string_view text,
|
|
|
|
|
const ::XCEngine::UI::UIColor& color,
|
|
|
|
|
float fontSize) override {
|
|
|
|
|
m_host.DrawText(position, text, color, fontSize);
|
2026-04-05 14:05:46 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-05 17:41:31 +08:00
|
|
|
void EndCanvas() override {
|
|
|
|
|
m_host.EndCanvas();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TryGetLatestFrameSnapshot(
|
|
|
|
|
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasFrameSnapshot& outSnapshot) const override {
|
|
|
|
|
return m_host.TryGetLatestFrameSnapshot(outSnapshot);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost& m_host;
|
|
|
|
|
};
|
2026-04-05 14:05:46 +08:00
|
|
|
|
2026-04-05 17:41:31 +08:00
|
|
|
void AppendDrawData(::XCEngine::UI::UIDrawData& destination, const ::XCEngine::UI::UIDrawData& source) {
|
|
|
|
|
for (const ::XCEngine::UI::UIDrawList& drawList : source.GetDrawLists()) {
|
|
|
|
|
destination.AddDrawList(drawList);
|
|
|
|
|
}
|
2026-04-05 14:05:46 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-05 04:55:25 +08:00
|
|
|
} // namespace
|
|
|
|
|
|
2026-04-05 14:05:46 +08:00
|
|
|
bool Application::IsNativeWindowHostEnabled() const {
|
|
|
|
|
return m_windowHostMode == WindowHostMode::NativeXCUI;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Application::InitializeNativeShell() {
|
|
|
|
|
m_nativeActivePanel = m_shellChromeState.IsPanelVisible(ShellPanelId::XCUIDemo)
|
|
|
|
|
? ShellPanelId::XCUIDemo
|
|
|
|
|
: ShellPanelId::XCUILayoutLab;
|
|
|
|
|
m_nativeDemoCanvasHost.ClearCanvasSession();
|
|
|
|
|
m_nativeLayoutCanvasHost.ClearCanvasSession();
|
2026-04-05 17:41:31 +08:00
|
|
|
|
|
|
|
|
const auto buildNativePreviewPresenter =
|
|
|
|
|
[this](ShellPanelId panelId, bool hostedPreviewEnabled) {
|
|
|
|
|
if (!hostedPreviewEnabled || !IsNativeHostedPreviewEnabled(panelId)) {
|
|
|
|
|
return std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return CreateHostedPreviewPresenter(true);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const ShellPanelChromeState* demoState = TryGetShellPanelState(ShellPanelId::XCUIDemo);
|
|
|
|
|
const bool demoHostedPreviewEnabled = demoState == nullptr || demoState->hostedPreviewEnabled;
|
|
|
|
|
m_demoPanel = std::make_unique<XCUIDemoPanel>(
|
|
|
|
|
&m_xcuiInputSource,
|
|
|
|
|
buildNativePreviewPresenter(ShellPanelId::XCUIDemo, demoHostedPreviewEnabled),
|
|
|
|
|
std::make_unique<ForwardingNativePanelCanvasHost>(m_nativeDemoCanvasHost));
|
|
|
|
|
m_demoPanel->SetVisible(demoState != nullptr && demoState->visible);
|
|
|
|
|
m_demoPanel->SetHostedPreviewEnabled(demoHostedPreviewEnabled);
|
|
|
|
|
|
|
|
|
|
const ShellPanelChromeState* layoutState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab);
|
|
|
|
|
const bool layoutHostedPreviewEnabled = layoutState == nullptr || layoutState->hostedPreviewEnabled;
|
|
|
|
|
m_layoutLabPanel = std::make_unique<XCUILayoutLabPanel>(
|
|
|
|
|
&m_xcuiInputSource,
|
|
|
|
|
buildNativePreviewPresenter(ShellPanelId::XCUILayoutLab, layoutHostedPreviewEnabled),
|
|
|
|
|
std::make_unique<ForwardingNativePanelCanvasHost>(m_nativeLayoutCanvasHost));
|
|
|
|
|
m_layoutLabPanel->SetVisible(layoutState != nullptr && layoutState->visible);
|
|
|
|
|
m_layoutLabPanel->SetHostedPreviewEnabled(layoutHostedPreviewEnabled);
|
2026-04-05 14:05:46 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-05 12:50:55 +08:00
|
|
|
const Application::ShellPanelChromeState* Application::TryGetShellPanelState(ShellPanelId panelId) const {
|
2026-04-05 13:24:14 +08:00
|
|
|
return m_shellChromeState.TryGetPanelState(panelId);
|
2026-04-05 12:50:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Application::IsShellViewToggleEnabled(ShellViewToggleId toggleId) const {
|
2026-04-05 13:24:14 +08:00
|
|
|
return m_shellChromeState.GetViewToggle(toggleId);
|
2026-04-05 12:50:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Application::SetShellViewToggleEnabled(ShellViewToggleId toggleId, bool enabled) {
|
2026-04-05 13:24:14 +08:00
|
|
|
m_shellChromeState.SetViewToggle(toggleId, enabled);
|
2026-04-05 12:50:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Application::IsNativeHostedPreviewEnabled(ShellPanelId panelId) const {
|
2026-04-05 13:24:14 +08:00
|
|
|
return m_shellChromeState.IsNativeHostedPreviewActive(panelId);
|
2026-04-05 12:50:55 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-05 04:55:25 +08:00
|
|
|
Application::HostedPreviewPanelDiagnostics Application::BuildHostedPreviewPanelDiagnostics(
|
|
|
|
|
const char* debugName,
|
|
|
|
|
const char* fallbackDebugSource,
|
|
|
|
|
bool visible,
|
|
|
|
|
bool hostedPreviewEnabled,
|
|
|
|
|
bool nativeRequested,
|
|
|
|
|
bool nativePresenterBound,
|
|
|
|
|
const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& previewStats) const {
|
|
|
|
|
HostedPreviewPanelDiagnostics diagnostics = {};
|
|
|
|
|
if (debugName != nullptr) {
|
|
|
|
|
diagnostics.debugName = debugName;
|
|
|
|
|
}
|
|
|
|
|
if (fallbackDebugSource != nullptr) {
|
|
|
|
|
diagnostics.debugSource = fallbackDebugSource;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
diagnostics.visible = visible;
|
|
|
|
|
diagnostics.hostedPreviewEnabled = hostedPreviewEnabled;
|
|
|
|
|
diagnostics.nativeRequested = nativeRequested;
|
|
|
|
|
diagnostics.nativePresenterBound = nativePresenterBound;
|
|
|
|
|
diagnostics.presentedThisFrame = previewStats.presented;
|
|
|
|
|
diagnostics.queuedToNativePassThisFrame = previewStats.queuedToNativePass;
|
|
|
|
|
diagnostics.submittedDrawListCount = previewStats.submittedDrawListCount;
|
|
|
|
|
diagnostics.submittedCommandCount = previewStats.submittedCommandCount;
|
|
|
|
|
diagnostics.flushedDrawListCount = previewStats.flushedDrawListCount;
|
|
|
|
|
diagnostics.flushedCommandCount = previewStats.flushedCommandCount;
|
|
|
|
|
|
|
|
|
|
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor descriptor = {};
|
|
|
|
|
if (!diagnostics.debugName.empty() &&
|
|
|
|
|
m_hostedPreviewSurfaceRegistry.TryGetSurfaceDescriptor(diagnostics.debugName.c_str(), descriptor)) {
|
|
|
|
|
diagnostics.descriptorAvailable = true;
|
|
|
|
|
diagnostics.surfaceImageAvailable = descriptor.image.IsValid();
|
|
|
|
|
diagnostics.surfaceWidth = descriptor.image.surfaceWidth;
|
|
|
|
|
diagnostics.surfaceHeight = descriptor.image.surfaceHeight;
|
|
|
|
|
diagnostics.logicalWidth = descriptor.logicalSize.width;
|
|
|
|
|
diagnostics.logicalHeight = descriptor.logicalSize.height;
|
|
|
|
|
diagnostics.queuedFrameIndex = descriptor.queuedFrameIndex;
|
|
|
|
|
if (!descriptor.debugSource.empty()) {
|
|
|
|
|
diagnostics.debugSource = descriptor.debugSource;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!diagnostics.debugName.empty()) {
|
|
|
|
|
const HostedPreviewOffscreenSurface* previewSurface = FindHostedPreviewSurface(diagnostics.debugName);
|
|
|
|
|
if (previewSurface != nullptr) {
|
|
|
|
|
diagnostics.surfaceAllocated =
|
|
|
|
|
previewSurface->colorTexture != nullptr ||
|
|
|
|
|
previewSurface->colorView != nullptr ||
|
|
|
|
|
previewSurface->width > 0u ||
|
|
|
|
|
previewSurface->height > 0u;
|
|
|
|
|
diagnostics.surfaceReady = previewSurface->IsReady();
|
|
|
|
|
if (diagnostics.surfaceWidth == 0u) {
|
|
|
|
|
diagnostics.surfaceWidth = previewSurface->width;
|
|
|
|
|
}
|
|
|
|
|
if (diagnostics.surfaceHeight == 0u) {
|
|
|
|
|
diagnostics.surfaceHeight = previewSurface->height;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return diagnostics;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Application::Run(HINSTANCE instance, int nCmdShow) {
|
|
|
|
|
if (!CreateMainWindow(instance, nCmdShow)) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_xcuiInputSource.Reset();
|
|
|
|
|
|
|
|
|
|
auto& resourceManager = ::XCEngine::Resources::ResourceManager::Get();
|
|
|
|
|
resourceManager.Initialize();
|
|
|
|
|
resourceManager.SetResourceRoot(XCENGINE_NEW_EDITOR_REPO_ROOT);
|
|
|
|
|
|
|
|
|
|
if (!InitializeRenderer()) {
|
|
|
|
|
resourceManager.Shutdown();
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 06:15:24 +08:00
|
|
|
InitializeWindowCompositor();
|
2026-04-05 15:28:42 +08:00
|
|
|
InitializePanelsForActiveWindowHost();
|
2026-04-05 12:50:55 +08:00
|
|
|
m_shellInputBridge.Reset();
|
|
|
|
|
ConfigureShellCommandRouter();
|
2026-04-05 04:55:25 +08:00
|
|
|
m_running = true;
|
|
|
|
|
m_renderReady = true;
|
|
|
|
|
|
|
|
|
|
MSG message = {};
|
|
|
|
|
while (m_running) {
|
|
|
|
|
while (PeekMessageW(&message, nullptr, 0, 0, PM_REMOVE)) {
|
|
|
|
|
if (message.message == WM_QUIT) {
|
|
|
|
|
m_running = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TranslateMessage(&message);
|
|
|
|
|
DispatchMessageW(&message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!m_running) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Frame();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 17:12:43 +08:00
|
|
|
ResetCompatibilityHostPanels();
|
2026-04-05 06:15:24 +08:00
|
|
|
ShutdownWindowCompositor();
|
2026-04-05 04:55:25 +08:00
|
|
|
ShutdownRenderer();
|
|
|
|
|
resourceManager.Shutdown();
|
|
|
|
|
return static_cast<int>(message.wParam);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LRESULT CALLBACK Application::StaticWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
|
|
|
|
Application* app = reinterpret_cast<Application*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
|
|
|
|
|
if (message == WM_NCCREATE) {
|
|
|
|
|
const CREATESTRUCTW* createStruct = reinterpret_cast<const CREATESTRUCTW*>(lParam);
|
|
|
|
|
app = reinterpret_cast<Application*>(createStruct->lpCreateParams);
|
|
|
|
|
SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(app));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return app != nullptr
|
|
|
|
|
? app->WndProc(hwnd, message, wParam, lParam)
|
|
|
|
|
: DefWindowProcW(hwnd, message, wParam, lParam);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LRESULT Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
|
|
|
|
m_xcuiInputSource.HandleWindowMessage(hwnd, message, wParam, lParam);
|
|
|
|
|
|
2026-04-05 06:15:24 +08:00
|
|
|
if (m_windowCompositor != nullptr &&
|
|
|
|
|
m_windowCompositor->HandleWindowMessage(hwnd, message, wParam, lParam)) {
|
2026-04-05 04:55:25 +08:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (message) {
|
|
|
|
|
case WM_SIZE:
|
|
|
|
|
if (wParam != SIZE_MINIMIZED && m_renderReady) {
|
|
|
|
|
m_windowRenderer.Resize(static_cast<int>(LOWORD(lParam)), static_cast<int>(HIWORD(lParam)));
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
case WM_CLOSE:
|
|
|
|
|
DestroyWindow(hwnd);
|
|
|
|
|
return 0;
|
|
|
|
|
case WM_DESTROY:
|
|
|
|
|
PostQuitMessage(0);
|
|
|
|
|
return 0;
|
|
|
|
|
case WM_PAINT: {
|
|
|
|
|
PAINTSTRUCT paintStruct = {};
|
|
|
|
|
BeginPaint(hwnd, &paintStruct);
|
|
|
|
|
if (m_renderReady) {
|
|
|
|
|
Frame();
|
|
|
|
|
}
|
|
|
|
|
EndPaint(hwnd, &paintStruct);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
return DefWindowProcW(hwnd, message, wParam, lParam);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Application::CreateMainWindow(HINSTANCE instance, int nCmdShow) {
|
|
|
|
|
WNDCLASSEXW windowClass = {};
|
|
|
|
|
windowClass.cbSize = sizeof(windowClass);
|
|
|
|
|
windowClass.style = CS_HREDRAW | CS_VREDRAW;
|
|
|
|
|
windowClass.lpfnWndProc = &Application::StaticWndProc;
|
|
|
|
|
windowClass.hInstance = instance;
|
|
|
|
|
windowClass.hCursor = LoadCursorW(nullptr, IDC_ARROW);
|
|
|
|
|
windowClass.lpszClassName = kWindowClassName;
|
|
|
|
|
if (!RegisterClassExW(&windowClass)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_hwnd = CreateWindowExW(
|
|
|
|
|
0,
|
|
|
|
|
kWindowClassName,
|
|
|
|
|
kWindowTitle,
|
|
|
|
|
WS_OVERLAPPEDWINDOW,
|
|
|
|
|
CW_USEDEFAULT,
|
|
|
|
|
CW_USEDEFAULT,
|
|
|
|
|
1360,
|
|
|
|
|
840,
|
|
|
|
|
nullptr,
|
|
|
|
|
nullptr,
|
|
|
|
|
instance,
|
|
|
|
|
this);
|
|
|
|
|
if (m_hwnd == nullptr) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ShowWindow(m_hwnd, nCmdShow);
|
|
|
|
|
UpdateWindow(m_hwnd);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Application::InitializeRenderer() {
|
|
|
|
|
RECT clientRect = {};
|
|
|
|
|
if (!GetClientRect(m_hwnd, &clientRect)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const int width = clientRect.right - clientRect.left;
|
|
|
|
|
const int height = clientRect.bottom - clientRect.top;
|
|
|
|
|
const bool initialized = width > 0 &&
|
|
|
|
|
height > 0 &&
|
|
|
|
|
m_windowRenderer.Initialize(m_hwnd, width, height);
|
|
|
|
|
if (initialized) {
|
|
|
|
|
m_startTime = std::chrono::steady_clock::now();
|
|
|
|
|
}
|
|
|
|
|
return initialized;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 06:15:24 +08:00
|
|
|
void Application::ShutdownWindowCompositor() {
|
2026-04-05 04:55:25 +08:00
|
|
|
DestroyHostedPreviewSurfaces();
|
2026-04-05 06:15:24 +08:00
|
|
|
if (m_windowCompositor != nullptr) {
|
|
|
|
|
m_windowCompositor->Shutdown();
|
|
|
|
|
m_windowCompositor.reset();
|
|
|
|
|
}
|
2026-04-05 04:55:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Application::ShutdownRenderer() {
|
|
|
|
|
m_renderReady = false;
|
|
|
|
|
m_hostedPreviewRenderBackend.Shutdown();
|
|
|
|
|
m_nativeBackdropRenderer.Shutdown();
|
|
|
|
|
m_windowRenderer.Shutdown();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Application::DestroyHostedPreviewSurfaces() {
|
|
|
|
|
for (HostedPreviewOffscreenSurface& previewSurface : m_hostedPreviewSurfaces) {
|
2026-04-05 14:36:02 +08:00
|
|
|
if (Application::HasHostedPreviewTextureRegistration(previewSurface.textureRegistration)) {
|
2026-04-05 06:15:24 +08:00
|
|
|
if (m_windowCompositor != nullptr) {
|
2026-04-05 06:41:32 +08:00
|
|
|
m_windowCompositor->FreeTextureDescriptor(previewSurface.textureRegistration);
|
2026-04-05 06:15:24 +08:00
|
|
|
}
|
2026-04-05 04:55:25 +08:00
|
|
|
}
|
|
|
|
|
ShutdownAndDelete(previewSurface.colorView);
|
|
|
|
|
ShutdownAndDelete(previewSurface.colorTexture);
|
|
|
|
|
previewSurface = {};
|
|
|
|
|
}
|
|
|
|
|
m_hostedPreviewSurfaces.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Application::SyncHostedPreviewSurfaces() {
|
|
|
|
|
const auto isNativePreviewEnabled = [this](const std::string& debugName) {
|
2026-04-05 12:50:55 +08:00
|
|
|
const ShellPanelChromeState* demoState = TryGetShellPanelState(ShellPanelId::XCUIDemo);
|
|
|
|
|
if (demoState != nullptr &&
|
|
|
|
|
debugName == demoState->previewDebugName &&
|
|
|
|
|
IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ShellPanelChromeState* layoutLabState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab);
|
|
|
|
|
return layoutLabState != nullptr &&
|
|
|
|
|
debugName == layoutLabState->previewDebugName &&
|
|
|
|
|
IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab);
|
2026-04-05 04:55:25 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const auto syncSurfaceForNameAndSize =
|
|
|
|
|
[this, &isNativePreviewEnabled](
|
|
|
|
|
const std::string& debugName,
|
|
|
|
|
const ::XCEngine::UI::UISize& logicalSize) {
|
|
|
|
|
if (!isNativePreviewEnabled(debugName)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::uint32_t width = logicalSize.width > 1.0f
|
|
|
|
|
? static_cast<std::uint32_t>(std::ceil(logicalSize.width))
|
|
|
|
|
: 0u;
|
|
|
|
|
const std::uint32_t height = logicalSize.height > 1.0f
|
|
|
|
|
? static_cast<std::uint32_t>(std::ceil(logicalSize.height))
|
|
|
|
|
: 0u;
|
|
|
|
|
if (width == 0u || height == 0u) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HostedPreviewOffscreenSurface& previewSurface = FindOrAddHostedPreviewSurface(debugName);
|
|
|
|
|
EnsureHostedPreviewSurface(previewSurface, width, height);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (const auto& descriptor : m_hostedPreviewSurfaceRegistry.GetDescriptors()) {
|
|
|
|
|
syncSurfaceForNameAndSize(descriptor.debugName, descriptor.logicalSize);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const auto& queuedFrame : m_hostedPreviewQueue.GetQueuedFrames()) {
|
|
|
|
|
syncSurfaceForNameAndSize(queuedFrame.debugName, queuedFrame.logicalSize);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Application::HostedPreviewOffscreenSurface* Application::FindHostedPreviewSurface(const std::string& debugName) {
|
|
|
|
|
for (HostedPreviewOffscreenSurface& previewSurface : m_hostedPreviewSurfaces) {
|
|
|
|
|
if (previewSurface.debugName == debugName) {
|
|
|
|
|
return &previewSurface;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Application::HostedPreviewOffscreenSurface* Application::FindHostedPreviewSurface(const std::string& debugName) const {
|
|
|
|
|
for (const HostedPreviewOffscreenSurface& previewSurface : m_hostedPreviewSurfaces) {
|
|
|
|
|
if (previewSurface.debugName == debugName) {
|
|
|
|
|
return &previewSurface;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Application::HostedPreviewOffscreenSurface& Application::FindOrAddHostedPreviewSurface(const std::string& debugName) {
|
|
|
|
|
HostedPreviewOffscreenSurface* existingSurface = FindHostedPreviewSurface(debugName);
|
|
|
|
|
if (existingSurface != nullptr) {
|
|
|
|
|
return *existingSurface;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HostedPreviewOffscreenSurface previewSurface = {};
|
|
|
|
|
previewSurface.debugName = debugName;
|
|
|
|
|
m_hostedPreviewSurfaces.push_back(std::move(previewSurface));
|
|
|
|
|
return m_hostedPreviewSurfaces.back();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Application::EnsureHostedPreviewSurface(
|
|
|
|
|
HostedPreviewOffscreenSurface& previewSurface,
|
|
|
|
|
std::uint32_t width,
|
|
|
|
|
std::uint32_t height) {
|
|
|
|
|
if (previewSurface.IsReady() && previewSurface.width == width && previewSurface.height == height) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 14:36:02 +08:00
|
|
|
if (Application::HasHostedPreviewTextureRegistration(previewSurface.textureRegistration)) {
|
2026-04-05 06:15:24 +08:00
|
|
|
if (m_windowCompositor != nullptr) {
|
2026-04-05 06:41:32 +08:00
|
|
|
m_windowCompositor->FreeTextureDescriptor(previewSurface.textureRegistration);
|
2026-04-05 06:15:24 +08:00
|
|
|
}
|
2026-04-05 04:55:25 +08:00
|
|
|
}
|
|
|
|
|
ShutdownAndDelete(previewSurface.colorView);
|
|
|
|
|
ShutdownAndDelete(previewSurface.colorTexture);
|
2026-04-05 06:41:32 +08:00
|
|
|
previewSurface.textureRegistration = {};
|
2026-04-05 04:55:25 +08:00
|
|
|
previewSurface.width = width;
|
|
|
|
|
previewSurface.height = height;
|
|
|
|
|
previewSurface.colorState = ::XCEngine::RHI::ResourceStates::Common;
|
|
|
|
|
|
|
|
|
|
if (width == 0u || height == 0u) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
::XCEngine::RHI::TextureDesc colorDesc = {};
|
|
|
|
|
colorDesc.width = width;
|
|
|
|
|
colorDesc.height = height;
|
|
|
|
|
colorDesc.depth = 1;
|
|
|
|
|
colorDesc.mipLevels = 1;
|
|
|
|
|
colorDesc.arraySize = 1;
|
|
|
|
|
colorDesc.format = static_cast<std::uint32_t>(::XCEngine::RHI::Format::R8G8B8A8_UNorm);
|
|
|
|
|
colorDesc.textureType = static_cast<std::uint32_t>(::XCEngine::RHI::TextureType::Texture2D);
|
|
|
|
|
colorDesc.sampleCount = 1;
|
|
|
|
|
previewSurface.colorTexture = m_windowRenderer.GetRHIDevice()->CreateTexture(colorDesc);
|
|
|
|
|
if (previewSurface.colorTexture == nullptr) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
::XCEngine::RHI::ResourceViewDesc colorViewDesc = {};
|
|
|
|
|
colorViewDesc.format = static_cast<std::uint32_t>(::XCEngine::RHI::Format::R8G8B8A8_UNorm);
|
|
|
|
|
colorViewDesc.dimension = ::XCEngine::RHI::ResourceViewDimension::Texture2D;
|
|
|
|
|
previewSurface.colorView =
|
|
|
|
|
m_windowRenderer.GetRHIDevice()->CreateRenderTargetView(previewSurface.colorTexture, colorViewDesc);
|
|
|
|
|
if (previewSurface.colorView == nullptr) {
|
|
|
|
|
ShutdownAndDelete(previewSurface.colorTexture);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 06:15:24 +08:00
|
|
|
if (m_windowCompositor == nullptr ||
|
|
|
|
|
!m_windowCompositor->CreateTextureDescriptor(
|
2026-04-05 04:55:25 +08:00
|
|
|
m_windowRenderer.GetRHIDevice(),
|
|
|
|
|
previewSurface.colorTexture,
|
2026-04-05 06:41:32 +08:00
|
|
|
previewSurface.textureRegistration)) {
|
2026-04-05 04:55:25 +08:00
|
|
|
ShutdownAndDelete(previewSurface.colorView);
|
|
|
|
|
ShutdownAndDelete(previewSurface.colorTexture);
|
2026-04-05 06:41:32 +08:00
|
|
|
previewSurface.textureRegistration = {};
|
2026-04-05 04:55:25 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Application::RenderHostedPreviewOffscreenSurface(
|
|
|
|
|
HostedPreviewOffscreenSurface& previewSurface,
|
|
|
|
|
const ::XCEngine::Rendering::RenderContext& renderContext,
|
|
|
|
|
const ::XCEngine::UI::UIDrawData& drawData) {
|
|
|
|
|
if (!previewSurface.IsReady() || renderContext.commandList == nullptr) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
::XCEngine::Rendering::RenderSurface renderSurface(previewSurface.width, previewSurface.height);
|
|
|
|
|
renderSurface.SetColorAttachment(previewSurface.colorView);
|
|
|
|
|
renderSurface.SetAutoTransitionEnabled(false);
|
|
|
|
|
renderSurface.SetColorStateBefore(::XCEngine::RHI::ResourceStates::RenderTarget);
|
|
|
|
|
renderSurface.SetColorStateAfter(::XCEngine::RHI::ResourceStates::RenderTarget);
|
|
|
|
|
|
|
|
|
|
renderContext.commandList->TransitionBarrier(
|
|
|
|
|
previewSurface.colorView,
|
|
|
|
|
previewSurface.colorState,
|
|
|
|
|
::XCEngine::RHI::ResourceStates::RenderTarget);
|
|
|
|
|
renderContext.commandList->SetRenderTargets(1, &previewSurface.colorView, nullptr);
|
|
|
|
|
renderContext.commandList->ClearRenderTarget(previewSurface.colorView, kHostedPreviewClearColor);
|
|
|
|
|
|
|
|
|
|
if (!m_hostedPreviewRenderBackend.Render(renderContext, renderSurface, drawData)) {
|
|
|
|
|
previewSurface.colorState = ::XCEngine::RHI::ResourceStates::RenderTarget;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
renderContext.commandList->TransitionBarrier(
|
|
|
|
|
previewSurface.colorView,
|
|
|
|
|
::XCEngine::RHI::ResourceStates::RenderTarget,
|
|
|
|
|
::XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
|
|
|
previewSurface.colorState = ::XCEngine::RHI::ResourceStates::PixelShaderResource;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 14:05:46 +08:00
|
|
|
::XCEngine::UI::UIDrawData Application::BuildNativeShellDrawData(
|
|
|
|
|
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot& shellSnapshot,
|
|
|
|
|
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta& shellFrameDelta) {
|
2026-04-05 16:38:00 +08:00
|
|
|
using NativeShellLayout = ::XCEngine::Editor::XCUIBackend::XCUINativeShellLayoutResult;
|
|
|
|
|
using NativeShellMetrics = ::XCEngine::Editor::XCUIBackend::XCUINativeShellMetrics;
|
|
|
|
|
using NativeShellPanelLayout = ::XCEngine::Editor::XCUIBackend::XCUINativeShellPanelLayout;
|
|
|
|
|
using NativeShellPanelSpec = ::XCEngine::Editor::XCUIBackend::XCUINativeShellPanelSpec;
|
|
|
|
|
using PanelChromeMetrics = ::XCEngine::UI::Widgets::UIEditorPanelChromeMetrics;
|
|
|
|
|
using PanelChromePalette = ::XCEngine::UI::Widgets::UIEditorPanelChromePalette;
|
|
|
|
|
using PanelChromeState = ::XCEngine::UI::Widgets::UIEditorPanelChromeState;
|
|
|
|
|
using PanelChromeText = ::XCEngine::UI::Widgets::UIEditorPanelChromeText;
|
|
|
|
|
|
2026-04-05 14:05:46 +08:00
|
|
|
::XCEngine::UI::UIDrawData composedDrawData = {};
|
|
|
|
|
|
|
|
|
|
RECT clientRect = {};
|
|
|
|
|
if (!GetClientRect(m_hwnd, &clientRect)) {
|
|
|
|
|
return composedDrawData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float windowWidth = static_cast<float>(clientRect.right - clientRect.left);
|
|
|
|
|
const float windowHeight = static_cast<float>(clientRect.bottom - clientRect.top);
|
|
|
|
|
if (windowWidth <= 1.0f || windowHeight <= 1.0f) {
|
|
|
|
|
return composedDrawData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const UI::UIRect shellRect(0.0f, 0.0f, windowWidth, windowHeight);
|
2026-04-05 16:38:00 +08:00
|
|
|
const NativeShellMetrics nativeShellMetrics = {};
|
|
|
|
|
const PanelChromeMetrics panelChromeMetrics = {};
|
|
|
|
|
const PanelChromePalette panelChromePalette = {};
|
2026-04-05 14:05:46 +08:00
|
|
|
const UI::UIColor shellBorderColor(40.0f / 255.0f, 54.0f / 255.0f, 74.0f / 255.0f, 1.0f);
|
|
|
|
|
const UI::UIColor shellSurfaceColor(11.0f / 255.0f, 15.0f / 255.0f, 22.0f / 255.0f, 180.0f / 255.0f);
|
2026-04-05 16:38:00 +08:00
|
|
|
const UI::UIColor& panelBorderColor = panelChromePalette.borderColor;
|
|
|
|
|
const UI::UIColor& panelAccentColor = panelChromePalette.accentColor;
|
|
|
|
|
const UI::UIColor& textPrimary = panelChromePalette.textPrimary;
|
|
|
|
|
const UI::UIColor& textSecondary = panelChromePalette.textSecondary;
|
|
|
|
|
const UI::UIColor& textMuted = panelChromePalette.textMuted;
|
|
|
|
|
|
|
|
|
|
const NativeShellLayout shellLayout = ::XCEngine::Editor::XCUIBackend::BuildXCUINativeShellLayout(
|
|
|
|
|
shellRect,
|
|
|
|
|
{
|
|
|
|
|
NativeShellPanelSpec{
|
|
|
|
|
ShellPanelId::XCUIDemo,
|
|
|
|
|
"XCUI Demo",
|
|
|
|
|
m_shellChromeState.IsPanelVisible(ShellPanelId::XCUIDemo),
|
|
|
|
|
},
|
|
|
|
|
NativeShellPanelSpec{
|
|
|
|
|
ShellPanelId::XCUILayoutLab,
|
|
|
|
|
"XCUI Layout Lab",
|
|
|
|
|
m_shellChromeState.IsPanelVisible(ShellPanelId::XCUILayoutLab),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
m_nativeActivePanel,
|
|
|
|
|
shellSnapshot.pointerPosition,
|
|
|
|
|
shellSnapshot.windowFocused,
|
|
|
|
|
shellFrameDelta.pointer.pressed[0],
|
|
|
|
|
nativeShellMetrics);
|
|
|
|
|
m_nativeActivePanel = shellLayout.activePanel;
|
|
|
|
|
|
|
|
|
|
const UI::UIRect& topBarRect = shellLayout.topBarRect;
|
|
|
|
|
const UI::UIRect& footerRect = shellLayout.footerRect;
|
|
|
|
|
const UI::UIRect& workspaceRect = shellLayout.workspaceRect;
|
|
|
|
|
const std::vector<NativeShellPanelLayout>& panelLayouts = shellLayout.panelLayouts;
|
2026-04-05 14:05:46 +08:00
|
|
|
|
|
|
|
|
::XCEngine::UI::UIDrawList& chromeBackground =
|
|
|
|
|
composedDrawData.EmplaceDrawList("XCUI.NativeShell.Background");
|
|
|
|
|
chromeBackground.AddFilledRect(shellRect, shellSurfaceColor, 0.0f);
|
|
|
|
|
chromeBackground.AddFilledRect(topBarRect, UI::UIColor(12.0f / 255.0f, 18.0f / 255.0f, 26.0f / 255.0f, 230.0f / 255.0f), 14.0f);
|
|
|
|
|
chromeBackground.AddRectOutline(topBarRect, shellBorderColor, 1.0f, 14.0f);
|
|
|
|
|
chromeBackground.AddFilledRect(footerRect, UI::UIColor(12.0f / 255.0f, 18.0f / 255.0f, 26.0f / 255.0f, 214.0f / 255.0f), 12.0f);
|
|
|
|
|
chromeBackground.AddRectOutline(footerRect, shellBorderColor, 1.0f, 12.0f);
|
|
|
|
|
|
|
|
|
|
if (panelLayouts.empty()) {
|
|
|
|
|
const UI::UIRect emptyStateRect(
|
|
|
|
|
workspaceRect.x,
|
|
|
|
|
workspaceRect.y,
|
|
|
|
|
workspaceRect.width,
|
|
|
|
|
(std::max)(0.0f, workspaceRect.height));
|
2026-04-05 16:38:00 +08:00
|
|
|
::XCEngine::UI::Widgets::AppendUIEditorPanelChromeBackground(
|
|
|
|
|
chromeBackground,
|
|
|
|
|
emptyStateRect,
|
|
|
|
|
{},
|
|
|
|
|
panelChromePalette,
|
|
|
|
|
panelChromeMetrics);
|
2026-04-05 14:05:46 +08:00
|
|
|
|
|
|
|
|
::XCEngine::UI::UIDrawList& emptyForeground =
|
|
|
|
|
composedDrawData.EmplaceDrawList("XCUI.NativeShell.EmptyState");
|
|
|
|
|
emptyForeground.AddText(
|
|
|
|
|
UI::UIPoint(emptyStateRect.x + 24.0f, emptyStateRect.y + 28.0f),
|
|
|
|
|
"XCUI native shell is active, but both sandbox panels are hidden.",
|
|
|
|
|
textPrimary);
|
|
|
|
|
emptyForeground.AddText(
|
|
|
|
|
UI::UIPoint(emptyStateRect.x + 24.0f, emptyStateRect.y + 50.0f),
|
|
|
|
|
"Use Ctrl+1 or Ctrl+2 to bring a panel back.",
|
|
|
|
|
textSecondary);
|
|
|
|
|
return composedDrawData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const NativeShellPanelLayout& panelLayout : panelLayouts) {
|
2026-04-05 16:38:00 +08:00
|
|
|
::XCEngine::UI::Widgets::AppendUIEditorPanelChromeBackground(
|
|
|
|
|
chromeBackground,
|
|
|
|
|
panelLayout.panelRect,
|
|
|
|
|
PanelChromeState{ panelLayout.active, panelLayout.hovered },
|
|
|
|
|
panelChromePalette,
|
|
|
|
|
panelChromeMetrics);
|
2026-04-05 14:05:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct NativePanelFrameSummary {
|
|
|
|
|
NativeShellPanelLayout layout = {};
|
|
|
|
|
std::string lineA = {};
|
|
|
|
|
std::string lineB = {};
|
|
|
|
|
::XCEngine::UI::UIDrawData overlay = {};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::vector<NativePanelFrameSummary> panelSummaries = {};
|
|
|
|
|
panelSummaries.reserve(panelLayouts.size());
|
|
|
|
|
|
|
|
|
|
const auto capturePanelSnapshot =
|
|
|
|
|
[this, &shellSnapshot](const NativeShellPanelLayout& panelLayout, bool wantsKeyboard) {
|
|
|
|
|
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions options = {};
|
|
|
|
|
options.timestampNanoseconds = shellSnapshot.timestampNanoseconds;
|
|
|
|
|
options.windowFocused = shellSnapshot.windowFocused;
|
|
|
|
|
options.hasPointerInsideOverride = true;
|
|
|
|
|
options.pointerInsideOverride = panelLayout.hovered;
|
|
|
|
|
|
|
|
|
|
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot panelSnapshot =
|
|
|
|
|
m_xcuiInputSource.CaptureSnapshot(options);
|
|
|
|
|
if (!wantsKeyboard) {
|
|
|
|
|
panelSnapshot.keys.clear();
|
|
|
|
|
panelSnapshot.characters.clear();
|
|
|
|
|
panelSnapshot.wantCaptureKeyboard = false;
|
|
|
|
|
panelSnapshot.wantTextInput = false;
|
|
|
|
|
}
|
|
|
|
|
return panelSnapshot;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const auto extractCanvasOverlay =
|
|
|
|
|
[](const ::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost& canvasHost) {
|
|
|
|
|
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasFrameSnapshot snapshot = {};
|
|
|
|
|
if (!canvasHost.TryGetLatestFrameSnapshot(snapshot)) {
|
|
|
|
|
return ::XCEngine::UI::UIDrawData();
|
|
|
|
|
}
|
|
|
|
|
return snapshot.overlayDrawData;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (const NativeShellPanelLayout& panelLayout : panelLayouts) {
|
|
|
|
|
if (panelLayout.panelId == ShellPanelId::XCUIDemo) {
|
2026-04-05 14:36:02 +08:00
|
|
|
const ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUIDemo);
|
2026-04-05 17:41:31 +08:00
|
|
|
const bool nativeHostedPreviewRequested =
|
2026-04-05 14:36:02 +08:00
|
|
|
panelState != nullptr &&
|
|
|
|
|
panelState->hostedPreviewEnabled &&
|
|
|
|
|
IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo);
|
|
|
|
|
|
2026-04-05 14:05:46 +08:00
|
|
|
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = {};
|
|
|
|
|
canvasSession.hostRect = panelLayout.panelRect;
|
|
|
|
|
canvasSession.canvasRect = panelLayout.canvasRect;
|
|
|
|
|
canvasSession.pointerPosition = shellSnapshot.pointerPosition;
|
|
|
|
|
canvasSession.validCanvas = panelLayout.canvasRect.width > 1.0f && panelLayout.canvasRect.height > 1.0f;
|
|
|
|
|
canvasSession.hovered = panelLayout.hovered;
|
|
|
|
|
canvasSession.windowFocused = shellSnapshot.windowFocused;
|
|
|
|
|
m_nativeDemoCanvasHost.SetCanvasSession(canvasSession);
|
|
|
|
|
|
|
|
|
|
const bool wantsKeyboard = panelLayout.active;
|
|
|
|
|
const auto panelSnapshot = capturePanelSnapshot(panelLayout, wantsKeyboard);
|
2026-04-05 17:41:31 +08:00
|
|
|
const bool showPanelHud = IsShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud);
|
|
|
|
|
|
|
|
|
|
const XCUIDemoPanelFrameComposition* composition = nullptr;
|
|
|
|
|
if (m_demoPanel != nullptr) {
|
|
|
|
|
composition = &m_demoPanel->ComposeFrame(
|
|
|
|
|
XCUIDemoPanelFrameComposeOptions{
|
|
|
|
|
panelLayout.panelRect.height,
|
|
|
|
|
panelLayout.canvasRect.y - panelLayout.panelRect.y,
|
|
|
|
|
panelState == nullptr || panelState->hostedPreviewEnabled,
|
|
|
|
|
showPanelHud,
|
|
|
|
|
showPanelHud,
|
|
|
|
|
panelSnapshot.timestampNanoseconds,
|
|
|
|
|
"XCUIDemo.NativeCanvas",
|
|
|
|
|
&panelSnapshot,
|
|
|
|
|
false,
|
|
|
|
|
nativeHostedPreviewRequested ? "Native XCUI preview pending" : nullptr,
|
|
|
|
|
nativeHostedPreviewRequested
|
|
|
|
|
? "Waiting for queued native preview output to publish into the shell card."
|
|
|
|
|
: nullptr,
|
|
|
|
|
false,
|
|
|
|
|
nullptr,
|
|
|
|
|
nullptr,
|
|
|
|
|
});
|
2026-04-05 14:05:46 +08:00
|
|
|
}
|
2026-04-05 17:41:31 +08:00
|
|
|
|
|
|
|
|
const Application::NativeHostedPreviewConsumption previewConsumption =
|
|
|
|
|
composition != nullptr
|
|
|
|
|
? Application::ResolveNativeHostedPreviewConsumption(
|
|
|
|
|
composition->nativeHostedPreview,
|
|
|
|
|
composition->hasHostedSurfaceDescriptor,
|
|
|
|
|
composition->showHostedSurfaceImage,
|
|
|
|
|
"Native XCUI preview pending",
|
|
|
|
|
"Waiting for queued native preview output to publish into the shell card.")
|
|
|
|
|
: Application::NativeHostedPreviewConsumption{};
|
|
|
|
|
if (composition != nullptr &&
|
|
|
|
|
composition->frame != nullptr &&
|
|
|
|
|
previewConsumption.appendRuntimeDrawDataToShell) {
|
|
|
|
|
AppendDrawData(composedDrawData, composition->frame->drawData);
|
2026-04-05 14:36:02 +08:00
|
|
|
}
|
2026-04-05 14:05:46 +08:00
|
|
|
|
2026-04-05 17:41:31 +08:00
|
|
|
if (showPanelHud && composition != nullptr && composition->frame != nullptr) {
|
|
|
|
|
const auto& stats = composition->frame->stats;
|
2026-04-05 14:05:46 +08:00
|
|
|
const UI::UIRect hudRect(
|
2026-04-05 17:41:31 +08:00
|
|
|
composition->canvasSession.canvasRect.x + 10.0f,
|
|
|
|
|
composition->canvasSession.canvasRect.y + 10.0f,
|
|
|
|
|
(std::min)(composition->canvasSession.canvasRect.width - 20.0f, 360.0f),
|
2026-04-05 14:05:46 +08:00
|
|
|
62.0f);
|
|
|
|
|
if (hudRect.width > 40.0f && hudRect.height > 20.0f) {
|
|
|
|
|
m_nativeDemoCanvasHost.DrawFilledRect(
|
|
|
|
|
hudRect,
|
|
|
|
|
UI::UIColor(12.0f / 255.0f, 18.0f / 255.0f, 26.0f / 255.0f, 214.0f / 255.0f),
|
|
|
|
|
10.0f);
|
|
|
|
|
m_nativeDemoCanvasHost.DrawOutlineRect(hudRect, panelBorderColor, 1.0f, 10.0f);
|
|
|
|
|
m_nativeDemoCanvasHost.DrawText(
|
|
|
|
|
UI::UIPoint(hudRect.x + 10.0f, hudRect.y + 8.0f),
|
|
|
|
|
"Direct native XCUI frame",
|
|
|
|
|
textPrimary);
|
|
|
|
|
m_nativeDemoCanvasHost.DrawText(
|
|
|
|
|
UI::UIPoint(hudRect.x + 10.0f, hudRect.y + 28.0f),
|
|
|
|
|
std::string("Tree ") + std::to_string(static_cast<unsigned long long>(stats.treeGeneration)) +
|
|
|
|
|
" | Elements " + std::to_string(stats.elementCount) +
|
|
|
|
|
" | Commands " + std::to_string(stats.commandCount),
|
|
|
|
|
textSecondary);
|
|
|
|
|
m_nativeDemoCanvasHost.DrawText(
|
|
|
|
|
UI::UIPoint(hudRect.x + 10.0f, hudRect.y + 46.0f),
|
|
|
|
|
stats.statusMessage,
|
|
|
|
|
textMuted);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NativePanelFrameSummary summary = {};
|
|
|
|
|
summary.layout = panelLayout;
|
2026-04-05 17:41:31 +08:00
|
|
|
summary.lineA =
|
|
|
|
|
m_demoPanel != nullptr && m_demoPanel->GetLastReloadSucceeded()
|
2026-04-05 14:36:02 +08:00
|
|
|
? Application::ComposeNativeHostedPreviewStatusLine(
|
|
|
|
|
previewConsumption,
|
2026-04-05 17:41:31 +08:00
|
|
|
composition != nullptr && composition->frame != nullptr
|
|
|
|
|
? composition->frame->stats.statusMessage
|
|
|
|
|
: std::string_view("XCUI demo frame unavailable"))
|
2026-04-05 14:05:46 +08:00
|
|
|
: "Document reload failed; showing last retained runtime state.";
|
|
|
|
|
summary.lineB =
|
|
|
|
|
std::string(panelLayout.active ? "Active" : "Passive") +
|
2026-04-05 17:41:31 +08:00
|
|
|
" | " + std::to_string(
|
|
|
|
|
composition != nullptr && composition->frame != nullptr
|
|
|
|
|
? composition->frame->stats.elementCount
|
|
|
|
|
: 0u) +
|
|
|
|
|
" elements | " + std::to_string(
|
|
|
|
|
composition != nullptr && composition->frame != nullptr
|
|
|
|
|
? composition->frame->stats.commandCount
|
|
|
|
|
: 0u) +
|
2026-04-05 14:05:46 +08:00
|
|
|
" cmds";
|
|
|
|
|
summary.overlay = extractCanvasOverlay(m_nativeDemoCanvasHost);
|
|
|
|
|
panelSummaries.push_back(std::move(summary));
|
|
|
|
|
m_nativeDemoCanvasHost.ClearCanvasSession();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 14:36:02 +08:00
|
|
|
const ShellPanelChromeState* panelState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab);
|
2026-04-05 17:41:31 +08:00
|
|
|
const bool nativeHostedPreviewRequested =
|
2026-04-05 14:36:02 +08:00
|
|
|
panelState != nullptr &&
|
|
|
|
|
panelState->hostedPreviewEnabled &&
|
|
|
|
|
IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab);
|
|
|
|
|
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor hostedSurfaceDescriptor = {};
|
|
|
|
|
const bool hasHostedSurfaceDescriptor =
|
2026-04-05 17:41:31 +08:00
|
|
|
nativeHostedPreviewRequested &&
|
2026-04-05 14:36:02 +08:00
|
|
|
panelState != nullptr &&
|
|
|
|
|
!panelState->previewDebugName.empty() &&
|
|
|
|
|
m_hostedPreviewSurfaceRegistry.TryGetSurfaceDescriptor(
|
|
|
|
|
panelState->previewDebugName.data(),
|
|
|
|
|
hostedSurfaceDescriptor);
|
2026-04-05 17:41:31 +08:00
|
|
|
|
2026-04-05 14:36:02 +08:00
|
|
|
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage hostedSurfaceImage = {};
|
|
|
|
|
const bool showHostedSurfaceImage =
|
2026-04-05 17:41:31 +08:00
|
|
|
nativeHostedPreviewRequested &&
|
2026-04-05 14:36:02 +08:00
|
|
|
panelState != nullptr &&
|
|
|
|
|
!panelState->previewDebugName.empty() &&
|
|
|
|
|
m_hostedPreviewSurfaceRegistry.TryGetSurfaceImage(
|
|
|
|
|
panelState->previewDebugName.data(),
|
|
|
|
|
hostedSurfaceImage);
|
2026-04-05 15:28:42 +08:00
|
|
|
const Application::NativeHostedPreviewConsumption previewConsumption =
|
2026-04-05 14:36:02 +08:00
|
|
|
Application::ResolveNativeHostedPreviewConsumption(
|
2026-04-05 17:41:31 +08:00
|
|
|
nativeHostedPreviewRequested,
|
2026-04-05 14:36:02 +08:00
|
|
|
hasHostedSurfaceDescriptor,
|
|
|
|
|
showHostedSurfaceImage,
|
|
|
|
|
"Native layout preview pending",
|
|
|
|
|
"Waiting for queued native preview output to publish into the layout card.");
|
|
|
|
|
|
2026-04-05 14:05:46 +08:00
|
|
|
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = {};
|
|
|
|
|
canvasSession.hostRect = panelLayout.panelRect;
|
|
|
|
|
canvasSession.canvasRect = panelLayout.canvasRect;
|
|
|
|
|
canvasSession.pointerPosition = shellSnapshot.pointerPosition;
|
|
|
|
|
canvasSession.validCanvas = panelLayout.canvasRect.width > 1.0f && panelLayout.canvasRect.height > 1.0f;
|
|
|
|
|
canvasSession.hovered = panelLayout.hovered;
|
|
|
|
|
canvasSession.windowFocused = shellSnapshot.windowFocused;
|
|
|
|
|
m_nativeLayoutCanvasHost.SetCanvasSession(canvasSession);
|
|
|
|
|
|
|
|
|
|
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest canvasRequest = {};
|
|
|
|
|
canvasRequest.childId = "XCUILayoutLab.NativeCanvas";
|
|
|
|
|
canvasRequest.height = panelLayout.panelRect.height;
|
|
|
|
|
canvasRequest.topInset = panelLayout.canvasRect.y - panelLayout.panelRect.y;
|
|
|
|
|
canvasRequest.drawPreviewFrame = false;
|
2026-04-05 14:36:02 +08:00
|
|
|
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();
|
2026-04-05 14:05:46 +08:00
|
|
|
const auto resolvedSession = m_nativeLayoutCanvasHost.BeginCanvas(canvasRequest);
|
|
|
|
|
|
|
|
|
|
const bool wantsKeyboard = panelLayout.active;
|
|
|
|
|
const auto panelSnapshot = capturePanelSnapshot(panelLayout, wantsKeyboard);
|
2026-04-05 17:41:31 +08:00
|
|
|
const XCUILayoutLabFrameComposition* composition = nullptr;
|
|
|
|
|
if (m_layoutLabPanel != nullptr) {
|
|
|
|
|
composition = &m_layoutLabPanel->ComposeFrame(
|
|
|
|
|
XCUILayoutLabFrameCompositionRequest{
|
|
|
|
|
resolvedSession,
|
|
|
|
|
panelSnapshot });
|
2026-04-05 14:36:02 +08:00
|
|
|
}
|
2026-04-05 14:05:46 +08:00
|
|
|
m_nativeLayoutCanvasHost.EndCanvas();
|
|
|
|
|
|
2026-04-05 17:41:31 +08:00
|
|
|
const Application::NativeHostedPreviewConsumption composedPreviewConsumption =
|
|
|
|
|
composition != nullptr
|
|
|
|
|
? Application::ResolveNativeHostedPreviewConsumption(
|
|
|
|
|
composition->nativeHostedPreview,
|
|
|
|
|
composition->hasHostedSurfaceDescriptor,
|
|
|
|
|
composition->showHostedSurfaceImage,
|
|
|
|
|
"Native layout preview pending",
|
|
|
|
|
"Waiting for queued native preview output to publish into the layout card.")
|
|
|
|
|
: previewConsumption;
|
|
|
|
|
if (composition != nullptr &&
|
|
|
|
|
composition->frameResult != nullptr &&
|
|
|
|
|
composedPreviewConsumption.appendRuntimeDrawDataToShell) {
|
|
|
|
|
AppendDrawData(composedDrawData, composition->frameResult->drawData);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 14:05:46 +08:00
|
|
|
NativePanelFrameSummary summary = {};
|
|
|
|
|
summary.layout = panelLayout;
|
2026-04-05 17:41:31 +08:00
|
|
|
summary.lineA =
|
|
|
|
|
m_layoutLabPanel != nullptr && m_layoutLabPanel->GetLastReloadSucceeded()
|
2026-04-05 14:36:02 +08:00
|
|
|
? Application::ComposeNativeHostedPreviewStatusLine(
|
2026-04-05 17:41:31 +08:00
|
|
|
composedPreviewConsumption,
|
|
|
|
|
composition != nullptr && composition->frameResult != nullptr
|
|
|
|
|
? composition->frameResult->stats.statusMessage
|
|
|
|
|
: std::string_view("XCUI layout frame unavailable"))
|
2026-04-05 14:05:46 +08:00
|
|
|
: "Layout lab reload failed; showing last retained runtime state.";
|
|
|
|
|
summary.lineB =
|
2026-04-05 17:41:31 +08:00
|
|
|
std::to_string(
|
|
|
|
|
composition != nullptr && composition->frameResult != nullptr
|
|
|
|
|
? composition->frameResult->stats.rowCount
|
|
|
|
|
: 0u) + " rows | " +
|
|
|
|
|
std::to_string(
|
|
|
|
|
composition != nullptr && composition->frameResult != nullptr
|
|
|
|
|
? composition->frameResult->stats.columnCount
|
|
|
|
|
: 0u) + " cols | " +
|
|
|
|
|
std::to_string(
|
|
|
|
|
composition != nullptr && composition->frameResult != nullptr
|
|
|
|
|
? composition->frameResult->stats.commandCount
|
|
|
|
|
: 0u) + " cmds";
|
2026-04-05 14:05:46 +08:00
|
|
|
summary.overlay = extractCanvasOverlay(m_nativeLayoutCanvasHost);
|
|
|
|
|
panelSummaries.push_back(std::move(summary));
|
|
|
|
|
m_nativeLayoutCanvasHost.ClearCanvasSession();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
::XCEngine::UI::UIDrawList& chromeForeground =
|
|
|
|
|
composedDrawData.EmplaceDrawList("XCUI.NativeShell.Foreground");
|
|
|
|
|
chromeForeground.AddText(
|
|
|
|
|
UI::UIPoint(topBarRect.x + 18.0f, topBarRect.y + 14.0f),
|
|
|
|
|
"XCUI Native Shell",
|
|
|
|
|
textPrimary);
|
|
|
|
|
chromeForeground.AddText(
|
|
|
|
|
UI::UIPoint(topBarRect.x + 18.0f, topBarRect.y + 34.0f),
|
|
|
|
|
"Default host path is now direct XCUI composition over the swapchain, with ImGui kept only as an explicit compatibility shell.",
|
|
|
|
|
textSecondary);
|
|
|
|
|
|
|
|
|
|
std::ostringstream footerStream = {};
|
|
|
|
|
footerStream
|
|
|
|
|
<< "Ctrl+1 Demo | Ctrl+2 Layout | Ctrl+Shift+B Backdrop "
|
|
|
|
|
<< "| Ctrl+Shift+H HUD | Active panel: "
|
|
|
|
|
<< (m_nativeActivePanel == ShellPanelId::XCUIDemo ? "XCUI Demo" : "XCUI Layout Lab");
|
|
|
|
|
chromeForeground.AddText(
|
|
|
|
|
UI::UIPoint(footerRect.x + 14.0f, footerRect.y + 10.0f),
|
|
|
|
|
footerStream.str(),
|
|
|
|
|
textSecondary);
|
|
|
|
|
|
|
|
|
|
for (const NativePanelFrameSummary& summary : panelSummaries) {
|
2026-04-05 16:38:00 +08:00
|
|
|
::XCEngine::UI::Widgets::AppendUIEditorPanelChromeForeground(
|
|
|
|
|
chromeForeground,
|
|
|
|
|
summary.layout.panelRect,
|
|
|
|
|
PanelChromeText{
|
|
|
|
|
summary.layout.title,
|
|
|
|
|
summary.lineA,
|
|
|
|
|
summary.lineB
|
|
|
|
|
},
|
|
|
|
|
panelChromePalette,
|
|
|
|
|
panelChromeMetrics);
|
2026-04-05 14:05:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay)) {
|
|
|
|
|
const UI::UIRect overlayRect(
|
|
|
|
|
topBarRect.x + topBarRect.width - 282.0f,
|
|
|
|
|
topBarRect.y + 10.0f,
|
|
|
|
|
266.0f,
|
|
|
|
|
38.0f);
|
|
|
|
|
chromeForeground.AddFilledRect(
|
|
|
|
|
overlayRect,
|
|
|
|
|
UI::UIColor(18.0f / 255.0f, 72.0f / 255.0f, 112.0f / 255.0f, 196.0f / 255.0f),
|
|
|
|
|
10.0f);
|
|
|
|
|
chromeForeground.AddRectOutline(overlayRect, panelAccentColor, 1.0f, 10.0f);
|
|
|
|
|
chromeForeground.AddText(
|
|
|
|
|
UI::UIPoint(overlayRect.x + 12.0f, overlayRect.y + 12.0f),
|
|
|
|
|
"Overlay: native compositor + direct XCUI packet",
|
|
|
|
|
textPrimary);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const NativePanelFrameSummary& summary : panelSummaries) {
|
|
|
|
|
AppendDrawData(composedDrawData, summary.overlay);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return composedDrawData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Application::FrameNativeXCUIHost() {
|
2026-04-05 14:36:02 +08:00
|
|
|
Application::BeginHostedPreviewFrameLifecycle(
|
|
|
|
|
m_hostedPreviewQueue,
|
|
|
|
|
m_hostedPreviewSurfaceRegistry);
|
|
|
|
|
SyncHostedPreviewSurfaces();
|
|
|
|
|
|
2026-04-05 14:05:46 +08:00
|
|
|
auto* nativeCompositor =
|
|
|
|
|
dynamic_cast<::XCEngine::Editor::XCUIBackend::NativeWindowUICompositor*>(m_windowCompositor.get());
|
|
|
|
|
if (nativeCompositor == nullptr) {
|
|
|
|
|
m_xcuiInputSource.ClearFrameTransients();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions options = {};
|
|
|
|
|
options.timestampNanoseconds = MakeFrameTimestampNanoseconds();
|
|
|
|
|
options.windowFocused = m_xcuiInputSource.IsWindowFocused();
|
|
|
|
|
const auto shellSnapshot = m_xcuiInputSource.CaptureSnapshot(options);
|
|
|
|
|
const auto shellFrameDelta = DispatchShellShortcuts(shellSnapshot);
|
|
|
|
|
::XCEngine::UI::UIDrawData nativeShellDrawData = BuildNativeShellDrawData(shellSnapshot, shellFrameDelta);
|
|
|
|
|
nativeCompositor->SubmitRenderPacket(nativeShellDrawData, &m_hostedPreviewTextAtlasProvider);
|
2026-04-05 14:36:02 +08:00
|
|
|
SyncHostedPreviewSurfaces();
|
2026-04-05 14:05:46 +08:00
|
|
|
|
|
|
|
|
m_windowCompositor->RenderFrame(
|
|
|
|
|
kClearColor,
|
|
|
|
|
{},
|
|
|
|
|
[this](
|
|
|
|
|
const ::XCEngine::Rendering::RenderContext& renderContext,
|
|
|
|
|
const ::XCEngine::Rendering::RenderSurface& surface) {
|
2026-04-05 14:36:02 +08:00
|
|
|
RenderQueuedHostedPreviews(renderContext, surface);
|
|
|
|
|
|
2026-04-05 14:05:46 +08:00
|
|
|
MainWindowNativeBackdropRenderer::FrameState frameState = {};
|
|
|
|
|
frameState.elapsedSeconds = static_cast<float>(
|
|
|
|
|
std::chrono::duration<double>(std::chrono::steady_clock::now() - m_startTime).count());
|
|
|
|
|
frameState.pulseAccent = IsShellViewToggleEnabled(ShellViewToggleId::PulseAccent);
|
|
|
|
|
frameState.drawBackdrop = IsShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop);
|
|
|
|
|
m_nativeBackdropRenderer.Render(renderContext, surface, frameState);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
m_xcuiInputSource.ClearFrameTransients();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 04:55:25 +08:00
|
|
|
void Application::RenderQueuedHostedPreviews(
|
|
|
|
|
const ::XCEngine::Rendering::RenderContext& renderContext,
|
|
|
|
|
const ::XCEngine::Rendering::RenderSurface& surface) {
|
|
|
|
|
(void)surface;
|
|
|
|
|
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewDrainStats drainStats = {};
|
|
|
|
|
const auto& queuedFrames = m_hostedPreviewQueue.GetQueuedFrames();
|
|
|
|
|
drainStats.queuedFrameCount = queuedFrames.size();
|
|
|
|
|
for (const auto& queuedFrame : queuedFrames) {
|
|
|
|
|
drainStats.queuedDrawListCount += queuedFrame.drawData.GetDrawListCount();
|
|
|
|
|
drainStats.queuedCommandCount += queuedFrame.drawData.GetTotalCommandCount();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (queuedFrames.empty()) {
|
|
|
|
|
m_hostedPreviewQueue.SetLastDrainStats(drainStats);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_hostedPreviewRenderBackend.SetTextAtlasProvider(&m_hostedPreviewTextAtlasProvider);
|
|
|
|
|
std::size_t queuedFrameIndex = 0u;
|
|
|
|
|
for (const auto& queuedFrame : queuedFrames) {
|
|
|
|
|
m_hostedPreviewSurfaceRegistry.RecordQueuedFrame(queuedFrame, queuedFrameIndex);
|
|
|
|
|
if (queuedFrame.debugName.empty()) {
|
|
|
|
|
++drainStats.skippedFrameCount;
|
|
|
|
|
++queuedFrameIndex;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::uint32_t expectedWidth = queuedFrame.logicalSize.width > 1.0f
|
|
|
|
|
? static_cast<std::uint32_t>(std::ceil(queuedFrame.logicalSize.width))
|
|
|
|
|
: 0u;
|
|
|
|
|
const std::uint32_t expectedHeight = queuedFrame.logicalSize.height > 1.0f
|
|
|
|
|
? static_cast<std::uint32_t>(std::ceil(queuedFrame.logicalSize.height))
|
|
|
|
|
: 0u;
|
|
|
|
|
if (expectedWidth == 0u || expectedHeight == 0u) {
|
|
|
|
|
++drainStats.skippedFrameCount;
|
|
|
|
|
++queuedFrameIndex;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HostedPreviewOffscreenSurface& previewSurface = FindOrAddHostedPreviewSurface(queuedFrame.debugName);
|
|
|
|
|
if (!EnsureHostedPreviewSurface(previewSurface, expectedWidth, expectedHeight) ||
|
|
|
|
|
!RenderHostedPreviewOffscreenSurface(previewSurface, renderContext, queuedFrame.drawData)) {
|
|
|
|
|
++drainStats.skippedFrameCount;
|
|
|
|
|
++queuedFrameIndex;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
++drainStats.renderedFrameCount;
|
|
|
|
|
const auto& overlayStats = m_hostedPreviewRenderBackend.GetLastOverlayStats();
|
|
|
|
|
drainStats.renderedDrawListCount += overlayStats.drawListCount;
|
|
|
|
|
drainStats.renderedCommandCount += overlayStats.renderedCommandCount;
|
|
|
|
|
drainStats.skippedCommandCount += overlayStats.skippedCommandCount;
|
|
|
|
|
m_hostedPreviewSurfaceRegistry.UpdateSurface(
|
|
|
|
|
queuedFrame.debugName,
|
2026-04-05 06:41:32 +08:00
|
|
|
previewSurface.textureRegistration.texture,
|
2026-04-05 04:55:25 +08:00
|
|
|
::XCEngine::UI::UIRect(
|
|
|
|
|
0.0f,
|
|
|
|
|
0.0f,
|
|
|
|
|
static_cast<float>(previewSurface.width),
|
|
|
|
|
static_cast<float>(previewSurface.height)));
|
|
|
|
|
++queuedFrameIndex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_hostedPreviewQueue.SetLastDrainStats(drainStats);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Application::Frame() {
|
|
|
|
|
if (!m_renderReady || !m_windowRenderer.BeginFrame()) {
|
|
|
|
|
m_xcuiInputSource.ClearFrameTransients();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 06:15:24 +08:00
|
|
|
if (m_windowCompositor == nullptr) {
|
|
|
|
|
m_xcuiInputSource.ClearFrameTransients();
|
|
|
|
|
return;
|
2026-04-05 04:55:25 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-05 14:05:46 +08:00
|
|
|
if (IsNativeWindowHostEnabled()) {
|
|
|
|
|
FrameNativeXCUIHost();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-04-05 04:55:25 +08:00
|
|
|
|
2026-04-05 16:53:40 +08:00
|
|
|
FrameCompatibilityHost();
|
2026-04-05 04:55:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace NewEditor
|
|
|
|
|
} // namespace XCEngine
|