github.com/utopiagio/gio@v0.0.8/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  	"github.com/utopiagio/gio/gpu/internal/driver"
    16  	"github.com/utopiagio/gio/internal/byteslice"
    17  	"github.com/utopiagio/gio/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  }