Support the VK_KHR_incremental_present extension.

This extension allows apps to provide a hint to the presentation engine
indicating which parts of the surface need updating. To provide this
hint, we call `-[CALayer setNeedsDisplayInRect:]`, which indicates that
only the given rectangle needs updating.

I'm not sure if this will have any effect, especially if
`CAMetalLayer.presentsWithTransaction` is `NO`. Luckily for us, this is
only a hint, and it is permissible for the presentation engine to do
nothing with the hint.

The tests don't work because they apparently can't handle
`VK_SUBOPTIMAL_KHR` being returned.
This commit is contained in:
Chip Davis 2023-07-10 00:54:17 -07:00
parent 2db85ea060
commit 3914b0f07d
6 changed files with 52 additions and 4 deletions

View File

@ -434,6 +434,12 @@ VkExtent2D mvkVkExtent2DFromCGSize(CGSize cgSize);
/** Returns a CGSize that corresponds to the specified VkExtent2D. */
CGSize mvkCGSizeFromVkExtent2D(VkExtent2D vkExtent);
/** Returns a CGPoint that corresponds to the specified VkOffset2D. */
CGPoint mvkCGPointFromVkOffset2D(VkOffset2D vkOffset);
/** Returns a CGRect that corresponds to the specified VkRectLayerKHR. The layer is ignored. */
CGRect mvkCGRectFromVkRectLayerKHR(VkRectLayerKHR vkRect);
/** Returns a Metal MTLOrigin constructed from a VkOffset3D. */
static inline MTLOrigin mvkMTLOriginFromVkOffset3D(VkOffset3D vkOffset) {
return MTLOriginMake(vkOffset.x, vkOffset.y, vkOffset.z);

View File

@ -583,8 +583,12 @@ MVKQueuePresentSurfaceSubmission::MVKQueuePresentSurfaceSubmission(MVKQueue* que
const VkPresentTimesInfoGOOGLE* pPresentTimesInfo = nullptr;
const VkSwapchainPresentFenceInfoEXT* pPresentFenceInfo = nullptr;
const VkSwapchainPresentModeInfoEXT* pPresentModeInfo = nullptr;
const VkPresentRegionsKHR* pPresentRegions = nullptr;
for (auto* next = (const VkBaseInStructure*)pPresentInfo->pNext; next; next = next->pNext) {
switch (next->sType) {
case VK_STRUCTURE_TYPE_PRESENT_REGIONS_KHR:
pPresentRegions = (const VkPresentRegionsKHR*) next;
break;
case VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_FENCE_INFO_EXT:
pPresentFenceInfo = (const VkSwapchainPresentFenceInfoEXT*) next;
break;
@ -616,6 +620,10 @@ MVKQueuePresentSurfaceSubmission::MVKQueuePresentSurfaceSubmission(MVKQueue* que
pFences = pPresentFenceInfo->pFences;
MVKAssert(pPresentFenceInfo->swapchainCount == scCnt, "VkSwapchainPresentFenceInfoEXT swapchainCount must match VkPresentInfo swapchainCount.");
}
const VkPresentRegionKHR* pRegions = nullptr;
if (pPresentRegions) {
pRegions = pPresentRegions->pRegions;
}
VkResult* pSCRslts = pPresentInfo->pResults;
_presentInfo.reserve(scCnt);
@ -630,6 +638,7 @@ MVKQueuePresentSurfaceSubmission::MVKQueuePresentSurfaceSubmission(MVKQueue* que
presentInfo.presentID = pPresentTimes[scIdx].presentID;
presentInfo.desiredPresentTime = pPresentTimes[scIdx].desiredPresentTime;
}
mvkSC->setLayerNeedsDisplay(pRegions ? &pRegions[scIdx] : nullptr);
_presentInfo.push_back(presentInfo);
VkResult scRslt = mvkSC->getSurfaceStatus();
if (pSCRslts) { pSCRslts[scIdx] = scRslt; }

View File

@ -99,6 +99,9 @@ public:
/** VK_GOOGLE_display_timing - returns past presentation times */
VkResult getPastPresentationTiming(uint32_t *pCount, VkPastPresentationTimingGOOGLE *pPresentationTimings);
/** Marks parts of the underlying CAMetalLayer as needing update. */
void setLayerNeedsDisplay(const VkPresentRegionKHR* pRegion);
void destroy() override;
#pragma mark Construction

View File

@ -582,6 +582,30 @@ void MVKSwapchain::recordPresentTime(const MVKImagePresentInfo& presentInfo, uin
_presentHistoryIndex = (_presentHistoryIndex + 1) % kMaxPresentationHistory;
}
void MVKSwapchain::setLayerNeedsDisplay(const VkPresentRegionKHR* pRegion) {
if (!pRegion || pRegion->rectangleCount == 0) {
[_mtlLayer setNeedsDisplay];
return;
}
for (uint32_t i = 0; i < pRegion->rectangleCount; ++i) {
CGRect cgRect = mvkCGRectFromVkRectLayerKHR(pRegion->pRectangles[i]);
#if MVK_MACOS
// VK_KHR_incremental_present specifies an upper-left origin, but macOS by default
// uses a lower-left origin.
cgRect.origin.y = _mtlLayer.bounds.size.height - cgRect.origin.y;
#endif
// We were given rectangles in pixels, but -[CALayer setNeedsDisplayInRect:] wants them
// in points, which is pixels / contentsScale.
CGFloat scaleFactor = _mtlLayer.contentsScale;
cgRect.origin.x /= scaleFactor;
cgRect.origin.y /= scaleFactor;
cgRect.size.width /= scaleFactor;
cgRect.size.height /= scaleFactor;
[_mtlLayer setNeedsDisplayInRect:cgRect];
}
}
// A retention loop exists between the swapchain and its images. The swapchain images
// retain the swapchain because they can be in flight when the app destroys the swapchain.
// Release the images now, when the app destroys the swapchain, so they will be destroyed when

View File

@ -68,6 +68,7 @@ MVK_EXTENSION(KHR_get_physical_device_properties2, KHR_GET_PHYSICAL_DEVICE_PR
MVK_EXTENSION(KHR_get_surface_capabilities2, KHR_GET_SURFACE_CAPABILITIES_2, INSTANCE, 10.11, 8.0)
MVK_EXTENSION(KHR_imageless_framebuffer, KHR_IMAGELESS_FRAMEBUFFER, DEVICE, 10.11, 8.0)
MVK_EXTENSION(KHR_image_format_list, KHR_IMAGE_FORMAT_LIST, DEVICE, 10.11, 8.0)
MVK_EXTENSION(KHR_incremental_present, KHR_INCREMENTAL_PRESENT, DEVICE, 10.11, 8.0)
MVK_EXTENSION(KHR_maintenance1, KHR_MAINTENANCE1, DEVICE, 10.11, 8.0)
MVK_EXTENSION(KHR_maintenance2, KHR_MAINTENANCE2, DEVICE, 10.11, 8.0)
MVK_EXTENSION(KHR_maintenance3, KHR_MAINTENANCE3, DEVICE, 10.11, 8.0)

View File

@ -780,10 +780,15 @@ MVK_PUBLIC_SYMBOL VkExtent2D mvkVkExtent2DFromCGSize(CGSize cgSize) {
}
MVK_PUBLIC_SYMBOL CGSize mvkCGSizeFromVkExtent2D(VkExtent2D vkExtent) {
CGSize cgSize;
cgSize.width = vkExtent.width;
cgSize.height = vkExtent.height;
return cgSize;
return CGSizeMake(vkExtent.width, vkExtent.height);
}
MVK_PUBLIC_SYMBOL CGPoint mvkCGPointFromVkOffset2D(VkOffset2D vkOffset) {
return CGPointMake(vkOffset.x, vkOffset.y);
}
MVK_PUBLIC_SYMBOL CGRect mvkCGRectFromVkRectLayerKHR(VkRectLayerKHR vkRect) {
return { mvkCGPointFromVkOffset2D(vkRect.offset), mvkCGSizeFromVkExtent2D(vkRect.extent) };
}