1package render
2
3import (
4 "github.com/bnema/nefergui/internal/css"
5 "github.com/bnema/nefergui/internal/layout"
6 "github.com/bnema/nefergui/internal/text"
7 "image"
8 "reflect"
9 "testing"
10)
11
12func TestImageBatchOrder(t *testing.T) {
13 a, b := image.NewRGBA(image.Rect(0, 0, 2, 2)), image.NewNRGBA(image.Rect(0, 0, 2, 2))
14 frame := Frame{Quads: []Quad{{Op: "rect"}, {Op: "glyph"}, {Op: "image", Image: a, Color: css.Color{A: .5}}, {Op: "image", Image: b}, {Op: "rect"}, {Op: "border"}, {Op: "image", Image: a}}}
15 instances, batches, stats := ListBatches(frame)
16 if len(instances) != 7 || stats.Drawn != 7 || len(batches) != 5 {
17 t.Fatalf("instances=%d batches=%+v stats=%+v", len(instances), batches, stats)
18 }
19 for i, want := range []struct {
20 first, count uint32
21 source image.Image
22 }{{0, 2, nil}, {2, 1, a}, {3, 1, b}, {4, 2, nil}, {6, 1, a}} {
23 if batches[i].First != want.first || batches[i].Count != want.count || batches[i].Source != want.source {
24 t.Fatalf("batch %d: %+v", i, batches[i])
25 }
26 }
27 if instances[2].KindLayer[0] != 6 || instances[2].UV != [4]float32{0, 0, 1, 1} || instances[2].Color[3] != .5 {
28 t.Fatalf("image instance: %+v", instances[2])
29 }
30}
31
32func TestListInstances(t *testing.T) {
33 frame := Frame{Quads: []Quad{
34 {Op: "rect", Rect: layout.Rect{X: 1, Y: 2, W: 3, H: 4}, Clip: layout.Rect{W: 10, H: 10}, Color: css.Color{R: 1, G: .5, A: .5}, Radii: [4]float64{5, 6, 7, 8}},
35 {Op: "border"}, {Op: "glyph", Glyph: text.Placement{Page: 2, Rect: image.Rect(10, 20, 14, 26)}},
36 }}
37 got, stats := ListInstances(frame)
38 if stats.Drawn != 3 || len(stats.Skipped) != 0 || len(got) != 3 {
39 t.Fatalf("stats=%+v got=%+v", stats, got)
40 }
41 if got[0].Bounds != [4]float32{1, 2, 3, 4} || got[0].Clip != [4]float32{0, 0, 10, 10} || got[0].Color != [4]float32{1, .5, 0, .5} || got[0].Radii != [4]float32{5, 6, 7, 8} {
42 t.Fatalf("instance=%+v", got[0])
43 }
44 if got[2].KindLayer != [4]float32{1, 2} || got[2].UV != [4]float32{10.0 / 1024, 20.0 / 1024, 14.0 / 1024, 26.0 / 1024} {
45 t.Fatalf("glyph=%+v", got[2])
46 }
47}
48
49func TestListBufferMatchesListBatches(t *testing.T) {
50 a := image.NewRGBA(image.Rect(0, 0, 2, 2))
51 frames := []Frame{
52 listFixture(200),
53 {Quads: []Quad{{Op: "rect"}, {Op: "mystery"}, {Op: "image", Image: a}, {Op: "mystery"}, {Op: "glyph"}, {Op: "glyph"}}},
54 {},
55 listFixture(7),
56 }
57 var buf ListBuffer
58 for round := 0; round < 2; round++ {
59 for i, f := range frames {
60 wi, wb, ws := ListBatches(f)
61 gi, gb, gs := buf.List(f)
62 if !reflect.DeepEqual(wi, gi) || !reflect.DeepEqual(wb, gb) || !reflect.DeepEqual(ws, gs) {
63 t.Fatalf("round %d frame %d differs:\nwant %v %v %v\ngot %v %v %v", round, i, len(wi), wb, ws, len(gi), gb, gs)
64 }
65 }
66 }
67}
68
69// A result stays valid until the next List; a later smaller frame must not
70// leave stale instances, batches, image references or skip counts visible.
71func TestListBufferReuseLifetime(t *testing.T) {
72 a := image.NewRGBA(image.Rect(0, 0, 2, 2))
73 var buf ListBuffer
74 i1, b1, s1 := buf.List(Frame{Quads: []Quad{{Op: "rect"}, {Op: "image", Image: a}, {Op: "nope"}}})
75 if len(i1) != 2 || len(b1) != 2 || s1.Skipped["nope"] != 1 {
76 t.Fatalf("first: %v %v %v", i1, b1, s1)
77 }
78 i2, b2, s2 := buf.List(Frame{Quads: []Quad{{Op: "glyph"}}})
79 if len(i2) != 1 || len(b2) != 1 || b2[0].Source != nil || len(s2.Skipped) != 0 || i2[0].KindLayer[0] != 1 {
80 t.Fatalf("second: %v %v %v", i2, b2, s2)
81 }
82 buf.Release()
83 for _, batch := range b2[:cap(b2)] {
84 if batch.Source != nil {
85 t.Fatal("Release left an image reference")
86 }
87 }
88 // Results from the non-buffer API never alias the buffer.
89 fi, fb, _ := ListBatches(Frame{Quads: []Quad{{Op: "rect"}}})
90 buf.List(listFixture(50))
91 if fi[0].KindLayer != [4]float32{} || len(fb) != 1 || fb[0].Count != 1 {
92 t.Fatalf("ListBatches result aliased: %v %v", fi, fb)
93 }
94}
95
96func TestListBufferDropsOversizedStorage(t *testing.T) {
97 var buf ListBuffer
98 buf.List(listFixture(20000))
99 if _, _, _ = buf.List(listFixture(10)); cap(buf.instances) > 4*10+64 {
100 t.Fatalf("retained %d instances for 10 quads", cap(buf.instances))
101 }
102}
103
104func TestListBatchesAllocations(t *testing.T) {
105 frame := listFixture(2000)
106 if n := testing.AllocsPerRun(20, func() { ListBatches(frame) }); n > 4 {
107 t.Fatalf("ListBatches allocs=%v, want <= 4 (instances, batches, skipped map)", n)
108 }
109 var buf ListBuffer
110 buf.List(frame)
111 if n := testing.AllocsPerRun(20, func() { buf.List(frame) }); n != 0 {
112 t.Fatalf("ListBuffer.List allocs=%v, want 0 after warmup", n)
113 }
114}