Friday 21 March 2014

Using emscripten to port NGL to the Web

Introduction

I have been using the ngl:: library for many years as part of teaching various graphic programming courses. I decided recently it would be interesting to port the core library and many of the demos to work interactively on the web using WebGL so started investigating a number of ways to do this. The main library is written in C++ and uses either Qt or SDL to create the OpenGL context. 
I had a number of choices as to which approach to take, I could learn Java Script and  three.js however this would mean porting all my codebase to Java Script which seemed like too much work.
In the end I came across the emscripten system which is a LLVM-to-JavaScript Compiler which can convert my C++ code into LLVM and then into JavaScript and then into asm.js the process was quite a steep learning curve, however I manage to get quite a lot of demos ported very quickly which can be seen here the rest of the blog will outline the process and how the webngl system was developed.

Installing Emscripten

My main development environment is a mac, however I have also tested the files under linux and it also works well. To get started I followed the tutorial here and all worked first time. The next stage was to try a simple WebGL demo that is provided with the examples. There are several WebGL demos using different libraries for OpenGL context creation however as I'm most familiar with SDL I chose to use this as the basis of the framework.

A Simple SDL demo program

The following code is a simple SDL program (very similar to a normal SDL program) the only difference is the call to emscripten_set_main_loop.

#include "SDL.h"
#include <GLES2/gl2.h>
#define GL_GLEXT_PROTOTYPES 1
#include <GLES2/gl2ext.h>
#include <emscripten.h>
#include <iostream>
#include <cstdlib>


void process()
{
  // as we don't have a timer we need to do something here
  // using a static to update at an interval
  static int t=0;
  if(++t > 100)
  {
    float r=(double)rand() / ((double)RAND_MAX + 1);
    float g=(double)rand() / ((double)RAND_MAX + 1);
    float b=(double)rand() / ((double)RAND_MAX + 1);
  
    glClearColor(r,g,b,1);
    t=0;
  }
  glClear(GL_COLOR_BUFFER_BIT);
  // this is where we draw
  SDL_GL_SwapBuffers();
}

int main(int argc, char *argv[])
{

 SDL_Surface *screen;

 // Init SDL
 if ( SDL_Init(SDL_INIT_VIDEO) != 0 ) 
 {
  std::cerr<<"Unable to initialize SDL: "<<SDL_GetError();
  return EXIT_FAILURE;
 }

 SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );

 screen = SDL_SetVideoMode( 720, 576, 16, SDL_OPENGL  | SDL_RESIZABLE); 
 if ( !screen ) 
 {
  std::cerr<<"Unable to set video mode: "<<SDL_GetError();
  return EXIT_FAILURE;
 }

 glEnable(GL_DEPTH_TEST);

 // let emscripten process something then 
 // give control back to the browser
 emscripten_set_main_loop (process, 0, true);

 SDL_Quit();
 return EXIT_SUCCESS;
}

The emscripten_set_main_loop function is explained very well here basically we create a function that is called asynchronously to allow the browser to regain control after every iteration of the function. It is important that this function does exit else the browser will hang up and I have had several times when I get a complete lockup of the system.

Compiling the program

To compile the program we use the following command line (in this case I'm using c++ )
em++ -s FULL_ES2=1 SDL1.cpp -o SDL1.html
The flag -s FULL_ES2 tells emscripten to use full OpenGL ES 2 specification when compiling the code, the -o SDL1.html will generate an html file as well as the javascript file for the canvas to use. The html file can now be opened in the browser an in this case you will see a screen that changes colour every 100 cycles of the main loop. In the next post I will begin to discuss how I ported the rest of ngl to use emscripten.