On Performance

Performance is getting to be a real issue here. I have chipped away at it each release by improving many different aspects of the code; however, the issue still lingers there – becoming more apparent the more you play Wayward. Could it be the monsters? Could it be the particles? Could it be the environmentals? All of these things do impact the performance, yes, but the performance is actually being hindered from another factor – the technologies that the game exists on itself. The slowdown (from what I have tested), is actually coming from drawing images (through HTML5’s drawImage) – all the different graphical elements on the screen: layered tiles, character, enemies, environmental, items, lighting, particles, text/item animations, etc.

Each movement we have to render a brand new screen – 13×13, 169 tiles. But not only 169 tiles, we have to loop this 13 times (since there’s 13 layers of tiles – some that overlap others). The looping surprisingly takes no time at all – the thing that takes the time is the drawImage functions being called 169 times per move. This doesn’t even include the other stuff I mentioned above like the monsters, lighting, and animations for example. I mention those three examples because these ones in particular take more resources than anything else. How could this be? Aside from HTML5’s drawImage not being the best for performance in it’s current state, if you want to do other more complex things like opacity (lighting/animations) or image mirroring (monsters), this can reduce performance up to 50% depending on the browser.

To put this in to perspective,

  • Want to draw a colored square 1000 times? Okay, cool, that will take a measly 6 milliseconds.
  • Want to draw a simple image 1000 times? Okay, well, that will cost you 19 milliseconds.
  • Oh you’re the fancy type? You want to draw an image with opacity 1000 times? Well, now we’re talking 40 milliseconds.

I suspect something else weird is going on here too aside from canvas specific performance. The performance degrades over time and garbage collection doesn’t seem to be firing in the correct instances. The CPU keeps going up and up. I understand some of these issues, and I can try to work on some of these issues about by buffering and pre-rendering certain things; however, pre-rendering isn’t the solution as different browsers have opposite results. Not only that, but pre-rendering will actually hurt me in a world with manipulative terrain. Right now I just have to re-draw the screen when I dig some dirt. Imagine regenerating that pre-rendering of an even larger portion.

So i’m kind of stuck here. If any technical HTML5 programming types happen to stumble upon this, I would love to hear your thoughts.

12 thoughts on “On Performance”

  1. I was looking at the images contained in the downloadable version, and i noticed they’re all scaled larger than they have to be. Is that on purpose? How much faster is it to have them already scaled than to render the screen, then zoom in to 400%? (You would apply particle effects afterwards.)

    The way the tiles work also seems sort of… strange. I’m not sure it’d be easy to make graphics for it. You could always change it so that the tiles don’t need opacity.

    If the mirroring of the enemy sprites takes time, the stupid simple way to fix that sounds like adding in the mirrored version in there from the get-go.

    Reply
    • Hey there, thanks for the reply.

      The images are scaled up unfortunately due to Chrome (Windows) does not supporting nearest neighbor image scaling on canvas elements. I really hope they support this soon. Basically, what it means is if I scaled the images up, they would become very blurry instead of keeping their crisp pixel-goodness. This is at the cost of some performance, but not an extreme amount. I will do a test though to make sure this still is the case.

      The opacity is used for the lighting in the game, but I might have some ideas on speeding that up too. Mirroring the enemies in the graphics is also a good option, thanks!

      Reply
      • Hey, you’re welcome.

        If you don’t mind, I actually went through the images for the game and changed them so they use indexed colors instead of plain RGB, ended up knocking off a good MiB off the total size of the images. (It’s a little lossy though.) Some of the images also weren’t perfect resizes, like some of the gravel I think had some lone pixels breaking up the 4×4 pixels. A quick nearest neighbor scale fixed that up.
        I also changed the alpha channel to just being a transparent color, so it might actually help with your issues there. I’m not quite sure, however. It does seem to run faster when i run the file from my browser though.

        http://filesmelt.com/dl/wayward_alpha_1.3_fan_edit_.zip

        Hope it helps you out.

        Reply
        • (If you go in and play it you’ll see that it was a bit rushed, if you did it yourself there probably wouldn’t be so many issues.)

          Reply
          • Unfortunately, the image quality did not effect the rendering time at all. The only time where it did was scaling images up, but only in Chrome (where we don’t get nearest neighbor re-sampling).

            Adding in the monster movements into the graphic helped speed it up though. Thanks for that simple idea.

            Unfortunately again, I found the true bottleneck here (the thing that takes the most calculation time).

            Each turn, we draw the 169 tiles; however, we loop through each monster, each item, each environmental every single tile, every turn – to see if that thing is ON that tile, which equals up to some 20,000-30,000 loop iterations each move – with tons of IF statements in between. This is a terrible way to do this and I plan to resolve this… as soon as I figure out how, haha.

          • You might have already looked into it, but just in case, a quick google search netted me this right off the bat.
            http://stackoverflow.com/questions/8028864/using-nearest-neighbor-with-css-zoom-on-canvas-and-img
            (One of the answers even has a script that can scale it using nearest neighbor, but supposedly it’s a little slow. It might still be faster, or it might be slower.)

            Aw, that’s disappointing. Lower image quality should make it load faster for those who have slower internet, though. Probably still worth using, right?

        • Thanks a lot for this. I’m going to try this theory in a couple different test scenarios later today.

          Full uncompressed .PNG drawing speed
          Compressed/indexed .PNG drawing speed
          .GIF (indexed) drawing speed – Maybe the smaller the file/filetype, the faster?
          Non-upscaled .PNG drawing speed (even though it will be blurry on Chrome)

          And possibly some in betweens depending on the results.

          Reply
  2. I have no idea how canvas rendering in javascript works, but I can try to explain some of what I’m doing for my tile-based engine in Java using OpenGL. Hopefully some of the concepts will work here.

    The first thing I do is divide my map up into sectors— 8×8 sections of tiles (and the items on the tiles) that are built into a single graphics call. In the case of this game, that would be your pre-rendered image (sans living entities).

    Every logic tick, I iterate through the map and have each tile perform whatever action is necessary, but I don’t update the “pre-rendered” sectors unless they’re in view. That allows me to perform hundreds of thousands of updates on the map as a whole with barely any performance hit.

    I also apply a boolean to each sector called “hasChanged”, which defaults to false. Whenever a tile is changed within a sector, the boolean for that sector is changed to true. If “hasChanged” is true, the next time the sector would be rendered, it updates the sector’s “pre-rendering”, then renders it normally. This means that anything off-screen is not updated graphically until it actually comes into view.

    Using the above methods, I get a consistent 4500-5000fps, even when updating 73,798 tiles 30 times per second. Granted, this is OpenGL and I have no idea how that will translate to Canvas rendering, but I do hope it helps at least a little bit.

    Reply
    • Thanks for the comment.

      Splitting up chunks of the map I think would definitely help me a bit; however, Canvas/JavaScript speed has nothing on OpenGL/Java – it’s probably not even comparable at this point. Even though supposedly Canvas operations try to use GPU – the bottleneck ultimately was JavaScript which is still slow in comparison to most true compiled programming languages out there. Mix that with my bad programming and we have a grim scenario here indeed, haha.

      Part of one of the issues was that I had was using loop that checked if a monster/item/thing was on a tile to render it, instead of just attaching them to the world data or using pointers. This fix alone saved me about 50% performance or more. Saving the map as chunks is next on the list.

      Reply
  3. I’ve got two suggestions for you. I hope they’re useful.

    1. Only redraw the tiles that actually changed. In the case of moving the screen, copy the existing canvas onto itself offset by the movement amount instead of clearing it during each frame, then redraw the tiles that were out of view and any that changed. You’ll have to keep track of what was in which tile last redraw, but that’s the tradeoff: use more memory, draw less stuff, but like you said, iterating over the structure isn’t the slow part. This is a pretty common method for tile based games going back to when tiles were a new idea.

    2. Use WebGL. One textured quad for each tile, including one for each thing that could be on a tile. Then you can draw the tile instances with one draw call per type of tile visible, or with the right shader only one draw call period.

    Reply
    • Thanks for the ideas. I had thought about the first point; however, i’m doing something unusual with layering and tiles, so rendering only the “edge” won’t exactly work since it’s possible that the tiles would appear over the edge tiles, and not always below; however again, your idea of storing what was last drawn might work to correct this. Tile rendering isn’t a huge resource sink at the moment though, so I will play around with it a bit later.

      I’ve definitely though of WebGL – I just haven’t dived into it yet. I know this will pretty much fix all CPU issues that I could have, i’m just not sure if I want to go with that solution yet.

      Reply

Leave a Comment