diff --git a/src/core/renderer/API.md b/src/core/renderer/API.md index aec4eea08..93a6fecd7 100644 --- a/src/core/renderer/API.md +++ b/src/core/renderer/API.md @@ -890,6 +890,19 @@ potentially causing brief gaps during rapid movement. Only applies when `optimizedLoadStrategy` is enabled. +### .loadParents + +```js +loadParents: boolean +``` + +**Experimental.** When `true`, parent tiles are queued for download and displayed as a +fallback while children are loading — similar to the behavior of the standard load +strategy. Increases memory usage but provides smoother transitions on first load. + +Only applies when `optimizedLoadStrategy` is enabled. + + ### .maxTilesProcessed ```js diff --git a/src/core/renderer/tiles/TilesRendererBase.d.ts b/src/core/renderer/tiles/TilesRendererBase.d.ts index 2cc6be490..d73c4638e 100644 --- a/src/core/renderer/tiles/TilesRendererBase.d.ts +++ b/src/core/renderer/tiles/TilesRendererBase.d.ts @@ -36,6 +36,7 @@ export class TilesRendererBase { // load internal tile sets first return a.internal.hasUnrenderableContent ? 1 : - 1; + } else if ( a.traversal.error !== b.traversal.error ) { + + // load the tile with the higher error + return a.traversal.error > b.traversal.error ? 1 : - 1; + } else if ( a.traversal.distanceFromCamera !== b.traversal.distanceFromCamera ) { // load closer tiles first return a.traversal.distanceFromCamera > b.traversal.distanceFromCamera ? - 1 : 1; + } else if ( a.internal.depthFromRenderedParent !== b.internal.depthFromRenderedParent ) { + + // when distance is equal (e.g. camera inside bounds), load shallower tiles first + return a.internal.depthFromRenderedParent > b.internal.depthFromRenderedParent ? - 1 : 1; + } return 0; @@ -569,6 +579,16 @@ export class TilesRendererBase { */ this.loadSiblings = true; + /** + * **Experimental.** When `true`, parent tiles are queued for download and displayed as a + * fallback while children are loading — similar to the behavior of the standard load + * strategy. Increases memory usage but provides smoother transitions on first load. + * + * Only applies when `optimizedLoadStrategy` is enabled. + * @type {boolean} + */ + this.loadParents = false; + /** * The number of tiles to process immediately when traversing the tile set to determine * what to render. Lower numbers prevent frame hiccups caused by processing too many tiles @@ -766,7 +786,7 @@ export class TilesRendererBase { update() { // load root - const { lruCache, usedSet, stats, root, downloadQueue, parseQueue, processNodeQueue, optimizedLoadStrategy } = this; + const { lruCache, usedSet, stats, root, downloadQueue, parseQueue, processNodeQueue, optimizedLoadStrategy, loadParents } = this; if ( this.rootLoadingState === UNLOADED ) { this.rootLoadingState = LOADING; @@ -864,7 +884,7 @@ export class TilesRendererBase { usedSet.clear(); // assign the correct callbacks - const priorityCallback = optimizedLoadStrategy ? optimizedPriorityCallback : defaultPriorityCallback; + const priorityCallback = ( optimizedLoadStrategy && ! loadParents ) ? optimizedPriorityCallback : defaultPriorityCallback; downloadQueue.priorityCallback = priorityCallback; parseQueue.priorityCallback = priorityCallback; diff --git a/src/core/renderer/tiles/optimizedTraverseFunctions.js b/src/core/renderer/tiles/optimizedTraverseFunctions.js index 1725e8830..702028bb2 100644 --- a/src/core/renderer/tiles/optimizedTraverseFunctions.js +++ b/src/core/renderer/tiles/optimizedTraverseFunctions.js @@ -61,6 +61,7 @@ function resetFrameState( tile, renderer ) { tile.traversal.error = Infinity; tile.traversal.distanceFromCamera = Infinity; tile.traversal.allChildrenReady = false; + tile.traversal.allChildrenLoaded = false; tile.traversal.kicked = false; tile.traversal.allUsedChildrenProcessed = false; @@ -308,6 +309,31 @@ function markUsedSetLeaves( tile, renderer ) { } + // compute content-readiness so markVisibleTiles can stop traversal at this tile when + // loadParents is enabled and children haven't finished loading their content yet. + // only computed when anyChildrenUsed — leaf tiles retain allChildrenLoaded = false + // (from resetFrameState) so parents correctly see them as not yet loaded. + let allChildrenLoaded = true; + for ( let i = 0, l = children.length; i < l; i ++ ) { + + const c = children[ i ]; + if ( isUsedThisFrame( c, frameCount ) ) { + + const childCanDisplay = ! canUnconditionallyRefine( c ); + const childContentReady = ! c.internal.hasContent || isDownloadFinished( c.internal.loadingState ); + const childIsReady = ( childCanDisplay && childContentReady ) || c.traversal.allChildrenLoaded; + if ( ! childIsReady ) { + + allChildrenLoaded = false; + + } + + } + + } + + tile.traversal.allChildrenLoaded = allChildrenLoaded; + } // save whether any children are processed @@ -340,6 +366,16 @@ function markVisibleTiles( tile, renderer ) { const hasContent = tile.internal.hasContent; const loadedContent = isDownloadFinished( tile.internal.loadingState ) && hasContent; const children = tile.children; + + // When loading parent tiles as fallbacks: if children aren't content-ready yet, mark this tile + // as a leaf so it is displayed as a placeholder while children load (mirrors legacy behavior). + // allChildrenLoaded was computed bottom-up in markUsedSetLeaves so it can be checked before recursing. + if ( renderer.loadParents && ! tile.traversal.allChildrenLoaded && ! canUnconditionallyRefine( tile ) ) { + + tile.traversal.isLeaf = true; + + } + if ( tile.traversal.isLeaf ) { // if we're allowed to stop at this tile then mark it as active and allow any previously active tiles to @@ -440,6 +476,15 @@ function toggleTiles( tile, renderer ) { } + // when loading parent tiles as fallbacks, keep all used tiles downloaded + // regardless of active state so they are available to display while children load + if ( renderer.loadParents && tile.internal.hasContent ) { + + renderer.markTileUsed( tile ); + renderer.queueTileForDownload( tile ); + + } + // keep tiles with virtual children retained in the LRU cache so the content is // available to regenerate virtual children if the overlay configuration changes. if ( tile.internal.virtualChildCount > 0 && tile.internal.hasContent ) {