github.com/cybriq/giocore@v0.0.7-0.20210703034601-cfb9cb5f3900/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  	"image"
     9  	"image/color"
    10  	"runtime"
    11  
    12  	"github.com/cybriq/giocore/gpu"
    13  	"github.com/cybriq/giocore/gpu/internal/driver"
    14  	"github.com/cybriq/giocore/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  	fbo    driver.Framebuffer
    25  }
    26  
    27  type context interface {
    28  	API() gpu.API
    29  	MakeCurrent() error
    30  	ReleaseCurrent()
    31  	Release()
    32  }
    33  
    34  // NewWindow creates a new headless window.
    35  func NewWindow(width, height int) (*Window, error) {
    36  	ctx, err := newContext()
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  	w := &Window{
    41  		size: image.Point{X: width, Y: height},
    42  		ctx:  ctx,
    43  	}
    44  	err = contextDo(ctx, func() error {
    45  		api := ctx.API()
    46  		dev, err := driver.NewDevice(api)
    47  		if err != nil {
    48  			return err
    49  		}
    50  		dev.Viewport(0, 0, width, height)
    51  		fboTex, err := dev.NewTexture(
    52  			driver.TextureFormatSRGB,
    53  			width, height,
    54  			driver.FilterNearest, driver.FilterNearest,
    55  			driver.BufferBindingFramebuffer,
    56  		)
    57  		if err != nil {
    58  			return nil
    59  		}
    60  		const depthBits = 16
    61  		fbo, err := dev.NewFramebuffer(fboTex, depthBits)
    62  		if err != nil {
    63  			fboTex.Release()
    64  			return err
    65  		}
    66  		gp, err := gpu.New(api)
    67  		if err != nil {
    68  			fbo.Release()
    69  			fboTex.Release()
    70  			return err
    71  		}
    72  		w.fboTex = fboTex
    73  		w.fbo = fbo
    74  		w.gpu = gp
    75  		w.dev = dev
    76  		return err
    77  	})
    78  	if err != nil {
    79  		ctx.Release()
    80  		return nil, err
    81  	}
    82  	return w, nil
    83  }
    84  
    85  // Release resources associated with the window.
    86  func (w *Window) Release() {
    87  	contextDo(w.ctx, func() error {
    88  		if w.fbo != nil {
    89  			w.fbo.Release()
    90  			w.fbo = nil
    91  		}
    92  		if w.fboTex != nil {
    93  			w.fboTex.Release()
    94  			w.fboTex = nil
    95  		}
    96  		if w.gpu != nil {
    97  			w.gpu.Release()
    98  			w.gpu = nil
    99  		}
   100  		return nil
   101  	})
   102  	if w.ctx != nil {
   103  		w.ctx.Release()
   104  		w.ctx = nil
   105  	}
   106  }
   107  
   108  // Frame replace the window content and state with the
   109  // operation list.
   110  func (w *Window) Frame(frame *op.Ops) error {
   111  	return contextDo(w.ctx, func() error {
   112  		w.dev.BindFramebuffer(w.fbo)
   113  		w.gpu.Clear(color.NRGBA{})
   114  		w.gpu.Collect(w.size, frame)
   115  		return w.gpu.Frame()
   116  	})
   117  }
   118  
   119  // Screenshot returns an image with the content of the window.
   120  func (w *Window) Screenshot() (*image.RGBA, error) {
   121  	var img *image.RGBA
   122  	err := contextDo(w.ctx, func() error {
   123  		var err error
   124  		img, err = driver.DownloadImage(w.dev, w.fbo, image.Rectangle{Max: w.size})
   125  		return err
   126  	})
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	return img, nil
   131  }
   132  
   133  func contextDo(ctx context, f func() error) error {
   134  	errCh := make(chan error)
   135  	go func() {
   136  		runtime.LockOSThread()
   137  		defer runtime.UnlockOSThread()
   138  		if err := ctx.MakeCurrent(); err != nil {
   139  			errCh <- err
   140  			return
   141  		}
   142  		err := f()
   143  		ctx.ReleaseCurrent()
   144  		errCh <- err
   145  	}()
   146  	return <-errCh
   147  }