From 00c7f42ce45f300897ce2ef0c4b27680b6b5eed4 Mon Sep 17 00:00:00 2001 From: Rezmason Date: Fri, 25 Aug 2023 09:10:19 -0700 Subject: [PATCH] Using webgl-debug to emit the low-level WebGL API calls from the regl project --- js/main.js | 261 +++++++++- js/rainPass.js | 17 +- lib/webgl-debug.js | 1195 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1457 insertions(+), 16 deletions(-) create mode 100644 lib/webgl-debug.js diff --git a/js/main.js b/js/main.js index ad82f7c..2dd8cd8 100644 --- a/js/main.js +++ b/js/main.js @@ -23,6 +23,7 @@ const loadJS = (src) => const init = async () => { await loadJS("lib/regl.js"); + await loadJS("lib/webgl-debug.js"); const resize = () => { const devicePixelRatio = window.devicePixelRatio ?? 1; @@ -49,7 +50,249 @@ const init = async () => { // These extensions are also needed, but Safari misreports that they are missing const optionalExtensions = ["EXT_color_buffer_half_float", "WEBGL_color_buffer_float", "OES_standard_derivatives"]; - const regl = createREGL({ canvas, pixelRatio: 1, extensions, optionalExtensions }); + const { makeDebugContext } = WebGLDebugUtils; + + const glConsts = { + DEPTH_TEST: 0x0B71, + BLEND: 0x0BE2, + UNPACK_ALIGNMENT: 0x0CF5, + TEXTURE_2D: 0x0DE1, + RGBA: 0x1908, + LUMINANCE: 0x1909, + NEAREST: 0x2600, + LINEAR: 0x2601, + TEXTURE_MAG_FILTER: 0x2800, + TEXTURE_MIN_FILTER: 0x2801, + TEXTURE_WRAP_S: 0x2802, + TEXTURE_WRAP_T: 0x2803, + CLAMP_TO_EDGE: 0x812F, + DEPTH_STENCIL_ATTACHMENT: 0x821A, + TEXTURE0: 0x84C0, + DEPTH_STENCIL: 0x84F9, + RGBA16F_EXT: 0x881A, + STATIC_DRAW: 0x88E4, + ACTIVE_UNIFORMS: 0x8B86, + ACTIVE_ATTRIBUTES: 0x8B89, + COLOR_ATTACHMENT0: 0x8CE0, + DEPTH_ATTACHMENT: 0x8D00, + STENCIL_ATTACHMENT: 0x8D20, + FRAMEBUFFER: 0x8D40, + RENDERBUFFER: 0x8D41, + HALF_FLOAT_OES: 0x8D61, + UNPACK_FLIP_Y_WEBGL: 0x9240, + UNPACK_PREMULTIPLY_ALPHA_WEBGL: 0x9241, + UNPACK_COLORSPACE_CONVERSION_WEBGL: 0x9243, + BROWSER_DEFAULT_WEBGL: 0x9244, + + ARRAY_BUFFER: 34962, + FRAGMENT_SHADER: 35632, + FRAGMENT_SHADER: 35632, + VERTEX_SHADER: 35633, + COMPILE_STATUS: 35713, + LINK_STATUS: 35714, + TRIANGLES: 4, + UNSIGNED_BYTE: 5121, + FLOAT: 5126, + }; + + for (let i = 1; i < 32; i++) { + glConsts[`TEXTURE${i}`] = 0x84C0 + i; + } + + const betterNames = { + [11]: "defaultFragShader", + [12]: "defaultVertShader", + [13]: "defaultProgram", + [17]: "fullscreen_geometry", + [18]: "rain_compute_doublebuffer_1_texture", [19]: "rain_compute_doublebuffer_1_framebuffer", + [20]: "rain_compute_doublebuffer_2_texture", [21]: "rain_compute_doublebuffer_2_framebuffer", + [22]: "rain_compute_shader", + [23]: "placeholder_texture", + [24]: "rain_output_texture", [25]: "rain_output_framebuffer", [26]: "rain_output_renderbuffer", + [27]: "rain_frag_shader", + [28]: "rain_vert_shader", + [29]: "rain_program", + [47]: "rain_geometry", + + [48]: "bloom_high_pass_pyr_0_texture", [49]: "bloom_high_pass_pyr_0_framebuffer", [50]: "bloom_high_pass_pyr_0_renderbuffer", + [51]: "bloom_high_pass_pyr_1_texture", [52]: "bloom_high_pass_pyr_1_framebuffer", [53]: "bloom_high_pass_pyr_1_renderbuffer", + [54]: "bloom_high_pass_pyr_2_texture", [55]: "bloom_high_pass_pyr_2_framebuffer", [56]: "bloom_high_pass_pyr_2_renderbuffer", + [57]: "bloom_high_pass_pyr_3_texture", [58]: "bloom_high_pass_pyr_3_framebuffer", [59]: "bloom_high_pass_pyr_3_renderbuffer", + [60]: "bloom_high_pass_pyr_4_texture", [61]: "bloom_high_pass_pyr_4_framebuffer", [62]: "bloom_high_pass_pyr_4_renderbuffer", + + [63]: "bloom_h_blur_pyr_0_texture", [64]: "bloom_h_blur_pyr_0_framebuffer", [65]: "bloom_h_blur_pyr_0_renderbuffer", + [66]: "bloom_h_blur_pyr_1_texture", [67]: "bloom_h_blur_pyr_1_framebuffer", [68]: "bloom_h_blur_pyr_1_renderbuffer", + [69]: "bloom_h_blur_pyr_2_texture", [70]: "bloom_h_blur_pyr_2_framebuffer", [71]: "bloom_h_blur_pyr_2_renderbuffer", + [72]: "bloom_h_blur_pyr_3_texture", [73]: "bloom_h_blur_pyr_3_framebuffer", [74]: "bloom_h_blur_pyr_3_renderbuffer", + [75]: "bloom_h_blur_pyr_4_texture", [76]: "bloom_h_blur_pyr_4_framebuffer", [77]: "bloom_h_blur_pyr_4_renderbuffer", + + [78]: "bloom_v_blur_pyr_0_texture", [79]: "bloom_v_blur_pyr_0_framebuffer", [80]: "bloom_v_blur_pyr_0_renderbuffer", + [81]: "bloom_v_blur_pyr_1_texture", [82]: "bloom_v_blur_pyr_1_framebuffer", [83]: "bloom_v_blur_pyr_1_renderbuffer", + [84]: "bloom_v_blur_pyr_2_texture", [85]: "bloom_v_blur_pyr_2_framebuffer", [86]: "bloom_v_blur_pyr_2_renderbuffer", + [87]: "bloom_v_blur_pyr_3_texture", [88]: "bloom_v_blur_pyr_3_framebuffer", [89]: "bloom_v_blur_pyr_3_renderbuffer", + [90]: "bloom_v_blur_pyr_4_texture", [91]: "bloom_v_blur_pyr_4_framebuffer", [92]: "bloom_v_blur_pyr_4_renderbuffer", + + [93]: "bloom_output_texture", [94]: "bloom_output_framebuffer", [95]: "bloom_output_renderbuffer", + [96]: "bloom_high_pass_shader", + [97]: "bloom_blur_shader", + [98]: "bloom_combine_shader", + [99]: "palette_output_texture", [100]: "palette_output_framebuffer", [101]: "palette_output_renderbuffer", + [102]: "palette_texture", + [103]: "palette_shader", + [104]: "msdf_texture", + [105]: "rain_compute_program", + [125]: "bloom_high_pass_program", + [131]: "bloom_blur_program", + [141]: "bloom_combine_program", + [155]: "palette_program", + }; + + const returnedValueNames = []; + + const glConstsByID = {}; + Object.entries(glConsts).forEach(([key, value]) => { + if (glConstsByID[value] == null) { + glConstsByID[value] = []; + } + glConstsByID[value].push(key); + }); + + const returnedValues = []; + const returnedValueUsages = []; + const commands = []; + + const log = []; + + const printCommands = (label) => { + const printedCommands = []; + for (const {name, args, retIndex} of commands) { + const printedArgs = []; + for (const [type, value] of args) { + if (value == null) { + printedArgs.push(`null`); + continue; + } + if (Array.isArray(value) || ArrayBuffer.isView(value)) { + printedArgs.push(`[${value.join(", ")}]`); + continue; + } + switch (type) { + case "string": + printedArgs.push(`\`${value}\``); + break; + case "GLenum": + printedArgs.push(`gl.${value}`); + break; + case "object": + printedArgs.push(`${value}`); + break; + case "returnedValue": + printedArgs.push(`state.${returnedValueNames[value]}`); + break; + default: + printedArgs.push(`${value}`); + break; + } + } + if (retIndex != -1 && returnedValueUsages[retIndex] > 0) { + printedCommands.push(`state.${returnedValueNames[retIndex]} = gl.${name}(${printedArgs.join(", ")});`); + } else { + printedCommands.push(`gl.${name}(${printedArgs.join(", ")});`); + } + } + log.push(`// ${label}`); + log.push(printedCommands.join("\n")); + commands.length = 0; + }; + + const rawGL = canvas.getContext("webgl"); + + const gl = new Proxy(makeDebugContext( + rawGL, + null, + (...a) => { + const name = a[0]; + const args = Array.from(a[1]); + const ret = a[2]; + for (let i = 0; i < args.length; i++) { + let value = args[i]; + let type = typeof value; + if (type === "number" && glConstsByID[value] != null) { + type = "GLenum"; + value = glConstsByID[value][0]; + } + if (returnedValues.includes(value)) { + type = "returnedValue"; + value = returnedValues.indexOf(value); + returnedValueUsages[value]++; + } + args[i] = [type, value]; + } + + let retIndex = -1; + if (typeof ret === "object" && !returnedValues.includes(ret)) { + returnedValues.push(ret); + returnedValueUsages.push(0); + retIndex = returnedValues.length - 1; + let glType = (ret[Symbol.toStringTag] ?? "object").replaceAll("WebGL", "").toLowerCase(); + let retName = glType + "_" + retIndex; + switch (name) { + case "getExtension": + retName = glType; + break; + } + switch (glType) { + case "texture": + break; + case "framebuffer": + break; + case "renderbuffer": + break; + case "program": + break; + case "shader": + if (args[0][1].toLowerCase().includes("fragment")) { + retName = "fragment_shader" + "_" + retIndex; + } else { + retName = "vertex_shader" + "_" + retIndex;; + } + break; + case "activeinfo": + retName = returnedValueNames[args[0][1]] + "_a_" + ret.name; + break; + case "uniformlocation": + retName = returnedValueNames[args[0][1]] + "_u_" + args[1][1]; + break; + } + if (returnedValueNames.includes(retName)) { + retName = retName + "_" + retIndex; + } + + if (betterNames[retIndex] != null) { + retName = betterNames[retIndex]; + } + + returnedValueNames[retIndex] = retName; + } + + commands.push({ name, args, retIndex }); + } + ), { + /* + get(target, prop, receiver) { + const ret = Reflect.get(...arguments); + if (typeof ret !== "function") { + console.log("GET", prop, ret); + } + return ret; + } + */ + }); + + const regl = createREGL({ gl, pixelRatio: 1, extensions, optionalExtensions }); + + printCommands("INIT"); + returnedValueUsages.fill(0); // All this takes place in a full screen quad. const fullScreenQuad = makeFullScreenQuad(regl); @@ -58,6 +301,8 @@ const init = async () => { const drawToScreen = regl({ uniforms: screenUniforms }); await Promise.all(pipeline.map((step) => step.ready)); + printCommands("LOAD"); + const render = ({ viewportWidth, viewportHeight }) => { const now = regl.now() * 1000; @@ -67,6 +312,7 @@ const init = async () => { for (const step of pipeline) { step.setSize(viewportWidth, viewportHeight); } + printCommands("RESIZE"); } fullScreenQuad(() => { for (const step of pipeline) { @@ -74,11 +320,20 @@ const init = async () => { } drawToScreen(); }); + printCommands("DRAW"); }; - render({ viewportWidth: 1, viewportHeight: 1 }); + render({ viewportWidth: 640, viewportHeight: 480 }); - const tick = regl.frame(render); + await new Promise(resolve => { + setTimeout(() => resolve(), 1000) + }); + + render({ viewportWidth: 640, viewportHeight: 480 }); + + console.log(log.join("\n")); + + // const tick = regl.frame(render); }; document.body.onload = () => { diff --git a/js/rainPass.js b/js/rainPass.js index f679129..64e6c6a 100644 --- a/js/rainPass.js +++ b/js/rainPass.js @@ -115,14 +115,6 @@ export default ({ regl }) => { framebuffer: computeDoubleBuffer.front, }); - const quadPositions = Array(1) - .fill() - .map((_, y) => - Array(1) - .fill() - .map((_, x) => Array(numVerticesPerQuad).fill([x, y])) - ); - // We render the code into an FBO using MSDFs: https://github.com/Chlumsky/msdfgen const glyphMSDF = loadImage(regl, "assets/matrixcode_msdf.png"); const output = makePassFBO(regl); @@ -137,13 +129,13 @@ export default ({ regl }) => { vert: ` precision lowp float; - attribute vec2 aPosition, aCorner; + attribute vec2 aPosition; uniform vec2 screenSize; varying vec2 vUV; void main() { - vUV = aPosition + aCorner; - gl_Position = vec4((aPosition + aCorner - 0.5) * 2.0 * screenSize, 0.0, 1.0); + vUV = aPosition; + gl_Position = vec4((aPosition - 0.5) * 2.0 * screenSize, 0.0, 1.0); } `, frag: ` @@ -232,8 +224,7 @@ export default ({ regl }) => { }, attributes: { - aPosition: quadPositions, - aCorner: quadVertices, + aPosition: quadVertices, }, count: numVerticesPerQuad, diff --git a/lib/webgl-debug.js b/lib/webgl-debug.js new file mode 100644 index 0000000..ac417e3 --- /dev/null +++ b/lib/webgl-debug.js @@ -0,0 +1,1195 @@ +/* +** 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. +*/ + +// Various functions for helping debug WebGL apps. + +WebGLDebugUtils = function() { + +/** + * Wrapped logging function. + * @param {string} msg Message to log. + */ +var log = function(msg) { + if (window.console && window.console.log) { + window.console.log(msg); + } +}; + +/** + * Wrapped error logging function. + * @param {string} msg Message to log. + */ +var error = function(msg) { + if (window.console && window.console.error) { + window.console.error(msg); + } else { + log(msg); + } +}; + + +/** + * 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 + * + * @type {!Object.} + */ +var 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 }}, + + // 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 }}, + + // 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 and texSubImage2D are defined below with WebGL 2 entrypoints + 'copyTexImage2D': {8: { 0:true, 2:true }}, + 'copyTexSubImage2D': {8: { 0:true }}, + 'generateMipmap': {1: { 0:true }}, + // compressedTexImage2D and compressedTexSubImage2D are defined below with WebGL 2 entrypoints + + // Buffer objects + + 'bindBuffer': {2: { 0:true }}, + // bufferData and bufferSubData are defined below with WebGL 2 entrypoints + 'getBufferParameter': {2: { 0:true, 1:true }}, + + // Renderbuffers and framebuffers + + 'pixelStorei': {2: { 0:true, 1:true }}, + // readPixels is defined below with WebGL 2 entrypoints + 'bindRenderbuffer': {2: { 0:true }}, + 'bindFramebuffer': {2: { 0:true }}, + 'checkFramebufferStatus': {1: { 0:true }}, + 'framebufferRenderbuffer': {4: { 0:true, 1:true, 2:true }}, + 'framebufferTexture2D': {5: { 0:true, 1:true, 2:true }}, + 'getFramebufferAttachmentParameter': {3: { 0:true, 1:true, 2:true }}, + 'getRenderbufferParameter': {2: { 0:true, 1:true }}, + 'renderbufferStorage': {4: { 0:true, 1:true }}, + + // Frame buffer operations (clear, blend, depth test, stencil) + + 'clear': {1: { 0: { 'enumBitwiseOr': ['COLOR_BUFFER_BIT', 'DEPTH_BUFFER_BIT', 'STENCIL_BUFFER_BIT'] }}}, + '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 }}, + + // WebGL 2 Buffer objects + + 'bufferData': { + 3: { 0:true, 2:true }, // WebGL 1 + 4: { 0:true, 2:true }, // WebGL 2 + 5: { 0:true, 2:true } // WebGL 2 + }, + 'bufferSubData': { + 3: { 0:true }, // WebGL 1 + 4: { 0:true }, // WebGL 2 + 5: { 0:true } // WebGL 2 + }, + 'copyBufferSubData': {5: { 0:true, 1:true }}, + 'getBufferSubData': {3: { 0:true }, 4: { 0:true }, 5: { 0:true }}, + + // WebGL 2 Framebuffer objects + + 'blitFramebuffer': {10: { 8: { 'enumBitwiseOr': ['COLOR_BUFFER_BIT', 'DEPTH_BUFFER_BIT', 'STENCIL_BUFFER_BIT'] }, 9:true }}, + 'framebufferTextureLayer': {5: { 0:true, 1:true }}, + 'invalidateFramebuffer': {2: { 0:true }}, + 'invalidateSubFramebuffer': {6: { 0:true }}, + 'readBuffer': {1: { 0:true }}, + + // WebGL 2 Renderbuffer objects + + 'getInternalformatParameter': {3: { 0:true, 1:true, 2:true }}, + 'renderbufferStorageMultisample': {5: { 0:true, 2:true }}, + + // WebGL 2 Texture objects + + 'texStorage2D': {5: { 0:true, 2:true }}, + 'texStorage3D': {6: { 0:true, 2:true }}, + 'texImage2D': { + 9: { 0:true, 2:true, 6:true, 7:true }, // WebGL 1 & 2 + 6: { 0:true, 2:true, 3:true, 4:true }, // WebGL 1 + 10: { 0:true, 2:true, 6:true, 7:true } // WebGL 2 + }, + 'texImage3D': { + 10: { 0:true, 2:true, 7:true, 8:true }, + 11: { 0:true, 2:true, 7:true, 8:true } + }, + 'texSubImage2D': { + 9: { 0:true, 6:true, 7:true }, // WebGL 1 & 2 + 7: { 0:true, 4:true, 5:true }, // WebGL 1 + 10: { 0:true, 6:true, 7:true } // WebGL 2 + }, + 'texSubImage3D': { + 11: { 0:true, 8:true, 9:true }, + 12: { 0:true, 8:true, 9:true } + }, + 'copyTexSubImage3D': {9: { 0:true }}, + 'compressedTexImage2D': { + 7: { 0: true, 2:true }, // WebGL 1 & 2 + 8: { 0: true, 2:true }, // WebGL 2 + 9: { 0: true, 2:true } // WebGL 2 + }, + 'compressedTexImage3D': { + 8: { 0: true, 2:true }, + 9: { 0: true, 2:true }, + 10: { 0: true, 2:true } + }, + 'compressedTexSubImage2D': { + 8: { 0: true, 6:true }, // WebGL 1 & 2 + 9: { 0: true, 6:true }, // WebGL 2 + 10: { 0: true, 6:true } // WebGL 2 + }, + 'compressedTexSubImage3D': { + 10: { 0: true, 8:true }, + 11: { 0: true, 8:true }, + 12: { 0: true, 8:true } + }, + + // WebGL 2 Vertex attribs + + 'vertexAttribIPointer': {5: { 2:true }}, + + // WebGL 2 Writing to the drawing buffer + + 'drawArraysInstanced': {4: { 0:true }}, + 'drawElementsInstanced': {5: { 0:true, 2:true }}, + 'drawRangeElements': {6: { 0:true, 4:true }}, + + // WebGL 2 Reading back pixels + + 'readPixels': { + 7: { 4:true, 5:true }, // WebGL 1 & 2 + 8: { 4:true, 5:true } // WebGL 2 + }, + + // WebGL 2 Multiple Render Targets + + 'clearBufferfv': {3: { 0:true }, 4: { 0:true }}, + 'clearBufferiv': {3: { 0:true }, 4: { 0:true }}, + 'clearBufferuiv': {3: { 0:true }, 4: { 0:true }}, + 'clearBufferfi': {4: { 0:true }}, + + // WebGL 2 Query objects + + 'beginQuery': {2: { 0:true }}, + 'endQuery': {1: { 0:true }}, + 'getQuery': {2: { 0:true, 1:true }}, + 'getQueryParameter': {2: { 1:true }}, + + // WebGL 2 Sampler objects + + 'samplerParameteri': {3: { 1:true, 2:true }}, + 'samplerParameterf': {3: { 1:true }}, + 'getSamplerParameter': {2: { 1:true }}, + + // WebGL 2 Sync objects + + 'fenceSync': {2: { 0:true, 1: { 'enumBitwiseOr': [] } }}, + 'clientWaitSync': {3: { 1: { 'enumBitwiseOr': ['SYNC_FLUSH_COMMANDS_BIT'] } }}, + 'waitSync': {3: { 1: { 'enumBitwiseOr': [] } }}, + 'getSyncParameter': {2: { 1:true }}, + + // WebGL 2 Transform Feedback + + 'bindTransformFeedback': {2: { 0:true }}, + 'beginTransformFeedback': {1: { 0:true }}, + 'transformFeedbackVaryings': {3: { 2:true }}, + + // WebGL2 Uniform Buffer Objects and Transform Feedback Buffers + + 'bindBufferBase': {3: { 0:true }}, + 'bindBufferRange': {5: { 0:true }}, + 'getIndexedParameter': {2: { 0:true }}, + 'getActiveUniforms': {3: { 2:true }}, + 'getActiveUniformBlockParameter': {3: { 2:true }} +}; + +/** + * Map of numbers to names. + * @type {Object} + */ +var glEnums = null; + +/** + * Map of names to numbers. + * @type {Object} + */ +var enumStringToValue = null; + +/** + * 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 init(ctx) { + if (glEnums == null) { + glEnums = { }; + enumStringToValue = { }; + for (var propertyName in ctx) { + if (typeof ctx[propertyName] == 'number') { + glEnums[ctx[propertyName]] = propertyName; + enumStringToValue[propertyName] = ctx[propertyName]; + } + } + } +} + +/** + * Checks the utils have been initialized. + */ +function checkInit() { + if (glEnums == null) { + throw 'WebGLDebugUtils.init(ctx) not called'; + } +} + +/** + * Returns true or false if value matches any WebGL enum + * @param {*} value Value to check if it might be an enum. + * @return {boolean} True if value matches one of the WebGL defined enums + */ +function mightBeEnum(value) { + checkInit(); + return (glEnums[value] !== undefined); +} + +/** + * 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) { + checkInit(); + var 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) { + var funcInfo = glValidEnumContexts[functionName]; + if (funcInfo !== undefined) { + var funcInfo = funcInfo[numArgs]; + if (funcInfo !== undefined) { + if (funcInfo[argumentIndex]) { + if (typeof funcInfo[argumentIndex] === 'object' && + funcInfo[argumentIndex]['enumBitwiseOr'] !== undefined) { + var enums = funcInfo[argumentIndex]['enumBitwiseOr']; + var orResult = 0; + var orEnums = []; + for (var i = 0; i < enums.length; ++i) { + var enumValue = enumStringToValue[enums[i]]; + if ((value & enumValue) !== 0) { + orResult |= enumValue; + orEnums.push(glEnumToString(enumValue)); + } + } + if (orResult === value) { + return orEnums.join(' | '); + } else { + return glEnumToString(value); + } + } else { + return glEnumToString(value); + } + } + } + } + if (value === null) { + return "null"; + } else if (value === undefined) { + return "undefined"; + } else if (ArrayBuffer.isView(value)) { + // Large typed array views are common in WebGL APIs and create + // huge strings in logs. + return "<" + value.constructor.name + ">"; + } 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(","); + var argStr = ""; + var numArgs = args.length; + for (var ii = 0; ii < numArgs; ++ii) { + argStr += ((ii == 0) ? '' : ', ') + + glFunctionArgToString(functionName, numArgs, ii, args[ii]); + } + return argStr; +}; + + +function makePropertyWrapper(wrapper, original, propertyName) { + //log("wrap prop: " + propertyName); + wrapper.__defineGetter__(propertyName, function() { + return original[propertyName]; + }); + // TODO(gmane): this needs to handle properties that take more than + // one value? + wrapper.__defineSetter__(propertyName, function(value) { + //log("set: " + propertyName); + original[propertyName] = value; + }); +} + +// Makes a function that calls a function on another object. +function makeFunctionWrapper(original, functionName) { + //log("wrap fn: " + functionName); + var f = original[functionName]; + return function() { + //log("call: " + functionName); + var result = f.apply(original, arguments); + return result; + }; +} + +/** + * 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, opt_onErrorFunc, opt_onFunc, opt_err_ctx) { + opt_err_ctx = opt_err_ctx || ctx; + init(ctx); + opt_onErrorFunc = opt_onErrorFunc || function(err, functionName, args) { + // apparently we can't do args.join(","); + var argStr = ""; + var numArgs = args.length; + for (var ii = 0; ii < numArgs; ++ii) { + argStr += ((ii == 0) ? '' : ', ') + + glFunctionArgToString(functionName, numArgs, ii, args[ii]); + } + error("WebGL error "+ glEnumToString(err) + " in "+ functionName + + "(" + argStr + ")"); + }; + + // Holds booleans for each GL error so after we get the error ourselves + // we can still return it to the client app. + var glErrorShadow = { }; + + // Makes a function that calls a WebGL function and then calls getError. + function makeErrorWrapper(ctx, functionName) { + return function() { + var result = ctx[functionName].apply(ctx, arguments); + var err = opt_err_ctx.getError(); + if (err != 0) { + glErrorShadow[err] = true; + opt_onErrorFunc(err, functionName, arguments); + } + if (opt_onFunc) { + opt_onFunc(functionName, arguments, result); + } + return result; + }; + } + + // Make a an object that has a copy of every property of the WebGL context + // but wraps all functions. + var wrapper = {}; + for (var propertyName in ctx) { + if (typeof ctx[propertyName] == 'function') { + if (propertyName != 'getExtension') { + wrapper[propertyName] = makeErrorWrapper(ctx, propertyName); + } else { + var wrapped = makeErrorWrapper(ctx, propertyName); + wrapper[propertyName] = function () { + var result = wrapped.apply(ctx, arguments); + if (!result) { + return null; + } + return makeDebugContext(result, opt_onErrorFunc, opt_onFunc, opt_err_ctx); + }; + } + } else { + makePropertyWrapper(wrapper, ctx, propertyName); + } + } + + // Override the getError function with one that returns our saved results. + wrapper.getError = function() { + for (var err in glErrorShadow) { + if (glErrorShadow.hasOwnProperty(err)) { + if (glErrorShadow[err]) { + glErrorShadow[err] = false; + return err; + } + } + } + return ctx.NO_ERROR; + }; + + return wrapper; +} + +function resetToInitialState(ctx) { + var isWebGL2RenderingContext = !!ctx.createTransformFeedback; + + if (isWebGL2RenderingContext) { + ctx.bindVertexArray(null); + } + + var numAttribs = ctx.getParameter(ctx.MAX_VERTEX_ATTRIBS); + var tmp = ctx.createBuffer(); + ctx.bindBuffer(ctx.ARRAY_BUFFER, tmp); + for (var ii = 0; ii < numAttribs; ++ii) { + ctx.disableVertexAttribArray(ii); + ctx.vertexAttribPointer(ii, 4, ctx.FLOAT, false, 0, 0); + ctx.vertexAttrib1f(ii, 0); + if (isWebGL2RenderingContext) { + ctx.vertexAttribDivisor(ii, 0); + } + } + ctx.deleteBuffer(tmp); + + var numTextureUnits = ctx.getParameter(ctx.MAX_TEXTURE_IMAGE_UNITS); + for (var ii = 0; ii < numTextureUnits; ++ii) { + ctx.activeTexture(ctx.TEXTURE0 + ii); + ctx.bindTexture(ctx.TEXTURE_CUBE_MAP, null); + ctx.bindTexture(ctx.TEXTURE_2D, null); + if (isWebGL2RenderingContext) { + ctx.bindTexture(ctx.TEXTURE_2D_ARRAY, null); + ctx.bindTexture(ctx.TEXTURE_3D, null); + ctx.bindSampler(ii, null); + } + } + + ctx.activeTexture(ctx.TEXTURE0); + ctx.useProgram(null); + ctx.bindBuffer(ctx.ARRAY_BUFFER, null); + ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, null); + ctx.bindFramebuffer(ctx.FRAMEBUFFER, null); + ctx.bindRenderbuffer(ctx.RENDERBUFFER, null); + ctx.disable(ctx.BLEND); + ctx.disable(ctx.CULL_FACE); + ctx.disable(ctx.DEPTH_TEST); + ctx.disable(ctx.DITHER); + ctx.disable(ctx.SCISSOR_TEST); + ctx.blendColor(0, 0, 0, 0); + ctx.blendEquation(ctx.FUNC_ADD); + ctx.blendFunc(ctx.ONE, ctx.ZERO); + ctx.clearColor(0, 0, 0, 0); + ctx.clearDepth(1); + ctx.clearStencil(-1); + ctx.colorMask(true, true, true, true); + ctx.cullFace(ctx.BACK); + ctx.depthFunc(ctx.LESS); + ctx.depthMask(true); + ctx.depthRange(0, 1); + ctx.frontFace(ctx.CCW); + ctx.hint(ctx.GENERATE_MIPMAP_HINT, ctx.DONT_CARE); + ctx.lineWidth(1); + ctx.pixelStorei(ctx.PACK_ALIGNMENT, 4); + ctx.pixelStorei(ctx.UNPACK_ALIGNMENT, 4); + ctx.pixelStorei(ctx.UNPACK_FLIP_Y_WEBGL, false); + ctx.pixelStorei(ctx.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); + // TODO: Delete this IF. + if (ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL) { + ctx.pixelStorei(ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL, ctx.BROWSER_DEFAULT_WEBGL); + } + ctx.polygonOffset(0, 0); + ctx.sampleCoverage(1, false); + ctx.scissor(0, 0, ctx.canvas.width, ctx.canvas.height); + ctx.stencilFunc(ctx.ALWAYS, 0, 0xFFFFFFFF); + ctx.stencilMask(0xFFFFFFFF); + ctx.stencilOp(ctx.KEEP, ctx.KEEP, ctx.KEEP); + ctx.viewport(0, 0, ctx.canvas.width, ctx.canvas.height); + ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT | ctx.STENCIL_BUFFER_BIT); + + if (isWebGL2RenderingContext) { + ctx.drawBuffers([ctx.BACK]); + ctx.readBuffer(ctx.BACK); + ctx.bindBuffer(ctx.COPY_READ_BUFFER, null); + ctx.bindBuffer(ctx.COPY_WRITE_BUFFER, null); + ctx.bindBuffer(ctx.PIXEL_PACK_BUFFER, null); + ctx.bindBuffer(ctx.PIXEL_UNPACK_BUFFER, null); + var numTransformFeedbacks = ctx.getParameter(ctx.MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS); + for (var ii = 0; ii < numTransformFeedbacks; ++ii) { + ctx.bindBufferBase(ctx.TRANSFORM_FEEDBACK_BUFFER, ii, null); + } + var numUBOs = ctx.getParameter(ctx.MAX_UNIFORM_BUFFER_BINDINGS); + for (var ii = 0; ii < numUBOs; ++ii) { + ctx.bindBufferBase(ctx.UNIFORM_BUFFER, ii, null); + } + ctx.disable(ctx.RASTERIZER_DISCARD); + ctx.pixelStorei(ctx.UNPACK_IMAGE_HEIGHT, 0); + ctx.pixelStorei(ctx.UNPACK_SKIP_IMAGES, 0); + ctx.pixelStorei(ctx.UNPACK_ROW_LENGTH, 0); + ctx.pixelStorei(ctx.UNPACK_SKIP_ROWS, 0); + ctx.pixelStorei(ctx.UNPACK_SKIP_PIXELS, 0); + ctx.pixelStorei(ctx.PACK_ROW_LENGTH, 0); + ctx.pixelStorei(ctx.PACK_SKIP_ROWS, 0); + ctx.pixelStorei(ctx.PACK_SKIP_PIXELS, 0); + ctx.hint(ctx.FRAGMENT_SHADER_DERIVATIVE_HINT, ctx.DONT_CARE); + } + + // TODO: This should NOT be needed but Firefox fails with 'hint' + while(ctx.getError()); +} + +function makeLostContextSimulatingCanvas(canvas) { + var unwrappedContext_; + var wrappedContext_; + var onLost_ = []; + var onRestored_ = []; + var wrappedContext_ = {}; + var contextId_ = 1; + var contextLost_ = false; + var resourceId_ = 0; + var resourceDb_ = []; + var numCallsToLoseContext_ = 0; + var numCalls_ = 0; + var canRestore_ = false; + var restoreTimeout_ = 0; + var isWebGL2RenderingContext; + + // Holds booleans for each GL error so can simulate errors. + var glErrorShadow_ = { }; + + canvas.getContext = function(f) { + return function() { + var ctx = f.apply(canvas, arguments); + // Did we get a context and is it a WebGL context? + if ((ctx instanceof WebGLRenderingContext) || (window.WebGL2RenderingContext && (ctx instanceof WebGL2RenderingContext))) { + if (ctx != unwrappedContext_) { + if (unwrappedContext_) { + throw "got different context" + } + isWebGL2RenderingContext = window.WebGL2RenderingContext && (ctx instanceof WebGL2RenderingContext); + unwrappedContext_ = ctx; + wrappedContext_ = makeLostContextSimulatingContext(unwrappedContext_); + } + return wrappedContext_; + } + return ctx; + } + }(canvas.getContext); + + function wrapEvent(listener) { + if (typeof(listener) == "function") { + return listener; + } else { + return function(info) { + listener.handleEvent(info); + } + } + } + + var addOnContextLostListener = function(listener) { + onLost_.push(wrapEvent(listener)); + }; + + var addOnContextRestoredListener = function(listener) { + onRestored_.push(wrapEvent(listener)); + }; + + + function wrapAddEventListener(canvas) { + var f = canvas.addEventListener; + canvas.addEventListener = function(type, listener, bubble) { + switch (type) { + case 'webglcontextlost': + addOnContextLostListener(listener); + break; + case 'webglcontextrestored': + addOnContextRestoredListener(listener); + break; + default: + f.apply(canvas, arguments); + } + }; + } + + wrapAddEventListener(canvas); + + canvas.loseContext = function() { + if (!contextLost_) { + contextLost_ = true; + numCallsToLoseContext_ = 0; + ++contextId_; + while (unwrappedContext_.getError()); + clearErrors(); + glErrorShadow_[unwrappedContext_.CONTEXT_LOST_WEBGL] = true; + var event = makeWebGLContextEvent("context lost"); + var callbacks = onLost_.slice(); + setTimeout(function() { + //log("numCallbacks:" + callbacks.length); + for (var ii = 0; ii < callbacks.length; ++ii) { + //log("calling callback:" + ii); + callbacks[ii](event); + } + if (restoreTimeout_ >= 0) { + setTimeout(function() { + canvas.restoreContext(); + }, restoreTimeout_); + } + }, 0); + } + }; + + canvas.restoreContext = function() { + if (contextLost_) { + if (onRestored_.length) { + setTimeout(function() { + if (!canRestore_) { + throw "can not restore. webglcontestlost listener did not call event.preventDefault"; + } + freeResources(); + resetToInitialState(unwrappedContext_); + contextLost_ = false; + numCalls_ = 0; + canRestore_ = false; + var callbacks = onRestored_.slice(); + var event = makeWebGLContextEvent("context restored"); + for (var ii = 0; ii < callbacks.length; ++ii) { + callbacks[ii](event); + } + }, 0); + } + } + }; + + canvas.loseContextInNCalls = function(numCalls) { + if (contextLost_) { + throw "You can not ask a lost contet to be lost"; + } + numCallsToLoseContext_ = numCalls_ + numCalls; + }; + + canvas.getNumCalls = function() { + return numCalls_; + }; + + canvas.setRestoreTimeout = function(timeout) { + restoreTimeout_ = timeout; + }; + + function isWebGLObject(obj) { + //return false; + return (obj instanceof WebGLBuffer || + obj instanceof WebGLFramebuffer || + obj instanceof WebGLProgram || + obj instanceof WebGLRenderbuffer || + obj instanceof WebGLShader || + obj instanceof WebGLTexture); + } + + function checkResources(args) { + for (var ii = 0; ii < args.length; ++ii) { + var arg = args[ii]; + if (isWebGLObject(arg)) { + return arg.__webglDebugContextLostId__ == contextId_; + } + } + return true; + } + + function clearErrors() { + var k = Object.keys(glErrorShadow_); + for (var ii = 0; ii < k.length; ++ii) { + delete glErrorShadow_[k[ii]]; + } + } + + function loseContextIfTime() { + ++numCalls_; + if (!contextLost_) { + if (numCallsToLoseContext_ == numCalls_) { + canvas.loseContext(); + } + } + } + + // Makes a function that simulates WebGL when out of context. + function makeLostContextFunctionWrapper(ctx, functionName) { + var f = ctx[functionName]; + return function() { + // log("calling:" + functionName); + // Only call the functions if the context is not lost. + loseContextIfTime(); + if (!contextLost_) { + //if (!checkResources(arguments)) { + // glErrorShadow_[wrappedContext_.INVALID_OPERATION] = true; + // return; + //} + var result = f.apply(ctx, arguments); + return result; + } + }; + } + + function freeResources() { + for (var ii = 0; ii < resourceDb_.length; ++ii) { + var resource = resourceDb_[ii]; + if (resource instanceof WebGLBuffer) { + unwrappedContext_.deleteBuffer(resource); + } else if (resource instanceof WebGLFramebuffer) { + unwrappedContext_.deleteFramebuffer(resource); + } else if (resource instanceof WebGLProgram) { + unwrappedContext_.deleteProgram(resource); + } else if (resource instanceof WebGLRenderbuffer) { + unwrappedContext_.deleteRenderbuffer(resource); + } else if (resource instanceof WebGLShader) { + unwrappedContext_.deleteShader(resource); + } else if (resource instanceof WebGLTexture) { + unwrappedContext_.deleteTexture(resource); + } + else if (isWebGL2RenderingContext) { + if (resource instanceof WebGLQuery) { + unwrappedContext_.deleteQuery(resource); + } else if (resource instanceof WebGLSampler) { + unwrappedContext_.deleteSampler(resource); + } else if (resource instanceof WebGLSync) { + unwrappedContext_.deleteSync(resource); + } else if (resource instanceof WebGLTransformFeedback) { + unwrappedContext_.deleteTransformFeedback(resource); + } else if (resource instanceof WebGLVertexArrayObject) { + unwrappedContext_.deleteVertexArray(resource); + } + } + } + } + + function makeWebGLContextEvent(statusMessage) { + return { + statusMessage: statusMessage, + preventDefault: function() { + canRestore_ = true; + } + }; + } + + return canvas; + + function makeLostContextSimulatingContext(ctx) { + // copy all functions and properties to wrapper + for (var propertyName in ctx) { + if (typeof ctx[propertyName] == 'function') { + wrappedContext_[propertyName] = makeLostContextFunctionWrapper( + ctx, propertyName); + } else { + makePropertyWrapper(wrappedContext_, ctx, propertyName); + } + } + + // Wrap a few functions specially. + wrappedContext_.getError = function() { + loseContextIfTime(); + if (!contextLost_) { + var err; + while (err = unwrappedContext_.getError()) { + glErrorShadow_[err] = true; + } + } + for (var err in glErrorShadow_) { + if (glErrorShadow_[err]) { + delete glErrorShadow_[err]; + return err; + } + } + return wrappedContext_.NO_ERROR; + }; + + var creationFunctions = [ + "createBuffer", + "createFramebuffer", + "createProgram", + "createRenderbuffer", + "createShader", + "createTexture" + ]; + if (isWebGL2RenderingContext) { + creationFunctions.push( + "createQuery", + "createSampler", + "fenceSync", + "createTransformFeedback", + "createVertexArray" + ); + } + for (var ii = 0; ii < creationFunctions.length; ++ii) { + var functionName = creationFunctions[ii]; + wrappedContext_[functionName] = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return null; + } + var obj = f.apply(ctx, arguments); + obj.__webglDebugContextLostId__ = contextId_; + resourceDb_.push(obj); + return obj; + }; + }(ctx[functionName]); + } + + var functionsThatShouldReturnNull = [ + "getActiveAttrib", + "getActiveUniform", + "getBufferParameter", + "getContextAttributes", + "getAttachedShaders", + "getFramebufferAttachmentParameter", + "getParameter", + "getProgramParameter", + "getProgramInfoLog", + "getRenderbufferParameter", + "getShaderParameter", + "getShaderInfoLog", + "getShaderSource", + "getTexParameter", + "getUniform", + "getUniformLocation", + "getVertexAttrib" + ]; + if (isWebGL2RenderingContext) { + functionsThatShouldReturnNull.push( + "getInternalformatParameter", + "getQuery", + "getQueryParameter", + "getSamplerParameter", + "getSyncParameter", + "getTransformFeedbackVarying", + "getIndexedParameter", + "getUniformIndices", + "getActiveUniforms", + "getActiveUniformBlockParameter", + "getActiveUniformBlockName" + ); + } + for (var ii = 0; ii < functionsThatShouldReturnNull.length; ++ii) { + var functionName = functionsThatShouldReturnNull[ii]; + wrappedContext_[functionName] = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return null; + } + return f.apply(ctx, arguments); + } + }(wrappedContext_[functionName]); + } + + var isFunctions = [ + "isBuffer", + "isEnabled", + "isFramebuffer", + "isProgram", + "isRenderbuffer", + "isShader", + "isTexture" + ]; + if (isWebGL2RenderingContext) { + isFunctions.push( + "isQuery", + "isSampler", + "isSync", + "isTransformFeedback", + "isVertexArray" + ); + } + for (var ii = 0; ii < isFunctions.length; ++ii) { + var functionName = isFunctions[ii]; + wrappedContext_[functionName] = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return false; + } + return f.apply(ctx, arguments); + } + }(wrappedContext_[functionName]); + } + + wrappedContext_.checkFramebufferStatus = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return wrappedContext_.FRAMEBUFFER_UNSUPPORTED; + } + return f.apply(ctx, arguments); + }; + }(wrappedContext_.checkFramebufferStatus); + + wrappedContext_.getAttribLocation = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return -1; + } + return f.apply(ctx, arguments); + }; + }(wrappedContext_.getAttribLocation); + + wrappedContext_.getVertexAttribOffset = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return 0; + } + return f.apply(ctx, arguments); + }; + }(wrappedContext_.getVertexAttribOffset); + + wrappedContext_.isContextLost = function() { + return contextLost_; + }; + + if (isWebGL2RenderingContext) { + wrappedContext_.getFragDataLocation = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return -1; + } + return f.apply(ctx, arguments); + }; + }(wrappedContext_.getFragDataLocation); + + wrappedContext_.clientWaitSync = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return wrappedContext_.WAIT_FAILED; + } + return f.apply(ctx, arguments); + }; + }(wrappedContext_.clientWaitSync); + + wrappedContext_.getUniformBlockIndex = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return wrappedContext_.INVALID_INDEX; + } + return f.apply(ctx, arguments); + }; + }(wrappedContext_.getUniformBlockIndex); + } + + return wrappedContext_; + } +} + +return { + /** + * 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. + */ + 'init': init, + + /** + * Returns true or false if value matches any WebGL enum + * @param {*} value Value to check if it might be an enum. + * @return {boolean} True if value matches one of the WebGL defined enums + */ + 'mightBeEnum': mightBeEnum, + + /** + * Gets an string version of an WebGL enum. + * + * Example: + * WebGLDebugUtil.init(ctx); + * var str = WebGLDebugUtil.glEnumToString(ctx.getError()); + * + * @param {number} value Value to return an enum for + * @return {string} The string version of the enum. + */ + 'glEnumToString': glEnumToString, + + /** + * Converts the argument of a WebGL function to a string. + * Attempts to convert enum arguments to strings. + * + * Example: + * WebGLDebugUtil.init(ctx); + * var str = WebGLDebugUtil.glFunctionArgToString('bindTexture', 2, 0, gl.TEXTURE_2D); + * + * would return 'TEXTURE_2D' + * + * @param {string} functionName the name of the WebGL function. + * @param {number} numArgs The number of arguments + * @param {number} argumentIndx the index of the argument. + * @param {*} value The value of the argument. + * @return {string} The value as a string. + */ + 'glFunctionArgToString': glFunctionArgToString, + + /** + * 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. + */ + 'glFunctionArgsToString': glFunctionArgsToString, + + /** + * Given a WebGL context returns a wrapped context that calls + * gl.getError after every command and calls a function if the + * result is not NO_ERROR. + * + * You can supply your own function if you want. For example, if you'd like + * an exception thrown on any GL error you could do this + * + * function throwOnGLError(err, funcName, args) { + * throw WebGLDebugUtils.glEnumToString(err) + + * " was caused by call to " + funcName; + * }; + * + * ctx = WebGLDebugUtils.makeDebugContext( + * canvas.getContext("webgl"), throwOnGLError); + * + * @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. + */ + 'makeDebugContext': makeDebugContext, + + /** + * Given a canvas element returns a wrapped canvas element that will + * simulate lost context. The canvas returned adds the following functions. + * + * loseContext: + * simulates a lost context event. + * + * restoreContext: + * simulates the context being restored. + * + * lostContextInNCalls: + * loses the context after N gl calls. + * + * getNumCalls: + * tells you how many gl calls there have been so far. + * + * setRestoreTimeout: + * sets the number of milliseconds until the context is restored + * after it has been lost. Defaults to 0. Pass -1 to prevent + * automatic restoring. + * + * @param {!Canvas} canvas The canvas element to wrap. + */ + 'makeLostContextSimulatingCanvas': makeLostContextSimulatingCanvas, + + /** + * Resets a context to the initial state. + * @param {!WebGLRenderingContext} ctx The webgl context to + * reset. + */ + 'resetToInitialState': resetToInitialState +}; + +}(); +