Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SDL2: vsync document and test existing behaviour #735

Open
illume opened this issue Jan 14, 2019 · 22 comments
Open

SDL2: vsync document and test existing behaviour #735

illume opened this issue Jan 14, 2019 · 22 comments
Milestone

Comments

@illume
Copy link
Member

illume commented Jan 14, 2019

At the moment pygame+sdl2 vsync defaults to off, but we should change it to default to on to be backwards compatible with pygame+sdl1.

https://wiki.libsdl.org/SDL_HINT_RENDER_VSYNC

Also, we should perhaps expose a display.set_mode(vsync=True) option.

Related Docs: https://www.pygame.org/docs/ref/display.html#pygame.display.set_mode

@illume illume added the display pygame.display label Jan 14, 2019
@illume illume added this to the 2.0 milestone Jan 14, 2019
@husano896
Copy link
Contributor

How will vsync flag work in pygame+sdl1?

@illume
Copy link
Member Author

illume commented Jan 15, 2019

Good question. Maybe it will just do the current behavior, and it will be ignored. I'm not sure if SDL supports vsync in a way where it can be enabled or not.

@husano896
Copy link
Contributor

Previously in SDL1, OpenGL contexts are vsync'd by SDL_GL_SWAP_CONTROL

and surfaces are vsync'd by HWSURFACE | DOUBLEBUF + clock.tick().

But windowed surfaces can only use clock.tick(), which may cause screen jitters.

Probably related? #304

@dlon
Copy link
Member

dlon commented Feb 5, 2019

The hint has no effect for me. Perhaps it only works for SDL_Renderer.

@illume
Copy link
Member Author

illume commented Oct 3, 2019

Talking about this some more, I agree it should probably be left "as is" for pygame 2. Since this is a new feature, it should be moved to pygame 2.1+ (if we do it at all).

@illume illume modified the milestones: 2.0, 2.1 Oct 3, 2019
@MyreMylar
Copy link
Contributor

My question; is there a way to enable vsynch at all in Pygame 2?

The pygame 1.9 method is no longer available.

@illume
Copy link
Member Author

illume commented Oct 19, 2019

Sorry, which method was that?

@MyreMylar
Copy link
Contributor

In Pygame 1 you can enable v-sync (on windows) by:

  1. Setting: os.environ['SDL_VIDEODRIVER'] = 'directx' before you call pygame.init()
  2. Setting these flags: pygame.FULLSCREEN | pygame.DOUBLEBUF when you call pygame.display.set_mode()
  3. Don't restrict the frame rate with clock.tick(FPS).

On my computer this locks the frame rate to 60 FPS and if you scroll sprites or surfaces around they will move smoothly with no hitching. As far as I know this is the only way to enable v-sync/smooth graphic movement in pygame 1 so it's not ideal (doesn't work in windowed mode, no idea what the situation is on mac or linux) but it's better than nothing.

Step 1 is the one we can no longer do in pygame 2 as 'directx' is no longer an option for the SDL_VIDEODRIVER environment variable.

I got this method from issue #304 talking about the pygame clock. I'm not sure if the clock can be made any better, discussion elsewhere seems to indicate the hitching that we get without v-sync is likely to be an SDL wide issue (see: https://forums.libsdl.org/viewtopic.php?p=51727). However, to me, having an option to enable v-sync in some manner in pygame 2 seems a necessary feature.

In short; we have v-sync available in pygame 1 (albeit in a slightly crappy manner, but its there on the biggest platform for games) so we should have it in pygame 2, or if not that some other method (not sure there actually is one) that lets us have properly smooth scrolling graphics.

@MyreMylar
Copy link
Contributor

Perhaps the best way to add it would be just by adding an extra flag 'VSYNC' to display.set_mode(), much like the 'SCALED' one that was already added? I mean people are already going to have to deal with that difference between pygame 1 and pygame 2 when making cross pygame version applications.

I'm currently dealing with version specific differences with code like:
if int(pygame.__version__.split('.')[0]) == 1

But the best way to reduce people needing/wanting pygame 1 compatibility code is to make sure pygame 2 has all the capabilities of pygame 1 + more. And that includes some kind of v sync.

@mcpalmer1980
Copy link
Contributor

I never noticed any hitching on Windows or Linux and I don't use v-sync(can't in Linux Mate for some reason). I use the clock.tick(30) on all my games and base movement on that framerate. Some might say that 30fps is too low, but if 24 is enough for film, than 30 is enough for a solo indie platformer. The first version of Flyboy ran at 18.2 fps on DOS because it was the only reliable clock available to me without a liscence for a DOS extender, since matching the 60Hz v-sync was unattainable on 16-bit processor.

That said, I see no reason why v-sync should not be an option in 2.0, even if the request cannot be honored by the host OS / window manager.

@illume illume modified the milestones: 2.1, 2.0 Oct 26, 2019
@illume
Copy link
Member Author

illume commented Oct 26, 2019

  • SDL_HINT_RENDER_VSYNC is apparently 1 by default. I wonder if using SCALED flag pygame.set_mode(..., flags=SCALED) turns vsync on with MacOS? NO
  • PYGAME_VSYNC=1 python3 turns VSYNC on with Mac. Does it work with windows?
  • SDL_RENDERER_PRESENTVSYNC https://wiki.libsdl.org/SDL_RendererFlags

@robertpfeiffer
Copy link
Contributor

I don't think vsync should default to on, and we'd need an API to get the vsync FPS to make it useful.

@ghost
Copy link

ghost commented Mar 8, 2020

SDL_HINT_RENDER_VSYNC is apparently 1 by default. I wonder if using SCALED flag pygame.set_mode(..., flags=SCALED) turns vsync on with MacOS? NO

I've been playing with SCALED and the new sdl2 stuff on mac os and the "NO" you said at the end confuses me because, on my end, using SCALED does turn on VSYNC by default.

window = pygame.display.set_mode((960, 540), pygame.SCALED) gives me a metal renderer by default (great!) with vsync.
This is on mac os 10.15.3 with Python 3.8.2 and Pygame 2.0.0.dev6.

The hint has no effect for me. Perhaps it only works for SDL_Renderer.

Same for me. Since it's on by default, I can't disable it. Changing the default renderer driver using SDL_HINT_RENDER_DRIVER also doesn't work (but it does using the new Renderer class).

On mac os, renderer or not, I always have smooth movements and I can't have a higher FPS than my monitor refresh rate. It dosen't seem to be the case on other platforms.
I tried it on Linux (Raspberry Pi with Raspbian) and I get the opposite effect: I can't turn vsync on with SCALED or the new Renderers. There is also a lot of hitching.

@robertpfeiffer
Copy link
Contributor

Oh no. I worked on this and didn't update here. I talked to some SDL devs and reported this in the Discord. I should write it here too:

VSync cannot be made to work reliably cross-platform with SDL2

  1. This is entirely dependent on your driver configuration
  2. There is no way to talk to your driver directly with SDL2
  3. You cannot query the driver to find whether VSync is actually supported or currently enabled
  4. This goes for both OpenGL (pygame.OPENGL) and SDL_Renderer (pygame.SCALED)

On Linux, I need to enable VSync support for my NVidia drivers in the xorg.conf. If it's disabled, SDL_Renderer will happily request VSync and set the VSync flag. There is no way to query whether VSync is actually happening. SDL_Renderer just knows VSync is a thing the driver supports, and might prefer Metal to OpenGL if the OpenGL on mac is known to SDL2 not to support VSync.

This is a one-way street. It's only possible to query whether there's an API available to set the VSync flags. The SDL dev told me to just set the flag to be sure.

It gets worse. It looks like VSync on MacOS defaults to blocking double buffering. One frame is rendered, the second frame is rendered, and pygame.display.flip() will block until the buffers are swapped and the first frame is actually displayed on the screen. This means the third call to pygame.display.flip() (invoking either SDL_RenderPresent or SDL_GL_SwapWindow) will block until the first frame has been displayed. This means that on a 60FPS monitor, you can use VSync to actually limit your frame rate to 60 FPS - on a Mac.

It depends entirely on your drivers. The driver could use VSync or GSync internally, SDL2 supports that, and the driver could use triple buffering. In the triple buffering case, there are two back buffers, so a pygame program would happily churn out frames into the both back buffers at 1000 FPS without ever blocking. VSync doesn't limit the rate of your game loop, but it prevents tearing. That's why the SDL2 people said it can't hurt to request VSync. If it doesn't prevent tearing for some reason, the user will just disable it in the driver.

But at the same time, there is no way to ensure pygame.display.flip() is always non-bocking. If your game logic is on the same thread as your rendering - and we're still working with the CPython GIL - then we have a huge problem. You cannot rely on VSync as a substitute for clock.tick() but you cannot rely on anything else either. It might prevent tearing.

I'll keep you updated. Maybe in the future there will be an API to query this. But for now, in order to ensure the old behaviour cross-platform, VSync MUST default to off.

I could put a test case that requests VSync in in the examples directory, but that cannot to my knowledge be automated. You have to run it on your machine, and eyeball if you see any tearing on the screen. That can be easily detected if you just draw a pattern of vertical moving lines. Framerate limiting can be detected programmatically, but even that is finnicky. Maybe the test case runs too slow, and I don't think it can do anything sensible in a headless test environment without a dedicated graphics card.

I propose to close this as WONTFIX or similar, especially for PyGame 2.0. Even if we can enable VSync by default, which we can, we can't guarantee it will do the right thing, and it might harm backward compatibility with old PyGame 1.X games.

@MyreMylar MyreMylar added needs-testing This issue requires testing on one or more platforms Platform: Windows needs-docs labels May 16, 2020
@MyreMylar
Copy link
Contributor

MyreMylar commented May 16, 2020

sounds like the main things left to do here are:

  • Test the methods of setting VSYNC in windows from this comment on windows.
  • document the behaviour of VSYNC being a request with no guarantees.

@MyreMylar MyreMylar added the easy An easy challenge to solve label May 16, 2020
@MyreMylar MyreMylar changed the title SDL2: vsync default to on SDL2: vsync document and test existing behaviour May 16, 2020
@MyreMylar MyreMylar self-assigned this May 16, 2020
@MyreMylar
Copy link
Contributor

Looks like in pygame 2.0.0.dev8 with SDL 2.0.12 adding:

os.environ['PYGAME_VSYNC'] = "1"

before calling display.set_mode() and adding pygame.SCALED to the set_mode flags will turn on vsync on windows. Seems to do so in windowed and fullscreen modes. Nothing else seems to have any effect.

@MyreMylar
Copy link
Contributor

I added the vsync enabling method above that seems to work on both mac and windows to the docs in the linked pull request.

@MyreMylar MyreMylar added Difficulty: Waiting and removed easy An easy challenge to solve needs-docs needs-testing This issue requires testing on one or more platforms critical labels May 17, 2020
@illume
Copy link
Member Author

illume commented May 30, 2020

There's a vsync keyword now... thanks to @robertpfeiffer and @MyreMylar.

I'd like to continue on with this stuff after the next release:

  • investigate using vsync in 'software' mode.
  • display.set_mode vsync tests

@MyreMylar MyreMylar removed their assignment Jun 5, 2020
@illume illume added the Experimental Experimental code label Oct 23, 2020
@Farbfetzen
Copy link
Contributor

Hello, I'd like to make a request for enabling vsync without the SCALED flag. Would this be possible? Because my problem with the SCALED flag is that I have no control over the size of the window. Or if there is no way of using vsync without SCALED, make it possible to set the size of the resulting scaled window myself?

@robertpfeiffer
Copy link
Contributor

@BastiHz
For technical reasons and because of the nature of vsync, vsync only works when using the GPU. In PyGame, this means using OpenGL with pygame.OPENGL, using the SDL2 renderer (with OpenGL, DirectX, or Metal backends) via pygame._sdl2, or using pygame.SCALED, which uses the SDL2 renderer internally to render the display surface.

It would be possible to create another mode for pygame.display.set_mode that always uses the GPU, but doesn't do any automatic scaling, but for backwards compatibility reasons, we can't use a GPU by default.

@Farbfetzen
Copy link
Contributor

Ok, thank you.

It would be possible to create another mode for pygame.display.set_mode that always uses the GPU, but doesn't do any automatic scaling

That would be awesome. Maybe in a future version where backwards compatibility is not an issue anymore.

@illume illume modified the milestones: 2.0, 2.1 Aug 21, 2021
@ankith26 ankith26 modified the milestones: 2.1, 2.2 Nov 14, 2021
@fladd
Copy link
Contributor

fladd commented Nov 29, 2022

Looking at the code in display.c, it seems that when using an OpenGL context, currently vsync=1 always tries to set adaptive vsync first, and uses this if successful. This, however, should not be the default behaviour! It is currently not clear to me how to force normal vsync (i.e. have the the same behaviour as in Pygame 1).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants