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

Less holes, and improvements to "forbid holes" mode #488

Merged
merged 9 commits into from
Apr 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Change Log

### ? - ?

##### Additions :tada:

- Improved the load performance when `TilesetOptions::forbidHoles` is enabled by only loading child tiles when their parent does not meet the necessary screen-space error requirement.

##### Fixes :wrench:

- Fixed a bug that could cause the same tiles to be continually loaded and unloaded when `TilesetOptions::forbidHoles` was enabled.
- Fixed a bug that could sometimes cause tilesets to fail to show their full detail when making changes to raster overlays.
- Fixed a bug that could cause holes even with `TilesetOptions::forbidHoles` enabled, particularly when using external tilesets.
- Tiles will no longer be selected to render when they have no content and they have a higher "geometric error" than their parent. In previous versions, this situation could briefly lead to holes while the children of such tiles loaded.

### v0.14.1 - 2022-04-14

##### Fixes :wrench:
Expand Down
21 changes: 9 additions & 12 deletions Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h
Original file line number Diff line number Diff line change
Expand Up @@ -461,26 +461,23 @@ class CESIUM3DTILESSELECTION_API Tileset final {

/**
* @brief Queues load of tiles that are _required_ to be loaded before the
* given tile can be refined.
* given tile can be refined in "Forbid Holes" mode.
*
* If {@link TilesetOptions::forbidHoles} is false (the default), any tile can
* be refined, regardless of whether its children are loaded or not. So in
* that case, this method immediately returns `false`.
* The queued tiles may include descedents, too, if any children are set to
* Unconditionally Refine ({@link Tile::getUnconditionallyRefine}).
*
* When `forbidHoles` is true, however, and some of this tile's children are
* not yet renderable, this method returns `true`. It also adds those
* not-yet-renderable tiles to the load queue.
* This method should only be called if {@link TilesetOptions::forbidHoles} is enabled.
*
* @param frameState The state of the current frame.
* @param tile The tile that is potentially being refined.
* @param implicitInfo The implicit traversal info.
* @param distance The distance to the tile.
* @return true Some of the required children are not yet loaded, so this tile
* _cannot_ yet be refined.
* @return false All of the required children (if there are any) are loaded,
* so this tile _can_ be refined.
* @return true Some of the required descendents are not yet loaded, so this
* tile _cannot_ yet be refined.
* @return false All of the required descendents (if there are any) are
* loaded, so this tile _can_ be refined.
*/
bool _queueLoadOfChildrenRequiredForRefinement(
bool _queueLoadOfChildrenRequiredForForbidHoles(
const FrameState& frameState,
Tile& tile,
const ImplicitTraversalInfo& implicitInfo,
Expand Down
7 changes: 6 additions & 1 deletion Cesium3DTilesSelection/src/Tile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -846,7 +846,6 @@ void Tile::update(
}
}

// TODO: if there's no model, we can actually free any existing overlays.
if (this->getState() == LoadState::Done &&
this->getTileset()->supportsRasterOverlays() && this->getContent() &&
this->getContent()->model) {
Comment on lines 849 to 851
Copy link
Contributor

Choose a reason for hiding this comment

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

Not directly related to this PR, but "supportsRasterOverlays" doesn't need to exist anymore right? Removing that would make this and the "else if" logic a little clearer to read. Opened this issue for it: #489

Expand Down Expand Up @@ -904,6 +903,12 @@ void Tile::update(
if (moreRasterDetailAvailable && this->_children.empty()) {
createQuadtreeSubdividedChildren(*this);
}
} else if (
this->getState() == LoadState::Done && !this->_rasterTiles.empty()) {
// We can't hang raster images on a tile without geometry, and their
// existence can prevent the tile from being deemed done loading. So clear
// them out here.
this->_rasterTiles.clear();
}
}

Expand Down
83 changes: 70 additions & 13 deletions Cesium3DTilesSelection/src/Tileset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -701,18 +701,21 @@ Tileset::TraversalDetails Tileset::_renderLeaf(
return traversalDetails;
}

bool Tileset::_queueLoadOfChildrenRequiredForRefinement(
bool Tileset::_queueLoadOfChildrenRequiredForForbidHoles(
const FrameState& frameState,
Tile& tile,
const ImplicitTraversalInfo& implicitInfo,
const std::vector<double>& distances) {
if (!this->_options.forbidHoles) {
return false;
}
// This method should only be called in "Forbid Holes" mode.
assert(this->_options.forbidHoles);

bool waitingForChildren = false;

// If we're forbidding holes, don't refine if any children are still loading.
gsl::span<Tile> children = tile.getChildren();
bool waitingForChildren = false;
for (Tile& child : children) {
this->_markTileVisited(child);

if (!child.isRenderable() && !child.isExternalTileset()) {
waitingForChildren = true;

Expand All @@ -727,7 +730,6 @@ bool Tileset::_queueLoadOfChildrenRequiredForRefinement(
childInfo);
}
child.update(frameState.lastFrameNumber, frameState.currentFrameNumber);
this->_markTileVisited(child);

// We're using the distance to the parent tile to compute the load
// priority. This is fine because the relative priority of the children is
Expand All @@ -738,8 +740,22 @@ bool Tileset::_queueLoadOfChildrenRequiredForRefinement(
frameState.frustums,
child,
distances);
} else if (child.getUnconditionallyRefine()) {
// This child tile is set to unconditionally refine. That means refining
// _to_ it will immediately refine _through_ it. So we need to make sure
// its children are renderable, too.
// The distances are not correct for the child's children, but once again
// we don't care because all tiles must be loaded before we can render any
// of them, so their relative priority doesn't matter.
ImplicitTraversalInfo childInfo(&child, &implicitInfo);
waitingForChildren |= this->_queueLoadOfChildrenRequiredForForbidHoles(
frameState,
child,
childInfo,
distances);
}
}

return waitingForChildren;
}

Expand Down Expand Up @@ -1000,14 +1016,23 @@ Tileset::TraversalDetails Tileset::_visitTile(

const bool unconditionallyRefine = tile.getUnconditionallyRefine();
const bool meetsSse = _meetsSse(frameState.frustums, tile, distances, culled);
const bool waitingForChildren = _queueLoadOfChildrenRequiredForRefinement(
frameState,
tile,
implicitInfo,
distances);

if (!unconditionallyRefine &&
(meetsSse || ancestorMeetsSse || waitingForChildren)) {
bool wantToRefine = unconditionallyRefine || (!meetsSse && !ancestorMeetsSse);

// In "Forbid Holes" mode, we cannot refine this tile until all its children
// are loaded. But don't queue the children for load until we _want_ to
// refine this tile.
if (wantToRefine && this->_options.forbidHoles) {
const bool waitingForChildren =
this->_queueLoadOfChildrenRequiredForForbidHoles(
frameState,
tile,
implicitInfo,
distances);
wantToRefine = !waitingForChildren;
}

if (!wantToRefine) {
// This tile (or an ancestor) is the one we want to render this frame, but
// we'll do different things depending on the state of this tile and on what
// we did _last_ frame.
Expand Down Expand Up @@ -1372,6 +1397,38 @@ static bool anyRasterOverlaysNeedLoading(const Tile& tile) noexcept {
// state if needed.
if (tile.getState() == Tile::LoadState::Unloaded) {
tile.setState(Tile::LoadState::ContentLoaded);

// There are two possible ways to handle a tile with no content:
//
// 1. Treat it as a placeholder used for more efficient culling, but
// never render it. Refining to this tile is equivalent to refining
// to its children. To have this behavior, the tile _should_ have
// content, but that content's model should be std::nullopt.
// 2. Treat it as an indication that nothing need be rendered in this
// area at this level-of-detail. In other words, "render" it as a
// hole. To have this behavior, the tile should _not_ have content at
// all.
//
// We distinguish whether the tileset creator wanted (1) or (2) by
// comparing this tile's geometricError to the geometricError of its
// parent tile. If this tile's error is greater than or equal to its
// parent, treat it as (1). If it's less, treat it as (2).
//
// For a tile with no parent there's no difference between the
// behaviors.
double myGeometricError = tile.getNonZeroGeometricError();

Tile* pAncestor = tile.getParent();
while (pAncestor && pAncestor->getUnconditionallyRefine()) {
pAncestor = pAncestor->getParent();
}

double parentGeometricError =
pAncestor ? pAncestor->getNonZeroGeometricError()
: myGeometricError * 2.0;
if (myGeometricError >= parentGeometricError) {
tile.setEmptyContent();
}
}
} else if (shouldLoad) {
loadQueue.push_back({&tile, highestLoadPriority});
Expand Down