Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a comment that as an optimization, this does not include MainLoop but instead checks for it at runtime and does nothing?

Separately, I wonder if we can do better than this. We could in theory add a "weak linking" mechanism, that would add some code only if MainLoop was actually included. Though if it's complicated it might not be worth it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly I don't think we have such a weak linking mechanism today. The way I did it here I think is the closest thing we have to weak linking in JS code.

// Does nothing unless $MainLoop is included/used.
typeof MainLoop != 'undefined' && MainLoop.postMainLoop.push(f);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
typeof MainLoop != 'undefined' && MainLoop.postMainLoop.push(f);
MainLoop?.postMainLoop.push(f);

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly I don't think the ? operator works like that (i.e. it doesn't work on top level things):

$ node
Welcome to Node.js v18.20.3.
Type ".help" for more information.
> a?.foo
Uncaught ReferenceError: a is not defined
> 

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining:

Optional chaining cannot be used on a non-declared root object

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry. That is unfortunate, I wonder why it has that restriction.

},

$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
Loading