trim_test.go RawHistoryFolder

1package vulkan
2
3import (
4	"image/color"
5	"slices"
6	"testing"
7	"time"
8
9	"github.com/bnema/neferwl/internal/ports"
10)
11
12// trimSettled calls Trim at now until the frames the GPU was running
13// finished and their deferred frees ran: Trim never waits for them.
14func trimSettled(t *testing.T, r *Renderer, now time.Time) {
15	t.Helper()
16	deadline := time.Now().Add(5 * time.Second)
17	for {
18		if err := r.Trim(now); err != nil {
19			t.Fatal(err)
20		}
21		if len(r.deferred) == 0 {
22			return
23		}
24		if time.Now().After(deadline) {
25			t.Fatalf("%d frees never ran", len(r.deferred))
26		}
27		time.Sleep(time.Millisecond)
28	}
29}
30
31// An idle output frees the pool mapping and GPU copies of a window it no
32// longer draws once idleTTL passed, without rendering; the window drawn
33// by the last frame stays, and a window coming back is copied again.
34func TestTrimFreesUndrawnSHMWithoutRender(t *testing.T) {
35	r, err := New(32, 32)
36	if err != nil {
37		t.Skipf("Vulkan unavailable: %v", err)
38	}
39	defer r.Close()
40	a := solidContent(t, 16, 16, color.RGBA{255, 0, 0, 255})
41	a.ID, a.Seq, a.Version = 1, 1, 1
42	b := solidContent(t, 16, 16, color.RGBA{0, 0, 255, 255})
43	b.ID, b.Seq, b.Version = 2, 1, 1
44	contents := map[ports.WindowID]ports.SurfaceContent{1: a, 2: b}
45	both := ports.Scene{Background: "#000000", Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 16, H: 16}}, {ID: 2, Rect: ports.Rect{X: 16, W: 16, H: 16}}}}
46	onlyB := ports.Scene{Background: "#000000", Windows: []ports.SceneWindow{{ID: 2, Rect: ports.Rect{X: 16, W: 16, H: 16}}}}
47	if err := render(r, both, contents); err != nil {
48		t.Fatal(err)
49	}
50	if err := render(r, onlyB, contents); err != nil {
51		t.Fatal(err)
52	}
53	start := time.Unix(1000, 0)
54	for _, dt := range []time.Duration{0, 10 * time.Second, idleTTL - time.Second} {
55		if err := r.Trim(start.Add(dt)); err != nil {
56			t.Fatal(err)
57		}
58	}
59	if len(r.pools) != 2 || len(r.shm) != 2 {
60		t.Fatalf("freed before idleTTL: pools %d, copies %d", len(r.pools), len(r.shm))
61	}
62	trimSettled(t, r, start.Add(idleTTL))
63	if _, ok := r.pools[a.SHM.Pool]; ok || len(r.pools) != 1 {
64		t.Fatalf("undrawn pool kept: %d pools", len(r.pools))
65	}
66	if _, ok := r.shm[shmKey{win: 1}]; ok || len(r.shm) != 1 {
67		t.Fatalf("undrawn copies kept: %d surfaces", len(r.shm))
68	}
69	// Still idle: the window on screen stays.
70	if err := r.Trim(start.Add(10 * idleTTL)); err != nil {
71		t.Fatal(err)
72	}
73	if len(r.pools) != 1 || len(r.shm) != 1 {
74		t.Fatalf("shown window freed: pools %d, copies %d", len(r.pools), len(r.shm))
75	}
76	copied := r.copied
77	if err := render(r, both, contents); err != nil {
78		t.Fatal(err)
79	}
80	if r.copied != copied+16*16*4 {
81		t.Fatalf("returning window copied %d bytes", r.copied-copied)
82	}
83	if got := readPixels(t, r).RGBAAt(4, 4); got != (color.RGBA{255, 0, 0, 255}) {
84		t.Fatalf("returning window shows %v", got)
85	}
86}
87
88// A window drawn again within idleTTL keeps its caches: frames date them.
89func TestTrimKeepsRecentlyDrawn(t *testing.T) {
90	r, err := New(32, 32)
91	if err != nil {
92		t.Skipf("Vulkan unavailable: %v", err)
93	}
94	defer r.Close()
95	a := solidContent(t, 16, 16, color.RGBA{255, 0, 0, 255})
96	a.ID, a.Seq, a.Version = 1, 1, 1
97	contents := map[ports.WindowID]ports.SurfaceContent{1: a}
98	shown := ports.Scene{Background: "#000000", Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 16, H: 16}}}}
99	empty := ports.Scene{Background: "#000000"}
100	start := time.Unix(1000, 0)
101	if err := render(r, shown, contents); err != nil {
102		t.Fatal(err)
103	}
104	if err := render(r, empty, contents); err != nil {
105		t.Fatal(err)
106	}
107	if err := r.Trim(start); err != nil {
108		t.Fatal(err)
109	}
110	// Back on screen, then away again just before idleTTL.
111	if err := render(r, shown, contents); err != nil {
112		t.Fatal(err)
113	}
114	if err := render(r, empty, contents); err != nil {
115		t.Fatal(err)
116	}
117	if err := r.Trim(start.Add(idleTTL - time.Second)); err != nil {
118		t.Fatal(err)
119	}
120	if err := r.Trim(start.Add(idleTTL)); err != nil {
121		t.Fatal(err)
122	}
123	if len(r.pools) != 1 || len(r.shm) != 1 {
124		t.Fatalf("drawn %v ago but freed", time.Second)
125	}
126	if err := r.Trim(start.Add(2*idleTTL - time.Second)); err != nil {
127		t.Fatal(err)
128	}
129	if len(r.pools) != 0 || len(r.shm) != 0 {
130		t.Fatalf("kept after idleTTL: pools %d, copies %d", len(r.pools), len(r.shm))
131	}
132}
133
134// A dmabuf import left undrawn is released by Trim without a frame.
135func TestTrimReleasesUndrawnDMABuf(t *testing.T) {
136	r, err := New(80, 32)
137	if err != nil {
138		t.Skipf("Vulkan unavailable: %v", err)
139	}
140	defer r.Close()
141	linear := ports.DMABufFormat{Format: fourcc('A', 'R', '2', '4'), Modifier: 0}
142	if !slices.Contains(r.DMABuf().Formats, linear) {
143		t.Skipf("linear ARGB8888 not importable: %+v", r.DMABuf())
144	}
145	f := udmabuf(t, 64, 16, func(int, int) [4]byte { return [4]byte{0, 0, 255, 255} })
146	buf := &ports.DMABuf{ID: 9, Width: 64, Height: 16, Format: linear.Format, Planes: []ports.DMABufPlane{{File: f, Stride: 256}}}
147	scene := ports.Scene{Background: "#000000", Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 64, H: 16}}}}
148	contents := map[ports.WindowID]ports.SurfaceContent{1: {ID: 1, Width: 64, Height: 16, DMABuf: buf}}
149	if err := render(r, scene, contents); err != nil {
150		t.Fatal(err)
151	}
152	if err := render(r, ports.Scene{Background: "#000000"}, nil); err != nil {
153		t.Fatal(err)
154	}
155	start := time.Unix(1000, 0)
156	if err := r.Trim(start); err != nil {
157		t.Fatal(err)
158	}
159	trimSettled(t, r, start.Add(idleTTL))
160	if len(r.imports) != 0 {
161		t.Fatalf("imports %d", len(r.imports))
162	}
163}
164
165// poll frees what a finished frame held without waiting for it.
166func TestTrimPollsFinishedFrames(t *testing.T) {
167	r, err := New(32, 32)
168	if err != nil {
169		t.Skipf("Vulkan unavailable: %v", err)
170	}
171	defer r.Close()
172	if !r.syncFD {
173		t.Skip("frames are finished on return without sync files")
174	}
175	if err := render(r, ports.Scene{Background: "#000000"}, nil); err != nil {
176		t.Fatal(err)
177	}
178	freed := false
179	r.retire(r.frame, func() { freed = true })
180	deadline := time.Now().Add(5 * time.Second)
181	for !freed {
182		if time.Now().After(deadline) {
183			t.Fatal("finished frame never polled")
184		}
185		if err := r.Trim(time.Now()); err != nil {
186			t.Fatal(err)
187		}
188	}
189	if r.completed != r.frame {
190		t.Fatalf("completed %d, frame %d", r.completed, r.frame)
191	}
192}

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