2026-04-26 17:14:32 +08:00
|
|
|
#include "Windowing/Runtime/EditorWindowRuntimeController.h"
|
2026-04-25 16:46:01 +08:00
|
|
|
|
|
|
|
|
#include "Bootstrap/EditorResources.h"
|
|
|
|
|
#include "Support/EmbeddedPngLoader.h"
|
2026-04-26 17:14:32 +08:00
|
|
|
#include "Support/TextFormat.h"
|
2026-04-25 16:46:01 +08:00
|
|
|
|
|
|
|
|
#include <XCEditor/Docking/UIEditorDockHostTransfer.h>
|
2026-04-26 17:14:32 +08:00
|
|
|
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
|
2026-04-25 16:46:01 +08:00
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <cassert>
|
|
|
|
|
#include <chrono>
|
|
|
|
|
#include <cmath>
|
|
|
|
|
#include <cstdio>
|
2026-04-26 17:14:32 +08:00
|
|
|
#include <string_view>
|
2026-04-25 16:46:01 +08:00
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
|
|
namespace XCEngine::UI::Editor::App {
|
|
|
|
|
|
|
|
|
|
using App::LoadEmbeddedPngTexture;
|
2026-04-26 17:14:32 +08:00
|
|
|
using App::TruncateText;
|
2026-04-25 16:46:01 +08:00
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
constexpr float kFrameTimeSmoothingFactor = 0.12f;
|
|
|
|
|
constexpr float kFrameStatsDisplayRefreshIntervalSeconds = 0.25f;
|
|
|
|
|
|
2026-04-26 17:14:32 +08:00
|
|
|
void LogRuntimeTrace(std::string_view channel, std::string_view message) {
|
|
|
|
|
AppendUIEditorRuntimeTrace(channel, message);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 16:46:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EditorWindowRuntimeController::EditorWindowRuntimeController(
|
2026-04-26 17:14:32 +08:00
|
|
|
EditorContext& editorContext,
|
2026-04-25 16:46:01 +08:00
|
|
|
std::unique_ptr<EditorWindowContentController> contentController)
|
2026-04-26 17:14:32 +08:00
|
|
|
: m_editorContext(editorContext)
|
|
|
|
|
, m_contentController(std::move(contentController)) {}
|
2026-04-25 16:46:01 +08:00
|
|
|
|
|
|
|
|
EditorWindowRuntimeController::~EditorWindowRuntimeController() = default;
|
|
|
|
|
|
|
|
|
|
bool EditorWindowRuntimeController::IsReady() const {
|
|
|
|
|
return m_ready;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-26 17:14:32 +08:00
|
|
|
EditorWindowContentCapabilities EditorWindowRuntimeController::GetCapabilities() const {
|
|
|
|
|
return m_contentController != nullptr
|
|
|
|
|
? m_contentController->GetCapabilities()
|
|
|
|
|
: EditorWindowContentCapabilities{};
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 16:46:01 +08:00
|
|
|
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;
|
|
|
|
|
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,
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(m_contentController != nullptr);
|
2026-04-26 17:14:32 +08:00
|
|
|
m_contentController->PrepareEditorContext(m_editorContext, m_textSystem);
|
2026-04-25 16:46:01 +08:00
|
|
|
m_contentController->Initialize(EditorWindowContentInitializationContext{
|
|
|
|
|
.repoRoot = repoRoot,
|
2026-04-26 17:14:32 +08:00
|
|
|
.editorContext = m_editorContext,
|
2026-04-25 16:46:01 +08:00
|
|
|
.textureHost = m_textureHost,
|
|
|
|
|
.textMeasurer = m_textSystem,
|
|
|
|
|
.viewportRenderer = m_windowRenderer,
|
|
|
|
|
});
|
|
|
|
|
m_contentController->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 (const EditorWindowWorkspaceBinding* workspaceBinding = TryGetWorkspaceBinding();
|
|
|
|
|
workspaceBinding != nullptr) {
|
|
|
|
|
LogRuntimeTrace(
|
|
|
|
|
"app",
|
2026-04-26 11:17:57 +08:00
|
|
|
"shell runtime initialized: workspace content");
|
2026-04-25 16:46:01 +08:00
|
|
|
} else {
|
|
|
|
|
LogRuntimeTrace("app", "window content initialized: non-workspace content");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ResetFrameTiming();
|
|
|
|
|
m_ready = true;
|
|
|
|
|
|
|
|
|
|
m_screenshotController.Initialize(captureRoot);
|
|
|
|
|
if (autoCaptureOnStartup) {
|
|
|
|
|
m_screenshotController.RequestCapture("startup");
|
2026-04-26 17:14:32 +08:00
|
|
|
m_contentController->NotifyStartupCaptureRequested(m_editorContext);
|
2026-04-25 16:46:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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=ScreenshotController");
|
|
|
|
|
m_screenshotController.Shutdown();
|
|
|
|
|
LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=WindowContent");
|
|
|
|
|
if (m_contentController != nullptr) {
|
|
|
|
|
m_contentController->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();
|
|
|
|
|
m_dpiScale = 1.0f;
|
|
|
|
|
LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown end");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindowRuntimeController::ResetInteractionState() {
|
|
|
|
|
if (m_contentController != nullptr) {
|
|
|
|
|
m_contentController->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);
|
|
|
|
|
if (m_contentController != nullptr) {
|
|
|
|
|
m_contentController->SetViewportSurfacePresentationEnabled(
|
|
|
|
|
resizeResult.hasViewportSurfacePresentation);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!resizeResult.windowRendererWarning.empty()) {
|
|
|
|
|
LogRuntimeTrace("present", resizeResult.windowRendererWarning);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return resizeResult.hasViewportSurfacePresentation;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-26 17:14:32 +08:00
|
|
|
void EditorWindowRuntimeController::PrepareEditorContext() {
|
2026-04-26 16:01:07 +08:00
|
|
|
if (m_contentController != nullptr) {
|
2026-04-26 17:14:32 +08:00
|
|
|
m_contentController->PrepareEditorContext(m_editorContext, m_textSystem);
|
2026-04-26 16:01:07 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-26 17:14:32 +08:00
|
|
|
bool EditorWindowRuntimeController::IsEditorContextValid() const {
|
2026-04-26 16:01:07 +08:00
|
|
|
return m_contentController != nullptr &&
|
2026-04-26 17:14:32 +08:00
|
|
|
m_contentController->IsEditorContextValid(m_editorContext);
|
2026-04-26 16:01:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorWindowRuntimeController::AppendInvalidFrame(
|
|
|
|
|
::XCEngine::UI::UIDrawList& drawList) const {
|
|
|
|
|
if (m_contentController != nullptr) {
|
2026-04-26 17:14:32 +08:00
|
|
|
m_contentController->AppendInvalidFrame(m_editorContext, drawList);
|
2026-04-26 16:01:07 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 16:46:01 +08:00
|
|
|
Host::D3D12WindowRenderLoopFrameContext EditorWindowRuntimeController::BeginFrame() {
|
|
|
|
|
UpdateFrameTiming();
|
|
|
|
|
return m_windowRenderLoop.BeginFrame();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Host::D3D12WindowRenderLoopPresentResult EditorWindowRuntimeController::Present(
|
|
|
|
|
const ::XCEngine::UI::UIDrawData& drawData) {
|
|
|
|
|
std::filesystem::path capturePath = {};
|
|
|
|
|
const bool captureRequested = m_screenshotController.TryBeginCapture(capturePath);
|
|
|
|
|
|
|
|
|
|
Host::D3D12WindowRenderLoopPresentResult result =
|
|
|
|
|
m_windowRenderLoop.Present(
|
|
|
|
|
drawData,
|
|
|
|
|
captureRequested ? &capturePath : nullptr);
|
|
|
|
|
|
|
|
|
|
if (captureRequested) {
|
|
|
|
|
const bool captureSucceeded = result.framePresented && result.captureSucceeded;
|
|
|
|
|
if (captureSucceeded) {
|
|
|
|
|
m_screenshotController.CompleteCaptureSuccess(capturePath);
|
|
|
|
|
LogRuntimeTrace("capture", "native d3d12 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 d3d12 capture failed: " + m_screenshotController.GetLastCaptureError());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EditorWindowFrameTransferRequests EditorWindowRuntimeController::UpdateAndAppend(
|
2026-04-26 17:14:32 +08:00
|
|
|
const ::XCEngine::UI::UIRect& bounds,
|
|
|
|
|
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
|
|
|
|
|
std::optional<EditorWindowScreenPoint> cursorScreenPoint,
|
|
|
|
|
bool primary,
|
|
|
|
|
bool globalTabDragActive,
|
|
|
|
|
bool useDetachedTitleBarTabStrip,
|
2026-04-25 16:46:01 +08:00
|
|
|
::XCEngine::UI::UIDrawData& drawData) {
|
|
|
|
|
assert(m_contentController != nullptr);
|
2026-04-26 17:14:32 +08:00
|
|
|
return m_contentController->UpdateAndAppend(
|
|
|
|
|
EditorWindowContentFrameContext{
|
|
|
|
|
.editorContext = m_editorContext,
|
|
|
|
|
.bounds = bounds,
|
|
|
|
|
.inputEvents = inputEvents,
|
|
|
|
|
.cursorScreenPoint = cursorScreenPoint,
|
|
|
|
|
.captureStatusText = BuildCaptureStatusText(),
|
|
|
|
|
.primary = primary,
|
|
|
|
|
.globalTabDragActive = globalTabDragActive,
|
|
|
|
|
.useDetachedTitleBarTabStrip = useDetachedTitleBarTabStrip,
|
|
|
|
|
},
|
|
|
|
|
drawData);
|
2026-04-25 16:46:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|