~eliasnaur/gio

ref: 284659d3eac9 gio/app/headless/headless.go -rw-r--r-- 2.8 KiB
284659d3Elias Naur gpu/shaders: [compute] remove unused command from kernel4 2 months ago
                                                                                
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
// 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
}