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.
 
 
 
 
 

402 lines
16 KiB

/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018
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
@todoc speeding up compilation
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="../toolchains/generic/Emscripten-wasm.cmake" \
-DCMAKE_BUILD_TYPE=Release
cmake --build .
@endcode
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 graphics 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 an Emscripten application wrapper in
@ref Platform::Sdl2Application. See its documentation for more information
about general usage. You can also use the Emscripten APIs directly or any other
way.
@note The @ref Platform::Sdl2Application also contains a fully configured
bootstrap project that's 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 and are also put into `share/magnum/` inside your
install prefix.
@code{.html-jinja}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Magnum Emscripten Application</title>
<link rel="stylesheet" href="WebApplication.css" />
</head>
<body>
<h1>Magnum Emscripten Application</h1>
<div id="listener">
<canvas id="module"></canvas>
<div id="status">Initialization...</div>
<div id="statusDescription"></div>
<script src="EmscriptenApplication.js"></script>
<script async="async" src="{{ application }}.js"></script>
</div>
</body>
</html>
@endcode
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. 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 contains
a rudimentary style.
In order to deploy the app, you need to install 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 modules 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
EmscriptenApplication.js
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 The @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 in the [src/Magnum/Platform/](https://github.com/mosra/magnum/tree/master/src/Magnum/Platform)
directory in the source tree and are also put into `share/magnum/` inside your
install prefix.
@code{.html-jinja}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Magnum Windowless Emscripten Application</title>
<link rel="stylesheet" href="WebApplication.css" />
</head>
<body>
<h1>Magnum Windowless Emscripten Application</h1>
<div id="listener">
<canvas id="module" class="hidden"></canvas>
<pre id="log"></pre>
<div id="status">Initialization...</div>
<div id="statusDescription"></div>
<script src="WindowlessEmscriptenApplication.js"></script>
<script async="async" src="{{ application }}.js"></script>
</div>
</body>
</html>
@endcode
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 contains a rudimentary style.
Deployment is similar to @ref platforms-html5-apps "graphics 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
WindowlessEmscriptenApplication.js
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-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 graphics 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)
@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-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_MODULE_DIR
If initial CMake configuration fails with
@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/ \
-G Ninja
@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 either with:
- Adding `-s TOTAL_MEMORY=&lt;bytes&gt;` to compiler/linker flags, where
&lt;bytes&gt; 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 `Module { TOTAL_MEMORY: &lt;bytes&gt;; }` 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.
*/
}