You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
158 lines
2.9 KiB
Go
158 lines
2.9 KiB
Go
// 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 ctx.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
|
|
}
|
|
}
|