Skip to content

Commit

Permalink
Add support for preMainLoop/postMainLoop functions (emscripten-core#2…
Browse files Browse the repository at this point in the history
…2621)

Rather than having code from e.g SDL, etc, in event loop code use a
callback registration system.
  • Loading branch information
sbc100 authored Sep 26, 2024
1 parent 2b0980d commit f508b43
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 41 deletions.
70 changes: 30 additions & 40 deletions src/library_eventloop.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -173,6 +184,8 @@ LibraryJSEventLoop = {
timingValue: 0,
currentFrameNumber: 0,
queue: [],
preMainLoop: [],
postMainLoop: [],

pause() {
MainLoop.scheduler = null;
Expand Down Expand Up @@ -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
},

Expand Down Expand Up @@ -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!');
Expand All @@ -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();
}

Expand Down
26 changes: 26 additions & 0 deletions src/library_html5_webgl.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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',
Expand All @@ -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;
Expand Down
7 changes: 6 additions & 1 deletion src/library_sdl.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
10 changes: 10 additions & 0 deletions src/library_webgl.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit f508b43

Please sign in to comment.