diff --git a/assets/pydevices/micropython.mjs b/assets/pydevices/micropython.mjs index 2ce57fd..5669fe9 100755 --- a/assets/pydevices/micropython.mjs +++ b/assets/pydevices/micropython.mjs @@ -4583,6 +4583,22 @@ var findStringEnd = (heapOrArray, idx, maxBytesToRead, ignoreNul) => { }; + + var pydevices_rgba = () => { + if (!pydevices_state.rgba) { + const table = pydevices_state.rgba = new Uint32Array(65536); + const color = new Uint8ClampedArray(4); + const word = new Uint32Array(color.buffer); + for (let value = 0; value < 65536; value++) { + color[0] = ((value >> 11) & 31) * 255 / 31; + color[1] = ((value >> 5) & 63) * 255 / 63; + color[2] = (value & 31) * 255 / 31; + color[3] = 255; + table[value] = word[0]; + } + } + return pydevices_state.rgba; + }; var pydevices_paint = () => { const state = pydevices_state; let active = false; @@ -4590,16 +4606,27 @@ var findStringEnd = (heapOrArray, idx, maxBytesToRead, ignoreNul) => { const fb = display.framebuffer; if (!fb || !display.context) continue; active = true; - const src = HEAPU8.subarray(fb.ptr, fb.ptr + fb.width * fb.height * 2); - const dst = display.image.data; - for (let s = 0, d = 0; s < src.length; s += 2, d += 4) { - const value = src[s] | (src[s + 1] << 8); - dst[d] = ((value >> 11) & 31) * 255 / 31; - dst[d + 1] = ((value >> 5) & 63) * 255 / 63; - dst[d + 2] = (value & 31) * 255 / 31; - dst[d + 3] = 255; + // This scan runs every frame and shares its thread with the audio + // writes, so it has to be cheap: a table lookup, and only for the + // pixels that changed, and no canvas call when none did. + const count = fb.width * fb.height; + const src = fb.ptr & 1 ? new Uint16Array(HEAPU8.slice(fb.ptr, fb.ptr + count * 2).buffer) + : new Uint16Array(HEAPU8.buffer, fb.ptr, count); + const {shown, pixels} = display; + const rgba = pydevices_rgba(); + let changed = !display.painted; + for (let i = 0; i < count; i++) { + const value = src[i]; + if (value !== shown[i]) { + shown[i] = value; + pixels[i] = rgba[value]; + changed = true; + } + } + if (changed) { + display.context.putImageData(display.image, 0, 0); + display.painted = true; } - display.context.putImageData(display.image, 0, 0); display.framesPainted++; } if (!active) { state.animation = 0; return; } @@ -4717,6 +4744,7 @@ var findStringEnd = (heapOrArray, idx, maxBytesToRead, ignoreNul) => { }; }; + var _pydevices_display_register = (ptr, len, width, height, canvasPtr) => { if (typeof document === 'undefined') return 0; const canvasId = UTF8ToString(canvasPtr); @@ -4729,6 +4757,12 @@ var findStringEnd = (heapOrArray, idx, maxBytesToRead, ignoreNul) => { display.canvas = canvas; display.context = context; display.image = context.createImageData(width, height); + // What the canvas shows, as RGB565, and the same pixels as the words of + // the image: both start black, to match the framebuffer's zeros. + display.shown = new Uint16Array(width * height); + display.pixels = new Uint32Array(display.image.data.buffer); + display.pixels.fill(pydevices_rgba()[0]); + display.painted = false; display.framesPainted = 0; pydevices_install_input(canvas, canvasId); pydevices_export_host(); @@ -5771,6 +5805,7 @@ missingLibrarySymbols.forEach(missingLibrarySymbol) 'pydevices_relative_point', 'pydevices_install_input', 'pydevices_release_canvas', + 'pydevices_rgba', 'pydevices_paint', 'pydevices_export_host', 'pydevices_decode_pcm', diff --git a/assets/pydevices/micropython.wasm b/assets/pydevices/micropython.wasm index 880de82..8ed5dc1 100755 Binary files a/assets/pydevices/micropython.wasm and b/assets/pydevices/micropython.wasm differ diff --git a/assets/pydevices/micropython.wasm.provenance b/assets/pydevices/micropython.wasm.provenance index 492440f..09449fd 100644 --- a/assets/pydevices/micropython.wasm.provenance +++ b/assets/pydevices/micropython.wasm.provenance @@ -1,10 +1,10 @@ { "binary": { "name": "micropython.wasm", - "sha256": "933e642b1397dbc331da106b7b5cfe8e5e867ef2f116714d776874ac902a9d81", + "sha256": "ccfa3c47b90920650c49561ace5fedb58bd8bda34c98b42e3c585316a7ac17fc", "size": 8142825 }, - "built_by": "https://github.com/PyDevices/micropython-pydevices/actions/runs/41586523147", + "built_by": "https://github.com/PyDevices/micropython-pydevices/actions/runs/41597089596", "format": 1, "install_only": false, "modules": "all", @@ -57,9 +57,9 @@ "port": "webassembly", "sources": { "audiocomponents": { - "describe": "363eebf5a560", + "describe": "1b9e2ea23015", "dirty": false, - "head": "363eebf5a56097651e1d949f943160b6f61238d8" + "head": "1b9e2ea23015c015f7fa0d1528da84aaa5b13752" }, "audiodsp": { "describe": "14be12c475f7", @@ -67,62 +67,62 @@ "head": "14be12c475f73423cfd422128e26874966984111" }, "audioeffects": { - "describe": "363eebf5a560", + "describe": "1b9e2ea23015", "dirty": false, - "head": "363eebf5a56097651e1d949f943160b6f61238d8" + "head": "1b9e2ea23015c015f7fa0d1528da84aaa5b13752" }, "audioif": { - "describe": "363eebf5a560", + "describe": "1b9e2ea23015", "dirty": false, - "head": "363eebf5a56097651e1d949f943160b6f61238d8" + "head": "1b9e2ea23015c015f7fa0d1528da84aaa5b13752" }, "audioinstruments": { - "describe": "363eebf5a560", + "describe": "1b9e2ea23015", "dirty": false, - "head": "363eebf5a56097651e1d949f943160b6f61238d8" + "head": "1b9e2ea23015c015f7fa0d1528da84aaa5b13752" }, "cameraif": { - "describe": "363eebf5a560", + "describe": "1b9e2ea23015", "dirty": false, - "head": "363eebf5a56097651e1d949f943160b6f61238d8" + "head": "1b9e2ea23015c015f7fa0d1528da84aaa5b13752" }, "castif": { - "describe": "363eebf5a560", + "describe": "1b9e2ea23015", "dirty": false, - "head": "363eebf5a56097651e1d949f943160b6f61238d8" + "head": "1b9e2ea23015c015f7fa0d1528da84aaa5b13752" }, "displayif": { - "describe": "363eebf5a560", + "describe": "1b9e2ea23015", "dirty": false, - "head": "363eebf5a56097651e1d949f943160b6f61238d8" + "head": "1b9e2ea23015c015f7fa0d1528da84aaa5b13752" }, "h264enc": { - "describe": "363eebf5a560", + "describe": "1b9e2ea23015", "dirty": false, - "head": "363eebf5a56097651e1d949f943160b6f61238d8" + "head": "1b9e2ea23015c015f7fa0d1528da84aaa5b13752" }, "jpegio": { - "describe": "363eebf5a560", + "describe": "1b9e2ea23015", "dirty": false, - "head": "363eebf5a56097651e1d949f943160b6f61238d8" + "head": "1b9e2ea23015c015f7fa0d1528da84aaa5b13752" }, "mdns": { - "describe": "363eebf5a560", + "describe": "1b9e2ea23015", "dirty": false, - "head": "363eebf5a56097651e1d949f943160b6f61238d8" + "head": "1b9e2ea23015c015f7fa0d1528da84aaa5b13752" }, "micropython": { - "describe": "89a8b7093c17" + "describe": "47df9e1f71b0" }, "micropython-lib": { - "describe": "363eebf5a560", + "describe": "1b9e2ea23015", "dirty": false, - "head": "363eebf5a56097651e1d949f943160b6f61238d8" + "head": "1b9e2ea23015c015f7fa0d1528da84aaa5b13752" }, "micropython-pydevices": { - "describe": "363eebf", + "describe": "1b9e2ea", "dirty": false, - "head": "363eebf5a56097651e1d949f943160b6f61238d8" + "head": "1b9e2ea23015c015f7fa0d1528da84aaa5b13752" }, "omnipython-lib": { "describe": "e6325630cd18", @@ -135,29 +135,29 @@ "head": "e2254f39a8e548be3e9b886dfef3f84d2155aa27" }, "palettes": { - "describe": "363eebf5a560", + "describe": "1b9e2ea23015", "dirty": false, - "head": "363eebf5a56097651e1d949f943160b6f61238d8" + "head": "1b9e2ea23015c015f7fa0d1528da84aaa5b13752" }, "pdwidgets": { - "describe": "363eebf5a560", + "describe": "1b9e2ea23015", "dirty": false, - "head": "363eebf5a56097651e1d949f943160b6f61238d8" + "head": "1b9e2ea23015c015f7fa0d1528da84aaa5b13752" }, "pngio": { - "describe": "363eebf5a560", + "describe": "1b9e2ea23015", "dirty": false, - "head": "363eebf5a56097651e1d949f943160b6f61238d8" + "head": "1b9e2ea23015c015f7fa0d1528da84aaa5b13752" }, "ppa": { - "describe": "363eebf5a560", + "describe": "1b9e2ea23015", "dirty": false, - "head": "363eebf5a56097651e1d949f943160b6f61238d8" + "head": "1b9e2ea23015c015f7fa0d1528da84aaa5b13752" }, "pydevices": { - "describe": "363eebf5a560", + "describe": "1b9e2ea23015", "dirty": false, - "head": "363eebf5a56097651e1d949f943160b6f61238d8" + "head": "1b9e2ea23015c015f7fa0d1528da84aaa5b13752" }, "pygraphics": { "describe": "f97fd2e20e73", @@ -165,14 +165,14 @@ "head": "f97fd2e20e73f23c2138ab52a630cf04b0a7aea1" }, "sayso": { - "describe": "363eebf5a560", + "describe": "1b9e2ea23015", "dirty": false, - "head": "363eebf5a56097651e1d949f943160b6f61238d8" + "head": "1b9e2ea23015c015f7fa0d1528da84aaa5b13752" }, "tsmux": { - "describe": "363eebf5a560", + "describe": "1b9e2ea23015", "dirty": false, - "head": "363eebf5a56097651e1d949f943160b6f61238d8" + "head": "1b9e2ea23015c015f7fa0d1528da84aaa5b13752" }, "ulab": { "describe": "1d3ddd8f5228", @@ -180,11 +180,11 @@ "head": "1d3ddd8f5228e2c7335dac71f4846e6abb0ec9f9" }, "usbhost": { - "describe": "363eebf5a560", + "describe": "1b9e2ea23015", "dirty": false, - "head": "363eebf5a56097651e1d949f943160b6f61238d8" + "head": "1b9e2ea23015c015f7fa0d1528da84aaa5b13752" } }, - "stamped": "2026-11-03T05:56:11+00:00", + "stamped": "2026-11-03T07:36:04+00:00", "target": "mp-wasm" }