nico icon indicating copy to clipboard operation
nico copied to clipboard

Fix CPU usage when using vsync

Open markusgritsch opened this issue 2 years ago • 7 comments

I noticed that the CPU usage increases over time when using setVSync(true), which is the default. This happens even for a simple example like lines.nim.

When using setVSync(false), this does not happen, but of course this has the drawback of tearing or not smooth screen updates.

The main difference when using setVSync(false) is in proc run*() where waiting is done manually using delay(). However, it seems that when solely relying on vsync, CPU usage seems to increase over time.

As a solution this pull request adds a delay(1) even for the vsync case, if sleepTime is not smaller than 1.0 ms. This fixes the reported CPU usage problem.

markusgritsch avatar Feb 23 '22 15:02 markusgritsch

Are you seeing this behaviour on Linux? We've seen similar before on Linux where vsync doesn't appear to be supported correctly.

ftsf avatar Feb 23 '22 20:02 ftsf

No, I'm using Windows 10 on an Intel HD Graphics 2000. The fan of my Notebook makes high CPU usage very noticable :)

markusgritsch avatar Feb 24 '22 05:02 markusgritsch

Perhaps we can do something to determine if vsync is working correctly or not, if the time between loops is super short (and not close to vsync frequency), we could disable vsync mode and use the wait timer instead

ftsf avatar Feb 24 '22 05:02 ftsf

Hmm, I would not prefer having the wait timer approach being automatically turned on, as it has the mentioned drawbacks on its own. The proposed kludge fixes the vsync CPU issue for me, and I can enjoy smooth scrolling. If it does not gets mainlined, I can live with it too :)

markusgritsch avatar Feb 24 '22 08:02 markusgritsch

If you're getting high CPU usage with vsync mode it's likely vsync is not working correctly. We've encountered a bunch of cases where some setups do not actually do vsync and thus return instantly and create high CPU load. So I think some form of detection as to whether vsync is working may be necessary.

ftsf avatar Feb 24 '22 08:02 ftsf

But it's working when relying on vsync just a little bit later, by using the delay(1).

markusgritsch avatar Feb 24 '22 09:02 markusgritsch

Also, I don't think vsync is broken on my hardware per se, because a) it can be "fixed" with the above workaround, and b) I don't experience any issues when using Pyxel. I tried the same example to test smooth screen updates with Pyxel and with Nico:

import pyxel

x = 0

def update():
  pass

def draw():
  global x
  pyxel.cls(0)
  pyxel.line(x, 0, x, 128, 7)
  x += 1
  x %= 128

pyxel.init(128, 128, title="Pyxel", fps=60)
pyxel.mouse(True)
pyxel.run(update, draw)
import nico

var x = 0

proc gameInit() =
  # setVSync(false)
  setColor(8)

proc gameUpdate(dt: Pfloat) =
  discard

proc gameDraw() =
  cls()
  line(x,0,x,64)
  x += 1
  x = x mod 128

nico.init("nico", "test")
fixedSize(true)
integerScale(true)
nico.createWindow("nico", 128, 128, 4)
nico.run(gameInit, gameUpdate, gameDraw)

Skipped screen updates are immediately visible by jumps in the moving line when uncomminting setVSync(false). It runs super smooth when not disabling vsync.

The Pyxel version runs with 1.5% CPU usage out of the box (also smooth). With the fix applied, CPU usage stays low at 2.5% also with the Nico version.

I use the same SDL2.dll on both examples.

I haven't looked into what Pyxel does different from Nico regarding the SDL configuration.

markusgritsch avatar Feb 24 '22 14:02 markusgritsch