Rasterizer-ordered views, and a pixel shader that Metal runs four times
D3D12's rasterizer-ordered views now work on Metal, after finding that Metal runs a pixel shader that writes memory once per covered sample in multisampled passes.
What changed
A rasterizer-ordered view (ROV) is a D3D12 UAV (a texture or buffer a shader can write to freely) whose reads and writes happen in the order the triangles were drawn. That lets a pixel shader do its own blending. Metal's counterpart is the raster order group, an attribute on a resource. Metal rejects that attribute on the descriptor arrays the layer uses for D3D's descriptor heap, but accepts it on a member inside each descriptor record. So a SPIRV-Cross patch (0031) gives ordered resources a record type of their own. The device now reports ROVsSupported.

The interesting part
The two vkd3d-proton ROV tests still failed at 4x MSAA. Small native Metal programs showed why. When a pixel-rate fragment shader writes to memory in a multisampled pass, Metal runs it once per covered sample, not once per pixel. At 4 samples a pixel is processed 4 times. The copies can't tell each other apart: each sees the full coverage mask and the pixel-centre position. Every atomic counter and read-modify-write was multiplied. That affected all UAV writes, not only ROVs.
The fix (patch 0032) asks Metal for each copy's sample index and lets only the copy whose index is the lowest covered sample write memory. The others are treated like helper lanes, which SPIRV-Cross already knows how to keep from storing. Every copy still writes its colour sample. Calling discard_fragment() in the extra copies doesn't work, because Metal still performs a discarded fragment's memory writes.
The gallery picture turned up one more thing. Where a card's two triangles meet inside a pixel, the two fragments cover different samples, and the raster order group does not order them: both read before either writes. A 4x4 spike (probe11) shows it every time. My reading is that Metal orders by sample, not by pixel. That is not fixed and is recorded as a known limit.
Numbers
- rov.m, 200 order-dependent updates per draw at 4 samples: wrong texels out of 320 are 320 without a raster order group, 320 with it on the record alone, and 0 with the record plus the gate.
- Gallery
rov: 214 pixels differ across three runs, all within a pixel of a card's inner diagonal. Away from card edges and diagonals the frame matches fixed-function alpha blending to within 2/255. - Full run: 778 pass, 27 fail, 106 skip of 911. 0 newly failing; 3 newly passing: the new api/msaa_uav_once_per_fragment and test_rasterizer_ordered_views_dxbc/_dxil.
- Unverified: raw and structured buffer ROVs are not ordered, since Metal has no raster order group for a pointer, and no test covers them. Early depth testing can drop stores on edge pixels (probe10).
shader variant · wrong texels over 20 runs of 16 pixels
- no raster order group320
- argument-buffer record with ROG320
- argument-buffer record without ROG320
- record with ROG + one-copy gate0
- record without ROG + one-copy gate320
- texture argument with ROG0
- pass
- fail
- skip
suite · share of each row's total
- vkd3d-proton (+2)457 pass, 18 fail, 102 skip of 577
- vkd3d177 pass, 9 fail, 4 skip of 190
- apps77 pass, 0 fail, 0 skip of 77
- api (+1)63 pass, 0 fail, 0 skip of 63
- samples4 pass, 0 fail, 0 skip of 4