apple: display server, including ProMotion support (#16963)

This commit is contained in:
Eric Warmenhoven 2024-09-05 01:52:59 -04:00 committed by GitHub
parent 0c6a93311c
commit 60ee32b879
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 270 additions and 26 deletions

View File

@ -0,0 +1,193 @@
/* RetroArch - A frontend for libretro.
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#import <TargetConditionals.h>
#ifdef IOS
#import <UIKit/UIKit.h>
#else
#import <AppKit/AppKit.h>
#endif
#include <stddef.h>
#include "../verbosity.h"
#include "../video_display_server.h"
#include "../video_driver.h"
#include "../../ui/drivers/cocoa/apple_platform.h"
#include "../../ui/drivers/cocoa/cocoa_common.h"
#ifdef OSX
#import <AppKit/AppKit.h>
#import <CoreGraphics/CoreGraphics.h>
#endif
#ifdef OSX
static bool apple_display_server_set_window_opacity(void *data, unsigned opacity)
{
settings_t *settings = config_get_ptr();
bool windowed_full = settings->bools.video_windowed_fullscreen;
NSWindow *window = ((RetroArch_OSX*)[[NSApplication sharedApplication] delegate]).window;
if (windowed_full || !window.keyWindow)
return false;
window.alphaValue = (CGFloat)opacity / (CGFloat)100.0f;
return true;
}
static bool apple_display_server_set_window_progress(void *data, int progress, bool finished)
{
static NSProgressIndicator *indicator;
static dispatch_once_t once;
dispatch_once(&once, ^{
NSDockTile *dockTile = [NSApp dockTile];
NSImageView *iv = [[NSImageView alloc] init];
[iv setImage:[[NSApplication sharedApplication] applicationIconImage]];
[dockTile setContentView:iv];
indicator = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(0, 0, dockTile.size.width, 20)];
indicator.indeterminate = NO;
indicator.minValue = 0;
indicator.maxValue = 100;
indicator.doubleValue = 0;
// Create a custom view for the dock tile
[iv addSubview:indicator];
});
if (finished)
indicator.doubleValue = (double)-1;
else
indicator.doubleValue = (double)progress;
indicator.hidden = finished;
[[NSApp dockTile] display];
return true;
}
static bool apple_display_server_set_window_decorations(void *data, bool on)
{
settings_t *settings = config_get_ptr();
bool windowed_full = settings->bools.video_windowed_fullscreen;
NSWindow *window = ((RetroArch_OSX*)[[NSApplication sharedApplication] delegate]).window;
if (windowed_full)
return false;
if (on)
window.styleMask |= NSWindowStyleMaskTitled;
else
window.styleMask &= ~NSWindowStyleMaskTitled;
return true;
}
#endif
static bool apple_display_server_set_resolution(void *data,
unsigned width, unsigned height, int int_hz, float hz,
int center, int monitor_index, int xoffset, int padjust)
{
#if (defined(OSX) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000)
if (@available(macOS 14, *))
[CocoaView get].displayLink.preferredFrameRateRange = CAFrameRateRangeMake(hz * 0.9, hz * 1.2, hz);
#elif defined(IOS)
#if (TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000) || (TARGET_OS_TV && __TV_OS_VERSION_MAX_ALLOWED >= 150000)
if (@available(iOS 15, tvOS 15, *))
[CocoaView get].displayLink.preferredFrameRateRange = CAFrameRateRangeMake(hz * 0.9, hz * 1.2, hz);
else
#endif
[CocoaView get].displayLink.preferredFramesPerSecond = hz;
#endif
return true;
}
static void *apple_display_server_get_resolution_list(
void *data, unsigned *len)
{
unsigned j = 0;
struct video_display_config *conf = NULL;
unsigned width, height;
NSMutableSet *rates = [NSMutableSet set];
double currentRate;
#ifdef OSX
NSRect bounds = [CocoaView get].bounds;
float scale = cocoa_screen_get_backing_scale_factor();
width = bounds.size.width * scale;
height = bounds.size.height * scale;
CGDirectDisplayID mainDisplayID = CGMainDisplayID();
CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(mainDisplayID);
currentRate = CGDisplayModeGetRefreshRate(currentMode);
CFRelease(currentMode);
CFArrayRef displayModes = CGDisplayCopyAllDisplayModes(mainDisplayID, NULL);
for (CFIndex i = 0; i < CFArrayGetCount(displayModes); i++)
{
CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(displayModes, i);
double refreshRate = CGDisplayModeGetRefreshRate(mode);
if (refreshRate > 0)
[rates addObject:@(refreshRate)];
}
CFRelease(displayModes);
#else
CGRect bounds = [CocoaView get].view.bounds;
float scale = cocoa_screen_get_native_scale();
width = bounds.size.width * scale;
height = bounds.size.height * scale;
UIScreen *mainScreen = [UIScreen mainScreen];
currentRate = mainScreen.maximumFramesPerSecond;
#if !TARGET_OS_TV
if (@available(iOS 15, *))
[rates addObjectsFromArray:@[@(24), @(30), @(40), @(48), @(60), @(120)]];
else
#endif
[rates addObject:@(mainScreen.maximumFramesPerSecond)];
#endif
NSArray *sorted = [[rates allObjects] sortedArrayUsingSelector:@selector(compare:)];
*len = (unsigned)[sorted count];
RARCH_LOG("Available screen refresh rates: %s\n", [[NSString stringWithFormat:@"%@", sorted] UTF8String]);
if (!(conf = (struct video_display_config*)calloc(*len, sizeof(struct video_display_config))))
return NULL;
for (j = 0; j < *len; j++)
{
NSNumber *rate = sorted[j];
conf[j].width = width;
conf[j].height = height;
conf[j].bpp = 32;
conf[j].refreshrate = [rate unsignedIntValue];
conf[j].refreshrate_float = [rate floatValue];
conf[j].interlaced = false;
conf[j].dblscan = false;
conf[j].idx = j;
conf[j].current = ([rate doubleValue] == currentRate);
}
return conf;
}
const video_display_server_t dispserv_apple = {
NULL, /* init */
NULL, /* destroy */
#ifdef OSX
apple_display_server_set_window_opacity,
apple_display_server_set_window_progress,
apple_display_server_set_window_decorations,
#else
NULL, /* set_window_opacity */
NULL, /* set_window_progress */
NULL, /* set_window_decorations */
#endif
apple_display_server_set_resolution,
apple_display_server_get_resolution_list,
NULL, /* get_output_options */
NULL, /* set_screen_orientation */
NULL, /* get_screen_orientation */
NULL, /* get_flags */
"apple"
};

View File

@ -232,6 +232,19 @@ static void cocoa_gl_gfx_ctx_get_video_size(void *data,
}
#endif
static float cocoa_gl_gfx_ctx_get_refresh_rate(void *data)
{
#ifdef OSX
CGDirectDisplayID mainDisplayID = CGMainDisplayID();
CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(mainDisplayID);
float currentRate = CGDisplayModeGetRefreshRate(currentMode);
CFRelease(currentMode);
return currentRate;
#else
return [UIScreen mainScreen].maximumFramesPerSecond;
#endif
}
static gfx_ctx_proc_t cocoa_gl_gfx_ctx_get_proc_address(const char *symbol_name)
{
return (gfx_ctx_proc_t)CFBundleGetFunctionPointerForName(
@ -532,7 +545,7 @@ const gfx_ctx_driver_t gfx_ctx_cocoagl = {
#else
cocoa_gl_gfx_ctx_get_video_size,
#endif
NULL, /* get_refresh_rate */
cocoa_gl_gfx_ctx_get_refresh_rate,
NULL, /* get_video_output_size */
NULL, /* get_video_output_prev */
NULL, /* get_video_output_next */

View File

@ -138,6 +138,19 @@ static void cocoa_vk_gfx_ctx_get_video_size(void *data,
}
#endif
static float cocoa_vk_gfx_ctx_get_refresh_rate(void *data)
{
#ifdef OSX
CGDirectDisplayID mainDisplayID = CGMainDisplayID();
CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(mainDisplayID);
float currentRate = CGDisplayModeGetRefreshRate(currentMode);
CFRelease(currentMode);
return currentRate;
#else
return [UIScreen mainScreen].maximumFramesPerSecond;
#endif
}
static gfx_ctx_proc_t cocoa_vk_gfx_ctx_get_proc_address(const char *symbol_name)
{
return NULL;
@ -353,7 +366,7 @@ const gfx_ctx_driver_t gfx_ctx_cocoavk = {
#else
cocoa_vk_gfx_ctx_get_video_size,
#endif
NULL, /* get_refresh_rate */
cocoa_vk_gfx_ctx_get_refresh_rate,
NULL, /* get_video_output_size */
NULL, /* get_video_output_prev */
NULL, /* get_video_output_next */

View File

@ -104,6 +104,7 @@ extern const video_display_server_t dispserv_win32;
extern const video_display_server_t dispserv_x11;
extern const video_display_server_t dispserv_kms;
extern const video_display_server_t dispserv_android;
extern const video_display_server_t dispserv_apple;
RETRO_END_DECLS

View File

@ -1103,6 +1103,8 @@ void* video_display_server_init(enum rarch_display_type type)
default:
#if defined(ANDROID)
current_display_server = &dispserv_android;
#elif defined(__APPLE__)
current_display_server = &dispserv_apple;
#else
current_display_server = &dispserv_null;
#endif

View File

@ -27,6 +27,8 @@
#define HAVE_COMPRESSION 1
#endif
#include "../gfx/display_servers/dispserv_apple.m"
#if defined(HAVE_COCOATOUCH) || defined(HAVE_COCOA) || defined(HAVE_COCOA_METAL)
#include "../ui/drivers/cocoa/cocoa_common.m"

View File

@ -13461,7 +13461,9 @@ static bool setting_append_list(
general_write_handler,
general_read_handler,
SD_FLAG_NONE);
#ifndef OSX
MENU_SETTINGS_LIST_CURRENT_ADD_CMD(list, list_info, CMD_EVENT_REINIT);
#endif
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
CONFIG_BOOL(
@ -14170,7 +14172,7 @@ static bool setting_append_list(
SD_FLAG_NONE
);
#if !defined(RARCH_MOBILE)
#if !defined(RARCH_MOBILE) || defined(IOS)
{
#if defined(HAVE_STEAM) && defined(HAVE_MIST)
bool on_deck = false;

View File

@ -6,6 +6,8 @@
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>ALTDeviceID</key>
<string>$(TARGET_DEVICE_IDENTIFIER)</string>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>

View File

@ -16,6 +16,8 @@ extern void ios_show_file_sheet(void);
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#ifdef HAVE_METAL
#import <Metal/Metal.h>
#import <MetalKit/MetalKit.h>
@ -53,9 +55,6 @@ typedef enum apple_view_type
* the displays should not sleep.
*/
- (bool)setDisableDisplaySleep:(bool)disable;
#if defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH)
- (void)updateWindowedMode;
#endif
@end
#endif
@ -76,6 +75,8 @@ void rarch_stop_draw_observer(void);
@end
#endif
#import <UIKit/UIKit.h>
@interface RetroArch_iOS : UINavigationController<ApplePlatform, UIApplicationDelegate,
UINavigationControllerDelegate> {
UIView *_renderView;
@ -94,7 +95,11 @@ UINavigationControllerDelegate> {
- (void)refreshSystemConfig;
@end
#else
#import <AppKit/AppKit.h>
#if defined(HAVE_COCOA_METAL)
@interface RetroArch_OSX : NSObject<ApplePlatform, NSApplicationDelegate> {
#elif (defined(__MACH__) && defined(MAC_OS_X_VERSION_MAX_ALLOWED) && (MAC_OS_X_VERSION_MAX_ALLOWED < 101200))

View File

@ -18,6 +18,7 @@
#define __COCOA_COMMON_SHARED_H
#include <Foundation/Foundation.h>
#include <QuartzCore/QuartzCore.h>
#if defined(HAVE_COCOATOUCH)
#include <UIKit/UIKit.h>
@ -73,6 +74,8 @@
@property(readwrite) UIInterfaceOrientation lockInterfaceOrientation;
#endif
@property(nonatomic,readwrite) CADisplayLink *displayLink;
+ (CocoaView*)get;
@end
@ -87,6 +90,10 @@ void get_ios_version(int *major, int *minor);
- (void)display;
#endif
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000
@property(nonatomic,readwrite) CADisplayLink *displayLink API_AVAILABLE(macos(14.0));
#endif
@end
#endif

View File

@ -104,6 +104,12 @@ void cocoa_file_load_with_detect_core(const char *filename);
- (void)scrollWheel:(NSEvent *)theEvent { }
#endif
#if !defined(OSX) || __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000
-(void)step:(CADisplayLink*)target API_AVAILABLE(macos(14.0), ios(3.1), tvos(3.1))
{
}
#endif
+ (CocoaView*)get
{
CocoaView *view = (BRIDGE CocoaView*)nsview_get_ptr();
@ -111,6 +117,21 @@ void cocoa_file_load_with_detect_core(const char *filename);
{
view = [CocoaView new];
nsview_set_ptr(view);
#if defined(IOS)
view.displayLink = [CADisplayLink displayLinkWithTarget:view selector:@selector(step:)];
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000 || __TV_OS_VERSION_MAX_ALLOWED >= 150000
if (@available(iOS 15.0, tvOS 15.0, *))
[view.displayLink setPreferredFrameRateRange:CAFrameRateRangeMake(60, 120, 120)];
#endif
[view.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
#elif defined(OSX) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000
if (@available(macOS 14.0, *))
{
view.displayLink = [view displayLinkWithTarget:view selector:@selector(step:)];
view.displayLink.preferredFrameRateRange = CAFrameRateRangeMake(60, 120, 120);
[view.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
#endif
}
return view;
}

View File

@ -533,7 +533,9 @@ static ui_application_t ui_application_cocoa = {
- (void)windowDidBecomeKey:(NSNotification *)notification
{
[apple_platform updateWindowedMode];
settings_t *settings = config_get_ptr();
video_display_server_set_window_opacity(settings->uints.video_window_opacity);
video_display_server_set_window_decorations(settings->bools.video_window_show_decorations);
}
- (void)windowDidMove:(NSNotification *)notification
@ -720,7 +722,6 @@ static ui_application_t ui_application_cocoa = {
if (is_fullscreen)
[self.window toggleFullScreen:self];
[self updateWindowedSize:mode];
[self updateWindowedMode];
}
/* HACK(sgc): ensure MTKView posts a drawable resize event */
@ -754,24 +755,6 @@ static ui_application_t ui_application_cocoa = {
[self.window setContentSize:NSMakeSize(mode.width, mode.height)];
}
- (void)updateWindowedMode
{
settings_t *settings = config_get_ptr();
bool windowed_full = settings->bools.video_windowed_fullscreen;
bool show_decorations = settings->bools.video_window_show_decorations;
CGFloat opacity = (CGFloat)settings->uints.video_window_opacity / (CGFloat)100.0;
if (windowed_full || !self.window.keyWindow)
return;
if (show_decorations)
self.window.styleMask |= NSWindowStyleMaskTitled;
else
self.window.styleMask &= ~NSWindowStyleMaskTitled;
self.window.alphaValue = opacity;
}
- (void)setCursorVisible:(bool)v
{
if (v)