From b9aa9e59f7e74ccbbdf6fb222b20422625932218 Mon Sep 17 00:00:00 2001 From: Joe Julian Date: Mon, 20 Apr 2026 16:51:31 -0700 Subject: [PATCH] app: lock explicitly before refreshing contexts Signed-off-by: Joe Julian --- app/gl_ios.go | 3 -- app/os.go | 1 + app/window.go | 12 +++---- app/window_test.go | 85 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 92 insertions(+), 9 deletions(-) create mode 100644 app/window_test.go 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/os.go b/app/os.go index 6da6d34b..78ffa4cf 100644 --- a/app/os.go +++ b/app/os.go @@ -169,6 +169,7 @@ type context interface { API() gpu.API RenderTarget() (gpu.RenderTarget, error) Present() error + // Refresh assumes the context is already locked and current. Refresh() error Release() Lock() 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 { diff --git a/app/window_test.go b/app/window_test.go new file mode 100644 index 00000000..ca049d6c --- /dev/null +++ b/app/window_test.go @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +package app + +import ( + "image" + "image/color" + "testing" + + "gioui.org/gpu" + "gioui.org/op" +) + +func TestValidateAndProcessLocksBeforeRefresh(t *testing.T) { + ctx := &testContext{} + w := &Window{ + ctx: ctx, + gpu: &testGPU{}, + } + + if err := w.validateAndProcess(image.Pt(320, 240), true, new(op.Ops), nil); err != nil { + t.Fatalf("validateAndProcess returned error: %v", err) + } + + want := []string{"lock", "refresh", "render-target", "present", "unlock"} + if got := ctx.ops; !equalStringSlices(got, want) { + t.Fatalf("unexpected call order:\n got %v\n want %v", got, want) + } +} + +type testContext struct { + ops []string +} + +func (c *testContext) API() gpu.API { + return nil +} + +func (c *testContext) RenderTarget() (gpu.RenderTarget, error) { + c.ops = append(c.ops, "render-target") + return gpu.OpenGLRenderTarget{}, nil +} + +func (c *testContext) Present() error { + c.ops = append(c.ops, "present") + return nil +} + +func (c *testContext) Refresh() error { + c.ops = append(c.ops, "refresh") + return nil +} + +func (c *testContext) Release() {} + +func (c *testContext) Lock() error { + c.ops = append(c.ops, "lock") + return nil +} + +func (c *testContext) Unlock() { + c.ops = append(c.ops, "unlock") +} + +type testGPU struct{} + +func (g *testGPU) Release() {} + +func (g *testGPU) Clear(color.NRGBA) {} + +func (g *testGPU) Frame(*op.Ops, gpu.RenderTarget, image.Point) error { + return nil +} + +func equalStringSlices(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +}