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.
This commit is contained in:
Bill Hollings 2020-10-03 16:11:25 -04:00
parent 766e05b70c
commit 1b4c045707
7 changed files with 57 additions and 54 deletions

View File

@ -21,6 +21,8 @@ Released TBD
- Use `VK_KHR_image_format_list` to disable `MTLTextureUsagePixelFormatView` - Use `VK_KHR_image_format_list` to disable `MTLTextureUsagePixelFormatView`
if only swizzles or `sRGB` conversion will be used for image views, improving if only swizzles or `sRGB` conversion will be used for image views, improving
performance on *iOS* by allowing Metal to use lossless texture compression. 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.

View File

@ -429,6 +429,14 @@ typedef struct MVKSwapchainImageAvailability {
bool operator< (const MVKSwapchainImageAvailability& rhs) const; bool operator< (const MVKSwapchainImageAvailability& rhs) const;
} MVKSwapchainImageAvailability; } 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. */ /** Tracks a semaphore and fence for later signaling. */
typedef std::pair<MVKSemaphore*, MVKFence*> MVKSwapchainSignaler; typedef std::pair<MVKSemaphore*, MVKFence*> MVKSwapchainSignaler;
@ -440,15 +448,9 @@ public:
#pragma mark Metal #pragma mark Metal
/** /** Presents the contained drawable to the OS. */
* Presents the contained drawable to the OS, releases the Metal drawable and its void presentCAMetalDrawable(id<MTLCommandBuffer> mtlCmdBuff,
* texture back to the Metal layer's pool, and makes the image memory available for new use. MVKPresentTimingInfo presentTimingInfo);
*
* 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);
#pragma mark Construction #pragma mark Construction
@ -465,6 +467,7 @@ protected:
friend MVKSwapchain; friend MVKSwapchain;
id<CAMetalDrawable> getCAMetalDrawable() override; id<CAMetalDrawable> getCAMetalDrawable() override;
void presentCAMetalDrawable(MVKPresentTimingInfo presentTimingInfo);
void releaseMetalDrawable(); void releaseMetalDrawable();
MVKSwapchainImageAvailability getAvailability(); MVKSwapchainImageAvailability getAvailability();
void makeAvailable(); void makeAvailable();

View File

@ -1285,52 +1285,53 @@ id<CAMetalDrawable> MVKPresentableSwapchainImage::getCAMetalDrawable() {
} }
// Present the drawable and make myself available only once the command buffer has completed. // 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); _swapchain->willPresentSurface(getMTLTexture(0), mtlCmdBuff);
NSString* scName = _swapchain->getDebugName(); [mtlCmdBuff addScheduledHandler: ^(id<MTLCommandBuffer> mcb) {
if (scName) { mvkPushDebugGroup(mtlCmdBuff, scName); } presentCAMetalDrawable(presentTimingInfo);
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); }
signalPresentationSemaphore(mtlCmdBuff); // Ensure this image is not destroyed while awaiting MTLCommandBuffer completion
retain();
retain(); // Ensure this image is not destroyed while awaiting MTLCommandBuffer completion
[mtlCmdBuff addCompletedHandler: ^(id<MTLCommandBuffer> mcb) { [mtlCmdBuff addCompletedHandler: ^(id<MTLCommandBuffer> mcb) {
makeAvailable(); makeAvailable();
release(); 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 MVK_OS_SIMULATOR
// If MTLDrawable.presentedTime/addPresentedHandler isn't supported, just treat it as if the _swapchain->recordPresentTime(presentTimingInfo);
// present happened when requested
_swapchain->recordPresentTime(presentID, desiredPresentTime, desiredPresentTime);
#else #else
if ([_mtlDrawable respondsToSelector: @selector(addPresentedHandler:)]) { if ([mtlDrwbl respondsToSelector: @selector(addPresentedHandler:)]) {
retain(); // Ensure this image is not destroyed while awaiting presentation // Ensure this image is not destroyed while awaiting presentation
[_mtlDrawable addPresentedHandler: ^(id<MTLDrawable> drawable) { retain();
// Record the presentation time [mtlDrwbl addPresentedHandler: ^(id<MTLDrawable> drawable) {
CFTimeInterval presentedTimeSeconds = drawable.presentedTime; _swapchain->recordPresentTime(presentTimingInfo, drawable.presentedTime * 1.0e9);
uint64_t presentedTimeNanoseconds = (uint64_t)(presentedTimeSeconds * 1.0e9);
_swapchain->recordPresentTime(presentID, desiredPresentTime, presentedTimeNanoseconds);
release(); release();
}]; }];
} else { } else {
// If MTLDrawable.presentedTime/addPresentedHandler isn't supported, just treat it as if the _swapchain->recordPresentTime(presentTimingInfo);
// present happened when requested
_swapchain->recordPresentTime(presentID, desiredPresentTime, desiredPresentTime);
} }
#endif #endif
} else {
[mtlDrwbl present];
} }
} }
// Resets the MTLTexture and CAMetalDrawable underlying this image. // Resets the MTLTexture and CAMetalDrawable underlying this image.

View File

@ -255,13 +255,6 @@ public:
protected: protected:
id<MTLCommandBuffer> getMTLCommandBuffer(); id<MTLCommandBuffer> getMTLCommandBuffer();
typedef struct { MVKSmallVector<MVKPresentTimingInfo, 4> _presentInfo;
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;
}; };

View File

@ -353,7 +353,7 @@ void MVKQueuePresentSurfaceSubmission::execute() {
for (auto& ws : _waitSemaphores) { ws->encodeWait(mtlCmdBuff); } for (auto& ws : _waitSemaphores) { ws->encodeWait(mtlCmdBuff); }
for (int i = 0; i < _presentInfo.size(); i++ ) { for (int i = 0; i < _presentInfo.size(); i++ ) {
MVKPresentableSwapchainImage *img = _presentInfo[i].presentableImage; 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); } for (auto& ws : _waitSemaphores) { ws->encodeWait(nil); }
[mtlCmdBuff commit]; [mtlCmdBuff commit];
@ -401,7 +401,7 @@ MVKQueuePresentSurfaceSubmission::MVKQueuePresentSurfaceSubmission(MVKQueue* que
_presentInfo.reserve(scCnt); _presentInfo.reserve(scCnt);
for (uint32_t scIdx = 0; scIdx < scCnt; scIdx++) { for (uint32_t scIdx = 0; scIdx < scCnt; scIdx++) {
MVKSwapchain* mvkSC = (MVKSwapchain*)pPresentInfo->pSwapchains[scIdx]; MVKSwapchain* mvkSC = (MVKSwapchain*)pPresentInfo->pSwapchains[scIdx];
PresentInfo presentInfo = {}; MVKPresentTimingInfo presentInfo = {};
presentInfo.presentableImage = mvkSC->getPresentableImage(pPresentInfo->pImageIndices[scIdx]); presentInfo.presentableImage = mvkSC->getPresentableImage(pPresentInfo->pImageIndices[scIdx]);
if ( pPresentTimesGOOGLE ) { if ( pPresentTimesGOOGLE ) {
presentInfo.hasPresentTime = true; presentInfo.hasPresentTime = true;

View File

@ -113,7 +113,7 @@ protected:
void willPresentSurface(id<MTLTexture> mtlTexture, id<MTLCommandBuffer> mtlCmdBuff); void willPresentSurface(id<MTLTexture> mtlTexture, id<MTLCommandBuffer> mtlCmdBuff);
void renderWatermark(id<MTLTexture> mtlTexture, id<MTLCommandBuffer> mtlCmdBuff); void renderWatermark(id<MTLTexture> mtlTexture, id<MTLCommandBuffer> mtlCmdBuff);
void markFrameInterval(); void markFrameInterval();
void recordPresentTime(uint32_t presentID, uint64_t desiredPresentTime, uint64_t actualPresentTime); void recordPresentTime(MVKPresentTimingInfo presentTimingInfo, uint64_t actualPresentTime = 0);
CAMetalLayer* _mtlLayer; CAMetalLayer* _mtlLayer;
MVKWatermark* _licenseWatermark; MVKWatermark* _licenseWatermark;

View File

@ -434,7 +434,7 @@ VkResult MVKSwapchain::getPastPresentationTiming(uint32_t *pCount, VkPastPresent
return VK_SUCCESS; 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); std::lock_guard<std::mutex> lock(_presentHistoryLock);
if (_presentHistoryCount < kMaxPresentationHistory) { if (_presentHistoryCount < kMaxPresentationHistory) {
_presentHistoryCount++; _presentHistoryCount++;
@ -442,8 +442,12 @@ void MVKSwapchain::recordPresentTime(uint32_t presentID, uint64_t desiredPresent
} else { } else {
_presentHistoryHeadIndex = (_presentHistoryHeadIndex + 1) % kMaxPresentationHistory; _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; _presentTimingHistory[_presentHistoryIndex].actualPresentTime = actualPresentTime;
// These details are not available in Metal // These details are not available in Metal
_presentTimingHistory[_presentHistoryIndex].earliestPresentTime = actualPresentTime; _presentTimingHistory[_presentHistoryIndex].earliestPresentTime = actualPresentTime;