Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Set iOS default frame rate to screen max. #29797

Merged
merged 11 commits into from
Apr 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -1546,6 +1546,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/KeyCodeMap_Int
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/VsyncWaiterIosTest.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_ios.h
Expand Down
1 change: 1 addition & 0 deletions shell/platform/darwin/ios/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ source_set("ios_test_flutter_mrc") {
"framework/Source/FlutterPlatformPluginTest.mm",
"framework/Source/FlutterPlatformViewsTest.mm",
"framework/Source/FlutterViewTest.mm",
"framework/Source/VsyncWaiterIosTest.mm",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this test need to use mrc? Can it be moved to ios_test_flutter? Your test as written isn't releasing everything.

Copy link
Contributor Author

@cyanglaz cyanglaz Apr 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got compiling issue trying to use ARC due to scoped_nsobject used in the header. "flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h"

../../flutter/fml/platform/darwin/scoped_nsobject.h:153:28: error: 'NSAutoreleasePool' is unavailable: not available in automatic reference counting mode
  explicit scoped_nsobject(NSAutoreleasePool* object = nil);
                           ^
/Applications/Xcode_13_1.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator15.0.sdk/System/Library/Frameworks/Foundation.framework/Headers/NSAutoreleasePool.h:10:12: note: 'NSAutoreleasePool' has been explicitly marked unavailable here
@interface NSAutoreleasePool : NSObject {
           ^
In file included from ../../flutter/shell/platform/darwin/ios/framework/Source/VsyncWaiterIosTest.mm:11:
In file included from ../../flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h:12:
../../flutter/fml/platform/darwin/scoped_nsobject.h:51:35: error: 'release' is unavailable: not available in automatic reference counting mode
  ~scoped_nsprotocol() { [object_ release]; }
                                  ^
../../flutter/fml/platform/darwin/scoped_nsobject.h:133:12: note: in instantiation of member function 'fml::scoped_nsprotocol<id>::~scoped_nsprotocol' requested here
  explicit scoped_nsobject(id object = nil) : scoped_nsprotocol<id>(object) {}
           ^
/Applications/Xcode_13_1.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator15.0.sdk/usr/include/objc/NSObject.h:37:1: note: 'release' has been explicitly marked unavailable here
- (oneway void)release OBJC_ARC_UNAVAILABLE;
^
In file included from ../../flutter/shell/platform/darwin/ios/framework/Source/VsyncWaiterIosTest.mm:11:
In file included from ../../flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h:12:

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dang.

"framework/Source/accessibility_bridge_test.mm",
"platform_message_handler_ios_test.mm",
]
Expand Down
98 changes: 98 additions & 0 deletions shell/platform/darwin/ios/framework/Source/VsyncWaiterIosTest.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>

#include "flutter/fml/thread.h"

#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h"

FLUTTER_ASSERT_NOT_ARC
namespace {
fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
auto thread = std::make_unique<fml::Thread>(name);
auto runner = thread->GetTaskRunner();
return runner;
}
}

@interface VSyncClient (Testing)

- (CADisplayLink*)getDisplayLink;

@end

@interface VsyncWaiterIosTest : XCTestCase
@end

@implementation VsyncWaiterIosTest

- (void)testSetCorrectVariableRefreshRates {
auto thread_task_runner = CreateNewThread("VsyncWaiterIosTest");
auto callback = [](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {};
id bundleMock = OCMPartialMock([NSBundle mainBundle]);
OCMStub([bundleMock objectForInfoDictionaryKey:@"CADisableMinimumFrameDurationOnPhone"])
.andReturn(@YES);
id mockDisplayLinkManager = [OCMockObject mockForClass:[DisplayLinkManager class]];
double maxFrameRate = 120;
[[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];

VSyncClient* vsyncClient = [[[VSyncClient alloc] initWithTaskRunner:thread_task_runner
callback:callback] autorelease];
CADisplayLink* link = [vsyncClient getDisplayLink];
if (@available(iOS 15.0, *)) {
XCTAssertEqual(link.preferredFrameRateRange.maximum, maxFrameRate);
XCTAssertEqual(link.preferredFrameRateRange.preferred, maxFrameRate);
XCTAssertEqual(link.preferredFrameRateRange.minimum, maxFrameRate / 2);
} else if (@available(iOS 10.0, *)) {
XCTAssertEqual(link.preferredFramesPerSecond, maxFrameRate);
}
[vsyncClient release];
}

- (void)testDoNotSetVariableRefreshRatesIfCADisableMinimumFrameDurationOnPhoneIsNotOn {
auto thread_task_runner = CreateNewThread("VsyncWaiterIosTest");
auto callback = [](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {};
id bundleMock = OCMPartialMock([NSBundle mainBundle]);
OCMStub([bundleMock objectForInfoDictionaryKey:@"CADisableMinimumFrameDurationOnPhone"])
.andReturn(@NO);
id mockDisplayLinkManager = [OCMockObject mockForClass:[DisplayLinkManager class]];
double maxFrameRate = 120;
[[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];

VSyncClient* vsyncClient = [[[VSyncClient alloc] initWithTaskRunner:thread_task_runner
callback:callback] autorelease];
CADisplayLink* link = [vsyncClient getDisplayLink];
if (@available(iOS 15.0, *)) {
XCTAssertEqual(link.preferredFrameRateRange.maximum, 0);
XCTAssertEqual(link.preferredFrameRateRange.preferred, 0);
XCTAssertEqual(link.preferredFrameRateRange.minimum, 0);
} else if (@available(iOS 10.0, *)) {
XCTAssertEqual(link.preferredFramesPerSecond, 0);
}
[vsyncClient release];
}

- (void)testDoNotSetVariableRefreshRatesIfCADisableMinimumFrameDurationOnPhoneIsNotSet {
auto thread_task_runner = CreateNewThread("VsyncWaiterIosTest");
auto callback = [](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {};
id mockDisplayLinkManager = [OCMockObject mockForClass:[DisplayLinkManager class]];
double maxFrameRate = 120;
[[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
VSyncClient* vsyncClient = [[[VSyncClient alloc] initWithTaskRunner:thread_task_runner
callback:callback] autorelease];
CADisplayLink* link = [vsyncClient getDisplayLink];
if (@available(iOS 15.0, *)) {
XCTAssertEqual(link.preferredFrameRateRange.maximum, 0);
XCTAssertEqual(link.preferredFrameRateRange.preferred, 0);
XCTAssertEqual(link.preferredFrameRateRange.minimum, 0);
} else if (@available(iOS 10.0, *)) {
XCTAssertEqual(link.preferredFramesPerSecond, 0);
}
[vsyncClient release];
}

@end
2 changes: 2 additions & 0 deletions shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_VSYNC_WAITER_IOS_H_
#define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_VSYNC_WAITER_IOS_H_

#include <QuartzCore/CADisplayLink.h>

#include "flutter/fml/macros.h"
#include "flutter/fml/memory/weak_ptr.h"
#include "flutter/fml/platform/darwin/scoped_nsobject.h"
Expand Down
25 changes: 23 additions & 2 deletions shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
#include <utility>

#include <Foundation/Foundation.h>
#include <QuartzCore/CADisplayLink.h>
#include <UIKit/UIKit.h>
#include <mach/mach_time.h>

Expand Down Expand Up @@ -64,6 +63,8 @@ - (instancetype)initWithTaskRunner:(fml::RefPtr<fml::TaskRunner>)task_runner
};
display_link_.get().paused = YES;

[self setMaxRefreshRateIfEnabled];

task_runner->PostTask([client = [self retain]]() {
[client->display_link_.get() addToRunLoop:[NSRunLoop currentRunLoop]
forMode:NSRunLoopCommonModes];
Expand All @@ -74,6 +75,23 @@ - (instancetype)initWithTaskRunner:(fml::RefPtr<fml::TaskRunner>)task_runner
return self;
}

- (void)setMaxRefreshRateIfEnabled {
NSNumber* minimumFrameRateDisabled =
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CADisableMinimumFrameDurationOnPhone"];
if (![minimumFrameRateDisabled boolValue]) {
return;
}
double maxFrameRate = fmax([DisplayLinkManager displayRefreshRate], 60);
double minFrameRate = fmax(maxFrameRate / 2, 60);

if (@available(iOS 15.0, *)) {
display_link_.get().preferredFrameRateRange =
CAFrameRateRangeMake(minFrameRate, maxFrameRate, maxFrameRate);
} else if (@available(iOS 10.0, *)) {
display_link_.get().preferredFramesPerSecond = maxFrameRate;
}
}

- (void)await {
display_link_.get().paused = NO;
}
Expand All @@ -99,7 +117,6 @@ - (void)onDisplayLink:(CADisplayLink*)link {

recorder->RecordVsync(frame_start_time, frame_target_time);
display_link_.get().paused = YES;

callback_(std::move(recorder));
}

Expand All @@ -117,6 +134,10 @@ - (double)getRefreshRate {
return current_refresh_rate_;
}

- (CADisplayLink*)getDisplayLink {
return display_link_.get();
}

@end

@implementation DisplayLinkManager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
0D6AB6C922BB05E200EEE540 /* IosUnitTestsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = IosUnitTestsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
0D6AB6CF22BB05E200EEE540 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0D6AB73E22BD8F0200EEE540 /* FlutterEngineConfig.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = FlutterEngineConfig.xcconfig; sourceTree = "<group>"; };
68B6091227F62F990036AC78 /* VsyncWaiterIosTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = VsyncWaiterIosTest.mm; sourceTree = "<group>"; };
F7521D7226BB671E005F15C5 /* libios_test_flutter.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libios_test_flutter.dylib; path = "../../../../out/$(FLUTTER_ENGINE)/libios_test_flutter.dylib"; sourceTree = "<group>"; };
F7521D7526BB673E005F15C5 /* libocmock_shared.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libocmock_shared.dylib; path = "../../../../out/$(FLUTTER_ENGINE)/libocmock_shared.dylib"; sourceTree = "<group>"; };
F77E081726FA9CE6003E6E4C /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = "../../../../out/$(FLUTTER_ENGINE)/Flutter.framework"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -96,6 +97,7 @@
0AC232E924BA71D300A85907 /* Source */ = {
isa = PBXGroup;
children = (
68B6091227F62F990036AC78 /* VsyncWaiterIosTest.mm */,
F7A3FDE026B9E0A300EADD61 /* FlutterAppDelegateTest.mm */,
0AC232F424BA71D300A85907 /* SemanticsObjectTest.mm */,
0AC232F724BA71D300A85907 /* FlutterEngineTest.mm */,
Expand Down