From 9cbcf78090826b7f10d20a231cac0b573ccce770 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 18 Oct 2025 14:46:22 +1000 Subject: [PATCH] FullscreenUI: Move transition handling to widgets file --- src/core/fullscreenui.cpp | 204 +----------------------------- src/core/fullscreenui.h | 24 ---- src/core/fullscreenui_widgets.cpp | 200 ++++++++++++++++++++++++++++- src/core/fullscreenui_widgets.h | 24 ++++ 4 files changed, 227 insertions(+), 225 deletions(-) diff --git a/src/core/fullscreenui.cpp b/src/core/fullscreenui.cpp index d637388d3..718056c7f 100644 --- a/src/core/fullscreenui.cpp +++ b/src/core/fullscreenui.cpp @@ -129,7 +129,6 @@ static void ExitFullscreenAndOpenURL(std::string_view url); static void CopyTextToClipboard(std::string title, std::string_view text); static void DrawAboutWindow(); static void FixStateIfPaused(); -static bool CompileTransitionPipelines(); ////////////////////////////////////////////////////////////////////////// // Backgrounds @@ -150,7 +149,7 @@ static ImVec4 GetTransparentBackgroundColor(const ImVec4& no_background_color = ////////////////////////////////////////////////////////////////////////// // Resources ////////////////////////////////////////////////////////////////////////// -static bool LoadResources(); +static void LoadResources(); static void DestroyResources(); static GPUTexture* GetUserThemeableTexture( const std::string_view png_name, const std::string_view svg_name, bool* is_colorable = nullptr, @@ -390,7 +389,6 @@ private: struct ALIGN_TO_CACHE_LINE WidgetsState { // Main - TransitionState transition_state = TransitionState::Inactive; MainWindowType current_main_window = MainWindowType::None; PauseSubMenu current_pause_submenu = PauseSubMenu::None; MainWindowType previous_main_window = MainWindowType::None; @@ -413,14 +411,6 @@ struct ALIGN_TO_CACHE_LINE WidgetsState std::unique_ptr app_background_shader; Timer::Value app_background_load_time = 0; - // Transition Resources - TransitionStartCallback transition_start_callback; - std::unique_ptr transition_prev_texture; - std::unique_ptr transition_current_texture; - std::unique_ptr transition_blend_pipeline; - float transition_total_time = 0.0f; - float transition_remaining_time = 0.0f; - // Pause Menu std::time_t current_time = 0; std::string current_time_string; @@ -483,18 +473,16 @@ bool FullscreenUI::Initialize() if (s_state.tried_to_initialize || !ImGuiManager::IsInitialized()) return false; - s_state.show_localized_titles = Host::GetBaseBoolSettingValue("Main", "FullscreenUIShowLocalizedTitles", true); - - if (!InitializeWidgets() || !LoadResources()) + if (!InitializeWidgets()) { - DestroyResources(); - Shutdown(true); s_state.tried_to_initialize = true; return false; } s_state.initialized = true; + s_state.show_localized_titles = Host::GetBaseBoolSettingValue("Main", "FullscreenUIShowLocalizedTitles", true); + LoadResources(); LoadBackground(); // in case we open the pause menu while the game is running @@ -520,7 +508,7 @@ bool FullscreenUI::IsInitialized() bool FullscreenUI::HasActiveWindow() { return s_state.initialized && (s_state.current_main_window != MainWindowType::None || - s_state.transition_state != TransitionState::Inactive || AreAnyDialogsOpen()); + GetTransitionState() != TransitionState::Inactive || AreAnyDialogsOpen()); } bool FullscreenUI::AreAnyDialogsOpen() @@ -540,176 +528,6 @@ void FullscreenUI::UpdateRunIdleState() GPUThread::SetRunIdleReason(GPUThread::RunIdleReason::FullscreenUIActive, new_run_idle); } -void FullscreenUI::BeginTransition(TransitionStartCallback func, float time) -{ - if (s_state.transition_state == TransitionState::Inactive) - { - const float real_time = UIStyle.Animations ? time : 0.0f; - s_state.transition_state = TransitionState::Starting; - s_state.transition_total_time = real_time; - s_state.transition_remaining_time = real_time; - } - - // run any callback if we queue another transition in the middle of one already active - if (s_state.transition_start_callback) - { - if (s_state.transition_state == TransitionState::Starting) - WARNING_LOG("More than one transition started"); - - std::move(s_state.transition_start_callback)(); - } - - s_state.transition_start_callback = std::move(func); - - UpdateRunIdleState(); -} - -void FullscreenUI::CancelTransition() -{ - if (s_state.transition_state != TransitionState::Active) - return; - - if (s_state.transition_start_callback) - std::move(s_state.transition_start_callback)(); - - s_state.transition_state = TransitionState::Inactive; - s_state.transition_start_callback = {}; - s_state.transition_remaining_time = 0.0f; -} - -void FullscreenUI::BeginTransition(float time, TransitionStartCallback func) -{ - BeginTransition(std::move(func), time); -} - -bool FullscreenUI::IsTransitionActive() -{ - return (s_state.transition_state != TransitionState::Inactive); -} - -FullscreenUI::TransitionState FullscreenUI::GetTransitionState() -{ - return s_state.transition_state; -} - -GPUTexture* FullscreenUI::GetTransitionRenderTexture(GPUSwapChain* swap_chain) -{ - if (!g_gpu_device->ResizeTexture(&s_state.transition_current_texture, swap_chain->GetWidth(), swap_chain->GetHeight(), - GPUTexture::Type::RenderTarget, swap_chain->GetFormat(), GPUTexture::Flags::None, - false)) - { - ERROR_LOG("Failed to allocate {}x{} texture for transition, cancelling.", swap_chain->GetWidth(), - swap_chain->GetHeight()); - s_state.transition_state = TransitionState::Inactive; - return nullptr; - } - - return s_state.transition_current_texture.get(); -} - -bool FullscreenUI::CompileTransitionPipelines() -{ - const RenderAPI render_api = g_gpu_device->GetRenderAPI(); - const ShaderGen shadergen(render_api, ShaderGen::GetShaderLanguageForAPI(render_api), false, false); - GPUSwapChain* const swap_chain = g_gpu_device->GetMainSwapChain(); - - Error error; - std::unique_ptr vs = g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GetLanguage(), - shadergen.GeneratePassthroughVertexShader(), &error); - std::unique_ptr fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), - shadergen.GenerateFadeFragmentShader(), &error); - if (!vs || !fs) - { - ERROR_LOG("Failed to compile transition shaders: {}", error.GetDescription()); - return false; - } - GL_OBJECT_NAME(vs, "Transition Vertex Shader"); - GL_OBJECT_NAME(fs, "Transition Fragment Shader"); - - GPUPipeline::GraphicsConfig plconfig; - GPUBackend::SetScreenQuadInputLayout(plconfig); - plconfig.layout = GPUPipeline::Layout::MultiTextureAndPushConstants; - plconfig.rasterization = GPUPipeline::RasterizationState::GetNoCullState(); - plconfig.depth = GPUPipeline::DepthState::GetNoTestsState(); - plconfig.blend = GPUPipeline::BlendState::GetNoBlendingState(); - plconfig.SetTargetFormats(swap_chain ? swap_chain->GetFormat() : GPUTexture::Format::RGBA8); - plconfig.samples = 1; - plconfig.per_sample_shading = false; - plconfig.render_pass_flags = GPUPipeline::NoRenderPassFlags; - plconfig.vertex_shader = vs.get(); - plconfig.geometry_shader = nullptr; - plconfig.fragment_shader = fs.get(); - - s_state.transition_blend_pipeline = g_gpu_device->CreatePipeline(plconfig, &error); - if (!s_state.transition_blend_pipeline) - { - ERROR_LOG("Failed to create transition blend pipeline: {}", error.GetDescription()); - return false; - } - - return true; -} - -void FullscreenUI::RenderTransitionBlend(GPUSwapChain* swap_chain) -{ - GPUTexture* const curr = s_state.transition_current_texture.get(); - DebugAssert(curr); - - if (s_state.transition_state == TransitionState::Starting) - { - // copy current frame - if (!g_gpu_device->ResizeTexture(&s_state.transition_prev_texture, curr->GetWidth(), curr->GetHeight(), - GPUTexture::Type::RenderTarget, curr->GetFormat(), GPUTexture::Flags::None, false)) - { - ERROR_LOG("Failed to allocate {}x{} texture for transition, cancelling.", curr->GetWidth(), curr->GetHeight()); - s_state.transition_state = TransitionState::Inactive; - return; - } - - g_gpu_device->CopyTextureRegion(s_state.transition_prev_texture.get(), 0, 0, 0, 0, curr, 0, 0, 0, 0, - curr->GetWidth(), curr->GetHeight()); - - s_state.transition_state = TransitionState::Active; - } - - const float transition_alpha = s_state.transition_remaining_time / s_state.transition_total_time; - const float uniforms[2] = {1.0f - transition_alpha, transition_alpha}; - g_gpu_device->PushUniformBuffer(uniforms, sizeof(uniforms)); - g_gpu_device->SetPipeline(s_state.transition_blend_pipeline.get()); - g_gpu_device->SetViewportAndScissor(0, 0, swap_chain->GetPostRotatedWidth(), swap_chain->GetPostRotatedHeight()); - g_gpu_device->SetTextureSampler(0, curr, g_gpu_device->GetNearestSampler()); - g_gpu_device->SetTextureSampler(1, s_state.transition_prev_texture.get(), g_gpu_device->GetNearestSampler()); - - const GSVector2i size = swap_chain->GetSizeVec(); - const GSVector2i postrotated_size = swap_chain->GetPostRotatedSizeVec(); - const GSVector4 uv_rect = g_gpu_device->UsesLowerLeftOrigin() ? GSVector4::cxpr(0.0f, 1.0f, 1.0f, 0.0f) : - GSVector4::cxpr(0.0f, 0.0f, 1.0f, 1.0f); - GPUPresenter::DrawScreenQuad(GSVector4i::loadh(size), uv_rect, size, postrotated_size, DisplayRotation::Normal, - swap_chain->GetPreRotation()); -} - -void FullscreenUI::UpdateTransitionState() -{ - if (s_state.transition_state == TransitionState::Inactive) - return; - - // this callback will exist after starting if a second transition gets queued - if (s_state.transition_start_callback) - { - std::move(s_state.transition_start_callback)(); - s_state.transition_start_callback = {}; - } - - s_state.transition_remaining_time -= ImGui::GetIO().DeltaTime; - if (s_state.transition_remaining_time <= 0.0f) - { - // At 1080p we're only talking 2MB of VRAM, 16MB at 4K.. saves reallocating it on the next transition. - // g_gpu_device->RecycleTexture(std::move(s_state.transition_current_texture)); - // g_gpu_device->RecycleTexture(std::move(s_state.transition_prev_texture)); - s_state.transition_state = TransitionState::Inactive; - } -} - void FullscreenUI::OnSystemStarting() { // NOTE: Called on CPU thread. @@ -1112,7 +930,7 @@ void FullscreenUI::InvalidateCoverCache() }); } -bool FullscreenUI::LoadResources() +void FullscreenUI::LoadResources() { s_state.app_icon_texture = LoadTexture("images/duck.png"); s_state.fallback_disc_texture = LoadTexture("fullscreenui/cdrom.png"); @@ -1121,21 +939,11 @@ bool FullscreenUI::LoadResources() s_state.fallback_playlist_texture = LoadTexture("fullscreenui/playlist-file.png"); s_state.cover_placeholder_texture = LoadTexture("images/cover-placeholder.png"); - if (!CompileTransitionPipelines()) - return false; - InitializeHotkeyList(); - - return true; } void FullscreenUI::DestroyResources() { - s_state.transition_blend_pipeline.reset(); - g_gpu_device->RecycleTexture(std::move(s_state.transition_prev_texture)); - g_gpu_device->RecycleTexture(std::move(s_state.transition_current_texture)); - s_state.transition_state = TransitionState::Inactive; - s_state.transition_start_callback = {}; s_state.cover_placeholder_texture.reset(); s_state.fallback_playlist_texture.reset(); s_state.fallback_psf_texture.reset(); diff --git a/src/core/fullscreenui.h b/src/core/fullscreenui.h index c4f3519b2..1036f314b 100644 --- a/src/core/fullscreenui.h +++ b/src/core/fullscreenui.h @@ -16,9 +16,6 @@ class SmallStringBase; -class GPUSwapChain; -class GPUTexture; - struct GPUSettings; namespace FullscreenUI { @@ -45,27 +42,6 @@ void CloseLoadingScreen(); void UpdateTheme(); void UpdateRunIdleState(); -void UpdateTransitionState(); - -inline constexpr float SHORT_TRANSITION_TIME = 0.08f; -inline constexpr float DEFAULT_TRANSITION_TIME = 0.15f; -inline constexpr float LONG_TRANSITION_TIME = 0.3f; - -enum class TransitionState : u8 -{ - Inactive, - Starting, - Active, -}; - -using TransitionStartCallback = std::function; -void BeginTransition(TransitionStartCallback func, float time = DEFAULT_TRANSITION_TIME); -void BeginTransition(float time, TransitionStartCallback func); -void CancelTransition(); -bool IsTransitionActive(); -TransitionState GetTransitionState(); -GPUTexture* GetTransitionRenderTexture(GPUSwapChain* swap_chain); -void RenderTransitionBlend(GPUSwapChain* swap_chain); #ifndef __ANDROID__ diff --git a/src/core/fullscreenui_widgets.cpp b/src/core/fullscreenui_widgets.cpp index 6ba72e8ba..854812321 100644 --- a/src/core/fullscreenui_widgets.cpp +++ b/src/core/fullscreenui_widgets.cpp @@ -3,6 +3,8 @@ #include "fullscreenui_widgets.h" #include "fullscreenui.h" +#include "gpu_backend.h" +#include "gpu_presenter.h" #include "gpu_thread.h" #include "host.h" #include "imgui_overlays.h" @@ -12,6 +14,7 @@ #include "util/image.h" #include "util/imgui_animated.h" #include "util/imgui_manager.h" +#include "util/shadergen.h" #include "common/assert.h" #include "common/easing.h" @@ -54,6 +57,8 @@ static constexpr u32 LOADING_PROGRESS_SAMPLE_COUNT = 30; static std::optional LoadTextureImage(std::string_view path, u32 svg_width, u32 svg_height); static std::shared_ptr UploadTexture(std::string_view path, const Image& image); +static bool CompileTransitionPipelines(); + static void CreateFooterTextString(SmallStringBase& dest, std::span> items); @@ -109,7 +114,7 @@ static_assert( namespace { -enum class CloseButtonState +enum class CloseButtonState : u8 { None, KeyboardPressed, @@ -292,8 +297,9 @@ struct ALIGN_TO_CACHE_LINE WidgetsState std::recursive_mutex shared_state_mutex; CloseButtonState close_button_state = CloseButtonState::None; - ImGuiDir has_pending_nav_move = ImGuiDir_None; FocusResetType focus_reset_queued = FocusResetType::None; + TransitionState transition_state = TransitionState::Inactive; + ImGuiDir has_pending_nav_move = ImGuiDir_None; u32 menu_button_index = 0; ImVec2 horizontal_menu_button_size = {}; @@ -302,6 +308,14 @@ struct ALIGN_TO_CACHE_LINE WidgetsState std::shared_ptr placeholder_texture; std::deque> texture_upload_queue; + // Transition Resources + TransitionStartCallback transition_start_callback; + std::unique_ptr transition_prev_texture; + std::unique_ptr transition_current_texture; + std::unique_ptr transition_blend_pipeline; + float transition_total_time = 0.0f; + float transition_remaining_time = 0.0f; + SmallString fullscreen_footer_text; SmallString last_fullscreen_footer_text; SmallString left_fullscreen_footer_text; @@ -368,8 +382,11 @@ bool FullscreenUI::InitializeWidgets() s_state.close_button_state = CloseButtonState::None; s_state.placeholder_texture = LoadTexture("images/placeholder.png"); - if (!s_state.placeholder_texture) + if (!s_state.placeholder_texture || !CompileTransitionPipelines()) + { + ShutdownWidgets(true); return false; + } UpdateWidgetsSettings(); ResetMenuButtonFrame(); @@ -380,6 +397,13 @@ bool FullscreenUI::InitializeWidgets() void FullscreenUI::ShutdownWidgets(bool clear_state) { std::unique_lock lock(s_state.shared_state_mutex); + + s_state.transition_blend_pipeline.reset(); + g_gpu_device->RecycleTexture(std::move(s_state.transition_prev_texture)); + g_gpu_device->RecycleTexture(std::move(s_state.transition_current_texture)); + s_state.transition_state = TransitionState::Inactive; + s_state.transition_start_callback = {}; + s_state.texture_upload_queue.clear(); s_state.placeholder_texture.reset(); UIStyle.Font = nullptr; @@ -650,6 +674,176 @@ void FullscreenUI::UploadAsyncTextures() } } +void FullscreenUI::BeginTransition(TransitionStartCallback func, float time) +{ + if (s_state.transition_state == TransitionState::Inactive) + { + const float real_time = UIStyle.Animations ? time : 0.0f; + s_state.transition_state = TransitionState::Starting; + s_state.transition_total_time = real_time; + s_state.transition_remaining_time = real_time; + } + + // run any callback if we queue another transition in the middle of one already active + if (s_state.transition_start_callback) + { + if (s_state.transition_state == TransitionState::Starting) + WARNING_LOG("More than one transition started"); + + std::move(s_state.transition_start_callback)(); + } + + s_state.transition_start_callback = std::move(func); + + UpdateRunIdleState(); +} + +void FullscreenUI::CancelTransition() +{ + if (s_state.transition_state != TransitionState::Active) + return; + + if (s_state.transition_start_callback) + std::move(s_state.transition_start_callback)(); + + s_state.transition_state = TransitionState::Inactive; + s_state.transition_start_callback = {}; + s_state.transition_remaining_time = 0.0f; +} + +void FullscreenUI::BeginTransition(float time, TransitionStartCallback func) +{ + BeginTransition(std::move(func), time); +} + +bool FullscreenUI::IsTransitionActive() +{ + return (s_state.transition_state != TransitionState::Inactive); +} + +FullscreenUI::TransitionState FullscreenUI::GetTransitionState() +{ + return s_state.transition_state; +} + +GPUTexture* FullscreenUI::GetTransitionRenderTexture(GPUSwapChain* swap_chain) +{ + if (!g_gpu_device->ResizeTexture(&s_state.transition_current_texture, swap_chain->GetWidth(), swap_chain->GetHeight(), + GPUTexture::Type::RenderTarget, swap_chain->GetFormat(), GPUTexture::Flags::None, + false)) + { + ERROR_LOG("Failed to allocate {}x{} texture for transition, cancelling.", swap_chain->GetWidth(), + swap_chain->GetHeight()); + s_state.transition_state = TransitionState::Inactive; + return nullptr; + } + + return s_state.transition_current_texture.get(); +} + +bool FullscreenUI::CompileTransitionPipelines() +{ + const RenderAPI render_api = g_gpu_device->GetRenderAPI(); + const ShaderGen shadergen(render_api, ShaderGen::GetShaderLanguageForAPI(render_api), false, false); + GPUSwapChain* const swap_chain = g_gpu_device->GetMainSwapChain(); + + Error error; + std::unique_ptr vs = g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GetLanguage(), + shadergen.GeneratePassthroughVertexShader(), &error); + std::unique_ptr fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), + shadergen.GenerateFadeFragmentShader(), &error); + if (!vs || !fs) + { + ERROR_LOG("Failed to compile transition shaders: {}", error.GetDescription()); + return false; + } + GL_OBJECT_NAME(vs, "Transition Vertex Shader"); + GL_OBJECT_NAME(fs, "Transition Fragment Shader"); + + GPUPipeline::GraphicsConfig plconfig; + GPUBackend::SetScreenQuadInputLayout(plconfig); + plconfig.layout = GPUPipeline::Layout::MultiTextureAndPushConstants; + plconfig.rasterization = GPUPipeline::RasterizationState::GetNoCullState(); + plconfig.depth = GPUPipeline::DepthState::GetNoTestsState(); + plconfig.blend = GPUPipeline::BlendState::GetNoBlendingState(); + plconfig.SetTargetFormats(swap_chain ? swap_chain->GetFormat() : GPUTexture::Format::RGBA8); + plconfig.samples = 1; + plconfig.per_sample_shading = false; + plconfig.render_pass_flags = GPUPipeline::NoRenderPassFlags; + plconfig.vertex_shader = vs.get(); + plconfig.geometry_shader = nullptr; + plconfig.fragment_shader = fs.get(); + + s_state.transition_blend_pipeline = g_gpu_device->CreatePipeline(plconfig, &error); + if (!s_state.transition_blend_pipeline) + { + ERROR_LOG("Failed to create transition blend pipeline: {}", error.GetDescription()); + return false; + } + + return true; +} + +void FullscreenUI::RenderTransitionBlend(GPUSwapChain* swap_chain) +{ + GPUTexture* const curr = s_state.transition_current_texture.get(); + DebugAssert(curr); + + if (s_state.transition_state == TransitionState::Starting) + { + // copy current frame + if (!g_gpu_device->ResizeTexture(&s_state.transition_prev_texture, curr->GetWidth(), curr->GetHeight(), + GPUTexture::Type::RenderTarget, curr->GetFormat(), GPUTexture::Flags::None, false)) + { + ERROR_LOG("Failed to allocate {}x{} texture for transition, cancelling.", curr->GetWidth(), curr->GetHeight()); + s_state.transition_state = TransitionState::Inactive; + return; + } + + g_gpu_device->CopyTextureRegion(s_state.transition_prev_texture.get(), 0, 0, 0, 0, curr, 0, 0, 0, 0, + curr->GetWidth(), curr->GetHeight()); + + s_state.transition_state = TransitionState::Active; + } + + const float transition_alpha = s_state.transition_remaining_time / s_state.transition_total_time; + const float uniforms[2] = {1.0f - transition_alpha, transition_alpha}; + g_gpu_device->PushUniformBuffer(uniforms, sizeof(uniforms)); + g_gpu_device->SetPipeline(s_state.transition_blend_pipeline.get()); + g_gpu_device->SetViewportAndScissor(0, 0, swap_chain->GetPostRotatedWidth(), swap_chain->GetPostRotatedHeight()); + g_gpu_device->SetTextureSampler(0, curr, g_gpu_device->GetNearestSampler()); + g_gpu_device->SetTextureSampler(1, s_state.transition_prev_texture.get(), g_gpu_device->GetNearestSampler()); + + const GSVector2i size = swap_chain->GetSizeVec(); + const GSVector2i postrotated_size = swap_chain->GetPostRotatedSizeVec(); + const GSVector4 uv_rect = g_gpu_device->UsesLowerLeftOrigin() ? GSVector4::cxpr(0.0f, 1.0f, 1.0f, 0.0f) : + GSVector4::cxpr(0.0f, 0.0f, 1.0f, 1.0f); + GPUPresenter::DrawScreenQuad(GSVector4i::loadh(size), uv_rect, size, postrotated_size, DisplayRotation::Normal, + swap_chain->GetPreRotation()); +} + +void FullscreenUI::UpdateTransitionState() +{ + if (s_state.transition_state == TransitionState::Inactive) + return; + + // this callback will exist after starting if a second transition gets queued + if (s_state.transition_start_callback) + { + std::move(s_state.transition_start_callback)(); + s_state.transition_start_callback = {}; + } + + s_state.transition_remaining_time -= ImGui::GetIO().DeltaTime; + if (s_state.transition_remaining_time <= 0.0f) + { + // At 1080p we're only talking 2MB of VRAM, 16MB at 4K.. saves reallocating it on the next transition. + // g_gpu_device->RecycleTexture(std::move(s_state.transition_current_texture)); + // g_gpu_device->RecycleTexture(std::move(s_state.transition_prev_texture)); + s_state.transition_state = TransitionState::Inactive; + } +} + bool FullscreenUI::UpdateLayoutScale() { #ifndef __ANDROID__ diff --git a/src/core/fullscreenui_widgets.h b/src/core/fullscreenui_widgets.h index e3f7f5284..b56a7be96 100644 --- a/src/core/fullscreenui_widgets.h +++ b/src/core/fullscreenui_widgets.h @@ -25,6 +25,7 @@ class Image; class GPUTexture; +class GPUSwapChain; class ProgressCallback; namespace FullscreenUI { @@ -236,6 +237,29 @@ bool InvalidateCachedTexture(std::string_view path); bool TextureNeedsSVGDimensions(std::string_view path); void UploadAsyncTextures(); +/// Screen transitions. +inline constexpr float SHORT_TRANSITION_TIME = 0.08f; +inline constexpr float DEFAULT_TRANSITION_TIME = 0.15f; +inline constexpr float LONG_TRANSITION_TIME = 0.3f; + +enum class TransitionState : u8 +{ + Inactive, + Starting, + Active, +}; + +using TransitionStartCallback = std::function; +void BeginTransition(TransitionStartCallback func, float time = DEFAULT_TRANSITION_TIME); +void BeginTransition(float time, TransitionStartCallback func); +void CancelTransition(); +bool IsTransitionActive(); +TransitionState GetTransitionState(); +GPUTexture* GetTransitionRenderTexture(GPUSwapChain* swap_chain); +void RenderTransitionBlend(GPUSwapChain* swap_chain); +void UpdateTransitionState(); + +/// Layout helpers. void BeginLayout(); void EndLayout();