Merge pull request #850 from billhollings/master

Support screen captures.
diff --git a/Docs/Whats_New.md b/Docs/Whats_New.md
index 6c389d4..a32632d 100644
--- a/Docs/Whats_New.md
+++ b/Docs/Whats_New.md
@@ -29,6 +29,7 @@
 - Add ability to disable command memory pooling using `MVK_CONFIG_USE_COMMAND_POOLING`
   environment variable.
 - Fix memory leak when pre-filling `MTLCommandBuffers` using `MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS`.
+- Broaden conditions for host read sync for image memory barriers on macOS.
 - Update the `README.md` and `MoltenVK_Runtime_UserGuide.md` documents to clarify that 
   **MoltenVK** is not a fully-compliant implementation of *Vulkan*.
 - Support Xcode 11.4.
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
index 423bc0e..b49b4f1 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
@@ -2131,7 +2131,7 @@
 		}
 	}
 	if (swapchainInfo) {
-		return (MVKImage*)addResource(new MVKSwapchainImage(this, pCreateInfo, (MVKSwapchain*)swapchainInfo->swapchain));
+		return createSwapchainImage(pCreateInfo, (MVKSwapchain*)swapchainInfo->swapchain, uint32_t(-1), pAllocator);
 	}
 	return (MVKImage*)addResource(new MVKImage(this, pCreateInfo));
 }
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
index 48a951a..fc1ba81 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
@@ -272,6 +272,82 @@
 
 
 #pragma mark -
+#pragma mark MVKSwapchainImage
+
+/** Indicates the relative availability of each image in the swapchain. */
+typedef struct MVKSwapchainImageAvailability {
+	uint64_t acquisitionID;			/**< When this image was last made available, relative to the other images in the swapchain. Smaller value is earlier. */
+	uint32_t waitCount;				/**< The number of semaphores already waiting for this image. */
+	bool isAvailable;				/**< Indicates whether this image is currently available. */
+
+	bool operator< (const MVKSwapchainImageAvailability& rhs) const;
+} MVKSwapchainImageAvailability;
+
+/** Tracks a semaphore and fence for later signaling. */
+typedef std::pair<MVKSemaphore*, MVKFence*> MVKSwapchainSignaler;
+
+
+/** Represents a Vulkan image used as a rendering destination within a swapchain. */
+class MVKSwapchainImage : public MVKImage {
+
+public:
+
+	/** Binds this resource to the specified offset within the specified memory allocation. */
+	VkResult bindDeviceMemory(MVKDeviceMemory* mvkMem, VkDeviceSize memOffset) override;
+
+	/** Binds this resource according to the specified bind information. */
+	VkResult bindDeviceMemory2(const void* pBindInfo) override;
+
+
+#pragma mark Metal
+
+	/**
+	 * Presents the contained drawable to the OS, releases the Metal drawable and its
+	 * texture back to the Metal layer's pool, and makes the image memory available for new use.
+	 *
+	 * If mtlCmdBuff is not nil, the contained drawable is scheduled for presentation using
+	 * the presentDrawable: method of the command buffer. If mtlCmdBuff is nil, the contained
+	 * drawable is presented immediately using the present method of the drawable.
+	 */
+	void presentCAMetalDrawable(id<MTLCommandBuffer> mtlCmdBuff);
+
+
+#pragma mark Construction
+
+	/** Constructs an instance for the specified device and swapchain. */
+	MVKSwapchainImage(MVKDevice* device,
+					  const VkImageCreateInfo* pCreateInfo,
+					  MVKSwapchain* swapchain,
+					  uint32_t swapchainIndex);
+
+	~MVKSwapchainImage() override;
+
+protected:
+	friend MVKSwapchain;
+
+	id<MTLTexture> newMTLTexture() override;
+	id<CAMetalDrawable> getCAMetalDrawable();
+	void resetMetalDrawable();
+	MVKSwapchainImageAvailability getAvailability();
+	void makeAvailable();
+	void signalWhenAvailable(MVKSemaphore* semaphore, MVKFence* fence);
+	void signal(MVKSwapchainSignaler& signaler, id<MTLCommandBuffer> mtlCmdBuff);
+	void signalPresentationSemaphore(id<MTLCommandBuffer> mtlCmdBuff);
+	static void markAsTracked(MVKSwapchainSignaler& signaler);
+	static void unmarkAsTracked(MVKSwapchainSignaler& signaler);
+	void renderWatermark(id<MTLCommandBuffer> mtlCmdBuff);
+
+	MVKSwapchain* _swapchain;
+	uint32_t _swapchainIndex;
+	id<CAMetalDrawable> _mtlDrawable;
+	MVKSwapchainImageAvailability _availability;
+	MVKVectorInline<MVKSwapchainSignaler, 1> _availabilitySignalers;
+	MVKSwapchainSignaler _preSignaler;
+	std::mutex _availabilityLock;
+};
+
+
+#pragma mark -
 #pragma mark MVKImageView
 
 /** Represents a Vulkan image view. */
@@ -401,58 +477,3 @@
 	SPIRV_CROSS_NAMESPACE::MSLConstexprSampler _constExprSampler;
 	bool _requiresConstExprSampler;
 };
-
-
-#pragma mark -
-#pragma mark MVKSwapchainImage
-
-/** Represents a Vulkan image used as a rendering destination within a swapchain. */
-class MVKSwapchainImage : public MVKImage {
-
-public:
-
-	/** Binds this resource to the specified offset within the specified memory allocation. */
-	VkResult bindDeviceMemory(MVKDeviceMemory* mvkMem, VkDeviceSize memOffset) override;
-
-	/** Binds this resource according to the specified bind information. */
-	VkResult bindDeviceMemory2(const void* pBindInfo) override;
-
-	
-#pragma mark Metal
-
-	/**
-	 * Presents the contained drawable to the OS, releases the Metal drawable and its 
-	 * texture back to the Metal layer's pool, and makes the image memory available for new use.
-	 *
-	 * If mtlCmdBuff is not nil, the contained drawable is scheduled for presentation using
-	 * the presentDrawable: method of the command buffer. If mtlCmdBuff is nil, the contained
-	 * drawable is presented immediately using the present method of the drawable.
-	 */
-	void presentCAMetalDrawable(id<MTLCommandBuffer> mtlCmdBuff);
-
-
-#pragma mark Construction
-	
-	/** Constructs an instance for the specified device and swapchain. */
-	MVKSwapchainImage(MVKDevice* device,
-					  const VkImageCreateInfo* pCreateInfo,
-					  MVKSwapchain* swapchain,
-					  uint32_t swapchainIndex);
-
-	/** Constructs an instance for the specified device and swapchain, without binding to a particular swapchain image index. */
-	MVKSwapchainImage(MVKDevice* device,
-					  const VkImageCreateInfo* pCreateInfo,
-					  MVKSwapchain* swapchain);
-
-	~MVKSwapchainImage() override;
-
-protected:
-	id<MTLTexture> newMTLTexture() override;
-	id<CAMetalDrawable> getCAMetalDrawable();
-    void resetMetalSurface();
-    void renderWatermark(id<MTLCommandBuffer> mtlCmdBuff);
-
-	MVKSwapchain* _swapchain;
-	uint32_t _swapchainIndex;
-};
-
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
index dd30a27..4a226d9 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
@@ -21,10 +21,9 @@
 #include "MVKSwapchain.h"
 #include "MVKCommandBuffer.h"
 #include "MVKCmdDebug.h"
-#include "mvk_datatypes.hpp"
-#include "MVKFoundation.h"
-#include "MVKLogging.h"
 #include "MVKEnvironment.h"
+#include "MVKFoundation.h"
+#include "MVKOSExtensions.h"
 #include "MVKLogging.h"
 #include "MVKCodec.h"
 #import "MTLTextureDescriptor+MoltenVK.h"
@@ -143,8 +142,7 @@
 #endif
 #if MVK_MACOS
 	return ((pImageMemoryBarrier->newLayout == VK_IMAGE_LAYOUT_GENERAL) &&
-			mvkIsAnyFlagEnabled(dstStageMask, (VK_PIPELINE_STAGE_HOST_BIT)) &&
-			mvkIsAnyFlagEnabled(pImageMemoryBarrier->dstAccessMask, (VK_ACCESS_HOST_READ_BIT)) &&
+			mvkIsAnyFlagEnabled(pImageMemoryBarrier->dstAccessMask, (VK_ACCESS_HOST_READ_BIT | VK_ACCESS_MEMORY_READ_BIT)) &&
 			isMemoryHostAccessible() && !isMemoryHostCoherent());
 #endif
 }
@@ -287,11 +285,13 @@
 
 	id<MTLTexture> mtlTex = _mtlTextureViews[mtlPixFmt];
 	if ( !mtlTex ) {
-		// Lock and check again in case another thread has created the texture.
+		// Lock and check again in case another thread has created the view texture.
+		// baseTex retreived outside of lock to avoid deadlock if it too needs to be lazily created.
+		id<MTLTexture> baseTex = getMTLTexture();
 		lock_guard<mutex> lock(_lock);
 		mtlTex = _mtlTextureViews[mtlPixFmt];
 		if ( !mtlTex ) {
-			mtlTex = [getMTLTexture() newTextureViewWithPixelFormat: mtlPixFmt];	// retained
+			mtlTex = [baseTex newTextureViewWithPixelFormat: mtlPixFmt];	// retained
 			_mtlTextureViews[mtlPixFmt] = mtlTex;
 		}
 	}
@@ -348,10 +348,12 @@
 	return mtlTex;
 }
 
-// Removes and releases the MTLTexture object, so that it can be lazily created by getMTLTexture().
+// Removes and releases the MTLTexture object, and all associated texture views
 void MVKImage::resetMTLTexture() {
 	[_mtlTexture release];
 	_mtlTexture = nil;
+	for (auto elem : _mtlTextureViews) { [elem.second release]; }
+	_mtlTextureViews.clear();
 }
 
 void MVKImage::resetIOSurface() {
@@ -364,6 +366,7 @@
 IOSurfaceRef MVKImage::getIOSurface() { return _ioSurface; }
 
 VkResult MVKImage::useIOSurface(IOSurfaceRef ioSurface) {
+	lock_guard<mutex> lock(_lock);
 
     if (!_device->_pMetalFeatures->ioSurfaces) { return reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkUseIOSurfaceMVK() : IOSurfaces are not supported on this platform."); }
 
@@ -787,7 +790,208 @@
 	if (_deviceMemory) { _deviceMemory->removeImage(this); }
 	resetMTLTexture();
     resetIOSurface();
-	for (auto elem : _mtlTextureViews) { [elem.second release]; }
+}
+
+
+#pragma mark -
+#pragma mark MVKSwapchainImage
+
+VkResult MVKSwapchainImage::bindDeviceMemory(MVKDeviceMemory*, VkDeviceSize) {
+	return VK_ERROR_OUT_OF_DEVICE_MEMORY;
+}
+
+VkResult MVKSwapchainImage::bindDeviceMemory2(const void* pBindInfo) {
+	const auto* imageInfo = (const VkBindImageMemoryInfo*)pBindInfo;
+	const VkBindImageMemorySwapchainInfoKHR* swapchainInfo = nullptr;
+	for (const auto* next = (const VkBaseInStructure*)imageInfo->pNext; next; next = next->pNext) {
+		switch (next->sType) {
+			case VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_SWAPCHAIN_INFO_KHR:
+				swapchainInfo = (const VkBindImageMemorySwapchainInfoKHR*)next;
+				break;
+			default:
+				break;
+		}
+		if (swapchainInfo) { break; }
+	}
+	if (!swapchainInfo) {
+		return VK_ERROR_OUT_OF_DEVICE_MEMORY;
+	}
+	_swapchainIndex = swapchainInfo->imageIndex;
+	return VK_SUCCESS;
+}
+
+bool MVKSwapchainImageAvailability::operator< (const MVKSwapchainImageAvailability& rhs) const {
+	if (  isAvailable && !rhs.isAvailable) { return true; }
+	if ( !isAvailable &&  rhs.isAvailable) { return false; }
+
+	if (waitCount < rhs.waitCount) { return true; }
+	if (waitCount > rhs.waitCount) { return false; }
+
+	return acquisitionID < rhs.acquisitionID;
+}
+
+MVKSwapchainImageAvailability MVKSwapchainImage::getAvailability() {
+	lock_guard<mutex> lock(_availabilityLock);
+
+	return _availability;
+}
+
+// Makes an image available for acquisition by the app.
+// If any semaphores are waiting to be signaled when this image becomes available, the
+// earliest semaphore is signaled, and this image remains unavailable for other uses.
+void MVKSwapchainImage::makeAvailable() {
+	lock_guard<mutex> lock(_availabilityLock);
+
+	// Mark when this event happened, relative to that of other images
+	_availability.acquisitionID = _swapchain->getNextAcquisitionID();
+
+	// Mark this image as available if no semaphores or fences are waiting to be signaled.
+	_availability.isAvailable = _availabilitySignalers.empty();
+
+	MVKSwapchainSignaler signaler;
+	if (_availability.isAvailable) {
+		// If this image is available, signal the semaphore and fence that were associated
+		// with the last time this image was acquired while available. This is a workaround for
+		// when an app uses a single semaphore or fence for more than one swapchain image.
+		// Becuase the semaphore or fence will be signaled by more than one image, it will
+		// get out of sync, and the final use of the image would not be signaled as a result.
+		signaler = _preSignaler;
+	} else {
+		// If this image is not yet available, extract and signal the first semaphore and fence.
+		auto sigIter = _availabilitySignalers.begin();
+		signaler = *sigIter;
+		_availabilitySignalers.erase(sigIter);
+	}
+
+	// Signal the semaphore and fence, and let them know they are no longer being tracked.
+	signal(signaler, nil);
+	unmarkAsTracked(signaler);
+
+//	MVKLogDebug("Signaling%s swapchain image %p semaphore %p from present, with %lu remaining semaphores.", (_availability.isAvailable ? " pre-signaled" : ""), this, signaler.first, _availabilitySignalers.size());
+}
+
+void MVKSwapchainImage::signalWhenAvailable(MVKSemaphore* semaphore, MVKFence* fence) {
+	lock_guard<mutex> lock(_availabilityLock);
+
+	auto signaler = make_pair(semaphore, fence);
+	if (_availability.isAvailable) {
+		_availability.isAvailable = false;
+
+		// If signalling through a MTLEvent, use an ephemeral MTLCommandBuffer.
+		// Another option would be to use MTLSharedEvent in MVKSemaphore, but that might
+		// impose unacceptable performance costs to handle this particular case.
+		@autoreleasepool {
+			MVKSemaphore* mvkSem = signaler.first;
+			id<MTLCommandBuffer> mtlCmdBuff = (mvkSem && mvkSem->isUsingCommandEncoding()
+											   ? [_device->getAnyQueue()->getMTLCommandQueue() commandBufferWithUnretainedReferences]
+											   : nil);
+			signal(signaler, mtlCmdBuff);
+			[mtlCmdBuff commit];
+		}
+
+		_preSignaler = signaler;
+	} else {
+		_availabilitySignalers.push_back(signaler);
+	}
+	markAsTracked(signaler);
+
+//	MVKLogDebug("%s swapchain image %p semaphore %p in acquire with %lu other semaphores.", (_availability.isAvailable ? "Signaling" : "Tracking"), this, semaphore, _availabilitySignalers.size());
+}
+
+// If present, signal the semaphore for the first waiter for the given image.
+void MVKSwapchainImage::signalPresentationSemaphore(id<MTLCommandBuffer> mtlCmdBuff) {
+	lock_guard<mutex> lock(_availabilityLock);
+
+	if ( !_availabilitySignalers.empty() ) {
+		MVKSemaphore* mvkSem = _availabilitySignalers.front().first;
+		if (mvkSem) { mvkSem->encodeSignal(mtlCmdBuff); }
+	}
+}
+
+// Signal either or both of the semaphore and fence in the specified tracker pair.
+void MVKSwapchainImage::signal(MVKSwapchainSignaler& signaler, id<MTLCommandBuffer> mtlCmdBuff) {
+	if (signaler.first) { signaler.first->encodeSignal(mtlCmdBuff); }
+	if (signaler.second) { signaler.second->signal(); }
+}
+
+// Tell the semaphore and fence that they are being tracked for future signaling.
+void MVKSwapchainImage::markAsTracked(MVKSwapchainSignaler& signaler) {
+	if (signaler.first) { signaler.first->retain(); }
+	if (signaler.second) { signaler.second->retain(); }
+}
+
+// Tell the semaphore and fence that they are no longer being tracked for future signaling.
+void MVKSwapchainImage::unmarkAsTracked(MVKSwapchainSignaler& signaler) {
+	if (signaler.first) { signaler.first->release(); }
+	if (signaler.second) { signaler.second->release(); }
+}
+
+
+#pragma mark Metal
+
+// Creates and returns a retained Metal texture suitable for use in this instance.
+// This implementation retrieves a MTLTexture from the CAMetalDrawable.
+id<MTLTexture> MVKSwapchainImage::newMTLTexture() {
+	return [[getCAMetalDrawable() texture] retain];
+}
+
+id<CAMetalDrawable> MVKSwapchainImage::getCAMetalDrawable() {
+	while ( !_mtlDrawable ) {
+		@autoreleasepool {      // Reclaim auto-released drawable object before end of loop
+			uint64_t startTime = _device->getPerformanceTimestamp();
+
+			_mtlDrawable = [_swapchain->_mtlLayer.nextDrawable retain];
+			if ( !_mtlDrawable ) { MVKLogError("CAMetalDrawable could not be acquired after %.6f ms.", mvkGetElapsedMilliseconds(startTime)); }
+
+			_device->addActivityPerformance(_device->_performanceStatistics.queue.nextCAMetalDrawable, startTime);
+		}
+	}
+	return _mtlDrawable;
+}
+
+// Present the drawable and make myself available only once the command buffer has completed.
+void MVKSwapchainImage::presentCAMetalDrawable(id<MTLCommandBuffer> mtlCmdBuff) {
+	_swapchain->willPresentSurface(getMTLTexture(), mtlCmdBuff);
+
+	NSString* scName = _swapchain->getDebugName();
+	if (scName) { mvkPushDebugGroup(mtlCmdBuff, scName); }
+	[mtlCmdBuff presentDrawable: getCAMetalDrawable()];
+	if (scName) { mvkPopDebugGroup(mtlCmdBuff); }
+
+	signalPresentationSemaphore(mtlCmdBuff);
+
+	retain();	// Ensure this image is not destroyed while awaiting MTLCommandBuffer completion
+	[mtlCmdBuff addCompletedHandler: ^(id<MTLCommandBuffer> mcb) {
+		makeAvailable();
+		release();
+	}];
+}
+
+// Resets the MTLTexture and CAMetalDrawable underlying this image.
+void MVKSwapchainImage::resetMetalDrawable() {
+	resetMTLTexture();			// Release texture first so drawable will be last to release it
+	[_mtlDrawable release];
+	_mtlDrawable = nil;
+}
+
+
+#pragma mark Construction
+
+MVKSwapchainImage::MVKSwapchainImage(MVKDevice* device,
+									 const VkImageCreateInfo* pCreateInfo,
+									 MVKSwapchain* swapchain,
+									 uint32_t swapchainIndex) : MVKImage(device, pCreateInfo) {
+	_swapchain = swapchain;
+	_swapchainIndex = swapchainIndex;
+	_mtlDrawable = nil;
+
+	_availability.acquisitionID = _swapchain->getNextAcquisitionID();
+	_availability.isAvailable = true;
+	_preSignaler = make_pair(nullptr, nullptr);
+}
+
+MVKSwapchainImage::~MVKSwapchainImage() {
+	resetMetalDrawable();
 }
 
 
@@ -1225,94 +1429,3 @@
 MVKSampler::~MVKSampler() {
 	[_mtlSamplerState release];
 }
-
-
-#pragma mark -
-#pragma mark MVKSwapchainImage
-
-VkResult MVKSwapchainImage::bindDeviceMemory(MVKDeviceMemory*, VkDeviceSize) {
-	return VK_ERROR_OUT_OF_DEVICE_MEMORY;
-}
-
-VkResult MVKSwapchainImage::bindDeviceMemory2(const void* pBindInfo) {
-	const auto* imageInfo = (const VkBindImageMemoryInfo*)pBindInfo;
-	const VkBindImageMemorySwapchainInfoKHR* swapchainInfo = nullptr;
-	for (const auto* next = (const VkBaseInStructure*)imageInfo->pNext; next; next = next->pNext) {
-		switch (next->sType) {
-		case VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_SWAPCHAIN_INFO_KHR:
-			swapchainInfo = (const VkBindImageMemorySwapchainInfoKHR*)next;
-			break;
-		default:
-			break;
-		}
-		if (swapchainInfo) { break; }
-	}
-	if (!swapchainInfo) {
-		return VK_ERROR_OUT_OF_DEVICE_MEMORY;
-	}
-	_swapchainIndex = swapchainInfo->imageIndex;
-	return VK_SUCCESS;
-}
-
-
-#pragma mark Metal
-
-// Creates and returns a retained Metal texture suitable for use in this instance.
-// This implementation retrieves a MTLTexture from the CAMetalDrawable.
-id<MTLTexture> MVKSwapchainImage::newMTLTexture() {
-	return [[getCAMetalDrawable() texture] retain];
-}
-
-id<CAMetalDrawable> MVKSwapchainImage::getCAMetalDrawable() {
-	id<CAMetalDrawable> mtlDrawable = _swapchain->getCAMetalDrawable(_swapchainIndex);
-	MVKAssert(mtlDrawable, "Could not acquire an available CAMetalDrawable from the CAMetalLayer in MVKSwapchain image: %p.", this);
-	return mtlDrawable;
-}
-
-// Present the drawable and make myself available only once the command buffer has completed.
-void MVKSwapchainImage::presentCAMetalDrawable(id<MTLCommandBuffer> mtlCmdBuff) {
-	_swapchain->willPresentSurface(getMTLTexture(), mtlCmdBuff);
-
-	NSString* scName = _swapchain->getDebugName();
-	if (scName) { mvkPushDebugGroup(mtlCmdBuff, scName); }
-	[mtlCmdBuff presentDrawable: getCAMetalDrawable()];
-	if (scName) { mvkPopDebugGroup(mtlCmdBuff); }
-
-	resetMetalSurface();
-	_swapchain->signalPresentationSemaphore(_swapchainIndex, mtlCmdBuff);
-
-	retain();	// Ensure this image is not destroyed while awaiting MTLCommandBuffer completion
-	[mtlCmdBuff addCompletedHandler: ^(id<MTLCommandBuffer> mcb) {
-		_swapchain->makeAvailable(_swapchainIndex);
-		release();
-	}];
-}
-
-// Resets the MTLTexture and CAMetalDrawable underlying this image.
-void MVKSwapchainImage::resetMetalSurface() {
-    resetMTLTexture();			// Release texture first so drawable will be last to release it
-    _swapchain->resetCAMetalDrawable(_swapchainIndex);
-}
-
-
-#pragma mark Construction
-
-MVKSwapchainImage::MVKSwapchainImage(MVKDevice* device,
-									 const VkImageCreateInfo* pCreateInfo,
-									 MVKSwapchain* swapchain,
-									 uint32_t swapchainIndex) : MVKImage(device, pCreateInfo) {
-	_swapchain = swapchain;
-	_swapchainIndex = swapchainIndex;
-}
-
-MVKSwapchainImage::MVKSwapchainImage(MVKDevice* device,
-									 const VkImageCreateInfo* pCreateInfo,
-									 MVKSwapchain* swapchain) : MVKImage(device, pCreateInfo) {
-	_swapchain = swapchain;
-	_swapchainIndex = uint32_t(-1);
-}
-
-MVKSwapchainImage::~MVKSwapchainImage() {
-	resetMetalSurface();	// remove drawable from swapchain
-}
-
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h
index cce5a85..8b88c64 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h
@@ -27,21 +27,9 @@
 @class MVKBlockObserver;
 
 
-/** Indicates the relative availability of each image in the swapchain. */
-typedef struct MVKSwapchainImageAvailability {
-	uint64_t acquisitionID;			/**< When this image was last made available, relative to the other images in the swapchain. Smaller value is earlier. */
-	uint32_t waitCount;				/**< The number of semaphores already waiting for this image. */
-	bool isAvailable;				/**< Indicates whether this image is currently available. */
-
-	bool operator< (const MVKSwapchainImageAvailability& rhs) const;
-} MVKSwapchainImageAvailability;
-
-
+#pragma mark -
 #pragma mark MVKSwapchain
 
-/** Tracks a semaphore and fence for later signaling. */
-typedef std::pair<MVKSemaphore*, MVKFence*> MVKSwapchainSignaler;
-
 /** Represents a Vulkan swapchain. */
 class MVKSwapchain : public MVKVulkanAPIDeviceObject {
 
@@ -54,10 +42,10 @@
 	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_SWAPCHAIN_KHR_EXT; }
 
 	/** Returns the number of images in this swapchain. */
-	uint32_t getImageCount();
+	inline uint32_t getImageCount() { return (uint32_t)_surfaceImages.size(); }
 
 	/** Returns the image at the specified index. */
-	MVKSwapchainImage* getImage(uint32_t index);
+	inline MVKSwapchainImage* getImage(uint32_t index) { return _surfaceImages[index]; }
 
 	/**
 	 * Returns the array of presentable images associated with this swapchain.
@@ -93,26 +81,6 @@
 	/** Adds HDR metadata to this swapchain. */
 	void setHDRMetadataEXT(const VkHdrMetadataEXT& metadata);
 
-	/**
-	 * Registers a semaphore and/or fence that will be signaled when the image at the given index becomes available.
-	 * This function accepts both a semaphore and a fence, and either none, one, or both may be provided.
-	 * If this image is available already, the semaphore and fence are immediately signaled.
-	 */
-	void signalWhenAvailable(uint32_t imageIndex, MVKSemaphore* semaphore, MVKFence* fence);
-
-	
-#pragma mark Metal
-
-	/** 
-	 * Returns the Metal drawable providing backing for the image at the given
-	 * index in this swapchain. If none is established, the next available
-	 * drawable is acquired and returned.
-	 *
-	 * This function may block until the next drawable is available, 
-	 * and may return nil if no drawable is available at all.
-	 */
-	id<CAMetalDrawable> getCAMetalDrawable(uint32_t imgIdx);
-
 
 #pragma mark Construction
 	
@@ -123,12 +91,6 @@
 protected:
 	friend class MVKSwapchainImage;
 
-	struct Availability {
-		MVKSwapchainImageAvailability status;
-		MVKVectorInline<MVKSwapchainSignaler, 1> signalers;
-		MVKSwapchainSignaler preSignaled;
-	};
-
 	void propogateDebugName() override;
 	void initCAMetalLayer(const VkSwapchainCreateInfoKHR* pCreateInfo, uint32_t imgCnt);
 	void initSurfaceImages(const VkSwapchainCreateInfoKHR* pCreateInfo, uint32_t imgCnt);
@@ -138,19 +100,10 @@
     void willPresentSurface(id<MTLTexture> mtlTexture, id<MTLCommandBuffer> mtlCmdBuff);
     void renderWatermark(id<MTLTexture> mtlTexture, id<MTLCommandBuffer> mtlCmdBuff);
     void markFrameInterval();
-	void resetCAMetalDrawable(uint32_t imgIdx);
-	void signal(MVKSwapchainSignaler& signaler, id<MTLCommandBuffer> mtlCmdBuff);
-	void signalPresentationSemaphore(uint32_t imgIdx, id<MTLCommandBuffer> mtlCmdBuff);
-	static void markAsTracked(MVKSwapchainSignaler& signaler);
-	static void unmarkAsTracked(MVKSwapchainSignaler& signaler);
-	void makeAvailable(uint32_t imgIdx);
 
 	CAMetalLayer* _mtlLayer;
     MVKWatermark* _licenseWatermark;
 	MVKVectorInline<MVKSwapchainImage*, kMVKMaxSwapchainImageCount> _surfaceImages;
-	MVKVectorInline<id<CAMetalDrawable>, kMVKMaxSwapchainImageCount> _mtlDrawables;
-	MVKVectorInline<Availability, kMVKMaxSwapchainImageCount> _imageAvailability;
-	std::mutex _availabilityLock;
 	std::atomic<uint64_t> _currentAcquisitionID;
     CGSize _mtlLayerOrigDrawSize;
     MVKSwapchainPerformance _performanceStatistics;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
index 1543d7b..b93d3b9 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
@@ -35,18 +35,9 @@
 using namespace std;
 
 
+#pragma mark -
 #pragma mark MVKSwapchain
 
-bool MVKSwapchainImageAvailability::operator< (const MVKSwapchainImageAvailability& rhs) const {
-	if (  isAvailable && !rhs.isAvailable) { return true; }
-	if ( !isAvailable &&  rhs.isAvailable) { return false; }
-
-	if (waitCount < rhs.waitCount) { return true; }
-	if (waitCount > rhs.waitCount) { return false; }
-
-	return acquisitionID < rhs.acquisitionID;
-}
-
 void MVKSwapchain::propogateDebugName() {
 	if (_debugName) {
 		size_t imgCnt = _surfaceImages.size();
@@ -58,10 +49,6 @@
 	}
 }
 
-uint32_t MVKSwapchain::getImageCount() { return (uint32_t)_imageAvailability.size(); }
-
-MVKSwapchainImage* MVKSwapchain::getImage(uint32_t index) { return _surfaceImages[index]; }
-
 VkResult MVKSwapchain::getImages(uint32_t* pCount, VkImage* pSwapchainImages) {
 
 	// Get the number of surface images
@@ -86,29 +73,34 @@
 }
 
 VkResult MVKSwapchain::acquireNextImageKHR(uint64_t timeout,
-                                           VkSemaphore semaphore,
-                                           VkFence fence,
+										   VkSemaphore semaphore,
+										   VkFence fence,
 										   uint32_t deviceMask,
-                                           uint32_t* pImageIndex) {
+										   uint32_t* pImageIndex) {
 
-    if ( getIsSurfaceLost() ) { return VK_ERROR_SURFACE_LOST_KHR; }
+	if ( getIsSurfaceLost() ) { return VK_ERROR_SURFACE_LOST_KHR; }
 
-    // Find the image that has the smallest availability measure
-    uint32_t minWaitIndex = 0;
-    MVKSwapchainImageAvailability minAvailability = { .acquisitionID = kMVKUndefinedLargeUInt64,
+	// Find the image that has the smallest availability measure
+	MVKSwapchainImage* minWaitImage = nullptr;
+	MVKSwapchainImageAvailability minAvailability = { .acquisitionID = kMVKUndefinedLargeUInt64,
 													  .waitCount = kMVKUndefinedLargeUInt32,
 													  .isAvailable = false };
-    for (uint32_t imgIdx = 0; imgIdx < _imageAvailability.size(); imgIdx++) {
-        const Availability& avail = _imageAvailability[imgIdx];
-        if (avail.status < minAvailability) {
-            minAvailability = avail.status;
-            minWaitIndex = imgIdx;
-        }
-    }
+	uint32_t imgCnt = getImageCount();
+	for (uint32_t imgIdx = 0; imgIdx < imgCnt; imgIdx++) {
+		MVKSwapchainImage* img = getImage(imgIdx);
+		auto imgAvail = img->getAvailability();
+		if (imgAvail < minAvailability) {
+			minAvailability = imgAvail;
+			minWaitImage = img;
+		}
+	}
 
-    *pImageIndex = minWaitIndex;	// Return the index of the image with the shortest wait
-    signalWhenAvailable(minWaitIndex, (MVKSemaphore*)semaphore, (MVKFence*)fence);
-    return getHasSurfaceSizeChanged() ? VK_ERROR_OUT_OF_DATE_KHR : VK_SUCCESS;
+	// Return the index of the image with the shortest wait and signal the semaphore and fence when it's available
+	*pImageIndex = minWaitImage->_swapchainIndex;
+	minWaitImage->resetMetalDrawable();
+	minWaitImage->signalWhenAvailable((MVKSemaphore*)semaphore, (MVKFence*)fence);
+
+	return getHasSurfaceSizeChanged() ? VK_ERROR_OUT_OF_DATE_KHR : VK_SUCCESS;
 }
 
 bool MVKSwapchain::getHasSurfaceSizeChanged() {
@@ -117,104 +109,10 @@
 
 uint64_t MVKSwapchain::getNextAcquisitionID() { return ++_currentAcquisitionID; }
 
-/**
- * Releases any surfaces that are not currently being displayed,
- * so they can be used by a different swapchain.
- */
+// Releases any surfaces that are not currently being displayed,
+// so they can be used by a different swapchain.
 void MVKSwapchain::releaseUndisplayedSurfaces() {}
 
-// Makes an image available for acquisition by the app.
-// If any semaphores are waiting to be signaled when this image becomes available, the
-// earliest semaphore is signaled, and this image remains unavailable for other uses.
-void MVKSwapchain::makeAvailable(uint32_t imgIdx) {
-	lock_guard<mutex> lock(_availabilityLock);
-	auto& availability = _imageAvailability[imgIdx].status;
-
-	// Mark when this event happened, relative to that of other images
-	availability.acquisitionID = getNextAcquisitionID();
-
-	// Mark this image as available if no semaphores or fences are waiting to be signaled.
-	availability.isAvailable = _imageAvailability[imgIdx].signalers.empty();
-
-	MVKSwapchainSignaler signaler;
-	if (availability.isAvailable) {
-		// If this image is available, signal the semaphore and fence that were associated
-		// with the last time this image was acquired while available. This is a workaround for
-		// when an app uses a single semaphore or fence for more than one swapchain image.
-		// Becuase the semaphore or fence will be signaled by more than one image, it will
-		// get out of sync, and the final use of the image would not be signaled as a result.
-		signaler = _imageAvailability[imgIdx].preSignaled;
-	} else {
-		// If this image is not yet available, extract and signal the first semaphore and fence.
-		auto& imgSignalers = _imageAvailability[imgIdx].signalers;
-		auto sigIter = imgSignalers.begin();
-		signaler = *sigIter;
-		imgSignalers.erase(sigIter);
-	}
-
-	// Signal the semaphore and fence, and let them know they are no longer being tracked.
-	signal(signaler, nil);
-	unmarkAsTracked(signaler);
-
-//	MVKLogDebug("Signaling%s swapchain image %p semaphore %p from present, with %lu remaining semaphores.", (_availability.isAvailable ? " pre-signaled" : ""), this, signaler.first, _availabilitySignalers.size());
-}
-
-void MVKSwapchain::signalWhenAvailable(uint32_t imageIndex, MVKSemaphore* semaphore, MVKFence* fence) {
-	lock_guard<mutex> lock(_availabilityLock);
-	auto signaler = make_pair(semaphore, fence);
-	auto& availability = _imageAvailability[imageIndex].status;
-	if (availability.isAvailable) {
-		availability.isAvailable = false;
-
-		// If signalling through a MTLEvent, use an ephemeral MTLCommandBuffer.
-		// Another option would be to use MTLSharedEvent in MVKSemaphore, but that might
-		// impose unacceptable performance costs to handle this particular case.
-		@autoreleasepool {
-			MVKSemaphore* mvkSem = signaler.first;
-			id<MTLCommandBuffer> mtlCmdBuff = (mvkSem && mvkSem->isUsingCommandEncoding()
-											   ? [_device->getAnyQueue()->getMTLCommandQueue() commandBufferWithUnretainedReferences]
-											   : nil);
-			signal(signaler, mtlCmdBuff);
-			[mtlCmdBuff commit];
-		}
-
-		_imageAvailability[imageIndex].preSignaled = signaler;
-	} else {
-		_imageAvailability[imageIndex].signalers.push_back(signaler);
-	}
-	markAsTracked(signaler);
-
-//	MVKLogDebug("%s swapchain image %p semaphore %p in acquire with %lu other semaphores.", (_availability.isAvailable ? "Signaling" : "Tracking"), this, semaphore, _availabilitySignalers.size());
-}
-
-// Signal either or both of the semaphore and fence in the specified tracker pair.
-void MVKSwapchain::signal(MVKSwapchainSignaler& signaler, id<MTLCommandBuffer> mtlCmdBuff) {
-	if (signaler.first) { signaler.first->encodeSignal(mtlCmdBuff); }
-	if (signaler.second) { signaler.second->signal(); }
-}
-
-// If present, signal the semaphore for the first waiter for the given image.
-void MVKSwapchain::signalPresentationSemaphore(uint32_t imgIdx, id<MTLCommandBuffer> mtlCmdBuff) {
-	lock_guard<mutex> lock(_availabilityLock);
-	auto& imgSignalers = _imageAvailability[imgIdx].signalers;
-	if ( !imgSignalers.empty() ) {
-		MVKSemaphore* mvkSem = imgSignalers.front().first;
-		if (mvkSem) { mvkSem->encodeSignal(mtlCmdBuff); }
-	}
-}
-
-// Tell the semaphore and fence that they are being tracked for future signaling.
-void MVKSwapchain::markAsTracked(MVKSwapchainSignaler& signaler) {
-	if (signaler.first) { signaler.first->retain(); }
-	if (signaler.second) { signaler.second->retain(); }
-}
-
-// Tell the semaphore and fence that they are no longer being tracked for future signaling.
-void MVKSwapchain::unmarkAsTracked(MVKSwapchainSignaler& signaler) {
-	if (signaler.first) { signaler.first->release(); }
-	if (signaler.second) { signaler.second->release(); }
-}
-
 
 #pragma mark Rendering
 
@@ -343,31 +241,6 @@
 }
 
 
-#pragma mark Metal
-
-id<CAMetalDrawable> MVKSwapchain::getCAMetalDrawable(uint32_t imageIndex) {
-	id<CAMetalDrawable> nextDrwbl = _mtlDrawables[imageIndex];
-	while ( !nextDrwbl ) {
-		@autoreleasepool {      // Allow auto-released drawable object to be reclaimed before end of loop
-			uint64_t startTime = _device->getPerformanceTimestamp();
-
-			nextDrwbl = _mtlLayer.nextDrawable;
-			if ( !nextDrwbl ) { MVKLogError("Drawable could not be retrieved! Elapsed time: %.6f ms.", mvkGetElapsedMilliseconds()); }
-			_mtlDrawables[imageIndex] = [nextDrwbl retain];
-
-			_device->addActivityPerformance(_device->_performanceStatistics.queue.nextCAMetalDrawable, startTime);
-		}
-	}
-	return nextDrwbl;
-}
-
-// Removes and releases a Metal drawable object, so that it can be lazily created by getCAMetalDrawable().
-void MVKSwapchain::resetCAMetalDrawable(uint32_t imgIdx) {
-	[_mtlDrawables[imgIdx] release];
-	_mtlDrawables[imgIdx] = nil;
-}
-
-
 #pragma mark Construction
 
 MVKSwapchain::MVKSwapchain(MVKDevice* device,
@@ -510,16 +383,9 @@
 		mvkEnableFlags(imgInfo.flags, VK_IMAGE_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT);
 	}
 
-	_surfaceImages.reserve(imgCnt);
-	_mtlDrawables.resize(imgCnt);
-	_imageAvailability.resize(imgCnt);
-    for (uint32_t imgIdx = 0; imgIdx < imgCnt; imgIdx++) {
-        _surfaceImages.push_back(_device->createSwapchainImage(&imgInfo, this, imgIdx, NULL));
-        _imageAvailability[imgIdx].status.acquisitionID = getNextAcquisitionID();
-        _imageAvailability[imgIdx].status.isAvailable = true;
-        _imageAvailability[imgIdx].preSignaled = make_pair(nullptr, nullptr);
-        _mtlDrawables[imgIdx] = nil;
-    }
+	for (uint32_t imgIdx = 0; imgIdx < imgCnt; imgIdx++) {
+		_surfaceImages.push_back(_device->createSwapchainImage(&imgInfo, this, imgIdx, NULL));
+	}
 
     MVKLogInfo("Created %d swapchain images with initial size (%d, %d).", imgCnt, imgExtent.width, imgExtent.height);
 }