HTML5 Canvas save() and restore() performance - html

So the issue that I'm having is that in developing an HTML5 canvas app I need to use a lot of transformations (i.e. translate, rotate, scale) and therefore a lot of calls being made to context.save() and context.restore(). The performance drops very quickly even with drawing very little (because the save() and restore() are being called as many times as possible in the loop). Is there an alternative to using these methods but still be able to use the transformations? Thank you!

Animation and Game performance tips.
Avoid save restore
Use setTransform as that will negate the need for save and restore.
There are many reasons that save an restore will slow things down and these are dependent on the current GPU && 2D context state. If you have the current fill and/or stroke styles set to a large pattern, or you have a complex font / gradient, or you are using filters (if available) then the save and restore process can take longer than rendering the image.
When writing for animations and games performance is everything, for me it is about sprite counts. The more sprites I can draw per frame (60th second) the more FX I can add, the more detailed the environment, and the better the game.
I leave the state open ended, that is I do not keep a detailed track of the current 2D context state. This way I never have to use save and restore.
ctx.setTransform rather than ctx.transform
Because the transforms functions transform, rotate, scale, translate multiply the current transform, they are seldom used, as i do not know what the transform state is.
To deal with the unknown I use setTransform that completely replaces the current transformation matrix. This also allows me to set the scale and translation in one call without needing to know what the current state is.
ctx.setTransform(scaleX,0,0,scaleY,posX,posY); // scale and translate in one call
I could also add the rotation but the javascript code to find the x,y axis vectors (the first 4 numbers in setTransform) is slower than rotate.
Sprites and rendering them
Below is an expanded sprite function. It draws a sprite from a sprite sheet, the sprite has x & y scale, position, and center, and as I always use alpha so set alpha as well
// image is the image. Must have an array of sprites
// image.sprites = [{x:0,y:0,w:10,h:10},{x:20,y:0,w:30,h:40},....]
// where the position and size of each sprite is kept
// spriteInd is the index of the sprite
// x,y position on sprite center
// cx,cy location of sprite center (I also have that in the sprite list for some situations)
// sx,sy x and y scales
// r rotation in radians
// a alpha value
function drawSprite(image, spriteInd, x, y, cx, cy, sx, sy, r, a){
var spr = image.sprites[spriteInd];
var w = spr.w;
var h = spr.h;
ctx.setTransform(sx,0,0,sy,x,y); // set scale and position
ctx.rotate(r);
ctx.globalAlpha = a;
ctx.drawImage(image,spr.x,spr.y,w,h,-cx,-cy,w,h); // render the subimage
}
On just an average machine you can render 1000 +sprites at full frame rate with that function. On Firefox (at time of writing) I am getting 2000+ for that function (sprites are randomly selected sprites from a 1024 by 2048 sprite sheet) max sprite size 256 * 256
But I have well over 15 such functions, each with the minimum functionality to do what I want. If it is never rotated, or scaled (ie for UI) then
function drawSprite(image, spriteInd, x, y, a){
var spr = image.sprites[spriteInd];
var w = spr.w;
var h = spr.h;
ctx.setTransform(1,0,0,1,x,y); // set scale and position
ctx.globalAlpha = a;
ctx.drawImage(image,spr.x,spr.y,w,h,0,0,w,h); // render the subimage
}
Or the simplest play sprite, particle, bullets, etc
function drawSprite(image, spriteInd, x, y,s,r,a){
var spr = image.sprites[spriteInd];
var w = spr.w;
var h = spr.h;
ctx.setTransform(s,0,0,s,x,y); // set scale and position
ctx.rotate(r);
ctx.globalAlpha = a;
ctx.drawImage(image,spr.x,spr.y,w,h,-w/2,-h/2,w,h); // render the subimage
}
if it is a background image
function drawSprite(image){
var s = Math.max(image.width / canvasWidth, image.height / canvasHeight); // canvasWidth and height are globals
ctx.setTransform(s,0,0,s,0,0); // set scale and position
ctx.globalAlpha = 1;
ctx.drawImage(image,0,0); // render the subimage
}
It is common that the playfield can be zoomed, panned, and rotated. For this I maintain a closure transform state (all globals above are closed over variables and part of the render object)
// all coords are relative to the global transfrom
function drawGlobalSprite(image, spriteInd, x, y, cx, cy, sx, sy, r, a){
var spr = image.sprites[spriteInd];
var w = spr.w;
var h = spr.h;
// m1 to m6 are the global transform
ctx.setTransform(m1,m2,m3,m4,m5,m6); // set playfield
ctx.transform(sx,0,0,sy,x,y); // set scale and position
ctx.rotate(r);
ctx.globalAlpha = a * globalAlpha; (a real global alpha)
ctx.drawImage(image,spr.x,spr.y,w,h,-cx,-cy,w,h); // render the subimage
}
All the above are about as fast as you can get for practical game sprite rendering.
General tips
Never use any of the vector type rendering methods (unless you have the spare frame time) like, fill, stroke, filltext, arc, rect, moveTo, lineTo as they are an instant slowdown. If you need to render text create a offscreen canvas, render once to that, and display as a sprite or image.
Image sizes and GPU RAM
When creating content, always use the power rule for image sizes. GPU handle images in sizes that are powers of 2. (2,4,8,16,32,64,128....) so the width and height have to be a power of two. ie 1024 by 512, or 2048 by 128 are good sizes.
When you do not use these sizes the 2D context does not care, what it does is expand the image to fit the closest power. So if I have an image that is 300 by 300 to fit that on the GPU the image has to be expanded to the closest power, which is 512 by 512. So the actual memory footprint is over 2.5 times greater than the pixels you are able to display. When the GPU runs out of local memory it will start switching memory from mainboard RAM, when this happens your frame rate drops to unusable.
Ensuring that you size images so that you do not waste RAM will mean you can pack a lot more into you game before you hit the RAM wall (which for smaller devices is not much at all).
GC is a major frame theef
One last optimisation is to make sure that the GC (garbage collector) has little to nothing to do. With in the main loop, avoid using new (reuse and object rather than dereference it and create another), avoid pushing and popping from arrays (keep their lengths from falling) keep a separate count of active items. Create a custom iterator and push functions that are item context aware (know if an array item is active or not). When you push you don't push a new item unless there are no inactive items, when an item becomes inactive, leave it in the array and use it later if one is needed.
There is a simple strategy that I call a fast stack that is beyond the scope of this answer but can handle 1000s of transient (short lived) gameobjects with ZERO GC load. Some of the better game engines use a similar approch (pool arrays that provide a pool of inactive items).
GC should be less than 5% of your game activity, if not you need to find where you are needlessly creating and dereferencing.

Related

Proper using of scene2d's Stage in a game with a huge world

If the whole "game world" is thousands of times wider than a viewport, and if I want to use scene2d to manage game objects as Actors, should I create Stage object as wide as the whole world, or should the Stage be some area around current viewport but not the whole world?
In other words, does a Stage with greater width and height consume more memory itself, even if I render objects only on a small viewport-sized part of it?
I think you misunderstood what exactly a Stage is. A Stage doesn't really have a size itself. You don't specify a width or height or the Stage, you only specify the width and height of the viewport. The viewport is like a window, which shows only a part of your world, aka scene. A Stage is a 2D scene graph and it "grows" with your Actors. The more Actors you have, the bigger (memory wise) your Stage is, but it doesn't depend on how far spreaded your Actors actually are. If they are very far spreaded and you only display a very small part of your whole Stage, it will be handled very efficient, because a scene graph sub-divides this huge space to be able to very quickly decide whether to ignore a certain Actor, or draw it on the Screen.
That means a Stage is actually exactly what you need for this kind of situation and you should probably not have any problems, FPS and memory wise. But of course if your Stage is 1000s of times the size of your viewport and you know yourself that certain Actors aren't displayed soon, then it might make sense to not add them to the Stage yet.
A stage is only a root node that will hold all the actors. It's role is to call methods for its children (like draw and act); thus only the number and complexity of actor have an impact on memory and frame rate.
For your situation a culling method is certainly required. The simplest one would be to check if an actor is in the viewport and if not skip drawing him. Create a custom actor and add this code: source
public void draw (SpriteBatch batch, float parentAlpha) {
// if this actor is not within the view of the camera we don't draw it.
if (isCulled()) return;
// otherwise we draw via the super class method
super.draw(batch, parentAlpha);
}
Rectangle actorRect = new Rectangle();
Rectangle camRect = new Rectangle();
boolean visible;
private boolean isCulled() {
// we start by setting the stage coordinates to this
// actors coordinates which are relative to its parent
// Group.
float stageX = getX();
float stageY = getY();
// now we go up the hierarchy and add all the parents'
// coordinates to this actors coordinates. Note that
// this assumes that neither this actor nor any of its
// parents are rotated or scaled!
Actor parent = this.getParent();
while (parent != null) {
stageX += parent.getX();
stageY += parent.getY();
parent = parent.getParent();
}
// now we check if the rectangle of this actor in screen
// coordinates is in the rectangle spanned by the camera's
// view. This assumes that the camera has no zoom and is
// not rotated!
actorRect.set(stageX, stageY, getWidth(), getHeight());
camRect.set(camera.position.x - camera.viewportWidth / 2.0f,
camera.position.y - camera.viewportHeight / 2.0f,
camera.viewportWidth, camera.viewportHeight);
visible = (camRect.overlaps(actorRect));
return !visible;
}
If you need to improve performance even further you can switch to manually deciding what is visible and what not (ex when moving the camera). This would be faster because all those culling calculations are executed at EVERY frame, for EVERY actor. So although it's a lot faster to do some math instead of drawing, a big number of actors would give a huge amount of unwanted calls.

Speeding up a canvas image crop

I'm working on a simple image crop where the user draws a line with the mouse around an area that they want to keep. When they confirm, the rest of the image will be cropped out. Here's how I'm currently handling said cropping:
var data = c.getImageData(0,0,canvas.width,canvas.height);
for (var x = 0; x < data.width; x++) {
for (var y = 0; y < data.height; y++) {
if (!c.isPointInPath(x,y)) {
var n = x + (data.width * y);
var index = n*4;
data.data[index+3] = 0;
}
}
}
However, this can bog down really quickly. The less of the image you try to retain, the faster it goes, but even saving 30% of the image (canvas is 800x800) causes it to hang for several seconds. Is there a faster way to go about this?
I don't really understand why you are diving into pixel details to manipulate your cropping image functionality. It's understandable as bigger the image is get as more time is needed for cropping out the rest of the image, because practically with iterating over a two dimensional array of pixels the processing time needed for the operation is exponentially increasing with the increasing in size of the pixels map.
So my suggestion would be to try to remake the function without to even touch the getImageData and putImageData function. It's useless. I would make in the following way:
Obtain the pixel coordinates at the mouse down.
Create an event listener for the mouse move.
Create a semi-transparent image over the original image and use the fillRect function to draw into the created canvas.
Create an event listener for mouse up.
Obtain the pixel coordinates at the mouse up.
Calculate the coordinates of the resulting square.
Draw the resulting image into the canvas using as parameters the square coordinates.
As a final step draw the content of the canvas to an image.
This way you will save a lot of overhead on image cropping processing.
Here is a script for your reference: https://github.com/codepo8/canvascropper/blob/master/canvascrop.js
There is no real way to speed it up when you have to use a user defined shape, but the bogging down can be handled with a worker.
Some ideas:
Restrict getImageData to the bounding box of the polygon the user draws.
Put data.height, data.width etc. used inside the loop in a variable.
Maybe you can split up inside/outside tests and setting the imagedata alpha value.
Maybe even draw the polygon to a black and white imagedata object, then get the difference between the two?
Can you share the isPointInPath(x,y) function?

Extract derived 3D scaling from a Sprite to set to a 2D billboard

I am trying to get the derived position and scaling of a 3D Sprite and set them to a 2D Sprite.
I have managed to do the first part like this:
var p:Point = sprite3d.local3DToGlobal(new Vector3D(0,0,0));
billboard.x = p.x;
billboard.y = p.y;
But I can't get the scaling part correctly. I am trying this:
var mat:Matrix3D = sprite3d.transform.getRelativeMatrix3D(stage); // get derived matrix(?)
var scaleV:Vector3D = mat.decompose()[2]; // get scaling vector from derived matrix
var scale:Number = scaleV.length;
billboard.scaleX = scale;
billboard.scaleY = scale;
...but the result is apparently wrong.
PS. One might ask what I am trying to achieve. I am trying to create "billboard" 3D sprites, i.e. sprites which are affected by all 3D transformations except rotations, thus they always face the "camera".
The documentation says that you get the vector correctly, but its coefficients don't seem to be added together to form a single length value. Try first an unscaled sprite, and check if you're receiving a sqrt(3) value as its length. If yes, then you should use 0th element of the vector as X scale, and 1th as Y scale. I'm not sure what to do with 2nd element (in this case it'll be a Z scale, either divide both scales by it, or multiply by it). Hope that helped.

How to create a circular "brush tip" in HTML5 canvas drawing application?

I'm using HTML5 lineTo but any stroke greater than 1 creates squarish looking corners on the lines (the stroke extends perpendicular to the path of the line you draw). I want to create a circular brush tip, similar to http://muro.deviantart.com.
Any ideas?
The corners can be rounded by setting the line cap.
ctx.lineCap = "round"
You can also apply a bezier curve to the overall line to create a smoother overall line, by, for each point in the line P'0, …, P'n + 1, applying the equation P'k = (k/(n+1))Pk-1+(1-(k/(n+1)))Pk [NB: You might do well to select which points to which you apply the smoothing of the bezier curve by setting a threshold, perhaps on the angle between Pn and Pn+1]
Combining these two techniques with a standard box blur to the line itself will give you a much smoother appearing line.
Edit
From what I can tell, there's actually a number of ways to do this – which you use is entirely up to you. I'll give you an example, and let you decide: Assume you have a path drawn from a beginning point pm (mousedown) to an endpoint (mouseup) pn. That path is made up of subpaths (the points joined by miters). We can draw the path to the context from p0 to p1 with lineTo() and stroke() as normal. Just from watching console output, the points at which the subpaths join is the mousemove event firing. Record these points in order in an array.
Of course if we draw this to the main context, we have a problem removing it, so this should be done to a buffer context (an additional canvas element, for instance). The buffer is cleared, and we use the points of the miters to calculate the curve. bezierCurveTo prints a cubic function (B(t) = (1-t)3P0+3(1-t)2P1+3(1-t)t2P2+t3P3, t ∈ [0,1]. Step through your array (think for loop) recalculating the line with those points, updating the curve from P0 to Pn-3. (Doing quick head-math. You might need to think over this endpoint. All of this is dependent upon which arcing equation you use).
So let me see if I can do something with this... I'm not testing it so I guarantee bugginess.
// Assume:
// bfr = buffer context.
// ctx = main context.
// md = boolean value for mousedown
// pts = []; <-- already contains lp (below) at pts[0];
// We've also recorded Pm in associative array lp [last point]
// Draw is fired on mousemove. Mousemove records a current point in associative array cp
draw = function() {
if(md) {
bfr.beginPath();
bfr.moveTo(lp.x-.5, lp.y-.5);
bfr.lineTo(cp.x-.5, cp.y-.5);
pts.push({cp.x, cp.y});
bfr.stroke();
}
}
// Optionally, you could make this function recursive.
// This assumes that you want to estimate the curve based on the whole line.
bezier = function(pts) {
ctx.beginPath();
ctx.moveTo(pts[0].x, pts[0].y);
for( var i = 0; i < pts.length - 3; i++ ) {
ctx.bezierCurveTo( pts[i+1].x, pts[i+1].y, pts[i+2].x, pts[i+2].y, pts[i+3].x, pts[i+3].y);
}
ctx.stroke();
}
Again, this is what I see – someone else may have an entirely different and I'm sure better interpretation. I'm trying to tear chunks of things I've done and put them together with some new code quickly to give you some idea.

Huge area texture?

This is a very general question that's not related to a specific language. I'm having this array of int's:
int[100][100] map;
This contains just tile numbers, and is rendered as 256x256 tiles. So it's basically just a tile map or whatever it should be called. Thing is I want to be able to write anything to the map, anywhere, and it should stay there. For example be able to paint on stuff on the ground such as grass, flowers, stones and other stuff making the terrain more varied without having to render each of these sprites a huge number of times every time it renders. But making each tile contain it's own texture to write to would be terribly memory consuming at that would be 256x256x100x100 = 655360000 pixels to store. Would'nt that be like gigabytes of data or something!?
Does anyone know of a good general sulotion to make what I'm trying to do without killing too much memory?
If someone wonders I'm using C++ with HGE (Haaf's Game Engine).
EDIT: I've choosen to limit the amount of stuff on screen so that it can render. But look here so maybe you'll understand what I try to achieve:
Link to image because I'm not allowed to use image tags :(
If it's just tile based then you only store one instance of each unique tile and each unique "overlay" (flower, rock, etc.). You reference it by id or memory location as you have been doing.
You'd simply store a location (tile number and location on tile) and a reference to an overlay to "paint" it without consuming a lot of memory.
Also, I'm sure you know this but you only render what's on screen. So memory usage is pretty much constant once everything is loaded up.
I'm not exactly sure what you are trying to do, but you should probably have the tiles in separate layers. So say that for each "tile" you have a list of textures ordered bottom-up that you blend together, that way you only store the tile indexes.
Instead of storing just the tile number, store the overlay number and offset position also.
struct map_zone {
int tile; // tile number
int overlay; // overlay number (flower, rock, etc). In most cases will be zero
int overlay_offset_x; // draw overlay at X pixels across from left
int overlay_offset_y; // draw overlay at Y pixels down from top
}
map_zone[100][100] map;
And for rendering:
int x, y;
for(y = 0; y < 100; ++y) {
for(x = 0; x < 100; ++x) {
render_tile(map[y][x].tile)
render_overlay(map[y][x].overlay, map[y][x].overlay_offset_x, map[y][x].overlay_offset_y);
}
}
It's arguably faster to store the overlays and offsets in separate arrays from the tiles, but having each area on the map self-contained like this is easier to understand.
you have to use alpha maps..
you are going to paint a texture 256x256 which maps your whole terrain. for each channel r,g,b,a you will tile your terrain with your another texture..
r = sand.jpg
g = grass.jpg
b = water.jpg
a = soil.jpg
in shader, you will check the color of alpha map and paint with these textures..
i am doing such a thing now and i did like that