Yep, it’s WebGL and Native together. This is a short tutorial on using Emscripten to integrate a C++/OpenGL code with JS environment. There are some other tutorials on the net you might want to have a look at if you want to simply convert an existing native app and not bother with web-specific input events or render loops (gears,simplegl). If you have some native code you want to use within a bigger webapp, e.g., write a JS API around your existing library, read on. Also there are some conclusions regarding performance and maturity of Emscripten+WebGL solutions closer to the end of this page.
Live demo for results (we suggest you use Chrome or Opera).
Github project for sources.
- Install the Emscripten toolset
- Build the project using
index.htmlin your chrowser.
In this tutorial we give an introduction to how Emscripten-generated code could be integrated with the rest of your web-app, and there are some sources and build files that might be helpful for a quick start. It’s a simplified outcome of what we actually did as a (successful) experiment over one of our commercial products.
First of all, get Emscripten installed: https://github.com/kripken/emscripten/wiki/Emscripten-SDK
emcc is visible in your environment, typing
emcc in console/cmd should return something like this:
which means emcc is installed and wants to do some job. You must also have
node installed. I strongly recommend going through the Emscripten tutorial to make sure the whole toolchain is installed and working fine.
Getting the code
Download the project files manually from here or type
There are three files containing native C code, but let’s pretend there are hundreds of classes and millions of loc :-) If you don’t want to download anything and just skim through, I give here some bits of code and a general picture of what’s happening.
shaders couple of files have functions for compiling shaders and linking GL programs:
main.cpp uses those to initialise the actual shaders. There are two functions in
initGL initialises an SDL screen which in turn initialises the GL context, and
drawTriangle (surprise!) draws a triangle. Note that there is no
main entry point and in order to initialise the context and draw something, we actually need to call the two functions from somewhere. And that “somewhere” will be our JS code. The code is quite self-explanatory for those that have ever used OpenGL before.
a bit of main.cpp
Let us compile this code. Go to the directory with the files and type
It should generate a file named
glcore.js that contains our “compiled” library. You can have a look what has been generated but it’s not really meant to be edited or read. We are able to debug this though but a bit later on that.
Now that we have our C++ library ported to JS, we can call its functions from our JS code.
node installed, getting TypeScript is as simple as
Here are some bits of TypeScript code doing most of the gluing work (note the file extensions):
Emscripten provides a
Module object sitting in the global scope and that’s where all JS reference to our native functions reside. In
triangle.ts, we define
Module and its members to make the type-checker happy and, therefore, make less type mistakes. The
Bindings class contains references to the native functions obtained through this
Module object. For example, in order to get a “pointer” to our native function
initGL, we call
The parameters here are: function name, return type (as a JS type), and a list of function argument types as an array of strings. This would return a function that you can call as you would do with a usual JS function:
Note that where a C function expects an argument of a pointer type, the converted JS function would expect an integer which in fact represents a pointer to the Emscripten heap (or stack, choose for yourself). And since JS arrays and other JS objects are not located on that heap, we need to allocate a bit of that memory, use it in native functions by passing a pointer (JS integer number) as an argument, and later on free it as you would normally do in C. That is exactly what we do in our
render() method to pass the current
translation object as a float array.
triangle.ts has a reference to another TypeScript file,
input.ts, the two will be merged together and translated into
Putting everything together
Ok now as we’ve got the ported native library and the JS code that supposedly represents our JS API, we can glue everything together. We already have references to native functions (in the
Bindings class), and our JS code calls those functions when needed. What’s left is to make sure both the “native”
triangle.js scripts are loaded into our page, also there should be a canvas on our page that can receive those GL calls, and Emscripten needs to have a reference to that canvas.
We set the canvas which we want to be receiving GL calls to the
Module.canvas property. This says Emscripten not to generate its own canvas and use ours instead.
Also you’d want to make sure you have proper style sheet in the
styles folder and have this mousewheel library in your main folder (if you cloned the whole tutorial project earlier, you should have all of this set up).
EXPORTED_FUNCTIONS parameter to the build command:
You can read more details about name mangling and interaction with the native code here
Now build the library and open
index.html. You should see a nicely coloured triangle which we can actually move around and zoom with a mouse.
Automating the build process
Since you would already have MinGW and/or MSYS installed (if using Windows), there’s a high chance you’d have
make tool available to you. This could be a nice and simple way for automating your emcc build command. Create a file called
makefile in the same folder next to other sources and put this inside:
Also, move the array you supplied to
EXPORTED_FUNCTIONS to a separate file:
Now you can call
from within that folder to build the library, and call
to remove the generated files. There are certainly more scalable approaches to automating the build process out there but this would do for a quick start. Remember: if you are using TypeScript, you also need to build the main
It might sound crazy to some, and indeed it did look outlandish to me, nevertheless, we can debug C++ code in a browser. Ok, not the native code itself but its JS version mapped back to C++ which is close enough in the context of porting code to JS. To enable this, add the
-g flag to your emcc compilation:
This would generate an additional
.map file sitting next to the output file (in our case it would be
glcore.js.map) which we need to make sure is available to the browser at runtime.
index.html, open the developer console (in Chrome press F12), go to the Sources tab, open up the list of source files and you should see and able to open our original
.cpp files. Breakpoints and stepping through should all work as expected. The only thing is the variable viewer would not be able to resolve things like Emscripten pointers which would naturally appear as numbers. Otherwise you should be able to debug just fine although I would imagine writing a simple wrapper and using a native debugger and a native IDE would certainly bring its advantages in debugging.
There are two levels of optimisation available with the Emscripten tool chain. The first one is LLVM compiler optimisation which is triggered by setting the
--llvm-opts level of
emcc command, and the second is emcc optimisation set by
* Emcc optimisation
-O3 can sometimes over-optimise things and throw out relevant code, so use with care.
-O2 --llvm-opts 2 seems to be the most reasonable in terms of performance and stability combination. Emcc optimises for performance and size in one shot, so a difference between
-O0 can be of a factor up to 3.
* Emcc optimisation flag
-O makes a noticeable difference in performance (up to a factor of 3 again) whereas
--llvm-opts not so much.
All in all, we had almost no problems porting our C++ code to JS using the Emscripten toolchain. We hope this tutorial was useful to those looking at porting native code to JS as a possible solution. There is indeed a bit of learning and tweaking to do but it’s quite a flat learning curve for those who has some C++ and JS coding experience. And it is definitely easier than re-writing your library in JS from scratch so it looks like a viable solution in certain cases.
The only issue that might arise in a commercial project is the final size of the generated JS code. It is somewhat larger than the native binaries, and JS version needs to be loaded by a browser, so some measures need to be taken to not put off your customer while he/she waits for this to finish. This might be only necessary to do once and hope the browser would cache it, but again, deal with care. If you need to load a 20MB script and the user has only got a 1Mb connection, it would take him/her more than 2m 40s at best.
The story of WebGL is not so bright though. Since it is a standard and not a particular implementation, browser vendors need to actually make efforts to support it and not all do. You can have a look to what extent different browsers support WebGL (e.g. http://caniuse.com/webgl), but in our experience, recent Chrome and Opera (which is Chrome’s clone feature-wise) are the only browsers capable of rendering whatever we render natively. Others either do not support WebGL at all or produce visual artefacts given the same sequence of GL commands that we feed to GLES 2.0 capable devices. The example given in this tutorial does produce artefacts in Firefox 27, and we had artefacts in IE 11 with some of our other examples when we used VBOs. So, unless you can “suggest” your customers to only use Chrome, WebGL is not there yet for commercial products (as of March 2014) but it as well may be there in a couple of years if Firefox, IE and Safari catch up.