maplibre / maplibre-gl-js

MapLibre GL JS - Interactive vector tile maps in the browser
https://maplibre.org/maplibre-gl-js/docs/
Other
6.67k stars 716 forks source link

Stop rendering when hands off computer, a.k.a frame invalidation #4213

Open unphased opened 5 months ago

unphased commented 5 months ago

In interactive 3D applications I tend to build a framerate limiting feature into the control loop earlier rather than later. Typically whether it's a CAD application or data visualization, even when animations are involved (though that changes things), reducing/decimating/stopping rendering while user input is absent is an important usability aspect of software. It's a big deal for mobile devices. Consider even when I do not have 3D terrain enabled, if I open a GL based map in Chrome on my MacBook, it will render the map at 120hz nonstop drawing something like 20 watts above baseline. If I'm looking at terrain, this jumps to 40 watts. When using Safari on macOS, its behavior is much more tuned for saving power (e.g. 60fps cap always) so it's not possible to suck down battery as fast with Safari. But it cannot do miracles and the scene re-renders at a constant 60hz there as well.

On all mobile devices, the impact of power consumption can be felt in terms of temperature, fan noise if applicable, and shorter device battery life. Even for desktops there exists an environmental impact.

Typically if the app's experience is suitable for this, i would stop rendering entirely in the absence of user input. This makes the page behave very similarly to a regular webpage at all times when the user is not interacting with it. Frame invalidation logic may be more nuanced than merely firing on any input event, but doing it that way is a good start.

The changes to this are simple in terms of code, but generally invasive in terms of impact to core renderer and camera control internals. Is there interest in enhancing the library with configuration and maybe some callbacks to help address this concern? If not, I would just go ahead and perform my usual filthy hacks in the apps I'm building.

I would be interested in potentially contributing a generalized solution to the library for this.

HarelM commented 5 months ago

I think that this library has controls in place so that when there're no changes in the map/style/visualization there's no rerendering. I'm not an expert in that area though. Can you elaborate on what exactly you had in mind?

unphased commented 5 months ago

Perhaps something I'm tinkering with in my code is preventing

controls in place so that when there're no changes in the map/style/visualization there's no rerendering

from working.

Example: reproducible in the three.js maplibre example.

Will keep poking around. I'll report back if i find any examples in which the rendering stops when camera stops moving. (Like it should)

unphased commented 5 months ago

@HarelM Where is this supposed to work normally? I tested the simple https://maplibre.org/maplibre-gl-js/docs/examples/add-a-marker/ marker example and it definitely renders every single frame via rAF, even while idle (i can check if map is idle via map.on('idle')):

image

I think hanging something off of Map.on('idle') can achieve it but I need a catch-all event to use to invalidate framerate suppression.

The reason we don't see batteries getting chewed up is because the base map functionality is well optimized so it does leave the GPU and CPU mostly idle during the render loop. However this is still wasting a lot of power because it is always re-rendering.

HarelM commented 5 months ago

I though I saw some code that only gets called if there's a change in the style, but apparently I was wrong. While I think looking at a stale map while keeping the screen/browser on is kinda an edge case, I don't mind having this reviewed and improved if you have a use case where you need it. Please make sure to have a design in place before starting implementation so that we'll be aligned on the direction you are going. Thanks!

unphased commented 5 months ago

I will report back as I dig deeper into the library to see what would need to change. The importance of this work increases as you do more custom 3D rendering off the maplibre webgl context, as e.g. my three.js renderer that is hooked into the WebGL context will have to follow and render for the same frames maplibre renders, otherwise things will disappear.