/Demo/canvas.js
JavaScript | 452 lines | 342 code | 51 blank | 59 comment | 19 complexity | 50c748f72e7f63a3b7c8f6b6b82f0eac MD5 | raw file
1/* 2 * This file wraps several WebGL constructs and provides a simple, single texture based WebGLCanvas as well as a 3 * specialized YUVWebGLCanvas that can handle YUV->RGB conversion. 4 */ 5 6/** 7 * Represents a WebGL shader script. 8 */ 9var Script = (function script() { 10 function constructor() {} 11 12 constructor.createFromElementId = function(id) { 13 var script = document.getElementById(id); 14 15 // Didn't find an element with the specified ID, abort. 16 assert(script , "Could not find shader with ID: " + id); 17 18 // Walk through the source element's children, building the shader source string. 19 var source = ""; 20 var currentChild = script .firstChild; 21 while(currentChild) { 22 if (currentChild.nodeType == 3) { 23 source += currentChild.textContent; 24 } 25 currentChild = currentChild.nextSibling; 26 } 27 28 var res = new constructor(); 29 res.type = script.type; 30 res.source = source; 31 return res; 32 }; 33 34 constructor.createFromSource = function(type, source) { 35 var res = new constructor(); 36 res.type = type; 37 res.source = source; 38 return res; 39 } 40 return constructor; 41})(); 42 43/** 44 * Represents a WebGL shader object and provides a mechanism to load shaders from HTML 45 * script tags. 46 */ 47var Shader = (function shader() { 48 function constructor(gl, script) { 49 50 // Now figure out what type of shader script we have, based on its MIME type. 51 if (script.type == "x-shader/x-fragment") { 52 this.shader = gl.createShader(gl.FRAGMENT_SHADER); 53 } else if (script.type == "x-shader/x-vertex") { 54 this.shader = gl.createShader(gl.VERTEX_SHADER); 55 } else { 56 error("Unknown shader type: " + script.type); 57 return; 58 } 59 60 // Send the source to the shader object. 61 gl.shaderSource(this.shader, script.source); 62 63 // Compile the shader program. 64 gl.compileShader(this.shader); 65 66 // See if it compiled successfully. 67 if (!gl.getShaderParameter(this.shader, gl.COMPILE_STATUS)) { 68 error("An error occurred compiling the shaders: " + gl.getShaderInfoLog(this.shader)); 69 return; 70 } 71 } 72 return constructor; 73})(); 74 75var Program = (function () { 76 function constructor(gl) { 77 this.gl = gl; 78 this.program = this.gl.createProgram(); 79 } 80 constructor.prototype = { 81 attach: function (shader) { 82 this.gl.attachShader(this.program, shader.shader); 83 }, 84 link: function () { 85 this.gl.linkProgram(this.program); 86 // If creating the shader program failed, alert. 87 assert(this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS), 88 "Unable to initialize the shader program."); 89 }, 90 use: function () { 91 this.gl.useProgram(this.program); 92 }, 93 getAttributeLocation: function(name) { 94 return this.gl.getAttribLocation(this.program, name); 95 }, 96 setMatrixUniform: function(name, array) { 97 var uniform = this.gl.getUniformLocation(this.program, name); 98 this.gl.uniformMatrix4fv(uniform, false, array); 99 } 100 }; 101 return constructor; 102})(); 103 104/** 105 * Represents a WebGL texture object. 106 */ 107var Texture = (function texture() { 108 function constructor(gl, size) { 109 this.gl = gl; 110 this.size = size; 111 this.texture = gl.createTexture(); 112 gl.bindTexture(gl.TEXTURE_2D, this.texture); 113 gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, size.w, size.h, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, null); 114 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 115 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 116 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 117 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 118 } 119 var textureIDs = null; 120 constructor.prototype = { 121 fill: function(textureData) { 122 var gl = this.gl; 123 assert(textureData.length >= this.size.w * this.size.h, 124 "Texture size mismatch, data:" + textureData.length + ", texture: " + this.size.w * this.size.h); 125 gl.bindTexture(gl.TEXTURE_2D, this.texture); 126 gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.size.w , this.size.h, gl.LUMINANCE, gl.UNSIGNED_BYTE, textureData); 127 }, 128 bind: function(n, program, name) { 129 var gl = this.gl; 130 if (!textureIDs) { 131 textureIDs = [gl.TEXTURE0, gl.TEXTURE1, gl.TEXTURE2]; 132 } 133 gl.activeTexture(textureIDs[n]); 134 gl.bindTexture(gl.TEXTURE_2D, this.texture); 135 gl.uniform1i(gl.getUniformLocation(program.program, name), n); 136 } 137 }; 138 return constructor; 139})(); 140 141/** 142 * Generic WebGL backed canvas that sets up: a quad to paint a texture on, appropriate vertex/fragment shaders, 143 * scene parameters and other things. Specialized versions of this class can be created by overriding several 144 * initialization methods. 145 * 146 * <code> 147 * var canvas = new WebGLCanvas(document.getElementById('canvas'), new Size(512, 512); 148 * canvas.texture.fill(data); 149 * canvas.drawScene(); 150 * </code> 151 */ 152var WebGLCanvas = (function () { 153 154 var vertexShaderScript = Script.createFromSource("x-shader/x-vertex", text([ 155 "attribute vec3 aVertexPosition;", 156 "attribute vec2 aTextureCoord;", 157 "uniform mat4 uMVMatrix;", 158 "uniform mat4 uPMatrix;", 159 "varying highp vec2 vTextureCoord;", 160 "void main(void) {", 161 " gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);", 162 " vTextureCoord = aTextureCoord;", 163 "}" 164 ])); 165 166 var fragmentShaderScript = Script.createFromSource("x-shader/x-fragment", text([ 167 "precision highp float;", 168 "varying highp vec2 vTextureCoord;", 169 "uniform sampler2D texture;", 170 "void main(void) {", 171 " gl_FragColor = texture2D(texture, vTextureCoord);", 172 "}" 173 ])); 174 175 function constructor(canvas, size) { 176 this.canvas = canvas; 177 this.size = size; 178 this.canvas.width = size.w; 179 this.canvas.height = size.h; 180 181 this.onInitWebGL(); 182 this.onInitShaders(); 183 initBuffers.call(this); 184 this.onInitTextures(); 185 initScene.call(this); 186 } 187 188 /** 189 * Initialize vertex and texture coordinate buffers for a plane. 190 */ 191 function initBuffers() { 192 var tmp; 193 var gl = this.gl; 194 195 // Create vertex position buffer. 196 this.quadVPBuffer = gl.createBuffer(); 197 gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVPBuffer); 198 tmp = [ 199 1.0, 1.0, 0.0, 200 -1.0, 1.0, 0.0, 201 1.0, -1.0, 0.0, 202 -1.0, -1.0, 0.0]; 203 204 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(tmp), gl.STATIC_DRAW); 205 this.quadVPBuffer.itemSize = 3; 206 this.quadVPBuffer.numItems = 4; 207 208 /* 209 +--------------------+ 210 | -1,1 (1) | 1,1 (0) 211 | | 212 | | 213 | | 214 | | 215 | | 216 | -1,-1 (3) | 1,-1 (2) 217 +--------------------+ 218 */ 219 220 var scaleX = 1.0; 221 var scaleY = 1.0; 222 223 // Create vertex texture coordinate buffer. 224 this.quadVTCBuffer = gl.createBuffer(); 225 gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVTCBuffer); 226 tmp = [ 227 scaleX, 0.0, 228 0.0, 0.0, 229 scaleX, scaleY, 230 0.0, scaleY, 231 ]; 232 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(tmp), gl.STATIC_DRAW); 233 } 234 235 function mvIdentity() { 236 this.mvMatrix = Matrix.I(4); 237 } 238 239 function mvMultiply(m) { 240 this.mvMatrix = this.mvMatrix.x(m); 241 } 242 243 function mvTranslate(m) { 244 mvMultiply.call(this, Matrix.Translation($V([m[0], m[1], m[2]])).ensure4x4()); 245 } 246 247 function setMatrixUniforms() { 248 this.program.setMatrixUniform("uPMatrix", new Float32Array(this.perspectiveMatrix.flatten())); 249 this.program.setMatrixUniform("uMVMatrix", new Float32Array(this.mvMatrix.flatten())); 250 } 251 252 function initScene() { 253 var gl = this.gl; 254 255 // Establish the perspective with which we want to view the 256 // scene. Our field of view is 45 degrees, with a width/height 257 // ratio of 640:480, and we only want to see objects between 0.1 units 258 // and 100 units away from the camera. 259 260 this.perspectiveMatrix = makePerspective(45, 1, 0.1, 100.0); 261 262 // Set the drawing position to the "identity" point, which is 263 // the center of the scene. 264 mvIdentity.call(this); 265 266 // Now move the drawing position a bit to where we want to start 267 // drawing the square. 268 mvTranslate.call(this, [0.0, 0.0, -2.4]); 269 270 // Draw the cube by binding the array buffer to the cube's vertices 271 // array, setting attributes, and pushing it to GL. 272 gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVPBuffer); 273 gl.vertexAttribPointer(this.vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0); 274 275 // Set the texture coordinates attribute for the vertices. 276 277 gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVTCBuffer); 278 gl.vertexAttribPointer(this.textureCoordAttribute, 2, gl.FLOAT, false, 0, 0); 279 280 this.onInitSceneTextures(); 281 282 setMatrixUniforms.call(this); 283 } 284 285 constructor.prototype = { 286 toString: function() { 287 return "WebGLCanvas Size: " + this.size; 288 }, 289 checkLastError: function (operation) { 290 var err = this.gl.getError(); 291 if (err != this.gl.NO_ERROR) { 292 var name = this.glNames[err]; 293 name = (name !== undefined) ? name + "(" + err + ")": 294 ("Unknown WebGL ENUM (0x" + value.toString(16) + ")"); 295 if (operation) { 296 console.log("WebGL Error: %s, %s", operation, name); 297 } else { 298 console.log("WebGL Error: %s", name); 299 } 300 console.trace(); 301 } 302 }, 303 onInitWebGL: function () { 304 try { 305 this.gl = this.canvas.getContext("experimental-webgl"); 306 } catch(e) {} 307 308 if (!this.gl) { 309 error("Unable to initialize WebGL. Your browser may not support it."); 310 } 311 if (this.glNames) { 312 return; 313 } 314 this.glNames = {}; 315 for (var propertyName in this.gl) { 316 if (typeof this.gl[propertyName] == 'number') { 317 this.glNames[this.gl[propertyName]] = propertyName; 318 } 319 } 320 }, 321 onInitShaders: function() { 322 this.program = new Program(this.gl); 323 this.program.attach(new Shader(this.gl, vertexShaderScript)); 324 this.program.attach(new Shader(this.gl, fragmentShaderScript)); 325 this.program.link(); 326 this.program.use(); 327 this.vertexPositionAttribute = this.program.getAttributeLocation("aVertexPosition"); 328 this.gl.enableVertexAttribArray(this.vertexPositionAttribute); 329 this.textureCoordAttribute = this.program.getAttributeLocation("aTextureCoord");; 330 this.gl.enableVertexAttribArray(this.textureCoordAttribute); 331 }, 332 onInitTextures: function () { 333 this.texture = new Texture(this.gl, this.size); 334 }, 335 onInitSceneTextures: function () { 336 this.texture.bind(0, this.program, "texture"); 337 }, 338 drawScene: function() { 339 this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4); 340 } 341 }; 342 return constructor; 343})(); 344 345var YUVWebGLCanvas = (function () { 346 var vertexShaderScript = Script.createFromSource("x-shader/x-vertex", text([ 347 "attribute vec3 aVertexPosition;", 348 "attribute vec2 aTextureCoord;", 349 "uniform mat4 uMVMatrix;", 350 "uniform mat4 uPMatrix;", 351 "varying highp vec2 vTextureCoord;", 352 "void main(void) {", 353 " gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);", 354 " vTextureCoord = aTextureCoord;", 355 "}" 356 ])); 357 358 var fragmentShaderScriptOld = Script.createFromSource("x-shader/x-fragment", text([ 359 "precision highp float;", 360 "varying highp vec2 vTextureCoord;", 361 "uniform sampler2D YTexture;", 362 "uniform sampler2D UTexture;", 363 "uniform sampler2D VTexture;", 364 365 "void main(void) {", 366 " vec3 YUV = vec3", 367 " (", 368 " texture2D(YTexture, vTextureCoord).x * 1.1643828125, // premultiply Y", 369 " texture2D(UTexture, vTextureCoord).x,", 370 " texture2D(VTexture, vTextureCoord).x", 371 " );", 372 " gl_FragColor = vec4", 373 " (", 374 " YUV.x + 1.59602734375 * YUV.z - 0.87078515625,", 375 " YUV.x - 0.39176171875 * YUV.y - 0.81296875 * YUV.z + 0.52959375,", 376 " YUV.x + 2.017234375 * YUV.y - 1.081390625,", 377 " 1", 378 " );", 379 "}" 380 ])); 381 382 var fragmentShaderScriptSimple = Script.createFromSource("x-shader/x-fragment", text([ 383 "precision highp float;", 384 "varying highp vec2 vTextureCoord;", 385 "uniform sampler2D YTexture;", 386 "uniform sampler2D UTexture;", 387 "uniform sampler2D VTexture;", 388 389 "void main(void) {", 390 " gl_FragColor = texture2D(YTexture, vTextureCoord);", 391 "}" 392 ])); 393 394 var fragmentShaderScript = Script.createFromSource("x-shader/x-fragment", text([ 395 "precision highp float;", 396 "varying highp vec2 vTextureCoord;", 397 "uniform sampler2D YTexture;", 398 "uniform sampler2D UTexture;", 399 "uniform sampler2D VTexture;", 400 "const mat4 YUV2RGB = mat4", 401 "(", 402 " 1.1643828125, 0, 1.59602734375, -.87078515625,", 403 " 1.1643828125, -.39176171875, -.81296875, .52959375,", 404 " 1.1643828125, 2.017234375, 0, -1.081390625,", 405 " 0, 0, 0, 1", 406 ");", 407 408 "void main(void) {", 409 " gl_FragColor = vec4( texture2D(YTexture, vTextureCoord).x, texture2D(UTexture, vTextureCoord).x, texture2D(VTexture, vTextureCoord).x, 1) * YUV2RGB;", 410 "}" 411 ])); 412 413 414 function constructor(canvas, size) { 415 WebGLCanvas.call(this, canvas, size); 416 } 417 418 constructor.prototype = inherit(WebGLCanvas, { 419 onInitShaders: function() { 420 this.program = new Program(this.gl); 421 this.program.attach(new Shader(this.gl, vertexShaderScript)); 422 this.program.attach(new Shader(this.gl, fragmentShaderScript)); 423 this.program.link(); 424 this.program.use(); 425 this.vertexPositionAttribute = this.program.getAttributeLocation("aVertexPosition"); 426 this.gl.enableVertexAttribArray(this.vertexPositionAttribute); 427 this.textureCoordAttribute = this.program.getAttributeLocation("aTextureCoord");; 428 this.gl.enableVertexAttribArray(this.textureCoordAttribute); 429 }, 430 onInitTextures: function () { 431 console.log("creatingTextures: size: " + this.size); 432 this.YTexture = new Texture(this.gl, this.size); 433 this.UTexture = new Texture(this.gl, this.size.getHalfSize()); 434 this.VTexture = new Texture(this.gl, this.size.getHalfSize()); 435 }, 436 onInitSceneTextures: function () { 437 this.YTexture.bind(0, this.program, "YTexture"); 438 this.UTexture.bind(1, this.program, "UTexture"); 439 this.VTexture.bind(2, this.program, "VTexture"); 440 }, 441 fillYUVTextures: function(y, u, v) { 442 this.YTexture.fill(y); 443 this.UTexture.fill(u); 444 this.VTexture.fill(v); 445 }, 446 toString: function() { 447 return "YUVCanvas Size: " + this.size; 448 } 449 }); 450 451 return constructor; 452})();