icd_shim: the swapchain image count is the guest's, and one ring behind it
Two commits on top of master, both from the same question: who owns the swapchain's image count.
1. The image count is the guest's (fd48d9d)
The shim asked for minImageCount + 1 — plus one more for mailbox, plus one more for frame generation — while advertising a minImageCount of 2. That is the Winlator app-side swapchain's idiom (caps.minImageCount + 1), carried into a component that writes the capabilities itself, and it produced Mesa's totals only by accident (2 + 1 = 3, 2 + 1 + 1 = 4). What it cost was the ask: a guest that sizes its per-image state from vkGetSwapchainImagesKHR got a number it never asked for, which is exactly what the ten exes in image_count.rs fail on.
Mesa's convention (wsi_common_wayland.c), now followed:
- the caps advertise
SC_DEFAULT_IMAGES = 3(WSI_WL_DEFAULT_NUM_IMAGES) - creation honours the ask, with a mailbox floor of
SC_MAILBOX_IMAGES = 4(WSI_WL_BUMPED_NUM_IMAGES) — a floor, not an addend SC_FRAMEGEN_IMAGES = 3is the one shim-specific term, a floor on the source images, and a no-op for every conforming guest
The count no longer depends on the declared mode, so slot_mode, extra_slot and the create-time choose_live_mode call go. Shipped counts are unchanged for most sessions — DXVK asks minImageCount + 1 of the caps, so 4 either way. The changes are a native guest that honoured the old caps (3 → its own 3) and a vkd3d guest (4 → 3). A game on Mesa's strict list still gets exactly its ask with no bump of any kind.
image_count.rs loses its StrictGame struct with it: with every surface advertising 3, Mesa's vk_x11_override_min_image_count entry (Hades, 3) has nothing to override.
2. One ring behind the guest's images (5b788a0)
A strict game's hidden copies and frame generation's destinations are two mechanisms that never run together (spare_in_use is false whenever generation is live), but they were allocated as two consecutive sets: hidden slots first, then 3 × (multiplier − 1) + 2 destinations after them. A listed game with generation on paid for both — DOOM at x2 held 2 + 4 + 5 = 11 slots at 720p where 7 would do.
They now share one ring sized dest_ring(hidden, destinations) = the larger, never the sum. Sharing is safe because both allocators free a slot through the same listener (on_buffer_release clearing in_flight under shared); the hidden allocator, whose range used to be disjoint, now also skips slots a queued batch is about to be written into (gen_due, this swapchain's only). That guard is what makes a mid-session generation toggle safe.
Memory
| session | slots before | slots after |
|---|---|---|
| DOOM, generation off | 2 + 4 = 6 | 2 + 4 = 6 (unchanged) |
| DOOM, generation x2 | 2 + 4 + 5 = 11 | 2 + 5 = 7 |
| DOOM, generation x4 | 2 + 4 + 11 = 17 | 2 + 11 = 13 |
| non-strict, generation off | ask + 1 | ask |
| non-strict, generation on | ask + 2 | max(ask, 3) |
At 720p one slot is 3.69 MB, so the listed-game-with-generation case saves 14.7 MB per swapchain.
Verified
clippy -D warnings clean (shim, uapi, android, daemon); 124 + 99 + 36 + 13 + 19 + 8 + 2 + 2 tests pass; build-android.sh rc 0; check-exports.sh PASS.
Measured on device (Retroid Pocket Nova, 1280×960)
Menu scenes only, one session per title, each ended through Back → QuickMenu → Exit. Same device, same three titles, the branch payload against master.
Each arm is identified by its rule, not just its md5: ask 3 → 3 images is this branch, ask 3 → 4 and 5 is master's ask + 1 + extra.
| title | arm | ask → slots | swapchains | slot memory | menu |
|---|---|---|---|---|---|
| Hades II | branch | 3 → 3, 3 → 3 | 2 | 29.5 MB | 120 fps |
| Hades II | master | 3 → 4, 3 → 5 | 2 | 44.2 MB | 120 fps |
| Dave | branch | 4 → 4 | 1 | 19.7 MB | 60 fps |
| Dave | master | 3 → 4 | 1 | 19.7 MB | 60 fps |
| Nidhogg | branch | no Vulkan swapchain | — | 0 | 60 fps |
| Nidhogg | master | no Vulkan swapchain | — | 0 | 58 fps |
One slot at this extent is 1280×960×4 = 4.92 MB.
Hades II: −3 slots, −14.7 MB. That is the change working as intended, and it also exposes a defect in the old rule: master gives one process's two swapchains different counts (4 and 5) for the same ask, because slot_mode follows the declared mode and the declared mode races the cap note — one swapchain is created before it (Declared::CapCompat, the guest's FIFO, no spare) and one after (mailbox, spare). The branch gives 3 and 3 whichever instant creation lands.
Dave: unchanged, as designed. It asks caps + 1 (DXVK), so master computes (2+1) + 1 = 4 and this branch 3 + 1 = 4 — the same number from opposite directions. DXVK titles are not what this saves.
Nidhogg: not applicable. OpenGL through Xwayland; it never creates a Vulkan swapchain, so there is no slot for the rule to size.
PSS was recorded and is not used as evidence: it moved −29 MB / +13 MB / −74 MB between arms, far more than the ~15 MB of slots, and in the wrong direction for the title where nothing changed. EGL mtrack does not track the pool either (30.3 MB identical across Hades II's two arms). The created count from proxy swapchain … images is the only deterministic instrument here.
Not verified — device runs pending
- A generation-on session at the new ring size (5 destinations at x2, unchanged in number, but now shared with the hidden range).
- A mid-session generation toggle — the
gen_dueguard is the only thing between a copy and a slot being drawn into.