Megatexture.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. import Cartesian2 from "../Core/Cartesian2.js";
  2. import Cartesian3 from "../Core/Cartesian3.js";
  3. import ComponentDatatype from "../Core/ComponentDatatype.js";
  4. import ContextLimits from "../Renderer/ContextLimits.js";
  5. import defaultValue from "../Core/defaultValue.js";
  6. import defined from "../Core/defined.js";
  7. import destroyObject from "../Core/destroyObject.js";
  8. import DeveloperError from "../Core/DeveloperError.js";
  9. import CesiumMath from "../Core/Math.js";
  10. import MetadataComponentType from "./MetadataComponentType.js";
  11. import PixelDatatype from "../Renderer/PixelDatatype.js";
  12. import PixelFormat from "../Core/PixelFormat.js";
  13. import RuntimeError from "../Core/RuntimeError.js";
  14. import Sampler from "../Renderer/Sampler.js";
  15. import Texture from "../Renderer/Texture.js";
  16. import TextureMagnificationFilter from "../Renderer/TextureMagnificationFilter.js";
  17. import TextureMinificationFilter from "../Renderer/TextureMinificationFilter.js";
  18. import TextureWrap from "../Renderer/TextureWrap.js";
  19. /**
  20. * @alias Megatexture
  21. * @constructor
  22. *
  23. * @param {Context} context
  24. * @param {Cartesian3} dimensions
  25. * @param {number} channelCount
  26. * @param {MetadataComponentType} componentType
  27. * @param {number} [textureMemoryByteLength]
  28. *
  29. * @private
  30. */
  31. function Megatexture(
  32. context,
  33. dimensions,
  34. channelCount,
  35. componentType,
  36. textureMemoryByteLength
  37. ) {
  38. // TODO there are a lot of texture packing rules, see https://github.com/CesiumGS/cesium/issues/9572
  39. // Unsigned short textures not allowed in webgl 1, so treat as float
  40. if (componentType === MetadataComponentType.UNSIGNED_SHORT) {
  41. componentType = MetadataComponentType.FLOAT32;
  42. }
  43. const supportsFloatingPointTexture = context.floatingPointTexture;
  44. if (
  45. componentType === MetadataComponentType.FLOAT32 &&
  46. !supportsFloatingPointTexture
  47. ) {
  48. throw new RuntimeError("Floating point texture not supported");
  49. }
  50. // TODO support more
  51. let pixelType;
  52. if (
  53. componentType === MetadataComponentType.FLOAT32 ||
  54. componentType === MetadataComponentType.FLOAT64
  55. ) {
  56. pixelType = PixelDatatype.FLOAT;
  57. } else if (componentType === MetadataComponentType.UINT8) {
  58. pixelType = PixelDatatype.UNSIGNED_BYTE;
  59. }
  60. let pixelFormat;
  61. if (channelCount === 1) {
  62. pixelFormat = context.webgl2 ? PixelFormat.RED : PixelFormat.LUMINANCE;
  63. } else if (channelCount === 2) {
  64. pixelFormat = context.webgl2 ? PixelFormat.RG : PixelFormat.LUMINANCE_ALPHA;
  65. } else if (channelCount === 3) {
  66. pixelFormat = PixelFormat.RGB;
  67. } else if (channelCount === 4) {
  68. pixelFormat = PixelFormat.RGBA;
  69. }
  70. const maximumTextureMemoryByteLength = 512 * 1024 * 1024;
  71. const defaultTextureMemoryByteLength = 128 * 1024 * 1024;
  72. textureMemoryByteLength = Math.min(
  73. defaultValue(textureMemoryByteLength, defaultTextureMemoryByteLength),
  74. maximumTextureMemoryByteLength
  75. );
  76. const maximumTextureDimensionContext = ContextLimits.maximumTextureSize;
  77. const componentTypeByteLength = MetadataComponentType.getSizeInBytes(
  78. componentType
  79. );
  80. const texelCount = Math.floor(
  81. textureMemoryByteLength / (channelCount * componentTypeByteLength)
  82. );
  83. const textureDimension = Math.min(
  84. maximumTextureDimensionContext,
  85. CesiumMath.previousPowerOfTwo(Math.floor(Math.sqrt(texelCount)))
  86. );
  87. const sliceCountPerRegionX = Math.ceil(Math.sqrt(dimensions.x));
  88. const sliceCountPerRegionY = Math.ceil(dimensions.z / sliceCountPerRegionX);
  89. const voxelCountPerRegionX = sliceCountPerRegionX * dimensions.x;
  90. const voxelCountPerRegionY = sliceCountPerRegionY * dimensions.y;
  91. const regionCountPerMegatextureX = Math.floor(
  92. textureDimension / voxelCountPerRegionX
  93. );
  94. const regionCountPerMegatextureY = Math.floor(
  95. textureDimension / voxelCountPerRegionY
  96. );
  97. if (regionCountPerMegatextureX === 0 || regionCountPerMegatextureY === 0) {
  98. throw new RuntimeError("Tileset is too large to fit into megatexture");
  99. }
  100. /**
  101. * @type {number}
  102. * @readonly
  103. */
  104. this.channelCount = channelCount;
  105. /**
  106. * @type {MetadataComponentType}
  107. * @readonly
  108. */
  109. this.componentType = componentType;
  110. /**
  111. * @type {Cartesian3}
  112. * @readonly
  113. */
  114. this.voxelCountPerTile = Cartesian3.clone(dimensions, new Cartesian3());
  115. /**
  116. * @type {number}
  117. * @readonly
  118. */
  119. this.maximumTileCount =
  120. regionCountPerMegatextureX * regionCountPerMegatextureY;
  121. /**
  122. * @type {Cartesian2}
  123. * @readonly
  124. */
  125. this.regionCountPerMegatexture = new Cartesian2(
  126. regionCountPerMegatextureX,
  127. regionCountPerMegatextureY
  128. );
  129. /**
  130. * @type {Cartesian2}
  131. * @readonly
  132. */
  133. this.voxelCountPerRegion = new Cartesian2(
  134. voxelCountPerRegionX,
  135. voxelCountPerRegionY
  136. );
  137. /**
  138. * @type {Cartesian2}
  139. * @readonly
  140. */
  141. this.sliceCountPerRegion = new Cartesian2(
  142. sliceCountPerRegionX,
  143. sliceCountPerRegionY
  144. );
  145. /**
  146. * @type {Cartesian2}
  147. * @readonly
  148. */
  149. this.voxelSizeUv = new Cartesian2(
  150. 1.0 / textureDimension,
  151. 1.0 / textureDimension
  152. );
  153. /**
  154. * @type {Cartesian2}
  155. * @readonly
  156. */
  157. this.sliceSizeUv = new Cartesian2(
  158. dimensions.x / textureDimension,
  159. dimensions.y / textureDimension
  160. );
  161. /**
  162. * @type {Cartesian2}
  163. * @readonly
  164. */
  165. this.regionSizeUv = new Cartesian2(
  166. voxelCountPerRegionX / textureDimension,
  167. voxelCountPerRegionY / textureDimension
  168. );
  169. /**
  170. * @type {Texture}
  171. * @readonly
  172. */
  173. this.texture = new Texture({
  174. context: context,
  175. pixelFormat: pixelFormat,
  176. pixelDatatype: pixelType,
  177. flipY: false,
  178. width: textureDimension,
  179. height: textureDimension,
  180. sampler: new Sampler({
  181. wrapS: TextureWrap.CLAMP_TO_EDGE,
  182. wrapT: TextureWrap.CLAMP_TO_EDGE,
  183. minificationFilter: TextureMinificationFilter.LINEAR,
  184. magnificationFilter: TextureMagnificationFilter.LINEAR,
  185. }),
  186. });
  187. const componentDatatype = MetadataComponentType.toComponentDatatype(
  188. componentType
  189. );
  190. /**
  191. * @type {Array}
  192. */
  193. this.tileVoxelDataTemp = ComponentDatatype.createTypedArray(
  194. componentDatatype,
  195. voxelCountPerRegionX * voxelCountPerRegionY * channelCount
  196. );
  197. /**
  198. * @type {MegatextureNode[]}
  199. * @readonly
  200. */
  201. this.nodes = new Array(this.maximumTileCount);
  202. for (let tileIndex = 0; tileIndex < this.maximumTileCount; tileIndex++) {
  203. this.nodes[tileIndex] = new MegatextureNode(tileIndex);
  204. }
  205. for (let tileIndex = 0; tileIndex < this.maximumTileCount; tileIndex++) {
  206. const node = this.nodes[tileIndex];
  207. node.previousNode = tileIndex > 0 ? this.nodes[tileIndex - 1] : undefined;
  208. node.nextNode =
  209. tileIndex < this.maximumTileCount - 1
  210. ? this.nodes[tileIndex + 1]
  211. : undefined;
  212. }
  213. /**
  214. * @type {MegatextureNode}
  215. * @readonly
  216. */
  217. this.occupiedList = undefined;
  218. /**
  219. * @type {MegatextureNode}
  220. * @readonly
  221. */
  222. this.emptyList = this.nodes[0];
  223. /**
  224. * @type {number}
  225. * @readonly
  226. */
  227. this.occupiedCount = 0;
  228. }
  229. /**
  230. * @alias MegatextureNode
  231. * @constructor
  232. *
  233. * @param {number} index
  234. *
  235. * @private
  236. */
  237. function MegatextureNode(index) {
  238. /**
  239. * @type {number}
  240. */
  241. this.index = index;
  242. /**
  243. * @type {MegatextureNode}
  244. */
  245. this.nextNode = undefined;
  246. /**
  247. * @type {MegatextureNode}
  248. */
  249. this.previousNode = undefined;
  250. }
  251. /**
  252. * @param {Array} data
  253. * @returns {number}
  254. */
  255. Megatexture.prototype.add = function (data) {
  256. if (this.isFull()) {
  257. throw new DeveloperError("Trying to add when there are no empty spots");
  258. }
  259. // remove head of empty list
  260. const node = this.emptyList;
  261. this.emptyList = this.emptyList.nextNode;
  262. if (defined(this.emptyList)) {
  263. this.emptyList.previousNode = undefined;
  264. }
  265. // make head of occupied list
  266. node.nextNode = this.occupiedList;
  267. if (defined(node.nextNode)) {
  268. node.nextNode.previousNode = node;
  269. }
  270. this.occupiedList = node;
  271. const index = node.index;
  272. this.writeDataToTexture(index, data);
  273. this.occupiedCount++;
  274. return index;
  275. };
  276. /**
  277. * @param {number} index
  278. */
  279. Megatexture.prototype.remove = function (index) {
  280. if (index < 0 || index >= this.maximumTileCount) {
  281. throw new DeveloperError("Megatexture index out of bounds");
  282. }
  283. // remove from list
  284. const node = this.nodes[index];
  285. if (defined(node.previousNode)) {
  286. node.previousNode.nextNode = node.nextNode;
  287. }
  288. if (defined(node.nextNode)) {
  289. node.nextNode.previousNode = node.previousNode;
  290. }
  291. // make head of empty list
  292. node.nextNode = this.emptyList;
  293. if (defined(node.nextNode)) {
  294. node.nextNode.previousNode = node;
  295. }
  296. node.previousNode = undefined;
  297. this.emptyList = node;
  298. this.occupiedCount--;
  299. };
  300. /**
  301. * @returns {boolean}
  302. */
  303. Megatexture.prototype.isFull = function () {
  304. return this.emptyList === undefined;
  305. };
  306. /**
  307. * @param {number} tileCount
  308. * @param {Cartesian3} dimensions
  309. * @param {number} channelCount number of channels in the metadata. Must be 1 to 4.
  310. * @param {MetadataComponentType} componentType
  311. * @returns {number}
  312. */
  313. Megatexture.getApproximateTextureMemoryByteLength = function (
  314. tileCount,
  315. dimensions,
  316. channelCount,
  317. componentType
  318. ) {
  319. // TODO there's a lot of code duplicate with Megatexture constructor
  320. // Unsigned short textures not allowed in webgl 1, so treat as float
  321. if (componentType === MetadataComponentType.UNSIGNED_SHORT) {
  322. componentType = MetadataComponentType.FLOAT32;
  323. }
  324. const datatypeSizeInBytes = MetadataComponentType.getSizeInBytes(
  325. componentType
  326. );
  327. const voxelCountTotal =
  328. tileCount * dimensions.x * dimensions.y * dimensions.z;
  329. const sliceCountPerRegionX = Math.ceil(Math.sqrt(dimensions.z));
  330. const sliceCountPerRegionY = Math.ceil(dimensions.z / sliceCountPerRegionX);
  331. const voxelCountPerRegionX = sliceCountPerRegionX * dimensions.x;
  332. const voxelCountPerRegionY = sliceCountPerRegionY * dimensions.y;
  333. // Find the power of two that can fit all tile data, accounting for slices.
  334. // There's probably a non-iterative solution for this, but this is good enough for now.
  335. let textureDimension = CesiumMath.previousPowerOfTwo(
  336. Math.floor(Math.sqrt(voxelCountTotal))
  337. );
  338. for (;;) {
  339. const regionCountX = Math.floor(textureDimension / voxelCountPerRegionX);
  340. const regionCountY = Math.floor(textureDimension / voxelCountPerRegionY);
  341. const regionCount = regionCountX * regionCountY;
  342. if (regionCount >= tileCount) {
  343. break;
  344. } else {
  345. textureDimension *= 2;
  346. }
  347. }
  348. const textureMemoryByteLength =
  349. textureDimension * textureDimension * channelCount * datatypeSizeInBytes;
  350. return textureMemoryByteLength;
  351. };
  352. /**
  353. * @param {number} index
  354. * @param {Float32Array|Uint16Array|Uint8Array} data
  355. */
  356. Megatexture.prototype.writeDataToTexture = function (index, data) {
  357. // Unsigned short textures not allowed in webgl 1, so treat as float
  358. const tileData =
  359. data.constructor === Uint16Array ? new Float32Array(data) : data;
  360. const voxelDimensionsPerTile = this.voxelCountPerTile;
  361. const sliceDimensionsPerRegion = this.sliceCountPerRegion;
  362. const voxelDimensionsPerRegion = this.voxelCountPerRegion;
  363. const channelCount = this.channelCount;
  364. const tileVoxelData = this.tileVoxelDataTemp;
  365. for (let z = 0; z < voxelDimensionsPerTile.z; z++) {
  366. const sliceVoxelOffsetX =
  367. (z % sliceDimensionsPerRegion.x) * voxelDimensionsPerTile.x;
  368. const sliceVoxelOffsetY =
  369. Math.floor(z / sliceDimensionsPerRegion.x) * voxelDimensionsPerTile.y;
  370. for (let y = 0; y < voxelDimensionsPerTile.y; y++) {
  371. for (let x = 0; x < voxelDimensionsPerTile.x; x++) {
  372. const readIndex =
  373. z * voxelDimensionsPerTile.y * voxelDimensionsPerTile.x +
  374. y * voxelDimensionsPerTile.x +
  375. x;
  376. const writeIndex =
  377. (sliceVoxelOffsetY + y) * voxelDimensionsPerRegion.x +
  378. (sliceVoxelOffsetX + x);
  379. for (let c = 0; c < channelCount; c++) {
  380. tileVoxelData[writeIndex * channelCount + c] =
  381. tileData[readIndex * channelCount + c];
  382. }
  383. }
  384. }
  385. }
  386. const regionDimensionsPerMegatexture = this.regionCountPerMegatexture;
  387. const voxelWidth = voxelDimensionsPerRegion.x;
  388. const voxelHeight = voxelDimensionsPerRegion.y;
  389. const voxelOffsetX =
  390. (index % regionDimensionsPerMegatexture.x) * voxelDimensionsPerRegion.x;
  391. const voxelOffsetY =
  392. Math.floor(index / regionDimensionsPerMegatexture.x) *
  393. voxelDimensionsPerRegion.y;
  394. const source = {
  395. arrayBufferView: tileVoxelData,
  396. width: voxelWidth,
  397. height: voxelHeight,
  398. };
  399. const copyOptions = {
  400. source: source,
  401. xOffset: voxelOffsetX,
  402. yOffset: voxelOffsetY,
  403. };
  404. this.texture.copyFrom(copyOptions);
  405. };
  406. /**
  407. * Returns true if this object was destroyed; otherwise, false.
  408. * <br /><br />
  409. * If this object was destroyed, it should not be used; calling any function other than
  410. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  411. *
  412. * @returns {boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
  413. *
  414. * @see Megatexture#destroy
  415. */
  416. Megatexture.prototype.isDestroyed = function () {
  417. return false;
  418. };
  419. /**
  420. * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
  421. * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
  422. * <br /><br />
  423. * Once an object is destroyed, it should not be used; calling any function other than
  424. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  425. * assign the return value (<code>undefined</code>) to the object as done in the example.
  426. *
  427. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  428. *
  429. * @see Megatexture#isDestroyed
  430. *
  431. * @example
  432. * megatexture = megatexture && megatexture.destroy();
  433. */
  434. Megatexture.prototype.destroy = function () {
  435. this.texture = this.texture && this.texture.destroy();
  436. return destroyObject(this);
  437. };
  438. export default Megatexture;