Phaser Dev Log - October 2022

Oct 13, 2022

Greetings everyone! It's time for a brand-new Phaser Dev Log. We've got details about the new Beta release, several new games to check out, and tutorials, too. There's also an important piece of news at the very end. So, let's dive right in ...

Phaser 3.60 Beta 12

This week I published Phaser v3.60 Beta 12 to GitHub and npm. This is a very significant release, and I would urge everyone to try it if they can.

I spent a good while testing and debugging the renderer on mobile devices. Going right back to Phaser 3.22 I started comparing draw calls and gl ops, noting the impact it had on performance.

It finally led me to the conclusion that the renderer was being tripped up by the GPU blocking process that is bufferSubData. This, combined with the cost of the multi-texture shader, was utterly tanking mobile performance.

So I changed the approach the WebGL Pipelines took with regard to handling textures and moved to a hybrid batch system, similar to the way it worked in 3.22 but with all of the improvements I had made since then. And lo and behold, things got real, real fast!

I implemented a new Mobile Pipeline, which you can now flag from the game config or just let Phaser auto-detect the need for it, and it makes a crazy difference to frame rates, as I'll demonstrate shortly.

Phaser will check to see if the device is iOS or Android and invoke the new Mobile Pipeline automatically. You can prevent this by using the game config. You can now also set the default WebGL Pipeline which all Game Objects will be assigned via the new config flag. If this value is set, it overrides the automatic mobile detection, allowing you to have much more fine-grained control over which devices are considered mobile or not.

I also modified the way in which the maxTextures option works, and it now creates a much cleaner shader. It also caps itself at 16, so even if a device returns a crazy high number, it won't ever iterate beyond 16 texture lookups. The new config options are below:

They are all optional, you don't need to specify any of them, but they are there if you want better control.

What difference does the new batching system actually make?

After extensive testing and the help of several community members, I can confirm that Beta 12 (and 11) improves mobile rendering performance extensively, beyond the speed we had with 3.22. Even desktop performance benefits, where the multi-texture shader takes advantage of the new batch approach to help cut down on data buffering, and the results are off the charts.

First, I created a test which you can see here (note that this location will change when 3.60 goes live). If you click on the right of the test, it'll add another 64 texture binds into the batch. If you click on the left-hand side, it'll add 1,024 of them.

It's important to understand what is being shown here. This isn't a test to see how many sprites we can draw. It's a test to see how many texture binds and draw calls WebGL can handle under the new pipelines. Every single pixel in the color strip is actually a unique texture:

As you can see from the image above, each pixel is a texture. Not a frame or part of an atlas, but a unique, if small, texture. The test was constructed like this so we could stress test just how many texture binds WebGL could handle without first hitting fill-rate issues. Had we used large textures instead then it would have adversely impacted the results.

As I mentioned, it's important to remember that for this test it's the quantity of texture binds that matters. The higher the number, the better.

For example in an actual game, you may have a sprite sheet for your player in one PNG and then another for some enemies in a different PNG. Those are 2 unique texture binds. They may contain hundreds of frames in total, but as far as WebGL is concerned, it's all part of the same underlying texture. As the renderer flows through the display list, it can see that a different texture is being used, so it binds and activates the new one, allowing the shader to draw the vertices using the correct texture. This is why using texture atlases is so important. The more on-screen objects that share an atlas, the less texture binds and draws take place.

With Phaser v3.50 we introduced multi-texture batching. This allowed us to batch together typically 16 unique textures and then draw them all in a single draw call. The total depends on the GPU and device, but the WebGL spec guarantees at least 8 unique textures and most GPUs allow for 16 or 32. The problem was that when we introduced this feature, it caused a lot of data buffering to take place, which on some GPUs, is a blocking process. You could see this most clearly on iOS, where the frame rate would tank with just a few textures being used.

Previously Phaser v3.24 (and earlier) took a different approach. Here, it would only ever bind 1 single texture (texture unit zero), and it would also batch together all of the vertex data. Using a series of internal arrays and objects allowed it to flow through bunches of 16 textures, activating them and drawing as required. This resulted in lots of draw calls but minimal data buffering. As it turns out, mobile GPUs really like this approach, whereas desktop does not.

The new v3.60 approach uses one single-bound texture but does away with the enforced 16-texture limit. As a result, this stopped all of the sub-data buffering, removing the blocking process from the rendering step and also allowing for any number of textures internally. This meant I could get rid of lots of texture assignment and comparison code from the game step. The results speak for themselves:

In the chart above I tested the new batch demo across multiple hardware devices and 3 different versions of Phaser. I kept adding binds to the test until it could no longer maintain 60fps in the browser. This gave me the maximum quantity of texture binds the device could handle for that version of Phaser.

As you can imagine, the higher the number, the better.

For iOS hardware, the chart above speaks volumes. On an iPhone SE, for example, Phaser v3.24 could handle 5,248 texture binds at 60 fps. This dropped dramatically to just 192 texture binds under v3.50. But the 3.60 version? 15,104 texture binds at a rock solid 60 fps. A 187% increase from v3.24 and a 7,766% increase over v3.55. That's a dramatic improvement, and I'm sure you can agree.

It didn't improve just on mobile, though:

Here we can see the same test on an M1 iMac and a Windows 11 PC running an NVIDIA RTX 3080 Ti, both powerful bits of kit but by no means 'cutting edge' any longer. On Chrome on the PC, you can see that v3.24 managed 14,336 textures at 60 fps. This improved to 32,000 under v3.55 but on v3.60 we get 35,072. The story is similar, although a lot slower (as always) in Firefox, which suffers from a less optimized WebGL implementation than Chrome.

On the iMac, Phaser v3.24 rocked along nicely at 15,872 textures at 60 fps. Dropping to a paltry 2,560 under v3.55 and increasing to an astonishing 42,240 texture binds under v3.60. If you don't need to maintain 60 fps (i.e., for a much more static puzzle game), then you can push this even higher. M1s really are a beast.

Desktop GPUs, in most cases, had a real improvement from v3.24 to v3.55. The internal improvements in v3.60 push it even further while also improving mobile speed no-end. As far as I'm concerned that's a win-win for everyone.

Will you ever actually need 42,240 unique texture swaps per frame for your game? Well, it's doubtful. You should really be using texture atlases and minimizing the amount of swapping going on. Yet it's nice to know that this power is there, under the hood, should you require it. And the removal of the buffer data calls is what caused the majority of the speed-up, and that's a benefit for everyone.

A note about testing

One important thing to mention is that if you use Spector.js to test your game, or anything WebGL-related, you should be aware of its impact on performance. 

Don't get me wrong, Spector is a lifesaver. Every developer should have it installed in their toolbelt and use it often. Just be mindful of how much cost it takes.

For example, one of my tests was handling 45,824 texture binds and 2,864 draw calls at a smooth 60 fps with Spector disabled (i.e., the icon is red). With it enabled, the exact same test could only manage 1,792 texture binds and 112 draw calls.

Spector wasn't even running (i.e., analyzing a frame). It was just sat in memory waiting to be used, but of course, it still applied all of its hooks into the WebGL operations, and this slowed down the test dramatically.

Always, always have Spector available. And always, always disable it when you want to production test your game performance!

Dynamic Textures

Another new feature in v3.60 Beta 12 is Dynamic Textures. A Dynamic Texture is a special kind of texture that you can 'draw' to. You can draw Game Objects to it, or shapes, or fill it, or erase parts of it. Then, you can use it as the texture for other Game Objects. It's the perfect way to create dynamically generated textures at run-time.

Now, if this sounds an awful lot like the Render Texture Game Object, you'd be right! Dynamic Textures are the evolution of that. Previously, if you wanted to generate a texture to be used by Sprites, you'd have to create a Render Texture, make sure it wasn't on the display list, and then 'save' it to the Texture Manager. 

This all just felt a little backward to me.

Now, rather than go through all of this, you create Dynamic Textures directly from the Texture Manager:

Here we're creating a 512 x 512 texture called 'playerBanner'. It gets stored in the Texture Manager, so it's available globally across all Scenes, and it returns a reference to it, so we can call the methods it has available.

One of these new methods is called 'stamp.' It allows you to stamp a texture frame anywhere on the Dynamic Texture but also provides a load of optional additional properties, such as setting the alpha, the blend mode, the scale, the origin, and more. For example, let's stamp a flag and a random crest to our player banner:

The first line just stamps the green flag texture frame at 256x256, which is the center of our texture. The rest of the code picks a random crest from the texture atlas and stamps that with an alpha and a blend mode.

Finally, let's stamp 3 runes across the top of it:

Our texture is assigned to a Sprite and let's run it:

Here's our nice randomly generated player banner, created from parts of a texture atlas and all stamped and bound on a single new texture. Using this same code, we can generate a number of different banners, say one for each player:

And as these are just sprites, anything you do with the sprite is reflected here, too, such as scaling them, tweening them, and so on.

You can run the demo here. Refresh it to get different banners.

Dynamic Textures are native 'Phaser.Texture' instances, too, just with lots of added juice. This means you can assign them to any texture-based Game Object, but more importantly, you can easily add Frames or use them in the texture parsers. For example, it's now easy to create Sprite Sheets or Texture Atlases using a Dynamic Texture as the base.

Internally, Dynamic Textures are a lot cleaner than Render Textures. I managed to remove a lot of code that was specific to them being Game Objects. Most importantly of all, they use 50% less memory, too!

Previously in WebGL, a Render Texture would create a Canvas, the size of the texture, and also a WebGL Texture. After refactoring and turning them into real texture instances, this was no longer required. This means, under WebGL, if you previously used Render Textures you'll get the exact same features from Dynamic Textures but use 50% less memory in the process, and they are twice as fast to create, as well, in terms of CPU use.

In order to maintain compatibility, Render Textures are still available. These now just consist of an Image Game Object that has a Dynamic Texture as its texture. There are lots of proxy methods to keep the API virtually the same, so in most cases, you won't need to change much, if any, code. However, if you were only ever using Render Textures as a source of texture creation, swap to Dynamic Textures and benefit from easier and cleaner code :)

There are lots more changes to be found in Beta 12, but these are two of the most exciting and important by far.

Phaser v3.60 is very close to being finished. There isn't much more I want to do with it before we can release it. The more testing that can take place, though, the better. 

So please grab it, please do smash it around, and post your findings onto GitHub or Discord. However, I am not prepared to give a release date, and at the end of this Dev Log, you'll find an important announcement regarding this.

Fruity Match

Fruity Match is a new game by Daniel Albu. It's a simple take on the match-3 formula, with cute bright graphics and some suitably crunchy sound effects! How many fruits can you match in 1 minute?


HTML to APP is a new website that will convert your HTML5 project to a mobile Android or iOS app. This is done using Capacitor on the back end, and the process is entirely free.

There is a YouTube video showing how you can convert your Phaser games to a mobile app, and a template is provided on GitHub.

So, if you want to test out your game on mobile, but don't feel like playing around with Cordova or Capacitor, give this a bash and make sure you leave your feedback on their GitHub Discussions group.

Bundling Up Your Phaser 3 Games Using Parcel

I have to admit. I'm really not a fan of the Parcel bundling tool. It's superb for websites but leads beginners down some dark paths when it comes to bundling games. We frequently get issues arising from the use of it in the Phaser Discord, and those are just the ones who bothered to ask questions. I'm sure there are many who just quit outright.

Thankfully, Kevin Potschien has written a tutorial on how to bundle your Phaser 3 game using Parcel. It goes through the configuration process and explains how to handle the folder structure, static asset plugins, and more.

Infinity Dungeon

Welcome adventurer to the Infinity Dungeon! This place contains fabulous treasures but has become the lair of terrible creatures. To survive, you'll have to use your food wisely. Each of your moves requires 1 food. If you run out of food, you lose your hit points, and then your adventure will be over. Will you get out of the dungeon alive?

In this fun and comprehensive rogue-like, you can charge around the dungeon, slashing and shooting monsters, stealing their treasure, and hopefully coming across an apple or chicken drumstick! There are spells that increase your damage, items to collect and combine, and abilities that regenerate based on how much food you consume.

Definitely give it a play for a nice twist on a classic.

Hathora - Multiplayer Framework and Cloud Hosting

The Hathora team has been really busy recently. Their plan is to create a complete multiplayer game framework and all of the cloud hosting infrastructure to go with it. This consists of Hathora Builder, which is a NodeJS framework for the backend, Hathora BuildKit, which is a lightweight SDK that sits on top of their protocol and Hathora Cloud, which allows for a managed hosting platform to scale and run your game on.

Hathora provides plenty of example games using this technology created in Phaser, so you should absolutely take a look at their tutorials and docs site to see what they have on offer.


Astrogon, by Kosmoon Studio, is a hardcore precision platformer with creative mode, multiplayer versus, first competitive of the kind, with speedrun and international leaderboard. Jump and dash your way between walls and obstacles, to reach the teleport portal.

Check out this great YouTube trailer for the Steam release.

It's now available on Steam, Google Play, and the App Store and it looks great! Clearly, a lot of love and care has gone into this Phaser game, so it'd be nice to see it do well.

Kreso & Bubble Trouble - a 20-year Tale

"Kresimir Cvitanovic (a.k.a Kreso) is a Croatian game developer most notable for the hit web series “Bubble Trouble”. Having started coding websites at a young age, he went on to create the series just as he was starting college in 2002. 3 games and nearly 20 years later, support for Flash ended in 2020 leaving the future of the games uncertain. Converting the games from Flash to Phaser was no easy task but two years later, BT3 has been converted to HTML5 and joins its two predecessors on Poki. Now with over 5 million plays just in the last year, players are reliving the good ‘ol days."

Read the rest of this great interview.

Closing Thoughts

I was in two minds about if I should mention this or not. But I felt it was important and is likely to impact the short-term development of Phaser.

Last week, my sisters and I found out that our beautiful mother is riddled with cancer. This was a massive shock to us all, yet the tests were conclusive, and the prognosis wasn't good. They gave her weeks to live, not months, and she isn't even that old. It's not particularly easy for me to sit here and write this. Although to be honest, doing a bit of work is a nice distraction for my mind.

What it does mean is that I'm going to be occupied quite heavily with family time. We need to create as many final happy memories with her as we can - while we still can. I will keep on working on Phaser in between this, as like I said, it's nice to put my mind in a different zone. But if commits to the repo or responses on Discord take a little longer in the coming weeks, this is the reason why. I will also not be scheduling any support calls during this time.

I still want to release Phaser v3.60 by the end of the year. Just please appreciate that right now I have other priorities and I'm not going to beat myself up if this doesn't happen.

Cherish your family and loved ones. Code can always wait.

Become a patron to

Unlock 52 exclusive posts
Be part of the community
Connect via private message