github.com/utopiagio/gio@v0.0.8/gpu/headless/headless.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  // Package headless implements headless windows for rendering
     4  // an operation list to an image.
     5  package headless
     6  
     7  import (
     8  	"errors"
     9  	"image"
    10  	"image/color"
    11  
    12  	"github.com/utopiagio/gio/gpu"
    13  	"github.com/utopiagio/gio/gpu/internal/driver"
    14  	"github.com/utopiagio/gio/op"
    15  )
    16  
    17  // Window is a headless window.
    18  type Window struct {
    19  	size   image.Point
    20  	ctx    context
    21  	dev    driver.Device
    22  	gpu    gpu.GPU
    23  	fboTex driver.Texture
    24  }
    25  
    26  type context interface {
    27  	API() gpu.API
    28  	MakeCurrent() error
    29  	ReleaseCurrent()
    30  	Release()
    31  }
    32  
    33  var (
    34  	newContextPrimary  func() (context, error)
    35  	newContextFallback func() (context, error)
    36  )
    37  
    38  func newContext() (context, error) {
    39  	funcs := []func() (context, error){newContextPrimary, newContextFallback}
    40  	var firstErr error
    41  	for _, f := range funcs {
    42  		if f == nil {
    43  			continue
    44  		}
    45  		c, err := f()
    46  		if err != nil {
    47  			if firstErr == nil {
    48  				firstErr = err
    49  			}
    50  			continue
    51  		}
    52  		return c, nil
    53  	}
    54  	if firstErr != nil {
    55  		return nil, firstErr
    56  	}
    57  	return nil, errors.New("headless: no available GPU backends")
    58  }
    59  
    60  // NewWindow creates a new headless window.
    61  func NewWindow(width, height int) (*Window, error) {
    62  	ctx, err := newContext()
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	w := &Window{
    67  		size: image.Point{X: width, Y: height},
    68  		ctx:  ctx,
    69  	}
    70  	err = contextDo(ctx, func() error {
    71  		dev, err := driver.NewDevice(ctx.API())
    72  		if err != nil {
    73  			return err
    74  		}
    75  		fboTex, err := dev.NewTexture(
    76  			driver.TextureFormatSRGBA,
    77  			width, height,
    78  			driver.FilterNearest, driver.FilterNearest,
    79  			driver.BufferBindingFramebuffer,
    80  		)
    81  		if err != nil {
    82  			dev.Release()
    83  			return err
    84  		}
    85  		// Note that the gpu takes ownership of dev.
    86  		gp, err := gpu.NewWithDevice(dev)
    87  		if err != nil {
    88  			fboTex.Release()
    89  			return err
    90  		}
    91  		w.fboTex = fboTex
    92  		w.gpu = gp
    93  		w.dev = dev
    94  		return err
    95  	})
    96  	if err != nil {
    97  		ctx.Release()
    98  		return nil, err
    99  	}
   100  	return w, nil
   101  }
   102  
   103  // Release resources associated with the window.
   104  func (w *Window) Release() {
   105  	contextDo(w.ctx, func() error {
   106  		if w.fboTex != nil {
   107  			w.fboTex.Release()
   108  			w.fboTex = nil
   109  		}
   110  		if w.gpu != nil {
   111  			w.gpu.Release()
   112  			w.gpu = nil
   113  		}
   114  		// w.dev is owned and freed by w.gpu.
   115  		w.dev = nil
   116  		return nil
   117  	})
   118  	if w.ctx != nil {
   119  		w.ctx.Release()
   120  		w.ctx = nil
   121  	}
   122  }
   123  
   124  // Size returns the window size.
   125  func (w *Window) Size() image.Point {
   126  	return w.size
   127  }
   128  
   129  // Frame replaces the window content and state with the
   130  // operation list.
   131  func (w *Window) Frame(frame *op.Ops) error {
   132  	return contextDo(w.ctx, func() error {
   133  		w.gpu.Clear(color.NRGBA{})
   134  		return w.gpu.Frame(frame, w.fboTex, w.size)
   135  	})
   136  }
   137  
   138  // Screenshot transfers the Window content at origin img.Rect.Min to img.
   139  func (w *Window) Screenshot(img *image.RGBA) error {
   140  	return contextDo(w.ctx, func() error {
   141  		return driver.DownloadImage(w.dev, w.fboTex, img)
   142  	})
   143  }
   144  
   145  func contextDo(ctx context, f func() error) error {
   146  	errCh := make(chan error)
   147  	go func() {
   148  		if err := ctx.MakeCurrent(); err != nil {
   149  			errCh <- err
   150  			return
   151  		}
   152  		err := f()
   153  		ctx.ReleaseCurrent()
   154  		errCh <- err
   155  	}()
   156  	return <-errCh
   157  }