mirror of
https://github.com/stenzek/duckstation.git
synced 2026-07-11 01:24:11 +02:00
ImGuiManager: Allow renderer switch without destroying context
Fixes UI getting reset when changing renderers, and works around being left in an inconsistent state if FSUI wasn't started and renderers are changed from the ingame settings menu.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<pre>DuckStation PS1 Emulator
|
||||
Copyright (C) 2019-2025 Connor McLaughlin <stenzek@gmail.com>
|
||||
Copyright (C) 2019-2026 Connor McLaughlin <stenzek@gmail.com>
|
||||
|
||||
This program is licensed under the terms of the Creative Commons<br>
|
||||
Attribution-NonCommercial-NoDerivatives International License<br>
|
||||
|
||||
+24
-26
@@ -67,7 +67,6 @@ static void DrawShaderBackgroundCallback(const ImDrawList* parent_list, const Im
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Resources
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
static void DestroyResources();
|
||||
static GPUTexture* GetUserThemeableTexture(
|
||||
const std::string_view png_name, const std::string_view svg_name, bool* is_colorable = nullptr,
|
||||
const ImVec2& svg_size = LayoutScale(LAYOUT_HORIZONTAL_MENU_ITEM_IMAGE_SIZE, LAYOUT_HORIZONTAL_MENU_ITEM_IMAGE_SIZE));
|
||||
@@ -169,7 +168,7 @@ ALIGN_TO_CACHE_LINE static Locals s_locals;
|
||||
// Main
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void FullscreenUI::Initialize(bool preserve_state /*= false */)
|
||||
void FullscreenUI::Initialize()
|
||||
{
|
||||
// some achievement callbacks fire early while e.g. there is a load state popup blocking system init
|
||||
if (s_locals.initialized || !ImGuiManager::IsInitialized())
|
||||
@@ -178,7 +177,7 @@ void FullscreenUI::Initialize(bool preserve_state /*= false */)
|
||||
s_locals.initialized = true;
|
||||
|
||||
// in case we open the pause menu while the game is running
|
||||
if (!preserve_state && s_locals.current_main_window == MainWindowType::None && !GPUThread::HasGPUBackend() &&
|
||||
if (s_locals.current_main_window == MainWindowType::None && !GPUThread::HasGPUBackend() &&
|
||||
!GPUThread::IsGPUBackendRequested())
|
||||
{
|
||||
ReturnToMainWindow();
|
||||
@@ -455,31 +454,37 @@ void FullscreenUI::ReturnToMainWindow(float transition_time)
|
||||
});
|
||||
}
|
||||
|
||||
void FullscreenUI::Shutdown(bool preserve_fsui_state)
|
||||
void FullscreenUI::Shutdown()
|
||||
{
|
||||
if (!preserve_fsui_state)
|
||||
{
|
||||
SoundEffectManager::Shutdown();
|
||||
DestroyGPUResources();
|
||||
|
||||
s_locals.current_main_window = MainWindowType::None;
|
||||
s_locals.current_pause_submenu = PauseSubMenu::None;
|
||||
s_locals.pause_menu_was_open = false;
|
||||
s_locals.was_paused_on_quick_menu_open = false;
|
||||
SoundEffectManager::Shutdown();
|
||||
|
||||
ClearAchievementsState();
|
||||
ClearSaveStateEntryList();
|
||||
ClearSettingsState();
|
||||
ClearGameListState();
|
||||
s_locals.current_time_string = {};
|
||||
s_locals.current_time = 0;
|
||||
}
|
||||
s_locals.current_main_window = MainWindowType::None;
|
||||
s_locals.current_pause_submenu = PauseSubMenu::None;
|
||||
s_locals.pause_menu_was_open = false;
|
||||
s_locals.was_paused_on_quick_menu_open = false;
|
||||
|
||||
DestroyResources();
|
||||
ClearAchievementsState();
|
||||
ClearSettingsState();
|
||||
ClearGameListState();
|
||||
s_locals.current_time_string = {};
|
||||
s_locals.current_time = 0;
|
||||
|
||||
s_locals.initialized = false;
|
||||
UpdateRunIdleState();
|
||||
}
|
||||
|
||||
void FullscreenUI::DestroyGPUResources()
|
||||
{
|
||||
// has textures...
|
||||
ClearSaveStateEntryList();
|
||||
|
||||
s_locals.app_background_texture.reset();
|
||||
s_locals.app_background_shader.reset();
|
||||
s_locals.background_loaded = false;
|
||||
}
|
||||
|
||||
void FullscreenUI::Render()
|
||||
{
|
||||
if (!s_locals.initialized)
|
||||
@@ -535,13 +540,6 @@ void FullscreenUI::Render()
|
||||
UpdateTransitionState();
|
||||
}
|
||||
|
||||
void FullscreenUI::DestroyResources()
|
||||
{
|
||||
s_locals.app_background_texture.reset();
|
||||
s_locals.app_background_shader.reset();
|
||||
s_locals.background_loaded = false;
|
||||
}
|
||||
|
||||
GPUTexture* FullscreenUI::GetUserThemeableTexture(const std::string_view png_name, const std::string_view svg_name,
|
||||
bool* is_colorable, const ImVec2& svg_size)
|
||||
{
|
||||
|
||||
@@ -14,12 +14,13 @@
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
class Error;
|
||||
class SmallStringBase;
|
||||
|
||||
struct GPUSettings;
|
||||
|
||||
namespace FullscreenUI {
|
||||
void Initialize(bool preserve_state = false);
|
||||
void Initialize();
|
||||
bool IsInitialized();
|
||||
bool HasActiveWindow();
|
||||
void CheckForConfigChanges(const GPUSettings& old_settings);
|
||||
@@ -28,7 +29,10 @@ void OnSystemPaused();
|
||||
void OnSystemResumed();
|
||||
void OnSystemDestroyed();
|
||||
|
||||
void Shutdown(bool preserve_state);
|
||||
void Shutdown();
|
||||
|
||||
void DestroyGPUResources();
|
||||
|
||||
void Render();
|
||||
void InvalidateCoverCache(std::string path = {});
|
||||
|
||||
|
||||
@@ -401,8 +401,6 @@ struct WidgetsState
|
||||
u32 loading_screen_valid_samples = 0;
|
||||
bool loading_screen_open = false;
|
||||
|
||||
ImGuiInputSource resource_input_source = ImGuiInputSource_None;
|
||||
|
||||
std::array<std::pair<Timer::Value, s32>, LOADING_PROGRESS_SAMPLE_COUNT> loading_screen_samples;
|
||||
};
|
||||
|
||||
@@ -418,30 +416,14 @@ void FullscreenUI::SetFont(ImFont* ui_font)
|
||||
UIStyle.Font = ui_font;
|
||||
}
|
||||
|
||||
bool FullscreenUI::InitializeWidgets(bool preserve_fsui_state, Error* error)
|
||||
bool FullscreenUI::InitializeWidgets(Error* error)
|
||||
{
|
||||
{
|
||||
std::unique_lock lock(s_state.shared_state_mutex);
|
||||
|
||||
if (!(s_state.placeholder_texture = LoadTexture("images/placeholder.png")))
|
||||
{
|
||||
Error::SetStringView(error, "Failed to load placeholder.png");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!CompileTransitionPipelines(error))
|
||||
{
|
||||
ShutdownWidgets(preserve_fsui_state);
|
||||
if (!CreateWidgetsGPUResources(error))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!preserve_fsui_state)
|
||||
{
|
||||
s_state.focus_reset_queued = FocusResetType::ViewChanged;
|
||||
s_state.close_button_state = CloseButtonState::None;
|
||||
ResetMenuButtonFrame();
|
||||
}
|
||||
s_state.focus_reset_queued = FocusResetType::ViewChanged;
|
||||
s_state.close_button_state = CloseButtonState::None;
|
||||
ResetMenuButtonFrame();
|
||||
|
||||
UpdateWidgetsSettings();
|
||||
|
||||
@@ -449,24 +431,14 @@ bool FullscreenUI::InitializeWidgets(bool preserve_fsui_state, Error* error)
|
||||
return true;
|
||||
}
|
||||
|
||||
void FullscreenUI::ShutdownWidgets(bool preserve_fsui_state)
|
||||
void FullscreenUI::ShutdownWidgets()
|
||||
{
|
||||
std::unique_lock lock(s_state.shared_state_mutex);
|
||||
DestroyWidgetsGPUResources();
|
||||
|
||||
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.texture_upload_queue.clear();
|
||||
s_state.placeholder_texture.reset();
|
||||
UIStyle.Font = nullptr;
|
||||
|
||||
s_state.texture_cache.Clear();
|
||||
|
||||
s_state.has_initialized = false;
|
||||
|
||||
if (!preserve_fsui_state)
|
||||
{
|
||||
std::unique_lock lock(s_state.shared_state_mutex);
|
||||
s_state.texture_upload_queue.clear();
|
||||
|
||||
s_state.transition_state = TransitionState::Inactive;
|
||||
if (s_state.transition_start_callback) [[unlikely]]
|
||||
WARNING_LOG("Shutting down FullscreenUI while a transition callback is still set.");
|
||||
@@ -484,6 +456,10 @@ void FullscreenUI::ShutdownWidgets(bool preserve_fsui_state)
|
||||
s_state.file_selector_dialog.ClearState();
|
||||
}
|
||||
|
||||
s_state.has_initialized = false;
|
||||
|
||||
UIStyle.Font = nullptr;
|
||||
|
||||
UpdateLoadingScreenRunIdle();
|
||||
UpdateNotificationsRunIdle();
|
||||
}
|
||||
@@ -512,6 +488,31 @@ void FullscreenUI::UpdateWidgetsSettings()
|
||||
ImGuiManager::SetGamepadFaceButtonsSwapped(swap_face_buttons);
|
||||
}
|
||||
|
||||
bool FullscreenUI::CreateWidgetsGPUResources(Error* error)
|
||||
{
|
||||
if (!(s_state.placeholder_texture = LoadTexture("images/placeholder.png")))
|
||||
{
|
||||
Error::SetStringView(error, "Failed to load placeholder.png");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CompileTransitionPipelines(error))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FullscreenUI::DestroyWidgetsGPUResources()
|
||||
{
|
||||
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.placeholder_texture.reset();
|
||||
|
||||
s_state.texture_cache.Clear();
|
||||
}
|
||||
|
||||
const std::shared_ptr<GPUTexture>& FullscreenUI::GetPlaceholderTexture()
|
||||
{
|
||||
return s_state.placeholder_texture;
|
||||
|
||||
@@ -218,14 +218,17 @@ ImRect CenterImage(const ImRect& fit_rect, const GPUTexture* texture);
|
||||
ImRect FitImage(const ImVec2& fit_size, const ImVec2& image_size);
|
||||
|
||||
/// Initializes, setting up any state.
|
||||
bool InitializeWidgets(bool preserve_fsui_state, Error* error);
|
||||
bool InitializeWidgets(Error* error);
|
||||
|
||||
/// Shuts down, clearing all state.
|
||||
void ShutdownWidgets(bool preserve_fsui_state);
|
||||
void ShutdownWidgets();
|
||||
|
||||
/// Loads settings from the settings interface.
|
||||
void UpdateWidgetsSettings();
|
||||
|
||||
bool CreateWidgetsGPUResources(Error* error);
|
||||
void DestroyWidgetsGPUResources();
|
||||
|
||||
std::span<const char* const> GetThemeNames();
|
||||
std::span<const char* const> GetThemeDisplayNames();
|
||||
std::vector<std::string_view> GetLocalizedThemeDisplayNames();
|
||||
|
||||
+34
-15
@@ -74,8 +74,8 @@ static void WakeGPUThread();
|
||||
static void WakeGPUThreadIfSleeping();
|
||||
static bool SleepGPUThread(bool allow_sleep);
|
||||
|
||||
static bool CreateDeviceOnThread(RenderAPI api, bool fullscreen, bool preserve_fsui_state, Error* error);
|
||||
static void DestroyDeviceOnThread(bool preserve_fsui_state);
|
||||
static bool CreateDeviceOnThread(RenderAPI api, bool fullscreen, bool preserve_imgui_on_failure, Error* error);
|
||||
static void DestroyDeviceOnThread(bool preserve_imgui_state);
|
||||
static void ResizeDisplayWindowOnThread(u32 width, u32 height, float scale, float refresh_rate);
|
||||
static void UpdateDisplayWindowOnThread(bool fullscreen, bool allow_exclusive_fullscreen);
|
||||
static void DisplayWindowResizedOnThread();
|
||||
@@ -632,7 +632,7 @@ bool GPUThread::IsFullscreen()
|
||||
return s_state.requested_fullscreen;
|
||||
}
|
||||
|
||||
bool GPUThread::CreateDeviceOnThread(RenderAPI api, bool fullscreen, bool preserve_fsui_state, Error* error)
|
||||
bool GPUThread::CreateDeviceOnThread(RenderAPI api, bool fullscreen, bool preserve_imgui_on_failure, Error* error)
|
||||
{
|
||||
DebugAssert(!g_gpu_device);
|
||||
|
||||
@@ -710,11 +710,18 @@ bool GPUThread::CreateDeviceOnThread(RenderAPI api, bool fullscreen, bool preser
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ImGuiManager::Initialize(preserve_fsui_state, &create_error))
|
||||
// might be initialized already if we're recreating the device
|
||||
const bool imgui_was_initialized = ImGuiManager::IsInitialized();
|
||||
if (!(imgui_was_initialized ? ImGuiManager::CreateGPUResources(&create_error) :
|
||||
ImGuiManager::Initialize(&create_error)))
|
||||
{
|
||||
ERROR_LOG("Failed to initialize ImGuiManager: {}", create_error.GetDescription());
|
||||
Error::SetStringFmt(error, "Failed to initialize ImGuiManager: {}", create_error.GetDescription());
|
||||
ImGuiManager::Shutdown(preserve_fsui_state);
|
||||
if (preserve_imgui_on_failure)
|
||||
ImGuiManager::DestroyGPUResources();
|
||||
else
|
||||
ImGuiManager::Shutdown();
|
||||
|
||||
g_gpu_device->Destroy();
|
||||
g_gpu_device.reset();
|
||||
if (wi.has_value())
|
||||
@@ -722,8 +729,16 @@ bool GPUThread::CreateDeviceOnThread(RenderAPI api, bool fullscreen, bool preser
|
||||
return false;
|
||||
}
|
||||
|
||||
// still need to update the window size
|
||||
if (imgui_was_initialized && g_gpu_device->HasMainSwapChain())
|
||||
{
|
||||
const GPUSwapChain* sc = g_gpu_device->GetMainSwapChain();
|
||||
ImGuiManager::WindowResized(sc->GetWindowInfo().surface_format, static_cast<float>(sc->GetWidth()),
|
||||
static_cast<float>(sc->GetHeight()));
|
||||
}
|
||||
|
||||
if (s_state.requested_fullscreen_ui)
|
||||
FullscreenUI::Initialize(preserve_fsui_state);
|
||||
FullscreenUI::Initialize();
|
||||
|
||||
if (const GPUSwapChain* swap_chain = g_gpu_device->GetMainSwapChain())
|
||||
s_state.render_window_info = swap_chain->GetWindowInfo();
|
||||
@@ -745,7 +760,7 @@ bool GPUThread::CreateDeviceOnThread(RenderAPI api, bool fullscreen, bool preser
|
||||
return true;
|
||||
}
|
||||
|
||||
void GPUThread::DestroyDeviceOnThread(bool preserve_fsui_state)
|
||||
void GPUThread::DestroyDeviceOnThread(bool preserve_imgui_state)
|
||||
{
|
||||
if (!g_gpu_device)
|
||||
return;
|
||||
@@ -753,8 +768,15 @@ void GPUThread::DestroyDeviceOnThread(bool preserve_fsui_state)
|
||||
// Presenter should be gone by this point
|
||||
Assert(!s_state.gpu_presenter);
|
||||
|
||||
FullscreenUI::Shutdown(preserve_fsui_state);
|
||||
ImGuiManager::Shutdown(preserve_fsui_state);
|
||||
if (preserve_imgui_state)
|
||||
{
|
||||
DEV_LOG("Preserving ImGui state on destroy");
|
||||
ImGuiManager::DestroyGPUResources();
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGuiManager::Shutdown();
|
||||
}
|
||||
|
||||
INFO_LOG("Destroying {} GPU device...", GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()));
|
||||
g_gpu_device->Destroy();
|
||||
@@ -884,14 +906,12 @@ void GPUThread::ReconfigureOnThread(GPUThreadReconfigureCommand* cmd)
|
||||
Settings::GetRenderAPIForRenderer(s_state.requested_renderer.value_or(g_gpu_settings.gpu_renderer));
|
||||
if (cmd->force_recreate_device || !GPUDevice::IsSameRenderAPI(current_api, expected_api))
|
||||
{
|
||||
const bool preserve_fsui_state = FullscreenUI::IsInitialized();
|
||||
|
||||
Timer timer;
|
||||
DestroyGPUPresenterOnThread();
|
||||
DestroyDeviceOnThread(preserve_fsui_state);
|
||||
DestroyDeviceOnThread(true);
|
||||
|
||||
Error local_error;
|
||||
if (!CreateDeviceOnThread(expected_api, cmd->fullscreen, preserve_fsui_state, &local_error))
|
||||
if (!CreateDeviceOnThread(expected_api, cmd->fullscreen, current_api != RenderAPI::None, &local_error))
|
||||
{
|
||||
Host::AddIconOSDMessage(
|
||||
OSDMessageType::Error, "DeviceSwitchFailed", ICON_FA_PAINT_ROLLER,
|
||||
@@ -900,8 +920,7 @@ void GPUThread::ReconfigureOnThread(GPUThreadReconfigureCommand* cmd)
|
||||
local_error.GetDescription()));
|
||||
|
||||
Host::ReleaseRenderWindow();
|
||||
if (current_api == RenderAPI::None ||
|
||||
!CreateDeviceOnThread(current_api, cmd->fullscreen, preserve_fsui_state, &local_error))
|
||||
if (current_api == RenderAPI::None || !CreateDeviceOnThread(current_api, cmd->fullscreen, false, &local_error))
|
||||
{
|
||||
if (cmd->error_ptr)
|
||||
*cmd->error_ptr = local_error;
|
||||
|
||||
@@ -193,7 +193,7 @@ void ImGuiManager::SetTextFontOrder(const TextFontOrder& order)
|
||||
ReloadFontDataIfActive();
|
||||
}
|
||||
|
||||
bool ImGuiManager::Initialize(bool preserve_fsui_state, Error* error)
|
||||
bool ImGuiManager::Initialize(Error* error)
|
||||
{
|
||||
if (!LoadFontData(error))
|
||||
{
|
||||
@@ -232,11 +232,8 @@ bool ImGuiManager::Initialize(bool preserve_fsui_state, Error* error)
|
||||
FullscreenUI::UpdateTheme();
|
||||
FullscreenUI::UpdateLayoutScale();
|
||||
|
||||
if (!CreateFontAtlas(error) || !CompilePipelines(error) ||
|
||||
!FullscreenUI::InitializeWidgets(preserve_fsui_state, error))
|
||||
{
|
||||
if (!CreateFontAtlas(error) || !CompilePipelines(error) || !FullscreenUI::InitializeWidgets(error))
|
||||
return false;
|
||||
}
|
||||
|
||||
NewFrame(Timer::GetCurrentValue());
|
||||
|
||||
@@ -244,11 +241,12 @@ bool ImGuiManager::Initialize(bool preserve_fsui_state, Error* error)
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImGuiManager::Shutdown(bool preserve_fsui_state)
|
||||
void ImGuiManager::Shutdown()
|
||||
{
|
||||
DestroySoftwareCursorTextures();
|
||||
|
||||
FullscreenUI::ShutdownWidgets(preserve_fsui_state);
|
||||
FullscreenUI::Shutdown();
|
||||
FullscreenUI::ShutdownWidgets();
|
||||
|
||||
s_state.text_font = nullptr;
|
||||
s_state.fixed_font = nullptr;
|
||||
@@ -272,6 +270,46 @@ void ImGuiManager::Shutdown(bool preserve_fsui_state)
|
||||
}
|
||||
}
|
||||
|
||||
bool ImGuiManager::CreateGPUResources(Error* error)
|
||||
{
|
||||
if (!CompilePipelines(error))
|
||||
return false;
|
||||
|
||||
if (!FullscreenUI::CreateWidgetsGPUResources(error))
|
||||
return false;
|
||||
|
||||
CreateSoftwareCursorTextures();
|
||||
|
||||
// Font textures get created on render.
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImGuiManager::DestroyGPUResources()
|
||||
{
|
||||
DestroySoftwareCursorTextures();
|
||||
|
||||
FullscreenUI::DestroyGPUResources();
|
||||
FullscreenUI::DestroyWidgetsGPUResources();
|
||||
|
||||
s_state.imgui_pipeline.reset();
|
||||
|
||||
if (s_state.imgui_context)
|
||||
{
|
||||
for (ImTextureData* tex : s_state.imgui_context->IO.Fonts->TexList)
|
||||
{
|
||||
if (tex->Status == ImTextureStatus_Destroyed)
|
||||
continue;
|
||||
|
||||
delete std::exchange(tex->TexID, nullptr);
|
||||
|
||||
if (tex->Status == ImTextureStatus_WantDestroy)
|
||||
tex->Status = ImTextureStatus_Destroyed;
|
||||
else
|
||||
tex->Status = ImTextureStatus_WantCreate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiContext* ImGuiManager::GetMainContext()
|
||||
{
|
||||
return s_state.imgui_context;
|
||||
|
||||
@@ -91,10 +91,13 @@ TextFontOrder GetDefaultTextFontOrder();
|
||||
void SetTextFontOrder(const TextFontOrder& order);
|
||||
|
||||
/// Initializes ImGui, creates fonts, etc.
|
||||
bool Initialize(bool preserve_fsui_state, Error* error);
|
||||
bool Initialize(Error* error);
|
||||
|
||||
/// Frees all ImGui resources.
|
||||
void Shutdown(bool preserve_fsui_state);
|
||||
void Shutdown();
|
||||
|
||||
bool CreateGPUResources(Error* error);
|
||||
void DestroyGPUResources();
|
||||
|
||||
/// Returns main ImGui context.
|
||||
ImGuiContext* GetMainContext();
|
||||
|
||||
Reference in New Issue
Block a user