Merge pull request #2086 from billhollings/VK_EXT_headless_surface

Add support for extension VK_EXT_headless_surface.
This commit is contained in:
Bill Hollings 2023-12-05 09:11:50 -05:00 committed by GitHub
commit e6a3886313
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 313 additions and 237 deletions

View File

@ -24,6 +24,9 @@
#include <limits>
#pragma mark -
#pragma mark Operating System versions
typedef float MVKOSVersion;
/*** Constant indicating unsupported functionality in an OS. */
@ -66,20 +69,31 @@ static inline bool mvkOSVersionIsAtLeast(MVKOSVersion macOSMinVer,
#endif
}
#pragma mark -
#pragma mark Timestamps
/**
* Returns a monotonic timestamp value for use in Vulkan and performance timestamping.
* Returns a monotonic tick value for use in Vulkan and performance timestamping.
*
* The returned value corresponds to the number of CPU "ticks" since the app was initialized.
*
* Calling this value twice, subtracting the first value from the second, and then multiplying
* the result by the value returned by mvkGetTimestampPeriod() will provide an indication of the
* number of nanoseconds between the two calls. The convenience function mvkGetElapsedMilliseconds()
* can be used to perform this calculation.
* The returned value corresponds to the number of CPU ticks since an arbitrary
* point in the past, and does not increment while the system is asleep.
*/
uint64_t mvkGetTimestamp();
/** Returns the number of nanoseconds between each increment of the value returned by mvkGetTimestamp(). */
double mvkGetTimestampPeriod();
/**
* Returns the number of runtime nanoseconds since an arbitrary point in the past,
* excluding any time spent while the system is asleep.
*
* This value corresponds to the timestamps returned by Metal presentation timings.
*/
uint64_t mvkGetRuntimeNanoseconds();
/**
* Returns the number of nanoseconds since an arbitrary point in the past,
* including any time spent while the system is asleep.
*/
uint64_t mvkGetContinuousNanoseconds();
/**
* Returns the number of nanoseconds elapsed between startTimestamp and endTimestamp,
@ -97,12 +111,6 @@ uint64_t mvkGetElapsedNanoseconds(uint64_t startTimestamp = 0, uint64_t endTimes
*/
double mvkGetElapsedMilliseconds(uint64_t startTimestamp = 0, uint64_t endTimestamp = 0);
/** Returns the current absolute time in nanoseconds. */
uint64_t mvkGetAbsoluteTime();
/** Ensures the block is executed on the main thread. */
void mvkDispatchToMainAndWait(dispatch_block_t block);
#pragma mark -
#pragma mark Process environment
@ -141,8 +149,12 @@ uint64_t mvkGetUsedMemorySize();
/** Returns the size of a page of host memory on this platform. */
uint64_t mvkGetHostMemoryPageSize();
#pragma mark -
#pragma mark Threading
/** Returns the amount of avaliable CPU cores. */
uint32_t mvkGetAvaliableCPUCores();
/** Ensures the block is executed on the main thread. */
void mvkDispatchToMainAndWait(dispatch_block_t block);

View File

@ -29,6 +29,10 @@
using namespace std;
#pragma mark -
#pragma mark Operating System versions
MVKOSVersion mvkOSVersion() {
static MVKOSVersion _mvkOSVersion = 0;
if ( !_mvkOSVersion ) {
@ -38,43 +42,35 @@ MVKOSVersion mvkOSVersion() {
return _mvkOSVersion;
}
static uint64_t _mvkTimestampBase;
static double _mvkTimestampPeriod;
#pragma mark -
#pragma mark Timestamps
static mach_timebase_info_data_t _mvkMachTimebase;
uint64_t mvkGetTimestamp() { return mach_absolute_time() - _mvkTimestampBase; }
uint64_t mvkGetTimestamp() { return mach_absolute_time(); }
double mvkGetTimestampPeriod() { return _mvkTimestampPeriod; }
uint64_t mvkGetRuntimeNanoseconds() { return mach_absolute_time() * _mvkMachTimebase.numer / _mvkMachTimebase.denom; }
uint64_t mvkGetContinuousNanoseconds() { return mach_continuous_time() * _mvkMachTimebase.numer / _mvkMachTimebase.denom; }
uint64_t mvkGetElapsedNanoseconds(uint64_t startTimestamp, uint64_t endTimestamp) {
if (endTimestamp == 0) { endTimestamp = mvkGetTimestamp(); }
return (endTimestamp - startTimestamp) * _mvkTimestampPeriod;
return (endTimestamp - startTimestamp) * _mvkMachTimebase.numer / _mvkMachTimebase.denom;
}
double mvkGetElapsedMilliseconds(uint64_t startTimestamp, uint64_t endTimestamp) {
return mvkGetElapsedNanoseconds(startTimestamp, endTimestamp) / 1e6;
}
uint64_t mvkGetAbsoluteTime() { return mach_continuous_time() * _mvkMachTimebase.numer / _mvkMachTimebase.denom; }
// Initialize timestamping capabilities on app startup.
//Called automatically when the framework is loaded and initialized.
// Initialize timestamp capabilities on app startup.
// Called automatically when the framework is loaded and initialized.
static bool _mvkTimestampsInitialized = false;
__attribute__((constructor)) static void MVKInitTimestamps() {
if (_mvkTimestampsInitialized ) { return; }
_mvkTimestampsInitialized = true;
_mvkTimestampBase = mach_absolute_time();
mach_timebase_info(&_mvkMachTimebase);
_mvkTimestampPeriod = (double)_mvkMachTimebase.numer / (double)_mvkMachTimebase.denom;
}
void mvkDispatchToMainAndWait(dispatch_block_t block) {
if (NSThread.isMainThread) {
block();
} else {
dispatch_sync(dispatch_get_main_queue(), block);
}
}
@ -145,6 +141,7 @@ uint64_t mvkGetUsedMemorySize() {
uint64_t mvkGetHostMemoryPageSize() { return sysconf(_SC_PAGESIZE); }
#pragma mark -
#pragma mark Threading
@ -152,3 +149,11 @@ uint64_t mvkGetHostMemoryPageSize() { return sysconf(_SC_PAGESIZE); }
uint32_t mvkGetAvaliableCPUCores() {
return (uint32_t)[[NSProcessInfo processInfo] activeProcessorCount];
}
void mvkDispatchToMainAndWait(dispatch_block_t block) {
if (NSThread.isMainThread) {
block();
} else {
dispatch_sync(dispatch_get_main_queue(), block);
}
}

View File

@ -380,6 +380,9 @@ In addition to core *Vulkan* functionality, **MoltenVK** also supports the foll
- `VK_EXT_external_memory_host`
- `VK_EXT_fragment_shader_interlock`
- *Requires Metal 2.0 and Raster Order Groups.*
- `VK_EXT_hdr_metadata`
- *macOS only.*
- `VK_EXT_headless_surface`
- `VK_EXT_host_query_reset`
- `VK_EXT_image_robustness`
- `VK_EXT_inline_uniform_block`

View File

@ -20,11 +20,13 @@ Released TBD
- Add support for extensions:
- `VK_EXT_extended_dynamic_state3` *(Metal does not support `VK_POLYGON_MODE_POINT`)*
- `VK_EXT_headless_surface`
- Fix regression that broke `VK_POLYGON_MODE_LINE`.
- Fix regression in marking rendering state dirty after `vkCmdClearAttachments()`.
- Reduce disk space consumed after running `fetchDependencies` script by removing intermediate file caches.
- Fix rare deadlock during launch via `dlopen()`.
- Fix initial value of `VkPhysicalDeviceLimits::timestampPeriod` on non-Apple Silicon GPUs.
- Fix swapchain and surface bugs when windowing system is accessed from off the main thread.
- Update to latest SPIRV-Cross:
- MSL: Fix regression error in argument buffer runtime arrays.
- MSL: Work around broken cube texture gradients on Apple Silicon.

View File

@ -117,7 +117,7 @@
2FEA0AAF24902F9F00EEF3AD /* MVKLayers.mm in Sources */ = {isa = PBXBuildFile; fileRef = A94FB7A11C7DFB4800632CA3 /* MVKLayers.mm */; };
2FEA0AB024902F9F00EEF3AD /* MVKFramebuffer.mm in Sources */ = {isa = PBXBuildFile; fileRef = A94FB7881C7DFB4800632CA3 /* MVKFramebuffer.mm */; };
2FEA0AB124902F9F00EEF3AD /* MVKMTLBufferAllocation.mm in Sources */ = {isa = PBXBuildFile; fileRef = A9C96DCF1DDC20C20053187F /* MVKMTLBufferAllocation.mm */; };
2FEA0AB224902F9F00EEF3AD /* CAMetalLayer+MoltenVK.m in Sources */ = {isa = PBXBuildFile; fileRef = A9E53DD62100B197002781DD /* CAMetalLayer+MoltenVK.m */; };
2FEA0AB224902F9F00EEF3AD /* CAMetalLayer+MoltenVK.mm in Sources */ = {isa = PBXBuildFile; fileRef = A9E53DD62100B197002781DD /* CAMetalLayer+MoltenVK.mm */; };
2FEA0AB324902F9F00EEF3AD /* MVKCmdDispatch.mm in Sources */ = {isa = PBXBuildFile; fileRef = A9096E5D1F81E16300DFBEA6 /* MVKCmdDispatch.mm */; };
2FEA0AB424902F9F00EEF3AD /* MVKCmdDebug.mm in Sources */ = {isa = PBXBuildFile; fileRef = A99C90ED229455B300A061DA /* MVKCmdDebug.mm */; };
45003E73214AD4E500E989CB /* MVKExtensions.def in Headers */ = {isa = PBXBuildFile; fileRef = 45003E6F214AD4C900E989CB /* MVKExtensions.def */; };
@ -360,8 +360,8 @@
A9E53DE62100B197002781DD /* NSString+MoltenVK.mm in Sources */ = {isa = PBXBuildFile; fileRef = A9E53DD42100B197002781DD /* NSString+MoltenVK.mm */; };
A9E53DE72100B197002781DD /* MTLTextureDescriptor+MoltenVK.m in Sources */ = {isa = PBXBuildFile; fileRef = A9E53DD52100B197002781DD /* MTLTextureDescriptor+MoltenVK.m */; };
A9E53DE82100B197002781DD /* MTLTextureDescriptor+MoltenVK.m in Sources */ = {isa = PBXBuildFile; fileRef = A9E53DD52100B197002781DD /* MTLTextureDescriptor+MoltenVK.m */; };
A9E53DE92100B197002781DD /* CAMetalLayer+MoltenVK.m in Sources */ = {isa = PBXBuildFile; fileRef = A9E53DD62100B197002781DD /* CAMetalLayer+MoltenVK.m */; };
A9E53DEA2100B197002781DD /* CAMetalLayer+MoltenVK.m in Sources */ = {isa = PBXBuildFile; fileRef = A9E53DD62100B197002781DD /* CAMetalLayer+MoltenVK.m */; };
A9E53DE92100B197002781DD /* CAMetalLayer+MoltenVK.mm in Sources */ = {isa = PBXBuildFile; fileRef = A9E53DD62100B197002781DD /* CAMetalLayer+MoltenVK.mm */; };
A9E53DEA2100B197002781DD /* CAMetalLayer+MoltenVK.mm in Sources */ = {isa = PBXBuildFile; fileRef = A9E53DD62100B197002781DD /* CAMetalLayer+MoltenVK.mm */; };
A9E53DF32100B302002781DD /* MTLRenderPassDescriptor+MoltenVK.h in Headers */ = {isa = PBXBuildFile; fileRef = A9E53DEE2100B302002781DD /* MTLRenderPassDescriptor+MoltenVK.h */; };
A9E53DF42100B302002781DD /* MTLRenderPassDescriptor+MoltenVK.h in Headers */ = {isa = PBXBuildFile; fileRef = A9E53DEE2100B302002781DD /* MTLRenderPassDescriptor+MoltenVK.h */; };
A9E53DF52100B302002781DD /* MTLRenderPassDescriptor+MoltenVK.m in Sources */ = {isa = PBXBuildFile; fileRef = A9E53DF22100B302002781DD /* MTLRenderPassDescriptor+MoltenVK.m */; };
@ -495,7 +495,7 @@
DCFD7F572A45BC6E007BBBF7 /* MVKFramebuffer.mm in Sources */ = {isa = PBXBuildFile; fileRef = A94FB7881C7DFB4800632CA3 /* MVKFramebuffer.mm */; };
DCFD7F582A45BC6E007BBBF7 /* MTLRenderPassStencilAttachmentDescriptor+MoltenVK.m in Sources */ = {isa = PBXBuildFile; fileRef = 453638302508A4C6000EFFD3 /* MTLRenderPassStencilAttachmentDescriptor+MoltenVK.m */; };
DCFD7F592A45BC6E007BBBF7 /* MVKMTLBufferAllocation.mm in Sources */ = {isa = PBXBuildFile; fileRef = A9C96DCF1DDC20C20053187F /* MVKMTLBufferAllocation.mm */; };
DCFD7F5A2A45BC6E007BBBF7 /* CAMetalLayer+MoltenVK.m in Sources */ = {isa = PBXBuildFile; fileRef = A9E53DD62100B197002781DD /* CAMetalLayer+MoltenVK.m */; };
DCFD7F5A2A45BC6E007BBBF7 /* CAMetalLayer+MoltenVK.mm in Sources */ = {isa = PBXBuildFile; fileRef = A9E53DD62100B197002781DD /* CAMetalLayer+MoltenVK.mm */; };
DCFD7F5B2A45BC6E007BBBF7 /* MVKCmdDispatch.mm in Sources */ = {isa = PBXBuildFile; fileRef = A9096E5D1F81E16300DFBEA6 /* MVKCmdDispatch.mm */; };
DCFD7F5C2A45BC6E007BBBF7 /* MVKCmdDebug.mm in Sources */ = {isa = PBXBuildFile; fileRef = A99C90ED229455B300A061DA /* MVKCmdDebug.mm */; };
/* End PBXBuildFile section */
@ -691,7 +691,7 @@
A9E53DD32100B197002781DD /* MTLSamplerDescriptor+MoltenVK.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MTLSamplerDescriptor+MoltenVK.h"; sourceTree = "<group>"; };
A9E53DD42100B197002781DD /* NSString+MoltenVK.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSString+MoltenVK.mm"; sourceTree = "<group>"; };
A9E53DD52100B197002781DD /* MTLTextureDescriptor+MoltenVK.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MTLTextureDescriptor+MoltenVK.m"; sourceTree = "<group>"; };
A9E53DD62100B197002781DD /* CAMetalLayer+MoltenVK.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CAMetalLayer+MoltenVK.m"; sourceTree = "<group>"; };
A9E53DD62100B197002781DD /* CAMetalLayer+MoltenVK.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "CAMetalLayer+MoltenVK.mm"; sourceTree = "<group>"; };
A9E53DEE2100B302002781DD /* MTLRenderPassDescriptor+MoltenVK.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MTLRenderPassDescriptor+MoltenVK.h"; sourceTree = "<group>"; };
A9E53DF22100B302002781DD /* MTLRenderPassDescriptor+MoltenVK.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MTLRenderPassDescriptor+MoltenVK.m"; sourceTree = "<group>"; };
A9E53DFA21064F84002781DD /* MTLRenderPipelineDescriptor+MoltenVK.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MTLRenderPipelineDescriptor+MoltenVK.m"; sourceTree = "<group>"; };
@ -889,7 +889,7 @@
isa = PBXGroup;
children = (
A9E53DD12100B197002781DD /* CAMetalLayer+MoltenVK.h */,
A9E53DD62100B197002781DD /* CAMetalLayer+MoltenVK.m */,
A9E53DD62100B197002781DD /* CAMetalLayer+MoltenVK.mm */,
453638312508A4C7000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.h */,
4536382F2508A4C6000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.m */,
A9E53DEE2100B302002781DD /* MTLRenderPassDescriptor+MoltenVK.h */,
@ -1703,7 +1703,7 @@
2FEA0AAF24902F9F00EEF3AD /* MVKLayers.mm in Sources */,
2FEA0AB024902F9F00EEF3AD /* MVKFramebuffer.mm in Sources */,
2FEA0AB124902F9F00EEF3AD /* MVKMTLBufferAllocation.mm in Sources */,
2FEA0AB224902F9F00EEF3AD /* CAMetalLayer+MoltenVK.m in Sources */,
2FEA0AB224902F9F00EEF3AD /* CAMetalLayer+MoltenVK.mm in Sources */,
2FEA0AB324902F9F00EEF3AD /* MVKCmdDispatch.mm in Sources */,
2FEA0AB424902F9F00EEF3AD /* MVKCmdDebug.mm in Sources */,
);
@ -1763,7 +1763,7 @@
A94FB7EE1C7DFB4800632CA3 /* MVKFramebuffer.mm in Sources */,
453638382508A4C7000EFFD3 /* MTLRenderPassStencilAttachmentDescriptor+MoltenVK.m in Sources */,
A9C96DD21DDC20C20053187F /* MVKMTLBufferAllocation.mm in Sources */,
A9E53DE92100B197002781DD /* CAMetalLayer+MoltenVK.m in Sources */,
A9E53DE92100B197002781DD /* CAMetalLayer+MoltenVK.mm in Sources */,
A9096E5E1F81E16300DFBEA6 /* MVKCmdDispatch.mm in Sources */,
A99C90F0229455B300A061DA /* MVKCmdDebug.mm in Sources */,
);
@ -1823,7 +1823,7 @@
A94FB7EF1C7DFB4800632CA3 /* MVKFramebuffer.mm in Sources */,
4536383A2508A4C7000EFFD3 /* MTLRenderPassStencilAttachmentDescriptor+MoltenVK.m in Sources */,
A9C96DD31DDC20C20053187F /* MVKMTLBufferAllocation.mm in Sources */,
A9E53DEA2100B197002781DD /* CAMetalLayer+MoltenVK.m in Sources */,
A9E53DEA2100B197002781DD /* CAMetalLayer+MoltenVK.mm in Sources */,
A9096E5F1F81E16300DFBEA6 /* MVKCmdDispatch.mm in Sources */,
A99C90F1229455B300A061DA /* MVKCmdDebug.mm in Sources */,
);
@ -1883,7 +1883,7 @@
DCFD7F572A45BC6E007BBBF7 /* MVKFramebuffer.mm in Sources */,
DCFD7F582A45BC6E007BBBF7 /* MTLRenderPassStencilAttachmentDescriptor+MoltenVK.m in Sources */,
DCFD7F592A45BC6E007BBBF7 /* MVKMTLBufferAllocation.mm in Sources */,
DCFD7F5A2A45BC6E007BBBF7 /* CAMetalLayer+MoltenVK.m in Sources */,
DCFD7F5A2A45BC6E007BBBF7 /* CAMetalLayer+MoltenVK.mm in Sources */,
DCFD7F5B2A45BC6E007BBBF7 /* MVKCmdDispatch.mm in Sources */,
DCFD7F5C2A45BC6E007BBBF7 /* MVKCmdDebug.mm in Sources */,
);

View File

@ -1202,8 +1202,8 @@ VkResult MVKPhysicalDevice::getSurfaceSupport(uint32_t queueFamilyIndex,
isHeadless = getMTLDevice().isHeadless;
#endif
// If this device is headless or the surface does not have a CAMetalLayer, it is not supported.
*pSupported = !(isHeadless || (surface->getCAMetalLayer() == nil));
// If this device is headless, the surface must be headless.
*pSupported = isHeadless ? surface->isHeadless() : wasConfigurationSuccessful();
return *pSupported ? VK_SUCCESS : surface->getConfigurationResult();
}
@ -1262,13 +1262,12 @@ VkResult MVKPhysicalDevice::getSurfaceCapabilities( const VkPhysicalDeviceSurfac
// The CAlayer underlying the surface must be a CAMetalLayer.
MVKSurface* surface = (MVKSurface*)pSurfaceInfo->surface;
CAMetalLayer* mtlLayer = surface->getCAMetalLayer();
if ( !mtlLayer ) { return surface->getConfigurationResult(); }
if ( !surface->wasConfigurationSuccessful() ) { return surface->getConfigurationResult(); }
VkSurfaceCapabilitiesKHR& surfCaps = pSurfaceCapabilities->surfaceCapabilities;
surfCaps.minImageCount = _metalFeatures.minSwapchainImageCount;
surfCaps.maxImageCount = _metalFeatures.maxSwapchainImageCount;
surfCaps.currentExtent = mvkGetNaturalExtent(mtlLayer);
surfCaps.currentExtent = surface->getNaturalExtent();
surfCaps.minImageExtent = { 1, 1 };
surfCaps.maxImageExtent = { _properties.limits.maxImageDimension2D, _properties.limits.maxImageDimension2D };
surfCaps.maxImageArrayLayers = 1;
@ -1347,9 +1346,7 @@ VkResult MVKPhysicalDevice::getSurfaceFormats(MVKSurface* surface,
uint32_t* pCount,
VkSurfaceFormatKHR* pSurfaceFormats) {
// The layer underlying the surface view must be a CAMetalLayer.
CAMetalLayer* mtlLayer = surface->getCAMetalLayer();
if ( !mtlLayer ) { return surface->getConfigurationResult(); }
if ( !surface->wasConfigurationSuccessful() ) { return surface->getConfigurationResult(); }
#define addSurfFmt(MTL_FMT) \
do { \
@ -1472,9 +1469,7 @@ VkResult MVKPhysicalDevice::getSurfacePresentModes(MVKSurface* surface,
uint32_t* pCount,
VkPresentModeKHR* pPresentModes) {
// The layer underlying the surface view must be a CAMetalLayer.
CAMetalLayer* mtlLayer = surface->getCAMetalLayer();
if ( !mtlLayer ) { return surface->getConfigurationResult(); }
if ( !surface->wasConfigurationSuccessful() ) { return surface->getConfigurationResult(); }
#define ADD_VK_PRESENT_MODE(VK_PM) \
do { \
@ -1502,9 +1497,7 @@ VkResult MVKPhysicalDevice::getPresentRectangles(MVKSurface* surface,
uint32_t* pRectCount,
VkRect2D* pRects) {
// The layer underlying the surface view must be a CAMetalLayer.
CAMetalLayer* mtlLayer = surface->getCAMetalLayer();
if ( !mtlLayer ) { return surface->getConfigurationResult(); }
if ( !surface->wasConfigurationSuccessful() ) { return surface->getConfigurationResult(); }
if ( !pRects ) {
*pRectCount = 1;
@ -1516,7 +1509,7 @@ VkResult MVKPhysicalDevice::getPresentRectangles(MVKSurface* surface,
*pRectCount = 1;
pRects[0].offset = { 0, 0 };
pRects[0].extent = mvkGetNaturalExtent(mtlLayer);
pRects[0].extent = surface->getNaturalExtent();
return VK_SUCCESS;
}
@ -3672,14 +3665,14 @@ void MVKDevice::getCalibratedTimestamps(uint32_t timestampCount,
MTLTimestamp cpuStamp, gpuStamp;
uint64_t cpuStart, cpuEnd;
cpuStart = mvkGetAbsoluteTime();
cpuStart = mvkGetContinuousNanoseconds();
[getMTLDevice() sampleTimestamps: &cpuStamp gpuTimestamp: &gpuStamp];
// Sample again to calculate the maximum deviation. Note that the
// -[MTLDevice sampleTimestamps:gpuTimestamp:] method guarantees that CPU
// timestamps are in nanoseconds. We don't want to call the method again,
// because that could result in an expensive syscall to query the GPU time-
// stamp.
cpuEnd = mvkGetAbsoluteTime();
cpuEnd = mvkGetContinuousNanoseconds();
for (uint32_t tsIdx = 0; tsIdx < timestampCount; ++tsIdx) {
switch (pTimestampInfos[tsIdx].timeDomain) {
case VK_TIME_DOMAIN_DEVICE_EXT:

View File

@ -378,14 +378,8 @@ class MVKSwapchainImage : public MVKImage {
public:
/** Binds this resource to the specified offset within the specified memory allocation. */
VkResult bindDeviceMemory(MVKDeviceMemory* mvkMem, VkDeviceSize memOffset, uint8_t planeIndex) override;
#pragma mark Metal
/** Returns the Metal texture used by the CAMetalDrawable underlying this image. */
id<MTLTexture> getMTLTexture(uint8_t planeIndex) override;
#pragma mark Construction
@ -399,7 +393,6 @@ public:
protected:
friend class MVKPeerSwapchainImage;
virtual id<CAMetalDrawable> getCAMetalDrawable() = 0;
void detachSwapchain();
std::mutex _detachmentLock;
@ -445,6 +438,8 @@ public:
#pragma mark Metal
id<MTLTexture> getMTLTexture(uint8_t planeIndex) override;
/** Presents the contained drawable to the OS. */
VkResult presentCAMetalDrawable(id<MTLCommandBuffer> mtlCmdBuff, MVKImagePresentInfo presentInfo);
@ -468,16 +463,16 @@ public:
protected:
friend MVKSwapchain;
id<CAMetalDrawable> getCAMetalDrawable() override;
id<CAMetalDrawable> getCAMetalDrawable();
void addPresentedHandler(id<CAMetalDrawable> mtlDrawable, MVKImagePresentInfo presentInfo, MVKSwapchainSignaler signaler);
void releaseMetalDrawable();
MVKSwapchainImageAvailability getAvailability();
void makeAvailable(const MVKSwapchainSignaler& signaler);
void makeAvailable();
VkResult acquireAndSignalWhenAvailable(MVKSemaphore* semaphore, MVKFence* fence);
MVKSwapchainSignaler getPresentationSignaler();
id<CAMetalDrawable> _mtlDrawable = nil;
id<MTLTexture> _mtlTextureHeadless = nil;
MVKSwapchainImageAvailability _availability;
MVKSmallVector<MVKSwapchainSignaler, 1> _availabilitySignalers;
MVKSwapchainSignaler _preSignaler = {};
@ -494,7 +489,8 @@ class MVKPeerSwapchainImage : public MVKSwapchainImage {
public:
/** Binds this resource according to the specified bind information. */
id<MTLTexture> getMTLTexture(uint8_t planeIndex) override;
VkResult bindDeviceMemory2(const VkBindImageMemoryInfo* pBindInfo) override;
@ -504,10 +500,6 @@ public:
const VkImageCreateInfo* pCreateInfo,
MVKSwapchain* swapchain,
uint32_t swapchainIndex);
protected:
id<CAMetalDrawable> getCAMetalDrawable() override;
};

View File

@ -25,8 +25,10 @@
#include "MVKFoundation.h"
#include "MVKOSExtensions.h"
#include "MVKCodec.h"
#import "MTLTextureDescriptor+MoltenVK.h"
#import "MTLSamplerDescriptor+MoltenVK.h"
#import "CAMetalLayer+MoltenVK.h"
using namespace std;
using namespace SPIRV_CROSS_NAMESPACE;
@ -1169,12 +1171,6 @@ VkResult MVKSwapchainImage::bindDeviceMemory(MVKDeviceMemory* mvkMem, VkDeviceSi
}
#pragma mark Metal
// Overridden to always retrieve the MTLTexture directly from the CAMetalDrawable.
id<MTLTexture> MVKSwapchainImage::getMTLTexture(uint8_t planeIndex) { return [getCAMetalDrawable() texture]; }
#pragma mark Construction
MVKSwapchainImage::MVKSwapchainImage(MVKDevice* device,
@ -1212,39 +1208,33 @@ MVKSwapchainImageAvailability MVKPresentableSwapchainImage::getAvailability() {
return _availability;
}
// 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 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) {
static void track(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 signal(MVKSemaphore* semaphore, uint64_t semaphoreSignalToken, id<MTLCommandBuffer> mtlCmdBuff) {
if (semaphore) { semaphore->encodeDeferredSignal(mtlCmdBuff, semaphoreSignalToken); }
}
static void signalAndUnmarkAsTracked(const MVKSwapchainSignaler& signaler) {
signal(signaler, nil);
unmarkAsTracked(signaler);
static void signal(MVKFence* fence) {
if (fence) { fence->signal(); }
}
// Signal the semaphore and fence and tell them that they are no longer being tracked for future signaling.
static void signalAndUntrack(const MVKSwapchainSignaler& signaler) {
signal(signaler.semaphore, signaler.semaphoreSignalToken, nil);
if (signaler.semaphore) { signaler.semaphore->release(); }
signal(signaler.fence);
if (signaler.fence) { signaler.fence->release(); }
}
VkResult MVKPresentableSwapchainImage::acquireAndSignalWhenAvailable(MVKSemaphore* semaphore, MVKFence* fence) {
// Now that this image is being acquired, release the existing drawable and its texture.
// This is not done earlier so the texture is retained for any post-processing such as screen captures, etc.
// This may trigger a delayed presentation callback, which uses the _availabilityLock, also used below.
releaseMetalDrawable();
lock_guard<mutex> lock(_availabilityLock);
@ -1267,7 +1257,8 @@ VkResult MVKPresentableSwapchainImage::acquireAndSignalWhenAvailable(MVKSemaphor
mtlCmdBuff = _device->getAnyQueue()->getMTLCommandBuffer(kMVKCommandUseAcquireNextImage);
if ( !mtlCmdBuff ) { setConfigurationResult(VK_ERROR_OUT_OF_POOL_MEMORY); }
}
signal(signaler, mtlCmdBuff);
signal(signaler.semaphore, signaler.semaphoreSignalToken, mtlCmdBuff);
signal(signaler.fence);
[mtlCmdBuff commit];
}
@ -1275,7 +1266,7 @@ VkResult MVKPresentableSwapchainImage::acquireAndSignalWhenAvailable(MVKSemaphor
} else {
_availabilitySignalers.push_back(signaler);
}
markAsTracked(signaler);
track(signaler);
return getConfigurationResult();
}
@ -1284,6 +1275,9 @@ VkResult MVKPresentableSwapchainImage::acquireAndSignalWhenAvailable(MVKSemaphor
// Attempt several times to retrieve a good drawable, and set an error to trigger the
// swapchain to be re-established if one cannot be retrieved.
id<CAMetalDrawable> MVKPresentableSwapchainImage::getCAMetalDrawable() {
if (_mtlTextureHeadless) { return nil; } // If headless, there is no drawable.
if ( !_mtlDrawable ) {
@autoreleasepool {
bool hasInvalidFormat = false;
@ -1305,6 +1299,11 @@ id<CAMetalDrawable> MVKPresentableSwapchainImage::getCAMetalDrawable() {
return _mtlDrawable;
}
// If not headless, retrieve the MTLTexture directly from the CAMetalDrawable.
id<MTLTexture> MVKPresentableSwapchainImage::getMTLTexture(uint8_t planeIndex) {
return _mtlTextureHeadless ? _mtlTextureHeadless : getCAMetalDrawable().texture;
}
// Present the drawable and make myself available only once the command buffer has completed.
// Pass MVKImagePresentInfo by value because it may not exist when the callback runs.
VkResult MVKPresentableSwapchainImage::presentCAMetalDrawable(id<MTLCommandBuffer> mtlCmdBuff,
@ -1343,15 +1342,13 @@ VkResult MVKPresentableSwapchainImage::presentCAMetalDrawable(id<MTLCommandBuffe
auto* fence = presentInfo.fence;
if (fence) { fence->retain(); }
[mtlCmdBuff addCompletedHandler: ^(id<MTLCommandBuffer> mcb) {
if (fence) {
fence->signal();
fence->release();
}
signal(fence);
if (fence) { fence->release(); }
[mtlDrwbl release];
release();
}];
signalPresentationSemaphore(signaler, mtlCmdBuff);
signal(signaler.semaphore, signaler.semaphoreSignalToken, mtlCmdBuff);
return getConfigurationResult();
}
@ -1408,6 +1405,13 @@ void MVKPresentableSwapchainImage::beginPresentation(const MVKImagePresentInfo&
void MVKPresentableSwapchainImage::endPresentation(const MVKImagePresentInfo& presentInfo,
const MVKSwapchainSignaler& signaler,
uint64_t actualPresentTime) {
// If the presentation time is not available, use the current nanosecond runtime clock,
// which should be reasonably accurate (sub-ms) to the presentation time. The presentation
// time will not be available if the presentation did not actually happen, such as when
// running headless, or on a test harness that is not attached to the windowing system.
if (actualPresentTime == 0) { actualPresentTime = mvkGetRuntimeNanoseconds(); }
{ // Scope to avoid deadlock if release() is run within detachment lock
// If I have become detached from the swapchain, it means the swapchain, and possibly the
// VkDevice, have been destroyed by the time of this callback, so do not reference them.
@ -1415,7 +1419,11 @@ void MVKPresentableSwapchainImage::endPresentation(const MVKImagePresentInfo& pr
if (_device) { _device->addPerformanceInterval(_device->_performanceStatistics.queue.presentSwapchains, _presentationStartTime); }
if (_swapchain) { _swapchain->endPresentation(presentInfo, actualPresentTime); }
}
makeAvailable(signaler);
// 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.
signalAndUntrack(signaler);
release();
}
@ -1425,15 +1433,6 @@ 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.
// Release the drawable before the lock, as it may trigger completion callback.
void MVKPresentableSwapchainImage::makeAvailable() {
@ -1441,9 +1440,9 @@ void MVKPresentableSwapchainImage::makeAvailable() {
lock_guard<mutex> lock(_availabilityLock);
if ( !_availability.isAvailable ) {
signalAndUnmarkAsTracked(_preSignaler);
signalAndUntrack(_preSignaler);
for (auto& sig : _availabilitySignalers) {
signalAndUnmarkAsTracked(sig);
signalAndUntrack(sig);
}
_availabilitySignalers.clear();
_availability.isAvailable = true;
@ -1460,11 +1459,26 @@ MVKPresentableSwapchainImage::MVKPresentableSwapchainImage(MVKDevice* device,
_availability.acquisitionID = _swapchain->getNextAcquisitionID();
_availability.isAvailable = true;
if (swapchain->isHeadless()) {
@autoreleasepool {
MTLTextureDescriptor* mtlTexDesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: getMTLPixelFormat()
width: pCreateInfo->extent.width
height: pCreateInfo->extent.height
mipmapped: NO];
mtlTexDesc.usageMVK = MTLTextureUsageRenderTarget;
mtlTexDesc.storageModeMVK = MTLStorageModePrivate;
_mtlTextureHeadless = [[getMTLDevice() newTextureWithDescriptor: mtlTexDesc] retain]; // retained
}
}
}
void MVKPresentableSwapchainImage::destroy() {
releaseMetalDrawable();
[_mtlTextureHeadless release];
_mtlTextureHeadless = nil;
MVKSwapchainImage::destroy();
}
@ -1498,8 +1512,8 @@ VkResult MVKPeerSwapchainImage::bindDeviceMemory2(const VkBindImageMemoryInfo* p
#pragma mark Metal
id<CAMetalDrawable> MVKPeerSwapchainImage::getCAMetalDrawable() {
return ((MVKSwapchainImage*)_swapchain->getPresentableImage(_swapchainIndex))->getCAMetalDrawable();
id<MTLTexture> MVKPeerSwapchainImage::getMTLTexture(uint8_t planeIndex) {
return ((MVKSwapchainImage*)_swapchain->getPresentableImage(_swapchainIndex))->getMTLTexture(planeIndex);
}

View File

@ -117,6 +117,9 @@ public:
MVKSurface* createSurface(const VkMetalSurfaceCreateInfoEXT* pCreateInfo,
const VkAllocationCallbacks* pAllocator);
MVKSurface* createSurface(const VkHeadlessSurfaceCreateInfoEXT* pCreateInfo,
const VkAllocationCallbacks* pAllocator);
MVKSurface* createSurface(const Vk_PLATFORM_SurfaceCreateInfoMVK* pCreateInfo,
const VkAllocationCallbacks* pAllocator);

View File

@ -102,6 +102,11 @@ MVKSurface* MVKInstance::createSurface(const VkMetalSurfaceCreateInfoEXT* pCreat
return new MVKSurface(this, pCreateInfo, pAllocator);
}
MVKSurface* MVKInstance::createSurface(const VkHeadlessSurfaceCreateInfoEXT* pCreateInfo,
const VkAllocationCallbacks* pAllocator) {
return new MVKSurface(this, pCreateInfo, pAllocator);
}
MVKSurface* MVKInstance::createSurface(const Vk_PLATFORM_SurfaceCreateInfoMVK* pCreateInfo,
const VkAllocationCallbacks* pAllocator) {
return new MVKSurface(this, pCreateInfo, pAllocator);
@ -426,6 +431,8 @@ void MVKInstance::initProcAddrs() {
ADD_INST_EXT_ENTRY_POINT(vkGetPhysicalDeviceSurfacePresentModesKHR, KHR_SURFACE);
ADD_INST_EXT_ENTRY_POINT(vkGetPhysicalDeviceSurfaceCapabilities2KHR, KHR_GET_SURFACE_CAPABILITIES_2);
ADD_INST_EXT_ENTRY_POINT(vkGetPhysicalDeviceSurfaceFormats2KHR, KHR_GET_SURFACE_CAPABILITIES_2);
ADD_INST_EXT_ENTRY_POINT(vkCreateHeadlessSurfaceEXT, EXT_HEADLESS_SURFACE);
ADD_INST_EXT_ENTRY_POINT(vkCreateMetalSurfaceEXT, EXT_METAL_SURFACE);
ADD_INST_EXT_ENTRY_POINT(vkCreateDebugReportCallbackEXT, EXT_DEBUG_REPORT);
ADD_INST_EXT_ENTRY_POINT(vkDestroyDebugReportCallbackEXT, EXT_DEBUG_REPORT);
ADD_INST_EXT_ENTRY_POINT(vkDebugReportMessageEXT, EXT_DEBUG_REPORT);
@ -441,7 +448,6 @@ void MVKInstance::initProcAddrs() {
ADD_INST_EXT_ENTRY_POINT(vkCreateDebugUtilsMessengerEXT, EXT_DEBUG_UTILS);
ADD_INST_EXT_ENTRY_POINT(vkDestroyDebugUtilsMessengerEXT, EXT_DEBUG_UTILS);
ADD_INST_EXT_ENTRY_POINT(vkSubmitDebugUtilsMessageEXT, EXT_DEBUG_UTILS);
ADD_INST_EXT_ENTRY_POINT(vkCreateMetalSurfaceEXT, EXT_METAL_SURFACE);
#ifdef VK_USE_PLATFORM_IOS_MVK
ADD_INST_EXT_ENTRY_POINT(vkCreateIOSSurfaceMVK, MVK_IOS_SURFACE);

View File

@ -20,7 +20,7 @@
#include "MVKBaseObject.h"
#include "MVKOSExtensions.h"
#include "mvk_datatypes.h"
#include "mvk_datatypes.hpp"
#include <spirv_msl.hpp>
#include <unordered_map>

View File

@ -24,16 +24,6 @@
#import <Metal/Metal.h>
#import <QuartzCore/CAMetalLayer.h>
#ifdef VK_USE_PLATFORM_IOS_MVK
# define PLATFORM_VIEW_CLASS UIView
# import <UIKit/UIView.h>
#endif
#ifdef VK_USE_PLATFORM_MACOS_MVK
# define PLATFORM_VIEW_CLASS NSView
# import <AppKit/NSView.h>
#endif
class MVKInstance;
class MVKSwapchain;
@ -59,6 +49,14 @@ public:
/** Returns the CAMetalLayer underlying this surface. */
CAMetalLayer* getCAMetalLayer();
/** Returns the extent of this surface. */
VkExtent2D getExtent();
/** Returns the extent for which the underlying CAMetalLayer will not need to be scaled when composited. */
VkExtent2D getNaturalExtent();
/** Returns whether this surface is headless. */
bool isHeadless() { return !_mtlCAMetalLayer && wasConfigurationSuccessful(); }
#pragma mark Construction
@ -66,6 +64,10 @@ public:
const VkMetalSurfaceCreateInfoEXT* pCreateInfo,
const VkAllocationCallbacks* pAllocator);
MVKSurface(MVKInstance* mvkInstance,
const VkHeadlessSurfaceCreateInfoEXT* pCreateInfo,
const VkAllocationCallbacks* pAllocator);
MVKSurface(MVKInstance* mvkInstance,
const Vk_PLATFORM_SurfaceCreateInfoMVK* pCreateInfo,
const VkAllocationCallbacks* pAllocator);
@ -76,7 +78,8 @@ protected:
friend class MVKSwapchain;
void propagateDebugName() override {}
void initLayer(CAMetalLayer* mtlLayer, const char* vkFuncName);
void setActiveSwapchain(MVKSwapchain* swapchain);
void initLayer(CAMetalLayer* mtlLayer, const char* vkFuncName, bool isHeadless);
void releaseLayer();
std::mutex _layerLock;
@ -84,5 +87,6 @@ protected:
CAMetalLayer* _mtlCAMetalLayer = nil;
MVKBlockObserver* _layerObserver = nil;
MVKSwapchain* _activeSwapchain = nullptr;
VkExtent2D _headlessExtent = {0xFFFFFFFF, 0xFFFFFFFF};
};

View File

@ -17,11 +17,26 @@
*/
#include "MVKSurface.h"
#include "MVKSwapchain.h"
#include "MVKInstance.h"
#include "MVKFoundation.h"
#include "MVKOSExtensions.h"
#include "mvk_datatypes.hpp"
#import "CAMetalLayer+MoltenVK.h"
#import "MVKBlockObserver.h"
#ifdef VK_USE_PLATFORM_IOS_MVK
# define PLATFORM_VIEW_CLASS UIView
# import <UIKit/UIView.h>
#endif
#ifdef VK_USE_PLATFORM_MACOS_MVK
# define PLATFORM_VIEW_CLASS NSView
# import <AppKit/NSView.h>
#endif
// We need to double-dereference the name to first convert to the platform symbol, then to a string.
#define STR_PLATFORM(NAME) #NAME
#define STR(NAME) STR_PLATFORM(NAME)
@ -34,38 +49,55 @@ CAMetalLayer* MVKSurface::getCAMetalLayer() {
return _mtlCAMetalLayer;
}
VkExtent2D MVKSurface::getExtent() {
return _mtlCAMetalLayer ? mvkVkExtent2DFromCGSize(_mtlCAMetalLayer.drawableSize) : _headlessExtent;
}
VkExtent2D MVKSurface::getNaturalExtent() {
return _mtlCAMetalLayer ? mvkVkExtent2DFromCGSize(_mtlCAMetalLayer.naturalDrawableSizeMVK) : _headlessExtent;
}
// Per spec, headless surface extent is set from the swapchain.
void MVKSurface::setActiveSwapchain(MVKSwapchain* swapchain) {
_activeSwapchain = swapchain;
_headlessExtent = swapchain->getImageExtent();
}
MVKSurface::MVKSurface(MVKInstance* mvkInstance,
const VkMetalSurfaceCreateInfoEXT* pCreateInfo,
const VkAllocationCallbacks* pAllocator) : _mvkInstance(mvkInstance) {
initLayer((CAMetalLayer*)pCreateInfo->pLayer, "vkCreateMetalSurfaceEXT");
initLayer((CAMetalLayer*)pCreateInfo->pLayer, "vkCreateMetalSurfaceEXT", false);
}
MVKSurface::MVKSurface(MVKInstance* mvkInstance,
const VkHeadlessSurfaceCreateInfoEXT* pCreateInfo,
const VkAllocationCallbacks* pAllocator) : _mvkInstance(mvkInstance) {
initLayer(nil, "vkCreateHeadlessSurfaceEXT", true);
}
// pCreateInfo->pView can be either a CAMetalLayer or a view (NSView/UIView).
MVKSurface::MVKSurface(MVKInstance* mvkInstance,
const Vk_PLATFORM_SurfaceCreateInfoMVK* pCreateInfo,
const VkAllocationCallbacks* pAllocator) : _mvkInstance(mvkInstance) {
MVKLogWarn("%s() is deprecated. Use vkCreateMetalSurfaceEXT() from the VK_EXT_metal_surface extension.", STR(vkCreate_PLATFORM_SurfaceMVK));
// Get the platform object contained in pView
id<NSObject> obj = (id<NSObject>)pCreateInfo->pView;
// If it's a view (NSView/UIView), extract the layer, otherwise assume it's already a CAMetalLayer.
id<NSObject> obj = (id<NSObject>)pCreateInfo->pView;
if ([obj isKindOfClass: [PLATFORM_VIEW_CLASS class]]) {
obj = ((PLATFORM_VIEW_CLASS*)obj).layer;
if ( !NSThread.isMainThread ) {
MVKLogWarn("%s(): You are not calling this function from the main thread. %s should only be accessed from the main thread. When using this function outside the main thread, consider passing the CAMetalLayer itself in %s::pView, instead of the %s.",
STR(vkCreate_PLATFORM_SurfaceMVK), STR(PLATFORM_VIEW_CLASS), STR(Vk_PLATFORM_SurfaceCreateInfoMVK), STR(PLATFORM_VIEW_CLASS));
}
__block id<NSObject> layer;
mvkDispatchToMainAndWait(^{ layer = ((PLATFORM_VIEW_CLASS*)obj).layer; });
obj = layer;
}
// Confirm that we were provided with a CAMetalLayer
initLayer([obj isKindOfClass: CAMetalLayer.class] ? (CAMetalLayer*)obj : nil,
STR(vkCreate_PLATFORM_SurfaceMVK));
initLayer([obj isKindOfClass: CAMetalLayer.class] ? (CAMetalLayer*)obj : nil, STR(vkCreate_PLATFORM_SurfaceMVK), false);
}
void MVKSurface::initLayer(CAMetalLayer* mtlLayer, const char* vkFuncName) {
void MVKSurface::initLayer(CAMetalLayer* mtlLayer, const char* vkFuncName, bool isHeadless) {
_mtlCAMetalLayer = [mtlLayer retain]; // retained
if ( !_mtlCAMetalLayer ) { setConfigurationResult(reportError(VK_ERROR_SURFACE_LOST_KHR, "%s(): On-screen rendering requires a layer of type CAMetalLayer.", vkFuncName)); }
if ( !_mtlCAMetalLayer && !isHeadless ) { setConfigurationResult(reportError(VK_ERROR_SURFACE_LOST_KHR, "%s(): On-screen rendering requires a layer of type CAMetalLayer.", vkFuncName)); }
// Sometimes, the owning view can replace its CAMetalLayer.
// When that happens, the app needs to recreate the surface.

View File

@ -23,7 +23,6 @@
#include "MVKSmallVector.h"
#include <mutex>
#import "CAMetalLayer+MoltenVK.h"
#import <Metal/Metal.h>
class MVKWatermark;
@ -46,9 +45,15 @@ public:
/** Returns the CAMetalLayer underlying the surface used by this swapchain. */
CAMetalLayer* getCAMetalLayer();
/** Returns whether the surface is headless. */
bool isHeadless();
/** Returns the number of images in this swapchain. */
uint32_t getImageCount() { return (uint32_t)_presentableImages.size(); }
/** Returns the size of the images in this swapchain. */
VkExtent2D getImageExtent() { return _imageExtent; }
/** Returns the image at the specified index. */
MVKPresentableSwapchainImage* getPresentableImage(uint32_t index) { return _presentableImages[index]; }
@ -126,7 +131,7 @@ protected:
std::atomic<uint64_t> _currentAcquisitionID = 0;
std::mutex _presentHistoryLock;
uint64_t _lastFrameTime = 0;
VkExtent2D _mtlLayerDrawableExtent = {0, 0};
VkExtent2D _imageExtent = {0, 0};
std::atomic<uint32_t> _unpresentedImageCount = 0;
uint32_t _currentPerfLogFrameCount = 0;
uint32_t _presentHistoryCount = 0;
@ -134,18 +139,3 @@ protected:
uint32_t _presentHistoryHeadIndex = 0;
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

@ -26,9 +26,11 @@
#include "MVKWatermarkTextureContent.h"
#include "MVKWatermarkShaderSource.h"
#include "mvk_datatypes.hpp"
#include <libkern/OSByteOrder.h>
#import "CAMetalLayer+MoltenVK.h"
#import "MVKBlockObserver.h"
#include <libkern/OSByteOrder.h>
using namespace std;
@ -49,6 +51,8 @@ void MVKSwapchain::propagateDebugName() {
CAMetalLayer* MVKSwapchain::getCAMetalLayer() { return _surface->getCAMetalLayer(); }
bool MVKSwapchain::isHeadless() { return _surface->isHeadless(); }
VkResult MVKSwapchain::getImages(uint32_t* pCount, VkImage* pSwapchainImages) {
// Get the number of surface images
@ -124,16 +128,15 @@ VkResult MVKSwapchain::getSurfaceStatus() {
return VK_SUCCESS;
}
// 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.
// This swapchain is optimally sized for the surface if the app has specified
// deliberate swapchain scaling, or the surface extent has not changed since the
// swapchain was created, and the surface will not need to be scaled when composited.
bool MVKSwapchain::hasOptimalSurface() {
if (_isDeliberatelyScaled) { return true; }
auto* mtlLayer = getCAMetalLayer();
VkExtent2D drawExtent = mvkVkExtent2DFromCGSize(mtlLayer.drawableSize);
return (mvkVkExtent2DsAreEqual(drawExtent, _mtlLayerDrawableExtent) &&
mvkVkExtent2DsAreEqual(drawExtent, mvkGetNaturalExtent(mtlLayer)));
VkExtent2D surfExtent = _surface->getExtent();
return (mvkVkExtent2DsAreEqual(surfExtent, _imageExtent) &&
mvkVkExtent2DsAreEqual(surfExtent, _surface->getNaturalExtent()));
}
@ -187,30 +190,29 @@ void MVKSwapchain::markFrameInterval() {
VkResult MVKSwapchain::getRefreshCycleDuration(VkRefreshCycleDurationGOOGLE *pRefreshCycleDuration) {
if (_device->getConfigurationResult() != VK_SUCCESS) { return _device->getConfigurationResult(); }
auto* mtlLayer = getCAMetalLayer();
#if MVK_VISIONOS
// TODO: See if this can be obtained from OS instead
NSInteger framesPerSecond = 90;
auto* screen = getCAMetalLayer().screenMVK; // Will be nil if headless
#if MVK_MACOS && !MVK_MACCAT
double framesPerSecond = 60;
if (screen) {
CGDirectDisplayID displayId = [[[screen deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue];
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displayId);
framesPerSecond = CGDisplayModeGetRefreshRate(mode);
CGDisplayModeRelease(mode);
#if MVK_XCODE_13
if (framesPerSecond == 0 && [screen respondsToSelector: @selector(maximumFramesPerSecond)])
framesPerSecond = [screen maximumFramesPerSecond];
#endif
// Builtin panels, e.g., on MacBook, report a zero refresh rate.
if (framesPerSecond == 0)
framesPerSecond = 60.0;
}
#elif MVK_IOS_OR_TVOS || MVK_MACCAT
NSInteger framesPerSecond = 60;
UIScreen* screen = mtlLayer.screenMVK;
if ([screen respondsToSelector: @selector(maximumFramesPerSecond)]) {
framesPerSecond = screen.maximumFramesPerSecond;
}
#elif MVK_MACOS && !MVK_MACCAT
NSScreen* screen = mtlLayer.screenMVK;
CGDirectDisplayID displayId = [[[screen deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue];
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displayId);
double framesPerSecond = CGDisplayModeGetRefreshRate(mode);
CGDisplayModeRelease(mode);
#if MVK_XCODE_13
if (framesPerSecond == 0 && [screen respondsToSelector: @selector(maximumFramesPerSecond)])
framesPerSecond = [screen maximumFramesPerSecond];
#endif
// Builtin panels, e.g., on MacBook, report a zero refresh rate.
if (framesPerSecond == 0)
framesPerSecond = 60.0;
#elif MVK_VISIONOS
NSInteger framesPerSecond = 90; // TODO: See if this can be obtained from OS instead
#endif
pRefreshCycleDuration->refreshDuration = (uint64_t)1e9 / framesPerSecond;
@ -260,12 +262,6 @@ void MVKSwapchain::endPresentation(const MVKImagePresentInfo& presentInfo, uint6
_presentHistoryHeadIndex = (_presentHistoryHeadIndex + 1) % kMaxPresentationHistory;
}
// If actual present time is not available, use desired time instead, and if that
// hasn't been set, use the current time, which should be reasonably accurate (sub-ms),
// since we are here as part of the addPresentedHandler: callback.
if (actualPresentTime == 0) { actualPresentTime = presentInfo.desiredPresentTime; }
if (actualPresentTime == 0) { actualPresentTime = CACurrentMediaTime() * 1.0e9; }
_presentTimingHistory[_presentHistoryIndex].presentID = presentInfo.presentID;
_presentTimingHistory[_presentHistoryIndex].desiredPresentTime = presentInfo.desiredPresentTime;
_presentTimingHistory[_presentHistoryIndex].actualPresentTime = actualPresentTime;
@ -380,12 +376,13 @@ void MVKSwapchain::setHDRMetadataEXT(const VkHdrMetadataEXT& metadata) {
MVKSwapchain::MVKSwapchain(MVKDevice* device, const VkSwapchainCreateInfoKHR* pCreateInfo)
: MVKVulkanAPIDeviceObject(device),
_surface((MVKSurface*)pCreateInfo->surface) {
_surface((MVKSurface*)pCreateInfo->surface),
_imageExtent(pCreateInfo->imageExtent) {
// Check if oldSwapchain is properly set
auto* oldSwapchain = (MVKSwapchain*)pCreateInfo->oldSwapchain;
if (oldSwapchain == _surface->_activeSwapchain) {
_surface->_activeSwapchain = this;
_surface->setActiveSwapchain(this);
} else {
setConfigurationResult(reportError(VK_ERROR_NATIVE_WINDOW_IN_USE_KHR, "vkCreateSwapchainKHR(): pCreateInfo->oldSwapchain does not match the VkSwapchain that is in use by the surface"));
return;
@ -470,10 +467,11 @@ void MVKSwapchain::initCAMetalLayer(const VkSwapchainCreateInfoKHR* pCreateInfo,
VkSwapchainPresentScalingCreateInfoEXT* pScalingInfo,
uint32_t imgCnt) {
if ( getIsSurfaceLost() ) { return; }
auto* mtlLayer = getCAMetalLayer();
if ( !mtlLayer || getIsSurfaceLost() ) { return; }
auto minMagFilter = mvkConfig().swapchainMinMagFilterUseNearest ? kCAFilterNearest : kCAFilterLinear;
mtlLayer.drawableSize = mvkCGSizeFromVkExtent2D(_imageExtent);
mtlLayer.device = getMTLDevice();
mtlLayer.pixelFormat = getPixelFormats()->getMTLPixelFormat(pCreateInfo->imageFormat);
mtlLayer.maximumDrawableCountMVK = imgCnt;
@ -491,15 +489,10 @@ void MVKSwapchain::initCAMetalLayer(const VkSwapchainCreateInfoKHR* pCreateInfo,
// presentations on the oldSwapchain to complete and call back, but if the drawableSize
// is not changing from the previous, we force those completions first.
auto* oldSwapchain = (MVKSwapchain*)pCreateInfo->oldSwapchain;
if (oldSwapchain && mvkVkExtent2DsAreEqual(pCreateInfo->imageExtent, mvkVkExtent2DFromCGSize(mtlLayer.drawableSize))) {
if (oldSwapchain && mvkVkExtent2DsAreEqual(pCreateInfo->imageExtent, _surface->getExtent())) {
oldSwapchain->forceUnpresentedImageCompletion();
}
// 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;
}
@ -585,14 +578,13 @@ void MVKSwapchain::initSurfaceImages(const VkSwapchainCreateInfoKHR* pCreateInfo
}
}
auto* mtlLayer = getCAMetalLayer();
VkExtent2D imgExtent = pCreateInfo->imageExtent;
VkImageCreateInfo imgInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = VK_NULL_HANDLE,
.imageType = VK_IMAGE_TYPE_2D,
.format = getPixelFormats()->getVkFormat(mtlLayer.pixelFormat),
.extent = { imgExtent.width, imgExtent.height, 1 },
.format = pCreateInfo->imageFormat,
.extent = mvkVkExtent3DFromVkExtent2D(imgExtent),
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
@ -618,14 +610,20 @@ void MVKSwapchain::initSurfaceImages(const VkSwapchainCreateInfoKHR* pCreateInfo
_presentableImages.push_back(_device->createPresentableSwapchainImage(&imgInfo, this, imgIdx, nullptr));
}
NSString* screenName = @"Main Screen";
auto* mtlLayer = getCAMetalLayer();
if (mtlLayer) {
NSString* screenName = @"Main Screen";
#if MVK_MACOS && !MVK_MACCAT
if ([mtlLayer.screenMVK respondsToSelector:@selector(localizedName)]) {
screenName = mtlLayer.screenMVK.localizedName;
}
auto* screen = mtlLayer.screenMVK;
if ([screen respondsToSelector:@selector(localizedName)]) {
screenName = screen.localizedName;
}
#endif
MVKLogInfo("Created %d swapchain images with size (%d, %d) and contents scale %.1f in layer %s (%p) on screen %s.",
imgCnt, imgExtent.width, imgExtent.height, mtlLayer.contentsScale, mtlLayer.name.UTF8String, mtlLayer, screenName.UTF8String);
MVKLogInfo("Created %d swapchain images with size (%d, %d) and contents scale %.1f in layer %s (%p) on screen %s.",
imgCnt, imgExtent.width, imgExtent.height, mtlLayer.contentsScale, mtlLayer.name.UTF8String, mtlLayer, screenName.UTF8String);
} else {
MVKLogInfo("Created %d swapchain images with size (%d, %d) on headless surface.", imgCnt, imgExtent.width, imgExtent.height);
}
}
void MVKSwapchain::destroy() {

View File

@ -128,7 +128,7 @@ uint64_t MVKSemaphoreMTLEvent::deferSignal() {
}
void MVKSemaphoreMTLEvent::encodeDeferredSignal(id<MTLCommandBuffer> mtlCmdBuff, uint64_t deferToken) {
if (mtlCmdBuff) { [mtlCmdBuff encodeSignalEvent: _mtlEvent value: deferToken]; }
[mtlCmdBuff encodeSignalEvent: _mtlEvent value: deferToken];
}
MVKSemaphoreMTLEvent::MVKSemaphoreMTLEvent(MVKDevice* device,

View File

@ -109,6 +109,7 @@ MVK_EXTENSION(EXT_extended_dynamic_state3, EXT_EXTENDED_DYNAMIC_STATE
MVK_EXTENSION(EXT_external_memory_host, EXT_EXTERNAL_MEMORY_HOST, DEVICE, 10.11, 8.0, 1.0)
MVK_EXTENSION(EXT_fragment_shader_interlock, EXT_FRAGMENT_SHADER_INTERLOCK, DEVICE, 10.13, 11.0, 1.0)
MVK_EXTENSION(EXT_hdr_metadata, EXT_HDR_METADATA, DEVICE, 10.15, MVK_NA, MVK_NA)
MVK_EXTENSION(EXT_headless_surface, EXT_HEADLESS_SURFACE, INSTANCE, 10.11, 8.0, 1.0)
MVK_EXTENSION(EXT_host_query_reset, EXT_HOST_QUERY_RESET, DEVICE, 10.11, 8.0, 1.0)
MVK_EXTENSION(EXT_image_robustness, EXT_IMAGE_ROBUSTNESS, DEVICE, 10.11, 8.0, 1.0)
MVK_EXTENSION(EXT_inline_uniform_block, EXT_INLINE_UNIFORM_BLOCK, DEVICE, 10.11, 8.0, 1.0)

View File

@ -23,12 +23,10 @@
#import <QuartzCore/QuartzCore.h>
#if MVK_IOS_OR_TVOS || MVK_MACCAT
# define PLATFORM_SCREEN_CLASS UIScreen
# include <UIKit/UIScreen.h>
#endif
#if MVK_MACOS && !MVK_MACCAT
# define PLATFORM_SCREEN_CLASS NSScreen
# include <AppKit/NSScreen.h>
#endif
@ -76,9 +74,16 @@
*/
@property(nonatomic, readwrite) CFStringRef colorspaceNameMVK;
#if !MVK_VISIONOS
#if MVK_IOS_OR_TVOS || MVK_MACCAT
/** Returns the screen on which this layer is rendering. */
@property(nonatomic, readonly) PLATFORM_SCREEN_CLASS* screenMVK;
@property(nonatomic, readonly) UIScreen* screenMVK;
#endif
#if MVK_MACOS && !MVK_MACCAT
/** Returns the screen on which this layer is rendering. */
@property(nonatomic, readonly) NSScreen* screenMVK;
@property(nonatomic, readonly) NSScreen* privateScreenMVKImpl;
#endif
@end

View File

@ -18,6 +18,7 @@
#include "CAMetalLayer+MoltenVK.h"
#include "MVKOSExtensions.h"
#if MVK_MACOS && !MVK_MACCAT
# include <AppKit/NSApplication.h>
@ -88,6 +89,13 @@
#if MVK_MACOS && !MVK_MACCAT
-(NSScreen*) screenMVK {
__block NSScreen* screen;
mvkDispatchToMainAndWait(^{ screen = self.privateScreenMVKImpl; });
return screen;
}
// Search for the screen currently displaying the layer, and default to the main screen if it can't be found.
-(NSScreen*) privateScreenMVKImpl {
// If this layer has a delegate that is an NSView, and the view is in a window, retrieve the screen from the window.
if ([self.delegate isKindOfClass: NSView.class]) {
NSWindow* window = ((NSView*)self.delegate).window;

View File

@ -3869,6 +3869,26 @@ MVK_PUBLIC_VULKAN_SYMBOL void vkSetHdrMetadataEXT(
}
#pragma mark -
#pragma mark VK_EXT_headless_surface extension
MVK_PUBLIC_VULKAN_SYMBOL VkResult vkCreateHeadlessSurfaceEXT(
VkInstance instance,
const VkHeadlessSurfaceCreateInfoEXT* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkSurfaceKHR* pSurface) {
MVKTraceVulkanCallStart();
MVKInstance* mvkInst = MVKInstance::getMVKInstance(instance);
MVKSurface* mvkSrfc = mvkInst->createSurface(pCreateInfo, pAllocator);
*pSurface = (VkSurfaceKHR)mvkSrfc;
VkResult rslt = mvkSrfc->getConfigurationResult();
if (rslt < 0) { *pSurface = VK_NULL_HANDLE; mvkInst->destroySurface(mvkSrfc, pAllocator); }
MVKTraceVulkanCallEnd();
return rslt;
}
#pragma mark -
#pragma mark VK_EXT_host_query_reset extension

View File

@ -149,21 +149,14 @@ for which to build the external libraries. The platform choices include:
--maccat
--tvos
--tvossim
--visionos
--visionossim
The `visionos` and `visionossim` selections require Xcode 15+.
You can specify multiple of these selections. The result is a single `XCFramework`
for each external dependency library, with each `XCFramework` containing binaries for
each of the requested platforms.
The `--all` selection is the same as entering all of the other platform choices, except
`--visionos` and `--visionossim`, and will result in a single `XCFramework` for each
external dependency library, with each `XCFramework` containing binaries for all supported
platforms and simulators. The `--visionos` and `--visionossim` selections must be invoked
with a separate invocation of `fetchDependencies`, because those selections require
Xcode 15+, and will cause a multi-platform build on older versions of Xcode to abort.
The `--all` selection is the same as entering all of the other platform choices,
and will result in a single `XCFramework` for each external dependency library,
with each `XCFramework` containing binaries for all supported platforms and simulators.
Running `fetchDependencies` repeatedly with different platforms will accumulate targets
in the `XCFramework`, if the `--keep-cache` option is used on each invocation.
@ -263,8 +256,6 @@ from the command line. The following `make` targets are provided:
make maccat
make tvos
make tvossim
make visionos
make visionossim
make all-debug
make macos-debug
@ -273,15 +264,12 @@ from the command line. The following `make` targets are provided:
make maccat-debug
make tvos-debug
make tvossim-debug
make visionos-debug
make visionossim-debug
make clean
make install
- Running `make` repeatedly with different targets will accumulate binaries for these different targets.
- The `all` target executes all platform targets, except `visionos` and `visionossim`, as these require
Xcode 15+, and will abort a multi-platform build on older versions of Xcode.
- The `all` target executes all platform targets.
- The `all` target is the default target. Running `make` with no arguments is the same as running `make all`.
- The `*-debug` targets build the binaries using the **_Debug_** configuration.
- The `install` target will copy the most recently built `MoltenVK.xcframework` into the