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

Texture.js 36KB

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