tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

util.js (33992B)


      1 /*
      2 Utilities for the OpenGL ES 2.0 HTML Canvas context
      3 */
      4 
      5 /*
      6 Copyright (c) 2019 The Khronos Group Inc.
      7 Use of this source code is governed by an MIT-style license that can be
      8 found in the LICENSE.txt file.
      9 */
     10 
     11 function loadTexture(gl, elem, mipmaps) {
     12  var tex = gl.createTexture();
     13  gl.bindTexture(gl.TEXTURE_2D, tex);
     14  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, elem);
     15  if (mipmaps != false)
     16    gl.generateMipmap(gl.TEXTURE_2D);
     17  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
     18  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
     19  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
     20  if (mipmaps)
     21    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
     22  else
     23    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
     24  return tex;
     25 }
     26 
     27 function getShader(gl, id) {
     28  var shaderScript = document.getElementById(id);
     29  if (!shaderScript) {
     30    throw(new Error("No shader element with id: "+id));
     31  }
     32 
     33  var str = "";
     34  var k = shaderScript.firstChild;
     35  while (k) {
     36    if (k.nodeType == 3)
     37      str += k.textContent;
     38    k = k.nextSibling;
     39  }
     40 
     41  var shader;
     42  if (shaderScript.type == "x-shader/x-fragment") {
     43    shader = gl.createShader(gl.FRAGMENT_SHADER);
     44  } else if (shaderScript.type == "x-shader/x-vertex") {
     45    shader = gl.createShader(gl.VERTEX_SHADER);
     46  } else {
     47    throw(new Error("Unknown shader type "+shaderScript.type));
     48  }
     49 
     50  gl.shaderSource(shader, str);
     51  gl.compileShader(shader);
     52 
     53  if (gl.getShaderParameter(shader, gl.COMPILE_STATUS) != 1) {
     54    var ilog = gl.getShaderInfoLog(shader);
     55    gl.deleteShader(shader);
     56    throw(new Error("Failed to compile shader "+shaderScript.id + ", Shader info log: " + ilog));
     57  }
     58  return shader;
     59 }
     60 
     61 function loadShaderArray(gl, shaders) {
     62  var id = gl.createProgram();
     63  var shaderObjs = [];
     64  for (var i=0; i<shaders.length; ++i) {
     65    try {
     66      var sh = getShader(gl, shaders[i]);
     67      shaderObjs.push(sh);
     68      gl.attachShader(id, sh);
     69    } catch (e) {
     70      var pr = {program: id, shaders: shaderObjs};
     71      deleteShader(gl, pr);
     72      throw (e);
     73    }
     74  }
     75  var prog = {program: id, shaders: shaderObjs};
     76  gl.linkProgram(id);
     77  gl.validateProgram(id);
     78  if (gl.getProgramParameter(id, gl.LINK_STATUS) != 1) {
     79    deleteShader(gl,prog);
     80    throw(new Error("Failed to link shader"));
     81  }
     82  if (gl.getProgramParameter(id, gl.VALIDATE_STATUS) != 1) {
     83    deleteShader(gl,prog);
     84    throw(new Error("Failed to validate shader"));
     85  }
     86  return prog;
     87 }
     88 function loadShader(gl) {
     89  var sh = [];
     90  for (var i=1; i<arguments.length; ++i)
     91    sh.push(arguments[i]);
     92  return loadShaderArray(gl, sh);
     93 }
     94 
     95 function deleteShader(gl, sh) {
     96  gl.useProgram(null);
     97  sh.shaders.forEach(function(s){
     98    gl.detachShader(sh.program, s);
     99    gl.deleteShader(s);
    100  });
    101  gl.deleteProgram(sh.program);
    102 }
    103 
    104 function getGLErrorAsString(ctx, err) {
    105  if (err === ctx.NO_ERROR) {
    106    return "NO_ERROR";
    107  }
    108  for (var name in ctx) {
    109    if (ctx[name] === err) {
    110      return name;
    111    }
    112  }
    113  return err.toString();
    114 }
    115 
    116 function checkError(gl, msg) {
    117  var e = gl.getError();
    118  if (e != gl.NO_ERROR) {
    119    log("Error " + getGLErrorAsString(gl, e) + " at " + msg);
    120  }
    121  return e;
    122 }
    123 
    124 function throwError(gl, msg) {
    125  var e = gl.getError();
    126  if (e != 0) {
    127    throw(new Error("Error " + getGLErrorAsString(gl, e) + " at " + msg));
    128  }
    129 }
    130 
    131 Math.cot = function(z) { return 1.0 / Math.tan(z); }
    132 
    133 /*
    134  Matrix utilities, using the OpenGL element order where
    135  the last 4 elements are the translation column.
    136 
    137  Uses flat arrays as matrices for performance.
    138 
    139  Most operations have in-place variants to avoid allocating temporary matrices.
    140 
    141  Naming logic:
    142    Matrix.method operates on a 4x4 Matrix and returns a new Matrix.
    143    Matrix.method3x3 operates on a 3x3 Matrix and returns a new Matrix. Not all operations have a 3x3 version (as 3x3 is usually only used for the normal matrix: Matrix.transpose3x3(Matrix.inverseTo3x3(mat4x4)))
    144    Matrix.method[3x3]InPlace(args, target) stores its result in the target matrix.
    145 
    146    Matrix.scale([sx, sy, sz]) -- non-uniform scale by vector
    147    Matrix.scale1(s)           -- uniform scale by scalar
    148    Matrix.scale3(sx, sy, sz)  -- non-uniform scale by scalars
    149 
    150    Ditto for translate.
    151 */
    152 Matrix = {
    153  identity : [
    154    1.0, 0.0, 0.0, 0.0,
    155    0.0, 1.0, 0.0, 0.0,
    156    0.0, 0.0, 1.0, 0.0,
    157    0.0, 0.0, 0.0, 1.0
    158  ],
    159 
    160  newIdentity : function() {
    161    return [
    162      1.0, 0.0, 0.0, 0.0,
    163      0.0, 1.0, 0.0, 0.0,
    164      0.0, 0.0, 1.0, 0.0,
    165      0.0, 0.0, 0.0, 1.0
    166    ];
    167  },
    168 
    169  newIdentity3x3 : function() {
    170    return [
    171      1.0, 0.0, 0.0,
    172      0.0, 1.0, 0.0,
    173      0.0, 0.0, 1.0
    174    ];
    175  },
    176 
    177  copyMatrix : function(src, dst) {
    178    for (var i=0; i<16; i++) dst[i] = src[i];
    179    return dst;
    180  },
    181 
    182  to3x3 : function(m) {
    183    return [
    184      m[0], m[1], m[2],
    185      m[4], m[5], m[6],
    186      m[8], m[9], m[10]
    187    ];
    188  },
    189 
    190  // orthonormal matrix inverse
    191  inverseON : function(m) {
    192    var n = this.transpose4x4(m);
    193    var t = [m[12], m[13], m[14]];
    194    n[3] = n[7] = n[11] = 0;
    195    n[12] = -Vec3.dot([n[0], n[4], n[8]], t);
    196    n[13] = -Vec3.dot([n[1], n[5], n[9]], t);
    197    n[14] = -Vec3.dot([n[2], n[6], n[10]], t);
    198    return n;
    199  },
    200 
    201  inverseTo3x3 : function(m) {
    202    return this.inverse4x4to3x3InPlace(m, this.newIdentity3x3());
    203  },
    204 
    205  inverseTo3x3InPlace : function(m,n) {
    206    var a11 = m[10]*m[5]-m[6]*m[9],
    207        a21 = -m[10]*m[1]+m[2]*m[9],
    208        a31 = m[6]*m[1]-m[2]*m[5],
    209        a12 = -m[10]*m[4]+m[6]*m[8],
    210        a22 = m[10]*m[0]-m[2]*m[8],
    211        a32 = -m[6]*m[0]+m[2]*m[4],
    212        a13 = m[9]*m[4]-m[5]*m[8],
    213        a23 = -m[9]*m[0]+m[1]*m[8],
    214        a33 = m[5]*m[0]-m[1]*m[4];
    215    var det = m[0]*(a11) + m[1]*(a12) + m[2]*(a13);
    216    if (det == 0) // no inverse
    217      return [1,0,0,0,1,0,0,0,1];
    218    var idet = 1 / det;
    219    n[0] = idet*a11;
    220    n[1] = idet*a21;
    221    n[2] = idet*a31;
    222    n[3] = idet*a12;
    223    n[4] = idet*a22;
    224    n[5] = idet*a32;
    225    n[6] = idet*a13;
    226    n[7] = idet*a23;
    227    n[8] = idet*a33;
    228    return n;
    229  },
    230 
    231  inverse3x3 : function(m) {
    232    return this.inverse3x3InPlace(m, this.newIdentity3x3());
    233  },
    234 
    235  inverse3x3InPlace : function(m,n) {
    236    var a11 = m[8]*m[4]-m[5]*m[7],
    237        a21 = -m[8]*m[1]+m[2]*m[7],
    238        a31 = m[5]*m[1]-m[2]*m[4],
    239        a12 = -m[8]*m[3]+m[5]*m[6],
    240        a22 = m[8]*m[0]-m[2]*m[6],
    241        a32 = -m[5]*m[0]+m[2]*m[3],
    242        a13 = m[7]*m[4]-m[4]*m[8],
    243        a23 = -m[7]*m[0]+m[1]*m[6],
    244        a33 = m[4]*m[0]-m[1]*m[3];
    245    var det = m[0]*(a11) + m[1]*(a12) + m[2]*(a13);
    246    if (det == 0) // no inverse
    247      return [1,0,0,0,1,0,0,0,1];
    248    var idet = 1 / det;
    249    n[0] = idet*a11;
    250    n[1] = idet*a21;
    251    n[2] = idet*a31;
    252    n[3] = idet*a12;
    253    n[4] = idet*a22;
    254    n[5] = idet*a32;
    255    n[6] = idet*a13;
    256    n[7] = idet*a23;
    257    n[8] = idet*a33;
    258    return n;
    259  },
    260 
    261  frustum : function (left, right, bottom, top, znear, zfar) {
    262    var X = 2*znear/(right-left);
    263    var Y = 2*znear/(top-bottom);
    264    var A = (right+left)/(right-left);
    265    var B = (top+bottom)/(top-bottom);
    266    var C = -(zfar+znear)/(zfar-znear);
    267    var D = -2*zfar*znear/(zfar-znear);
    268 
    269    return [
    270      X, 0, 0, 0,
    271      0, Y, 0, 0,
    272      A, B, C, -1,
    273      0, 0, D, 0
    274    ];
    275 },
    276 
    277  perspective : function (fovy, aspect, znear, zfar) {
    278    var ymax = znear * Math.tan(fovy * Math.PI / 360.0);
    279    var ymin = -ymax;
    280    var xmin = ymin * aspect;
    281    var xmax = ymax * aspect;
    282 
    283    return this.frustum(xmin, xmax, ymin, ymax, znear, zfar);
    284  },
    285 
    286  mul4x4 : function (a,b) {
    287    return this.mul4x4InPlace(a,b,this.newIdentity());
    288  },
    289 
    290  mul4x4InPlace : function (a, b, c) {
    291        c[0] =   b[0] * a[0] +
    292                 b[0+1] * a[4] +
    293                 b[0+2] * a[8] +
    294                 b[0+3] * a[12];
    295        c[0+1] = b[0] * a[1] +
    296                 b[0+1] * a[5] +
    297                 b[0+2] * a[9] +
    298                 b[0+3] * a[13];
    299        c[0+2] = b[0] * a[2] +
    300                 b[0+1] * a[6] +
    301                 b[0+2] * a[10] +
    302                 b[0+3] * a[14];
    303        c[0+3] = b[0] * a[3] +
    304                 b[0+1] * a[7] +
    305                 b[0+2] * a[11] +
    306                 b[0+3] * a[15];
    307        c[4] =   b[4] * a[0] +
    308                 b[4+1] * a[4] +
    309                 b[4+2] * a[8] +
    310                 b[4+3] * a[12];
    311        c[4+1] = b[4] * a[1] +
    312                 b[4+1] * a[5] +
    313                 b[4+2] * a[9] +
    314                 b[4+3] * a[13];
    315        c[4+2] = b[4] * a[2] +
    316                 b[4+1] * a[6] +
    317                 b[4+2] * a[10] +
    318                 b[4+3] * a[14];
    319        c[4+3] = b[4] * a[3] +
    320                 b[4+1] * a[7] +
    321                 b[4+2] * a[11] +
    322                 b[4+3] * a[15];
    323        c[8] =   b[8] * a[0] +
    324                 b[8+1] * a[4] +
    325                 b[8+2] * a[8] +
    326                 b[8+3] * a[12];
    327        c[8+1] = b[8] * a[1] +
    328                 b[8+1] * a[5] +
    329                 b[8+2] * a[9] +
    330                 b[8+3] * a[13];
    331        c[8+2] = b[8] * a[2] +
    332                 b[8+1] * a[6] +
    333                 b[8+2] * a[10] +
    334                 b[8+3] * a[14];
    335        c[8+3] = b[8] * a[3] +
    336                 b[8+1] * a[7] +
    337                 b[8+2] * a[11] +
    338                 b[8+3] * a[15];
    339        c[12] =   b[12] * a[0] +
    340                 b[12+1] * a[4] +
    341                 b[12+2] * a[8] +
    342                 b[12+3] * a[12];
    343        c[12+1] = b[12] * a[1] +
    344                 b[12+1] * a[5] +
    345                 b[12+2] * a[9] +
    346                 b[12+3] * a[13];
    347        c[12+2] = b[12] * a[2] +
    348                 b[12+1] * a[6] +
    349                 b[12+2] * a[10] +
    350                 b[12+3] * a[14];
    351        c[12+3] = b[12] * a[3] +
    352                 b[12+1] * a[7] +
    353                 b[12+2] * a[11] +
    354                 b[12+3] * a[15];
    355    return c;
    356  },
    357 
    358  mulv4 : function (a, v) {
    359    c = new Array(4);
    360    for (var i=0; i<4; ++i) {
    361      var x = 0;
    362      for (var k=0; k<4; ++k)
    363        x += v[k] * a[k*4+i];
    364      c[i] = x;
    365    }
    366    return c;
    367  },
    368 
    369  rotate : function (angle, axis) {
    370    axis = Vec3.normalize(axis);
    371    var x=axis[0], y=axis[1], z=axis[2];
    372    var c = Math.cos(angle);
    373    var c1 = 1-c;
    374    var s = Math.sin(angle);
    375    return [
    376      x*x*c1+c, y*x*c1+z*s, z*x*c1-y*s, 0,
    377      x*y*c1-z*s, y*y*c1+c, y*z*c1+x*s, 0,
    378      x*z*c1+y*s, y*z*c1-x*s, z*z*c1+c, 0,
    379      0,0,0,1
    380    ];
    381  },
    382  rotateInPlace : function(angle, axis, m) {
    383    axis = Vec3.normalize(axis);
    384    var x=axis[0], y=axis[1], z=axis[2];
    385    var c = Math.cos(angle);
    386    var c1 = 1-c;
    387    var s = Math.sin(angle);
    388    var tmpMatrix = this.tmpMatrix;
    389    var tmpMatrix2 = this.tmpMatrix2;
    390    tmpMatrix[0] = x*x*c1+c; tmpMatrix[1] = y*x*c1+z*s; tmpMatrix[2] = z*x*c1-y*s; tmpMatrix[3] = 0;
    391    tmpMatrix[4] = x*y*c1-z*s; tmpMatrix[5] = y*y*c1+c; tmpMatrix[6] = y*z*c1+x*s; tmpMatrix[7] = 0;
    392    tmpMatrix[8] = x*z*c1+y*s; tmpMatrix[9] = y*z*c1-x*s; tmpMatrix[10] = z*z*c1+c; tmpMatrix[11] = 0;
    393    tmpMatrix[12] = 0; tmpMatrix[13] = 0; tmpMatrix[14] = 0; tmpMatrix[15] = 1;
    394    this.copyMatrix(m, tmpMatrix2);
    395    return this.mul4x4InPlace(tmpMatrix2, tmpMatrix, m);
    396  },
    397 
    398  scale : function(v) {
    399    return [
    400      v[0], 0, 0, 0,
    401      0, v[1], 0, 0,
    402      0, 0, v[2], 0,
    403      0, 0, 0, 1
    404    ];
    405  },
    406  scale3 : function(x,y,z) {
    407    return [
    408      x, 0, 0, 0,
    409      0, y, 0, 0,
    410      0, 0, z, 0,
    411      0, 0, 0, 1
    412    ];
    413  },
    414  scale1 : function(s) {
    415    return [
    416      s, 0, 0, 0,
    417      0, s, 0, 0,
    418      0, 0, s, 0,
    419      0, 0, 0, 1
    420    ];
    421  },
    422  scale3InPlace : function(x, y, z, m) {
    423    var tmpMatrix = this.tmpMatrix;
    424    var tmpMatrix2 = this.tmpMatrix2;
    425    tmpMatrix[0] = x; tmpMatrix[1] = 0; tmpMatrix[2] = 0; tmpMatrix[3] = 0;
    426    tmpMatrix[4] = 0; tmpMatrix[5] = y; tmpMatrix[6] = 0; tmpMatrix[7] = 0;
    427    tmpMatrix[8] = 0; tmpMatrix[9] = 0; tmpMatrix[10] = z; tmpMatrix[11] = 0;
    428    tmpMatrix[12] = 0; tmpMatrix[13] = 0; tmpMatrix[14] = 0; tmpMatrix[15] = 1;
    429    this.copyMatrix(m, tmpMatrix2);
    430    return this.mul4x4InPlace(tmpMatrix2, tmpMatrix, m);
    431  },
    432  scale1InPlace : function(s, m) { return this.scale3InPlace(s, s, s, m); },
    433  scaleInPlace : function(s, m) { return this.scale3InPlace(s[0],s[1],s[2],m); },
    434 
    435  translate3 : function(x,y,z) {
    436    return [
    437      1, 0, 0, 0,
    438      0, 1, 0, 0,
    439      0, 0, 1, 0,
    440      x, y, z, 1
    441    ];
    442  },
    443 
    444  translate : function(v) {
    445    return this.translate3(v[0], v[1], v[2]);
    446  },
    447  tmpMatrix : [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0],
    448  tmpMatrix2 : [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0],
    449  translate3InPlace : function(x,y,z,m) {
    450    var tmpMatrix = this.tmpMatrix;
    451    var tmpMatrix2 = this.tmpMatrix2;
    452    tmpMatrix[0] = 1; tmpMatrix[1] = 0; tmpMatrix[2] = 0; tmpMatrix[3] = 0;
    453    tmpMatrix[4] = 0; tmpMatrix[5] = 1; tmpMatrix[6] = 0; tmpMatrix[7] = 0;
    454    tmpMatrix[8] = 0; tmpMatrix[9] = 0; tmpMatrix[10] = 1; tmpMatrix[11] = 0;
    455    tmpMatrix[12] = x; tmpMatrix[13] = y; tmpMatrix[14] = z; tmpMatrix[15] = 1;
    456    this.copyMatrix(m, tmpMatrix2);
    457    return this.mul4x4InPlace(tmpMatrix2, tmpMatrix, m);
    458  },
    459  translateInPlace : function(v,m){ return this.translate3InPlace(v[0], v[1], v[2], m); },
    460 
    461  lookAt : function (eye, center, up) {
    462    var z = Vec3.direction(eye, center);
    463    var x = Vec3.normalizeInPlace(Vec3.cross(up, z));
    464    var y = Vec3.normalizeInPlace(Vec3.cross(z, x));
    465 
    466    var m = [
    467      x[0], y[0], z[0], 0,
    468      x[1], y[1], z[1], 0,
    469      x[2], y[2], z[2], 0,
    470      0, 0, 0, 1
    471    ];
    472 
    473    var t = [
    474      1, 0, 0, 0,
    475      0, 1, 0, 0,
    476      0, 0, 1, 0,
    477      -eye[0], -eye[1], -eye[2], 1
    478    ];
    479 
    480    return this.mul4x4(m,t);
    481  },
    482 
    483  transpose4x4 : function(m) {
    484    return [
    485      m[0], m[4], m[8], m[12],
    486      m[1], m[5], m[9], m[13],
    487      m[2], m[6], m[10], m[14],
    488      m[3], m[7], m[11], m[15]
    489    ];
    490  },
    491 
    492  transpose4x4InPlace : function(m) {
    493    var tmp = 0.0;
    494    tmp = m[1]; m[1] = m[4]; m[4] = tmp;
    495    tmp = m[2]; m[2] = m[8]; m[8] = tmp;
    496    tmp = m[3]; m[3] = m[12]; m[12] = tmp;
    497    tmp = m[6]; m[6] = m[9]; m[9] = tmp;
    498    tmp = m[7]; m[7] = m[13]; m[13] = tmp;
    499    tmp = m[11]; m[11] = m[14]; m[14] = tmp;
    500    return m;
    501  },
    502 
    503  transpose3x3 : function(m) {
    504    return [
    505      m[0], m[3], m[6],
    506      m[1], m[4], m[7],
    507      m[2], m[5], m[8]
    508    ];
    509  },
    510 
    511  transpose3x3InPlace : function(m) {
    512    var tmp = 0.0;
    513    tmp = m[1]; m[1] = m[3]; m[3] = tmp;
    514    tmp = m[2]; m[2] = m[6]; m[6] = tmp;
    515    tmp = m[5]; m[5] = m[7]; m[7] = tmp;
    516    return m;
    517  },
    518 }
    519 
    520 Vec3 = {
    521  make : function() { return [0,0,0]; },
    522  copy : function(v) { return [v[0],v[1],v[2]]; },
    523 
    524  add : function (u,v) {
    525    return [u[0]+v[0], u[1]+v[1], u[2]+v[2]];
    526  },
    527 
    528  sub : function (u,v) {
    529    return [u[0]-v[0], u[1]-v[1], u[2]-v[2]];
    530  },
    531 
    532  negate : function (u) {
    533    return [-u[0], -u[1], -u[2]];
    534  },
    535 
    536  direction : function (u,v) {
    537    return this.normalizeInPlace(this.sub(u,v));
    538  },
    539 
    540  normalizeInPlace : function(v) {
    541    var imag = 1.0 / Math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
    542    v[0] *= imag; v[1] *= imag; v[2] *= imag;
    543    return v;
    544  },
    545 
    546  normalize : function(v) {
    547    return this.normalizeInPlace(this.copy(v));
    548  },
    549 
    550  scale : function(f, v) {
    551    return [f*v[0], f*v[1], f*v[2]];
    552  },
    553 
    554  dot : function(u,v) {
    555    return u[0]*v[0] + u[1]*v[1] + u[2]*v[2];
    556  },
    557 
    558  inner : function(u,v) {
    559    return [u[0]*v[0], u[1]*v[1], u[2]*v[2]];
    560  },
    561 
    562  cross : function(u,v) {
    563    return [
    564      u[1]*v[2] - u[2]*v[1],
    565      u[2]*v[0] - u[0]*v[2],
    566      u[0]*v[1] - u[1]*v[0]
    567    ];
    568  }
    569 }
    570 
    571 Shader = function(gl){
    572  this.gl = gl;
    573  this.shaders = [];
    574  this.uniformLocations = {};
    575  this.attribLocations = {};
    576  for (var i=1; i<arguments.length; i++) {
    577    this.shaders.push(arguments[i]);
    578  }
    579 }
    580 Shader.prototype = {
    581  id : null,
    582  gl : null,
    583  compiled : false,
    584  shader : null,
    585  shaders : [],
    586 
    587  destroy : function() {
    588    if (this.shader != null) deleteShader(this.gl, this.shader);
    589  },
    590 
    591  compile : function() {
    592    this.shader = loadShaderArray(this.gl, this.shaders);
    593  },
    594 
    595  use : function() {
    596    if (this.shader == null)
    597      this.compile();
    598    this.gl.useProgram(this.shader.program);
    599  },
    600 
    601  uniform1fv : function(name, value) {
    602    var loc = this.uniform(name);
    603    this.gl.uniform1fv(loc, value);
    604  },
    605 
    606  uniform2fv : function(name, value) {
    607    var loc = this.uniform(name);
    608    this.gl.uniform2fv(loc, value);
    609  },
    610 
    611  uniform3fv : function(name, value) {
    612    var loc = this.uniform(name);
    613    this.gl.uniform3fv(loc, value);
    614  },
    615 
    616  uniform4fv : function(name, value) {
    617    var loc = this.uniform(name);
    618    this.gl.uniform4fv(loc, value);
    619  },
    620 
    621  uniform1f : function(name, value) {
    622    var loc = this.uniform(name);
    623    this.gl.uniform1f(loc, value);
    624  },
    625 
    626  uniform2f : function(name, v1,v2) {
    627    var loc = this.uniform(name);
    628    this.gl.uniform2f(loc, v1,v2);
    629  },
    630 
    631  uniform3f : function(name, v1,v2,v3) {
    632    var loc = this.uniform(name);
    633    this.gl.uniform3f(loc, v1,v2,v3);
    634  },
    635 
    636  uniform4f : function(name, v1,v2,v3,v4) {
    637    var loc = this.uniform(name);
    638    this.gl.uniform4f(loc, v1, v2, v3, v4);
    639  },
    640 
    641  uniform1iv : function(name, value) {
    642    var loc = this.uniform(name);
    643    this.gl.uniform1iv(loc, value);
    644  },
    645 
    646  uniform2iv : function(name, value) {
    647    var loc = this.uniform(name);
    648    this.gl.uniform2iv(loc, value);
    649  },
    650 
    651  uniform3iv : function(name, value) {
    652    var loc = this.uniform(name);
    653    this.gl.uniform3iv(loc, value);
    654  },
    655 
    656  uniform4iv : function(name, value) {
    657    var loc = this.uniform(name);
    658    this.gl.uniform4iv(loc, value);
    659  },
    660 
    661  uniform1i : function(name, value) {
    662    var loc = this.uniform(name);
    663    this.gl.uniform1i(loc, value);
    664  },
    665 
    666  uniform2i : function(name, v1,v2) {
    667    var loc = this.uniform(name);
    668    this.gl.uniform2i(loc, v1,v2);
    669  },
    670 
    671  uniform3i : function(name, v1,v2,v3) {
    672    var loc = this.uniform(name);
    673    this.gl.uniform3i(loc, v1,v2,v3);
    674  },
    675 
    676  uniform4i : function(name, v1,v2,v3,v4) {
    677    var loc = this.uniform(name);
    678    this.gl.uniform4i(loc, v1, v2, v3, v4);
    679  },
    680 
    681  uniformMatrix4fv : function(name, value) {
    682    var loc = this.uniform(name);
    683    this.gl.uniformMatrix4fv(loc, false, value);
    684  },
    685 
    686  uniformMatrix3fv : function(name, value) {
    687    var loc = this.uniform(name);
    688    this.gl.uniformMatrix3fv(loc, false, value);
    689  },
    690 
    691  uniformMatrix2fv : function(name, value) {
    692    var loc = this.uniform(name);
    693    this.gl.uniformMatrix2fv(loc, false, value);
    694  },
    695 
    696  attrib : function(name) {
    697    if (this.attribLocations[name] == null) {
    698      var loc = this.gl.getAttribLocation(this.shader.program, name);
    699      this.attribLocations[name] = loc;
    700    }
    701    return this.attribLocations[name];
    702  },
    703 
    704  uniform : function(name) {
    705    if (this.uniformLocations[name] == null) {
    706      var loc = this.gl.getUniformLocation(this.shader.program, name);
    707      this.uniformLocations[name] = loc;
    708    }
    709    return this.uniformLocations[name];
    710  }
    711 }
    712 Filter = function(gl, shader) {
    713  Shader.apply(this, arguments);
    714 }
    715 Filter.prototype = new Shader();
    716 Filter.prototype.apply = function(init) {
    717  this.use();
    718  var va = this.attrib("Vertex");
    719  var ta = this.attrib("Tex");
    720  var vbo = Quad.getCachedVBO(this.gl);
    721  if (init) init(this);
    722  vbo.draw(va, null, ta);
    723 }
    724 
    725 
    726 VBO = function(gl) {
    727  this.gl = gl;
    728  this.data = [];
    729  this.elementsVBO = null;
    730  for (var i=1; i<arguments.length; i++) {
    731    if (arguments[i].elements)
    732      this.elements = arguments[i];
    733    else
    734      this.data.push(arguments[i]);
    735  }
    736 }
    737 
    738 VBO.prototype = {
    739  initialized : false,
    740  length : 0,
    741  vbos : null,
    742  type : 'TRIANGLES',
    743  elementsVBO : null,
    744  elements : null,
    745 
    746  setData : function() {
    747    this.destroy();
    748    this.data = [];
    749    for (var i=0; i<arguments.length; i++) {
    750      if (arguments[i].elements)
    751        this.elements = arguments[i];
    752      else
    753        this.data.push(arguments[i]);
    754    }
    755  },
    756 
    757  destroy : function() {
    758    if (this.vbos != null)
    759      for (var i=0; i<this.vbos.length; i++)
    760        this.gl.deleteBuffer(this.vbos[i]);
    761    if (this.elementsVBO != null)
    762      this.gl.deleteBuffer(this.elementsVBO);
    763    this.length = this.elementsLength = 0;
    764    this.vbos = this.elementsVBO = null;
    765    this.initialized = false;
    766  },
    767 
    768  init : function() {
    769    this.destroy();
    770    var gl = this.gl;
    771 
    772    gl.getError();
    773    var vbos = [];
    774    var length = 0;
    775    for (var i=0; i<this.data.length; i++)
    776      vbos.push(gl.createBuffer());
    777    if (this.elements != null)
    778      this.elementsVBO = gl.createBuffer();
    779    try {
    780      throwError(gl, "genBuffers");
    781      for (var i = 0; i<this.data.length; i++) {
    782        var d = this.data[i];
    783        var dlen = Math.floor(d.data.length / d.size);
    784        if (i == 0 || dlen < length)
    785            length = dlen;
    786        if (!d.floatArray)
    787          d.floatArray = new Float32Array(d.data);
    788        gl.bindBuffer(gl.ARRAY_BUFFER, vbos[i]);
    789        throwError(gl, "bindBuffer");
    790        gl.bufferData(gl.ARRAY_BUFFER, d.floatArray, gl.STATIC_DRAW);
    791        throwError(gl, "bufferData");
    792      }
    793      if (this.elementsVBO != null) {
    794        var d = this.elements;
    795        this.elementsLength = d.data.length;
    796        this.elementsType = d.type == gl.UNSIGNED_BYTE ? d.type : gl.UNSIGNED_SHORT;
    797        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.elementsVBO);
    798        throwError(gl, "bindBuffer ELEMENT_ARRAY_BUFFER");
    799        if (this.elementsType == gl.UNSIGNED_SHORT && !d.ushortArray) {
    800          d.ushortArray = new Uint16Array(d.data);
    801          gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, d.ushortArray, gl.STATIC_DRAW);
    802        } else if (this.elementsType == gl.UNSIGNED_BYTE && !d.ubyteArray) {
    803          d.ubyteArray = new Uint8Array(d.data);
    804          gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, d.ubyteArray, gl.STATIC_DRAW);
    805        }
    806        throwError(gl, "bufferData ELEMENT_ARRAY_BUFFER");
    807      }
    808    } catch(e) {
    809      for (var i=0; i<vbos.length; i++)
    810        gl.deleteBuffer(vbos[i]);
    811      throw(e);
    812    }
    813 
    814    gl.bindBuffer(gl.ARRAY_BUFFER, null);
    815    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
    816 
    817    this.length = length;
    818    this.vbos = vbos;
    819 
    820    this.initialized = true;
    821  },
    822 
    823  use : function() {
    824    if (!this.initialized) this.init();
    825    var gl = this.gl;
    826    for (var i=0; i<arguments.length; i++) {
    827      if (arguments[i] == null || arguments[i] == -1) continue;
    828      gl.bindBuffer(gl.ARRAY_BUFFER, this.vbos[i]);
    829      gl.vertexAttribPointer(arguments[i], this.data[i].size, gl.FLOAT, false, 0, 0);
    830      gl.enableVertexAttribArray(arguments[i]);
    831    }
    832    if (this.elementsVBO != null) {
    833      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.elementsVBO);
    834    }
    835  },
    836 
    837  draw : function() {
    838    var args = [];
    839    this.use.apply(this, arguments);
    840    var gl = this.gl;
    841    if (this.elementsVBO != null) {
    842      gl.drawElements(gl[this.type], this.elementsLength, this.elementsType, 0);
    843    } else {
    844      gl.drawArrays(gl[this.type], 0, this.length);
    845    }
    846  }
    847 }
    848 
    849 FBO = function(gl, width, height, use_depth) {
    850  this.gl = gl;
    851  this.width = width;
    852  this.height = height;
    853  if (use_depth != null)
    854    this.useDepth = use_depth;
    855 }
    856 FBO.prototype = {
    857  initialized : false,
    858  useDepth : true,
    859  fbo : null,
    860  rbo : null,
    861  texture : null,
    862 
    863  destroy : function() {
    864    if (this.fbo) this.gl.deleteFramebuffer(this.fbo);
    865    if (this.rbo) this.gl.deleteRenderbuffer(this.rbo);
    866    if (this.texture) this.gl.deleteTexture(this.texture);
    867  },
    868 
    869  init : function() {
    870    var gl = this.gl;
    871    var w = this.width, h = this.height;
    872    var fbo = this.fbo != null ? this.fbo : gl.createFramebuffer();
    873    var rb;
    874 
    875    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
    876    checkError(gl, "FBO.init bindFramebuffer");
    877    if (this.useDepth) {
    878      rb = this.rbo != null ? this.rbo : gl.createRenderbuffer();
    879      gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
    880      checkError(gl, "FBO.init bindRenderbuffer");
    881      gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, w, h);
    882      checkError(gl, "FBO.init renderbufferStorage");
    883    }
    884 
    885    var tex = this.texture != null ? this.texture : gl.createTexture();
    886    gl.bindTexture(gl.TEXTURE_2D, tex);
    887    try {
    888      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
    889    } catch (e) { // argh, no null texture support
    890      var tmp = this.getTempCanvas(w,h);
    891      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, tmp);
    892    }
    893    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    894    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    895    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    896    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    897    checkError(gl, "FBO.init tex");
    898 
    899    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
    900    checkError(gl, "FBO.init bind tex");
    901 
    902    if (this.useDepth) {
    903      gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, rb);
    904      checkError(gl, "FBO.init bind depth buffer");
    905    }
    906 
    907    var fbstat = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
    908    if (fbstat != gl.FRAMEBUFFER_COMPLETE) {
    909      var glv;
    910      for (var v in gl) {
    911        try { glv = gl[v]; } catch (e) { glv = null; }
    912        if (glv == fbstat) { fbstat = v; break; }}
    913        log("Framebuffer status: " + fbstat);
    914    }
    915    checkError(gl, "FBO.init check fbo");
    916 
    917    this.fbo = fbo;
    918    this.rbo = rb;
    919    this.texture = tex;
    920    this.initialized = true;
    921  },
    922 
    923  getTempCanvas : function(w, h) {
    924    if (!FBO.tempCanvas) {
    925      FBO.tempCanvas = document.createElement('canvas');
    926    }
    927    FBO.tempCanvas.width = w;
    928    FBO.tempCanvas.height = h;
    929    return FBO.tempCanvas;
    930  },
    931 
    932  use : function() {
    933    if (!this.initialized) this.init();
    934    this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.fbo);
    935  }
    936 }
    937 
    938 function GLError(err, msg, fileName, lineNumber) {
    939  this.message = msg;
    940  this.glError = err;
    941 }
    942 
    943 GLError.prototype = new Error();
    944 
    945 function makeGLErrorWrapper(gl, fname) {
    946  return (function() {
    947    try {
    948      var rv = gl[fname].apply(gl, arguments);
    949      var err = gl.getError();
    950      if (err != gl.NO_ERROR) {
    951        throw(new GLError(
    952            err, "GL error "+getGLErrorAsString(gl, err)+" in "+fname));
    953      }
    954      return rv;
    955    } catch (e) {
    956      if (e.glError !== undefined) {
    957        throw e;
    958      }
    959      throw(new Error("Threw " + e.name +
    960                      " in " + fname + "\n" +
    961                      e.message + "\n" +
    962                      arguments.callee.caller));
    963    }
    964  });
    965 }
    966 
    967 function wrapGLContext(gl) {
    968  var wrap = {};
    969  for (var i in gl) {
    970    try {
    971      if (typeof gl[i] == 'function') {
    972          wrap[i] = makeGLErrorWrapper(gl, i);
    973      } else {
    974          wrap[i] = gl[i];
    975      }
    976    } catch (e) {
    977      // log("wrapGLContext: Error accessing " + i);
    978    }
    979  }
    980  wrap.getError = function(){ return gl.getError(); };
    981  return wrap;
    982 }
    983 
    984 function getGLContext(canvas) {
    985  return canvas.getContext(GL_CONTEXT_ID, {antialias: false});
    986 }
    987 
    988 // Assert that f generates a specific GL error.
    989 function assertGLError(gl, err, name, f) {
    990  if (f == null) { f = name; name = null; }
    991  var r = false;
    992  var glErr = 0;
    993  try { f(); } catch(e) { r=true; glErr = e.glError; }
    994  if (glErr !== err) {
    995    if (glErr === undefined) {
    996      testFailed("assertGLError: UNEXPECTED EXCEPTION", name, f);
    997    } else {
    998      testFailed("assertGLError: expected: " + getGLErrorAsString(gl, err) +
    999                 " actual: " + getGLErrorAsString(gl, glErr), name, f);
   1000    }
   1001    return false;
   1002  }
   1003  return true;
   1004 }
   1005 
   1006 // Assert that f generates a GL error from a list.
   1007 function assertGLErrorIn(gl, expectedErrorList, name, f) {
   1008  if (f == null) { f = name; name = null; }
   1009 
   1010  var actualError = 0;
   1011  try {
   1012    f();
   1013  } catch(e) {
   1014    if ('glError' in e) {
   1015      actualError = e.glError;
   1016    } else {
   1017      testFailed("assertGLError: UNEXPCETED EXCEPTION", name, f);
   1018      return false;
   1019    }
   1020  }
   1021 
   1022  var expectedErrorStrList = [];
   1023  var expectedErrorSet = {};
   1024  for (var i in expectedErrorList) {
   1025    var cur = expectedErrorList[i];
   1026    expectedErrorSet[cur] = true;
   1027    expectedErrorStrList.push(getGLErrorAsString(gl, cur));
   1028  }
   1029  var expectedErrorListStr = "[" + expectedErrorStrList.join(", ") + "]";
   1030 
   1031  if (actualError in expectedErrorSet) {
   1032    return true;
   1033  }
   1034 
   1035  testFailed("assertGLError: expected: " + expectedErrorListStr +
   1036             " actual: " + getGLErrorAsString(gl, actualError), name, f);
   1037  return false;
   1038 }
   1039 
   1040 // Assert that f generates some GL error. Used in situations where it's
   1041 // ambigious which of multiple possible errors will be generated.
   1042 function assertSomeGLError(gl, name, f) {
   1043  if (f == null) { f = name; name = null; }
   1044  var r = false;
   1045  var glErr = 0;
   1046  var err = 0;
   1047  try { f(); } catch(e) { r=true; glErr = e.glError; }
   1048  if (glErr === 0) {
   1049    if (glErr === undefined) {
   1050      testFailed("assertGLError: UNEXPECTED EXCEPTION", name, f);
   1051    } else {
   1052      testFailed("assertGLError: expected: " + getGLErrorAsString(gl, err) +
   1053                 " actual: " + getGLErrorAsString(gl, glErr), name, f);
   1054    }
   1055    return false;
   1056  }
   1057  return true;
   1058 }
   1059 
   1060 // Assert that f throws an exception but does not generate a GL error.
   1061 function assertThrowNoGLError(gl, name, f) {
   1062  if (f == null) { f = name; name = null; }
   1063  var r = false;
   1064  var glErr = undefined;
   1065  var exp;
   1066  try { f(); } catch(e) { r=true; glErr = e.glError; exp = e;}
   1067  if (!r) {
   1068    testFailed(
   1069      "assertThrowNoGLError: should have thrown exception", name, f);
   1070    return false;
   1071  } else {
   1072    if (glErr !== undefined) {
   1073      testFailed(
   1074        "assertThrowNoGLError: should be no GL error but generated: " +
   1075        getGLErrorAsString(gl, glErr), name, f);
   1076      return false;
   1077    }
   1078  }
   1079  testPassed("assertThrowNoGLError", name, f);
   1080  return true;
   1081 }
   1082 
   1083 function assertThrows(gl, shouldThrow, info, func) {
   1084  var didThrow = false;
   1085  try {
   1086    func();
   1087  } catch (e) {
   1088    var didGLError = (e instanceof GLError);
   1089    if (!didGLError) {
   1090      didThrow = true;
   1091    }
   1092  }
   1093 
   1094  var text = shouldThrow ? "Should throw: "
   1095                         : "Should not throw: ";
   1096  var func = (didThrow == shouldThrow) ? testPassed : testFailed;
   1097 
   1098  func(text + info);
   1099 }
   1100 
   1101 Quad = {
   1102  vertices : [
   1103    -1,-1,0,
   1104    1,-1,0,
   1105    -1,1,0,
   1106    1,-1,0,
   1107    1,1,0,
   1108    -1,1,0
   1109  ],
   1110  normals : [
   1111    0,0,-1,
   1112    0,0,-1,
   1113    0,0,-1,
   1114    0,0,-1,
   1115    0,0,-1,
   1116    0,0,-1
   1117  ],
   1118  texcoords : [
   1119    0,0,
   1120    1,0,
   1121    0,1,
   1122    1,0,
   1123    1,1,
   1124    0,1
   1125  ],
   1126  indices : [0,1,2,1,5,2],
   1127  makeVBO : function(gl) {
   1128    return new VBO(gl,
   1129        {size:3, data: Quad.vertices},
   1130        {size:3, data: Quad.normals},
   1131        {size:2, data: Quad.texcoords}
   1132    )
   1133  },
   1134  cache: {},
   1135  getCachedVBO : function(gl) {
   1136    if (!this.cache[gl])
   1137      this.cache[gl] = this.makeVBO(gl);
   1138    return this.cache[gl];
   1139  }
   1140 }
   1141 Cube = {
   1142  vertices : [  0.5, -0.5,  0.5, // +X
   1143                0.5, -0.5, -0.5,
   1144                0.5,  0.5, -0.5,
   1145                0.5,  0.5,  0.5,
   1146 
   1147                0.5,  0.5,  0.5, // +Y
   1148                0.5,  0.5, -0.5,
   1149                -0.5,  0.5, -0.5,
   1150                -0.5,  0.5,  0.5,
   1151 
   1152                0.5,  0.5,  0.5, // +Z
   1153                -0.5,  0.5,  0.5,
   1154                -0.5, -0.5,  0.5,
   1155                0.5, -0.5,  0.5,
   1156 
   1157                -0.5, -0.5,  0.5, // -X
   1158                -0.5,  0.5,  0.5,
   1159                -0.5,  0.5, -0.5,
   1160                -0.5, -0.5, -0.5,
   1161 
   1162                -0.5, -0.5,  0.5, // -Y
   1163                -0.5, -0.5, -0.5,
   1164                0.5, -0.5, -0.5,
   1165                0.5, -0.5,  0.5,
   1166 
   1167                -0.5, -0.5, -0.5, // -Z
   1168                -0.5,  0.5, -0.5,
   1169                0.5,  0.5, -0.5,
   1170                0.5, -0.5, -0.5,
   1171      ],
   1172 
   1173  normals : [ 1, 0, 0,
   1174              1, 0, 0,
   1175              1, 0, 0,
   1176              1, 0, 0,
   1177 
   1178              0, 1, 0,
   1179              0, 1, 0,
   1180              0, 1, 0,
   1181              0, 1, 0,
   1182 
   1183              0, 0, 1,
   1184              0, 0, 1,
   1185              0, 0, 1,
   1186              0, 0, 1,
   1187 
   1188              -1, 0, 0,
   1189              -1, 0, 0,
   1190              -1, 0, 0,
   1191              -1, 0, 0,
   1192 
   1193              0,-1, 0,
   1194              0,-1, 0,
   1195              0,-1, 0,
   1196              0,-1, 0,
   1197 
   1198              0, 0,-1,
   1199              0, 0,-1,
   1200              0, 0,-1,
   1201              0, 0,-1
   1202      ],
   1203 
   1204  indices : [],
   1205  create : function(){
   1206    for (var i = 0; i < 6; i++) {
   1207      Cube.indices.push(i*4 + 0);
   1208      Cube.indices.push(i*4 + 1);
   1209      Cube.indices.push(i*4 + 3);
   1210      Cube.indices.push(i*4 + 1);
   1211      Cube.indices.push(i*4 + 2);
   1212      Cube.indices.push(i*4 + 3);
   1213    }
   1214  },
   1215 
   1216  makeVBO : function(gl) {
   1217    return new VBO(gl,
   1218        {size:3, data: Cube.vertices},
   1219        {size:3, data: Cube.normals},
   1220        {elements: true, data: Cube.indices}
   1221    )
   1222  },
   1223  cache : {},
   1224  getCachedVBO : function(gl) {
   1225    if (!this.cache[gl])
   1226      this.cache[gl] = this.makeVBO(gl);
   1227    return this.cache[gl];
   1228  }
   1229 }
   1230 Cube.create();
   1231 
   1232 Sphere = {
   1233  vertices : [],
   1234  normals : [],
   1235  indices : [],
   1236  create : function(){
   1237    var r = 0.75;
   1238    function vert(theta, phi)
   1239    {
   1240      var r = 0.75;
   1241      var x, y, z, nx, ny, nz;
   1242 
   1243      nx = Math.sin(theta) * Math.cos(phi);
   1244      ny = Math.sin(phi);
   1245      nz = Math.cos(theta) * Math.cos(phi);
   1246      Sphere.normals.push(nx);
   1247      Sphere.normals.push(ny);
   1248      Sphere.normals.push(nz);
   1249 
   1250      x = r * Math.sin(theta) * Math.cos(phi);
   1251      y = r * Math.sin(phi);
   1252      z = r * Math.cos(theta) * Math.cos(phi);
   1253      Sphere.vertices.push(x);
   1254      Sphere.vertices.push(y);
   1255      Sphere.vertices.push(z);
   1256    }
   1257    for (var phi = -Math.PI/2; phi < Math.PI/2; phi += Math.PI/20) {
   1258      var phi2 = phi + Math.PI/20;
   1259      for (var theta = -Math.PI/2; theta <= Math.PI/2; theta += Math.PI/20) {
   1260        vert(theta, phi);
   1261        vert(theta, phi2);
   1262      }
   1263    }
   1264  }
   1265 }
   1266 
   1267 Sphere.create();
   1268 
   1269 initGL_CONTEXT_ID = function(){
   1270  var c = document.createElement('canvas');
   1271  var contextNames = ['webgl', 'experimental-webgl'];
   1272  GL_CONTEXT_ID = null;
   1273  for (var i=0; i<contextNames.length; i++) {
   1274    try {
   1275      if (c.getContext(contextNames[i])) {
   1276        GL_CONTEXT_ID = contextNames[i];
   1277        break;
   1278      }
   1279    } catch (e) {
   1280    }
   1281  }
   1282  if (!GL_CONTEXT_ID) {
   1283    log("No WebGL context found. Unable to run tests.");
   1284  }
   1285 }
   1286 
   1287 initGL_CONTEXT_ID();