Merge pull request #850 from billhollings/master
Support screen captures.
diff --git a/Docs/Whats_New.md b/Docs/Whats_New.md
index 6c389d4..a32632d 100644
--- a/Docs/Whats_New.md
+++ b/Docs/Whats_New.md
@@ -29,6 +29,7 @@
- Add ability to disable command memory pooling using `MVK_CONFIG_USE_COMMAND_POOLING`
environment variable.
- Fix memory leak when pre-filling `MTLCommandBuffers` using `MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS`.
+- Broaden conditions for host read sync for image memory barriers on macOS.
- Update the `README.md` and `MoltenVK_Runtime_UserGuide.md` documents to clarify that
**MoltenVK** is not a fully-compliant implementation of *Vulkan*.
- Support Xcode 11.4.
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
index 423bc0e..b49b4f1 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
@@ -2131,7 +2131,7 @@
}
}
if (swapchainInfo) {
- return (MVKImage*)addResource(new MVKSwapchainImage(this, pCreateInfo, (MVKSwapchain*)swapchainInfo->swapchain));
+ return createSwapchainImage(pCreateInfo, (MVKSwapchain*)swapchainInfo->swapchain, uint32_t(-1), pAllocator);
}
return (MVKImage*)addResource(new MVKImage(this, pCreateInfo));
}
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
index 48a951a..fc1ba81 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
@@ -272,6 +272,82 @@
#pragma mark -
+#pragma mark MVKSwapchainImage
+
+/** Indicates the relative availability of each image in the swapchain. */
+typedef struct MVKSwapchainImageAvailability {
+ uint64_t acquisitionID; /**< When this image was last made available, relative to the other images in the swapchain. Smaller value is earlier. */
+ uint32_t waitCount; /**< The number of semaphores already waiting for this image. */
+ bool isAvailable; /**< Indicates whether this image is currently available. */
+
+ bool operator< (const MVKSwapchainImageAvailability& rhs) const;
+} MVKSwapchainImageAvailability;
+
+/** Tracks a semaphore and fence for later signaling. */
+typedef std::pair<MVKSemaphore*, MVKFence*> MVKSwapchainSignaler;
+
+
+/** Represents a Vulkan image used as a rendering destination within a swapchain. */
+class MVKSwapchainImage : public MVKImage {
+
+public:
+
+ /** Binds this resource to the specified offset within the specified memory allocation. */
+ VkResult bindDeviceMemory(MVKDeviceMemory* mvkMem, VkDeviceSize memOffset) override;
+
+ /** Binds this resource according to the specified bind information. */
+ VkResult bindDeviceMemory2(const void* pBindInfo) override;
+
+
+#pragma mark Metal
+
+ /**
+ * Presents the contained drawable to the OS, releases the Metal drawable and its
+ * texture back to the Metal layer's pool, and makes the image memory available for new use.
+ *
+ * If mtlCmdBuff is not nil, the contained drawable is scheduled for presentation using
+ * the presentDrawable: method of the command buffer. If mtlCmdBuff is nil, the contained
+ * drawable is presented immediately using the present method of the drawable.
+ */
+ void presentCAMetalDrawable(id<MTLCommandBuffer> mtlCmdBuff);
+
+
+#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;
+
+protected:
+ friend MVKSwapchain;
+
+ id<MTLTexture> newMTLTexture() override;
+ id<CAMetalDrawable> getCAMetalDrawable();
+ void resetMetalDrawable();
+ MVKSwapchainImageAvailability getAvailability();
+ void makeAvailable();
+ void signalWhenAvailable(MVKSemaphore* semaphore, MVKFence* fence);
+ void signal(MVKSwapchainSignaler& signaler, id<MTLCommandBuffer> mtlCmdBuff);
+ void signalPresentationSemaphore(id<MTLCommandBuffer> mtlCmdBuff);
+ static void markAsTracked(MVKSwapchainSignaler& signaler);
+ static void unmarkAsTracked(MVKSwapchainSignaler& signaler);
+ void renderWatermark(id<MTLCommandBuffer> mtlCmdBuff);
+
+ MVKSwapchain* _swapchain;
+ uint32_t _swapchainIndex;
+ id<CAMetalDrawable> _mtlDrawable;
+ MVKSwapchainImageAvailability _availability;
+ MVKVectorInline<MVKSwapchainSignaler, 1> _availabilitySignalers;
+ MVKSwapchainSignaler _preSignaler;
+ std::mutex _availabilityLock;
+};
+
+
+#pragma mark -
#pragma mark MVKImageView
/** Represents a Vulkan image view. */
@@ -401,58 +477,3 @@
SPIRV_CROSS_NAMESPACE::MSLConstexprSampler _constExprSampler;
bool _requiresConstExprSampler;
};
-
-
-#pragma mark -
-#pragma mark MVKSwapchainImage
-
-/** Represents a Vulkan image used as a rendering destination within a swapchain. */
-class MVKSwapchainImage : public MVKImage {
-
-public:
-
- /** Binds this resource to the specified offset within the specified memory allocation. */
- VkResult bindDeviceMemory(MVKDeviceMemory* mvkMem, VkDeviceSize memOffset) override;
-
- /** Binds this resource according to the specified bind information. */
- VkResult bindDeviceMemory2(const void* pBindInfo) override;
-
-
-#pragma mark Metal
-
- /**
- * Presents the contained drawable to the OS, releases the Metal drawable and its
- * texture back to the Metal layer's pool, and makes the image memory available for new use.
- *
- * If mtlCmdBuff is not nil, the contained drawable is scheduled for presentation using
- * the presentDrawable: method of the command buffer. If mtlCmdBuff is nil, the contained
- * drawable is presented immediately using the present method of the drawable.
- */
- void presentCAMetalDrawable(id<MTLCommandBuffer> mtlCmdBuff);
-
-
-#pragma mark Construction
-
- /** Constructs an instance for the specified device and swapchain. */
- MVKSwapchainImage(MVKDevice* device,
- const VkImageCreateInfo* pCreateInfo,
- MVKSwapchain* swapchain,
- uint32_t swapchainIndex);
-
- /** Constructs an instance for the specified device and swapchain, without binding to a particular swapchain image index. */
- MVKSwapchainImage(MVKDevice* device,
- const VkImageCreateInfo* pCreateInfo,
- MVKSwapchain* swapchain);
-
- ~MVKSwapchainImage() override;
-
-protected:
- id<MTLTexture> newMTLTexture() override;
- id<CAMetalDrawable> getCAMetalDrawable();
- void resetMetalSurface();
- void renderWatermark(id<MTLCommandBuffer> mtlCmdBuff);
-
- MVKSwapchain* _swapchain;
- uint32_t _swapchainIndex;
-};
-
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
index dd30a27..4a226d9 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
@@ -21,10 +21,9 @@
#include "MVKSwapchain.h"
#include "MVKCommandBuffer.h"
#include "MVKCmdDebug.h"
-#include "mvk_datatypes.hpp"
-#include "MVKFoundation.h"
-#include "MVKLogging.h"
#include "MVKEnvironment.h"
+#include "MVKFoundation.h"
+#include "MVKOSExtensions.h"
#include "MVKLogging.h"
#include "MVKCodec.h"
#import "MTLTextureDescriptor+MoltenVK.h"
@@ -143,8 +142,7 @@
#endif
#if MVK_MACOS
return ((pImageMemoryBarrier->newLayout == VK_IMAGE_LAYOUT_GENERAL) &&
- mvkIsAnyFlagEnabled(dstStageMask, (VK_PIPELINE_STAGE_HOST_BIT)) &&
- mvkIsAnyFlagEnabled(pImageMemoryBarrier->dstAccessMask, (VK_ACCESS_HOST_READ_BIT)) &&
+ mvkIsAnyFlagEnabled(pImageMemoryBarrier->dstAccessMask, (VK_ACCESS_HOST_READ_BIT | VK_ACCESS_MEMORY_READ_BIT)) &&
isMemoryHostAccessible() && !isMemoryHostCoherent());
#endif
}
@@ -287,11 +285,13 @@
id<MTLTexture> mtlTex = _mtlTextureViews[mtlPixFmt];
if ( !mtlTex ) {
- // Lock and check again in case another thread has created the texture.
+ // Lock and check again in case another thread has created the view texture.
+ // baseTex retreived outside of lock to avoid deadlock if it too needs to be lazily created.
+ id<MTLTexture> baseTex = getMTLTexture();
lock_guard<mutex> lock(_lock);
mtlTex = _mtlTextureViews[mtlPixFmt];
if ( !mtlTex ) {
- mtlTex = [getMTLTexture() newTextureViewWithPixelFormat: mtlPixFmt]; // retained
+ mtlTex = [baseTex newTextureViewWithPixelFormat: mtlPixFmt]; // retained
_mtlTextureViews[mtlPixFmt] = mtlTex;
}
}
@@ -348,10 +348,12 @@
return mtlTex;
}
-// Removes and releases the MTLTexture object, so that it can be lazily created by getMTLTexture().
+// Removes and releases the MTLTexture object, and all associated texture views
void MVKImage::resetMTLTexture() {
[_mtlTexture release];
_mtlTexture = nil;
+ for (auto elem : _mtlTextureViews) { [elem.second release]; }
+ _mtlTextureViews.clear();
}
void MVKImage::resetIOSurface() {
@@ -364,6 +366,7 @@
IOSurfaceRef MVKImage::getIOSurface() { return _ioSurface; }
VkResult MVKImage::useIOSurface(IOSurfaceRef ioSurface) {
+ lock_guard<mutex> lock(_lock);
if (!_device->_pMetalFeatures->ioSurfaces) { return reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkUseIOSurfaceMVK() : IOSurfaces are not supported on this platform."); }
@@ -787,7 +790,208 @@
if (_deviceMemory) { _deviceMemory->removeImage(this); }
resetMTLTexture();
resetIOSurface();
- for (auto elem : _mtlTextureViews) { [elem.second release]; }
+}
+
+
+#pragma mark -
+#pragma mark MVKSwapchainImage
+
+VkResult MVKSwapchainImage::bindDeviceMemory(MVKDeviceMemory*, VkDeviceSize) {
+ return VK_ERROR_OUT_OF_DEVICE_MEMORY;
+}
+
+VkResult MVKSwapchainImage::bindDeviceMemory2(const void* pBindInfo) {
+ const auto* imageInfo = (const VkBindImageMemoryInfo*)pBindInfo;
+ const VkBindImageMemorySwapchainInfoKHR* swapchainInfo = nullptr;
+ for (const auto* next = (const VkBaseInStructure*)imageInfo->pNext; next; next = next->pNext) {
+ switch (next->sType) {
+ case VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_SWAPCHAIN_INFO_KHR:
+ swapchainInfo = (const VkBindImageMemorySwapchainInfoKHR*)next;
+ break;
+ default:
+ break;
+ }
+ if (swapchainInfo) { break; }
+ }
+ if (!swapchainInfo) {
+ return VK_ERROR_OUT_OF_DEVICE_MEMORY;
+ }
+ _swapchainIndex = swapchainInfo->imageIndex;
+ return VK_SUCCESS;
+}
+
+bool MVKSwapchainImageAvailability::operator< (const MVKSwapchainImageAvailability& rhs) const {
+ if ( isAvailable && !rhs.isAvailable) { return true; }
+ if ( !isAvailable && rhs.isAvailable) { return false; }
+
+ if (waitCount < rhs.waitCount) { return true; }
+ if (waitCount > rhs.waitCount) { return false; }
+
+ return acquisitionID < rhs.acquisitionID;
+}
+
+MVKSwapchainImageAvailability MVKSwapchainImage::getAvailability() {
+ lock_guard<mutex> lock(_availabilityLock);
+
+ return _availability;
+}
+
+// Makes an image available for acquisition by the app.
+// If any semaphores are waiting to be signaled when this image becomes available, the
+// earliest semaphore is signaled, and this image remains unavailable for other uses.
+void MVKSwapchainImage::makeAvailable() {
+ lock_guard<mutex> lock(_availabilityLock);
+
+ // Mark when this event happened, relative to that of other images
+ _availability.acquisitionID = _swapchain->getNextAcquisitionID();
+
+ // Mark this image as available if no semaphores or fences are waiting to be signaled.
+ _availability.isAvailable = _availabilitySignalers.empty();
+
+ MVKSwapchainSignaler signaler;
+ if (_availability.isAvailable) {
+ // If this image is available, signal the semaphore and fence that were associated
+ // with the last time this image was acquired while available. This is a workaround for
+ // when an app uses a single semaphore or fence for more than one swapchain image.
+ // Becuase 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;
+ } else {
+ // If this image is not yet available, extract and signal the first semaphore and fence.
+ auto sigIter = _availabilitySignalers.begin();
+ signaler = *sigIter;
+ _availabilitySignalers.erase(sigIter);
+ }
+
+ // Signal the semaphore and fence, and let them know they are no longer being tracked.
+ signal(signaler, nil);
+ unmarkAsTracked(signaler);
+
+// MVKLogDebug("Signaling%s swapchain image %p semaphore %p from present, with %lu remaining semaphores.", (_availability.isAvailable ? " pre-signaled" : ""), this, signaler.first, _availabilitySignalers.size());
+}
+
+void MVKSwapchainImage::signalWhenAvailable(MVKSemaphore* semaphore, MVKFence* fence) {
+ lock_guard<mutex> lock(_availabilityLock);
+
+ auto signaler = make_pair(semaphore, fence);
+ if (_availability.isAvailable) {
+ _availability.isAvailable = false;
+
+ // If signalling through a MTLEvent, use an ephemeral MTLCommandBuffer.
+ // Another option would be to use MTLSharedEvent in MVKSemaphore, but that might
+ // impose unacceptable performance costs to handle this particular case.
+ @autoreleasepool {
+ MVKSemaphore* mvkSem = signaler.first;
+ id<MTLCommandBuffer> mtlCmdBuff = (mvkSem && mvkSem->isUsingCommandEncoding()
+ ? [_device->getAnyQueue()->getMTLCommandQueue() commandBufferWithUnretainedReferences]
+ : nil);
+ signal(signaler, mtlCmdBuff);
+ [mtlCmdBuff commit];
+ }
+
+ _preSignaler = signaler;
+ } else {
+ _availabilitySignalers.push_back(signaler);
+ }
+ markAsTracked(signaler);
+
+// MVKLogDebug("%s swapchain image %p semaphore %p in acquire with %lu other semaphores.", (_availability.isAvailable ? "Signaling" : "Tracking"), this, semaphore, _availabilitySignalers.size());
+}
+
+// If present, signal the semaphore for the first waiter for the given image.
+void MVKSwapchainImage::signalPresentationSemaphore(id<MTLCommandBuffer> mtlCmdBuff) {
+ lock_guard<mutex> lock(_availabilityLock);
+
+ if ( !_availabilitySignalers.empty() ) {
+ MVKSemaphore* mvkSem = _availabilitySignalers.front().first;
+ if (mvkSem) { mvkSem->encodeSignal(mtlCmdBuff); }
+ }
+}
+
+// Signal either or both of the semaphore and fence in the specified tracker pair.
+void MVKSwapchainImage::signal(MVKSwapchainSignaler& signaler, id<MTLCommandBuffer> mtlCmdBuff) {
+ if (signaler.first) { signaler.first->encodeSignal(mtlCmdBuff); }
+ if (signaler.second) { signaler.second->signal(); }
+}
+
+// Tell the semaphore and fence that they are being tracked for future signaling.
+void MVKSwapchainImage::markAsTracked(MVKSwapchainSignaler& signaler) {
+ if (signaler.first) { signaler.first->retain(); }
+ if (signaler.second) { signaler.second->retain(); }
+}
+
+// Tell the semaphore and fence that they are no longer being tracked for future signaling.
+void MVKSwapchainImage::unmarkAsTracked(MVKSwapchainSignaler& signaler) {
+ if (signaler.first) { signaler.first->release(); }
+ if (signaler.second) { signaler.second->release(); }
+}
+
+
+#pragma mark Metal
+
+// Creates and returns a retained Metal texture suitable for use in this instance.
+// This implementation retrieves a MTLTexture from the CAMetalDrawable.
+id<MTLTexture> MVKSwapchainImage::newMTLTexture() {
+ return [[getCAMetalDrawable() texture] retain];
+}
+
+id<CAMetalDrawable> MVKSwapchainImage::getCAMetalDrawable() {
+ while ( !_mtlDrawable ) {
+ @autoreleasepool { // Reclaim auto-released drawable object before end of loop
+ uint64_t startTime = _device->getPerformanceTimestamp();
+
+ _mtlDrawable = [_swapchain->_mtlLayer.nextDrawable retain];
+ if ( !_mtlDrawable ) { MVKLogError("CAMetalDrawable could not be acquired after %.6f ms.", mvkGetElapsedMilliseconds(startTime)); }
+
+ _device->addActivityPerformance(_device->_performanceStatistics.queue.nextCAMetalDrawable, startTime);
+ }
+ }
+ return _mtlDrawable;
+}
+
+// Present the drawable and make myself available only once the command buffer has completed.
+void MVKSwapchainImage::presentCAMetalDrawable(id<MTLCommandBuffer> mtlCmdBuff) {
+ _swapchain->willPresentSurface(getMTLTexture(), mtlCmdBuff);
+
+ NSString* scName = _swapchain->getDebugName();
+ if (scName) { mvkPushDebugGroup(mtlCmdBuff, scName); }
+ [mtlCmdBuff presentDrawable: getCAMetalDrawable()];
+ if (scName) { mvkPopDebugGroup(mtlCmdBuff); }
+
+ signalPresentationSemaphore(mtlCmdBuff);
+
+ retain(); // Ensure this image is not destroyed while awaiting MTLCommandBuffer completion
+ [mtlCmdBuff addCompletedHandler: ^(id<MTLCommandBuffer> mcb) {
+ makeAvailable();
+ release();
+ }];
+}
+
+// Resets the MTLTexture and CAMetalDrawable underlying this image.
+void MVKSwapchainImage::resetMetalDrawable() {
+ resetMTLTexture(); // Release texture first so drawable will be last to release it
+ [_mtlDrawable release];
+ _mtlDrawable = nil;
+}
+
+
+#pragma mark Construction
+
+MVKSwapchainImage::MVKSwapchainImage(MVKDevice* device,
+ const VkImageCreateInfo* pCreateInfo,
+ MVKSwapchain* swapchain,
+ uint32_t swapchainIndex) : MVKImage(device, pCreateInfo) {
+ _swapchain = swapchain;
+ _swapchainIndex = swapchainIndex;
+ _mtlDrawable = nil;
+
+ _availability.acquisitionID = _swapchain->getNextAcquisitionID();
+ _availability.isAvailable = true;
+ _preSignaler = make_pair(nullptr, nullptr);
+}
+
+MVKSwapchainImage::~MVKSwapchainImage() {
+ resetMetalDrawable();
}
@@ -1225,94 +1429,3 @@
MVKSampler::~MVKSampler() {
[_mtlSamplerState release];
}
-
-
-#pragma mark -
-#pragma mark MVKSwapchainImage
-
-VkResult MVKSwapchainImage::bindDeviceMemory(MVKDeviceMemory*, VkDeviceSize) {
- return VK_ERROR_OUT_OF_DEVICE_MEMORY;
-}
-
-VkResult MVKSwapchainImage::bindDeviceMemory2(const void* pBindInfo) {
- const auto* imageInfo = (const VkBindImageMemoryInfo*)pBindInfo;
- const VkBindImageMemorySwapchainInfoKHR* swapchainInfo = nullptr;
- for (const auto* next = (const VkBaseInStructure*)imageInfo->pNext; next; next = next->pNext) {
- switch (next->sType) {
- case VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_SWAPCHAIN_INFO_KHR:
- swapchainInfo = (const VkBindImageMemorySwapchainInfoKHR*)next;
- break;
- default:
- break;
- }
- if (swapchainInfo) { break; }
- }
- if (!swapchainInfo) {
- return VK_ERROR_OUT_OF_DEVICE_MEMORY;
- }
- _swapchainIndex = swapchainInfo->imageIndex;
- return VK_SUCCESS;
-}
-
-
-#pragma mark Metal
-
-// Creates and returns a retained Metal texture suitable for use in this instance.
-// This implementation retrieves a MTLTexture from the CAMetalDrawable.
-id<MTLTexture> MVKSwapchainImage::newMTLTexture() {
- return [[getCAMetalDrawable() texture] retain];
-}
-
-id<CAMetalDrawable> MVKSwapchainImage::getCAMetalDrawable() {
- id<CAMetalDrawable> mtlDrawable = _swapchain->getCAMetalDrawable(_swapchainIndex);
- MVKAssert(mtlDrawable, "Could not acquire an available CAMetalDrawable from the CAMetalLayer in MVKSwapchain image: %p.", this);
- return mtlDrawable;
-}
-
-// Present the drawable and make myself available only once the command buffer has completed.
-void MVKSwapchainImage::presentCAMetalDrawable(id<MTLCommandBuffer> mtlCmdBuff) {
- _swapchain->willPresentSurface(getMTLTexture(), mtlCmdBuff);
-
- NSString* scName = _swapchain->getDebugName();
- if (scName) { mvkPushDebugGroup(mtlCmdBuff, scName); }
- [mtlCmdBuff presentDrawable: getCAMetalDrawable()];
- if (scName) { mvkPopDebugGroup(mtlCmdBuff); }
-
- resetMetalSurface();
- _swapchain->signalPresentationSemaphore(_swapchainIndex, mtlCmdBuff);
-
- retain(); // Ensure this image is not destroyed while awaiting MTLCommandBuffer completion
- [mtlCmdBuff addCompletedHandler: ^(id<MTLCommandBuffer> mcb) {
- _swapchain->makeAvailable(_swapchainIndex);
- release();
- }];
-}
-
-// Resets the MTLTexture and CAMetalDrawable underlying this image.
-void MVKSwapchainImage::resetMetalSurface() {
- resetMTLTexture(); // Release texture first so drawable will be last to release it
- _swapchain->resetCAMetalDrawable(_swapchainIndex);
-}
-
-
-#pragma mark Construction
-
-MVKSwapchainImage::MVKSwapchainImage(MVKDevice* device,
- const VkImageCreateInfo* pCreateInfo,
- MVKSwapchain* swapchain,
- uint32_t swapchainIndex) : MVKImage(device, pCreateInfo) {
- _swapchain = swapchain;
- _swapchainIndex = swapchainIndex;
-}
-
-MVKSwapchainImage::MVKSwapchainImage(MVKDevice* device,
- const VkImageCreateInfo* pCreateInfo,
- MVKSwapchain* swapchain) : MVKImage(device, pCreateInfo) {
- _swapchain = swapchain;
- _swapchainIndex = uint32_t(-1);
-}
-
-MVKSwapchainImage::~MVKSwapchainImage() {
- resetMetalSurface(); // remove drawable from swapchain
-}
-
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h
index cce5a85..8b88c64 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h
@@ -27,21 +27,9 @@
@class MVKBlockObserver;
-/** Indicates the relative availability of each image in the swapchain. */
-typedef struct MVKSwapchainImageAvailability {
- uint64_t acquisitionID; /**< When this image was last made available, relative to the other images in the swapchain. Smaller value is earlier. */
- uint32_t waitCount; /**< The number of semaphores already waiting for this image. */
- bool isAvailable; /**< Indicates whether this image is currently available. */
-
- bool operator< (const MVKSwapchainImageAvailability& rhs) const;
-} MVKSwapchainImageAvailability;
-
-
+#pragma mark -
#pragma mark MVKSwapchain
-/** Tracks a semaphore and fence for later signaling. */
-typedef std::pair<MVKSemaphore*, MVKFence*> MVKSwapchainSignaler;
-
/** Represents a Vulkan swapchain. */
class MVKSwapchain : public MVKVulkanAPIDeviceObject {
@@ -54,10 +42,10 @@
VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_SWAPCHAIN_KHR_EXT; }
/** Returns the number of images in this swapchain. */
- uint32_t getImageCount();
+ inline uint32_t getImageCount() { return (uint32_t)_surfaceImages.size(); }
/** Returns the image at the specified index. */
- MVKSwapchainImage* getImage(uint32_t index);
+ inline MVKSwapchainImage* getImage(uint32_t index) { return _surfaceImages[index]; }
/**
* Returns the array of presentable images associated with this swapchain.
@@ -93,26 +81,6 @@
/** Adds HDR metadata to this swapchain. */
void setHDRMetadataEXT(const VkHdrMetadataEXT& metadata);
- /**
- * Registers a semaphore and/or fence that will be signaled when the image at the given index becomes available.
- * This function accepts both a semaphore and a fence, and either none, one, or both may be provided.
- * If this image is available already, the semaphore and fence are immediately signaled.
- */
- void signalWhenAvailable(uint32_t imageIndex, MVKSemaphore* semaphore, MVKFence* fence);
-
-
-#pragma mark Metal
-
- /**
- * Returns the Metal drawable providing backing for the image at the given
- * index in this swapchain. If none is established, the next available
- * drawable is acquired and returned.
- *
- * This function may block until the next drawable is available,
- * and may return nil if no drawable is available at all.
- */
- id<CAMetalDrawable> getCAMetalDrawable(uint32_t imgIdx);
-
#pragma mark Construction
@@ -123,12 +91,6 @@
protected:
friend class MVKSwapchainImage;
- struct Availability {
- MVKSwapchainImageAvailability status;
- MVKVectorInline<MVKSwapchainSignaler, 1> signalers;
- MVKSwapchainSignaler preSignaled;
- };
-
void propogateDebugName() override;
void initCAMetalLayer(const VkSwapchainCreateInfoKHR* pCreateInfo, uint32_t imgCnt);
void initSurfaceImages(const VkSwapchainCreateInfoKHR* pCreateInfo, uint32_t imgCnt);
@@ -138,19 +100,10 @@
void willPresentSurface(id<MTLTexture> mtlTexture, id<MTLCommandBuffer> mtlCmdBuff);
void renderWatermark(id<MTLTexture> mtlTexture, id<MTLCommandBuffer> mtlCmdBuff);
void markFrameInterval();
- void resetCAMetalDrawable(uint32_t imgIdx);
- void signal(MVKSwapchainSignaler& signaler, id<MTLCommandBuffer> mtlCmdBuff);
- void signalPresentationSemaphore(uint32_t imgIdx, id<MTLCommandBuffer> mtlCmdBuff);
- static void markAsTracked(MVKSwapchainSignaler& signaler);
- static void unmarkAsTracked(MVKSwapchainSignaler& signaler);
- void makeAvailable(uint32_t imgIdx);
CAMetalLayer* _mtlLayer;
MVKWatermark* _licenseWatermark;
MVKVectorInline<MVKSwapchainImage*, kMVKMaxSwapchainImageCount> _surfaceImages;
- MVKVectorInline<id<CAMetalDrawable>, kMVKMaxSwapchainImageCount> _mtlDrawables;
- MVKVectorInline<Availability, kMVKMaxSwapchainImageCount> _imageAvailability;
- std::mutex _availabilityLock;
std::atomic<uint64_t> _currentAcquisitionID;
CGSize _mtlLayerOrigDrawSize;
MVKSwapchainPerformance _performanceStatistics;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
index 1543d7b..b93d3b9 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
@@ -35,18 +35,9 @@
using namespace std;
+#pragma mark -
#pragma mark MVKSwapchain
-bool MVKSwapchainImageAvailability::operator< (const MVKSwapchainImageAvailability& rhs) const {
- if ( isAvailable && !rhs.isAvailable) { return true; }
- if ( !isAvailable && rhs.isAvailable) { return false; }
-
- if (waitCount < rhs.waitCount) { return true; }
- if (waitCount > rhs.waitCount) { return false; }
-
- return acquisitionID < rhs.acquisitionID;
-}
-
void MVKSwapchain::propogateDebugName() {
if (_debugName) {
size_t imgCnt = _surfaceImages.size();
@@ -58,10 +49,6 @@
}
}
-uint32_t MVKSwapchain::getImageCount() { return (uint32_t)_imageAvailability.size(); }
-
-MVKSwapchainImage* MVKSwapchain::getImage(uint32_t index) { return _surfaceImages[index]; }
-
VkResult MVKSwapchain::getImages(uint32_t* pCount, VkImage* pSwapchainImages) {
// Get the number of surface images
@@ -86,29 +73,34 @@
}
VkResult MVKSwapchain::acquireNextImageKHR(uint64_t timeout,
- VkSemaphore semaphore,
- VkFence fence,
+ VkSemaphore semaphore,
+ VkFence fence,
uint32_t deviceMask,
- uint32_t* pImageIndex) {
+ uint32_t* pImageIndex) {
- if ( getIsSurfaceLost() ) { return VK_ERROR_SURFACE_LOST_KHR; }
+ if ( getIsSurfaceLost() ) { return VK_ERROR_SURFACE_LOST_KHR; }
- // Find the image that has the smallest availability measure
- uint32_t minWaitIndex = 0;
- MVKSwapchainImageAvailability minAvailability = { .acquisitionID = kMVKUndefinedLargeUInt64,
+ // Find the image that has the smallest availability measure
+ MVKSwapchainImage* minWaitImage = nullptr;
+ MVKSwapchainImageAvailability minAvailability = { .acquisitionID = kMVKUndefinedLargeUInt64,
.waitCount = kMVKUndefinedLargeUInt32,
.isAvailable = false };
- for (uint32_t imgIdx = 0; imgIdx < _imageAvailability.size(); imgIdx++) {
- const Availability& avail = _imageAvailability[imgIdx];
- if (avail.status < minAvailability) {
- minAvailability = avail.status;
- minWaitIndex = imgIdx;
- }
- }
+ uint32_t imgCnt = getImageCount();
+ for (uint32_t imgIdx = 0; imgIdx < imgCnt; imgIdx++) {
+ MVKSwapchainImage* img = getImage(imgIdx);
+ auto imgAvail = img->getAvailability();
+ if (imgAvail < minAvailability) {
+ minAvailability = imgAvail;
+ minWaitImage = img;
+ }
+ }
- *pImageIndex = minWaitIndex; // Return the index of the image with the shortest wait
- signalWhenAvailable(minWaitIndex, (MVKSemaphore*)semaphore, (MVKFence*)fence);
- return getHasSurfaceSizeChanged() ? VK_ERROR_OUT_OF_DATE_KHR : VK_SUCCESS;
+ // Return the index of the image with the shortest wait and signal the semaphore and fence when it's available
+ *pImageIndex = minWaitImage->_swapchainIndex;
+ minWaitImage->resetMetalDrawable();
+ minWaitImage->signalWhenAvailable((MVKSemaphore*)semaphore, (MVKFence*)fence);
+
+ return getHasSurfaceSizeChanged() ? VK_ERROR_OUT_OF_DATE_KHR : VK_SUCCESS;
}
bool MVKSwapchain::getHasSurfaceSizeChanged() {
@@ -117,104 +109,10 @@
uint64_t MVKSwapchain::getNextAcquisitionID() { return ++_currentAcquisitionID; }
-/**
- * Releases any surfaces that are not currently being displayed,
- * so they can be used by a different swapchain.
- */
+// Releases any surfaces that are not currently being displayed,
+// so they can be used by a different swapchain.
void MVKSwapchain::releaseUndisplayedSurfaces() {}
-// Makes an image available for acquisition by the app.
-// If any semaphores are waiting to be signaled when this image becomes available, the
-// earliest semaphore is signaled, and this image remains unavailable for other uses.
-void MVKSwapchain::makeAvailable(uint32_t imgIdx) {
- lock_guard<mutex> lock(_availabilityLock);
- auto& availability = _imageAvailability[imgIdx].status;
-
- // Mark when this event happened, relative to that of other images
- availability.acquisitionID = getNextAcquisitionID();
-
- // Mark this image as available if no semaphores or fences are waiting to be signaled.
- availability.isAvailable = _imageAvailability[imgIdx].signalers.empty();
-
- MVKSwapchainSignaler signaler;
- if (availability.isAvailable) {
- // If this image is available, signal the semaphore and fence that were associated
- // with the last time this image was acquired while available. This is a workaround for
- // when an app uses a single semaphore or fence for more than one swapchain image.
- // Becuase 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 = _imageAvailability[imgIdx].preSignaled;
- } else {
- // If this image is not yet available, extract and signal the first semaphore and fence.
- auto& imgSignalers = _imageAvailability[imgIdx].signalers;
- auto sigIter = imgSignalers.begin();
- signaler = *sigIter;
- imgSignalers.erase(sigIter);
- }
-
- // Signal the semaphore and fence, and let them know they are no longer being tracked.
- signal(signaler, nil);
- unmarkAsTracked(signaler);
-
-// MVKLogDebug("Signaling%s swapchain image %p semaphore %p from present, with %lu remaining semaphores.", (_availability.isAvailable ? " pre-signaled" : ""), this, signaler.first, _availabilitySignalers.size());
-}
-
-void MVKSwapchain::signalWhenAvailable(uint32_t imageIndex, MVKSemaphore* semaphore, MVKFence* fence) {
- lock_guard<mutex> lock(_availabilityLock);
- auto signaler = make_pair(semaphore, fence);
- auto& availability = _imageAvailability[imageIndex].status;
- if (availability.isAvailable) {
- availability.isAvailable = false;
-
- // If signalling through a MTLEvent, use an ephemeral MTLCommandBuffer.
- // Another option would be to use MTLSharedEvent in MVKSemaphore, but that might
- // impose unacceptable performance costs to handle this particular case.
- @autoreleasepool {
- MVKSemaphore* mvkSem = signaler.first;
- id<MTLCommandBuffer> mtlCmdBuff = (mvkSem && mvkSem->isUsingCommandEncoding()
- ? [_device->getAnyQueue()->getMTLCommandQueue() commandBufferWithUnretainedReferences]
- : nil);
- signal(signaler, mtlCmdBuff);
- [mtlCmdBuff commit];
- }
-
- _imageAvailability[imageIndex].preSignaled = signaler;
- } else {
- _imageAvailability[imageIndex].signalers.push_back(signaler);
- }
- markAsTracked(signaler);
-
-// MVKLogDebug("%s swapchain image %p semaphore %p in acquire with %lu other semaphores.", (_availability.isAvailable ? "Signaling" : "Tracking"), this, semaphore, _availabilitySignalers.size());
-}
-
-// Signal either or both of the semaphore and fence in the specified tracker pair.
-void MVKSwapchain::signal(MVKSwapchainSignaler& signaler, id<MTLCommandBuffer> mtlCmdBuff) {
- if (signaler.first) { signaler.first->encodeSignal(mtlCmdBuff); }
- if (signaler.second) { signaler.second->signal(); }
-}
-
-// If present, signal the semaphore for the first waiter for the given image.
-void MVKSwapchain::signalPresentationSemaphore(uint32_t imgIdx, id<MTLCommandBuffer> mtlCmdBuff) {
- lock_guard<mutex> lock(_availabilityLock);
- auto& imgSignalers = _imageAvailability[imgIdx].signalers;
- if ( !imgSignalers.empty() ) {
- MVKSemaphore* mvkSem = imgSignalers.front().first;
- if (mvkSem) { mvkSem->encodeSignal(mtlCmdBuff); }
- }
-}
-
-// Tell the semaphore and fence that they are being tracked for future signaling.
-void MVKSwapchain::markAsTracked(MVKSwapchainSignaler& signaler) {
- if (signaler.first) { signaler.first->retain(); }
- if (signaler.second) { signaler.second->retain(); }
-}
-
-// Tell the semaphore and fence that they are no longer being tracked for future signaling.
-void MVKSwapchain::unmarkAsTracked(MVKSwapchainSignaler& signaler) {
- if (signaler.first) { signaler.first->release(); }
- if (signaler.second) { signaler.second->release(); }
-}
-
#pragma mark Rendering
@@ -343,31 +241,6 @@
}
-#pragma mark Metal
-
-id<CAMetalDrawable> MVKSwapchain::getCAMetalDrawable(uint32_t imageIndex) {
- id<CAMetalDrawable> nextDrwbl = _mtlDrawables[imageIndex];
- while ( !nextDrwbl ) {
- @autoreleasepool { // Allow auto-released drawable object to be reclaimed before end of loop
- uint64_t startTime = _device->getPerformanceTimestamp();
-
- nextDrwbl = _mtlLayer.nextDrawable;
- if ( !nextDrwbl ) { MVKLogError("Drawable could not be retrieved! Elapsed time: %.6f ms.", mvkGetElapsedMilliseconds()); }
- _mtlDrawables[imageIndex] = [nextDrwbl retain];
-
- _device->addActivityPerformance(_device->_performanceStatistics.queue.nextCAMetalDrawable, startTime);
- }
- }
- return nextDrwbl;
-}
-
-// Removes and releases a Metal drawable object, so that it can be lazily created by getCAMetalDrawable().
-void MVKSwapchain::resetCAMetalDrawable(uint32_t imgIdx) {
- [_mtlDrawables[imgIdx] release];
- _mtlDrawables[imgIdx] = nil;
-}
-
-
#pragma mark Construction
MVKSwapchain::MVKSwapchain(MVKDevice* device,
@@ -510,16 +383,9 @@
mvkEnableFlags(imgInfo.flags, VK_IMAGE_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT);
}
- _surfaceImages.reserve(imgCnt);
- _mtlDrawables.resize(imgCnt);
- _imageAvailability.resize(imgCnt);
- for (uint32_t imgIdx = 0; imgIdx < imgCnt; imgIdx++) {
- _surfaceImages.push_back(_device->createSwapchainImage(&imgInfo, this, imgIdx, NULL));
- _imageAvailability[imgIdx].status.acquisitionID = getNextAcquisitionID();
- _imageAvailability[imgIdx].status.isAvailable = true;
- _imageAvailability[imgIdx].preSignaled = make_pair(nullptr, nullptr);
- _mtlDrawables[imgIdx] = nil;
- }
+ for (uint32_t imgIdx = 0; imgIdx < imgCnt; imgIdx++) {
+ _surfaceImages.push_back(_device->createSwapchainImage(&imgInfo, this, imgIdx, NULL));
+ }
MVKLogInfo("Created %d swapchain images with initial size (%d, %d).", imgCnt, imgExtent.width, imgExtent.height);
}