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();