Skip to content

feat(esp32p4): support Pocket Vapor and full PocketJS - #208

Draft
doodlewind wants to merge 3 commits into
mainfrom
gleaming-value
Draft

feat(esp32p4): support Pocket Vapor and full PocketJS#208
doodlewind wants to merge 3 commits into
mainfrom
gleaming-value

Conversation

@doodlewind

@doodlewind doodlewind commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add the exact Waveshare ESP32-P4 7B Pocket Vapor profile and its hardware-neutral touch controls
  • add a separate complete PocketJS host that runs target-bound JavaScript bundles in QuickJS ABI 6 with retained UI, lifecycle, touch/button input, and the RGB565 PPA renderer
  • add backend-independent structural DrawList damage, rounded-layer fusion, and accelerated PPA fill, A8, RGBA8, text, and compatible RGB565 paths
  • preserve a persistent 960x544 RGB565 shadow target and give each native 1024x600 scanout buffer its own accumulated damage, copying only the rows and columns that buffer has missed
  • add on-device profiling and separate B/V benchmarks so DrawList raster cost cannot be confused with physical framebuffer presentation cost
  • add deterministic esp32p4:bundle and esp32p4:device commands for stock apps such as Chrome and Feature Cards, including clean project staging and validated segmented flashing

Why

The board-specific work initially proved the Pocket Vapor path, but that is not the complete PocketJS runtime. The board also needs a product host that loads ordinary target-bound PocketJS bundles through QuickJS and the normal retained UI surface, as the existing console and Symbian hosts do.

The full host is intentionally separate from the Vapor firmware so both execution paths remain explicit and independently testable.

LVGL is not in the runtime presentation path. The upstream Waveshare BSP dependency graph still resolves LVGL, but BSP_CONFIG_NO_GRAPHIC_LIB=1 keeps it unused. PocketJS renders DrawLists directly into RGB565 and uses ESP-IDF DMA2D plus the DPI driver's two native framebuffers for presentation.

Performance findings

The connected ESP32-P4 cleanly separates two costs:

Device benchmark Result Interpretation
60 forced full-target presentations (V 60) 60.388 FPS; p95 16.373 ms; max 16.554 ms full 960x544 DMA2D copy plus visible DPI page flip meets 60 Hz
30 forced full DrawList rasters (B 30) 10.066 FPS; raster/runtime avg 85.618 ms; total avg 99.328 ms re-rasterizing the complete complex Detail scene is the bottleneck

The V result is deliberately not presented as proof that a full DrawList can be rebuilt at 60 FPS. Direct rendering into the native scanout buffer could remove only the copy portion; it cannot remove the roughly 86 ms full-raster cost.

Normal Feature Cards interaction now stays incremental:

  • focus movement improved from the earlier 46.792 ms frame to 33.119 ms while animation damage was active
  • a stable Detail close rasterized and copied only a 908x138 band: runtime 9.990 ms, DMA2D copy 1.678 ms, total 24.624 ms
  • later animation frames copied as few as 3,280-3,600 pixels in about 0.1 ms
  • stable idle frames take about 1.0-1.3 ms and perform no presentation after both native buffers have caught up

Warm Detail opens still take about 109-113 ms. The new receipts place about 60-65 ms in the synchronous QuickJS/Solid guest mount and about 29-30 ms in replaying the changed rounded band. The seven-node mount makes 28 individual HostOps calls. A follow-up should keep ordinary Solid node mirrors as plain data rather than installing the Vue Vapor DOM facade's self-referential accessors, then batch ordered numeric HostOps; full-raster throughput needs retained subtree/layer caching or an exact, parity-tested asynchronous PPA batch, not an LVGL port.

Impact

Compatible PocketJS apps can be built and flashed from the repository root:

bun run esp32p4:device build chrome
bun run esp32p4:device flash cards --port /dev/cu.usbmodem101

The full host renders a 480x272 logical viewport at density 2 into a persistent 960x544 RGB565 buffer centered on the 1024x600 panel. GT911 coordinates are mapped back to PocketJS logical touch contacts. New contacts are bounds-hit against the committed frame before the guest turn, and that fact is carried until lift.

This is currently a single target-bound guest per firmware image. The existing launcher can be rendered as an app, but live multi-app switching still needs a board-side bundle table and launcher host operations.

Validation

Automated

  • Rust suites: 171 passed across core, PPA backend and strip parity, pocket-mod, UI surface, and ESP32-P4 runtime; doc tests passed
  • ESP32-P4 profile/device tests: 13 passed
  • renderer browser tests: 46 passed
  • bunx tsc --noEmit: passed
  • git diff --check: passed
  • clean ESP-IDF v5.5.4 firmware build/link: 1,459/1,459 steps; final incremental rebuild/link also passed

Attached hardware

  • flashed and booted the final 2,546,880-byte Feature Cards firmware on ESP32-P4 rev 1.3
  • final host reports build d93c5da7c0d40705, QuickJS enabled, ABI 6, 60 Hz, and PPA enabled
  • closed/open framebuffer hashes settle deterministically at 78e598fa454884a7 and c5bc563cd01ab3c8; repeated open/close cycles leave no residual pixels
  • verified immediate partial presentation plus the second native buffer's idle catch-up, followed by zero-copy stable idle frames
  • verified both benchmark coverage counters: all requested frames were full-presented, and the forced-raster benchmark full-rasterized every requested frame

@doodlewind doodlewind changed the title feat(vapor): support Waveshare ESP32-P4 7B feat(esp32p4): support Pocket Vapor and full PocketJS Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant