2727
2828// Note: The slow frame threshold is now dynamically computed based on UIScreen's
2929// maximumFramesPerSecond to align with device capabilities (ProMotion, tvOS, etc.).
30+ // Slow threshold = 1000 / UIScreen.maximumFramesPerSecond ms (or 1.0 / maxFPS seconds).
3031// For devices reporting 60 FPS, the threshold is approximately 16.67ms (1/60).
32+ // The threshold is cached and refreshed when the app becomes active.
3133// TODO(b/73498642): Make these configurable.
3234
3335// Legacy constant maintained for test compatibility. The actual slow frame detection
34- // uses FPRSlowBudgetSeconds() which queries UIScreen.maximumFramesPerSecond dynamically .
36+ // uses a cached value computed from UIScreen.maximumFramesPerSecond.
3537CFTimeInterval const kFPRSlowFrameThreshold = 1.0 / 60.0 ;
3638
3739CFTimeInterval const kFPRFrozenFrameThreshold = 700.0 / 1000.0 ;
@@ -91,6 +93,14 @@ static inline CFTimeInterval FPRSlowBudgetSeconds(void) {
9193 }
9294}
9395
96+ @interface FPRScreenTraceTracker ()
97+
98+ @property (nonatomic ) NSInteger fpr_cachedMaxFPS;
99+
100+ @property (nonatomic ) CFTimeInterval fpr_cachedSlowBudget;
101+
102+ @end
103+
94104@implementation FPRScreenTraceTracker {
95105 /* * Instance variable storing the total frames observed so far. */
96106 atomic_int_fast64_t _totalFramesCount;
@@ -146,6 +156,17 @@ - (instancetype)init {
146156 selector: @selector (appWillResignActiveNotification: )
147157 name: UIApplicationWillResignActiveNotification
148158 object: [UIApplication sharedApplication ]];
159+
160+ // Initialize cached FPS and slow budget
161+ NSInteger __fps = UIScreen.mainScreen .maximumFramesPerSecond ?: 60 ;
162+ self.fpr_cachedMaxFPS = __fps;
163+ self.fpr_cachedSlowBudget = 1.0 / (double )__fps;
164+
165+ // Observe app becoming active to refresh FPS cache
166+ [[NSNotificationCenter defaultCenter ] addObserver: self
167+ selector: @selector (appDidBecomeActive: )
168+ name: UIApplicationDidBecomeActiveNotification
169+ object: nil ];
149170 }
150171 return self;
151172}
@@ -156,6 +177,9 @@ - (void)dealloc {
156177 [[NSNotificationCenter defaultCenter ] removeObserver: self
157178 name: UIApplicationDidBecomeActiveNotification
158179 object: [UIApplication sharedApplication ]];
180+ [[NSNotificationCenter defaultCenter ] removeObserver: self
181+ name: UIApplicationDidBecomeActiveNotification
182+ object: nil ];
159183 [[NSNotificationCenter defaultCenter ] removeObserver: self
160184 name: UIApplicationWillResignActiveNotification
161185 object: [UIApplication sharedApplication ]];
@@ -179,6 +203,12 @@ - (void)appDidBecomeActiveNotification:(NSNotification *)notification {
179203 });
180204}
181205
206+ - (void )appDidBecomeActive : (NSNotification *)note {
207+ NSInteger __fps = UIScreen.mainScreen .maximumFramesPerSecond ?: 60 ;
208+ self.fpr_cachedMaxFPS = __fps;
209+ self.fpr_cachedSlowBudget = 1.0 / (double )__fps;
210+ }
211+
182212- (void )appWillResignActiveNotification : (NSNotification *)notification {
183213 // To get the most accurate numbers of total, frozen and slow frames, we need to capture them as
184214 // soon as we're notified of an event.
@@ -208,8 +238,9 @@ - (void)appWillResignActiveNotification:(NSNotification *)notification {
208238- (void )displayLinkStep {
209239 static CFAbsoluteTime previousTimestamp = kFPRInvalidTime ;
210240 CFAbsoluteTime currentTimestamp = self.displayLink .timestamp ;
211- RecordFrameType (currentTimestamp, previousTimestamp, &_slowFramesCount, &_frozenFramesCount,
212- &_totalFramesCount);
241+ CFTimeInterval slowBudget = self.fpr_cachedSlowBudget ;
242+ RecordFrameType (currentTimestamp, previousTimestamp, slowBudget, &_slowFramesCount,
243+ &_frozenFramesCount, &_totalFramesCount);
213244 previousTimestamp = currentTimestamp;
214245}
215246
@@ -218,21 +249,22 @@ - (void)displayLinkStep {
218249 *
219250 * @param currentTimestamp The current timestamp of the displayLink.
220251 * @param previousTimestamp The previous timestamp of the displayLink.
252+ * @param slowBudget The cached slow frame budget in seconds (1.0 / maxFPS).
221253 * @param slowFramesCounter The value of the slowFramesCount before this function was called.
222254 * @param frozenFramesCounter The value of the frozenFramesCount before this function was called.
223255 * @param totalFramesCounter The value of the totalFramesCount before this function was called.
224256 */
225257FOUNDATION_STATIC_INLINE
226258void RecordFrameType (CFAbsoluteTime currentTimestamp,
227259 CFAbsoluteTime previousTimestamp,
260+ CFTimeInterval slowBudget,
228261 atomic_int_fast64_t *slowFramesCounter,
229262 atomic_int_fast64_t *frozenFramesCounter,
230263 atomic_int_fast64_t *totalFramesCounter) {
231264 CFTimeInterval frameDuration = currentTimestamp - previousTimestamp;
232265 if (previousTimestamp == kFPRInvalidTime ) {
233266 return ;
234267 }
235- CFTimeInterval slowBudget = FPRSlowBudgetSeconds ();
236268 if (frameDuration > slowBudget) {
237269 atomic_fetch_add_explicit (slowFramesCounter, 1 , memory_order_relaxed);
238270 }
0 commit comments