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 }