Behaviour of a 0-value `accelerationStructureID`

When constructing a MTLIndirectAccelerationStructureInstanceDescriptor, one specifies an accelerationStructureID. In the equivalents in both Vulkan and DirectX12, one can set it to zero to be an "inactive" instance. However, on Metal, this field does not appear to have any documentation, and thus it is difficult to figure out if there is similar behavior (and no other Metal documentation seems to mention this). Does setting this field to 0 (i.e. null) disable the instance? If not, is there any other way to have an equivalent effect?

Answered by DTS Engineer in 901519022

The documented way to make an instance inactive is the mask field, which MTLIndirectAccelerationStructureInstanceDescriptor (https://developer.apple.com/documentation/metal/mtlindirectaccelerationstructureinstancedescriptor) already carries. Its declaration in MTLAccelerationStructure.h describes it as "Instance mask used to ignore geometry during ray tracing".

The semantics are in the Metal Shading Language Specification (https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf), in Table 6.29, Intersect function input parameters. That table describes the mask parameter as the "Intersection mask to be AND'd with instance mask defined in the Metal API". It also states that "Instances with nonoverlapping masks are skipped". An instance mask of zero cannot overlap any ray mask, so an instance with a zero mask is skipped for every ray. The specification names MTLAccelerationStructureInstanceDescriptor in that sentence, and that is the same mask field the indirect descriptor carries.

Your reading of accelerationStructureID is correct. It is documented only as the "Acceleration structure resource handle to use for this instance", and MTLResourceID is an opaque structure whose single member is private. Nothing documents a zero, null, or reserved handle value, so there is no documented behavior to build on.

For a GPU-driven build this costs you nothing extra. Zeroing mask is a single 32-bit store into a descriptor your shader is already writing.

The documented way to make an instance inactive is the mask field, which MTLIndirectAccelerationStructureInstanceDescriptor (https://developer.apple.com/documentation/metal/mtlindirectaccelerationstructureinstancedescriptor) already carries. Its declaration in MTLAccelerationStructure.h describes it as "Instance mask used to ignore geometry during ray tracing".

The semantics are in the Metal Shading Language Specification (https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf), in Table 6.29, Intersect function input parameters. That table describes the mask parameter as the "Intersection mask to be AND'd with instance mask defined in the Metal API". It also states that "Instances with nonoverlapping masks are skipped". An instance mask of zero cannot overlap any ray mask, so an instance with a zero mask is skipped for every ray. The specification names MTLAccelerationStructureInstanceDescriptor in that sentence, and that is the same mask field the indirect descriptor carries.

Your reading of accelerationStructureID is correct. It is documented only as the "Acceleration structure resource handle to use for this instance", and MTLResourceID is an opaque structure whose single member is private. Nothing documents a zero, null, or reserved handle value, so there is no documented behavior to build on.

For a GPU-driven build this costs you nothing extra. Zeroing mask is a single 32-bit store into a descriptor your shader is already writing.

Thank you for your response. I'm slightly confused though, if there is no null MTLResourceID then on what value would is_null_acceleration_structure (metal shading language 6.19.1) return true on?

It would be nice to be able to disable instances without knowing a separate valid acceleration structure, if the mask is 0 then does the acceleration structure id have to be valid?

is_null_acceleration_structure exists, and it tests something different from the descriptor field.

The Metal Shading Language Specification (https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf) gives three of these in section 6.19. is_null_primitive_acceleration_structure and is_null_instance_acceleration_structure since Metal 2.3, and the templated is_null_acceleration_structure(acceleration_structure<intersection_tags...>) since Metal 2.4. Metal 3.1 adds get_instance_count() and get_acceleration_structure(uint instance_id), so a shader can retrieve what an instance references and test that for null.

So the language does have a null acceleration structure, and nullness is observable from a shader. What it does not give you is a documented way to produce one from the API side. accelerationStructureID appears in exactly two places across the Metal headers, both field declarations in the indirect instance descriptors, with nothing about zero, null, or reserved values. Nothing says a zero mask exempts the rest of a descriptor from needing valid contents either.

The goal behind your question does have a documented mechanism. MTLIndirectInstanceAccelerationStructureDescriptor (https://developer.apple.com/documentation/metal/mtlindirectinstanceaccelerationstructuredescriptor) carries maxInstanceCount alongside instanceCountBuffer. The header comment on it reads "Buffer containing the instance count as a uint32_t value. Value at build time must be less than or equal to maxInstanceCount." (MTLAccelerationStructure.h, in Xcode's SDK at System/Library/Frameworks/Metal.framework/Headers/.)

The count is therefore not fixed when you create the descriptor. The same shader that fills your instance descriptor buffer can compact the active instances to the front and write how many it wrote. Inactive instances are then not in the build at all. They have no descriptor, so nothing needs a valid handle and the question of what to put in accelerationStructureID does not arise.

Compacting moves instances, so instance_id shifts between builds. Identity survives that. The header comment on userID in the descriptor calls it a "User-assigned instance ID to help identify this instance in an application-defined way". Table 2.9 of the specification lists user_instance_id among the built-ins an intersection function can read when the instancing tag is present.

mask is still the answer when an instance has to stay in the array but not be hit, for example when you want its index stable across frames. The two mechanisms answer different questions: mask for present but not intersectable, the count buffer for not present at all.

If you are on the Metal 4 path, MTL4IndirectInstanceAccelerationStructureDescriptor (https://developer.apple.com/documentation/metal/mtl4indirectinstanceaccelerationstructuredescriptor) has the same mechanism. Its documentation describes it as allowing "providing the instance count and motion transform count indirectly, through buffer references."

Thank you for this clarification. It would be nice to have this clarified on the relevant structures. Is feedback assistant the right place for that?

Behaviour of a 0-value &#96;accelerationStructureID&#96;
 
 
Q