Rework workaround to force incomplete CAMetalDrawable presentations to complete.
- To force any incomplete CAMetalDrawable presentations to complete, don't force the creation of another transient drawable, as this can stall the creation of future drawables. Instead, when a swapchain is destroyed, or replaced by a new swapchain, set the CAMetalLayer drawableSize, which will force presentation completion. - Add presentation completion handler in command buffer scheduling callback, move marking available to presentation completion handler, and minimize mutex locking. - MVKQueue::waitIdle() remove wait for swapchain presentations, and remove callbacks to MVKQueue from drawable completions. - MVKQueue::submit() don't bypass submitting a misconfigured submission, so that semaphores and fences will be signalled, and ensure misconfigured submissions are well behaved. - Add MVKSwapchain::getCAMetalLayer() to streamline layer access (unrelated).
This commit is contained in:
parent
8f4619a788
commit
f0cb31a12b
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user