test(vulkan): cover descriptor invalidation on release and busy-slot batch trimming
- commit
- 32dfd2bddd32301cbf6772e4763776a6f3d4096d
- author
- brice · · 2026-10-14 21:16 UTC
- parent
- 8624a64
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
1 file · +64 -10
M internal/adapters/vulkan/compose_batch_test.go +64 -10
| @@ -206,15 +206,69 @@ func TestComposePreparedReuseAndGrowth(t *testing.T) { | |||
| 206 | 206 | if batches, err := r.prepareCompose(slot, ds); err != nil || len(batches) != 1 || len(slot.batches) != 1 { | |
| 207 | 207 | t.Fatal("unused batches retained beyond frame TTL") | |
| 208 | 208 | } | |
| 209 | - | key := resourceKey{shm: &shmCopy{}} | |
| 210 | - | slot.batches[0].cached[0] = key | |
| 211 | - | r.invalidateCompose(key) | |
| 212 | - | if slot.batches[0].valid || slot.batches[0].cached[0] != (resourceKey{}) { | |
| 213 | - | t.Fatal("released resource kept a valid descriptor cache") | |
| 214 | - | } | |
| 215 | - | r.frame++ | |
| 216 | - | r.trimCompose(r.frame - 1) | |
| 217 | - | if len(slot.batches) != 0 { | |
| 218 | - | t.Fatal("idle batches retained beyond wall-clock cutoff") | |
| 209 | + | } | |
| 210 | + | ||
| 211 | + | // Zero handles: release, freeShmCopy and trimCompose make no Vulkan call. | |
| 212 | + | func TestComposeReleaseInvalidatesCachedDescriptors(t *testing.T) { | |
| 213 | + | im, shm := &imported{fd: -1}, &shmCopy{} | |
| 214 | + | other := resourceKey{shm: &shmCopy{}} | |
| 215 | + | for _, tc := range []struct { | |
| 216 | + | name string | |
| 217 | + | key resourceKey | |
| 218 | + | release func(*Renderer) | |
| 219 | + | }{ | |
| 220 | + | {"dmabuf", resourceKey{im: im}, func(r *Renderer) { r.release(im) }}, | |
| 221 | + | {"shm", resourceKey{shm: shm}, func(r *Renderer) { r.freeShmCopy(shm) }}, | |
| 222 | + | } { | |
| 223 | + | t.Run(tc.name, func(t *testing.T) { | |
| 224 | + | r := &Renderer{} | |
| 225 | + | for i := range r.slots { | |
| 226 | + | hit := &composeBatch{valid: true} | |
| 227 | + | hit.keys[0], hit.cached[0], hit.cached[1] = tc.key, tc.key, other | |
| 228 | + | miss := &composeBatch{valid: true} | |
| 229 | + | miss.cached[0] = other | |
| 230 | + | r.slots[i].batches = []*composeBatch{hit, miss} | |
| 231 | + | } | |
| 232 | + | tc.release(r) | |
| 233 | + | for i := range r.slots { | |
| 234 | + | hit, miss := r.slots[i].batches[0], r.slots[i].batches[1] | |
| 235 | + | if hit.valid || hit.cached[0] != (resourceKey{}) || hit.keys[0] != (resourceKey{}) || hit.cached[1] != other { | |
| 236 | + | t.Fatalf("slot %d: released resource kept its descriptor cache", i) | |
| 237 | + | } | |
| 238 | + | if !miss.valid || miss.cached[0] != other { | |
| 239 | + | t.Fatalf("slot %d: unrelated batch invalidated", i) | |
| 240 | + | } | |
| 241 | + | } | |
| 242 | + | }) | |
| 243 | + | } | |
| 244 | + | } | |
| 245 | + | ||
| 246 | + | func TestTrimComposeKeepsInFlightAndRecentBatches(t *testing.T) { | |
| 247 | + | const frame = 100 | |
| 248 | + | for _, tc := range []struct { | |
| 249 | + | name string | |
| 250 | + | busy bool | |
| 251 | + | cutoff uint64 | |
| 252 | + | last []uint64 // per batch, in slot order | |
| 253 | + | want int | |
| 254 | + | }{ | |
| 255 | + | {"idle stale", false, 50, []uint64{10, 20}, 0}, | |
| 256 | + | {"busy stale", true, 50, []uint64{10, 20}, 2}, | |
| 257 | + | {"newer than cutoff", false, 50, []uint64{10, 60}, 2}, | |
| 258 | + | {"current frame", false, frame, []uint64{10, frame}, 2}, | |
| 259 | + | {"only trailing stale", false, 50, []uint64{60, 10}, 1}, | |
| 260 | + | } { | |
| 261 | + | t.Run(tc.name, func(t *testing.T) { | |
| 262 | + | r := &Renderer{frame: frame} | |
| 263 | + | s := &r.slots[0] | |
| 264 | + | s.busy = tc.busy | |
| 265 | + | for _, last := range tc.last { | |
| 266 | + | s.batches = append(s.batches, &composeBatch{last: last}) | |
| 267 | + | } | |
| 268 | + | r.trimCompose(tc.cutoff) | |
| 269 | + | if len(s.batches) != tc.want { | |
| 270 | + | t.Fatalf("kept %d batches, want %d", len(s.batches), tc.want) | |
| 271 | + | } | |
| 272 | + | }) | |
| 219 | 273 | } | |
| 220 | 274 | } | |
| @@ -206,15 +206,69 @@ func TestComposePreparedReuseAndGrowth(t *testing.T) { | |||||
| 206 | if batches, err := r.prepareCompose(slot, ds); err != nil || len(batches) != 1 || len(slot.batches) != 1 { | 206 | if batches, err := r.prepareCompose(slot, ds); err != nil || len(batches) != 1 || len(slot.batches) != 1 { | ||
| 207 | t.Fatal("unused batches retained beyond frame TTL") | 207 | t.Fatal("unused batches retained beyond frame TTL") | ||
| 208 | } | 208 | } | ||
| 209 | - | key := resourceKey{shm: &shmCopy{}} | 209 | + | } |
| 210 | - | slot.batches[0].cached[0] = key | 210 | + | |
| 211 | - | r.invalidateCompose(key) | 211 | + | // Zero handles: release, freeShmCopy and trimCompose make no Vulkan call. |
| 212 | - | if slot.batches[0].valid || slot.batches[0].cached[0] != (resourceKey{}) { | 212 | + | func TestComposeReleaseInvalidatesCachedDescriptors(t *testing.T) { |
| 213 | - | t.Fatal("released resource kept a valid descriptor cache") | 213 | + | im, shm := &imported{fd: -1}, &shmCopy{} |
| 214 | - | } | 214 | + | other := resourceKey{shm: &shmCopy{}} |
| 215 | - | r.frame++ | 215 | + | for _, tc := range []struct { |
| 216 | - | r.trimCompose(r.frame - 1) | 216 | + | name string |
| 217 | - | if len(slot.batches) != 0 { | 217 | + | key resourceKey |
| 218 | - | t.Fatal("idle batches retained beyond wall-clock cutoff") | 218 | + | release func(*Renderer) |
| 219 | + | }{ | |||
| 220 | + | {"dmabuf", resourceKey{im: im}, func(r *Renderer) { r.release(im) }}, | |||
| 221 | + | {"shm", resourceKey{shm: shm}, func(r *Renderer) { r.freeShmCopy(shm) }}, | |||
| 222 | + | } { | |||
| 223 | + | t.Run(tc.name, func(t *testing.T) { | |||
| 224 | + | r := &Renderer{} | |||
| 225 | + | for i := range r.slots { | |||
| 226 | + | hit := &composeBatch{valid: true} | |||
| 227 | + | hit.keys[0], hit.cached[0], hit.cached[1] = tc.key, tc.key, other | |||
| 228 | + | miss := &composeBatch{valid: true} | |||
| 229 | + | miss.cached[0] = other | |||
| 230 | + | r.slots[i].batches = []*composeBatch{hit, miss} | |||
| 231 | + | } | |||
| 232 | + | tc.release(r) | |||
| 233 | + | for i := range r.slots { | |||
| 234 | + | hit, miss := r.slots[i].batches[0], r.slots[i].batches[1] | |||
| 235 | + | if hit.valid || hit.cached[0] != (resourceKey{}) || hit.keys[0] != (resourceKey{}) || hit.cached[1] != other { | |||
| 236 | + | t.Fatalf("slot %d: released resource kept its descriptor cache", i) | |||
| 237 | + | } | |||
| 238 | + | if !miss.valid || miss.cached[0] != other { | |||
| 239 | + | t.Fatalf("slot %d: unrelated batch invalidated", i) | |||
| 240 | + | } | |||
| 241 | + | } | |||
| 242 | + | }) | |||
| 243 | + | } | |||
| 244 | + | } | |||
| 245 | + | ||||
| 246 | + | func TestTrimComposeKeepsInFlightAndRecentBatches(t *testing.T) { | |||
| 247 | + | const frame = 100 | |||
| 248 | + | for _, tc := range []struct { | |||
| 249 | + | name string | |||
| 250 | + | busy bool | |||
| 251 | + | cutoff uint64 | |||
| 252 | + | last []uint64 // per batch, in slot order | |||
| 253 | + | want int | |||
| 254 | + | }{ | |||
| 255 | + | {"idle stale", false, 50, []uint64{10, 20}, 0}, | |||
| 256 | + | {"busy stale", true, 50, []uint64{10, 20}, 2}, | |||
| 257 | + | {"newer than cutoff", false, 50, []uint64{10, 60}, 2}, | |||
| 258 | + | {"current frame", false, frame, []uint64{10, frame}, 2}, | |||
| 259 | + | {"only trailing stale", false, 50, []uint64{60, 10}, 1}, | |||
| 260 | + | } { | |||
| 261 | + | t.Run(tc.name, func(t *testing.T) { | |||
| 262 | + | r := &Renderer{frame: frame} | |||
| 263 | + | s := &r.slots[0] | |||
| 264 | + | s.busy = tc.busy | |||
| 265 | + | for _, last := range tc.last { | |||
| 266 | + | s.batches = append(s.batches, &composeBatch{last: last}) | |||
| 267 | + | } | |||
| 268 | + | r.trimCompose(tc.cutoff) | |||
| 269 | + | if len(s.batches) != tc.want { | |||
| 270 | + | t.Fatalf("kept %d batches, want %d", len(s.batches), tc.want) | |||
| 271 | + | } | |||
| 272 | + | }) | |||
| 219 | } | 273 | } | ||
| 220 | } | 274 | } | ||