CesiumGS / cesium

An open-source JavaScript library for world-class 3D globes and maps :earth_americas:
https://cesium.com/cesiumjs/
Apache License 2.0
12.81k stars 3.46k forks source link

Entity primitive continuously re-drawn when position is SampledProperty #6631

Open thw0rted opened 6 years ago

thw0rted commented 6 years ago

Example in Sandcastle, based on the Interpolation demo.

Note that the debug frame counter shows continuous frames being drawn even though we're in requestRenderMode = true and the animation is paused. I found this because my app is rendering with a poor framerate in some cases, and I tracked it to a single rogue entity that has a SampledPositionProperty and an ellipsoid graphic. I dug in the debugger a little and figured out that for some reason, the associated primitive collection is being updated just about every frame, which didn't make sense to me as I wasn't modifying anything.

This doesn't happen in 3D scene mode, and it also doesn't happen with other 2D primitives (billboard, label, etc). I haven't tried with other solids but I'd be surprised if the issue was limited to ellipsoid.

hpinkos commented 6 years ago

Hmm, that's very weird. Thanks for reporting this @thw0rted!

@ggetz any ideas here?

ggetz commented 6 years ago

Looks like setReady is being called by the primitive every frame in 2D mode, but not 3D mode. (This add a function that triggers after the frame is rendered, which signals another frame to be rendered in requestRenderMode).

mramato commented 6 years ago

I assume this is a trivial fix, but @bagnell can you confirm that there's no reason we should be calling setReady every frame?

OmarShehata commented 5 years ago

This might be related, trying to get the Clipping Planes Sandcastle working with requestRenderMode doesn't really work because the entity has a PlaneGraphics with a Plane which is a CallbackProperty, which forces it to re-render every frame.

Is this expected that CallbackProperties essentially break requestRenderMode (even if they're not returning different values?) If so, I think this could be worked around by not using a CallbackProperty and manually updating the property's value. @ggetz

Original forum post.

hpinkos commented 5 years ago

@OmarShehata this is how entities are designed to work when they're dynamic. The Entity API is really good at drawing things that are static, and things that are constantly changing, but it unfortunately doesn't handle updating things that change intermittently in the best way because it will always update every frame if any of the properties are not constant.

thw0rted commented 5 years ago

I haven't looked at this since it was first filed. The last response before Omar's was Matt asking if the diagnosis was correct. Has the issue been fixed in the meantime? If not, can it get some attention?

OmarShehata commented 5 years ago

I understand that this is by design and it works in most cases. I do feel like for this situation it would be nice if as an application developer I could do something like entity.markDirty() or someway to force to update only when needed, since in requestRenderMode you're also responsible for calling the scene.requestRender when needed.

Right now to get the Sandcastle to work I had to clone the Entity's position and re-set it to force it to update, but even then it doesn't trigger an update in the same frame.

The last response before Omar's was Matt asking if the diagnosis was correct. Has the issue been fixed in the meantime? If not, can it get some attention?

Just to bump that question, @bagnell do you know if calling setReady on the Primitive every frame is intentional? https://github.com/AnalyticalGraphicsInc/cesium/issues/6631#issuecomment-392858303

jpespartero commented 5 years ago

I have seen in the @thw0rted that this behavior doesn't happen in 3D scene, but in the example provided in #7943 the requestRenderMode is not working in the 3D scene when a CallbackProperty is used to update the entity vertices

Example on v1.59

Why the Callback function is not only called when the scene.requestRender is called?

cmcleese commented 4 years ago

I see this has been dormant for awhile.

Perhaps it would possible to switch to the isConstant flag dynamically based on the currentTime. Basically if the clock has changed time you can trigger the callback event by comparison of the timestamps. Maybe this sounds hacky. Is there no way to prevent scene rendering when time is paused and using a callbackProperty or sampledProperty on an entity?

Aviian314 commented 3 years ago

My team recently encountered this issue when using request render mode with entities whose position is a sampledPositionProperty. The goal of request render mode for us is to reduce the computer resources required when the clock is paused. However, this issue seems to cause the screen to continually update.

Has any progress been made towards this?

dhyams commented 3 years ago

We have run into this too; is anyone looking at this issue and/or is there a workaround available?

adi64 commented 2 years ago

We're also having this problem. I'll dump what my understanding is so far. I am not 100% sure about this, so if anything is wrong, please correct me :)

If you define a dynamic property of an Entity (e.g. CallbackProperty, but probably also SampledPositionProperty), it is marked as dynamic. A dynamic entity is updated every frame, because Cesium has no way of knowing if property values for the new frame will change. That's all fair. If properties related to the geometry are affected, the DynamicGeometryUpdater will be invoked. In DynamicGeometryUpdater.prototype.update, the underlying Primitive is destroyed: https://github.com/CesiumGS/cesium/blob/e4336e10a81a7122e024bf6062c5d186d6901ae4/Source/DataSources/DynamicGeometryUpdater.js#L81 and then re-created.

If I've tracked the code correctly, this will cause the Primitive to re-initialize, which is probably the point where it calls setReady. setReady then causes a new frame to be requested, which again leads to an update of the dynamic Entity, which will destroy and re-create its Primitive, which will call setReady again.

At this point, I could see a potential fix in two directions:

rropp5 commented 7 months ago

Hey @ggetz I have a hunch that this is resolved if we change setReady() in Primitive.js to this:

function setReady(primitive, frameState, state, error) {
  primitive._error = error;
  primitive._state = state;
  primitive._ready =
    primitive._state === PrimitiveState.COMPLETE ||
    primitive._state === PrimitiveState.FAILED;
}

instead of this:

function setReady(primitive, frameState, state, error) {
  primitive._error = error;
  primitive._state = state;
  frameState.afterRender.push(function () {
    primitive._ready =
      primitive._state === PrimitiveState.COMPLETE ||
      primitive._state === PrimitiveState.FAILED;
    if (!defined(error)) {
      return true;
    }
  });
}

Essentially, the primitive follows this lifecycle on a single frame/clock tick:

  1. The clock ticks which causes DynamicGeometryUpdater to destroy and re-create the dynamic Primitives.
  2. Immediately following this, the Scene determines if it should render the next frame.
  3. Through the course of rendering the next frame, updateAndRenderPrimitives(scene) is invoked.
  4. updateAndRenderPrimitives(scene) ultimately calls update on the newly created dynamic Primitives
  5. When the Primitives are updated, setReady is invoked
  6. As the code stands today, primitive._ready is not being set until after the render completes since it's done in the afterRender function.

By setting primitive._ready immediately, there's no need to request another render which solves the infinite rendering problem reported on this issue. Do you see any reason why this solution would cause problems?

rropp5 commented 7 months ago

I built a few more Sandcastles to test different cases.

In all of these sandcastles, the expectation is that the Ellipse should only render when a renderRequest (button click) is explicitly made or the amount of time specified by maximumRenderTimeChange has elapsed. Instead, the scene constantly renders in all of them.

Case 1: animation is paused, Infinity maximumRenderTimeChange

Ground Primitive : Expected behavior is that the ellipse only changes size when the "Request Render" button is clicked

Primitive : Expected behavior is that the ellipse only changes size when the "Request Render" button is clicked

Case 2: animation is active, 2 second maximumRenderTimeChange

Ground Primitive : Expected behavior is that the ellipse changes size every 2 seconds

Primitive : Expected behavior is that the ellipse changes size every 2 seconds

rropp5 commented 7 months ago

This original Sandcastle from this bug report is missing one important detail but once this line is added, my commit fixes the constant rendering issue:

// Only render when explicitly requested
viewer.scene.maximumRenderTimeChange = Infinity;

The default maximum render time change is 0.0 so when debugging this Sandcastle with my fix (#11855) I noticed it continued to render constantly. However, I figured out this was because the time difference always greater than 0: Scene.js:L4030. Once I added the line above, all worked as expected. Maybe a better default value for viewer.scene.maximumRenderTimeChange would be Infinity?

Updated original Sandcastle

ZenDay commented 5 months ago

In fact, I truly hope if it could render when the entity's property value updated instead of never change until requestRender was called.