phaserjs / phaser

Phaser is a fun, free and fast 2D game framework for making HTML5 games for desktop and mobile web browsers, supporting Canvas and WebGL rendering.
https://phaser.io
MIT License
36.65k stars 7.07k forks source link

The amount of tilemap layers has a huge impact on performance #839

Closed valueerrorx closed 9 years ago

valueerrorx commented 10 years ago

http://test.xapient.net/phaser/ALL/layers.html (AUTO) http://test.xapient.net/phaser/ALL/layers-canvas.html (CANVAS)

as you can see in the given example 1 layer (no matter how many tiles are actually filled) does not affect the performance at all (62fps)

canvas size 800x640 tilemap 200x20 tilesize 32x32

4 layers will result in 52 fps 6 layers in 42 fps 8 layers in 34 fps
(on my workstation)

these test results will show if you use a webbrowser without gpu rendering enabled (for example chromium on linux) or a low performance device like a tablet (tested with google nexus 7)

if you combine all the layers to one and place all tiles on the same layer you will again have 62 fps

so the amount of tiles is not responsible for any noticeable performance loss.. even 4 giant background images moved relative to the camera (parallax) will have no impact on the performance.. but even a complete empty layer will do ! mariohills1

max-m commented 10 years ago

For me the FPS get low when moving the camera.


AFAIK, please correct me if I'm wrong: While moving the engine has to recheck and rerender layers(width/tilewidth)(height/teilheight) tiles. For one layer you've got 25 to 26 (when not perfectly aligned to the grid it should be 26 tiles) by 20 tiles on screen. So your worst case would be 26_20_8 = 4160 tiles to be checked and rendered on every frame update as opposed to 520 on one layer.


Btw. even on your single layer demos, scrolling results in ~40 FPS for me.

photonstorm commented 10 years ago

You've 8 x 800x640 sized canvases being drawn, excluding any Sprites or UI, every single frame. That's a lot. You'd see the same sort of performance issue with 8x BitmapDatas of the same size.

Even on a GPU based system it still has to push 8 fresh textures up, which will take its toll. However on WebGL we could look at using a RenderTexture instead to help avoid this, but it won't make any difference for Canvas.

Yet stick them all in one layer and you can no longer render Sprites between them (which was the whole point of layering in the first place, based on numerous requests for that).

There is one easy optimisation we could make - strip rendering the tiles. So we shift the previously drawn canvas off to the left then only drawn down the new tiles that have scrolled into view on the right. There is a trade off here though beyond which calculating those new tiles can cost more than just redrawing them all (especially for n-way scrolling maps).

In the meantime we could look at being able to redirect the render pass of a TilemapLayer to an already existing layer, so you could render your 8 down to just one. But you will loose any ability to order the layers in the display, or inject items into your scene as a result. Trade offs.

valueerrorx commented 10 years ago

oke.. so every layer will result in a new canvas drawn over each other..
i thought they are already all rendered down to one layer..

what about background images.. they are also moving and i can place something between them (change their z value) they seem not to have a noticeable impact on performance. are they all on the same layer?

what about layers that are only control layers.. (in my case "layer-ropes" "layer-enemybounds" "object-layer") they are not shown (atm alpha=0) i just check occasionally if there would be a filled tile on that layers.. they still affect performance but are not drawn (or are they ?)

lewster32 commented 10 years ago

Stuff with alpha = 0 is still drawn, but is totally transparent so you can't see it. If you want it to be hidden, you should use visible = false, and I believe in addition you can use renderable = false to stop the item being updated on the render pass.

valueerrorx commented 10 years ago

@lewster32 visible = false; on 2 of my layers immediately gave me back 5 fps - thx for that !!(renderable has no effect)

now there are 3 to 4 layers in my setup that could be drawn on one single canvas without any trade offs.. (if there were an option for that) but this is still "dealing with the current situation" it's good but i was hoping for a solution on the framework side...

(another solution is still "turning on gpu rendering and using a faster device" ^^)

lewster32 commented 10 years ago

I may be thinking of pixi specifically with renderable, I presume Phaser sets renderable to false when visible is set to false (which would make sense).

jloa commented 9 years ago

@photonstorm "You've 8 x 800x640 sized canvases being drawn, excluding any Sprites or UI, every single frame. That's a lot" -- having 6 layers should not result in having 6 canvases. In fact the amount of layers should not matter at all as you should render them all on a single canvas/texture one on top of another (according to z). Imo the renderer should be rewriten. 8 layers is not the limit btw, there are games that use about 10-12 layers for effects etc. With the current renderer tilemaps are alsmost useless as they work only with 1-2 layers which in 90% cases is simply not enough. The render code should be rewriten - that's the only option to make tilemaps fast.

lewster32 commented 9 years ago

As Rich said, rendering to a single canvas means other display objects can't be placed in-between layers, defeating one of the main points of having layers in the first place.

jloa commented 9 years ago

@lewster32 lol what? Here's a quick example how it should be done. The green square is between other squares as it's on the layer with z index of 1. If you want to move it, move the layer up/down. http://jsfiddle.net/bxdcx651/1/

Imo the whole phaser render engine should be rewriten, coz now it's just ridiculous. For example every tilelayer creates a new canvas [!] and ofc when you have like 10 layers the fps drop is huge. http://docs.phaser.io/TilemapLayer.js.html#sunlight-1-line-43 etc. The logic should like

  1. global "texture" clear
  2. draw everything on it according to z-index
  3. post render stuff

That's it. As a result you'll be able to have almost as many layers/displayobjects as you want without fps drops.

hilts-vaughan commented 9 years ago

Ouch. That's a lot of canvas for multi-layered stuff. Agreed, my RPG uses ten layers and I already start to see massive drops on a GPU accelerated PC. Something needs to be done about that.

lewster32 commented 9 years ago

@jloa The problem is that Phaser isn't just a Tilemap engine, it's based on pixi which as a general scene graph rendering engine is faster than anything else out there that I know of. The Tilemap implementation isn't perfect - indeed there are plans to improve it as mentioned above - but in order to remain as easy to use and flexible as possible this is the implementation that's provided. If you need better performance, you'll need to sacrifice flexibility, and for a general game framework this isn't really an option.

jloa commented 9 years ago

@lewster32 im not saying that phaser nor pixi are bad. They are awesome, really. So many things out of the box (camera, loader, cache manager etc), all i'm saying that the tilemap related stuff is not written performance-wize. I would love to rewrite that (using the method i've described above, which i've already used in several core-js games and it proved to work flawless), but as i'm not the author of phaser, nor i've worked with pixi, i lack the architecture knowledge of these libs and btw there's no such information on the web. So i'm stuck with the tilemaps and as my current project is tile-related, i've got to choose whether to use phaser at all or not.

jloa commented 9 years ago

@hilts-vaughan ha! I use 4 layers and already got the 20 fps drop in firefox resulting in 40/60 fps with just moving the camera around the world

lewster32 commented 9 years ago

For what it's worth, Phaser is extremely well written and laid out. I managed to add isometric projection and port Arcade Physics and all of its associated dependencies to 3D in about a week of casual work, so I reckon if you feel you have a good idea for how to improve or rewrite the Tilemap implementation you should just go for it. It's having specialists in all of these varied fields contributing to a project which makes it better for all :)

jloa commented 9 years ago

@lewster32 i do and i would love to rewrite the tilemaps, but there's no info on how does the internal phaser loop work. I already asked at the forum, but rich didn't respond. It might take weeks to just figure out the workflow of phaser in order to try changing it. I mean the life cycle of phaser and especially of the tick. Here's a nice example of laravel's workflow http://laravel-recipes.com/images/bootstrap1.jpg

lewster32 commented 9 years ago

It's not hard to piece together just by looking at the code, but I'm sure if you're serious about this you can contact Rich (rich@photonstorm.com) and get the information you need, fork the repo and start to work on it. If you build it, they will come.

photonstorm commented 9 years ago

@jloa

having 6 layers should not result in having 6 canvases. In fact the amount of layers should not matter at all as you should render them all on a single canvas/texture one on top of another (according to z).

This simply isn't possible with the way Pixi (and display lists in general) work. In WebGL there is no "single texture" to render to. All objects in the display list need rendering, and then each layer of the tilemap needs rendering to its own unique texture, otherwise positioning it within the display list is impossible, it just becomes flat.

The performance killer for WebGL is creating new textures and pushing them up to the GPU. At the moment that is exactly what happens, one per layer. However there are alternatives we can explore - again this is for WebGL only. But we are experimenting using a RenderTexture instead of a Canvas, as they are incredibly fast to draw (being fully batched). We would still have to create one RenderTexture per layer, there is no other possible way, but they still perform so much better it's a safe trade-off. Another alternative we're looking at is a shader based tilemap renderer, but again you'd generate each layer on its own.

In Canvas games there are a couple of things we can try: 1) Render direct to the renderSession context, and not to its own canvas. This should help a bit as it removes the need for drawing the layers to their own canvas and then again to the Stage. However something better to test would be 2) Edge rendering. So if the tilemap has only scrolled say 16px horizontally, then we can shift the rest of it once by copying it as a whole chunk, and then only drawing the new tiles that have scrolled into view.

The downside of this approach is that it doesn't allow for tiles to animate or modify themselves once drawn. In most cases this is ok, but not all. If they are allowed to modify then you have to start calculating which tiles are dirty and replotting just those, and there becomes a finite point at which it's worse to do that than just re-paint every tile anyway. The other downside is that you can't combine it with rendering direct to the renderSession (because it's cleared every frame).

So as you can see we've been thinking quite hard about how to improve it, and work will start on it shortly. I do agree with @lewster32 though - actually figuring out what happens internally really doesn't take much time. You could grok it within just several hours of poking around. it does require motivation however.

jloa commented 9 years ago

Thanks for this awesome explanation. Now I see the issues clearly. As I've worked with canvas only so far, I can tell that the direct session switch would be a solid boost for canvas mode. I'll take my time to dig into this

jloa commented 9 years ago

By the way, @photonstorm, I've been working pretty close with p2 for the past 3 weeks now and I've made some fixes to the p2.world and some minor once to phaser's core, also i've extended the p2 core itself as it lacks some very useful methods like applying impulses, some constraint fixes etc. Those methods I ported from the c# box2d. Should I mail you them or push to the repo? Ofc if you are interested.

photonstorm commented 9 years ago

@jloa if the fixes are to p2 itself they should be submitted as PRs to Stefans p2 repository, as then they will work their way back into Phaser via the correct route.

We're going to upgrade p2 to the latest release for Phaser 2.1 (as it's an API breaking upgrade we couldn't include it in 2.0.7) which will be the most current version of p2. So if your fixes are aligned with that build, then all is good. If you've been fixing the Phaser P2 classes instead then feel free to submit them as PRs (against the dev branch only) as long as they don't use a customised version of p2?

photonstorm commented 9 years ago

Closing this off as we've updated tilemap rendering lots recently to assist with this.