cocoa_common: prefer panel refresh rate over our own display link rate

cocoa_get_refresh_rate() on iOS/tvOS reads the CADisplayLink's own
preferred rate.  Since 12c4ff0739 ("apple: better refresh rate
restoration") apple_display_server_init() stamps
settings->floats.video_refresh_rate onto that same display link at
startup, so the getter and the setter share one storage slot.

That closes a feedback loop.  DEFAULT_REFRESH_RATE is 60 on Apple
targets, so on a fresh config the link is pulled from the panel rate
the CocoaView set it up with ([UIScreen maximumFramesPerSecond], 120
on ProMotion) down to 60, and every later read reports 60.  "Set
Display-Reported Refresh Rate" can therefore never report anything
the user does not already have configured; on a ProMotion iPhone it
is pinned at 60 Hz.

All four Apple refresh-rate vtable entries (cocoa_gl_ctx,
cocoa_vk_ctx, metal, dispserv_apple) funnel into this helper, so the
stale value also reaches runloop.c's vsync throttle decision and
RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE.

Query [UIScreen maximumFramesPerSecond] first.  That is the
iOS/tvOS analogue of the CGDisplayModeGetRefreshRate() call the macOS
branch of this same function already makes: a property of the
display, not of our own timer.

This is strictly additive.  An earlier attempt at this fix (580c738efd,
reverted in 6ec192c883) replaced the fallback ladder instead of
extending it, which stranded iOS 10.0 - 10.2 on deprecated
frameInterval even though preferredFramesPerSecond covers it.  Here
the new query is one more rung on top and nothing below it is
touched:

  10.3+ / tvOS 10.2+  maximumFramesPerSecond   (new, SDK + @available)
  15.0+               preferredFrameRateRange  (unchanged)
  10.0+               preferredFramesPerSecond (unchanged)
  < 10.0              frameInterval            (unchanged)
  no display link     maximumFramesPerSecond   (unchanged)
  otherwise           60.0f                    (unchanged)

The new rung is guarded by both __IPHONE_OS_VERSION_MAX_ALLOWED /
__TV_OS_VERSION_MAX_ALLOWED and a runtime @available, so pre-10.3
SDKs do not see the selector and pre-10.3 devices do not call it.
maximumFramesPerSecond is known to answer 0 on some simulators, so a
non-positive result falls through to the ladder rather than being
returned.  A 0 result with no display link present still returns 0
exactly as before, which video_driver_get_refresh_rate() reads as
"unsupported" and resolves from config.

The existing ladder is now wrapped in a block so that its
CADisplayLink declaration stays at block top; that reindent is the
only reason this diff shows deletions.  `git diff -w` against the
parent removes nothing.

The display link keeps honouring the configured rate for actual frame
pacing; only the reporting path changes.
This commit is contained in:
libretroadmin authored and libretroadmin committed 2026-07-27 05:42:04 +02:00
1 parent 6ec192c883
commit fde942cba9
1 file changed
+44 -14
+44 -14
View File
@@ -1161,28 +1161,58 @@ float cocoa_get_refresh_rate(void)
return (rate > 0.0) ? (float)rate : 60.0f;
#endif
#else /* iOS / tvOS */
CADisplayLink *dl = [CocoaView get].displayLink;
if (dl)
/* Prefer the panel's own capability over the CADisplayLink's
* preferred rate.
*
* apple_display_server_init() stamps
* settings->floats.video_refresh_rate onto the display link at
* startup, so reading that link back here closes a feedback
* loop: "Set Display-Reported Refresh Rate" only ever echoes the
* value the user already has configured, and a ProMotion panel
* reports 60 Hz forever because 60 is DEFAULT_REFRESH_RATE.
*
* maximumFramesPerSecond is the iOS/tvOS analogue of the
* CGDisplayModeGetRefreshRate() query the macOS branch above
* makes - a property of the display, not of our own timer.
*
* It only exists on iOS 10.3 / tvOS 10.2, and it is known to
* answer 0 on some simulators, so this is an extra rung on top
* of the ladder rather than a replacement for it. Every path
* below stays reachable and unchanged: 10.0 - 10.2 still gets
* preferredFramesPerSecond, pre-10.0 still gets frameInterval,
* and a 0 answer here still falls through to them. */
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 100300 || __TV_OS_VERSION_MAX_ALLOWED >= 100200
if (@available(iOS 10.3, tvOS 10.2, *))
{
NSInteger max_fps = [[UIScreen mainScreen] maximumFramesPerSecond];
if (max_fps > 0)
return (float)max_fps;
}
#endif
{
CADisplayLink *dl = [CocoaView get].displayLink;
if (dl)
{
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000 || __TV_OS_VERSION_MAX_ALLOWED >= 150000
if (@available(iOS 15.0, tvOS 15.0, *))
return dl.preferredFrameRateRange.preferred;
if (@available(iOS 15.0, tvOS 15.0, *))
return dl.preferredFrameRateRange.preferred;
#endif
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000 || __TV_OS_VERSION_MAX_ALLOWED >= 100000
if (@available(iOS 10.0, tvOS 10.0, *))
return dl.preferredFramesPerSecond;
if (@available(iOS 10.0, tvOS 10.0, *))
return dl.preferredFramesPerSecond;
#endif
/* iOS 6 - 9 / tvOS < 10: only frameInterval exists. It is
* the number of screen refreshes between callbacks, so
* convert to Hz assuming a 60 Hz panel (accurate for every
* pre-iOS-10 device - ProMotion is iPad Pro 2017+). */
/* iOS 6 - 9 / tvOS < 10: only frameInterval exists. It is
* the number of screen refreshes between callbacks, so
* convert to Hz assuming a 60 Hz panel (accurate for every
* pre-iOS-10 device - ProMotion is iPad Pro 2017+). */
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
{
NSInteger fi = dl.frameInterval;
return 60.0f / (float)(fi > 0 ? fi : 1);
}
{
NSInteger fi = dl.frameInterval;
return 60.0f / (float)(fi > 0 ? fi : 1);
}
#pragma clang diagnostic pop
}
}
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 100300 || __TV_OS_VERSION_MAX_ALLOWED >= 100200
if (@available(iOS 10.3, tvOS 10.2, *))