1package vulkan
2
3import (
4 "image"
5 "image/color"
6 "testing"
7
8 "github.com/bnema/neferwl/internal/ports"
9)
10
11// TestRenderSteadyStateAllocations measures the cost of a small stable scene,
12// on a normal and on a rotated output (orient works in place).
13func TestRenderSteadyStateAllocations(t *testing.T) {
14 for _, tc := range []struct {
15 name string
16 transform ports.BufferTransform
17 tw, th int
18 sceneW, sceneH int
19 }{
20 {"transform 0", 0, 32, 32, 32, 32},
21 {"transform 1", 1, 32, 24, 24, 32},
22 } {
23 t.Run(tc.name, func(t *testing.T) {
24 r, err := New(tc.tw, tc.th)
25 if err != nil {
26 t.Skipf("Vulkan unavailable: %v", err)
27 }
28 defer r.Close()
29 scene := ports.Scene{Seq: 1, Transform: tc.transform, Scale: 1, Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: tc.sceneW, H: tc.sceneH}}}}
30 content := solidContent(t, 8, 8, color.RGBA{R: 255, A: 255})
31 content.ID, content.Surface, content.Seq, content.Version = 1, 1, 1, 1
32 contents := map[ports.WindowID]ports.SurfaceContent{1: content}
33 frame := func() {
34 if err := render(r, scene, contents); err != nil {
35 t.Fatal(err)
36 }
37 }
38 frame()
39 if allocs := testing.AllocsPerRun(20, frame); allocs > 13 {
40 t.Errorf("small steady-state Render: %.1f allocs/frame, want <=13", allocs)
41 } else {
42 t.Logf("small steady-state Render: %.1f allocs/frame", allocs)
43 }
44 })
45 }
46}
47
48// TestSceneWalkUnchangedTiledSHM guards the GPU copy budget of an unchanged
49// subsurface tree. Each child has its own real wl_shm pool.
50func TestSceneWalkUnchangedTiledSHM(t *testing.T) {
51 const tiles = 64
52 r, err := New(128, 128)
53 if err != nil {
54 t.Skipf("Vulkan unavailable: %v", err)
55 }
56 defer r.Close()
57 root := ports.SurfaceContent{ID: 1, Seq: 1, Surface: 1, Version: 1}
58 for i := range tiles {
59 child := solidContent(t, 8, 8, color.RGBA{R: uint8(i + 1), A: 255})
60 child.ID, child.Seq, child.Surface, child.Version = 1, 1, uint64(i+2), 1
61 root.Children = append(root.Children, ports.Subsurface{X: i % 8 * 8, Y: i / 8 * 8, SurfaceContent: child})
62 }
63 video := solidContent(t, 8, 8, color.RGBA{G: 255, A: 255})
64 video.ID, video.Seq, video.Surface, video.Version = 1, 1, 66, 1
65 root.Children = append(root.Children, ports.Subsurface{X: 80, Y: 0, SurfaceContent: video})
66 scene := ports.Scene{Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 128, H: 128}}}}
67 contents := map[ports.WindowID]ports.SurfaceContent{1: root}
68 if err := render(r, scene, contents); err != nil {
69 t.Fatal(err)
70 }
71 for _, count := range []int{16, 64} {
72 contents[1] = ports.SurfaceContent{ID: 1, Seq: root.Seq, Surface: root.Surface, Version: root.Version, Children: root.Children[:count]}
73 walk := func() {
74 dmg := newDamage(&target{}, scene, image.Rect(0, 0, r.width, r.height))
75 _ = r.draws(scene, contents, dmg)
76 }
77 walk()
78 if allocs := testing.AllocsPerRun(20, walk); allocs > 8 {
79 t.Errorf("%d tiles: %.1f allocations/walk, want <=8", count, allocs)
80 } else {
81 t.Logf("%d tiles: %.1f allocations/walk", count, allocs)
82 }
83 }
84 contents[1] = root
85 // The same renderer and scene are reused; measure only steady-state frame work.
86 frame := func() {
87 if err := render(r, scene, contents); err != nil {
88 t.Fatal(err)
89 }
90 }
91 frame()
92 if allocs := testing.AllocsPerRun(20, frame); allocs > 16 {
93 t.Errorf("steady-state Render: %.1f allocs/frame, want <=16", allocs)
94 } else {
95 t.Logf("steady-state Render: %.1f allocs/frame", allocs)
96 }
97 before := r.copied
98 for i := range 100 {
99 root.Seq++
100 root.Children[tiles].Version++
101 contents[1] = root
102 if err := render(r, scene, contents); err != nil {
103 t.Fatal(err)
104 }
105 if delta := r.copied - before; delta != 8*8*4 {
106 t.Fatalf("publication %d copied %d bytes, want video only (%d)", i, delta, 8*8*4)
107 }
108 before = r.copied
109 }
110 // Reordering equal-sized children must not reuse another child's pixels.
111 root.Children[0], root.Children[1] = root.Children[1], root.Children[0]
112 root.Children[0].X, root.Children[1].X = 0, 8
113 root.Seq++
114 contents[1] = root
115 if err := render(r, scene, contents); err != nil {
116 t.Fatal(err)
117 }
118 if got := readPixels(t, r).RGBAAt(4, 4); got != (color.RGBA{R: 2, A: 255}) {
119 t.Fatalf("restacked tile has pixels %v, want second child's colour", got)
120 }
121}