You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

573 lines
19 KiB

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