#include "Platform/Win32/EditorWindowRuntimeController.h" #include "Bootstrap/EditorResources.h" #include "Composition/EditorContext.h" #include "Platform/Win32/EditorWindowSupport.h" #include "Support/EmbeddedPngLoader.h" #include #include #include #include #include namespace XCEngine::UI::Editor::App { using App::LoadEmbeddedPngTexture; using namespace EditorWindowSupport; namespace { constexpr float kFrameTimeSmoothingFactor = 0.12f; constexpr float kFrameStatsDisplayRefreshIntervalSeconds = 0.25f; } EditorWindowRuntimeController::EditorWindowRuntimeController( UIEditorWorkspaceController workspaceController) : m_workspaceController(std::move(workspaceController)) { } EditorWindowRuntimeController::~EditorWindowRuntimeController() = default; bool EditorWindowRuntimeController::IsReady() const { return m_ready; } const UIEditorWorkspaceController& EditorWindowRuntimeController::GetWorkspaceController() const { return m_workspaceController; } UIEditorWorkspaceController& EditorWindowRuntimeController::GetMutableWorkspaceController() { return m_workspaceController; } void EditorWindowRuntimeController::ReplaceWorkspaceController( UIEditorWorkspaceController workspaceController) { m_workspaceController = std::move(workspaceController); } const EditorShellRuntime& EditorWindowRuntimeController::GetShellRuntime() const { return m_shellRuntime; } EditorShellRuntime& EditorWindowRuntimeController::GetShellRuntime() { return m_shellRuntime; } const UIEditorShellInteractionFrame& EditorWindowRuntimeController::GetShellFrame() const { return m_shellRuntime.GetShellFrame(); } const UIEditorShellInteractionState& EditorWindowRuntimeController::GetShellInteractionState() const { return m_shellRuntime.GetShellInteractionState(); } void EditorWindowRuntimeController::SetExternalDockHostDropPreview( const Widgets::UIEditorDockHostDropPreviewState& preview) { m_shellRuntime.SetExternalDockHostDropPreview(preview); } void EditorWindowRuntimeController::ClearExternalDockHostDropPreview() { m_shellRuntime.ClearExternalDockHostDropPreview(); } void EditorWindowRuntimeController::SetDpiScale(float dpiScale) { m_dpiScale = dpiScale > 0.0f ? dpiScale : 1.0f; m_renderer.SetDpiScale(dpiScale); m_textSystem.SetDpiScale(m_dpiScale); m_uiRenderer.SetDpiScale(m_dpiScale); } ::XCEngine::UI::Editor::UIEditorTextMeasurer& EditorWindowRuntimeController::GetTextMeasurer() { return m_textSystem; } const ::XCEngine::UI::Editor::UIEditorTextMeasurer& EditorWindowRuntimeController::GetTextMeasurer() const { return m_textSystem; } const ::XCEngine::UI::UITextureHandle& EditorWindowRuntimeController::GetTitleBarLogoIcon() const { return m_titleBarLogoIcon; } bool EditorWindowRuntimeController::Initialize( HWND hwnd, const std::filesystem::path& repoRoot, EditorContext& editorContext, const std::filesystem::path& captureRoot, bool autoCaptureOnStartup) { if (hwnd == nullptr) { LogRuntimeTrace("app", "window initialize skipped: hwnd is null"); return false; } RECT clientRect = {}; GetClientRect(hwnd, &clientRect); const int clientWidth = (std::max)(clientRect.right - clientRect.left, 1L); const int clientHeight = (std::max)(clientRect.bottom - clientRect.top, 1L); if (!m_windowRenderer.Initialize(hwnd, clientWidth, clientHeight)) { LogRuntimeTrace("app", "d3d12 window renderer initialization failed"); return false; } if (!m_textureHost.Initialize(m_windowRenderer)) { LogRuntimeTrace("app", "d3d12 ui texture host initialization failed"); m_windowRenderer.Shutdown(); return false; } if (!m_textSystem.Initialize()) { LogRuntimeTrace("app", "d3d12 ui text system initialization failed"); m_textureHost.Shutdown(); m_windowRenderer.Shutdown(); return false; } m_textSystem.SetDpiScale(m_dpiScale); if (!m_uiRenderer.Initialize(m_windowRenderer, m_textureHost, m_textSystem)) { LogRuntimeTrace("app", "d3d12 ui renderer initialization failed"); m_textSystem.Shutdown(); m_textureHost.Shutdown(); m_windowRenderer.Shutdown(); return false; } m_uiRenderer.SetDpiScale(m_dpiScale); const Host::D3D12WindowRenderLoopAttachResult attachResult = m_windowRenderLoop.Attach(m_uiRenderer, m_windowRenderer); if (!attachResult.warning.empty()) { LogRuntimeTrace("app", attachResult.warning); } editorContext.AttachTextMeasurer(m_textSystem); m_shellRuntime.Initialize(repoRoot, m_textureHost, m_textSystem); m_shellRuntime.AttachViewportWindowRenderer(m_windowRenderer); m_shellRuntime.SetViewportSurfacePresentationEnabled( attachResult.hasViewportSurfacePresentation); std::string titleBarLogoError = {}; if (!LoadEmbeddedPngTexture( m_textureHost, IDR_PNG_LOGO_ICON, m_titleBarLogoIcon, titleBarLogoError)) { LogRuntimeTrace("icons", "titlebar logo_icon.png: " + titleBarLogoError); } if (!m_shellRuntime.GetBuiltInIconError().empty()) { LogRuntimeTrace("icons", m_shellRuntime.GetBuiltInIconError()); } LogRuntimeTrace( "app", "shell runtime initialized: " + editorContext.DescribeWorkspaceState( m_workspaceController, m_shellRuntime.GetShellInteractionState())); ResetFrameTiming(); m_ready = true; m_autoScreenshot.Initialize(captureRoot); if (autoCaptureOnStartup && IsAutoCaptureOnStartupEnabled()) { m_autoScreenshot.RequestCapture("startup"); editorContext.SetStatus("Capture", "Startup capture requested."); } return true; } void EditorWindowRuntimeController::Shutdown() { m_ready = false; ResetFrameTiming(); LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=WaitForGpuIdle"); m_windowRenderer.WaitForGpuIdle(); LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=AutoScreenshot"); m_autoScreenshot.Shutdown(); LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=ShellRuntime"); m_shellRuntime.Shutdown(); LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=RenderLoopDetach"); m_windowRenderLoop.Detach(); LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=UiRenderer"); m_uiRenderer.Shutdown(); LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=TextSystem"); m_textSystem.Shutdown(); LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=TitleBarLogo"); m_textureHost.ReleaseTexture(m_titleBarLogoIcon); LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=TextureHost"); m_textureHost.Shutdown(); LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=WindowRenderer"); m_windowRenderer.Shutdown(); LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=NativeRenderer"); m_renderer.Shutdown(); m_dpiScale = 1.0f; LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown end"); } void EditorWindowRuntimeController::ResetInteractionState() { m_shellRuntime.ResetInteractionState(); ResetFrameTiming(); } bool EditorWindowRuntimeController::ApplyResize(UINT width, UINT height) { if (!m_ready || width == 0u || height == 0u) { return false; } const Host::D3D12WindowRenderLoopResizeResult resizeResult = m_windowRenderLoop.ApplyResize(width, height); m_shellRuntime.SetViewportSurfacePresentationEnabled( resizeResult.hasViewportSurfacePresentation); if (!resizeResult.windowRendererWarning.empty()) { LogRuntimeTrace("present", resizeResult.windowRendererWarning); } return resizeResult.hasViewportSurfacePresentation; } Host::D3D12WindowRenderLoopFrameContext EditorWindowRuntimeController::BeginFrame() { UpdateFrameTiming(); return m_windowRenderLoop.BeginFrame(); } Host::D3D12WindowRenderLoopPresentResult EditorWindowRuntimeController::Present( const ::XCEngine::UI::UIDrawData& drawData) const { return m_windowRenderLoop.Present(drawData); } void EditorWindowRuntimeController::CaptureIfRequested( const ::XCEngine::UI::UIDrawData& drawData, UINT pixelWidth, UINT pixelHeight, bool framePresented) { m_autoScreenshot.CaptureIfRequested( m_renderer, m_windowRenderer, drawData, pixelWidth, pixelHeight, framePresented); } void EditorWindowRuntimeController::RequestManualScreenshot(std::string reason) { m_autoScreenshot.RequestCapture(std::move(reason)); } std::string EditorWindowRuntimeController::BuildCaptureStatusText() const { if (m_autoScreenshot.HasPendingCapture()) { return "Shot pending..."; } if (!m_autoScreenshot.GetLastCaptureError().empty()) { return TruncateText(m_autoScreenshot.GetLastCaptureError(), 38u); } if (!m_autoScreenshot.GetLastCaptureSummary().empty()) { return TruncateText(m_autoScreenshot.GetLastCaptureSummary(), 38u); } return {}; } std::string EditorWindowRuntimeController::BuildFrameRateText() const { return m_frameRateText; } void EditorWindowRuntimeController::ResetFrameTiming() { m_lastFrameTime = {}; m_hasLastFrameTime = false; m_smoothedDeltaTimeSeconds = 0.0f; m_frameStatsDisplayAccumulatorSeconds = 0.0f; m_displayFps = 0.0f; m_displayFrameTimeMs = 0.0f; m_frameRateText.clear(); } void EditorWindowRuntimeController::UpdateFrameTiming() { const auto now = std::chrono::steady_clock::now(); if (!m_hasLastFrameTime) { m_lastFrameTime = now; m_hasLastFrameTime = true; return; } const float deltaTime = std::chrono::duration(now - m_lastFrameTime).count(); m_lastFrameTime = now; if (deltaTime <= 0.0f) { return; } if (m_smoothedDeltaTimeSeconds <= 0.0f) { m_smoothedDeltaTimeSeconds = deltaTime; } else { m_smoothedDeltaTimeSeconds += (deltaTime - m_smoothedDeltaTimeSeconds) * kFrameTimeSmoothingFactor; } m_displayFrameTimeMs = m_smoothedDeltaTimeSeconds * 1000.0f; m_displayFps = m_smoothedDeltaTimeSeconds > 0.0f ? 1.0f / m_smoothedDeltaTimeSeconds : 0.0f; m_frameStatsDisplayAccumulatorSeconds += deltaTime; if (m_frameRateText.empty() || m_frameStatsDisplayAccumulatorSeconds >= kFrameStatsDisplayRefreshIntervalSeconds) { RefreshDisplayedFrameStats(); m_frameStatsDisplayAccumulatorSeconds = 0.0f; } } void EditorWindowRuntimeController::RefreshDisplayedFrameStats() { if (m_displayFps <= 0.0f || m_displayFrameTimeMs <= 0.0f) { m_frameRateText.clear(); return; } const int roundedFps = (std::max)(0, static_cast(std::lround(m_displayFps))); const int roundedFrameTimeMs = (std::max)(0, static_cast(std::lround(m_displayFrameTimeMs))); char buffer[48] = {}; std::snprintf( buffer, sizeof(buffer), "FPS %3d | %2d ms", roundedFps, roundedFrameTimeMs); m_frameRateText = buffer; } } // namespace XCEngine::UI::Editor::App