Merge pull request #1131 from billhollings/VK_EXT_descriptor_indexing

Initial support for the VK_EXT_descriptor_indexing extension
diff --git a/Docs/MoltenVK_Runtime_UserGuide.md b/Docs/MoltenVK_Runtime_UserGuide.md
index 2a0fcd5..c4b6da0 100644
--- a/Docs/MoltenVK_Runtime_UserGuide.md
+++ b/Docs/MoltenVK_Runtime_UserGuide.md
@@ -303,6 +303,7 @@
 - `VK_EXT_debug_marker`
 - `VK_EXT_debug_report`
 - `VK_EXT_debug_utils`
+- `VK_EXT_descriptor_indexing`
 - `VK_EXT_fragment_shader_interlock` *(requires Metal 2.0 and Raster Order Groups)*
 - `VK_EXT_host_query_reset`
 - `VK_EXT_image_robustness`
diff --git a/Docs/Whats_New.md b/Docs/Whats_New.md
index a3c9c45..bb11f8a 100644
--- a/Docs/Whats_New.md
+++ b/Docs/Whats_New.md
@@ -19,6 +19,7 @@
 Released TBD
 
 - Add support for extensions:
+	- `VK_EXT_descriptor_indexing` (initial release limited to Metal Tier 1: 96/128 textures, 16 samplers)
 	- `VK_EXT_private_data`
 	- `VK_IMG_format_pvrtc` (macOS)
 - Use `VK_KHR_image_format_list` to disable `MTLTextureUsagePixelFormatView` 
@@ -27,7 +28,10 @@
 - Move *Metal* drawable presentation from `MTLCommandBuffer` to `MTLDrawable`
   to improve performance and reduce blocking.
 - Allow binding descriptor set using layout different than it was created with.
+- Report `VkPhysicalDeviceLimits::maxPerStageDescriptorStorageImages` as Metal limit of `8`.
+- Increase per-stage texture count to `96` for A11 SoC's and above.
 - Clarify documentation on mapping limitations for host-coherent image memory on *macOS*.
+- Update `VK_MVK_MOLTENVK_SPEC_VERSION` to `29`.
 
 
 
diff --git a/ExternalRevisions/SPIRV-Cross_repo_revision b/ExternalRevisions/SPIRV-Cross_repo_revision
index e938194..00be046 100644
--- a/ExternalRevisions/SPIRV-Cross_repo_revision
+++ b/ExternalRevisions/SPIRV-Cross_repo_revision
@@ -1 +1 @@
-0db1569e970b0b1b9bfcaec691e2bfb2f26a73db
+fc644b50e631ccd88c324793823ade040a80caf5
diff --git a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
index cfb2663..fbe42f9 100644
--- a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
+++ b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
@@ -55,7 +55,7 @@
 #define MVK_MAKE_VERSION(major, minor, patch)    (((major) * 10000) + ((minor) * 100) + (patch))
 #define MVK_VERSION     MVK_MAKE_VERSION(MVK_VERSION_MAJOR, MVK_VERSION_MINOR, MVK_VERSION_PATCH)
 
-#define VK_MVK_MOLTENVK_SPEC_VERSION            28
+#define VK_MVK_MOLTENVK_SPEC_VERSION            29
 #define VK_MVK_MOLTENVK_EXTENSION_NAME          "VK_MVK_moltenvk"
 
 /**
@@ -271,11 +271,16 @@
 	 * command buffer. Depending on the number of command buffers that you use, you may also need to
 	 * change the value of the maxActiveMetalCommandBuffersPerQueue setting.
 	 *
-	 * In addition, if this feature is enabled, be aware that if you have recorded commands to a
-	 * Vulkan command buffer, and then choose to reset that command buffer instead of submitting it,
-	 * the corresponding prefilled Metal command buffer will still be submitted. This is because Metal
-	 * command buffers do not support the concept of being reset after being filled. Depending on when
-	 * and how often you do this, it may cause unexpected visual artifacts and unnecessary GPU load.
+	 * If this feature is enabled, be aware that if you have recorded commands to a Vulkan command buffer,
+	 * and then choose to reset that command buffer instead of submitting it, the corresponding prefilled
+	 * Metal command buffer will still be submitted. This is because Metal command buffers do not support
+	 * the concept of being reset after being filled. Depending on when and how often you do this,
+	 * it may cause unexpected visual artifacts and unnecessary GPU load.
+	 *
+	 * This feature is incompatible with updating descriptors after binding. If any of the
+	 * *UpdateAfterBind feature flags of VkPhysicalDeviceDescriptorIndexingFeaturesEXT or
+	 * VkPhysicalDeviceInlineUniformBlockFeaturesEXT have been enabled, the value of this
+	 * setting will be ignored and treated as if it is false.
 	 *
 	 * The value of this parameter may be changed at any time during application runtime,
 	 * and the changed value will immediately effect subsequent MoltenVK behaviour.
@@ -621,6 +626,7 @@
 	VkBool32 depthResolve;						/**< If true, resolving depth textures with filters other than Sample0 is supported. */
 	VkBool32 stencilResolve;					/**< If true, resolving stencil textures with filters other than Sample0 is supported. */
 	uint32_t maxPerStageDynamicMTLBufferCount;	/**< The maximum number of inline buffers that can be set on a command buffer. */
+	uint32_t maxPerStageStorageTextureCount;    /**< The total number of per-stage Metal textures with read-write access available for writing to from a shader. */
 } MVKPhysicalDeviceMetalFeatures;
 
 /** MoltenVK performance of a particular type of activity. */
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm b/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm
index 7d619b5..5c0efd2 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm
@@ -161,7 +161,7 @@
 }
 
 bool MVKCommandBuffer::canPrefill() {
-	bool wantPrefill = _device->_pMVKConfig->prefillMetalCommandBuffers;
+	bool wantPrefill = _device->shouldPrefillMTLCommandBuffers();
 	return wantPrefill && !(_isSecondary || _supportsConcurrentExecution);
 }
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.h b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.h
index d88c1f8..0ce732e 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.h
@@ -70,8 +70,20 @@
 	/** Returns the binding number of this layout. */
 	inline uint32_t getBinding() { return _info.binding; }
 
-	/** Returns the number of descriptors in this layout. */
-    inline uint32_t getDescriptorCount() { return (_info.descriptorType == VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT) ? 1 : _info.descriptorCount; }
+	/** Returns whether this binding has a variable descriptor count. */
+	inline bool hasVariableDescriptorCount() {
+		return mvkIsAnyFlagEnabled(_flags, VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT_EXT);
+	}
+
+	/**
+	 * Returns the number of descriptors in this layout.
+	 *
+	 * If this is an inline block data descriptor, always returns 1. If this descriptor
+	 * has a variable descriptor count, and descSet is not null, the variable descriptor
+	 * count provided to that descriptor set is returned. Otherwise returns the value
+	 * defined in VkDescriptorSetLayoutBinding::descriptorCount.
+	 */
+	uint32_t getDescriptorCount(MVKDescriptorSet* descSet);
 
 	/** Returns the descriptor type of this layout. */
 	inline VkDescriptorType getDescriptorType() { return _info.descriptorType; }
@@ -86,12 +98,15 @@
 	/** Returns the immutable sampler at the index, or nullptr if immutable samplers are not used. */
 	MVKSampler* getImmutableSampler(uint32_t index);
 
-	/** Encodes the descriptors in the descriptor set that are specified by this layout, */
-	void bind(MVKCommandEncoder* cmdEncoder,
-			  MVKDescriptorSet* descSet,
-			  MVKShaderResourceBinding& dslMTLRezIdxOffsets,
-			  MVKArrayRef<uint32_t> dynamicOffsets,
-			  uint32_t baseDynamicOffsetIndex);
+	/**
+	 * Encodes the descriptors in the descriptor set that are specified by this layout,
+	 * Returns the number of dynamic offsets consumed.
+	 */
+	uint32_t bind(MVKCommandEncoder* cmdEncoder,
+				  MVKDescriptorSet* descSet,
+				  MVKShaderResourceBinding& dslMTLRezIdxOffsets,
+				  MVKArrayRef<uint32_t> dynamicOffsets,
+				  uint32_t descSetDynamicOffsetIndex);
 
     /** Encodes this binding layout and the specified descriptor on the specified command encoder immediately. */
     void push(MVKCommandEncoder* cmdEncoder,
@@ -110,7 +125,8 @@
 
 	MVKDescriptorSetLayoutBinding(MVKDevice* device,
 								  MVKDescriptorSetLayout* layout,
-								  const VkDescriptorSetLayoutBinding* pBinding);
+								  const VkDescriptorSetLayoutBinding* pBinding,
+								  VkDescriptorBindingFlagsEXT bindingFlags);
 
 	MVKDescriptorSetLayoutBinding(const MVKDescriptorSetLayoutBinding& binding);
 
@@ -125,6 +141,7 @@
 
 	MVKDescriptorSetLayout* _layout;
 	VkDescriptorSetLayoutBinding _info;
+	VkDescriptorBindingFlagsEXT _flags;
 	MVKSmallVector<MVKSampler*> _immutableSamplers;
 	MVKShaderResourceBinding _mtlResourceIndexOffsets;
 	bool _applyToStage[kMVKShaderStageMax];
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.mm
index 74ce403..e70a769 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.mm
@@ -74,26 +74,48 @@
 
 MVKVulkanAPIObject* MVKDescriptorSetLayoutBinding::getVulkanAPIObject() { return _layout; };
 
+uint32_t MVKDescriptorSetLayoutBinding::getDescriptorCount(MVKDescriptorSet* descSet) {
+
+	if (_info.descriptorType == VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT) {
+		return 1;
+	}
+
+	if (descSet && hasVariableDescriptorCount()) {
+		return descSet->_variableDescriptorCount;
+	}
+
+	return _info.descriptorCount;
+}
+
 MVKSampler* MVKDescriptorSetLayoutBinding::getImmutableSampler(uint32_t index) {
 	return (index < _immutableSamplers.size()) ? _immutableSamplers[index] : nullptr;
 }
 
 // A null cmdEncoder can be passed to perform a validation pass
-void MVKDescriptorSetLayoutBinding::bind(MVKCommandEncoder* cmdEncoder,
-										 MVKDescriptorSet* descSet,
-										 MVKShaderResourceBinding& dslMTLRezIdxOffsets,
-										 MVKArrayRef<uint32_t> dynamicOffsets,
-										 uint32_t baseDynamicOffsetIndex) {
+uint32_t MVKDescriptorSetLayoutBinding::bind(MVKCommandEncoder* cmdEncoder,
+											 MVKDescriptorSet* descSet,
+											 MVKShaderResourceBinding& dslMTLRezIdxOffsets,
+											 MVKArrayRef<uint32_t> dynamicOffsets,
+											 uint32_t descSetDynamicOffsetIndex) {
 
 	// Establish the resource indices to use, by combining the offsets of the DSL and this DSL binding.
     MVKShaderResourceBinding mtlIdxs = _mtlResourceIndexOffsets + dslMTLRezIdxOffsets;
 
-    uint32_t descCnt = getDescriptorCount();
+	VkDescriptorType descType = getDescriptorType();
+    uint32_t descCnt = getDescriptorCount(descSet);
     for (uint32_t descIdx = 0; descIdx < descCnt; descIdx++) {
 		MVKDescriptor* mvkDesc = descSet->getDescriptor(getBinding(), descIdx);
-        mvkDesc->bind(cmdEncoder, _info.descriptorType, descIdx, _applyToStage, mtlIdxs,
-					  dynamicOffsets, baseDynamicOffsetIndex + _dynamicOffsetIndex + descIdx);
+        mvkDesc->bind(cmdEncoder, descType, descIdx, _applyToStage, mtlIdxs,
+					  dynamicOffsets, descSetDynamicOffsetIndex + _dynamicOffsetIndex + descIdx);
     }
+
+	switch (descType) {
+		case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
+		case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
+			return descCnt;
+		default:
+			return 0;
+	}
 }
 
 template<typename T>
@@ -352,6 +374,7 @@
                                               models[i],
                                               dslIndex,
                                               _info.binding,
+											  getDescriptorCount(nullptr),
 											  mvkSamp);
         }
     }
@@ -359,8 +382,15 @@
 
 MVKDescriptorSetLayoutBinding::MVKDescriptorSetLayoutBinding(MVKDevice* device,
 															 MVKDescriptorSetLayout* layout,
-                                                             const VkDescriptorSetLayoutBinding* pBinding) : MVKBaseDeviceObject(device), _layout(layout),
-																 _dynamicOffsetIndex(0) {
+															 const VkDescriptorSetLayoutBinding* pBinding,
+															 VkDescriptorBindingFlagsEXT bindingFlags) :
+	MVKBaseDeviceObject(device),
+	_layout(layout),
+	_info(*pBinding),
+	_flags(bindingFlags),
+	_dynamicOffsetIndex(0) {
+
+	_info.pImmutableSamplers = nullptr;     // Remove dangling pointer
 
 	for (uint32_t i = kMVKShaderStageVertex; i < kMVKShaderStageMax; i++) {
         // Determine if this binding is used by this shader stage
@@ -383,13 +413,14 @@
             }
         }
 
-    _info = *pBinding;
-    _info.pImmutableSamplers = nullptr;     // Remove dangling pointer
 }
 
 MVKDescriptorSetLayoutBinding::MVKDescriptorSetLayoutBinding(const MVKDescriptorSetLayoutBinding& binding) :
-	MVKBaseDeviceObject(binding._device), _layout(binding._layout),
-	_info(binding._info), _immutableSamplers(binding._immutableSamplers),
+	MVKBaseDeviceObject(binding._device),
+	_layout(binding._layout),
+	_info(binding._info),
+	_flags(binding._flags),
+	_immutableSamplers(binding._immutableSamplers),
 	_mtlResourceIndexOffsets(binding._mtlResourceIndexOffsets),
 	_dynamicOffsetIndex(0) {
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
index 1f2ce42..c31ef35 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
@@ -43,15 +43,15 @@
 	/** Returns the debug report object type of this object. */
 	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT_EXT; }
 
-	inline uint32_t getDynamicDescriptorCount() { return _dynamicDescriptorCount; }
-
-	/** Encodes this descriptor set layout and the specified descriptor set on the specified command encoder. */
-    void bindDescriptorSet(MVKCommandEncoder* cmdEncoder,
-                           MVKDescriptorSet* descSet,
-                           MVKShaderResourceBinding& dslMTLRezIdxOffsets,
-                           MVKArrayRef<uint32_t> dynamicOffsets,
-                           uint32_t baseDynamicOffsetIndex);
-
+	/**
+	 * Encodes this descriptor set layout and the specified descriptor set on the specified command encoder.
+	 * Returns the number of dynamic offsets consumed.
+	 */
+	uint32_t bindDescriptorSet(MVKCommandEncoder* cmdEncoder,
+							   MVKDescriptorSet* descSet,
+							   MVKShaderResourceBinding& dslMTLRezIdxOffsets,
+							   MVKArrayRef<uint32_t> dynamicOffsets,
+							   uint32_t dynamicOffsetIndex);
 
 	/** Encodes this descriptor set layout and the specified descriptor updates on the specified command encoder immediately. */
 	void pushDescriptorSet(MVKCommandEncoder* cmdEncoder,
@@ -87,13 +87,13 @@
 	inline uint32_t getDescriptorCount() { return _descriptorCount; }
 	inline uint32_t getDescriptorIndex(uint32_t binding, uint32_t elementIndex = 0) { return _bindingToDescriptorIndex[binding] + elementIndex; }
 	inline MVKDescriptorSetLayoutBinding* getBinding(uint32_t binding) { return &_bindings[_bindingToIndex[binding]]; }
+	const VkDescriptorBindingFlags* getBindingFlags(const VkDescriptorSetLayoutCreateInfo* pCreateInfo);
 
 	MVKSmallVector<MVKDescriptorSetLayoutBinding> _bindings;
 	std::unordered_map<uint32_t, uint32_t> _bindingToIndex;
 	std::unordered_map<uint32_t, uint32_t> _bindingToDescriptorIndex;
 	MVKShaderResourceBinding _mtlResourceCounts;
 	uint32_t _descriptorCount;
-	uint32_t _dynamicDescriptorCount;
 	bool _isPushDescriptorLayout;
 };
 
@@ -129,7 +129,9 @@
 			  VkBufferView* pTexelBufferView,
 			  VkWriteDescriptorSetInlineUniformBlockEXT* pInlineUniformBlock);
 
-	MVKDescriptorSet(MVKDescriptorSetLayout* layout, MVKDescriptorPool* pool);
+	MVKDescriptorSet(MVKDescriptorSetLayout* layout,
+					 uint32_t variableDescriptorCount,
+					 MVKDescriptorPool* pool);
 
 	~MVKDescriptorSet() override;
 
@@ -143,6 +145,7 @@
 	MVKDescriptorSetLayout* _layout;
 	MVKDescriptorPool* _pool;
 	MVKSmallVector<MVKDescriptor*> _descriptors;
+	uint32_t _variableDescriptorCount;
 };
 
 
@@ -225,9 +228,8 @@
 	/** Returns the debug report object type of this object. */
 	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_POOL_EXT; }
 
-	/** Allocates the specified number of descriptor sets. */
-	VkResult allocateDescriptorSets(uint32_t count,
-									const VkDescriptorSetLayout* pSetLayouts,
+	/** Allocates descriptor sets. */
+	VkResult allocateDescriptorSets(const VkDescriptorSetAllocateInfo* pAllocateInfo,
 									VkDescriptorSet* pDescriptorSets);
 
 	/** Free's up the specified descriptor set. */
@@ -244,7 +246,8 @@
 	friend class MVKDescriptorSet;
 
 	void propagateDebugName() override {}
-	VkResult allocateDescriptorSet(MVKDescriptorSetLayout* mvkDSL, VkDescriptorSet* pVKDS);
+	VkResult allocateDescriptorSet(MVKDescriptorSetLayout* mvkDSL, uint32_t variableDescriptorCount, VkDescriptorSet* pVKDS);
+	const uint32_t* getVariableDecriptorCounts(const VkDescriptorSetAllocateInfo* pAllocateInfo);
 	void freeDescriptorSet(MVKDescriptorSet* mvkDS);
 	VkResult allocateDescriptor(VkDescriptorType descriptorType, MVKDescriptor** pMVKDesc);
 	void freeDescriptor(MVKDescriptor* mvkDesc);
@@ -314,4 +317,5 @@
 									   spv::ExecutionModel stage,
 									   uint32_t descriptorSetIndex,
 									   uint32_t bindingIndex,
+									   uint32_t count,
 									   MVKSampler* immutableSampler);
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
index ac6be1f..b8a4b94 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
@@ -24,18 +24,23 @@
 #pragma mark -
 #pragma mark MVKDescriptorSetLayout
 
+// Dynamic offsets passed in are in order of binding number, not binding index.
+// Each binding offsets relative to the index for the descriptor set itself.
 // A null cmdEncoder can be passed to perform a validation pass
-void MVKDescriptorSetLayout::bindDescriptorSet(MVKCommandEncoder* cmdEncoder,
-											   MVKDescriptorSet* descSet,
-											   MVKShaderResourceBinding& dslMTLRezIdxOffsets,
-											   MVKArrayRef<uint32_t> dynamicOffsets,
-											   uint32_t baseDynamicOffsetIndex) {
-	if (_isPushDescriptorLayout) return;
-
+uint32_t MVKDescriptorSetLayout::bindDescriptorSet(MVKCommandEncoder* cmdEncoder,
+												   MVKDescriptorSet* descSet,
+												   MVKShaderResourceBinding& dslMTLRezIdxOffsets,
+												   MVKArrayRef<uint32_t> dynamicOffsets,
+												   uint32_t dynamicOffsetIndex) {
 	clearConfigurationResult();
-	for (auto& dslBind : _bindings) {
-		dslBind.bind(cmdEncoder, descSet, dslMTLRezIdxOffsets, dynamicOffsets, baseDynamicOffsetIndex);
+	uint32_t dynOffsetsConsumed = 0;
+	if ( !_isPushDescriptorLayout ) {
+		for (auto& dslBind : _bindings) {
+			dynOffsetsConsumed += dslBind.bind(cmdEncoder, descSet, dslMTLRezIdxOffsets,
+											   dynamicOffsets, dynamicOffsetIndex);
+		}
 	}
+	return dynOffsetsConsumed;
 }
 
 static const void* getWriteParameters(VkDescriptorType type, const VkDescriptorImageInfo* pImageInfo,
@@ -170,19 +175,19 @@
 
 MVKDescriptorSetLayout::MVKDescriptorSetLayout(MVKDevice* device,
                                                const VkDescriptorSetLayoutCreateInfo* pCreateInfo) : MVKVulkanAPIDeviceObject(device) {
+	const auto* pBindingFlags = getBindingFlags(pCreateInfo);
     _isPushDescriptorLayout = (pCreateInfo->flags & VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR) != 0;
-
 	_descriptorCount = 0;
     _bindings.reserve(pCreateInfo->bindingCount);
 	MVKSmallVector<MVKDescriptorSetLayoutBinding*, 32> dynamicBindings;
     for (uint32_t i = 0; i < pCreateInfo->bindingCount; i++) {
 		auto* pBind = &pCreateInfo->pBindings[i];
-        _bindings.emplace_back(_device, this, pBind);
+        _bindings.emplace_back(_device, this, pBind, (pBindingFlags ? pBindingFlags[i] : 0));
 		_bindingToIndex[pBind->binding] = i;
 		_bindingToDescriptorIndex[pBind->binding] = _descriptorCount;
 
 		MVKDescriptorSetLayoutBinding* mvkBind = &_bindings.back();
-		_descriptorCount += mvkBind->getDescriptorCount();
+		_descriptorCount += mvkBind->getDescriptorCount(nullptr);
 		if (pBind->descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC ||
 			pBind->descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC) {
 			dynamicBindings.push_back(mvkBind);
@@ -196,13 +201,31 @@
 				[](MVKDescriptorSetLayoutBinding* mvkBind1, MVKDescriptorSetLayoutBinding* mvkBind2) {
 					return mvkBind1->getBinding() < mvkBind2->getBinding(); });
 
-	_dynamicDescriptorCount = 0;
+	// Only the last dynamic binding (highest binding number) can have a variable descriptor count.
+	// The other bindings will each have an accurate descriptor count while setting the dynamic offsets.
+	uint32_t dynDescCnt = 0;
 	for (auto mvkBind : dynamicBindings) {
-		mvkBind->setDynamicOffsetIndex(_dynamicDescriptorCount);
-		_dynamicDescriptorCount += mvkBind->getDescriptorCount();
+		mvkBind->setDynamicOffsetIndex(dynDescCnt);
+		dynDescCnt += mvkBind->getDescriptorCount(nullptr);
 	}
 }
 
+// Find and return an array of binding flags from the pNext chain of pCreateInfo,
+// or return nullptr if the chain does not include binding flags.
+const VkDescriptorBindingFlags* MVKDescriptorSetLayout::getBindingFlags(const VkDescriptorSetLayoutCreateInfo* pCreateInfo) {
+	for (const auto* next = (VkBaseInStructure*)pCreateInfo->pNext; next; next = next->pNext) {
+		switch (next->sType) {
+			case VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO_EXT: {
+				auto* pDescSetLayoutBindingFlags = (VkDescriptorSetLayoutBindingFlagsCreateInfoEXT*)next;
+				return pDescSetLayoutBindingFlags->bindingCount ? pDescSetLayoutBindingFlags->pBindingFlags : nullptr;
+			}
+			default:
+				break;
+		}
+	}
+	return nullptr;
+}
+
 
 #pragma mark -
 #pragma mark MVKDescriptorSet
@@ -261,31 +284,27 @@
     }
 }
 
-// If the descriptor pool fails to allocate a descriptor, record a configuration error
-MVKDescriptorSet::MVKDescriptorSet(MVKDescriptorSetLayout* layout, MVKDescriptorPool* pool) :
-	MVKVulkanAPIDeviceObject(pool->_device), _layout(layout), _pool(pool) {
+MVKDescriptorSet::MVKDescriptorSet(MVKDescriptorSetLayout* layout,
+								   uint32_t variableDescriptorCount,
+								   MVKDescriptorPool* pool) :
+	MVKVulkanAPIDeviceObject(pool->_device),
+	_layout(layout),
+	_variableDescriptorCount(variableDescriptorCount),
+	_pool(pool) {
 
 	_descriptors.reserve(layout->getDescriptorCount());
 	uint32_t bindCnt = (uint32_t)layout->_bindings.size();
 	for (uint32_t bindIdx = 0; bindIdx < bindCnt; bindIdx++) {
 		MVKDescriptorSetLayoutBinding* mvkDSLBind = &layout->_bindings[bindIdx];
-        MVKDescriptor* mvkDesc = nullptr;
-        if (mvkDSLBind->getDescriptorType() == VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT) {
-            setConfigurationResult(_pool->allocateDescriptor(mvkDSLBind->getDescriptorType(), &mvkDesc));
-            if ( !wasConfigurationSuccessful() ) { break; }
+		uint32_t descCnt = mvkDSLBind->getDescriptorCount(this);
+		for (uint32_t descIdx = 0; descIdx < descCnt; descIdx++) {
+			MVKDescriptor* mvkDesc = nullptr;
+			setConfigurationResult(_pool->allocateDescriptor(mvkDSLBind->getDescriptorType(), &mvkDesc));
+			if ( !wasConfigurationSuccessful() ) { break; }
 
-            mvkDesc->setLayout(mvkDSLBind, 0);
-            _descriptors.push_back(mvkDesc);
-        } else {
-            uint32_t descCnt = mvkDSLBind->getDescriptorCount();
-            for (uint32_t descIdx = 0; descIdx < descCnt; descIdx++) {
-                setConfigurationResult(_pool->allocateDescriptor(mvkDSLBind->getDescriptorType(), &mvkDesc));
-                if ( !wasConfigurationSuccessful() ) { break; }
-
-                mvkDesc->setLayout(mvkDSLBind, descIdx);
-                _descriptors.push_back(mvkDesc);
-            }
-        }
+			mvkDesc->setLayout(mvkDSLBind, descIdx);
+			_descriptors.push_back(mvkDesc);
+		}
 		if ( !wasConfigurationSuccessful() ) { break; }
 	}
 }
@@ -532,10 +551,9 @@
 #pragma mark -
 #pragma mark MVKDescriptorPool
 
-VkResult MVKDescriptorPool::allocateDescriptorSets(uint32_t count,
-												   const VkDescriptorSetLayout* pSetLayouts,
+VkResult MVKDescriptorPool::allocateDescriptorSets(const VkDescriptorSetAllocateInfo* pAllocateInfo,
 												   VkDescriptorSet* pDescriptorSets) {
-	if (_allocatedSets.size() + count > _maxSets) {
+	if (_allocatedSets.size() + pAllocateInfo->descriptorSetCount > _maxSets) {
 		if (_device->_enabledExtensions.vk_KHR_maintenance1.enabled ||
 			_device->getInstance()->getAPIVersion() >= VK_API_VERSION_1_1) {
 			return VK_ERROR_OUT_OF_POOL_MEMORY;		// Failure is an acceptable test...don't log as error.
@@ -545,16 +563,33 @@
 	}
 
 	VkResult rslt = VK_SUCCESS;
-	for (uint32_t dsIdx = 0; dsIdx < count; dsIdx++) {
-		MVKDescriptorSetLayout* mvkDSL = (MVKDescriptorSetLayout*)pSetLayouts[dsIdx];
+	const auto* pVarDescCounts = getVariableDecriptorCounts(pAllocateInfo);
+	for (uint32_t dsIdx = 0; dsIdx < pAllocateInfo->descriptorSetCount; dsIdx++) {
+		MVKDescriptorSetLayout* mvkDSL = (MVKDescriptorSetLayout*)pAllocateInfo->pSetLayouts[dsIdx];
 		if ( !mvkDSL->isPushDescriptorLayout() ) {
-			rslt = allocateDescriptorSet(mvkDSL, &pDescriptorSets[dsIdx]);
+			rslt = allocateDescriptorSet(mvkDSL, (pVarDescCounts ? pVarDescCounts[dsIdx] : 0), &pDescriptorSets[dsIdx]);
 			if (rslt) { break; }
 		}
 	}
 	return rslt;
 }
 
+// Find and return an array of variable descriptor counts from the pNext chain of pCreateInfo,
+// or return nullptr if the chain does not include variable descriptor counts.
+const uint32_t* MVKDescriptorPool::getVariableDecriptorCounts(const VkDescriptorSetAllocateInfo* pAllocateInfo) {
+	for (const auto* next = (VkBaseInStructure*)pAllocateInfo->pNext; next; next = next->pNext) {
+		switch (next->sType) {
+			case VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO_EXT: {
+				auto* pVarDescSetVarCounts = (VkDescriptorSetVariableDescriptorCountAllocateInfoEXT*)next;
+				return pVarDescSetVarCounts->descriptorSetCount ? pVarDescSetVarCounts->pDescriptorCounts : nullptr;
+			}
+			default:
+				break;
+		}
+	}
+	return nullptr;
+}
+
 // Ensure descriptor set was actually allocated, then return to pool
 VkResult MVKDescriptorPool::freeDescriptorSets(uint32_t count, const VkDescriptorSet* pDescriptorSets) {
 	for (uint32_t dsIdx = 0; dsIdx < count; dsIdx++) {
@@ -575,8 +610,9 @@
 }
 
 VkResult MVKDescriptorPool::allocateDescriptorSet(MVKDescriptorSetLayout* mvkDSL,
+												  uint32_t variableDescriptorCount,
 												  VkDescriptorSet* pVKDS) {
-	MVKDescriptorSet* mvkDS = new MVKDescriptorSet(mvkDSL, this);
+	MVKDescriptorSet* mvkDS = new MVKDescriptorSet(mvkDSL, variableDescriptorCount, this);
 	VkResult rslt = mvkDS->getConfigurationResult();
 
 	if (mvkDS->wasConfigurationSuccessful()) {
@@ -781,6 +817,7 @@
 									   spv::ExecutionModel stage,
 									   uint32_t descriptorSetIndex,
 									   uint32_t bindingIndex,
+									   uint32_t count,
 									   MVKSampler* immutableSampler) {
 	mvk::MSLResourceBinding rb;
 
@@ -788,6 +825,7 @@
 	rbb.stage = stage;
 	rbb.desc_set = descriptorSetIndex;
 	rbb.binding = bindingIndex;
+	rbb.count = count;
 	rbb.msl_buffer = ssRB.bufferIndex;
 	rbb.msl_texture = ssRB.textureIndex;
 	rbb.msl_sampler = ssRB.samplerIndex;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
index c6e3a96..5beb05a 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
@@ -667,6 +667,14 @@
     /** Returns the memory type index corresponding to the specified Metal memory storage mode. */
     uint32_t getVulkanMemoryTypeIndex(MTLStorageMode mtlStorageMode);
 
+	/**
+	 * Returns whether MTLCommandBuffers can be prefilled.
+	 *
+	 * This depends both on whether the app config has requested prefilling, and whether doing so will
+	 * interfere with other requested features, such as updating resource descriptors after bindings.
+	 */
+	bool shouldPrefillMTLCommandBuffers();
+
 
 #pragma mark Properties directly accessible
 
@@ -680,6 +688,8 @@
 	const VkPhysicalDeviceFloat16Int8FeaturesKHR _enabledF16I8Features;
 	const VkPhysicalDeviceUniformBufferStandardLayoutFeaturesKHR _enabledUBOLayoutFeatures;
 	const VkPhysicalDeviceVariablePointerFeatures _enabledVarPtrFeatures;
+	const VkPhysicalDeviceDescriptorIndexingFeaturesEXT _enabledDescriptorIndexingFeatures;
+	const VkPhysicalDeviceInlineUniformBlockFeaturesEXT _enabledInlineUniformBlockFeatures;
 	const VkPhysicalDeviceFragmentShaderInterlockFeaturesEXT _enabledInterlockFeatures;
 	const VkPhysicalDeviceHostQueryResetFeaturesEXT _enabledHostQryResetFeatures;
 	const VkPhysicalDeviceSamplerYcbcrConversionFeatures _enabledSamplerYcbcrConversionFeatures;
@@ -741,6 +751,9 @@
     const char* getActivityPerformanceDescription(MVKPerformanceTracker& activity, MVKPerformanceStatistics& perfStats);
 	void logActivityPerformance(MVKPerformanceTracker& activity, MVKPerformanceStatistics& perfStats, bool isInline = false);
 	void updateActivityPerformance(MVKPerformanceTracker& activity, uint64_t startTime, uint64_t endTime);
+	void getDescriptorVariableDescriptorCountLayoutSupport(const VkDescriptorSetLayoutCreateInfo* pCreateInfo,
+														   VkDescriptorSetLayoutSupport* pSupport,
+														   VkDescriptorSetVariableDescriptorCountLayoutSupportEXT* pVarDescSetCountSupport);
 
 	MVKPhysicalDevice* _physicalDevice;
     MVKCommandResourceFactory* _commandResourceFactory;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
index 6546d95..22ce772 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
@@ -134,6 +134,30 @@
 				varPtrFeatures->variablePointers = true;
 				break;
 			}
+			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT: {
+				auto* pDescIdxFeatures = (VkPhysicalDeviceDescriptorIndexingFeaturesEXT*)next;
+				pDescIdxFeatures->shaderInputAttachmentArrayDynamicIndexing = _metalFeatures.arrayOfTextures;
+				pDescIdxFeatures->shaderUniformTexelBufferArrayDynamicIndexing = _metalFeatures.arrayOfTextures;
+				pDescIdxFeatures->shaderStorageTexelBufferArrayDynamicIndexing = _metalFeatures.arrayOfTextures;
+				pDescIdxFeatures->shaderUniformBufferArrayNonUniformIndexing = false;
+				pDescIdxFeatures->shaderSampledImageArrayNonUniformIndexing = _metalFeatures.arrayOfTextures && _metalFeatures.arrayOfSamplers;
+				pDescIdxFeatures->shaderStorageBufferArrayNonUniformIndexing = false;
+				pDescIdxFeatures->shaderStorageImageArrayNonUniformIndexing = _metalFeatures.arrayOfTextures;
+				pDescIdxFeatures->shaderInputAttachmentArrayNonUniformIndexing = _metalFeatures.arrayOfTextures;
+				pDescIdxFeatures->shaderUniformTexelBufferArrayNonUniformIndexing = _metalFeatures.arrayOfTextures;
+				pDescIdxFeatures->shaderStorageTexelBufferArrayNonUniformIndexing = _metalFeatures.arrayOfTextures;
+				pDescIdxFeatures->descriptorBindingUniformBufferUpdateAfterBind = true;
+				pDescIdxFeatures->descriptorBindingSampledImageUpdateAfterBind = true;
+				pDescIdxFeatures->descriptorBindingStorageImageUpdateAfterBind = true;
+				pDescIdxFeatures->descriptorBindingStorageBufferUpdateAfterBind = true;
+				pDescIdxFeatures->descriptorBindingUniformTexelBufferUpdateAfterBind = true;
+				pDescIdxFeatures->descriptorBindingStorageTexelBufferUpdateAfterBind = true;
+				pDescIdxFeatures->descriptorBindingUpdateUnusedWhilePending = true;
+				pDescIdxFeatures->descriptorBindingPartiallyBound = true;
+				pDescIdxFeatures->descriptorBindingVariableDescriptorCount = true;
+				pDescIdxFeatures->runtimeDescriptorArray = true;
+				break;
+			}
 			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_INTERLOCK_FEATURES_EXT: {
 				auto* interlockFeatures = (VkPhysicalDeviceFragmentShaderInterlockFeaturesEXT*)next;
 				interlockFeatures->fragmentShaderSampleInterlock = _metalFeatures.rasterOrderGroups;
@@ -315,6 +339,33 @@
                 timelineSem4Props->maxTimelineSemaphoreValueDifference = std::numeric_limits<uint64_t>::max();
                 break;
             }
+			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_PROPERTIES_EXT: {
+				auto* pDescIdxProps = (VkPhysicalDeviceDescriptorIndexingPropertiesEXT*)next;
+				pDescIdxProps->maxUpdateAfterBindDescriptorsInAllPools				= kMVKUndefinedLargeUInt32;
+				pDescIdxProps->shaderUniformBufferArrayNonUniformIndexingNative		= false;
+				pDescIdxProps->shaderSampledImageArrayNonUniformIndexingNative		= _metalFeatures.arrayOfTextures && _metalFeatures.arrayOfSamplers;
+				pDescIdxProps->shaderStorageBufferArrayNonUniformIndexingNative		= false;
+				pDescIdxProps->shaderStorageImageArrayNonUniformIndexingNative		= _metalFeatures.arrayOfTextures;
+				pDescIdxProps->shaderInputAttachmentArrayNonUniformIndexingNative	= _metalFeatures.arrayOfTextures;
+				pDescIdxProps->robustBufferAccessUpdateAfterBind					= _features.robustBufferAccess;
+				pDescIdxProps->quadDivergentImplicitLod								= false;
+				pDescIdxProps->maxPerStageDescriptorUpdateAfterBindSamplers			= _properties.limits.maxPerStageDescriptorSamplers;
+				pDescIdxProps->maxPerStageDescriptorUpdateAfterBindUniformBuffers	= _properties.limits.maxPerStageDescriptorUniformBuffers;
+				pDescIdxProps->maxPerStageDescriptorUpdateAfterBindStorageBuffers	= _properties.limits.maxPerStageDescriptorStorageBuffers;
+				pDescIdxProps->maxPerStageDescriptorUpdateAfterBindSampledImages	= _properties.limits.maxPerStageDescriptorSampledImages;
+				pDescIdxProps->maxPerStageDescriptorUpdateAfterBindStorageImages	= _properties.limits.maxPerStageDescriptorStorageImages;
+				pDescIdxProps->maxPerStageDescriptorUpdateAfterBindInputAttachments	= _properties.limits.maxPerStageDescriptorInputAttachments;
+				pDescIdxProps->maxPerStageUpdateAfterBindResources					= _properties.limits.maxPerStageResources;
+				pDescIdxProps->maxDescriptorSetUpdateAfterBindSamplers				= _properties.limits.maxDescriptorSetSamplers;
+				pDescIdxProps->maxDescriptorSetUpdateAfterBindUniformBuffers		= _properties.limits.maxDescriptorSetUniformBuffers;
+				pDescIdxProps->maxDescriptorSetUpdateAfterBindUniformBuffersDynamic	= _properties.limits.maxDescriptorSetUniformBuffersDynamic;
+				pDescIdxProps->maxDescriptorSetUpdateAfterBindStorageBuffers		= _properties.limits.maxDescriptorSetStorageBuffers;
+				pDescIdxProps->maxDescriptorSetUpdateAfterBindStorageBuffersDynamic	= _properties.limits.maxDescriptorSetStorageBuffersDynamic;
+				pDescIdxProps->maxDescriptorSetUpdateAfterBindSampledImages			= _properties.limits.maxDescriptorSetSampledImages;
+				pDescIdxProps->maxDescriptorSetUpdateAfterBindStorageImages			= _properties.limits.maxDescriptorSetStorageImages;
+				pDescIdxProps->maxDescriptorSetUpdateAfterBindInputAttachments		= _properties.limits.maxDescriptorSetInputAttachments;
+				break;
+			}
             case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INLINE_UNIFORM_BLOCK_PROPERTIES_EXT: {
 				auto* inlineUniformBlockProps = (VkPhysicalDeviceInlineUniformBlockPropertiesEXT*)next;
 				inlineUniformBlockProps->maxInlineUniformBlockSize = _metalFeatures.dynamicMTLBufferSize;
@@ -1076,9 +1127,10 @@
 
 	_metalFeatures.vertexStrideAlignment = 4;
 
+	_metalFeatures.maxPerStageStorageTextureCount = 8;
+
 #if MVK_TVOS
 	_metalFeatures.mslVersionEnum = MTLLanguageVersion1_1;
-    _metalFeatures.maxPerStageTextureCount = 31;
     _metalFeatures.mtlBufferAlignment = 64;
 	_metalFeatures.mtlCopyBufferAlignment = 1;
     _metalFeatures.texelBuffers = true;
@@ -1126,6 +1178,12 @@
 		}
 	}
 
+	if (supportsMTLGPUFamily(Apple4)) {
+		_metalFeatures.maxPerStageTextureCount = 96;
+	} else {
+		_metalFeatures.maxPerStageTextureCount = 31;
+	}
+
 #if MVK_XCODE_12
 	if ( mvkOSVersionIsAtLeast(14.0) ) {
 		_metalFeatures.mslVersionEnum = MTLLanguageVersion2_3;
@@ -1136,7 +1194,6 @@
 
 #if MVK_IOS
 	_metalFeatures.mslVersionEnum = MTLLanguageVersion1_0;
-    _metalFeatures.maxPerStageTextureCount = 31;
     _metalFeatures.mtlBufferAlignment = 64;
 	_metalFeatures.mtlCopyBufferAlignment = 1;
     _metalFeatures.texelBuffers = true;
@@ -1206,6 +1263,12 @@
 		}
 	}
 
+	if (supportsMTLGPUFamily(Apple4)) {
+		_metalFeatures.maxPerStageTextureCount = 96;
+	} else {
+		_metalFeatures.maxPerStageTextureCount = 31;
+	}
+
 #if MVK_XCODE_12
 	if ( mvkOSVersionIsAtLeast(14.0) ) {
 		_metalFeatures.mslVersionEnum = MTLLanguageVersion2_3;
@@ -1582,7 +1645,7 @@
 	_properties.limits.maxPerStageDescriptorUniformBuffers = _metalFeatures.maxPerStageBufferCount;
 	_properties.limits.maxPerStageDescriptorStorageBuffers = _metalFeatures.maxPerStageBufferCount;
 	_properties.limits.maxPerStageDescriptorSampledImages = _metalFeatures.maxPerStageTextureCount;
-	_properties.limits.maxPerStageDescriptorStorageImages = _metalFeatures.maxPerStageTextureCount;
+	_properties.limits.maxPerStageDescriptorStorageImages = _metalFeatures.maxPerStageStorageTextureCount;
 	_properties.limits.maxPerStageDescriptorInputAttachments = _metalFeatures.maxPerStageTextureCount;
 
     _properties.limits.maxPerStageResources = (_metalFeatures.maxPerStageBufferCount + _metalFeatures.maxPerStageTextureCount);
@@ -2603,6 +2666,120 @@
 		descriptorCount += pCreateInfo->pBindings[i].descriptorCount;
 	}
 	pSupport->supported = (descriptorCount < ((_physicalDevice->_metalFeatures.maxPerStageBufferCount + _physicalDevice->_metalFeatures.maxPerStageTextureCount + _physicalDevice->_metalFeatures.maxPerStageSamplerCount) * 2));
+
+	// Check whether the layout has a variable-count descriptor, and if so, whether we can support it.
+	for (auto* next = (VkBaseOutStructure*)pSupport->pNext; next; next = next->pNext) {
+		switch (next->sType) {
+			case VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_LAYOUT_SUPPORT_EXT: {
+				auto* pVarDescSetCountSupport = (VkDescriptorSetVariableDescriptorCountLayoutSupportEXT*)next;
+				getDescriptorVariableDescriptorCountLayoutSupport(pCreateInfo, pSupport, pVarDescSetCountSupport);
+				break;
+			}
+			default:
+				break;
+		}
+	}
+}
+
+// Check whether the layout has a variable-count descriptor, and if so, whether we can support it.
+void MVKDevice::getDescriptorVariableDescriptorCountLayoutSupport(const VkDescriptorSetLayoutCreateInfo* pCreateInfo,
+																  VkDescriptorSetLayoutSupport* pSupport,
+																  VkDescriptorSetVariableDescriptorCountLayoutSupportEXT* pVarDescSetCountSupport) {
+	// Assume we don't need this, then set appropriately if we do.
+	pVarDescSetCountSupport->maxVariableDescriptorCount = 0;
+
+	// Look for a variable length descriptor and remember its index.
+	int32_t varBindingIdx = -1;
+	for (const auto* next = (VkBaseInStructure*)pCreateInfo->pNext; next; next = next->pNext) {
+		switch (next->sType) {
+			case VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO_EXT: {
+				auto* pDescSetLayoutBindingFlags = (VkDescriptorSetLayoutBindingFlagsCreateInfoEXT*)next;
+				for (uint32_t bindIdx = 0; bindIdx < pDescSetLayoutBindingFlags->bindingCount; bindIdx++) {
+					if (mvkIsAnyFlagEnabled(pDescSetLayoutBindingFlags->pBindingFlags[bindIdx], VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT)) {
+						varBindingIdx = bindIdx;
+						break;
+					}
+				}
+				break;
+			}
+			default:
+				break;
+		}
+	}
+
+	// If no variable length descriptor is found, we can skip the rest.
+	if (varBindingIdx < 0) { return; }
+
+	// Device does not support variable descriptor counts but it has been requested.
+	if ( !_enabledDescriptorIndexingFeatures.descriptorBindingVariableDescriptorCount ) {
+		pSupport->supported = false;
+		return;
+	}
+
+	uint32_t mtlBuffCnt = 0;
+	uint32_t mtlTexCnt = 0;
+	uint32_t mtlSampCnt = 0;
+	uint32_t requestedCount = 0;
+	uint32_t maxVarDescCount = 0;
+
+	// Determine the number of descriptors available for use by the variable descriptor by
+	// accumulating the number of resources accumulated by other descriptors and subtracting
+	// that from the device's per-stage max counts. This is not perfect because it does not
+	// take into consideration other descriptor sets in the pipeline layout, but we can't
+	// anticipate that here. The variable descriptor must have the highest binding number,
+	// but it may not be the last descriptor in the array. The handling here accommodates that.
+	for (uint32_t bindIdx = 0; bindIdx < pCreateInfo->bindingCount; bindIdx++) {
+		auto* pBind = &pCreateInfo->pBindings[bindIdx];
+		if (bindIdx == varBindingIdx) {
+			requestedCount = std::max(pBind->descriptorCount, 1u);
+		} else {
+			switch (pBind->descriptorType) {
+				case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
+				case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
+				case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
+				case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
+					mtlBuffCnt += pBind->descriptorCount;
+					maxVarDescCount = _pMetalFeatures->maxPerStageBufferCount - mtlBuffCnt;
+					break;
+				case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT:
+					maxVarDescCount = (uint32_t)min<VkDeviceSize>(_pMetalFeatures->maxMTLBufferSize, numeric_limits<uint32_t>::max());
+					break;
+				case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
+				case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
+				case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
+					mtlTexCnt += pBind->descriptorCount;
+					maxVarDescCount = _pMetalFeatures->maxPerStageTextureCount - mtlTexCnt;
+					break;
+				case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
+				case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
+					mtlTexCnt += pBind->descriptorCount;
+					mtlBuffCnt += pBind->descriptorCount;
+					maxVarDescCount = min(_pMetalFeatures->maxPerStageTextureCount - mtlTexCnt,
+										  _pMetalFeatures->maxPerStageBufferCount - mtlBuffCnt);
+					break;
+				case VK_DESCRIPTOR_TYPE_SAMPLER:
+					mtlSampCnt += pBind->descriptorCount;
+					maxVarDescCount = _pMetalFeatures->maxPerStageSamplerCount - mtlSampCnt;
+					break;
+				case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
+					mtlTexCnt += pBind->descriptorCount;
+					mtlSampCnt += pBind->descriptorCount;
+					maxVarDescCount = min(_pMetalFeatures->maxPerStageTextureCount - mtlTexCnt,
+										  _pMetalFeatures->maxPerStageSamplerCount - mtlSampCnt);
+					break;
+				default:
+					break;
+			}
+		}
+	}
+
+	// If there is enough room for the requested size, indicate the amount available,
+	// otherwise indicate that the requested size cannot be supported.
+	if (requestedCount < maxVarDescCount) {
+		pVarDescSetCountSupport->maxVariableDescriptorCount = maxVarDescCount;
+	} else {
+		pSupport->supported = false;
+	}
 }
 
 VkResult MVKDevice::getDeviceGroupPresentCapabilities(VkDeviceGroupPresentCapabilitiesKHR* pDeviceGroupPresentCapabilities) {
@@ -3246,6 +3423,18 @@
     return _globalVisibilityQueryCount - queryCount;     // Might be lower than requested if an overflow occurred
 }
 
+// Can't use prefilled Metal command buffers if any of the resource descriptors can be updated after binding.
+bool MVKDevice::shouldPrefillMTLCommandBuffers() {
+	return (_pMVKConfig->prefillMetalCommandBuffers &&
+			!(_enabledDescriptorIndexingFeatures.descriptorBindingUniformBufferUpdateAfterBind ||
+			  _enabledDescriptorIndexingFeatures.descriptorBindingSampledImageUpdateAfterBind ||
+			  _enabledDescriptorIndexingFeatures.descriptorBindingStorageImageUpdateAfterBind ||
+			  _enabledDescriptorIndexingFeatures.descriptorBindingStorageBufferUpdateAfterBind ||
+			  _enabledDescriptorIndexingFeatures.descriptorBindingUniformTexelBufferUpdateAfterBind ||
+			  _enabledDescriptorIndexingFeatures.descriptorBindingStorageTexelBufferUpdateAfterBind ||
+			  _enabledInlineUniformBlockFeatures.descriptorBindingInlineUniformBlockUpdateAfterBind));
+}
+
 
 #pragma mark Construction
 
@@ -3256,6 +3445,8 @@
 	_enabledF16I8Features(),
 	_enabledUBOLayoutFeatures(),
 	_enabledVarPtrFeatures(),
+	_enabledDescriptorIndexingFeatures(),
+	_enabledInlineUniformBlockFeatures(),
 	_enabledInterlockFeatures(),
 	_enabledHostQryResetFeatures(),
 	_enabledSamplerYcbcrConversionFeatures(),
@@ -3372,7 +3563,6 @@
 	_pProperties = &_physicalDevice->_properties;
 	_pMemoryProperties = &_physicalDevice->_memoryProperties;
 
-
 	// Indicates whether semaphores should use a MTLFence if available.
 	// Set by the MVK_ALLOW_METAL_FENCES environment variable if MTLFences are available.
 	// This should be a temporary fix after some repair to semaphore handling.
@@ -3405,6 +3595,8 @@
 	mvkClear(&_enabledF16I8Features);
 	mvkClear(&_enabledUBOLayoutFeatures);
 	mvkClear(&_enabledVarPtrFeatures);
+	mvkClear(&_enabledDescriptorIndexingFeatures);
+	mvkClear(&_enabledInlineUniformBlockFeatures);
 	mvkClear(&_enabledInterlockFeatures);
 	mvkClear(&_enabledHostQryResetFeatures);
 	mvkClear(&_enabledSamplerYcbcrConversionFeatures);
@@ -3447,9 +3639,17 @@
 	pdInterlockFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_INTERLOCK_FEATURES_EXT;
 	pdInterlockFeatures.pNext = &pdHostQryResetFeatures;
 
+	VkPhysicalDeviceInlineUniformBlockFeaturesEXT pdInlnUnfmBlkFeatures;
+	pdInlnUnfmBlkFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INLINE_UNIFORM_BLOCK_FEATURES_EXT;
+	pdInlnUnfmBlkFeatures.pNext = &pdInterlockFeatures;
+
+	VkPhysicalDeviceDescriptorIndexingFeaturesEXT pdDescIdxFeatures;
+	pdDescIdxFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT;
+	pdDescIdxFeatures.pNext = &pdInlnUnfmBlkFeatures;
+
 	VkPhysicalDeviceVariablePointerFeatures pdVarPtrFeatures;
 	pdVarPtrFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTER_FEATURES;
-	pdVarPtrFeatures.pNext = &pdInterlockFeatures;
+	pdVarPtrFeatures.pNext = &pdDescIdxFeatures;
 
 	VkPhysicalDeviceUniformBufferStandardLayoutFeaturesKHR pdUBOLayoutFeatures;
 	pdUBOLayoutFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_UNIFORM_BUFFER_STANDARD_LAYOUT_FEATURES_KHR;
@@ -3524,6 +3724,20 @@
 							   &pdVarPtrFeatures.variablePointersStorageBuffer, 2);
 				break;
 			}
+			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT: {
+				auto* requestedFeatures = (VkPhysicalDeviceDescriptorIndexingFeaturesEXT*)next;
+				enableFeatures(&_enabledDescriptorIndexingFeatures.shaderInputAttachmentArrayDynamicIndexing,
+							   &requestedFeatures->shaderInputAttachmentArrayDynamicIndexing,
+							   &pdDescIdxFeatures.shaderInputAttachmentArrayDynamicIndexing, 20);
+				break;
+			}
+			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INLINE_UNIFORM_BLOCK_FEATURES_EXT: {
+				auto* requestedFeatures = (VkPhysicalDeviceInlineUniformBlockFeaturesEXT*)next;
+				enableFeatures(&_enabledInlineUniformBlockFeatures.inlineUniformBlock,
+							   &requestedFeatures->inlineUniformBlock,
+							   &pdInlnUnfmBlkFeatures.inlineUniformBlock, 2);
+				break;
+			}
 			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_INTERLOCK_FEATURES_EXT: {
 				auto* requestedFeatures = (VkPhysicalDeviceFragmentShaderInterlockFeaturesEXT*)next;
 				enableFeatures(&_enabledInterlockFeatures.fragmentShaderSampleInterlock,
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
index b2f9930..a0c6e9b 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
@@ -42,16 +42,15 @@
                                            uint32_t firstSet,
                                            MVKArrayRef<uint32_t> dynamicOffsets) {
 	clearConfigurationResult();
-	uint32_t baseDynamicOffsetIndex = 0;
+	uint32_t dynamicOffsetIndex = 0;
 	size_t dsCnt = descriptorSets.size;
 	for (uint32_t dsIdx = 0; dsIdx < dsCnt; dsIdx++) {
 		MVKDescriptorSet* descSet = descriptorSets[dsIdx];
 		uint32_t dslIdx = firstSet + dsIdx;
 		MVKDescriptorSetLayout* dsl = _descriptorSetLayouts[dslIdx];
-		dsl->bindDescriptorSet(cmdEncoder, descSet,
-							   _dslMTLResourceIndexOffsets[dslIdx],
-							   dynamicOffsets, baseDynamicOffsetIndex);
-		baseDynamicOffsetIndex += dsl->getDynamicDescriptorCount();
+		dynamicOffsetIndex += dsl->bindDescriptorSet(cmdEncoder, descSet,
+													 _dslMTLResourceIndexOffsets[dslIdx],
+													 dynamicOffsets, dynamicOffsetIndex);
 		setConfigurationResult(dsl->getConfigurationResult());
 	}
 }
@@ -102,6 +101,7 @@
 										  models[i],
 										  kPushConstDescSet,
 										  kPushConstBinding,
+										  1,
 										  nullptr);
 	}
 }
@@ -1970,7 +1970,7 @@
 				opt.enable_base_index_zero,
 				opt.pad_fragment_output_components,
 				opt.ios_support_base_vertex_instance,
-				opt.ios_use_framebuffer_fetch_subpasses,
+				opt.use_framebuffer_fetch_subpasses,
 				opt.invariant_float_math,
 				opt.emulate_cube_array,
 				opt.enable_decoration_binding,
@@ -1980,8 +1980,8 @@
 				opt.enable_clip_distance_user_varying,
 				opt.multi_patch_workgroup,
 				opt.vertex_for_tessellation,
-				opt.vertex_index_type,
-				opt.arrayed_subpass_input);
+				opt.arrayed_subpass_input,
+				opt.vertex_index_type);
 	}
 
 	template<class Archive>
@@ -1997,6 +1997,7 @@
 		archive(rb.stage,
 				rb.desc_set,
 				rb.binding,
+				rb.count,
 				rb.msl_buffer,
 				rb.msl_texture,
 				rb.msl_sampler);
diff --git a/MoltenVK/MoltenVK/Layers/MVKExtensions.def b/MoltenVK/MoltenVK/Layers/MVKExtensions.def
index 0364cd9..c9bd20f 100644
--- a/MoltenVK/MoltenVK/Layers/MVKExtensions.def
+++ b/MoltenVK/MoltenVK/Layers/MVKExtensions.def
@@ -81,6 +81,7 @@
 MVK_EXTENSION(EXT_debug_marker, EXT_DEBUG_MARKER, DEVICE)
 MVK_EXTENSION(EXT_debug_report, EXT_DEBUG_REPORT, INSTANCE)
 MVK_EXTENSION(EXT_debug_utils, EXT_DEBUG_UTILS, INSTANCE)
+MVK_EXTENSION(EXT_descriptor_indexing, EXT_DESCRIPTOR_INDEXING, DEVICE)
 MVK_EXTENSION(EXT_fragment_shader_interlock, EXT_FRAGMENT_SHADER_INTERLOCK, DEVICE)
 MVK_EXTENSION(EXT_hdr_metadata, EXT_HDR_METADATA, DEVICE)
 MVK_EXTENSION(EXT_host_query_reset, EXT_HOST_QUERY_RESET, DEVICE)
diff --git a/MoltenVK/MoltenVK/Vulkan/vulkan.mm b/MoltenVK/MoltenVK/Vulkan/vulkan.mm
index 208c566..8aa6359 100644
--- a/MoltenVK/MoltenVK/Vulkan/vulkan.mm
+++ b/MoltenVK/MoltenVK/Vulkan/vulkan.mm
@@ -1172,9 +1172,7 @@
 
 	MVKTraceVulkanCallStart();
 	MVKDescriptorPool* mvkDP = (MVKDescriptorPool*)pAllocateInfo->descriptorPool;
-	VkResult rslt = mvkDP->allocateDescriptorSets(pAllocateInfo->descriptorSetCount,
-												  pAllocateInfo->pSetLayouts,
-												  pDescriptorSets);
+	VkResult rslt = mvkDP->allocateDescriptorSets(pAllocateInfo, pDescriptorSets);
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
diff --git a/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVToMSLConverter.cpp b/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVToMSLConverter.cpp
index 705bb72..fb1bb93 100644
--- a/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVToMSLConverter.cpp
+++ b/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVToMSLConverter.cpp
@@ -101,6 +101,7 @@
 	if (resourceBinding.stage != other.resourceBinding.stage) { return false; }
 	if (resourceBinding.desc_set != other.resourceBinding.desc_set) { return false; }
 	if (resourceBinding.binding != other.resourceBinding.binding) { return false; }
+	if (resourceBinding.count != other.resourceBinding.count) { return false; }
 	if (resourceBinding.msl_buffer != other.resourceBinding.msl_buffer) { return false; }
 	if (resourceBinding.msl_texture != other.resourceBinding.msl_texture) { return false; }
 	if (resourceBinding.msl_sampler != other.resourceBinding.msl_sampler) { return false; }