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
26WebGLDebugUtils = function() {
27
28/**
29 * Wrapped logging function.
30 * @param {string} msg Message to log.
31 */
32var 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 */
42var 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 */
64var 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 */
153var 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 */
161function 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 */
175function 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 */
186function 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 */
200function 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 */
216function 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 */
243function 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
255function 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.
269function 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 */
294function 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
355function 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
419function 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
783return {
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