We talked offline about this yesterday so I figured I'd write up an issue for it. The default render loop in CesiumWidget and Viewer currently render at 60fps no matter what. Setting the targetFrameRate property can lower it, but is only useful in limited cases.
What we really want to do is only render if we actually need to. This will reduce CPU load and also help with battery life (for devices where that's an issue). Here's the "rules" @kring previously came up with.
We don't render unless:
- The simulation time has changed,
- The user interacts via the mouse, etc.,
- A property of something in the scene has changed, or
- New data is loaded (such as terrain tiles)
Discussion: https://groups.google.com/forum/#!topic/cesium-dev/cGXhVoUacMg
As mentioned in the above issue, using the visibility API might also help out here in some circumstances: http://www.smashingmagazine.com/2015/01/20/creating-sites-with-the-page-visibility-api/
I don't think the visibility API is the right answer here, as requestAnimationFrame already takes page visibility into account.
Question, what is the best approach to implement this? Would there be a flag on Scene or RenderState or Context or someplace that gets set "dirty" indicating that one or more of @kring's conditions have been met?
Agreed re: the visibility API. It doesn't help.
I think a dirty flag is a reasonable way to go. Ideally we would stop the requestAnimationFrame completely when nothing is changing. We currently do this in National Map. It works ok, but it's pretty hacky doing it outside of Cesium. For example, I've hooked loadWithXhr and TaskProcessor to fire the render loop back up when data is loaded or a Web Worker sends a response.
Ken Russell touched on this topic in a recent WebGL-dev post. The first part of his message covers something we already do (aggregate events before rendering), but the next part talks about a flag that indicates whether requestAnimationFrame has yet been called for the very next frame.
It's a little trickier in Cesium's case, since the requestAnimationFrame loop is handled all the way up at the widget level, and is optional even there, it can be handled at the app level (and some apps use custom render loops).
Ken writes:
in your event handler:
- Do whatever computation needed to update the position of the camera
- Check the flag to see whether a render is pending
- If the flag is false:
-- Call requestAnimationFrame to render the scene
-- set the flag to trueIn your requestAnimationFrame callback:
- Draw the scene with the camera's current position
- set the flag to false
So, adapting this to Cesium's needs, we might do something like this:
Scene called nextFrameRequested, initially false.Event to Scene called onRequestNextFrame.requestNextFrame to Scene. It checks the flag and if false, sets to true and fires the event.Scene.render the flag is reset to false without any event.CesiumWidget already has a useDefaultRenderLoop flag, which will remain but its implementation changes: When useDefaultRenderLoop is true, CesiumWidget will listen for Scene.onRequestNextFrame events to fire, and always respond to those events by calling Cesium.requestAnimationFrame to queue up the next call to Scene.render and friends.CesiumWidget.useDefaultRenderLoop is false, then it is the app's responsibility to listen for Scene.onRequestNextFrame and call Cesium.requestAnimationFrame in response, requesting its own app-specific render function to fire.Scene.requestNextFrame, for example if the currentTime changes, if Tweens change things, if inertia moves the camera, if tiles load, if responses are received from web workers, etc.Interestingly, this does not break backwards compatibility. Apps that relied on the default render loop will get the upgraded behavior automatically. Apps that had their own custom render loop will continue to have the old behavior: They will ignore the previously unknown event from Scene and simply call requestAnimationFrame immediately at the end of their own render code, keeping the render loop going at 100% but not causing any problems.
If an author wishes to upgrade such an app to the new system while keeping the app-specific custom render loop, the author removes the last line of their custom render loop where they call requestAnimationFrame, and pastes the removed line into a new event listener on Scene.onRequestNextFrame to cause their own custom loop to re-fire. After upgrading to the new system, they may need to add their own calls to Scene.requestNextFrame if they've manually changed primitives in the scene.
Old app code: (still works)
function myRenderLoop() {
// custom code here
cesiumWidget.render(); // this includes scene.initializeFrame().
Cesium.requestAnimationFrame(myRenderLoop);
}
// Prime the pump.
Cesium.requestAnimationFrame(myRenderLoop);
New app code: (uses new behavior)
function myRenderLoop() {
// custom code here
cesiumWidget.render(); // this includes scene.initializeFrame().
// Don't ask for next frame here anymore.
}
// Listen to events indicating that a new frame will be needed:
scene.onRequestNextFrame.addEventListener(function() {
Cesium.requestAnimationFrame(myRenderLoop);
});
// Prime the pump.
scene.requestNextFrame();
Or we could encapsulate the event callback in Scene itself. There should only ever be one callback running at a time, so Scene could just be given a reference to it.
function myRenderLoop() {
// custom code here
cesiumWidget.render(); // this includes scene.initializeFrame().
// Don't ask for next frame here if scene.frameRequestHandler is defined.
}
scene.frameRequestHandler = myRenderLoop;
scene.requestNextFrame();
This can't go down into Context because that's private. Can everything that needs to call Scene.requestNextFrame get access to the scene object?
@kring this would badly mess up your target framerate watchdog stuff I think.
Hey @emackey, you've pretty much described how it works in National Map. The main difference is we use the actual useDefaultRenderLoop flag instead of introducing a new one plus an event, since we're not concerned with maintaining the semantics of that flag.
You're right that it messes up the framerate watchdog stuff. Our (fairly lame, but effective enough) approach is to ignore the watchdog if the animation shuts down before the watchdog finishes its sampling window. The assumption is that a low frame rate would make it take much longer than the sampling window to get to the point where we can stop rendering.
Can everything that needs to call Scene.requestNextFrame get access to the scene object?
I'm not sure. But if not, Scene can hold an animation trigger object that is passed down to anyone that might need it.
Another forum thread: https://groups.google.com/forum/#!topic/cesium-dev/X32mc77CTic
Just wondering if there is any ETA on this issue?
@chris-cooper no update other than #3476 will make this a bit easier. If you have the time, contributions here are appreciated.
+1
@chris-cooper We have been extending ol3-cesium's AutoRenderLoop. It still isn't perfect, but it is much better than before.
Brought up on the forum: https://groups.google.com/forum/#!topic/cesium-dev/nS_GzoZ4b5g
I've posted our initial plans to tackle this issue on the forum here: https://groups.google.com/forum/#!topic/cesium-dev/E2XXB44zWew
Please give us your feedback, including any of your thoughts or use cases there!
For initial work here, please provide feedback on #6065.
Feedback also welcome for additional work in #6107.
Closing this since requestRenderMode was a success and as far as I know we don't have any additional short term plans.
Congratulations on closing the issue! I found these Cesium forum links in the comments above:
https://groups.google.com/forum/#!topic/cesium-dev/cGXhVoUacMg
https://groups.google.com/forum/#!topic/cesium-dev/X32mc77CTic
https://groups.google.com/forum/#!topic/cesium-dev/nS_GzoZ4b5g
https://groups.google.com/forum/#!topic/cesium-dev/E2XXB44zWew
If this issue affects any of these threads, please post a comment like the following:
__I am a bot who helps you make Cesium awesome! Contributions to my configuration are welcome.__
:earth_africa: :earth_americas: :earth_asia:
Most helpful comment
I've posted our initial plans to tackle this issue on the forum here: https://groups.google.com/forum/#!topic/cesium-dev/E2XXB44zWew
Please give us your feedback, including any of your thoughts or use cases there!