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; }