Benchmark app frame time improvements (#1776)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Alex Cristici
2023-11-03 23:03:52 +01:00
committed by GitHub
parent 827ced7b6f
commit 8ad5f9c1fd
21 changed files with 245 additions and 102 deletions

View File

@@ -42,7 +42,8 @@ public:
RenderMode mode; RenderMode mode;
bool needsRepaint; // In continous mode, shows that there are ongoig transitions. bool needsRepaint; // In continous mode, shows that there are ongoig transitions.
bool placementChanged; bool placementChanged;
double frameTime; double frameEncodingTime;
double frameRenderingTime;
}; };
virtual void onCameraWillChange(CameraChangeMode) {} virtual void onCameraWillChange(CameraChangeMode) {}

View File

@@ -36,7 +36,11 @@ public:
virtual void onDidFinishRenderingFrame(RenderMode, bool /*repaint*/, bool /*placementChanged*/) {} virtual void onDidFinishRenderingFrame(RenderMode, bool /*repaint*/, bool /*placementChanged*/) {}
/// End of frame, booleans flags that a repaint is required and that placement changed. /// 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); onDidFinishRenderingFrame(mode, repaint, placementChanged);
} }

View File

@@ -232,7 +232,7 @@ objc_library(
objc_library( objc_library(
name = "ios-benchapp", name = "ios-benchapp",
copts = MAPLIBRE_FLAGS, copts = CPP_FLAGS + MAPLIBRE_FLAGS,
srcs = ["//platform/ios/benchmark:ios_benchmark_srcs"], srcs = ["//platform/ios/benchmark:ios_benchmark_srcs"],
hdrs = ["//platform/ios/benchmark:ios_benchmark_hdrs"], hdrs = ["//platform/ios/benchmark:ios_benchmark_hdrs"],
includes = [ includes = [

View File

@@ -34,9 +34,11 @@ public:
void onDidFinishRenderingFrame(RenderMode mode, void onDidFinishRenderingFrame(RenderMode mode,
bool repaintNeeded, bool repaintNeeded,
bool placementChanged, bool placementChanged,
double frameTime) override { double frameEncodingTime,
void (RendererObserver::*f)(RenderMode, bool, bool, double) = &RendererObserver::onDidFinishRenderingFrame; double frameRenderingTime) override {
delegate.invoke(f, mode, repaintNeeded, placementChanged, frameTime); 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); } void onDidFinishRenderingMap() override { delegate.invoke(&RendererObserver::onDidFinishRenderingMap); }

View File

@@ -31,7 +31,8 @@ public:
float pixelRatio_, float pixelRatio_,
gfx::HeadlessBackend::SwapBehaviour swapBehavior = gfx::HeadlessBackend::SwapBehaviour::NoFlush, gfx::HeadlessBackend::SwapBehaviour swapBehavior = gfx::HeadlessBackend::SwapBehaviour::NoFlush,
gfx::ContextMode mode = gfx::ContextMode::Unique, gfx::ContextMode mode = gfx::ContextMode::Unique,
const std::optional<std::string>& localFontFamily = std::nullopt); const std::optional<std::string>& localFontFamily = std::nullopt,
bool invalidateOnUpdate_ = true);
~HeadlessFrontend() override; ~HeadlessFrontend() override;
void reset() override; void reset() override;
@@ -56,6 +57,7 @@ public:
PremultipliedImage readStillImage(); PremultipliedImage readStillImage();
RenderResult render(Map&); RenderResult render(Map&);
void renderOnce(Map&); void renderOnce(Map&);
void renderFrame();
std::optional<TransformState> getTransformState() const; std::optional<TransformState> getTransformState() const;
@@ -66,6 +68,7 @@ private:
std::atomic<double> frameTime; std::atomic<double> frameTime;
std::unique_ptr<gfx::HeadlessBackend> backend; std::unique_ptr<gfx::HeadlessBackend> backend;
util::AsyncTask asyncInvalidate; util::AsyncTask asyncInvalidate;
bool invalidateOnUpdate;
std::unique_ptr<Renderer> renderer; std::unique_ptr<Renderer> renderer;
std::shared_ptr<UpdateParameters> updateParameters; std::shared_ptr<UpdateParameters> updateParameters;

View File

@@ -21,7 +21,8 @@ HeadlessFrontend::HeadlessFrontend(Size size_,
float pixelRatio_, float pixelRatio_,
gfx::HeadlessBackend::SwapBehaviour swapBehavior, gfx::HeadlessBackend::SwapBehaviour swapBehavior,
const gfx::ContextMode contextMode, const gfx::ContextMode contextMode,
const std::optional<std::string>& localFontFamily) const std::optional<std::string>& localFontFamily,
bool invalidateOnUpdate_)
: size(size_), : size(size_),
pixelRatio(pixelRatio_), pixelRatio(pixelRatio_),
frameTime(0), frameTime(0),
@@ -29,23 +30,8 @@ HeadlessFrontend::HeadlessFrontend(Size size_,
{static_cast<uint32_t>(size.width * pixelRatio), static_cast<uint32_t>(size.height * pixelRatio)}, {static_cast<uint32_t>(size.width * pixelRatio), static_cast<uint32_t>(size.height * pixelRatio)},
swapBehavior, swapBehavior,
contextMode)), contextMode)),
asyncInvalidate([this] { asyncInvalidate([this] { renderFrame(); }),
if (renderer && updateParameters) { invalidateOnUpdate(invalidateOnUpdate_),
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();
}
}),
renderer(std::make_unique<Renderer>(*getBackend(), pixelRatio, localFontFamily)) {} renderer(std::make_unique<Renderer>(*getBackend(), pixelRatio, localFontFamily)) {}
HeadlessFrontend::~HeadlessFrontend() = default; HeadlessFrontend::~HeadlessFrontend() = default;
@@ -57,7 +43,9 @@ void HeadlessFrontend::reset() {
void HeadlessFrontend::update(std::shared_ptr<UpdateParameters> updateParameters_) { void HeadlessFrontend::update(std::shared_ptr<UpdateParameters> updateParameters_) {
updateParameters = updateParameters_; updateParameters = updateParameters_;
asyncInvalidate.send(); if (invalidateOnUpdate) {
asyncInvalidate.send();
}
} }
void HeadlessFrontend::setObserver(RendererObserver& observer_) { void HeadlessFrontend::setObserver(RendererObserver& observer_) {
@@ -169,6 +157,24 @@ void HeadlessFrontend::renderOnce(Map&) {
util::RunLoop::Get()->runOnce(); 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<TransformState> HeadlessFrontend::getTransformState() const { std::optional<TransformState> HeadlessFrontend::getTransformState() const {
if (updateParameters) { if (updateParameters) {
return updateParameters->transformState; return updateParameters->transformState;

View File

@@ -39,9 +39,11 @@ public:
void onDidFinishRenderingFrame(RenderMode mode, void onDidFinishRenderingFrame(RenderMode mode,
bool repaintNeeded, bool repaintNeeded,
bool placementChanged, bool placementChanged,
double frameTime) override { double frameEncodingTime,
void (RendererObserver::*f)(RenderMode, bool, bool, double) = &RendererObserver::onDidFinishRenderingFrame; double frameRenderingTime) override {
delegate.invoke(f, mode, repaintNeeded, placementChanged, frameTime); 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 { void onStyleImageMissing(const std::string& image, const StyleImageMissingCallback& cb) override {
@@ -84,11 +86,13 @@ public:
void onDidFinishRenderingFrame(RenderMode mode, void onDidFinishRenderingFrame(RenderMode mode,
bool repaintNeeded, bool repaintNeeded,
bool placementChanged, bool placementChanged,
double frameTime) override { double frameEncodingTime,
double frameRenderingTime) override {
if (mode == RenderMode::Full && hasPendingStillImageRequest) { if (mode == RenderMode::Full && hasPendingStillImageRequest) {
stillImage = frontend.readStillImage(); 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 { void onStyleImageMissing(const std::string& id, const StyleImageMissingCallback& done) override {

View File

@@ -2478,9 +2478,9 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
return features; 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) { if (self.frameTimeGraphEnabled) {
[self.frameTimeGraphView updatePathWithFrameDuration:frameTime]; [self.frameTimeGraphView updatePathWithFrameDuration:frameEncodingTime];
} }
} }

View File

@@ -3,8 +3,8 @@ load("//platform/ios/bazel:macros.bzl", "info_plist")
filegroup( filegroup(
name = "ios_benchmark_srcs", name = "ios_benchmark_srcs",
srcs = [ srcs = [
"main.m", "main.mm",
"MBXBenchAppDelegate.m", "MBXBenchAppDelegate.mm",
"MBXBenchViewController.mm", "MBXBenchViewController.mm",
"locations.cpp", "locations.cpp",
], ],

View File

@@ -1,15 +1,52 @@
#import "MBXBenchViewController.h" #import "MBXBenchViewController.h"
#import "MBXBenchAppDelegate.h" #import "MBXBenchAppDelegate.h"
#import "MLNMapView_Private.h" #import "MLNMapView_Private.h"
#import "MLNMapViewDelegate.h" #import "MLNOfflineStorage_Private.h"
#import "MLNSettings_Private.h"
#include "locations.hpp" #include "locations.hpp"
#include <chrono> #include <chrono>
@interface MBXBenchViewController () <MLNMapViewDelegate> #include <mbgl/gfx/backend_scope.hpp>
#include <mbgl/gfx/headless_frontend.hpp>
#include <mbgl/map/map.hpp>
#include <mbgl/style/style.hpp>
@property (nonatomic) MLNMapView *mapView; @protocol BenchMapDelegate <NSObject>
- (void)mapDidFinishRenderingFrameFullyRendered:(BOOL)fullyRendered
frameEncodingTime:(double)frameEncodingTime
frameRenderingTime:(double)frameRenderingTime;
@end
class BenchMapObserver : public mbgl::MapObserver {
public:
BenchMapObserver() = delete;
BenchMapObserver(id<BenchMapDelegate> 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<BenchMapDelegate> mapDelegate = nullptr;
};
@interface MBXBenchViewController () <BenchMapDelegate> {
std::unique_ptr<BenchMapObserver> observer;
std::unique_ptr<mbgl::HeadlessFrontend> frontend;
std::unique_ptr<mbgl::Map> map;
}
@property (nonatomic) UIImageView *imageView;
@end @end
@@ -32,6 +69,15 @@
- (void)viewDidLoad - (void)viewDidLoad
{ {
[super 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. // 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"]; NSURL *tile = [[NSBundle mainBundle] URLForResource:@"11" withExtension:@"pbf" subdirectory:@"tiles/tiles/v3/5/7"];
@@ -42,28 +88,50 @@
constexpr auto styleIndex = 0; constexpr auto styleIndex = 0;
NSURL *url = [NSURL URLWithString:tile ? @"asset://styles/streets.json" : [NSString stringWithCString:styles[styleIndex].c_str() encoding:NSUTF8StringEncoding]]; NSURL *url = [NSURL URLWithString:tile ? @"asset://styles/streets.json" : [NSString stringWithCString:styles[styleIndex].c_str() encoding:NSUTF8StringEncoding]];
NSLog(@"Using style URL: \"%@\"", [url absoluteString]); NSLog(@"Using style URL: \"%@\"", [url absoluteString]);
self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
self.imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:self.imageView];
self.mapView = [[MLNMapView alloc] initWithFrame:self.view.bounds styleURL:url]; mbgl::Size viewSize = { static_cast<uint32_t>(self.view.bounds.size.width),
self.mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; static_cast<uint32_t>(self.view.bounds.size.height) };
self.mapView.delegate = self; auto pixelRatio = [[UIScreen mainScreen] scale];
self.mapView.opaque = YES;
self.mapView.zoomEnabled = NO; observer = std::make_unique<BenchMapObserver>(self);
self.mapView.scrollEnabled = NO; frontend = std::make_unique<mbgl::HeadlessFrontend>(
self.mapView.rotateEnabled = NO; viewSize,
self.mapView.userInteractionEnabled = YES; pixelRatio,
self.mapView.preferredFramesPerSecond = MLNMapViewPreferredFramesPerSecondMaximum; mbgl::gfx::HeadlessBackend::SwapBehaviour::Flush,
mbgl::gfx::ContextMode::Unique,
[self.view addSubview:self.mapView]; /* localFontFamily */ std::nullopt,
} /* invalidateOnUpdate */ false
);
- (void)viewDidAppear:(BOOL)animated
{ mbgl::MapOptions mapOptions;
[super viewDidAppear:animated]; 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<mbgl::Map>(*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]; [self startBenchmarkIteration];
} }
@@ -110,9 +178,10 @@ NSDate* const currentDate = [NSDate date];
size_t idx = 0; size_t idx = 0;
enum class State { None, WaitingForAssets, WarmingUp, Benchmarking } state = State::None; enum class State { None, WaitingForAssets, WarmingUp, Benchmarking } state = State::None;
int frames = 0; int frames = 0;
double totalFrameTime = 0; double totalFrameEncodingTime = 0;
double totalFrameRenderingTime = 0;
std::chrono::steady_clock::time_point started; std::chrono::steady_clock::time_point started;
std::vector<std::pair<std::string, double> > result; std::vector<std::pair<std::string, std::pair<double, double>> > result;
static const int warmupDuration = 20; // frames static const int warmupDuration = 20; // frames
static const int benchmarkDuration = 200; // frames static const int benchmarkDuration = 200; // frames
@@ -121,31 +190,70 @@ namespace mbgl {
extern std::size_t uploadCount, uploadBuildCount, uploadVertextAttrsDirty, uploadInvalidSegments; 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 - (void)startBenchmarkIteration
{ {
if (mbgl::bench::locations.size() > idx) { if (mbgl::bench::locations.size() > idx) {
const auto& location = mbgl::bench::locations[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; state = State::WaitingForAssets;
NSLog(@"Benchmarking \"%s\"", location.name.c_str()); NSLog(@"Benchmarking \"%s\"", location.name.c_str());
NSLog(@"- Loading assets..."); NSLog(@"- Loading assets...");
dispatch_async(dispatch_get_main_queue(), ^{
[self renderFrame];
});
} else { } else {
// Do nothing. The benchmark is completed. // Do nothing. The benchmark is completed.
NSLog(@"Benchmark completed."); NSLog(@"Benchmark completed.");
NSLog(@"Result:"); NSLog(@"Result:");
double totalFrameTime = 0;
size_t colWidth = 0; size_t colWidth = 0;
for (const auto& row : result) { for (const auto& row : result) {
colWidth = std::max(row.first.size(), colWidth); colWidth = std::max(row.first.size(), colWidth);
} }
double totalFrameEncodingTime = 0;
double totalFrameRenderingTime = 0;
for (const auto& row : result) { 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); NSLog(@"| %-*s | %4.1f ms | %4.1f ms |", int(colWidth), row.first.c_str(), 1e3 * row.second.first, 1e3 * row.second.second);
totalFrameTime += row.second; totalFrameEncodingTime += row.second.first;
totalFrameRenderingTime += row.second.second;
} }
NSLog(@"Average frame time: %4.1f ms", totalFrameTime * 1e3 / result.size()); NSLog(@"Average frame encoding time: %4.1f ms", totalFrameEncodingTime * 1e3 / result.size());
NSLog(@"Average FPS: %4.1f", result.size() / totalFrameTime); NSLog(@"Average frame rendering time: %4.1f ms", totalFrameRenderingTime * 1e3 / result.size());
// NSLog(@"Total uploads: %zu", mbgl::uploadCount); // NSLog(@"Total uploads: %zu", mbgl::uploadCount);
// NSLog(@"Total uploads with dirty vattr: %zu", mbgl::uploadVertextAttrsDirty); // NSLog(@"Total uploads with dirty vattr: %zu", mbgl::uploadVertextAttrsDirty);
@@ -165,21 +273,18 @@ namespace mbgl {
{ {
[app performSelector:@selector(suspend)]; [app performSelector:@selector(suspend)];
} }
else if ([app respondsToSelector:@selector(terminate)])
{
[app performSelector:@selector(terminate)];
}
} }
} }
- (void)mapViewDidFinishRenderingFrame:(MLNMapView *)mapView - (void)mapDidFinishRenderingFrameFullyRendered:(__unused BOOL)fullyRendered
fullyRendered:(__unused BOOL)fullyRendered frameEncodingTime:(double)frameEncodingTime
frameTime:(double)frameTime frameRenderingTime:(double)frameRenderingTime
{ {
if (state == State::Benchmarking) if (state == State::Benchmarking)
{ {
frames++; frames++;
totalFrameTime += frameTime; totalFrameEncodingTime += frameEncodingTime;
totalFrameRenderingTime += frameRenderingTime;
if (frames >= benchmarkDuration) if (frames >= benchmarkDuration)
{ {
state = State::None; state = State::None;
@@ -187,45 +292,49 @@ namespace mbgl {
// Report FPS // Report FPS
const auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - started).count(); const auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - started).count();
const auto wallClockFPS = double(frames * 1e6) / duration; const auto wallClockFPS = double(frames * 1e6) / duration;
const auto frameTime = static_cast<double>(totalFrameTime) / frames; const auto frameEncodingTime = static_cast<double>(totalFrameEncodingTime) / frames;
const auto potentialFPS = 1.0 / frameTime; const auto frameRenderingTime = static_cast<double>(totalFrameRenderingTime) / frames;
result.emplace_back(mbgl::bench::locations[idx].name, frameTime); result.emplace_back(mbgl::bench::locations[idx].name, std::make_pair(frameEncodingTime, frameRenderingTime));
NSLog(@"- Frame time: %.1f ms, FPS: %.1f (%.1f sync FPS)", frameTime * 1e3, potentialFPS, wallClockFPS); NSLog(@"- Frame encoding time: %.1f ms, Frame rendering time: %.1f ms (%.1f sync FPS)", frameEncodingTime * 1e3, frameRenderingTime * 1e3, wallClockFPS);
// Start benchmarking the next location. // Start benchmarking the next location.
idx++; idx++;
[self startBenchmarkIteration]; [self startBenchmarkIteration];
} else { } else {
[mapView setNeedsRerender]; dispatch_async(dispatch_get_main_queue(), ^{
[self renderFrame];
});
} }
return; return;
} }
else if (state == State::WarmingUp) else if (state == State::WarmingUp)
{ {
frames++; frames++;
if (frames >= warmupDuration) if (frames >= warmupDuration)
{ {
frames = 0; frames = 0;
totalFrameTime = 0; totalFrameEncodingTime = 0;
totalFrameRenderingTime = 0;
state = State::Benchmarking; state = State::Benchmarking;
started = std::chrono::steady_clock::now(); started = std::chrono::steady_clock::now();
NSLog(@"- Benchmarking for %d frames...", benchmarkDuration); NSLog(@"- Benchmarking for %d frames...", benchmarkDuration);
} }
[mapView setNeedsRerender]; dispatch_async(dispatch_get_main_queue(), ^{
[self renderFrame];
});
return; return;
} }
else if (state == State::WaitingForAssets) else if (state == State::WaitingForAssets)
{ {
if ([mapView isFullyLoaded]) if (map->isFullyLoaded())
{ {
// Start the benchmarking timer. // Start the benchmarking timer.
state = State::WarmingUp; state = State::WarmingUp;
[self.mapView didReceiveMemoryWarning];
NSLog(@"- Warming up for %d frames...", warmupDuration); NSLog(@"- Warming up for %d frames...", warmupDuration);
[mapView setNeedsRerender];
} }
dispatch_async(dispatch_get_main_queue(), ^{
[self renderFrame];
});
return; return;
} }
} }

View File

@@ -81,7 +81,7 @@ void MLNMapViewImpl::onWillStartRenderingFrame() {
void MLNMapViewImpl::onDidFinishRenderingFrame(mbgl::MapObserver::RenderFrameStatus status) { void MLNMapViewImpl::onDidFinishRenderingFrame(mbgl::MapObserver::RenderFrameStatus status) {
bool fullyRendered = status.mode == mbgl::MapObserver::RenderMode::Full; 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() { void MLNMapViewImpl::onWillStartRenderingMap() {

View File

@@ -6813,7 +6813,8 @@ static void *windowScreenContext = &windowScreenContext;
} }
- (void)mapViewDidFinishRenderingFrameFullyRendered:(BOOL)fullyRendered - (void)mapViewDidFinishRenderingFrameFullyRendered:(BOOL)fullyRendered
frameTime:(double)frameTime { frameEncodingTime:(double)frameEncodingTime
frameRenderingTime:(double)frameRenderingTime {
if (!_mbglMap) if (!_mbglMap)
{ {
return; return;
@@ -6825,9 +6826,9 @@ static void *windowScreenContext = &windowScreenContext;
[self.style didChangeValueForKey:@"layers"]; [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:)]) else if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame:fullyRendered:)])
{ {

View File

@@ -254,7 +254,8 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
- (void)mapViewDidFinishRenderingFrame:(MLNMapView *)mapView - (void)mapViewDidFinishRenderingFrame:(MLNMapView *)mapView
fullyRendered:(BOOL)fullyRendered 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 Tells the delegate that the map view is entering an idle state, and no more

View File

@@ -38,7 +38,8 @@ FOUNDATION_EXTERN MLN_EXPORT MLNExceptionName const _Nonnull MLNUnderlyingMapUna
- (void)mapViewDidFailLoadingMapWithError:(nonnull NSError *)error; - (void)mapViewDidFailLoadingMapWithError:(nonnull NSError *)error;
- (void)mapViewWillStartRenderingFrame; - (void)mapViewWillStartRenderingFrame;
- (void)mapViewDidFinishRenderingFrameFullyRendered:(BOOL)fullyRendered - (void)mapViewDidFinishRenderingFrameFullyRendered:(BOOL)fullyRendered
frameTime:(double)frameTime; frameEncodingTime:(double)frameEncodingTime
frameRenderingTime:(double)frameRenderingTime;
- (void)mapViewWillStartRenderingMap; - (void)mapViewWillStartRenderingMap;
- (void)mapViewDidFinishRenderingMapFullyRendered:(BOOL)fullyRendered; - (void)mapViewDidFinishRenderingMapFullyRendered:(BOOL)fullyRendered;
- (void)mapViewDidBecomeIdle; - (void)mapViewDidBecomeIdle;

View File

@@ -61,7 +61,7 @@ extension MLNMapViewDelegateIntegrationTests: MLNMapViewDelegate {
func mapViewDidFinishRenderingFrame(_ mapView: MLNMapView, fullyRendered: Bool) {} 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 } func mapView(_ mapView: MLNMapView, shapeAnnotationIsEnabled annotation: MLNShape) -> Bool { return false }

View File

@@ -33,10 +33,11 @@ public:
void onDidFinishRenderingFrame(RenderMode mode, void onDidFinishRenderingFrame(RenderMode mode,
bool repaintNeeded, bool repaintNeeded,
bool placementChanged, bool placementChanged,
double frameTime) override { double frameEncodingTime,
double frameRenderingTime) override {
void (mbgl::RendererObserver::*f)( void (mbgl::RendererObserver::*f)(
RenderMode, bool, bool, double) = &mbgl::RendererObserver::onDidFinishRenderingFrame; RenderMode, bool, bool, double, double) = &mbgl::RendererObserver::onDidFinishRenderingFrame;
delegate.invoke(f, mode, repaintNeeded, placementChanged, frameTime); delegate.invoke(f, mode, repaintNeeded, placementChanged, frameEncodingTime, frameRenderingTime);
} }
void onDidFinishRenderingMap() final { delegate.invoke(&mbgl::RendererObserver::onDidFinishRenderingMap); } void onDidFinishRenderingMap() final { delegate.invoke(&mbgl::RendererObserver::onDidFinishRenderingMap); }

View File

@@ -131,12 +131,16 @@ void Map::Impl::onWillStartRenderingFrame() {
void Map::Impl::onDidFinishRenderingFrame(RenderMode renderMode, void Map::Impl::onDidFinishRenderingFrame(RenderMode renderMode,
bool needsRepaint, bool needsRepaint,
bool placemenChanged, bool placemenChanged,
double frameTime) { double frameEncodingTime,
double frameRenderingTime) {
rendererFullyLoaded = renderMode == RenderMode::Full; rendererFullyLoaded = renderMode == RenderMode::Full;
if (mode == MapMode::Continuous) { if (mode == MapMode::Continuous) {
observer.onDidFinishRenderingFrame( observer.onDidFinishRenderingFrame({MapObserver::RenderMode(renderMode),
{MapObserver::RenderMode(renderMode), needsRepaint, placemenChanged, frameTime}); needsRepaint,
placemenChanged,
frameEncodingTime,
frameRenderingTime});
if (needsRepaint || transform.inTransition()) { if (needsRepaint || transform.inTransition()) {
onUpdate(); onUpdate();

View File

@@ -45,7 +45,7 @@ public:
void onInvalidate() final; void onInvalidate() final;
void onResourceError(std::exception_ptr) final; void onResourceError(std::exception_ptr) final;
void onWillStartRenderingFrame() final; void onWillStartRenderingFrame() final;
void onDidFinishRenderingFrame(RenderMode, bool, bool, double) final; void onDidFinishRenderingFrame(RenderMode, bool, bool, double, double) final;
void onWillStartRenderingMap() final; void onWillStartRenderingMap() final;
void onDidFinishRenderingMap() final; void onDidFinishRenderingMap() final;
void onStyleImageMissing(const std::string&, const std::function<void()>&) final; void onStyleImageMissing(const std::string&, const std::function<void()>&) final;

View File

@@ -382,16 +382,22 @@ void Renderer::Impl::render(const RenderTree& renderTree,
// Ends the RenderPass // Ends the RenderPass
parameters.renderPass.reset(); parameters.renderPass.reset();
const auto startRendering = util::MonotonicTimer::now().count();
parameters.encoder->present(parameters.backend.getDefaultRenderable()); parameters.encoder->present(parameters.backend.getDefaultRenderable());
const auto renderingTime = util::MonotonicTimer::now().count() - startRendering;
// CommandEncoder destructor submits render commands. // CommandEncoder destructor submits render commands.
parameters.encoder.reset(); parameters.encoder.reset();
const auto encodingTime = renderTree.getElapsedTime() - renderingTime;
observer->onDidFinishRenderingFrame( observer->onDidFinishRenderingFrame(
renderTreeParameters.loaded ? RendererObserver::RenderMode::Full : RendererObserver::RenderMode::Partial, renderTreeParameters.loaded ? RendererObserver::RenderMode::Full : RendererObserver::RenderMode::Partial,
renderTreeParameters.needsRepaint, renderTreeParameters.needsRepaint,
renderTreeParameters.placementChanged, renderTreeParameters.placementChanged,
renderTree.getElapsedTime()); encodingTime,
renderingTime);
if (!renderTreeParameters.loaded) { if (!renderTreeParameters.loaded) {
renderState = RenderState::Partial; renderState = RenderState::Partial;