gui: free textures after frame is drawn

Fixes crashes when GUI attempts to destroy textures still in use during the current frame
This commit is contained in:
bookmist 2025-06-07 16:27:20 +03:00
parent 28a0d4492c
commit 20df386acb
9 changed files with 57 additions and 57 deletions

View File

@ -20,6 +20,8 @@
#include <imgui.h>
#include <cstdint>
#include <mutex>
#include <vector>
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<ImTextureID> 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;

View File

@ -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);
}

View File

@ -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<std::mutex> 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<ImGui_VulkanState &>(*state), texture);
}
break;
}
default:
LOG_ERROR("Missing ImGui init for backend {}.", static_cast<int>(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<ImGui_VulkanState &>(*state), texture);
default:
LOG_ERROR("Missing ImGui init for backend {}.", static_cast<int>(state->renderer->current_backend));
}
std::lock_guard<std::mutex> 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);

View File

@ -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);

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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);