gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/gpu/cpu.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package gpu
     4  
     5  import (
     6  	"unsafe"
     7  
     8  	"gioui.org/cpu"
     9  )
    10  
    11  // This file contains code specific to running compute shaders on the CPU.
    12  
    13  // dispatcher dispatches CPU compute programs across multiple goroutines.
    14  type dispatcher struct {
    15  	// done is notified when a worker completes its work slice.
    16  	done chan struct{}
    17  	// work receives work slice indices. It is closed when the dispatcher is released.
    18  	work chan work
    19  	// dispatch receives compute jobs, which is then split among workers.
    20  	dispatch chan dispatch
    21  	// sync receives notification when a Sync completes.
    22  	sync chan struct{}
    23  }
    24  
    25  type work struct {
    26  	ctx   *cpu.DispatchContext
    27  	index int
    28  }
    29  
    30  type dispatch struct {
    31  	_type   jobType
    32  	program *cpu.ProgramInfo
    33  	descSet unsafe.Pointer
    34  	x, y, z int
    35  }
    36  
    37  type jobType uint8
    38  
    39  const (
    40  	jobDispatch jobType = iota
    41  	jobBarrier
    42  	jobSync
    43  )
    44  
    45  func newDispatcher(workers int) *dispatcher {
    46  	d := &dispatcher{
    47  		work: make(chan work, workers),
    48  		done: make(chan struct{}, workers),
    49  		// Leave some room to avoid blocking calls to Dispatch.
    50  		dispatch: make(chan dispatch, 20),
    51  		sync:     make(chan struct{}),
    52  	}
    53  	for i := 0; i < workers; i++ {
    54  		go d.worker()
    55  	}
    56  	go d.dispatcher()
    57  	return d
    58  }
    59  
    60  func (d *dispatcher) dispatcher() {
    61  	defer close(d.work)
    62  	var free []*cpu.DispatchContext
    63  	defer func() {
    64  		for _, ctx := range free {
    65  			ctx.Free()
    66  		}
    67  	}()
    68  	var used []*cpu.DispatchContext
    69  	for job := range d.dispatch {
    70  		switch job._type {
    71  		case jobDispatch:
    72  			if len(free) == 0 {
    73  				free = append(free, cpu.NewDispatchContext())
    74  			}
    75  			ctx := free[len(free)-1]
    76  			free = free[:len(free)-1]
    77  			used = append(used, ctx)
    78  			ctx.Prepare(cap(d.work), job.program, job.descSet, job.x, job.y, job.z)
    79  			for i := 0; i < cap(d.work); i++ {
    80  				d.work <- work{
    81  					ctx:   ctx,
    82  					index: i,
    83  				}
    84  			}
    85  		case jobBarrier:
    86  			// Wait for all outstanding dispatches to complete.
    87  			for i := 0; i < len(used)*cap(d.work); i++ {
    88  				<-d.done
    89  			}
    90  			free = append(free, used...)
    91  			used = used[:0]
    92  		case jobSync:
    93  			d.sync <- struct{}{}
    94  		}
    95  	}
    96  }
    97  
    98  func (d *dispatcher) worker() {
    99  	thread := cpu.NewThreadContext()
   100  	defer thread.Free()
   101  	for w := range d.work {
   102  		w.ctx.Dispatch(w.index, thread)
   103  		d.done <- struct{}{}
   104  	}
   105  }
   106  
   107  func (d *dispatcher) Barrier() {
   108  	d.dispatch <- dispatch{_type: jobBarrier}
   109  }
   110  
   111  func (d *dispatcher) Sync() {
   112  	d.dispatch <- dispatch{_type: jobSync}
   113  	<-d.sync
   114  }
   115  
   116  func (d *dispatcher) Dispatch(program *cpu.ProgramInfo, descSet unsafe.Pointer, x, y, z int) {
   117  	d.dispatch <- dispatch{
   118  		_type:   jobDispatch,
   119  		program: program,
   120  		descSet: descSet,
   121  		x:       x,
   122  		y:       y,
   123  		z:       z,
   124  	}
   125  }
   126  
   127  func (d *dispatcher) Stop() {
   128  	close(d.dispatch)
   129  }