Intel Arc never used Vulkan Video because our pNext order fed the decode caps to the wrong struct #92
Merged
enricobuehler
merged 8 commits from 2026-08-07 14:13:16 +00:00
worktree-probe-codec-names into main
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b166c53cc2 |
fix(vkdecode): a refused device says what the refusal costs, and about which format
ci / web (pull_request) Successful in 1m8s
apple / swift (pull_request) Successful in 1m33s
apple / screenshots (pull_request) Skipped
ci / bun-nix (pull_request) Successful in 1m33s
ci / docs-site (pull_request) Successful in 2m2s
ci / rust-arm64 (pull_request) Successful in 2m25s
android / android (pull_request) Successful in 3m1s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 2m13s
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m3s
ci / rust (pull_request) Successful in 11m15s
nix / flake (pull_request) Successful in 12m49s
Three things the Intel Arc measurement showed were wrong or unhelpful in the refusal path. The message named NV12 whatever the stream was. A Main 10 session refused over P010 was told about NV12, which sends the reader to look up the wrong format's support. Both variants now carry the format the driver's own entry reported. A missing SAMPLED now says what it costs. "does not advertise usage SAMPLED" is accurate and tells a field reporter nothing: the consequence is that no shader can read this device's decoded pictures, so the zero-copy path cannot exist on it at all — which is a different conversation from a device that is merely slower. The line points at --probe-decode for the driver's own words. And the probe's second opinion no longer claims to be one. Measured on both vendors, vkGetPhysicalDeviceImageFormatProperties2 answers "creatable" for combinations the video-format query rejects — on NVIDIA too, for SAMPLED alone, which is not a legal video image usage at all. So it does not honour the chained profile list and must not be read as permission; it is still printed, because otherwise everyone who reads a refusal asks the question again, but it is labelled as not authority. Also names the three video ENCODE usage bits, which NVIDIA advertises on decode pictures and the probe was printing as "unrecognised 0xC000". |
||
|
|
c06ee55b61 |
diag(vkdecode): --probe-decode reports what the driver says about video images
ci / bun-nix (pull_request) Successful in 38s
apple / swift (pull_request) Successful in 1m31s
apple / screenshots (pull_request) Skipped
ci / web (pull_request) Successful in 2m11s
ci / docs-site (pull_request) Successful in 2m21s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 2m44s
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m10s
ci / rust-arm64 (pull_request) Successful in 6m29s
android / android (pull_request) Successful in 7m44s
ci / rust (pull_request) Canceled after 9m40s
nix / flake (pull_request) Canceled after 9m38s
The Intel Arc refusal moved one step down the caps query and stopped again: the coincide NV12 entry does not advertise SAMPLED. That sentence is punktfunk's, not the driver's, and the last two times a conclusion was drawn from a sentence of ours the conclusion was wrong. So --probe-decode now prints the driver's own answers instead. For every profile the client can negotiate (H.264 High, H.265 Main and Main 10, AV1 Main 8- and 10-bit) it asks vkGetPhysicalDeviceVideoFormatPropertiesKHR in six usage combinations — the three the image pools really create with, plus DPB|DST without sampling, SAMPLED alone and DST alone, which are what localise a refusal to a half. Each answer is printed as the driver gave it: format, usage and create flags named AND in hex with unrecognised bits called out, image type, tiling. A failed query prints its VkResult rather than vanishing into an empty list. It goes through pf-vkdecode's own query rather than a copy of it, which meant splitting query_formats into a physical-device form — the call never needed the VkDevice the old signature demanded. VideoFormat gains imageType and imageTiling to carry the whole record; VUID-VkImageCreateInfo-pNext-06811 compares both for equality, so they were being assumed rather than read. And because a driver that under-reports usage would be indistinguishable from one that genuinely lacks it, the probe asks a second, independent question — vkGetPhysicalDeviceImageFormatProperties2 over the same profile list — and prints it only where the two disagree. A disagreement is the finding. No behaviour change to any decode path: derivation reads the same fields it did. |
||
|
|
ca667cb79a |
fix(vkdecode): the pNext order decided which struct got the decode caps
ci / web (pull_request) Successful in 1m6s
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m4s
ci / docs-site (pull_request) Successful in 1m14s
apple / swift (pull_request) Successful in 1m26s
apple / screenshots (pull_request) Skipped
ci / rust-arm64 (pull_request) Successful in 1m40s
ci / bun-nix (pull_request) Successful in 2m5s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 2m10s
android / android (pull_request) Successful in 5m17s
ci / rust (pull_request) Successful in 6m44s
Intel Arc never used Vulkan Video decode on Windows. The rung refused every session with "driver advertises neither DPB_AND_OUTPUT_COINCIDE nor DISTINCT" and fell back to D3D11VA — and that refusal was ours. vkGetPhysicalDeviceVideoCapabilitiesKHR was called with the codec capability struct chained BEFORE VkVideoDecodeCapabilitiesKHR (push_next prepends, so the chain was caps -> h265_caps -> decode_caps). On Arc/Windows 101.8724 the driver fills those two by POSITION, not by sType, and returned them SWAPPED. Measured, on glass, both ways: before: decode_flags_raw=12 max_level_idc=1 after: decode_flags_raw=1 max_level_idc=12 12 is STD_VIDEO_H265_LEVEL_IDC_6_2 and 1 is DPB_AND_OUTPUT_COINCIDE. We were reading an H.265 level as a decode-capability bitmask; 12 contains neither 0x1 nor 0x2, so the check concluded the device had no DPB mode. It had one all along. The base struct was fully populated throughout — 15 DPB slots, 8192x8192 max extent — which is what gave the lie away: a driver that answers in that much detail is not declining. NVIDIA and RADV dispatch by sType and do not care about the order, which is exactly why the fleet stayed green and this reached the field. Both orders are spec-legal for us to write; only one survives a driver that assumes the conventional one, and the conventional one — decode caps first, as every Vulkan sample writes it — is now what all three codecs use. ⚠ This does NOT yet give the Arc Vulkan Video. It moves the refusal one step down the same function: the device advertises only COINCIDE (no DISTINCT), and its NV12 coincide entry does not advertise SAMPLED usage, which the zero-copy presenter path needs. Whether that is a second bug of ours or a real Intel constraint is not yet established, and this commit does not claim it either way. Found because the user disbelieved my "Intel driver bug" conclusion. He was right: I had reasoned from our own error message, which is the same circularity the caps logging added in fb1a0a61/a183cac8 now exists to break. Gates: fmt clean; clippy -D warnings; 187 pf-vkdecode tests. The GPU parity legs that cover this code cannot run here (no GPU on the build host) — the evidence is the on-glass A/B above. |
||
|
|
a183cac8aa |
diag(vkdecode): log maxLevelIdc beside the decode flags
ci / bun-nix (pull_request) Successful in 44s
ci / docs-site (pull_request) Successful in 1m21s
apple / swift (pull_request) Successful in 1m33s
ci / web (pull_request) Successful in 1m28s
apple / screenshots (pull_request) Skipped
ci / rust-arm64 (pull_request) Successful in 2m15s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 2m33s
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m11s
ci / rust (pull_request) Successful in 5m23s
android / android (pull_request) Canceled after 6m14s
The Arc returned decode_flags=0b1100 = 12 with a fully populated base struct (15 DPB slots, 8192x8192 max extent). Neither COINCIDE (0x1) nor DISTINCT (0x2) is set, and 0x4|0x8 are not defined for that field at all — but 12 IS STD_VIDEO_H265_LEVEL_IDC_6_2, and VkVideoDecodeCapabilitiesKHR and VkVideoDecodeH265CapabilitiesKHR have identical layouts (sType, pNext, one u32). So the suspicion is that we are reading H.265's maxLevelIdc where the decode flags belong. Logging both settles it: if max_level_idc comes back as 1 or 2 the two structs are crossed, and the refusal is ours rather than the driver's. |
||
|
|
fb1a0a61e9 |
diag(vkdecode): log the driver's video capabilities verbatim
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m9s
ci / bun-nix (pull_request) Successful in 30s
ci / web (pull_request) Successful in 1m20s
apple / swift (pull_request) Successful in 1m28s
apple / screenshots (pull_request) Skipped
ci / docs-site (pull_request) Successful in 1m51s
ci / rust-arm64 (pull_request) Successful in 2m44s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 2m46s
android / android (pull_request) Successful in 4m16s
ci / rust (pull_request) Successful in 4m52s
Nothing in the caps module logged anything, so when a device refused with "advertises neither DPB_AND_OUTPUT_COINCIDE nor DISTINCT" there was no way to separate two very different situations that present identically as a zero: the driver filling the chain and genuinely declaring no DPB mode, versus our own pNext chain never reaching VkVideoDecodeCapabilitiesKHR at all. Printing the BASE VkVideoCapabilitiesKHR beside the decode flags is the discriminator. A populated max_dpb_slots next to decode_flags: 0 means the driver traversed the chain and answered; zeros across both mean the query never landed and the refusal is ours, not the driver's. Raised by the Intel Arc result on .221, where I concluded "driver bug" on the strength of our own code's report — which is precisely the circular reasoning this line exists to break. |
||
|
|
c34e1412fb |
fix(client): a decoder pin with a stray space was silently ignored
ci / bun-nix (pull_request) Successful in 20s
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m7s
apple / swift (pull_request) Successful in 1m26s
apple / screenshots (pull_request) Skipped
ci / web (pull_request) Successful in 1m32s
ci / docs-site (pull_request) Successful in 1m44s
ci / rust-arm64 (pull_request) Successful in 2m23s
android / android (pull_request) Successful in 3m9s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 2m7s
ci / rust (pull_request) Successful in 6m0s
Found on glass, and it cost a whole session to find. PUNKTFUNK_DECODER was read
untrimmed, so "native-vulkan " — ONE trailing space — matched no arm of
native_vulkan_gate, fell through to `auto`, and on an Intel box `auto` takes
d3d11va first. The operator's pin never ran and NOTHING said so. Read against a
log, that is indistinguishable from the rung being refused for a hardware
reason, which is precisely the ambiguity the rest of this module's logging was
just rewritten to remove.
The space is not exotic. A Windows .cmd produces it for free: `echo x>> file`
keeps the space before the redirect, so every line written that way carries one.
PUNKTFUNK_VK_ADAPTER already trimmed; this did not, and the inconsistency is what
made it invisible — the GPU override obeyed while the decoder override did not.
The rule now lives in one pure function, resolve_decoder_pref, called by BOTH
readers. decode_pinned_to_software had the identical untrimmed expression, and
its own doc comment says a second reading of the same two inputs is a second
place for them to drift — fixing one and not the other would have proved it
right. Whitespace-only counts as ABSENT rather than as a pin to "", because an
exported-but-empty variable means "no override" and "" is a value the gate
happens to accept.
Tested as a pure rule (no process environment), including the end-to-end leg
that matters: the trimmed pin reaches native_vulkan_gate and is admitted. Like
the create-array tests in
|
||
|
|
c0f8f051c3 |
fix(client): the probe printed a device index the env var does not take
--probe-decode printed its DISPLAY position and called it the PUNKTFUNK_VK_DEVICE value. It is not. pick_device resolves that variable against the RAW vkEnumeratePhysicalDevices order (setup.rs, `devices.get(i)`) BEFORE any ranking runs, while the probe sorts discrete-first for readability. Those two orders disagree precisely on the hardware this flag exists to diagnose. pick_device's own comment records why the ranking is there: "enumeration order puts the iGPU FIRST on some hybrids (observed: Ryzen iGPU ahead of an RTX dGPU)". So on a hybrid laptop the number the probe printed for the iGPU could well be the number for the dGPU — a diagnostic handing out an actionable value that selects the other GPU, which is worse than printing none. Measured on the Arc + RTX 3500 Ada laptop, which is also where the first output went out with the wrong claim in it: three adapters, and the same Arc iGPU enumerated TWICE. So AdapterDecode now carries the raw enumeration index, captured before the sort, and the printer uses it; the "default presenter" marker stays on the first LISTED entry, because sorted-first is what pick_device lands on when nothing overrides. The duplicate is why the trailing hint names PUNKTFUNK_VK_ADAPTER as the safer knob and admits its limit: two adapters sharing a marketing name cannot be told apart by it, and a name match resolves to whichever enumerates first. The hint also states the thing this whole output invites a reader to get wrong — that a capable GPU in the list does not mean the decoder will use it, because Vulkan Video decodes on the presenter's device and PUNKTFUNK_DECODER does not move the presenter. Gates: fmt clean; clippy -D warnings on punktfunk-client-session and pf-presenter. |
||
|
|
43e713ecca |
fix(client): the probe now accounts for every bit it prints
First hardware run of --probe-decode, on the RTX 5070 Ti:
driver decode ops: H.264, H.265, AV1 (0xF)
Three names, four bits. 0xF is H.264|H.265|AV1|VP9 — bit 3 is
VK_VIDEO_CODEC_OPERATION_DECODE_VP9_BIT_KHR, a real decode operation this
client has no rung for, so the name table stopped short of it and the line
looked complete while silently dropping a codec the driver had advertised.
That is the exact failure this flag exists to prevent. The whole point of
--probe-decode is that a reader can trust the words to cover the number; a mask
with an unexplained bit asks them to trust it instead. VP9 is now named (marked
as having no punktfunk rung, because advertising it as decodable would be its
own lie), and any bit beyond the four we know prints as "unrecognised bits
0x…" rather than vanishing — so the next codec Khronos adds shows up as an
unknown rather than as nothing at all.
Gates: fmt clean; clippy -D warnings on punktfunk-client-session.
|