1/* 2** Copyright (c) 2012 The Khronos Group Inc. 3** 4** Permission is hereby granted, free of charge, to any person obtaining a 5** copy of this software and/or associated documentation files (the 6** "Materials"), to deal in the Materials without restriction, including 7** without limitation the rights to use, copy, modify, merge, publish, 8** distribute, sublicense, and/or sell copies of the Materials, and to 9** permit persons to whom the Materials are furnished to do so, subject to 10** the following conditions: 11** 12** The above copyright notice and this permission notice shall be included 13** in all copies or substantial portions of the Materials. 14** 15** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 19** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 20** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 21** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. 22*/ 23 24// Various functions for helping debug WebGL apps. 25 26WebGLDebugUtils = function() { 27 28/** 29 * Wrapped logging function. 30 * @param {string} msg Message to log. 31 */ 32var log = function(msg) { 33 if (window.console && window.console.log) { 34 window.console.log(msg); 35 } 36}; 37 38/** 39 * Wrapped error logging function. 40 * @param {string} msg Message to log. 41 */ 42var error = function(msg) { 43 if (window.console && window.console.error) { 44 window.console.error(msg); 45 } else { 46 log(msg); 47 } 48}; 49 50 51/** 52 * Which arguments are enums based on the number of arguments to the function. 53 * So 54 * 'texImage2D': { 55 * 9: { 0:true, 2:true, 6:true, 7:true }, 56 * 6: { 0:true, 2:true, 3:true, 4:true }, 57 * }, 58 * 59 * means if there are 9 arguments then 6 and 7 are enums, if there are 6 60 * arguments 3 and 4 are enums 61 * 62 * @type {!Object.<number, !Object.<number, string>}} 63 */ 64var glValidEnumContexts = { 65 // Generic setters and getters 66 67 'enable': {1: { 0:true }}, 68 'disable': {1: { 0:true }}, 69 'getParameter': {1: { 0:true }}, 70 71 // Rendering 72 73 'drawArrays': {3:{ 0:true }}, 74 'drawElements': {4:{ 0:true, 2:true }}, 75 76 // Shaders 77 78 'createShader': {1: { 0:true }}, 79 'getShaderParameter': {2: { 1:true }}, 80 'getProgramParameter': {2: { 1:true }}, 81 'getShaderPrecisionFormat': {2: { 0: true, 1:true }}, 82 83 // Vertex attributes 84 85 'getVertexAttrib': {2: { 1:true }}, 86 'vertexAttribPointer': {6: { 2:true }}, 87 88 // Textures 89 90 'bindTexture': {2: { 0:true }}, 91 'activeTexture': {1: { 0:true }}, 92 'getTexParameter': {2: { 0:true, 1:true }}, 93 'texParameterf': {3: { 0:true, 1:true }}, 94 'texParameteri': {3: { 0:true, 1:true, 2:true }}, 95 'texImage2D': { 96 9: { 0:true, 2:true, 6:true, 7:true }, 97 6: { 0:true, 2:true, 3:true, 4:true }, 98 }, 99 'texSubImage2D': { 100 9: { 0:true, 6:true, 7:true }, 101 7: { 0:true, 4:true, 5:true }, 102 }, 103 'copyTexImage2D': {8: { 0:true, 2:true }}, 104 'copyTexSubImage2D': {8: { 0:true }}, 105 'generateMipmap': {1: { 0:true }}, 106 'compressedTexImage2D': {7: { 0: true, 2:true }}, 107 'compressedTexSubImage2D': {8: { 0: true, 6:true }}, 108 109 // Buffer objects 110 111 'bindBuffer': {2: { 0:true }}, 112 'bufferData': {3: { 0:true, 2:true }}, 113 'bufferSubData': {3: { 0:true }}, 114 'getBufferParameter': {2: { 0:true, 1:true }}, 115 116 // Renderbuffers and framebuffers 117 118 'pixelStorei': {2: { 0:true, 1:true }}, 119 'readPixels': {7: { 4:true, 5:true }}, 120 'bindRenderbuffer': {2: { 0:true }}, 121 'bindFramebuffer': {2: { 0:true }}, 122 'checkFramebufferStatus': {1: { 0:true }}, 123 'framebufferRenderbuffer': {4: { 0:true, 1:true, 2:true }}, 124 'framebufferTexture2D': {5: { 0:true, 1:true, 2:true }}, 125 'getFramebufferAttachmentParameter': {3: { 0:true, 1:true, 2:true }}, 126 'getRenderbufferParameter': {2: { 0:true, 1:true }}, 127 'renderbufferStorage': {4: { 0:true, 1:true }}, 128 129 // Frame buffer operations (clear, blend, depth test, stencil) 130 131 'clear': {1: { 0:true }}, 132 'depthFunc': {1: { 0:true }}, 133 'blendFunc': {2: { 0:true, 1:true }}, 134 'blendFuncSeparate': {4: { 0:true, 1:true, 2:true, 3:true }}, 135 'blendEquation': {1: { 0:true }}, 136 'blendEquationSeparate': {2: { 0:true, 1:true }}, 137 'stencilFunc': {3: { 0:true }}, 138 'stencilFuncSeparate': {4: { 0:true, 1:true }}, 139 'stencilMaskSeparate': {2: { 0:true }}, 140 'stencilOp': {3: { 0:true, 1:true, 2:true }}, 141 'stencilOpSeparate': {4: { 0:true, 1:true, 2:true, 3:true }}, 142 143 // Culling 144 145 'cullFace': {1: { 0:true }}, 146 'frontFace': {1: { 0:true }}, 147}; 148 149/** 150 * Map of numbers to names. 151 * @type {Object} 152 */ 153var glEnums = null; 154 155/** 156 * Initializes this module. Safe to call more than once. 157 * @param {!WebGLRenderingContext} ctx A WebGL context. If 158 * you have more than one context it doesn't matter which one 159 * you pass in, it is only used to pull out constants. 160 */ 161function init(ctx) { 162 if (glEnums == null) { 163 glEnums = { }; 164 for (var propertyName in ctx) { 165 if (typeof ctx[propertyName] == 'number') { 166 glEnums[ctx[propertyName]] = propertyName; 167 } 168 } 169 } 170} 171 172/** 173 * Checks the utils have been initialized. 174 */ 175function checkInit() { 176 if (glEnums == null) { 177 throw 'WebGLDebugUtils.init(ctx) not called'; 178 } 179} 180 181/** 182 * Returns true or false if value matches any WebGL enum 183 * @param {*} value Value to check if it might be an enum. 184 * @return {boolean} True if value matches one of the WebGL defined enums 185 */ 186function mightBeEnum(value) { 187 checkInit(); 188 return (glEnums[value] !== undefined); 189} 190 191/** 192 * Gets an string version of an WebGL enum. 193 * 194 * Example: 195 * var str = WebGLDebugUtil.glEnumToString(ctx.getError()); 196 * 197 * @param {number} value Value to return an enum for 198 * @return {string} The string version of the enum. 199 */ 200function glEnumToString(value) { 201 checkInit(); 202 var name = glEnums[value]; 203 return (name !== undefined) ? ("gl." + name) : 204 ("/*UNKNOWN WebGL ENUM*/ 0x" + value.toString(16) + ""); 205} 206 207/** 208 * Returns the string version of a WebGL argument. 209 * Attempts to convert enum arguments to strings. 210 * @param {string} functionName the name of the WebGL function. 211 * @param {number} numArgs the number of arguments passed to the function. 212 * @param {number} argumentIndx the index of the argument. 213 * @param {*} value The value of the argument. 214 * @return {string} The value as a string. 215 */ 216function glFunctionArgToString(functionName, numArgs, argumentIndex, value) { 217 var funcInfo = glValidEnumContexts[functionName]; 218 if (funcInfo !== undefined) { 219 var funcInfo = funcInfo[numArgs]; 220 if (funcInfo !== undefined) { 221 if (funcInfo[argumentIndex]) { 222 return glEnumToString(value); 223 } 224 } 225 } 226 if (value === null) { 227 return "null"; 228 } else if (value === undefined) { 229 return "undefined"; 230 } else { 231 return value.toString(); 232 } 233} 234 235/** 236 * Converts the arguments of a WebGL function to a string. 237 * Attempts to convert enum arguments to strings. 238 * 239 * @param {string} functionName the name of the WebGL function. 240 * @param {number} args The arguments. 241 * @return {string} The arguments as a string. 242 */ 243function glFunctionArgsToString(functionName, args) { 244 // apparently we can't do args.join(","); 245 var argStr = ""; 246 var numArgs = args.length; 247 for (var ii = 0; ii < numArgs; ++ii) { 248 argStr += ((ii == 0) ? '' : ', ') + 249 glFunctionArgToString(functionName, numArgs, ii, args[ii]); 250 } 251 return argStr; 252}; 253 254 255function makePropertyWrapper(wrapper, original, propertyName) { 256 //log("wrap prop: " + propertyName); 257 wrapper.__defineGetter__(propertyName, function() { 258 return original[propertyName]; 259 }); 260 // TODO(gmane): this needs to handle properties that take more than 261 // one value? 262 wrapper.__defineSetter__(propertyName, function(value) { 263 //log("set: " + propertyName); 264 original[propertyName] = value; 265 }); 266} 267 268// Makes a function that calls a function on another object. 269function makeFunctionWrapper(original, functionName) { 270 //log("wrap fn: " + functionName); 271 var f = original[functionName]; 272 return function() { 273 //log("call: " + functionName); 274 var result = f.apply(original, arguments); 275 return result; 276 }; 277} 278 279/** 280 * Given a WebGL context returns a wrapped context that calls 281 * gl.getError after every command and calls a function if the 282 * result is not gl.NO_ERROR. 283 * 284 * @param {!WebGLRenderingContext} ctx The webgl context to 285 * wrap. 286 * @param {!function(err, funcName, args): void} opt_onErrorFunc 287 * The function to call when gl.getError returns an 288 * error. If not specified the default function calls 289 * console.log with a message. 290 * @param {!function(funcName, args): void} opt_onFunc The 291 * function to call when each webgl function is called. 292 * You can use this to log all calls for example. 293 */ 294function makeDebugContext(ctx, opt_onErrorFunc, opt_onFunc) { 295 init(ctx); 296 opt_onErrorFunc = opt_onErrorFunc || function(err, functionName, args) { 297 // apparently we can't do args.join(","); 298 var argStr = ""; 299 var numArgs = args.length; 300 for (var ii = 0; ii < numArgs; ++ii) { 301 argStr += ((ii == 0) ? '' : ', ') + 302 glFunctionArgToString(functionName, numArgs, ii, args[ii]); 303 } 304 error("WebGL error "+ glEnumToString(err) + " in "+ functionName + 305 "(" + argStr + ")"); 306 }; 307 308 // Holds booleans for each GL error so after we get the error ourselves 309 // we can still return it to the client app. 310 var glErrorShadow = { }; 311 312 // Makes a function that calls a WebGL function and then calls getError. 313 function makeErrorWrapper(ctx, functionName) { 314 return function() { 315 if (opt_onFunc) { 316 opt_onFunc(functionName, arguments); 317 } 318 var result = ctx[functionName].apply(ctx, arguments); 319 var err = ctx.getError(); 320 if (err != 0) { 321 glErrorShadow[err] = true; 322 opt_onErrorFunc(err, functionName, arguments); 323 } 324 return result; 325 }; 326 } 327 328 // Make a an object that has a copy of every property of the WebGL context 329 // but wraps all functions. 330 var wrapper = {}; 331 for (var propertyName in ctx) { 332 if (typeof ctx[propertyName] == 'function') { 333 wrapper[propertyName] = makeErrorWrapper(ctx, propertyName); 334 } else { 335 makePropertyWrapper(wrapper, ctx, propertyName); 336 } 337 } 338 339 // Override the getError function with one that returns our saved results. 340 wrapper.getError = function() { 341 for (var err in glErrorShadow) { 342 if (glErrorShadow.hasOwnProperty(err)) { 343 if (glErrorShadow[err]) { 344 glErrorShadow[err] = false; 345 return err; 346 } 347 } 348 } 349 return ctx.NO_ERROR; 350 }; 351 352 return wrapper; 353} 354 355function resetToInitialState(ctx) { 356 var numAttribs = ctx.getParameter(ctx.MAX_VERTEX_ATTRIBS); 357 var tmp = ctx.createBuffer(); 358 ctx.bindBuffer(ctx.ARRAY_BUFFER, tmp); 359 for (var ii = 0; ii < numAttribs; ++ii) { 360 ctx.disableVertexAttribArray(ii); 361 ctx.vertexAttribPointer(ii, 4, ctx.FLOAT, false, 0, 0); 362 ctx.vertexAttrib1f(ii, 0); 363 } 364 ctx.deleteBuffer(tmp); 365 366 var numTextureUnits = ctx.getParameter(ctx.MAX_TEXTURE_IMAGE_UNITS); 367 for (var ii = 0; ii < numTextureUnits; ++ii) { 368 ctx.activeTexture(ctx.TEXTURE0 + ii); 369 ctx.bindTexture(ctx.TEXTURE_CUBE_MAP, null); 370 ctx.bindTexture(ctx.TEXTURE_2D, null); 371 } 372 373 ctx.activeTexture(ctx.TEXTURE0); 374 ctx.useProgram(null); 375 ctx.bindBuffer(ctx.ARRAY_BUFFER, null); 376 ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, null); 377 ctx.bindFramebuffer(ctx.FRAMEBUFFER, null); 378 ctx.bindRenderbuffer(ctx.RENDERBUFFER, null); 379 ctx.disable(ctx.BLEND); 380 ctx.disable(ctx.CULL_FACE); 381 ctx.disable(ctx.DEPTH_TEST); 382 ctx.disable(ctx.DITHER); 383 ctx.disable(ctx.SCISSOR_TEST); 384 ctx.blendColor(0, 0, 0, 0); 385 ctx.blendEquation(ctx.FUNC_ADD); 386 ctx.blendFunc(ctx.ONE, ctx.ZERO); 387 ctx.clearColor(0, 0, 0, 0); 388 ctx.clearDepth(1); 389 ctx.clearStencil(-1); 390 ctx.colorMask(true, true, true, true); 391 ctx.cullFace(ctx.BACK); 392 ctx.depthFunc(ctx.LESS); 393 ctx.depthMask(true); 394 ctx.depthRange(0, 1); 395 ctx.frontFace(ctx.CCW); 396 ctx.hint(ctx.GENERATE_MIPMAP_HINT, ctx.DONT_CARE); 397 ctx.lineWidth(1); 398 ctx.pixelStorei(ctx.PACK_ALIGNMENT, 4); 399 ctx.pixelStorei(ctx.UNPACK_ALIGNMENT, 4); 400 ctx.pixelStorei(ctx.UNPACK_FLIP_Y_WEBGL, false); 401 ctx.pixelStorei(ctx.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); 402 // TODO: Delete this IF. 403 if (ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL) { 404 ctx.pixelStorei(ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL, ctx.BROWSER_DEFAULT_WEBGL); 405 } 406 ctx.polygonOffset(0, 0); 407 ctx.sampleCoverage(1, false); 408 ctx.scissor(0, 0, ctx.canvas.width, ctx.canvas.height); 409 ctx.stencilFunc(ctx.ALWAYS, 0, 0xFFFFFFFF); 410 ctx.stencilMask(0xFFFFFFFF); 411 ctx.stencilOp(ctx.KEEP, ctx.KEEP, ctx.KEEP); 412 ctx.viewport(0, 0, ctx.canvas.width, ctx.canvas.height); 413 ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT | ctx.STENCIL_BUFFER_BIT); 414 415 // TODO: This should NOT be needed but Firefox fails with 'hint' 416 while(ctx.getError()); 417} 418 419function makeLostContextSimulatingCanvas(canvas) { 420 var unwrappedContext_; 421 var wrappedContext_; 422 var onLost_ = []; 423 var onRestored_ = []; 424 var wrappedContext_ = {}; 425 var contextId_ = 1; 426 var contextLost_ = false; 427 var resourceId_ = 0; 428 var resourceDb_ = []; 429 var numCallsToLoseContext_ = 0; 430 var numCalls_ = 0; 431 var canRestore_ = false; 432 var restoreTimeout_ = 0; 433 434 // Holds booleans for each GL error so can simulate errors. 435 var glErrorShadow_ = { }; 436 437 canvas.getContext = function(f) { 438 return function() { 439 var ctx = f.apply(canvas, arguments); 440 // Did we get a context and is it a WebGL context? 441 if (ctx instanceof WebGLRenderingContext) { 442 if (ctx != unwrappedContext_) { 443 if (unwrappedContext_) { 444 throw "got different context" 445 } 446 unwrappedContext_ = ctx; 447 wrappedContext_ = makeLostContextSimulatingContext(unwrappedContext_); 448 } 449 return wrappedContext_; 450 } 451 return ctx; 452 } 453 }(canvas.getContext); 454 455 function wrapEvent(listener) { 456 if (typeof(listener) == "function") { 457 return listener; 458 } else { 459 return function(info) { 460 listener.handleEvent(info); 461 } 462 } 463 } 464 465 var addOnContextLostListener = function(listener) { 466 onLost_.push(wrapEvent(listener)); 467 }; 468 469 var addOnContextRestoredListener = function(listener) { 470 onRestored_.push(wrapEvent(listener)); 471 }; 472 473 474 function wrapAddEventListener(canvas) { 475 var f = canvas.addEventListener; 476 canvas.addEventListener = function(type, listener, bubble) { 477 switch (type) { 478 case 'webglcontextlost': 479 addOnContextLostListener(listener); 480 break; 481 case 'webglcontextrestored': 482 addOnContextRestoredListener(listener); 483 break; 484 default: 485 f.apply(canvas, arguments); 486 } 487 }; 488 } 489 490 wrapAddEventListener(canvas); 491 492 canvas.loseContext = function() { 493 if (!contextLost_) { 494 contextLost_ = true; 495 numCallsToLoseContext_ = 0; 496 ++contextId_; 497 while (unwrappedContext_.getError()); 498 clearErrors(); 499 glErrorShadow_[unwrappedContext_.CONTEXT_LOST_WEBGL] = true; 500 var event = makeWebGLContextEvent("context lost"); 501 var callbacks = onLost_.slice(); 502 setTimeout(function() { 503 //log("numCallbacks:" + callbacks.length); 504 for (var ii = 0; ii < callbacks.length; ++ii) { 505 //log("calling callback:" + ii); 506 callbacks[ii](event); 507 } 508 if (restoreTimeout_ >= 0) { 509 setTimeout(function() { 510 canvas.restoreContext(); 511 }, restoreTimeout_); 512 } 513 }, 0); 514 } 515 }; 516 517 canvas.restoreContext = function() { 518 if (contextLost_) { 519 if (onRestored_.length) { 520 setTimeout(function() { 521 if (!canRestore_) { 522 throw "can not restore. webglcontestlost listener did not call event.preventDefault"; 523 } 524 freeResources(); 525 resetToInitialState(unwrappedContext_); 526 contextLost_ = false; 527 numCalls_ = 0; 528 canRestore_ = false; 529 var callbacks = onRestored_.slice(); 530 var event = makeWebGLContextEvent("context restored"); 531 for (var ii = 0; ii < callbacks.length; ++ii) { 532 callbacks[ii](event); 533 } 534 }, 0); 535 } 536 } 537 }; 538 539 canvas.loseContextInNCalls = function(numCalls) { 540 if (contextLost_) { 541 throw "You can not ask a lost contet to be lost"; 542 } 543 numCallsToLoseContext_ = numCalls_ + numCalls; 544 }; 545 546 canvas.getNumCalls = function() { 547 return numCalls_; 548 }; 549 550 canvas.setRestoreTimeout = function(timeout) { 551 restoreTimeout_ = timeout; 552 }; 553 554 function isWebGLObject(obj) { 555 //return false; 556 return (obj instanceof WebGLBuffer || 557 obj instanceof WebGLFramebuffer || 558 obj instanceof WebGLProgram || 559 obj instanceof WebGLRenderbuffer || 560 obj instanceof WebGLShader || 561 obj instanceof WebGLTexture); 562 } 563 564 function checkResources(args) { 565 for (var ii = 0; ii < args.length; ++ii) { 566 var arg = args[ii]; 567 if (isWebGLObject(arg)) { 568 return arg.__webglDebugContextLostId__ == contextId_; 569 } 570 } 571 return true; 572 } 573 574 function clearErrors() { 575 var k = Object.keys(glErrorShadow_); 576 for (var ii = 0; ii < k.length; ++ii) { 577 delete glErrorShadow_[k]; 578 } 579 } 580 581 function loseContextIfTime() { 582 ++numCalls_; 583 if (!contextLost_) { 584 if (numCallsToLoseContext_ == numCalls_) { 585 canvas.loseContext(); 586 } 587 } 588 } 589 590 // Makes a function that simulates WebGL when out of context. 591 function makeLostContextFunctionWrapper(ctx, functionName) { 592 var f = ctx[functionName]; 593 return function() { 594 // log("calling:" + functionName); 595 // Only call the functions if the context is not lost. 596 loseContextIfTime(); 597 if (!contextLost_) { 598 //if (!checkResources(arguments)) { 599 // glErrorShadow_[wrappedContext_.INVALID_OPERATION] = true; 600 // return; 601 //} 602 var result = f.apply(ctx, arguments); 603 return result; 604 } 605 }; 606 } 607 608 function freeResources() { 609 for (var ii = 0; ii < resourceDb_.length; ++ii) { 610 var resource = resourceDb_[ii]; 611 if (resource instanceof WebGLBuffer) { 612 unwrappedContext_.deleteBuffer(resource); 613 } else if (resource instanceof WebGLFramebuffer) { 614 unwrappedContext_.deleteFramebuffer(resource); 615 } else if (resource instanceof WebGLProgram) { 616 unwrappedContext_.deleteProgram(resource); 617 } else if (resource instanceof WebGLRenderbuffer) { 618 unwrappedContext_.deleteRenderbuffer(resource); 619 } else if (resource instanceof WebGLShader) { 620 unwrappedContext_.deleteShader(resource); 621 } else if (resource instanceof WebGLTexture) { 622 unwrappedContext_.deleteTexture(resource); 623 } 624 } 625 } 626 627 function makeWebGLContextEvent(statusMessage) { 628 return { 629 statusMessage: statusMessage, 630 preventDefault: function() { 631 canRestore_ = true; 632 } 633 }; 634 } 635 636 return canvas; 637 638 function makeLostContextSimulatingContext(ctx) { 639 // copy all functions and properties to wrapper 640 for (var propertyName in ctx) { 641 if (typeof ctx[propertyName] == 'function') { 642 wrappedContext_[propertyName] = makeLostContextFunctionWrapper( 643 ctx, propertyName); 644 } else { 645 makePropertyWrapper(wrappedContext_, ctx, propertyName); 646 } 647 } 648 649 // Wrap a few functions specially. 650 wrappedContext_.getError = function() { 651 loseContextIfTime(); 652 if (!contextLost_) { 653 var err; 654 while (err = unwrappedContext_.getError()) { 655 glErrorShadow_[err] = true; 656 } 657 } 658 for (var err in glErrorShadow_) { 659 if (glErrorShadow_[err]) { 660 delete glErrorShadow_[err]; 661 return err; 662 } 663 } 664 return wrappedContext_.NO_ERROR; 665 }; 666 667 var creationFunctions = [ 668 "createBuffer", 669 "createFramebuffer", 670 "createProgram", 671 "createRenderbuffer", 672 "createShader", 673 "createTexture" 674 ]; 675 for (var ii = 0; ii < creationFunctions.length; ++ii) { 676 var functionName = creationFunctions[ii]; 677 wrappedContext_[functionName] = function(f) { 678 return function() { 679 loseContextIfTime(); 680 if (contextLost_) { 681 return null; 682 } 683 var obj = f.apply(ctx, arguments); 684 obj.__webglDebugContextLostId__ = contextId_; 685 resourceDb_.push(obj); 686 return obj; 687 }; 688 }(ctx[functionName]); 689 } 690 691 var functionsThatShouldReturnNull = [ 692 "getActiveAttrib", 693 "getActiveUniform", 694 "getBufferParameter", 695 "getContextAttributes", 696 "getAttachedShaders", 697 "getFramebufferAttachmentParameter", 698 "getParameter", 699 "getProgramParameter", 700 "getProgramInfoLog", 701 "getRenderbufferParameter", 702 "getShaderParameter", 703 "getShaderInfoLog", 704 "getShaderSource", 705 "getTexParameter", 706 "getUniform", 707 "getUniformLocation", 708 "getVertexAttrib" 709 ]; 710 for (var ii = 0; ii < functionsThatShouldReturnNull.length; ++ii) { 711 var functionName = functionsThatShouldReturnNull[ii]; 712 wrappedContext_[functionName] = function(f) { 713 return function() { 714 loseContextIfTime(); 715 if (contextLost_) { 716 return null; 717 } 718 return f.apply(ctx, arguments); 719 } 720 }(wrappedContext_[functionName]); 721 } 722 723 var isFunctions = [ 724 "isBuffer", 725 "isEnabled", 726 "isFramebuffer", 727 "isProgram", 728 "isRenderbuffer", 729 "isShader", 730 "isTexture" 731 ]; 732 for (var ii = 0; ii < isFunctions.length; ++ii) { 733 var functionName = isFunctions[ii]; 734 wrappedContext_[functionName] = function(f) { 735 return function() { 736 loseContextIfTime(); 737 if (contextLost_) { 738 return false; 739 } 740 return f.apply(ctx, arguments); 741 } 742 }(wrappedContext_[functionName]); 743 } 744 745 wrappedContext_.checkFramebufferStatus = function(f) { 746 return function() { 747 loseContextIfTime(); 748 if (contextLost_) { 749 return wrappedContext_.FRAMEBUFFER_UNSUPPORTED; 750 } 751 return f.apply(ctx, arguments); 752 }; 753 }(wrappedContext_.checkFramebufferStatus); 754 755 wrappedContext_.getAttribLocation = function(f) { 756 return function() { 757 loseContextIfTime(); 758 if (contextLost_) { 759 return -1; 760 } 761 return f.apply(ctx, arguments); 762 }; 763 }(wrappedContext_.getAttribLocation); 764 765 wrappedContext_.getVertexAttribOffset = function(f) { 766 return function() { 767 loseContextIfTime(); 768 if (contextLost_) { 769 return 0; 770 } 771 return f.apply(ctx, arguments); 772 }; 773 }(wrappedContext_.getVertexAttribOffset); 774 775 wrappedContext_.isContextLost = function() { 776 return contextLost_; 777 }; 778 779 return wrappedContext_; 780 } 781} 782 783return { 784 /** 785 * Initializes this module. Safe to call more than once. 786 * @param {!WebGLRenderingContext} ctx A WebGL context. If 787 } 788 * you have more than one context it doesn't matter which one 789 * you pass in, it is only used to pull out constants. 790 */ 791 'init': init, 792 793 /** 794 * Returns true or false if value matches any WebGL enum 795 * @param {*} value Value to check if it might be an enum. 796 * @return {boolean} True if value matches one of the WebGL defined enums 797 */ 798 'mightBeEnum': mightBeEnum, 799 800 /** 801 * Gets an string version of an WebGL enum. 802 * 803 * Example: 804 * WebGLDebugUtil.init(ctx); 805 * var str = WebGLDebugUtil.glEnumToString(ctx.getError()); 806 * 807 * @param {number} value Value to return an enum for 808 * @return {string} The string version of the enum. 809 */ 810 'glEnumToString': glEnumToString, 811 812 /** 813 * Converts the argument of a WebGL function to a string. 814 * Attempts to convert enum arguments to strings. 815 * 816 * Example: 817 * WebGLDebugUtil.init(ctx); 818 * var str = WebGLDebugUtil.glFunctionArgToString('bindTexture', 2, 0, gl.TEXTURE_2D); 819 * 820 * would return 'TEXTURE_2D' 821 * 822 * @param {string} functionName the name of the WebGL function. 823 * @param {number} numArgs The number of arguments 824 * @param {number} argumentIndx the index of the argument. 825 * @param {*} value The value of the argument. 826 * @return {string} The value as a string. 827 */ 828 'glFunctionArgToString': glFunctionArgToString, 829 830 /** 831 * Converts the arguments of a WebGL function to a string. 832 * Attempts to convert enum arguments to strings. 833 * 834 * @param {string} functionName the name of the WebGL function. 835 * @param {number} args The arguments. 836 * @return {string} The arguments as a string. 837 */ 838 'glFunctionArgsToString': glFunctionArgsToString, 839 840 /** 841 * Given a WebGL context returns a wrapped context that calls 842 * gl.getError after every command and calls a function if the 843 * result is not NO_ERROR. 844 * 845 * You can supply your own function if you want. For example, if you'd like 846 * an exception thrown on any GL error you could do this 847 * 848 * function throwOnGLError(err, funcName, args) { 849 * throw WebGLDebugUtils.glEnumToString(err) + 850 * " was caused by call to " + funcName; 851 * }; 852 * 853 * ctx = WebGLDebugUtils.makeDebugContext( 854 * canvas.getContext("webgl"), throwOnGLError); 855 * 856 * @param {!WebGLRenderingContext} ctx The webgl context to wrap. 857 * @param {!function(err, funcName, args): void} opt_onErrorFunc The function 858 * to call when gl.getError returns an error. If not specified the default 859 * function calls console.log with a message. 860 * @param {!function(funcName, args): void} opt_onFunc The 861 * function to call when each webgl function is called. You 862 * can use this to log all calls for example. 863 */ 864 'makeDebugContext': makeDebugContext, 865 866 /** 867 * Given a canvas element returns a wrapped canvas element that will 868 * simulate lost context. The canvas returned adds the following functions. 869 * 870 * loseContext: 871 * simulates a lost context event. 872 * 873 * restoreContext: 874 * simulates the context being restored. 875 * 876 * lostContextInNCalls: 877 * loses the context after N gl calls. 878 * 879 * getNumCalls: 880 * tells you how many gl calls there have been so far. 881 * 882 * setRestoreTimeout: 883 * sets the number of milliseconds until the context is restored 884 * after it has been lost. Defaults to 0. Pass -1 to prevent 885 * automatic restoring. 886 * 887 * @param {!Canvas} canvas The canvas element to wrap. 888 */ 889 'makeLostContextSimulatingCanvas': makeLostContextSimulatingCanvas, 890 891 /** 892 * Resets a context to the initial state. 893 * @param {!WebGLRenderingContext} ctx The webgl context to 894 * reset. 895 */ 896 'resetToInitialState': resetToInitialState 897}; 898 899}(); 900 901