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.