Issue #304 new

Pygame Clock inaccurate

Don Polettone
created an issue

Hi all,

this is something that's been making me mad for months, if not years, and I hope someone can finally fix this:

The clock seems not doing well in pygame; animation stutters / jitters. First I thought I was doing something wrong, but when I digged deeper and broke it down to a test as simple as can be, I recognised it must be an issue with pygame itself.

Try THIS out:

import sys
import pygame
pygame.init()

RES = (640, 480)
FPS = 60
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)

display = pygame.display.set_mode(RES, pygame.FULLSCREEN)
clock = pygame.time.Clock()

bg_img = pygame.Surface(RES)
bg_img.fill(WHITE)
for y in (229, 290):
    for x in range(40, RES[0], 40):
        bg_img.set_at((x, y), BLUE)

def motion_test():

    rect = pygame.rect.Rect((0, 240), (40, 40))

    while True:

        display.blit(bg_img, (0, 0))

        for e in pygame.event.get():
            if (e.type == pygame.QUIT or
                e.type == pygame.KEYDOWN and e.key == pygame.K_ESCAPE):
                pygame.quit()
                sys.exit()

        rect.x += 1
        if rect.x >= RES[0]:
            rect.x = 0

        pygame.draw.rect(display, BLUE, rect)

        pygame.display.flip()
        clock.tick(FPS)

if __name__ == "__main__":
    motion_test()

Every few seconds, the rect stutters a bit. In a pixelated game, this looks awful. Especially when scrolling over pixely tilemaps. I tried it out on both Mac and Win - it is a general issue.

Can someone help/test/agree/decline/fix this ??

Comments (14)

  1. Christopher Jones

    I get those same jitters with your script on 64-bit Windows 7. Similar issues with my pygame script are driving me crazy too. Can't find any fix that works by Googling. Have you thought of raising the issue on stackoverflow? Chris Jones

  2. Christopher Jones

    I've just found the answer! The pygame.display.flip() doco says "If your display mode is using the flags pygame.HWSURFACE and pygame.DOUBLEBUF, this will wait for a vertical retrace and swap the surfaces". This should stop the display juddering. But putting those flags in pygame.display.set_mode() does nothing on my system, which defaults to using the windib driver. After changing the driver to directx, those flags are honoured, and the rectangle moves smoothly. Add these lines at the start of your script:-

    import os
    os.environ['SDL_VIDEODRIVER'] = 'directx'
    

    Add the set_mode flags:-

    display = pygame.display.set_mode(RES, pygame.FULLSCREEN | pygame.HWSURFACE | pygame.DOUBLEBUF)
    

    Remove this line:-

            clock.tick(FPS)
    

    or change it to:-

            clock.tick(0)
    
  3. Don Polettone reporter

    Hi Christopher,

    thanks a lot for the very interesting hint!

    I've tried out both versions:

    If I do the changes and remove the line, the program runs extremely fast (no time delay at all I think).

    If I do the changes and amend the line as described below, the program runs much too fast (around 120 FPS or even more).

    (Win 10 Home 64bit)

    Additionally, this work-around would not solve the issue, because I do not know what hardware my games will be running on, and so I would never know when V-TRACE would happen. And as I use an FPS-based, fix time-step, the game would run on different speeds, depending on the hardware.

    I got soooo tired of this clock issue recently, so I coded my own python-based clock for Win, Mac and Linux now. To make the clock work on Win, I had to mess around with the Windows API a bit to tighten up the OS' time resolution. Some lines of python-C synthax were necessary to achieve this, and thanks to some very helpful people on stackoverflow, I got it working now.

    My own clock is pretty much as accurate as Pyglet's, but has much less code. But don't get me wrong, compared to the guy that writes the pyglet engine I am nothing but a small, helpless worm. I tried to "extract" the clock from pyglet first and use Pygame for all the rest, but it was a mess, and I did not like it (mixing 2 engines). After realising this, I gave it one more try and started to mess around coding my own clock (again). It was really Windows' time granularity of about 16 ms that prevented me from achieving this goal, but now I finally got it.

    I still believe that fixing Pygame's clock (for Win users) should be considered.

  4. Christopher Jones

    So my fix evidently doesn't work on all systems: on both of my PCs (Windows 7 and Windows 10) it syncs the frame rate to the monitor's screen refresh rate. If your clock manages to achieve 60 fps by setting 16.666 ms between display flips, won't your game still jitter on monitors with refresh rate not a multiple of 60 fps? Any chance of posting your motion test example with your clock code included?

  5. Don Polettone reporter

    Hi again,

    What exactly do you mean with "it jitters"? Is it like when limitting pygame to 60 FPS (ca. one "hickup" per second)? Or does it continously jitter around with no pattern?

    I did lots of testings on both my machines (fast desktop and rather slow notebook, both Win10 64bit Home). The results were most interesting. I got some code running in which I can switch the clock that's used during execution, and it runs this same rect test, limitting each clock to 60 FPS. The 3 clocks are pygame's, pyglet's, and mine.

    The pyglet clock behaves much like my clock, on both systems: The rect sometimes jitters for a tiny fraction of a second, but mostly it moves smoothly. With pygame, the rect like stops/jitters in a clear interval of like once per second. I really think the pygame clock does not work properly (at least on Windows (10(64bit))).

    So I am stuck with either pyglet's clock or mine, but both seem to have the same issue. Sometimes, with no clear time interval, there's something happening which produces a slight jittering. At first I thought it must be the OS interrupting the process in some way, draining ressources. So I raised the process priority with psutil, but still, the jittering sometimes happens (it's the same). Then I thought it could be python's garbage collection that causes the issue, so I made my clock turn it off before the timing starts and switch it on again afterwards, but that did not help either.

    I have included some code in my clock to see whether there's any frame where the clock would have no wait time at all, and throw an error if this happens. It does not happen when the jittering occurs, I got no error. So my clock has plenty of time to wait each frame (about 14-15ms per frame), and it works fine.

    Also I think the problem is not related to the display refresh rate, because if it would be, you'd notice some logical interval at which the jittering happens. If I run the VSYNC test it gives me about 59.998 on my desktop and 60.05 on the notebook, and the program behaves quite equally on both systems.

    Is there some way to monitor what exactly happens in Windows while my program is running?

    And what is your suggestion? How can I code an NES-like program with python using a fix time step (60 FPS, PAL) which runs 100% accurate? Is this even possible?

  6. Christopher Jones

    Movement is mostly smooth, but with occasional jitters with no apparent pattern. Interestingly, if I just change the driver to directx with

    os.environ['SDL_VIDEODRIVER'] = 'directx'
    

    I get tearing of the blue rectangle, implying that pygame.display.flip() is writing to the frame buffer at the same time as it is being read out to the display. This doesn't happen with the default windib driver, so there must be some double-buffering and/or locking to prevent it. So although your clock accurately controls the frequency of the pygame.display.flip() calls, asynchronous processing inside this function may result in the actual frame buffer update happening slightly earlier or later on some frames. I still believe, therefore, that a 100% accurate solution must entail sychronisation to vertical retrace. I'm surprised that the fix I provided with pygame.HWSURFACE, pygame.DOUBLEBUF, and the directx driver didn't work for you. Could you check with

    print(hex(display.get_flags() & 0xFFFFFFFF))
    

    that the display flags have been accepted - should be 0xc0000001.

  7. Don Polettone reporter

    Hey again!

    I don't believe it... it worked?! It gave me 0xc0000001L (don't know what the L is for), but also a 100% smooth movement!

    I think I must have done something wrong in the 1st attempt, sorry. You're awesome. Thank you.

    My only concern: How can we check the refresh rate of any connected monitor? I googled a bit but I think with pygame itself that's not possible?

    But it's awesome! That's the 1st time I see real smooth motion in pygame.

    Thanks man

  8. Christopher Jones

    Glad it's working now! You could get the nominal refresh rate outside of pygame by

    import win32api
    fps = win32api.EnumDisplaySettings().DisplayFrequency
    

    but on my system that gives 59 not 60 fps. Would be better to get the actual frequency using clock.get_fps(), where clock = pygame.time.Clock() and you have a clock.tick() in your game loop: that will give the fps computed by averaging the last ten calls to Clock.tick().

  9. Don Polettone reporter

    Did many testings and once again, new issues popped up:

    I did a blitting benchmark which stops the time it needs to blit 1000 sprites per frame and blits that time onto the screen.

    It seems that using the HWSURFACE + DOUBLEBUF flags results in a much higher blitting time, and it's much more inconsistent as well. Especially when dealing with per pixel alpha images.

    Using the ordinary "display = pygame.display.set_mode(RES, pygame.FULLSCREEN)", it takes me 1-2 milsec to blit 1000 .png images loaded with either convert() OR convert_alpha(). If I use convert() and set_colorkey(), I get 2-6 milsecs (inconsistent).

    If I use a HWSURFACE + DOUBLEBUF, the exact same code takes 35 - 1215 (!) milsecs for convert_alpha() png's, and 2-6 milsecs for convert() only / convert() + set_colorkey() png's. That's way off limits.

    (I did all these benchmarks using the directx SDL vid driver.)

    Alas, using .jpg's with a colorkey doesn't work either because pygame changes the image's colours in a weird way if they're loaded from disk.

    Conclusion:

    If I don't use VSYNC I can blit as much per pixel transparent png's as I wish in 1-2 ms. That's enough for a NES-ish 2D bullet hell game. But it's not accurate (jittering problem).

    If I use VSYNC, I get smooth animation, which is a must-have for such a game, but the blitting times are way too high and inconsistent, which is a complete showstopper.

    It seems my journey goes on... dammit! Have you experienced the same issues with VSYNC?

    This stuff is really making me mad again.... I think I should really consider trying another framework for game dev purposes (else than pygame).

  10. Christopher Jones

    Actually, you can get smooth movement with just the DOUBLEBUF flag and not the HWSURFACE flag. You could try benchmarking your sprite blits without HWSURFACE to see if it makes a difference. I am just blitting one 1920 x 1200 full-screen image, and that is fitting OK with the 60 fps frame rate.

  11. Christopher Jones

    I have reproduced your excessive blit timings for png images with alpha transparency. Removing HWSURFACE didn't help. A workaround which gives acceptable blit timings is to blit the images on to a non-transparent background image and then blit that to the display, along these lines:-

    bg = bg_img.copy()
    bg.blit(alphapng, (50, 50))
    display.blit(bg, (0, 0))
    
  12. Don Polettone reporter

    Yes, it seems that's the only solution on Windows to get smooth motion in Pygame (so far).

    I have found out that it's the pygame.DOUBLEBUF flag that sets VSYNC to True, HWSURFACE is not needed. But it only works if you set the directx driver first, so that really did the trick.

    But still, it's all very quirky on windows with pygame.. I would prefer a non-jittering, non-VSYNC, and fast standard setup for pygame on windows without having to care about display flags and all that stuff.

    Is there no way to get smooth motion using the std windib driver?

  13. Log in to comment