Implement support for setting a swap interval on the main loop update rate via the function emscripten_set_main_loop_interval. Add test and documentation.
This commit is contained in:
Родитель
96912bee22
Коммит
38321c2c0b
|
@ -237,6 +237,24 @@ Functions
|
|||
|
||||
See also :c:func:`emscripten_set_main_loop` and :c:func:`emscripten_set_main_loop_arg` for information about setting and using the main loop.
|
||||
|
||||
.. c:function:: int emscripten_set_main_loop_interval(int interval)
|
||||
|
||||
Specifies the interval that the current main loop tick function will be called with.
|
||||
|
||||
This function can be used to interactively control the rate at which Emscripten runtime drives the main loop specified by calling the function :c:func:`emscripten_set_main_loop`. In native development, this corresponds with the "swap interval" or the "presentation interval" for 3D rendering. The new tick interval specified by this function takes effect immediately on the existing main loop, and this function must be called only after setting up a main loop via :c:func:`emscripten_set_main_loop`.
|
||||
|
||||
:param int interval: The swap interval to activate for the main loop. This interval value has the following overloaded interpretation:
|
||||
|
||||
- If a positive value is specified, then ``interval`` denotes a fixed number of milliseconds between subsequent ticks to the main loop, and updates occur independent of the vsync rate of the display (vsync off). This method uses the JavaScript ``setTimeout`` function to drive the animation.
|
||||
- If ``interval`` is zero, then the runtime will call the tick function as fast as possible without throttling, by using the ``setTimeout`` function.
|
||||
- If ``interval`` is negative, then updates are performed using the ``requestAnimationFrame`` function (with vsync enabled), and this value is interpreted as a "swap interval" rate for the main loop. The value of ``-1`` specifies the runtime that it should render at every vsync (typically 60fps), whereas the value ``-2`` means that the main loop callback should be called only every second vsync (30fps). As a general formula, the value ``-n`` means that the main loop is updated at every n'th vsync, or at a rate of ``60/n`` for 60Hz displays, and ``120/n`` for 120Hz displays.
|
||||
|
||||
:rtype: int
|
||||
:return: The value 0 is returned on success, and a nonzero value is returned on failure. A failure occurs if there is no main loop active before calling this function.
|
||||
|
||||
.. note:: Browsers heavily optimize towards using ``requestAnimationFrame`` for animation instead of ``setTimeout``. Because of that, for best experience across browsers, calling this function with the value of ``-1`` will yield best results. Using the JavaScript ``setTimeout`` function is known to cause stutter and generally worse experience than using the ``requestAnimationFrame`` function. Using a value of ``-2`` or lower can be interesting for power-saving purposes, whereas using the value of zero can be interesting for benchmarking purposes.
|
||||
|
||||
.. note:: There is a functional difference between ``setTimeout`` and ``requestAnimationFrame``: If the user minimizes the browser window or hides your application tab, browsers will typically stop calling ``requestAnimationFrame`` callbacks, but ``setTimeout``-based main loop will continue to be run, although with heavily throttled intervals. See `setTimeout on MDN <https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers.setTimeout#Inactive_tabs>` for more information.
|
||||
|
||||
.. c:function:: void emscripten_set_main_loop_expected_blockers(int num)
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
// Utilities for browser environments
|
||||
mergeInto(LibraryManager.library, {
|
||||
$Browser__deps: ['$PATH', 'emscripten_set_main_loop'],
|
||||
$Browser__deps: ['$PATH', 'emscripten_set_main_loop', 'emscripten_set_main_loop_interval'],
|
||||
$Browser__postset: 'Module["requestFullScreen"] = function Module_requestFullScreen(lockPointer, resizeCanvas) { Browser.requestFullScreen(lockPointer, resizeCanvas) };\n' + // exports
|
||||
'Module["requestAnimationFrame"] = function Module_requestAnimationFrame(func) { Browser.requestAnimationFrame(func) };\n' +
|
||||
'Module["setCanvasSize"] = function Module_setCanvasSize(width, height, noUpdates) { Browser.setCanvasSize(width, height, noUpdates) };\n' +
|
||||
|
@ -16,6 +16,10 @@ mergeInto(LibraryManager.library, {
|
|||
// Each main loop is numbered with a ID in sequence order. Only one main loop can run at a time. This variable stores the ordinal number of the main loop that is currently
|
||||
// allowed to run. All previous main loops will quit themselves. This is incremented whenever a new main loop is created.
|
||||
currentlyRunningMainloop: 0,
|
||||
func: null, // The main loop tick function that will be called at each iteration.
|
||||
arg: 0, // The argument that will be passed to the main loop. (of type void*)
|
||||
interval: -1, // A positive or zero value denotes a msec value for the desired update rate. A negative value means a swap interval (-1 = every rAF, most often 60fps, -2 = every second rAF, or 30fps, and so on)
|
||||
currentFrameNumber: 0,
|
||||
queue: [],
|
||||
pause: function() {
|
||||
Browser.mainLoop.scheduler = null;
|
||||
|
@ -23,7 +27,9 @@ mergeInto(LibraryManager.library, {
|
|||
},
|
||||
resume: function() {
|
||||
Browser.mainLoop.currentlyRunningMainloop++;
|
||||
_emscripten_set_main_loop(Browser.mainLoop.func, Browser.mainLoop.fps, false, Browser.mainLoop.arg);
|
||||
var interval = Browser.mainLoop.interval;
|
||||
_emscripten_set_main_loop(Browser.mainLoop.func, (interval > 0) ? (1000.0 / interval) : 0, false, Browser.mainLoop.arg);
|
||||
_emscripten_set_main_loop_interval(interval);
|
||||
},
|
||||
updateStatus: function() {
|
||||
if (Module['setStatus']) {
|
||||
|
@ -962,13 +968,37 @@ mergeInto(LibraryManager.library, {
|
|||
document.body.appendChild(script);
|
||||
},
|
||||
|
||||
emscripten_set_main_loop_interval: function(interval) {
|
||||
Browser.mainLoop.interval = interval;
|
||||
|
||||
if (!Browser.mainLoop.func) {
|
||||
#if ASSERTIONS
|
||||
console.error('emscripten_set_main_loop_interval: Cannot set swap interval for main loop since a main loop does not exist! Call emscripten_set_main_loop first to set one up.');
|
||||
#endif
|
||||
return 1; // Return non-zero on failure, can't set swap interval when there is no main loop.
|
||||
}
|
||||
|
||||
if (interval >= 0) {
|
||||
Browser.mainLoop.scheduler = function Browser_mainLoop_scheduler() {
|
||||
setTimeout(Browser.mainLoop.runner, interval); // doing this each time means that on exception, we stop
|
||||
};
|
||||
Browser.mainLoop.method = 'timeout';
|
||||
} else {
|
||||
Browser.mainLoop.scheduler = function Browser_mainLoop_scheduler() {
|
||||
Browser.requestAnimationFrame(Browser.mainLoop.runner);
|
||||
};
|
||||
Browser.mainLoop.method = 'rAF';
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
|
||||
emscripten_set_main_loop__deps: ['emscripten_set_main_loop_interval'],
|
||||
emscripten_set_main_loop: function(func, fps, simulateInfiniteLoop, arg) {
|
||||
Module['noExitRuntime'] = true;
|
||||
|
||||
assert(!Browser.mainLoop.scheduler, 'there can only be one main loop function at once: call emscripten_cancel_main_loop to cancel the previous one before setting a new one with different parameters.');
|
||||
assert(!Browser.mainLoop.func, 'emscripten_set_main_loop: there can only be one main loop function at once: call emscripten_cancel_main_loop to cancel the previous one before setting a new one with different parameters.');
|
||||
|
||||
Browser.mainLoop.func = func;
|
||||
Browser.mainLoop.fps = fps;
|
||||
Browser.mainLoop.arg = arg;
|
||||
|
||||
var thisMainLoopId = Browser.mainLoop.currentlyRunningMainloop;
|
||||
|
@ -999,6 +1029,14 @@ mergeInto(LibraryManager.library, {
|
|||
// catch pauses from non-main loop sources
|
||||
if (thisMainLoopId < Browser.mainLoop.currentlyRunningMainloop) return;
|
||||
|
||||
// Implement very basic swap interval control
|
||||
Browser.mainLoop.currentFrameNumber = Browser.mainLoop.currentFrameNumber + 1 | 0;
|
||||
if (Browser.mainLoop.interval < 0 && Browser.mainLoop.currentFrameNumber % -Browser.mainLoop.interval != 0) {
|
||||
// Not the scheduled time to render this frame - skip.
|
||||
Browser.mainLoop.scheduler();
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 USES_GL_EMULATION
|
||||
|
@ -1029,17 +1067,10 @@ mergeInto(LibraryManager.library, {
|
|||
|
||||
Browser.mainLoop.scheduler();
|
||||
}
|
||||
if (fps && fps > 0) {
|
||||
Browser.mainLoop.scheduler = function Browser_mainLoop_scheduler() {
|
||||
setTimeout(Browser.mainLoop.runner, 1000/fps); // doing this each time means that on exception, we stop
|
||||
};
|
||||
Browser.mainLoop.method = 'timeout';
|
||||
} else {
|
||||
Browser.mainLoop.scheduler = function Browser_mainLoop_scheduler() {
|
||||
Browser.requestAnimationFrame(Browser.mainLoop.runner);
|
||||
};
|
||||
Browser.mainLoop.method = 'rAF';
|
||||
}
|
||||
|
||||
if (fps && fps > 0) _emscripten_set_main_loop_interval(1000.0 / fps);
|
||||
else _emscripten_set_main_loop_interval(-1); // Do rAF by rendering each frame (no decimating)
|
||||
|
||||
Browser.mainLoop.scheduler();
|
||||
|
||||
if (simulateInfiniteLoop) {
|
||||
|
@ -1054,6 +1085,7 @@ mergeInto(LibraryManager.library, {
|
|||
|
||||
emscripten_cancel_main_loop: function() {
|
||||
Browser.mainLoop.pause();
|
||||
Browser.mainLoop.func = null;
|
||||
},
|
||||
|
||||
emscripten_pause_main_loop: function() {
|
||||
|
|
|
@ -63,6 +63,7 @@ extern void emscripten_async_load_script(const char *script, em_callback_func on
|
|||
|
||||
#if __EMSCRIPTEN__
|
||||
extern void emscripten_set_main_loop(em_callback_func func, int fps, int simulate_infinite_loop);
|
||||
extern int emscripten_set_main_loop_interval(int interval);
|
||||
extern void emscripten_set_main_loop_arg(em_arg_callback_func func, void *arg, int fps, int simulate_infinite_loop);
|
||||
extern void emscripten_pause_main_loop(void);
|
||||
extern void emscripten_resume_main_loop(void);
|
||||
|
|
|
@ -30,9 +30,25 @@ void looper() {
|
|||
exit(0);
|
||||
}
|
||||
prevTime = curTime;
|
||||
if ((frame == 25 || frame == 45 || frame == 65) && timeSincePrevious < 30) {
|
||||
printf("Abort: With swap interval of 4, we should be running at most 15fps! (or 30fps on 120Hz displays) but seems like swap control is not working and we are running at 60fps!\n");
|
||||
int result = 1;
|
||||
#ifdef REPORT_RESULT
|
||||
REPORT_RESULT();
|
||||
#endif
|
||||
emscripten_cancel_main_loop();
|
||||
exit(0);
|
||||
}
|
||||
if (frame > 0 && frame < 90 && frame % 10 == 0) {
|
||||
emscripten_cancel_main_loop();
|
||||
emscripten_set_main_loop(looper, 0, 0);
|
||||
int ret;
|
||||
if (frame % 20 == 0) {
|
||||
ret = emscripten_set_main_loop_interval(-4);
|
||||
} else {
|
||||
ret = emscripten_set_main_loop_interval(-1);
|
||||
}
|
||||
assert(ret == 0);
|
||||
} else if (frame == 90) {
|
||||
emscripten_cancel_main_loop();
|
||||
emscripten_set_main_loop(looper, 100, 1);
|
||||
|
|
Загрузка…
Ссылка в новой задаче