From 7bb7a1407f759f025ed963ea9e00867e6cdbb345 Mon Sep 17 00:00:00 2001 From: Joe Julian Date: Tue, 21 Apr 2026 06:31:35 -0700 Subject: [PATCH 1/2] app: lock explicitly before refreshing contexts Signed-off-by: Joe Julian --- app/gl_ios.go | 3 --- app/gl_macos.go | 2 -- app/os.go | 2 ++ app/window.go | 12 ++++++------ 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/app/gl_ios.go b/app/gl_ios.go index 31db2541..34d85d1c 100644 --- a/app/gl_ios.go +++ b/app/gl_ios.go @@ -115,9 +115,6 @@ func (c *context) Unlock() { } func (c *context) Refresh() error { - if C.gio_makeCurrent(c.ctx) == 0 { - return errors.New("[EAGLContext setCurrentContext] failed") - } if !c.init { c.init = true c.frameBuffer = c.c.CreateFramebuffer() diff --git a/app/gl_macos.go b/app/gl_macos.go index 041bcb32..fe77ea0a 100644 --- a/app/gl_macos.go +++ b/app/gl_macos.go @@ -111,8 +111,6 @@ func (c *glContext) Unlock() { } func (c *glContext) Refresh() error { - c.Lock() - defer c.Unlock() C.gio_updateContext(c.ctx) return nil } diff --git a/app/os.go b/app/os.go index 6da6d34b..cf337985 100644 --- a/app/os.go +++ b/app/os.go @@ -165,6 +165,8 @@ type frameEvent struct { Sync bool } +// The caller must hold the context lock while using API, Refresh, +// RenderTarget, or Present. type context interface { API() gpu.API RenderTarget() (gpu.RenderTarget, error) diff --git a/app/window.go b/app/window.go index 977ab6a1..0e5d04d2 100644 --- a/app/window.go +++ b/app/window.go @@ -149,6 +149,12 @@ func (w *Window) validateAndProcess(size image.Point, sync bool, frame *op.Ops, sync = true } } + if w.ctx != nil { + if err := w.ctx.Lock(); err != nil { + w.destroyGPU() + return err + } + } if sync && w.ctx != nil { if err := w.ctx.Refresh(); err != nil { if errors.Is(err, errOutOfDate) { @@ -163,12 +169,6 @@ func (w *Window) validateAndProcess(size image.Point, sync bool, frame *op.Ops, return err } } - if w.ctx != nil { - if err := w.ctx.Lock(); err != nil { - w.destroyGPU() - return err - } - } if w.gpu == nil && !w.nocontext { gpu, err := gpu.New(w.ctx.API()) if err != nil { -- 2.52.0 From d9b0c8c1c569fae01f26302984bfaab83cae2737 Mon Sep 17 00:00:00 2001 From: Joe Julian Date: Tue, 21 Apr 2026 06:32:24 -0700 Subject: [PATCH 2/2] app: avoid relocking contexts every frame Signed-off-by: Joe Julian --- app/window.go | 57 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/app/window.go b/app/window.go index 0e5d04d2..39eb6e48 100644 --- a/app/window.go +++ b/app/window.go @@ -46,6 +46,10 @@ type Window struct { ctx context gpu gpu.GPU + // ctxNeedsLock tracks whether the rendering context must be made + // current again before the next GPU operation. Refresh paths, surface + // loss, and explicit unlocks all invalidate the current binding. + ctxNeedsLock bool // timer tracks the delayed invalidate goroutine. timer struct { // quit is shuts down the goroutine. @@ -146,14 +150,13 @@ func (w *Window) validateAndProcess(size image.Point, sync bool, frame *op.Ops, if err != nil { return err } + w.ctxNeedsLock = true sync = true } } - if w.ctx != nil { - if err := w.ctx.Lock(); err != nil { - w.destroyGPU() - return err - } + if err := w.lockContext(); err != nil { + w.destroyGPU() + return err } if sync && w.ctx != nil { if err := w.ctx.Refresh(); err != nil { @@ -168,11 +171,16 @@ func (w *Window) validateAndProcess(size image.Point, sync bool, frame *op.Ops, } return err } + w.unlockContext() + if err := w.lockContext(); err != nil { + w.destroyGPU() + return err + } } if w.gpu == nil && !w.nocontext { gpu, err := gpu.New(w.ctx.API()) if err != nil { - w.ctx.Unlock() + w.unlockContext() w.destroyGPU() return err } @@ -180,7 +188,7 @@ func (w *Window) validateAndProcess(size image.Point, sync bool, frame *op.Ops, } if w.gpu != nil { if err := w.frame(frame, size); err != nil { - w.ctx.Unlock() + w.unlockContext() if errors.Is(err, errOutOfDate) { // GPU surface needs refreshing. sync = true @@ -200,7 +208,6 @@ func (w *Window) validateAndProcess(size image.Point, sync bool, frame *op.Ops, var err error if w.gpu != nil { err = w.ctx.Present() - w.ctx.Unlock() } return err } @@ -503,16 +510,37 @@ func (c *callbacks) ActionAt(p f32.Point) (system.Action, bool) { return c.w.queue.ActionAt(p) } +func (w *Window) lockContext() error { + if w.ctx == nil || !w.ctxNeedsLock { + return nil + } + if err := w.ctx.Lock(); err != nil { + return err + } + w.ctxNeedsLock = false + return nil +} + +func (w *Window) unlockContext() { + if w.ctx == nil || w.ctxNeedsLock { + return + } + w.ctx.Unlock() + w.ctxNeedsLock = true +} + func (w *Window) destroyGPU() { if w.gpu != nil { - w.ctx.Lock() - w.gpu.Release() - w.ctx.Unlock() + if err := w.lockContext(); err == nil { + w.gpu.Release() + w.unlockContext() + } w.gpu = nil } if w.ctx != nil { w.ctx.Release() w.ctx = nil + w.ctxNeedsLock = false } } @@ -655,10 +683,11 @@ func (w *Window) processEvent(e event.Event) bool { w.coalesced.destroy = &e2 case ViewEvent: if !e2.Valid() && w.gpu != nil { - w.ctx.Lock() - w.gpu.Release() + if err := w.lockContext(); err == nil { + w.gpu.Release() + w.unlockContext() + } w.gpu = nil - w.ctx.Unlock() } w.coalesced.view = &e2 case ConfigEvent: -- 2.52.0