From 51b666edef3bce628a32ea1cc6af53757a95b939 Mon Sep 17 00:00:00 2001 From: Rayhan Faizel Date: Thu, 3 Oct 2024 18:04:53 +0300 Subject: [PATCH] iOS: Add support for higher framerates on ProMotion devices --- misc/ios/Info.plist | 2 ++ src/Game.c | 7 +++++ src/Game.h | 6 +++- src/MenuOptions.c | 6 +++- src/Window_ios.m | 72 ++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 90 insertions(+), 3 deletions(-) diff --git a/misc/ios/Info.plist b/misc/ios/Info.plist index a4b063668..bee69a21e 100644 --- a/misc/ios/Info.plist +++ b/misc/ios/Info.plist @@ -137,5 +137,7 @@ + CADisableMinimumFrameDurationOnPhone + \ No newline at end of file diff --git a/src/Game.c b/src/Game.c index 7e0cceb66..38fdc44e4 100644 --- a/src/Game.c +++ b/src/Game.c @@ -75,6 +75,9 @@ int Game_NumStates = 1; const char* const FpsLimit_Names[FPS_LIMIT_COUNT] = { "LimitVSync", "Limit30FPS", "Limit60FPS", "Limit120FPS", "Limit144FPS", "LimitNone", +#ifdef CC_BUILD_IOS + "LimitProMotion", +#endif }; static struct IGameComponent* comps_head; @@ -490,6 +493,10 @@ void Game_SetFpsLimit(int method) { case FPS_LIMIT_30: minFrameTime = 1000/30.0f; break; } Gfx_SetVSync(method == FPS_LIMIT_VSYNC); +#ifdef CC_BUILD_IOS + extern void Gfx_SetProMotion(cc_bool); + Gfx_SetProMotion(method == FPS_LIMIT_PROMOTION); +#endif Game_SetMinFrameTime(minFrameTime); } diff --git a/src/Game.h b/src/Game.h index 2914089bd..20c278b5d 100644 --- a/src/Game.h +++ b/src/Game.h @@ -84,7 +84,11 @@ extern struct GameVersion Game_Version; extern void GameVersion_Load(void); enum FpsLimitMethod { - FPS_LIMIT_VSYNC, FPS_LIMIT_30, FPS_LIMIT_60, FPS_LIMIT_120, FPS_LIMIT_144, FPS_LIMIT_NONE, FPS_LIMIT_COUNT + FPS_LIMIT_VSYNC, FPS_LIMIT_30, FPS_LIMIT_60, FPS_LIMIT_120, FPS_LIMIT_144, FPS_LIMIT_NONE, +#ifdef CC_BUILD_IOS + FPS_LIMIT_PROMOTION, +#endif + FPS_LIMIT_COUNT }; extern const char* const FpsLimit_Names[FPS_LIMIT_COUNT]; diff --git a/src/MenuOptions.c b/src/MenuOptions.c index 2a76c6671..bf768199b 100644 --- a/src/MenuOptions.c +++ b/src/MenuOptions.c @@ -775,7 +775,11 @@ static void GraphicsOptionsScreen_InitWidgets(struct MenuOptionsScreen* s) { "&eVSync: &fNumber of frames rendered is at most the monitor's refresh rate.\n" \ "&e30/60/120/144 FPS: &fRenders 30/60/120/144 frames at most each second.\n" \ "&eNoLimit: &fRenders as many frames as possible each second.\n" \ - "&cNoLimit is pointless - it wastefully renders frames that you don't even see!"); + "&cNoLimit is pointless - it wastefully renders frames that you don't even see!" +#ifdef CC_BUILD_IOS + "\n&eProMotion: &fRender above 60Hz on devices with ProMotion." +#endif + ); MenuOptionsScreen_AddInt(s, "View distance", 8, 4096, 512, GrO_GetViewDist, GrO_SetViewDist, NULL); diff --git a/src/Window_ios.m b/src/Window_ios.m index 184c01dba..1b683763e 100644 --- a/src/Window_ios.m +++ b/src/Window_ios.m @@ -34,6 +34,7 @@ UITextField* kb_widget; CGContextRef win_ctx; UIView* view_handle; UIViewController* cc_controller; +CADisplayLink *displayLink = Nil; UIColor* ToUIColor(BitmapCol color, float A); NSString* ToNSString(const cc_string* text); @@ -551,7 +552,10 @@ void Window_Create3D(int width, int height) { Init3DLayer(); } -void Window_Destroy(void) { } +void Window_Destroy(void) { + [cc_controller.view removeFromSuperview]; + cc_controller.view = Nil; +} /*########################################################################################################################* @@ -653,6 +657,22 @@ cc_bool GLContext_SwapBuffers(void) { return true; } +void Gfx_SetProMotion(bool pro_motion) { + /* + * If ProMotion is off, pause our CADisplayLink object and hence disable higher + * ranges of ProMotion. Otherwise, users may experience framepacing issues + * with other FPS limit options. + */ + if (displayLink) { + if (pro_motion) + Platform_LogConst("ProMotion: ON"); + else + Platform_LogConst("ProMotion: OFF"); + + displayLink.paused = !pro_motion; + } +} + void GLContext_SetVSync(cc_bool vsync) { } void GLContext_GetApiInfo(cc_string* info) { } @@ -663,6 +683,56 @@ void GLContext_GetApiInfo(cc_string* info) { } return [CAEAGLLayer class]; } +- (void)commonInit { + if (@available(iOS 15.0, *)) { + NSInteger maxFPS = [UIScreen.mainScreen maximumFramesPerSecond]; + displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLink:)]; + + /* Suggest the device to possibily increase frame-rate cap from the usual 60 + * up to 120. Due to how quirky ProMotion is, supported iPhones in + * practice may achieve 80FPS at best. In other words, there is no way to + * force a specific refresh rate. + * https://developer.apple.com/documentation/quartzcore/optimizing_promotion_refresh_rates_for_iphone_13_pro_and_ipad_pro?language=objc + */ + displayLink.preferredFrameRateRange = CAFrameRateRangeMake(maxFPS, maxFPS, maxFPS); + + [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; + + Platform_LogConst("CADisplayLink Initialized"); + } else { + displayLink = Nil; + } +} + +- (void)displayLink:(CADisplayLink *)sender { + /* Dummy display link callback */ +} + +- (id)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder: coder]; + [self commonInit]; + + return self; +} + +- (id)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + [self commonInit]; + + return self; +} + +- (void)removeFromSuperview { + [super removeFromSuperview]; + + if (displayLink) { + [displayLink invalidate]; + displayLink = Nil; + + Platform_LogConst("CADisplayLink Invalidated"); + } +} + - (void)layoutSubviews { [super layoutSubviews]; GLContext_OnLayout();