Skip to content

feat(sifli): SF32LB5x host with EPIC/VG Lite executors and a one-pass planner - #338

Draft
HalfSweet wants to merge 23 commits into
pocket-stack:mainfrom
HalfSweet:sifli-epic
Draft

feat(sifli): SF32LB5x host with EPIC/VG Lite executors and a one-pass planner#338
HalfSweet wants to merge 23 commits into
pocket-stack:mainfrom
HalfSweet:sifli-epic

Conversation

@HalfSweet

Copy link
Copy Markdown
Collaborator

Summary

A SiFli SF32LB5x host for PocketJS: hosts/sifli plus a restructured engine/backends/sifli-epic. Firmware projects keep their app, board files, and assets; every hardware call lives in this repository.

  • engine/backends/sifli-epic decodes the DrawList once per frame (pocketjs_core::drawlist, now public), plans each damage region against a static Capabilities table, and emits typed commands (Fill, FillAlpha, BlendA8, Gradient, Blit, BlitQuad, TileOut/TileIn, Fence) to an executor behind the Submit/Frame traits. The CPU never writes the framebuffer: software fallback renders into executor-owned SRAM tiles, A8 coverage is built in SRAM planes split into bands, and fences appear only where a plane or tile is reused. Coordinate limits (EPIC_COORDINATES_MAX: 1010 on 55x/56x/58x, 505 on 52x/57x) split fills and bands in the planner. MockGpu/DeferredMockGpu run every command with the core's formulas; 43 tests compare against the core RGB565 rasterizer, including deferred/immediate parity, strips, tiles, bands, and native textures.
  • hosts/sifli/components/pocketjs_gpu is the C executor behind pocketjs_gpu.h: EPIC through public HAL_EPIC_* entry points (one interrupt-driven transaction in flight; fills, 2×2 gradients, two-layer A8 blends, video-layer scaled native blits, tile copies) and, on SF32LB58, VG Lite for projective quads, RGB-modulated blits, and portable-format textures EPIC cannot read (the SDK defines EPIC's color and 3×3 matrices for 57x only). Engines drain each other on a switch; the profile counts switches. Capabilities come from the SDK feature gates in pocketjs_gpu_chip.h.
  • hosts/sifli/components/pocketjs_host: shared PSRAM heap, SF32LB58 MPU override (PSRAM1 cached, PSRAM2 non-cacheable), RGB565 framebuffer ring with one draw_rect_async in flight, keys (level or pulse + long-press semantics) and touch, the QuickJS realm with the ui bindings, an embedded guest catalog with frame-boundary switching, the serial profiler, and validation modes (POCKETJS_SELF_CHECK, POCKETJS_FRAME_CRC, POCKETJS_FORCE_SOFTWARE).
  • hosts/sifli/rust: the pocketjs-sifli staticlib (standalone crate) exporting the pocket_core_* C ABI.
  • Tooling: bun tools/sifli.ts assets|bake|vendor|build|audit|verify|crc|selfcheck, tests/sifli-sim.test.ts (new sifli sim stage), a .epic native pak baker, pocket_spec.h as a second output of contracts/spec/gen-c.ts, and the wasm export ui_render_rgb565_scaled for CRC parity.
  • Core: engine/core/src/drawlist.rs (public decoder shared with damage.rs), Texture::coverage_only classified at upload. esp32p4-ppa is unchanged except its structural-damage test, updated for the damage semantics 94cd4c8 introduced.

The two SF32LB58 firmware projects (Motion Lab, Cover Flow launcher) were switched to this layout with local commits; their regenerated .pak/.epic assets are byte-identical to the previous toolchain's.

Verification

  • cargo test — core 128, sifli-epic 43, esp32p4-ppa 20; bun tests/contract.ts green.
  • bun tools/sifli.ts verify and POCKETJS_SIFLI_PROJECT=<launcher project> bun test --conditions=browser tests/sifli-sim.test.ts (five guests + RIGHT → CIRCLE → cafe-main).
  • hosts/sifli/rust builds for thumbv8m.main-none-eabihf; scons --board=sf32lb58-lcd_n16r32n1_a1_dpi links examples/hero-smoke (VG Lite and self-check on), the Motion Lab project (main.bin 3,002,016 B), and the launcher project on N16 (8,479,024 B) and A128 NAND (8,463,296 B); bun tools/sifli.ts audit clean on each (no undefined symbols, component-owned EPIC_IRQHandler and mpu_config, no drv_epic).

Still on the board

  • VG Lite conventions to confirm with the self-check: vg_lite_color_t channel order for tints, VG_LITE_BLEND_SRC_OVER against non-premultiplied sources, projective sampling phase.
  • Per-transaction cost baselines (S1), continuous-blend text (S2), engine-switch cost (S7); the executor keeps a single transaction in flight until those numbers exist.
  • HAL clock gating and the op_hist debug ring are used as the SDK ships them (out of scope by design).

TEXT_RUN and SURFACE_QUAD are known members of the closed DrawList op set that a fixed-function RGB565 target never renders. The walker used to fail the whole frame on them, which made the host repaint everything through the software rasterizer; now it advances past them exactly like the core rasterizer.
Core commit 'perf(core): retain damage across structural changes' resynchronizes inserted or removed operations at exact anchors instead of forcing a full redraw. Update the structural-change test and the README sentence that described the old behaviour.
Move the damage tracker's private DrawList decoder into pocketjs_core::drawlist so hardware backends can decode a DrawList once per frame with the same op lengths, scissor tracking, and conservative bounds. Classify every uploaded texture as coverage-only (white RGB or nearest-transparent texels) at upload and after in-place T8 updates, and mark baked corner discs directly, so backends no longer scan texture bytes per frame.
Route the rounded-corner and alpha-only texture decision through Ui::texture_coverage_only instead of scanning texture bytes and caching the result per handle. In-place T8 updates are reclassified by the core, so the renderer no longer tracks raster revisions; invalidate_resources stays as a no-op for host compatibility.
Replace the per-op EpicOps trait and the hand-written walker with a one-pass planner over the core's public DrawList decoder, a typed command set, and a Submit/Frame executor contract driven by static Capabilities. Executors bind the target once per frame and own the A8 planes; consecutive CPU fallbacks share one rasterizer dispatch behind a single fence. The recording MockGpu executes every command with the core's pixel formulas, so blits and quads now have pixel-parity tests.
CPU fallback no longer needs a CPU-writable framebuffer: when the executor forbids direct writes, each batch is copied into an executor-owned tile, rendered there with the core's window rasterizer, and copied back, split into bands that fit the tile. A8 runs are split into plane-sized bands and a plane is rewritten only after a fence retired the blend reading it. DeferredMockGpu keeps commands in flight until fences and proves the ordering for every capability preset; strip rendering is checked against full renders at scale 1 and 2.
Start hosts/sifli with the two C contracts the SiFli host is built on: pocket_core.h, the core entry points the firmware calls (now with an explicit framebuffer count, a software render for device self-checks, and a DrawList hash), and pocketjs_gpu.h, the command-queue ABI the renderer submits through. The pocketjs-sifli staticlib crate implements the entry points over the SiFli backend and forwards commands to the C queue; contracts/spec/gen-c.ts now writes pocket_spec.h for this host as well.
The planner now asks the executor for a natively registered copy of a texture before falling back to portable bytes, gated by blit_native/blit_quad_native, and keeps tinted blits on the CPU unless the executor reports blit_modulate. Fills and A8 bands are split so no command exceeds the executor's coordinate registers; gradients, blits, and quads that would are rendered by the CPU instead.
Add the C executor behind hosts/sifli/include/pocketjs_gpu.h as an SDK
component, the project entry points, and a firmware example that links
the whole stack.

components/pocketjs_gpu:
- pocketjs_gpu.c owns the queue: capability report from the chip table,
  target binding, command dispatch with validation before any hardware
  write (a refused command returns -(index+1) and has no side effects),
  fences, two A8 planes and two RGB565 tiles in L1 SRAM (64-byte
  aligned, non-retained), the native texture registry keyed by core
  handle and revision, and DWT-based profiling counters.
- pocketjs_gpu_epic.c runs one interrupt-driven HAL transaction at a
  time through public HAL entry points only: fills (with alpha), 2x2
  corner gradients, two-layer A8 blends reading a plane window, native
  RGB565/ARGB8888/L8 blits scaled by the video layer with horizontal
  mirroring, and tile copies as one-layer blends. Cache cleans are
  no-ops for the SRAM planes and cover cached PSRAM sources.
- pocketjs_gpu_chip.h derives the constants from the SDK feature gates:
  EPIC_COORDINATES_MAX, A8 and L8 support, vertical mirroring, VG Lite.
- Kconfig: POCKETJS_GPU, plane sizes, registry capacity, the
  transaction threshold, and direct CPU framebuffer writes (off).

hosts/sifli/SConscript builds the pocketjs-sifli staticlib with cargo
(project working directory so a project's vendoring config applies),
appends it to LINKFLAGS_POST, and includes the components;
hosts/sifli/Kconfig is sourced from a project's Kconfig.proj through
POCKETJS_ROOT.

examples/hero-smoke resolves POCKETJS_ROOT by position inside the
repository, provides the allocator and panic hooks, opens the queue,
creates a core, renders one frame into a PSRAM2 framebuffer, and prints
the render statistics and the draw hash.

Verified: scons --board=sf32lb58-lcd_n16r32n1_a1_dpi links main.elf with
every pocketjs_gpu_*, pocket_core_*, and HAL_EPIC_* symbol resolved, the
planes in SRAM and the framebuffer in PSRAM2, no drv_epic symbols, and
no undefined references.
Move everything the two SF32LB58 firmware projects duplicated into a
reusable host component, add the tool that generates what a project
embeds, and make hero-smoke a real guest.

components/pocketjs_host (Kconfig POCKETJS_HOST):
- host_heap.c: one rt_memheap in cached PSRAM shared by QuickJS and the
  Rust core behind an alignment-preserving wrapper; the pocket_heap_*
  and pocket_rust_panic hooks the staticlib links against.
- host_mpu.c: the SF32LB58 MPU override (PSRAM1 cached, PSRAM2
  non-cacheable) as a strong mpu_config, under POCKETJS_MPU_OVERRIDE.
- host_lcd.c: the RGB565 framebuffer ring in PSRAM2 and the RT-Thread
  LCD device with one draw_rect_async in flight.
- host_input.c: KEY1/KEY2 as LEFT/RIGHT levels, or with
  POCKETJS_KEY_LONG_PRESS_MS the release-pulse plus long-press contract
  (CIRCLE on the launcher, return to the launcher in a guest); GT911
  touch packed into the wide-coordinate wire form.
- host_guest.c: pak loading, native texture registration under the
  handle's content revision (new pocket_core_texture_revision export),
  the QuickJS realm with the ui bindings and appLaunch, frame and render.
- host_main.c: pocketjs_host_run(catalog) with guest switching at frame
  boundaries and the serial profiler (perf/render/work lines now report
  GPU submit and wait time, transactions, refusals, tiles, fences, and
  A8 bands).
- pocketjs_catalog.h: the embedded guest table a project generates.

tools/sifli.ts: `assets` builds every guest of a project's
pocket-sifli.json, bakes optional native paks, writes pocket_assets.S,
pocket_catalog.generated.c, and a SHA-256 manifest; `bake`, `vendor`
(offline third-party crates plus .cargo/config.toml), `build`, `audit`
(link checks), and `verify`. tools/sifli-bake.ts is the .epic baker as
an importable module. tests/sifli-sim.test.ts boots the hero demo at
512x300, density 2, scale 2 and asserts a non-flat, deterministic
1024x600 frame; a project directory in POCKETJS_SIFLI_PROJECT adds its
guests and launcher flow. Registered as the "sifli sim" stage and the
`bun sifli` script.

examples/hero-smoke embeds the repository's hero demo through the
catalog, ships the 9 MiB hcpu_flash_code partition table the density-2
pak needs, and relaxes GCC 14 pointer diagnostics for the SDK's QuickJS
the same way the projects do.

hosts/sifli/README.md and docs/PORTING.md document the integration, the
memory map, the executor recipes, the input contract, the profiler
fields, and the verification boundary.

Verified: the sim smoke passes, the bake is byte-deterministic, the
contract test is green, the staticlib builds for thumbv8m, and scons
links hero-smoke (main.bin 4,848,128 bytes) with the audit clean:
no undefined symbols, HAL EPIC entry points and the strong mpu_config
and EPIC_IRQHandler from the components, no drv_epic.
…lits

SF32LB58 EPIC has no color matrix and no 3x3 transform, so the renderer's
capability-gated routing sent projective quads, RGB-modulated blits, and
textures without a native copy to the CPU tile path. Add the second
executor the capabilities already describe.

components/pocketjs_gpu:
- pocketjs_gpu_vglite.c (POCKETJS_GPU_VGLITE, needs the SDK's
  USING_VGLITE): hands the library a contiguous SRAM pool, binds the
  framebuffer as VG_LITE_BGR565, and runs BLIT through vg_lite_blit_rect
  with a translate/scale matrix (negative scale for mirrors), BLIT_QUAD
  through vg_lite_get_transform_matrix from the source rectangle's
  TL/BL/BR/TR onto the command's quad, tints through
  VG_LITE_MULTIPLY_IMAGE_MODE, global alpha through
  vg_lite_source_global_alpha, clips through the scissor, and indexed
  textures through a CLUT rebuilt per palette. Solid quads sample a 4x4
  color texture in SRAM. A vg_lite_finish failure disables the engine for
  the session.
- The registry accepts portable IMG entries (PSM_5650/4444/8888/T8) next
  to native blobs; VG Lite reads those formats directly. Sources must be
  64-byte aligned and cache-clean, which registration enforces, so the
  queue advertises no inline portable formats and reports
  BLIT_QUAD_NATIVE | BLIT_MODULATE when VG Lite is up.
- The queue tracks the engine in use and drains the other one before a
  command switches engines; fences and end drain both. The profile counts
  engine_switches and the host prints it as `switches=`.

components/pocketjs_host registers every image without a native copy as
a staged portable copy when VG Lite is enabled, so a guest needs no .epic
pak to blit through hardware.

Documented in hosts/sifli/README.md and docs/PORTING.md, including the
conventions still to confirm on a board (tint channel order, SRC_OVER
against non-premultiplied sources, projective sampling phase).

Verified: hero-smoke links with USING_VGLITE and POCKETJS_GPU_VGLITE
(vg_lite_blit_rect, vg_lite_get_transform_matrix, V2D_GPU_IRQHandler
resolved, pool in SRAM), the audit is clean, and the renderer crate is
unchanged (its routing was already capability-driven).
Two validation modes for the board, and the simulator side that reads
them back.

components/pocketjs_host:
- POCKETJS_SELF_CHECK renders every POCKETJS_SELF_CHECK_INTERVAL-th frame
  a second time with pocket_core_render_rgb565_software into a scratch
  framebuffer in PSRAM2 and prints the mismatch ratio, PSNR over all
  channels, the largest 8-bit channel delta, both CRC32s, the hardware
  work of the frame, the VG Lite command count, and up to 16 differing
  pixels.
- POCKETJS_FRAME_CRC prints an IEEE CRC-32 of every presented frame with
  the DrawList hash.
- POCKETJS_FORCE_SOFTWARE renders every frame through the software
  rasterizer to validate the heap, MPU, LCD ring, and input chain before
  trusting the hardware path.
- The GPU profile counts VG Lite commands and can be read without a
  reset.

engine/wasm exports ui_render_rgb565_scaled: the core's RGB565 rasterizer
at an integer scale, byte for byte the device's software path.
hosts/web/wasm-ops.js exposes it as renderRgb565Scaled and the simulator
world as renderRgb565.

tools/sifli.ts: `crc <output> --frames N --assert <log>` renders the
guest in the simulator at 512x300, density 2, scale 2 and compares the
CRC sequence with a board log; `selfcheck <log>` applies the acceptance
thresholds (exact for fills, A8 blends, and 1:1 copies; PSNR >= 45 dB,
maxd <= 8, mismatch <= 0.5 % for EPIC gradients and scaled blits;
PSNR >= 38 dB, mismatch <= 3 % for VG Lite; below 35 dB fails).
tests/sifli-sim.test.ts covers the CRC check vector, the deterministic
RGB565 frame sequence and its log parser, and the thresholds.

Verified: the sifli sim tests pass, the renderer and cafe determinism
suites still pass, and hero-smoke links with the self-check enabled
(scratch framebuffer at 0x62384000, right after the ring).
Describe the SiFli host as it now exists: the once-per-frame planner and
typed command stream in engine/backends/sifli-epic, the EPIC and VG Lite
executors in hosts/sifli, SRAM tiles and planes instead of CPU framebuffer
writes, the SDK-derived coordinate limits, and the self-check boundary.
Add the host to the README host table, the portable-backend host list, the
hosts tree, and the non-workspace crate list.
…tries

A project entry built with --no-config loses its app-local Pocket config
(animation themes, tick rate); use the pocket.config.ts next to the entry
when it exists, or the manifest's `config` path, and --no-config only when
neither applies. Repository demo outputs keep --no-config.
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