forked from joejulian/gio
84b586ae6c
Gio UI may be overlaid on top of custom graphics such as in the glfw example. That will only work if Gio doesn't clear the screen (to white). Signed-off-by: Elias Naur <mail@eliasnaur.com>
148 lines
2.8 KiB
Go
148 lines
2.8 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
// Package headless implements headless windows for rendering
|
|
// an operation list to an image.
|
|
package headless
|
|
|
|
import (
|
|
"image"
|
|
"image/color"
|
|
"runtime"
|
|
|
|
"gioui.org/gpu"
|
|
"gioui.org/gpu/backend"
|
|
"gioui.org/op"
|
|
)
|
|
|
|
// Window is a headless window.
|
|
type Window struct {
|
|
size image.Point
|
|
ctx context
|
|
backend backend.Device
|
|
gpu gpu.GPU
|
|
fboTex backend.Texture
|
|
fbo backend.Framebuffer
|
|
}
|
|
|
|
type context interface {
|
|
Backend() (backend.Device, error)
|
|
MakeCurrent() error
|
|
ReleaseCurrent()
|
|
Release()
|
|
}
|
|
|
|
// 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 := ctx.Backend()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dev.Viewport(0, 0, width, height)
|
|
fboTex, err := dev.NewTexture(
|
|
backend.TextureFormatSRGB,
|
|
width, height,
|
|
backend.FilterNearest, backend.FilterNearest,
|
|
backend.BufferBindingFramebuffer,
|
|
)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
const depthBits = 16
|
|
fbo, err := dev.NewFramebuffer(fboTex, depthBits)
|
|
if err != nil {
|
|
fboTex.Release()
|
|
return err
|
|
}
|
|
dev.BindFramebuffer(fbo)
|
|
gp, err := gpu.New(dev)
|
|
if err != nil {
|
|
fbo.Release()
|
|
fboTex.Release()
|
|
return err
|
|
}
|
|
w.fboTex = fboTex
|
|
w.fbo = fbo
|
|
w.gpu = gp
|
|
w.backend = 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.fbo != nil {
|
|
w.fbo.Release()
|
|
w.fbo = nil
|
|
}
|
|
if w.fboTex != nil {
|
|
w.fboTex.Release()
|
|
w.fboTex = nil
|
|
}
|
|
if w.gpu != nil {
|
|
w.gpu.Release()
|
|
w.gpu = nil
|
|
}
|
|
return nil
|
|
})
|
|
if w.ctx != nil {
|
|
w.ctx.Release()
|
|
w.ctx = nil
|
|
}
|
|
}
|
|
|
|
// Frame replace 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{A: 0xff, R: 0xff, G: 0xff, B: 0xff})
|
|
w.gpu.Collect(w.size, frame)
|
|
return w.gpu.Frame()
|
|
})
|
|
}
|
|
|
|
// 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})
|
|
err := contextDo(w.ctx, func() error {
|
|
return w.fbo.ReadPixels(
|
|
image.Rectangle{
|
|
Max: image.Point{X: w.size.X, Y: w.size.Y},
|
|
}, img.Pix)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return img, nil
|
|
}
|
|
|
|
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
|
|
}
|