apprt/gtk: fix SIGSEGV on ImGui GLArea re-realize

Fixes #10406

ImGui_ImplOpenGL3_Shutdown() calls imgl3wShutdown() which dlcloses the
GL library handles but does not zero out the imgl3w function pointer
table (imgl3wProcs). When a GLArea is re-realized (e.g. during
reparenting), ImGui_ImplOpenGL3_Init() calls ImGui_ImplOpenGL3_InitLoader()
which checks "if (glGetIntegerv == nullptr)". Since the stale pointers
are non-null, it skips re-initialization. The next GL call through a
dangling function pointer causes a SIGSEGV.

Fix this by introducing ImGui_ImplOpenGL3_ShutdownWithLoaderCleanup()
which calls the normal shutdown and then zeroes the imgl3wProcs table,
forcing the next Init to reload GL function pointers via imgl3wInit().

Also properly destroy the ImGui context and reset widget state in
glAreaUnrealize so re-realize starts clean. This was extra but was
probably leaking memory.
This commit is contained in:
Mitchell Hashimoto
2026-02-25 15:21:01 -08:00
parent 7935ae6649
commit 7db8346fca
3 changed files with 36 additions and 1 deletions

View File

@@ -27,4 +27,25 @@ CIMGUI_API void ImGuiStyle_ImGuiStyle(cimgui::ImGuiStyle* self)
::ImGuiStyle defaults;
*reinterpret_cast<::ImGuiStyle*>(self) = defaults;
}
// Perform the OpenGL3 backend shutdown and then zero out the imgl3w
// function pointer table. ImGui_ImplOpenGL3_Shutdown() calls
// imgl3wShutdown() which dlcloses the GL library handles but does not
// zero out the function pointers. A subsequent ImGui_ImplOpenGL3_Init()
// sees the stale (non-null) pointers, skips loader re-initialization,
// and crashes when calling through them. Zeroing the table forces the
// next Init to reload the GL function pointers via imgl3wInit().
#ifndef IMGUI_DISABLE
#if __has_include("backends/imgui_impl_opengl3.h")
#include "backends/imgui_impl_opengl3.h"
#include "backends/imgui_impl_opengl3_loader.h"
CIMGUI_API void ImGui_ImplOpenGL3_ShutdownWithLoaderCleanup()
{
::ImGui_ImplOpenGL3_Shutdown();
memset(&imgl3wProcs, 0, sizeof(imgl3wProcs));
}
#endif
#endif
}

View File

@@ -16,6 +16,10 @@ pub extern fn ImGui_ImplOpenGL3_Shutdown() callconv(.c) void;
pub extern fn ImGui_ImplOpenGL3_NewFrame() callconv(.c) void;
pub extern fn ImGui_ImplOpenGL3_RenderDrawData(draw_data: *c.ImDrawData) callconv(.c) void;
// Extension: shutdown the OpenGL3 backend and zero out the imgl3w function
// pointer table so a subsequent Init can re-initialize the loader.
pub extern fn ImGui_ImplOpenGL3_ShutdownWithLoaderCleanup() callconv(.c) void;
// Metal backend
pub extern fn ImGui_ImplMetal_Init(device: *anyopaque) callconv(.c) bool;
pub extern fn ImGui_ImplMetal_Shutdown() callconv(.c) void;

View File

@@ -257,8 +257,18 @@ pub const ImguiWidget = extern struct {
priv.tick_callback_id = 0;
}
// Unrealize is not guaranteed to be called with a current GL context,
// so we make it current for ImGui cleanup.
priv.gl_area.makeCurrent();
if (priv.gl_area.getError()) |err| {
log.warn("GLArea for Dear ImGui widget failed to realize: {s}", .{err.f_message orelse "(unknown)"});
return;
}
self.setCurrentContext() catch return;
cimgui.ImGui_ImplOpenGL3_Shutdown();
cimgui.ImGui_ImplOpenGL3_ShutdownWithLoaderCleanup();
cimgui.c.ImGui_DestroyContext(priv.ig_context);
priv.ig_context = null;
}
/// Handle a request to resize the GLArea