Fix rounding error when checking if the drawableSize of a CAMetalLayer has changed.

- Add mvkGetNaturalExtent() to consolidate retrieving the natural rounded
  extent of the CAMetalDrawables that will be created by a CAMetalLayer,
  based on its drawableSize and contentsScale properties.
- Replace MVKSwapchain::_mtlLayerDrawableSize with _mtlLayerDrawableExtent.
- MVKSwapchain::hasOptimalSurface() compares last, actual,
  and natural rounded drawable extents of CAMetalLayer.
This commit is contained in:
Bill Hollings 2023-02-13 23:17:00 -05:00
parent eabede8cdf
commit fbf5159ec2
4 changed files with 37 additions and 21 deletions

View File

@ -1141,7 +1141,7 @@ VkResult MVKPhysicalDevice::getSurfaceCapabilities( const VkPhysicalDeviceSurfac
VkSurfaceCapabilitiesKHR& surfCaps = pSurfaceCapabilities->surfaceCapabilities;
surfCaps.minImageCount = _metalFeatures.minSwapchainImageCount;
surfCaps.maxImageCount = _metalFeatures.maxSwapchainImageCount;
surfCaps.currentExtent = mvkVkExtent2DFromCGSize(mtlLayer.naturalDrawableSizeMVK);
surfCaps.currentExtent = mvkGetNaturalExtent(mtlLayer);
surfCaps.minImageExtent = { 1, 1 };
surfCaps.maxImageExtent = { _properties.limits.maxImageDimension2D, _properties.limits.maxImageDimension2D };
surfCaps.maxImageArrayLayers = 1;
@ -1389,7 +1389,7 @@ VkResult MVKPhysicalDevice::getPresentRectangles(MVKSurface* surface,
*pRectCount = 1;
pRects[0].offset = { 0, 0 };
pRects[0].extent = mvkVkExtent2DFromCGSize(mtlLayer.naturalDrawableSizeMVK);
pRects[0].extent = mvkGetNaturalExtent(mtlLayer);
return VK_SUCCESS;
}

View File

@ -79,19 +79,8 @@ public:
/** Returns whether the parent surface is now lost and this swapchain must be recreated. */
bool getIsSurfaceLost() { return _surfaceLost; }
/**
* Returns whether this swapchain is optimally sized for the surface.
* It is if the app has specified deliberate swapchain scaling, or the CAMetalLayer
* drawableSize has not changed since the swapchain was created, and the CAMetalLayer
* will not need to be scaled when composited.
*/
bool hasOptimalSurface() {
if (_isDeliberatelyScaled) { return true; }
auto drawSize = _mtlLayer.drawableSize;
return (CGSizeEqualToSize(drawSize, _mtlLayerDrawableSize) &&
CGSizeEqualToSize(drawSize, _mtlLayer.naturalDrawableSizeMVK));
}
/** Returns whether this swapchain is optimally sized for the surface. */
bool hasOptimalSurface();
/** Returns the status of the surface. Surface loss takes precedence over sub-optimal errors. */
VkResult getSurfaceStatus() {
@ -145,7 +134,7 @@ protected:
std::mutex _presentHistoryLock;
std::mutex _layerLock;
uint64_t _lastFrameTime = 0;
CGSize _mtlLayerDrawableSize = {0.0, 0.0};
VkExtent2D _mtlLayerDrawableExtent = {0, 0};
uint32_t _currentPerfLogFrameCount = 0;
uint32_t _presentHistoryCount = 0;
uint32_t _presentHistoryIndex = 0;
@ -154,3 +143,17 @@ protected:
bool _isDeliberatelyScaled = false;
};
#pragma mark -
#pragma mark Support functions
/**
* Returns the natural extent of the CAMetalLayer.
*
* The natural extent is the size of the bounds property of the layer,
* multiplied by the contentsScale property of the layer, rounded
* to nearest integer using half-to-even rounding.
*/
static inline VkExtent2D mvkGetNaturalExtent(CAMetalLayer* mtlLayer) {
return mvkVkExtent2DFromCGSize(mtlLayer.naturalDrawableSizeMVK);
}

View File

@ -115,6 +115,18 @@ uint64_t MVKSwapchain::getNextAcquisitionID() { return ++_currentAcquisitionID;
void MVKSwapchain::releaseUndisplayedSurfaces() {}
// This swapchain is optimally sized for the surface if the app has specified deliberate
// swapchain scaling, or the CAMetalLayer drawableSize has not changed since the swapchain
// was created, and the CAMetalLayer will not need to be scaled when composited.
bool MVKSwapchain::hasOptimalSurface() {
if (_isDeliberatelyScaled) { return true; }
VkExtent2D drawExtent = mvkVkExtent2DFromCGSize(_mtlLayer.drawableSize);
return (mvkVkExtent2DsAreEqual(drawExtent, _mtlLayerDrawableExtent) &&
mvkVkExtent2DsAreEqual(drawExtent, mvkGetNaturalExtent(_mtlLayer)));
}
#pragma mark Rendering
// Called automatically when a swapchain image is about to be presented to the surface by the queue.
@ -339,9 +351,10 @@ void MVKSwapchain::initCAMetalLayer(const VkSwapchainCreateInfoKHR* pCreateInfo,
VK_IMAGE_USAGE_TRANSFER_DST_BIT |
VK_IMAGE_USAGE_SAMPLED_BIT |
VK_IMAGE_USAGE_STORAGE_BIT));
// Remember drawable size to later detect if it has changed under the covers.
_mtlLayerDrawableSize = mvkCGSizeFromVkExtent2D(pCreateInfo->imageExtent);
_mtlLayer.drawableSize = _mtlLayerDrawableSize;
// Remember the extent to later detect if it has changed under the covers,
// and set the drawable size of the CAMetalLayer from the extent.
_mtlLayerDrawableExtent = pCreateInfo->imageExtent;
_mtlLayer.drawableSize = mvkCGSizeFromVkExtent2D(_mtlLayerDrawableExtent);
if (pCreateInfo->compositeAlpha != VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) {
_mtlLayer.opaque = pCreateInfo->compositeAlpha == VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;

View File

@ -38,8 +38,8 @@
/**
* Returns the natural drawable size for this layer.
*
* The natural drawable size is the size of the bounds property of this layer,
* multiplied by the contentsScale property of this layer.
* The natural drawable size is the size of the bounds
* property multiplied by the contentsScale property.
*/
@property(nonatomic, readonly) CGSize naturalDrawableSizeMVK;