CubeMap.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. import Check from "../Core/Check.js";
  2. import defaultValue from "../Core/defaultValue.js";
  3. import defined from "../Core/defined.js";
  4. import destroyObject from "../Core/destroyObject.js";
  5. import DeveloperError from "../Core/DeveloperError.js";
  6. import CesiumMath from "../Core/Math.js";
  7. import PixelFormat from "../Core/PixelFormat.js";
  8. import ContextLimits from "./ContextLimits.js";
  9. import CubeMapFace from "./CubeMapFace.js";
  10. import MipmapHint from "./MipmapHint.js";
  11. import PixelDatatype from "./PixelDatatype.js";
  12. import Sampler from "./Sampler.js";
  13. import TextureMagnificationFilter from "./TextureMagnificationFilter.js";
  14. import TextureMinificationFilter from "./TextureMinificationFilter.js";
  15. /**
  16. * @private
  17. */
  18. function CubeMap(options) {
  19. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  20. //>>includeStart('debug', pragmas.debug);
  21. Check.defined("options.context", options.context);
  22. //>>includeEnd('debug');
  23. const context = options.context;
  24. const source = options.source;
  25. let width;
  26. let height;
  27. if (defined(source)) {
  28. const faces = [
  29. source.positiveX,
  30. source.negativeX,
  31. source.positiveY,
  32. source.negativeY,
  33. source.positiveZ,
  34. source.negativeZ,
  35. ];
  36. //>>includeStart('debug', pragmas.debug);
  37. if (
  38. !faces[0] ||
  39. !faces[1] ||
  40. !faces[2] ||
  41. !faces[3] ||
  42. !faces[4] ||
  43. !faces[5]
  44. ) {
  45. throw new DeveloperError(
  46. "options.source requires positiveX, negativeX, positiveY, negativeY, positiveZ, and negativeZ faces."
  47. );
  48. }
  49. //>>includeEnd('debug');
  50. width = faces[0].width;
  51. height = faces[0].height;
  52. //>>includeStart('debug', pragmas.debug);
  53. for (let i = 1; i < 6; ++i) {
  54. if (
  55. Number(faces[i].width) !== width ||
  56. Number(faces[i].height) !== height
  57. ) {
  58. throw new DeveloperError(
  59. "Each face in options.source must have the same width and height."
  60. );
  61. }
  62. }
  63. //>>includeEnd('debug');
  64. } else {
  65. width = options.width;
  66. height = options.height;
  67. }
  68. const size = width;
  69. const pixelDatatype = defaultValue(
  70. options.pixelDatatype,
  71. PixelDatatype.UNSIGNED_BYTE
  72. );
  73. const pixelFormat = defaultValue(options.pixelFormat, PixelFormat.RGBA);
  74. const internalFormat = PixelFormat.toInternalFormat(
  75. pixelFormat,
  76. pixelDatatype,
  77. context
  78. );
  79. //>>includeStart('debug', pragmas.debug);
  80. if (!defined(width) || !defined(height)) {
  81. throw new DeveloperError(
  82. "options requires a source field to create an initialized cube map or width and height fields to create a blank cube map."
  83. );
  84. }
  85. if (width !== height) {
  86. throw new DeveloperError("Width must equal height.");
  87. }
  88. if (size <= 0) {
  89. throw new DeveloperError("Width and height must be greater than zero.");
  90. }
  91. if (size > ContextLimits.maximumCubeMapSize) {
  92. throw new DeveloperError(
  93. `Width and height must be less than or equal to the maximum cube map size (${ContextLimits.maximumCubeMapSize}). Check maximumCubeMapSize.`
  94. );
  95. }
  96. if (!PixelFormat.validate(pixelFormat)) {
  97. throw new DeveloperError("Invalid options.pixelFormat.");
  98. }
  99. if (PixelFormat.isDepthFormat(pixelFormat)) {
  100. throw new DeveloperError(
  101. "options.pixelFormat cannot be DEPTH_COMPONENT or DEPTH_STENCIL."
  102. );
  103. }
  104. if (!PixelDatatype.validate(pixelDatatype)) {
  105. throw new DeveloperError("Invalid options.pixelDatatype.");
  106. }
  107. if (pixelDatatype === PixelDatatype.FLOAT && !context.floatingPointTexture) {
  108. throw new DeveloperError(
  109. "When options.pixelDatatype is FLOAT, this WebGL implementation must support the OES_texture_float extension."
  110. );
  111. }
  112. if (
  113. pixelDatatype === PixelDatatype.HALF_FLOAT &&
  114. !context.halfFloatingPointTexture
  115. ) {
  116. throw new DeveloperError(
  117. "When options.pixelDatatype is HALF_FLOAT, this WebGL implementation must support the OES_texture_half_float extension."
  118. );
  119. }
  120. //>>includeEnd('debug');
  121. const sizeInBytes =
  122. PixelFormat.textureSizeInBytes(pixelFormat, pixelDatatype, size, size) * 6;
  123. // Use premultiplied alpha for opaque textures should perform better on Chrome:
  124. // http://media.tojicode.com/webglCamp4/#20
  125. const preMultiplyAlpha =
  126. options.preMultiplyAlpha ||
  127. pixelFormat === PixelFormat.RGB ||
  128. pixelFormat === PixelFormat.LUMINANCE;
  129. const flipY = defaultValue(options.flipY, true);
  130. const skipColorSpaceConversion = defaultValue(
  131. options.skipColorSpaceConversion,
  132. false
  133. );
  134. const gl = context._gl;
  135. const textureTarget = gl.TEXTURE_CUBE_MAP;
  136. const texture = gl.createTexture();
  137. gl.activeTexture(gl.TEXTURE0);
  138. gl.bindTexture(textureTarget, texture);
  139. function createFace(
  140. target,
  141. sourceFace,
  142. preMultiplyAlpha,
  143. flipY,
  144. skipColorSpaceConversion
  145. ) {
  146. let arrayBufferView = sourceFace.arrayBufferView;
  147. if (!defined(arrayBufferView)) {
  148. arrayBufferView = sourceFace.bufferView;
  149. }
  150. let unpackAlignment = 4;
  151. if (defined(arrayBufferView)) {
  152. unpackAlignment = PixelFormat.alignmentInBytes(
  153. pixelFormat,
  154. pixelDatatype,
  155. width
  156. );
  157. }
  158. gl.pixelStorei(gl.UNPACK_ALIGNMENT, unpackAlignment);
  159. if (skipColorSpaceConversion) {
  160. gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);
  161. } else {
  162. gl.pixelStorei(
  163. gl.UNPACK_COLORSPACE_CONVERSION_WEBGL,
  164. gl.BROWSER_DEFAULT_WEBGL
  165. );
  166. }
  167. if (defined(arrayBufferView)) {
  168. gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
  169. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
  170. if (flipY) {
  171. arrayBufferView = PixelFormat.flipY(
  172. arrayBufferView,
  173. pixelFormat,
  174. pixelDatatype,
  175. size,
  176. size
  177. );
  178. }
  179. gl.texImage2D(
  180. target,
  181. 0,
  182. internalFormat,
  183. size,
  184. size,
  185. 0,
  186. pixelFormat,
  187. PixelDatatype.toWebGLConstant(pixelDatatype, context),
  188. arrayBufferView
  189. );
  190. } else {
  191. // Only valid for DOM-Element uploads
  192. gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, preMultiplyAlpha);
  193. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
  194. // Source: ImageData, HTMLImageElement, HTMLCanvasElement, or HTMLVideoElement
  195. gl.texImage2D(
  196. target,
  197. 0,
  198. internalFormat,
  199. pixelFormat,
  200. PixelDatatype.toWebGLConstant(pixelDatatype, context),
  201. sourceFace
  202. );
  203. }
  204. }
  205. if (defined(source)) {
  206. createFace(
  207. gl.TEXTURE_CUBE_MAP_POSITIVE_X,
  208. source.positiveX,
  209. preMultiplyAlpha,
  210. flipY,
  211. skipColorSpaceConversion
  212. );
  213. createFace(
  214. gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
  215. source.negativeX,
  216. preMultiplyAlpha,
  217. flipY,
  218. skipColorSpaceConversion
  219. );
  220. createFace(
  221. gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
  222. source.positiveY,
  223. preMultiplyAlpha,
  224. flipY,
  225. skipColorSpaceConversion
  226. );
  227. createFace(
  228. gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
  229. source.negativeY,
  230. preMultiplyAlpha,
  231. flipY,
  232. skipColorSpaceConversion
  233. );
  234. createFace(
  235. gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
  236. source.positiveZ,
  237. preMultiplyAlpha,
  238. flipY,
  239. skipColorSpaceConversion
  240. );
  241. createFace(
  242. gl.TEXTURE_CUBE_MAP_NEGATIVE_Z,
  243. source.negativeZ,
  244. preMultiplyAlpha,
  245. flipY,
  246. skipColorSpaceConversion
  247. );
  248. } else {
  249. gl.texImage2D(
  250. gl.TEXTURE_CUBE_MAP_POSITIVE_X,
  251. 0,
  252. internalFormat,
  253. size,
  254. size,
  255. 0,
  256. pixelFormat,
  257. PixelDatatype.toWebGLConstant(pixelDatatype, context),
  258. null
  259. );
  260. gl.texImage2D(
  261. gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
  262. 0,
  263. internalFormat,
  264. size,
  265. size,
  266. 0,
  267. pixelFormat,
  268. PixelDatatype.toWebGLConstant(pixelDatatype, context),
  269. null
  270. );
  271. gl.texImage2D(
  272. gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
  273. 0,
  274. internalFormat,
  275. size,
  276. size,
  277. 0,
  278. pixelFormat,
  279. PixelDatatype.toWebGLConstant(pixelDatatype, context),
  280. null
  281. );
  282. gl.texImage2D(
  283. gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
  284. 0,
  285. internalFormat,
  286. size,
  287. size,
  288. 0,
  289. pixelFormat,
  290. PixelDatatype.toWebGLConstant(pixelDatatype, context),
  291. null
  292. );
  293. gl.texImage2D(
  294. gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
  295. 0,
  296. internalFormat,
  297. size,
  298. size,
  299. 0,
  300. pixelFormat,
  301. PixelDatatype.toWebGLConstant(pixelDatatype, context),
  302. null
  303. );
  304. gl.texImage2D(
  305. gl.TEXTURE_CUBE_MAP_NEGATIVE_Z,
  306. 0,
  307. internalFormat,
  308. size,
  309. size,
  310. 0,
  311. pixelFormat,
  312. PixelDatatype.toWebGLConstant(pixelDatatype, context),
  313. null
  314. );
  315. }
  316. gl.bindTexture(textureTarget, null);
  317. this._context = context;
  318. this._textureFilterAnisotropic = context._textureFilterAnisotropic;
  319. this._textureTarget = textureTarget;
  320. this._texture = texture;
  321. this._pixelFormat = pixelFormat;
  322. this._pixelDatatype = pixelDatatype;
  323. this._size = size;
  324. this._hasMipmap = false;
  325. this._sizeInBytes = sizeInBytes;
  326. this._preMultiplyAlpha = preMultiplyAlpha;
  327. this._flipY = flipY;
  328. this._sampler = undefined;
  329. const initialized = defined(source);
  330. this._positiveX = new CubeMapFace(
  331. context,
  332. texture,
  333. textureTarget,
  334. gl.TEXTURE_CUBE_MAP_POSITIVE_X,
  335. internalFormat,
  336. pixelFormat,
  337. pixelDatatype,
  338. size,
  339. preMultiplyAlpha,
  340. flipY,
  341. initialized
  342. );
  343. this._negativeX = new CubeMapFace(
  344. context,
  345. texture,
  346. textureTarget,
  347. gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
  348. internalFormat,
  349. pixelFormat,
  350. pixelDatatype,
  351. size,
  352. preMultiplyAlpha,
  353. flipY,
  354. initialized
  355. );
  356. this._positiveY = new CubeMapFace(
  357. context,
  358. texture,
  359. textureTarget,
  360. gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
  361. internalFormat,
  362. pixelFormat,
  363. pixelDatatype,
  364. size,
  365. preMultiplyAlpha,
  366. flipY,
  367. initialized
  368. );
  369. this._negativeY = new CubeMapFace(
  370. context,
  371. texture,
  372. textureTarget,
  373. gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
  374. internalFormat,
  375. pixelFormat,
  376. pixelDatatype,
  377. size,
  378. preMultiplyAlpha,
  379. flipY,
  380. initialized
  381. );
  382. this._positiveZ = new CubeMapFace(
  383. context,
  384. texture,
  385. textureTarget,
  386. gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
  387. internalFormat,
  388. pixelFormat,
  389. pixelDatatype,
  390. size,
  391. preMultiplyAlpha,
  392. flipY,
  393. initialized
  394. );
  395. this._negativeZ = new CubeMapFace(
  396. context,
  397. texture,
  398. textureTarget,
  399. gl.TEXTURE_CUBE_MAP_NEGATIVE_Z,
  400. internalFormat,
  401. pixelFormat,
  402. pixelDatatype,
  403. size,
  404. preMultiplyAlpha,
  405. flipY,
  406. initialized
  407. );
  408. this.sampler = defined(options.sampler) ? options.sampler : new Sampler();
  409. }
  410. Object.defineProperties(CubeMap.prototype, {
  411. positiveX: {
  412. get: function () {
  413. return this._positiveX;
  414. },
  415. },
  416. negativeX: {
  417. get: function () {
  418. return this._negativeX;
  419. },
  420. },
  421. positiveY: {
  422. get: function () {
  423. return this._positiveY;
  424. },
  425. },
  426. negativeY: {
  427. get: function () {
  428. return this._negativeY;
  429. },
  430. },
  431. positiveZ: {
  432. get: function () {
  433. return this._positiveZ;
  434. },
  435. },
  436. negativeZ: {
  437. get: function () {
  438. return this._negativeZ;
  439. },
  440. },
  441. sampler: {
  442. get: function () {
  443. return this._sampler;
  444. },
  445. set: function (sampler) {
  446. let minificationFilter = sampler.minificationFilter;
  447. let magnificationFilter = sampler.magnificationFilter;
  448. const mipmap =
  449. minificationFilter ===
  450. TextureMinificationFilter.NEAREST_MIPMAP_NEAREST ||
  451. minificationFilter ===
  452. TextureMinificationFilter.NEAREST_MIPMAP_LINEAR ||
  453. minificationFilter ===
  454. TextureMinificationFilter.LINEAR_MIPMAP_NEAREST ||
  455. minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_LINEAR;
  456. const context = this._context;
  457. const pixelDatatype = this._pixelDatatype;
  458. // float textures only support nearest filtering unless the linear extensions are supported, so override the sampler's settings
  459. if (
  460. (pixelDatatype === PixelDatatype.FLOAT &&
  461. !context.textureFloatLinear) ||
  462. (pixelDatatype === PixelDatatype.HALF_FLOAT &&
  463. !context.textureHalfFloatLinear)
  464. ) {
  465. minificationFilter = mipmap
  466. ? TextureMinificationFilter.NEAREST_MIPMAP_NEAREST
  467. : TextureMinificationFilter.NEAREST;
  468. magnificationFilter = TextureMagnificationFilter.NEAREST;
  469. }
  470. const gl = context._gl;
  471. const target = this._textureTarget;
  472. gl.activeTexture(gl.TEXTURE0);
  473. gl.bindTexture(target, this._texture);
  474. gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, minificationFilter);
  475. gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, magnificationFilter);
  476. gl.texParameteri(target, gl.TEXTURE_WRAP_S, sampler.wrapS);
  477. gl.texParameteri(target, gl.TEXTURE_WRAP_T, sampler.wrapT);
  478. if (defined(this._textureFilterAnisotropic)) {
  479. gl.texParameteri(
  480. target,
  481. this._textureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT,
  482. sampler.maximumAnisotropy
  483. );
  484. }
  485. gl.bindTexture(target, null);
  486. this._sampler = sampler;
  487. },
  488. },
  489. pixelFormat: {
  490. get: function () {
  491. return this._pixelFormat;
  492. },
  493. },
  494. pixelDatatype: {
  495. get: function () {
  496. return this._pixelDatatype;
  497. },
  498. },
  499. width: {
  500. get: function () {
  501. return this._size;
  502. },
  503. },
  504. height: {
  505. get: function () {
  506. return this._size;
  507. },
  508. },
  509. sizeInBytes: {
  510. get: function () {
  511. if (this._hasMipmap) {
  512. return Math.floor((this._sizeInBytes * 4) / 3);
  513. }
  514. return this._sizeInBytes;
  515. },
  516. },
  517. preMultiplyAlpha: {
  518. get: function () {
  519. return this._preMultiplyAlpha;
  520. },
  521. },
  522. flipY: {
  523. get: function () {
  524. return this._flipY;
  525. },
  526. },
  527. _target: {
  528. get: function () {
  529. return this._textureTarget;
  530. },
  531. },
  532. });
  533. /**
  534. * Generates a complete mipmap chain for each cubemap face.
  535. *
  536. * @param {MipmapHint} [hint=MipmapHint.DONT_CARE] A performance vs. quality hint.
  537. *
  538. * @exception {DeveloperError} hint is invalid.
  539. * @exception {DeveloperError} This CubeMap's width must be a power of two to call generateMipmap().
  540. * @exception {DeveloperError} This CubeMap's height must be a power of two to call generateMipmap().
  541. * @exception {DeveloperError} This CubeMap was destroyed, i.e., destroy() was called.
  542. *
  543. * @example
  544. * // Generate mipmaps, and then set the sampler so mipmaps are used for
  545. * // minification when the cube map is sampled.
  546. * cubeMap.generateMipmap();
  547. * cubeMap.sampler = new Sampler({
  548. * minificationFilter : Cesium.TextureMinificationFilter.NEAREST_MIPMAP_LINEAR
  549. * });
  550. */
  551. CubeMap.prototype.generateMipmap = function (hint) {
  552. hint = defaultValue(hint, MipmapHint.DONT_CARE);
  553. //>>includeStart('debug', pragmas.debug);
  554. if (this._size > 1 && !CesiumMath.isPowerOfTwo(this._size)) {
  555. throw new DeveloperError(
  556. "width and height must be a power of two to call generateMipmap()."
  557. );
  558. }
  559. if (!MipmapHint.validate(hint)) {
  560. throw new DeveloperError("hint is invalid.");
  561. }
  562. //>>includeEnd('debug');
  563. this._hasMipmap = true;
  564. const gl = this._context._gl;
  565. const target = this._textureTarget;
  566. gl.hint(gl.GENERATE_MIPMAP_HINT, hint);
  567. gl.activeTexture(gl.TEXTURE0);
  568. gl.bindTexture(target, this._texture);
  569. gl.generateMipmap(target);
  570. gl.bindTexture(target, null);
  571. };
  572. CubeMap.prototype.isDestroyed = function () {
  573. return false;
  574. };
  575. CubeMap.prototype.destroy = function () {
  576. this._context._gl.deleteTexture(this._texture);
  577. this._positiveX = destroyObject(this._positiveX);
  578. this._negativeX = destroyObject(this._negativeX);
  579. this._positiveY = destroyObject(this._positiveY);
  580. this._negativeY = destroyObject(this._negativeY);
  581. this._positiveZ = destroyObject(this._positiveZ);
  582. this._negativeZ = destroyObject(this._negativeZ);
  583. return destroyObject(this);
  584. };
  585. export default CubeMap;