diff --git a/include/mbgl/map/map_observer.hpp b/include/mbgl/map/map_observer.hpp index 0ebed53d1f0..6f29a0b2d68 100644 --- a/include/mbgl/map/map_observer.hpp +++ b/include/mbgl/map/map_observer.hpp @@ -42,7 +42,8 @@ class MapObserver { RenderMode mode; bool needsRepaint; // In continous mode, shows that there are ongoig transitions. bool placementChanged; - double frameTime; + double frameEncodingTime; + double frameRenderingTime; }; virtual void onCameraWillChange(CameraChangeMode) {} diff --git a/include/mbgl/renderer/renderer_observer.hpp b/include/mbgl/renderer/renderer_observer.hpp index 990c03081e2..29968d896ba 100644 --- a/include/mbgl/renderer/renderer_observer.hpp +++ b/include/mbgl/renderer/renderer_observer.hpp @@ -36,7 +36,11 @@ class RendererObserver { virtual void onDidFinishRenderingFrame(RenderMode, bool /*repaint*/, bool /*placementChanged*/) {} /// End of frame, booleans flags that a repaint is required and that placement changed. - virtual void onDidFinishRenderingFrame(RenderMode mode, bool repaint, bool placementChanged, double /*frameTime*/) { + virtual void onDidFinishRenderingFrame(RenderMode mode, + bool repaint, + bool placementChanged, + double /*frameEncodingTime*/, + double /*frameRenderingTime*/) { onDidFinishRenderingFrame(mode, repaint, placementChanged); } diff --git a/platform/BUILD.bazel b/platform/BUILD.bazel index 71ae8fe7976..284e461d7b3 100644 --- a/platform/BUILD.bazel +++ b/platform/BUILD.bazel @@ -232,7 +232,7 @@ objc_library( objc_library( name = "ios-benchapp", - copts = MAPLIBRE_FLAGS, + copts = CPP_FLAGS + MAPLIBRE_FLAGS, srcs = ["//platform/ios/benchmark:ios_benchmark_srcs"], hdrs = ["//platform/ios/benchmark:ios_benchmark_hdrs"], includes = [ diff --git a/platform/android/MapboxGLAndroidSDK/src/cpp/android_renderer_frontend.cpp b/platform/android/MapboxGLAndroidSDK/src/cpp/android_renderer_frontend.cpp index 840e4404aac..481e8122151 100644 --- a/platform/android/MapboxGLAndroidSDK/src/cpp/android_renderer_frontend.cpp +++ b/platform/android/MapboxGLAndroidSDK/src/cpp/android_renderer_frontend.cpp @@ -34,9 +34,11 @@ class ForwardingRendererObserver : public RendererObserver { void onDidFinishRenderingFrame(RenderMode mode, bool repaintNeeded, bool placementChanged, - double frameTime) override { - void (RendererObserver::*f)(RenderMode, bool, bool, double) = &RendererObserver::onDidFinishRenderingFrame; - delegate.invoke(f, mode, repaintNeeded, placementChanged, frameTime); + double frameEncodingTime, + double frameRenderingTime) override { + void (RendererObserver::*f)( + RenderMode, bool, bool, double, double) = &RendererObserver::onDidFinishRenderingFrame; + delegate.invoke(f, mode, repaintNeeded, placementChanged, frameEncodingTime, frameRenderingTime); } void onDidFinishRenderingMap() override { delegate.invoke(&RendererObserver::onDidFinishRenderingMap); } diff --git a/platform/default/include/mbgl/gfx/headless_frontend.hpp b/platform/default/include/mbgl/gfx/headless_frontend.hpp index 1e1f1c14030..ff961ffc1d1 100644 --- a/platform/default/include/mbgl/gfx/headless_frontend.hpp +++ b/platform/default/include/mbgl/gfx/headless_frontend.hpp @@ -31,7 +31,8 @@ class HeadlessFrontend : public RendererFrontend { float pixelRatio_, gfx::HeadlessBackend::SwapBehaviour swapBehavior = gfx::HeadlessBackend::SwapBehaviour::NoFlush, gfx::ContextMode mode = gfx::ContextMode::Unique, - const std::optional& localFontFamily = std::nullopt); + const std::optional& localFontFamily = std::nullopt, + bool invalidateOnUpdate_ = true); ~HeadlessFrontend() override; void reset() override; @@ -56,6 +57,7 @@ class HeadlessFrontend : public RendererFrontend { PremultipliedImage readStillImage(); RenderResult render(Map&); void renderOnce(Map&); + void renderFrame(); std::optional getTransformState() const; @@ -66,6 +68,7 @@ class HeadlessFrontend : public RendererFrontend { std::atomic frameTime; std::unique_ptr backend; util::AsyncTask asyncInvalidate; + bool invalidateOnUpdate; std::unique_ptr renderer; std::shared_ptr updateParameters; diff --git a/platform/default/src/mbgl/gfx/headless_frontend.cpp b/platform/default/src/mbgl/gfx/headless_frontend.cpp index a2a03a5e625..8efe4cb5bab 100644 --- a/platform/default/src/mbgl/gfx/headless_frontend.cpp +++ b/platform/default/src/mbgl/gfx/headless_frontend.cpp @@ -21,7 +21,8 @@ HeadlessFrontend::HeadlessFrontend(Size size_, float pixelRatio_, gfx::HeadlessBackend::SwapBehaviour swapBehavior, const gfx::ContextMode contextMode, - const std::optional& localFontFamily) + const std::optional& localFontFamily, + bool invalidateOnUpdate_) : size(size_), pixelRatio(pixelRatio_), frameTime(0), @@ -29,23 +30,8 @@ HeadlessFrontend::HeadlessFrontend(Size size_, {static_cast(size.width * pixelRatio), static_cast(size.height * pixelRatio)}, swapBehavior, contextMode)), - asyncInvalidate([this] { - if (renderer && updateParameters) { - auto startTime = mbgl::util::MonotonicTimer::now(); - gfx::BackendScope guard{*getBackend()}; - - // onStyleImageMissing might be called during a render. The user - // implemented method could trigger a call to - // MLNRenderFrontend#update which overwrites `updateParameters`. - // Copy the shared pointer here so that the parameters aren't - // destroyed while `render(...)` is still using them. - auto updateParameters_ = updateParameters; - renderer->render(updateParameters_); - - auto endTime = mbgl::util::MonotonicTimer::now(); - frameTime = (endTime - startTime).count(); - } - }), + asyncInvalidate([this] { renderFrame(); }), + invalidateOnUpdate(invalidateOnUpdate_), renderer(std::make_unique(*getBackend(), pixelRatio, localFontFamily)) {} HeadlessFrontend::~HeadlessFrontend() = default; @@ -57,7 +43,9 @@ void HeadlessFrontend::reset() { void HeadlessFrontend::update(std::shared_ptr updateParameters_) { updateParameters = updateParameters_; - asyncInvalidate.send(); + if (invalidateOnUpdate) { + asyncInvalidate.send(); + } } void HeadlessFrontend::setObserver(RendererObserver& observer_) { @@ -169,6 +157,24 @@ void HeadlessFrontend::renderOnce(Map&) { util::RunLoop::Get()->runOnce(); } +void HeadlessFrontend::renderFrame() { + if (renderer && updateParameters) { + auto startTime = mbgl::util::MonotonicTimer::now(); + gfx::BackendScope guard{*getBackend()}; + + // onStyleImageMissing might be called during a render. The user + // implemented method could trigger a call to + // MLNRenderFrontend#update which overwrites `updateParameters`. + // Copy the shared pointer here so that the parameters aren't + // destroyed while `render(...)` is still using them. + auto updateParameters_ = updateParameters; + renderer->render(updateParameters_); + + auto endTime = mbgl::util::MonotonicTimer::now(); + frameTime = (endTime - startTime).count(); + } +} + std::optional HeadlessFrontend::getTransformState() const { if (updateParameters) { return updateParameters->transformState; diff --git a/platform/default/src/mbgl/map/map_snapshotter.cpp b/platform/default/src/mbgl/map/map_snapshotter.cpp index eab4baa1ef1..56da5d787c4 100644 --- a/platform/default/src/mbgl/map/map_snapshotter.cpp +++ b/platform/default/src/mbgl/map/map_snapshotter.cpp @@ -39,9 +39,11 @@ class ForwardingRendererObserver final : public RendererObserver { void onDidFinishRenderingFrame(RenderMode mode, bool repaintNeeded, bool placementChanged, - double frameTime) override { - void (RendererObserver::*f)(RenderMode, bool, bool, double) = &RendererObserver::onDidFinishRenderingFrame; - delegate.invoke(f, mode, repaintNeeded, placementChanged, frameTime); + double frameEncodingTime, + double frameRenderingTime) override { + void (RendererObserver::*f)( + RenderMode, bool, bool, double, double) = &RendererObserver::onDidFinishRenderingFrame; + delegate.invoke(f, mode, repaintNeeded, placementChanged, frameEncodingTime, frameRenderingTime); } void onStyleImageMissing(const std::string& image, const StyleImageMissingCallback& cb) override { @@ -84,11 +86,13 @@ class SnapshotterRenderer final : public RendererObserver { void onDidFinishRenderingFrame(RenderMode mode, bool repaintNeeded, bool placementChanged, - double frameTime) override { + double frameEncodingTime, + double frameRenderingTime) override { if (mode == RenderMode::Full && hasPendingStillImageRequest) { stillImage = frontend.readStillImage(); } - rendererObserver->onDidFinishRenderingFrame(mode, repaintNeeded, placementChanged, frameTime); + rendererObserver->onDidFinishRenderingFrame( + mode, repaintNeeded, placementChanged, frameEncodingTime, frameRenderingTime); } void onStyleImageMissing(const std::string& id, const StyleImageMissingCallback& done) override { diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index 37247a23632..1eb9f60fe04 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -2478,9 +2478,9 @@ - (void)updateHUD { return features; } -- (void)mapViewDidFinishRenderingFrame:(MLNMapView *)mapView fullyRendered:(BOOL)fullyRendered frameTime:(double)frameTime { +- (void)mapViewDidFinishRenderingFrame:(MLNMapView *)mapView fullyRendered:(BOOL)fullyRendered frameEncodingTime:(double)frameEncodingTime frameRenderingTime:(double)frameRenderingTime { if (self.frameTimeGraphEnabled) { - [self.frameTimeGraphView updatePathWithFrameDuration:frameTime]; + [self.frameTimeGraphView updatePathWithFrameDuration:frameEncodingTime]; } } diff --git a/platform/ios/benchmark/BUILD.bazel b/platform/ios/benchmark/BUILD.bazel index 5094e7566a6..af5d09d07bb 100644 --- a/platform/ios/benchmark/BUILD.bazel +++ b/platform/ios/benchmark/BUILD.bazel @@ -3,8 +3,8 @@ load("//platform/ios/bazel:macros.bzl", "info_plist") filegroup( name = "ios_benchmark_srcs", srcs = [ - "main.m", - "MBXBenchAppDelegate.m", + "main.mm", + "MBXBenchAppDelegate.mm", "MBXBenchViewController.mm", "locations.cpp", ], diff --git a/platform/ios/benchmark/MBXBenchAppDelegate.m b/platform/ios/benchmark/MBXBenchAppDelegate.mm similarity index 100% rename from platform/ios/benchmark/MBXBenchAppDelegate.m rename to platform/ios/benchmark/MBXBenchAppDelegate.mm diff --git a/platform/ios/benchmark/MBXBenchViewController.mm b/platform/ios/benchmark/MBXBenchViewController.mm index 43309d325c9..b01be05c24f 100644 --- a/platform/ios/benchmark/MBXBenchViewController.mm +++ b/platform/ios/benchmark/MBXBenchViewController.mm @@ -1,15 +1,52 @@ #import "MBXBenchViewController.h" #import "MBXBenchAppDelegate.h" #import "MLNMapView_Private.h" -#import "MLNMapViewDelegate.h" +#import "MLNOfflineStorage_Private.h" +#import "MLNSettings_Private.h" #include "locations.hpp" #include -@interface MBXBenchViewController () +#include +#include +#include +#include + +@protocol BenchMapDelegate +- (void)mapDidFinishRenderingFrameFullyRendered:(BOOL)fullyRendered + frameEncodingTime:(double)frameEncodingTime + frameRenderingTime:(double)frameRenderingTime; +@end -@property (nonatomic) MLNMapView *mapView; + +class BenchMapObserver : public mbgl::MapObserver { +public: + BenchMapObserver() = delete; + BenchMapObserver(id mapDelegate_) : mapDelegate(mapDelegate_) {} + virtual ~BenchMapObserver() = default; + + void onDidFinishRenderingFrame(RenderFrameStatus status) override final { + //NSLog(@"Frame encoding time: %4.1f ms", status.frameEncodingTime * 1e3); + //NSLog(@"Frame rendering time: %4.1f ms", status.frameRenderingTime * 1e3); + + bool fullyRendered = status.mode == mbgl::MapObserver::RenderMode::Full; + [mapDelegate mapDidFinishRenderingFrameFullyRendered:fullyRendered + frameEncodingTime:status.frameEncodingTime + frameRenderingTime:status.frameRenderingTime]; + } + +protected: + __weak id mapDelegate = nullptr; +}; + +@interface MBXBenchViewController () { + std::unique_ptr observer; + std::unique_ptr frontend; + std::unique_ptr map; +} + +@property (nonatomic) UIImageView *imageView; @end @@ -32,6 +69,15 @@ + (void)initialize - (void)viewDidLoad { [super viewDidLoad]; +} + +- (void)viewDidAppear:(BOOL)animated +{ + [super viewDidAppear:animated]; + + #ifdef LOG_TO_DOCUMENTS_DIR + [self setAndRedirectLogFileToDocuments]; + #endif // Use a local style and local assets if they’ve been downloaded. NSURL *tile = [[NSBundle mainBundle] URLForResource:@"11" withExtension:@"pbf" subdirectory:@"tiles/tiles/v3/5/7"]; @@ -42,28 +88,50 @@ - (void)viewDidLoad constexpr auto styleIndex = 0; NSURL *url = [NSURL URLWithString:tile ? @"asset://styles/streets.json" : [NSString stringWithCString:styles[styleIndex].c_str() encoding:NSUTF8StringEncoding]]; NSLog(@"Using style URL: \"%@\"", [url absoluteString]); - - self.mapView = [[MLNMapView alloc] initWithFrame:self.view.bounds styleURL:url]; - self.mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - self.mapView.delegate = self; - self.mapView.opaque = YES; - self.mapView.zoomEnabled = NO; - self.mapView.scrollEnabled = NO; - self.mapView.rotateEnabled = NO; - self.mapView.userInteractionEnabled = YES; - self.mapView.preferredFramesPerSecond = MLNMapViewPreferredFramesPerSecondMaximum; - - [self.view addSubview:self.mapView]; -} -- (void)viewDidAppear:(BOOL)animated -{ - [super viewDidAppear:animated]; + self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds]; + self.imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [self.view addSubview:self.imageView]; + + mbgl::Size viewSize = { static_cast(self.view.bounds.size.width), + static_cast(self.view.bounds.size.height) }; + auto pixelRatio = [[UIScreen mainScreen] scale]; + + observer = std::make_unique(self); + frontend = std::make_unique( + viewSize, + pixelRatio, + mbgl::gfx::HeadlessBackend::SwapBehaviour::Flush, + mbgl::gfx::ContextMode::Unique, + /* localFontFamily */ std::nullopt, + /* invalidateOnUpdate */ false + ); + + mbgl::MapOptions mapOptions; + mapOptions.withMapMode(mbgl::MapMode::Continuous) + .withSize(viewSize) + .withPixelRatio(pixelRatio) + .withConstrainMode(mbgl::ConstrainMode::None) + .withViewportMode(mbgl::ViewportMode::Default) + .withCrossSourceCollisions(true); + + mbgl::TileServerOptions* tileServerOptions = [[MLNSettings sharedSettings] tileServerOptionsInternal]; + mbgl::ResourceOptions resourceOptions; + resourceOptions.withCachePath(MLNOfflineStorage.sharedOfflineStorage.databasePath.UTF8String) + .withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String) + .withTileServerOptions(*tileServerOptions); + mbgl::ClientOptions clientOptions; + + auto apiKey = [[MLNSettings sharedSettings] apiKey]; + if (apiKey) { + resourceOptions.withApiKey([apiKey UTF8String]); + } + + map = std::make_unique(*frontend, *observer, mapOptions, resourceOptions, clientOptions); + map->setSize(viewSize); + map->setDebug(mbgl::MapDebugOptions::NoDebug); + map->getStyle().loadURL([url.absoluteString UTF8String]); - #ifdef LOG_TO_DOCUMENTS_DIR - [self setAndRedirectLogFileToDocuments]; - #endif - [self startBenchmarkIteration]; } @@ -110,9 +178,10 @@ - (void)redirectLogToDocuments: (NSString*) fileName size_t idx = 0; enum class State { None, WaitingForAssets, WarmingUp, Benchmarking } state = State::None; int frames = 0; -double totalFrameTime = 0; +double totalFrameEncodingTime = 0; +double totalFrameRenderingTime = 0; std::chrono::steady_clock::time_point started; -std::vector > result; +std::vector> > result; static const int warmupDuration = 20; // frames static const int benchmarkDuration = 200; // frames @@ -121,31 +190,70 @@ - (void)redirectLogToDocuments: (NSString*) fileName extern std::size_t uploadCount, uploadBuildCount, uploadVertextAttrsDirty, uploadInvalidSegments; } +- (void)renderFrame +{ + mbgl::gfx::BackendScope guard{*(frontend->getBackend())}; + + frontend->renderFrame(); + + auto image = frontend->readStillImage(); + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef bitmapContext = CGBitmapContextCreate( + image.data.get(), + image.size.width, + image.size.height, + 8, + 4 * image.size.width, + colorSpace, + kCGImageAlphaPremultipliedLast | kCGBitmapByteOrderDefault + ); + CFRelease(colorSpace); + CGImageRef cgImage = CGBitmapContextCreateImage(bitmapContext); + CGContextRelease(bitmapContext); + + self.imageView.image = [UIImage imageWithCGImage:cgImage]; + CGImageRelease(cgImage); +} + - (void)startBenchmarkIteration { if (mbgl::bench::locations.size() > idx) { const auto& location = mbgl::bench::locations[idx]; - [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(location.latitude, location.longitude) zoomLevel:location.zoom animated:NO]; - self.mapView.direction = location.bearing; + + mbgl::CameraOptions cameraOptions; + cameraOptions.center = mbgl::LatLng(location.latitude, location.longitude); + cameraOptions.zoom = location.zoom; + cameraOptions.bearing = location.bearing; + map->jumpTo(cameraOptions); + state = State::WaitingForAssets; NSLog(@"Benchmarking \"%s\"", location.name.c_str()); NSLog(@"- Loading assets..."); + + dispatch_async(dispatch_get_main_queue(), ^{ + [self renderFrame]; + }); } else { // Do nothing. The benchmark is completed. NSLog(@"Benchmark completed."); + NSLog(@"Result:"); - double totalFrameTime = 0; size_t colWidth = 0; for (const auto& row : result) { colWidth = std::max(row.first.size(), colWidth); } + + double totalFrameEncodingTime = 0; + double totalFrameRenderingTime = 0; for (const auto& row : result) { - NSLog(@"| %-*s | %4.1f ms | %4.1f fps |", int(colWidth), row.first.c_str(), 1e3 * row.second, 1.0 / row.second); - totalFrameTime += row.second; + NSLog(@"| %-*s | %4.1f ms | %4.1f ms |", int(colWidth), row.first.c_str(), 1e3 * row.second.first, 1e3 * row.second.second); + totalFrameEncodingTime += row.second.first; + totalFrameRenderingTime += row.second.second; } - NSLog(@"Average frame time: %4.1f ms", totalFrameTime * 1e3 / result.size()); - NSLog(@"Average FPS: %4.1f", result.size() / totalFrameTime); + NSLog(@"Average frame encoding time: %4.1f ms", totalFrameEncodingTime * 1e3 / result.size()); + NSLog(@"Average frame rendering time: %4.1f ms", totalFrameRenderingTime * 1e3 / result.size()); // NSLog(@"Total uploads: %zu", mbgl::uploadCount); // NSLog(@"Total uploads with dirty vattr: %zu", mbgl::uploadVertextAttrsDirty); @@ -165,21 +273,18 @@ - (void)startBenchmarkIteration { [app performSelector:@selector(suspend)]; } - else if ([app respondsToSelector:@selector(terminate)]) - { - [app performSelector:@selector(terminate)]; - } } } -- (void)mapViewDidFinishRenderingFrame:(MLNMapView *)mapView - fullyRendered:(__unused BOOL)fullyRendered - frameTime:(double)frameTime +- (void)mapDidFinishRenderingFrameFullyRendered:(__unused BOOL)fullyRendered + frameEncodingTime:(double)frameEncodingTime + frameRenderingTime:(double)frameRenderingTime { if (state == State::Benchmarking) { frames++; - totalFrameTime += frameTime; + totalFrameEncodingTime += frameEncodingTime; + totalFrameRenderingTime += frameRenderingTime; if (frames >= benchmarkDuration) { state = State::None; @@ -187,45 +292,49 @@ - (void)mapViewDidFinishRenderingFrame:(MLNMapView *)mapView // Report FPS const auto duration = std::chrono::duration_cast(std::chrono::steady_clock::now() - started).count(); const auto wallClockFPS = double(frames * 1e6) / duration; - const auto frameTime = static_cast(totalFrameTime) / frames; - const auto potentialFPS = 1.0 / frameTime; - result.emplace_back(mbgl::bench::locations[idx].name, frameTime); - NSLog(@"- Frame time: %.1f ms, FPS: %.1f (%.1f sync FPS)", frameTime * 1e3, potentialFPS, wallClockFPS); + const auto frameEncodingTime = static_cast(totalFrameEncodingTime) / frames; + const auto frameRenderingTime = static_cast(totalFrameRenderingTime) / frames; + result.emplace_back(mbgl::bench::locations[idx].name, std::make_pair(frameEncodingTime, frameRenderingTime)); + NSLog(@"- Frame encoding time: %.1f ms, Frame rendering time: %.1f ms (%.1f sync FPS)", frameEncodingTime * 1e3, frameRenderingTime * 1e3, wallClockFPS); // Start benchmarking the next location. idx++; [self startBenchmarkIteration]; } else { - [mapView setNeedsRerender]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self renderFrame]; + }); } return; } - else if (state == State::WarmingUp) { frames++; if (frames >= warmupDuration) { frames = 0; - totalFrameTime = 0; + totalFrameEncodingTime = 0; + totalFrameRenderingTime = 0; state = State::Benchmarking; started = std::chrono::steady_clock::now(); NSLog(@"- Benchmarking for %d frames...", benchmarkDuration); } - [mapView setNeedsRerender]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self renderFrame]; + }); return; } - else if (state == State::WaitingForAssets) { - if ([mapView isFullyLoaded]) + if (map->isFullyLoaded()) { // Start the benchmarking timer. state = State::WarmingUp; - [self.mapView didReceiveMemoryWarning]; NSLog(@"- Warming up for %d frames...", warmupDuration); - [mapView setNeedsRerender]; } + dispatch_async(dispatch_get_main_queue(), ^{ + [self renderFrame]; + }); return; } } diff --git a/platform/ios/benchmark/main.m b/platform/ios/benchmark/main.mm similarity index 100% rename from platform/ios/benchmark/main.m rename to platform/ios/benchmark/main.mm diff --git a/platform/ios/src/MLNMapView+Impl.mm b/platform/ios/src/MLNMapView+Impl.mm index 21d47a21cd7..901a542f714 100644 --- a/platform/ios/src/MLNMapView+Impl.mm +++ b/platform/ios/src/MLNMapView+Impl.mm @@ -81,7 +81,7 @@ void MLNMapViewImpl::onDidFinishRenderingFrame(mbgl::MapObserver::RenderFrameStatus status) { bool fullyRendered = status.mode == mbgl::MapObserver::RenderMode::Full; - [mapView mapViewDidFinishRenderingFrameFullyRendered:fullyRendered frameTime:status.frameTime]; + [mapView mapViewDidFinishRenderingFrameFullyRendered:fullyRendered frameEncodingTime:status.frameEncodingTime frameRenderingTime:status.frameRenderingTime]; } void MLNMapViewImpl::onWillStartRenderingMap() { diff --git a/platform/ios/src/MLNMapView.mm b/platform/ios/src/MLNMapView.mm index 939c4db6a5c..ef0e4ff6fd7 100644 --- a/platform/ios/src/MLNMapView.mm +++ b/platform/ios/src/MLNMapView.mm @@ -6813,7 +6813,8 @@ - (void)mapViewWillStartRenderingFrame { } - (void)mapViewDidFinishRenderingFrameFullyRendered:(BOOL)fullyRendered - frameTime:(double)frameTime { + frameEncodingTime:(double)frameEncodingTime + frameRenderingTime:(double)frameRenderingTime { if (!_mbglMap) { return; @@ -6825,9 +6826,9 @@ - (void)mapViewDidFinishRenderingFrameFullyRendered:(BOOL)fullyRendered [self.style didChangeValueForKey:@"layers"]; } - if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame:fullyRendered:frameTime:)]) + if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame:fullyRendered:frameEncodingTime:frameRenderingTime:)]) { - [self.delegate mapViewDidFinishRenderingFrame:self fullyRendered:fullyRendered frameTime:frameTime]; + [self.delegate mapViewDidFinishRenderingFrame:self fullyRendered:fullyRendered frameEncodingTime:frameEncodingTime frameRenderingTime:frameRenderingTime]; } else if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame:fullyRendered:)]) { diff --git a/platform/ios/src/MLNMapViewDelegate.h b/platform/ios/src/MLNMapViewDelegate.h index 451cd06ccbf..ca48aab711e 100644 --- a/platform/ios/src/MLNMapViewDelegate.h +++ b/platform/ios/src/MLNMapViewDelegate.h @@ -254,7 +254,8 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)mapViewDidFinishRenderingFrame:(MLNMapView *)mapView fullyRendered:(BOOL)fullyRendered - frameTime:(double)frameTime; + frameEncodingTime:(double)frameEncodingTime + frameRenderingTime:(double)frameRenderingTime; /** Tells the delegate that the map view is entering an idle state, and no more diff --git a/platform/ios/src/MLNMapView_Private.h b/platform/ios/src/MLNMapView_Private.h index 90343b6df9a..de090836777 100644 --- a/platform/ios/src/MLNMapView_Private.h +++ b/platform/ios/src/MLNMapView_Private.h @@ -38,7 +38,8 @@ FOUNDATION_EXTERN MLN_EXPORT MLNExceptionName const _Nonnull MLNUnderlyingMapUna - (void)mapViewDidFailLoadingMapWithError:(nonnull NSError *)error; - (void)mapViewWillStartRenderingFrame; - (void)mapViewDidFinishRenderingFrameFullyRendered:(BOOL)fullyRendered - frameTime:(double)frameTime; + frameEncodingTime:(double)frameEncodingTime + frameRenderingTime:(double)frameRenderingTime; - (void)mapViewWillStartRenderingMap; - (void)mapViewDidFinishRenderingMapFullyRendered:(BOOL)fullyRendered; - (void)mapViewDidBecomeIdle; diff --git a/platform/ios/test/MLNMapViewDelegateIntegrationTests.swift b/platform/ios/test/MLNMapViewDelegateIntegrationTests.swift index 4192598fb9e..3742db89fa7 100644 --- a/platform/ios/test/MLNMapViewDelegateIntegrationTests.swift +++ b/platform/ios/test/MLNMapViewDelegateIntegrationTests.swift @@ -61,7 +61,7 @@ extension MLNMapViewDelegateIntegrationTests: MLNMapViewDelegate { func mapViewDidFinishRenderingFrame(_ mapView: MLNMapView, fullyRendered: Bool) {} - func mapViewDidFinishRenderingFrame(_ mapView: MLNMapView, fullyRendered: Bool, frameTime: Double) {} + func mapViewDidFinishRenderingFrame(_ mapView: MLNMapView, fullyRendered: Bool, frameEncodingTime: Double, frameRenderingTime: Double) {} func mapView(_ mapView: MLNMapView, shapeAnnotationIsEnabled annotation: MLNShape) -> Bool { return false } diff --git a/platform/qt/src/utils/renderer_observer.hpp b/platform/qt/src/utils/renderer_observer.hpp index 785c3ce8a06..f226910fbe2 100644 --- a/platform/qt/src/utils/renderer_observer.hpp +++ b/platform/qt/src/utils/renderer_observer.hpp @@ -33,10 +33,11 @@ class RendererObserver : public mbgl::RendererObserver { void onDidFinishRenderingFrame(RenderMode mode, bool repaintNeeded, bool placementChanged, - double frameTime) override { + double frameEncodingTime, + double frameRenderingTime) override { void (mbgl::RendererObserver::*f)( - RenderMode, bool, bool, double) = &mbgl::RendererObserver::onDidFinishRenderingFrame; - delegate.invoke(f, mode, repaintNeeded, placementChanged, frameTime); + RenderMode, bool, bool, double, double) = &mbgl::RendererObserver::onDidFinishRenderingFrame; + delegate.invoke(f, mode, repaintNeeded, placementChanged, frameEncodingTime, frameRenderingTime); } void onDidFinishRenderingMap() final { delegate.invoke(&mbgl::RendererObserver::onDidFinishRenderingMap); } diff --git a/src/mbgl/map/map_impl.cpp b/src/mbgl/map/map_impl.cpp index 9f901c3a505..789adc8e3b8 100644 --- a/src/mbgl/map/map_impl.cpp +++ b/src/mbgl/map/map_impl.cpp @@ -131,12 +131,16 @@ void Map::Impl::onWillStartRenderingFrame() { void Map::Impl::onDidFinishRenderingFrame(RenderMode renderMode, bool needsRepaint, bool placemenChanged, - double frameTime) { + double frameEncodingTime, + double frameRenderingTime) { rendererFullyLoaded = renderMode == RenderMode::Full; if (mode == MapMode::Continuous) { - observer.onDidFinishRenderingFrame( - {MapObserver::RenderMode(renderMode), needsRepaint, placemenChanged, frameTime}); + observer.onDidFinishRenderingFrame({MapObserver::RenderMode(renderMode), + needsRepaint, + placemenChanged, + frameEncodingTime, + frameRenderingTime}); if (needsRepaint || transform.inTransition()) { onUpdate(); diff --git a/src/mbgl/map/map_impl.hpp b/src/mbgl/map/map_impl.hpp index 025d0f9d6f2..2d7794f5dad 100644 --- a/src/mbgl/map/map_impl.hpp +++ b/src/mbgl/map/map_impl.hpp @@ -45,7 +45,7 @@ class Map::Impl final : public style::Observer, public RendererObserver { void onInvalidate() final; void onResourceError(std::exception_ptr) final; void onWillStartRenderingFrame() final; - void onDidFinishRenderingFrame(RenderMode, bool, bool, double) final; + void onDidFinishRenderingFrame(RenderMode, bool, bool, double, double) final; void onWillStartRenderingMap() final; void onDidFinishRenderingMap() final; void onStyleImageMissing(const std::string&, const std::function&) final; diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index ece37357228..e1f65a2b8da 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -382,16 +382,22 @@ void Renderer::Impl::render(const RenderTree& renderTree, // Ends the RenderPass parameters.renderPass.reset(); + + const auto startRendering = util::MonotonicTimer::now().count(); parameters.encoder->present(parameters.backend.getDefaultRenderable()); + const auto renderingTime = util::MonotonicTimer::now().count() - startRendering; // CommandEncoder destructor submits render commands. parameters.encoder.reset(); + const auto encodingTime = renderTree.getElapsedTime() - renderingTime; + observer->onDidFinishRenderingFrame( renderTreeParameters.loaded ? RendererObserver::RenderMode::Full : RendererObserver::RenderMode::Partial, renderTreeParameters.needsRepaint, renderTreeParameters.placementChanged, - renderTree.getElapsedTime()); + encodingTime, + renderingTime); if (!renderTreeParameters.loaded) { renderState = RenderState::Partial;