1 /*! 2 Copyright 2013 @greweb 3 http://github.com/gre/glsl.js 4 5 Licensed under the Apache License, Version 2.0 (the "License"); 6 you may not use this file except in compliance with the License. 7 You may obtain a copy of the License at 8 9 http://www.apache.org/licenses/LICENSE-2.0 10 11 Unless required by applicable law or agreed to in writing, software 12 distributed under the License is distributed on an "AS IS" BASIS, 13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 See the License for the specific language governing permissions and 15 limitations under the License. 16 */ 17 (function () { 18 19 var requiredOptions = ["fragment", "canvas", "variables"]; 20 21 var typesSuffixMap = { 22 "bool": "1i", 23 "int": "1i", 24 "float": "1f", 25 "vec2": "2f", 26 "ivec2": "2i", 27 "bvec2": "2b", 28 "vec3": "3f", 29 "ivec3": "3i", 30 "bvec3": "3b", 31 "vec4": "4f", 32 "ivec4": "4i", 33 "bvec4": "4b", 34 "mat2": "Matrix2fv", 35 "mat3": "Matrix3fv", 36 "mat4": "Matrix4fv" 37 }; 38 39 var rUniform = /uniform\s+([a-z]+\s+)?([A-Za-z0-9]+)\s+([a-zA-Z_0-9]+)\s*(\[\s*(.+)\s*\])?/; 40 var rStruct = /struct\s+\w+\s*{[^}]+}\s*;/g; 41 var rStructExtract = /struct\s+(\w+)\s*{([^}]+)}\s*;/; 42 var rStructFields = /[^;]+;/g; 43 var rStructField = /\s*([a-z]+\s+)?([A-Za-z0-9]+)\s+([a-zA-Z_0-9]+)\s*(\[\s*(.+)\s*\])?\s*;/; 44 var rDefine = /#define\s+([a-zA-Z_0-9]+)\s+(.*)/; 45 46 var Lprefix = "Glsl: "; 47 function log (msg) { 48 console.log && console.log(Lprefix+msg); 49 } 50 function warn (msg) { 51 if (console.warn) console.warn(Lprefix+msg); 52 else log("WARN "+msg); 53 } 54 function error (msg) { 55 if (console.error) console.error(Lprefix+msg); 56 else log("ERR "+msg); 57 } 58 59 var genid = (function (i) { return function () { return ++i; } })(0); 60 61 function isArray (a) { 62 return 'length' in a; // duck typing 63 } 64 65 /** 66 * Creates a new Glsl. 67 * init(), update() and render() are called When GL is ready. 68 * 69 * @param options 70 * @param {HTMLCanvasElement} options.canvas The Canvas to render. 71 * @param {String} options.fragment The fragment shader source code. 72 * @param {Object} options.variables The variables map (initial values). 73 * @param {Function} [options.update] The update function to call each frame. (the relative time from the start() and the time since the last update) in milliseconds is given to the function. 74 * @param {Function} [options.init] Call once when GL is initialized. 75 * @param {Function} [options.ready] Call after the first render has been achieved. 76 * @param {Object} [options.contextArgs] Specify WebGLContextAttributes. See http://www.khronos.org/registry/webgl/specs/latest/#5.2 77 * 78 * @namespace 79 */ 80 this.Glsl = function (options) { 81 if ( !(this instanceof arguments.callee) ) return new arguments.callee(options); 82 if (!options) throw new Error("Glsl: {"+requiredOptions+"} are required."); 83 for (var i=0; i<requiredOptions.length; i++) 84 if (!(requiredOptions[i] in options)) 85 throw new Error("Glsl: '"+requiredOptions[i]+"' is required."); 86 87 this.canvas = options.canvas; 88 this.variables = options.variables; // Variable references 89 this.init = options.init || function(t){}; 90 this.update = options.update || function(t){}; 91 this.ready = options.ready || function(t){}; 92 93 this.prog = new Glsl.Program ('attribute vec2 position; void main() { gl_Position = vec4(2.0*position-1.0, 0.0, 1.0);}', options.fragment); 94 this.defines = this.prog.defines; 95 96 if (!this.prog.uniformTypes.resolution) throw new Error("Glsl: You must use a 'vec2 resolution' in your shader."); 97 98 for (var key in this.prog.uniformTypes) { 99 if (!(key in this.variables) && key!="resolution") { 100 warn("variable '"+key+"' not initialized"); 101 } 102 } 103 104 this.initGL(options.contextArgs); 105 this.load(); 106 this.syncAll(); 107 this.init(); 108 this.update(0, 0); 109 this.render(); 110 this.ready(); 111 }; 112 113 /** 114 * Checks if WebGL is supported by the browser. 115 * @type boolean 116 * @public 117 */ 118 Glsl.supported = function () { 119 return !!getWebGLContext(document.createElement("canvas")); 120 }; 121 122 /** 123 * A WebGL program with shaders and variables. 124 * @param {String} vertex The vertex shader source code. 125 * @param {String} fragment The fragment shader source code. 126 * @public 127 */ 128 Glsl.Program = function (vertex, fragment) { 129 this.gl = null; 130 this.vertex = vertex; 131 this.fragment = fragment; 132 133 var src = vertex + '\n' + fragment; 134 this.parseDefines(src); 135 this.parseStructs(src); 136 this.parseUniforms(src); 137 }; 138 139 Glsl.Program.prototype = { 140 141 /** 142 * A map containing all the #define declarations of the GLSL. 143 * 144 * You can use it to synchronize some constants between GLSL and Javascript (like an array capacity). 145 * @public 146 */ 147 defines: null, 148 149 /** 150 * Synchronize a variable from the Javascript into the GLSL. 151 * @param {String} name variable name to synchronize. 152 * @param {String} value variable value. 153 * @public 154 */ 155 syncVariable: function (name, value) { 156 this.recSyncVariable(name, value, this.uniformTypes[name], name); 157 }, 158 159 // ~~~ Going Private Now 160 161 parseDefines: function (src) { 162 this.defines = {}; 163 var lines = src.split("\n"); 164 for (var l=0; l<lines.length; ++l) { 165 var matches = lines[l].match(rDefine); 166 if (matches && matches.length==3) { 167 var dname = matches[1], 168 dvalue = matches[2]; 169 this.defines[dname] = dvalue; 170 } 171 } 172 }, 173 174 parseStructs: function (src) { 175 this.structTypes = {}; 176 var structs = src.match(rStruct); 177 if (!structs) return; 178 for (var s=0; s<structs.length; ++s) { 179 var struct = structs[s]; 180 var structExtract = struct.match(rStructExtract); 181 var structName = structExtract[1]; 182 var structBody = structExtract[2]; 183 var fields = structBody.match(rStructFields); 184 var structType = {}; 185 for (var f=0; f<fields.length; ++f) { 186 var field = fields[f]; 187 var matches = field.match(rStructField); 188 var nativeType = matches[2], 189 vname = matches[3], 190 arrayLength = matches[4]; 191 var type = typesSuffixMap[nativeType] || nativeType; 192 if (arrayLength) { 193 if (arrayLength in this.defines) arrayLength = this.defines[arrayLength]; 194 type = [type, parseInt(arrayLength, 10)]; 195 } 196 structType[vname] = type; 197 } 198 this.structTypes[structName] = structType; 199 } 200 }, 201 202 parseUniforms: function (src) { 203 this.uniformTypes = {}; 204 var lines = src.split("\n"); 205 for (var l=0; l<lines.length; ++l) { 206 var line = lines[l]; 207 var matches = line.match(rUniform); 208 if (matches) { 209 var nativeType = matches[2], 210 vname = matches[3], 211 arrayLength = matches[5]; 212 var type = typesSuffixMap[nativeType] || nativeType; 213 if (arrayLength) { 214 if (arrayLength in this.defines) arrayLength = this.defines[arrayLength]; 215 type = [type, parseInt(arrayLength, 10)]; 216 } 217 this.uniformTypes[vname] = type; 218 } 219 } 220 }, 221 222 recSyncVariable: function (name, value, type, varpath) { 223 var gl = this.gl; 224 if (!type) { 225 warn("variable '"+name+"' not found in your GLSL."); 226 return; 227 } 228 var arrayType = type instanceof Array; 229 var arrayLength; 230 if (arrayType) { 231 arrayLength = type[1]; 232 type = type[0]; 233 } 234 var loc = this.locations[varpath]; 235 if (type in this.structTypes) { 236 var structType = this.structTypes[type]; 237 if (arrayType) { 238 for (var i=0; i<arrayLength && i<value.length; ++i) { 239 var pref = varpath+"["+i+"]."; 240 var v = value[i]; 241 for (var field in structType) { 242 if (!(field in v)) { 243 warn("variable '"+varpath+"["+i+"]' ("+type+") has no field '"+field+"'"); 244 break; 245 } 246 var fieldType = structType[field]; 247 this.recSyncVariable(field, v[field], fieldType, pref+field); 248 } 249 } 250 } 251 else { 252 var pref = varpath+"."; 253 for (var field in structType) { 254 if (!(field in value)) { 255 warn("variable '"+varpath+"' ("+type+") has no field '"+field+"'"); 256 break; 257 } 258 var fieldType = structType[field]; 259 this.recSyncVariable(field, value[field], fieldType, pref+field); 260 } 261 } 262 } 263 else { 264 var t = type; 265 if (arrayType) t += "v"; 266 var fn = "uniform"+t; 267 switch (t) { 268 case "2f": 269 case "2i": 270 if (isArray(value)) 271 gl[fn].call(gl, loc, value[0], value[1]); 272 else if ('x' in value && 'y' in value) 273 gl[fn].call(gl, loc, value.x, value.y); 274 else if ('s' in value && 't' in value) 275 gl[fn].call(gl, loc, value.s, value.t); 276 else 277 error("variable '"+varpath+"' is not valid for binding to vec2(). Use an Array, a {x,y} or a {s,t}."); 278 break; 279 280 case "3f": 281 case "3i": 282 if (isArray(value)) 283 gl[fn].call(gl, loc, value[0], value[1], value[2]); 284 else if ('x' in value && 'y' in value && 'z' in value) 285 gl[fn].call(gl, loc, value.x, value.y, value.z); 286 else if ('s' in value && 't' in value && 'p' in value) 287 gl[fn].call(gl, loc, value.s, value.t, value.p); 288 else if ('r' in value && 'g' in value && 'b' in value) 289 gl[fn].call(gl, loc, value.r, value.g, value.b); 290 else 291 error("variable '"+varpath+"' is not valid for binding to vec3(). Use an Array, a {x,y,z}, a {r,g,b} or a {s,t,p}."); 292 break; 293 294 case "4f": 295 case "4i": 296 if (isArray(value)) 297 gl[fn].call(gl, loc, value[0], value[1], value[2], value[3]); 298 else if ('x' in value && 'y' in value && 'z' in value && 'w' in value) 299 gl[fn].call(gl, loc, value.x, value.y, value.z, value.w); 300 else if ('s' in value && 't' in value && 'p' in value && 'q' in value) 301 gl[fn].call(gl, loc, value.s, value.t, value.p, value.q); 302 else if ('r' in value && 'g' in value && 'b' in value && 'a' in value) 303 gl[fn].call(gl, loc, value.r, value.g, value.b, value.a); 304 else 305 error("variable '"+varpath+"' is not valid for binding to vec4(). Use an Array, a {x,y,z,w}, a {r,g,b,a} or a {s,t,p,q}."); 306 break; 307 308 case "sampler2D": 309 this.syncTexture(gl, loc, value, varpath); 310 break; 311 312 default: 313 if (fn in gl) 314 gl[fn].call(gl, loc, value); // works for simple types and arrays 315 else 316 error("type '"+type+"' not found."); 317 break; 318 } 319 } 320 }, 321 322 syncTexture: function (gl, loc, value, id) { 323 var textureUnit = this.textureUnitForNames[id]; 324 if (!textureUnit) { 325 textureUnit = this.allocTexture(id); 326 } 327 328 gl.activeTexture(gl.TEXTURE0 + textureUnit); 329 330 var texture = this.textureForTextureUnit[textureUnit]; 331 if (texture) { 332 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, value); 333 } 334 else { 335 texture = gl.createTexture(); 336 gl.bindTexture(gl.TEXTURE_2D, texture); 337 gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); 338 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, value); 339 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 340 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 341 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 342 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 343 gl.uniform1i(loc, textureUnit); 344 this.textureForTextureUnit[textureUnit] = texture; 345 } 346 }, 347 348 allocTexture: function (id) { 349 var textureUnit = this.textureUnitCounter; 350 this.textureUnitForNames[id] = textureUnit; 351 this.textureUnitCounter ++; 352 return textureUnit; 353 }, 354 355 initUniformLocations: function () { 356 this.locations = {}; // uniforms locations 357 for (var v in this.uniformTypes) 358 this.recBindLocations(v, this.uniformTypes[v], v); 359 }, 360 361 recBindLocations: function (name, type, varpath) { 362 var arrayType = type instanceof Array; 363 var arrayLength; 364 if (arrayType) { 365 arrayLength = type[1]; 366 type = type[0]; 367 } 368 if (type in this.structTypes) { 369 var structType = this.structTypes[type]; 370 if (arrayType) { 371 for (var i=0; i<arrayLength; ++i) { 372 var pref = varpath+"["+i+"]."; 373 for (var field in structType) { 374 this.recBindLocations(field, structType[field], pref+field); 375 } 376 } 377 } 378 else { 379 var pref = varpath+"."; 380 for (var field in structType) { 381 this.recBindLocations(field, structType[field], pref+field); 382 } 383 } 384 } 385 else { 386 this.locations[varpath] = this.gl.getUniformLocation(this.program, varpath); 387 } 388 }, 389 390 load: function () { 391 var gl = this.gl; 392 393 // Clean old program 394 if (this.program) { 395 gl.deleteProgram(this.program); 396 this.program = null; 397 } 398 399 // Create new program 400 this.program = this.loadProgram([ 401 this.loadShader(this.vertex, gl.VERTEX_SHADER), 402 this.loadShader(this.fragment, gl.FRAGMENT_SHADER) 403 ]); 404 gl.useProgram(this.program); 405 406 /* 407 var nbUniforms = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS); 408 for (var i=0; i<nbUniforms; ++i) { 409 console.log(this.gl.getActiveUniform(this.program, i)); 410 } 411 */ 412 413 // Bind custom variables 414 this.initUniformLocations(); 415 416 // Init textures 417 this.textureUnitForNames = {}; 418 this.textureForTextureUnit = {}; 419 this.textureUnitCounter = 0; 420 }, 421 422 loadProgram: function (shaders) { 423 var gl = this.gl; 424 var program = gl.createProgram(); 425 shaders.forEach(function (shader) { 426 gl.attachShader(program, shader); 427 }); 428 gl.linkProgram(program); 429 430 var linked = gl.getProgramParameter(program, gl.LINK_STATUS); 431 if (!linked) { 432 gl.deleteProgram(program); 433 throw new Error(program+" "+gl.getProgramInfoLog(program)); 434 } 435 return program; 436 }, 437 438 loadShader: function (shaderSource, shaderType) { 439 var gl = this.gl; 440 var shader = gl.createShader(shaderType); 441 gl.shaderSource(shader, shaderSource); 442 gl.compileShader(shader); 443 var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); 444 if (!compiled) { 445 var lastError = gl.getShaderInfoLog(shader); 446 var split = lastError.split(":"); 447 var col = parseInt(split[1], 10); 448 var line = parseInt(split[2], 10); 449 var s = ""; 450 if (!isNaN(col)) { 451 var spaces = ""; for (var i=0; i<col; ++i) spaces+=" "; 452 s = "\n"+spaces+"^"; 453 } 454 error(lastError+"\n"+shaderSource.split("\n")[line-1]+s); 455 gl.deleteShader(shader); 456 throw new Error(shader+" "+lastError); 457 } 458 return shader; 459 } 460 }; 461 462 463 464 Glsl.prototype = { 465 466 /** 467 * A map containing all the #define declarations of the GLSL. 468 * 469 * You can use it to synchronize some constants between GLSL and Javascript (like an array capacity). 470 * @public 471 */ 472 defines: null, 473 474 // ~~~ Public Methods 475 476 /** 477 * Starts/Continues the render and update loop. 478 * The call is not mandatory if you need a one time rendering, but don't need to update things through time (rendering is performed once at Glsl instanciation). 479 * @return the Glsl instance. 480 * @public 481 */ 482 start: function () { 483 var self = this; 484 self._stop = false; 485 if (self._running) return self; 486 var id = self._running = genid(); 487 var startTime = Date.now(); 488 var lastTime = self._stopTime||0; 489 //log("start at "+lastTime); 490 requestAnimationFrame(function loop () { 491 var t = Date.now()-startTime+(self._stopTime||0); 492 if (self._stop || self._running !== id) { // handle stop request and ensure the last start loop is running 493 //log("stop at "+t); 494 self._running = 0; 495 self._stopTime = t; 496 } 497 else { 498 requestAnimationFrame(loop, self.canvas); 499 var delta = t-lastTime; 500 lastTime = t; 501 self.update(t, delta); 502 self.render(); 503 } 504 }, self.canvas); 505 return self; 506 }, 507 508 /** 509 * Pauses the render and update loop. 510 * @return the Glsl instance. 511 * @public 512 */ 513 stop: function () { 514 this._stop = true; 515 return this; 516 }, 517 518 /** 519 * Synchronizes variables from the Javascript into the GLSL. 520 * @param {String} variableNames* all variables to synchronize. 521 * @return the Glsl instance. 522 * @public 523 */ 524 sync: function (/*var1, var2, ...*/) { 525 for (var i=0; i<arguments.length; ++i) { 526 var v = arguments[i]; 527 this.syncVariable(v); 528 } 529 return this; 530 }, 531 532 /** 533 * Synchronizes all variables. 534 * Prefer using sync for a deeper optimization. 535 * @return the Glsl instance. 536 * @public 537 */ 538 syncAll: function () { 539 for (var v in this.variables) this.syncVariable(v); 540 return this; 541 }, 542 543 /** 544 * Set and synchronize a variable to a value. 545 * 546 * @param {String} vname the variable name to set and synchronize. 547 * @param {Any} vvalue the value to set. 548 * @return the Glsl instance. 549 * @public 550 */ 551 set: function (vname, vvalue) { 552 this.variables[vname] = vvalue; 553 this.sync(vname); 554 return this; 555 }, 556 557 /** 558 * Resize the canvas with a new width and height. 559 * @public 560 */ 561 setSize: function (width, height) { 562 this.canvas.width = width; 563 this.canvas.height = height; 564 this.syncResolution(); 565 }, 566 567 // ~~~ Going Private Now 568 569 initGL: function (contextArgs) { 570 var self = this; 571 this.canvas.addEventListener("webglcontextlost", function(event) { 572 event.preventDefault(); 573 }, false); 574 this.canvas.addEventListener("webglcontextrestored", function () { 575 self.running && self.syncAll(); 576 self.load(); 577 }, false); 578 this.gl = this.prog.gl = this.getWebGLContext(this.canvas, contextArgs); 579 }, 580 581 render: function () { 582 this.gl.drawArrays(this.gl.TRIANGLES, 0, 6); 583 }, 584 585 getWebGLContext: function (canvas, contextArgs) { 586 return getWebGLContext(canvas, contextArgs); 587 }, 588 589 syncVariable: function (name) { 590 return this.prog.syncVariable(name, this.variables[name]); 591 }, 592 593 load: function() { 594 var gl = this.gl; 595 this.prog.load(); 596 597 // position 598 var buffer = gl.createBuffer(); 599 gl.bindBuffer(gl.ARRAY_BUFFER, buffer); 600 var positionLocation = gl.getAttribLocation(this.prog.program, "position"); 601 gl.enableVertexAttribArray(positionLocation); 602 gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); 603 604 this.syncResolution(); 605 }, 606 607 syncResolution: function () { 608 var gl = this.gl; 609 var w = this.canvas.width, h = this.canvas.height; 610 gl.viewport(0, 0, w, h); 611 var resolutionLocation = this.prog.locations.resolution; 612 gl.uniform2f(resolutionLocation, w, h); 613 var x1 = 0, y1 = 0, x2 = w, y2 = h; 614 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 615 x1, y1, 616 x2, y1, 617 x1, y2, 618 x1, y2, 619 x2, y1, 620 x2, y2]), gl.STATIC_DRAW); 621 } 622 623 }; 624 625 function getWebGLContext (canvas, contextArgs) { 626 if (!canvas.getContext) return; 627 var names = ["webgl", "experimental-webgl"]; 628 for (var i = 0; i < names.length; ++i) { 629 try { 630 var ctx = canvas.getContext(names[i], contextArgs); 631 if (ctx) return ctx; 632 } catch(e) {} 633 } 634 } 635 636 // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 637 // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating 638 639 // requestAnimationFrame polyfill by Erik Möller 640 // fixes from Paul Irish and Tino Zijdel 641 642 (function() { 643 var lastTime = 0; 644 var vendors = ['ms', 'moz', 'webkit', 'o']; 645 for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 646 window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; 647 window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] 648 || window[vendors[x]+'CancelRequestAnimationFrame']; 649 } 650 651 if (!window.requestAnimationFrame) 652 window.requestAnimationFrame = function(callback, element) { 653 var currTime = new Date().getTime(); 654 var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 655 var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 656 timeToCall); 657 lastTime = currTime + timeToCall; 658 return id; 659 }; 660 661 if (!window.cancelAnimationFrame) 662 window.cancelAnimationFrame = function(id) { 663 clearTimeout(id); 664 }; 665 }()); 666 667 }()); 668