Files
XCEngine/editor/app/Windowing/Runtime/EditorWindowRuntimeController.cpp

430 lines
15 KiB
C++

#include "Windowing/Runtime/EditorWindowRuntimeController.h"
#include "Bootstrap/EditorResources.h"
#include "Support/EmbeddedPngLoader.h"
#include "Support/TextFormat.h"
#include <XCEditor/Docking/UIEditorDockHostTransfer.h>
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
#include <algorithm>
#include <cassert>
#include <chrono>
#include <cmath>
#include <cstdio>
#include <string_view>
#include <utility>
namespace XCEngine::UI::Editor::App {
using App::LoadEmbeddedPngTexture;
using App::TruncateText;
namespace {
constexpr float kFrameTimeSmoothingFactor = 0.12f;
constexpr float kFrameStatsDisplayRefreshIntervalSeconds = 0.25f;
void LogRuntimeTrace(std::string_view channel, std::string_view message) {
AppendUIEditorRuntimeTrace(channel, message);
}
}
EditorWindowRuntimeController::EditorWindowRuntimeController(
EditorContext& editorContext,
std::unique_ptr<EditorWindowContentController> contentController,
std::unique_ptr<Rendering::Host::EditorWindowRenderRuntime> renderRuntime)
: m_editorContext(editorContext)
, m_renderRuntime(std::move(renderRuntime))
, m_contentController(std::move(contentController)) {}
EditorWindowRuntimeController::~EditorWindowRuntimeController() = default;
bool EditorWindowRuntimeController::IsReady() const {
return m_ready && m_renderRuntime != nullptr && m_renderRuntime->IsReady();
}
EditorWindowContentCapabilities EditorWindowRuntimeController::GetCapabilities() const {
return m_contentController != nullptr
? m_contentController->GetCapabilities()
: EditorWindowContentCapabilities{};
}
EditorWindowWorkspaceBinding* EditorWindowRuntimeController::TryGetWorkspaceBinding() {
return m_contentController != nullptr
? m_contentController->TryGetWorkspaceBinding()
: nullptr;
}
const EditorWindowWorkspaceBinding* EditorWindowRuntimeController::TryGetWorkspaceBinding() const {
return m_contentController != nullptr
? m_contentController->TryGetWorkspaceBinding()
: nullptr;
}
EditorWindowDockHostBinding* EditorWindowRuntimeController::TryGetDockHostBinding() {
return m_contentController != nullptr
? m_contentController->TryGetDockHostBinding()
: nullptr;
}
const EditorWindowDockHostBinding* EditorWindowRuntimeController::TryGetDockHostBinding() const {
return m_contentController != nullptr
? m_contentController->TryGetDockHostBinding()
: nullptr;
}
const EditorWindowInputFeedbackBinding*
EditorWindowRuntimeController::TryGetInputFeedbackBinding() const {
return m_contentController != nullptr
? m_contentController->TryGetInputFeedbackBinding()
: nullptr;
}
const EditorWindowTitleBarBinding* EditorWindowRuntimeController::TryGetTitleBarBinding() const {
return m_contentController != nullptr
? m_contentController->TryGetTitleBarBinding()
: nullptr;
}
const UIEditorShellInteractionFrame& EditorWindowRuntimeController::GetShellFrame() const {
assert(m_contentController != nullptr);
return m_contentController->GetShellFrame();
}
const UIEditorShellInteractionState& EditorWindowRuntimeController::GetShellInteractionState()
const {
assert(m_contentController != nullptr);
return m_contentController->GetShellInteractionState();
}
::XCEngine::UI::UISize EditorWindowRuntimeController::ResolveMinimumOuterSize() const {
assert(m_contentController != nullptr);
return m_contentController->ResolveMinimumOuterSize();
}
void EditorWindowRuntimeController::SetDpiScale(float dpiScale) {
m_dpiScale = dpiScale > 0.0f ? dpiScale : 1.0f;
if (m_renderRuntime != nullptr) {
m_renderRuntime->SetDpiScale(m_dpiScale);
}
}
::XCEngine::UI::Editor::UIEditorTextMeasurer& EditorWindowRuntimeController::GetTextMeasurer() {
assert(m_renderRuntime != nullptr);
return m_renderRuntime->GetTextMeasurer();
}
const ::XCEngine::UI::Editor::UIEditorTextMeasurer&
EditorWindowRuntimeController::GetTextMeasurer() const {
assert(m_renderRuntime != nullptr);
return m_renderRuntime->GetTextMeasurer();
}
const ::XCEngine::UI::UITextureHandle& EditorWindowRuntimeController::GetTitleBarLogoIcon() const {
return m_titleBarLogoIcon;
}
bool EditorWindowRuntimeController::Initialize(
const Rendering::Host::EditorWindowRenderRuntimeInitializeParams& renderParams,
const std::filesystem::path& repoRoot,
const std::filesystem::path& captureRoot,
bool autoCaptureOnStartup) {
if (m_renderRuntime == nullptr) {
LogRuntimeTrace("app", "window initialize failed: render runtime is null");
return false;
}
const Rendering::Host::EditorWindowRenderRuntimeInitializeResult initializeResult =
m_renderRuntime->Initialize(renderParams);
if (!initializeResult.success) {
LogRuntimeTrace("app", initializeResult.errorMessage.empty()
? "window render runtime initialization failed"
: initializeResult.errorMessage);
return false;
}
if (!initializeResult.warning.empty()) {
LogRuntimeTrace("app", initializeResult.warning);
}
assert(m_contentController != nullptr);
m_contentController->PrepareEditorContext(
m_editorContext,
m_renderRuntime->GetTextMeasurer());
m_contentController->Initialize(EditorWindowContentInitializationContext{
.repoRoot = repoRoot,
.editorContext = m_editorContext,
.textureHost = m_renderRuntime->GetTextureHost(),
.textMeasurer = m_renderRuntime->GetTextMeasurer(),
.viewportRenderer = m_renderRuntime->GetViewportRenderHost(),
});
m_contentController->SetViewportSurfacePresentationEnabled(
initializeResult.hasViewportSurfacePresentation);
std::string titleBarLogoError = {};
if (!LoadEmbeddedPngTexture(
m_renderRuntime->GetTextureHost(),
IDR_PNG_LOGO_ICON,
m_titleBarLogoIcon,
titleBarLogoError)) {
LogRuntimeTrace("icons", "titlebar logo_icon.png: " + titleBarLogoError);
}
if (const EditorWindowWorkspaceBinding* workspaceBinding = TryGetWorkspaceBinding();
workspaceBinding != nullptr) {
LogRuntimeTrace(
"app",
"shell runtime initialized: workspace content");
} else {
LogRuntimeTrace("app", "window content initialized: non-workspace content");
}
ResetFrameTiming();
m_ready = true;
m_screenshotController.Initialize(captureRoot);
if (autoCaptureOnStartup) {
m_screenshotController.RequestCapture("startup");
m_contentController->NotifyStartupCaptureRequested(m_editorContext);
}
return true;
}
void EditorWindowRuntimeController::Shutdown() {
m_ready = false;
ResetFrameTiming();
LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=WaitForGpuIdle");
if (m_renderRuntime != nullptr) {
m_renderRuntime->WaitForGpuIdle();
}
LogRuntimeTrace(
"window-close",
"EditorWindowRuntimeController::Shutdown stage=ScreenshotController");
m_screenshotController.Shutdown();
LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=WindowContent");
if (m_contentController != nullptr) {
m_contentController->Shutdown();
}
LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=TitleBarLogo");
if (m_renderRuntime != nullptr) {
m_renderRuntime->GetTextureHost().ReleaseTexture(m_titleBarLogoIcon);
LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=RenderRuntime");
m_renderRuntime->Shutdown();
}
m_dpiScale = 1.0f;
LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown end");
}
void EditorWindowRuntimeController::ResetInteractionState() {
if (m_contentController != nullptr) {
m_contentController->ResetInteractionState();
}
ResetFrameTiming();
}
bool EditorWindowRuntimeController::ApplyResize(std::uint32_t width, std::uint32_t height) {
if (!IsReady() || width == 0u || height == 0u) {
return false;
}
const Rendering::Host::EditorWindowRenderRuntimeResizeResult resizeResult =
m_renderRuntime->ApplyResize(width, height);
if (m_contentController != nullptr) {
m_contentController->SetViewportSurfacePresentationEnabled(
resizeResult.hasViewportSurfacePresentation);
}
if (!resizeResult.warning.empty()) {
LogRuntimeTrace("present", resizeResult.warning);
}
return resizeResult.hasViewportSurfacePresentation;
}
void EditorWindowRuntimeController::PrepareEditorContext() {
if (m_contentController != nullptr && m_renderRuntime != nullptr) {
m_contentController->PrepareEditorContext(
m_editorContext,
m_renderRuntime->GetTextMeasurer());
}
}
bool EditorWindowRuntimeController::IsEditorContextValid() const {
return m_contentController != nullptr &&
m_contentController->IsEditorContextValid(m_editorContext);
}
void EditorWindowRuntimeController::AppendInvalidFrame(
::XCEngine::UI::UIDrawList& drawList) const {
if (m_contentController != nullptr) {
m_contentController->AppendInvalidFrame(m_editorContext, drawList);
}
}
Rendering::Host::EditorWindowRenderRuntimeFrameContext EditorWindowRuntimeController::BeginFrame() {
UpdateFrameTiming();
return m_renderRuntime != nullptr
? m_renderRuntime->BeginFrame()
: Rendering::Host::EditorWindowRenderRuntimeFrameContext{};
}
Rendering::Host::EditorWindowRenderRuntimePresentResult EditorWindowRuntimeController::Present(
const ::XCEngine::UI::UIDrawData& drawData) {
if (m_renderRuntime == nullptr) {
Rendering::Host::EditorWindowRenderRuntimePresentResult result = {};
result.warning = "window render runtime is null.";
return result;
}
std::filesystem::path capturePath = {};
const bool captureRequested = m_screenshotController.TryBeginCapture(capturePath);
Rendering::Host::EditorWindowRenderRuntimePresentResult result =
m_renderRuntime->Present(
drawData,
captureRequested ? &capturePath : nullptr);
if (captureRequested) {
const bool captureSucceeded = result.framePresented && result.captureSucceeded;
if (captureSucceeded) {
m_screenshotController.CompleteCaptureSuccess(capturePath);
LogRuntimeTrace("capture", "native capture succeeded: " + capturePath.string());
} else {
std::string captureError = result.captureError;
if (captureError.empty()) {
captureError = !result.warning.empty()
? result.warning
: "Screenshot capture did not complete.";
}
m_screenshotController.CompleteCaptureFailure(std::move(captureError));
LogRuntimeTrace(
"capture",
"native capture failed: " + m_screenshotController.GetLastCaptureError());
}
}
return result;
}
EditorWindowFrameTransferRequests EditorWindowRuntimeController::UpdateAndAppend(
const ::XCEngine::UI::UIRect& bounds,
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
std::optional<EditorWindowScreenPoint> cursorScreenPoint,
bool primary,
bool globalTabDragActive,
bool useDetachedTitleBarTabStrip,
::XCEngine::UI::UIDrawData& drawData) {
assert(m_contentController != nullptr);
return m_contentController->UpdateAndAppend(
EditorWindowContentFrameContext{
.editorContext = m_editorContext,
.bounds = bounds,
.inputEvents = inputEvents,
.cursorScreenPoint = cursorScreenPoint,
.captureStatusText = BuildCaptureStatusText(),
.primary = primary,
.globalTabDragActive = globalTabDragActive,
.useDetachedTitleBarTabStrip = useDetachedTitleBarTabStrip,
},
drawData);
}
void EditorWindowRuntimeController::RenderRequestedViewports(
const ::XCEngine::Rendering::RenderContext& renderContext) {
if (m_contentController != nullptr) {
m_contentController->RenderRequestedViewports(renderContext);
}
}
void EditorWindowRuntimeController::RequestManualScreenshot(std::string reason) {
m_screenshotController.RequestCapture(std::move(reason));
}
std::string EditorWindowRuntimeController::BuildCaptureStatusText() const {
if (m_screenshotController.HasPendingCapture()) {
return "Shot pending...";
}
if (!m_screenshotController.GetLastCaptureError().empty()) {
return TruncateText(m_screenshotController.GetLastCaptureError(), 38u);
}
if (!m_screenshotController.GetLastCaptureSummary().empty()) {
return TruncateText(m_screenshotController.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