Merge pull request #1715 from cdavis5e/tess-unwritten-builtin-read

MVKPipeline: Add builtins that are read but not written to tessellation pipelines.
diff --git a/Docs/MoltenVK_Runtime_UserGuide.md b/Docs/MoltenVK_Runtime_UserGuide.md
index a8433a4..2dbb67d 100644
--- a/Docs/MoltenVK_Runtime_UserGuide.md
+++ b/Docs/MoltenVK_Runtime_UserGuide.md
@@ -303,7 +303,9 @@
 - `VK_EXT_debug_marker`
 - `VK_EXT_debug_report`
 - `VK_EXT_debug_utils`
-- `VK_EXT_descriptor_indexing` *(initial release limited to Metal Tier 1: 96/128 textures, 16 samplers)*
+- `VK_EXT_descriptor_indexing` *(initial release limited to Metal Tier 1: 96/128 textures, 
+  16 samplers, except macOS 11.0 (Big Sur) or later, or on older versions of macOS using 
+  an Intel GPU, and if Metal argument buffers enabled in config)*
 - `VK_EXT_fragment_shader_interlock` *(requires Metal 2.0 and Raster Order Groups)*
 - `VK_EXT_host_query_reset`
 - `VK_EXT_image_robustness`
diff --git a/Docs/Whats_New.md b/Docs/Whats_New.md
index f68de67..1edf4a1 100644
--- a/Docs/Whats_New.md
+++ b/Docs/Whats_New.md
@@ -20,8 +20,16 @@
 
 - Add support for extensions:
 	- `VK_KHR_shader_float_controls`
+- Vulkan semaphore functional improvements:
+	- Replace use of `MTLFence` with an option to limit to a single Vulkan queue and use Metal's implicit submisison order guarantees.
+	- Support option to force use of `MTLEvents` for Vulkan semaphores on NVIDIA and Rosetta2.
+	- `MVKConfiguration` replace booleans `semaphoreUseMTLEvent` and `semaphoreUseMTLFence` with enumerated `semaphoreSupportStyle`.
+- Support config option to automatically use Metal argument buffers when `VK_EXT_descriptor_indexing` 
+  extension is enabled. `MVKConfiguration::useMetalArgumentBuffers` (`MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS`) 
+  is now an enum field. The use of Metal argument buffers is still disabled by default (`MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS_NEVER`).
 - Fix occassional crash from retention of `MVKSwapchain` for future drawable presentations.
 - Add `MVK_USE_CEREAL` build setting to avoid use of Cereal external library (for pipeline caching).
+- Update `VK_MVK_MOLTENVK_SPEC_VERSION` to version `36`.
 
 
 
diff --git a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
index 080bb02..683a435 100644
--- a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
+++ b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
@@ -56,7 +56,7 @@
 #define MVK_MAKE_VERSION(major, minor, patch)    (((major) * 10000) + ((minor) * 100) + (patch))
 #define MVK_VERSION     MVK_MAKE_VERSION(MVK_VERSION_MAJOR, MVK_VERSION_MINOR, MVK_VERSION_PATCH)
 
-#define VK_MVK_MOLTENVK_SPEC_VERSION            35
+#define VK_MVK_MOLTENVK_SPEC_VERSION            36
 #define VK_MVK_MOLTENVK_EXTENSION_NAME          "VK_MVK_moltenvk"
 
 /** Identifies the level of logging MoltenVK should be limited to outputting. */
@@ -96,6 +96,23 @@
 } MVKConfigAdvertiseExtensionBits;
 typedef VkFlags MVKConfigAdvertiseExtensions;
 
+/** Identifies the use of Metal Argument Buffers. */
+typedef enum MVKUseMetalArgumentBuffers {
+	MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS_NEVER               = 0,	/**< Don't use Metal Argument Buffers. */
+	MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS_ALWAYS              = 1,	/**< Use Metal Argument Buffers for all pipelines. */
+	MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS_DESCRIPTOR_INDEXING = 2,	/**< Use Metal Argument Buffers only if VK_EXT_descriptor_indexing extension is enabled. */
+	MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS_MAX_ENUM            = 0x7FFFFFFF
+} MVKUseMetalArgumentBuffers;
+
+/** Identifies the Metal functionality used to support Vulkan semaphore functionality (VkSemaphore). */
+typedef enum MVKVkSemaphoreSupportStyle {
+	MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_CALLBACK                = 0,	/**< Use CPU callbacks upon GPU submission completion. This is the slowest technique. */
+	MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_METAL_EVENTS_WHERE_SAFE = 1,	/**< Use Metal events (MTLEvent) when available on the platform, and where safe. This will revert to same as MVK_CONFIG_VK_SEMAPHORE_USE_SINGLE_QUEUE on some NVIDIA GPUs and Rosetta2, due to potential challenges with MTLEvents on those platforms. */
+	MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_METAL_EVENTS            = 2,	/**< Always use Metal events (MTLEvent) when available on the platform. */
+	MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE            = 3,	/**< Limit Vulkan to a single queue, with no explicit semaphore synchronization, and use Metal's implicit guarantees that all operations submitted to a queue will give the same result as if they had been run in submission order. */
+	MVK_CONFIG_VK_SEMAPHORE_MAX_ENUM                              = 0x7FFFFFFF
+} MVKVkSemaphoreSupportStyle;
+
 /**
  * MoltenVK configuration settings.
  *
@@ -557,52 +574,37 @@
 	 */
 	VkBool32 forceLowPowerGPU;
 
-	/**
-	 * Use MTLFence, if it is available on the device, for VkSemaphore synchronization behaviour.
-	 *
-	 * This parameter interacts with semaphoreUseMTLEvent. If both are enabled, on GPUs other than
-	 * NVIDIA, semaphoreUseMTLEvent takes priority and MTLEvent will be used if it is available,
-	 * otherwise MTLFence will be used if it is available. On NVIDIA GPUs, MTLEvent is disabled
-	 * for VkSemaphores, so CPU-based synchronization will be used unless semaphoreUseMTLFence
-	 * is enabled and MTLFence is available.
-	 *
-	 * In the special case of VK_SEMAPHORE_TYPE_TIMELINE semaphores, MoltenVK will always
-	 * use MTLSharedEvent if it is available on the platform, regardless of the values of
-	 * semaphoreUseMTLEvent or semaphoreUseMTLFence.
-	 *
-	 * The value of this parameter must be changed before creating a VkDevice,
-	 * for the change to take effect.
-	 *
-	 * The initial value or this parameter is set by the
-	 * MVK_ALLOW_METAL_FENCES
-	 * runtime environment variable or MoltenVK compile-time build setting.
-	 * If neither is set, this setting is disabled by default, and VkSemaphore will not use MTLFence.
-	 */
+	/** Deprecated. Use semaphoreSupportStyle instead. */
 	VkBool32 semaphoreUseMTLFence;
 
 	/**
-	 * Use MTLEvent, if it is available on the device, for VkSemaphore synchronization behaviour.
+	 * Determines the style used to implement Vulkan semaphore (VkSemaphore) functionality in Metal.
+	 * See the documentation of the MVKVkSemaphoreSupportStyle for the options.
 	 *
-	 * This parameter interacts with semaphoreUseMTLFence. If both are enabled, on GPUs other than
-	 * NVIDIA, semaphoreUseMTLEvent takes priority and MTLEvent will be used if it is available,
-	 * otherwise MTLFence will be used if it is available. On NVIDIA GPUs, MTLEvent is disabled
-	 * for VkSemaphores, so CPU-based synchronization will be used unless semaphoreUseMTLFence
-	 * is enabled and MTLFence is available.
+	 * In the special case of VK_SEMAPHORE_TYPE_TIMELINE semaphores, MoltenVK will always use
+	 * MTLSharedEvent if it is available on the platform, regardless of the value of this parameter.
 	 *
-	 * In the special case of VK_SEMAPHORE_TYPE_TIMELINE semaphores, MoltenVK will always
-	 * use MTLSharedEvent if it is available on the platform, regardless of the values of
-	 * semaphoreUseMTLEvent or semaphoreUseMTLFence.
-	 *
-	 * The value of this parameter must be changed before creating a VkDevice,
+	 * The value of this parameter must be changed before creating a VkInstance,
 	 * for the change to take effect.
 	 *
 	 * The initial value or this parameter is set by the
-	 * MVK_ALLOW_METAL_EVENTS
+	 * MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE
 	 * runtime environment variable or MoltenVK compile-time build setting.
-	 * If neither is set, this setting is enabled by default, and VkSemaphore will use MTLEvent,
-	 * if it is available, except on NVIDIA GPUs.
+	 * If neither is set, this setting is set to
+	 * MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_METAL_EVENTS_WHERE_SAFE by default,
+	 * and MoltenVK will use MTLEvent, except on NVIDIA GPU, and Rosetta2 environments,
+	 * where it will use a single queue with implicit synchronization
+	 * (as if this parameter was set to MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE).
+	 *
+	 * This parameter interacts with the deprecated legacy parameters semaphoreUseMTLEvent
+	 * and semaphoreUseMTLFence. If semaphoreUseMTLEvent is enabled, this parameter
+	 * will be set to MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_METAL_EVENTS_WHERE_SAFE.
+	 * If semaphoreUseMTLEvent is disabled, and semaphoreUseMTLFence is enabled,
+	 * this parameter will be set to MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE.
+	 * Structurally, this parameter replaces, and is aliased by, semaphoreUseMTLEvent.
 	 */
-	VkBool32 semaphoreUseMTLEvent;
+	MVKVkSemaphoreSupportStyle semaphoreSupportStyle;
+#define semaphoreUseMTLEvent semaphoreSupportStyle
 
 	/**
 	 * Controls whether Metal should run an automatic GPU capture without the user having to
@@ -809,25 +811,26 @@
 	 * Controls whether MoltenVK should use Metal argument buffers for resources defined in
 	 * descriptor sets, if Metal argument buffers are supported on the platform. Using Metal
 	 * argument buffers dramatically increases the number of buffers, textures and samplers
-	 * that can be bound to a pipeline shader, and in most cases improves performance. If this
-	 * setting is enabled, MoltenVK will use Metal argument buffers to bind resources to the
-	 * shaders. If this setting is disabled, MoltenVK will bind resources to shaders discretely.
+	 * that can be bound to a pipeline shader, and in most cases improves performance.
+	 * This setting is an enumeration that specifies the conditions under which MoltenVK
+	 * will use Metal argument buffers.
 	 *
 	 * NOTE: Currently, Metal argument buffer support is in beta stage, and is only supported
 	 * on macOS 11.0 (Big Sur) or later, or on older versions of macOS using an Intel GPU.
 	 * Metal argument buffers support is not available on iOS. Development to support iOS
 	 * and a wider combination of GPU's on older macOS versions is under way.
 	 *
-	 * The value of this parameter must be changed before creating a VkInstance,
+	 * The value of this parameter must be changed before creating a VkDevice,
 	 * for the change to take effect.
 	 *
 	 * The initial value or this parameter is set by the
 	 * MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS
 	 * runtime environment variable or MoltenVK compile-time build setting.
-	 * If neither is set, this setting is enabled by default, and MoltenVK will not
-	 * use Metal argument buffers, and will bind resources to shaders discretely.
+	 * If neither is set, this setting is set to
+	 * MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS_NEVER by default,
+	 * and MoltenVK will not use Metal argument buffers.
 	 */
-	VkBool32 useMetalArgumentBuffers;
+	MVKUseMetalArgumentBuffers useMetalArgumentBuffers;
 
 } MVKConfiguration;
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
index c88d65e..d378fd9 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
@@ -90,6 +90,23 @@
 #pragma mark -
 #pragma mark MVKPhysicalDevice
 
+typedef enum {
+	MVKSemaphoreStyleUseMTLEvent,
+	MVKSemaphoreStyleUseEmulation,
+	MVKSemaphoreStyleSingleQueue,
+} MVKSemaphoreStyle;
+
+/** VkPhysicalDeviceVulkan12Features entries that did not originate in a prior extension. */
+typedef struct MVKPhysicalDeviceVulkan12FeaturesNoExt {
+	VkBool32 samplerMirrorClampToEdge;
+	VkBool32 drawIndirectCount;
+	VkBool32 descriptorIndexing;
+	VkBool32 samplerFilterMinmax;
+	VkBool32 shaderOutputViewportIndex;
+	VkBool32 shaderOutputLayer;
+	VkBool32 subgroupBroadcastDynamicId;
+} MVKPhysicalDeviceVulkan12FeaturesNoExt;
+
 /** Represents a Vulkan physical GPU device. */
 class MVKPhysicalDevice : public MVKDispatchableVulkanAPIObject {
 
@@ -333,8 +350,10 @@
 	/** Returns whether the MSL version is supported on this device. */
 	bool mslVersionIsAtLeast(MTLLanguageVersion minVer) { return _metalFeatures.mslVersionEnum >= minVer; }
 
-	/** Returns whether this device is using Metal argument buffers. */
-	bool isUsingMetalArgumentBuffers() const  { return _metalFeatures.argumentBuffers && mvkConfig().useMetalArgumentBuffers; };
+	/** Returns whether this physical device supports Metal argument buffers. */
+	bool supportsMetalArgumentBuffers() const  {
+		return _metalFeatures.argumentBuffers && mvkConfig().useMetalArgumentBuffers != MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS_NEVER;
+	};
 
 	/**
 	 * Returns the start timestamps of a timestamp correlation.
@@ -384,6 +403,7 @@
 	void initLimits();
 	void initGPUInfoProperties();
 	void initMemoryProperties();
+	void initVkSemaphoreStyle();
 	void setMemoryHeap(uint32_t heapIndex, VkDeviceSize heapSize, VkMemoryHeapFlags heapFlags);
 	void setMemoryType(uint32_t typeIndex, uint32_t heapIndex, VkMemoryPropertyFlags propertyFlags);
 	uint64_t getVRAMSize();
@@ -406,6 +426,7 @@
 	MVKInstance* _mvkInstance;
 	const MVKExtensionList _supportedExtensions;
 	VkPhysicalDeviceFeatures _features;
+	MVKPhysicalDeviceVulkan12FeaturesNoExt _vulkan12FeaturesNoExt;
 	MVKPhysicalDeviceMetalFeatures _metalFeatures;
 	VkPhysicalDeviceProperties _properties;
 	VkPhysicalDeviceTexelBufferAlignmentPropertiesEXT _texelBuffAlignProperties;
@@ -413,6 +434,7 @@
 	MVKSmallVector<MVKQueueFamily*, kMVKQueueFamilyCount> _queueFamilies;
 	MVKPixelFormats _pixelFormats;
 	id<MTLCounterSet> _timestampMTLCounterSet;
+	MVKSemaphoreStyle _vkSemaphoreStyle;
 	uint32_t _allMemoryTypes;
 	uint32_t _hostVisibleMemoryTypes;
 	uint32_t _hostCoherentMemoryTypes;
@@ -431,12 +453,6 @@
 	id<MTLCommandBuffer> mtlCmdBuffer = nil;
 } MVKMTLBlitEncoder;
 
-typedef enum {
-	MVKSemaphoreStyleUseMTLEvent,
-	MVKSemaphoreStyleUseMTLFence,
-	MVKSemaphoreStyleUseEmulation
-} MVKSemaphoreStyle;
-
 /** Represents a Vulkan logical GPU device, associated with a physical device. */
 class MVKDevice : public MVKDispatchableVulkanAPIObject {
 
@@ -694,6 +710,9 @@
 	/** Returns the underlying Metal device. */
 	inline id<MTLDevice> getMTLDevice() { return _physicalDevice->getMTLDevice(); }
 
+	/** Returns whether this device is using Metal argument buffers. */
+	bool isUsingMetalArgumentBuffers() { return _isUsingMetalArgumentBuffers; };
+
 	/**
 	 * Returns an autoreleased options object to be used when compiling MSL shaders.
 	 * The useFastMath parameter is and-combined with MVKConfiguration::fastMathEnabled
@@ -786,6 +805,9 @@
 	VkPhysicalDevice##structName##Features##extnSfx _enabled##structName##Features;
 #include "MVKDeviceFeatureStructs.def"
 
+	/** VkPhysicalDeviceVulkan12Features entries that did not originate in a prior extension available and enabled. */
+	MVKPhysicalDeviceVulkan12FeaturesNoExt _enabledVulkan12FeaturesNoExt;
+
 	/** Pointer to the Metal-specific features of the underlying physical device. */
 	const MVKPhysicalDeviceMetalFeatures* _pMetalFeatures;
 
@@ -849,8 +871,8 @@
 														   VkDescriptorSetLayoutSupport* pSupport,
 														   VkDescriptorSetVariableDescriptorCountLayoutSupportEXT* pVarDescSetCountSupport);
 
-	MVKPhysicalDevice* _physicalDevice;
-    MVKCommandResourceFactory* _commandResourceFactory;
+	MVKPhysicalDevice* _physicalDevice = nullptr;
+    MVKCommandResourceFactory* _commandResourceFactory = nullptr;
 	MVKSmallVector<MVKSmallVector<MVKQueue*, kMVKQueueCountPerQueueFamily>, kMVKQueueFamilyCount> _queuesByQueueFamilyIndex;
 	MVKSmallVector<MVKResource*, 256> _resources;
 	MVKSmallVector<MVKPrivateDataSlot*> _privateDataSlots;
@@ -864,11 +886,11 @@
     id<MTLBuffer> _globalVisibilityResultMTLBuffer = nil;
 	id<MTLSamplerState> _defaultMTLSamplerState = nil;
 	id<MTLBuffer> _dummyBlitMTLBuffer = nil;
-	MVKSemaphoreStyle _vkSemaphoreStyle = MVKSemaphoreStyleUseEmulation;
     uint32_t _globalVisibilityQueryCount = 0;
 	bool _logActivityPerformanceInline = false;
 	bool _isPerformanceTracking = false;
 	bool _isCurrentlyAutoGPUCapturing = false;
+	bool _isUsingMetalArgumentBuffers = false;
 
 };
 
@@ -888,25 +910,25 @@
 public:
 
 	/** Returns the device for which this object was created. */
-	inline MVKDevice* getDevice() { return _device; }
+	MVKDevice* getDevice() { return _device; }
 
 	/** Returns the physical device underlying this logical device. */
-	inline MVKPhysicalDevice* getPhysicalDevice() { return _device->getPhysicalDevice(); }
+	MVKPhysicalDevice* getPhysicalDevice() { return _device->getPhysicalDevice(); }
 
 	/** Returns the underlying Metal device. */
-	inline id<MTLDevice> getMTLDevice() { return _device->getMTLDevice(); }
+	id<MTLDevice> getMTLDevice() { return _device->getMTLDevice(); }
 
 	/** Returns info about the pixel format supported by the physical device. */
-	inline MVKPixelFormats* getPixelFormats() { return _device->getPixelFormats(); }
+	MVKPixelFormats* getPixelFormats() { return _device->getPixelFormats(); }
 
 	/** Returns whether this device is using Metal argument buffers. */
-	inline bool isUsingMetalArgumentBuffers() { return getPhysicalDevice()->isUsingMetalArgumentBuffers(); };
+	bool isUsingMetalArgumentBuffers() { return _device->isUsingMetalArgumentBuffers(); };
 
 	/** Returns whether this device is using one Metal argument buffer for each descriptor set, on multiple pipeline and pipeline stages. */
-	inline bool isUsingDescriptorSetMetalArgumentBuffers() { return isUsingMetalArgumentBuffers() && _device->_pMetalFeatures->descriptorSetArgumentBuffers; };
+	bool isUsingDescriptorSetMetalArgumentBuffers() { return isUsingMetalArgumentBuffers() && _device->_pMetalFeatures->descriptorSetArgumentBuffers; };
 
 	/** Returns whether this device is using one Metal argument buffer for each descriptor set-pipeline-stage combination. */
-	inline bool isUsingPipelineStageMetalArgumentBuffers() { return isUsingMetalArgumentBuffers() && !_device->_pMetalFeatures->descriptorSetArgumentBuffers; };
+	bool isUsingPipelineStageMetalArgumentBuffers() { return isUsingMetalArgumentBuffers() && !_device->_pMetalFeatures->descriptorSetArgumentBuffers; };
 
 	/** Constructs an instance for the specified device. */
     MVKDeviceTrackingMixin(MVKDevice* device) : _device(device) { assert(_device); }
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
index e39a981..f43f942 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
@@ -116,8 +116,8 @@
 	VkPhysicalDeviceVulkan12Features supportedFeats12 = {
 		.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
 		.pNext = nullptr,
-		.samplerMirrorClampToEdge = _metalFeatures.samplerMirrorClampToEdge,
-		.drawIndirectCount = false,
+		.samplerMirrorClampToEdge = _vulkan12FeaturesNoExt.samplerMirrorClampToEdge,
+		.drawIndirectCount = _vulkan12FeaturesNoExt.drawIndirectCount,
 		.storageBuffer8BitAccess = true,
 		.uniformAndStorageBuffer8BitAccess = true,
 		.storagePushConstant8 = true,
@@ -125,7 +125,7 @@
 		.shaderSharedInt64Atomics = false,
 		.shaderFloat16 = true,
 		.shaderInt8 = true,
-		.descriptorIndexing = false,		// Requires _metalFeatures.arrayOfTextures && _metalFeatures.arrayOfSamplers && shaderStorageBufferArrayNonUniformIndexing
+		.descriptorIndexing = _vulkan12FeaturesNoExt.descriptorIndexing,
 		.shaderInputAttachmentArrayDynamicIndexing = _metalFeatures.arrayOfTextures,
 		.shaderUniformTexelBufferArrayDynamicIndexing = _metalFeatures.arrayOfTextures,
 		.shaderStorageTexelBufferArrayDynamicIndexing = _metalFeatures.arrayOfTextures,
@@ -146,7 +146,7 @@
 		.descriptorBindingPartiallyBound = true,
 		.descriptorBindingVariableDescriptorCount = true,
 		.runtimeDescriptorArray = true,
-		.samplerFilterMinmax = false,
+		.samplerFilterMinmax = _vulkan12FeaturesNoExt.samplerFilterMinmax,
 		.scalarBlockLayout = true,
 		.imagelessFramebuffer = true,
 		.uniformBufferStandardLayout = true,
@@ -160,9 +160,9 @@
 		.vulkanMemoryModel = false,
 		.vulkanMemoryModelDeviceScope = false,
 		.vulkanMemoryModelAvailabilityVisibilityChains = false,
-		.shaderOutputViewportIndex = true,
-		.shaderOutputLayer = true,
-		.subgroupBroadcastDynamicId = true,
+		.shaderOutputViewportIndex = _vulkan12FeaturesNoExt.shaderOutputViewportIndex,
+		.shaderOutputLayer = _vulkan12FeaturesNoExt.shaderOutputLayer,
+		.subgroupBroadcastDynamicId = _vulkan12FeaturesNoExt.subgroupBroadcastDynamicId,
 	};
 
 	features->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
@@ -414,7 +414,7 @@
 void MVKPhysicalDevice::getProperties(VkPhysicalDeviceProperties2* properties) {
 	uint32_t uintMax = std::numeric_limits<uint32_t>::max();
 	uint32_t maxSamplerCnt = getMaxSamplerCount();
-	bool isTier2 = isUsingMetalArgumentBuffers() && (_metalFeatures.argumentBuffersTier >= MTLArgumentBuffersTier2);
+	bool isTier2 = supportsMetalArgumentBuffers() && (_metalFeatures.argumentBuffersTier >= MTLArgumentBuffersTier2);
 
 	// Create a SSOT for these Vulkan 1.1 properties, which can be queried via two mechanisms here.
 	VkPhysicalDeviceVulkan11Properties supportedProps11;
@@ -1309,17 +1309,21 @@
 		qfProps.queueFlags = (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT);
 		_queueFamilies.push_back(new MVKQueueFamily(this, qfIdx++, &qfProps));
 
-		// Dedicated graphics queue family...or another general-purpose queue family.
-		if (specialize) { qfProps.queueFlags = (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_TRANSFER_BIT); }
-		_queueFamilies.push_back(new MVKQueueFamily(this, qfIdx++, &qfProps));
+		// Single queue semaphore requires using a single queue for everything
+		// So don't allow anyone to have more than one
+		if (_vkSemaphoreStyle != MVKSemaphoreStyleSingleQueue) {
+			// Dedicated graphics queue family...or another general-purpose queue family.
+			if (specialize) { qfProps.queueFlags = (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_TRANSFER_BIT); }
+			_queueFamilies.push_back(new MVKQueueFamily(this, qfIdx++, &qfProps));
 
-		// Dedicated compute queue family...or another general-purpose queue family.
-		if (specialize) { qfProps.queueFlags = (VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT); }
-		_queueFamilies.push_back(new MVKQueueFamily(this, qfIdx++, &qfProps));
+			// Dedicated compute queue family...or another general-purpose queue family.
+			if (specialize) { qfProps.queueFlags = (VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT); }
+			_queueFamilies.push_back(new MVKQueueFamily(this, qfIdx++, &qfProps));
 
-		// Dedicated transfer queue family...or another general-purpose queue family.
-		if (specialize) { qfProps.queueFlags = VK_QUEUE_TRANSFER_BIT; }
-		_queueFamilies.push_back(new MVKQueueFamily(this, qfIdx++, &qfProps));
+			// Dedicated transfer queue family...or another general-purpose queue family.
+			if (specialize) { qfProps.queueFlags = VK_QUEUE_TRANSFER_BIT; }
+			_queueFamilies.push_back(new MVKQueueFamily(this, qfIdx++, &qfProps));
+		}
 
 		MVKAssert(kMVKQueueFamilyCount >= _queueFamilies.size(), "Adjust value of kMVKQueueFamilyCount.");
 	}
@@ -1440,6 +1444,7 @@
 	initMemoryProperties();
 	initExternalMemoryProperties();
 	initCounterSets();
+	initVkSemaphoreStyle();
 	logGPUInfo();
 }
 
@@ -1986,9 +1991,7 @@
 	_features.shaderSampledImageArrayDynamicIndexing = _metalFeatures.arrayOfTextures;
 	_features.textureCompressionBC = mvkSupportsBCTextureCompression(_mtlDevice);
 
-    if (_metalFeatures.indirectDrawing && _metalFeatures.baseVertexInstanceDrawing) {
-        _features.drawIndirectFirstInstance = true;
-    }
+	_features.drawIndirectFirstInstance = _metalFeatures.indirectDrawing && _metalFeatures.baseVertexInstanceDrawing;
 
 #if MVK_TVOS
     _features.textureCompressionETC2 = true;
@@ -2087,6 +2090,17 @@
         _features.textureCompressionASTC_LDR = true;
     }
 #endif
+
+	// Additional non-extension Vulkan 1.2 features.
+	mvkClear(&_vulkan12FeaturesNoExt);		// Start with everything cleared
+	_vulkan12FeaturesNoExt.samplerMirrorClampToEdge = _metalFeatures.samplerMirrorClampToEdge;
+	_vulkan12FeaturesNoExt.drawIndirectCount = false;
+	_vulkan12FeaturesNoExt.descriptorIndexing = true;
+	_vulkan12FeaturesNoExt.samplerFilterMinmax = false;
+	_vulkan12FeaturesNoExt.shaderOutputViewportIndex = _features.multiViewport;
+	_vulkan12FeaturesNoExt.shaderOutputLayer = _metalFeatures.layeredRendering;
+	_vulkan12FeaturesNoExt.subgroupBroadcastDynamicId = _metalFeatures.simdPermute || _metalFeatures.quadPermute;
+
 }
 
 
@@ -2751,7 +2765,7 @@
 	// Next 4 bytes contains flags based on enabled Metal features that
 	// might affect the contents of the pipeline cache (mostly MSL content).
 	uint32_t mtlFeatures = 0;
-	mtlFeatures |= isUsingMetalArgumentBuffers() << 0;
+	mtlFeatures |= supportsMetalArgumentBuffers() << 0;
 	*(uint32_t*)&_properties.pipelineCacheUUID[uuidComponentOffset] = NSSwapHostIntToBig(mtlFeatures);
 	uuidComponentOffset += sizeof(mtlFeatures);
 }
@@ -3005,7 +3019,7 @@
 // objects that can be created within the app. When not using argument buffers, no such
 // limit is imposed. This has been verified with testing up to 1M MTLSamplerStates.
 uint32_t MVKPhysicalDevice::getMaxSamplerCount() {
-	if (isUsingMetalArgumentBuffers()) {
+	if (supportsMetalArgumentBuffers()) {
 		return ([_mtlDevice respondsToSelector: @selector(maxArgumentBufferSamplerCount)]
 				? (uint32_t)_mtlDevice.maxArgumentBufferSamplerCount : 1024);
 	} else {
@@ -3101,6 +3115,35 @@
 	}
 }
 
+// Determine whether Vulkan semaphores should use a MTLEvent, CPU callbacks, or should limit
+// Vulkan to a single queue and use Metal's implicit guarantees that all operations submitted
+// to a queue will give the same result as if they had been run in submission order.
+// MTLEvents for semaphores can sometimes prove troublesome on some platforms,
+// and so may optionally be disabled on those platforms.
+void MVKPhysicalDevice::initVkSemaphoreStyle() {
+
+	// Default to CPU callback if other options unavailable.
+	_vkSemaphoreStyle = MVKSemaphoreStyleUseEmulation;
+
+	switch (mvkConfig().semaphoreSupportStyle) {
+		case MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_METAL_EVENTS_WHERE_SAFE: {
+			bool isNVIDIA = _properties.vendorID == kNVVendorId;
+			bool isRosetta2 = _properties.vendorID == kAppleVendorId && !MVK_APPLE_SILICON;
+			if (_metalFeatures.events && !(isRosetta2 || isNVIDIA)) { _vkSemaphoreStyle = MVKSemaphoreStyleUseMTLEvent; }
+			break;
+		}
+		case MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_METAL_EVENTS:
+			if (_metalFeatures.events) { _vkSemaphoreStyle = MVKSemaphoreStyleUseMTLEvent; }
+			break;
+		case MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE:
+			_vkSemaphoreStyle = MVKSemaphoreStyleSingleQueue;
+			break;
+		case MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_CALLBACK:
+		default:
+			break;
+	}
+}
+
 // Workaround for a bug in Intel Iris Plus Graphics driver where the counterSets array is
 // not properly retained internally, and becomes a zombie when counterSets is called more
 // than once, which occurs when an app creates more than one VkInstance. This workaround
@@ -3633,10 +3676,10 @@
 			return new MVKTimelineSemaphoreEmulated(this, pCreateInfo, pTypeCreateInfo, pExportInfo, pImportInfo);
 		}
 	} else {
-		switch (_vkSemaphoreStyle) {
+		switch (_physicalDevice->_vkSemaphoreStyle) {
 			case MVKSemaphoreStyleUseMTLEvent:  return new MVKSemaphoreMTLEvent(this, pCreateInfo, pExportInfo, pImportInfo);
-			case MVKSemaphoreStyleUseMTLFence:  return new MVKSemaphoreMTLFence(this, pCreateInfo, pExportInfo, pImportInfo);
 			case MVKSemaphoreStyleUseEmulation: return new MVKSemaphoreEmulated(this, pCreateInfo, pExportInfo, pImportInfo);
+			case MVKSemaphoreStyleSingleQueue:  return new MVKSemaphoreSingleQueue(this, pCreateInfo, pExportInfo, pImportInfo);
 		}
 	}
 }
@@ -4179,7 +4222,7 @@
 		if ( !_defaultMTLSamplerState ) {
 			@autoreleasepool {
 				MTLSamplerDescriptor* mtlSampDesc = [[MTLSamplerDescriptor new] autorelease];
-				mtlSampDesc.supportArgumentBuffers = _physicalDevice->isUsingMetalArgumentBuffers();
+				mtlSampDesc.supportArgumentBuffers = isUsingMetalArgumentBuffers();
 				_defaultMTLSamplerState = [getMTLDevice() newSamplerStateWithDescriptor: mtlSampDesc];	// retained
 			}
 		}
@@ -4344,7 +4387,8 @@
 
 MVKDevice::MVKDevice(MVKPhysicalDevice* physicalDevice, const VkDeviceCreateInfo* pCreateInfo) : _enabledExtensions(this) {
 
-		// If the physical device is lost, bail.
+	// If the physical device is lost, bail.
+	// Must have initialized everything accessed in destructor to null.
 	if (physicalDevice->getConfigurationResult() != VK_SUCCESS) {
 		setConfigurationResult(physicalDevice->getConfigurationResult());
 		return;
@@ -4357,11 +4401,13 @@
 	initQueues(pCreateInfo);
 	reservePrivateData(pCreateInfo);
 
-    _globalVisibilityResultMTLBuffer = nil;
-    _globalVisibilityQueryCount = 0;
-
-	_defaultMTLSamplerState = nil;
-	_dummyBlitMTLBuffer = nil;
+	// After enableExtensions && enableFeatures
+	// Use Metal arg buffs if available, and either config wants them always,
+	// or config wants them with descriptor indexing and descriptor indexing has been enabled.
+	_isUsingMetalArgumentBuffers = (_physicalDevice->supportsMetalArgumentBuffers() &&
+									(mvkConfig().useMetalArgumentBuffers == MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS_ALWAYS ||
+									 (mvkConfig().useMetalArgumentBuffers == MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS_DESCRIPTOR_INDEXING &&
+									  (_enabledVulkan12FeaturesNoExt.descriptorIndexing || _enabledExtensions.vk_EXT_descriptor_indexing.enabled))));
 
 	_commandResourceFactory = new MVKCommandResourceFactory(this);
 
@@ -4426,25 +4472,15 @@
 	_pProperties = &_physicalDevice->_properties;
 	_pMemoryProperties = &_physicalDevice->_memoryProperties;
 
-	// Decide whether Vulkan semaphores should use a MTLEvent or MTLFence if they are available.
-	// Prefer MTLEvent, because MTLEvent handles sync across MTLCommandBuffers and MTLCommandQueues.
-	// However, do not allow use of MTLEvents on Rosetta2 (x86 build on M1 runtime) or NVIDIA GPUs,
-	// which have demonstrated trouble with MTLEvents. In that case, since MTLFence use is disabled
-	// by default, unless MTLFence is deliberately enabled, CPU emulation will be used.
-	bool isNVIDIA = _pProperties->vendorID == kNVVendorId;
-	bool isRosetta2 = _pProperties->vendorID == kAppleVendorId && !MVK_APPLE_SILICON;
-	bool canUseMTLEventForSem4 = _pMetalFeatures->events && mvkConfig().semaphoreUseMTLEvent && !(isRosetta2 || isNVIDIA);
-	bool canUseMTLFenceForSem4 = _pMetalFeatures->fences && mvkConfig().semaphoreUseMTLFence;
-	_vkSemaphoreStyle = canUseMTLEventForSem4 ? MVKSemaphoreStyleUseMTLEvent : (canUseMTLFenceForSem4 ? MVKSemaphoreStyleUseMTLFence : MVKSemaphoreStyleUseEmulation);
-	switch (_vkSemaphoreStyle) {
+	switch (_physicalDevice->_vkSemaphoreStyle) {
 		case MVKSemaphoreStyleUseMTLEvent:
-			MVKLogInfo("Using MTLEvent for Vulkan semaphores.");
-			break;
-		case MVKSemaphoreStyleUseMTLFence:
-			MVKLogInfo("Using MTLFence for Vulkan semaphores.");
+			MVKLogInfo("Vulkan semaphores using MTLEvent.");
 			break;
 		case MVKSemaphoreStyleUseEmulation:
-			MVKLogInfo("Using emulation for Vulkan semaphores.");
+			MVKLogInfo("Vulkan semaphores using CPU callbacks upon GPU submission completion.");
+			break;
+		case MVKSemaphoreStyleSingleQueue:
+			MVKLogInfo("Vulkan semaphores using Metal implicit guarantees within a single queue.");
 			break;
 	}
 }
@@ -4476,6 +4512,8 @@
 
 #include "MVKDeviceFeatureStructs.def"
 
+	mvkClear(&_enabledVulkan12FeaturesNoExt);
+
 	sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
 	mvkClear(&_enabledFeatures);
 	VkPhysicalDeviceFeatures2 pdFeats2;
@@ -4524,7 +4562,11 @@
 				break;
 			}
 			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES: {
+				auto& pdvulkan12FeaturesNoExt = _physicalDevice->_vulkan12FeaturesNoExt;
 				auto* requestedFeatures = (VkPhysicalDeviceVulkan12Features*)next;
+				enableFeatures(&_enabledVulkan12FeaturesNoExt.samplerMirrorClampToEdge,
+							   &requestedFeatures->samplerMirrorClampToEdge,
+							   &pdvulkan12FeaturesNoExt.samplerMirrorClampToEdge, 2);
 				enableFeatures(&_enabled8BitStorageFeatures.storageBuffer8BitAccess,
 							   &requestedFeatures->storageBuffer8BitAccess,
 							   &pd8BitStorageFeatures.storageBuffer8BitAccess, 3);
@@ -4534,9 +4576,15 @@
 				enableFeatures(&_enabledShaderFloat16Int8Features.shaderFloat16,
 							   &requestedFeatures->shaderFloat16,
 							   &pdShaderFloat16Int8Features.shaderFloat16, 2);
+				enableFeatures(&_enabledVulkan12FeaturesNoExt.descriptorIndexing,
+							   &requestedFeatures->descriptorIndexing,
+							   &pdvulkan12FeaturesNoExt.descriptorIndexing, 1);
 				enableFeatures(&_enabledDescriptorIndexingFeatures.shaderInputAttachmentArrayDynamicIndexing,
 							   &requestedFeatures->shaderInputAttachmentArrayDynamicIndexing,
 							   &pdDescriptorIndexingFeatures.shaderInputAttachmentArrayDynamicIndexing, 20);
+				enableFeatures(&_enabledVulkan12FeaturesNoExt.samplerFilterMinmax,
+							   &requestedFeatures->samplerFilterMinmax,
+							   &pdvulkan12FeaturesNoExt.samplerFilterMinmax, 1);
 				enableFeatures(&_enabledScalarBlockLayoutFeatures.scalarBlockLayout,
 							   &requestedFeatures->scalarBlockLayout,
 							   &pdScalarBlockLayoutFeatures.scalarBlockLayout, 1);
@@ -4564,6 +4612,9 @@
 				enableFeatures(&_enabledVulkanMemoryModelFeatures.vulkanMemoryModel,
 							   &requestedFeatures->vulkanMemoryModel,
 							   &pdVulkanMemoryModelFeatures.vulkanMemoryModel, 3);
+				enableFeatures(&_enabledVulkan12FeaturesNoExt.shaderOutputViewportIndex,
+							   &requestedFeatures->shaderOutputViewportIndex,
+							   &pdvulkan12FeaturesNoExt.shaderOutputViewportIndex, 3);
 				break;
 			}
 
@@ -4663,7 +4714,7 @@
 	for (auto& queues : _queuesByQueueFamilyIndex) {
 		mvkDestroyContainerContents(queues);
 	}
-	_commandResourceFactory->destroy();
+	if (_commandResourceFactory) { _commandResourceFactory->destroy(); }
 
     [_globalVisibilityResultMTLBuffer release];
 	[_defaultMTLSamplerState release];
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSync.h b/MoltenVK/MoltenVK/GPUObjects/MVKSync.h
index 3562369..0a03499 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSync.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSync.h
@@ -201,27 +201,29 @@
 
 
 #pragma mark -
-#pragma mark MVKSemaphoreMTLFence
+#pragma mark MVKSemaphoreSingleQueue
 
-/** An MVKSemaphore that uses MTLFence to provide synchronization. */
-class MVKSemaphoreMTLFence : public MVKSemaphore {
+/**
+ * An MVKSemaphore that uses Metal's built-in guarantees on single-queue submission to provide semaphore-like guarantees.
+ *
+ * Relies on Metal's enabled-by-default hazard tracking, and will need to start doing things with MTLFences
+ * if we start using things with MTLHazardTrackingModeUntracked
+ */
+class MVKSemaphoreSingleQueue : public MVKSemaphore {
 
 public:
 	void encodeWait(id<MTLCommandBuffer> mtlCmdBuff, uint64_t) override;
 	void encodeSignal(id<MTLCommandBuffer> mtlCmdBuff, uint64_t) override;
 	uint64_t deferSignal() override;
 	void encodeDeferredSignal(id<MTLCommandBuffer> mtlCmdBuff, uint64_t) override;
-	bool isUsingCommandEncoding() override { return true; }
+	bool isUsingCommandEncoding() override { return false; }
 
-	MVKSemaphoreMTLFence(MVKDevice* device,
-						 const VkSemaphoreCreateInfo* pCreateInfo,
-						 const VkExportMetalObjectCreateInfoEXT* pExportInfo,
-						 const VkImportMetalSharedEventInfoEXT* pImportInfo);
+	MVKSemaphoreSingleQueue(MVKDevice* device,
+	                        const VkSemaphoreCreateInfo* pCreateInfo,
+	                        const VkExportMetalObjectCreateInfoEXT* pExportInfo,
+	                        const VkImportMetalSharedEventInfoEXT* pImportInfo);
 
-	~MVKSemaphoreMTLFence() override;
-
-protected:
-	id<MTLFence> _mtlFence;
+	~MVKSemaphoreSingleQueue() override;
 };
 
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm
index 29d36d3..318e86a 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm
@@ -77,47 +77,34 @@
 
 
 #pragma mark -
-#pragma mark MVKSemaphoreMTLFence
+#pragma mark MVKSemaphoreSingleQueue
 
-// Could use any encoder. Assume BLIT is fastest and lightest.
-// Nil mtlCmdBuff will do nothing.
-void MVKSemaphoreMTLFence::encodeWait(id<MTLCommandBuffer> mtlCmdBuff, uint64_t) {
-	id<MTLBlitCommandEncoder> mtlCmdEnc = mtlCmdBuff.blitCommandEncoder;
-	[mtlCmdEnc waitForFence: _mtlFence];
-	[mtlCmdEnc endEncoding];
+void MVKSemaphoreSingleQueue::encodeWait(id<MTLCommandBuffer> mtlCmdBuff, uint64_t) {
+	// Metal will handle all synchronization for us automatically
 }
 
-// Could use any encoder. Assume BLIT is fastest and lightest.
-// Nil mtlCmdBuff will do nothing.
-void MVKSemaphoreMTLFence::encodeSignal(id<MTLCommandBuffer> mtlCmdBuff, uint64_t) {
-	id<MTLBlitCommandEncoder> mtlCmdEnc = mtlCmdBuff.blitCommandEncoder;
-	[mtlCmdEnc updateFence: _mtlFence];
-	[mtlCmdEnc endEncoding];
+void MVKSemaphoreSingleQueue::encodeSignal(id<MTLCommandBuffer> mtlCmdBuff, uint64_t) {
+	// Metal will handle all synchronization for us automatically
 }
 
-uint64_t MVKSemaphoreMTLFence::deferSignal() {
+uint64_t MVKSemaphoreSingleQueue::deferSignal() {
 	return 0;
 }
 
-void MVKSemaphoreMTLFence::encodeDeferredSignal(id<MTLCommandBuffer> mtlCmdBuff, uint64_t) {
+void MVKSemaphoreSingleQueue::encodeDeferredSignal(id<MTLCommandBuffer> mtlCmdBuff, uint64_t) {
 	encodeSignal(mtlCmdBuff, 0);
 }
 
-MVKSemaphoreMTLFence::MVKSemaphoreMTLFence(MVKDevice* device,
-										   const VkSemaphoreCreateInfo* pCreateInfo,
-										   const VkExportMetalObjectCreateInfoEXT* pExportInfo,
-										   const VkImportMetalSharedEventInfoEXT* pImportInfo) : MVKSemaphore(device, pCreateInfo) {
-
-	_mtlFence = [device->getMTLDevice() newFence];		//retained
-
+MVKSemaphoreSingleQueue::MVKSemaphoreSingleQueue(MVKDevice* device,
+                                                 const VkSemaphoreCreateInfo* pCreateInfo,
+                                                 const VkExportMetalObjectCreateInfoEXT* pExportInfo,
+                                                 const VkImportMetalSharedEventInfoEXT* pImportInfo) : MVKSemaphore(device, pCreateInfo) {
 	if ((pImportInfo && pImportInfo->mtlSharedEvent) || (pExportInfo && pExportInfo->exportObjectType == VK_EXPORT_METAL_OBJECT_TYPE_METAL_SHARED_EVENT_BIT_EXT)) {
-		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "vkCreateEvent(): MTLSharedEvent is not available with VkSemaphores that use MTLFence."));
+		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "vkCreateEvent(): MTLSharedEvent is not available with VkSemaphores that use implicit synchronization."));
 	}
 }
 
-MVKSemaphoreMTLFence::~MVKSemaphoreMTLFence() {
-	[_mtlFence release];
-}
+MVKSemaphoreSingleQueue::~MVKSemaphoreSingleQueue() = default;
 
 
 #pragma mark -
diff --git a/MoltenVK/MoltenVK/Utility/MVKEnvironment.cpp b/MoltenVK/MoltenVK/Utility/MVKEnvironment.cpp
index 30db5ea..f28af09 100644
--- a/MoltenVK/MoltenVK/Utility/MVKEnvironment.cpp
+++ b/MoltenVK/MoltenVK/Utility/MVKEnvironment.cpp
@@ -46,12 +46,11 @@
 	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (evCfg.defaultGPUCaptureScopeQueueFamilyIndex, MVK_CONFIG_DEFAULT_GPU_CAPTURE_SCOPE_QUEUE_FAMILY_INDEX);
 	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (evCfg.defaultGPUCaptureScopeQueueIndex,       MVK_CONFIG_DEFAULT_GPU_CAPTURE_SCOPE_QUEUE_INDEX);
 	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (evCfg.fastMathEnabled,                        MVK_CONFIG_FAST_MATH_ENABLED);
-
 	MVK_SET_FROM_ENV_OR_BUILD_INT32 (evCfg.logLevel,                               MVK_CONFIG_LOG_LEVEL);
 	MVK_SET_FROM_ENV_OR_BUILD_INT32 (evCfg.traceVulkanCalls,                       MVK_CONFIG_TRACE_VULKAN_CALLS);
 	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (evCfg.forceLowPowerGPU,                       MVK_CONFIG_FORCE_LOW_POWER_GPU);
 	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (evCfg.semaphoreUseMTLFence,                   MVK_ALLOW_METAL_FENCES);
-	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (evCfg.semaphoreUseMTLEvent,                   MVK_ALLOW_METAL_EVENTS);
+	MVK_SET_FROM_ENV_OR_BUILD_INT32 (evCfg.semaphoreSupportStyle,                  MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE);
 	MVK_SET_FROM_ENV_OR_BUILD_INT32 (evCfg.autoGPUCaptureScope,                    MVK_CONFIG_AUTO_GPU_CAPTURE_SCOPE);
 	MVK_SET_FROM_ENV_OR_BUILD_STRING(evCfg.autoGPUCaptureOutputFilepath,           MVK_CONFIG_AUTO_GPU_CAPTURE_OUTPUT_FILE, evGPUCapFileStrObj);
 	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (evCfg.texture1DAs2D,                          MVK_CONFIG_TEXTURE_1D_AS_2D);
@@ -61,7 +60,19 @@
 	MVK_SET_FROM_ENV_OR_BUILD_INT32 (evCfg.apiVersionToAdvertise,                  MVK_CONFIG_API_VERSION_TO_ADVERTISE);
 	MVK_SET_FROM_ENV_OR_BUILD_INT32 (evCfg.advertiseExtensions,                    MVK_CONFIG_ADVERTISE_EXTENSIONS);
 	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (evCfg.resumeLostDevice,                       MVK_CONFIG_RESUME_LOST_DEVICE);
-	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (evCfg.useMetalArgumentBuffers,                MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS);
+	MVK_SET_FROM_ENV_OR_BUILD_INT32 (evCfg.useMetalArgumentBuffers,                MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS);
+
+	// Deprected legacy VkSemaphore MVK_ALLOW_METAL_FENCES and MVK_ALLOW_METAL_EVENTS config.
+	// Legacy MVK_ALLOW_METAL_EVENTS is covered by MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE,
+	// but for backwards compatibility, if legacy MVK_ALLOW_METAL_EVENTS is explicitly
+	// disabled, disable semaphoreUseMTLEvent (aliased as semaphoreSupportStyle value
+	// MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_CALLBACK), and let mvkSetConfig() further
+	// process legacy behavior based on the value of legacy semaphoreUseMTLFence).
+	bool sem4UseMTLEvent;
+	MVK_SET_FROM_ENV_OR_BUILD_BOOL(sem4UseMTLEvent, MVK_ALLOW_METAL_EVENTS);
+	if ( !sem4UseMTLEvent ) {
+		evCfg.semaphoreUseMTLEvent = (MVKVkSemaphoreSupportStyle)false;		// Disabled. Also semaphoreSupportStyle MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_CALLBACK.
+	}
 
 	mvkSetConfig(evCfg);
 }
@@ -90,7 +101,14 @@
 	_mvkConfig.apiVersionToAdvertise = VK_MAKE_VERSION(VK_VERSION_MAJOR(_mvkConfig.apiVersionToAdvertise),
 													   VK_VERSION_MINOR(_mvkConfig.apiVersionToAdvertise),
 													   VK_HEADER_VERSION);
-	
+
+	// Deprecated legacy support for specific case where semaphoreUseMTLFence is enabled and legacy
+	// semaphoreUseMTLEvent (now aliased to semaphoreSupportStyle) is disabled. In this case the user
+	// had been using the legacy MTLFence, so use MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE now.
+	if (_mvkConfig.semaphoreUseMTLFence && !_mvkConfig.semaphoreUseMTLEvent) {
+		_mvkConfig.semaphoreSupportStyle = MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE;
+	}
+
 	// Set capture file path string
 	if (_mvkConfig.autoGPUCaptureOutputFilepath) {
 		_autoGPUCaptureOutputFile = _mvkConfig.autoGPUCaptureOutputFilepath;
diff --git a/MoltenVK/MoltenVK/Utility/MVKEnvironment.h b/MoltenVK/MoltenVK/Utility/MVKEnvironment.h
index 81d0def..307038d 100644
--- a/MoltenVK/MoltenVK/Utility/MVKEnvironment.h
+++ b/MoltenVK/MoltenVK/Utility/MVKEnvironment.h
@@ -231,15 +231,16 @@
 #endif
 
 /**
- * Allow the use of MTLFence or MTLEvent for VkSemaphore synchronization behaviour.
- * By default:
- *   - MVK_ALLOW_METAL_EVENTS is enabled
- *   - MVK_ALLOW_METAL_FENCES is disabled
- * */
-#ifndef MVK_ALLOW_METAL_EVENTS
+ * Determines the style used to implement Vulkan semaphore (VkSemaphore) functionality in Metal.
+ * By default, use Metal events, if availalble, on most platforms.
+ */
+#ifndef MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE
+#   define MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE    MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_METAL_EVENTS_WHERE_SAFE
+#endif
+#ifndef MVK_ALLOW_METAL_EVENTS		// Deprecated
 #   define MVK_ALLOW_METAL_EVENTS    1
 #endif
-#ifndef MVK_ALLOW_METAL_FENCES
+#ifndef MVK_ALLOW_METAL_FENCES		// Deprecated
 #   define MVK_ALLOW_METAL_FENCES    0
 #endif
 
@@ -280,5 +281,5 @@
 
 /** Support Metal argument buffers. Disabled by default. */
 #ifndef MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS
-#   define MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS    0
+#   define MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS    MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS_NEVER
 #endif
diff --git a/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/xcshareddata/xcschemes/MoltenVKShaderConverter.xcscheme b/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/xcshareddata/xcschemes/MoltenVKShaderConverter.xcscheme
index 422f9d2..fb864b4 100644
--- a/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/xcshareddata/xcschemes/MoltenVKShaderConverter.xcscheme
+++ b/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/xcshareddata/xcschemes/MoltenVKShaderConverter.xcscheme
@@ -109,7 +109,7 @@
             isEnabled = "NO">
          </CommandLineArgument>
          <CommandLineArgument
-            argument = "-XS"
+            argument = "-mab"
             isEnabled = "NO">
          </CommandLineArgument>
          <CommandLineArgument
@@ -117,6 +117,10 @@
             isEnabled = "YES">
          </CommandLineArgument>
          <CommandLineArgument
+            argument = "-XS"
+            isEnabled = "NO">
+         </CommandLineArgument>
+         <CommandLineArgument
             argument = "-p"
             isEnabled = "NO">
          </CommandLineArgument>
diff --git a/MoltenVKShaderConverter/MoltenVKShaderConverterTool/MoltenVKShaderConverterTool.cpp b/MoltenVKShaderConverter/MoltenVKShaderConverterTool/MoltenVKShaderConverterTool.cpp
index 8d64a59..f720f51 100644
--- a/MoltenVKShaderConverter/MoltenVKShaderConverterTool/MoltenVKShaderConverterTool.cpp
+++ b/MoltenVKShaderConverter/MoltenVKShaderConverterTool/MoltenVKShaderConverterTool.cpp
@@ -219,6 +219,9 @@
 	mslContext.options.mslOptions.platform = _mslPlatform;
 	mslContext.options.mslOptions.set_msl_version(_mslVersionMajor, _mslVersionMinor, _mslVersionPatch);
 	mslContext.options.shouldFlipVertexY = _shouldFlipVertexY;
+	mslContext.options.mslOptions.argument_buffers = _useMetalArgumentBuffers;
+	mslContext.options.mslOptions.force_active_argument_buffer_resources = _useMetalArgumentBuffers;
+	mslContext.options.mslOptions.pad_argument_buffer_resources = _useMetalArgumentBuffers;
 
 	SPIRVToMSLConverter spvConverter;
 	spvConverter.setSPIRV(spv);
@@ -364,6 +367,7 @@
     log("                       May be omitted for defaults (\"cp cmp comp compute kn kl krn kern kernel\").");
 	log("  -sx \"fileExtns\"    - List of SPIR-V shader file extensions.");
 	log("                       May be omitted for defaults (\"spv spirv\").");
+	log("  -mab               - Use Metal Argument Buffers to hold resources in the shaders.");
 	log("  -l                 - Log the conversion results to the console (to aid debugging).");
 	log("  -p                 - Log the performance of the shader conversions.");
 	log("  -q                 - Quiet mode. Stops logging of informational messages.");
@@ -419,18 +423,25 @@
 	_shouldReportPerformance = false;
 	_shouldOutputAsHeaders = false;
 	_quietMode = false;
+	_useMetalArgumentBuffers = false;
 
-	_mslVersionMajor = 2;
-
-	if (mvkOSVersionIsAtLeast(12.0)) {
+	if (mvkOSVersionIsAtLeast(13.0)) {
+		_mslVersionMajor = 3;
+		_mslVersionMinor = 0;
+	} else if (mvkOSVersionIsAtLeast(12.0)) {
+		_mslVersionMajor = 2;
 		_mslVersionMinor = 4;
 	} else if (mvkOSVersionIsAtLeast(11.0)) {
+		_mslVersionMajor = 2;
 		_mslVersionMinor = 3;
 	} else if (mvkOSVersionIsAtLeast(10.15)) {
+		_mslVersionMajor = 2;
 		_mslVersionMinor = 2;
 	} else if (mvkOSVersionIsAtLeast(10.14)) {
+		_mslVersionMajor = 2;
 		_mslVersionMinor = 1;
 	} else if (mvkOSVersionIsAtLeast(10.13)) {
+		_mslVersionMajor = 2;
 		_mslVersionMinor = 0;
 	} else if (mvkOSVersionIsAtLeast(10.12)) {
 		_mslVersionMajor = 1;
@@ -633,6 +644,11 @@
 			continue;
 		}
 
+		if(equal(arg, "-mab", true)) {
+			_useMetalArgumentBuffers = true;
+			continue;
+		}
+
 		if(equal(arg, "-l", true)) {
 			_shouldLogConversions = true;
 			continue;
diff --git a/MoltenVKShaderConverter/MoltenVKShaderConverterTool/MoltenVKShaderConverterTool.h b/MoltenVKShaderConverter/MoltenVKShaderConverterTool/MoltenVKShaderConverterTool.h
index 917425f..179ce3b 100644
--- a/MoltenVKShaderConverter/MoltenVKShaderConverterTool/MoltenVKShaderConverterTool.h
+++ b/MoltenVKShaderConverter/MoltenVKShaderConverterTool/MoltenVKShaderConverterTool.h
@@ -123,6 +123,7 @@
 		bool _shouldReportPerformance;
 		bool _shouldOutputAsHeaders;
 		bool _quietMode;
+		bool _useMetalArgumentBuffers;
 	};
 
 
diff --git a/Scripts/runcts b/Scripts/runcts
index f408a75..6a8761d 100755
--- a/Scripts/runcts
+++ b/Scripts/runcts
@@ -106,8 +106,9 @@
 # editing below, or can be set before calling this script.
 export MVK_CONFIG_RESUME_LOST_DEVICE=1
 export MVK_CONFIG_FAST_MATH_ENABLED=1
-export MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=0
+export MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=0  #(2 = VK_EXT_descriptor_indexing enabled)
 export MVK_CONFIG_FORCE_LOW_POWER_GPU=0
+export MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE=2  #(2 = MTLEvents always)
 
 
 # -------------- Operation --------------------