HIGH PERFORMANCE GRAPHICS WITH WEBGL

Thom February 14th 2017

WebGL is a wicked fast JavaScript API for GPU accelerated 3D rendering. With WebGL 2 support starting to land in browsers, WebGL is becoming a viable medium for producing high performance graphics on the web. Even mobile has fairly good support.

Recently at Gather we had a requirement for the ability to allow users to create thumbnails from 3D models. I thought this should be fairly straightforward. I was very, very wrong. Early prototypes that were built using three.js certainly /worked/, but only supported .obj files. This turned out to be hilariously inadequate. Since the models are CAD files, there is a plethora of different file formats - the spec requires support for 48 different types of models!

 

AutoDesk was an early adopter of WebGL and provides a variety of different services and tools for dealing with CAD files. After some dismayed hunting around the web for solutions to this rendering problem I discovered they provide an API for converting all kinds of different CAD models. It's definitely worth checking out their model viewer demo that showcases some of the abilities of their web based model renderer.

 

But WebGL isn't just for 3D graphics. It's possible to use it to exploit GPU rendering for 2D graphics. The most well known library supporting this functionality is Pixi.js which bills itself as the "fastest, most flexible 2D WebGL renderer". Many sites and libraries use this base functionality for seamless animations on the web as an alternative to straight canvas or the notoriously slow DOM. The game engine Phaser is built on top of this technology.

 

At some point I came across this blogpost about a dancing pythagorean tree fractal implementation in various javascript frameworks. Each implementation is based on the first and they all use SVG as a rendering layer. There are 2048 rectangles that need to be updated each frame. That's a lot of DOM mutation. You can see the jank in performance when the tree grows below;

 

 

I find the fact that people are using this to compare frameworks weird as the bottleneck in performance is the rendering method not the framework. To prove this, I went about reimplementing the results in WebGL using Pixi.js primitives.

 

You can try the live demo here and grab the source code here. This WebGL version performs much better than the SVG one. You can crank up the rendering depth and still achieve good speeds at near 32k nodes.

 

Another advantage of using Pixi.js as a rendering layer is that if you don't have WebGL support, it will fall back to canvas. This will incur a performance drop, but it'll still work - a vital requirement if you wish to use this technology in production applications.

Really, similar performance can be achieved without WebGL or any kind of framework, but there may be some caveats. You can see an explanation of some of the tricks used to make this lo-fi version work here.

 

The data visualisation framework we use at Gather is d3.js. One of the built in visualisation types that d3 provides is a force directed chart for displaying data that has network style nodes with links between them. A common problem with these types of charts that with large amounts of nodes, you will end up with slowdown, regardless of whether you used SVG or canvas in some cases.

 

I went about reimplementing the basic force chart with Pixi.js to see what the performance implications are, and to see how difficult integrating WebGL with d3 might be. If your browser supports WebGL, you should see some graphics hardware accelerated charting below;

 

Upon reading the code, you may be surprised how similar this implementation is to raw canvas. This is a good demonstration of d3's paradigm of sitting on top of web standards and allowing you to switch out the renderer for your own with ease.

 

After stress testing this, I found that performance was similar to canvas with extremely high numbers of nodes. Amoung the features Pixi.js provides, it has graphics primitives for rendering simple things like rectangles, circles, paths, etc. After playing about, I've come to the conclusion that the path primitive may be what is causing the slowdown - or at least my implementation of it. If you compare canvas vs SVG vs WebGL by rendering the nodes only, WebGL comes out as a clear winner. I would love to know if this can be optimised further, as this would give us a great solution for visually busy force directed charts

Author Thom Full Stack Developer

Thom is a web application developer focused on data visualisation and modern tech.