text: /*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~
  /* viaWebGL
  /* Set shaders on Image or Canvas with WebGL
  /* Built on 2016-9-9
  /* http://via.hoff.in
  */
  ViaWebGL = function(incoming) {
  
      /* Custom WebGL API calls
      ~*~*~*~*~*~*~*~*~*~*~*~*/
      this['gl-drawing'] = function(e) { return e; };
      this['gl-loaded'] = function(e) { return e; };
      this.ready = function(e) { return e; };
  
      var gl = this.maker();
      this.flat = document.createElement('canvas').getContext('2d');
      this.tile_size = 'u_tile_size';
      this.vShader = 'vShader.glsl';
      this.fShader = 'fShader.glsl';
      this.wrap = gl.CLAMP_TO_EDGE;
      this.tile_pos = 'a_tile_pos';
      this.filter = gl.NEAREST;
      this.pos = 'a_pos';
      this.height = 128;
      this.width = 128;
      this.on = 0;
      this.gl = gl;
      // Assign from incoming terms
      for (var key in incoming) {
          this[key] = incoming[key];
      }
  };
  
  ViaWebGL.prototype = {
  
      init: function(source) {
          var ready = this.ready;
          // Allow for mouse actions on click
          if (this.hasOwnProperty('container') && this.hasOwnProperty('onclick')) {
              this.container.onclick = this[this.onclick].bind(this);
          }
          if (source && source.height && source.width) {
              this.ready = this.toCanvas.bind(this,source);
              this.height = source.height;
              this.width = source.width;
          }
          this.source = source;
          this.gl.canvas.width = this.width;
          this.gl.canvas.height = this.height;
          this.gl.viewport(0, 0, this.width, this.height);
          // Load the shaders when ready and return the promise
          var step = [[this.vShader, this.fShader].map(this.getter)];
          step.push(this.toProgram.bind(this), this.toBuffers.bind(this));
          return Promise.all(step[0]).then(step[1]).then(step[2]).then(this.ready);
  
      },
      // Make a canvas
      maker: function(options){
          return this.context(document.createElement('canvas'));
      },
      context: function(a){
          return a.getContext('experimental-webgl') || a.getContext('webgl');
      },
      // Get a file as a promise
      getter: function(where) {
          return new Promise(function(done){
              // Return if not a valid filename
              if (where.slice(-4) != 'glsl') {
                  return done(where);
              }
              var bid = new XMLHttpRequest();
              var win = function(){
                  if (bid.status == 200) {
                      return done(bid.response);
                  }
                  return done(where);
              };
              bid.open('GET', where, true);
              bid.onerror = bid.onload = win;
              bid.send();
          });
      },
      // Link shaders from strings
      toProgram: function(files) {
          var gl = this.gl;
          var program = gl.createProgram();
          var ok = function(kind,status,value,sh) {
              if (!gl['get'+kind+'Parameter'](value, gl[status+'_STATUS'])){
                  console.log((sh||'LINK')+':\n'+gl['get'+kind+'InfoLog'](value));
              }
              return value;
          }
          // 1st is vertex; 2nd is fragment
          files.map(function(given,i) {
              var sh = ['VERTEX_SHADER', 'FRAGMENT_SHADER'][i];
              var shader = gl.createShader(gl[sh]);
              gl.shaderSource(shader, given);
              gl.compileShader(shader);
              gl.attachShader(program, shader);
              ok('Shader','COMPILE',shader,sh);
          });
          gl.linkProgram(program);
          return ok('Program','LINK',program);
      },
      // Load data to the buffers
      toBuffers: function(program) {
  
          // Allow for custom loading
          this.gl.useProgram(program);
          this['gl-loaded'].call(this, program);
  
          // Unchangeable square array buffer fills viewport with texture
          var boxes = [[-1, 1,-1,-1, 1, 1, 1,-1], [0, 1, 0, 0, 1, 1, 1, 0]];
          var buffer = new Float32Array([].concat.apply([], boxes));
          var bytes = buffer.BYTES_PER_ELEMENT;
          var gl = this.gl;
          var count = 4;
  
          // Get uniform term
          var tile_size = gl.getUniformLocation(program, this.tile_size);
          gl.uniform2f(tile_size, gl.canvas.height, gl.canvas.width);
  
          // Get attribute terms
          this.att = [this.pos, this.tile_pos].map(function(name, number) {
  
              var index = Math.min(number, boxes.length-1);
              var vec = Math.floor(boxes[index].length/count);
              var vertex = gl.getAttribLocation(program, name);
  
              return [vertex, vec, gl.FLOAT, 0, vec*bytes, count*index*vec*bytes];
          });
          // Get texture
          this.tex = {
              texParameteri: [
                  [gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, this.wrap],
                  [gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, this.wrap],
                  [gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, this.filter],
                  [gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this.filter]
              ],
              texImage2D: [gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE],
              bindTexture: [gl.TEXTURE_2D, gl.createTexture()],
              drawArrays: [gl.TRIANGLE_STRIP, 0, count],
              pixelStorei: [gl.UNPACK_FLIP_Y_WEBGL, 1]
          };
          // Build the position and texture buffer
          gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
          gl.bufferData(gl.ARRAY_BUFFER, buffer, gl.STATIC_DRAW);
      },
      // Turns image or canvas into a rendered canvas
      toCanvas: function(tile) {
          // Stop Rendering
          if (this.on%2 !== 0) {
              if(tile.nodeName == 'IMG') {
                  this.flat.canvas.width = tile.width;
                  this.flat.canvas.height = tile.height;
                  this.flat.drawImage(tile,0,0,tile.width,tile.height);
                  return this.flat.canvas;
              }
              return tile;
          }
  
          // Allow for custom drawing in webGL
          this['gl-drawing'].call(this,tile);
          var gl = this.gl;
  
          // Set Attributes for GLSL
          this.att.map(function(x){
  
              gl.enableVertexAttribArray(x.slice(0,1));
              gl.vertexAttribPointer.apply(gl, x);
          });
  
          // Set Texture for GLSL
          gl.activeTexture(gl.TEXTURE0);
          gl.bindTexture.apply(gl, this.tex.bindTexture);
          gl.pixelStorei.apply(gl, this.tex.pixelStorei);
  
          // Apply texture parameters
          this.tex.texParameteri.map(function(x){
              gl.texParameteri.apply(gl, x);
          });
          // Send the tile into the texture.
          var output = this.tex.texImage2D.concat([tile]);
          gl.texImage2D.apply(gl, output);
  
          // Draw everything needed to canvas
          gl.drawArrays.apply(gl, this.tex.drawArrays);
  
          // Apply to container if needed
          if (this.container) {
              this.container.appendChild(this.gl.canvas);
          }
          return this.gl.canvas;
      },
      toggle: function() {
          this.on ++;
          this.container.innerHTML = '';
          this.container.appendChild(this.toCanvas(this.source));
  
      }
  }