diff --git a/python_payload/st3m/ui/elements/overlays.py b/python_payload/st3m/ui/elements/overlays.py
index d582e8736da5ade734740a4598474c5f5a7b38a3..5ab91ab76917c0f2ed957fc9715f691e9690f17f 100644
--- a/python_payload/st3m/ui/elements/overlays.py
+++ b/python_payload/st3m/ui/elements/overlays.py
@@ -38,19 +38,78 @@ _all_kinds = [
     OverlayKind.Toast,
 ]
 
-_clip_x0 = 120
-_clip_x1 = 120
-_clip_y0 = 0
-_clip_y1 = 0
+
+class Region:
+    x0: int
+    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):
     """
-    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
 
+    def needs_redraw(rect: Region) -> bool:
+        rect.add(-120, -120, 120, 120)
+        return True
+
 
 class Compositor(Responder):
     """
@@ -67,7 +126,8 @@ class Compositor(Responder):
             OverlayKind.Toast: True,
         }
         self._last_fps_string = ""
-        self._frame_skip = 0
+        self._clip_rect = Region()
+        self._last_clip = Region()
 
     def _enabled_overlays(self) -> List[Responder]:
         res: List[Responder] = []
@@ -87,32 +147,31 @@ class Compositor(Responder):
             or settings.onoff_show_fps.value
         ):
             return
-        for overlay in self._enabled_overlays():
-            overlay.think(ins, delta_ms)
+        overlays = self._enabled_overlays()
+        for i in range(len(overlays)):
+            overlays[i].think(ins, delta_ms)
 
     def draw(self, ctx: Context) -> None:
-        global _clip_x0, _clip_y0, _clip_x1, _clip_y1
         self.main.draw(ctx)
         if (sys_display.get_mode() & sys_display.osd) == 0:
             return
+        overlays = self._enabled_overlays()
         if settings.onoff_show_fps.value:
             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)
                 self._last_fps_string = fps_string
-                _clip_x0 = 0
-                _clip_y1 = 0
-                _clip_x1 = 240
-                _clip_y1 = 24
+                self._clip_rect.set(-120, -120, 120, -96)
+                self._last_clip.add_region(self._clip_rect)
+                if self._last_clip != self._clip_rect:
+                    octx.save()
+                    octx.compositing_mode = octx.CLEAR
+                    self._last_clip.fill(octx)
+                    octx.restore()
                 octx.save()
                 octx.rgba(0, 0, 0, 0.5)
                 octx.compositing_mode = octx.COPY
-                octx.rectangle(
-                    _clip_x0 - 120,
-                    _clip_y0 - 120,
-                    _clip_x1 - _clip_x0 + 1,
-                    _clip_y1 - _clip_y0 + 2,
-                ).fill()
+                self._clip_rect.fill(octx)
                 octx.restore()
                 octx.gray(1)
                 octx.font_size = 18
@@ -120,34 +179,32 @@ class Compositor(Responder):
                 octx.move_to(0, -103)
                 octx.text_align = octx.CENTER
                 octx.text(fps_string)
-                self._frame_skip = 5
                 sys_display.update(octx)
-                sys_display.overlay_clip(_clip_x0, _clip_y0, _clip_x1, _clip_y1)
-            else:
-                self._frame_skip -= 1
+                self._clip_rect.set_clip()
+                self._last_clip.copy(self._clip_rect)
         else:
-            if self._frame_skip <= 0:
-                overlays = self._enabled_overlays()
-                _clip_x0 = 80
-                _clip_y0 = 0
-                _clip_x1 = 160
-                _clip_y1 = 0
-
-                octx = sys_display.ctx(sys_display.osd)
-                octx.save()
-                octx.compositing_mode = octx.CLEAR
-                if len(overlays) == 1:
-                    octx.rectangle(-50, -120, 100, 150).fill()
-                else:
-                    octx.rectangle(-120, -120, 240, 240).fill()
-                octx.restore()
-                for overlay in overlays:
-                    overlay.draw(octx)
-                sys_display.update(octx)
-                sys_display.overlay_clip(_clip_x0, _clip_y0, _clip_x1, _clip_y1)
-                self._frame_skip = 2
-            else:
-                self._frame_skip -= 1
+            self._clip_rect.clear()
+            redraw = False
+            for i in range(len(overlays)):
+                redraw = overlays[i].needs_redraw(self._clip_rect) or redraw
+            if self._clip_rect.is_empty():
+                self._clip_rect.set_clip()
+                return
+            if self._last_clip != self._clip_rect:
+                redraw = True
+            if not redraw:
+                return
+            octx = sys_display.ctx(sys_display.osd)
+            octx.save()
+            octx.compositing_mode = octx.CLEAR
+            self._last_clip.add_region(self._clip_rect)
+            self._last_clip.fill(octx)
+            octx.restore()
+            for i in range(len(overlays)):
+                overlays[i].draw(octx)
+            sys_display.update(octx)
+            self._clip_rect.set_clip()
+            self._last_clip.copy(self._clip_rect)
 
     def add_overlay(self, ov: Overlay) -> None:
         """
@@ -235,6 +292,10 @@ class OverlayDebug(Overlay):
         ctx.gray(1).move_to(0, 86).text(self.text())
         ctx.restore()
 
+    def needs_redraw(self, rect: Region) -> bool:
+        rect.add(-120, 80, 120, 92)
+        return True
+
 
 class OverlayCaptouch(Overlay):
     kind = OverlayKind.Touch
@@ -273,16 +334,15 @@ class OverlayCaptouch(Overlay):
             dot.think(ins, delta_ms)
 
     def draw(self, ctx: Context) -> None:
-        global _clip_y1, _clip_x0, _clip_y0, _clip_y1
         ctx.rgb(1, 0, 1)
         for dot in self.dots:
             ctx.save()
             dot.draw(ctx)
             ctx.restore()
-        _clip_x0 = 0
-        _clip_x1 = 240
-        _clip_y0 = 0
-        _clip_y1 = 240
+
+    def needs_redraw(self, rect: Region) -> bool:
+        rect.add(0, 0, 240, 240)
+        return True
 
 
 class OverlayVolume(Overlay):
@@ -336,15 +396,13 @@ class OverlayVolume(Overlay):
 
         if self._showing is None:
             return
-        self._showing -= delta_ms / 2
+        self._showing -= delta_ms
         if self._showing < 0:
             self._showing = None
 
     def draw(self, ctx: Context) -> None:
-        global _clip_y1
         if self._showing is None:
             return
-        _clip_y1 = max(_clip_y1, 161)
 
         opacity = self._showing / 200
         opacity = min(opacity, 0.8)
@@ -393,6 +451,11 @@ class OverlayVolume(Overlay):
         ctx.round_rectangle(-30, 20, width, 10, 3)
         ctx.fill()
 
+    def needs_redraw(self, rect: Region) -> bool:
+        if self._showing:
+            rect.add(-40, -40, 40, 40)
+        return self._showing
+
 
 class Icon(Responder):
     """
@@ -403,11 +466,15 @@ class Icon(Responder):
     """
 
     WIDTH: int = 25
+    _changed: bool = True
 
     @abstractmethod
     def visible(self) -> bool:
         ...
 
+    def changed(self) -> bool:
+        return self._changed
+
 
 class USBIcon(Icon):
     """
@@ -418,8 +485,15 @@ class USBIcon(Icon):
 
     WIDTH: int = 30
 
+    def __init__(self):
+        self._visible = sys_kernel.usb_connected()
+
     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:
         ctx.gray(1.0)
@@ -435,6 +509,12 @@ class USBIcon(Icon):
     def think(self, ins: InputState, delta_ms: int) -> None:
         pass
 
+    def changed(self) -> bool:
+        if self._changed:
+            self._changed = False
+            return True
+        return False
+
 
 class WifiIcon(Icon):
     WIDTH: int = 30
@@ -442,6 +522,7 @@ class WifiIcon(Icon):
     def __init__(self) -> None:
         super().__init__()
         self._rssi: float = -120
+        self._changed = True
 
     def visible(self) -> bool:
         return st3m.wifi.enabled()
@@ -461,8 +542,13 @@ class WifiIcon(Icon):
         ctx.gray(1.0 if r > -95 else 0.2)
         ctx.arc(0, 65, 40, a, b, 0).stroke()
 
+        self._changed = False
+
     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):
@@ -472,6 +558,7 @@ class BatteryIcon(Icon):
         super().__init__()
         self._percent = 100.0
         self._charging = False
+        self._changed = False
 
     def visible(self) -> bool:
         return True
@@ -506,9 +593,17 @@ class BatteryIcon(Icon):
             ctx.line_to(40, 35 - 10)
             ctx.stroke()
 
+        self._changed = False
+
     def think(self, ins: InputState, delta_ms: int) -> None:
-        self._percent = approximate_battery_percentage(power.battery_voltage)
-        self._charging = power.battery_charging
+        percent = approximate_battery_percentage(power.battery_voltage)
+        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):
@@ -528,22 +623,28 @@ class IconTray(Overlay):
 
     def think(self, ins: InputState, delta_ms: int) -> None:
         self.visible = [i for i in self.icons if i.visible()]
-        for v in self.visible:
-            v.think(ins, delta_ms)
+        for i in range(len(self.visible)):
+            self.visible[i].think(ins, delta_ms)
 
     def draw(self, ctx: Context) -> None:
-        global _clip_y1
-        if len(self.visible) < 1:
+        if not self.visible:
             return
-        _clip_y1 = max(_clip_y1, 32)
         width = 0
-        for icon in self.visible:
-            width += icon.WIDTH
+        for i in range(len(self.visible)):
+            width += self.visible[i].WIDTH
         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.translate(x0, -100)
             ctx.scale(0.13, 0.13)
-            v.draw(ctx)
+            self.visible[i].draw(ctx)
             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