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:
Jukka Jylänki 2014-09-27 01:56:20 +03:00
Родитель 96912bee22
Коммит 38321c2c0b
4 изменённых файлов: 82 добавлений и 15 удалений

Просмотреть файл

@ -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);