shithub: h264bsd

ref: 164821c259bd23ef7b93a6541048bc977bf5783f
dir: /js/canvas.js/

View raw version
/*
 * This file wraps several WebGL constructs and provides a simple, single texture based WebGLCanvas as well as a
 * specialized YUVWebGLCanvas that can handle YUV->RGB conversion. 
 */

/**
 * Represents a WebGL shader script.
 */
var Script = (function script() {
  function constructor() {}
  
  constructor.createFromElementId = function(id) {
    var script = document.getElementById(id);
    
    // Didn't find an element with the specified ID, abort.
    assert(script , "Could not find shader with ID: " + id);
    
    // Walk through the source element's children, building the shader source string.
    var source = "";
    var currentChild = script .firstChild;
    while(currentChild) {
      if (currentChild.nodeType == 3) {
        source += currentChild.textContent;
      }
      currentChild = currentChild.nextSibling;
    }
    
    var res = new constructor();
    res.type = script.type;
    res.source = source;
    return res;
  };
  
  constructor.createFromSource = function(type, source) {
    var res = new constructor();
    res.type = type;
    res.source = source;
    return res;
  }
  return constructor;
})();

/**
 * Represents a WebGL shader object and provides a mechanism to load shaders from HTML
 * script tags.
 */
var Shader = (function shader() {
  function constructor(gl, script) {
    
    // Now figure out what type of shader script we have, based on its MIME type.
    if (script.type == "x-shader/x-fragment") {
      this.shader = gl.createShader(gl.FRAGMENT_SHADER);
    } else if (script.type == "x-shader/x-vertex") {
      this.shader = gl.createShader(gl.VERTEX_SHADER);
    } else {
      error("Unknown shader type: " + script.type);
      return;
    }
    
    // Send the source to the shader object.
    gl.shaderSource(this.shader, script.source);
    
    // Compile the shader program.
    gl.compileShader(this.shader);
    
    // See if it compiled successfully.
    if (!gl.getShaderParameter(this.shader, gl.COMPILE_STATUS)) {
      error("An error occurred compiling the shaders: " + gl.getShaderInfoLog(this.shader));
      return;
    }
  }
  return constructor;
})();

var Program = (function () {
  function constructor(gl) {
    this.gl = gl;
    this.program = this.gl.createProgram();
  }
  constructor.prototype = {
    attach: function (shader) {
      this.gl.attachShader(this.program, shader.shader);
    }, 
    link: function () {
      this.gl.linkProgram(this.program);
      // If creating the shader program failed, alert.
      assert(this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS),
             "Unable to initialize the shader program.");
    },
    use: function () {
      this.gl.useProgram(this.program);
    },
    getAttributeLocation: function(name) {
      return this.gl.getAttribLocation(this.program, name);
    },
    setMatrixUniform: function(name, array) {
      var uniform = this.gl.getUniformLocation(this.program, name);
      this.gl.uniformMatrix4fv(uniform, false, array);
    }
  };
  return constructor;
})();

/**
 * Represents a WebGL texture object.
 */
var Texture = (function texture() {
  function constructor(gl, size, format) {
    this.gl = gl;
    this.size = size;
    this.texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, this.texture);
    this.format = format ? format : gl.LUMINANCE; 
    gl.texImage2D(gl.TEXTURE_2D, 0, this.format, size.w, size.h, 0, this.format, gl.UNSIGNED_BYTE, null);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  }
  var textureIDs = null;
  constructor.prototype = {
    fill: function(textureData, useTexSubImage2D) {
      var gl = this.gl;
      assert(textureData.length >= this.size.w * this.size.h, 
             "Texture size mismatch, data:" + textureData.length + ", texture: " + this.size.w * this.size.h);
      gl.bindTexture(gl.TEXTURE_2D, this.texture);
      if (useTexSubImage2D) {
        gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.size.w , this.size.h, this.format, gl.UNSIGNED_BYTE, textureData);
      } else {
        // texImage2D seems to be faster, thus keeping it as the default
        gl.texImage2D(gl.TEXTURE_2D, 0, this.format, this.size.w, this.size.h, 0, this.format, gl.UNSIGNED_BYTE, textureData);
      }
    },
    bind: function(n, program, name) {
      var gl = this.gl;
      if (!textureIDs) {
        textureIDs = [gl.TEXTURE0, gl.TEXTURE1, gl.TEXTURE2];
      }
      gl.activeTexture(textureIDs[n]);
      gl.bindTexture(gl.TEXTURE_2D, this.texture);
      gl.uniform1i(gl.getUniformLocation(program.program, name), n);
    }
  };
  return constructor; 
})();

/**
 * Generic WebGL backed canvas that sets up: a quad to paint a texture on, appropriate vertex/fragment shaders,
 * scene parameters and other things. Specialized versions of this class can be created by overriding several 
 * initialization methods.
 * 
 * <code>
 * var canvas = new WebGLCanvas(document.getElementById('canvas'), new Size(512, 512);
 * canvas.texture.fill(data);
 * canvas.drawScene();
 * </code>
 */
var WebGLCanvas = (function () {
  
  var vertexShaderScript = Script.createFromSource("x-shader/x-vertex", text([
    "attribute vec3 aVertexPosition;",
    "attribute vec2 aTextureCoord;",
    "uniform mat4 uMVMatrix;",
    "uniform mat4 uPMatrix;",
    "varying highp vec2 vTextureCoord;",
    "void main(void) {",
    "  gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);",
    "  vTextureCoord = aTextureCoord;",
    "}"
    ]));
  
  var fragmentShaderScript = Script.createFromSource("x-shader/x-fragment", text([
    "precision highp float;",
    "varying highp vec2 vTextureCoord;",
    "uniform sampler2D texture;",
    "void main(void) {",
    "  gl_FragColor = texture2D(texture, vTextureCoord);",
    "}"
    ]));
  
  function constructor(canvas, size, useFrameBuffer) {
    this.canvas = canvas;
    this.size = size;
    this.canvas.width = size.w;
    this.canvas.height = size.h;
    
    this.onInitWebGL();
    this.onInitShaders();
    initBuffers.call(this);
    if (useFrameBuffer) {
      initFramebuffer.call(this);
    }
    this.onInitTextures();
    initScene.call(this);
  }

  /**
   * Initialize a frame buffer so that we can render off-screen.
   */
  function initFramebuffer() {
    var gl = this.gl;
    
    // Create framebuffer object and texture.
    this.framebuffer = gl.createFramebuffer(); 
    gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
    this.framebufferTexture = new Texture(this.gl, this.size, gl.RGB);

    // Create and allocate renderbuffer for depth data.
    var renderbuffer = gl.createRenderbuffer();
    gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
    gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, this.size.w, this.size.h);

    // Attach texture and renderbuffer to the framebuffer.
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.framebufferTexture.texture, 0);
    gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer);
  }
  
  /**
   * Initialize vertex and texture coordinate buffers for a plane.
   */
  function initBuffers() {
    var tmp;
    var gl = this.gl;
    
    // Create vertex position buffer.
    this.quadVPBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVPBuffer);
    tmp = [
       1.0,  1.0, 0.0,
      -1.0,  1.0, 0.0, 
       1.0, -1.0, 0.0, 
      -1.0, -1.0, 0.0];
    
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(tmp), gl.STATIC_DRAW);
    this.quadVPBuffer.itemSize = 3;
    this.quadVPBuffer.numItems = 4;
    
    /*
     +--------------------+ 
     | -1,1 (1)           | 1,1 (0)
     |                    |
     |                    |
     |                    |
     |                    |
     |                    |
     | -1,-1 (3)          | 1,-1 (2)
     +--------------------+
     */
    
    var scaleX = 1.0;
    var scaleY = 1.0;
    
    // Create vertex texture coordinate buffer.
    this.quadVTCBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVTCBuffer);
    tmp = [
      scaleX, 0.0,
      0.0, 0.0,
      scaleX, scaleY,
      0.0, scaleY,
    ];
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(tmp), gl.STATIC_DRAW);
  }
  
  function mvIdentity() {
    this.mvMatrix = Matrix.I(4);
  }

  function mvMultiply(m) {
    this.mvMatrix = this.mvMatrix.x(m);
  }

  function mvTranslate(m) {
    mvMultiply.call(this, Matrix.Translation($V([m[0], m[1], m[2]])).ensure4x4());
  }
  
  function setMatrixUniforms() {
    this.program.setMatrixUniform("uPMatrix", new Float32Array(this.perspectiveMatrix.flatten()));
    this.program.setMatrixUniform("uMVMatrix", new Float32Array(this.mvMatrix.flatten()));
  }

  function initScene() {
    var gl = this.gl;
    
    // Establish the perspective with which we want to view the
    // scene. Our field of view is 45 degrees, with a width/height
    // ratio of 640:480, and we only want to see objects between 0.1 units
    // and 100 units away from the camera.
    
    this.perspectiveMatrix = makePerspective(45, 1, 0.1, 100.0);
    
    // Set the drawing position to the "identity" point, which is
    // the center of the scene.
    mvIdentity.call(this);
    
    // Now move the drawing position a bit to where we want to start
    // drawing the square.
    mvTranslate.call(this, [0.0, 0.0, -2.4]);

    // Draw the cube by binding the array buffer to the cube's vertices
    // array, setting attributes, and pushing it to GL.
    gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVPBuffer);
    gl.vertexAttribPointer(this.vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
    
    // Set the texture coordinates attribute for the vertices.
    
    gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVTCBuffer);
    gl.vertexAttribPointer(this.textureCoordAttribute, 2, gl.FLOAT, false, 0, 0);  
    
    this.onInitSceneTextures();
    
    setMatrixUniforms.call(this);
    
    if (this.framebuffer) {
      console.log("Bound Frame Buffer");
      gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
    }
  }
  
  constructor.prototype = {
    toString: function() {
      return "WebGLCanvas Size: " + this.size;
    },
    checkLastError: function (operation) {
      var err = this.gl.getError();
      if (err != this.gl.NO_ERROR) {
        var name = this.glNames[err];
        name = (name !== undefined) ? name + "(" + err + ")":
            ("Unknown WebGL ENUM (0x" + value.toString(16) + ")");
        if (operation) {
          console.log("WebGL Error: %s, %s", operation, name);
        } else {
          console.log("WebGL Error: %s", name);
        }
        console.trace();
      }
    },
    onInitWebGL: function () {
      try {
        this.gl = this.canvas.getContext("experimental-webgl");
      } catch(e) {}
      
      if (!this.gl) {
        error("Unable to initialize WebGL. Your browser may not support it.");
      }
      if (this.glNames) {
        return;
      }
      this.glNames = {};
      for (var propertyName in this.gl) {
        if (typeof this.gl[propertyName] == 'number') {
          this.glNames[this.gl[propertyName]] = propertyName;
        }
      }
    },
    onInitShaders: function() {
      this.program = new Program(this.gl);
      this.program.attach(new Shader(this.gl, vertexShaderScript));
      this.program.attach(new Shader(this.gl, fragmentShaderScript));
      this.program.link();
      this.program.use();
      this.vertexPositionAttribute = this.program.getAttributeLocation("aVertexPosition");
      this.gl.enableVertexAttribArray(this.vertexPositionAttribute);
      this.textureCoordAttribute = this.program.getAttributeLocation("aTextureCoord");;
      this.gl.enableVertexAttribArray(this.textureCoordAttribute);
    },
    onInitTextures: function () {
      var gl = this.gl;
      this.texture = new Texture(gl, this.size, gl.RGB);
    },
    onInitSceneTextures: function () {
      this.texture.bind(0, this.program, "texture");
    },
    drawScene: function() {
      this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
    },
    readPixels: function(buffer) {
      var gl = this.gl;
      gl.readPixels(0, 0, this.size.w, this.size.h, gl.RGB, gl.UNSIGNED_BYTE, buffer);
    }
  };
  return constructor;
})();

var YUVWebGLCanvas = (function () {
  var vertexShaderScript = Script.createFromSource("x-shader/x-vertex", text([
    "attribute vec3 aVertexPosition;",
    "attribute vec2 aTextureCoord;",
    "uniform mat4 uMVMatrix;",
    "uniform mat4 uPMatrix;",
    "varying highp vec2 vTextureCoord;",
    "void main(void) {",
    "  gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);",
    "  vTextureCoord = aTextureCoord;",
    "}"
  ]));
  
  var fragmentShaderScriptOld = Script.createFromSource("x-shader/x-fragment", text([
    "precision highp float;",
    "varying highp vec2 vTextureCoord;",
    "uniform sampler2D YTexture;",
    "uniform sampler2D UTexture;",
    "uniform sampler2D VTexture;",
    
    "void main(void) {",
    "  vec3 YUV = vec3",
    "  (",
    "    texture2D(YTexture, vTextureCoord).x * 1.1643828125,   // premultiply Y",
    "    texture2D(UTexture, vTextureCoord).x,",
    "    texture2D(VTexture, vTextureCoord).x",
    "  );",
    "  gl_FragColor = vec4",
    "  (",
    "    YUV.x + 1.59602734375 * YUV.z - 0.87078515625,",
    "    YUV.x - 0.39176171875 * YUV.y - 0.81296875 * YUV.z + 0.52959375,",
    "    YUV.x + 2.017234375   * YUV.y - 1.081390625,",
    "    1",
    "  );",
    "}"
  ]));
  
  var fragmentShaderScriptSimple = Script.createFromSource("x-shader/x-fragment", text([
   "precision highp float;",
   "varying highp vec2 vTextureCoord;",
   "uniform sampler2D YTexture;",
   "uniform sampler2D UTexture;",
   "uniform sampler2D VTexture;",
   
   "void main(void) {",
   "  gl_FragColor = texture2D(YTexture, vTextureCoord);",
   "}"
   ]));

  var fragmentShaderScript = Script.createFromSource("x-shader/x-fragment", text([
    "precision highp float;",
    "varying highp vec2 vTextureCoord;",
    "uniform sampler2D YTexture;",
    "uniform sampler2D UTexture;",
    "uniform sampler2D VTexture;",
    "const mat4 YUV2RGB = mat4",
    "(",
    " 1.1643828125, 0, 1.59602734375, -.87078515625,",
    " 1.1643828125, -.39176171875, -.81296875, .52959375,",
    " 1.1643828125, 2.017234375, 0, -1.081390625,",
    " 0, 0, 0, 1",
    ");",
  
    "void main(void) {",
    " gl_FragColor = vec4( texture2D(YTexture,  vTextureCoord).x, texture2D(UTexture, vTextureCoord).x, texture2D(VTexture, vTextureCoord).x, 1) * YUV2RGB;",
    "}"
  ]));
  
  
  function constructor(canvas, size) {
    WebGLCanvas.call(this, canvas, size);
  } 
  
  constructor.prototype = inherit(WebGLCanvas, {
    onInitShaders: function() {
      this.program = new Program(this.gl);
      this.program.attach(new Shader(this.gl, vertexShaderScript));
      this.program.attach(new Shader(this.gl, fragmentShaderScript));
      this.program.link();
      this.program.use();
      this.vertexPositionAttribute = this.program.getAttributeLocation("aVertexPosition");
      this.gl.enableVertexAttribArray(this.vertexPositionAttribute);
      this.textureCoordAttribute = this.program.getAttributeLocation("aTextureCoord");;
      this.gl.enableVertexAttribArray(this.textureCoordAttribute);
    },
    onInitTextures: function () {
      this.YTexture = new Texture(this.gl, this.size);
      this.UTexture = new Texture(this.gl, this.size.getHalfSize());
      this.VTexture = new Texture(this.gl, this.size.getHalfSize());
    },
    onInitSceneTextures: function () {
      this.YTexture.bind(0, this.program, "YTexture");
      this.UTexture.bind(1, this.program, "UTexture");
      this.VTexture.bind(2, this.program, "VTexture");
    },
    fillYUVTextures: function(y, u, v) {
      this.YTexture.fill(y);
      this.UTexture.fill(u);
      this.VTexture.fill(v);
    },
    toString: function() {
      return "YUVCanvas Size: " + this.size;
    }
  });
  
  return constructor;
})(); 


var FilterWebGLCanvas = (function () {
  var vertexShaderScript = Script.createFromSource("x-shader/x-vertex", text([
    "attribute vec3 aVertexPosition;",
    "attribute vec2 aTextureCoord;",
    "uniform mat4 uMVMatrix;",
    "uniform mat4 uPMatrix;",
    "varying highp vec2 vTextureCoord;",
    "void main(void) {",
    "  gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);",
    "  vTextureCoord = aTextureCoord;",
    "}"
  ]));

  var fragmentShaderScript = Script.createFromSource("x-shader/x-fragment", text([
    "precision highp float;",
    "varying highp vec2 vTextureCoord;",
    "uniform sampler2D FTexture;",
    
    "void main(void) {",
    " gl_FragColor = texture2D(FTexture,  vTextureCoord);",
    "}"
  ]));
  
  
  function constructor(canvas, size, useFrameBuffer) {
    WebGLCanvas.call(this, canvas, size, useFrameBuffer);
  } 
  
  constructor.prototype = inherit(WebGLCanvas, {
    onInitShaders: function() {
      this.program = new Program(this.gl);
      this.program.attach(new Shader(this.gl, vertexShaderScript));
      this.program.attach(new Shader(this.gl, fragmentShaderScript));
      this.program.link();
      this.program.use();
      this.vertexPositionAttribute = this.program.getAttributeLocation("aVertexPosition");
      this.gl.enableVertexAttribArray(this.vertexPositionAttribute);
      this.textureCoordAttribute = this.program.getAttributeLocation("aTextureCoord");
      this.gl.enableVertexAttribArray(this.textureCoordAttribute);
    },
    onInitTextures: function () {
      console.log("creatingTextures: size: " + this.size);
      this.FTexture = new Texture(this.gl, this.size, this.gl.RGB);
    },
    onInitSceneTextures: function () {
      this.FTexture.bind(0, this.program, "FTexture");
    },
    process: function(buffer, output) {
      this.FTexture.fill(buffer);
      this.drawScene();
      this.readPixels(output);
    },
    toString: function() {
      return "FilterWebGLCanvas Size: " + this.size;
    }
  });
  
  return constructor;
})();