/* This file is part of Magnum. Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Vladimír Vondruš 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} Magnum Emscripten Application

Magnum Emscripten Application

Initialization...
@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} @ce enclosed in listener @cb{.html}
@ce. The JavaScript file contains event listeners which print loading status on the page. The status displayed in the remaining two @cb{.html}
@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} Magnum Windowless Emscripten Application

Magnum Windowless Emscripten Application


      
Initialization...
@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} @ce enclosed in listener @cb{.html}
@ce and the @cb{.html}
 @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} 
@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}
 @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=<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 `Module { TOTAL_MEMORY: <bytes>; }` 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

*/

}