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  }