Files
XCEngine/new_editor/src/Application.cpp

941 lines
36 KiB
C++

#include "Application.h"
#include "XCUIBackend/ImGuiWindowUICompositor.h"
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <imgui.h>
#include <algorithm>
#include <cmath>
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;
}
void ConfigureFonts() {
ImGuiIO& io = ImGui::GetIO();
ImFont* uiFont = nullptr;
::XCEngine::Editor::XCUIBackend::BuildDefaultXCUIEditorFontAtlas(*io.Fonts, uiFont);
io.FontDefault = uiFont;
}
const char* GetHostedPreviewPathLabel(bool nativeRequested, bool nativePresenterBound) {
if (nativeRequested && nativePresenterBound) {
return "native queued offscreen surface";
}
if (nativeRequested) {
return "native requested, legacy presenter bound";
}
if (nativePresenterBound) {
return "legacy requested, native presenter still bound";
}
return "legacy imgui transition";
}
const char* GetHostedPreviewStateLabel(
bool hostedPreviewEnabled,
bool nativePresenterBound,
bool presentedThisFrame,
bool queuedToNativePassThisFrame,
bool surfaceImageAvailable,
bool surfaceAllocated,
bool surfaceReady,
bool descriptorAvailable) {
if (!hostedPreviewEnabled) {
return "disabled";
}
if (nativePresenterBound) {
if (surfaceImageAvailable && surfaceReady) {
return "live";
}
if (queuedToNativePassThisFrame || surfaceAllocated || descriptorAvailable) {
return "warming";
}
return "awaiting submit";
}
if (presentedThisFrame) {
return "live";
}
return "idle";
}
} // namespace
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter>
Application::CreateHostedPreviewPresenter(bool nativePreview) {
if (nativePreview) {
return ::XCEngine::Editor::XCUIBackend::CreateQueuedNativeXCUIHostedPreviewPresenter(
m_hostedPreviewQueue,
m_hostedPreviewSurfaceRegistry);
}
return ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter();
}
void Application::ConfigureHostedPreviewPresenters() {
if (m_demoPanel != nullptr) {
m_demoPanel->SetHostedPreviewEnabled(true);
m_demoPanel->SetHostedPreviewPresenter(CreateHostedPreviewPresenter(m_showNativeDemoPanelPreview));
}
if (m_layoutLabPanel != nullptr) {
m_layoutLabPanel->SetHostedPreviewEnabled(true);
m_layoutLabPanel->SetHostedPreviewPresenter(CreateHostedPreviewPresenter(m_showNativeLayoutLabPreview));
}
}
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;
}
InitializeWindowCompositor();
m_demoPanel = std::make_unique<XCUIDemoPanel>(
&m_xcuiInputSource,
CreateHostedPreviewPresenter(m_showNativeDemoPanelPreview));
m_layoutLabPanel = std::make_unique<XCUILayoutLabPanel>(
&m_xcuiInputSource,
CreateHostedPreviewPresenter(m_showNativeLayoutLabPreview));
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();
}
m_demoPanel.reset();
m_layoutLabPanel.reset();
ShutdownWindowCompositor();
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);
if (m_windowCompositor != nullptr &&
m_windowCompositor->HandleWindowMessage(hwnd, message, wParam, lParam)) {
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;
}
void Application::InitializeWindowCompositor() {
m_windowCompositor = ::XCEngine::Editor::XCUIBackend::CreateImGuiWindowUICompositor();
if (m_windowCompositor != nullptr) {
m_windowCompositor->Initialize(
m_hwnd,
m_windowRenderer,
[]() { ConfigureFonts(); });
}
}
void Application::ShutdownWindowCompositor() {
DestroyHostedPreviewSurfaces();
if (m_windowCompositor != nullptr) {
m_windowCompositor->Shutdown();
m_windowCompositor.reset();
}
}
void Application::ShutdownRenderer() {
m_renderReady = false;
m_hostedPreviewRenderBackend.Shutdown();
m_nativeBackdropRenderer.Shutdown();
m_windowRenderer.Shutdown();
}
void Application::DestroyHostedPreviewSurfaces() {
for (HostedPreviewOffscreenSurface& previewSurface : m_hostedPreviewSurfaces) {
if (previewSurface.imguiCpuHandle.ptr != 0) {
if (m_windowCompositor != nullptr) {
m_windowCompositor->FreeTextureDescriptor(
previewSurface.imguiCpuHandle,
previewSurface.imguiGpuHandle);
}
}
ShutdownAndDelete(previewSurface.colorView);
ShutdownAndDelete(previewSurface.colorTexture);
previewSurface = {};
}
m_hostedPreviewSurfaces.clear();
}
void Application::SyncHostedPreviewSurfaces() {
const auto isNativePreviewEnabled = [this](const std::string& debugName) {
return
(debugName == "XCUI Demo" && m_showNativeDemoPanelPreview) ||
(debugName == "XCUI Layout Lab" && m_showNativeLayoutLabPreview);
};
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;
}
if (previewSurface.imguiCpuHandle.ptr != 0) {
if (m_windowCompositor != nullptr) {
m_windowCompositor->FreeTextureDescriptor(
previewSurface.imguiCpuHandle,
previewSurface.imguiGpuHandle);
}
}
ShutdownAndDelete(previewSurface.colorView);
ShutdownAndDelete(previewSurface.colorTexture);
previewSurface.imguiCpuHandle = {};
previewSurface.imguiGpuHandle = {};
previewSurface.imguiTextureId = {};
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;
}
if (m_windowCompositor == nullptr ||
!m_windowCompositor->CreateTextureDescriptor(
m_windowRenderer.GetRHIDevice(),
previewSurface.colorTexture,
&previewSurface.imguiCpuHandle,
&previewSurface.imguiGpuHandle,
&previewSurface.imguiTextureId)) {
ShutdownAndDelete(previewSurface.colorView);
ShutdownAndDelete(previewSurface.colorTexture);
previewSurface.imguiCpuHandle = {};
previewSurface.imguiGpuHandle = {};
previewSurface.imguiTextureId = {};
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;
}
void Application::RenderShellChrome() {
ImGuiViewport* viewport = ImGui::GetMainViewport();
if (viewport == nullptr) {
return;
}
ImGuiWindowFlags windowFlags =
ImGuiWindowFlags_NoDocking |
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoNavFocus |
ImGuiWindowFlags_MenuBar;
ImGui::SetNextWindowPos(viewport->WorkPos);
ImGui::SetNextWindowSize(viewport->WorkSize);
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::SetNextWindowBgAlpha(0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
const bool opened = ImGui::Begin("XCNewEditorShell", nullptr, windowFlags);
ImGui::PopStyleVar(3);
if (opened) {
if (ImGui::BeginMenuBar()) {
if (ImGui::BeginMenu("View")) {
const bool demoVisible = m_demoPanel != nullptr ? m_demoPanel->IsVisible() : false;
bool demoToggle = demoVisible;
if (ImGui::MenuItem("XCUI Demo", nullptr, &demoToggle) && m_demoPanel != nullptr) {
m_demoPanel->SetVisible(demoToggle);
}
const bool layoutLabVisible =
m_layoutLabPanel != nullptr ? m_layoutLabPanel->IsVisible() : false;
bool layoutLabToggle = layoutLabVisible;
if (ImGui::MenuItem("XCUI Layout Lab", nullptr, &layoutLabToggle) &&
m_layoutLabPanel != nullptr) {
m_layoutLabPanel->SetVisible(layoutLabToggle);
}
ImGui::MenuItem("ImGui Demo", nullptr, &m_showImGuiDemoWindow);
ImGui::Separator();
ImGui::MenuItem("Native Backdrop", nullptr, &m_showNativeBackdrop);
ImGui::MenuItem("Pulse Accent", nullptr, &m_pulseNativeBackdropAccent);
ImGui::MenuItem("Native XCUI Overlay", nullptr, &m_showNativeXCUIOverlay);
ImGui::MenuItem("Hosted Preview HUD", nullptr, &m_showHostedPreviewHud);
bool nativeDemoPanelPreview = m_showNativeDemoPanelPreview;
if (ImGui::MenuItem("Native Demo Panel Preview", nullptr, &nativeDemoPanelPreview) &&
nativeDemoPanelPreview != m_showNativeDemoPanelPreview) {
m_showNativeDemoPanelPreview = nativeDemoPanelPreview;
ConfigureHostedPreviewPresenters();
}
bool nativeLayoutLabPreview = m_showNativeLayoutLabPreview;
if (ImGui::MenuItem("Native Layout Lab Preview", nullptr, &nativeLayoutLabPreview) &&
nativeLayoutLabPreview != m_showNativeLayoutLabPreview) {
m_showNativeLayoutLabPreview = nativeLayoutLabPreview;
ConfigureHostedPreviewPresenters();
}
ImGui::EndMenu();
}
ImGui::SeparatorText("XCUI Sandbox");
const MainWindowNativeBackdropRenderer::OverlayStats& nativeOverlayStats =
m_nativeBackdropRenderer.GetLastOverlayStats();
const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameStats& overlayFrameStats =
m_nativeOverlayRuntime.GetFrameResult().stats;
const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewDrainStats& hostedPreviewStats =
m_hostedPreviewQueue.GetLastDrainStats();
if (m_showNativeXCUIOverlay) {
ImGui::TextDisabled(
"Native XCUI overlay: %s | runtime %zu cmds (%zu fill, %zu outline, %zu text, %zu image, clips %zu/%zu)",
overlayFrameStats.nativeOverlayReady ? "preflight OK" : "preflight issues",
overlayFrameStats.commandCount,
overlayFrameStats.filledRectCommandCount,
overlayFrameStats.rectOutlineCommandCount,
overlayFrameStats.textCommandCount,
overlayFrameStats.imageCommandCount,
overlayFrameStats.clipPushCommandCount,
overlayFrameStats.clipPopCommandCount);
ImGui::TextDisabled(
"%s | supported %zu | unsupported %zu | prev native pass %zu cmds, %zu rendered, %zu skipped",
overlayFrameStats.nativeOverlayStatusMessage.empty()
? "Overlay diagnostics unavailable"
: overlayFrameStats.nativeOverlayStatusMessage.c_str(),
overlayFrameStats.nativeSupportedCommandCount,
overlayFrameStats.nativeUnsupportedCommandCount,
nativeOverlayStats.commandCount,
nativeOverlayStats.renderedCommandCount,
nativeOverlayStats.skippedCommandCount);
} else {
ImGui::TextDisabled(
m_showNativeBackdrop
? "Transition backend + runtime diagnostics + native backbuffer pass"
: "Transition backend + runtime diagnostics");
}
ImGui::TextDisabled(
"Hosted preview queue: %zu frames | queued %zu cmds | rendered %zu cmds | skipped %zu cmds",
hostedPreviewStats.queuedFrameCount,
hostedPreviewStats.queuedCommandCount,
hostedPreviewStats.renderedCommandCount,
hostedPreviewStats.skippedCommandCount);
std::size_t allocatedSurfaceCount = 0u;
std::size_t readySurfaceCount = 0u;
for (const HostedPreviewOffscreenSurface& previewSurface : m_hostedPreviewSurfaces) {
if (previewSurface.colorTexture != nullptr || previewSurface.colorView != nullptr) {
++allocatedSurfaceCount;
}
if (previewSurface.IsReady()) {
++readySurfaceCount;
}
}
ImGui::TextDisabled(
"Hosted surfaces: %zu registry entries | %zu allocated | %zu ready",
m_hostedPreviewSurfaceRegistry.GetDescriptors().size(),
allocatedSurfaceCount,
readySurfaceCount);
if (m_demoPanel != nullptr) {
ImGui::TextDisabled(
"XCUI Demo preview: %s",
m_showNativeDemoPanelPreview ? "native offscreen preview surface" : "ImGui hosted preview");
}
if (m_layoutLabPanel != nullptr) {
ImGui::TextDisabled(
"Layout Lab preview: %s",
m_showNativeLayoutLabPreview ? "native offscreen preview surface" : "ImGui hosted preview");
}
ImGui::EndMenuBar();
}
ImGui::DockSpace(
ImGui::GetID("XCNewEditorDockSpace"),
ImVec2(0.0f, 0.0f),
ImGuiDockNodeFlags_PassthruCentralNode);
}
ImGui::End();
if (m_showHostedPreviewHud) {
RenderHostedPreviewHud();
}
}
void Application::RenderHostedPreviewHud() {
ImGuiViewport* viewport = ImGui::GetMainViewport();
if (viewport == nullptr) {
return;
}
const HostedPreviewPanelDiagnostics demoDiagnostics = BuildHostedPreviewPanelDiagnostics(
"XCUI Demo",
"new_editor.panels.xcui_demo",
m_demoPanel != nullptr && m_demoPanel->IsVisible(),
m_demoPanel != nullptr && m_demoPanel->IsHostedPreviewEnabled(),
m_showNativeDemoPanelPreview,
m_demoPanel != nullptr && m_demoPanel->IsUsingNativeHostedPreview(),
m_demoPanel != nullptr
? m_demoPanel->GetLastPreviewStats()
: ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats{});
const HostedPreviewPanelDiagnostics layoutLabDiagnostics = BuildHostedPreviewPanelDiagnostics(
"XCUI Layout Lab",
"new_editor.panels.xcui_layout_lab",
m_layoutLabPanel != nullptr && m_layoutLabPanel->IsVisible(),
m_layoutLabPanel != nullptr && m_layoutLabPanel->IsHostedPreviewEnabled(),
m_showNativeLayoutLabPreview,
m_layoutLabPanel != nullptr && m_layoutLabPanel->IsUsingNativeHostedPreview(),
m_layoutLabPanel != nullptr
? m_layoutLabPanel->GetLastPreviewStats()
: ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats{});
std::size_t allocatedSurfaceCount = 0u;
std::size_t readySurfaceCount = 0u;
for (const HostedPreviewOffscreenSurface& previewSurface : m_hostedPreviewSurfaces) {
if (previewSurface.colorTexture != nullptr || previewSurface.colorView != nullptr) {
++allocatedSurfaceCount;
}
if (previewSurface.IsReady()) {
++readySurfaceCount;
}
}
const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewDrainStats& drainStats =
m_hostedPreviewQueue.GetLastDrainStats();
ImGuiWindowFlags windowFlags =
ImGuiWindowFlags_NoDocking |
ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoNav;
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::SetNextWindowPos(
ImVec2(viewport->WorkPos.x + viewport->WorkSize.x - 18.0f, viewport->WorkPos.y + 42.0f),
ImGuiCond_Always,
ImVec2(1.0f, 0.0f));
ImGui::SetNextWindowBgAlpha(0.9f);
if (!ImGui::Begin("XCUI Hosted Preview HUD", nullptr, windowFlags)) {
ImGui::End();
return;
}
ImGui::TextUnformatted("XCUI Hosted Preview");
ImGui::Text(
"Registry %zu | surfaces %zu/%zu ready | last native drain %zu rendered, %zu skipped",
m_hostedPreviewSurfaceRegistry.GetDescriptors().size(),
readySurfaceCount,
allocatedSurfaceCount,
drainStats.renderedFrameCount,
drainStats.skippedFrameCount);
ImGui::Separator();
const auto drawPanelRow = [](const HostedPreviewPanelDiagnostics& diagnostics) {
const char* const pathLabel =
GetHostedPreviewPathLabel(diagnostics.nativeRequested, diagnostics.nativePresenterBound);
const char* const stateLabel = GetHostedPreviewStateLabel(
diagnostics.hostedPreviewEnabled,
diagnostics.nativePresenterBound,
diagnostics.presentedThisFrame,
diagnostics.queuedToNativePassThisFrame,
diagnostics.surfaceImageAvailable,
diagnostics.surfaceAllocated,
diagnostics.surfaceReady,
diagnostics.descriptorAvailable);
ImGui::Text(
"%s [%s] %s",
diagnostics.debugName.c_str(),
diagnostics.visible ? "visible" : "hidden",
stateLabel);
ImGui::TextDisabled("%s", pathLabel);
ImGui::Text(
"source %s | submit %zu lists / %zu cmds | flush %zu lists / %zu cmds",
diagnostics.debugSource.empty() ? "n/a" : diagnostics.debugSource.c_str(),
diagnostics.submittedDrawListCount,
diagnostics.submittedCommandCount,
diagnostics.flushedDrawListCount,
diagnostics.flushedCommandCount);
if (diagnostics.nativePresenterBound) {
ImGui::Text(
"surface %ux%u | logical %.0f x %.0f | descriptor %s | image %s | submit->native %s",
diagnostics.surfaceWidth,
diagnostics.surfaceHeight,
diagnostics.logicalWidth,
diagnostics.logicalHeight,
diagnostics.descriptorAvailable ? "yes" : "no",
diagnostics.surfaceImageAvailable ? "yes" : "no",
diagnostics.queuedToNativePassThisFrame ? "yes" : "no");
} else {
ImGui::Text(
"legacy present %s | cached native surface %s",
diagnostics.presentedThisFrame ? "yes" : "no",
(diagnostics.surfaceAllocated || diagnostics.surfaceImageAvailable) ? "retained" : "none");
}
};
drawPanelRow(demoDiagnostics);
ImGui::Separator();
drawPanelRow(layoutLabDiagnostics);
ImGui::End();
}
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,
previewSurface.imguiTextureId,
previewSurface.width,
previewSurface.height,
::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;
}
m_hostedPreviewQueue.BeginFrame();
m_hostedPreviewSurfaceRegistry.BeginFrame();
SyncHostedPreviewSurfaces();
if (m_windowCompositor == nullptr) {
m_xcuiInputSource.ClearFrameTransients();
return;
}
m_windowCompositor->RenderFrame(
kClearColor,
[this]() {
RenderShellChrome();
if (m_demoPanel) {
m_demoPanel->RenderIfVisible();
}
if (m_layoutLabPanel) {
m_layoutLabPanel->RenderIfVisible();
}
if (m_showImGuiDemoWindow) {
ImGui::ShowDemoWindow(&m_showImGuiDemoWindow);
}
SyncHostedPreviewSurfaces();
},
[this](
const ::XCEngine::Rendering::RenderContext& renderContext,
const ::XCEngine::Rendering::RenderSurface& surface) {
RenderQueuedHostedPreviews(renderContext, surface);
if (!m_showNativeBackdrop && !m_showNativeXCUIOverlay) {
return;
}
MainWindowNativeBackdropRenderer::FrameState frameState = {};
frameState.elapsedSeconds = static_cast<float>(
std::chrono::duration<double>(std::chrono::steady_clock::now() - m_startTime).count());
frameState.pulseAccent = m_pulseNativeBackdropAccent;
frameState.drawBackdrop = m_showNativeBackdrop;
if (m_showNativeXCUIOverlay) {
const float width = static_cast<float>(surface.GetWidth());
const float height = static_cast<float>(surface.GetHeight());
const float horizontalMargin = (std::min)(width * 0.14f, 128.0f);
const float topMargin = (std::min)(height * 0.15f, 132.0f);
const float bottomMargin = (std::min)(height * 0.12f, 96.0f);
::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState overlayInput = {};
overlayInput.canvasRect = ::XCEngine::UI::UIRect(
horizontalMargin,
topMargin,
(std::max)(0.0f, width - horizontalMargin * 2.0f),
(std::max)(0.0f, height - topMargin - bottomMargin));
overlayInput.pointerPosition = m_xcuiInputSource.GetPointerPosition();
overlayInput.pointerInside =
overlayInput.pointerPosition.x >= overlayInput.canvasRect.x &&
overlayInput.pointerPosition.y >= overlayInput.canvasRect.y &&
overlayInput.pointerPosition.x <= overlayInput.canvasRect.x + overlayInput.canvasRect.width &&
overlayInput.pointerPosition.y <= overlayInput.canvasRect.y + overlayInput.canvasRect.height;
const auto& overlayFrame = m_nativeOverlayRuntime.Update(overlayInput);
frameState.overlayDrawData = &overlayFrame.drawData;
}
m_nativeBackdropRenderer.Render(renderContext, surface, frameState);
});
m_xcuiInputSource.ClearFrameTransients();
}
} // namespace NewEditor
} // namespace XCEngine