Merge pull request #1669 from billhollings/fix-retrieve-screen-refresh-duration
Fix retrieval of accurate refresh duration across multiple display screens.
diff --git a/Docs/MoltenVK_Runtime_UserGuide.md b/Docs/MoltenVK_Runtime_UserGuide.md
index 155e1fd..ad840f5 100644
--- a/Docs/MoltenVK_Runtime_UserGuide.md
+++ b/Docs/MoltenVK_Runtime_UserGuide.md
@@ -143,7 +143,7 @@
- `MoltenVK/dylib/macOS` *(macOS)*
- `MoltenVK/dylib/iOS` *(iOS)*
- `MoltenVK/dylib/tvOS` *(tvOS)*
-
+
3. In the **Runpath Search Paths** (aka `LD_RUNPATH_SEARCH_PATHS`) setting,
add an entry that matches where the dynamic library will be located in your runtime
environment. If the dynamic library is to be embedded within your application,
@@ -188,11 +188,11 @@
- To copy the `libMoltenVK.dylib` file into your application or component library:
1. On the *Build Phases* tab, add a new *Copy Files* build phase.
-
+
2. Set the *Destination* into which you want to place the `libMoltenVK.dylib` file.
Typically this will be *Frameworks* (and it should match the **Runpath Search Paths**
(aka `LD_RUNPATH_SEARCH_PATHS`) build setting you added above).
-
+
3. Drag **_one_** of the following files to the *Copy Files* list in this new build phase:
- `MoltenVK/dylib/macOS/libMoltenVK.dylib` *(macOS)*
- `MoltenVK/dylib/iOS/libMoltenVK.dylib` *(iOS)*
@@ -229,7 +229,7 @@
When a *Metal* app is running from *Xcode*, the default ***Scheme*** settings may reduce performance.
To improve performance and gain the benefits of *Metal*, perform the following in *Xcode*:
-
+
1. Open the ***Scheme Editor*** for building your main application. You can do
this by selecting ***Edit Scheme...*** from the ***Scheme*** drop-down menu, or select
***Product -> Scheme -> Edit Scheme...*** from the main menu.
@@ -337,8 +337,25 @@
In order to visibly display your content on *macOS*, *iOS*, or *tvOS*, you must enable the
`VK_EXT_metal_surface` extension, and use the function defined in that extension to create a
-*Vulkan* rendering surface. You can enable the `VK_EXT_metal_surface` extension by defining the `VK_USE_PLATFORM_METAL_EXT` guard macro in your compiler build settings. See the description of
-the `mvk_vulkan.h` file below for a convenient way to enable this extension automatically.
+*Vulkan* rendering surface. You can enable the `VK_EXT_metal_surface` extension by defining
+the `VK_USE_PLATFORM_METAL_EXT` guard macro in your compiler build settings. See the description
+of the `mvk_vulkan.h` file below for a convenient way to enable this extension automatically.
+
+When creating a `CAMetalLayer` to underpin the *Vulkan* surface to render to, it is strongly
+recommended that you ensure the `delegate` of the `CAMetalLayer` is the `NSView/UIView` in
+which the layer is contained, to ensure correct and optimized *Vulkan* swapchain and refresh
+timing behavior across multiple display screens that might have different properties.
+
+The view will automatically be the `delegate` of the layer when the view creates the
+`CAMetalLayer`, as per Apple's documentation:
+
+>If the layer object was created by a view, the view typically assigns itself as the layer’s
+delegate automatically, and you should not change that relationship. For layers you create
+yourself, you can assign a delegate object and use that object to provide the contents of
+the layer dynamically and perform other tasks.
+
+But in the case where you create the `CAMetalLayer` yourself and assign it to the view,
+you should also assign the view as the `delegate` of the layer.
Because **MoltenVK** supports the `VK_KHR_portability_subset` extension, when using the
*Vulkan Loader* from the *Vulkan SDK* to run **MoltenVK** on *macOS*, the *Vulkan Loader*
diff --git a/Docs/Whats_New.md b/Docs/Whats_New.md
index 1a6bef6..4f82bfb 100644
--- a/Docs/Whats_New.md
+++ b/Docs/Whats_New.md
@@ -27,10 +27,11 @@
- Work around MTLCounterSet crash on additional Intel Iris Plus Graphics drivers.
- Check `MTLDevice` to enable support for `VK_KHR_fragment_shader_barycentric`
and `VK_NV_fragment_shader_barycentric` extensions.
+- Ignore sampler update in descriptor set bindings that use immutable samplers.
- Fix query pool wait block when query is not encoded to be written to.
- Fix `vkUpdateDescriptorSetWithTemplate()` for inline block descriptors.
-- Ignore sampler update in descriptor set bindings that use immutable samplers.
-- Update _macOS Cube_ demo to demonstrate optimizing swapchain across multiple screens.
+- Fix retrieval of accurate `vkGetRefreshCycleDurationGOOGLE()` across multiple display screens.
+- Update _macOS Cube_ demo to demonstrate optimizing the swapchain across multiple display screens.
- Update `VK_MVK_MOLTENVK_SPEC_VERSION` to version `35`.
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
index 137eaa1..d614790 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
@@ -26,20 +26,8 @@
#include "MVKWatermarkTextureContent.h"
#include "MVKWatermarkShaderSource.h"
#include "mvk_datatypes.hpp"
-#import "CAMetalLayer+MoltenVK.h"
#import "MVKBlockObserver.h"
-#if MVK_IOS_OR_TVOS || MVK_MACCAT
-# include <UIKit/UIScreen.h>
-#endif
-
-#if MVK_MACOS && !MVK_MACCAT
-# include <AppKit/NSApplication.h>
-# include <AppKit/NSScreen.h>
-# include <AppKit/NSWindow.h>
-# include <AppKit/NSView.h>
-#endif
-
#include <libkern/OSByteOrder.h>
using namespace std;
@@ -409,8 +397,13 @@
_presentableImages.push_back(_device->createPresentableSwapchainImage(&imgInfo, this, imgIdx, NULL));
}
- MVKLogInfo("Created %d swapchain images with initial size (%d, %d) and contents scale %.1f.",
- imgCnt, imgExtent.width, imgExtent.height, _mtlLayer.contentsScale);
+#if MVK_MACOS && !MVK_MACCAT
+ NSString* screenName = _mtlLayer.screenMVK.localizedName;
+#else
+ NSString* screenName = @"Main Screen";
+#endif
+ MVKLogInfo("Created %d swapchain images with initial size (%d, %d) and contents scale %.1f for screen %s.",
+ imgCnt, imgExtent.width, imgExtent.height, _mtlLayer.contentsScale, screenName.UTF8String);
}
VkResult MVKSwapchain::getRefreshCycleDuration(VkRefreshCycleDurationGOOGLE *pRefreshCycleDuration) {
@@ -418,33 +411,17 @@
#if MVK_IOS_OR_TVOS || MVK_MACCAT
NSInteger framesPerSecond = 60;
- UIScreen* screen = [UIScreen mainScreen];
+ UIScreen* screen = _mtlLayer.screenMVK;
if ([screen respondsToSelector: @selector(maximumFramesPerSecond)]) {
framesPerSecond = screen.maximumFramesPerSecond;
}
#endif
-
#if MVK_MACOS && !MVK_MACCAT
- // Find the screen for the window whose content view is backed by the swapchains CAMetalLayer.
- // Default to the mainScreen if no such window can be found.
- NSScreen* screen = [NSScreen mainScreen];
- CALayer* layer = _mtlLayer;
- while (layer.superlayer) {
- layer = layer.superlayer;
- }
-
- for (NSWindow* window in [[NSApplication sharedApplication] windows]) {
- NSView *view = [window contentView];
- if (view && ([view layer] == layer)) { // Check against layer and not _mtlLayer.
- screen = [window screen];
- }
- }
-
+ 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];
diff --git a/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.h b/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.h
index afe487f..0cf039b 100644
--- a/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.h
+++ b/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.h
@@ -18,8 +18,20 @@
#pragma once
+#include "MVKCommonEnvironment.h"
+
#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
+
/** Extensions to CAMetalLayer to support MoltenVK. */
@interface CAMetalLayer (MoltenVK)
@@ -73,4 +85,7 @@
*/
@property(nonatomic, readwrite) CFStringRef colorspaceNameMVK;
+/** Returns the screen on which this layer is rendering. */
+@property(nonatomic, readonly) PLATFORM_SCREEN_CLASS* screenMVK;
+
@end
diff --git a/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.m b/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.m
index 2da3118..0214e8b 100644
--- a/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.m
+++ b/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.m
@@ -18,7 +18,13 @@
#include "CAMetalLayer+MoltenVK.h"
-#include "MVKCommonEnvironment.h"
+
+#if MVK_MACOS && !MVK_MACCAT
+# include <AppKit/NSApplication.h>
+# include <AppKit/NSWindow.h>
+# include <AppKit/NSView.h>
+#endif
+
@implementation CAMetalLayer (MoltenVK)
@@ -84,4 +90,33 @@
CGColorSpaceRelease(csRef);
}
+#if MVK_IOS_OR_TVOS || MVK_MACCAT
+-(UIScreen*) screenMVK {
+ return UIScreen.mainScreen;
+}
+#endif
+
+#if MVK_MACOS && !MVK_MACCAT
+-(NSScreen*) screenMVK {
+ // 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;
+ if (window) { return window.screen; }
+ } else {
+ // Otherwise we need to iterate through all the windows used by this app, and
+ // check if the content view is using this layer or one of its ancestor layers.
+ // If a match is found, retrieve the screen from the window. It is not sufficient
+ // to first search for the top structural layer, because Core Animation may add
+ // a superlayer to the CAMetalLayer, independent of the content view.
+ for (NSWindow* window in NSApplication.sharedApplication.windows) {
+ CALayer* windowContentLayer = window.contentView.layer;
+ for (CALayer* layer = self; layer; layer = layer.superlayer) {
+ if (layer == windowContentLayer) { return window.screen; }
+ }
+ }
+ }
+ return NSScreen.mainScreen; // Default to main screen if not found
+}
+#endif
+
@end