-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Conversation
@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. 🌍 🌎 🌏 |
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. |
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:
|
Part of #1865 |
@wallw-bits @chris-cooper @willemvdg @kring testing and code reviews are appreciated here, thanks! |
There was a problem hiding this 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> |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
Source/Scene/Scene.js
Outdated
* @see Scene#requestRenderMode | ||
*/ | ||
Scene.prototype.requestRender = function() { | ||
if (!this._isRendering) { |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
Source/Scene/Scene.js
Outdated
this._isRendering = true; | ||
|
||
/** | ||
* If {@link requestRenderMode} is <code>true</code>, this value defines the maximum change in |
There was a problem hiding this comment.
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.
Source/Scene/Scene.js
Outdated
* @default false | ||
*/ | ||
this.requestRenderMode = defaultValue(options.requestRenderMode, false); | ||
this._isRendering = true; |
There was a problem hiding this comment.
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.?
Fixed test failures and addressed comments from @pjcozzi |
There was a problem hiding this 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.
function startup(Cesium) { | ||
'use strict'; | ||
//Sandcastle_Begin | ||
var viewer = new Cesium.Viewer('cesiumContainer', { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Source/Core/RequestScheduler.js
Outdated
@@ -154,6 +165,7 @@ define([ | |||
--numberOfActiveRequestsByServer[request.serverKey]; | |||
request.state = RequestState.RECEIVED; | |||
request.deferred.resolve(results); | |||
requestLoadedEvent.raiseEvent(); |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Source/Scene/PerformanceDisplay.js
Outdated
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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Source/Scene/Scene.js
Outdated
@@ -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. |
There was a problem hiding this comment.
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.
Source/Scene/Scene.js
Outdated
* 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. |
There was a problem hiding this comment.
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.
Source/Scene/Scene.js
Outdated
} | ||
if (defined(this._removeTaskProcessorListenerCallback)) { | ||
this._removeTaskProcessorListenerCallback(); | ||
} |
There was a problem hiding this comment.
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.
Source/Scene/Scene.js
Outdated
@@ -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>. |
There was a problem hiding this comment.
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.
Source/Scene/Scene.js
Outdated
*/ | ||
Scene.prototype.requestRender = function() { | ||
this._renderRequested = true; | ||
}; |
There was a problem hiding this comment.
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.
Specs/Core/TaskProcessorSpec.js
Outdated
}; | ||
var eventRaised = false; | ||
var removeListenerCallback = TaskProcessor.taskCompletedEvent.addEventListener(function () { | ||
console.log('2'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this.
Source/Scene/Scene.js
Outdated
|
||
var shouldRender = !this.requestRenderMode || this._renderRequested || cameraChanged || (this.mode === SceneMode.MORPHING); | ||
|
||
var now = JulianDate.clone(time); |
There was a problem hiding this comment.
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)
.
Thanks @lilleyse! Updated according to your comments. Also,
The |
Cool I'll look at the recent updates.
Yup, I was just stating that things were operating correctly. |
There are a couple eslint errors. |
@lilleyse Sorry, they're all cleaned up! |
Source/Scene/PerformanceDisplay.js
Outdated
@@ -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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[renderedThisFrame=true]
Source/Scene/Scene.js
Outdated
* @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. |
There was a problem hiding this comment.
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.
Source/Scene/Scene.js
Outdated
shouldRender = shouldRender || difference >= this.maximumRenderTimeChange; | ||
} | ||
|
||
try { | ||
if (shouldRender) { | ||
this._lastRenderTime = now; | ||
this._lastRenderTime = JulianDate.clone(time, scratchTime);; |
There was a problem hiding this comment.
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)
.
It looks like a couple review comments from the latest review got hidden, make sure to expand all. |
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 |
Missed this on the first go, but |
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! |
Addressed your additional feedback @lilleyse, only #6065 (comment) is outstanding. |
@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 |
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. |
There was a problem hiding this comment.
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
andscene.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 toscene.preRender
andscene.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. |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
Source/Scene/Scene.js
Outdated
* @see scene#postUpdate | ||
* @see scene#preRender | ||
* @see scene#postRender | ||
* @see scene#render |
There was a problem hiding this comment.
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.
Source/Scene/Scene.js
Outdated
* @see scene#postUpdate | ||
* @see scene#preRender | ||
* @see scene#postRender | ||
* @see scene#render |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same below.
Source/Scene/Scene.js
Outdated
@@ -2787,7 +2840,7 @@ define([ | |||
} | |||
} | |||
|
|||
function callAfterRenderFunctions(frameState) { | |||
function callAfterRenderCycleFunctions(frameState) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the name change?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
Specs/Scene/SceneSpec.js
Outdated
s.destroyForSpecs(); | ||
}); | ||
|
||
it('always raises preUpdate event after updating', function() { |
There was a problem hiding this comment.
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?
The recent changes look good. Would anyone else like to review? The PR is being merged into |
Merging into |
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 callingrequestRender()
.TaskProcessor
on a successful completed web worker task andRequestScheduler
on a successful request that the scene can watch for. (I'm thinking these might be better off private?)scene.maximumRenderTimeChange
, defaults to0.5
and can be set toundefined
to never change based on simulation time.scene.requestRender
for forcing render in this modedebugShowFrames
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?