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