123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469 |
- /*global define*/
- define([
- '../Core/defaultValue',
- '../Core/defined',
- '../Core/defineProperties',
- '../Core/DeveloperError',
- '../Core/getTimestamp',
- '../Core/Queue',
- '../Core/Visibility',
- './QuadtreeOccluders',
- './QuadtreeTile',
- './QuadtreeTileLoadState',
- './SceneMode',
- './TileReplacementQueue'
- ], function(
- defaultValue,
- defined,
- defineProperties,
- DeveloperError,
- getTimestamp,
- Queue,
- Visibility,
- QuadtreeOccluders,
- QuadtreeTile,
- QuadtreeTileLoadState,
- SceneMode,
- TileReplacementQueue) {
- "use strict";
- /**
- * Renders massive sets of data by utilizing level-of-detail and culling. The globe surface is divided into
- * a quadtree of tiles with large, low-detail tiles at the root and small, high-detail tiles at the leaves.
- * The set of tiles to render is selected by projecting an estimate of the geometric error in a tile onto
- * the screen to estimate screen-space error, in pixels, which must be below a user-specified threshold.
- * The actual content of the tiles is arbitrary and is specified using a {@link QuadtreeTileProvider}.
- *
- * @alias QuadtreePrimitive
- * @constructor
- * @private
- *
- * @param {QuadtreeTileProvider} options.tileProvider The tile provider that loads, renders, and estimates
- * the distance to individual tiles.
- * @param {Number} [options.maximumScreenSpaceError=2] The maximum screen-space error, in pixels, that is allowed.
- * A higher maximum error will render fewer tiles and improve performance, while a lower
- * value will improve visual quality.
- * @param {Number} [options.tileCacheSize=100] The maximum number of tiles that will be retained in the tile cache.
- * Note that tiles will never be unloaded if they were used for rendering the last
- * frame, so the actual number of resident tiles may be higher. The value of
- * this property will not affect visual quality.
- */
- var QuadtreePrimitive = function QuadtreePrimitive(options) {
- //>>includeStart('debug', pragmas.debug);
- if (!defined(options) || !defined(options.tileProvider)) {
- throw new DeveloperError('options.tileProvider is required.');
- }
- if (defined(options.tileProvider.quadtree)) {
- throw new DeveloperError('A QuadtreeTileProvider can only be used with a single QuadtreePrimitive');
- }
- //>>includeEnd('debug');
- this._tileProvider = options.tileProvider;
- this._tileProvider.quadtree = this;
- this._debug = {
- enableDebugOutput : false,
- maxDepth : 0,
- tilesVisited : 0,
- tilesCulled : 0,
- tilesRendered : 0,
- tilesWaitingForChildren : 0,
- lastMaxDepth : -1,
- lastTilesVisited : -1,
- lastTilesCulled : -1,
- lastTilesRendered : -1,
- lastTilesWaitingForChildren : -1,
- suspendLodUpdate : false
- };
- var tilingScheme = this._tileProvider.tilingScheme;
- var ellipsoid = tilingScheme.ellipsoid;
- this._tilesToRender = [];
- this._tileTraversalQueue = new Queue();
- this._tileLoadQueue = [];
- this._tileReplacementQueue = new TileReplacementQueue();
- this._levelZeroTiles = undefined;
- this._levelZeroTilesReady = false;
- this._loadQueueTimeSlice = 5.0;
- /**
- * Gets or sets the maximum screen-space error, in pixels, that is allowed.
- * A higher maximum error will render fewer tiles and improve performance, while a lower
- * value will improve visual quality.
- * @type {Number}
- * @default 2
- */
- this.maximumScreenSpaceError = defaultValue(options.maximumScreenSpaceError, 2);
- /**
- * Gets or sets the maximum number of tiles that will be retained in the tile cache.
- * Note that tiles will never be unloaded if they were used for rendering the last
- * frame, so the actual number of resident tiles may be higher. The value of
- * this property will not affect visual quality.
- * @type {Number}
- * @default 100
- */
- this.tileCacheSize = defaultValue(options.tileCacheSize, 100);
- this._occluders = new QuadtreeOccluders({
- ellipsoid : ellipsoid
- });
- };
- defineProperties(QuadtreePrimitive.prototype, {
- /**
- * Gets the provider of {@link QuadtreeTile} instances for this quadtree.
- * @type {QuadtreeTile}
- * @memberof QuadtreePrimitive.prototype
- */
- tileProvider : {
- get : function() {
- return this._tileProvider;
- }
- }
- });
- /**
- * Invalidates and frees all the tiles in the quadtree. The tiles must be reloaded
- * before they can be displayed.
- *
- * @memberof QuadtreePrimitive
- */
- QuadtreePrimitive.prototype.invalidateAllTiles = function() {
- // Clear the replacement queue
- var replacementQueue = this._tileReplacementQueue;
- replacementQueue.head = undefined;
- replacementQueue.tail = undefined;
- replacementQueue.count = 0;
- // Free and recreate the level zero tiles.
- var levelZeroTiles = this._levelZeroTiles;
- if (defined(levelZeroTiles)) {
- for (var i = 0; i < levelZeroTiles.length; ++i) {
- levelZeroTiles[i].freeResources();
- }
- }
- this._levelZeroTiles = undefined;
- };
- /**
- * Invokes a specified function for each {@link QuadtreeTile} that is partially
- * or completely loaded.
- *
- * @param {Function} tileFunction The function to invoke for each loaded tile. The
- * function is passed a reference to the tile as its only parameter.
- */
- QuadtreePrimitive.prototype.forEachLoadedTile = function(tileFunction) {
- var tile = this._tileReplacementQueue.head;
- while (defined(tile)) {
- if (tile.state !== QuadtreeTileLoadState.START) {
- tileFunction(tile);
- }
- tile = tile.replacementNext;
- }
- };
- /**
- * Invokes a specified function for each {@link QuadtreeTile} that was rendered
- * in the most recent frame.
- *
- * @param {Function} tileFunction The function to invoke for each rendered tile. The
- * function is passed a reference to the tile as its only parameter.
- */
- QuadtreePrimitive.prototype.forEachRenderedTile = function(tileFunction) {
- var tilesRendered = this._tilesToRender;
- for (var i = 0, len = tilesRendered.length; i < len; ++i) {
- tileFunction(tilesRendered[i]);
- }
- };
- /**
- * Updates the primitive.
- *
- * @param {Context} context The rendering context to use.
- * @param {FrameState} frameState The state of the current frame.
- * @param {DrawCommand[]} commandList The list of draw commands. The primitive will usually add
- * commands to this array during the update call.
- */
- QuadtreePrimitive.prototype.update = function(context, frameState, commandList) {
- this._tileProvider.beginUpdate(context, frameState, commandList);
- selectTilesForRendering(this, context, frameState);
- processTileLoadQueue(this, context, frameState);
- createRenderCommandsForSelectedTiles(this, context, frameState, commandList);
- this._tileProvider.endUpdate(context, frameState, commandList);
- };
- /**
- * Returns true if this object was destroyed; otherwise, false.
- * <br /><br />
- * If this object was destroyed, it should not be used; calling any function other than
- * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
- *
- * @memberof QuadtreePrimitive
- *
- * @returns {Boolean} True if this object was destroyed; otherwise, false.
- *
- * @see QuadtreePrimitive#destroy
- */
- QuadtreePrimitive.prototype.isDestroyed = function() {
- return false;
- };
- /**
- * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
- * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
- * <br /><br />
- * Once an object is destroyed, it should not be used; calling any function other than
- * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
- * assign the return value (<code>undefined</code>) to the object as done in the example.
- *
- * @memberof QuadtreePrimitive
- *
- * @returns {undefined}
- *
- * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
- *
- * @see QuadtreePrimitive#isDestroyed
- *
- * @example
- * primitive = primitive && primitive.destroy();
- */
- QuadtreePrimitive.prototype.destroy = function() {
- this._tileProvider = this._tileProvider && this._tileProvider.destroy();
- };
- function selectTilesForRendering(primitive, context, frameState) {
- var debug = primitive._debug;
- if (debug.suspendLodUpdate) {
- return;
- }
- var i;
- var len;
- // Clear the render list.
- var tilesToRender = primitive._tilesToRender;
- tilesToRender.length = 0;
- var traversalQueue = primitive._tileTraversalQueue;
- traversalQueue.clear();
- debug.maxDepth = 0;
- debug.tilesVisited = 0;
- debug.tilesCulled = 0;
- debug.tilesRendered = 0;
- debug.tilesWaitingForChildren = 0;
- primitive._tileLoadQueue.length = 0;
- primitive._tileReplacementQueue.markStartOfRenderFrame();
- // We can't render anything before the level zero tiles exist.
- if (!defined(primitive._levelZeroTiles)) {
- if (primitive._tileProvider.ready) {
- var terrainTilingScheme = primitive._tileProvider.tilingScheme;
- primitive._levelZeroTiles = QuadtreeTile.createLevelZeroTiles(terrainTilingScheme);
- } else {
- // Nothing to do until the provider is ready.
- return;
- }
- }
- primitive._occluders.ellipsoid.cameraPosition = frameState.camera.positionWC;
- var tileProvider = primitive._tileProvider;
- var occluders = primitive._occluders;
- var tile;
- // Enqueue the root tiles that are renderable and visible.
- var levelZeroTiles = primitive._levelZeroTiles;
- for (i = 0, len = levelZeroTiles.length; i < len; ++i) {
- tile = levelZeroTiles[i];
- primitive._tileReplacementQueue.markTileRendered(tile);
- if (tile.needsLoading) {
- queueTileLoad(primitive, tile);
- }
- if (tile.renderable && tileProvider.computeTileVisibility(tile, frameState, occluders) !== Visibility.NONE) {
- traversalQueue.enqueue(tile);
- } else {
- ++debug.tilesCulled;
- if (!tile.renderable) {
- ++debug.tilesWaitingForChildren;
- }
- }
- }
- // Traverse the tiles in breadth-first order.
- // This ordering allows us to load bigger, lower-detail tiles before smaller, higher-detail ones.
- // This maximizes the average detail across the scene and results in fewer sharp transitions
- // between very different LODs.
- while (defined((tile = traversalQueue.dequeue()))) {
- ++debug.tilesVisited;
- primitive._tileReplacementQueue.markTileRendered(tile);
- if (tile.level > debug.maxDepth) {
- debug.maxDepth = tile.level;
- }
- // There are a few different algorithms we could use here.
- // This one doesn't load children unless we refine to them.
- // We may want to revisit this in the future.
- if (screenSpaceError(primitive, context, frameState, tile) < primitive.maximumScreenSpaceError) {
- // This tile meets SSE requirements, so render it.
- addTileToRenderList(primitive, tile);
- } else if (queueChildrenLoadAndDetermineIfChildrenAreAllRenderable(primitive, tile)) {
- // SSE is not good enough and children are loaded, so refine.
- var children = tile.children;
- // PERFORMANCE_IDEA: traverse children front-to-back so we can avoid sorting by distance later.
- for (i = 0, len = children.length; i < len; ++i) {
- if (tileProvider.computeTileVisibility(children[i], frameState, occluders) !== Visibility.NONE) {
- traversalQueue.enqueue(children[i]);
- } else {
- ++debug.tilesCulled;
- }
- }
- } else {
- ++debug.tilesWaitingForChildren;
- // SSE is not good enough but not all children are loaded, so render this tile anyway.
- addTileToRenderList(primitive, tile);
- }
- }
- if (debug.enableDebugOutput) {
- if (debug.tilesVisited !== debug.lastTilesVisited ||
- debug.tilesRendered !== debug.lastTilesRendered ||
- debug.tilesCulled !== debug.lastTilesCulled ||
- debug.maxDepth !== debug.lastMaxDepth ||
- debug.tilesWaitingForChildren !== debug.lastTilesWaitingForChildren) {
- /*global console*/
- console.log('Visited ' + debug.tilesVisited + ', Rendered: ' + debug.tilesRendered + ', Culled: ' + debug.tilesCulled + ', Max Depth: ' + debug.maxDepth + ', Waiting for children: ' + debug.tilesWaitingForChildren);
- debug.lastTilesVisited = debug.tilesVisited;
- debug.lastTilesRendered = debug.tilesRendered;
- debug.lastTilesCulled = debug.tilesCulled;
- debug.lastMaxDepth = debug.maxDepth;
- debug.lastTilesWaitingForChildren = debug.tilesWaitingForChildren;
- }
- }
- }
- function screenSpaceError(primitive, context, frameState, tile) {
- if (frameState.mode === SceneMode.SCENE2D) {
- return screenSpaceError2D(primitive, context, frameState, tile);
- }
- var maxGeometricError = primitive._tileProvider.getLevelMaximumGeometricError(tile.level);
- var distance = primitive._tileProvider.computeDistanceToTile(tile, frameState);
- tile._distance = distance;
- var height = context.drawingBufferHeight;
- var camera = frameState.camera;
- var frustum = camera.frustum;
- var fovy = frustum.fovy;
- // PERFORMANCE_IDEA: factor out stuff that's constant across tiles.
- return (maxGeometricError * height) / (2 * distance * Math.tan(0.5 * fovy));
- }
- function screenSpaceError2D(primitive, context, frameState, tile) {
- var camera = frameState.camera;
- var frustum = camera.frustum;
- var width = context.drawingBufferWidth;
- var height = context.drawingBufferHeight;
- var maxGeometricError = primitive._tileProvider.getLevelMaximumGeometricError(tile.level);
- var pixelSize = Math.max(frustum.top - frustum.bottom, frustum.right - frustum.left) / Math.max(width, height);
- return maxGeometricError / pixelSize;
- }
- function addTileToRenderList(primitive, tile) {
- primitive._tilesToRender.push(tile);
- ++primitive._debug.tilesRendered;
- }
- function queueChildrenLoadAndDetermineIfChildrenAreAllRenderable(primitive, tile) {
- var allRenderable = true;
- var allUpsampledOnly = true;
- var children = tile.children;
- for (var i = 0, len = children.length; i < len; ++i) {
- var child = children[i];
- primitive._tileReplacementQueue.markTileRendered(child);
- allUpsampledOnly = allUpsampledOnly && child.upsampledFromParent;
- allRenderable = allRenderable && child.renderable;
- if (child.needsLoading) {
- queueTileLoad(primitive, child);
- }
- }
- if (!allRenderable) {
- ++primitive._debug.tilesWaitingForChildren;
- }
- // If all children are upsampled from this tile, we just render this tile instead of its children.
- return allRenderable && !allUpsampledOnly;
- }
- function queueTileLoad(primitive, tile) {
- primitive._tileLoadQueue.push(tile);
- }
- function processTileLoadQueue(primitive, context, frameState) {
- var tileLoadQueue = primitive._tileLoadQueue;
- var tileProvider = primitive._tileProvider;
- if (tileLoadQueue.length === 0) {
- return;
- }
- // Remove any tiles that were not used this frame beyond the number
- // we're allowed to keep.
- primitive._tileReplacementQueue.trimTiles(primitive.tileCacheSize);
- var startTime = getTimestamp();
- var timeSlice = primitive._loadQueueTimeSlice;
- var endTime = startTime + timeSlice;
- for (var len = tileLoadQueue.length - 1, i = len; i >= 0; --i) {
- var tile = tileLoadQueue[i];
- primitive._tileReplacementQueue.markTileRendered(tile);
- tileProvider.loadTile(context, frameState, tile);
- if (getTimestamp() >= endTime) {
- break;
- }
- }
- }
- function tileDistanceSortFunction(a, b) {
- return a._distance - b._distance;
- }
- function createRenderCommandsForSelectedTiles(primitive, context, frameState, commandList) {
- var tileProvider = primitive._tileProvider;
- var tilesToRender = primitive._tilesToRender;
- tilesToRender.sort(tileDistanceSortFunction);
- for (var i = 0, len = tilesToRender.length; i < len; ++i) {
- tileProvider.showTileThisFrame(tilesToRender[i], context, frameState, commandList);
- }
- }
- return QuadtreePrimitive;
- });
|