MrZ MrZ - 2 months ago 9
TypeScript Question

Is it normal that my simple canvas with requestAnimationFrame + drawimage uses about ~8-9% of cpu?



I'm testing HTML5 Canvas with a easy example to draw some isometric tiles.
I'm using

requestAnimationFrame
method. However, I can see about 8% of CPU usage in Chrome task manager.

PS: I'm using
Typescript



Here is my draw loop:

private Draw = () => {
this.Context.clearRect(0, 0, this.Canvas.width, this.Canvas.height);
this.Context.imageSmoothingEnabled = false;
this.DrawCtx();
requestAnimationFrame(this.Draw);
}


DrawCtx calls another class with this function:

public Draw(MousePosition: Array<number>, ClickPosition: any): void {
this.MousePosition = MousePosition;
this.ClickPosition = ClickPosition;

this.DrawBackground();
this.ParseMap();
this.Container.Draw();
this.DrawTiles();
}


and here's a snippet of drawTiles (other things are just some
for()
for the map array)

if(!curtile.isDoor && typeof(this.Map.getMap().data[x - 1]) != "undefined" && this.Map.getMap().data[x - 1][y].height != 0 && this.Map.getMap().data[x - 1][y].height == (parseInt(curtile.height) + 1)) {
this.Container.drawImage(x + y + 500, this.Resources["1.png"], curtile.left, curtile.top - 24 - ((curtile.height - 1) * 6), 64, 64);
} else if(!curtile.isDoor && typeof(this.Map.getMap().data[x][y - 1]) != "undefined" && this.Map.getMap().data[x][y - 1].height != 0 && this.Map.getMap().data[x][y - 1].height == (parseInt(curtile.height) + 1)) {
this.Container.drawImage(x + y + 500, this.Resources["2.png"], curtile.left, curtile.top - 24 - ((curtile.height - 1) * 6), 64, 64);
} else if(!curtile.isDoor && ((typeof(this.Map.getMap().data[x][y - 1]) != "undefined" && this.Map.getMap().data[x][y - 1].height != 0 && this.Map.getMap().data[x][y - 1].height == curtile.height) || typeof(this.Map.getMap().data[x - 1][y]) != "undefined" && this.Map.getMap().data[x - 1][y].height != 0 && this.Map.getMap().data[x - 1][y].height == curtile.height) && typeof(this.Map.getMap().data[x - 1][y - 1]) != "undefined" && this.Map.getMap().data[x - 1][y - 1].height != 0 && this.Map.getMap().data[x - 1][y - 1].height == (parseInt(curtile.height) + 1)) {
this.Container.drawImage(x + y + 500, this.Resources["3.png"], curtile.left, curtile.top - 24 - ((curtile.height - 1) * 6), 64, 64);
} else {
this.Container.drawImage(x + y + 500, this.Resources["tile.png"], curtile.left, curtile.top - ((curtile.height - 1) * 6), Config.Game.TileWidth, Config.Game.TileHeight);
}


and this.Container is my drawImage index system

export default class Container {
Objects: any = [];
Context: any;

constructor(Context: any) {
this.Context = Context;
}

public drawImage(index: number, ...drawArguments: any[]) {
this.Objects.push({
index: index,
args: drawArguments
});
}

public Draw(): void {
this.Objects.sort(function(a: any, b: any) {
return (a.index > b.index) ? 1 : ((b.index > a.index) ? -1 : 0);
});

for(var i in this.Objects) {
var object = this.Objects[i];
this.Context.drawImage.apply(this.Context, object.args);
}

this.Objects = [];
}
}


Why is it using ~8-9% of CPU just for some
drawImages
? What's wrong with my code? Or is it normal?

Answer

For speed the general rule is "Write at the lowest level available": Typescript adds overhead write the code in Javascript where you need speed.

Don't use for( in ??) iterate with standard for loop, it is faster.

Don't use apply, call the function directly, its a good 6 times quicker (and more on Chrome). Note this is for the call operation not the code in the function being called.

You create and destroy a new array every time you call Draw. This will have a major impact. Reuse objects, never dereference anything inside a performance loop. Even if you must dereference all the new object you put in the array, don't create a new one at the end of the draw function. Just reset its length. 'objects.length = 0' does not incur the overhead of constructing a new array object.

var currentObjectCount = 0;
var objects = [];

function drawImage(index,...etc)
    var o = objects[currentObjectCount];
    if(o === undefined){
       objects[currentObjectCount] = o = {};
    }
    o.index = index;
    o.args = etc;
    currentObjectCount += 1;
}

At the beginning of every frame before you add object set currentObjectCount = 0

In the render loop (Draw function) use currentObjectCount to trim the array for the sort

objects.length = currentObjectCount;

But if you can get rid of the sort then dont trim the array and use currentObjectCount in a for loop. Ignore any objects not needed for the current frame. It cost nothing to have extra unused items on the array, it cost nothing to reuse old ones when you need them, It costs to dereference, it costs to construct.

Must you really sort, any type of pre sorting you can do will improve the sort. Use an insertion sort in the drawImage call, just a simple one that does a rough sort that will make the real sort quicker.

But for rendering you only need to sort if there is overlap, if you at any stage do a distance or collision test between object, use that data to optimise the sort, keep two objects array one for sorting and another that can be rendered unsorted. (I have written 100's of 2D games over 35 years and never have I had to use a sort in the main render loop, there has always been a better way)

Get rid of the spread operator. Its slow and there are not that many variants for draw image to make it worth the overhead you incur with ...

KISS is the rule that fast code uses. Keep It Simple (second S is to make the acronym). Keeping it simple does not mean on the surface where your code is, but simple all the way down to the core.

Comments