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