Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance issues #10

Closed
filipproch opened this issue Aug 11, 2021 · 25 comments
Closed

Performance issues #10

filipproch opened this issue Aug 11, 2021 · 25 comments

Comments

@filipproch
Copy link

Hi there,

first kudos, its amazing someone finally took to implement native vector maps for Flutter,

unfortunately, I just tried this package with our own tiles and I am having serious performance issues, basically the map freezes and I am unable to interact with it, after every zoom change/movement, for a few seconds.

This happens both in debug mode (Android emulator/device) and release mode.

Our config: https://gist.github.com/filipproch/a010f95fcab7a559ab3074c3ebd0e290
We use the map mostly over Czech Republic - Europe

I would like to contribute to help improve this, but not sure where exactly to look/start
or maybe you already have some ideas for performance improvements?

@greensopinion
Copy link
Owner

Hi, thanks for the kudos!

Performance is a real problem when it comes to vector tiles. So great to hear that you're interested in contributing.

There are a few things that you can do to tune the codebase as-is:

  1. Check that you're using RenderMode.mixed, which uses raster tiles (rendered from vector data) while zooming, and then renders again with vector tiles when zooming is finished.
  2. Check your tile data to verify that it doesn't include unnecessary information that would render outside of the tile bounds.
  3. Tune your map theme to avoid rendering unnecessary polygons/lines
  4. Check your memory usage - if the app is freezing, it's possible that you are using too much memory and hitting the upper limits.

I've done the above and come up with a decent frame rate while zooming/panning. There is some jitter (i.e. skipped frames) especially in dense regions (e.g. London, UK) however the user experience is still pretty good.

At one point I did experience freezing as you mentioned. I made some changes to the codebase to avoid rendering outside of the tile area for labels on the Canvas, and that made a huge difference. There's potentially more that could be done here (e.g. with polygons/lines) I wonder if your tiles contain shapes outside of the tile rendering area? That could be a good place to start with a contribution.

The Flutter memory view is really helpful to see if you're hitting an upper memory limit.

@filipproch
Copy link
Author

Thanks for your fast reply, I will experiment and try to get into the code more. With Memory view didn't see immediate problems - so I started debugging it with the performance view (see below). I will write updates in this issue if I manage to get some improvements.

image

@greensopinion
Copy link
Owner

I've done some more work in this area since I was noticing jank on iPad.

It's still not perfect, but these two commits make a big difference:

b46c80a
55f3841

Also, I've found that tuning the map style to reduce detail at different zoom levels can help. For example, the style that I'm using has minzoom=14 for road_minor, and minzoom=15 for road_minor_casing. By having casing for minor roads only render at zoom level 15 and above, there's half as much work to do at zoom level < 15.

@filipproch
Copy link
Author

Cool, I will try it out. I should get to experiment with it more during the upcoming week

@greensopinion
Copy link
Owner

greensopinion commented Oct 6, 2021 via email

@HusseinCopol
Copy link

I've done some more work in this area since I was noticing jank on iPad.

It's still not perfect, but these two commits make a big difference:

b46c80a 55f3841

Also, I've found that tuning the map style to reduce detail at different zoom levels can help. For example, the style that I'm using has minzoom=14 for road_minor, and minzoom=15 for road_minor_casing. By having casing for minor roads only render at zoom level 15 and above, there's half as much work to do at zoom level < 15.

Hi.
I have performance issues too , how do i tune my map style to reduce details?
thanks

@greensopinion
Copy link
Owner

how do i tune my map style to reduce details?

to do that you'll need to edit the map style, which you could do with a text editor but it's easier with something like https://maputnik.github.io

The fewer things showing on a map, the less computationally expensive it is (i.e. the faster it will render.) There are a few ways to achieve fewer things while still having beautiful maps:

  1. Consider rendering finer details only at higher zoom levels. For example, if your style shows buildings, consider having them show up only when the map is zoomed level 14, 15 or higher. That can be achieved with the minzoom property. The same applies for things like trails, alleys, and minor roads.

  2. Consider reducing the level of detail of roads by eliminating borders at lower zoom levels. Most styles that I've seen render roads twice at different widths to give the effect of a border. By eliminating road borders, there's less work in rendering those tiles.

I hope that helps. If it's still not enough, you could use raster rendering which will provide the styling advantages of vector tiles, but without the crisp edges.

@HusseinCopol
Copy link

how do i tune my map style to reduce details?

to do that you'll need to edit the map style, which you could do with a text editor but it's easier with something like https://maputnik.github.io

The fewer things showing on a map, the less computationally expensive it is (i.e. the faster it will render.) There are a few ways to achieve fewer things while still having beautiful maps:

1. Consider rendering finer details only at higher zoom levels. For example, if your style shows buildings, consider having them show up only when the map is zoomed level 14, 15 or higher. That can be achieved with the `minzoom` property. The same applies for things like trails, alleys, and minor roads.

2. Consider reducing the level of detail of roads by eliminating borders at lower zoom levels. Most styles that I've seen render roads twice at different widths to give the effect of a border. By eliminating road borders, there's less work in rendering those tiles.

I hope that helps. If it's still not enough, you could use raster rendering which will provide the styling advantages of vector tiles, but without the crisp edges.

thanks.i have one more question! maputnik gave me a JSON file and where do i use it? the map controller didn't have any styling property (like google map controller ) .

@mvarendorff
Copy link

mvarendorff commented Nov 26, 2021

@HusseinCopol You'd use that in your dart code like this:

const _themeData = /* { all of this is your json } */;

final mapTheme = ThemeReader().read(_themeData); // You can also supply logger: Logger.console() to the ThemeReader to spot parsing issues with your theme.

Then in your VectorTileLayerOptions, you can provide your theme by setting theme: mapTheme.

ThemeReader needs import 'package:vector_tile_renderer/vector_tile_renderer.dart'; which is a dependency of this library so it should be available.

@mvarendorff
Copy link

I've found that most of the time spent rendering is looping over the theme layers so to increase performance, you can start by throwing out all layers from your theme, that you won't need. For example I built an app with a map scoped in Germany but the default theme from Mapbox includes a theme layer for US highway symbols. After trimming down the layers from a few hundred to just over a dozen which suffice in my case, performance went from 5 FPS went around 50 when zooming, moving and rotating.

Of course if you have layers that should only show at specific zoom levels, using the minzoom/maxzoom properties works as well.

@FaFre
Copy link
Contributor

FaFre commented Dec 19, 2021

I read the rendering code today, it was really a pleasure. Great work @greensopinion !

I was also doing some canvas painting, but on top of the standard flutter_map tile layer. In my case I had a big performance improvement when actually adding lines via addPolygon() to the Path. It's a lot faster compared to lineTo()when having larger linestrings. However, I tried it out and couldn't feel a notably difference when applying to this project. Maybe it's a little bit faster, but I haven't done any benchmarking. I think because the tiled linestrings are already very small, so most CPU time is taken by the painting overhead.

When interested, take a look here: FaFre/dart-vector-tile-renderer@f43fffe

But I think having 1-4 long running isolates and distribute the tile rendering jobs to them will bring a huge performance improvement. Downside, isolates are not supported for web.

@greensopinion
Copy link
Owner

greensopinion commented Dec 19, 2021

I read the rendering code today, it was really a pleasure. Great work @greensopinion !

Thanks @FaFre, great to hear.

I was also doing some canvas painting, but on top of the standard flutter_map tile layer. In my case I had a big performance improvement when actually adding lines via addPolygon() to the Path. It's a lot faster compared to lineTo()when having larger linestrings. However, I tried it out and couldn't feel a notably difference when applying to this project. Maybe it's a little bit faster, but I haven't done any benchmarking. I think because the tiled linestrings are already very small, so most CPU time is taken by the painting overhead.

When interested, take a look here: FaFre/dart-vector-tile-renderer@f43fffe

Feel free to issue a pull request - this is worth adding regardless of performance since it simplifies the code.

But I think having 1-4 long running isolates and distribute the tile rendering jobs to them will bring a huge performance improvement. Downside, isolates are not supported for web.

What do you have in mind for rendering jobs? From what I understand, calls to Canvas can only be done on the UI thread.

@filipproch
Copy link
Author

regarding canvas on UI thread, there is a Flutter issue for this FYI
flutter/flutter#75755

@FaFre
Copy link
Contributor

FaFre commented Dec 20, 2021

Ohh too bad. I thought through the rasterization it could be also done in an isolate, but it seems like that it's not even possible to instantiate a Canvas inside an isolate. However, with the new release they put a lot of focus on isolates. Hopefully it will be possible soon.

@greensopinion
Copy link
Owner

@FaFre I couldn't wait - with some profiling I made a few changes inspired by your suggestion greensopinion/dart-vector-tile-renderer@548c0cc

@greensopinion
Copy link
Owner

I've had a run at some performance related changes. There's more work to do, but there are some noticeable improvements in the latest release.

  • eliminated unnecessary re-rendering of tiles while panning
  • reduced CPU overhead of text labels
  • reduced memory overhead
  • improved cache hit rate

@greensopinion
Copy link
Owner

The latest release has more performance improvements.

Some performance issues remain, clearly showing up as jank with the default theme.

Current recommendations:

  1. Tune your theme:
    • reduce unnecessary map elements (such as buildings, minor roads, labels) at lower zoom levels using minzoom
    • avoid rendering unnecessary polygons/lines
  2. Check that you're using RenderMode.mixed, which uses raster tiles (rendered from vector data) while zooming, and then renders again with vector tiles when zooming is finished.
  3. Check your tile data to verify that it doesn't include unnecessary information that would render outside of the tile bounds.
  4. Check your memory usage and avoid large cache sizes - if the app is freezing, it's possible that you are using too much memory and hitting the upper limits.

@greensopinion
Copy link
Owner

Closing based on recent optimizations including issues #21. Feel free to open a new issue if there continues to be a problem.

@greensopinion
Copy link
Owner

A lot of work has been done to improve performance. There is still room for more improvement, however in testing I've found that RenderMode.mixed is no longer needed for my use-case. See #21 for related discussion.

@ibrierley
Copy link

I'm just adding a few bits here, after recent thoughts on the removal of raster discussion...some has already been covered I think, so apologies, just getting it out of my head before it pops out again. Some may have already been done! But writing it down in case someone wants to explore (I don't think I've got quite time to dabble atm, but will if I do).

Generally in Flutter, canvas paths are typically very slow to render with strokewidths sub 1px (especially when zoomed) as it uses a different algorithm for linecaps etc. Sometimes there may be a path say at 1.2px...when pinchzooming, could anything between 1-2px be rendered at 0.99px for example (I experimented a bit with this in the renderer, and maybe it helped a little, in some cases, but not sure how the renderer would know if pinchzooming, or if thats better done outside the rendering code and figured before any calls to it)

Generally calls to drawPath should be minimised. Can paths with the same style be grouped into one superpath (with a Move inbetween them), cache that path and draw (may be fiddly if using expressions that change per zoom, but could be done in inbeween zoom level changes). This may be similar to the addPolygon idea, but we can also group multiple lines as well.

TextLayouts are typically expensive, so can that be cached (I know thats been discussed a bit before, not sure where that is currently), I was thinking about a simple storage of a previous frame type cache even. If a labels textlayout (based on latlng point and name for example) was stored in a hash of previous display frame/update, it should be able to be reused (I think!).

I played with the idea of "overZooming" (I think there's another term for it, but it eludes me). So let's say we are at zoom 14. We draw the tiles at zoom 13, which means you only need a quarter of the tiles (some loss of lines maybe, but for tiles that have too much data it can help a bit). (Does this also play into the mapbox tilesize of 512 vs 256, not sure ?)

Instead of drawPath, during a pinchzoom, can Canvas drawPoints with a PointMode of lines/polygon be used ? It may not need to calculate linecaps etc do maybe faster (similar to earlier...if a strokeWidth is < 4 say, do we care about accuracy of joins ? Now, I did try this a year or two ago, and the odd time got an out of mem error, but I think this was referencing GPU memory for point storage, and it may well be all of that is fixed now.

If pinchzooming, could rendering be interrupted if a new zoom level comes in (bit fiddly this one...one may end up with blank tiles, not sure I like this...) ?

Out of interest, is there a way for the rendering code to know that a pinchzoom is taking place ? I couldn't quite tell from the code if that's possible or not...

@greensopinion
Copy link
Owner

greensopinion commented Feb 23, 2022 via email

@ibrierley
Copy link

Thinking out loud, I may be barking up the wrong street, so this may not apply! I was thinking about why I didn't really get any major text performance issues later on when experimenting with vector tiles.

When I was doing Vector stuff, in the end, I ended up not using Positioned Widgets (maybe a hybrid is interesting..can't remember if I tried this). I suspect what may be happening is that every zoom change, all the text layouts need to be recalculated, and then readjusted inversely according to the scale (and text layout is very expensive iirc).

However, in reality, we don't need to do any of this at all (unless we are either literally resizing the text to fit in with a road width for example, or curving text along a road). At all times, typically we want the text to be the same size (visually to the human eye). So we don't need to recalculate any of this, one can just reuse the old layout and draw on one none transformed Canvas.

For example...all on one single upper Canvas

///pos = tile position..
matrix = Matrix4.identity();
matrix..translate(pos.point.x.toDouble(), pos.point.y.toDouble())
          ..scale(pos.scale);

...


for (Label label in labels) {

    label.transformedPoint =
            MatrixUtils.transformPoint(matrix, label.point);

         // collision detection, maybe every x frames

         //canvas.save(); // only if map rotated

    canvas.translate(
            transformedPoint.dx, transformedPoint.dy);

         //canvas.rotate(-widgetRotation * 0.0174533); // only if map rotated
         //canvas.restore(); // only if map rotated




Apologies again, if this is a redherring.

@serraojoao
Copy link

Where is this RenderMode being set? could not find it anywhere with latest version of the package

@greensopinion
Copy link
Owner

Where is this RenderMode being set? could not find it anywhere with latest version of the package

RenderMode was removed, see discussion on #31 and implementation on #52

@ch-muhammad-adil
Copy link

ch-muhammad-adil commented Feb 3, 2023

@HusseinCopol You'd use that in your dart code like this:

const _themeData = /* { all of this is your json } */;

final mapTheme = ThemeReader().read(_themeData); // You can also supply logger: Logger.console() to the ThemeReader to spot parsing issues with your theme.

Then in your VectorTileLayerOptions, you can provide your theme by setting theme: mapTheme.

ThemeReader needs import 'package:vector_tile_renderer/vector_tile_renderer.dart'; which is a dependency of this library so it should be available.

With this implementation you have to make sure that chosen theme and map providers are same. In my case I have been using Stadia map provider and picked a theme from GeoApify. I started facing problems because of incompatibilities. Then I switched to GeoApify map provider with the following link inside _urlTemplate()

https://maps.geoapify.com/v1/tile/klokantech-basic/{z}/{x}/{y}.pbf?apiKey=YOUR_API_KEY

I have picked theme style from here

https://maps.geoapify.com/v1/styles/klokantech-basic/style.json?apiKey=YOUR_API_KEY

This has completed my implementation ... rest anyone can pick a theme from the plugin's gallery and can add to application by making sure both providers and theme are from same source.
Also one more important thing here is source for me, which says default and inside this plugin's example app we have openmaptiles, Question is why I am using 'default' source? Because this source is mentioned inside style json file we pick. I have attached few screenshots below.

Screenshot 2023-02-03 at 2 48 40 PM

Screenshot 2023-02-03 at 2 50 51 PM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants