SDL: Enable high refresh rates on iOS

From 835b6e0c1ac37ad0af98dd4b1f26c5421dc02128 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 18 Dec 2024 14:18:11 -0800
Subject: [PATCH] Enable high refresh rates on iOS

Fixes https://github.com/libsdl-org/SDL/issues/7518
---
 src/video/uikit/SDL_uikitviewcontroller.m | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/src/video/uikit/SDL_uikitviewcontroller.m b/src/video/uikit/SDL_uikitviewcontroller.m
index c7628e13d575e..c4345578d1752 100644
--- a/src/video/uikit/SDL_uikitviewcontroller.m
+++ b/src/video/uikit/SDL_uikitviewcontroller.m
@@ -111,6 +111,19 @@ - (instancetype)initWithSDLWindow:(SDL_Window *)_window
                             SDL_HideHomeIndicatorHintChanged,
                             (__bridge void *)self);
 #endif
+
+        // Enable high refresh rates on iOS
+        // To enable this on phones, you should add the following line to Info.plist:
+        // <key>CADisableMinimumFrameDurationOnPhone</key> <true/>
+        if (@available(iOS 15.0, tvOS 15.0, *)) {
+            const SDL_DisplayMode *mode = SDL_GetDesktopDisplayMode(SDL_GetPrimaryDisplay());
+            if (mode && mode->refresh_rate > 60.0f) {
+                int frame_rate = (int)mode->refresh_rate;
+                displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(doLoop:)];
+                displayLink.preferredFrameRateRange = CAFrameRateRangeMake((frame_rate * 2) / 3, frame_rate, frame_rate);
+                [displayLink addToRunLoop:NSRunLoop.currentRunLoop forMode:NSDefaultRunLoopMode];
+            }
+        }
     }
     return self;
 }
@@ -145,6 +158,9 @@ - (void)setAnimationCallback:(int)interval
 {
     [self stopAnimation];
 
+    if (interval <= 0) {
+        interval = 1;
+    }
     animationInterval = interval;
     animationCallback = callback;
     animationCallbackParam = callbackParam;
@@ -185,7 +201,7 @@ - (void)stopAnimation
 - (void)doLoop:(CADisplayLink *)sender
 {
     // Don't run the game loop while a messagebox is up
-    if (!UIKit_ShowingMessageBox()) {
+    if (animationCallback && !UIKit_ShowingMessageBox()) {
         // See the comment in the function definition.
 #if defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2)
         UIKit_GL_RestoreCurrentContext();