Wednesday, June 2, 2010

Stupidly fast WebGL Matrices

I've made a matrix library for WebGL. It's fast. Really fast. It's linked right here: glMatrix
[EDIT: And just been updated! Now even faster!]

And, lest some scoff at my claims of fastness, I've also made a benchmark: glMatrix Benchmark

Don't bother with that last link if you're browser doesn't have WebGL enabled. None of the comparison libraries really work without it. (Though glMatrix will function with any javascript sequence.)

Okay, so let's get a little more serious. Why in the world would I want to make yet another matrix library when there are already a good selection of them?  That's a tough one, especially because I'm a very firm believer in not reinventing any wheels. What it really comes down to is this: I tried all the other matrix libraries I could find and didn't feel like any of them were meeting my needs.
  • Sylvester is a very nice and robust library that I've seen some people using with WebGL, and I can't really blame them. It's probably the most complete one that I've seen, and is aimed at people doing heavy duty math in their browser. The biggest problem with it is simply that Sylvester was built for robustness, not speed, and as such is ill suited for realtime graphics.
  • Khronos recommends using CanvasMatrix in their introductory tutorial. It's apparently from Apple (according to the comment at the top), and has a nice interface, but once again isn't really built with an eye towards performance.
  • mjs was built to be far more speed conscious, and it shows. Done by the guy who did the original Spore WebGL demo, it's a nice little library and it's what I've been using so far. I only have a few complaints with it: I'm not a huge fan of the syntax ('M4X4' is awkward to type over and over again), and you end up creating a lot of temporary matrices to store intermediate values in (passing a single matrix as both the source and result matrix can corrupt the value). There's also some odd bits of missing functionality (like a full, generic inverse) that may or may not be a problem. Still, it's very workable.
  • EWGL Matrices appear to be the latest of the bunch, and the idea behind them was very specifically to be extremely fast. To that end they did a great job, but there were still a couple of things that I was annoyed by. All operations take place on the source matrix, which can lead to some unnecessary duplications in certain scenarios. (Basically the inverse of mjs's problem) And it's object oriented nature tends to force you to use, for example, their vector types when passing translation or axis data, which I find somewhat cumbersome.
Now, all of these complaints are, in reality, quite petty. If that were the extent of it I'd just use one of the latter two and be done with it. But there was one feature that was missing from all of them (except Sylvester, which doesn't count for speed reasons) that killed it for me: none of them had facilities to multiply a vector by a matrix! To me that seems like a rather obvious one, because while certainly in an environment like WebGL we want to let the GPU do as much work as possible there's some times where you simply want to rotate a point in memory.

So, of course, noticing a small deficiency in the existing options I did what any normal programmer would do: Wrote my own. :) 

There were a few key things I was aiming for when I wrote it:
  • The interface needed to be clean and consistent
  • Operations needed to be able to happen in-place OR written out to a destination array, leaving the original untouched.
  • The library should not lock you into using a certain type or set of classes. (ie: It should work just as easily with Arrays as WebGLFloatArrays)
  • And for crying out loud it needs to be able to transform vectors!
Believe it or not, speed was a completely secondary concern while building it. Once I got all my desired functionality in there and started doing some benchmarks, however, I realized that I wasn't too far off from the faster existing libraries. So I spent another evening optimizing the crap out of it to make my little matrix library "stupidly fast". A few key optimizations were:
  • Unroll EVERYTHING. There's not a single loop in the code.
  • Take advantage of javascript's variable caching. If a matrix element was read more than once in any function it was stored in a local variable first. I was honestly surprised at just how much of a difference this one made!
  • Inline anything that made sense. ie: Although I have a vector normalize function, I did an inlined version in the matrix inverse to cut down on call overhead and additional variable creation.
  • Take shortcuts when possible! If the source and destination matrix for a transpose are the same, we don't need to alter the elements on the diagonal. Or when rotating if we notice that it's along the X, Y, or Z axis we can cut out a lot of calculations that would just end up as 0 anyway.
And the end result is that glMatrix outperforms even EWGL in every scenario I've tested! No small feat!

Of course, "stupidly fast" is probably pushing it. After all, this is Javascript we're talking about. Even the most naive of C matrix libs would run circles around the best javascript libs. But when it comes to WebGL we really don't have much of a choice now do we? At the very least I feel confident in saying that this library is one of the fastest (if not THE fastest) javascript matrix libs available today.

This being a first release, I fully expect a few bugs here and there and welcome any feedback on how to improve things. But hopefully it can serve as a meaningful contribution to the WebGL development scene.

Happy coding!