From fabad21405d22ff8d1c81531140fb2947db0064e Mon Sep 17 00:00:00 2001 From: Bill Hollings Date: Thu, 2 Feb 2023 23:00:37 -0500 Subject: [PATCH 1/6] Add support for VK_EXT_swapchain_maintenance1 and VK_EXT_surface_maintenance1. - Support querying scaling capabilities and present mode compatibilies when querying surface capabilities. - Rename MVKPresentTimingInfo to MVKImagePresentInfo and add present mode and fence to support dynamic present mode changes and fence signaling. - MVKPresentableSwapchainImage remove static functions from class declaration. - MVKSwapchain support releasing swapchain images on command. - MVKSwapchain support configuring with scaling and gravity info, apply it to CAMetalLayer.and do not return VK_SUBOPTIMAL_KHR if swapchain was configured with scaling info. - Rename MVKSwapchain::acquireNextImageKHR to acquireNextImage. - CAMetalLayer naturalDrawableSizeMVK compute precise drawable size. - CAMetalLayer remove obsolete and unused updatedDrawableSizeMVK method. - Rename MVKConfiguration::swapchainMagFilterUseNearest to swapchainMinMagFilterUseNearest to apply CAMetalLayer size filtering to both magnification and minification, and rename corresponding env var MVK_CONFIG_SWAPCHAIN_MAG_FILTER_USE_NEAREST to MVK_CONFIG_SWAPCHAIN_MIN_MAG_FILTER_USE_NEAREST. - Detect when size of surface has changed under the covers. - Change rounding of surface size provided by Metal from truncation to rounding-with-half-to-even. - Remove MVKLogSizeOf() logging macro as redundant to mvkPrintSizeOf() macro. --- Docs/MoltenVK_Runtime_UserGuide.md | 2 + Docs/Whats_New.md | 6 +- MoltenVK/MoltenVK/API/mvk_datatypes.h | 19 +-- MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h | 11 +- MoltenVK/MoltenVK/GPUObjects/MVKDevice.h | 9 +- MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm | 155 +++++++++++++++--- .../GPUObjects/MVKDeviceFeatureStructs.def | 1 + MoltenVK/MoltenVK/GPUObjects/MVKImage.h | 24 ++- MoltenVK/MoltenVK/GPUObjects/MVKImage.mm | 122 +++++++------- MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm | 2 +- MoltenVK/MoltenVK/GPUObjects/MVKQueue.h | 2 +- MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm | 50 ++++-- MoltenVK/MoltenVK/GPUObjects/MVKSurface.mm | 2 - MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h | 67 +++++--- MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm | 126 +++++++++++--- MoltenVK/MoltenVK/Layers/MVKExtensions.def | 2 + MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.h | 13 +- MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.m | 14 +- MoltenVK/MoltenVK/Utility/MVKEnvironment.cpp | 3 +- MoltenVK/MoltenVK/Utility/MVKEnvironment.h | 7 +- MoltenVK/MoltenVK/Utility/MVKFoundation.h | 8 +- MoltenVK/MoltenVK/Utility/MVKLogging.h | 3 - MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm | 18 ++ MoltenVK/MoltenVK/Vulkan/vulkan.mm | 32 ++-- 24 files changed, 474 insertions(+), 224 deletions(-) diff --git a/Docs/MoltenVK_Runtime_UserGuide.md b/Docs/MoltenVK_Runtime_UserGuide.md index ae08b5da..805671a3 100644 --- a/Docs/MoltenVK_Runtime_UserGuide.md +++ b/Docs/MoltenVK_Runtime_UserGuide.md @@ -338,7 +338,9 @@ In addition to core *Vulkan* functionality, **MoltenVK** also supports the foll - `VK_EXT_shader_stencil_export` *(requires Mac GPU family 2 or iOS GPU family 5)* - `VK_EXT_shader_viewport_index_layer` - `VK_EXT_subgroup_size_control` *(requires Metal 2.1 on Mac or Metal 2.2 and Apple family 4 on iOS)* +- `VK_EXT_surface_maintenance1` - `VK_EXT_swapchain_colorspace` +- `VK_EXT_swapchain_maintenance1` - `VK_EXT_vertex_attribute_divisor` - `VK_EXT_texel_buffer_alignment` *(requires Metal 2.0)* - `VK_EXT_texture_compression_astc_hdr` *(iOS and macOS, requires family 6 (A13) or better Apple GPU)* diff --git a/Docs/Whats_New.md b/Docs/Whats_New.md index 21b0d2cd..8ac6cf7b 100644 --- a/Docs/Whats_New.md +++ b/Docs/Whats_New.md @@ -18,9 +18,13 @@ MoltenVK 1.2.3 Released TBA +- Add support for extensions: + - `VK_EXT_swapchain_maintenance1` + - `VK_EXT_surface_maintenance1` - Fix issue where extension `VK_KHR_fragment_shader_barycentric` was sometimes incorrectly disabled due to a Metal driver bug. - +- Detect when size of surface has changed under the covers. +- Change rounding of surface size provided by Metal from truncation to rounding-with-half-to-even. MoltenVK 1.2.2 diff --git a/MoltenVK/MoltenVK/API/mvk_datatypes.h b/MoltenVK/MoltenVK/API/mvk_datatypes.h index 27017bc1..76470233 100644 --- a/MoltenVK/MoltenVK/API/mvk_datatypes.h +++ b/MoltenVK/MoltenVK/API/mvk_datatypes.h @@ -416,21 +416,14 @@ MTLBarrierScope mvkMTLBarrierScopeFromVkAccessFlags(VkAccessFlags vkAccess); #pragma mark - #pragma mark Geometry conversions -/** Returns a VkExtent2D that corresponds to the specified CGSize. */ -static inline VkExtent2D mvkVkExtent2DFromCGSize(CGSize cgSize) { - VkExtent2D vkExt; - vkExt.width = cgSize.width; - vkExt.height = cgSize.height; - return vkExt; -} +/** + * Returns a VkExtent2D that corresponds to the specified CGSize. + * Rounds to nearest integer using half-to-even rounding. + */ +VkExtent2D mvkVkExtent2DFromCGSize(CGSize cgSize); /** Returns a CGSize that corresponds to the specified VkExtent2D. */ -static inline CGSize mvkCGSizeFromVkExtent2D(VkExtent2D vkExtent) { - CGSize cgSize; - cgSize.width = vkExtent.width; - cgSize.height = vkExtent.height; - return cgSize; -} +CGSize mvkCGSizeFromVkExtent2D(VkExtent2D vkExtent); /** Returns a Metal MTLOrigin constructed from a VkOffset3D. */ static inline MTLOrigin mvkMTLOriginFromVkOffset3D(VkOffset3D vkOffset) { diff --git a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h index e78c00fc..f0759ad4 100644 --- a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h +++ b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h @@ -322,22 +322,23 @@ typedef struct { VkBool32 presentWithCommandBuffer; /** - * If enabled, swapchain images will use simple Nearest sampling when magnifying the - * swapchain image to fit a physical display surface. If disabled, swapchain images will + * If enabled, swapchain images will use simple Nearest sampling when minifying or magnifying + * the swapchain image to fit a physical display surface. If disabled, swapchain images will * use Linear sampling when magnifying the swapchain image to fit a physical display surface. * Enabling this setting avoids smearing effects when swapchain images are simple interger * multiples of display pixels (eg- macOS Retina, and typical of graphics apps and games), * but may cause aliasing effects when using non-integer display scaling. * - * The value of this parameter may be changed before creating a VkSwapchain, + * The value of this parameter must be changed before creating a VkSwapchain, * for the change to take effect. * * The initial value or this parameter is set by the - * MVK_CONFIG_SWAPCHAIN_MAG_FILTER_USE_NEAREST + * MVK_CONFIG_SWAPCHAIN_MIN_MAG_FILTER_USE_NEAREST * runtime environment variable or MoltenVK compile-time build setting. * If neither is set, the value of this parameter defaults to true. */ - VkBool32 swapchainMagFilterUseNearest; + VkBool32 swapchainMinMagFilterUseNearest; +#define swapchainMagFilterUseNearest swapchainMinMagFilterUseNearest /** * The maximum amount of time, in nanoseconds, to wait for a Metal library, function, or diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h index b6617405..a37d91ea 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h +++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h @@ -181,8 +181,12 @@ public: */ VkResult getSurfaceSupport(uint32_t queueFamilyIndex, MVKSurface* surface, VkBool32* pSupported); - /** Returns the capabilities of the specified surface. */ - VkResult getSurfaceCapabilities(MVKSurface* surface, VkSurfaceCapabilitiesKHR* pSurfaceCapabilities); + /** Returns the capabilities of the surface. */ + VkResult getSurfaceCapabilities(VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR* pSurfaceCapabilities); + + /** Returns the capabilities of the surface. */ + VkResult getSurfaceCapabilities(const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, + VkSurfaceCapabilities2KHR* pSurfaceCapabilities); /** * Returns the pixel formats supported by the surface. @@ -540,6 +544,7 @@ public: MVKPresentableSwapchainImage* createPresentableSwapchainImage(const VkImageCreateInfo* pCreateInfo, MVKSwapchain* swapchain, uint32_t swapchainIndex, + bool deferImgMemAlloc, const VkAllocationCallbacks* pAllocator); void destroyPresentableSwapchainImage(MVKPresentableSwapchainImage* mvkImg, const VkAllocationCallbacks* pAllocator); diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm index 4ede8a47..86cb1dd3 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm @@ -385,6 +385,11 @@ void MVKPhysicalDevice::getFeatures(VkPhysicalDeviceFeatures2* features) { robustness2Features->nullDescriptor = false; break; } + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_EXT: { + auto* swapchainMaintenance1Features = (VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT*)next; + swapchainMaintenance1Features->swapchainMaintenance1 = true; + break; + } case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXEL_BUFFER_ALIGNMENT_FEATURES_EXT: { auto* texelBuffAlignFeatures = (VkPhysicalDeviceTexelBufferAlignmentFeaturesEXT*)next; texelBuffAlignFeatures->texelBufferAlignment = _metalFeatures.texelBuffers && [_mtlDevice respondsToSelector: @selector(minimumLinearTextureAlignmentForPixelFormat:)]; @@ -1075,32 +1080,139 @@ VkResult MVKPhysicalDevice::getSurfaceSupport(uint32_t queueFamilyIndex, return *pSupported ? VK_SUCCESS : surface->getConfigurationResult(); } -VkResult MVKPhysicalDevice::getSurfaceCapabilities(MVKSurface* surface, +VkResult MVKPhysicalDevice::getSurfaceCapabilities(VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR* pSurfaceCapabilities) { + VkPhysicalDeviceSurfaceInfo2KHR surfaceInfo; + surfaceInfo.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR; + surfaceInfo.pNext = nullptr; + surfaceInfo.surface = surface; - // The layer underlying the surface view must be a CAMetalLayer. + VkSurfaceCapabilities2KHR surfaceCaps; + surfaceCaps.sType = VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR; + surfaceCaps.pNext = nullptr; + surfaceCaps.surfaceCapabilities = *pSurfaceCapabilities; + + VkResult rslt = getSurfaceCapabilities(&surfaceInfo, &surfaceCaps); + + *pSurfaceCapabilities = surfaceCaps.surfaceCapabilities; + + return rslt; +} + +VkResult MVKPhysicalDevice::getSurfaceCapabilities( const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, + VkSurfaceCapabilities2KHR* pSurfaceCapabilities) { + + // Retrieve the present mode if it is supplied in this query. + VkPresentModeKHR presentMode = VK_PRESENT_MODE_MAX_ENUM_KHR; + for (auto* next = (const VkBaseInStructure*)pSurfaceInfo->pNext; next; next = next->pNext) { + switch (next->sType) { + case VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_EXT: { + presentMode = ((VkSurfacePresentModeEXT*)next)->presentMode; + break; + } + default: + break; + } + } + + // Retrieve the scaling and present mode compatibility structs if they are supplied in this query. + VkSurfacePresentScalingCapabilitiesEXT* pScalingCaps = nullptr; + VkSurfacePresentModeCompatibilityEXT* pCompatibility = nullptr; + for (auto* next = (VkBaseOutStructure*)pSurfaceCapabilities->pNext; next; next = next->pNext) { + switch (next->sType) { + case VK_STRUCTURE_TYPE_SURFACE_PRESENT_SCALING_CAPABILITIES_EXT: { + pScalingCaps = (VkSurfacePresentScalingCapabilitiesEXT*)next; + break; + } + case VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_COMPATIBILITY_EXT: { + pCompatibility = (VkSurfacePresentModeCompatibilityEXT*)next; + break; + } + default: + break; + } + } + + // The CAlayer underlying the surface must be a CAMetalLayer. + MVKSurface* surface = (MVKSurface*)pSurfaceInfo->surface; CAMetalLayer* mtlLayer = surface->getCAMetalLayer(); if ( !mtlLayer ) { return surface->getConfigurationResult(); } - VkExtent2D surfExtnt = mvkVkExtent2DFromCGSize(mtlLayer.naturalDrawableSizeMVK); + VkSurfaceCapabilitiesKHR& surfCaps = pSurfaceCapabilities->surfaceCapabilities; + surfCaps.minImageCount = _metalFeatures.minSwapchainImageCount; + surfCaps.maxImageCount = _metalFeatures.maxSwapchainImageCount; + surfCaps.currentExtent = mvkVkExtent2DFromCGSize(mtlLayer.naturalDrawableSizeMVK); + surfCaps.minImageExtent = { 1, 1 }; + surfCaps.maxImageExtent = { _properties.limits.maxImageDimension2D, _properties.limits.maxImageDimension2D }; + surfCaps.maxImageArrayLayers = 1; + surfCaps.supportedTransforms = (VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR); + surfCaps.currentTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + surfCaps.supportedCompositeAlpha = (VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR | + VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR | + VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR); + surfCaps.supportedUsageFlags = (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | + VK_IMAGE_USAGE_STORAGE_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_SAMPLED_BIT); - pSurfaceCapabilities->minImageCount = _metalFeatures.minSwapchainImageCount; - pSurfaceCapabilities->maxImageCount = _metalFeatures.maxSwapchainImageCount; + // Swapchain-to-surface scaling capabilities. + if (pScalingCaps) { + pScalingCaps->supportedPresentScaling = (VK_PRESENT_SCALING_ONE_TO_ONE_BIT_EXT | + VK_PRESENT_SCALING_ASPECT_RATIO_STRETCH_BIT_EXT | + VK_PRESENT_SCALING_STRETCH_BIT_EXT); + pScalingCaps->supportedPresentGravityX = (VK_PRESENT_GRAVITY_MIN_BIT_EXT | + VK_PRESENT_GRAVITY_MAX_BIT_EXT | + VK_PRESENT_GRAVITY_CENTERED_BIT_EXT); + pScalingCaps->supportedPresentGravityY = (VK_PRESENT_GRAVITY_MIN_BIT_EXT | + VK_PRESENT_GRAVITY_MAX_BIT_EXT | + VK_PRESENT_GRAVITY_CENTERED_BIT_EXT); + pScalingCaps->minScaledImageExtent = surfCaps.minImageExtent; + pScalingCaps->maxScaledImageExtent = surfCaps.maxImageExtent; + } + + + // Per spec, always include the queried present mode in returned compatibility results. + MVKSmallVector compatiblePresentModes; + if (presentMode != VK_PRESENT_MODE_MAX_ENUM_KHR) { compatiblePresentModes.push_back(presentMode); } + + // Customize results based on the provided present mode. + switch (presentMode) { + case VK_PRESENT_MODE_FIFO_KHR: + // This could be dodgy, because Metal may not match the Vulkan spec's tight + // requirements for transitioning from FIFO to IMMEDIATE, because the change to + // IMMEDIATE may occur while some FIFO items are still on the GPU queue. + if (_metalFeatures.presentModeImmediate) { + compatiblePresentModes.push_back(VK_PRESENT_MODE_IMMEDIATE_KHR); + } + break; + case VK_PRESENT_MODE_IMMEDIATE_KHR: + compatiblePresentModes.push_back(VK_PRESENT_MODE_FIFO_KHR); + surfCaps.minImageCount = surfCaps.maxImageCount; // Recommend always using max count to avoid visual tearing. + break; + case VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR: + case VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR: + // Although these are not advertised as supported, per spec, counts must be 1. + surfCaps.minImageCount = 1; + surfCaps.maxImageCount = 1; + break; + + default: + break; + } + + // If compatible present modes are requested, return them, otherwise return the count of them. + if (pCompatibility) { + if (pCompatibility->pPresentModes) { + pCompatibility->presentModeCount = min(pCompatibility->presentModeCount, (uint32_t)compatiblePresentModes.size()); + for (uint32_t pmIdx = 0; pmIdx < pCompatibility->presentModeCount; pmIdx++) { + pCompatibility->pPresentModes[pmIdx] = compatiblePresentModes[pmIdx]; + } + } else { + pCompatibility->presentModeCount = (uint32_t)compatiblePresentModes.size(); + } + } - pSurfaceCapabilities->currentExtent = surfExtnt; - pSurfaceCapabilities->minImageExtent = { 1, 1 }; - pSurfaceCapabilities->maxImageExtent = { _properties.limits.maxImageDimension2D, _properties.limits.maxImageDimension2D }; - pSurfaceCapabilities->maxImageArrayLayers = 1; - pSurfaceCapabilities->supportedTransforms = (VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR); - pSurfaceCapabilities->currentTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; - pSurfaceCapabilities->supportedCompositeAlpha = (VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR | - VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR | - VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR); - pSurfaceCapabilities->supportedUsageFlags = (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | - VK_IMAGE_USAGE_STORAGE_BIT | - VK_IMAGE_USAGE_TRANSFER_SRC_BIT | - VK_IMAGE_USAGE_TRANSFER_DST_BIT | - VK_IMAGE_USAGE_SAMPLED_BIT); return VK_SUCCESS; } @@ -3458,8 +3570,11 @@ void MVKDevice::destroySwapchain(MVKSwapchain* mvkSwpChn, MVKPresentableSwapchainImage* MVKDevice::createPresentableSwapchainImage(const VkImageCreateInfo* pCreateInfo, MVKSwapchain* swapchain, uint32_t swapchainIndex, + bool deferImgMemAlloc, const VkAllocationCallbacks* pAllocator) { - MVKPresentableSwapchainImage* mvkImg = new MVKPresentableSwapchainImage(this, pCreateInfo, swapchain, swapchainIndex); + MVKPresentableSwapchainImage* mvkImg = new MVKPresentableSwapchainImage(this, pCreateInfo, + swapchain, swapchainIndex, + deferImgMemAlloc); for (auto& memoryBinding : mvkImg->_memoryBindings) { addResource(memoryBinding); } diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDeviceFeatureStructs.def b/MoltenVK/MoltenVK/GPUObjects/MVKDeviceFeatureStructs.def index 35dc9f08..c6de213e 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKDeviceFeatureStructs.def +++ b/MoltenVK/MoltenVK/GPUObjects/MVKDeviceFeatureStructs.def @@ -64,6 +64,7 @@ MVK_DEVICE_FEATURE_EXTN(FragmentShaderBarycentric, FRAGMENT_SHADER_BARYCENTRIC, MVK_DEVICE_FEATURE_EXTN(PortabilitySubset, PORTABILITY_SUBSET, KHR, 15) MVK_DEVICE_FEATURE_EXTN(FragmentShaderInterlock, FRAGMENT_SHADER_INTERLOCK, EXT, 3) MVK_DEVICE_FEATURE_EXTN(Robustness2, ROBUSTNESS_2, EXT, 3) +MVK_DEVICE_FEATURE_EXTN(SwapchainMaintenance1, SWAPCHAIN_MAINTENANCE_1, EXT, 1) MVK_DEVICE_FEATURE_EXTN(TexelBufferAlignment, TEXEL_BUFFER_ALIGNMENT, EXT, 1) MVK_DEVICE_FEATURE_EXTN(VertexAttributeDivisor, VERTEX_ATTRIBUTE_DIVISOR, EXT, 2) MVK_DEVICE_FEATURE_EXTN(ShaderIntegerFunctions2, SHADER_INTEGER_FUNCTIONS_2, INTEL, 1) diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h index 3beea0f4..b6167533 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h +++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h @@ -423,13 +423,15 @@ typedef struct MVKSwapchainImageAvailability { bool operator< (const MVKSwapchainImageAvailability& rhs) const; } MVKSwapchainImageAvailability; -/** VK_GOOGLE_display_timing extension info */ +/** Presentation info. */ typedef struct { MVKPresentableSwapchainImage* presentableImage; - bool hasPresentTime; // Keep track of whether presentation included VK_GOOGLE_display_timing - uint32_t presentID; // VK_GOOGLE_display_timing presentID + MVKFence* fence; // VK_EXT_swapchain_maintenance1 fence signaled when resources can be destroyed uint64_t desiredPresentTime; // VK_GOOGLE_display_timing desired presentation time in nanoseconds -} MVKPresentTimingInfo; + uint32_t presentID; // VK_GOOGLE_display_timing presentID + VkPresentModeKHR presentMode; // VK_EXT_swapchain_maintenance1 present mode specialization + bool hasPresentTime; // Keep track of whether presentation included VK_GOOGLE_display_timing +} MVKImagePresentInfo; /** Tracks a semaphore and fence for later signaling. */ struct MVKSwapchainSignaler { @@ -447,8 +449,7 @@ public: #pragma mark Metal /** Presents the contained drawable to the OS. */ - void presentCAMetalDrawable(id mtlCmdBuff, - MVKPresentTimingInfo presentTimingInfo); + void presentCAMetalDrawable(id mtlCmdBuff, MVKImagePresentInfo presentInfo); #pragma mark Construction @@ -456,7 +457,8 @@ public: MVKPresentableSwapchainImage(MVKDevice* device, const VkImageCreateInfo* pCreateInfo, MVKSwapchain* swapchain, - uint32_t swapchainIndex); + uint32_t swapchainIndex, + bool deferImgMemAlloc); ~MVKPresentableSwapchainImage() override; @@ -464,16 +466,12 @@ protected: friend MVKSwapchain; id getCAMetalDrawable() override; - void addPresentedHandler(id mtlDrawable, MVKPresentTimingInfo presentTimingInfo); + void addPresentedHandler(id mtlDrawable, MVKImagePresentInfo presentInfo); void releaseMetalDrawable(); MVKSwapchainImageAvailability getAvailability(); void makeAvailable(const MVKSwapchainSignaler& signaler); + void makeAvailable(); void acquireAndSignalWhenAvailable(MVKSemaphore* semaphore, MVKFence* fence); - void signal(const MVKSwapchainSignaler& signaler, id mtlCmdBuff); - void signalPresentationSemaphore(const MVKSwapchainSignaler& signaler, id mtlCmdBuff); - static void markAsTracked(const MVKSwapchainSignaler& signaler); - static void unmarkAsTracked(const MVKSwapchainSignaler& signaler); - void untrackAllSignalers(); void renderWatermark(id mtlCmdBuff); id _mtlDrawable; diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm index 5c00ea33..7cded7b9 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm @@ -1218,13 +1218,30 @@ MVKSwapchainImageAvailability MVKPresentableSwapchainImage::getAvailability() { 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 MVKPresentableSwapchainImage::makeAvailable(const MVKSwapchainSignaler& signaler) { - lock_guard lock(_availabilityLock); +// If present, signal the semaphore for the first waiter for the given image. +static void signalPresentationSemaphore(const MVKSwapchainSignaler& signaler, id mtlCmdBuff) { + if (signaler.semaphore) { signaler.semaphore->encodeDeferredSignal(mtlCmdBuff, signaler.semaphoreSignalToken); } +} - // Signal the semaphore and fence, and let them know they are no longer being tracked. +// Signal either or both of the semaphore and fence in the specified tracker pair. +static void signal(const MVKSwapchainSignaler& signaler, id mtlCmdBuff) { + if (signaler.semaphore) { signaler.semaphore->encodeDeferredSignal(mtlCmdBuff, signaler.semaphoreSignalToken); } + if (signaler.fence) { signaler.fence->signal(); } +} + +// Tell the semaphore and fence that they are being tracked for future signaling. +static void markAsTracked(const MVKSwapchainSignaler& signaler) { + if (signaler.semaphore) { signaler.semaphore->retain(); } + if (signaler.fence) { signaler.fence->retain(); } +} + +// Tell the semaphore and fence that they are no longer being tracked for future signaling. +static void unmarkAsTracked(const MVKSwapchainSignaler& signaler) { + if (signaler.semaphore) { signaler.semaphore->release(); } + if (signaler.fence) { signaler.fence->release(); } +} + +static void signalAndUnmarkAsTracked(const MVKSwapchainSignaler& signaler) { signal(signaler, nil); unmarkAsTracked(signaler); } @@ -1263,45 +1280,6 @@ void MVKPresentableSwapchainImage::acquireAndSignalWhenAvailable(MVKSemaphore* s markAsTracked(signaler); } -// If present, signal the semaphore for the first waiter for the given image. -void MVKPresentableSwapchainImage::signalPresentationSemaphore(const MVKSwapchainSignaler& signaler, id mtlCmdBuff) { - MVKSemaphore* mvkSem = signaler.semaphore; - if (mvkSem) { mvkSem->encodeDeferredSignal(mtlCmdBuff, signaler.semaphoreSignalToken); } -} - -// Signal either or both of the semaphore and fence in the specified tracker pair. -void MVKPresentableSwapchainImage::signal(const MVKSwapchainSignaler& signaler, id mtlCmdBuff) { - if (signaler.semaphore) { signaler.semaphore->encodeDeferredSignal(mtlCmdBuff, signaler.semaphoreSignalToken); } - if (signaler.fence) { signaler.fence->signal(); } -} - -// Tell the semaphore and fence that they are being tracked for future signaling. -void MVKPresentableSwapchainImage::markAsTracked(const MVKSwapchainSignaler& signaler) { - if (signaler.semaphore) { signaler.semaphore->retain(); } - if (signaler.fence) { signaler.fence->retain(); } -} - -// Tell the semaphore and fence that they are no longer being tracked for future signaling. -void MVKPresentableSwapchainImage::unmarkAsTracked(const MVKSwapchainSignaler& signaler) { - if (signaler.semaphore) { signaler.semaphore->release(); } - if (signaler.fence) { signaler.fence->release(); } -} - -// Untrack any signalers that are still tracking, releasing the fences and semaphores. -void MVKPresentableSwapchainImage::untrackAllSignalers() { - lock_guard lock(_availabilityLock); - - if ( !_availability.isAvailable ) { - unmarkAsTracked(_preSignaler); - for (auto& sig : _availabilitySignalers) { - unmarkAsTracked(sig); - } - } -} - - -#pragma mark Metal - id MVKPresentableSwapchainImage::getCAMetalDrawable() { while ( !_mtlDrawable ) { @autoreleasepool { // Reclaim auto-released drawable object before end of loop @@ -1318,8 +1296,7 @@ id MVKPresentableSwapchainImage::getCAMetalDrawable() { // Present the drawable and make myself available only once the command buffer has completed. void MVKPresentableSwapchainImage::presentCAMetalDrawable(id mtlCmdBuff, - MVKPresentTimingInfo presentTimingInfo) { - + MVKImagePresentInfo presentInfo) { lock_guard lock(_availabilityLock); _swapchain->willPresentSurface(getMTLTexture(0), mtlCmdBuff); @@ -1330,10 +1307,15 @@ void MVKPresentableSwapchainImage::presentCAMetalDrawable(id m // Attach present handler before presenting to avoid race condition. id mtlDrwbl = getCAMetalDrawable(); [mtlCmdBuff addScheduledHandler: ^(id mcb) { - if (presentTimingInfo.hasPresentTime) { + // 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. + if (presentInfo.presentMode != VK_PRESENT_MODE_MAX_ENUM_KHR) { + mtlDrwbl.layer.displaySyncEnabledMVK = (presentInfo.presentMode != VK_PRESENT_MODE_IMMEDIATE_KHR); + } + if (presentInfo.hasPresentTime) { // Convert from nsecs to seconds for Metal - addPresentedHandler(mtlDrwbl, presentTimingInfo); - [mtlDrwbl presentAtTime: (double)presentTimingInfo.desiredPresentTime * 1.0e-9]; + addPresentedHandler(mtlDrwbl, presentInfo); + [mtlDrwbl presentAtTime: (double)presentInfo.desiredPresentTime * 1.0e-9]; } else { [mtlDrwbl present]; } @@ -1349,7 +1331,6 @@ void MVKPresentableSwapchainImage::presentCAMetalDrawable(id m // 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. signaler = _preSignaler; - // Save the command buffer in case this image is acquired before presentation is finished. } else { // If this image is not yet available, extract and signal the first semaphore and fence. auto sigIter = _availabilitySignalers.begin(); @@ -1365,13 +1346,14 @@ void MVKPresentableSwapchainImage::presentCAMetalDrawable(id m [mtlDrwbl release]; makeAvailable(signaler); release(); + if (presentInfo.fence) { presentInfo.fence->signal(); } }]; signalPresentationSemaphore(signaler, mtlCmdBuff); } void MVKPresentableSwapchainImage::addPresentedHandler(id mtlDrawable, - MVKPresentTimingInfo presentTimingInfo) { + MVKImagePresentInfo presentInfo) { #if !MVK_OS_SIMULATOR if ([mtlDrawable respondsToSelector: @selector(addPresentedHandler:)]) { retain(); // Ensure this image is not destroyed while awaiting presentation @@ -1379,7 +1361,7 @@ void MVKPresentableSwapchainImage::addPresentedHandler(id mtlDr // 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 cblock(_swapchainLock); - if (_swapchain) { _swapchain->recordPresentTime(presentTimingInfo, drawable.presentedTime * 1.0e9); } + if (_swapchain) { _swapchain->recordPresentTime(presentInfo, drawable.presentedTime * 1.0e9); } release(); }]; return; @@ -1392,7 +1374,7 @@ void MVKPresentableSwapchainImage::addPresentedHandler(id mtlDr // 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 lock(_swapchainLock); - if (_swapchain) {_swapchain->recordPresentTime(presentTimingInfo); } + if (_swapchain) {_swapchain->recordPresentTime(presentInfo); } } // Resets the MTLTexture and CAMetalDrawable underlying this image. @@ -1404,13 +1386,39 @@ void MVKPresentableSwapchainImage::releaseMetalDrawable() { _mtlDrawable = nil; } +// 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 MVKPresentableSwapchainImage::makeAvailable(const MVKSwapchainSignaler& signaler) { + lock_guard lock(_availabilityLock); + + signalAndUnmarkAsTracked(signaler); +} + +// Signal, untrack, and release any signalers that are tracking. +void MVKPresentableSwapchainImage::makeAvailable() { + lock_guard lock(_availabilityLock); + + if ( !_availability.isAvailable ) { + signalAndUnmarkAsTracked(_preSignaler); + for (auto& sig : _availabilitySignalers) { + signalAndUnmarkAsTracked(sig); + } + _availabilitySignalers.clear(); + _availability.isAvailable = true; + } +} + #pragma mark Construction +// The deferImgMemAlloc parameter is ignored, because image memory allocation is provided by a MTLDrawable, +// which is retrieved lazily, and hence is already as deferred (or as deferred as we can make it). MVKPresentableSwapchainImage::MVKPresentableSwapchainImage(MVKDevice* device, const VkImageCreateInfo* pCreateInfo, MVKSwapchain* swapchain, - uint32_t swapchainIndex) : + uint32_t swapchainIndex, + bool deferImgMemAlloc) : MVKSwapchainImage(device, pCreateInfo, swapchain, swapchainIndex) { _mtlDrawable = nil; @@ -1421,10 +1429,10 @@ MVKPresentableSwapchainImage::MVKPresentableSwapchainImage(MVKDevice* device, } // Unsignaled signalers will exist if this image is acquired more than it is presented. -// Ensure they are 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() { releaseMetalDrawable(); - untrackAllSignalers(); + makeAvailable(); } diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm b/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm index 0036bd15..dc953db5 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm @@ -729,9 +729,9 @@ void MVKInstance::initProcAddrs() { ADD_DVC_EXT_ENTRY_POINT(vkSetPrivateDataEXT, EXT_PRIVATE_DATA); ADD_DVC_EXT_ENTRY_POINT(vkGetPhysicalDeviceMultisamplePropertiesEXT, EXT_SAMPLE_LOCATIONS); ADD_DVC_EXT_ENTRY_POINT(vkCmdSetSampleLocationsEXT, EXT_SAMPLE_LOCATIONS); + ADD_DVC_EXT_ENTRY_POINT(vkReleaseSwapchainImagesEXT, EXT_SWAPCHAIN_MAINTENANCE_1); ADD_DVC_EXT_ENTRY_POINT(vkGetRefreshCycleDurationGOOGLE, GOOGLE_DISPLAY_TIMING); ADD_DVC_EXT_ENTRY_POINT(vkGetPastPresentationTimingGOOGLE, GOOGLE_DISPLAY_TIMING); - } void MVKInstance::logVersions() { diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.h b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.h index 48c69bc9..54a68595 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.h +++ b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.h @@ -272,6 +272,6 @@ public: protected: void stopAutoGPUCapture(); - MVKSmallVector _presentInfo; + MVKSmallVector _presentInfo; }; diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm index ea6bafdf..33c3246a 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm @@ -538,8 +538,7 @@ void MVKQueuePresentSurfaceSubmission::execute() { [mtlCmdBuff enqueue]; for (auto& ws : _waitSemaphores) { ws.first->encodeWait(mtlCmdBuff, 0); } for (int i = 0; i < _presentInfo.size(); i++ ) { - MVKPresentableSwapchainImage *img = _presentInfo[i].presentableImage; - img->presentCAMetalDrawable(mtlCmdBuff, _presentInfo[i]); + _presentInfo[i].presentableImage->presentCAMetalDrawable(mtlCmdBuff, _presentInfo[i]); } for (auto& ws : _waitSemaphores) { ws.first->encodeWait(nil, 0); } [mtlCmdBuff commit]; @@ -564,13 +563,19 @@ MVKQueuePresentSurfaceSubmission::MVKQueuePresentSurfaceSubmission(MVKQueue* que const VkPresentInfoKHR* pPresentInfo) : MVKQueueSubmission(queue, pPresentInfo->waitSemaphoreCount, pPresentInfo->pWaitSemaphores) { - const VkPresentTimesInfoGOOGLE *pPresentTimesInfoGOOGLE = nullptr; - for ( const auto *next = ( VkBaseInStructure* ) pPresentInfo->pNext; next; next = next->pNext ) - { - switch ( next->sType ) - { + const VkPresentTimesInfoGOOGLE* pPresentTimesInfo = nullptr; + const VkSwapchainPresentFenceInfoEXT* pPresentFenceInfo = nullptr; + const VkSwapchainPresentModeInfoEXT* pPresentModeInfo = nullptr; + for (auto* next = (const VkBaseInStructure*)pPresentInfo->pNext; next; next = next->pNext) { + switch (next->sType) { + case VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_FENCE_INFO_EXT: + pPresentFenceInfo = (const VkSwapchainPresentFenceInfoEXT*) next; + break; + case VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODE_INFO_EXT: + pPresentModeInfo = (const VkSwapchainPresentModeInfoEXT*) next; + break; case VK_STRUCTURE_TYPE_PRESENT_TIMES_INFO_GOOGLE: - pPresentTimesInfoGOOGLE = ( const VkPresentTimesInfoGOOGLE * ) next; + pPresentTimesInfo = (const VkPresentTimesInfoGOOGLE*) next; break; default: break; @@ -579,21 +584,34 @@ MVKQueuePresentSurfaceSubmission::MVKQueuePresentSurfaceSubmission(MVKQueue* que // Populate the array of swapchain images, testing each one for status uint32_t scCnt = pPresentInfo->swapchainCount; - const VkPresentTimeGOOGLE *pPresentTimesGOOGLE = nullptr; - if ( pPresentTimesInfoGOOGLE && pPresentTimesInfoGOOGLE->pTimes ) { - pPresentTimesGOOGLE = pPresentTimesInfoGOOGLE->pTimes; - MVKAssert( pPresentTimesInfoGOOGLE->swapchainCount == pPresentInfo->swapchainCount, "VkPresentTimesInfoGOOGLE swapchainCount must match VkPresentInfo swapchainCount" ); + const VkPresentTimeGOOGLE* pPresentTimes = nullptr; + if (pPresentTimesInfo && pPresentTimesInfo->pTimes) { + pPresentTimes = pPresentTimesInfo->pTimes; + MVKAssert(pPresentTimesInfo->swapchainCount == scCnt, "VkPresentTimesInfoGOOGLE swapchainCount must match VkPresentInfo swapchainCount."); } + const VkPresentModeKHR* pPresentModes = nullptr; + if (pPresentModeInfo) { + pPresentModes = pPresentModeInfo->pPresentModes; + MVKAssert(pPresentModeInfo->swapchainCount == scCnt, "VkSwapchainPresentModeInfoEXT swapchainCount must match VkPresentInfo swapchainCount."); + } + const VkFence* pFences = nullptr; + if (pPresentFenceInfo) { + pFences = pPresentFenceInfo->pFences; + MVKAssert(pPresentFenceInfo->swapchainCount == scCnt, "VkSwapchainPresentFenceInfoEXT swapchainCount must match VkPresentInfo swapchainCount."); + } + VkResult* pSCRslts = pPresentInfo->pResults; _presentInfo.reserve(scCnt); for (uint32_t scIdx = 0; scIdx < scCnt; scIdx++) { MVKSwapchain* mvkSC = (MVKSwapchain*)pPresentInfo->pSwapchains[scIdx]; - MVKPresentTimingInfo presentInfo = {}; + MVKImagePresentInfo presentInfo = {}; presentInfo.presentableImage = mvkSC->getPresentableImage(pPresentInfo->pImageIndices[scIdx]); - if ( pPresentTimesGOOGLE ) { + presentInfo.presentMode = pPresentModes ? pPresentModes[scIdx] : VK_PRESENT_MODE_MAX_ENUM_KHR; + presentInfo.fence = pFences ? (MVKFence*)pFences[scIdx] : nullptr; + if (pPresentTimes) { presentInfo.hasPresentTime = true; - presentInfo.presentID = pPresentTimesGOOGLE[scIdx].presentID; - presentInfo.desiredPresentTime = pPresentTimesGOOGLE[scIdx].desiredPresentTime; + presentInfo.presentID = pPresentTimes[scIdx].presentID; + presentInfo.desiredPresentTime = pPresentTimes[scIdx].desiredPresentTime; } else { presentInfo.hasPresentTime = false; } diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSurface.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSurface.mm index c3885da1..1309d73d 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKSurface.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKSurface.mm @@ -42,8 +42,6 @@ MVKSurface::MVKSurface(MVKInstance* mvkInstance, const Vk_PLATFORM_SurfaceCreateInfoMVK* pCreateInfo, const VkAllocationCallbacks* pAllocator) : _mvkInstance(mvkInstance) { -// MVKLogInfo("%s(): This function is obsolete. Consider using the vkCreateMetalSurfaceEXT() function from the VK_EXT_metal_surface extension instead.", STR(vkCreate_PLATFORM_SurfaceMVK)); - // Get the platform object contained in pView id obj = (id)pCreateInfo->pView; diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h index e13474bd..a82ef814 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h +++ b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h @@ -66,26 +66,38 @@ public: */ VkResult getImages(uint32_t* pCount, VkImage* pSwapchainImages); - /** Returns the index of the next swapchain image. */ - VkResult acquireNextImageKHR(uint64_t timeout, - VkSemaphore semaphore, - VkFence fence, - uint32_t deviceMask, - uint32_t* pImageIndex); + /** Returns the index of the next acquireable image. */ + VkResult acquireNextImage(uint64_t timeout, + VkSemaphore semaphore, + VkFence fence, + uint32_t deviceMask, + uint32_t* pImageIndex); + + /** Releases swapchain images. */ + VkResult releaseImages(const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo); /** Returns whether the parent surface is now lost and this swapchain must be recreated. */ bool getIsSurfaceLost() { return _surfaceLost; } - /** Returns whether the surface size or resolution scale has changed since the last time this function was called. */ - bool getHasSurfaceSizeChanged() { - return !CGSizeEqualToSize(_mtlLayer.naturalDrawableSizeMVK, _mtlLayer.drawableSize); + /** + * 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 the status of the surface. Surface loss takes precedence over out-of-date errors. */ + /** Returns the status of the surface. Surface loss takes precedence over sub-optimal errors. */ VkResult getSurfaceStatus() { if (_device->getConfigurationResult() != VK_SUCCESS) { return _device->getConfigurationResult(); } if (getIsSurfaceLost()) { return VK_ERROR_SURFACE_LOST_KHR; } - if (getHasSurfaceSizeChanged()) { return VK_SUBOPTIMAL_KHR; } + if ( !hasOptimalSurface() ) { return VK_SUBOPTIMAL_KHR; } return VK_SUCCESS; } @@ -97,7 +109,7 @@ public: /** VK_GOOGLE_display_timing - returns past presentation times */ VkResult getPastPresentationTiming(uint32_t *pCount, VkPastPresentationTimingGOOGLE *pPresentationTimings); - + void destroy() override; #pragma mark Construction @@ -110,7 +122,9 @@ protected: friend class MVKPresentableSwapchainImage; void propagateDebugName() override; - void initCAMetalLayer(const VkSwapchainCreateInfoKHR* pCreateInfo, uint32_t imgCnt); + void initCAMetalLayer(const VkSwapchainCreateInfoKHR* pCreateInfo, + VkSwapchainPresentScalingCreateInfoEXT* pScalingInfo, + uint32_t imgCnt); void initSurfaceImages(const VkSwapchainCreateInfoKHR* pCreateInfo, uint32_t imgCnt); void releaseLayer(); void releaseUndisplayedSurfaces(); @@ -118,22 +132,25 @@ protected: void willPresentSurface(id mtlTexture, id mtlCmdBuff); void renderWatermark(id mtlTexture, id mtlCmdBuff); void markFrameInterval(); - void recordPresentTime(MVKPresentTimingInfo presentTimingInfo, uint64_t actualPresentTime = 0); + void recordPresentTime(MVKImagePresentInfo presentInfo, uint64_t actualPresentTime = 0); - CAMetalLayer* _mtlLayer; - MVKWatermark* _licenseWatermark; + CAMetalLayer* _mtlLayer = nil; + MVKWatermark* _licenseWatermark = nil; MVKSmallVector _presentableImages; - std::atomic _currentAcquisitionID; - uint64_t _lastFrameTime; - uint32_t _currentPerfLogFrameCount; - std::atomic _surfaceLost; - MVKBlockObserver* _layerObserver; - std::mutex _layerLock; + MVKSmallVector _compatiblePresentModes; static const int kMaxPresentationHistory = 60; VkPastPresentationTimingGOOGLE _presentTimingHistory[kMaxPresentationHistory]; - uint32_t _presentHistoryCount; - uint32_t _presentHistoryIndex; - uint32_t _presentHistoryHeadIndex; + std::atomic _currentAcquisitionID = 0; + MVKBlockObserver* _layerObserver = nil; std::mutex _presentHistoryLock; + std::mutex _layerLock; + uint64_t _lastFrameTime = 0; + CGSize _mtlLayerDrawableSize = {0.0, 0.0}; + uint32_t _currentPerfLogFrameCount = 0; + uint32_t _presentHistoryCount = 0; + uint32_t _presentHistoryIndex = 0; + uint32_t _presentHistoryHeadIndex = 0; + std::atomic _surfaceLost = false; + bool _isDeliberatelyScaled = false; }; diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm index bef4dc42..49686a19 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm @@ -70,11 +70,11 @@ VkResult MVKSwapchain::getImages(uint32_t* pCount, VkImage* pSwapchainImages) { return result; } -VkResult MVKSwapchain::acquireNextImageKHR(uint64_t timeout, - VkSemaphore semaphore, - VkFence fence, - uint32_t deviceMask, - uint32_t* pImageIndex) { +VkResult MVKSwapchain::acquireNextImage(uint64_t timeout, + VkSemaphore semaphore, + VkFence fence, + uint32_t deviceMask, + uint32_t* pImageIndex) { if ( _device->getConfigurationResult() != VK_SUCCESS ) { return _device->getConfigurationResult(); } if ( getIsSurfaceLost() ) { return VK_ERROR_SURFACE_LOST_KHR; } @@ -100,6 +100,14 @@ VkResult MVKSwapchain::acquireNextImageKHR(uint64_t timeout, return getSurfaceStatus(); } +VkResult MVKSwapchain::releaseImages(const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo) { + for (uint32_t imgIdxIdx = 0; imgIdxIdx < pReleaseInfo->imageIndexCount; imgIdxIdx++) { + getPresentableImage(pReleaseInfo->pImageIndices[imgIdxIdx])->makeAvailable(); + } + + return VK_SUCCESS; +} + uint64_t MVKSwapchain::getNextAcquisitionID() { return ++_currentAcquisitionID; } // Releases any surfaces that are not currently being displayed, @@ -227,19 +235,37 @@ void MVKSwapchain::setHDRMetadataEXT(const VkHdrMetadataEXT& metadata) { #pragma mark Construction MVKSwapchain::MVKSwapchain(MVKDevice* device, - const VkSwapchainCreateInfoKHR* pCreateInfo) : - MVKVulkanAPIDeviceObject(device), - _licenseWatermark(nil), - _currentAcquisitionID(0), - _lastFrameTime(0), - _currentPerfLogFrameCount(0), - _surfaceLost(false), - _layerObserver(nil), - _presentHistoryCount(0), - _presentHistoryIndex(0), - _presentHistoryHeadIndex(0) { - + const VkSwapchainCreateInfoKHR* pCreateInfo) : MVKVulkanAPIDeviceObject(device) { memset(_presentTimingHistory, 0, sizeof(_presentTimingHistory)); + + // Retrieve the scaling and present mode structs if they are supplied. + VkSwapchainPresentScalingCreateInfoEXT* pScalingInfo = nullptr; + VkSwapchainPresentModesCreateInfoEXT* pPresentModesInfo = nullptr; + for (auto* next = (const VkBaseInStructure*)pCreateInfo->pNext; next; next = next->pNext) { + switch (next->sType) { + case VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_SCALING_CREATE_INFO_EXT: { + pScalingInfo = (VkSwapchainPresentScalingCreateInfoEXT*)next; + break; + } + case VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODES_CREATE_INFO_EXT: { + pPresentModesInfo = (VkSwapchainPresentModesCreateInfoEXT*)next; + break; + } + default: + break; + } + } + + _isDeliberatelyScaled = pScalingInfo && pScalingInfo->scalingBehavior; + + // Set the list of present modes that can be specified in a queue + // present submission without causing the swapchain to be rebuilt. + if (pPresentModesInfo) { + for (uint32_t pmIdx = 0; pmIdx < pPresentModesInfo->presentModeCount; pmIdx++) { + _compatiblePresentModes.push_back(pPresentModesInfo->pPresentModes[pmIdx]); + } + } + // If applicable, release any surfaces (not currently being displayed) from the old swapchain. MVKSwapchain* oldSwapchain = (MVKSwapchain*)pCreateInfo->oldSwapchain; if (oldSwapchain) { oldSwapchain->releaseUndisplayedSurfaces(); } @@ -247,12 +273,51 @@ MVKSwapchain::MVKSwapchain(MVKDevice* device, uint32_t imgCnt = mvkClamp(pCreateInfo->minImageCount, _device->_pMetalFeatures->minSwapchainImageCount, _device->_pMetalFeatures->maxSwapchainImageCount); - initCAMetalLayer(pCreateInfo, imgCnt); + initCAMetalLayer(pCreateInfo, pScalingInfo, imgCnt); initSurfaceImages(pCreateInfo, imgCnt); // After initCAMetalLayer() } +// kCAGravityResize is the Metal default +static CALayerContentsGravity getCALayerContentsGravity(VkSwapchainPresentScalingCreateInfoEXT* pScalingInfo) { + + if( !pScalingInfo ) { return kCAGravityResize; } + + switch (pScalingInfo->scalingBehavior) { + case VK_PRESENT_SCALING_STRETCH_BIT_EXT: return kCAGravityResize; + case VK_PRESENT_SCALING_ASPECT_RATIO_STRETCH_BIT_EXT: return kCAGravityResizeAspect; + case VK_PRESENT_SCALING_ONE_TO_ONE_BIT_EXT: + switch (pScalingInfo->presentGravityY) { + case VK_PRESENT_GRAVITY_MIN_BIT_EXT: + switch (pScalingInfo->presentGravityX) { + case VK_PRESENT_GRAVITY_MIN_BIT_EXT: return kCAGravityTopLeft; + case VK_PRESENT_GRAVITY_CENTERED_BIT_EXT: return kCAGravityTop; + case VK_PRESENT_GRAVITY_MAX_BIT_EXT: return kCAGravityTopRight; + default: return kCAGravityTop; + } + case VK_PRESENT_GRAVITY_CENTERED_BIT_EXT: + switch (pScalingInfo->presentGravityX) { + case VK_PRESENT_GRAVITY_MIN_BIT_EXT: return kCAGravityLeft; + case VK_PRESENT_GRAVITY_CENTERED_BIT_EXT: return kCAGravityCenter; + case VK_PRESENT_GRAVITY_MAX_BIT_EXT: return kCAGravityRight; + default: return kCAGravityCenter; + } + case VK_PRESENT_GRAVITY_MAX_BIT_EXT: + switch (pScalingInfo->presentGravityX) { + case VK_PRESENT_GRAVITY_MIN_BIT_EXT: return kCAGravityBottomLeft; + case VK_PRESENT_GRAVITY_CENTERED_BIT_EXT: return kCAGravityBottom; + case VK_PRESENT_GRAVITY_MAX_BIT_EXT: return kCAGravityBottomRight; + default: return kCAGravityBottom; + } + default: return kCAGravityCenter; + } + default: return kCAGravityResize; + } +} + // Initializes the CAMetalLayer underlying the surface of this swapchain. -void MVKSwapchain::initCAMetalLayer(const VkSwapchainCreateInfoKHR* pCreateInfo, uint32_t imgCnt) { +void MVKSwapchain::initCAMetalLayer(const VkSwapchainCreateInfoKHR* pCreateInfo, + VkSwapchainPresentScalingCreateInfoEXT* pScalingInfo, + uint32_t imgCnt) { MVKSurface* mvkSrfc = (MVKSurface*)pCreateInfo->surface; _mtlLayer = mvkSrfc->getCAMetalLayer(); @@ -262,15 +327,22 @@ void MVKSwapchain::initCAMetalLayer(const VkSwapchainCreateInfoKHR* pCreateInfo, return; } + auto minMagFilter = mvkConfig().swapchainMinMagFilterUseNearest ? kCAFilterNearest : kCAFilterLinear; _mtlLayer.device = getMTLDevice(); _mtlLayer.pixelFormat = getPixelFormats()->getMTLPixelFormat(pCreateInfo->imageFormat); _mtlLayer.maximumDrawableCountMVK = imgCnt; _mtlLayer.displaySyncEnabledMVK = (pCreateInfo->presentMode != VK_PRESENT_MODE_IMMEDIATE_KHR); - _mtlLayer.magnificationFilter = mvkConfig().swapchainMagFilterUseNearest ? kCAFilterNearest : kCAFilterLinear; + _mtlLayer.minificationFilter = minMagFilter; + _mtlLayer.magnificationFilter = minMagFilter; + _mtlLayer.contentsGravity = getCALayerContentsGravity(pScalingInfo); _mtlLayer.framebufferOnly = !mvkIsAnyFlagEnabled(pCreateInfo->imageUsage, (VK_IMAGE_USAGE_TRANSFER_SRC_BIT | 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; + if (pCreateInfo->compositeAlpha != VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) { _mtlLayer.opaque = pCreateInfo->compositeAlpha == VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; } @@ -330,7 +402,6 @@ void MVKSwapchain::initCAMetalLayer(const VkSwapchainCreateInfoKHR* pCreateInfo, setConfigurationResult(reportError(VK_ERROR_FORMAT_NOT_SUPPORTED, "vkCreateSwapchainKHR(): Metal does not support VkColorSpaceKHR value %d.", pCreateInfo->imageColorSpace)); break; } - _mtlLayer.drawableSize = mvkCGSizeFromVkExtent2D(pCreateInfo->imageExtent); // TODO: set additional CAMetalLayer properties before extracting drawables: // - presentsWithTransaction @@ -398,8 +469,11 @@ void MVKSwapchain::initSurfaceImages(const VkSwapchainCreateInfoKHR* pCreateInfo mvkEnableFlags(imgInfo.flags, VK_IMAGE_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT); } + bool deferImgMemAlloc = mvkAreAllFlagsEnabled(pCreateInfo->flags, VK_SWAPCHAIN_CREATE_DEFERRED_MEMORY_ALLOCATION_BIT_EXT); + for (uint32_t imgIdx = 0; imgIdx < imgCnt; imgIdx++) { - _presentableImages.push_back(_device->createPresentableSwapchainImage(&imgInfo, this, imgIdx, NULL)); + _presentableImages.push_back(_device->createPresentableSwapchainImage(&imgInfo, this, imgIdx, + deferImgMemAlloc, NULL)); } NSString* screenName = @"Main Screen"; @@ -469,7 +543,7 @@ VkResult MVKSwapchain::getPastPresentationTiming(uint32_t *pCount, VkPastPresent return res; } -void MVKSwapchain::recordPresentTime(MVKPresentTimingInfo presentTimingInfo, uint64_t actualPresentTime) { +void MVKSwapchain::recordPresentTime(MVKImagePresentInfo presentInfo, uint64_t actualPresentTime) { std::lock_guard lock(_presentHistoryLock); if (_presentHistoryCount < kMaxPresentationHistory) { _presentHistoryCount++; @@ -478,10 +552,10 @@ void MVKSwapchain::recordPresentTime(MVKPresentTimingInfo presentTimingInfo, uin } // If actual time not supplied, use desired time instead - if (actualPresentTime == 0) { actualPresentTime = presentTimingInfo.desiredPresentTime; } + if (actualPresentTime == 0) { actualPresentTime = presentInfo.desiredPresentTime; } - _presentTimingHistory[_presentHistoryIndex].presentID = presentTimingInfo.presentID; - _presentTimingHistory[_presentHistoryIndex].desiredPresentTime = presentTimingInfo.desiredPresentTime; + _presentTimingHistory[_presentHistoryIndex].presentID = presentInfo.presentID; + _presentTimingHistory[_presentHistoryIndex].desiredPresentTime = presentInfo.desiredPresentTime; _presentTimingHistory[_presentHistoryIndex].actualPresentTime = actualPresentTime; // These details are not available in Metal _presentTimingHistory[_presentHistoryIndex].earliestPresentTime = actualPresentTime; diff --git a/MoltenVK/MoltenVK/Layers/MVKExtensions.def b/MoltenVK/MoltenVK/Layers/MVKExtensions.def index 17ed1f25..598757c2 100644 --- a/MoltenVK/MoltenVK/Layers/MVKExtensions.def +++ b/MoltenVK/MoltenVK/Layers/MVKExtensions.def @@ -114,7 +114,9 @@ MVK_EXTENSION(EXT_separate_stencil_usage, EXT_SEPARATE_STENCIL_USAGE, MVK_EXTENSION(EXT_shader_stencil_export, EXT_SHADER_STENCIL_EXPORT, DEVICE, 10.14, 12.0) MVK_EXTENSION(EXT_shader_viewport_index_layer, EXT_SHADER_VIEWPORT_INDEX_LAYER, DEVICE, 10.11, 8.0) MVK_EXTENSION(EXT_subgroup_size_control, EXT_SUBGROUP_SIZE_CONTROL, DEVICE, 10.14, 13.0) +MVK_EXTENSION(EXT_surface_maintenance1, EXT_SURFACE_MAINTENANCE_1, INSTANCE, 10.11, 8.0) MVK_EXTENSION(EXT_swapchain_colorspace, EXT_SWAPCHAIN_COLOR_SPACE, INSTANCE, 10.11, 9.0) +MVK_EXTENSION(EXT_swapchain_maintenance1, EXT_SWAPCHAIN_MAINTENANCE_1, DEVICE, 10.11, 8.0) MVK_EXTENSION(EXT_texel_buffer_alignment, EXT_TEXEL_BUFFER_ALIGNMENT, DEVICE, 10.13, 11.0) MVK_EXTENSION(EXT_texture_compression_astc_hdr, EXT_TEXTURE_COMPRESSION_ASTC_HDR, DEVICE, 11.0, 13.0) MVK_EXTENSION(EXT_vertex_attribute_divisor, EXT_VERTEX_ATTRIBUTE_DIVISOR, DEVICE, 10.11, 8.0) diff --git a/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.h b/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.h index 55b82f24..dcde22dc 100644 --- a/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.h +++ b/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.h @@ -38,20 +38,11 @@ /** * 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, and is the value that the drawableSize - * property will be set to when the updatedDrawableSizeMVK nethod is invoked. + * The natural drawable size is the size of the bounds property of this layer, + * multiplied by the contentsScale property of this layer. */ @property(nonatomic, readonly) CGSize naturalDrawableSizeMVK; -/** - * Ensures the drawableSize property of this layer is up to date, by ensuring - * it is set to the value returned by the naturalDrawableSizeMVK property. - * - * Returns the updated drawableSize value. - */ --(CGSize) updatedDrawableSizeMVK; - /** * Replacement for the displaySyncEnabled property. * diff --git a/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.m b/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.m index 707b908a..a9e5a009 100644 --- a/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.m +++ b/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.m @@ -31,18 +31,8 @@ -(CGSize) naturalDrawableSizeMVK { CGSize drawSize = self.bounds.size; CGFloat scaleFactor = self.contentsScale; - drawSize.width = trunc(drawSize.width * scaleFactor); - drawSize.height = trunc(drawSize.height * scaleFactor); - return drawSize; -} - -// Only update drawableSize property value if it needs to be, -// in case updating to same value causes internal reconfigurations. --(CGSize) updatedDrawableSizeMVK { - CGSize drawSize = self.naturalDrawableSizeMVK; - if ( !CGSizeEqualToSize(drawSize, self.drawableSize) ) { - self.drawableSize = drawSize; - } + drawSize.width *= scaleFactor; + drawSize.height *= scaleFactor; return drawSize; } diff --git a/MoltenVK/MoltenVK/Utility/MVKEnvironment.cpp b/MoltenVK/MoltenVK/Utility/MVKEnvironment.cpp index 7275daaf..9861a359 100644 --- a/MoltenVK/MoltenVK/Utility/MVKEnvironment.cpp +++ b/MoltenVK/MoltenVK/Utility/MVKEnvironment.cpp @@ -34,7 +34,8 @@ static void mvkInitConfigFromEnvVars() { MVK_SET_FROM_ENV_OR_BUILD_INT32 (evCfg.maxActiveMetalCommandBuffersPerQueue, MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE); MVK_SET_FROM_ENV_OR_BUILD_BOOL (evCfg.supportLargeQueryPools, MVK_CONFIG_SUPPORT_LARGE_QUERY_POOLS); MVK_SET_FROM_ENV_OR_BUILD_BOOL (evCfg.presentWithCommandBuffer, MVK_CONFIG_PRESENT_WITH_COMMAND_BUFFER); - MVK_SET_FROM_ENV_OR_BUILD_BOOL (evCfg.swapchainMagFilterUseNearest, MVK_CONFIG_SWAPCHAIN_MAG_FILTER_USE_NEAREST); + MVK_SET_FROM_ENV_OR_BUILD_BOOL (evCfg.swapchainMinMagFilterUseNearest, MVK_CONFIG_SWAPCHAIN_MAG_FILTER_USE_NEAREST); // Deprecated legacy env var + MVK_SET_FROM_ENV_OR_BUILD_BOOL (evCfg.swapchainMinMagFilterUseNearest, MVK_CONFIG_SWAPCHAIN_MIN_MAG_FILTER_USE_NEAREST); MVK_SET_FROM_ENV_OR_BUILD_INT64 (evCfg.metalCompileTimeout, MVK_CONFIG_METAL_COMPILE_TIMEOUT); MVK_SET_FROM_ENV_OR_BUILD_BOOL (evCfg.performanceTracking, MVK_CONFIG_PERFORMANCE_TRACKING); MVK_SET_FROM_ENV_OR_BUILD_INT32 (evCfg.performanceLoggingFrameCount, MVK_CONFIG_PERFORMANCE_LOGGING_FRAME_COUNT); diff --git a/MoltenVK/MoltenVK/Utility/MVKEnvironment.h b/MoltenVK/MoltenVK/Utility/MVKEnvironment.h index 90883245..ae90fd3f 100644 --- a/MoltenVK/MoltenVK/Utility/MVKEnvironment.h +++ b/MoltenVK/MoltenVK/Utility/MVKEnvironment.h @@ -128,9 +128,12 @@ void mvkSetConfig(const MVKConfiguration& mvkConfig); # define MVK_CONFIG_PRESENT_WITH_COMMAND_BUFFER 1 #endif -/** Use nearest sampling to magnify swapchain images. Enabled by default. */ +/** Use nearest sampling to minify or magnify swapchain images. Enabled by default. Second macro name is deprecated legacy. */ +#ifndef MVK_CONFIG_SWAPCHAIN_MIN_MAG_FILTER_USE_NEAREST +# define MVK_CONFIG_SWAPCHAIN_MIN_MAG_FILTER_USE_NEAREST 1 +#endif #ifndef MVK_CONFIG_SWAPCHAIN_MAG_FILTER_USE_NEAREST -# define MVK_CONFIG_SWAPCHAIN_MAG_FILTER_USE_NEAREST 1 +# define MVK_CONFIG_SWAPCHAIN_MAG_FILTER_USE_NEAREST MVK_CONFIG_SWAPCHAIN_MIN_MAG_FILTER_USE_NEAREST #endif /** The maximum amount of time, in nanoseconds, to wait for a Metal library. Default is infinite. */ diff --git a/MoltenVK/MoltenVK/Utility/MVKFoundation.h b/MoltenVK/MoltenVK/Utility/MVKFoundation.h index 5bcf10f1..d03b42f9 100644 --- a/MoltenVK/MoltenVK/Utility/MVKFoundation.h +++ b/MoltenVK/MoltenVK/Utility/MVKFoundation.h @@ -339,11 +339,13 @@ static inline bool mvkVkComponentMappingsMatch(VkComponentMapping cm1, VkCompone #define mvkPrintSizeOf(type) printf("Size of " #type " is %lu.\n", sizeof(type)) -#pragma mark - -#pragma mark Template functions - #pragma mark Math +/** Rounds the value to nearest integer using half-to-even rounding. */ +static inline double mvkRoundHalfToEven(const double val) { + return val - std::remainder(val, 1.0); // remainder() uses half-to-even rounding +} + /** Returns whether the value will fit inside the numeric type. */ template const bool mvkFits(const Tval& val) { diff --git a/MoltenVK/MoltenVK/Utility/MVKLogging.h b/MoltenVK/MoltenVK/Utility/MVKLogging.h index 347f65b3..bea3a92f 100644 --- a/MoltenVK/MoltenVK/Utility/MVKLogging.h +++ b/MoltenVK/MoltenVK/Utility/MVKLogging.h @@ -207,9 +207,6 @@ do { \ # define MVK_DEBUGGER() { kill( getpid(), SIGINT ) ; } #endif -// Log the size of a type, struct, or class -#define MVKLogSizeOf(T) printf("sizeof(%s): %lu.\n", #T, sizeof(T)) - #ifdef __cplusplus } diff --git a/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm index 7998d3e6..1960dedd 100644 --- a/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm +++ b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm @@ -730,6 +730,24 @@ MVK_PUBLIC_SYMBOL MTLBarrierScope mvkMTLBarrierScopeFromVkAccessFlags(VkAccessFl } +#pragma mark - +#pragma mark Geometry conversions + +MVK_PUBLIC_SYMBOL VkExtent2D mvkVkExtent2DFromCGSize(CGSize cgSize) { + VkExtent2D vkExt; + vkExt.width = mvkRoundHalfToEven(cgSize.width); + vkExt.height = mvkRoundHalfToEven(cgSize.height); + return vkExt; +} + +MVK_PUBLIC_SYMBOL CGSize mvkCGSizeFromVkExtent2D(VkExtent2D vkExtent) { + CGSize cgSize; + cgSize.width = vkExtent.width; + cgSize.height = vkExtent.height; + return cgSize; +} + + #pragma mark - #pragma mark Memory options diff --git a/MoltenVK/MoltenVK/Vulkan/vulkan.mm b/MoltenVK/MoltenVK/Vulkan/vulkan.mm index d84a3506..dfea51b2 100644 --- a/MoltenVK/MoltenVK/Vulkan/vulkan.mm +++ b/MoltenVK/MoltenVK/Vulkan/vulkan.mm @@ -2795,7 +2795,7 @@ MVK_PUBLIC_VULKAN_SYMBOL VkResult vkAcquireNextImageKHR( MVKTraceVulkanCallStart(); MVKSwapchain* mvkSwapchain = (MVKSwapchain*)swapchain; - VkResult rslt = mvkSwapchain->acquireNextImageKHR(timeout, semaphore, fence, ~0u, pImageIndex); + VkResult rslt = mvkSwapchain->acquireNextImage(timeout, semaphore, fence, ~0u, pImageIndex); MVKTraceVulkanCallEnd(); return rslt; } @@ -2856,11 +2856,25 @@ MVK_PUBLIC_VULKAN_SYMBOL VkResult vkAcquireNextImage2KHR( MVKTraceVulkanCallStart(); MVKSwapchain* mvkSwapchain = (MVKSwapchain*)pAcquireInfo->swapchain; - VkResult rslt = mvkSwapchain->acquireNextImageKHR(pAcquireInfo->timeout, - pAcquireInfo->semaphore, - pAcquireInfo->fence, - pAcquireInfo->deviceMask, - pImageIndex); + VkResult rslt = mvkSwapchain->acquireNextImage(pAcquireInfo->timeout, + pAcquireInfo->semaphore, + pAcquireInfo->fence, + pAcquireInfo->deviceMask, + pImageIndex); + MVKTraceVulkanCallEnd(); + return rslt; +} + +#pragma mark - +#pragma mark VK_EXT_swapchain_maintenance1 extension + +MVK_PUBLIC_VULKAN_SYMBOL VkResult vkReleaseSwapchainImagesEXT( + VkDevice device, + const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo) { + + MVKTraceVulkanCallStart(); + MVKSwapchain* mvkSwapchain = (MVKSwapchain*)pReleaseInfo->swapchain; + VkResult rslt = mvkSwapchain->releaseImages(pReleaseInfo); MVKTraceVulkanCallEnd(); return rslt; } @@ -2901,8 +2915,7 @@ MVK_PUBLIC_VULKAN_SYMBOL VkResult vkGetPhysicalDeviceSurfaceCapabilitiesKHR( MVKTraceVulkanCallStart(); MVKPhysicalDevice* mvkPD = MVKPhysicalDevice::getMVKPhysicalDevice(physicalDevice); - MVKSurface* mvkSrfc = (MVKSurface*)surface; - VkResult rslt = mvkPD->getSurfaceCapabilities(mvkSrfc, pSurfaceCapabilities); + VkResult rslt = mvkPD->getSurfaceCapabilities(surface, pSurfaceCapabilities); MVKTraceVulkanCallEnd(); return rslt; } @@ -2946,8 +2959,7 @@ MVK_PUBLIC_VULKAN_SYMBOL VkResult vkGetPhysicalDeviceSurfaceCapabilities2KHR( MVKTraceVulkanCallStart(); MVKPhysicalDevice* mvkPD = MVKPhysicalDevice::getMVKPhysicalDevice(physicalDevice); - MVKSurface* mvkSrfc = (MVKSurface*)pSurfaceInfo->surface; - VkResult rslt = mvkPD->getSurfaceCapabilities(mvkSrfc, &pSurfaceCapabilities->surfaceCapabilities); + VkResult rslt = mvkPD->getSurfaceCapabilities(pSurfaceInfo, pSurfaceCapabilities); MVKTraceVulkanCallEnd(); return rslt; } From 3250569260fd3bf26de3d24e549e5813f5aa4287 Mon Sep 17 00:00:00 2001 From: Bill Hollings Date: Sat, 4 Feb 2023 11:27:16 -0500 Subject: [PATCH 2/6] Queue submissions retain wait semaphores until MTLCommandBuffer finishes. Add additional Vulkan error strings (unrelated). --- Docs/Whats_New.md | 1 + MoltenVK/MoltenVK/GPUObjects/MVKImage.mm | 12 ++- MoltenVK/MoltenVK/GPUObjects/MVKQueue.h | 6 +- MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm | 35 ++++++--- MoltenVK/MoltenVK/Utility/MVKFoundation.cpp | 84 +++++++++++---------- 5 files changed, 84 insertions(+), 54 deletions(-) diff --git a/Docs/Whats_New.md b/Docs/Whats_New.md index 8ac6cf7b..19ec6ea7 100644 --- a/Docs/Whats_New.md +++ b/Docs/Whats_New.md @@ -25,6 +25,7 @@ Released TBA was sometimes incorrectly disabled due to a Metal driver bug. - Detect when size of surface has changed under the covers. - Change rounding of surface size provided by Metal from truncation to rounding-with-half-to-even. +- Queue submissions retain wait semaphores until `MTLCommandBuffer` finishes. MoltenVK 1.2.2 diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm index 7cded7b9..89631353 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm @@ -1338,15 +1338,21 @@ void MVKPresentableSwapchainImage::presentCAMetalDrawable(id m _availabilitySignalers.erase(sigIter); } - // Ensure this image and the drawable are not destroyed while awaiting MTLCommandBuffer completion. - // We retain the drawable separately because new drawable might be acquired by this image by then. + // 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 mcb) { [mtlDrwbl release]; makeAvailable(signaler); release(); - if (presentInfo.fence) { presentInfo.fence->signal(); } + if (fence) { + fence->signal(); + fence->release(); + } }]; signalPresentationSemaphore(signaler, mtlCmdBuff); diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.h b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.h index 54a68595..bad444f3 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.h +++ b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.h @@ -189,6 +189,8 @@ public: protected: friend class MVKQueue; + virtual void finish() = 0; + MVKQueue* _queue; MVKSmallVector> _waitSemaphores; }; @@ -213,7 +215,7 @@ protected: id getActiveMTLCommandBuffer(); void setActiveMTLCommandBuffer(id mtlCmdBuff); void commitActiveMTLCommandBuffer(bool signalCompletion = false); - virtual void finish(); + void finish() override; virtual void submitCommandBuffers() {} MVKSmallVector> _signalSemaphores; @@ -270,7 +272,7 @@ public: const VkPresentInfoKHR* pPresentInfo); protected: - void stopAutoGPUCapture(); + void finish() override; MVKSmallVector _presentInfo; }; diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm index 33c3246a..192de63c 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm @@ -290,11 +290,15 @@ MVKQueueSubmission::MVKQueueSubmission(MVKQueue* queue, _waitSemaphores.reserve(waitSemaphoreCount); for (uint32_t i = 0; i < waitSemaphoreCount; i++) { - _waitSemaphores.push_back(make_pair((MVKSemaphore*)pWaitSemaphores[i], (uint64_t)0)); + auto* sem4 = (MVKSemaphore*)pWaitSemaphores[i]; + sem4->retain(); + uint64_t sem4Val = 0; + _waitSemaphores.emplace_back(sem4, sem4Val); } } MVKQueueSubmission::~MVKQueueSubmission() { + for (auto s : _waitSemaphores) { s.first->release(); } _queue->release(); } @@ -530,33 +534,42 @@ void MVKQueueFullCommandBufferSubmission::finish() { #pragma mark - #pragma mark MVKQueuePresentSurfaceSubmission +// If the semaphores are encodable, wait on them by encoding them on the MTLCommandBuffer before presenting. +// If the semaphores are not encodable, wait on them inline after presenting. +// The semaphores know what to do. void MVKQueuePresentSurfaceSubmission::execute() { - // If the semaphores are encodable, wait on them by encoding them on the MTLCommandBuffer before presenting. - // If the semaphores are not encodable, wait on them inline after presenting. - // The semaphores know what to do. id mtlCmdBuff = _queue->getMTLCommandBuffer(kMVKCommandUseQueuePresent); [mtlCmdBuff enqueue]; for (auto& ws : _waitSemaphores) { ws.first->encodeWait(mtlCmdBuff, 0); } + + // Add completion handler that will destroy this submission only once the MTLCommandBuffer + // is finished with the resources retained here, including the wait semaphores. + // Completion handlers are also added in presentCAMetalDrawable() to retain the swapchain images. + [mtlCmdBuff addCompletedHandler: ^(id mcb) { + this->finish(); + }]; + for (int i = 0; i < _presentInfo.size(); i++ ) { _presentInfo[i].presentableImage->presentCAMetalDrawable(mtlCmdBuff, _presentInfo[i]); } + for (auto& ws : _waitSemaphores) { ws.first->encodeWait(nil, 0); } [mtlCmdBuff commit]; +} - // Let Xcode know the current frame is done, then start a new frame +void MVKQueuePresentSurfaceSubmission::finish() { + + // Let Xcode know the current frame is done, then start a new frame, + // and if auto GPU capture is active, and it's time to stop it, do so. auto cs = _queue->_submissionCaptureScope; cs->endScope(); cs->beginScope(); - stopAutoGPUCapture(); - - this->destroy(); -} - -void MVKQueuePresentSurfaceSubmission::stopAutoGPUCapture() { if (_queue->_queueFamily->getIndex() == mvkConfig().defaultGPUCaptureScopeQueueFamilyIndex && _queue->_index == mvkConfig().defaultGPUCaptureScopeQueueIndex) { _queue->getDevice()->stopAutoGPUCapture(MVK_CONFIG_AUTO_GPU_CAPTURE_SCOPE_FRAME); } + + this->destroy(); } MVKQueuePresentSurfaceSubmission::MVKQueuePresentSurfaceSubmission(MVKQueue* queue, diff --git a/MoltenVK/MoltenVK/Utility/MVKFoundation.cpp b/MoltenVK/MoltenVK/Utility/MVKFoundation.cpp index 0314f609..29ee115f 100644 --- a/MoltenVK/MoltenVK/Utility/MVKFoundation.cpp +++ b/MoltenVK/MoltenVK/Utility/MVKFoundation.cpp @@ -24,49 +24,57 @@ const char* mvkVkResultName(VkResult vkResult) { switch (vkResult) { - CASE_STRINGIFY(VK_SUCCESS); - CASE_STRINGIFY(VK_NOT_READY); - CASE_STRINGIFY(VK_TIMEOUT); - CASE_STRINGIFY(VK_EVENT_SET); - CASE_STRINGIFY(VK_EVENT_RESET); - CASE_STRINGIFY(VK_INCOMPLETE); + CASE_STRINGIFY(VK_SUCCESS); + CASE_STRINGIFY(VK_NOT_READY); + CASE_STRINGIFY(VK_TIMEOUT); + CASE_STRINGIFY(VK_EVENT_SET); + CASE_STRINGIFY(VK_EVENT_RESET); + CASE_STRINGIFY(VK_INCOMPLETE); - CASE_STRINGIFY(VK_ERROR_OUT_OF_HOST_MEMORY); - CASE_STRINGIFY(VK_ERROR_OUT_OF_DEVICE_MEMORY); - CASE_STRINGIFY(VK_ERROR_INITIALIZATION_FAILED); - CASE_STRINGIFY(VK_ERROR_DEVICE_LOST); - CASE_STRINGIFY(VK_ERROR_MEMORY_MAP_FAILED); - CASE_STRINGIFY(VK_ERROR_LAYER_NOT_PRESENT); - CASE_STRINGIFY(VK_ERROR_EXTENSION_NOT_PRESENT); - CASE_STRINGIFY(VK_ERROR_FEATURE_NOT_PRESENT); - CASE_STRINGIFY(VK_ERROR_INCOMPATIBLE_DRIVER); - CASE_STRINGIFY(VK_ERROR_TOO_MANY_OBJECTS); - CASE_STRINGIFY(VK_ERROR_FORMAT_NOT_SUPPORTED); - CASE_STRINGIFY(VK_ERROR_FRAGMENTED_POOL); + CASE_STRINGIFY(VK_ERROR_OUT_OF_HOST_MEMORY); + CASE_STRINGIFY(VK_ERROR_OUT_OF_DEVICE_MEMORY); + CASE_STRINGIFY(VK_ERROR_INITIALIZATION_FAILED); + CASE_STRINGIFY(VK_ERROR_DEVICE_LOST); + CASE_STRINGIFY(VK_ERROR_MEMORY_MAP_FAILED); + CASE_STRINGIFY(VK_ERROR_LAYER_NOT_PRESENT); + CASE_STRINGIFY(VK_ERROR_EXTENSION_NOT_PRESENT); + CASE_STRINGIFY(VK_ERROR_FEATURE_NOT_PRESENT); + CASE_STRINGIFY(VK_ERROR_INCOMPATIBLE_DRIVER); + CASE_STRINGIFY(VK_ERROR_TOO_MANY_OBJECTS); + CASE_STRINGIFY(VK_ERROR_FORMAT_NOT_SUPPORTED); + CASE_STRINGIFY(VK_ERROR_FRAGMENTED_POOL); - CASE_STRINGIFY(VK_ERROR_UNKNOWN); - CASE_STRINGIFY(VK_ERROR_OUT_OF_POOL_MEMORY); - CASE_STRINGIFY(VK_ERROR_INVALID_EXTERNAL_HANDLE); - CASE_STRINGIFY(VK_ERROR_FRAGMENTATION); - CASE_STRINGIFY(VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS); + CASE_STRINGIFY(VK_ERROR_UNKNOWN); + CASE_STRINGIFY(VK_ERROR_OUT_OF_POOL_MEMORY); + CASE_STRINGIFY(VK_ERROR_INVALID_EXTERNAL_HANDLE); + CASE_STRINGIFY(VK_ERROR_FRAGMENTATION); + CASE_STRINGIFY(VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS); + CASE_STRINGIFY(VK_PIPELINE_COMPILE_REQUIRED); - CASE_STRINGIFY(VK_ERROR_SURFACE_LOST_KHR); - CASE_STRINGIFY(VK_ERROR_NATIVE_WINDOW_IN_USE_KHR); - CASE_STRINGIFY(VK_SUBOPTIMAL_KHR); - CASE_STRINGIFY(VK_ERROR_OUT_OF_DATE_KHR); - CASE_STRINGIFY(VK_ERROR_INCOMPATIBLE_DISPLAY_KHR); + CASE_STRINGIFY(VK_ERROR_SURFACE_LOST_KHR); + CASE_STRINGIFY(VK_ERROR_NATIVE_WINDOW_IN_USE_KHR); + CASE_STRINGIFY(VK_SUBOPTIMAL_KHR); + CASE_STRINGIFY(VK_ERROR_OUT_OF_DATE_KHR); + CASE_STRINGIFY(VK_ERROR_INCOMPATIBLE_DISPLAY_KHR); - CASE_STRINGIFY(VK_ERROR_VALIDATION_FAILED_EXT); - CASE_STRINGIFY(VK_ERROR_INVALID_SHADER_NV); + CASE_STRINGIFY(VK_ERROR_VALIDATION_FAILED_EXT); + CASE_STRINGIFY(VK_ERROR_INVALID_SHADER_NV); + CASE_STRINGIFY(VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR); - CASE_STRINGIFY(VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT); - CASE_STRINGIFY(VK_ERROR_NOT_PERMITTED_EXT); - CASE_STRINGIFY(VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT); - CASE_STRINGIFY(VK_THREAD_IDLE_KHR); - CASE_STRINGIFY(VK_THREAD_DONE_KHR); - CASE_STRINGIFY(VK_OPERATION_DEFERRED_KHR); - CASE_STRINGIFY(VK_OPERATION_NOT_DEFERRED_KHR); - CASE_STRINGIFY(VK_PIPELINE_COMPILE_REQUIRED_EXT); + CASE_STRINGIFY(VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR); + CASE_STRINGIFY(VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR); + CASE_STRINGIFY(VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR); + CASE_STRINGIFY(VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR); + CASE_STRINGIFY(VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR); + + CASE_STRINGIFY(VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT); + CASE_STRINGIFY(VK_ERROR_NOT_PERMITTED_KHR); + CASE_STRINGIFY(VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT); + CASE_STRINGIFY(VK_THREAD_IDLE_KHR); + CASE_STRINGIFY(VK_THREAD_DONE_KHR); + CASE_STRINGIFY(VK_OPERATION_DEFERRED_KHR); + CASE_STRINGIFY(VK_OPERATION_NOT_DEFERRED_KHR); + CASE_STRINGIFY(VK_ERROR_COMPRESSION_EXHAUSTED_EXT); default: return "VK_UNKNOWN_VK_Result"; } From 2f87f85278c6044272a1feb5b8875bafb59912ad Mon Sep 17 00:00:00 2001 From: Bill Hollings Date: Sun, 12 Feb 2023 19:02:47 +0000 Subject: [PATCH 3/6] Fixes from PR review of VK_EXT_swapchain_maintenance1 and VK_EXT_surface_maintenance1. - Pass MVKImagePresentInfo struct by reference instead of copy. - Remove unused boolean to pass around VK_SWAPCHAIN_CREATE_DEFERRED_MEMORY_ALLOCATION_BIT_EXT status. --- MoltenVK/MoltenVK/GPUObjects/MVKDevice.h | 1 - MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm | 4 +--- MoltenVK/MoltenVK/GPUObjects/MVKImage.h | 11 ++++------- MoltenVK/MoltenVK/GPUObjects/MVKImage.mm | 9 +++------ MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h | 4 ++-- MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm | 9 +++++---- 6 files changed, 15 insertions(+), 23 deletions(-) diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h index a37d91ea..f58d9ee3 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h +++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h @@ -544,7 +544,6 @@ public: MVKPresentableSwapchainImage* createPresentableSwapchainImage(const VkImageCreateInfo* pCreateInfo, MVKSwapchain* swapchain, uint32_t swapchainIndex, - bool deferImgMemAlloc, const VkAllocationCallbacks* pAllocator); void destroyPresentableSwapchainImage(MVKPresentableSwapchainImage* mvkImg, const VkAllocationCallbacks* pAllocator); diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm index 8fe673d2..2612005f 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm @@ -3573,11 +3573,9 @@ void MVKDevice::destroySwapchain(MVKSwapchain* mvkSwpChn, MVKPresentableSwapchainImage* MVKDevice::createPresentableSwapchainImage(const VkImageCreateInfo* pCreateInfo, MVKSwapchain* swapchain, uint32_t swapchainIndex, - bool deferImgMemAlloc, const VkAllocationCallbacks* pAllocator) { MVKPresentableSwapchainImage* mvkImg = new MVKPresentableSwapchainImage(this, pCreateInfo, - swapchain, swapchainIndex, - deferImgMemAlloc); + swapchain, swapchainIndex); for (auto& memoryBinding : mvkImg->_memoryBindings) { addResource(memoryBinding); } diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h index b6167533..c2a03ee5 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h +++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h @@ -449,16 +449,13 @@ public: #pragma mark Metal /** Presents the contained drawable to the OS. */ - void presentCAMetalDrawable(id mtlCmdBuff, MVKImagePresentInfo presentInfo); + void presentCAMetalDrawable(id mtlCmdBuff, MVKImagePresentInfo& presentInfo); #pragma mark Construction - MVKPresentableSwapchainImage(MVKDevice* device, - const VkImageCreateInfo* pCreateInfo, - MVKSwapchain* swapchain, - uint32_t swapchainIndex, - bool deferImgMemAlloc); + MVKPresentableSwapchainImage(MVKDevice* device, const VkImageCreateInfo* pCreateInfo, + MVKSwapchain* swapchain, uint32_t swapchainIndex); ~MVKPresentableSwapchainImage() override; @@ -466,7 +463,7 @@ protected: friend MVKSwapchain; id getCAMetalDrawable() override; - void addPresentedHandler(id mtlDrawable, MVKImagePresentInfo presentInfo); + void addPresentedHandler(id mtlDrawable, MVKImagePresentInfo& presentInfo); void releaseMetalDrawable(); MVKSwapchainImageAvailability getAvailability(); void makeAvailable(const MVKSwapchainSignaler& signaler); diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm index 89631353..19822de6 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm @@ -1296,7 +1296,7 @@ id MVKPresentableSwapchainImage::getCAMetalDrawable() { // Present the drawable and make myself available only once the command buffer has completed. void MVKPresentableSwapchainImage::presentCAMetalDrawable(id mtlCmdBuff, - MVKImagePresentInfo presentInfo) { + MVKImagePresentInfo& presentInfo) { lock_guard lock(_availabilityLock); _swapchain->willPresentSurface(getMTLTexture(0), mtlCmdBuff); @@ -1359,7 +1359,7 @@ void MVKPresentableSwapchainImage::presentCAMetalDrawable(id m } void MVKPresentableSwapchainImage::addPresentedHandler(id mtlDrawable, - MVKImagePresentInfo presentInfo) { + MVKImagePresentInfo& presentInfo) { #if !MVK_OS_SIMULATOR if ([mtlDrawable respondsToSelector: @selector(addPresentedHandler:)]) { retain(); // Ensure this image is not destroyed while awaiting presentation @@ -1418,13 +1418,10 @@ void MVKPresentableSwapchainImage::makeAvailable() { #pragma mark Construction -// The deferImgMemAlloc parameter is ignored, because image memory allocation is provided by a MTLDrawable, -// which is retrieved lazily, and hence is already as deferred (or as deferred as we can make it). MVKPresentableSwapchainImage::MVKPresentableSwapchainImage(MVKDevice* device, const VkImageCreateInfo* pCreateInfo, MVKSwapchain* swapchain, - uint32_t swapchainIndex, - bool deferImgMemAlloc) : + uint32_t swapchainIndex) : MVKSwapchainImage(device, pCreateInfo, swapchain, swapchainIndex) { _mtlDrawable = nil; diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h index a82ef814..926661aa 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h +++ b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h @@ -132,10 +132,10 @@ protected: void willPresentSurface(id mtlTexture, id mtlCmdBuff); void renderWatermark(id mtlTexture, id mtlCmdBuff); void markFrameInterval(); - void recordPresentTime(MVKImagePresentInfo presentInfo, uint64_t actualPresentTime = 0); + void recordPresentTime(MVKImagePresentInfo& presentInfo, uint64_t actualPresentTime = 0); CAMetalLayer* _mtlLayer = nil; - MVKWatermark* _licenseWatermark = nil; + MVKWatermark* _licenseWatermark = nullptr; MVKSmallVector _presentableImages; MVKSmallVector _compatiblePresentModes; static const int kMaxPresentationHistory = 60; diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm index 49686a19..792c81fa 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm @@ -469,11 +469,12 @@ void MVKSwapchain::initSurfaceImages(const VkSwapchainCreateInfoKHR* pCreateInfo mvkEnableFlags(imgInfo.flags, VK_IMAGE_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT); } - bool deferImgMemAlloc = mvkAreAllFlagsEnabled(pCreateInfo->flags, VK_SWAPCHAIN_CREATE_DEFERRED_MEMORY_ALLOCATION_BIT_EXT); + // The VK_SWAPCHAIN_CREATE_DEFERRED_MEMORY_ALLOCATION_BIT_EXT flag is ignored, because + // swapchain image memory allocation is provided by a MTLDrawable, which is retrieved + // lazily, and hence is already deferred (or as deferred as we can make it). for (uint32_t imgIdx = 0; imgIdx < imgCnt; imgIdx++) { - _presentableImages.push_back(_device->createPresentableSwapchainImage(&imgInfo, this, imgIdx, - deferImgMemAlloc, NULL)); + _presentableImages.push_back(_device->createPresentableSwapchainImage(&imgInfo, this, imgIdx, nullptr)); } NSString* screenName = @"Main Screen"; @@ -543,7 +544,7 @@ VkResult MVKSwapchain::getPastPresentationTiming(uint32_t *pCount, VkPastPresent return res; } -void MVKSwapchain::recordPresentTime(MVKImagePresentInfo presentInfo, uint64_t actualPresentTime) { +void MVKSwapchain::recordPresentTime(MVKImagePresentInfo& presentInfo, uint64_t actualPresentTime) { std::lock_guard lock(_presentHistoryLock); if (_presentHistoryCount < kMaxPresentationHistory) { _presentHistoryCount++; From eabede8cdf8aac026656349a709d86e202afa16e Mon Sep 17 00:00:00 2001 From: Bill Hollings Date: Sun, 12 Feb 2023 20:14:18 +0000 Subject: [PATCH 4/6] Replace static inline with constexpr where possible. --- .../Commands/MVKCommandEncodingPool.mm | 2 +- MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h | 2 +- MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm | 2 +- MoltenVK/MoltenVK/Utility/MVKFoundation.h | 47 +++++++++---------- 4 files changed, 26 insertions(+), 27 deletions(-) diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.mm b/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.mm index 11e07cec..d861422b 100644 --- a/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.mm +++ b/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.mm @@ -109,7 +109,7 @@ id MVKCommandEncodingPool::getCmdFillBufferMTLComputePi MVK_ENC_REZ_ACCESS(_mtlFillBufferComputePipelineState, newCmdFillBufferMTLComputePipelineState(_commandPool)); } -static inline uint32_t getRenderpassLoadStoreStateIndex(MVKFormatType type) { +constexpr uint32_t getRenderpassLoadStoreStateIndex(MVKFormatType type) { switch (type) { case kMVKFormatColorHalf: case kMVKFormatColorFloat: diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h index 85ec750a..414e33a5 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h +++ b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h @@ -314,7 +314,7 @@ uint32_t mvkGetAttachments(const VkRenderingInfo* pRenderingInfo, VkClearValue clearValues[]); /** Returns whether the view mask uses multiview. */ -static inline bool mvkIsMultiview(uint32_t viewMask) { return viewMask != 0; } +constexpr bool mvkIsMultiview(uint32_t viewMask) { return viewMask != 0; } /** Returns whether the attachment is being used. */ bool mvkIsColorAttachmentUsed(const VkPipelineRenderingCreateInfo* pRendInfo, uint32_t colorAttIdx); diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm index 792c81fa..af87fd60 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm @@ -176,7 +176,7 @@ struct CIE1931XY { // According to D.3.28: // "[x and y] specify the normalized x and y chromaticity coordinates, respectively... // in normalized increments of 0.00002." -static inline uint16_t FloatToCIE1931Unorm(float x) { return OSSwapHostToBigInt16((uint16_t)(x * 100000 / 2)); } +constexpr uint16_t FloatToCIE1931Unorm(float x) { return OSSwapHostToBigInt16((uint16_t)(x * 100000 / 2)); } static inline CIE1931XY VkXYColorEXTToCIE1931XY(VkXYColorEXT xy) { return { FloatToCIE1931Unorm(xy.x), FloatToCIE1931Unorm(xy.y) }; } diff --git a/MoltenVK/MoltenVK/Utility/MVKFoundation.h b/MoltenVK/MoltenVK/Utility/MVKFoundation.h index d03b42f9..cbaaf685 100644 --- a/MoltenVK/MoltenVK/Utility/MVKFoundation.h +++ b/MoltenVK/MoltenVK/Utility/MVKFoundation.h @@ -137,8 +137,7 @@ static inline std::string mvkGetMoltenVKVersionString(uint32_t mvkVersion) { /** Returns whether the specified positive value is a power-of-two. */ template -static inline bool mvkIsPowerOfTwo(T value) { - // Test POT: (x != 0) && ((x & (x - 1)) == 0) +constexpr bool mvkIsPowerOfTwo(T value) { return value && ((value & (value - 1)) == 0); } @@ -148,7 +147,7 @@ static inline bool mvkIsPowerOfTwo(T value) { * that is larger than the specified value is returned. */ template -static inline T mvkEnsurePowerOfTwo(T value) { +constexpr T mvkEnsurePowerOfTwo(T value) { if (mvkIsPowerOfTwo(value)) { return value; } T pot = 1; @@ -163,7 +162,7 @@ static inline T mvkEnsurePowerOfTwo(T value) { * This implementation returns zero for both zero and one as inputs. */ template -static inline T mvkPowerOfTwoExponent(T value) { +constexpr T mvkPowerOfTwoExponent(T value) { T p2Value = mvkEnsurePowerOfTwo(value); // Count the trailing zeros @@ -184,7 +183,7 @@ static inline T mvkPowerOfTwoExponent(T value) { * This is a low level utility method. Usually you will use the convenience functions * mvkAlignAddress() and mvkAlignByteCount() to align addresses and offsets respectively. */ -static inline uintptr_t mvkAlignByteRef(uintptr_t byteRef, uintptr_t byteAlignment, bool alignDown = false) { +constexpr uintptr_t mvkAlignByteRef(uintptr_t byteRef, uintptr_t byteAlignment, bool alignDown = false) { if (byteAlignment == 0) { return byteRef; } assert(mvkIsPowerOfTwo(byteAlignment)); @@ -213,7 +212,7 @@ static inline void* mvkAlignAddress(void* address, uintptr_t byteAlignment, bool * which will be greater than or equal to the original offset if alignDown is false, or less * than or equal to the original offset if alignDown is true. */ -static inline uintptr_t mvkAlignByteCount(uintptr_t byteCount, uintptr_t byteAlignment, bool alignDown = false) { +constexpr uint64_t mvkAlignByteCount(uint64_t byteCount, uint64_t byteAlignment, bool alignDown = false) { return mvkAlignByteRef(byteCount, byteAlignment, alignDown); } @@ -254,22 +253,22 @@ static inline VkExtent2D mvkVkExtent2DFromVkExtent3D(VkExtent3D e) { return {e.w static inline VkExtent3D mvkVkExtent3DFromVkExtent2D(VkExtent2D e) { return {e.width, e.height, 1U }; } /** Returns whether the two Vulkan extents are equal by comparing their respective components. */ -static inline bool mvkVkExtent2DsAreEqual(VkExtent2D e1, VkExtent2D e2) { +constexpr bool mvkVkExtent2DsAreEqual(VkExtent2D e1, VkExtent2D e2) { return (e1.width == e2.width) && (e1.height == e2.height); } /** Returns whether the two Vulkan extents are equal by comparing their respective components. */ -static inline bool mvkVkExtent3DsAreEqual(VkExtent3D e1, VkExtent3D e2) { +constexpr bool mvkVkExtent3DsAreEqual(VkExtent3D e1, VkExtent3D e2) { return (e1.width == e2.width) && (e1.height == e2.height) && (e1.depth == e2.depth); } /** Returns whether the two Vulkan offsets are equal by comparing their respective components. */ -static inline bool mvkVkOffset2DsAreEqual(VkOffset2D os1, VkOffset2D os2) { +constexpr bool mvkVkOffset2DsAreEqual(VkOffset2D os1, VkOffset2D os2) { return (os1.x == os2.x) && (os1.y == os2.y); } /** Returns whether the two Vulkan offsets are equal by comparing their respective components. */ -static inline bool mvkVkOffset3DsAreEqual(VkOffset3D os1, VkOffset3D os2) { +constexpr bool mvkVkOffset3DsAreEqual(VkOffset3D os1, VkOffset3D os2) { return (os1.x == os2.x) && (os1.y == os2.y) && (os1.z == os2.z); } @@ -286,9 +285,9 @@ static inline VkOffset3D mvkVkOffset3DDifference(VkOffset3D minuend, VkOffset3D } /** Packs the four swizzle components into a single 32-bit word. */ -static inline uint32_t mvkPackSwizzle(VkComponentMapping components) { - return ((components.r & 0xFF) << 0) | ((components.g & 0xFF) << 8) | - ((components.b & 0xFF) << 16) | ((components.a & 0xFF) << 24); +constexpr uint32_t mvkPackSwizzle(VkComponentMapping components) { + return (((components.r & 0xFF) << 0) | ((components.g & 0xFF) << 8) | + ((components.b & 0xFF) << 16) | ((components.a & 0xFF) << 24)); } /** Unpacks a single 32-bit word containing four swizzle components. */ @@ -311,9 +310,9 @@ static inline VkComponentMapping mvkUnpackSwizzle(uint32_t packed) { * 3) Either cs1 or cs2 is VK_COMPONENT_SWIZZLE_MAX_ENUM, which is considered a wildcard, * and matches any value. */ -static inline bool mvkVKComponentSwizzlesMatch(VkComponentSwizzle cs1, - VkComponentSwizzle cs2, - VkComponentSwizzle csPos) { +constexpr bool mvkVKComponentSwizzlesMatch(VkComponentSwizzle cs1, + VkComponentSwizzle cs2, + VkComponentSwizzle csPos) { return ((cs1 == cs2) || ((cs1 == VK_COMPONENT_SWIZZLE_IDENTITY) && (cs2 == csPos)) || ((cs2 == VK_COMPONENT_SWIZZLE_IDENTITY) && (cs1 == csPos)) || @@ -328,7 +327,7 @@ static inline bool mvkVKComponentSwizzlesMatch(VkComponentSwizzle cs1, * A component value of VK_COMPONENT_SWIZZLE_MAX_ENUM is considered a wildcard and matches * any value in the corresponding component in the other mapping. */ -static inline bool mvkVkComponentMappingsMatch(VkComponentMapping cm1, VkComponentMapping cm2) { +constexpr bool mvkVkComponentMappingsMatch(VkComponentMapping cm1, VkComponentMapping cm2) { return (mvkVKComponentSwizzlesMatch(cm1.r, cm2.r, VK_COMPONENT_SWIZZLE_R) && mvkVKComponentSwizzlesMatch(cm1.g, cm2.g, VK_COMPONENT_SWIZZLE_G) && mvkVKComponentSwizzlesMatch(cm1.b, cm2.b, VK_COMPONENT_SWIZZLE_B) && @@ -342,7 +341,7 @@ static inline bool mvkVkComponentMappingsMatch(VkComponentMapping cm1, VkCompone #pragma mark Math /** Rounds the value to nearest integer using half-to-even rounding. */ -static inline double mvkRoundHalfToEven(const double val) { +constexpr double mvkRoundHalfToEven(const double val) { return val - std::remainder(val, 1.0); // remainder() uses half-to-even rounding } @@ -556,7 +555,7 @@ bool mvkAreEqual(const T* pV1, const T* pV2, size_t count = 1) { * otherwise returns false. This functionality is different than the char version of mvkAreEqual(), * which works on individual chars or char arrays, not strings. */ -static inline bool mvkStringsAreEqual(const char* pV1, const char* pV2, size_t count = 1) { +constexpr bool mvkStringsAreEqual(const char* pV1, const char* pV2, size_t count = 1) { return (pV1 && pV2) ? (strcmp(pV1, pV2) == 0) : false; } @@ -573,7 +572,7 @@ static inline bool mvkStringsAreEqual(const char* pV1, const char* pV2, size_t c * If the destination pointer is NULL, does nothing, and returns false. */ template -bool mvkSetOrClear(T* pDest, const T* pSrc) { +constexpr bool mvkSetOrClear(T* pDest, const T* pSrc) { if (pDest && pSrc) { *pDest = *pSrc; return true; @@ -595,17 +594,17 @@ void mvkDisableFlags(Tv& value, const Tm bitMask) { value = (Tv)(value & ~(Tv)bi /** Returns whether the specified value has ANY of the flags specified in bitMask enabled (set to 1). */ template -bool mvkIsAnyFlagEnabled(Tv value, const Tm bitMask) { return ((value & bitMask) != 0); } +constexpr bool mvkIsAnyFlagEnabled(Tv value, const Tm bitMask) { return ((value & bitMask) != 0); } /** Returns whether the specified value has ALL of the flags specified in bitMask enabled (set to 1). */ template -bool mvkAreAllFlagsEnabled(Tv value, const Tm bitMask) { return ((value & bitMask) == bitMask); } +constexpr bool mvkAreAllFlagsEnabled(Tv value, const Tm bitMask) { return ((value & bitMask) == bitMask); } /** Returns whether the specified value has ONLY one or more of the flags specified in bitMask enabled (set to 1), and none others. */ template -bool mvkIsOnlyAnyFlagEnabled(Tv value, const Tm bitMask) { return (mvkIsAnyFlagEnabled(value, bitMask) && ((value | bitMask) == bitMask)); } +constexpr bool mvkIsOnlyAnyFlagEnabled(Tv value, const Tm bitMask) { return (mvkIsAnyFlagEnabled(value, bitMask) && ((value | bitMask) == bitMask)); } /** Returns whether the specified value has ONLY ALL of the flags specified in bitMask enabled (set to 1), and none others. */ template -bool mvkAreOnlyAllFlagsEnabled(Tv value, const Tm bitMask) { return (value == bitMask); } +constexpr bool mvkAreOnlyAllFlagsEnabled(Tv value, const Tm bitMask) { return (value == bitMask); } From fbf5159ec2b5f5fa53ae0aad90d6879859f9fb9e Mon Sep 17 00:00:00 2001 From: Bill Hollings Date: Mon, 13 Feb 2023 23:17:00 -0500 Subject: [PATCH 5/6] 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. --- MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm | 4 +-- MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h | 31 +++++++++++--------- MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm | 19 ++++++++++-- MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.h | 4 +-- 4 files changed, 37 insertions(+), 21 deletions(-) diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm index 2612005f..11ab9443 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm @@ -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; } diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h index 926661aa..47a26d2a 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h +++ b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h @@ -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); +} diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm index af87fd60..1c0702af 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm @@ -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; diff --git a/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.h b/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.h index dcde22dc..7181b237 100644 --- a/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.h +++ b/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.h @@ -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; From e897f49185eff0cb0180c55213283b50878144cc Mon Sep 17 00:00:00 2001 From: Bill Hollings Date: Tue, 14 Feb 2023 15:36:43 -0500 Subject: [PATCH 6/6] Fixes to recent update, to suit building on older Xcode versions. - Revert mvkRoundHalfToEven() to static inline instead of constexpr, because internal use of std::remainder() is not constexpr until C++23. - Replace non-template constexpr functions with static constexpr. - Update Github CI to default to use Xcode 14.2, and macos-latest (which is now macos-12). --- .github/workflows/CI.yml | 4 +- .../Commands/MVKCommandEncodingPool.mm | 2 +- MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h | 2 +- MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm | 2 +- MoltenVK/MoltenVK/Utility/MVKFoundation.h | 40 +++++++++---------- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e7d9de8a..cb792195 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -15,9 +15,9 @@ jobs: build: strategy: matrix: - xcode: [ "14.1" ] + xcode: [ "14.2" ] platform: [ "macos", "maccat", "ios", "tvos" ] - os: [ "macos-12" ] + os: [ "macos-latest" ] upload_artifacts: [ true ] # additional specific configurations include: diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.mm b/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.mm index d861422b..1d8c893d 100644 --- a/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.mm +++ b/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.mm @@ -109,7 +109,7 @@ id MVKCommandEncodingPool::getCmdFillBufferMTLComputePi MVK_ENC_REZ_ACCESS(_mtlFillBufferComputePipelineState, newCmdFillBufferMTLComputePipelineState(_commandPool)); } -constexpr uint32_t getRenderpassLoadStoreStateIndex(MVKFormatType type) { +static constexpr uint32_t getRenderpassLoadStoreStateIndex(MVKFormatType type) { switch (type) { case kMVKFormatColorHalf: case kMVKFormatColorFloat: diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h index 414e33a5..25552b13 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h +++ b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h @@ -314,7 +314,7 @@ uint32_t mvkGetAttachments(const VkRenderingInfo* pRenderingInfo, VkClearValue clearValues[]); /** Returns whether the view mask uses multiview. */ -constexpr bool mvkIsMultiview(uint32_t viewMask) { return viewMask != 0; } +static constexpr bool mvkIsMultiview(uint32_t viewMask) { return viewMask != 0; } /** Returns whether the attachment is being used. */ bool mvkIsColorAttachmentUsed(const VkPipelineRenderingCreateInfo* pRendInfo, uint32_t colorAttIdx); diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm index 1c0702af..53aba660 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm @@ -188,7 +188,7 @@ struct CIE1931XY { // According to D.3.28: // "[x and y] specify the normalized x and y chromaticity coordinates, respectively... // in normalized increments of 0.00002." -constexpr uint16_t FloatToCIE1931Unorm(float x) { return OSSwapHostToBigInt16((uint16_t)(x * 100000 / 2)); } +static constexpr uint16_t FloatToCIE1931Unorm(float x) { return OSSwapHostToBigInt16((uint16_t)(x * 100000 / 2)); } static inline CIE1931XY VkXYColorEXTToCIE1931XY(VkXYColorEXT xy) { return { FloatToCIE1931Unorm(xy.x), FloatToCIE1931Unorm(xy.y) }; } diff --git a/MoltenVK/MoltenVK/Utility/MVKFoundation.h b/MoltenVK/MoltenVK/Utility/MVKFoundation.h index cbaaf685..3d123376 100644 --- a/MoltenVK/MoltenVK/Utility/MVKFoundation.h +++ b/MoltenVK/MoltenVK/Utility/MVKFoundation.h @@ -137,7 +137,7 @@ static inline std::string mvkGetMoltenVKVersionString(uint32_t mvkVersion) { /** Returns whether the specified positive value is a power-of-two. */ template -constexpr bool mvkIsPowerOfTwo(T value) { +static constexpr bool mvkIsPowerOfTwo(T value) { return value && ((value & (value - 1)) == 0); } @@ -147,7 +147,7 @@ constexpr bool mvkIsPowerOfTwo(T value) { * that is larger than the specified value is returned. */ template -constexpr T mvkEnsurePowerOfTwo(T value) { +static constexpr T mvkEnsurePowerOfTwo(T value) { if (mvkIsPowerOfTwo(value)) { return value; } T pot = 1; @@ -162,7 +162,7 @@ constexpr T mvkEnsurePowerOfTwo(T value) { * This implementation returns zero for both zero and one as inputs. */ template -constexpr T mvkPowerOfTwoExponent(T value) { +static constexpr T mvkPowerOfTwoExponent(T value) { T p2Value = mvkEnsurePowerOfTwo(value); // Count the trailing zeros @@ -183,7 +183,7 @@ constexpr T mvkPowerOfTwoExponent(T value) { * This is a low level utility method. Usually you will use the convenience functions * mvkAlignAddress() and mvkAlignByteCount() to align addresses and offsets respectively. */ -constexpr uintptr_t mvkAlignByteRef(uintptr_t byteRef, uintptr_t byteAlignment, bool alignDown = false) { +static constexpr uintptr_t mvkAlignByteRef(uintptr_t byteRef, uintptr_t byteAlignment, bool alignDown = false) { if (byteAlignment == 0) { return byteRef; } assert(mvkIsPowerOfTwo(byteAlignment)); @@ -212,7 +212,7 @@ static inline void* mvkAlignAddress(void* address, uintptr_t byteAlignment, bool * which will be greater than or equal to the original offset if alignDown is false, or less * than or equal to the original offset if alignDown is true. */ -constexpr uint64_t mvkAlignByteCount(uint64_t byteCount, uint64_t byteAlignment, bool alignDown = false) { +static constexpr uint64_t mvkAlignByteCount(uint64_t byteCount, uint64_t byteAlignment, bool alignDown = false) { return mvkAlignByteRef(byteCount, byteAlignment, alignDown); } @@ -253,22 +253,22 @@ static inline VkExtent2D mvkVkExtent2DFromVkExtent3D(VkExtent3D e) { return {e.w static inline VkExtent3D mvkVkExtent3DFromVkExtent2D(VkExtent2D e) { return {e.width, e.height, 1U }; } /** Returns whether the two Vulkan extents are equal by comparing their respective components. */ -constexpr bool mvkVkExtent2DsAreEqual(VkExtent2D e1, VkExtent2D e2) { +static constexpr bool mvkVkExtent2DsAreEqual(VkExtent2D e1, VkExtent2D e2) { return (e1.width == e2.width) && (e1.height == e2.height); } /** Returns whether the two Vulkan extents are equal by comparing their respective components. */ -constexpr bool mvkVkExtent3DsAreEqual(VkExtent3D e1, VkExtent3D e2) { +static constexpr bool mvkVkExtent3DsAreEqual(VkExtent3D e1, VkExtent3D e2) { return (e1.width == e2.width) && (e1.height == e2.height) && (e1.depth == e2.depth); } /** Returns whether the two Vulkan offsets are equal by comparing their respective components. */ -constexpr bool mvkVkOffset2DsAreEqual(VkOffset2D os1, VkOffset2D os2) { +static constexpr bool mvkVkOffset2DsAreEqual(VkOffset2D os1, VkOffset2D os2) { return (os1.x == os2.x) && (os1.y == os2.y); } /** Returns whether the two Vulkan offsets are equal by comparing their respective components. */ -constexpr bool mvkVkOffset3DsAreEqual(VkOffset3D os1, VkOffset3D os2) { +static constexpr bool mvkVkOffset3DsAreEqual(VkOffset3D os1, VkOffset3D os2) { return (os1.x == os2.x) && (os1.y == os2.y) && (os1.z == os2.z); } @@ -285,7 +285,7 @@ static inline VkOffset3D mvkVkOffset3DDifference(VkOffset3D minuend, VkOffset3D } /** Packs the four swizzle components into a single 32-bit word. */ -constexpr uint32_t mvkPackSwizzle(VkComponentMapping components) { +static constexpr uint32_t mvkPackSwizzle(VkComponentMapping components) { return (((components.r & 0xFF) << 0) | ((components.g & 0xFF) << 8) | ((components.b & 0xFF) << 16) | ((components.a & 0xFF) << 24)); } @@ -310,7 +310,7 @@ static inline VkComponentMapping mvkUnpackSwizzle(uint32_t packed) { * 3) Either cs1 or cs2 is VK_COMPONENT_SWIZZLE_MAX_ENUM, which is considered a wildcard, * and matches any value. */ -constexpr bool mvkVKComponentSwizzlesMatch(VkComponentSwizzle cs1, +static constexpr bool mvkVKComponentSwizzlesMatch(VkComponentSwizzle cs1, VkComponentSwizzle cs2, VkComponentSwizzle csPos) { return ((cs1 == cs2) || @@ -327,7 +327,7 @@ constexpr bool mvkVKComponentSwizzlesMatch(VkComponentSwizzle cs1, * A component value of VK_COMPONENT_SWIZZLE_MAX_ENUM is considered a wildcard and matches * any value in the corresponding component in the other mapping. */ -constexpr bool mvkVkComponentMappingsMatch(VkComponentMapping cm1, VkComponentMapping cm2) { +static constexpr bool mvkVkComponentMappingsMatch(VkComponentMapping cm1, VkComponentMapping cm2) { return (mvkVKComponentSwizzlesMatch(cm1.r, cm2.r, VK_COMPONENT_SWIZZLE_R) && mvkVKComponentSwizzlesMatch(cm1.g, cm2.g, VK_COMPONENT_SWIZZLE_G) && mvkVKComponentSwizzlesMatch(cm1.b, cm2.b, VK_COMPONENT_SWIZZLE_B) && @@ -341,8 +341,8 @@ constexpr bool mvkVkComponentMappingsMatch(VkComponentMapping cm1, VkComponentMa #pragma mark Math /** Rounds the value to nearest integer using half-to-even rounding. */ -constexpr double mvkRoundHalfToEven(const double val) { - return val - std::remainder(val, 1.0); // remainder() uses half-to-even rounding +static inline double mvkRoundHalfToEven(const double val) { + return val - std::remainder(val, 1.0); // remainder() uses half-to-even rounding, and unfortunately isn't constexpr until C++23. } /** Returns whether the value will fit inside the numeric type. */ @@ -555,7 +555,7 @@ bool mvkAreEqual(const T* pV1, const T* pV2, size_t count = 1) { * otherwise returns false. This functionality is different than the char version of mvkAreEqual(), * which works on individual chars or char arrays, not strings. */ -constexpr bool mvkStringsAreEqual(const char* pV1, const char* pV2, size_t count = 1) { +static constexpr bool mvkStringsAreEqual(const char* pV1, const char* pV2, size_t count = 1) { return (pV1 && pV2) ? (strcmp(pV1, pV2) == 0) : false; } @@ -572,7 +572,7 @@ constexpr bool mvkStringsAreEqual(const char* pV1, const char* pV2, size_t count * If the destination pointer is NULL, does nothing, and returns false. */ template -constexpr bool mvkSetOrClear(T* pDest, const T* pSrc) { +static constexpr bool mvkSetOrClear(T* pDest, const T* pSrc) { if (pDest && pSrc) { *pDest = *pSrc; return true; @@ -594,17 +594,17 @@ void mvkDisableFlags(Tv& value, const Tm bitMask) { value = (Tv)(value & ~(Tv)bi /** Returns whether the specified value has ANY of the flags specified in bitMask enabled (set to 1). */ template -constexpr bool mvkIsAnyFlagEnabled(Tv value, const Tm bitMask) { return ((value & bitMask) != 0); } +static constexpr bool mvkIsAnyFlagEnabled(Tv value, const Tm bitMask) { return ((value & bitMask) != 0); } /** Returns whether the specified value has ALL of the flags specified in bitMask enabled (set to 1). */ template -constexpr bool mvkAreAllFlagsEnabled(Tv value, const Tm bitMask) { return ((value & bitMask) == bitMask); } +static constexpr bool mvkAreAllFlagsEnabled(Tv value, const Tm bitMask) { return ((value & bitMask) == bitMask); } /** Returns whether the specified value has ONLY one or more of the flags specified in bitMask enabled (set to 1), and none others. */ template -constexpr bool mvkIsOnlyAnyFlagEnabled(Tv value, const Tm bitMask) { return (mvkIsAnyFlagEnabled(value, bitMask) && ((value | bitMask) == bitMask)); } +static constexpr bool mvkIsOnlyAnyFlagEnabled(Tv value, const Tm bitMask) { return (mvkIsAnyFlagEnabled(value, bitMask) && ((value | bitMask) == bitMask)); } /** Returns whether the specified value has ONLY ALL of the flags specified in bitMask enabled (set to 1), and none others. */ template -constexpr bool mvkAreOnlyAllFlagsEnabled(Tv value, const Tm bitMask) { return (value == bitMask); } +static constexpr bool mvkAreOnlyAllFlagsEnabled(Tv value, const Tm bitMask) { return (value == bitMask); }