From eeb71883cc09d76ff54d7e7547681b4f6143fd8e Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Wed, 21 Feb 2024 13:37:16 -0800 Subject: [PATCH] Fix webgl tracing issues The webgl tracing mechanism was assuming a fixed number of arguments for a given function whereas some webgl function can take variable number of arguments. Fix this by using the rest operator. This is not as slow as using `arguments`. Also, performance is not really an issue here since we are about to serialize all the arguments to string and send to the console which will vastly out weight the cost of using spread here. I believe the comments here about hot and cold functions as well as the comment at about the cost of using `arguments` were copied from the cpuprofiler.js but they don't apply here. Also, avoid serializing the entire heap, which can cause chrome to hang. --- src/library_webgl.js | 77 +++++++++++++------------------------------- test/test_browser.py | 4 ++- 2 files changed, 25 insertions(+), 56 deletions(-) diff --git a/src/library_webgl.js b/src/library_webgl.js index 6eafd1676adae..94d6f0709e724 100644 --- a/src/library_webgl.js +++ b/src/library_webgl.js @@ -611,76 +611,43 @@ for (/**@suppress{duplicate}*/var i = 0; i < {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; #if TRACE_WEBGL_CALLS hookWebGLFunction: (f, glCtx) => { - var realf = 'real_' + f; - glCtx[realf] = glCtx[f]; - var numArgs = glCtx[realf].length; - if (numArgs === undefined) console.warn(`Unexpected WebGL function ${f} when binding TRACE_WEBGL_CALLS`); + var orig = glCtx[f]; var contextHandle = glCtx.canvas.GLctxObject.handle; - var threadId = (typeof _pthread_self != 'undefined') ? _pthread_self : () => 1; - // Accessing 'arguments' is super slow, so to avoid overhead, statically reason the number of arguments. - switch (numArgs) { - case 0: glCtx[f] = () => { var ret = glCtx[realf](); err(`[Thread ${threadId()}, GL ctx: ${contextHandle}]: ${f}() -> ${ret}`); return ret; }; break; - case 1: glCtx[f] = (a1) => { var ret = glCtx[realf](a1); err(`[Thread ${threadId()}, GL ctx: ${contextHandle}]: ${f}(${a1}) -> ${ret}`); return ret; }; break; - case 2: glCtx[f] = (a1, a2) => { var ret = glCtx[realf](a1, a2); err(`[Thread ${threadId()}, GL ctx: ${contextHandle}]: ${f}(${a1}, ${a2}) -> ${ret}`); return ret; }; break; - case 3: glCtx[f] = (a1, a2, a3) => { var ret = glCtx[realf](a1, a2, a3); err(`[Thread ${threadId()}, GL ctx: ${contextHandle}]: ${f}(${a1}, ${a2}, ${a3}) -> ${ret}`); return ret; }; break; - case 4: glCtx[f] = (a1, a2, a3, a4) => { var ret = glCtx[realf](a1, a2, a3, a4); err(`[Thread ${threadId()}, GL ctx: ${contextHandle}]: ${f}(${a1}, ${a2}, ${a3}, ${a4}) -> ${ret}`); return ret; }; break; - case 5: glCtx[f] = (a1, a2, a3, a4, a5) => { var ret = glCtx[realf](a1, a2, a3, a4, a5); err(`[Thread ${threadId()}, GL ctx: ${contextHandle}]: ${f}(${a1}, ${a2}, ${a3}, ${a4}, ${a5}) -> ${ret}`); return ret; }; break; - case 6: glCtx[f] = (a1, a2, a3, a4, a5, a6) => { var ret = glCtx[realf](a1, a2, a3, a4, a5, a6); err(`[Thread ${threadId()}, GL ctx: ${contextHandle}]: ${f}(${a1}, ${a2}, ${a3}, ${a4}, ${a5}, ${a6}) -> ${ret}`); return ret; }; break; - case 7: glCtx[f] = (a1, a2, a3, a4, a5, a6, a7) => { var ret = glCtx[realf](a1, a2, a3, a4, a5, a6, a7); err(`[Thread ${threadId()}, GL ctx: ${contextHandle}]: ${f}(${a1}, ${a2}, ${a3}, ${a4}, ${a5}, ${a6}, ${a7}) -> ${ret}`); return ret; }; break; - case 8: glCtx[f] = (a1, a2, a3, a4, a5, a6, a7, a8) => { var ret = glCtx[realf](a1, a2, a3, a4, a5, a6, a7, a8); err(`[Thread ${threadId()}, GL ctx: ${contextHandle}]: ${f}(${a1}, ${a2}, ${a3}, ${a4}, ${a5}, ${a6}, ${a7}, ${a8}) -> ${ret}`); return ret; }; break; - case 9: glCtx[f] = (a1, a2, a3, a4, a5, a6, a7, a8, a9) => { var ret = glCtx[realf](a1, a2, a3, a4, a5, a6, a7, a8, a9); err(`[Thread ${threadId()}, GL ctx: ${contextHandle}]: ${f}(${a1}, ${a2}, ${a3}, ${a4}, ${a5}, ${a6}, ${a7}, ${a8}, ${a9}) -> ${ret}`); return ret; }; break; - case 10: glCtx[f] = (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) => { var ret = glCtx[realf](a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); err(`[Thread ${threadId()}, GL ctx: ${contextHandle}]: ${f}(${a1}, ${a2}, ${a3}, ${a4}, ${a5}, ${a6}, ${a7}, ${a8}, ${a9}, ${a10}) -> ${ret}`); return ret; }; break; - case 11: glCtx[f] = (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); err(`[Thread ${threadId()}, GL ctx: ${contextHandle}]: ${f}(${a1}, ${a2}, ${a3}, ${a4}, ${a5}, ${a6}, ${a7}, ${a8}, ${a9}, ${a10}, ${a11}) -> ${ret}`); return ret; }; break; - default: console.warn('hookWebGL failed! Unexpected length ' + glCtx[realf].length); - } + glCtx[f] = function(...args) { + var ret = orig.apply(this, args); + // Some GL functions take a view of the entire linear memory. Replace + // such arguments with the string 'HEAP' to avoid serializing all of + // memory. + for (var i in args) { + if (ArrayBuffer.isView(args[i]) && args[i].byteLength === HEAPU8.byteLength) { + args[i] = 'HEAP'; + } + } +#if PTHREADS + err(`[Thread ${_pthread_self()}, GL ctx: ${contextHandle}]: ${f}(${args}) -> ${ret}`); +#else + err(`[ctx: ${contextHandle}]: ${f}(${args}) -> ${ret}`); +#endif + return ret; + }; }, hookWebGL: function(glCtx) { if (!glCtx) glCtx = this.detectWebGLContext(); if (!glCtx) return; if (!((typeof WebGLRenderingContext != 'undefined' && glCtx instanceof WebGLRenderingContext) - || (typeof WebGL2RenderingContext != 'undefined' && glCtx instanceof WebGL2RenderingContext))) { + || (typeof WebGL2RenderingContext != 'undefined' && glCtx instanceof WebGL2RenderingContext))) { return; } if (glCtx.webGlTracerAlreadyHooked) return; glCtx.webGlTracerAlreadyHooked = true; - // 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). - // Distinguishing between these two allows pinpointing locations of - // troublesome GL usage that might cause performance issues. for (var f in glCtx) { - if (typeof glCtx[f] != 'function' || f.startsWith('real_')) continue; - this.hookWebGLFunction(f, glCtx); + if (typeof glCtx[f] == 'function') { + this.hookWebGLFunction(f, glCtx); + } } - // The above injection won't work for texImage2D and texSubImage2D, which - // have multiple overloads. - glCtx['texImage2D'] = (a1, a2, a3, a4, a5, a6, a7, a8, a9) => { - 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); - return ret; - }; - glCtx['texSubImage2D'] = (a1, a2, a3, a4, a5, a6, a7, a8, a9) => { - 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); - return ret; - }; - glCtx['texSubImage3D'] = (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) => { - 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); - return ret; - }; - glCtx['bufferData'] = (a1, a2, a3, a4, a5) => { - // WebGL1/2 versions have different parameters (not just extra ones) - var ret = (a4 !== undefined) ? glCtx['real_bufferData'](a1, a2, a3, a4, a5) : glCtx['real_bufferData'](a1, a2, a3); - return ret; - }; - const matrixFuncs = ['uniformMatrix2fv', 'uniformMatrix3fv', 'uniformMatrix4fv']; - matrixFuncs.forEach(f => { - glCtx[f] = (a1, a2, a3, a4, a5) => { - // WebGL2 version has 2 extra optional parameters, ensure we forward them - return glCtx['real_' + f](a1, a2, a3, a4, a5); - } - }); }, #endif // Returns the context handle to the new context. diff --git a/test/test_browser.py b/test/test_browser.py index 689b2a48b6aa9..cda3dce3a432c 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -2325,10 +2325,12 @@ def test_float_tex(self): @requires_graphics_hardware @parameterized({ '': ([],), + 'tracing': (['-sTRACE_WEBGL_CALLS'],), 'es2': (['-sMIN_WEBGL_VERSION=2', '-sFULL_ES2', '-sWEBGL2_BACKWARDS_COMPATIBILITY_EMULATION'],), + 'es2_tracing': (['-sMIN_WEBGL_VERSION=2', '-sFULL_ES2', '-sWEBGL2_BACKWARDS_COMPATIBILITY_EMULATION', '-sTRACE_WEBGL_CALLS'],), }) def test_subdata(self, args): - if self.is_4gb() and args: + if self.is_4gb() and '-sMIN_WEBGL_VERSION=2' in args: self.skipTest('texSubImage2D fails: https://crbug.com/325090165') self.btest('gl_subdata.c', reference='float_tex.png', args=['-lGL', '-lglut'] + args)