Rojoss

Canvas scaling performance

Recommended Posts

Hey guys, I'm looking for some advice on canvas scaling.
We're working on a PixiJS game and want to have the canvas as big as possible based on a set aspect ratio.

I've got it all working fine but when I started testing it on our benchmark devices I noticed the performance has dropped horribly.
When I scale the canvas using CSS transform the performance is perfect but then the graphic quality is unacceptable.

Does anyone have some advice how to get great scaling on any device (including 4k monitors) while still maintaining performance and good graphic quality?

---

Good performance but bad quality scaling method

const canvas: HTMLCanvasElement = document.getElementById('game-stage') as HTMLCanvasElement;
const width = window.innerWidth;
const height = window.innerHeight;
const ratio = window.devicePixelRatio * 2;

canvas.style.transformOrigin = '0 0';
canvas.style.transform = `scale(${Math.min(width / Constants.FIELD_SIZE.x, height / Constants.FIELD_SIZE.y)})`;

this.app.renderer.resize(canvas.width, canvas.height);

Good quality scaling but bad performance scaling method

const canvas: HTMLCanvasElement = document.getElementById('game-stage') as HTMLCanvasElement;
const width = window.innerWidth;
const height = window.innerHeight;
const ratio = window.devicePixelRatio * 2;

canvas.width = width * ratio;
canvas.height = height * ratio;

canvas.style.width = width + 'px';
canvas.style.height = height + 'px';

if (height / Constants.FIELD_SIZE.y < width / Constants.FIELD_SIZE.x) {
    this.scene.scale.x = this.scene.scale.y = height / Constants.FIELD_SIZE.y * ratio;
} else {
    this.scene.scale.x = this.scene.scale.y = width / Constants.FIELD_SIZE.x * ratio;
}

this.app.renderer.resize(canvas.width, canvas.height);

 

Share this post


Link to post
Share on other sites

It looks like both of your methods force the machine to perform scaling work on every frame, so avoiding the quality vs performance trade-off may be difficult. I prefer to resize the canvas as and resize the canvas content at the time the resize event is received. That way the content is already appropriately scaled when its time to render it in the canvas, so I get both good quality and good performance.

Share this post


Link to post
Share on other sites

@Rojoss  I've seen similar results, where fancy rendering (a lot of text, many sprites/particles, lots of transparency, etc.) to a large canvas (Your ideal display is 4K? dang...) is simply more expensive.

In general, I've tried to pick a middle range where it looks good enough but isn't working on a huge canvas.  E.g. both of your solutions combined:  Internal scaling logic (your scene.scale.x) to scale to a reasonable-sized canvas, and then a CSS scale in addition to go up to the final display.  For instance, on the Xbox One, I'm rendering to a 1280x720 canvas and then style-scaling up to 1920x1080.

Beyond that, I just had to work to optimize my canvas rendering, which is a whole topic on its own, and I would expect PixiJS to be doing that work already.

Another random thought:  In some environments, using a spritesheet canvas above a certain size (2048x2048) crossed some internal browser threshold where suddenly performance went through the floor.  It's been a while since I've dealt with that, so I don't know how common it is or if your environment is susceptible to that kind of thing.

Share this post


Link to post
Share on other sites

I definitely agree with @BobF, whilst lazy evaluation is great for developers it can be bad for perf, particularly where it does not result in doing the least amount of work over a longer period of time. Where possible do these calcs once and just render each frame, obviously rendering a larger area will result in decreased perf but at least you'll limit the amount of calcs to perform that render each tick.

Share this post


Link to post
Share on other sites

@mattstyles and @BobF

At the risk of me looking dumb for asking, but sincerely for my own edification, can you clarify what part of Rojoss's sample is going to be doing more work than necessary every frame?

For starters, I assume this code he's provided is being called once (e.g. in response to a resize event).  Is there a reason to think this code is running every frame?

The first of his two samples has a fancy transform expression - I expect that's evaluated every frame and bad.  That one I can see being problematic (though it's his "good performance" example... :) )

But the second sample just calculates an x and y scale for the scene to use later.  Is there a reason to think this calculation is happening more than once?  Or is the concern that later he'll have to call context.scale(this.scene.scale.x, this.scene.scale.y) once at the start of each frame?  Doesn't everyone use context.scale() at least once per frame in a real game?

I'm not familiar with PixiJS, so my apologies if there's something more subtle here.  Maybe this is all happening in WebGL anyway? :)

Thanks!

Share this post


Link to post
Share on other sites

Thanks for the replies guys, I didn't get a notification again. :(

I also don't fully understand why it'd do scaling on every frame.
Those two examples are only executed on resize which often only happens once at the start of the game.

Could you also go in a little more depth about the solution for that?
I'm quite new to Canvas and html5 game development so it'd be great if you could explain how I would implement it in that way or maybe point me to an article with some more information?

I found an article before that talks about Canvas performance but then I ended up with the transform CSS method which produces unacceptable quality.

Share this post


Link to post
Share on other sites

I think I skimmed it, read Bobs post and assumed that was running all the time, yeah, responding to a resize event and triggering those calcs is all golden.

I've never tried scaling each frame, but I can imagine that calling `context.scale` involves pixi resizing all renderables in the scene/context, which is bad, although it could (and probably does) have a memoization technique for reducing work. Would assume that is where your bad perf might be coming from though.

You could try some tests by resizing all the elements with a scale just once and see if removing the context.scale thing helps (you'll need to make sure your elements are actually scaled up as, obviously, larger sprites are heavier to render so testing against small renderables isn't a reflective test).

Share this post


Link to post
Share on other sites
14 hours ago, Rojoss said:

I also don't fully understand why it'd do scaling on every frame

When a resize event occurs your code runs and uses one or the other of your two methods for adjusting canvas size. The code will usually cause the value of canvas.width to differ from the value of canvas.style.width (and the same for the height). As a result, the machine will now need to scale the content of the canvas to match the actual size of the canvas. That scaling will occur on every frame under the hood (i.e., not in your code).

My suggestion was to try scaling your content once when the resize event occurs, but without using canvas.style.width/height. That way the canvas dimensions will always match the the canvas.style dimensions, so no per frame scaling occurs under the hood. My suggestion involves more coding because now you need to scale the canvas content yourself rather than relying on the setting of canvas.style.width/height values to do it for you, but the performance should be better because the actual work of scaling now only happens once per resize event.

Feel free to correct me if I have misunderstood anything. I have not done canvas content scaling via canvas.style.width/height, so I could easily be missing something.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

  • Recently Browsing   0 members

    No registered users viewing this page.