icd_shim: the X11 wrapper's feature lift, served by the shim

Merged
JO
8d ago
+474-1

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, vertexPipelineStoresAndAtomics reported as supported in vkGetPhysicalDeviceFeatures2;
  • masked back to the driver's answer at create on both ways a guest can ask (pEnabledFeatures and a VkPhysicalDeviceFeatures2 in the create's pNext), 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:

driverbits the lift raisesD3D11 create
Turnip v26.1.0 A12 Fixnone — the driver already reports all tensucceeds, D3D_FEATURE_LEVEL_11_1
Turnip v26.3.0-R6multiViewport only, masked again before the driverdriver 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 back line 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).