Skip to content

Commit f508b43

Browse files
authored
Add support for preMainLoop/postMainLoop functions (#22621)
Rather than having code from e.g SDL, etc, in event loop code use a callback registration system.
1 parent 2b0980d commit f508b43

File tree

4 files changed

+72
-41
lines changed

4 files changed

+72
-41
lines changed

src/library_eventloop.js

Lines changed: 30 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,23 @@ LibraryJSEventLoop = {
149149
clearInterval(id);
150150
},
151151

152+
$registerPostMainLoop: (f) => {
153+
// Does nothing unless $MainLoop is included/used.
154+
typeof MainLoop != 'undefined' && MainLoop.postMainLoop.push(f);
155+
},
156+
157+
$registerPreMainLoop: (f) => {
158+
// Does nothing unless $MainLoop is included/used.
159+
typeof MainLoop != 'undefined' && MainLoop.preMainLoop.push(f);
160+
},
161+
152162
$MainLoop__internal: true,
153163
$MainLoop__deps: ['$setMainLoop', '$callUserCallback', 'emscripten_set_main_loop_timing'],
154164
$MainLoop__postset: `
155165
Module["requestAnimationFrame"] = MainLoop.requestAnimationFrame;
156166
Module["pauseMainLoop"] = MainLoop.pause;
157-
Module["resumeMainLoop"] = MainLoop.resume;`,
167+
Module["resumeMainLoop"] = MainLoop.resume;
168+
MainLoop.init();`,
158169
$MainLoop: {
159170
running: false,
160171
scheduler: null,
@@ -173,6 +184,8 @@ LibraryJSEventLoop = {
173184
timingValue: 0,
174185
currentFrameNumber: 0,
175186
queue: [],
187+
preMainLoop: [],
188+
postMainLoop: [],
176189

177190
pause() {
178191
MainLoop.scheduler = null;
@@ -211,19 +224,28 @@ LibraryJSEventLoop = {
211224
#endif
212225
},
213226

227+
init() {
228+
#if expectToReceiveOnModule('preMainLoop')
229+
Module['preMainLoop'] && MainLoop.preMainLoop.push(Module['preMainLoop']);
230+
#endif
231+
#if expectToReceiveOnModule('postMainLoop')
232+
Module['postMainLoop'] && MainLoop.postMainLoop.push(Module['postMainLoop']);
233+
#endif
234+
},
235+
214236
runIter(func) {
215237
if (ABORT) return;
216-
#if expectToReceiveOnModule('preMainLoop')
217-
if (Module['preMainLoop']) {
218-
var preRet = Module['preMainLoop']();
219-
if (preRet === false) {
238+
for (var pre of MainLoop.preMainLoop) {
239+
if (pre() === false) {
220240
return; // |return false| skips a frame
221241
}
222242
}
223-
#endif
224243
callUserCallback(func);
225-
#if expectToReceiveOnModule('postMainLoop')
226-
Module['postMainLoop']?.();
244+
for (var post of MainLoop.postMainLoop) {
245+
post();
246+
}
247+
#if STACK_OVERFLOW_CHECK
248+
checkStackCookie();
227249
#endif
228250
},
229251

@@ -424,28 +446,6 @@ LibraryJSEventLoop = {
424446
MainLoop.tickStartTime = _emscripten_get_now();
425447
}
426448

427-
// Signal GL rendering layer that processing of a new frame is about to start. This helps it optimize
428-
// VBO double-buffering and reduce GPU stalls.
429-
#if FULL_ES2 || LEGACY_GL_EMULATION
430-
GL.newRenderingFrameStarted();
431-
#endif
432-
433-
#if PTHREADS && OFFSCREEN_FRAMEBUFFER && GL_SUPPORT_EXPLICIT_SWAP_CONTROL
434-
// 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,
435-
// perform the swap on behalf of the user.
436-
if (typeof GL != 'undefined' && GL.currentContext && GL.currentContextIsProxied) {
437-
var explicitSwapControl = {{{ makeGetValue('GL.currentContext', 0, 'i32') }}};
438-
if (!explicitSwapControl) _emscripten_webgl_commit_frame();
439-
}
440-
#endif
441-
442-
#if OFFSCREENCANVAS_SUPPORT
443-
// If the current GL context is an OffscreenCanvas, but it was initialized with implicit swap mode, perform the swap on behalf of the user.
444-
if (typeof GL != 'undefined' && GL.currentContext && !GL.currentContextIsProxied && !GL.currentContext.attributes.explicitSwapControl && GL.currentContext.GLctx.commit) {
445-
GL.currentContext.GLctx.commit();
446-
}
447-
#endif
448-
449449
#if ASSERTIONS
450450
if (MainLoop.method === 'timeout' && Module.ctx) {
451451
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 = {
455455

456456
MainLoop.runIter(iterFunc);
457457

458-
#if STACK_OVERFLOW_CHECK
459-
checkStackCookie();
460-
#endif
461-
462458
// catch pauses from the main loop itself
463459
if (!checkIsRunning()) return;
464460

465-
// Queue new audio data. This is important to be right after the main loop invocation, so that we will immediately be able
466-
// to queue the newest produced audio samples.
467-
// TODO: Consider adding pre- and post- rAF callbacks so that GL.newRenderingFrameStarted() and SDL.audio.queueNewAudioData()
468-
// do not need to be hardcoded into this function, but can be more generic.
469-
if (typeof SDL == 'object') SDL.audio?.queueNewAudioData?.();
470-
471461
MainLoop.scheduler();
472462
}
473463

src/library_html5_webgl.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,20 @@ var LibraryHtml5WebGL = {
4545
emscripten_webgl_commit_frame: 'emscripten_webgl_do_commit_frame',
4646
#endif
4747

48+
#if OFFSCREENCANVAS_SUPPORT
49+
emscripten_webgl_do_create_context__postset: `
50+
registerPreMainLoop(() => {
51+
// If the current GL context is an OffscreenCanvas, but it was initialized
52+
// with implicit swap mode, perform the swap on behalf of the user.
53+
if (GL.currentContext && !GL.currentContextIsProxied && !GL.currentContext.attributes.explicitSwapControl && GL.currentContext.GLctx.commit) {
54+
GL.currentContext.GLctx.commit();
55+
}
56+
});`,
57+
#endif
58+
4859
emscripten_webgl_do_create_context__deps: [
4960
#if OFFSCREENCANVAS_SUPPORT
61+
'$registerPreMainLoop',
5062
'malloc',
5163
'emscripten_supports_offscreencanvas',
5264
#endif
@@ -184,6 +196,7 @@ var LibraryHtml5WebGL = {
184196
var contextHandle = GL.createContext(canvas, contextAttributes);
185197
return contextHandle;
186198
},
199+
187200
#if PTHREADS && OFFSCREEN_FRAMEBUFFER
188201
// Runs on the calling thread, proxies if needed.
189202
emscripten_webgl_make_context_current_calling_thread__sig: 'ip',
@@ -196,6 +209,19 @@ var LibraryHtml5WebGL = {
196209
// 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
197210
// 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
198211
// has already accepted the GL context activation for our pthread, so that side is good)
212+
#if GL_SUPPORT_EXPLICIT_SWAP_CONTROL
213+
_emscripten_proxied_gl_context_activated_from_main_browser_thread__deps: ['$registerPreMainLoop'],
214+
_emscripten_proxied_gl_context_activated_from_main_browser_thread__postjs: `
215+
// If the current GL context is a proxied regular WebGL context, and was
216+
// initialized with implicit swap mode on the main thread, and we are on the
217+
// parent thread, perform the swap on behalf of the user.
218+
registerPreMainLoop(() => {
219+
if (GL.currentContext && GL.currentContextIsProxied) {
220+
var explicitSwapControl = {{{ makeGetValue('GL.currentContext', 0, 'i32') }}};
221+
if (!explicitSwapControl) _emscripten_webgl_commit_frame();
222+
}
223+
});`,
224+
#endif
199225
_emscripten_proxied_gl_context_activated_from_main_browser_thread: (contextHandle) => {
200226
GLctx = Module.ctx = GL.currentContext = contextHandle;
201227
GL.currentContextIsProxied = true;

src/library_sdl.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2311,8 +2311,13 @@ var LibrarySDL = {
23112311

23122312
// SDL_Audio
23132313

2314-
SDL_OpenAudio__deps: ['$autoResumeAudioContext', '$safeSetTimeout'],
2314+
SDL_OpenAudio__deps: ['$autoResumeAudioContext', '$safeSetTimeout', '$registerPostMainLoop'],
23152315
SDL_OpenAudio__proxy: 'sync',
2316+
SDL_OpenAudio__postset: `
2317+
// Queue new audio data. This is important to be right after the main loop
2318+
// invocation, so that we will immediately be able to queue the newest
2319+
// produced audio samples.
2320+
registerPostMainLoop(() => SDL.audio?.queueNewAudioData?.());`,
23162321
SDL_OpenAudio: (desired, obtained) => {
23172322
try {
23182323
SDL.audio = {

src/library_webgl.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,17 @@ for (/**@suppress{duplicate}*/var i = 0; i <= {{{ GL_POOL_TEMP_BUFFERS_SIZE }}};
252252
'$webgl_enable_WEBGL_multi_draw',
253253
'$getEmscriptenSupportedExtensions',
254254
#endif // GL_SUPPORT_AUTOMATIC_ENABLE_EXTENSIONS
255+
#if FULL_ES2 || LEGACY_GL_EMULATION
256+
'$registerPreMainLoop',
257+
#endif
255258
],
259+
#if FULL_ES2 || LEGACY_GL_EMULATION
260+
$GL__postset: `
261+
// Signal GL rendering layer that processing of a new frame is about to
262+
// start. This helps it optimize VBO double-buffering and reduce GPU stalls.
263+
registerPreMainLoop(() => GL.newRenderingFrameStarted());
264+
`,
265+
#endif
256266
$GL: {
257267
#if GL_DEBUG
258268
debug: true,

0 commit comments

Comments
 (0)