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  }