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