Texture.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035
  1. import Cartesian2 from "../Core/Cartesian2.js";
  2. import Check from "../Core/Check.js";
  3. import createGuid from "../Core/createGuid.js";
  4. import defaultValue from "../Core/defaultValue.js";
  5. import defined from "../Core/defined.js";
  6. import destroyObject from "../Core/destroyObject.js";
  7. import DeveloperError from "../Core/DeveloperError.js";
  8. import CesiumMath from "../Core/Math.js";
  9. import PixelFormat from "../Core/PixelFormat.js";
  10. import ContextLimits from "./ContextLimits.js";
  11. import MipmapHint from "./MipmapHint.js";
  12. import PixelDatatype from "./PixelDatatype.js";
  13. import Sampler from "./Sampler.js";
  14. import TextureMagnificationFilter from "./TextureMagnificationFilter.js";
  15. import TextureMinificationFilter from "./TextureMinificationFilter.js";
  16. /**
  17. * @private
  18. */
  19. function Texture(options) {
  20. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  21. //>>includeStart('debug', pragmas.debug);
  22. Check.defined("options.context", options.context);
  23. //>>includeEnd('debug');
  24. const context = options.context;
  25. let width = options.width;
  26. let height = options.height;
  27. const source = options.source;
  28. if (defined(source)) {
  29. if (!defined(width)) {
  30. width = defaultValue(source.videoWidth, source.width);
  31. }
  32. if (!defined(height)) {
  33. height = defaultValue(source.videoHeight, source.height);
  34. }
  35. }
  36. const pixelFormat = defaultValue(options.pixelFormat, PixelFormat.RGBA);
  37. const pixelDatatype = defaultValue(
  38. options.pixelDatatype,
  39. PixelDatatype.UNSIGNED_BYTE
  40. );
  41. const internalFormat = PixelFormat.toInternalFormat(
  42. pixelFormat,
  43. pixelDatatype,
  44. context
  45. );
  46. const isCompressed = PixelFormat.isCompressedFormat(internalFormat);
  47. //>>includeStart('debug', pragmas.debug);
  48. if (!defined(width) || !defined(height)) {
  49. throw new DeveloperError(
  50. "options requires a source field to create an initialized texture or width and height fields to create a blank texture."
  51. );
  52. }
  53. Check.typeOf.number.greaterThan("width", width, 0);
  54. if (width > ContextLimits.maximumTextureSize) {
  55. throw new DeveloperError(
  56. `Width must be less than or equal to the maximum texture size (${ContextLimits.maximumTextureSize}). Check maximumTextureSize.`
  57. );
  58. }
  59. Check.typeOf.number.greaterThan("height", height, 0);
  60. if (height > ContextLimits.maximumTextureSize) {
  61. throw new DeveloperError(
  62. `Height must be less than or equal to the maximum texture size (${ContextLimits.maximumTextureSize}). Check maximumTextureSize.`
  63. );
  64. }
  65. if (!PixelFormat.validate(pixelFormat)) {
  66. throw new DeveloperError("Invalid options.pixelFormat.");
  67. }
  68. if (!isCompressed && !PixelDatatype.validate(pixelDatatype)) {
  69. throw new DeveloperError("Invalid options.pixelDatatype.");
  70. }
  71. if (
  72. pixelFormat === PixelFormat.DEPTH_COMPONENT &&
  73. pixelDatatype !== PixelDatatype.UNSIGNED_SHORT &&
  74. pixelDatatype !== PixelDatatype.UNSIGNED_INT
  75. ) {
  76. throw new DeveloperError(
  77. "When options.pixelFormat is DEPTH_COMPONENT, options.pixelDatatype must be UNSIGNED_SHORT or UNSIGNED_INT."
  78. );
  79. }
  80. if (
  81. pixelFormat === PixelFormat.DEPTH_STENCIL &&
  82. pixelDatatype !== PixelDatatype.UNSIGNED_INT_24_8
  83. ) {
  84. throw new DeveloperError(
  85. "When options.pixelFormat is DEPTH_STENCIL, options.pixelDatatype must be UNSIGNED_INT_24_8."
  86. );
  87. }
  88. if (pixelDatatype === PixelDatatype.FLOAT && !context.floatingPointTexture) {
  89. throw new DeveloperError(
  90. "When options.pixelDatatype is FLOAT, this WebGL implementation must support the OES_texture_float extension. Check context.floatingPointTexture."
  91. );
  92. }
  93. if (
  94. pixelDatatype === PixelDatatype.HALF_FLOAT &&
  95. !context.halfFloatingPointTexture
  96. ) {
  97. throw new DeveloperError(
  98. "When options.pixelDatatype is HALF_FLOAT, this WebGL implementation must support the OES_texture_half_float extension. Check context.halfFloatingPointTexture."
  99. );
  100. }
  101. if (PixelFormat.isDepthFormat(pixelFormat)) {
  102. if (defined(source)) {
  103. throw new DeveloperError(
  104. "When options.pixelFormat is DEPTH_COMPONENT or DEPTH_STENCIL, source cannot be provided."
  105. );
  106. }
  107. if (!context.depthTexture) {
  108. throw new DeveloperError(
  109. "When options.pixelFormat is DEPTH_COMPONENT or DEPTH_STENCIL, this WebGL implementation must support WEBGL_depth_texture. Check context.depthTexture."
  110. );
  111. }
  112. }
  113. if (isCompressed) {
  114. if (!defined(source) || !defined(source.arrayBufferView)) {
  115. throw new DeveloperError(
  116. "When options.pixelFormat is compressed, options.source.arrayBufferView must be defined."
  117. );
  118. }
  119. if (PixelFormat.isDXTFormat(internalFormat) && !context.s3tc) {
  120. throw new DeveloperError(
  121. "When options.pixelFormat is S3TC compressed, this WebGL implementation must support the WEBGL_compressed_texture_s3tc extension. Check context.s3tc."
  122. );
  123. } else if (PixelFormat.isPVRTCFormat(internalFormat) && !context.pvrtc) {
  124. throw new DeveloperError(
  125. "When options.pixelFormat is PVRTC compressed, this WebGL implementation must support the WEBGL_compressed_texture_pvrtc extension. Check context.pvrtc."
  126. );
  127. } else if (PixelFormat.isASTCFormat(internalFormat) && !context.astc) {
  128. throw new DeveloperError(
  129. "When options.pixelFormat is ASTC compressed, this WebGL implementation must support the WEBGL_compressed_texture_astc extension. Check context.astc."
  130. );
  131. } else if (PixelFormat.isETC2Format(internalFormat) && !context.etc) {
  132. throw new DeveloperError(
  133. "When options.pixelFormat is ETC2 compressed, this WebGL implementation must support the WEBGL_compressed_texture_etc extension. Check context.etc."
  134. );
  135. } else if (PixelFormat.isETC1Format(internalFormat) && !context.etc1) {
  136. throw new DeveloperError(
  137. "When options.pixelFormat is ETC1 compressed, this WebGL implementation must support the WEBGL_compressed_texture_etc1 extension. Check context.etc1."
  138. );
  139. } else if (PixelFormat.isBC7Format(internalFormat) && !context.bc7) {
  140. throw new DeveloperError(
  141. "When options.pixelFormat is BC7 compressed, this WebGL implementation must support the EXT_texture_compression_bptc extension. Check context.bc7."
  142. );
  143. }
  144. if (
  145. PixelFormat.compressedTextureSizeInBytes(
  146. internalFormat,
  147. width,
  148. height
  149. ) !== source.arrayBufferView.byteLength
  150. ) {
  151. throw new DeveloperError(
  152. "The byte length of the array buffer is invalid for the compressed texture with the given width and height."
  153. );
  154. }
  155. }
  156. //>>includeEnd('debug');
  157. // Use premultiplied alpha for opaque textures should perform better on Chrome:
  158. // http://media.tojicode.com/webglCamp4/#20
  159. const preMultiplyAlpha =
  160. options.preMultiplyAlpha ||
  161. pixelFormat === PixelFormat.RGB ||
  162. pixelFormat === PixelFormat.LUMINANCE;
  163. const flipY = defaultValue(options.flipY, true);
  164. const skipColorSpaceConversion = defaultValue(
  165. options.skipColorSpaceConversion,
  166. false
  167. );
  168. let initialized = true;
  169. const gl = context._gl;
  170. const textureTarget = gl.TEXTURE_2D;
  171. const texture = gl.createTexture();
  172. gl.activeTexture(gl.TEXTURE0);
  173. gl.bindTexture(textureTarget, texture);
  174. let unpackAlignment = 4;
  175. if (defined(source) && defined(source.arrayBufferView) && !isCompressed) {
  176. unpackAlignment = PixelFormat.alignmentInBytes(
  177. pixelFormat,
  178. pixelDatatype,
  179. width
  180. );
  181. }
  182. gl.pixelStorei(gl.UNPACK_ALIGNMENT, unpackAlignment);
  183. if (skipColorSpaceConversion) {
  184. gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);
  185. } else {
  186. gl.pixelStorei(
  187. gl.UNPACK_COLORSPACE_CONVERSION_WEBGL,
  188. gl.BROWSER_DEFAULT_WEBGL
  189. );
  190. }
  191. if (defined(source)) {
  192. if (defined(source.arrayBufferView)) {
  193. gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
  194. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
  195. // Source: typed array
  196. let arrayBufferView = source.arrayBufferView;
  197. let i, mipWidth, mipHeight;
  198. if (isCompressed) {
  199. gl.compressedTexImage2D(
  200. textureTarget,
  201. 0,
  202. internalFormat,
  203. width,
  204. height,
  205. 0,
  206. arrayBufferView
  207. );
  208. if (defined(source.mipLevels)) {
  209. mipWidth = width;
  210. mipHeight = height;
  211. for (i = 0; i < source.mipLevels.length; ++i) {
  212. mipWidth = Math.floor(mipWidth / 2) | 0;
  213. if (mipWidth < 1) {
  214. mipWidth = 1;
  215. }
  216. mipHeight = Math.floor(mipHeight / 2) | 0;
  217. if (mipHeight < 1) {
  218. mipHeight = 1;
  219. }
  220. gl.compressedTexImage2D(
  221. textureTarget,
  222. i + 1,
  223. internalFormat,
  224. mipWidth,
  225. mipHeight,
  226. 0,
  227. source.mipLevels[i]
  228. );
  229. }
  230. }
  231. } else {
  232. if (flipY) {
  233. arrayBufferView = PixelFormat.flipY(
  234. arrayBufferView,
  235. pixelFormat,
  236. pixelDatatype,
  237. width,
  238. height
  239. );
  240. }
  241. gl.texImage2D(
  242. textureTarget,
  243. 0,
  244. internalFormat,
  245. width,
  246. height,
  247. 0,
  248. pixelFormat,
  249. PixelDatatype.toWebGLConstant(pixelDatatype, context),
  250. arrayBufferView
  251. );
  252. if (defined(source.mipLevels)) {
  253. mipWidth = width;
  254. mipHeight = height;
  255. for (i = 0; i < source.mipLevels.length; ++i) {
  256. mipWidth = Math.floor(mipWidth / 2) | 0;
  257. if (mipWidth < 1) {
  258. mipWidth = 1;
  259. }
  260. mipHeight = Math.floor(mipHeight / 2) | 0;
  261. if (mipHeight < 1) {
  262. mipHeight = 1;
  263. }
  264. gl.texImage2D(
  265. textureTarget,
  266. i + 1,
  267. internalFormat,
  268. mipWidth,
  269. mipHeight,
  270. 0,
  271. pixelFormat,
  272. PixelDatatype.toWebGLConstant(pixelDatatype, context),
  273. source.mipLevels[i]
  274. );
  275. }
  276. }
  277. }
  278. } else if (defined(source.framebuffer)) {
  279. gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
  280. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
  281. // Source: framebuffer
  282. if (source.framebuffer !== context.defaultFramebuffer) {
  283. source.framebuffer._bind();
  284. }
  285. gl.copyTexImage2D(
  286. textureTarget,
  287. 0,
  288. internalFormat,
  289. source.xOffset,
  290. source.yOffset,
  291. width,
  292. height,
  293. 0
  294. );
  295. if (source.framebuffer !== context.defaultFramebuffer) {
  296. source.framebuffer._unBind();
  297. }
  298. } else {
  299. // Only valid for DOM-Element uploads
  300. gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, preMultiplyAlpha);
  301. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
  302. // Source: ImageData, HTMLImageElement, HTMLCanvasElement, or HTMLVideoElement
  303. gl.texImage2D(
  304. textureTarget,
  305. 0,
  306. internalFormat,
  307. pixelFormat,
  308. PixelDatatype.toWebGLConstant(pixelDatatype, context),
  309. source
  310. );
  311. }
  312. } else {
  313. gl.texImage2D(
  314. textureTarget,
  315. 0,
  316. internalFormat,
  317. width,
  318. height,
  319. 0,
  320. pixelFormat,
  321. PixelDatatype.toWebGLConstant(pixelDatatype, context),
  322. null
  323. );
  324. initialized = false;
  325. }
  326. gl.bindTexture(textureTarget, null);
  327. let sizeInBytes;
  328. if (isCompressed) {
  329. sizeInBytes = PixelFormat.compressedTextureSizeInBytes(
  330. pixelFormat,
  331. width,
  332. height
  333. );
  334. } else {
  335. sizeInBytes = PixelFormat.textureSizeInBytes(
  336. pixelFormat,
  337. pixelDatatype,
  338. width,
  339. height
  340. );
  341. }
  342. this._id = createGuid();
  343. this._context = context;
  344. this._textureFilterAnisotropic = context._textureFilterAnisotropic;
  345. this._textureTarget = textureTarget;
  346. this._texture = texture;
  347. this._internalFormat = internalFormat;
  348. this._pixelFormat = pixelFormat;
  349. this._pixelDatatype = pixelDatatype;
  350. this._width = width;
  351. this._height = height;
  352. this._dimensions = new Cartesian2(width, height);
  353. this._hasMipmap = false;
  354. this._sizeInBytes = sizeInBytes;
  355. this._preMultiplyAlpha = preMultiplyAlpha;
  356. this._flipY = flipY;
  357. this._initialized = initialized;
  358. this._sampler = undefined;
  359. this.sampler = defined(options.sampler) ? options.sampler : new Sampler();
  360. }
  361. /**
  362. * This function is identical to using the Texture constructor except that it can be
  363. * replaced with a mock/spy in tests.
  364. * @private
  365. */
  366. Texture.create = function (options) {
  367. return new Texture(options);
  368. };
  369. /**
  370. * Creates a texture, and copies a subimage of the framebuffer to it. When called without arguments,
  371. * the texture is the same width and height as the framebuffer and contains its contents.
  372. *
  373. * @param {object} options Object with the following properties:
  374. * @param {Context} options.context The context in which the Texture gets created.
  375. * @param {PixelFormat} [options.pixelFormat=PixelFormat.RGB] The texture's internal pixel format.
  376. * @param {number} [options.framebufferXOffset=0] An offset in the x direction in the framebuffer where copying begins from.
  377. * @param {number} [options.framebufferYOffset=0] An offset in the y direction in the framebuffer where copying begins from.
  378. * @param {number} [options.width=canvas.clientWidth] The width of the texture in texels.
  379. * @param {number} [options.height=canvas.clientHeight] The height of the texture in texels.
  380. * @param {Framebuffer} [options.framebuffer=defaultFramebuffer] The framebuffer from which to create the texture. If this
  381. * parameter is not specified, the default framebuffer is used.
  382. * @returns {Texture} A texture with contents from the framebuffer.
  383. *
  384. * @exception {DeveloperError} Invalid pixelFormat.
  385. * @exception {DeveloperError} pixelFormat cannot be DEPTH_COMPONENT, DEPTH_STENCIL or a compressed format.
  386. * @exception {DeveloperError} framebufferXOffset must be greater than or equal to zero.
  387. * @exception {DeveloperError} framebufferYOffset must be greater than or equal to zero.
  388. * @exception {DeveloperError} framebufferXOffset + width must be less than or equal to canvas.clientWidth.
  389. * @exception {DeveloperError} framebufferYOffset + height must be less than or equal to canvas.clientHeight.
  390. *
  391. *
  392. * @example
  393. * // Create a texture with the contents of the framebuffer.
  394. * const t = Texture.fromFramebuffer({
  395. * context : context
  396. * });
  397. *
  398. * @see Sampler
  399. *
  400. * @private
  401. */
  402. Texture.fromFramebuffer = function (options) {
  403. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  404. //>>includeStart('debug', pragmas.debug);
  405. Check.defined("options.context", options.context);
  406. //>>includeEnd('debug');
  407. const context = options.context;
  408. const gl = context._gl;
  409. const pixelFormat = defaultValue(options.pixelFormat, PixelFormat.RGB);
  410. const framebufferXOffset = defaultValue(options.framebufferXOffset, 0);
  411. const framebufferYOffset = defaultValue(options.framebufferYOffset, 0);
  412. const width = defaultValue(options.width, gl.drawingBufferWidth);
  413. const height = defaultValue(options.height, gl.drawingBufferHeight);
  414. const framebuffer = options.framebuffer;
  415. //>>includeStart('debug', pragmas.debug);
  416. if (!PixelFormat.validate(pixelFormat)) {
  417. throw new DeveloperError("Invalid pixelFormat.");
  418. }
  419. if (
  420. PixelFormat.isDepthFormat(pixelFormat) ||
  421. PixelFormat.isCompressedFormat(pixelFormat)
  422. ) {
  423. throw new DeveloperError(
  424. "pixelFormat cannot be DEPTH_COMPONENT, DEPTH_STENCIL or a compressed format."
  425. );
  426. }
  427. Check.defined("options.context", options.context);
  428. Check.typeOf.number.greaterThanOrEquals(
  429. "framebufferXOffset",
  430. framebufferXOffset,
  431. 0
  432. );
  433. Check.typeOf.number.greaterThanOrEquals(
  434. "framebufferYOffset",
  435. framebufferYOffset,
  436. 0
  437. );
  438. if (framebufferXOffset + width > gl.drawingBufferWidth) {
  439. throw new DeveloperError(
  440. "framebufferXOffset + width must be less than or equal to drawingBufferWidth"
  441. );
  442. }
  443. if (framebufferYOffset + height > gl.drawingBufferHeight) {
  444. throw new DeveloperError(
  445. "framebufferYOffset + height must be less than or equal to drawingBufferHeight."
  446. );
  447. }
  448. //>>includeEnd('debug');
  449. const texture = new Texture({
  450. context: context,
  451. width: width,
  452. height: height,
  453. pixelFormat: pixelFormat,
  454. source: {
  455. framebuffer: defined(framebuffer)
  456. ? framebuffer
  457. : context.defaultFramebuffer,
  458. xOffset: framebufferXOffset,
  459. yOffset: framebufferYOffset,
  460. width: width,
  461. height: height,
  462. },
  463. });
  464. return texture;
  465. };
  466. Object.defineProperties(Texture.prototype, {
  467. /**
  468. * A unique id for the texture
  469. * @memberof Texture.prototype
  470. * @type {string}
  471. * @readonly
  472. * @private
  473. */
  474. id: {
  475. get: function () {
  476. return this._id;
  477. },
  478. },
  479. /**
  480. * The sampler to use when sampling this texture.
  481. * Create a sampler by calling {@link Sampler}. If this
  482. * parameter is not specified, a default sampler is used. The default sampler clamps texture
  483. * coordinates in both directions, uses linear filtering for both magnification and minification,
  484. * and uses a maximum anisotropy of 1.0.
  485. * @memberof Texture.prototype
  486. * @type {object}
  487. */
  488. sampler: {
  489. get: function () {
  490. return this._sampler;
  491. },
  492. set: function (sampler) {
  493. let minificationFilter = sampler.minificationFilter;
  494. let magnificationFilter = sampler.magnificationFilter;
  495. const context = this._context;
  496. const pixelFormat = this._pixelFormat;
  497. const pixelDatatype = this._pixelDatatype;
  498. const mipmap =
  499. minificationFilter ===
  500. TextureMinificationFilter.NEAREST_MIPMAP_NEAREST ||
  501. minificationFilter ===
  502. TextureMinificationFilter.NEAREST_MIPMAP_LINEAR ||
  503. minificationFilter ===
  504. TextureMinificationFilter.LINEAR_MIPMAP_NEAREST ||
  505. minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_LINEAR;
  506. // float textures only support nearest filtering unless the linear extensions are supported, so override the sampler's settings
  507. if (
  508. (pixelDatatype === PixelDatatype.FLOAT &&
  509. !context.textureFloatLinear) ||
  510. (pixelDatatype === PixelDatatype.HALF_FLOAT &&
  511. !context.textureHalfFloatLinear)
  512. ) {
  513. minificationFilter = mipmap
  514. ? TextureMinificationFilter.NEAREST_MIPMAP_NEAREST
  515. : TextureMinificationFilter.NEAREST;
  516. magnificationFilter = TextureMagnificationFilter.NEAREST;
  517. }
  518. // WebGL 2 depth texture only support nearest filtering. See section 3.8.13 OpenGL ES 3 spec
  519. if (context.webgl2) {
  520. if (PixelFormat.isDepthFormat(pixelFormat)) {
  521. minificationFilter = TextureMinificationFilter.NEAREST;
  522. magnificationFilter = TextureMagnificationFilter.NEAREST;
  523. }
  524. }
  525. const gl = context._gl;
  526. const target = this._textureTarget;
  527. gl.activeTexture(gl.TEXTURE0);
  528. gl.bindTexture(target, this._texture);
  529. gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, minificationFilter);
  530. gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, magnificationFilter);
  531. gl.texParameteri(target, gl.TEXTURE_WRAP_S, sampler.wrapS);
  532. gl.texParameteri(target, gl.TEXTURE_WRAP_T, sampler.wrapT);
  533. if (defined(this._textureFilterAnisotropic)) {
  534. gl.texParameteri(
  535. target,
  536. this._textureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT,
  537. sampler.maximumAnisotropy
  538. );
  539. }
  540. gl.bindTexture(target, null);
  541. this._sampler = sampler;
  542. },
  543. },
  544. pixelFormat: {
  545. get: function () {
  546. return this._pixelFormat;
  547. },
  548. },
  549. pixelDatatype: {
  550. get: function () {
  551. return this._pixelDatatype;
  552. },
  553. },
  554. dimensions: {
  555. get: function () {
  556. return this._dimensions;
  557. },
  558. },
  559. preMultiplyAlpha: {
  560. get: function () {
  561. return this._preMultiplyAlpha;
  562. },
  563. },
  564. flipY: {
  565. get: function () {
  566. return this._flipY;
  567. },
  568. },
  569. width: {
  570. get: function () {
  571. return this._width;
  572. },
  573. },
  574. height: {
  575. get: function () {
  576. return this._height;
  577. },
  578. },
  579. sizeInBytes: {
  580. get: function () {
  581. if (this._hasMipmap) {
  582. return Math.floor((this._sizeInBytes * 4) / 3);
  583. }
  584. return this._sizeInBytes;
  585. },
  586. },
  587. _target: {
  588. get: function () {
  589. return this._textureTarget;
  590. },
  591. },
  592. });
  593. /**
  594. * Copy new image data into this texture, from a source {@link ImageData}, {@link HTMLImageElement}, {@link HTMLCanvasElement}, or {@link HTMLVideoElement}.
  595. * or an object with width, height, and arrayBufferView properties.
  596. * @param {object} options Object with the following properties:
  597. * @param {object} options.source The source {@link ImageData}, {@link HTMLImageElement}, {@link HTMLCanvasElement}, or {@link HTMLVideoElement},
  598. * or an object with width, height, and arrayBufferView properties.
  599. * @param {number} [options.xOffset=0] The offset in the x direction within the texture to copy into.
  600. * @param {number} [options.yOffset=0] The offset in the y direction within the texture to copy into.
  601. * @param {boolean} [options.skipColorSpaceConversion=false] If true, any custom gamma or color profiles in the texture will be ignored.
  602. *
  603. * @exception {DeveloperError} Cannot call copyFrom when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.
  604. * @exception {DeveloperError} Cannot call copyFrom with a compressed texture pixel format.
  605. * @exception {DeveloperError} xOffset must be greater than or equal to zero.
  606. * @exception {DeveloperError} yOffset must be greater than or equal to zero.
  607. * @exception {DeveloperError} xOffset + source.width must be less than or equal to width.
  608. * @exception {DeveloperError} yOffset + source.height must be less than or equal to height.
  609. * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
  610. *
  611. * @example
  612. * texture.copyFrom({
  613. * source: {
  614. * width : 1,
  615. * height : 1,
  616. * arrayBufferView : new Uint8Array([255, 0, 0, 255])
  617. * }
  618. * });
  619. */
  620. Texture.prototype.copyFrom = function (options) {
  621. //>>includeStart('debug', pragmas.debug);
  622. Check.defined("options", options);
  623. //>>includeEnd('debug');
  624. const xOffset = defaultValue(options.xOffset, 0);
  625. const yOffset = defaultValue(options.yOffset, 0);
  626. //>>includeStart('debug', pragmas.debug);
  627. Check.defined("options.source", options.source);
  628. if (PixelFormat.isDepthFormat(this._pixelFormat)) {
  629. throw new DeveloperError(
  630. "Cannot call copyFrom when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL."
  631. );
  632. }
  633. if (PixelFormat.isCompressedFormat(this._pixelFormat)) {
  634. throw new DeveloperError(
  635. "Cannot call copyFrom with a compressed texture pixel format."
  636. );
  637. }
  638. Check.typeOf.number.greaterThanOrEquals("xOffset", xOffset, 0);
  639. Check.typeOf.number.greaterThanOrEquals("yOffset", yOffset, 0);
  640. Check.typeOf.number.lessThanOrEquals(
  641. "xOffset + options.source.width",
  642. xOffset + options.source.width,
  643. this._width
  644. );
  645. Check.typeOf.number.lessThanOrEquals(
  646. "yOffset + options.source.height",
  647. yOffset + options.source.height,
  648. this._height
  649. );
  650. //>>includeEnd('debug');
  651. const source = options.source;
  652. const context = this._context;
  653. const gl = context._gl;
  654. const target = this._textureTarget;
  655. gl.activeTexture(gl.TEXTURE0);
  656. gl.bindTexture(target, this._texture);
  657. const width = source.width;
  658. const height = source.height;
  659. let arrayBufferView = source.arrayBufferView;
  660. const textureWidth = this._width;
  661. const textureHeight = this._height;
  662. const internalFormat = this._internalFormat;
  663. const pixelFormat = this._pixelFormat;
  664. const pixelDatatype = this._pixelDatatype;
  665. const preMultiplyAlpha = this._preMultiplyAlpha;
  666. const flipY = this._flipY;
  667. const skipColorSpaceConversion = defaultValue(
  668. options.skipColorSpaceConversion,
  669. false
  670. );
  671. let unpackAlignment = 4;
  672. if (defined(arrayBufferView)) {
  673. unpackAlignment = PixelFormat.alignmentInBytes(
  674. pixelFormat,
  675. pixelDatatype,
  676. width
  677. );
  678. }
  679. gl.pixelStorei(gl.UNPACK_ALIGNMENT, unpackAlignment);
  680. if (skipColorSpaceConversion) {
  681. gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);
  682. } else {
  683. gl.pixelStorei(
  684. gl.UNPACK_COLORSPACE_CONVERSION_WEBGL,
  685. gl.BROWSER_DEFAULT_WEBGL
  686. );
  687. }
  688. let uploaded = false;
  689. if (!this._initialized) {
  690. if (
  691. xOffset === 0 &&
  692. yOffset === 0 &&
  693. width === textureWidth &&
  694. height === textureHeight
  695. ) {
  696. // initialize the entire texture
  697. if (defined(arrayBufferView)) {
  698. gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
  699. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
  700. if (flipY) {
  701. arrayBufferView = PixelFormat.flipY(
  702. arrayBufferView,
  703. pixelFormat,
  704. pixelDatatype,
  705. textureWidth,
  706. textureHeight
  707. );
  708. }
  709. gl.texImage2D(
  710. target,
  711. 0,
  712. internalFormat,
  713. textureWidth,
  714. textureHeight,
  715. 0,
  716. pixelFormat,
  717. PixelDatatype.toWebGLConstant(pixelDatatype, context),
  718. arrayBufferView
  719. );
  720. } else {
  721. // Only valid for DOM-Element uploads
  722. gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, preMultiplyAlpha);
  723. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
  724. gl.texImage2D(
  725. target,
  726. 0,
  727. internalFormat,
  728. pixelFormat,
  729. PixelDatatype.toWebGLConstant(pixelDatatype, context),
  730. source
  731. );
  732. }
  733. uploaded = true;
  734. } else {
  735. gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
  736. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
  737. // initialize the entire texture to zero
  738. const bufferView = PixelFormat.createTypedArray(
  739. pixelFormat,
  740. pixelDatatype,
  741. textureWidth,
  742. textureHeight
  743. );
  744. gl.texImage2D(
  745. target,
  746. 0,
  747. internalFormat,
  748. textureWidth,
  749. textureHeight,
  750. 0,
  751. pixelFormat,
  752. PixelDatatype.toWebGLConstant(pixelDatatype, context),
  753. bufferView
  754. );
  755. }
  756. this._initialized = true;
  757. }
  758. if (!uploaded) {
  759. if (defined(arrayBufferView)) {
  760. gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
  761. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
  762. if (flipY) {
  763. arrayBufferView = PixelFormat.flipY(
  764. arrayBufferView,
  765. pixelFormat,
  766. pixelDatatype,
  767. width,
  768. height
  769. );
  770. }
  771. gl.texSubImage2D(
  772. target,
  773. 0,
  774. xOffset,
  775. yOffset,
  776. width,
  777. height,
  778. pixelFormat,
  779. PixelDatatype.toWebGLConstant(pixelDatatype, context),
  780. arrayBufferView
  781. );
  782. } else {
  783. // Only valid for DOM-Element uploads
  784. gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, preMultiplyAlpha);
  785. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
  786. gl.texSubImage2D(
  787. target,
  788. 0,
  789. xOffset,
  790. yOffset,
  791. pixelFormat,
  792. PixelDatatype.toWebGLConstant(pixelDatatype, context),
  793. source
  794. );
  795. }
  796. }
  797. gl.bindTexture(target, null);
  798. };
  799. /**
  800. * @param {number} [xOffset=0] The offset in the x direction within the texture to copy into.
  801. * @param {number} [yOffset=0] The offset in the y direction within the texture to copy into.
  802. * @param {number} [framebufferXOffset=0] optional
  803. * @param {number} [framebufferYOffset=0] optional
  804. * @param {number} [width=width] optional
  805. * @param {number} [height=height] optional
  806. *
  807. * @exception {DeveloperError} Cannot call copyFromFramebuffer when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.
  808. * @exception {DeveloperError} Cannot call copyFromFramebuffer when the texture pixel data type is FLOAT.
  809. * @exception {DeveloperError} Cannot call copyFromFramebuffer when the texture pixel data type is HALF_FLOAT.
  810. * @exception {DeveloperError} Cannot call copyFrom with a compressed texture pixel format.
  811. * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
  812. * @exception {DeveloperError} xOffset must be greater than or equal to zero.
  813. * @exception {DeveloperError} yOffset must be greater than or equal to zero.
  814. * @exception {DeveloperError} framebufferXOffset must be greater than or equal to zero.
  815. * @exception {DeveloperError} framebufferYOffset must be greater than or equal to zero.
  816. * @exception {DeveloperError} xOffset + width must be less than or equal to width.
  817. * @exception {DeveloperError} yOffset + height must be less than or equal to height.
  818. */
  819. Texture.prototype.copyFromFramebuffer = function (
  820. xOffset,
  821. yOffset,
  822. framebufferXOffset,
  823. framebufferYOffset,
  824. width,
  825. height
  826. ) {
  827. xOffset = defaultValue(xOffset, 0);
  828. yOffset = defaultValue(yOffset, 0);
  829. framebufferXOffset = defaultValue(framebufferXOffset, 0);
  830. framebufferYOffset = defaultValue(framebufferYOffset, 0);
  831. width = defaultValue(width, this._width);
  832. height = defaultValue(height, this._height);
  833. //>>includeStart('debug', pragmas.debug);
  834. if (PixelFormat.isDepthFormat(this._pixelFormat)) {
  835. throw new DeveloperError(
  836. "Cannot call copyFromFramebuffer when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL."
  837. );
  838. }
  839. if (this._pixelDatatype === PixelDatatype.FLOAT) {
  840. throw new DeveloperError(
  841. "Cannot call copyFromFramebuffer when the texture pixel data type is FLOAT."
  842. );
  843. }
  844. if (this._pixelDatatype === PixelDatatype.HALF_FLOAT) {
  845. throw new DeveloperError(
  846. "Cannot call copyFromFramebuffer when the texture pixel data type is HALF_FLOAT."
  847. );
  848. }
  849. if (PixelFormat.isCompressedFormat(this._pixelFormat)) {
  850. throw new DeveloperError(
  851. "Cannot call copyFrom with a compressed texture pixel format."
  852. );
  853. }
  854. Check.typeOf.number.greaterThanOrEquals("xOffset", xOffset, 0);
  855. Check.typeOf.number.greaterThanOrEquals("yOffset", yOffset, 0);
  856. Check.typeOf.number.greaterThanOrEquals(
  857. "framebufferXOffset",
  858. framebufferXOffset,
  859. 0
  860. );
  861. Check.typeOf.number.greaterThanOrEquals(
  862. "framebufferYOffset",
  863. framebufferYOffset,
  864. 0
  865. );
  866. Check.typeOf.number.lessThanOrEquals(
  867. "xOffset + width",
  868. xOffset + width,
  869. this._width
  870. );
  871. Check.typeOf.number.lessThanOrEquals(
  872. "yOffset + height",
  873. yOffset + height,
  874. this._height
  875. );
  876. //>>includeEnd('debug');
  877. const gl = this._context._gl;
  878. const target = this._textureTarget;
  879. gl.activeTexture(gl.TEXTURE0);
  880. gl.bindTexture(target, this._texture);
  881. gl.copyTexSubImage2D(
  882. target,
  883. 0,
  884. xOffset,
  885. yOffset,
  886. framebufferXOffset,
  887. framebufferYOffset,
  888. width,
  889. height
  890. );
  891. gl.bindTexture(target, null);
  892. this._initialized = true;
  893. };
  894. /**
  895. * @param {MipmapHint} [hint=MipmapHint.DONT_CARE] optional.
  896. *
  897. * @exception {DeveloperError} Cannot call generateMipmap when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.
  898. * @exception {DeveloperError} Cannot call generateMipmap when the texture pixel format is a compressed format.
  899. * @exception {DeveloperError} hint is invalid.
  900. * @exception {DeveloperError} This texture's width must be a power of two to call generateMipmap() in a WebGL1 context.
  901. * @exception {DeveloperError} This texture's height must be a power of two to call generateMipmap() in a WebGL1 context.
  902. * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
  903. */
  904. Texture.prototype.generateMipmap = function (hint) {
  905. hint = defaultValue(hint, MipmapHint.DONT_CARE);
  906. //>>includeStart('debug', pragmas.debug);
  907. if (PixelFormat.isDepthFormat(this._pixelFormat)) {
  908. throw new DeveloperError(
  909. "Cannot call generateMipmap when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL."
  910. );
  911. }
  912. if (PixelFormat.isCompressedFormat(this._pixelFormat)) {
  913. throw new DeveloperError(
  914. "Cannot call generateMipmap with a compressed pixel format."
  915. );
  916. }
  917. if (!this._context.webgl2) {
  918. if (this._width > 1 && !CesiumMath.isPowerOfTwo(this._width)) {
  919. throw new DeveloperError(
  920. "width must be a power of two to call generateMipmap() in a WebGL1 context."
  921. );
  922. }
  923. if (this._height > 1 && !CesiumMath.isPowerOfTwo(this._height)) {
  924. throw new DeveloperError(
  925. "height must be a power of two to call generateMipmap() in a WebGL1 context."
  926. );
  927. }
  928. }
  929. if (!MipmapHint.validate(hint)) {
  930. throw new DeveloperError("hint is invalid.");
  931. }
  932. //>>includeEnd('debug');
  933. this._hasMipmap = true;
  934. const gl = this._context._gl;
  935. const target = this._textureTarget;
  936. gl.hint(gl.GENERATE_MIPMAP_HINT, hint);
  937. gl.activeTexture(gl.TEXTURE0);
  938. gl.bindTexture(target, this._texture);
  939. gl.generateMipmap(target);
  940. gl.bindTexture(target, null);
  941. };
  942. Texture.prototype.isDestroyed = function () {
  943. return false;
  944. };
  945. Texture.prototype.destroy = function () {
  946. this._context._gl.deleteTexture(this._texture);
  947. return destroyObject(this);
  948. };
  949. export default Texture;