/
headless.go
157 lines (144 loc) · 3.02 KB
/
headless.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// 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"
"github.com/Seikaijyu/gio/gpu"
"github.com/Seikaijyu/gio/gpu/internal/driver"
"github.com/Seikaijyu/gio/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 err
}
// 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() {
if err := ctx.MakeCurrent(); err != nil {
errCh <- err
return
}
err := f()
ctx.ReleaseCurrent()
errCh <- err
}()
return <-errCh
}