Move Metal drawable presentation from MTLCommandBuffer to MTLDrawable.

Update MVKPresentableSwapchainImage::presentCAMetalDrawable() to create a
MTLCommandBuffer scheduled-handler and present the MTLDrawable from there.

According to Apple, it is more performant to call MTLDrawable present from within a
MTLCommandBuffer scheduled-handler than it is to call MTLCommandBuffer presentDrawable:.

Pass presentation timing info as a struct to simplify calls.
diff --git a/Docs/Whats_New.md b/Docs/Whats_New.md
index 745fe07..36fe387 100644
--- a/Docs/Whats_New.md
+++ b/Docs/Whats_New.md
@@ -21,6 +21,8 @@
 - Use `VK_KHR_image_format_list` to disable `MTLTextureUsagePixelFormatView` 
   if only swizzles or `sRGB` conversion will be used for image views, improving 
   performance on *iOS* by allowing Metal to use lossless texture compression.
+- Move *Metal* drawable presentation from `MTLCommandBuffer` to `MTLDrawable`
+  to improve performance and reduce blocking.
 
 
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
index 8097a6e..3986320 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
@@ -429,6 +429,14 @@
 	bool operator< (const MVKSwapchainImageAvailability& rhs) const;
 } MVKSwapchainImageAvailability;
 
+/** VK_GOOGLE_display_timing extension info */
+typedef struct  {
+	MVKPresentableSwapchainImage* presentableImage;
+	bool hasPresentTime;      		// Keep track of whether presentation included VK_GOOGLE_display_timing
+	uint32_t presentID;           	// VK_GOOGLE_display_timing presentID
+	uint64_t desiredPresentTime;  	// VK_GOOGLE_display_timing desired presentation time in nanoseconds
+} MVKPresentTimingInfo;
+
 /** Tracks a semaphore and fence for later signaling. */
 typedef std::pair<MVKSemaphore*, MVKFence*> MVKSwapchainSignaler;
 
@@ -440,15 +448,9 @@
 
 #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, bool hasPresentTime, uint32_t presentID, uint64_t desiredPresentTime);
+	/** Presents the contained drawable to the OS. */
+	void presentCAMetalDrawable(id<MTLCommandBuffer> mtlCmdBuff,
+								MVKPresentTimingInfo presentTimingInfo);
 
 
 #pragma mark Construction
@@ -465,6 +467,7 @@
 	friend MVKSwapchain;
 
 	id<CAMetalDrawable> getCAMetalDrawable() override;
+	void presentCAMetalDrawable(MVKPresentTimingInfo presentTimingInfo);
 	void releaseMetalDrawable();
 	MVKSwapchainImageAvailability getAvailability();
 	void makeAvailable();
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
index 7e62de2..171f7da 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
@@ -1285,52 +1285,53 @@
 }
 
 // Present the drawable and make myself available only once the command buffer has completed.
-void MVKPresentableSwapchainImage::presentCAMetalDrawable(id<MTLCommandBuffer> mtlCmdBuff, bool hasPresentTime, uint32_t presentID, uint64_t desiredPresentTime) {
+void MVKPresentableSwapchainImage::presentCAMetalDrawable(id<MTLCommandBuffer> mtlCmdBuff,
+														  MVKPresentTimingInfo presentTimingInfo) {
+
 	_swapchain->willPresentSurface(getMTLTexture(0), mtlCmdBuff);
 
-	NSString* scName = _swapchain->getDebugName();
-	if (scName) { mvkPushDebugGroup(mtlCmdBuff, scName); }
-	if (!hasPresentTime) {
-		[mtlCmdBuff presentDrawable: getCAMetalDrawable()];
-	}
-	else {
-		// Convert from nsecs to seconds
-		CFTimeInterval presentTimeSeconds = ( double ) desiredPresentTime * 1.0e-9;
-		[mtlCmdBuff presentDrawable: getCAMetalDrawable() atTime:(CFTimeInterval)presentTimeSeconds];
-	}
-	if (scName) { mvkPopDebugGroup(mtlCmdBuff); }
+	[mtlCmdBuff addScheduledHandler: ^(id<MTLCommandBuffer> mcb) {
+		presentCAMetalDrawable(presentTimingInfo);
+	}];
 
-	signalPresentationSemaphore(mtlCmdBuff);
-
-	retain();	// Ensure this image is not destroyed while awaiting MTLCommandBuffer completion
+	// Ensure this image is not destroyed while awaiting MTLCommandBuffer completion
+	retain();
 	[mtlCmdBuff addCompletedHandler: ^(id<MTLCommandBuffer> mcb) {
 		makeAvailable();
 		release();
 	}];
-	
-	if (hasPresentTime) {
+
+	signalPresentationSemaphore(mtlCmdBuff);
+}
+
+// If MTLDrawable.presentedTime/addPresentedHandler isn't supported.
+// Treat it as if the present happened when requested.
+void MVKPresentableSwapchainImage::presentCAMetalDrawable(MVKPresentTimingInfo presentTimingInfo) {
+
+	id<CAMetalDrawable> mtlDrwbl = getCAMetalDrawable();
+
+	if (presentTimingInfo.hasPresentTime) {
+
+		// Convert from nsecs to seconds for Metal
+		[mtlDrwbl presentAtTime: (double)presentTimingInfo.desiredPresentTime * 1.0e-9];
+
 #if MVK_OS_SIMULATOR
-		// If MTLDrawable.presentedTime/addPresentedHandler isn't supported, just treat it as if the
-		// present happened when requested
-		_swapchain->recordPresentTime(presentID, desiredPresentTime, desiredPresentTime);
+		_swapchain->recordPresentTime(presentTimingInfo);
 #else
-		if ([_mtlDrawable respondsToSelector: @selector(addPresentedHandler:)]) {
-			retain();	// Ensure this image is not destroyed while awaiting presentation
-			[_mtlDrawable addPresentedHandler: ^(id<MTLDrawable> drawable) {
-				// Record the presentation time
-				CFTimeInterval presentedTimeSeconds = drawable.presentedTime;
-				uint64_t presentedTimeNanoseconds = (uint64_t)(presentedTimeSeconds * 1.0e9);
-				_swapchain->recordPresentTime(presentID, desiredPresentTime, presentedTimeNanoseconds);
+		if ([mtlDrwbl respondsToSelector: @selector(addPresentedHandler:)]) {
+			// Ensure this image is not destroyed while awaiting presentation
+			retain();
+			[mtlDrwbl addPresentedHandler: ^(id<MTLDrawable> drawable) {
+				_swapchain->recordPresentTime(presentTimingInfo, drawable.presentedTime * 1.0e9);
 				release();
 			}];
 		} else {
-			// If MTLDrawable.presentedTime/addPresentedHandler isn't supported, just treat it as if the
-			// present happened when requested
-			_swapchain->recordPresentTime(presentID, desiredPresentTime, desiredPresentTime);
+			_swapchain->recordPresentTime(presentTimingInfo);
 		}
 #endif
+	} else {
+		[mtlDrwbl present];
 	}
-
 }
 
 // Resets the MTLTexture and CAMetalDrawable underlying this image.
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.h b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.h
index 61ff838..0f798fc 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.h
@@ -255,13 +255,6 @@
 protected:
 	id<MTLCommandBuffer> getMTLCommandBuffer();
 
-	typedef struct  {
-		MVKPresentableSwapchainImage* presentableImage;
-		bool hasPresentTime;          // Keep track of whether present included VK_GOOGLE_display_timing
-		uint32_t presentID;           // VK_GOOGLE_display_timing presentID
-		uint64_t desiredPresentTime;  // VK_GOOGLE_display_timing desired presentation time in nanoseconds
-	} PresentInfo;
-
-	MVKSmallVector<PresentInfo, 4> _presentInfo;
+	MVKSmallVector<MVKPresentTimingInfo, 4> _presentInfo;
 };
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm
index 8eba6d8..a53754c 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm
@@ -353,7 +353,7 @@
 	for (auto& ws : _waitSemaphores) { ws->encodeWait(mtlCmdBuff); }
 	for (int i = 0; i < _presentInfo.size(); i++ ) {
 		MVKPresentableSwapchainImage *img = _presentInfo[i].presentableImage;
-		img->presentCAMetalDrawable(mtlCmdBuff, _presentInfo[i].hasPresentTime, _presentInfo[i].presentID, _presentInfo[i].desiredPresentTime);
+		img->presentCAMetalDrawable(mtlCmdBuff, _presentInfo[i]);
 	}
 	for (auto& ws : _waitSemaphores) { ws->encodeWait(nil); }
 	[mtlCmdBuff commit];
@@ -401,7 +401,7 @@
 	_presentInfo.reserve(scCnt);
 	for (uint32_t scIdx = 0; scIdx < scCnt; scIdx++) {
 		MVKSwapchain* mvkSC = (MVKSwapchain*)pPresentInfo->pSwapchains[scIdx];
-		PresentInfo presentInfo = {};
+		MVKPresentTimingInfo presentInfo = {};
 		presentInfo.presentableImage = mvkSC->getPresentableImage(pPresentInfo->pImageIndices[scIdx]);
 		if ( pPresentTimesGOOGLE ) {
 			presentInfo.hasPresentTime = true;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h
index a1fc360..e6e5ff1 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h
@@ -113,7 +113,7 @@
     void willPresentSurface(id<MTLTexture> mtlTexture, id<MTLCommandBuffer> mtlCmdBuff);
     void renderWatermark(id<MTLTexture> mtlTexture, id<MTLCommandBuffer> mtlCmdBuff);
     void markFrameInterval();
-	void recordPresentTime(uint32_t presentID, uint64_t desiredPresentTime, uint64_t actualPresentTime);
+	void recordPresentTime(MVKPresentTimingInfo presentTimingInfo, uint64_t actualPresentTime = 0);
 
 	CAMetalLayer* _mtlLayer;
     MVKWatermark* _licenseWatermark;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
index c2cbd32..8928d26 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
@@ -434,7 +434,7 @@
 	return VK_SUCCESS;
 }
 
-void MVKSwapchain::recordPresentTime(uint32_t presentID, uint64_t desiredPresentTime, uint64_t actualPresentTime) {
+void MVKSwapchain::recordPresentTime(MVKPresentTimingInfo presentTimingInfo, uint64_t actualPresentTime) {
 	std::lock_guard<std::mutex> lock(_presentHistoryLock);
 	if (_presentHistoryCount < kMaxPresentationHistory) {
 		_presentHistoryCount++;
@@ -442,8 +442,12 @@
 	} else {
 		_presentHistoryHeadIndex = (_presentHistoryHeadIndex + 1) % kMaxPresentationHistory;
 	}
-	_presentTimingHistory[_presentHistoryIndex].presentID = presentID;
-	_presentTimingHistory[_presentHistoryIndex].desiredPresentTime = desiredPresentTime;
+
+	// If actual time not supplied, use desired time instead
+	if (actualPresentTime == 0) { actualPresentTime = presentTimingInfo.desiredPresentTime; }
+
+	_presentTimingHistory[_presentHistoryIndex].presentID = presentTimingInfo.presentID;
+	_presentTimingHistory[_presentHistoryIndex].desiredPresentTime = presentTimingInfo.desiredPresentTime;
 	_presentTimingHistory[_presentHistoryIndex].actualPresentTime = actualPresentTime;
 	// These details are not available in Metal
 	_presentTimingHistory[_presentHistoryIndex].earliestPresentTime = actualPresentTime;