diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ba856f..0f2a440 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 3.0.1 + +* Timers now have `fps` and `fps_average` properties for obtaining the current and average frame rate +* Added `WindowConfig.hidden_window_framerate_limit` limiting framerate when the window is hidden. + The default value is currently 30 fps. This can be disabled by setting the value to 0. + This change combats framerate spikes in the thousands when the window is minimized eating up + battery life and resources. +* `run_window_config` was split into `create_window_config_instance` and `run_window_config_instance` + making customization easier. `run_window_config` will still behave as before. +* Minimized / iconified windows are now framerate capped +* Some doc improvements + ## 3.0.0 * All callback functions now has an `on_` prefix meaning existing code will need updating. The old names was somewhat unambiguous and was a source of confusion. It also makes it easier to separate the callback functions from other methods. diff --git a/docs/conf.py b/docs/conf.py index 56074a1..a748936 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -60,7 +60,7 @@ def __getattr__(cls: Any, name: Any) -> MagicMock: author = "Einar Forselv" # The short X.Y version -version = "3.0.0" +version = "3.0.1" # The full version, including alpha/beta/rc tags release = version diff --git a/moderngl_window/__init__.py b/moderngl_window/__init__.py index 57a23a8..be975ae 100644 --- a/moderngl_window/__init__.py +++ b/moderngl_window/__init__.py @@ -2,11 +2,11 @@ General helper functions aiding in the boostrapping of this library. """ -# pylint: disable = redefined-outer-name, too-few-public-methods import argparse import logging import os import sys +import time import weakref from pathlib import Path from typing import Any, Optional @@ -19,7 +19,7 @@ from moderngl_window.utils.keymaps import AZERTY, QWERTY, KeyMap, KeyMapFactory # noqa from moderngl_window.utils.module_loading import import_string -__version__ = "3.0.0" +__version__ = "3.0.1" IGNORE_DIRS = [ "__pycache__", @@ -278,6 +278,13 @@ def run_window_config_instance(config: WindowConfig) -> None: while not window.is_closing: current_time, delta = timer.next_frame() + # Framerate limit for hidden windows + if not window.visible and config.hidden_window_framerate_limit > 0: + expected_delta_time = 1.0 / config.hidden_window_framerate_limit + sleep_time = expected_delta_time - delta + if sleep_time > 0: + time.sleep(sleep_time) + if config.clear_color is not None: window.clear(*config.clear_color) diff --git a/moderngl_window/context/base/window.py b/moderngl_window/context/base/window.py index 9fed325..0dacfc2 100644 --- a/moderngl_window/context/base/window.py +++ b/moderngl_window/context/base/window.py @@ -1070,6 +1070,17 @@ def on_key_event(self, key, action, modifiers): # Default value resource_dir = None """ + hidden_window_framerate_limit = 30 + """ + The framerate limit for hidden windows. This is useful for windows that + should not render at full speed when hidden. On some platforms the + render loop can spike to thousands of frames per second when hidden + eating up battery life on laptops. + + A value less than 0 will disable the framerate limit. Otherwise the + the value is a suggested limit in frames per second. + """ + log_level = logging.INFO """ Sets the log level for this library using the standard `logging` module. diff --git a/moderngl_window/context/glfw/window.py b/moderngl_window/context/glfw/window.py index 0fb34d0..645b9ab 100644 --- a/moderngl_window/context/glfw/window.py +++ b/moderngl_window/context/glfw/window.py @@ -397,6 +397,7 @@ def glfw_window_iconify(self, window: Any, iconified: int) -> None: window: The window iconified (int): 1 = minimized, 0 = restored. """ + self._visible = iconified == 0 self._iconify_func(True if iconified == 1 else False) def glfw_window_close(self, window: Any) -> None: diff --git a/moderngl_window/context/pygame2/window.py b/moderngl_window/context/pygame2/window.py index 2959e32..5085508 100644 --- a/moderngl_window/context/pygame2/window.py +++ b/moderngl_window/context/pygame2/window.py @@ -325,8 +325,10 @@ def process_events(self) -> None: # Window iconify state if getattr(event, "state", None) == 2: if event.gain: + self._visible = True self._iconify_func(False) else: + self._visible = False self._iconify_func(True) # This is also a problem on linux, but is too disruptive during resize events diff --git a/moderngl_window/context/pyglet/window.py b/moderngl_window/context/pyglet/window.py index 4cb47e9..89d13ce 100644 --- a/moderngl_window/context/pyglet/window.py +++ b/moderngl_window/context/pyglet/window.py @@ -357,10 +357,12 @@ def on_close(self) -> None: def on_show(self) -> None: """Called when window first appear or restored from hidden state""" + self._visible = True self._iconify_func(False) def on_hide(self) -> None: """Called when window is minimized""" + self._visible = False self._iconify_func(True) def on_file_drop(self, x: int, y: int, paths: list[Union[str, Path]]) -> None: diff --git a/moderngl_window/context/pyqt5/window.py b/moderngl_window/context/pyqt5/window.py index fbf15dc..b16108e 100644 --- a/moderngl_window/context/pyqt5/window.py +++ b/moderngl_window/context/pyqt5/window.py @@ -361,10 +361,12 @@ def close(self) -> None: def show_event(self, event: QtCore.QEvent) -> None: """The standard Qt show event""" + self._visible = True self._iconify_func(False) def hide_event(self, event: QtCore.QEvent) -> None: """The standard Qt hide event""" + self._visible = False self._iconify_func(True) def destroy(self) -> None: diff --git a/moderngl_window/context/pyside2/window.py b/moderngl_window/context/pyside2/window.py index eafd138..97c66af 100644 --- a/moderngl_window/context/pyside2/window.py +++ b/moderngl_window/context/pyside2/window.py @@ -361,10 +361,12 @@ def close(self) -> None: def show_event(self, event: QtCore.QEvent) -> None: """The standard Qt show event""" + self._visible = True self._iconify_func(False) def hide_event(self, event: QtCore.QEvent) -> None: """The standard Qt hide event""" + self._visible = False self._iconify_func(True) def destroy(self) -> None: diff --git a/moderngl_window/context/sdl2/window.py b/moderngl_window/context/sdl2/window.py index f89b1cb..5d54bd7 100644 --- a/moderngl_window/context/sdl2/window.py +++ b/moderngl_window/context/sdl2/window.py @@ -311,8 +311,10 @@ def process_events(self) -> None: ]: self.resize(event.window.data1, event.window.data2) elif event.window.event == sdl2.SDL_WINDOWEVENT_MINIMIZED: + self._visible = False self._iconify_func(True) elif event.window.event == sdl2.SDL_WINDOWEVENT_RESTORED: + self._visible = True self._iconify_func(False) def close(self) -> None: diff --git a/moderngl_window/context/tk/window.py b/moderngl_window/context/tk/window.py index 3346b2a..9ddb80c 100644 --- a/moderngl_window/context/tk/window.py +++ b/moderngl_window/context/tk/window.py @@ -276,9 +276,11 @@ def tk_close_window(self) -> None: self._close = True def tk_map(self, event: tkinter.Event) -> None: + self._visible = True self._iconify_func(False) def tk_unmap(self, event: tkinter.Event) -> None: + self._visible = False self._iconify_func(True) def destroy(self) -> None: diff --git a/pyproject.toml b/pyproject.toml index 56afdbb..2185960 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "moderngl-window" -version = "3.0.0" +version = "3.0.1" description = "A cross platform helper library for ModernGL making window creation and resource loading simple" readme = "README.md" authors = [{ name = "Einar Forselv", email = "eforselv@gmail.com" }]