Skip to content

fix(gles): keep surface-created EGL context alive past Surface.Destroy - #362

Open
admin-else wants to merge 2 commits into
gogpu:mainfrom
admin-else:fix/gles-adapter-context-ownership
Open

admin-else wants to merge 2 commits into
gogpu:mainfrom
admin-else:fix/gles-adapter-context-ownership

Conversation

@admin-else

Copy link
Copy Markdown

Summary

On Linux GLES, a Surface that has to create its own EGL context (Wayland, or when the detected window kind does not match the real window) owned that context and freed it in Surface.Destroy. The Adapter, Device, and Queue created from that Surface share the same *AdapterContext and outlive the Surface, so closing the window freed the EGL context first and the later Device.Destroy locked a nil EGL context.

Reproduced signature:

ERROR gles: AdapterContext.Lock: nil egl context
panic: runtime error: invalid memory address or nil pointer dereference
... gles.(*Device).Destroy -> gl.(*Context).DeleteVertexArrays(0x0)

This crashes any app that closes a window under GLES when the context is Surface-owned.

Root cause

Instance.CreateSurface has two paths:

  • Path A (X11/headless): the Surface shares the Instance AdapterContext — ownsContext=false, so the Instance (released last) frees it.
  • Path B (Wayland / window-kind mismatch): the Surface created a new AdapterContext with ownsContext=true.

AdapterContext.Destroy sets eglCtx=nil and gl=nil. In Path B the Surface then destroyed the context that Device/Queue/Adapter still referenced. On shutdown gogpu destroys the Surface (window close), then the Device, then the Instance, so the Device hit a dead context: Lock() logged nil egl context, returned the nil gl table, and DeleteVertexArrays dereferenced nil.

Fix

CreateSurface now adopts the Path B context on the Instance. The Instance is released after the Device and Surface, so the shared context lives as long as everything that uses it. Path B now returns ownsContext=false; the Instance.ctx field is guarded by a mutex.

This mirrors the Windows/X11 model where the context is owned by the Instance, not the Surface.

Testing

  • go test ./core/ ./hal/... — pass
  • go test -tags integration ./hal/gles/ — pass, including a new regression test TestSurfaceDoesNotDestroyAdoptedContext that destroys a Surface sharing an adopted context, then the Device, then the Instance (the exact shutdown order that used to panic).
  • go build ./... — pass

Related

Follow-up to the GLES Linux AdapterContext work (FEAT-GLES-003, #332).

SloppaElse added 2 commits September 30, 2026 17:30
On Linux the AdapterContext that backs an EGL context may be created by the
Surface instead of the Instance (Wayland, or when the detected window kind
differs from the real window's kind, e.g. an X11 window under a Wayland session).
In that case Surface.SetFail owned the context and Surface.Destroy freed it.

The Adapter, Device and Queue created from that Surface hold the same
*AdapterContext and outlive the Surface. Closing the window freed the EGL
context first; the later Device.Destroy locked a nil EGL context ("gles:
AdapterContext.Lock: nil egl context") and dereferenced the nil *gl.Context in
DeleteVertexArrays, crashing during app shutdown:

  panic: runtime error: invalid memory address or nil pointer dereference
  ... gles.(*Device).Destroy -> gl.(*Context).DeleteVertexArrays(0x0)

Adopt ownership on the Instance when CreateSurface has to create the context.
The Instance is released after the Device and Surface, so the shared context now
lives as long as everything that uses it. Region Path B now always returns
ownsContext=false.

Adds an integration regression test that destroys a Surface sharing an adopted
context, then destroys the Device, then the Instance.
@admin-else
admin-else requested a review from kolkov as a code owner September 30, 2026 15:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant