Files
gio/gpu/headless/headless.go
T
Pierre Curto af63c089f6 gpu/headless: make Screenshot take an input image to tranfer into
When extracting headless.Window's content via screenshots,
it can be useful to keep reusing the same image for output,
as well as specify which area of the Window is to be
extracted.
The updated Screenshot method does this by using the supplied
image.

API change: users must pass an existing image to Window.Screenshot.

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2021-12-15 16:05:19 +01:00

161 lines
3.0 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
// Package headless implements headless windows for rendering
// an operation list to an image.
package headless
import (
"errors"
"image"
"image/color"
"runtime"
"gioui.org/gpu"
"gioui.org/gpu/internal/driver"
"gioui.org/op"
)
// Window is a headless window.
type Window struct {
size image.Point
ctx context
dev driver.Device
gpu gpu.GPU
fboTex driver.Texture
}
type context interface {
API() gpu.API
MakeCurrent() error
ReleaseCurrent()
Release()
}
var (
newContextPrimary func() (context, error)
newContextFallback func() (context, error)
)
func newContext() (context, error) {
funcs := []func() (context, error){newContextPrimary, newContextFallback}
var firstErr error
for _, f := range funcs {
if f == nil {
continue
}
c, err := f()
if err != nil {
if firstErr == nil {
firstErr = err
}
continue
}
return c, nil
}
if firstErr != nil {
return nil, firstErr
}
return nil, errors.New("headless: no available GPU backends")
}
// NewWindow creates a new headless window.
func NewWindow(width, height int) (*Window, error) {
ctx, err := newContext()
if err != nil {
return nil, err
}
w := &Window{
size: image.Point{X: width, Y: height},
ctx: ctx,
}
err = contextDo(ctx, func() error {
dev, err := driver.NewDevice(ctx.API())
if err != nil {
return err
}
fboTex, err := dev.NewTexture(
driver.TextureFormatSRGBA,
width, height,
driver.FilterNearest, driver.FilterNearest,
driver.BufferBindingFramebuffer,
)
if err != nil {
dev.Release()
return nil
}
// Note that the gpu takes ownership of dev.
gp, err := gpu.NewWithDevice(dev)
if err != nil {
fboTex.Release()
return err
}
w.fboTex = fboTex
w.gpu = gp
w.dev = dev
return err
})
if err != nil {
ctx.Release()
return nil, err
}
return w, nil
}
// Release resources associated with the window.
func (w *Window) Release() {
contextDo(w.ctx, func() error {
if w.fboTex != nil {
w.fboTex.Release()
w.fboTex = nil
}
if w.gpu != nil {
w.gpu.Release()
w.gpu = nil
}
// w.dev is owned and freed by w.gpu.
w.dev = nil
return nil
})
if w.ctx != nil {
w.ctx.Release()
w.ctx = nil
}
}
// Size returns the window size.
func (w *Window) Size() image.Point {
return w.size
}
// Frame replaces the window content and state with the
// operation list.
func (w *Window) Frame(frame *op.Ops) error {
return contextDo(w.ctx, func() error {
w.gpu.Clear(color.NRGBA{})
return w.gpu.Frame(frame, w.fboTex, w.size)
})
}
// Screenshot transfers the Window content at origin img.Rect.Min to img.
func (w *Window) Screenshot(img *image.RGBA) error {
return contextDo(w.ctx, func() error {
return driver.DownloadImage(w.dev, w.fboTex, img)
})
}
func contextDo(ctx context, f func() error) error {
errCh := make(chan error)
go func() {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if err := ctx.MakeCurrent(); err != nil {
errCh <- err
return
}
err := f()
ctx.ReleaseCurrent()
errCh <- err
}()
return <-errCh
}