MOOR security

Dieser Eintrag ist auf Englisch verfasst.

One draw, four views: view instancing by replay

Entry 042781 of 912 tests pass

D3D12 view instancing, where one draw renders the same geometry for up to four cameras, now works on Metal by replaying each draw once per view.

What changed

D3D12 view instancing lets one draw run for up to four "views", for example the two eyes of a VR headset or the faces of a cube map. The shaders read an SV_ViewID, and the pipeline sends each view to its own layer of an array texture and its own viewport (a rectangle of the target). The layer now reports view instancing tier 1, the level at which the driver may implement all of this however it likes.

The way I chose is plain: every draw, indexed draw, mesh dispatch and ExecuteIndirect is recorded once per view. Each copy carries one extra 32-bit word after the root arguments (the small block of constants and pointers a D3D12 draw passes to its shaders), holding the view number and the view's layer. dxil-spirv, the DXIL-to-SPIR-V compiler this project uses, already knew how to read such a word for vkd3d-proton. A small patch lets it come from our root arguments rather than a separate buffer. Which viewport each view uses is known when the pipeline is created, so it becomes a literal in the Metal shader source.

How one D3D12 draw becomes one Metal draw per view, and what the view word holds.DrawInstanced / DrawIndexedInstanced / DispatchMesh / ExecuteIndirect → views = all views, or mask & views with masking; views = all views, or mask & views with masking → record draw root words + view word 0 | layer₀ << 16 (view 0); views = all views, or mask & views with masking → record draw root words + view word 1 | layer₁ << 16 (view 1); views = all views, or mask & views with masking → record draw view word 3 | layer₃ << 16 (...); record draw root words + view word 0 | layer₀ << 16 → shaders (dxil-spirv patch 0003): SV_ViewID = word & 0xffff layer += word >> 16 viewport += 8-bit map[view] (a literal in the MSL); record draw root words + view word 1 | layer₁ << 16 → shaders (dxil-spirv patch 0003): SV_ViewID = word & 0xffff layer += word >> 16 viewport += 8-bit map[view] (a literal in the MSL); record draw view word 3 | layer₃ << 16 → shaders (dxil-spirv patch 0003): SV_ViewID = word & 0xffff layer += word >> 16 viewport += 8-bit map[view] (a literal in the MSL); shaders (dxil-spirv patch 0003): SV_ViewID = word & 0xffff layer += word >> 16 viewport += 8-bit map[view] (a literal in the MSL) → Metal: one draw per viewDrawInstanced / DrawIndexedInstanced /DispatchMesh / ExecuteIndirectviews = all views,or mask & viewswith maskingrecord drawroot words + view word0 | layer₀ << 16record drawroot words + view word1 | layer₁ << 16record drawview word3 | layer₃ << 16shaders (dxil-spirv patch 0003):SV_ViewID = word & 0xfffflayer += word >> 16viewport += 8-bit map[view](a literal in the MSL)Metal: one draw per viewview 0view 1...
How one D3D12 draw becomes one Metal draw per view, and what the view word holds. (Mermaid source)
Gallery scene views: one draw of a cube through a four-view pipeline: each view (SV_ViewID) turns the camera a quarter turn further and lands in its own viewport, so the quadrants show four sides. All scenes in the gallery

The interesting part

The vkd3d-proton test for this feature also pointed at an older gap. One case has a vertex shader that writes the layer, feeding a geometry shader (a stage that can emit new triangles). Metal has no geometry shaders, so the project runs them as Metal object functions. The Metal compiler rejected the result: SPIRV-Cross had declared the geometry shader's layer inputs the way a pixel shader receives them. A two-line SPIRV-Cross patch (0033) now reads them from the input vertices, like positions. Before this, any geometry shader that read SV_RenderTargetArrayIndex would have failed to compile.

I also had to correct my own ADR draft. It said Metal's vertex amplification (Metal's own multi-view feature) goes up to 2 views on Apple GPUs. A four-line Swift probe showed the M1 Max accepts 1 to 8. Amplification is still not used, because only plain vertex pipelines could use it. Mesh pipelines, and the emulated geometry and tessellation stages, would still need the replay.

Numbers

  • Full run: 781 pass, 27 fail, 104 skip of 912. Entry 041 ended at 778 of 911. 0 newly failing; 3 newly passing: the new api/view_instancing and vkd3d-proton's two view instancing tests.
  • api/view_instancing: 672 checks over 10 cases (layers, viewports, masks, indexed and indirect draws, a shader-written layer).
  • The two vkd3d-proton view instancing tests: 2,329 checks, up from 31 when they were skipped.
Tests by suite at the end of this entry: 781 of 912 pass (data as CSV)
  • pass
  • fail
  • skip

suite · share of each row's total

  1. vkd3d-proton (+2)459 pass, 18 fail, 100 skip of 577
  2. vkd3d177 pass, 9 fail, 4 skip of 190
  3. apps77 pass, 0 fail, 0 skip of 77
  4. api (+1)64 pass, 0 fail, 0 skip of 64
  5. samples4 pass, 0 fail, 0 skip of 4