Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Commit

Permalink
[ios] Add preferred FPS setting; vary maximum FPS by device capability
Browse files Browse the repository at this point in the history
- Add `MGLMapView.preferredFramesPerSecond`, which can be set with the provided `MGLMapViewPreferredFramesPerSecond` enum values or directly with an integer.
- Adaptively set the preferred FPS based on the capabilities of the device: the oldest and least powerful devices are now capped at 30 FPS, which results in a more consistent/smoother experience.
  • Loading branch information
friedbunny authored Jul 31, 2018
1 parent c77aa79 commit 885f6e3
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 3 deletions.
4 changes: 4 additions & 0 deletions platform/ios/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT

## 4.3.0

### Styles and rendering

* Added an `MGLMapView.preferredFramesPerSecond` property that controls the rate at which the map view is rendered. The default rate now adapts to device capabilities to provide a smoother experience. ([#12501](https://github.com/mapbox/mapbox-gl-native/issues/12501))

### Other changes

* Fixed a crash that occurred when the user started a gesture before the drift animation for a previous gesture was complete. ([#12148](https://github.com/mapbox/mapbox-gl-native/pull/12148))
Expand Down
12 changes: 12 additions & 0 deletions platform/ios/ios.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,10 @@
966FCF531F3C322400F2B6DE /* MGLUserLocationHeadingArrowLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 966FCF501F3C321000F2B6DE /* MGLUserLocationHeadingArrowLayer.h */; };
966FCF541F3C323300F2B6DE /* MGLUserLocationHeadingArrowLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 966FCF511F3C321000F2B6DE /* MGLUserLocationHeadingArrowLayer.m */; };
966FCF551F3C323500F2B6DE /* MGLUserLocationHeadingArrowLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 966FCF511F3C321000F2B6DE /* MGLUserLocationHeadingArrowLayer.m */; };
967C864B210A9D3C004DF794 /* UIDevice+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 967C8649210A9D3C004DF794 /* UIDevice+MGLAdditions.h */; };
967C864C210A9D3C004DF794 /* UIDevice+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 967C8649210A9D3C004DF794 /* UIDevice+MGLAdditions.h */; };
967C864D210A9D3C004DF794 /* UIDevice+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 967C864A210A9D3C004DF794 /* UIDevice+MGLAdditions.m */; };
967C864E210A9D3C004DF794 /* UIDevice+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 967C864A210A9D3C004DF794 /* UIDevice+MGLAdditions.m */; };
968F36B51E4D128D003A5522 /* MGLDistanceFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 3557F7AE1E1D27D300CCA5E6 /* MGLDistanceFormatter.h */; settings = {ATTRIBUTES = (Public, ); }; };
96E027231E57C76E004B8E66 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 96E027251E57C76E004B8E66 /* Localizable.strings */; };
96E516DC2000547000A02306 /* MGLPolyline_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 9654C1251FFC1AB900DB6A19 /* MGLPolyline_Private.h */; };
Expand Down Expand Up @@ -992,6 +996,8 @@
966FCF4B1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLUserLocationHeadingBeamLayer.m; sourceTree = "<group>"; };
966FCF501F3C321000F2B6DE /* MGLUserLocationHeadingArrowLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLUserLocationHeadingArrowLayer.h; sourceTree = "<group>"; };
966FCF511F3C321000F2B6DE /* MGLUserLocationHeadingArrowLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLUserLocationHeadingArrowLayer.m; sourceTree = "<group>"; };
967C8649210A9D3C004DF794 /* UIDevice+MGLAdditions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIDevice+MGLAdditions.h"; sourceTree = "<group>"; };
967C864A210A9D3C004DF794 /* UIDevice+MGLAdditions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIDevice+MGLAdditions.m"; sourceTree = "<group>"; };
968F36B41E4D0FC6003A5522 /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
96E027241E57C76E004B8E66 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = "<group>"; };
96E027271E57C77A004B8E66 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1526,6 +1532,8 @@
96036A00200565C700510F3D /* NSOrthography+MGLAdditions.m */,
35CE61801D4165D9004F2359 /* UIColor+MGLAdditions.h */,
35CE61811D4165D9004F2359 /* UIColor+MGLAdditions.mm */,
967C8649210A9D3C004DF794 /* UIDevice+MGLAdditions.h */,
967C864A210A9D3C004DF794 /* UIDevice+MGLAdditions.m */,
30E578111DAA7D690050F07E /* UIImage+MGLAdditions.h */,
30E578121DAA7D690050F07E /* UIImage+MGLAdditions.mm */,
DD9BE4F51EB263C50079A3AF /* UIViewController+MGLAdditions.h */,
Expand Down Expand Up @@ -2258,6 +2266,7 @@
35D3A1E61E9BE7EB002B38EE /* MGLScaleBar.h in Headers */,
0778DD431F67556700A73B34 /* MGLComputedShapeSource.h in Headers */,
DA8848311CBAFA6200AB86E3 /* NSString+MGLAdditions.h in Headers */,
967C864B210A9D3C004DF794 /* UIDevice+MGLAdditions.h in Headers */,
1FCAE2A220B872A400C577DD /* MGLLocationManager.h in Headers */,
DACA86262019218600E9693A /* MGLRasterDEMSource.h in Headers */,
353933F81D3FB79F003F57D7 /* MGLLineStyleLayer.h in Headers */,
Expand Down Expand Up @@ -2356,6 +2365,7 @@
4049C29E1DB6CD6C00B3F799 /* MGLPointCollection.h in Headers */,
3566C7671D4A77BA008152BC /* MGLShapeSource.h in Headers */,
DA35A29F1CC9E94C00E826B2 /* MGLCoordinateFormatter.h in Headers */,
967C864C210A9D3C004DF794 /* UIDevice+MGLAdditions.h in Headers */,
404C26E31D89B877000AA13D /* MGLTileSource.h in Headers */,
96E516F6200059EC00A02306 /* MGLRendererFrontend.h in Headers */,
071BBB041EE76147001FB02A /* MGLImageSource.h in Headers */,
Expand Down Expand Up @@ -2949,6 +2959,7 @@
40834BF71FE05E1800C1BD0D /* MMEUniqueIdentifier.m in Sources */,
3566C7681D4A77BA008152BC /* MGLShapeSource.mm in Sources */,
40834C4A1FE05F7500C1BD0D /* TSKPinningValidator.m in Sources */,
967C864D210A9D3C004DF794 /* UIDevice+MGLAdditions.m in Sources */,
400533021DB0862B0069F638 /* NSArray+MGLAdditions.mm in Sources */,
96036A03200565C700510F3D /* NSOrthography+MGLAdditions.m in Sources */,
40834BF31FE05E1800C1BD0D /* MMETimerManager.m in Sources */,
Expand Down Expand Up @@ -3076,6 +3087,7 @@
400533031DB086490069F638 /* NSArray+MGLAdditions.mm in Sources */,
40834C571FE05F7600C1BD0D /* TSKPinningValidator.m in Sources */,
35136D431D42274500C20EFD /* MGLRasterStyleLayer.mm in Sources */,
967C864E210A9D3C004DF794 /* UIDevice+MGLAdditions.m in Sources */,
96036A04200565C700510F3D /* NSOrthography+MGLAdditions.m in Sources */,
40834C071FE05E1800C1BD0D /* MMETimerManager.m in Sources */,
3538AA201D542239008EC33D /* MGLForegroundStyleLayer.mm in Sources */,
Expand Down
30 changes: 30 additions & 0 deletions platform/ios/src/MGLMapView.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,21 @@ typedef NS_ENUM(NSUInteger, MGLUserTrackingMode) {
MGLUserTrackingModeFollowWithCourse,
};

/** Options for `MGLMapView.preferredFramesPerSecond`. */
typedef NSInteger MGLMapViewPreferredFramesPerSecond NS_TYPED_EXTENSIBLE_ENUM;

/**
The default frame rate. This can be either 30 FPS or 60 FPS, depending on
device capabilities.
*/
FOUNDATION_EXTERN MGL_EXPORT const MGLMapViewPreferredFramesPerSecond MGLMapViewPreferredFramesPerSecondDefault;

/** A conservative frame rate; typically 30 FPS. */
FOUNDATION_EXTERN MGL_EXPORT const MGLMapViewPreferredFramesPerSecond MGLMapViewPreferredFramesPerSecondLowPower;

/** The maximum supported frame rate; typically 60 FPS. */
FOUNDATION_EXTERN MGL_EXPORT const MGLMapViewPreferredFramesPerSecond MGLMapViewPreferredFramesPerSecondMaximum;

/**
An interactive, customizable map view with an interface similar to the one
provided by Apple’s MapKit.
Expand Down Expand Up @@ -286,6 +301,21 @@ MGL_EXPORT IB_DESIGNABLE
*/
- (IBAction)showAttribution:(id)sender;

/**
The preferred frame rate at which the map view is rendered.
The default value for this property is
`MGLMapViewPreferredFramesPerSecondDefault`, which will adaptively set the
preferred frame rate based on the capability of the user’s device to maintain
a smooth experience.
In addition to the provided `MGLMapViewPreferredFramesPerSecond` options, this
property can be set to arbitrary integer values.
@see `CADisplayLink.preferredFramesPerSecond`
*/
@property (nonatomic, assign) MGLMapViewPreferredFramesPerSecond preferredFramesPerSecond;

@property (nonatomic) NSArray<NSString *> *styleClasses __attribute__((unavailable("Support for style classes has been removed.")));

- (BOOL)hasStyleClass:(NSString *)styleClass __attribute__((unavailable("Support for style classes has been removed.")));
Expand Down
55 changes: 52 additions & 3 deletions platform/ios/src/MGLMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
#import "NSProcessInfo+MGLAdditions.h"
#import "NSString+MGLAdditions.h"
#import "NSURL+MGLAdditions.h"
#import "UIDevice+MGLAdditions.h"
#import "UIImage+MGLAdditions.h"
#import "UIViewController+MGLAdditions.h"

Expand Down Expand Up @@ -87,6 +88,10 @@
const CGFloat MGLMapViewDecelerationRateFast = UIScrollViewDecelerationRateFast;
const CGFloat MGLMapViewDecelerationRateImmediate = 0.0;

const MGLMapViewPreferredFramesPerSecond MGLMapViewPreferredFramesPerSecondDefault = -1;
const MGLMapViewPreferredFramesPerSecond MGLMapViewPreferredFramesPerSecondLowPower = 30;
const MGLMapViewPreferredFramesPerSecond MGLMapViewPreferredFramesPerSecondMaximum = 60;

/// Indicates the manner in which the map view is tracking the user location.
typedef NS_ENUM(NSUInteger, MGLUserTrackingState) {
/// The map view is not yet tracking the user location.
Expand Down Expand Up @@ -118,8 +123,6 @@ typedef NS_ENUM(NSUInteger, MGLUserTrackingState) {
/// Initial zoom level when entering user tracking mode from a low zoom level.
const double MGLDefaultZoomLevelForUserTracking = 14.0;

const NSUInteger MGLTargetFrameInterval = 1; // Target FPS will be 60 divided by this value

/// Tolerance for snapping to true north, measured in degrees in either direction.
const CLLocationDirection MGLToleranceForSnappingToNorth = 7;

Expand Down Expand Up @@ -403,6 +406,9 @@ - (void)commonInit
self.backgroundColor = [UIColor clearColor];
self.clipsToBounds = YES;
if (@available(iOS 11.0, *)) { self.accessibilityIgnoresInvertColors = YES; }

self.preferredFramesPerSecond = MGLMapViewPreferredFramesPerSecondDefault;

// setup mbgl view
_mbglView = new MBGLView(self);

Expand Down Expand Up @@ -1125,7 +1131,7 @@ - (void)validateDisplayLink
}

_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateFromDisplayLink)];
_displayLink.frameInterval = MGLTargetFrameInterval;
[self updateDisplayLinkPreferredFramesPerSecond];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
_needsDisplayRefresh = YES;
[self updateFromDisplayLink];
Expand All @@ -1137,6 +1143,49 @@ - (void)validateDisplayLink
}
}

- (void)updateDisplayLinkPreferredFramesPerSecond
{
if (!_displayLink)
{
return;
}

NSInteger newFrameRate;
if (_preferredFramesPerSecond == MGLMapViewPreferredFramesPerSecondDefault)
{
// On legacy devices that cannot maintain a reasonable frame rate, set
// a lower limit to avoid jank.
newFrameRate = UIDevice.currentDevice.mgl_isLegacyDevice ? MGLMapViewPreferredFramesPerSecondLowPower : MGLMapViewPreferredFramesPerSecondMaximum;
}
else
{
newFrameRate = _preferredFramesPerSecond;
}

if (@available(iOS 10.0, *))
{
_displayLink.preferredFramesPerSecond = newFrameRate;
}
else
{
// CADisplayLink.frameInterval does not support more than 60 FPS (and
// no device that supports >60 FPS ever supported iOS 9).
NSInteger maximumFrameRate = 60;
_displayLink.frameInterval = maximumFrameRate / MIN(newFrameRate, maximumFrameRate);
}
}

- (void)setPreferredFramesPerSecond:(MGLMapViewPreferredFramesPerSecond)preferredFramesPerSecond
{
if (_preferredFramesPerSecond == preferredFramesPerSecond)
{
return;
}

_preferredFramesPerSecond = preferredFramesPerSecond;
[self updateDisplayLinkPreferredFramesPerSecond];
}

- (void)didMoveToWindow
{
[self validateDisplayLink];
Expand Down
7 changes: 7 additions & 0 deletions platform/ios/src/UIDevice+MGLAdditions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#import <UIKit/UIKit.h>

@interface UIDevice (MGLAdditions)

@property (nonatomic, readonly) BOOL mgl_isLegacyDevice;

@end
51 changes: 51 additions & 0 deletions platform/ios/src/UIDevice+MGLAdditions.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#import "UIDevice+MGLAdditions.h"
#include <sys/sysctl.h>

@implementation UIDevice (MGLAdditions)

- (NSString *)modelString {
char *typeSpecifier = "hw.machine";

size_t size;
sysctlbyname(typeSpecifier, NULL, &size, NULL, 0);

char *answer = malloc(size);
sysctlbyname(typeSpecifier, answer, &size, NULL, 0);

NSString *results = [NSString stringWithCString:answer encoding:NSUTF8StringEncoding];

free(answer);
return results;
}

- (BOOL)mgl_isLegacyDevice {
// This is a list of supported devices that cannot maintain a reasonable frame
// rate under typical load. For brevity, unsupported devices are not included.
NSSet *blacklist = [NSSet setWithObjects:
@"iPhone4", // iPhone 4s
@"iPhone5", // iPhone 5, 5c
@"iPhone6", // iPhone 5s

@"iPad2", // iPad 2, Mini
@"iPad3", // iPad 3
@"iPad4", // iPad Air, Mini 2, Mini 3

@"iPod5", // iPod Touch 5

nil
];

NSString *model = [self modelString];

for (NSString *blacklistedModel in blacklist) {
if ([model hasPrefix:[blacklistedModel stringByAppendingString:@","]]) {
return YES;
}
}

// TODO: Also handle simulator using something like `ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"]`.

return NO;
}

@end

0 comments on commit 885f6e3

Please sign in to comment.