智慧水务管理系统 - 精河县供水工程综合管理平台

Megatexture.js 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. import Cartesian3 from "../Core/Cartesian3.js";
  2. import Check from "../Core/Check.js";
  3. import ContextLimits from "../Renderer/ContextLimits.js";
  4. import defined from "../Core/defined.js";
  5. import destroyObject from "../Core/destroyObject.js";
  6. import DeveloperError from "../Core/DeveloperError.js";
  7. import MetadataComponentType from "./MetadataComponentType.js";
  8. import PixelDatatype from "../Renderer/PixelDatatype.js";
  9. import PixelFormat from "../Core/PixelFormat.js";
  10. import RuntimeError from "../Core/RuntimeError.js";
  11. import Sampler from "../Renderer/Sampler.js";
  12. import Texture3D from "../Renderer/Texture3D.js";
  13. import TextureMagnificationFilter from "../Renderer/TextureMagnificationFilter.js";
  14. import TextureMinificationFilter from "../Renderer/TextureMinificationFilter.js";
  15. import TextureWrap from "../Renderer/TextureWrap.js";
  16. /**
  17. * @alias Megatexture
  18. * @constructor
  19. *
  20. * @param {Context} context The context in which to create GPU resources.
  21. * @param {Cartesian3} dimensions The number of voxels in each dimension of the tile.
  22. * @param {number} channelCount The number of channels in the metadata.
  23. * @param {MetadataComponentType} componentType The component type of the metadata.
  24. * @param {number} [availableTextureMemoryBytes=134217728] An upper limit on the texture memory size in bytes.
  25. *
  26. * @exception {RuntimeError} The GL context does not support a 3D texture large enough to contain a tile with the given dimensions.
  27. * @exception {RuntimeError} Not enough texture memory available to create a megatexture with the given tile dimensions.
  28. *
  29. * @private
  30. */
  31. function Megatexture(
  32. context,
  33. dimensions,
  34. channelCount,
  35. componentType,
  36. availableTextureMemoryBytes = 134217728, // 128 MB
  37. tileCount,
  38. ) {
  39. // TODO there are a lot of texture packing rules, see https://github.com/CesiumGS/cesium/issues/9572
  40. const pixelDataType = getPixelDataType(componentType);
  41. const pixelFormat = getPixelFormat(channelCount);
  42. const bytesPerSample =
  43. channelCount * MetadataComponentType.getSizeInBytes(componentType);
  44. const textureDimension = Megatexture.get3DTextureDimension(
  45. dimensions,
  46. bytesPerSample,
  47. availableTextureMemoryBytes,
  48. tileCount,
  49. );
  50. const tileCounts = Cartesian3.divideComponents(
  51. textureDimension,
  52. dimensions,
  53. new Cartesian3(),
  54. );
  55. /**
  56. * @type {number}
  57. * @readonly
  58. */
  59. this.channelCount = channelCount;
  60. /**
  61. * @type {MetadataComponentType}
  62. * @readonly
  63. */
  64. this.componentType = componentType;
  65. /**
  66. * @type {number}
  67. * @readonly
  68. */
  69. this.textureMemoryByteLength =
  70. bytesPerSample *
  71. textureDimension.x *
  72. textureDimension.y *
  73. textureDimension.z;
  74. /**
  75. * @type {Cartesian3}
  76. * @readonly
  77. */
  78. this.tileCounts = Cartesian3.clone(tileCounts, new Cartesian3());
  79. /**
  80. * @type {Cartesian3}
  81. * @readonly
  82. */
  83. this.voxelCountPerTile = Cartesian3.clone(dimensions, new Cartesian3());
  84. /**
  85. * @type {number}
  86. * @readonly
  87. */
  88. this.maximumTileCount = tileCounts.x * tileCounts.y * tileCounts.z;
  89. /**
  90. * @type {Texture3D}
  91. * @readonly
  92. */
  93. this.texture = new Texture3D({
  94. context: context,
  95. pixelFormat: pixelFormat,
  96. pixelDatatype: pixelDataType,
  97. flipY: false,
  98. width: textureDimension.x,
  99. height: textureDimension.y,
  100. depth: textureDimension.z,
  101. sampler: new Sampler({
  102. wrapR: TextureWrap.CLAMP_TO_EDGE,
  103. wrapS: TextureWrap.CLAMP_TO_EDGE,
  104. wrapT: TextureWrap.CLAMP_TO_EDGE,
  105. minificationFilter: TextureMinificationFilter.LINEAR,
  106. magnificationFilter: TextureMagnificationFilter.LINEAR,
  107. }),
  108. });
  109. /**
  110. * @type {MegatextureNode[]}
  111. * @readonly
  112. */
  113. this.nodes = new Array(this.maximumTileCount);
  114. for (let tileIndex = 0; tileIndex < this.maximumTileCount; tileIndex++) {
  115. this.nodes[tileIndex] = new MegatextureNode(tileIndex);
  116. }
  117. for (let tileIndex = 0; tileIndex < this.maximumTileCount; tileIndex++) {
  118. const node = this.nodes[tileIndex];
  119. node.previousNode = tileIndex > 0 ? this.nodes[tileIndex - 1] : undefined;
  120. node.nextNode =
  121. tileIndex < this.maximumTileCount - 1
  122. ? this.nodes[tileIndex + 1]
  123. : undefined;
  124. }
  125. /**
  126. * @type {MegatextureNode}
  127. * @readonly
  128. */
  129. this.occupiedList = undefined;
  130. /**
  131. * @type {MegatextureNode}
  132. * @readonly
  133. */
  134. this.emptyList = this.nodes[0];
  135. /**
  136. * @type {number}
  137. * @readonly
  138. */
  139. this.occupiedCount = 0;
  140. this._nearestSampling = false;
  141. }
  142. Object.defineProperties(Megatexture.prototype, {
  143. /**
  144. * Gets or sets the nearest sampling flag.
  145. * @memberof Megatexture.prototype
  146. * @type {boolean}
  147. */
  148. nearestSampling: {
  149. get: function () {
  150. return this._nearestSampling;
  151. },
  152. set: function (nearestSampling) {
  153. //>>includeStart('debug', pragmas.debug);
  154. Check.typeOf.bool("nearestSampling", nearestSampling);
  155. //>>includeEnd('debug');
  156. if (this._nearestSampling === nearestSampling) {
  157. return;
  158. }
  159. if (nearestSampling) {
  160. this.texture.sampler = Sampler.NEAREST;
  161. } else {
  162. this.texture.sampler = new Sampler({
  163. wrapR: TextureWrap.CLAMP_TO_EDGE,
  164. wrapS: TextureWrap.CLAMP_TO_EDGE,
  165. wrapT: TextureWrap.CLAMP_TO_EDGE,
  166. minificationFilter: TextureMinificationFilter.LINEAR,
  167. magnificationFilter: TextureMagnificationFilter.LINEAR,
  168. });
  169. }
  170. this._nearestSampling = nearestSampling;
  171. },
  172. },
  173. });
  174. /**
  175. * Get the pixel data type to use in a megatexture.
  176. * TODO support more
  177. *
  178. * @param {MetadataComponentType} componentType The component type of the metadata.
  179. * @returns {PixelDatatype} The pixel datatype to use for a megatexture.
  180. *
  181. * @private
  182. */
  183. function getPixelDataType(componentType) {
  184. if (
  185. componentType === MetadataComponentType.FLOAT32 ||
  186. componentType === MetadataComponentType.FLOAT64
  187. ) {
  188. return PixelDatatype.FLOAT;
  189. } else if (componentType === MetadataComponentType.UINT8) {
  190. return PixelDatatype.UNSIGNED_BYTE;
  191. }
  192. }
  193. /**
  194. * Get the pixel format to use for a megatexture.
  195. *
  196. * @param {number} channelCount The number of channels in the metadata. Must be 1 to 4.
  197. * @returns {PixelFormat} The pixel format to use for a megatexture.
  198. *
  199. * @private
  200. */
  201. function getPixelFormat(channelCount) {
  202. switch (channelCount) {
  203. case 1:
  204. return PixelFormat.RED;
  205. case 2:
  206. return PixelFormat.RG;
  207. case 3:
  208. return PixelFormat.RGB;
  209. case 4:
  210. return PixelFormat.RGBA;
  211. }
  212. }
  213. /**
  214. * @alias MegatextureNode
  215. * @constructor
  216. *
  217. * @param {number} index
  218. *
  219. * @private
  220. */
  221. function MegatextureNode(index) {
  222. /**
  223. * @type {number}
  224. */
  225. this.index = index;
  226. /**
  227. * @type {MegatextureNode}
  228. */
  229. this.nextNode = undefined;
  230. /**
  231. * @type {MegatextureNode}
  232. */
  233. this.previousNode = undefined;
  234. }
  235. /**
  236. * Add an array of tile metadata to the megatexture.
  237. * @param {Array} data The data to be added.
  238. * @returns {number} The index of the tile's location in the megatexture.
  239. *
  240. * @exception {DeveloperError} Trying to add when there are no empty spots.
  241. */
  242. Megatexture.prototype.add = function (data) {
  243. if (this.isFull()) {
  244. throw new DeveloperError("Trying to add when there are no empty spots");
  245. }
  246. // remove head of empty list
  247. const node = this.emptyList;
  248. this.emptyList = this.emptyList.nextNode;
  249. if (defined(this.emptyList)) {
  250. this.emptyList.previousNode = undefined;
  251. }
  252. // make head of occupied list
  253. node.nextNode = this.occupiedList;
  254. if (defined(node.nextNode)) {
  255. node.nextNode.previousNode = node;
  256. }
  257. this.occupiedList = node;
  258. const index = node.index;
  259. this.writeDataToTexture(index, data);
  260. this.occupiedCount++;
  261. return index;
  262. };
  263. /**
  264. * @param {number} index
  265. * @exception {DeveloperError} Megatexture index out of bounds.
  266. */
  267. Megatexture.prototype.remove = function (index) {
  268. if (index < 0 || index >= this.maximumTileCount) {
  269. throw new DeveloperError("Megatexture index out of bounds");
  270. }
  271. // remove from list
  272. const node = this.nodes[index];
  273. if (defined(node.previousNode)) {
  274. node.previousNode.nextNode = node.nextNode;
  275. }
  276. if (defined(node.nextNode)) {
  277. node.nextNode.previousNode = node.previousNode;
  278. }
  279. // make head of empty list
  280. node.nextNode = this.emptyList;
  281. if (defined(node.nextNode)) {
  282. node.nextNode.previousNode = node;
  283. }
  284. node.previousNode = undefined;
  285. this.emptyList = node;
  286. this.occupiedCount--;
  287. };
  288. /**
  289. * @returns {boolean}
  290. */
  291. Megatexture.prototype.isFull = function () {
  292. return this.emptyList === undefined;
  293. };
  294. /**
  295. * Compute a 3D texture dimension that contains the given number of tiles, or as many tiles as can fit within the available texture memory.
  296. * Not used outside the class, but exposed for testing.
  297. * @param {Cartesian3} tileDimensions The dimensions of one tile in number of voxels.
  298. * @param {number} bytesPerSample The number of bytes per voxel sample.
  299. * @param {number} availableTextureMemoryBytes An upper limit on the texture memory size in bytes.
  300. * @param {number} [tileCount] The total number of tiles in the tileset.
  301. * @returns {Cartesian3} The computed 3D texture dimensions.
  302. *
  303. * @exception {RuntimeError} The GL context does not support a 3D texture large enough to contain a tile with the given dimensions.
  304. * @exception {RuntimeError} Not enough texture memory available to create a megatexture with the given tile dimensions.
  305. * @private
  306. */
  307. Megatexture.get3DTextureDimension = function (
  308. tileDimensions,
  309. bytesPerSample,
  310. availableTextureMemoryBytes,
  311. tileCount,
  312. ) {
  313. const { maximum3DTextureSize } = ContextLimits;
  314. if (Cartesian3.maximumComponent(tileDimensions) > maximum3DTextureSize) {
  315. throw new RuntimeError(
  316. "The GL context does not support a 3D texture large enough to contain a tile with the given dimensions.",
  317. );
  318. }
  319. // Find the number of tiles we can fit.
  320. const tileSizeInBytes =
  321. bytesPerSample * tileDimensions.x * tileDimensions.y * tileDimensions.z;
  322. const maxTileCount = Math.floor(
  323. availableTextureMemoryBytes / tileSizeInBytes,
  324. );
  325. if (maxTileCount < 1) {
  326. throw new RuntimeError(
  327. "Not enough texture memory available to create a megatexture with the given tile dimensions.",
  328. );
  329. }
  330. if (defined(tileCount)) {
  331. tileCount = Math.min(tileCount, maxTileCount);
  332. } else {
  333. tileCount = maxTileCount;
  334. }
  335. // Sort the tile dimensions from largest to smallest.
  336. const sortedDimensions = Object.entries(tileDimensions).sort(
  337. (a, b) => b[1] - a[1],
  338. );
  339. // Compute the number of tiles that we can fit along each axis of a 3D texture,
  340. // starting from the largest texture the context can support
  341. const tilesPerDimension = sortedDimensions.map(([axis, dimension]) =>
  342. Math.floor(maximum3DTextureSize / dimension),
  343. );
  344. // Reduce the number of tiles along each dimension until the total number of
  345. // tiles is close to but not less than tileCount.
  346. // Start from the dimension along which the tiles are largest, since
  347. // along this dimension each removed slice will contain the most tiles.
  348. for (let i = 0; i < 3; i++) {
  349. const currentTileCount = getVolume(tilesPerDimension);
  350. if (currentTileCount < tileCount) {
  351. break;
  352. }
  353. const sliceDimensions = tilesPerDimension.slice();
  354. sliceDimensions.splice(i, 1);
  355. const tilesPerSlice = sliceDimensions[0] * sliceDimensions[1];
  356. const excessTiles = currentTileCount - tileCount;
  357. const slicesToRemove = Math.floor(excessTiles / tilesPerSlice);
  358. tilesPerDimension[i] -= slicesToRemove;
  359. }
  360. // Make sure we are less than maximumTileCount (to fit within memory)
  361. if (getVolume(tilesPerDimension) > maxTileCount) {
  362. tilesPerDimension[2] = Math.floor(
  363. maxTileCount / (tilesPerDimension[0] * tilesPerDimension[1]),
  364. );
  365. }
  366. // Compute the final texture dimensions
  367. const textureDimension = new Cartesian3();
  368. for (let i = 0; i < 3; i++) {
  369. const [axis, dimension] = sortedDimensions[i];
  370. textureDimension[axis] = tilesPerDimension[i] * dimension;
  371. }
  372. return textureDimension;
  373. };
  374. function getVolume(dimensionsArray) {
  375. return dimensionsArray.reduce((p, d) => p * d);
  376. }
  377. /**
  378. * Write an array of tile metadata to the megatexture.
  379. * @param {number} index The index of the tile's location in the megatexture.
  380. * @param {Float32Array|Uint16Array|Uint8Array} tileData The data to be written.
  381. */
  382. Megatexture.prototype.writeDataToTexture = function (index, tileData) {
  383. const { tileCounts, voxelCountPerTile } = this;
  384. const source = {
  385. arrayBufferView: tileData,
  386. width: voxelCountPerTile.x,
  387. height: voxelCountPerTile.y,
  388. depth: voxelCountPerTile.z,
  389. };
  390. const tilesPerZ = tileCounts.x * tileCounts.y;
  391. const iz = Math.floor(index / tilesPerZ);
  392. const remainder = index - iz * tilesPerZ;
  393. const iy = Math.floor(remainder / tileCounts.x);
  394. const ix = remainder - iy * tileCounts.x;
  395. const copyOptions = {
  396. source: source,
  397. xOffset: ix * voxelCountPerTile.x,
  398. yOffset: iy * voxelCountPerTile.y,
  399. zOffset: iz * voxelCountPerTile.z,
  400. };
  401. this.texture.copyFrom(copyOptions);
  402. };
  403. /**
  404. * Returns true if this object was destroyed; otherwise, false.
  405. * <br /><br />
  406. * If this object was destroyed, it should not be used; calling any function other than
  407. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  408. *
  409. * @returns {boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
  410. *
  411. * @see Megatexture#destroy
  412. */
  413. Megatexture.prototype.isDestroyed = function () {
  414. return false;
  415. };
  416. /**
  417. * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
  418. * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
  419. * <br /><br />
  420. * Once an object is destroyed, it should not be used; calling any function other than
  421. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  422. * assign the return value (<code>undefined</code>) to the object as done in the example.
  423. *
  424. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  425. *
  426. * @see Megatexture#isDestroyed
  427. *
  428. * @example
  429. * megatexture = megatexture && megatexture.destroy();
  430. */
  431. Megatexture.prototype.destroy = function () {
  432. this.texture = this.texture && this.texture.destroy();
  433. return destroyObject(this);
  434. };
  435. export default Megatexture;