Display foundation¶
The display foundation shares panel geometry, frame buffers, clipping and fonts between the ESP32
firmware and awtrix-linux. Prepared layouts and their memory budget belong to Linux targets
with a display more than 8 pixels high. The code lives in
src/core/render/,
src/platform/linux/layout/ and
src/media/. The public contract
and examples for layouts are in Layouts; this page explains how the code
is divided and how it behaves when things fail.
Who owns what¶
| Responsibility | Owner |
|---|---|
| Legacy JSON normalisation and drawing | core/payload/PayloadParser, the legacy branches in core/render/RenderPipeline |
| Registered payload fields and prepared content | core/payload/PayloadParser, core/render/PageContent |
| Native layout JSON validation | platform/linux/layout/LayoutJson, LayoutPayload |
| Metrics, prepared regions and independent scrollers | platform/linux/layout/Layout |
| Checked data and shared ownership | core/memory/CheckedStorage, CheckedShared |
| Pixel clipping and font rendering | core/render/Canvas, TextRenderer, FontCatalog |
| Script ownership and handle limits | platform/linux/layout/LayoutScripting, ScriptLayouts, ScriptLayoutBindings |
| Rotation, notifications, transitions and global overlays | CoreEngine, RenderPipeline |
| Image metadata and bounded decoders | media/ImageInfo, DevicePageIcon, GifPlayer |
| Active geometry, frame allocation and electrical output | core/render/DisplayProfile, FrameMemory, the board and renderer adapters |
There is one notification queue and no widget tree. A prepared layout is a flat list of regions in draw order. Ordinary frames do not parse JSON and do not copy the whole display per region. Canvas views borrow their parent's buffer and clip in both axes. Frame composition owns the transition and power-animation buffers.
Legacy payloads keep their coordinates, baselines, icon pushes, chart scales and scroll rules.
The small and large fonts keep their original tables and IDs; the ten Matrix fonts come from
a separate generator with stable names (see Fonts). Every display size uses native
coordinates. Extra space on a larger panel is used only when the sender uses layout regions or
writes code that reads the display size.
Admission and memory¶
A native layout is admitted on the main loop. Admission validates the metadata and opens its media with bounded decoders before the new layout replaces the old one. An invalid update keeps the page that was shown before.
Pushed pages, notifications and script handles share one layout budget. It includes both pages
of a transition, so replacing a layout briefly needs room for the old and the new one. The budget
is 256 KiB on the TC002; GET /api/v1/capabilities reports it
under layouts.limits.
The renderer owns each layout's animation state. Completion reports carry a revision, so a report for a replaced layout is not mistaken for the current one. Notifications keep their own generation counter.
Large buffers and layout storage have explicit allocation-failure paths. Older subsystems and the network transports still use ordinary STL allocations, so there is no global guarantee that every allocation failure is handled.
Geometry¶
| Target | Width | Height | Pixel limit |
|---|---|---|---|
| ESP32 / ESP32-S3 | 32–128 | 8 | 1,024 |
| Ulanzi TC002 | 52 | 16 | 832 |
| Linux host | 8–128 | 8–32 | 4,096 |
The limits are kEspDisplayLimits in
MatrixLayout.h
and hostDisplayLimits in
DisplayProfile.h.
On the ESP32 a wider display is a horizontal chain of 8-pixel-high panels, driven from one data pin.
NVS erases the key pheight that older builds stored for a panel height.
A new width becomes active at the next restart. Until then scripts, the screen API, assets and the editors keep using the active size, and the configuration API shows the pending one.
Before the display starts, the firmware checks that the frame and output buffers can be allocated. If they cannot, it falls back to a smaller checked geometry and keeps the configured value visible instead of rewriting it. On the ESP32 an NVS (non-volatile storage) write counts only after a successful commit and read-back; a failed write is reported through the configuration error contract.
Frame rate¶
More pixels take longer to send over the single data line. These frame rates were measured with the
awtrix_s3_octal_probe build on an ESP32-S3 with 8 MiB PSRAM:
| Geometry | Blank | Two scrollers | Plasma | Mean LED output per frame |
|---|---|---|---|---|
| 32×8 | 41.6 fps | 41.7 fps | 41.4 fps | 8.1 ms |
| 64×8 | 40.7 fps | 40.7 fps | 40.6 fps | 15.9 ms |
| 128×8 | 27.6 fps | 26.8 fps | 25.4 fps | 31.6 ms |
The frame rate is capped at about 40 fps. Up to 512 pixels the cap holds; above that the LED output time dominates. The figures describe a device with its network and services running, not an isolated render loop. They time the electrical output, not an optical measurement of a physical panel of that size.
The plasma effect caches its axis values for every height and falls back to per-pixel calculation only if that small cache cannot be allocated.
Testing¶
python scripts/test_native.py -j 8
python tools/check_font_sync.py
python tools/check_docs_sync.py
python tools/check_berry_api.py
python tools/check_prelude_solidified.py
pio run -e awtrix -e awtrix_s3_octal -e awtrix_s3_quad
The native suites in test/ cover
plain payloads, panel mapping, clipping, fonts, checked storage and rejection of unregistered
payload fields. The CMake suites in
tests/layout/ cover prepared
regions, independent scrollers, revisions, atomic replacement, handle cleanup and injected
allocation failures. Layout registration is checked at heights 8, 9 and 16.
The CMake host build runs the HTTP contracts. They compare JSON and Berry output, check that a
rejected update keeps the displayed content, and send native notifications over MQTT. Configure
with -DAWTRIX_REQUIRE_INTEGRATION_SERVICES=ON to make a missing MQTT broker a failure instead of
a skip. See Building from source.
Related¶
- Layouts for the public contract
- System configuration for the panel settings
- Fonts