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