Files
gio/gpu/headless/backend_test.go
T
Elias Naur 69cff4b96b gpu/internal/driver,gpu/headless: don't y-axis flip OpenGL ReadPixels images
The CPU fallback of the compute renderer needs ReadPixels data in OpenGL
format (origin at bottom left). Unfortunately, the OpenGL driver
automatically mirrors images in the Y-axis to match the top left origin
image.RGBA.

Remove the mirroring from the driver and introduce a DownloadImage to
restore the old behaviour.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2021-03-08 18:12:33 +01:00

207 lines
5.1 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
package headless
import (
"bytes"
"flag"
"image"
"image/color"
"image/png"
"io/ioutil"
"runtime"
"testing"
"gioui.org/gpu/internal/driver"
"gioui.org/internal/f32color"
"gioui.org/internal/unsafe"
)
var dumpImages = flag.Bool("saveimages", false, "save test images")
var clearCol = color.NRGBA{A: 0xff, R: 0xde, G: 0xad, B: 0xbe}
var clearColExpect = f32color.NRGBAToRGBA(clearCol)
func TestFramebufferClear(t *testing.T) {
b := newBackend(t)
sz := image.Point{X: 800, Y: 600}
fbo := setupFBO(t, b, sz)
img := screenshot(t, b, fbo, sz)
if got := img.RGBAAt(0, 0); got != clearColExpect {
t.Errorf("got color %v, expected %v", got, clearColExpect)
}
}
func TestSimpleShader(t *testing.T) {
b := newBackend(t)
sz := image.Point{X: 800, Y: 600}
fbo := setupFBO(t, b, sz)
p, err := b.NewProgram(shader_simple_vert, shader_simple_frag)
if err != nil {
t.Fatal(err)
}
defer p.Release()
b.BindProgram(p)
b.DrawArrays(driver.DrawModeTriangles, 0, 3)
img := screenshot(t, b, fbo, sz)
if got := img.RGBAAt(0, 0); got != clearColExpect {
t.Errorf("got color %v, expected %v", got, clearColExpect)
}
// Just off the center to catch inverted triangles.
cx, cy := 300, 400
shaderCol := f32color.RGBA{R: .25, G: .55, B: .75, A: 1.0}
if got, exp := img.RGBAAt(cx, cy), shaderCol.SRGB(); got != f32color.NRGBAToRGBA(exp) {
t.Errorf("got color %v, expected %v", got, f32color.NRGBAToRGBA(exp))
}
}
func TestInputShader(t *testing.T) {
b := newBackend(t)
sz := image.Point{X: 800, Y: 600}
fbo := setupFBO(t, b, sz)
p, err := b.NewProgram(shader_input_vert, shader_simple_frag)
if err != nil {
t.Fatal(err)
}
defer p.Release()
b.BindProgram(p)
buf, err := b.NewImmutableBuffer(driver.BufferBindingVertices,
unsafe.BytesView([]float32{
0, .5, .5, 1,
-.5, -.5, .5, 1,
.5, -.5, .5, 1,
}),
)
if err != nil {
t.Fatal(err)
}
defer buf.Release()
b.BindVertexBuffer(buf, 4*4, 0)
layout, err := b.NewInputLayout(shader_input_vert, []driver.InputDesc{
{
Type: driver.DataTypeFloat,
Size: 4,
Offset: 0,
},
})
if err != nil {
t.Fatal(err)
}
defer layout.Release()
b.BindInputLayout(layout)
b.DrawArrays(driver.DrawModeTriangles, 0, 3)
img := screenshot(t, b, fbo, sz)
if got := img.RGBAAt(0, 0); got != clearColExpect {
t.Errorf("got color %v, expected %v", got, clearColExpect)
}
cx, cy := 300, 400
shaderCol := f32color.RGBA{R: .25, G: .55, B: .75, A: 1.0}
if got, exp := img.RGBAAt(cx, cy), shaderCol.SRGB(); got != f32color.NRGBAToRGBA(exp) {
t.Errorf("got color %v, expected %v", got, f32color.NRGBAToRGBA(exp))
}
}
func TestFramebuffers(t *testing.T) {
b := newBackend(t)
sz := image.Point{X: 800, Y: 600}
fbo1 := newFBO(t, b, sz)
fbo2 := newFBO(t, b, sz)
var (
col1 = color.NRGBA{R: 0xac, G: 0xbd, B: 0xef, A: 0xde}
col2 = color.NRGBA{R: 0xfe, G: 0xba, B: 0xbe, A: 0xca}
)
fcol1, fcol2 := f32color.LinearFromSRGB(col1), f32color.LinearFromSRGB(col2)
b.BindFramebuffer(fbo1)
b.Clear(fcol1.Float32())
b.BindFramebuffer(fbo2)
b.Clear(fcol2.Float32())
img := screenshot(t, b, fbo1, sz)
if got := img.RGBAAt(0, 0); got != f32color.NRGBAToRGBA(col1) {
t.Errorf("got color %v, expected %v", got, f32color.NRGBAToRGBA(col1))
}
img = screenshot(t, b, fbo2, sz)
if got := img.RGBAAt(0, 0); got != f32color.NRGBAToRGBA(col2) {
t.Errorf("got color %v, expected %v", got, f32color.NRGBAToRGBA(col2))
}
}
func setupFBO(t *testing.T, b driver.Device, size image.Point) driver.Framebuffer {
fbo := newFBO(t, b, size)
b.BindFramebuffer(fbo)
// ClearColor accepts linear RGBA colors, while 8-bit colors
// are in the sRGB color space.
col := f32color.LinearFromSRGB(clearCol)
b.Clear(col.Float32())
b.ClearDepth(0.0)
b.Viewport(0, 0, size.X, size.Y)
return fbo
}
func newFBO(t *testing.T, b driver.Device, size image.Point) driver.Framebuffer {
fboTex, err := b.NewTexture(
driver.TextureFormatSRGB,
size.X, size.Y,
driver.FilterNearest, driver.FilterNearest,
driver.BufferBindingFramebuffer,
)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
fboTex.Release()
})
const depthBits = 16
fbo, err := b.NewFramebuffer(fboTex, depthBits)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
fbo.Release()
})
return fbo
}
func newBackend(t *testing.T) driver.Device {
ctx, err := newContext()
if err != nil {
t.Skipf("no context available: %v", err)
}
runtime.LockOSThread()
if err := ctx.MakeCurrent(); err != nil {
t.Fatal(err)
}
b, err := driver.NewDevice(ctx.API())
if err != nil {
t.Fatal(err)
}
b.BeginFrame()
t.Cleanup(func() {
b.EndFrame()
ctx.ReleaseCurrent()
runtime.UnlockOSThread()
ctx.Release()
})
return b
}
func screenshot(t *testing.T, d driver.Device, fbo driver.Framebuffer, size image.Point) *image.RGBA {
img, err := driver.DownloadImage(d, fbo, image.Rectangle{Max: size})
if err != nil {
t.Fatal(err)
}
if *dumpImages {
if err := saveImage(t.Name()+".png", img); err != nil {
t.Error(err)
}
}
return img
}
func saveImage(file string, img image.Image) error {
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
return err
}
return ioutil.WriteFile(file, buf.Bytes(), 0666)
}