Merge pull request #1835 from billhollings/swapchain-mtce1

Add support for VK_EXT_swapchain_maintenance1 and VK_EXT_surface_maintenance1
This commit is contained in:
Bill Hollings 2023-02-14 22:55:42 +01:00 committed by GitHub
commit 0ad6d72bbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 600 additions and 311 deletions

View File

@ -15,9 +15,9 @@ jobs:
build:
strategy:
matrix:
xcode: [ "14.1" ]
xcode: [ "14.2" ]
platform: [ "macos", "maccat", "ios", "tvos" ]
os: [ "macos-12" ]
os: [ "macos-latest" ]
upload_artifacts: [ true ]
# additional specific configurations
include:

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,10 +18,16 @@ 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.
- Work around problems with using explicit LoD with arrayed depth images
on Apple Silicon.
- Detect when size of surface has changed under the covers.
- Change rounding of surface size provided by Metal from truncation to rounding-with-half-to-even.
- Queue submissions retain wait semaphores until `MTLCommandBuffer` finishes.
- Work around problems with using explicit LoD with arrayed depth images on Apple Silicon.
- Update `VK_MVK_MOLTENVK_SPEC_VERSION` to version `37`.
@ -49,6 +55,7 @@ Released 2023/01/23
- MSL: Fix potentially uninitialized warnings.
MoltenVK 1.2.1
--------------

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

@ -56,7 +56,7 @@ typedef unsigned long MTLArgumentBuffersTier;
#define MVK_MAKE_VERSION(major, minor, patch) (((major) * 10000) + ((minor) * 100) + (patch))
#define MVK_VERSION MVK_MAKE_VERSION(MVK_VERSION_MAJOR, MVK_VERSION_MINOR, MVK_VERSION_PATCH)
#define VK_MVK_MOLTENVK_SPEC_VERSION 36
#define VK_MVK_MOLTENVK_SPEC_VERSION 37
#define VK_MVK_MOLTENVK_EXTENSION_NAME "VK_MVK_moltenvk"
/** Identifies the level of logging MoltenVK should be limited to outputting. */
@ -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

@ -109,7 +109,7 @@ id<MTLComputePipelineState> MVKCommandEncodingPool::getCmdFillBufferMTLComputePi
MVK_ENC_REZ_ACCESS(_mtlFillBufferComputePipelineState, newCmdFillBufferMTLComputePipelineState(_commandPool));
}
static inline uint32_t getRenderpassLoadStoreStateIndex(MVKFormatType type) {
static constexpr uint32_t getRenderpassLoadStoreStateIndex(MVKFormatType type) {
switch (type) {
case kMVKFormatColorHalf:
case kMVKFormatColorFloat:

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.

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 = mvkGetNaturalExtent(mtlLayer);
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;
}
@ -1277,7 +1389,7 @@ VkResult MVKPhysicalDevice::getPresentRectangles(MVKSurface* surface,
*pRectCount = 1;
pRects[0].offset = { 0, 0 };
pRects[0].extent = mvkVkExtent2DFromCGSize(mtlLayer.naturalDrawableSizeMVK);
pRects[0].extent = mvkGetNaturalExtent(mtlLayer);
return VK_SUCCESS;
}
@ -3462,7 +3574,8 @@ MVKPresentableSwapchainImage* MVKDevice::createPresentableSwapchainImage(const V
MVKSwapchain* swapchain,
uint32_t swapchainIndex,
const VkAllocationCallbacks* pAllocator) {
MVKPresentableSwapchainImage* mvkImg = new MVKPresentableSwapchainImage(this, pCreateInfo, swapchain, swapchainIndex);
MVKPresentableSwapchainImage* mvkImg = new MVKPresentableSwapchainImage(this, pCreateInfo,
swapchain, swapchainIndex);
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,16 +449,13 @@ 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
MVKPresentableSwapchainImage(MVKDevice* device,
const VkImageCreateInfo* pCreateInfo,
MVKSwapchain* swapchain,
uint32_t swapchainIndex);
MVKPresentableSwapchainImage(MVKDevice* device, const VkImageCreateInfo* pCreateInfo,
MVKSwapchain* swapchain, uint32_t swapchainIndex);
~MVKPresentableSwapchainImage() override;
@ -464,16 +463,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();
@ -1357,21 +1338,28 @@ void MVKPresentableSwapchainImage::presentCAMetalDrawable(id<MTLCommandBuffer> m
_availabilitySignalers.erase(sigIter);
}
// Ensure this image and the drawable are not destroyed while awaiting MTLCommandBuffer completion.
// We retain the drawable separately because new drawable might be acquired by this image by then.
// Ensure this image, the drawable, and the present fence are not destroyed while
// awaiting MTLCommandBuffer completion. We retain the drawable separately because
// a new drawable might be acquired by this image by then.
retain();
[mtlDrwbl retain];
auto* fence = presentInfo.fence;
if (fence) { fence->retain(); }
[mtlCmdBuff addCompletedHandler: ^(id<MTLCommandBuffer> mcb) {
[mtlDrwbl release];
makeAvailable(signaler);
release();
if (fence) {
fence->signal();
fence->release();
}
}];
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 +1367,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 +1380,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,6 +1392,29 @@ 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
@ -1421,10 +1432,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

@ -189,6 +189,8 @@ public:
protected:
friend class MVKQueue;
virtual void finish() = 0;
MVKQueue* _queue;
MVKSmallVector<std::pair<MVKSemaphore*, uint64_t>> _waitSemaphores;
};
@ -213,7 +215,7 @@ protected:
id<MTLCommandBuffer> getActiveMTLCommandBuffer();
void setActiveMTLCommandBuffer(id<MTLCommandBuffer> mtlCmdBuff);
void commitActiveMTLCommandBuffer(bool signalCompletion = false);
virtual void finish();
void finish() override;
virtual void submitCommandBuffers() {}
MVKSmallVector<std::pair<MVKSemaphore*, uint64_t>> _signalSemaphores;
@ -270,8 +272,8 @@ public:
const VkPresentInfoKHR* pPresentInfo);
protected:
void stopAutoGPUCapture();
void finish() override;
MVKSmallVector<MVKPresentTimingInfo, 4> _presentInfo;
MVKSmallVector<MVKImagePresentInfo, 4> _presentInfo;
};

View File

@ -290,11 +290,15 @@ MVKQueueSubmission::MVKQueueSubmission(MVKQueue* queue,
_waitSemaphores.reserve(waitSemaphoreCount);
for (uint32_t i = 0; i < waitSemaphoreCount; i++) {
_waitSemaphores.push_back(make_pair((MVKSemaphore*)pWaitSemaphores[i], (uint64_t)0));
auto* sem4 = (MVKSemaphore*)pWaitSemaphores[i];
sem4->retain();
uint64_t sem4Val = 0;
_waitSemaphores.emplace_back(sem4, sem4Val);
}
}
MVKQueueSubmission::~MVKQueueSubmission() {
for (auto s : _waitSemaphores) { s.first->release(); }
_queue->release();
}
@ -530,47 +534,61 @@ void MVKQueueFullCommandBufferSubmission<N>::finish() {
#pragma mark -
#pragma mark MVKQueuePresentSurfaceSubmission
// If the semaphores are encodable, wait on them by encoding them on the MTLCommandBuffer before presenting.
// If the semaphores are not encodable, wait on them inline after presenting.
// The semaphores know what to do.
void MVKQueuePresentSurfaceSubmission::execute() {
// If the semaphores are encodable, wait on them by encoding them on the MTLCommandBuffer before presenting.
// If the semaphores are not encodable, wait on them inline after presenting.
// The semaphores know what to do.
id<MTLCommandBuffer> mtlCmdBuff = _queue->getMTLCommandBuffer(kMVKCommandUseQueuePresent);
[mtlCmdBuff enqueue];
for (auto& ws : _waitSemaphores) { ws.first->encodeWait(mtlCmdBuff, 0); }
// Add completion handler that will destroy this submission only once the MTLCommandBuffer
// is finished with the resources retained here, including the wait semaphores.
// Completion handlers are also added in presentCAMetalDrawable() to retain the swapchain images.
[mtlCmdBuff addCompletedHandler: ^(id<MTLCommandBuffer> mcb) {
this->finish();
}];
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];
}
// Let Xcode know the current frame is done, then start a new frame
void MVKQueuePresentSurfaceSubmission::finish() {
// Let Xcode know the current frame is done, then start a new frame,
// and if auto GPU capture is active, and it's time to stop it, do so.
auto cs = _queue->_submissionCaptureScope;
cs->endScope();
cs->beginScope();
stopAutoGPUCapture();
this->destroy();
}
void MVKQueuePresentSurfaceSubmission::stopAutoGPUCapture() {
if (_queue->_queueFamily->getIndex() == mvkConfig().defaultGPUCaptureScopeQueueFamilyIndex &&
_queue->_index == mvkConfig().defaultGPUCaptureScopeQueueIndex) {
_queue->getDevice()->stopAutoGPUCapture(MVK_CONFIG_AUTO_GPU_CAPTURE_SCOPE_FRAME);
}
this->destroy();
}
MVKQueuePresentSurfaceSubmission::MVKQueuePresentSurfaceSubmission(MVKQueue* queue,
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 +597,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

@ -314,7 +314,7 @@ uint32_t mvkGetAttachments(const VkRenderingInfo* pRenderingInfo,
VkClearValue clearValues[]);
/** Returns whether the view mask uses multiview. */
static inline bool mvkIsMultiview(uint32_t viewMask) { return viewMask != 0; }
static constexpr bool mvkIsMultiview(uint32_t viewMask) { return viewMask != 0; }
/** Returns whether the attachment is being used. */
bool mvkIsColorAttachmentUsed(const VkPipelineRenderingCreateInfo* pRendInfo, uint32_t colorAttIdx);

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,27 @@ 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. */
bool hasOptimalSurface();
/** 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 +98,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 +111,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 +121,39 @@ 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 = nullptr;
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;
VkExtent2D _mtlLayerDrawableExtent = {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;
};
#pragma mark -
#pragma mark Support functions
/**
* Returns the natural extent of the CAMetalLayer.
*
* The natural extent is the size of the bounds property of the layer,
* multiplied by the contentsScale property of the layer, rounded
* to nearest integer using half-to-even rounding.
*/
static inline VkExtent2D mvkGetNaturalExtent(CAMetalLayer* mtlLayer) {
return mvkVkExtent2DFromCGSize(mtlLayer.naturalDrawableSizeMVK);
}

View File

@ -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,
@ -107,6 +115,18 @@ uint64_t MVKSwapchain::getNextAcquisitionID() { return ++_currentAcquisitionID;
void MVKSwapchain::releaseUndisplayedSurfaces() {}
// This swapchain is optimally sized for the surface if the app has specified deliberate
// swapchain scaling, or the CAMetalLayer drawableSize has not changed since the swapchain
// was created, and the CAMetalLayer will not need to be scaled when composited.
bool MVKSwapchain::hasOptimalSurface() {
if (_isDeliberatelyScaled) { return true; }
VkExtent2D drawExtent = mvkVkExtent2DFromCGSize(_mtlLayer.drawableSize);
return (mvkVkExtent2DsAreEqual(drawExtent, _mtlLayerDrawableExtent) &&
mvkVkExtent2DsAreEqual(drawExtent, mvkGetNaturalExtent(_mtlLayer)));
}
#pragma mark Rendering
// Called automatically when a swapchain image is about to be presented to the surface by the queue.
@ -168,7 +188,7 @@ struct CIE1931XY {
// According to D.3.28:
// "[x and y] specify the normalized x and y chromaticity coordinates, respectively...
// in normalized increments of 0.00002."
static inline uint16_t FloatToCIE1931Unorm(float x) { return OSSwapHostToBigInt16((uint16_t)(x * 100000 / 2)); }
static constexpr uint16_t FloatToCIE1931Unorm(float x) { return OSSwapHostToBigInt16((uint16_t)(x * 100000 / 2)); }
static inline CIE1931XY VkXYColorEXTToCIE1931XY(VkXYColorEXT xy) {
return { FloatToCIE1931Unorm(xy.x), FloatToCIE1931Unorm(xy.y) };
}
@ -227,19 +247,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 +285,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 +339,23 @@ 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 the extent to later detect if it has changed under the covers,
// and set the drawable size of the CAMetalLayer from the extent.
_mtlLayerDrawableExtent = pCreateInfo->imageExtent;
_mtlLayer.drawableSize = mvkCGSizeFromVkExtent2D(_mtlLayerDrawableExtent);
if (pCreateInfo->compositeAlpha != VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) {
_mtlLayer.opaque = pCreateInfo->compositeAlpha == VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
}
@ -330,7 +415,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 +482,12 @@ void MVKSwapchain::initSurfaceImages(const VkSwapchainCreateInfoKHR* pCreateInfo
mvkEnableFlags(imgInfo.flags, VK_IMAGE_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT);
}
// The VK_SWAPCHAIN_CREATE_DEFERRED_MEMORY_ALLOCATION_BIT_EXT flag is ignored, because
// swapchain image memory allocation is provided by a MTLDrawable, which is retrieved
// lazily, and hence is already deferred (or as deferred as we can make it).
for (uint32_t imgIdx = 0; imgIdx < imgCnt; imgIdx++) {
_presentableImages.push_back(_device->createPresentableSwapchainImage(&imgInfo, this, imgIdx, NULL));
_presentableImages.push_back(_device->createPresentableSwapchainImage(&imgInfo, this, imgIdx, nullptr));
}
NSString* screenName = @"Main Screen";
@ -469,7 +557,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 +566,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 multiplied by the contentsScale property.
*/
@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

@ -24,49 +24,57 @@
const char* mvkVkResultName(VkResult vkResult) {
switch (vkResult) {
CASE_STRINGIFY(VK_SUCCESS);
CASE_STRINGIFY(VK_NOT_READY);
CASE_STRINGIFY(VK_TIMEOUT);
CASE_STRINGIFY(VK_EVENT_SET);
CASE_STRINGIFY(VK_EVENT_RESET);
CASE_STRINGIFY(VK_INCOMPLETE);
CASE_STRINGIFY(VK_SUCCESS);
CASE_STRINGIFY(VK_NOT_READY);
CASE_STRINGIFY(VK_TIMEOUT);
CASE_STRINGIFY(VK_EVENT_SET);
CASE_STRINGIFY(VK_EVENT_RESET);
CASE_STRINGIFY(VK_INCOMPLETE);
CASE_STRINGIFY(VK_ERROR_OUT_OF_HOST_MEMORY);
CASE_STRINGIFY(VK_ERROR_OUT_OF_DEVICE_MEMORY);
CASE_STRINGIFY(VK_ERROR_INITIALIZATION_FAILED);
CASE_STRINGIFY(VK_ERROR_DEVICE_LOST);
CASE_STRINGIFY(VK_ERROR_MEMORY_MAP_FAILED);
CASE_STRINGIFY(VK_ERROR_LAYER_NOT_PRESENT);
CASE_STRINGIFY(VK_ERROR_EXTENSION_NOT_PRESENT);
CASE_STRINGIFY(VK_ERROR_FEATURE_NOT_PRESENT);
CASE_STRINGIFY(VK_ERROR_INCOMPATIBLE_DRIVER);
CASE_STRINGIFY(VK_ERROR_TOO_MANY_OBJECTS);
CASE_STRINGIFY(VK_ERROR_FORMAT_NOT_SUPPORTED);
CASE_STRINGIFY(VK_ERROR_FRAGMENTED_POOL);
CASE_STRINGIFY(VK_ERROR_OUT_OF_HOST_MEMORY);
CASE_STRINGIFY(VK_ERROR_OUT_OF_DEVICE_MEMORY);
CASE_STRINGIFY(VK_ERROR_INITIALIZATION_FAILED);
CASE_STRINGIFY(VK_ERROR_DEVICE_LOST);
CASE_STRINGIFY(VK_ERROR_MEMORY_MAP_FAILED);
CASE_STRINGIFY(VK_ERROR_LAYER_NOT_PRESENT);
CASE_STRINGIFY(VK_ERROR_EXTENSION_NOT_PRESENT);
CASE_STRINGIFY(VK_ERROR_FEATURE_NOT_PRESENT);
CASE_STRINGIFY(VK_ERROR_INCOMPATIBLE_DRIVER);
CASE_STRINGIFY(VK_ERROR_TOO_MANY_OBJECTS);
CASE_STRINGIFY(VK_ERROR_FORMAT_NOT_SUPPORTED);
CASE_STRINGIFY(VK_ERROR_FRAGMENTED_POOL);
CASE_STRINGIFY(VK_ERROR_UNKNOWN);
CASE_STRINGIFY(VK_ERROR_OUT_OF_POOL_MEMORY);
CASE_STRINGIFY(VK_ERROR_INVALID_EXTERNAL_HANDLE);
CASE_STRINGIFY(VK_ERROR_FRAGMENTATION);
CASE_STRINGIFY(VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS);
CASE_STRINGIFY(VK_ERROR_UNKNOWN);
CASE_STRINGIFY(VK_ERROR_OUT_OF_POOL_MEMORY);
CASE_STRINGIFY(VK_ERROR_INVALID_EXTERNAL_HANDLE);
CASE_STRINGIFY(VK_ERROR_FRAGMENTATION);
CASE_STRINGIFY(VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS);
CASE_STRINGIFY(VK_PIPELINE_COMPILE_REQUIRED);
CASE_STRINGIFY(VK_ERROR_SURFACE_LOST_KHR);
CASE_STRINGIFY(VK_ERROR_NATIVE_WINDOW_IN_USE_KHR);
CASE_STRINGIFY(VK_SUBOPTIMAL_KHR);
CASE_STRINGIFY(VK_ERROR_OUT_OF_DATE_KHR);
CASE_STRINGIFY(VK_ERROR_INCOMPATIBLE_DISPLAY_KHR);
CASE_STRINGIFY(VK_ERROR_SURFACE_LOST_KHR);
CASE_STRINGIFY(VK_ERROR_NATIVE_WINDOW_IN_USE_KHR);
CASE_STRINGIFY(VK_SUBOPTIMAL_KHR);
CASE_STRINGIFY(VK_ERROR_OUT_OF_DATE_KHR);
CASE_STRINGIFY(VK_ERROR_INCOMPATIBLE_DISPLAY_KHR);
CASE_STRINGIFY(VK_ERROR_VALIDATION_FAILED_EXT);
CASE_STRINGIFY(VK_ERROR_INVALID_SHADER_NV);
CASE_STRINGIFY(VK_ERROR_VALIDATION_FAILED_EXT);
CASE_STRINGIFY(VK_ERROR_INVALID_SHADER_NV);
CASE_STRINGIFY(VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR);
CASE_STRINGIFY(VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT);
CASE_STRINGIFY(VK_ERROR_NOT_PERMITTED_EXT);
CASE_STRINGIFY(VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT);
CASE_STRINGIFY(VK_THREAD_IDLE_KHR);
CASE_STRINGIFY(VK_THREAD_DONE_KHR);
CASE_STRINGIFY(VK_OPERATION_DEFERRED_KHR);
CASE_STRINGIFY(VK_OPERATION_NOT_DEFERRED_KHR);
CASE_STRINGIFY(VK_PIPELINE_COMPILE_REQUIRED_EXT);
CASE_STRINGIFY(VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR);
CASE_STRINGIFY(VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR);
CASE_STRINGIFY(VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR);
CASE_STRINGIFY(VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR);
CASE_STRINGIFY(VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR);
CASE_STRINGIFY(VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT);
CASE_STRINGIFY(VK_ERROR_NOT_PERMITTED_KHR);
CASE_STRINGIFY(VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT);
CASE_STRINGIFY(VK_THREAD_IDLE_KHR);
CASE_STRINGIFY(VK_THREAD_DONE_KHR);
CASE_STRINGIFY(VK_OPERATION_DEFERRED_KHR);
CASE_STRINGIFY(VK_OPERATION_NOT_DEFERRED_KHR);
CASE_STRINGIFY(VK_ERROR_COMPRESSION_EXHAUSTED_EXT);
default: return "VK_UNKNOWN_VK_Result";
}

View File

@ -137,8 +137,7 @@ static inline std::string mvkGetMoltenVKVersionString(uint32_t mvkVersion) {
/** Returns whether the specified positive value is a power-of-two. */
template<typename T>
static inline bool mvkIsPowerOfTwo(T value) {
// Test POT: (x != 0) && ((x & (x - 1)) == 0)
static constexpr bool mvkIsPowerOfTwo(T value) {
return value && ((value & (value - 1)) == 0);
}
@ -148,7 +147,7 @@ static inline bool mvkIsPowerOfTwo(T value) {
* that is larger than the specified value is returned.
*/
template<typename T>
static inline T mvkEnsurePowerOfTwo(T value) {
static constexpr T mvkEnsurePowerOfTwo(T value) {
if (mvkIsPowerOfTwo(value)) { return value; }
T pot = 1;
@ -163,7 +162,7 @@ static inline T mvkEnsurePowerOfTwo(T value) {
* This implementation returns zero for both zero and one as inputs.
*/
template<typename T>
static inline T mvkPowerOfTwoExponent(T value) {
static constexpr T mvkPowerOfTwoExponent(T value) {
T p2Value = mvkEnsurePowerOfTwo(value);
// Count the trailing zeros
@ -184,7 +183,7 @@ static inline T mvkPowerOfTwoExponent(T value) {
* This is a low level utility method. Usually you will use the convenience functions
* mvkAlignAddress() and mvkAlignByteCount() to align addresses and offsets respectively.
*/
static inline uintptr_t mvkAlignByteRef(uintptr_t byteRef, uintptr_t byteAlignment, bool alignDown = false) {
static constexpr uintptr_t mvkAlignByteRef(uintptr_t byteRef, uintptr_t byteAlignment, bool alignDown = false) {
if (byteAlignment == 0) { return byteRef; }
assert(mvkIsPowerOfTwo(byteAlignment));
@ -213,7 +212,7 @@ static inline void* mvkAlignAddress(void* address, uintptr_t byteAlignment, bool
* which will be greater than or equal to the original offset if alignDown is false, or less
* than or equal to the original offset if alignDown is true.
*/
static inline uintptr_t mvkAlignByteCount(uintptr_t byteCount, uintptr_t byteAlignment, bool alignDown = false) {
static constexpr uint64_t mvkAlignByteCount(uint64_t byteCount, uint64_t byteAlignment, bool alignDown = false) {
return mvkAlignByteRef(byteCount, byteAlignment, alignDown);
}
@ -254,22 +253,22 @@ static inline VkExtent2D mvkVkExtent2DFromVkExtent3D(VkExtent3D e) { return {e.w
static inline VkExtent3D mvkVkExtent3DFromVkExtent2D(VkExtent2D e) { return {e.width, e.height, 1U }; }
/** Returns whether the two Vulkan extents are equal by comparing their respective components. */
static inline bool mvkVkExtent2DsAreEqual(VkExtent2D e1, VkExtent2D e2) {
static constexpr bool mvkVkExtent2DsAreEqual(VkExtent2D e1, VkExtent2D e2) {
return (e1.width == e2.width) && (e1.height == e2.height);
}
/** Returns whether the two Vulkan extents are equal by comparing their respective components. */
static inline bool mvkVkExtent3DsAreEqual(VkExtent3D e1, VkExtent3D e2) {
static constexpr bool mvkVkExtent3DsAreEqual(VkExtent3D e1, VkExtent3D e2) {
return (e1.width == e2.width) && (e1.height == e2.height) && (e1.depth == e2.depth);
}
/** Returns whether the two Vulkan offsets are equal by comparing their respective components. */
static inline bool mvkVkOffset2DsAreEqual(VkOffset2D os1, VkOffset2D os2) {
static constexpr bool mvkVkOffset2DsAreEqual(VkOffset2D os1, VkOffset2D os2) {
return (os1.x == os2.x) && (os1.y == os2.y);
}
/** Returns whether the two Vulkan offsets are equal by comparing their respective components. */
static inline bool mvkVkOffset3DsAreEqual(VkOffset3D os1, VkOffset3D os2) {
static constexpr bool mvkVkOffset3DsAreEqual(VkOffset3D os1, VkOffset3D os2) {
return (os1.x == os2.x) && (os1.y == os2.y) && (os1.z == os2.z);
}
@ -286,9 +285,9 @@ static inline VkOffset3D mvkVkOffset3DDifference(VkOffset3D minuend, VkOffset3D
}
/** Packs the four swizzle components into a single 32-bit word. */
static inline uint32_t mvkPackSwizzle(VkComponentMapping components) {
return ((components.r & 0xFF) << 0) | ((components.g & 0xFF) << 8) |
((components.b & 0xFF) << 16) | ((components.a & 0xFF) << 24);
static constexpr uint32_t mvkPackSwizzle(VkComponentMapping components) {
return (((components.r & 0xFF) << 0) | ((components.g & 0xFF) << 8) |
((components.b & 0xFF) << 16) | ((components.a & 0xFF) << 24));
}
/** Unpacks a single 32-bit word containing four swizzle components. */
@ -311,9 +310,9 @@ static inline VkComponentMapping mvkUnpackSwizzle(uint32_t packed) {
* 3) Either cs1 or cs2 is VK_COMPONENT_SWIZZLE_MAX_ENUM, which is considered a wildcard,
* and matches any value.
*/
static inline bool mvkVKComponentSwizzlesMatch(VkComponentSwizzle cs1,
VkComponentSwizzle cs2,
VkComponentSwizzle csPos) {
static constexpr bool mvkVKComponentSwizzlesMatch(VkComponentSwizzle cs1,
VkComponentSwizzle cs2,
VkComponentSwizzle csPos) {
return ((cs1 == cs2) ||
((cs1 == VK_COMPONENT_SWIZZLE_IDENTITY) && (cs2 == csPos)) ||
((cs2 == VK_COMPONENT_SWIZZLE_IDENTITY) && (cs1 == csPos)) ||
@ -328,7 +327,7 @@ static inline bool mvkVKComponentSwizzlesMatch(VkComponentSwizzle cs1,
* A component value of VK_COMPONENT_SWIZZLE_MAX_ENUM is considered a wildcard and matches
* any value in the corresponding component in the other mapping.
*/
static inline bool mvkVkComponentMappingsMatch(VkComponentMapping cm1, VkComponentMapping cm2) {
static constexpr bool mvkVkComponentMappingsMatch(VkComponentMapping cm1, VkComponentMapping cm2) {
return (mvkVKComponentSwizzlesMatch(cm1.r, cm2.r, VK_COMPONENT_SWIZZLE_R) &&
mvkVKComponentSwizzlesMatch(cm1.g, cm2.g, VK_COMPONENT_SWIZZLE_G) &&
mvkVKComponentSwizzlesMatch(cm1.b, cm2.b, VK_COMPONENT_SWIZZLE_B) &&
@ -339,11 +338,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, and unfortunately isn't constexpr until C++23.
}
/** Returns whether the value will fit inside the numeric type. */
template<typename T, typename Tval>
const bool mvkFits(const Tval& val) {
@ -554,7 +555,7 @@ bool mvkAreEqual(const T* pV1, const T* pV2, size_t count = 1) {
* otherwise returns false. This functionality is different than the char version of mvkAreEqual(),
* which works on individual chars or char arrays, not strings.
*/
static inline bool mvkStringsAreEqual(const char* pV1, const char* pV2, size_t count = 1) {
static constexpr bool mvkStringsAreEqual(const char* pV1, const char* pV2, size_t count = 1) {
return (pV1 && pV2) ? (strcmp(pV1, pV2) == 0) : false;
}
@ -571,7 +572,7 @@ static inline bool mvkStringsAreEqual(const char* pV1, const char* pV2, size_t c
* If the destination pointer is NULL, does nothing, and returns false.
*/
template<typename T>
bool mvkSetOrClear(T* pDest, const T* pSrc) {
static constexpr bool mvkSetOrClear(T* pDest, const T* pSrc) {
if (pDest && pSrc) {
*pDest = *pSrc;
return true;
@ -593,17 +594,17 @@ void mvkDisableFlags(Tv& value, const Tm bitMask) { value = (Tv)(value & ~(Tv)bi
/** Returns whether the specified value has ANY of the flags specified in bitMask enabled (set to 1). */
template<typename Tv, typename Tm>
bool mvkIsAnyFlagEnabled(Tv value, const Tm bitMask) { return ((value & bitMask) != 0); }
static constexpr bool mvkIsAnyFlagEnabled(Tv value, const Tm bitMask) { return ((value & bitMask) != 0); }
/** Returns whether the specified value has ALL of the flags specified in bitMask enabled (set to 1). */
template<typename Tv, typename Tm>
bool mvkAreAllFlagsEnabled(Tv value, const Tm bitMask) { return ((value & bitMask) == bitMask); }
static constexpr bool mvkAreAllFlagsEnabled(Tv value, const Tm bitMask) { return ((value & bitMask) == bitMask); }
/** Returns whether the specified value has ONLY one or more of the flags specified in bitMask enabled (set to 1), and none others. */
template<typename Tv, typename Tm>
bool mvkIsOnlyAnyFlagEnabled(Tv value, const Tm bitMask) { return (mvkIsAnyFlagEnabled(value, bitMask) && ((value | bitMask) == bitMask)); }
static constexpr bool mvkIsOnlyAnyFlagEnabled(Tv value, const Tm bitMask) { return (mvkIsAnyFlagEnabled(value, bitMask) && ((value | bitMask) == bitMask)); }
/** Returns whether the specified value has ONLY ALL of the flags specified in bitMask enabled (set to 1), and none others. */
template<typename Tv, typename Tm>
bool mvkAreOnlyAllFlagsEnabled(Tv value, const Tm bitMask) { return (value == bitMask); }
static constexpr bool mvkAreOnlyAllFlagsEnabled(Tv value, const Tm bitMask) { return (value == bitMask); }

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