Texture.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. /*global define*/
  2. define([
  3. '../Core/Cartesian2',
  4. '../Core/defaultValue',
  5. '../Core/defined',
  6. '../Core/defineProperties',
  7. '../Core/destroyObject',
  8. '../Core/DeveloperError',
  9. '../Core/Math',
  10. '../Core/PixelFormat',
  11. './MipmapHint',
  12. './PixelDatatype',
  13. './TextureMagnificationFilter',
  14. './TextureMinificationFilter',
  15. './TextureWrap'
  16. ], function(
  17. Cartesian2,
  18. defaultValue,
  19. defined,
  20. defineProperties,
  21. destroyObject,
  22. DeveloperError,
  23. CesiumMath,
  24. PixelFormat,
  25. MipmapHint,
  26. PixelDatatype,
  27. TextureMagnificationFilter,
  28. TextureMinificationFilter,
  29. TextureWrap) {
  30. "use strict";
  31. /**
  32. * @private
  33. */
  34. var Texture = function(context, options) {
  35. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  36. var source = options.source;
  37. var width = defined(source) ? source.width : options.width;
  38. var height = defined(source) ? source.height : options.height;
  39. var pixelFormat = defaultValue(options.pixelFormat, PixelFormat.RGBA);
  40. var pixelDatatype = defaultValue(options.pixelDatatype, PixelDatatype.UNSIGNED_BYTE);
  41. //>>includeStart('debug', pragmas.debug);
  42. if (!defined(width) || !defined(height)) {
  43. throw new DeveloperError('options requires a source field to create an initialized texture or width and height fields to create a blank texture.');
  44. }
  45. if (width <= 0) {
  46. throw new DeveloperError('Width must be greater than zero.');
  47. }
  48. if (width > context._maximumTextureSize) {
  49. throw new DeveloperError('Width must be less than or equal to the maximum texture size (' + context._maximumTextureSize + '). Check maximumTextureSize.');
  50. }
  51. if (height <= 0) {
  52. throw new DeveloperError('Height must be greater than zero.');
  53. }
  54. if (height > context._maximumTextureSize) {
  55. throw new DeveloperError('Height must be less than or equal to the maximum texture size (' + context._maximumTextureSize + '). Check maximumTextureSize.');
  56. }
  57. if (!PixelFormat.validate(pixelFormat)) {
  58. throw new DeveloperError('Invalid options.pixelFormat.');
  59. }
  60. if (!PixelDatatype.validate(pixelDatatype)) {
  61. throw new DeveloperError('Invalid options.pixelDatatype.');
  62. }
  63. if ((pixelFormat === PixelFormat.DEPTH_COMPONENT) &&
  64. ((pixelDatatype !== PixelDatatype.UNSIGNED_SHORT) && (pixelDatatype !== PixelDatatype.UNSIGNED_INT))) {
  65. throw new DeveloperError('When options.pixelFormat is DEPTH_COMPONENT, options.pixelDatatype must be UNSIGNED_SHORT or UNSIGNED_INT.');
  66. }
  67. if ((pixelFormat === PixelFormat.DEPTH_STENCIL) && (pixelDatatype !== PixelDatatype.UNSIGNED_INT_24_8_WEBGL)) {
  68. throw new DeveloperError('When options.pixelFormat is DEPTH_STENCIL, options.pixelDatatype must be UNSIGNED_INT_24_8_WEBGL.');
  69. }
  70. //>>includeEnd('debug');
  71. if ((pixelDatatype === PixelDatatype.FLOAT) && !context.floatingPointTexture) {
  72. throw new DeveloperError('When options.pixelDatatype is FLOAT, this WebGL implementation must support the OES_texture_float extension. Check context.floatingPointTexture.');
  73. }
  74. if (PixelFormat.isDepthFormat(pixelFormat)) {
  75. //>>includeStart('debug', pragmas.debug);
  76. if (defined(source)) {
  77. throw new DeveloperError('When options.pixelFormat is DEPTH_COMPONENT or DEPTH_STENCIL, source cannot be provided.');
  78. }
  79. //>>includeEnd('debug');
  80. if (!context.depthTexture) {
  81. throw new DeveloperError('When options.pixelFormat is DEPTH_COMPONENT or DEPTH_STENCIL, this WebGL implementation must support WEBGL_depth_texture. Check context.depthTexture.');
  82. }
  83. }
  84. // Use premultiplied alpha for opaque textures should perform better on Chrome:
  85. // http://media.tojicode.com/webglCamp4/#20
  86. var preMultiplyAlpha = options.preMultiplyAlpha || pixelFormat === PixelFormat.RGB || pixelFormat === PixelFormat.LUMINANCE;
  87. var flipY = defaultValue(options.flipY, true);
  88. var gl = context._gl;
  89. var textureTarget = gl.TEXTURE_2D;
  90. var texture = gl.createTexture();
  91. gl.activeTexture(gl.TEXTURE0);
  92. gl.bindTexture(textureTarget, texture);
  93. if (defined(source)) {
  94. // TODO: _gl.pixelStorei(_gl._UNPACK_ALIGNMENT, 4);
  95. gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, preMultiplyAlpha);
  96. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
  97. if (defined(source.arrayBufferView)) {
  98. // Source: typed array
  99. gl.texImage2D(textureTarget, 0, pixelFormat, width, height, 0, pixelFormat, pixelDatatype, source.arrayBufferView);
  100. } else if (defined(source.framebuffer)) {
  101. // Source: framebuffer
  102. if (source.framebuffer !== context.defaultFramebuffer) {
  103. source.framebuffer._bind();
  104. }
  105. gl.copyTexImage2D(textureTarget, 0, pixelFormat, source.xOffset, source.yOffset, width, height, 0);
  106. if (source.framebuffer !== context.defaultFramebuffer) {
  107. source.framebuffer._unBind();
  108. }
  109. } else {
  110. // Source: ImageData, HTMLImageElement, HTMLCanvasElement, or HTMLVideoElement
  111. gl.texImage2D(textureTarget, 0, pixelFormat, pixelFormat, pixelDatatype, source);
  112. }
  113. } else {
  114. gl.texImage2D(textureTarget, 0, pixelFormat, width, height, 0, pixelFormat, pixelDatatype, null);
  115. }
  116. gl.bindTexture(textureTarget, null);
  117. this._context = context;
  118. this._textureFilterAnisotropic = context._textureFilterAnisotropic;
  119. this._textureTarget = textureTarget;
  120. this._texture = texture;
  121. this._pixelFormat = pixelFormat;
  122. this._pixelDatatype = pixelDatatype;
  123. this._width = width;
  124. this._height = height;
  125. this._dimensions = new Cartesian2(width, height);
  126. this._preMultiplyAlpha = preMultiplyAlpha;
  127. this._flipY = flipY;
  128. this._sampler = undefined;
  129. this.sampler = undefined;
  130. };
  131. defineProperties(Texture.prototype, {
  132. /**
  133. * The sampler to use when sampling this texture.
  134. * Create a sampler by calling {@link Context#createSampler}. If this
  135. * parameter is not specified, a default sampler is used. The default sampler clamps texture
  136. * coordinates in both directions, uses linear filtering for both magnification and minifcation,
  137. * and uses a maximum anisotropy of 1.0.
  138. * @memberof Texture.prototype
  139. * @type {Object}
  140. */
  141. sampler : {
  142. get : function() {
  143. return this._sampler;
  144. },
  145. set : function(sampler) {
  146. var samplerDefined = true;
  147. if (!defined(sampler)) {
  148. samplerDefined = false;
  149. var minFilter = TextureMinificationFilter.LINEAR;
  150. var magFilter = TextureMagnificationFilter.LINEAR;
  151. if (this._pixelDatatype === PixelDatatype.FLOAT) {
  152. minFilter = TextureMinificationFilter.NEAREST;
  153. magFilter = TextureMagnificationFilter.NEAREST;
  154. }
  155. sampler = {
  156. wrapS : TextureWrap.CLAMP_TO_EDGE,
  157. wrapT : TextureWrap.CLAMP_TO_EDGE,
  158. minificationFilter : minFilter,
  159. magnificationFilter : magFilter,
  160. maximumAnisotropy : 1.0
  161. };
  162. }
  163. if (this._pixelDatatype === PixelDatatype.FLOAT) {
  164. if (sampler.minificationFilter !== TextureMinificationFilter.NEAREST &&
  165. sampler.minificationFilter !== TextureMinificationFilter.NEAREST_MIPMAP_NEAREST) {
  166. throw new DeveloperError('Only NEAREST and NEAREST_MIPMAP_NEAREST minification filters are supported for floating point textures.');
  167. }
  168. if (sampler.magnificationFilter !== TextureMagnificationFilter.NEAREST) {
  169. throw new DeveloperError('Only the NEAREST magnification filter is supported for floating point textures.');
  170. }
  171. }
  172. var gl = this._context._gl;
  173. var target = this._textureTarget;
  174. gl.activeTexture(gl.TEXTURE0);
  175. gl.bindTexture(target, this._texture);
  176. gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, sampler.minificationFilter);
  177. gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, sampler.magnificationFilter);
  178. gl.texParameteri(target, gl.TEXTURE_WRAP_S, sampler.wrapS);
  179. gl.texParameteri(target, gl.TEXTURE_WRAP_T, sampler.wrapT);
  180. if (defined(this._textureFilterAnisotropic)) {
  181. gl.texParameteri(target, this._textureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, sampler.maximumAnisotropy);
  182. }
  183. gl.bindTexture(target, null);
  184. this._sampler = !samplerDefined ? undefined : {
  185. wrapS : sampler.wrapS,
  186. wrapT : sampler.wrapT,
  187. minificationFilter : sampler.minificationFilter,
  188. magnificationFilter : sampler.magnificationFilter,
  189. maximumAnisotropy : sampler.maximumAnisotropy
  190. };
  191. }
  192. },
  193. pixelFormat : {
  194. get : function() {
  195. return this._pixelFormat;
  196. }
  197. },
  198. pixelDatatype : {
  199. get : function() {
  200. return this._pixelDatatype;
  201. }
  202. },
  203. dimensions : {
  204. get : function() {
  205. return this._dimensions;
  206. }
  207. },
  208. preMultiplyAlpha : {
  209. get : function() {
  210. return this._preMultiplyAlpha;
  211. }
  212. },
  213. flipY : {
  214. get : function() {
  215. return this._flipY;
  216. }
  217. },
  218. width : {
  219. get : function() {
  220. return this._width;
  221. }
  222. },
  223. height : {
  224. get : function() {
  225. return this._height;
  226. }
  227. },
  228. _target : {
  229. get : function() {
  230. return this._textureTarget;
  231. }
  232. }
  233. });
  234. /**
  235. * Copy new image data into this texture, from a source {@link ImageData}, {@link Image}, {@link Canvas}, or {@link Video}.
  236. * or an object with width, height, and arrayBufferView properties.
  237. *
  238. * @param {Object} source The source {@link ImageData}, {@link Image}, {@link Canvas}, or {@link Video},
  239. * or an object with width, height, and arrayBufferView properties.
  240. * @param {Number} [xOffset=0] The offset in the x direction within the texture to copy into.
  241. * @param {Number} [yOffset=0] The offset in the y direction within the texture to copy into.
  242. *
  243. * @exception {DeveloperError} Cannot call copyFrom when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.
  244. * @exception {DeveloperError} xOffset must be greater than or equal to zero.
  245. * @exception {DeveloperError} yOffset must be greater than or equal to zero.
  246. * @exception {DeveloperError} xOffset + source.width must be less than or equal to width.
  247. * @exception {DeveloperError} yOffset + source.height must be less than or equal to height.
  248. * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
  249. *
  250. * @example
  251. * texture.copyFrom({
  252. * width : 1,
  253. * height : 1,
  254. * arrayBufferView : new Uint8Array([255, 0, 0, 255])
  255. * });
  256. */
  257. Texture.prototype.copyFrom = function(source, xOffset, yOffset) {
  258. xOffset = defaultValue(xOffset, 0);
  259. yOffset = defaultValue(yOffset, 0);
  260. //>>includeStart('debug', pragmas.debug);
  261. if (!defined(source)) {
  262. throw new DeveloperError('source is required.');
  263. }
  264. if (PixelFormat.isDepthFormat(this._pixelFormat)) {
  265. throw new DeveloperError('Cannot call copyFrom when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.');
  266. }
  267. if (xOffset < 0) {
  268. throw new DeveloperError('xOffset must be greater than or equal to zero.');
  269. }
  270. if (yOffset < 0) {
  271. throw new DeveloperError('yOffset must be greater than or equal to zero.');
  272. }
  273. if (xOffset + source.width > this._width) {
  274. throw new DeveloperError('xOffset + source.width must be less than or equal to width.');
  275. }
  276. if (yOffset + source.height > this._height) {
  277. throw new DeveloperError('yOffset + source.height must be less than or equal to height.');
  278. }
  279. //>>includeEnd('debug');
  280. var gl = this._context._gl;
  281. var target = this._textureTarget;
  282. // TODO: gl.pixelStorei(gl._UNPACK_ALIGNMENT, 4);
  283. gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this._preMultiplyAlpha);
  284. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, this._flipY);
  285. gl.activeTexture(gl.TEXTURE0);
  286. gl.bindTexture(target, this._texture);
  287. if (source.arrayBufferView) {
  288. gl.texSubImage2D(target, 0, xOffset, yOffset, source.width, source.height, this._pixelFormat, this._pixelDatatype, source.arrayBufferView);
  289. } else {
  290. gl.texSubImage2D(target, 0, xOffset, yOffset, this._pixelFormat, this._pixelDatatype, source);
  291. }
  292. gl.bindTexture(target, null);
  293. };
  294. /**
  295. * @param {Number} [xOffset=0] The offset in the x direction within the texture to copy into.
  296. * @param {Number} [yOffset=0] The offset in the y direction within the texture to copy into.
  297. * @param {Number} [framebufferXOffset=0] optional
  298. * @param {Number} [framebufferYOffset=0] optional
  299. * @param {Number} [width=width] optional
  300. * @param {Number} [height=height] optional
  301. *
  302. * @exception {DeveloperError} Cannot call copyFromFramebuffer when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.
  303. * @exception {DeveloperError} Cannot call copyFromFramebuffer when the texture pixel data type is FLOAT.
  304. * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
  305. * @exception {DeveloperError} xOffset must be greater than or equal to zero.
  306. * @exception {DeveloperError} yOffset must be greater than or equal to zero.
  307. * @exception {DeveloperError} framebufferXOffset must be greater than or equal to zero.
  308. * @exception {DeveloperError} framebufferYOffset must be greater than or equal to zero.
  309. * @exception {DeveloperError} xOffset + width must be less than or equal to width.
  310. * @exception {DeveloperError} yOffset + height must be less than or equal to height.
  311. */
  312. Texture.prototype.copyFromFramebuffer = function(xOffset, yOffset, framebufferXOffset, framebufferYOffset, width, height) {
  313. xOffset = defaultValue(xOffset, 0);
  314. yOffset = defaultValue(yOffset, 0);
  315. framebufferXOffset = defaultValue(framebufferXOffset, 0);
  316. framebufferYOffset = defaultValue(framebufferYOffset, 0);
  317. width = defaultValue(width, this._width);
  318. height = defaultValue(height, this._height);
  319. //>>includeStart('debug', pragmas.debug);
  320. if (PixelFormat.isDepthFormat(this._pixelFormat)) {
  321. throw new DeveloperError('Cannot call copyFromFramebuffer when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.');
  322. }
  323. if (this._pixelDatatype === PixelDatatype.FLOAT) {
  324. throw new DeveloperError('Cannot call copyFromFramebuffer when the texture pixel data type is FLOAT.');
  325. }
  326. if (xOffset < 0) {
  327. throw new DeveloperError('xOffset must be greater than or equal to zero.');
  328. }
  329. if (yOffset < 0) {
  330. throw new DeveloperError('yOffset must be greater than or equal to zero.');
  331. }
  332. if (framebufferXOffset < 0) {
  333. throw new DeveloperError('framebufferXOffset must be greater than or equal to zero.');
  334. }
  335. if (framebufferYOffset < 0) {
  336. throw new DeveloperError('framebufferYOffset must be greater than or equal to zero.');
  337. }
  338. if (xOffset + width > this._width) {
  339. throw new DeveloperError('xOffset + width must be less than or equal to width.');
  340. }
  341. if (yOffset + height > this._height) {
  342. throw new DeveloperError('yOffset + height must be less than or equal to height.');
  343. }
  344. //>>includeEnd('debug');
  345. var gl = this._context._gl;
  346. var target = this._textureTarget;
  347. gl.activeTexture(gl.TEXTURE0);
  348. gl.bindTexture(target, this._texture);
  349. gl.copyTexSubImage2D(target, 0, xOffset, yOffset, framebufferXOffset, framebufferYOffset, width, height);
  350. gl.bindTexture(target, null);
  351. };
  352. /**
  353. * @param {MipmapHint} [hint=MipmapHint.DONT_CARE] optional.
  354. *
  355. * @exception {DeveloperError} Cannot call generateMipmap when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.
  356. * @exception {DeveloperError} hint is invalid.
  357. * @exception {DeveloperError} This texture's width must be a power of two to call generateMipmap().
  358. * @exception {DeveloperError} This texture's height must be a power of two to call generateMipmap().
  359. * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
  360. */
  361. Texture.prototype.generateMipmap = function(hint) {
  362. hint = defaultValue(hint, MipmapHint.DONT_CARE);
  363. //>>includeStart('debug', pragmas.debug);
  364. if (PixelFormat.isDepthFormat(this._pixelFormat)) {
  365. throw new DeveloperError('Cannot call generateMipmap when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.');
  366. }
  367. if (this._width > 1 && !CesiumMath.isPowerOfTwo(this._width)) {
  368. throw new DeveloperError('width must be a power of two to call generateMipmap().');
  369. }
  370. if (this._height > 1 && !CesiumMath.isPowerOfTwo(this._height)) {
  371. throw new DeveloperError('height must be a power of two to call generateMipmap().');
  372. }
  373. if (!MipmapHint.validate(hint)) {
  374. throw new DeveloperError('hint is invalid.');
  375. }
  376. //>>includeEnd('debug');
  377. var gl = this._context._gl;
  378. var target = this._textureTarget;
  379. gl.hint(gl.GENERATE_MIPMAP_HINT, hint);
  380. gl.activeTexture(gl.TEXTURE0);
  381. gl.bindTexture(target, this._texture);
  382. gl.generateMipmap(target);
  383. gl.bindTexture(target, null);
  384. };
  385. Texture.prototype.isDestroyed = function() {
  386. return false;
  387. };
  388. Texture.prototype.destroy = function() {
  389. this._context._gl.deleteTexture(this._texture);
  390. return destroyObject(this);
  391. };
  392. return Texture;
  393. });