Resolve and merge conflicts.
diff --git a/MoltenVK/MoltenVK/API/mvk_datatypes.h b/MoltenVK/MoltenVK/API/mvk_datatypes.h
index e7965bd..41cbf7a 100644
--- a/MoltenVK/MoltenVK/API/mvk_datatypes.h
+++ b/MoltenVK/MoltenVK/API/mvk_datatypes.h
@@ -390,18 +390,24 @@
 /** Returns the size, in bytes, of a vertex index of the specified type. */
 size_t mvkMTLIndexTypeSizeInBytes(MTLIndexType mtlIdxType);
 
-/** Returns the MVKShaderStage corresponding to the specified Vulkan VkShaderStageFlagBits. */
+/** Returns the MoltenVK MVKShaderStage corresponding to the specified Vulkan VkShaderStageFlagBits. */
 MVKShaderStage mvkShaderStageFromVkShaderStageFlagBits(VkShaderStageFlagBits vkStage);
 
-/** Returns the VkShaderStageFlagBits corresponding to the specified MoltenVK MVKShaderStage. */
+/** Returns the Vulkan VkShaderStageFlagBits corresponding to the specified MoltenVK MVKShaderStage. */
 VkShaderStageFlagBits mvkVkShaderStageFlagBitsFromMVKShaderStage(MVKShaderStage mvkStage);
 
-/** Returns the MTLWinding corresponding to the specified spv::ExecutionMode. */
+/** Returns the Metal MTLWinding corresponding to the specified SPIR-V spv::ExecutionMode. */
 MTLWinding mvkMTLWindingFromSpvExecutionMode(uint32_t spvMode);
 
-/** Returns the MTLTessellationPartitionMode corresponding to the specified spv::ExecutionMode. */
+/** Returns the Metal MTLTessellationPartitionMode corresponding to the specified SPIR-V spv::ExecutionMode. */
 MTLTessellationPartitionMode mvkMTLTessellationPartitionModeFromSpvExecutionMode(uint32_t spvMode);
 
+/** Returns the combination of Metal MTLRenderStage bits corresponding to the specified Vulkan VkPiplineStageFlags. */
+MTLRenderStages mvkMTLRenderStagesFromVkPipelineStageFlags(VkPipelineStageFlags vkStages);
+
+/** Returns the combination of Metal MTLBarrierScope bits corresponding to the specified Vulkan VkAccessFlags. */
+MTLBarrierScope mvkMTLBarrierScopeFromVkAccessFlags(VkAccessFlags vkAccess);
+
 #pragma mark -
 #pragma mark Geometry conversions
 
diff --git a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
index 04a1a65..381fd4e 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            19
+#define VK_MVK_MOLTENVK_SPEC_VERSION            20
 #define VK_MVK_MOLTENVK_EXTENSION_NAME          "VK_MVK_moltenvk"
 
 /**
@@ -524,6 +524,8 @@
 	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 memoryBarriers;					/**< If true, full memory barriers within Metal render passes are supported. */
 } MVKPhysicalDeviceMetalFeatures;
 
 /**
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.mm b/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.mm
index 580d0e2..5ce849e 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.mm
@@ -66,8 +66,36 @@
 
 #if MVK_MACOS
     // Calls below invoke MTLBlitCommandEncoder so must apply this first
-	if ( !(_memoryBarriers.empty() && _imageMemoryBarriers.empty()) ) {
-		[cmdEncoder->_mtlRenderEncoder textureBarrier];
+	if ( getDevice()->_pMetalFeatures->memoryBarriers ) {
+		MTLRenderStages srcStages = mvkMTLRenderStagesFromVkPipelineStageFlags(_srcStageMask);
+		MTLRenderStages dstStages = mvkMTLRenderStagesFromVkPipelineStageFlags(_dstStageMask);
+		for (auto& mb : _memoryBarriers) {
+			MTLBarrierScope scope = mvkMTLBarrierScopeFromVkAccessFlags(mb.dstAccessMask);
+			scope |= mvkMTLBarrierScopeFromVkAccessFlags(mb.srcAccessMask);
+			[cmdEncoder->_mtlRenderEncoder memoryBarrierWithScope: scope
+													  afterStages: srcStages
+													 beforeStages: dstStages];
+		}
+		std::vector<id<MTLResource>> resources;
+		resources.reserve(_bufferMemoryBarriers.size() + _imageMemoryBarriers.size());
+		for (auto& mb : _bufferMemoryBarriers) {
+			auto* mvkBuff = (MVKBuffer*)mb.buffer;
+			resources.push_back(mvkBuff->getMTLBuffer());
+		}
+		for (auto& mb : _imageMemoryBarriers) {
+			auto* mvkImg = (MVKImage*)mb.image;
+			resources.push_back(mvkImg->getMTLTexture());
+		}
+		if ( !resources.empty() ) {
+			[cmdEncoder->_mtlRenderEncoder memoryBarrierWithResources: resources.data()
+																count: resources.size()
+														  afterStages: srcStages
+														 beforeStages: dstStages];
+		}
+	} else {
+		if ( !(_memoryBarriers.empty() && _imageMemoryBarriers.empty()) ) {
+			[cmdEncoder->_mtlRenderEncoder textureBarrier];
+		}
 	}
 #endif
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
index 4e652b1..58fd374 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
@@ -736,6 +736,7 @@
 
 	if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily1_v5] ) {
 		_metalFeatures.mslVersionEnum = MTLLanguageVersion2_1;
+		_metalFeatures.events = true;
 	}
 
 	if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily3_v1] ) {
@@ -790,8 +791,10 @@
     }
 
     if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_macOS_GPUFamily1_v4] ) {
-		_metalFeatures.mslVersionEnum = MTLLanguageVersion2_1;
+        _metalFeatures.mslVersionEnum = MTLLanguageVersion2_1;
         _metalFeatures.multisampleArrayTextures = true;
+        _metalFeatures.events = true;
+        _metalFeatures.memoryBarriers = true;
     }
 
 #endif
@@ -1931,6 +1934,8 @@
 								   VkMemoryBarrier* pMemoryBarrier,
                                    MVKCommandEncoder* cmdEncoder,
                                    MVKCommandUse cmdUse) {
+	if (!mvkIsAnyFlagEnabled(dstStageMask, VK_PIPELINE_STAGE_HOST_BIT) ||
+		!mvkIsAnyFlagEnabled(pMemoryBarrier->dstAccessMask, VK_ACCESS_HOST_READ_BIT) ) { return; }
 	lock_guard<mutex> lock(_rezLock);
     for (auto& rez : _resources) {
 		rez->applyMemoryBarrier(srcStageMask, dstStageMask, pMemoryBarrier, cmdEncoder, cmdUse);
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
index 00c5b63..2092f45 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
@@ -17,6 +17,7 @@
  */
 
 #include "MVKImage.h"
+#include "MVKQueue.h"
 #include "MVKSwapchain.h"
 #include "MVKCommandBuffer.h"
 #include "mvk_datatypes.hpp"
@@ -1084,6 +1085,19 @@
 	if (_availability.isAvailable) {
 		_availability.isAvailable = false;
 		signal(signaler);
+		if (_device->_pMetalFeatures->events) {
+			// Unfortunately, we can't assume we have an MTLSharedEvent here.
+			// This means we need to execute a command on the device to signal
+			// the semaphore. Alternatively, we could always use an MTLSharedEvent,
+			// but that might impose unacceptable performance costs just to handle
+			// this one case.
+			MVKQueue* queue = _device->getQueue(0, 0);	
+			id<MTLCommandQueue> mtlQ = queue->getMTLCommandQueue();
+			id<MTLCommandBuffer> mtlCmdBuff = [mtlQ commandBufferWithUnretainedReferences];
+			[mtlCmdBuff enqueue];
+			signaler.first->encodeSignal(mtlCmdBuff);
+			[mtlCmdBuff commit];
+		}
 		_preSignaled = signaler;
 	} else {
 		_availabilitySignalers.push_back(signaler);
@@ -1095,7 +1109,7 @@
 
 // Signal either or both of the semaphore and fence in the specified tracker pair.
 void MVKSwapchainImage::signal(MVKSwapchainSignaler& signaler) {
-	if (signaler.first) { signaler.first->signal(); }
+	if (signaler.first && !_device->_pMetalFeatures->events) { signaler.first->signal(); }
 	if (signaler.second) { signaler.second->signal(); }
 }
 
@@ -1148,6 +1162,10 @@
     if (mtlCmdBuff) {
         [mtlCmdBuff presentDrawable: mtlDrawable];
         resetMetalSurface();
+        if (_device->_pMetalFeatures->events && !_availabilitySignalers.empty()) {
+            // Signal the semaphore device-side.
+            _availabilitySignalers.front().first->encodeSignal(mtlCmdBuff);
+        }
         [mtlCmdBuff addCompletedHandler: ^(id<MTLCommandBuffer> mcb) { makeAvailable(); }];
     } else {
         [mtlDrawable present];
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.h b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.h
index a8b1d30..e30d5ff 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.h
@@ -162,7 +162,7 @@
 	/** Returns the Vulkan API opaque object controlling this object. */
 	MVKVulkanAPIObject* getVulkanAPIObject() override { return _queue->getVulkanAPIObject(); }
 
-	/** 
+	/**
 	 * Executes this action on the queue and then disposes of this instance.
 	 *
 	 * Upon completion of this function, no further calls should be made to this instance.
@@ -212,6 +212,7 @@
 	MVKFence* _fence;
     MVKCommandUse _cmdBuffUse;
 	id<MTLCommandBuffer> _activeMTLCommandBuffer;
+	bool _isSignalingSemaphores;
 };
 
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm
index 8e14d82..4efac25 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm
@@ -221,12 +221,30 @@
 
 	_queue->_submissionCaptureScope->beginScope();
 
-    // Submit each command buffer.
+	MVKDevice* mvkDev = _queue->getDevice();
+
+	// If the device supports it, wait for any semaphores on the device.
+	if (mvkDev->_pMetalFeatures->events && _isAwaitingSemaphores) {
+		_isAwaitingSemaphores = false;
+		for (auto* ws : _waitSemaphores) {
+			ws->encodeWait(getActiveMTLCommandBuffer());
+		}
+	}
+
+	// Submit each command buffer.
 	for (auto& cb : _cmdBuffers) { cb->submit(this); }
 
 	// If a fence or semaphores were provided, ensure that a MTLCommandBuffer
 	// is available to trigger them, in case no command buffers were provided.
-	if (_fence || !_signalSemaphores.empty()) { getActiveMTLCommandBuffer(); }
+	if (_fence || _isSignalingSemaphores) { getActiveMTLCommandBuffer(); }
+
+	// If the device supports it, signal all semaphores on the device.
+	if (mvkDev->_pMetalFeatures->events && _isSignalingSemaphores) {
+		_isSignalingSemaphores = false;
+		for (auto* ss : _signalSemaphores) {
+			ss->encodeSignal(getActiveMTLCommandBuffer());
+		}
+	}
 
 	// Commit the last MTLCommandBuffer.
 	// Nothing after this because callback might destroy this instance before this function ends.
@@ -283,7 +301,9 @@
 	_queue->_submissionCaptureScope->endScope();
 
 	// Signal each of the signal semaphores.
-    for (auto& ss : _signalSemaphores) { ss->signal(); }
+    if (_isSignalingSemaphores) {
+        for (auto& ss : _signalSemaphores) { ss->signal(); }
+    }
 
 	// If a fence exists, signal it.
 	if (_fence) { _fence->signal(); }
@@ -310,6 +330,7 @@
         }
 
         uint32_t ssCnt = pSubmit->signalSemaphoreCount;
+        _isSignalingSemaphores = ssCnt > 0;
         _signalSemaphores.reserve(ssCnt);
         for (uint32_t i = 0; i < ssCnt; i++) {
             _signalSemaphores.push_back((MVKSemaphore*)pSubmit->pSignalSemaphores[i]);
@@ -331,8 +352,21 @@
 void MVKQueuePresentSurfaceSubmission::execute() {
     id<MTLCommandQueue> mtlQ = _queue->getMTLCommandQueue();
 
+	// If there are semaphores and this device supports MTLEvent, we must present
+	// with a command buffer in order to synchronize with the semaphores.
 	MVKDevice* mvkDev = _queue->getDevice();
-	if (mvkDev->_pMVKConfig->presentWithCommandBuffer || mvkDev->_pMVKConfig->displayWatermark) {
+	if (mvkDev->_pMetalFeatures->events && !_waitSemaphores.empty()) {
+		// Create a command buffer, have it wait for the semaphores, then present
+		// surfaces via the command buffer.
+		id<MTLCommandBuffer> mtlCmdBuff = [mtlQ commandBufferWithUnretainedReferences];
+		mtlCmdBuff.label = mvkMTLCommandBufferLabel(kMVKCommandUseQueuePresent);
+		[mtlCmdBuff enqueue];
+
+		for (auto& ws : _waitSemaphores) { ws->encodeWait(mtlCmdBuff); }
+		for (auto& si : _surfaceImages) { si->presentCAMetalDrawable(mtlCmdBuff); }
+
+		[mtlCmdBuff commit];
+	} else if (mvkDev->_pMVKConfig->presentWithCommandBuffer || mvkDev->_pMVKConfig->displayWatermark) {
 		// Create a command buffer, present surfaces via the command buffer,
 		// then wait on the semaphores before committing.
 		id<MTLCommandBuffer> mtlCmdBuff = [mtlQ commandBufferWithUnretainedReferences];
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSync.h b/MoltenVK/MoltenVK/GPUObjects/MVKSync.h
index 6d397bc..1504c92 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSync.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSync.h
@@ -19,6 +19,7 @@
 #pragma once
 
 #include "MVKDevice.h"
+#include <atomic>
 #include <mutex>
 #include <condition_variable>
 #include <unordered_set>
@@ -127,14 +128,23 @@
 	/** Signals the semaphore. Unblocks all waiting threads to continue processing. */
 	void signal();
 
+	/** Encodes an operation to block command buffer operation until this semaphore is signaled. */
+	void encodeWait(id<MTLCommandBuffer> cmdBuff);
+
+	/** Encodes an operation to signal the semaphore. */
+	void encodeSignal(id<MTLCommandBuffer> cmdBuff);
+
 
 #pragma mark Construction
 
-    MVKSemaphore(MVKDevice* device, const VkSemaphoreCreateInfo* pCreateInfo)
-        : MVKVulkanAPIDeviceObject(device), _blocker(false, 1) {}
+    MVKSemaphore(MVKDevice* device, const VkSemaphoreCreateInfo* pCreateInfo);
+
+    ~MVKSemaphore() override;
 
 protected:
 	MVKSemaphoreImpl _blocker;
+	id<MTLEvent> _mtlEvent;
+	std::atomic<uint64_t> _mtlEventValue;
 };
 
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm
index 4c677e7..171df2a 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm
@@ -85,6 +85,27 @@
     _blocker.release();
 }
 
+void MVKSemaphore::encodeWait(id<MTLCommandBuffer> cmdBuff) {
+    [cmdBuff encodeWaitForEvent: _mtlEvent value: _mtlEventValue];
+    ++_mtlEventValue;
+}
+
+void MVKSemaphore::encodeSignal(id<MTLCommandBuffer> cmdBuff) {
+    [cmdBuff encodeSignalEvent: _mtlEvent value: _mtlEventValue];
+}
+
+MVKSemaphore::MVKSemaphore(MVKDevice* device, const VkSemaphoreCreateInfo* pCreateInfo)
+    : MVKVulkanAPIDeviceObject(device), _blocker(false, 1), _mtlEvent(nil), _mtlEventValue(1) {
+
+    if (device->_pMetalFeatures->events) {
+        _mtlEvent = [device->getMTLDevice() newEvent];
+    }
+}
+
+MVKSemaphore::~MVKSemaphore() {
+    [_mtlEvent release];
+}
+
 
 #pragma mark -
 #pragma mark MVKFence
diff --git a/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm
index f6422a4..329fb6d 100644
--- a/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm
+++ b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm
@@ -1318,6 +1318,33 @@
 	}
 }
 
+MVK_PUBLIC_SYMBOL MTLRenderStages mvkMTLRenderStagesFromVkPipelineStageFlags(VkPipelineStageFlags vkStages) {
+	MTLRenderStages mtlStages = MTLRenderStages(0);
+	if ( mvkIsAnyFlagEnabled(vkStages, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT | VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT | VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT | VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT) ) {
+		mtlStages |= MTLRenderStageVertex;
+	}
+	if ( mvkIsAnyFlagEnabled(vkStages, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT ) ) {
+		mtlStages |= MTLRenderStageFragment;
+	}
+	return mtlStages;
+}
+
+MVK_PUBLIC_SYMBOL MTLBarrierScope mvkMTLBarrierScopeFromVkAccessFlags(VkAccessFlags vkAccess) {
+	MTLBarrierScope mtlScope = MTLBarrierScope(0);
+	if ( mvkIsAnyFlagEnabled(vkAccess, VK_ACCESS_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_INDEX_READ_BIT | VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT) ) {
+		mtlScope |= MTLBarrierScopeBuffers;
+	}
+	if ( mvkIsAnyFlagEnabled(vkAccess, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT) ) {
+		mtlScope |= MTLBarrierScopeBuffers | MTLBarrierScopeTextures;
+	}
+#if MVK_MACOS
+	if ( mvkIsAnyFlagEnabled(vkAccess, VK_ACCESS_INPUT_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT) ) {
+		mtlScope |= MTLBarrierScopeRenderTargets;
+	}
+#endif
+	return mtlScope;
+}
+
 
 #pragma mark -
 #pragma mark Memory options