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 }