Merge pull request #1058 from billhollings/xcode12

Sync Xcode12 branch from master and support Xcode 11.7.
diff --git a/Docs/MoltenVK_Runtime_UserGuide.md b/Docs/MoltenVK_Runtime_UserGuide.md
index 28e913f..f1fc211 100644
--- a/Docs/MoltenVK_Runtime_UserGuide.md
+++ b/Docs/MoltenVK_Runtime_UserGuide.md
@@ -274,6 +274,7 @@
 - `VK_KHR_bind_memory2`
 - `VK_KHR_create_renderpass2`
 - `VK_KHR_dedicated_allocation`
+- `VK_KHR_depth_stencil_resolve`
 - `VK_KHR_descriptor_update_template`
 - `VK_KHR_device_group`
 - `VK_KHR_device_group_creation`
@@ -286,6 +287,7 @@
 - `VK_KHR_maintenance2`
 - `VK_KHR_maintenance3`
 - `VK_KHR_multiview`
+- `VK_KHR_portability_subset`
 - `VK_KHR_push_descriptor`
 - `VK_KHR_relaxed_block_layout`
 - `VK_KHR_sampler_mirror_clamp_to_edge` *(macOS)*
@@ -315,7 +317,6 @@
 - `VK_EXT_swapchain_colorspace`
 - `VK_EXT_vertex_attribute_divisor`
 - `VK_EXT_texel_buffer_alignment` *(requires Metal 2.0)*
-- `VK_EXTX_portability_subset`
 - `VK_MVK_ios_surface` *(iOS) (Obsolete. Use `VK_EXT_metal_surface` instead.)*
 - `VK_MVK_macos_surface` *(macOS) (Obsolete. Use `VK_EXT_metal_surface` instead.)*
 - `VK_MVK_moltenvk`
diff --git a/Docs/Whats_New.md b/Docs/Whats_New.md
index b34e5a7..6ee632b 100644
--- a/Docs/Whats_New.md
+++ b/Docs/Whats_New.md
@@ -18,6 +18,8 @@
 
 Released 2020/09/28
 
+>**_Note:_** This release contains changes to library paths and framework linking options.
+
 - Add support for Vulkan 1.1, including:
 	- The `vkEnumerateInstanceVersion()` function
 	- The `vkGetDeviceQueue2()` function
@@ -25,7 +27,9 @@
 	- A feature struct for `VK_KHR_shader_draw_parameters`
 	- All extensions that were promoted to core in Vulkan 1.1
 - Add support for extensions:
+	- `VK_KHR_portability_subset`
 	- `VK_KHR_create_renderpass2`
+	- `VK_KHR_depth_stencil_resolve`
 	- `VK_KHR_external_fence` (non-functional groundwork for future extensions,
 	  including support for GCD and Mach semaphores)
 	- `VK_KHR_external_fence_capabilities` (non-functional groundwork for future
@@ -35,6 +39,13 @@
 	- `VK_KHR_external_semaphore_capabilities` (non-functional groundwork for
 	  future `MTLSharedEvent` Vulkan extension)
 	- `VK_KHR_multiview`
+- Remove support for obsolete `VK_EXTX_portability_subset` extension.
+- Redesign build and linking options that leverage newer framework technology:
+	- Add comprehensive support for multi-platform, multi-architecture `XCFrameworks`.
+	- Build fat single-platform, multi-architecture `dylibs`.
+	- Add support for *Apple Silicon* builds for *macOS* and *Simulators*.
+	- Remove support for distinct legacy frameworks and static libraries.
+	- Remove support for fat libraries and frameworks that span device and simulators.
 - Improve performance of tessellation control pipeline stage by processing multiple 
   patches per workgroup.
 - `vkCmdBindDescriptorSets` order `pDynamicOffsets` by descriptor binding number 
diff --git a/ExternalRevisions/Vulkan-Headers_repo_revision b/ExternalRevisions/Vulkan-Headers_repo_revision
index d347369..77ca791 100644
--- a/ExternalRevisions/Vulkan-Headers_repo_revision
+++ b/ExternalRevisions/Vulkan-Headers_repo_revision
@@ -1 +1 @@
-83825d55c7d522931124696ecb07ed48f2693e5c
+7f9879b1b1fab53f719a9ed5e6e29533b10972b2
diff --git a/ExternalRevisions/Vulkan-Portability_repo_revision b/ExternalRevisions/Vulkan-Portability_repo_revision
deleted file mode 100644
index 3a9f9eb..0000000
--- a/ExternalRevisions/Vulkan-Portability_repo_revision
+++ /dev/null
@@ -1 +0,0 @@
-53be040f04ce55463d0e5b25fd132f45f003e903
diff --git a/MoltenVK/MoltenVK.xcodeproj/project.pbxproj b/MoltenVK/MoltenVK.xcodeproj/project.pbxproj
index b3b771f..4c1567c 100644
--- a/MoltenVK/MoltenVK.xcodeproj/project.pbxproj
+++ b/MoltenVK/MoltenVK.xcodeproj/project.pbxproj
@@ -124,6 +124,16 @@
 		2FEA0AB424902F9F00EEF3AD /* MVKCmdDebug.mm in Sources */ = {isa = PBXBuildFile; fileRef = A99C90ED229455B300A061DA /* MVKCmdDebug.mm */; };
 		45003E73214AD4E500E989CB /* MVKExtensions.def in Headers */ = {isa = PBXBuildFile; fileRef = 45003E6F214AD4C900E989CB /* MVKExtensions.def */; };
 		45003E74214AD4E600E989CB /* MVKExtensions.def in Headers */ = {isa = PBXBuildFile; fileRef = 45003E6F214AD4C900E989CB /* MVKExtensions.def */; };
+		453638322508A4C7000EFFD3 /* MTLRenderPassStencilAttachmentDescriptor+MoltenVK.h in Headers */ = {isa = PBXBuildFile; fileRef = 4536382D2508A4C6000EFFD3 /* MTLRenderPassStencilAttachmentDescriptor+MoltenVK.h */; };
+		453638342508A4C7000EFFD3 /* MTLRenderPassStencilAttachmentDescriptor+MoltenVK.h in Headers */ = {isa = PBXBuildFile; fileRef = 4536382D2508A4C6000EFFD3 /* MTLRenderPassStencilAttachmentDescriptor+MoltenVK.h */; };
+		453638352508A4C7000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.m in Sources */ = {isa = PBXBuildFile; fileRef = 4536382F2508A4C6000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.m */; };
+		453638362508A4C7000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.m in Sources */ = {isa = PBXBuildFile; fileRef = 4536382F2508A4C6000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.m */; };
+		453638372508A4C7000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.m in Sources */ = {isa = PBXBuildFile; fileRef = 4536382F2508A4C6000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.m */; };
+		453638382508A4C7000EFFD3 /* MTLRenderPassStencilAttachmentDescriptor+MoltenVK.m in Sources */ = {isa = PBXBuildFile; fileRef = 453638302508A4C6000EFFD3 /* MTLRenderPassStencilAttachmentDescriptor+MoltenVK.m */; };
+		4536383A2508A4C7000EFFD3 /* MTLRenderPassStencilAttachmentDescriptor+MoltenVK.m in Sources */ = {isa = PBXBuildFile; fileRef = 453638302508A4C6000EFFD3 /* MTLRenderPassStencilAttachmentDescriptor+MoltenVK.m */; };
+		4536383B2508A4C7000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.h in Headers */ = {isa = PBXBuildFile; fileRef = 453638312508A4C7000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.h */; };
+		4536383C2508A4C7000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.h in Headers */ = {isa = PBXBuildFile; fileRef = 453638312508A4C7000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.h */; };
+		4536383D2508A4C7000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.h in Headers */ = {isa = PBXBuildFile; fileRef = 453638312508A4C7000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.h */; };
 		4553AEFB2251617100E8EBCD /* MVKBlockObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 4553AEF62251617100E8EBCD /* MVKBlockObserver.m */; };
 		4553AEFC2251617100E8EBCD /* MVKBlockObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 4553AEF62251617100E8EBCD /* MVKBlockObserver.m */; };
 		4553AEFD2251617100E8EBCD /* MVKBlockObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 4553AEFA2251617100E8EBCD /* MVKBlockObserver.h */; };
@@ -451,6 +461,10 @@
 /* Begin PBXFileReference section */
 		2FEA0ABA24902F9F00EEF3AD /* libMoltenVK.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libMoltenVK.a; sourceTree = BUILT_PRODUCTS_DIR; };
 		45003E6F214AD4C900E989CB /* MVKExtensions.def */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = MVKExtensions.def; sourceTree = "<group>"; };
+		4536382D2508A4C6000EFFD3 /* MTLRenderPassStencilAttachmentDescriptor+MoltenVK.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MTLRenderPassStencilAttachmentDescriptor+MoltenVK.h"; sourceTree = "<group>"; };
+		4536382F2508A4C6000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MTLRenderPassDepthAttachmentDescriptor+MoltenVK.m"; sourceTree = "<group>"; };
+		453638302508A4C6000EFFD3 /* MTLRenderPassStencilAttachmentDescriptor+MoltenVK.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MTLRenderPassStencilAttachmentDescriptor+MoltenVK.m"; sourceTree = "<group>"; };
+		453638312508A4C7000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MTLRenderPassDepthAttachmentDescriptor+MoltenVK.h"; sourceTree = "<group>"; };
 		4553AEF62251617100E8EBCD /* MVKBlockObserver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVKBlockObserver.m; sourceTree = "<group>"; };
 		4553AEFA2251617100E8EBCD /* MVKBlockObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVKBlockObserver.h; sourceTree = "<group>"; };
 		45557A4D21C9EFF3008868BD /* MVKCodec.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MVKCodec.cpp; sourceTree = "<group>"; };
@@ -571,7 +585,6 @@
 		A9E53DFE21064F84002781DD /* MTLRenderPipelineDescriptor+MoltenVK.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MTLRenderPipelineDescriptor+MoltenVK.h"; sourceTree = "<group>"; };
 		A9F0429D1FB4CF82009FCCB8 /* MVKCommonEnvironment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVKCommonEnvironment.h; sourceTree = "<group>"; };
 		A9F0429E1FB4CF82009FCCB8 /* MVKLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVKLogging.h; sourceTree = "<group>"; };
-		A9F2559121F96814008C7785 /* vulkan-portability */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "vulkan-portability"; sourceTree = "<group>"; };
 		A9F3D9D924732A4C00745190 /* MVKSmallVectorAllocator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVKSmallVectorAllocator.h; sourceTree = "<group>"; };
 		A9F3D9DB24732A4D00745190 /* MVKSmallVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVKSmallVector.h; sourceTree = "<group>"; };
 /* End PBXFileReference section */
@@ -750,7 +763,6 @@
 			isa = PBXGroup;
 			children = (
 				A9AD67C72054DD6C00ED3C08 /* vulkan */,
-				A9F2559121F96814008C7785 /* vulkan-portability */,
 			);
 			path = include;
 			sourceTree = "<group>";
@@ -760,8 +772,12 @@
 			children = (
 				A9E53DD12100B197002781DD /* CAMetalLayer+MoltenVK.h */,
 				A9E53DD62100B197002781DD /* CAMetalLayer+MoltenVK.m */,
+				453638312508A4C7000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.h */,
+				4536382F2508A4C6000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.m */,
 				A9E53DEE2100B302002781DD /* MTLRenderPassDescriptor+MoltenVK.h */,
 				A9E53DF22100B302002781DD /* MTLRenderPassDescriptor+MoltenVK.m */,
+				4536382D2508A4C6000EFFD3 /* MTLRenderPassStencilAttachmentDescriptor+MoltenVK.h */,
+				453638302508A4C6000EFFD3 /* MTLRenderPassStencilAttachmentDescriptor+MoltenVK.m */,
 				A9E53DFE21064F84002781DD /* MTLRenderPipelineDescriptor+MoltenVK.h */,
 				A9E53DFA21064F84002781DD /* MTLRenderPipelineDescriptor+MoltenVK.m */,
 				A9E53DD32100B197002781DD /* MTLSamplerDescriptor+MoltenVK.h */,
@@ -873,6 +889,7 @@
 				2FEA0A7524902F9F00EEF3AD /* MVKCmdDebug.h in Headers */,
 				2FEA0A7624902F9F00EEF3AD /* MVKWatermarkTextureContent.h in Headers */,
 				2FEA0A7724902F9F00EEF3AD /* MVKFoundation.h in Headers */,
+				4536383C2508A4C7000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.h in Headers */,
 				2FEA0A7824902F9F00EEF3AD /* MVKDeviceMemory.h in Headers */,
 				2FEA0A7924902F9F00EEF3AD /* MVKMTLResourceBindings.h in Headers */,
 				2FEA0A7A24902F9F00EEF3AD /* MVKExtensions.def in Headers */,
@@ -908,6 +925,7 @@
 				45557A5421C9EFF3008868BD /* MVKCodec.h in Headers */,
 				A94FB8041C7DFB4800632CA3 /* MVKRenderPass.h in Headers */,
 				A9F042A61FB4CF83009FCCB8 /* MVKLogging.h in Headers */,
+				453638322508A4C7000EFFD3 /* MTLRenderPassStencilAttachmentDescriptor+MoltenVK.h in Headers */,
 				A94FB8001C7DFB4800632CA3 /* MVKQueue.h in Headers */,
 				A94FB7EC1C7DFB4800632CA3 /* MVKFramebuffer.h in Headers */,
 				83A4AD2C21BD75570006C935 /* MVKVectorAllocator.h in Headers */,
@@ -951,6 +969,7 @@
 				A9CEAAD5227378D400FAF779 /* mvk_datatypes.hpp in Headers */,
 				A90C8DEA1F45354D009CB32C /* MVKCommandEncodingPool.h in Headers */,
 				A94FB8081C7DFB4800632CA3 /* MVKResource.h in Headers */,
+				4536383B2508A4C7000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.h in Headers */,
 				A9E53DDD2100B197002781DD /* MTLTextureDescriptor+MoltenVK.h in Headers */,
 				A9653FBA24129C84005999D7 /* MVKPixelFormats.h in Headers */,
 				A981496B1FB6A998005F00B4 /* MVKStrings.h in Headers */,
@@ -980,6 +999,7 @@
 				45557A5521C9EFF3008868BD /* MVKCodec.h in Headers */,
 				A94FB8051C7DFB4800632CA3 /* MVKRenderPass.h in Headers */,
 				A9F042A71FB4CF83009FCCB8 /* MVKLogging.h in Headers */,
+				453638342508A4C7000EFFD3 /* MTLRenderPassStencilAttachmentDescriptor+MoltenVK.h in Headers */,
 				A94FB8011C7DFB4800632CA3 /* MVKQueue.h in Headers */,
 				A94FB7ED1C7DFB4800632CA3 /* MVKFramebuffer.h in Headers */,
 				83A4AD2D21BD75570006C935 /* MVKVectorAllocator.h in Headers */,
@@ -1023,6 +1043,7 @@
 				A9CEAAD6227378D400FAF779 /* mvk_datatypes.hpp in Headers */,
 				A90C8DEB1F45354D009CB32C /* MVKCommandEncodingPool.h in Headers */,
 				A94FB8091C7DFB4800632CA3 /* MVKResource.h in Headers */,
+				4536383D2508A4C7000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.h in Headers */,
 				A9E53DDE2100B197002781DD /* MTLTextureDescriptor+MoltenVK.h in Headers */,
 				A9653FBB24129C84005999D7 /* MVKPixelFormats.h in Headers */,
 				A981496C1FB6A998005F00B4 /* MVKStrings.h in Headers */,
@@ -1377,6 +1398,7 @@
 				2FEA0A9B24902F9F00EEF3AD /* MVKFoundation.cpp in Sources */,
 				2FEA0A9C24902F9F00EEF3AD /* MVKPixelFormats.mm in Sources */,
 				2FEA0A9D24902F9F00EEF3AD /* MVKDevice.mm in Sources */,
+				453638362508A4C7000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.m in Sources */,
 				2FEA0A9E24902F9F00EEF3AD /* MTLRenderPassDescriptor+MoltenVK.m in Sources */,
 				2FEA0A9F24902F9F00EEF3AD /* MVKDescriptor.mm in Sources */,
 				2FEA0AA024902F9F00EEF3AD /* MVKPipeline.mm in Sources */,
@@ -1419,6 +1441,7 @@
 				A981494D1FB6A3F7005F00B4 /* MVKBaseObject.mm in Sources */,
 				A9E53DE52100B197002781DD /* NSString+MoltenVK.mm in Sources */,
 				A94FB8321C7DFB4800632CA3 /* vulkan.mm in Sources */,
+				453638352508A4C7000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.m in Sources */,
 				A94FB8121C7DFB4800632CA3 /* MVKSurface.mm in Sources */,
 				A94FB7FE1C7DFB4800632CA3 /* MVKQueryPool.mm in Sources */,
 				A94FB7F61C7DFB4800632CA3 /* MVKInstance.mm in Sources */,
@@ -1453,6 +1476,7 @@
 				A94FB7BE1C7DFB4800632CA3 /* MVKCmdPipeline.mm in Sources */,
 				A94FB81E1C7DFB4800632CA3 /* MVKLayers.mm in Sources */,
 				A94FB7EE1C7DFB4800632CA3 /* MVKFramebuffer.mm in Sources */,
+				453638382508A4C7000EFFD3 /* MTLRenderPassStencilAttachmentDescriptor+MoltenVK.m in Sources */,
 				A9C96DD21DDC20C20053187F /* MVKMTLBufferAllocation.mm in Sources */,
 				A9E53DE92100B197002781DD /* CAMetalLayer+MoltenVK.m in Sources */,
 				A9096E5E1F81E16300DFBEA6 /* MVKCmdDispatch.mm in Sources */,
@@ -1476,6 +1500,7 @@
 				A981494E1FB6A3F7005F00B4 /* MVKBaseObject.mm in Sources */,
 				A9E53DE62100B197002781DD /* NSString+MoltenVK.mm in Sources */,
 				A94FB8331C7DFB4800632CA3 /* vulkan.mm in Sources */,
+				453638372508A4C7000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.m in Sources */,
 				A94FB8131C7DFB4800632CA3 /* MVKSurface.mm in Sources */,
 				A94FB7FF1C7DFB4800632CA3 /* MVKQueryPool.mm in Sources */,
 				A94FB7F71C7DFB4800632CA3 /* MVKInstance.mm in Sources */,
@@ -1510,6 +1535,7 @@
 				A94FB7BF1C7DFB4800632CA3 /* MVKCmdPipeline.mm in Sources */,
 				A94FB81F1C7DFB4800632CA3 /* MVKLayers.mm in Sources */,
 				A94FB7EF1C7DFB4800632CA3 /* MVKFramebuffer.mm in Sources */,
+				4536383A2508A4C7000EFFD3 /* MTLRenderPassStencilAttachmentDescriptor+MoltenVK.m in Sources */,
 				A9C96DD31DDC20C20053187F /* MVKMTLBufferAllocation.mm in Sources */,
 				A9E53DEA2100B197002781DD /* CAMetalLayer+MoltenVK.m in Sources */,
 				A9096E5F1F81E16300DFBEA6 /* MVKCmdDispatch.mm in Sources */,
diff --git a/MoltenVK/MoltenVK/API/mvk_datatypes.h b/MoltenVK/MoltenVK/API/mvk_datatypes.h
index 3f9e601..12628c5 100644
--- a/MoltenVK/MoltenVK/API/mvk_datatypes.h
+++ b/MoltenVK/MoltenVK/API/mvk_datatypes.h
@@ -362,6 +362,14 @@
 /** Returns the Metal MTLStoreAction corresponding to the specified Vulkan VkAttachmentStoreOp. */
 MTLStoreAction mvkMTLStoreActionFromVkAttachmentStoreOp(VkAttachmentStoreOp vkStoreOp, bool hasResolveAttachment);
 
+/** Returns the Metal MTLMultisampleDepthResolveFilter corresponding to the specified Vulkan VkResolveModeFlagBits. */
+MTLMultisampleDepthResolveFilter mvkMTLMultisampleDepthResolveFilterFromVkResolveModeFlagBits(VkResolveModeFlagBits vkResolveMode);
+
+#if MVK_MACOS_OR_IOS
+/** Returns the Metal MTLMultisampleStencilResolveFilter corresponding to the specified Vulkan VkResolveModeFlagBits. */
+MTLMultisampleStencilResolveFilter mvkMTLMultisampleStencilResolveFilterFromVkResolveModeFlagBits(VkResolveModeFlagBits vkResolveMode);
+#endif
+
 /** Returns the Metal MTLViewport corresponding to the specified Vulkan VkViewport. */
 MTLViewport mvkMTLViewportFromVkViewport(VkViewport vkViewport);
 
diff --git a/MoltenVK/MoltenVK/API/mvk_vulkan.h b/MoltenVK/MoltenVK/API/mvk_vulkan.h
index 757bb7e..f234ead 100644
--- a/MoltenVK/MoltenVK/API/mvk_vulkan.h
+++ b/MoltenVK/MoltenVK/API/mvk_vulkan.h
@@ -35,6 +35,8 @@
 
 #define VK_USE_PLATFORM_METAL_EXT				1
 
+#define VK_ENABLE_BETA_EXTENSIONS				1		// VK_KHR_portability_subset
+
 #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED
 #	define VK_USE_PLATFORM_IOS_MVK				1
 #endif
@@ -44,6 +46,5 @@
 #endif
 
 #include <vulkan/vulkan.h>
-#include <vulkan-portability/vk_extx_portability_subset.h>
 
 #endif
diff --git a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
index 309097a..80f1e10 100644
--- a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
+++ b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
@@ -617,6 +617,10 @@
 	VkBool32 nonUniformThreadgroups;			/**< If true, the device supports arbitrary-sized grids in compute workloads. */
 	VkBool32 renderWithoutAttachments;          /**< If true, we don't have to create a dummy attachment for a render pass if there isn't one. */
 	VkBool32 deferredStoreActions;				/**< If true, render pass store actions can be specified after the render encoder is created. */
+	VkBool32 sharedLinearTextures;				/**< If true, linear textures and texture buffers can be created from buffers in Shared storage. */
+	VkBool32 depthResolve;						/**< If true, resolving depth textures with filters other than Sample0 is supported. */
+	VkBool32 stencilResolve;					/**< If true, resolving stencil textures with filters other than Sample0 is supported. */
+	uint32_t maxPerStageDynamicMTLBufferCount;	/**< The maximum number of inline buffers that can be set on a command buffer. */
 } MVKPhysicalDeviceMetalFeatures;
 
 /** MoltenVK performance of a particular type of activity. */
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdDraw.mm b/MoltenVK/MoltenVK/Commands/MVKCmdDraw.mm
index 964af0d..e657b04 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdDraw.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdDraw.mm
@@ -629,6 +629,11 @@
 					[mtlTessCtlEncoder dispatchThreadgroups: MTLSizeMake(mvkCeilingDivide<NSUInteger>(_drawCount, mtlConvertState.threadExecutionWidth), 1, 1)
 									  threadsPerThreadgroup: MTLSizeMake(mtlConvertState.threadExecutionWidth, 1, 1)];
 				}
+                // Mark pipelines, resources, and vertex push constants as dirty
+                // so I apply them during the next stage.
+                cmdEncoder->_graphicsPipelineState.beginMetalRenderPass();
+                cmdEncoder->_graphicsResourcesState.beginMetalRenderPass();
+                cmdEncoder->getPushConstants(VK_SHADER_STAGE_VERTEX_BIT)->beginMetalRenderPass();
             } else if (drawIdx == 0 && needsInstanceAdjustment) {
                 // Similarly, for multiview, we need to adjust the instance count now.
                 // Unfortunately, this requires switching to compute.
@@ -954,6 +959,11 @@
 													 indirectBufferOffset: mtlTempIndBuffOfst
                                                     threadsPerThreadgroup: MTLSizeMake(vtxThreadExecWidth, 1, 1)];
 				mtlIndBuffOfst += sizeof(MTLDrawIndexedPrimitivesIndirectArguments);
+                // Mark pipeline, resources, and vertex push constants as dirty
+                // so I apply them during the next stage.
+                cmdEncoder->_graphicsPipelineState.beginMetalRenderPass();
+                cmdEncoder->_graphicsResourcesState.beginMetalRenderPass();
+                cmdEncoder->getPushConstants(VK_SHADER_STAGE_VERTEX_BIT)->beginMetalRenderPass();
             } else if (drawIdx == 0 && needsInstanceAdjustment) {
                 // Similarly, for multiview, we need to adjust the instance count now.
                 // Unfortunately, this requires switching to compute. Luckily, we don't also
@@ -1089,11 +1099,11 @@
 						}
 
 						mtlTempIndBuffOfst += sizeof(MTLDrawPatchIndirectArguments);
-                        // Mark pipeline, resources, and tess control push constants as dirty
+                        // Mark pipeline, resources, and vertex push constants as dirty
                         // so I apply them during the next stage.
                         cmdEncoder->_graphicsPipelineState.beginMetalRenderPass();
                         cmdEncoder->_graphicsResourcesState.beginMetalRenderPass();
-                        cmdEncoder->getPushConstants(VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT)->beginMetalRenderPass();
+                        cmdEncoder->getPushConstants(VK_SHADER_STAGE_VERTEX_BIT)->beginMetalRenderPass();
                     } else {
                         [cmdEncoder->_mtlRenderEncoder drawIndexedPrimitives: cmdEncoder->_mtlPrimitiveType
                                                                    indexType: (MTLIndexType)ibb.mtlIndexType
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.h b/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.h
index b2985ee..98616cb 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.h
@@ -139,11 +139,13 @@
 
 	void encode(MVKCommandEncoder* cmdEncoder) override;
 
+	~MVKCmdBindDescriptorSetsStatic() override;
+
 protected:
 	MVKCommandTypePool<MVKCommand>* getTypePool(MVKCommandPool* cmdPool) override;
 
 	MVKSmallVector<MVKDescriptorSet*, N> _descriptorSets;
-	MVKPipelineLayout* _pipelineLayout;
+	MVKPipelineLayout* _pipelineLayout = nullptr;
 	VkPipelineBindPoint _pipelineBindPoint;
 	uint32_t _firstSet;
 };
@@ -211,7 +213,6 @@
 	MVKCommandTypePool<MVKCommand>* getTypePool(MVKCommandPool* cmdPool) override;
 
 	MVKSmallVector<char, N> _pushConstants;
-	MVKPipelineLayout* _pipelineLayout;
 	VkShaderStageFlags _stageFlags;
 	uint32_t _offset;
 };
@@ -245,7 +246,7 @@
 	void clearDescriptorWrites();
 
 	MVKSmallVector<VkWriteDescriptorSet, 1> _descriptorWrites;
-	MVKPipelineLayout* _pipelineLayout;
+	MVKPipelineLayout* _pipelineLayout = nullptr;
 	VkPipelineBindPoint _pipelineBindPoint;
 	uint32_t _set;
 };
@@ -272,7 +273,7 @@
 	MVKCommandTypePool<MVKCommand>* getTypePool(MVKCommandPool* cmdPool) override;
 
 	MVKDescriptorUpdateTemplate* _descUpdateTemplate;
-	MVKPipelineLayout* _pipelineLayout;
+	MVKPipelineLayout* _pipelineLayout = nullptr;
 	void* _pData = nullptr;
 	uint32_t _set;
 };
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.mm b/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.mm
index 4e12de5..00029a6 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.mm
@@ -193,10 +193,14 @@
 													   uint32_t firstSet,
 													   uint32_t setCount,
 													   const VkDescriptorSet* pDescriptorSets) {
+	if (_pipelineLayout) { _pipelineLayout->release(); }
+
 	_pipelineBindPoint = pipelineBindPoint;
 	_pipelineLayout = (MVKPipelineLayout*)layout;
 	_firstSet = firstSet;
 
+	_pipelineLayout->retain();
+
 	// Add the descriptor sets
 	_descriptorSets.clear();	// Clear for reuse
 	_descriptorSets.reserve(setCount);
@@ -212,6 +216,11 @@
 	_pipelineLayout->bindDescriptorSets(cmdEncoder, _descriptorSets.contents(), _firstSet, MVKArrayRef<uint32_t>());
 }
 
+template <size_t N>
+MVKCmdBindDescriptorSetsStatic<N>::~MVKCmdBindDescriptorSetsStatic() {
+	if (_pipelineLayout) { _pipelineLayout->release(); }
+}
+
 template class MVKCmdBindDescriptorSetsStatic<1>;
 template class MVKCmdBindDescriptorSetsStatic<4>;
 template class MVKCmdBindDescriptorSetsStatic<8>;
@@ -262,7 +271,6 @@
 											uint32_t offset,
 											uint32_t size,
 											const void* pValues) {
-	_pipelineLayout = (MVKPipelineLayout*)layout;
 	_stageFlags = stageFlags;
 	_offset = offset;
 
@@ -302,10 +310,14 @@
 											 uint32_t set,
 											 uint32_t descriptorWriteCount,
 											 const VkWriteDescriptorSet* pDescriptorWrites) {
+	if (_pipelineLayout) { _pipelineLayout->release(); }
+
 	_pipelineBindPoint = pipelineBindPoint;
 	_pipelineLayout = (MVKPipelineLayout*)layout;
 	_set = set;
 
+	_pipelineLayout->retain();
+
 	// Add the descriptor writes
 	MVKDevice* mvkDvc = cmdBuff->getDevice();
 	clearDescriptorWrites();	// Clear for reuse
@@ -360,6 +372,7 @@
 
 MVKCmdPushDescriptorSet::~MVKCmdPushDescriptorSet() {
 	clearDescriptorWrites();
+	if (_pipelineLayout) { _pipelineLayout->release(); }
 }
 
 void MVKCmdPushDescriptorSet::clearDescriptorWrites() {
@@ -393,9 +406,14 @@
 														 VkPipelineLayout layout,
 														 uint32_t set,
 														 const void* pData) {
+	if (_pipelineLayout) { _pipelineLayout->release(); }
+
 	_descUpdateTemplate = (MVKDescriptorUpdateTemplate*)descUpdateTemplate;
 	_pipelineLayout = (MVKPipelineLayout*)layout;
 	_set = set;
+
+	_pipelineLayout->retain();
+
 	if (_pData) delete[] (char*)_pData;
 	// Work out how big the memory block in pData is.
 	const VkDescriptorUpdateTemplateEntryKHR* pEntry =
@@ -443,6 +461,7 @@
 }
 
 MVKCmdPushDescriptorSetWithTemplate::~MVKCmdPushDescriptorSetWithTemplate() {
+	if (_pipelineLayout) { _pipelineLayout->release(); }
 	if (_pData) delete[] (char*)_pData;
 }
 
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.h b/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.h
index 4bc8b11..849d51c 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.h
@@ -125,9 +125,8 @@
 
 /** Describes Metal texture resolve parameters. */
 typedef struct {
-    VkImageCopy* copyRegion;
-    uint32_t level;
-    uint32_t slice;
+    VkImageSubresource srcSubresource;
+    VkImageSubresource dstSubresource;
 } MVKMetalResolveSlice;
 
 /**
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm b/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm
index f494748..15e4b1f 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm
@@ -77,11 +77,6 @@
 		_vkImageCopies.push_back(vkIR);
 	}
     
-    // Validate
-    if ((_srcImage->getMTLTextureType() == MTLTextureType3D) != (_dstImage->getMTLTextureType() == MTLTextureType3D)) {
-        return cmdBuff->reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdCopyImage(): Metal does not support copying to or from slices of a 3D texture.");
-    }
-
 	return VK_SUCCESS;
 }
 
@@ -160,25 +155,62 @@
             // If copies can be performed using direct texture-texture copying, do so
             uint32_t srcLevel = vkIC.srcSubresource.mipLevel;
             MTLOrigin srcOrigin = mvkMTLOriginFromVkOffset3D(vkIC.srcOffset);
-            MTLSize srcSize = mvkClampMTLSize(mvkMTLSizeFromVkExtent3D(vkIC.extent),
-                                              srcOrigin,
-                                              mvkMTLSizeFromVkExtent3D(_srcImage->getExtent3D(srcPlaneIndex, srcLevel)));
+            MTLSize srcSize;
+            uint32_t layCnt;
+            if ((_srcImage->getMTLTextureType() == MTLTextureType3D) != (_dstImage->getMTLTextureType() == MTLTextureType3D)) {
+                // In the case, the number of layers to copy is in extent.depth. Use that value,
+                // then clamp the depth so we don't try to copy more than Metal will allow.
+                layCnt = vkIC.extent.depth;
+                srcSize = mvkClampMTLSize(mvkMTLSizeFromVkExtent3D(vkIC.extent),
+                                          srcOrigin,
+                                          mvkMTLSizeFromVkExtent3D(_srcImage->getExtent3D(srcPlaneIndex, srcLevel)));
+                srcSize.depth = 1;
+            } else {
+                layCnt = vkIC.srcSubresource.layerCount;
+                srcSize = mvkClampMTLSize(mvkMTLSizeFromVkExtent3D(vkIC.extent),
+                                          srcOrigin,
+                                          mvkMTLSizeFromVkExtent3D(_srcImage->getExtent3D(srcPlaneIndex, srcLevel)));
+            }
             uint32_t dstLevel = vkIC.dstSubresource.mipLevel;
             MTLOrigin dstOrigin = mvkMTLOriginFromVkOffset3D(vkIC.dstOffset);
             uint32_t srcBaseLayer = vkIC.srcSubresource.baseArrayLayer;
             uint32_t dstBaseLayer = vkIC.dstSubresource.baseArrayLayer;
-            uint32_t layCnt = vkIC.srcSubresource.layerCount;
-
+            
             for (uint32_t layIdx = 0; layIdx < layCnt; layIdx++) {
-                [mtlBlitEnc copyFromTexture: srcMTLTex
-                                sourceSlice: srcBaseLayer + layIdx
-                                sourceLevel: srcLevel
-                               sourceOrigin: srcOrigin
-                                 sourceSize: srcSize
-                                  toTexture: dstMTLTex
-                           destinationSlice: dstBaseLayer + layIdx
-                           destinationLevel: dstLevel
-                          destinationOrigin: dstOrigin];
+                // We can copy between a 3D and a 2D image easily. Just copy between
+                // one slice of the 2D image and one plane of the 3D image at a time.
+                if ((_srcImage->getMTLTextureType() == MTLTextureType3D) == (_dstImage->getMTLTextureType() == MTLTextureType3D)) {
+                    [mtlBlitEnc copyFromTexture: srcMTLTex
+                                    sourceSlice: srcBaseLayer + layIdx
+                                    sourceLevel: srcLevel
+                                   sourceOrigin: srcOrigin
+                                     sourceSize: srcSize
+                                      toTexture: dstMTLTex
+                               destinationSlice: dstBaseLayer + layIdx
+                               destinationLevel: dstLevel
+                              destinationOrigin: dstOrigin];
+                } else if (_srcImage->getMTLTextureType() == MTLTextureType3D) {
+                    [mtlBlitEnc copyFromTexture: srcMTLTex
+                                    sourceSlice: srcBaseLayer
+                                    sourceLevel: srcLevel
+                                   sourceOrigin: MTLOriginMake(srcOrigin.x, srcOrigin.y, srcOrigin.z + layIdx)
+                                     sourceSize: srcSize
+                                      toTexture: dstMTLTex
+                               destinationSlice: dstBaseLayer + layIdx
+                               destinationLevel: dstLevel
+                              destinationOrigin: dstOrigin];
+                } else {
+                    assert(_dstImage->getMTLTextureType() == MTLTextureType3D);
+                    [mtlBlitEnc copyFromTexture: srcMTLTex
+                                    sourceSlice: srcBaseLayer + layIdx
+                                    sourceLevel: srcLevel
+                                   sourceOrigin: srcOrigin
+                                     sourceSize: srcSize
+                                      toTexture: dstMTLTex
+                               destinationSlice: dstBaseLayer
+                               destinationLevel: dstLevel
+                              destinationOrigin: MTLOriginMake(dstOrigin.x, dstOrigin.y, dstOrigin.z + layIdx)];
+                }
             }
         }
     }
@@ -396,6 +428,18 @@
             MVKRPSKeyBlitImg blitKey;
             blitKey.srcMTLPixelFormat = _srcImage->getMTLPixelFormat(srcPlaneIndex);
             blitKey.srcMTLTextureType = _srcImage->getMTLTextureType();
+            if (blitKey.srcMTLTextureType == MTLTextureTypeCube || blitKey.srcMTLTextureType == MTLTextureTypeCubeArray) {
+                // In this case, I'll use a temp 2D array view. That way, I don't have to
+                // deal with mapping the blit coordinates to a cube direction vector.
+                blitKey.srcMTLTextureType = MTLTextureType2DArray;
+                srcMTLTex = [srcMTLTex newTextureViewWithPixelFormat: (MTLPixelFormat)blitKey.srcMTLPixelFormat
+                                                         textureType: MTLTextureType2DArray
+                                                              levels: NSMakeRange(0, srcMTLTex.mipmapLevelCount)
+                                                              slices: NSMakeRange(0, srcMTLTex.arrayLength)];
+                [cmdEncoder->_mtlCmdBuffer addCompletedHandler: ^(id<MTLCommandBuffer>) {
+                    [srcMTLTex release];
+                }];
+            }
             blitKey.dstMTLPixelFormat = _dstImage->getMTLPixelFormat(dstPlaneIndex);
             blitKey.srcFilter = mvkMTLSamplerMinMagFilterFromVkFilter(_filter);
             blitKey.dstSampleCount = mvkSampleCountFromVkSampleCountFlagBits(_dstImage->getSampleCount());
@@ -406,12 +450,31 @@
             mtlColorAttDesc.level = mvkIBR.region.dstSubresource.mipLevel;
 
             uint32_t layCnt = mvkIBR.region.srcSubresource.layerCount;
+            if (_dstImage->getMTLTextureType() == MTLTextureType3D) {
+                layCnt = mvkAbsDiff(mvkIBR.region.dstOffsets[1].z, mvkIBR.region.dstOffsets[0].z);
+            }
             for (uint32_t layIdx = 0; layIdx < layCnt; layIdx++) {
                 // Update the render pass descriptor for the texture level and slice, and create a render encoder.
-                mtlColorAttDesc.slice = mvkIBR.region.dstSubresource.baseArrayLayer + layIdx;
+                if (_dstImage->getMTLTextureType() == MTLTextureType3D) {
+                    mtlColorAttDesc.depthPlane = mvkIBR.region.dstOffsets[0].z + (mvkIBR.region.dstOffsets[1].z > mvkIBR.region.dstOffsets[0].z ? layIdx : -(layIdx + 1));
+                } else {
+                    mtlColorAttDesc.slice = mvkIBR.region.dstSubresource.baseArrayLayer + layIdx;
+                }
                 id<MTLRenderCommandEncoder> mtlRendEnc = [cmdEncoder->_mtlCmdBuffer renderCommandEncoderWithDescriptor: mtlRPD];
                 setLabelIfNotNil(mtlRendEnc, mvkMTLRenderCommandEncoderLabel(commandUse));
 
+                if (blitKey.srcMTLTextureType == MTLTextureType3D) {
+                    // In this case, I need to interpolate along the third dimension manually.
+                    VkExtent3D srcExtent = _srcImage->getExtent3D(srcPlaneIndex, mvkIBR.region.dstSubresource.mipLevel);
+                    VkOffset3D so0 = mvkIBR.region.srcOffsets[0], so1 = mvkIBR.region.srcOffsets[1];
+                    VkOffset3D do0 = mvkIBR.region.dstOffsets[0], do1 = mvkIBR.region.dstOffsets[1];
+                    CGFloat startZ = (CGFloat)so0.z / (CGFloat)srcExtent.depth;
+                    CGFloat endZ = (CGFloat)so1.z / (CGFloat)srcExtent.depth;
+                    CGFloat zIncr = (endZ - startZ) / mvkAbsDiff(do1.z, do0.z);
+                    for (uint32_t i = 0; i < kMVKBlitVertexCount; ++i) {
+                        mvkIBR.vertices[i].texCoord.z = startZ + layIdx * zIncr;
+                    }
+                }
                 [mtlRendEnc pushDebugGroup: @"vkCmdBlitImage"];
                 [mtlRendEnc setRenderPipelineState: mtlRPS];
                 cmdEncoder->setVertexBytes(mtlRendEnc, mvkIBR.vertices, sizeof(mvkIBR.vertices), vtxBuffIdx);
@@ -490,68 +553,75 @@
         uint8_t srcPlaneIndex = MVKImage::getPlaneFromVkImageAspectFlags(vkIR.srcSubresource.aspectMask);
         uint8_t dstPlaneIndex = MVKImage::getPlaneFromVkImageAspectFlags(vkIR.dstSubresource.aspectMask);
 
-		uint32_t mipLvl = vkIR.dstSubresource.mipLevel;
-		VkExtent3D srcImgExt = _srcImage->getExtent3D(srcPlaneIndex, mipLvl);
-		VkExtent3D dstImgExt = _dstImage->getExtent3D(dstPlaneIndex, mipLvl);
+		VkExtent3D srcImgExt = _srcImage->getExtent3D(srcPlaneIndex, vkIR.srcSubresource.mipLevel);
+		VkExtent3D dstImgExt = _dstImage->getExtent3D(dstPlaneIndex, vkIR.dstSubresource.mipLevel);
 
-		// If the region does not cover the entire content of the source level, expand the
-		// destination content in the region to the temporary image. The purpose of this
+		// If the region does not cover the entire content of the destination level, expand
+		// the destination content in the region to the temporary image. The purpose of this
 		// expansion is to render the existing content of the destination image to the
 		// temporary transfer multisample image, so that regions of that temporary transfer
 		// image can then be overwritten with content from the source image, prior to
-		// resolving it back to the destination image. The source of this temporary content
-		// move is the full extent of the DESTINATION image of the resolve command, and the
-		// destination of this temporary content move is the full extent of the SOURCE image.
-		if ( !mvkVkExtent3DsAreEqual(srcImgExt, vkIR.extent) ) {
+		// resolving it back to the destination image.
+		if ( !mvkVkExtent3DsAreEqual(dstImgExt, vkIR.extent) ) {
 			VkImageBlit& expRgn = expansionRegions[expCnt++];
 			expRgn.srcSubresource = vkIR.dstSubresource;
 			expRgn.srcOffsets[0] = { 0, 0, 0 };
 			expRgn.srcOffsets[1] = { int32_t(dstImgExt.width), int32_t(dstImgExt.height), int32_t(dstImgExt.depth) };
 			expRgn.dstSubresource = vkIR.dstSubresource;
 			expRgn.dstOffsets[0] = { 0, 0, 0 };
-			expRgn.dstOffsets[1] = { int32_t(srcImgExt.width), int32_t(srcImgExt.height), int32_t(srcImgExt.depth) };
+			expRgn.dstOffsets[1] = { int32_t(dstImgExt.width), int32_t(dstImgExt.height), int32_t(dstImgExt.depth) };
 		}
 
 		// Copy the region from the source image to the temporary multisample image,
 		// prior to the temporary image being resolved back to the destination image.
 		// The source of this copy stage is the source image, and the destination of
 		// this copy stage is the temporary transfer image.
-		VkImageCopy& cpyRgn = copyRegions[copyCnt++];
-		cpyRgn.srcSubresource = vkIR.srcSubresource;
-		cpyRgn.srcOffset = vkIR.srcOffset;
-		cpyRgn.dstSubresource = vkIR.srcSubresource;
-		cpyRgn.dstOffset = vkIR.srcOffset;
-		cpyRgn.extent = vkIR.extent;
+		bool needXfrImage = !mvkVkExtent3DsAreEqual(srcImgExt, vkIR.extent) || !mvkVkExtent3DsAreEqual(dstImgExt, vkIR.extent);
+		if ( needXfrImage ) {
+			VkImageCopy& cpyRgn = copyRegions[copyCnt++];
+			cpyRgn.srcSubresource = vkIR.srcSubresource;
+			cpyRgn.srcOffset = vkIR.srcOffset;
+			cpyRgn.dstSubresource = vkIR.dstSubresource;
+			cpyRgn.dstOffset = vkIR.dstOffset;
+			cpyRgn.extent = vkIR.extent;
+		}
 
 		// Adds a resolve slice struct for each destination layer in the resolve region.
-		uint32_t baseLayer = vkIR.dstSubresource.baseArrayLayer;
+		// Note that the source subresource for this is that of the SOURCE image if we're doing a
+		// direct resolve, but that of the DESTINATION if we need a temporary transfer image.
 		uint32_t layCnt = vkIR.dstSubresource.layerCount;
 		for (uint32_t layIdx = 0; layIdx < layCnt; layIdx++) {
 			MVKMetalResolveSlice& rslvSlice = mtlResolveSlices[sliceCnt++];
-            rslvSlice.copyRegion = &cpyRgn;
-			rslvSlice.level = vkIR.dstSubresource.mipLevel;
-			rslvSlice.slice = baseLayer + layIdx;
+			rslvSlice.dstSubresource.aspectMask = vkIR.dstSubresource.aspectMask;
+			rslvSlice.dstSubresource.mipLevel = vkIR.dstSubresource.mipLevel;
+			rslvSlice.dstSubresource.arrayLayer = vkIR.dstSubresource.baseArrayLayer + layIdx;
+			rslvSlice.srcSubresource.aspectMask = needXfrImage ? vkIR.dstSubresource.aspectMask : vkIR.srcSubresource.aspectMask;
+			rslvSlice.srcSubresource.mipLevel = needXfrImage ? vkIR.dstSubresource.mipLevel : vkIR.srcSubresource.mipLevel;
+			rslvSlice.srcSubresource.arrayLayer = needXfrImage ? vkIR.dstSubresource.baseArrayLayer : vkIR.srcSubresource.baseArrayLayer;
+			rslvSlice.srcSubresource.arrayLayer += layIdx;
 		}
 	}
 
     // Expansion and copying is not required. Each mip level of the source image
     // is being resolved entirely. Resolve directly from the source image.
     MVKImage* xfrImage = _srcImage;
-	if (expCnt) {
-		// Expansion and copying is required. Acquire a temporary transfer image, expand
-		// the destination image into it, copy from the source image to the temporary image,
-		// and then resolve from the temporary image to the destination image.
+	if (copyCnt) {
+		// Expansion and/or copying is required. Acquire a temporary transfer image, expand
+		// the destination image into it if necessary, copy from the source image to the
+		// temporary image, and then resolve from the temporary image to the destination image.
 		MVKImageDescriptorData xferImageData;
 		_dstImage->getTransferDescriptorData(xferImageData);
 		xferImageData.samples = _srcImage->getSampleCount();
 		xfrImage = cmdEncoder->getCommandEncodingPool()->getTransferMVKImage(xferImageData);
 
-		// Expand the current content of the destination image to the temporary transfer image.
-		MVKCmdBlitImage<N> expCmd;
-		expCmd.setContent(cmdEncoder->_cmdBuffer,
-						  (VkImage)_dstImage, _dstLayout, (VkImage)xfrImage, _dstLayout,
-						  expCnt, expansionRegions, VK_FILTER_LINEAR);
-		expCmd.encode(cmdEncoder, kMVKCommandUseResolveExpandImage);
+		if (expCnt) {
+			// Expand the current content of the destination image to the temporary transfer image.
+			MVKCmdBlitImage<N> expCmd;
+			expCmd.setContent(cmdEncoder->_cmdBuffer,
+							  (VkImage)_dstImage, _dstLayout, (VkImage)xfrImage, _dstLayout,
+							  expCnt, expansionRegions, VK_FILTER_LINEAR);
+			expCmd.encode(cmdEncoder, kMVKCommandUseResolveExpandImage);
+		}
 
 		// Copy the resolve regions of the source image to the temporary transfer image.
 		MVKCmdCopyImage<N> copyCmd;
@@ -573,15 +643,15 @@
 	// the texture level and slice and create a render encoder.
 	for (uint32_t sIdx = 0; sIdx < sliceCnt; sIdx++) {
 		MVKMetalResolveSlice& rslvSlice = mtlResolveSlices[sIdx];
-        uint8_t srcPlaneIndex = MVKImage::getPlaneFromVkImageAspectFlags(rslvSlice.copyRegion->srcSubresource.aspectMask);
-        uint8_t dstPlaneIndex = MVKImage::getPlaneFromVkImageAspectFlags(rslvSlice.copyRegion->dstSubresource.aspectMask);
+        uint8_t srcPlaneIndex = MVKImage::getPlaneFromVkImageAspectFlags(rslvSlice.srcSubresource.aspectMask);
+        uint8_t dstPlaneIndex = MVKImage::getPlaneFromVkImageAspectFlags(rslvSlice.dstSubresource.aspectMask);
 
         mtlColorAttDesc.texture = xfrImage->getMTLTexture(srcPlaneIndex);
         mtlColorAttDesc.resolveTexture = _dstImage->getMTLTexture(dstPlaneIndex);
-		mtlColorAttDesc.level = rslvSlice.level;
-		mtlColorAttDesc.slice = rslvSlice.slice;
-		mtlColorAttDesc.resolveLevel = rslvSlice.level;
-		mtlColorAttDesc.resolveSlice = rslvSlice.slice;
+		mtlColorAttDesc.level = rslvSlice.srcSubresource.mipLevel;
+		mtlColorAttDesc.slice = rslvSlice.srcSubresource.arrayLayer;
+		mtlColorAttDesc.resolveLevel = rslvSlice.dstSubresource.mipLevel;
+		mtlColorAttDesc.resolveSlice = rslvSlice.dstSubresource.arrayLayer;
 		id<MTLRenderCommandEncoder> mtlRendEnc = [cmdEncoder->_mtlCmdBuffer renderCommandEncoderWithDescriptor: mtlRPD];
 		setLabelIfNotNil(mtlRendEnc, mvkMTLRenderCommandEncoderLabel(kMVKCommandUseResolveImage));
 
@@ -1159,8 +1229,8 @@
 
         // Validate
         MVKMTLFmtCaps mtlFmtCaps = cmdBuff->getPixelFormats()->getCapabilities(_image->getMTLPixelFormat(planeIndex));
-        if ((isDS && !mvkAreAllFlagsEnabled(mtlFmtCaps, kMVKMTLFmtCapsDSAtt)) ||
-            ( !isDS && !mvkAreAllFlagsEnabled(mtlFmtCaps, kMVKMTLFmtCapsColorAtt))) {
+		uint32_t reqCap = isDS ? kMVKMTLFmtCapsDSAtt : (_image->getIsLinear() ? kMVKMTLFmtCapsWrite : kMVKMTLFmtCapsColorAtt);
+        if (!mvkAreAllFlagsEnabled(mtlFmtCaps, reqCap)) {
             return cmdBuff->reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdClear%sImage(): Format %s cannot be cleared on this device.", (isDS ? "DepthStencil" : "Color"), cmdBuff->getPixelFormats()->getName(_image->getVkFormat()));
         }
         
@@ -1186,9 +1256,35 @@
 
 	MVKPixelFormats* pixFmts = cmdEncoder->getPixelFormats();
 	for (auto& srRange : _subresourceRanges) {
-        id<MTLTexture> imgMTLTex = _image->getMTLTexture(MVKImage::getPlaneFromVkImageAspectFlags(srRange.aspectMask));
+		uint8_t planeIndex = MVKImage::getPlaneFromVkImageAspectFlags(srRange.aspectMask);
+        id<MTLTexture> imgMTLTex = _image->getMTLTexture(planeIndex);
         if ( !imgMTLTex ) { continue; }
 
+#if MVK_MACOS
+        if ( _image->getIsLinear() ) {
+            // These images cannot be rendered. Instead, use a compute shader.
+            // Luckily for us, linear images only have one mip and one array layer under Metal.
+            assert( !isDS );
+            id<MTLComputePipelineState> mtlClearState = cmdEncoder->getCommandEncodingPool()->getCmdClearColorImageMTLComputePipelineState(pixFmts->getFormatType(_image->getVkFormat()));
+            id<MTLComputeCommandEncoder> mtlComputeEnc = cmdEncoder->getMTLComputeEncoder(kMVKCommandUseClearColorImage);
+            [mtlComputeEnc pushDebugGroup: @"vkCmdClearColorImage"];
+            [mtlComputeEnc setComputePipelineState: mtlClearState];
+            [mtlComputeEnc setTexture: imgMTLTex atIndex: 0];
+            cmdEncoder->setComputeBytes(mtlComputeEnc, &_clearValue, sizeof(_clearValue), 0);
+            MTLSize gridSize = mvkMTLSizeFromVkExtent3D(_image->getExtent3D());
+            MTLSize tgSize = MTLSizeMake(mtlClearState.threadExecutionWidth, 1, 1);
+            if (cmdEncoder->getDevice()->_pMetalFeatures->nonUniformThreadgroups) {
+                [mtlComputeEnc dispatchThreads: gridSize threadsPerThreadgroup: tgSize];
+            } else {
+                MTLSize tgCount = MTLSizeMake(gridSize.width / tgSize.width, gridSize.height, gridSize.depth);
+                if (gridSize.width % tgSize.width) { tgCount.width += 1; }
+                [mtlComputeEnc dispatchThreadgroups: tgCount threadsPerThreadgroup: tgSize];
+            }
+            [mtlComputeEnc popDebugGroup];
+            continue;
+        }
+#endif
+
 		MTLRenderPassDescriptor* mtlRPDesc = [MTLRenderPassDescriptor renderPassDescriptor];
 		MTLRenderPassColorAttachmentDescriptor* mtlRPCADesc = nil;
 		MTLRenderPassDepthAttachmentDescriptor* mtlRPDADesc = nil;
@@ -1230,7 +1326,8 @@
                               : (mipLvlStart + mipLvlCnt));
 
         // Extract the cube or array layers (slices) that are to be updated
-        uint32_t layerStart = srRange.baseArrayLayer;
+		bool is3D = _image->getMTLTextureType() == MTLTextureType3D;
+        uint32_t layerStart = is3D ? 0 : srRange.baseArrayLayer;
         uint32_t layerCnt = srRange.layerCount;
         uint32_t layerEnd = (layerCnt == VK_REMAINING_ARRAY_LAYERS
                              ? _image->getLayerCount()
@@ -1242,10 +1339,22 @@
 			mtlRPDADesc.level = mipLvl;
 			mtlRPSADesc.level = mipLvl;
 
+			// If a 3D image, we need to get the depth for each level.
+			if (is3D) {
+				layerCnt = _image->getExtent3D(planeIndex, mipLvl).depth;
+				layerEnd = layerStart + layerCnt;
+			}
+
             for (uint32_t layer = layerStart; layer < layerEnd; layer++) {
-                mtlRPCADesc.slice = layer;
-				mtlRPDADesc.slice = layer;
-				mtlRPSADesc.slice = layer;
+                if (is3D) {
+                    mtlRPCADesc.depthPlane = layer;
+                    mtlRPDADesc.depthPlane = layer;
+                    mtlRPSADesc.depthPlane = layer;
+                } else {
+                    mtlRPCADesc.slice = layer;
+                    mtlRPDADesc.slice = layer;
+                    mtlRPSADesc.slice = layer;
+                }
 
                 id<MTLRenderCommandEncoder> mtlRendEnc = [cmdEncoder->_mtlCmdBuffer renderCommandEncoderWithDescriptor: mtlRPDesc];
 				setLabelIfNotNil(mtlRendEnc, mtlRendEncName);
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm b/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm
index 24b65a4..0889577 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm
@@ -752,6 +752,7 @@
         case kMVKCommandUseCopyBufferToImage:   return @"vkCmdCopyBufferToImage ComputeEncoder";
         case kMVKCommandUseCopyImageToBuffer:   return @"vkCmdCopyImageToBuffer ComputeEncoder";
         case kMVKCommandUseFillBuffer:          return @"vkCmdFillBuffer ComputeEncoder";
+        case kMVKCommandUseClearColorImage:     return @"vkCmdClearColorImage ComputeEncoder";
         case kMVKCommandUseTessellationVertexTessCtl: return @"vkCmdDraw (vertex and tess control stages) ComputeEncoder";
         case kMVKCommandUseMultiviewInstanceCountAdjust: return @"vkCmdDraw (multiview instance count adjustment) ComputeEncoder";
         case kMVKCommandUseCopyQueryPoolResults:return @"vkCmdCopyQueryPoolResults ComputeEncoder";
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.h b/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.h
index 52e4704..eac9f3f 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.h
@@ -109,6 +109,11 @@
 	/** Returns a MTLComputePipelineState for filling a buffer. */
 	id<MTLComputePipelineState> getCmdFillBufferMTLComputePipelineState();
 
+#if MVK_MACOS
+	/** Returns a MTLComputePipelineState for clearing an image. Currently only used for 2D linear images on Mac. */
+	id<MTLComputePipelineState> getCmdClearColorImageMTLComputePipelineState(MVKFormatType type);
+#endif
+
 	/** Returns a MTLComputePipelineState for decompressing a buffer into a 3D image. */
 	id<MTLComputePipelineState> getCmdCopyBufferToImage3DDecompressMTLComputePipelineState(bool needsTempBuff);
 
@@ -151,6 +156,9 @@
     id<MTLDepthStencilState> _cmdClearDefaultDepthStencilState = nil;
     id<MTLComputePipelineState> _mtlCopyBufferBytesComputePipelineState = nil;
 	id<MTLComputePipelineState> _mtlFillBufferComputePipelineState = nil;
+#if MVK_MACOS
+	id<MTLComputePipelineState> _mtlClearColorImageComputePipelineState[3] = {nil, nil, nil};
+#endif
 	id<MTLComputePipelineState> _mtlCopyBufferToImage3DDecompressComputePipelineState[2] = {nil, nil};
 	id<MTLComputePipelineState> _mtlDrawIndirectMultiviewConvertBuffersComputePipelineState[2] = {nil, nil};
 	id<MTLComputePipelineState> _mtlDrawIndirectTessConvertBuffersComputePipelineState[2] = {nil, nil};
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.mm b/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.mm
index da0e661..b1a5a26 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.mm
@@ -102,6 +102,30 @@
 	MVK_ENC_REZ_ACCESS(_mtlFillBufferComputePipelineState, newCmdFillBufferMTLComputePipelineState(_commandPool));
 }
 
+#if MVK_MACOS
+static inline uint32_t getClearStateIndex(MVKFormatType type) {
+	switch (type) {
+		case kMVKFormatColorHalf:
+		case kMVKFormatColorFloat:
+			return 0;
+		case kMVKFormatColorInt8:
+		case kMVKFormatColorInt16:
+		case kMVKFormatColorInt32:
+			return 1;
+		case kMVKFormatColorUInt8:
+		case kMVKFormatColorUInt16:
+		case kMVKFormatColorUInt32:
+			return 2;
+		default:
+			return 0;
+	}
+}
+
+id<MTLComputePipelineState> MVKCommandEncodingPool::getCmdClearColorImageMTLComputePipelineState(MVKFormatType type) {
+	MVK_ENC_REZ_ACCESS(_mtlClearColorImageComputePipelineState[getClearStateIndex(type)], newCmdClearColorImageMTLComputePipelineState(type, _commandPool));
+}
+#endif
+
 id<MTLComputePipelineState> MVKCommandEncodingPool::getCmdCopyBufferToImage3DDecompressMTLComputePipelineState(bool needsTempBuff) {
 	MVK_ENC_REZ_ACCESS(_mtlCopyBufferToImage3DDecompressComputePipelineState[needsTempBuff ? 1 : 0], newCmdCopyBufferToImage3DDecompressMTLComputePipelineState(needsTempBuff, _commandPool));
 }
@@ -178,6 +202,15 @@
     [_mtlFillBufferComputePipelineState release];
     _mtlFillBufferComputePipelineState = nil;
 
+#if MVK_MACOS
+    [_mtlClearColorImageComputePipelineState[0] release];
+    [_mtlClearColorImageComputePipelineState[1] release];
+    [_mtlClearColorImageComputePipelineState[2] release];
+    _mtlClearColorImageComputePipelineState[0] = nil;
+    _mtlClearColorImageComputePipelineState[1] = nil;
+    _mtlClearColorImageComputePipelineState[2] = nil;
+#endif
+
     [_mtlCopyBufferToImage3DDecompressComputePipelineState[0] release];
     [_mtlCopyBufferToImage3DDecompressComputePipelineState[1] release];
     _mtlCopyBufferToImage3DDecompressComputePipelineState[0] = nil;
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandPipelineStateFactoryShaderSource.h b/MoltenVK/MoltenVK/Commands/MVKCommandPipelineStateFactoryShaderSource.h
index 124f6d9..d562183 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandPipelineStateFactoryShaderSource.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandPipelineStateFactoryShaderSource.h
@@ -29,12 +29,12 @@
                                                                                                                 \n\
 typedef struct {                                                                                                \n\
     float2 a_position [[attribute(0)]];                                                                         \n\
-    float2 a_texCoord [[attribute(1)]];                                                                         \n\
+    float3 a_texCoord [[attribute(1)]];                                                                         \n\
 } AttributesPosTex;                                                                                             \n\
                                                                                                                 \n\
 typedef struct {                                                                                                \n\
     float4 v_position [[position]];                                                                             \n\
-    float2 v_texCoord;                                                                                          \n\
+    float3 v_texCoord;                                                                                          \n\
 } VaryingsPosTex;                                                                                               \n\
                                                                                                                 \n\
 typedef size_t VkDeviceSize;                                                                                    \n\
@@ -91,13 +91,31 @@
     for (size_t i = 0; i < info.size; i++) {                                                                    \n\
         dst[i + info.dstOffset] = src[i + info.srcOffset];                                                      \n\
     }                                                                                                           \n\
-};                                                                                                              \n\
+}                                                                                                               \n\
                                                                                                                 \n\
 kernel void cmdFillBuffer(device uint32_t* dst [[ buffer(0) ]],                                                 \n\
                           constant uint32_t& fillValue [[ buffer(1) ]],                                         \n\
                           uint pos [[thread_position_in_grid]]) {                                               \n\
     dst[pos] = fillValue;                                                                                       \n\
-};                                                                                                              \n\
+}                                                                                                               \n\
+                                                                                                                \n\
+kernel void cmdClearColorImage2DFloat(texture2d<float, access::write> dst [[ texture(0) ]],                     \n\
+                                      constant float4& clearValue [[ buffer(0) ]],                              \n\
+                                      uint2 pos [[thread_position_in_grid]]) {                                  \n\
+    dst.write(clearValue, pos);                                                                                 \n\
+}                                                                                                               \n\
+                                                                                                                \n\
+kernel void cmdClearColorImage2DUInt(texture2d<uint, access::write> dst [[ texture(0) ]],                       \n\
+                                     constant uint4& clearValue [[ buffer(0) ]],                                \n\
+                                     uint2 pos [[thread_position_in_grid]]) {                                   \n\
+    dst.write(clearValue, pos);                                                                                 \n\
+}                                                                                                               \n\
+                                                                                                                \n\
+kernel void cmdClearColorImage2DInt(texture2d<int, access::write> dst [[ texture(0) ]],                         \n\
+                                    constant int4& clearValue [[ buffer(0) ]],                                  \n\
+                                    uint2 pos [[thread_position_in_grid]]) {                                    \n\
+    dst.write(clearValue, pos);                                                                                 \n\
+}                                                                                                               \n\
                                                                                                                 \n\
 typedef struct {                                                                                                \n\
     uint32_t srcRowStride;                                                                                      \n\
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandPool.mm b/MoltenVK/MoltenVK/Commands/MVKCommandPool.mm
index fb20ebf..7e8d55a 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandPool.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandPool.mm
@@ -59,7 +59,10 @@
 
 		// Command buffers start out in a VK_NOT_READY config result
 		VkResult cbRslt = mvkCmdBuff->getConfigurationResult();
-		if (rslt == VK_SUCCESS && cbRslt != VK_NOT_READY) { rslt = cbRslt; }
+		if (cbRslt != VK_NOT_READY) {
+			if (rslt == VK_SUCCESS) { rslt = cbRslt; }
+			freeCommandBuffers(1, &pCmdBuffer[cbIdx]);
+		}
 	}
 	return rslt;
 }
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.h b/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.h
index 7672fed..105483f 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.h
@@ -58,6 +58,8 @@
 
 	inline MTLSamplerMinMagFilter getSrcMTLSamplerMinMagFilter() { return (MTLSamplerMinMagFilter)srcFilter; }
 
+	inline MTLTextureType getSrcMTLTextureType() { return (MTLTextureType)srcMTLTextureType; }
+
 	inline bool isSrcArrayType() {
 		return (srcMTLTextureType == MTLTextureType2DArray ||
 #if MVK_MACOS
@@ -417,6 +419,12 @@
 	/** Returns a new MTLComputePipelineState for filling a buffer. */
 	id<MTLComputePipelineState> newCmdFillBufferMTLComputePipelineState(MVKVulkanAPIDeviceObject* owner);
 
+#if MVK_MACOS
+	/** Returns a new MTLComputePipelineState for clearing an image. */
+	id<MTLComputePipelineState> newCmdClearColorImageMTLComputePipelineState(MVKFormatType type,
+																			 MVKVulkanAPIDeviceObject* owner);
+#endif
+
 	/** Returns a new MTLComputePipelineState for copying between a buffer holding compressed data and a 3D image. */
 	id<MTLComputePipelineState> newCmdCopyBufferToImage3DDecompressMTLComputePipelineState(bool needTempBuf,
 																						   MVKVulkanAPIDeviceObject* owner);
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.mm b/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.mm
index a616a64..51640ae 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.mm
@@ -57,14 +57,14 @@
     vaDesc.format = MTLVertexFormatFloat2;
     vaDesc.bufferIndex = vtxBuffIdx;
     vaDesc.offset = vtxStride;
-    vtxStride += sizeof(simd::float2);
+    vtxStride += sizeof(simd::float4);
 
     // Vertex texture coords
     vaDesc = vaDescArray[1];
-    vaDesc.format = MTLVertexFormatFloat2;
+    vaDesc.format = MTLVertexFormatFloat3;
     vaDesc.bufferIndex = vtxBuffIdx;
     vaDesc.offset = vtxStride;
-    vtxStride += sizeof(simd::float2);
+    vtxStride += sizeof(simd::float4);
 
     // Vertex attribute buffer.
     MTLVertexBufferLayoutDescriptorArray* vbDescArray = vtxDesc.layouts;
@@ -158,7 +158,34 @@
 
 		bool isArrayType = blitKey.isSrcArrayType();
 		bool isLinearFilter = (blitKey.getSrcMTLSamplerMinMagFilter() == MTLSamplerMinMagFilterLinear);
-		NSString* arraySuffix = isArrayType ? @"_array" : @"";
+		NSString* typeSuffix;
+		NSString* coordArg;
+		switch (blitKey.getSrcMTLTextureType()) {
+			case MTLTextureType1D:
+				typeSuffix = @"1d";
+				coordArg = @".x";
+				break;
+			case MTLTextureType1DArray:
+				typeSuffix = @"1d_array";
+				coordArg = @".x";
+				break;
+			case MTLTextureType2D:
+				typeSuffix = @"2d";
+				coordArg = @".xy";
+				break;
+			case MTLTextureType2DArray:
+				typeSuffix = @"2d_array";
+				coordArg = @".xy";
+				break;
+			case MTLTextureType3D:
+				typeSuffix = @"3d";
+				coordArg = @"";
+				break;
+			default:
+				typeSuffix = @"unsupported";
+				coordArg = @"";
+				break;
+		}
 		NSString* sliceArg = isArrayType ? @", subRez.slice" : @"";
 		NSString* srcFilter = isLinearFilter ? @"linear" : @"nearest";
 
@@ -168,7 +195,7 @@
 		[msl appendLineMVK];
 		[msl appendLineMVK: @"typedef struct {"];
 		[msl appendLineMVK: @"    float4 v_position [[position]];"];
-		[msl appendLineMVK: @"    float2 v_texCoord;"];
+		[msl appendLineMVK: @"    float3 v_texCoord;"];
 		[msl appendLineMVK: @"} VaryingsPosTex;"];
 		[msl appendLineMVK];
 		[msl appendLineMVK: @"typedef struct {"];
@@ -183,10 +210,10 @@
 		NSString* funcName = @"fragCmdBlitImage";
 		[msl appendFormat: @"fragment %@4 %@(VaryingsPosTex varyings [[stage_in]],", typeStr, funcName];
 		[msl appendLineMVK];
-		[msl appendFormat: @"                         texture2d%@<%@> tex [[texture(0)]],", arraySuffix, typeStr];
+		[msl appendFormat: @"                         texture%@<%@> tex [[texture(0)]],", typeSuffix, typeStr];
 		[msl appendLineMVK];
 		[msl appendLineMVK: @"                         constant TexSubrez& subRez [[buffer(0)]]) {"];
-		[msl appendFormat: @"    return tex.sample(ce_sampler, varyings.v_texCoord%@, level(subRez.lod));", sliceArg];
+		[msl appendFormat: @"    return tex.sample(ce_sampler, varyings.v_texCoord%@%@, level(subRez.lod));", coordArg, sliceArg];
 		[msl appendLineMVK];
 		[msl appendLineMVK: @"}"];
 
@@ -410,6 +437,34 @@
 	return newMTLComputePipelineState("cmdFillBuffer", owner);
 }
 
+#if MVK_MACOS
+id<MTLComputePipelineState> MVKCommandResourceFactory::newCmdClearColorImageMTLComputePipelineState(MVKFormatType type,
+					MVKVulkanAPIDeviceObject* owner) {
+	const char* funcName;
+	switch (type) {
+		case kMVKFormatColorHalf:
+		case kMVKFormatColorFloat:
+			funcName = "cmdClearColorImage2DFloat";
+			break;
+		case kMVKFormatColorInt8:
+		case kMVKFormatColorInt16:
+		case kMVKFormatColorInt32:
+			funcName = "cmdClearColorImage2DInt";
+			break;
+		case kMVKFormatColorUInt8:
+		case kMVKFormatColorUInt16:
+		case kMVKFormatColorUInt32:
+			funcName = "cmdClearColorImage2DUInt";
+			break;
+		default:
+			owner->reportError(VK_ERROR_FORMAT_NOT_SUPPORTED,
+							   "Format type %u is not supported for clearing with a compute shader.", type);
+			return nil;
+	}
+	return newMTLComputePipelineState(funcName, owner);
+}
+#endif
+
 id<MTLComputePipelineState> MVKCommandResourceFactory::newCmdCopyBufferToImage3DDecompressMTLComputePipelineState(bool needTempBuf,
 																												  MVKVulkanAPIDeviceObject* owner) {
 	return newMTLComputePipelineState(needTempBuf
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.mm b/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.mm
index 92d39f2..de17272 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.mm
@@ -84,7 +84,7 @@
 
 #if MVK_MACOS
 	if (_deviceMemory) {
-		_isHostCoherentTexelBuffer = _deviceMemory->isMemoryHostCoherent() && mvkIsAnyFlagEnabled(_usage, VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT);
+		_isHostCoherentTexelBuffer = !_device->_pMetalFeatures->sharedLinearTextures && _deviceMemory->isMemoryHostCoherent() && mvkIsAnyFlagEnabled(_usage, VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT);
 	}
 #endif
 
@@ -272,7 +272,7 @@
         }
         id<MTLBuffer> mtlBuff;
         VkDeviceSize mtlBuffOffset;
-        if (MVK_MACOS && _buffer->isMemoryHostCoherent()) {
+        if ( !_device->_pMetalFeatures->sharedLinearTextures && _buffer->isMemoryHostCoherent() ) {
             mtlBuff = _buffer->getMTLBufferCache();
             mtlBuffOffset = _offset;
         } else {
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
index 23d74f5..e3013e1 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
@@ -362,7 +362,6 @@
 	void initPipelineCacheUUID();
 	uint32_t getHighestMTLFeatureSet();
 	uint64_t getMoltenVKGitRevision();
-	bool getImageViewIsSupported(const VkPhysicalDeviceImageFormatInfo2 *pImageFormatInfo);
 	void populate(VkPhysicalDeviceIDProperties* pDevIdProps);
 	void logGPUInfo();
 
@@ -678,7 +677,7 @@
 	const VkPhysicalDeviceScalarBlockLayoutFeaturesEXT _enabledScalarLayoutFeatures;
 	const VkPhysicalDeviceTexelBufferAlignmentFeaturesEXT _enabledTexelBuffAlignFeatures;
 	const VkPhysicalDeviceVertexAttributeDivisorFeaturesEXT _enabledVtxAttrDivFeatures;
-	const VkPhysicalDevicePortabilitySubsetFeaturesEXTX _enabledPortabilityFeatures;
+	const VkPhysicalDevicePortabilitySubsetFeaturesKHR _enabledPortabilityFeatures;
 
 	/** The list of Vulkan extensions, indicating whether each has been enabled by the app for this device. */
 	const MVKExtensionList _enabledExtensions;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
index 5aaf418..e485d39 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
@@ -164,13 +164,24 @@
 				divisorFeatures->vertexAttributeInstanceRateZeroDivisor = true;
 				break;
 			}
-			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_FEATURES_EXTX: {
-				auto* portabilityFeatures = (VkPhysicalDevicePortabilitySubsetFeaturesEXTX*)next;
-				portabilityFeatures->triangleFans = false;
-				portabilityFeatures->separateStencilMaskRef = true;
+			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_FEATURES_KHR: {
+				auto* portabilityFeatures = (VkPhysicalDevicePortabilitySubsetFeaturesKHR*)next;
+				portabilityFeatures->constantAlphaColorBlendFactors = true;
 				portabilityFeatures->events = true;
-				portabilityFeatures->standardImageViews = _mvkInstance->getMoltenVKConfiguration()->fullImageViewSwizzle || _metalFeatures.nativeTextureSwizzle;
+				portabilityFeatures->imageViewFormatReinterpretation = true;
+				portabilityFeatures->imageViewFormatSwizzle = (_metalFeatures.nativeTextureSwizzle ||
+															   _mvkInstance->getMoltenVKConfiguration()->fullImageViewSwizzle);
+				portabilityFeatures->imageView2DOn3DImage = false;
+				portabilityFeatures->multisampleArrayImage = _metalFeatures.multisampleArrayTextures;
+				portabilityFeatures->mutableComparisonSamplers = _metalFeatures.depthSampleCompare;
+				portabilityFeatures->pointPolygons = false;
 				portabilityFeatures->samplerMipLodBias = false;
+				portabilityFeatures->separateStencilMaskRef = true;
+				portabilityFeatures->shaderSampleRateInterpolationFunctions = false;
+				portabilityFeatures->tessellationIsolines = false;
+				portabilityFeatures->tessellationPointMode = false;
+				portabilityFeatures->triangleFans = false;
+				portabilityFeatures->vertexAttributeAccessBeyondStride = true;	// Costs additional buffers. Should make configuration switch.
 				break;
 			}
 			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_INTEGER_FUNCTIONS_2_FEATURES_INTEL: {
@@ -199,6 +210,23 @@
 	properties->properties = _properties;
 	for (auto* next = (VkBaseOutStructure*)properties->pNext; next; next = next->pNext) {
 		switch ((uint32_t)next->sType) {
+			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_STENCIL_RESOLVE_PROPERTIES: {
+				auto* depthStencilResolveProps = (VkPhysicalDeviceDepthStencilResolveProperties*)next;
+
+				// We can always support resolve from sample zero. Other modes require additional capabilities.
+				depthStencilResolveProps->supportedDepthResolveModes = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT;
+				if (_metalFeatures.depthResolve) {
+					depthStencilResolveProps->supportedDepthResolveModes |= VK_RESOLVE_MODE_MIN_BIT | VK_RESOLVE_MODE_MAX_BIT;
+				}
+				// Metal allows you to set the stencil resolve filter to either
+				// Sample0 or DepthResolvedSample--in other words, you can always use sample 0,
+				// but you can also use the sample chosen for depth resolve. This is impossible
+				// to express in Vulkan.
+				depthStencilResolveProps->supportedStencilResolveModes = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT;
+				depthStencilResolveProps->independentResolveNone = true;
+				depthStencilResolveProps->independentResolve = true;
+				break;
+			}
 			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES: {
 				auto* physicalDeviceDriverProps = (VkPhysicalDeviceDriverPropertiesKHR*)next;
 				strcpy(physicalDeviceDriverProps->driverName, "MoltenVK");
@@ -270,10 +298,10 @@
             case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INLINE_UNIFORM_BLOCK_PROPERTIES_EXT: {
 				auto* inlineUniformBlockProps = (VkPhysicalDeviceInlineUniformBlockPropertiesEXT*)next;
 				inlineUniformBlockProps->maxInlineUniformBlockSize = _metalFeatures.dynamicMTLBufferSize;
-                inlineUniformBlockProps->maxPerStageDescriptorInlineUniformBlocks = _properties.limits.maxPerStageDescriptorUniformBuffers;
-                inlineUniformBlockProps->maxPerStageDescriptorUpdateAfterBindInlineUniformBlocks = _properties.limits.maxPerStageDescriptorUniformBuffers;
-                inlineUniformBlockProps->maxDescriptorSetInlineUniformBlocks = _properties.limits.maxDescriptorSetUniformBuffers;
-                inlineUniformBlockProps->maxDescriptorSetUpdateAfterBindInlineUniformBlocks = _properties.limits.maxDescriptorSetUniformBuffers;
+                inlineUniformBlockProps->maxPerStageDescriptorInlineUniformBlocks = _metalFeatures.dynamicMTLBufferSize ? _metalFeatures.maxPerStageDynamicMTLBufferCount - 1 : 0;    // Less one for push constants
+                inlineUniformBlockProps->maxPerStageDescriptorUpdateAfterBindInlineUniformBlocks = inlineUniformBlockProps->maxPerStageDescriptorInlineUniformBlocks;
+                inlineUniformBlockProps->maxDescriptorSetInlineUniformBlocks = (inlineUniformBlockProps->maxPerStageDescriptorInlineUniformBlocks * 4);
+                inlineUniformBlockProps->maxDescriptorSetUpdateAfterBindInlineUniformBlocks = (inlineUniformBlockProps->maxPerStageDescriptorUpdateAfterBindInlineUniformBlocks * 4);
 				break;
 			}
 			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_PROPERTIES_EXT: {
@@ -298,8 +326,8 @@
 				divisorProps->maxVertexAttribDivisor = kMVKUndefinedLargeUInt32;
 				break;
 			}
-			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_PROPERTIES_EXTX: {
-				auto* portabilityProps = (VkPhysicalDevicePortabilitySubsetPropertiesEXTX*)next;
+			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_PROPERTIES_KHR: {
+				auto* portabilityProps = (VkPhysicalDevicePortabilitySubsetPropertiesKHR*)next;
 				portabilityProps->minVertexInputBindingStrideAlignment = (uint32_t)_metalFeatures.vertexStrideAlignment;
 				break;
 			}
@@ -384,6 +412,8 @@
 
 	if ( !pImageFormatProperties ) { return VK_SUCCESS; }
 
+	mvkClear(pImageFormatProperties);
+
 	// Metal does not support creating uncompressed views of compressed formats.
 	// Metal does not support split-instance images.
 	if (mvkIsAnyFlagEnabled(flags, VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT | VK_IMAGE_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT)) {
@@ -391,11 +421,19 @@
 	}
 
 	MVKFormatType mvkFmt = _pixelFormats.getFormatType(format);
+	bool isChromaSubsampled = _pixelFormats.getChromaSubsamplingPlaneCount(format) > 0;
+	bool isMultiPlanar = _pixelFormats.getChromaSubsamplingPlaneCount(format) > 1;
+	bool isBGRG = isChromaSubsampled && !isMultiPlanar && _pixelFormats.getBlockTexelSize(format).width > 1;
 	bool hasAttachmentUsage = mvkIsAnyFlagEnabled(usage, (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
 														  VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT |
 														  VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT |
 														  VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT));
 
+	// Disjoint memory requires a multiplanar format.
+	if (!isMultiPlanar && mvkIsAnyFlagEnabled(flags, VK_IMAGE_CREATE_DISJOINT_BIT)) {
+		return VK_ERROR_FORMAT_NOT_SUPPORTED;
+	}
+
 	VkPhysicalDeviceLimits* pLimits = &_properties.limits;
 	VkExtent3D maxExt = { 1, 1, 1};
 	uint32_t maxLevels = 1;
@@ -408,10 +446,7 @@
 		case VK_IMAGE_TYPE_1D:
 			maxExt.height = 1;
 			maxExt.depth = 1;
-			if (mvkTreatTexture1DAs2D()) {
-				maxExt.width = pLimits->maxImageDimension2D;
-				maxLevels = mvkMipmapLevels3D(maxExt);
-			} else {
+			if (!mvkTreatTexture1DAs2D()) {
 				maxExt.width = pLimits->maxImageDimension1D;
 				maxLevels = 1;
 				sampleCounts = VK_SAMPLE_COUNT_1_BIT;
@@ -425,29 +460,42 @@
 				// Metal does not allow compressed or depth/stencil formats on native 1D textures
 				if (mvkFmt == kMVKFormatDepthStencil) { return VK_ERROR_FORMAT_NOT_SUPPORTED; }
 				if (mvkFmt == kMVKFormatCompressed) { return VK_ERROR_FORMAT_NOT_SUPPORTED; }
+				if (isChromaSubsampled) { return VK_ERROR_FORMAT_NOT_SUPPORTED; }
+				break;
 			}
-			break;
 
+			// A 420 1D image doesn't make much sense.
+			if (isChromaSubsampled && _pixelFormats.getBlockTexelSize(format).height > 1) {
+				return VK_ERROR_FORMAT_NOT_SUPPORTED;
+			}
+			// Vulkan doesn't allow 1D multisampled images.
+			sampleCounts = VK_SAMPLE_COUNT_1_BIT;
+			/* fallthrough */
 		case VK_IMAGE_TYPE_2D:
 			if (mvkIsAnyFlagEnabled(flags, VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT) ) {
+				// Chroma-subsampled cube images aren't supported.
+				if (isChromaSubsampled) { return VK_ERROR_FORMAT_NOT_SUPPORTED; }
+				// 1D cube images aren't supported.
+				if (type == VK_IMAGE_TYPE_1D) { return VK_ERROR_FORMAT_NOT_SUPPORTED; }
 				maxExt.width = pLimits->maxImageDimensionCube;
 				maxExt.height = pLimits->maxImageDimensionCube;
 			} else {
 				maxExt.width = pLimits->maxImageDimension2D;
-				maxExt.height = pLimits->maxImageDimension2D;
+				maxExt.height = (type == VK_IMAGE_TYPE_1D ? 1 : pLimits->maxImageDimension2D);
 			}
 			maxExt.depth = 1;
 			if (tiling == VK_IMAGE_TILING_LINEAR) {
 				// Linear textures have additional restrictions under Metal:
-				// - They may not be depth/stencil or compressed textures.
-				if (mvkFmt == kMVKFormatDepthStencil || mvkFmt == kMVKFormatCompressed) {
+				// - They may not be depth/stencil, compressed, or chroma subsampled textures.
+				//   We allow multi-planar formats because those internally use non-subsampled formats.
+				if (mvkFmt == kMVKFormatDepthStencil || mvkFmt == kMVKFormatCompressed || isBGRG) {
 					return VK_ERROR_FORMAT_NOT_SUPPORTED;
 				}
 #if MVK_MACOS
 				// - On macOS, Linear textures may not be used as framebuffer attachments.
 				if (hasAttachmentUsage) { return VK_ERROR_FORMAT_NOT_SUPPORTED; }
 #endif
-				// Linear textures may only have one mip level. layer & sample
+				// Linear textures may only have one mip level, layer & sample.
 				maxLevels = 1;
 				maxLayers = 1;
 				sampleCounts = VK_SAMPLE_COUNT_1_BIT;
@@ -455,14 +503,22 @@
 				VkFormatProperties fmtProps;
 				getFormatProperties(format, &fmtProps);
 				// Compressed multisampled textures aren't supported.
+				// Chroma-subsampled multisampled textures aren't supported.
 				// Multisampled cube textures aren't supported.
 				// Non-renderable multisampled textures aren't supported.
-				if (mvkFmt == kMVKFormatCompressed ||
+				if (mvkFmt == kMVKFormatCompressed || isChromaSubsampled ||
 					mvkIsAnyFlagEnabled(flags, VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT) ||
 					!mvkIsAnyFlagEnabled(fmtProps.optimalTilingFeatures, VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT|VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) ) {
 					sampleCounts = VK_SAMPLE_COUNT_1_BIT;
 				}
-				maxLevels = mvkMipmapLevels3D(maxExt);
+				// BGRG and GBGR images may only have one mip level and one layer.
+				// Other chroma subsampled formats may have multiple mip levels, but still only one layer.
+				if (isChromaSubsampled) {
+					maxLevels = isBGRG ? 1 : mvkMipmapLevels3D(maxExt);
+					maxLayers = 1;
+				} else {
+					maxLevels = mvkMipmapLevels3D(maxExt);
+				}
 			}
 			break;
 
@@ -472,7 +528,8 @@
 				return VK_ERROR_FORMAT_NOT_SUPPORTED;
 			}
 			// Metal does not allow compressed or depth/stencil formats on 3D textures
-			if (mvkFmt == kMVKFormatDepthStencil
+			if (mvkFmt == kMVKFormatDepthStencil ||
+				isChromaSubsampled
 #if MVK_IOS_OR_TVOS
 				|| mvkFmt == kMVKFormatCompressed
 #endif
@@ -532,7 +589,7 @@
         switch (nextProps->sType) {
             case VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_IMAGE_FORMAT_PROPERTIES: {
                 auto* samplerYcbcrConvProps = (VkSamplerYcbcrConversionImageFormatProperties*)nextProps;
-                samplerYcbcrConvProps->combinedImageSamplerDescriptorCount = _pixelFormats.getChromaSubsamplingPlaneCount(pImageFormatInfo->format);
+                samplerYcbcrConvProps->combinedImageSamplerDescriptorCount = std::max(_pixelFormats.getChromaSubsamplingPlaneCount(pImageFormatInfo->format), (uint8_t)1u);
                 break;
             }
             default:
@@ -542,52 +599,12 @@
 
 	if ( !_pixelFormats.isSupported(pImageFormatInfo->format) ) { return VK_ERROR_FORMAT_NOT_SUPPORTED; }
 
-	if ( !getImageViewIsSupported(pImageFormatInfo) ) { return VK_ERROR_FORMAT_NOT_SUPPORTED; }
-
 	return getImageFormatProperties(pImageFormatInfo->format, pImageFormatInfo->type,
 									pImageFormatInfo->tiling, pImageFormatInfo->usage,
 									pImageFormatInfo->flags,
 									&pImageFormatProperties->imageFormatProperties);
 }
 
-// If the image format info links portability image view info, test if an image view of that configuration is supported
-bool MVKPhysicalDevice::getImageViewIsSupported(const VkPhysicalDeviceImageFormatInfo2 *pImageFormatInfo) {
-	for (const auto* next = (VkBaseInStructure*)pImageFormatInfo->pNext; next; next = next->pNext) {
-		switch ((uint32_t)next->sType) {
-			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_VIEW_SUPPORT_EXTX: {
-				auto* portImgViewInfo = (VkPhysicalDeviceImageViewSupportEXTX*)next;
-
-				// Create an image view and test whether it could be configured
-				VkImageViewCreateInfo viewInfo = {
-					.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
-					.pNext = portImgViewInfo->pNext,
-					.flags = portImgViewInfo->flags,
-					.image = nullptr,
-					.viewType = portImgViewInfo->viewType,
-					.format = portImgViewInfo->format,
-					.components = portImgViewInfo->components,
-					.subresourceRange = {
-						.aspectMask = portImgViewInfo->aspectMask,
-						.baseMipLevel = 0,
-						.levelCount = 1,
-						.baseArrayLayer = 0,
-						.layerCount = 1},
-				};
-                MTLPixelFormat mtlPixFmt = _pixelFormats.getMTLPixelFormat(viewInfo.format);
-				bool useSwizzle;
-				return (MVKImageView::validateSwizzledMTLPixelFormat(&viewInfo, this,
-																	 _metalFeatures.nativeTextureSwizzle,
-																	 _mvkInstance->getMoltenVKConfiguration()->fullImageViewSwizzle,
-																	 mtlPixFmt, useSwizzle) == VK_SUCCESS);
-			}
-			default:
-				break;
-		}
-	}
-
-	return true;
-}
-
 void MVKPhysicalDevice::getExternalBufferProperties(const VkPhysicalDeviceExternalBufferInfo* pExternalBufferInfo,
 													VkExternalBufferProperties* pExternalBufferProperties) {
 	pExternalBufferProperties->externalMemoryProperties = getExternalBufferProperties(pExternalBufferInfo->handleType);
@@ -1002,6 +1019,7 @@
 	_metalFeatures.maxPerStageBufferCount = 31;
     _metalFeatures.maxMTLBufferSize = (256 * MEBI);
     _metalFeatures.dynamicMTLBufferSize = 0;
+    _metalFeatures.maxPerStageDynamicMTLBufferCount = 0;
 
     _metalFeatures.maxPerStageSamplerCount = 16;
     _metalFeatures.maxQueryBufferSize = (64 * KIBI);
@@ -1026,6 +1044,8 @@
     _metalFeatures.texelBuffers = true;
 	_metalFeatures.maxTextureDimension = (8 * KIBI);
     _metalFeatures.dynamicMTLBufferSize = (4 * KIBI);
+    _metalFeatures.sharedLinearTextures = true;
+    _metalFeatures.maxPerStageDynamicMTLBufferCount = _metalFeatures.maxPerStageBufferCount;
 
     if (supportsMTLFeatureSet(tvOS_GPUFamily1_v2)) {
 		_metalFeatures.mslVersionEnum = MTLLanguageVersion1_2;
@@ -1055,6 +1075,7 @@
 		_metalFeatures.depthSampleCompare = true;
 		_metalFeatures.arrayOfTextures = true;
 		_metalFeatures.arrayOfSamplers = true;
+		_metalFeatures.depthResolve = true;
 	}
 
 	if ( mvkOSVersionIsAtLeast(13.0) ) {
@@ -1073,11 +1094,13 @@
 	_metalFeatures.mtlCopyBufferAlignment = 1;
     _metalFeatures.texelBuffers = true;
 	_metalFeatures.maxTextureDimension = (4 * KIBI);
+    _metalFeatures.sharedLinearTextures = true;
 
     if (supportsMTLFeatureSet(iOS_GPUFamily1_v2)) {
 		_metalFeatures.mslVersionEnum = MTLLanguageVersion1_1;
         _metalFeatures.dynamicMTLBufferSize = (4 * KIBI);
 		_metalFeatures.maxTextureDimension = (8 * KIBI);
+		_metalFeatures.maxPerStageDynamicMTLBufferCount = _metalFeatures.maxPerStageBufferCount;
     }
 
     if (supportsMTLFeatureSet(iOS_GPUFamily1_v3)) {
@@ -1106,6 +1129,7 @@
 		_metalFeatures.mtlBufferAlignment = 16;     // Min float4 alignment for typical vertex buffers. MTLBuffer may go down to 4 bytes for other data.
 		_metalFeatures.maxTextureDimension = (16 * KIBI);
 		_metalFeatures.depthSampleCompare = true;
+		_metalFeatures.depthResolve = true;
 	}
 
 	if (supportsMTLFeatureSet(iOS_GPUFamily3_v2)) {
@@ -1124,6 +1148,7 @@
 		_metalFeatures.layeredRendering = true;
 		_metalFeatures.stencilFeedback = true;
 		_metalFeatures.indirectTessellationDrawing = true;
+		_metalFeatures.stencilResolve = true;
 	}
 
 	if ( mvkOSVersionIsAtLeast(13.0) ) {
@@ -1157,6 +1182,7 @@
         _metalFeatures.combinedStoreResolveAction = true;
 		_metalFeatures.deferredStoreActions = true;
         _metalFeatures.maxMTLBufferSize = (1 * GIBI);
+        _metalFeatures.maxPerStageDynamicMTLBufferCount = 14;
     }
 
     if (supportsMTLFeatureSet(macOS_GPUFamily1_v3)) {
@@ -1180,12 +1206,17 @@
 	if (supportsMTLFeatureSet(macOS_GPUFamily2_v1)) {
 		_metalFeatures.multisampleLayeredRendering = _metalFeatures.layeredRendering;
 		_metalFeatures.stencilFeedback = true;
+		_metalFeatures.depthResolve = true;
+		_metalFeatures.stencilResolve = true;
 	}
 
 	if ( mvkOSVersionIsAtLeast(10.15) ) {
 		_metalFeatures.mslVersionEnum = MTLLanguageVersion2_2;
 		_metalFeatures.native3DCompressedTextures = true;
         _metalFeatures.renderWithoutAttachments = true;
+        if ( mvkOSVersionIsAtLeast(mvkMakeOSVersion(10, 15, 5)) ) {
+            _metalFeatures.sharedLinearTextures = true;
+        }
 		if (supportsMTLGPUFamily(Mac2)) {
 			_metalFeatures.nativeTextureSwizzle = true;
 			_metalFeatures.placementHeaps = useMTLHeaps;
@@ -1220,9 +1251,12 @@
 	_metalFeatures.mslVersion = SPIRV_CROSS_NAMESPACE::CompilerMSL::Options::make_msl_version(maj, min);
 
 	switch (_metalFeatures.mslVersionEnum) {
+#if (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 101600) ||  \
+	(defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000)		// also covers tvOS
 		case MTLLanguageVersion2_3:
 			setMSLVersion(2, 3);
 			break;
+#endif
 		case MTLLanguageVersion2_2:
 			setMSLVersion(2, 2);
 			break;
@@ -1268,7 +1302,6 @@
     _features.shaderClipDistance = true;
     _features.shaderInt16 = true;
     _features.multiDrawIndirect = true;
-    _features.variableMultisampleRate = true;
     _features.inheritedQueries = true;
 
 	_features.shaderSampledImageArrayDynamicIndexing = _metalFeatures.arrayOfTextures;
@@ -1410,7 +1443,7 @@
 //    VkBool32    sparseResidency8Samples;
 //    VkBool32    sparseResidency16Samples;
 //    VkBool32    sparseResidencyAliased;
-//    VkBool32    variableMultisampleRate;                      // done
+//    VkBool32    variableMultisampleRate;
 //    VkBool32    inheritedQueries;                             // done
 //} VkPhysicalDeviceFeatures;
 
@@ -1522,7 +1555,6 @@
         uint32_t maxStorage = 0, maxUniform = 0;
         bool singleTexelStorage = true, singleTexelUniform = true;
         _pixelFormats.enumerateSupportedFormats({0, 0, VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT}, true, [&](VkFormat vk) {
-            if ( _pixelFormats.getChromaSubsamplingComponentBits(vk) > 0 ) { return false; }    // Skip chroma subsampling formats
 			MTLPixelFormat mtlFmt = _pixelFormats.getMTLPixelFormat(vk);
 			if ( !mtlFmt ) { return false; }	// If format is invalid, avoid validation errors on MTLDevice format alignment calls
 
@@ -2264,6 +2296,11 @@
 	MVKExtensionList* pWritableExtns = (MVKExtensionList*)&_supportedExtensions;
 	pWritableExtns->disableAllButEnabledDeviceExtensions();
 
+#if MVK_IOS_OR_TVOS
+	if (!_metalFeatures.depthResolve) {
+		pWritableExtns->vk_KHR_depth_stencil_resolve.enabled = false;
+	}
+#endif
 	if (!_metalFeatures.rasterOrderGroups) {
 		pWritableExtns->vk_EXT_fragment_shader_interlock.enabled = false;
 	}
@@ -2961,8 +2998,15 @@
 VkDeviceSize MVKDevice::getVkFormatTexelBufferAlignment(VkFormat format, MVKBaseObject* mvkObj) {
 	VkDeviceSize deviceAlignment = 0;
 	id<MTLDevice> mtlDev = getMTLDevice();
+	MVKPixelFormats* mvkPixFmts = getPixelFormats();
 	if ([mtlDev respondsToSelector: @selector(minimumLinearTextureAlignmentForPixelFormat:)]) {
-		deviceAlignment = [mtlDev minimumLinearTextureAlignmentForPixelFormat: getPixelFormats()->getMTLPixelFormat(format)];
+		MTLPixelFormat mtlPixFmt = mvkPixFmts->getMTLPixelFormat(format);
+		if (mvkPixFmts->getChromaSubsamplingPlaneCount(format) >= 2) {
+			// Use plane 1 to get the alignment requirements. In a 2-plane format, this will
+			// typically have stricter alignment requirements due to it being a 2-component format.
+			mtlPixFmt = mvkPixFmts->getChromaSubsamplingPlaneMTLPixelFormat(format, 1);
+		}
+		deviceAlignment = [mtlDev minimumLinearTextureAlignmentForPixelFormat: mtlPixFmt];
 	}
 	return deviceAlignment ? deviceAlignment : _pProperties->limits.minTexelBufferOffsetAlignment;
 }
@@ -3175,8 +3219,8 @@
 	mvkClear(&_enabledPortabilityFeatures);
 
 	// Fetch the available physical device features.
-	VkPhysicalDevicePortabilitySubsetFeaturesEXTX pdPortabilityFeatures;
-	pdPortabilityFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_FEATURES_EXTX;
+	VkPhysicalDevicePortabilitySubsetFeaturesKHR pdPortabilityFeatures;
+	pdPortabilityFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_FEATURES_KHR;
 	pdPortabilityFeatures.pNext = NULL;
 
 	VkPhysicalDeviceVertexAttributeDivisorFeaturesEXT pdVtxAttrDivFeatures;
@@ -3322,11 +3366,11 @@
 							   &pdVtxAttrDivFeatures.vertexAttributeInstanceRateDivisor, 2);
 				break;
 			}
-			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_FEATURES_EXTX: {
-				auto* requestedFeatures = (VkPhysicalDevicePortabilitySubsetFeaturesEXTX*)next;
+			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_FEATURES_KHR: {
+				auto* requestedFeatures = (VkPhysicalDevicePortabilitySubsetFeaturesKHR*)next;
 				enableFeatures(&_enabledPortabilityFeatures.triangleFans,
 							   &requestedFeatures->triangleFans,
-							   &pdPortabilityFeatures.triangleFans, 5);
+							   &pdPortabilityFeatures.triangleFans, 15);
 				break;
 			}
 			default:
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDeviceMemory.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDeviceMemory.mm
index cfa410e..593be4d 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDeviceMemory.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDeviceMemory.mm
@@ -308,8 +308,10 @@
 			if (!((MVKImage*)dedicatedImage)->_isLinear) {
 				setConfigurationResult(reportError(VK_ERROR_OUT_OF_DEVICE_MEMORY, "Host-coherent VkDeviceMemory objects cannot be associated with optimal-tiling images."));
 			} else {
-				// Need to use the managed mode for images.
-				_mtlStorageMode = MTLStorageModeManaged;
+				if (!_device->_pMetalFeatures->sharedLinearTextures) {
+					// Need to use the managed mode for images.
+					_mtlStorageMode = MTLStorageModeManaged;
+				}
 				// Nonetheless, we need a buffer to be able to map the memory at will.
 				if (!ensureMTLBuffer() ) {
 					setConfigurationResult(reportError(VK_ERROR_OUT_OF_DEVICE_MEMORY, "Could not allocate a host-coherent VkDeviceMemory of size %llu bytes. The maximum memory-aligned size of a host-coherent VkDeviceMemory is %llu bytes.", _allocationSize, _device->_pMetalFeatures->maxMTLBufferSize));
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
index a4eae12..227fe3e 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
@@ -144,8 +144,10 @@
     MVKImageMemoryBinding(MVKDevice* device, MVKImage* image, uint8_t planeIndex);
 
     MVKImage* _image;
+    id<MTLBuffer> _mtlTexelBuffer = nil;
+    NSUInteger _mtlTexelBufferOffset = 0;
     uint8_t _planeIndex;
-    bool _usesTexelBuffer;
+    bool _ownsTexelBuffer = false;
 };
 
 
@@ -366,6 +368,9 @@
 	bool _isLinear;
 	bool _is3DCompressed;
 	bool _isAliasable;
+	bool _hasExtendedUsage;
+	bool _hasMutableFormat;
+	bool _isLinearForAtomics;
 };
 
 
@@ -586,6 +591,7 @@
 	 * This is a static function that can be used to validate image view formats prior to creating one.
 	 */
 	static VkResult validateSwizzledMTLPixelFormat(const VkImageViewCreateInfo* pCreateInfo,
+												   VkImageUsageFlags usage,
 												   MVKVulkanAPIObject* apiObject,
 												   bool hasNativeSwizzleSupport,
 												   bool hasShaderSwizzleSupport,
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
index 4bcf46d..b99eef9 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
@@ -51,10 +51,10 @@
                            newTextureWithDescriptor: mtlTexDesc
                            iosurface: _image->_ioSurface
                            plane: _planeIndex];
-        } else if (memoryBinding->_usesTexelBuffer) {
-            _mtlTexture = [memoryBinding->_deviceMemory->getMTLBuffer()
+        } else if (memoryBinding->_mtlTexelBuffer) {
+            _mtlTexture = [memoryBinding->_mtlTexelBuffer
                            newTextureWithDescriptor: mtlTexDesc
-                           offset: memoryBinding->getDeviceMemoryOffset()
+                           offset: memoryBinding->_mtlTexelBufferOffset
                            bytesPerRow: _subresources[0].layout.rowPitch];
         } else if (memoryBinding->_deviceMemory->getMTLHeap() && !_image->getIsDepthStencil()) {
             // Metal support for depth/stencil from heaps is flaky
@@ -123,7 +123,7 @@
     mtlTexDesc.mipmapLevelCount = _image->_mipLevels;
     mtlTexDesc.sampleCount = mvkSampleCountFromVkSampleCountFlagBits(_image->_samples);
     mtlTexDesc.arrayLength = _image->_arrayLayers;
-    mtlTexDesc.usageMVK = _image->getPixelFormats()->getMTLTextureUsage(_image->_usage, mtlPixFmt, minUsage, _image->_isLinear);
+    mtlTexDesc.usageMVK = _image->getPixelFormats()->getMTLTextureUsage(_image->_usage, mtlPixFmt, minUsage, _image->_isLinear, _image->_hasMutableFormat, _image->_hasExtendedUsage);
     mtlTexDesc.storageModeMVK = _image->getMTLStorageMode();
     mtlTexDesc.cpuCacheMode = _image->getMTLCPUCacheMode();
 
@@ -306,7 +306,7 @@
 
 	MVKImageMemoryBinding* memBind = getMemoryBinding();
 	bool needsSync = memBind->needsHostReadSync(srcStageMask, dstStageMask, barrier);
-	bool needsPull = (!memBind->_usesTexelBuffer &&
+	bool needsPull = ((!memBind->_mtlTexelBuffer || memBind->_ownsTexelBuffer) &&
 					  memBind->isMemoryHostCoherent() &&
 					  barrier.newLayout == VK_IMAGE_LAYOUT_GENERAL &&
 					  mvkIsAnyFlagEnabled(barrier.dstAccessMask, (VK_ACCESS_HOST_READ_BIT | VK_ACCESS_MEMORY_READ_BIT)));
@@ -375,9 +375,10 @@
         case VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS: {
             auto* dedicatedReqs = (VkMemoryDedicatedRequirements*)next;
             bool writable = mvkIsAnyFlagEnabled(_image->_usage, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT);
+            bool canUseTexelBuffer = _device->_pMetalFeatures->texelBuffers && _image->_isLinear && !_image->getIsCompressed();
             dedicatedReqs->requiresDedicatedAllocation = _requiresDedicatedMemoryAllocation;
             dedicatedReqs->prefersDedicatedAllocation = (dedicatedReqs->requiresDedicatedAllocation ||
-                                                        (!_usesTexelBuffer && (writable || !_device->_pMetalFeatures->placementHeaps)));
+                                                        (!canUseTexelBuffer && (writable || !_device->_pMetalFeatures->placementHeaps)));
             break;
         }
         default:
@@ -392,13 +393,34 @@
     if (_deviceMemory) { _deviceMemory->removeImageMemoryBinding(this); }
     MVKResource::bindDeviceMemory(mvkMem, memOffset);
 
-    _usesTexelBuffer = _device->_pMetalFeatures->texelBuffers && _deviceMemory && _deviceMemory->_mtlBuffer; // Texel buffers available
-    _usesTexelBuffer = _usesTexelBuffer && (isMemoryHostAccessible() || _device->_pMetalFeatures->placementHeaps) && _image->_isLinear && !_image->getIsCompressed(); // Applicable memory layout
+    bool usesTexelBuffer = _device->_pMetalFeatures->texelBuffers && _deviceMemory; // Texel buffers available
+    usesTexelBuffer = usesTexelBuffer && (isMemoryHostAccessible() || _device->_pMetalFeatures->placementHeaps) && _image->_isLinear && !_image->getIsCompressed(); // Applicable memory layout
 
-#if MVK_MACOS
-    // macOS cannot use shared memory for texel buffers.
-    _usesTexelBuffer = _usesTexelBuffer && !isMemoryHostCoherent();
-#endif
+    // macOS before 10.15.5 cannot use shared memory for texel buffers.
+    usesTexelBuffer = usesTexelBuffer && (_device->_pMetalFeatures->sharedLinearTextures || !isMemoryHostCoherent());
+
+    if (_image->_isLinearForAtomics) {
+        if (usesTexelBuffer && _deviceMemory->ensureMTLBuffer()) {
+            _mtlTexelBuffer = _deviceMemory->_mtlBuffer;
+            _mtlTexelBufferOffset = getDeviceMemoryOffset();
+        } else {
+            // Create our own buffer for this.
+            if (_deviceMemory && _deviceMemory->_mtlHeap && _image->getMTLStorageMode() == _deviceMemory->_mtlStorageMode) {
+                _mtlTexelBuffer = [_deviceMemory->_mtlHeap newBufferWithLength: _byteCount options: _deviceMemory->getMTLResourceOptions() offset: getDeviceMemoryOffset()];
+                if (_image->_isAliasable) { [_mtlTexelBuffer makeAliasable]; }
+            } else {
+                _mtlTexelBuffer = [getMTLDevice() newBufferWithLength: _byteCount options: _image->getMTLStorageMode() << MTLResourceStorageModeShift];
+            }
+            if (!_mtlTexelBuffer) {
+                return reportError(VK_ERROR_OUT_OF_DEVICE_MEMORY, "Could not create an MTLBuffer for an image that requires a buffer backing store. Images that can be used for atomic accesses must have a texel buffer backing them.");
+            }
+            _mtlTexelBufferOffset = 0;
+            _ownsTexelBuffer = true;
+        }
+    } else if (usesTexelBuffer && _deviceMemory->_mtlBuffer) {
+        _mtlTexelBuffer = _deviceMemory->_mtlBuffer;
+        _mtlTexelBufferOffset = getDeviceMemoryOffset();
+    }
 
     flushToDevice(getDeviceMemoryOffset(), getByteCount());
     return _deviceMemory ? _deviceMemory->addImageMemoryBinding(this) : VK_SUCCESS;
@@ -422,6 +444,9 @@
     for(uint8_t planeIndex = beginPlaneIndex(); planeIndex < endPlaneIndex(); planeIndex++) {
         _image->_planes[planeIndex]->propagateDebugName();
     }
+    if (_ownsTexelBuffer) {
+        setLabelIfNotNil(_mtlTexelBuffer, _image->_debugName);
+    }
 }
 
 // Returns whether the specified image memory barrier requires a sync between this
@@ -430,17 +455,16 @@
                                               VkPipelineStageFlags dstStageMask,
                                               MVKPipelineBarrier& barrier) {
 #if MVK_MACOS
-	//  On macOS, texture memory is never host-coherent, so don't test for it.
     return ((barrier.newLayout == VK_IMAGE_LAYOUT_GENERAL) &&
             mvkIsAnyFlagEnabled(barrier.dstAccessMask, (VK_ACCESS_HOST_READ_BIT | VK_ACCESS_MEMORY_READ_BIT)) &&
-            isMemoryHostAccessible());
+            isMemoryHostAccessible() && (!_device->_pMetalFeatures->sharedLinearTextures || !isMemoryHostCoherent()));
 #endif
 #if MVK_IOS_OR_TVOS
     return false;
 #endif
 }
 
-bool MVKImageMemoryBinding::shouldFlushHostMemory() { return isMemoryHostAccessible() && !_usesTexelBuffer; }
+bool MVKImageMemoryBinding::shouldFlushHostMemory() { return isMemoryHostAccessible() && (!_mtlTexelBuffer || _ownsTexelBuffer); }
 
 // Flushes the device memory at the specified memory range into the MTLTexture. Updates
 // all subresources that overlap the specified range and are in an updatable layout state.
@@ -492,14 +516,12 @@
     return (_image->_memoryBindings.size() > 1) ? _planeIndex : (uint8_t)_image->_memoryBindings.size();
 }
 
-MVKImageMemoryBinding::MVKImageMemoryBinding(MVKDevice* device, MVKImage* image, uint8_t planeIndex) : MVKResource(device) {
-    _image = image;
-    _planeIndex = planeIndex;
-    _usesTexelBuffer = false;
+MVKImageMemoryBinding::MVKImageMemoryBinding(MVKDevice* device, MVKImage* image, uint8_t planeIndex) : MVKResource(device), _image(image), _planeIndex(planeIndex) {
 }
 
 MVKImageMemoryBinding::~MVKImageMemoryBinding() {
     if (_deviceMemory) { _deviceMemory->removeImageMemoryBinding(this); }
+    if (_ownsTexelBuffer) { [_mtlTexelBuffer release]; }
 }
 
 
@@ -532,7 +554,7 @@
 
 VkExtent3D MVKImage::getExtent3D(uint8_t planeIndex, uint32_t mipLevel) {
     VkExtent3D extent = _extent;
-    if (_hasChromaSubsampling) {
+    if (_hasChromaSubsampling && planeIndex > 0) {
         extent.width /= _planes[planeIndex]->_blockTexelSize.width;
         extent.height /= _planes[planeIndex]->_blockTexelSize.height;
     }
@@ -540,14 +562,16 @@
 }
 
 VkDeviceSize MVKImage::getBytesPerRow(uint8_t planeIndex, uint32_t mipLevel) {
-    size_t bytesPerRow = getPixelFormats()->getBytesPerRow(_vkFormat, getExtent3D(planeIndex, mipLevel).width);
+    MTLPixelFormat planeMTLPixFmt = getPixelFormats()->getChromaSubsamplingPlaneMTLPixelFormat(_vkFormat, planeIndex);
+    size_t bytesPerRow = getPixelFormats()->getBytesPerRow(planeMTLPixFmt, getExtent3D(planeIndex, mipLevel).width);
     return mvkAlignByteCount(bytesPerRow, _rowByteAlignment);
 }
 
 VkDeviceSize MVKImage::getBytesPerLayer(uint8_t planeIndex, uint32_t mipLevel) {
+    MTLPixelFormat planeMTLPixFmt = getPixelFormats()->getChromaSubsamplingPlaneMTLPixelFormat(_vkFormat, planeIndex);
     VkExtent3D extent = getExtent3D(planeIndex, mipLevel);
     size_t bytesPerRow = getBytesPerRow(planeIndex, mipLevel);
-    return getPixelFormats()->getBytesPerLayer(_vkFormat, bytesPerRow, extent.height);
+    return getPixelFormats()->getBytesPerLayer(planeMTLPixFmt, bytesPerRow, extent.height);
 }
 
 VkResult MVKImage::getSubresourceLayout(const VkImageSubresource* pSubresource,
@@ -607,10 +631,11 @@
 
 VkResult MVKImage::getMemoryRequirements(const void* pInfo, VkMemoryRequirements2* pMemoryRequirements) {
     uint8_t planeIndex = 0;
-	for (auto* next = (VkBaseOutStructure*)pMemoryRequirements->pNext; next; next = next->pNext) {
+	const auto* pImageInfo = (const VkImageMemoryRequirementsInfo2*)pInfo;
+	for (const auto* next = (const VkBaseInStructure*)pImageInfo->pNext; next; next = next->pNext) {
 		switch (next->sType) {
 		case VK_STRUCTURE_TYPE_IMAGE_PLANE_MEMORY_REQUIREMENTS_INFO: {
-			auto* planeReqs = (VkImagePlaneMemoryRequirementsInfo*)next;
+			const auto* planeReqs = (const VkImagePlaneMemoryRequirementsInfo*)next;
             planeIndex = MVKImage::getPlaneFromVkImageAspectFlags(planeReqs->planeAspect);
 			break;
 		}
@@ -618,7 +643,9 @@
 			break;
 		}
 	}
-    return getMemoryRequirements(&pMemoryRequirements->memoryRequirements, planeIndex);
+    VkResult rslt = getMemoryRequirements(&pMemoryRequirements->memoryRequirements, planeIndex);
+    if (rslt != VK_SUCCESS) { return rslt; }
+    return _memoryBindings[planeIndex]->getMemoryRequirements(pInfo, pMemoryRequirements);
 }
 
 VkResult MVKImage::bindDeviceMemory(MVKDeviceMemory* mvkMem, VkDeviceSize memOffset, uint8_t planeIndex) {
@@ -762,8 +789,10 @@
     if (_ioSurface && stgMode == MTLStorageModePrivate) { stgMode = MTLStorageModeShared; }
 
 #if MVK_MACOS
-	// For macOS, textures cannot use Shared storage mode, so change to Managed storage mode.
-    if (stgMode == MTLStorageModeShared) { stgMode = MTLStorageModeManaged; }
+	// For macOS prior to 10.15.5, textures cannot use Shared storage mode, so change to Managed storage mode.
+    if (stgMode == MTLStorageModeShared && !_device->_pMetalFeatures->sharedLinearTextures) {
+        stgMode = MTLStorageModeManaged;
+    }
 #endif
     return stgMode;
 }
@@ -802,7 +831,16 @@
 	MVKPixelFormats* pixFmts = getPixelFormats();
     _vkFormat = pCreateInfo->format;
 	_usage = pCreateInfo->usage;
+	_hasMutableFormat = mvkIsAnyFlagEnabled(pCreateInfo->flags, VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT);
+	_hasExtendedUsage = mvkIsAnyFlagEnabled(pCreateInfo->flags, VK_IMAGE_CREATE_EXTENDED_USAGE_BIT);
 
+    // If this is a storage image of format R32_UINT or R32_SINT, or MUTABLE_FORMAT is set
+    // and R32_UINT is in the set of possible view formats, then we must use a texel buffer,
+    // or image atomics won't work.
+    // TODO: Also add handling for VK_KHR_image_format_list here.
+    _isLinearForAtomics = _isLinear && mvkIsAnyFlagEnabled(_usage, VK_IMAGE_USAGE_STORAGE_BIT) &&
+                          ((_vkFormat == VK_FORMAT_R32_UINT || _vkFormat == VK_FORMAT_R32_SINT) ||
+                           (_hasMutableFormat && pixFmts->getViewClass(_vkFormat) == MVKMTLViewClass::Color32));
 	_is3DCompressed = (getImageType() == VK_IMAGE_TYPE_3D) && (pixFmts->getFormatType(pCreateInfo->format) == kMVKFormatCompressed) && !_device->_pMetalFeatures->native3DCompressedTextures;
 	_isDepthStencilAttachment = (mvkAreAllFlagsEnabled(pCreateInfo->usage, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) ||
 								 mvkAreAllFlagsEnabled(pixFmts->getVkFormatProperties(pCreateInfo->format).optimalTilingFeatures, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT));
@@ -839,6 +877,16 @@
             memoryBinding->_byteCount += sizeAndAlign.size;
             memoryBinding->_byteAlignment = std::max(memoryBinding->_byteAlignment, (VkDeviceSize)sizeAndAlign.align);
             _isAliasable = mvkIsAnyFlagEnabled(pCreateInfo->flags, VK_IMAGE_CREATE_ALIAS_BIT);
+        } else if (_isLinearForAtomics && _device->_pMetalFeatures->placementHeaps) {
+            NSUInteger bufferLength = 0;
+            for (uint32_t mipLvl = 0; mipLvl < _mipLevels; mipLvl++) {
+                VkExtent3D mipExtent = getExtent3D(planeIndex, mipLvl);
+                bufferLength += getBytesPerLayer(planeIndex, mipLvl) * mipExtent.depth * _arrayLayers;
+            }
+            MTLSizeAndAlign sizeAndAlign = [_device->getMTLDevice() heapBufferSizeAndAlignWithLength: bufferLength options: MTLResourceStorageModePrivate];
+            memoryBinding->_byteCount += sizeAndAlign.size;
+            memoryBinding->_byteAlignment = std::max(std::max(memoryBinding->_byteAlignment, _rowByteAlignment), (VkDeviceSize)sizeAndAlign.align);
+            _isAliasable = mvkIsAnyFlagEnabled(pCreateInfo->flags, VK_IMAGE_CREATE_ALIAS_BIT);
         } else {
             for (uint32_t mipLvl = 0; mipLvl < _mipLevels; mipLvl++) {
                 VkExtent3D mipExtent = getExtent3D(planeIndex, mipLvl);
@@ -878,6 +926,10 @@
 		setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, multisampling cannot be used with compressed images. Setting sample count to 1."));
 		validSamples = VK_SAMPLE_COUNT_1_BIT;
 	}
+	if (getPixelFormats()->getChromaSubsamplingPlaneCount(pCreateInfo->format) > 0) {
+		setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, multisampling cannot be used with chroma subsampled images. Setting sample count to 1."));
+		validSamples = VK_SAMPLE_COUNT_1_BIT;
+	}
 
 	if (pCreateInfo->arrayLayers > 1) {
 		if ( !_device->_pMetalFeatures->multisampleArrayTextures ) {
@@ -898,6 +950,7 @@
 
 	bool is2D = (getImageType() == VK_IMAGE_TYPE_2D);
 	bool isCompressed = pixFmts->getFormatType(pCreateInfo->format) == kMVKFormatCompressed;
+	bool isChromaSubsampled = pixFmts->getChromaSubsamplingPlaneCount(pCreateInfo->format) > 0;
 
 #if MVK_IOS_OR_TVOS
 	if (isCompressed && !is2D) {
@@ -914,6 +967,16 @@
 	}
 #endif
 
+	if (isChromaSubsampled && !is2D) {
+		setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, chroma subsampled formats may only be used with 2D images."));
+	}
+	if (isChromaSubsampled && mvkIsAnyFlagEnabled(pCreateInfo->flags, VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT)) {
+		setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, chroma subsampled formats may not be used with cube images."));
+	}
+	if (isChromaSubsampled && (pCreateInfo->arrayLayers > 1)) {
+		setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Chroma-subsampled formats may only have one array layer."));
+	}
+
 	if ((pixFmts->getFormatType(pCreateInfo->format) == kMVKFormatDepthStencil) && !is2D ) {
 		setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, depth/stencil formats may only be used with 2D images."));
 	}
@@ -937,6 +1000,10 @@
 
 	if (validMipLevels == 1) { return validMipLevels; }
 
+	if (getPixelFormats()->getChromaSubsamplingPlaneCount(pCreateInfo->format) == 1) {
+		setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, GBGR and BGRG images cannot use mipmaps. Setting mip levels to 1."));
+		validMipLevels = 1;
+	}
 	if (getImageType() == VK_IMAGE_TYPE_1D) {
 		setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, native 1D images cannot use mipmaps. Setting mip levels to 1. Consider enabling MVK_CONFIG_TEXTURE_1D_AS_2D."));
 		validMipLevels = 1;
@@ -960,6 +1027,14 @@
 		setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, format must not be a depth/stencil format."));
 		isLin = false;
 	}
+	if (getPixelFormats()->getFormatType(pCreateInfo->format) == kMVKFormatCompressed) {
+		setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, format must not be a compressed format."));
+		isLin = false;
+	}
+	if (getPixelFormats()->getChromaSubsamplingPlaneCount(pCreateInfo->format) == 1) {
+		setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, format must not be a single-plane chroma subsampled format."));
+		isLin = false;
+	}
 
 	if (pCreateInfo->mipLevels > 1) {
 		setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, mipLevels must be 1."));
@@ -1311,7 +1386,7 @@
         mtlTextureType = MTLTextureType3D;
         sliceRange = NSMakeRange(0, 1);
     }
-    id<MTLTexture> mtlTex = _imageView->_image->getMTLTexture(MVKImage::getPlaneFromVkImageAspectFlags(_imageView->_subresourceRange.aspectMask));
+    id<MTLTexture> mtlTex = _imageView->_image->getMTLTexture(_planeIndex);
     if (_device->_pMetalFeatures->nativeTextureSwizzle && _packedSwizzle) {
         return [mtlTex newTextureViewWithPixelFormat: _mtlPixFmt
                                          textureType: mtlTextureType
@@ -1340,6 +1415,7 @@
 
     bool useSwizzle;
 	_imageView->setConfigurationResult(_imageView->validateSwizzledMTLPixelFormat(pCreateInfo,
+																				  _imageView->_usage,
 																				  _imageView,
 																				  _device->_pMetalFeatures->nativeTextureSwizzle,
 																				  _device->_pMVKConfig->fullImageViewSwizzle,
@@ -1464,15 +1540,15 @@
             beginPlaneIndex = 0,
             endPlaneIndex = subsamplingPlaneCount;
     if (subsamplingPlaneCount == 0) {
-        endPlaneIndex = 1;
-        mtlPixFmtOfPlane[0] = getPixelFormats()->getMTLPixelFormat(pCreateInfo->format);
+        if (_subresourceRange.aspectMask & (VK_IMAGE_ASPECT_PLANE_0_BIT | VK_IMAGE_ASPECT_PLANE_1_BIT | VK_IMAGE_ASPECT_PLANE_2_BIT)) {
+            beginPlaneIndex = MVKImage::getPlaneFromVkImageAspectFlags(_subresourceRange.aspectMask);
+        }
+        endPlaneIndex = beginPlaneIndex + 1;
+        mtlPixFmtOfPlane[beginPlaneIndex] = getPixelFormats()->getMTLPixelFormat(pCreateInfo->format);
     } else {
         if (!mvkVkComponentMappingsMatch(pCreateInfo->components, {VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A})) {
             setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "Image view swizzling for multi planar formats is not supported."));
         }
-        if (_subresourceRange.aspectMask & (VK_IMAGE_ASPECT_PLANE_0_BIT | VK_IMAGE_ASPECT_PLANE_1_BIT | VK_IMAGE_ASPECT_PLANE_2_BIT)) {
-            beginPlaneIndex = endPlaneIndex = MVKImage::getPlaneFromVkImageAspectFlags(_subresourceRange.aspectMask);
-        }
     }
     for (uint8_t planeIndex = beginPlaneIndex; planeIndex < endPlaneIndex; planeIndex++) {
         _planes.push_back(new MVKImageViewPlane(this, planeIndex, mtlPixFmtOfPlane[planeIndex], pCreateInfo));
@@ -1480,6 +1556,7 @@
 }
 
 VkResult MVKImageView::validateSwizzledMTLPixelFormat(const VkImageViewCreateInfo* pCreateInfo,
+													  VkImageUsageFlags usage,
 													  MVKVulkanAPIObject* apiObject,
 													  bool hasNativeSwizzleSupport,
 													  bool hasShaderSwizzleSupport,
@@ -1494,7 +1571,8 @@
 	// If we have an identity swizzle, we're all good.
 	if (SWIZZLE_MATCHES(R, G, B, A)) {
 		// Change to stencil-only format if only stencil aspect is requested
-		if (pCreateInfo->subresourceRange.aspectMask == VK_IMAGE_ASPECT_STENCIL_BIT) {
+		if (pCreateInfo->subresourceRange.aspectMask == VK_IMAGE_ASPECT_STENCIL_BIT &&
+			mvkIsAnyFlagEnabled(usage, (VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT))) {
 			if (mtlPixFmt == MTLPixelFormatDepth32Float_Stencil8)
 				mtlPixFmt = MTLPixelFormatX32_Stencil8;
 #if MVK_MACOS
@@ -1551,7 +1629,8 @@
 
 		case MTLPixelFormatDepth32Float_Stencil8:
 			// If aspect mask looking only for stencil then change to stencil-only format even if shader swizzling is needed
-			if (pCreateInfo->subresourceRange.aspectMask == VK_IMAGE_ASPECT_STENCIL_BIT) {
+			if (pCreateInfo->subresourceRange.aspectMask == VK_IMAGE_ASPECT_STENCIL_BIT &&
+				mvkIsAnyFlagEnabled(usage, (VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT))) {
 				mtlPixFmt = MTLPixelFormatX32_Stencil8;
 				if (SWIZZLE_MATCHES(R, ANY, ANY, ANY)) {
 					return VK_SUCCESS;
@@ -1562,7 +1641,8 @@
 #if MVK_MACOS
 		case MTLPixelFormatDepth24Unorm_Stencil8:
 			// If aspect mask looking only for stencil then change to stencil-only format even if shader swizzling is needed
-			if (pCreateInfo->subresourceRange.aspectMask == VK_IMAGE_ASPECT_STENCIL_BIT) {
+			if (pCreateInfo->subresourceRange.aspectMask == VK_IMAGE_ASPECT_STENCIL_BIT &&
+				mvkIsAnyFlagEnabled(usage, (VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT))) {
 				mtlPixFmt = MTLPixelFormatX24_Stencil8;
 				if (SWIZZLE_MATCHES(R, ANY, ANY, ANY)) {
 					return VK_SUCCESS;
@@ -1700,7 +1780,9 @@
 	MTLSamplerDescriptor* mtlSampDesc = [MTLSamplerDescriptor new];		// retained
 	mtlSampDesc.sAddressMode = mvkMTLSamplerAddressModeFromVkSamplerAddressMode(pCreateInfo->addressModeU);
 	mtlSampDesc.tAddressMode = mvkMTLSamplerAddressModeFromVkSamplerAddressMode(pCreateInfo->addressModeV);
-    mtlSampDesc.rAddressMode = mvkMTLSamplerAddressModeFromVkSamplerAddressMode(pCreateInfo->addressModeW);
+    if (!pCreateInfo->unnormalizedCoordinates) {
+        mtlSampDesc.rAddressMode = mvkMTLSamplerAddressModeFromVkSamplerAddressMode(pCreateInfo->addressModeW);
+    }
 	mtlSampDesc.minFilter = mvkMTLSamplerMinMagFilterFromVkFilter(pCreateInfo->minFilter);
 	mtlSampDesc.magFilter = mvkMTLSamplerMinMagFilterFromVkFilter(pCreateInfo->magFilter);
     mtlSampDesc.mipFilter = (pCreateInfo->unnormalizedCoordinates
@@ -1754,9 +1836,11 @@
 
 	_requiresConstExprSampler = (pCreateInfo->compareEnable && !_device->_pMetalFeatures->depthSampleCompare) || _ycbcrConversion;
 
-	MTLSamplerDescriptor* mtlSampDesc = newMTLSamplerDescriptor(pCreateInfo);	// temp retain
-    _mtlSamplerState = [getMTLDevice() newSamplerStateWithDescriptor: mtlSampDesc];
-	[mtlSampDesc release];														// temp release
+	@autoreleasepool {
+		MTLSamplerDescriptor* mtlSampDesc = newMTLSamplerDescriptor(pCreateInfo);	// temp retain
+		_mtlSamplerState = [getMTLDevice() newSamplerStateWithDescriptor: mtlSampDesc];
+		[mtlSampDesc release];														// temp release
+	}
 
 	initConstExprSampler(pCreateInfo);
 }
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
index ce836c1..28d7c9c 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
@@ -194,7 +194,7 @@
 
     id<MTLRenderCommandEncoder> mtlCmdEnc = cmdEncoder->_mtlRenderEncoder;
 	id<MTLComputeCommandEncoder> tessCtlEnc;
-    if ( stage != kMVKGraphicsStageTessControl && !mtlCmdEnc ) { return; }   // Pre-renderpass. Come back later.
+    if ( stage == kMVKGraphicsStageRasterization && !mtlCmdEnc ) { return; }   // Pre-renderpass. Come back later.
 
     switch (stage) {
 
@@ -386,6 +386,11 @@
 	_mtlPrimitiveType = MTLPrimitiveTypePoint;
 	if (pCreateInfo->pInputAssemblyState && !isRenderingPoints(pCreateInfo)) {
 		_mtlPrimitiveType = mvkMTLPrimitiveTypeFromVkPrimitiveTopology(pCreateInfo->pInputAssemblyState->topology);
+		// Explicitly fail creation with triangle fan topology.
+		if (pCreateInfo->pInputAssemblyState->topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN) {
+			setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "Metal does not support triangle fans."));
+			return;
+		}
 	}
 
 	// Tessellation
@@ -1134,8 +1139,8 @@
         vbCnt = pVertexInputDivisorState->vertexBindingDivisorCount;
         for (uint32_t i = 0; i < vbCnt; i++) {
             const VkVertexInputBindingDivisorDescriptionEXT* pVKVB = &pVertexInputDivisorState->pVertexBindingDivisors[i];
-            uint32_t vbIdx = getMetalBufferIndexForVertexAttributeBinding(pVKVB->binding);
-            if (shaderContext.isVertexBufferUsed(vbIdx)) {
+            if (shaderContext.isVertexBufferUsed(pVKVB->binding)) {
+                uint32_t vbIdx = getMetalBufferIndexForVertexAttributeBinding(pVKVB->binding);
                 if ((NSUInteger)inputDesc.layouts[vbIdx].stepFunction == MTLStepFunctionPerInstance ||
 					(NSUInteger)inputDesc.layouts[vbIdx].stepFunction == MTLStepFunctionThreadPositionInGridY) {
                     if (pVKVB->divisor == 0)
@@ -1353,6 +1358,7 @@
     // Multisampling
     if (pCreateInfo->pMultisampleState) {
         plDesc.sampleCount = mvkSampleCountFromVkSampleCountFlagBits(pCreateInfo->pMultisampleState->rasterizationSamples);
+        mvkRenderSubpass->setDefaultSampleCount(pCreateInfo->pMultisampleState->rasterizationSamples);
         plDesc.alphaToCoverageEnabled = pCreateInfo->pMultisampleState->alphaToCoverageEnable;
         plDesc.alphaToOneEnabled = pCreateInfo->pMultisampleState->alphaToOneEnable;
     }
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.h b/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.h
index 1df4621..fa576ab 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.h
@@ -77,6 +77,61 @@
 	kMVKMTLFmtCapsMultiPlanar = kMVKMTLFmtCapsChromaSubsampling,
 } MVKMTLFmtCaps;
 
+inline MVKMTLFmtCaps operator|(MVKMTLFmtCaps leftCaps, MVKMTLFmtCaps rightCaps) {
+	return static_cast<MVKMTLFmtCaps>(static_cast<uint32_t>(leftCaps) | rightCaps);
+}
+
+inline MVKMTLFmtCaps& operator|=(MVKMTLFmtCaps& leftCaps, MVKMTLFmtCaps rightCaps) {
+	return (leftCaps = leftCaps | rightCaps);
+}
+
+
+#pragma mark -
+#pragma mark Metal view classes
+
+enum class MVKMTLViewClass : uint8_t {
+	None,
+	Color8,
+	Color16,
+	Color32,
+	Color64,
+	Color128,
+	PVRTC_RGB_2BPP,
+	PVRTC_RGB_4BPP,
+	PVRTC_RGBA_2BPP,
+	PVRTC_RGBA_4BPP,
+	EAC_R11,
+	EAC_RG11,
+	EAC_RGBA8,
+	ETC2_RGB8,
+	ETC2_RGB8A1,
+	ASTC_4x4,
+	ASTC_5x4,
+	ASTC_5x5,
+	ASTC_6x5,
+	ASTC_6x6,
+	ASTC_8x5,
+	ASTC_8x6,
+	ASTC_8x8,
+	ASTC_10x5,
+	ASTC_10x6,
+	ASTC_10x8,
+	ASTC_10x10,
+	ASTC_12x10,
+	ASTC_12x12,
+	BC1_RGBA,
+	BC2_RGBA,
+	BC3_RGBA,
+	BC4_R,
+	BC5_RG,
+	BC6H_RGB,
+	BC7_RGBA,
+	Depth24_Stencil8,
+	Depth32_Stencil8,
+	BGRA10_XR,
+	BGR10_XR
+};
+
 
 #pragma mark -
 #pragma mark Format descriptors
@@ -114,6 +169,7 @@
 	};
 	VkFormat vkFormat;
 	MVKMTLFmtCaps mtlFmtCaps;
+	MVKMTLViewClass mtlViewClass;
 	const char* name;
 
 	inline bool isSupported() const { return (mtlPixelFormat != MTLPixelFormatInvalid) && (mtlFmtCaps != kMVKMTLFmtCapsNone); };
@@ -200,6 +256,9 @@
 	/** Returns the MSLFormatResolution of the specified chroma-subsampling (YCbCr) VkFormat */
 	SPIRV_CROSS_NAMESPACE::MSLFormatResolution getChromaSubsamplingResolution(VkFormat vkFormat);
 
+	/** Returns the MTLPixelFormat of the specified chroma-subsampling (YCbCr) VkFormat for the specified plane. */
+	MTLPixelFormat getChromaSubsamplingPlaneMTLPixelFormat(VkFormat vkFormat, uint8_t planeIndex);
+
     /** Returns the number of planes, blockTexelSize,  bytesPerBlock and mtlPixFmt of each plane of the specified chroma-subsampling (YCbCr) VkFormat into the given arrays */
     uint8_t getChromaSubsamplingPlanes(VkFormat vkFormat, VkExtent2D blockTexelSize[3], uint32_t bytesPerBlock[3], MTLPixelFormat mtlPixFmt[3]);
 
@@ -254,10 +313,16 @@
 	VkFormatProperties& getVkFormatProperties(VkFormat vkFormat);
 
 	/** Returns the Metal format capabilities supported by the specified Vulkan format, without substitution. */
-	MVKMTLFmtCaps getCapabilities(VkFormat vkFormat);
+	MVKMTLFmtCaps getCapabilities(VkFormat vkFormat, bool isExtended = false);
 
 	/** Returns the Metal format capabilities supported by the specified Metal format. */
-	MVKMTLFmtCaps getCapabilities(MTLPixelFormat mtlFormat);
+	MVKMTLFmtCaps getCapabilities(MTLPixelFormat mtlFormat, bool isExtended = false);
+
+	/** Returns the Metal view class of the specified Vulkan format. */
+	MVKMTLViewClass getViewClass(VkFormat vkFormat);
+
+	/** Returns the Metal view class of the specified Metal format. */
+	MVKMTLViewClass getViewClass(MTLPixelFormat mtlFormat);
 
 	/** Returns the name of the specified Vulkan format. */
 	const char* getName(VkFormat vkFormat);
@@ -283,12 +348,16 @@
 	/**
 	 * Returns the Metal texture usage from the Vulkan image usage and Metal format, ensuring that at least the
 	 * usages in minUsage are included, even if they wouldn't naturally be included based on the other two parameters.
-     *  isLinear further restricts the allowed usage to those that are valid for linear textures.
+     * isLinear further restricts the allowed usage to those that are valid for linear textures.
+     * isExtended expands the allowed usage to those that are valid for all formats which
+     * can be used in a view created from the specified format.
 	 */
 	MTLTextureUsage getMTLTextureUsage(VkImageUsageFlags vkImageUsageFlags,
 									   MTLPixelFormat mtlFormat,
 									   MTLTextureUsage minUsage = MTLTextureUsageUnknown,
-                                       bool isLinear = false);
+                                       bool isLinear = false,
+                                       bool isMutableFormat = true,
+                                       bool isExtended = false);
 
 	/** Enumerates all formats that support the given features, calling a specified function for each one. */
 	void enumerateSupportedFormats(VkFormatProperties properties, bool any, std::function<bool(VkFormat)> func);
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.mm b/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.mm
index 65caf00..d937e7b 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.mm
@@ -190,7 +190,7 @@
 
 	// If the MTLPixelFormat is not supported but VkFormat is valid,
 	// attempt to substitute a different format and potentially report an error.
-	if ( !mtlPixFmt && vkFormat ) {
+	if ( !mtlPixFmt && vkFormat && vkDesc.chromaSubsamplingPlaneCount <= 1 ) {
 		mtlPixFmt = vkDesc.mtlPixelFormatSubstitute;
 
 		// Report an error if there is no substitute, or the first time a substitution is made.
@@ -250,6 +250,23 @@
                                        : SPIRV_CROSS_NAMESPACE::MSL_FORMAT_RESOLUTION_420;
 }
 
+MTLPixelFormat MVKPixelFormats::getChromaSubsamplingPlaneMTLPixelFormat(VkFormat vkFormat, uint8_t planeIndex) {
+    uint8_t planes = getChromaSubsamplingPlaneCount(vkFormat);
+    uint8_t bits = getChromaSubsamplingComponentBits(vkFormat);
+    switch(planes) {
+        default:
+        case 1:
+            return getMTLPixelFormat(vkFormat);
+        case 2:
+            if (planeIndex == 1) {
+                return (bits == 8) ? MTLPixelFormatRG8Unorm : MTLPixelFormatRG16Unorm;
+            }
+            /* fallthrough */
+        case 3:
+            return (bits == 8) ? MTLPixelFormatR8Unorm : MTLPixelFormatR16Unorm;
+    }
+}
+
 uint8_t MVKPixelFormats::getChromaSubsamplingPlanes(VkFormat vkFormat, VkExtent2D blockTexelSize[3], uint32_t bytesPerBlock[3], MTLPixelFormat mtlPixFmt[3]) {
     uint8_t planes = getChromaSubsamplingPlaneCount(vkFormat);
     uint8_t bits = getChromaSubsamplingComponentBits(vkFormat);
@@ -273,7 +290,7 @@
             return 0;
         case 1:
             bytesPerBlock[0] *= 4;
-            mtlPixFmt[0] = (bits == 8) ? MTLPixelFormatRGBA8Unorm : MTLPixelFormatRGBA16Unorm;
+            mtlPixFmt[0] = getMTLPixelFormat(vkFormat);
             break;
         case 2:
             blockTexelSize[0] = VkExtent2D{1, 1};
@@ -320,12 +337,27 @@
 	return getVkFormatDesc(vkFormat).properties;
 }
 
-MVKMTLFmtCaps MVKPixelFormats::getCapabilities(VkFormat vkFormat) {
-	return getMTLPixelFormatDesc(getVkFormatDesc(vkFormat).mtlPixelFormat).mtlFmtCaps;
+MVKMTLFmtCaps MVKPixelFormats::getCapabilities(VkFormat vkFormat, bool isExtended) {
+	return getCapabilities(getVkFormatDesc(vkFormat).mtlPixelFormat, isExtended);
 }
 
-MVKMTLFmtCaps MVKPixelFormats::getCapabilities(MTLPixelFormat mtlFormat) {
-	return getMTLPixelFormatDesc(mtlFormat).mtlFmtCaps;
+MVKMTLFmtCaps MVKPixelFormats::getCapabilities(MTLPixelFormat mtlFormat, bool isExtended) {
+    MVKMTLFormatDesc& mtlDesc = getMTLPixelFormatDesc(mtlFormat);
+    MVKMTLFmtCaps caps = mtlDesc.mtlFmtCaps;
+    if (!isExtended || mtlDesc.mtlViewClass == MVKMTLViewClass::None) { return caps; }
+    // Now get caps of all formats in the view class.
+    for (auto& otherDesc : _mtlPixelFormatDescriptions) {
+        if (otherDesc.mtlViewClass == mtlDesc.mtlViewClass) { caps |= otherDesc.mtlFmtCaps; }
+    }
+    return caps;
+}
+
+MVKMTLViewClass MVKPixelFormats::getViewClass(VkFormat vkFormat) {
+    return getMTLPixelFormatDesc(getVkFormatDesc(vkFormat).mtlPixelFormat).mtlViewClass;
+}
+
+MVKMTLViewClass MVKPixelFormats::getViewClass(MTLPixelFormat mtlFormat) {
+    return getMTLPixelFormatDesc(mtlFormat).mtlViewClass;
 }
 
 const char* MVKPixelFormats::getName(VkFormat vkFormat) {
@@ -453,13 +485,15 @@
 MTLTextureUsage MVKPixelFormats::getMTLTextureUsage(VkImageUsageFlags vkImageUsageFlags,
 													MTLPixelFormat mtlFormat,
 													MTLTextureUsage minUsage,
-                                                    bool isLinear) {
+                                                    bool isLinear,
+                                                    bool isMutableFormat,
+                                                    bool isExtended) {
 	bool isDepthFmt = isDepthFormat(mtlFormat);
 	bool isStencilFmt = isStencilFormat(mtlFormat);
 	bool isCombinedDepthStencilFmt = isDepthFmt && isStencilFmt;
 	bool isColorFormat = !(isDepthFmt || isStencilFmt);
 	bool supportsStencilViews = _physicalDevice ? _physicalDevice->getMetalFeatures()->stencilViews : false;
-	MVKMTLFmtCaps mtlFmtCaps = getCapabilities(mtlFormat);
+	MVKMTLFmtCaps mtlFmtCaps = getCapabilities(mtlFormat, isExtended);
 
 	MTLTextureUsage mtlUsage = minUsage;
 
@@ -477,6 +511,14 @@
 
 		mvkEnableFlags(mtlUsage, MTLTextureUsageShaderWrite);
 	}
+#if MVK_MACOS
+    // Clearing a linear image may use shader writes.
+    if (mvkIsAnyFlagEnabled(vkImageUsageFlags, (VK_IMAGE_USAGE_TRANSFER_DST_BIT)) &&
+        mvkIsAnyFlagEnabled(mtlFmtCaps, kMVKMTLFmtCapsWrite) && isLinear) {
+
+		mvkEnableFlags(mtlUsage, MTLTextureUsageShaderWrite);
+    }
+#endif
 
 	// Render to but only if format supports rendering...
 	if (mvkIsAnyFlagEnabled(vkImageUsageFlags, (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
@@ -494,13 +536,21 @@
 	}
 
 	// Create view on, but only on color formats, or combined depth-stencil formats if supported by the GPU...
-	if (mvkIsAnyFlagEnabled(vkImageUsageFlags, (VK_IMAGE_USAGE_TRANSFER_SRC_BIT |	 		// May use temp view if transfer involves format change
-												VK_IMAGE_USAGE_SAMPLED_BIT |
+	if ((mvkIsAnyFlagEnabled(vkImageUsageFlags, (VK_IMAGE_USAGE_TRANSFER_SRC_BIT)) || 		// May use temp view if transfer involves format change
+		 (isMutableFormat &&
+		  mvkIsAnyFlagEnabled(vkImageUsageFlags, (VK_IMAGE_USAGE_SAMPLED_BIT |
+												  VK_IMAGE_USAGE_STORAGE_BIT |
+												  VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT |
+												  VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)))) &&
+		isColorFormat) {
+
+		mvkEnableFlags(mtlUsage, MTLTextureUsagePixelFormatView);
+	}
+	if (mvkIsAnyFlagEnabled(vkImageUsageFlags, (VK_IMAGE_USAGE_TRANSFER_SRC_BIT | 		// May use temp view if transfer involves format change
+		 										VK_IMAGE_USAGE_SAMPLED_BIT |
 												VK_IMAGE_USAGE_STORAGE_BIT |
-												VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT |
-												VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
-												VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)) &&
-		(isColorFormat || (isCombinedDepthStencilFmt && supportsStencilViews))) {
+												VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT)) &&
+		isCombinedDepthStencilFmt && supportsStencilViews) {
 
 		mvkEnableFlags(mtlUsage, MTLTextureUsagePixelFormatView);
 	}
@@ -817,9 +867,9 @@
     addVkFormatDescChromaSubsampling( G8_B8_R8_3PLANE_422_UNORM, Invalid, 3, 8, 2, 1, 4 );
     addVkFormatDescChromaSubsampling( G8_B8R8_2PLANE_422_UNORM, Invalid, 2, 8, 2, 1, 4 );
     addVkFormatDescChromaSubsampling( G8_B8_R8_3PLANE_444_UNORM, Invalid, 3, 8, 1, 1, 3 );
-    addVkFormatDescChromaSubsampling( R10X6_UNORM_PACK16, Invalid, 0, 10, 1, 1, 2 );
-    addVkFormatDescChromaSubsampling( R10X6G10X6_UNORM_2PACK16, Invalid, 0, 10, 1, 1, 4 );
-    addVkFormatDescChromaSubsampling( R10X6G10X6B10X6A10X6_UNORM_4PACK16, Invalid, 0, 10, 1, 1, 8 );
+    addVkFormatDescChromaSubsampling( R10X6_UNORM_PACK16, R16Unorm, 0, 10, 1, 1, 2 );
+    addVkFormatDescChromaSubsampling( R10X6G10X6_UNORM_2PACK16, RG16Unorm, 0, 10, 1, 1, 4 );
+    addVkFormatDescChromaSubsampling( R10X6G10X6B10X6A10X6_UNORM_4PACK16, RGBA16Unorm, 0, 10, 1, 1, 8 );
     addVkFormatDescChromaSubsampling( G10X6B10X6G10X6R10X6_422_UNORM_4PACK16, Invalid, 1, 10, 2, 1, 8 );
     addVkFormatDescChromaSubsampling( B10X6G10X6R10X6G10X6_422_UNORM_4PACK16, Invalid, 1, 10, 2, 1, 8 );
     addVkFormatDescChromaSubsampling( G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16, Invalid, 3, 10, 2, 2, 12 );
@@ -827,9 +877,9 @@
     addVkFormatDescChromaSubsampling( G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16, Invalid, 3, 10, 2, 1, 8 );
     addVkFormatDescChromaSubsampling( G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16, Invalid, 2, 10, 2, 1, 8 );
     addVkFormatDescChromaSubsampling( G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16, Invalid, 3, 10, 1, 1, 6 );
-    addVkFormatDescChromaSubsampling( R12X4_UNORM_PACK16, Invalid, 0, 12, 1, 1, 2 );
-    addVkFormatDescChromaSubsampling( R12X4G12X4_UNORM_2PACK16, Invalid, 0, 12, 1, 1, 4 );
-    addVkFormatDescChromaSubsampling( R12X4G12X4B12X4A12X4_UNORM_4PACK16, Invalid, 0, 12, 1, 1, 8 );
+    addVkFormatDescChromaSubsampling( R12X4_UNORM_PACK16, R16Unorm, 0, 12, 1, 1, 2 );
+    addVkFormatDescChromaSubsampling( R12X4G12X4_UNORM_2PACK16, RG16Unorm, 0, 12, 1, 1, 4 );
+    addVkFormatDescChromaSubsampling( R12X4G12X4B12X4A12X4_UNORM_4PACK16, RGBA16Unorm, 0, 12, 1, 1, 8 );
     addVkFormatDescChromaSubsampling( G12X4B12X4G12X4R12X4_422_UNORM_4PACK16, Invalid, 1, 12, 2, 1, 8 );
     addVkFormatDescChromaSubsampling( B12X4G12X4R12X4G12X4_422_UNORM_4PACK16, Invalid, 1, 12, 2, 1, 8 );
     addVkFormatDescChromaSubsampling( G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16, Invalid, 3, 12, 2, 2, 12 );
@@ -848,11 +898,11 @@
 	// When adding to this list, be sure to ensure _vkFormatCount is large enough for the format count
 }
 
-#define addMTLPixelFormatDesc(MTL_FMT, IOS_CAPS, MACOS_CAPS)  \
+#define addMTLPixelFormatDesc(MTL_FMT, VIEW_CLASS, IOS_CAPS, MACOS_CAPS)  \
 	MVKAssert(fmtIdx < _mtlPixelFormatCount, "Attempting to describe %d MTLPixelFormats, but only have space for %d. Increase the value of _mtlPixelFormatCount", fmtIdx + 1, _mtlPixelFormatCount);  \
 	_mtlPixelFormatDescriptions[fmtIdx++] = { .mtlPixelFormat = MTLPixelFormat ##MTL_FMT, VK_FORMAT_UNDEFINED,  \
 											  mvkSelectPlatformValue<MVKMTLFmtCaps>(kMVKMTLFmtCaps ##MACOS_CAPS, kMVKMTLFmtCaps ##IOS_CAPS),  \
-											  "MTLPixelFormat" #MTL_FMT }
+											  MVKMTLViewClass:: VIEW_CLASS, "MTLPixelFormat" #MTL_FMT }
 
 void MVKPixelFormats::initMTLPixelFormatCapabilities() {
 
@@ -863,159 +913,157 @@
 	// When adding to this list, be sure to ensure _mtlPixelFormatCount is large enough for the format count
 
 	// MTLPixelFormatInvalid must come first.
-	addMTLPixelFormatDesc( Invalid, None, None );
+	addMTLPixelFormatDesc( Invalid, None, None, None );
 
 	// Ordinary 8-bit pixel formats
-	addMTLPixelFormatDesc( A8Unorm, RF, RF );
-	addMTLPixelFormatDesc( R8Unorm, All, All );
-	addMTLPixelFormatDesc( R8Unorm_sRGB, RFCMRB, None );
-	addMTLPixelFormatDesc( R8Snorm, RFWCMB, All );
-	addMTLPixelFormatDesc( R8Uint, RWCM, RWCM );
-	addMTLPixelFormatDesc( R8Sint, RWCM, RWCM );
+	addMTLPixelFormatDesc( A8Unorm, Color8, RF, RF );
+	addMTLPixelFormatDesc( R8Unorm, Color8, All, All );
+	addMTLPixelFormatDesc( R8Unorm_sRGB, Color8, RFCMRB, None );
+	addMTLPixelFormatDesc( R8Snorm, Color8, RFWCMB, All );
+	addMTLPixelFormatDesc( R8Uint, Color8, RWCM, RWCM );
+	addMTLPixelFormatDesc( R8Sint, Color8, RWCM, RWCM );
 
 	// Ordinary 16-bit pixel formats
-	addMTLPixelFormatDesc( R16Unorm, RFWCMB, All );
-	addMTLPixelFormatDesc( R16Snorm, RFWCMB, All );
-	addMTLPixelFormatDesc( R16Uint, RWCM, RWCM );
-	addMTLPixelFormatDesc( R16Sint, RWCM, RWCM );
-	addMTLPixelFormatDesc( R16Float, All, All );
+	addMTLPixelFormatDesc( R16Unorm, Color16, RFWCMB, All );
+	addMTLPixelFormatDesc( R16Snorm, Color16, RFWCMB, All );
+	addMTLPixelFormatDesc( R16Uint, Color16, RWCM, RWCM );
+	addMTLPixelFormatDesc( R16Sint, Color16, RWCM, RWCM );
+	addMTLPixelFormatDesc( R16Float, Color16, All, All );
 
-	addMTLPixelFormatDesc( RG8Unorm, All, All );
-	addMTLPixelFormatDesc( RG8Unorm_sRGB, RFCMRB, None );
-	addMTLPixelFormatDesc( RG8Snorm, RFWCMB, All );
-	addMTLPixelFormatDesc( RG8Uint, RWCM, RWCM );
-	addMTLPixelFormatDesc( RG8Sint, RWCM, RWCM );
+	addMTLPixelFormatDesc( RG8Unorm, Color16, All, All );
+	addMTLPixelFormatDesc( RG8Unorm_sRGB, Color16, RFCMRB, None );
+	addMTLPixelFormatDesc( RG8Snorm, Color16, RFWCMB, All );
+	addMTLPixelFormatDesc( RG8Uint, Color16, RWCM, RWCM );
+	addMTLPixelFormatDesc( RG8Sint, Color16, RWCM, RWCM );
 
 	// Packed 16-bit pixel formats
-	addMTLPixelFormatDesc( B5G6R5Unorm, RFCMRB, None );
-	addMTLPixelFormatDesc( A1BGR5Unorm, RFCMRB, None );
-	addMTLPixelFormatDesc( ABGR4Unorm, RFCMRB, None );
-	addMTLPixelFormatDesc( BGR5A1Unorm, RFCMRB, None );
+	addMTLPixelFormatDesc( B5G6R5Unorm, Color16, RFCMRB, None );
+	addMTLPixelFormatDesc( A1BGR5Unorm, Color16, RFCMRB, None );
+	addMTLPixelFormatDesc( ABGR4Unorm, Color16, RFCMRB, None );
+	addMTLPixelFormatDesc( BGR5A1Unorm, Color16, RFCMRB, None );
 
 	// Ordinary 32-bit pixel formats
-	addMTLPixelFormatDesc( R32Uint, RC, RWCM );
-	addMTLPixelFormatDesc( R32Sint, RC, RWCM );
-	addMTLPixelFormatDesc( R32Float, RCMB, All );
+	addMTLPixelFormatDesc( R32Uint, Color32, RC, RWCM );
+	addMTLPixelFormatDesc( R32Sint, Color32, RC, RWCM );
+	addMTLPixelFormatDesc( R32Float, Color32, RCMB, All );
 
-	addMTLPixelFormatDesc( RG16Unorm, RFWCMB, All );
-	addMTLPixelFormatDesc( RG16Snorm, RFWCMB, All );
-	addMTLPixelFormatDesc( RG16Uint, RWCM, RWCM );
-	addMTLPixelFormatDesc( RG16Sint, RWCM, RWCM );
-	addMTLPixelFormatDesc( RG16Float, All, All );
+	addMTLPixelFormatDesc( RG16Unorm, Color32, RFWCMB, All );
+	addMTLPixelFormatDesc( RG16Snorm, Color32, RFWCMB, All );
+	addMTLPixelFormatDesc( RG16Uint, Color32, RWCM, RWCM );
+	addMTLPixelFormatDesc( RG16Sint, Color32, RWCM, RWCM );
+	addMTLPixelFormatDesc( RG16Float, Color32, All, All );
 
-	addMTLPixelFormatDesc( RGBA8Unorm, All, All );
-	addMTLPixelFormatDesc( RGBA8Unorm_sRGB, RFCMRB, RFCMRB );
-	addMTLPixelFormatDesc( RGBA8Snorm, RFWCMB, All );
-	addMTLPixelFormatDesc( RGBA8Uint, RWCM, RWCM );
-	addMTLPixelFormatDesc( RGBA8Sint, RWCM, RWCM );
+	addMTLPixelFormatDesc( RGBA8Unorm, Color32, All, All );
+	addMTLPixelFormatDesc( RGBA8Unorm_sRGB, Color32, RFCMRB, RFCMRB );
+	addMTLPixelFormatDesc( RGBA8Snorm, Color32, RFWCMB, All );
+	addMTLPixelFormatDesc( RGBA8Uint, Color32, RWCM, RWCM );
+	addMTLPixelFormatDesc( RGBA8Sint, Color32, RWCM, RWCM );
 
-	addMTLPixelFormatDesc( BGRA8Unorm, All, All );
-	addMTLPixelFormatDesc( BGRA8Unorm_sRGB, RFCMRB, RFCMRB );
+	addMTLPixelFormatDesc( BGRA8Unorm, Color32, All, All );
+	addMTLPixelFormatDesc( BGRA8Unorm_sRGB, Color32, RFCMRB, RFCMRB );
 
 	// Packed 32-bit pixel formats
-	addMTLPixelFormatDesc( RGB10A2Unorm, RFCMRB, All );
-	addMTLPixelFormatDesc( RGB10A2Uint, RCM, RWCM );
-	addMTLPixelFormatDesc( RG11B10Float, RFCMRB, All );
-	addMTLPixelFormatDesc( RGB9E5Float, RFCMRB, RF );
+	addMTLPixelFormatDesc( RGB10A2Unorm, Color32, RFCMRB, All );
+	addMTLPixelFormatDesc( RGB10A2Uint, Color32, RCM, RWCM );
+	addMTLPixelFormatDesc( RG11B10Float, Color32, RFCMRB, All );
+	addMTLPixelFormatDesc( RGB9E5Float, Color32, RFCMRB, RF );
 
 	// Ordinary 64-bit pixel formats
-	addMTLPixelFormatDesc( RG32Uint, RC, RWCM );
-	addMTLPixelFormatDesc( RG32Sint, RC, RWCM );
-	addMTLPixelFormatDesc( RG32Float, RCB, All );
+	addMTLPixelFormatDesc( RG32Uint, Color64, RC, RWCM );
+	addMTLPixelFormatDesc( RG32Sint, Color64, RC, RWCM );
+	addMTLPixelFormatDesc( RG32Float, Color64, RCB, All );
 
-	addMTLPixelFormatDesc( RGBA16Unorm, RFWCMB, All );
-	addMTLPixelFormatDesc( RGBA16Snorm, RFWCMB, All );
-	addMTLPixelFormatDesc( RGBA16Uint, RWCM, RWCM );
-	addMTLPixelFormatDesc( RGBA16Sint, RWCM, RWCM );
-	addMTLPixelFormatDesc( RGBA16Float, All, All );
+	addMTLPixelFormatDesc( RGBA16Unorm, Color64, RFWCMB, All );
+	addMTLPixelFormatDesc( RGBA16Snorm, Color64, RFWCMB, All );
+	addMTLPixelFormatDesc( RGBA16Uint, Color64, RWCM, RWCM );
+	addMTLPixelFormatDesc( RGBA16Sint, Color64, RWCM, RWCM );
+	addMTLPixelFormatDesc( RGBA16Float, Color64, All, All );
 
 	// Ordinary 128-bit pixel formats
-	addMTLPixelFormatDesc( RGBA32Uint, RC, RWCM );
-	addMTLPixelFormatDesc( RGBA32Sint, RC, RWCM );
-	addMTLPixelFormatDesc( RGBA32Float, RC, All );
+	addMTLPixelFormatDesc( RGBA32Uint, Color128, RC, RWCM );
+	addMTLPixelFormatDesc( RGBA32Sint, Color128, RC, RWCM );
+	addMTLPixelFormatDesc( RGBA32Float, Color128, RC, All );
 
 	// Compressed pixel formats
-	addMTLPixelFormatDesc( PVRTC_RGBA_2BPP, RF, None );
-	addMTLPixelFormatDesc( PVRTC_RGBA_4BPP, RF, None );
-	addMTLPixelFormatDesc( PVRTC_RGBA_2BPP_sRGB, RF, None );
-	addMTLPixelFormatDesc( PVRTC_RGBA_4BPP_sRGB, RF, None );
+	addMTLPixelFormatDesc( PVRTC_RGBA_2BPP, PVRTC_RGBA_2BPP, RF, None );
+	addMTLPixelFormatDesc( PVRTC_RGBA_4BPP, PVRTC_RGBA_4BPP, RF, None );
+	addMTLPixelFormatDesc( PVRTC_RGBA_2BPP_sRGB, PVRTC_RGBA_2BPP, RF, None );
+	addMTLPixelFormatDesc( PVRTC_RGBA_4BPP_sRGB, PVRTC_RGBA_4BPP, RF, None );
 
-	addMTLPixelFormatDesc( ETC2_RGB8, RF, None );
-	addMTLPixelFormatDesc( ETC2_RGB8_sRGB, RF, None );
-	addMTLPixelFormatDesc( ETC2_RGB8A1, RF, None );
-	addMTLPixelFormatDesc( ETC2_RGB8A1_sRGB, RF, None );
-	addMTLPixelFormatDesc( EAC_RGBA8, RF, None );
-	addMTLPixelFormatDesc( EAC_RGBA8_sRGB, RF, None );
-	addMTLPixelFormatDesc( EAC_R11Unorm, RF, None );
-	addMTLPixelFormatDesc( EAC_R11Snorm, RF, None );
-	addMTLPixelFormatDesc( EAC_RG11Unorm, RF, None );
-	addMTLPixelFormatDesc( EAC_RG11Snorm, RF, None );
+	addMTLPixelFormatDesc( ETC2_RGB8, ETC2_RGB8, RF, None );
+	addMTLPixelFormatDesc( ETC2_RGB8_sRGB, ETC2_RGB8, RF, None );
+	addMTLPixelFormatDesc( ETC2_RGB8A1, ETC2_RGB8A1, RF, None );
+	addMTLPixelFormatDesc( ETC2_RGB8A1_sRGB, ETC2_RGB8A1, RF, None );
+	addMTLPixelFormatDesc( EAC_RGBA8, EAC_RGBA8, RF, None );
+	addMTLPixelFormatDesc( EAC_RGBA8_sRGB, EAC_RGBA8, RF, None );
+	addMTLPixelFormatDesc( EAC_R11Unorm, EAC_R11, RF, None );
+	addMTLPixelFormatDesc( EAC_R11Snorm, EAC_R11, RF, None );
+	addMTLPixelFormatDesc( EAC_RG11Unorm, EAC_RG11, RF, None );
+	addMTLPixelFormatDesc( EAC_RG11Snorm, EAC_RG11, RF, None );
 
-	addMTLPixelFormatDesc( ASTC_4x4_LDR, None, None );
-	addMTLPixelFormatDesc( ASTC_4x4_sRGB, None, None );
-	addMTLPixelFormatDesc( ASTC_5x4_LDR, None, None );
-	addMTLPixelFormatDesc( ASTC_5x4_sRGB, None, None );
-	addMTLPixelFormatDesc( ASTC_5x5_LDR, None, None );
-	addMTLPixelFormatDesc( ASTC_5x5_sRGB, None, None );
-	addMTLPixelFormatDesc( ASTC_6x5_LDR, None, None );
-	addMTLPixelFormatDesc( ASTC_6x5_sRGB, None, None );
-	addMTLPixelFormatDesc( ASTC_6x6_LDR, None, None );
-	addMTLPixelFormatDesc( ASTC_6x6_sRGB, None, None );
-	addMTLPixelFormatDesc( ASTC_8x5_LDR, None, None );
-	addMTLPixelFormatDesc( ASTC_8x5_sRGB, None, None );
-	addMTLPixelFormatDesc( ASTC_8x6_LDR, None, None );
-	addMTLPixelFormatDesc( ASTC_8x6_sRGB, None, None );
-	addMTLPixelFormatDesc( ASTC_8x8_LDR, None, None );
-	addMTLPixelFormatDesc( ASTC_8x8_sRGB, None, None );
-	addMTLPixelFormatDesc( ASTC_10x5_LDR, None, None );
-	addMTLPixelFormatDesc( ASTC_10x5_sRGB, None, None );
-	addMTLPixelFormatDesc( ASTC_10x6_LDR, None, None );
-	addMTLPixelFormatDesc( ASTC_10x6_sRGB, None, None );
-	addMTLPixelFormatDesc( ASTC_10x8_LDR, None, None );
-	addMTLPixelFormatDesc( ASTC_10x8_sRGB, None, None );
-	addMTLPixelFormatDesc( ASTC_10x10_LDR, None, None );
-	addMTLPixelFormatDesc( ASTC_10x10_sRGB, None, None );
-	addMTLPixelFormatDesc( ASTC_12x10_LDR, None, None );
-	addMTLPixelFormatDesc( ASTC_12x10_sRGB, None, None );
-	addMTLPixelFormatDesc( ASTC_12x12_LDR, None, None );
-	addMTLPixelFormatDesc( ASTC_12x12_sRGB, None, None );
+	addMTLPixelFormatDesc( ASTC_4x4_LDR, ASTC_4x4, None, None );
+	addMTLPixelFormatDesc( ASTC_4x4_sRGB, ASTC_4x4, None, None );
+	addMTLPixelFormatDesc( ASTC_5x4_LDR, ASTC_5x4, None, None );
+	addMTLPixelFormatDesc( ASTC_5x4_sRGB, ASTC_5x4, None, None );
+	addMTLPixelFormatDesc( ASTC_5x5_LDR, ASTC_5x5, None, None );
+	addMTLPixelFormatDesc( ASTC_5x5_sRGB, ASTC_5x5, None, None );
+	addMTLPixelFormatDesc( ASTC_6x5_LDR, ASTC_6x5, None, None );
+	addMTLPixelFormatDesc( ASTC_6x5_sRGB, ASTC_6x5, None, None );
+	addMTLPixelFormatDesc( ASTC_6x6_LDR, ASTC_6x6, None, None );
+	addMTLPixelFormatDesc( ASTC_6x6_sRGB, ASTC_6x6, None, None );
+	addMTLPixelFormatDesc( ASTC_8x5_LDR, ASTC_8x5, None, None );
+	addMTLPixelFormatDesc( ASTC_8x5_sRGB, ASTC_8x5, None, None );
+	addMTLPixelFormatDesc( ASTC_8x6_LDR, ASTC_8x6, None, None );
+	addMTLPixelFormatDesc( ASTC_8x6_sRGB, ASTC_8x6, None, None );
+	addMTLPixelFormatDesc( ASTC_8x8_LDR, ASTC_8x8, None, None );
+	addMTLPixelFormatDesc( ASTC_8x8_sRGB, ASTC_8x8, None, None );
+	addMTLPixelFormatDesc( ASTC_10x5_LDR, ASTC_10x5, None, None );
+	addMTLPixelFormatDesc( ASTC_10x5_sRGB, ASTC_10x5, None, None );
+	addMTLPixelFormatDesc( ASTC_10x6_LDR, ASTC_10x6, None, None );
+	addMTLPixelFormatDesc( ASTC_10x6_sRGB, ASTC_10x6, None, None );
+	addMTLPixelFormatDesc( ASTC_10x8_LDR, ASTC_10x8, None, None );
+	addMTLPixelFormatDesc( ASTC_10x8_sRGB, ASTC_10x8, None, None );
+	addMTLPixelFormatDesc( ASTC_10x10_LDR, ASTC_10x10, None, None );
+	addMTLPixelFormatDesc( ASTC_10x10_sRGB, ASTC_10x10, None, None );
+	addMTLPixelFormatDesc( ASTC_12x10_LDR, ASTC_12x10, None, None );
+	addMTLPixelFormatDesc( ASTC_12x10_sRGB, ASTC_12x10, None, None );
+	addMTLPixelFormatDesc( ASTC_12x12_LDR, ASTC_12x12, None, None );
+	addMTLPixelFormatDesc( ASTC_12x12_sRGB, ASTC_12x12, None, None );
 
-	addMTLPixelFormatDesc( BC1_RGBA, None, RF );
-	addMTLPixelFormatDesc( BC1_RGBA_sRGB, None, RF );
-	addMTLPixelFormatDesc( BC1_RGBA, None, RF );
-	addMTLPixelFormatDesc( BC1_RGBA_sRGB, None, RF );
-	addMTLPixelFormatDesc( BC2_RGBA, None, RF );
-	addMTLPixelFormatDesc( BC2_RGBA_sRGB, None, RF );
-	addMTLPixelFormatDesc( BC3_RGBA, None, RF );
-	addMTLPixelFormatDesc( BC3_RGBA_sRGB, None, RF );
-	addMTLPixelFormatDesc( BC4_RUnorm, None, RF );
-	addMTLPixelFormatDesc( BC4_RSnorm, None, RF );
-	addMTLPixelFormatDesc( BC5_RGUnorm, None, RF );
-	addMTLPixelFormatDesc( BC5_RGSnorm, None, RF );
-	addMTLPixelFormatDesc( BC6H_RGBUfloat, None, RF );
-	addMTLPixelFormatDesc( BC6H_RGBFloat, None, RF );
-	addMTLPixelFormatDesc( BC7_RGBAUnorm, None, RF );
-	addMTLPixelFormatDesc( BC7_RGBAUnorm_sRGB, None, RF );
+	addMTLPixelFormatDesc( BC1_RGBA, BC1_RGBA, None, RF );
+	addMTLPixelFormatDesc( BC1_RGBA_sRGB, BC1_RGBA, None, RF );
+	addMTLPixelFormatDesc( BC2_RGBA, BC2_RGBA, None, RF );
+	addMTLPixelFormatDesc( BC2_RGBA_sRGB, BC2_RGBA, None, RF );
+	addMTLPixelFormatDesc( BC3_RGBA, BC3_RGBA, None, RF );
+	addMTLPixelFormatDesc( BC3_RGBA_sRGB, BC3_RGBA, None, RF );
+	addMTLPixelFormatDesc( BC4_RUnorm, BC4_R, None, RF );
+	addMTLPixelFormatDesc( BC4_RSnorm, BC4_R, None, RF );
+	addMTLPixelFormatDesc( BC5_RGUnorm, BC5_RG, None, RF );
+	addMTLPixelFormatDesc( BC5_RGSnorm, BC5_RG, None, RF );
+	addMTLPixelFormatDesc( BC6H_RGBUfloat, BC6H_RGB, None, RF );
+	addMTLPixelFormatDesc( BC6H_RGBFloat, BC6H_RGB, None, RF );
+	addMTLPixelFormatDesc( BC7_RGBAUnorm, BC7_RGBA, None, RF );
+	addMTLPixelFormatDesc( BC7_RGBAUnorm_sRGB, BC7_RGBA, None, RF );
 
 	// YUV pixel formats
-	addMTLPixelFormatDesc( GBGR422, RF, RF );
-	addMTLPixelFormatDesc( BGRG422, RF, RF );
+	addMTLPixelFormatDesc( GBGR422, None, RF, RF );
+	addMTLPixelFormatDesc( BGRG422, None, RF, RF );
 
 	// Extended range and wide color pixel formats
-	addMTLPixelFormatDesc( BGRA10_XR, None, None );
-	addMTLPixelFormatDesc( BGRA10_XR_sRGB, None, None );
-	addMTLPixelFormatDesc( BGR10_XR, None, None );
-	addMTLPixelFormatDesc( BGR10_XR_sRGB, None, None );
-	addMTLPixelFormatDesc( BGR10A2Unorm, None, None );
+	addMTLPixelFormatDesc( BGRA10_XR, BGRA10_XR, None, None );
+	addMTLPixelFormatDesc( BGRA10_XR_sRGB, BGRA10_XR, None, None );
+	addMTLPixelFormatDesc( BGR10_XR, BGR10_XR, None, None );
+	addMTLPixelFormatDesc( BGR10_XR_sRGB, BGR10_XR, None, None );
+	addMTLPixelFormatDesc( BGR10A2Unorm, Color32, None, None );
 
 	// Depth and stencil pixel formats
-	addMTLPixelFormatDesc( Depth16Unorm, None, None );
-	addMTLPixelFormatDesc( Depth32Float, DRM, DRFMR );
-	addMTLPixelFormatDesc( Stencil8, DRM, DRM );
-	addMTLPixelFormatDesc( Depth24Unorm_Stencil8, None, None );
-	addMTLPixelFormatDesc( Depth32Float_Stencil8, DRM, DRFMR );
-	addMTLPixelFormatDesc( X24_Stencil8, None, DRM );
-	addMTLPixelFormatDesc( X32_Stencil8, DRM, DRM );
+	addMTLPixelFormatDesc( Depth16Unorm, None, None, None );
+	addMTLPixelFormatDesc( Depth32Float, None, DRM, DRFMR );
+	addMTLPixelFormatDesc( Stencil8, None, DRM, DRMR );
+	addMTLPixelFormatDesc( Depth24Unorm_Stencil8, Depth24_Stencil8, None, None );
+	addMTLPixelFormatDesc( Depth32Float_Stencil8, Depth32_Stencil8, DRM, DRFMR );
+	addMTLPixelFormatDesc( X24_Stencil8, Depth24_Stencil8, None, DRMR );
+	addMTLPixelFormatDesc( X32_Stencil8, Depth32_Stencil8, DRM, DRMR );
 
 	// When adding to this list, be sure to ensure _mtlPixelFormatCount is large enough for the format count
 }
@@ -1024,7 +1072,7 @@
 	MVKAssert(fmtIdx < _mtlVertexFormatCount, "Attempting to describe %d MTLVertexFormats, but only have space for %d. Increase the value of _mtlVertexFormatCount", fmtIdx + 1, _mtlVertexFormatCount);  \
 	_mtlVertexFormatDescriptions[fmtIdx++] = { .mtlVertexFormat = MTLVertexFormat ##MTL_VTX_FMT, VK_FORMAT_UNDEFINED,  \
                                                mvkSelectPlatformValue<MVKMTLFmtCaps>(kMVKMTLFmtCaps ##MACOS_CAPS, kMVKMTLFmtCaps ##IOS_CAPS),  \
-                                               "MTLVertexFormat" #MTL_VTX_FMT }
+                                               MVKMTLViewClass::None, "MTLVertexFormat" #MTL_VTX_FMT }
 
 void MVKPixelFormats::initMTLVertexFormatCapabilities() {
 
@@ -1322,6 +1370,7 @@
 
 	addFeatSetMTLPixFmtCaps( tvOS_GPUFamily2_v1, Depth32Float, DRMR );
 	addFeatSetMTLPixFmtCaps( tvOS_GPUFamily2_v1, Depth32Float_Stencil8, DRMR );
+	addFeatSetMTLPixFmtCaps( tvOS_GPUFamily2_v1, Stencil8, DRMR );
 
 	addFeatSetMTLPixFmtCaps(tvOS_GPUFamily2_v1, BGRA10_XR, All );
 	addFeatSetMTLPixFmtCaps(tvOS_GPUFamily2_v1, BGRA10_XR_sRGB, All );
@@ -1460,6 +1509,7 @@
 
 	addFeatSetMTLPixFmtCaps( iOS_GPUFamily3_v1, Depth32Float, DRMR );
 	addFeatSetMTLPixFmtCaps( iOS_GPUFamily3_v1, Depth32Float_Stencil8, DRMR );
+	addFeatSetMTLPixFmtCaps( iOS_GPUFamily3_v1, Stencil8, DRMR );
 
 	addFeatSetMTLPixFmtCaps( iOS_GPUFamily3_v2, BGRA10_XR, All );
 	addFeatSetMTLPixFmtCaps( iOS_GPUFamily3_v2, BGRA10_XR_sRGB, All );
@@ -1600,9 +1650,7 @@
 	kMVKVkFormatFeatureFlagsTexDSAtt    = (VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT),
 	kMVKVkFormatFeatureFlagsTexBlend    = (VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT),
     kMVKVkFormatFeatureFlagsTexTransfer          = (VK_FORMAT_FEATURE_TRANSFER_SRC_BIT |
-                                                    VK_FORMAT_FEATURE_TRANSFER_DST_BIT |
-                                                    VK_FORMAT_FEATURE_BLIT_SRC_BIT |
-                                                    VK_FORMAT_FEATURE_BLIT_DST_BIT),
+                                                    VK_FORMAT_FEATURE_TRANSFER_DST_BIT),
     kMVKVkFormatFeatureFlagsTexChromaSubsampling = (VK_FORMAT_FEATURE_MIDPOINT_CHROMA_SAMPLES_BIT_KHR |
                                                     VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT_KHR),
     kMVKVkFormatFeatureFlagsTexMultiPlanar       = (VK_FORMAT_FEATURE_COSITED_CHROMA_SAMPLES_BIT_KHR |
@@ -1630,12 +1678,13 @@
     vkProps.linearTilingFeatures = kMVKVkFormatFeatureFlagsTexNone;
 
     // Chroma subsampling and multi planar features
-    if (getChromaSubsamplingComponentBits(vkDesc.vkFormat) > 0) {
-        vkProps.optimalTilingFeatures = kMVKVkFormatFeatureFlagsTexTransfer;
-    }
     uint8_t chromaSubsamplingPlaneCount = getChromaSubsamplingPlaneCount(vkDesc.vkFormat);
-    if (chromaSubsamplingPlaneCount > 0) {
-        mtlPixFmtCaps = kMVKMTLFmtCapsRF;
+    uint8_t chromaSubsamplingComponentBits = getChromaSubsamplingComponentBits(vkDesc.vkFormat);
+    if (chromaSubsamplingComponentBits > 0) {
+        if (mtlPixFmtCaps != 0 || chromaSubsamplingPlaneCount > 1) {
+            mtlPixFmtCaps = kMVKMTLFmtCapsRF;
+            vkProps.optimalTilingFeatures = kMVKVkFormatFeatureFlagsTexTransfer;
+        }
         enableFormatFeatures(ChromaSubsampling, Tex, mtlPixFmtCaps, vkProps.optimalTilingFeatures);
     }
     if (chromaSubsamplingPlaneCount > 1) {
@@ -1650,8 +1699,15 @@
 	enableFormatFeatures(DSAtt, Tex, mtlPixFmtCaps, vkProps.optimalTilingFeatures);
 	enableFormatFeatures(Blend, Tex, mtlPixFmtCaps, vkProps.optimalTilingFeatures);
 
+	if (chromaSubsamplingComponentBits > 0) {
+		// Vulkan forbids blits between chroma-subsampled formats.
+		mvkDisableFlags(vkProps.optimalTilingFeatures, (VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT));
+	}
+
 	// Linear tiling is not available to depth/stencil or compressed formats.
-	if ( !(vkDesc.formatType == kMVKFormatDepthStencil || vkDesc.formatType == kMVKFormatCompressed) ) {
+	// GBGR and BGRG formats also do not support linear tiling in Metal.
+	if ( !(vkDesc.formatType == kMVKFormatDepthStencil || vkDesc.formatType == kMVKFormatCompressed ||
+		   (chromaSubsamplingPlaneCount == 1 && vkDesc.blockTexelSize.width > 1)) ) {
 		// Start with optimal tiling features, and modify.
 		vkProps.linearTilingFeatures = vkProps.optimalTilingFeatures;
 
@@ -1666,9 +1722,10 @@
 #endif
 	}
 
-	// Texel buffers are not available to depth/stencil or compressed formats.
+	// Texel buffers are not available to depth/stencil, compressed, or chroma subsampled formats.
 	vkProps.bufferFeatures = kMVKVkFormatFeatureFlagsTexNone;
-	if ( !(vkDesc.formatType == kMVKFormatDepthStencil || vkDesc.formatType == kMVKFormatCompressed) ) {
+	if ( !(vkDesc.formatType == kMVKFormatDepthStencil || vkDesc.formatType == kMVKFormatCompressed ||
+		   chromaSubsamplingComponentBits > 0) ) {
 		enableFormatFeatures(Read, Buf, mtlPixFmtCaps, vkProps.bufferFeatures);
 		enableFormatFeatures(Write, Buf, mtlPixFmtCaps, vkProps.bufferFeatures);
 		enableFormatFeatures(Atomic, Buf, mtlPixFmtCaps, vkProps.bufferFeatures);
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h
index f8decda..7706fd3 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h
@@ -67,6 +67,9 @@
 	/** Returns the Vulkan sample count of the attachments used in this subpass. */
 	VkSampleCountFlagBits getSampleCount();
 
+	/** Sets the default sample count for when there are no attachments used in this subpass. */
+	void setDefaultSampleCount(VkSampleCountFlagBits count) { _defaultSampleCount = count; }
+
 	/** Returns whether or not this is a multiview subpass. */
 	bool isMultiview() const { return _viewMask != 0; }
 
@@ -140,7 +143,11 @@
 	MVKSmallVector<VkAttachmentReference2, kMVKDefaultAttachmentCount> _resolveAttachments;
 	MVKSmallVector<uint32_t, kMVKDefaultAttachmentCount> _preserveAttachments;
 	VkAttachmentReference2 _depthStencilAttachment;
+	VkAttachmentReference2 _depthStencilResolveAttachment;
+	VkResolveModeFlagBits _depthResolveMode = VK_RESOLVE_MODE_NONE;
+	VkResolveModeFlagBits _stencilResolveMode = VK_RESOLVE_MODE_NONE;
 	id<MTLTexture> _mtlDummyTex = nil;
+	VkSampleCountFlagBits _defaultSampleCount = VK_SAMPLE_COUNT_1_BIT;
 };
 
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm
index c3eeb6a..52d21bc 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm
@@ -21,6 +21,10 @@
 #include "MVKCommandBuffer.h"
 #include "MVKFoundation.h"
 #include "mvk_datatypes.hpp"
+#include "MTLRenderPassDepthAttachmentDescriptor+MoltenVK.h"
+#if MVK_MACOS_OR_IOS
+#include "MTLRenderPassStencilAttachmentDescriptor+MoltenVK.h"
+#endif
 #include <cassert>
 
 using namespace std;
@@ -224,17 +228,31 @@
 
 	// Populate the Metal depth and stencil attachments
 	uint32_t dsRPAttIdx = _depthStencilAttachment.attachment;
+	uint32_t dsRslvRPAttIdx = _depthStencilResolveAttachment.attachment;
 	if (dsRPAttIdx != VK_ATTACHMENT_UNUSED) {
 		MVKRenderPassAttachment* dsMVKRPAtt = &_renderPass->_attachments[dsRPAttIdx];
 		MVKImageView* dsImage = framebuffer->getAttachment(dsRPAttIdx);
+		MVKImageView* dsRslvImage = nullptr;
 		MTLPixelFormat mtlDSFormat = dsImage->getMTLPixelFormat(0);
 
+		if (dsRslvRPAttIdx != VK_ATTACHMENT_UNUSED) {
+			dsRslvImage = framebuffer->getAttachment(dsRslvRPAttIdx);
+		}
+
 		if (pixFmts->isDepthFormat(mtlDSFormat)) {
 			MTLRenderPassDepthAttachmentDescriptor* mtlDepthAttDesc = mtlRPDesc.depthAttachment;
+			bool hasResolveAttachment = (dsRslvRPAttIdx != VK_ATTACHMENT_UNUSED && _depthResolveMode != VK_RESOLVE_MODE_NONE);
+			if (hasResolveAttachment) {
+				dsRslvImage->populateMTLRenderPassAttachmentDescriptorResolve(mtlDepthAttDesc);
+				mtlDepthAttDesc.depthResolveFilterMVK = mvkMTLMultisampleDepthResolveFilterFromVkResolveModeFlagBits(_depthResolveMode);
+				if (isMultiview()) {
+					mtlDepthAttDesc.resolveSlice += getFirstViewIndexInMetalPass(passIdx);
+				}
+			}
 			dsImage->populateMTLRenderPassAttachmentDescriptor(mtlDepthAttDesc);
 			if (dsMVKRPAtt->populateMTLRenderPassAttachmentDescriptor(mtlDepthAttDesc, this,
                                                                       isRenderingEntireAttachment,
-                                                                      false, false,
+                                                                      hasResolveAttachment, false,
                                                                       loadOverride)) {
                 mtlDepthAttDesc.clearDepth = pixFmts->getMTLClearDepthValue(clearValues[dsRPAttIdx]);
 			}
@@ -244,10 +262,20 @@
 		}
 		if (pixFmts->isStencilFormat(mtlDSFormat)) {
 			MTLRenderPassStencilAttachmentDescriptor* mtlStencilAttDesc = mtlRPDesc.stencilAttachment;
+			bool hasResolveAttachment = (dsRslvRPAttIdx != VK_ATTACHMENT_UNUSED && _stencilResolveMode != VK_RESOLVE_MODE_NONE);
+			if (hasResolveAttachment) {
+				dsRslvImage->populateMTLRenderPassAttachmentDescriptorResolve(mtlStencilAttDesc);
+#if MVK_MACOS_OR_IOS
+				mtlStencilAttDesc.stencilResolveFilterMVK = mvkMTLMultisampleStencilResolveFilterFromVkResolveModeFlagBits(_stencilResolveMode);
+#endif
+				if (isMultiview()) {
+					mtlStencilAttDesc.resolveSlice += getFirstViewIndexInMetalPass(passIdx);
+				}
+			}
 			dsImage->populateMTLRenderPassAttachmentDescriptor(mtlStencilAttDesc);
 			if (dsMVKRPAtt->populateMTLRenderPassAttachmentDescriptor(mtlStencilAttDesc, this,
                                                                       isRenderingEntireAttachment,
-                                                                      false, true,
+                                                                      hasResolveAttachment, true,
                                                                       loadOverride)) {
 				mtlStencilAttDesc.clearStencil = pixFmts->getMTLClearStencilValue(clearValues[dsRPAttIdx]);
 			}
@@ -259,10 +287,11 @@
 
 	_mtlDummyTex = nil;
 	if (caUsedCnt == 0 && dsRPAttIdx == VK_ATTACHMENT_UNUSED) {
+		uint32_t sampleCount = mvkSampleCountFromVkSampleCountFlagBits(_defaultSampleCount);
         if (_renderPass->getDevice()->_pMetalFeatures->renderWithoutAttachments) {
             // We support having no attachments.
 #if MVK_MACOS_OR_IOS
-            mtlRPDesc.defaultRasterSampleCount = 1;
+            mtlRPDesc.defaultRasterSampleCount = sampleCount;
 #endif
             return;
         }
@@ -271,11 +300,32 @@
 		VkExtent2D fbExtent = framebuffer->getExtent2D();
 		MTLTextureDescriptor* mtlTexDesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: MTLPixelFormatR8Unorm width: fbExtent.width height: fbExtent.height mipmapped: NO];
 		if (isMultiview()) {
+#if MVK_MACOS
+			if (sampleCount > 1 && _renderPass->getDevice()->_pMetalFeatures->multisampleLayeredRendering) {
+				mtlTexDesc.textureType = MTLTextureType2DMultisampleArray;
+				mtlTexDesc.sampleCount = sampleCount;
+			} else {
+				mtlTexDesc.textureType = MTLTextureType2DArray;
+			}
+#else
 			mtlTexDesc.textureType = MTLTextureType2DArray;
+#endif
 			mtlTexDesc.arrayLength = getViewCountInMetalPass(passIdx);
 		} else if (framebuffer->getLayerCount() > 1) {
+#if MVK_MACOS
+			if (sampleCount > 1 && _renderPass->getDevice()->_pMetalFeatures->multisampleLayeredRendering) {
+				mtlTexDesc.textureType = MTLTextureType2DMultisampleArray;
+				mtlTexDesc.sampleCount = sampleCount;
+			} else {
+				mtlTexDesc.textureType = MTLTextureType2DArray;
+			}
+#else
 			mtlTexDesc.textureType = MTLTextureType2DArray;
+#endif
 			mtlTexDesc.arrayLength = framebuffer->getLayerCount();
+		} else if (sampleCount > 1) {
+			mtlTexDesc.textureType = MTLTextureType2DMultisample;
+			mtlTexDesc.sampleCount = sampleCount;
 		}
 #if MVK_IOS
 		if ([_renderPass->getMTLDevice() supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily1_v3]) {
@@ -315,8 +365,11 @@
     }
     uint32_t dsRPAttIdx = _depthStencilAttachment.attachment;
     if (dsRPAttIdx != VK_ATTACHMENT_UNUSED) {
-        _renderPass->_attachments[dsRPAttIdx].encodeStoreAction(cmdEncoder, this, isRenderingEntireAttachment, false, 0, false, storeOverride);
-        _renderPass->_attachments[dsRPAttIdx].encodeStoreAction(cmdEncoder, this, isRenderingEntireAttachment, false, 0, true, storeOverride);
+        bool hasResolveAttachment = _depthStencilResolveAttachment.attachment != VK_ATTACHMENT_UNUSED;
+        bool hasDepthResolveAttachment = hasResolveAttachment && _depthResolveMode != VK_RESOLVE_MODE_NONE;
+        bool hasStencilResolveAttachment = hasResolveAttachment && _stencilResolveMode != VK_RESOLVE_MODE_NONE;
+        _renderPass->_attachments[dsRPAttIdx].encodeStoreAction(cmdEncoder, this, isRenderingEntireAttachment, hasDepthResolveAttachment, 0, false, storeOverride);
+        _renderPass->_attachments[dsRPAttIdx].encodeStoreAction(cmdEncoder, this, isRenderingEntireAttachment, hasStencilResolveAttachment, 0, true, storeOverride);
     }
 }
 
@@ -394,6 +447,7 @@
 		}
 	}
 	if (_depthStencilAttachment.attachment == rpAttIdx) { mvkEnableFlags(caps, kMVKMTLFmtCapsDSAtt); }
+	if (_depthStencilResolveAttachment.attachment == rpAttIdx) { mvkEnableFlags(caps, kMVKMTLFmtCapsResolve); }
 
 	return caps;
 }
@@ -442,6 +496,8 @@
 		_depthStencilAttachment.attachment = VK_ATTACHMENT_UNUSED;
 	}
 
+	_depthStencilResolveAttachment.attachment = VK_ATTACHMENT_UNUSED;
+
 	_preserveAttachments.reserve(pCreateInfo->preserveAttachmentCount);
 	for (uint32_t i = 0; i < pCreateInfo->preserveAttachmentCount; i++) {
 		_preserveAttachments.push_back(pCreateInfo->pPreserveAttachments[i]);
@@ -450,6 +506,18 @@
 
 MVKRenderSubpass::MVKRenderSubpass(MVKRenderPass* renderPass,
 								   const VkSubpassDescription2* pCreateInfo) {
+
+	VkSubpassDescriptionDepthStencilResolve* pDSResolveInfo = nullptr;
+	for (auto* next = (const VkBaseInStructure*)pCreateInfo->pNext; next; next = next->pNext) {
+		switch (next->sType) {
+		case VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE:
+			pDSResolveInfo = (VkSubpassDescriptionDepthStencilResolve*)next;
+			break;
+		default:
+			break;
+		}
+	}
+
 	_renderPass = renderPass;
 	_subpassIndex = (uint32_t)_renderPass->_subpasses.size();
 	_viewMask = pCreateInfo->viewMask;
@@ -478,6 +546,14 @@
 		_depthStencilAttachment.attachment = VK_ATTACHMENT_UNUSED;
 	}
 
+	if (pDSResolveInfo && pDSResolveInfo->pDepthStencilResolveAttachment) {
+		_depthStencilResolveAttachment = *pDSResolveInfo->pDepthStencilResolveAttachment;
+		_depthResolveMode = pDSResolveInfo->depthResolveMode;
+		_stencilResolveMode = pDSResolveInfo->stencilResolveMode;
+	} else {
+		_depthStencilResolveAttachment.attachment = VK_ATTACHMENT_UNUSED;
+	}
+
 	_preserveAttachments.reserve(pCreateInfo->preserveAttachmentCount);
 	for (uint32_t i = 0; i < pCreateInfo->preserveAttachmentCount; i++) {
 		_preserveAttachments.push_back(pCreateInfo->pPreserveAttachments[i]);
diff --git a/MoltenVK/MoltenVK/Layers/MVKExtensions.def b/MoltenVK/MoltenVK/Layers/MVKExtensions.def
index cbb1f16..cd54eb6 100644
--- a/MoltenVK/MoltenVK/Layers/MVKExtensions.def
+++ b/MoltenVK/MoltenVK/Layers/MVKExtensions.def
@@ -44,6 +44,7 @@
 MVK_EXTENSION(KHR_bind_memory2, KHR_BIND_MEMORY_2, DEVICE)
 MVK_EXTENSION(KHR_create_renderpass2, KHR_CREATE_RENDERPASS_2, DEVICE)
 MVK_EXTENSION(KHR_dedicated_allocation, KHR_DEDICATED_ALLOCATION, DEVICE)
+MVK_EXTENSION(KHR_depth_stencil_resolve, KHR_DEPTH_STENCIL_RESOLVE, DEVICE)
 MVK_EXTENSION(KHR_descriptor_update_template, KHR_DESCRIPTOR_UPDATE_TEMPLATE, DEVICE)
 MVK_EXTENSION(KHR_device_group, KHR_DEVICE_GROUP, DEVICE)
 MVK_EXTENSION(KHR_device_group_creation, KHR_DEVICE_GROUP_CREATION, INSTANCE)
@@ -62,6 +63,7 @@
 MVK_EXTENSION(KHR_maintenance2, KHR_MAINTENANCE2, DEVICE)
 MVK_EXTENSION(KHR_maintenance3, KHR_MAINTENANCE3, DEVICE)
 MVK_EXTENSION(KHR_multiview, KHR_MULTIVIEW, DEVICE)
+MVK_EXTENSION(KHR_portability_subset, KHR_PORTABILITY_SUBSET, DEVICE)
 MVK_EXTENSION(KHR_push_descriptor, KHR_PUSH_DESCRIPTOR, DEVICE)
 MVK_EXTENSION(KHR_relaxed_block_layout, KHR_RELAXED_BLOCK_LAYOUT, DEVICE)
 MVK_EXTENSION(KHR_sampler_mirror_clamp_to_edge, KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE, DEVICE)
@@ -92,7 +94,6 @@
 MVK_EXTENSION(EXT_swapchain_colorspace, EXT_SWAPCHAIN_COLOR_SPACE, INSTANCE)
 MVK_EXTENSION(EXT_texel_buffer_alignment, EXT_TEXEL_BUFFER_ALIGNMENT, DEVICE)
 MVK_EXTENSION(EXT_vertex_attribute_divisor, EXT_VERTEX_ATTRIBUTE_DIVISOR, DEVICE)
-MVK_EXTENSION(EXTX_portability_subset, EXTX_PORTABILITY_SUBSET, DEVICE)
 MVK_EXTENSION(MVK_ios_surface, MVK_IOS_SURFACE, INSTANCE)
 MVK_EXTENSION(MVK_macos_surface, MVK_MACOS_SURFACE, INSTANCE)
 MVK_EXTENSION(MVK_moltenvk, MVK_MOLTENVK, INSTANCE)
diff --git a/MoltenVK/MoltenVK/OS/MTLRenderPassDepthAttachmentDescriptor+MoltenVK.h b/MoltenVK/MoltenVK/OS/MTLRenderPassDepthAttachmentDescriptor+MoltenVK.h
new file mode 100644
index 0000000..4ea08a5
--- /dev/null
+++ b/MoltenVK/MoltenVK/OS/MTLRenderPassDepthAttachmentDescriptor+MoltenVK.h
@@ -0,0 +1,34 @@
+/*
+ * MTLRenderPassDepthAttachmentDescriptor+MoltenVK.h
+ *
+ * Copyright (c) 2020 Chip Davis for CodeWeavers
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#import <Metal/Metal.h>
+
+/** Extensions to MTLRenderPassDepthAttachmentDescriptor to support MoltenVK. */
+@interface MTLRenderPassDepthAttachmentDescriptor (MoltenVK)
+
+/**
+ * Replacement for the depthResolveFilter property.
+ *
+ * This property allows support under all OS versions. Delegates to the depthResolveFilter
+ * property if it is available. Otherwise, returns MTLMultisampleDepthResolveFilterSample0 when read and does nothing when set.
+ */
+@property(nonatomic, readwrite) MTLMultisampleDepthResolveFilter depthResolveFilterMVK;
+
+@end
diff --git a/MoltenVK/MoltenVK/OS/MTLRenderPassDepthAttachmentDescriptor+MoltenVK.m b/MoltenVK/MoltenVK/OS/MTLRenderPassDepthAttachmentDescriptor+MoltenVK.m
new file mode 100644
index 0000000..eb249da
--- /dev/null
+++ b/MoltenVK/MoltenVK/OS/MTLRenderPassDepthAttachmentDescriptor+MoltenVK.m
@@ -0,0 +1,42 @@
+/*
+ * MTLRenderPassDepthAttachmentDescriptor+MoltenVK.m
+ *
+ * Copyright (c) 2020 Chip Davis for CodeWeavers
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include "MTLRenderPassDepthAttachmentDescriptor+MoltenVK.h"
+#include "MVKEnvironment.h"
+
+@implementation MTLRenderPassDepthAttachmentDescriptor (MoltenVK)
+
+-(MTLMultisampleDepthResolveFilter) depthResolveFilterMVK {
+
+	if ( [self respondsToSelector: @selector(depthResolveFilter)] ) {
+		return self.depthResolveFilter;
+	}
+	return MTLMultisampleDepthResolveFilterSample0;
+
+}
+
+-(void) setDepthResolveFilterMVK: (MTLMultisampleDepthResolveFilter) filter {
+
+	if ( [self respondsToSelector: @selector(setDepthResolveFilter:)] ) {
+		self.depthResolveFilter = filter;
+	}
+
+}
+
+@end
diff --git a/MoltenVK/MoltenVK/OS/MTLRenderPassStencilAttachmentDescriptor+MoltenVK.h b/MoltenVK/MoltenVK/OS/MTLRenderPassStencilAttachmentDescriptor+MoltenVK.h
new file mode 100644
index 0000000..d2fb4fe
--- /dev/null
+++ b/MoltenVK/MoltenVK/OS/MTLRenderPassStencilAttachmentDescriptor+MoltenVK.h
@@ -0,0 +1,34 @@
+/*
+ * MTLRenderPassStencilAttachmentDescriptor+MoltenVK.h
+ *
+ * Copyright (c) 2020 Chip Davis for CodeWeavers
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#import <Metal/Metal.h>
+
+/** Extensions to MTLRenderPassStencilAttachmentDescriptor to support MoltenVK. */
+@interface MTLRenderPassStencilAttachmentDescriptor (MoltenVK)
+
+/**
+ * Replacement for the stencilResolveFilter property.
+ *
+ * This property allows support under all OS versions. Delegates to the stencilResolveFilter
+ * property if it is available. Otherwise, returns MTLMultisampleStencilResolveFilterSample0 when read and does nothing when set.
+ */
+@property(nonatomic, readwrite) MTLMultisampleStencilResolveFilter stencilResolveFilterMVK;
+
+@end
diff --git a/MoltenVK/MoltenVK/OS/MTLRenderPassStencilAttachmentDescriptor+MoltenVK.m b/MoltenVK/MoltenVK/OS/MTLRenderPassStencilAttachmentDescriptor+MoltenVK.m
new file mode 100644
index 0000000..4cbda58
--- /dev/null
+++ b/MoltenVK/MoltenVK/OS/MTLRenderPassStencilAttachmentDescriptor+MoltenVK.m
@@ -0,0 +1,42 @@
+/*
+ * MTLRenderPassStencilAttachmentDescriptor+MoltenVK.m
+ *
+ * Copyright (c) 2020 Chip Davis for CodeWeavers
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include "MTLRenderPassStencilAttachmentDescriptor+MoltenVK.h"
+#include "MVKEnvironment.h"
+
+@implementation MTLRenderPassStencilAttachmentDescriptor (MoltenVK)
+
+-(MTLMultisampleStencilResolveFilter) stencilResolveFilterMVK {
+
+	if ( [self respondsToSelector: @selector(stencilResolveFilter)] ) {
+		return self.stencilResolveFilter;
+	}
+	return MTLMultisampleStencilResolveFilterSample0;
+
+}
+
+-(void) setStencilResolveFilterMVK: (MTLMultisampleStencilResolveFilter) filter {
+
+	if ( [self respondsToSelector: @selector(setStencilResolveFilter:)] ) {
+		self.stencilResolveFilter = filter;
+	}
+
+}
+
+@end
diff --git a/MoltenVK/MoltenVK/Utility/MVKFoundation.h b/MoltenVK/MoltenVK/Utility/MVKFoundation.h
index 9748abc..d8d1ed2 100644
--- a/MoltenVK/MoltenVK/Utility/MVKFoundation.h
+++ b/MoltenVK/MoltenVK/Utility/MVKFoundation.h
@@ -54,7 +54,7 @@
 /** 2D vertex position and texcoord content. */
 typedef struct {
 	simd::float2 position;
-	simd::float2 texCoord;
+	simd::float3 texCoord;
 } MVKVertexPosTex;
 
 
@@ -376,6 +376,12 @@
 	}
 };
 
+/** Returns the absolute value of the difference of two numbers. */
+template<typename T, typename U>
+constexpr typename std::common_type<T, U>::type mvkAbsDiff(T x, U y) {
+	return x >= y ? x - y : y - x;
+}
+
 /** Returns the greatest common divisor of two numbers. */
 template<typename T>
 constexpr T mvkGreatestCommonDivisorImpl(T a, T b) {
diff --git a/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.hpp b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.hpp
index be0c519..7a2a5d9 100644
--- a/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.hpp
+++ b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.hpp
@@ -62,6 +62,14 @@
 MTLStoreAction mvkMTLStoreActionFromVkAttachmentStoreOpInObj(VkAttachmentStoreOp vkStoreOp, bool hasResolveAttachment, MVKBaseObject* mvkObj);
 #define mvkMTLStoreActionFromVkAttachmentStoreOp(vkStoreOp, hasResolveAttachment) mvkMTLStoreActionFromVkAttachmentStoreOpInObj(vkStoreOp, hasResolveAttachment, this)
 
+MTLMultisampleDepthResolveFilter mvkMTLMultisampleDepthResolveFilterFromVkResolveModeFlagBitsInObj(VkResolveModeFlagBits vkResolveMode, MVKBaseObject* mvkObj);
+#define mvkMTLMultisampleDepthResolveFilterFromVkResolveModeFlagBits(vkResolveMode) mvkMTLMultisampleDepthResolveFilterFromVkResolveModeFlagBitsInObj(vkResolveMode, this)
+
+#if MVK_MACOS_OR_IOS
+MTLMultisampleStencilResolveFilter mvkMTLMultisampleStencilResolveFilterFromVkResolveModeFlagBitsInObj(VkResolveModeFlagBits vkResolveMode, MVKBaseObject* mvkObj);
+#define mvkMTLMultisampleStencilResolveFilterFromVkResolveModeFlagBits(vkResolveMode) mvkMTLMultisampleStencilResolveFilterFromVkResolveModeFlagBitsInObj(vkResolveMode, this)
+#endif
+
 MVKShaderStage mvkShaderStageFromVkShaderStageFlagBitsInObj(VkShaderStageFlagBits vkStage, MVKBaseObject* mvkObj);
 #define mvkShaderStageFromVkShaderStageFlagBits(vkStage) mvkShaderStageFromVkShaderStageFlagBitsInObj(vkStage, this)
 
diff --git a/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm
index 57d6de1..8bee86e 100644
--- a/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm
+++ b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm
@@ -509,6 +509,40 @@
 	}
 }
 
+#undef mvkMTLMultisampleDepthResolveFilterFromVkResolveModeFlagBits
+MVK_PUBLIC_SYMBOL MTLMultisampleDepthResolveFilter mvkMTLMultisampleDepthResolveFilterFromVkResolveModeFlagBits(VkResolveModeFlagBits vkResolveMode) {
+	return mvkMTLMultisampleDepthResolveFilterFromVkResolveModeFlagBitsInObj(vkResolveMode, nullptr);
+}
+
+MTLMultisampleDepthResolveFilter mvkMTLMultisampleDepthResolveFilterFromVkResolveModeFlagBitsInObj(VkResolveModeFlagBits vkResolveMode, MVKBaseObject* mvkObj) {
+	switch (vkResolveMode) {
+		case VK_RESOLVE_MODE_SAMPLE_ZERO_BIT:	return MTLMultisampleDepthResolveFilterSample0;
+		case VK_RESOLVE_MODE_MIN_BIT:			return MTLMultisampleDepthResolveFilterMin;
+		case VK_RESOLVE_MODE_MAX_BIT:			return MTLMultisampleDepthResolveFilterMax;
+
+		default:
+			MVKBaseObject::reportError(mvkObj, VK_ERROR_FORMAT_NOT_SUPPORTED, "VkResolveModeFlagBits value %d is not supported.", vkResolveMode);
+			return MTLMultisampleDepthResolveFilterSample0;
+	}
+}
+
+#if MVK_MACOS_OR_IOS
+#undef mvkMTLMultisampleStencilResolveFilterFromVkResolveModeFlagBits
+MVK_PUBLIC_SYMBOL MTLMultisampleStencilResolveFilter mvkMTLMultisampleStencilResolveFilterFromVkResolveModeFlagBits(VkResolveModeFlagBits vkResolveMode) {
+	return mvkMTLMultisampleStencilResolveFilterFromVkResolveModeFlagBitsInObj(vkResolveMode, nullptr);
+}
+
+MTLMultisampleStencilResolveFilter mvkMTLMultisampleStencilResolveFilterFromVkResolveModeFlagBitsInObj(VkResolveModeFlagBits vkResolveMode, MVKBaseObject* mvkObj) {
+	switch (vkResolveMode) {
+		case VK_RESOLVE_MODE_SAMPLE_ZERO_BIT:	return MTLMultisampleStencilResolveFilterSample0;
+
+		default:
+			MVKBaseObject::reportError(mvkObj, VK_ERROR_FORMAT_NOT_SUPPORTED, "VkResolveModeFlagBits value %d is not supported.", vkResolveMode);
+			return MTLMultisampleStencilResolveFilterSample0;
+	}
+}
+#endif
+
 MVK_PUBLIC_SYMBOL MTLViewport mvkMTLViewportFromVkViewport(VkViewport vkViewport) {
 	MTLViewport mtlViewport;
 	mtlViewport.originX	= vkViewport.x;
diff --git a/MoltenVK/MoltenVK/Vulkan/vulkan.mm b/MoltenVK/MoltenVK/Vulkan/vulkan.mm
index f0e1824..1fecb88 100644
--- a/MoltenVK/MoltenVK/Vulkan/vulkan.mm
+++ b/MoltenVK/MoltenVK/Vulkan/vulkan.mm
@@ -174,6 +174,7 @@
 	MVKInstance* mvkInst = new MVKInstance(pCreateInfo);
 	*pInstance = mvkInst->getVkInstance();
 	VkResult rslt = mvkInst->getConfigurationResult();
+	if (rslt < 0) { *pInstance = nullptr; mvkInst->destroy(); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -313,6 +314,7 @@
 	MVKDevice* mvkDev = new MVKDevice(mvkPD, pCreateInfo);
 	*pDevice = mvkDev->getVkDevice();
 	VkResult rslt = mvkDev->getConfigurationResult();
+	if (rslt < 0) { *pDevice = nullptr; mvkDev->destroy(); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -428,6 +430,7 @@
 	MVKDeviceMemory* mvkMem = mvkDev->allocateMemory(pAllocateInfo, pAllocator);
 	VkResult rslt = mvkMem->getConfigurationResult();
 	*pMem = (VkDeviceMemory)((rslt == VK_SUCCESS) ? mvkMem : VK_NULL_HANDLE);
+    if (rslt != VK_SUCCESS) { mvkDev->freeMemory(mvkMem, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -618,6 +621,7 @@
 	MVKFence* mvkFence = mvkDev->createFence(pCreateInfo, pAllocator);
 	*pFence = (VkFence)mvkFence;
 	VkResult rslt = mvkFence->getConfigurationResult();
+	if (rslt < 0) { *pFence = VK_NULL_HANDLE; mvkDev->destroyFence(mvkFence, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -680,6 +684,7 @@
 	MVKSemaphore* mvkSem4 = mvkDev->createSemaphore(pCreateInfo, pAllocator);
 	*pSemaphore = (VkSemaphore)mvkSem4;
 	VkResult rslt = mvkSem4->getConfigurationResult();
+	if (rslt < 0) { *pSemaphore = VK_NULL_HANDLE; mvkDev->destroySemaphore(mvkSem4, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -706,6 +711,7 @@
 	MVKEvent* mvkEvent = mvkDev->createEvent(pCreateInfo, pAllocator);
 	*pEvent = (VkEvent)mvkEvent;
 	VkResult rslt = mvkEvent->getConfigurationResult();
+	if (rslt < 0) { *pEvent = VK_NULL_HANDLE; mvkDev->destroyEvent(mvkEvent, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -765,6 +771,7 @@
 	MVKQueryPool* mvkQP = mvkDev->createQueryPool(pCreateInfo, pAllocator);
 	*pQueryPool = (VkQueryPool)mvkQP;
 	VkResult rslt = mvkQP->getConfigurationResult();
+	if (rslt < 0) { *pQueryPool = VK_NULL_HANDLE; mvkDev->destroyQueryPool(mvkQP, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -808,6 +815,7 @@
 	MVKBuffer* mvkBuff = mvkDev->createBuffer(pCreateInfo, pAllocator);
 	*pBuffer = (VkBuffer)mvkBuff;
 	VkResult rslt = mvkBuff->getConfigurationResult();
+	if (rslt < 0) { *pBuffer = VK_NULL_HANDLE; mvkDev->destroyBuffer(mvkBuff, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -834,6 +842,7 @@
     MVKBufferView* mvkBuffView = mvkDev->createBufferView(pCreateInfo, pAllocator);
     *pView = (VkBufferView)mvkBuffView;
     VkResult rslt = mvkBuffView->getConfigurationResult();
+	if (rslt < 0) { *pView = VK_NULL_HANDLE; mvkDev->destroyBufferView(mvkBuffView, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -860,6 +869,7 @@
 	MVKImage* mvkImg = mvkDev->createImage(pCreateInfo, pAllocator);
 	*pImage = (VkImage)mvkImg;
 	VkResult rslt = mvkImg->getConfigurationResult();
+	if (rslt < 0) { *pImage = VK_NULL_HANDLE; mvkDev->destroyImage(mvkImg, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -898,6 +908,7 @@
 	MVKImageView* mvkImgView = mvkDev->createImageView(pCreateInfo, pAllocator);
 	*pView = (VkImageView)mvkImgView;
 	VkResult rslt = mvkImgView->getConfigurationResult();
+	if (rslt < 0) { *pView = VK_NULL_HANDLE; mvkDev->destroyImageView(mvkImgView, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -924,6 +935,7 @@
 	MVKShaderModule* mvkShdrMod = mvkDev->createShaderModule(pCreateInfo, pAllocator);
 	*pShaderModule = (VkShaderModule)mvkShdrMod;
 	VkResult rslt = mvkShdrMod->getConfigurationResult();
+	if (rslt < 0) { *pShaderModule = VK_NULL_HANDLE; mvkDev->destroyShaderModule(mvkShdrMod, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -950,6 +962,7 @@
 	MVKPipelineCache* mvkPLC = mvkDev->createPipelineCache(pCreateInfo, pAllocator);
 	*pPipelineCache = (VkPipelineCache)mvkPLC;
 	VkResult rslt = mvkPLC->getConfigurationResult();
+	if (rslt < 0) { *pPipelineCache = VK_NULL_HANDLE; mvkDev->destroyPipelineCache(mvkPLC, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -1043,6 +1056,7 @@
 	MVKPipelineLayout* mvkPLL = mvkDev->createPipelineLayout(pCreateInfo, pAllocator);
 	*pPipelineLayout = (VkPipelineLayout)mvkPLL;
 	VkResult rslt = mvkPLL->getConfigurationResult();
+	if (rslt < 0) { *pPipelineLayout = VK_NULL_HANDLE; mvkDev->destroyPipelineLayout(mvkPLL, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -1069,6 +1083,7 @@
 	MVKSampler* mvkSamp = mvkDev->createSampler(pCreateInfo, pAllocator);
 	*pSampler = (VkSampler)mvkSamp;
 	VkResult rslt = mvkSamp->getConfigurationResult();
+	if (rslt < 0) { *pSampler = VK_NULL_HANDLE; mvkDev->destroySampler(mvkSamp, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -1095,6 +1110,7 @@
 	MVKDescriptorSetLayout* mvkDSL = mvkDev->createDescriptorSetLayout(pCreateInfo, pAllocator);
 	*pSetLayout = (VkDescriptorSetLayout)mvkDSL;
 	VkResult rslt = mvkDSL->getConfigurationResult();
+	if (rslt < 0) { *pSetLayout = VK_NULL_HANDLE; mvkDev->destroyDescriptorSetLayout(mvkDSL, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -1121,6 +1137,7 @@
 	MVKDescriptorPool* mvkDP = mvkDev->createDescriptorPool(pCreateInfo, pAllocator);
 	*pDescriptorPool = (VkDescriptorPool)mvkDP;
 	VkResult rslt = mvkDP->getConfigurationResult();
+	if (rslt < 0) { *pDescriptorPool = VK_NULL_HANDLE; mvkDev->destroyDescriptorPool(mvkDP, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -1198,6 +1215,7 @@
 	MVKFramebuffer* mvkFB = mvkDev->createFramebuffer(pCreateInfo, pAllocator);
 	*pFramebuffer = (VkFramebuffer)mvkFB;
 	VkResult rslt = mvkFB->getConfigurationResult();
+	if (rslt < 0) { *pFramebuffer = VK_NULL_HANDLE; mvkDev->destroyFramebuffer(mvkFB, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -1224,6 +1242,7 @@
 	MVKRenderPass* mvkRendPass = mvkDev->createRenderPass(pCreateInfo, pAllocator);
 	*pRenderPass = (VkRenderPass)mvkRendPass;
 	VkResult rslt = mvkRendPass->getConfigurationResult();
+	if (rslt < 0) { *pRenderPass = VK_NULL_HANDLE; mvkDev->destroyRenderPass(mvkRendPass, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -1261,6 +1280,7 @@
 	MVKCommandPool* mvkCmdPool = mvkDev->createCommandPool(pCreateInfo, pAllocator);
 	*pCmdPool = (VkCommandPool)mvkCmdPool;
 	VkResult rslt = mvkCmdPool->getConfigurationResult();
+	if (rslt < 0) { *pCmdPool = VK_NULL_HANDLE; mvkDev->destroyCommandPool(mvkCmdPool, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -2146,6 +2166,10 @@
                                                           pAllocator);
     *pDescriptorUpdateTemplate = (VkDescriptorUpdateTemplate)mvkDUT;
     VkResult rslt = mvkDUT->getConfigurationResult();
+    if (rslt < 0) {
+        *pDescriptorUpdateTemplate = VK_NULL_HANDLE;
+        mvkDev->destroyDescriptorUpdateTemplate(mvkDUT, pAllocator);
+    }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -2194,6 +2218,10 @@
 	MVKSamplerYcbcrConversion* mvkSampConv = mvkDev->createSamplerYcbcrConversion(pCreateInfo, pAllocator);
 	*pYcbcrConversion = (VkSamplerYcbcrConversion)mvkSampConv;
 	VkResult rslt = mvkSampConv->getConfigurationResult();
+    if (rslt < 0) {
+        *pYcbcrConversion = VK_NULL_HANDLE;
+        mvkDev->destroySamplerYcbcrConversion(mvkSampConv, pAllocator);
+    }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -2267,6 +2295,7 @@
 	MVKRenderPass* mvkRendPass = mvkDev->createRenderPass(pCreateInfo, pAllocator);
 	*pRenderPass = (VkRenderPass)mvkRendPass;
 	VkResult rslt = mvkRendPass->getConfigurationResult();
+    if (rslt < 0) { *pRenderPass = VK_NULL_HANDLE; mvkDev->destroyRenderPass(mvkRendPass, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -2423,6 +2452,7 @@
     MVKSwapchain* mvkSwpChn = mvkDev->createSwapchain(pCreateInfo, pAllocator);
     *pSwapchain = (VkSwapchainKHR)(mvkSwpChn);
     VkResult rslt = mvkSwpChn->getConfigurationResult();
+    if (rslt < 0) { *pSwapchain = VK_NULL_HANDLE; mvkDev->destroySwapchain(mvkSwpChn, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -2647,6 +2677,7 @@
 	MVKDebugReportCallback* mvkDRCB = mvkInst->createDebugReportCallback(pCreateInfo, pAllocator);
 	*pCallback = (VkDebugReportCallbackEXT)mvkDRCB;
 	VkResult rslt = mvkDRCB->getConfigurationResult();
+    if (rslt < 0) { *pCallback = VK_NULL_HANDLE; mvkInst->destroyDebugReportCallback(mvkDRCB, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -2814,6 +2845,7 @@
 	MVKDebugUtilsMessenger* mvkDUM = mvkInst->createDebugUtilsMessenger(pCreateInfo, pAllocator);
 	*pMessenger = (VkDebugUtilsMessengerEXT)mvkDUM;
 	VkResult rslt = mvkDUM->getConfigurationResult();
+    if (rslt < 0) { *pMessenger = VK_NULL_HANDLE; mvkInst->destroyDebugUtilsMessenger(mvkDUM, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -2890,6 +2922,7 @@
 	MVKSurface* mvkSrfc = mvkInst->createSurface(pCreateInfo, pAllocator);
 	*pSurface = (VkSurfaceKHR)mvkSrfc;
 	VkResult rslt = mvkSrfc->getConfigurationResult();
+    if (rslt < 0) { *pSurface = VK_NULL_HANDLE; mvkInst->destroySurface(mvkSrfc, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
@@ -2934,6 +2967,7 @@
     MVKSurface* mvkSrfc = mvkInst->createSurface(pCreateInfo, pAllocator);
     *pSurface = (VkSurfaceKHR)mvkSrfc;
     VkResult rslt = mvkSrfc->getConfigurationResult();
+    if (rslt < 0) { *pSurface = VK_NULL_HANDLE; mvkInst->destroySurface(mvkSrfc, pAllocator); }
 	MVKTraceVulkanCallEnd();
 	return rslt;
 }
diff --git a/MoltenVK/include/vulkan-portability b/MoltenVK/include/vulkan-portability
deleted file mode 120000
index cfe721d..0000000
--- a/MoltenVK/include/vulkan-portability
+++ /dev/null
@@ -1 +0,0 @@
-../../External/Vulkan-Portability/include/vulkan
\ No newline at end of file
diff --git a/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/project.pbxproj b/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/project.pbxproj
index 8157a46..5b64a99 100644
--- a/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/project.pbxproj
+++ b/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/project.pbxproj
@@ -770,7 +770,6 @@
 				GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO;
 				GCC_WARN_CHECK_SWITCH_STATEMENTS = NO;
 				GCC_WARN_UNUSED_PARAMETER = NO;
-				LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/../External/build/Latest/tvOS\"";
 				PRODUCT_NAME = MoltenVKGLSLToSPIRVConverter;
 				SDKROOT = appletvos;
 				TVOS_DEPLOYMENT_TARGET = 11.0;
@@ -785,7 +784,6 @@
 				GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO;
 				GCC_WARN_CHECK_SWITCH_STATEMENTS = NO;
 				GCC_WARN_UNUSED_PARAMETER = NO;
-				LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/../External/build/Latest/tvOS\"";
 				PRODUCT_NAME = MoltenVKGLSLToSPIRVConverter;
 				SDKROOT = appletvos;
 				TVOS_DEPLOYMENT_TARGET = 11.0;
@@ -800,7 +798,6 @@
 					"SPIRV_CROSS_NAMESPACE_OVERRIDE=MVK_spirv_cross",
 				);
 				GCC_WARN_ABOUT_MISSING_PROTOTYPES = NO;
-				LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/../External/build/Latest/tvOS\"";
 				PRODUCT_NAME = MoltenVKSPIRVToMSLConverter;
 				SDKROOT = appletvos;
 				TVOS_DEPLOYMENT_TARGET = 11.0;
@@ -815,7 +812,6 @@
 					"SPIRV_CROSS_NAMESPACE_OVERRIDE=MVK_spirv_cross",
 				);
 				GCC_WARN_ABOUT_MISSING_PROTOTYPES = NO;
-				LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/../External/build/Latest/tvOS\"";
 				PRODUCT_NAME = MoltenVKSPIRVToMSLConverter;
 				SDKROOT = appletvos;
 				TVOS_DEPLOYMENT_TARGET = 11.0;
@@ -858,7 +854,6 @@
 				GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO;
 				GCC_WARN_CHECK_SWITCH_STATEMENTS = NO;
 				GCC_WARN_UNUSED_PARAMETER = NO;
-				LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/../External/build/Latest/iOS\"";
 				PRODUCT_NAME = MoltenVKGLSLToSPIRVConverter;
 				SDKROOT = iphoneos;
 			};
@@ -872,7 +867,6 @@
 				GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO;
 				GCC_WARN_CHECK_SWITCH_STATEMENTS = NO;
 				GCC_WARN_UNUSED_PARAMETER = NO;
-				LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/../External/build/Latest/iOS\"";
 				PRODUCT_NAME = MoltenVKGLSLToSPIRVConverter;
 				SDKROOT = iphoneos;
 			};
@@ -886,7 +880,6 @@
 				GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO;
 				GCC_WARN_CHECK_SWITCH_STATEMENTS = NO;
 				GCC_WARN_UNUSED_PARAMETER = NO;
-				LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/../External/build/Latest/macOS\"";
 				PRODUCT_NAME = MoltenVKGLSLToSPIRVConverter;
 				SDKROOT = macosx;
 			};
@@ -900,7 +893,6 @@
 				GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO;
 				GCC_WARN_CHECK_SWITCH_STATEMENTS = NO;
 				GCC_WARN_UNUSED_PARAMETER = NO;
-				LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/../External/build/Latest/macOS\"";
 				PRODUCT_NAME = MoltenVKGLSLToSPIRVConverter;
 				SDKROOT = macosx;
 			};
@@ -914,7 +906,6 @@
 					"SPIRV_CROSS_NAMESPACE_OVERRIDE=MVK_spirv_cross",
 				);
 				GCC_WARN_ABOUT_MISSING_PROTOTYPES = NO;
-				LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/../External/build/Latest/iOS\"";
 				PRODUCT_NAME = MoltenVKSPIRVToMSLConverter;
 				SDKROOT = iphoneos;
 			};
@@ -928,7 +919,6 @@
 					"SPIRV_CROSS_NAMESPACE_OVERRIDE=MVK_spirv_cross",
 				);
 				GCC_WARN_ABOUT_MISSING_PROTOTYPES = NO;
-				LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/../External/build/Latest/iOS\"";
 				PRODUCT_NAME = MoltenVKSPIRVToMSLConverter;
 				SDKROOT = iphoneos;
 			};
@@ -942,7 +932,6 @@
 					"SPIRV_CROSS_NAMESPACE_OVERRIDE=MVK_spirv_cross",
 				);
 				GCC_WARN_ABOUT_MISSING_PROTOTYPES = NO;
-				LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/../External/build/Latest/macOS\"";
 				PRODUCT_NAME = MoltenVKSPIRVToMSLConverter;
 				SDKROOT = macosx;
 			};
@@ -956,7 +945,6 @@
 					"SPIRV_CROSS_NAMESPACE_OVERRIDE=MVK_spirv_cross",
 				);
 				GCC_WARN_ABOUT_MISSING_PROTOTYPES = NO;
-				LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/../External/build/Latest/macOS\"";
 				PRODUCT_NAME = MoltenVKSPIRVToMSLConverter;
 				SDKROOT = macosx;
 			};
diff --git a/fetchDependencies b/fetchDependencies
index 08a2cb0..28bd8d7 100755
--- a/fetchDependencies
+++ b/fetchDependencies
@@ -252,18 +252,6 @@
 	update_repo ${REPO_NAME} ${REPO_URL} ${REPO_REV}
 fi
 
-# ----------------- Vulkan-Portability -------------------
-
-echo
-echo ========== Vulkan-Portability ==========
-echo
-
-REPO_NAME=Vulkan-Portability
-REPO_URL="https://github.com/KhronosGroup/${REPO_NAME}.git"
-REPO_REV=$(cat "${EXT_REV_DIR}/${REPO_NAME}_repo_revision")
-
-update_repo ${REPO_NAME} ${REPO_URL} ${REPO_REV}
-
 
 # ----------------- SPIRV-Cross -------------------