Graphics technologies have changed a lot over the years, including on the web, and the web is on the cusp of its next big change with WebGPU on the horizon. In this first blog post in a series of two, I'll cover a brief history of graphics APIs and what's in store for the future. I'll also focus on Construct - our leading web game engine - and how it's evolved with these changes over the years too.
A few notes on terminology
Construct's renderer is the code that draws all the graphics to the screen - also known as rendering. When it comes to rendering, most modern devices have two chips in them which are involved:
- The CPU (Central Processing Unit), a general-purpose chip that runs the main application logic
- The GPU (Graphics Processing Unit), which is specially designed to handle rendering graphics really quickly.
In general the application logic on the CPU builds up a list of rendering instructions, and then sends these to the GPU to execute quickly, so both are involved in rendering. These days GPUs are used for other specialist purposes like AI too. Broadly speaking, CPUs are designed to run serial instructions (one-after-another) efficiently, which is typical of application code, and GPUs are designed to run parallel instructions efficiently, which is typical of rendering (e.g. filling all pixels in an area the same way). Some devices have both the CPU and GPU on the same physical chip, also known as integrated graphics, but the same purposes apply.
The CPU can also render graphics by itself, but this is usually very slow as the chip isn't specially designed for it, so wherever possible the GPU is used instead. Drawing on the CPU is called software rendering, and drawing on the GPU is called hardware acceleration.
It's also worth mentioning that before Construct 2 I had plenty of experience working with C++ and DirectX 9 on Windows while working on Construct Classic. So coming from that, I knew what it took to build a native-grade high performance renderer for games.
The early web
Historically, it wasn't generally possible to achieve high-performance graphics on the web. Browsers generally used software rendering so tended to be very slow with graphics. They also lacked appropriate features, meaning games would have to resort to inefficient hacks like moving HTML elements around. Flash was the best available option, and so became the de-facto web gaming technology.
Construct 2 was first released in February 2011, around the time HTML5 was taking off. The key feature of HTML5 that made games possible was the <canvas> element. This provides an area of the screen that you can draw images to, which is exactly what you want for a game.
At first it only supported the "2d" context (often referred to as canvas2d), which essentially provides a basic "draw image at position" capability. It had pretty limited features, and was not as fast as other lower-level graphics APIs like DirectX or OpenGL. The main reason for that was around how efficiently the CPU can issue drawing commands to the GPU. With canvas2d all you can do is repeatedly say "draw this image at that position" (via drawImage), requiring lots of function calls with lots of performance overhead. The key to achieving good rendering performance is the ability to batch commands together - meaning being able to have a single command that says "draw these images at these positions". This creates lots of work for the GPU to do at once, which is ideal for a massively parallel processor. But more on that later.
Even canvas2d was software rendered at first, but pretty quickly browsers added hardware acceleration since making fast games - and getting rid of plugins like Flash - was on the browser maker's minds. Still, this was enough for Construct 2 to launch with its canvas2d renderer, being one of the first engines to provide a reasonably performant HTML5 game engine without needing any plugins at all.
I can't resist a bit of nostalgia, but at the time we assumed this would only ever work on desktop. Mobile devices in 2011 were very weak by today's standards, both in hardware and software. The idea of running high-performance games in mobile browsers was unimaginable at the time. We've come a long, long way!
The Next Big Thing in web graphics was WebGL, which appeared surprisingly quickly after <canvas>. WebGL is essentially OpenGL for the web (OpenGL ES 2.0, strictly speaking). OpenGL is a low-level native-grade graphics API similar to DirectX. If you are serious about high-performance graphics with impressive effects, it's a must. So WebGL was clearly an essential feature for a web game engine.
WebGL first appeared in Chrome 9 and Firefox 4, both released in early 2011. In November that year we released our first support for WebGL rendering in Construct 2 r68. Our initial benchmarks showed it was 4x faster than canvas2d, later increasing to more like 10-20x faster - a huge improvement! It also had far more features and support for beautiful shader effects. WebGL allows us to produce sophisticated batched commands, such as drawing hundreds of sprites in one go, which is why it's so much faster. Back in 2014 I wrote a blog post on How the Construct 2 WebGL renderer works which despite being quite old, is still a pretty good overview of the details of optimising WebGL rendering and how batching works. It's useful background reading to help you understand the rest of this blog series.
Full browser support for WebGL took a few more years. It wasn't until iOS 8 added WebGL in 2014 that all desktop and mobile browsers had support. Also at first not even all desktop systems supported WebGL, and IE11 had added WebGL in 2013 but was taking years to roll out to everyone. So we had to support both canvas2d and WebGL in Construct for many years. In fact we only really fully removed canvas2d when we launched the C3 runtime in late 2018 - which requires WebGL and has removed all canvas2d code. By then WebGL was ubiquitous so it was no real loss, and greatly simplified our codebase too, allowing us to focus on one renderer. (The old canvas2d code is actually still hanging around in the C2 runtime in Construct 3! But the C2 runtime will be retired in July 2021 along with Construct 2 itself, when after a decade we'll finally say goodbye to our ancient canvas2d code.)
WebGL 2 is an update that improves WebGL's capabilities to be equivalent to OpenGL ES 3.0 (where WebGL 1 is equivalent to OpenGL ES 2.0). WebGL 2 adds loads more features to the API, but in the context of Construct, it's a minor update that just lifts some minor restrictions and allowed us to tweak some parts of the engine to be a bit more efficient. It's not really that important an update for 2D games, and is probably much more important for 3D games.
WebGL 2 first arrived in 2017 with Chrome 56 and Firefox 51. It never got added to Internet Explorer or Edge, until Edge switched to the same browser engine as Chrome (which only got released this year). Mystifyingly, Safari has to this day still not added support for WebGL 2. It appears to still be in development though, but it's not really especially important, since as noted it's only a minor update for Construct. Meanwhile Apple do appear to be actively working on WebGPU - an even more advanced API - so it'll probably matter even less in the long run, but more on that later.
Since adding support for WebGL 2 in Construct only meant making a few changes, it's still basically the same renderer. Most modern devices support WebGL 2, but if it's not supported, Construct switches back to WebGL 1, and everything largely works identically. It's hard to come by good numbers but probably something like 60%-80% of devices support WebGL 2, so there's still quite a significant use of WebGL 1, but unlike with canvas2d it's very little work to keep supporting that.
We've caught up to where we are today: using WebGL 2 or WebGL 1 for high-performance graphics in Construct. However the tech continues to improve, and there is now WebGPU in the works to provide even faster and more powerful graphics technology for the web! But to understand what's exciting about WebGPU, we have to review a brief history of native graphics technologies too.
OpenGL, the cross-platform low-level high-performance graphics technology that WebGL is based on, is very old. It was originally developed in the early 1990s. In computing terms, that's ancient! Modern GPUs actually work very differently to how they did back then - but many core concepts of OpenGL had not changed. Consequently, applications were basically using OpenGL like it's the 90s, and then the graphics driver - the software between the application and the hardware - would convert that in to something that can actually run on the GPU.
As GPUs became more complex and powerful, the graphics driver ended up having to do a lot of extremely complex work. This made graphics drivers notoriously buggy, and in many cases slower too, since they have to do all this work on-the-fly. A similar fate had befallen DirectX, although probably to a lesser extent, since Microsoft had the power to make major upgrades at a couple of points through its life. OpenGL was particularly lumbered by decades of backwards-compatibility.
What can't be fixed can still be replaced. As a result Khronos, the group behind OpenGL, came up with an all-new, completely redesigned modern graphics API: Vulkan, released in 2016. It's even more low-level, faster, simpler and a much better match for modern hardware.
However it also meant applications had to completely rewrite all their graphics code to support it. This kind of tectonic shift in technology takes years to play out, and as a result there's still a lot of OpenGL out there.
Whilst Vulkan is designed to be a standard API that is able to work on all systems, as has long been the case with standards, Apple also came up with Metal for iOS and macOS and Microsoft also came up with DirectX 12 for Windows and Xbox. Both are more or less the same idea as Vulkan: new, lower-level APIs that throws out all the historical baggage and starts with a clean slate design that much more closely matches how modern hardware works.
With the graphics world moving on to this new generation of APIs, the question was then what to do on the web. WebGL is basically OpenGL with many of the same pitfalls, and high-performance web game engines like Construct's still stand to benefit a lot from the new generation of graphics APIs.
Unfortunately unlike OpenGL, Vulkan has run in to trouble getting true cross-platform reach due to Apple. iOS and macOS only support Metal and have no official support for Vulkan, although there are third-party libraries for it. Further, even Vulkan is not really suitable for the web: it's just too low level, even dealing with minutiae like GPU memory allocators so that AAA game engines could extract the maximum conceivable performance. Not all of this is appropriate for the web platform, and security is also a much more significant concern in browsers.
So the solution was an all-new API designed specifically for the web, high level enough to be usable and secure in a browser, and able to be implemented on top of any of Vulkan, Metal and DirectX 12. This is WebGPU, and it seems to be the only truly cross-platform, modern and low-level graphics API. It's still in development, but all major browser vendors are on-board - including Apple - and working on experimental implementations. And I've been prototyping a WebGPU renderer for Construct! But more on that next time.
That covers the history of graphics technologies on the web, up to recent progress with WebGPU. Note that WebGPU is still very much an experimental technology under active development. There's lots more work to be done before it gets anywhere near release, so don't hold your breath.
I didn't want to end up with too epic a blog post, so I've split the second part of this blog in to a follow-up post: From WebGL to WebGPU in Construct. This continues on from this blog, covering how my experiments with WebGPU support in Construct have gone, what I've learnt while porting our engine to support WebGPU, and how it compares to WebGL. So take a look for the next part of this series!