github.com/gop9/olt@v0.0.0-20200202132135-d956aad50b08/gio/app/loop.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package app 4 5 import ( 6 "image" 7 "runtime" 8 9 "github.com/gop9/olt/gio/app/internal/gpu" 10 "github.com/gop9/olt/gio/app/internal/window" 11 "github.com/gop9/olt/gio/op" 12 ) 13 14 type renderLoop struct { 15 summary string 16 drawing bool 17 err error 18 19 frames chan frame 20 results chan frameResult 21 refresh chan struct{} 22 refreshErr chan error 23 ack chan struct{} 24 stop chan struct{} 25 stopped chan struct{} 26 } 27 28 type frame struct { 29 collectStats bool 30 viewport image.Point 31 ops *op.Ops 32 } 33 34 type frameResult struct { 35 summary string 36 err error 37 } 38 39 func newLoop(ctx window.Context) (*renderLoop, error) { 40 l := &renderLoop{ 41 frames: make(chan frame), 42 results: make(chan frameResult), 43 refresh: make(chan struct{}), 44 refreshErr: make(chan error), 45 // Ack is buffered so GPU commands can be issued after 46 // ack'ing the frame. 47 ack: make(chan struct{}, 1), 48 stop: make(chan struct{}), 49 stopped: make(chan struct{}), 50 } 51 if err := l.renderLoop(ctx); err != nil { 52 return nil, err 53 } 54 return l, nil 55 } 56 57 func (l *renderLoop) renderLoop(glctx window.Context) error { 58 // GL Operations must happen on a single OS thread, so 59 // pass initialization result through a channel. 60 initErr := make(chan error) 61 go func() { 62 defer close(l.stopped) 63 runtime.LockOSThread() 64 // Don't UnlockOSThread to avoid reuse by the Go runtime. 65 66 if err := glctx.MakeCurrent(); err != nil { 67 initErr <- err 68 return 69 } 70 g, err := gpu.New(glctx.Functions()) 71 if err != nil { 72 initErr <- err 73 return 74 } 75 defer glctx.Release() 76 initErr <- nil 77 loop: 78 for { 79 select { 80 case <-l.refresh: 81 l.refreshErr <- glctx.MakeCurrent() 82 case frame := <-l.frames: 83 glctx.Lock() 84 g.Collect(frame.collectStats, frame.viewport, frame.ops) 85 // Signal that we're done with the frame ops. 86 l.ack <- struct{}{} 87 g.Frame(frame.collectStats, frame.viewport) 88 var res frameResult 89 res.err = glctx.Present() 90 res.summary = g.EndFrame(frame.collectStats) 91 glctx.Unlock() 92 l.results <- res 93 case <-l.stop: 94 break loop 95 } 96 } 97 }() 98 return <-initErr 99 } 100 101 func (l *renderLoop) Release() { 102 // Flush error. 103 l.Flush() 104 close(l.stop) 105 <-l.stopped 106 l.stop = nil 107 } 108 109 func (l *renderLoop) Flush() error { 110 if l.drawing { 111 st := <-l.results 112 l.setErr(st.err) 113 if st.summary != "" { 114 l.summary = st.summary 115 } 116 l.drawing = false 117 } 118 return l.err 119 } 120 121 func (l *renderLoop) Summary() string { 122 return l.summary 123 } 124 125 func (l *renderLoop) Refresh() { 126 if l.err != nil { 127 return 128 } 129 // Make sure any pending frame is complete. 130 l.Flush() 131 l.refresh <- struct{}{} 132 l.setErr(<-l.refreshErr) 133 } 134 135 // Draw initiates a draw of a frame. It returns a channel 136 // than signals when the frame is no longer being accessed. 137 func (l *renderLoop) Draw(profile bool, viewport image.Point, frameOps *op.Ops) <-chan struct{} { 138 if l.err != nil { 139 l.ack <- struct{}{} 140 return l.ack 141 } 142 l.Flush() 143 l.frames <- frame{profile, viewport, frameOps} 144 l.drawing = true 145 return l.ack 146 } 147 148 func (l *renderLoop) setErr(err error) { 149 if l.err == nil { 150 l.err = err 151 } 152 }