Repository navigation
Conversation
frans-k
marked this pull request as ready for review
October 5, 2026 20:22
frans-k
force-pushed
the
batch-scanlines
branch
from
October 5, 2026 20:25
4113fba to
f2f5ed0
Compare
Every scanline was sent as its own transaction, and the task waited for each to finish before swapping buffers. Finishing a transaction wakes this task, which is slow while the VM's schedulers are busy (the task runs below them), so on a badge with both schedulers casting frames the 240 waits of a full frame took 100 to 155 ms of a frame that needs about 30 ms of wire time. The two scanline buffers now hold a batch of lines each, as many as fit in one transaction of at most 4092 bytes (the ESP-IDF default) and at most 8: 6 lines on a 320 pixel wide RGB565 panel. A frame is 40 waits instead of 240. Output is unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Signed-off-by: Frans Krojegård <frans.krojegard@gmail.com> Signed-off-by: Frans Krojegård <frans@krojegard.com>
frans-k
force-pushed
the
batch-scanlines
branch
from
October 12, 2026 22:40
f2f5ed0 to
fff856f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
I'm a total noob so this might be AI slop (generated and tested with Claude),
but as far as I can tell it's sensible, and it seems to work for the game I tested.
Summary
do_updatesent every scanline as its own SPI transaction and waited for each one to finishbefore swapping buffers: 240 waits for a 320 x 240 frame. A wait ends when the SPI interrupt
wakes the display task, and that task runs at priority 1, no higher than any task involved (the
second scheduler runs at 5), so while both schedulers are busy each wakeup waits for a slot.
The two scanline buffers now hold a batch of lines each, as many as fit in one transaction
of at most 4092 bytes (what ESP-IDF's SPI master accepts for a bus opened without
max_transfer_sz) and at most 8. On a 320 pixel wide RGB565 panel that is 6 lines, so a frameis 40 transactions and 40 waits instead of 240. The drawing functions still draw one scanline
at a time into
screen.pixels, which now moves down the batch. Output is unchanged. TheRGB888 path (ILI9488) converts the whole batch at once.
No new configuration, no priority change, and no change for a panel too wide for two lines
(the batch is then 1, which is the old behaviour).
Measured on hardware
ESP32-S3 badge, ST7789 320 x 240 RGB565 at 80 MHz SPI, ESP-IDF 5.5.5, AtomVM
badge-v1fork,AtomGL
mainat df47163 with and without this change. The application is a first person gamethat redraws the whole panel as fast as it can, ray casting on both schedulers, with the LED
chain, wifi retries and the keyboard scan running as on any badge. Frames per second counted
as frames handed to AtomGL, turning on the spot, 5 s windows, six windows each:
main(df47163)main+ this changeTime the display task spent waiting for transactions, per frame, from a profiling build of an
older AtomGL (before #21) with the same application: about 100 to 155 ms with a transaction per
line, 50 to 56 ms with batches of 6.
Stability: two sessions of 4 to 5 minutes of play with held keys on this VM, no task watchdog
and no crash.
Not tested: ILI9488 (3 bytes per pixel) and any panel other than this one.
Why not a higher task priority
Raising the display task's priority above the schedulers made the frame about 30 ms, but it is
not safe: the task calls
display_message_send, which takes the scheduler's spin lock, andthe lock can be held by a scheduler thread the display task has just pre-empted on the same
core (ESP-IDF's main task, which runs scheduler 0, is priority 1).
sched_yielddoes not giveway to a lower priority, so the core livelocks until the task watchdog fires (seen at
priority 5 and 6 after 45 to 90 seconds of play). Fewer wakeups avoids the problem.
Update: rebased onto main after #18, #21 and #22
The branch is now one commit on
mainat 4a4065a. The conflict was indo_update:mainnow walks only the items on each row (display_items_row) and has theATOMGL_PROFILEtimers. Both are kept: the row lookup and the per-line timing run for each line inside a batch, and the SPI wait is timed per batch.Measured again on the same badge with protolux's
badge-v4AtomVM build (which pinsmainat 4a4065a), with and without this commit. Same game, turning on the spot, sleep off, two alternating 90 s runs each, average frames per second:main(4a4065a)main+ this changeStability since the rebase: many 90 s measurement runs and several minutes of play by hand on this build, with no watchdog or crash seen.
🤖 Generated with Claude Code