Merge pull request #1092 from billhollings/drawable-release

Fix potential drawable present race conditions.
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
index 3986320..aa1417a 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
@@ -467,7 +467,7 @@
 	friend MVKSwapchain;
 
 	id<CAMetalDrawable> getCAMetalDrawable() override;
-	void presentCAMetalDrawable(MVKPresentTimingInfo presentTimingInfo);
+	void presentCAMetalDrawable(id<CAMetalDrawable> mtlDrawable, MVKPresentTimingInfo presentTimingInfo);
 	void releaseMetalDrawable();
 	MVKSwapchainImageAvailability getAvailability();
 	void makeAvailable();
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
index c7c15b7..d742c73 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
@@ -1292,8 +1292,10 @@
 
 	_swapchain->willPresentSurface(getMTLTexture(0), mtlCmdBuff);
 
+	// Get current drawable now. Don't retrieve in handler, because a new drawable might be acquired by then.
+	id<CAMetalDrawable> mtlDrwbl = getCAMetalDrawable();
 	[mtlCmdBuff addScheduledHandler: ^(id<MTLCommandBuffer> mcb) {
-		presentCAMetalDrawable(presentTimingInfo);
+		presentCAMetalDrawable(mtlDrwbl, presentTimingInfo);
 	}];
 
 	// Ensure this image is not destroyed while awaiting MTLCommandBuffer completion
@@ -1306,24 +1308,21 @@
 	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();
+void MVKPresentableSwapchainImage::presentCAMetalDrawable(id<CAMetalDrawable> mtlDrawable,
+														  MVKPresentTimingInfo presentTimingInfo) {
 
 	if (presentTimingInfo.hasPresentTime) {
 
-		// Convert from nsecs to seconds for Metal
-		[mtlDrwbl presentAtTime: (double)presentTimingInfo.desiredPresentTime * 1.0e-9];
-
+		// Attach present handler before presenting to avoid race condition.
+		// If MTLDrawable.presentedTime/addPresentedHandler isn't supported,
+		// treat it as if the present happened when requested.
 #if MVK_OS_SIMULATOR
 		_swapchain->recordPresentTime(presentTimingInfo);
 #else
-		if ([mtlDrwbl respondsToSelector: @selector(addPresentedHandler:)]) {
+		if ([mtlDrawable respondsToSelector: @selector(addPresentedHandler:)]) {
 			// Ensure this image is not destroyed while awaiting presentation
 			retain();
-			[mtlDrwbl addPresentedHandler: ^(id<MTLDrawable> drawable) {
+			[mtlDrawable addPresentedHandler: ^(id<MTLDrawable> drawable) {
 				_swapchain->recordPresentTime(presentTimingInfo, drawable.presentedTime * 1.0e9);
 				release();
 			}];
@@ -1331,8 +1330,10 @@
 			_swapchain->recordPresentTime(presentTimingInfo);
 		}
 #endif
+		// Convert from nsecs to seconds for Metal
+		[mtlDrawable presentAtTime: (double)presentTimingInfo.desiredPresentTime * 1.0e-9];
 	} else {
-		[mtlDrwbl present];
+		[mtlDrawable present];
 	}
 }