File: external_begin_frame_source_ios.mm

package info (click to toggle)
chromium 139.0.7258.138-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,120,676 kB
  • sloc: cpp: 35,100,869; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (242 lines) | stat: -rw-r--r-- 7,530 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "components/viz/common/frame_sinks/external_begin_frame_source_ios.h"

#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>

#include <algorithm>

#include "base/apple/mach_logging.h"
#include "base/logging.h"
#include "base/numerics/checked_math.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"

namespace {

// ProMotion devices only support up to 120Hz.
constexpr float kMaxRefreshRate = 120;

constexpr float kMinimumRefreshRate =
    (viz::BeginFrameArgs::MinInterval() == base::TimeDelta()
         ? 1
         : 1 / viz::BeginFrameArgs::MinInterval().InSecondsF());

// Translates CFTimeInterval to absolute time.
uint64_t GetMachTimeFromSeconds(CFTimeInterval seconds) {
  mach_timebase_info_data_t info;
  kern_return_t kr = mach_timebase_info(&info);
  MACH_DCHECK(kr == KERN_SUCCESS, kr) << "mach_timebase_info";
  DCHECK(info.numer);
  DCHECK(info.denom);

  // From https://developer.apple.com/library/archive/qa/qa1643/_index.html.
  base::CheckedNumeric<uint64_t> time(base::Time::kNanosecondsPerSecond *
                                      seconds);
  // Skip for fast path.
  if (info.denom != info.numer) {
    time *= info.denom;
    time /= info.numer;
  }

  if (!time.IsValid()) {
    DLOG(ERROR) << "Bailing due to overflow: "
                << base::Time::kNanosecondsPerSecond << " * " << seconds
                << " / " << info.numer << " * " << info.denom;
    return 0;
  }

  return time.ValueOrDie();
}

}  // namespace

@interface CADisplayLinkImpl : NSObject {
 @private
  // A timer object that helps to synchronize with the refresh rate of the
  // display.
  CADisplayLink* __strong _displayLink;

  // Determines if a vsync listener is enabled.
  bool _enabled;

  // A client that receives vsync updates. Owns us.
  raw_ptr<viz::ExternalBeginFrameSourceIOS> _client;

  // Current preferred refresh rate in frames per second. The system may ignore
  // this and, for example, throttle the frame rate. Please note that the frame
  // rate that the system chooses will be rounded to the nearest factor of a
  // maximum refresh rate of display. Eg, if a display supports 60Hz, the
  // refresh rate might be rounded to 15, 20, 30, and 60 FPS respectively.
  float _preferredRefreshRate;

  // The maximum refresh rate that depends on a maximum supported refresh rate
  // of a display that a device uses.
  float _maximumRefreshRate;
}

@end

@implementation CADisplayLinkImpl

- (instancetype)initWithClient:(viz::ExternalBeginFrameSourceIOS*)client {
  self = [super init];

  if (self) {
    _client = client;

    // Create a CADisplayLink and suspend it until a request for begin frames
    // comes.
    _displayLink =
        [CADisplayLink displayLinkWithTarget:self
                                    selector:@selector(displayLinkDidFire:)];
    _maximumRefreshRate = kMaxRefreshRate;
    [self setPreferredInterval:base::Hertz(_maximumRefreshRate)];
    [self setEnabled:false];
    [_displayLink addToRunLoop:NSRunLoop.currentRunLoop
                       forMode:NSRunLoopCommonModes];
  }
  return self;
}

- (id)init {
  NOTREACHED();
}

- (void)setEnabled:(bool)enabled {
  if (!_displayLink || _enabled == enabled) {
    return;
  }

  _enabled = enabled;

  // Resume or suspend the display link's notifications.
  if (_enabled) {
    _displayLink.paused = NO;
  } else {
    _displayLink.paused = YES;
  }
}

- (void)invalidateDisplayLink {
  [self setEnabled:false];
  [_displayLink invalidate];
  _displayLink = nil;
  _client = nil;
}

- (void)setPreferredInterval:(base::TimeDelta)interval {
  if (!_displayLink) {
    return;
  }

  DCHECK_GE(interval, base::TimeDelta());
  const float refresh_rate = 1 / interval.InSecondsF();

  if (_preferredRefreshRate != refresh_rate) {
    // The preferred refresh rate mustn't exceed the maximum one. The floating
    // part can result in exceeding the maximum rate because of the division
    // operation.
    _preferredRefreshRate =
        std::clamp(refresh_rate, kMinimumRefreshRate, _maximumRefreshRate);
    if (@available(iOS 15, *)) {
      [_displayLink
          setPreferredFrameRateRange:CAFrameRateRange{
                                         .minimum = kMinimumRefreshRate,
                                         .maximum = _maximumRefreshRate,
                                         .preferred = _preferredRefreshRate}];
    } else if (@available(iOS 10, *)) {
      [_displayLink setPreferredFramesPerSecond:_preferredRefreshRate];
    }

    // _displayLink.frameInterval is used on iOS 3-10. However, these are pretty
    // old iOS versions, which we are not targeting.
  }
}

- (void)displayLinkDidFire:(CADisplayLink*)displayLink {
  DCHECK(_client);

  // Get the previous vsync time.
  const base::TimeTicks vsync_time = base::TimeTicks::FromMachAbsoluteTime(
      GetMachTimeFromSeconds(displayLink.timestamp));

  // Get the next vsync time.
  const base::TimeTicks next_vsync_time = base::TimeTicks::FromMachAbsoluteTime(
      GetMachTimeFromSeconds(displayLink.targetTimestamp));

  // An error happened. Skip.
  if (vsync_time.is_null() || next_vsync_time.is_null()) {
    return;
  }

  // Get the interval of the current vsync.
  const base::TimeDelta vsync_interval = next_vsync_time - vsync_time;

  // If interval is not positive, we have to skip this frame.
  if (!vsync_interval.is_positive()) {
    return;
  }

  _client->OnVSync(vsync_time, next_vsync_time, vsync_interval);
}

- (int64_t)maximumRefreshRate {
  return _maximumRefreshRate;
}

@end

namespace viz {

struct ExternalBeginFrameSourceIOS::ObjCStorage {
  CADisplayLinkImpl* __strong display_link_impl;
};

ExternalBeginFrameSourceIOS::ExternalBeginFrameSourceIOS(uint32_t restart_id)
    : ExternalBeginFrameSource(this, restart_id),
      objc_storage_(std::make_unique<ObjCStorage>()) {
  objc_storage_->display_link_impl =
      [[CADisplayLinkImpl alloc] initWithClient:this];
}

ExternalBeginFrameSourceIOS::~ExternalBeginFrameSourceIOS() {
  // We must manually invalidate the CADisplayLink as its addToRunLoop keeps
  // strong reference to its target. Thus, releasing our wrapper won't really
  // result in destroying the object.
  [objc_storage_->display_link_impl invalidateDisplayLink];
  objc_storage_->display_link_impl = nil;
}

void ExternalBeginFrameSourceIOS::SetPreferredInterval(
    base::TimeDelta interval) {
  [objc_storage_->display_link_impl setPreferredInterval:interval];
}

base::TimeDelta ExternalBeginFrameSourceIOS::GetMinimumFrameInterval() {
  const int64_t max_refresh_rate =
      [objc_storage_->display_link_impl maximumRefreshRate];
  if (max_refresh_rate <= 0) [[unlikely]] {
    return BeginFrameArgs::DefaultInterval();
  }
  return base::Hertz(max_refresh_rate);
}

void ExternalBeginFrameSourceIOS::OnVSync(base::TimeTicks vsync_time,
                                          base::TimeTicks next_vsync_time,
                                          base::TimeDelta vsync_interval) {
  OnBeginFrame(begin_frame_args_generator_.GenerateBeginFrameArgs(
      source_id(), vsync_time, next_vsync_time, vsync_interval));
}

void ExternalBeginFrameSourceIOS::OnNeedsBeginFrames(bool needs_begin_frames) {
  SetEnabled(needs_begin_frames);
}

void ExternalBeginFrameSourceIOS::SetEnabled(bool enabled) {
  [objc_storage_->display_link_impl setEnabled:enabled];
}

}  // namespace viz