Skip to content

Commit 2ee2e73

Browse files
Merge pull request #1874 from robertpfeiffer/better-vsync
Better VSync Enable vsync by forcing GPU render paths Raise exceptions when VSync is unavailable Allow games to query VSync state
2 parents 046efe1 + 550f1f9 commit 2ee2e73

File tree

5 files changed

+300
-60
lines changed

5 files changed

+300
-60
lines changed

buildconfig/stubs/pygame/display.pyi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,6 @@ def get_allow_screensaver() -> bool: ...
7575
def set_allow_screensaver(value: bool = True) -> None: ...
7676
def get_desktop_sizes() -> List[Tuple[int, int]]: ...
7777
def is_fullscreen() -> bool: ...
78+
def is_vsync() -> bool: ...
79+
def get_current_refresh_rate() -> int: ...
80+
def get_desktop_refresh_rates() -> List[int]: ...

docs/reST/ref/display.rst

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,9 @@ required).
115115
| :sl:`Initialize a window or screen for display`
116116
| :sg:`set_mode(size=(0, 0), flags=0, depth=0, display=0, vsync=0) -> Surface`
117117
118-
This function will create a display Surface. The arguments passed in are
119-
requests for a display type. The actual created display will be the best
120-
possible match supported by the system.
118+
This will create a window or display output and return a display Surface.
119+
The arguments passed in are requests for a display type. The actual created
120+
display will be the best possible match supported by the system.
121121

122122
Note that calling this function implicitly initializes ``pygame.display``, if
123123
it was not initialized before.
@@ -173,21 +173,42 @@ required).
173173
.. versionadded:: 2.0.0 ``SCALED``, ``SHOWN`` and ``HIDDEN``
174174

175175
By setting the ``vsync`` parameter to ``1``, it is possible to get a display
176-
with vertical sync, but you are not guaranteed to get one. The request only
177-
works at all for calls to ``set_mode()`` with the ``pygame.OPENGL`` or
178-
``pygame.SCALED`` flags set, and is still not guaranteed even with one of
179-
those set. What you get depends on the hardware and driver configuration
180-
of the system pygame is running on. Here is an example usage of a call
176+
with vertical sync at a constant frame rate. Subsequent calls to
177+
:func:`pygame.display.flip()` will block (i.e. *wait*) until the screen has
178+
refreshed.
179+
Be careful when using this feature together with ``pygame.time.Clock`` or
180+
:func:`pygame.time.delay()`, as multiple forms of waiting and frame rate
181+
limiting may interact to cause skipped frames.
182+
183+
The request only works when graphics acceleration is available on the
184+
system. The exact behaviour depends on the hardware and driver
185+
configuration. When ``vsync`` is requested, but unavailable,
186+
``set_mode()`` may raise an exception.
187+
188+
Setting the ``vsync`` parameter to ``-1`` in conjunction with ``OPENGL``
189+
will request the OpenGL-specific feature "adaptive vsync".
190+
191+
Here is an example usage of a call
181192
to ``set_mode()`` that may give you a display with vsync:
182193

183194
::
184195

185196
flags = pygame.OPENGL | pygame.FULLSCREEN
186-
window_surface = pygame.display.set_mode((1920, 1080), flags, vsync=1)
197+
try:
198+
window_surface = pygame.display.set_mode((1920, 1080), flags, vsync=1)
199+
vsync_success=True
200+
except pygame.error:
201+
window_surface = pygame.display.set_mode((1920, 1080), flags)
202+
vsync_success=False
187203

188-
Vsync behaviour is considered experimental, and may change in future releases.
204+
.. versionadded:: 2.0.0 ``vsync`` parameter
205+
206+
.. versionchanged:: 2.2.0 passing ``vsync`` can raise an exception
207+
208+
.. versionchanged:: 2.2.0 explicit request for "adaptive vsync"
209+
210+
.. versionchanged:: 2.2.0 ``vsync=1`` does not require ``SCALED`` or ``OPENGL``
189211

190-
.. versionadded:: 2.0.0 ``vsync``
191212

192213
Basic example:
193214

@@ -723,7 +744,43 @@ required).
723744

724745
.. versionadded:: 2.0.0
725746

747+
.. function:: is_vsync
748+
749+
| :sl:`Returns True if vertical synchronisation for pygame.display.flip() is enabled`
750+
| :sg:`is_vsync() -> bool`
751+
752+
.. versionadded:: 2.2.0
753+
754+
.. function:: get_current_refresh_rate() -> int
755+
756+
| :sl:`Returns the screen refresh rate or 0 if unknown`
757+
| :sg:`get_current_refresh_rate() -> int`
758+
759+
The screen refresh rate for the current window. In windowed mode, this
760+
should be equal to the refresh rate of the desktop the window is on.
761+
762+
If no window is open, an exception is raised.
763+
764+
When a constant refresh rate cannot be determined, 0 is returned.
765+
766+
.. versionadded:: 2.2.0
767+
768+
.. function:: get_desktop_refresh_rates() -> list
769+
770+
| :sl:`Returns the screen refresh rates for all displays (in windowed mode).`
771+
| :sg:`get_desktop_refresh_rates() -> list`
772+
773+
If the current window is in full-screen mode, the actual refresh rate for
774+
that window can differ.
775+
776+
This is safe to call when no window is open (i.e. before any calls to
777+
:func:`pygame.display.set_mode()`
778+
779+
When a constant refresh rate cannot be determined, 0 is returned for that
780+
desktop.
781+
726782

783+
.. versionadded:: 2.2.0
727784
.. ## pygame.display.set_allow_screensaver ##
728785
729786
.. ## pygame.display ##

examples/setmodescale.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"""
1212

1313
import pygame as pg
14+
import sys
1415

1516
pg.init()
1617

@@ -19,7 +20,13 @@
1920
clock = pg.time.Clock()
2021

2122
print("desktops", pg.display.get_desktop_sizes())
22-
screen = pg.display.set_mode(RES, pg.SCALED | pg.RESIZABLE)
23+
24+
do_vsync = bool("--vsync" in sys.argv)
25+
26+
if do_vsync:
27+
screen = pg.display.set_mode(RES, pg.SCALED | pg.RESIZABLE, vsync=1)
28+
else:
29+
screen = pg.display.set_mode(RES, pg.SCALED | pg.RESIZABLE)
2330

2431
# MAIN LOOP
2532

@@ -47,9 +54,6 @@
4754
done = True
4855
if event.type == pg.KEYDOWN and event.key == pg.K_f:
4956
pg.display.toggle_fullscreen()
50-
if event.type == pg.VIDEORESIZE:
51-
pg.display._resize_event(event)
52-
5357
i += 1
5458
i = i % screen.get_width()
5559
j += i % 2
@@ -59,9 +63,21 @@
5963
pg.draw.circle(screen, (0, 0, 0), (100, 100), 20)
6064
pg.draw.circle(screen, (0, 0, 200), (0, 0), 10)
6165
pg.draw.circle(screen, (200, 0, 0), (160, 120), 30)
62-
pg.draw.line(screen, (250, 250, 0), (0, 120), (160, 0))
66+
if do_vsync:
67+
# vertical line that moves horizontally to make screen tearing obvious
68+
pg.draw.line(screen, (250, 250, 0), (i, 0), (i, 120))
69+
else:
70+
pg.draw.line(screen, (250, 250, 0), (0, 120), (160, 0))
6371
pg.draw.circle(screen, (255, 255, 255), (i, j), 5)
6472

65-
pg.display.flip()
66-
clock.tick(FPS)
73+
pg.display.set_caption("FPS:" + str(clock.get_fps()))
74+
if do_vsync:
75+
pg.display.flip()
76+
# FPS should be limited by vsync, so we tick really fast
77+
# we only need to have the clock tick to track FPS
78+
clock.tick()
79+
else:
80+
clock.tick(FPS)
81+
pg.display.flip()
82+
6783
pg.quit()

0 commit comments

Comments
 (0)