Merge pull request #1683 from billhollings/fix-swapchain-retention
Fix retention of MVKSwapchain for future drawable presentations.
diff --git a/Docs/Whats_New.md b/Docs/Whats_New.md
index a2efc7d..b54e5ab 100644
--- a/Docs/Whats_New.md
+++ b/Docs/Whats_New.md
@@ -13,6 +13,16 @@
+MoltenVK 1.1.12
+--------------
+
+Released TBD
+
+- Fix occassional crash from retention of `MVKSwapchain` for future drawable presentations.
+- Add `MVK_USE_CEREAL` build setting to avoid use of Cereal external library (for pipeline caching).
+
+
+
MoltenVK 1.1.11
--------------
diff --git a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
index 70d6030..080bb02 100644
--- a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
+++ b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
@@ -51,7 +51,7 @@
*/
#define MVK_VERSION_MAJOR 1
#define MVK_VERSION_MINOR 1
-#define MVK_VERSION_PATCH 11
+#define MVK_VERSION_PATCH 12
#define MVK_MAKE_VERSION(major, minor, patch) (((major) * 10000) + ((minor) * 100) + (patch))
#define MVK_VERSION MVK_MAKE_VERSION(MVK_VERSION_MAJOR, MVK_VERSION_MINOR, MVK_VERSION_PATCH)
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
index b56eeb0..fc9e5fd 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
@@ -393,20 +393,21 @@
#pragma mark Construction
- /** Constructs an instance for the specified device and swapchain. */
MVKSwapchainImage(MVKDevice* device,
const VkImageCreateInfo* pCreateInfo,
MVKSwapchain* swapchain,
uint32_t swapchainIndex);
- ~MVKSwapchainImage() override;
+ void destroy() override;
protected:
friend class MVKPeerSwapchainImage;
virtual id<CAMetalDrawable> getCAMetalDrawable() = 0;
+ void detachSwapchain();
MVKSwapchain* _swapchain;
+ std::mutex _swapchainLock;
uint32_t _swapchainIndex;
};
@@ -452,7 +453,6 @@
#pragma mark Construction
- /** Constructs an instance for the specified device and swapchain. */
MVKPresentableSwapchainImage(MVKDevice* device,
const VkImageCreateInfo* pCreateInfo,
MVKSwapchain* swapchain,
@@ -464,7 +464,7 @@
friend MVKSwapchain;
id<CAMetalDrawable> getCAMetalDrawable() override;
- void presentCAMetalDrawable(id<CAMetalDrawable> mtlDrawable, MVKPresentTimingInfo presentTimingInfo);
+ void addPresentedHandler(id<CAMetalDrawable> mtlDrawable, MVKPresentTimingInfo presentTimingInfo);
void releaseMetalDrawable();
MVKSwapchainImageAvailability getAvailability();
void makeAvailable(const MVKSwapchainSignaler& signaler);
@@ -498,7 +498,6 @@
#pragma mark Construction
- /** Constructs an instance for the specified device and swapchain. */
MVKPeerSwapchainImage(MVKDevice* device,
const VkImageCreateInfo* pCreateInfo,
MVKSwapchain* swapchain,
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
index 8340095..dc85fd4 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
@@ -1190,12 +1190,16 @@
uint32_t swapchainIndex) : MVKImage(device, pCreateInfo) {
_swapchain = swapchain;
_swapchainIndex = swapchainIndex;
-
- _swapchain->retain();
}
-MVKSwapchainImage::~MVKSwapchainImage() {
- if (_swapchain) { _swapchain->release(); }
+void MVKSwapchainImage::detachSwapchain() {
+ lock_guard<mutex> lock(_swapchainLock);
+ _swapchain = nullptr;
+}
+
+void MVKSwapchainImage::destroy() {
+ detachSwapchain();
+ MVKImage::destroy();
}
@@ -1308,10 +1312,19 @@
_swapchain->willPresentSurface(getMTLTexture(0), mtlCmdBuff);
- // Get current drawable now. Don't retrieve in handler, because a new drawable might be acquired by then.
+ // According to Apple, it is more performant to call MTLDrawable present from within a
+ // MTLCommandBuffer scheduled-handler than it is to call MTLCommandBuffer presentDrawable:.
+ // 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();
[mtlCmdBuff addScheduledHandler: ^(id<MTLCommandBuffer> mcb) {
- presentCAMetalDrawable(mtlDrwbl, presentTimingInfo);
+ if (presentTimingInfo.hasPresentTime) {
+ // Convert from nsecs to seconds for Metal
+ addPresentedHandler(mtlDrwbl, presentTimingInfo);
+ [mtlDrwbl presentAtTime: (double)presentTimingInfo.desiredPresentTime * 1.0e-9];
+ } else {
+ [mtlDrwbl present];
+ }
}];
MVKSwapchainSignaler signaler;
@@ -1342,33 +1355,29 @@
signalPresentationSemaphore(signaler, mtlCmdBuff);
}
-void MVKPresentableSwapchainImage::presentCAMetalDrawable(id<CAMetalDrawable> mtlDrawable,
- MVKPresentTimingInfo presentTimingInfo) {
-
- if (presentTimingInfo.hasPresentTime) {
-
- // Attach present handler before presenting to avoid race condition.
- // If MTLDrawable.presentedTime/addPresentedHandler isn't supported,
- // treat it as if the present happened when requested.
-#if MVK_OS_SIMULATOR
- _swapchain->recordPresentTime(presentTimingInfo);
-#else
- if ([mtlDrawable respondsToSelector: @selector(addPresentedHandler:)]) {
- // Ensure this image is not destroyed while awaiting presentation
- retain();
- [mtlDrawable addPresentedHandler: ^(id<MTLDrawable> drawable) {
- _swapchain->recordPresentTime(presentTimingInfo, drawable.presentedTime * 1.0e9);
- release();
- }];
- } else {
- _swapchain->recordPresentTime(presentTimingInfo);
- }
-#endif
- // Convert from nsecs to seconds for Metal
- [mtlDrawable presentAtTime: (double)presentTimingInfo.desiredPresentTime * 1.0e-9];
- } else {
- [mtlDrawable present];
+void MVKPresentableSwapchainImage::addPresentedHandler(id<CAMetalDrawable> mtlDrawable,
+ MVKPresentTimingInfo presentTimingInfo) {
+#if !MVK_OS_SIMULATOR
+ if ([mtlDrawable respondsToSelector: @selector(addPresentedHandler:)]) {
+ retain(); // Ensure this image is not destroyed while awaiting presentation
+ [mtlDrawable addPresentedHandler: ^(id<MTLDrawable> drawable) {
+ // Since we're in a callback, it's possible that the swapchain has been released by now.
+ // Lock the swapchain, and test if it is present before doing anything with it.
+ lock_guard<mutex> cblock(_swapchainLock);
+ if (_swapchain) { _swapchain->recordPresentTime(presentTimingInfo, drawable.presentedTime * 1.0e9); }
+ release();
+ }];
+ return;
}
+#endif
+
+ // If MTLDrawable.presentedTime/addPresentedHandler isn't supported,
+ // treat it as if the present happened when requested.
+ // Since this function may be called in a callback, it's possible that
+ // the swapchain has been released by the time this function runs.
+ // Lock the swapchain, and test if it is present before doing anything with it.
+ lock_guard<mutex> lock(_swapchainLock);
+ if (_swapchain) {_swapchain->recordPresentTime(presentTimingInfo); }
}
// Resets the MTLTexture and CAMetalDrawable underlying this image.
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSurface.h b/MoltenVK/MoltenVK/GPUObjects/MVKSurface.h
index bcb69fd..8ec3eb1 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSurface.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSurface.h
@@ -58,7 +58,7 @@
/** Returns the CAMetalLayer underlying this surface. */
inline CAMetalLayer* getCAMetalLayer() {
- std::lock_guard<std::mutex> lock(_lock);
+ std::lock_guard<std::mutex> lock(_layerLock);
return _mtlCAMetalLayer;
}
@@ -78,10 +78,11 @@
protected:
void propagateDebugName() override {}
void initLayerObserver();
+ void releaseLayer();
MVKInstance* _mvkInstance;
CAMetalLayer* _mtlCAMetalLayer;
- std::mutex _lock;
MVKBlockObserver* _layerObserver;
+ std::mutex _layerLock;
};
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSurface.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSurface.mm
index a70537c..a355177 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSurface.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSurface.mm
@@ -77,18 +77,20 @@
_layerObserver = [MVKBlockObserver observerWithBlock: ^(NSString* path, id, NSDictionary*, void*) {
if ( ![path isEqualToString: @"layer"] ) { return; }
- std::lock_guard<std::mutex> lock(this->_lock);
- [this->_mtlCAMetalLayer release];
- this->_mtlCAMetalLayer = nil;
- this->setConfigurationResult(VK_ERROR_SURFACE_LOST_KHR);
- [this->_layerObserver release];
- this->_layerObserver = nil;
+ this->releaseLayer();
} forObject: _mtlCAMetalLayer.delegate atKeyPath: @"layer"];
}
-MVKSurface::~MVKSurface() {
- std::lock_guard<std::mutex> lock(_lock);
+void MVKSurface::releaseLayer() {
+ std::lock_guard<std::mutex> lock(_layerLock);
+ setConfigurationResult(VK_ERROR_SURFACE_LOST_KHR);
[_mtlCAMetalLayer release];
+ _mtlCAMetalLayer = nil;
[_layerObserver release];
+ _layerObserver = nil;
+}
+
+MVKSurface::~MVKSurface() {
+ releaseLayer();
}
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h
index 7521e65..76ae1ce 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h
@@ -21,6 +21,7 @@
#include "MVKDevice.h"
#include "MVKImage.h"
#include "MVKSmallVector.h"
+#include <mutex>
#import "CAMetalLayer+MoltenVK.h"
#import <Metal/Metal.h>
@@ -111,6 +112,7 @@
void propagateDebugName() override;
void initCAMetalLayer(const VkSwapchainCreateInfoKHR* pCreateInfo, uint32_t imgCnt);
void initSurfaceImages(const VkSwapchainCreateInfoKHR* pCreateInfo, uint32_t imgCnt);
+ void releaseLayer();
void releaseUndisplayedSurfaces();
uint64_t getNextAcquisitionID();
void willPresentSurface(id<MTLTexture> mtlTexture, id<MTLCommandBuffer> mtlCmdBuff);
@@ -126,6 +128,7 @@
uint32_t _currentPerfLogFrameCount;
std::atomic<bool> _surfaceLost;
MVKBlockObserver* _layerObserver;
+ std::mutex _layerLock;
static const int kMaxPresentationHistory = 60;
VkPastPresentationTimingGOOGLE _presentTimingHistory[kMaxPresentationHistory];
uint32_t _presentHistoryCount;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
index d614790..781f72b 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
@@ -255,13 +255,13 @@
void MVKSwapchain::initCAMetalLayer(const VkSwapchainCreateInfoKHR* pCreateInfo, uint32_t imgCnt) {
MVKSurface* mvkSrfc = (MVKSurface*)pCreateInfo->surface;
- if ( !mvkSrfc->getCAMetalLayer() ) {
+ _mtlLayer = mvkSrfc->getCAMetalLayer();
+ if ( !_mtlLayer ) {
setConfigurationResult(mvkSrfc->getConfigurationResult());
_surfaceLost = true;
return;
}
- _mtlLayer = mvkSrfc->getCAMetalLayer();
_mtlLayer.device = getMTLDevice();
_mtlLayer.pixelFormat = getPixelFormats()->getMTLPixelFormat(pCreateInfo->imageFormat);
_mtlLayer.maximumDrawableCountMVK = imgCnt;
@@ -341,13 +341,18 @@
// needs to recreate the swapchain, or no content will be displayed.
_layerObserver = [MVKBlockObserver observerWithBlock: ^(NSString* path, id, NSDictionary*, void*) {
if ( ![path isEqualToString: @"layer"] ) { return; }
- this->_surfaceLost = true;
- [this->_layerObserver release];
- this->_layerObserver = nil;
+ this->releaseLayer();
} forObject: _mtlLayer.delegate atKeyPath: @"layer"];
}
}
+void MVKSwapchain::releaseLayer() {
+ std::lock_guard<std::mutex> lock(_layerLock);
+ _surfaceLost = true;
+ [_layerObserver release];
+ _layerObserver = nil;
+}
+
// Initializes the array of images used for the surface of this swapchain.
// The CAMetalLayer should already be initialized when this is called.
void MVKSwapchain::initSurfaceImages(const VkSwapchainCreateInfoKHR* pCreateInfo, uint32_t imgCnt) {
@@ -494,6 +499,6 @@
MVKSwapchain::~MVKSwapchain() {
if (_licenseWatermark) { _licenseWatermark->destroy(); }
- [this->_layerObserver release];
+ releaseLayer();
}