PageSourceSearch

https://henrikrydgard.com/static/webgl/electricflower/electricflower_files/textures.js

js henrikrydgard.com collected 2026-10-02 05:53:04 UTC 17,094 bytes, 551 lines download raw bytes

1/*
2 * Copyright 2009, Google Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 *     * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *     * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *     * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32
33/**
34 * @fileoverview This file contains objects to manage textures.
35 */
36
37tdl.provide('tdl.textures');
38
39tdl.require('tdl.webgl');
40
41/**
42 * A module for textures.
43 * @namespace
44 */
45tdl.textures = tdl.textures || {};
46
47/**
48 * Loads a texture
49 * @param {{!tdl.math.Vector4|string|!Array.<string>|!img|!canvas}} Passing a
50 *        color makes a solid 1pixel 2d texture, passing a URL
51 *        makes a 2d texture with that url, passing an array of
52 *        urls makes a cubemap, passing an img or canvas makes a 2d texture with
53 *        that image.
54 * @param {boolean} opt_flipY Flip the texture in Y?
55 * @param {function} opt_callback Function to execute when texture is loaded.
56 */
57tdl.textures.loadTexture = function(arg, opt_flipY, opt_callback) {
58  if (opt_callback) {
59    alert('callback!');
60  }
61  var id;
62  if (typeof arg == 'string') {
63    td = arg;
64  } else if (arg.length == 4 && typeof arg[0] == 'number') {
65    id = arg.toString();
66  } else if ((arg.length == 1 || arg.length == 6) &&
67             typeof arg[0] == 'string') {
68    id = arg.toString();
69  } else if (arg.tagName == 'CANVAS') {
70    id = undefined;
71  } else if (arg.tagName == 'IMG') {
72    id = arg.src;
73  } else if (arg.width) {
74    id = undefined;
75  } else {
76    throw "bad args";
77  }
78
79  var texture;
80  tdl.textures.init_(gl);
81  if (id !== undefined) {
82    texture = gl.tdl.textures.db[id];
83  }
84  if (texture) {
85    return texture;
86  }
87  if (typeof arg == 'string') {
88    texture = new tdl.textures.Texture2D(arg, opt_flipY, opt_callback);
89  } else if (arg.length == 4 && typeof arg[0] == 'number') {
90    texture = new tdl.textures.SolidTexture(arg);
91  } else if ((arg.length == 1 || arg.length == 6) &&
92             typeof arg[0] == 'string') {
93    texture = new tdl.textures.CubeMap(arg);
94  } else if (arg.tagName == 'CANVAS' || arg.tagName == 'IMG') {
95    texture = new tdl.textures.Texture2D(arg, opt_flipY);
96  } else if (arg.width) {
97    texture = new tdl.textures.ColorTexture2D(arg);
98  } else {
99    throw "bad args";
100  }
101  gl.tdl.textures.db[arg.toString()] = texture;
102  return texture;
103};
104
105tdl.textures.addLoadingImage_ = function(img) {
106  tdl.textures.init_(gl);
107  gl.tdl.textures.loadingImages.push(img);
108};
109
110tdl.textures.removeLoadingImage_ = function(img) {
111  gl.tdl.textures.loadingImages.splice(gl.tdl.textures.loadingImages.indexOf(img), 1);
112};
113
114tdl.textures.init_ = function(gl) {
115  if (!gl.tdl.textures) {
116    gl.tdl.textures = { };
117    gl.tdl.textures.loadingImages = [];
118    tdl.webgl.registerContextLostHandler(
119        gl.canvas, tdl.textures.handleContextLost_, true);
120  }
121  if (!gl.tdl.textures.maxTextureSize) {
122    gl.tdl.textures.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
123    gl.tdl.textures.maxCubeMapSize = gl.getParameter(
124        gl.MAX_CUBE_MAP_TEXTURE_SIZE);
125  }
126  if (!gl.tdl.textures.db) {
127    gl.tdl.textures.db = { };
128  }
129};
130
131tdl.textures.handleContextLost_ = function() {
132  if (gl.tdl && gl.tdl.textures) {
133    delete gl.tdl.textures.db;
134    var imgs = gl.tdl.textures.loadingImages;
135    for (var ii = 0; ii < imgs.length; ++ii) {
136      imgs[ii].onload = undefined;
137    }
138    gl.tdl.textures.loadingImages = [];
139  }
140};
141
142tdl.textures.Texture = function(target) {
143  this.target = target;
144  this.texture = gl.createTexture();
145  this.params = { };
146};
147
148tdl.textures.Texture.prototype.setParameter = function(pname, value) {
149  this.params[pname] = value;
150  gl.bindTexture(this.target, this.texture);
151  gl.texParameteri(this.target, pname, value);
152};
153
154tdl.textures.Texture.prototype.recoverFromLostContext = function() {
155  this.texture = gl.createTexture();
156  gl.bindTexture(this.target, this.texture);
157  for (var pname in this.params) {
158    gl.texParameteri(this.target, pname, this.params[pname]);
159  }
160};
161
162/**
163 * A solid color texture.
164 * @constructor
165 * @param {!tdl.math.vector4} color.
166 */
167tdl.textures.SolidTexture = function(color) {
168  tdl.textures.Texture.call(this, gl.TEXTURE_2D);
169  this.color = color.slice(0, 4);
170  this.uploadTexture();
171};
172
173tdl.base.inherit(tdl.textures.SolidTexture, tdl.textures.Texture);
174
175tdl.textures.SolidTexture.prototype.uploadTexture = function() {
176  gl.bindTexture(gl.TEXTURE_2D, this.texture);
177  var pixel = new Uint8Array(this.color);
178  gl.texImage2D(
179    gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
180};
181
182tdl.textures.SolidTexture.prototype.recoverFromLostContext = function() {
183  tdl.textures.Texture.recoverFromLostContext.call(this);
184  this.uploadTexture();
185};
186
187tdl.textures.SolidTexture.prototype.bindToUnit = function(unit) {
188  gl.activeTexture(gl.TEXTURE0 + unit);
189  gl.bindTexture(gl.TEXTURE_2D, this.texture);
190};
191
192/**
193 * A color from an array of values texture.
194 * @constructor
195 * @param {!{width: number, height: number: pixels:
196 *        !Array.<number>} data.
197 */
198tdl.textures.ColorTexture = function(data, opt_format, opt_type) {
199  tdl.textures.Texture.call(this, gl.TEXTURE_2D);
200  this.format = opt_format || gl.RGBA;
201  this.type   = opt_type || gl.UNSIGNED_BYTE;
202  if (data.pixels instanceof Array) {
203    data.pixels = new Uint8Array(data.pixels);
204  }
205  this.data   = data;
206  this.uploadTexture();
207};
208
209tdl.base.inherit(tdl.textures.ColorTexture, tdl.textures.Texture);
210
211tdl.textures.ColorTexture.prototype.uploadTexture = function() {
212  gl.bindTexture(gl.TEXTURE_2D, this.texture);
213  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
214  gl.texImage2D(
215    gl.TEXTURE_2D, 0, this.format, this.data.width, this.data.height,
216    0, this.format, this.type, this.data.pixels);
217};
218
219tdl.textures.ColorTexture.prototype.recoverFromLostContext = function() {
220  tdl.textures.Texture.recoverFromLostContext.call(this);
221  this.uploadTexture();
222};
223
224tdl.textures.ColorTexture.prototype.bindToUnit = function(unit) {
225  gl.activeTexture(gl.TEXTURE0 + unit);
226  gl.bindTexture(gl.TEXTURE_2D, this.texture);
227};
228
229/**
230 * @constructor
231 * @param {{string|!Element}} url URL of image to load into texture.
232 * @param {function} opt_callback Function to execute when texture is loaded.
233 */
234tdl.textures.Texture2D = function(url, opt_flipY, opt_callback) {
235  if (opt_callback) {
236    alert('callback');
237  }
238  tdl.textures.Texture.call(this, gl.TEXTURE_2D);
239  this.flipY = opt_flipY || false;
240  var that = this;
241  var img;
242  // Handle dataURLs?
243  if (typeof url !== 'string') {
244    img = url;
245    this.loaded = true;
246    if (opt_callback) {
247      opt_callback();
248    }
249  } else {
250    img = document.createElement('img');
251    tdl.textures.addLoadingImage_(img);
252    img.onload = function() {
253      tdl.textures.removeLoadingImage_(img);
254      //tdl.log("loaded image: ", url);
255      that.updateTexture();
256      if (opt_callback) {
257        opt_callback();
258      }
259    };
260    img.onerror = function() {
261      tdl.log("could not load image: ", url);
262    };
263  }
264  this.img = img;
265  this.uploadTexture();
266
267  if (!this.loaded) {
268    img.src = url;
269  }
270};
271
272tdl.base.inherit(tdl.textures.Texture2D, tdl.textures.Texture);
273
274tdl.textures.isPowerOf2 = function(value) {
275  return (value & (value - 1)) == 0;
276};
277
278tdl.textures.Texture2D.prototype.uploadTexture = function() {
279  gl.bindTexture(gl.TEXTURE_2D, this.texture);
280  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
281  if (this.loaded) {
282    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, this.flipY);
283    this.setTexture(this.img);
284  } else {
285    var pixel = new Uint8Array([255, 255, 255, 255]);
286    gl.texImage2D(
287        gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
288  }
289};
290
291tdl.textures.Texture2D.prototype.setTexture = function(element) {
292  // TODO(gman): use texSubImage2D if the size is the same.
293  gl.bindTexture(gl.TEXTURE_2D, this.texture);
294  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, element);
295  if (tdl.textures.isPowerOf2(element.width) &&
296      tdl.textures.isPowerOf2(element.height)) {
297    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
298    gl.generateMipmap(gl.TEXTURE_2D);
299  } else {
300    this.setParameter(gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
301    this.setParameter(gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
302    this.setParameter(gl.TEXTURE_MIN_FILTER, gl.LINEAR);
303  }
304};
305
306tdl.textures.Texture2D.prototype.updateTexture = function() {
307  this.loaded = true;
308  this.uploadTexture();
309};
310
311tdl.textures.Texture2D.prototype.recoverFromLostContext = function() {
312  tdl.textures.Texture.recoverFromLostContext.call(this);
313  this.uploadTexture();
314};
315
316tdl.textures.Texture2D.prototype.bindToUnit = function(unit) {
317  gl.activeTexture(gl.TEXTURE0 + unit);
318  gl.bindTexture(gl.TEXTURE_2D, this.texture);
319};
320
321/**
322 * Create a texture to be managed externally.
323 * @constructor
324 * @param {string} type GL enum for texture type.
325 */
326tdl.textures.ExternalTexture = function(type) {
327  tdl.textures.Texture.call(this, type);
328  this.type = type;
329};
330
331tdl.base.inherit(tdl.textures.ExternalTexture, tdl.textures.Texture);
332
333tdl.textures.ExternalTexture.prototype.recoverFromLostContext = function() {
334};
335
336tdl.textures.ExternalTexture.prototype.bindToUnit = function(unit) {
337  gl.activeTexture(gl.TEXTURE0 + unit);
338  gl.bindTexture(this.type, this.texture);
339}
340
341/**
342 * Create a 2D texture to be managed externally.
343 * @constructor
344 */
345tdl.textures.ExternalTexture2D = function() {
346  tdl.textures.ExternalTexture.call(this, gl.TEXTURE_2D);
347};
348
349tdl.base.inherit(tdl.textures.ExternalTexture2D, tdl.textures.ExternalTexture);
350
351/**
352 * Create and load a CubeMap.
353 * @constructor
354 * @param {!Array.<string>} urls The urls of the 6 faces, which
355 *     must be in the order positive_x, negative_x positive_y,
356 *     negative_y, positive_z, negative_z OR an array with a single url
357 *     where the images are arranged as a cross in this order.
358 *
359 *     +--+--+--+--+
360 *     |  |PY|  |  |
361 *     +--+--+--+--+
362 *     |NX|PZ|PX|NZ|
363 *     +--+--+--+--+
364 *     |  |NY|  |  |
365 *     +--+--+--+--+
366 */
367tdl.textures.CubeMap = function(urls) {
368  tdl.textures.init_(gl);
369  tdl.textures.Texture.call(this, gl.TEXTURE_CUBE_MAP);
370  // TODO(gman): make this global.
371  if (!tdl.textures.CubeMap.faceTargets) {
372    tdl.textures.CubeMap.faceTargets = [
373      gl.TEXTURE_CUBE_MAP_POSITIVE_X,
374      gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
375      gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
376      gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
377      gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
378      gl.TEXTURE_CUBE_MAP_NEGATIVE_Z];
379    tdl.textures.CubeMap.offsets = [
380      [2, 1],
381      [0, 1],
382      [1, 0],
383      [1, 2],
384      [1, 1],
385      [3, 1]];
386  }
387  var faceTargets = tdl.textures.CubeMap.faceTargets;
388  gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.texture);
389  this.setParameter(gl.TEXTURE_MAG_FILTER, gl.LINEAR);
390  this.setParameter(gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
391  this.setParameter(gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
392  this.faces = [];
393  if (!urls.length) {
394    this.numUrls = 0;
395    this.size = urls;
396  } else {
397    this.numUrls = urls.length;
398    var that = this;
399    for (var ff = 0; ff < urls.length; ++ff) {
400      var face = { };
401      this.faces[ff] = face;
402      var img = document.createElement('img');
403      tdl.textures.addLoadingImage_(img);
404      face.img = img;
405      img.onload = function(faceIndex) {
406        return function() {
407          tdl.textures.removeLoadingImage_(img);
408          tdl.log("loaded image: ", urls[faceIndex]);
409          that.updateTexture(faceIndex);
410        }
411      } (ff);
412      img.onerror = function(url) {
413        return function() {
414          tdl.log("could not load image: ", url);
415        }
416      }(urls[ff]);
417      img.src = urls[ff];
418    }
419  }
420  this.uploadTextures();
421};
422
423tdl.base.inherit(tdl.textures.CubeMap, tdl.textures.Texture);
424
425/**
426 * Check if all faces are loaded.
427 * @return {boolean} true if all faces are loaded.
428 */
429tdl.textures.CubeMap.prototype.loaded = function() {
430  for (var ff = 0; ff < this.faces.length; ++ff) {
431    if (!this.faces[ff].loaded) {
432      return false;
433    }
434  }
435  return true;
436};
437
438tdl.textures.clampToMaxSize = function(element, maxSize) {
439  if (element.width <= maxSize && element.height <= maxSize) {
440    return element;
441  }
442  var maxDimension = Math.max(element.width, element.height);
443  var newWidth = Math.floor(element.width * maxSize / maxDimension);
444  var newHeight = Math.floor(element.height * maxSize / maxDimension);
445
446  var canvas = document.createElement('canvas');
447  canvas.width = newWidth;
448  canvas.height = newHeight;
449  var ctx = canvas.getContext("2d");
450  ctx.drawImage(
451      element,
452      0, 0, element.width, element.height,
453      0, 0, newWidth, newHeight);
454  return canvas;
455};
456
457/**
458 * Uploads the images to the texture.
459 */
460tdl.textures.CubeMap.prototype.uploadTextures = function() {
461  var allFacesLoaded = this.loaded();
462  var faceTargets = tdl.textures.CubeMap.faceTargets;
463  for (var faceIndex = 0; faceIndex < 6; ++faceIndex) {
464    var uploaded = false;
465    var target = faceTargets[faceIndex];
466    if (this.faces.length) {
467      var face = this.faces[Math.min(this.faces.length - 1, faceIndex)];
468      gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.texture);
469      if (allFacesLoaded) {
470        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
471        if (this.faces.length == 6) {
472          gl.texImage2D(
473              target, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE,
474              tdl.textures.clampToMaxSize(
475                  face.img, gl.tdl.textures.maxCubeMapSize));
476        } else {
477          var canvas = document.createElement('canvas');
478          var width = face.img.width / 4;
479          var height = face.img.height / 3;
480          canvas.width = width;
481          canvas.height = height;
482          var ctx = canvas.getContext("2d");
483          var sx = tdl.textures.CubeMap.offsets[faceIndex][0] * width;
484          var sy = tdl.textures.CubeMap.offsets[faceIndex][1] * height;
485          ctx.drawImage(face.img, sx, sy, width, height, 0, 0, width, height);
486          gl.texImage2D(
487              target, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE,
488              tdl.textures.clampToMaxSize(
489                  canvas, gl.tdl.textures.maxCubeMapSize));
490        }
491        uploaded = true;
492      }
493    }
494    if (!uploaded) {
495      var pixel = new Uint8Array([100,100,255,255]);
496      gl.texImage2D(target, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
497    }
498  }
499  var genMips = false;
500  if (this.faces.length) {
501    var faceImg = this.faces[0].img;
502    if (this.faces.length == 6) {
503      genMips = tdl.textures.isPowerOf2(faceImg.width) &&
504                tdl.textures.isPowerOf2(faceImg.height);
505    } else {
506      genMips = tdl.textures.isPowerOf2(faceImg.width / 4) &&
507                tdl.textures.isPowerOf2(faceImg.height / 3);
508    }
509  }
510  if (genMips) {
511    gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
512    this.setParameter(gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
513  } else {
514    this.setParameter(gl.TEXTURE_MIN_FILTER, gl.LINEAR);
515  }
516};
517
518/**
519 * Recover from lost context.
520 */
521tdl.textures.CubeMap.prototype.recoverFromLostContext = function() {
522  tdl.textures.Texture.recoverFromLostContext.call(this);
523  this.uploadTextures();
524};
525
526/**
527 * Update a just downloaded loaded texture.
528 * @param {number} faceIndex index of face.
529 */
530tdl.textures.CubeMap.prototype.updateTexture = function(faceIndex) {
531  // mark the face as loaded
532  var face = this.faces[faceIndex];
533  face.loaded = true;
534  // If all 6 faces are loaded then upload to GPU.
535  var loaded = this.loaded();
536  if (loaded) {
537    this.uploadTextures();
538  }
539};
540
541/**
542 * Binds the CubeMap to a texture unit
543 * @param {number} unit The texture unit.
544 */
545tdl.textures.CubeMap.prototype.bindToUnit = function(unit) {
546  gl.activeTexture(gl.TEXTURE0 + unit);
547  gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.texture);
548};
549
550
551

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.