Metal's SIMD-group functions take no 64-bit values, so they travel in two halves
Any D3D12 shader that ran a wave operation on a 64-bit integer failed to compile on Metal, because Metal's SIMD-group functions have no 64-bit overloads. A SPIRV-Cross patch now splits such values into two 32-bit halves, or loops over the lanes for sums, products, min and max.
What changed
Two things this session. First I corrected last session's number. The timers that put our Windows-side command recording at about 3.5 ms of bgfx's drawstress frame also counted their own cost. With that cost subtracted, recording is about 2 ms of the 23 ms frame (8-9.5% of wall time), nearly all of it in the 16,000 draw calls a frame at about 135 ns each. Every remaining lever on our side is worth at most about 1 ms, so I parked the CPU-overhead work.
Then I took a correctness item: 64-bit integers in shaders (Shader Model 6.2). The new test, api/int64_ops, runs one 32-thread compute dispatch. Thread 0 checks 28 scalar 64-bit operations: carries across the halves, divide, shifts, bit counts, conversions. Every lane then checks 18 results from groupshared memory and wave operations. A wave operation is an instruction that reads values from the other threads running in lockstep with this one, such as WaveActiveSum or QuadReadAcrossX.
The interesting part
The test didn't get as far as running. Pipeline creation failed with "no matching function for call to 'simd_sum'". Metal's SIMD-group and quad-group functions (simd_sum, simd_shuffle, simd_broadcast, quad_shuffle and the rest) have no long or ulong overloads, though they do take uint2. So any D3D12 shader that ran a wave op on a 64-bit integer could never compile.
SPIRV-Cross patch 0042 handles them in three ways:
- Moving a value between lanes (reading another lane, shuffles, quad reads, all-equal): reinterpret the value as two 32-bit halves,
as_type<uint2>(v), move both in one call, and reinterpret back. - Bitwise and, or and xor: the halves don't interact, so the same trick works.
- Sums, products, min and max, both totals and prefix scans: a loop over the active lanes that fetches each lane's halves and combines them as 64-bit numbers. It's the same uniform loop an earlier patch (0017) uses for the bitwise scans Metal lacks. It costs up to 32 shuffles per operation, but stays correct when only some lanes are active.
The device still doesn't advertise 64-bit shader ops to applications. With Shader Model 6.6, that flag also promises 64-bit atomics, which this GPU family (Apple7) lacks.
Numbers
- api/int64_ops: 605 checks pass. Before the patch, pipeline creation failed. The dumped Metal source takes the new path for all 18 64-bit wave ops in the shader.
- Full suite: 849 pass, 28 fail, 76 skip of 953. The only per-test change from entry 064 is the new passing test.
- Recording time, corrected: about 2 ms a frame, about 135 ns per draw call.
- pass
- fail
- skip
suite · share of each row's total
- vkd3d-proton490 pass, 15 fail, 72 skip of 577
- vkd3d177 pass, 9 fail, 4 skip of 190
- api (+1)82 pass, 0 fail, 0 skip of 82
- apps80 pass, 0 fail, 0 skip of 80
- wine-dxgi16 pass, 4 fail, 0 skip of 20
- samples4 pass, 0 fail, 0 skip of 4