Triangle fans from ExecuteIndirect, where only the GPU knows how many there are
mtl12 now draws triangle fans and strips with adjacency that a D3D12 program issues with Execute
What changed
Metal has no triangle fans (triangles that all share the first vertex), and no strips with adjacency (strips whose every other vertex is extra data for a geometry shader). mtl12 already draws both for ordinary draw calls. A compute pre-pass rewrites the draw's indices into a plain triangle list, and the CPU works out how long that list will be.
ExecuteIndirect is D3D12's draw call whose arguments live in a GPU buffer: the vertex count, the start and, optionally, which index buffer to use. Often another shader wrote them, so the CPU never sees them. mtl12 used to drop such draws whenever they used a fan or adjacency.
A new test, api/execute_indirect_fan, issues fans and a strip with adjacency through ExecuteIndirect:
- non-indexed fans, with a count buffer that hides the last command;
- 16-bit fans cut by the "strip cut" value 0xFFFF (an index that ends one fan and starts the next);
- 32-bit fans from per-command index buffers, cut at 0xFFFFFFFF;
- a fan of 300 triangles.
Before the change, 800 of its 867 checks failed.
The interesting part
The pre-pass needs each draw's size before it runs, so for ExecuteIndirect the sizing moved to the GPU too. One extra compute thread walks the commands. For each it chooses the conversion, computes how many list indices it will produce, and gives it a slice of a 16 MiB scratch buffer. A second dispatch then converts each command in its own threadgroup, reusing the direct path's helpers. The indirect command buffer (Metal's GPU-encoded draw list) then draws each command as a 32-bit triangle list from its slice.
The conversion has to be chosen per command, not per call. In D3D12 the pipeline fixes the cut value at 0xFFFF or 0xFFFFFFFF, but each command may bring its own index buffer, in either format. A 0xFFFFFFFF cut can't occur in 16-bit indices, so those commands convert without cuts. I checked that this matters by swapping the per-command choice for a plain conversion: 206 checks failed, in all four regions with cuts or a start index.
A command that doesn't fit in the 16 MiB (2^22 indices, about 1.4 million triangles per call) draws nothing. That limit is not tested yet.
Numbers
- api/execute_indirect_fan: 800 of 867 checks failed before, 0 after. The plain-conversion mutation fails 206.
- Full run: 873 of 967 tests pass, 26 fail, 68 skip; 871 of 965 at the end of entry 076. Newly failing: none; newly passing: the two new api tests.
- pass
- fail
- skip
suite · share of each row's total
- vkd3d-proton499 pass, 13 fail, 65 skip of 577
- vkd3d178 pass, 9 fail, 3 skip of 190
- api (+2)96 pass, 0 fail, 0 skip of 96
- 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
build · failed checks
- draws dropped (before)800
- plain assembly (cut mode removed)206
- per-command cut mode0