Browse Source

separate webgl-debug-helper

Gregg Tavares 6 years ago
parent
commit
b624f07435

+ 3 - 3
threejs/resources/editor-settings.js

@@ -31,9 +31,9 @@ function getPrefix(url) {
  * @returns {string} the source after having urls fixed.
  */
 function fixSourceLinks(url, source) {
-  const srcRE = /(src=)(")(.*?)(")/g;
-  const linkRE = /(href=)(")(.*?)(")/g;
-  const imageSrcRE = /((?:image|img)\.src = )(")(.*?)(")/g;
+  const srcRE = /(src=)(")(.*?)(")()/g;
+  const linkRE = /(href=)(")(.*?)(")()/g;
+  const imageSrcRE = /((?:image|img)\.src = )(")(.*?)(")()/g;
   const loaderLoadRE = /(loader\.load[a-z]*\s*\(\s*)('|")(.*?)('|")/ig;
   const loaderArrayLoadRE = /(loader\.load[a-z]*\(\[)([\s\S]*?)(\])/ig;
   const loadFileRE = /(loadFile\s*\(\s*)('|")(.*?)('|")/ig;

+ 13 - 4
threejs/resources/editor.js

@@ -12,6 +12,7 @@ const {
 } = lessonEditorSettings;
 
 const lessonHelperScriptRE = /<script src="[^"]+lessons-helper\.js"><\/script>/;
+const webglDebugHelperScriptRE = /<script src="[^"]+webgl-debug-helper\.js"><\/script>/;
 
 function getQuery(s) {
   s = s === undefined ? window.location.search : s;
@@ -351,6 +352,7 @@ function makeBlobURLsForSources(scriptInfo) {
       scriptInfo.numLinesBeforeScript = 0;
       if (scriptInfo.isWorker) {
         const extra = `self.lessonSettings = ${JSON.stringify(lessonSettings)};
+importScripts('${dirname(scriptInfo.fqURL)}/resources/webgl-debug-helper.js');
 importScripts('${dirname(scriptInfo.fqURL)}/resources/lessons-worker-helper.js')`;
         scriptInfo.numLinesBeforeScript = extra.split('\n').length;
         text = `${extra}\n${text}`;
@@ -376,7 +378,8 @@ function getSourceBlob(htmlParts) {
   <link rel="stylesheet" href="${prefix}/resources/lesson-helper.css" type="text/css">
   <script match="false">self.lessonSettings = ${JSON.stringify(lessonSettings)}</script>`);
 
-  source = source.replace('</head>', `<script src="${prefix}/resources/lessons-helper.js"></script>
+  source = source.replace('</head>', `<script src="${prefix}/resources/webgl-debug-helper.js"></script>
+<script src="${prefix}/resources/lessons-helper.js"></script>
   </head>`);
   const scriptNdx = source.indexOf('<script>');
   g.rootScriptInfo.numLinesBeforeScript = (source.substring(0, scriptNdx).match(/\n/g) || []).length;
@@ -423,7 +426,7 @@ function getSourceBlobFromOrig() {
 
 function dirname(path) {
   const ndx = path.lastIndexOf('/');
-  return path.substring(0, ndx + 1);
+  return path.substring(0, ndx);
 }
 
 function basename(path) {
@@ -503,6 +506,12 @@ function getWorkerBlob() {
   };
 }
 
+function fixHTMLForCodeSite(html) {
+  html = html.replace(lessonHelperScriptRE, '');
+  html = html.replace(webglDebugHelperScriptRE, '');
+  return html;
+}
+
 function openInCodepen() {
   const comment = `// ${g.title}
 // from ${g.url}
@@ -516,7 +525,7 @@ function openInCodepen() {
     description           : 'from: ' + g.url,
     tags                  : lessonEditorSettings.tags,
     editors               : '101',
-    html                  : scripts.html + htmlParts.html.sources[0].source.replace(lessonHelperScriptRE, ''),
+    html                  : scripts.html + fixHTMLForCodeSite(htmlParts.html.sources[0].source),
     css                   : htmlParts.css.sources[0].source,
     js                    : comment + fixJSForCodeSite(scripts.js),
   };
@@ -554,7 +563,7 @@ function openInJSFiddle() {
       <input type="submit" />
     </form>
   `;
-  elem.querySelector('input[name=html]').value = scripts.html + htmlParts.html.sources[0].source.replace(lessonHelperScriptRE, '');
+  elem.querySelector('input[name=html]').value = scripts.html + fixHTMLForCodeSite(htmlParts.html.sources[0].source);
   elem.querySelector('input[name=css]').value = htmlParts.css.sources[0].source;
   elem.querySelector('input[name=js]').value = comment + fixJSForCodeSite(scripts.js);
   elem.querySelector('input[name=title]').value = g.title;

+ 23 - 537
threejs/resources/lessons-helper.js

@@ -93,21 +93,21 @@
       const temp = doc.createElement('div');
       temp.innerHTML = `
         <div style="
-           position: absolute;
-           left: 0;
-           top: 0;
-           background-color: #DEF;
-           width: 100vw;
-           height: 100vh;
-           display: flex;
-           flex-flow: column;
-           justify-content: center;
-           align-content: center;
-           align-items: center;
+          position: absolute;
+          left: 0;
+          top: 0;
+          background-color: #DEF;
+          width: 100vw;
+          height: 100vh;
+          display: flex;
+          flex-flow: column;
+          justify-content: center;
+          align-content: center;
+          align-items: center;
         ">
           <div style="text-align: center;">
-             It doesn't appear your browser supports WebGL.<br/>
-             <a href="http://get.webgl.org" target="_blank">Click here for more information.</a>
+            It doesn't appear your browser supports WebGL.<br/>
+            <a href="http://get.webgl.org" target="_blank">Click here for more information.</a>
           </div>
         </div>
       `;
@@ -468,528 +468,6 @@
 
   updateCSSIfInIFrame();
 
-  //------------ [ from https://github.com/KhronosGroup/WebGLDeveloperTools ]
-
-  /*
-  ** Copyright (c) 2012 The Khronos Group Inc.
-  **
-  ** Permission is hereby granted, free of charge, to any person obtaining a
-  ** copy of this software and/or associated documentation files (the
-  ** "Materials"), to deal in the Materials without restriction, including
-  ** without limitation the rights to use, copy, modify, merge, publish,
-  ** distribute, sublicense, and/or sell copies of the Materials, and to
-  ** permit persons to whom the Materials are furnished to do so, subject to
-  ** the following conditions:
-  **
-  ** The above copyright notice and this permission notice shall be included
-  ** in all copies or substantial portions of the Materials.
-  **
-  ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-  ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-  ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-  ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-  ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-  ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-  ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
-  */
-
-  /**
-   * Types of contexts we have added to map
-   */
-  const mappedContextTypes = {};
-
-  /**
-   * Map of numbers to names.
-   * @type {Object}
-   */
-  const glEnums = {};
-
-  /**
-   * Map of names to numbers.
-   * @type {Object}
-   */
-  const enumStringToValue = {};
-
-  /**
-   * Initializes this module. Safe to call more than once.
-   * @param {!WebGLRenderingContext} ctx A WebGL context. If
-   *    you have more than one context it doesn't matter which one
-   *    you pass in, it is only used to pull out constants.
-   */
-  function addEnumsForContext(ctx, type) {
-    if (!mappedContextTypes[type]) {
-      mappedContextTypes[type] = true;
-      for (const propertyName in ctx) {
-        if (typeof ctx[propertyName] === 'number') {
-          glEnums[ctx[propertyName]] = propertyName;
-          enumStringToValue[propertyName] = ctx[propertyName];
-        }
-      }
-    }
-  }
-
-  function enumArrayToString(enums) {
-    const enumStrings = [];
-    if (enums.length) {
-      for (let i = 0; i < enums.length; ++i) {
-        enums.push(glEnumToString(enums[i]));  // eslint-disable-line
-      }
-      return '[' + enumStrings.join(', ') + ']';
-    }
-    return enumStrings.toString();
-  }
-
-  function makeBitFieldToStringFunc(enums) {
-    return function(value) {
-      let orResult = 0;
-      const orEnums = [];
-      for (let i = 0; i < enums.length; ++i) {
-        const enumValue = enumStringToValue[enums[i]];
-        if ((value & enumValue) !== 0) {
-          orResult |= enumValue;
-          orEnums.push(glEnumToString(enumValue));  // eslint-disable-line
-        }
-      }
-      if (orResult === value) {
-        return orEnums.join(' | ');
-      } else {
-        return glEnumToString(value);  // eslint-disable-line
-      }
-    };
-  }
-
-  const destBufferBitFieldToString = makeBitFieldToStringFunc([
-    'COLOR_BUFFER_BIT',
-    'DEPTH_BUFFER_BIT',
-    'STENCIL_BUFFER_BIT',
-  ]);
-
-  /**
-   * Which arguments are enums based on the number of arguments to the function.
-   * So
-   *    'texImage2D': {
-   *       9: { 0:true, 2:true, 6:true, 7:true },
-   *       6: { 0:true, 2:true, 3:true, 4:true },
-   *    },
-   *
-   * means if there are 9 arguments then 6 and 7 are enums, if there are 6
-   * arguments 3 and 4 are enums. Maybe a function as well in which case
-   * value is passed to function and returns a string
-   *
-   * @type {!Object.<number, (!Object.<number, string>|function)}
-   */
-  const glValidEnumContexts = {
-    // Generic setters and getters
-
-    'enable': {1: { 0:true }},
-    'disable': {1: { 0:true }},
-    'getParameter': {1: { 0:true }},
-
-    // Rendering
-
-    'drawArrays': {3:{ 0:true }},
-    'drawElements': {4:{ 0:true, 2:true }},
-    'drawArraysInstanced': {4: { 0:true }},
-    'drawElementsInstanced': {5: {0:true, 2: true }},
-    'drawRangeElements': {6: {0:true, 4: true }},
-
-    // Shaders
-
-    'createShader': {1: { 0:true }},
-    'getShaderParameter': {2: { 1:true }},
-    'getProgramParameter': {2: { 1:true }},
-    'getShaderPrecisionFormat': {2: { 0: true, 1:true }},
-
-    // Vertex attributes
-
-    'getVertexAttrib': {2: { 1:true }},
-    'vertexAttribPointer': {6: { 2:true }},
-    'vertexAttribIPointer': {5: { 2:true }},  // WebGL2
-
-    // Textures
-
-    'bindTexture': {2: { 0:true }},
-    'activeTexture': {1: { 0:true }},
-    'getTexParameter': {2: { 0:true, 1:true }},
-    'texParameterf': {3: { 0:true, 1:true }},
-    'texParameteri': {3: { 0:true, 1:true, 2:true }},
-    'texImage2D': {
-       9: { 0:true, 2:true, 6:true, 7:true },
-       6: { 0:true, 2:true, 3:true, 4:true },
-       10: { 0:true, 2:true, 6:true, 7:true },  // WebGL2
-    },
-    'texImage3D': {
-      10: { 0:true, 2:true, 7:true, 8:true },  // WebGL2
-      11: { 0:true, 2:true, 7:true, 8:true },  // WebGL2
-    },
-    'texSubImage2D': {
-      9: { 0:true, 6:true, 7:true },
-      7: { 0:true, 4:true, 5:true },
-      10: { 0:true, 6:true, 7:true },  // WebGL2
-    },
-    'texSubImage3D': {
-      11: { 0:true, 8:true, 9:true },  // WebGL2
-      12: { 0:true, 8:true, 9:true },  // WebGL2
-    },
-    'texStorage2D': { 5: { 0:true, 2:true }},  // WebGL2
-    'texStorage3D': { 6: { 0:true, 2:true }},  // WebGL2
-    'copyTexImage2D': {8: { 0:true, 2:true }},
-    'copyTexSubImage2D': {8: { 0:true }},
-    'copyTexSubImage3D': {9: { 0:true }},  // WebGL2
-    'generateMipmap': {1: { 0:true }},
-    'compressedTexImage2D': {
-      7: { 0: true, 2:true },
-      8: { 0: true, 2:true },  // WebGL2
-    },
-    'compressedTexSubImage2D': {
-      8: { 0: true, 6:true },
-      9: { 0: true, 6:true },  // WebGL2
-    },
-    'compressedTexImage3D': {
-      8: { 0: true, 2: true, },  // WebGL2
-      9: { 0: true, 2: true, },  // WebGL2
-    },
-    'compressedTexSubImage3D': {
-      9: { 0: true, 8: true, },  // WebGL2
-      10: { 0: true, 8: true, },  // WebGL2
-    },
-
-    // Buffer objects
-
-    'bindBuffer': {2: { 0:true }},
-    'bufferData': {
-      3: { 0:true, 2:true },
-      4: { 0:true, 2:true },  // WebGL2
-      5: { 0:true, 2:true },  // WebGL2
-    },
-    'bufferSubData': {
-      3: { 0:true },
-      4: { 0:true },  // WebGL2
-      5: { 0:true },  // WebGL2
-    },
-    'copyBufferSubData': {
-      5: { 0:true },  // WeBGL2
-    },
-    'getBufferParameter': {2: { 0:true, 1:true }},
-    'getBufferSubData': {
-      3: { 0: true, },  // WebGL2
-      4: { 0: true, },  // WebGL2
-      5: { 0: true, },  // WebGL2
-    },
-
-    // Renderbuffers and framebuffers
-
-    'pixelStorei': {2: { 0:true, 1:true }},
-    'readPixels': {
-      7: { 4:true, 5:true },
-      8: { 4:true, 5:true },  // WebGL2
-    },
-    'bindRenderbuffer': {2: { 0:true }},
-    'bindFramebuffer': {2: { 0:true }},
-    'blitFramebuffer': {10: { 8: destBufferBitFieldToString, 9:true }},  // WebGL2
-    'checkFramebufferStatus': {1: { 0:true }},
-    'framebufferRenderbuffer': {4: { 0:true, 1:true, 2:true }},
-    'framebufferTexture2D': {5: { 0:true, 1:true, 2:true }},
-    'framebufferTextureLayer': {5: {0:true, 1:true }},  // WebGL2
-    'getFramebufferAttachmentParameter': {3: { 0:true, 1:true, 2:true }},
-    'getInternalformatParameter': {3: {0:true, 1:true, 2:true }},  // WebGL2
-    'getRenderbufferParameter': {2: { 0:true, 1:true }},
-    'invalidateFramebuffer': {2: { 0:true, 1: enumArrayToString, }},  // WebGL2
-    'invalidateSubFramebuffer': {6: {0: true, 1: enumArrayToString, }},  // WebGL2
-    'readBuffer': {1: {0: true}},  // WebGL2
-    'renderbufferStorage': {4: { 0:true, 1:true }},
-    'renderbufferStorageMultisample': {5: { 0: true, 2: true }},  // WebGL2
-
-    // Frame buffer operations (clear, blend, depth test, stencil)
-
-    'clear': {1: { 0: destBufferBitFieldToString }},
-    'depthFunc': {1: { 0:true }},
-    'blendFunc': {2: { 0:true, 1:true }},
-    'blendFuncSeparate': {4: { 0:true, 1:true, 2:true, 3:true }},
-    'blendEquation': {1: { 0:true }},
-    'blendEquationSeparate': {2: { 0:true, 1:true }},
-    'stencilFunc': {3: { 0:true }},
-    'stencilFuncSeparate': {4: { 0:true, 1:true }},
-    'stencilMaskSeparate': {2: { 0:true }},
-    'stencilOp': {3: { 0:true, 1:true, 2:true }},
-    'stencilOpSeparate': {4: { 0:true, 1:true, 2:true, 3:true }},
-
-    // Culling
-
-    'cullFace': {1: { 0:true }},
-    'frontFace': {1: { 0:true }},
-
-    // ANGLE_instanced_arrays extension
-
-    'drawArraysInstancedANGLE': {4: { 0:true }},
-    'drawElementsInstancedANGLE': {5: { 0:true, 2:true }},
-
-    // EXT_blend_minmax extension
-
-    'blendEquationEXT': {1: { 0:true }},
-
-    // Multiple Render Targets
-
-    'drawBuffersWebGL': {1: {0: enumArrayToString, }},  // WEBGL_draw_bufers
-    'drawBuffers': {1: {0: enumArrayToString, }},  // WebGL2
-    'clearBufferfv': {
-      4: {0: true },  // WebGL2
-      5: {0: true },  // WebGL2
-    },
-    'clearBufferiv': {
-      4: {0: true },  // WebGL2
-      5: {0: true },  // WebGL2
-    },
-    'clearBufferuiv': {
-      4: {0: true },  // WebGL2
-      5: {0: true },  // WebGL2
-    },
-    'clearBufferfi': { 4: {0: true}},  // WebGL2
-
-    // QueryObjects
-
-    'beginQuery': { 2: { 0: true }},  // WebGL2
-    'endQuery': { 1: { 0: true }},  // WebGL2
-    'getQuery': { 2: { 0: true, 1: true }},  // WebGL2
-    'getQueryParameter': { 2: { 1: true }},  // WebGL2
-
-    //  Sampler Objects
-
-    'samplerParameteri': { 3: { 1: true }},  // WebGL2
-    'samplerParameterf': { 3: { 1: true }},  // WebGL2
-    'getSamplerParameter': { 2: { 1: true }},  // WebGL2
-
-    //  Sync objects
-
-    'clientWaitSync': { 3: { 1: makeBitFieldToStringFunc(['SYNC_FLUSH_COMMANDS_BIT']) }},  // WebGL2
-    'fenceSync': { 2: { 0: true }},  // WebGL2
-    'getSyncParameter': { 2: { 1: true }},  // WebGL2
-
-    //  Transform Feedback
-
-    'bindTransformFeedback': { 2: { 0: true }},  // WebGL2
-    'beginTransformFeedback': { 1: { 0: true }},  // WebGL2
-
-    // Uniform Buffer Objects and Transform Feedback Buffers
-    'bindBufferBase': { 3: { 0: true }},  // WebGL2
-    'bindBufferRange': { 5: { 0: true }},  // WebGL2
-    'getIndexedParameter': { 2: { 0: true }},  // WebGL2
-    'getActiveUniforms': { 3: { 2: true }},  // WebGL2
-    'getActiveUniformBlockParameter': { 3: { 2: true }},  // WebGL2
-  };
-
-  /**
-   * Gets an string version of an WebGL enum.
-   *
-   * Example:
-   *   var str = WebGLDebugUtil.glEnumToString(ctx.getError());
-   *
-   * @param {number} value Value to return an enum for
-   * @return {string} The string version of the enum.
-   */
-  function glEnumToString(value) {
-    const name = glEnums[value];
-    return (name !== undefined)
-        ? `gl.${name}`
-        : `/*UNKNOWN WebGL ENUM*/ 0x${value.toString(16)}`;
-  }
-
-  /**
-   * Returns the string version of a WebGL argument.
-   * Attempts to convert enum arguments to strings.
-   * @param {string} functionName the name of the WebGL function.
-   * @param {number} numArgs the number of arguments passed to the function.
-   * @param {number} argumentIndx the index of the argument.
-   * @param {*} value The value of the argument.
-   * @return {string} The value as a string.
-   */
-  function glFunctionArgToString(functionName, numArgs, argumentIndex, value) {
-    const funcInfos = glValidEnumContexts[functionName];
-    if (funcInfos !== undefined) {
-      const funcInfo = funcInfos[numArgs];
-      if (funcInfo !== undefined) {
-        const argType = funcInfo[argumentIndex];
-        if (argType) {
-          if (typeof argType === 'function') {
-            return argType(value);
-          } else {
-            return glEnumToString(value);
-          }
-        }
-      }
-    }
-    if (value === null) {
-      return 'null';
-    } else if (value === undefined) {
-      return 'undefined';
-    } else {
-      return value.toString();
-    }
-  }
-
-  /**
-   * Converts the arguments of a WebGL function to a string.
-   * Attempts to convert enum arguments to strings.
-   *
-   * @param {string} functionName the name of the WebGL function.
-   * @param {number} args The arguments.
-   * @return {string} The arguments as a string.
-   */
-  function glFunctionArgsToString(functionName, args) {
-    // apparently we can't do args.join(",");
-    const argStrs = [];
-    const numArgs = args.length;
-    for (let ii = 0; ii < numArgs; ++ii) {
-      argStrs.push(glFunctionArgToString(functionName, numArgs, ii, args[ii]));
-    }
-    return argStrs.join(', ');
-  }
-
-  function makePropertyWrapper(wrapper, original, propertyName) {
-    wrapper.__defineGetter__(propertyName, function() {  // eslint-disable-line
-      return original[propertyName];
-    });
-    // TODO(gmane): this needs to handle properties that take more than
-    // one value?
-    wrapper.__defineSetter__(propertyName, function(value) {  // eslint-disable-line
-      original[propertyName] = value;
-    });
-  }
-
-  /**
-   * Given a WebGL context returns a wrapped context that calls
-   * gl.getError after every command and calls a function if the
-   * result is not gl.NO_ERROR.
-   *
-   * @param {!WebGLRenderingContext} ctx The webgl context to
-   *        wrap.
-   * @param {!function(err, funcName, args): void} opt_onErrorFunc
-   *        The function to call when gl.getError returns an
-   *        error. If not specified the default function calls
-   *        console.log with a message.
-   * @param {!function(funcName, args): void} opt_onFunc The
-   *        function to call when each webgl function is called.
-   *        You can use this to log all calls for example.
-   * @param {!WebGLRenderingContext} opt_err_ctx The webgl context
-   *        to call getError on if different than ctx.
-   */
-  function makeDebugContext(ctx, options) {
-    options = options || {};
-    const errCtx = options.errCtx || ctx;
-    const onFunc = options.funcFunc;
-    const sharedState = options.sharedState || {
-      numDrawCallsRemaining: options.maxDrawCalls || -1,
-      wrappers: {},
-    };
-    options.sharedState = sharedState;
-
-    const errorFunc = options.errorFunc || function(err, functionName, args) {
-      console.error(`WebGL error ${glEnumToString(err)} in ${functionName}(${glFunctionArgsToString(functionName, args)})`);  /* eslint-disable-line no-console */
-    };
-
-    // Holds booleans for each GL error so after we get the error ourselves
-    // we can still return it to the client app.
-    const glErrorShadow = { };
-    const wrapper = {};
-
-    function removeChecks() {
-      Object.keys(sharedState.wrappers).forEach(function(name) {
-        const pair = sharedState.wrappers[name];
-        const wrapper = pair.wrapper;
-        const orig = pair.orig;
-        for (const propertyName in wrapper) {
-          if (typeof wrapper[propertyName] === 'function') {
-            wrapper[propertyName] = orig[propertyName].bind(orig);
-          }
-        }
-      });
-    }
-
-    function checkMaxDrawCalls() {
-      if (sharedState.numDrawCallsRemaining === 0) {
-        removeChecks();
-      }
-      --sharedState.numDrawCallsRemaining;
-    }
-
-    function noop() {
-    }
-
-    // Makes a function that calls a WebGL function and then calls getError.
-    function makeErrorWrapper(ctx, functionName) {
-      const check = functionName.substring(0, 4) === 'draw' ? checkMaxDrawCalls : noop;
-      return function() {
-        if (onFunc) {
-          onFunc(functionName, arguments);
-        }
-        const result = ctx[functionName].apply(ctx, arguments);
-        const err = errCtx.getError();
-        if (err !== 0) {
-          glErrorShadow[err] = true;
-          errorFunc(err, functionName, arguments);
-        }
-        check();
-        return result;
-      };
-    }
-
-    function makeGetExtensionWrapper(ctx, wrapped) {
-      return function() {
-        const extensionName = arguments[0];
-        let ext = sharedState.wrappers[extensionName];
-        if (!ext) {
-          ext = wrapped.apply(ctx, arguments);
-          if (ext) {
-            const origExt = ext;
-            ext = makeDebugContext(ext, Object.assign({}, options, {errCtx: ctx}));
-            sharedState.wrappers[extensionName] = { wrapper: ext, orig: origExt };
-            addEnumsForContext(origExt, extensionName);
-          }
-        }
-        return ext;
-      };
-    }
-
-    // Make a an object that has a copy of every property of the WebGL context
-    // but wraps all functions.
-    for (const propertyName in ctx) {
-      if (typeof ctx[propertyName] === 'function') {
-        if (propertyName !== 'getExtension') {
-          wrapper[propertyName] = makeErrorWrapper(ctx, propertyName);
-        } else {
-          const wrapped = makeErrorWrapper(ctx, propertyName);
-          wrapper[propertyName] = makeGetExtensionWrapper(ctx, wrapped);
-        }
-      } else {
-        makePropertyWrapper(wrapper, ctx, propertyName);
-      }
-    }
-
-    // Override the getError function with one that returns our saved results.
-    if (wrapper.getError) {
-      wrapper.getError = function() {
-        for (const err in glErrorShadow) {
-          if (glErrorShadow.hasOwnProperty(err)) {
-            if (glErrorShadow[err]) {
-              glErrorShadow[err] = false;
-              return err;
-            }
-          }
-        }
-        return ctx.NO_ERROR;
-      };
-    }
-
-    if (wrapper.bindBuffer) {
-      sharedState.wrappers['webgl'] = { wrapper: wrapper, orig: ctx };
-      addEnumsForContext(ctx, ctx.bindBufferBase ? 'WebGL2' : 'WebGL');
-    }
-
-    return wrapper;
-  }
-
-  //------------
-
   function captureJSErrors() {
     // capture JavaScript Errors
     window.addEventListener('error', function(e) {
@@ -1056,6 +534,16 @@
   }
 
   function installWebGLDebugContextCreator() {
+    if (!self.webglDebugHelper) {
+      return;
+    }
+
+    const {
+      makeDebugContext,
+      glFunctionArgToString,
+      glEnumToString,
+    } = self.webglDebugHelper;
+
     // capture GL errors
     HTMLCanvasElement.prototype.getContext = (function(oldFn) {
       return function() {
@@ -1104,8 +592,6 @@
   return {
     setupLesson: setupLesson,
     showNeedWebGL: showNeedWebGL,
-    makeDebugContext: makeDebugContext,
-    glFunctionArgsToString: glFunctionArgsToString,
   };
 
 }));

+ 11 - 522
threejs/resources/lessons-worker-helper.js

@@ -30,6 +30,7 @@
  */
 
 /* global */
+
 'use strict';
 
 (function() {
@@ -90,528 +91,6 @@
 
   };
 
-
-  //------------ [ from https://github.com/KhronosGroup/WebGLDeveloperTools ]
-
-  /*
-  ** Copyright (c) 2012 The Khronos Group Inc.
-  **
-  ** Permission is hereby granted, free of charge, to any person obtaining a
-  ** copy of this software and/or associated documentation files (the
-  ** "Materials"), to deal in the Materials without restriction, including
-  ** without limitation the rights to use, copy, modify, merge, publish,
-  ** distribute, sublicense, and/or sell copies of the Materials, and to
-  ** permit persons to whom the Materials are furnished to do so, subject to
-  ** the following conditions:
-  **
-  ** The above copyright notice and this permission notice shall be included
-  ** in all copies or substantial portions of the Materials.
-  **
-  ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-  ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-  ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-  ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-  ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-  ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-  ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
-  */
-
-  /**
-   * Types of contexts we have added to map
-   */
-  const mappedContextTypes = {};
-
-  /**
-   * Map of numbers to names.
-   * @type {Object}
-   */
-  const glEnums = {};
-
-  /**
-   * Map of names to numbers.
-   * @type {Object}
-   */
-  const enumStringToValue = {};
-
-  /**
-   * Initializes this module. Safe to call more than once.
-   * @param {!WebGLRenderingContext} ctx A WebGL context. If
-   *    you have more than one context it doesn't matter which one
-   *    you pass in, it is only used to pull out constants.
-   */
-  function addEnumsForContext(ctx, type) {
-    if (!mappedContextTypes[type]) {
-      mappedContextTypes[type] = true;
-      for (const propertyName in ctx) {
-        if (typeof ctx[propertyName] === 'number') {
-          glEnums[ctx[propertyName]] = propertyName;
-          enumStringToValue[propertyName] = ctx[propertyName];
-        }
-      }
-    }
-  }
-
-  function enumArrayToString(enums) {
-    const enumStrings = [];
-    if (enums.length) {
-      for (let i = 0; i < enums.length; ++i) {
-        enums.push(glEnumToString(enums[i]));  // eslint-disable-line
-      }
-      return '[' + enumStrings.join(', ') + ']';
-    }
-    return enumStrings.toString();
-  }
-
-  function makeBitFieldToStringFunc(enums) {
-    return function(value) {
-      let orResult = 0;
-      const orEnums = [];
-      for (let i = 0; i < enums.length; ++i) {
-        const enumValue = enumStringToValue[enums[i]];
-        if ((value & enumValue) !== 0) {
-          orResult |= enumValue;
-          orEnums.push(glEnumToString(enumValue));  // eslint-disable-line
-        }
-      }
-      if (orResult === value) {
-        return orEnums.join(' | ');
-      } else {
-        return glEnumToString(value);  // eslint-disable-line
-      }
-    };
-  }
-
-  const destBufferBitFieldToString = makeBitFieldToStringFunc([
-    'COLOR_BUFFER_BIT',
-    'DEPTH_BUFFER_BIT',
-    'STENCIL_BUFFER_BIT',
-  ]);
-
-  /**
-   * Which arguments are enums based on the number of arguments to the function.
-   * So
-   *    'texImage2D': {
-   *       9: { 0:true, 2:true, 6:true, 7:true },
-   *       6: { 0:true, 2:true, 3:true, 4:true },
-   *    },
-   *
-   * means if there are 9 arguments then 6 and 7 are enums, if there are 6
-   * arguments 3 and 4 are enums. Maybe a function as well in which case
-   * value is passed to function and returns a string
-   *
-   * @type {!Object.<number, (!Object.<number, string>|function)}
-   */
-  const glValidEnumContexts = {
-    // Generic setters and getters
-
-    'enable': {1: { 0:true }},
-    'disable': {1: { 0:true }},
-    'getParameter': {1: { 0:true }},
-
-    // Rendering
-
-    'drawArrays': {3:{ 0:true }},
-    'drawElements': {4:{ 0:true, 2:true }},
-    'drawArraysInstanced': {4: { 0:true }},
-    'drawElementsInstanced': {5: {0:true, 2: true }},
-    'drawRangeElements': {6: {0:true, 4: true }},
-
-    // Shaders
-
-    'createShader': {1: { 0:true }},
-    'getShaderParameter': {2: { 1:true }},
-    'getProgramParameter': {2: { 1:true }},
-    'getShaderPrecisionFormat': {2: { 0: true, 1:true }},
-
-    // Vertex attributes
-
-    'getVertexAttrib': {2: { 1:true }},
-    'vertexAttribPointer': {6: { 2:true }},
-    'vertexAttribIPointer': {5: { 2:true }},  // WebGL2
-
-    // Textures
-
-    'bindTexture': {2: { 0:true }},
-    'activeTexture': {1: { 0:true }},
-    'getTexParameter': {2: { 0:true, 1:true }},
-    'texParameterf': {3: { 0:true, 1:true }},
-    'texParameteri': {3: { 0:true, 1:true, 2:true }},
-    'texImage2D': {
-       9: { 0:true, 2:true, 6:true, 7:true },
-       6: { 0:true, 2:true, 3:true, 4:true },
-       10: { 0:true, 2:true, 6:true, 7:true },  // WebGL2
-    },
-    'texImage3D': {
-      10: { 0:true, 2:true, 7:true, 8:true },  // WebGL2
-      11: { 0:true, 2:true, 7:true, 8:true },  // WebGL2
-    },
-    'texSubImage2D': {
-      9: { 0:true, 6:true, 7:true },
-      7: { 0:true, 4:true, 5:true },
-      10: { 0:true, 6:true, 7:true },  // WebGL2
-    },
-    'texSubImage3D': {
-      11: { 0:true, 8:true, 9:true },  // WebGL2
-      12: { 0:true, 8:true, 9:true },  // WebGL2
-    },
-    'texStorage2D': { 5: { 0:true, 2:true }},  // WebGL2
-    'texStorage3D': { 6: { 0:true, 2:true }},  // WebGL2
-    'copyTexImage2D': {8: { 0:true, 2:true }},
-    'copyTexSubImage2D': {8: { 0:true }},
-    'copyTexSubImage3D': {9: { 0:true }},  // WebGL2
-    'generateMipmap': {1: { 0:true }},
-    'compressedTexImage2D': {
-      7: { 0: true, 2:true },
-      8: { 0: true, 2:true },  // WebGL2
-    },
-    'compressedTexSubImage2D': {
-      8: { 0: true, 6:true },
-      9: { 0: true, 6:true },  // WebGL2
-    },
-    'compressedTexImage3D': {
-      8: { 0: true, 2: true, },  // WebGL2
-      9: { 0: true, 2: true, },  // WebGL2
-    },
-    'compressedTexSubImage3D': {
-      9: { 0: true, 8: true, },  // WebGL2
-      10: { 0: true, 8: true, },  // WebGL2
-    },
-
-    // Buffer objects
-
-    'bindBuffer': {2: { 0:true }},
-    'bufferData': {
-      3: { 0:true, 2:true },
-      4: { 0:true, 2:true },  // WebGL2
-      5: { 0:true, 2:true },  // WebGL2
-    },
-    'bufferSubData': {
-      3: { 0:true },
-      4: { 0:true },  // WebGL2
-      5: { 0:true },  // WebGL2
-    },
-    'copyBufferSubData': {
-      5: { 0:true },  // WeBGL2
-    },
-    'getBufferParameter': {2: { 0:true, 1:true }},
-    'getBufferSubData': {
-      3: { 0: true, },  // WebGL2
-      4: { 0: true, },  // WebGL2
-      5: { 0: true, },  // WebGL2
-    },
-
-    // Renderbuffers and framebuffers
-
-    'pixelStorei': {2: { 0:true, 1:true }},
-    'readPixels': {
-      7: { 4:true, 5:true },
-      8: { 4:true, 5:true },  // WebGL2
-    },
-    'bindRenderbuffer': {2: { 0:true }},
-    'bindFramebuffer': {2: { 0:true }},
-    'blitFramebuffer': {10: { 8: destBufferBitFieldToString, 9:true }},  // WebGL2
-    'checkFramebufferStatus': {1: { 0:true }},
-    'framebufferRenderbuffer': {4: { 0:true, 1:true, 2:true }},
-    'framebufferTexture2D': {5: { 0:true, 1:true, 2:true }},
-    'framebufferTextureLayer': {5: {0:true, 1:true }},  // WebGL2
-    'getFramebufferAttachmentParameter': {3: { 0:true, 1:true, 2:true }},
-    'getInternalformatParameter': {3: {0:true, 1:true, 2:true }},  // WebGL2
-    'getRenderbufferParameter': {2: { 0:true, 1:true }},
-    'invalidateFramebuffer': {2: { 0:true, 1: enumArrayToString, }},  // WebGL2
-    'invalidateSubFramebuffer': {6: {0: true, 1: enumArrayToString, }},  // WebGL2
-    'readBuffer': {1: {0: true}},  // WebGL2
-    'renderbufferStorage': {4: { 0:true, 1:true }},
-    'renderbufferStorageMultisample': {5: { 0: true, 2: true }},  // WebGL2
-
-    // Frame buffer operations (clear, blend, depth test, stencil)
-
-    'clear': {1: { 0: destBufferBitFieldToString }},
-    'depthFunc': {1: { 0:true }},
-    'blendFunc': {2: { 0:true, 1:true }},
-    'blendFuncSeparate': {4: { 0:true, 1:true, 2:true, 3:true }},
-    'blendEquation': {1: { 0:true }},
-    'blendEquationSeparate': {2: { 0:true, 1:true }},
-    'stencilFunc': {3: { 0:true }},
-    'stencilFuncSeparate': {4: { 0:true, 1:true }},
-    'stencilMaskSeparate': {2: { 0:true }},
-    'stencilOp': {3: { 0:true, 1:true, 2:true }},
-    'stencilOpSeparate': {4: { 0:true, 1:true, 2:true, 3:true }},
-
-    // Culling
-
-    'cullFace': {1: { 0:true }},
-    'frontFace': {1: { 0:true }},
-
-    // ANGLE_instanced_arrays extension
-
-    'drawArraysInstancedANGLE': {4: { 0:true }},
-    'drawElementsInstancedANGLE': {5: { 0:true, 2:true }},
-
-    // EXT_blend_minmax extension
-
-    'blendEquationEXT': {1: { 0:true }},
-
-    // Multiple Render Targets
-
-    'drawBuffersWebGL': {1: {0: enumArrayToString, }},  // WEBGL_draw_bufers
-    'drawBuffers': {1: {0: enumArrayToString, }},  // WebGL2
-    'clearBufferfv': {
-      4: {0: true },  // WebGL2
-      5: {0: true },  // WebGL2
-    },
-    'clearBufferiv': {
-      4: {0: true },  // WebGL2
-      5: {0: true },  // WebGL2
-    },
-    'clearBufferuiv': {
-      4: {0: true },  // WebGL2
-      5: {0: true },  // WebGL2
-    },
-    'clearBufferfi': { 4: {0: true}},  // WebGL2
-
-    // QueryObjects
-
-    'beginQuery': { 2: { 0: true }},  // WebGL2
-    'endQuery': { 1: { 0: true }},  // WebGL2
-    'getQuery': { 2: { 0: true, 1: true }},  // WebGL2
-    'getQueryParameter': { 2: { 1: true }},  // WebGL2
-
-    //  Sampler Objects
-
-    'samplerParameteri': { 3: { 1: true }},  // WebGL2
-    'samplerParameterf': { 3: { 1: true }},  // WebGL2
-    'getSamplerParameter': { 2: { 1: true }},  // WebGL2
-
-    //  Sync objects
-
-    'clientWaitSync': { 3: { 1: makeBitFieldToStringFunc(['SYNC_FLUSH_COMMANDS_BIT']) }},  // WebGL2
-    'fenceSync': { 2: { 0: true }},  // WebGL2
-    'getSyncParameter': { 2: { 1: true }},  // WebGL2
-
-    //  Transform Feedback
-
-    'bindTransformFeedback': { 2: { 0: true }},  // WebGL2
-    'beginTransformFeedback': { 1: { 0: true }},  // WebGL2
-
-    // Uniform Buffer Objects and Transform Feedback Buffers
-    'bindBufferBase': { 3: { 0: true }},  // WebGL2
-    'bindBufferRange': { 5: { 0: true }},  // WebGL2
-    'getIndexedParameter': { 2: { 0: true }},  // WebGL2
-    'getActiveUniforms': { 3: { 2: true }},  // WebGL2
-    'getActiveUniformBlockParameter': { 3: { 2: true }},  // WebGL2
-  };
-
-  /**
-   * Gets an string version of an WebGL enum.
-   *
-   * Example:
-   *   var str = WebGLDebugUtil.glEnumToString(ctx.getError());
-   *
-   * @param {number} value Value to return an enum for
-   * @return {string} The string version of the enum.
-   */
-  function glEnumToString(value) {
-    const name = glEnums[value];
-    return (name !== undefined) ? ('gl.' + name) :
-        ('/*UNKNOWN WebGL ENUM*/ 0x' + value.toString(16) + '');
-  }
-
-  /**
-   * Returns the string version of a WebGL argument.
-   * Attempts to convert enum arguments to strings.
-   * @param {string} functionName the name of the WebGL function.
-   * @param {number} numArgs the number of arguments passed to the function.
-   * @param {number} argumentIndx the index of the argument.
-   * @param {*} value The value of the argument.
-   * @return {string} The value as a string.
-   */
-  function glFunctionArgToString(functionName, numArgs, argumentIndex, value) {
-    const funcInfos = glValidEnumContexts[functionName];
-    if (funcInfos !== undefined) {
-      const funcInfo = funcInfos[numArgs];
-      if (funcInfo !== undefined) {
-        const argType = funcInfo[argumentIndex];
-        if (argType) {
-          if (typeof argType === 'function') {
-            return argType(value);
-          } else {
-            return glEnumToString(value);
-          }
-        }
-      }
-    }
-    if (value === null) {
-      return 'null';
-    } else if (value === undefined) {
-      return 'undefined';
-    } else {
-      return value.toString();
-    }
-  }
-
-  /**
-   * Converts the arguments of a WebGL function to a string.
-   * Attempts to convert enum arguments to strings.
-   *
-   * @param {string} functionName the name of the WebGL function.
-   * @param {number} args The arguments.
-   * @return {string} The arguments as a string.
-   */
-  function glFunctionArgsToString(functionName, args) {
-    // apparently we can't do args.join(",");
-    const argStrs = [];
-    const numArgs = args.length;
-    for (let ii = 0; ii < numArgs; ++ii) {
-      argStrs.push(glFunctionArgToString(functionName, numArgs, ii, args[ii]));
-    }
-    return argStrs.join(', ');
-  }
-
-  function makePropertyWrapper(wrapper, original, propertyName) {
-    wrapper.__defineGetter__(propertyName, function() {  // eslint-disable-line
-      return original[propertyName];
-    });
-    // TODO(gmane): this needs to handle properties that take more than
-    // one value?
-    wrapper.__defineSetter__(propertyName, function(value) {  // eslint-disable-line
-      original[propertyName] = value;
-    });
-  }
-
-  /**
-   * Given a WebGL context returns a wrapped context that calls
-   * gl.getError after every command and calls a function if the
-   * result is not gl.NO_ERROR.
-   *
-   * @param {!WebGLRenderingContext} ctx The webgl context to
-   *        wrap.
-   * @param {!function(err, funcName, args): void} opt_onErrorFunc
-   *        The function to call when gl.getError returns an
-   *        error. If not specified the default function calls
-   *        console.log with a message.
-   * @param {!function(funcName, args): void} opt_onFunc The
-   *        function to call when each webgl function is called.
-   *        You can use this to log all calls for example.
-   * @param {!WebGLRenderingContext} opt_err_ctx The webgl context
-   *        to call getError on if different than ctx.
-   */
-  function makeDebugContext(ctx, options) {
-    options = options || {};
-    const errCtx = options.errCtx || ctx;
-    const onFunc = options.funcFunc;
-    const sharedState = options.sharedState || {
-      numDrawCallsRemaining: options.maxDrawCalls || -1,
-      wrappers: {},
-    };
-    options.sharedState = sharedState;
-
-    const errorFunc = options.errorFunc || function(err, functionName, args) {
-      console.error(`WebGL error ${glEnumToString(err)} in ${functionName}(${glFunctionArgsToString(functionName, args)})`);  /* eslint-disable-line no-console */
-    };
-
-    // Holds booleans for each GL error so after we get the error ourselves
-    // we can still return it to the client app.
-    const glErrorShadow = { };
-    const wrapper = {};
-
-    function removeChecks() {
-      Object.keys(sharedState.wrappers).forEach(function(name) {
-        const pair = sharedState.wrappers[name];
-        const wrapper = pair.wrapper;
-        const orig = pair.orig;
-        for (const propertyName in wrapper) {
-          if (typeof wrapper[propertyName] === 'function') {
-            wrapper[propertyName] = orig[propertyName].bind(orig);
-          }
-        }
-      });
-    }
-
-    function checkMaxDrawCalls() {
-      if (sharedState.numDrawCallsRemaining === 0) {
-        removeChecks();
-      }
-      --sharedState.numDrawCallsRemaining;
-    }
-
-    function noop() {
-    }
-
-    // Makes a function that calls a WebGL function and then calls getError.
-    function makeErrorWrapper(ctx, functionName) {
-      const check = functionName.substring(0, 4) === 'draw' ? checkMaxDrawCalls : noop;
-      return function() {
-        if (onFunc) {
-          onFunc(functionName, arguments);
-        }
-        const result = ctx[functionName].apply(ctx, arguments);
-        const err = errCtx.getError();
-        if (err !== 0) {
-          glErrorShadow[err] = true;
-          errorFunc(err, functionName, arguments);
-        }
-        check();
-        return result;
-      };
-    }
-
-    function makeGetExtensionWrapper(ctx, wrapped) {
-      return function() {
-        const extensionName = arguments[0];
-        let ext = sharedState.wrappers[extensionName];
-        if (!ext) {
-          ext = wrapped.apply(ctx, arguments);
-          if (ext) {
-            const origExt = ext;
-            ext = makeDebugContext(ext, options);
-            sharedState.wrappers[extensionName] = { wrapper: ext, orig: origExt };
-            addEnumsForContext(origExt, extensionName);
-          }
-        }
-        return ext;
-      };
-    }
-
-    // Make a an object that has a copy of every property of the WebGL context
-    // but wraps all functions.
-    for (const propertyName in ctx) {
-      if (typeof ctx[propertyName] === 'function') {
-        if (propertyName !== 'getExtension') {
-          wrapper[propertyName] = makeErrorWrapper(ctx, propertyName);
-        } else {
-          const wrapped = makeErrorWrapper(ctx, propertyName);
-          wrapper[propertyName] = makeGetExtensionWrapper(ctx, wrapped);
-        }
-      } else {
-        makePropertyWrapper(wrapper, ctx, propertyName);
-      }
-    }
-
-    // Override the getError function with one that returns our saved results.
-    if (wrapper.getError) {
-      wrapper.getError = function() {
-        for (const err in glErrorShadow) {
-          if (glErrorShadow.hasOwnProperty(err)) {
-            if (glErrorShadow[err]) {
-              glErrorShadow[err] = false;
-              return err;
-            }
-          }
-        }
-        return ctx.NO_ERROR;
-      };
-    }
-
-    if (wrapper.bindBuffer) {
-      sharedState.wrappers['webgl'] = { wrapper: wrapper, orig: ctx };
-      addEnumsForContext(ctx, ctx.bindBufferBase ? 'WebGL2' : 'WebGL');
-    }
-
-    return wrapper;
-  }
-
-  //------------
-
   function captureJSErrors() {
     // capture JavaScript Errors
     self.addEventListener('error', function(e) {
@@ -649,6 +128,16 @@
   }
 
   function installWebGLDebugContextCreator() {
+    if (!self.webglDebugHelper) {
+      return;
+    }
+
+    const {
+      makeDebugContext,
+      glFunctionArgToString,
+      glEnumToString,
+    } = self.webglDebugHelper;
+
     // capture GL errors
     OffscreenCanvas.prototype.getContext = (function(oldFn) {
       return function() {

+ 575 - 0
threejs/resources/webgl-debug-helper.js

@@ -0,0 +1,575 @@
+/*
+ * Copyright 2012, Gregg Tavares.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Gregg Tavares. nor the names of his
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* global define */
+
+(function(root, factory) {  // eslint-disable-line
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as an anonymous module.
+    define([], function() {
+      return factory.call(root);
+    });
+  } else {
+    // Browser globals
+    root.webglDebugHelper = factory.call(root);
+  }
+}(this, function() {
+  'use strict';  // eslint-disable-line
+
+  //------------ [ from https://github.com/KhronosGroup/WebGLDeveloperTools ]
+
+  /*
+  ** Copyright (c) 2012 The Khronos Group Inc.
+  **
+  ** Permission is hereby granted, free of charge, to any person obtaining a
+  ** copy of this software and/or associated documentation files (the
+  ** "Materials"), to deal in the Materials without restriction, including
+  ** without limitation the rights to use, copy, modify, merge, publish,
+  ** distribute, sublicense, and/or sell copies of the Materials, and to
+  ** permit persons to whom the Materials are furnished to do so, subject to
+  ** the following conditions:
+  **
+  ** The above copyright notice and this permission notice shall be included
+  ** in all copies or substantial portions of the Materials.
+  **
+  ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+  ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+  ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+  ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+  ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+  ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+  */
+
+  /**
+   * Types of contexts we have added to map
+   */
+  const mappedContextTypes = {};
+
+  /**
+   * Map of numbers to names.
+   * @type {Object}
+   */
+  const glEnums = {};
+
+  /**
+   * Map of names to numbers.
+   * @type {Object}
+   */
+  const enumStringToValue = {};
+
+  /**
+   * Initializes this module. Safe to call more than once.
+   * @param {!WebGLRenderingContext} ctx A WebGL context. If
+   *    you have more than one context it doesn't matter which one
+   *    you pass in, it is only used to pull out constants.
+   */
+  function addEnumsForContext(ctx, type) {
+    if (!mappedContextTypes[type]) {
+      mappedContextTypes[type] = true;
+      for (const propertyName in ctx) {
+        if (typeof ctx[propertyName] === 'number') {
+          glEnums[ctx[propertyName]] = propertyName;
+          enumStringToValue[propertyName] = ctx[propertyName];
+        }
+      }
+    }
+  }
+
+  function enumArrayToString(enums) {
+    const enumStrings = [];
+    if (enums.length) {
+      for (let i = 0; i < enums.length; ++i) {
+        enums.push(glEnumToString(enums[i]));  // eslint-disable-line
+      }
+      return '[' + enumStrings.join(', ') + ']';
+    }
+    return enumStrings.toString();
+  }
+
+  function makeBitFieldToStringFunc(enums) {
+    return function(value) {
+      let orResult = 0;
+      const orEnums = [];
+      for (let i = 0; i < enums.length; ++i) {
+        const enumValue = enumStringToValue[enums[i]];
+        if ((value & enumValue) !== 0) {
+          orResult |= enumValue;
+          orEnums.push(glEnumToString(enumValue));  // eslint-disable-line
+        }
+      }
+      if (orResult === value) {
+        return orEnums.join(' | ');
+      } else {
+        return glEnumToString(value);  // eslint-disable-line
+      }
+    };
+  }
+
+  const destBufferBitFieldToString = makeBitFieldToStringFunc([
+    'COLOR_BUFFER_BIT',
+    'DEPTH_BUFFER_BIT',
+    'STENCIL_BUFFER_BIT',
+  ]);
+
+  /**
+   * Which arguments are enums based on the number of arguments to the function.
+   * So
+   *    'texImage2D': {
+   *       9: { 0:true, 2:true, 6:true, 7:true },
+   *       6: { 0:true, 2:true, 3:true, 4:true },
+   *    },
+   *
+   * means if there are 9 arguments then 6 and 7 are enums, if there are 6
+   * arguments 3 and 4 are enums. Maybe a function as well in which case
+   * value is passed to function and returns a string
+   *
+   * @type {!Object.<number, (!Object.<number, string>|function)}
+   */
+  const glValidEnumContexts = {
+    // Generic setters and getters
+
+    'enable': {1: { 0:true }},
+    'disable': {1: { 0:true }},
+    'getParameter': {1: { 0:true }},
+
+    // Rendering
+
+    'drawArrays': {3:{ 0:true }},
+    'drawElements': {4:{ 0:true, 2:true }},
+    'drawArraysInstanced': {4: { 0:true }},
+    'drawElementsInstanced': {5: {0:true, 2: true }},
+    'drawRangeElements': {6: {0:true, 4: true }},
+
+    // Shaders
+
+    'createShader': {1: { 0:true }},
+    'getShaderParameter': {2: { 1:true }},
+    'getProgramParameter': {2: { 1:true }},
+    'getShaderPrecisionFormat': {2: { 0: true, 1:true }},
+
+    // Vertex attributes
+
+    'getVertexAttrib': {2: { 1:true }},
+    'vertexAttribPointer': {6: { 2:true }},
+    'vertexAttribIPointer': {5: { 2:true }},  // WebGL2
+
+    // Textures
+
+    'bindTexture': {2: { 0:true }},
+    'activeTexture': {1: { 0:true }},
+    'getTexParameter': {2: { 0:true, 1:true }},
+    'texParameterf': {3: { 0:true, 1:true }},
+    'texParameteri': {3: { 0:true, 1:true, 2:true }},
+    'texImage2D': {
+      9: { 0:true, 2:true, 6:true, 7:true },
+      6: { 0:true, 2:true, 3:true, 4:true },
+      10: { 0:true, 2:true, 6:true, 7:true },  // WebGL2
+    },
+    'texImage3D': {
+      10: { 0:true, 2:true, 7:true, 8:true },  // WebGL2
+      11: { 0:true, 2:true, 7:true, 8:true },  // WebGL2
+    },
+    'texSubImage2D': {
+      9: { 0:true, 6:true, 7:true },
+      7: { 0:true, 4:true, 5:true },
+      10: { 0:true, 6:true, 7:true },  // WebGL2
+    },
+    'texSubImage3D': {
+      11: { 0:true, 8:true, 9:true },  // WebGL2
+      12: { 0:true, 8:true, 9:true },  // WebGL2
+    },
+    'texStorage2D': { 5: { 0:true, 2:true }},  // WebGL2
+    'texStorage3D': { 6: { 0:true, 2:true }},  // WebGL2
+    'copyTexImage2D': {8: { 0:true, 2:true }},
+    'copyTexSubImage2D': {8: { 0:true }},
+    'copyTexSubImage3D': {9: { 0:true }},  // WebGL2
+    'generateMipmap': {1: { 0:true }},
+    'compressedTexImage2D': {
+      7: { 0: true, 2:true },
+      8: { 0: true, 2:true },  // WebGL2
+    },
+    'compressedTexSubImage2D': {
+      8: { 0: true, 6:true },
+      9: { 0: true, 6:true },  // WebGL2
+    },
+    'compressedTexImage3D': {
+      8: { 0: true, 2: true, },  // WebGL2
+      9: { 0: true, 2: true, },  // WebGL2
+    },
+    'compressedTexSubImage3D': {
+      9: { 0: true, 8: true, },  // WebGL2
+      10: { 0: true, 8: true, },  // WebGL2
+    },
+
+    // Buffer objects
+
+    'bindBuffer': {2: { 0:true }},
+    'bufferData': {
+      3: { 0:true, 2:true },
+      4: { 0:true, 2:true },  // WebGL2
+      5: { 0:true, 2:true },  // WebGL2
+    },
+    'bufferSubData': {
+      3: { 0:true },
+      4: { 0:true },  // WebGL2
+      5: { 0:true },  // WebGL2
+    },
+    'copyBufferSubData': {
+      5: { 0:true },  // WeBGL2
+    },
+    'getBufferParameter': {2: { 0:true, 1:true }},
+    'getBufferSubData': {
+      3: { 0: true, },  // WebGL2
+      4: { 0: true, },  // WebGL2
+      5: { 0: true, },  // WebGL2
+    },
+
+    // Renderbuffers and framebuffers
+
+    'pixelStorei': {2: { 0:true, 1:true }},
+    'readPixels': {
+      7: { 4:true, 5:true },
+      8: { 4:true, 5:true },  // WebGL2
+    },
+    'bindRenderbuffer': {2: { 0:true }},
+    'bindFramebuffer': {2: { 0:true }},
+    'blitFramebuffer': {10: { 8: destBufferBitFieldToString, 9:true }},  // WebGL2
+    'checkFramebufferStatus': {1: { 0:true }},
+    'framebufferRenderbuffer': {4: { 0:true, 1:true, 2:true }},
+    'framebufferTexture2D': {5: { 0:true, 1:true, 2:true }},
+    'framebufferTextureLayer': {5: {0:true, 1:true }},  // WebGL2
+    'getFramebufferAttachmentParameter': {3: { 0:true, 1:true, 2:true }},
+    'getInternalformatParameter': {3: {0:true, 1:true, 2:true }},  // WebGL2
+    'getRenderbufferParameter': {2: { 0:true, 1:true }},
+    'invalidateFramebuffer': {2: { 0:true, 1: enumArrayToString, }},  // WebGL2
+    'invalidateSubFramebuffer': {6: {0: true, 1: enumArrayToString, }},  // WebGL2
+    'readBuffer': {1: {0: true}},  // WebGL2
+    'renderbufferStorage': {4: { 0:true, 1:true }},
+    'renderbufferStorageMultisample': {5: { 0: true, 2: true }},  // WebGL2
+
+    // Frame buffer operations (clear, blend, depth test, stencil)
+
+    'clear': {1: { 0: destBufferBitFieldToString }},
+    'depthFunc': {1: { 0:true }},
+    'blendFunc': {2: { 0:true, 1:true }},
+    'blendFuncSeparate': {4: { 0:true, 1:true, 2:true, 3:true }},
+    'blendEquation': {1: { 0:true }},
+    'blendEquationSeparate': {2: { 0:true, 1:true }},
+    'stencilFunc': {3: { 0:true }},
+    'stencilFuncSeparate': {4: { 0:true, 1:true }},
+    'stencilMaskSeparate': {2: { 0:true }},
+    'stencilOp': {3: { 0:true, 1:true, 2:true }},
+    'stencilOpSeparate': {4: { 0:true, 1:true, 2:true, 3:true }},
+
+    // Culling
+
+    'cullFace': {1: { 0:true }},
+    'frontFace': {1: { 0:true }},
+
+    // ANGLE_instanced_arrays extension
+
+    'drawArraysInstancedANGLE': {4: { 0:true }},
+    'drawElementsInstancedANGLE': {5: { 0:true, 2:true }},
+
+    // EXT_blend_minmax extension
+
+    'blendEquationEXT': {1: { 0:true }},
+
+    // Multiple Render Targets
+
+    'drawBuffersWebGL': {1: {0: enumArrayToString, }},  // WEBGL_draw_bufers
+    'drawBuffers': {1: {0: enumArrayToString, }},  // WebGL2
+    'clearBufferfv': {
+      4: {0: true },  // WebGL2
+      5: {0: true },  // WebGL2
+    },
+    'clearBufferiv': {
+      4: {0: true },  // WebGL2
+      5: {0: true },  // WebGL2
+    },
+    'clearBufferuiv': {
+      4: {0: true },  // WebGL2
+      5: {0: true },  // WebGL2
+    },
+    'clearBufferfi': { 4: {0: true}},  // WebGL2
+
+    // QueryObjects
+
+    'beginQuery': { 2: { 0: true }},  // WebGL2
+    'endQuery': { 1: { 0: true }},  // WebGL2
+    'getQuery': { 2: { 0: true, 1: true }},  // WebGL2
+    'getQueryParameter': { 2: { 1: true }},  // WebGL2
+
+    //  Sampler Objects
+
+    'samplerParameteri': { 3: { 1: true }},  // WebGL2
+    'samplerParameterf': { 3: { 1: true }},  // WebGL2
+    'getSamplerParameter': { 2: { 1: true }},  // WebGL2
+
+    //  Sync objects
+
+    'clientWaitSync': { 3: { 1: makeBitFieldToStringFunc(['SYNC_FLUSH_COMMANDS_BIT']) }},  // WebGL2
+    'fenceSync': { 2: { 0: true }},  // WebGL2
+    'getSyncParameter': { 2: { 1: true }},  // WebGL2
+
+    //  Transform Feedback
+
+    'bindTransformFeedback': { 2: { 0: true }},  // WebGL2
+    'beginTransformFeedback': { 1: { 0: true }},  // WebGL2
+
+    // Uniform Buffer Objects and Transform Feedback Buffers
+    'bindBufferBase': { 3: { 0: true }},  // WebGL2
+    'bindBufferRange': { 5: { 0: true }},  // WebGL2
+    'getIndexedParameter': { 2: { 0: true }},  // WebGL2
+    'getActiveUniforms': { 3: { 2: true }},  // WebGL2
+    'getActiveUniformBlockParameter': { 3: { 2: true }},  // WebGL2
+  };
+
+  /**
+   * Gets an string version of an WebGL enum.
+   *
+   * Example:
+   *   var str = WebGLDebugUtil.glEnumToString(ctx.getError());
+   *
+   * @param {number} value Value to return an enum for
+   * @return {string} The string version of the enum.
+   */
+  function glEnumToString(value) {
+    const name = glEnums[value];
+    return (name !== undefined)
+        ? `gl.${name}`
+        : `/*UNKNOWN WebGL ENUM*/ 0x${value.toString(16)}`;
+  }
+
+  /**
+   * Returns the string version of a WebGL argument.
+   * Attempts to convert enum arguments to strings.
+   * @param {string} functionName the name of the WebGL function.
+   * @param {number} numArgs the number of arguments passed to the function.
+   * @param {number} argumentIndx the index of the argument.
+   * @param {*} value The value of the argument.
+   * @return {string} The value as a string.
+   */
+  function glFunctionArgToString(functionName, numArgs, argumentIndex, value) {
+    const funcInfos = glValidEnumContexts[functionName];
+    if (funcInfos !== undefined) {
+      const funcInfo = funcInfos[numArgs];
+      if (funcInfo !== undefined) {
+        const argType = funcInfo[argumentIndex];
+        if (argType) {
+          if (typeof argType === 'function') {
+            return argType(value);
+          } else {
+            return glEnumToString(value);
+          }
+        }
+      }
+    }
+    if (value === null) {
+      return 'null';
+    } else if (value === undefined) {
+      return 'undefined';
+    } else {
+      return value.toString();
+    }
+  }
+
+  /**
+   * Converts the arguments of a WebGL function to a string.
+   * Attempts to convert enum arguments to strings.
+   *
+   * @param {string} functionName the name of the WebGL function.
+   * @param {number} args The arguments.
+   * @return {string} The arguments as a string.
+   */
+  function glFunctionArgsToString(functionName, args) {
+    // apparently we can't do args.join(",");
+    const argStrs = [];
+    const numArgs = args.length;
+    for (let ii = 0; ii < numArgs; ++ii) {
+      argStrs.push(glFunctionArgToString(functionName, numArgs, ii, args[ii]));
+    }
+    return argStrs.join(', ');
+  }
+
+  function makePropertyWrapper(wrapper, original, propertyName) {
+    wrapper.__defineGetter__(propertyName, function() {  // eslint-disable-line
+      return original[propertyName];
+    });
+    // TODO(gmane): this needs to handle properties that take more than
+    // one value?
+    wrapper.__defineSetter__(propertyName, function(value) {  // eslint-disable-line
+      original[propertyName] = value;
+    });
+  }
+
+  /**
+   * Given a WebGL context returns a wrapped context that calls
+   * gl.getError after every command and calls a function if the
+   * result is not gl.NO_ERROR.
+   *
+   * @param {!WebGLRenderingContext} ctx The webgl context to
+   *        wrap.
+   * @param {!function(err, funcName, args): void} opt_onErrorFunc
+   *        The function to call when gl.getError returns an
+   *        error. If not specified the default function calls
+   *        console.log with a message.
+   * @param {!function(funcName, args): void} opt_onFunc The
+   *        function to call when each webgl function is called.
+   *        You can use this to log all calls for example.
+   * @param {!WebGLRenderingContext} opt_err_ctx The webgl context
+   *        to call getError on if different than ctx.
+   */
+  function makeDebugContext(ctx, options) {
+    options = options || {};
+    const errCtx = options.errCtx || ctx;
+    const onFunc = options.funcFunc;
+    const sharedState = options.sharedState || {
+      numDrawCallsRemaining: options.maxDrawCalls || -1,
+      wrappers: {},
+    };
+    options.sharedState = sharedState;
+
+    const errorFunc = options.errorFunc || function(err, functionName, args) {
+      console.error(`WebGL error ${glEnumToString(err)} in ${functionName}(${glFunctionArgsToString(functionName, args)})`);  /* eslint-disable-line no-console */
+    };
+
+    // Holds booleans for each GL error so after we get the error ourselves
+    // we can still return it to the client app.
+    const glErrorShadow = { };
+    const wrapper = {};
+
+    function removeChecks() {
+      Object.keys(sharedState.wrappers).forEach(function(name) {
+        const pair = sharedState.wrappers[name];
+        const wrapper = pair.wrapper;
+        const orig = pair.orig;
+        for (const propertyName in wrapper) {
+          if (typeof wrapper[propertyName] === 'function') {
+            wrapper[propertyName] = orig[propertyName].bind(orig);
+          }
+        }
+      });
+    }
+
+    function checkMaxDrawCalls() {
+      if (sharedState.numDrawCallsRemaining === 0) {
+        removeChecks();
+      }
+      --sharedState.numDrawCallsRemaining;
+    }
+
+    function noop() {
+    }
+
+    // Makes a function that calls a WebGL function and then calls getError.
+    function makeErrorWrapper(ctx, functionName) {
+      const check = functionName.substring(0, 4) === 'draw' ? checkMaxDrawCalls : noop;
+      return function() {
+        if (onFunc) {
+          onFunc(functionName, arguments);
+        }
+        const result = ctx[functionName].apply(ctx, arguments);
+        const err = errCtx.getError();
+        if (err !== 0) {
+          glErrorShadow[err] = true;
+          errorFunc(err, functionName, arguments);
+        }
+        check();
+        return result;
+      };
+    }
+
+    function makeGetExtensionWrapper(ctx, wrapped) {
+      return function() {
+        const extensionName = arguments[0];
+        let ext = sharedState.wrappers[extensionName];
+        if (!ext) {
+          ext = wrapped.apply(ctx, arguments);
+          if (ext) {
+            const origExt = ext;
+            ext = makeDebugContext(ext, Object.assign({}, options, {errCtx: ctx}));
+            sharedState.wrappers[extensionName] = { wrapper: ext, orig: origExt };
+            addEnumsForContext(origExt, extensionName);
+          }
+        }
+        return ext;
+      };
+    }
+
+    // Make a an object that has a copy of every property of the WebGL context
+    // but wraps all functions.
+    for (const propertyName in ctx) {
+      if (typeof ctx[propertyName] === 'function') {
+        if (propertyName !== 'getExtension') {
+          wrapper[propertyName] = makeErrorWrapper(ctx, propertyName);
+        } else {
+          const wrapped = makeErrorWrapper(ctx, propertyName);
+          wrapper[propertyName] = makeGetExtensionWrapper(ctx, wrapped);
+        }
+      } else {
+        makePropertyWrapper(wrapper, ctx, propertyName);
+      }
+    }
+
+    // Override the getError function with one that returns our saved results.
+    if (wrapper.getError) {
+      wrapper.getError = function() {
+        for (const err in glErrorShadow) {
+          if (glErrorShadow.hasOwnProperty(err)) {
+            if (glErrorShadow[err]) {
+              glErrorShadow[err] = false;
+              return err;
+            }
+          }
+        }
+        return ctx.NO_ERROR;
+      };
+    }
+
+    if (wrapper.bindBuffer) {
+      sharedState.wrappers['webgl'] = { wrapper: wrapper, orig: ctx };
+      addEnumsForContext(ctx, ctx.bindBufferBase ? 'WebGL2' : 'WebGL');
+    }
+
+    return wrapper;
+  }
+
+  return {
+    makeDebugContext,
+    glFunctionArgsToString,
+    glFunctionArgToString,
+    glEnumToString,
+  };
+
+}));
+