• Program.js

  • ¶
    var Context = require('./Context');
    var sys = require('pex-sys');
    var IO = sys.IO;
    
    var kVertexShaderPrefix = '' +
      '#ifdef GL_ES\n' +
      'precision highp float;\n' +
      '#endif\n' +
      '#define VERT\n';
    
    var kFragmentShaderPrefix = '' +
      '#ifdef GL_ES\n' +
      '#ifdef GL_FRAGMENT_PRECISION_HIGH\n' +
      '  precision highp float;\n' +
      '#else\n' +
      '  precision mediump float;\n' +
      '#endif\n' +
      '#endif\n' +
      '#define FRAG\n';
    
    function Program(vertSrc, fragSrc) {
      this.gl = Context.currentContext;
      this.handle = this.gl.createProgram();
      this.uniforms = {};
      this.attributes = {};
      this.addSources(vertSrc, fragSrc);
      this.ready = false;
      if (this.vertShader && this.fragShader) {
        this.link();
      }
    }
    
    Program.prototype.addSources = function(vertSrc, fragSrc) {
      if (fragSrc == null) {
        fragSrc = vertSrc;
      }
      if (vertSrc) {
        this.addVertexSource(vertSrc);
      }
      if (fragSrc) {
        return this.addFragmentSource(fragSrc);
      }
    };
    
    Program.prototype.addVertexSource = function(vertSrc) {
      this.vertShader = this.gl.createShader(this.gl.VERTEX_SHADER);
      this.gl.shaderSource(this.vertShader, kVertexShaderPrefix + vertSrc + '\n');
      this.gl.compileShader(this.vertShader);
      if (!this.gl.getShaderParameter(this.vertShader, this.gl.COMPILE_STATUS)) {
        throw new Error(this.gl.getShaderInfoLog(this.vertShader));
      }
    };
    
    Program.prototype.addFragmentSource = function(fragSrc) {
      this.fragShader = this.gl.createShader(this.gl.FRAGMENT_SHADER);
      this.gl.shaderSource(this.fragShader, kFragmentShaderPrefix + fragSrc + '\n');
      this.gl.compileShader(this.fragShader);
      if (!this.gl.getShaderParameter(this.fragShader, this.gl.COMPILE_STATUS)) {
        throw new Error(this.gl.getShaderInfoLog(this.fragShader));
      }
    };
    
    Program.prototype.link = function() {
      this.gl.attachShader(this.handle, this.vertShader);
      this.gl.attachShader(this.handle, this.fragShader);
      this.gl.linkProgram(this.handle);
    
      if (!this.gl.getProgramParameter(this.handle, this.gl.LINK_STATUS)) {
        throw new Error(this.gl.getProgramInfoLog(this.handle));
      }
    
      var numUniforms = this.gl.getProgramParameter(this.handle, this.gl.ACTIVE_UNIFORMS);
    
      for (var i=0; i<numUniforms; i++) {
        var info = this.gl.getActiveUniform(this.handle, i);
        if (info.size > 1) {
          for (var j=0; j<info.size; j++) {
            var arrayElementName = info.name.replace(/\[\d+\]/, '[' + j + ']');
            var location = this.gl.getUniformLocation(this.handle, arrayElementName);
            this.uniforms[arrayElementName] = Program.makeUniformSetter(this.gl, info.type, location);
          }
        } else {
          var location = this.gl.getUniformLocation(this.handle, info.name);
          this.uniforms[info.name] = Program.makeUniformSetter(this.gl, info.type, location);
        }
      }
    
      var numAttributes = this.gl.getProgramParameter(this.handle, this.gl.ACTIVE_ATTRIBUTES);
      for (var i=0; i<numAttributes; i++) {
        info = this.gl.getActiveAttrib(this.handle, i);
        var location = this.gl.getAttribLocation(this.handle, info.name);
        this.attributes[info.name] = location;
      }
      this.ready = true;
      return this;
    };
    
    Program.prototype.use = function() {
      if (Program.currentProgram !== this.handle) {
        Program.currentProgram = this.handle;
        return this.gl.useProgram(this.handle);
      }
    };
    
    Program.prototype.dispose = function() {
      this.gl.deleteShader(this.vertShader);
      this.gl.deleteShader(this.fragShader);
      return this.gl.deleteProgram(this.handle);
    };
    
    Program.load = function(url, callback, options) {
      var program;
      program = new Program();
      IO.loadTextFile(url, function(source) {
        console.log("Program.Compiling " + url);
        program.addSources(source);
        program.link();
        if (callback) {
          callback();
        }
        if (options && options.autoreload) {
          return IO.watchTextFile(url, function(source) {
            var e;
            try {
              program.gl.detachShader(program.handle, program.vertShader);
              program.gl.detachShader(program.handle, program.fragShader);
              program.addSources(source);
              return program.link();
            } catch (_error) {
              e = _error;
              console.log("Program.load : failed to reload " + url);
              return console.log(e);
            }
          });
        }
      });
      return program;
    };
    
    Program.makeUniformSetter = function(gl, type, location) {
      var setterFun = null;
      switch (type) {
        case gl.BOOL:
        case gl.INT:
          setterFun = function(value) {
            return gl.uniform1i(location, value);
          };
          break;
        case gl.SAMPLER_2D:
        case gl.SAMPLER_CUBE:
          setterFun = function(value) {
            return gl.uniform1i(location, value);
          };
          break;
        case gl.FLOAT:
          setterFun = function(value) {
            return gl.uniform1f(location, value);
          };
          break;
        case gl.FLOAT_VEC2:
          setterFun = function(v) {
            return gl.uniform2f(location, v.x, v.y);
          };
          break;
        case gl.FLOAT_VEC3:
          setterFun = function(v) {
            return gl.uniform3f(location, v.x, v.y, v.z);
          };
          break;
        case gl.FLOAT_VEC4:
          setterFun = function(v) {
            if (v.r != null) {
              gl.uniform4f(location, v.r, v.g, v.b, v.a);
            }
            if (v.x != null) {
              return gl.uniform4f(location, v.x, v.y, v.z, v.w);
            }
          };
          break;
        case gl.FLOAT_MAT4:
          var mv = new Float32Array(16);
          setterFun = function(m) {
            mv[0] = m.a11;
            mv[1] = m.a21;
            mv[2] = m.a31;
            mv[3] = m.a41;
            mv[4] = m.a12;
            mv[5] = m.a22;
            mv[6] = m.a32;
            mv[7] = m.a42;
            mv[8] = m.a13;
            mv[9] = m.a23;
            mv[10] = m.a33;
            mv[11] = m.a43;
            mv[12] = m.a14;
            mv[13] = m.a24;
            mv[14] = m.a34;
            mv[15] = m.a44;
            return gl.uniformMatrix4fv(location, false, mv);
          };
      }
      if (setterFun) {
        setterFun.type = type;
        return setterFun;
      } else {
        return function() {
          throw new Error('Unknown uniform type: ' + type);
        };
      }
    };
    
    module.exports = Program;