/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020 Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
namespace Magnum {
/** @page platforms-html5 JavaScript, HTML5 and WebGL
@brief Building, testing and deploying HTML5 and WebGL projects
@tableofcontents
@m_footernavigation
@m_keywords{HTML5 Emscripten}
@todoc testing, gl tests, coverage, travis setup
@todoc static plugins
The following guide explains basic workflow of using
[Emscripten](http://kripken.github.io/emscripten-site/) for deploying HTML5
apps using WebGL.
At the very least you need to have Emscripten installed. Running console
applications requires Node.js, running browser apps require a webserver that's
able to serve static content (for example Apache, if you have Python installed,
it has a builtin webserver too).
@note On ArchLinux it's the `emscripten` and `nodejs` packages, both in the
`[extras]` repository.
Cross-compilation to Emscripten is done using a CMake toolchain that's part of
the toolchains repository at https://github.com/mosra/toolchains. Add it as a
submodule to your project or fetch the contents any other way that suits your project. The following guide will assume the contents of the repository are
placed in a `toolchains/` subdirectory.
@code{.sh}
git submodule add git://github.com/mosra/toolchains
@endcode
There are two toolchain files. The `generic/Emscripten.cmake` is for the
classical (asm.js) build, the `generic/Emscripten-wasm.cmake` is for
WebAssembly build. The following guide will work with the WASM toolchain. Don't
forget to adapt `EMSCRIPTEN_PREFIX` variable in
`toolchains/generic/Emscripten*.cmake` to path where Emscripten is installed;
you can also pass it explicitly on command-line using `-DEMSCRIPTEN_PREFIX`.
Default is `/usr/emscripten`.
@section platforms-html5-console Building and running console applications
Emscripten allows you to run arbitrary console utilities and tests via Node.js,
except for all code that accesses browsers APIs such as WebGL or audio.
Assuming you have Magnum installed in the Emscripten path as described in
@ref building-cross-emscripten, build your project simply as this, using one
of the toolchain files from above:
@code{.sh}
mkdir build-emscripten-wasm && cd build-emscripten-wasm
cmake .. \
-DCMAKE_TOOLCHAIN_FILE="path/to/toolchains/generic/Emscripten-wasm.cmake" \
-DCMAKE_BUILD_TYPE=Release
cmake --build .
@endcode
Note that the `CMAKE_TOOLCHAIN_FILE` path needs to be absolute --- otherwise
CMake will silently ignore it and continue compiling natively.
After that you can run the generated JavaScript file using Node.js. Note that
it looks for the corresponding `*.wasm` file in the current directory, so you
need to @cb{.sh} cd @ce there first:
@code{.sh}
cd build-emscripten-wasm/src
node my-application.js
@endcode
@section platforms-html5-apps Building and deploying graphical apps
In case you don't have an OpenGL ES build set up yet, you need to copy
`FindOpenGLES2.cmake` (or `FindOpenGLES3.cmake`) from the
[modules/](https://github.com/mosra/magnum/tree/master/modules) directory in
Magnum source to the `modules/` dir in your project so it is able to find the
WebGL libraries.
Magnum provides Emscripten application wrappers in @ref Platform::Sdl2Application
and @ref Platform::EmscriptenApplication. See their documentation for more
information about general usage. You can also use the Emscripten APIs directly
or any other way.
@note @ref Platform::EmscriptenApplication also contains a fully configured
bootstrap project that is ready to build and deploy. Check its
documentation for details.
To target the web browser, you need to provide a HTML markup for your
application. Template one is below. The markup references two files,
`EmscriptenApplication.js` and `WebApplication.css`, both are in the
[src/Magnum/Platform/](https://github.com/mosra/magnum/tree/master/src/Magnum/Platform)
directory in the source tree, are also put into `share/magnum/` inside your
install prefix and if you use CMake, their @ref cmake "full path is available"
through the `MAGNUM_EMSCRIPTENAPPLICATION_JS` and `MAGNUM_WEBAPPLICATION_CSS`
variables.
@code{.html-jinja}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Magnum Emscripten Application</title>
<link rel="stylesheet" href="WebApplication.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<h1>Magnum Emscripten Application</h1>
<div class="mn-container">
<div class="mn-sizer"><div class="mn-expander"><div class="mn-listener">
<canvas class="mn-canvas" id="canvas"></canvas>
<div class="mn-status" id="status">Initialization...</div>
<div class="mn-status-description" id="status-description"></div>
<script src="EmscriptenApplication.js"></script>
<script async="async" src="{{ application }}.js"></script>
</div></div></div>
</div>
</body>
</html>
@endcode
@m_class{m-note m-danger}
@par
For backwards compatibility purposes the default CSS style supports also
@cb{.css} #container @ce, @cb{.css} #sizer @ce, @cb{.css} #expander @ce,
@cb{.css} #listener @ce, @cb{.css} #canvas @ce, @cb{.css} #status @ce and
@cb{.css} #status-description @ce IDs instead of the @cb{.css} .mn- @ce
prefixed classes, but their use is discouraged as the classes are less
likely to clash with other markup and allow more than one
@cb{.html} <canvas> @ce on a page. This compatibility is scheduled to be
removed in the future.
For basic usage you don't need to modify the CSS and JS files at all and
everything can be set up directly from the HTML markup. Replace
@cb{.jinja} {{ application }} @ce with the name of your application executable
and adapt page title and heading as desired. You can modify all files to your
liking, but the HTML file must contain at least a
@cb{.html} <canvas> @ce referenced using an ID in @cb{.js} Module.canvas @ce
--- by default it's @cb{.css} #canvas @ce but
@ref platforms-html5-multiple-apps "it's possible to override it". The
JavaScript file contains event listeners that print loading status on the page.
The status is displayed in the remaining two @cb{.html} <div> @ce s, if they
are available. The CSS file contains a basic style, see
@ref platforms-html5-layout "below" for available options to tweak the default
look.
In order to deploy the app, you need to put the JS driver code, the WebAssembly
binary (or the asm.js memory image, in case you are compiling with the classic
asm.js toolchain), the HTML markup and the JS/CSS files to a common location.
The following CMake snippet handles all of that:
@code{.cmake}
if(CORRADE_TARGET_EMSCRIPTEN)
install(TARGETS my-application DESTINATION ${CMAKE_INSTALL_PREFIX})
install(FILES
my-application.html
${MAGNUM_EMSCRIPTENAPPLICATION_JS}
${MAGNUM_WEBAPPLICATION_CSS}
DESTINATION ${CMAKE_INSTALL_PREFIX})
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/my-application.js.mem
${CMAKE_CURRENT_BINARY_DIR}/my-application.wasm
DESTINATION ${CMAKE_INSTALL_PREFIX} OPTIONAL)
endif()
@endcode
To deploy, you can either point `CMAKE_INSTALL_PREFIX` to a location inside
your system webserver or you can point it to an arbitrary directory and use
Python's builtin webserver to serve its contents:
@code{.sh}
cd build-emscripten-wasm
cmake -DCMAKE_INSTALL_PREFIX=/path/to/my/emscripten/deploy ..
cmake --build . --target install
cd /path/to/my/emscripten/deploy
python -m http.server # or python -m SimpleHTTPServer with Python 2
@endcode
After that, you can open http://localhost:8000 to see the files. Stop the
webserver again by pressing @m_class{m-label m-warning} **Ctrl**
@m_class{m-label m-default} **C**.
@section platforms-html5-windowless-apps Building and deploying windowless apps
In case you don't have an EGL + OpenGL ES build set up yet, you need to copy
`FindEGL.cmake` and `FindOpenGLES2.cmake` (or `FindOpenGLES3.cmake`) from the
[modules/](https://github.com/mosra/magnum/tree/master/modules) directory in
Magnum source to the `modules/` dir in your project so it is able to find the
EGL and WebGL libraries.
Windowless Magnum apps (i.e. apps that use the OpenGL context without a window)
can be run in the browser as well using the @ref Platform::WindowlessEglApplication
class. See its documentation for more information about general usage. You can
also use the Emscripten APIs directly or any other way.
@note @ref Platform::WindowlessEglApplication also contains a fully configured
bootstrap project that's ready to build and deploy. Check its documentation
for details.
Similarly to graphics apps, you need to provide a HTML markup for your
application. Template one is below, its main difference from the one above is
that it shows the console output instead of the canvas. The markup references
two files, `WindowlessEmscriptenApplication.js` and `WebApplication.css`, both
are again in the [src/Magnum/Platform/](https://github.com/mosra/magnum/tree/master/src/Magnum/Platform)
directory in the source tree, are put into `share/magnum/` inside your
install prefix and available through `MAGNUM_WINDOWLESSEMSCRIPTENAPPLICATION_JS`
and `MAGNUM_WEBAPPLICATION_CSS` CMake variables.
@code{.html-jinja}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Magnum Windowless Emscripten Application</title>
<link rel="stylesheet" href="WebApplication.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<h1>Magnum Windowless Emscripten Application</h1>
<div class="mn-container">
<div class="mn-sizer"><div class="mn-expander"><div class="mn-listener">
<canvas class="mn-canvas mn-hidden" id="canvas"></canvas>
<pre class="mn-log" id="log"></pre>
<div class="mn-status" id="status">Initialization...</div>
<div class="mn-status-description" id="status-description"></div>
<script src="WindowlessEmscriptenApplication.js"></script>
<script async="async" src="{{ application }}.js"></script>
</div></div></div>
</div>
</body>
</html>
@endcode
@m_class{m-note m-danger}
@par
For backwards compatibility purposes the default CSS style supports also
@cb{.css} #container @ce, @cb{.css} #sizer @ce, @cb{.css} #expander @ce,
@cb{.css} #listener @ce, @cb{.css} #canvas @ce, @cb{.css} #log @ce,
@cb{.css} #status @ce and @cb{.css} #status-description @ce IDs instead of
the @cb{.css} .mn- @ce prefixed classes, but their use is discouraged as
the classes are less likely to clash with other markup and allow more than
one @cb{.html} <canvas> @ce on a page. This compatibility is scheduled to
be removed in the future.
Replace @cb{.jinja} {{ application }} @ce with the name of your application
executable. You can modify all the files to your liking, but the HTML file must
contain at least the @cb{.html} <canvas> @ce enclosed in listener @cb{.html} <div> @ce
and the @cb{.html} <pre id="log"> @ce for displaying the output. The JavaScript
file contains event listeners which print loading status on the page. The
status displayed in the remaining two @cb{.html} <div> @ce s, if they are
available. The CSS file is shared with graphical apps, again see
@ref platforms-html5-layout "below" for available options to tweak the default
look.
Deployment is similar to @ref platforms-html5-apps "graphical apps", only
referencing a different JS file:
@code{.cmake}
if(CORRADE_TARGET_EMSCRIPTEN)
install(TARGETS my-application DESTINATION ${CMAKE_INSTALL_PREFIX})
install(FILES
my-application.html
${MAGNUM_WINDOWLESSEMSCRIPTENAPPLICATION_JS}
${MAGNUM_WEBAPPLICATION_CSS}
DESTINATION ${CMAKE_INSTALL_PREFIX})
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/my-application.js.mem
${CMAKE_CURRENT_BINARY_DIR}/my-application.wasm
DESTINATION ${CMAKE_INSTALL_PREFIX} OPTIONAL)
endif()
@endcode
@section platforms-html5-multiple-apps Multiple applications on one page
@note Support for this feature is currently only tested with
@ref Platform::EmscriptenApplication. @ref Platform::Sdl2Application
support largely depends on Emscripten's underlying emulation libraries and
may vary across Emscripten versions.
Running multiple applications on a single page is possible, but requires a few
changes. By default, Emscripten populates a global @cb{.js} Module @ce object
with things like callbacks and the canvas element to render into. This is not
suitable when using more than one application. Emscripten's `MODULARIZE` option
creates a script that defines a function with a local @cb{.js} Module @ce
object instead. This way each application runs inside its own function with a
separate environment:
@code{.cmake}
# If you have CMake 3.13+
target_link_options(my-application PRIVATE
"SHELL:-s MODULARIZE"
"SHELL:-s EXPORT_NAME=createModule")
# Or alternatively, supported by older CMake versions as well
target_link_libraries(my-application PRIVATE
"-s MODULARIZE -s EXPORT_NAME=createModule")
@endcode
Instead of running instantly, the application can then be instantiated using
the function name set with `EXPORT_NAME`. The function optionally takes an
object to populate the local @cb{.js} Module @ce.
While `EmscriptenApplication.js` populates the @cb{.js} Module @ce object by
default, it also defines a @cb{.js} createMagnumModule() @ce function that lets
you create new module objects to pass to the Emscripten module function. Pass
an object to it to override the default values. This is the easiest way to set
the canvas or status elements of each application:
@code{.html-jinja}
<script src="EmscriptenApplication.js"></script>
<script src="{{ application }}.js"></script>
<script>
const myModule = createMagnumModule({
canvas: document.getElementById('my-canvas'),
status: document.getElementById('my-status'),
statusDescription: document.getElementById('my-status-description')
});
createModule(myModule).then(function(myModule) {
// application loaded and running
});
</script>
@endcode
@section platforms-html5-layout Modifying page style, canvas size and aspect ratio
The `WebApplication.css` file contains a basic style and the additional
@cb{.css} .mn-container @ce, @cb{.css} .mn-sizer @ce, @cb{.css} .mn-expander @ce
and @cb{.css} .mn-listener @ce @cb{.html} <div> @ce s take care of aligning the
canvas to the center and making it responsively scale on narrow screens,
preserving aspect ratio. For proper responsiveness on all platforms it's
important to include the @cb{.html} <meta name="viewport"> @ce tag in the HTML
markup as well.
By default the canvas is @cpp 640px @ce wide with a 4:3 aspect ratio, you can
modify this by placing one of the @cb{.css} .mn-aspect-* @ce CSS classes on the
@cb{.html} <div class="mn-container"> @ce:
Aspect ratio | CSS class (landscape/portrait)
--------------- | ------------------------------
1:1 | @cb{.css} .mn-aspect-1-1 @ce <b></b>
4:3 (1.33:1) | @cb{.css} .mn-aspect-4-3 @ce (default) / @cb{.css} .mn-aspect-3-4 @ce
3:2 (1.5:1) | @cb{.css} .mn-aspect-3-2 @ce / @cb{.css} .mn-aspect-2-3 @ce
16:9 (1.78:1) | @cb{.css} .mn-aspect-16-9 @ce / @cb{.css} .mn-aspect-9-16 @ce
2:1 | @cb{.css} .mn-aspect-2-1 @ce / @cb{.css} .mn-aspect-1-2 @ce
For example, for a 640x360 canvas (16:9 aspect ratio), you would use
@cb{.html} <div class="mn-container mn-aspect-16-9"> @ce. Besides the
predefined classes above, it's also possible to specify your own aspect ratio
using a @cb{.css} padding-bottom @ce style with a percentage equal to inverse
of the ratio for @cb{.css} div#expander @ce --- for example, a 2.35:1 ratio
would be @cb{.html} <div class="mn-expander" style="padding-bottom: 42.553%"> @ce.
Size of the canvas can be also overriden by specifying one of the
@cb{.css} .mn-width-* @ce CSS classes on the
@cb{.html} <div class="mn-container"> @ce:
Width | CSS class
--------------- | ---------
240px | @cb{.css} .mn-width-240 @ce <b></b>
320px | @cb{.css} .mn-width-320 @ce <b></b>
360px | @cb{.css} .mn-width-360 @ce <b></b>
480px | @cb{.css} .mn-width-480 @ce <b></b>
600px | @cb{.css} .mn-width-600 @ce <b></b>
640px | @cb{.css} .mn-width-640 @ce (default)
800px | @cb{.css} .mn-width-800 @ce <b></b>
For example, for a 480x640 canvas (3:4, portrait) you would use
@cb{.html} <div class="mn-container mn-width-480 mn-aspect-3-4"> @ce. Besides
the predefined classes above, it's also possible to specify your own by adding
a @cb{.css} width @ce style to the @cb{.css} div.mn-sizer @ce --- for example,
a 1024x768 canvas would be
@cb{.html} <div class="mn-sizer" style="width: 1024px"> @ce. Again note that if
the canvas is larger than window width, it gets automatically scaled down,
preserving its aspect ratio.
It's also possible to stretch the canvas to occupy the full page using the
@cb{.css} .mn-fullsize @ce CSS class set to @cb{.css} div.mn-container @ce. In
this case it's advised to remove all other elements (such as the
@cb{.html} <h1> @ce element) from the page to avoid them affecting the canvas.
Combining @cb{.css} .mn-fullsize @ce with the other @cb{.css} .mn-aspect-* @ce
or @cb{.css} .mn-width-* @ce classes is not supported.
Besides the canvas, there's minimal styling for @cb{.html} <h1> @ce,
@cb{.html} <p> @ce and @cb{.html} <code> @ce tags. Putting extra content inside
the @cb{.css} div.mn-sizer @ce will center it, following the canvas width. If
you need more advanced styling, check out [m.css](https://mcss.mosra.cz).
@note It's also possible to modify the container CSS classes from the C++ side
using @ref Platform::EmscriptenApplication::setContainerCssClass() /
@ref Platform::Sdl2Application::setContainerCssClass().
@m_class{m-note m-danger}
@par
For backwards compatibility purposes, on a markup that uses the discouraged
@cb{.css} #container @ce, @cb{.css} #sizer @ce, @cb{.css} #expander @ce,
@cb{.css} #listener @ce, @cb{.css} #status @ce, @cb{.css} #canvas @ce and
@cb{.css} #status-description @ce IDs, the above @cb{.css} .mn-width-* @ce
and @cb{.css} .mn-aspect-* @ce CSS classes are supported without the
@cb{.css} .mn- @ce prefix. This compatibility is scheduled to be removed in
the future.
@section platforms-html5-files Bundling files
Emscripten applications don't have access to a filesystem, which means you need
to bundle the files explicitly. One possibility is via
@ref Corrade::Utility::Resource (basically the same way as you probably already
bundle shader code etc., as shown in the @ref examples-textured-triangle
example). In this case the file is compiled directly into the `*.wasm` binary,
which is the most optimal way, but it means you need to port your code away
from the usual filesystem APIs. Another possibility is via the
@cmake emscripten_embed_file() @ce CMake macro, which is implicitly available
when you build for Emscripten, coming from the [UseEmscripten.cmake](https://github.com/mosra/toolchains/blob/master/modules/UseEmscripten.cmake)
module:
@code{.cmake}
add_executable(MyApplication main.cpp)
if(CORRADE_TARGET_EMSCRIPTEN)
emscripten_embed_file(MyApplication file.dat /path/in/virtual/fs/file.dat)
endif()
@endcode
This approach will make the file available in Emscripten's virtual filesystem,
meaning you can use the usual filesystem APIs to get it. However, the file will
be Base64-encoded in the `*.js` file (and not the `*.wasm` binary), inflating
the file size much more. This approach is thus most useful for quick demos or
test executables where size doesn't matter that much. If you use
@ref Corrade::TestSuite, files can be bundled this way also using the `FILES`
option in the @ref corrade-cmake-add-test "corrade_add_test()" macro.
Among other possibilities for bundling and accessing files is Emscripten's
[file_packager.py](https://emscripten.org/docs/porting/files/packaging_files.html#packaging-using-the-file-packager-tool)
or the various @m_class{m-doc-external} [emscripten_async_wget()](https://emscripten.org/docs/api_reference/emscripten.h.html#c.emscripten_async_wget)
functions, retrieving files from a URL.
@section platforms-html5-events Controlling event behavior
@subsection platforms-html5-events-keyboard Keyboard events
By default, the graphical Emscripten application grabs all keyboard input
anywhere on the page (so e.g. @m_class{m-label m-default} **F5** or opening
browser console via a keyboard shortcut doesn't work). If you don't want it to
grab keyboard input at all, set @cb{.js} Module.doNotCaptureKeyboard @ce to
@cb{.js} true @ce, either by modifying the `EmscriptenApplication.js` file or
directly in the HTML source:
@code{.html}
<script>
// after EmscriptenApplication.js has been loaded
Module.doNotCaptureKeyboard = true;
</script>
@endcode
The above is implicitly set for @ref Platform::WindowlessEglApplication,
because it does not have an event loop.
@cb{.js} Module.doNotCaptureKeyboard @ce is not supported by
@ref Platform::EmscriptenApplication.
Another solution is to specify the element on which it should capture keybard
using @cb{.js} Module.keyboardListeningElement @ce --- it requires the actual
element instance (*not* just its ID). The element then needs to be activated
(for example with a mouse click) in order to start receiving input. The
@cb{.html} <canvas> @ce element is a bit special in this regard --- in order to
make it receive keyboard input, you have to add a `tabindex` attribute to it
like this:
@code{.html}
Platforms: update Emscripten markup for 1.38.27 and up.
This took me a while -- the old behavior for all emscripten_*()
functions was to take a DOM element ID as an argument, with nullptr
acting as a "the element that makes most sense for given operation". The
new behavior when -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 is
enabled is to take a CSS selector instead. Presence of this option is
not detectable at compile time, so there was no easy way of knowing
what's the expected value, whether `"module"` or `"#module"`.
After a few failed attempts, I discoverd that using `"#canvas"` would
work for both the old and the new version -- in the new version it would
be selecting an element with id="canvas", while in the old version it
was a special value denoting Module['canvas']. Problem was, however,
that the markup was historically using id="module" and not id="canvas",
so this had to be changed.
This is a breaking change affecting everyone who targets Emscripten. You
need to update the HTML markup and, in case you maintain copies or forks
of the CSS and JS files, these as well. Details in the changelog.
7 years ago
<canvas id="canvas" tabindex="0"></canvas>
@endcode
After that, the canvas can be focused with a @m_class{m-label m-default} **Tab**
key. But because Emscripten eats all mouse input, the `mousedown` event won't
be propagated to focus the canvas unless you do that manually:
@code{.js}
Module.keyboardListeningElement = Module.canvas;
Module.canvas.addEventListener('mousedown', function(event) {
event.target.focus();
});
@endcode
@subsection platforms-html5-events-contextmenu Context menu
By default, the canvas opens a browser-specific popup menu on right click. That
allows the user to for example save a screenshot of the canvas, but if you are
handling right click directly in your app, you may want to disable the default
behavior:
@code{.js}
Module.canvas.addEventListener('contextmenu', function(event) {
event.preventDefault();
}, true);
@endcode
@section platforms-html5-environment Terminal output, environment and command-line arguments
When running console apps using Node.js, command-line arguments and terminal
output work like usual.
For graphical apps in the browser, `EmscriptenApplication.js` redirects all
output (thus also @ref Corrade::Utility::Debug "Debug",
@ref Corrade::Utility::Warning "Warning" and @ref Corrade::Utility::Error "Error")
to JavaScript console. For windowless apps, `WindowlessEmscriptenApplication.js`
redirects output to the @cb{.html} <pre id="log"> @ce element on the page.
It's possible to pass command-line arguments to @cpp main() @ce using GET URL
parameters. For example, `/app/?foo=bar&fizz&buzz=3` will go to the app as
@cb{.py} ['--foo', 'bar', '--fizz', '--buzz', '3'] @ce.
Emscripten provides its own set of environment variables through
@ref std::getenv() and doesn't expose system environment when running through
Node.js. In order to access system environment, you can use the
@ref Corrade::Utility::Arguments class, especially
@ref Corrade::Utility::Arguments::environment().
@section platforms-html5-webgl Differences between WebGL and OpenGL ES
WebGL is subset of OpenGL ES with some specific restrictions, namely
requirement for unique buffer target binding, aligned buffer offset and
stride and some other restrictions. The most prominent difference is that while
the following was enough on desktop:
@snippet MagnumGL.cpp Buffer-webgl-nope
On WebGL (even 2.0) you always have to initialize the buffers like this (and
other target hints for UBOs etc.):
@snippet MagnumGL.cpp Buffer-webgl
See @ref GL-Buffer-webgl-restrictions "GL::Buffer",
@ref GL-Mesh-webgl-restrictions "GL::Mesh",
@ref GL::Texture::setSubImage() "GL::*Texture::setSubImage()",
@ref GL::Mesh::addVertexBuffer(), @ref GL::Renderer::setStencilFunction(),
@ref GL::Renderer::setStencilMask() and @ref GL::Renderer::setBlendFunction()
documentation for more information. The corresponding sections in official
WebGL specification provide even more detail:
- [Differences Between WebGL and OpenGL ES 2.0](http://www.khronos.org/registry/webgl/specs/latest/1.0/#6)
- [Differences Between WebGL and OpenGL ES 3.0](https://www.khronos.org/registry/webgl/specs/latest/2.0/#5)
@subsection platforms-html5-webgl-timer-queries Timer queries not available in the browser
Similarly to [Rowhammer](https://en.wikipedia.org/wiki/Row_hammer), GPU timer
queries could be abused to do a bit-flip attack, which is the reason why
@webgl_extension{EXT,disjoint_timer_query} / @webgl_extension{EXT,disjoint_timer_query_webgl2} might not be available in
your browser. Recent versions of Chrome [expose it again](https://bugs.chromium.org/p/chromium/issues/detail?id=823863),
and Firefox exposes it if you enable `webgl.enable-privileged-extensions` in
`about:config` ([source](https://www.khronos.org/webgl/public-mailing-list/public_webgl/1803/msg00017.php)).
[More details about the GLitch vulnerability.](https://www.vusec.net/wp-content/uploads/2018/05/glitch.pdf)
@subsection platforms-html5-webgl-queries-zero Queries always report zero results
Compared to OpenGL ES, WebGL [has an additional restriction](https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.12)
where it's not guaranteed for a @ref GL::PrimitiveQuery, @ref GL::SampleQuery
or @ref GL::TimeQuery to return a result in the same frame, which in practice
means that the queries will return zero. This is particularly problematic in
headless tests / benchmarks and currently there's no builtin way in Magnum to
circumvent this limitation.
If you're on Firefox, you can enable the `webgl.allow-immediate-queries` option
in `about:flags`, which will make these work even without giving the control
back to the browser.
@section platforms-html5-code-size Compilation time / code size tradeoffs
By default, the toolchain configures the compiler and linker to use `-O3` in
`Release` builds, together with enabling `--llvm-lto 1` for the linker. When
deploying, ensure your `CMAKE_BUILD_TYPE` is set to `Release`, otherwise the
generated file sizes will be huge due to the additional debug information and
non-minified JS driver file. Also, unless you need compatibility with
non-WebAssembly-capable browsers, ensure you're compiling with the
`Emscripten-wasm.cmake` toolchain. Further optimizations are possible but are
not enabled by default in the toolchain file due to the features either slowing
down the build significantly or being very recent:
- use [`-Os`](https://emscripten.org/docs/tools_reference/emcc.html#emcc-os)
or [`-Oz`](https://emscripten.org/docs/tools_reference/emcc.html#emcc-oz)
to compile and link with optimizations for code size at the expense of
performance and increased compile times
- enable [`--closure 1`](https://emscripten.org/docs/tools_reference/emcc.html#emcc-closure)
for further minification of the JavaScript driver file (the @ref Audio
library needs at least Emscripten 1.38.21, see
[emscripten-core/emscripten#7558](https://github.com/emscripten-core/emscripten/pull/7558)
for more information) --- however, be prepared for *serious* increase in
compile times and memory usage of the Closure Compiler
- disable assertions with [`-s ASSERTIONS=0`](https://emscripten.org/docs/tools_reference/emcc.html#emcc-s-option-value)
(enabled by default)
- [`-s MINIMAL_RUNTIME=1`](https://github.com/emscripten-core/emscripten/pull/7923)
(new since 1.38.27) --- note that, while this leads to significant size
savings, it currently requires significant amount of work on the
application side and Magnum was not yet tested with this option enabled
- [`-s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1`](https://github.com/emscripten-core/emscripten/pull/7977)
(new since 1.38.27) --- leads to minor code size savings as well
On the other hand, if you need shorter compile times and don't care much about
code size (for example when testing on the CIs), consider *removing* some of
these flags --- especially removing `-O3` and `--llvm-lto` can result in
significant compile speed gains.
@subsection platforms-html5-code-size-server Server-side compression
Servers are usually configured to apply GZip compression to plain text files
(CSS, JS and HTML), but the WebAssembly binary is nicely compressible as well.
With Apache, for example, adding this line to `.htaccess` will make the `*.wasm`
files compressed on-the-fly as well:
AddOutputFilter DEFLATE wasm
@todoc use .htaccess extension for the code here once Pygments know it
Apart from GZip, some modern browsers support Brotli compression, which leads
to much bigger size savings. Details in this
[Qt blog post](https://blog.qt.io/blog/2018/11/19/getting-started-qt-webassembly/).
@section platforms-html5-troubleshooting Troubleshooting
@subsection platforms-html5-troubleshooting-macos Setting up Emscripten on macOS
Emscripten is available through Homebrew:
@code{.sh}
brew install emscripten
@endcode
Because LLVM is also distributed as part of Xcode, it will get picked up by
default. Emscripten however needs to use its own bundled version, so you may
need to export the @cb{.sh} $LLVM @ce environment variable to port to the other
location:
@code{.sh}
export LLVM=/usr/local/opt/emscripten/libexec/llvm/bin
@endcode
@subsection platforms-html5-troubleshooting-windows-compiler CMake insists on using Visual C++ as a compiler
On Windows, running CMake as-is without specifying a generator via `-G` will
always use the Visual Studio compiler and seemingly ignore the Emscripten
toolchain file altogether. With recent toolchain files, you may get this
message instead:
@code{.shell-session}
CMake Error at toolchains/generic/Emscripten-wasm.cmake:19 (message):
Visual Studio project generator doesn't support cross-compiling to
Emscripten. Please use -G Ninja or other generators instead.
@endcode
This is because Visual Studio Project Files as the default generator on Windows
is not able to build for any other system than Windows itself. To fix it, use a
different generator --- for example [Ninja](https://ninja-build.org/). Download
the binary, put it somewhere on your `PATH` and pass `-G Ninja` to CMake.
Alternatively, pass the path to it using
`-DCMAKE_MAKE_PROGRAM=C:/path/to/ninja.exe`.
@m_class{m-block m-success}
@par Using Ninja that's bundled with Visual Studio
If you have Visual Studio with CMake support installed, instead of
downloading Ninja yourself, you can make use of the version that's bundled
with it. In version 2017 it's in the following location, adjust the path
for other versions:
@par
@parblock
<!-- gotta love doxygen, making easy things hard and complex things such as
applying a class to a nested block element next-to-impossible -->
@m_class{m-console-wrap}
@code{.bat}
cmake -G Ninja -DCMAKE_MAKE_PROGRAM=C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\Ninja\ninja.exe ...
@endcode
@endparblock
@subsection platforms-html5-troubleshooting-bootstrap First Emscripten run takes long or fails
Emscripten downloads and builds a lot of things on first startup or after
upgrade. That's expected and might take quite some time. If you are calling
Emscripten through the CMake toolchain, it might be attempting to bootstrap
itself multiple times, taking extreme amounts of time, or even fail during the
initial CMake compiler checks for various reasons such as
@code{.shell-session}
File "/usr/lib/python2.7/subprocess.py", line 1025, in _execute_child
raise child_exception
OSError: [Errno 13] Permission denied
@endcode
The CMake toolchain might interfere with the bootstrap operation, causing it to
fail. Solution is to wipe all Emscripten caches and trigger a rebuild of all
needed libraries by compiling a minimal project, as shown in the shell snippet
below --- enter it into the console prior to building anything else. It will
take a while to download and build various system libraries and random tools.
The @cb{.sh} -s WASM=1 @ce flag is needed in order to enable a rebuild of the
`binaryen` tool as well:
@code{.sh}
cd /tmp
emcc --clear-cache
emcc --clear-ports
echo "int main() {}" > main.cpp
em++ -s WASM=1 main.cpp
@endcode
After this step it might be also needed to purge all CMake build directories
and set them up again to ensure no bad state got cached.
@subsection platforms-html5-troubleshooting-corrade-not-found CMake can't find CORRADE_INCLUDE_DIR, _CORRADE_CONFIGURE_FILE or _CORRADE_MODULE_DIR
If you set `CMAKE_INSTALL_PREFIX` to a path contained in `EMSCRIPTEN_TOOLCHAIN_PATH` (which is specified in the toolchain file) and
dependencies are installed there as well, things should "just work". In other
cases you might end up with variants of the following error:
@code{.shell-session}
Could NOT find Corrade (missing: _CORRADE_MODULE_DIR)
@endcode
The solution is to explicitly pass `CMAKE_PREFIX_PATH` pointing to directory
where Corrade is installed. In some cases it might also be needed to point
`CMAKE_FIND_ROOT_PATH` to the same location. For example:
@code{.sh}
mkdir build-emscripten && cd build-emscripte
cmake .. \
-DCMAKE_TOOLCHAIN_FILE=../toolchains/generic/Emscripten-wasm.cmake \
-DCMAKE_PREFIX_PATH=/your/emscripten/libs/ \
-DCMAKE_FIND_ROOT_PATH=/your/emscripten/libs/ \
...
@endcode
@subsection platforms-html5-troubleshooting-loading-failed Application fails to load
Depending on what's the exact error printed in the browser console, the
following scenarios are possible:
- By default, the size of Emscripten heap is restricted to 16 MB. That might
not be enough if you have large compiled-in resources or allocate large
amount of memory. This can be solved with either of these:
- Adding `-s TOTAL_MEMORY=<bytes>` to compiler/linker flags, where
<bytes> is the new heap size
- Adding `-s ALLOW_MEMORY_GROWTH=1` to compiler/linker flags. This is
useful in case you don't know how much memory you need in advance and
[might disable some optimizations](https://kripken.github.io/emscripten-site/docs/optimizing/Optimizing-Code.html#memory-growth).
- Setting @cb{.js} Module { TOTAL_MEMORY: <bytes>; } @ce in the
JavaScript driver file
- Sometimes Chromium-based browsers refuse to create WebGL context on a
particular page, while on other sites it works and the same page works in
other browsers such as Firefox. This can be caused by Chromium running for
too long, restart it and try again.
- If you compile your application with a different set of compiler / linker
flags or a different Emscripten version than your dependencies, it can fail
to load for a variety of random reasons. Try to rebuild everything with the
same set of flags.
@subsection platforms-html5-troubleshooting-mime Incorrect response MIME type
Depending on your browser, you might see a warning similar to the following in
the console:
@code{.shell-session}
wasm streaming compile failed: TypeError: Failed to execute 'compile' on
'WebAssembly': Incorrect response MIME type. Expected 'application/wasm'.
falling back to ArrayBuffer instantiation
@endcode
This is not a critical error, but causes slow startup since the browser usually
attempts to load the file twice. This is because the HTTP `Content-Type` header
is not set properly by the webserver. In case you use Apache, fixing this is a
matter of adding the following line to your `.htaccess`:
AddType application/wasm .wasm
@todoc use .htaccess extension for the code here once Pygments know it
It is not possible when using Python `http.server` from the command line, but
the mapping [can be added programmatically](https://docs.python.org/3/library/http.server.html#http.server.SimpleHTTPRequestHandler):
@code{.py}
http.server.SimpleHTTPRequestHandler.extensions_map['.wasm'] = 'application/wasm'
@endcode
@subsection platforms-html5-troubleshooting-emasm Warnings about $ characters when using EM_ASM
The [EM_ASM()](https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#interacting-with-code-call-javascript-from-native)
family of macros is using `$0`, `$1`, ... to refer to macro arguments from
JavaScript code. Using them in C++ sources triggers a warning from Clang:
@code{.shell-session}
warning: '$' in identifier
@endcode
Solution is to disable the warning for the offending lines, below for example
when modifying a title element contents through DOM. No @cpp #ifdef __clang__ @ce
is necessary, as the code in question is only ever compiled by Emscripten's
Clang anyway:
@snippet platforms-html5.cpp emasm-dollar
*/
}