19

I am currently working on a JavaScript(pure js) based game. The game contains 5 large sprite sheets(e.g. 2861 × 768 and 4096 × 4864). When the game starts, all 5 sprite sheets are preloaded to canvas elements. Three of those 5 sprites represent together one animation, where each sprite contains 75 frames. When one sprite ends with its animation, I hide it and display the next sprite. When the second sprite finishes animating, I hide it and display the third/last one.

When the second or third sprite is about to be displayed, a small delay of 0.5 s - 1 s happens. The image is being decoded.

It is not something that happens just the first time, it is something that happens always. And that animation repeats every 5 minutes, and the small delay happens always.

The reason why I'm using canvas elements for preloading is that I thought WebKit would just throw away decoded images for some time being unused and that the canvas element would prevent WebKit from deleting it from memory. But that does not work.

I've tried almost every optimization I'm aware of. I have even refactored all my CSS by removing descendant selectors etc.

The renderer I'm using to draw those animations is built by myself and it is working perfect, so that could not be the problem, since it is working very well in Firefox.

EDIT [2016/03/04]: I made a mode with canvas and the result is even worse. It laggs a lot. And the delay remains the same. Only in NW, the problem does not persist in Chrome neither in Firefox.

Canvas mode - Lags: enter image description here

Default(HTML) mode - Works perfect: enter image description here

Codepen: My renderer http://codepen.io/anon/pen/JXPWXX

Note: If i hide those other elements with opacity:0.2 rather than opacity:0, the problem does not happen. But, I can not hide them like that since they remain still visible. They shouldn't be visible. If I add opacity:0.01 it is not visible and the problem does not happen in Chrome, but still persists in NW.

In NW, when I swtich from opacity:0.2 to opacity:1, an image decode is being processed. The same thing does not happen in Chrome browser. enter image description here

I am using the following version:

nw.js v0.12.3
io.js v1.2.0
Chromium 41.0.2272.76
commit hash: 591068b-b48a69e-27b6800-459755a-2bdc251-1764a45

The three image sprites are 14.4MB, 14.9MB and 15.5MB size. Each sprite contains 75 frames.

Why could this be happening and how can I prevent it?

Darko Riđić
  • 459
  • 3
  • 18
  • 1
    Note that this happens only in Node Webkit. It works just fine in Chrome. – Darko Riđić Feb 22 '16 at 09:48
  • 1
    Have you looked at the garbage collector? Sounds like the garbage collector is running which is causing the pause. Either the way you are dealing with the canvases or maybe your renderer is creating more garbage than you think. – Bill Feb 25 '16 at 00:13
  • 3
    Could you share some code or put it on [JsFiddle](https://jsfiddle.net/)? – Andrew Myers Feb 25 '16 at 00:15
  • 1
    I agree with both @Bill and @AndrewMyers. Doing lots of prototyping on Khan Academy, I recently discovered heavy GC overhead when using the `image()` method there. Not sure whether that is due to Processing.js library or an effect of the Khan Academy sandbox, but reverting to pure javascript removed about 60MB/s GC on a small 100x100 pixel `putImage()` operation and provided 2-3x performance boost. Would help to see the part of the code that handles the images in question. – Nolo Feb 25 '16 at 04:10
  • I have no garbage collection in my dev tools timeline. http://prntscr.com/a7oy3j Although I have 2 rAF calls in a row, but my function for starting rAF is called just once. I don't know why there are 2 calls. Here is my renderer and examples and data on how I use those animatinos bellow the renderer in the comments. @Nolo take a look. Thank you! http://codepen.io/anon/pen/JXPWXX?editors=0010 – Darko Riđić Feb 25 '16 at 10:19
  • 1
    @Darko Riđić one thing that made me suspicious is line 89. `renderer.rAF = window.requestAnimationFrame(this.render.bind(this));` binding should be done only once, ahead of time. When you call bind, you're creating an new something... :D (whether it's a closure or an actual new instance of a function, I'm not very familiar with Function internals). I wouldn't recommend doing that in any kind of loop unless absolutely necessary. – Nolo Feb 29 '16 at 05:30
  • @Darko Riđić otherwise, odd. I'm not seeing any major snarls. Does any of the data have to be fetch/downloaded on the app end, i.e. any chance a request is being deferred? – Nolo Feb 29 '16 at 06:10
  • @Bill do you have any ideas? – Darko Riđić Feb 29 '16 at 09:29
  • @AndrewMyers do you have any ideas? – Darko Riđić Feb 29 '16 at 09:29
  • 1
    I didn't see anything in the code that aroused my suspicions. Not to be the guy who's always asking for something more, but could you save your DevTools timeline as JSON (see Save and Load Recordings in [this document](https://developers.google.com/web/tools/chrome-devtools/profile/evaluate-performance/timeline-tool)) and put it up somewhere so everybody look through it? – Andrew Myers Feb 29 '16 at 15:24
  • @AndrewMyers I've already looked trough the timeline and there is nothing suspicious. Normal reflows and paints are happening. The paints are just as small as they need to be and the Update Layer Tree affects only 3-4 elements. Everything is smooth and under 11ms per frame. Not one frames goes higher than that. I will test smth else now. I will let you know when I'm finished. Maybe it will help you to get an idea. – Darko Riđić Mar 02 '16 at 08:03
  • I haven't found a solution. Just not to be confused, I'm using real HTML elements for sprite animations. Not canvas. I use canvas only for preloading. – Darko Riđić Mar 02 '16 at 11:04
  • 2
    You should edit relevant code parts from your codepen example into your question (as well as your additional research, such as the dev tools screenshots), so that it remains useful for people facing similar issues in the future, without having to rely on external code sources. – doldt Mar 02 '16 at 22:39

3 Answers3

1

Given that keeping Webkit thinking the image is still displayed makes the problem disappear (as your opacity experiment shows), I'd move it nearly completely out of the visible area, with only a single transparent row overlapping with the viewport (using overflow hidden).

Note that an unpacked 4000x4000 sprite sheet will use 64 Megabytes of RAM (4 bytes (=RGBA) per pixel), so perhaps it might be better to make sure the next image gets "warmed up" a bit ahead of time, without keeping all of them unpacked all the time?

Stefan Haustein
  • 18,427
  • 3
  • 36
  • 51
  • 1
    Although I've already tried something similar, I will try that again and will let you know if it works. Thank you for the suggestion :) But instead of doing this hack it would be better for me to switch to Electron instead of NW. Assuming that Electron would work just fine. I wont switch to Electron and let this issue get away unsolved! :D – Darko Riđić Mar 04 '16 at 16:50
1

Try to switch to google-chrome directly since the new nw version is probably released 19.04.2016. After that NW will hopefully keep up with every Chromium release.

You should not have the same problems in Chrome.

swolfish
  • 771
  • 1
  • 9
  • 25
0

I'd recommend using idata = ctx.getImageData(0, 0, canvas.width, canvas.height) to retrieve the data array from the canvases, then ctx.putImageData(idata, 0, 0) to switch between sprites, rather than hiding the canvases.

aecend
  • 2,432
  • 14
  • 16
  • I use canvas elements only for preloading. But I will make support for canvas, and the "HTML" way will be a fallback. Thank you for the recommendation :) – Darko Riđić Mar 02 '16 at 11:05