app/headless: remove OpenGL assumptions

To prepare package headless for multiple backends, refactor the common headless
driver to no longer assume an OpenGL context. Instead, introduce a headless
backend type and the OpenGL implementation, glBackend.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2020-02-15 18:59:27 +01:00
parent 94fdc26cb5
commit b34216c124
5 changed files with 82 additions and 40 deletions
+6 -37
View File
@@ -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()
+1 -1
View File
@@ -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
}
+1 -1
View File
@@ -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)
}
+73
View File
@@ -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()
}
+1 -1
View File
@@ -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")