Merge pull request #1312 from billhollings/optimize-descriptor-sizes

MVKDescriptor subclasses: Reduce redundant and unused content.
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.h b/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.h
index eb9637c..c2466a7 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.h
@@ -222,7 +222,6 @@
 	MVKSmallVector<VkBufferImageCopy, N> _bufferImageCopyRegions;
     MVKBuffer* _buffer;
     MVKImage* _image;
-    VkImageLayout _imageLayout;
     bool _toImage = false;
 };
 
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm b/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm
index 5bee3c0..df50a7d 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm
@@ -876,7 +876,6 @@
 											  bool toImage) {
     _buffer = (MVKBuffer*)buffer;
     _image = (MVKImage*)image;
-    _imageLayout = imageLayout;
     _toImage = toImage;
 
     // Add buffer regions
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.h b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.h
index 23c599a..6aa2137 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.h
@@ -153,7 +153,8 @@
 
 	/** Encodes this descriptor (based on its layout binding index) on the the command encoder. */
 	virtual void bind(MVKCommandEncoder* cmdEncoder,
-					  uint32_t descriptorIndex,
+					  MVKDescriptorSetLayoutBinding* mvkDSLBind,
+					  uint32_t elementIndex,
 					  bool stages[],
 					  MVKShaderResourceBinding& mtlIndexes,
 					  MVKArrayRef<uint32_t> dynamicOffsets,
@@ -164,7 +165,8 @@
 	 * on the descriptor type, and is extracted from pData at the location given by index * stride.
 	 * MVKInlineUniformBlockDescriptor uses the index as byte offset to write to.
 	 */
-	virtual void write(MVKDescriptorSet* mvkDescSet,
+	virtual void write(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+					   MVKDescriptorSet* mvkDescSet,
 					   uint32_t index,
 					   size_t stride,
 					   const void* pData) = 0;
@@ -180,16 +182,14 @@
 	 * at which to start writing.
 	 * MVKInlineUniformBlockDescriptor uses the index as byte offset to read from.
 	 */
-	virtual void read(MVKDescriptorSet* mvkDescSet,
+	virtual void read(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+					  MVKDescriptorSet* mvkDescSet,
 					  uint32_t index,
 					  VkDescriptorImageInfo* pImageInfo,
 					  VkDescriptorBufferInfo* pBufferInfo,
 					  VkBufferView* pTexelBufferView,
 					  VkWriteDescriptorSetInlineUniformBlockEXT* inlineUniformBlock) = 0;
 
-	/** Sets the binding layout. */
-	virtual void setLayout(MVKDescriptorSetLayoutBinding* dslBinding, uint32_t index) {}
-
 	/** Resets any internal content. */
 	virtual void reset() {}
 
@@ -206,18 +206,21 @@
 
 public:
 	void bind(MVKCommandEncoder* cmdEncoder,
-			  uint32_t descriptorIndex,
+			  MVKDescriptorSetLayoutBinding* mvkDSLBind,
+			  uint32_t elementIndex,
 			  bool stages[],
 			  MVKShaderResourceBinding& mtlIndexes,
 			  MVKArrayRef<uint32_t> dynamicOffsets,
 			  uint32_t& dynamicOffsetIndex) override;
 
-	void write(MVKDescriptorSet* mvkDescSet,
+	void write(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+			   MVKDescriptorSet* mvkDescSet,
 			   uint32_t srcIndex,
 			   size_t stride,
 			   const void* pData) override;
 
-	void read(MVKDescriptorSet* mvkDescSet,
+	void read(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+			  MVKDescriptorSet* mvkDescSet,
 			  uint32_t dstIndex,
 			  VkDescriptorImageInfo* pImageInfo,
 			  VkDescriptorBufferInfo* pBufferInfo,
@@ -281,33 +284,33 @@
 	VkDescriptorType getDescriptorType() override { return VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT; }
 
 	void bind(MVKCommandEncoder* cmdEncoder,
-			  uint32_t descriptorIndex,
+			  MVKDescriptorSetLayoutBinding* mvkDSLBind,
+			  uint32_t elementIndex,
 			  bool stages[],
 			  MVKShaderResourceBinding& mtlIndexes,
 			  MVKArrayRef<uint32_t> dynamicOffsets,
 			  uint32_t& dynamicOffsetIndex) override;
 
-	void write(MVKDescriptorSet* mvkDescSet,
+	void write(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+			   MVKDescriptorSet* mvkDescSet,
 			   uint32_t dstOffset, // For inline buffers we are using this parameter as dst offset not as src descIdx
 			   size_t stride,
 			   const void* pData) override;
 
-	void read(MVKDescriptorSet* mvkDescSet,
+	void read(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+			  MVKDescriptorSet* mvkDescSet,
 			  uint32_t srcOffset, // For inline buffers we are using this parameter as src offset not as dst descIdx
 			  VkDescriptorImageInfo* pImageInfo,
 			  VkDescriptorBufferInfo* pBufferInfo,
 			  VkBufferView* pTexelBufferView,
 			  VkWriteDescriptorSetInlineUniformBlockEXT* inlineUniformBlock) override;
     
-    void setLayout(MVKDescriptorSetLayoutBinding* dslBinding, uint32_t index) override;
-
 	void reset() override;
 
 	~MVKInlineUniformBlockDescriptor() { reset(); }
 
 protected:
 	uint8_t* _buffer = nullptr;
-    uint32_t _length;
 };
 
 
@@ -319,18 +322,21 @@
 
 public:
 	void bind(MVKCommandEncoder* cmdEncoder,
-			  uint32_t descriptorIndex,
+			  MVKDescriptorSetLayoutBinding* mvkDSLBind,
+			  uint32_t elementIndex,
 			  bool stages[],
 			  MVKShaderResourceBinding& mtlIndexes,
 			  MVKArrayRef<uint32_t> dynamicOffsets,
 			  uint32_t& dynamicOffsetIndex) override;
 
-	void write(MVKDescriptorSet* mvkDescSet,
+	void write(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+			   MVKDescriptorSet* mvkDescSet,
 			   uint32_t srcIndex,
 			   size_t stride,
 			   const void* pData) override;
 
-	void read(MVKDescriptorSet* mvkDescSet,
+	void read(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+			  MVKDescriptorSet* mvkDescSet,
 			  uint32_t dstIndex,
 			  VkDescriptorImageInfo* pImageInfo,
 			  VkDescriptorBufferInfo* pBufferInfo,
@@ -343,7 +349,6 @@
 
 protected:
 	MVKImageView* _mvkImageView = nullptr;
-	VkImageLayout _imageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
 };
 
 
@@ -388,32 +393,32 @@
 
 protected:
 	void bind(MVKCommandEncoder* cmdEncoder,
-			  uint32_t descriptorIndex,
+			  MVKDescriptorSetLayoutBinding* mvkDSLBind,
+			  uint32_t elementIndex,
 			  bool stages[],
 			  MVKShaderResourceBinding& mtlIndexes,
 			  MVKArrayRef<uint32_t> dynamicOffsets,
 			  uint32_t& dynamicOffsetIndex);
 
-	void write(MVKDescriptorSet* mvkDescSet,
+	void write(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+			   MVKDescriptorSet* mvkDescSet,
 			   uint32_t srcIndex,
 			   size_t stride,
 			   const void* pData);
 
-	void read(MVKDescriptorSet* mvkDescSet,
+	void read(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+			  MVKDescriptorSet* mvkDescSet,
 			  uint32_t dstIndex,
 			  VkDescriptorImageInfo* pImageInfo,
 			  VkDescriptorBufferInfo* pBufferInfo,
 			  VkBufferView* pTexelBufferView,
 			  VkWriteDescriptorSetInlineUniformBlockEXT* inlineUniformBlock);
 
-	void setLayout(MVKDescriptorSetLayoutBinding* dslBinding, uint32_t index);
-
 	void reset();
 
 	~MVKSamplerDescriptorMixin() { reset(); }
 
 	MVKSampler* _mvkSampler = nullptr;
-	bool _hasDynamicSampler = true;
 };
 
 
@@ -427,26 +432,27 @@
 	VkDescriptorType getDescriptorType() override { return VK_DESCRIPTOR_TYPE_SAMPLER; }
 
 	void bind(MVKCommandEncoder* cmdEncoder,
-			  uint32_t descriptorIndex,
+			  MVKDescriptorSetLayoutBinding* mvkDSLBind,
+			  uint32_t elementIndex,
 			  bool stages[],
 			  MVKShaderResourceBinding& mtlIndexes,
 			  MVKArrayRef<uint32_t> dynamicOffsets,
 			  uint32_t& dynamicOffsetIndex) override;
 
-	void write(MVKDescriptorSet* mvkDescSet,
+	void write(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+			   MVKDescriptorSet* mvkDescSet,
 			   uint32_t srcIndex,
 			   size_t stride,
 			   const void* pData) override;
 
-	void read(MVKDescriptorSet* mvkDescSet,
+	void read(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+			  MVKDescriptorSet* mvkDescSet,
 			  uint32_t dstIndex,
 			  VkDescriptorImageInfo* pImageInfo,
 			  VkDescriptorBufferInfo* pBufferInfo,
 			  VkBufferView* pTexelBufferView,
 			  VkWriteDescriptorSetInlineUniformBlockEXT* inlineUniformBlock) override;
 
-	void setLayout(MVKDescriptorSetLayoutBinding* dslBinding, uint32_t index) override;
-
 	void reset() override;
 
 	~MVKSamplerDescriptor() { reset(); }
@@ -464,26 +470,27 @@
 	VkDescriptorType getDescriptorType() override { return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; }
 
 	void bind(MVKCommandEncoder* cmdEncoder,
-			  uint32_t descriptorIndex,
+			  MVKDescriptorSetLayoutBinding* mvkDSLBind,
+			  uint32_t elementIndex,
 			  bool stages[],
 			  MVKShaderResourceBinding& mtlIndexes,
 			  MVKArrayRef<uint32_t> dynamicOffsets,
 			  uint32_t& dynamicOffsetIndex) override;
 
-	void write(MVKDescriptorSet* mvkDescSet,
+	void write(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+			   MVKDescriptorSet* mvkDescSet,
 			   uint32_t srcIndex,
 			   size_t stride,
 			   const void* pData) override;
 
-	void read(MVKDescriptorSet* mvkDescSet,
+	void read(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+			  MVKDescriptorSet* mvkDescSet,
 			  uint32_t dstIndex,
 			  VkDescriptorImageInfo* pImageInfo,
 			  VkDescriptorBufferInfo* pBufferInfo,
 			  VkBufferView* pTexelBufferView,
 			  VkWriteDescriptorSetInlineUniformBlockEXT* inlineUniformBlock) override;
 
-	void setLayout(MVKDescriptorSetLayoutBinding* dslBinding, uint32_t index) override;
-
 	void reset() override;
 
 	~MVKCombinedImageSamplerDescriptor() { reset(); }
@@ -499,18 +506,21 @@
 
 public:
 	void bind(MVKCommandEncoder* cmdEncoder,
-			  uint32_t descriptorIndex,
+			  MVKDescriptorSetLayoutBinding* mvkDSLBind,
+			  uint32_t elementIndex,
 			  bool stages[],
 			  MVKShaderResourceBinding& mtlIndexes,
 			  MVKArrayRef<uint32_t> dynamicOffsets,
 			  uint32_t& dynamicOffsetIndex) override;
 
-	void write(MVKDescriptorSet* mvkDescSet,
+	void write(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+			   MVKDescriptorSet* mvkDescSet,
 			   uint32_t srcIndex,
 			   size_t stride,
 			   const void* pData) override;
 
-	void read(MVKDescriptorSet* mvkDescSet,
+	void read(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+			  MVKDescriptorSet* mvkDescSet,
 			  uint32_t dstIndex,
 			  VkDescriptorImageInfo* pImageInfo,
 			  VkDescriptorBufferInfo* pBufferInfo,
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.mm
index 43c1195..48ce529 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptor.mm
@@ -106,7 +106,7 @@
     for (uint32_t descIdx = 0; descIdx < descCnt; descIdx++) {
 		MVKDescriptor* mvkDesc = descSet->getDescriptor(getBinding(), descIdx);
 		if (mvkDesc->getDescriptorType() == descType) {
-			mvkDesc->bind(cmdEncoder, descIdx, _applyToStage, mtlIdxs, dynamicOffsets, dynamicOffsetIndex);
+			mvkDesc->bind(cmdEncoder, this, descIdx, _applyToStage, mtlIdxs, dynamicOffsets, dynamicOffsetIndex);
 		}
     }
 }
@@ -509,7 +509,8 @@
 
 // A null cmdEncoder can be passed to perform a validation pass
 void MVKBufferDescriptor::bind(MVKCommandEncoder* cmdEncoder,
-							   uint32_t descriptorIndex,
+							   MVKDescriptorSetLayoutBinding* mvkDSLBind,
+							   uint32_t elementIndex,
 							   bool stages[],
 							   MVKShaderResourceBinding& mtlIndexes,
 							   MVKArrayRef<uint32_t> dynamicOffsets,
@@ -533,7 +534,7 @@
 	}
 	for (uint32_t i = kMVKShaderStageVertex; i < kMVKShaderStageMax; i++) {
 		if (stages[i]) {
-			bb.index = mtlIndexes.stages[i].bufferIndex + descriptorIndex;
+			bb.index = mtlIndexes.stages[i].bufferIndex + elementIndex;
 			if (i == kMVKShaderStageCompute) {
 				if (cmdEncoder) { cmdEncoder->_computeResourcesState.bindBuffer(bb); }
 			} else {
@@ -543,7 +544,8 @@
 	}
 }
 
-void MVKBufferDescriptor::write(MVKDescriptorSet* mvkDescSet,
+void MVKBufferDescriptor::write(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+								MVKDescriptorSet* mvkDescSet,
 								uint32_t srcIndex,
 								size_t stride,
 								const void* pData) {
@@ -558,7 +560,8 @@
 	if (oldBuff) { oldBuff->release(); }
 }
 
-void MVKBufferDescriptor::read(MVKDescriptorSet* mvkDescSet,
+void MVKBufferDescriptor::read(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+							   MVKDescriptorSet* mvkDescSet,
 							   uint32_t dstIndex,
 							   VkDescriptorImageInfo* pImageInfo,
 							   VkDescriptorBufferInfo* pBufferInfo,
@@ -584,14 +587,15 @@
 
 // A null cmdEncoder can be passed to perform a validation pass
 void MVKInlineUniformBlockDescriptor::bind(MVKCommandEncoder* cmdEncoder,
-										   uint32_t descriptorIndex,
+										   MVKDescriptorSetLayoutBinding* mvkDSLBind,
+										   uint32_t elementIndex,
 										   bool stages[],
 										   MVKShaderResourceBinding& mtlIndexes,
 										   MVKArrayRef<uint32_t> dynamicOffsets,
 										   uint32_t& dynamicOffsetIndex) {
 	MVKMTLBufferBinding bb;
 	bb.mtlBytes = _buffer;
-	bb.size = _length;
+	bb.size = mvkDSLBind->_info.descriptorCount;
 	bb.isInline = true;
 	for (uint32_t i = kMVKShaderStageVertex; i < kMVKShaderStageMax; i++) {
 		if (stages[i]) {
@@ -605,36 +609,39 @@
 	}
 }
 
-void MVKInlineUniformBlockDescriptor::write(MVKDescriptorSet* mvkDescSet,
-                                            uint32_t dstOffset,
-                                            size_t stride,
-                                            const void* pData) {
+void MVKInlineUniformBlockDescriptor::write(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+											MVKDescriptorSet* mvkDescSet,
+											uint32_t dstOffset,
+											size_t stride,
+											const void* pData) {
+	// Ensure there is a destination to write to
+	uint32_t buffSize = mvkDSLBind->_info.descriptorCount;
+	if ( !_buffer ) { _buffer = (uint8_t*)malloc(buffSize); }
+
 	const auto& pInlineUniformBlock = *(VkWriteDescriptorSetInlineUniformBlockEXT*)pData;
-	if (pInlineUniformBlock.pData && _buffer) {
-		memcpy(_buffer + dstOffset, pInlineUniformBlock.pData, pInlineUniformBlock.dataSize);
+	if (_buffer && pInlineUniformBlock.pData && dstOffset < buffSize) {
+		uint32_t dataLen = std::min(pInlineUniformBlock.dataSize, buffSize - dstOffset);
+		memcpy(_buffer + dstOffset, pInlineUniformBlock.pData, dataLen);
 	}
 }
 
-void MVKInlineUniformBlockDescriptor::read(MVKDescriptorSet* mvkDescSet,
-                                           uint32_t srcOffset,
-                                           VkDescriptorImageInfo* pImageInfo,
-                                           VkDescriptorBufferInfo* pBufferInfo,
-                                           VkBufferView* pTexelBufferView,
-                                           VkWriteDescriptorSetInlineUniformBlockEXT* pInlineUniformBlock) {
-	if (_buffer && pInlineUniformBlock->pData) {
-		memcpy((void*)pInlineUniformBlock->pData, _buffer + srcOffset, pInlineUniformBlock->dataSize);
+void MVKInlineUniformBlockDescriptor::read(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+										   MVKDescriptorSet* mvkDescSet,
+										   uint32_t srcOffset,
+										   VkDescriptorImageInfo* pImageInfo,
+										   VkDescriptorBufferInfo* pBufferInfo,
+										   VkBufferView* pTexelBufferView,
+										   VkWriteDescriptorSetInlineUniformBlockEXT* pInlineUniformBlock) {
+	uint32_t buffSize = mvkDSLBind->_info.descriptorCount;
+	if (_buffer && pInlineUniformBlock->pData && srcOffset < buffSize) {
+		uint32_t dataLen = std::min(pInlineUniformBlock->dataSize, buffSize - srcOffset);
+		memcpy((void*)pInlineUniformBlock->pData, _buffer + srcOffset, dataLen);
 	}
 }
 
-void MVKInlineUniformBlockDescriptor::setLayout(MVKDescriptorSetLayoutBinding* dslBinding, uint32_t index) {
-    _length = dslBinding->_info.descriptorCount;
-    _buffer = (uint8_t*)malloc(_length);
-}
-
 void MVKInlineUniformBlockDescriptor::reset() {
     free(_buffer);
 	_buffer = nullptr;
-    _length = 0;
 	MVKDescriptor::reset();
 }
 
@@ -644,7 +651,8 @@
 
 // A null cmdEncoder can be passed to perform a validation pass
 void MVKImageDescriptor::bind(MVKCommandEncoder* cmdEncoder,
-							  uint32_t descriptorIndex,
+							  MVKDescriptorSetLayoutBinding* mvkDSLBind,
+							  uint32_t elementIndex,
 							  bool stages[],
 							  MVKShaderResourceBinding& mtlIndexes,
 							  MVKArrayRef<uint32_t> dynamicOffsets,
@@ -671,14 +679,14 @@
         }
         for (uint32_t i = kMVKShaderStageVertex; i < kMVKShaderStageMax; i++) {
             if (stages[i]) {
-                tb.index = mtlIndexes.stages[i].textureIndex + descriptorIndex + planeIndex;
+                tb.index = mtlIndexes.stages[i].textureIndex + elementIndex + planeIndex;
                 if (i == kMVKShaderStageCompute) {
                     if (cmdEncoder) { cmdEncoder->_computeResourcesState.bindTexture(tb); }
                 } else {
                     if (cmdEncoder) { cmdEncoder->_graphicsResourcesState.bindTexture(MVKShaderStage(i), tb); }
                 }
                 if (descType == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) {
-                    bb.index = mtlIndexes.stages[i].bufferIndex + descriptorIndex + planeIndex;
+                    bb.index = mtlIndexes.stages[i].bufferIndex + elementIndex + planeIndex;
                     if (i == kMVKShaderStageCompute) {
                         if (cmdEncoder) { cmdEncoder->_computeResourcesState.bindBuffer(bb); }
                     } else {
@@ -690,7 +698,8 @@
     }
 }
 
-void MVKImageDescriptor::write(MVKDescriptorSet* mvkDescSet,
+void MVKImageDescriptor::write(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+							   MVKDescriptorSet* mvkDescSet,
 							   uint32_t srcIndex,
 							   size_t stride,
 							   const void* pData) {
@@ -698,13 +707,13 @@
 
 	const auto* pImgInfo = &get<VkDescriptorImageInfo>(pData, stride, srcIndex);
 	_mvkImageView = (MVKImageView*)pImgInfo->imageView;
-	_imageLayout = pImgInfo->imageLayout;
 
 	if (_mvkImageView) { _mvkImageView->retain(); }
 	if (oldImgView) { oldImgView->release(); }
 }
 
-void MVKImageDescriptor::read(MVKDescriptorSet* mvkDescSet,
+void MVKImageDescriptor::read(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+							  MVKDescriptorSet* mvkDescSet,
 							  uint32_t dstIndex,
 							  VkDescriptorImageInfo* pImageInfo,
 							  VkDescriptorBufferInfo* pBufferInfo,
@@ -712,13 +721,12 @@
 							  VkWriteDescriptorSetInlineUniformBlockEXT* pInlineUniformBlock) {
 	auto& imgInfo = pImageInfo[dstIndex];
 	imgInfo.imageView = (VkImageView)_mvkImageView;
-	imgInfo.imageLayout = _imageLayout;
+	imgInfo.imageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
 }
 
 void MVKImageDescriptor::reset() {
 	if (_mvkImageView) { _mvkImageView->release(); }
 	_mvkImageView = nullptr;
-	_imageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
 	MVKDescriptor::reset();
 }
 
@@ -730,18 +738,23 @@
 // Metal validation requires each sampler in an array of samplers to be populated,
 // even if not used, so populate a default if one hasn't been set.
 void MVKSamplerDescriptorMixin::bind(MVKCommandEncoder* cmdEncoder,
-									 uint32_t descriptorIndex,
+									 MVKDescriptorSetLayoutBinding* mvkDSLBind,
+									 uint32_t elementIndex,
 									 bool stages[],
 									 MVKShaderResourceBinding& mtlIndexes,
 									 MVKArrayRef<uint32_t> dynamicOffsets,
 									 uint32_t& dynamicOffsetIndex) {
+
+	MVKSampler* imutSamp = mvkDSLBind->getImmutableSampler(elementIndex);
+	MVKSampler* mvkSamp = imutSamp ? imutSamp : _mvkSampler;
+
 	MVKMTLSamplerStateBinding sb;
-	sb.mtlSamplerState = (_mvkSampler
-						  ? _mvkSampler->getMTLSamplerState()
+	sb.mtlSamplerState = (mvkSamp
+						  ? mvkSamp->getMTLSamplerState()
 						  : cmdEncoder->getDevice()->getDefaultMTLSamplerState());
 	for (uint32_t i = kMVKShaderStageVertex; i < kMVKShaderStageMax; i++) {
 		if (stages[i]) {
-			sb.index = mtlIndexes.stages[i].samplerIndex + descriptorIndex;
+			sb.index = mtlIndexes.stages[i].samplerIndex + elementIndex;
 			if (i == kMVKShaderStageCompute) {
 				if (cmdEncoder) { cmdEncoder->_computeResourcesState.bindSamplerState(sb); }
 			} else {
@@ -751,50 +764,37 @@
 	}
 }
 
-void MVKSamplerDescriptorMixin::write(MVKDescriptorSet* mvkDescSet,
+void MVKSamplerDescriptorMixin::write(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+									  MVKDescriptorSet* mvkDescSet,
 									  uint32_t srcIndex,
 									  size_t stride,
 									  const void* pData) {
-	if (_hasDynamicSampler) {
-		auto* oldSamp = _mvkSampler;
+	auto* oldSamp = _mvkSampler;
 
-		const auto* pImgInfo = &get<VkDescriptorImageInfo>(pData, stride, srcIndex);
-		_mvkSampler = (MVKSampler*)pImgInfo->sampler;
-		if (_mvkSampler && _mvkSampler->getRequiresConstExprSampler()) {
-			_mvkSampler->reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkUpdateDescriptorSets(): Tried to push an immutable sampler.");
-		}
-
-		if (_mvkSampler) { _mvkSampler->retain(); }
-		if (oldSamp) { oldSamp->release(); }
+	const auto* pImgInfo = &get<VkDescriptorImageInfo>(pData, stride, srcIndex);
+	_mvkSampler = (MVKSampler*)pImgInfo->sampler;
+	if (_mvkSampler && _mvkSampler->getRequiresConstExprSampler()) {
+		_mvkSampler->reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkUpdateDescriptorSets(): Tried to push an immutable sampler.");
 	}
+
+	if (_mvkSampler) { _mvkSampler->retain(); }
+	if (oldSamp) { oldSamp->release(); }
 }
 
-void MVKSamplerDescriptorMixin::read(MVKDescriptorSet* mvkDescSet,
+void MVKSamplerDescriptorMixin::read(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+									 MVKDescriptorSet* mvkDescSet,
 									 uint32_t dstIndex,
 									 VkDescriptorImageInfo* pImageInfo,
 									 VkDescriptorBufferInfo* pBufferInfo,
 									 VkBufferView* pTexelBufferView,
 									 VkWriteDescriptorSetInlineUniformBlockEXT* pInlineUniformBlock) {
 	auto& imgInfo = pImageInfo[dstIndex];
-	imgInfo.sampler = _hasDynamicSampler ? (VkSampler)_mvkSampler : nullptr;
-}
-
-// If the descriptor set layout binding contains immutable samplers, use them
-// Otherwise the sampler will be populated dynamically at a later time.
-void MVKSamplerDescriptorMixin::setLayout(MVKDescriptorSetLayoutBinding* dslBinding, uint32_t index) {
-	auto* oldSamp = _mvkSampler;
-
-	_mvkSampler = dslBinding->getImmutableSampler(index);
-	_hasDynamicSampler = !_mvkSampler;
-
-	if (_mvkSampler) { _mvkSampler->retain(); }
-	if (oldSamp) { oldSamp->release(); }
+	imgInfo.sampler = (VkSampler)_mvkSampler;
 }
 
 void MVKSamplerDescriptorMixin::reset() {
 	if (_mvkSampler) { _mvkSampler->release(); }
 	_mvkSampler = nullptr;
-	_hasDynamicSampler = true;
 }
 
 
@@ -803,33 +803,31 @@
 
 // A null cmdEncoder can be passed to perform a validation pass
 void MVKSamplerDescriptor::bind(MVKCommandEncoder* cmdEncoder,
-								uint32_t descriptorIndex,
+								MVKDescriptorSetLayoutBinding* mvkDSLBind,
+								uint32_t elementIndex,
 								bool stages[],
 								MVKShaderResourceBinding& mtlIndexes,
 								MVKArrayRef<uint32_t> dynamicOffsets,
 								uint32_t& dynamicOffsetIndex) {
-	MVKSamplerDescriptorMixin::bind(cmdEncoder, descriptorIndex, stages, mtlIndexes, dynamicOffsets, dynamicOffsetIndex);
+	MVKSamplerDescriptorMixin::bind(cmdEncoder, mvkDSLBind, elementIndex, stages, mtlIndexes, dynamicOffsets, dynamicOffsetIndex);
 }
 
-void MVKSamplerDescriptor::write(MVKDescriptorSet* mvkDescSet,
+void MVKSamplerDescriptor::write(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+								 MVKDescriptorSet* mvkDescSet,
 								 uint32_t srcIndex,
 								 size_t stride,
 								 const void* pData) {
-	MVKSamplerDescriptorMixin::write(mvkDescSet, srcIndex, stride, pData);
+	MVKSamplerDescriptorMixin::write(mvkDSLBind, mvkDescSet, srcIndex, stride, pData);
 }
 
-void MVKSamplerDescriptor::read(MVKDescriptorSet* mvkDescSet,
+void MVKSamplerDescriptor::read(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+								MVKDescriptorSet* mvkDescSet,
 								uint32_t dstIndex,
 								VkDescriptorImageInfo* pImageInfo,
 								VkDescriptorBufferInfo* pBufferInfo,
 								VkBufferView* pTexelBufferView,
 								VkWriteDescriptorSetInlineUniformBlockEXT* pInlineUniformBlock) {
-	MVKSamplerDescriptorMixin::read(mvkDescSet, dstIndex, pImageInfo, pBufferInfo, pTexelBufferView, pInlineUniformBlock);
-}
-
-void MVKSamplerDescriptor::setLayout(MVKDescriptorSetLayoutBinding* dslBinding, uint32_t index) {
-	MVKDescriptor::setLayout(dslBinding, index);
-	MVKSamplerDescriptorMixin::setLayout(dslBinding, index);
+	MVKSamplerDescriptorMixin::read(mvkDSLBind, mvkDescSet, dstIndex, pImageInfo, pBufferInfo, pTexelBufferView, pInlineUniformBlock);
 }
 
 void MVKSamplerDescriptor::reset() {
@@ -843,36 +841,34 @@
 
 // A null cmdEncoder can be passed to perform a validation pass
 void MVKCombinedImageSamplerDescriptor::bind(MVKCommandEncoder* cmdEncoder,
-											 uint32_t descriptorIndex,
+											 MVKDescriptorSetLayoutBinding* mvkDSLBind,
+											 uint32_t elementIndex,
 											 bool stages[],
 											 MVKShaderResourceBinding& mtlIndexes,
 											 MVKArrayRef<uint32_t> dynamicOffsets,
 											 uint32_t& dynamicOffsetIndex) {
-	MVKImageDescriptor::bind(cmdEncoder, descriptorIndex, stages, mtlIndexes, dynamicOffsets, dynamicOffsetIndex);
-	MVKSamplerDescriptorMixin::bind(cmdEncoder, descriptorIndex, stages, mtlIndexes, dynamicOffsets, dynamicOffsetIndex);
+	MVKImageDescriptor::bind(cmdEncoder, mvkDSLBind, elementIndex, stages, mtlIndexes, dynamicOffsets, dynamicOffsetIndex);
+	MVKSamplerDescriptorMixin::bind(cmdEncoder, mvkDSLBind, elementIndex, stages, mtlIndexes, dynamicOffsets, dynamicOffsetIndex);
 }
 
-void MVKCombinedImageSamplerDescriptor::write(MVKDescriptorSet* mvkDescSet,
+void MVKCombinedImageSamplerDescriptor::write(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+											  MVKDescriptorSet* mvkDescSet,
 											  uint32_t srcIndex,
 											  size_t stride,
 											  const void* pData) {
-	MVKImageDescriptor::write(mvkDescSet, srcIndex, stride, pData);
-	MVKSamplerDescriptorMixin::write(mvkDescSet, srcIndex, stride, pData);
+	MVKImageDescriptor::write(mvkDSLBind, mvkDescSet, srcIndex, stride, pData);
+	MVKSamplerDescriptorMixin::write(mvkDSLBind, mvkDescSet, srcIndex, stride, pData);
 }
 
-void MVKCombinedImageSamplerDescriptor::read(MVKDescriptorSet* mvkDescSet,
+void MVKCombinedImageSamplerDescriptor::read(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+											 MVKDescriptorSet* mvkDescSet,
 											 uint32_t dstIndex,
 											 VkDescriptorImageInfo* pImageInfo,
 											 VkDescriptorBufferInfo* pBufferInfo,
 											 VkBufferView* pTexelBufferView,
 											 VkWriteDescriptorSetInlineUniformBlockEXT* pInlineUniformBlock) {
-	MVKImageDescriptor::read(mvkDescSet, dstIndex, pImageInfo, pBufferInfo, pTexelBufferView, pInlineUniformBlock);
-	MVKSamplerDescriptorMixin::read(mvkDescSet, dstIndex, pImageInfo, pBufferInfo, pTexelBufferView, pInlineUniformBlock);
-}
-
-void MVKCombinedImageSamplerDescriptor::setLayout(MVKDescriptorSetLayoutBinding* dslBinding, uint32_t index) {
-	MVKImageDescriptor::setLayout(dslBinding, index);
-	MVKSamplerDescriptorMixin::setLayout(dslBinding, index);
+	MVKImageDescriptor::read(mvkDSLBind, mvkDescSet, dstIndex, pImageInfo, pBufferInfo, pTexelBufferView, pInlineUniformBlock);
+	MVKSamplerDescriptorMixin::read(mvkDSLBind, mvkDescSet, dstIndex, pImageInfo, pBufferInfo, pTexelBufferView, pInlineUniformBlock);
 }
 
 void MVKCombinedImageSamplerDescriptor::reset() {
@@ -886,7 +882,8 @@
 
 // A null cmdEncoder can be passed to perform a validation pass
 void MVKTexelBufferDescriptor::bind(MVKCommandEncoder* cmdEncoder,
-									uint32_t descriptorIndex,
+									MVKDescriptorSetLayoutBinding* mvkDSLBind,
+									uint32_t elementIndex,
 									bool stages[],
 									MVKShaderResourceBinding& mtlIndexes,
 									MVKArrayRef<uint32_t> dynamicOffsets,
@@ -905,14 +902,14 @@
 	}
 	for (uint32_t i = kMVKShaderStageVertex; i < kMVKShaderStageMax; i++) {
 		if (stages[i]) {
-			tb.index = mtlIndexes.stages[i].textureIndex + descriptorIndex;
+			tb.index = mtlIndexes.stages[i].textureIndex + elementIndex;
 			if (i == kMVKShaderStageCompute) {
 				if (cmdEncoder) { cmdEncoder->_computeResourcesState.bindTexture(tb); }
 			} else {
 				if (cmdEncoder) { cmdEncoder->_graphicsResourcesState.bindTexture(MVKShaderStage(i), tb); }
 			}
 			if (descType == VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER) {
-				bb.index = mtlIndexes.stages[i].bufferIndex + descriptorIndex;
+				bb.index = mtlIndexes.stages[i].bufferIndex + elementIndex;
 				if (i == kMVKShaderStageCompute) {
 					if (cmdEncoder) { cmdEncoder->_computeResourcesState.bindBuffer(bb); }
 				} else {
@@ -923,7 +920,8 @@
 	}
 }
 
-void MVKTexelBufferDescriptor::write(MVKDescriptorSet* mvkDescSet,
+void MVKTexelBufferDescriptor::write(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+									 MVKDescriptorSet* mvkDescSet,
 									 uint32_t srcIndex,
 									 size_t stride,
 									 const void* pData) {
@@ -936,7 +934,8 @@
 	if (oldBuffView) { oldBuffView->release(); }
 }
 
-void MVKTexelBufferDescriptor::read(MVKDescriptorSet* mvkDescSet,
+void MVKTexelBufferDescriptor::read(MVKDescriptorSetLayoutBinding* mvkDSLBind,
+									MVKDescriptorSet* mvkDescSet,
 									uint32_t dstIndex,
 									VkDescriptorImageInfo* pImageInfo,
 									VkDescriptorBufferInfo* pBufferInfo,
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
index 5630f4e..16a79ef 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
@@ -235,12 +235,13 @@
 							 size_t stride,
 							 const void* pData) {
 
-	VkDescriptorType descType = getDescriptorType(pDescriptorAction->dstBinding);
+	MVKDescriptorSetLayoutBinding* mvkDSLBind = _layout->getBinding(pDescriptorAction->dstBinding);
+	VkDescriptorType descType = mvkDSLBind->getDescriptorType();
     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(this, pDescriptorAction->dstArrayElement, stride, pData);
+			mvkDesc->write(mvkDSLBind, this, pDescriptorAction->dstArrayElement, stride, pData);
 		}
     } else {
         uint32_t dstStartIdx = _layout->getDescriptorIndex(pDescriptorAction->dstBinding, pDescriptorAction->dstArrayElement);
@@ -248,7 +249,7 @@
         for (uint32_t descIdx = 0; descIdx < descCnt; descIdx++) {
 			MVKDescriptor* mvkDesc = _descriptors[dstStartIdx + descIdx];
 			if (mvkDesc->getDescriptorType() == descType) {
-				mvkDesc->write(this, descIdx, stride, pData);
+				mvkDesc->write(mvkDSLBind, this, descIdx, stride, pData);
 			}
         }
     }
@@ -260,20 +261,21 @@
 							VkBufferView* pTexelBufferView,
 							VkWriteDescriptorSetInlineUniformBlockEXT* pInlineUniformBlock) {
 
-	VkDescriptorType descType = getDescriptorType(pDescriptorCopy->srcBinding);
+	MVKDescriptorSetLayoutBinding* mvkDSLBind = _layout->getBinding(pDescriptorCopy->srcBinding);
+	VkDescriptorType descType = mvkDSLBind->getDescriptorType();
 	uint32_t descCnt = pDescriptorCopy->descriptorCount;
     if (descType == VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT) {
 		// For inline buffers srcArrayElement is a byte offset
 		MVKDescriptor* mvkDesc = getDescriptor(pDescriptorCopy->srcBinding);
 		if (mvkDesc->getDescriptorType() == descType) {
-			mvkDesc->read(this, pDescriptorCopy->srcArrayElement, pImageInfo, pBufferInfo, pTexelBufferView, pInlineUniformBlock);
+			mvkDesc->read(mvkDSLBind, this, pDescriptorCopy->srcArrayElement, pImageInfo, pBufferInfo, pTexelBufferView, pInlineUniformBlock);
 		}
     } else {
         uint32_t srcStartIdx = _layout->getDescriptorIndex(pDescriptorCopy->srcBinding, pDescriptorCopy->srcArrayElement);
         for (uint32_t descIdx = 0; descIdx < descCnt; descIdx++) {
 			MVKDescriptor* mvkDesc = _descriptors[srcStartIdx + descIdx];
 			if (mvkDesc->getDescriptorType() == descType) {
-				mvkDesc->read(this, descIdx, pImageInfo, pBufferInfo, pTexelBufferView, pInlineUniformBlock);
+				mvkDesc->read(mvkDSLBind, this, descIdx, pImageInfo, pBufferInfo, pTexelBufferView, pInlineUniformBlock);
 			}
         }
     }
@@ -296,8 +298,6 @@
 			MVKDescriptor* mvkDesc = nullptr;
 			setConfigurationResult(_pool->allocateDescriptor(mvkDSLBind->getDescriptorType(), &mvkDesc));
 			if ( !wasConfigurationSuccessful() ) { break; }
-
-			mvkDesc->setLayout(mvkDSLBind, descIdx);
 			_descriptors.push_back(mvkDesc);
 		}
 		if ( !wasConfigurationSuccessful() ) { break; }