[Metal] Descriptor set layout bindings out-of-bounds access and unsorted bindings invariant violation
Describe the bug There are two related descriptor set handling issues in the Metal backend when handling sparse/high binding indices and unsorted bindings:
Out-of-bounds indexing in
MetalDriver::updateDescriptorSetBuffer: Infilament/backend/src/metal/MetalDriver.mm:ShaderStageFlags stageFlags = descriptorSet->layout->getBindings()[binding].stageFlags;descriptorSet->layout->getBindings()returnsmLayout.descriptors, which is an array with size equal to the descriptor count (e.g. 3 bindings). Indexing directly by[binding]assumes contiguous 0-based binding indices matching array indices. When a descriptor set has sparse or high binding indices (e.g. binding 32 or 63),getBindings()[binding]accesses out of bounds, causing memory corruption or crash. In contrast,MetalDriver::updateDescriptorSetTexturecorrectly looks up the binding viastd::find_if:auto const& bindings = descriptorSet->layout->getBindings(); auto found = std::find_if(bindings.begin(), bindings.end(), [binding](const auto& b) { return b.binding == binding; });Binding sort invariant trapped inside
#if FILAMENT_METAL_DEBUG_LOG == 1: Infilament/backend/src/metal/MetalHandles.mm:// Important! The bindings must be sorted by binding number. This has already been done inside // createDescriptorSetLayout.However, in
filament/backend/src/metal/MetalDriver.mm:void MetalDriver::createDescriptorSetLayoutR(...) { #if FILAMENT_METAL_DEBUG_LOG == 1 ... std::sort(info.descriptors.begin(), info.descriptors.end(), [](const auto& a, const auto& b) { return a.binding < b.binding; }); ... #endif construct_handle<MetalDescriptorSetLayout>(dslh, std::move(info)); }std::sortwas accidentally placed inside theFILAMENT_METAL_DEBUG_LOG == 1block. In standard release and debug builds,FILAMENT_METAL_DEBUG_LOGis 0, so bindings are never sorted. This violates the assumption inMetalDescriptorSetLayout::getArgumentEncoderSlowand can cause mismatched texture type indexing and broken argument encoder cache keys.
To Reproduce Steps to reproduce the behavior:
- Create a
DescriptorSetLayoutwith sparse/high binding indices, e.g. binding 0 and binding 32. - Create a descriptor set from the layout.
- Call
updateDescriptorSetBufferon binding 32. - Observe out-of-bounds vector access in
updateDescriptorSetBuffer.
Expected behavior
updateDescriptorSetBuffershould locate the binding descriptor via lookup (std::find_if) rather than directly indexinggetBindings()[binding].MetalDescriptorSetLayoutorcreateDescriptorSetLayoutRshould unconditionally sortdescriptorsby binding number regardless of debug logging macros.
Desktop (please complete the following information):
- OS: macOS / iOS
- Backend: Metal
Additional context Discovered while auditing descriptor set binding index limits and writing cross-backend tests.
Source: google/filament