Files
XCEngine/new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp

332 lines
10 KiB
C++

#include "Platform/Win32/EditorWindowRuntimeController.h"
#include "Bootstrap/EditorResources.h"
#include "Composition/EditorContext.h"
#include "Platform/Win32/EditorWindowSupport.h"
#include "Support/EmbeddedPngLoader.h"
#include <algorithm>
#include <chrono>
#include <cstdio>
#include <cmath>
#include <utility>
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();
m_autoScreenshot.Shutdown();
m_shellRuntime.Shutdown();
m_windowRenderLoop.Detach();
m_uiRenderer.Shutdown();
m_textSystem.Shutdown();
m_textureHost.ReleaseTexture(m_titleBarLogoIcon);
m_textureHost.Shutdown();
m_windowRenderer.Shutdown();
m_renderer.Shutdown();
m_dpiScale = 1.0f;
}
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<float>(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<int>(std::lround(m_displayFps)));
const int roundedFrameTimeMs =
(std::max)(0, static_cast<int>(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