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 4439fc26b..6c24b37aa 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; @@ -497,6 +500,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 867f59c52..2c0674ba1 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 640ec4e40..0a9f0e7e6 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 86424dd35..fbfd17ee3 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();