Playing with asm.js and Browser Physics

 
Asm.js Physics Demo

Lately I’ve been very intrigued about Mozilla’s asm.js specification. ASM.js is a subset of javascript that allows javascript interpreters to compile code ahead of time, directly into assembly code. This results in huge speed increases.

Current benchmarks of asm.js code running in Firefox’s Nightly build, place javascript at only half the speed of native compiled code! A big difference from the usual, which is around 4 to 10 times slower. One fun showcase of this was the recently ported Unreal Engine 3 to Asm.js.

Yes. We’re running high performance 3D games in web browsers now…

John Resig has a nice writeup about asm.js, including a QA transcript with David Herman (Senior Researcher at Mozilla Research). You should give it a read if you’re interested.

Of course, being curious and well caffeinated, I like to play around. I did a simple benchmark and a little proof of concept of my own. But first, let’s quickly go over asm.js.

asm.js Modules in a nutshell

Firstly, a disclaimer. asm.js code is a pain to write by hand. (I do so because I drink too much coffee…) I don’t really recommend starting any large project thinking that you’ll write it in asm.js from scratch. Likely, the best way to leverage asm.js will be to write in another language (like C++, or something like LLJS), and then “compile” it into asm.js. The painful part is mainly that it feels like writing in two languages at once. It’s javascript… but it feels like something else… anyways, let’s move on.

Asm.js contexts begin with a declaration like this:

"use asm";

This is in the spirit of the "use strict"; declaration, that you are hopefully familiar with. (If not, google it. It’s good to know).

This will most often be placed inside a function that acts as a “factory” for an asm.js module. This asm.js module format looks like this:

function my_module( stdlib, foreign, heap ){
    "use asm";

    // magic... (not really)

    return {
        someMethod: someMethod
    };
}

var instance = my_module(
        window,
        {
            customProperty: value,
            externalFunction: function(){
                // ...
            }
        },
        new ArrayBuffer( size )
    );

When this function is run, it will return an instance of an asm.js module. If asm.js is supported, the methods in the returned object will be compiled into native code. If not, they will be javascript with identical functionality.

Inside the module factory, stdlib will hold standard global functionality (like Math), foreign is user specified and can be used to escape back into regular javascript, and heap is a fixed-size ArrayBuffer for storage.

The magic that happens in between relies on a very restrictive requirement that all primitives must be statically typed as integers, or floats. Numerical computation is pretty much all you can do in an asm.js context (and that’s why it’s so fast). Of course, javascript doesn’t allow for declaring types, so this is done by transforming variables into their types like so:

variable_int = variable_int|0;
variable_float = +variabla_float;

I’m not going to go into great detail about the specifics of hand crafting asm.js code. For that you should check out John Resig’s article and read the specification.

However, one of the big pains to writing it by hand is dealing with memory allocation. The only thing you really have access to in terms of memory, is the fixed-size ArrayBuffer you specify when you instantiate the asm.js module. You then have to then create views into the array buffer to manipulate data.

Easier asm.js Memory Management

Memory management becomes cumbersome. After playing around with it, I decided I needed to write a quick helper module to make this easier.

Basically, the helper lets you manipulate “collections” of objects that store all of their (primitive) properties in an array buffer. For example:

var coln = ASMHelpers.Collection({
        foo: 'int16'
    });

This would create a collection instance that holds objects that hold 16 bit integers in their foo properties. You can then add one of these objects to the collection and all of the ArrayBuffer memory management is taken care of behind the scenes:

coln.add({
    foo: 42
});

var obj = coln.at( 0 );
obj.foo; // => 42

The objects have getters and setters defined, so if you change the property, it will get changed in the array buffer.

obj.foo = 34; // changed in array buffer

Unfortunately, non of those tricks can be used in the context of an asm.js module. But that’s ok, because the helper collection sets up pointer addresses that can be used in asm.js. Here’s how:

// include asm.js module methods into the collection
coln.include(function(stdlib, coln, heap){
    "use asm";

    // general purpose std functions
    var sqrt = stdlib.Math.sqrt;
    // set up our view to look into the heap
    // this will be used to access our int16 properties
    // in our example, this would be the "foo" property
    var int16 = new stdlib.Int16Array( heap );

    // object property pointers, relative to object ptr
    var $foo = coln.$foo|0;
    
    // function to get the number of objects in the collection
    var getLen = coln.getLen;
    // the size of each object in bytes
    var size = coln.objSize|0;
    // starting point for iteration (ie: the address of first object)
    var iterator = coln.ptr|0;

    // example function that increments the foo property 
    // of every object by specified number
    //
    function incrementFoo( n ){
        // declare n as an integer
        n = n|0;

        // declare local variables
        var i = 0, l = 0, ptr = 0;
        ptr = iterator|0;
        l = getLen()|0;

        // loop through objects
        while ((i|0) < (l|0)){

            // foo += n;
            int16[(ptr + $foo) >> 1] = ( (n|0) + (int16[(ptr + $foo) >> 1]|0) )|0;
            // i++;
            i = ((i|0) + 1)|0;
            // ptr += size;
            ptr = ((ptr|0) + (size|0))|0;
        }
    }

    // these functions will get mixed into the collection
    return {

        incrementFoo: incrementFoo
    }
});

Benchmarking with JSPerf

Using this helper, I wrote a very simple 1D physics engine to test performance difference between simulating numbers of objects with the “use asm” declaration, and without it.

Here’s the benchmark on JSPerf

The results in Firefox 23 nightly build:

Aside for some weirdness on the last test, enabling asm.js is significantly faster for this physics algorithm.

Building a simple physics engine in asm.js

Of course, benchmarks are nice… but graphics are more fun! I wanted to keep going and see what this speed increase felt like for some real-time browser simulation.

asm.js Physics Demo To that end, I created this fun demo.

Creating a 3D Histogram with CSS 3D Transforms

 

Using CSS 3D transforms can be a bit challenging. It’s new, support is sketchy, and it requires a type of spatial thinking that we aren’t used to on the web (yet). But the results are worth it. With a bit of CSS and some javascript for event binding, you can turn a boring old table into a 3D histogram like this one:

30 20 60
80 10 40
20 50 30

Step 1 - Markup

First, we need the HTML. We’ll create two container elements that are quite common in 3D css work:

  • a viewport: acts as the projection layer or “camera”
  • a world: which holds every other child element which we can easily rotate

I also like to add another element, the ground, which is just a plane that objects will sit on.

Inside this tree of three elements, we’ll add our table, which contains the 3D histogram values. Here’s the markup:

<div class="viewport">
    <div class="world">
        <div class="ground">
            <table class="histogram-3d">
                <tr>
                    <td>30</td>
                    <td>20</td>
                    <td>60</td>
                </tr>
                <tr>
                    <td>80</td>
                    <td>10</td>
                    <td>40</td>
                </tr>
                <tr>
                    <td>20</td>
                    <td>50</td>
                    <td>30</td>
                </tr>
            </table>
        </div>
    </div>
</div>

Step 2 - The environment CSS

Next we need to set up our 3D environment. For the sake of simplicity, I will omit -vendor-prefixed values.

First, the viewport. I some relative positioning, and dimensions, but the important values are the perspective and perspective-origin values. Roughly speaking, the perspective controls how far the “camera” is from the origin, and perspective-origin defines its position.

.viewport {
    position: relative;
    width: 100%;
    padding-bottom: 100%;
    cursor: move;

    perspective: 4000px;
    perspective-origin: 50% -100%;
}

Next, the world. Again, we set some positioning and dimension values, but the important values for the world are tranform and transform-style. The transform value will rotate the world to its starting orientation. The transform-style is set to preserve-3d. This is so that all children of the world will also have 3D appearances. If we didn’t set this, all children of the world element would be smushed into the world plane with no depth. Note: we’re also setting preserve-3d on every child of the world too. Firefox doesn’t seem to properly propagate this setting down the DOM tree… so this gets around that.

.viewport .world {
    position: absolute;
    top:0;
    left:0;
    right:0;
    bottom:0;

    transform: rotateX(-15deg) rotateY(-20deg);
}

.viewport .world, 
.viewport .world * {
    transform-style: preserve-3d;
}

Finally, the ground. Easy peasy. Set some positioning (absolute centered alignement), give it a background, and rotate it along the X axis so that it lies flat on the world’s X-Z plane.

.viewport .ground {
    position: absolute;
    z-index: 1;
    top: 50%;
    left: 50%;
    width: 90%;
    height: 90%;
    margin-left: -50%;
    margin-top: -50%;
    background: #eee;
    
    transform: rotateX(90deg);
}

Try it out in a JSFiddle with the appropriate vendor prefixes and you should see a boring grey square skewed a bit with the table data inside.

Step 3 - The Histogram

Here’s where things get tricky… and fun! First, we want to set up the table to be the grid of the histogram. That’s easy. Just set some dimensions and borders on the tables, like you normally would.

.viewport .histogram-3d {
    width: 80%;
    height: 80%;
    margin: 10% auto;
    border-collapse: collapse;
    border-style: solid;

    /* make sure grid is raised above ground */
    transform: translateZ(1px);
}

.viewport .histogram-3d td {
    position: relative;
    width: 30%;
    height: 30%;
    padding: 10px;
    border: 2px solid #555;
    z-index: 0;
}

The tricky part is creating the bars. What we need to do is use some javascript to replace the numeric values in the table with some markup to act as the 3D bar. The markup we want to insert into every table cell looks like this:

<script id="bartpl" type="text/template">
    <div class="bar">
        <div class="face top"></div>
        <div class="face front"></div>
        <div class="face back"></div>
        <div class="face left"></div>
        <div class="face right"></div>
    </div>
</script>

The .bar element just contains the five faces (we don’t need a bottom face). To do the replacement, it’s easiest to use a template. We can store this markup in a script tag with a type="text/template" or similar. The browser won’t try to run this as javascript, and we can simply use .innerHTML to get the content. The javascript will look like this (using jQuery):

// get the template
var tpl = $('#bartpl').html();

// insert template markup into each td
// and set the font size to be the value of the td
$('.histogram-3d td').each(function(){
    var val = this.innerHTML;
    $(this)
        .html(tpl)
        .css('font-size', val+'px')
        ;
});

Pretty straightforward, but why are we setting a font size!? This is the magic bullet to control the size of the 3D bars.

We’re going to use dimensions relative to the font size (em), in order to control the height of the bars. So if we set a font-size on the bar element, the faces will resize appropriately to the correct height. Snazzy, eh? I can’t take full credit for this strategy, though.

So, let’s add the bar css. First, we simply resize the bar container to fill the table cell, and set a relative position. Then for every face element, set a background, an absolute position, relative size, and orientation in 3D space. The differences in color correspond to different shadings of each face. If you want to get fancy, you can use the Photon CSS lighting engine, but beware, I found some buggy behaviour in firefox when using it table elements. More about that later. Here’s the rest of the CSS.

.viewport .bar {
    position: relative;
    width: 100%;
    height: 100%;
    z-index: 1;
}

.viewport .bar .face {
    background: hsl(0, 100%, 50%);
    position: absolute;
    width: 100%;

    overflow: hidden;
    z-index: 1;
}

.viewport .bar .face.front {
    background: hsl(0, 100%, 20%);
    bottom: 0;
    height: 1em;

    transform-origin: bottom center;
    transform: rotateX(-90deg);
}

.viewport .bar .face.right {
    top: 0;
    right: 0;
    width: 1em;
    height: 100%;

    transform-origin: center right;
    transform: rotateY(90deg);
}

.viewport .bar .face.left {
    background: hsl(0, 100%, 45%);
    top: 0;
    left: 0;
    width: 1em;
    height: 100%;

    transform-origin: center left;
    transform: rotateY(-90deg);
}

.viewport .bar .face.back {
    top: 0;
    height: 1em;

    transform-origin: top center;
    transform: rotateX(90deg);
}

.viewport .bar .face.top {
    background: hsl(0, 100%, 40%);
    height: 100%;
    width: 100%;
    top: 0;

    transform: translateZ(1em);
}

Step 4 - Make it rotate!

The last step can be done in many different ways. We need to attach mouse/touch events to change the world element’s orientation in order to rotate the histogram.

Generally this means tracking the mouse/touch event coordinates and mapping them to an angle. This is how I’ve done it, but you can probably come up with a cleaner and more general way to do this, right? :)

var dragStart = {}
    ,dragging = false
    ,curpos = {x:100,y:-75}
    ;

var touch = Modernizr.touch
    ,$vp = $('.viewport:first')
    ;

$vp.on(touch?'touchstart':'mousedown', function(e){
  
    var evt = touch? e.originalEvent.touches[0] : e;
    dragStart = {
        x: evt.screenX + curpos.x,
        y: evt.screenY + curpos.y
    };

    dragging = true;
    $('body').addClass('noselect');
});

$(document).on(touch?'touchend':'mouseup', function(){
    dragging = false;
    $('body').removeClass('noselect');
});

$(document).on(touch?'touchmove':'mousemove', function(e){
  
    if (!dragging) return;

    e.preventDefault();

    var evt = touch? e.originalEvent.touches[0] : e
        ,x = dragStart.x - evt.screenX
        ,y = dragStart.y - evt.screenY
        ,amp = 0.2
        ;

    curpos.x = x;
    curpos.y = y;

    $vp.find('.world').css(
        Modernizr.prefixed('transform'),
        ['rotateX(',y*amp,'deg) rotateY(',-x*amp,'deg)'].join('')
    );

});

Caveats

Unfortunately, things get messier because of cross-browser support. I’m not even talking about IE. I’m talking about Firefox. Turns out 3D transforms won’t work with CSS table layouts in Firefox. This means, for Firefox (and perhaps other browsers), we need to reset the layouts of the table, tr, and td elements to use only block and inline-block display values… which is annoying. What I’ve done on this page is browser sniff for firefox, and add a body class no-3dtablelayout. Then I override the layouts within that scope like so:

/* don't use table displays... need to use a combination of block and inline-block. Eww. */
.no-3dtablelayout .viewport .histogram-3d,
.no-3dtablelayout .viewport .histogram-3d tbody,
.no-3dtablelayout .viewport .histogram-3d tr,
.no-3dtablelayout .viewport .histogram-3d td { 
    display: block; 
    -webkit-box-sizing: border-box;
       -moz-box-sizing: border-box;
        -ms-box-sizing: border-box;
            box-sizing: border-box;
}

.no-3dtablelayout .viewport .histogram-3d tbody { height: 100%; }
.no-3dtablelayout .viewport .histogram-3d tr {
    text-align: center;
    height: 33%;
    /* fix grid */
    letter-spacing: -0.5em;
    margin-top: -1px;
}
.no-3dtablelayout .viewport .histogram-3d td {
    display: inline-block;
    height: 100%;
}

To add insult to injury, the result looks a bit ugly. Firefox doesn’t seem to be using anti-aliasing for the face edges. To make it look a bit better, we can apply a little hack:

.viewport .histogram-3d * {
    /* this improves jagged edges in firefox */
    outline: 1px solid transparent;
}

That’s all for now. I’d love to hear any other cross-browser issues you find.