Merge pull request #845 from billhollings/master

Do not create depth/stencil MTLTextures from MTLHeap.
diff --git a/Common/MVKOSExtensions.h b/Common/MVKOSExtensions.h
index 245e02b..9df2a77 100644
--- a/Common/MVKOSExtensions.h
+++ b/Common/MVKOSExtensions.h
@@ -31,7 +31,10 @@
  * - (10.12.3) => 10.1203
  * - (8.0.2) => 8.0002
  */
-MVKOSVersion mvkOSVersion(void);
+MVKOSVersion mvkOSVersion();
+
+/** Returns whether the operating system version is at least minVer. */
+inline bool mvkOSVersionIsAtLeast(MVKOSVersion minVer) { return mvkOSVersion() >= minVer; }
 
 /**
  * Returns a monotonic timestamp value for use in Vulkan and performance timestamping.
diff --git a/Common/MVKOSExtensions.mm b/Common/MVKOSExtensions.mm
index f783b11..97c2112 100644
--- a/Common/MVKOSExtensions.mm
+++ b/Common/MVKOSExtensions.mm
@@ -109,7 +109,7 @@
 
 uint64_t mvkGetAvailableMemorySize() {
 #if MVK_IOS
-	if (mvkOSVersion() >= 13.0) { return os_proc_available_memory(); }
+	if (mvkOSVersionIsAtLeast(13.0)) { return os_proc_available_memory(); }
 #endif
 	mach_port_t host_port;
 	mach_msg_type_number_t host_size;
diff --git a/MoltenVK/MoltenVK/API/mvk_datatypes.h b/MoltenVK/MoltenVK/API/mvk_datatypes.h
index 3a162f2..4ecd650 100644
--- a/MoltenVK/MoltenVK/API/mvk_datatypes.h
+++ b/MoltenVK/MoltenVK/API/mvk_datatypes.h
@@ -205,8 +205,8 @@
 /** Returns the Metal MTLTextureType corresponding to the Vulkan VkImageViewType. */
 MTLTextureType mvkMTLTextureTypeFromVkImageViewType(VkImageViewType vkImageViewType, bool isMultisample);
 
-/** Returns the Metal texture usage from the Vulkan image usage. */
-MTLTextureUsage mvkMTLTextureUsageFromVkImageUsageFlags(VkImageUsageFlags vkImageUsageFlags);
+/** Returns the Metal texture usage from the Vulkan image usage taking into considertion usage limits for the pixel format. */
+MTLTextureUsage mvkMTLTextureUsageFromVkImageUsageFlags(VkImageUsageFlags vkImageUsageFlags, MTLPixelFormat mtlPixFmt);
 
 /** Returns the Vulkan image usage from the Metal texture usage and format. */
 VkImageUsageFlags mvkVkImageUsageFlagsFromMTLTextureUsage(MTLTextureUsage mtlUsage, MTLPixelFormat mtlFormat);
@@ -357,7 +357,7 @@
 MTLLoadAction mvkMTLLoadActionFromVkAttachmentLoadOp(VkAttachmentLoadOp vkLoadOp);
 
 /** Returns the Metal MTLStoreAction corresponding to the specified Vulkan VkAttachmentStoreOp. */
-MTLStoreAction mvkMTLStoreActionFromVkAttachmentStoreOp(VkAttachmentStoreOp vkStoreOp, bool hasResolveAttachment = false);
+MTLStoreAction mvkMTLStoreActionFromVkAttachmentStoreOp(VkAttachmentStoreOp vkStoreOp, bool hasResolveAttachment);
 
 /** Returns the Metal MTLViewport corresponding to the specified Vulkan VkViewport. */
 MTLViewport mvkMTLViewportFromVkViewport(VkViewport vkViewport);
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm b/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm
index f23b2eb..8bc388d 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm
@@ -1095,8 +1095,9 @@
 	if (_image->getImageType() == VK_IMAGE_TYPE_1D) {
 		setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdClearImage(): Native 1D images cannot be cleared on this device. Consider enabling MVK_CONFIG_TEXTURE_1D_AS_2D."));
 	}
-	if ((_isDepthStencilClear && !_image->getSupportsAllFormatFeatures(VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) ||
-		(!_isDepthStencilClear && !_image->getSupportsAllFormatFeatures(VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT))) {
+	MVKMTLFmtCaps mtlFmtCaps = getPixelFormats()->getMTLPixelFormatCapabilities(_image->getMTLPixelFormat());
+	if ((_isDepthStencilClear && !mvkAreAllFlagsEnabled(mtlFmtCaps, kMVKMTLFmtCapsDSAtt)) ||
+		( !_isDepthStencilClear && !mvkAreAllFlagsEnabled(mtlFmtCaps, kMVKMTLFmtCapsColorAtt))) {
 		setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdClearImage(): Format %s cannot be cleared on this device.", getPixelFormats()->getVkFormatName(_image->getVkFormat())));
 	}
 }
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
index b2fc00e..95bbad2 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
@@ -287,9 +287,6 @@
 	
 #pragma mark Metal
 
-	/** Returns whether the underlying MTLDevice supports the GPU family. */
-	bool getSupportsGPUFamily(MTLGPUFamily gpuFamily);
-
 	/** Populates the specified structure with the Metal-specific features of this device. */
 	inline const MVKPhysicalDeviceMetalFeatures* getMetalFeatures() { return &_metalFeatures; }
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
index bc81118..423bc0e 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
@@ -51,6 +51,9 @@
 #	define MVKViewClass		NSView
 #endif
 
+#define supportsMTLFeatureSet(MFS)	[_mtlDevice supportsFeatureSet: MTLFeatureSet_ ##MFS]
+#define supportsMTLGPUFamily(GPUF)	([_mtlDevice respondsToSelector: @selector(supportsFamily:)] && [_mtlDevice supportsFamily: MTLGPUFamily ##GPUF])
+
 
 #pragma mark -
 #pragma mark MVKPhysicalDevice
@@ -488,11 +491,11 @@
 		colorSpaces.push_back(VK_COLOR_SPACE_BT709_NONLINEAR_EXT);
 		colorSpaces.push_back(VK_COLOR_SPACE_ADOBERGB_NONLINEAR_EXT);
 		colorSpaces.push_back(VK_COLOR_SPACE_PASS_THROUGH_EXT);
-		if (mvkOSVersion() >= 10.12) {
+		if (mvkOSVersionIsAtLeast(10.12)) {
 			colorSpaces.push_back(VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT);
 			colorSpaces.push_back(VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT);
 		}
-		if (mvkOSVersion() >= 10.14) {
+		if (mvkOSVersionIsAtLeast(10.14)) {
 			colorSpaces.push_back(VK_COLOR_SPACE_DISPLAY_P3_LINEAR_EXT);
 			colorSpaces.push_back(VK_COLOR_SPACE_BT2020_LINEAR_EXT);
 			colorSpaces.push_back(VK_COLOR_SPACE_HDR10_ST2084_EXT);
@@ -501,25 +504,25 @@
 #endif
 #if MVK_IOS
 		// iOS 8 doesn't support anything but sRGB.
-		if (mvkOSVersion() >= 9.0) {
+		if (mvkOSVersionIsAtLeast(9.0)) {
 			colorSpaces.push_back(VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT);
 			colorSpaces.push_back(VK_COLOR_SPACE_DCI_P3_NONLINEAR_EXT);
 			colorSpaces.push_back(VK_COLOR_SPACE_BT709_NONLINEAR_EXT);
 			colorSpaces.push_back(VK_COLOR_SPACE_ADOBERGB_NONLINEAR_EXT);
 			colorSpaces.push_back(VK_COLOR_SPACE_PASS_THROUGH_EXT);
 		}
-		if (mvkOSVersion() >= 10.0) {
+		if (mvkOSVersionIsAtLeast(10.0)) {
 			colorSpaces.push_back(VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT);
 			colorSpaces.push_back(VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT);
 		}
-		if (mvkOSVersion() >= 12.0) {
+		if (mvkOSVersionIsAtLeast(12.0)) {
 			colorSpaces.push_back(VK_COLOR_SPACE_HDR10_ST2084_EXT);
 		}
-		if (mvkOSVersion() >= 12.3) {
+		if (mvkOSVersionIsAtLeast(12.3)) {
 			colorSpaces.push_back(VK_COLOR_SPACE_DCI_P3_LINEAR_EXT);
 			colorSpaces.push_back(VK_COLOR_SPACE_BT2020_LINEAR_EXT);
 		}
-		if (mvkOSVersion() >= 13.0) {
+		if (mvkOSVersionIsAtLeast(13.0)) {
 			colorSpaces.push_back(VK_COLOR_SPACE_HDR10_HLG_EXT);
 		}
 #endif
@@ -755,14 +758,14 @@
 #pragma mark Construction
 
 MVKPhysicalDevice::MVKPhysicalDevice(MVKInstance* mvkInstance, id<MTLDevice> mtlDevice) :
+	_mtlDevice([mtlDevice retain]),		// Set first
 	_mvkInstance(mvkInstance),
 	_supportedExtensions(this, true),
-	_pixelFormats(this, mtlDevice),
-	_mtlDevice([mtlDevice retain]) {
+	_pixelFormats(this) {				// Set after _mtlDevice
 
-	initMetalFeatures();        // Call first.
-	initFeatures();             // Call second.
-	initProperties();           // Call third.
+	initMetalFeatures();        		// Call first.
+	initFeatures();             		// Call second.
+	initProperties();           		// Call third.
 	initMemoryProperties();
 	initExtensions();
 	logGPUInfo();
@@ -795,30 +798,30 @@
     _metalFeatures.texelBuffers = true;
 	_metalFeatures.maxTextureDimension = (4 * KIBI);
 
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily1_v2] ) {
+    if (supportsMTLFeatureSet(iOS_GPUFamily1_v2)) {
 		_metalFeatures.mslVersionEnum = MTLLanguageVersion1_1;
         _metalFeatures.dynamicMTLBufferSize = (4 * KIBI);
 		_metalFeatures.maxTextureDimension = (8 * KIBI);
     }
 
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily1_v3] ) {
+    if (supportsMTLFeatureSet(iOS_GPUFamily1_v3)) {
 		_metalFeatures.mslVersionEnum = MTLLanguageVersion1_2;
         _metalFeatures.shaderSpecialization = true;
         _metalFeatures.stencilViews = true;
 		_metalFeatures.fences = true;
     }
 
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily1_v4] ) {
+    if (supportsMTLFeatureSet(iOS_GPUFamily1_v4)) {
 		_metalFeatures.mslVersionEnum = MTLLanguageVersion2_0;
     }
 
-	if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily1_v5] ) {
+	if (supportsMTLFeatureSet(iOS_GPUFamily1_v5)) {
 		_metalFeatures.mslVersionEnum = MTLLanguageVersion2_1;
 		_metalFeatures.events = true;
 		_metalFeatures.textureBuffers = true;
 	}
 
-	if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily3_v1] ) {
+	if (supportsMTLFeatureSet(iOS_GPUFamily3_v1)) {
 		_metalFeatures.indirectDrawing = true;
 		_metalFeatures.baseVertexInstanceDrawing = true;
 		_metalFeatures.combinedStoreResolveAction = true;
@@ -827,26 +830,26 @@
 		_metalFeatures.depthSampleCompare = true;
 	}
 
-	if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily3_v2] ) {
+	if (supportsMTLFeatureSet(iOS_GPUFamily3_v2)) {
 		_metalFeatures.arrayOfTextures = true;
 	}
-	if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily3_v3] ) {
+	if (supportsMTLFeatureSet(iOS_GPUFamily3_v3)) {
 		_metalFeatures.arrayOfSamplers = true;
 	}
 
-	if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily4_v1] ) {
+	if (supportsMTLFeatureSet(iOS_GPUFamily4_v1)) {
 		_metalFeatures.postDepthCoverage = true;
 	}
 
-	if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily5_v1] ) {
+	if (supportsMTLFeatureSet(iOS_GPUFamily5_v1)) {
 		_metalFeatures.layeredRendering = true;
 		_metalFeatures.stencilFeedback = true;
 	}
 
-	if ( mvkOSVersion() >= 13.0 ) {
+	if ( mvkOSVersionIsAtLeast(13.0) ) {
 		_metalFeatures.mslVersionEnum = MTLLanguageVersion2_2;
 		_metalFeatures.placementHeaps = true;
-		if ( getSupportsGPUFamily(MTLGPUFamilyApple4) ) {
+		if (supportsMTLGPUFamily(Apple4)) {
 			_metalFeatures.nativeTextureSwizzle = true;
 		}
 	}
@@ -864,7 +867,7 @@
 	_metalFeatures.maxTextureDimension = (16 * KIBI);
 	_metalFeatures.depthSampleCompare = true;
 
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_macOS_GPUFamily1_v2] ) {
+    if (supportsMTLFeatureSet(macOS_GPUFamily1_v2)) {
 		_metalFeatures.mslVersionEnum = MTLLanguageVersion1_2;
         _metalFeatures.dynamicMTLBufferSize = (4 * KIBI);
         _metalFeatures.shaderSpecialization = true;
@@ -874,7 +877,7 @@
         _metalFeatures.maxMTLBufferSize = (1 * GIBI);
     }
 
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_macOS_GPUFamily1_v3] ) {
+    if (supportsMTLFeatureSet(macOS_GPUFamily1_v3)) {
 		_metalFeatures.mslVersionEnum = MTLLanguageVersion2_0;
         _metalFeatures.texelBuffers = true;
 		_metalFeatures.arrayOfTextures = true;
@@ -883,7 +886,7 @@
 		_metalFeatures.fences = true;
     }
 
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_macOS_GPUFamily1_v4] ) {
+    if (supportsMTLFeatureSet(macOS_GPUFamily1_v4)) {
         _metalFeatures.mslVersionEnum = MTLLanguageVersion2_1;
         _metalFeatures.multisampleArrayTextures = true;
 		_metalFeatures.events = true;
@@ -891,15 +894,15 @@
         _metalFeatures.textureBuffers = true;
     }
 
-	if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_macOS_GPUFamily2_v1] ) {
+	if (supportsMTLFeatureSet(macOS_GPUFamily2_v1)) {
 		_metalFeatures.multisampleLayeredRendering = _metalFeatures.layeredRendering;
 		_metalFeatures.stencilFeedback = true;
 	}
 
-	if ( mvkOSVersion() >= 10.15 ) {
+	if ( mvkOSVersionIsAtLeast(10.15) ) {
 		_metalFeatures.mslVersionEnum = MTLLanguageVersion2_2;
 		_metalFeatures.native3DCompressedTextures = true;
-		if ( getSupportsGPUFamily(MTLGPUFamilyMac2) ) {
+		if (supportsMTLGPUFamily(Mac2)) {
 			_metalFeatures.nativeTextureSwizzle = true;
 			_metalFeatures.placementHeaps = true;
 		}
@@ -957,11 +960,6 @@
 
 }
 
-bool MVKPhysicalDevice::getSupportsGPUFamily(MTLGPUFamily gpuFamily) {
-	return ([_mtlDevice respondsToSelector: @selector(supportsFamily:)] &&
-			[_mtlDevice supportsFamily: gpuFamily]);
-}
-
 // Initializes the physical device features of this instance.
 void MVKPhysicalDevice::initFeatures() {
 	mvkClear(&_features);	// Start with everything cleared
@@ -996,32 +994,32 @@
 #if MVK_IOS
     _features.textureCompressionETC2 = true;
 
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily2_v1] ) {
+    if (supportsMTLFeatureSet(iOS_GPUFamily2_v1)) {
         _features.textureCompressionASTC_LDR = true;
     }
 
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily3_v1] ) {
+    if (supportsMTLFeatureSet(iOS_GPUFamily3_v1)) {
         _features.occlusionQueryPrecise = true;
     }
 
-	if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily1_v4] ) {
+	if (supportsMTLFeatureSet(iOS_GPUFamily1_v4)) {
 		_features.dualSrcBlend = true;
 	}
 
-	if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily2_v4] ) {
+	if (supportsMTLFeatureSet(iOS_GPUFamily2_v4)) {
 		_features.depthClamp = true;
 	}
 
-	if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily3_v2] ) {
+	if (supportsMTLFeatureSet(iOS_GPUFamily3_v2)) {
 		_features.tessellationShader = true;
 		_features.shaderTessellationAndGeometryPointSize = true;
 	}
 
-	if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily4_v1] ) {
+	if (supportsMTLFeatureSet(iOS_GPUFamily4_v1)) {
 		_features.imageCubeArray = true;
 	}
   
-	if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily5_v1] ) {
+	if (supportsMTLFeatureSet(iOS_GPUFamily5_v1)) {
 		_features.multiViewport = true;
 	}
 #endif
@@ -1036,17 +1034,17 @@
 
     _features.shaderStorageImageArrayDynamicIndexing = _metalFeatures.arrayOfTextures;
 
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_macOS_GPUFamily1_v2] ) {
+    if (supportsMTLFeatureSet(macOS_GPUFamily1_v2)) {
         _features.tessellationShader = true;
         _features.dualSrcBlend = true;
         _features.shaderTessellationAndGeometryPointSize = true;
     }
 
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_macOS_GPUFamily1_v3] ) {
+    if (supportsMTLFeatureSet(macOS_GPUFamily1_v3)) {
         _features.multiViewport = true;
     }
 
-    if ( mvkOSVersion() >= 10.15 ) {
+    if ( mvkOSVersionIsAtLeast(10.15) ) {
         _features.shaderResourceMinLod = true;
     }
 #endif
@@ -1125,7 +1123,7 @@
 
 	// Limits
 #if MVK_IOS
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily2_v1] ) {
+    if (supportsMTLFeatureSet(iOS_GPUFamily2_v1)) {
         _properties.limits.maxColorAttachments = kMVKCachedColorAttachmentCount;
     } else {
         _properties.limits.maxColorAttachments = 4;		// < kMVKCachedColorAttachmentCount
@@ -1273,7 +1271,7 @@
         _properties.limits.minTexelBufferOffsetAlignment = max(maxStorage, maxUniform);
     } else {
 #if MVK_IOS
-        if ([_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily3_v1]) {
+        if (supportsMTLFeatureSet(iOS_GPUFamily3_v1)) {
             _properties.limits.minTexelBufferOffsetAlignment = 16;
         } else {
             _properties.limits.minTexelBufferOffsetAlignment = 64;
@@ -1291,16 +1289,16 @@
 #if MVK_IOS
     _properties.limits.maxFragmentInputComponents = 60;
 
-    if ([_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily3_v1]) {
+    if (supportsMTLFeatureSet(iOS_GPUFamily3_v1)) {
         _properties.limits.optimalBufferCopyOffsetAlignment = 16;
     } else {
         _properties.limits.optimalBufferCopyOffsetAlignment = 64;
     }
 
-    if ([_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily5_v1]) {
+    if (supportsMTLFeatureSet(iOS_GPUFamily5_v1)) {
         _properties.limits.maxTessellationGenerationLevel = 64;
         _properties.limits.maxTessellationPatchSize = 32;
-    } else if ([_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily3_v2]) {
+    } else if (supportsMTLFeatureSet(iOS_GPUFamily3_v2)) {
         _properties.limits.maxTessellationGenerationLevel = 16;
         _properties.limits.maxTessellationPatchSize = 32;
     } else {
@@ -1312,7 +1310,7 @@
     _properties.limits.maxFragmentInputComponents = 128;
     _properties.limits.optimalBufferCopyOffsetAlignment = 256;
 
-    if ([_mtlDevice supportsFeatureSet: MTLFeatureSet_macOS_GPUFamily1_v2]) {
+    if (supportsMTLFeatureSet(macOS_GPUFamily1_v2)) {
         _properties.limits.maxTessellationGenerationLevel = 64;
         _properties.limits.maxTessellationPatchSize = 32;
     } else {
@@ -1365,9 +1363,9 @@
 		_properties.limits.maxComputeSharedMemorySize = (uint32_t)_mtlDevice.maxThreadgroupMemoryLength;
 	} else {
 #if MVK_IOS
-		if ([_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily4_v1]) {
+		if (supportsMTLFeatureSet(iOS_GPUFamily4_v1)) {
 			_properties.limits.maxComputeSharedMemorySize = (32 * KIBI);
-		} else if ([_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily3_v1]) {
+		} else if (supportsMTLFeatureSet(iOS_GPUFamily3_v1)) {
 			_properties.limits.maxComputeSharedMemorySize = (16 * KIBI);
 		} else {
 			_properties.limits.maxComputeSharedMemorySize = ((16 * KIBI) - 32);
@@ -1510,13 +1508,13 @@
 void MVKPhysicalDevice::initGPUInfoProperties() {
 	NSUInteger coreCnt = NSProcessInfo.processInfo.processorCount;
 	uint32_t devID = 0xa070;
-	if ([_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily5_v1]) {
+	if (supportsMTLFeatureSet(iOS_GPUFamily5_v1)) {
 		devID = 0xa120;
-	} else if ([_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily4_v1]) {
+	} else if (supportsMTLFeatureSet(iOS_GPUFamily4_v1)) {
 		devID = 0xa110;
-	} else if ([_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily3_v1]) {
+	} else if (supportsMTLFeatureSet(iOS_GPUFamily3_v1)) {
 		devID = coreCnt > 2 ? 0xa101 : 0xa100;
-	} else if ([_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily2_v1]) {
+	} else if (supportsMTLFeatureSet(iOS_GPUFamily2_v1)) {
 		devID = coreCnt > 2 ? 0xa081 : 0xa080;
 	}
 
@@ -1678,21 +1676,21 @@
 	// (Mac & Apple GPU lists should be mutex on platform)
 	uint32_t mtlVer = 0;
 #if MVK_IOS
-	if (mvkOSVersion() >= 13.0) { mtlVer = 0x30000; }
+	if (mvkOSVersionIsAtLeast(13.0)) { mtlVer = 0x30000; }
 #endif
 #if MVK_MACOS
-	if (mvkOSVersion() >= 10.15) { mtlVer = 0x30000; }
+	if (mvkOSVersionIsAtLeast(10.15)) { mtlVer = 0x30000; }
 #endif
 
 	MTLGPUFamily mtlFam = MTLGPUFamily(0);
-	if (getSupportsGPUFamily(MTLGPUFamilyMac1)) { mtlFam = MTLGPUFamilyMac1; }
-	if (getSupportsGPUFamily(MTLGPUFamilyMac2)) { mtlFam = MTLGPUFamilyMac2; }
+	if (supportsMTLGPUFamily(Mac1)) { mtlFam = MTLGPUFamilyMac1; }
+	if (supportsMTLGPUFamily(Mac2)) { mtlFam = MTLGPUFamilyMac2; }
 
-	if (getSupportsGPUFamily(MTLGPUFamilyApple1)) { mtlFam = MTLGPUFamilyApple1; }
-	if (getSupportsGPUFamily(MTLGPUFamilyApple2)) { mtlFam = MTLGPUFamilyApple2; }
-	if (getSupportsGPUFamily(MTLGPUFamilyApple3)) { mtlFam = MTLGPUFamilyApple3; }
-	if (getSupportsGPUFamily(MTLGPUFamilyApple4)) { mtlFam = MTLGPUFamilyApple4; }
-	if (getSupportsGPUFamily(MTLGPUFamilyApple5)) { mtlFam = MTLGPUFamilyApple5; }
+	if (supportsMTLGPUFamily(Apple1)) { mtlFam = MTLGPUFamilyApple1; }
+	if (supportsMTLGPUFamily(Apple2)) { mtlFam = MTLGPUFamilyApple2; }
+	if (supportsMTLGPUFamily(Apple3)) { mtlFam = MTLGPUFamilyApple3; }
+	if (supportsMTLGPUFamily(Apple4)) { mtlFam = MTLGPUFamilyApple4; }
+	if (supportsMTLGPUFamily(Apple5)) { mtlFam = MTLGPUFamilyApple5; }
 
 	// Not explicitly guaranteed to be unique...but close enough without spilling over
 	uint32_t mtlFS = (mtlVer << 8) + (uint32_t)mtlFam;
@@ -1824,7 +1822,7 @@
 	// Memoryless storage
 	uint32_t memlessBit = 0;
 #if MVK_IOS
-	if ([_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily1_v3]) {
+	if (supportsMTLFeatureSet(iOS_GPUFamily1_v3)) {
 		memlessBit = 1 << typeIdx;
 		setMemoryType(typeIdx, mainHeapIdx, MVK_VK_MEMORY_TYPE_METAL_MEMORYLESS);
 		typeIdx++;
@@ -1932,55 +1930,55 @@
 	logMsg += "\n\tsupports the following Metal Versions, GPU's and Feature Sets:";
 	logMsg += "\n\t\tMetal Shading Language %s";
 
-	if (getSupportsGPUFamily(MTLGPUFamilyApple5)) { logMsg += "\n\t\tGPU Family Apple 5"; }
-	if (getSupportsGPUFamily(MTLGPUFamilyApple4)) { logMsg += "\n\t\tGPU Family Apple 4"; }
-	if (getSupportsGPUFamily(MTLGPUFamilyApple3)) { logMsg += "\n\t\tGPU Family Apple 3"; }
-	if (getSupportsGPUFamily(MTLGPUFamilyApple2)) { logMsg += "\n\t\tGPU Family Apple 2"; }
-	if (getSupportsGPUFamily(MTLGPUFamilyApple1)) { logMsg += "\n\t\tGPU Family Apple 1"; }
+	if (supportsMTLGPUFamily(Apple5)) { logMsg += "\n\t\tGPU Family Apple 5"; }
+	if (supportsMTLGPUFamily(Apple4)) { logMsg += "\n\t\tGPU Family Apple 4"; }
+	if (supportsMTLGPUFamily(Apple3)) { logMsg += "\n\t\tGPU Family Apple 3"; }
+	if (supportsMTLGPUFamily(Apple2)) { logMsg += "\n\t\tGPU Family Apple 2"; }
+	if (supportsMTLGPUFamily(Apple1)) { logMsg += "\n\t\tGPU Family Apple 1"; }
 
-	if (getSupportsGPUFamily(MTLGPUFamilyMac2)) { logMsg += "\n\t\tGPU Family Mac 2"; }
-	if (getSupportsGPUFamily(MTLGPUFamilyMac1)) { logMsg += "\n\t\tGPU Family Mac 1"; }
+	if (supportsMTLGPUFamily(Mac2)) { logMsg += "\n\t\tGPU Family Mac 2"; }
+	if (supportsMTLGPUFamily(Mac1)) { logMsg += "\n\t\tGPU Family Mac 1"; }
 
-	if (getSupportsGPUFamily(MTLGPUFamilyCommon3)) { logMsg += "\n\t\tGPU Family Common 3"; }
-	if (getSupportsGPUFamily(MTLGPUFamilyCommon2)) { logMsg += "\n\t\tGPU Family Common 2"; }
-	if (getSupportsGPUFamily(MTLGPUFamilyCommon1)) { logMsg += "\n\t\tGPU Family Common 1"; }
+	if (supportsMTLGPUFamily(Common3)) { logMsg += "\n\t\tGPU Family Common 3"; }
+	if (supportsMTLGPUFamily(Common2)) { logMsg += "\n\t\tGPU Family Common 2"; }
+	if (supportsMTLGPUFamily(Common1)) { logMsg += "\n\t\tGPU Family Common 1"; }
 
-	if (getSupportsGPUFamily(MTLGPUFamilyMacCatalyst2)) { logMsg += "\n\t\tGPU Family Mac Catalyst 2"; }
-	if (getSupportsGPUFamily(MTLGPUFamilyMacCatalyst1)) { logMsg += "\n\t\tGPU Family Mac Catalyst 1"; }
+	if (supportsMTLGPUFamily(MacCatalyst2)) { logMsg += "\n\t\tGPU Family Mac Catalyst 2"; }
+	if (supportsMTLGPUFamily(MacCatalyst1)) { logMsg += "\n\t\tGPU Family Mac Catalyst 1"; }
 
 #if MVK_IOS
-	if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily5_v1] ) { logMsg += "\n\t\tiOS GPU Family 5 v1"; }
+	if (supportsMTLFeatureSet(iOS_GPUFamily5_v1)) { logMsg += "\n\t\tiOS GPU Family 5 v1"; }
 
-	if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily4_v2] ) { logMsg += "\n\t\tiOS GPU Family 4 v2"; }
-	if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily4_v1] ) { logMsg += "\n\t\tiOS GPU Family 4 v1"; }
+	if (supportsMTLFeatureSet(iOS_GPUFamily4_v2)) { logMsg += "\n\t\tiOS GPU Family 4 v2"; }
+	if (supportsMTLFeatureSet(iOS_GPUFamily4_v1)) { logMsg += "\n\t\tiOS GPU Family 4 v1"; }
 
-	if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily3_v4] ) { logMsg += "\n\t\tiOS GPU Family 3 v4"; }
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily3_v3] ) { logMsg += "\n\t\tiOS GPU Family 3 v3"; }
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily3_v2] ) { logMsg += "\n\t\tiOS GPU Family 3 v2"; }
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily3_v1] ) { logMsg += "\n\t\tiOS GPU Family 3 v1"; }
+	if (supportsMTLFeatureSet(iOS_GPUFamily3_v4)) { logMsg += "\n\t\tiOS GPU Family 3 v4"; }
+    if (supportsMTLFeatureSet(iOS_GPUFamily3_v3)) { logMsg += "\n\t\tiOS GPU Family 3 v3"; }
+    if (supportsMTLFeatureSet(iOS_GPUFamily3_v2)) { logMsg += "\n\t\tiOS GPU Family 3 v2"; }
+    if (supportsMTLFeatureSet(iOS_GPUFamily3_v1)) { logMsg += "\n\t\tiOS GPU Family 3 v1"; }
 
-	if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily2_v5] ) { logMsg += "\n\t\tiOS GPU Family 2 v5"; }
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily2_v4] ) { logMsg += "\n\t\tiOS GPU Family 2 v4"; }
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily2_v3] ) { logMsg += "\n\t\tiOS GPU Family 2 v3"; }
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily2_v2] ) { logMsg += "\n\t\tiOS GPU Family 2 v2"; }
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily2_v1] ) { logMsg += "\n\t\tiOS GPU Family 2 v1"; }
+	if (supportsMTLFeatureSet(iOS_GPUFamily2_v5)) { logMsg += "\n\t\tiOS GPU Family 2 v5"; }
+    if (supportsMTLFeatureSet(iOS_GPUFamily2_v4)) { logMsg += "\n\t\tiOS GPU Family 2 v4"; }
+    if (supportsMTLFeatureSet(iOS_GPUFamily2_v3)) { logMsg += "\n\t\tiOS GPU Family 2 v3"; }
+    if (supportsMTLFeatureSet(iOS_GPUFamily2_v2)) { logMsg += "\n\t\tiOS GPU Family 2 v2"; }
+    if (supportsMTLFeatureSet(iOS_GPUFamily2_v1)) { logMsg += "\n\t\tiOS GPU Family 2 v1"; }
 
-	if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily1_v5] ) { logMsg += "\n\t\tiOS GPU Family 1 v5"; }
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily1_v4] ) { logMsg += "\n\t\tiOS GPU Family 1 v4"; }
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily1_v3] ) { logMsg += "\n\t\tiOS GPU Family 1 v3"; }
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily1_v2] ) { logMsg += "\n\t\tiOS GPU Family 1 v2"; }
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily1_v1] ) { logMsg += "\n\t\tiOS GPU Family 1 v1"; }
+	if (supportsMTLFeatureSet(iOS_GPUFamily1_v5)) { logMsg += "\n\t\tiOS GPU Family 1 v5"; }
+    if (supportsMTLFeatureSet(iOS_GPUFamily1_v4)) { logMsg += "\n\t\tiOS GPU Family 1 v4"; }
+    if (supportsMTLFeatureSet(iOS_GPUFamily1_v3)) { logMsg += "\n\t\tiOS GPU Family 1 v3"; }
+    if (supportsMTLFeatureSet(iOS_GPUFamily1_v2)) { logMsg += "\n\t\tiOS GPU Family 1 v2"; }
+    if (supportsMTLFeatureSet(iOS_GPUFamily1_v1)) { logMsg += "\n\t\tiOS GPU Family 1 v1"; }
 #endif
 
 #if MVK_MACOS
-	if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_macOS_GPUFamily2_v1] ) { logMsg += "\n\t\tmacOS GPU Family 2 v1"; }
+	if (supportsMTLFeatureSet(macOS_GPUFamily2_v1)) { logMsg += "\n\t\tmacOS GPU Family 2 v1"; }
 
-	if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_macOS_GPUFamily1_v4] ) { logMsg += "\n\t\tmacOS GPU Family 1 v4"; }
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_macOS_GPUFamily1_v3] ) { logMsg += "\n\t\tmacOS GPU Family 1 v3"; }
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_macOS_GPUFamily1_v2] ) { logMsg += "\n\t\tmacOS GPU Family 1 v2"; }
-    if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_macOS_GPUFamily1_v1] ) { logMsg += "\n\t\tmacOS GPU Family 1 v1"; }
+	if (supportsMTLFeatureSet(macOS_GPUFamily1_v4)) { logMsg += "\n\t\tmacOS GPU Family 1 v4"; }
+    if (supportsMTLFeatureSet(macOS_GPUFamily1_v3)) { logMsg += "\n\t\tmacOS GPU Family 1 v3"; }
+    if (supportsMTLFeatureSet(macOS_GPUFamily1_v2)) { logMsg += "\n\t\tmacOS GPU Family 1 v2"; }
+    if (supportsMTLFeatureSet(macOS_GPUFamily1_v1)) { logMsg += "\n\t\tmacOS GPU Family 1 v1"; }
 
-	if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_macOS_ReadWriteTextureTier2] ) { logMsg += "\n\t\tmacOS Read-Write Texture Tier 2"; }
+	if (supportsMTLFeatureSet(macOS_ReadWriteTextureTier2)) { logMsg += "\n\t\tmacOS Read-Write Texture Tier 2"; }
 
 #endif
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
index 1b991d4..48a951a 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
@@ -65,21 +65,12 @@
     /** Returns the Vulkan image format of this image. */
     VkFormat getVkFormat();
 
+	/** Returns whether this image has a depth or stencil format. */
+	bool getIsDepthStencil();
+
 	/** Returns whether this image is compressed. */
 	bool getIsCompressed();
 
-	/**
-	 * Returns whether the format of this image supports ANY of the indicated feature flags,
-	 * taking into consideration whether this image is using linear or optimal tiling.
-	 */
-	bool getSupportsAnyFormatFeature(VkFormatFeatureFlags requiredFormatFeatureFlags);
-
-	/**
-	 * Returns whether the format of this image supports ALL of the indicated feature flags,
-	 * taking into consideration whether this image is using linear or optimal tiling.
-	 */
-	bool getSupportsAllFormatFeatures(VkFormatFeatureFlags requiredFormatFeatureFlags);
-
 	/** 
 	 * Returns the 3D extent of this image at the base mipmap level.
 	 * For 2D or cube images, the Z component will be 1.  
@@ -200,9 +191,6 @@
 	/** Returns the Metal texture type of this image. */
 	inline MTLTextureType getMTLTextureType() { return _mtlTextureType; }
 
-	/** Returns the Metal texture usage of this image. */
-	MTLTextureUsage getMTLTextureUsage();
-
     /** 
      * Returns whether the Metal texel size is the same as the Vulkan texel size.
      *
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
index c2410a4..dd30a27 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
@@ -42,23 +42,9 @@
 
 VkFormat MVKImage::getVkFormat() { return getPixelFormats()->getVkFormatFromMTLPixelFormat(_mtlPixelFormat); }
 
-bool MVKImage::getIsCompressed() {
-	return getPixelFormats()->getFormatTypeFromMTLPixelFormat(_mtlPixelFormat) == kMVKFormatCompressed;
-}
+bool MVKImage::getIsDepthStencil() { return getPixelFormats()->getFormatTypeFromMTLPixelFormat(_mtlPixelFormat) == kMVKFormatDepthStencil; }
 
-bool MVKImage::getSupportsAnyFormatFeature(VkFormatFeatureFlags requiredFormatFeatureFlags) {
-	VkFormatProperties props;
-	_device->getPhysicalDevice()->getFormatProperties(getVkFormat(), &props);
-	VkFormatFeatureFlags imageFeatureFlags = _isLinear ? props.linearTilingFeatures : props.optimalTilingFeatures;
-	return mvkIsAnyFlagEnabled(imageFeatureFlags, requiredFormatFeatureFlags);
-}
-
-bool MVKImage::getSupportsAllFormatFeatures(VkFormatFeatureFlags requiredFormatFeatureFlags) {
-	VkFormatProperties props;
-	_device->getPhysicalDevice()->getFormatProperties(getVkFormat(), &props);
-	VkFormatFeatureFlags imageFeatureFlags = _isLinear ? props.linearTilingFeatures : props.optimalTilingFeatures;
-	return mvkAreAllFlagsEnabled(imageFeatureFlags, requiredFormatFeatureFlags);
-}
+bool MVKImage::getIsCompressed() { return getPixelFormats()->getFormatTypeFromMTLPixelFormat(_mtlPixelFormat) == kMVKFormatCompressed; }
 
 VkExtent3D MVKImage::getExtent3D(uint32_t mipLevel) {
 	return mvkMipmapLevelSizeFromBaseSize3D(_extent, mipLevel);
@@ -350,7 +336,7 @@
 		mtlTex = [_deviceMemory->_mtlBuffer newTextureWithDescriptor: mtlTexDesc
 															  offset: getDeviceMemoryOffset()
 														 bytesPerRow: _subresources[0].layout.rowPitch];
-	} else if (_deviceMemory->_mtlHeap) {
+	} else if (_deviceMemory->_mtlHeap && !getIsDepthStencil()) {	// Metal support for depth/stencil from heaps is flaky
 		mtlTex = [_deviceMemory->_mtlHeap newTextureWithDescriptor: mtlTexDesc
 															offset: getDeviceMemoryOffset()];
 		if (_isAliasable) [mtlTex makeAliasable];
@@ -419,51 +405,22 @@
     return VK_SUCCESS;
 }
 
-MTLTextureUsage MVKImage::getMTLTextureUsage() {
-
-	MTLTextureUsage usage = mvkMTLTextureUsageFromVkImageUsageFlags(_usage);
-
-	// Remove view usage from D/S if Metal doesn't support it
-	MVKPixelFormats* pixFmts = getPixelFormats();
-	if ( !_device->_pMetalFeatures->stencilViews &&
-		pixFmts->mtlPixelFormatIsDepthFormat(_mtlPixelFormat) &&
-		pixFmts->mtlPixelFormatIsStencilFormat(_mtlPixelFormat)) {
-
-		mvkDisableFlags(usage, MTLTextureUsagePixelFormatView);
-	}
-
-	// If this format doesn't support being rendered to, disable MTLTextureUsageRenderTarget.
-	if ( !getSupportsAnyFormatFeature(VK_FORMAT_FEATURE_BLIT_DST_BIT |
-									  VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT |
-									  VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) ) {
-		mvkDisableFlags(usage, MTLTextureUsageRenderTarget);
-	}
-
-#if MVK_MACOS
-	// If this is a 3D compressed texture, tell Metal we might write to it.
-	if (_is3DCompressed) {
-		mvkEnableFlags(usage, MTLTextureUsageShaderWrite);
-	}
-#endif
-
-	return usage;
-}
-
 // Returns a Metal texture descriptor constructed from the properties of this image.
 // It is the caller's responsibility to release the returned descriptor object.
 MTLTextureDescriptor* MVKImage::newMTLTextureDescriptor() {
-	MTLTextureDescriptor* mtlTexDesc = [MTLTextureDescriptor new];	// retained
+	MTLPixelFormat mtlPixFmt = _mtlPixelFormat;
+	MTLTextureUsage minUsage = MTLTextureUsageUnknown;
 #if MVK_MACOS
 	if (_is3DCompressed) {
 		// Metal before 3.0 doesn't support 3D compressed textures, so we'll decompress
 		// the texture ourselves. This, then, is the *uncompressed* format.
-		mtlTexDesc.pixelFormat = MTLPixelFormatBGRA8Unorm;
-	} else {
-		mtlTexDesc.pixelFormat = _mtlPixelFormat;
+		mtlPixFmt = MTLPixelFormatBGRA8Unorm;
+		minUsage = MTLTextureUsageShaderWrite;
 	}
-#else
-	mtlTexDesc.pixelFormat = _mtlPixelFormat;
 #endif
+
+	MTLTextureDescriptor* mtlTexDesc = [MTLTextureDescriptor new];	// retained
+	mtlTexDesc.pixelFormat = mtlPixFmt;
 	mtlTexDesc.textureType = _mtlTextureType;
 	mtlTexDesc.width = _extent.width;
 	mtlTexDesc.height = _extent.height;
@@ -471,7 +428,7 @@
 	mtlTexDesc.mipmapLevelCount = _mipLevels;
 	mtlTexDesc.sampleCount = mvkSampleCountFromVkSampleCountFlagBits(_samples);
 	mtlTexDesc.arrayLength = _arrayLayers;
-	mtlTexDesc.usageMVK = getMTLTextureUsage();
+	mtlTexDesc.usageMVK = getPixelFormats()->getMTLTextureUsageFromVkImageUsageFlags(_usage, mtlPixFmt, minUsage);
 	mtlTexDesc.storageModeMVK = getMTLStorageMode();
 	mtlTexDesc.cpuCacheMode = getMTLCPUCacheMode();
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKInstance.h b/MoltenVK/MoltenVK/GPUObjects/MVKInstance.h
index 575033d..aa10c2c 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKInstance.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKInstance.h
@@ -19,6 +19,7 @@
 #pragma once
 
 #include "MVKEnvironment.h"
+#include "MVKDevice.h"
 #include "MVKLayers.h"
 #include "MVKVulkanAPIObject.h"
 #include "MVKVector.h"
@@ -27,8 +28,6 @@
 #include <string>
 #include <mutex>
 
-class MVKPhysicalDevice;
-class MVKDevice;
 class MVKSurface;
 class MVKDebugReportCallback;
 class MVKDebugUtilsMessenger;
@@ -190,7 +189,7 @@
 
 	MVKConfiguration _mvkConfig;
 	VkApplicationInfo _appInfo;
-	MVKVectorInline<MVKPhysicalDevice*, 1> _physicalDevices;
+	MVKVectorInline<MVKPhysicalDevice, 2> _physicalDevices;
 	MVKVectorDefault<MVKDebugReportCallback*> _debugReportCallbacks;
 	MVKVectorDefault<MVKDebugUtilsMessenger*> _debugUtilMessengers;
 	std::unordered_map<std::string, MVKEntryPoint> _entryPoints;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm b/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm
index 12787cc..5706f58 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm
@@ -18,9 +18,7 @@
 
 
 #include "MVKInstance.h"
-#include "MVKDevice.h"
 #include "MVKFoundation.h"
-#include "MVKEnvironment.h"
 #include "MVKSurface.h"
 #include "MVKOSExtensions.h"
 #include "MVKLogging.h"
@@ -64,7 +62,7 @@
 
 	// Now populate the devices
 	for (uint32_t pdIdx = 0; pdIdx < *pCount; pdIdx++) {
-		pPhysicalDevices[pdIdx] = _physicalDevices[pdIdx]->getVkPhysicalDevice();
+		pPhysicalDevices[pdIdx] = _physicalDevices[pdIdx].getVkPhysicalDevice();
 	}
 
 	return result;
@@ -91,7 +89,7 @@
 	// Now populate the device groups
 	for (uint32_t pdIdx = 0; pdIdx < *pCount; pdIdx++) {
 		pPhysicalDeviceGroupProps[pdIdx].physicalDeviceCount = 1;
-		pPhysicalDeviceGroupProps[pdIdx].physicalDevices[0] = _physicalDevices[pdIdx]->getVkPhysicalDevice();
+		pPhysicalDeviceGroupProps[pdIdx].physicalDevices[0] = _physicalDevices[pdIdx].getVkPhysicalDevice();
 		pPhysicalDeviceGroupProps[pdIdx].subsetAllocation = VK_FALSE;
 	}
 
@@ -359,9 +357,8 @@
 	// and other Obj-C classes, so wrap it all in an autorelease pool.
 	@autoreleasepool {
 		NSArray<id<MTLDevice>>* mtlDevices = availableMTLDevicesArray();
-		_physicalDevices.reserve(mtlDevices.count);
 		for (id<MTLDevice> mtlDev in mtlDevices) {
-			_physicalDevices.push_back(new MVKPhysicalDevice(this, mtlDev));
+			_physicalDevices.emplace_back(this, mtlDev);
 		}
 	}
 
@@ -679,10 +676,8 @@
 }
 
 MVKInstance::~MVKInstance() {
-	_useCreationCallbacks = true;
-	mvkDestroyContainerContents(_physicalDevices);
-
 	lock_guard<mutex> lock(_dcbLock);
+	_useCreationCallbacks = true;
 	mvkDestroyContainerContents(_debugReportCallbacks);
 }
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.h b/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.h
index c01d950..2e09b0b 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.h
@@ -25,6 +25,8 @@
 
 #import <Metal/Metal.h>
 
+class MVKPhysicalDevice;
+
 
 // Validate these values periodically as new formats are added over time.
 static const uint32_t _vkFormatCount = 256;
@@ -120,7 +122,7 @@
 public:
 
 	/** Returns the Vulkan API opaque object controlling this object. */
-	MVKVulkanAPIObject* getVulkanAPIObject() override { return _apiObject; };
+	MVKVulkanAPIObject* getVulkanAPIObject() override;
 
 	/** Returns whether the VkFormat is supported by this implementation. */
 	bool vkFormatIsSupported(VkFormat vkFormat);
@@ -260,6 +262,14 @@
 	/** Returns the Vulkan image usage from the Metal texture usage and format. */
 	VkImageUsageFlags getVkImageUsageFlagsFromMTLTextureUsage(MTLTextureUsage mtlUsage, MTLPixelFormat mtlFormat);
 
+	/**
+	 * 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.
+	 */
+	MTLTextureUsage getMTLTextureUsageFromVkImageUsageFlags(VkImageUsageFlags vkImageUsageFlags,
+															MTLPixelFormat mtlFormat,
+															MTLTextureUsage minUsage = MTLTextureUsageUnknown);
+
 	/** 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);
 
@@ -272,9 +282,7 @@
 
 #pragma mark Construction
 
-	MVKPixelFormats(MVKVulkanAPIObject* apiObject, id<MTLDevice> mtlDevice);
-
-	MVKPixelFormats();
+	MVKPixelFormats(MVKPhysicalDevice* physicalDevice = nullptr);
 
 protected:
 	MVKVkFormatDesc& getVkFormatDesc(VkFormat vkFormat);
@@ -286,12 +294,12 @@
 	VkFormatFeatureFlags getOptimalTilingFeatures(MVKMTLFmtCaps mtlFmtCaps);
 	VkFormatFeatureFlags getLinearTilingFeatures(MVKMTLFmtCaps mtlFmtCaps, MVKFormatType mvkFmtType);
 	VkFormatFeatureFlags getBufferFeatures(MVKMTLFmtCaps mtlFmtTexCaps, MVKMTLFmtCaps mtlFmtVtxCaps, MVKFormatType mvkFmtType);
-	void init(id<MTLDevice> mtlDevice);
 	void initVkFormatCapabilities();
 	void initMTLPixelFormatCapabilities();
 	void initMTLVertexFormatCapabilities();
 	void buildMTLFormatMaps();
 	void buildVkFormatMaps();
+	void modifyMTLFormatCapabilities();
 	void modifyMTLFormatCapabilities(id<MTLDevice> mtlDevice);
 	void addMTLPixelFormatCapabilities(id<MTLDevice> mtlDevice,
 									   MTLFeatureSet mtlFeatSet,
@@ -305,9 +313,9 @@
 	template<typename T>
 	void testFmt(const T v1, const T v2, const char* fmtName, const char* funcName);
 	void testProps(const VkFormatProperties p1, const VkFormatProperties p2, const char* fmtName);
-	void test(id<MTLDevice> mtlDevice);
+	void test();
 
-	MVKVulkanAPIObject* _apiObject;
+	MVKPhysicalDevice* _physicalDevice;
 	MVKVkFormatDesc _vkFormatDescriptions[_vkFormatCount];
 	MVKMTLFormatDesc _mtlPixelFormatDescriptions[_mtlPixelFormatCount];
 	MVKMTLFormatDesc _mtlVertexFormatDescriptions[_mtlVertexFormatCount];
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.mm b/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.mm
index a8526de..3b08761 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.mm
@@ -17,7 +17,7 @@
  */
 
 #include "MVKPixelFormats.h"
-#include "MVKVulkanAPIObject.h"
+#include "MVKDevice.h"
 #include "MVKFoundation.h"
 #include "MVKLogging.h"
 #include <string>
@@ -116,6 +116,8 @@
 #pragma mark -
 #pragma mark MVKPixelFormats
 
+MVKVulkanAPIObject* MVKPixelFormats::getVulkanAPIObject() { return _physicalDevice; };
+
 bool MVKPixelFormats::vkFormatIsSupported(VkFormat vkFormat) {
 	return getVkFormatDesc(vkFormat).isSupported();
 }
@@ -207,7 +209,7 @@
 				errMsg += (vkDescSubs.name) ? vkDescSubs.name : to_string(vkDescSubs.vkFormat);
 				errMsg += " instead.";
 			}
-			MVKBaseObject::reportError(_apiObject, VK_ERROR_FORMAT_NOT_SUPPORTED, "%s", errMsg.c_str());
+			MVKBaseObject::reportError(_physicalDevice, VK_ERROR_FORMAT_NOT_SUPPORTED, "%s", errMsg.c_str());
 		}
 	}
 
@@ -320,7 +322,7 @@
 			errMsg += (vkDescSubs.name) ? vkDescSubs.name : to_string(vkDescSubs.vkFormat);
 			errMsg += " instead.";
 		}
-		MVKBaseObject::reportError(_apiObject, VK_ERROR_FORMAT_NOT_SUPPORTED, "%s", errMsg.c_str());
+		MVKBaseObject::reportError(_physicalDevice, VK_ERROR_FORMAT_NOT_SUPPORTED, "%s", errMsg.c_str());
 	}
 
 	return mtlVtxFmt;
@@ -395,6 +397,57 @@
     return vkImageUsageFlags;
 }
 
+MTLTextureUsage MVKPixelFormats::getMTLTextureUsageFromVkImageUsageFlags(VkImageUsageFlags vkImageUsageFlags,
+																		 MTLPixelFormat mtlFormat,
+																		 MTLTextureUsage minUsage) {
+	bool isDepthFmt = mtlPixelFormatIsDepthFormat(mtlFormat);
+	bool isStencilFmt = mtlPixelFormatIsStencilFormat(mtlFormat);
+	bool isCombinedDepthStencilFmt = isDepthFmt && isStencilFmt;
+	bool isColorFormat = !(isDepthFmt || isStencilFmt);
+	bool supportsStencilViews = _physicalDevice ? _physicalDevice->getMetalFeatures()->stencilViews : false;
+	MVKMTLFmtCaps mtlFmtCaps = getMTLPixelFormatCapabilities(mtlFormat);
+
+	MTLTextureUsage mtlUsage = minUsage;
+
+	// Read from...
+	if (mvkIsAnyFlagEnabled(vkImageUsageFlags, (VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
+												VK_IMAGE_USAGE_SAMPLED_BIT |
+												VK_IMAGE_USAGE_STORAGE_BIT |
+												VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT))) {
+		mvkEnableFlags(mtlUsage, MTLTextureUsageShaderRead);
+	}
+
+	// Write to, but only if format supports writing...
+	if (mvkIsAnyFlagEnabled(vkImageUsageFlags, (VK_IMAGE_USAGE_STORAGE_BIT)) &&
+		mvkIsAnyFlagEnabled(mtlFmtCaps, kMVKMTLFmtCapsWrite)) {
+
+		mvkEnableFlags(mtlUsage, MTLTextureUsageShaderWrite);
+	}
+
+	// Render to but only if format supports rendering...
+	if (mvkIsAnyFlagEnabled(vkImageUsageFlags, (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
+												VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT |
+												VK_IMAGE_USAGE_TRANSFER_DST_BIT)) &&	// Scaling a BLIT may use rendering.
+		mvkIsAnyFlagEnabled(mtlFmtCaps, (kMVKMTLFmtCapsColorAtt | kMVKMTLFmtCapsDSAtt))) {
+
+		mvkEnableFlags(mtlUsage, MTLTextureUsageRenderTarget);
+	}
+
+	// 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 |
+												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))) {
+
+		mvkEnableFlags(mtlUsage, MTLTextureUsagePixelFormatView);
+	}
+
+	return mtlUsage;
+}
+
 // Return a reference to the Vulkan format descriptor corresponding to the VkFormat.
 MVKVkFormatDesc& MVKPixelFormats::getVkFormatDesc(VkFormat vkFormat) {
 	uint16_t fmtIdx = (vkFormat < _vkFormatCoreCount) ? _vkFormatDescIndicesByVkFormatsCore[vkFormat] : _vkFormatDescIndicesByVkFormatsExt[vkFormat];
@@ -431,30 +484,19 @@
 
 #pragma mark Construction
 
-MVKPixelFormats::MVKPixelFormats(MVKVulkanAPIObject* apiObject, id<MTLDevice> mtlDevice) : _apiObject(apiObject) {
-	init(mtlDevice);
-}
-
-//	Retrieves the default MTLDevice, which needs to be released after use.
-MVKPixelFormats::MVKPixelFormats() : _apiObject(nullptr) {
-	id<MTLDevice> mtlDevice = MTLCreateSystemDefaultDevice();	// retained
-	init(mtlDevice);
-	[mtlDevice release];	// Release temp instance
-}
-
-void MVKPixelFormats::init(id<MTLDevice> mtlDevice) {
+MVKPixelFormats::MVKPixelFormats(MVKPhysicalDevice* physicalDevice) : _physicalDevice(physicalDevice) {
 
 	// Build and update the Metal formats
 	initMTLPixelFormatCapabilities();
 	initMTLVertexFormatCapabilities();
 	buildMTLFormatMaps();
-	modifyMTLFormatCapabilities(mtlDevice);
+	modifyMTLFormatCapabilities();
 
 	// Build the Vulkan formats and link them to the Metal formats
 	initVkFormatCapabilities();
 	buildVkFormatMaps();
 
-//	test(mtlDevice);
+//	test();
 }
 
 #define addVkFormatDesc(VK_FMT, MTL_FMT, MTL_FMT_ALT, MTL_VTX_FMT, MTL_VTX_FMT_ALT, BLK_W, BLK_H, BLK_BYTE_CNT, MVK_FMT_TYPE)  \
@@ -1009,6 +1051,18 @@
 	}
 }
 
+// If supporting a physical device, retrieve the MTLDevice from it,
+// otherwise create a temp copy of the system default MTLDevice.
+void MVKPixelFormats::modifyMTLFormatCapabilities() {
+	if (_physicalDevice) {
+		modifyMTLFormatCapabilities(_physicalDevice->getMTLDevice());
+	} else {
+		id<MTLDevice> mtlDevice = MTLCreateSystemDefaultDevice();	// temp retained
+		modifyMTLFormatCapabilities(mtlDevice);
+		[mtlDevice release];										// release temp instance
+	}
+}
+
 #define addMTLPixelFormatCapabilities(FEAT_SET, MTL_FMT, CAPS)  \
 	addMTLPixelFormatCapabilities(mtlDevice, MTLFeatureSet_ ##FEAT_SET, MTLPixelFormat ##MTL_FMT, kMVKMTLFmtCaps ##CAPS)
 
@@ -1018,8 +1072,6 @@
 // Modifies the format capability tables based on the capabilities of the specific MTLDevice
 #if MVK_MACOS
 void MVKPixelFormats::modifyMTLFormatCapabilities(id<MTLDevice> mtlDevice) {
-	if ( !mtlDevice ) { return; }
-
 	if (mtlDevice.isDepth24Stencil8PixelFormatSupported) {
 		addMTLPixelFormatCapabilities( macOS_GPUFamily1_v1, Depth24Unorm_Stencil8, DRFMR );
 	}
@@ -1042,8 +1094,6 @@
 #endif
 #if MVK_IOS
 void MVKPixelFormats::modifyMTLFormatCapabilities(id<MTLDevice> mtlDevice) {
-	if ( !mtlDevice ) { return; }
-
 	addMTLPixelFormatCapabilities( iOS_GPUFamily2_v3, R8Unorm_sRGB, All );
 	addMTLPixelFormatCapabilities( iOS_GPUFamily3_v1, R8Unorm_sRGB, All );
 
@@ -1290,70 +1340,78 @@
 
 // Validate the functionality of this class against the previous format data within MoltenVK.
 // This is a temporary function to confirm that converting to using this class matches existing behaviour at first.
-void MVKPixelFormats::test(id<MTLDevice> mtlDevice) {
-	@autoreleasepool {
-		// Don't test a static instance, and only test the default MTLDevice if more than one GPU.
-		if ( !_apiObject || mtlDevice != [MTLCreateSystemDefaultDevice() autorelease] ) { return; }
+#define testFmt(V1, V2)	  testFmt(V1, V2, fd.name, #V1)
+#define testProps(V1, V2)  testProps(V1, V2, fd.name)
+void MVKPixelFormats::test() {
+	if ( !_physicalDevice ) { return; }		// Don't test a static instance not associated with a physical device
 
-		MVKLogInfo("Starting testing formats");
-		for (uint32_t fmtIdx = 0; fmtIdx < _vkFormatCount; fmtIdx++) {
-			auto& fd = _vkFormatDescriptions[fmtIdx];
-			VkFormat vkFmt = fd.vkFormat;
-			MTLPixelFormat mtlFmt = fd.mtlPixelFormat;
+	// If more than one GPU, only test the system default MTLDevice.
+	// Can release system MTLDevice immediates because we are just comparing it's address.
+	id<MTLDevice> sysMTLDvc = MTLCreateSystemDefaultDevice();		// temp retained
+	[sysMTLDvc release];											// release temp instance
+	if ( _physicalDevice->getMTLDevice() != sysMTLDvc ) { return; }
 
-			if (fd.vkFormat) {
-				if (fd.isSupportedOrSubstitutable()) {
-					MVKLogInfo("Testing %s", fd.name);
+	MVKLogInfo("Starting testing formats");
+	for (uint32_t fmtIdx = 0; fmtIdx < _vkFormatCount; fmtIdx++) {
+		auto& fd = _vkFormatDescriptions[fmtIdx];
+		VkFormat vkFmt = fd.vkFormat;
+		MTLPixelFormat mtlFmt = fd.mtlPixelFormat;
 
-#					define testFmt(V1, V2)	  testFmt(V1, V2, fd.name, #V1)
-#					define testProps(V1, V2)  testProps(V1, V2, fd.name)
+		if (fd.vkFormat) {
+			if (fd.isSupportedOrSubstitutable()) {
+				MVKLogInfo("Testing %s", fd.name);
 
-					testFmt(vkFormatIsSupported(vkFmt), mvkVkFormatIsSupported(vkFmt));
-					testFmt(mtlPixelFormatIsSupported(mtlFmt), mvkMTLPixelFormatIsSupported(mtlFmt));
-					testFmt(mtlPixelFormatIsDepthFormat(mtlFmt), mvkMTLPixelFormatIsDepthFormat(mtlFmt));
-					testFmt(mtlPixelFormatIsStencilFormat(mtlFmt), mvkMTLPixelFormatIsStencilFormat(mtlFmt));
-					testFmt(mtlPixelFormatIsPVRTCFormat(mtlFmt), mvkMTLPixelFormatIsPVRTCFormat(mtlFmt));
-					testFmt(getFormatTypeFromVkFormat(vkFmt), mvkFormatTypeFromVkFormat(vkFmt));
-					testFmt(getFormatTypeFromMTLPixelFormat(mtlFmt), mvkFormatTypeFromMTLPixelFormat(mtlFmt));
-					testFmt(getMTLPixelFormatFromVkFormat(vkFmt), mvkMTLPixelFormatFromVkFormat(vkFmt));
-					testFmt(getVkFormatFromMTLPixelFormat(mtlFmt), mvkVkFormatFromMTLPixelFormat(mtlFmt));
-					testFmt(getVkFormatBytesPerBlock(vkFmt), mvkVkFormatBytesPerBlock(vkFmt));
-					testFmt(getMTLPixelFormatBytesPerBlock(mtlFmt), mvkMTLPixelFormatBytesPerBlock(mtlFmt));
-					testFmt(getVkFormatBlockTexelSize(vkFmt), mvkVkFormatBlockTexelSize(vkFmt));
-					testFmt(getMTLPixelFormatBlockTexelSize(mtlFmt), mvkMTLPixelFormatBlockTexelSize(mtlFmt));
-					testFmt(getVkFormatBytesPerTexel(vkFmt), mvkVkFormatBytesPerTexel(vkFmt));
-					testFmt(getMTLPixelFormatBytesPerTexel(mtlFmt), mvkMTLPixelFormatBytesPerTexel(mtlFmt));
-					testFmt(getVkFormatBytesPerRow(vkFmt, 4), mvkVkFormatBytesPerRow(vkFmt, 4));
-					testFmt(getMTLPixelFormatBytesPerRow(mtlFmt, 4), mvkMTLPixelFormatBytesPerRow(mtlFmt, 4));
-					testFmt(getVkFormatBytesPerLayer(vkFmt, 256, 4), mvkVkFormatBytesPerLayer(vkFmt, 256, 4));
-					testFmt(getMTLPixelFormatBytesPerLayer(mtlFmt, 256, 4), mvkMTLPixelFormatBytesPerLayer(mtlFmt, 256, 4));
-					testProps(getVkFormatProperties(vkFmt), mvkVkFormatProperties(vkFmt));
-					testFmt(strcmp(getVkFormatName(vkFmt), mvkVkFormatName(vkFmt)), 0);
-					testFmt(strcmp(getMTLPixelFormatName(mtlFmt), mvkMTLPixelFormatName(mtlFmt)), 0);
-					testFmt(getMTLClearColorFromVkClearValue(VkClearValue(), vkFmt),
-							mvkMTLClearColorFromVkClearValue(VkClearValue(), vkFmt));
+				testFmt(vkFormatIsSupported(vkFmt), mvkVkFormatIsSupported(vkFmt));
+				testFmt(mtlPixelFormatIsSupported(mtlFmt), mvkMTLPixelFormatIsSupported(mtlFmt));
+				testFmt(mtlPixelFormatIsDepthFormat(mtlFmt), mvkMTLPixelFormatIsDepthFormat(mtlFmt));
+				testFmt(mtlPixelFormatIsStencilFormat(mtlFmt), mvkMTLPixelFormatIsStencilFormat(mtlFmt));
+				testFmt(mtlPixelFormatIsPVRTCFormat(mtlFmt), mvkMTLPixelFormatIsPVRTCFormat(mtlFmt));
+				testFmt(getFormatTypeFromVkFormat(vkFmt), mvkFormatTypeFromVkFormat(vkFmt));
+				testFmt(getFormatTypeFromMTLPixelFormat(mtlFmt), mvkFormatTypeFromMTLPixelFormat(mtlFmt));
+				testFmt(getMTLPixelFormatFromVkFormat(vkFmt), mvkMTLPixelFormatFromVkFormat(vkFmt));
+				testFmt(getVkFormatFromMTLPixelFormat(mtlFmt), mvkVkFormatFromMTLPixelFormat(mtlFmt));
+				testFmt(getVkFormatBytesPerBlock(vkFmt), mvkVkFormatBytesPerBlock(vkFmt));
+				testFmt(getMTLPixelFormatBytesPerBlock(mtlFmt), mvkMTLPixelFormatBytesPerBlock(mtlFmt));
+				testFmt(getVkFormatBlockTexelSize(vkFmt), mvkVkFormatBlockTexelSize(vkFmt));
+				testFmt(getMTLPixelFormatBlockTexelSize(mtlFmt), mvkMTLPixelFormatBlockTexelSize(mtlFmt));
+				testFmt(getVkFormatBytesPerTexel(vkFmt), mvkVkFormatBytesPerTexel(vkFmt));
+				testFmt(getMTLPixelFormatBytesPerTexel(mtlFmt), mvkMTLPixelFormatBytesPerTexel(mtlFmt));
+				testFmt(getVkFormatBytesPerRow(vkFmt, 4), mvkVkFormatBytesPerRow(vkFmt, 4));
+				testFmt(getMTLPixelFormatBytesPerRow(mtlFmt, 4), mvkMTLPixelFormatBytesPerRow(mtlFmt, 4));
+				testFmt(getVkFormatBytesPerLayer(vkFmt, 256, 4), mvkVkFormatBytesPerLayer(vkFmt, 256, 4));
+				testFmt(getMTLPixelFormatBytesPerLayer(mtlFmt, 256, 4), mvkMTLPixelFormatBytesPerLayer(mtlFmt, 256, 4));
+				testProps(getVkFormatProperties(vkFmt), mvkVkFormatProperties(vkFmt));
+				testFmt(strcmp(getVkFormatName(vkFmt), mvkVkFormatName(vkFmt)), 0);
+				testFmt(strcmp(getMTLPixelFormatName(mtlFmt), mvkMTLPixelFormatName(mtlFmt)), 0);
+				testFmt(getMTLClearColorFromVkClearValue(VkClearValue(), vkFmt),
+						mvkMTLClearColorFromVkClearValue(VkClearValue(), vkFmt));
 
-					testFmt(getVkImageUsageFlagsFromMTLTextureUsage(MTLTextureUsageUnknown, mtlFmt),
-							mvkVkImageUsageFlagsFromMTLTextureUsage(MTLTextureUsageUnknown, mtlFmt));
-					testFmt(getVkImageUsageFlagsFromMTLTextureUsage(MTLTextureUsageShaderRead, mtlFmt),
-							mvkVkImageUsageFlagsFromMTLTextureUsage(MTLTextureUsageShaderRead, mtlFmt));
-					testFmt(getVkImageUsageFlagsFromMTLTextureUsage(MTLTextureUsageShaderWrite, mtlFmt),
-							mvkVkImageUsageFlagsFromMTLTextureUsage(MTLTextureUsageShaderWrite, mtlFmt));
-					testFmt(getVkImageUsageFlagsFromMTLTextureUsage(MTLTextureUsageRenderTarget, mtlFmt),
-							mvkVkImageUsageFlagsFromMTLTextureUsage(MTLTextureUsageRenderTarget, mtlFmt));
-					testFmt(getVkImageUsageFlagsFromMTLTextureUsage(MTLTextureUsagePixelFormatView, mtlFmt),
-							mvkVkImageUsageFlagsFromMTLTextureUsage(MTLTextureUsagePixelFormatView, mtlFmt));
+				testFmt(getVkImageUsageFlagsFromMTLTextureUsage(MTLTextureUsageUnknown, mtlFmt),
+						mvkVkImageUsageFlagsFromMTLTextureUsage(MTLTextureUsageUnknown, mtlFmt));
+				testFmt(getVkImageUsageFlagsFromMTLTextureUsage(MTLTextureUsageShaderRead, mtlFmt),
+						mvkVkImageUsageFlagsFromMTLTextureUsage(MTLTextureUsageShaderRead, mtlFmt));
+				testFmt(getVkImageUsageFlagsFromMTLTextureUsage(MTLTextureUsageShaderWrite, mtlFmt),
+						mvkVkImageUsageFlagsFromMTLTextureUsage(MTLTextureUsageShaderWrite, mtlFmt));
+				testFmt(getVkImageUsageFlagsFromMTLTextureUsage(MTLTextureUsageRenderTarget, mtlFmt),
+						mvkVkImageUsageFlagsFromMTLTextureUsage(MTLTextureUsageRenderTarget, mtlFmt));
+				testFmt(getVkImageUsageFlagsFromMTLTextureUsage(MTLTextureUsagePixelFormatView, mtlFmt),
+						mvkVkImageUsageFlagsFromMTLTextureUsage(MTLTextureUsagePixelFormatView, mtlFmt));
 
-					testFmt(getMTLVertexFormatFromVkFormat(vkFmt), mvkMTLVertexFormatFromVkFormat(vkFmt));
+				VkImageUsageFlags vkUsage;
+				vkUsage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | 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;
+				testFmt(getMTLTextureUsageFromVkImageUsageFlags(vkUsage, mtlFmt), mvkMTLTextureUsageFromVkImageUsageFlags(vkUsage, mtlFmt));
 
-#					undef testFmt
-#					undef testProps
+				vkUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_STORAGE_BIT;
+				testFmt(getMTLTextureUsageFromVkImageUsageFlags(vkUsage, mtlFmt), mvkMTLTextureUsageFromVkImageUsageFlags(vkUsage, mtlFmt));
 
-				} else {
-					MVKLogInfo("%s not supported or substitutable on this device.", fd.name);
-				}
+				testFmt(getMTLVertexFormatFromVkFormat(vkFmt), mvkMTLVertexFormatFromVkFormat(vkFmt));
+
+			} else {
+				MVKLogInfo("%s not supported or substitutable on this device.", fd.name);
 			}
 		}
-		MVKLogInfo("Finished testing formats.\n");
 	}
+	MVKLogInfo("Finished testing formats.\n");
 }
+#undef testFmt
+#undef testProps
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm
index ecf02cd..7b920cf 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm
@@ -146,7 +146,7 @@
 			mtlTexDesc.arrayLength = framebuffer->getLayerCount();
 		}
 #if MVK_IOS
-		if ([_renderPass->getDevice()->getMTLDevice() supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily1_v3]) {
+		if ([_renderPass->getMTLDevice() supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily1_v3]) {
 			mtlTexDesc.storageMode = MTLStorageModeMemoryless;
 		} else {
 			mtlTexDesc.storageMode = MTLStorageModePrivate;
@@ -155,7 +155,7 @@
 		mtlTexDesc.storageMode = MTLStorageModePrivate;
 #endif
 		mtlTexDesc.usage = MTLTextureUsageRenderTarget;
-		_mtlDummyTex = [_renderPass->getDevice()->getMTLDevice() newTextureWithDescriptor: mtlTexDesc];  // not retained
+		_mtlDummyTex = [_renderPass->getMTLDevice() newTextureWithDescriptor: mtlTexDesc];  // not retained
 		[_mtlDummyTex setPurgeableState: MTLPurgeableStateVolatile];
 		MTLRenderPassColorAttachmentDescriptor* mtlColorAttDesc = mtlRPDesc.colorAttachments[0];
 		mtlColorAttDesc.texture = _mtlDummyTex;
diff --git a/MoltenVK/MoltenVK/Layers/MVKExtensions.mm b/MoltenVK/MoltenVK/Layers/MVKExtensions.mm
index 897230d..4d15962 100644
--- a/MoltenVK/MoltenVK/Layers/MVKExtensions.mm
+++ b/MoltenVK/MoltenVK/Layers/MVKExtensions.mm
@@ -48,25 +48,25 @@
 static bool mvkIsSupportedOnPlatform(VkExtensionProperties* pProperties) {
 #if MVK_MACOS
 	if (pProperties == &kVkExtProps_EXT_HDR_METADATA) {
-		return mvkOSVersion() >= 10.15;
+		return mvkOSVersionIsAtLeast(10.15);
 	}
 	if (pProperties == &kVkExtProps_EXT_FRAGMENT_SHADER_INTERLOCK) {
-		return mvkOSVersion() >= 10.13;
+		return mvkOSVersionIsAtLeast(10.13);
 	}
 	if (pProperties == &kVkExtProps_EXT_MEMORY_BUDGET) {
-		return mvkOSVersion() >= 10.13;
+		return mvkOSVersionIsAtLeast(10.13);
 	}
 	if (pProperties == &kVkExtProps_EXT_POST_DEPTH_COVERAGE) { return false; }
 	if (pProperties == &kVkExtProps_EXT_SHADER_STENCIL_EXPORT) {
-		return mvkOSVersion() >= 10.14;
+		return mvkOSVersionIsAtLeast(10.14);
 	}
 	if (pProperties == &kVkExtProps_EXT_TEXEL_BUFFER_ALIGNMENT) {
-		return mvkOSVersion() >= 10.13;
+		return mvkOSVersionIsAtLeast(10.13);
 	}
 	if (pProperties == &kVkExtProps_MVK_IOS_SURFACE) { return false; }
 	if (pProperties == &kVkExtProps_AMD_SHADER_IMAGE_LOAD_STORE_LOD) { return false; }
 	if (pProperties == &kVkExtProps_AMD_SHADER_TRINARY_MINMAX) {
-		return mvkOSVersion() >= 10.14;
+		return mvkOSVersionIsAtLeast(10.14);
 	}
 	if (pProperties == &kVkExtProps_IMG_FORMAT_PVRTC) { return false; }
 #endif
@@ -74,26 +74,26 @@
 	if (pProperties == &kVkExtProps_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE) { return false; }
 	if (pProperties == &kVkExtProps_EXT_HDR_METADATA) { return false; }
 	if (pProperties == &kVkExtProps_EXT_FRAGMENT_SHADER_INTERLOCK) {
-		return mvkOSVersion() >= 11.0;
+		return mvkOSVersionIsAtLeast(11.0);
 	}
 	if (pProperties == &kVkExtProps_EXT_MEMORY_BUDGET) {
-		return mvkOSVersion() >= 11.0;
+		return mvkOSVersionIsAtLeast(11.0);
 	}
 	if (pProperties == &kVkExtProps_EXT_POST_DEPTH_COVERAGE) {
-		return mvkOSVersion() >= 11.0;
+		return mvkOSVersionIsAtLeast(11.0);
 	}
 	if (pProperties == &kVkExtProps_EXT_SHADER_STENCIL_EXPORT) {
-		return mvkOSVersion() >= 12.0;
+		return mvkOSVersionIsAtLeast(12.0);
 	}
 	if (pProperties == &kVkExtProps_EXT_SWAPCHAIN_COLOR_SPACE) {
-		return mvkOSVersion() >= 9.0;
+		return mvkOSVersionIsAtLeast(9.0);
 	}
 	if (pProperties == &kVkExtProps_EXT_TEXEL_BUFFER_ALIGNMENT) {
-		return mvkOSVersion() >= 11.0;
+		return mvkOSVersionIsAtLeast(11.0);
 	}
 	if (pProperties == &kVkExtProps_MVK_MACOS_SURFACE) { return false; }
 	if (pProperties == &kVkExtProps_AMD_SHADER_TRINARY_MINMAX) {
-		return mvkOSVersion() >= 12.0;
+		return mvkOSVersionIsAtLeast(12.0);
 	}
 #endif
 
diff --git a/MoltenVK/MoltenVK/OS/MVKGPUCapture.mm b/MoltenVK/MoltenVK/OS/MVKGPUCapture.mm
index 1e548e9..8954556 100644
--- a/MoltenVK/MoltenVK/OS/MVKGPUCapture.mm
+++ b/MoltenVK/MoltenVK/OS/MVKGPUCapture.mm
@@ -58,7 +58,7 @@
 
 MVKGPUCaptureScope::MVKGPUCaptureScope(MVKQueue* mvkQueue, const char* purpose) : _queue(mvkQueue) {
 	_mtlQueue = [_queue->getMTLCommandQueue() retain];	// retained
-	if (mvkOSVersion() >= kMinOSVersionMTLCaptureScope) {
+	if (mvkOSVersionIsAtLeast(kMinOSVersionMTLCaptureScope)) {
 		NSString* nsQLbl = [[NSString alloc] initWithUTF8String: (_queue->getName() + "-" + purpose).c_str()];		// temp retained
 		_mtlCaptureScope = [[MTLCaptureManager sharedCaptureManager] newCaptureScopeWithCommandQueue: _mtlQueue];	// retained
 		_mtlCaptureScope.label = nsQLbl;
diff --git a/MoltenVK/MoltenVK/Utility/MVKEnvironment.h b/MoltenVK/MoltenVK/Utility/MVKEnvironment.h
index 755f4be..2a9ca9f 100644
--- a/MoltenVK/MoltenVK/Utility/MVKEnvironment.h
+++ b/MoltenVK/MoltenVK/Utility/MVKEnvironment.h
@@ -72,7 +72,7 @@
 #   define MVK_MTLEVENT_MIN_OS  12.0
 #endif
 #ifndef MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS
-#   define MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS    (mvkOSVersion() >= MVK_MTLEVENT_MIN_OS)
+#   define MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS    mvkOSVersionIsAtLeast(MVK_MTLEVENT_MIN_OS)
 #endif
 
 /** Fill a Metal command buffers when each Vulkan command buffer is filled. */
diff --git a/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm
index 9b4901f..b9e52c5 100644
--- a/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm
+++ b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm
@@ -191,40 +191,8 @@
 	}
 }
 
-MVK_PUBLIC_SYMBOL MTLTextureUsage mvkMTLTextureUsageFromVkImageUsageFlags(VkImageUsageFlags vkImageUsageFlags) {
-    MTLTextureUsage mtlUsage = MTLTextureUsageUnknown;
-
-	// Read from...
-	if (mvkIsAnyFlagEnabled(vkImageUsageFlags, (VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
-												VK_IMAGE_USAGE_SAMPLED_BIT |
-												VK_IMAGE_USAGE_STORAGE_BIT |
-												VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT))) {
-		mvkEnableFlags(mtlUsage, MTLTextureUsageShaderRead);
-	}
-
-	// Write to...
-	if (mvkIsAnyFlagEnabled(vkImageUsageFlags, (VK_IMAGE_USAGE_STORAGE_BIT))) {
-		mvkEnableFlags(mtlUsage, MTLTextureUsageShaderWrite);
-	}
-
-	// Render to...
-	if (mvkIsAnyFlagEnabled(vkImageUsageFlags, (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
-												VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT |
-												VK_IMAGE_USAGE_TRANSFER_DST_BIT))) {				// Scaling a BLIT may use rendering.
-		mvkEnableFlags(mtlUsage, MTLTextureUsageRenderTarget);
-	}
-
-	// Create view on...
-	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))) {	// D/S may be filtered out after device check
-		mvkEnableFlags(mtlUsage, MTLTextureUsagePixelFormatView);
-	}
-
-    return mtlUsage;
+MVK_PUBLIC_SYMBOL MTLTextureUsage mvkMTLTextureUsageFromVkImageUsageFlags(VkImageUsageFlags vkImageUsageFlags, MTLPixelFormat mtlPixFmt) {
+	return _platformPixelFormats.getMTLTextureUsageFromVkImageUsageFlags(vkImageUsageFlags, mtlPixFmt);
 }
 
 MVK_PUBLIC_SYMBOL VkImageUsageFlags mvkVkImageUsageFlagsFromMTLTextureUsage(MTLTextureUsage mtlUsage, MTLPixelFormat mtlFormat) {