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;
|
_wasExecuted = true;
|
||||||
return true;
|
return wasConfigurationSuccessful();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the number of bits set in the view mask, with a minimum value of 1.
|
// 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);
|
void beginPresentation(const MVKImagePresentInfo& presentInfo);
|
||||||
|
|
||||||
/** Called via callback when the presentation completes. */
|
/** Called via callback when the presentation completes. */
|
||||||
void endPresentation(const MVKImagePresentInfo& presentInfo, uint64_t actualPresentTime = 0);
|
void endPresentation(const MVKImagePresentInfo& presentInfo,
|
||||||
|
const MVKSwapchainSignaler& signaler,
|
||||||
/** If this image is stuck in-flight, attempt to force it to complete. */
|
uint64_t actualPresentTime = 0);
|
||||||
void forcePresentationCompletion();
|
|
||||||
|
|
||||||
#pragma mark Construction
|
#pragma mark Construction
|
||||||
|
|
||||||
@ -478,12 +477,13 @@ protected:
|
|||||||
friend MVKSwapchain;
|
friend MVKSwapchain;
|
||||||
|
|
||||||
id<CAMetalDrawable> getCAMetalDrawable() override;
|
id<CAMetalDrawable> getCAMetalDrawable() override;
|
||||||
void addPresentedHandler(id<CAMetalDrawable> mtlDrawable, MVKImagePresentInfo presentInfo);
|
void addPresentedHandler(id<CAMetalDrawable> mtlDrawable, MVKImagePresentInfo presentInfo, MVKSwapchainSignaler signaler);
|
||||||
void releaseMetalDrawable();
|
void releaseMetalDrawable();
|
||||||
MVKSwapchainImageAvailability getAvailability();
|
MVKSwapchainImageAvailability getAvailability();
|
||||||
void makeAvailable(const MVKSwapchainSignaler& signaler);
|
void makeAvailable(const MVKSwapchainSignaler& signaler);
|
||||||
void makeAvailable();
|
void makeAvailable();
|
||||||
VkResult acquireAndSignalWhenAvailable(MVKSemaphore* semaphore, MVKFence* fence);
|
VkResult acquireAndSignalWhenAvailable(MVKSemaphore* semaphore, MVKFence* fence);
|
||||||
|
MVKSwapchainSignaler getPresentationSignaler();
|
||||||
|
|
||||||
id<CAMetalDrawable> _mtlDrawable = nil;
|
id<CAMetalDrawable> _mtlDrawable = nil;
|
||||||
MVKSwapchainImageAvailability _availability;
|
MVKSwapchainImageAvailability _availability;
|
||||||
|
@ -1248,16 +1248,18 @@ static void signalAndUnmarkAsTracked(const MVKSwapchainSignaler& signaler) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
VkResult MVKPresentableSwapchainImage::acquireAndSignalWhenAvailable(MVKSemaphore* semaphore, MVKFence* fence) {
|
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);
|
lock_guard<mutex> lock(_availabilityLock);
|
||||||
|
|
||||||
// Upon acquisition, update acquisition ID immediately, to move it to the back of the chain,
|
// 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.
|
// so other images will be preferred if either all images are available or no images are available.
|
||||||
_availability.acquisitionID = _swapchain->getNextAcquisitionID();
|
_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};
|
auto signaler = MVKSwapchainSignaler{fence, semaphore, semaphore ? semaphore->deferSignal() : 0};
|
||||||
if (_availability.isAvailable) {
|
if (_availability.isAvailable) {
|
||||||
_availability.isAvailable = false;
|
_availability.isAvailable = false;
|
||||||
@ -1292,10 +1294,10 @@ id<CAMetalDrawable> MVKPresentableSwapchainImage::getCAMetalDrawable() {
|
|||||||
if ( !_mtlDrawable ) {
|
if ( !_mtlDrawable ) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
bool hasInvalidFormat = false;
|
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++) {
|
for (uint32_t attemptIdx = 0; !_mtlDrawable && attemptIdx < attemptCnt; attemptIdx++) {
|
||||||
uint64_t startTime = _device->getPerformanceTimestamp();
|
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);
|
_device->addPerformanceInterval(_device->_performanceStatistics.queue.retrieveCAMetalDrawable, startTime);
|
||||||
hasInvalidFormat = _mtlDrawable && !_mtlDrawable.texture.pixelFormat;
|
hasInvalidFormat = _mtlDrawable && !_mtlDrawable.texture.pixelFormat;
|
||||||
if (hasInvalidFormat) { releaseMetalDrawable(); }
|
if (hasInvalidFormat) { releaseMetalDrawable(); }
|
||||||
@ -1314,8 +1316,6 @@ id<CAMetalDrawable> MVKPresentableSwapchainImage::getCAMetalDrawable() {
|
|||||||
// Pass MVKImagePresentInfo by value because it may not exist when the callback runs.
|
// Pass MVKImagePresentInfo by value because it may not exist when the callback runs.
|
||||||
VkResult MVKPresentableSwapchainImage::presentCAMetalDrawable(id<MTLCommandBuffer> mtlCmdBuff,
|
VkResult MVKPresentableSwapchainImage::presentCAMetalDrawable(id<MTLCommandBuffer> mtlCmdBuff,
|
||||||
MVKImagePresentInfo presentInfo) {
|
MVKImagePresentInfo presentInfo) {
|
||||||
lock_guard<mutex> lock(_availabilityLock);
|
|
||||||
|
|
||||||
_swapchain->renderWatermark(getMTLTexture(0), mtlCmdBuff);
|
_swapchain->renderWatermark(getMTLTexture(0), mtlCmdBuff);
|
||||||
|
|
||||||
// According to Apple, it is more performant to call MTLDrawable present from within a
|
// 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.
|
// 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.
|
// Attach present handler before presenting to avoid race condition.
|
||||||
id<CAMetalDrawable> mtlDrwbl = getCAMetalDrawable();
|
id<CAMetalDrawable> mtlDrwbl = getCAMetalDrawable();
|
||||||
addPresentedHandler(mtlDrwbl, presentInfo);
|
MVKSwapchainSignaler signaler = getPresentationSignaler();
|
||||||
[mtlCmdBuff addScheduledHandler: ^(id<MTLCommandBuffer> mcb) {
|
[mtlCmdBuff addScheduledHandler: ^(id<MTLCommandBuffer> mcb) {
|
||||||
|
|
||||||
|
addPresentedHandler(mtlDrwbl, presentInfo, signaler);
|
||||||
|
|
||||||
// Try to do any present mode transitions as late as possible in an attempt
|
// 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.
|
// to avoid visual disruptions on any presents already on the queue.
|
||||||
if (presentInfo.presentMode != VK_PRESENT_MODE_MAX_ENUM_KHR) {
|
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.
|
// Mark this image as available if no semaphores or fences are waiting to be signaled.
|
||||||
_availability.isAvailable = _availabilitySignalers.empty();
|
_availability.isAvailable = _availabilitySignalers.empty();
|
||||||
if (_availability.isAvailable) {
|
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.
|
// 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
|
// 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.
|
// get out of sync, and the final use of the image would not be signaled as a result.
|
||||||
signaler = _preSignaler;
|
return _preSignaler;
|
||||||
} else {
|
} else {
|
||||||
// If this image is not yet available, extract and signal the first semaphore and fence.
|
// If this image is not yet available, extract and signal the first semaphore and fence.
|
||||||
|
MVKSwapchainSignaler signaler;
|
||||||
auto sigIter = _availabilitySignalers.begin();
|
auto sigIter = _availabilitySignalers.begin();
|
||||||
signaler = *sigIter;
|
signaler = *sigIter;
|
||||||
_availabilitySignalers.erase(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,
|
void MVKPresentableSwapchainImage::addPresentedHandler(id<CAMetalDrawable> mtlDrawable,
|
||||||
MVKImagePresentInfo presentInfo) {
|
MVKImagePresentInfo presentInfo,
|
||||||
|
MVKSwapchainSignaler signaler) {
|
||||||
beginPresentation(presentInfo);
|
beginPresentation(presentInfo);
|
||||||
|
|
||||||
#if !MVK_OS_SIMULATOR
|
#if !MVK_OS_SIMULATOR
|
||||||
if ([mtlDrawable respondsToSelector: @selector(addPresentedHandler:)]) {
|
if ([mtlDrawable respondsToSelector: @selector(addPresentedHandler:)]) {
|
||||||
[mtlDrawable addPresentedHandler: ^(id<MTLDrawable> mtlDrwbl) {
|
[mtlDrawable addPresentedHandler: ^(id<MTLDrawable> mtlDrwbl) {
|
||||||
endPresentation(presentInfo, mtlDrwbl.presentedTime * 1.0e9);
|
endPresentation(presentInfo, signaler, mtlDrwbl.presentedTime * 1.0e9);
|
||||||
}];
|
}];
|
||||||
} else
|
} else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
// If MTLDrawable.presentedTime/addPresentedHandler isn't supported,
|
// If MTLDrawable.presentedTime/addPresentedHandler isn't supported,
|
||||||
// treat it as if the present happened when requested.
|
// 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) {
|
void MVKPresentableSwapchainImage::beginPresentation(const MVKImagePresentInfo& presentInfo) {
|
||||||
retain();
|
retain();
|
||||||
_swapchain->beginPresentation(presentInfo);
|
_swapchain->beginPresentation(presentInfo);
|
||||||
presentInfo.queue->beginPresentation(presentInfo);
|
|
||||||
_presentationStartTime = getDevice()->getPerformanceTimestamp();
|
_presentationStartTime = getDevice()->getPerformanceTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MVKPresentableSwapchainImage::endPresentation(const MVKImagePresentInfo& presentInfo,
|
void MVKPresentableSwapchainImage::endPresentation(const MVKImagePresentInfo& presentInfo,
|
||||||
|
const MVKSwapchainSignaler& signaler,
|
||||||
uint64_t actualPresentTime) {
|
uint64_t actualPresentTime) {
|
||||||
{ // Scope to avoid deadlock if release() is run within detachment lock
|
{ // 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
|
// 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 (_device) { _device->addPerformanceInterval(_device->_performanceStatistics.queue.presentSwapchains, _presentationStartTime); }
|
||||||
if (_swapchain) { _swapchain->endPresentation(presentInfo, actualPresentTime); }
|
if (_swapchain) { _swapchain->endPresentation(presentInfo, actualPresentTime); }
|
||||||
}
|
}
|
||||||
presentInfo.queue->endPresentation(presentInfo);
|
makeAvailable(signaler);
|
||||||
release();
|
release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1432,7 +1442,9 @@ void MVKPresentableSwapchainImage::makeAvailable(const MVKSwapchainSignaler& sig
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Signal, untrack, and release any signalers that are tracking.
|
// Signal, untrack, and release any signalers that are tracking.
|
||||||
|
// Release the drawable before the lock, as it may trigger completion callback.
|
||||||
void MVKPresentableSwapchainImage::makeAvailable() {
|
void MVKPresentableSwapchainImage::makeAvailable() {
|
||||||
|
releaseMetalDrawable();
|
||||||
lock_guard<mutex> lock(_availabilityLock);
|
lock_guard<mutex> lock(_availabilityLock);
|
||||||
|
|
||||||
if ( !_availability.isAvailable ) {
|
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
|
#pragma mark Construction
|
||||||
|
|
||||||
MVKPresentableSwapchainImage::MVKPresentableSwapchainImage(MVKDevice* device,
|
MVKPresentableSwapchainImage::MVKPresentableSwapchainImage(MVKDevice* device,
|
||||||
@ -1467,14 +1471,13 @@ MVKPresentableSwapchainImage::MVKPresentableSwapchainImage(MVKDevice* device,
|
|||||||
|
|
||||||
|
|
||||||
void MVKPresentableSwapchainImage::destroy() {
|
void MVKPresentableSwapchainImage::destroy() {
|
||||||
forcePresentationCompletion();
|
releaseMetalDrawable();
|
||||||
MVKSwapchainImage::destroy();
|
MVKSwapchainImage::destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unsignaled signalers will exist if this image is acquired more than it is presented.
|
// 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.
|
// Ensure they are signaled and untracked so the fences and semaphores will be released.
|
||||||
MVKPresentableSwapchainImage::~MVKPresentableSwapchainImage() {
|
MVKPresentableSwapchainImage::~MVKPresentableSwapchainImage() {
|
||||||
releaseMetalDrawable();
|
|
||||||
makeAvailable();
|
makeAvailable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,13 +100,6 @@ public:
|
|||||||
/** Block the current thread until this queue is idle. */
|
/** Block the current thread until this queue is idle. */
|
||||||
VkResult waitIdle(MVKCommandUse cmdUse);
|
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
|
#pragma mark Metal
|
||||||
|
|
||||||
/** Returns the Metal queue underlying this queue. */
|
/** Returns the Metal queue underlying this queue. */
|
||||||
@ -150,11 +143,8 @@ protected:
|
|||||||
VkResult submit(MVKQueueSubmission* qSubmit);
|
VkResult submit(MVKQueueSubmission* qSubmit);
|
||||||
NSString* getMTLCommandBufferLabel(MVKCommandUse cmdUse);
|
NSString* getMTLCommandBufferLabel(MVKCommandUse cmdUse);
|
||||||
void handleMTLCommandBufferError(id<MTLCommandBuffer> mtlCmdBuff);
|
void handleMTLCommandBufferError(id<MTLCommandBuffer> mtlCmdBuff);
|
||||||
void waitSwapchainPresentations(MVKCommandUse cmdUse);
|
|
||||||
|
|
||||||
MVKQueueFamily* _queueFamily;
|
MVKQueueFamily* _queueFamily;
|
||||||
MVKSemaphoreImpl _presentationCompletionBlocker;
|
|
||||||
std::unordered_map<MVKPresentableSwapchainImage*, uint32_t> _presentedImages;
|
|
||||||
std::string _name;
|
std::string _name;
|
||||||
dispatch_queue_t _execQueue;
|
dispatch_queue_t _execQueue;
|
||||||
id<MTLCommandQueue> _mtlQueue = nil;
|
id<MTLCommandQueue> _mtlQueue = nil;
|
||||||
@ -166,7 +156,6 @@ protected:
|
|||||||
NSString* _mtlCmdBuffLabelAcquireNextImage = nil;
|
NSString* _mtlCmdBuffLabelAcquireNextImage = nil;
|
||||||
NSString* _mtlCmdBuffLabelInvalidateMappedMemoryRanges = nil;
|
NSString* _mtlCmdBuffLabelInvalidateMappedMemoryRanges = nil;
|
||||||
MVKGPUCaptureScope* _submissionCaptureScope = nil;
|
MVKGPUCaptureScope* _submissionCaptureScope = nil;
|
||||||
std::mutex _presentedImagesLock;
|
|
||||||
float _priority;
|
float _priority;
|
||||||
uint32_t _index;
|
uint32_t _index;
|
||||||
};
|
};
|
||||||
|
@ -80,13 +80,14 @@ VkResult MVKQueue::submit(MVKQueueSubmission* qSubmit) {
|
|||||||
|
|
||||||
if ( !qSubmit ) { return VK_SUCCESS; } // Ignore nils
|
if ( !qSubmit ) { return VK_SUCCESS; } // Ignore nils
|
||||||
|
|
||||||
VkResult rslt = qSubmit->getConfigurationResult(); // Extract result before submission to avoid race condition with early destruction
|
// Extract result before submission to avoid race condition with early destruction
|
||||||
if (rslt == VK_SUCCESS) {
|
// Submit regardless of config result, to ensure submission semaphores and fences are signalled.
|
||||||
if (_execQueue) {
|
// The submissions will ensure a misconfiguration will be safe to execute.
|
||||||
dispatch_async(_execQueue, ^{ execute(qSubmit); } );
|
VkResult rslt = qSubmit->getConfigurationResult();
|
||||||
} else {
|
if (_execQueue) {
|
||||||
rslt = execute(qSubmit);
|
dispatch_async(_execQueue, ^{ execute(qSubmit); } );
|
||||||
}
|
} else {
|
||||||
|
rslt = execute(qSubmit);
|
||||||
}
|
}
|
||||||
return rslt;
|
return rslt;
|
||||||
}
|
}
|
||||||
@ -140,50 +141,9 @@ VkResult MVKQueue::waitIdle(MVKCommandUse cmdUse) {
|
|||||||
[mtlCmdBuff commit];
|
[mtlCmdBuff commit];
|
||||||
[mtlCmdBuff waitUntilCompleted];
|
[mtlCmdBuff waitUntilCompleted];
|
||||||
|
|
||||||
waitSwapchainPresentations(cmdUse);
|
|
||||||
|
|
||||||
return VK_SUCCESS;
|
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> MVKQueue::getMTLCommandBuffer(MVKCommandUse cmdUse, bool retainRefs) {
|
||||||
id<MTLCommandBuffer> mtlCmdBuff = nil;
|
id<MTLCommandBuffer> mtlCmdBuff = nil;
|
||||||
MVKDevice* mvkDev = getDevice();
|
MVKDevice* mvkDev = getDevice();
|
||||||
@ -312,25 +272,6 @@ void MVKQueue::handleMTLCommandBufferError(id<MTLCommandBuffer> mtlCmdBuff) {
|
|||||||
#endif
|
#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
|
#pragma mark Construction
|
||||||
|
|
||||||
#define MVK_DISPATCH_QUEUE_QOS_CLASS QOS_CLASS_USER_INITIATED
|
#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
|
// 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
|
// 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
|
// Use temp var for MTLCommandBuffer commit and release because completion callback
|
||||||
// may destroy this instance before this function ends.
|
// may destroy this instance before this function ends.
|
||||||
id<MTLCommandBuffer> mtlCmdBuff = signalCompletion ? getActiveMTLCommandBuffer() : _activeMTLCommandBuffer;
|
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.
|
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 commit];
|
||||||
[mtlCmdBuff release]; // retained
|
[mtlCmdBuff release]; // retained
|
||||||
|
|
||||||
@ -508,7 +451,7 @@ VkResult MVKQueueCommandBufferSubmission::commitActiveMTLCommandBuffer(bool sign
|
|||||||
// was not created, call the finish() function directly.
|
// was not created, call the finish() function directly.
|
||||||
if (signalCompletion && !mtlCmdBuff) { finish(); }
|
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
|
// 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. */
|
/** Returns the debug report object type of this object. */
|
||||||
VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_SWAPCHAIN_KHR_EXT; }
|
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. */
|
/** 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. */
|
/** 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.
|
* Returns the array of presentable images associated with this swapchain.
|
||||||
@ -112,6 +115,7 @@ protected:
|
|||||||
void markFrameInterval();
|
void markFrameInterval();
|
||||||
void beginPresentation(const MVKImagePresentInfo& presentInfo);
|
void beginPresentation(const MVKImagePresentInfo& presentInfo);
|
||||||
void endPresentation(const MVKImagePresentInfo& presentInfo, uint64_t actualPresentTime = 0);
|
void endPresentation(const MVKImagePresentInfo& presentInfo, uint64_t actualPresentTime = 0);
|
||||||
|
void forceUnpresentedImageCompletion();
|
||||||
|
|
||||||
MVKSurface* _surface = nullptr;
|
MVKSurface* _surface = nullptr;
|
||||||
MVKWatermark* _licenseWatermark = nullptr;
|
MVKWatermark* _licenseWatermark = nullptr;
|
||||||
@ -123,6 +127,7 @@ protected:
|
|||||||
std::mutex _presentHistoryLock;
|
std::mutex _presentHistoryLock;
|
||||||
uint64_t _lastFrameTime = 0;
|
uint64_t _lastFrameTime = 0;
|
||||||
VkExtent2D _mtlLayerDrawableExtent = {0, 0};
|
VkExtent2D _mtlLayerDrawableExtent = {0, 0};
|
||||||
|
std::atomic<uint32_t> _unpresentedImageCount = 0;
|
||||||
uint32_t _currentPerfLogFrameCount = 0;
|
uint32_t _currentPerfLogFrameCount = 0;
|
||||||
uint32_t _presentHistoryCount = 0;
|
uint32_t _presentHistoryCount = 0;
|
||||||
uint32_t _presentHistoryIndex = 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) {
|
VkResult MVKSwapchain::getImages(uint32_t* pCount, VkImage* pSwapchainImages) {
|
||||||
|
|
||||||
// Get the number of surface images
|
// Get the number of surface images
|
||||||
@ -104,7 +106,7 @@ VkResult MVKSwapchain::releaseImages(const VkReleaseSwapchainImagesInfoEXT* pRel
|
|||||||
getPresentableImage(pReleaseInfo->pImageIndices[imgIdxIdx])->makeAvailable();
|
getPresentableImage(pReleaseInfo->pImageIndices[imgIdxIdx])->makeAvailable();
|
||||||
}
|
}
|
||||||
|
|
||||||
return VK_SUCCESS;
|
return _surface->getConfigurationResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t MVKSwapchain::getNextAcquisitionID() { return ++_currentAcquisitionID; }
|
uint64_t MVKSwapchain::getNextAcquisitionID() { return ++_currentAcquisitionID; }
|
||||||
@ -128,7 +130,7 @@ VkResult MVKSwapchain::getSurfaceStatus() {
|
|||||||
bool MVKSwapchain::hasOptimalSurface() {
|
bool MVKSwapchain::hasOptimalSurface() {
|
||||||
if (_isDeliberatelyScaled) { return true; }
|
if (_isDeliberatelyScaled) { return true; }
|
||||||
|
|
||||||
auto* mtlLayer = _surface->getCAMetalLayer();
|
auto* mtlLayer = getCAMetalLayer();
|
||||||
VkExtent2D drawExtent = mvkVkExtent2DFromCGSize(mtlLayer.drawableSize);
|
VkExtent2D drawExtent = mvkVkExtent2DFromCGSize(mtlLayer.drawableSize);
|
||||||
return (mvkVkExtent2DsAreEqual(drawExtent, _mtlLayerDrawableExtent) &&
|
return (mvkVkExtent2DsAreEqual(drawExtent, _mtlLayerDrawableExtent) &&
|
||||||
mvkVkExtent2DsAreEqual(drawExtent, mvkGetNaturalExtent(mtlLayer)));
|
mvkVkExtent2DsAreEqual(drawExtent, mvkGetNaturalExtent(mtlLayer)));
|
||||||
@ -185,7 +187,7 @@ void MVKSwapchain::markFrameInterval() {
|
|||||||
VkResult MVKSwapchain::getRefreshCycleDuration(VkRefreshCycleDurationGOOGLE *pRefreshCycleDuration) {
|
VkResult MVKSwapchain::getRefreshCycleDuration(VkRefreshCycleDurationGOOGLE *pRefreshCycleDuration) {
|
||||||
if (_device->getConfigurationResult() != VK_SUCCESS) { return _device->getConfigurationResult(); }
|
if (_device->getConfigurationResult() != VK_SUCCESS) { return _device->getConfigurationResult(); }
|
||||||
|
|
||||||
auto* mtlLayer = _surface->getCAMetalLayer();
|
auto* mtlLayer = getCAMetalLayer();
|
||||||
#if MVK_VISIONOS
|
#if MVK_VISIONOS
|
||||||
// TODO: See if this can be obtained from OS instead
|
// TODO: See if this can be obtained from OS instead
|
||||||
NSInteger framesPerSecond = 90;
|
NSInteger framesPerSecond = 90;
|
||||||
@ -242,9 +244,13 @@ VkResult MVKSwapchain::getPastPresentationTiming(uint32_t *pCount, VkPastPresent
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MVKSwapchain::beginPresentation(const MVKImagePresentInfo& presentInfo) {}
|
void MVKSwapchain::beginPresentation(const MVKImagePresentInfo& presentInfo) {
|
||||||
|
_unpresentedImageCount++;
|
||||||
|
}
|
||||||
|
|
||||||
void MVKSwapchain::endPresentation(const MVKImagePresentInfo& presentInfo, uint64_t actualPresentTime) {
|
void MVKSwapchain::endPresentation(const MVKImagePresentInfo& presentInfo, uint64_t actualPresentTime) {
|
||||||
|
_unpresentedImageCount--;
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(_presentHistoryLock);
|
std::lock_guard<std::mutex> lock(_presentHistoryLock);
|
||||||
|
|
||||||
markFrameInterval();
|
markFrameInterval();
|
||||||
@ -269,8 +275,18 @@ void MVKSwapchain::endPresentation(const MVKImagePresentInfo& presentInfo, uint6
|
|||||||
_presentHistoryIndex = (_presentHistoryIndex + 1) % kMaxPresentationHistory;
|
_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) {
|
void MVKSwapchain::setLayerNeedsDisplay(const VkPresentRegionKHR* pRegion) {
|
||||||
auto* mtlLayer = _surface->getCAMetalLayer();
|
auto* mtlLayer = getCAMetalLayer();
|
||||||
if (!pRegion || pRegion->rectangleCount == 0) {
|
if (!pRegion || pRegion->rectangleCount == 0) {
|
||||||
[mtlLayer setNeedsDisplay];
|
[mtlLayer setNeedsDisplay];
|
||||||
return;
|
return;
|
||||||
@ -350,7 +366,7 @@ void MVKSwapchain::setHDRMetadataEXT(const VkHdrMetadataEXT& metadata) {
|
|||||||
CAEDRMetadata* caMetadata = [CAEDRMetadata HDR10MetadataWithDisplayInfo: colorVolData
|
CAEDRMetadata* caMetadata = [CAEDRMetadata HDR10MetadataWithDisplayInfo: colorVolData
|
||||||
contentInfo: lightLevelData
|
contentInfo: lightLevelData
|
||||||
opticalOutputScale: 1];
|
opticalOutputScale: 1];
|
||||||
auto* mtlLayer = _surface->getCAMetalLayer();
|
auto* mtlLayer = getCAMetalLayer();
|
||||||
mtlLayer.EDRMetadata = caMetadata;
|
mtlLayer.EDRMetadata = caMetadata;
|
||||||
mtlLayer.wantsExtendedDynamicRangeContent = YES;
|
mtlLayer.wantsExtendedDynamicRangeContent = YES;
|
||||||
[caMetadata release];
|
[caMetadata release];
|
||||||
@ -456,7 +472,7 @@ void MVKSwapchain::initCAMetalLayer(const VkSwapchainCreateInfoKHR* pCreateInfo,
|
|||||||
|
|
||||||
if ( getIsSurfaceLost() ) { return; }
|
if ( getIsSurfaceLost() ) { return; }
|
||||||
|
|
||||||
auto* mtlLayer = _surface->getCAMetalLayer();
|
auto* mtlLayer = getCAMetalLayer();
|
||||||
auto minMagFilter = mvkConfig().swapchainMinMagFilterUseNearest ? kCAFilterNearest : kCAFilterLinear;
|
auto minMagFilter = mvkConfig().swapchainMinMagFilterUseNearest ? kCAFilterNearest : kCAFilterLinear;
|
||||||
mtlLayer.device = getMTLDevice();
|
mtlLayer.device = getMTLDevice();
|
||||||
mtlLayer.pixelFormat = getPixelFormats()->getMTLPixelFormat(pCreateInfo->imageFormat);
|
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_TRANSFER_DST_BIT |
|
||||||
VK_IMAGE_USAGE_SAMPLED_BIT |
|
VK_IMAGE_USAGE_SAMPLED_BIT |
|
||||||
VK_IMAGE_USAGE_STORAGE_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,
|
// Remember the extent to later detect if it has changed under the covers,
|
||||||
// and set the drawable size of the CAMetalLayer from the extent.
|
// and set the drawable size of the CAMetalLayer from the extent.
|
||||||
_mtlLayerDrawableExtent = pCreateInfo->imageExtent;
|
_mtlLayerDrawableExtent = pCreateInfo->imageExtent;
|
||||||
@ -559,7 +585,7 @@ void MVKSwapchain::initSurfaceImages(const VkSwapchainCreateInfoKHR* pCreateInfo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* mtlLayer = _surface->getCAMetalLayer();
|
auto* mtlLayer = getCAMetalLayer();
|
||||||
VkExtent2D imgExtent = pCreateInfo->imageExtent;
|
VkExtent2D imgExtent = pCreateInfo->imageExtent;
|
||||||
VkImageCreateInfo imgInfo = {
|
VkImageCreateInfo imgInfo = {
|
||||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||||
@ -598,12 +624,17 @@ void MVKSwapchain::initSurfaceImages(const VkSwapchainCreateInfoKHR* pCreateInfo
|
|||||||
screenName = mtlLayer.screenMVK.localizedName;
|
screenName = mtlLayer.screenMVK.localizedName;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
MVKLogInfo("Created %d swapchain images with initial size (%d, %d) and contents scale %.1f for screen %s.",
|
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, screenName.UTF8String);
|
imgCnt, imgExtent.width, imgExtent.height, mtlLayer.contentsScale, mtlLayer.name.UTF8String, mtlLayer, screenName.UTF8String);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MVKSwapchain::destroy() {
|
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); }
|
for (auto& img : _presentableImages) { _device->destroyPresentableSwapchainImage(img, NULL); }
|
||||||
MVKVulkanAPIDeviceObject::destroy();
|
MVKVulkanAPIDeviceObject::destroy();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user