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.
This commit is contained in:
Bill Hollings 2023-02-02 23:00:37 -05:00
parent e8885a24e8
commit fabad21405
24 changed files with 474 additions and 224 deletions

View File

@ -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)*

View File

@ -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

View File

@ -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) {

View File

@ -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

View File

@ -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);

View File

@ -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<VkPresentModeKHR> 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);
}

View File

@ -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)

View File

@ -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<MTLCommandBuffer> mtlCmdBuff,
MVKPresentTimingInfo presentTimingInfo);
void presentCAMetalDrawable(id<MTLCommandBuffer> 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<CAMetalDrawable> getCAMetalDrawable() override;
void addPresentedHandler(id<CAMetalDrawable> mtlDrawable, MVKPresentTimingInfo presentTimingInfo);
void addPresentedHandler(id<CAMetalDrawable> 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<MTLCommandBuffer> mtlCmdBuff);
void signalPresentationSemaphore(const MVKSwapchainSignaler& signaler, id<MTLCommandBuffer> mtlCmdBuff);
static void markAsTracked(const MVKSwapchainSignaler& signaler);
static void unmarkAsTracked(const MVKSwapchainSignaler& signaler);
void untrackAllSignalers();
void renderWatermark(id<MTLCommandBuffer> mtlCmdBuff);
id<CAMetalDrawable> _mtlDrawable;

View File

@ -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<mutex> lock(_availabilityLock);
// If present, signal the semaphore for the first waiter for the given image.
static void signalPresentationSemaphore(const MVKSwapchainSignaler& signaler, id<MTLCommandBuffer> 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<MTLCommandBuffer> 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<MTLCommandBuffer> 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<MTLCommandBuffer> 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<mutex> lock(_availabilityLock);
if ( !_availability.isAvailable ) {
unmarkAsTracked(_preSignaler);
for (auto& sig : _availabilitySignalers) {
unmarkAsTracked(sig);
}
}
}
#pragma mark Metal
id<CAMetalDrawable> MVKPresentableSwapchainImage::getCAMetalDrawable() {
while ( !_mtlDrawable ) {
@autoreleasepool { // Reclaim auto-released drawable object before end of loop
@ -1318,8 +1296,7 @@ id<CAMetalDrawable> MVKPresentableSwapchainImage::getCAMetalDrawable() {
// Present the drawable and make myself available only once the command buffer has completed.
void MVKPresentableSwapchainImage::presentCAMetalDrawable(id<MTLCommandBuffer> mtlCmdBuff,
MVKPresentTimingInfo presentTimingInfo) {
MVKImagePresentInfo presentInfo) {
lock_guard<mutex> lock(_availabilityLock);
_swapchain->willPresentSurface(getMTLTexture(0), mtlCmdBuff);
@ -1330,10 +1307,15 @@ void MVKPresentableSwapchainImage::presentCAMetalDrawable(id<MTLCommandBuffer> m
// Attach present handler before presenting to avoid race condition.
id<CAMetalDrawable> mtlDrwbl = getCAMetalDrawable();
[mtlCmdBuff addScheduledHandler: ^(id<MTLCommandBuffer> 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<MTLCommandBuffer> 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<MTLCommandBuffer> m
[mtlDrwbl release];
makeAvailable(signaler);
release();
if (presentInfo.fence) { presentInfo.fence->signal(); }
}];
signalPresentationSemaphore(signaler, mtlCmdBuff);
}
void MVKPresentableSwapchainImage::addPresentedHandler(id<CAMetalDrawable> 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<CAMetalDrawable> 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<mutex> 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<CAMetalDrawable> 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<mutex> 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<mutex> lock(_availabilityLock);
signalAndUnmarkAsTracked(signaler);
}
// Signal, untrack, and release any signalers that are tracking.
void MVKPresentableSwapchainImage::makeAvailable() {
lock_guard<mutex> 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();
}

View File

@ -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() {

View File

@ -272,6 +272,6 @@ public:
protected:
void stopAutoGPUCapture();
MVKSmallVector<MVKPresentTimingInfo, 4> _presentInfo;
MVKSmallVector<MVKImagePresentInfo, 4> _presentInfo;
};

View File

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

View File

@ -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<NSObject> obj = (id<NSObject>)pCreateInfo->pView;

View File

@ -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> mtlTexture, id<MTLCommandBuffer> mtlCmdBuff);
void renderWatermark(id<MTLTexture> mtlTexture, id<MTLCommandBuffer> 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<MVKPresentableSwapchainImage*, kMVKMaxSwapchainImageCount> _presentableImages;
std::atomic<uint64_t> _currentAcquisitionID;
uint64_t _lastFrameTime;
uint32_t _currentPerfLogFrameCount;
std::atomic<bool> _surfaceLost;
MVKBlockObserver* _layerObserver;
std::mutex _layerLock;
MVKSmallVector<VkPresentModeKHR, 2> _compatiblePresentModes;
static const int kMaxPresentationHistory = 60;
VkPastPresentationTimingGOOGLE _presentTimingHistory[kMaxPresentationHistory];
uint32_t _presentHistoryCount;
uint32_t _presentHistoryIndex;
uint32_t _presentHistoryHeadIndex;
std::atomic<uint64_t> _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<bool> _surfaceLost = false;
bool _isDeliberatelyScaled = false;
};

View File

@ -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<std::mutex> 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;

View File

@ -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)

View File

@ -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.
*

View File

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

View File

@ -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);

View File

@ -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. */

View File

@ -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<typename T, typename Tval>
const bool mvkFits(const Tval& val) {

View File

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

View File

@ -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

View File

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