Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2023 Graphics & Games

WWDC23 · 32 min · Graphics & Games

Your guide to Metal ray tracing

Discover how you can enhance the visual quality of your games and apps with Metal ray tracing. We’ll take you through the fundamentals of the Metal ray tracing API. Explore the latest enhancements and techniques that will enable you to create larger and more complex scenes, reduce memory usage and build times, and efficiently render visual content like hair and fur.

Watch at developer.apple.com ↗

Transcript all transcripts

Chapters

Code shown on screen · 23 snippets

Create triangle geometry descriptor swift · at 3:06 ↗
// Create geometry descriptor:
let geometryDescriptor = MTLAccelerationStructureTriangleGeometryDescriptor()

geometryDescriptor.vertexBuffer = vertexBuffer
geometryDescriptor.indexBuffer = indexBuffer
geometryDescriptor.triangleCount = triangleCount
Create bounding box geometry descriptor swift · at 3:20 ↗
// Create geometry descriptor:
let geometryDescriptor = MTLAccelerationStructureBoundingBoxGeometryDescriptor()

geometryDescriptor.boundingBoxBuffer = boundingBoxBuffer
geometryDescriptor.boundingBoxCount = boundingBoxCount
Create curve geometry descriptor swift · at 6:42 ↗
let geometryDescriptor = MTLAccelerationStructureCurveGeometryDescriptor()
  
geometryDescriptor.controlPointBuffer = controlPointBuffer
geometryDescriptor.radiusBuffer = radiusBuffer
geometryDescriptor.indexBuffer = indexBuffer

geometryDescriptor.controlPointCount = controlPointCount
geometryDescriptor.segmentCount = segmentCount
geometryDescriptor.curveType = .round
geometryDescriptor.curveBasis = .bezier
geometryDescriptor.segmentControlPointCount = 4
Create primitive acceleration structure descriptor swift · at 7:29 ↗
// Create acceleration structure descriptor
let accelerationStructureDescriptor = MTLPrimitiveAccelerationStructureDescriptor()

// Add geometry descriptor to acceleration structure descriptor
accelerationStructureDescriptor.geometryDescriptors = [ geometryDescriptor ]
Query for acceleration size and alignment requirements swift · at 8:08 ↗
// Query for acceleration structure sizes
let sizes: MTLAccelerationStructureSizes
sizes = device.accelerationStructureSizes(descriptor: accelerationStructureDescriptor)

// Query for size and alignment requirement in a heap
let heapSize: MTLSizeAndAlign
heapSize = device.heapAccelerationStructureSizeAndAlign(size: sizes.accelerationStructureSize)
Allocate acceleration structure and scratch buffer swift · at 8:39 ↗
// Allocate acceleration structure from heap
var accelerationStructure: MTLAccelerationStructure!
accelerationStructure = heap.makeAccelerationStructure(size: heapSize.size)

// Allocate scratch buffer
let scratchBuffer = device.makeBuffer(length: sizes.buildScratchBufferSize,
                                      options: .storageModePrivate)!
Encode the acceleration structure build swift · at 8:40 ↗
let commandEncoder = commandBuffer.makeAccelerationStructureCommandEncoder()!

commandEncoder.build(accelerationStructure: accelerationStructure,
                     descriptor: accelerationStructureDescriptor,
                     scratchBuffer: scratchBuffer,
                     scratchBufferOffset: 0)

commandEncoder.endEncoding()
Create instance acceleration structure descriptor swift · at 11:30 ↗
var instanceASDesc = MTLInstanceAccelerationStructureDescriptor()

instanceASDesc.instanceCount = ...
instanceASDesc.instancedAccelerationStructures = [ mountainAS, treeAS, ... ]
instanceASDesc.instanceDescriptorType = .userID
Allocate the instance descriptor buffer swift · at 12:07 ↗
let size = MemoryLayout<MTLAccelerationStructureUserIDInstanceDescriptor>.stride
let instanceDescriptorBufferSize = size * instanceASDesc.instanceCount

let instanceDescriptorBuffer = device.makeBuffer(length: instanceDescriptorBufferSize,
                                                 options: .storageModeShared)!
    
instanceASDesc.instanceDescriptorBuffer = instanceDescriptorBuffer
Populate instance descriptors swift · at 12:33 ↗
var instanceDesc = MTLAccelerationStructureUserIDInstanceDescriptor()

instanceDesc.accelerationStructureIndex = 0    // index into instancedAccelerationStructures
instanceDesc.transformationMatrix = ...
instanceDesc.mask = 0xFFFFFFFF
Configure indirect instance acceleration structure descriptor swift · at 14:06 ↗
var instanceASDesc = MTLIndirectInstanceAccelerationStructureDescriptor()

instanceASDesc.instanceDescriptorType = .indirect
instanceASDesc.maxInstanceCount = ...
instanceASDesc.instanceCountBuffer = ...
instanceASDesc.instanceDescriptorBuffer = ...
Populate indirect instance descriptor cpp · at 14:29 ↗
device MTLIndirectAccelerationStructureInstanceDescriptor *instance_buffer = ...;
// ...
acceleration_structure<> as = ...;
instance_buffer[i].accelerationStructureID = as;
instance_buffer[i].transformationMatrix[0] = ...;
instance_buffer[i].transformationMatrix[1] = ...;
instance_buffer[i].transformationMatrix[2] = ...;
instance_buffer[i].transformationMatrix[3] = ...;
instance_buffer[i].mask = 0xFFFFFFFF;
Update geometry using refitting swift · at 19:22 ↗
// Allocate scratch buffer
let scratchBuffer = device.makeBuffer(length: sizes.refitScratchBufferSize,
                                      options: .storageModePrivate)!

// Create command buffer/encoder ...

// Refit acceleration structure
commandEncoder.refit(sourceAccelerationStructure: accelerationStructure,
                     descriptor: asDescriptor,
                     destinationAccelerationStructure: accelerationStructure,
                     scratchBuffer: scratchBuffer,
                     scratchBufferOffset: 0)
Use compaction to reclaim memory swift · at 20:24 ↗
// Use compaction to reclaim memory

// Create command buffer/encoder ...

sizeCommandEncoder.writeCompactedSize(accelerationStructure: accelerationStructure,
                                      buffer: sizeBuffer,
                                      offset: 0,
                                      sizeDataType: .ulong)

// endEncoding(), commit command buffer and wait until completed ...

// Allocate new acceleration structure using UInt64 from sizeBuffer ...

compactCommandEncoder.copyAndCompact(sourceAccelerationStructure: accelerationStructure,
                             destinationAccelerationStructure: compactedAccelerationStructure)
Set acceleration structure on the command encoder swift · at 21:36 ↗
encoder.setAccelerationStructure(primitiveAccelerationStructure, bufferIndex:0)
Intersect rays with primitive acceleration structure cpp · at 21:48 ↗
// Intersect rays with a primitive acceleration structure

[[kernel]]
void trace_rays(acceleration_structure<> as, /* ... */) {
  intersector<> i;

  ray r(origin, direction);

  intersection_result<> result = i.intersect(r, as);

  if (result.type == intersection_type::triangle) {
    float distance = result.distance;


    // shade triangle...
  }
}
Use triangle_data tag to get triangle barycentric coordinates cpp · at 22:24 ↗
// Intersect rays with a primitive acceleration structure

[[kernel]]
void trace_rays(acceleration_structure<> as, /* ... */) {
  intersector<triangle_data> i;

  ray r(origin, direction);

  intersection_result<triangle_data> result = i.intersect(r, as);

  if (result.type == intersection_type::triangle) {
    float distance = result.distance;
    float2 coords = result.triangle_barycentric_coord;

    // shade triangle...
  }
}
Set instance acceleration structure on the command encoder swift · at 22:51 ↗
encoder.setAccelerationStructure(instanceAccelerationStructure, bufferIndex:0)
encoder.useHeap(accelerationStructureHeap);
Intersect rays with instance acceleration structure cpp · at 23:07 ↗
// Intersect rays with an instance acceleration structure

[[kernel]]
void trace_rays(acceleration_structure<instancing> as, /* ... */) {
  intersector<instancing, max_levels<3>> i;

  ray r(origin, direction);

  intersection_result<instancing, max_levels<3>> result = i.intersect(r, as);

  if (result.type == intersection_type::triangle) {
    float distance = result.distance;

    // shade triangle...
  }
}
Find intersected instance information in the intersection result cpp · at 24:43 ↗
// Intersect rays with an instance acceleration structure

[[kernel]]
void trace_rays(acceleration_structure<instancing> as, /* ... */) {
  intersector<instancing, max_levels<3>> i;

  ray r(origin, direction);

  intersection_result<instancing, max_levels<3>> result = i.intersect(r, as);

  if (result.type == intersection_type::triangle) {
    float distance = result.distance;
    for (uint i = 0; i < result.instance_count; ++i) {
      uint id = result.instance_id[i];
      // ...
    }
    // shade triangle...
  }
}
Intersect rays with curve primitives cpp · at 25:02 ↗
// Intersect rays with curve primitives

[[kernel]]
void trace_rays(acceleration_structure<> as, /* ... */) {
  intersector<> i;

  i.assume_geometry_type(geometry_type::curve | geometry_type::triangle);

  ray r(origin, direction);

  intersection_result<> result = i.intersect(r, as);

  if (result.type == intersection_type::curve) {
    float distance = result.distance;
    // shade curve...
  }
}
Find curve parameter in the intersection result cpp · at 25:26 ↗
// Intersect rays with curve primitives

[[kernel]]
void trace_rays(acceleration_structure<> as, /* ... */) {
  intersector<curve_data> i;

  i.assume_geometry_type(geometry_type::curve | geometry_type::triangle);

  ray r(origin, direction);

  intersection_result<curve_data> result = i.intersect(r, as);

  if (result.type == intersection_type::curve) {
    float distance = result.distance;
    float param = result.curve_parameter;
    // shade curve...
  }
}
Set geometry type on the intersector for better performance cpp · at 26:04 ↗
// Intersect rays with curve primitives

[[kernel]]
void trace_rays(acceleration_structure<> as, /* ... */) {
  intersector<curve_data> i;

  i.assume_geometry_type(geometry_type::curve | geometry_type::triangle);
  i.assume_curve_type(curve_type::round);
  i.assume_curve_basis(curve_basis::bezier);
  i.assume_curve_control_point_count(3);

  ray r(origin, direction);

  intersection_result<curve_data> result = i.intersect(r, as);

  if (result.type == intersection_type::curve) {
    float distance = result.distance;
    float param = result.curve_parameter;
    // shade curve...
  }
}

Resources