driver_test.go raw
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/p9c/p9/pkg/gel/gio/gpu/internal/driver"
16 "github.com/p9c/p9/pkg/gel/gio/internal/byteslice"
17 "github.com/p9c/p9/pkg/gel/gio/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()
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 }
207