From dfca95bfb44a61e0c6c5bf9fa49698d245965734 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Tue, 27 Feb 2024 15:58:43 -0800 Subject: [PATCH] Avoid garbage-free WebGL APIs when memory size is over 2gb. Both chrome and firefox see have some issues with passing 2gb+ and 4gb+ offsets to these APIs. Once the browser issues are addressed we can lift these restrictions over time. Fixes: #20533 --- src/library_webgl.js | 147 +++++++++++++++++---------------------- src/settings_internal.js | 6 ++ test/test_browser.py | 8 --- tools/link.py | 6 ++ 4 files changed, 77 insertions(+), 90 deletions(-) diff --git a/src/library_webgl.js b/src/library_webgl.js index 94d6f0709e724..88c3b366a55aa 100644 --- a/src/library_webgl.js +++ b/src/library_webgl.js @@ -13,7 +13,8 @@ if (MIN_WEBGL_VERSION >= 2) return 'true'; if (MAX_WEBGL_VERSION <= 1) return 'false'; return 'GL.currentContext.version >= 2'; - } + }; + null; }}} @@ -1517,14 +1518,14 @@ for (/**@suppress{duplicate}*/var i = 0; i < {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; glCompressedTexImage2D: (target, level, internalFormat, width, height, border, imageSize, data) => { #if MAX_WEBGL_VERSION >= 2 if ({{{ isCurrentContextWebGL2() }}}) { - // WebGL 2 provides new garbage-free entry points to call to WebGL. Use - // those always when possible. if (GLctx.currentPixelUnpackBufferBinding || !imageSize) { GLctx.compressedTexImage2D(target, level, internalFormat, width, height, border, imageSize, data); - } else { - GLctx.compressedTexImage2D(target, level, internalFormat, width, height, border, HEAPU8, data, imageSize); + return; } +#if WEBGL_USE_GARBAGE_FREE_APIS + GLctx.compressedTexImage2D(target, level, internalFormat, width, height, border, HEAPU8, data, imageSize); return; +#endif } #endif GLctx.compressedTexImage2D(target, level, internalFormat, width, height, border, data ? {{{ makeHEAPView('U8', 'data', 'data+imageSize') }}} : null); @@ -1534,14 +1535,14 @@ for (/**@suppress{duplicate}*/var i = 0; i < {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; glCompressedTexSubImage2D: (target, level, xoffset, yoffset, width, height, format, imageSize, data) => { #if MAX_WEBGL_VERSION >= 2 if ({{{ isCurrentContextWebGL2() }}}) { - // WebGL 2 provides new garbage-free entry points to call to WebGL. Use - // those always when possible. if (GLctx.currentPixelUnpackBufferBinding || !imageSize) { GLctx.compressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, imageSize, data); - } else { - GLctx.compressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, HEAPU8, data, imageSize); + return; } +#if WEBGL_USE_GARBAGE_FREE_APIS + GLctx.compressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, HEAPU8, data, imageSize); return; +#endif } #endif GLctx.compressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, data ? {{{ makeHEAPView('U8', 'data', 'data+imageSize') }}} : null); @@ -1636,20 +1637,21 @@ for (/**@suppress{duplicate}*/var i = 0; i < {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; } #endif if ({{{ isCurrentContextWebGL2() }}}) { - // WebGL 2 provides new garbage-free entry points to call to WebGL. Use - // those always when possible. if (GLctx.currentPixelUnpackBufferBinding) { GLctx.texImage2D(target, level, internalFormat, width, height, border, format, type, pixels); - } else if (pixels) { + return; + } +#if WEBGL_USE_GARBAGE_FREE_APIS + if (pixels) { var heap = heapObjectForWebGLType(type); GLctx.texImage2D(target, level, internalFormat, width, height, border, format, type, heap, toTypedArrayIndex(pixels, heap)); - } else { - GLctx.texImage2D(target, level, internalFormat, width, height, border, format, type, null); + return; } - return; +#endif } #endif - GLctx.texImage2D(target, level, internalFormat, width, height, border, format, type, pixels ? emscriptenWebGLGetTexPixelData(type, format, width, height, pixels, internalFormat) : null); + var pixelData = pixels ? emscriptenWebGLGetTexPixelData(type, format, width, height, pixels, internalFormat) : null; + GLctx.texImage2D(target, level, internalFormat, width, height, border, format, type, pixelData); }, glTexSubImage2D__deps: ['$emscriptenWebGLGetTexPixelData' @@ -1669,21 +1671,20 @@ for (/**@suppress{duplicate}*/var i = 0; i < {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; } #endif if ({{{ isCurrentContextWebGL2() }}}) { - // WebGL 2 provides new garbage-free entry points to call to WebGL. Use - // those always when possible. if (GLctx.currentPixelUnpackBufferBinding) { GLctx.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels); - } else if (pixels) { + return; + } +#if WEBGL_USE_GARBAGE_FREE_APIS + if (pixels) { var heap = heapObjectForWebGLType(type); GLctx.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, heap, toTypedArrayIndex(pixels, heap)); - } else { - GLctx.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, null); + return; } - return; + #endif } #endif - var pixelData = null; - if (pixels) pixelData = emscriptenWebGLGetTexPixelData(type, format, width, height, pixels, 0); + var pixelData = pixels? emscriptenWebGLGetTexPixelData(type, format, width, height, pixels, 0) : null; GLctx.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixelData); }, @@ -1695,16 +1696,16 @@ for (/**@suppress{duplicate}*/var i = 0; i < {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; glReadPixels: (x, y, width, height, format, type, pixels) => { #if MAX_WEBGL_VERSION >= 2 if ({{{ isCurrentContextWebGL2() }}}) { - // WebGL 2 provides new garbage-free entry points to call to WebGL. Use - // those always when possible. if (GLctx.currentPixelPackBufferBinding) { GLctx.readPixels(x, y, width, height, format, type, pixels); - } else { - var heap = heapObjectForWebGLType(type); - var target = toTypedArrayIndex(pixels, heap); - GLctx.readPixels(x, y, width, height, format, type, heap, target); + return; } +#if WEBGL_USE_GARBAGE_FREE_APIS + var heap = heapObjectForWebGLType(type); + var target = toTypedArrayIndex(pixels, heap); + GLctx.readPixels(x, y, width, height, format, type, heap, target); return; +#endif } #endif var pixelData = emscriptenWebGLGetTexPixelData(type, format, width, height, pixels, format); @@ -1841,36 +1842,30 @@ for (/**@suppress{duplicate}*/var i = 0; i < {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; } #endif -#if MAX_WEBGL_VERSION >= 2 +#if WEBGL_USE_GARBAGE_FREE_APIS if ({{{ isCurrentContextWebGL2() }}}) { - // WebGL 2 provides new garbage-free entry points to call to WebGL. Use - // those always when possible. If size is zero, WebGL would interpret - // uploading the whole input arraybuffer (starting from given offset), - // which would not make sense in WebAssembly, so avoid uploading if size - // is zero. However we must still call bufferData to establish a backing - // storage of zero bytes. + // If size is zero, WebGL would interpret uploading the whole input + // arraybuffer (starting from given offset), which would not make sense in + // WebAssembly, so avoid uploading if size is zero. However we must still + // call bufferData to establish a backing storage of zero bytes. if (data && size) { GLctx.bufferData(target, HEAPU8, usage, data, size); } else { GLctx.bufferData(target, size, usage); } - } else { -#endif - // N.b. here first form specifies a heap subarray, second form an integer - // size, so the ?: code here is polymorphic. It is advised to avoid - // randomly mixing both uses in calling code, to avoid any potential JS - // engine JIT issues. - GLctx.bufferData(target, data ? HEAPU8.subarray(data, data+size) : size, usage); -#if MAX_WEBGL_VERSION >= 2 + return; } #endif + // N.b. here first form specifies a heap subarray, second form an integer + // size, so the ?: code here is polymorphic. It is advised to avoid + // randomly mixing both uses in calling code, to avoid any potential JS + // engine JIT issues. + GLctx.bufferData(target, data ? HEAPU8.subarray(data, data+size) : size, usage); }, glBufferSubData: (target, offset, size, data) => { -#if MAX_WEBGL_VERSION >= 2 +#if WEBGL_USE_GARBAGE_FREE_APIS if ({{{ isCurrentContextWebGL2() }}}) { - // WebGL 2 provides new garbage-free entry points to call to WebGL. Use - // those always when possible. size && GLctx.bufferSubData(target, offset, HEAPU8, data, size); return; } @@ -2433,8 +2428,8 @@ for (/**@suppress{duplicate}*/var i = 0; i < {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; count && GLctx.uniform1iv(webglGetUniformLocation(location), HEAP32, {{{ getHeapOffset('value', 'i32') }}}, count); #else -#if MAX_WEBGL_VERSION >= 2 - if ({{{ isCurrentContextWebGL2() }}}) { // WebGL 2 provides new garbage-free entry points to call to WebGL. Use those always when possible. +#if WEBGL_USE_GARBAGE_FREE_APIS + if ({{{ isCurrentContextWebGL2() }}}) { count && GLctx.uniform1iv(webglGetUniformLocation(location), HEAP32, {{{ getHeapOffset('value', 'i32') }}}, count); return; } @@ -2474,8 +2469,8 @@ for (/**@suppress{duplicate}*/var i = 0; i < {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; count && GLctx.uniform2iv(webglGetUniformLocation(location), HEAP32, {{{ getHeapOffset('value', 'i32') }}}, count*2); #else -#if MAX_WEBGL_VERSION >= 2 - if ({{{ isCurrentContextWebGL2() }}}) { // WebGL 2 provides new garbage-free entry points to call to WebGL. Use those always when possible. +#if WEBGL_USE_GARBAGE_FREE_APIS + if ({{{ isCurrentContextWebGL2() }}}) { count && GLctx.uniform2iv(webglGetUniformLocation(location), HEAP32, {{{ getHeapOffset('value', 'i32') }}}, count*2); return; } @@ -2516,8 +2511,8 @@ for (/**@suppress{duplicate}*/var i = 0; i < {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; count && GLctx.uniform3iv(webglGetUniformLocation(location), HEAP32, {{{ getHeapOffset('value', 'i32') }}}, count*3); #else -#if MAX_WEBGL_VERSION >= 2 - if ({{{ isCurrentContextWebGL2() }}}) { // WebGL 2 provides new garbage-free entry points to call to WebGL. Use those always when possible. +#if WEBGL_USE_GARBAGE_FREE_APIS + if ({{{ isCurrentContextWebGL2() }}}) { count && GLctx.uniform3iv(webglGetUniformLocation(location), HEAP32, {{{ getHeapOffset('value', 'i32') }}}, count*3); return; } @@ -2559,9 +2554,7 @@ for (/**@suppress{duplicate}*/var i = 0; i < {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; count && GLctx.uniform4iv(webglGetUniformLocation(location), HEAP32, {{{ getHeapOffset('value', 'i32') }}}, count*4); #else -#if MAX_WEBGL_VERSION >= 2 - // WebGL 2 provides new garbage-free entry points to call to WebGL. Use - // those always when possible. +#if WEBGL_USE_GARBAGE_FREE_APIS if ({{{ isCurrentContextWebGL2() }}}) { count && GLctx.uniform4iv(webglGetUniformLocation(location), HEAP32, {{{ getHeapOffset('value', 'i32') }}}, count*4); return; @@ -2605,8 +2598,8 @@ for (/**@suppress{duplicate}*/var i = 0; i < {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; count && GLctx.uniform1fv(webglGetUniformLocation(location), HEAPF32, {{{ getHeapOffset('value', 'float') }}}, count); #else -#if MAX_WEBGL_VERSION >= 2 - if ({{{ isCurrentContextWebGL2() }}}) { // WebGL 2 provides new garbage-free entry points to call to WebGL. Use those always when possible. +#if WEBGL_USE_GARBAGE_FREE_APIS + if ({{{ isCurrentContextWebGL2() }}}) { count && GLctx.uniform1fv(webglGetUniformLocation(location), HEAPF32, {{{ getHeapOffset('value', 'float') }}}, count); return; } @@ -2646,9 +2639,7 @@ for (/**@suppress{duplicate}*/var i = 0; i < {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; count && GLctx.uniform2fv(webglGetUniformLocation(location), HEAPF32, {{{ getHeapOffset('value', 'float') }}}, count*2); #else -#if MAX_WEBGL_VERSION >= 2 - // WebGL 2 provides new garbage-free entry points to call to WebGL. Use - // those always when possible. +#if WEBGL_USE_GARBAGE_FREE_APIS if ({{{ isCurrentContextWebGL2() }}}) { count && GLctx.uniform2fv(webglGetUniformLocation(location), HEAPF32, {{{ getHeapOffset('value', 'float') }}}, count*2); return; @@ -2690,9 +2681,7 @@ for (/**@suppress{duplicate}*/var i = 0; i < {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; count && GLctx.uniform3fv(webglGetUniformLocation(location), HEAPF32, {{{ getHeapOffset('value', 'float') }}}, count*3); #else -#if MAX_WEBGL_VERSION >= 2 - // WebGL 2 provides new garbage-free entry points to call to WebGL. Use - // those always when possible. +#if WEBGL_USE_GARBAGE_FREE_APIS if ({{{ isCurrentContextWebGL2() }}}) { count && GLctx.uniform3fv(webglGetUniformLocation(location), HEAPF32, {{{ getHeapOffset('value', 'float') }}}, count*3); return; @@ -2735,9 +2724,7 @@ for (/**@suppress{duplicate}*/var i = 0; i < {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; count && GLctx.uniform4fv(webglGetUniformLocation(location), HEAPF32, {{{ getHeapOffset('value', 'float') }}}, count*4); #else -#if MAX_WEBGL_VERSION >= 2 - // WebGL 2 provides new garbage-free entry points to call to WebGL. Use - // those always when possible. +#if WEBGL_USE_GARBAGE_FREE_APIS if ({{{ isCurrentContextWebGL2() }}}) { count && GLctx.uniform4fv(webglGetUniformLocation(location), HEAPF32, {{{ getHeapOffset('value', 'float') }}}, count*4); return; @@ -2785,9 +2772,7 @@ for (/**@suppress{duplicate}*/var i = 0; i < {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; count && GLctx.uniformMatrix2fv(webglGetUniformLocation(location), !!transpose, HEAPF32, {{{ getHeapOffset('value', 'float') }}}, count*4); #else -#if MAX_WEBGL_VERSION >= 2 - // WebGL 2 provides new garbage-free entry points to call to WebGL. Use - // those always when possible. +#if WEBGL_USE_GARBAGE_FREE_APIS if ({{{ isCurrentContextWebGL2() }}}) { count && GLctx.uniformMatrix2fv(webglGetUniformLocation(location), !!transpose, HEAPF32, {{{ getHeapOffset('value', 'float') }}}, count*4); return; @@ -2831,9 +2816,7 @@ for (/**@suppress{duplicate}*/var i = 0; i < {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; count && GLctx.uniformMatrix3fv(webglGetUniformLocation(location), !!transpose, HEAPF32, {{{ getHeapOffset('value', 'float') }}}, count*9); #else -#if MAX_WEBGL_VERSION >= 2 - // WebGL 2 provides new garbage-free entry points to call to WebGL. Use - // those always when possible. +#if WEBGL_USE_GARBAGE_FREE_APIS if ({{{ isCurrentContextWebGL2() }}}) { count && GLctx.uniformMatrix3fv(webglGetUniformLocation(location), !!transpose, HEAPF32, {{{ getHeapOffset('value', 'float') }}}, count*9); return; @@ -2882,9 +2865,7 @@ for (/**@suppress{duplicate}*/var i = 0; i < {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; count && GLctx.uniformMatrix4fv(webglGetUniformLocation(location), !!transpose, HEAPF32, {{{ getHeapOffset('value', 'float') }}}, count*16); #else -#if MAX_WEBGL_VERSION >= 2 - // WebGL 2 provides new garbage-free entry points to call to WebGL. Use - // those always when possible. +#if WEBGL_USE_GARBAGE_FREE_APIS if ({{{ isCurrentContextWebGL2() }}}) { count && GLctx.uniformMatrix4fv(webglGetUniformLocation(location), !!transpose, HEAPF32, {{{ getHeapOffset('value', 'float') }}}, count*16); return; @@ -4158,12 +4139,14 @@ for (/**@suppress{duplicate}*/var i = 0; i < {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; return 0; } - if (!(mapping.access & 0x10)) /* GL_MAP_FLUSH_EXPLICIT_BIT */ - if ({{{ isCurrentContextWebGL2() }}}) { // WebGL 2 provides new garbage-free entry points to call to WebGL. Use those always when possible. + if (!(mapping.access & 0x10)) { /* GL_MAP_FLUSH_EXPLICIT_BIT */ +#if WEBGL_USE_GARBAGE_FREE_APIS + if ({{{ isCurrentContextWebGL2() }}}) { GLctx.bufferSubData(target, mapping.offset, HEAPU8, mapping.mem, mapping.length); - } else { - GLctx.bufferSubData(target, mapping.offset, HEAPU8.subarray(mapping.mem, mapping.mem+mapping.length)); - } + } else +#endif + GLctx.bufferSubData(target, mapping.offset, HEAPU8.subarray(mapping.mem, mapping.mem+mapping.length)); + } _free(mapping.mem); mapping.mem = 0; return 1; diff --git a/src/settings_internal.js b/src/settings_internal.js index 60a77da6b4f9c..47603a60ebcae 100644 --- a/src/settings_internal.js +++ b/src/settings_internal.js @@ -275,3 +275,9 @@ var MINIFY_WHITESPACE = true; var ASYNCIFY_IMPORTS_EXCEPT_JS_LIBS = []; var WARN_DEPRECATED = true; + +// WebGL 2 provides new garbage-free entry points to call to WebGL. Use +// those always when possible. +// We currently set this to false for memory sizes over 2GB due to browser +// bugs. +var WEBGL_USE_GARBAGE_FREE_APIS = false; diff --git a/test/test_browser.py b/test/test_browser.py index 5527899a241ae..c6a5b25e56bb8 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -1325,7 +1325,6 @@ def test_webgl_parallel_shader_compile(self): self.btest_exit('webgl_parallel_shader_compile.cpp') @requires_graphics_hardware - @no_4gb('readPixels fails: https://crbug.com/324992397') def test_webgl_explicit_uniform_location(self): self.btest_exit('webgl_explicit_uniform_location.c', args=['-sGL_EXPLICIT_UNIFORM_LOCATION', '-sMIN_WEBGL_VERSION=2']) @@ -1334,7 +1333,6 @@ def test_webgl_sampler_layout_binding(self): self.btest_exit('webgl_sampler_layout_binding.c', args=['-sGL_EXPLICIT_UNIFORM_BINDING']) @requires_graphics_hardware - @no_4gb('readPixels fails: https://crbug.com/324992397') def test_webgl2_ubo_layout_binding(self): self.btest_exit('webgl2_ubo_layout_binding.c', args=['-sGL_EXPLICIT_UNIFORM_BINDING', '-sMIN_WEBGL_VERSION=2']) @@ -1536,7 +1534,6 @@ def test_sdl_gl_read(self): self.btest_exit('test_sdl_gl_read.c', args=['-lSDL', '-lGL']) @requires_graphics_hardware - @no_4gb('readPixels fails: https://crbug.com/324992397') def test_sdl_gl_mapbuffers(self): self.btest_exit('test_sdl_gl_mapbuffers.c', args=['-sFULL_ES3', '-lSDL', '-lGL']) @@ -2039,12 +2036,10 @@ def test_gl_stride(self): self.reftest('gl_stride.c', 'gl_stride.png', args=['-sGL_UNSAFE_OPTS=0', '-sLEGACY_GL_EMULATION', '-lGL', '-lSDL']) @requires_graphics_hardware - @no_4gb('assertion failure') def test_gl_vertex_buffer_pre(self): self.reftest('gl_vertex_buffer_pre.c', 'gl_vertex_buffer_pre.png', args=['-sGL_UNSAFE_OPTS=0', '-sLEGACY_GL_EMULATION', '-lGL', '-lSDL']) @requires_graphics_hardware - @no_4gb('assertion failure') def test_gl_vertex_buffer(self): self.reftest('gl_vertex_buffer.c', 'gl_vertex_buffer.png', args=['-sGL_UNSAFE_OPTS=0', '-sLEGACY_GL_EMULATION', '-lGL', '-lSDL'], reference_slack=1) @@ -2842,7 +2837,6 @@ def test_webgl2_pbo(self): @no_firefox('fails on CI likely due to GPU drivers there') @requires_graphics_hardware - @no_4gb('fails to render') def test_webgl2_sokol_mipmap(self): self.reftest('third_party/sokol/mipmap-emsc.c', 'third_party/sokol/mipmap-emsc.png', args=['-sMAX_WEBGL_VERSION=2', '-lGL', '-O1'], reference_slack=2) @@ -4691,7 +4685,6 @@ def test_webgl_draw_base_vertex_base_instance(self): '-DWEBGL_CONTEXT_VERSION=2']) @requires_graphics_hardware - @no_4gb('fails to render') def test_webgl_sample_query(self): self.btest_exit('webgl_sample_query.cpp', args=['-sMAX_WEBGL_VERSION=2', '-lGL']) @@ -4749,7 +4742,6 @@ def test_webgl_offscreen_framebuffer_state_restoration(self): # Tests that using an array of structs in GL uniforms works. @requires_graphics_hardware - @no_4gb('fails to render') def test_webgl_array_of_structs_uniform(self): self.reftest('webgl_array_of_structs_uniform.c', 'webgl_array_of_structs_uniform.png', args=['-lGL', '-sMAX_WEBGL_VERSION=2']) diff --git a/tools/link.py b/tools/link.py index f2f631dd5213e..37a82d9ebce82 100644 --- a/tools/link.py +++ b/tools/link.py @@ -1643,6 +1643,12 @@ def check_memory_setting(setting): if not settings.MEMORY64 and settings.MAXIMUM_MEMORY > 2 * 1024 * 1024 * 1024: settings.CAN_ADDRESS_2GB = 1 + # Browsers currently have issues with 2gb+ and 4gb+ offests when + # using the garbage-free WebGL2 APIs. + # - https://bugzilla.mozilla.org/show_bug.cgi?id=1838218 + # - https://crbug.com/324992397 + settings.WEBGL_USE_GARBAGE_FREE_APIS = settings.MAX_WEBGL_VERSION >= 2 and settings.MAXIMUM_MEMORY < 2 * 1024 * 1024 * 1024 + if settings.MINIMAL_RUNTIME: if settings.EXIT_RUNTIME: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['proc_exit', '$callRuntimeCallbacks']