gles.js - a lightweight WebGL renderer for Android and Ouya (WIP)

Boris van Schooten, 4 Sept 2015 (boris@13thmonkey.org)

Latest news: gles.js is now on github! Note that the text below is old and needs to be updated.

After being unsatisfied with existing Android WebGL engines, I decided to create my own engine.

Chrome Android refuses to run WebGL on many (old and new) devices, unless you use the ignore blacklist option under the section "careful, these experiments may bite". Performance is not so good on low-end devices. For example, most games I played at html5games or playzap ran at 5-10fps in Chrome on my tablet, even if they had like 5 moving sprites. This is a cheap tablet, but it generally runs 3D games smoothly. And no, this is by no means the only device with this problem.

For those who want to package their html5 game as an app (I certainly do) there is CocoonJS and Crosswalk (based on Chromium). I did find CocoonJS significantly faster than Chrome, but it still failed to produce sufficient speed on my tablet. It sometimes also crashes or glitches, or input or sound do not work. It's kind of hard to figure out why because it's not open source. The huge file that the cloud compiler produces is not encouraging either (I got a 10Mb-22Mb APK without any clue as how to make it smaller). The only other html5 wrappers I know are Ejecta-X (2D canvas only), and Phonegap (based on WebView, which will not have WebGL until Android L, and does not have 2D hardware acceleration in Android 4.4 up to 4.4.2).

Since WebGL is virtually identical to OpenGL ES 2.0, which is available on any recent Android device, I was surprised with the low performance. I've seen my native apps do so much better. WebGL on mobile should be a better fit than WebGL on desktop, not worse. My suspicion is that Chrome/Chromium has a lot of security/safety/sandboxing overhead, and/or uses a renderbuffer compositing scheme for handling canvas scaling and HTML element overlays that doesn't play well with cheaper devices. CocoonJS probably has some of the same problems, in particular with regard to compositing.

To see if I could do any better, I created a lightweight implementation of WebGL which I call gles.js. The goal is not to provide a complete Web browser environment, but just a no-frills direct WebGL canvas, similar to what Ejecta does for IOS. That means no compositing and no HTML element overlays, because it's precisely that sort of stuff that kills performance and produces a large APK. It is based on the V8 engine, with bindings that provide direct calls to OpenGL ES. A small Javascript wrapper emulates the most essential parts of HTML5 (that is, the parts you need for a WebGL game). It contains a simple HTML parser that reads a HTML file into a DOM and loads and executes the scripts found. Basic audio, input, and XMLHttpRequest are also supported.

At this point I have two games and some pixi.js and phaser.js examples running, and it looks good! I managed to whittle down the base APK size to 1,3 Mb. I've tested it against Chrome on several devices, but I guess evidence remains somewhat anecdotical. My experience is that gles.js indeed runs faster than Chrome, speed difference varies across devices. It's like between 50% faster and 5 times (!) faster. It also runs smoother, that is, where I often see small hiccups in Chrome even on fast devices, gles.js runs the same code silky smooth 60fps, which has become the norm on Android nowadays. And my limited testing with CocoonJS also indicates it's faster than their implementation.

UPDATE: gles.js now has Ouya game controller support through the gamepad API. I'm now working on local storage.

UPDATE: gles.js now has proper pause/resume handling. It no longer exits or crashes when pausing/resuming. Sound is also nicely paused. Note that the demos are not yet updated, but the games are.

10,000 sprite demo

This was my first demo. Updates and draws the sprites in JS, with the code compatible with WebGL. It manages 10,000 sprites animating at 60fps on my tablet. Note that Chrome runs this demo at about 10fps on my tablet, and CocoonJS at 22fps. See below for more performance info.

Download/view proof of concept demo:

TODO: add FPS counter and the option to add more sprites.

First games running in gles.js

I got my game Nyan Can vs Flappy Bird Clones running in gles.js without changing the code! This was the reason I started on gles.js. I just didn't care to recode my WebGL games in Java.

When it first ran, I was happy that it ran at a steady 30fps! It wasn't the full 60fps I had hoped, but it was quite useable. This game runs at 5-10 fps in Chrome, so it's really much faster. I tried optimising it a bit here and there (I found that GL calls are really slow on mobile, so my code was and still is really suboptimal), and at this point got the fps up to 45-50. Chrome manages the same optimised code at no more than 10 fps, and CocoonJS at 25-30 fps. It also runs 60fps on the Ouya (now with gamepad controls!), which means it's much faster than Ouya's Android canvas, which is what I was aiming for. Note that I still have to do more testing, so consider this alpha.

I am happy to report that my second game, Tsunami Cruiser, also runs! I quickly made a gamepad port for the Ouya. I was quite surprised it runs at 60fps on the Ouya, as this beast is not well optimised and has like 2,000 particles and 100s of line segments and tons of trigonometry. CocoonJS runs this at around 40-50 fps with hiccups. Not bad, but the hiccups look terrible. I also ran it on the Moto G, where it also runs at 60fps (note you cannot move yet on Android). CocoonJS manages no more than 30fps on the Moto, and Chrome no more than around 20. So again, gles.js is consistently faster.

Pixi.js examples

Pixi.js runs without modification now, and I a currently going through the examples to make sure the different features work. I still have to do a few more examples. Here are some highlights. Oh--sorry, some of the apps are still called "nyan cat vs flappy bird clones". I'll fix that later. I guess have to create a new make script to maintain multiple packages more easily.

TODO: Fonts don't work yet. Bitmap fonts should be easiest to get running after I adapt my DOM parser to read generic XML.

Phaser example games

I got the first Phaser example games up and running! UPDATE: the games now have proper pause/resume handling. Unfortunately you cannot restart these games, so you need to kill the app to play again. TODO: I disabled the fonts because they rely on 2D canvas. For the rest, the code works unmodified. I did have to fix a couple of small bugs. Also, pixel perfect collision (not used in the examples) depends on canvas, so that doesn't work yet either. So does the debug function (I think it's a canvas overlay), so I had to turn that off for the game to work.

Where to go from here

I want to provide the app both as a "viewer" which loads resources from the SD card, or from a web site, and as an easy to package APK (just put your stuff in the assets/ directory and apk it). Also I plan to release the source, which should be quite hackable due to its simplicity.

I'm almost ready for a release! I still want to improve error reporting. Right now it does not report the line numbers of syntax errors.

After that I want to do the following in the short(er) term:

  • improve Ouya support (iap), as I plan to release my web games for Ouya also.
  • Add font/text support. In the first place, pixi style bitmap fonts.
  • Add local storage support
  • add support for the most popular ad and monetisation services.
  • Finish testing pixi.js and phaser.js. Try three.js as well. And by popular demand, Construct 2. C2 won't be easy because it's not open source, which means it will be harder to debug.

Appendix: Preliminary tests

I tested the demo on different devices. Up till now, it worked on all tablets I found, and also on all new phones. It does not work on some old phones, like my own (those with armv6 architecture, if I'm not mistaken). Most tablets and almost half the cheap phones manage the demo at 60fps. In fact, generally it runs better on tablets than on desktop PCs, as PCs often have trouble managing 60fps full-screen. See the overview below.

  • Point of View Tab-P703 (this is a cheapo relabeled Allwinner tablet, cost was 57 euro). CPU: Allwinner A13, 1.2GHz. GPU Mali 400. 800x480. 60fps.
  • ASUS MeMO Pad Smart 10", NVIDIA Tegra3 Quad-Core, 1.2 GHz, 1280x800. 60fps.
  • Lenovo IdeaTab A2109A. 1280x800. 60fps.
  • Ouya. The Ouya ran at like 30fps. After reducing the sprites to 1/4 size, it ran at 60fps. Apparently this device is fill rate limited, not too surprising as it has 6x more pixels than my P703. So, JS is not the primary bottleneck here.
  • Samsung Galaxy Tab Pro 10.1 (wifi version). 2560x1600. Quite surprisingly, it ran at 60fps, though the screen has 10x more pixels than the P703. This device has very good fill rate.
  • Arnova 9G3 (an older 9.7" tablet). Rockchip RK2918 (Cortex A8) at 1Ghz. GPU: Vivante GC800 GPU. It ran rather slow on this one. After reducing sprite size by 1/4 it ran significantly faster. I still had to reduce the number of sprites to 4,000 to get 60fps. Performance is still pretty reasonable.