WebGL vs Canvas performance

I noticed something today. When I run my game on my laptop (Chrome and Safari on Macbook) with WEBGL (set to AUTO which selects WEBGL) gives me horrible performance. FPS goes down towards 16 if I go a bit nuts on the particle generators.

However, switching to CANVAS gives me rock solid 60 fps and smooth experience for a while but then the application comes to a direct halt with the following error message (looping in the console) without any input to the game, i.e. just by idling:

[Error] CanvasRenderingContext2D.save() has been called without a matching restore() too many times. Ignoring save().
render (vendor.js:41118)
render (vendor.js:31459)
render (vendor.js:55109)
render (vendor.js:53365)
step (vendor.js:38578)
step
step (vendor.js:64532)
step
step (vendor.js:37736)

(Sorry for the vendor.js references, map is not working when testing on Safari for me right now)

Any ideas what is going here? I am kind of perplexed why Canvas has so much better performance, and then also not sure why it is crashing all of a sudden - maybe they are tied together but I can clearly see that the fps in canvas mode is higher and much smoother.

The error seems like a bug, but would be difficult to figure out without knowing what you actually render and where exactly it comes from.

As for WebGL being slower, could it be possible your laptop has dedicated GPU, but it’s not enabled on chrome, and the integrated GPU can’t handle it? Or WebGL is being entirely simulated by CPU, or something along those lines. Does it happen for any WebGL content?

I think all scenarios described below are possible, this is a small macbook (2018 model though) and it has notoriously bad performance which is why I tend to use it to check performance.

The render call must be within Phaser as I don’t have any render function in my code.

I also got a fair amount of weird bugs when rendering in Canvas but unfortunately I have not been able to reproduce this in a codepen (not yet anyway).

So, I narrowed down at least one suspect now. On my iPad (2018 model) I get an fps of about 3 frames per second. However, if I remove a mask that I am using then the framerate jumps up to 50. Below is a representation of what I do. Why is masking destroying performance on iOS? Am I doing something stupid here?

let viewPort = new Phaser.Geom.Rectangle(x, y, width, height);

let shape = scene.add.graphics();
shape.lineStyle(0, 0xff0000, 0);
shape.fillStyle(0xffffff, 0);
shape.strokeRect(viewPort.x, viewPort.y, viewPort.width, viewPort.height);
shape.fillRect(viewPort.x, viewPort.y, viewPort.width, viewPort.height);

let mask = new Phaser.Display.Masks.GeometryMask(scene, shape);

this.container.setMask(mask); // Commenting this out solves the issue on iOS

Hi!

I have the very same problem with my game on mobile Safari, and can’t figure out how to fix it while keeping my masks.

Have you come up with any solution for this issue apart from not using masks?

I turned off antialiasing in the game config which gives me enough frame rate to keep the game smooth. I don’t think this is a fix for the mask but rather just a work around to get better fps.

We also added a check, so if mobile then antialias = false because on mobile devices you can hardly tell the difference due to the high pixel density on the small screen anyway.

One thing we did do was to use only one mask instead of five as originally. The issue is still there of course but it got a bit better. Using bitmap instead of geometry made no difference we could observe.

Everytime you draw to a graphics object, it is something that webgl must draw. In other words, you are are drawing 3 shapes in the above example every frame, and they aren’t getting cleared out of your memory or essentially memory leak.

Try shape.clear() or container.clearMask() before you call this everytime and then try your FPS.
https://photonstorm.github.io/phaser3-docs/Phaser.GameObjects.Graphics.html#clear__anchor
https://photonstorm.github.io/phaser3-docs/Phaser.GameObjects.Graphics.html#clearMask__anchor

edit: thinking more on this, you are probably losing the reference to shape if you are running the above code in update loop. you should store a reference to it either via this.shape or create the graphics object outside of this function in a more global scope to have a reference to it to clear it, or possibly create it in create method, but make draw method occur only during update loop (which also will be cleared once per frame).

I’ve end up not using dynamically changing graphics objects and no masks at all for canvas renderer.

If I used graphics, before using, I rendered it to a texture. Used cropping instead of masking.

But that was the issue for CANVAS renderer on Safari (mobile and desktop) only.

Hi!
We had a similar problem and we gave up using masks completely, instead of masks we use cameras. The camera gives a similar effect as the mask, but it is more difficult to use.