diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js index a3e97050932..c488cca3f05 100644 --- a/Source/Scene/Cesium3DTile.js +++ b/Source/Scene/Cesium3DTile.js @@ -248,14 +248,16 @@ define([ this.hasTilesetContent = false; /** - * The corresponding node in the cache replacement list. + * The corresponding node in the cache. + * + * See {@link Cesium3DTilesetCache} * * @type {DoublyLinkedListNode} * @readonly * * @private */ - this.replacementNode = undefined; + this.cacheNode = undefined; var expire = header.expire; var expireDuration; @@ -730,8 +732,6 @@ define([ this._contentReadyToProcessPromise = undefined; this._contentReadyPromise = undefined; - this.replacementNode = undefined; - this.lastStyleTime = 0; this.clippingPlanesDirty = (this._clippingPlanesState === 0); this._clippingPlanesState = 0; diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 086f27e75a7..f0dd2520cde 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -27,6 +27,7 @@ define([ './Cesium3DTileColorBlendMode', './Cesium3DTileContentState', './Cesium3DTileOptimizations', + './Cesium3DTilesetCache', './Cesium3DTilesetStatistics', './Cesium3DTilesetTraversal', './Cesium3DTileStyleEngine', @@ -68,6 +69,7 @@ define([ Cesium3DTileColorBlendMode, Cesium3DTileContentState, Cesium3DTileOptimizations, + Cesium3DTilesetCache, Cesium3DTilesetStatistics, Cesium3DTilesetTraversal, Cesium3DTileStyleEngine, @@ -168,6 +170,7 @@ define([ this._properties = undefined; // Metadata for per-model/point/etc properties this._geometricError = undefined; // Geometric error when the tree is not rendered at all this._gltfUpAxis = undefined; + this._cache = new Cesium3DTilesetCache(); this._processingQueue = []; this._selectedTiles = []; this._requestedTiles = []; @@ -176,14 +179,6 @@ define([ this._loadTimestamp = undefined; this._timeSinceLoad = 0.0; - var replacementList = new DoublyLinkedList(); - - // [head, sentinel) -> tiles that weren't selected this frame and may be replaced - // (sentinel, tail] -> tiles that were selected this frame - this._replacementList = replacementList; // Tiles with content loaded. For cache management. - this._replacementSentinel = replacementList.add(); - this._trimTiles = false; - this._cullWithChildrenBounds = defaultValue(options.cullWithChildrenBounds, true); this._hasMixedContent = false; @@ -1485,10 +1480,8 @@ define([ tileset._statistics.incrementLoadCounts(tile.content); ++tileset._statistics.numberOfTilesWithContentReady; - // Add to the tile cache. Previously expired tiles are already in the cache. - if (!defined(tile.replacementNode)) { - tile.replacementNode = tileset._replacementList.add(tile); - } + // Add to the tile cache. Previously expired tiles are already in the cache and won't get re-added. + tileset._cache.add(tile); } tileset.tileLoad.raiseEvent(tile); @@ -1748,52 +1741,27 @@ define([ stack.push(children[i]); } if (tile !== root) { - unloadTileFromCache(tileset, tile); - tile.destroy(); + destroyTile(tileset, tile); --statistics.numberOfTilesTotal; } } root.children = []; } - function unloadTileFromCache(tileset, tile) { - var node = tile.replacementNode; - if (!defined(node)) { - return; - } - - var statistics = tileset._statistics; - var replacementList = tileset._replacementList; - var tileUnload = tileset.tileUnload; + function unloadTile(tileset, tile) { + tileset.tileUnload.raiseEvent(tile); + tileset._statistics.decrementLoadCounts(tile.content); + --tileset._statistics.numberOfTilesWithContentReady; + tile.unloadContent(); + } - tileUnload.raiseEvent(tile); - replacementList.remove(node); - statistics.decrementLoadCounts(tile.content); - --statistics.numberOfTilesWithContentReady; + function destroyTile(tileset, tile) { + tileset._cache.unloadTile(tileset, tile, unloadTile); + tile.destroy(); } function unloadTiles(tileset) { - var trimTiles = tileset._trimTiles; - tileset._trimTiles = false; - - var replacementList = tileset._replacementList; - - var totalMemoryUsageInBytes = tileset.totalMemoryUsageInBytes; - var maximumMemoryUsageInBytes = tileset._maximumMemoryUsage * 1024 * 1024; - - // Traverse the list only to the sentinel since tiles/nodes to the - // right of the sentinel were used this frame. - // - // The sub-list to the left of the sentinel is ordered from LRU to MRU. - var sentinel = tileset._replacementSentinel; - var node = replacementList.head; - while ((node !== sentinel) && ((totalMemoryUsageInBytes > maximumMemoryUsageInBytes) || trimTiles)) { - var tile = node.item; - node = node.next; - unloadTileFromCache(tileset, tile); - tile.unloadContent(); - totalMemoryUsageInBytes = tileset.totalMemoryUsageInBytes; - } + tileset._cache.unloadTiles(tileset, unloadTile); } /** @@ -1806,8 +1774,7 @@ define([ *

*/ Cesium3DTileset.prototype.trimLoadedTiles = function() { - // Defer to next frame so WebGL delete calls happen inside the render loop - this._trimTiles = true; + this._cache.trim(); }; /////////////////////////////////////////////////////////////////////////// diff --git a/Source/Scene/Cesium3DTilesetCache.js b/Source/Scene/Cesium3DTilesetCache.js new file mode 100644 index 00000000000..848a41da252 --- /dev/null +++ b/Source/Scene/Cesium3DTilesetCache.js @@ -0,0 +1,79 @@ +define([ + '../Core/defined', + '../Core/DoublyLinkedList' + ], function( + defined, + DoublyLinkedList) { + 'use strict'; + + /** + * Stores tiles with content loaded. + * + * @private + */ + function Cesium3DTilesetCache() { + // [head, sentinel) -> tiles that weren't selected this frame and may be removed from the cache + // (sentinel, tail] -> tiles that were selected this frame + this._list = new DoublyLinkedList(); + this._sentinel = this._list.add(); + this._trimTiles = false; + } + + Cesium3DTilesetCache.prototype.reset = function() { + // Move sentinel node to the tail so, at the start of the frame, all tiles + // may be potentially replaced. Tiles are moved to the right of the sentinel + // when they are selected so they will not be replaced. + this._list.splice(this._list.tail, this._sentinel); + }; + + Cesium3DTilesetCache.prototype.touch = function(tile) { + var node = tile.cacheNode; + if (defined(node)) { + this._list.splice(this._sentinel, node); + } + }; + + Cesium3DTilesetCache.prototype.add = function(tile) { + if (!defined(tile.cacheNode)) { + tile.cacheNode = this._list.add(tile); + } + }; + + Cesium3DTilesetCache.prototype.unloadTile = function(tileset, tile, unloadCallback) { + var node = tile.cacheNode; + if (!defined(node)) { + return; + } + + this._list.remove(node); + tile.cacheNode = undefined; + unloadCallback(tileset, tile); + }; + + Cesium3DTilesetCache.prototype.unloadTiles = function(tileset, unloadCallback) { + var trimTiles = this._trimTiles; + this._trimTiles = false; + + var list = this._list; + + var maximumMemoryUsageInBytes = tileset.maximumMemoryUsage * 1024 * 1024; + + // Traverse the list only to the sentinel since tiles/nodes to the + // right of the sentinel were used this frame. + // + // The sub-list to the left of the sentinel is ordered from LRU to MRU. + var sentinel = this._sentinel; + var node = list.head; + while ((node !== sentinel) && ((tileset.totalMemoryUsageInBytes > maximumMemoryUsageInBytes) || trimTiles)) { + var tile = node.item; + node = node.next; + this.unloadTile(tileset, tile, unloadCallback); + } + }; + + Cesium3DTilesetCache.prototype.trim = function() { + this._trimTiles = true; + }; + + return Cesium3DTilesetCache; +}); diff --git a/Source/Scene/Cesium3DTilesetTraversal.js b/Source/Scene/Cesium3DTilesetTraversal.js index 3052b00ddb7..2fd1038170f 100644 --- a/Source/Scene/Cesium3DTilesetTraversal.js +++ b/Source/Scene/Cesium3DTilesetTraversal.js @@ -25,7 +25,8 @@ define([ /** * @private */ - var Cesium3DTilesetTraversal = {}; + function Cesium3DTilesetTraversal() { + } function selectTiles(tileset, frameState, outOfCore) { if (tileset.debugFreezeFrame) { @@ -40,11 +41,7 @@ define([ tileset._selectedTilesToStyle.length = 0; tileset._hasMixedContent = false; - // Move sentinel node to the tail so, at the start of the frame, all tiles - // may be potentially replaced. Tiles are moved to the right of the sentinel - // when they are selected so they will not be replaced. - var replacementList = tileset._replacementList; - replacementList.splice(replacementList.tail, tileset._replacementSentinel); + tileset._cache.reset(); var root = tileset._root; root.updateTransform(tileset._modelMatrix); @@ -625,10 +622,7 @@ define([ if (!outOfCore) { return; } - var node = tile.replacementNode; - if (defined(node)) { - tileset._replacementList.splice(tileset._replacementSentinel, node); - } + tileset._cache.touch(tile); } function computeSSE(tile, frameState) { diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js index fe2d50b80f7..342edbbc02c 100644 --- a/Specs/Scene/Cesium3DTilesetSpec.js +++ b/Specs/Scene/Cesium3DTilesetSpec.js @@ -2294,14 +2294,14 @@ defineSuite([ it('Unloads cached tiles in a tileset with external tileset JSON file using maximumMemoryUsage', function() { return Cesium3DTilesTester.loadTileset(scene, tilesetOfTilesetsUrl).then(function(tileset) { var statistics = tileset._statistics; - var replacementList = tileset._replacementList; + var cacheList = tileset._cache._list; tileset.maximumMemoryUsage = 0.025; scene.renderForSpecs(); expect(statistics.numberOfCommands).toEqual(5); expect(statistics.numberOfTilesWithContentReady).toEqual(5); - expect(replacementList.length - 1).toEqual(5); // Only tiles with content are on the replacement list. -1 for sentinel. + expect(cacheList.length - 1).toEqual(5); // Only tiles with content are on the replacement list. -1 for sentinel. // Zoom out so only root tile is needed to meet SSE. This unloads // all tiles except the root and one of the b3dm children @@ -2310,7 +2310,7 @@ defineSuite([ expect(statistics.numberOfCommands).toEqual(1); expect(statistics.numberOfTilesWithContentReady).toEqual(2); - expect(replacementList.length - 1).toEqual(2); + expect(cacheList.length - 1).toEqual(2); // Reset camera so all tiles are reloaded viewAllTiles(); @@ -2319,7 +2319,7 @@ defineSuite([ expect(statistics.numberOfCommands).toEqual(5); expect(statistics.numberOfTilesWithContentReady).toEqual(5); - expect(replacementList.length - 1).toEqual(5); + expect(cacheList.length - 1).toEqual(5); }); }); });