From fda77b8965ee0357d25b5edfd3221ee96f4c1435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 22 Aug 2017 16:28:32 +0200 Subject: [PATCH] Platform: port WindowlessEglApplication to Emscripten. Makes it finally possible to have magnum-info and GL tests. --- CMakeLists.txt | 9 +- doc/changelog.dox | 2 + package/archlinux/PKGBUILD-emscripten | 1 + package/archlinux/PKGBUILD-emscripten-noopt | 1 + .../PKGBUILD-emscripten-noopt-webgl2 | 1 + package/archlinux/PKGBUILD-emscripten-wasm | 1 + .../archlinux/PKGBUILD-emscripten-wasm-webgl2 | 1 + package/archlinux/PKGBUILD-emscripten-webgl2 | 1 + package/ci/travis-emscripten.sh | 1 + src/Magnum/Platform/CMakeLists.txt | 19 ++-- src/Magnum/Platform/WebApplication.css | 14 +++ .../Platform/WindowlessEglApplication.cpp | 22 +++- .../Platform/WindowlessEglApplication.h | 100 ++++++++++++++++-- .../WindowlessEmscriptenApplication.js | 46 ++++++++ 14 files changed, 195 insertions(+), 24 deletions(-) create mode 100644 src/Magnum/Platform/WindowlessEmscriptenApplication.js diff --git a/CMakeLists.txt b/CMakeLists.txt index 00b74a323..adaa35c29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,12 +99,9 @@ cmake_dependent_option(WITH_TEXTURETOOLS "Build TextureTools library" ON "NOT WI # Magnum AL Info cmake_dependent_option(WITH_AL_INFO "Build magnum-al-info utility" OFF "WITH_AUDIO" OFF) -# EGL context and windowless EGL application, available everywhere except on -# platforms which don't support extension loading -if(NOT CORRADE_TARGET_EMSCRIPTEN) - cmake_dependent_option(WITH_WINDOWLESSEGLAPPLICATION "Build WindowlessEglApplication library" OFF "NOT TARGET_GLES OR TARGET_DESKTOP_GLES OR NOT WITH_MAGNUMINFO" ON) - option(WITH_EGLCONTEXT "Build EglContext library" OFF) -endif() +# EGL context and windowless EGL application, available everywhere +cmake_dependent_option(WITH_WINDOWLESSEGLAPPLICATION "Build WindowlessEglApplication library" OFF "NOT TARGET_GLES OR TARGET_DESKTOP_GLES OR NOT WITH_MAGNUMINFO" ON) +option(WITH_EGLCONTEXT "Build EglContext library" OFF) # Android-specific application libraries if(CORRADE_TARGET_ANDROID) diff --git a/doc/changelog.dox b/doc/changelog.dox index 872dd95d7..5d7ee1b60 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -221,6 +221,8 @@ namespace Magnum { @ref Platform::GlfwApplication - Added @ref Platform::WindowlessWindowsEglApplication and @ref Platform::WindowlessIosApplication for ANGLE and iOS +- New @ref Platform::WindowlessEglApplication that works on headless NVidia, + Mesa drivers and Emscripten - New @ref Platform::WindowlessGlxContext "Platform::Windowless*Context" classes that manage OpenGL context to make threaded context creation possible diff --git a/package/archlinux/PKGBUILD-emscripten b/package/archlinux/PKGBUILD-emscripten index 88aba3e6f..6b4f1649f 100644 --- a/package/archlinux/PKGBUILD-emscripten +++ b/package/archlinux/PKGBUILD-emscripten @@ -30,6 +30,7 @@ build() { -DCMAKE_INSTALL_PREFIX=/usr/lib/emscripten/system \ -DWITH_AUDIO=ON \ -DWITH_SDL2APPLICATION=ON \ + -WDITH_WINDOWLESSEGLAPPLICATION=ON \ -DWITH_MAGNUMFONT=ON \ -DWITH_OBJIMPORTER=ON \ -DWITH_TGAIMAGECONVERTER=ON \ diff --git a/package/archlinux/PKGBUILD-emscripten-noopt b/package/archlinux/PKGBUILD-emscripten-noopt index d5cf528a2..9754db38f 100644 --- a/package/archlinux/PKGBUILD-emscripten-noopt +++ b/package/archlinux/PKGBUILD-emscripten-noopt @@ -32,6 +32,7 @@ build() { -DCMAKE_EXE_LINKER_FLAGS_RELEASE="-O1" \ -DWITH_AUDIO=ON \ -DWITH_SDL2APPLICATION=ON \ + -WDITH_WINDOWLESSEGLAPPLICATION=ON \ -DWITH_MAGNUMFONT=ON \ -DWITH_OBJIMPORTER=ON \ -DWITH_TGAIMAGECONVERTER=ON \ diff --git a/package/archlinux/PKGBUILD-emscripten-noopt-webgl2 b/package/archlinux/PKGBUILD-emscripten-noopt-webgl2 index c9ac9a4ed..6aa93c87f 100644 --- a/package/archlinux/PKGBUILD-emscripten-noopt-webgl2 +++ b/package/archlinux/PKGBUILD-emscripten-noopt-webgl2 @@ -33,6 +33,7 @@ build() { -DTARGET_GLES2=OFF \ -DWITH_AUDIO=ON \ -DWITH_SDL2APPLICATION=ON \ + -WDITH_WINDOWLESSEGLAPPLICATION=ON \ -DWITH_MAGNUMFONT=ON \ -DWITH_OBJIMPORTER=ON \ -DWITH_TGAIMAGECONVERTER=ON \ diff --git a/package/archlinux/PKGBUILD-emscripten-wasm b/package/archlinux/PKGBUILD-emscripten-wasm index a14ce7983..a8b26f628 100644 --- a/package/archlinux/PKGBUILD-emscripten-wasm +++ b/package/archlinux/PKGBUILD-emscripten-wasm @@ -30,6 +30,7 @@ build() { -DCMAKE_INSTALL_PREFIX=/usr/lib/emscripten/system \ -DWITH_AUDIO=ON \ -DWITH_SDL2APPLICATION=ON \ + -WDITH_WINDOWLESSEGLAPPLICATION=ON \ -DWITH_MAGNUMFONT=ON \ -DWITH_OBJIMPORTER=ON \ -DWITH_TGAIMAGECONVERTER=ON \ diff --git a/package/archlinux/PKGBUILD-emscripten-wasm-webgl2 b/package/archlinux/PKGBUILD-emscripten-wasm-webgl2 index 30b321075..c92d21dbd 100644 --- a/package/archlinux/PKGBUILD-emscripten-wasm-webgl2 +++ b/package/archlinux/PKGBUILD-emscripten-wasm-webgl2 @@ -31,6 +31,7 @@ build() { -DTARGET_GLES2=OFF \ -DWITH_AUDIO=ON \ -DWITH_SDL2APPLICATION=ON \ + -WDITH_WINDOWLESSEGLAPPLICATION=ON \ -DWITH_MAGNUMFONT=ON \ -DWITH_OBJIMPORTER=ON \ -DWITH_TGAIMAGECONVERTER=ON \ diff --git a/package/archlinux/PKGBUILD-emscripten-webgl2 b/package/archlinux/PKGBUILD-emscripten-webgl2 index 70662d8c6..4b3d805f0 100644 --- a/package/archlinux/PKGBUILD-emscripten-webgl2 +++ b/package/archlinux/PKGBUILD-emscripten-webgl2 @@ -31,6 +31,7 @@ build() { -DTARGET_GLES2=OFF \ -DWITH_AUDIO=ON \ -DWITH_SDL2APPLICATION=ON \ + -WDITH_WINDOWLESSEGLAPPLICATION=ON \ -DWITH_MAGNUMFONT=ON \ -DWITH_OBJIMPORTER=ON \ -DWITH_TGAIMAGECONVERTER=ON \ diff --git a/package/ci/travis-emscripten.sh b/package/ci/travis-emscripten.sh index 846341499..4b581e7b2 100755 --- a/package/ci/travis-emscripten.sh +++ b/package/ci/travis-emscripten.sh @@ -48,6 +48,7 @@ cmake .. \ -DCMAKE_FIND_ROOT_PATH=$HOME/deps \ -DWITH_AUDIO=ON \ -DWITH_SDL2APPLICATION=ON \ + -WDITH_WINDOWLESSEGLAPPLICATION=ON \ -DWITH_MAGNUMFONT=ON \ -DWITH_MAGNUMFONTCONVERTER=ON \ -DWITH_OBJIMPORTER=ON \ diff --git a/src/Magnum/Platform/CMakeLists.txt b/src/Magnum/Platform/CMakeLists.txt index ea655264d..d01710b2a 100644 --- a/src/Magnum/Platform/CMakeLists.txt +++ b/src/Magnum/Platform/CMakeLists.txt @@ -201,12 +201,19 @@ if(WITH_SDL2APPLICATION) endif() # JavaScript and CSS stuff for Emscripten -if(WITH_SDL2APPLICATION AND CORRADE_TARGET_EMSCRIPTEN) - set(MagnumSdl2Application_FILES - EmscriptenApplication.js - WebApplication.css) - list(APPEND MagnumPlatform_FILES ${MagnumSdl2Application_FILES}) - install(FILES ${MagnumSdl2Application_FILES} DESTINATION ${MAGNUM_DATA_INSTALL_DIR}) +if(CORRADE_TARGET_EMSCRIPTEN) + set(MagnumEmscriptenApplication_FILES ) + if(WITH_SDL2APPLICATION) + list(APPEND MagnumEmscriptenApplication_FILES EmscriptenApplication.js) + endif() + if(WITH_WINDOWLESSEGLAPPLICATION) + list(APPEND MagnumEmscriptenApplication_FILES WindowlessEmscriptenApplication.js) + endif() + if(WITH_SDL2APPLICATION OR WITH_WINDOWLESSEGLAPPLICATION) + list(APPEND MagnumEmscriptenApplication_FILES WebApplication.css) + list(APPEND MagnumPlatform_FILES ${MagnumEmscriptenApplication_FILES}) + install(FILES ${MagnumEmscriptenApplication_FILES} DESTINATION ${MAGNUM_DATA_INSTALL_DIR}) + endif() endif() # GLX application diff --git a/src/Magnum/Platform/WebApplication.css b/src/Magnum/Platform/WebApplication.css index a2abf2c85..7cd10e08a 100644 --- a/src/Magnum/Platform/WebApplication.css +++ b/src/Magnum/Platform/WebApplication.css @@ -49,3 +49,17 @@ h1 { font-size: 15px; z-index: 9; } + +#module.hidden { + display: none; +} + +#log { + width: 640px; + height: 480px; + overflow-y: scroll; + overflow-x: hidden; + white-space: pre-wrap; + z-index: 10; + margin: 0; +} diff --git a/src/Magnum/Platform/WindowlessEglApplication.cpp b/src/Magnum/Platform/WindowlessEglApplication.cpp index ca63f7a5e..9b0b83d57 100644 --- a/src/Magnum/Platform/WindowlessEglApplication.cpp +++ b/src/Magnum/Platform/WindowlessEglApplication.cpp @@ -60,10 +60,13 @@ WindowlessEglContext::WindowlessEglContext(const Configuration& configuration, C EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, #ifndef MAGNUM_TARGET_GLES EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, + #elif defined(MAGNUM_TARGET_GLES2) || defined(CORRADE_TARGET_EMSCRIPTEN) + /* Emscripten doesn't know about EGL_OPENGL_ES3_BIT_KHR for WebGL 2 and + the whole thing is controlled by -s USE_WEBGL2=1 flag anyway, so it + doesn't matter that we ask for ES2 on WebGL 2 as well. */ + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, #elif defined(MAGNUM_TARGET_GLES3) EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR, - #elif defined(MAGNUM_TARGET_GLES2) - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, #else #error unsupported OpenGL edition #endif @@ -84,18 +87,27 @@ WindowlessEglContext::WindowlessEglContext(const Configuration& configuration, C const EGLint attributes[] = { #ifdef MAGNUM_TARGET_GLES EGL_CONTEXT_CLIENT_VERSION, - #ifdef MAGNUM_TARGET_GLES3 - 3, - #elif defined(MAGNUM_TARGET_GLES2) + #if defined(MAGNUM_TARGET_GLES2) || defined(CORRADE_TARGET_EMSCRIPTEN) + /* Emscripten doesn't know about version 3 for WebGL 2 and the + whole thing is controlled by -s USE_WEBGL2=1 flag anyway, so it + doesn't matter that we ask for ES2 on WebGL 2 as well. */ 2, + #elif defined(MAGNUM_TARGET_GLES3) + 3, #else #error unsupported OpenGL ES version #endif #endif + #ifndef MAGNUM_TARGET_WEBGL EGL_CONTEXT_FLAGS_KHR, EGLint(configuration.flags()), + #endif EGL_NONE }; + #ifdef MAGNUM_TARGET_WEBGL + static_cast(configuration); + #endif + if(!(_context = eglCreateContext(_display, config, EGL_NO_CONTEXT, attributes))) { Error() << "Platform::WindowlessEglApplication::tryCreateContext(): cannot create EGL context:" << Implementation::eglErrorString(eglGetError()); return; diff --git a/src/Magnum/Platform/WindowlessEglApplication.h b/src/Magnum/Platform/WindowlessEglApplication.h index d6e73af07..f3a661ee0 100644 --- a/src/Magnum/Platform/WindowlessEglApplication.h +++ b/src/Magnum/Platform/WindowlessEglApplication.h @@ -126,10 +126,12 @@ class WindowlessEglContext { */ class WindowlessEglContext::Configuration { public: + #ifndef MAGNUM_TARGET_WEBGL /** * @brief Context flag * * @see @ref Flags, @ref setFlags(), @ref Context::Flag + * @requires_gles Context flags are not available in WebGL. */ enum class Flag: int { Debug = EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR /**< Create debug context */ @@ -139,16 +141,23 @@ class WindowlessEglContext::Configuration { * @brief Context flags * * @see @ref setFlags(), @ref Context::Flags + * @requires_gles Context flags are not available in WebGL. */ #ifndef DOXYGEN_GENERATING_OUTPUT typedef Containers::EnumSet Flags; #else typedef Containers::EnumSet Flags; #endif + #endif constexpr /*implicit*/ Configuration() {} - /** @brief Context flags */ + #ifndef MAGNUM_TARGET_WEBGL + /** + * @brief Context flags + * + * @requires_gles Context flags are not available in WebGL. + */ Flags flags() const { return _flags; } /** @@ -156,25 +165,28 @@ class WindowlessEglContext::Configuration { * @return Reference to self (for method chaining) * * Default is no flag. See also @ref Context::flags(). + * @requires_gles Context flags are not available in WebGL. */ Configuration& setFlags(Flags flags) { _flags = flags; return *this; } + #endif private: + #ifndef MAGNUM_TARGET_WEBGL Flags _flags; + #endif }; /** @brief Windowless EGL application -Application for offscreen rendering using @ref WindowlessEglContext. Supported -mainly on OpenGL ES drivers, for desktop OpenGL the only driver that supports -this configuration is NVidia >= 355. See other `Windowless*Application` classes -for an alternative. - -It is built if `WITH_WINDOWLESSEGLAPPLICATION` is enabled in CMake. +Application for offscreen rendering using @ref WindowlessEglContext. This +application library is in theory available for all platforms for which EGL +works (Linux desktop or ES, iOS and also @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten"). +See other `Windowless*Application` classes for an alternative. It is built if +`WITH_WINDOWLESSEGLAPPLICATION` is enabled in CMake. ## Bootstrap application @@ -193,6 +205,39 @@ application with these four commands: See @ref cmake for more information. +## Bootstrap application for Emscripten + +Fully contained windowless application together with Emscripten support along +with full HTML markup and CMake setup is available in `windowless-emscripten` +branch of [Magnum Bootstrap](https://github.com/mosra/magnum-bootstrap) +repository, download it as [tar.gz](https://github.com/mosra/magnum-bootstrap/archive/windowless-emscripten.tar.gz) +or [zip](https://github.com/mosra/magnum-bootstrap/archive/windowless-emscripten.zip) +file. After extracting the downloaded archive, you can do the desktop build in +the same way as above. For the Emscripten build you also need to put the +contents of toolchains repository from https://github.com/mosra/toolchains +in `toolchains/` subdirectory. 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. 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`. + +Then create build directory and run `cmake` and build/install commands in it. +Set `CMAKE_PREFIX_PATH` to where you have all the dependencies installed, set +`CMAKE_INSTALL_PREFIX` to have the files installed in proper location (a +webserver, e.g. `/srv/http/emscripten`). + + mkdir build-emscripten && cd build-emscripten + cmake .. \ + -DCMAKE_TOOLCHAIN_FILE="../toolchains/generic/Emscripten.cmake" \ + -DCMAKE_PREFIX_PATH=/usr/lib/emscripten/system \ + -DCMAKE_INSTALL_PREFIX=/srv/http/emscripten + cmake --build . + cmake --build . --target install + +You can then open `MyApplication.html` in your browser (through webserver, e.g. +`http://localhost/emscripten/MyApplication.html`). + ## General usage In CMake you need to request `WindowlessEglApplication` component and link to @@ -213,6 +258,47 @@ MAGNUM_WINDOWLESSEGLAPPLICATION_MAIN(MyApplication) If no other application header is included, this class is also aliased to `Platform::WindowlessApplication` and the macro is aliased to `MAGNUM_WINDOWLESSAPPLICATION_MAIN()` to simplify porting. + +## Usage with Emscripten + +If you are targetting Emscripten, you need to provide HTML markup for your +application. Template one is below or in the bootstrap application, you can +modify it to your liking. The markup references two files, +`WindowlessEmscriptenApplication.js` and `WebApplication.css`, both are in +`Platform/` directory in the source tree and are also installed into +`share/magnum/` inside your Emscripten toolchain. Change `<application>` +to name of your executable. +@code + + + + Magnum Windowless Emscripten Application + + + + +

Magnum Windowless Emscripten Application

+
+ +

+      
Initialization...
+
+ + +
+ + +@endcode + +You can modify all the files to your liking, but the HTML file must contain at +least the `<canvas>` enclosed in listener `<div>`. The JavaScript +file contains event listeners which print loading status on the page. The +status displayed in the remaining two `<div>`s, if they are available. +The CSS file contains rudimentary style to avoid eye bleeding. + +The application prints all output (thus also @ref Corrade::Utility::Debug "Debug", +@ref Corrade::Utility::Warning "Warning" and @ref Corrade::Utility::Error "Error") +to the `<pre>` on the page. */ class WindowlessEglApplication { public: diff --git a/src/Magnum/Platform/WindowlessEmscriptenApplication.js b/src/Magnum/Platform/WindowlessEmscriptenApplication.js new file mode 100644 index 000000000..adbd56a1f --- /dev/null +++ b/src/Magnum/Platform/WindowlessEmscriptenApplication.js @@ -0,0 +1,46 @@ +var Module = { + preRun: [], + postRun: [], + + printErr: function(message) { + var log = document.getElementById('log'); + log.innerHTML += Array.prototype.slice.call(arguments).join(' ') + '\n'; + }, + + print: function(message) { + var log = document.getElementById('log'); + log.innerHTML += Array.prototype.slice.call(arguments).join(' ') + '\n'; + }, + + canvas: document.getElementById('module'), + + setStatus: function(message) { + var status = document.getElementById('status'); + if(status) status.innerHTML = message; + }, + + setStatusDescription: function(message) { + var statusDescription = document.getElementById('statusDescription'); + if(statusDescription) statusDescription.innerHTML = message; + }, + + totalDependencies: 0, + + monitorRunDependencies: function(left) { + this.totalDependencies = Math.max(this.totalDependencies, left); + + if(left) { + Module.setStatus('Downloading...'); + Module.setStatusDescription((this.totalDependencies - left) + '/' + this.totalDependencies); + } else { + Module.setStatus(''); + Module.setStatusDescription(''); + } + } +}; + +Module.setStatus('Downloading...'); + +Module.canvas.addEventListener('contextmenu', function(event) { + event.preventDefault(); +}, true);