From c7f9f1582f39115e3b05c89e8c253f258515f112 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Mon, 16 Jul 2018 12:19:18 -0400 Subject: [ios] Add frame duration graph view to iosapp --- platform/ios/app/MBXFrameTimeGraphView.h | 11 +++ platform/ios/app/MBXFrameTimeGraphView.m | 140 +++++++++++++++++++++++++++++ platform/ios/app/MBXViewController.m | 23 ++++- platform/ios/app/Main.storyboard | 27 ++++-- platform/ios/ios.xcodeproj/project.pbxproj | 6 ++ 5 files changed, 198 insertions(+), 9 deletions(-) create mode 100644 platform/ios/app/MBXFrameTimeGraphView.h create mode 100644 platform/ios/app/MBXFrameTimeGraphView.m diff --git a/platform/ios/app/MBXFrameTimeGraphView.h b/platform/ios/app/MBXFrameTimeGraphView.h new file mode 100644 index 0000000000..9c3f6f8c32 --- /dev/null +++ b/platform/ios/app/MBXFrameTimeGraphView.h @@ -0,0 +1,11 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MBXFrameTimeGraphView : UIView + +- (void)updatePathWithFrameDuration:(CFTimeInterval)frameDuration; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/ios/app/MBXFrameTimeGraphView.m b/platform/ios/app/MBXFrameTimeGraphView.m new file mode 100644 index 0000000000..f689768818 --- /dev/null +++ b/platform/ios/app/MBXFrameTimeGraphView.m @@ -0,0 +1,140 @@ +#import "MBXFrameTimeGraphView.h" + +const CGFloat MBXFrameTimeExaggeration = 4.f * 1000.f; +const CGFloat MBXFrameTimeBarWidth = 4.f; + +@interface MBXFrameTimeGraphView () + +@property (nonatomic) CAScrollLayer *scrollLayer; +@property (nonatomic) CAShapeLayer *thresholdLayer; +@property (nonatomic) CGFloat currentX; +@property (nonatomic) NSMutableArray *barLayers; + +@property (nonatomic) UIColor *safeColor; +@property (nonatomic) UIColor *warningColor; +@property (nonatomic) UIColor *dangerColor; + +@end + +@implementation MBXFrameTimeGraphView + +- (instancetype)init { + if (self = [super init]) { + [self commonInit]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + if (self = [super initWithCoder:aDecoder]) { + [self commonInit]; + } + return self; +} + +- (void)commonInit { + self.userInteractionEnabled = NO; + self.layer.opacity = 0.9f; + + self.scrollLayer = [CAScrollLayer layer]; + self.scrollLayer.scrollMode = kCAScrollHorizontally; + self.scrollLayer.masksToBounds = YES; + [self.layer addSublayer:self.scrollLayer]; + + self.thresholdLayer = [CAShapeLayer layer]; + self.thresholdLayer.fillColor = [UIColor darkGrayColor].CGColor; + [self.layer addSublayer:self.thresholdLayer]; + + self.barLayers = [NSMutableArray array]; + + self.safeColor = [UIColor colorWithRed:(CGFloat)(0.f/255.f) green:(CGFloat)(190.f/255.f) blue:(CGFloat)(123.f/255.f) alpha:1.f]; + self.warningColor = [UIColor colorWithRed:(CGFloat)(255.f/255.f) green:(CGFloat)(154.f/255.f) blue:(CGFloat)(82.f/255.f) alpha:1.f]; + self.dangerColor = [UIColor colorWithRed:(CGFloat)(255.f/255.f) green:(CGFloat)(91.f/255.f) blue:(CGFloat)(86.f/255.f) alpha:1.f]; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + if (!CGRectEqualToRect(self.scrollLayer.frame, self.bounds)) { + self.scrollLayer.frame = self.bounds; + + CGRect thresholdLineRect = CGRectMake(0, self.frame.size.height - [self renderDurationTargetMilliseconds], self.frame.size.width, 1); + self.thresholdLayer.path = CGPathCreateWithRect(thresholdLineRect, nil); + } +} + +- (void)updatePathWithFrameDuration:(CFTimeInterval)frameDuration { + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + + self.currentX += MBXFrameTimeBarWidth; + + CAShapeLayer *bar = [self barWithFrameDuration:frameDuration]; + bar.position = CGPointMake(self.currentX, self.frame.size.height); + + [self.scrollLayer addSublayer:bar]; + [self.barLayers addObject:bar]; + + [self.scrollLayer scrollToPoint:CGPointMake(self.currentX - self.frame.size.width, 0)]; + + [self removeStaleBars]; + + [CATransaction commit]; +} + +- (CGFloat)renderDurationTargetMilliseconds { + CGFloat maximumFramesPerSecond; + if (@available(iOS 10.3, *)) { + maximumFramesPerSecond = UIScreen.mainScreen.maximumFramesPerSecond; + } else { + // Not always strictly accurate, but works as an expedient approximation. + maximumFramesPerSecond = 60; + } + + CGFloat target = (1.0 / maximumFramesPerSecond) * MBXFrameTimeExaggeration; + return [self roundedFloat:target]; +} + +- (CGFloat)roundedFloat:(CGFloat)f { +#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR + CGFloat scaleFactor = [UIScreen mainScreen].nativeScale; +#elif TARGET_OS_MAC + CGFloat scaleFactor = [NSScreen mainScreen].backingScaleFactor; +#endif + return round(f * scaleFactor) / scaleFactor; +} + +- (CAShapeLayer *)barWithFrameDuration:(CFTimeInterval)frameDuration { + CAShapeLayer *bar = [CAShapeLayer layer]; + + CGRect barRect = CGRectMake(0, 0, MBXFrameTimeBarWidth, -(fminf(frameDuration * MBXFrameTimeExaggeration, self.frame.size.height))); + UIBezierPath *barPath = [UIBezierPath bezierPathWithRect:barRect]; + bar.path = barPath.CGPath; + bar.fillColor = [self colorForFrameDuration:frameDuration].CGColor; + + return bar; +} + +- (UIColor *)colorForFrameDuration:(CFTimeInterval)frameDuration { + CGFloat renderDurationTargetMilliseconds = [self renderDurationTargetMilliseconds]; + frameDuration *= MBXFrameTimeExaggeration; + + if (frameDuration < renderDurationTargetMilliseconds && frameDuration > (renderDurationTargetMilliseconds * 0.75)) { + return self.warningColor; + } else if (frameDuration > renderDurationTargetMilliseconds) { + return self.dangerColor; + } else { + return self.safeColor; + } +} + +- (void)removeStaleBars { + if (self.barLayers.count > (self.frame.size.width / MBXFrameTimeBarWidth * 3)) { + NSRange staleBarsRange = NSMakeRange(0, self.frame.size.width / MBXFrameTimeBarWidth); + NSArray *staleBars = [self.barLayers subarrayWithRange:staleBarsRange]; + [staleBars makeObjectsPerformSelector:@selector(removeFromSuperlayer)]; + [self.barLayers removeObjectsInRange:staleBarsRange]; + } +} + +@end diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index f058b75dc9..856e4de481 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -8,6 +8,8 @@ #import "LimeGreenStyleLayer.h" #import "MBXEmbeddedMapViewController.h" +#import "MBXFrameTimeGraphView.h" + #import #import "../src/MGLMapView_Experimental.h" @@ -95,6 +97,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { MBXSettingsMiscellaneousWorldTour, MBXSettingsMiscellaneousRandomTour, MBXSettingsMiscellaneousShowZoomLevel, + MBXSettingsMiscellaneousShowFrameTimeGraph, MBXSettingsMiscellaneousScrollView, MBXSettingsMiscellaneousToggleTwoMaps, MBXSettingsMiscellaneousLocalizeLabels, @@ -192,14 +195,17 @@ CLLocationCoordinate2D randomWorldCoordinate() { @property (nonatomic) IBOutlet MGLMapView *mapView; @property (weak, nonatomic) IBOutlet UIButton *hudLabel; +@property (weak, nonatomic) IBOutlet MBXFrameTimeGraphView *frameTimeGraphView; @property (nonatomic) NSInteger styleIndex; @property (nonatomic) BOOL debugLoggingEnabled; @property (nonatomic) BOOL customUserLocationAnnnotationEnabled; @property (nonatomic, getter=isLocalizingLabels) BOOL localizingLabels; @property (nonatomic) BOOL reuseQueueStatsEnabled; @property (nonatomic) BOOL mapInfoHUDEnabled; +@property (nonatomic) BOOL frameTimeGraphEnabled; @property (nonatomic) BOOL shouldLimitCameraChanges; @property (nonatomic) BOOL randomWalk; + @end @interface MGLMapView (MBXViewController) @@ -238,6 +244,7 @@ CLLocationCoordinate2D randomWorldCoordinate() { self.debugLoggingEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:@"MGLMapboxMetricsDebugLoggingEnabled"]; self.mapView.showsScale = YES; self.mapView.showsUserHeadingIndicator = YES; + self.mapView.experimental_enableFrameRateMeasurement = YES; self.hudLabel.titleLabel.font = [UIFont monospacedDigitSystemFontOfSize:10 weight:UIFontWeightRegular]; if ([MGLAccountManager accessToken].length) @@ -293,6 +300,7 @@ CLLocationCoordinate2D randomWorldCoordinate() { [defaults setBool:self.mapView.showsUserLocation forKey:@"MBXShowsUserLocation"]; [defaults setInteger:self.mapView.debugMask forKey:@"MBXDebugMask"]; [defaults setBool:self.mapInfoHUDEnabled forKey:@"MBXShowsZoomLevelHUD"]; + [defaults setBool:self.mapInfoHUDEnabled forKey:@"MBXShowsFrameTimeGraph"]; [defaults synchronize]; } @@ -323,6 +331,7 @@ CLLocationCoordinate2D randomWorldCoordinate() { self.mapInfoHUDEnabled = YES; [self updateHUD]; } + self.frameTimeGraphEnabled = [defaults boolForKey:@"MBXShowsFrameTimeGraph"]; } - (UIInterfaceOrientationMask)supportedInterfaceOrientations @@ -452,6 +461,7 @@ CLLocationCoordinate2D randomWorldCoordinate() { @"Start World Tour", @"Random Tour", [NSString stringWithFormat:@"%@ Map Info HUD", (_mapInfoHUDEnabled ? @"Hide" :@"Show")], + [NSString stringWithFormat:@"%@ Frame Time Graph", (_frameTimeGraphEnabled ? @"Hide" :@"Show")], @"Embedded Map View", [NSString stringWithFormat:@"%@ Second Map", ([self.view viewWithTag:2] == nil ? @"Show" : @"Hide")], [NSString stringWithFormat:@"Show Labels in %@", (_localizingLabels ? @"Default Language" : [[NSLocale currentLocale] displayNameForKey:NSLocaleIdentifier value:[self bestLanguageForUser]])], @@ -681,6 +691,12 @@ CLLocationCoordinate2D randomWorldCoordinate() { [self updateHUD]; break; } + case MBXSettingsMiscellaneousShowFrameTimeGraph: + { + self.frameTimeGraphEnabled = !self.frameTimeGraphEnabled; + self.frameTimeGraphView.hidden = !self.frameTimeGraphEnabled; + break; + } case MBXSettingsMiscellaneousScrollView: { UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; @@ -2224,7 +2240,6 @@ CLLocationCoordinate2D randomWorldCoordinate() { } hudString = [NSString stringWithFormat:@"Visible: %ld Queued: %ld", (unsigned long)self.mapView.visibleAnnotations.count, (unsigned long)queuedAnnotations]; } else if (self.mapInfoHUDEnabled) { - if (!self.mapView.experimental_enableFrameRateMeasurement) self.mapView.experimental_enableFrameRateMeasurement = YES; hudString = [NSString stringWithFormat:@"%.f FPS (%.1fms) ∕ %.2f ∕ ↕\U0000FE0E%.f° ∕ %.f°", roundf(self.mapView.averageFrameRate), self.mapView.averageFrameTime, self.mapView.zoomLevel, self.mapView.camera.pitch, self.mapView.direction]; @@ -2281,4 +2296,10 @@ CLLocationCoordinate2D randomWorldCoordinate() { return features; } +- (void)mapViewDidFinishRenderingFrame:(MGLMapView *)mapView fullyRendered:(BOOL)fullyRendered { + if (self.frameTimeGraphEnabled) { + [self.frameTimeGraphView updatePathWithFrameDuration:mapView.frameTime]; + } +} + @end diff --git a/platform/ios/app/Main.storyboard b/platform/ios/app/Main.storyboard index 3e8a0ad02a..f4e535a56c 100644 --- a/platform/ios/app/Main.storyboard +++ b/platform/ios/app/Main.storyboard @@ -1,14 +1,11 @@ - + - - - - + @@ -42,12 +39,25 @@ + + + + @@ -104,6 +114,7 @@ + @@ -117,7 +128,7 @@ - + @@ -279,7 +290,7 @@