Home | History | Annotate | Download | only in debug
      1 /*
      2 ** Copyright (c) 2012 The Khronos Group Inc.
      3 **
      4 ** Permission is hereby granted, free of charge, to any person obtaining a
      5 ** copy of this software and/or associated documentation files (the
      6 ** "Materials"), to deal in the Materials without restriction, including
      7 ** without limitation the rights to use, copy, modify, merge, publish,
      8 ** distribute, sublicense, and/or sell copies of the Materials, and to
      9 ** permit persons to whom the Materials are furnished to do so, subject to
     10 ** the following conditions:
     11 **
     12 ** The above copyright notice and this permission notice shall be included
     13 ** in all copies or substantial portions of the Materials.
     14 **
     15 ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     16 ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     17 ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
     18 ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
     19 ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
     20 ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
     21 ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
     22 */
     23 
     24 // Various functions for helping debug WebGL apps.
     25 
     26 WebGLDebugUtils = function() {
     27 
     28 /**
     29  * Wrapped logging function.
     30  * @param {string} msg Message to log.
     31  */
     32 var log = function(msg) {
     33   if (window.console && window.console.log) {
     34     window.console.log(msg);
     35   }
     36 };
     37 
     38 /**
     39  * Wrapped error logging function.
     40  * @param {string} msg Message to log.
     41  */
     42 var error = function(msg) {
     43   if (window.console && window.console.error) {
     44     window.console.error(msg);
     45   } else {
     46     log(msg);
     47   }
     48 };
     49 
     50 
     51 /**
     52  * Which arguments are enums based on the number of arguments to the function.
     53  * So
     54  *    'texImage2D': {
     55  *       9: { 0:true, 2:true, 6:true, 7:true },
     56  *       6: { 0:true, 2:true, 3:true, 4:true },
     57  *    },
     58  *
     59  * means if there are 9 arguments then 6 and 7 are enums, if there are 6
     60  * arguments 3 and 4 are enums
     61  *
     62  * @type {!Object.<number, !Object.<number, string>}}
     63  */
     64 var glValidEnumContexts = {
     65   // Generic setters and getters
     66 
     67   'enable': {1: { 0:true }},
     68   'disable': {1: { 0:true }},
     69   'getParameter': {1: { 0:true }},
     70 
     71   // Rendering
     72 
     73   'drawArrays': {3:{ 0:true }},
     74   'drawElements': {4:{ 0:true, 2:true }},
     75 
     76   // Shaders
     77 
     78   'createShader': {1: { 0:true }},
     79   'getShaderParameter': {2: { 1:true }},
     80   'getProgramParameter': {2: { 1:true }},
     81   'getShaderPrecisionFormat': {2: { 0: true, 1:true }},
     82 
     83   // Vertex attributes
     84 
     85   'getVertexAttrib': {2: { 1:true }},
     86   'vertexAttribPointer': {6: { 2:true }},
     87 
     88   // Textures
     89 
     90   'bindTexture': {2: { 0:true }},
     91   'activeTexture': {1: { 0:true }},
     92   'getTexParameter': {2: { 0:true, 1:true }},
     93   'texParameterf': {3: { 0:true, 1:true }},
     94   'texParameteri': {3: { 0:true, 1:true, 2:true }},
     95   'texImage2D': {
     96      9: { 0:true, 2:true, 6:true, 7:true },
     97      6: { 0:true, 2:true, 3:true, 4:true },
     98   },
     99   'texSubImage2D': {
    100     9: { 0:true, 6:true, 7:true },
    101     7: { 0:true, 4:true, 5:true },
    102   },
    103   'copyTexImage2D': {8: { 0:true, 2:true }},
    104   'copyTexSubImage2D': {8: { 0:true }},
    105   'generateMipmap': {1: { 0:true }},
    106   'compressedTexImage2D': {7: { 0: true, 2:true }},
    107   'compressedTexSubImage2D': {8: { 0: true, 6:true }},
    108 
    109   // Buffer objects
    110 
    111   'bindBuffer': {2: { 0:true }},
    112   'bufferData': {3: { 0:true, 2:true }},
    113   'bufferSubData': {3: { 0:true }},
    114   'getBufferParameter': {2: { 0:true, 1:true }},
    115 
    116   // Renderbuffers and framebuffers
    117 
    118   'pixelStorei': {2: { 0:true, 1:true }},
    119   'readPixels': {7: { 4:true, 5:true }},
    120   'bindRenderbuffer': {2: { 0:true }},
    121   'bindFramebuffer': {2: { 0:true }},
    122   'checkFramebufferStatus': {1: { 0:true }},
    123   'framebufferRenderbuffer': {4: { 0:true, 1:true, 2:true }},
    124   'framebufferTexture2D': {5: { 0:true, 1:true, 2:true }},
    125   'getFramebufferAttachmentParameter': {3: { 0:true, 1:true, 2:true }},
    126   'getRenderbufferParameter': {2: { 0:true, 1:true }},
    127   'renderbufferStorage': {4: { 0:true, 1:true }},
    128 
    129   // Frame buffer operations (clear, blend, depth test, stencil)
    130 
    131   'clear': {1: { 0:true }},
    132   'depthFunc': {1: { 0:true }},
    133   'blendFunc': {2: { 0:true, 1:true }},
    134   'blendFuncSeparate': {4: { 0:true, 1:true, 2:true, 3:true }},
    135   'blendEquation': {1: { 0:true }},
    136   'blendEquationSeparate': {2: { 0:true, 1:true }},
    137   'stencilFunc': {3: { 0:true }},
    138   'stencilFuncSeparate': {4: { 0:true, 1:true }},
    139   'stencilMaskSeparate': {2: { 0:true }},
    140   'stencilOp': {3: { 0:true, 1:true, 2:true }},
    141   'stencilOpSeparate': {4: { 0:true, 1:true, 2:true, 3:true }},
    142 
    143   // Culling
    144 
    145   'cullFace': {1: { 0:true }},
    146   'frontFace': {1: { 0:true }},
    147 };
    148 
    149 /**
    150  * Map of numbers to names.
    151  * @type {Object}
    152  */
    153 var glEnums = null;
    154 
    155 /**
    156  * Initializes this module. Safe to call more than once.
    157  * @param {!WebGLRenderingContext} ctx A WebGL context. If
    158  *    you have more than one context it doesn't matter which one
    159  *    you pass in, it is only used to pull out constants.
    160  */
    161 function init(ctx) {
    162   if (glEnums == null) {
    163     glEnums = { };
    164     for (var propertyName in ctx) {
    165       if (typeof ctx[propertyName] == 'number') {
    166         glEnums[ctx[propertyName]] = propertyName;
    167       }
    168     }
    169   }
    170 }
    171 
    172 /**
    173  * Checks the utils have been initialized.
    174  */
    175 function checkInit() {
    176   if (glEnums == null) {
    177     throw 'WebGLDebugUtils.init(ctx) not called';
    178   }
    179 }
    180 
    181 /**
    182  * Returns true or false if value matches any WebGL enum
    183  * @param {*} value Value to check if it might be an enum.
    184  * @return {boolean} True if value matches one of the WebGL defined enums
    185  */
    186 function mightBeEnum(value) {
    187   checkInit();
    188   return (glEnums[value] !== undefined);
    189 }
    190 
    191 /**
    192  * Gets an string version of an WebGL enum.
    193  *
    194  * Example:
    195  *   var str = WebGLDebugUtil.glEnumToString(ctx.getError());
    196  *
    197  * @param {number} value Value to return an enum for
    198  * @return {string} The string version of the enum.
    199  */
    200 function glEnumToString(value) {
    201   checkInit();
    202   var name = glEnums[value];
    203   return (name !== undefined) ? ("gl." + name) :
    204       ("/*UNKNOWN WebGL ENUM*/ 0x" + value.toString(16) + "");
    205 }
    206 
    207 /**
    208  * Returns the string version of a WebGL argument.
    209  * Attempts to convert enum arguments to strings.
    210  * @param {string} functionName the name of the WebGL function.
    211  * @param {number} numArgs the number of arguments passed to the function.
    212  * @param {number} argumentIndx the index of the argument.
    213  * @param {*} value The value of the argument.
    214  * @return {string} The value as a string.
    215  */
    216 function glFunctionArgToString(functionName, numArgs, argumentIndex, value) {
    217   var funcInfo = glValidEnumContexts[functionName];
    218   if (funcInfo !== undefined) {
    219     var funcInfo = funcInfo[numArgs];
    220     if (funcInfo !== undefined) {
    221       if (funcInfo[argumentIndex]) {
    222         return glEnumToString(value);
    223       }
    224     }
    225   }
    226   if (value === null) {
    227     return "null";
    228   } else if (value === undefined) {
    229     return "undefined";
    230   } else {
    231     return value.toString();
    232   }
    233 }
    234 
    235 /**
    236  * Converts the arguments of a WebGL function to a string.
    237  * Attempts to convert enum arguments to strings.
    238  *
    239  * @param {string} functionName the name of the WebGL function.
    240  * @param {number} args The arguments.
    241  * @return {string} The arguments as a string.
    242  */
    243 function glFunctionArgsToString(functionName, args) {
    244   // apparently we can't do args.join(",");
    245   var argStr = "";
    246   var numArgs = args.length;
    247   for (var ii = 0; ii < numArgs; ++ii) {
    248     argStr += ((ii == 0) ? '' : ', ') +
    249         glFunctionArgToString(functionName, numArgs, ii, args[ii]);
    250   }
    251   return argStr;
    252 };
    253 
    254 
    255 function makePropertyWrapper(wrapper, original, propertyName) {
    256   //log("wrap prop: " + propertyName);
    257   wrapper.__defineGetter__(propertyName, function() {
    258     return original[propertyName];
    259   });
    260   // TODO(gmane): this needs to handle properties that take more than
    261   // one value?
    262   wrapper.__defineSetter__(propertyName, function(value) {
    263     //log("set: " + propertyName);
    264     original[propertyName] = value;
    265   });
    266 }
    267 
    268 // Makes a function that calls a function on another object.
    269 function makeFunctionWrapper(original, functionName) {
    270   //log("wrap fn: " + functionName);
    271   var f = original[functionName];
    272   return function() {
    273     //log("call: " + functionName);
    274     var result = f.apply(original, arguments);
    275     return result;
    276   };
    277 }
    278 
    279 /**
    280  * Given a WebGL context returns a wrapped context that calls
    281  * gl.getError after every command and calls a function if the
    282  * result is not gl.NO_ERROR.
    283  *
    284  * @param {!WebGLRenderingContext} ctx The webgl context to
    285  *        wrap.
    286  * @param {!function(err, funcName, args): void} opt_onErrorFunc
    287  *        The function to call when gl.getError returns an
    288  *        error. If not specified the default function calls
    289  *        console.log with a message.
    290  * @param {!function(funcName, args): void} opt_onFunc The
    291  *        function to call when each webgl function is called.
    292  *        You can use this to log all calls for example.
    293  */
    294 function makeDebugContext(ctx, opt_onErrorFunc, opt_onFunc) {
    295   init(ctx);
    296   opt_onErrorFunc = opt_onErrorFunc || function(err, functionName, args) {
    297         // apparently we can't do args.join(",");
    298         var argStr = "";
    299         var numArgs = args.length;
    300         for (var ii = 0; ii < numArgs; ++ii) {
    301           argStr += ((ii == 0) ? '' : ', ') +
    302               glFunctionArgToString(functionName, numArgs, ii, args[ii]);
    303         }
    304         error("WebGL error "+ glEnumToString(err) + " in "+ functionName +
    305               "(" + argStr + ")");
    306       };
    307 
    308   // Holds booleans for each GL error so after we get the error ourselves
    309   // we can still return it to the client app.
    310   var glErrorShadow = { };
    311 
    312   // Makes a function that calls a WebGL function and then calls getError.
    313   function makeErrorWrapper(ctx, functionName) {
    314     return function() {
    315       if (opt_onFunc) {
    316         opt_onFunc(functionName, arguments);
    317       }
    318       var result = ctx[functionName].apply(ctx, arguments);
    319       var err = ctx.getError();
    320       if (err != 0) {
    321         glErrorShadow[err] = true;
    322         opt_onErrorFunc(err, functionName, arguments);
    323       }
    324       return result;
    325     };
    326   }
    327 
    328   // Make a an object that has a copy of every property of the WebGL context
    329   // but wraps all functions.
    330   var wrapper = {};
    331   for (var propertyName in ctx) {
    332     if (typeof ctx[propertyName] == 'function') {
    333        wrapper[propertyName] = makeErrorWrapper(ctx, propertyName);
    334      } else {
    335        makePropertyWrapper(wrapper, ctx, propertyName);
    336      }
    337   }
    338 
    339   // Override the getError function with one that returns our saved results.
    340   wrapper.getError = function() {
    341     for (var err in glErrorShadow) {
    342       if (glErrorShadow.hasOwnProperty(err)) {
    343         if (glErrorShadow[err]) {
    344           glErrorShadow[err] = false;
    345           return err;
    346         }
    347       }
    348     }
    349     return ctx.NO_ERROR;
    350   };
    351 
    352   return wrapper;
    353 }
    354 
    355 function resetToInitialState(ctx) {
    356   var numAttribs = ctx.getParameter(ctx.MAX_VERTEX_ATTRIBS);
    357   var tmp = ctx.createBuffer();
    358   ctx.bindBuffer(ctx.ARRAY_BUFFER, tmp);
    359   for (var ii = 0; ii < numAttribs; ++ii) {
    360     ctx.disableVertexAttribArray(ii);
    361     ctx.vertexAttribPointer(ii, 4, ctx.FLOAT, false, 0, 0);
    362     ctx.vertexAttrib1f(ii, 0);
    363   }
    364   ctx.deleteBuffer(tmp);
    365 
    366   var numTextureUnits = ctx.getParameter(ctx.MAX_TEXTURE_IMAGE_UNITS);
    367   for (var ii = 0; ii < numTextureUnits; ++ii) {
    368     ctx.activeTexture(ctx.TEXTURE0 + ii);
    369     ctx.bindTexture(ctx.TEXTURE_CUBE_MAP, null);
    370     ctx.bindTexture(ctx.TEXTURE_2D, null);
    371   }
    372 
    373   ctx.activeTexture(ctx.TEXTURE0);
    374   ctx.useProgram(null);
    375   ctx.bindBuffer(ctx.ARRAY_BUFFER, null);
    376   ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, null);
    377   ctx.bindFramebuffer(ctx.FRAMEBUFFER, null);
    378   ctx.bindRenderbuffer(ctx.RENDERBUFFER, null);
    379   ctx.disable(ctx.BLEND);
    380   ctx.disable(ctx.CULL_FACE);
    381   ctx.disable(ctx.DEPTH_TEST);
    382   ctx.disable(ctx.DITHER);
    383   ctx.disable(ctx.SCISSOR_TEST);
    384   ctx.blendColor(0, 0, 0, 0);
    385   ctx.blendEquation(ctx.FUNC_ADD);
    386   ctx.blendFunc(ctx.ONE, ctx.ZERO);
    387   ctx.clearColor(0, 0, 0, 0);
    388   ctx.clearDepth(1);
    389   ctx.clearStencil(-1);
    390   ctx.colorMask(true, true, true, true);
    391   ctx.cullFace(ctx.BACK);
    392   ctx.depthFunc(ctx.LESS);
    393   ctx.depthMask(true);
    394   ctx.depthRange(0, 1);
    395   ctx.frontFace(ctx.CCW);
    396   ctx.hint(ctx.GENERATE_MIPMAP_HINT, ctx.DONT_CARE);
    397   ctx.lineWidth(1);
    398   ctx.pixelStorei(ctx.PACK_ALIGNMENT, 4);
    399   ctx.pixelStorei(ctx.UNPACK_ALIGNMENT, 4);
    400   ctx.pixelStorei(ctx.UNPACK_FLIP_Y_WEBGL, false);
    401   ctx.pixelStorei(ctx.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
    402   // TODO: Delete this IF.
    403   if (ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL) {
    404     ctx.pixelStorei(ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL, ctx.BROWSER_DEFAULT_WEBGL);
    405   }
    406   ctx.polygonOffset(0, 0);
    407   ctx.sampleCoverage(1, false);
    408   ctx.scissor(0, 0, ctx.canvas.width, ctx.canvas.height);
    409   ctx.stencilFunc(ctx.ALWAYS, 0, 0xFFFFFFFF);
    410   ctx.stencilMask(0xFFFFFFFF);
    411   ctx.stencilOp(ctx.KEEP, ctx.KEEP, ctx.KEEP);
    412   ctx.viewport(0, 0, ctx.canvas.width, ctx.canvas.height);
    413   ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT | ctx.STENCIL_BUFFER_BIT);
    414 
    415   // TODO: This should NOT be needed but Firefox fails with 'hint'
    416   while(ctx.getError());
    417 }
    418 
    419 function makeLostContextSimulatingCanvas(canvas) {
    420   var unwrappedContext_;
    421   var wrappedContext_;
    422   var onLost_ = [];
    423   var onRestored_ = [];
    424   var wrappedContext_ = {};
    425   var contextId_ = 1;
    426   var contextLost_ = false;
    427   var resourceId_ = 0;
    428   var resourceDb_ = [];
    429   var numCallsToLoseContext_ = 0;
    430   var numCalls_ = 0;
    431   var canRestore_ = false;
    432   var restoreTimeout_ = 0;
    433 
    434   // Holds booleans for each GL error so can simulate errors.
    435   var glErrorShadow_ = { };
    436 
    437   canvas.getContext = function(f) {
    438     return function() {
    439       var ctx = f.apply(canvas, arguments);
    440       // Did we get a context and is it a WebGL context?
    441       if (ctx instanceof WebGLRenderingContext) {
    442         if (ctx != unwrappedContext_) {
    443           if (unwrappedContext_) {
    444             throw "got different context"
    445           }
    446           unwrappedContext_ = ctx;
    447           wrappedContext_ = makeLostContextSimulatingContext(unwrappedContext_);
    448         }
    449         return wrappedContext_;
    450       }
    451       return ctx;
    452     }
    453   }(canvas.getContext);
    454 
    455   function wrapEvent(listener) {
    456     if (typeof(listener) == "function") {
    457       return listener;
    458     } else {
    459       return function(info) {
    460         listener.handleEvent(info);
    461       }
    462     }
    463   }
    464 
    465   var addOnContextLostListener = function(listener) {
    466     onLost_.push(wrapEvent(listener));
    467   };
    468 
    469   var addOnContextRestoredListener = function(listener) {
    470     onRestored_.push(wrapEvent(listener));
    471   };
    472 
    473 
    474   function wrapAddEventListener(canvas) {
    475     var f = canvas.addEventListener;
    476     canvas.addEventListener = function(type, listener, bubble) {
    477       switch (type) {
    478         case 'webglcontextlost':
    479           addOnContextLostListener(listener);
    480           break;
    481         case 'webglcontextrestored':
    482           addOnContextRestoredListener(listener);
    483           break;
    484         default:
    485           f.apply(canvas, arguments);
    486       }
    487     };
    488   }
    489 
    490   wrapAddEventListener(canvas);
    491 
    492   canvas.loseContext = function() {
    493     if (!contextLost_) {
    494       contextLost_ = true;
    495       numCallsToLoseContext_ = 0;
    496       ++contextId_;
    497       while (unwrappedContext_.getError());
    498       clearErrors();
    499       glErrorShadow_[unwrappedContext_.CONTEXT_LOST_WEBGL] = true;
    500       var event = makeWebGLContextEvent("context lost");
    501       var callbacks = onLost_.slice();
    502       setTimeout(function() {
    503           //log("numCallbacks:" + callbacks.length);
    504           for (var ii = 0; ii < callbacks.length; ++ii) {
    505             //log("calling callback:" + ii);
    506             callbacks[ii](event);
    507           }
    508           if (restoreTimeout_ >= 0) {
    509             setTimeout(function() {
    510                 canvas.restoreContext();
    511               }, restoreTimeout_);
    512           }
    513         }, 0);
    514     }
    515   };
    516 
    517   canvas.restoreContext = function() {
    518     if (contextLost_) {
    519       if (onRestored_.length) {
    520         setTimeout(function() {
    521             if (!canRestore_) {
    522               throw "can not restore. webglcontestlost listener did not call event.preventDefault";
    523             }
    524             freeResources();
    525             resetToInitialState(unwrappedContext_);
    526             contextLost_ = false;
    527             numCalls_ = 0;
    528             canRestore_ = false;
    529             var callbacks = onRestored_.slice();
    530             var event = makeWebGLContextEvent("context restored");
    531             for (var ii = 0; ii < callbacks.length; ++ii) {
    532               callbacks[ii](event);
    533             }
    534           }, 0);
    535       }
    536     }
    537   };
    538 
    539   canvas.loseContextInNCalls = function(numCalls) {
    540     if (contextLost_) {
    541       throw "You can not ask a lost contet to be lost";
    542     }
    543     numCallsToLoseContext_ = numCalls_ + numCalls;
    544   };
    545 
    546   canvas.getNumCalls = function() {
    547     return numCalls_;
    548   };
    549 
    550   canvas.setRestoreTimeout = function(timeout) {
    551     restoreTimeout_ = timeout;
    552   };
    553 
    554   function isWebGLObject(obj) {
    555     //return false;
    556     return (obj instanceof WebGLBuffer ||
    557             obj instanceof WebGLFramebuffer ||
    558             obj instanceof WebGLProgram ||
    559             obj instanceof WebGLRenderbuffer ||
    560             obj instanceof WebGLShader ||
    561             obj instanceof WebGLTexture);
    562   }
    563 
    564   function checkResources(args) {
    565     for (var ii = 0; ii < args.length; ++ii) {
    566       var arg = args[ii];
    567       if (isWebGLObject(arg)) {
    568         return arg.__webglDebugContextLostId__ == contextId_;
    569       }
    570     }
    571     return true;
    572   }
    573 
    574   function clearErrors() {
    575     var k = Object.keys(glErrorShadow_);
    576     for (var ii = 0; ii < k.length; ++ii) {
    577       delete glErrorShadow_[k];
    578     }
    579   }
    580 
    581   function loseContextIfTime() {
    582     ++numCalls_;
    583     if (!contextLost_) {
    584       if (numCallsToLoseContext_ == numCalls_) {
    585         canvas.loseContext();
    586       }
    587     }
    588   }
    589 
    590   // Makes a function that simulates WebGL when out of context.
    591   function makeLostContextFunctionWrapper(ctx, functionName) {
    592     var f = ctx[functionName];
    593     return function() {
    594       // log("calling:" + functionName);
    595       // Only call the functions if the context is not lost.
    596       loseContextIfTime();
    597       if (!contextLost_) {
    598         //if (!checkResources(arguments)) {
    599         //  glErrorShadow_[wrappedContext_.INVALID_OPERATION] = true;
    600         //  return;
    601         //}
    602         var result = f.apply(ctx, arguments);
    603         return result;
    604       }
    605     };
    606   }
    607 
    608   function freeResources() {
    609     for (var ii = 0; ii < resourceDb_.length; ++ii) {
    610       var resource = resourceDb_[ii];
    611       if (resource instanceof WebGLBuffer) {
    612         unwrappedContext_.deleteBuffer(resource);
    613       } else if (resource instanceof WebGLFramebuffer) {
    614         unwrappedContext_.deleteFramebuffer(resource);
    615       } else if (resource instanceof WebGLProgram) {
    616         unwrappedContext_.deleteProgram(resource);
    617       } else if (resource instanceof WebGLRenderbuffer) {
    618         unwrappedContext_.deleteRenderbuffer(resource);
    619       } else if (resource instanceof WebGLShader) {
    620         unwrappedContext_.deleteShader(resource);
    621       } else if (resource instanceof WebGLTexture) {
    622         unwrappedContext_.deleteTexture(resource);
    623       }
    624     }
    625   }
    626 
    627   function makeWebGLContextEvent(statusMessage) {
    628     return {
    629       statusMessage: statusMessage,
    630       preventDefault: function() {
    631           canRestore_ = true;
    632         }
    633     };
    634   }
    635 
    636   return canvas;
    637 
    638   function makeLostContextSimulatingContext(ctx) {
    639     // copy all functions and properties to wrapper
    640     for (var propertyName in ctx) {
    641       if (typeof ctx[propertyName] == 'function') {
    642          wrappedContext_[propertyName] = makeLostContextFunctionWrapper(
    643              ctx, propertyName);
    644        } else {
    645          makePropertyWrapper(wrappedContext_, ctx, propertyName);
    646        }
    647     }
    648 
    649     // Wrap a few functions specially.
    650     wrappedContext_.getError = function() {
    651       loseContextIfTime();
    652       if (!contextLost_) {
    653         var err;
    654         while (err = unwrappedContext_.getError()) {
    655           glErrorShadow_[err] = true;
    656         }
    657       }
    658       for (var err in glErrorShadow_) {
    659         if (glErrorShadow_[err]) {
    660           delete glErrorShadow_[err];
    661           return err;
    662         }
    663       }
    664       return wrappedContext_.NO_ERROR;
    665     };
    666 
    667     var creationFunctions = [
    668       "createBuffer",
    669       "createFramebuffer",
    670       "createProgram",
    671       "createRenderbuffer",
    672       "createShader",
    673       "createTexture"
    674     ];
    675     for (var ii = 0; ii < creationFunctions.length; ++ii) {
    676       var functionName = creationFunctions[ii];
    677       wrappedContext_[functionName] = function(f) {
    678         return function() {
    679           loseContextIfTime();
    680           if (contextLost_) {
    681             return null;
    682           }
    683           var obj = f.apply(ctx, arguments);
    684           obj.__webglDebugContextLostId__ = contextId_;
    685           resourceDb_.push(obj);
    686           return obj;
    687         };
    688       }(ctx[functionName]);
    689     }
    690 
    691     var functionsThatShouldReturnNull = [
    692       "getActiveAttrib",
    693       "getActiveUniform",
    694       "getBufferParameter",
    695       "getContextAttributes",
    696       "getAttachedShaders",
    697       "getFramebufferAttachmentParameter",
    698       "getParameter",
    699       "getProgramParameter",
    700       "getProgramInfoLog",
    701       "getRenderbufferParameter",
    702       "getShaderParameter",
    703       "getShaderInfoLog",
    704       "getShaderSource",
    705       "getTexParameter",
    706       "getUniform",
    707       "getUniformLocation",
    708       "getVertexAttrib"
    709     ];
    710     for (var ii = 0; ii < functionsThatShouldReturnNull.length; ++ii) {
    711       var functionName = functionsThatShouldReturnNull[ii];
    712       wrappedContext_[functionName] = function(f) {
    713         return function() {
    714           loseContextIfTime();
    715           if (contextLost_) {
    716             return null;
    717           }
    718           return f.apply(ctx, arguments);
    719         }
    720       }(wrappedContext_[functionName]);
    721     }
    722 
    723     var isFunctions = [
    724       "isBuffer",
    725       "isEnabled",
    726       "isFramebuffer",
    727       "isProgram",
    728       "isRenderbuffer",
    729       "isShader",
    730       "isTexture"
    731     ];
    732     for (var ii = 0; ii < isFunctions.length; ++ii) {
    733       var functionName = isFunctions[ii];
    734       wrappedContext_[functionName] = function(f) {
    735         return function() {
    736           loseContextIfTime();
    737           if (contextLost_) {
    738             return false;
    739           }
    740           return f.apply(ctx, arguments);
    741         }
    742       }(wrappedContext_[functionName]);
    743     }
    744 
    745     wrappedContext_.checkFramebufferStatus = function(f) {
    746       return function() {
    747         loseContextIfTime();
    748         if (contextLost_) {
    749           return wrappedContext_.FRAMEBUFFER_UNSUPPORTED;
    750         }
    751         return f.apply(ctx, arguments);
    752       };
    753     }(wrappedContext_.checkFramebufferStatus);
    754 
    755     wrappedContext_.getAttribLocation = function(f) {
    756       return function() {
    757         loseContextIfTime();
    758         if (contextLost_) {
    759           return -1;
    760         }
    761         return f.apply(ctx, arguments);
    762       };
    763     }(wrappedContext_.getAttribLocation);
    764 
    765     wrappedContext_.getVertexAttribOffset = function(f) {
    766       return function() {
    767         loseContextIfTime();
    768         if (contextLost_) {
    769           return 0;
    770         }
    771         return f.apply(ctx, arguments);
    772       };
    773     }(wrappedContext_.getVertexAttribOffset);
    774 
    775     wrappedContext_.isContextLost = function() {
    776       return contextLost_;
    777     };
    778 
    779     return wrappedContext_;
    780   }
    781 }
    782 
    783 return {
    784     /**
    785      * Initializes this module. Safe to call more than once.
    786      * @param {!WebGLRenderingContext} ctx A WebGL context. If
    787     }
    788    *    you have more than one context it doesn't matter which one
    789    *    you pass in, it is only used to pull out constants.
    790    */
    791   'init': init,
    792 
    793   /**
    794    * Returns true or false if value matches any WebGL enum
    795    * @param {*} value Value to check if it might be an enum.
    796    * @return {boolean} True if value matches one of the WebGL defined enums
    797    */
    798   'mightBeEnum': mightBeEnum,
    799 
    800   /**
    801    * Gets an string version of an WebGL enum.
    802    *
    803    * Example:
    804    *   WebGLDebugUtil.init(ctx);
    805    *   var str = WebGLDebugUtil.glEnumToString(ctx.getError());
    806    *
    807    * @param {number} value Value to return an enum for
    808    * @return {string} The string version of the enum.
    809    */
    810   'glEnumToString': glEnumToString,
    811 
    812   /**
    813    * Converts the argument of a WebGL function to a string.
    814    * Attempts to convert enum arguments to strings.
    815    *
    816    * Example:
    817    *   WebGLDebugUtil.init(ctx);
    818    *   var str = WebGLDebugUtil.glFunctionArgToString('bindTexture', 2, 0, gl.TEXTURE_2D);
    819    *
    820    * would return 'TEXTURE_2D'
    821    *
    822    * @param {string} functionName the name of the WebGL function.
    823    * @param {number} numArgs The number of arguments
    824    * @param {number} argumentIndx the index of the argument.
    825    * @param {*} value The value of the argument.
    826    * @return {string} The value as a string.
    827    */
    828   'glFunctionArgToString': glFunctionArgToString,
    829 
    830   /**
    831    * Converts the arguments of a WebGL function to a string.
    832    * Attempts to convert enum arguments to strings.
    833    *
    834    * @param {string} functionName the name of the WebGL function.
    835    * @param {number} args The arguments.
    836    * @return {string} The arguments as a string.
    837    */
    838   'glFunctionArgsToString': glFunctionArgsToString,
    839 
    840   /**
    841    * Given a WebGL context returns a wrapped context that calls
    842    * gl.getError after every command and calls a function if the
    843    * result is not NO_ERROR.
    844    *
    845    * You can supply your own function if you want. For example, if you'd like
    846    * an exception thrown on any GL error you could do this
    847    *
    848    *    function throwOnGLError(err, funcName, args) {
    849    *      throw WebGLDebugUtils.glEnumToString(err) +
    850    *            " was caused by call to " + funcName;
    851    *    };
    852    *
    853    *    ctx = WebGLDebugUtils.makeDebugContext(
    854    *        canvas.getContext("webgl"), throwOnGLError);
    855    *
    856    * @param {!WebGLRenderingContext} ctx The webgl context to wrap.
    857    * @param {!function(err, funcName, args): void} opt_onErrorFunc The function
    858    *     to call when gl.getError returns an error. If not specified the default
    859    *     function calls console.log with a message.
    860    * @param {!function(funcName, args): void} opt_onFunc The
    861    *     function to call when each webgl function is called. You
    862    *     can use this to log all calls for example.
    863    */
    864   'makeDebugContext': makeDebugContext,
    865 
    866   /**
    867    * Given a canvas element returns a wrapped canvas element that will
    868    * simulate lost context. The canvas returned adds the following functions.
    869    *
    870    * loseContext:
    871    *   simulates a lost context event.
    872    *
    873    * restoreContext:
    874    *   simulates the context being restored.
    875    *
    876    * lostContextInNCalls:
    877    *   loses the context after N gl calls.
    878    *
    879    * getNumCalls:
    880    *   tells you how many gl calls there have been so far.
    881    *
    882    * setRestoreTimeout:
    883    *   sets the number of milliseconds until the context is restored
    884    *   after it has been lost. Defaults to 0. Pass -1 to prevent
    885    *   automatic restoring.
    886    *
    887    * @param {!Canvas} canvas The canvas element to wrap.
    888    */
    889   'makeLostContextSimulatingCanvas': makeLostContextSimulatingCanvas,
    890 
    891   /**
    892    * Resets a context to the initial state.
    893    * @param {!WebGLRenderingContext} ctx The webgl context to
    894    *     reset.
    895    */
    896   'resetToInitialState': resetToInitialState
    897 };
    898 
    899 }();
    900 
    901