feat(render): Add RGB565 raster path and ESP32-P4 PPA backend#160
Conversation
Scrub ESP32-P4/PPA-specific wording from engine/core docs and comments — the core raster targets are format contracts (RGBA8, LE-ARGB8888 words, RGB565), not properties of one host. Export coverage_index alongside linear_sample_coordinates and drop the backend's byte-for-byte duplicate, so hardware glyph masks can never drift from the software fallback. Also fix the pixel-count assert message left over from the byte-length days. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
doodlewind
left a comment
There was a problem hiding this comment.
Reviewed the full diff, ran the complete local suite, and pushed one maintainer commit (7975a29) on top — details below. This is a model contribution: the hardware surface is isolated behind a narrow trait, the generic layers gain a format, not a platform, and the fallback story preserves DrawList ordering exactly.
What I verified
engine/coretests: 85 passed; backend tests: 10 passed;--features esp-idfcargo check clean.contractsparity:bun tests/contract.tsgreen, andgen-rust.tsregeneratesspec.rsbyte-identical — the hand edit matches the generator.- Full
bun run testchain green. - Goldens: 42 pass; the 7 failures (music/gallery/motions) reproduce identically on
mainwith a fresh wasm rebuild in this environment, so they are pre-existing and not from this PR. TheRenderTargetrefactor keeps the RGBA path byte-exact.
Architecture notes (all good)
PpaOpskeeps ESP-IDF headers out of the Rust crate entirely; the C component owns the clients and the DMA/cache contract. Board/BSP concerns stay out of the repo.- The alpha-texture classification cache is safe against handle reuse because core handles are generation-tagged, and T8 video planes are correctly excluded (in-place
update_texture_t8mutation). - Fractional-UV rejection (
exact_texel_edge) and exact 1/16-scale admission are the right conservative gates — everything else falls back in order. - C side: rect/surface validation, SRM overlap rejection,
fill_argb_colorexpansion, and log-once fallback are all careful.
One heads-up for downstream — @odex21
This PR changes render_scaled_argb output from [A,R,G,B] byte order to [B,G,R,A] (the little-endian layout of an ARGB8888 word, which is what the PPA actually consumes). If your #140 firmware compensated for the old order anywhere, it needs the inverse adjustment when it picks up this change. The new order is the correct one for feeding the PPA without a reorder pass.
Maintainer commit 7975a29
- De-platformed
engine/coredocs/comments: core describes format contracts (RGBA8 / LE-ARGB8888 / RGB565), not one host's hardware; per-host rationale lives inengine/backends/esp32p4-ppaandhosts/esp32p4where it belongs. - Exported
coverage_indexfrompocketjs_core::raster(same rationale as yourlinear_sample_coordinatesshare) and removed the backend's duplicate, so glyph-mask sampling can never drift from the software fallback. - Fixed the stale "wrong byte length" assert message (it now checks pixel count).
Thanks for the thorough tests — the mock-PPA parity tests against render_scaled_rgb565 are exactly the right oracle. Merging once CI is green on the new head.
|
Correction to my review's golden-suite note, for the record. I wrote that the 7 golden failures (gallery ×5, music-main.60, motions-main.236) "reproduce identically on main" — that framing was wrong. The accurate diagnosis:
No action needed — the merge is clean. Lesson for future branches: goldens are only meaningful against the branch's own base or after rebasing onto current main. |
Real-device A/B/C benchmark on ESP32-P4 (AXS15260, 452×1280)Thanks @HalfSweet for this work — the backend architecture (ordered PPA dispatch + software fallback + damage) is solid and runs tear-free on real hardware. We benchmarked it on our player UI and wanted to share the numbers, since the conclusion differs by UI shape. Setup. Three builds on the same device and display chain, ESP-IDF v5.5.4, pocketjs @ d027222 (main + touch-events, i.e. post-#160). Panel always scans RGB888; RGB565 only feeds the PPA input, converted to RGB888 during the 2× scale + 90° rotate. Triple-buffered, vsync-owned. Sampling: serial
Invariants ( Observations.
Takeaway for us. The +fps on static/playing is real, but it is offset by the per-frame 565→888 conversion and reversed on our drag-heavy page, SRM doesn't apply to our UI, and the gradient banding is a blocker (we plan heavier fluid gradients). The backend's payoff model fits "fullscreen image + large flat-color" UIs better than our "many small rects + small text + one small cover" player. So we won't adopt the RGB565 backend itself. That said, #160's raster rework ( No action needed — just sharing real-device data in case it helps tune the PPA area thresholds or documents the RGB565 banding trade-off. Happy to provide the raw serial logs if useful. |
Summary
Implementation
The new
pocketjs-esp32p4-ppabackend interprets DrawLists directly into an RGB565 destination.Hardware-compatible operations are routed to:
Unsupported gradients, triangles, texture formats, transforms, and sampling cases fall back to the native RGB565 software rasterizer in DrawList order.
The reusable ESP-IDF adapter:
fill_argb_colorLinear alpha-texture classification and edge sampling are shared with the core rasterizer. Colored transparent textures using linear filtering correctly fall back to software rendering.
Compatibility
release/v6.0: supported and used as the CI baselinerelease/v6.1: supported and locally build-testedHardware status
In the M5Stack Tab5 hardware integration build, the average frame rate reached 19.8 FPS during extended operation. The test was conducted at a CPU frequency of 360 MHz and a resolution of 1280 × 720.