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  }