From 20df386acbca2fa39d5a409abe1167dddb38dd7f Mon Sep 17 00:00:00 2001 From: bookmist Date: Sat, 7 Jun 2025 16:27:20 +0300 Subject: [PATCH] gui: free textures after frame is drawn Fixes crashes when GUI attempts to destroy textures still in use during the current frame --- vita3k/gui/include/gui/imgui_impl_sdl_state.h | 11 ++- vita3k/gui/src/gui.cpp | 6 +- vita3k/gui/src/imgui_impl_sdl.cpp | 73 +++++++++---------- vita3k/gui/src/information_bar.cpp | 2 +- vita3k/gui/src/live_area.cpp | 2 +- vita3k/gui/src/settings.cpp | 2 +- vita3k/gui/src/themes.cpp | 11 ++- vita3k/gui/src/trophy_collection.cpp | 4 +- vita3k/gui/src/user_management.cpp | 3 +- 9 files changed, 57 insertions(+), 57 deletions(-) diff --git a/vita3k/gui/include/gui/imgui_impl_sdl_state.h b/vita3k/gui/include/gui/imgui_impl_sdl_state.h index 2564d55ed..15963c3f2 100644 --- a/vita3k/gui/include/gui/imgui_impl_sdl_state.h +++ b/vita3k/gui/include/gui/imgui_impl_sdl_state.h @@ -20,6 +20,8 @@ #include #include +#include +#include struct SDL_Window; struct SDL_Cursor; @@ -42,6 +44,9 @@ struct ImGui_State { bool is_typing = false; bool do_clear_screen = true; + std::mutex textures_to_free_mutex; + std::vector textures_to_free; + ImGui_State() = default; virtual ~ImGui_State() = default; @@ -55,13 +60,11 @@ public: ImGui_Texture() = default; ImGui_Texture(ImGui_State *new_state, void *data, int width, int height); ImGui_Texture(ImGui_Texture &&texture) noexcept; - - void init(ImGui_State *new_state, ImTextureID texture); - void init(ImGui_State *new_state, void *data, int width, int height); + ImGui_Texture(const ImGui_Texture &) = delete; operator bool() const; operator ImTextureID() const; - bool operator==(const ImGui_Texture &texture); + bool operator==(const ImGui_Texture &texture) const; ImGui_Texture &operator=(ImGui_Texture &&texture) noexcept; ImGui_Texture &operator=(const ImGui_Texture &texture) = delete; diff --git a/vita3k/gui/src/gui.cpp b/vita3k/gui/src/gui.cpp index 3373d8bd5..0f38b891d 100644 --- a/vita3k/gui/src/gui.cpp +++ b/vita3k/gui/src/gui.cpp @@ -352,7 +352,7 @@ static IconData load_app_icon(GuiState &gui, EmuEnvState &emuenv, const std::str void init_app_icon(GuiState &gui, EmuEnvState &emuenv, const std::string &app_path) { IconData data = load_app_icon(gui, emuenv, app_path); if (data.data) { - gui.app_selector.user_apps_icon[app_path].init(gui.imgui_state.get(), data.data.get(), data.width, data.height); + gui.app_selector.user_apps_icon[app_path] = ImGui_Texture(gui.imgui_state.get(), data.data.get(), data.width, data.height); } } @@ -364,7 +364,7 @@ void IconAsyncLoader::commit(GuiState &gui) { for (const auto &pair : icon_data) { if (pair.second.data) { - gui.app_selector.user_apps_icon[pair.first].init(gui.imgui_state.get(), pair.second.data.get(), pair.second.width, pair.second.height); + gui.app_selector.user_apps_icon[pair.first] = ImGui_Texture(gui.imgui_state.get(), pair.second.data.get(), pair.second.width, pair.second.height); } } @@ -435,7 +435,7 @@ void init_app_background(GuiState &gui, EmuEnvState &emuenv, const std::string & LOG_ERROR("Invalid background for application {} [{}].", title, app_path); return; } - gui.apps_background[app_path].init(gui.imgui_state.get(), data, width, height); + gui.apps_background[app_path] = ImGui_Texture(gui.imgui_state.get(), data, width, height); stbi_image_free(data); } diff --git a/vita3k/gui/src/imgui_impl_sdl.cpp b/vita3k/gui/src/imgui_impl_sdl.cpp index 531de81ea..1ecb45297 100644 --- a/vita3k/gui/src/imgui_impl_sdl.cpp +++ b/vita3k/gui/src/imgui_impl_sdl.cpp @@ -427,7 +427,31 @@ static void ImGui_ImplSDL3_UpdateGamepads(ImGui_State *state) { #undef MAP_ANALOG } +static void ImGui_ImplSdl_FreeTextures(ImGui_State *state) { + std::lock_guard lock(state->textures_to_free_mutex); + switch (state->renderer->current_backend) { + case renderer::Backend::OpenGL: { + for (auto texture : state->textures_to_free) { + ImGui_ImplSdlGL3_DeleteTexture(texture); + } + break; + } + case renderer::Backend::Vulkan: { + for (auto texture : state->textures_to_free) { + ImGui_ImplSdlVulkan_DeleteTexture(dynamic_cast(*state), texture); + } + break; + } + + default: + LOG_ERROR("Missing ImGui init for backend {}.", static_cast(state->renderer->current_backend)); + } + state->textures_to_free.clear(); +} + IMGUI_API void ImGui_ImplSdl_NewFrame(ImGui_State *state) { + // Free textures, marked as deleted on previous frame. + ImGui_ImplSdl_FreeTextures(state); ImGuiIO &io = ImGui::GetIO(); // Setup display size (every frame to accommodate for window resizing) @@ -493,16 +517,8 @@ IMGUI_API ImTextureID ImGui_ImplSdl_CreateTexture(ImGui_State *state, void *data } IMGUI_API void ImGui_ImplSdl_DeleteTexture(ImGui_State *state, ImTextureID texture) { - switch (state->renderer->current_backend) { - case renderer::Backend::OpenGL: - return ImGui_ImplSdlGL3_DeleteTexture(texture); - - case renderer::Backend::Vulkan: - return ImGui_ImplSdlVulkan_DeleteTexture(dynamic_cast(*state), texture); - - default: - LOG_ERROR("Missing ImGui init for backend {}.", static_cast(state->renderer->current_backend)); - } + std::lock_guard lock(state->textures_to_free_mutex); + state->textures_to_free.push_back(texture); } // Use if you want to reset your rendering device without losing ImGui state. @@ -532,17 +548,10 @@ IMGUI_API bool ImGui_ImplSdl_CreateDeviceObjects(ImGui_State *state) { } } -void ImGui_Texture::init(ImGui_State *new_state, ImTextureID texture) { - assert(!texture_id); - +ImGui_Texture::ImGui_Texture(ImGui_State *new_state, void *data, int width, int height) { state = new_state; - texture_id = texture; + texture_id = ImGui_ImplSdl_CreateTexture(new_state, data, width, height); } - -void ImGui_Texture::init(ImGui_State *new_state, void *data, int width, int height) { - init(new_state, ImGui_ImplSdl_CreateTexture(new_state, data, width, height)); -} - ImGui_Texture::operator bool() const { return texture_id != nullptr; } @@ -551,31 +560,21 @@ ImGui_Texture::operator ImTextureID() const { return texture_id; } -bool ImGui_Texture::operator==(const ImGui_Texture &texture) { +bool ImGui_Texture::operator==(const ImGui_Texture &texture) const { return texture_id == texture.texture_id; } +ImGui_Texture::ImGui_Texture(ImGui_Texture &&texture) noexcept { + std::swap(state, texture.state); + std::swap(texture_id, texture.texture_id); +} + ImGui_Texture &ImGui_Texture::operator=(ImGui_Texture &&texture) noexcept { - this->state = texture.state; - this->texture_id = texture.texture_id; - - texture.state = nullptr; - texture.texture_id = nullptr; - + std::swap(state, texture.state); + std::swap(texture_id, texture.texture_id); return *this; } -ImGui_Texture::ImGui_Texture(ImGui_State *new_state, void *data, int width, int height) { - init(new_state, data, width, height); -} - -ImGui_Texture::ImGui_Texture(ImGui_Texture &&texture) noexcept - : state(texture.state) - , texture_id(texture.texture_id) { - texture.state = nullptr; - texture.texture_id = nullptr; -} - ImGui_Texture::~ImGui_Texture() { if (texture_id) ImGui_ImplSdl_DeleteTexture(state, texture_id); diff --git a/vita3k/gui/src/information_bar.cpp b/vita3k/gui/src/information_bar.cpp index cf1583639..b5b8b9990 100644 --- a/vita3k/gui/src/information_bar.cpp +++ b/vita3k/gui/src/information_bar.cpp @@ -114,7 +114,7 @@ static bool init_notice_icon(GuiState &gui, EmuEnvState &emuenv, const fs::path LOG_ERROR("Invalid icon for notice id: {} in path {}.", info.id, content_path); return false; } - gui.notice_info_icon[info.time].init(gui.imgui_state.get(), data, width, height); + gui.notice_info_icon[info.time] = ImGui_Texture(gui.imgui_state.get(), data, width, height); stbi_image_free(data); return gui.notice_info_icon.contains(info.time); diff --git a/vita3k/gui/src/live_area.cpp b/vita3k/gui/src/live_area.cpp index 1f1e4dcbc..15b0b2bdb 100644 --- a/vita3k/gui/src/live_area.cpp +++ b/vita3k/gui/src/live_area.cpp @@ -268,7 +268,7 @@ void init_live_area(GuiState &gui, EmuEnvState &emuenv, const std::string &app_p continue; } - gui.live_area_contents[app_path][contents.first].init(gui.imgui_state.get(), data, width, height); + gui.live_area_contents[app_path][contents.first] = ImGui_Texture(gui.imgui_state.get(), data, width, height); stbi_image_free(data); } diff --git a/vita3k/gui/src/settings.cpp b/vita3k/gui/src/settings.cpp index 2dfede43f..a5e9ad78b 100644 --- a/vita3k/gui/src/settings.cpp +++ b/vita3k/gui/src/settings.cpp @@ -184,7 +184,7 @@ static void get_themes_list(GuiState &gui, EmuEnvState &emuenv) { continue; } - gui.themes_preview[content_id][preview_type].init(gui.imgui_state.get(), data, width, height); + gui.themes_preview[content_id][preview_type] = ImGui_Texture(gui.imgui_state.get(), data, width, height); stbi_image_free(data); } } diff --git a/vita3k/gui/src/themes.cpp b/vita3k/gui/src/themes.cpp index cedf4682a..2201246ce 100644 --- a/vita3k/gui/src/themes.cpp +++ b/vita3k/gui/src/themes.cpp @@ -56,8 +56,7 @@ bool init_user_background(GuiState &gui, EmuEnvState &emuenv, const std::string return false; } - gui.user_backgrounds[background_path] = {}; - gui.user_backgrounds[background_path].init(gui.imgui_state.get(), data, width, height); + gui.user_backgrounds[background_path] = ImGui_Texture(gui.imgui_state.get(), data, width, height); stbi_image_free(data); fclose(f); @@ -178,7 +177,7 @@ void init_theme_start_background(GuiState &gui, EmuEnvState &emuenv, const std:: return; } - gui.start_background.init(gui.imgui_state.get(), data, width, height); + gui.start_background = ImGui_Texture(gui.imgui_state.get(), data, width, height); stbi_image_free(data); } @@ -203,7 +202,7 @@ bool init_user_start_background(GuiState &gui, const std::string &image_path) { return false; } - gui.start_background.init(gui.imgui_state.get(), data, width, height); + gui.start_background = ImGui_Texture(gui.imgui_state.get(), data, width, height); stbi_image_free(data); fclose(f); @@ -299,7 +298,7 @@ bool init_theme(GuiState &gui, EmuEnvState &emuenv, const std::string &content_i LOG_ERROR("Invalid notice icon for content id: {}.", content_id); continue; } - gui.theme_information_bar_notice[type].init(gui.imgui_state.get(), data, width, height); + gui.theme_information_bar_notice[type] = ImGui_Texture(gui.imgui_state.get(), data, width, height); stbi_image_free(data); } } @@ -345,7 +344,7 @@ bool init_theme(GuiState &gui, EmuEnvState &emuenv, const std::string &content_i continue; } - gui.app_selector.sys_apps_icon[title_id].init(gui.imgui_state.get(), data, width, height); + gui.app_selector.sys_apps_icon[title_id] = ImGui_Texture(gui.imgui_state.get(), data, width, height); stbi_image_free(data); } diff --git a/vita3k/gui/src/trophy_collection.cpp b/vita3k/gui/src/trophy_collection.cpp index 828386dcb..a48cc3133 100644 --- a/vita3k/gui/src/trophy_collection.cpp +++ b/vita3k/gui/src/trophy_collection.cpp @@ -258,7 +258,7 @@ void init_trophy_collection(GuiState &gui, EmuEnvState &emuenv) { continue; } - gui.trophy_np_com_id_list_icons[np_com_id][group.first].init(gui.imgui_state.get(), data, width, height); + gui.trophy_np_com_id_list_icons[np_com_id][group.first] = ImGui_Texture(gui.imgui_state.get(), data, width, height); stbi_image_free(data); } } @@ -346,7 +346,7 @@ static void get_trophy_list(GuiState &gui, EmuEnvState &emuenv, const std::strin continue; } - gui.trophy_list[trophy_id].init(gui.imgui_state.get(), data, width, height); + gui.trophy_list[trophy_id] = ImGui_Texture(gui.imgui_state.get(), data, width, height); stbi_image_free(data); auto &common = gui.lang.common.main; diff --git a/vita3k/gui/src/user_management.cpp b/vita3k/gui/src/user_management.cpp index 27bb58234..9777ced4e 100644 --- a/vita3k/gui/src/user_management.cpp +++ b/vita3k/gui/src/user_management.cpp @@ -74,8 +74,7 @@ static bool init_avatar(GuiState &gui, EmuEnvState &emuenv, const std::string &u return false; } - gui.users_avatar[user_id] = {}; - gui.users_avatar[user_id].init(gui.imgui_state.get(), data, width, height); + gui.users_avatar[user_id] = ImGui_Texture(gui.imgui_state.get(), data, width, height); stbi_image_free(data); fclose(f);