1package vulkan
2
3import (
4 "image"
5 "math"
6 "testing"
7
8 "github.com/bnema/neferwl/internal/ports"
9)
10
11// These walk tests need no Vulkan device: a single-pixel buffer is a solid
12// quad, which imports nothing.
13func TestSceneSolid(t *testing.T) {
14 walk := func(r *Renderer, c ports.SurfaceContent) []draw {
15 c.ID = 1
16 c.Width, c.Height, c.LogicalW, c.LogicalH = 1, 1, 20, 20
17 s := ports.Scene{Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 20, H: 20}}}}
18 contents := map[ports.WindowID]ports.SurfaceContent{1: c}
19 return r.draws(s, contents, newDamage(&target{}, s, image.Rect(0, 0, 80, 60)))
20 }
21 // The window's own background fill comes first; the content follows.
22 solids := func(ds []draw) []draw {
23 if len(ds) == 0 || ds[0].pc.misc[0] != modeSolid || ds[0].pc.rect != [4]int32{0, 0, 20, 20} {
24 t.Fatalf("no window background first: %+v", ds)
25 }
26 return ds[1:]
27 }
28 near := func(a, b [4]float32) bool {
29 for i := range a {
30 if math.Abs(float64(a[i]-b[i])) > 1e-6 {
31 return false
32 }
33 }
34 return true
35 }
36 half := &ports.SolidColor{R: 0.25, G: 0.125, B: 0, A: 0.5} // premultiplied
37 t.Run("premultiplied", func(t *testing.T) {
38 r := &Renderer{width: 80, height: 60}
39 got := solids(walk(r, ports.SurfaceContent{Solid: half}))
40 if len(got) != 1 || got[0].pc.color != [4]float32{0.25, 0.125, 0, 0.5} {
41 t.Fatalf("draws = %+v", got)
42 }
43 })
44 t.Run("fade", func(t *testing.T) {
45 r := &Renderer{width: 80, height: 60}
46 got := solids(walk(r, ports.SurfaceContent{Solid: half, Fade: 0.5}))
47 if len(got) != 1 || got[0].pc.color != [4]float32{0.125, 0.0625, 0, 0.25} {
48 t.Fatalf("draws = %+v", got)
49 }
50 })
51 t.Run("opaque", func(t *testing.T) {
52 r := &Renderer{width: 80, height: 60}
53 got := solids(walk(r, ports.SurfaceContent{Solid: half, Opaque: true}))
54 if len(got) != 1 || got[0].pc.color != [4]float32{0.25, 0.125, 0, 1} {
55 t.Fatalf("draws = %+v", got)
56 }
57 })
58 t.Run("hdr", func(t *testing.T) {
59 r := &Renderer{width: 80, height: 60, hdrNits: 203}
60 got := solids(walk(r, ports.SurfaceContent{Solid: half}))
61 if len(got) != 1 {
62 t.Fatalf("draws = %+v", got)
63 }
64 // The HDR shader decodes rgb (decodeSRGB) and blends linear light:
65 // decoded values must be the linear straight color times alpha.
66 c := got[0].pc.color
67 decoded := [4]float32{float32(srgbToLinear(float64(c[0]))), float32(srgbToLinear(float64(c[1]))), float32(srgbToLinear(float64(c[2]))), c[3]}
68 want := [4]float32{float32(srgbToLinear(0.5) * 0.5), float32(srgbToLinear(0.25) * 0.5), 0, 0.5}
69 if !near(decoded, want) {
70 t.Fatalf("decoded = %v, want %v", decoded, want)
71 }
72 })
73 t.Run("transparent", func(t *testing.T) {
74 r := &Renderer{width: 80, height: 60}
75 if got := solids(walk(r, ports.SurfaceContent{Solid: &ports.SolidColor{}})); len(got) != 0 {
76 t.Fatalf("draws = %+v", got)
77 }
78 // Fully faded away too.
79 if got := walk(r, ports.SurfaceContent{Solid: half, Fade: 1}); len(solids(got)) != 0 {
80 t.Fatalf("faded draws = %+v", got)
81 }
82 })
83}
84
85func TestLinearToSRGB(t *testing.T) {
86 for _, v := range []float64{0, 0.002, 0.04045, 0.5, 1} {
87 if got := srgbToLinear(linearToSRGB(v)); math.Abs(got-v) > 1e-6 {
88 t.Errorf("round trip of %v = %v", v, got)
89 }
90 if got := linearToSRGB(srgbToLinear(v)); math.Abs(got-v) > 1e-6 {
91 t.Errorf("inverse round trip of %v = %v", v, got)
92 }
93 }
94}