~eliasnaur/gio

gio/app/loop.go -rw-r--r-- 2.9 KiB
4c3de5f5Pierre Curto app: support horizontal scrolling on Windows and Linux 3 days 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
148
149
150
151
152
153
154
155
156
157
// SPDX-License-Identifier: Unlicense OR MIT

package app

import (
	"image"
	"runtime"

	"gioui.org/app/internal/window"
	"gioui.org/gpu"
	"gioui.org/op"
)

type renderLoop struct {
	summary string
	drawing bool
	err     error

	frames     chan frame
	results    chan frameResult
	refresh    chan struct{}
	refreshErr chan error
	ack        chan struct{}
	stop       chan struct{}
	stopped    chan struct{}
}

type frame struct {
	viewport image.Point
	ops      *op.Ops
}

type frameResult struct {
	profile string
	err     error
}

func newLoop(ctx window.Context) (*renderLoop, error) {
	l := &renderLoop{
		frames:     make(chan frame),
		results:    make(chan frameResult),
		refresh:    make(chan struct{}),
		refreshErr: make(chan error),
		// Ack is buffered so GPU commands can be issued after
		// ack'ing the frame.
		ack:     make(chan struct{}, 1),
		stop:    make(chan struct{}),
		stopped: make(chan struct{}),
	}
	if err := l.renderLoop(ctx); err != nil {
		return nil, err
	}
	return l, nil
}

func (l *renderLoop) renderLoop(ctx window.Context) error {
	// GL Operations must happen on a single OS thread, so
	// pass initialization result through a channel.
	initErr := make(chan error)
	go func() {
		defer close(l.stopped)
		runtime.LockOSThread()
		// Don't UnlockOSThread to avoid reuse by the Go runtime.

		if err := ctx.MakeCurrent(); err != nil {
			initErr <- err
			return
		}
		b, err := ctx.Backend()
		if err != nil {
			initErr <- err
			return
		}
		g, err := gpu.New(b)
		if err != nil {
			initErr <- err
			return
		}
		defer g.Release()
		initErr <- nil
	loop:
		for {
			select {
			case <-l.refresh:
				l.refreshErr <- ctx.MakeCurrent()
			case frame := <-l.frames:
				ctx.Lock()
				g.Collect(frame.viewport, frame.ops)
				// Signal that we're done with the frame ops.
				l.ack <- struct{}{}
				g.BeginFrame()
				var res frameResult
				res.err = ctx.Present()
				g.EndFrame()
				res.profile = g.Profile()
				ctx.Unlock()
				l.results <- res
			case <-l.stop:
				break loop
			}
		}
	}()
	return <-initErr
}

func (l *renderLoop) Release() {
	// Flush error.
	l.Flush()
	close(l.stop)
	<-l.stopped
	l.stop = nil
}

func (l *renderLoop) Flush() error {
	if l.drawing {
		st := <-l.results
		l.setErr(st.err)
		if st.profile != "" {
			l.summary = st.profile
		}
		l.drawing = false
	}
	return l.err
}

func (l *renderLoop) Summary() string {
	return l.summary
}

func (l *renderLoop) Refresh() {
	if l.err != nil {
		return
	}
	// Make sure any pending frame is complete.
	l.Flush()
	l.refresh <- struct{}{}
	l.setErr(<-l.refreshErr)
}

// Draw initiates a draw of a frame. It returns a channel
// than signals when the frame is no longer being accessed.
func (l *renderLoop) Draw(viewport image.Point, frameOps *op.Ops) <-chan struct{} {
	if l.err != nil {
		l.ack <- struct{}{}
		return l.ack
	}
	l.Flush()
	l.frames <- frame{viewport, frameOps}
	l.drawing = true
	return l.ack
}

func (l *renderLoop) setErr(err error) {
	if l.err == nil {
		l.err = err
	}
}