MOOR security

Dieser Eintrag ist auf Englisch verfasst.

Derivatives in compute shaders, and a compiler crash that a texture write avoids

Entry 022558 of 799 tests pass

Most of what Shader Model 6.6 adds now works on Metal, including derivatives in compute shaders, which Metal kernels don't have; one Apple GPU limit remains.

What changed

mtl12 translates Direct3D 12 to Apple's Metal, so Windows games can run under Wine on Apple Silicon. Until this session the device reported Shader Model 6.0. Last session, reporting 6.6 for a single run showed seven tests failing for separate reasons. This session went through them in turn, writing a failing test first for each. The device now reports 6.6.

  • Wave size. A wave is a group of threads that run in lockstep; on Metal that is a SIMD-group, and it always has 32 threads. The device now reports 32-lane waves. A compute shader that demands another size fails to compile, as it would on D3D12 hardware with fixed 32-lane waves.
  • Helper lanes. Pixel shaders run in 2x2 quads. A pixel that calls discard stays in its quad as a "helper", so its neighbours can still compute derivatives. IsHelperLane() came out as an undeclared variable in the translated shader. The fix was a small change in SPIRV-Cross, the shader translator (patch 0016).
  • Bitwise multi-prefix scans. Metal's SIMD-group scans only add and multiply. Patch 0017 builds AND, OR and XOR scans out of shuffles.
  • Derivatives in compute shaders. This is the main piece, described below.
  • Raw buffers read as typed buffers. A shader may read a raw buffer view through a typed buffer declaration. These views now get a texture-buffer view as well.
  • Per-sample shading. See below.

An architecture decision records the mappings. A review found one bug: when the extra texture-buffer view for a raw buffer couldn't be created, the whole view was lost. It now keeps the raw view.

Scene derivatives: a compute shader samples a floor with automatic mips: one colour per mip, bands to the horizon
Gallery scene derivatives: a compute shader samples a floor with automatic mips: one colour per mip, bands to the horizon. All scenes in the gallery

The interesting part

Derivatives. SM 6.6 lets compute shaders call ddx() and sample textures with automatic mip selection. Both need derivatives. In compute, derivatives come from 2x2 groups of threads. Metal rejects every derivative function in a kernel. dxil-spirv (the DXIL-to-SPIR-V compiler) already arranges threads so that each quad is four consecutive SIMD lanes. That matches Metal's quadgroups, so the derivative becomes the difference of two quad_shuffle reads. Sampling becomes an explicit-gradient sample built from those differences (patch 0018). The vkd3d-proton test passes 823 checks.

The crash. A per-sample pixel shader runs once per MSAA sample and reads the sample's position. With indirect-command-buffer support turned on, Apple's shader compiler crashes if such a shader writes depth; I found that in entry 018. It turns out a plain buffer store or an atomic crashes it too. A texture write fails with a clean error instead. The compiler never returns in the crashing cases, so there is nothing to catch. Any shader that reads a sample position now gets a pipeline without ICB support.

The one that stays. -denorm preserve asks the GPU to keep very small floats (denormals) instead of flushing them to zero. On an M1 Max, every Metal math mode flushed 32-bit denormals, including precise::fma. That test keeps failing unless floats are emulated in software.

Derivatives in a compute shader: patch 0018 builds them from quad shuffles, which Metal kernels do have.HLSL compute shader ddx(), Sample(), CalculateLevelOfDetail() → dxil-spirv 2D group → linear quads (lanes 4k..4k+3); dxil-spirv 2D group → linear quads (lanes 4k..4k+3) → SPIR-V OpDPdx / implicit-LOD sample; SPIR-V OpDPdx / implicit-LOD sample → stock SPIRV-Cross: dfdx() in a kernel; stock SPIRV-Cross: dfdx() in a kernel → pipeline creation fails (Metal: unsupported builtin); SPIR-V OpDPdx / implicit-LOD sample → patch 0018; patch 0018 → ddx = quad_shuffle(v, 1) − quad_shuffle(v, 0) ddy = quad_shuffle(v, 2) − quad_shuffle(v, 0); patch 0018 → sample(..., gradient2d(ddx, ddy))HLSL compute shaderddx(), Sample(), CalculateLevelOfDetail()dxil-spirv2D group → linear quads(lanes 4k..4k+3)SPIR-V OpDPdx / implicit-LOD samplestock SPIRV-Cross: dfdx() in a kernelpipeline creation failspatch 0018ddx = quad_shuffle(v, 1) − quad_shuffle(v, 0)ddy = quad_shuffle(v, 2) − quad_shuffle(v, 0)sample(..., gradient2d(ddx, ddy))Metal: unsupported builtin
Derivatives in a compute shader: patch 0018 builds them from quad shuffles, which Metal kernels do have. (Mermaid source)
Shaders that read the sample position get a pipeline without indirect-command-buffer support.with ICB support (spike, M1 Max): + depth / buffer / atomic write, compiler crashes or hangs, + texture write, clean error, colour only, compiles; fragment MSL → calls get_sample_position()?; calls get_sample_position()? → pipeline with ICB support ExecuteIndirect encoded on the GPU (no); calls get_sample_position()? → pipeline without ICB support ExecuteIndirect drawn one by one (yes); + depth / buffer / atomic write → compiler crashes or hangs; + texture write → clean error; colour only → compileswith ICB support (spike, M1 Max)fragment MSLcalls get_sample_position()?pipeline with ICB supportExecuteIndirect encoded on the GPUpipeline without ICB supportExecuteIndirect drawn one by one+ depth / buffer / atomic writecompiler crashes or hangs+ texture writeclean errorcolour onlycompilesnoyes
Shaders that read the sample position get a pipeline without indirect-command-buffer support. (Mermaid source)

Numbers

  • Full run at the end, with 6.6 reported: 558 pass, 79 fail, 162 skip of 799 tests. At the end of entry 021: 544, 78 and 170 of 792.
  • Of the 14 new passes, seven are new api tests. Six are vkd3d-proton tests that skipped at 6.0. test_update_tile_mappings_remap_smem is flaky and passed this time.
  • Failures went up by one. Two tests that skipped before now fail one check each: denormals, and a helper lane on the seam of a clipped triangle. The flaky tile test took one failure off.
  • Failed checks in the vkd3d and vkd3d-proton suites: 3,322 to 3,297.
  • A full run with 6.6 reported, just before switching it on for good: 557 pass, 79 fail, 162 skip of 798 tests.
  • Checks passed by the SM 6.x tests with 6.6 reported: wave_intrinsics 582, wave_size 279, compute_derivatives 823, heap_mismatch_types 523.
vkd3d-proton SM 6.x tests with SM 6.6 reported: checks passed (data as CSV)

test · checks ok

  1. sm65_wave_intrinsics582
  2. sm66_wave_size279
  3. sm66_compute_derivatives823
  4. heap_mismatch_types523
Tests by suite at the end of entry 022: 558 of 799 pass (data as CSV)
  • pass
  • fail
  • skip

suite · share of each row's total

  1. vkd3d-proton (+7)367 pass, 54 fail, 156 skip of 577
  2. vkd3d159 pass, 25 fail, 6 skip of 190
  3. api (+7)29 pass, 0 fail, 0 skip of 29
  4. samples3 pass, 0 fail, 0 skip of 3