Merge branch 'master' of https://github.com/KhronosGroup/MoltenVK
diff --git a/Docs/MoltenVK_Runtime_UserGuide.md b/Docs/MoltenVK_Runtime_UserGuide.md
index a89e672..ab4c480 100644
--- a/Docs/MoltenVK_Runtime_UserGuide.md
+++ b/Docs/MoltenVK_Runtime_UserGuide.md
@@ -253,11 +253,13 @@
 - `VK_EXT_debug_report`
 - `VK_EXT_debug_utils`
 - `VK_EXT_host_query_reset`
-- `VK_EXT_memory_budget`
+- `VK_EXT_memory_budget` *(requires Metal 2.0)*
 - `VK_EXT_metal_surface`
 - `VK_EXT_shader_stencil_export` *(requires Mac GPU family 2 or iOS GPU family 5)*
 - `VK_EXT_shader_viewport_index_layer`
+- `VK_EXT_swapchain_colorspace` *(macOS)*
 - `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.)*
diff --git a/ExternalRevisions/Vulkan-Headers_repo_revision b/ExternalRevisions/Vulkan-Headers_repo_revision
index 6dd277b..eb32d0f 100644
--- a/ExternalRevisions/Vulkan-Headers_repo_revision
+++ b/ExternalRevisions/Vulkan-Headers_repo_revision
@@ -1 +1 @@
-097a1045098213919fd56442f52c716fc78eeb27
+737f4c1cd96283747967c2024a0108b742214455
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.mm b/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.mm
index e60dab4..ee23a31 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.mm
@@ -223,6 +223,7 @@
 		// We can just use a simple 1D texel array.
 		_textureSize.width = uint32_t(blockCount * fmtBlockSize.width);
 		_textureSize.height = 1;
+		_mtlBytesPerRow = byteCount;
 	}
 
     if ( !_device->_pMetalFeatures->texelBuffers ) {
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
index 233d360..e22b724 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
@@ -336,6 +336,7 @@
 	VkPhysicalDeviceFeatures _features;
 	MVKPhysicalDeviceMetalFeatures _metalFeatures;
 	VkPhysicalDeviceProperties _properties;
+	VkPhysicalDeviceTexelBufferAlignmentPropertiesEXT _texelBuffAlignProperties;
 	VkPhysicalDeviceMemoryProperties _memoryProperties;
 	std::vector<MVKQueueFamily*> _queueFamilies;
 	uint32_t _allMemoryTypes;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
index 9bc202d..3f3d823 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
@@ -69,8 +69,7 @@
     if (features) {
         features->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
         features->features = _features;
-        auto* next = (VkBaseOutStructure*)features->pNext;
-        while (next) {
+        for (auto* next = (VkBaseOutStructure*)features->pNext; next; next = next->pNext) {
             switch ((uint32_t)next->sType) {
                 case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES: {
                     auto* storageFeatures = (VkPhysicalDevice16BitStorageFeatures*)next;
@@ -109,6 +108,11 @@
                     hostQueryResetFeatures->hostQueryReset = true;
                     break;
                 }
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXEL_BUFFER_ALIGNMENT_FEATURES_EXT: {
+                    auto* texelBuffAlignFeatures = (VkPhysicalDeviceTexelBufferAlignmentFeaturesEXT*)next;
+                    texelBuffAlignFeatures->texelBufferAlignment = _metalFeatures.texelBuffers && [_mtlDevice respondsToSelector: @selector(minimumLinearTextureAlignmentForPixelFormat:)];
+                    break;
+                }
                 case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_FEATURES_EXT: {
                     auto* divisorFeatures = (VkPhysicalDeviceVertexAttributeDivisorFeaturesEXT*)next;
                     divisorFeatures->vertexAttributeInstanceRateDivisor = true;
@@ -127,7 +131,6 @@
                 default:
                     break;
             }
-            next = next->pNext;
         }
     }
 }
@@ -140,42 +143,44 @@
     if (properties) {
         properties->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
         properties->properties = _properties;
-		auto* next = (MVKVkAPIStructHeader*)properties->pNext;
-		while (next) {
+		for (auto* next = (VkBaseOutStructure*)properties->pNext; next; next = next->pNext) {
 			switch ((uint32_t)next->sType) {
             case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_POINT_CLIPPING_PROPERTIES: {
                 auto* pointClipProps = (VkPhysicalDevicePointClippingProperties*)next;
                 pointClipProps->pointClippingBehavior = VK_POINT_CLIPPING_BEHAVIOR_ALL_CLIP_PLANES;
-                next = (MVKVkAPIStructHeader*)pointClipProps->pNext;
                 break;
             }
             case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_3_PROPERTIES: {
                 auto* maint3Props = (VkPhysicalDeviceMaintenance3Properties*)next;
-                maint3Props->maxPerSetDescriptors = (_metalFeatures.maxPerStageBufferCount + _metalFeatures.maxPerStageTextureCount + _metalFeatures.maxPerStageSamplerCount) * 2;
+                maint3Props->maxPerSetDescriptors = (_metalFeatures.maxPerStageBufferCount + _metalFeatures.maxPerStageTextureCount + _metalFeatures.maxPerStageSamplerCount) * 4;
                 maint3Props->maxMemoryAllocationSize = _metalFeatures.maxMTLBufferSize;
-                next = (MVKVkAPIStructHeader*)maint3Props->pNext;
                 break;
             }
             case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR: {
                 auto* pushDescProps = (VkPhysicalDevicePushDescriptorPropertiesKHR*)next;
                 pushDescProps->maxPushDescriptors = _properties.limits.maxPerStageResources;
-                next = (MVKVkAPIStructHeader*)pushDescProps->pNext;
+                break;
+            }
+            case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXEL_BUFFER_ALIGNMENT_PROPERTIES_EXT: {
+                auto* texelBuffAlignProps = (VkPhysicalDeviceTexelBufferAlignmentPropertiesEXT*)next;
+                // Save the 'next' pointer; we'll unintentionally overwrite it
+                // on the next line. Put it back when we're done.
+                void* savedNext = texelBuffAlignProps->pNext;
+                *texelBuffAlignProps = _texelBuffAlignProperties;
+                texelBuffAlignProps->pNext = savedNext;
                 break;
             }
             case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_PROPERTIES_EXT: {
                 auto* divisorProps = (VkPhysicalDeviceVertexAttributeDivisorPropertiesEXT*)next;
                 divisorProps->maxVertexAttribDivisor = kMVKUndefinedLargeUInt32;
-                next = (MVKVkAPIStructHeader*)divisorProps->pNext;
                 break;
             }
 			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_PROPERTIES_EXTX: {
 				auto* portabilityProps = (VkPhysicalDevicePortabilitySubsetPropertiesEXTX*)next;
 				portabilityProps->minVertexInputBindingStrideAlignment = 4;
-				next = (MVKVkAPIStructHeader*)portabilityProps->pNext;
 				break;
 			}
             default:
-                next = (MVKVkAPIStructHeader*)next->pNext;
                 break;
             }
         }
@@ -466,22 +471,42 @@
 		MTLPixelFormatRGBA16Float,
 	};
 
+	MVKVectorInline<VkColorSpaceKHR, 16> colorSpaces;
+	colorSpaces.push_back(VK_COLOR_SPACE_SRGB_NONLINEAR_KHR);
+#if MVK_MACOS
+	if (getInstance()->_enabledExtensions.vk_EXT_swapchain_colorspace.enabled) {
+		// 10.11 supports some but not all of the color spaces specified by VK_EXT_swapchain_colorspace.
+		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.12) {
+			colorSpaces.push_back(VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT);
+			colorSpaces.push_back(VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT);
+		}
+	}
+#endif
+
 	const uint mtlFmtsCnt = sizeof(mtlFormats) / sizeof(MTLPixelFormat);
+	const uint vkFmtsCnt = mtlFmtsCnt * (uint)colorSpaces.size();
 
 	// If properties aren't actually being requested yet, simply update the returned count
 	if ( !pSurfaceFormats ) {
-		*pCount = mtlFmtsCnt;
+		*pCount = vkFmtsCnt;
 		return VK_SUCCESS;
 	}
 
 	// Determine how many results we'll return, and return that number
-	VkResult result = (*pCount >= mtlFmtsCnt) ? VK_SUCCESS : VK_INCOMPLETE;
-	*pCount = min(*pCount, mtlFmtsCnt);
+	VkResult result = (*pCount >= vkFmtsCnt) ? VK_SUCCESS : VK_INCOMPLETE;
+	*pCount = min(*pCount, vkFmtsCnt);
 
 	// Now populate the supplied array
-	for (uint fmtIdx = 0; fmtIdx < *pCount; fmtIdx++) {
-		pSurfaceFormats[fmtIdx].format = mvkVkFormatFromMTLPixelFormat(mtlFormats[fmtIdx]);
-		pSurfaceFormats[fmtIdx].colorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR;
+	for (uint csIdx = 0, idx = 0; idx < *pCount && csIdx < colorSpaces.size(); csIdx++) {
+		for (uint fmtIdx = 0; idx < *pCount && fmtIdx < mtlFmtsCnt; fmtIdx++, idx++) {
+			pSurfaceFormats[idx].format = mvkVkFormatFromMTLPixelFormat(mtlFormats[fmtIdx]);
+			pSurfaceFormats[idx].colorSpace = colorSpaces[csIdx];
+		}
 	}
 
 	return result;
@@ -1093,14 +1118,86 @@
     _properties.limits.bufferImageGranularity = _metalFeatures.mtlBufferAlignment;
     _properties.limits.nonCoherentAtomSize = _metalFeatures.mtlBufferAlignment;
 
+    if ([_mtlDevice respondsToSelector: @selector(minimumLinearTextureAlignmentForPixelFormat:)]) {
+        // Figure out the greatest alignment required by all supported formats, and
+        // whether or not they only require alignment to a single texel. We'll use this
+        // information to fill out the VkPhysicalDeviceTexelBufferAlignmentPropertiesEXT
+        // struct.
+        uint32_t maxStorage = 0, maxUniform = 0;
+        bool singleTexelStorage = true, singleTexelUniform = true;
+        mvkEnumerateSupportedFormats({0, 0, VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT}, true, [&](VkFormat vk) {
+            NSUInteger alignment;
+            if ([_mtlDevice respondsToSelector: @selector(minimumTextureBufferAlignmentForPixelFormat:)]) {
+                alignment = [_mtlDevice minimumTextureBufferAlignmentForPixelFormat: mvkMTLPixelFormatFromVkFormat(vk)];
+            } else {
+                alignment = [_mtlDevice minimumLinearTextureAlignmentForPixelFormat: mvkMTLPixelFormatFromVkFormat(vk)];
+            }
+            VkFormatProperties props = mvkVkFormatProperties(vk, getFormatIsSupported(vk));
+            // For uncompressed formats, this is the size of a single texel.
+            // Note that no implementations of Metal support compressed formats
+            // in a linear texture (including texture buffers). It's likely that even
+            // if they did, this would be the absolute minimum alignment.
+            uint32_t texelSize = mvkVkFormatBytesPerBlock(vk);
+            // From the spec:
+            //   "If the size of a single texel is a multiple of three bytes, then
+            //    the size of a single component of the format is used instead."
+            if (texelSize % 3 == 0) {
+                switch (mvkFormatTypeFromVkFormat(vk)) {
+                case kMVKFormatColorInt8:
+                case kMVKFormatColorUInt8:
+                    texelSize = 1;
+                    break;
+                case kMVKFormatColorHalf:
+                case kMVKFormatColorInt16:
+                case kMVKFormatColorUInt16:
+                    texelSize = 2;
+                    break;
+                case kMVKFormatColorFloat:
+                case kMVKFormatColorInt32:
+                case kMVKFormatColorUInt32:
+                default:
+                    texelSize = 4;
+                    break;
+                }
+            }
+            if (mvkAreAllFlagsEnabled(props.bufferFeatures, VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT)) {
+                maxStorage = max(maxStorage, uint32_t(alignment));
+                if (alignment % texelSize != 0) { singleTexelStorage = false; }
+            }
+            if (mvkAreAllFlagsEnabled(props.bufferFeatures, VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT)) {
+                maxUniform = max(maxUniform, uint32_t(alignment));
+                if (alignment % texelSize != 0) { singleTexelUniform = false; }
+            }
+            return true;
+        });
+        _texelBuffAlignProperties.storageTexelBufferOffsetAlignmentBytes = maxStorage;
+        _texelBuffAlignProperties.storageTexelBufferOffsetSingleTexelAlignment = singleTexelStorage;
+        _texelBuffAlignProperties.uniformTexelBufferOffsetAlignmentBytes = maxUniform;
+        _texelBuffAlignProperties.uniformTexelBufferOffsetSingleTexelAlignment = singleTexelUniform;
+        _properties.limits.minTexelBufferOffsetAlignment = max(maxStorage, maxUniform);
+    } else {
+#if MVK_IOS
+        if ([_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily3_v1]) {
+            _properties.limits.minTexelBufferOffsetAlignment = 16;
+        } else {
+            _properties.limits.minTexelBufferOffsetAlignment = 64;
+        }
+#endif
+#if MVK_MACOS
+        _properties.limits.minTexelBufferOffsetAlignment = 256;
+#endif
+        _texelBuffAlignProperties.storageTexelBufferOffsetAlignmentBytes = _properties.limits.minTexelBufferOffsetAlignment;
+        _texelBuffAlignProperties.storageTexelBufferOffsetSingleTexelAlignment = VK_FALSE;
+        _texelBuffAlignProperties.uniformTexelBufferOffsetAlignmentBytes = _properties.limits.minTexelBufferOffsetAlignment;
+        _texelBuffAlignProperties.uniformTexelBufferOffsetSingleTexelAlignment = VK_FALSE;
+    }
+
 #if MVK_IOS
     _properties.limits.maxFragmentInputComponents = 60;
 
     if ([_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily3_v1]) {
-        _properties.limits.minTexelBufferOffsetAlignment = 16;
         _properties.limits.optimalBufferCopyOffsetAlignment = 16;
     } else {
-        _properties.limits.minTexelBufferOffsetAlignment = 64;
         _properties.limits.optimalBufferCopyOffsetAlignment = 64;
     }
 
@@ -1117,7 +1214,6 @@
 #endif
 #if MVK_MACOS
     _properties.limits.maxFragmentInputComponents = 128;
-    _properties.limits.minTexelBufferOffsetAlignment = 256;
     _properties.limits.optimalBufferCopyOffsetAlignment = 256;
 
     if ([_mtlDevice supportsFeatureSet: MTLFeatureSet_macOS_GPUFamily1_v2]) {
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
index c45c8a6..ac3eabc 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
@@ -226,12 +226,40 @@
 																			   VK_IMAGE_USAGE_TRANSFER_DST_BIT |
 																			   VK_IMAGE_USAGE_SAMPLED_BIT |
 																			   VK_IMAGE_USAGE_STORAGE_BIT));
+#if MVK_MACOS
+	switch (pCreateInfo->imageColorSpace) {
+		case VK_COLOR_SPACE_SRGB_NONLINEAR_KHR:
+			_mtlLayer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
+			break;
+		case VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT:
+			_mtlLayer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceDisplayP3);
+			break;
+		case VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT:
+			_mtlLayer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearSRGB);
+			break;
+		case VK_COLOR_SPACE_DCI_P3_NONLINEAR_EXT:
+			_mtlLayer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceDCIP3);
+			break;
+		case VK_COLOR_SPACE_BT709_NONLINEAR_EXT:
+			_mtlLayer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceITUR_709);
+			break;
+		case VK_COLOR_SPACE_ADOBERGB_NONLINEAR_EXT:
+			_mtlLayer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceAdobeRGB1998);
+			break;
+		case VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT:
+			_mtlLayer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedSRGB);
+			break;
+		case VK_COLOR_SPACE_PASS_THROUGH_EXT:
+		default:
+			// Nothing - the default is not to do color matching.
+			break;
+	}
+#endif
 	_mtlLayerOrigDrawSize = _mtlLayer.updatedDrawableSizeMVK;
 
 	// TODO: set additional CAMetalLayer properties before extracting drawables:
 	//	- presentsWithTransaction
 	//	- drawsAsynchronously
-	//  - colorspace (macOS only) Vulkan only supports sRGB colorspace for now.
 	//  - wantsExtendedDynamicRangeContent (macOS only)
 
 	if ( [_mtlLayer.delegate isKindOfClass: [PLATFORM_VIEW_CLASS class]] ) {
diff --git a/MoltenVK/MoltenVK/Layers/MVKExtensions.def b/MoltenVK/MoltenVK/Layers/MVKExtensions.def
index 5235f43..b7bf198 100644
--- a/MoltenVK/MoltenVK/Layers/MVKExtensions.def
+++ b/MoltenVK/MoltenVK/Layers/MVKExtensions.def
@@ -61,6 +61,8 @@
 MVK_EXTENSION(EXT_metal_surface, EXT_METAL_SURFACE)
 MVK_EXTENSION(EXT_shader_stencil_export, EXT_SHADER_STENCIL_EXPORT)
 MVK_EXTENSION(EXT_shader_viewport_index_layer, EXT_SHADER_VIEWPORT_INDEX_LAYER)
+MVK_EXTENSION(EXT_swapchain_colorspace, EXT_SWAPCHAIN_COLOR_SPACE)
+MVK_EXTENSION(EXT_texel_buffer_alignment, EXT_TEXEL_BUFFER_ALIGNMENT)
 MVK_EXTENSION(EXT_vertex_attribute_divisor, EXT_VERTEX_ATTRIBUTE_DIVISOR)
 MVK_EXTENSION(EXTX_portability_subset, EXTX_PORTABILITY_SUBSET)
 MVK_EXTENSION(MVK_ios_surface, MVK_IOS_SURFACE)
@@ -68,6 +70,7 @@
 MVK_EXTENSION(MVK_moltenvk, MVK_MOLTENVK)
 MVK_EXTENSION(AMD_gpu_shader_half_float, AMD_GPU_SHADER_HALF_FLOAT)
 MVK_EXTENSION(AMD_negative_viewport_height, AMD_NEGATIVE_VIEWPORT_HEIGHT)
+MVK_EXTENSION(AMD_shader_image_load_store_lod, AMD_SHADER_IMAGE_LOAD_STORE_LOD)
 MVK_EXTENSION(IMG_format_pvrtc, IMG_FORMAT_PVRTC)
 MVK_EXTENSION_LAST(NV_glsl_shader, NV_GLSL_SHADER)
 
diff --git a/MoltenVK/MoltenVK/Layers/MVKExtensions.mm b/MoltenVK/MoltenVK/Layers/MVKExtensions.mm
index e1580dc..1ba1a70 100644
--- a/MoltenVK/MoltenVK/Layers/MVKExtensions.mm
+++ b/MoltenVK/MoltenVK/Layers/MVKExtensions.mm
@@ -46,17 +46,21 @@
 
 // Returns whether the specified properties are valid for this platform
 static bool mvkIsSupportedOnPlatform(VkExtensionProperties* pProperties) {
-#if !(MVK_IOS)
+#if MVK_MACOS
 	if (pProperties == &kVkExtProps_EXT_MEMORY_BUDGET) {
 		return mvkOSVersion() >= 10.13;
 	}
 	if (pProperties == &kVkExtProps_EXT_SHADER_STENCIL_EXPORT) {
 		return mvkOSVersion() >= 10.14;
 	}
+	if (pProperties == &kVkExtProps_EXT_TEXEL_BUFFER_ALIGNMENT) {
+		return mvkOSVersion() >= 10.13;
+	}
 	if (pProperties == &kVkExtProps_MVK_IOS_SURFACE) { return false; }
+	if (pProperties == &kVkExtProps_AMD_SHADER_IMAGE_LOAD_STORE_LOD) { return false; }
 	if (pProperties == &kVkExtProps_IMG_FORMAT_PVRTC) { return false; }
 #endif
-#if !(MVK_MACOS)
+#if MVK_IOS
 	if (pProperties == &kVkExtProps_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE) { return false; }
 	if (pProperties == &kVkExtProps_EXT_MEMORY_BUDGET) {
 		return mvkOSVersion() >= 11.0;
@@ -64,6 +68,10 @@
 	if (pProperties == &kVkExtProps_EXT_SHADER_STENCIL_EXPORT) {
 		return mvkOSVersion() >= 12.0;
 	}
+	if (pProperties == &kVkExtProps_EXT_SWAPCHAIN_COLOR_SPACE) { return false; }
+	if (pProperties == &kVkExtProps_EXT_TEXEL_BUFFER_ALIGNMENT) {
+		return mvkOSVersion() >= 11.0;
+	}
 	if (pProperties == &kVkExtProps_MVK_MACOS_SURFACE) { return false; }
 #endif
 
diff --git a/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.hpp b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.hpp
index 6cc918f..62eeae7 100644
--- a/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.hpp
+++ b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.hpp
@@ -22,6 +22,8 @@
 
 #include "mvk_datatypes.h"
 
+#include <functional>
+
 class MVKBaseObject;
 
 /*
@@ -75,4 +77,13 @@
 MTLTessellationPartitionMode mvkMTLTessellationPartitionModeFromSpvExecutionModeInObj(uint32_t spvMode, MVKBaseObject* mvkObj);
 #define mvkMTLTessellationPartitionModeFromSpvExecutionMode(spvMode) mvkMTLTessellationPartitionModeFromSpvExecutionModeInObj(spvMode, this)
 
+
+#pragma mark -
+#pragma mark Image properties
+
+#pragma mark Texture formats
+
+/** Enumerates all formats that support the given features, calling a specified function for each one. */
+void mvkEnumerateSupportedFormats(VkFormatProperties properties, bool any, std::function<bool(VkFormat)> func);
+
 #endif
diff --git a/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm
index b58a603..b69f09a 100644
--- a/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm
+++ b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm
@@ -665,6 +665,25 @@
     return formatDescForMTLPixelFormat(mtlFormat).mtlName;
 }
 
+void mvkEnumerateSupportedFormats(VkFormatProperties properties, bool any, std::function<bool(VkFormat)> func) {
+    static const auto areFeaturesSupported = [any](uint32_t a, uint32_t b) {
+        if (any)
+            return mvkIsAnyFlagEnabled(a, b);
+        else
+            return mvkAreAllFlagsEnabled(a, b);
+    };
+    for (auto& formatDesc : _formatDescriptions) {
+        if (formatDesc.isSupported() &&
+            areFeaturesSupported(formatDesc.properties.linearTilingFeatures, properties.linearTilingFeatures) &&
+            areFeaturesSupported(formatDesc.properties.optimalTilingFeatures, properties.optimalTilingFeatures) &&
+            areFeaturesSupported(formatDesc.properties.bufferFeatures, properties.bufferFeatures)) {
+            if (!func(formatDesc.vk)) {
+                break;
+            }
+        }
+    }
+}
+
 #undef mvkMTLVertexFormatFromVkFormat
 MVK_PUBLIC_SYMBOL MTLVertexFormat mvkMTLVertexFormatFromVkFormat(VkFormat vkFormat) {
 	return mvkMTLVertexFormatFromVkFormatInObj(vkFormat, nullptr);