Compare commits

..

2 Commits

Author SHA1 Message Date
Joe Julian fb19cd6043 app: avoid relocking contexts every frame
Signed-off-by: Joe Julian <me@joejulian.name>
2026-04-20 16:52:33 -07:00
Joe Julian b9aa9e59f7 app: lock explicitly before refreshing contexts
Signed-off-by: Joe Julian <me@joejulian.name>
2026-04-20 16:51:31 -07:00
3 changed files with 110 additions and 2 deletions
+2
View File
@@ -111,6 +111,8 @@ func (c *glContext) Unlock() {
} }
func (c *glContext) Refresh() error { func (c *glContext) Refresh() error {
c.Lock()
defer c.Unlock()
C.gio_updateContext(c.ctx) C.gio_updateContext(c.ctx)
return nil return nil
} }
+1 -2
View File
@@ -165,12 +165,11 @@ type frameEvent struct {
Sync bool Sync bool
} }
// The caller must hold the context lock while using API, Refresh,
// RenderTarget, or Present.
type context interface { type context interface {
API() gpu.API API() gpu.API
RenderTarget() (gpu.RenderTarget, error) RenderTarget() (gpu.RenderTarget, error)
Present() error Present() error
// Refresh assumes the context is already locked and current.
Refresh() error Refresh() error
Release() Release()
Lock() error Lock() error
+107
View File
@@ -0,0 +1,107 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"image"
"image/color"
"testing"
"gioui.org/gpu"
"gioui.org/op"
)
func TestValidateAndProcessRelocksAfterRefresh(t *testing.T) {
ctx := &testContext{}
w := &Window{
ctx: ctx,
gpu: &testGPU{},
ctxNeedsLock: false,
}
if err := w.validateAndProcess(image.Pt(320, 240), true, new(op.Ops), nil); err != nil {
t.Fatalf("validateAndProcess returned error: %v", err)
}
want := []string{"refresh", "lock", "render-target", "present"}
if got := ctx.ops; !equalStringSlices(got, want) {
t.Fatalf("unexpected call order:\n got %v\n want %v", got, want)
}
if w.ctxNeedsLock {
t.Fatalf("context should remain current after a successful frame")
}
}
func TestValidateAndProcessSkipsRedundantRelock(t *testing.T) {
ctx := &testContext{}
w := &Window{
ctx: ctx,
gpu: &testGPU{},
ctxNeedsLock: false,
}
if err := w.validateAndProcess(image.Pt(320, 240), false, new(op.Ops), nil); err != nil {
t.Fatalf("validateAndProcess returned error: %v", err)
}
want := []string{"render-target", "present"}
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
}