ruby: Various Mac driver settings fix-ups (#1486)

This PR applies several minor fixes and improvements to macOS driver
settings and the driver settings pane.

#### Remove the "exclusive mode" video option on macOS.

* There is no real notion of exclusive presentation on macOS beyond what
already exists in normal fullscreen. If an application is covering the
screen and no other application or window is visible, the system
automatically switches to a "direct" presentation mode that optimizes
for single-application presentation performance. There is not a good
reason to show this option on macOS, even disabled.
* By contrast, it is possible (though not implemented by ares) to enter
exclusive mode on the audio device with CoreAudio, so leave that option
there, just disabled.
#### Add a "use native fullscreen" option on macOS.
* There are various good reasons to prefer either native platform
fullscreen behavior, or a custom borderless windowed fullscreen. Rather
than guess what the user wants, offer an option.
* If unchecked, make the window title bar enlarge the window rather than
fullscreen it, so we don't mix behaviors.
#### Implement fullscreen monitor selection behavior for Metal, and
correctly enumerate the user's monitor names.
* Fullscreen display on the selected monitor in the settings pane was
previously not implemented on macOS. This implementation only works if
"Use native fullscreen" is disabled, since the macOS fullscreen idiom
doesn't feature selecting a specific display.
* Additionally, the old function to retrieve the monitor's localized
name did not work reliably on newer macOS versions. Use the modern
property `localizedName` on NSScreen for macOS versions above 10.15, and
fall back to the old implementation otherwise.
* Implementing this meant adding a `uintptr` handle to the NSScreen
instance in the `Monitor` struct in ruby that uses a bridged cast to
interface with Objective-C. I would have preferred not to do this, but I
didn't see another good way to handle getting the `NSScreen` instance
that didn't involve a serious refactor.
#### (all platforms) Disable monitor selection if the video driver's
`hasMonitor()` is false.
* Just a minor fixup; the existing behavior is that the dropdown list
can be navigated and selected, but the selection does not persist. It
makes more sense to just disable it if the driver doesn't support it.

Co-authored-by: jcm <butt@butts.com>
このコミットが含まれているのは:
jcm 2024-05-05 10:11:35 -05:00 committed by GitHub
コミット 67fc9ea81b
この署名に対応する既知のキーがデータベースに存在しません
GPGキーID: B5690EEEBB952194
7個のファイルの変更97行の追加43行の削除

ファイルの表示

@ -27,10 +27,12 @@ auto DriverSettings::construct() -> void {
program.videoFormatUpdate();
videoRefresh();
});
#if !defined(PLATFORM_MACOS)
videoExclusiveToggle.setText("Exclusive mode").onToggle([&] {
settings.video.exclusive = videoExclusiveToggle.checked();
ruby::video.setExclusive(settings.video.exclusive);
});
#endif
videoBlockingToggle.setText("Synchronize").onToggle([&] {
settings.video.blocking = videoBlockingToggle.checked();
ruby::video.setBlocking(settings.video.blocking);
@ -48,6 +50,11 @@ auto DriverSettings::construct() -> void {
settings.video.threadedRenderer = videoThreadedRendererToggle.checked();
ruby::video.setThreadedRenderer(settings.video.threadedRenderer);
});
videoNativeFullScreenToggle.setText("Use native fullscreen").onToggle([&] {
settings.video.nativeFullScreen = videoNativeFullScreenToggle.checked();
ruby::video.setNativeFullScreen(settings.video.nativeFullScreen);
videoRefresh();
});
#endif
audioLabel.setText("Audio").setFont(Font().setBold());
@ -153,13 +160,16 @@ auto DriverSettings::videoRefresh() -> void {
item.setText(format);
if(format == ruby::video.format()) item.setSelected();
}
videoMonitorList.setEnabled(videoMonitorList.itemCount() > 1);
videoMonitorList.setEnabled(videoMonitorList.itemCount() > 1 && ruby::video.hasMonitor());
videoFormatList.setEnabled(0 && videoFormatList.itemCount() > 1);
#if !defined(PLATFORM_MACOS)
videoExclusiveToggle.setChecked(ruby::video.exclusive()).setEnabled(ruby::video.hasExclusive());
#endif
videoBlockingToggle.setChecked(ruby::video.blocking()).setEnabled(ruby::video.hasBlocking());
#if defined(PLATFORM_MACOS)
videoColorSpaceToggle.setChecked(ruby::video.forceSRGB()).setEnabled(ruby::video.hasForceSRGB());
videoThreadedRendererToggle.setChecked(ruby::video.threadedRenderer()).setEnabled(ruby::video.hasThreadedRenderer());
videoNativeFullScreenToggle.setChecked(ruby::video.nativeFullScreen()).setEnabled(ruby::video.hasNativeFullScreen());
#endif
videoFlushToggle.setChecked(ruby::video.flush()).setEnabled(ruby::video.hasFlush());
VerticalLayout::resize();

ファイルの表示

@ -58,6 +58,7 @@ auto Settings::process(bool load) -> void {
bind(boolean, "Video/Blocking", video.blocking);
bind(boolean, "Video/PresentSRGB", video.forceSRGB);
bind(boolean, "Video/ThreadedRenderer", video.threadedRenderer);
bind(boolean, "Video/NativeFullScreen", video.nativeFullScreen);
bind(boolean, "Video/Flush", video.flush);
bind(string, "Video/Shader", video.shader);
bind(natural, "Video/Multiplier", video.multiplier);

ファイルの表示

@ -13,6 +13,7 @@ struct Settings : Markup::Node {
bool blocking = false;
bool forceSRGB = false;
bool threadedRenderer = true;
bool nativeFullScreen = false;
bool flush = false;
string shader = "None";
u32 multiplier = 2;
@ -333,12 +334,15 @@ struct DriverSettings : VerticalLayout {
Label videoFormatLabel{&videoPropertyLayout, Size{0, 0}};
ComboButton videoFormatList{&videoPropertyLayout, Size{0, 0}};
HorizontalLayout videoToggleLayout{this, Size{~0, 0}};
#if !defined(PLATFORM_MACOS)
CheckLabel videoExclusiveToggle{&videoToggleLayout, Size{0, 0}};
#endif
CheckLabel videoBlockingToggle{&videoToggleLayout, Size{0, 0}};
CheckLabel videoFlushToggle{&videoToggleLayout, Size{0, 0}};
#if defined(PLATFORM_MACOS)
CheckLabel videoColorSpaceToggle{&videoToggleLayout, Size{0, 0}};
CheckLabel videoThreadedRendererToggle{&videoToggleLayout, Size{0, 0}};
CheckLabel videoNativeFullScreenToggle{&videoToggleLayout, Size{0, 0}};
#endif
//
Label audioLabel{this, Size{~0, 0}, 5};

ファイルの表示

@ -31,7 +31,9 @@ struct VideoMetal : VideoDriver, Metal {
auto ready() -> bool override { return _ready; }
auto hasFullScreen() -> bool override { return true; }
auto hasMonitor() -> bool override { return !_nativeFullScreen; }
auto hasContext() -> bool override { return true; }
auto hasFlush() -> bool override { return true; }
auto hasBlocking() -> bool override {
if (@available(macOS 10.15.4, *)) {
return true;
@ -41,31 +43,34 @@ struct VideoMetal : VideoDriver, Metal {
}
auto hasForceSRGB() -> bool override { return true; }
auto hasThreadedRenderer() -> bool override { return true; }
auto hasFlush() -> bool override { return true; }
auto hasNativeFullScreen() -> bool override { return true; }
auto hasShader() -> bool override { return true; }
auto setFullScreen(bool fullScreen) -> bool override {
/// This function implements non-idiomatic macOS fullscreen behavior that sets the window frame equal to the display's
/// frame size and hides the cursor. Idiomatic fullscreen is still available via the normal stoplight window controls. This
/// version of fullscreen is desirable because it allows us to render around the camera housing on newer Macs
/// (important for bezel-style shaders), has snappier entrance/exit and tabbing behavior, and functions better with
/// recording and capture software such as OBS and screen recorders. Hiding the mouse cursor is also essential to
/// rendering with appropriate frame pacing in Metal's 'direct' presentation mode.
// todo: unify with cursor auto-hide in hiro, ideally ares-wide fullscreen mode option
if (fullScreen) {
frameBeforeFullScreen = view.window.frame;
[NSApp setPresentationOptions:(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)];
[view.window setStyleMask:NSWindowStyleMaskBorderless];
[view.window setFrame:view.window.screen.frame display:YES];
[NSCursor setHiddenUntilMouseMoves:YES];
// todo: fix/make consistent mouse cursor hide behavior
if (_nativeFullScreen) {
[view.window toggleFullScreen:nil];
} else {
[NSApp setPresentationOptions:NSApplicationPresentationDefault];
[view.window setStyleMask:NSWindowStyleMaskTitled];
[view.window setFrame:frameBeforeFullScreen display:YES];
/// This option implements non-idiomatic macOS fullscreen behavior that sets the window frame equal to the selected display's
/// frame size and hides the cursor. This version of fullscreen is desirable because it allows us to render around the camera
/// housing on newer Macs (important for bezel-style shaders), has snappier entrance/exit and tabbing behavior, and functions
/// better with recording and capture software such as OBS.
if (fullScreen) {
auto monitor = Video::monitor(self.monitor);
NSScreen *handle = (__bridge NSScreen *)(void *)monitor.nativeHandle; //eew
frameBeforeFullScreen = view.window.frame;
[NSApp setPresentationOptions:(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)];
[view.window setStyleMask:NSWindowStyleMaskBorderless];
[view.window setFrame:handle.frame display:YES];
[NSCursor setHiddenUntilMouseMoves:YES];
} else {
[NSApp setPresentationOptions:NSApplicationPresentationDefault];
[view.window setStyleMask:(NSWindowStyleMaskTitled | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable | NSWindowStyleMaskClosable)];
[view.window setFrame:frameBeforeFullScreen display:YES];
}
[view.window makeFirstResponder:view];
}
[view.window makeFirstResponder:view];
return true;
}
@ -92,6 +97,18 @@ struct VideoMetal : VideoDriver, Metal {
_threaded = threadedRenderer;
return true;
}
auto setNativeFullScreen(bool nativeFullScreen) -> bool override {
_nativeFullScreen = nativeFullScreen;
if (nativeFullScreen) {
//maximize goes fullscreen
[view.window setCollectionBehavior: NSWindowCollectionBehaviorFullScreenPrimary];
} else {
//maximize does not go fullscreen
[view.window setCollectionBehavior: NSWindowCollectionBehaviorFullScreenAuxiliary];
}
return true;
}
auto setFlush(bool flush) -> bool override {
_flush = flush;
@ -562,15 +579,16 @@ private:
bool forceSRGB = self.forceSRGB;
self.setForceSRGB(forceSRGB);
view.autoresizingMask = NSViewWidthSizable|NSViewHeightSizable;
_threaded = self.threadedRenderer;
_blocking = self.blocking;
setNativeFullScreen(self.nativeFullScreen);
_libra = librashader_load_instance();
if (!_libra.instance_loaded) {
print("Metal: Failed to load librashader: shaders will be disabled\n");
}
_blocking = self.blocking;
initialized = true;
return _ready = true;
}

ファイルの表示

@ -78,6 +78,7 @@ struct Metal {
bool _flush = false;
bool _vrrIsSupported = false;
bool _threaded = true;
bool _nativeFullScreen = false;
NSRect frameBeforeFullScreen = NSMakeRect(0,0,0,0);

ファイルの表示

@ -76,6 +76,14 @@ auto Video::setThreadedRenderer(bool threadedRenderer) -> bool {
return true;
}
auto Video::setNativeFullScreen(bool nativeFullScreen) -> bool {
lock_guard<recursive_mutex> lock(mutex);
if(instance->nativeFullScreen == nativeFullScreen) return true;
if(!instance->hasNativeFullScreen()) return false;
if(!instance->setNativeFullScreen(instance->nativeFullScreen = nativeFullScreen)) return false;
return true;
}
auto Video::setFlush(bool flush) -> bool {
lock_guard<recursive_mutex> lock(mutex);
if(instance->flush == flush) return true;
@ -300,28 +308,33 @@ auto Video::hasMonitors() -> vector<Monitor> {
monitor.y = rectangle.origin.y;
monitor.width = rectangle.size.width;
monitor.height = rectangle.size.height;
//getting the name of the monitor on macOS: "Think Different"
auto screenDictionary = [screen deviceDescription];
auto screenID = [screenDictionary objectForKey:@"NSScreenNumber"];
auto displayID = [screenID unsignedIntValue];
CFUUIDRef displayUUID = CGDisplayCreateUUIDFromDisplayID(displayID);
io_service_t displayPort = CGDisplayGetDisplayIDFromUUID(displayUUID);
auto dictionary = IODisplayCreateInfoDictionary(displayPort, 0);
CFRetain(dictionary);
if(auto names = (CFDictionaryRef)CFDictionaryGetValue(dictionary, CFSTR(kDisplayProductName))) {
auto languageKeys = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
CFDictionaryApplyFunction(names, MonitorKeyArrayCallback, (void*)languageKeys);
auto orderLanguageKeys = CFBundleCopyPreferredLocalizationsFromArray(languageKeys);
CFRelease(languageKeys);
if(orderLanguageKeys && CFArrayGetCount(orderLanguageKeys)) {
auto languageKey = CFArrayGetValueAtIndex(orderLanguageKeys, 0);
auto localName = CFDictionaryGetValue(names, languageKey);
monitor.name = {1 + monitors.size(), ": ", [(__bridge NSString*)localName UTF8String]};
CFRelease(localName);
monitor.nativeHandle = (uintptr)screen;
if (@available(macOS 10.15, *)) {
monitor.name = {1 + monitors.size(), ": ", screen.localizedName.UTF8String};
} else {
//getting the name of the monitor on macOS: "Think Different"
auto screenDictionary = [screen deviceDescription];
auto screenID = [screenDictionary objectForKey:@"NSScreenNumber"];
auto displayID = [screenID unsignedIntValue];
CFUUIDRef displayUUID = CGDisplayCreateUUIDFromDisplayID(displayID);
io_service_t displayPort = CGDisplayGetDisplayIDFromUUID(displayUUID);
auto dictionary = IODisplayCreateInfoDictionary(displayPort, 0);
CFRetain(dictionary);
if(auto names = (CFDictionaryRef)CFDictionaryGetValue(dictionary, CFSTR(kDisplayProductName))) {
auto languageKeys = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
CFDictionaryApplyFunction(names, MonitorKeyArrayCallback, (void*)languageKeys);
auto orderLanguageKeys = CFBundleCopyPreferredLocalizationsFromArray(languageKeys);
CFRelease(languageKeys);
if(orderLanguageKeys && CFArrayGetCount(orderLanguageKeys)) {
auto languageKey = CFArrayGetValueAtIndex(orderLanguageKeys, 0);
auto localName = CFDictionaryGetValue(names, languageKey);
monitor.name = {1 + monitors.size(), ": ", [(__bridge NSString*)localName UTF8String]};
CFRelease(localName);
}
CFRelease(orderLanguageKeys);
}
CFRelease(orderLanguageKeys);
CFRelease(dictionary);
}
CFRelease(dictionary);
monitors.append(monitor);
}
}

ファイルの表示

@ -15,6 +15,7 @@ struct VideoDriver {
virtual auto hasBlocking() -> bool { return false; }
virtual auto hasForceSRGB() -> bool { return false; }
virtual auto hasThreadedRenderer() -> bool { return false; }
virtual auto hasNativeFullScreen() -> bool { return false; }
virtual auto hasFlush() -> bool { return false; }
virtual auto hasFormats() -> vector<string> { return {"ARGB24"}; }
virtual auto hasShader() -> bool { return false; }
@ -28,6 +29,7 @@ struct VideoDriver {
virtual auto setBlocking(bool blocking) -> bool { return true; }
virtual auto setForceSRGB(bool forceSRGB) -> bool { return true; }
virtual auto setThreadedRenderer(bool threadedRenderer) -> bool { return true; }
virtual auto setNativeFullScreen(bool nativeFullScreen) -> bool { return true; }
virtual auto setFlush(bool flush) -> bool { return true; }
virtual auto setFormat(string format) -> bool { return true; }
virtual auto setShader(string shader) -> bool { return true; }
@ -52,6 +54,7 @@ protected:
bool blocking = false;
bool forceSRGB = false;
bool threadedRenderer = true;
bool nativeFullScreen = false;
bool flush = false;
string format = "ARGB24";
string shader = "None";
@ -70,6 +73,7 @@ struct Video {
s32 y = 0;
s32 width = 0;
s32 height = 0;
uintptr_t nativeHandle = 0;
};
static auto monitor(string name) -> Monitor;
static auto hasMonitors() -> vector<Monitor>;
@ -94,6 +98,7 @@ struct Video {
auto hasBlocking() -> bool { return instance->hasBlocking(); }
auto hasForceSRGB() -> bool { return instance->hasForceSRGB(); }
auto hasThreadedRenderer() -> bool { return instance->hasThreadedRenderer(); }
auto hasNativeFullScreen() -> bool { return instance->hasNativeFullScreen(); }
auto hasFlush() -> bool { return instance->hasFlush(); }
auto hasFormats() -> vector<string> { return instance->hasFormats(); }
auto hasShader() -> bool { return instance->hasShader(); }
@ -107,6 +112,7 @@ struct Video {
auto blocking() -> bool { return instance->blocking; }
auto forceSRGB() -> bool { return instance->forceSRGB; }
auto threadedRenderer() -> bool { return instance->threadedRenderer; }
auto nativeFullScreen() -> bool { return instance->nativeFullScreen; }
auto flush() -> bool { return instance->flush; }
auto format() -> string { return instance->format; }
auto shader() -> string { return instance->shader; }
@ -118,6 +124,7 @@ struct Video {
auto setBlocking(bool blocking) -> bool;
auto setForceSRGB(bool forceSRGB) -> bool;
auto setThreadedRenderer(bool threadedRenderer) -> bool;
auto setNativeFullScreen(bool nativeFullScreen) -> bool;
auto setFlush(bool flush) -> bool;
auto setFormat(string format) -> bool;
auto setShader(string shader) -> bool;