Support optional use of MTLFence for Vulkan semaphores via the
MVK_ALLOW_METAL_FENCES environment variable.

Refactor MVKSemaphore class into separate MVKSemaphoreMTLFence,
MVKSemaphoreMTLEvent, and MVKSemaphoreEmulated subclasses.
Add MVK_ALLOW_METAL_FENCES environment variable to optionally
enable using MTLFence for Vulkan semaphores.
Add MVKPhysicalDeviceMetalFeatures::fences to track MTLFence availability.
Update VK_MVK_MOLTENVK_SPEC_VERSION to version 22.
diff --git a/Docs/Whats_New.md b/Docs/Whats_New.md
index 1d4a4b0..6ad0843 100644
--- a/Docs/Whats_New.md
+++ b/Docs/Whats_New.md
@@ -33,6 +33,7 @@
 - `vkCmdClearImage():` Set error if attempt made to clear 1D image, and fix validation of depth attachment formats.
 - `vkCreateRenderPass():` Return `VK_ERROR_FORMAT_NOT_SUPPORTED` if format not supported.
 - `vkCmdFillBuffer():` Improve performance 150x by using parallelism more effectively.
+- Support optional use of `MTLFence` for Vulkan semaphores via the `MVK_ALLOW_METAL_FENCES` environment variable.
 - Remove error logging on `VK_TIMEOUT` of `VkSemaphore` and `VkFence`.
 - Consolidate the various linkable objects into a `MVKLinkableMixin` template base class.
 - Use `MVKVector` whenever possible in MoltenVK, especially within render loop.
@@ -43,6 +44,7 @@
   `MVKConfiguration::presentWithCommandBuffer` is now obsolete.
 - Don't use `MTLCommandBuffer push/popDebugGroup` if not available.
 - Add ability to automatically cause an *Xcode* GPU capture without developer intervention.
+- Update `VK_MVK_MOLTENVK_SPEC_VERSION` to version 22.
 
 
 
diff --git a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
index 39ffc35..fbd7978 100644
--- a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
+++ b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
@@ -55,7 +55,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            21
+#define VK_MVK_MOLTENVK_SPEC_VERSION            22
 #define VK_MVK_MOLTENVK_EXTENSION_NAME          "VK_MVK_moltenvk"
 
 /**
@@ -114,9 +114,11 @@
  * 3. Setting the MVK_CONFIG_FORCE_LOW_POWER_GPU runtime environment variable or MoltenVK compile-time
  *    build setting to 1 will force MoltenVK to use a low-power GPU, if one is availble on the device.
  *
- * 4. Setting the MVK_ALLOW_METAL_EVENTS runtime environment variable or MoltenVK compile-time build
- *    setting to 1 will cause MoltenVK to use Metal events, if they are available on the device, for
- *    for VkSemaphore sychronization behaviour. This is disabled by default.
+ * 4. Setting the MVK_ALLOW_METAL_FENCES or MVK_ALLOW_METAL_EVENTS runtime environment variable
+ *    or MoltenVK compile-time build setting to 1 will cause MoltenVK to use MTLFence or MTLEvent
+ *    if they are available on the device, for VkSemaphore sychronization behaviour.
+ *    If both variables are set, MVK_ALLOW_METAL_FENCES takes priority over MVK_ALLOW_METAL_EVENTS.
+ *    Both options are disabled by default.
  *
  * 5. The MVK_CONFIG_AUTO_GPU_CAPTURE_SCOPE runtime environment variable or MoltenVK compile-time
  *    build setting controls whether Xcode should run an automatic GPU capture without the user
@@ -206,7 +208,7 @@
 	 * buffers (VK_COMMAND_BUFFER_LEVEL_SECONDARY), or for primary command buffers that are intended
 	 * to be submitted to multiple queues concurrently (VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT).
 	 *
-	 * When enabling this features, be aware that one Metal command buffer is required for each Vulkan
+	 * When enabling this feature, be aware that one Metal command buffer is required for each Vulkan
 	 * command buffer. Depending on the number of command buffers that you use, you may also need to
 	 * change the value of the maxActiveMetalCommandBuffersPerQueue setting.
 	 *
@@ -538,12 +540,13 @@
 	VkBool32 arrayOfSamplers;			 	  	/**< If true, arrays of texture samplers is supported. */
 	MTLLanguageVersion mslVersionEnum;			/**< The version of the Metal Shading Language available on this device, as a Metal enumeration. */
 	VkBool32 depthSampleCompare;				/**< If true, depth texture samplers support the comparison of the pixel value against a reference value. */
-	VkBool32 events;							/**< If true, Metal synchronization events are supported. */
+	VkBool32 events;							/**< If true, Metal synchronization events (MTLEvent) are supported. */
 	VkBool32 memoryBarriers;					/**< If true, full memory barriers within Metal render passes are supported. */
 	VkBool32 multisampleLayeredRendering;       /**< If true, layered rendering to multiple multi-sampled cube or texture array layers is supported. */
 	VkBool32 stencilFeedback;					/**< If true, fragment shaders that write to [[stencil]] outputs are supported. */
 	VkBool32 textureBuffers;					/**< If true, textures of type MTLTextureTypeBuffer are supported. */
 	VkBool32 postDepthCoverage;					/**< If true, coverage masks in fragment shaders post-depth-test are supported. */
+	VkBool32 fences;							/**< If true, Metal synchronization fences (MTLFence) are supported. */
 } MVKPhysicalDeviceMetalFeatures;
 
 /**
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
index 893c47f..6649ba0 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
@@ -659,10 +659,15 @@
     /** Performance statistics. */
     MVKPerformanceStatistics _performanceStatistics;
 
-	// Indicates whether semaphores should use MTLEvents if available.
+	// Indicates whether semaphores should use a MTLFence if available.
+	// Set by the MVK_ALLOW_METAL_FENCES environment variable if MTLFences are available.
+	// This should be a temporary fix after some repair to semaphore handling.
+	bool _useMTLFenceForSemaphores;
+
+	// Indicates whether semaphores should use a MTLEvent if available.
 	// Set by the MVK_ALLOW_METAL_EVENTS environment variable if MTLEvents are available.
 	// This should be a temporary fix after some repair to semaphore handling.
-	bool _useMTLEventsForSemaphores;
+	bool _useMTLEventForSemaphores;
 
 
 #pragma mark Construction
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
index 3629643..7a1c50a 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
@@ -609,6 +609,9 @@
 // In order to allow a Metal command buffer to be prefilled before it is formally submitted to
 // a Vulkan queue, we need to enforce that each Vulkan queue family can have only one Metal queue.
 // In order to provide parallel queue operations, we therefore provide multiple queue families.
+// In addition, Metal queues are always general purpose, so the default behaviour is for all
+// queue families to support graphics + compute + transfer, unless the app indicates it
+// requires queue family specialization.
 MVKVector<MVKQueueFamily*>& MVKPhysicalDevice::getQueueFamilies() {
 	if (_queueFamilies.empty()) {
 		VkQueueFamilyProperties qfProps;
@@ -773,6 +776,7 @@
 		_metalFeatures.mslVersionEnum = MTLLanguageVersion1_2;
         _metalFeatures.shaderSpecialization = true;
         _metalFeatures.stencilViews = true;
+		_metalFeatures.fences = true;
     }
 
     if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily1_v4] ) {
@@ -839,6 +843,7 @@
 		_metalFeatures.arrayOfTextures = true;
 		_metalFeatures.arrayOfSamplers = true;
 		_metalFeatures.presentModeImmediate = true;
+		_metalFeatures.fences = true;
     }
 
     if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_macOS_GPUFamily1_v4] ) {
@@ -1906,7 +1911,13 @@
 
 MVKSemaphore* MVKDevice::createSemaphore(const VkSemaphoreCreateInfo* pCreateInfo,
 										 const VkAllocationCallbacks* pAllocator) {
-	return new MVKSemaphore(this, pCreateInfo);
+	if (_useMTLFenceForSemaphores) {
+		return new MVKSemaphoreMTLFence(this, pCreateInfo);
+	} else if (_useMTLEventForSemaphores) {
+		return new MVKSemaphoreMTLEvent(this, pCreateInfo);
+	} else {
+		return new MVKSemaphoreEmulated(this, pCreateInfo);
+	}
 }
 
 void MVKDevice::destroySemaphore(MVKSemaphore* mvkSem4,
@@ -2355,11 +2366,15 @@
 	_pProperties = &_physicalDevice->_properties;
 	_pMemoryProperties = &_physicalDevice->_memoryProperties;
 
-	_useMTLEventsForSemaphores = MVK_ALLOW_METAL_EVENTS;
-	if (_pMetalFeatures->events) {
-		MVK_SET_FROM_ENV_OR_BUILD_BOOL(_useMTLEventsForSemaphores, MVK_ALLOW_METAL_EVENTS);
+	_useMTLFenceForSemaphores = false;
+	if (_pMetalFeatures->fences) {
+		MVK_SET_FROM_ENV_OR_BUILD_BOOL(_useMTLFenceForSemaphores, MVK_ALLOW_METAL_FENCES);
 	}
-	MVKLogInfo("%s MTLEvent for semaphores.", _useMTLEventsForSemaphores ? "Using" : "NOT using");
+	_useMTLEventForSemaphores = false;
+	if (_pMetalFeatures->events) {
+		MVK_SET_FROM_ENV_OR_BUILD_BOOL(_useMTLEventForSemaphores, MVK_ALLOW_METAL_EVENTS);
+	}
+	MVKLogInfo("Using %s for semaphores.", _useMTLFenceForSemaphores ? "MTLFence" : (_useMTLEventForSemaphores ? "MTLEvent" : "emulation"));
 
 #if MVK_MACOS
 	// If we have selected a high-power GPU and want to force the window system
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
index ff35854..a4716f0 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
@@ -169,7 +169,7 @@
 		// impose unacceptable performance costs to handle this particular case.
 		@autoreleasepool {
 			MVKSemaphore* mvkSem = signaler.first;
-			id<MTLCommandBuffer> mtlCmdBuff = (mvkSem && mvkSem->isUsingMTLEvent()
+			id<MTLCommandBuffer> mtlCmdBuff = (mvkSem && mvkSem->isUsingCommandEncoding()
 											   ? [_device->getQueue()->getMTLCommandQueue() commandBufferWithUnretainedReferences]
 											   : nil);
 			signal(signaler, mtlCmdBuff);
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSync.h b/MoltenVK/MoltenVK/GPUObjects/MVKSync.h
index 1c7bcc5..95c90c1 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSync.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSync.h
@@ -109,7 +109,7 @@
 #pragma mark -
 #pragma mark MVKSemaphore
 
-/** Represents a Vulkan semaphore. */
+/** Abstract class that represents a Vulkan semaphore. */
 class MVKSemaphore : public MVKVulkanAPIDeviceObject {
 
 public:
@@ -123,54 +123,107 @@
 	/**
 	 * Wait for this semaphore to be signaled.
 	 *
-	 * If this instance is using MTLEvent AND the mtlCmdBuff is not nil, a MTLEvent wait
-	 * is encoded on the mtlCmdBuff, and this call returns immediately. Otherwise, if this
-	 * instance is NOT using MTLEvent, AND the mtlCmdBuff is nil, this call blocks
+	 * If the subclass uses command encoding AND the mtlCmdBuff is not nil, a wait
+	 * is encoded on the mtlCmdBuff, and this call returns immediately. Otherwise, if the
+	 * subclass does NOT use command encoding, AND the mtlCmdBuff is nil, this call blocks
 	 * indefinitely until this semaphore is signaled. Other combinations do nothing.
 	 *
-	 * This design allows this function to be blindly called twice, from different points in
-	 * the code path, once with a mtlCmdBuff to support encoding a wait on the command buffer
-	 * if this instance supports MTLEvents, and once without a mtlCmdBuff, at the point in
-	 * the code path where the code should block if this instance does not support MTLEvents.
+	 * This design allows this function to be blindly called twice, from different points in the
+	 * code path, once with a mtlCmdBuff to support encoding a wait on the command buffer if the
+	 * subclass supports command encoding, and once without a mtlCmdBuff, at the point in the
+	 * code path where the code should block if the subclass does not support command encoding.
 	 */
-	void encodeWait(id<MTLCommandBuffer> mtlCmdBuff);
+	virtual void encodeWait(id<MTLCommandBuffer> mtlCmdBuff) = 0;
 
 	/**
 	 * Signals this semaphore.
 	 *
-	 * If this instance is using MTLEvent AND the mtlCmdBuff is not nil, a MTLEvent signal
-	 * is encoded on the mtlCmdBuff. Otherwise, if this instance is NOT using MTLEvent,
+	 * If the subclass uses command encoding AND the mtlCmdBuff is not nil, a signal is
+	 * encoded on the mtlCmdBuff. Otherwise, if the subclass does NOT use command encoding,
 	 * AND the mtlCmdBuff is nil, this call immediately signals any waiting calls.
 	 * Either way, this call returns immediately. Other combinations do nothing.
 	 *
-	 * This design allows this function to be blindly called twice, from different points in
-	 * the code path, once with a mtlCmdBuff to support encoding a signal on the command buffer
-	 * if this instance supports MTLEvents, and once without a mtlCmdBuff, at the point in
-	 * the code path where the code should immediately signal any existing waits, if this
-	 * instance does not support MTLEvents.
+	 * This design allows this function to be blindly called twice, from different points in the
+	 * code path, once with a mtlCmdBuff to support encoding a wait on the command buffer if the
+	 * subclass supports command encoding, and once without a mtlCmdBuff, at the point in the
+	 * code path where the code should block if the subclass does not support command encoding.
 	 */
-	void encodeSignal(id<MTLCommandBuffer> mtlCmdBuff);
+	virtual void encodeSignal(id<MTLCommandBuffer> mtlCmdBuff) = 0;
 
-	/** Returns whether this semaphore is using a MTLEvent. */
-	bool isUsingMTLEvent() { return _mtlEvent != nil; }
+	/** Returns whether this semaphore uses command encoding. */
+	virtual bool isUsingCommandEncoding() = 0;
 
 
 #pragma mark Construction
 
-    MVKSemaphore(MVKDevice* device, const VkSemaphoreCreateInfo* pCreateInfo);
-
-    ~MVKSemaphore() override;
+    MVKSemaphore(MVKDevice* device, const VkSemaphoreCreateInfo* pCreateInfo) : MVKVulkanAPIDeviceObject(device) {}
 
 protected:
 	void propogateDebugName() override {}
 
-	MVKSemaphoreImpl _blocker;
+};
+
+
+#pragma mark -
+#pragma mark MVKSemaphoreMTLFence
+
+/** An MVKSemaphore that uses MTLFence to provide synchronization. */
+class MVKSemaphoreMTLFence : public MVKSemaphore {
+
+public:
+	void encodeWait(id<MTLCommandBuffer> mtlCmdBuff) override;
+	void encodeSignal(id<MTLCommandBuffer> mtlCmdBuff) override;
+	bool isUsingCommandEncoding() override { return true; }
+
+	MVKSemaphoreMTLFence(MVKDevice* device, const VkSemaphoreCreateInfo* pCreateInfo);
+
+	~MVKSemaphoreMTLFence() override;
+
+protected:
+	id<MTLFence> _mtlFence;
+};
+
+
+#pragma mark -
+#pragma mark MVKSemaphoreMTLEvent
+
+/** An MVKSemaphore that uses MTLEvent to provide synchronization. */
+class MVKSemaphoreMTLEvent : public MVKSemaphore {
+
+public:
+	void encodeWait(id<MTLCommandBuffer> mtlCmdBuff) override;
+	void encodeSignal(id<MTLCommandBuffer> mtlCmdBuff) override;
+	bool isUsingCommandEncoding() override { return true; }
+
+	MVKSemaphoreMTLEvent(MVKDevice* device, const VkSemaphoreCreateInfo* pCreateInfo);
+
+	~MVKSemaphoreMTLEvent() override;
+
+protected:
 	id<MTLEvent> _mtlEvent;
 	std::atomic<uint64_t> _mtlEventValue;
 };
 
 
 #pragma mark -
+#pragma mark MVKSemaphoreEmulated
+
+/** An MVKSemaphore that uses CPU synchronization to provide synchronization functionality. */
+class MVKSemaphoreEmulated : public MVKSemaphore {
+
+public:
+	void encodeWait(id<MTLCommandBuffer> mtlCmdBuff) override;
+	void encodeSignal(id<MTLCommandBuffer> mtlCmdBuff) override;
+	bool isUsingCommandEncoding() override { return false; }
+
+	MVKSemaphoreEmulated(MVKDevice* device, const VkSemaphoreCreateInfo* pCreateInfo);
+
+protected:
+	MVKSemaphoreImpl _blocker;
+};
+
+
+#pragma mark -
 #pragma mark MVKFence
 
 /** Represents a Vulkan fence. */
@@ -267,7 +320,7 @@
 #pragma mark -
 #pragma mark MVKEvent
 
-/** Represents a Vulkan semaphore. */
+/** Abstract class that represents a Vulkan event. */
 class MVKEvent : public MVKVulkanAPIDeviceObject {
 
 public:
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm
index 2d4a62a..a7ad544 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm
@@ -78,36 +78,73 @@
 
 
 #pragma mark -
-#pragma mark MVKSemaphore
+#pragma mark MVKSemaphoreMTLEvent
 
-void MVKSemaphore::encodeWait(id<MTLCommandBuffer> mtlCmdBuff) {
-	if (_mtlEvent && mtlCmdBuff) {
-		[mtlCmdBuff encodeWaitForEvent: _mtlEvent value: _mtlEventValue++];
-	} else if ( !_mtlEvent && !mtlCmdBuff ) {
-		_blocker.wait(UINT64_MAX, true);
-	}
+// Could use any encoder. Assume BLIT is fastest and lightest.
+// Nil mtlCmdBuff will do nothing.
+void MVKSemaphoreMTLFence::encodeWait(id<MTLCommandBuffer> mtlCmdBuff) {
+	id<MTLBlitCommandEncoder> mtlCmdEnc = mtlCmdBuff.blitCommandEncoder;
+	[mtlCmdEnc waitForFence: _mtlFence];
+	[mtlCmdEnc endEncoding];
 }
 
-void MVKSemaphore::encodeSignal(id<MTLCommandBuffer> mtlCmdBuff) {
-	if (_mtlEvent && mtlCmdBuff) {
-		[mtlCmdBuff encodeSignalEvent: _mtlEvent value: _mtlEventValue];
-	} else if ( !_mtlEvent && !mtlCmdBuff ) {
-		 _blocker.release();
-	}
+// Could use any encoder. Assume BLIT is fastest and lightest.
+// Nil mtlCmdBuff will do nothing.
+void MVKSemaphoreMTLFence::encodeSignal(id<MTLCommandBuffer> mtlCmdBuff) {
+	id<MTLBlitCommandEncoder> mtlCmdEnc = mtlCmdBuff.blitCommandEncoder;
+	[mtlCmdEnc updateFence: _mtlFence];
+	[mtlCmdEnc endEncoding];
 }
 
-MVKSemaphore::MVKSemaphore(MVKDevice* device, const VkSemaphoreCreateInfo* pCreateInfo) :
-	MVKVulkanAPIDeviceObject(device),
-	_blocker(false, 1),
-	_mtlEvent(device->_useMTLEventsForSemaphores ? [device->getMTLDevice() newEvent] : nil),
+MVKSemaphoreMTLFence::MVKSemaphoreMTLFence(MVKDevice* device, const VkSemaphoreCreateInfo* pCreateInfo) :
+	MVKSemaphore(device, pCreateInfo),
+	_mtlFence([device->getMTLDevice() newFence]) {}		//retained
+
+MVKSemaphoreMTLFence::~MVKSemaphoreMTLFence() {
+	[_mtlFence release];
+}
+
+
+#pragma mark -
+#pragma mark MVKSemaphoreMTLEvent
+
+// Nil mtlCmdBuff will do nothing.
+void MVKSemaphoreMTLEvent::encodeWait(id<MTLCommandBuffer> mtlCmdBuff) {
+	[mtlCmdBuff encodeWaitForEvent: _mtlEvent value: _mtlEventValue++];
+}
+
+// Nil mtlCmdBuff will do nothing.
+void MVKSemaphoreMTLEvent::encodeSignal(id<MTLCommandBuffer> mtlCmdBuff) {
+	[mtlCmdBuff encodeSignalEvent: _mtlEvent value: _mtlEventValue];
+}
+
+MVKSemaphoreMTLEvent::MVKSemaphoreMTLEvent(MVKDevice* device, const VkSemaphoreCreateInfo* pCreateInfo) :
+	MVKSemaphore(device, pCreateInfo),
+	_mtlEvent([device->getMTLDevice() newEvent]),	//retained
 	_mtlEventValue(1) {}
 
-MVKSemaphore::~MVKSemaphore() {
+MVKSemaphoreMTLEvent::~MVKSemaphoreMTLEvent() {
     [_mtlEvent release];
 }
 
 
 #pragma mark -
+#pragma mark MVKSemaphoreEmulated
+
+void MVKSemaphoreEmulated::encodeWait(id<MTLCommandBuffer> mtlCmdBuff) {
+	if ( !mtlCmdBuff ) { _blocker.wait(UINT64_MAX, true); }
+}
+
+void MVKSemaphoreEmulated::encodeSignal(id<MTLCommandBuffer> mtlCmdBuff) {
+	if ( !mtlCmdBuff ) { _blocker.release(); }
+}
+
+MVKSemaphoreEmulated::MVKSemaphoreEmulated(MVKDevice* device, const VkSemaphoreCreateInfo* pCreateInfo) :
+	MVKSemaphore(device, pCreateInfo),
+	_blocker(false, 1) {}
+
+
+#pragma mark -
 #pragma mark MVKFence
 
 void MVKFence::addSitter(MVKFenceSitter* fenceSitter) {
diff --git a/MoltenVK/MoltenVK/Utility/MVKEnvironment.h b/MoltenVK/MoltenVK/Utility/MVKEnvironment.h
index 2114446..8ae135c 100644
--- a/MoltenVK/MoltenVK/Utility/MVKEnvironment.h
+++ b/MoltenVK/MoltenVK/Utility/MVKEnvironment.h
@@ -157,7 +157,10 @@
 #   define MVK_CONFIG_FORCE_LOW_POWER_GPU    0
 #endif
 
-/** Allow the use of Metal events for VkSemaphore synchronization behaviour. Disabled by default. */
+/** Allow the use of MTLFence or MTLEvent for VkSemaphore synchronization behaviour. Disabled by default. */
+#ifndef MVK_ALLOW_METAL_FENCES
+#   define MVK_ALLOW_METAL_FENCES    0
+#endif
 #ifndef MVK_ALLOW_METAL_EVENTS
 #   define MVK_ALLOW_METAL_EVENTS    0
 #endif