github.com/gop9/olt@v0.0.0-20200202132135-d956aad50b08/gio/app/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  	"fmt"
     9  	"image"
    10  	"runtime"
    11  
    12  	"github.com/gop9/olt/gio/app/internal/gl"
    13  	"github.com/gop9/olt/gio/app/internal/gpu"
    14  	"github.com/gop9/olt/gio/op"
    15  )
    16  
    17  // Window is a headless window.
    18  type Window struct {
    19  	size image.Point
    20  	ctx  context
    21  	fbo  *gl.SRGBFBO
    22  	gpu  *gpu.GPU
    23  }
    24  
    25  type context interface {
    26  	Functions() *gl.Functions
    27  	MakeCurrent() error
    28  	ReleaseCurrent()
    29  	Release()
    30  }
    31  
    32  // NewWindow creates a new headless window.
    33  func NewWindow(width, height int) (*Window, error) {
    34  	ctx, err := newContext()
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  	w := &Window{
    39  		size: image.Point{X: width, Y: height},
    40  		ctx:  ctx,
    41  	}
    42  	err = contextDo(ctx, func() error {
    43  		f := ctx.Functions()
    44  		fbo, err := gl.NewSRGBFBO(f)
    45  		if err != nil {
    46  			ctx.Release()
    47  			return err
    48  		}
    49  		if err := fbo.Refresh(width, height); err != nil {
    50  			fbo.Release()
    51  			ctx.Release()
    52  			return err
    53  		}
    54  		gpu, err := gpu.New(f)
    55  		if err != nil {
    56  			fbo.Release()
    57  			ctx.Release()
    58  			return err
    59  		}
    60  		w.fbo = fbo
    61  		w.gpu = gpu
    62  		return err
    63  	})
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  	return w, nil
    68  }
    69  
    70  // Release resources associated with the window.
    71  func (w *Window) Release() {
    72  	contextDo(w.ctx, func() error {
    73  		if w.gpu != nil {
    74  			w.gpu.Release()
    75  			w.gpu = nil
    76  		}
    77  		if w.fbo != nil {
    78  			w.fbo.Release()
    79  			w.fbo = nil
    80  		}
    81  		if w.ctx != nil {
    82  			w.ctx.Release()
    83  			w.ctx = nil
    84  		}
    85  		return nil
    86  	})
    87  }
    88  
    89  // Frame replace the window content and state with the
    90  // operation list.
    91  func (w *Window) Frame(frame *op.Ops) {
    92  	contextDo(w.ctx, func() error {
    93  		w.gpu.Collect(false, w.size, frame)
    94  		w.gpu.Frame(false, w.size)
    95  		w.gpu.EndFrame(false)
    96  		return nil
    97  	})
    98  }
    99  
   100  // Screenshot returns an image with the content of the window.
   101  func (w *Window) Screenshot() (*image.RGBA, error) {
   102  	img := image.NewRGBA(image.Rectangle{Max: w.size})
   103  	if len(img.Pix) != w.size.X*w.size.Y*4 {
   104  		panic("unexpected RGBA size")
   105  	}
   106  	contextDo(w.ctx, func() error {
   107  		f := w.ctx.Functions()
   108  		f.ReadPixels(0, 0, w.size.X, w.size.Y, gl.RGBA, gl.UNSIGNED_BYTE, img.Pix)
   109  		if glErr := f.GetError(); glErr != gl.NO_ERROR {
   110  			return fmt.Errorf("glReadPixels failed: %d", glErr)
   111  		}
   112  		return nil
   113  	})
   114  	// Flip image in y-direction. OpenGL's origin is in the lower
   115  	// left corner.
   116  	row := make([]uint8, img.Stride)
   117  	for y := 0; y < w.size.Y/2; y++ {
   118  		y1 := w.size.Y - y - 1
   119  		dest := img.PixOffset(0, y1)
   120  		src := img.PixOffset(0, y)
   121  		copy(row, img.Pix[dest:])
   122  		copy(img.Pix[dest:], img.Pix[src:src+len(row)])
   123  		copy(img.Pix[src:], row)
   124  	}
   125  	return img, nil
   126  }
   127  
   128  func contextDo(ctx context, f func() error) error {
   129  	errCh := make(chan error)
   130  	go func() {
   131  		runtime.LockOSThread()
   132  		defer runtime.UnlockOSThread()
   133  		if err := ctx.MakeCurrent(); err != nil {
   134  			errCh <- err
   135  			return
   136  		}
   137  		defer ctx.ReleaseCurrent()
   138  		errCh <- f()
   139  	}()
   140  	return <-errCh
   141  }