On the current master (commit 72b6ddc9bf5c68c15b93c3bc47690f55fbc35c0f) , Cesium performance degrades greatly when rendering DynamicEllipses via CZML. This has only become an issue in the last week or so - previous commit that I had tested where it was fine was 46c9ecc6d4b04ec6d90ef27b8ced61e198d3d605.
I've attached a DevTools CPU profile export (Cesium was idle at the time) and the CZML used for profiling.
CPU Profile:
https://www.dropbox.com/s/7lwo23f11vheway/CPU-20140227T160058.cpuprofile
CZML:
https://www.dropbox.com/s/7uk26trux0huahg/ellipses.czml
This post probably goes into more detail than you would care to hear, but I have a feeling this issue may come up again and wanted to create a good write-up for it.
The entire way we render geometry via CZML was re-written this release and went into master within the past week, so what you are seeing is a direct result of that. The set of capabilities for CZML has expanded greatly (for example you can do extruded ellipses & polygons now); and static geometry performance has been improved by several orders of magnitude (that's not hyperbole either, it's that much faster). As you have discovered yourself, there were some tradeoffs made.
The good news is I have two fixes, one of them should be very easy to make on your end; and if I need to, I can make a slightly more involved fix on my end. First, we'll go with your end. Looking at the very first ellipse in your file I see the following.
"semiMinorAxis": {
"number": [
0,
3000,
900,
3000,
1020,
3000
],
"epoch": "2015-07-04T08:21:00Z"
},
"semiMajorAxis": {
"number": [
0,
3000,
900,
3000,
1020,
3000
],
"epoch": "2015-07-04T08:21:00Z"
},
"rotation": {
"number": [
0,
1.5707963267948966,
900,
1.5707963267948966
],
"epoch": "2015-07-04T08:21:00Z"
}
None of these properties actually change, they have a constant value. However, you are specifying them as sampled properties. Cesium is detecting that they are sampled properties and treating them as if they are going to change every frame (thus constantly recomputing the primitive). This is what's causing the performance you are seeing. The best solution is to write out static properties as static, for example the above snippet can be condensed to this:
"semiMinorAxis": 3000,
"semiMajorAxis": 3000,
"rotation": 1.5707963267948966,
Not only is this way less data to send to the client, it will load faster too and rendering speed will improve compared to b24. Properties that are actually time-dynamic should still be written as samples.
So the reason you see a regression is pretty simple. Cesium used to only have one path for each shape (ellipse, polygon, etc...). That path would evaluate every property of every object every frame. It would store a copy of every value of every property at that time and only recompute a primitive if something changed. As you can probably guess, this polling approach is pretty slow and did not scale well at all once you started to have lots of primitives. It worked pretty well for a small number of primitives, regardless of whether they changed over time or not.
Now, after the refactor, there are two paths for each shape. A static path and a dynamic path. When I say static, I mean in regards to the shape of the object, not it's color, visibility, or material. For static data, we compute once and never touch it again, this is how we can now handle 50,000 shapes without breaking a sweat (think rendering of buildings in a large city). The dynamic path now assumes the shape is actually dynamic, so in order to save processing time and memory, it doesn't store a copy of every property like we used to (after all, if the geometry is truly dynamic then most properties will never be equal anyway, and all it takes is for one property to change for the entire geometry to have to be recomputed.
So by now hopefully you have a good understanding of what's happening here, we are recomputing all of your ellipses every frame even though a good chunk of them aren't changing. We are doing this because the CZML data is presenting itself as time-dynamic, even though in many cases it isn't.
Now the second, more involved, fix I mentioned could be made on our end. It would involve changing the fast path to store the last copy of each value and do a comparison each frame. This would bring your current CZML back to the same characteristics of b24, but overall your app would not be as fast as it good be if you made the first fix.
I will add that one use case that would be genuinely slower in the new system is geometry that changes based on an interval instead of samples. But I felt this use case was rare enough (and could easily be worked around using two different object) that I didn't worry about it for this pass on the code.
And finally, I just want to add that dynamic geometry in Cesium will get much faster all around once we add dynamic buffer support. This will probably happen sometime this year, but I don't know exactly when yet. At that point dynamic shapes and lines will get way faster than they are now. At that point the potentially slow interval-based performance I described above should disappear as well.
Anyway, hope that helps. If you can make the change I suggest and this answer satisfied you, please close this. Otherwise, let me know what else I can do to help.
Thanks for the explanation! I'll make the changes on my end and let you know how it goes.
I've made the changes on my end so that now only truly dynamic ellipses are specified in the CZML as such. Still, anything more than three dynamic ellipses being rendered has a serious impact on performance, so I think your other change (keeping the last calculation for dynamic objects) is still applicable.
Is this something that's in the works?
The correct way to do dynamic geometry is with dynamic buffers, that's something that @bagnell will hopefully be adding within the next couple of months. At that point, this problem should go away completely. That being said, I'm not against making some temporary changes to improve performance in the mean time; though these changes would be in b27, not b26 (which is about to be released today). Can you post the updated CZML file so I can verify what I think is going on is actually what is going on. Thanks.
Dynamic ellipse CZML:
https://www.dropbox.com/s/stdeh7v77e92n4f/DynamicEllipses.czml
Fair enough.
So there's good news and there's bad news. The bad news is that the problem is not what I thought it was (and I still don't know what the real problem is). Because of this, I don't have an ETA for a code fix. The old behavior computed the outline of an ellipse and then rendered it as a polygon. The new behavior simply generates an ellipse mesh directly. There's a chance that the answer is the old behavior was fast (but wrong in many cases) and the new behavior is slow (but correct in all cases). However, that's worst case scenario and I'm not ready to say that yet, I just need more time to dig into this and may need to pull in @bagnell or someone else to help me figure it out.
Okay, now for the good news. One theory I have is that the old behavior didn't actually honor the granularity settings correctly and so the new correct behavior is simply doing way more work every frame with the same settings. We default to one degree, so another CZML tweak in the mean time is to set the granularity of the ellipse explicitly. For each of your dynamic ellipses (or you can do it for all ellipses if you want), add "granularity" : 0.0523598776, to the CZML. (that's 3 degrees in radians). This greatly improves performance and to my eye looks just as good as the higher sampling. (I tried it with the last CZML profile you provided).
The other changes I asked you to make to your CZML are still valid, and you should definitely keep them. Let me know if this workaround works for you and is sufficient for your purposes. I'll keep this issue open until we full understand what's going on either way.
Lowering the granularity helps tremendously, thanks for the suggestion. I look forward to your findings.
The polygon doesn't add any points to the interior of the polygon. The granularity is for subdividing the edges of the polygon. For example, if you gave it four positions to make square and a granularity of 1 degree, each segment connecting the four points of the square would be subdivided on 1 degree intervals. The same thing happens when passing it the boundary of an ellipse. However, the EllipseGeometry does add points to the interior (more triangles) at the given granularity and pushes them to the surface.
Ellipse with 1 degree granularity created using PolygonGeometry wireframe:

Those aren't indiviual triangles in the center. The line segments are overlapping because the endpoints are on the boundary.
Ellipse with 5 degree granularity created with EllipseGeometry wireframe:

There are no overlapping lines here. Those are all triangles.
Sorry, I was just looking at the code. The first is triangulated with ear clipping, then the triangles are subdivided. So those are triangles in the center. The second is triangulated differently because we have special knowledge about its shape and that it will always be convex.
So after talking to @bagnell a little offline to clarify his above posts, it sounds like I was not far off the mark. The problem is either in EllipseGeometry or PolygonGeoemtry. EllipseGeometry should be faster than PolygonGeometry at the same granularity, right now it's much slower. This is either because PolygonGeometry is incorrect in a way that makes it faster or EllipseGeometry is incorrect in a way that makes it slower. Depending on which way it goes, one solution would be to change our default granularity to something like 3 degrees, instead of 1 like it is now.
Sorry for the long wait on a response...
It looks like version b28 has addressed our lingering performance issue with ellipses. Feel free to close this issue.
I thought I replied to this, but apparently I never did. Glad to hear it, thanks.
Hmm, has this been resolved? I still get terrible performance. I am visualising around 110 entities. I can easily show a complex 3d model with each without any performance issues, but showing an ellipses with granularity = 1, just a few triangles, really tanks fps to be unusable. Should I load a model of a circle in? What am I doing wrong?
@killroy42 If your ellipses are dynamic, they are recomputed and redrawn every frame and this can have a negative impact on performance. However, I wouldn't expect poor performance if your ellipses are drawn once and you're not changing the position or other properties. Depending on your use case, I would probably recommend using a point instead.
Most helpful comment
This post probably goes into more detail than you would care to hear, but I have a feeling this issue may come up again and wanted to create a good write-up for it.
The entire way we render geometry via CZML was re-written this release and went into master within the past week, so what you are seeing is a direct result of that. The set of capabilities for CZML has expanded greatly (for example you can do extruded ellipses & polygons now); and static geometry performance has been improved by several orders of magnitude (that's not hyperbole either, it's that much faster). As you have discovered yourself, there were some tradeoffs made.
The good news is I have two fixes, one of them should be very easy to make on your end; and if I need to, I can make a slightly more involved fix on my end. First, we'll go with your end. Looking at the very first ellipse in your file I see the following.
None of these properties actually change, they have a constant value. However, you are specifying them as sampled properties. Cesium is detecting that they are sampled properties and treating them as if they are going to change every frame (thus constantly recomputing the primitive). This is what's causing the performance you are seeing. The best solution is to write out static properties as static, for example the above snippet can be condensed to this:
Not only is this way less data to send to the client, it will load faster too and rendering speed will improve compared to b24. Properties that are actually time-dynamic should still be written as samples.
So the reason you see a regression is pretty simple. Cesium used to only have one path for each shape (ellipse, polygon, etc...). That path would evaluate every property of every object every frame. It would store a copy of every value of every property at that time and only recompute a primitive if something changed. As you can probably guess, this polling approach is pretty slow and did not scale well at all once you started to have lots of primitives. It worked pretty well for a small number of primitives, regardless of whether they changed over time or not.
Now, after the refactor, there are two paths for each shape. A static path and a dynamic path. When I say static, I mean in regards to the shape of the object, not it's color, visibility, or material. For static data, we compute once and never touch it again, this is how we can now handle 50,000 shapes without breaking a sweat (think rendering of buildings in a large city). The dynamic path now assumes the shape is actually dynamic, so in order to save processing time and memory, it doesn't store a copy of every property like we used to (after all, if the geometry is truly dynamic then most properties will never be equal anyway, and all it takes is for one property to change for the entire geometry to have to be recomputed.
So by now hopefully you have a good understanding of what's happening here, we are recomputing all of your ellipses every frame even though a good chunk of them aren't changing. We are doing this because the CZML data is presenting itself as time-dynamic, even though in many cases it isn't.
Now the second, more involved, fix I mentioned could be made on our end. It would involve changing the fast path to store the last copy of each value and do a comparison each frame. This would bring your current CZML back to the same characteristics of b24, but overall your app would not be as fast as it good be if you made the first fix.
I will add that one use case that would be genuinely slower in the new system is geometry that changes based on an interval instead of samples. But I felt this use case was rare enough (and could easily be worked around using two different object) that I didn't worry about it for this pass on the code.
And finally, I just want to add that dynamic geometry in Cesium will get much faster all around once we add dynamic buffer support. This will probably happen sometime this year, but I don't know exactly when yet. At that point dynamic shapes and lines will get way faster than they are now. At that point the potentially slow interval-based performance I described above should disappear as well.
Anyway, hope that helps. If you can make the change I suggest and this answer satisfied you, please close this. Otherwise, let me know what else I can do to help.