MOOR security

Dieser Eintrag ist auf Englisch verfasst.

ExecuteIndirect, and timeouts that were really assertions

Entry 008430 of 772 tests pass

This session added ExecuteIndirect (draws and dispatches whose arguments the GPU writes), reworked typed buffer views so millions of them share a few Metal textures, and found that most test timeouts were Metal validation assertions that hang instead of aborting under Wine.

What changed

ExecuteIndirect lets the GPU write the arguments of draws and dispatches, including the draw count and per-draw root constants (small values passed straight to shaders). Before each barrier-free segment of a command list, a compute pass expands the argument buffer into one record per command: the Metal indirect arguments followed by a copy of the root data. Replay then encodes one Metal indirect draw or dispatch per possible command, and commands past the count get zeroed records.

A typed buffer view (a buffer read through a texel format) used to be its own Metal texture. Now its descriptor holds an element offset and a count into one texture buffer per large region, and the shader applies them. A test creating 13.5 million views stopped timing out.

Destroyed resources now stay alive until the GPU finishes work submitted before the destroy, because Metal 4 command buffers don't retain what they use. Overlapping buffer copies get a barrier between them, which fixed a flaky test.

Scene indirect: ExecuteIndirect, 48 records of [root constants | draw] making three rings
Gallery scene indirect: ExecuteIndirect, 48 records of [root constants | draw] making three rings. All scenes in the gallery

The interesting part

Five tests timed out. None of them was slow. Each had tripped a Metal validation assertion, for example a blend state on an integer render target. Under Wine such an assertion doesn't end the process: the failing call never returns normally, so any lock held around it stays held, and the next call that takes that lock blocks forever. The test runner then reports a timeout, not a crash. The fix is to reject those cases on the D3D12 side before Metal sees them.

A smaller surprise: D3D12 step rate 0 means every instance reads the same vertex attribute, starting at the draw's first instance. Metal's matching "constant" step function ignores that first instance, so I use a per-instance step rate of 2^32−1 instead.

How one ExecuteIndirect record becomes a Metal indirect draw.D3D12 argument buffer (root constants, draw args) → expand pass (compute kernel); expand pass (compute kernel) → one indirect record: Metal draw args, then the root block; one indirect record: Metal draw args, then the root block → drawIndirect reads the Metal args; one indirect record: Metal draw args, then the root block → shaders read the root blockD3D12 argument buffer(root constants, draw args)expand pass(compute kernel)one indirect record:Metal draw args,then the root blockdrawIndirectreads the Metal argsshaders readthe root block
How one ExecuteIndirect record becomes a Metal indirect draw. (Mermaid source)

Numbers

  • Final full run of the session: 430 of 772 tests pass, up from 408, with 0 newly failing. 22 tests newly pass, among them 6 ExecuteIndirect tests, 3 instance-ID tests (the step-rate fix) and the 5 former assertion timeouts.
  • Timeouts went from 9 to 1; the remaining one needs shared fences, which aren't implemented.
  • test_copy_buffer_overlap had 7,663 to 19,702 failed checks per run across 14 runs today. After the overlap fix it passed 3 of 3 targeted runs and the final full run.
Tests by suite at the end of entry 008: 430 of 772 pass (data as CSV)
  • pass
  • fail
  • skip

suite · share of each row's total

  1. vkd3d-proton (+19)285 pass, 113 fail, 179 skip of 577
  2. vkd3d (+3)141 pass, 38 fail, 11 skip of 190
  3. api3 pass, 0 fail, 0 skip of 3
  4. samples1 pass, 1 fail, 0 skip of 2