diff --git a/app/headless/headless.go b/app/headless/headless.go index 892e5a4f..ee6682cf 100644 --- a/app/headless/headless.go +++ b/app/headless/headless.go @@ -5,27 +5,22 @@ package headless import ( - "fmt" "image" "runtime" - "gioui.org/app/internal/glimpl" - "gioui.org/app/internal/srgb" "gioui.org/gpu" - "gioui.org/gpu/gl" "gioui.org/op" ) // Window is a headless window. type Window struct { size image.Point - ctx context - fbo *srgb.SRGBFBO + ctx backend gpu *gpu.GPU } -type context interface { - Functions() *glimpl.Functions +type backend interface { + Screenshot(width, height int, pixels []byte) error Backend() (gpu.Backend, error) MakeCurrent() error ReleaseCurrent() @@ -34,7 +29,7 @@ type context interface { // NewWindow creates a new headless window. func NewWindow(width, height int) (*Window, error) { - ctx, err := newContext() + ctx, err := newContext(width, height) if err != nil { return nil, err } @@ -43,30 +38,16 @@ func NewWindow(width, height int) (*Window, error) { ctx: ctx, } err = contextDo(ctx, func() error { - f := ctx.Functions() - fbo, err := srgb.NewSRGBFBO(f) - if err != nil { - ctx.Release() - return err - } - if err := fbo.Refresh(width, height); err != nil { - fbo.Release() - ctx.Release() - return err - } backend, err := ctx.Backend() if err != nil { - fbo.Release() ctx.Release() return err } gpu, err := gpu.New(backend) if err != nil { - fbo.Release() ctx.Release() return err } - w.fbo = fbo w.gpu = gpu return err }) @@ -83,10 +64,6 @@ func (w *Window) Release() { w.gpu.Release() w.gpu = nil } - if w.fbo != nil { - w.fbo.Release() - w.fbo = nil - } if w.ctx != nil { w.ctx.Release() w.ctx = nil @@ -109,16 +86,8 @@ func (w *Window) Frame(frame *op.Ops) { // Screenshot returns an image with the content of the window. func (w *Window) Screenshot() (*image.RGBA, error) { img := image.NewRGBA(image.Rectangle{Max: w.size}) - if len(img.Pix) != w.size.X*w.size.Y*4 { - panic("unexpected RGBA size") - } contextDo(w.ctx, func() error { - f := w.ctx.Functions() - f.ReadPixels(0, 0, w.size.X, w.size.Y, gl.RGBA, gl.UNSIGNED_BYTE, img.Pix) - if glErr := f.GetError(); glErr != gl.NO_ERROR { - return fmt.Errorf("glReadPixels failed: %d", glErr) - } - return nil + return w.ctx.Screenshot(w.size.X, w.size.Y, img.Pix) }) // Flip image in y-direction. OpenGL's origin is in the lower // left corner. @@ -134,7 +103,7 @@ func (w *Window) Screenshot() (*image.RGBA, error) { return img, nil } -func contextDo(ctx context, f func() error) error { +func contextDo(ctx backend, f func() error) error { errCh := make(chan error) go func() { runtime.LockOSThread() diff --git a/app/headless/headless_darwin.go b/app/headless/headless_darwin.go index 5d8c5ecf..46342b37 100644 --- a/app/headless/headless_darwin.go +++ b/app/headless/headless_darwin.go @@ -22,7 +22,7 @@ type nsContext struct { prepared bool } -func newContext() (context, error) { +func newGLContext() (glContext, error) { ctx := C.gio_headless_newContext() return &nsContext{ctx: ctx, c: new(glimpl.Functions)}, nil } diff --git a/app/headless/headless_egl.go b/app/headless/headless_egl.go index 8ccb70a6..10817e1a 100644 --- a/app/headless/headless_egl.go +++ b/app/headless/headless_egl.go @@ -8,6 +8,6 @@ import ( "gioui.org/app/internal/egl" ) -func newContext() (context, error) { +func newGLContext() (glContext, error) { return egl.NewContext(egl.EGL_DEFAULT_DISPLAY) } diff --git a/app/headless/headless_gl.go b/app/headless/headless_gl.go new file mode 100644 index 00000000..92654c37 --- /dev/null +++ b/app/headless/headless_gl.go @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +package headless + +import ( + "fmt" + "runtime" + + "gioui.org/app/internal/glimpl" + "gioui.org/app/internal/srgb" + "gioui.org/gpu" + "gioui.org/gpu/gl" +) + +type glContext interface { + Functions() *glimpl.Functions + Backend() (gpu.Backend, error) + MakeCurrent() error + ReleaseCurrent() + Release() +} + +type glBackend struct { + glContext + srgb *srgb.SRGBFBO +} + +func newContext(width, height int) (backend, error) { + glctx, err := newGLContext() + if err != nil { + return nil, err + } + // Create the back buffer FBO after locking the thread and making + // the context current. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if err := glctx.MakeCurrent(); err != nil { + glctx.Release() + return nil, err + } + defer glctx.ReleaseCurrent() + fbo, err := srgb.NewSRGBFBO(glctx.Functions()) + if err != nil { + glctx.Release() + return nil, err + } + if err := fbo.Refresh(width, height); err != nil { + fbo.Release() + glctx.Release() + return nil, err + } + return &glBackend{glctx, fbo}, nil +} + +func (b *glBackend) Screenshot(width, height int, pixels []byte) error { + if len(pixels) != width*height*4 { + panic("unexpected RGBA size") + } + f := b.Functions() + f.ReadPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels) + if glErr := f.GetError(); glErr != gl.NO_ERROR { + return fmt.Errorf("glReadPixels failed: %d", glErr) + } + return nil +} + +func (b *glBackend) Release() { + if b.srgb != nil { + b.srgb.Release() + b.srgb = nil + } + b.glContext.Release() +} diff --git a/app/headless/headless_js.go b/app/headless/headless_js.go index c8815ea5..4264ef5f 100644 --- a/app/headless/headless_js.go +++ b/app/headless/headless_js.go @@ -16,7 +16,7 @@ type jsContext struct { f *glimpl.Functions } -func newContext() (*jsContext, error) { +func newGLContext() (glContext, error) { version := 2 doc := js.Global().Get("document") cnv := doc.Call("createElement", "canvas")