v0.5.0 5fd5066

image_cache_policy_test.go RawHistoryFolder

1package vkdevice
2
3import (
4	"image"
5	"image/color"
6	"strings"
7	"testing"
8)
9
10type nonComparableImage struct{ data []byte }
11
12func (n nonComparableImage) ColorModel() color.Model { return color.NRGBAModel }
13func (n nonComparableImage) Bounds() image.Rectangle { return image.Rect(0, 0, 1, 1) }
14func (n nonComparableImage) At(x, y int) color.Color { return color.NRGBA{} }
15
16func TestImageCacheBudgetAndFenceReuse(t *testing.T) {
17	p := newImageCachePolicy()
18	p.begin(1, 0)
19	images := make([]*image.RGBA, 64)
20	for i := range images {
21		images[i] = image.NewRGBA(image.Rect(0, 0, 1, 1))
22		if _, _, err := p.reserve(images[i]); err != nil {
23			t.Fatal(err)
24		}
25	}
26	fresh := image.NewRGBA(image.Rect(0, 0, 1, 1))
27	if _, _, err := p.reserve(fresh); err == nil || !strings.Contains(err.Error(), "budget") {
28		t.Fatalf("expected pinned budget: %v", err)
29	}
30	p.begin(2, 0)
31	if _, _, err := p.reserve(fresh); err == nil || !strings.Contains(err.Error(), "budget") {
32		t.Fatalf("expected in-flight budget: %v", err)
33	}
34	if len(p.free) != 0 || len(p.retired) != 0 {
35		t.Fatalf("slot retired before fence: %+v", p)
36	}
37	p.begin(3, 1)
38	entry, destroyed, err := p.reserve(fresh)
39	if err != nil || entry == nil || len(destroyed) != 1 || entry.slot != destroyed[0].slot {
40		t.Fatalf("slot reuse after fence: %+v %v", entry, err)
41	}
42	if _, _, err := p.reserve(nonComparableImage{data: []byte{1}}); err == nil || !strings.Contains(err.Error(), "comparable") {
43		t.Fatalf("expected identity error: %v", err)
44	}
45	if _, _, err := p.reserve(image.NewRGBA(image.Rect(0, 0, 4097, 1))); err == nil || !strings.Contains(err.Error(), "maximum") {
46		t.Fatalf("expected size error: %v", err)
47	}
48}
49
50func TestImageCacheLRUAndBytes(t *testing.T) {
51	p := newImageCachePolicy()
52	old := image.NewRGBA(image.Rect(0, 0, 2048, 2048))
53	other := image.NewRGBA(image.Rect(0, 0, 2048, 2048))
54	p.begin(1, 0)
55	p.reserve(old)
56	p.begin(2, 1)
57	p.reserve(other)
58	p.begin(3, 2)
59	p.reserve(other)
60	newest := image.NewRGBA(image.Rect(0, 0, 4096, 3072))
61	entry, destroyed, err := p.reserve(newest)
62	if err != nil || entry == nil || len(destroyed) == 0 || destroyed[0].key != old || p.bytes > maxImageBytes {
63		t.Fatalf("LRU: entry=%v evicted=%v err=%v bytes=%d", entry, destroyed, err, p.bytes)
64	}
65}
66
67// A frame whose resolution fails must not stall later fence completion or
68// leak the slots it reserved.
69func TestImageCacheAbortCompletesSerialAndFreesSlots(t *testing.T) {
70	c := &imageCache{policy: newImageCachePolicy(), textures: make(map[*imageEntry]*gpuImage), done: make(map[uint64]bool), inflight: make(map[*Frame]struct{})}
71	free := len(c.policy.free)
72	failed := &Frame{}
73	c.serial++
74	failed.imageSerial = c.serial
75	c.policy.begin(c.serial, c.policy.completed)
76	e, _, err := c.policy.reserve(image.NewRGBA(image.Rect(0, 0, 1, 1)))
77	if err != nil {
78		t.Fatal(err)
79	}
80	failed.imageCopies = append(failed.imageCopies, imageCopy{entry: e})
81	c.abort(failed)
82	if len(c.policy.free) != free || c.policy.bytes != 0 || len(c.policy.entries) != 0 {
83		t.Fatalf("abort leaked reservation: free=%d bytes=%d entries=%d", len(c.policy.free), c.policy.bytes, len(c.policy.entries))
84	}
85	c.serial++
86	c.policy.begin(c.serial, c.policy.completed)
87	c.complete(c.serial)
88	if c.policy.completed != c.serial {
89		t.Fatalf("completion stalled at %d, want %d", c.policy.completed, c.serial)
90	}
91}
92
93// After a submitted list frame settles, a failure on the same slot (e.g. a
94// rect submission) must not destroy textures that submitted work used.
95func TestSettledFrameDoesNotAbortSubmittedTextures(t *testing.T) {
96	c := &imageCache{policy: newImageCachePolicy(), textures: make(map[*imageEntry]*gpuImage), done: make(map[uint64]bool), inflight: make(map[*Frame]struct{})}
97	f := &Frame{device: &Device{images: c}}
98	c.serial++
99	f.imageSerial = c.serial
100	c.policy.begin(c.serial, c.policy.completed)
101	img := image.NewRGBA(image.Rect(0, 0, 1, 1))
102	e, _, err := c.policy.reserve(img)
103	if err != nil {
104		t.Fatal(err)
105	}
106	f.imageCopies = append(f.imageCopies, imageCopy{entry: e})
107	f.submitted = true
108	f.settleImages()
109	if f.imageSerial != 0 || len(f.imageCopies) != 0 || c.policy.completed != 1 {
110		t.Fatalf("settle: serial=%d copies=%d completed=%d", f.imageSerial, len(f.imageCopies), c.policy.completed)
111	}
112	// Close before submit (settle on an unsubmitted frame) frees new entries.
113	c.serial++
114	unsent := &Frame{device: f.device, imageSerial: c.serial}
115	c.policy.begin(c.serial, c.policy.completed)
116	fresh := image.NewRGBA(image.Rect(0, 0, 1, 1))
117	fe, _, err := c.policy.reserve(fresh)
118	if err != nil {
119		t.Fatal(err)
120	}
121	unsent.imageCopies = append(unsent.imageCopies, imageCopy{entry: fe})
122	unsent.settleImages()
123	if c.policy.entries[fresh] != nil || c.policy.completed != 2 {
124		t.Fatalf("unsubmitted settle: entry kept=%v completed=%d", c.policy.entries[fresh] != nil, c.policy.completed)
125	}
126	// Settling again (as a later failed submission would) must keep the entry.
127	f.submitted = false
128	f.settleImages()
129	if c.policy.entries[img] != e {
130		t.Fatal("submitted texture entry dropped")
131	}
132}
133
134// A frame slot that stays idle (not re-recorded) after its fence signals must
135// not stall the contiguous completed serial: later frames would otherwise pin
136// retired textures until the 64-slot budget is exhausted.
137func TestIdleSlotDoesNotPinTextures(t *testing.T) {
138	c := &imageCache{policy: newImageCachePolicy(), textures: make(map[*imageEntry]*gpuImage), done: make(map[uint64]bool), inflight: make(map[*Frame]struct{})}
139	d := &Device{images: c}
140	idle := &Frame{device: d}
141	busy := &Frame{device: d}
142	signalled := map[*Frame]bool{}
143	ready := func(f *Frame) (bool, error) { return signalled[f], nil }
144	submit := func(f *Frame) {
145		if err := c.pollInflight(ready); err != nil {
146			t.Fatal(err)
147		}
148		f.settleImages()
149		c.serial++
150		f.imageSerial = c.serial
151		c.policy.begin(c.serial, c.policy.completed)
152		if _, _, err := c.policy.reserve(image.NewRGBA(image.Rect(0, 0, 1, 1))); err != nil {
153			t.Fatalf("frame %d: %v", c.serial, err)
154		}
155		f.submitted = true
156		c.inflight[f] = struct{}{}
157	}
158	submit(idle)
159	signalled[idle] = true // fence done, but the slot is never recorded again
160	for i := 0; i < 3*maxImageTextures; i++ {
161		submit(busy)
162		signalled[busy] = true
163	}
164	if c.policy.completed+1 < c.serial {
165		t.Fatalf("completion stalled at %d of %d", c.policy.completed, c.serial)
166	}
167}

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 1

Tags 12

↑↓ moveenter openesc close