From WebGL to WebGPU in Construct

22
Official Construct Team Post
Ashley's avatar
Ashley
  • 24 Apr, 2020
  • 2,703 words
  • ~11-18 mins
  • 13,006 visits
  • 0 favourites

This is the second in a series of two blog posts about web graphics, WebGPU, and Construct. The first is A brief history of graphics on the web and WebGPU. Have a read of that if you haven't already - this post largely continues on from where that one left off. I'll cover how WebGPU compares with WebGL in practice, what I've learned while adding WebGPU support in our web game engine Construct, and what it means for the future.

WebGPU vs. WebGL

It's useful to just give a rough overview of how WebGPU is different to WebGL. Without going in to too many complex technical details, the overall design of the two are along the lines of:

  • WebGL, like OpenGL, involves lots of individual function calls to change individual settings. There's also more or less one big pile of global settings that you change.
  • WebGPU creates groups of settings the application will use in advance. Then at runtime it can switch between entire groups of settings with a single function call, which is much faster. It also organises all the settings according to how modern GPUs work, allowing applications to work more efficiently with hardware.

A good example of this is also shown in this blog post about WebGPU in Safari. Here's some WebGL code for rendering a single object:

gl.useProgram(program1);
gl.frontFace(gl.CW);
gl.cullFace(gl.FRONT);
gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_MIN);
gl.blendFuncSeparate(gl.SRC_COLOR, gl.ZERO, gl.SRC_ALPHA, gl.ZERO);
gl.colorMask(true, false, true, true);
gl.depthMask(true);
gl.stencilMask(1);
gl.depthFunc(gl.GREATER);
gl.drawArrays(gl.TRIANGLES, 0, count);

Here's the equivalent in WebGPU:

encoder.setPipeline(renderPipeline);
encoder.draw(count, 1, 0, 0);

Note all the equivalent state would have been set in advance for renderPipeline. But you don't need to understand the code to see the point: there's less code to run, so it's faster.

The Mozilla Hacks blog post A taste of WebGPU in Firefox also includes a more technical summary of the differences between WebGL and WebGPU.

There are a lot of benefits that come with WebGPU (many of which are shared with the other new low-level graphics APIs):

  • The API is actually cleaner, simpler and easier to understand. OpenGL's way of doing things was never exactly popular.
  • Applications have much more control over exactly how rendering happens, and can do more to optimise performance.
  • As a new graphics API, it's also much more powerful, and brings new capabilities that were previously difficult or impossible with WebGL, like managing command buffers.
  • Browsers do a lot of validation and security checks, which has a performance overhead. With WebGPU it can do most of that in advance instead of during rendering, reducing the browser overhead.
  • It cuts out a huge amount of complexity and overhead in the graphics driver. Now the driver can be much simpler and smaller, being closer to just forwarding calls to the hardware. This also makes the notoriously awful class of graphics driver bugs much rarer.

In short, it's good for everyone involved - better for application developers like us, better for hardware manufacturers like nVidia and AMD who have an easier job, and better for gamers who get faster performance.

Nothing is perfect though, and there's one big downside: all rendering code has to be completely rewritten to take advantage of this. Not only does this apply to Construct's renderer which has to be rewritten to use WebGPU - it also applies to the entire graphics stack, with the browser's rendering code, the graphics driver code, and probably parts of the OS as well. And that's also after all the tough specification work has been done, working out all the details of exactly how the API should be designed to work across a range of different low-level graphics APIs. This is a huge amount of work across the whole industry, so naturally it will take time for all of this to happen and for these new technologies to finally become broadly available.

WebGPU in Construct

Although WebGPU is still very much under development and is likely a long way off release, experimental versions of WebGPU are available in some browsers. I've been trying it out with Chrome Canary, which has allowed me to prototype an early WebGPU renderer for Construct.

As is often the case with low-level APIs, it can take a lot of code before you get anywhere. It took about 1000 lines of WebGPU code before I finally saw the first sprite ever rendered with WebGPU in Construct!

First sprite ever rendered with Construct in WebGPU

The blending is wrong and when I looked closely the rendering quality wasn't great either, but you can definitely see the image! After some further work it was quicker to make progress, fixing blending, improving rendering quality, and adding a few more features. Before long I had the Ghost Shooter example rendering entirely in WebGPU!

Ghost Shooter demo rendered with WebGPU

This game has very simple rendering requirements, though: it's little more than basic sprites and "additive" blend mode. The code is still very hacky and not production-ready, and there are a wide range of much more complex rendering features to cover - and I anticipate the effect compositor being particularly tricky. (For background on that, see my past blog posts on Construct's effect compositor part 1 and part 2.) And WebGPU itself is a long way from done - the specification team only just agreed on a direction to go in for the shader language, for example. And once the new shader language is specified, developed, and tested, we then have the job of porting 80+ of Construct's existing shader effects to it. So while this is promising early progress, there is still a very long way to go.

Early findings

Having done some initial prototyping with WebGPU in Construct, it looks very promising and is an exciting development for web gaming. Much of this is based on early work with experimental WebGPU support, so is subject to change, but here are a few of my early-on findings.

No more batch queue

One of the key design aspects of WebGPU over WebGL that is important to Construct, is the fact commands can be reordered. To explain why this is significant, I need to briefly explain how the WebGL renderer works internally. When rendering lots of sprites, it batches them together, as my past blog posts have explained. However while doing this it also builds up a big list of co-ordinates of all the sprites to be rendered (the vertex buffer). For performance this list must be sent to the GPU in one go. But the list is not complete until all the sprites have been drawn! So the WebGL renderer instead queues all WebGL commands, delaying them until the list is complete. The process looks like this:

  1. Draw all sprites, building up a big list of co-ordinates, and saving - but not running - all the WebGL commands.
  2. When rendering is done, send the list of co-ordinates to the GPU.
  3. Then run through all the queued WebGL commands.

In many cases this means building up a big queue of commands during rendering, and then at the end of the frame, going through the queue of commands again to actually run them.

The design advantage of WebGPU is that step 2 - sending the list of co-ordinates to the GPU - can be re-ordered. This completely removes the need for the queue! So the process can now look like this:

  1. Draw all sprites, building up a big list of co-ordinates.
  2. When rendering is done, insert "send the list of co-ordinates to the GPU" as the first command.

That's it! Now all the WebGPU commands run after the list of co-ordinates has been sent to the GPU, and we completely removed the need to build our own queue of commands.

This is a big architectural improvement for Construct's renderer. It's both much simpler, making it easier to work with, and much more efficient too.

Better API

As noted above, the actual function calls WebGPU uses are much clearer and better organised. So while being even more low-level, once I got in to the swing of things I've actually found it easier and more obvious to use WebGPU. In particular WebGL's two-step binding model, where you have to actually first "bind" the thing you want to change, and then change it, is clunky and error-prone. WebGPU pretty much does away with that completely, which is a big improvement in itself - and there are similar improvements right across the whole API. Also being much closer to the way the hardware works means I can adjust calls to be the most efficient for the purposes of Construct's rendering, rather than relying on all sorts of complex handling that happens in the graphics driver, which may not be fast, and sometimes may not even be correct.

Promising performance results

I think it's still too early to publish actual benchmark numbers - there's still so much that is yet to change in both WebGPU and my prototype code. However my early performance testing has shown promising results mainly in cases that were previously challenging for WebGL. I designed some rendering "torture tests" that deliberately produce very long lists of draw commands. These in particular are far faster with WebGPU. I suspect this is both because of the removal of the batch queue as I explained earlier, and also because the graphics driver is much simpler so has less overhead too. This could well translate in to meaningful real-world performance improvements, particularly for games which cannot be batched efficiently.

Reasonably good compatibility

An outcome I had hoped to avoid was having to write all our rendering code twice for two renderers. This is what we had to manage for several years when we had both the canvas2d and WebGL renderers. It means lots of extra work checking they render identically, fixing bugs in both, and every time we changed something, having to change it in two places and verify both. It also meant third-party addon developers had to write code for both, too.

It's early on, but it looks like WebGPU is actually close enough to our existing rendering code that it should be possible to support it with the same rendering code, both in our own drawing code and in third-party addons. In short, we have our own renderer class that all Construct code uses, rather than dealing with WebGL directly. The renderer class handles all the details of WebGL internally. It looks like this class should be able to handle WebGPU as an internal change only, avoiding the need to change all the rest of the code in Construct. This is an important point to make it easy to maintain and avoid having to require third-party developers to make changes (which will never happen if they've stopped maintaining their addons).

One significant change is WebGPU will use a different shader language to WebGL. This means all effects will most likely have to be rewritten, and third parties will have to do that too. This is a smaller impact though and should be manageable.

No new features yet

A common question will be "What new features will we get with WebGPU? 😎" The answer is that at first there probably won't be any new WebGPU-specific features in Construct. This is for a two reasons:

  1. Writing a completely new renderer from the ground up with a new technology, testing and verifying it, fixing all the bugs, and managing a gradual roll-out to everyone, is quite enough work already!
  2. With any new renderer-specific features, we have to deal with the question: what happens on renderers that don't support it? For example if WebGPU can do a shiny new effect specific to WebGPU, what happens on devices that only support WebGL? Removing support for WebGL is not an option if there are still many devices out there which only support WebGL. Simply not showing the effect often produces a result that looks wrong, resulting in confused users and players. The same problem would happen if we added a feature specific to WebGL 2 - what about WebGL 1 systems? The easiest solution is to avoid the problem completely, and only implement features that can be done in WebGL 1, and so work everywhere. And several potential new rendering features that we could add can be done in WebGL 1 too - so it's not always necessary to make new features specific to a renderer.

However there is still good news on features with WebGPU:

  • WebGPU is the latest and most modern graphics API. It's possible that in future, particularly advanced new rendering features are added to WebGPU only, and not available in WebGL. Having a WebGPU renderer means we can take advantage of new features if they come up (and we decide diverging from WebGL is worth it).
  • Some WebGPU features make significant performance improvements possible. This kind of "performance is a feature" approach is something we can still take full advantage of, such as the batching improvement described earlier.

Next steps

As mentioned the prototype WebGPU renderer is pretty hacky, but is complete enough to render basic games like Ghost Shooter and Space Blaster. It already takes advantage of major architectural improvements made possible by WebGPU, and early indications are there are promising performance results with the potential for significant real-world improvements for some games. I also found and reported a Chrome bug - unsurprising at this early stage, but reporting issues early helps make sure problems are solved before release.

However there are major omissions, like shader effects not being supported at all yet, and lots of ugly code that needs cleaning up to make production-grade. For the time being I've decided to put further development work on hold, especially since at this point I'd have to write shader programs to go further, and those will in turn have to be rewritten when the WebGPU shader language is finished, which means wasted effort. I will be continuing to follow WebGPU's development closely though, and it will be interesting to see how it evolves further before release. There may yet still be further improvements we can make to Construct as WebGPU matures and gains more features.

I will certainly pick it up again in future, and will open up the WebGPU renderer to wider testing amongst Construct users when it's ready for that. I don't want to commit to any particular time scale since there's so much uncertainty around how WebGPU itself progresses, but I would hope there will be something you can try out some time in 2021.

Conclusion

WebGPU will be a major technology upgrade for Construct. As we've long done, we're following the bleeding edge of the latest web technologies, and will be amongst the first engines to benefit from the improvements that come with it.

Also I can't believe how far the web platform has advanced! We started in 2011 with a software-rendered canvas2d renderer which could eke out 30 FPS on a high-end desktop. I couldn't have even dreamed of something like WebGPU in the browser. I think this goes to show that betting on the web was a great move, and it's still paying off.

WebGPU is a next-generation technology learning from decades of past technologies like OpenGL, fixing many old mistakes, and making the kinds of fundamental improvements that can only be done with a fresh start. Compared to something like OpenGL, WebGPU is better than native-grade. It exceeds technologies like OpenGL, WebGL, and the older generation of DirectX APIs, being faster, more powerful, and better designed. Construct's renderer with WebGPU could even overtake and outperform native engines that have yet to make the jump to the next generation of graphics APIs - but we'd need benchmarks to prove it (and I'd be keen to see some!)

These improvements are all in the pipeline and I'm excited to see how they develop and eventually come to wider release of WebGPU support in Construct. This will take time though - lots of time. So we just need to be patient. But this is a clear sign there is a very bright future ahead for graphics in Construct and on the web.

Subscribe

Get emailed when there are new posts!

  • 4 Comments

  • Order by
Want to leave a comment? Login or Register an account!
  • Is Ashley the John Carmack for 2D game engineering? :)

  • Great article. Pleased to know that you are working so early on it. Awesome. I guess your gamble of html based games was fruitful

    Now the only thing which I am worried about is Cordova. I hope that something releases in future to replace it

  • Like EntityB, I appreciate how Scirra is involved in early web rendering APIs. Let's hope WebGPU has broad, consistent adoption!

  • Very nice summary. And I'm also quite impressed you have started with WebGPU so soon. I always loved how construct is one step ahead with latest web tech. Now you made it 2 steps ahead with these articles!

    On the other hand I have to say I'm a little bit sceptic with WebGPU. If we talk purely about technology itself I have no doubts! Some issues are ahead but those will be solved as always. But as a long term observer I saw it as "Apple didn't finish part of implementation of WebGL 2 with few but significant features, and initiated WebGPU implementation" which was at least confusing. So I hope implementation of WebGPU will be consistent enough over different platforms.