Merge pull request #304 from cdavis5e/wolf2-packed-pixels

Correct MTLPixelFormats for a couple of formats.
diff --git a/ExternalRevisions/SPIRV-Cross_repo_revision b/ExternalRevisions/SPIRV-Cross_repo_revision
index 91450c6..6ab048f 100644
--- a/ExternalRevisions/SPIRV-Cross_repo_revision
+++ b/ExternalRevisions/SPIRV-Cross_repo_revision
@@ -1 +1 @@
-c9210427b9ab547d41f1af804dedae581b382965
+cc5c0204d8bcdadbb4add03e53346df98bf27fa4
diff --git a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
index 72de183..d34b019 100644
--- a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
+++ b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
@@ -48,7 +48,7 @@
  */
 #define MVK_VERSION_MAJOR   1
 #define MVK_VERSION_MINOR   0
-#define MVK_VERSION_PATCH   23
+#define MVK_VERSION_PATCH   24
 
 #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)
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.h b/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.h
index 8e54826..bd23b84 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.h
@@ -25,6 +25,8 @@
 class MVKCommandEncoder;
 class MVKOcclusionQueryPool;
 
+struct MVKShaderAuxBufferBinding;
+
 
 #pragma mark -
 #pragma mark MVKCommandEncoderState
@@ -388,6 +390,16 @@
             }
         }
     }
+
+    struct AuxBuffer {
+        uint32_t swizzleConst[1];
+    };
+
+    // Updates the swizzle for an image in the given buffer.
+    void updateSwizzle(id<MTLBuffer> buffer, uint32_t index, uint32_t swizzle) {
+        auto* aux = (AuxBuffer*)buffer.contents;
+        aux->swizzleConst[index] = swizzle;
+    }
 };
 
 
@@ -425,6 +437,9 @@
         _mtlIndexBufferBinding = binding;   // No need to track dirty state
     }
 
+    /** Sets the current auxiliary buffer state. */
+    void bindAuxBuffer(id<MTLBuffer> buffer, const MVKShaderAuxBufferBinding& binding, bool needVertexAuxBuffer, bool needFragmentAuxBuffer);
+
 
 #pragma mark Construction
     
@@ -442,6 +457,8 @@
     std::vector<MVKMTLTextureBinding> _fragmentTextureBindings;
     std::vector<MVKMTLSamplerStateBinding> _vertexSamplerStateBindings;
     std::vector<MVKMTLSamplerStateBinding> _fragmentSamplerStateBindings;
+    MVKMTLBufferBinding _vertexAuxBufferBinding;
+    MVKMTLBufferBinding _fragmentAuxBufferBinding;
 
     bool _areVertexBufferBindingsDirty = false;
     bool _areFragmentBufferBindingsDirty = false;
@@ -469,6 +486,8 @@
     /** Binds the specified sampler state. */
     void bindSamplerState(const MVKMTLSamplerStateBinding& binding);
 
+    /** Sets the current auxiliary buffer state. */
+    void bindAuxBuffer(id<MTLBuffer> buffer, const MVKShaderAuxBufferBinding& binding);
 
 #pragma mark Construction
 
@@ -483,6 +502,7 @@
     std::vector<MVKMTLBufferBinding> _bufferBindings;
     std::vector<MVKMTLTextureBinding> _textureBindings;
     std::vector<MVKMTLSamplerStateBinding> _samplerStateBindings;
+    MVKMTLBufferBinding _auxBufferBinding;
 
     bool _areBufferBindingsDirty = false;
     bool _areTextureBindingsDirty = false;
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.mm b/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.mm
index 4c560c1..a8ae908 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.mm
@@ -425,6 +425,15 @@
     bind(binding, _fragmentSamplerStateBindings, _areFragmentSamplerStateBindingsDirty);
 }
 
+void MVKGraphicsResourcesCommandEncoderState::bindAuxBuffer(id<MTLBuffer> buffer, const MVKShaderAuxBufferBinding& binding, bool needVertexAuxBuffer, bool needFragmentAuxBuffer) {
+    _vertexAuxBufferBinding.mtlBuffer = needVertexAuxBuffer ? buffer : nil;
+    _vertexAuxBufferBinding.index = binding.vertex;
+    _vertexAuxBufferBinding.isDirty = needVertexAuxBuffer;
+    _fragmentAuxBufferBinding.mtlBuffer = needFragmentAuxBuffer ? buffer : nil;
+    _fragmentAuxBufferBinding.index = binding.fragment;
+    _fragmentAuxBufferBinding.isDirty = needFragmentAuxBuffer;
+}
+
 // Mark everything as dirty
 void MVKGraphicsResourcesCommandEncoderState::markDirty() {
     MVKCommandEncoderState::markDirty();
@@ -434,10 +443,26 @@
     MVKResourcesCommandEncoderState::markDirty(_fragmentTextureBindings, _areFragmentTextureBindingsDirty);
     MVKResourcesCommandEncoderState::markDirty(_vertexSamplerStateBindings, _areVertexSamplerStateBindingsDirty);
     MVKResourcesCommandEncoderState::markDirty(_fragmentSamplerStateBindings, _areFragmentSamplerStateBindingsDirty);
+    _vertexAuxBufferBinding.isDirty = true;
+    _fragmentAuxBufferBinding.isDirty = true;
 }
 
 void MVKGraphicsResourcesCommandEncoderState::encodeImpl() {
 
+    if (_vertexAuxBufferBinding.mtlBuffer) {
+        for (auto& b : _vertexTextureBindings) {
+            if (b.isDirty)
+                updateSwizzle(_vertexAuxBufferBinding.mtlBuffer, b.index, b.swizzle);
+        }
+    }
+
+    if (_fragmentAuxBufferBinding.mtlBuffer) {
+        for (auto& b : _fragmentTextureBindings) {
+            if (b.isDirty)
+                updateSwizzle(_fragmentAuxBufferBinding.mtlBuffer, b.index, b.swizzle);
+        }
+    }
+
     encodeBinding<MVKMTLBufferBinding>(_vertexBufferBindings, _areVertexBufferBindingsDirty,
                                        [](MVKCommandEncoder* cmdEncoder, MVKMTLBufferBinding& b)->void {
                                            [cmdEncoder->_mtlRenderEncoder setVertexBuffer: b.mtlBuffer
@@ -452,6 +477,20 @@
                                                                                     atIndex: b.index];
                                        });
 
+    if (_vertexAuxBufferBinding.isDirty) {
+        _vertexAuxBufferBinding.isDirty = false;
+        [_cmdEncoder->_mtlRenderEncoder setVertexBuffer: _vertexAuxBufferBinding.mtlBuffer
+                                        offset: 0
+                                        atIndex: _vertexAuxBufferBinding.index];
+    }
+
+    if (_fragmentAuxBufferBinding.isDirty) {
+        _fragmentAuxBufferBinding.isDirty = false;
+        [_cmdEncoder->_mtlRenderEncoder setFragmentBuffer: _fragmentAuxBufferBinding.mtlBuffer
+                                        offset: 0
+                                        atIndex: _fragmentAuxBufferBinding.index];
+    }
+
     encodeBinding<MVKMTLTextureBinding>(_vertexTextureBindings, _areVertexTextureBindingsDirty,
                                         [](MVKCommandEncoder* cmdEncoder, MVKMTLTextureBinding& b)->void {
                                             [cmdEncoder->_mtlRenderEncoder setVertexTexture: b.mtlTexture
@@ -484,6 +523,8 @@
     _fragmentTextureBindings.clear();
     _vertexSamplerStateBindings.clear();
     _fragmentSamplerStateBindings.clear();
+    _vertexAuxBufferBinding.mtlBuffer = nil;
+    _fragmentAuxBufferBinding.mtlBuffer = nil;
 
     _areVertexBufferBindingsDirty = false;
     _areFragmentBufferBindingsDirty = false;
@@ -491,6 +532,8 @@
     _areFragmentTextureBindingsDirty = false;
     _areVertexSamplerStateBindingsDirty = false;
     _areFragmentSamplerStateBindingsDirty = false;
+    _vertexAuxBufferBinding.isDirty = false;
+    _fragmentAuxBufferBinding.isDirty = false;
 }
 
 
@@ -509,16 +552,30 @@
     bind(binding, _samplerStateBindings, _areSamplerStateBindingsDirty);
 }
 
+void MVKComputeResourcesCommandEncoderState::bindAuxBuffer(id<MTLBuffer> buffer, const MVKShaderAuxBufferBinding& binding) {
+    _auxBufferBinding.mtlBuffer = buffer;
+    _auxBufferBinding.index = binding.compute;
+    _auxBufferBinding.isDirty = buffer != nil;
+}
+
 // Mark everything as dirty
 void MVKComputeResourcesCommandEncoderState::markDirty() {
     MVKCommandEncoderState::markDirty();
     MVKResourcesCommandEncoderState::markDirty(_bufferBindings, _areBufferBindingsDirty);
     MVKResourcesCommandEncoderState::markDirty(_textureBindings, _areTextureBindingsDirty);
     MVKResourcesCommandEncoderState::markDirty(_samplerStateBindings, _areSamplerStateBindingsDirty);
+    _auxBufferBinding.isDirty = true;
 }
 
 void MVKComputeResourcesCommandEncoderState::encodeImpl() {
 
+    if (_auxBufferBinding.mtlBuffer) {
+        for (auto& b : _textureBindings) {
+            if (b.isDirty)
+                updateSwizzle(_auxBufferBinding.mtlBuffer, b.index, b.swizzle);
+        }
+    }
+
     encodeBinding<MVKMTLBufferBinding>(_bufferBindings, _areBufferBindingsDirty,
                                        [](MVKCommandEncoder* cmdEncoder, MVKMTLBufferBinding& b)->void {
                                            [cmdEncoder->getMTLComputeEncoder(kMVKCommandUseDispatch) setBuffer: b.mtlBuffer
@@ -526,6 +583,13 @@
 																									   atIndex: b.index];
                                        });
 
+    if (_auxBufferBinding.isDirty) {
+        _auxBufferBinding.isDirty = false;
+        [_cmdEncoder->getMTLComputeEncoder(kMVKCommandUseDispatch) setBuffer: _auxBufferBinding.mtlBuffer
+                                                                   offset: 0
+                                                                   atIndex: _auxBufferBinding.index];
+    }
+
     encodeBinding<MVKMTLTextureBinding>(_textureBindings, _areTextureBindingsDirty,
                                         [](MVKCommandEncoder* cmdEncoder, MVKMTLTextureBinding& b)->void {
                                             [cmdEncoder->getMTLComputeEncoder(kMVKCommandUseDispatch) setTexture: b.mtlTexture
@@ -543,10 +607,12 @@
     _bufferBindings.clear();
     _textureBindings.clear();
     _samplerStateBindings.clear();
+    _auxBufferBinding.mtlBuffer = nil;
 
     _areBufferBindingsDirty = false;
     _areTextureBindingsDirty = false;
     _areSamplerStateBindingsDirty = false;
+    _auxBufferBinding.isDirty = false;
 }
 
 
diff --git a/MoltenVK/MoltenVK/Commands/MVKMTLResourceBindings.h b/MoltenVK/MoltenVK/Commands/MVKMTLResourceBindings.h
index 140f12e..ddb615a 100644
--- a/MoltenVK/MoltenVK/Commands/MVKMTLResourceBindings.h
+++ b/MoltenVK/MoltenVK/Commands/MVKMTLResourceBindings.h
@@ -24,6 +24,7 @@
 typedef struct {
     union { id<MTLTexture> mtlTexture = nil; id<MTLTexture> mtlResource; }; // aliases
     uint32_t index = 0;
+    uint32_t swizzle = 0;
     bool isDirty = true;
 } MVKMTLTextureBinding;
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
index d2c0f79..71e9f1f 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
@@ -100,6 +100,7 @@
 
 protected:
 	friend class MVKDescriptorBinding;
+	friend class MVKPipelineLayout;
 
 	VkResult initMetalResourceIndexOffsets(MVKShaderStageResourceBinding* pBindingIndexes,
                                            MVKShaderStageResourceBinding* pDescSetCounts,
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
index a8367e2..909b665 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
@@ -111,6 +111,11 @@
             case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
             case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT: {
                 tb.mtlTexture = descBinding._mtlTextures[rezIdx];
+                if (_info.descriptorType == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE && tb.mtlTexture) {
+                    tb.swizzle = ((MVKImageView*)descBinding._imageBindings[rezIdx].imageView)->getPackedSwizzle();
+                } else {
+                    tb.swizzle = 0;
+                }
                 if (_applyToVertexStage) {
                     tb.index = mtlIdxs.vertexStage.textureIndex + rezIdx;
                     cmdEncoder->_graphicsResourcesState.bindVertexTexture(tb);
@@ -145,6 +150,11 @@
 
             case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: {
                 tb.mtlTexture = descBinding._mtlTextures[rezIdx];
+                if (tb.mtlTexture) {
+                    tb.swizzle = ((MVKImageView*)descBinding._imageBindings[rezIdx].imageView)->getPackedSwizzle();
+                } else {
+                    tb.swizzle = 0;
+                }
                 sb.mtlSamplerState = descBinding._mtlSamplers[rezIdx];
                 if (_applyToVertexStage) {
                     tb.index = mtlIdxs.vertexStage.textureIndex + rezIdx;
@@ -243,6 +253,11 @@
                 const auto& imageInfo = get<VkDescriptorImageInfo>(pData, stride, rezIdx - dstArrayElement);
                 MVKImageView* imageView = (MVKImageView*)imageInfo.imageView;
                 tb.mtlTexture = imageView->getMTLTexture();
+                if (_info.descriptorType == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE && imageView) {
+                    tb.swizzle = imageView->getPackedSwizzle();
+                } else {
+                    tb.swizzle = 0;
+                }
                 if (_applyToVertexStage) {
                     tb.index = mtlIdxs.vertexStage.textureIndex + rezIdx;
                     cmdEncoder->_graphicsResourcesState.bindVertexTexture(tb);
@@ -262,6 +277,7 @@
             case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: {
                 auto* bufferView = get<MVKBufferView*>(pData, stride, rezIdx - dstArrayElement);
                 tb.mtlTexture = bufferView->getMTLTexture();
+                tb.swizzle = 0;
                 if (_applyToVertexStage) {
                     tb.index = mtlIdxs.vertexStage.textureIndex + rezIdx;
                     cmdEncoder->_graphicsResourcesState.bindVertexTexture(tb);
@@ -304,6 +320,11 @@
                 MVKImageView* imageView = (MVKImageView*)imageInfo.imageView;
                 MVKSampler* sampler = _immutableSamplers.empty() ? (MVKSampler*)imageInfo.sampler : _immutableSamplers[rezIdx];
                 tb.mtlTexture = imageView->getMTLTexture();
+                if (imageView) {
+                    tb.swizzle = imageView->getPackedSwizzle();
+                } else {
+                    tb.swizzle = 0;
+                }
                 sb.mtlSamplerState = sampler->getMTLSamplerState();
                 if (_applyToVertexStage) {
                     tb.index = mtlIdxs.vertexStage.textureIndex + rezIdx;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
index c68cc77..9c3c98b 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
@@ -261,6 +261,9 @@
 	/** Returns the Metal texture type of this image view. */
 	inline MTLTextureType getMTLTextureType() { return _mtlTextureType; }
 
+	/** Returns the packed component swizzle of this image view. */
+	inline uint32_t getPackedSwizzle() { return _packedSwizzle; }
+
 	/**
 	 * Populates the texture of the specified render pass descriptor
 	 * with the Metal texture underlying this image.
@@ -283,10 +286,10 @@
 protected:
 	id<MTLTexture> newMTLTexture();
 	void initMTLTextureViewSupport();
-    MTLPixelFormat getSwizzledMTLPixelFormat(VkFormat format, VkComponentMapping components);
+    MTLPixelFormat getSwizzledMTLPixelFormat(VkFormat format, VkComponentMapping components, bool& useSwizzle);
     bool matchesSwizzle(VkComponentMapping components, VkComponentMapping pattern);
     const char* getSwizzleName(VkComponentSwizzle swizzle);
-    void setSwizzleFormatError(VkFormat format, VkComponentMapping components);
+    uint32_t packSwizzle(VkComponentMapping components);
 	void validateImageViewConfig(const VkImageViewCreateInfo* pCreateInfo);
 
     MVKImage* _image;
@@ -296,6 +299,7 @@
 	std::mutex _lock;
 	MTLPixelFormat _mtlPixelFormat;
 	MTLTextureType _mtlTextureType;
+	uint32_t _packedSwizzle;
 	bool _useMTLTextureView;
 };
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
index 8d936a5..3e0703b 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
@@ -702,10 +702,12 @@
 		_subresourceRange.layerCount = _image->getLayerCount() - _subresourceRange.baseArrayLayer;
 	}
 
+	bool useSwizzle;
 	_mtlTexture = nil;
-    _mtlPixelFormat = getSwizzledMTLPixelFormat(pCreateInfo->format, pCreateInfo->components);
+    _mtlPixelFormat = getSwizzledMTLPixelFormat(pCreateInfo->format, pCreateInfo->components, useSwizzle);
 	_mtlTextureType = mvkMTLTextureTypeFromVkImageViewType(pCreateInfo->viewType, (_image->getSampleCount() != VK_SAMPLE_COUNT_1_BIT));
 	initMTLTextureViewSupport();
+	_packedSwizzle = useSwizzle ? packSwizzle(pCreateInfo->components) : 0;
 }
 
 // Validate whether the image view configuration can be supported
@@ -731,9 +733,10 @@
 // Metal does not support general per-texture swizzles, and so this function relies on a few coincidental
 // alignments of existing MTLPixelFormats of the same structure. If swizzling is not possible for a
 // particular combination of format and swizzle spec, the original MTLPixelFormat is returned.
-MTLPixelFormat MVKImageView::getSwizzledMTLPixelFormat(VkFormat format, VkComponentMapping components) {
+MTLPixelFormat MVKImageView::getSwizzledMTLPixelFormat(VkFormat format, VkComponentMapping components, bool& useSwizzle) {
     MTLPixelFormat mtlPF = mtlPixelFormatFromVkFormat(format);
 
+    useSwizzle = false;
     switch (mtlPF) {
         case MTLPixelFormatR8Unorm:
             if (matchesSwizzle(components, {VK_COMPONENT_SWIZZLE_ZERO, VK_COMPONENT_SWIZZLE_MAX_ENUM, VK_COMPONENT_SWIZZLE_MAX_ENUM, VK_COMPONENT_SWIZZLE_R} ) ) {
@@ -741,16 +744,6 @@
             }
             break;
 
-        case MTLPixelFormatR8Snorm:
-#if MVK_IOS
-        case MTLPixelFormatR8Unorm_sRGB:
-#endif
-            if (matchesSwizzle(components, {VK_COMPONENT_SWIZZLE_ZERO, VK_COMPONENT_SWIZZLE_MAX_ENUM, VK_COMPONENT_SWIZZLE_MAX_ENUM, VK_COMPONENT_SWIZZLE_R} ) ) {
-                setSwizzleFormatError(format, components);
-                return MTLPixelFormatA8Unorm;
-            }
-            break;
-
         case MTLPixelFormatA8Unorm:
             if (matchesSwizzle(components, {VK_COMPONENT_SWIZZLE_A, VK_COMPONENT_SWIZZLE_MAX_ENUM, VK_COMPONENT_SWIZZLE_MAX_ENUM, VK_COMPONENT_SWIZZLE_ZERO} ) ) {
                 return MTLPixelFormatR8Unorm;
@@ -769,13 +762,6 @@
             }
             break;
 
-        case MTLPixelFormatRGBA8Snorm:
-            if (matchesSwizzle(components, {VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_A} ) ) {
-                setSwizzleFormatError(format, components);
-                return MTLPixelFormatBGRA8Unorm;
-            }
-            break;
-
         case MTLPixelFormatBGRA8Unorm:
             if (matchesSwizzle(components, {VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_A} ) ) {
                 return MTLPixelFormatRGBA8Unorm;
@@ -789,16 +775,20 @@
             break;
 
         case MTLPixelFormatDepth32Float_Stencil8:
-            if (_subresourceRange.aspectMask == VK_IMAGE_ASPECT_STENCIL_BIT &&
-                matchesSwizzle(components, {VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_MAX_ENUM, VK_COMPONENT_SWIZZLE_MAX_ENUM, VK_COMPONENT_SWIZZLE_MAX_ENUM} ) ) {
+            if (_subresourceRange.aspectMask == VK_IMAGE_ASPECT_STENCIL_BIT) {
+                if (!matchesSwizzle(components, {VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_MAX_ENUM, VK_COMPONENT_SWIZZLE_MAX_ENUM, VK_COMPONENT_SWIZZLE_MAX_ENUM} ) ) {
+                    useSwizzle = true;
+                }
                 return MTLPixelFormatX32_Stencil8;
             }
             break;
 
 #if MVK_MACOS
         case MTLPixelFormatDepth24Unorm_Stencil8:
-            if (_subresourceRange.aspectMask == VK_IMAGE_ASPECT_STENCIL_BIT &&
-                matchesSwizzle(components, {VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_MAX_ENUM, VK_COMPONENT_SWIZZLE_MAX_ENUM, VK_COMPONENT_SWIZZLE_MAX_ENUM} ) ) {
+            if (_subresourceRange.aspectMask == VK_IMAGE_ASPECT_STENCIL_BIT) {
+                if (!matchesSwizzle(components, {VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_MAX_ENUM, VK_COMPONENT_SWIZZLE_MAX_ENUM, VK_COMPONENT_SWIZZLE_MAX_ENUM} ) ) {
+                    useSwizzle = true;
+                }
                 return MTLPixelFormatX24_Stencil8;
             }
             break;
@@ -809,7 +799,7 @@
     }
 
     if ( !matchesSwizzle(components, {VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A} ) ) {
-        setSwizzleFormatError(format, components);
+        useSwizzle = true;
     }
     return mtlPF;
 }
@@ -827,17 +817,6 @@
     }
 }
 
-// Sets a standard swizzle format error during instance construction.
-void MVKImageView::setSwizzleFormatError(VkFormat format, VkComponentMapping components) {
-    setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FORMAT_NOT_SUPPORTED,
-                                                  "VkImageView format %s and swizzle (%s, %s, %s, %s) does not map to a valid MTLPixelFormat.\n",
-                                                  mvkVkFormatName(format),
-                                                  getSwizzleName(components.r),
-                                                  getSwizzleName(components.g),
-                                                  getSwizzleName(components.b),
-                                                  getSwizzleName(components.a)));
-}
-
 // Returns whether the swizzle components of the internal VkComponentMapping matches the
 // swizzle pattern, by comparing corresponding elements of the two structures. The pattern
 // supports wildcards, in that any element of pattern can be set to VK_COMPONENT_SWIZZLE_MAX_ENUM
@@ -855,6 +834,12 @@
     return true;
 }
 
+// Packs a VkComponentMapping structure into a single 32-bit word.
+uint32_t MVKImageView::packSwizzle(VkComponentMapping components) {
+    return ((components.r & 0xFF) << 0) | ((components.g & 0xFF) << 8) |
+           ((components.b & 0xFF) << 16) | ((components.a & 0xFF) << 24);
+}
+
 // Determine whether this image view should use a Metal texture view,
 // and set the _useMTLTextureView variable appropriately.
 void MVKImageView::initMTLTextureViewSupport() {
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
index 951b153..d6e0557 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
@@ -34,6 +34,16 @@
 
 
 #pragma mark -
+#pragma mark MVKShaderAuxBufferBinding
+
+struct MVKShaderAuxBufferBinding {
+	uint32_t vertex;
+	uint32_t fragment;
+	uint32_t compute;
+};
+
+
+#pragma mark -
 #pragma mark MVKPipelineLayout
 
 /** Represents a Vulkan pipeline layout. */
@@ -61,6 +71,12 @@
 						   uint32_t set,
 						   const void* pData);
 
+	/** Returns the current auxiliary buffer bindings. */
+	const MVKShaderAuxBufferBinding& getAuxBufferIndex() { return _auxBufferIndex; }
+
+	/** Returns the number of textures in this layout. This is used to calculate the size of the auxiliary buffer. */
+	uint32_t getNumTextures() { return _numTextures; }
+
 	/** Constructs an instance for the specified device. */
 	MVKPipelineLayout(MVKDevice* device, const VkPipelineLayoutCreateInfo* pCreateInfo);
 
@@ -69,6 +85,8 @@
 	std::vector<MVKShaderResourceBinding> _dslMTLResourceIndexOffsets;
 	std::vector<VkPushConstantRange> _pushConstants;
 	MVKShaderResourceBinding _pushConstantsMTLResourceIndexOffsets;
+	MVKShaderAuxBufferBinding _auxBufferIndex;
+	uint32_t _numTextures;
 };
 
 
@@ -83,12 +101,17 @@
 	/** Binds this pipeline to the specified command encoder. */
 	virtual void encode(MVKCommandEncoder* cmdEncoder) = 0;
 
+	/** Returns the current auxiliary buffer bindings. */
+	const MVKShaderAuxBufferBinding& getAuxBufferIndex() { return _auxBufferIndex; }
+
 	/** Constructs an instance for the device. layout, and parent (which may be NULL). */
 	MVKPipeline(MVKDevice* device, MVKPipelineCache* pipelineCache, MVKPipeline* parent) : MVKBaseDeviceObject(device),
 																						   _pipelineCache(pipelineCache) {}
 
 protected:
 	MVKPipelineCache* _pipelineCache;
+	MVKShaderAuxBufferBinding _auxBufferIndex;
+	id<MTLBuffer> _auxBuffer = nil;
 
 };
 
@@ -107,6 +130,12 @@
     /** Returns whether this pipeline permits dynamic setting of the specifie state. */
     bool supportsDynamicState(VkDynamicState state);
 
+    /** Returns whether or not the vertex shader needs a buffer to hold auxiliary state. */
+    bool needsVertexAuxBuffer() { return _needsVertexAuxBuffer; }
+
+    /** Returns whether or not the fragment shader needs a buffer to hold auxiliary state. */
+    bool needsFragmentAuxBuffer() { return _needsFragmentAuxBuffer; }
+
 	/** Constructs an instance for the device and parent (which may be NULL). */
 	MVKGraphicsPipeline(MVKDevice* device,
 						MVKPipelineCache* pipelineCache,
@@ -138,6 +167,8 @@
 
 	bool _dynamicStateEnabled[VK_DYNAMIC_STATE_RANGE_SIZE];
 	bool _hasDepthStencilInfo;
+	bool _needsVertexAuxBuffer;
+	bool _needsFragmentAuxBuffer;
 };
 
 
@@ -152,6 +183,9 @@
 	/** Binds this pipeline to the specified command encoder. */
 	void encode(MVKCommandEncoder* cmdEncoder) override;
 
+	/** Returns whether or not the compute shader needs a buffer to hold auxiliary state. */
+	bool needsAuxBuffer() { return _needsAuxBuffer; }
+
 	/** Constructs an instance for the device and parent (which may be NULL). */
 	MVKComputePipeline(MVKDevice* device,
 					   MVKPipelineCache* pipelineCache,
@@ -165,6 +199,7 @@
 
     id<MTLComputePipelineState> _mtlPipelineState;
     MTLSize _mtlThreadgroupSize;
+    bool _needsAuxBuffer;
 };
 
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
index eeb209d..c35e66c 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
@@ -107,6 +107,46 @@
                                       spv::ExecutionModelGLCompute,
                                       kPushConstDescSet,
                                       kPushConstBinding);
+
+    // Scan the resource bindings, looking for an unused buffer index.
+    // FIXME: If we ever encounter a device that supports more than 31 buffer
+    // bindings, we'll need to update this code.
+    unordered_map<uint32_t, uint32_t> freeBufferMasks;
+    freeBufferMasks[spv::ExecutionModelVertex] = freeBufferMasks[spv::ExecutionModelFragment] = freeBufferMasks[spv::ExecutionModelGLCompute] = (1 << _device->_pMetalFeatures->maxPerStageBufferCount) - 1;
+    _numTextures = 0;
+    for (auto& binding : context.resourceBindings) {
+        if (binding.descriptorSet == kPushConstDescSet && binding.binding == kPushConstBinding) {
+            // This is the special push constant buffer.
+            freeBufferMasks[binding.stage] &= ~(1 << binding.mslBuffer);
+            continue;
+        }
+        VkDescriptorType descriptorType = _descriptorSetLayouts[binding.descriptorSet]._bindings[binding.binding]._info.descriptorType;
+        switch (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:
+            // This buffer is being used.
+            freeBufferMasks[binding.stage] &= ~(1 << binding.mslBuffer);
+            break;
+        case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
+        case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
+        case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
+        case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
+        case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
+            // If it results in a texture binding, we need to account for it so
+            // we know how big to make the auxiliary buffer.
+            if (binding.mslTexture + 1 > _numTextures)
+                _numTextures = binding.mslTexture + 1;
+            break;
+        default:
+            break;
+        }
+    }
+    // Pick the lowest index that isn't used.
+    _auxBufferIndex.vertex = ffs(freeBufferMasks[spv::ExecutionModelVertex]) - 1;
+    _auxBufferIndex.fragment = ffs(freeBufferMasks[spv::ExecutionModelFragment]) - 1;
+    _auxBufferIndex.compute = ffs(freeBufferMasks[spv::ExecutionModelGLCompute]) - 1;
 }
 
 MVKPipelineLayout::MVKPipelineLayout(MVKDevice* device,
@@ -174,6 +214,8 @@
     if (_device->_pFeatures->depthClamp) {
         [mtlCmdEnc setDepthClipMode: _mtlDepthClipMode];
     }
+
+    cmdEncoder->_graphicsResourcesState.bindAuxBuffer(_auxBuffer, _auxBufferIndex, _needsVertexAuxBuffer, _needsFragmentAuxBuffer);
 }
 
 bool MVKGraphicsPipeline::supportsDynamicState(VkDynamicState state) {
@@ -295,6 +337,9 @@
 
     SPIRVToMSLConverterContext shaderContext;
     initMVKShaderConverterContext(shaderContext, pCreateInfo);
+    auto* mvkLayout = (MVKPipelineLayout*)pCreateInfo->layout;
+    _auxBufferIndex = mvkLayout->getAuxBufferIndex();
+    uint32_t auxBufferSize = sizeof(uint32_t) * mvkLayout->getNumTextures();
 
     // Retrieve the render subpass for which this pipeline is being constructed
     MVKRenderPass* mvkRendPass = (MVKRenderPass*)pCreateInfo->renderPass;
@@ -302,6 +347,8 @@
 
     MTLRenderPipelineDescriptor* plDesc = [[MTLRenderPipelineDescriptor new] autorelease];
 
+    uint32_t vbCnt = pCreateInfo->pVertexInputState->vertexBindingDescriptionCount;
+
     // Add shader stages
     for (uint32_t i = 0; i < pCreateInfo->stageCount; i++) {
         const VkPipelineShaderStageCreateInfo* pSS = &pCreateInfo->pStages[i];
@@ -312,6 +359,7 @@
         // Vertex shader
         if (mvkAreFlagsEnabled(pSS->stage, VK_SHADER_STAGE_VERTEX_BIT)) {
 			shaderContext.options.entryPointStage = spv::ExecutionModelVertex;
+			shaderContext.options.auxBufferIndex = _auxBufferIndex.vertex;
 			id<MTLFunction> mtlFunction = mvkShdrMod->getMTLFunction(&shaderContext, pSS->pSpecializationInfo, _pipelineCache).mtlFunction;
 			if ( !mtlFunction ) {
 				setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Vertex shader function could not be compiled into pipeline. See previous error."));
@@ -319,19 +367,36 @@
 			}
 			plDesc.vertexFunction = mtlFunction;
 			plDesc.rasterizationEnabled = !shaderContext.options.isRasterizationDisabled;
+			_needsVertexAuxBuffer = shaderContext.options.needsAuxBuffer;
+			// If we need the auxiliary buffer and there's no place to put it,
+			// we're in serious trouble.
+			if (_needsVertexAuxBuffer && (_auxBufferIndex.vertex == ~0u || _auxBufferIndex.vertex >= _device->_pMetalFeatures->maxPerStageBufferCount - vbCnt) ) {
+				setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Vertex shader requires auxiliary buffer, but there is no free slot to pass it."));
+				return nil;
+			}
         }
 
         // Fragment shader
         if (mvkAreFlagsEnabled(pSS->stage, VK_SHADER_STAGE_FRAGMENT_BIT)) {
 			shaderContext.options.entryPointStage = spv::ExecutionModelFragment;
+			shaderContext.options.auxBufferIndex = _auxBufferIndex.fragment;
 			id<MTLFunction> mtlFunction = mvkShdrMod->getMTLFunction(&shaderContext, pSS->pSpecializationInfo, _pipelineCache).mtlFunction;
 			if ( !mtlFunction ) {
 				setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Fragment shader function could not be compiled into pipeline. See previous error."));
 			}
 			plDesc.fragmentFunction = mtlFunction;
+			_needsFragmentAuxBuffer = shaderContext.options.needsAuxBuffer;
+			if (_needsFragmentAuxBuffer && _auxBufferIndex.fragment == ~0u) {
+				setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Fragment shader requires auxiliary buffer, but there is no free slot to pass it."));
+				return nil;
+			}
         }
     }
 
+    if (_needsVertexAuxBuffer || _needsFragmentAuxBuffer) {
+        _auxBuffer = [_device->getMTLDevice() newBufferWithLength: auxBufferSize options: MTLResourceStorageModeShared];
+    }
+
     // Vertex attributes
     uint32_t vaCnt = pCreateInfo->pVertexInputState->vertexAttributeDescriptionCount;
     for (uint32_t i = 0; i < vaCnt; i++) {
@@ -345,7 +410,6 @@
     }
 
     // Vertex buffer bindings
-    uint32_t vbCnt = pCreateInfo->pVertexInputState->vertexBindingDescriptionCount;
     for (uint32_t i = 0; i < vbCnt; i++) {
         const VkVertexInputBindingDescription* pVKVB = &pCreateInfo->pVertexInputState->pVertexBindingDescriptions[i];
         uint32_t vbIdx = _device->getMetalBufferIndexForVertexAttributeBinding(pVKVB->binding);
@@ -481,6 +545,7 @@
 void MVKComputePipeline::encode(MVKCommandEncoder* cmdEncoder) {
     [cmdEncoder->getMTLComputeEncoder(kMVKCommandUseDispatch) setComputePipelineState: _mtlPipelineState];
     cmdEncoder->_mtlThreadgroupSize = _mtlThreadgroupSize;
+    cmdEncoder->_computeResourcesState.bindAuxBuffer(_auxBuffer, _auxBufferIndex);
 }
 
 MVKComputePipeline::MVKComputePipeline(MVKDevice* device,
@@ -499,6 +564,10 @@
 	} else {
 		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Compute shader function could not be compiled into pipeline. See previous error."));
 	}
+
+	if (_needsAuxBuffer && _auxBufferIndex.compute == ~0u) {
+		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Compute shader requires auxiliary buffer, but there is no free slot to pass it."));
+	}
 }
 
 // Returns a MTLFunction to use when creating the MTLComputePipelineState.
@@ -514,9 +583,17 @@
 
     MVKPipelineLayout* layout = (MVKPipelineLayout*)pCreateInfo->layout;
     layout->populateShaderConverterContext(shaderContext);
+    _auxBufferIndex = layout->getAuxBufferIndex();
+    uint32_t auxBufferSize = sizeof(uint32_t) * layout->getNumTextures();
+    shaderContext.options.auxBufferIndex = _auxBufferIndex.compute;
 
     MVKShaderModule* mvkShdrMod = (MVKShaderModule*)pSS->module;
-    return mvkShdrMod->getMTLFunction(&shaderContext, pSS->pSpecializationInfo, _pipelineCache);
+    auto func = mvkShdrMod->getMTLFunction(&shaderContext, pSS->pSpecializationInfo, _pipelineCache);
+    _needsAuxBuffer = shaderContext.options.needsAuxBuffer;
+    if (_needsAuxBuffer) {
+        _auxBuffer = [_device->getMTLDevice() newBufferWithLength: auxBufferSize options: MTLResourceStorageModeShared];
+    }
+    return func;
 }
 
 
diff --git a/MoltenVKShaderConverter/MoltenVKSPIRVToMSLConverter/SPIRVToMSLConverter.cpp b/MoltenVKShaderConverter/MoltenVKSPIRVToMSLConverter/SPIRVToMSLConverter.cpp
index 9c131bf..2b8040a 100644
--- a/MoltenVKShaderConverter/MoltenVKSPIRVToMSLConverter/SPIRVToMSLConverter.cpp
+++ b/MoltenVKShaderConverter/MoltenVKSPIRVToMSLConverter/SPIRVToMSLConverter.cpp
@@ -201,9 +201,11 @@
 
 		mslOpts.msl_version = context.options.mslVersion;
 		mslOpts.texel_buffer_texture_width = context.options.texelBufferTextureWidth;
+		mslOpts.aux_buffer_index = context.options.auxBufferIndex;
 		mslOpts.enable_point_size_builtin = context.options.isRenderingPoints;
 		mslOpts.disable_rasterization = context.options.isRasterizationDisabled;
 		mslOpts.resolve_specialized_array_lengths = true;
+		mslOpts.swizzle_texture_samples = true;
 		pMSLCompiler->set_msl_options(mslOpts);
 
 		auto scOpts = pMSLCompiler->get_common_options();
@@ -229,6 +231,7 @@
     // Populate content extracted from the SPRI-V compiler.
 	populateEntryPoint(_entryPoint, pMSLCompiler, context.options);
 	context.options.isRasterizationDisabled = pMSLCompiler && pMSLCompiler->get_is_rasterization_disabled();
+	context.options.needsAuxBuffer = pMSLCompiler && pMSLCompiler->needs_aux_buffer();
 	delete pMSLCompiler;
 
 	// Copy whether the vertex attributes and resource bindings are used by the shader
diff --git a/MoltenVKShaderConverter/MoltenVKSPIRVToMSLConverter/SPIRVToMSLConverter.h b/MoltenVKShaderConverter/MoltenVKSPIRVToMSLConverter/SPIRVToMSLConverter.h
index 25b6c3e..751e752 100644
--- a/MoltenVKShaderConverter/MoltenVKSPIRVToMSLConverter/SPIRVToMSLConverter.h
+++ b/MoltenVKShaderConverter/MoltenVKSPIRVToMSLConverter/SPIRVToMSLConverter.h
@@ -37,10 +37,12 @@
 
         uint32_t mslVersion = makeMSLVersion(2);
 		uint32_t texelBufferTextureWidth = 4096;
+		uint32_t auxBufferIndex = 0;
 		bool shouldFlipVertexY = true;
 		bool isRenderingPoints = false;
 
 		bool isRasterizationDisabled = false;
+		bool needsAuxBuffer = false;
 
         /** 
          * Returns whether the specified options match this one.