MOOR security

Dieser Eintrag ist auf Englisch verfasst.

Counting, flipping and packing: three small Direct3D 12 options on Metal

Entry 070865 of 961 tests pass

mtl12 now reports three more optional Direct3D 12 features: indirect draws that know their own index, upside-down viewports, and resources packed tighter than 64 KB.

What changed

Direct3D 12 has many small optional features. A game asks for them with CheckFeatureSupport and gets a slower path, or none, if the driver says no. This session turned on three of them.

  • ExecuteIndirect tier 1.1. ExecuteIndirect runs a list of draws whose arguments live in a GPU buffer. Tier 1.1 adds an "incrementing constant": a root constant (a small value passed straight to the shaders) that holds the command's own index in the list, 0, 1, 2 and so on, without the buffer storing it. mtl12 already rewrites each D3D12 command into a Metal record with a small compute kernel. An unused word in each copy instruction became a flag that says "write the command's index here instead of copying".
  • Negative viewport heights. A viewport with a negative height draws the picture upside down. Metal's viewport does the same, so the feature only had to be reported. One bug showed up: a pass with no render targets sized its area from TopLeftY + Height. That is the top edge when the height is negative, so the whole pass was clipped away. The area now runs to the lower of the two edges.
  • Tight resource alignment, tier 1. D3D12 normally places every buffer and texture in a heap at 64 KB boundaries, so a 4-byte buffer costs 64 KB. With USE_TIGHT_ALIGNMENT, the driver may report a smaller alignment.
How an incrementing constant reaches the shader through the expand_indirect kernel.D3D12 command signature [INCREMENTING_CONSTANT -> root param 0, DRAW] → d3d12: one op per argument {src, dst, words, index} INCREMENTING_CONSTANT: words = 1, index = 1; argument buffer command i: draw arguments only → expand_indirect kernel thread (word x, command y); d3d12: one op per argument {src, dst, words, index} INCREMENTING_CONSTANT: words = 1, index = 1 → expand_indirect kernel thread (word x, command y); expand_indirect kernel thread (word x, command y) → op.index set?; op.index set? → write y, the command's index (yes); op.index set? → copy the word from command y's arguments (no); write y, the command's index → Metal record y: draw arguments + root constants; copy the word from command y's arguments → Metal record y: draw arguments + root constants; Metal record y: draw arguments + root constants → the draw reads index as an ordinary root constantD3D12 command signature[INCREMENTING_CONSTANT -> root param 0, DRAW]d3d12: one op per argument {src, dst, words, index}INCREMENTING_CONSTANT: words = 1, index = 1argument buffercommand i: draw arguments onlyexpand_indirect kernelthread (word x, command y)op.index set?write y, the command's indexcopy the word fromcommand y's argumentsMetal record y:draw arguments + root constantsthe draw reads indexas an ordinary root constantyesno
How an incrementing constant reaches the shader through the expand_indirect kernel. (Mermaid source)
Gallery scene flipindirect: one ExecuteIndirect of 60 draw-only records whose INCREMENTING_CONSTANT index places each quad of a spiral, run in the left half with a normal viewport and in the right half with a negative height, which mirrors it; animated, the spiral turns once. All scenes in the gallery

The interesting part

Tight alignment looked like "report Metal's alignment and you're done". On the M1 Max, Metal needs 256 bytes for heap buffers and 128 for the textures I measured, and it refuses a buffer at offset 64. The first version reported exactly that, and vkd3d-proton's test_tight_resource_alignment passed.

Then the older vkd3d test test_resource_alignment went from skipping its tight cases to 2,976 failed checks. It encodes the alignments Windows drivers accept: a committed buffer (one with its own memory) may ask for up to 4 KB, a placed one for up to 256 bytes, a texture for up to the default, and the 64 KB-swizzled layouts not at all. None of that follows from Metal. It is API behaviour that a game could depend on. After following that table, the test passes 69,651 checks with 0 failures. Before this session it ran 27,215 and skipped the tight part.

The explicit Alignment a game passes doesn't change the alignment the driver reports, but it still limits where a placed resource may go. Both rules came from the tests, not from Metal.

Numbers

  • Full run: 865 pass, 27 fail, 69 skip of 961, against 858/27/73 of 958 at the end of the last session. 7 newly passing, 0 newly failing. The baseline rerun gave the same numbers.
  • api/execute_indirect_incrementing: 170 checks, 31 failed before the change. api/negative_viewport: 18 checks, 5 failed without the area fix. api/tight_alignment: 38 checks. It packs five resources, including a 4-byte buffer and two small textures, into one heap under 64 KB and reads them back; 7 checks failed before.
Tests by suite at the end of this entry: 865 of 961 pass (data as CSV)
  • pass
  • fail
  • skip

suite · share of each row's total

  1. vkd3d-proton (+3)497 pass, 14 fail, 66 skip of 577
  2. vkd3d (+1)178 pass, 9 fail, 3 skip of 190
  3. api (+3)90 pass, 0 fail, 0 skip of 90
  4. apps80 pass, 0 fail, 0 skip of 80
  5. wine-dxgi16 pass, 4 fail, 0 skip of 20
  6. samples4 pass, 0 fail, 0 skip of 4