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

3D Tiles Traversal cleanup #6390

Merged
merged 64 commits into from
Aug 16, 2018
Merged

3D Tiles Traversal cleanup #6390

merged 64 commits into from
Aug 16, 2018

Conversation

lilleyse
Copy link
Contributor

@lilleyse lilleyse commented Mar 29, 2018

This is mainly a cleanup of the 3D Tiles traversal code. The base traversal and skip traversal are now one code path which should make it easier to understand and maintain. The fundamentals haven't changed.

Besides cleanup some of the fixes are

  • Fixed a bug where skip lods would load all descendant tiles, without skipping, until the parent is requested. For a static view deep in a photogrammetry tileset the number of tiles with content ready dropped from 65 to 53 after this fix. All other selection stats stayed the same.
  • Traditional replacement refinement (skip-lods off) is much faster since it doesn't wait for a tile to refine before traversing and loading more tiles. This will be good for vector tiles and other things that don't use skip-lods.
  • Replacement tiles now use SSE as their request priority rather than distance. Additive tiles continue to use distance. Subjectively it looks slightly better this way and loads faster when skip-lods is off. It should be straightforward to add other priority metrics now, like closeness to view center.
  • Sort requests by priority before issuing them. Less chance of getting cancelled requests.
  • Pick statistics in inspector are correct now
  • Debug outlines get drawn for empty tiles
  • Debug labels get drawn for external tilesets
  • If a tileset has all additive tiles it avoids the final selection traversal required by skip lods.

Includes changes from these PRs which should be merged before this:
#6240
#6247
#6364
#6391

Part of #6243

To do

  • More performance tests, especially for dynamic views
  • Continue to test out different tilesets

@lilleyse lilleyse force-pushed the traversal-cleanup branch from e49d8c2 to a0d5c5c Compare July 5, 2018 00:08
@lilleyse lilleyse changed the base branch from request-performance to master July 5, 2018 00:52
@lilleyse lilleyse force-pushed the traversal-cleanup branch from 23e71dc to 2705996 Compare July 5, 2018 00:58
@lilleyse
Copy link
Contributor Author

lilleyse commented Jul 5, 2018

After too long.. updated.

Copy link
Contributor

@ggetz ggetz left a comment

Choose a reason for hiding this comment

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

Looks a lot cleaner overall! Looks like adding new functionality like other tile priority function will be straightforward.


if (getScreenSpaceError(tileset, tileset._geometricError, root, frameState) <= maximumScreenSpaceError) {
// The SSE of not rendering the tree is small enough that the tree does not need to be rendered
// The SSE of not rendering the tree is small enough that the tree does not need to be rendered
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment isn't super clear - The root tile doesn't meet the SSE requirement, therefore the tree does not need to be rendered?

function isVisibleAndMeetsSSE(tileset, tile, frameState) {
/**
* Traverse the tree and check if their selected frame is the current frame. If so, add it to a selection queue.
* Furthermore, this is a preorder traversal so children tiles are selected before ancestor tiles.
Copy link
Contributor

Choose a reason for hiding this comment

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

Furthermore

* no deeper than 255. It is very, very unlikely this will cause a problem.
*
* NOTE: when the scene has inverted classification enabled, the stencil buffer will be masked to 4 bits. So, the
* selected tiles must be no deeper than 15. This is still very unlikely.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we check the selection depth at this point and throw an error if either of these cases arise? Since it's so unlikely to happen, it won't immediately be apparent what is happening.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Throwing an error is probably too extreme since the tileset can still render there just may be artifacts.

color = Color.GREEN;
} else {
color = Color.RED;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

A little picky, but since red usually means "bad" and green usually mean "good" or "go", I would mark the colors something like so:

!finalResolution -> Yellow
empty -> gray
hasContentBoundingVolume -> green
otherwise -> blue

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 ended up going with:

!finalResolution -> yellow
empty -> gray
boundingVolume -> white
contentBoundingVolume -> blue (the code for drawing content bounding volumes is further down)

I like white as the "base" color because it's really neutral.

@@ -253,14 +253,16 @@ define([
this.hasTilesetContent = false;

/**
* The corresponding node in the cache replacement list.
* The corresponding node in the cache.
Copy link
Contributor

Choose a reason for hiding this comment

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

I would briefly mention what the cache is used for here.

@@ -458,13 +456,13 @@ define([
*/
contentAvailable : {
get : function() {
return this.contentReady || (defined(this._expiredContent) && this._contentState !== Cesium3DTileContentState.FAILED);
return (this.contentReady && this.hasRenderableContent) || (defined(this._expiredContent) && this._contentState !== Cesium3DTileContentState.FAILED);
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't contentReady determine if the content is renderable? Would it make more sence to make sure it's non-empty and also if the content is ready?

Copy link
Contributor

Choose a reason for hiding this comment

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

Overall, it seems like there is a mix of setting variables like hasRenderableContent explicitly, along with using states like this._contentState === Cesium3DTileContentState.READY. hadRenderableContent can be a getter that checks the state, but I think we should only be setting one or the other.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes this was confusing, I removed this.hasRenderableContent and instead check hasEmptyContent or hasTilesetContent as needed.

var add = tile.refine === Cesium3DTileRefine.ADD;
if (add) {
return tile._distanceToCamera;
} else if (replace) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to return some priority as a default, or leave the priority undefined?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also you don't need the else if because of the early return above.

if (hasEmptyContent(tile)) {
// Add empty tile just to show its debug bounding volume
addEmptyTile(tileset, tile, frameState);
loadTile(tileset, tile, 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 are we loading an empty tile? Won't it just return from the loadTile function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hasEmptyContent is a catch-all for anything that has Empty3DTileContent or Tileset3DTileContent. The load is needed for the Tileset3DTileContent case to load the external tileset. I left a comment to clarify.


tile._childrenVisibility = flag;
function executeEmptyTraversal(tileset, root, frameState) {
// Depth-first traversal that checks if all nearest descendants with content are loaded. Ignores visibility.
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe you mentioned you'd like to combine this function with the normal traversal function if possible. Taking a look at both, I would say they are fine being separate as the logic would get too complex.

lastAncestor = tile;
if (!traverse) {
selectTile(tileset, tile, frameState);
tile._finalResolution = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

So _finalResolution is a tile marked for rendering at the end of this round of traversal, correct? I think we should call this _selectedForRender or something a little more specific (as it is used externally to this class for debug code).

Copy link
Contributor Author

@lilleyse lilleyse Jul 28, 2018

Choose a reason for hiding this comment

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

_selectedForRender is a bit too broad, _finalResolution says a tile is rendered AND none of its descendants are rendered. I wonder if there's a better name specifically for that condition...

But that's where the !finalResolution -> yellow comes in, it indicates a tile and its descendant are rendering simultaneously and the tile is not entirely "resolved".

I did clean up some of the _finalResolution code though so that _finalResolution = false is set in one place instead of _finalResolution = true being set everywhere.

@lilleyse
Copy link
Contributor Author

@ggetz Updated

@lilleyse
Copy link
Contributor Author

lilleyse commented Aug 9, 2018

@ggetz bump - we should merge this sooner than later so it has time to sit in master.

@ggetz
Copy link
Contributor

ggetz commented Aug 10, 2018

Sorry for the delay @lilleyse!

Code looks good to me, the only thing I see is that http://cesium-dev.s3-website-us-east-1.amazonaws.com/cesium/traversal-cleanup/Apps/Sandcastle/index.html?src=3D%20Tiles%20Terrain%20Classification.html&label=3D%20Tiles takes a significantly longer time to load the tileset then it did previously. Is that a consequence of something in this PR?

@lilleyse
Copy link
Contributor Author

I believe it is an unrelated bug - #6908.

@ggetz
Copy link
Contributor

ggetz commented Aug 16, 2018

@lilleyse Yep, that fixed it!

Thanks for all the work here!

@ggetz ggetz merged commit 939e4c3 into master Aug 16, 2018
@ggetz ggetz deleted the traversal-cleanup branch August 16, 2018 17:24
@lilleyse lilleyse mentioned this pull request Sep 3, 2018
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