MOOR security

Dieser Eintrag ist auf Englisch verfasst.

Strip cuts Metal can't turn off, and crashes that weren't ours

Entry 007408 of 772 tests pass

This session turned on DXIL shaders (shader model 6.0), added the newer command-list interfaces for dynamic depth bias, per-face stencil and dynamic strip cut, and emulated D3D12's index-buffer strip cut on Metal, which always cuts strips at the largest index value.

What changed

Pixel shaders can now write two colours for dual-source blending (blending with a second colour output of the same shader). When a pixel shader reads fewer components than the vertex shader wrote, its inputs are widened, because Metal needs the same type on both sides. The device reports shader model 6.0, so tests now also run their DXIL variants (DXIL is the compiled shader format of modern HLSL). DXIL goes through the same dxil-spirv and SPIRV-Cross path as before. That made 19 _dxil tests pass with none newly failing.

ID3D12GraphicsCommandList8 and 9 are exposed. Depth bias can be set per command list, and front and back faces get separate stencil references and masks.

The interesting part

A strip (a triangle or line strip, where each new index adds a primitive) can be cut by a special index value. D3D12 lets the pipeline pick the cut value: none, 0xFFFF or 0xFFFFFFFF, independently of the index size. Metal always cuts at the largest value of the index type, and nothing turns that off. So 16-bit indices with cutting disabled lose the vertex 0xFFFF, and 32-bit indices can't cut at 0xFFFF.

The fix rewrites the indices of just those draws into a 32-bit scratch buffer on the GPU. A CPU copy wouldn't do, because the GPU may have written the index data earlier in the same list. To avoid splitting a render pass once per draw, replay scans ahead to the next barrier and converts all such draws in one compute pass. Index data can't change between them without a barrier. One case is still wrong: 32-bit indices with cutting disabled, where no index value escapes Metal's cut.

The dynamic strip-cut test showed a second bug. Its viewport and scissor are zero-sized, and I had treated such draws as no-ops. D3D12 still runs the vertex shader, and this test counts vertex shader invocations through buffer writes. Now those draws run with the viewport moved outside a 1×1 scissor.

I also triaged the crashing tests. Most were not crashes in our code. They were tests that ignore an E_NOTIMPL from pipeline creation (geometry shaders, tessellation, stream output) and then use the NULL pipeline, or that keep going after skipping. One was a known out-of-bounds read in a test.

Strip-cut draws between two barriers: one compute pass rewrites their indices before the render encoders run.barrier → replay scans ahead to the next barrier; replay scans ahead to the next barrier → one compute pass rewrites the indices of every strip-cut draw into 32-bit scratch; one compute pass rewrites the indices of every strip-cut draw into 32-bit scratch → render encoder 1; render encoder 1 → render encoder 2; render encoder 2 → render encoder 3; render encoder 3 → next barrierbarrierreplay scans aheadto the next barrierone compute pass rewrites the indicesof every strip-cut draw into 32-bit scratchrender encoder 1render encoder 2render encoder 3next barrier
Strip-cut draws between two barriers: one compute pass rewrites their indices before the render encoders run. (Mermaid source)

Numbers

  • Full run: 408 of 772 tests pass, up from 380 in the previous full run, with 0 newly failing. 28 tests newly pass, most of them DXIL variants.
  • 20 tests moved from skip to fail. They are DXIL variants of features Metal lacks and we haven't emulated yet (geometry shaders, tessellation, clip distance), plus the dynamic strip-cut test with 1 of 36 checks failing.
  • Crashes went from 26 to 28 and timeouts from 8 to 9. Five tests stopped crashing or timing out: both dual-source blending tests, both shader input/output tests and the depth-bias one. The eight new entries are DXIL variants of tests whose DXBC versions already crash or time out.
Tests by suite at the end of entry 007: 408 of 772 pass (data as CSV)
  • pass
  • fail
  • skip

suite · share of each row's total

  1. vkd3d-proton (+25)266 pass, 132 fail, 179 skip of 577
  2. vkd3d (+3)138 pass, 41 fail, 11 skip of 190
  3. api3 pass, 0 fail, 0 skip of 3
  4. samples1 pass, 1 fail, 0 skip of 2