├── CODE_OF_CONDUCT.md ├── README.md └── src └── debug ├── debug-sample.html ├── externs ├── README.md └── webgl-debug.js ├── lost-context-simulator-test.html └── webgl-debug.js /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | A reminder that this issue tracker is managed by the Khronos Group. Interactions here should follow the Khronos Code of Conduct (https://www.khronos.org/developers/code-of-conduct), which prohibits aggressive or derogatory language. Please keep the discussion friendly and civil. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebGLDeveloperTools 2 | 3 | A small repository containing a few useful WebGL developer tools. 4 | 5 | Intended to be used as an ES6 module. 6 | 7 | Releases: 8 | 9 | v1.0.0: Initial [semver tag](http://semver.org/), used for module versioning by module loader. 10 | 11 | For more information and other useful tools, consult the WebGL wiki: 12 | http://www.khronos.org/webgl/wiki/ 13 | -------------------------------------------------------------------------------- /src/debug/debug-sample.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WebGL Debgging Sample 5 | 7 | 8 | 9 | 80 | 85 | 86 | 87 |
88 | 89 |
90 | Open the JavaScript Console or Web Console on your browser then click the buttons below. 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/debug/externs/README.md: -------------------------------------------------------------------------------- 1 | This directory contains externs files for using webgl-debug.js with the Google 2 | Closure compiler. 3 | -------------------------------------------------------------------------------- /src/debug/externs/webgl-debug.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @externs 3 | * @see http://www.khronos.org/webgl/wiki/Debugging 4 | * @see http://www.khronos.org/webgl/wiki/HandlingContextLost 5 | */ 6 | 7 | 8 | 9 | /** 10 | * @constructor 11 | * @extends {WebGLRenderingContext} 12 | */ 13 | var WebGLDebugRenderingContext = function() {}; 14 | 15 | 16 | 17 | /** 18 | * @constructor 19 | * @extends {HTMLCanvasElement} 20 | */ 21 | var WebGLDebugLostContextSimulatingCanvas = function() {}; 22 | 23 | 24 | /** 25 | * @nosideeffects 26 | * @return {number} 27 | */ 28 | WebGLDebugLostContextSimulatingCanvas.prototype.getNumCalls = function() {}; 29 | 30 | 31 | /** 32 | */ 33 | WebGLDebugLostContextSimulatingCanvas.prototype.loseContext = function() {}; 34 | 35 | 36 | /** 37 | * @param {number} numCalls 38 | */ 39 | WebGLDebugLostContextSimulatingCanvas.prototype.loseContextInNCalls = 40 | function(numCalls) {}; 41 | 42 | 43 | /** 44 | */ 45 | WebGLDebugLostContextSimulatingCanvas.prototype.restoreContext = function() {}; 46 | 47 | 48 | /** 49 | * @param {number} timeout 50 | */ 51 | WebGLDebugLostContextSimulatingCanvas.prototype.setRestoreTimeout = 52 | function(timeout) {}; 53 | 54 | 55 | /** 56 | * @type {Object} 57 | */ 58 | var WebGLDebugUtils = {}; 59 | 60 | 61 | /** 62 | * @nosideeffects 63 | * @param {number} value 64 | * @return {string} 65 | */ 66 | WebGLDebugUtils.glEnumToString = function(value) {}; 67 | 68 | 69 | /** 70 | * @nosideeffects 71 | * @param {string} functionName 72 | * @param {Array} args Args. 73 | * @return {string} String. 74 | */ 75 | WebGLDebugUtils.glFunctionArgsToString = function(functionName, args) {}; 76 | 77 | 78 | /** 79 | * @param {WebGLRenderingContext} ctx 80 | */ 81 | WebGLDebugUtils.init = function(ctx) {}; 82 | 83 | 84 | /** 85 | * @param {HTMLCanvasElement} canvas 86 | * @return {WebGLDebugLostContextSimulatingCanvas} 87 | */ 88 | WebGLDebugUtils.makeLostContextSimulatingCanvas = function(canvas) {}; 89 | 90 | 91 | /** 92 | * @param {WebGLRenderingContext} context 93 | * @param {Function=} opt_onErrorFunc 94 | * @param {Function=} opt_onFunc 95 | * @return {WebGLDebugRenderingContext} 96 | */ 97 | WebGLDebugUtils.makeDebugContext = 98 | function(context, opt_onErrorFunc, opt_onFunc) {}; 99 | -------------------------------------------------------------------------------- /src/debug/lost-context-simulator-test.html: -------------------------------------------------------------------------------- 1 | 27 | 29 | 30 | 31 | WebGL Lost Context Wrapper Test 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 |
41 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/debug/webgl-debug.js: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (c) 2012 The Khronos Group Inc. 3 | ** 4 | ** Permission is hereby granted, free of charge, to any person obtaining a 5 | ** copy of this software and/or associated documentation files (the 6 | ** "Materials"), to deal in the Materials without restriction, including 7 | ** without limitation the rights to use, copy, modify, merge, publish, 8 | ** distribute, sublicense, and/or sell copies of the Materials, and to 9 | ** permit persons to whom the Materials are furnished to do so, subject to 10 | ** the following conditions: 11 | ** 12 | ** The above copyright notice and this permission notice shall be included 13 | ** in all copies or substantial portions of the Materials. 14 | ** 15 | ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 19 | ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 20 | ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 21 | ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. 22 | */ 23 | 24 | // Various functions for helping debug WebGL apps. 25 | 26 | WebGLDebugUtils = function() { 27 | 28 | /** 29 | * Wrapped logging function. 30 | * @param {string} msg Message to log. 31 | */ 32 | var log = function(msg) { 33 | if (window.console && window.console.log) { 34 | window.console.log(msg); 35 | } 36 | }; 37 | 38 | /** 39 | * Wrapped error logging function. 40 | * @param {string} msg Message to log. 41 | */ 42 | var error = function(msg) { 43 | if (window.console && window.console.error) { 44 | window.console.error(msg); 45 | } else { 46 | log(msg); 47 | } 48 | }; 49 | 50 | 51 | /** 52 | * Which arguments are enums based on the number of arguments to the function. 53 | * So 54 | * 'texImage2D': { 55 | * 9: { 0:true, 2:true, 6:true, 7:true }, 56 | * 6: { 0:true, 2:true, 3:true, 4:true }, 57 | * }, 58 | * 59 | * means if there are 9 arguments then 6 and 7 are enums, if there are 6 60 | * arguments 3 and 4 are enums 61 | * 62 | * @type {!Object.} 63 | */ 64 | var glValidEnumContexts = { 65 | // Generic setters and getters 66 | 67 | 'enable': {1: { 0:true }}, 68 | 'disable': {1: { 0:true }}, 69 | 'getParameter': {1: { 0:true }}, 70 | 71 | // Rendering 72 | 73 | 'drawArrays': {3:{ 0:true }}, 74 | 'drawElements': {4:{ 0:true, 2:true }}, 75 | 76 | // Shaders 77 | 78 | 'createShader': {1: { 0:true }}, 79 | 'getShaderParameter': {2: { 1:true }}, 80 | 'getProgramParameter': {2: { 1:true }}, 81 | 'getShaderPrecisionFormat': {2: { 0: true, 1:true }}, 82 | 83 | // Vertex attributes 84 | 85 | 'getVertexAttrib': {2: { 1:true }}, 86 | 'vertexAttribPointer': {6: { 2:true }}, 87 | 88 | // Textures 89 | 90 | 'bindTexture': {2: { 0:true }}, 91 | 'activeTexture': {1: { 0:true }}, 92 | 'getTexParameter': {2: { 0:true, 1:true }}, 93 | 'texParameterf': {3: { 0:true, 1:true }}, 94 | 'texParameteri': {3: { 0:true, 1:true, 2:true }}, 95 | // texImage2D and texSubImage2D are defined below with WebGL 2 entrypoints 96 | 'copyTexImage2D': {8: { 0:true, 2:true }}, 97 | 'copyTexSubImage2D': {8: { 0:true }}, 98 | 'generateMipmap': {1: { 0:true }}, 99 | // compressedTexImage2D and compressedTexSubImage2D are defined below with WebGL 2 entrypoints 100 | 101 | // Buffer objects 102 | 103 | 'bindBuffer': {2: { 0:true }}, 104 | // bufferData and bufferSubData are defined below with WebGL 2 entrypoints 105 | 'getBufferParameter': {2: { 0:true, 1:true }}, 106 | 107 | // Renderbuffers and framebuffers 108 | 109 | 'pixelStorei': {2: { 0:true, 1:true }}, 110 | // readPixels is defined below with WebGL 2 entrypoints 111 | 'bindRenderbuffer': {2: { 0:true }}, 112 | 'bindFramebuffer': {2: { 0:true }}, 113 | 'checkFramebufferStatus': {1: { 0:true }}, 114 | 'framebufferRenderbuffer': {4: { 0:true, 1:true, 2:true }}, 115 | 'framebufferTexture2D': {5: { 0:true, 1:true, 2:true }}, 116 | 'getFramebufferAttachmentParameter': {3: { 0:true, 1:true, 2:true }}, 117 | 'getRenderbufferParameter': {2: { 0:true, 1:true }}, 118 | 'renderbufferStorage': {4: { 0:true, 1:true }}, 119 | 120 | // Frame buffer operations (clear, blend, depth test, stencil) 121 | 122 | 'clear': {1: { 0: { 'enumBitwiseOr': ['COLOR_BUFFER_BIT', 'DEPTH_BUFFER_BIT', 'STENCIL_BUFFER_BIT'] }}}, 123 | 'depthFunc': {1: { 0:true }}, 124 | 'blendFunc': {2: { 0:true, 1:true }}, 125 | 'blendFuncSeparate': {4: { 0:true, 1:true, 2:true, 3:true }}, 126 | 'blendEquation': {1: { 0:true }}, 127 | 'blendEquationSeparate': {2: { 0:true, 1:true }}, 128 | 'stencilFunc': {3: { 0:true }}, 129 | 'stencilFuncSeparate': {4: { 0:true, 1:true }}, 130 | 'stencilMaskSeparate': {2: { 0:true }}, 131 | 'stencilOp': {3: { 0:true, 1:true, 2:true }}, 132 | 'stencilOpSeparate': {4: { 0:true, 1:true, 2:true, 3:true }}, 133 | 134 | // Culling 135 | 136 | 'cullFace': {1: { 0:true }}, 137 | 'frontFace': {1: { 0:true }}, 138 | 139 | // ANGLE_instanced_arrays extension 140 | 141 | 'drawArraysInstancedANGLE': {4: { 0:true }}, 142 | 'drawElementsInstancedANGLE': {5: { 0:true, 2:true }}, 143 | 144 | // EXT_blend_minmax extension 145 | 146 | 'blendEquationEXT': {1: { 0:true }}, 147 | 148 | // WebGL 2 Buffer objects 149 | 150 | 'bufferData': { 151 | 3: { 0:true, 2:true }, // WebGL 1 152 | 4: { 0:true, 2:true }, // WebGL 2 153 | 5: { 0:true, 2:true } // WebGL 2 154 | }, 155 | 'bufferSubData': { 156 | 3: { 0:true }, // WebGL 1 157 | 4: { 0:true }, // WebGL 2 158 | 5: { 0:true } // WebGL 2 159 | }, 160 | 'copyBufferSubData': {5: { 0:true, 1:true }}, 161 | 'getBufferSubData': {3: { 0:true }, 4: { 0:true }, 5: { 0:true }}, 162 | 163 | // WebGL 2 Framebuffer objects 164 | 165 | 'blitFramebuffer': {10: { 8: { 'enumBitwiseOr': ['COLOR_BUFFER_BIT', 'DEPTH_BUFFER_BIT', 'STENCIL_BUFFER_BIT'] }, 9:true }}, 166 | 'framebufferTextureLayer': {5: { 0:true, 1:true }}, 167 | 'invalidateFramebuffer': {2: { 0:true }}, 168 | 'invalidateSubFramebuffer': {6: { 0:true }}, 169 | 'readBuffer': {1: { 0:true }}, 170 | 171 | // WebGL 2 Renderbuffer objects 172 | 173 | 'getInternalformatParameter': {3: { 0:true, 1:true, 2:true }}, 174 | 'renderbufferStorageMultisample': {5: { 0:true, 2:true }}, 175 | 176 | // WebGL 2 Texture objects 177 | 178 | 'texStorage2D': {5: { 0:true, 2:true }}, 179 | 'texStorage3D': {6: { 0:true, 2:true }}, 180 | 'texImage2D': { 181 | 9: { 0:true, 2:true, 6:true, 7:true }, // WebGL 1 & 2 182 | 6: { 0:true, 2:true, 3:true, 4:true }, // WebGL 1 183 | 10: { 0:true, 2:true, 6:true, 7:true } // WebGL 2 184 | }, 185 | 'texImage3D': { 186 | 10: { 0:true, 2:true, 7:true, 8:true }, 187 | 11: { 0:true, 2:true, 7:true, 8:true } 188 | }, 189 | 'texSubImage2D': { 190 | 9: { 0:true, 6:true, 7:true }, // WebGL 1 & 2 191 | 7: { 0:true, 4:true, 5:true }, // WebGL 1 192 | 10: { 0:true, 6:true, 7:true } // WebGL 2 193 | }, 194 | 'texSubImage3D': { 195 | 11: { 0:true, 8:true, 9:true }, 196 | 12: { 0:true, 8:true, 9:true } 197 | }, 198 | 'copyTexSubImage3D': {9: { 0:true }}, 199 | 'compressedTexImage2D': { 200 | 7: { 0: true, 2:true }, // WebGL 1 & 2 201 | 8: { 0: true, 2:true }, // WebGL 2 202 | 9: { 0: true, 2:true } // WebGL 2 203 | }, 204 | 'compressedTexImage3D': { 205 | 8: { 0: true, 2:true }, 206 | 9: { 0: true, 2:true }, 207 | 10: { 0: true, 2:true } 208 | }, 209 | 'compressedTexSubImage2D': { 210 | 8: { 0: true, 6:true }, // WebGL 1 & 2 211 | 9: { 0: true, 6:true }, // WebGL 2 212 | 10: { 0: true, 6:true } // WebGL 2 213 | }, 214 | 'compressedTexSubImage3D': { 215 | 10: { 0: true, 8:true }, 216 | 11: { 0: true, 8:true }, 217 | 12: { 0: true, 8:true } 218 | }, 219 | 220 | // WebGL 2 Vertex attribs 221 | 222 | 'vertexAttribIPointer': {5: { 2:true }}, 223 | 224 | // WebGL 2 Writing to the drawing buffer 225 | 226 | 'drawArraysInstanced': {4: { 0:true }}, 227 | 'drawElementsInstanced': {5: { 0:true, 2:true }}, 228 | 'drawRangeElements': {6: { 0:true, 4:true }}, 229 | 230 | // WebGL 2 Reading back pixels 231 | 232 | 'readPixels': { 233 | 7: { 4:true, 5:true }, // WebGL 1 & 2 234 | 8: { 4:true, 5:true } // WebGL 2 235 | }, 236 | 237 | // WebGL 2 Multiple Render Targets 238 | 239 | 'clearBufferfv': {3: { 0:true }, 4: { 0:true }}, 240 | 'clearBufferiv': {3: { 0:true }, 4: { 0:true }}, 241 | 'clearBufferuiv': {3: { 0:true }, 4: { 0:true }}, 242 | 'clearBufferfi': {4: { 0:true }}, 243 | 244 | // WebGL 2 Query objects 245 | 246 | 'beginQuery': {2: { 0:true }}, 247 | 'endQuery': {1: { 0:true }}, 248 | 'getQuery': {2: { 0:true, 1:true }}, 249 | 'getQueryParameter': {2: { 1:true }}, 250 | 251 | // WebGL 2 Sampler objects 252 | 253 | 'samplerParameteri': {3: { 1:true, 2:true }}, 254 | 'samplerParameterf': {3: { 1:true }}, 255 | 'getSamplerParameter': {2: { 1:true }}, 256 | 257 | // WebGL 2 Sync objects 258 | 259 | 'fenceSync': {2: { 0:true, 1: { 'enumBitwiseOr': [] } }}, 260 | 'clientWaitSync': {3: { 1: { 'enumBitwiseOr': ['SYNC_FLUSH_COMMANDS_BIT'] } }}, 261 | 'waitSync': {3: { 1: { 'enumBitwiseOr': [] } }}, 262 | 'getSyncParameter': {2: { 1:true }}, 263 | 264 | // WebGL 2 Transform Feedback 265 | 266 | 'bindTransformFeedback': {2: { 0:true }}, 267 | 'beginTransformFeedback': {1: { 0:true }}, 268 | 'transformFeedbackVaryings': {3: { 2:true }}, 269 | 270 | // WebGL2 Uniform Buffer Objects and Transform Feedback Buffers 271 | 272 | 'bindBufferBase': {3: { 0:true }}, 273 | 'bindBufferRange': {5: { 0:true }}, 274 | 'getIndexedParameter': {2: { 0:true }}, 275 | 'getActiveUniforms': {3: { 2:true }}, 276 | 'getActiveUniformBlockParameter': {3: { 2:true }} 277 | }; 278 | 279 | /** 280 | * Map of numbers to names. 281 | * @type {Object} 282 | */ 283 | var glEnums = null; 284 | 285 | /** 286 | * Map of names to numbers. 287 | * @type {Object} 288 | */ 289 | var enumStringToValue = null; 290 | 291 | /** 292 | * Initializes this module. Safe to call more than once. 293 | * @param {!WebGLRenderingContext} ctx A WebGL context. If 294 | * you have more than one context it doesn't matter which one 295 | * you pass in, it is only used to pull out constants. 296 | */ 297 | function init(ctx) { 298 | if (glEnums == null) { 299 | glEnums = { }; 300 | enumStringToValue = { }; 301 | for (var propertyName in ctx) { 302 | if (typeof ctx[propertyName] == 'number') { 303 | glEnums[ctx[propertyName]] = propertyName; 304 | enumStringToValue[propertyName] = ctx[propertyName]; 305 | } 306 | } 307 | } 308 | } 309 | 310 | /** 311 | * Checks the utils have been initialized. 312 | */ 313 | function checkInit() { 314 | if (glEnums == null) { 315 | throw 'WebGLDebugUtils.init(ctx) not called'; 316 | } 317 | } 318 | 319 | /** 320 | * Returns true or false if value matches any WebGL enum 321 | * @param {*} value Value to check if it might be an enum. 322 | * @return {boolean} True if value matches one of the WebGL defined enums 323 | */ 324 | function mightBeEnum(value) { 325 | checkInit(); 326 | return (glEnums[value] !== undefined); 327 | } 328 | 329 | /** 330 | * Gets an string version of an WebGL enum. 331 | * 332 | * Example: 333 | * var str = WebGLDebugUtil.glEnumToString(ctx.getError()); 334 | * 335 | * @param {number} value Value to return an enum for 336 | * @return {string} The string version of the enum. 337 | */ 338 | function glEnumToString(value) { 339 | checkInit(); 340 | var name = glEnums[value]; 341 | return (name !== undefined) ? ("gl." + name) : 342 | ("/*UNKNOWN WebGL ENUM*/ 0x" + value.toString(16) + ""); 343 | } 344 | 345 | /** 346 | * Returns the string version of a WebGL argument. 347 | * Attempts to convert enum arguments to strings. 348 | * @param {string} functionName the name of the WebGL function. 349 | * @param {number} numArgs the number of arguments passed to the function. 350 | * @param {number} argumentIndx the index of the argument. 351 | * @param {*} value The value of the argument. 352 | * @return {string} The value as a string. 353 | */ 354 | function glFunctionArgToString(functionName, numArgs, argumentIndex, value) { 355 | var funcInfo = glValidEnumContexts[functionName]; 356 | if (funcInfo !== undefined) { 357 | var funcInfo = funcInfo[numArgs]; 358 | if (funcInfo !== undefined) { 359 | if (funcInfo[argumentIndex]) { 360 | if (typeof funcInfo[argumentIndex] === 'object' && 361 | funcInfo[argumentIndex]['enumBitwiseOr'] !== undefined) { 362 | var enums = funcInfo[argumentIndex]['enumBitwiseOr']; 363 | var orResult = 0; 364 | var orEnums = []; 365 | for (var i = 0; i < enums.length; ++i) { 366 | var enumValue = enumStringToValue[enums[i]]; 367 | if ((value & enumValue) !== 0) { 368 | orResult |= enumValue; 369 | orEnums.push(glEnumToString(enumValue)); 370 | } 371 | } 372 | if (orResult === value) { 373 | return orEnums.join(' | '); 374 | } else { 375 | return glEnumToString(value); 376 | } 377 | } else { 378 | return glEnumToString(value); 379 | } 380 | } 381 | } 382 | } 383 | if (value === null) { 384 | return "null"; 385 | } else if (value === undefined) { 386 | return "undefined"; 387 | } else if (ArrayBuffer.isView(value)) { 388 | // Large typed array views are common in WebGL APIs and create 389 | // huge strings in logs. 390 | return "<" + value.constructor.name + ">"; 391 | } else { 392 | return value.toString(); 393 | } 394 | } 395 | 396 | /** 397 | * Converts the arguments of a WebGL function to a string. 398 | * Attempts to convert enum arguments to strings. 399 | * 400 | * @param {string} functionName the name of the WebGL function. 401 | * @param {number} args The arguments. 402 | * @return {string} The arguments as a string. 403 | */ 404 | function glFunctionArgsToString(functionName, args) { 405 | // apparently we can't do args.join(","); 406 | var argStr = ""; 407 | var numArgs = args.length; 408 | for (var ii = 0; ii < numArgs; ++ii) { 409 | argStr += ((ii == 0) ? '' : ', ') + 410 | glFunctionArgToString(functionName, numArgs, ii, args[ii]); 411 | } 412 | return argStr; 413 | }; 414 | 415 | 416 | function makePropertyWrapper(wrapper, original, propertyName) { 417 | //log("wrap prop: " + propertyName); 418 | wrapper.__defineGetter__(propertyName, function() { 419 | return original[propertyName]; 420 | }); 421 | // TODO(gmane): this needs to handle properties that take more than 422 | // one value? 423 | wrapper.__defineSetter__(propertyName, function(value) { 424 | //log("set: " + propertyName); 425 | original[propertyName] = value; 426 | }); 427 | } 428 | 429 | // Makes a function that calls a function on another object. 430 | function makeFunctionWrapper(original, functionName) { 431 | //log("wrap fn: " + functionName); 432 | var f = original[functionName]; 433 | return function() { 434 | //log("call: " + functionName); 435 | var result = f.apply(original, arguments); 436 | return result; 437 | }; 438 | } 439 | 440 | /** 441 | * Given a WebGL context returns a wrapped context that calls 442 | * gl.getError after every command and calls a function if the 443 | * result is not gl.NO_ERROR. 444 | * 445 | * @param {!WebGLRenderingContext} ctx The webgl context to 446 | * wrap. 447 | * @param {!function(err, funcName, args): void} opt_onErrorFunc 448 | * The function to call when gl.getError returns an 449 | * error. If not specified the default function calls 450 | * console.log with a message. 451 | * @param {!function(funcName, args): void} opt_onFunc The 452 | * function to call when each webgl function is called. 453 | * You can use this to log all calls for example. 454 | * @param {!WebGLRenderingContext} opt_err_ctx The webgl context 455 | * to call getError on if different than ctx. 456 | */ 457 | function makeDebugContext(ctx, opt_onErrorFunc, opt_onFunc, opt_err_ctx) { 458 | opt_err_ctx = opt_err_ctx || ctx; 459 | init(ctx); 460 | opt_onErrorFunc = opt_onErrorFunc || function(err, functionName, args) { 461 | // apparently we can't do args.join(","); 462 | var argStr = ""; 463 | var numArgs = args.length; 464 | for (var ii = 0; ii < numArgs; ++ii) { 465 | argStr += ((ii == 0) ? '' : ', ') + 466 | glFunctionArgToString(functionName, numArgs, ii, args[ii]); 467 | } 468 | error("WebGL error "+ glEnumToString(err) + " in "+ functionName + 469 | "(" + argStr + ")"); 470 | }; 471 | 472 | // Holds booleans for each GL error so after we get the error ourselves 473 | // we can still return it to the client app. 474 | var glErrorShadow = { }; 475 | 476 | // Makes a function that calls a WebGL function and then calls getError. 477 | function makeErrorWrapper(ctx, functionName) { 478 | return function() { 479 | if (opt_onFunc) { 480 | opt_onFunc(functionName, arguments); 481 | } 482 | var result = ctx[functionName].apply(ctx, arguments); 483 | var err = opt_err_ctx.getError(); 484 | if (err != 0) { 485 | glErrorShadow[err] = true; 486 | opt_onErrorFunc(err, functionName, arguments); 487 | } 488 | return result; 489 | }; 490 | } 491 | 492 | // Make a an object that has a copy of every property of the WebGL context 493 | // but wraps all functions. 494 | var wrapper = {}; 495 | for (var propertyName in ctx) { 496 | if (typeof ctx[propertyName] == 'function') { 497 | if (propertyName != 'getExtension') { 498 | wrapper[propertyName] = makeErrorWrapper(ctx, propertyName); 499 | } else { 500 | var wrapped = makeErrorWrapper(ctx, propertyName); 501 | wrapper[propertyName] = function () { 502 | var result = wrapped.apply(ctx, arguments); 503 | if (!result) { 504 | return null; 505 | } 506 | return makeDebugContext(result, opt_onErrorFunc, opt_onFunc, opt_err_ctx); 507 | }; 508 | } 509 | } else { 510 | makePropertyWrapper(wrapper, ctx, propertyName); 511 | } 512 | } 513 | 514 | // Override the getError function with one that returns our saved results. 515 | wrapper.getError = function() { 516 | for (var err in glErrorShadow) { 517 | if (glErrorShadow.hasOwnProperty(err)) { 518 | if (glErrorShadow[err]) { 519 | glErrorShadow[err] = false; 520 | return err; 521 | } 522 | } 523 | } 524 | return ctx.NO_ERROR; 525 | }; 526 | 527 | return wrapper; 528 | } 529 | 530 | function resetToInitialState(ctx) { 531 | var isWebGL2RenderingContext = !!ctx.createTransformFeedback; 532 | 533 | if (isWebGL2RenderingContext) { 534 | ctx.bindVertexArray(null); 535 | } 536 | 537 | var numAttribs = ctx.getParameter(ctx.MAX_VERTEX_ATTRIBS); 538 | var tmp = ctx.createBuffer(); 539 | ctx.bindBuffer(ctx.ARRAY_BUFFER, tmp); 540 | for (var ii = 0; ii < numAttribs; ++ii) { 541 | ctx.disableVertexAttribArray(ii); 542 | ctx.vertexAttribPointer(ii, 4, ctx.FLOAT, false, 0, 0); 543 | ctx.vertexAttrib1f(ii, 0); 544 | if (isWebGL2RenderingContext) { 545 | ctx.vertexAttribDivisor(ii, 0); 546 | } 547 | } 548 | ctx.deleteBuffer(tmp); 549 | 550 | var numTextureUnits = ctx.getParameter(ctx.MAX_TEXTURE_IMAGE_UNITS); 551 | for (var ii = 0; ii < numTextureUnits; ++ii) { 552 | ctx.activeTexture(ctx.TEXTURE0 + ii); 553 | ctx.bindTexture(ctx.TEXTURE_CUBE_MAP, null); 554 | ctx.bindTexture(ctx.TEXTURE_2D, null); 555 | if (isWebGL2RenderingContext) { 556 | ctx.bindTexture(ctx.TEXTURE_2D_ARRAY, null); 557 | ctx.bindTexture(ctx.TEXTURE_3D, null); 558 | ctx.bindSampler(ii, null); 559 | } 560 | } 561 | 562 | ctx.activeTexture(ctx.TEXTURE0); 563 | ctx.useProgram(null); 564 | ctx.bindBuffer(ctx.ARRAY_BUFFER, null); 565 | ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, null); 566 | ctx.bindFramebuffer(ctx.FRAMEBUFFER, null); 567 | ctx.bindRenderbuffer(ctx.RENDERBUFFER, null); 568 | ctx.disable(ctx.BLEND); 569 | ctx.disable(ctx.CULL_FACE); 570 | ctx.disable(ctx.DEPTH_TEST); 571 | ctx.disable(ctx.DITHER); 572 | ctx.disable(ctx.SCISSOR_TEST); 573 | ctx.blendColor(0, 0, 0, 0); 574 | ctx.blendEquation(ctx.FUNC_ADD); 575 | ctx.blendFunc(ctx.ONE, ctx.ZERO); 576 | ctx.clearColor(0, 0, 0, 0); 577 | ctx.clearDepth(1); 578 | ctx.clearStencil(-1); 579 | ctx.colorMask(true, true, true, true); 580 | ctx.cullFace(ctx.BACK); 581 | ctx.depthFunc(ctx.LESS); 582 | ctx.depthMask(true); 583 | ctx.depthRange(0, 1); 584 | ctx.frontFace(ctx.CCW); 585 | ctx.hint(ctx.GENERATE_MIPMAP_HINT, ctx.DONT_CARE); 586 | ctx.lineWidth(1); 587 | ctx.pixelStorei(ctx.PACK_ALIGNMENT, 4); 588 | ctx.pixelStorei(ctx.UNPACK_ALIGNMENT, 4); 589 | ctx.pixelStorei(ctx.UNPACK_FLIP_Y_WEBGL, false); 590 | ctx.pixelStorei(ctx.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); 591 | // TODO: Delete this IF. 592 | if (ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL) { 593 | ctx.pixelStorei(ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL, ctx.BROWSER_DEFAULT_WEBGL); 594 | } 595 | ctx.polygonOffset(0, 0); 596 | ctx.sampleCoverage(1, false); 597 | ctx.scissor(0, 0, ctx.canvas.width, ctx.canvas.height); 598 | ctx.stencilFunc(ctx.ALWAYS, 0, 0xFFFFFFFF); 599 | ctx.stencilMask(0xFFFFFFFF); 600 | ctx.stencilOp(ctx.KEEP, ctx.KEEP, ctx.KEEP); 601 | ctx.viewport(0, 0, ctx.canvas.width, ctx.canvas.height); 602 | ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT | ctx.STENCIL_BUFFER_BIT); 603 | 604 | if (isWebGL2RenderingContext) { 605 | ctx.drawBuffers([ctx.BACK]); 606 | ctx.readBuffer(ctx.BACK); 607 | ctx.bindBuffer(ctx.COPY_READ_BUFFER, null); 608 | ctx.bindBuffer(ctx.COPY_WRITE_BUFFER, null); 609 | ctx.bindBuffer(ctx.PIXEL_PACK_BUFFER, null); 610 | ctx.bindBuffer(ctx.PIXEL_UNPACK_BUFFER, null); 611 | var numTransformFeedbacks = ctx.getParameter(ctx.MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS); 612 | for (var ii = 0; ii < numTransformFeedbacks; ++ii) { 613 | ctx.bindBufferBase(ctx.TRANSFORM_FEEDBACK_BUFFER, ii, null); 614 | } 615 | var numUBOs = ctx.getParameter(ctx.MAX_UNIFORM_BUFFER_BINDINGS); 616 | for (var ii = 0; ii < numUBOs; ++ii) { 617 | ctx.bindBufferBase(ctx.UNIFORM_BUFFER, ii, null); 618 | } 619 | ctx.disable(ctx.RASTERIZER_DISCARD); 620 | ctx.pixelStorei(ctx.UNPACK_IMAGE_HEIGHT, 0); 621 | ctx.pixelStorei(ctx.UNPACK_SKIP_IMAGES, 0); 622 | ctx.pixelStorei(ctx.UNPACK_ROW_LENGTH, 0); 623 | ctx.pixelStorei(ctx.UNPACK_SKIP_ROWS, 0); 624 | ctx.pixelStorei(ctx.UNPACK_SKIP_PIXELS, 0); 625 | ctx.pixelStorei(ctx.PACK_ROW_LENGTH, 0); 626 | ctx.pixelStorei(ctx.PACK_SKIP_ROWS, 0); 627 | ctx.pixelStorei(ctx.PACK_SKIP_PIXELS, 0); 628 | ctx.hint(ctx.FRAGMENT_SHADER_DERIVATIVE_HINT, ctx.DONT_CARE); 629 | } 630 | 631 | // TODO: This should NOT be needed but Firefox fails with 'hint' 632 | while(ctx.getError()); 633 | } 634 | 635 | function makeLostContextSimulatingCanvas(canvas) { 636 | var unwrappedContext_; 637 | var wrappedContext_; 638 | var onLost_ = []; 639 | var onRestored_ = []; 640 | var wrappedContext_ = {}; 641 | var contextId_ = 1; 642 | var contextLost_ = false; 643 | var resourceId_ = 0; 644 | var resourceDb_ = []; 645 | var numCallsToLoseContext_ = 0; 646 | var numCalls_ = 0; 647 | var canRestore_ = false; 648 | var restoreTimeout_ = 0; 649 | var isWebGL2RenderingContext; 650 | 651 | // Holds booleans for each GL error so can simulate errors. 652 | var glErrorShadow_ = { }; 653 | 654 | canvas.getContext = function(f) { 655 | return function() { 656 | var ctx = f.apply(canvas, arguments); 657 | // Did we get a context and is it a WebGL context? 658 | if ((ctx instanceof WebGLRenderingContext) || (window.WebGL2RenderingContext && (ctx instanceof WebGL2RenderingContext))) { 659 | if (ctx != unwrappedContext_) { 660 | if (unwrappedContext_) { 661 | throw "got different context" 662 | } 663 | isWebGL2RenderingContext = window.WebGL2RenderingContext && (ctx instanceof WebGL2RenderingContext); 664 | unwrappedContext_ = ctx; 665 | wrappedContext_ = makeLostContextSimulatingContext(unwrappedContext_); 666 | } 667 | return wrappedContext_; 668 | } 669 | return ctx; 670 | } 671 | }(canvas.getContext); 672 | 673 | function wrapEvent(listener) { 674 | if (typeof(listener) == "function") { 675 | return listener; 676 | } else { 677 | return function(info) { 678 | listener.handleEvent(info); 679 | } 680 | } 681 | } 682 | 683 | var addOnContextLostListener = function(listener) { 684 | onLost_.push(wrapEvent(listener)); 685 | }; 686 | 687 | var addOnContextRestoredListener = function(listener) { 688 | onRestored_.push(wrapEvent(listener)); 689 | }; 690 | 691 | 692 | function wrapAddEventListener(canvas) { 693 | var f = canvas.addEventListener; 694 | canvas.addEventListener = function(type, listener, bubble) { 695 | switch (type) { 696 | case 'webglcontextlost': 697 | addOnContextLostListener(listener); 698 | break; 699 | case 'webglcontextrestored': 700 | addOnContextRestoredListener(listener); 701 | break; 702 | default: 703 | f.apply(canvas, arguments); 704 | } 705 | }; 706 | } 707 | 708 | wrapAddEventListener(canvas); 709 | 710 | canvas.loseContext = function() { 711 | if (!contextLost_) { 712 | contextLost_ = true; 713 | numCallsToLoseContext_ = 0; 714 | ++contextId_; 715 | while (unwrappedContext_.getError()); 716 | clearErrors(); 717 | glErrorShadow_[unwrappedContext_.CONTEXT_LOST_WEBGL] = true; 718 | var event = makeWebGLContextEvent("context lost"); 719 | var callbacks = onLost_.slice(); 720 | setTimeout(function() { 721 | //log("numCallbacks:" + callbacks.length); 722 | for (var ii = 0; ii < callbacks.length; ++ii) { 723 | //log("calling callback:" + ii); 724 | callbacks[ii](event); 725 | } 726 | if (restoreTimeout_ >= 0) { 727 | setTimeout(function() { 728 | canvas.restoreContext(); 729 | }, restoreTimeout_); 730 | } 731 | }, 0); 732 | } 733 | }; 734 | 735 | canvas.restoreContext = function() { 736 | if (contextLost_) { 737 | if (onRestored_.length) { 738 | setTimeout(function() { 739 | if (!canRestore_) { 740 | throw "can not restore. webglcontestlost listener did not call event.preventDefault"; 741 | } 742 | freeResources(); 743 | resetToInitialState(unwrappedContext_); 744 | contextLost_ = false; 745 | numCalls_ = 0; 746 | canRestore_ = false; 747 | var callbacks = onRestored_.slice(); 748 | var event = makeWebGLContextEvent("context restored"); 749 | for (var ii = 0; ii < callbacks.length; ++ii) { 750 | callbacks[ii](event); 751 | } 752 | }, 0); 753 | } 754 | } 755 | }; 756 | 757 | canvas.loseContextInNCalls = function(numCalls) { 758 | if (contextLost_) { 759 | throw "You can not ask a lost contet to be lost"; 760 | } 761 | numCallsToLoseContext_ = numCalls_ + numCalls; 762 | }; 763 | 764 | canvas.getNumCalls = function() { 765 | return numCalls_; 766 | }; 767 | 768 | canvas.setRestoreTimeout = function(timeout) { 769 | restoreTimeout_ = timeout; 770 | }; 771 | 772 | function isWebGLObject(obj) { 773 | //return false; 774 | return (obj instanceof WebGLBuffer || 775 | obj instanceof WebGLFramebuffer || 776 | obj instanceof WebGLProgram || 777 | obj instanceof WebGLRenderbuffer || 778 | obj instanceof WebGLShader || 779 | obj instanceof WebGLTexture); 780 | } 781 | 782 | function checkResources(args) { 783 | for (var ii = 0; ii < args.length; ++ii) { 784 | var arg = args[ii]; 785 | if (isWebGLObject(arg)) { 786 | return arg.__webglDebugContextLostId__ == contextId_; 787 | } 788 | } 789 | return true; 790 | } 791 | 792 | function clearErrors() { 793 | var k = Object.keys(glErrorShadow_); 794 | for (var ii = 0; ii < k.length; ++ii) { 795 | delete glErrorShadow_[k[ii]]; 796 | } 797 | } 798 | 799 | function loseContextIfTime() { 800 | ++numCalls_; 801 | if (!contextLost_) { 802 | if (numCallsToLoseContext_ == numCalls_) { 803 | canvas.loseContext(); 804 | } 805 | } 806 | } 807 | 808 | // Makes a function that simulates WebGL when out of context. 809 | function makeLostContextFunctionWrapper(ctx, functionName) { 810 | var f = ctx[functionName]; 811 | return function() { 812 | // log("calling:" + functionName); 813 | // Only call the functions if the context is not lost. 814 | loseContextIfTime(); 815 | if (!contextLost_) { 816 | //if (!checkResources(arguments)) { 817 | // glErrorShadow_[wrappedContext_.INVALID_OPERATION] = true; 818 | // return; 819 | //} 820 | var result = f.apply(ctx, arguments); 821 | return result; 822 | } 823 | }; 824 | } 825 | 826 | function freeResources() { 827 | for (var ii = 0; ii < resourceDb_.length; ++ii) { 828 | var resource = resourceDb_[ii]; 829 | if (resource instanceof WebGLBuffer) { 830 | unwrappedContext_.deleteBuffer(resource); 831 | } else if (resource instanceof WebGLFramebuffer) { 832 | unwrappedContext_.deleteFramebuffer(resource); 833 | } else if (resource instanceof WebGLProgram) { 834 | unwrappedContext_.deleteProgram(resource); 835 | } else if (resource instanceof WebGLRenderbuffer) { 836 | unwrappedContext_.deleteRenderbuffer(resource); 837 | } else if (resource instanceof WebGLShader) { 838 | unwrappedContext_.deleteShader(resource); 839 | } else if (resource instanceof WebGLTexture) { 840 | unwrappedContext_.deleteTexture(resource); 841 | } 842 | else if (isWebGL2RenderingContext) { 843 | if (resource instanceof WebGLQuery) { 844 | unwrappedContext_.deleteQuery(resource); 845 | } else if (resource instanceof WebGLSampler) { 846 | unwrappedContext_.deleteSampler(resource); 847 | } else if (resource instanceof WebGLSync) { 848 | unwrappedContext_.deleteSync(resource); 849 | } else if (resource instanceof WebGLTransformFeedback) { 850 | unwrappedContext_.deleteTransformFeedback(resource); 851 | } else if (resource instanceof WebGLVertexArrayObject) { 852 | unwrappedContext_.deleteVertexArray(resource); 853 | } 854 | } 855 | } 856 | } 857 | 858 | function makeWebGLContextEvent(statusMessage) { 859 | return { 860 | statusMessage: statusMessage, 861 | preventDefault: function() { 862 | canRestore_ = true; 863 | } 864 | }; 865 | } 866 | 867 | return canvas; 868 | 869 | function makeLostContextSimulatingContext(ctx) { 870 | // copy all functions and properties to wrapper 871 | for (var propertyName in ctx) { 872 | if (typeof ctx[propertyName] == 'function') { 873 | wrappedContext_[propertyName] = makeLostContextFunctionWrapper( 874 | ctx, propertyName); 875 | } else { 876 | makePropertyWrapper(wrappedContext_, ctx, propertyName); 877 | } 878 | } 879 | 880 | // Wrap a few functions specially. 881 | wrappedContext_.getError = function() { 882 | loseContextIfTime(); 883 | if (!contextLost_) { 884 | var err; 885 | while (err = unwrappedContext_.getError()) { 886 | glErrorShadow_[err] = true; 887 | } 888 | } 889 | for (var err in glErrorShadow_) { 890 | if (glErrorShadow_[err]) { 891 | delete glErrorShadow_[err]; 892 | return err; 893 | } 894 | } 895 | return wrappedContext_.NO_ERROR; 896 | }; 897 | 898 | var creationFunctions = [ 899 | "createBuffer", 900 | "createFramebuffer", 901 | "createProgram", 902 | "createRenderbuffer", 903 | "createShader", 904 | "createTexture" 905 | ]; 906 | if (isWebGL2RenderingContext) { 907 | creationFunctions.push( 908 | "createQuery", 909 | "createSampler", 910 | "fenceSync", 911 | "createTransformFeedback", 912 | "createVertexArray" 913 | ); 914 | } 915 | for (var ii = 0; ii < creationFunctions.length; ++ii) { 916 | var functionName = creationFunctions[ii]; 917 | wrappedContext_[functionName] = function(f) { 918 | return function() { 919 | loseContextIfTime(); 920 | if (contextLost_) { 921 | return null; 922 | } 923 | var obj = f.apply(ctx, arguments); 924 | obj.__webglDebugContextLostId__ = contextId_; 925 | resourceDb_.push(obj); 926 | return obj; 927 | }; 928 | }(ctx[functionName]); 929 | } 930 | 931 | var functionsThatShouldReturnNull = [ 932 | "getActiveAttrib", 933 | "getActiveUniform", 934 | "getBufferParameter", 935 | "getContextAttributes", 936 | "getAttachedShaders", 937 | "getFramebufferAttachmentParameter", 938 | "getParameter", 939 | "getProgramParameter", 940 | "getProgramInfoLog", 941 | "getRenderbufferParameter", 942 | "getShaderParameter", 943 | "getShaderInfoLog", 944 | "getShaderSource", 945 | "getTexParameter", 946 | "getUniform", 947 | "getUniformLocation", 948 | "getVertexAttrib" 949 | ]; 950 | if (isWebGL2RenderingContext) { 951 | functionsThatShouldReturnNull.push( 952 | "getInternalformatParameter", 953 | "getQuery", 954 | "getQueryParameter", 955 | "getSamplerParameter", 956 | "getSyncParameter", 957 | "getTransformFeedbackVarying", 958 | "getIndexedParameter", 959 | "getUniformIndices", 960 | "getActiveUniforms", 961 | "getActiveUniformBlockParameter", 962 | "getActiveUniformBlockName" 963 | ); 964 | } 965 | for (var ii = 0; ii < functionsThatShouldReturnNull.length; ++ii) { 966 | var functionName = functionsThatShouldReturnNull[ii]; 967 | wrappedContext_[functionName] = function(f) { 968 | return function() { 969 | loseContextIfTime(); 970 | if (contextLost_) { 971 | return null; 972 | } 973 | return f.apply(ctx, arguments); 974 | } 975 | }(wrappedContext_[functionName]); 976 | } 977 | 978 | var isFunctions = [ 979 | "isBuffer", 980 | "isEnabled", 981 | "isFramebuffer", 982 | "isProgram", 983 | "isRenderbuffer", 984 | "isShader", 985 | "isTexture" 986 | ]; 987 | if (isWebGL2RenderingContext) { 988 | isFunctions.push( 989 | "isQuery", 990 | "isSampler", 991 | "isSync", 992 | "isTransformFeedback", 993 | "isVertexArray" 994 | ); 995 | } 996 | for (var ii = 0; ii < isFunctions.length; ++ii) { 997 | var functionName = isFunctions[ii]; 998 | wrappedContext_[functionName] = function(f) { 999 | return function() { 1000 | loseContextIfTime(); 1001 | if (contextLost_) { 1002 | return false; 1003 | } 1004 | return f.apply(ctx, arguments); 1005 | } 1006 | }(wrappedContext_[functionName]); 1007 | } 1008 | 1009 | wrappedContext_.checkFramebufferStatus = function(f) { 1010 | return function() { 1011 | loseContextIfTime(); 1012 | if (contextLost_) { 1013 | return wrappedContext_.FRAMEBUFFER_UNSUPPORTED; 1014 | } 1015 | return f.apply(ctx, arguments); 1016 | }; 1017 | }(wrappedContext_.checkFramebufferStatus); 1018 | 1019 | wrappedContext_.getAttribLocation = function(f) { 1020 | return function() { 1021 | loseContextIfTime(); 1022 | if (contextLost_) { 1023 | return -1; 1024 | } 1025 | return f.apply(ctx, arguments); 1026 | }; 1027 | }(wrappedContext_.getAttribLocation); 1028 | 1029 | wrappedContext_.getVertexAttribOffset = function(f) { 1030 | return function() { 1031 | loseContextIfTime(); 1032 | if (contextLost_) { 1033 | return 0; 1034 | } 1035 | return f.apply(ctx, arguments); 1036 | }; 1037 | }(wrappedContext_.getVertexAttribOffset); 1038 | 1039 | wrappedContext_.isContextLost = function() { 1040 | return contextLost_; 1041 | }; 1042 | 1043 | if (isWebGL2RenderingContext) { 1044 | wrappedContext_.getFragDataLocation = function(f) { 1045 | return function() { 1046 | loseContextIfTime(); 1047 | if (contextLost_) { 1048 | return -1; 1049 | } 1050 | return f.apply(ctx, arguments); 1051 | }; 1052 | }(wrappedContext_.getFragDataLocation); 1053 | 1054 | wrappedContext_.clientWaitSync = function(f) { 1055 | return function() { 1056 | loseContextIfTime(); 1057 | if (contextLost_) { 1058 | return wrappedContext_.WAIT_FAILED; 1059 | } 1060 | return f.apply(ctx, arguments); 1061 | }; 1062 | }(wrappedContext_.clientWaitSync); 1063 | 1064 | wrappedContext_.getUniformBlockIndex = function(f) { 1065 | return function() { 1066 | loseContextIfTime(); 1067 | if (contextLost_) { 1068 | return wrappedContext_.INVALID_INDEX; 1069 | } 1070 | return f.apply(ctx, arguments); 1071 | }; 1072 | }(wrappedContext_.getUniformBlockIndex); 1073 | } 1074 | 1075 | return wrappedContext_; 1076 | } 1077 | } 1078 | 1079 | return { 1080 | /** 1081 | * Initializes this module. Safe to call more than once. 1082 | * @param {!WebGLRenderingContext} ctx A WebGL context. If 1083 | * you have more than one context it doesn't matter which one 1084 | * you pass in, it is only used to pull out constants. 1085 | */ 1086 | 'init': init, 1087 | 1088 | /** 1089 | * Returns true or false if value matches any WebGL enum 1090 | * @param {*} value Value to check if it might be an enum. 1091 | * @return {boolean} True if value matches one of the WebGL defined enums 1092 | */ 1093 | 'mightBeEnum': mightBeEnum, 1094 | 1095 | /** 1096 | * Gets an string version of an WebGL enum. 1097 | * 1098 | * Example: 1099 | * WebGLDebugUtil.init(ctx); 1100 | * var str = WebGLDebugUtil.glEnumToString(ctx.getError()); 1101 | * 1102 | * @param {number} value Value to return an enum for 1103 | * @return {string} The string version of the enum. 1104 | */ 1105 | 'glEnumToString': glEnumToString, 1106 | 1107 | /** 1108 | * Converts the argument of a WebGL function to a string. 1109 | * Attempts to convert enum arguments to strings. 1110 | * 1111 | * Example: 1112 | * WebGLDebugUtil.init(ctx); 1113 | * var str = WebGLDebugUtil.glFunctionArgToString('bindTexture', 2, 0, gl.TEXTURE_2D); 1114 | * 1115 | * would return 'TEXTURE_2D' 1116 | * 1117 | * @param {string} functionName the name of the WebGL function. 1118 | * @param {number} numArgs The number of arguments 1119 | * @param {number} argumentIndx the index of the argument. 1120 | * @param {*} value The value of the argument. 1121 | * @return {string} The value as a string. 1122 | */ 1123 | 'glFunctionArgToString': glFunctionArgToString, 1124 | 1125 | /** 1126 | * Converts the arguments of a WebGL function to a string. 1127 | * Attempts to convert enum arguments to strings. 1128 | * 1129 | * @param {string} functionName the name of the WebGL function. 1130 | * @param {number} args The arguments. 1131 | * @return {string} The arguments as a string. 1132 | */ 1133 | 'glFunctionArgsToString': glFunctionArgsToString, 1134 | 1135 | /** 1136 | * Given a WebGL context returns a wrapped context that calls 1137 | * gl.getError after every command and calls a function if the 1138 | * result is not NO_ERROR. 1139 | * 1140 | * You can supply your own function if you want. For example, if you'd like 1141 | * an exception thrown on any GL error you could do this 1142 | * 1143 | * function throwOnGLError(err, funcName, args) { 1144 | * throw WebGLDebugUtils.glEnumToString(err) + 1145 | * " was caused by call to " + funcName; 1146 | * }; 1147 | * 1148 | * ctx = WebGLDebugUtils.makeDebugContext( 1149 | * canvas.getContext("webgl"), throwOnGLError); 1150 | * 1151 | * @param {!WebGLRenderingContext} ctx The webgl context to wrap. 1152 | * @param {!function(err, funcName, args): void} opt_onErrorFunc The function 1153 | * to call when gl.getError returns an error. If not specified the default 1154 | * function calls console.log with a message. 1155 | * @param {!function(funcName, args): void} opt_onFunc The 1156 | * function to call when each webgl function is called. You 1157 | * can use this to log all calls for example. 1158 | */ 1159 | 'makeDebugContext': makeDebugContext, 1160 | 1161 | /** 1162 | * Given a canvas element returns a wrapped canvas element that will 1163 | * simulate lost context. The canvas returned adds the following functions. 1164 | * 1165 | * loseContext: 1166 | * simulates a lost context event. 1167 | * 1168 | * restoreContext: 1169 | * simulates the context being restored. 1170 | * 1171 | * lostContextInNCalls: 1172 | * loses the context after N gl calls. 1173 | * 1174 | * getNumCalls: 1175 | * tells you how many gl calls there have been so far. 1176 | * 1177 | * setRestoreTimeout: 1178 | * sets the number of milliseconds until the context is restored 1179 | * after it has been lost. Defaults to 0. Pass -1 to prevent 1180 | * automatic restoring. 1181 | * 1182 | * @param {!Canvas} canvas The canvas element to wrap. 1183 | */ 1184 | 'makeLostContextSimulatingCanvas': makeLostContextSimulatingCanvas, 1185 | 1186 | /** 1187 | * Resets a context to the initial state. 1188 | * @param {!WebGLRenderingContext} ctx The webgl context to 1189 | * reset. 1190 | */ 1191 | 'resetToInitialState': resetToInitialState 1192 | }; 1193 | 1194 | }(); 1195 | 1196 | --------------------------------------------------------------------------------