Thursday, July 07, 2011

Accelerating ImpactJS With WebGL

First of all, I want to say that I am not claiming any significant credit for this. The cake was already for the most part complete. I just spread a little icing and added a cherry on top.

One of the problems with the implementation of HTML5's Canvas on the Mac is that it is not hardware accelerated (at least in Chrome, which is what I test with). Consequently, all of the drawing to the canvas is done in software, which is a shame seeing as on most computers there is a perfectly capable graphics card sitting idly by while the processor does all of the work. The end result was that my top-of-the-line MacBook Pro struggled to get 30-40fps which a two year old crappy laptop running Windows (which has a hardware-accelerated canvas in Chrome) got a smooth 60fps. Hell, even Windows Chrome running in VMWare on my Mac got 60fps.

Creatures & Castles for the Chrome Web Store uses the ImpactJS framework, which has a rather useful plug-in system to override and replace core parts of the functionality. A while back, the creator of ImpactJS tweeted that he had played with Corban Brook's WebGL Canvas Wrapper API created in Javascript. The only catch was that it did not support alpha. Luckily, there was a fork here by FuzzYspo0N (Sven Bergström) which had implemented limited transparency in an older version of the original project.

It was simple enough to look at the shader modifications in the alpha-enabled version and apply them to the absolute latest version of Corban's original. I even extended the alpha support to other drawing types supported by canvas (e.g. line and shape) so that the alpha was respected by all of the API calls (at least all that I have tested). Okay, so that's the icing... How about the cherry?

I extracted the plugin from the ImpactJS demo game here, but I wanted a plugin for ImpactJS that would automatically probe to see if the browser supported WebGL and fall back to standard 2D if it did not. Well, with the current implementation of the WebGL wrapper, it throws an exception if WebGL is not supported. (Note: I'm not to keen on using this as a detection mechanism, and may well improve this in future). In any case, with all of the browsers I've currently tested it on, the plugin has the desired effect of using WebGL if it's available and falling back to Canvas2D if not. It's a bit quick and dirty, but it works.

The end result? A smooth 60fps on my MacBook Pro. Hooray!

The code can be found here. Suggestions for improvements and any bug fixes are welcome!

Just for completeness, here is the obligatory before and after comparison:

Before (standard Canvas 2D)
After (WebGL)

21 comments:

Anonymous said...

Hi Andrew, thanks for the great plugin! I implemented it into my Impact game and got everything working. Oddly, when I switch to the webgl context I lose 10 fps, but I'm attributing that to my ancient MacBook Pro :)

Have you thought about putting this up on Github and posting it to the Impact forums?

Thanks again!
Nikki

Andrew (hiive) said...

Hi there! Thanks for the comments... I haven't had time to announce it on the ImpactJS forums (as I'm travelling right now) but if you happen to be on there then please be my guest. :)

Derek Thomas said...

Yea, I see the same result... a lose of about 10fps even with good hardware.

Andrew (hiive) said...

How odd... I'm not seeing that here. That is a concern of course. Can you verify with Dominic's original demo here: http://www.phoboslab.org/xtype/ and compare it to here: http://www.phoboslab.org/xtype/?force-canvas-2d to see whether you see the same results.

quidmonkey said...

I've got the plugin in the plugins folder, but where do I dump the actual webgl-2d.js (WebGL file)? I can't get Chrome to load it.

Andrew (hiive) said...

Just put it in your script directory and include it like any other script.

Don't forget to reference your plugin according to the ImpactJS instructions.

quidmonkey said...

Sweet. Forgot about the script. Working now! Thx for the help.

John-David said...

You can check for webgl support without a try-catch via something like this or this. I prefer the first detection example because it avoids direct property access which has been known to cause exceptions in some browsers for other host objects.

Andrew (hiive) said...

Thanks! I'll take a look at that and snaffle the relevant bit :)

tcorpse said...

Thx alot for the great plugin. We're currently evaluating impactJS for our project and saw your game. I thougth it's really awesome that you can turn WebGL in your game on and off (I mean how cool is that). Checked your blog and got the code :D.

Thanks again
t0n!ght

Andrew Rollings said...

You're welcome :)

encryptoknight said...

Hi Andrew,

This looks fantastic! I run into an issue with ImpactJS 1.2.0, though. The error in the console is:

Uncaught TypeError: Property 'getDrawPos' of object [object Object] is not a function

in lib/impact/game.js line 226. I've included the webGL-2d main script just after the ImpactJS script in my main HTML page, and have added 'plugins.webgl-2d' to the list of required modules in my main game script. Any idea what might be causing this?

Andrew Rollings said...

I'm not sure, as I haven't upgraded to the latest impactJS yet.

However, when I do, I'll fix the problem and add a blog post to address the issue.

encryptoknight said...

That's much appreciated. I'll give the usual excuse here: if I manage to make some time, I'll look into it as well and see what I can uncover.

fulvio said...

Perhaps a proper tutorial on implementing this into an existing Impact game?

fulvio said...

I'm getting the same error. Did you manage to get this working in the end?

fulvio said...

Upgraded yet? I'm getting the same getDrawPos error.

encryptoknight said...

No; I haven't had time to really look into it further. There is a pretty interesting WebGL demo at http://asmallgame.com/labsopen/webgl_impact/. They've mixed three.js with ImpactJS.

Mitja said...

Very easy fix, inside of ImpactJS "webgl-2d" plugin, this line is missing:

this.getDrawPos = ig.System.drawMode;

Just add it after the: this.resize(width,height,scale);

So whole stack is:

this.fps = fps;
this.clock=new ig.Timer();
this.canvas=ig.$(canvasId);
this.resize(width,height,scale);


this.getDrawPos = ig.System.drawMode;

&thats it :)

encryptoknight said...

Interesting, Mitja. It seems to work for me. Thanks for this.

Andrew Rollings said...

Thanks Mitja! I've been too busy to get to it. I appreciate the fix, and I'll update this post as soon as I can get a moment to test everything.