gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/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 "os" 12 "runtime" 13 "testing" 14 15 "gioui.org/gpu/internal/driver" 16 "gioui.org/internal/byteslice" 17 "gioui.org/internal/f32color" 18 "gioui.org/shader" 19 "gioui.org/shader/gio" 20 ) 21 22 var dumpImages = flag.Bool("saveimages", false, "save test images") 23 24 var clearCol = color.NRGBA{A: 0xff, R: 0xde, G: 0xad, B: 0xbe} 25 var clearColExpect = f32color.NRGBAToRGBA(clearCol) 26 27 func TestFramebufferClear(t *testing.T) { 28 b := newDriver(t) 29 sz := image.Point{X: 800, Y: 600} 30 fbo := newFBO(t, b, sz) 31 d := driver.LoadDesc{ 32 // ClearColor accepts linear RGBA colors, while 8-bit colors 33 // are in the sRGB color space. 34 ClearColor: f32color.LinearFromSRGB(clearCol), 35 Action: driver.LoadActionClear, 36 } 37 b.BeginRenderPass(fbo, d) 38 b.EndRenderPass() 39 img := screenshot(t, b, fbo, sz) 40 if got := img.RGBAAt(0, 0); got != clearColExpect { 41 t.Errorf("got color %v, expected %v", got, clearColExpect) 42 } 43 } 44 45 func TestInputShader(t *testing.T) { 46 b := newDriver(t) 47 sz := image.Point{X: 800, Y: 600} 48 vsh, fsh, err := newShaders(b, gio.Shader_input_vert, gio.Shader_simple_frag) 49 if err != nil { 50 t.Fatal(err) 51 } 52 defer vsh.Release() 53 defer fsh.Release() 54 layout := driver.VertexLayout{ 55 Inputs: []driver.InputDesc{ 56 { 57 Type: shader.DataTypeFloat, 58 Size: 4, 59 Offset: 0, 60 }, 61 }, 62 Stride: 4 * 4, 63 } 64 fbo := newFBO(t, b, sz) 65 pipe, err := b.NewPipeline(driver.PipelineDesc{ 66 VertexShader: vsh, 67 FragmentShader: fsh, 68 VertexLayout: layout, 69 PixelFormat: driver.TextureFormatSRGBA, 70 Topology: driver.TopologyTriangles, 71 }) 72 if err != nil { 73 t.Fatal(err) 74 } 75 defer pipe.Release() 76 buf, err := b.NewImmutableBuffer(driver.BufferBindingVertices, 77 byteslice.Slice([]float32{ 78 0, -.5, .5, 1, 79 -.5, +.5, .5, 1, 80 .5, +.5, .5, 1, 81 }), 82 ) 83 if err != nil { 84 t.Fatal(err) 85 } 86 defer buf.Release() 87 d := driver.LoadDesc{ 88 ClearColor: f32color.LinearFromSRGB(clearCol), 89 Action: driver.LoadActionClear, 90 } 91 b.BeginRenderPass(fbo, d) 92 b.Viewport(0, 0, sz.X, sz.Y) 93 b.BindPipeline(pipe) 94 b.BindVertexBuffer(buf, 0) 95 b.DrawArrays(0, 3) 96 b.EndRenderPass() 97 img := screenshot(t, b, fbo, sz) 98 if got := img.RGBAAt(0, 0); got != clearColExpect { 99 t.Errorf("got color %v, expected %v", got, clearColExpect) 100 } 101 cx, cy := 300, 400 102 shaderCol := f32color.RGBA{R: .25, G: .55, B: .75, A: 1.0} 103 if got, exp := img.RGBAAt(cx, cy), shaderCol.SRGB(); got != f32color.NRGBAToRGBA(exp) { 104 t.Errorf("got color %v, expected %v", got, f32color.NRGBAToRGBA(exp)) 105 } 106 } 107 108 func newShaders(ctx driver.Device, vsrc, fsrc shader.Sources) (vert driver.VertexShader, frag driver.FragmentShader, err error) { 109 vert, err = ctx.NewVertexShader(vsrc) 110 if err != nil { 111 return 112 } 113 frag, err = ctx.NewFragmentShader(fsrc) 114 if err != nil { 115 vert.Release() 116 } 117 return 118 } 119 120 func TestFramebuffers(t *testing.T) { 121 b := newDriver(t) 122 sz := image.Point{X: 800, Y: 600} 123 var ( 124 col1 = color.NRGBA{R: 0xac, G: 0xbd, B: 0xef, A: 0xde} 125 col2 = color.NRGBA{R: 0xfe, G: 0xbb, B: 0xbe, A: 0xca} 126 ) 127 fbo1 := newFBO(t, b, sz) 128 fbo2 := newFBO(t, b, sz) 129 fcol1, fcol2 := f32color.LinearFromSRGB(col1), f32color.LinearFromSRGB(col2) 130 d := driver.LoadDesc{Action: driver.LoadActionClear} 131 d.ClearColor = fcol1 132 b.BeginRenderPass(fbo1, d) 133 b.EndRenderPass() 134 d.ClearColor = fcol2 135 b.BeginRenderPass(fbo2, d) 136 b.EndRenderPass() 137 img := screenshot(t, b, fbo1, sz) 138 if got := img.RGBAAt(0, 0); got != f32color.NRGBAToRGBA(col1) { 139 t.Errorf("got color %v, expected %v", got, f32color.NRGBAToRGBA(col1)) 140 } 141 img = screenshot(t, b, fbo2, sz) 142 if got := img.RGBAAt(0, 0); got != f32color.NRGBAToRGBA(col2) { 143 t.Errorf("got color %v, expected %v", got, f32color.NRGBAToRGBA(col2)) 144 } 145 } 146 147 func newFBO(t *testing.T, b driver.Device, size image.Point) driver.Texture { 148 fboTex, err := b.NewTexture( 149 driver.TextureFormatSRGBA, 150 size.X, size.Y, 151 driver.FilterNearest, driver.FilterNearest, 152 driver.BufferBindingFramebuffer, 153 ) 154 if err != nil { 155 t.Fatal(err) 156 } 157 t.Cleanup(func() { 158 fboTex.Release() 159 }) 160 return fboTex 161 } 162 163 func newDriver(t *testing.T) driver.Device { 164 ctx, err := newContext() 165 if err != nil { 166 t.Skipf("no context available: %v", err) 167 } 168 if err := ctx.MakeCurrent(); err != nil { 169 t.Fatal(err) 170 } 171 b, err := driver.NewDevice(ctx.API()) 172 if err != nil { 173 t.Fatal(err) 174 } 175 b.BeginFrame(nil, true, image.Pt(1, 1)) 176 t.Cleanup(func() { 177 b.EndFrame() 178 b.Release() 179 ctx.ReleaseCurrent() 180 runtime.UnlockOSThread() 181 ctx.Release() 182 }) 183 return b 184 } 185 186 func screenshot(t *testing.T, d driver.Device, fbo driver.Texture, size image.Point) *image.RGBA { 187 img := image.NewRGBA(image.Rectangle{Max: size}) 188 err := driver.DownloadImage(d, fbo, img) 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 os.WriteFile(file, buf.Bytes(), 0666) 206 }