email sol follow sol rss feed of the blog wishlist Sol::Tutorials

Emscripten tutorial

Emscripten Framework

As the virtual box has been set up, you can now store your code in the share and use a terminal in the virtual Linux to just invoke the Emscripten; for example:

  cd ~/emscripten/share/
  ../emscripten/emcc main.cpp -o test.html

If you have SDL-based applications, you can turn them into web pages using Emscripten. There's only one big gotcha (and several small ones). The big gotcha is the Emscripten-browser-environment. Long story short, your application can't "own" the main loop, and instead you have to specify your main loop as a function and tell Emscripten to call it.

Basically this means including a header and calling a function, assuming your application is well-structured. In worst case you may find yourself refactoring code, but in most cases the process should be pretty simple.

You can also compile the same code with your development environment and Emscripten by defining the Emscripten-specific stuff inside the predefined macro EMSCRIPTEN.

The include file required is:


#ifdef EMSCRIPTEN
#include <emscripten.h>
#endif

And an example of the modified main loop would be:


#ifdef EMSCRIPTEN
  emscripten_set_main_loop(mainloop, 30, 0);
#else
  while (1)
  {
    mainloop();
  }
#endif    

The parameters for the emscripten_set_main_loop are - a "void foo(void)" format function that will be called as the main loop; desired frame rate (0 for full blast, which is probably not a good idea if you don't want the browser to take 100% CPU time), and flag to request Emscripten to simulate infinite loop.

The way I understand the last flag is that Emscripten does something rather complicated to try to keep stuff in stack; it's probably better not to expect anything to live in the stack in your main() function and assume that the main function gets called from a "zero state", kinda like how window functions tend to get called.

When it comes to smaller gotchas, I've noticed a couple; first, the byte order may not be what you'd expect (when plotting 32bit pixels), and the surface needs to be locked/unlocked with SDL_LockSurface/SDL_UnlockSurface.

Without further ado, here's the full source to the "scroller" example I embedded on the first page of this tutorial; apart from the embedded font data, it's actually rather simple.


#ifdef EMSCRIPTEN
#include <emscripten.h>
#endif
#include <stdlib.h>
#include <math.h>
#include "SDL.h"

SDL_Surface *screen;

unsigned char TFX_AsciiFontdata[12*256] = {
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,// ' '
  0, 12, 30, 30, 30, 12, 12,  0, 12, 12,  0,  0,// '!'
  0,102,102,102, 36,  0,  0,  0,  0,  0,  0,  0,// '"'
  0, 54, 54,127, 54, 54, 54,127, 54, 54,  0,  0,// '#'
 12, 12, 62,  3,  3, 30, 48, 48, 31, 12, 12,  0,// '$'
  0,  0,  0, 35, 51, 24, 12,  6, 51, 49,  0,  0,// '%'
  0, 14, 27, 27, 14, 95,123, 51, 59,110,  0,  0,// '&'
  0, 12, 12, 12,  6,  0,  0,  0,  0,  0,  0,  0,// '''
  0, 48, 24, 12,  6,  6,  6, 12, 24, 48,  0,  0,// '('
  0,  6, 12, 24, 48, 48, 48, 24, 12,  6,  0,  0,// ')'
  0,  0,  0,102, 60,255, 60,102,  0,  0,  0,  0,// '*'
  0,  0,  0, 24, 24,126, 24, 24,  0,  0,  0,  0,// '+'
  0,  0,  0,  0,  0,  0,  0,  0, 28, 28,  6,  0,// ','
  0,  0,  0,  0,  0,127,  0,  0,  0,  0,  0,  0,// '-'
  0,  0,  0,  0,  0,  0,  0,  0, 28, 28,  0,  0,// '.'
  0,  0, 64, 96, 48, 24, 12,  6,  3,  1,  0,  0,// '/'
  0, 62, 99,115,123,107,111,103, 99, 62,  0,  0,// '0'
  0,  8, 12, 15, 12, 12, 12, 12, 12, 63,  0,  0,// '1'
  0, 30, 51, 51, 48, 24, 12,  6, 51, 63,  0,  0,// '2'
  0, 30, 51, 48, 48, 28, 48, 48, 51, 30,  0,  0,// '3'
  0, 48, 56, 60, 54, 51,127, 48, 48,120,  0,  0,// '4'
  0, 63,  3,  3,  3, 31, 48, 48, 51, 30,  0,  0,// '5'
  0, 28,  6,  3,  3, 31, 51, 51, 51, 30,  0,  0,// '6'
  0,127, 99, 99, 96, 48, 24, 12, 12, 12,  0,  0,// '7'
  0, 30, 51, 51, 55, 30, 59, 51, 51, 30,  0,  0,// '8'
  0, 30, 51, 51, 51, 62, 24, 24, 12, 14,  0,  0,// '9'
  0,  0,  0, 28, 28,  0,  0, 28, 28,  0,  0,  0,// ':'
  0,  0,  0, 28, 28,  0,  0, 28, 28, 24, 12,  0,// ';'
  0, 48, 24, 12,  6,  3,  6, 12, 24, 48,  0,  0,// '<'
  0,  0,  0,  0,126,  0,126,  0,  0,  0,  0,  0,// '='
  0,  6, 12, 24, 48, 96, 48, 24, 12,  6,  0,  0,// '>'
  0, 30, 51, 48, 24, 12, 12,  0, 12, 12,  0,  0,// '?'
  0, 62, 99, 99,123,123,123,  3,  3, 62,  0,  0,// '@'
  0, 12, 30, 51, 51, 51, 63, 51, 51, 51,  0,  0,// 'A'
  0, 63,102,102,102, 62,102,102,102, 63,  0,  0,// 'B'
  0, 60,102, 99,  3,  3,  3, 99,102, 60,  0,  0,// 'C'
  0, 31, 54,102,102,102,102,102, 54, 31,  0,  0,// 'D'
  0,127, 70,  6, 38, 62, 38,  6, 70,127,  0,  0,// 'E'
  0,127,102, 70, 38, 62, 38,  6,  6, 15,  0,  0,// 'F'
  0, 60,102, 99,  3,  3,115, 99,102,124,  0,  0,// 'G'
  0, 51, 51, 51, 51, 63, 51, 51, 51, 51,  0,  0,// 'H'
  0, 30, 12, 12, 12, 12, 12, 12, 12, 30,  0,  0,// 'I'
  0,120, 48, 48, 48, 48, 51, 51, 51, 30,  0,  0,// 'J'
  0,103,102, 54, 54, 30, 54, 54,102,103,  0,  0,// 'K'
  0, 15,  6,  6,  6,  6, 70,102,102,127,  0,  0,// 'L'
  0, 99,119,127,127,107, 99, 99, 99, 99,  0,  0,// 'M'
  0, 99, 99,103,111,127,123,115, 99, 99,  0,  0,// 'N'
  0, 28, 54, 99, 99, 99, 99, 99, 54, 28,  0,  0,// 'O'
  0, 63,102,102,102, 62,  6,  6,  6, 15,  0,  0,// 'P'
  0, 28, 54, 99, 99, 99,115,123, 62, 48,120,  0,// 'Q'
  0, 63,102,102,102, 62, 54,102,102,103,  0,  0,// 'R'
  0, 30, 51, 51,  3, 14, 24, 51, 51, 30,  0,  0,// 'S'
  0, 63, 45, 12, 12, 12, 12, 12, 12, 30,  0,  0,// 'T'
  0, 51, 51, 51, 51, 51, 51, 51, 51, 30,  0,  0,// 'U'
  0, 51, 51, 51, 51, 51, 51, 51, 30, 12,  0,  0,// 'V'
  0, 99, 99, 99, 99,107,107, 54, 54, 54,  0,  0,// 'W'
  0, 51, 51, 51, 30, 12, 30, 51, 51, 51,  0,  0,// 'X'
  0, 51, 51, 51, 51, 30, 12, 12, 12, 30,  0,  0,// 'Y'
  0,127,115, 25, 24, 12,  6, 70, 99,127,  0,  0,// 'Z'
  0, 60, 12, 12, 12, 12, 12, 12, 12, 60,  0,  0,// '['
  0,  0,  1,  3,  6, 12, 24, 48, 96, 64,  0,  0,// '\'
  0, 60, 48, 48, 48, 48, 48, 48, 48, 60,  0,  0,// ']'
  8, 28, 54, 99,  0,  0,  0,  0,  0,  0,  0,  0,// '^'
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,255,  0,// '_'
 12, 12, 24,  0,  0,  0,  0,  0,  0,  0,  0,  0,// '`'
  0,  0,  0,  0, 30, 48, 62, 51, 51,110,  0,  0,// 'a'
  0,  7,  6,  6, 62,102,102,102,102, 59,  0,  0,// 'b'
  0,  0,  0,  0, 30, 51,  3,  3, 51, 30,  0,  0,// 'c'
  0, 56, 48, 48, 62, 51, 51, 51, 51,110,  0,  0,// 'd'
  0,  0,  0,  0, 30, 51, 63,  3, 51, 30,  0,  0,// 'e'
  0, 28, 54,  6,  6, 31,  6,  6,  6, 15,  0,  0,// 'f'
  0,  0,  0,  0,110, 51, 51, 51, 62, 48, 51, 30,// 'g'
  0,  7,  6,  6, 54,110,102,102,102,103,  0,  0,// 'h'
  0, 24, 24,  0, 30, 24, 24, 24, 24,126,  0,  0,// 'i'
  0, 48, 48,  0, 60, 48, 48, 48, 48, 51, 51, 30,// 'j'
  0,  7,  6,  6,102, 54, 30, 54,102,103,  0,  0,// 'k'
  0, 30, 24, 24, 24, 24, 24, 24, 24,126,  0,  0,// 'l'
  0,  0,  0,  0, 63,107,107,107,107, 99,  0,  0,// 'm'
  0,  0,  0,  0, 31, 51, 51, 51, 51, 51,  0,  0,// 'n'
  0,  0,  0,  0, 30, 51, 51, 51, 51, 30,  0,  0,// 'o'
  0,  0,  0,  0, 59,102,102,102,102, 62,  6, 15,// 'p'
  0,  0,  0,  0,110, 51, 51, 51, 51, 62, 48,120,// 'q'
  0,  0,  0,  0, 55,118,110,  6,  6, 15,  0,  0,// 'r'
  0,  0,  0,  0, 30, 51,  6, 24, 51, 30,  0,  0,// 's'
  0,  0,  4,  6, 63,  6,  6,  6, 54, 28,  0,  0,// 't'
  0,  0,  0,  0, 51, 51, 51, 51, 51,110,  0,  0,// 'u'
  0,  0,  0,  0, 51, 51, 51, 51, 30, 12,  0,  0,// 'v'
  0,  0,  0,  0, 99, 99,107,107, 54, 54,  0,  0,// 'w'
  0,  0,  0,  0, 99, 54, 28, 28, 54, 99,  0,  0,// 'x'
  0,  0,  0,  0,102,102,102,102, 60, 48, 24, 15,// 'y'
  0,  0,  0,  0, 63, 49, 24,  6, 35, 63,  0,  0,// 'z'
  0, 56, 12, 12,  6,  3,  6, 12, 12, 56,  0,  0,// '{'
  0, 24, 24, 24, 24,  0, 24, 24, 24, 24,  0,  0,// '|'
  0,  7, 12, 12, 24, 48, 24, 12, 12,  7,  0,  0,// '}'
  0,206, 91,115,  0,  0,  0,  0,  0,  0,  0,  0,// '~'
};

// The pixel data is 8x12 bits, so each glyph is 12 bytes.
int ispixel(char ch, int x, int y)
{
  return (TFX_AsciiFontdata[(ch - 32) * 12 + y] & (1 << x)) != 0;
}

// The scroll text.
char scrolltext[]=" .       .    .   .   .  . . . . .. .... "
                  "Hello from the Emscripten world.. this small thingy was originally written as a "
                  "C++ SDL application and then, through a couple of small modifications, compiled "
                  "into JavaScript and HTML using Emscripten.... .. . . ..            .  ..   ...  ";

void render()
{   
  // Ask SDL for the time in milliseconds
  int tick = SDL_GetTicks();

  // Declare a couple of variables
  int i, j, yofs, ofs;
  
  // Emscripten won't update canvas if we don't lock the surface
  SDL_LockSurface(screen);
  
  yofs = 0;
  int scrolltick = tick / 10;
  
  // We fill the "screen" in vertical slices, so we have the horizontal loop
  // first, followed by the vertical one. This is done for a couple of reasons:
  // the DYPP scroller is pretty simple this way, and we only need to do the
  // "heavy" math once per vertical slice.
  for (j = 0; j < 400; j++, ofs++)
  {
    // Background wavy color thingy. Magic numbers galore.
    int bcol = 
(((int)((sinf((j + tick * 0.1f) * 0.0345987f) * sin((j - tick * 0.11f) * 0.0256987f)) * 0x7f) + 0x7f) << 0) |
(((int)((sinf((j + tick * 0.1f) * 0.0145987f) * sin((j - tick * 0.12f) * 0.0156987f)) * 0x7f) + 0x7f) << 8) |
(((int)((sinf((j + tick * 0.1f) * 0.0245987f) * sin((j - tick * 0.13f) * 0.0356987f)) * 0x7f) + 0x7f) << 16);

    // Y pixel position for the scroller. Same deal.
    int ypp = (int)(((sinf((j + tick * 0.043f * 2) * 0.0732f) +
                      sinf((j - tick * 0.034f * 2) * 0.0342f) -
                      sinf((j + tick * 0.027f * 2) * 0.0432f) -
                      sinf((j - tick * 0.017f * 2) * 0.0932f)) / 8 + 0.5f) * 40 + 6);

    for (i = 0, ofs = j; i < 80; i++, ofs += screen->pitch / 4)
    {
      // First, assume color is background
      int col = bcol;
      int chpos = i - ypp;
      
      if (chpos >= 0 && chpos < 24 && 
          ispixel(scrolltext[((j + scrolltick) / 16) % sizeof(scrolltext)],
                  ((j + scrolltick) / 2) & 7, chpos / 2))
      {
        // If we hit the scroller, change color to white
        col = 0xffffffff;
      }
      else      
      if (chpos >= 4 && chpos < 28 && 
          ispixel(scrolltext[((j + scrolltick - 4) / 16) % sizeof(scrolltext)],
                  ((j + scrolltick - 4) / 2) & 7, (chpos - 4) / 2))
      {
        // Otherwise, if we hit the scroller's shadow, set color to black
        col = 0;
      }
      
      // Plot the color (whichever it ended up)
      ((unsigned int*)screen->pixels)[ofs] = col;
    }
  }

  // Remember to unlock..
  SDL_UnlockSurface(screen);

  // Tell SDL to update the whole screen
  SDL_UpdateRect(screen, 0, 0, 400, 80);    
}


void mainloop()
{
  // Render stuff
  render();

  // Poll for events, and handle the ones we care about.
  SDL_Event event;
  while (SDL_PollEvent(&event)) 
  {
    switch (event.type) 
    {
    case SDL_KEYDOWN:
      break;
    case SDL_KEYUP:
    // If escape is pressed, return (and thus, quit)
      if (event.key.keysym.sym == SDLK_ESCAPE)
        exit(0);
      break;
    case SDL_QUIT:
      exit(0);
    }
  }
}

// Entry point
int main(int argc, char *argv[])
{
  SDL_Init(SDL_INIT_VIDEO);
  atexit(SDL_Quit);
  screen = SDL_SetVideoMode(400, 80, 32, SDL_SWSURFACE);  
  if ( screen == NULL ) 
  {
    exit(1);
  }
#ifdef EMSCRIPTEN
  emscripten_set_main_loop(mainloop, 30, 0);
#else
  while (1)
  {
    mainloop();
  }
#endif    
  return 0;
}

If you compile the above code into a HTML page and run it, you'll get something like..

Now the layout of the output page is more clear. At the top, you can find the canvas, some options (including a "Fullscreen" button), and a text box. The text box receives anything you might print out, which is rather handy if you need to debug.

If you don't see the scroller in the top part of the window, you're probably using a browser that doesn't support the HTML5 features needed. As of this writing, at least Firefox and Chrome work fine, while Safari and IE doesn't. I manually commented out the 'bind' instructions for the scroller example, which may not be too wise for general use.

Tutorial TODO:

  • Handling files
  • Other handy options like optimization
  • OpenGL

As usual, feedback is appreciated.

Site design & Copyright © 2021 Jari Komppa
Possibly modified around: April 08 2013