MOOR security

Dieser Eintrag ist auf Englisch verfasst.

Conservative rasterization on an M1: draw a bigger polygon

Entry 046797 of 915 tests pass

D3D12 conservative rasterization, which Metal lacks on an M1 Mac, is now emulated by replacing each triangle with a slightly larger polygon before Metal rasterizes it.

What changed

Normally a GPU lights a pixel only if the triangle covers the pixel's centre. With conservative rasterization it lights every pixel the triangle touches at all. Voxelizers and some shadow and occlusion techniques depend on this, because thin or tiny triangles must not fall between pixel centres. Metal has no such mode on this M1 Max, so the layer reported it as not supported. It now reports Tier 1, which allows the result to be off by up to half a pixel.

A pipeline with conservative rasterization now takes the path the layer already uses for geometry shaders. The vertex shader writes its outputs to a buffer. A generated compute-like "object function" then replaces each triangle with a polygon of up to 7 vertices: the triangle's pixel-aligned bounding box, clipped against the triangle's three edges, each moved outwards by just over half a pixel. Every pixel whose square touches the triangle has its centre inside that polygon, so Metal's ordinary rasterizer lights it. The new vertices get their attributes (colours, texture coordinates) by extrapolating the original triangle, with perspective correction, as D3D12 allows. With multisampling (several coverage samples per pixel) the edges move out a full pixel so that every sample is covered, and a new SPIRV-Cross patch (0035) discards the extra pixels in the fragment function.

What happens to one triangle: capture, enlarge and clip in the object function, rasterize, and the MSAA discard.Draw with ConservativeRaster ON → Vertex shader captures its outputs; Vertex shader captures its outputs → Object function: one thread per triangle; Object function: one thread per triangle → w > 0 and area > 0?; w > 0 and area > 0? → Emit the triangle unchanged (no); w > 0 and area > 0? → Clip the pixel-aligned box against the edges moved out by h: 1/2 + 1/256, or 1 + 1/256 with MSAA (yes); Clip the pixel-aligned box against the edges moved out by h: 1/2 + 1/256, or 1 + 1/256 with MSAA → Snap to 1/256, weights λ and β; Snap to 1/256, weights λ and β → Blend attributes, emit a strip of up to 7 vertices; Emit the triangle unchanged → Metal rasterizes; Blend attributes, emit a strip of up to 7 vertices → Metal rasterizes; Metal rasterizes → MSAA?; MSAA? → Pixel shader (1x); MSAA? → Discard pixels whose edge functions are < 0 (MSAA); Discard pixels whose edge functions are < 0 → Pixel shaderDraw withConservativeRaster ONVertex shadercaptures its outputsObject function:one thread per trianglew > 0 andarea > 0?Emit the triangleunchangedClip the pixel-aligned boxagainst the edgesmoved out by h:1/2 + 1/256, or1 + 1/256 with MSAASnap to 1/256,weights λ and βBlend attributes,emit a strip ofup to 7 verticesMetal rasterizesMSAA?Pixel shaderDiscard pixels whoseedge functions are < 0noyes1xMSAA
What happens to one triangle: capture, enlarge and clip in the object function, rasterize, and the MSAA discard. (Mermaid source)
Gallery scene conservative: a 32x18 target enlarged 20 times, with a pixel grid and the exact triangles as white lines: three thin slivers drawn normally (left) break into dashes or vanish, and with ConservativeRaster ON (right) light every pixel they touch; animated, the slivers turn one full circle. All scenes in the gallery

The interesting part

The first version got the coverage right but the attributes 0.4% wrong on a long sliver. The cause was Metal's vertex snapping: the rasterizer rounds vertex positions to 1/256 of a pixel. For a normal triangle that doesn't matter, but these new vertices sit on the plane of an extrapolated attribute, and rounding them moves them off it. Rounding the polygon's vertices to the same 1/256 grid before computing their weights fixed it.

The review found two combinations that are now switched off with a warning rather than drawn wrong: strip cut indices (the capture would treat them as vertices) and wireframe (invalid with conservative rasterization in D3D12).

The gallery scene shows three thin slivers on a 32x18 target, enlarged 20 times. Drawn normally (left), they break into dashes or vanish entirely. Drawn conservatively (right), every touched pixel lights up.

Numbers

  • Full run: 797 pass, 26 fail, 92 skip of 915; 0 newly failing, 1 newly passing, the new api/conservative_raster (2,160 checks over 8 cases).
  • Previous run: 796 pass, 26 fail, 92 skip of 914.
  • vkd3d-proton's test_conservative_rasterization_dxbc and _dxil now run 28 checks each instead of 10, all passing; they still skip their Tier 3 part.
Tests by suite at the end of this entry: 797 of 915 pass (data as CSV)
  • pass
  • fail
  • skip

suite · share of each row's total

  1. vkd3d-proton472 pass, 17 fail, 88 skip of 577
  2. vkd3d177 pass, 9 fail, 4 skip of 190
  3. apps77 pass, 0 fail, 0 skip of 77
  4. api (+1)67 pass, 0 fail, 0 skip of 67
  5. samples4 pass, 0 fail, 0 skip of 4