QuadtreePrimitive.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. /*global define*/
  2. define([
  3. '../Core/defaultValue',
  4. '../Core/defined',
  5. '../Core/defineProperties',
  6. '../Core/DeveloperError',
  7. '../Core/getTimestamp',
  8. '../Core/Queue',
  9. '../Core/Visibility',
  10. './QuadtreeOccluders',
  11. './QuadtreeTile',
  12. './QuadtreeTileLoadState',
  13. './SceneMode',
  14. './TileReplacementQueue'
  15. ], function(
  16. defaultValue,
  17. defined,
  18. defineProperties,
  19. DeveloperError,
  20. getTimestamp,
  21. Queue,
  22. Visibility,
  23. QuadtreeOccluders,
  24. QuadtreeTile,
  25. QuadtreeTileLoadState,
  26. SceneMode,
  27. TileReplacementQueue) {
  28. "use strict";
  29. /**
  30. * Renders massive sets of data by utilizing level-of-detail and culling. The globe surface is divided into
  31. * a quadtree of tiles with large, low-detail tiles at the root and small, high-detail tiles at the leaves.
  32. * The set of tiles to render is selected by projecting an estimate of the geometric error in a tile onto
  33. * the screen to estimate screen-space error, in pixels, which must be below a user-specified threshold.
  34. * The actual content of the tiles is arbitrary and is specified using a {@link QuadtreeTileProvider}.
  35. *
  36. * @alias QuadtreePrimitive
  37. * @constructor
  38. * @private
  39. *
  40. * @param {QuadtreeTileProvider} options.tileProvider The tile provider that loads, renders, and estimates
  41. * the distance to individual tiles.
  42. * @param {Number} [options.maximumScreenSpaceError=2] The maximum screen-space error, in pixels, that is allowed.
  43. * A higher maximum error will render fewer tiles and improve performance, while a lower
  44. * value will improve visual quality.
  45. * @param {Number} [options.tileCacheSize=100] The maximum number of tiles that will be retained in the tile cache.
  46. * Note that tiles will never be unloaded if they were used for rendering the last
  47. * frame, so the actual number of resident tiles may be higher. The value of
  48. * this property will not affect visual quality.
  49. */
  50. var QuadtreePrimitive = function QuadtreePrimitive(options) {
  51. //>>includeStart('debug', pragmas.debug);
  52. if (!defined(options) || !defined(options.tileProvider)) {
  53. throw new DeveloperError('options.tileProvider is required.');
  54. }
  55. if (defined(options.tileProvider.quadtree)) {
  56. throw new DeveloperError('A QuadtreeTileProvider can only be used with a single QuadtreePrimitive');
  57. }
  58. //>>includeEnd('debug');
  59. this._tileProvider = options.tileProvider;
  60. this._tileProvider.quadtree = this;
  61. this._debug = {
  62. enableDebugOutput : false,
  63. maxDepth : 0,
  64. tilesVisited : 0,
  65. tilesCulled : 0,
  66. tilesRendered : 0,
  67. tilesWaitingForChildren : 0,
  68. lastMaxDepth : -1,
  69. lastTilesVisited : -1,
  70. lastTilesCulled : -1,
  71. lastTilesRendered : -1,
  72. lastTilesWaitingForChildren : -1,
  73. suspendLodUpdate : false
  74. };
  75. var tilingScheme = this._tileProvider.tilingScheme;
  76. var ellipsoid = tilingScheme.ellipsoid;
  77. this._tilesToRender = [];
  78. this._tileTraversalQueue = new Queue();
  79. this._tileLoadQueue = [];
  80. this._tileReplacementQueue = new TileReplacementQueue();
  81. this._levelZeroTiles = undefined;
  82. this._levelZeroTilesReady = false;
  83. this._loadQueueTimeSlice = 5.0;
  84. /**
  85. * Gets or sets the maximum screen-space error, in pixels, that is allowed.
  86. * A higher maximum error will render fewer tiles and improve performance, while a lower
  87. * value will improve visual quality.
  88. * @type {Number}
  89. * @default 2
  90. */
  91. this.maximumScreenSpaceError = defaultValue(options.maximumScreenSpaceError, 2);
  92. /**
  93. * Gets or sets the maximum number of tiles that will be retained in the tile cache.
  94. * Note that tiles will never be unloaded if they were used for rendering the last
  95. * frame, so the actual number of resident tiles may be higher. The value of
  96. * this property will not affect visual quality.
  97. * @type {Number}
  98. * @default 100
  99. */
  100. this.tileCacheSize = defaultValue(options.tileCacheSize, 100);
  101. this._occluders = new QuadtreeOccluders({
  102. ellipsoid : ellipsoid
  103. });
  104. };
  105. defineProperties(QuadtreePrimitive.prototype, {
  106. /**
  107. * Gets the provider of {@link QuadtreeTile} instances for this quadtree.
  108. * @type {QuadtreeTile}
  109. * @memberof QuadtreePrimitive.prototype
  110. */
  111. tileProvider : {
  112. get : function() {
  113. return this._tileProvider;
  114. }
  115. }
  116. });
  117. /**
  118. * Invalidates and frees all the tiles in the quadtree. The tiles must be reloaded
  119. * before they can be displayed.
  120. *
  121. * @memberof QuadtreePrimitive
  122. */
  123. QuadtreePrimitive.prototype.invalidateAllTiles = function() {
  124. // Clear the replacement queue
  125. var replacementQueue = this._tileReplacementQueue;
  126. replacementQueue.head = undefined;
  127. replacementQueue.tail = undefined;
  128. replacementQueue.count = 0;
  129. // Free and recreate the level zero tiles.
  130. var levelZeroTiles = this._levelZeroTiles;
  131. if (defined(levelZeroTiles)) {
  132. for (var i = 0; i < levelZeroTiles.length; ++i) {
  133. levelZeroTiles[i].freeResources();
  134. }
  135. }
  136. this._levelZeroTiles = undefined;
  137. };
  138. /**
  139. * Invokes a specified function for each {@link QuadtreeTile} that is partially
  140. * or completely loaded.
  141. *
  142. * @param {Function} tileFunction The function to invoke for each loaded tile. The
  143. * function is passed a reference to the tile as its only parameter.
  144. */
  145. QuadtreePrimitive.prototype.forEachLoadedTile = function(tileFunction) {
  146. var tile = this._tileReplacementQueue.head;
  147. while (defined(tile)) {
  148. if (tile.state !== QuadtreeTileLoadState.START) {
  149. tileFunction(tile);
  150. }
  151. tile = tile.replacementNext;
  152. }
  153. };
  154. /**
  155. * Invokes a specified function for each {@link QuadtreeTile} that was rendered
  156. * in the most recent frame.
  157. *
  158. * @param {Function} tileFunction The function to invoke for each rendered tile. The
  159. * function is passed a reference to the tile as its only parameter.
  160. */
  161. QuadtreePrimitive.prototype.forEachRenderedTile = function(tileFunction) {
  162. var tilesRendered = this._tilesToRender;
  163. for (var i = 0, len = tilesRendered.length; i < len; ++i) {
  164. tileFunction(tilesRendered[i]);
  165. }
  166. };
  167. /**
  168. * Updates the primitive.
  169. *
  170. * @param {Context} context The rendering context to use.
  171. * @param {FrameState} frameState The state of the current frame.
  172. * @param {DrawCommand[]} commandList The list of draw commands. The primitive will usually add
  173. * commands to this array during the update call.
  174. */
  175. QuadtreePrimitive.prototype.update = function(context, frameState, commandList) {
  176. this._tileProvider.beginUpdate(context, frameState, commandList);
  177. selectTilesForRendering(this, context, frameState);
  178. processTileLoadQueue(this, context, frameState);
  179. createRenderCommandsForSelectedTiles(this, context, frameState, commandList);
  180. this._tileProvider.endUpdate(context, frameState, commandList);
  181. };
  182. /**
  183. * Returns true if this object was destroyed; otherwise, false.
  184. * <br /><br />
  185. * If this object was destroyed, it should not be used; calling any function other than
  186. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  187. *
  188. * @memberof QuadtreePrimitive
  189. *
  190. * @returns {Boolean} True if this object was destroyed; otherwise, false.
  191. *
  192. * @see QuadtreePrimitive#destroy
  193. */
  194. QuadtreePrimitive.prototype.isDestroyed = function() {
  195. return false;
  196. };
  197. /**
  198. * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
  199. * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
  200. * <br /><br />
  201. * Once an object is destroyed, it should not be used; calling any function other than
  202. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  203. * assign the return value (<code>undefined</code>) to the object as done in the example.
  204. *
  205. * @memberof QuadtreePrimitive
  206. *
  207. * @returns {undefined}
  208. *
  209. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  210. *
  211. * @see QuadtreePrimitive#isDestroyed
  212. *
  213. * @example
  214. * primitive = primitive && primitive.destroy();
  215. */
  216. QuadtreePrimitive.prototype.destroy = function() {
  217. this._tileProvider = this._tileProvider && this._tileProvider.destroy();
  218. };
  219. function selectTilesForRendering(primitive, context, frameState) {
  220. var debug = primitive._debug;
  221. if (debug.suspendLodUpdate) {
  222. return;
  223. }
  224. var i;
  225. var len;
  226. // Clear the render list.
  227. var tilesToRender = primitive._tilesToRender;
  228. tilesToRender.length = 0;
  229. var traversalQueue = primitive._tileTraversalQueue;
  230. traversalQueue.clear();
  231. debug.maxDepth = 0;
  232. debug.tilesVisited = 0;
  233. debug.tilesCulled = 0;
  234. debug.tilesRendered = 0;
  235. debug.tilesWaitingForChildren = 0;
  236. primitive._tileLoadQueue.length = 0;
  237. primitive._tileReplacementQueue.markStartOfRenderFrame();
  238. // We can't render anything before the level zero tiles exist.
  239. if (!defined(primitive._levelZeroTiles)) {
  240. if (primitive._tileProvider.ready) {
  241. var terrainTilingScheme = primitive._tileProvider.tilingScheme;
  242. primitive._levelZeroTiles = QuadtreeTile.createLevelZeroTiles(terrainTilingScheme);
  243. } else {
  244. // Nothing to do until the provider is ready.
  245. return;
  246. }
  247. }
  248. primitive._occluders.ellipsoid.cameraPosition = frameState.camera.positionWC;
  249. var tileProvider = primitive._tileProvider;
  250. var occluders = primitive._occluders;
  251. var tile;
  252. // Enqueue the root tiles that are renderable and visible.
  253. var levelZeroTiles = primitive._levelZeroTiles;
  254. for (i = 0, len = levelZeroTiles.length; i < len; ++i) {
  255. tile = levelZeroTiles[i];
  256. primitive._tileReplacementQueue.markTileRendered(tile);
  257. if (tile.needsLoading) {
  258. queueTileLoad(primitive, tile);
  259. }
  260. if (tile.renderable && tileProvider.computeTileVisibility(tile, frameState, occluders) !== Visibility.NONE) {
  261. traversalQueue.enqueue(tile);
  262. } else {
  263. ++debug.tilesCulled;
  264. if (!tile.renderable) {
  265. ++debug.tilesWaitingForChildren;
  266. }
  267. }
  268. }
  269. // Traverse the tiles in breadth-first order.
  270. // This ordering allows us to load bigger, lower-detail tiles before smaller, higher-detail ones.
  271. // This maximizes the average detail across the scene and results in fewer sharp transitions
  272. // between very different LODs.
  273. while (defined((tile = traversalQueue.dequeue()))) {
  274. ++debug.tilesVisited;
  275. primitive._tileReplacementQueue.markTileRendered(tile);
  276. if (tile.level > debug.maxDepth) {
  277. debug.maxDepth = tile.level;
  278. }
  279. // There are a few different algorithms we could use here.
  280. // This one doesn't load children unless we refine to them.
  281. // We may want to revisit this in the future.
  282. if (screenSpaceError(primitive, context, frameState, tile) < primitive.maximumScreenSpaceError) {
  283. // This tile meets SSE requirements, so render it.
  284. addTileToRenderList(primitive, tile);
  285. } else if (queueChildrenLoadAndDetermineIfChildrenAreAllRenderable(primitive, tile)) {
  286. // SSE is not good enough and children are loaded, so refine.
  287. var children = tile.children;
  288. // PERFORMANCE_IDEA: traverse children front-to-back so we can avoid sorting by distance later.
  289. for (i = 0, len = children.length; i < len; ++i) {
  290. if (tileProvider.computeTileVisibility(children[i], frameState, occluders) !== Visibility.NONE) {
  291. traversalQueue.enqueue(children[i]);
  292. } else {
  293. ++debug.tilesCulled;
  294. }
  295. }
  296. } else {
  297. ++debug.tilesWaitingForChildren;
  298. // SSE is not good enough but not all children are loaded, so render this tile anyway.
  299. addTileToRenderList(primitive, tile);
  300. }
  301. }
  302. if (debug.enableDebugOutput) {
  303. if (debug.tilesVisited !== debug.lastTilesVisited ||
  304. debug.tilesRendered !== debug.lastTilesRendered ||
  305. debug.tilesCulled !== debug.lastTilesCulled ||
  306. debug.maxDepth !== debug.lastMaxDepth ||
  307. debug.tilesWaitingForChildren !== debug.lastTilesWaitingForChildren) {
  308. /*global console*/
  309. console.log('Visited ' + debug.tilesVisited + ', Rendered: ' + debug.tilesRendered + ', Culled: ' + debug.tilesCulled + ', Max Depth: ' + debug.maxDepth + ', Waiting for children: ' + debug.tilesWaitingForChildren);
  310. debug.lastTilesVisited = debug.tilesVisited;
  311. debug.lastTilesRendered = debug.tilesRendered;
  312. debug.lastTilesCulled = debug.tilesCulled;
  313. debug.lastMaxDepth = debug.maxDepth;
  314. debug.lastTilesWaitingForChildren = debug.tilesWaitingForChildren;
  315. }
  316. }
  317. }
  318. function screenSpaceError(primitive, context, frameState, tile) {
  319. if (frameState.mode === SceneMode.SCENE2D) {
  320. return screenSpaceError2D(primitive, context, frameState, tile);
  321. }
  322. var maxGeometricError = primitive._tileProvider.getLevelMaximumGeometricError(tile.level);
  323. var distance = primitive._tileProvider.computeDistanceToTile(tile, frameState);
  324. tile._distance = distance;
  325. var height = context.drawingBufferHeight;
  326. var camera = frameState.camera;
  327. var frustum = camera.frustum;
  328. var fovy = frustum.fovy;
  329. // PERFORMANCE_IDEA: factor out stuff that's constant across tiles.
  330. return (maxGeometricError * height) / (2 * distance * Math.tan(0.5 * fovy));
  331. }
  332. function screenSpaceError2D(primitive, context, frameState, tile) {
  333. var camera = frameState.camera;
  334. var frustum = camera.frustum;
  335. var width = context.drawingBufferWidth;
  336. var height = context.drawingBufferHeight;
  337. var maxGeometricError = primitive._tileProvider.getLevelMaximumGeometricError(tile.level);
  338. var pixelSize = Math.max(frustum.top - frustum.bottom, frustum.right - frustum.left) / Math.max(width, height);
  339. return maxGeometricError / pixelSize;
  340. }
  341. function addTileToRenderList(primitive, tile) {
  342. primitive._tilesToRender.push(tile);
  343. ++primitive._debug.tilesRendered;
  344. }
  345. function queueChildrenLoadAndDetermineIfChildrenAreAllRenderable(primitive, tile) {
  346. var allRenderable = true;
  347. var allUpsampledOnly = true;
  348. var children = tile.children;
  349. for (var i = 0, len = children.length; i < len; ++i) {
  350. var child = children[i];
  351. primitive._tileReplacementQueue.markTileRendered(child);
  352. allUpsampledOnly = allUpsampledOnly && child.upsampledFromParent;
  353. allRenderable = allRenderable && child.renderable;
  354. if (child.needsLoading) {
  355. queueTileLoad(primitive, child);
  356. }
  357. }
  358. if (!allRenderable) {
  359. ++primitive._debug.tilesWaitingForChildren;
  360. }
  361. // If all children are upsampled from this tile, we just render this tile instead of its children.
  362. return allRenderable && !allUpsampledOnly;
  363. }
  364. function queueTileLoad(primitive, tile) {
  365. primitive._tileLoadQueue.push(tile);
  366. }
  367. function processTileLoadQueue(primitive, context, frameState) {
  368. var tileLoadQueue = primitive._tileLoadQueue;
  369. var tileProvider = primitive._tileProvider;
  370. if (tileLoadQueue.length === 0) {
  371. return;
  372. }
  373. // Remove any tiles that were not used this frame beyond the number
  374. // we're allowed to keep.
  375. primitive._tileReplacementQueue.trimTiles(primitive.tileCacheSize);
  376. var startTime = getTimestamp();
  377. var timeSlice = primitive._loadQueueTimeSlice;
  378. var endTime = startTime + timeSlice;
  379. for (var len = tileLoadQueue.length - 1, i = len; i >= 0; --i) {
  380. var tile = tileLoadQueue[i];
  381. primitive._tileReplacementQueue.markTileRendered(tile);
  382. tileProvider.loadTile(context, frameState, tile);
  383. if (getTimestamp() >= endTime) {
  384. break;
  385. }
  386. }
  387. }
  388. function tileDistanceSortFunction(a, b) {
  389. return a._distance - b._distance;
  390. }
  391. function createRenderCommandsForSelectedTiles(primitive, context, frameState, commandList) {
  392. var tileProvider = primitive._tileProvider;
  393. var tilesToRender = primitive._tilesToRender;
  394. tilesToRender.sort(tileDistanceSortFunction);
  395. for (var i = 0, len = tilesToRender.length; i < len; ++i) {
  396. tileProvider.showTileThisFrame(tilesToRender[i], context, frameState, commandList);
  397. }
  398. }
  399. return QuadtreePrimitive;
  400. });