CubeMap.js 15 KB

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