Several underlying non-functional changes towards Metal argument buffer support.

Move tracking descriptor index to MVKDescriptorSetLayoutBinding from MVKDescriptorSetLayout.
Refactor MVKDescriptorSet::write() to be more DRY.
Grow and shrink MVKDescriptorSet memory consumption during allocate and free.
MVKPhysicalDevice refactor layout of pipelineCacheUUID to remove redundancy in MoltenVK
revision and allow tracking of enabled features that affect pipeline cache content.
MVKBitArray support resizing and simplify setBit() functionality.
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.h b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.h
index 6aa2137..21f1808 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.h
@@ -108,25 +108,29 @@
               const void* pData,
               MVKShaderResourceBinding& dslMTLRezIdxOffsets);
 
-	/** Populates the specified shader converter context, at the specified descriptor set binding. */
-	void populateShaderConverterContext(mvk::SPIRVToMSLConversionConfiguration& context,
-                                        MVKShaderResourceBinding& dslMTLRezIdxOffsets,
-                                        uint32_t dslIndex);
+	/** Returns the index of the descriptor within the descriptor set of the element at the index within this descriptor layout. */
+	inline uint32_t getDescriptorIndex(uint32_t elementIndex = 0) { return _descriptorIndex + elementIndex; }
 
 	MVKDescriptorSetLayoutBinding(MVKDevice* device,
 								  MVKDescriptorSetLayout* layout,
 								  const VkDescriptorSetLayoutBinding* pBinding,
-								  VkDescriptorBindingFlagsEXT bindingFlags);
+								  VkDescriptorBindingFlagsEXT bindingFlags,
+								  uint32_t descriptorIndex);
 
 	MVKDescriptorSetLayoutBinding(const MVKDescriptorSetLayoutBinding& binding);
 
 	~MVKDescriptorSetLayoutBinding() override;
 
 protected:
+	friend class MVKDescriptorSetLayout;
     friend class MVKInlineUniformBlockDescriptor;
+	
 	void initMetalResourceIndexOffsets(MVKShaderStageResourceBinding* pBindingIndexes,
 									   MVKShaderStageResourceBinding* pDescSetCounts,
 									   const VkDescriptorSetLayoutBinding* pBinding);
+	void populateShaderConverterContext(mvk::SPIRVToMSLConversionConfiguration& context,
+										MVKShaderResourceBinding& dslMTLRezIdxOffsets,
+										uint32_t dslIndex);
 	bool validate(MVKSampler* mvkSampler);
 
 	MVKDescriptorSetLayout* _layout;
@@ -134,6 +138,7 @@
 	VkDescriptorBindingFlagsEXT _flags;
 	MVKSmallVector<MVKSampler*> _immutableSamplers;
 	MVKShaderResourceBinding _mtlResourceIndexOffsets;
+	uint32_t _descriptorIndex;
 	bool _applyToStage[kMVKShaderStageMax];
 };
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.mm
index 48ce529..725752d 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.mm
@@ -376,11 +376,13 @@
 MVKDescriptorSetLayoutBinding::MVKDescriptorSetLayoutBinding(MVKDevice* device,
 															 MVKDescriptorSetLayout* layout,
 															 const VkDescriptorSetLayoutBinding* pBinding,
-															 VkDescriptorBindingFlagsEXT bindingFlags) :
+															 VkDescriptorBindingFlagsEXT bindingFlags,
+															 uint32_t descriptorIndex) :
 	MVKBaseDeviceObject(device),
 	_layout(layout),
 	_info(*pBinding),
-	_flags(bindingFlags) {
+	_flags(bindingFlags),
+	_descriptorIndex(descriptorIndex) {
 
 	_info.pImmutableSamplers = nullptr;     // Remove dangling pointer
 
@@ -412,6 +414,7 @@
 	_layout(binding._layout),
 	_info(binding._info),
 	_flags(binding._flags),
+	_descriptorIndex(binding._descriptorIndex),
 	_immutableSamplers(binding._immutableSamplers),
 	_mtlResourceIndexOffsets(binding._mtlResourceIndexOffsets) {
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
index 8d1e931..4d69c35 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
@@ -83,13 +83,12 @@
 
 	void propagateDebugName() override {}
 	inline uint32_t getDescriptorCount() { return _descriptorCount; }
-	inline uint32_t getDescriptorIndex(uint32_t binding, uint32_t elementIndex = 0) { return _bindingToDescriptorIndex[binding] + elementIndex; }
+	inline uint32_t getDescriptorIndex(uint32_t binding, uint32_t elementIndex = 0) { return getBinding(binding)->getDescriptorIndex(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;
 	bool _isPushDescriptorLayout;
@@ -138,8 +137,8 @@
 	VkResult allocate(MVKDescriptorSetLayout* layout, uint32_t variableDescriptorCount);
 	void free(bool isPoolReset);
 
-	MVKDescriptorSetLayout* _layout;
 	MVKDescriptorPool* _pool;
+	MVKDescriptorSetLayout* _layout;
 	MVKSmallVector<MVKDescriptor*> _descriptors;
 	uint32_t _variableDescriptorCount;
 };
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
index aeeb58f..7e233f1 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
@@ -195,9 +195,8 @@
     _bindings.reserve(bindCnt);
     for (uint32_t bindIdx = 0; bindIdx < bindCnt; bindIdx++) {
 		BindInfo& bindInfo = sortedBindings[bindIdx];
-        _bindings.emplace_back(_device, this, bindInfo.pBinding, bindInfo.bindingFlags);
+        _bindings.emplace_back(_device, this, bindInfo.pBinding, bindInfo.bindingFlags, _descriptorCount);
 		_bindingToIndex[bindInfo.pBinding->binding] = bindIdx;
-		_bindingToDescriptorIndex[bindInfo.pBinding->binding] = _descriptorCount;
 		_descriptorCount += _bindings.back().getDescriptorCount(nullptr);
 	}
 }
@@ -234,25 +233,28 @@
 void MVKDescriptorSet::write(const DescriptorAction* pDescriptorAction,
 							 size_t stride,
 							 const void* pData) {
+#define writeDescriptorAt(IDX)                                    \
+	do {                                                          \
+		MVKDescriptor* mvkDesc = _descriptors[descIdx];           \
+		if (mvkDesc->getDescriptorType() == descType) {           \
+			mvkDesc->write(mvkDSLBind, this, IDX, stride, pData); \
+		}                                                         \
+	} while(false)
 
 	MVKDescriptorSetLayoutBinding* mvkDSLBind = _layout->getBinding(pDescriptorAction->dstBinding);
 	VkDescriptorType descType = mvkDSLBind->getDescriptorType();
-    if (descType == VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT) {
+	if (descType == VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT) {
 		// For inline buffers dstArrayElement is a byte offset
-		MVKDescriptor* mvkDesc = getDescriptor(pDescriptorAction->dstBinding);
-		if (mvkDesc->getDescriptorType() == descType) {
-			mvkDesc->write(mvkDSLBind, this, pDescriptorAction->dstArrayElement, stride, pData);
+		uint32_t descIdx = _layout->getDescriptorIndex(pDescriptorAction->dstBinding);
+		writeDescriptorAt(pDescriptorAction->dstArrayElement);
+	} else {
+		uint32_t descStartIdx = _layout->getDescriptorIndex(pDescriptorAction->dstBinding, pDescriptorAction->dstArrayElement);
+		uint32_t elemCnt = pDescriptorAction->descriptorCount;
+		for (uint32_t elemIdx = 0; elemIdx < elemCnt; elemIdx++) {
+			uint32_t descIdx = descStartIdx + elemIdx;
+			writeDescriptorAt(elemIdx);
 		}
-    } else {
-        uint32_t dstStartIdx = _layout->getDescriptorIndex(pDescriptorAction->dstBinding, pDescriptorAction->dstArrayElement);
-		uint32_t descCnt = pDescriptorAction->descriptorCount;
-        for (uint32_t descIdx = 0; descIdx < descCnt; descIdx++) {
-			MVKDescriptor* mvkDesc = _descriptors[dstStartIdx + descIdx];
-			if (mvkDesc->getDescriptorType() == descType) {
-				mvkDesc->write(mvkDSLBind, this, descIdx, stride, pData);
-			}
-        }
-    }
+	}
 }
 
 void MVKDescriptorSet::read(const VkCopyDescriptorSet* pDescriptorCopy,
@@ -285,12 +287,14 @@
 	_layout = layout;
 	_variableDescriptorCount = variableDescriptorCount;
 
-	_descriptors.reserve(layout->getDescriptorCount());
+	uint32_t descCnt = layout->getDescriptorCount();
+	_descriptors.reserve(descCnt);
+
 	uint32_t bindCnt = (uint32_t)layout->_bindings.size();
 	for (uint32_t bindIdx = 0; bindIdx < bindCnt; bindIdx++) {
 		MVKDescriptorSetLayoutBinding* mvkDSLBind = &layout->_bindings[bindIdx];
-		uint32_t descCnt = mvkDSLBind->getDescriptorCount(this);
-		for (uint32_t descIdx = 0; descIdx < descCnt; descIdx++) {
+		uint32_t elemCnt = mvkDSLBind->getDescriptorCount(this);
+		for (uint32_t elemIdx = 0; elemIdx < elemCnt; elemIdx++) {
 			MVKDescriptor* mvkDesc = nullptr;
 			setConfigurationResult(_pool->allocateDescriptor(mvkDSLBind->getDescriptorType(), &mvkDesc));
 			if ( !wasConfigurationSuccessful() ) { return getConfigurationResult(); }
@@ -309,6 +313,7 @@
 		for (auto mvkDesc : _descriptors) { _pool->freeDescriptor(mvkDesc); }
 	}
 	_descriptors.clear();
+	_descriptors.shrink_to_fit();
 
 	clearConfigurationResult();
 }
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
index c65ffae..b8b6dd3 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
@@ -369,7 +369,7 @@
 	MVKArrayRef<MVKQueueFamily*> getQueueFamilies();
 	void initPipelineCacheUUID();
 	uint32_t getHighestMTLFeatureSet();
-	uint64_t getMoltenVKGitRevision();
+	uint32_t getMoltenVKGitRevision();
 	void populate(VkPhysicalDeviceIDProperties* pDevIdProps);
 	void logGPUInfo();
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
index 60d4e46..d304fa0 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
@@ -2362,20 +2362,23 @@
 
 	size_t uuidComponentOffset = 0;
 
-	// First 4 bytes contains MoltenVK version
-	uint32_t mvkVersion = MVK_VERSION;
-	*(uint32_t*)&_properties.pipelineCacheUUID[uuidComponentOffset] = NSSwapHostIntToBig(mvkVersion);
-	uuidComponentOffset += sizeof(mvkVersion);
+	// First 4 bytes contains MoltenVK revision.
+	// This is captured either as the MoltenVK Git revision, or if that's not available, as the MoltenVK version.
+	uint32_t mvkRev = getMoltenVKGitRevision();
+	if ( !mvkRev ) { mvkRev = MVK_VERSION; }
+	*(uint32_t*)&_properties.pipelineCacheUUID[uuidComponentOffset] = NSSwapHostIntToBig(mvkRev);
+	uuidComponentOffset += sizeof(mvkRev);
 
 	// Next 4 bytes contains highest Metal feature set supported by this device
 	uint32_t mtlFeatSet = getHighestMTLFeatureSet();
 	*(uint32_t*)&_properties.pipelineCacheUUID[uuidComponentOffset] = NSSwapHostIntToBig(mtlFeatSet);
 	uuidComponentOffset += sizeof(mtlFeatSet);
 
-	// Last 8 bytes contain the first part of the MoltenVK Git revision
-	uint64_t mvkRev = getMoltenVKGitRevision();
-	*(uint64_t*)&_properties.pipelineCacheUUID[uuidComponentOffset] = NSSwapHostLongLongToBig(mvkRev);
-	uuidComponentOffset += sizeof(mvkRev);
+	// Next 4 bytes contains flags based on enabled Metal features that
+	// might affect the contents of the pipeline cache (mostly MSL content).
+	uint32_t mtlFeatures = 0;
+	*(uint32_t*)&_properties.pipelineCacheUUID[uuidComponentOffset] = NSSwapHostIntToBig(mtlFeatures);
+	uuidComponentOffset += sizeof(mtlFeatures);
 }
 
 uint32_t MVKPhysicalDevice::getHighestMTLFeatureSet() {
@@ -2436,14 +2439,14 @@
 // which is generated in advance, either statically, or more typically in
 // an early build phase script, and contains a line similar to the following:
 // static const char* mvkRevString = "fc0750d67cfe825b887dd2cf25a42e9d9a013eb2";
-uint64_t MVKPhysicalDevice::getMoltenVKGitRevision() {
+uint32_t MVKPhysicalDevice::getMoltenVKGitRevision() {
 
 #include "mvkGitRevDerived.h"
 
-	static const string revStr(mvkRevString, 0, 16);	// We just need the first 16 chars
+	static const string revStr(mvkRevString, 0, 8);		// We just need the first 8 chars
 	static const string lut("0123456789ABCDEF");
 
-	uint64_t revVal = 0;
+	uint32_t revVal = 0;
 	for (char c : revStr) {
 		size_t cVal = lut.find(toupper(c));
 		if (cVal != string::npos) {
diff --git a/MoltenVK/MoltenVK/Utility/MVKBitArray.h b/MoltenVK/MoltenVK/Utility/MVKBitArray.h
index b6e1ecb..4016547 100755
--- a/MoltenVK/MoltenVK/Utility/MVKBitArray.h
+++ b/MoltenVK/MoltenVK/Utility/MVKBitArray.h
@@ -39,30 +39,20 @@
 		return mvkIsAnyFlagEnabled(_pSections[getIndexOfSection(bitIndex)], getSectionSetMask(bitIndex));

 	}

 

-	/** Sets the value of the bit to 1. */

-	inline void setBit(size_t bitIndex) {

+	/** Sets the value of the bit to the val (or to 1 by default). */

+	inline void setBit(size_t bitIndex, bool val = true) {

 		size_t secIdx = getIndexOfSection(bitIndex);

-		mvkEnableFlags(_pSections[secIdx], getSectionSetMask(bitIndex));

-

-		if (secIdx < _minUnclearedSectionIndex) { _minUnclearedSectionIndex = secIdx; }

+		if (val) {

+			mvkEnableFlags(_pSections[secIdx], getSectionSetMask(bitIndex));

+			if (secIdx < _minUnclearedSectionIndex) { _minUnclearedSectionIndex = secIdx; }

+		} else {

+			mvkDisableFlags(_pSections[secIdx], getSectionSetMask(bitIndex));

+			if (secIdx == _minUnclearedSectionIndex && !_pSections[secIdx]) { _minUnclearedSectionIndex++; }

+		}

 	}

 

 	/** Sets the value of the bit to 0. */

-	inline void clearBit(size_t bitIndex) {

-		size_t secIdx = getIndexOfSection(bitIndex);

-		mvkDisableFlags(_pSections[secIdx], getSectionSetMask(bitIndex));

-

-		if (secIdx == _minUnclearedSectionIndex && !_pSections[secIdx]) { _minUnclearedSectionIndex++; }

-	}

-

-	/** Sets the value of the bit to the value. */

-	inline void setBit(size_t bitIndex, bool val) {

-		if (val) {

-			setBit(bitIndex);

-		} else {

-			clearBit(bitIndex);

-		}

-	}

+	inline void clearBit(size_t bitIndex) { setBit(bitIndex, false); }

 

 	/** Sets all bits in the array to 1. */

 	inline void setAllBits() { setAllSections(~0); }

@@ -120,8 +110,10 @@
 	/** Returns whether this array is empty. */

 	inline bool empty() { return !_bitCount; }

 

-	/** Constructs an instance for the specified number of bits, and sets the initial value of all the bits. */

-	MVKBitArray(size_t size, bool val = false) {

+	/** Resize this array to the specified number of bits, and sets the initial value of all the bits. */

+	inline void resize(size_t size = 0, bool val = false) {

+		free(_pSections);

+

 		_bitCount = size;

 		_pSections = _bitCount ? (uint64_t*)malloc(getSectionCount() * SectionByteCount) : nullptr;

 		if (val) {

@@ -131,6 +123,12 @@
 		}

 	}

 

+	/** Constructs an instance for the specified number of bits, and sets the initial value of all the bits. */

+	MVKBitArray(size_t size = 0, bool val = false) {

+		_pSections = nullptr;

+		resize(size, val);

+	}

+

 	~MVKBitArray() { free(_pSections); }

 

 protected: