Merge pull request #2020 from billhollings/incomplete-presentation-workaround-fix

Rework workaround to force incomplete CAMetalDrawable presentations to complete.
This commit is contained in:
Bill Hollings 2023-09-15 19:23:31 -04:00 committed by GitHub
commit aed91cb563
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 118 additions and 147 deletions

View File

@ -260,7 +260,7 @@ bool MVKCommandBuffer::canExecute() {
}
_wasExecuted = true;
return true;
return wasConfigurationSuccessful();
}
// Return the number of bits set in the view mask, with a minimum value of 1.

View File

@ -460,10 +460,9 @@ public:
void beginPresentation(const MVKImagePresentInfo& presentInfo);
/** Called via callback when the presentation completes. */
void endPresentation(const MVKImagePresentInfo& presentInfo, uint64_t actualPresentTime = 0);
/** If this image is stuck in-flight, attempt to force it to complete. */
void forcePresentationCompletion();
void endPresentation(const MVKImagePresentInfo& presentInfo,
const MVKSwapchainSignaler& signaler,
uint64_t actualPresentTime = 0);
#pragma mark Construction
@ -478,12 +477,13 @@ protected:
friend MVKSwapchain;
id<CAMetalDrawable> getCAMetalDrawable() override;
void addPresentedHandler(id<CAMetalDrawable> mtlDrawable, MVKImagePresentInfo presentInfo);
void addPresentedHandler(id<CAMetalDrawable> mtlDrawable, MVKImagePresentInfo presentInfo, MVKSwapchainSignaler signaler);
void releaseMetalDrawable();
MVKSwapchainImageAvailability getAvailability();
void makeAvailable(const MVKSwapchainSignaler& signaler);
void makeAvailable();
VkResult acquireAndSignalWhenAvailable(MVKSemaphore* semaphore, MVKFence* fence);
MVKSwapchainSignaler getPresentationSignaler();
id<CAMetalDrawable> _mtlDrawable = nil;
MVKSwapchainImageAvailability _availability;

View File

@ -1248,16 +1248,18 @@ static void signalAndUnmarkAsTracked(const MVKSwapchainSignaler& signaler) {
}
VkResult MVKPresentableSwapchainImage::acquireAndSignalWhenAvailable(MVKSemaphore* semaphore, MVKFence* fence) {
// Now that this image is being acquired, release the existing drawable and its texture.
// This is not done earlier so the texture is retained for any post-processing such as screen captures, etc.
// This may trigger a delayed presentation callback, which uses the _availabilityLock, also used below.
releaseMetalDrawable();
lock_guard<mutex> lock(_availabilityLock);
// Upon acquisition, update acquisition ID immediately, to move it to the back of the chain,
// so other images will be preferred if either all images are available or no images are available.
_availability.acquisitionID = _swapchain->getNextAcquisitionID();
// Now that this image is being acquired, release the existing drawable and its texture.
// This is not done earlier so the texture is retained for any post-processing such as screen captures, etc.
releaseMetalDrawable();
auto signaler = MVKSwapchainSignaler{fence, semaphore, semaphore ? semaphore->deferSignal() : 0};
if (_availability.isAvailable) {
_availability.isAvailable = false;
@ -1292,10 +1294,10 @@ id<CAMetalDrawable> MVKPresentableSwapchainImage::getCAMetalDrawable() {
if ( !_mtlDrawable ) {
@autoreleasepool {
bool hasInvalidFormat = false;
uint32_t attemptCnt = _swapchain->getImageCount() * 2; // Attempt a resonable number of times
uint32_t attemptCnt = _swapchain->getImageCount(); // Attempt a resonable number of times
for (uint32_t attemptIdx = 0; !_mtlDrawable && attemptIdx < attemptCnt; attemptIdx++) {
uint64_t startTime = _device->getPerformanceTimestamp();
_mtlDrawable = [_swapchain->_surface->getCAMetalLayer().nextDrawable retain]; // retained
_mtlDrawable = [_swapchain->getCAMetalLayer().nextDrawable retain]; // retained
_device->addPerformanceInterval(_device->_performanceStatistics.queue.retrieveCAMetalDrawable, startTime);
hasInvalidFormat = _mtlDrawable && !_mtlDrawable.texture.pixelFormat;
if (hasInvalidFormat) { releaseMetalDrawable(); }
@ -1314,8 +1316,6 @@ id<CAMetalDrawable> MVKPresentableSwapchainImage::getCAMetalDrawable() {
// Pass MVKImagePresentInfo by value because it may not exist when the callback runs.
VkResult MVKPresentableSwapchainImage::presentCAMetalDrawable(id<MTLCommandBuffer> mtlCmdBuff,
MVKImagePresentInfo presentInfo) {
lock_guard<mutex> lock(_availabilityLock);
_swapchain->renderWatermark(getMTLTexture(0), mtlCmdBuff);
// According to Apple, it is more performant to call MTLDrawable present from within a
@ -1323,8 +1323,11 @@ VkResult MVKPresentableSwapchainImage::presentCAMetalDrawable(id<MTLCommandBuffe
// But get current drawable now, intead of in handler, because a new drawable might be acquired by then.
// Attach present handler before presenting to avoid race condition.
id<CAMetalDrawable> mtlDrwbl = getCAMetalDrawable();
addPresentedHandler(mtlDrwbl, presentInfo);
MVKSwapchainSignaler signaler = getPresentationSignaler();
[mtlCmdBuff addScheduledHandler: ^(id<MTLCommandBuffer> mcb) {
addPresentedHandler(mtlDrwbl, presentInfo, signaler);
// Try to do any present mode transitions as late as possible in an attempt
// to avoid visual disruptions on any presents already on the queue.
if (presentInfo.presentMode != VK_PRESENT_MODE_MAX_ENUM_KHR) {
@ -1337,7 +1340,32 @@ VkResult MVKPresentableSwapchainImage::presentCAMetalDrawable(id<MTLCommandBuffe
}
}];
MVKSwapchainSignaler signaler;
// Ensure this image, the drawable, and the present fence are not destroyed while
// awaiting MTLCommandBuffer completion. We retain the drawable separately because
// a new drawable might be acquired by this image by then.
// Signal the fence from this callback, because the last one or two presentation
// completion callbacks can occasionally stall.
retain();
[mtlDrwbl retain];
auto* fence = presentInfo.fence;
if (fence) { fence->retain(); }
[mtlCmdBuff addCompletedHandler: ^(id<MTLCommandBuffer> mcb) {
if (fence) {
fence->signal();
fence->release();
}
[mtlDrwbl release];
release();
}];
signalPresentationSemaphore(signaler, mtlCmdBuff);
return getConfigurationResult();
}
MVKSwapchainSignaler MVKPresentableSwapchainImage::getPresentationSignaler() {
lock_guard<mutex> lock(_availabilityLock);
// Mark this image as available if no semaphores or fences are waiting to be signaled.
_availability.isAvailable = _availabilitySignalers.empty();
if (_availability.isAvailable) {
@ -1346,52 +1374,34 @@ VkResult MVKPresentableSwapchainImage::presentCAMetalDrawable(id<MTLCommandBuffe
// when an app uses a single semaphore or fence for more than one swapchain image.
// Because 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;
return _preSignaler;
} else {
// If this image is not yet available, extract and signal the first semaphore and fence.
MVKSwapchainSignaler signaler;
auto sigIter = _availabilitySignalers.begin();
signaler = *sigIter;
_availabilitySignalers.erase(sigIter);
return signaler;
}
// Ensure this image, the drawable, and the present fence are not destroyed while
// awaiting MTLCommandBuffer completion. We retain the drawable separately because
// a new drawable might be acquired by this image by then.
retain();
[mtlDrwbl retain];
auto* fence = presentInfo.fence;
if (fence) { fence->retain(); }
[mtlCmdBuff addCompletedHandler: ^(id<MTLCommandBuffer> mcb) {
[mtlDrwbl release];
makeAvailable(signaler);
release();
if (fence) {
fence->signal();
fence->release();
}
}];
signalPresentationSemaphore(signaler, mtlCmdBuff);
return getConfigurationResult();
}
// Pass MVKImagePresentInfo by value because it may not exist when the callback runs.
// Pass MVKImagePresentInfo & MVKSwapchainSignaler by value because they may not exist when the callback runs.
void MVKPresentableSwapchainImage::addPresentedHandler(id<CAMetalDrawable> mtlDrawable,
MVKImagePresentInfo presentInfo) {
MVKImagePresentInfo presentInfo,
MVKSwapchainSignaler signaler) {
beginPresentation(presentInfo);
#if !MVK_OS_SIMULATOR
if ([mtlDrawable respondsToSelector: @selector(addPresentedHandler:)]) {
[mtlDrawable addPresentedHandler: ^(id<MTLDrawable> mtlDrwbl) {
endPresentation(presentInfo, mtlDrwbl.presentedTime * 1.0e9);
endPresentation(presentInfo, signaler, mtlDrwbl.presentedTime * 1.0e9);
}];
} else
#endif
{
// If MTLDrawable.presentedTime/addPresentedHandler isn't supported,
// treat it as if the present happened when requested.
endPresentation(presentInfo);
endPresentation(presentInfo, signaler);
}
}
@ -1399,11 +1409,11 @@ void MVKPresentableSwapchainImage::addPresentedHandler(id<CAMetalDrawable> mtlDr
void MVKPresentableSwapchainImage::beginPresentation(const MVKImagePresentInfo& presentInfo) {
retain();
_swapchain->beginPresentation(presentInfo);
presentInfo.queue->beginPresentation(presentInfo);
_presentationStartTime = getDevice()->getPerformanceTimestamp();
}
void MVKPresentableSwapchainImage::endPresentation(const MVKImagePresentInfo& presentInfo,
const MVKSwapchainSignaler& signaler,
uint64_t actualPresentTime) {
{ // Scope to avoid deadlock if release() is run within detachment lock
// If I have become detached from the swapchain, it means the swapchain, and possibly the
@ -1412,7 +1422,7 @@ void MVKPresentableSwapchainImage::endPresentation(const MVKImagePresentInfo& pr
if (_device) { _device->addPerformanceInterval(_device->_performanceStatistics.queue.presentSwapchains, _presentationStartTime); }
if (_swapchain) { _swapchain->endPresentation(presentInfo, actualPresentTime); }
}
presentInfo.queue->endPresentation(presentInfo);
makeAvailable(signaler);
release();
}
@ -1432,7 +1442,9 @@ void MVKPresentableSwapchainImage::makeAvailable(const MVKSwapchainSignaler& sig
}
// Signal, untrack, and release any signalers that are tracking.
// Release the drawable before the lock, as it may trigger completion callback.
void MVKPresentableSwapchainImage::makeAvailable() {
releaseMetalDrawable();
lock_guard<mutex> lock(_availabilityLock);
if ( !_availability.isAvailable ) {
@ -1445,14 +1457,6 @@ void MVKPresentableSwapchainImage::makeAvailable() {
}
}
// Clear the existing CAMetalDrawable and retrieve and release a new transient one,
// in an attempt to trigger the existing CAMetalDrawable to complete it's callback.
void MVKPresentableSwapchainImage::forcePresentationCompletion() {
releaseMetalDrawable();
if (_swapchain) { @autoreleasepool { [_swapchain->_surface->getCAMetalLayer() nextDrawable]; } }
}
#pragma mark Construction
MVKPresentableSwapchainImage::MVKPresentableSwapchainImage(MVKDevice* device,
@ -1467,14 +1471,13 @@ MVKPresentableSwapchainImage::MVKPresentableSwapchainImage(MVKDevice* device,
void MVKPresentableSwapchainImage::destroy() {
forcePresentationCompletion();
releaseMetalDrawable();
MVKSwapchainImage::destroy();
}
// Unsignaled signalers will exist if this image is acquired more than it is presented.
// Ensure they are signaled and untracked so the fences and semaphores will be released.
MVKPresentableSwapchainImage::~MVKPresentableSwapchainImage() {
releaseMetalDrawable();
makeAvailable();
}

View File

@ -100,13 +100,6 @@ public:
/** Block the current thread until this queue is idle. */
VkResult waitIdle(MVKCommandUse cmdUse);
/** Mark the beginning of a swapchain image presentation. */
void beginPresentation(const MVKImagePresentInfo& presentInfo);
/** Mark the end of a swapchain image presentation. */
void endPresentation(const MVKImagePresentInfo& presentInfo);
#pragma mark Metal
/** Returns the Metal queue underlying this queue. */
@ -150,11 +143,8 @@ protected:
VkResult submit(MVKQueueSubmission* qSubmit);
NSString* getMTLCommandBufferLabel(MVKCommandUse cmdUse);
void handleMTLCommandBufferError(id<MTLCommandBuffer> mtlCmdBuff);
void waitSwapchainPresentations(MVKCommandUse cmdUse);
MVKQueueFamily* _queueFamily;
MVKSemaphoreImpl _presentationCompletionBlocker;
std::unordered_map<MVKPresentableSwapchainImage*, uint32_t> _presentedImages;
std::string _name;
dispatch_queue_t _execQueue;
id<MTLCommandQueue> _mtlQueue = nil;
@ -166,7 +156,6 @@ protected:
NSString* _mtlCmdBuffLabelAcquireNextImage = nil;
NSString* _mtlCmdBuffLabelInvalidateMappedMemoryRanges = nil;
MVKGPUCaptureScope* _submissionCaptureScope = nil;
std::mutex _presentedImagesLock;
float _priority;
uint32_t _index;
};

View File

@ -80,13 +80,14 @@ VkResult MVKQueue::submit(MVKQueueSubmission* qSubmit) {
if ( !qSubmit ) { return VK_SUCCESS; } // Ignore nils
VkResult rslt = qSubmit->getConfigurationResult(); // Extract result before submission to avoid race condition with early destruction
if (rslt == VK_SUCCESS) {
if (_execQueue) {
dispatch_async(_execQueue, ^{ execute(qSubmit); } );
} else {
rslt = execute(qSubmit);
}
// Extract result before submission to avoid race condition with early destruction
// Submit regardless of config result, to ensure submission semaphores and fences are signalled.
// The submissions will ensure a misconfiguration will be safe to execute.
VkResult rslt = qSubmit->getConfigurationResult();
if (_execQueue) {
dispatch_async(_execQueue, ^{ execute(qSubmit); } );
} else {
rslt = execute(qSubmit);
}
return rslt;
}
@ -140,50 +141,9 @@ VkResult MVKQueue::waitIdle(MVKCommandUse cmdUse) {
[mtlCmdBuff commit];
[mtlCmdBuff waitUntilCompleted];
waitSwapchainPresentations(cmdUse);
return VK_SUCCESS;
}
// If there are any swapchain presentations in flight, wait a few frames for them to complete.
// If they don't complete within a few frames, attempt to force them to complete, and wait another
// few frames for that to happen. If there are still swapchain presentations that haven't completed,
// log a warning, and force them to end presentation, so the images and drawables will be released.
void MVKQueue::waitSwapchainPresentations(MVKCommandUse cmdUse) {
uint32_t waitFrames = _device->_pMetalFeatures->maxSwapchainImageCount + 2;
uint64_t waitNanos = waitFrames * _device->_performanceStatistics.queue.frameInterval.average * 1e6;
if (_presentationCompletionBlocker.wait(waitNanos)) { return; }
auto imgCnt = _presentationCompletionBlocker.getReservationCount();
MVKPresentableSwapchainImage* images[imgCnt];
mvkClear(images, imgCnt);
{
// Scope of image lock limited to creating array copy of uncompleted presentations
// Populate a working array of the unpresented images.
lock_guard<mutex> lock(_presentedImagesLock);
size_t imgIdx = 0;
for (auto imgPair : _presentedImages) { images[imgIdx++] = imgPair.first; }
}
// Attempt to force each image to complete presentation through the callback.
for (size_t imgIdx = 0; imgIdx < imgCnt && _presentationCompletionBlocker.getReservationCount(); imgIdx++) {
auto* img = images[imgIdx];
if (img) { img->forcePresentationCompletion(); }
}
// Wait for forced presentation completions. If we still have unfinished swapchain image
// presentations, log a warning, and force each image to end, so that it can be released.
if ( !_presentationCompletionBlocker.wait(waitNanos) ) {
reportWarning(VK_TIMEOUT, "%s timed out after %d frames while awaiting %d swapchain image presentations to complete.",
mvkVkCommandName(cmdUse), waitFrames * 2, _presentationCompletionBlocker.getReservationCount());
for (size_t imgIdx = 0; imgIdx < imgCnt; imgIdx++) {
auto* img = images[imgIdx];
if (_presentedImages.count(img)) { img->endPresentation({.queue = this, .presentableImage = img}); }
}
}
}
id<MTLCommandBuffer> MVKQueue::getMTLCommandBuffer(MVKCommandUse cmdUse, bool retainRefs) {
id<MTLCommandBuffer> mtlCmdBuff = nil;
MVKDevice* mvkDev = getDevice();
@ -312,25 +272,6 @@ void MVKQueue::handleMTLCommandBufferError(id<MTLCommandBuffer> mtlCmdBuff) {
#endif
}
// _presentedImages counts presentations per swapchain image, because the presentation of an image can
// begin before the previous presentation of that image has indicated that it has completed via a callback.
void MVKQueue::beginPresentation(const MVKImagePresentInfo& presentInfo) {
lock_guard<mutex> lock(_presentedImagesLock);
_presentationCompletionBlocker.reserve();
_presentedImages[presentInfo.presentableImage]++;
}
void MVKQueue::endPresentation(const MVKImagePresentInfo& presentInfo) {
lock_guard<mutex> lock(_presentedImagesLock);
_presentationCompletionBlocker.release();
if (_presentedImages[presentInfo.presentableImage]) {
_presentedImages[presentInfo.presentableImage]--;
}
if ( !_presentedImages[presentInfo.presentableImage] ) {
_presentedImages.erase(presentInfo.presentableImage);
}
}
#pragma mark Construction
#define MVK_DISPATCH_QUEUE_QOS_CLASS QOS_CLASS_USER_INITIATED
@ -488,7 +429,7 @@ VkResult MVKQueueCommandBufferSubmission::commitActiveMTLCommandBuffer(bool sign
// If we need to signal completion, use getActiveMTLCommandBuffer() to ensure at least
// one MTLCommandBuffer is used, otherwise if this instance has no content, it will not
// finish(), signal the fence and semaphores ,and be destroyed.
// finish(), signal the fence and semaphores, and be destroyed.
// Use temp var for MTLCommandBuffer commit and release because completion callback
// may destroy this instance before this function ends.
id<MTLCommandBuffer> mtlCmdBuff = signalCompletion ? getActiveMTLCommandBuffer() : _activeMTLCommandBuffer;
@ -501,6 +442,8 @@ VkResult MVKQueueCommandBufferSubmission::commitActiveMTLCommandBuffer(bool sign
if (signalCompletion) { this->finish(); } // Must be the last thing the completetion callback does.
}];
// Retrieve the result before committing MTLCommandBuffer, because finish() will destroy this instance.
VkResult rslt = mtlCmdBuff ? getConfigurationResult() : VK_ERROR_OUT_OF_POOL_MEMORY;
[mtlCmdBuff commit];
[mtlCmdBuff release]; // retained
@ -508,7 +451,7 @@ VkResult MVKQueueCommandBufferSubmission::commitActiveMTLCommandBuffer(bool sign
// was not created, call the finish() function directly.
if (signalCompletion && !mtlCmdBuff) { finish(); }
return mtlCmdBuff ? VK_SUCCESS : VK_ERROR_OUT_OF_POOL_MEMORY;
return rslt;
}
// Be sure to retain() any API objects referenced in this function, and release() them in the

View File

@ -43,11 +43,14 @@ public:
/** Returns the debug report object type of this object. */
VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_SWAPCHAIN_KHR_EXT; }
/** Returns the CAMetalLayer underlying the surface used by this swapchain. */
CAMetalLayer* getCAMetalLayer();
/** Returns the number of images in this swapchain. */
inline uint32_t getImageCount() { return (uint32_t)_presentableImages.size(); }
uint32_t getImageCount() { return (uint32_t)_presentableImages.size(); }
/** Returns the image at the specified index. */
inline MVKPresentableSwapchainImage* getPresentableImage(uint32_t index) { return _presentableImages[index]; }
MVKPresentableSwapchainImage* getPresentableImage(uint32_t index) { return _presentableImages[index]; }
/**
* Returns the array of presentable images associated with this swapchain.
@ -112,6 +115,7 @@ protected:
void markFrameInterval();
void beginPresentation(const MVKImagePresentInfo& presentInfo);
void endPresentation(const MVKImagePresentInfo& presentInfo, uint64_t actualPresentTime = 0);
void forceUnpresentedImageCompletion();
MVKSurface* _surface = nullptr;
MVKWatermark* _licenseWatermark = nullptr;
@ -123,6 +127,7 @@ protected:
std::mutex _presentHistoryLock;
uint64_t _lastFrameTime = 0;
VkExtent2D _mtlLayerDrawableExtent = {0, 0};
std::atomic<uint32_t> _unpresentedImageCount = 0;
uint32_t _currentPerfLogFrameCount = 0;
uint32_t _presentHistoryCount = 0;
uint32_t _presentHistoryIndex = 0;

View File

@ -47,6 +47,8 @@ void MVKSwapchain::propagateDebugName() {
}
}
CAMetalLayer* MVKSwapchain::getCAMetalLayer() { return _surface->getCAMetalLayer(); }
VkResult MVKSwapchain::getImages(uint32_t* pCount, VkImage* pSwapchainImages) {
// Get the number of surface images
@ -104,7 +106,7 @@ VkResult MVKSwapchain::releaseImages(const VkReleaseSwapchainImagesInfoEXT* pRel
getPresentableImage(pReleaseInfo->pImageIndices[imgIdxIdx])->makeAvailable();
}
return VK_SUCCESS;
return _surface->getConfigurationResult();
}
uint64_t MVKSwapchain::getNextAcquisitionID() { return ++_currentAcquisitionID; }
@ -128,7 +130,7 @@ VkResult MVKSwapchain::getSurfaceStatus() {
bool MVKSwapchain::hasOptimalSurface() {
if (_isDeliberatelyScaled) { return true; }
auto* mtlLayer = _surface->getCAMetalLayer();
auto* mtlLayer = getCAMetalLayer();
VkExtent2D drawExtent = mvkVkExtent2DFromCGSize(mtlLayer.drawableSize);
return (mvkVkExtent2DsAreEqual(drawExtent, _mtlLayerDrawableExtent) &&
mvkVkExtent2DsAreEqual(drawExtent, mvkGetNaturalExtent(mtlLayer)));
@ -185,7 +187,7 @@ void MVKSwapchain::markFrameInterval() {
VkResult MVKSwapchain::getRefreshCycleDuration(VkRefreshCycleDurationGOOGLE *pRefreshCycleDuration) {
if (_device->getConfigurationResult() != VK_SUCCESS) { return _device->getConfigurationResult(); }
auto* mtlLayer = _surface->getCAMetalLayer();
auto* mtlLayer = getCAMetalLayer();
#if MVK_VISIONOS
// TODO: See if this can be obtained from OS instead
NSInteger framesPerSecond = 90;
@ -242,9 +244,13 @@ VkResult MVKSwapchain::getPastPresentationTiming(uint32_t *pCount, VkPastPresent
return res;
}
void MVKSwapchain::beginPresentation(const MVKImagePresentInfo& presentInfo) {}
void MVKSwapchain::beginPresentation(const MVKImagePresentInfo& presentInfo) {
_unpresentedImageCount++;
}
void MVKSwapchain::endPresentation(const MVKImagePresentInfo& presentInfo, uint64_t actualPresentTime) {
_unpresentedImageCount--;
std::lock_guard<std::mutex> lock(_presentHistoryLock);
markFrameInterval();
@ -269,8 +275,18 @@ void MVKSwapchain::endPresentation(const MVKImagePresentInfo& presentInfo, uint6
_presentHistoryIndex = (_presentHistoryIndex + 1) % kMaxPresentationHistory;
}
// Because of a regression in Metal, the most recent one or two presentations may not complete
// and call back. To work around this, if there are any uncompleted presentations, change the
// drawableSize of the CAMetalLayer, which will trigger presentation completion and callbacks.
// The drawableSize will be set to a correct size by the next swapchain created on the same surface.
void MVKSwapchain::forceUnpresentedImageCompletion() {
if (_unpresentedImageCount) {
getCAMetalLayer().drawableSize = { 1,1 };
}
}
void MVKSwapchain::setLayerNeedsDisplay(const VkPresentRegionKHR* pRegion) {
auto* mtlLayer = _surface->getCAMetalLayer();
auto* mtlLayer = getCAMetalLayer();
if (!pRegion || pRegion->rectangleCount == 0) {
[mtlLayer setNeedsDisplay];
return;
@ -350,7 +366,7 @@ void MVKSwapchain::setHDRMetadataEXT(const VkHdrMetadataEXT& metadata) {
CAEDRMetadata* caMetadata = [CAEDRMetadata HDR10MetadataWithDisplayInfo: colorVolData
contentInfo: lightLevelData
opticalOutputScale: 1];
auto* mtlLayer = _surface->getCAMetalLayer();
auto* mtlLayer = getCAMetalLayer();
mtlLayer.EDRMetadata = caMetadata;
mtlLayer.wantsExtendedDynamicRangeContent = YES;
[caMetadata release];
@ -456,7 +472,7 @@ void MVKSwapchain::initCAMetalLayer(const VkSwapchainCreateInfoKHR* pCreateInfo,
if ( getIsSurfaceLost() ) { return; }
auto* mtlLayer = _surface->getCAMetalLayer();
auto* mtlLayer = getCAMetalLayer();
auto minMagFilter = mvkConfig().swapchainMinMagFilterUseNearest ? kCAFilterNearest : kCAFilterLinear;
mtlLayer.device = getMTLDevice();
mtlLayer.pixelFormat = getPixelFormats()->getMTLPixelFormat(pCreateInfo->imageFormat);
@ -469,6 +485,16 @@ void MVKSwapchain::initCAMetalLayer(const VkSwapchainCreateInfoKHR* pCreateInfo,
VK_IMAGE_USAGE_TRANSFER_DST_BIT |
VK_IMAGE_USAGE_SAMPLED_BIT |
VK_IMAGE_USAGE_STORAGE_BIT));
// Because of a regression in Metal, the most recent one or two presentations may not
// complete and call back. Changing the CAMetalLayer drawableSize will force any incomplete
// presentations on the oldSwapchain to complete and call back, but if the drawableSize
// is not changing from the previous, we force those completions first.
auto* oldSwapchain = (MVKSwapchain*)pCreateInfo->oldSwapchain;
if (oldSwapchain && mvkVkExtent2DsAreEqual(pCreateInfo->imageExtent, mvkVkExtent2DFromCGSize(mtlLayer.drawableSize))) {
oldSwapchain->forceUnpresentedImageCompletion();
}
// Remember the extent to later detect if it has changed under the covers,
// and set the drawable size of the CAMetalLayer from the extent.
_mtlLayerDrawableExtent = pCreateInfo->imageExtent;
@ -559,7 +585,7 @@ void MVKSwapchain::initSurfaceImages(const VkSwapchainCreateInfoKHR* pCreateInfo
}
}
auto* mtlLayer = _surface->getCAMetalLayer();
auto* mtlLayer = getCAMetalLayer();
VkExtent2D imgExtent = pCreateInfo->imageExtent;
VkImageCreateInfo imgInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
@ -598,12 +624,17 @@ void MVKSwapchain::initSurfaceImages(const VkSwapchainCreateInfoKHR* pCreateInfo
screenName = mtlLayer.screenMVK.localizedName;
}
#endif
MVKLogInfo("Created %d swapchain images with initial size (%d, %d) and contents scale %.1f for screen %s.",
imgCnt, imgExtent.width, imgExtent.height, mtlLayer.contentsScale, screenName.UTF8String);
MVKLogInfo("Created %d swapchain images with size (%d, %d) and contents scale %.1f in layer %s (%p) on screen %s.",
imgCnt, imgExtent.width, imgExtent.height, mtlLayer.contentsScale, mtlLayer.name.UTF8String, mtlLayer, screenName.UTF8String);
}
void MVKSwapchain::destroy() {
if (_surface->_activeSwapchain == this) { _surface->_activeSwapchain = nullptr; }
// If this swapchain was not replaced by a new swapchain, remove this swapchain
// from the surface, and force any outstanding presentations to complete.
if (_surface->_activeSwapchain == this) {
_surface->_activeSwapchain = nullptr;
forceUnpresentedImageCompletion();
}
for (auto& img : _presentableImages) { _device->destroyPresentableSwapchainImage(img, NULL); }
MVKVulkanAPIDeviceObject::destroy();
}