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}