diff --git a/gfx/display_servers/dispserv_apple.m b/gfx/display_servers/dispserv_apple.m new file mode 100644 index 0000000000..240f226e50 --- /dev/null +++ b/gfx/display_servers/dispserv_apple.m @@ -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 . + */ + +#import +#ifdef IOS +#import +#else +#import +#endif +#include +#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 +#import +#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" +}; diff --git a/gfx/drivers_context/cocoa_gl_ctx.m b/gfx/drivers_context/cocoa_gl_ctx.m index 16f62b0f70..8875bd8fb0 100644 --- a/gfx/drivers_context/cocoa_gl_ctx.m +++ b/gfx/drivers_context/cocoa_gl_ctx.m @@ -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 */ diff --git a/gfx/drivers_context/cocoa_vk_ctx.m b/gfx/drivers_context/cocoa_vk_ctx.m index 7738d22aa5..9e732dc319 100755 --- a/gfx/drivers_context/cocoa_vk_ctx.m +++ b/gfx/drivers_context/cocoa_vk_ctx.m @@ -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 */ diff --git a/gfx/video_display_server.h b/gfx/video_display_server.h index be6a7088cc..cff4b6eebf 100644 --- a/gfx/video_display_server.h +++ b/gfx/video_display_server.h @@ -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 diff --git a/gfx/video_driver.c b/gfx/video_driver.c index ad2735deba..4ffa368676 100644 --- a/gfx/video_driver.c +++ b/gfx/video_driver.c @@ -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 diff --git a/griffin/griffin_objc.m b/griffin/griffin_objc.m index c37b9b5630..68c6ac6e47 100644 --- a/griffin/griffin_objc.m +++ b/griffin/griffin_objc.m @@ -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" diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 4c1fa82562..d9ff8b2a9c 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -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; diff --git a/pkg/apple/iOS/Info.plist b/pkg/apple/iOS/Info.plist index 238c84fb73..40df489f54 100644 --- a/pkg/apple/iOS/Info.plist +++ b/pkg/apple/iOS/Info.plist @@ -6,6 +6,8 @@ $(PRODUCT_BUNDLE_IDENTIFIER) ALTDeviceID $(TARGET_DEVICE_IDENTIFIER) + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion en CFBundleDisplayName diff --git a/ui/drivers/cocoa/apple_platform.h b/ui/drivers/cocoa/apple_platform.h index 9e9514f37a..66013794af 100644 --- a/ui/drivers/cocoa/apple_platform.h +++ b/ui/drivers/cocoa/apple_platform.h @@ -16,6 +16,8 @@ extern void ios_show_file_sheet(void); #ifdef __OBJC__ +#import + #ifdef HAVE_METAL #import #import @@ -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 + @interface RetroArch_iOS : UINavigationController { UIView *_renderView; @@ -94,7 +95,11 @@ UINavigationControllerDelegate> { - (void)refreshSystemConfig; @end + #else + +#import + #if defined(HAVE_COCOA_METAL) @interface RetroArch_OSX : NSObject { #elif (defined(__MACH__) && defined(MAC_OS_X_VERSION_MAX_ALLOWED) && (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)) diff --git a/ui/drivers/cocoa/cocoa_common.h b/ui/drivers/cocoa/cocoa_common.h index 13cb0d5c59..15d88430b1 100644 --- a/ui/drivers/cocoa/cocoa_common.h +++ b/ui/drivers/cocoa/cocoa_common.h @@ -18,6 +18,7 @@ #define __COCOA_COMMON_SHARED_H #include +#include #if defined(HAVE_COCOATOUCH) #include @@ -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 diff --git a/ui/drivers/cocoa/cocoa_common.m b/ui/drivers/cocoa/cocoa_common.m index fd90d0f310..8fed836beb 100644 --- a/ui/drivers/cocoa/cocoa_common.m +++ b/ui/drivers/cocoa/cocoa_common.m @@ -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; } diff --git a/ui/drivers/ui_cocoa.m b/ui/drivers/ui_cocoa.m index 4bcbe7d81a..0c2d405554 100644 --- a/ui/drivers/ui_cocoa.m +++ b/ui/drivers/ui_cocoa.m @@ -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)