Skip to content

Commit 576a434

Browse files
authored
Reach FPS closer to max_fps on Windows (#109)
1 parent 1a38b7c commit 576a434

File tree

4 files changed

+23
-11
lines changed

4 files changed

+23
-11
lines changed

rendercanvas/_loop.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ def _rc_call_later(self, delay, callback):
372372
This method is optional. A subclass must either implement ``_rc_add_task`` or ``_rc_call_later``.
373373
374374
* If you implememt this, make ``_rc_add_task()`` call ``super()._rc_add_task()``.
375-
* If delay is zero, this should behave like "call_later".
375+
* If delay is zero, this should behave like "call_soon".
376376
* No need to catch errors from the callback; that's dealt with internally.
377377
* Return None.
378378
"""

rendercanvas/_scheduler.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@
22
The scheduler class/loop.
33
"""
44

5+
import sys
56
import time
67
import weakref
78

89
from ._coreutils import BaseEnum
910
from .utils.asyncs import sleep, Event
1011

1112

13+
IS_WIN = sys.platform.startswith("win")
14+
15+
1216
class UpdateMode(BaseEnum):
1317
"""The UpdateMode enum specifies the different modes to schedule draws for the canvas."""
1418

@@ -123,14 +127,23 @@ async def __scheduler_task(self):
123127
delay = 1 / self._max_fps
124128
delay = 0 if delay < 0 else delay # 0 means cannot keep up
125129

126-
# Offset delay for time spent on processing events, etc.
127-
time_since_tick_start = time.perf_counter() - last_tick_time
128-
delay -= time_since_tick_start
129-
delay = max(0, delay)
130-
131-
# Wait. Even if delay is zero, it gives control back to the loop,
132-
# allowing other tasks to do work.
133-
await sleep(delay)
130+
# Determine amount of sleep
131+
sleep_time = delay - (time.perf_counter() - last_tick_time)
132+
133+
if IS_WIN:
134+
# On Windows OS-level timers have an in accuracy of 15.6 ms.
135+
# This can cause sleep to take longer than intended. So we sleep
136+
# less, and then do a few small sync-sleeps that have high accuracy.
137+
await sleep(max(0, sleep_time - 0.0156))
138+
sleep_time = delay - (time.perf_counter() - last_tick_time)
139+
while sleep_time > 0:
140+
time.sleep(min(sleep_time, 0.001)) # sleep hard for at most 1ms
141+
await sleep(0) # Allow other tasks to run but don't wait
142+
sleep_time = delay - (time.perf_counter() - last_tick_time)
143+
else:
144+
# Wait. Even if delay is zero, it gives control back to the loop,
145+
# allowing other tasks to do work.
146+
await sleep(max(0, sleep_time))
134147

135148
# Below is the "tick"
136149

rendercanvas/qt.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
WA_PaintOnScreen = QtCore.Qt.WidgetAttribute.WA_PaintOnScreen
3232
WA_DeleteOnClose = QtCore.Qt.WidgetAttribute.WA_DeleteOnClose
3333
WA_InputMethodEnabled = QtCore.Qt.WidgetAttribute.WA_InputMethodEnabled
34-
PreciseTimer = QtCore.Qt.TimerType.PreciseTimer
3534
KeyboardModifiers = QtCore.Qt.KeyboardModifier
3635
FocusPolicy = QtCore.Qt.FocusPolicy
3736
CursorShape = QtCore.Qt.CursorShape
@@ -42,7 +41,6 @@
4241
WA_PaintOnScreen = QtCore.Qt.WA_PaintOnScreen
4342
WA_DeleteOnClose = QtCore.Qt.WA_DeleteOnClose
4443
WA_InputMethodEnabled = QtCore.Qt.WA_InputMethodEnabled
45-
PreciseTimer = QtCore.Qt.PreciseTimer
4644
KeyboardModifiers = QtCore.Qt
4745
FocusPolicy = QtCore.Qt
4846
CursorShape = QtCore.Qt

rendercanvas/raw.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def _rc_run(self):
6060
break
6161
else:
6262
# Wait until its time for it to be called
63+
# Note that on Windows, the accuracy of the timeout is 15.6 ms
6364
wait_time = wrapper.time - time.perf_counter()
6465
self._event.wait(max(wait_time, 0))
6566

0 commit comments

Comments
 (0)