MOOR security

Dieser Eintrag ist auf Englisch verfasst.

Geometry shaders on tessellation, and stream output out of a GPU stage Metal doesn't have

Entry 030653 of 824 tests pass

The D3D12-on-Metal layer now runs geometry shaders after tessellation and captures their output with stream output, two D3D features Metal has no equivalent for; 17 more tests pass and none regressed.

What changed

A geometry shader (GS) is a D3D pipeline stage that takes whole primitives and emits new ones. Metal has no such stage, so since entry 028 the layer runs the GS as an object function (a compute-like stage that feeds a draw inside the same render pass). This session closed the gaps around it:

  • GS after tessellation. The domain shader, the last tessellation stage, now captures one record per tessellated vertex, and the GS reads those records.
  • Indexed draws. Draws that use an index buffer now work with a GS or tessellation.
  • Stream output from a GS. Stream output, D3D's "write the vertices you just produced into a buffer", now works for a GS, stream 0 only.

Smaller fixes this session:

  • pipeline creation checks that each stage's outputs match the next stage's inputs;
  • occlusion queries can overlap;
  • invalid resource barriers fail validation;
  • write-watched upload heaps work.
Scene gsstreamout: a GS turns 7 points into ring strips, captured by stream output; the right copy draws the capture as a list, every other triangle darker
Gallery scene gsstreamout: a GS turns 7 points into ring strips, captured by stream output; the right copy draws the capture as a list, every other triangle darker. All scenes in the gallery

The interesting part

Metal has no transform feedback either. So after the GS runs, one GPU thread (gs_so_plan) walks what the GS emitted and turns each triangle strip into separate triangles. A second vertex function then writes those triangles into the D3D buffers. The planning thread also decides how many whole triangles fit in every target buffer, and it advances the buffers' filled sizes and the stream-output queries.

The order matters. For odd triangles in a strip, D3D stores the vertices as (i, i+2, i+1), not (i, i+1, i+2), so that every triangle keeps the same winding. In the picture, the right half redraws the captured buffer as a plain triangle list, with every other triangle darker. Those darker triangles are the odd ones.

Getting stream output to work exposed a bug that had been hidden. vkd3d-proton's test_primitive_id_read_tess_geom draws 4 patches in 2 instances. It expects each shader's SV_PrimitiveID (the index of the primitive being processed) to restart at 0 with every instance. The hull and domain shader emulation had used a single patch counter for two jobs: the ID the shader sees, and the index into its own buffers. That counter ran from 0 to 7 across the whole draw. Before this session, the test failed at pipeline creation, so the wrong IDs never showed. SPIRV-Cross patch 0026 splits the counter into two values.

Numbers

  • Full run: 653 pass, 27 fail and 144 skip, out of 824 tests. Before this session it was 636 / 39 / 143 out of 818. There were no new failures and 17 newly passing tests.
  • api/gs_stream_output: 167 checks. api/tessellation_geometry: 257 checks. test_primitive_id_read_tess_geom: 210 checks. All pass.
  • One vkd3d-proton test, test_dstorage_decompression, went from fail to skip. The layer now reports no meta commands, and the test skips itself when it finds none.