|
| 1 | +""" |
| 2 | +PyDevices Multi-Interpreter Benchmark (Hero Canvas App for pydevices-examples) |
| 3 | +============================================================================= |
| 4 | +Interactive multi-interpreter execution matrix and benchmark meter: |
| 5 | +- 5-target performance comparator (MicroPython, CircuitPython, CPython, WASM, Android) |
| 6 | +- Real-time frame throughput and dirty-rect blit telemetry |
| 7 | +- Interactive benchmark mode selector |
| 8 | +""" |
| 9 | + |
| 10 | +import math |
| 11 | +import sys |
| 12 | +import time |
| 13 | +import types |
| 14 | +from random import random |
| 15 | + |
| 16 | +try: |
| 17 | + import js |
| 18 | + from js import document, window |
| 19 | + from pyodide.ffi import create_proxy |
| 20 | +except ImportError: |
| 21 | + js = None |
| 22 | + document = None |
| 23 | + window = None |
| 24 | + create_proxy = lambda fn: fn |
| 25 | + |
| 26 | +from displaydev.psdisplay import PSDisplay |
| 27 | + |
| 28 | + |
| 29 | +BENCH_MODES = ["BLIT 16-BIT", "DIRTY-RECT", "MATH / TRIG"] |
| 30 | + |
| 31 | +TARGETS = [ |
| 32 | + ("MP", "#F59E0B", 0.88), |
| 33 | + ("CP", "#38BDF8", 0.76), |
| 34 | + ("CPy", "#10B981", 0.98), |
| 35 | + ("WASM", "#8B5CF6", 0.92), |
| 36 | + ("AND", "#EC4899", 0.84), |
| 37 | +] |
| 38 | + |
| 39 | + |
| 40 | +class BenchmarkDeckHero: |
| 41 | + def __init__(self, canvas_id="hero_canvas", size=240): |
| 42 | + self.canvas_id = canvas_id |
| 43 | + self.size = size |
| 44 | + self.w = size |
| 45 | + self.h = size |
| 46 | + |
| 47 | + if "board_config" not in sys.modules: |
| 48 | + bc = types.ModuleType("board_config") |
| 49 | + bc.display_drv = PSDisplay(canvas_id, width=size, height=size) |
| 50 | + sys.modules["board_config"] = bc |
| 51 | + self.drv = bc.display_drv |
| 52 | + else: |
| 53 | + self.drv = sys.modules["board_config"].display_drv |
| 54 | + |
| 55 | + self.mode_idx = 0 |
| 56 | + self.frame_cnt = 0 |
| 57 | + self.burst = 0.0 |
| 58 | + |
| 59 | + self.draw() |
| 60 | + self._bind_events() |
| 61 | + |
| 62 | + self._tick_proxy = create_proxy(self._js_tick_cb) if window else None |
| 63 | + self._tick_interval = window.setInterval(self._tick_proxy, 33) if window else None |
| 64 | + |
| 65 | + def _js_tick_cb(self): |
| 66 | + self.tick() |
| 67 | + |
| 68 | + def _bind_events(self): |
| 69 | + if not document: |
| 70 | + return |
| 71 | + canvas = document.getElementById(self.canvas_id) |
| 72 | + if not canvas: |
| 73 | + return |
| 74 | + |
| 75 | + def on_pointer_down(event): |
| 76 | + event.preventDefault() |
| 77 | + self.mode_idx = (self.mode_idx + 1) % len(BENCH_MODES) |
| 78 | + self.burst = 1.0 |
| 79 | + self.draw() |
| 80 | + |
| 81 | + self._p_down = create_proxy(on_pointer_down) |
| 82 | + canvas.addEventListener("pointerdown", self._p_down) |
| 83 | + |
| 84 | + def tick(self): |
| 85 | + self.frame_cnt += 1 |
| 86 | + if self.burst > 0.0: |
| 87 | + self.burst = max(0.0, self.burst - 0.05) |
| 88 | + self.draw() |
| 89 | + |
| 90 | + def draw(self): |
| 91 | + if not hasattr(self.drv, "_buf_ctx") or not self.drv._buf_ctx: |
| 92 | + return |
| 93 | + ctx = self.drv._buf_ctx |
| 94 | + w, h = self.w, self.h |
| 95 | + |
| 96 | + # 1. Dark Console Background |
| 97 | + ctx.fillStyle = "#0A0E17" |
| 98 | + ctx.fillRect(0, 0, w, h) |
| 99 | + |
| 100 | + # 2. Header Bar |
| 101 | + ctx.fillStyle = "rgba(15, 23, 42, 0.95)" |
| 102 | + ctx.fillRect(0, 0, w, 28) |
| 103 | + ctx.strokeStyle = "#1E293B" |
| 104 | + ctx.lineWidth = 1 |
| 105 | + ctx.beginPath() |
| 106 | + ctx.moveTo(0, 28) |
| 107 | + ctx.lineTo(w, 28) |
| 108 | + ctx.stroke() |
| 109 | + |
| 110 | + ctx.fillStyle = "#F59E0B" |
| 111 | + ctx.font = "bold 9px system-ui, monospace" |
| 112 | + ctx.textAlign = "left" |
| 113 | + ctx.fillText("⚡ MULTI-INTERPRETER", 10, 18) |
| 114 | + |
| 115 | + ctx.fillStyle = "#10B981" |
| 116 | + ctx.textAlign = "right" |
| 117 | + ctx.fillText("5 RUNTIMES · 60FPS", w - 10, 18) |
| 118 | + |
| 119 | + # 3. Mode Pill Selector (x: 12, y: 36, w: 216, h: 26) |
| 120 | + mx, my, mw, mh = 12, 36, 216, 26 |
| 121 | + ctx.fillStyle = "#0F172A" |
| 122 | + ctx.beginPath() |
| 123 | + ctx.roundRect(mx, my, mw, mh, 6) |
| 124 | + ctx.fill() |
| 125 | + ctx.strokeStyle = "#334155" |
| 126 | + ctx.lineWidth = 1 |
| 127 | + ctx.stroke() |
| 128 | + |
| 129 | + ctx.fillStyle = "#94A3B8" |
| 130 | + ctx.font = "bold 8px system-ui, sans-serif" |
| 131 | + ctx.textAlign = "left" |
| 132 | + ctx.fillText("BENCHMARK:", mx + 8, my + 17) |
| 133 | + |
| 134 | + ctx.fillStyle = "#38BDF8" |
| 135 | + ctx.font = "bold 9px monospace" |
| 136 | + ctx.textAlign = "right" |
| 137 | + ctx.fillText(f"▶ {BENCH_MODES[self.mode_idx]}", mx + mw - 8, my + 17) |
| 138 | + |
| 139 | + # 4. Multi-Target Performance Bars (x: 12, y: 70) |
| 140 | + t = time.time() * 2.0 |
| 141 | + bar_y_start = 72 |
| 142 | + bar_h = 16 |
| 143 | + gap = 10 |
| 144 | + |
| 145 | + for i, (tag, col_hex, base_val) in enumerate(TARGETS): |
| 146 | + row_y = bar_y_start + i * (bar_h + gap) |
| 147 | + |
| 148 | + # Label Tag |
| 149 | + ctx.fillStyle = "#E2E8F0" |
| 150 | + ctx.font = "bold 9px monospace" |
| 151 | + ctx.textAlign = "left" |
| 152 | + ctx.fillText(f"{tag:<4}", 12, row_y + 12) |
| 153 | + |
| 154 | + # Bar Track |
| 155 | + bx, bw = 52, w - 66 |
| 156 | + ctx.fillStyle = "#1E293B" |
| 157 | + ctx.beginPath() |
| 158 | + ctx.roundRect(bx, row_y, bw, bar_h, 4) |
| 159 | + ctx.fill() |
| 160 | + |
| 161 | + # Dynamic Wave Load |
| 162 | + wave = math.sin(t + i * 1.2) * 0.08 + (random() * 0.04 - 0.02) + self.burst * 0.1 |
| 163 | + eff_val = max(0.2, min(1.0, base_val + wave)) |
| 164 | + |
| 165 | + # Active Bar Fill |
| 166 | + fill_w = int(bw * eff_val) |
| 167 | + ctx.fillStyle = col_hex |
| 168 | + ctx.beginPath() |
| 169 | + ctx.roundRect(bx, row_y, fill_w, bar_h, 4) |
| 170 | + ctx.fill() |
| 171 | + |
| 172 | + # Percentage text inside or beside bar |
| 173 | + ctx.fillStyle = "#FFFFFF" |
| 174 | + ctx.font = "bold 8px monospace" |
| 175 | + ctx.textAlign = "right" |
| 176 | + ctx.fillText(f"{int(eff_val * 100)}%", bx + fill_w - 4 if fill_w > 32 else bx + fill_w + 18, row_y + 12) |
| 177 | + |
| 178 | + # 5. Bottom Tap Prompt |
| 179 | + ctx.fillStyle = "#64748B" |
| 180 | + ctx.font = "8px system-ui, sans-serif" |
| 181 | + ctx.textAlign = "center" |
| 182 | + ctx.fillText("TAP TO CYCLE BENCHMARK MODE", w // 2, 218) |
| 183 | + |
| 184 | + if hasattr(self.drv, "show"): |
| 185 | + self.drv.show() |
| 186 | + |
| 187 | + |
| 188 | +_bench_app = None |
| 189 | + |
| 190 | + |
| 191 | +def main(canvas_id="hero_canvas"): |
| 192 | + global _bench_app |
| 193 | + print(f"Initializing PyDevices Benchmark Deck on canvas '{canvas_id}'...") |
| 194 | + _bench_app = BenchmarkDeckHero(canvas_id, size=240) |
| 195 | + print("PyDevices Benchmark Deck running successfully!") |
| 196 | + |
| 197 | + |
| 198 | +if __name__ == "__main__": |
| 199 | + cid = sys.argv[1] if len(sys.argv) > 1 else "hero_canvas" |
| 200 | + main(cid) |
0 commit comments