Programming

JavaScript and 3d graphics don’t mix

Writing WebGL code in JavaScript has been very frustrating. There are numerous aspects of the language which make this task harder than it should be. Lack of typing, no operator overload, and by reference assignment are the three major issues. Performance is also a factor, but I understood that one going in.

I’m using the three.js library for my WebGL coding. My criticism in this article is aimed at JavaScript itself, not this fine library. As comparison I’ve also done a bit of OpenGL work in Java and C++. Some of the issues exist in Java as well.

By reference assignment

I’ve done a lot of coding of JavaScript and haven’t really been bothered by the reference assignments until now. It generally made sense to be dealing with high-level objects by reference. The problem with math is that I don’t consider vectors and matrices to be high-level objects. In the context of graphics these are fundamentals.

Several defects in my code result from failing to take a copy of a variable. Simple assignment is sometimes to blame, though function parameters are more problematic. Consider a common example of creating a world object with a helper function.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function place_new_object( where ) {
    //make some object
    var object = create_object()

    var wrap = {
        object: object,
        position: where,
    }

    scene.add(wrap)
    return wrap
}

The defect is not obvious to see, even though I know it is there. The assignment of position in the map is at fault. The caller is expecting the current value of position to be used, instead the new object binds itself by reference and thus shares a position value. These two common examples show how easily this breaks the application.

1
2
3
4
5
6
7
// create several new objects into the distance
var p = new Vector3(100,100,0)
var group = []
for( i=0; i < 5; ++i ) {
    p.z += 100
    group.push( place_new_object(p) )
}
1
2
//clone an object where it is now
var n = place_new_object( cur_object.position )

place_new_object needs to explicitly clone the position: position: where.clone(). It’s easy to forget since it’s not obvious. Making matters worse, JavaScript doesn’t actually have a built-in clone function. Each value could end up being cloned in a different fashion, depending on the library — assuming a clone function has even been provided.

Lack of type safety

One of the key features of dynamic languages is also a significant problem: the ability to assign anything to a variable. It isn’t unique to JavaScript, though in my work with WebGL it’s becoming a significant hurdle. I accidentally assign a Vector3 when a Vector4 is required, or I assign an object of {x,y,z} where a Vector3 is required. Since many operations can actually work on the wrong type (à la duck typing), the error may not occur anywhere close to the incorrect assignment. Instead I’m left perplexed with an error deep in the library code.

Perhaps the problem is more pronounced in graphics. A lot of similar data types are used, and must be mixed within an expression. 2, 3, and 4 dimensional vectors, along with corresponding matrices, are all required to produce a geometry. Rotations can be represented with both euler angles or quaternions. A color may be a strict color type, or a vector in RGB colorspace. Even very basic code has no choice but to use a lot of very similar types.

A lack of type safety also complicates the use of objects. It’s too easy to assign to a member when instead you should be calling set or assigning to a sub-field. This can be seen with uniforms in three.js. The uniform is an object containing some meta-data, as well as the value.

1
2
//update the angle of a shader effect
material.uniforms['angle'] = nextAngle(time)

Far away from this code an error will be generated. Instead of setting the value of the uniform I’ve completely replaced the uniform object with a floating point value! I should have set the value field instead.

1
material.uniforms['angle'].value = nextAngle(time)

These types of problems sour my attitude towards dynamic typing. On this project it has absolutely not been convenient. I’ve wasted a fair amount of time tracking down incorrect type assignments. It’s something that even very basic type declarations would prevent.

No (operator) overloading

Linear algebra uses common operations like addition and multiplication. It is natural to write the multiplication of a vector by a matrix as M * V. With OpenGL the use of vectors and matrices in expressions is as common as integers and floating point. A lack of operators leads to a loss of clarity.

Also see my article The Evil and Joy of Overloading

1
2
3
4
5
//JavaScript
var next = perp.applyMatrix4( trans ).add( from )

//C++, GLSL
vec4 next = trans * perp + from

It’s often actually worse. three.js is optimized for not creating new objects on all operations since this is actually quite expensive in JavaScript. Like with by reference assignments one must explicitly clone.

1
var next = perp.clone().applyMatrix4( trans ).add( from )

One could argue this is a library issue; the operations could create new values. Here is where JavaScript’s performance becomes a major issue. If every operation allocated new arrays the memory manager would quickly trip over itself and die. In C++ one does however just return copies. There we can rely on proper optimizations that make it cheap to do so.

The lack of types, and thus overloads on types also makes it difficult to write generic utility functions. For example, there is no way to write a generic dot-product function that operates on different vector sizes. Each size requires its own version. It is possible to store the dimensions of vectors and matrices in their respective objects, and make a generic function, but then performance again becomes a major issue.

C programmers might not have a problem with this, but at least in C you can’t accidentally call the wrong version of the function. Lacking type safety in JavaScript it’s easy to call the wrong function, for example multiply3 when you meant multiply4. Worse, it’s even possible to just not notice. A function which expects a Vector2 will gladly accept at a Vector3 since it has enough matching fields. It won’t do the correct calculation, but it will merrily proceed as though everything is fine.

Going Forward

I think I do need to mention performance, though it’s not the aspect that bothers me the most, it certainly doesn’t help anything. Even after managing to get the code working it just won’t perform very well in JavaScript. The lack of typing and value semantics makes the job of the optimizer too difficult. Even for minimal graphics code there is a definite need to optimize. It’s not like it’s just a tad slower than C++ either, it’s unbelievably slower. A lot of graphics algorithms, and thus applications, are simply not possible if they have to be done in JavaScript.

I looked at the emscripten project and asm.js. This uses the LLVM toolchain to compile C/C++ code into JavaScript and then execute it efficiently. What comes out of this basically signals the death of JavaScript in this arena. There’s simply no value in coding in a language which is both harder and slower. Once this toolchain matures a bit I will definitely go only this route for my graphics code. Indeed, it’s likely my next avenue of pursuit for Leaf: compiling to asm.js.

Categories: Programming

Tagged as: , ,

16 replies »

    • Looking at this I see that GWT is Java based. Thus I must bring up the issues which still exist with Java when compared to JavaScript.

      1. It uses by reference by default, and offers no value assignment, not default cloning. Thus I’m still forced to manually clone objects and ensure I don’t accidental refer to the same object. This is a big issue for me. Vectors and Matrices are fundamental types in this domain.

      2. Lack or operator overloads. At least it allows type based overloads, which is a huge step up from no overloads whatsoever. I could actually live without the operators, though I find it less readable.

      3. No parametric functions. This still means I have to write the same code multiple times to apply to each type (like Vector2, Vector3, Vector4).

      Though compared to JavaScript I have no doubt Java is the better option for what I’m working on now. But GWT itself may be going overboard here. I don’t want an application framework, I just want the graphics parts of my application written efficiently. I’m still fine using JavaScript to combine the various components on the page.

    • There are in fact two sides to GWT: the UI web-app framework which generates browser-specific HTML/JS to represent a bunch of widgets such as tabbed views, buttons etc, and the plain Java to Javascript transpiler.

      It is quite possible to use the second of these two on its own, and create a Javascript library in Java, that can be included within your web page and invoked from hand-written Javascript. Look at GWT Exporter to assist with this.

  1. Hi, I am an emscripten developer – I’m curious what issues you found with the emscripten toolchain, that we need to fix? Thanks!

    • I didn’t mean to imply there were issues that needed to be fixed. I only looked at it briefly, and was highly impressed with some of the demo apps (those guys that ported Qt are kind of crazy though!) By lacking maturity I primarily mean it is non-trivial to get running, and the documentation/community is still limited. The only real issue I spotted in my short excursion was compilation speed — it is somewhat slow. Though since I can test natively first it may not be a real issue.

      In any case, I do stand behind my comment that emscripten is the way to go. I will defintiely make my language Leaf target this LLVM backend.

  2. At my work, we are doing heavy-duty development with JavaScript, and like you, we would have gone insane without type safety. Thankfully, we use an unlikely hero – the open source TypeScript compiler from Microsoft.

    Together with declarations from DefinitelyTyped, my Debian machine uses my custom Makefiles to compile the TypeScript .ts files into easily identifiable equivalent .js ones (with .map files that Chrome can use during debugging, so I debug with the original .ts code, not the generated .js code). If nothing else, I strongly suggest you have a look.

    • Numerous people have pointed me towards TypeScript now. I’m actually already abandoned raw JS in favour of CoffeeScrip. If TypeScript will interface with my existing code, and give me type safety, It’ll definitely speed up my WebGL development.

  3. Fwiw, I believe that carefully unit testing every function in the code is even more important in a language with this kind of lack of type-safe compilation. This approach to programming seems to be widely accepted as valuable in many contexts. There are those who still want to hack first and test later but I don’t think it’s considered a best practice any longer. Also please discriminate between “it’s” and “its” correctly. It’s not that hard :D

    • It’s not a question of whether the code is unit tested or not. Indeed I try to unit test all of my non-UI code. The problem is that my unit test fails and I have no idea why. I spend a lot of time debugging unit tests.

      The lack of type safety also ensure that unit tests won’t be perfectly correct. While I may call the function in a unit test with the correct type, it doesn’t stop me from calling it with the wrong types in the live code.

    • If you mostly avoid using stubs/fakes/mocks you should end up with a pretty good regression suite at least on the non-ui part of the code. That ought to catch most cases like the ones you’ve described. But yeah, you still have to go looking for the source of the test failure as opposed to getting a compiler error.

    • I’ll have to step in. This is a bad answer.

      Hoping that unit tests will save you because you will be “carefully unit testing every function” sounds naive at best. Static type checking allows you have tests you don’t have to write. Say you’re using a language like Haskell or typed Racket that gives you a relatively advanced type system, matching this with unit tests would be incredibly long and borderline counterproductive. More code to maintain, more possible bugs, later shipping date. Great. And that’s assuming your unit tests are perfect.

      Moreover, having a direct access to the type signatures of every function makes the “simple” mistakes mentioned in TFA more rare, and you can even be helped by whatever IDE you’re using.

      I’m all for testing software, and I’m a happy continuous integration user, but no, a suite of tests won’t make up for static type checking. People should stop spreading this idea.

  4. I totally agree with this article on the weaknesses of JS for graphics/physics/UX. I reccommend you check out Uno (www.outracks.com). It’s a language designed for graphics programming, which addresses all of the problems you are describing, and compiles to JavaScript with asm.js optimizations + lots of other platforsm :)

    • Wow, Uno looks promising. Do you have any idea of the licensing type we can expect? (In other words, do you know whether it’s going to be open source?).

  5. Hi mate! I may not be a JavaScript expert, but have already been working with this language for long enough to feel comfortable with most of the issues you’ve pointed out, except for the lack of operator overloading. It’s a huge disappointment every time I try to perform some linear algebra with this language. I see you’re working on Leaf, which I suppose will work like CoffeeScript. Do you plan to add support for operator overloading in it?

    • Leaf is a standalone language. One of the targets will be JS, but so will native programs for most platforms. It belongs more in the family of C++ than JavaScript — though it has all the nice high-level features like closures (though C++11 also has those). I firmly believe in operator overloading, so yes, Leaf supports that (already does). However, I don’t like lossy conversions, so no implicit conversion will ever lose precision/information. I think this is required to keep overloading sane.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s