Skip to content

Commit

Permalink
feat: add two throttling mechanisms to avoid sending too many frames …
Browse files Browse the repository at this point in the history
…to the display, one based on total bandwidth of all draw commands of all `HeadlessWidget`s and the other based on set fps of a particular `HeadlessWidget`
  • Loading branch information
sassanh committed Oct 29, 2024
1 parent 5cc0c19 commit a5f6749
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 81 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Upcoming

- feat: divide each frame into multiple rectangles, compare rectangle with the same rectangle in the previous frame and update only the changed ones
- feat: add two throttling mechanisms to avoid sending too many frames to the display, one based on total bandwidth of all draw commands of all `HeadlessWidget`s and the other based on set fps of a particular `HeadlessWidget`

## Version 0.11.1

Expand Down
7 changes: 0 additions & 7 deletions demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,20 @@
from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING

import png

from headless_kivy import HeadlessWidget, config

if TYPE_CHECKING:
from threading import Thread


WIDTH = 400
HEIGHT = 240


def render(
*,
regions: list[config.Region],
last_render_thread: Thread,
) -> None:
"""Render the data to a png file."""
_ = last_render_thread
data = regions[0]['data']
with Path('demo.png').open('wb') as file:
png.Writer(
Expand Down
50 changes: 32 additions & 18 deletions headless_kivy/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,21 @@
from kivy.metrics import dp

from headless_kivy.constants import (
BANDWIDTH_LIMIT,
BANDWIDTH_LIMIT_OVERHEAD,
BANDWIDTH_LIMIT_WINDOW,
DOUBLE_BUFFERING,
FLIP_HORIZONTAL,
FLIP_VERTICAL,
HEIGHT,
IS_DEBUG_MODE,
MAX_FPS,
REGION_SIZE,
ROTATION,
WIDTH,
)
from headless_kivy.logger import add_file_handler, add_stdout_handler

if TYPE_CHECKING:
from threading import Thread

from numpy._typing import NDArray

kivy.require('2.1.0')
Expand All @@ -39,8 +39,12 @@ class SetupHeadlessConfig(TypedDict):
----------
callback: `Callback`
The callback function that will render the data to the screen.
max_fps: `int`, optional
Maximum frames per second for the Kivy application.
bandwidth_limit: `int`, optional
Maximum bandwidth limit in pixels per second, no limit if set to 0.
bandwidth_limit_window: `float`, optional
Length of the window in seconds to check the bandwidth limit.
bandwidth_limit_overhead: `int`, optional
Bandwidth overhead of each draw regardless of the region size.
width: `int`, optional
The width of the display in pixels.
height: `int`, optional
Expand All @@ -64,7 +68,9 @@ class SetupHeadlessConfig(TypedDict):
"""

callback: Callback
max_fps: NotRequired[int]
bandwidth_limit: NotRequired[int]
bandwidth_limit_window: NotRequired[float]
bandwidth_limit_overhead: NotRequired[int]
width: NotRequired[int]
height: NotRequired[int]
is_debug_mode: NotRequired[bool]
Expand Down Expand Up @@ -104,10 +110,7 @@ def setup_headless_kivy(config: SetupHeadlessConfig) -> None:
Config.set('kivy', 'kivy_clock', 'default')
Config.set('graphics', 'fbo', 'force-hardware')
Config.set('graphics', 'fullscreen', '0')
Config.set('graphics', 'maxfps', f'{max_fps()}')
Config.set('graphics', 'multisamples', '1')
Config.set('graphics', 'resizable', '0')
Config.set('graphics', 'vsync', '0')
Config.set('graphics', 'width', f'{width()}')
Config.set('graphics', 'height', f'{height()}')

Expand Down Expand Up @@ -135,12 +138,7 @@ class Region(TypedDict):
class Callback(Protocol):
"""The signature of the renderer function."""

def __call__(
self: Callback,
*,
regions: list[Region],
last_render_thread: Thread,
) -> None:
def __call__(self: Callback, *, regions: list[Region]) -> None:
"""Render the data to the screen."""


Expand All @@ -153,10 +151,26 @@ def callback() -> Callback:


@cache
def max_fps() -> int:
"""Return the maximum frames per second for the Kivy application."""
def bandwidth_limit() -> int:
"""Return the bandwidth limit in pixels per second."""
if _config:
return _config.get('bandwidth_limit', BANDWIDTH_LIMIT)
report_uninitialized()


@cache
def bandwidth_limit_window() -> float:
"""Return the length of the window in seconds to check the bandwidth limit."""
if _config:
return _config.get('bandwidth_limit_window', BANDWIDTH_LIMIT_WINDOW)
report_uninitialized()


@cache
def bandwidth_limit_overhead() -> int:
"""Return the bandwidth overhead of each draw regardless of the region size."""
if _config:
return _config.get('max_fps', MAX_FPS)
return _config.get('bandwidth_limit_overhead', BANDWIDTH_LIMIT_OVERHEAD)
report_uninitialized()


Expand Down
8 changes: 7 additions & 1 deletion headless_kivy/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@

from str_to_bool import str_to_bool

MAX_FPS = int(os.environ.get('HEADLESS_KIVY_MAX_FPS', '32'))
BANDWIDTH_LIMIT = int(os.environ.get('HEADLESS_KIVY_BANDWIDTH_LIMIT', '0'))
BANDWIDTH_LIMIT_WINDOW = float(
os.environ.get('HEADLESS_KIVY_BANDWIDTH_LIMIT_WINDOW', '1'),
)
BANDWIDTH_LIMIT_OVERHEAD = int(
os.environ.get('HEADLESS_KIVY_BANDWIDTH_LIMIT_OVERHEAD', '0'),
)
WIDTH = int(os.environ.get('HEADLESS_KIVY_WIDTH', '240'))
HEIGHT = int(os.environ.get('HEADLESS_KIVY_HEIGHT', '240'))
IS_DEBUG_MODE = str_to_bool(os.environ.get('HEADLESS_KIVY_DEBUG', 'False')) == 1
Expand Down
2 changes: 1 addition & 1 deletion headless_kivy/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def transform_coordinates(
) -> tuple[int, int, int, int]:
"""Transform the coordinates of a region."""
y1, x1, y2, x2 = region[:4]
h, w = int(dp(config.width())), int(dp(config.height()))
w, h = int(dp(config.width())), int(dp(config.height()))
positions = {
0: (x1, y1, x2, y2),
1: (y1, w - x2, y2, w - x1),
Expand Down
Loading

0 comments on commit a5f6749

Please sign in to comment.