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;