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