shithub: h264bsd

Download patch

ref: d6f22738160011bec3a5317d7542d8d756a14a38
parent: 60f29cb6e436c457edf5fd6f5d590ebd5ec1f1bb
author: Chris Jarabek <chris.jarabek@gmail.com>
date: Tue Jan 21 09:22:41 EST 2014

Color correct H264 frames being decoded w/ YUV canvas, color skewed frames being decoded with YUV->RGB conversion.

--- /dev/null
+++ b/js/canvas.js
@@ -1,0 +1,553 @@
+/*
+ * 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 () {
+      console.log("creatingTextures: size: " + this.size);
+      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;
+})(); 
\ No newline at end of file
--- /dev/null
+++ b/js/glUtils.js
@@ -1,0 +1,193 @@
+// augment Sylvester some
+Matrix.Translation = function (v)
+{
+  if (v.elements.length == 2) {
+    var r = Matrix.I(3);
+    r.elements[2][0] = v.elements[0];
+    r.elements[2][1] = v.elements[1];
+    return r;
+  }
+
+  if (v.elements.length == 3) {
+    var r = Matrix.I(4);
+    r.elements[0][3] = v.elements[0];
+    r.elements[1][3] = v.elements[1];
+    r.elements[2][3] = v.elements[2];
+    return r;
+  }
+
+  throw "Invalid length for Translation";
+}
+
+Matrix.prototype.flatten = function ()
+{
+    var result = [];
+    if (this.elements.length == 0)
+        return [];
+
+
+    for (var j = 0; j < this.elements[0].length; j++)
+        for (var i = 0; i < this.elements.length; i++)
+            result.push(this.elements[i][j]);
+    return result;
+}
+
+Matrix.prototype.ensure4x4 = function()
+{
+    if (this.elements.length == 4 &&
+        this.elements[0].length == 4)
+        return this;
+
+    if (this.elements.length > 4 ||
+        this.elements[0].length > 4)
+        return null;
+
+    for (var i = 0; i < this.elements.length; i++) {
+        for (var j = this.elements[i].length; j < 4; j++) {
+            if (i == j)
+                this.elements[i].push(1);
+            else
+                this.elements[i].push(0);
+        }
+    }
+
+    for (var i = this.elements.length; i < 4; i++) {
+        if (i == 0)
+            this.elements.push([1, 0, 0, 0]);
+        else if (i == 1)
+            this.elements.push([0, 1, 0, 0]);
+        else if (i == 2)
+            this.elements.push([0, 0, 1, 0]);
+        else if (i == 3)
+            this.elements.push([0, 0, 0, 1]);
+    }
+
+    return this;
+};
+
+Matrix.prototype.make3x3 = function()
+{
+    if (this.elements.length != 4 ||
+        this.elements[0].length != 4)
+        return null;
+
+    return Matrix.create([[this.elements[0][0], this.elements[0][1], this.elements[0][2]],
+                          [this.elements[1][0], this.elements[1][1], this.elements[1][2]],
+                          [this.elements[2][0], this.elements[2][1], this.elements[2][2]]]);
+};
+
+Vector.prototype.flatten = function ()
+{
+    return this.elements;
+};
+
+function mht(m) {
+    var s = "";
+    if (m.length == 16) {
+        for (var i = 0; i < 4; i++) {
+            s += "<span style='font-family: monospace'>[" + m[i*4+0].toFixed(4) + "," + m[i*4+1].toFixed(4) + "," + m[i*4+2].toFixed(4) + "," + m[i*4+3].toFixed(4) + "]</span><br>";
+        }
+    } else if (m.length == 9) {
+        for (var i = 0; i < 3; i++) {
+            s += "<span style='font-family: monospace'>[" + m[i*3+0].toFixed(4) + "," + m[i*3+1].toFixed(4) + "," + m[i*3+2].toFixed(4) + "]</font><br>";
+        }
+    } else {
+        return m.toString();
+    }
+    return s;
+}
+
+//
+// gluLookAt
+//
+function makeLookAt(ex, ey, ez,
+                    cx, cy, cz,
+                    ux, uy, uz)
+{
+    var eye = $V([ex, ey, ez]);
+    var center = $V([cx, cy, cz]);
+    var up = $V([ux, uy, uz]);
+
+    var mag;
+
+    var z = eye.subtract(center).toUnitVector();
+    var x = up.cross(z).toUnitVector();
+    var y = z.cross(x).toUnitVector();
+
+    var m = $M([[x.e(1), x.e(2), x.e(3), 0],
+                [y.e(1), y.e(2), y.e(3), 0],
+                [z.e(1), z.e(2), z.e(3), 0],
+                [0, 0, 0, 1]]);
+
+    var t = $M([[1, 0, 0, -ex],
+                [0, 1, 0, -ey],
+                [0, 0, 1, -ez],
+                [0, 0, 0, 1]]);
+    return m.x(t);
+}
+
+//
+// glOrtho
+//
+function makeOrtho(left, right,
+                   bottom, top,
+                   znear, zfar)
+{
+    var tx = -(right+left)/(right-left);
+    var ty = -(top+bottom)/(top-bottom);
+    var tz = -(zfar+znear)/(zfar-znear);
+
+    return $M([[2/(right-left), 0, 0, tx],
+               [0, 2/(top-bottom), 0, ty],
+               [0, 0, -2/(zfar-znear), tz],
+               [0, 0, 0, 1]]);
+}
+
+//
+// gluPerspective
+//
+function makePerspective(fovy, aspect, znear, zfar)
+{
+    var ymax = znear * Math.tan(fovy * Math.PI / 360.0);
+    var ymin = -ymax;
+    var xmin = ymin * aspect;
+    var xmax = ymax * aspect;
+
+    return makeFrustum(xmin, xmax, ymin, ymax, znear, zfar);
+}
+
+//
+// glFrustum
+//
+function makeFrustum(left, right,
+                     bottom, top,
+                     znear, zfar)
+{
+    var X = 2*znear/(right-left);
+    var Y = 2*znear/(top-bottom);
+    var A = (right+left)/(right-left);
+    var B = (top+bottom)/(top-bottom);
+    var C = -(zfar+znear)/(zfar-znear);
+    var D = -2*zfar*znear/(zfar-znear);
+
+    return $M([[X, 0, A, 0],
+               [0, Y, B, 0],
+               [0, 0, C, D],
+               [0, 0, -1, 0]]);
+}
+
+//
+// glOrtho
+//
+function makeOrtho(left, right, bottom, top, znear, zfar)
+{
+    var tx = - (right + left) / (right - left);
+    var ty = - (top + bottom) / (top - bottom);
+    var tz = - (zfar + znear) / (zfar - znear);
+
+    return $M([[2 / (right - left), 0, 0, tx],
+           [0, 2 / (top - bottom), 0, ty],
+           [0, 0, -2 / (zfar - znear), tz],
+           [0, 0, 0, 1]]);
+}
+
--- a/js/h264bsd.js
+++ b/js/h264bsd.js
@@ -23,7 +23,7 @@
 /*
  * This class wraps the details of the h264bsd library.
  */
-function H264Decoder(Module) {
+function H264Decoder(Module, targetCanvas) {
 	H264Decoder.Module = Module;
 	H264Decoder.released = false;
 
@@ -30,8 +30,10 @@
 	H264Decoder.pStorage = H264Decoder.h264bsdAlloc();
 
 	H264Decoder.h264bsdInit(H264Decoder.Module, H264Decoder.pStorage, 0);
+	H264Decoder.targetCanvas = targetCanvas;
 };
 
+
 H264Decoder.RDY = 0;
 H264Decoder.PIC_RDY = 1;
 H264Decoder.HDRS_RDY = 2;
@@ -42,6 +44,8 @@
 H264Decoder.Module = null;
 H264Decoder.released = false;
 H264Decoder.pStorage = null;
+H264Decoder.targetCanvas = null;
+H264Decoder.yuvCanvas = null;
 
 H264Decoder.prototype.release = function() {
 	if(this.released) return;
@@ -105,6 +109,10 @@
 
 };
 
+H264Decoder.clamp = function(num, max, min) {
+  return Math.min(Math.max(num, min), max);
+};
+
 H264Decoder.getNextOutputPicture = function(){
 	var length = H264Decoder.getYUVLength();
 
@@ -130,29 +138,133 @@
 	bytes = H264Decoder.Module.HEAPU8.subarray(pBytes, (pBytes + length));
 
 
-	if (picIdPtr != 0){
-        H264Decoder.free(picIdPtr);		
+	if (pPicId != 0){
+        H264Decoder.free(pPicId);		
 	}
                
-    if (isIdrPicPtr != 0){
-    	H264Decoder.free(isIdrPicPtr);
+    if (pIsIdrPic != 0){
+    	H264Decoder.free(pIsIdrPic);
     }
             
-    if (numErrMbsPtr != 0){
-        H264Decoder.free(numErrMbsPtr);	
+    if (pNumErrMbs != 0){
+        H264Decoder.free(pNumErrMbs);	
     }
 
     var croppingInfo = H264Decoder.getCroppingInfo();
-    var result = convertYUV2RGB(bytes, croppingInfo);
+    H264Decoder.drawWebGl(bytes, croppingInfo);
+    //var result = H264Decoder.convertYUV2RGB(bytes, croppingInfo);
     
-    H264Decoder.free(picIdPtr);		
-  	H264Decoder.free(isIdrPicPtr);
-    H264Decoder.free(numErrMbsPtr);	
+    H264Decoder.free(pPicId);		
+  	H264Decoder.free(pIsIdrPic);
+    H264Decoder.free(pNumErrMbs);	
 
-    return result;
+};
 
+H264Decoder.drawWebGl = function(yuvBytes, croppingInfo){
+	if (yuvBytes == null)
+		return;
+
+	var width = croppingInfo.width - croppingInfo.left;
+	var height = croppingInfo.height - croppingInfo.top;
+	H264Decoder.yuvCanvas = new YUVWebGLCanvas(H264Decoder.targetCanvas, new Size(width, height));
+	
+	var startTime = (new Date);	
+	var lumaSize = width * height;
+	var chromaSize = lumaSize >> 2;
+
+    H264Decoder.yuvCanvas.YTexture.fill(yuvBytes.subarray(0, lumaSize), true);
+    H264Decoder.yuvCanvas.UTexture.fill(yuvBytes.subarray(lumaSize, lumaSize + chromaSize), true);
+    H264Decoder.yuvCanvas.VTexture.fill(yuvBytes.subarray(lumaSize + chromaSize, lumaSize + 2 * chromaSize), true);
+    H264Decoder.yuvCanvas.drawScene();
+
+    console.log("WebGL YUV decode: " + ((new Date) - startTime));
+
+	// var thisFrame = (self.thisLoop = new Date) - self.lastLoop;
+ //  	self.frameTime += (thisFrame - self.frameTime) / self.filterStrength;
+ //  	self.lastLoop = self.thisLoop;
+ //  	self.stats['fps']  = (1000/self.frameTime).toFixed(1);
+ //  	if (self.onImageUpdated)
+ //  		self.onImageUpdated(self.stats);		
+ //  	self.frameCount = 0;
+
 };
 
+//Excessively pink
+H264Decoder.convertYUV2RGB = function(yuvBytes, croppingInfo){
+	var width = croppingInfo.width - croppingInfo.left;
+	var height = croppingInfo.height - croppingInfo.top;
+
+	var buffer = document.createElement('canvas');
+	buffer.height = height;
+	buffer.width = width; 
+	var context = buffer.getContext('2d');
+	var output = context.createImageData(width,height);
+	var rgbBytes = output.data;
+
+	var cb = width * height;
+	var cr = cb + ((width * height) / 2);	
+	var numPixels = width * height;
+
+	var dst = 0;
+	var dst_width = 0;
+
+	var k = 0;
+	for (var i = 0; i < numPixels; i += 2)
+	{
+		k += 1;
+
+		var y1 = yuvBytes[i] & 0xff;
+		var y2 = yuvBytes[i + 1] & 0xff;
+		var y3 = yuvBytes[width + i] & 0xff;
+		var y4 = yuvBytes[width + i + 1] & 0xff;
+		
+		var v = yuvBytes[cr + k] & 0xff;
+		var u = yuvBytes[cb + k] & 0xff;
+		
+		v = v - 128;
+		u = u - 128;
+
+		dst = i * 4;
+		dst_width = width*4 + dst;
+
+		// i
+		rgbBytes[dst] = 0xff;
+		rgbBytes[dst + 1] = H264Decoder.clamp((298 * (y1 - 16) + 409 * v + 128) >> 8, 255, 0);
+		rgbBytes[dst + 2] = H264Decoder.clamp((298 * (y1 - 16) - 100 * u - 208 *v + 128) >> 8, 255,0);
+		rgbBytes[dst + 3] = H264Decoder.clamp((298 * y1 + 516*u + 128) >> 8, 255, 0);
+				
+		// i + 1
+		rgbBytes[dst + 4] = 0xff;
+		rgbBytes[dst + 5] = H264Decoder.clamp((298 * (y2 - 16) + 409 * v + 128) >> 8, 255, 0);
+		rgbBytes[dst + 6] = H264Decoder.clamp((298 * (y2 - 16) - 100 * u - 208 *v + 128) >> 8, 255,0);
+		rgbBytes[dst + 7] = H264Decoder.clamp((298 * y2 + 516*u + 128) >> 8, 255, 0);
+		
+		//width
+		rgbBytes[dst_width] = 0xff;
+		rgbBytes[dst_width + 1] = H264Decoder.clamp((298 * (y3 - 16) + 409 * v + 128) >> 8, 255, 0);
+		rgbBytes[dst_width + 2] = H264Decoder.clamp((298 * (y2 - 16) - 100 * u - 208 *v + 128) >> 8, 255,0);
+		rgbBytes[dst_width + 3] = H264Decoder.clamp((298 * y3 + 516*u + 128) >> 8, 255, 0);
+				
+		//width + 1
+		rgbBytes[dst_width + 4] = 0xff;
+		rgbBytes[dst_width + 5] = H264Decoder.clamp((298 * (y4 - 16) + 409 * v + 128) >> 8, 255, 0);
+		rgbBytes[dst_width + 6] = H264Decoder.clamp((298 * (y4 - 16) - 100 * u - 208 *v + 128) >> 8, 255,0);
+		rgbBytes[dst_width + 7] = H264Decoder.clamp((298 * y4 + 516*u + 128) >> 8, 255, 0);
+
+		if (i != 0 && (i+2)%width ==0) {
+			i += width;					
+		} 
+	}
+
+	var c = document.getElementById('canvas');
+	c.height = height;
+	c.width = width;
+	c.style.height = height;
+	c.style.width = width;
+	var outputContext = c.getContext('2d'); 
+	outputContext.putImageData(output,0,0);
+};
+
 H264Decoder.getCroppingInfo = function(){
 	var pCroppingFlag = H264Decoder.malloc(H264Decoder.Module, 4);
 	var pLeftOffset = H264Decoder.malloc(H264Decoder.Module, 4);
@@ -160,13 +272,11 @@
 	var pWidth = H264Decoder.malloc(H264Decoder.Module, 4);
 	var pHeight = H264Decoder.malloc(H264Decoder.Module, 4);
 
-	H264Decoder.h264bsdNextOutputPicture(H264Decoder.Module, H264Decoder.pStorage, pCroppingFlag, pLeftOffset, pWidth, pTopOffset, pHeight);
-
 	var result = {
-		'width': H264Decoder.Module.getValue(pWidth, 'i32');	
-		'height': picId = H264Decoder.Module.getValue(pHeight, 'i32');	
-		'topOffset' H264Decoder.Module.getValue(pTopOffset, 'i32');	
-		'leftOffset': H264Decoder.Module.getValue(pLeftOffset, 'i32');	
+		'width': (H264Decoder.h264bsdPicWidth(H264Decoder.Module, H264Decoder.pStorage)*16),
+		'height': (H264Decoder.h264bsdPicHeight(H264Decoder.Module, H264Decoder.pStorage)*16),
+		'top': 0,
+		'left': 0
 	};
 
 	H264Decoder.free(pCroppingFlag);
@@ -181,9 +291,7 @@
 H264Decoder.getYUVLength = function(){
 	var width = H264Decoder.h264bsdPicWidth(H264Decoder.Module, H264Decoder.pStorage);
 	var height = H264Decoder.h264bsdPicHeight(H264Decoder.Module, H264Decoder.pStorage);
-    // Y is w *h * 16
-    // U,V are w *h * 8            
-    return (width * height * 16) + 2*(width * height * 8);  
+    return (width * 16 * height * 16) + (2 * width * 16 * height * 8);
 };
 
 // u32 h264bsdDecode(storage_t *pStorage, u8 *byteStrm, u32 len, u32 picId, u32 *readBytes);
--- /dev/null
+++ b/js/sylvester.js
@@ -1,0 +1,1 @@
+eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('9 17={3i:\'0.1.3\',16:1e-6};l v(){}v.23={e:l(i){8(i<1||i>7.4.q)?w:7.4[i-1]},2R:l(){8 7.4.q},1u:l(){8 F.1x(7.2u(7))},24:l(a){9 n=7.4.q;9 V=a.4||a;o(n!=V.q){8 1L}J{o(F.13(7.4[n-1]-V[n-1])>17.16){8 1L}}H(--n);8 2x},1q:l(){8 v.u(7.4)},1b:l(a){9 b=[];7.28(l(x,i){b.19(a(x,i))});8 v.u(b)},28:l(a){9 n=7.4.q,k=n,i;J{i=k-n;a(7.4[i],i+1)}H(--n)},2q:l(){9 r=7.1u();o(r===0){8 7.1q()}8 7.1b(l(x){8 x/r})},1C:l(a){9 V=a.4||a;9 n=7.4.q,k=n,i;o(n!=V.q){8 w}9 b=0,1D=0,1F=0;7.28(l(x,i){b+=x*V[i-1];1D+=x*x;1F+=V[i-1]*V[i-1]});1D=F.1x(1D);1F=F.1x(1F);o(1D*1F===0){8 w}9 c=b/(1D*1F);o(c<-1){c=-1}o(c>1){c=1}8 F.37(c)},1m:l(a){9 b=7.1C(a);8(b===w)?w:(b<=17.16)},34:l(a){9 b=7.1C(a);8(b===w)?w:(F.13(b-F.1A)<=17.16)},2k:l(a){9 b=7.2u(a);8(b===w)?w:(F.13(b)<=17.16)},2j:l(a){9 V=a.4||a;o(7.4.q!=V.q){8 w}8 7.1b(l(x,i){8 x+V[i-1]})},2C:l(a){9 V=a.4||a;o(7.4.q!=V.q){8 w}8 7.1b(l(x,i){8 x-V[i-1]})},22:l(k){8 7.1b(l(x){8 x*k})},x:l(k){8 7.22(k)},2u:l(a){9 V=a.4||a;9 i,2g=0,n=7.4.q;o(n!=V.q){8 w}J{2g+=7.4[n-1]*V[n-1]}H(--n);8 2g},2f:l(a){9 B=a.4||a;o(7.4.q!=3||B.q!=3){8 w}9 A=7.4;8 v.u([(A[1]*B[2])-(A[2]*B[1]),(A[2]*B[0])-(A[0]*B[2]),(A[0]*B[1])-(A[1]*B[0])])},2A:l(){9 m=0,n=7.4.q,k=n,i;J{i=k-n;o(F.13(7.4[i])>F.13(m)){m=7.4[i]}}H(--n);8 m},2Z:l(x){9 a=w,n=7.4.q,k=n,i;J{i=k-n;o(a===w&&7.4[i]==x){a=i+1}}H(--n);8 a},3g:l(){8 S.2X(7.4)},2d:l(){8 7.1b(l(x){8 F.2d(x)})},2V:l(x){8 7.1b(l(y){8(F.13(y-x)<=17.16)?x:y})},1o:l(a){o(a.K){8 a.1o(7)}9 V=a.4||a;o(V.q!=7.4.q){8 w}9 b=0,2b;7.28(l(x,i){2b=x-V[i-1];b+=2b*2b});8 F.1x(b)},3a:l(a){8 a.1h(7)},2T:l(a){8 a.1h(7)},1V:l(t,a){9 V,R,x,y,z;2S(7.4.q){27 2:V=a.4||a;o(V.q!=2){8 w}R=S.1R(t).4;x=7.4[0]-V[0];y=7.4[1]-V[1];8 v.u([V[0]+R[0][0]*x+R[0][1]*y,V[1]+R[1][0]*x+R[1][1]*y]);1I;27 3:o(!a.U){8 w}9 C=a.1r(7).4;R=S.1R(t,a.U).4;x=7.4[0]-C[0];y=7.4[1]-C[1];z=7.4[2]-C[2];8 v.u([C[0]+R[0][0]*x+R[0][1]*y+R[0][2]*z,C[1]+R[1][0]*x+R[1][1]*y+R[1][2]*z,C[2]+R[2][0]*x+R[2][1]*y+R[2][2]*z]);1I;2P:8 w}},1t:l(a){o(a.K){9 P=7.4.2O();9 C=a.1r(P).4;8 v.u([C[0]+(C[0]-P[0]),C[1]+(C[1]-P[1]),C[2]+(C[2]-(P[2]||0))])}1d{9 Q=a.4||a;o(7.4.q!=Q.q){8 w}8 7.1b(l(x,i){8 Q[i-1]+(Q[i-1]-x)})}},1N:l(){9 V=7.1q();2S(V.4.q){27 3:1I;27 2:V.4.19(0);1I;2P:8 w}8 V},2n:l(){8\'[\'+7.4.2K(\', \')+\']\'},26:l(a){7.4=(a.4||a).2O();8 7}};v.u=l(a){9 V=25 v();8 V.26(a)};v.i=v.u([1,0,0]);v.j=v.u([0,1,0]);v.k=v.u([0,0,1]);v.2J=l(n){9 a=[];J{a.19(F.2F())}H(--n);8 v.u(a)};v.1j=l(n){9 a=[];J{a.19(0)}H(--n);8 v.u(a)};l S(){}S.23={e:l(i,j){o(i<1||i>7.4.q||j<1||j>7.4[0].q){8 w}8 7.4[i-1][j-1]},33:l(i){o(i>7.4.q){8 w}8 v.u(7.4[i-1])},2E:l(j){o(j>7.4[0].q){8 w}9 a=[],n=7.4.q,k=n,i;J{i=k-n;a.19(7.4[i][j-1])}H(--n);8 v.u(a)},2R:l(){8{2D:7.4.q,1p:7.4[0].q}},2D:l(){8 7.4.q},1p:l(){8 7.4[0].q},24:l(a){9 M=a.4||a;o(1g(M[0][0])==\'1f\'){M=S.u(M).4}o(7.4.q!=M.q||7.4[0].q!=M[0].q){8 1L}9 b=7.4.q,15=b,i,G,10=7.4[0].q,j;J{i=15-b;G=10;J{j=10-G;o(F.13(7.4[i][j]-M[i][j])>17.16){8 1L}}H(--G)}H(--b);8 2x},1q:l(){8 S.u(7.4)},1b:l(a){9 b=[],12=7.4.q,15=12,i,G,10=7.4[0].q,j;J{i=15-12;G=10;b[i]=[];J{j=10-G;b[i][j]=a(7.4[i][j],i+1,j+1)}H(--G)}H(--12);8 S.u(b)},2i:l(a){9 M=a.4||a;o(1g(M[0][0])==\'1f\'){M=S.u(M).4}8(7.4.q==M.q&&7.4[0].q==M[0].q)},2j:l(a){9 M=a.4||a;o(1g(M[0][0])==\'1f\'){M=S.u(M).4}o(!7.2i(M)){8 w}8 7.1b(l(x,i,j){8 x+M[i-1][j-1]})},2C:l(a){9 M=a.4||a;o(1g(M[0][0])==\'1f\'){M=S.u(M).4}o(!7.2i(M)){8 w}8 7.1b(l(x,i,j){8 x-M[i-1][j-1]})},2B:l(a){9 M=a.4||a;o(1g(M[0][0])==\'1f\'){M=S.u(M).4}8(7.4[0].q==M.q)},22:l(a){o(!a.4){8 7.1b(l(x){8 x*a})}9 b=a.1u?2x:1L;9 M=a.4||a;o(1g(M[0][0])==\'1f\'){M=S.u(M).4}o(!7.2B(M)){8 w}9 d=7.4.q,15=d,i,G,10=M[0].q,j;9 e=7.4[0].q,4=[],21,20,c;J{i=15-d;4[i]=[];G=10;J{j=10-G;21=0;20=e;J{c=e-20;21+=7.4[i][c]*M[c][j]}H(--20);4[i][j]=21}H(--G)}H(--d);9 M=S.u(4);8 b?M.2E(1):M},x:l(a){8 7.22(a)},32:l(a,b,c,d){9 e=[],12=c,i,G,j;9 f=7.4.q,1p=7.4
\ No newline at end of file
--- /dev/null
+++ b/js/util.js
@@ -1,0 +1,71 @@
+'use strict';
+
+function error(message) {
+  console.error(message);
+  console.trace();
+}
+
+function assert(condition, message) {
+  if (!condition) {
+    error(message);
+  }
+}
+
+function isPowerOfTwo(x) {
+  return (x & (x - 1)) == 0;
+}
+
+/**
+ * Joins a list of lines using a newline separator, not the fastest
+ * thing in the world but good enough for initialization code. 
+ */
+function text(lines) {
+  return lines.join("\n");
+}
+
+/**
+ * Rounds up to the next highest power of two.
+ */
+function nextHighestPowerOfTwo(x) {
+  --x;
+  for (var i = 1; i < 32; i <<= 1) {
+      x = x | x >> i;
+  }
+  return x + 1;
+}
+
+/**
+ * Represents a 2-dimensional size value. 
+ */
+var Size = (function size() {
+  function constructor(w, h) {
+    this.w = w;
+    this.h = h;
+  }
+  constructor.prototype = {
+    toString: function () {
+      return "(" + this.w + ", " + this.h + ")";
+    },
+    getHalfSize: function() {
+      return new Size(this.w >>> 1, this.h >>> 1);
+    },
+    length: function() {
+      return this.w * this.h;
+    }
+  }
+  return constructor;
+})();
+
+/**
+ * Creates a new prototype object derived from another objects prototype along with a list of additional properties.
+ *
+ * @param base object whose prototype to use as the created prototype object's prototype
+ * @param properties additional properties to add to the created prototype object
+ */
+function inherit(base, properties) {
+  var prot = Object.create(base.prototype);
+  for (var p in properties) {
+    prot[p] = properties[p];
+  }
+  return prot;
+}
\ No newline at end of file
--- a/test/h264bsd.html
+++ b/test/h264bsd.html
@@ -5,13 +5,18 @@
     <title>h.264bsd test</title>
 </head>
 <body>
-    <input type="file" id="file" name="file" />
-    <canvas id="canvas" style="width:640px;height:480px;border:solid;"></canvas>
+    <input type="file" id="file" name="file" /><br/>
+    <canvas id="canvas" width="640" height="480" style="border:solid;"></canvas>
     <h264decoder id="h264decoder"><h264decoder>
     <script src="../js/h264bsd_asm.js"></script>
+    <script src="../js/sylvester.js"></script>
+    <script src="../js/glUtils.js"></script>
+    <script src="../js/util.js"></script>
+    <script src="../js/canvas.js"></script>    
     <script src="../js/h264bsd.js"></script>
     <script type="text/javascript">
-    	var d = new H264Decoder(Module);
+    	var canvas = document.getElementById('canvas');
+    	var d = new H264Decoder(Module, canvas);
 
     	var f = document.getElementById('h264decoder');
     	console.log(f);