Texture.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691
  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 defineProperties from '../Core/defineProperties.js';
  7. import destroyObject from '../Core/destroyObject.js';
  8. import DeveloperError from '../Core/DeveloperError.js';
  9. import CesiumMath from '../Core/Math.js';
  10. import PixelFormat from '../Core/PixelFormat.js';
  11. import WebGLConstants from '../Core/WebGLConstants.js';
  12. import ContextLimits from './ContextLimits.js';
  13. import MipmapHint from './MipmapHint.js';
  14. import PixelDatatype from './PixelDatatype.js';
  15. import Sampler from './Sampler.js';
  16. import TextureMagnificationFilter from './TextureMagnificationFilter.js';
  17. import TextureMinificationFilter from './TextureMinificationFilter.js';
  18. /**
  19. * @private
  20. */
  21. function Texture(options) {
  22. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  23. //>>includeStart('debug', pragmas.debug);
  24. Check.defined('options.context', options.context);
  25. //>>includeEnd('debug');
  26. var context = options.context;
  27. var width = options.width;
  28. var height = options.height;
  29. var source = options.source;
  30. if (defined(source)) {
  31. if (!defined(width)) {
  32. width = defaultValue(source.videoWidth, source.width);
  33. }
  34. if (!defined(height)) {
  35. height = defaultValue(source.videoHeight, source.height);
  36. }
  37. }
  38. var pixelFormat = defaultValue(options.pixelFormat, PixelFormat.RGBA);
  39. var pixelDatatype = defaultValue(options.pixelDatatype, PixelDatatype.UNSIGNED_BYTE);
  40. var internalFormat = pixelFormat;
  41. var isCompressed = PixelFormat.isCompressedFormat(internalFormat);
  42. if (context.webgl2) {
  43. if (pixelFormat === PixelFormat.DEPTH_STENCIL) {
  44. internalFormat = WebGLConstants.DEPTH24_STENCIL8;
  45. } else if (pixelFormat === PixelFormat.DEPTH_COMPONENT) {
  46. if (pixelDatatype === PixelDatatype.UNSIGNED_SHORT) {
  47. internalFormat = WebGLConstants.DEPTH_COMPONENT16;
  48. } else if (pixelDatatype === PixelDatatype.UNSIGNED_INT) {
  49. internalFormat = WebGLConstants.DEPTH_COMPONENT24;
  50. }
  51. }
  52. if (pixelDatatype === PixelDatatype.FLOAT) {
  53. switch (pixelFormat) {
  54. case PixelFormat.RGBA:
  55. internalFormat = WebGLConstants.RGBA32F;
  56. break;
  57. case PixelFormat.RGB:
  58. internalFormat = WebGLConstants.RGB32F;
  59. break;
  60. case PixelFormat.RG:
  61. internalFormat = WebGLConstants.RG32F;
  62. break;
  63. case PixelFormat.R:
  64. internalFormat = WebGLConstants.R32F;
  65. break;
  66. }
  67. } else if (pixelDatatype === PixelDatatype.HALF_FLOAT) {
  68. switch (pixelFormat) {
  69. case PixelFormat.RGBA:
  70. internalFormat = WebGLConstants.RGBA16F;
  71. break;
  72. case PixelFormat.RGB:
  73. internalFormat = WebGLConstants.RGB16F;
  74. break;
  75. case PixelFormat.RG:
  76. internalFormat = WebGLConstants.RG16F;
  77. break;
  78. case PixelFormat.R:
  79. internalFormat = WebGLConstants.R16F;
  80. break;
  81. }
  82. }
  83. }
  84. //>>includeStart('debug', pragmas.debug);
  85. if (!defined(width) || !defined(height)) {
  86. throw new DeveloperError('options requires a source field to create an initialized texture or width and height fields to create a blank texture.');
  87. }
  88. Check.typeOf.number.greaterThan('width', width, 0);
  89. if (width > ContextLimits.maximumTextureSize) {
  90. throw new DeveloperError('Width must be less than or equal to the maximum texture size (' + ContextLimits.maximumTextureSize + '). Check maximumTextureSize.');
  91. }
  92. Check.typeOf.number.greaterThan('height', height, 0);
  93. if (height > ContextLimits.maximumTextureSize) {
  94. throw new DeveloperError('Height must be less than or equal to the maximum texture size (' + ContextLimits.maximumTextureSize + '). Check maximumTextureSize.');
  95. }
  96. if (!PixelFormat.validate(pixelFormat)) {
  97. throw new DeveloperError('Invalid options.pixelFormat.');
  98. }
  99. if (!isCompressed && !PixelDatatype.validate(pixelDatatype)) {
  100. throw new DeveloperError('Invalid options.pixelDatatype.');
  101. }
  102. if ((pixelFormat === PixelFormat.DEPTH_COMPONENT) &&
  103. ((pixelDatatype !== PixelDatatype.UNSIGNED_SHORT) && (pixelDatatype !== PixelDatatype.UNSIGNED_INT))) {
  104. throw new DeveloperError('When options.pixelFormat is DEPTH_COMPONENT, options.pixelDatatype must be UNSIGNED_SHORT or UNSIGNED_INT.');
  105. }
  106. if ((pixelFormat === PixelFormat.DEPTH_STENCIL) && (pixelDatatype !== PixelDatatype.UNSIGNED_INT_24_8)) {
  107. throw new DeveloperError('When options.pixelFormat is DEPTH_STENCIL, options.pixelDatatype must be UNSIGNED_INT_24_8.');
  108. }
  109. if ((pixelDatatype === PixelDatatype.FLOAT) && !context.floatingPointTexture) {
  110. throw new DeveloperError('When options.pixelDatatype is FLOAT, this WebGL implementation must support the OES_texture_float extension. Check context.floatingPointTexture.');
  111. }
  112. if ((pixelDatatype === PixelDatatype.HALF_FLOAT) && !context.halfFloatingPointTexture) {
  113. throw new DeveloperError('When options.pixelDatatype is HALF_FLOAT, this WebGL implementation must support the OES_texture_half_float extension. Check context.halfFloatingPointTexture.');
  114. }
  115. if (PixelFormat.isDepthFormat(pixelFormat)) {
  116. if (defined(source)) {
  117. throw new DeveloperError('When options.pixelFormat is DEPTH_COMPONENT or DEPTH_STENCIL, source cannot be provided.');
  118. }
  119. if (!context.depthTexture) {
  120. throw new DeveloperError('When options.pixelFormat is DEPTH_COMPONENT or DEPTH_STENCIL, this WebGL implementation must support WEBGL_depth_texture. Check context.depthTexture.');
  121. }
  122. }
  123. if (isCompressed) {
  124. if (!defined(source) || !defined(source.arrayBufferView)) {
  125. throw new DeveloperError('When options.pixelFormat is compressed, options.source.arrayBufferView must be defined.');
  126. }
  127. if (PixelFormat.isDXTFormat(internalFormat) && !context.s3tc) {
  128. throw new DeveloperError('When options.pixelFormat is S3TC compressed, this WebGL implementation must support the WEBGL_texture_compression_s3tc extension. Check context.s3tc.');
  129. } else if (PixelFormat.isPVRTCFormat(internalFormat) && !context.pvrtc) {
  130. throw new DeveloperError('When options.pixelFormat is PVRTC compressed, this WebGL implementation must support the WEBGL_texture_compression_pvrtc extension. Check context.pvrtc.');
  131. } else if (PixelFormat.isETC1Format(internalFormat) && !context.etc1) {
  132. throw new DeveloperError('When options.pixelFormat is ETC1 compressed, this WebGL implementation must support the WEBGL_texture_compression_etc1 extension. Check context.etc1.');
  133. }
  134. if (PixelFormat.compressedTextureSizeInBytes(internalFormat, width, height) !== source.arrayBufferView.byteLength) {
  135. throw new DeveloperError('The byte length of the array buffer is invalid for the compressed texture with the given width and height.');
  136. }
  137. }
  138. //>>includeEnd('debug');
  139. // Use premultiplied alpha for opaque textures should perform better on Chrome:
  140. // http://media.tojicode.com/webglCamp4/#20
  141. var preMultiplyAlpha = options.preMultiplyAlpha || pixelFormat === PixelFormat.RGB || pixelFormat === PixelFormat.LUMINANCE;
  142. var flipY = defaultValue(options.flipY, true);
  143. var initialized = true;
  144. var gl = context._gl;
  145. var textureTarget = gl.TEXTURE_2D;
  146. var texture = gl.createTexture();
  147. gl.activeTexture(gl.TEXTURE0);
  148. gl.bindTexture(textureTarget, texture);
  149. var unpackAlignment = 4;
  150. if (defined(source) && defined(source.arrayBufferView) && !isCompressed) {
  151. unpackAlignment = PixelFormat.alignmentInBytes(pixelFormat, pixelDatatype, width);
  152. }
  153. gl.pixelStorei(gl.UNPACK_ALIGNMENT, unpackAlignment);
  154. if (defined(source)) {
  155. if (defined(source.arrayBufferView)) {
  156. gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
  157. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
  158. // Source: typed array
  159. var arrayBufferView = source.arrayBufferView;
  160. if (isCompressed) {
  161. gl.compressedTexImage2D(textureTarget, 0, internalFormat, width, height, 0, arrayBufferView);
  162. } else {
  163. if (flipY) {
  164. arrayBufferView = PixelFormat.flipY(arrayBufferView, pixelFormat, pixelDatatype, width, height);
  165. }
  166. gl.texImage2D(textureTarget, 0, internalFormat, width, height, 0, pixelFormat, pixelDatatype, arrayBufferView);
  167. }
  168. } else if (defined(source.framebuffer)) {
  169. gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
  170. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
  171. // Source: framebuffer
  172. if (source.framebuffer !== context.defaultFramebuffer) {
  173. source.framebuffer._bind();
  174. }
  175. gl.copyTexImage2D(textureTarget, 0, internalFormat, source.xOffset, source.yOffset, width, height, 0);
  176. if (source.framebuffer !== context.defaultFramebuffer) {
  177. source.framebuffer._unBind();
  178. }
  179. } else {
  180. // Only valid for DOM-Element uploads
  181. gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, preMultiplyAlpha);
  182. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
  183. // Source: ImageData, HTMLImageElement, HTMLCanvasElement, or HTMLVideoElement
  184. gl.texImage2D(textureTarget, 0, internalFormat, pixelFormat, pixelDatatype, source);
  185. }
  186. } else {
  187. gl.texImage2D(textureTarget, 0, internalFormat, width, height, 0, pixelFormat, pixelDatatype, null);
  188. initialized = false;
  189. }
  190. gl.bindTexture(textureTarget, null);
  191. var sizeInBytes;
  192. if (isCompressed) {
  193. sizeInBytes = PixelFormat.compressedTextureSizeInBytes(pixelFormat, width, height);
  194. } else {
  195. sizeInBytes = PixelFormat.textureSizeInBytes(pixelFormat, pixelDatatype, width, height);
  196. }
  197. this._id = createGuid();
  198. this._context = context;
  199. this._textureFilterAnisotropic = context._textureFilterAnisotropic;
  200. this._textureTarget = textureTarget;
  201. this._texture = texture;
  202. this._pixelFormat = pixelFormat;
  203. this._pixelDatatype = pixelDatatype;
  204. this._width = width;
  205. this._height = height;
  206. this._dimensions = new Cartesian2(width, height);
  207. this._hasMipmap = false;
  208. this._sizeInBytes = sizeInBytes;
  209. this._preMultiplyAlpha = preMultiplyAlpha;
  210. this._flipY = flipY;
  211. this._initialized = initialized;
  212. this._sampler = undefined;
  213. this.sampler = defined(options.sampler) ? options.sampler : new Sampler();
  214. }
  215. /**
  216. * This function is identical to using the Texture constructor except that it can be
  217. * replaced with a mock/spy in tests.
  218. * @private
  219. */
  220. Texture.create = function(options) {
  221. return new Texture(options);
  222. };
  223. /**
  224. * Creates a texture, and copies a subimage of the framebuffer to it. When called without arguments,
  225. * the texture is the same width and height as the framebuffer and contains its contents.
  226. *
  227. * @param {Object} options Object with the following properties:
  228. * @param {Context} options.context The context in which the Texture gets created.
  229. * @param {PixelFormat} [options.pixelFormat=PixelFormat.RGB] The texture's internal pixel format.
  230. * @param {Number} [options.framebufferXOffset=0] An offset in the x direction in the framebuffer where copying begins from.
  231. * @param {Number} [options.framebufferYOffset=0] An offset in the y direction in the framebuffer where copying begins from.
  232. * @param {Number} [options.width=canvas.clientWidth] The width of the texture in texels.
  233. * @param {Number} [options.height=canvas.clientHeight] The height of the texture in texels.
  234. * @param {Framebuffer} [options.framebuffer=defaultFramebuffer] The framebuffer from which to create the texture. If this
  235. * parameter is not specified, the default framebuffer is used.
  236. * @returns {Texture} A texture with contents from the framebuffer.
  237. *
  238. * @exception {DeveloperError} Invalid pixelFormat.
  239. * @exception {DeveloperError} pixelFormat cannot be DEPTH_COMPONENT, DEPTH_STENCIL or a compressed format.
  240. * @exception {DeveloperError} framebufferXOffset must be greater than or equal to zero.
  241. * @exception {DeveloperError} framebufferYOffset must be greater than or equal to zero.
  242. * @exception {DeveloperError} framebufferXOffset + width must be less than or equal to canvas.clientWidth.
  243. * @exception {DeveloperError} framebufferYOffset + height must be less than or equal to canvas.clientHeight.
  244. *
  245. *
  246. * @example
  247. * // Create a texture with the contents of the framebuffer.
  248. * var t = Texture.fromFramebuffer({
  249. * context : context
  250. * });
  251. *
  252. * @see Sampler
  253. *
  254. * @private
  255. */
  256. Texture.fromFramebuffer = function(options) {
  257. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  258. //>>includeStart('debug', pragmas.debug);
  259. Check.defined('options.context', options.context);
  260. //>>includeEnd('debug');
  261. var context = options.context;
  262. var gl = context._gl;
  263. var pixelFormat = defaultValue(options.pixelFormat, PixelFormat.RGB);
  264. var framebufferXOffset = defaultValue(options.framebufferXOffset, 0);
  265. var framebufferYOffset = defaultValue(options.framebufferYOffset, 0);
  266. var width = defaultValue(options.width, gl.drawingBufferWidth);
  267. var height = defaultValue(options.height, gl.drawingBufferHeight);
  268. var framebuffer = options.framebuffer;
  269. //>>includeStart('debug', pragmas.debug);
  270. if (!PixelFormat.validate(pixelFormat)) {
  271. throw new DeveloperError('Invalid pixelFormat.');
  272. }
  273. if (PixelFormat.isDepthFormat(pixelFormat) || PixelFormat.isCompressedFormat(pixelFormat)) {
  274. throw new DeveloperError('pixelFormat cannot be DEPTH_COMPONENT, DEPTH_STENCIL or a compressed format.');
  275. }
  276. Check.defined('options.context', options.context);
  277. Check.typeOf.number.greaterThanOrEquals('framebufferXOffset', framebufferXOffset, 0);
  278. Check.typeOf.number.greaterThanOrEquals('framebufferYOffset', framebufferYOffset, 0);
  279. if (framebufferXOffset + width > gl.drawingBufferWidth) {
  280. throw new DeveloperError('framebufferXOffset + width must be less than or equal to drawingBufferWidth');
  281. }
  282. if (framebufferYOffset + height > gl.drawingBufferHeight) {
  283. throw new DeveloperError('framebufferYOffset + height must be less than or equal to drawingBufferHeight.');
  284. }
  285. //>>includeEnd('debug');
  286. var texture = new Texture({
  287. context : context,
  288. width : width,
  289. height : height,
  290. pixelFormat : pixelFormat,
  291. source : {
  292. framebuffer : defined(framebuffer) ? framebuffer : context.defaultFramebuffer,
  293. xOffset : framebufferXOffset,
  294. yOffset : framebufferYOffset,
  295. width : width,
  296. height : height
  297. }
  298. });
  299. return texture;
  300. };
  301. defineProperties(Texture.prototype, {
  302. /**
  303. * A unique id for the texture
  304. * @memberof Texture.prototype
  305. * @type {String}
  306. * @readonly
  307. * @private
  308. */
  309. id : {
  310. get : function() {
  311. return this._id;
  312. }
  313. },
  314. /**
  315. * The sampler to use when sampling this texture.
  316. * Create a sampler by calling {@link Sampler}. If this
  317. * parameter is not specified, a default sampler is used. The default sampler clamps texture
  318. * coordinates in both directions, uses linear filtering for both magnification and minification,
  319. * and uses a maximum anisotropy of 1.0.
  320. * @memberof Texture.prototype
  321. * @type {Object}
  322. */
  323. sampler : {
  324. get : function() {
  325. return this._sampler;
  326. },
  327. set : function(sampler) {
  328. var minificationFilter = sampler.minificationFilter;
  329. var magnificationFilter = sampler.magnificationFilter;
  330. var mipmap =
  331. (minificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_NEAREST) ||
  332. (minificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_LINEAR) ||
  333. (minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_NEAREST) ||
  334. (minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_LINEAR);
  335. var context = this._context;
  336. var pixelDatatype = this._pixelDatatype;
  337. // float textures only support nearest filtering unless the linear extensions are supported, so override the sampler's settings
  338. if ((pixelDatatype === PixelDatatype.FLOAT && !context.textureFloatLinear) || (pixelDatatype === PixelDatatype.HALF_FLOAT && !context.textureHalfFloatLinear)) {
  339. minificationFilter = mipmap ? TextureMinificationFilter.NEAREST_MIPMAP_NEAREST : TextureMinificationFilter.NEAREST;
  340. magnificationFilter = TextureMagnificationFilter.NEAREST;
  341. }
  342. var gl = context._gl;
  343. var target = this._textureTarget;
  344. gl.activeTexture(gl.TEXTURE0);
  345. gl.bindTexture(target, this._texture);
  346. gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, minificationFilter);
  347. gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, magnificationFilter);
  348. gl.texParameteri(target, gl.TEXTURE_WRAP_S, sampler.wrapS);
  349. gl.texParameteri(target, gl.TEXTURE_WRAP_T, sampler.wrapT);
  350. if (defined(this._textureFilterAnisotropic)) {
  351. gl.texParameteri(target, this._textureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, sampler.maximumAnisotropy);
  352. }
  353. gl.bindTexture(target, null);
  354. this._sampler = sampler;
  355. }
  356. },
  357. pixelFormat : {
  358. get : function() {
  359. return this._pixelFormat;
  360. }
  361. },
  362. pixelDatatype : {
  363. get : function() {
  364. return this._pixelDatatype;
  365. }
  366. },
  367. dimensions : {
  368. get : function() {
  369. return this._dimensions;
  370. }
  371. },
  372. preMultiplyAlpha : {
  373. get : function() {
  374. return this._preMultiplyAlpha;
  375. }
  376. },
  377. flipY : {
  378. get : function() {
  379. return this._flipY;
  380. }
  381. },
  382. width : {
  383. get : function() {
  384. return this._width;
  385. }
  386. },
  387. height : {
  388. get : function() {
  389. return this._height;
  390. }
  391. },
  392. sizeInBytes : {
  393. get : function() {
  394. if (this._hasMipmap) {
  395. return Math.floor(this._sizeInBytes * 4 / 3);
  396. }
  397. return this._sizeInBytes;
  398. }
  399. },
  400. _target : {
  401. get : function() {
  402. return this._textureTarget;
  403. }
  404. }
  405. });
  406. /**
  407. * Copy new image data into this texture, from a source {@link ImageData}, {@link Image}, {@link Canvas}, or {@link Video}.
  408. * or an object with width, height, and arrayBufferView properties.
  409. *
  410. * @param {Object} source The source {@link ImageData}, {@link Image}, {@link Canvas}, or {@link Video},
  411. * or an object with width, height, and arrayBufferView properties.
  412. * @param {Number} [xOffset=0] The offset in the x direction within the texture to copy into.
  413. * @param {Number} [yOffset=0] The offset in the y direction within the texture to copy into.
  414. *
  415. * @exception {DeveloperError} Cannot call copyFrom when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.
  416. * @exception {DeveloperError} Cannot call copyFrom with a compressed texture pixel format.
  417. * @exception {DeveloperError} xOffset must be greater than or equal to zero.
  418. * @exception {DeveloperError} yOffset must be greater than or equal to zero.
  419. * @exception {DeveloperError} xOffset + source.width must be less than or equal to width.
  420. * @exception {DeveloperError} yOffset + source.height must be less than or equal to height.
  421. * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
  422. *
  423. * @example
  424. * texture.copyFrom({
  425. * width : 1,
  426. * height : 1,
  427. * arrayBufferView : new Uint8Array([255, 0, 0, 255])
  428. * });
  429. */
  430. Texture.prototype.copyFrom = function(source, xOffset, yOffset) {
  431. xOffset = defaultValue(xOffset, 0);
  432. yOffset = defaultValue(yOffset, 0);
  433. //>>includeStart('debug', pragmas.debug);
  434. Check.defined('source', source);
  435. if (PixelFormat.isDepthFormat(this._pixelFormat)) {
  436. throw new DeveloperError('Cannot call copyFrom when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.');
  437. }
  438. if (PixelFormat.isCompressedFormat(this._pixelFormat)) {
  439. throw new DeveloperError('Cannot call copyFrom with a compressed texture pixel format.');
  440. }
  441. Check.typeOf.number.greaterThanOrEquals('xOffset', xOffset, 0);
  442. Check.typeOf.number.greaterThanOrEquals('yOffset', yOffset, 0);
  443. Check.typeOf.number.lessThanOrEquals('xOffset + source.width', xOffset + source.width, this._width);
  444. Check.typeOf.number.lessThanOrEquals('yOffset + source.height', yOffset + source.height, this._height);
  445. //>>includeEnd('debug');
  446. var gl = this._context._gl;
  447. var target = this._textureTarget;
  448. gl.activeTexture(gl.TEXTURE0);
  449. gl.bindTexture(target, this._texture);
  450. var width = source.width;
  451. var height = source.height;
  452. var arrayBufferView = source.arrayBufferView;
  453. var textureWidth = this._width;
  454. var textureHeight = this._height;
  455. var pixelFormat = this._pixelFormat;
  456. var pixelDatatype = this._pixelDatatype;
  457. var preMultiplyAlpha = this._preMultiplyAlpha;
  458. var flipY = this._flipY;
  459. var unpackAlignment = 4;
  460. if (defined(arrayBufferView)) {
  461. unpackAlignment = PixelFormat.alignmentInBytes(pixelFormat, pixelDatatype, width);
  462. }
  463. gl.pixelStorei(gl.UNPACK_ALIGNMENT, unpackAlignment);
  464. var uploaded = false;
  465. if (!this._initialized) {
  466. if (xOffset === 0 && yOffset === 0 && width === textureWidth && height === textureHeight) {
  467. // initialize the entire texture
  468. if (defined(arrayBufferView)) {
  469. gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
  470. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
  471. if (flipY) {
  472. arrayBufferView = PixelFormat.flipY(arrayBufferView, pixelFormat, pixelDatatype, textureWidth, textureHeight);
  473. }
  474. gl.texImage2D(target, 0, pixelFormat, textureWidth, textureHeight, 0, pixelFormat, pixelDatatype, arrayBufferView);
  475. } else {
  476. // Only valid for DOM-Element uploads
  477. gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, preMultiplyAlpha);
  478. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
  479. gl.texImage2D(target, 0, pixelFormat, pixelFormat, pixelDatatype, source);
  480. }
  481. uploaded = true;
  482. } else {
  483. gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
  484. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
  485. // initialize the entire texture to zero
  486. var bufferView = PixelFormat.createTypedArray(pixelFormat, pixelDatatype, textureWidth, textureHeight);
  487. gl.texImage2D(target, 0, pixelFormat, textureWidth, textureHeight, 0, pixelFormat, pixelDatatype, bufferView);
  488. }
  489. this._initialized = true;
  490. }
  491. if (!uploaded) {
  492. if (defined(arrayBufferView)) {
  493. gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
  494. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
  495. if (flipY) {
  496. arrayBufferView = PixelFormat.flipY(arrayBufferView, pixelFormat, pixelDatatype, width, height);
  497. }
  498. gl.texSubImage2D(target, 0, xOffset, yOffset, width, height, pixelFormat, pixelDatatype, arrayBufferView);
  499. } else {
  500. // Only valid for DOM-Element uploads
  501. gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, preMultiplyAlpha);
  502. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
  503. gl.texSubImage2D(target, 0, xOffset, yOffset, pixelFormat, pixelDatatype, source);
  504. }
  505. }
  506. gl.bindTexture(target, null);
  507. };
  508. /**
  509. * @param {Number} [xOffset=0] The offset in the x direction within the texture to copy into.
  510. * @param {Number} [yOffset=0] The offset in the y direction within the texture to copy into.
  511. * @param {Number} [framebufferXOffset=0] optional
  512. * @param {Number} [framebufferYOffset=0] optional
  513. * @param {Number} [width=width] optional
  514. * @param {Number} [height=height] optional
  515. *
  516. * @exception {DeveloperError} Cannot call copyFromFramebuffer when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.
  517. * @exception {DeveloperError} Cannot call copyFromFramebuffer when the texture pixel data type is FLOAT.
  518. * @exception {DeveloperError} Cannot call copyFromFramebuffer when the texture pixel data type is HALF_FLOAT.
  519. * @exception {DeveloperError} Cannot call copyFrom with a compressed texture pixel format.
  520. * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
  521. * @exception {DeveloperError} xOffset must be greater than or equal to zero.
  522. * @exception {DeveloperError} yOffset must be greater than or equal to zero.
  523. * @exception {DeveloperError} framebufferXOffset must be greater than or equal to zero.
  524. * @exception {DeveloperError} framebufferYOffset must be greater than or equal to zero.
  525. * @exception {DeveloperError} xOffset + width must be less than or equal to width.
  526. * @exception {DeveloperError} yOffset + height must be less than or equal to height.
  527. */
  528. Texture.prototype.copyFromFramebuffer = function(xOffset, yOffset, framebufferXOffset, framebufferYOffset, width, height) {
  529. xOffset = defaultValue(xOffset, 0);
  530. yOffset = defaultValue(yOffset, 0);
  531. framebufferXOffset = defaultValue(framebufferXOffset, 0);
  532. framebufferYOffset = defaultValue(framebufferYOffset, 0);
  533. width = defaultValue(width, this._width);
  534. height = defaultValue(height, this._height);
  535. //>>includeStart('debug', pragmas.debug);
  536. if (PixelFormat.isDepthFormat(this._pixelFormat)) {
  537. throw new DeveloperError('Cannot call copyFromFramebuffer when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.');
  538. }
  539. if (this._pixelDatatype === PixelDatatype.FLOAT) {
  540. throw new DeveloperError('Cannot call copyFromFramebuffer when the texture pixel data type is FLOAT.');
  541. }
  542. if (this._pixelDatatype === PixelDatatype.HALF_FLOAT) {
  543. throw new DeveloperError('Cannot call copyFromFramebuffer when the texture pixel data type is HALF_FLOAT.');
  544. }
  545. if (PixelFormat.isCompressedFormat(this._pixelFormat)) {
  546. throw new DeveloperError('Cannot call copyFrom with a compressed texture pixel format.');
  547. }
  548. Check.typeOf.number.greaterThanOrEquals('xOffset', xOffset, 0);
  549. Check.typeOf.number.greaterThanOrEquals('yOffset', yOffset, 0);
  550. Check.typeOf.number.greaterThanOrEquals('framebufferXOffset', framebufferXOffset, 0);
  551. Check.typeOf.number.greaterThanOrEquals('framebufferYOffset', framebufferYOffset, 0);
  552. Check.typeOf.number.lessThanOrEquals('xOffset + width', xOffset + width, this._width);
  553. Check.typeOf.number.lessThanOrEquals('yOffset + height', yOffset + height, this._height);
  554. //>>includeEnd('debug');
  555. var gl = this._context._gl;
  556. var target = this._textureTarget;
  557. gl.activeTexture(gl.TEXTURE0);
  558. gl.bindTexture(target, this._texture);
  559. gl.copyTexSubImage2D(target, 0, xOffset, yOffset, framebufferXOffset, framebufferYOffset, width, height);
  560. gl.bindTexture(target, null);
  561. this._initialized = true;
  562. };
  563. /**
  564. * @param {MipmapHint} [hint=MipmapHint.DONT_CARE] optional.
  565. *
  566. * @exception {DeveloperError} Cannot call generateMipmap when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.
  567. * @exception {DeveloperError} Cannot call generateMipmap when the texture pixel format is a compressed format.
  568. * @exception {DeveloperError} hint is invalid.
  569. * @exception {DeveloperError} This texture's width must be a power of two to call generateMipmap().
  570. * @exception {DeveloperError} This texture's height must be a power of two to call generateMipmap().
  571. * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
  572. */
  573. Texture.prototype.generateMipmap = function(hint) {
  574. hint = defaultValue(hint, MipmapHint.DONT_CARE);
  575. //>>includeStart('debug', pragmas.debug);
  576. if (PixelFormat.isDepthFormat(this._pixelFormat)) {
  577. throw new DeveloperError('Cannot call generateMipmap when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.');
  578. }
  579. if (PixelFormat.isCompressedFormat(this._pixelFormat)) {
  580. throw new DeveloperError('Cannot call generateMipmap with a compressed pixel format.');
  581. }
  582. if (this._width > 1 && !CesiumMath.isPowerOfTwo(this._width)) {
  583. throw new DeveloperError('width must be a power of two to call generateMipmap().');
  584. }
  585. if (this._height > 1 && !CesiumMath.isPowerOfTwo(this._height)) {
  586. throw new DeveloperError('height must be a power of two to call generateMipmap().');
  587. }
  588. if (!MipmapHint.validate(hint)) {
  589. throw new DeveloperError('hint is invalid.');
  590. }
  591. //>>includeEnd('debug');
  592. this._hasMipmap = true;
  593. var gl = this._context._gl;
  594. var target = this._textureTarget;
  595. gl.hint(gl.GENERATE_MIPMAP_HINT, hint);
  596. gl.activeTexture(gl.TEXTURE0);
  597. gl.bindTexture(target, this._texture);
  598. gl.generateMipmap(target);
  599. gl.bindTexture(target, null);
  600. };
  601. Texture.prototype.isDestroyed = function() {
  602. return false;
  603. };
  604. Texture.prototype.destroy = function() {
  605. this._context._gl.deleteTexture(this._texture);
  606. return destroyObject(this);
  607. };
  608. export default Texture;