scene_perf_test.go RawHistoryFolder

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), and that
13// building the draws of a scene with a moving window (a new Seq and window
14// slice every frame, as an animation publishes) allocates nothing.
15// A new scene Seq with one of two windows moved is a partial redraw seeded by
16// sceneDelta and allocates nothing either.
17func TestRenderSteadyStateAllocations(t *testing.T) {
18	for _, tc := range []struct {
19		name           string
20		transform      ports.BufferTransform
21		tw, th         int
22		sceneW, sceneH int
23	}{
24		{"transform 0", 0, 32, 32, 32, 32},
25		{"transform 1", 1, 32, 24, 24, 32},
26	} {
27		t.Run(tc.name, func(t *testing.T) {
28			r, err := New(tc.tw, tc.th)
29			if err != nil {
30				t.Skipf("Vulkan unavailable: %v", err)
31			}
32			defer r.Close()
33			scene := ports.Scene{Seq: 1, Transform: tc.transform, Scale: 1, Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: tc.sceneW, H: tc.sceneH}}}}
34			content := solidContent(t, 8, 8, color.RGBA{R: 255, A: 255})
35			content.ID, content.Surface, content.Seq, content.Version = 1, 1, 1, 1
36			contents := map[ports.WindowID]ports.SurfaceContent{1: content}
37			frame := func() {
38				if err := render(r, scene, contents); err != nil {
39					t.Fatal(err)
40				}
41			}
42			frame()
43			if allocs := testing.AllocsPerRun(20, frame); allocs > 13 {
44				t.Errorf("small steady-state Render: %.1f allocs/frame, want <=13", allocs)
45			} else {
46				t.Logf("small steady-state Render: %.1f allocs/frame", allocs)
47			}
48			// A moving window: scenes are prebuilt (core allocates them, not
49			// the renderer), each with a fresh Seq, so every frame draws
50			// everything again. The draw building must not allocate.
51			var moving [4]ports.Scene
52			for i := range moving {
53				moving[i] = ports.Scene{Seq: uint64(100 + i), Transform: tc.transform, Scale: 1, Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{X: i, W: tc.sceneW - 4, H: tc.sceneH - 4}}}}
54			}
55			bounds := image.Rect(0, 0, r.width, r.height)
56			n := 0
57			frameDraws := func() {
58				n++
59				s := moving[n%len(moving)]
60				_ = r.draws(s, contents, r.frameDamage(r.last, s, bounds))
61			}
62			frameDraws()
63			if allocs := testing.AllocsPerRun(50, frameDraws); allocs != 0 {
64				t.Errorf("moving window draws: %.1f allocs/frame, want 0", allocs)
65			}
66			// A content change under an unchanged scene Seq: a partial
67			// redraw clipped to the changed buffer region, replicating the
68			// partial sequence of Render. The stand-in target holds the
69			// previous content Seq of the window, and is never updated, so
70			// every run is damaged.
71			dc := content
72			dc.Seq, dc.Version = 2, 2
73			dc.DamageHistory = []ports.SeqDamage{{Seq: 2, Rects: []ports.Rect{{X: 1, Y: 1, W: 2, H: 2}}}}
74			pcontents := map[ports.WindowID]ports.SurfaceContent{1: dc}
75			held := &target{valid: true, sceneSeq: scene.Seq, windows: map[ports.WindowID]heldWindow{1: {seq: 1, rect: image.Rect(0, 0, r.width, r.height)}}}
76			var area image.Rectangle
77			var whole bool
78			partial := func() {
79				dmg := r.frameDamage(held, scene, bounds)
80				ds := r.draws(scene, pcontents, dmg)
81				dmg.finish()
82				ds = append(ds, draw{})
83				copy(ds[1:], ds[:len(ds)-1])
84				ds[0] = r.fillDraw(dmg.area, parseColor(scene.Background))
85				ds = dmg.clip(ds)
86				r.scratchDraws = ds
87				r.orient(ds, scene.Transform)
88				area, whole = dmg.area, dmg.all()
89			}
90			partial()
91			if whole || area.Empty() || area.Dx()*area.Dy() >= bounds.Dx()*bounds.Dy() {
92				t.Fatalf("not a partial redraw: all %v, area %v of %v", whole, area, bounds)
93			}
94			if allocs := testing.AllocsPerRun(50, partial); allocs != 0 {
95				t.Errorf("partial redraw draws: %.1f allocs/frame, want 0", allocs)
96			}
97			// A new scene Seq with one window moved: the delta against the
98			// scene the target holds seeds the partial redraw (A2). The
99			// stand-in target holds the old scene and is never updated.
100			half := tc.sceneW / 2
101			content2 := solidContent(t, 8, 8, color.RGBA{G: 255, A: 255})
102			content2.ID, content2.Surface, content2.Seq, content2.Version = 2, 2, 1, 1
103			dcontents := map[ports.WindowID]ports.SurfaceContent{1: content, 2: content2}
104			before := ports.Scene{Seq: 200, Transform: tc.transform, Scale: 1, Windows: []ports.SceneWindow{
105				{ID: 1, Rect: ports.Rect{W: half, H: tc.sceneH}},
106				{ID: 2, Rect: ports.Rect{X: half, W: tc.sceneW - half, H: tc.sceneH}},
107			}}
108			moved := before
109			moved.Seq = 201
110			moved.Windows = []ports.SceneWindow{before.Windows[0], {ID: 2, Rect: ports.Rect{X: half + 1, W: tc.sceneW - half - 1, H: tc.sceneH}}}
111			full := image.Rect(0, 0, r.width, r.height)
112			held = &target{valid: true, sceneSeq: before.Seq, scene: before, windows: map[ports.WindowID]heldWindow{
113				1: {seq: 1, rect: image.Rect(0, 0, half, tc.sceneH)},
114				2: {seq: 1, rect: image.Rect(half, 0, tc.sceneW, tc.sceneH)},
115			}}
116			seeded := func() {
117				dmg := r.frameDamage(held, moved, bounds)
118				ds := r.draws(moved, dcontents, dmg)
119				dmg.finish()
120				ds = append(ds, draw{})
121				copy(ds[1:], ds[:len(ds)-1])
122				ds[0] = r.fillDraw(dmg.area, parseColor(moved.Background))
123				ds = dmg.clip(ds)
124				r.scratchDraws = ds
125				r.orient(ds, moved.Transform)
126				area, whole = dmg.area, dmg.all()
127			}
128			seeded()
129			if whole || area.Empty() || area.Dx()*area.Dy() >= full.Dx()*full.Dy() {
130				t.Fatalf("sceneDelta did not seed a partial redraw: all %v, area %v of %v", whole, area, bounds)
131			}
132			if allocs := testing.AllocsPerRun(50, seeded); allocs != 0 {
133				t.Errorf("sceneDelta-seeded partial redraw draws: %.1f allocs/frame, want 0", allocs)
134			}
135		})
136	}
137}
138
139// TestSceneWalkUnchangedTiledSHM guards the GPU copy budget of an unchanged
140// subsurface tree. Each child has its own real wl_shm pool.
141func TestSceneWalkUnchangedTiledSHM(t *testing.T) {
142	const tiles = 64
143	r, err := New(128, 128)
144	if err != nil {
145		t.Skipf("Vulkan unavailable: %v", err)
146	}
147	defer r.Close()
148	root := ports.SurfaceContent{ID: 1, Seq: 1, Surface: 1, Version: 1}
149	for i := range tiles {
150		child := solidContent(t, 8, 8, color.RGBA{R: uint8(i + 1), A: 255})
151		child.ID, child.Seq, child.Surface, child.Version = 1, 1, uint64(i+2), 1
152		root.Children = append(root.Children, ports.Subsurface{X: i % 8 * 8, Y: i / 8 * 8, SurfaceContent: child})
153	}
154	video := solidContent(t, 8, 8, color.RGBA{G: 255, A: 255})
155	video.ID, video.Seq, video.Surface, video.Version = 1, 1, 66, 1
156	root.Children = append(root.Children, ports.Subsurface{X: 80, Y: 0, SurfaceContent: video})
157	scene := ports.Scene{Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 128, H: 128}}}}
158	contents := map[ports.WindowID]ports.SurfaceContent{1: root}
159	if err := render(r, scene, contents); err != nil {
160		t.Fatal(err)
161	}
162	for _, count := range []int{16, 64} {
163		contents[1] = ports.SurfaceContent{ID: 1, Seq: root.Seq, Surface: root.Surface, Version: root.Version, Children: root.Children[:count]}
164		walk := func() {
165			dmg := newDamage(&target{}, scene, image.Rect(0, 0, r.width, r.height))
166			_ = r.draws(scene, contents, dmg)
167		}
168		walk()
169		if allocs := testing.AllocsPerRun(20, walk); allocs > 8 {
170			t.Errorf("%d tiles: %.1f allocations/walk, want <=8", count, allocs)
171		} else {
172			t.Logf("%d tiles: %.1f allocations/walk", count, allocs)
173		}
174	}
175	contents[1] = root
176	// The same renderer and scene are reused; measure only steady-state frame work.
177	frame := func() {
178		if err := render(r, scene, contents); err != nil {
179			t.Fatal(err)
180		}
181	}
182	frame()
183	if allocs := testing.AllocsPerRun(20, frame); allocs > 16 {
184		t.Errorf("steady-state Render: %.1f allocs/frame, want <=16", allocs)
185	} else {
186		t.Logf("steady-state Render: %.1f allocs/frame", allocs)
187	}
188	before := r.copied
189	for i := range 100 {
190		root.Seq++
191		root.Children[tiles].Version++
192		contents[1] = root
193		if err := render(r, scene, contents); err != nil {
194			t.Fatal(err)
195		}
196		if delta := r.copied - before; delta != 8*8*4 {
197			t.Fatalf("publication %d copied %d bytes, want video only (%d)", i, delta, 8*8*4)
198		}
199		before = r.copied
200	}
201	// Reordering equal-sized children must not reuse another child's pixels.
202	root.Children[0], root.Children[1] = root.Children[1], root.Children[0]
203	root.Children[0].X, root.Children[1].X = 0, 8
204	root.Seq++
205	contents[1] = root
206	if err := render(r, scene, contents); err != nil {
207		t.Fatal(err)
208	}
209	if got := readPixels(t, r).RGBAAt(4, 4); got != (color.RGBA{R: 2, A: 255}) {
210		t.Fatalf("restacked tile has pixels %v, want second child's colour", got)
211	}
212}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 23

Tags 10

↑↓ moveenter openesc close