icd_shim: the X11 wrapper's feature lift, served by the shim
What this does
Under X11 a container's Vulkan ICD is libvulkan_wrapper.so; under gamescope it is this shim. The wrapper reports a fixed set of VkPhysicalDeviceFeatures whatever the driver answers, then ANDs each one back to the driver's own answer at vkCreateDevice (wrapper_physical_device.c, wrapper_device.c's DISABLE_FEATURE). Both halves are the mechanism — a driver that gates a feature it implements answers VK_ERROR_FEATURE_NOT_PRESENT for a create that requests the bit, so advertising alone turns a higher D3D feature level into a failed device.
advertise.rs carries the same pair so a gamescope guest sees what an X11 guest sees, in the layer that already sits between the guest and the driver:
geometryShader,multiViewport,depthClamp,depthBiasClamp,fillModeNonSolid,shaderClipDistance,shaderCullDistance,dualSrcBlend,multiDrawIndirect,vertexPipelineStoresAndAtomicsreported as supported invkGetPhysicalDeviceFeatures2;- masked back to the driver's answer at create on both ways a guest can ask (
pEnabledFeaturesand aVkPhysicalDeviceFeatures2in the create'spNext), then restored.
textureCompressionBC is deliberately excluded: the wrapper backs that bit with real BCN decoding (bcdec.c, the bc6/bc7 compute shaders) and this shim has no such path, so advertising it would trade a clean unsupported-format for corrupt textures. logicOp and variableMultisampleRate stay the driver's answer, as in the wrapper.
Measured
Retroid Pocket RG_55G1, Adreno A12, Ori DE, one container, shim as the ICD, GNDRM_LOG_VKSHIM=1:
| driver | bits the lift raises | D3D11 create |
|---|---|---|
Turnip v26.1.0 A12 Fix | none — the driver already reports all ten | succeeds, D3D_FEATURE_LEVEL_11_1 |
Turnip v26.3.0-R6 | multiViewport only, masked again before the driver | driver create failed res=-8 (VK_ERROR_FEATURE_NOT_PRESENT) |
So the lift is inert on a driver that reports its capabilities, and it is not a fix for R6. On R6 the guest's only core-feature mismatch is multiViewport (masked), and the create still fails on a feature in the extension chain — the chain carries MAINTENANCE_5, VERTEX_ATTRIBUTE_DIVISOR, GRAPHICS_PIPELINE_LIBRARY, EXTENDED_DYNAMIC_STATE_3, CUSTOM_BORDER_COLOR, ROBUSTNESS_2, Vulkan 1.1/1.2/1.3 features and others, none of which the wrapper fakes either. Unity catches the failure and falls back to D3D9, which is why the game appears to run on R6.
Diagnostics
Two one-shot lines and two per-create lines, all under GNDRM_LOG_VKSHIM:
driver core features zero: <bits the driver answers 0>
wrapper-style feature lift over the driver's zeros: <bits raised>
feature lift masked back to the driver's answer: <bits dropped>
guest requests features the driver answers zero: <bits>
proxy device: driver create failed res=<VkResult>
proxy device: create chain [<sType>:<words> ...]
These exist because the guest's own log reports only a generic E_FAIL, and a VK_ERROR_FEATURE_NOT_PRESENT names no feature. The chain dump is scaffolding for identifying a rejected extension feature and can go once that is settled.
Risks
- On a driver that reports its capabilities the change is a no-op by construction, and that is what the A12 Fix run shows.
- On a driver that gates a feature it implements, the lift advertises what the driver will not enable — the same exposure the wrapper accepts, mitigated only by the
masked backline naming the bits. - If the driver answers neither features query (
driver_core→None), no masking happens at all: an unreadable base must not be read as "supports nothing". - The mask mutates guest-owned structs and restores them after the create, the same discipline as the existing unlink/relink of stripped structs.
Verification
cargo test --workspace 260 passed; clippy clean on host and aarch64-linux-android; tools/build-android.sh builds; check-exports.sh PASS (3 exports, allowlist unchanged).