Browse Source

Platform: light-weight main loop for EmscriptenApplication

Signed-off-by: Squareys <squareys@googlemail.com>
pull/300/head
Squareys 7 years ago committed by Vladimír Vondruš
parent
commit
6e5f6b8858
  1. 84
      src/Magnum/Platform/EmscriptenApplication.cpp
  2. 89
      src/Magnum/Platform/EmscriptenApplication.h
  3. 5
      src/Magnum/Platform/Sdl2Application.h
  4. 10
      src/Magnum/Platform/Test/EmscriptenApplicationTest.cpp

84
src/Magnum/Platform/EmscriptenApplication.cpp

@ -265,6 +265,7 @@ bool EmscriptenApplication::tryCreate(const Configuration& configuration) {
}
setupCallbacks(!!(configuration.windowFlags() & Configuration::WindowFlag::Resizable));
setupAnimationFrame(!!(configuration.windowFlags() & Configuration::WindowFlag::AlwaysRequestAnimationFrame));
return true;
}
@ -353,6 +354,7 @@ bool EmscriptenApplication::tryCreate(const Configuration& configuration, const
CORRADE_INTERNAL_ASSERT_OUTPUT(emscripten_webgl_make_context_current(_glContext = context) == EMSCRIPTEN_RESULT_SUCCESS);
setupCallbacks(!!(configuration.windowFlags() & Configuration::WindowFlag::Resizable));
setupAnimationFrame(!!(configuration.windowFlags() & Configuration::WindowFlag::AlwaysRequestAnimationFrame));
/* Return true if the initialization succeeds */
return _context->tryCreate();
@ -535,6 +537,49 @@ void EmscriptenApplication::setupCallbacks(bool resizable) {
}
}
void EmscriptenApplication::setupAnimationFrame(bool forceAnimationFrame) {
if(forceAnimationFrame) {
_callback = [](void* userData) -> int {
auto& app = *static_cast<EmscriptenApplication*>(userData);
if(app._flags & Flag::ExitRequested) {
app._flags &= ~Flag::LoopActive;
return false;
}
if(app._flags & Flag::Redraw) {
app._flags &= ~Flag::Redraw;
app.drawEvent();
}
return true;
};
} else {
_callback = [](void* userData) -> int {
auto& app = *static_cast<EmscriptenApplication*>(userData);
if((app._flags & Flag::Redraw) && !(app._flags & Flag::ExitRequested)) {
app._flags &= ~Flag::Redraw;
app.drawEvent();
}
/* If redraw is requested, we will not cancel the already requested
animation frame.
If ForceAnimationFrame is set, we will request an animation frame
even if redraw is not requested. */
if((app._flags & Flag::Redraw) && !(app._flags & Flag::ExitRequested)) {
return true;
}
/* Cancel last requested animation frame and make redraw()
requestAnimationFrame again next time */
app._flags &= ~Flag::LoopActive;
return false;
};
}
}
void EmscriptenApplication::startTextInput() {
_flags |= Flag::TextInputActive;
}
@ -562,18 +607,41 @@ EmscriptenApplication::GLConfiguration::GLConfiguration():
#endif
int EmscriptenApplication::exec() {
emscripten_set_main_loop_arg([](void* userData) {
auto& app = *static_cast<EmscriptenApplication*>(userData);
if(!(app._flags & Flag::Redraw)) return;
app._flags &= ~Flag::Redraw;
app.drawEvent();
}, this, 0, true);
redraw();
return 0;
}
void EmscriptenApplication::redraw() {
_flags |= Flag::Redraw;
/* Loop already running, no need to start,
Note that should javascript runtimes ever be multithreaded, we
will have a reentrancy issue here. */
if(_flags & Flag::LoopActive) return;
/* Start requestAnimationFrame loop */
_flags |= Flag::LoopActive;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdollar-in-identifier-extension"
EM_ASM({
/* Animation frame callback */
var drawEvent = function() {
var id = window.requestAnimationFrame(drawEvent);
/* Call our callback via function pointer returning int with two
int params */
if(!dynCall('ii', $0, [$1])) {
window.cancelAnimationFrame(id);
}
};
window.requestAnimationFrame(drawEvent);
}, _callback, this);
#pragma GCC diagnostic pop
}
void EmscriptenApplication::exit(int) {
emscripten_cancel_main_loop();
_flags |= Flag::ExitRequested;
}
EmscriptenApplication::MouseEvent::Button EmscriptenApplication::MouseEvent::button() const {

89
src/Magnum/Platform/EmscriptenApplication.h

@ -155,6 +155,36 @@ Unlike desktop platforms, the browser has no concept of application exit code,
so the return value of @ref exec() is always @cpp 0 @ce and whatever is passed
to @ref exit(int) is ignored.
@subsection Platform-EmscriptenApplication-browser-main-loop Main loop implementation
Magnum application implementations default to redrawing only when needed to
save power and while this is simple to implement efficiently on desktop apps
where the application has the full control over the main loop, it's harder in
the callback-based browser environment.
@ref Sdl2Application makes use of @m_class{m-doc-external} [emscripten_set_main_loop()](https://emscripten.org/docs/api_reference/emscripten.h.html#c.emscripten_set_main_loop),
which periodically calls @m_class{m-doc-external} [window.requestAnimationFrame()](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame)
in order to maintain a steady frame rate. For apps that need to redraw only
when needed this means the callback will be called 60 times per second only to
be a no-op. While that's still significantly more efficient than drawing
everything each time, it still means the browser has to wake up 60 times per
second to do nothing.
@ref EmscriptenApplication instead makes use of `requestAnimationFrame()`
directly --- on initialization and on @ref redraw(), an animation frame will be
requested and the callback set up. The callback will immediately schedule
another animation frame, but cancel that request after @ref drawEvent() if
@ref redraw() was not requested. Note that due to the way Emscripten internals
work, this also requires the class instance to be stored as a global variable
instead of a local variable in @cpp main() @ce. The
@ref MAGNUM_EMSCRIPTENAPPLICATION_MAIN() macro handles this in a portable way
for you.
For testing purposes or for more predictable behavior for example when the
application has to redraw all the time anyway this can be disabled using
@ref Configuration::WindowFlag::AlwaysRequestAnimationFrame. Setting the flag
will make the main loop behave equivalently to @ref Sdl2Application.
@section Platform-EmscriptenApplication-webgl WebGL-specific behavior
While WebGL itself requires all extensions to be
@ -322,8 +352,13 @@ class EmscriptenApplication {
protected:
/* Nobody will need to have (and delete) EmscriptenApplication*, thus
this is faster than public pure virtual destructor */
~EmscriptenApplication();
just making it protected would be faster than public pure virtual
destructor. However, because we store it in a Pointer in
MAGNUM_EMSCRIPTENAPPLICATION_MAIN(), Clang complains that
"delete called on non-final 'MyApplication' that has virtual
functions but non-virtual destructor", so we have to mark it virtual
anyway. */
virtual ~EmscriptenApplication();
#ifdef MAGNUM_TARGET_GL
/**
@ -467,7 +502,7 @@ class EmscriptenApplication {
void swapBuffers();
/** @copydoc Sdl2Application::redraw() */
void redraw() { _flags |= Flag::Redraw; }
void redraw();
private:
/** @copydoc GlfwApplication::viewportEvent(ViewportEvent&) */
@ -581,7 +616,9 @@ class EmscriptenApplication {
private:
enum class Flag: UnsignedByte {
Redraw = 1 << 0,
TextInputActive = 1 << 1
TextInputActive = 1 << 1,
ExitRequested = 1 << 2,
LoopActive = 1 << 3
};
typedef Containers::EnumSet<Flag> Flags;
@ -589,6 +626,7 @@ class EmscriptenApplication {
/* Sorry, but can't use Configuration::WindowFlags here :( */
void setupCallbacks(bool resizable);
void setupAnimationFrame(bool ForceAnimationFrame);
Vector2 _devicePixelRatio, _dpiScaling;
Vector2i _lastKnownCanvasSize;
@ -603,6 +641,9 @@ class EmscriptenApplication {
/* These are saved from command-line arguments */
bool _verboseLog{};
Vector2 _commandLineDpiScaling;
/* Animation frame callback */
int (*_callback)(void*);
};
CORRADE_ENUMSET_OPERATORS(EmscriptenApplication::Flags)
@ -846,7 +887,22 @@ class EmscriptenApplication::Configuration {
*
* Implement @ref viewportEvent() to react to the resizing events.
*/
Resizable = 1 << 1
Resizable = 1 << 1,
/**
* Always request the next animation frame. Disables the
* idle-efficient main loop described in
* @ref Platform-EmscriptenApplication-browser-main-loop and
* unconditionally schedules @m_class{m-doc-external} [window.requestAnimationFrame()](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame),
* matching the behavior of @ref Sdl2Application. Useful for
* testing or for simpler internal state when your app is going to
* redraw all the time anyway.
*
* Note that this does not affect how @ref drawEvent() is executed
* --- it depends on @ref redraw() being called independently of
* this flag being set.
*/
AlwaysRequestAnimationFrame = 1 << 2
};
/**
@ -1481,24 +1537,23 @@ class EmscriptenApplication::TextInputEvent {
See @ref Magnum::Platform::EmscriptenApplication "Platform::EmscriptenApplication"
for usage information. This macro abstracts out platform-specific entry point
code. See
@ref portability-applications for more information.
@code{.cpp}
int main(int argc, char** argv) {
className app({argc, argv});
return app.exec();
}
@endcode
code. See @ref portability-applications for more information.
When no other application header is included this macro is also aliased to
@cpp MAGNUM_APPLICATION_MAIN() @ce.
Compared to for example @ref MAGNUM_SDL2APPLICATION_MAIN(), the macro
instantiates the application instance as a global variable instead of a local
variable inside @cpp main() @ce. This is in order to support the
@ref Platform-EmscriptenApplication-browser-main-loop "idle-efficient main loop",
as otherwise the local scope would end before any event callback has a chance
to happen.
*/
#define MAGNUM_EMSCRIPTENAPPLICATION_MAIN(className) \
namespace { Corrade::Containers::Pointer<className> emscriptenApplicationInstance ; } \
int main(int argc, char** argv) { \
className app({argc, argv}); \
app.exec(); \
return 0; \
emscriptenApplicationInstance.reset(new className{{argc, argv}}); \
return emscriptenApplicationInstance->exec(); \
}
#ifndef DOXYGEN_GENERATING_OUTPUT

5
src/Magnum/Platform/Sdl2Application.h

@ -278,6 +278,11 @@ If you enable @ref Configuration::WindowFlag::Resizable, the canvas will be
resized when size of the canvas changes and you get @ref viewportEvent(). If
the flag is not enabled, no canvas resizing is performed.
@note While this implementation supports Esmcripten and is going to continue
supporting it for the foreseeable future, @ref EmscriptenApplication is now
the preferred application implementation for the web. It offers a broader
range of features, more efficient idle behavior and smaller code size.
@subsection Platform-Sdl2Application-usage-gles OpenGL ES specifics
For OpenGL ES, SDL2 defaults to a "desktop GLES" context of the system driver.

10
src/Magnum/Platform/Test/EmscriptenApplicationTest.cpp

@ -53,9 +53,14 @@ struct EmscriptenApplicationTest: Platform::Application {
}
virtual void drawEvent() override {
Debug() << "draw event";
GL::defaultFramebuffer.clear(GL::FramebufferClear::Color);
swapBuffers();
if(_redraw) {
redraw();
}
}
#ifdef MAGNUM_TARGET_GL
@ -96,6 +101,10 @@ struct EmscriptenApplicationTest: Platform::Application {
if(event.key() == KeyEvent::Key::F1) {
Debug{} << "starting text input";
startTextInput();
} else if(event.key() == KeyEvent::Key::F2) {
_redraw = !_redraw;
Debug{} << "redrawing" << (_redraw ? "enabled" : "disabled");
if(_redraw) redraw();
} else if(event.key() == KeyEvent::Key::Esc) {
Debug{} << "stopping text input";
stopTextInput();
@ -130,6 +139,7 @@ struct EmscriptenApplicationTest: Platform::Application {
private:
bool _fullscreen = false;
bool _redraw = false;
};
}}}

Loading…
Cancel
Save