Jump to content

Massive CPU usage due to 500 bitmapdata sprites


Massemassimo
 Share

Recommended Posts

Hey guys,

 

I am playing around with terrain generation and everything is coming along just fine (ofc feel free to copy/paste if you see anything you like and you want to play around with it), but it seems generating having generated (bitmapdata) sprites in my scene is a bit too much for my CPU. It gets really hot and the fans go crazy. At 1000 sprites my PC (windows) shut down on me. :)

 

I guess I am doing something I am not supposed to do or am doing the wrong way, so here is my code, in typescript (if you prefer the JS, I am happy to add the generated JS, just let me know!) :

module Terrain01 {    declare var Voronoi: any;    declare var noise;    export class World extends Phaser.State {        private siteAmount: number = 500;        private relaxLoops: number = 5;        private seed: number = 123;        private sites: Phaser.Point[] = [];        private voronoi;        private bbox;        private diagram;        private bmd;        create() {            noise.seed(this.seed);            this.bmd = this.game.add.bitmapData(this.game.width, this.game.height);            this.game.stage.backgroundColor = '#454645';            this.game.rnd.sow([this.seed]);            this.createSites();            this.bbox = { xl: 0, xr: this.game.width, yt: 0, yb: this.game.height };            this.createVoronoi();            for (var i = 0; i < this.relaxLoops; i++) {                this.relaxSites();                this.voronoi.recycle(this.diagram);                this.diagram = this.voronoi.compute(this.sites, this.bbox);            }                                    this.drawPolygons();            //this.drawEdges();            //this.drawRoads();            //this.drawSites();                    }        createSites() {            for (var i = 0; i < this.siteAmount; i++) {                var x = this.game.rnd.integerInRange(0, this.game.width);                var y = this.game.rnd.integerInRange(0, this.game.height);                this.sites[i] = new Phaser.Point(x, y);            }        }        createVoronoi() {            this.voronoi = new Voronoi();            this.diagram = this.voronoi.compute(this.sites, this.bbox);        }        getCellVertices(cell: any) : Phaser.Point[] {            var halfedges = cell.halfedges;            var vertices: Phaser.Point[] = [];            for (var i = 0; i < halfedges.length; i++) {                vertices.push(halfedges[i].getStartpoint());            }            return vertices;        }        relaxSites() {            this.sites = [];            for (var i = 0; i < this.diagram.cells.length; i++) {                var vertices = this.getCellVertices(this.diagram.cells[i]);                var centroid = Phaser.Point.centroid(vertices);                this.sites.push(centroid);            }        }        drawPolygons() {            var graphics = this.game.add.graphics(0, 0);            var cells = this.diagram.cells;                        for (var i = 0; i < cells.length; i++) {                var site = cells[i].site;                var value = Math.abs(noise.simplex2(site.x/400, site.y/400));                var halfedges = cells[i].halfedges;                this.bmd.ctx.beginPath();                this.bmd.ctx.moveTo(halfedges[0].getStartpoint().x, halfedges[0].getStartpoint().y);                for (var j = 0; j < halfedges.length; j++) {                    var halfedge = halfedges[j];                                        this.bmd.ctx.lineTo(halfedge.getEndpoint().x, halfedge.getEndpoint().y);                }                this.bmd.ctx.closePath();                var r = Math.floor(50 * value + 50);                var g = Math.floor(100 * value + 110);                var b = Math.floor(50 * value + 0);                var rgba = "rgba(" + r + ", " + g + ", " + b + ", " + value + ")";                this.bmd.ctx.fillStyle = rgba;                this.bmd.ctx.fill();                this.game.add.sprite(0, 0, this.bmd);            }        }        update() {        }    }}

The Phaser.Game renderer is "Phaser.AUTO" if that's important somehow. I use noisejs by Joseph Gentle (https://github.com/josephg/noisejs) & javascript-voronoi by Raymond Hill (https://github.com/gorhill/Javascript-Voronoi).

 

So, my questions are: why is this so tough to handle for my PC and what can I do to change it?

Link to comment
Share on other sites

It's hard to see what's happening here - do you have a version you could upload? I think profiling may help a lot here so you can see exactly where it's getting bogged down.

 

If indeed you're creating 500 sprites each with a separate BitmapData instance, I think this will really make the renderers struggle - especially WebGL as one of your biggest bottlenecks when working with GPU accelerated stuff is uploading data to the GPU in the first place. This is one of the primary reasons why we use texture atlases and sprite sheets, as by having all of our sprites on a single image, this only needs to be uploaded once to the GPU, which then batches it very efficiently and displays parts of that single texture all over the screen.

 

Should it be the case that it's simply having too many separate textures being uploaded to the GPU, a solution may be to generate your own texture atlas with a single BitmapData object containing all of your individual sprites, and then parsing that into frames to be used like a preloaded one.

Link to comment
Share on other sites

Hey, first of all thanks for your answer!

 

Now since the sprites are generated "on the fly" and depend on random numbers and pseudorandom noise, I believe spritesheets & texture atlases are not an option, unless I am mistaken (which happens quite alot :D). I created a cpu profile with the help of google devtools and it seems the CPU is not the problem, having around 98% "free time". You said the GPU will be the problem, can I profile that, too? Maybe a little hint on how?

 

Also I tried to create a JSFiddle for the problem but came up short and I am not sure on how to debug it @ JSFiddle. I just copy/pasted the output JS and linked all the used libraries, not sure how this can go wrong but it does. Link is : http://jsfiddle.net/sav2W/1/

 

Thanks again and see you later (hopefully)!

Link to comment
Share on other sites

It was just the window.onLoad bit that was bugging jsfiddle, I've forked and fixed it here: http://jsfiddle.net/lewster32/Dr8wG/

 

I'm afraid I can't really help much with GPU debugging, however having tried using Phaser.CANVAS as the render mode, it's still murdering my fairly powerful work development machine (I have to knock it down to 10 iterations to be able to deal with it properly on here) so GPU probably isn't the issue. If I use Phaser.HEADLESS it becomes usable, so the issue is likely related directly to rendering. Beyond this I can't really say what's going on - this really needs a pair of eyes that knows what's going on deep at the renderer level I'm afraid. 

Link to comment
Share on other sites

I think I've found the issue. In drawPolygons you're adding a sprite for every single generation step of the texture, so you're adding one huge (1280x840) sprite 500 times to the stage, one on top of another. If you move the this.game.add.sprite call outside of the loop, it renders fine, and runs smoothly.

 

Here it is doing 5000 iterations: http://jsfiddle.net/lewster32/Dr8wG/

Link to comment
Share on other sites

Oh boy. You're completely right of course. Thanks a bunch!!!

 

P.S.: After adapting my code and tinkering a bit more, I found my graphics driver crashes with 5000 polygons when waiting for too long. Well I guess I don't need 5000 then. :>

 

I've tested this on my iPhone 4S with 5000 iterations and, despite taking about 5-10 seconds to generate it, otherwise runs fine. you could maybe spread the generation over multiple frames, as because you're writing to a single texture, there shouldn't be issues with memory or rendering speed after the generation is complete. Probably means rewriting your generation algorithm slightly, or even adapting it to use web workers, which seems to me an ideal use case - this is the sort of thing web workers were intended for.

Link to comment
Share on other sites

 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...