Flutter iOS Embedder
VsyncWaiterIosTest.mm
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #import <OCMock/OCMock.h>
6 #import <XCTest/XCTest.h>
7 
8 #include "flutter/fml/raster_thread_merger.h"
9 #include "flutter/fml/thread.h"
10 
13 
15 namespace {
16 fml::RefPtr<fml::TaskRunner> CreateNewThread(const std::string& name) {
17  auto thread = std::make_unique<fml::Thread>(name);
18  auto runner = thread->GetTaskRunner();
19  return runner;
20 }
21 } // namespace
22 
23 @interface VSyncClient (Testing)
24 
25 - (CADisplayLink*)getDisplayLink;
26 - (void)onDisplayLink:(CADisplayLink*)link;
27 
28 @end
29 
30 @interface VsyncWaiterIosTest : XCTestCase
31 @end
32 
33 @implementation VsyncWaiterIosTest
34 
35 - (void)testSetAllowPauseAfterVsyncCorrect {
36  auto thread_task_runner = CreateNewThread("VsyncWaiterIosTest");
37  VSyncClient* vsyncClient = [[VSyncClient alloc]
38  initWithTaskRunner:thread_task_runner
39  callback:[](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {}];
40  CADisplayLink* link = [vsyncClient getDisplayLink];
41  vsyncClient.allowPauseAfterVsync = NO;
42  [vsyncClient await];
43  [vsyncClient onDisplayLink:link];
44  XCTAssertFalse(link.isPaused);
45 
46  vsyncClient.allowPauseAfterVsync = YES;
47  [vsyncClient await];
48  [vsyncClient onDisplayLink:link];
49  XCTAssertTrue(link.isPaused);
50 }
51 
52 - (void)testSetCorrectVariableRefreshRates {
53  auto thread_task_runner = CreateNewThread("VsyncWaiterIosTest");
54  auto callback = [](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {};
55  id bundleMock = OCMPartialMock([NSBundle mainBundle]);
56  OCMStub([bundleMock objectForInfoDictionaryKey:@"CADisableMinimumFrameDurationOnPhone"])
57  .andReturn(@YES);
58  id mockDisplayLinkManager = [OCMockObject mockForClass:[DisplayLinkManager class]];
59  double maxFrameRate = 120;
60  [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
61 
62  VSyncClient* vsyncClient = [[VSyncClient alloc] initWithTaskRunner:thread_task_runner
63  callback:callback];
64  CADisplayLink* link = [vsyncClient getDisplayLink];
65  if (@available(iOS 15.0, *)) {
66  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.maximum, maxFrameRate, 0.1);
67  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.preferred, maxFrameRate, 0.1);
68  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.minimum, maxFrameRate / 2, 0.1);
69  } else {
70  XCTAssertEqualWithAccuracy(link.preferredFramesPerSecond, maxFrameRate, 0.1);
71  }
72 }
73 
74 - (void)testDoNotSetVariableRefreshRatesIfCADisableMinimumFrameDurationOnPhoneIsNotOn {
75  auto thread_task_runner = CreateNewThread("VsyncWaiterIosTest");
76  auto callback = [](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {};
77  id bundleMock = OCMPartialMock([NSBundle mainBundle]);
78  OCMStub([bundleMock objectForInfoDictionaryKey:@"CADisableMinimumFrameDurationOnPhone"])
79  .andReturn(@NO);
80  id mockDisplayLinkManager = [OCMockObject mockForClass:[DisplayLinkManager class]];
81  double maxFrameRate = 120;
82  [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
83 
84  VSyncClient* vsyncClient = [[VSyncClient alloc] initWithTaskRunner:thread_task_runner
85  callback:callback];
86  CADisplayLink* link = [vsyncClient getDisplayLink];
87  if (@available(iOS 15.0, *)) {
88  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.maximum, 0, 0.1);
89  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.preferred, 0, 0.1);
90  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.minimum, 0, 0.1);
91  } else {
92  XCTAssertEqualWithAccuracy(link.preferredFramesPerSecond, 0, 0.1);
93  }
94 }
95 
96 - (void)testDoNotSetVariableRefreshRatesIfCADisableMinimumFrameDurationOnPhoneIsNotSet {
97  auto thread_task_runner = CreateNewThread("VsyncWaiterIosTest");
98  auto callback = [](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {};
99  id mockDisplayLinkManager = [OCMockObject mockForClass:[DisplayLinkManager class]];
100  double maxFrameRate = 120;
101  [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
102  VSyncClient* vsyncClient = [[VSyncClient alloc] initWithTaskRunner:thread_task_runner
103  callback:callback];
104  CADisplayLink* link = [vsyncClient getDisplayLink];
105  if (@available(iOS 15.0, *)) {
106  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.maximum, 0, 0.1);
107  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.preferred, 0, 0.1);
108  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.minimum, 0, 0.1);
109  } else {
110  XCTAssertEqualWithAccuracy(link.preferredFramesPerSecond, 0, 0.1);
111  }
112 }
113 
114 - (void)testAwaitAndPauseWillWorkCorrectly {
115  auto thread_task_runner = CreateNewThread("VsyncWaiterIosTest");
116  VSyncClient* vsyncClient = [[VSyncClient alloc]
117  initWithTaskRunner:thread_task_runner
118  callback:[](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {}];
119 
120  CADisplayLink* link = [vsyncClient getDisplayLink];
121  XCTAssertTrue(link.isPaused);
122 
123  [vsyncClient await];
124  XCTAssertFalse(link.isPaused);
125 
126  [vsyncClient pause];
127  XCTAssertTrue(link.isPaused);
128 }
129 
130 - (void)testRefreshRateUpdatedTo80WhenThraedsMerge {
131  auto platform_thread_task_runner = CreateNewThread("Platform");
132  auto raster_thread_task_runner = CreateNewThread("Raster");
133  auto ui_thread_task_runner = CreateNewThread("UI");
134  auto io_thread_task_runner = CreateNewThread("IO");
135  auto task_runners =
136  flutter::TaskRunners("test", platform_thread_task_runner, raster_thread_task_runner,
137  ui_thread_task_runner, io_thread_task_runner);
138 
139  id mockDisplayLinkManager = [OCMockObject mockForClass:[DisplayLinkManager class]];
140  double maxFrameRate = 120;
141  [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
142  [[[mockDisplayLinkManager stub] andReturnValue:@(YES)] maxRefreshRateEnabledOnIPhone];
143  auto vsync_waiter = flutter::VsyncWaiterIOS(task_runners);
144 
145  fml::scoped_nsobject<VSyncClient> vsyncClient = vsync_waiter.GetVsyncClient();
146  CADisplayLink* link = [vsyncClient.get() getDisplayLink];
147 
148  if (@available(iOS 15.0, *)) {
149  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.maximum, maxFrameRate, 0.1);
150  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.preferred, maxFrameRate, 0.1);
151  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.minimum, maxFrameRate / 2, 0.1);
152  } else {
153  XCTAssertEqualWithAccuracy(link.preferredFramesPerSecond, maxFrameRate, 0.1);
154  }
155 
156  const auto merger = fml::RasterThreadMerger::CreateOrShareThreadMerger(
157  nullptr, platform_thread_task_runner->GetTaskQueueId(),
158  raster_thread_task_runner->GetTaskQueueId());
159 
160  merger->MergeWithLease(5);
161  vsync_waiter.AwaitVSync();
162 
163  if (@available(iOS 15.0, *)) {
164  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.maximum, 80, 0.1);
165  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.preferred, 80, 0.1);
166  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.minimum, 60, 0.1);
167  } else {
168  XCTAssertEqualWithAccuracy(link.preferredFramesPerSecond, 80, 0.1);
169  }
170 
171  merger->UnMergeNowIfLastOne();
172  vsync_waiter.AwaitVSync();
173 
174  if (@available(iOS 15.0, *)) {
175  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.maximum, maxFrameRate, 0.1);
176  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.preferred, maxFrameRate, 0.1);
177  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.minimum, maxFrameRate / 2, 0.1);
178  } else {
179  XCTAssertEqualWithAccuracy(link.preferredFramesPerSecond, maxFrameRate, 0.1);
180  }
181 
182  if (@available(iOS 14.0, *)) {
183  // Fake response that we are running on Mac.
184  id processInfo = [NSProcessInfo processInfo];
185  id processInfoPartialMock = OCMPartialMock(processInfo);
186  bool iOSAppOnMac = true;
187  [OCMStub([processInfoPartialMock isiOSAppOnMac]) andReturnValue:OCMOCK_VALUE(iOSAppOnMac)];
188 
189  merger->MergeWithLease(5);
190  vsync_waiter.AwaitVSync();
191 
192  // On Mac, framerate should be uncapped.
193  if (@available(iOS 15.0, *)) {
194  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.maximum, maxFrameRate, 0.1);
195  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.preferred, maxFrameRate, 0.1);
196  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.minimum, maxFrameRate / 2, 0.1);
197  } else {
198  XCTAssertEqualWithAccuracy(link.preferredFramesPerSecond, 80, 0.1);
199  }
200 
201  merger->UnMergeNowIfLastOne();
202  vsync_waiter.AwaitVSync();
203 
204  if (@available(iOS 15.0, *)) {
205  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.maximum, maxFrameRate, 0.1);
206  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.preferred, maxFrameRate, 0.1);
207  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.minimum, maxFrameRate / 2, 0.1);
208  } else {
209  XCTAssertEqualWithAccuracy(link.preferredFramesPerSecond, maxFrameRate, 0.1);
210  }
211  }
212 }
213 
214 @end
-[VSyncClient pause]
void pause()
Definition: vsync_waiter_ios.mm:125
VsyncWaiterIosTest
Definition: VsyncWaiterIosTest.mm:30
FLUTTER_ASSERT_ARC::CreateNewThread
fml::RefPtr< fml::TaskRunner > CreateNewThread(const std::string &name)
Definition: VsyncWaiterIosTest.mm:16
-[VSyncClient(Testing) getDisplayLink]
CADisplayLink * getDisplayLink()
-[VSyncClient await]
void await()
Definition: vsync_waiter_ios.mm:121
flutter::VsyncWaiterIOS
Definition: vsync_waiter_ios.h:67
FlutterMacros.h
VSyncClient::allowPauseAfterVsync
BOOL allowPauseAfterVsync
Default value is YES. Vsync client will pause vsync callback after receiving a vsync signal....
Definition: vsync_waiter_ios.h:48
VSyncClient(Testing)
Definition: FlutterViewControllerTest.mm:182
vsync_waiter_ios.h
FLUTTER_ASSERT_ARC
Definition: VsyncWaiterIosTest.mm:15
VSyncClient
Definition: vsync_waiter_ios.h:38