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  autorelease];
41  CADisplayLink* link = [vsyncClient getDisplayLink];
42  vsyncClient.allowPauseAfterVsync = NO;
43  [vsyncClient await];
44  [vsyncClient onDisplayLink:link];
45  XCTAssertFalse(link.isPaused);
46 
47  vsyncClient.allowPauseAfterVsync = YES;
48  [vsyncClient await];
49  [vsyncClient onDisplayLink:link];
50  XCTAssertTrue(link.isPaused);
51 }
52 
53 - (void)testSetCorrectVariableRefreshRates {
54  auto thread_task_runner = CreateNewThread("VsyncWaiterIosTest");
55  auto callback = [](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {};
56  id bundleMock = OCMPartialMock([NSBundle mainBundle]);
57  OCMStub([bundleMock objectForInfoDictionaryKey:@"CADisableMinimumFrameDurationOnPhone"])
58  .andReturn(@YES);
59  id mockDisplayLinkManager = [OCMockObject mockForClass:[DisplayLinkManager class]];
60  double maxFrameRate = 120;
61  [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
62 
63  VSyncClient* vsyncClient = [[[VSyncClient alloc] initWithTaskRunner:thread_task_runner
64  callback:callback] autorelease];
65  CADisplayLink* link = [vsyncClient getDisplayLink];
66  if (@available(iOS 15.0, *)) {
67  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.maximum, maxFrameRate, 0.1);
68  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.preferred, maxFrameRate, 0.1);
69  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.minimum, maxFrameRate / 2, 0.1);
70  } else {
71  XCTAssertEqualWithAccuracy(link.preferredFramesPerSecond, maxFrameRate, 0.1);
72  }
73 }
74 
75 - (void)testDoNotSetVariableRefreshRatesIfCADisableMinimumFrameDurationOnPhoneIsNotOn {
76  auto thread_task_runner = CreateNewThread("VsyncWaiterIosTest");
77  auto callback = [](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {};
78  id bundleMock = OCMPartialMock([NSBundle mainBundle]);
79  OCMStub([bundleMock objectForInfoDictionaryKey:@"CADisableMinimumFrameDurationOnPhone"])
80  .andReturn(@NO);
81  id mockDisplayLinkManager = [OCMockObject mockForClass:[DisplayLinkManager class]];
82  double maxFrameRate = 120;
83  [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
84 
85  VSyncClient* vsyncClient = [[[VSyncClient alloc] initWithTaskRunner:thread_task_runner
86  callback:callback] autorelease];
87  CADisplayLink* link = [vsyncClient getDisplayLink];
88  if (@available(iOS 15.0, *)) {
89  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.maximum, 0, 0.1);
90  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.preferred, 0, 0.1);
91  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.minimum, 0, 0.1);
92  } else {
93  XCTAssertEqualWithAccuracy(link.preferredFramesPerSecond, 0, 0.1);
94  }
95 }
96 
97 - (void)testDoNotSetVariableRefreshRatesIfCADisableMinimumFrameDurationOnPhoneIsNotSet {
98  auto thread_task_runner = CreateNewThread("VsyncWaiterIosTest");
99  auto callback = [](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {};
100  id mockDisplayLinkManager = [OCMockObject mockForClass:[DisplayLinkManager class]];
101  double maxFrameRate = 120;
102  [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
103  VSyncClient* vsyncClient = [[[VSyncClient alloc] initWithTaskRunner:thread_task_runner
104  callback:callback] autorelease];
105  CADisplayLink* link = [vsyncClient getDisplayLink];
106  if (@available(iOS 15.0, *)) {
107  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.maximum, 0, 0.1);
108  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.preferred, 0, 0.1);
109  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.minimum, 0, 0.1);
110  } else {
111  XCTAssertEqualWithAccuracy(link.preferredFramesPerSecond, 0, 0.1);
112  }
113 }
114 
115 - (void)testAwaitAndPauseWillWorkCorrectly {
116  auto thread_task_runner = CreateNewThread("VsyncWaiterIosTest");
117  VSyncClient* vsyncClient = [[[VSyncClient alloc]
118  initWithTaskRunner:thread_task_runner
119  callback:[](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {}]
120  autorelease];
121 
122  CADisplayLink* link = [vsyncClient getDisplayLink];
123  XCTAssertTrue(link.isPaused);
124 
125  [vsyncClient await];
126  XCTAssertFalse(link.isPaused);
127 
128  [vsyncClient pause];
129  XCTAssertTrue(link.isPaused);
130 }
131 
132 - (void)testRefreshRateUpdatedTo80WhenThraedsMerge {
133  auto platform_thread_task_runner = CreateNewThread("Platform");
134  auto raster_thread_task_runner = CreateNewThread("Raster");
135  auto ui_thread_task_runner = CreateNewThread("UI");
136  auto io_thread_task_runner = CreateNewThread("IO");
137  auto task_runners =
138  flutter::TaskRunners("test", platform_thread_task_runner, raster_thread_task_runner,
139  ui_thread_task_runner, io_thread_task_runner);
140 
141  id mockDisplayLinkManager = [OCMockObject mockForClass:[DisplayLinkManager class]];
142  double maxFrameRate = 120;
143  [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
144  [[[mockDisplayLinkManager stub] andReturnValue:@(YES)] maxRefreshRateEnabledOnIPhone];
145  auto vsync_waiter = flutter::VsyncWaiterIOS(task_runners);
146 
147  fml::scoped_nsobject<VSyncClient> vsyncClient = vsync_waiter.GetVsyncClient();
148  CADisplayLink* link = [vsyncClient.get() getDisplayLink];
149 
150  if (@available(iOS 15.0, *)) {
151  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.maximum, maxFrameRate, 0.1);
152  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.preferred, maxFrameRate, 0.1);
153  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.minimum, maxFrameRate / 2, 0.1);
154  } else {
155  XCTAssertEqualWithAccuracy(link.preferredFramesPerSecond, maxFrameRate, 0.1);
156  }
157 
158  const auto merger = fml::RasterThreadMerger::CreateOrShareThreadMerger(
159  nullptr, platform_thread_task_runner->GetTaskQueueId(),
160  raster_thread_task_runner->GetTaskQueueId());
161 
162  merger->MergeWithLease(5);
163  vsync_waiter.AwaitVSync();
164 
165  if (@available(iOS 15.0, *)) {
166  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.maximum, 80, 0.1);
167  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.preferred, 80, 0.1);
168  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.minimum, 60, 0.1);
169  } else {
170  XCTAssertEqualWithAccuracy(link.preferredFramesPerSecond, 80, 0.1);
171  }
172 
173  merger->UnMergeNowIfLastOne();
174  vsync_waiter.AwaitVSync();
175 
176  if (@available(iOS 15.0, *)) {
177  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.maximum, maxFrameRate, 0.1);
178  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.preferred, maxFrameRate, 0.1);
179  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.minimum, maxFrameRate / 2, 0.1);
180  } else {
181  XCTAssertEqualWithAccuracy(link.preferredFramesPerSecond, maxFrameRate, 0.1);
182  }
183 
184  if (@available(iOS 14.0, *)) {
185  // Fake response that we are running on Mac.
186  id processInfo = [NSProcessInfo processInfo];
187  id processInfoPartialMock = OCMPartialMock(processInfo);
188  bool iOSAppOnMac = true;
189  [OCMStub([processInfoPartialMock isiOSAppOnMac]) andReturnValue:OCMOCK_VALUE(iOSAppOnMac)];
190 
191  merger->MergeWithLease(5);
192  vsync_waiter.AwaitVSync();
193 
194  // On Mac, framerate should be uncapped.
195  if (@available(iOS 15.0, *)) {
196  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.maximum, maxFrameRate, 0.1);
197  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.preferred, maxFrameRate, 0.1);
198  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.minimum, maxFrameRate / 2, 0.1);
199  } else {
200  XCTAssertEqualWithAccuracy(link.preferredFramesPerSecond, 80, 0.1);
201  }
202 
203  merger->UnMergeNowIfLastOne();
204  vsync_waiter.AwaitVSync();
205 
206  if (@available(iOS 15.0, *)) {
207  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.maximum, maxFrameRate, 0.1);
208  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.preferred, maxFrameRate, 0.1);
209  XCTAssertEqualWithAccuracy(link.preferredFrameRateRange.minimum, maxFrameRate / 2, 0.1);
210  } else {
211  XCTAssertEqualWithAccuracy(link.preferredFramesPerSecond, maxFrameRate, 0.1);
212  }
213  }
214 }
215 
216 @end
FLUTTER_ASSERT_NOT_ARC::CreateNewThread
fml::RefPtr< fml::TaskRunner > CreateNewThread(const std::string &name)
Definition: VsyncWaiterIosTest.mm:16
FLUTTER_ASSERT_NOT_ARC
Definition: VsyncWaiterIosTest.mm:15
-[VSyncClient pause]
void pause()
Definition: vsync_waiter_ios.mm:125
VsyncWaiterIosTest
Definition: VsyncWaiterIosTest.mm:30
-[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_mrc.mm:21
vsync_waiter_ios.h
VSyncClient
Definition: vsync_waiter_ios.h:38