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

Enforce memory limit for Cesium3DTilesetCache #11310

Merged
merged 14 commits into from
Jun 7, 2023
Merged
12 changes: 12 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Change Log

### 1.107 - 2023-07-01

##### Additions :tada:

- Added `Cesium3DTileset.cacheBytes` and `Cesium3DTileset.maximumCacheOverflowBytes` to better control memory usage. To replicate previous behavior, change `maximumMemoryUsage` to `cacheBytes`, and set `maximumCacheOverflowBytes = Number.MAX_VALUE`
Copy link
Contributor

Choose a reason for hiding this comment

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

And convert MB to bytes, correct?


##### Fixes :wrench:

##### Deprecated :hourglass_flowing_sand:

- `Cesium3DTileset.maximumMemoryUsage` has been deprecated in CesiumJS 1.107. It will be removed in 1.110. Use `Cesium3DTileset.cacheBytes` and `Cesium3DTileset.maximumCacheOverflowBytes` instead. [#11310](https://github.com/CesiumGS/cesium/pull/11310)
ggetz marked this conversation as resolved.
Show resolved Hide resolved

### 1.106.1 - 2023-06-02

This is an npm-only release to fix a dependency issue published in 1.106
Expand Down
9 changes: 4 additions & 5 deletions packages/engine/Source/Scene/Cesium3DTile.js
Original file line number Diff line number Diff line change
Expand Up @@ -870,15 +870,15 @@ function isPriorityDeferred(tile, frameState) {
);
const sseRelaxation = tileset.foveatedInterpolationCallback(
tileset.foveatedMinimumScreenSpaceErrorRelaxation,
tileset.maximumScreenSpaceError,
tileset.memoryAdjustedScreenSpaceError,
jjhembd marked this conversation as resolved.
Show resolved Hide resolved
normalizedFoveatedFactor
);
const sse =
tile._screenSpaceError === 0.0 && defined(tile.parent)
? tile.parent._screenSpaceError * 0.5
: tile._screenSpaceError;

return tileset.maximumScreenSpaceError - sseRelaxation <= sse;
return tileset.memoryAdjustedScreenSpaceError - sseRelaxation <= sse;
}

const scratchJulianDate = new JulianDate();
Expand Down Expand Up @@ -957,12 +957,11 @@ function isPriorityProgressiveResolution(tileset, tile) {
return false;
}

const maximumScreenSpaceError = tileset.memoryAdjustedScreenSpaceError;
let isProgressiveResolutionTile =
tile._screenSpaceErrorProgressiveResolution >
tileset._maximumScreenSpaceError; // Mark non-SSE leaves
tile._screenSpaceErrorProgressiveResolution > maximumScreenSpaceError; // Mark non-SSE leaves
tile._priorityProgressiveResolutionScreenSpaceErrorLeaf = false; // Needed for skipLOD
const parent = tile.parent;
const maximumScreenSpaceError = tileset._maximumScreenSpaceError;
const tilePasses =
tile._screenSpaceErrorProgressiveResolution <= maximumScreenSpaceError;
const parentFails =
Expand Down
193 changes: 183 additions & 10 deletions packages/engine/Source/Scene/Cesium3DTileset.js
Copy link
Contributor

Choose a reason for hiding this comment

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

Reminder to add some unit tests

Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ import Cesium3DTilesetSkipTraversal from "./Cesium3DTilesetSkipTraversal.js";
* @property {Axis} [modelForwardAxis=Axis.X] Which axis is considered forward when loading models for tile contents.
* @property {ShadowMode} [shadows=ShadowMode.ENABLED] Determines whether the tileset casts or receives shadows from light sources.
* @property {number} [maximumScreenSpaceError=16] The maximum screen space error used to drive level of detail refinement.
* @property {number} [maximumMemoryUsage=512] The maximum amount of memory in MB that can be used by the tileset.
* @property {number} [maximumMemoryUsage=512] The maximum amount of memory in MB that can be used by the tileset. Deprecated.
* @property {number} [cacheBytes=536870912] The size (in bytes) to which the tile cache will be trimmed, if the cache contains tiles not needed for the current view.
* @property {number} [maximumCacheOverflowBytes=536870912] The maximum additional memory (in bytes) to allow for cache headroom, if more than {@link Cesium3DTileset#cacheBytes} are needed for the current view.
* @property {boolean} [cullWithChildrenBounds=true] Optimization option. Whether to cull tiles using the union of their children bounding volumes.
* @property {boolean} [cullRequestsWhileMoving=true] Optimization option. Don't request tiles that will likely be unused when they come back because of the camera's movement. This optimization only applies to stationary tilesets.
* @property {number} [cullRequestsWhileMovingMultiplier=60.0] Optimization option. Multiplier used in culling requests while moving. Larger is more aggressive culling, smaller less aggressive culling.
Expand Down Expand Up @@ -221,7 +223,33 @@ function Cesium3DTileset(options) {
options.maximumScreenSpaceError,
16
);
this._maximumMemoryUsage = defaultValue(options.maximumMemoryUsage, 512);
this._memoryAdjustedScreenSpaceError = this._maximumScreenSpaceError;

let defaultCacheBytes = 512 * 1024 * 1024;
if (defined(options.maximumMemoryUsage)) {
deprecationWarning(
"Cesium3DTileset.maximumMemoryUsage",
"Cesium3DTileset.maximumMemoryUsage was deprecated in CesiumJS 1.107. It will be removed in CesiumJS 1.110. Use Cesium3DTileset.cacheBytes instead."
);
defaultCacheBytes = options.maximumMemoryUsage * 1024 * 1024;
}
this._cacheBytes = defaultValue(options.cacheBytes, defaultCacheBytes);
ggetz marked this conversation as resolved.
Show resolved Hide resolved
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number.greaterThanOrEquals("cacheBytes", this._cacheBytes, 0);
//>>includeEnd('debug');

const maximumCacheOverflowBytes = defaultValue(
options.maximumCacheOverflowBytes,
512 * 1024 * 1024
);
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number.greaterThanOrEquals(
"maximumCacheOverflowBytes",
maximumCacheOverflowBytes,
0
);
//>>includeEnd('debug');
this._maximumCacheOverflowBytes = maximumCacheOverflowBytes;

this._styleEngine = new Cesium3DTileStyleEngine();
this._styleApplied = false;
Expand Down Expand Up @@ -590,7 +618,7 @@ function Cesium3DTileset(options) {
* console.log('A tile was unloaded from the cache.');
* });
*
* @see Cesium3DTileset#maximumMemoryUsage
* @see Cesium3DTileset#cacheBytes
* @see Cesium3DTileset#trimLoadedTiles
*/
this.tileUnload = new Event();
Expand Down Expand Up @@ -1516,6 +1544,7 @@ Object.defineProperties(Cesium3DTileset.prototype, {
//>>includeEnd('debug');

this._maximumScreenSpaceError = value;
this._memoryAdjustedScreenSpaceError = value;
},
},

Expand Down Expand Up @@ -1545,17 +1574,122 @@ Object.defineProperties(Cesium3DTileset.prototype, {
*
* @exception {DeveloperError} <code>maximumMemoryUsage</code> must be greater than or equal to zero.
* @see Cesium3DTileset#totalMemoryUsageInBytes
*
* @deprecated
*/
maximumMemoryUsage: {
Copy link
Contributor

Choose a reason for hiding this comment

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

Several tests should be updated to use cacheBytes.

get: function () {
deprecationWarning(
"Cesium3DTileset.maximumMemoryUsage",
"Cesium3DTileset.maximumMemoryUsage was deprecated in CesiumJS 1.107. It will be removed in CesiumJS 1.110. Use Cesium3DTileset.cacheBytes instead."
);
return this._maximumMemoryUsage;
},
set: function (value) {
deprecationWarning(
"Cesium3DTileset.maximumMemoryUsage",
"Cesium3DTileset.maximumMemoryUsage was deprecated in CesiumJS 1.107. It will be removed in CesiumJS 1.110. Use Cesium3DTileset.cacheBytes instead."
);
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number.greaterThanOrEquals("value", value, 0);
//>>includeEnd('debug');

this._maximumMemoryUsage = value;
this._cacheBytes = value * 1024 * 1024;
},
},

/**
* The amount of GPU memory (in bytes) used to cache tiles. This memory usage is estimated from
* geometry, textures, and batch table textures of loaded tiles. For point clouds, this value also
* includes per-point metadata.
* <p>
* Tiles not in view are unloaded to enforce this.
* </p>
* <p>
* If decreasing this value results in unloading tiles, the tiles are unloaded the next frame.
* </p>
* <p>
* If tiles sized more than <code>cacheBytes</code> are needed to meet the
* desired screen space error, determined by {@link Cesium3DTileset#maximumScreenSpaceError},
* for the current view, then the memory usage of the tiles loaded will exceed
* <code>cacheBytes</code> by up to <code>maximumCacheOverflowBytes</code>.
* For example, if <code>cacheBytes</code> is 500000, but 600000 bytes
* of tiles are needed to meet the screen space error, then 600000 bytes of tiles
* may be loaded (if <code>maximumCacheOverflowBytes</code> is at least 100000).
* When these tiles go out of view, they will be unloaded.
* </p>
*
* @memberof Cesium3DTileset.prototype
*
* @type {number}
* @default 536870912
*
* @exception {DeveloperError} <code>cacheBytes</code> must be typeof 'number' and greater than or equal to 0
* @see Cesium3DTileset#totalMemoryUsageInBytes
*/
cacheBytes: {
get: function () {
return this._cacheBytes;
},
set: function (value) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number.greaterThanOrEquals("value", value, 0);
//>>includeEnd('debug');

this._cacheBytes = value;
},
},

/**
* The maximum additional amount of GPU memory (in bytes) that will be used to cache tiles.
* <p>
* If tiles sized more than <code>cacheBytes</code> plus <code>maximumCacheOverflowBytes</code>
* are needed to meet the desired screen space error, determined by
* {@link Cesium3DTileset#maximumScreenSpaceError} for the current view, then
* {@link Cesium3DTileset#memoryAdjustedScreenSpaceError} will be adjusted
* until the tiles required to meet the adjusted screen space error use less
* than <code>cacheBytes</code> plus <code>maximumCacheOverflowBytes</code>.
* </p>
*
* @memberof Cesium3DTileset.prototype
*
* @type {number}
* @default 536870912
*
* @exception {DeveloperError} <code>maximumCacheOverflowBytes</code> must be typeof 'number' and greater than or equal to 0
* @see Cesium3DTileset#totalMemoryUsageInBytes
*/
maximumCacheOverflowBytes: {
get: function () {
return this._maximumCacheOverflowBytes;
},
set: function (value) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number.greaterThanOrEquals("value", value, 0);
//>>includeEnd('debug');

this._maximumCacheOverflowBytes = value;
},
},

/**
* If loading the level of detail required by @{link Cesium3DTileset#maximumScreenSpaceError}
* results in the memory usage exceeding @{link Cesium3DTileset#cacheBytes}
* plus @{link Cesium3DTileset#maximumCacheOverflowBytes}, level of detail refinement
* will instead use this (larger) adjusted screen space error to achieve the
* best possible visual quality within the available memory
*
* @memberof Cesium3DTileset.prototype
*
* @type {number}
* @readonly
*
* @private
*/
memoryAdjustedScreenSpaceError: {
get: function () {
return this._memoryAdjustedScreenSpaceError;
},
},

Expand Down Expand Up @@ -1665,7 +1799,7 @@ Object.defineProperties(Cesium3DTileset.prototype, {
* @type {number}
* @readonly
*
* @see Cesium3DTileset#maximumMemoryUsage
* @see Cesium3DTileset#cacheBytes
*/
totalMemoryUsageInBytes: {
get: function () {
Expand Down Expand Up @@ -2524,7 +2658,7 @@ function requestContent(tileset, tile) {
tileset._requestedTilesInFlight.push(tile);
}

function sortRequestByPriority(a, b) {
function sortTilesByPriority(a, b) {
return a._priority - b._priority;
}

Expand Down Expand Up @@ -2627,7 +2761,7 @@ function cancelOutOfViewRequests(tileset, frameState) {
*/
function requestTiles(tileset) {
const requestedTiles = tileset._requestedTiles;
requestedTiles.sort(sortRequestByPriority);
requestedTiles.sort(sortTilesByPriority);
for (let i = 0; i < requestedTiles.length; ++i) {
requestContent(tileset, requestedTiles[i]);
}
Expand Down Expand Up @@ -2694,10 +2828,18 @@ function filterProcessingQueue(tileset) {
function processTiles(tileset, frameState) {
filterProcessingQueue(tileset);
const tiles = tileset._processingQueue;
const statistics = tileset._statistics;
let tile;

const { cacheBytes, maximumCacheOverflowBytes, statistics } = tileset;
const cacheByteLimit = cacheBytes + maximumCacheOverflowBytes;

let memoryExceeded = false;
for (let i = 0; i < tiles.length; ++i) {
tile = tiles[i];
if (tileset.totalMemoryUsageInBytes > cacheByteLimit) {
memoryExceeded = true;
break;
}

const tile = tiles[i];
try {
tile.process(tileset, frameState);

Expand All @@ -2710,6 +2852,37 @@ function processTiles(tileset, frameState) {
handleTileFailure(error, tileset, tile);
}
}

if (tileset.totalMemoryUsageInBytes < cacheBytes) {
decreaseScreenSpaceError(tileset);
} else if (memoryExceeded && tiles.length > 0) {
increaseScreenSpaceError(tileset);
}
}

function increaseScreenSpaceError(tileset) {
//>>includeStart('debug', pragmas.debug);
oneTimeWarning(
"increase-screenSpaceError",
`The tiles needed to meet maximumScreenSpaceError would use more memory than allocated for this tileset.
The tileset will be rendered with a larger screen space error (see memoryAdjustedScreenSpaceError).
Consider using larger values for cacheBytes and maximumCacheOverflowBytes.`
);
//>>includeEnd('debug');

tileset._memoryAdjustedScreenSpaceError *= 1.02;
const tiles = tileset._processingQueue;
for (let i = 0; i < tiles.length; ++i) {
tiles[i].updatePriority();
}
tiles.sort(sortTilesByPriority);
}

function decreaseScreenSpaceError(tileset) {
tileset._memoryAdjustedScreenSpaceError = Math.max(
tileset.memoryAdjustedScreenSpaceError / 1.02,
tileset.maximumScreenSpaceError
);
}

const scratchCartesian = new Cartesian3();
Expand Down Expand Up @@ -3057,7 +3230,7 @@ function destroyTile(tileset, tile) {
/**
* Unloads all tiles that weren't selected the previous frame. This can be used to
* explicitly manage the tile cache and reduce the total number of tiles loaded below
* {@link Cesium3DTileset#maximumMemoryUsage}.
* {@link Cesium3DTileset#cacheBytes}.
* <p>
* Tile unloads occur at the next frame to keep all the WebGL delete calls
* within the render loop.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Cesium3DTilesetBaseTraversal.selectTiles = function (tileset, frameState) {

if (
root.getScreenSpaceError(frameState, true) <=
tileset._maximumScreenSpaceError
tileset.memoryAdjustedScreenSpaceError
) {
return;
}
Expand Down
4 changes: 1 addition & 3 deletions packages/engine/Source/Scene/Cesium3DTilesetCache.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,6 @@ Cesium3DTilesetCache.prototype.unloadTiles = function (

const list = this._list;

const 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.
//
Expand All @@ -68,7 +66,7 @@ Cesium3DTilesetCache.prototype.unloadTiles = function (
let node = list.head;
while (
node !== sentinel &&
(tileset.totalMemoryUsageInBytes > maximumMemoryUsageInBytes || trimTiles)
(tileset.totalMemoryUsageInBytes > tileset.cacheBytes || trimTiles)
) {
const tile = node.item;
node = node.next;
Expand Down
7 changes: 5 additions & 2 deletions packages/engine/Source/Scene/Cesium3DTilesetSkipTraversal.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Cesium3DTilesetSkipTraversal.selectTiles = function (tileset, frameState) {

if (
root.getScreenSpaceError(frameState, true) <=
tileset._maximumScreenSpaceError
tileset.memoryAdjustedScreenSpaceError
) {
return;
}
Expand Down Expand Up @@ -267,7 +267,10 @@ function executeTraversal(root, frameState) {
const { tileset } = root;
const baseScreenSpaceError = tileset.immediatelyLoadDesiredLevelOfDetail
? Number.MAX_VALUE
: Math.max(tileset.baseScreenSpaceError, tileset.maximumScreenSpaceError);
: Math.max(
tileset.baseScreenSpaceError,
tileset.memoryAdjustedScreenSpaceError
);
const {
canTraverse,
loadTile,
Expand Down
4 changes: 2 additions & 2 deletions packages/engine/Source/Scene/Cesium3DTilesetTraversal.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Cesium3DTilesetTraversal.canTraverse = function (tile) {
// Don't traverse if the subtree is expired because it will be destroyed
return !tile.contentExpired;
}
return tile._screenSpaceError > tile.tileset._maximumScreenSpaceError;
return tile._screenSpaceError > tile.tileset.memoryAdjustedScreenSpaceError;
};

/**
Expand Down Expand Up @@ -260,7 +260,7 @@ function meetsScreenSpaceErrorEarly(tile, frameState) {
// Use parent's geometric error with child's box to see if the tile already meet the SSE
return (
tile.getScreenSpaceError(frameState, true) <=
tileset._maximumScreenSpaceError
tileset.memoryAdjustedScreenSpaceError
);
}

Expand Down
Loading