Skip to content

Commit da9e8a9

Browse files
authored
Merge pull request #5529 from juj/trace_webgl
Multithreading 4/N: Trace WebGL calls with pthread_self() info
2 parents 13bb686 + 46fbabf commit da9e8a9

File tree

3 files changed

+80
-90
lines changed

3 files changed

+80
-90
lines changed

src/library_gl.js

Lines changed: 77 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,78 @@ var LibraryGL = {
410410
}
411411
},
412412
#endif
413-
413+
414+
#if TRACE_WEBGL_CALLS
415+
webGLFunctionLengths: { 'getContextAttributes': 0, 'isContextLost': 0, 'getSupportedExtensions': 0, 'createBuffer': 0, 'createFramebuffer': 0, 'createProgram': 0, 'createRenderbuffer': 0, 'createTexture': 0, 'finish': 0, 'flush': 0, 'getError': 0, 'createVertexArray': 0, 'createQuery': 0, 'createSampler': 0, 'createTransformFeedback': 0, 'endTransformFeedback': 0, 'pauseTransformFeedback': 0, 'resumeTransformFeedback': 0, 'commit': 0,
416+
'getExtension': 1, 'activeTexture': 1, 'blendEquation': 1, 'checkFramebufferStatus': 1, 'clear': 1, 'clearDepth': 1, 'clearStencil': 1, 'compileShader': 1, 'createShader': 1, 'cullFace': 1, 'deleteBuffer': 1, 'deleteFramebuffer': 1, 'deleteProgram': 1, 'deleteRenderbuffer': 1, 'deleteShader': 1, 'deleteTexture': 1, 'depthFunc': 1, 'depthMask': 1, 'disable': 1, 'disableVertexAttribArray': 1, 'enable': 1, 'enableVertexAttribArray': 1, 'frontFace': 1, 'generateMipmap': 1, 'getAttachedShaders': 1, 'getParameter': 1, 'getProgramInfoLog': 1, 'getShaderInfoLog': 1, 'getShaderSource': 1, 'isBuffer': 1, 'isEnabled': 1, 'isFramebuffer': 1, 'isProgram': 1, 'isRenderbuffer': 1, 'isShader': 1, 'isTexture': 1, 'lineWidth': 1, 'linkProgram': 1, 'stencilMask': 1, 'useProgram': 1, 'validateProgram': 1, 'deleteQuery': 1, 'isQuery': 1, 'deleteVertexArray': 1, 'bindVertexArray': 1, 'isVertexArray': 1, 'drawBuffers': 1, 'readBuffer': 1, 'endQuery': 1, 'deleteSampler': 1, 'isSampler': 1, 'isSync': 1, 'deleteSync': 1, 'deleteTransformFeedback': 1, 'isTransformFeedback': 1, 'beginTransformFeedback': 1,
417+
'attachShader': 2, 'bindBuffer': 2, 'bindFramebuffer': 2, 'bindRenderbuffer': 2, 'bindTexture': 2, 'blendEquationSeparate': 2, 'blendFunc': 2, 'depthRange': 2, 'detachShader': 2, 'getActiveAttrib': 2, 'getActiveUniform': 2, 'getAttribLocation': 2, 'getBufferParameter': 2, 'getProgramParameter': 2, 'getRenderbufferParameter': 2, 'getShaderParameter': 2, 'getShaderPrecisionFormat': 2, 'getTexParameter': 2, 'getUniform': 2, 'getUniformLocation': 2, 'getVertexAttrib': 2, 'getVertexAttribOffset': 2, 'hint': 2, 'pixelStorei': 2, 'polygonOffset': 2, 'sampleCoverage': 2, 'shaderSource': 2, 'stencilMaskSeparate': 2, 'uniform1f': 2, 'uniform1fv': 2, 'uniform1i': 2, 'uniform1iv': 2, 'uniform2fv': 2, 'uniform2iv': 2, 'uniform3fv': 2, 'uniform3iv': 2, 'uniform4fv': 2, 'uniform4iv': 2, 'vertexAttrib1f': 2, 'vertexAttrib1fv': 2, 'vertexAttrib2fv': 2, 'vertexAttrib3fv': 2, 'vertexAttrib4fv': 2, 'vertexAttribDivisor': 2, 'beginQuery': 2, 'invalidateFramebuffer': 2, 'getFragDataLocation': 2, 'uniform1ui': 2, 'uniform1uiv': 2, 'uniform2uiv': 2, 'uniform3uiv': 2, 'uniform4uiv': 2, 'vertexAttribI4iv': 2, 'vertexAttribI4uiv': 2, 'getQuery': 2, 'getQueryParameter': 2, 'bindSampler': 2, 'getSamplerParameter': 2, 'fenceSync': 2, 'getSyncParameter': 2, 'bindTransformFeedback': 2, 'getTransformFeedbackVarying': 2, 'getIndexedParameter': 2, 'getUniformIndices': 2, 'getUniformBlockIndex': 2, 'getActiveUniformBlockName': 2,
418+
'bindAttribLocation': 3, 'bufferData': 3, 'bufferSubData': 3, 'drawArrays': 3, 'getFramebufferAttachmentParameter': 3, 'stencilFunc': 3, 'stencilOp': 3, 'texParameterf': 3, 'texParameteri': 3, 'uniform2f': 3, 'uniform2i': 3, 'uniformMatrix2fv': 3, 'uniformMatrix3fv': 3, 'uniformMatrix4fv': 3, 'vertexAttrib2f': 3, 'getBufferSubData': 3, 'getInternalformatParameter': 3, 'uniform2ui': 3, 'uniformMatrix2x3fv': 3, 'uniformMatrix3x2fv': 3, 'uniformMatrix2x4fv': 3, 'uniformMatrix4x2fv': 3, 'uniformMatrix3x4fv': 3, 'uniformMatrix4x3fv': 3, 'clearBufferiv': 3, 'clearBufferuiv': 3, 'clearBufferfv': 3, 'samplerParameteri': 3, 'samplerParameterf': 3, 'clientWaitSync': 3, 'waitSync': 3, 'transformFeedbackVaryings': 3, 'bindBufferBase': 3, 'getActiveUniforms': 3, 'getActiveUniformBlockParameter': 3, 'uniformBlockBinding': 3,
419+
'blendColor': 4, 'blendFuncSeparate': 4, 'clearColor': 4, 'colorMask': 4, 'drawElements': 4, 'framebufferRenderbuffer': 4, 'renderbufferStorage': 4, 'scissor': 4, 'stencilFuncSeparate': 4, 'stencilOpSeparate': 4, 'uniform3f': 4, 'uniform3i': 4, 'vertexAttrib3f': 4, 'viewport': 4, 'drawArraysInstanced': 4, 'uniform3ui': 4, 'clearBufferfi': 4,
420+
'framebufferTexture2D': 5, 'uniform4f': 5, 'uniform4i': 5, 'vertexAttrib4f': 5, 'drawElementsInstanced': 5, 'copyBufferSubData': 5, 'framebufferTextureLayer': 5, 'renderbufferStorageMultisample': 5, 'texStorage2D': 5, 'uniform4ui': 5, 'vertexAttribI4i': 5, 'vertexAttribI4ui': 5, 'vertexAttribIPointer': 5, 'bindBufferRange': 5,
421+
'texImage2D': 6, 'vertexAttribPointer': 6, 'invalidateSubFramebuffer': 6, 'texStorage3D': 6, 'drawRangeElements': 6,
422+
'compressedTexImage2D': 7, 'readPixels': 7, 'texSubImage2D': 7,
423+
'compressedTexSubImage2D': 8, 'copyTexImage2D': 8, 'copyTexSubImage2D': 8, 'compressedTexImage3D': 8,
424+
'copyTexSubImage3D': 9,
425+
'blitFramebuffer': 10, 'texImage3D': 10, 'compressedTexSubImage3D': 10,
426+
'texSubImage3D': 11 },
427+
428+
hookWebGLFunction: function(f, glCtx) {
429+
var realf = 'real_' + f;
430+
glCtx[realf] = glCtx[f];
431+
var numArgs = GL.webGLFunctionLengths[f]; // On Firefox & Chrome, could do "glCtx[realf].length", but that doesn't work on Edge, which always reports 0.
432+
if (numArgs === undefined) throw 'Unexpected WebGL function ' + f;
433+
var contextHandle = glCtx.canvas.GLctxObject.handle;
434+
var threadId = (typeof _pthread_self !== 'undefined') ? _pthread_self : function() { return 1; };
435+
// Accessing 'arguments' is super slow, so to avoid overhead, statically reason the number of arguments.
436+
switch(numArgs) {
437+
case 0: glCtx[f] = function webgl_0() { var ret = glCtx[realf](); console.error('[Thread ' + threadId() + ', GL ctx: ' + contextHandle + ']: ' + f + '() -> ' + ret); return ret; }; break;
438+
case 1: glCtx[f] = function webgl_1(a1) { var ret = glCtx[realf](a1); console.error('[Thread ' + threadId() + ', GL ctx: ' + contextHandle + ']: ' + f + '('+a1+') -> ' + ret); return ret; }; break;
439+
case 2: glCtx[f] = function webgl_2(a1, a2) { var ret = glCtx[realf](a1, a2); console.error('[Thread ' + threadId() + ', GL ctx: ' + contextHandle + ']: ' + f + '('+a1+', ' + a2 +') -> ' + ret); return ret; }; break;
440+
case 3: glCtx[f] = function webgl_3(a1, a2, a3) { var ret = glCtx[realf](a1, a2, a3); console.error('[Thread ' + threadId() + ', GL ctx: ' + contextHandle + ']: ' + f + '('+a1+', ' + a2 +', ' + a3 +') -> ' + ret); return ret; }; break;
441+
case 4: glCtx[f] = function webgl_4(a1, a2, a3, a4) { var ret = glCtx[realf](a1, a2, a3, a4); console.error('[Thread ' + threadId() + ', GL ctx: ' + contextHandle + ']: ' + f + '('+a1+', ' + a2 +', ' + a3 +', ' + a4 +') -> ' + ret); return ret; }; break;
442+
case 5: glCtx[f] = function webgl_5(a1, a2, a3, a4, a5) { var ret = glCtx[realf](a1, a2, a3, a4, a5); console.error('[Thread ' + threadId() + ', GL ctx: ' + contextHandle + ']: ' + f + '('+a1+', ' + a2 +', ' + a3 +', ' + a4 +', ' + a5 +') -> ' + ret); return ret; }; break;
443+
case 6: glCtx[f] = function webgl_6(a1, a2, a3, a4, a5, a6) { var ret = glCtx[realf](a1, a2, a3, a4, a5, a6); console.error('[Thread ' + threadId() + ', GL ctx: ' + contextHandle + ']: ' + f + '('+a1+', ' + a2 +', ' + a3 +', ' + a4 +', ' + a5 +', ' + a6 +') -> ' + ret); return ret; }; break;
444+
case 7: glCtx[f] = function webgl_7(a1, a2, a3, a4, a5, a6, a7) { var ret = glCtx[realf](a1, a2, a3, a4, a5, a6, a7); console.error('[Thread ' + threadId() + ', GL ctx: ' + contextHandle + ']: ' + f + '('+a1+', ' + a2 +', ' + a3 +', ' + a4 +', ' + a5 +', ' + a6 +', ' + a7 +') -> ' + ret); return ret; }; break;
445+
case 8: glCtx[f] = function webgl_8(a1, a2, a3, a4, a5, a6, a7, a8) { var ret = glCtx[realf](a1, a2, a3, a4, a5, a6, a7, a8); console.error('[Thread ' + threadId() + ', GL ctx: ' + contextHandle + ']: ' + f + '('+a1+', ' + a2 +', ' + a3 +', ' + a4 +', ' + a5 +', ' + a6 +', ' + a7 +', ' + a8 +') -> ' + ret); return ret; }; break;
446+
case 9: glCtx[f] = function webgl_9(a1, a2, a3, a4, a5, a6, a7, a8, a9) { var ret = glCtx[realf](a1, a2, a3, a4, a5, a6, a7, a8, a9); console.error('[Thread ' + threadId() + ', GL ctx: ' + contextHandle + ']: ' + f + '('+a1+', ' + a2 +', ' + a3 +', ' + a4 +', ' + a5 +', ' + a6 +', ' + a7 +', ' + a8 +', ' + a9 +') -> ' + ret); return ret; }; break;
447+
case 10: glCtx[f] = function webgl_10(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { var ret = glCtx[realf](a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); console.error('[Thread ' + threadId() + ', GL ctx: ' + contextHandle + ']: ' + f + '('+a1+', ' + a2 +', ' + a3 +', ' + a4 +', ' + a5 +', ' + a6 +', ' + a7 +', ' + a8 +', ' + a9 +', ' + a10 +') -> ' + ret); return ret; }; break;
448+
case 11: glCtx[f] = function webgl_11(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) { var ret = glCtx[realf](a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11); console.error('[Thread ' + threadId() + ', GL ctx: ' + contextHandle + ']: ' + f + '('+a1+', ' + a2 +', ' + a3 +', ' + a4 +', ' + a5 +', ' + a6 +', ' + a7 +', ' + a8 +', ' + a9 +', ' + a10 +', ' + a11 +') -> ' + ret); return ret; }; break;
449+
default: throw 'hookWebGL failed! Unexpected length ' + glCtx[realf].length;
450+
}
451+
},
452+
453+
hookWebGL: function(glCtx) {
454+
if (!glCtx) glCtx = this.detectWebGLContext();
455+
if (!glCtx) return;
456+
if (!((typeof WebGLRenderingContext !== 'undefined' && glCtx instanceof WebGLRenderingContext)
457+
|| (typeof WebGL2RenderingContext !== 'undefined' && glCtx instanceof WebGL2RenderingContext))) {
458+
return;
459+
}
460+
461+
if (glCtx.webGlTracerAlreadyHooked) return;
462+
glCtx.webGlTracerAlreadyHooked = true;
463+
464+
// Hot GL functions are ones that you'd expect to find during render loops (render calls, dynamic resource uploads), cold GL functions are load time functions (shader compilation, texture/mesh creation)
465+
// Distinguishing between these two allows pinpointing locations of troublesome GL usage that might cause performance issues.
466+
for(var f in glCtx) {
467+
if (typeof glCtx[f] !== 'function' || f.indexOf('real_') == 0) continue;
468+
this.hookWebGLFunction(f, glCtx);
469+
}
470+
// The above injection won't work for texImage2D and texSubImage2D, which have multiple overloads.
471+
glCtx['texImage2D'] = function(a1, a2, a3, a4, a5, a6, a7, a8, a9) {
472+
var ret = (a7 !== undefined) ? glCtx['real_texImage2D'](a1, a2, a3, a4, a5, a6, a7, a8, a9) : glCtx['real_texImage2D'](a1, a2, a3, a4, a5, a6);
473+
return ret;
474+
};
475+
glCtx['texSubImage2D'] = function(a1, a2, a3, a4, a5, a6, a7, a8, a9) {
476+
var ret = (a8 !== undefined) ? glCtx['real_texSubImage2D'](a1, a2, a3, a4, a5, a6, a7, a8, a9) : glCtx['real_texSubImage2D'](a1, a2, a3, a4, a5, a6, a7);
477+
return ret;
478+
};
479+
glCtx['texSubImage3D'] = function(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) {
480+
var ret = (a9 !== undefined) ? glCtx['real_texSubImage3D'](a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) : glCtx['real_texSubImage2D'](a1, a2, a3, a4, a5, a6, a7, a8);
481+
return ret;
482+
};
483+
},
484+
#endif
414485
// Returns the context handle to the new context.
415486
createContext: function(canvas, webGLContextAttributes) {
416487
if (typeof webGLContextAttributes['majorVersion'] === 'undefined' && typeof webGLContextAttributes['minorVersion'] === 'undefined') {
@@ -462,94 +533,13 @@ var LibraryGL = {
462533
Module.print('Could not create canvas: ' + [errorInfo, e, JSON.stringify(webGLContextAttributes)]);
463534
return 0;
464535
}
465-
#if GL_DEBUG
466-
function wrapDebugGL(ctx) {
467-
468-
var printObjectList = [];
469-
470-
function prettyPrint(arg) {
471-
if (typeof arg == 'undefined') return '!UNDEFINED!';
472-
if (typeof arg == 'boolean') arg = arg + 0;
473-
if (!arg) return arg;
474-
var index = printObjectList.indexOf(arg);
475-
if (index >= 0) return '<' + arg + '|'; // + index + '>';
476-
if (arg.toString() == '[object HTMLImageElement]') {
477-
return arg + '\n\n';
478-
}
479-
if (arg.byteLength) {
480-
return '{' + Array.prototype.slice.call(arg, 0, Math.min(arg.length, 400)) + '}'; // Useful for correct arrays, less so for compiled arrays, see the code below for that
481-
var buf = new ArrayBuffer(32);
482-
var i8buf = new Int8Array(buf);
483-
var i16buf = new Int16Array(buf);
484-
var f32buf = new Float32Array(buf);
485-
switch(arg.toString()) {
486-
case '[object Uint8Array]':
487-
i8buf.set(arg.subarray(0, 32));
488-
break;
489-
case '[object Float32Array]':
490-
f32buf.set(arg.subarray(0, 5));
491-
break;
492-
case '[object Uint16Array]':
493-
i16buf.set(arg.subarray(0, 16));
494-
break;
495-
default:
496-
alert('unknown array for debugging: ' + arg);
497-
throw 'see alert';
498-
}
499-
var ret = '{' + arg.byteLength + ':\n';
500-
var arr = Array.prototype.slice.call(i8buf);
501-
ret += 'i8:' + arr.toString().replace(/,/g, ',') + '\n';
502-
arr = Array.prototype.slice.call(f32buf, 0, 8);
503-
ret += 'f32:' + arr.toString().replace(/,/g, ',') + '}';
504-
return ret;
505-
}
506-
if (typeof arg == 'object') {
507-
printObjectList.push(arg);
508-
return '<' + arg + '|'; // + (printObjectList.length-1) + '>';
509-
}
510-
if (typeof arg == 'number') {
511-
if (arg > 0) return '0x' + arg.toString(16) + ' (' + arg + ')';
512-
}
513-
return arg;
514-
}
515-
516-
var wrapper = {};
517-
for (var prop in ctx) {
518-
(function(prop) {
519-
switch (typeof ctx[prop]) {
520-
case 'function': {
521-
wrapper[prop] = function gl_wrapper() {
522-
var printArgs = Array.prototype.slice.call(arguments).map(prettyPrint);
523-
dump('[gl_f:' + prop + ':' + printArgs + ']\n');
524-
var ret = ctx[prop].apply(ctx, arguments);
525-
if (typeof ret != 'undefined') {
526-
dump('[ gl:' + prop + ':return:' + prettyPrint(ret) + ']\n');
527-
}
528-
return ret;
529-
}
530-
break;
531-
}
532-
case 'number': case 'string': {
533-
wrapper.__defineGetter__(prop, function() {
534-
//dump('[gl_g:' + prop + ':' + ctx[prop] + ']\n');
535-
return ctx[prop];
536-
});
537-
wrapper.__defineSetter__(prop, function(value) {
538-
dump('[gl_s:' + prop + ':' + value + ']\n');
539-
ctx[prop] = value;
540-
});
541-
break;
542-
}
543-
}
544-
})(prop);
545-
}
546-
return wrapper;
547-
}
548-
#endif
549-
// possible GL_DEBUG entry point: ctx = wrapDebugGL(ctx);
550536

551537
if (!ctx) return 0;
552-
return GL.registerContext(ctx, webGLContextAttributes);
538+
var context = GL.registerContext(ctx, webGLContextAttributes);
539+
#if TRACE_WEBGL_CALLS
540+
GL.hookWebGL(ctx);
541+
#endif
542+
return context;
553543
},
554544

555545
registerContext: function(ctx, webGLContextAttributes) {

src/settings.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,8 @@ var WEBSOCKET_SUBPROTOCOL = 'binary'; // A string containing a comma separated l
258258
var OPENAL_DEBUG = 0; // Print out debugging information from our OpenAL implementation.
259259

260260
var GL_ASSERTIONS = 0; // Adds extra checks for error situations in the GL library. Can impact performance.
261-
var GL_DEBUG = 0; // Print out all calls into WebGL. As with LIBRARY_DEBUG, you can set a runtime
262-
// option, in this case GL.debug.
261+
var TRACE_WEBGL_CALLS = 0; // If enabled, prints out all API calls to WebGL contexts. (*very* verbose)
262+
var GL_DEBUG = 0; // Enables more verbose debug printing of WebGL related operations. As with LIBRARY_DEBUG, this is toggleable at runtime with option GL.debug.
263263
var GL_TESTING = 0; // When enabled, sets preserveDrawingBuffer in the context, to allow tests to work (but adds overhead)
264264
var GL_MAX_TEMP_BUFFER_SIZE = 2097152; // How large GL emulation temp buffers are
265265
var GL_UNSAFE_OPTS = 1; // Enables some potentially-unsafe optimizations in GL emulation code

0 commit comments

Comments
 (0)