Support preallocated descriptor pooling via VkDescriptorPoolSize.

Add MVK_CONFIG_PREALLOCATE_DESCRIPTORS env var to enable descriptor preallocation
in pool. Add MVKDescriptor subclasses for all used VkDescriptorType values.
Support allocating MVKDescriptor subclasses via direct instantiation,
or referencing preallocated instances held in a fixed collection.
MVKDescriptor subclasses support resetting and reuse from preallocated collection.
MVKDescriptorPool optionally holds collections of preallocated descriptors,
one collection per VkDescriptorType value.
Support VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT to mark preallocated
descriptors as available when freed.
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.h b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.h
index 833656b..d4ef426 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.h
@@ -140,6 +140,8 @@
 	/** Returns the Vulkan API opaque object controlling this object. */
 	MVKVulkanAPIObject* getVulkanAPIObject() override { return nullptr; };
 
+	virtual VkDescriptorType getDescriptorType() = 0;
+
 	/** Encodes this descriptor (based on its layout binding index) on the the command encoder. */
 	virtual void bind(MVKCommandEncoder* cmdEncoder,
 					  VkDescriptorType descriptorType,
@@ -180,6 +182,11 @@
 	/** Sets the binding layout. */
 	virtual void setLayout(MVKDescriptorSetLayoutBinding* dslBinding, uint32_t index) {}
 
+	/** Resets any internal content. */
+	virtual void reset() {}
+
+	~MVKDescriptor() { reset(); }
+
 };
 
 
@@ -212,7 +219,9 @@
 			  VkBufferView* pTexelBufferView,
 			  VkWriteDescriptorSetInlineUniformBlockEXT* inlineUniformBlock) override;
 
-	~MVKBufferDescriptor();
+	void reset() override;
+
+	~MVKBufferDescriptor() { reset(); }
 
 protected:
 	MVKBuffer* _mvkBuffer = nullptr;
@@ -222,12 +231,50 @@
 
 
 #pragma mark -
-#pragma mark MVKInlineUniformDescriptor
+#pragma mark MVKUniformBufferDescriptor
+
+class MVKUniformBufferDescriptor : public MVKBufferDescriptor {
+public:
+	VkDescriptorType getDescriptorType() override { return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; }
+};
+
+
+#pragma mark -
+#pragma mark MVKUniformBufferDynamicDescriptor
+
+class MVKUniformBufferDynamicDescriptor : public MVKBufferDescriptor {
+public:
+	VkDescriptorType getDescriptorType() override { return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; }
+};
+
+
+#pragma mark -
+#pragma mark MVKStorageBufferDescriptor
+
+class MVKStorageBufferDescriptor : public MVKBufferDescriptor {
+public:
+	VkDescriptorType getDescriptorType() override { return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; }
+};
+
+
+#pragma mark -
+#pragma mark MVKStorageBufferDynamicDescriptor
+
+class MVKStorageBufferDynamicDescriptor : public MVKBufferDescriptor {
+public:
+	VkDescriptorType getDescriptorType() override { return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC; }
+};
+
+
+#pragma mark -
+#pragma mark MVKInlineUniformBlockDescriptor
 
 /** Represents a Vulkan descriptor tracking an inline block of uniform data. */
-class MVKInlineUniformDescriptor : public MVKDescriptor {
+class MVKInlineUniformBlockDescriptor : public MVKDescriptor {
 
 public:
+	VkDescriptorType getDescriptorType() override { return VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT; }
+
 	void bind(MVKCommandEncoder* cmdEncoder,
 			  VkDescriptorType descriptorType,
 			  uint32_t descriptorIndex,
@@ -250,11 +297,13 @@
 			  VkBufferView* pTexelBufferView,
 			  VkWriteDescriptorSetInlineUniformBlockEXT* inlineUniformBlock) override;
 
-	~MVKInlineUniformDescriptor();
+	void reset() override;
+
+	~MVKInlineUniformBlockDescriptor() { reset(); }
 
 protected:
 	id<MTLBuffer> _mtlBuffer = nil;
-	uint32_t _dataSize;
+	uint32_t _dataSize = 0;
 };
 
 
@@ -287,7 +336,9 @@
 			  VkBufferView* pTexelBufferView,
 			  VkWriteDescriptorSetInlineUniformBlockEXT* inlineUniformBlock) override;
 
-	~MVKImageDescriptor();
+	void reset() override;
+
+	~MVKImageDescriptor() { reset(); }
 
 protected:
 	MVKImageView* _mvkImageView = nullptr;
@@ -296,6 +347,33 @@
 
 
 #pragma mark -
+#pragma mark MVKSampledImageDescriptor
+
+class MVKSampledImageDescriptor : public MVKImageDescriptor {
+public:
+	VkDescriptorType getDescriptorType() override { return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; }
+};
+
+
+#pragma mark -
+#pragma mark MVKStorageImageDescriptor
+
+class MVKStorageImageDescriptor : public MVKImageDescriptor {
+public:
+	VkDescriptorType getDescriptorType() override { return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; }
+};
+
+
+#pragma mark -
+#pragma mark MVKInputAttachmentDescriptor
+
+class MVKInputAttachmentDescriptor : public MVKImageDescriptor {
+public:
+	VkDescriptorType getDescriptorType() override { return VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT; }
+};
+
+
+#pragma mark -
 #pragma mark MVKSamplerDescriptorMixin
 
 /**
@@ -332,7 +410,9 @@
 
 	void setLayout(MVKDescriptorSetLayoutBinding* dslBinding, uint32_t index);
 
-	virtual ~MVKSamplerDescriptorMixin();
+	void reset();
+
+	~MVKSamplerDescriptorMixin() { reset(); }
 
 	MVKSampler* _mvkSampler = nullptr;
 	bool _hasDynamicSampler = true;
@@ -346,6 +426,8 @@
 class MVKSamplerDescriptor : public MVKDescriptor, public MVKSamplerDescriptorMixin {
 
 public:
+	VkDescriptorType getDescriptorType() override { return VK_DESCRIPTOR_TYPE_SAMPLER; }
+
 	void bind(MVKCommandEncoder* cmdEncoder,
 			  VkDescriptorType descriptorType,
 			  uint32_t descriptorIndex,
@@ -370,6 +452,10 @@
 
 	void setLayout(MVKDescriptorSetLayoutBinding* dslBinding, uint32_t index) override;
 
+	void reset() override;
+
+	~MVKSamplerDescriptor() { reset(); }
+
 };
 
 
@@ -380,6 +466,8 @@
 class MVKCombinedImageSamplerDescriptor : public MVKImageDescriptor, public MVKSamplerDescriptorMixin {
 
 public:
+	VkDescriptorType getDescriptorType() override { return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; }
+
 	void bind(MVKCommandEncoder* cmdEncoder,
 			  VkDescriptorType descriptorType,
 			  uint32_t descriptorIndex,
@@ -404,6 +492,10 @@
 
 	void setLayout(MVKDescriptorSetLayoutBinding* dslBinding, uint32_t index) override;
 
+	void reset() override;
+
+	~MVKCombinedImageSamplerDescriptor() { reset(); }
+
 };
 
 
@@ -436,8 +528,28 @@
 			  VkBufferView* pTexelBufferView,
 			  VkWriteDescriptorSetInlineUniformBlockEXT* inlineUniformBlock) override;
 
-	~MVKTexelBufferDescriptor();
+	void reset() override;
+
+	~MVKTexelBufferDescriptor() { reset(); }
 
 protected:
 	MVKBufferView* _mvkBufferView = nullptr;
 };
+
+
+#pragma mark -
+#pragma mark MVKUniformTexelBufferDescriptor
+
+class MVKUniformTexelBufferDescriptor : public MVKTexelBufferDescriptor {
+public:
+	VkDescriptorType getDescriptorType() override { return VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; }
+};
+
+
+#pragma mark -
+#pragma mark MVKStorageTexelBufferDescriptor
+
+class MVKStorageTexelBufferDescriptor : public MVKTexelBufferDescriptor {
+public:
+	VkDescriptorType getDescriptorType() override { return VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER; }
+};
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.mm
index 17f3445..74d0b4a 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.mm
@@ -20,9 +20,6 @@
 #include "MVKDescriptorSet.h"
 #include "MVKBuffer.h"
 
-using namespace std;
-using namespace mvk;
-
 
 #pragma mark MVKShaderStageResourceBinding
 
@@ -45,15 +42,15 @@
 #pragma mark MVKShaderResourceBinding
 
 uint32_t MVKShaderResourceBinding::getMaxBufferIndex() {
-	return max({stages[kMVKShaderStageVertex].bufferIndex, stages[kMVKShaderStageTessCtl].bufferIndex, stages[kMVKShaderStageTessEval].bufferIndex, stages[kMVKShaderStageFragment].bufferIndex, stages[kMVKShaderStageCompute].bufferIndex});
+	return std::max({stages[kMVKShaderStageVertex].bufferIndex, stages[kMVKShaderStageTessCtl].bufferIndex, stages[kMVKShaderStageTessEval].bufferIndex, stages[kMVKShaderStageFragment].bufferIndex, stages[kMVKShaderStageCompute].bufferIndex});
 }
 
 uint32_t MVKShaderResourceBinding::getMaxTextureIndex() {
-	return max({stages[kMVKShaderStageVertex].textureIndex, stages[kMVKShaderStageTessCtl].textureIndex, stages[kMVKShaderStageTessEval].textureIndex, stages[kMVKShaderStageFragment].textureIndex, stages[kMVKShaderStageCompute].textureIndex});
+	return std::max({stages[kMVKShaderStageVertex].textureIndex, stages[kMVKShaderStageTessCtl].textureIndex, stages[kMVKShaderStageTessEval].textureIndex, stages[kMVKShaderStageFragment].textureIndex, stages[kMVKShaderStageCompute].textureIndex});
 }
 
 uint32_t MVKShaderResourceBinding::getMaxSamplerIndex() {
-	return max({stages[kMVKShaderStageVertex].samplerIndex, stages[kMVKShaderStageTessCtl].samplerIndex, stages[kMVKShaderStageTessEval].samplerIndex, stages[kMVKShaderStageFragment].samplerIndex, stages[kMVKShaderStageCompute].samplerIndex});
+	return std::max({stages[kMVKShaderStageVertex].samplerIndex, stages[kMVKShaderStageTessCtl].samplerIndex, stages[kMVKShaderStageTessEval].samplerIndex, stages[kMVKShaderStageFragment].samplerIndex, stages[kMVKShaderStageCompute].samplerIndex});
 }
 
 MVKShaderResourceBinding MVKShaderResourceBinding::operator+ (const MVKShaderResourceBinding& rhs) {
@@ -303,7 +300,7 @@
 	return true;
 }
 
-void MVKDescriptorSetLayoutBinding::populateShaderConverterContext(SPIRVToMSLConversionConfiguration& context,
+void MVKDescriptorSetLayoutBinding::populateShaderConverterContext(mvk::SPIRVToMSLConversionConfiguration& context,
                                                                    MVKShaderResourceBinding& dslMTLRezIdxOffsets,
                                                                    uint32_t dslIndex) {
 
@@ -534,16 +531,20 @@
 	}
 }
 
-MVKBufferDescriptor::~MVKBufferDescriptor() {
+void MVKBufferDescriptor::reset() {
 	if (_mvkBuffer) { _mvkBuffer->release(); }
+	_mvkBuffer = nullptr;
+	_buffOffset = 0;
+	_buffRange = 0;
+	MVKDescriptor::reset();
 }
 
 
 #pragma mark -
-#pragma mark MVKInlineUniformDescriptor
+#pragma mark MVKInlineUniformBlockDescriptor
 
 // A null cmdEncoder can be passed to perform a validation pass
-void MVKInlineUniformDescriptor::bind(MVKCommandEncoder* cmdEncoder,
+void MVKInlineUniformBlockDescriptor::bind(MVKCommandEncoder* cmdEncoder,
 									  VkDescriptorType descriptorType,
 									  uint32_t descriptorIndex,
 									  bool stages[],
@@ -574,7 +575,7 @@
 	}
 }
 
-void MVKInlineUniformDescriptor::write(MVKDescriptorSet* mvkDescSet,
+void MVKInlineUniformBlockDescriptor::write(MVKDescriptorSet* mvkDescSet,
 									   VkDescriptorType descriptorType,
 									   uint32_t srcIndex,
 									   size_t stride,
@@ -602,7 +603,7 @@
 	}
 }
 
-void MVKInlineUniformDescriptor::read(MVKDescriptorSet* mvkDescSet,
+void MVKInlineUniformBlockDescriptor::read(MVKDescriptorSet* mvkDescSet,
 									  VkDescriptorType descriptorType,
 									  uint32_t dstIndex,
 									  VkDescriptorImageInfo* pImageInfo,
@@ -628,8 +629,11 @@
 	}
 }
 
-MVKInlineUniformDescriptor::~MVKInlineUniformDescriptor() {
+void MVKInlineUniformBlockDescriptor::reset() {
 	[_mtlBuffer release];
+	_mtlBuffer = nil;
+	_dataSize = 0;
+	MVKDescriptor::reset();
 }
 
 
@@ -726,8 +730,11 @@
 	}
 }
 
-MVKImageDescriptor::~MVKImageDescriptor() {
+void MVKImageDescriptor::reset() {
 	if (_mvkImageView) { _mvkImageView->release(); }
+	_mvkImageView = nullptr;
+	_imageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+	MVKDescriptor::reset();
 }
 
 
@@ -845,8 +852,10 @@
 	if (oldSamp) { oldSamp->release(); }
 }
 
-MVKSamplerDescriptorMixin::~MVKSamplerDescriptorMixin() {
+void MVKSamplerDescriptorMixin::reset() {
 	if (_mvkSampler) { _mvkSampler->release(); }
+	_mvkSampler = nullptr;
+	_hasDynamicSampler = true;
 }
 
 
@@ -913,6 +922,11 @@
 	MVKSamplerDescriptorMixin::setLayout(dslBinding, index);
 }
 
+void MVKSamplerDescriptor::reset() {
+	MVKSamplerDescriptorMixin::reset();
+	MVKDescriptor::reset();
+}
+
 
 #pragma mark -
 #pragma mark MVKCombinedImageSamplerDescriptor
@@ -982,6 +996,11 @@
 	MVKSamplerDescriptorMixin::setLayout(dslBinding, index);
 }
 
+void MVKCombinedImageSamplerDescriptor::reset() {
+	MVKSamplerDescriptorMixin::reset();
+	MVKImageDescriptor::reset();
+}
+
 
 #pragma mark -
 #pragma mark MVKTexelBufferDescriptor
@@ -1062,6 +1081,8 @@
 	}
 }
 
-MVKTexelBufferDescriptor::~MVKTexelBufferDescriptor() {
+void MVKTexelBufferDescriptor::reset() {
 	if (_mvkBufferView) { _mvkBufferView->release(); }
+	_mvkBufferView = nullptr;
+	MVKDescriptor::reset();
 }
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
index ea8c4f4..212fc63 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
@@ -142,9 +142,70 @@
 
 
 #pragma mark -
-#pragma mark MVKDescriptorPool
+#pragma mark MVKDescriptorTypePreallocation
 
-typedef MVKDeviceObjectPool<MVKDescriptorSet> MVKDescriptorSetPool;
+/** Support class for MVKDescriptorPool that holds preallocated instances of a single concrete descriptor class. */
+template<class DescriptorClass>
+class MVKDescriptorTypePreallocation : public MVKBaseObject {
+
+public:
+
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override { return nullptr; };
+
+	MVKDescriptorTypePreallocation(const VkDescriptorPoolCreateInfo* pCreateInfo,
+								   VkDescriptorType descriptorType);
+
+protected:
+	friend class MVKPreallocatedDescriptors;
+
+	VkResult allocateDescriptor(MVKDescriptor** pMVKDesc);
+	bool findDescriptor(uint32_t endIndex, MVKDescriptor** pMVKDesc);
+	void freeDescriptor(MVKDescriptor* mvkDesc);
+
+	std::vector<DescriptorClass> _descriptors;
+	std::vector<bool> _availability;
+	uint32_t _nextAvailableIndex;
+	bool _supportAvailability;
+};
+
+
+#pragma mark -
+#pragma mark MVKPreallocatedDescriptors
+
+/** Support class for MVKDescriptorPool that holds preallocated instances of all concrete descriptor classes. */
+class MVKPreallocatedDescriptors : public MVKBaseObject {
+
+public:
+
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override { return nullptr; };
+
+	MVKPreallocatedDescriptors(const VkDescriptorPoolCreateInfo* pCreateInfo);
+
+protected:
+	friend class MVKDescriptorPool;
+
+	VkResult allocateDescriptor(VkDescriptorType descriptorType, MVKDescriptor** pMVKDesc);
+	void freeDescriptor(MVKDescriptor* mvkDesc);
+
+	MVKDescriptorTypePreallocation<MVKUniformBufferDescriptor> _uniformBufferDescriptors;
+	MVKDescriptorTypePreallocation<MVKStorageBufferDescriptor> _storageBufferDescriptors;
+	MVKDescriptorTypePreallocation<MVKUniformBufferDynamicDescriptor> _uniformBufferDynamicDescriptors;
+	MVKDescriptorTypePreallocation<MVKStorageBufferDynamicDescriptor> _storageBufferDynamicDescriptors;
+	MVKDescriptorTypePreallocation<MVKInlineUniformBlockDescriptor> _inlineUniformBlockDescriptors;
+	MVKDescriptorTypePreallocation<MVKSampledImageDescriptor> _sampledImageDescriptors;
+	MVKDescriptorTypePreallocation<MVKStorageImageDescriptor> _storageImageDescriptors;
+	MVKDescriptorTypePreallocation<MVKInputAttachmentDescriptor> _inputAttachmentDescriptors;
+	MVKDescriptorTypePreallocation<MVKSamplerDescriptor> _samplerDescriptors;
+	MVKDescriptorTypePreallocation<MVKCombinedImageSamplerDescriptor> _combinedImageSamplerDescriptors;
+	MVKDescriptorTypePreallocation<MVKUniformTexelBufferDescriptor> _uniformTexelBufferDescriptors;
+	MVKDescriptorTypePreallocation<MVKStorageTexelBufferDescriptor> _storageTexelBufferDescriptors;
+};
+
+
+#pragma mark -
+#pragma mark MVKDescriptorPool
 
 /** Represents a Vulkan descriptor pool. */
 class MVKDescriptorPool : public MVKVulkanAPIDeviceObject {
@@ -183,6 +244,7 @@
 
 	uint32_t _maxSets;
 	std::unordered_set<MVKDescriptorSet*> _allocatedSets;
+	MVKPreallocatedDescriptors* _preallocatedDescriptors;
 };
 
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
index a13b689..cfecbfd 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
@@ -17,8 +17,7 @@
  */
 
 #include "MVKDescriptorSet.h"
-
-using namespace mvk;
+#include "MVKOSExtensions.h"
 
 
 #pragma mark -
@@ -174,7 +173,7 @@
     }
 }
 
-void MVKDescriptorSetLayout::populateShaderConverterContext(SPIRVToMSLConversionConfiguration& context,
+void MVKDescriptorSetLayout::populateShaderConverterContext(mvk::SPIRVToMSLConversionConfiguration& context,
                                                             MVKShaderResourceBinding& dslMTLRezIdxOffsets,
 															uint32_t dslIndex) {
 	uint32_t bindCnt = (uint32_t)_bindings.size();
@@ -270,6 +269,220 @@
 
 
 #pragma mark -
+#pragma mark MVKDescriptorTypePreallocation
+
+#ifndef MVK_CONFIG_PREALLOCATE_DESCRIPTORS
+#   define MVK_CONFIG_PREALLOCATE_DESCRIPTORS    0
+#endif
+
+static bool _mvkPreallocateDescriptors = MVK_CONFIG_PREALLOCATE_DESCRIPTORS;
+static bool _mvkPreallocateDescriptorsInitialized = false;
+
+// Returns whether descriptors should be preallocated in the descriptor pools
+// We do this once lazily instead of in a library constructor function to
+// ensure the NSProcessInfo environment is available when called upon.
+static inline bool getMVKPreallocateDescriptors() {
+	if ( !_mvkPreallocateDescriptorsInitialized ) {
+		_mvkPreallocateDescriptorsInitialized = true;
+		MVK_SET_FROM_ENV_OR_BUILD_BOOL(_mvkPreallocateDescriptors, MVK_CONFIG_PREALLOCATE_DESCRIPTORS);
+	}
+	return _mvkPreallocateDescriptors;
+}
+
+template<class DescriptorClass>
+VkResult MVKDescriptorTypePreallocation<DescriptorClass>::allocateDescriptor(MVKDescriptor** pMVKDesc) {
+
+	uint32_t descCnt = (uint32_t)_descriptors.size();
+
+	// Preallocated descriptors that CANNOT be freed.
+	// Next available index can only monotonically increase towards the limit.
+	if ( !_supportAvailability ) {
+		if (_nextAvailableIndex < descCnt) {
+			*pMVKDesc = &_descriptors[_nextAvailableIndex++];
+			return VK_SUCCESS;
+		} else {
+			return VK_ERROR_OUT_OF_POOL_MEMORY;
+		}
+	}
+
+	// Descriptors that CAN be freed.
+	// An available index might exist anywhere in the pool of descriptors.
+	uint32_t origNextAvailPoolIdx = _nextAvailableIndex;
+
+	// First start looking from most recently found available slot
+	if (findDescriptor(descCnt, pMVKDesc)) { return VK_SUCCESS; }
+
+	// Then look from beginning of the collection, in case any previous descriptors were freed
+	_nextAvailableIndex = 0;
+	if (findDescriptor(origNextAvailPoolIdx, pMVKDesc)) { return VK_SUCCESS; }
+
+	return VK_ERROR_OUT_OF_POOL_MEMORY;
+}
+
+// Find a descriptor within a range in a preallocated collection based on availability,
+// and return true if found, false if not
+template<typename DescriptorClass>
+bool MVKDescriptorTypePreallocation<DescriptorClass>::findDescriptor(uint32_t endIndex,
+																	 MVKDescriptor** pMVKDesc) {
+	while (_nextAvailableIndex < endIndex) {
+		if (_availability[_nextAvailableIndex]) {
+			_availability[_nextAvailableIndex] = false;
+			*pMVKDesc = &_descriptors[_nextAvailableIndex];
+			_nextAvailableIndex++;
+			return true;
+		}
+		_nextAvailableIndex++;
+	}
+	return false;
+}
+
+// Reset a descriptor and mark it available, if applicable
+template<typename DescriptorClass>
+void MVKDescriptorTypePreallocation<DescriptorClass>::freeDescriptor(MVKDescriptor* mvkDesc) {
+
+	mvkDesc->reset();
+
+	if (_supportAvailability) {
+		bool found = false;
+		size_t descCnt = _descriptors.size();
+		for (uint32_t descIdx = 0; !found && descIdx < descCnt; descIdx++) {
+			if (&_descriptors[descIdx] == mvkDesc) {
+				found = true;
+				_availability[descIdx] = true;
+			}
+		}
+	}
+}
+
+template<typename DescriptorClass>
+MVKDescriptorTypePreallocation<DescriptorClass>::MVKDescriptorTypePreallocation(const VkDescriptorPoolCreateInfo* pCreateInfo,
+																				VkDescriptorType descriptorType) {
+	// There may be more than  one poolSizeCount instance for the desired VkDescriptorType.
+	// Accumulate the descriptor count for the desired VkDescriptorType, and size the collections accordingly.
+	uint32_t descriptorCount = 0;
+	uint32_t poolCnt = pCreateInfo->poolSizeCount;
+	for (uint32_t poolIdx = 0; poolIdx < poolCnt; poolIdx++) {
+		auto& poolSize = pCreateInfo->pPoolSizes[poolIdx];
+		if (poolSize.type == descriptorType) { descriptorCount += poolSize.descriptorCount; }
+	}
+	_descriptors.resize(descriptorCount);
+
+	// Determine whether we need to track the availability of previously freed descriptors.
+	_supportAvailability = mvkIsAnyFlagEnabled(pCreateInfo->flags, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT);
+	if (_supportAvailability) { _availability.resize(descriptorCount, true); }
+	_nextAvailableIndex = 0;
+}
+
+
+#pragma mark -
+#pragma mark MVKPreallocatedDescriptors
+
+// Allocate a descriptor of the specified type
+VkResult MVKPreallocatedDescriptors::allocateDescriptor(VkDescriptorType descriptorType,
+														MVKDescriptor** pMVKDesc) {
+	switch (descriptorType) {
+		case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
+			return _uniformBufferDescriptors.allocateDescriptor(pMVKDesc);
+
+		case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
+			return _storageBufferDescriptors.allocateDescriptor(pMVKDesc);
+
+		case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
+			return _uniformBufferDynamicDescriptors.allocateDescriptor(pMVKDesc);
+
+		case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
+			return _storageBufferDynamicDescriptors.allocateDescriptor(pMVKDesc);
+
+		case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT:
+			return _inlineUniformBlockDescriptors.allocateDescriptor(pMVKDesc);
+
+		case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
+			return _sampledImageDescriptors.allocateDescriptor(pMVKDesc);
+
+		case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
+			return _storageImageDescriptors.allocateDescriptor(pMVKDesc);
+
+		case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
+			return _inputAttachmentDescriptors.allocateDescriptor(pMVKDesc);
+
+		case VK_DESCRIPTOR_TYPE_SAMPLER:
+			return _samplerDescriptors.allocateDescriptor(pMVKDesc);
+
+		case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
+			return _combinedImageSamplerDescriptors.allocateDescriptor(pMVKDesc);
+
+		case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
+			return _uniformTexelBufferDescriptors.allocateDescriptor(pMVKDesc);
+
+		case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
+			return _storageTexelBufferDescriptors.allocateDescriptor(pMVKDesc);
+
+		default:
+			return reportError(VK_ERROR_INITIALIZATION_FAILED, "Unrecognized VkDescriptorType %d.", descriptorType);
+	}
+}
+
+void MVKPreallocatedDescriptors::freeDescriptor(MVKDescriptor* mvkDesc) {
+	VkDescriptorType descriptorType = mvkDesc->getDescriptorType();
+	switch (descriptorType) {
+		case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
+			return _uniformBufferDescriptors.freeDescriptor(mvkDesc);
+
+		case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
+			return _storageBufferDescriptors.freeDescriptor(mvkDesc);
+
+		case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
+			return _uniformBufferDynamicDescriptors.freeDescriptor(mvkDesc);
+
+		case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
+			return _storageBufferDynamicDescriptors.freeDescriptor(mvkDesc);
+
+		case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT:
+			return _inlineUniformBlockDescriptors.freeDescriptor(mvkDesc);
+
+		case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
+			return _sampledImageDescriptors.freeDescriptor(mvkDesc);
+
+		case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
+			return _storageImageDescriptors.freeDescriptor(mvkDesc);
+
+		case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
+			return _inputAttachmentDescriptors.freeDescriptor(mvkDesc);
+
+		case VK_DESCRIPTOR_TYPE_SAMPLER:
+			return _samplerDescriptors.freeDescriptor(mvkDesc);
+
+		case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
+			return _combinedImageSamplerDescriptors.freeDescriptor(mvkDesc);
+
+		case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
+			return _uniformTexelBufferDescriptors.freeDescriptor(mvkDesc);
+
+		case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
+			return _storageTexelBufferDescriptors.freeDescriptor(mvkDesc);
+
+		default:
+			reportError(VK_ERROR_INITIALIZATION_FAILED, "Unrecognized VkDescriptorType %d.", descriptorType);
+	}
+}
+
+MVKPreallocatedDescriptors::MVKPreallocatedDescriptors(const VkDescriptorPoolCreateInfo* pCreateInfo) :
+	_uniformBufferDescriptors(pCreateInfo, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER),
+	_storageBufferDescriptors(pCreateInfo, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER),
+	_uniformBufferDynamicDescriptors(pCreateInfo, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC),
+	_storageBufferDynamicDescriptors(pCreateInfo, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC),
+	_inlineUniformBlockDescriptors(pCreateInfo, VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT),
+	_sampledImageDescriptors(pCreateInfo, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE),
+	_storageImageDescriptors(pCreateInfo, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE),
+	_inputAttachmentDescriptors(pCreateInfo, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT),
+	_samplerDescriptors(pCreateInfo, VK_DESCRIPTOR_TYPE_SAMPLER),
+	_combinedImageSamplerDescriptors(pCreateInfo, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER),
+	_uniformTexelBufferDescriptors(pCreateInfo, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER),
+	_storageTexelBufferDescriptors(pCreateInfo, VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER) {
+}
+
+
+#pragma mark -
 #pragma mark MVKDescriptorPool
 
 VkResult MVKDescriptorPool::allocateDescriptorSets(uint32_t count,
@@ -327,54 +540,90 @@
 
 void MVKDescriptorPool::freeDescriptorSet(MVKDescriptorSet* mvkDS) { mvkDS->destroy(); }
 
+// Allocate a descriptor of the specified type
 VkResult MVKDescriptorPool::allocateDescriptor(VkDescriptorType descriptorType,
 											   MVKDescriptor** pMVKDesc) {
+
+	// If descriptors are preallocated allocate from the preallocated pools
+	if (_preallocatedDescriptors) {
+		return _preallocatedDescriptors->allocateDescriptor(descriptorType, pMVKDesc);
+	}
+
+	// Otherwise instantiate one of the apporpriate type now
 	switch (descriptorType) {
-		case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
-		case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
 		case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
+			*pMVKDesc = new MVKUniformBufferDescriptor();
+			break;
+
 		case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
-			*pMVKDesc = new MVKBufferDescriptor();
-			return VK_SUCCESS;
+			*pMVKDesc = new MVKStorageBufferDescriptor();
+			break;
+
+		case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
+			*pMVKDesc = new MVKUniformBufferDynamicDescriptor();
+			break;
+
+		case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
+			*pMVKDesc = new MVKStorageBufferDynamicDescriptor();
+			break;
 
 		case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT:
-			*pMVKDesc = new MVKInlineUniformDescriptor();
-			return VK_SUCCESS;
+			*pMVKDesc = new MVKInlineUniformBlockDescriptor();
+			break;
 
 		case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
+			*pMVKDesc = new MVKSampledImageDescriptor();
+			break;
+
 		case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
+			*pMVKDesc = new MVKStorageImageDescriptor();
+			break;
+
 		case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
-			*pMVKDesc = new MVKImageDescriptor();
-			return VK_SUCCESS;
+			*pMVKDesc = new MVKInputAttachmentDescriptor();
+			break;
 
 		case VK_DESCRIPTOR_TYPE_SAMPLER:
 			*pMVKDesc = new MVKSamplerDescriptor();
-			return VK_SUCCESS;
+			break;
 
 		case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
 			*pMVKDesc = new MVKCombinedImageSamplerDescriptor();
-			return VK_SUCCESS;
+			break;
 
 		case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
+			*pMVKDesc = new MVKUniformTexelBufferDescriptor();
+			break;
+
 		case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
-			*pMVKDesc = new MVKTexelBufferDescriptor();
-			return VK_SUCCESS;
+			*pMVKDesc = new MVKStorageTexelBufferDescriptor();
+			break;
 
 		default:
 			return reportError(VK_ERROR_INITIALIZATION_FAILED, "Unrecognized VkDescriptorType %d.", descriptorType);
 	}
+	return VK_SUCCESS;
 }
 
-void MVKDescriptorPool::freeDescriptor(MVKDescriptor* mvkDesc) { mvkDesc->destroy(); }
+// Free a descriptor, either through the preallocated pool, or directly destroy it
+void MVKDescriptorPool::freeDescriptor(MVKDescriptor* mvkDesc) {
+	if (_preallocatedDescriptors) {
+		_preallocatedDescriptors->freeDescriptor(mvkDesc);
+	} else {
+		mvkDesc->destroy();
+	}
+}
 
 MVKDescriptorPool::MVKDescriptorPool(MVKDevice* device,
 									 const VkDescriptorPoolCreateInfo* pCreateInfo) : MVKVulkanAPIDeviceObject(device) {
 	_maxSets = pCreateInfo->maxSets;
+	_preallocatedDescriptors = getMVKPreallocateDescriptors() ? new MVKPreallocatedDescriptors(pCreateInfo) : nullptr;
 }
 
-// Destroy all allocated descriptor sets
+// Destroy all allocated descriptor sets and preallocated descriptors
 MVKDescriptorPool::~MVKDescriptorPool() {
 	reset(0);
+	if (_preallocatedDescriptors) { _preallocatedDescriptors->destroy(); }
 }
 
 
@@ -478,13 +727,13 @@
 	}
 }
 
-void mvkPopulateShaderConverterContext(SPIRVToMSLConversionConfiguration& context,
+void mvkPopulateShaderConverterContext(mvk::SPIRVToMSLConversionConfiguration& context,
 									   MVKShaderStageResourceBinding& ssRB,
 									   spv::ExecutionModel stage,
 									   uint32_t descriptorSetIndex,
 									   uint32_t bindingIndex,
 									   MVKSampler* immutableSampler) {
-	MSLResourceBinding rb;
+	mvk::MSLResourceBinding rb;
 
 	auto& rbb = rb.resourceBinding;
 	rbb.stage = stage;