diff --git a/src/library_eventloop.js b/src/library_eventloop.js index d6f2d14161e2e..c72182104223c 100644 --- a/src/library_eventloop.js +++ b/src/library_eventloop.js @@ -149,12 +149,23 @@ LibraryJSEventLoop = { clearInterval(id); }, + $registerPostMainLoop: (f) => { + // Does nothing unless $MainLoop is included/used. + typeof MainLoop != 'undefined' && MainLoop.postMainLoop.push(f); + }, + + $registerPreMainLoop: (f) => { + // Does nothing unless $MainLoop is included/used. + typeof MainLoop != 'undefined' && MainLoop.preMainLoop.push(f); + }, + $MainLoop__internal: true, $MainLoop__deps: ['$setMainLoop', '$callUserCallback', 'emscripten_set_main_loop_timing'], $MainLoop__postset: ` Module["requestAnimationFrame"] = MainLoop.requestAnimationFrame; Module["pauseMainLoop"] = MainLoop.pause; - Module["resumeMainLoop"] = MainLoop.resume;`, + Module["resumeMainLoop"] = MainLoop.resume; + MainLoop.init();`, $MainLoop: { running: false, scheduler: null, @@ -173,6 +184,8 @@ LibraryJSEventLoop = { timingValue: 0, currentFrameNumber: 0, queue: [], + preMainLoop: [], + postMainLoop: [], pause() { MainLoop.scheduler = null; @@ -211,19 +224,28 @@ LibraryJSEventLoop = { #endif }, + init() { +#if expectToReceiveOnModule('preMainLoop') + Module['preMainLoop'] && MainLoop.preMainLoop.push(Module['preMainLoop']); +#endif +#if expectToReceiveOnModule('postMainLoop') + Module['postMainLoop'] && MainLoop.postMainLoop.push(Module['postMainLoop']); +#endif + }, + runIter(func) { if (ABORT) return; -#if expectToReceiveOnModule('preMainLoop') - if (Module['preMainLoop']) { - var preRet = Module['preMainLoop'](); - if (preRet === false) { + for (var pre of MainLoop.preMainLoop) { + if (pre() === false) { return; // |return false| skips a frame } } -#endif callUserCallback(func); -#if expectToReceiveOnModule('postMainLoop') - Module['postMainLoop']?.(); + for (var post of MainLoop.postMainLoop) { + post(); + } +#if STACK_OVERFLOW_CHECK + checkStackCookie(); #endif }, @@ -424,28 +446,6 @@ LibraryJSEventLoop = { MainLoop.tickStartTime = _emscripten_get_now(); } - // Signal GL rendering layer that processing of a new frame is about to start. This helps it optimize - // VBO double-buffering and reduce GPU stalls. -#if FULL_ES2 || LEGACY_GL_EMULATION - GL.newRenderingFrameStarted(); -#endif - -#if PTHREADS && OFFSCREEN_FRAMEBUFFER && GL_SUPPORT_EXPLICIT_SWAP_CONTROL - // If the current GL context is a proxied regular WebGL context, and was initialized with implicit swap mode on the main thread, and we are on the parent thread, - // perform the swap on behalf of the user. - if (typeof GL != 'undefined' && GL.currentContext && GL.currentContextIsProxied) { - var explicitSwapControl = {{{ makeGetValue('GL.currentContext', 0, 'i32') }}}; - if (!explicitSwapControl) _emscripten_webgl_commit_frame(); - } -#endif - -#if OFFSCREENCANVAS_SUPPORT - // If the current GL context is an OffscreenCanvas, but it was initialized with implicit swap mode, perform the swap on behalf of the user. - if (typeof GL != 'undefined' && GL.currentContext && !GL.currentContextIsProxied && !GL.currentContext.attributes.explicitSwapControl && GL.currentContext.GLctx.commit) { - GL.currentContext.GLctx.commit(); - } -#endif - #if ASSERTIONS if (MainLoop.method === 'timeout' && Module.ctx) { warnOnce('Looks like you are rendering without using requestAnimationFrame for the main loop. You should use 0 for the frame rate in emscripten_set_main_loop in order to use requestAnimationFrame, as that can greatly improve your frame rates!'); @@ -455,19 +455,9 @@ LibraryJSEventLoop = { MainLoop.runIter(iterFunc); -#if STACK_OVERFLOW_CHECK - checkStackCookie(); -#endif - // catch pauses from the main loop itself if (!checkIsRunning()) return; - // Queue new audio data. This is important to be right after the main loop invocation, so that we will immediately be able - // to queue the newest produced audio samples. - // TODO: Consider adding pre- and post- rAF callbacks so that GL.newRenderingFrameStarted() and SDL.audio.queueNewAudioData() - // do not need to be hardcoded into this function, but can be more generic. - if (typeof SDL == 'object') SDL.audio?.queueNewAudioData?.(); - MainLoop.scheduler(); } diff --git a/src/library_html5_webgl.js b/src/library_html5_webgl.js index 9ff25d0e2631e..a99bfec219693 100644 --- a/src/library_html5_webgl.js +++ b/src/library_html5_webgl.js @@ -45,8 +45,20 @@ var LibraryHtml5WebGL = { emscripten_webgl_commit_frame: 'emscripten_webgl_do_commit_frame', #endif +#if OFFSCREENCANVAS_SUPPORT + emscripten_webgl_do_create_context__postset: ` + registerPreMainLoop(() => { + // If the current GL context is an OffscreenCanvas, but it was initialized + // with implicit swap mode, perform the swap on behalf of the user. + if (GL.currentContext && !GL.currentContextIsProxied && !GL.currentContext.attributes.explicitSwapControl && GL.currentContext.GLctx.commit) { + GL.currentContext.GLctx.commit(); + } + });`, +#endif + emscripten_webgl_do_create_context__deps: [ #if OFFSCREENCANVAS_SUPPORT + '$registerPreMainLoop', 'malloc', 'emscripten_supports_offscreencanvas', #endif @@ -184,6 +196,7 @@ var LibraryHtml5WebGL = { var contextHandle = GL.createContext(canvas, contextAttributes); return contextHandle; }, + #if PTHREADS && OFFSCREEN_FRAMEBUFFER // Runs on the calling thread, proxies if needed. emscripten_webgl_make_context_current_calling_thread__sig: 'ip', @@ -196,6 +209,19 @@ var LibraryHtml5WebGL = { // In this scenario, the pthread does not hold a high-level JS object to the GL context, because it lives on the main thread, in which case we record // an integer pointer as a token value to represent the GL context activation from another thread. (when this function is called, the main browser thread // has already accepted the GL context activation for our pthread, so that side is good) +#if GL_SUPPORT_EXPLICIT_SWAP_CONTROL + _emscripten_proxied_gl_context_activated_from_main_browser_thread__deps: ['$registerPreMainLoop'], + _emscripten_proxied_gl_context_activated_from_main_browser_thread__postjs: ` + // If the current GL context is a proxied regular WebGL context, and was + // initialized with implicit swap mode on the main thread, and we are on the + // parent thread, perform the swap on behalf of the user. + registerPreMainLoop(() => { + if (GL.currentContext && GL.currentContextIsProxied) { + var explicitSwapControl = {{{ makeGetValue('GL.currentContext', 0, 'i32') }}}; + if (!explicitSwapControl) _emscripten_webgl_commit_frame(); + } + });`, +#endif _emscripten_proxied_gl_context_activated_from_main_browser_thread: (contextHandle) => { GLctx = Module.ctx = GL.currentContext = contextHandle; GL.currentContextIsProxied = true; diff --git a/src/library_sdl.js b/src/library_sdl.js index 9cf53f86a2855..dc8df2465d1d6 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -2311,8 +2311,13 @@ var LibrarySDL = { // SDL_Audio - SDL_OpenAudio__deps: ['$autoResumeAudioContext', '$safeSetTimeout'], + SDL_OpenAudio__deps: ['$autoResumeAudioContext', '$safeSetTimeout', '$registerPostMainLoop'], SDL_OpenAudio__proxy: 'sync', + SDL_OpenAudio__postset: ` + // Queue new audio data. This is important to be right after the main loop + // invocation, so that we will immediately be able to queue the newest + // produced audio samples. + registerPostMainLoop(() => SDL.audio?.queueNewAudioData?.());`, SDL_OpenAudio: (desired, obtained) => { try { SDL.audio = { diff --git a/src/library_webgl.js b/src/library_webgl.js index 990c384be027f..51e76cba54225 100644 --- a/src/library_webgl.js +++ b/src/library_webgl.js @@ -252,7 +252,17 @@ for (/**@suppress{duplicate}*/var i = 0; i <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}}; '$webgl_enable_WEBGL_multi_draw', '$getEmscriptenSupportedExtensions', #endif // GL_SUPPORT_AUTOMATIC_ENABLE_EXTENSIONS +#if FULL_ES2 || LEGACY_GL_EMULATION + '$registerPreMainLoop', +#endif ], +#if FULL_ES2 || LEGACY_GL_EMULATION + $GL__postset: ` + // Signal GL rendering layer that processing of a new frame is about to + // start. This helps it optimize VBO double-buffering and reduce GPU stalls. + registerPreMainLoop(() => GL.newRenderingFrameStarted()); + `, +#endif $GL: { #if GL_DEBUG debug: true,