apprt/gtk: make GL context current in unrealize

Fixes #6872

This commit explicitly acquires the GL context in the `unrealize`
signal handler of the GTK Surface prior to cleaning up GPU resources.

A GLArea only guarantees that the associated GdkContext is current for
the `render` signal (see the docs[1]). This is why our OpenGL renderer
"defers" various operations such as resize, font grid changing, etc.
(see the `deferred_`-prefix fields in `renderer/OpenGL.zig`).
However, we missed a spot.

The `gtk-widget::unrealize` signal is emitted when the widget is
destroyed, and it is the last chance we have to clean up our GPU
resources. But it is not guaranteed that the GL context is current at
that point, and we weren't making it current. On the NGL GTK renderer,
this was freeing GPU resources we didn't own.

As best I can understand, the old GL renderer only ever used a handful
of GL resources that were early in the ID space, so by coincidence we
were probably freeing nothing and everything was fine. But with the new
NGL renderer uses a LOT more GL resources (make a few splits and the ID
space is already in the thousands, from GTK!), so we were freeing real
resources that we didn't own, which caused rendering issues. :)

I suspect the above also resulted in VRAM memory leaks (which would be
RAM memory leaks for unified memory GPUs). This potentially relates to
#5491.

The fix is to explicitly make the GL context current in the `unrealize`
handler.

[1]: https://docs.gtk.org/gtk4/method.GLArea.make_current.html
pull/6877/head
Mitchell Hashimoto 2025-03-22 14:12:40 -07:00
parent 747c43ffa0
commit 36f841ee80
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
1 changed files with 21 additions and 2 deletions

View File

@ -1377,12 +1377,31 @@ fn gtkRealize(gl_area: *gtk.GLArea, self: *Surface) callconv(.C) void {
/// This is called when the underlying OpenGL resources must be released.
/// This is usually due to the OpenGL area changing GDK surfaces.
fn gtkUnrealize(_: *gtk.GLArea, self: *Surface) callconv(.C) void {
fn gtkUnrealize(gl_area: *gtk.GLArea, self: *Surface) callconv(.C) void {
log.debug("gl surface unrealized", .{});
self.core_surface.renderer.displayUnrealized();
// See gtkRealize for why we do this here.
self.im_context.as(gtk.IMContext).setClientWidget(null);
// There is no guarantee that our GLArea context is current
// when unrealize is emitted, so we need to make it current.
gl_area.makeCurrent();
if (gl_area.getError()) |err| {
// I don't know a scenario this can happen, but it means
// we probably leaked memory because displayUnrealized
// below frees resources that aren't specifically OpenGL
// related. I didn't make the OpenGL renderer handle this
// scenario because I don't know if its even possible
// under valid circumstances, so let's log.
log.warn(
"gl_area_make_current failed in unrealize msg={s}",
.{err.f_message orelse "(no message)"},
);
log.warn("OpenGL resources and memory likely leaked", .{});
return;
} else {
self.core_surface.renderer.displayUnrealized();
}
}
/// render signal