Skip to content
Snippets Groups Projects
Verified Commit e95404e9 authored by dos's avatar dos
Browse files

py,st3m: Only redraw overlays when something changes

Let each overlay report whether it needs to redraw itself
and what region of the screen it's interested in.

In order to not introduce unneccessary allocations, a single
mutable region object gets passed into each overlay.
parent d941f137
No related branches found
No related tags found
No related merge requests found
Pipeline #8169 passed
...@@ -38,19 +38,78 @@ _all_kinds = [ ...@@ -38,19 +38,78 @@ _all_kinds = [
OverlayKind.Toast, OverlayKind.Toast,
] ]
_clip_x0 = 120
_clip_x1 = 120 class Region:
_clip_y0 = 0 x0: int
_clip_y1 = 0 y0: int
x1: int
y1: int
def __init__(self, *args):
if not args:
self.clear()
else:
self.set(*args)
def set(self, x0, y0, x1, y1):
self.x0 = x0
self.y0 = y0
self.x1 = x1
self.y1 = y1
def add(self, x0, y0, x1, y1):
self.x0 = min(self.x0, x0)
self.y0 = min(self.y0, y0)
self.x1 = max(self.x1, x1)
self.y1 = max(self.y1, y1)
def add_region(self, rect):
self.add(rect.x0, rect.y0, rect.x1, rect.y1)
def clear(self):
self.x0 = self.y0 = self.x1 = self.y1 = -120
def fill(self, ctx):
ctx.rectangle(self.x0, self.y0, self.x1 - self.x0, self.y1 - self.y0)
ctx.fill()
def is_empty(self):
return self.x1 == self.x0 or self.y1 == self.y0
def set_clip(self):
sys_display.overlay_clip(
self.x0 + 120, self.y0 + 120, self.x1 + 120, self.y1 + 120
)
def copy(self, rect):
self.x0 = rect.x0
self.x1 = rect.x1
self.y0 = rect.y0
self.y1 = rect.y1
def __eq__(self, rect):
return (
self.x0 == rect.x0
and self.x1 == rect.x1
and self.y0 == rect.y0
and self.y1 == rect.y1
)
def __repr__(self):
return f"({self.x0} x {self.y0} - {self.x1} x {self.y1})"
class Overlay(Responder): class Overlay(Responder):
""" """
An Overlay is a Responder with some kind of OverlayKind attached.g An Overlay is a Responder with some kind of OverlayKind attached.
""" """
kind: OverlayKind kind: OverlayKind
def needs_redraw(rect: Region) -> bool:
rect.add(-120, -120, 120, 120)
return True
class Compositor(Responder): class Compositor(Responder):
""" """
...@@ -67,7 +126,8 @@ class Compositor(Responder): ...@@ -67,7 +126,8 @@ class Compositor(Responder):
OverlayKind.Toast: True, OverlayKind.Toast: True,
} }
self._last_fps_string = "" self._last_fps_string = ""
self._frame_skip = 0 self._clip_rect = Region()
self._last_clip = Region()
def _enabled_overlays(self) -> List[Responder]: def _enabled_overlays(self) -> List[Responder]:
res: List[Responder] = [] res: List[Responder] = []
...@@ -87,32 +147,31 @@ class Compositor(Responder): ...@@ -87,32 +147,31 @@ class Compositor(Responder):
or settings.onoff_show_fps.value or settings.onoff_show_fps.value
): ):
return return
for overlay in self._enabled_overlays(): overlays = self._enabled_overlays()
overlay.think(ins, delta_ms) for i in range(len(overlays)):
overlays[i].think(ins, delta_ms)
def draw(self, ctx: Context) -> None: def draw(self, ctx: Context) -> None:
global _clip_x0, _clip_y0, _clip_x1, _clip_y1
self.main.draw(ctx) self.main.draw(ctx)
if (sys_display.get_mode() & sys_display.osd) == 0: if (sys_display.get_mode() & sys_display.osd) == 0:
return return
overlays = self._enabled_overlays()
if settings.onoff_show_fps.value: if settings.onoff_show_fps.value:
fps_string = "{0:.1f}".format(sys_display.fps()) fps_string = "{0:.1f}".format(sys_display.fps())
if fps_string != self._last_fps_string and self._frame_skip <= 0: if fps_string != self._last_fps_string:
octx = sys_display.ctx(sys_display.osd) octx = sys_display.ctx(sys_display.osd)
self._last_fps_string = fps_string self._last_fps_string = fps_string
_clip_x0 = 0 self._clip_rect.set(-120, -120, 120, -96)
_clip_y1 = 0 self._last_clip.add_region(self._clip_rect)
_clip_x1 = 240 if self._last_clip != self._clip_rect:
_clip_y1 = 24 octx.save()
octx.compositing_mode = octx.CLEAR
self._last_clip.fill(octx)
octx.restore()
octx.save() octx.save()
octx.rgba(0, 0, 0, 0.5) octx.rgba(0, 0, 0, 0.5)
octx.compositing_mode = octx.COPY octx.compositing_mode = octx.COPY
octx.rectangle( self._clip_rect.fill(octx)
_clip_x0 - 120,
_clip_y0 - 120,
_clip_x1 - _clip_x0 + 1,
_clip_y1 - _clip_y0 + 2,
).fill()
octx.restore() octx.restore()
octx.gray(1) octx.gray(1)
octx.font_size = 18 octx.font_size = 18
...@@ -120,34 +179,32 @@ class Compositor(Responder): ...@@ -120,34 +179,32 @@ class Compositor(Responder):
octx.move_to(0, -103) octx.move_to(0, -103)
octx.text_align = octx.CENTER octx.text_align = octx.CENTER
octx.text(fps_string) octx.text(fps_string)
self._frame_skip = 5
sys_display.update(octx) sys_display.update(octx)
sys_display.overlay_clip(_clip_x0, _clip_y0, _clip_x1, _clip_y1) self._clip_rect.set_clip()
self._last_clip.copy(self._clip_rect)
else: else:
self._frame_skip -= 1 self._clip_rect.clear()
else: redraw = False
if self._frame_skip <= 0: for i in range(len(overlays)):
overlays = self._enabled_overlays() redraw = overlays[i].needs_redraw(self._clip_rect) or redraw
_clip_x0 = 80 if self._clip_rect.is_empty():
_clip_y0 = 0 self._clip_rect.set_clip()
_clip_x1 = 160 return
_clip_y1 = 0 if self._last_clip != self._clip_rect:
redraw = True
if not redraw:
return
octx = sys_display.ctx(sys_display.osd) octx = sys_display.ctx(sys_display.osd)
octx.save() octx.save()
octx.compositing_mode = octx.CLEAR octx.compositing_mode = octx.CLEAR
if len(overlays) == 1: self._last_clip.add_region(self._clip_rect)
octx.rectangle(-50, -120, 100, 150).fill() self._last_clip.fill(octx)
else:
octx.rectangle(-120, -120, 240, 240).fill()
octx.restore() octx.restore()
for overlay in overlays: for i in range(len(overlays)):
overlay.draw(octx) overlays[i].draw(octx)
sys_display.update(octx) sys_display.update(octx)
sys_display.overlay_clip(_clip_x0, _clip_y0, _clip_x1, _clip_y1) self._clip_rect.set_clip()
self._frame_skip = 2 self._last_clip.copy(self._clip_rect)
else:
self._frame_skip -= 1
def add_overlay(self, ov: Overlay) -> None: def add_overlay(self, ov: Overlay) -> None:
""" """
...@@ -235,6 +292,10 @@ class OverlayDebug(Overlay): ...@@ -235,6 +292,10 @@ class OverlayDebug(Overlay):
ctx.gray(1).move_to(0, 86).text(self.text()) ctx.gray(1).move_to(0, 86).text(self.text())
ctx.restore() ctx.restore()
def needs_redraw(self, rect: Region) -> bool:
rect.add(-120, 80, 120, 92)
return True
class OverlayCaptouch(Overlay): class OverlayCaptouch(Overlay):
kind = OverlayKind.Touch kind = OverlayKind.Touch
...@@ -273,16 +334,15 @@ class OverlayCaptouch(Overlay): ...@@ -273,16 +334,15 @@ class OverlayCaptouch(Overlay):
dot.think(ins, delta_ms) dot.think(ins, delta_ms)
def draw(self, ctx: Context) -> None: def draw(self, ctx: Context) -> None:
global _clip_y1, _clip_x0, _clip_y0, _clip_y1
ctx.rgb(1, 0, 1) ctx.rgb(1, 0, 1)
for dot in self.dots: for dot in self.dots:
ctx.save() ctx.save()
dot.draw(ctx) dot.draw(ctx)
ctx.restore() ctx.restore()
_clip_x0 = 0
_clip_x1 = 240 def needs_redraw(self, rect: Region) -> bool:
_clip_y0 = 0 rect.add(0, 0, 240, 240)
_clip_y1 = 240 return True
class OverlayVolume(Overlay): class OverlayVolume(Overlay):
...@@ -336,15 +396,13 @@ class OverlayVolume(Overlay): ...@@ -336,15 +396,13 @@ class OverlayVolume(Overlay):
if self._showing is None: if self._showing is None:
return return
self._showing -= delta_ms / 2 self._showing -= delta_ms
if self._showing < 0: if self._showing < 0:
self._showing = None self._showing = None
def draw(self, ctx: Context) -> None: def draw(self, ctx: Context) -> None:
global _clip_y1
if self._showing is None: if self._showing is None:
return return
_clip_y1 = max(_clip_y1, 161)
opacity = self._showing / 200 opacity = self._showing / 200
opacity = min(opacity, 0.8) opacity = min(opacity, 0.8)
...@@ -393,6 +451,11 @@ class OverlayVolume(Overlay): ...@@ -393,6 +451,11 @@ class OverlayVolume(Overlay):
ctx.round_rectangle(-30, 20, width, 10, 3) ctx.round_rectangle(-30, 20, width, 10, 3)
ctx.fill() ctx.fill()
def needs_redraw(self, rect: Region) -> bool:
if self._showing:
rect.add(-40, -40, 40, 40)
return self._showing
class Icon(Responder): class Icon(Responder):
""" """
...@@ -403,11 +466,15 @@ class Icon(Responder): ...@@ -403,11 +466,15 @@ class Icon(Responder):
""" """
WIDTH: int = 25 WIDTH: int = 25
_changed: bool = True
@abstractmethod @abstractmethod
def visible(self) -> bool: def visible(self) -> bool:
... ...
def changed(self) -> bool:
return self._changed
class USBIcon(Icon): class USBIcon(Icon):
""" """
...@@ -418,8 +485,15 @@ class USBIcon(Icon): ...@@ -418,8 +485,15 @@ class USBIcon(Icon):
WIDTH: int = 30 WIDTH: int = 30
def __init__(self):
self._visible = sys_kernel.usb_connected()
def visible(self) -> bool: def visible(self) -> bool:
return sys_kernel.usb_connected() visible = sys_kernel.usb_connected()
if self._visible != visible:
self._changed = True
self._visible = visible
return self._visible
def draw(self, ctx: Context) -> None: def draw(self, ctx: Context) -> None:
ctx.gray(1.0) ctx.gray(1.0)
...@@ -435,6 +509,12 @@ class USBIcon(Icon): ...@@ -435,6 +509,12 @@ class USBIcon(Icon):
def think(self, ins: InputState, delta_ms: int) -> None: def think(self, ins: InputState, delta_ms: int) -> None:
pass pass
def changed(self) -> bool:
if self._changed:
self._changed = False
return True
return False
class WifiIcon(Icon): class WifiIcon(Icon):
WIDTH: int = 30 WIDTH: int = 30
...@@ -442,6 +522,7 @@ class WifiIcon(Icon): ...@@ -442,6 +522,7 @@ class WifiIcon(Icon):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self._rssi: float = -120 self._rssi: float = -120
self._changed = True
def visible(self) -> bool: def visible(self) -> bool:
return st3m.wifi.enabled() return st3m.wifi.enabled()
...@@ -461,8 +542,13 @@ class WifiIcon(Icon): ...@@ -461,8 +542,13 @@ class WifiIcon(Icon):
ctx.gray(1.0 if r > -95 else 0.2) ctx.gray(1.0 if r > -95 else 0.2)
ctx.arc(0, 65, 40, a, b, 0).stroke() ctx.arc(0, 65, 40, a, b, 0).stroke()
self._changed = False
def think(self, ins: InputState, delta_ms: int) -> None: def think(self, ins: InputState, delta_ms: int) -> None:
self._rssi = st3m.wifi.rssi() rssi = st3m.wifi.rssi()
if rssi != self._rssi:
self._rssi = rssi
self._changed = True
class BatteryIcon(Icon): class BatteryIcon(Icon):
...@@ -472,6 +558,7 @@ class BatteryIcon(Icon): ...@@ -472,6 +558,7 @@ class BatteryIcon(Icon):
super().__init__() super().__init__()
self._percent = 100.0 self._percent = 100.0
self._charging = False self._charging = False
self._changed = False
def visible(self) -> bool: def visible(self) -> bool:
return True return True
...@@ -506,9 +593,17 @@ class BatteryIcon(Icon): ...@@ -506,9 +593,17 @@ class BatteryIcon(Icon):
ctx.line_to(40, 35 - 10) ctx.line_to(40, 35 - 10)
ctx.stroke() ctx.stroke()
self._changed = False
def think(self, ins: InputState, delta_ms: int) -> None: def think(self, ins: InputState, delta_ms: int) -> None:
self._percent = approximate_battery_percentage(power.battery_voltage) percent = approximate_battery_percentage(power.battery_voltage)
self._charging = power.battery_charging charging = power.battery_charging
if percent != self._percent:
self._percent = percent
self._changed = True
if charging != self._charging:
self._charging = charging
self._changed = True
class IconTray(Overlay): class IconTray(Overlay):
...@@ -528,22 +623,28 @@ class IconTray(Overlay): ...@@ -528,22 +623,28 @@ class IconTray(Overlay):
def think(self, ins: InputState, delta_ms: int) -> None: def think(self, ins: InputState, delta_ms: int) -> None:
self.visible = [i for i in self.icons if i.visible()] self.visible = [i for i in self.icons if i.visible()]
for v in self.visible: for i in range(len(self.visible)):
v.think(ins, delta_ms) self.visible[i].think(ins, delta_ms)
def draw(self, ctx: Context) -> None: def draw(self, ctx: Context) -> None:
global _clip_y1 if not self.visible:
if len(self.visible) < 1:
return return
_clip_y1 = max(_clip_y1, 32)
width = 0 width = 0
for icon in self.visible: for i in range(len(self.visible)):
width += icon.WIDTH width += self.visible[i].WIDTH
x0 = width / -2 + self.visible[0].WIDTH / 2 x0 = width / -2 + self.visible[0].WIDTH / 2
for i, v in enumerate(self.visible): for i in range(len(self.visible)):
ctx.save() ctx.save()
ctx.translate(x0, -100) ctx.translate(x0, -100)
ctx.scale(0.13, 0.13) ctx.scale(0.13, 0.13)
v.draw(ctx) self.visible[i].draw(ctx)
ctx.restore() ctx.restore()
x0 = x0 + v.WIDTH x0 = x0 + self.visible[i].WIDTH
def needs_redraw(self, rect: Region) -> bool:
if self.visible:
rect.add(-40, -120, 40, -88)
for i in range(len(self.visible)):
if self.visible[i].changed():
return True
return False
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment