mirror of https://github.com/mosra/magnum.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
829 lines
38 KiB
829 lines
38 KiB
/* |
|
This file is part of Magnum. |
|
|
|
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, |
|
2020, 2021, 2022 Vladimír Vondruš <mosra@centrum.cz> |
|
Copyright © 2020 Pablo Escobar <mail@rvrs.in> |
|
|
|
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, ci 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 https://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 --- |
|
@cb{.sh} ${MAGNUM_WINDOWLESSEMSCRIPTENAPPLICATION_JS} @ce instead of |
|
@cb{.sh} ${MAGNUM_EMSCRIPTENAPPLICATION_JS} @ce: |
|
|
|
@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 overridden 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-texturedquad 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 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) |
|
include(UseEmscripten) |
|
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} |
|
<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 CORRADE_TARGET_CLANG @ce is necessary, as the code in question is |
|
only ever compiled by Emscripten's Clang anyway: |
|
|
|
@snippet platforms-html5.cpp emasm-dollar |
|
|
|
*/ |
|
|
|
}
|
|
|