Skip to main content

Improving Performance with Explicit Rendering

By default, Cesium renders new frames as a game engine does: regularly, at the target frame rate. While this works well for Cesium apps with dynamic data or that constantly stream data for new views, many Cesium apps can benefit from rendering less frequently. Rendering a new frame uses CPU resources and is often not necessary if your app is idle. Improved performance with explicit rendering means you can run Cesium apps without worries of kicking your laptop fan into high gear or draining battery life on mobile devices.

Starting with Cesium 1.42, a new opt-in explicit rendering mode allows the developer to precisely control the rendering in Cesium based on the needs of the application. When enabled, a new frame will be rendered only in the following scenarios:

  • The camera changes, either with user input or with the Cesium API.
  • The simulation time advances beyond a specified threshold.
  • Loading in terrain, imagery, 3D Tiles, or data sources, including each individual tile load. At a lower level, this is triggered when a web request is resolved via a URI or blob, or an asynchronous process returns from a web worker.
  • Globe imagery layers are added, removed, or changed.
  • Globe terrain providers change.
  • The scene mode morphs.
  • A frame is explicitly rendered with the Cesium API.

This covers a lot of the bread and butter functionality that Cesium is commonly used for, such as loading in terrain, imagery, and data like 3D Tiles. For more fine-tuned changes to the scene, instead of watching for every update that might require a render, this is left up to the application. If the app makes changes to the scene or its contents through the Cesium API in a way that is not covered by one of the above cases, for example, using the Entity or Primitive API, then explicitly request a new render frame.

When explicit rendering mode is enabled and changes are made to the scene, rendering occurs as normal at the target frame rate. However, when Cesium is idle, CPU usage is greatly reduced. For instance, using Chrome developer tools, CPU usage in an idle Cesium scene averaged 25.1%, but after enabling the performance improvement, it now averages 3.0%. This was measured on a laptop with an Intel i7 processor, running in Google Chrome.

CPU usage timeline

Timeline showing FPS and CPU usage over time when rendering normally and when in request render mode. During the initial load, both perform similarly. After the initial load, in request rendering mode, new frames are not rendered. This brings CPU usage down to almost 0%. When the scene initially loads the FPS is 60 as expected, but when idle drops to 0 FPS because no new frames are required.

To configure a Cesium app to use explicit rendering, request render mode should be enabled, the simulation time taken into consideration, and then any cases not automatically handled must explicitly render a frame.

Enabling Request Render Mode

Enable requestRenderMode to reduce the overall amount of time Cesium renders a new frame and to decrease Cesium’s overall CPU usage in your application.

var viewer = new Cesium.Viewer('cesiumContainer', {
    requestRenderMode : true
});

You can also make these adjustments to the scene after the viewer is created.

var viewer = new Cesium.Viewer('cesiumContainer');
viewer.scene.requestRenderMode = true;

Handling Simulation Time Changes

By default, when requestRenderMode is enabled, when the simulation time changes more than 0.0 seconds, a new frame will be requested. This value can be adjusted by setting maximumRenderTimeChange. Take this into account if your scene has elements that change with time, such as animations, lighting changes, water masks, or video. If no elements of your scene change with simulation time, consider setting the maximumRenderTimeChange to a high value, such as Infinity.

// Create a viewer that will not render frames based on changes in 
// simulation time.
var viewer = new Cesium.Viewer('cesiumContainer', {
    requestRenderMode : true,
    maximumRenderTimeChange : Infinity
});
viewer.scene.debugShowFramesPerSecond = true;

Explicitly Render a Frame

In your app code, after a change occurs that is not covered in the above list, explicitly render a new frame to show the change immediately. If not, the change will not be visible until a new frame is otherwise rendered.

Render a frame explicitly by calling Scene.requestRender.

// Hides the stars
scene.skyBox.show = false;
// Explicitly render a new frame
scene.requestRender();

For example, an application may have a UI that sets properties of the scene or an object in the scene. In this case, explicitly render when the state of the UI changes.

Update/Render Cycle Events

As part of these changes, we’ve split up some of the update and render logic. Cesium always updates at the same rate, depending on your app’s target frame rate (60 FPS is the default, see Viewer.targetFrameRate). When using requestRenderMode, render functions will only be called when a new frame is rendered. We’ve restructured the order of the render cycle events, as detailed below. Generally, calling Scene.requestRender (or making other changes that automatically prompt a render, like camera position) in one of the update events will queue a render in time for the next frame.

scene.postUpdate.addEventListener(function() {
    // This code will run at 60 FPS
    if (changeToPromptRender) {
        scene.requestRender();
    }
}); 

scene.preRender.addEventListener(function() {
    // This code will run when a new frame is rendered
    // including when changeToPromptRender is true
}); 

preUpdate

This event is raised at the beginning of the update/render cycle, before the scene contents are updated or rendered. This event is triggered every update/render cycle at the target frame rate.

postUpdate

This event is raised after the scene updates, but before a new frame potentially renders. This event is triggered every update/render cycle at the target frame rate.

preRender

This event is raised before a new frame is rendered, but after updates have been made. When requestRenderMode is enabled, this event is triggered only when a new frame is rendered.

postRender

This event is raised at the end of the update/render cycle after a new frame is rendered, but after updates have been made. When requestRenderMode is enabled, this event is triggered only when a new frame is rendered.

Use Cases and Examples

The Scene Rendering Performance Sandcastle example explores some examples of common use cases in the drop down lists and covers some examples of when you don’t need to explicitly render, as well as examples of where to explicitly render if you do.

Let’s improve performance in an example application that uses mouse-over picking to adjust the state of an entity. The code below demonstrates creating the scene, adding the entity, then creating the picking function. While we could have called requestRender each time the MOUSE_MOVE event is triggered, it’s not necessary unless a property of the entity changes.

// Create a viewer that won't render a new frame unless
// updates to the scene require it.
var viewer = new Cesium.Viewer('cesiumContainer', {
    requestRenderMode : true,
    maximumRenderTimeChange : Infinity
});

// Add an entity to the scene.
var entity = viewer.entities.add({
    position : Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
    box : {
        dimensions : new Cesium.Cartesian3(1000000.0, 1000000.0, 30000.0),
        material : Cesium.Color.CORNFLOWERBLUE
    }
});

// If the mouse is over the billboard, change its scale and color,
// then request a new render frame.
var lastPicked;
handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
handler.setInputAction(function(movement) {
    var pickedObject = scene.pick(movement.endPosition);
    if (Cesium.defined(pickedObject) && (pickedObject.id === entity)) {
        if (Cesium.defined(lastPicked)) {
            return;
        }
        entity.box.material = Cesium.Color.YELLOW;
        scene.requestRender();
        lastPicked = pickedObject;
    } else if (Cesium.defined(lastPicked)) {
        entity.box.material = Cesium.Color.CORNFLOWERBLUE;
        scene.requestRender();
        lastPicked = undefined;
    }
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);