MOOR security

Dieser Eintrag ist auf Englisch verfasst.

Inline ray queries on an M1, which has no ray-tracing hardware

Entry 033699 of 850 tests pass

D3D12 acceleration structures and DXR 1.1 inline ray queries now run on the D3D12-on-Metal layer on an M1 Max, which has no ray-tracing hardware. They use Metal's ray-tracing API and find each structure through a small header written at its D3D12 GPU address.

What changed

  • Acceleration structures and ray queries (the first tier of the ray-tracing plan).
    • A D3D12 application builds bottom-level and top-level acceleration structures (BLAS, TLAS) into ordinary buffers. It then traces rays against them from any shader with RayQuery.
    • mtl12, this project's D3D12-to-Metal 4 layer, now does both through Metal's ray-tracing API. That API runs on the M1 without ray-tracing hardware.
    • Updates (refits), clones, compaction and the postbuild size queries work too. A review found three problems, since fixed:
      • a contribution-table read without a bounds check;
      • a structure freed before the side queue finished with it;
      • a run of AS commands that had to be split where a later command depends on an earlier one.
    • The feature stays behind MTL12_RAYTRACING=1. Reporting ray-tracing tier 1.1 tells applications that DXR pipelines (TraceRay with shader tables) exist, and those do not yet.
  • Enhanced barriers. ID3D12GraphicsCommandList7::Barrier records the same conservative full barrier as the older ResourceBarrier. OPTIONS12 reports support.
  • Relaxed format casting. A resource can be created with a list of formats it may later be viewed as.
    • Views in those formats become Metal pixel-format views.
    • The lists are validated with the rules from vkd3d-proton's tests.
    • Metal can view a block-compressed texture only as its sRGB or signed twin, so other views of such a texture become null descriptors.
How a build writes the header and how a ray query finds the Metal structure through it.BuildRaytracingAccelerationStructure (replay): Metal 4 command buffer committed before the run, Metal 4 kernels: headers, triangle gather, instance descs → Metal, Metal 3 queue: build into an MTLAccelerationStructure, next Metal 4 command buffer waits; RayQuery in DXIL: RTAS SRV or root SRV = a 64-bit VA, dxil-spirv: ConvertUToAS SPIRV-Cross patch: read the header, MSL intersection_query on instance_acceleration_structure; Metal 4 command buffer committed before the run → Metal 4 kernels: headers, triangle gather, instance descs → Metal; Metal 4 kernels: headers, triangle gather, instance descs → Metal → Metal 3 queue: build into an MTLAccelerationStructure; Metal 3 queue: build into an MTLAccelerationStructure → next Metal 4 command buffer waits (shared event); Metal 4 kernels: headers, triangle gather, instance descs → Metal → header {MTLResourceID, instances} + contributions at VA + 16 (writes 16 bytes at the D3D12 VA); Metal 3 queue: build into an MTLAccelerationStructure → header {MTLResourceID, instances} + contributions at VA + 16 (table keyed by VA); RTAS SRV or root SRV = a 64-bit VA → dxil-spirv: ConvertUToAS SPIRV-Cross patch: read the header; dxil-spirv: ConvertUToAS SPIRV-Cross patch: read the header → MSL intersection_query on instance_acceleration_structure; header {MTLResourceID, instances} + contributions at VA + 16 → dxil-spirv: ConvertUToAS SPIRV-Cross patch: read the header; header {MTLResourceID, instances} + contributions at VA + 16 → MSL intersection_query on instance_acceleration_structure (contribution lookup (dxil-spirv patch 0002))BuildRaytracingAccelerationStructure (replay)RayQuery in DXILMetal 4 command buffercommitted before the runMetal 4 kernels:headers, triangle gather,instance descs → MetalMetal 3 queue:build into anMTLAccelerationStructurenext Metal 4command buffer waitsheader{MTLResourceID, instances}+ contributions at VA + 16RTAS SRV or root SRV= a 64-bit VAdxil-spirv: ConvertUToASSPIRV-Cross patch: read the headerMSL intersection_queryon instance_acceleration_structureshared eventwrites 16 bytes at the D3D12 VAtable keyed by VAcontribution lookup(dxil-spirv patch 0002)
How a build writes the header and how a ray query finds the Metal structure through it. (Mermaid source)
Gallery scene rayquery: a compute shader traces one RayQuery per pixel into a TLAS of seven instances (a floor BLAS, six of a pyramid BLAS) and one shadow ray per hit. All scenes in the gallery

The interesting part

D3D12 names an acceleration structure by a 64-bit GPU address. Metal names it by an opaque object. A shader reaches a D3D12 structure only through the address, whether from a root argument or read out of a descriptor heap. So the Metal shader has to turn a number into a structure.

  • A spike came first. A Metal 4 compute kernel read a MTLResourceID from device memory, used it as an instance_acceleration_structure, and queried a structure built on a Metal 3 queue. That worked, so the layer now writes a 16-byte header at the start of every D3D12 destination buffer: {MTLResourceID, instance count}. For a TLAS, a table of each instance's hit-group contribution follows the header. A patched SPIRV-Cross turns the address into a structure by reading the header.
  • One gap took a second patch. RayQuery::CommittedInstanceContributionToHitGroupIndex has no Metal counterpart. MSL's intersection_query can't be a struct member or a base class, so it can't carry the address along.
    • dxil-spirv patch 0002 gives each ray query a companion variable that remembers the address it traced.
    • The contribution is then read from the table after the header.
  • Builds run on a Metal 3 queue beside the Metal 4 one. Shared events order the two queues. Before the build, Metal 4 kernels gather the triangles into packed float3 scratch. That way every D3D12 vertex format, stride and transform takes one path.

Numbers

  • Full run: 699 pass, 27 fail, 124 skip of 850 tests, against 682 / 27 / 137 of 846 at the end of entry 032. None newly failing. 17 newly passing:
    • 4 new api tests: rt_ray_query (146 checks), rt_as_ops (85), enhanced_barriers (7) and castable_formats (42);
    • vkd3d-proton test_rayquery_root_desc and test_rayquery_root_table (738 checks each);
    • vkd3d-proton test_raytracing_acceleration_structure_validation;
    • all 10 vkd3d-proton test_enhanced_barrier_* tests.
  • Metal format views, probed on the M1 Max: every same-size uncompressed colour cast tried is allowed. Of block-compressed formats, only the sRGB or signed twin is allowed. Depth ↔ colour views are not allowed.

Not covered: DXR 1.0 pipelines (state objects, shader tables, DispatchRays). 19 vkd3d-proton test_raytracing_* tests still skip, most of them because they need pipelines. Compaction destinations are not yet sized from the compacted size. Uncompressed views of BC textures, which applications use to write compressed blocks from shaders, read zeros.

Tests by suite at the end of this entry: 699 of 850 pass (data as CSV)
  • pass
  • fail
  • skip

suite · share of each row's total

  1. vkd3d-proton (+13)439 pass, 18 fail, 120 skip of 577
  2. vkd3d177 pass, 9 fail, 4 skip of 190
  3. api (+4)49 pass, 0 fail, 0 skip of 49
  4. apps30 pass, 0 fail, 0 skip of 30
  5. samples4 pass, 0 fail, 0 skip of 4