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

Request render optin mode #6065

Merged
merged 21 commits into from
Jan 8, 2018
Merged

Request render optin mode #6065

merged 21 commits into from
Jan 8, 2018

Conversation

ggetz
Copy link
Contributor

@ggetz ggetz commented Dec 19, 2017

Created opt-in requestRenderMode which will only render the scene when needed. Changes it watches for are currently minimal, but can be triggered as needed by calling requestRender().

  • Added events to TaskProcessor on a successful completed web worker task and RequestScheduler on a successful request that the scene can watch for. (I'm thinking these might be better off private?)
  • Camera changes and window resizing will cause a render
  • If the simulation time change exceeds scene.maximumRenderTimeChange, defaults to 0.5 and can be set to undefined to never change based on simulation time.
  • new public function scene.requestRender for forcing render in this mode

debugShowFrames per second might be misleading, the updates are still happening at a higher frame rate than what's being rendered, and the fps value is not updated until after a new render. Should I update the display to reflect this or leave it as is?

@cesium-concierge
Copy link

Signed CLA is on file.

@ggetz, thanks for the pull request! Maintainers, we have a signed CLA from @ggetz, so you can review this at any time.


I am a bot who helps you make Cesium awesome! Contributions to my configuration are welcome.

🌍 🌎 🌏

@ggetz ggetz requested a review from mramato December 19, 2017 19:28
@pjcozzi
Copy link
Contributor

pjcozzi commented Dec 21, 2017

Added events to TaskProcessor on a successful completed web worker task and RequestScheduler on a successful request that the scene can watch for. (I'm thinking these might be better off private?)

In general, if we don't have a concrete use case for making something public, keep it private. Less API surface area is less work and less maintenance.

@pjcozzi
Copy link
Contributor

pjcozzi commented Dec 21, 2017

debugShowFrames per second might be misleading, the updates are still happening at a higher frame rate than what's being rendered, and the fps value is not updated until after a new render. Should I update the display to reflect this or leave it as is?

I don't 100% follow. Do you mean that the fps might be basically 0 when nothing is happening or it, for example, 3 when the scene is only rendered once in awhile due to tile downloads, etc.?

If so, perhaps report:

  • n fps when rendering full speed
  • n/a fps when not rendering
  • n fps (throttled) when rendering on demand

@pjcozzi
Copy link
Contributor

pjcozzi commented Dec 21, 2017

Part of #1865

@pjcozzi pjcozzi mentioned this pull request Dec 21, 2017
@pjcozzi
Copy link
Contributor

pjcozzi commented Dec 21, 2017

@wallw-bits @chris-cooper @willemvdg @kring testing and code reviews are appreciated here, thanks!

Copy link
Contributor

@pjcozzi pjcozzi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a nice start to CPU usage improvements!

Didn't look at the tests.

@@ -0,0 +1,123 @@
<!DOCTYPE html>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any screenshot or graphic that could be used for this Sandcastle example?

CHANGES.md Outdated
@@ -3,6 +3,9 @@ Change Log

### 1.41 - 2017-01-02

* Added optional scene request render mode to reduce CPU usage
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the holiday, I don't think this will make the 1.41 release.

* @see Scene#requestRenderMode
*/
Scene.prototype.requestRender = function() {
if (!this._isRendering) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why check, just set it to true.

@@ -709,6 +719,37 @@ define([
this._cameraVR = undefined;
this._aspectRatioVR = undefined;

/**
* When <code>true</code>, rendering a frame will only occur when needed as determined by changes within the scene.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide more detail here just like you did in the first comment in this pull request. In particular, make sure that the reader understands that they will need to explicitly call requestRender under many cases.

this._isRendering = true;

/**
* If {@link requestRenderMode} is <code>true</code>, this value defines the maximum change in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provide more context and implications - this will impact sun lighting, water animation, and the choppiness of CZML animation, for example.

* @default false
*/
this.requestRenderMode = defaultValue(options.requestRenderMode, false);
this._isRendering = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is _isRendering the best name? Should this be _requestRender, _renderDirty, etc.?

@ggetz
Copy link
Contributor Author

ggetz commented Dec 22, 2017

Fixed test failures and addressed comments from @pjcozzi

Copy link
Contributor

@lilleyse lilleyse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems really promising so far. I ran a bunch of Sandcastle examples and everything looked correct, if the camera doesn't move or things aren't loading the animations ticks every 0.5 seconds. I ran the 3D Tiles picking demo and forced a scene render for ever mouse move event which worked well.

I didn't go crazy with the benchmarking, but there is a clear difference between the optin mode on vs. off. Looks good.
renderer-stats

function startup(Cesium) {
'use strict';
//Sandcastle_Begin
var viewer = new Cesium.Viewer('cesiumContainer', {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be helpful to see a text box that says when the frame is rendered. That way someone using the demo will notice that rendering is enabled when the camera moves or when they click the request render button, and otherwise only renders once every 0.5 seconds.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a field with a timestamp of when the frame was last rendered.

@@ -154,6 +165,7 @@ define([
--numberOfActiveRequestsByServer[request.serverKey];
request.state = RequestState.RECEIVED;
request.deferred.resolve(results);
requestLoadedEvent.raiseEvent();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be consistent with TaskProcessor, should the raiseEvent should go before the resolve?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is one other area where the raiseEvent should be triggered.

        if (isDataUri(request.url) || isBlobUri(request.url)) {
            request.state = RequestState.RECEIVED;
            return request.requestFunction();
        }

This is a quick path for data uris and blob uris.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also add a test for this case.

var time = getTimestamp();

this._fpsFrameCount++;
var fpsElapsedTime = time - this._lastFpsSampleTime;
if (fpsElapsedTime > 1000) {
var fps = this._fpsFrameCount * 1000 / fpsElapsedTime | 0;
var fps = 'N/A';
if (renderedThisFrame) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fps counters in the Cesium Inspector and 3D Tiles Inspector widgets are showing up as NA, probably not taking into account renderedThisFrame.

fps

.

@@ -210,6 +218,8 @@ define([
* @param {Number} [options.terrainExaggeration=1.0] A scalar used to exaggerate the terrain. Note that terrain exaggeration will not modify any other primitive as they are positioned relative to the ellipsoid.
* @param {Boolean} [options.shadows=false] Determines if shadows are cast by the sun.
* @param {MapMode2D} [options.mapMode2D=MapMode2D.INFINITE_SCROLL] Determines if the 2D map is rotatable or can be scrolled infinitely in the horizontal direction.
* @param {Boolean} [options.requestRenderMode=false] If true, rendering a frame will only occur when needed as determined by changes within the scene. Enabling improves performance of the application.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth mentioning the possible caveats in this description as well.

* When <code>true</code>, rendering a frame will only occur when needed as determined by changes within the scene.
* Enabling improves performance of the application, but requires using {@link requestRender}
* to render a new frame explicitly in this mode. This will be necessary in many cases after making updates
* to the scene.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doc needs to be more direct: This will be necessary in many cases after making updates to the scene. should directly say that the user needs to call requestRender if they make changes to the scene.

}
if (defined(this._removeTaskProcessorListenerCallback)) {
this._removeTaskProcessorListenerCallback();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be defined always.

@@ -3563,5 +3653,15 @@ define([
return SceneTransforms.wgs84ToWindowCoordinates(this, position, result);
};

/**
* Requests a new rendered frame when {@link requestRenderMode} is set to <code>true</code>.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same note about the link not working.

*/
Scene.prototype.requestRender = function() {
this._renderRequested = true;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For organizational purposes this and cartesianToCanvasCoordinates should come before isDestroyed, and preferably closer to similar areas of the code.

};
var eventRaised = false;
var removeListenerCallback = TaskProcessor.taskCompletedEvent.addEventListener(function () {
console.log('2');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this.


var shouldRender = !this.requestRenderMode || this._renderRequested || cameraChanged || (this.mode === SceneMode.MORPHING);

var now = JulianDate.clone(time);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a scratch variable instead, and clone into _lastRenderTime inside if (shouldRender).

@ggetz
Copy link
Contributor Author

ggetz commented Jan 3, 2018

Thanks @lilleyse! Updated according to your comments.

Also,

if the camera doesn't move or things aren't loading the animations ticks every 0.5 seconds

The maxiumRenderTimeChange controls this update rate, so yo can do it less frequently (or not at all) if your scene has no animations, or more often if you need smoother animations.

@lilleyse
Copy link
Contributor

lilleyse commented Jan 3, 2018

Cool I'll look at the recent updates.

The maxiumRenderTimeChange controls this update rate, so yo can do it less frequently (or not at all) if your scene has no animations, or more often if you need smoother animations.

Yup, I was just stating that things were operating correctly.

@lilleyse
Copy link
Contributor

lilleyse commented Jan 3, 2018

There are a couple eslint errors.

@ggetz
Copy link
Contributor Author

ggetz commented Jan 3, 2018

@lilleyse Sorry, they're all cleaned up!

@@ -88,15 +88,18 @@ define([
/**
* Update the display. This function should only be called once per frame, because
* each call records a frame in the internal buffer and redraws the display.
*
* @param {Boolean} [renderedThisFrame] If provided, the FPS count will only update and display if true.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[renderedThisFrame=true]

* @param {Boolean} [options.requestRenderMode=false] If true, rendering a frame will only occur when needed as
* determined by changes within the scene. Enabling improves performance of the application, but requires using
* {@link Scene#requestRender} to render a new frame explicitly in this mode. This will be necessary in many cases
* after making changes to the scene in other parts of the API.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we typically keep parameter doc on a single line even it's long.

shouldRender = shouldRender || difference >= this.maximumRenderTimeChange;
}

try {
if (shouldRender) {
this._lastRenderTime = now;
this._lastRenderTime = JulianDate.clone(time, scratchTime);;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually now that I see the updated changes just remove the scratch and call JulianData.clone(time, this._lastRenderTime).

@lilleyse
Copy link
Contributor

lilleyse commented Jan 3, 2018

It looks like a couple review comments from the latest review got hidden, make sure to expand all.

@lilleyse
Copy link
Contributor

lilleyse commented Jan 3, 2018

I wonder if there's more we can do to improve globe/imagery loading. For example, a simple Sandcastle like:

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

will not show the globe. I'm guessing globe's render and update are tightly coupled, and am curious if there's anything we can do about that.

@lilleyse
Copy link
Contributor

lilleyse commented Jan 3, 2018

Missed this on the first go, but Viewer and CesiumWidget should have tests, including a test for resize if possible,

@ggetz
Copy link
Contributor Author

ggetz commented Jan 3, 2018

I wonder if there's more we can do to improve globe/imagery loading. For example, a simple Sandcastle like ... will not show the globe. I'm guessing globe's render and update are tightly coupled, and am curious if there's anything we can do about that.

This is exactly what my next PR addresses. The tile loading for the globe was tied to the render loop, so nothing was getting loaded without rendering a new frame. I separated the update/render logic there, so the globe always loads in completely without time changes triggering the render.

@lilleyse
Copy link
Contributor

lilleyse commented Jan 3, 2018

This is exactly what my next PR addresses. The tile loading for the globe was tied to the render loop, so nothing was getting loaded without rendering a new frame. I separated the update/render logic there, so the globe always loads in completely without time changes triggering the render.

Ok, awesome!

@ggetz
Copy link
Contributor Author

ggetz commented Jan 3, 2018

Addressed your additional feedback @lilleyse, only #6065 (comment) is outstanding.

@CesiumGS CesiumGS deleted a comment from biomassives Jan 3, 2018
@ggetz
Copy link
Contributor Author

ggetz commented Jan 4, 2018

@lilleyse Updated the events (and changed the structure of the render function to reflect that. The update function is empty, but that is where the majority of the refactored code will go for the globe updates talked about above.). I added preUpdate and postUpdate events, and check for rendering after these events are triggered. preRender and postRender are only triggered when the scene renders a new frame. Updated docs and CHANGES.md to reflect the changes.

@lilleyse
Copy link
Contributor

lilleyse commented Jan 4, 2018

Ok, I will look over the new changes a bit later today.

CHANGES.md Outdated
* Added optional scene request render mode to reduce CPU usage
* `scene.requestRenderMode` enables a mode which will only request new render frames on changes to the scene, or when the simulation time change exceeds `scene.maximumRenderTimeChange`.
* Breaking changes
* `scene.preRender` and `scene.postRender` events are called immediately before and after scene rendering only if the scene renders a frame. See `scene.requestRenderMode`. Use `scene.preUpdate` and `scene.postUpdate` for task that require regular updates.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually don't think this needs to be a breaking change. Though the connection between render and update events still needs to be clear. Maybe below write:

Added scene.preUpdate and scene.postUpdate events that are raised before and after the scene updates respectively. The scene is always updated before executing a potential render. Continue to listen to scene.preRender and scene.postRender for when the scene renders a frame.

CHANGES.md Outdated
* Added optional scene request render mode to reduce CPU usage
* `scene.requestRenderMode` enables a mode which will only request new render frames on changes to the scene, or when the simulation time change exceeds `scene.maximumRenderTimeChange`.
* Breaking changes
* `scene.preRender` and `scene.postRender` events are called immediately before and after scene rendering only if the scene renders a frame. See `scene.requestRenderMode`. Use `scene.preUpdate` and `scene.postUpdate` for task that require regular updates.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capitalize Scene for all these.

@@ -98,7 +98,7 @@ define([
this._pauseCount = 0;

var that = this;
this._preRenderRemoveListener = this._scene.preRender.addEventListener(function(scene, time) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are still a lot of other files and Sandcastle demos that reference preRender and postRender, do those need to use the update listeners instead too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usage of postRender didn't need to be changed in the examples, but I modified the preRender events where necessary.

* @see scene#postUpdate
* @see scene#preRender
* @see scene#postRender
* @see scene#render
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capitalize Scene for these and below.

* @see scene#postUpdate
* @see scene#preRender
* @see scene#postRender
* @see scene#render
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the link to scene#render, it is a private function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same below.

@@ -2787,7 +2840,7 @@ define([
}
}

function callAfterRenderFunctions(frameState) {
function callAfterRenderCycleFunctions(frameState) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the name change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to make it clear that it was called every call to scene.render, whether a new frame is rendered or not, but it's not necessary in retrospect.

try {
functionToExecute(scene, time);
} catch (error) {
scene._renderError.raiseEvent(scene, error);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both update and render use renderError. Do we need an updateError?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only place it's used in Cesium is that the viewer listens to it, and stops the render loop and displays it, which we'd still want. In case any app is also listening for this event, errors occurring during update would no longer be caught, which might be an issue.

Both are occurring during the scene.render function, so I don't think the naming is an issue, but if we wanted we could rename and deprecate scene.renderError

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably okay to leave as is for now. Maybe we can reevaluate for the upcoming PR.

s.destroyForSpecs();
});

it('always raises preUpdate event after updating', function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a duplicate of the other test. Should this be postUpdate?

@lilleyse
Copy link
Contributor

lilleyse commented Jan 5, 2018

The recent changes look good.

Would anyone else like to review? The PR is being merged into cpu-usage so there will be an opportunity to review everything once all the incremental PRs are done.

@lilleyse
Copy link
Contributor

lilleyse commented Jan 8, 2018

Merging into cpu-usage.

@lilleyse lilleyse merged commit f69edc1 into cpu-usage Jan 8, 2018
@lilleyse lilleyse deleted the requestRender-optin-mode branch January 8, 2018 15:23
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

Successfully merging this pull request may close these issues.

5 participants