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

Raster tile loading blocking UI, FPS < 2.0 #6643

Closed
hyperknot opened this issue May 8, 2018 · 17 comments
Closed

Raster tile loading blocking UI, FPS < 2.0 #6643

hyperknot opened this issue May 8, 2018 · 17 comments
Assignees
Labels
performance ⚡ Speed, stability, CPU usage, memory usage, or power usage

Comments

@hyperknot
Copy link

mapbox-gl-js version: 0.45.0
browser: Chrome 66 / macOS

  1. On an empty style, add a raster tile layer, especially with smaller tile sizes, like "fake-retina" 256 pixels as 128.
  2. Zoom around the map or use flyTo animations
  3. Things can get really choppy.

Link to Demonstration

(I tested using Chrome FPS meter)
example: https://zl761nnr3m.codesandbox.io/
code: https://codesandbox.io/s/zl761nnr3m

Expected Behavior

UI interactions should be smooth, independent of tile loadings in the background.

Actual Behavior

The UX is really bad with raster layers now. Chrome FPS meter regularly gets under 2.0 fps on my Macbook Pro.

I've almost finished porting a big codebase over to Mapbox GL viewer, but this one is puzzling me. Both Leaflet and OpenLayers can display raster maps at constant 58+ FPS. How is it possible that Mapbox GL can render the most complicated vector tile regions smoothly, but is breaking with simple raster tiles?

@mourner mourner added the performance ⚡ Speed, stability, CPU usage, memory usage, or power usage label Jul 19, 2018
@mourner mourner self-assigned this Jul 19, 2018
@mourner
Copy link
Member

mourner commented Jul 19, 2018

This seems to be some kind of regression with the source cache logic — most of the time is spend figuring out which tiles to show, not actual rendering or loading. Investigating now.

@mourner
Copy link
Member

mourner commented Jul 19, 2018

Looks like it's not a regression, just an algorithm that dramatically slows down quadratically when the tile size goes down — it was only optimized for vector tiles, where you typically only see 6-7 tiles on the screen. Thinking about a faster one now...

@mourner
Copy link
Member

mourner commented Jul 19, 2018

OK, I have a good lead on this. Will submit a PR tomorrow.

@mourner
Copy link
Member

mourner commented Jul 20, 2018

Turns out that the jank on raster layers is currently dominated by XHR requests, which we use to load images since #1472 because of an issue with browsers not fully respecting Cache-Control: no-cache (#1470). You can see how much snappier the raster test case becomes in 9b372d9, which uses the old image loading approach (git co bench-raster-tile-load, yarn run start-debug, then load http://localhost:9966/debug/). If you switch getImage back to XHR-based requests in ajax.js, it becomes unusably janky, as reported.

Since the original fix with XHR image loads was introduced specifically to fix sprite updates, and most image loads don't have short cache spans, I wonder if we can use some kind of a mixed approach. For example, a raster tile source would do its first request with XHR-based getImage, see if cacheControl is small (no-cache, small enough max-age or whatever), and then either continue using XHR or switch to simple new Image for snappier performance. @jfirebaugh what do you think?

@hyperknot
Copy link
Author

hyperknot commented Jul 20, 2018

Thanks, it's great to see this! Is there any use case for super frequently updating raster tiles? Like satellite images are the slowest changing data in a map platform, aren't they?

If it is seriously needed for any use case, I'd recommend adding a tileXHR or tileCache or something similar to rasterSource and letting the developers choose it manually.

@mourner
Copy link
Member

mourner commented Jul 20, 2018

@hyperknot I feel like letting the developers choose would expose too much internal detail for a thing that should just work well automatically. So let's try to exhaust automatic heuristics first and resort to an explicit option if it doesn't work.

@jfirebaugh
Copy link
Contributor

I was going to say we could probably just have tile requests always use Image directly, and use XHR only for sprites, but I wouldn't be surprised if there are use cases (e.g. weather apps) that depend on frequently updating raster tiles.

For browsers that support it, we could try out an XHR with req.responseType = 'blob' combined with createImageBitmap.

@hyperknot
Copy link
Author

@jfirebaugh do you think it's worth the added complexity? As I see neither does Safari nor Edge supports it, while a simple Image.src is probably super optimised in every browser.

@mourner
Copy link
Member

mourner commented Jul 20, 2018

For browsers that support it, we could try out an XHR with req.responseType = 'blob' combined with createImageBitmap.

@jfirebaugh this likely won't help — looks like it's not the blob construction that's the bottleneck, but the XHR itself. Here's a screenshot from a stuttered frame (notice that all time is spent in send + open):

image

I wouldn't be surprised if there are use cases (e.g. weather apps) that depend on frequently updating raster tiles.

I think we could come up with a heuristic that supports this use case. E.g. if Cache-Control on the first XHR indicates that you can cache for >1hr, switch to simple image loads, otherwise continue with XHR. Thoughts?

P.S. #6995 and #6998 are PRs that remove two other bottlenecks that I identified when researching this issue.

mourner added a commit that referenced this issue Jul 23, 2018
mourner added a commit that referenced this issue Jul 23, 2018
mourner added a commit that referenced this issue Jul 23, 2018
@mourner
Copy link
Member

mourner commented Jul 23, 2018

Added a proposal for addressing this issue in #7008.

@hyperknot try applying all three PRs (#7008, #6995, #6998) and let me know how the performance looks.

@hyperknot
Copy link
Author

hyperknot commented Jul 23, 2018

I've merged all PRs (#6998 was already merged), and the image loading logic works perfectly, however the jankiness is still present (and can be up to 0.5 second).

Here is a codepen with an updated build:
https://codepen.io/hyperknot/full/vaxvJN/

@hyperknot
Copy link
Author

OK, it seems to be depending on if the Developer Tools is open or not:

  • if Chrome developer tools is not open, the yank is I'd guess 1/10th of what it was previously, there really is a huge improvement here
  • if Chrome developer tools is open (even with disable caching turned off, even with "Stop recording network log") there is a jank, just like it was originally.

Unfortunately it's not possible to use the FPS meter without dev tools open, so it's a tricky case. But based on perception alone, the normal use case is about 10x better.

@mourner
Copy link
Member

mourner commented Jul 23, 2018

@hyperknot awesome! It's also worth noting that 128px tiles are an extreme case due to so many requests — performance after the patches is pretty smooth with bigger tile sizes.

@hyperknot
Copy link
Author

hyperknot commented Jul 23, 2018

Yes, solving this case also helped the normal satellite tiles work super smoothly, thanks a lot for tackling it!

(I wouldn't consider this an extreme case, as there are only 4x tiles visible compared to Mapbox satellite (whose tile size is actually 256px)).

@jfirebaugh
Copy link
Contributor

mourner added a commit that referenced this issue Jul 23, 2018
mourner added a commit that referenced this issue Jul 23, 2018
@mourner
Copy link
Member

mourner commented Jul 27, 2018

From #7008:

I've ported the example to OpenLayers, with a similar animation (the easing is different).
In my opinion OL is super smooth even with developer tools open. FPS meter shows constant 60 FPS with one or two occasional frames at 30 FPS, or maybe 20 FPS.

I've looked into how OpenLayers handles raster tile loading, and I believe the reason why their equivalent is fast is that they use a TileQueue data structure that makes sure there are never more than 16 tiles loading in parallel or requested at once in one frame. This makes it avoid requesting short-lived tiles that e.g. get requested on zoom in near the borders of the map and then immediately go off the view when zooming further during an animation. This might be a possible solution to this feature request #5482, and we should definitely explore a similar approach.

@mourner
Copy link
Member

mourner commented Mar 1, 2019

This seems in a pretty good shape after #7497 and earlier improvements, so closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
performance ⚡ Speed, stability, CPU usage, memory usage, or power usage
Projects
None yet
Development

No branches or pull requests

3 participants