-
Notifications
You must be signed in to change notification settings - Fork 216
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
Unload content of external tilesets to save memory usage #876
base: main
Are you sure you want to change the base?
Changes from 8 commits
9bada90
d240be9
24ef6aa
bfce55f
1089d32
685dee6
096d248
7592a2e
d805293
c1de433
0700c72
6bf7f24
6d02abd
5e4a4d4
4e6d064
b3ac5d4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -72,15 +72,6 @@ struct ContentKindSetter { | |
void* pRenderResources; | ||
}; | ||
|
||
void unloadTileRecursively( | ||
Tile& tile, | ||
TilesetContentManager& tilesetContentManager) { | ||
tilesetContentManager.unloadTileContent(tile); | ||
for (Tile& child : tile.getChildren()) { | ||
unloadTileRecursively(child, tilesetContentManager); | ||
} | ||
} | ||
|
||
bool anyRasterOverlaysNeedLoading(const Tile& tile) noexcept { | ||
for (const RasterMappedTo3DTile& mapped : tile.getMappedRasterTiles()) { | ||
const RasterOverlayTile* pLoading = mapped.getLoadingTile(); | ||
|
@@ -599,6 +590,7 @@ TilesetContentManager::TilesetContentManager( | |
const TilesetExternals& externals, | ||
const TilesetOptions& tilesetOptions, | ||
RasterOverlayCollection&& overlayCollection, | ||
Tile::LoadedLinkedList& loadedTiles, | ||
std::vector<CesiumAsync::IAssetAccessor::THeader>&& requestHeaders, | ||
std::unique_ptr<TilesetContentLoader>&& pLoader, | ||
std::unique_ptr<Tile>&& pRootTile) | ||
|
@@ -616,6 +608,7 @@ TilesetContentManager::TilesetContentManager( | |
_overlayCollection{std::move(overlayCollection)}, | ||
_tileLoadsInProgress{0}, | ||
_loadedTilesCount{0}, | ||
_loadedTiles(loadedTiles), | ||
_tilesDataUsed{0}, | ||
_destructionCompletePromise{externals.asyncSystem.createPromise<void>()}, | ||
_destructionCompleteFuture{ | ||
|
@@ -630,6 +623,7 @@ TilesetContentManager::TilesetContentManager( | |
const TilesetExternals& externals, | ||
const TilesetOptions& tilesetOptions, | ||
RasterOverlayCollection&& overlayCollection, | ||
Tile::LoadedLinkedList& loadedTiles, | ||
const std::string& url) | ||
: _externals{externals}, | ||
_requestHeaders{}, | ||
|
@@ -645,6 +639,7 @@ TilesetContentManager::TilesetContentManager( | |
_overlayCollection{std::move(overlayCollection)}, | ||
_tileLoadsInProgress{0}, | ||
_loadedTilesCount{0}, | ||
_loadedTiles(loadedTiles), | ||
_tilesDataUsed{0}, | ||
_destructionCompletePromise{externals.asyncSystem.createPromise<void>()}, | ||
_destructionCompleteFuture{ | ||
|
@@ -767,6 +762,7 @@ TilesetContentManager::TilesetContentManager( | |
const TilesetExternals& externals, | ||
const TilesetOptions& tilesetOptions, | ||
RasterOverlayCollection&& overlayCollection, | ||
Tile::LoadedLinkedList& loadedTiles, | ||
int64_t ionAssetID, | ||
const std::string& ionAccessToken, | ||
const std::string& ionAssetEndpointUrl) | ||
|
@@ -784,6 +780,7 @@ TilesetContentManager::TilesetContentManager( | |
_overlayCollection{std::move(overlayCollection)}, | ||
_tileLoadsInProgress{0}, | ||
_loadedTilesCount{0}, | ||
_loadedTiles(loadedTiles), | ||
_tilesDataUsed{0}, | ||
_destructionCompletePromise{externals.asyncSystem.createPromise<void>()}, | ||
_destructionCompleteFuture{ | ||
|
@@ -1026,6 +1023,27 @@ void TilesetContentManager::updateTileContent( | |
} | ||
} | ||
|
||
bool TilesetContentManager::handleUpsampledTileChildren(Tile& tile) { | ||
for (Tile& child : tile.getChildren()) { | ||
if (child.getState() == TileLoadState::ContentLoading && | ||
std::holds_alternative<CesiumGeometry::UpsampledQuadtreeNode>( | ||
child.getTileID())) { | ||
// Yes, a child is upsampling from this tile, so it may be using the | ||
// tile's content from another thread via lambda capture. We can't unload | ||
// it right now. So mark the tile as in the process of unloading and stop | ||
// here. | ||
tile.setState(TileLoadState::Unloading); | ||
return false; | ||
} | ||
|
||
if (!this->handleUpsampledTileChildren(child)) { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
bool TilesetContentManager::unloadTileContent(Tile& tile) { | ||
TileLoadState state = tile.getState(); | ||
if (state == TileLoadState::Unloaded) { | ||
|
@@ -1037,9 +1055,16 @@ bool TilesetContentManager::unloadTileContent(Tile& tile) { | |
} | ||
|
||
TileContent& content = tile.getContent(); | ||
bool isReadyToUnload = tile.isReadyToUnload(); | ||
bool isExternalContent = tile.isExternalContent(); | ||
|
||
// don't unload external or empty tile while children are still loading | ||
if ((isExternalContent || content.isEmptyContent()) && !isReadyToUnload) { | ||
return false; | ||
} | ||
|
||
// don't unload external or empty tile | ||
if (content.isExternalContent() || content.isEmptyContent()) { | ||
// Don't unload this tile if children are still upsampling | ||
if (!this->handleUpsampledTileChildren(tile)) { | ||
return false; | ||
} | ||
|
||
|
@@ -1050,46 +1075,50 @@ bool TilesetContentManager::unloadTileContent(Tile& tile) { | |
} | ||
tile.getMappedRasterTiles().clear(); | ||
|
||
// Unload the renderer resources and clear any raster overlay tiles. We can do | ||
// this even if the tile can't be fully unloaded because this tile's geometry | ||
// is being using by an async upsample operation (checked below). | ||
switch (state) { | ||
case TileLoadState::ContentLoaded: | ||
unloadContentLoadedState(tile); | ||
break; | ||
case TileLoadState::Done: | ||
unloadDoneState(tile); | ||
break; | ||
default: | ||
break; | ||
if (!tile.isEmptyContent() && !tile.isUnknownContent()) { | ||
// Unload the renderer resources and clear any raster overlay tiles. We can | ||
// do this even if the tile can't be fully unloaded because this tile's | ||
// geometry is being using by an async upsample operation (checked below). | ||
switch (state) { | ||
case TileLoadState::ContentLoaded: | ||
unloadContentLoadedState(tile); | ||
break; | ||
case TileLoadState::Done: | ||
unloadDoneState(tile); | ||
break; | ||
default: | ||
break; | ||
} | ||
} | ||
|
||
// Are any children currently being upsampled from this tile? | ||
for (const Tile& child : tile.getChildren()) { | ||
if (child.getState() == TileLoadState::ContentLoading && | ||
std::holds_alternative<CesiumGeometry::UpsampledQuadtreeNode>( | ||
child.getTileID())) { | ||
// Yes, a child is upsampling from this tile, so it may be using the | ||
// tile's content from another thread via lambda capture. We can't unload | ||
// it right now. So mark the tile as in the process of unloading and stop | ||
// here. | ||
tile.setState(TileLoadState::Unloading); | ||
return false; | ||
if (isReadyToUnload) { | ||
// Make sure we unload all children | ||
gsl::span<Cesium3DTilesSelection::Tile> children = tile.getChildren(); | ||
for (Tile& child : children) { | ||
this->unloadTileContent(child); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This raises some performance red flags, though I don't know exactly how to tell you to fix it. When we decide to unload a tile, we'll traverse all of its descendants in |
||
} | ||
} | ||
|
||
// If we make it this far, the tile's content will be fully unloaded. | ||
notifyTileUnloading(&tile); | ||
content.setContentKind(TileUnknownContent{}); | ||
tile.setState(TileLoadState::Unloaded); | ||
if (isExternalContent) { | ||
// We don't want to set external tilesets as Unloaded quite yet, because | ||
// then they might get reloaded before we've had a chance to clear their | ||
// children and cause an error. They'll get their children cleared and their | ||
// state set to Unloaded before next clean up | ||
tile.setState(TileLoadState::Done); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this is safe. Setting it to |
||
} else { | ||
tile.setState(TileLoadState::Unloaded); | ||
} | ||
return true; | ||
} | ||
|
||
void TilesetContentManager::unloadAll() { | ||
// TODO: use the linked-list of loaded tiles instead of walking the entire | ||
// tile tree. | ||
if (this->_pRootTile) { | ||
unloadTileRecursively(*this->_pRootTile, *this); | ||
this->unloadTileContent(*this->_pRootTile); | ||
} | ||
} | ||
|
||
|
@@ -1424,7 +1453,10 @@ void TilesetContentManager::updateDoneState( | |
void TilesetContentManager::unloadContentLoadedState(Tile& tile) { | ||
TileContent& content = tile.getContent(); | ||
TileRenderContent* pRenderContent = content.getRenderContent(); | ||
assert(pRenderContent && "Tile must have render content to be unloaded"); | ||
if (pRenderContent == nullptr) { | ||
// No resources we need to clean up | ||
return; | ||
} | ||
|
||
void* pWorkerRenderResources = pRenderContent->getRenderResources(); | ||
this->_externals.pPrepareRendererResources->free( | ||
|
@@ -1437,7 +1469,10 @@ void TilesetContentManager::unloadContentLoadedState(Tile& tile) { | |
void TilesetContentManager::unloadDoneState(Tile& tile) { | ||
TileContent& content = tile.getContent(); | ||
TileRenderContent* pRenderContent = content.getRenderContent(); | ||
assert(pRenderContent && "Tile must have render content to be unloaded"); | ||
if (pRenderContent == nullptr) { | ||
// No resources to clean up | ||
return; | ||
} | ||
|
||
void* pMainThreadRenderResources = pRenderContent->getRenderResources(); | ||
this->_externals.pPrepareRendererResources->free( | ||
|
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 won't do what you want. In fact, we should probably disable the assignment operator on this class. Take a look at how this LoadedLinkedList works. It's an "intrusive" linked list, where the links between nodes are actually stored in the data (Tiles) themselves. This means that a tile can only be in one list at a time. And clearing the tiles from a list requires traversing the entire list and setting the previous and next pointers in each tile to nullptr.