github.com/cybriq/giocore@v0.0.7-0.20210703034601-cfb9cb5f3900/gpu/headless/driver_test.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package headless 4 5 import ( 6 "bytes" 7 "flag" 8 "image" 9 "image/color" 10 "image/png" 11 "io/ioutil" 12 "runtime" 13 "testing" 14 15 "github.com/cybriq/giocore/gpu/internal/driver" 16 "github.com/cybriq/giocore/internal/byteslice" 17 "github.com/cybriq/giocore/internal/f32color" 18 ) 19 20 var dumpImages = flag.Bool("saveimages", false, "save test images") 21 22 var clearCol = color.NRGBA{A: 0xff, R: 0xde, G: 0xad, B: 0xbe} 23 var clearColExpect = f32color.NRGBAToRGBA(clearCol) 24 25 func TestFramebufferClear(t *testing.T) { 26 b := newDriver(t) 27 sz := image.Point{X: 800, Y: 600} 28 fbo := setupFBO(t, b, sz) 29 img := screenshot(t, b, fbo, sz) 30 if got := img.RGBAAt(0, 0); got != clearColExpect { 31 t.Errorf("got color %v, expected %v", got, clearColExpect) 32 } 33 } 34 35 func TestSimpleShader(t *testing.T) { 36 b := newDriver(t) 37 sz := image.Point{X: 800, Y: 600} 38 fbo := setupFBO(t, b, sz) 39 p, err := b.NewProgram(shader_simple_vert, shader_simple_frag) 40 if err != nil { 41 t.Fatal(err) 42 } 43 defer p.Release() 44 b.BindProgram(p) 45 b.DrawArrays(driver.DrawModeTriangles, 0, 3) 46 img := screenshot(t, b, fbo, sz) 47 if got := img.RGBAAt(0, 0); got != clearColExpect { 48 t.Errorf("got color %v, expected %v", got, clearColExpect) 49 } 50 // Just off the center to catch inverted triangles. 51 cx, cy := 300, 400 52 shaderCol := f32color.RGBA{R: .25, G: .55, B: .75, A: 1.0} 53 if got, exp := img.RGBAAt(cx, cy), shaderCol.SRGB(); got != f32color.NRGBAToRGBA(exp) { 54 t.Errorf("got color %v, expected %v", got, f32color.NRGBAToRGBA(exp)) 55 } 56 } 57 58 func TestInputShader(t *testing.T) { 59 b := newDriver(t) 60 sz := image.Point{X: 800, Y: 600} 61 fbo := setupFBO(t, b, sz) 62 p, err := b.NewProgram(shader_input_vert, shader_simple_frag) 63 if err != nil { 64 t.Fatal(err) 65 } 66 defer p.Release() 67 b.BindProgram(p) 68 buf, err := b.NewImmutableBuffer(driver.BufferBindingVertices, 69 byteslice.Slice([]float32{ 70 0, .5, .5, 1, 71 -.5, -.5, .5, 1, 72 .5, -.5, .5, 1, 73 }), 74 ) 75 if err != nil { 76 t.Fatal(err) 77 } 78 defer buf.Release() 79 b.BindVertexBuffer(buf, 4*4, 0) 80 layout, err := b.NewInputLayout(shader_input_vert, []driver.InputDesc{ 81 { 82 Type: driver.DataTypeFloat, 83 Size: 4, 84 Offset: 0, 85 }, 86 }) 87 if err != nil { 88 t.Fatal(err) 89 } 90 defer layout.Release() 91 b.BindInputLayout(layout) 92 b.DrawArrays(driver.DrawModeTriangles, 0, 3) 93 img := screenshot(t, b, fbo, sz) 94 if got := img.RGBAAt(0, 0); got != clearColExpect { 95 t.Errorf("got color %v, expected %v", got, clearColExpect) 96 } 97 cx, cy := 300, 400 98 shaderCol := f32color.RGBA{R: .25, G: .55, B: .75, A: 1.0} 99 if got, exp := img.RGBAAt(cx, cy), shaderCol.SRGB(); got != f32color.NRGBAToRGBA(exp) { 100 t.Errorf("got color %v, expected %v", got, f32color.NRGBAToRGBA(exp)) 101 } 102 } 103 104 func TestFramebuffers(t *testing.T) { 105 b := newDriver(t) 106 sz := image.Point{X: 800, Y: 600} 107 fbo1 := newFBO(t, b, sz) 108 fbo2 := newFBO(t, b, sz) 109 var ( 110 col1 = color.NRGBA{R: 0xac, G: 0xbd, B: 0xef, A: 0xde} 111 col2 = color.NRGBA{R: 0xfe, G: 0xba, B: 0xbe, A: 0xca} 112 ) 113 fcol1, fcol2 := f32color.LinearFromSRGB(col1), f32color.LinearFromSRGB(col2) 114 b.BindFramebuffer(fbo1) 115 b.Clear(fcol1.Float32()) 116 b.BindFramebuffer(fbo2) 117 b.Clear(fcol2.Float32()) 118 img := screenshot(t, b, fbo1, sz) 119 if got := img.RGBAAt(0, 0); got != f32color.NRGBAToRGBA(col1) { 120 t.Errorf("got color %v, expected %v", got, f32color.NRGBAToRGBA(col1)) 121 } 122 img = screenshot(t, b, fbo2, sz) 123 if got := img.RGBAAt(0, 0); got != f32color.NRGBAToRGBA(col2) { 124 t.Errorf("got color %v, expected %v", got, f32color.NRGBAToRGBA(col2)) 125 } 126 } 127 128 func setupFBO(t *testing.T, b driver.Device, size image.Point) driver.Framebuffer { 129 fbo := newFBO(t, b, size) 130 b.BindFramebuffer(fbo) 131 // ClearColor accepts linear RGBA colors, while 8-bit colors 132 // are in the sRGB color space. 133 col := f32color.LinearFromSRGB(clearCol) 134 b.Clear(col.Float32()) 135 b.ClearDepth(0.0) 136 b.Viewport(0, 0, size.X, size.Y) 137 return fbo 138 } 139 140 func newFBO(t *testing.T, b driver.Device, size image.Point) driver.Framebuffer { 141 fboTex, err := b.NewTexture( 142 driver.TextureFormatSRGB, 143 size.X, size.Y, 144 driver.FilterNearest, driver.FilterNearest, 145 driver.BufferBindingFramebuffer, 146 ) 147 if err != nil { 148 t.Fatal(err) 149 } 150 t.Cleanup(func() { 151 fboTex.Release() 152 }) 153 const depthBits = 16 154 fbo, err := b.NewFramebuffer(fboTex, depthBits) 155 if err != nil { 156 t.Fatal(err) 157 } 158 t.Cleanup(func() { 159 fbo.Release() 160 }) 161 return fbo 162 } 163 164 func newDriver(t *testing.T) driver.Device { 165 ctx, err := newContext() 166 if err != nil { 167 t.Skipf("no context available: %v", err) 168 } 169 runtime.LockOSThread() 170 if err := ctx.MakeCurrent(); err != nil { 171 t.Fatal(err) 172 } 173 b, err := driver.NewDevice(ctx.API()) 174 if err != nil { 175 t.Fatal(err) 176 } 177 b.BeginFrame(true, image.Pt(1, 1)) 178 t.Cleanup(func() { 179 b.EndFrame() 180 ctx.ReleaseCurrent() 181 runtime.UnlockOSThread() 182 ctx.Release() 183 }) 184 return b 185 } 186 187 func screenshot(t *testing.T, d driver.Device, fbo driver.Framebuffer, size image.Point) *image.RGBA { 188 img, err := driver.DownloadImage(d, fbo, image.Rectangle{Max: size}) 189 if err != nil { 190 t.Fatal(err) 191 } 192 if *dumpImages { 193 if err := saveImage(t.Name()+".png", img); err != nil { 194 t.Error(err) 195 } 196 } 197 return img 198 } 199 200 func saveImage(file string, img image.Image) error { 201 var buf bytes.Buffer 202 if err := png.Encode(&buf, img); err != nil { 203 return err 204 } 205 return ioutil.WriteFile(file, buf.Bytes(), 0666) 206 }