ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
longstrip.h File Reference

Continuous vertical-scroll (longstrip / manhwa) reading mode over an JOF band-tile atlas (#289). More...

#include <stdint.h>
#include "jof.h"
#include "ra8_err.h"
#include "ra8_tile_cache.h"
Include dependency graph for longstrip.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Data Structures

struct  longstrip_decode_ctx_t
 Context binding a JOF atlas to a ::ra8_tile_cache decode-on-miss. More...
struct  longstrip_cfg_t
 One-shot configuration handed to longstrip_open. More...
struct  longstrip_t
 Opened longstrip-strip scroll state (caller-owned; treat as private). More...
struct  longstrip_render_stats_t
 Per-frame render accounting returned by longstrip_render. More...

Typedefs

typedef ra8_err_t(* longstrip_blit_fn) (void *ctx, const uint8_t *pixels, uint16_t src_w, uint16_t src_h, uint8_t bpp, int32_t dst_x, int32_t dst_y)
 Composite one decoded band sub-window onto the display (DIP seam).

Enumerations

enum  longstrip_limits_t : uint16_t {
  k_longstrip_max_visible_bands = 4096U ,
  k_longstrip_max_prefetch = 8U
}
 Fixed engine bounds (NASA P10 Rule 2: every loop provably bounded). More...

Functions

ra8_err_t longstrip_tile_decode (void *ctx, const ra8_tile_key_t *key, uint8_t *cell, uint32_t cell_bytes, uint16_t *out_w, uint16_t *out_h)
 JOF-backed ra8_tile_decode_fn: page one band in on a cache miss.
ra8_err_t longstrip_open (longstrip_t *wt, const longstrip_cfg_t *cfg)
 Open a longstrip strip over a parsed + validated JOF atlas.
ra8_err_t longstrip_set_viewport (longstrip_t *wt, uint16_t viewport_w, uint16_t viewport_h)
 Resize the viewport of an open strip and re-derive the scroll clamp.
int32_t longstrip_clamp_scroll (const longstrip_t *wt, int32_t y)
 Clamp a candidate scroll position to the strip's legal range.
ra8_err_t longstrip_band_at_y (const longstrip_t *wt, uint32_t y, uint16_t *out_band)
 Report the band index containing canvas row y.
ra8_err_t longstrip_visible_bands (const longstrip_t *wt, int32_t scroll_y, uint16_t *out_first, uint16_t *out_last)
 Compute the inclusive visible band range for a scroll position.
ra8_err_t longstrip_scroll_by (longstrip_t *wt, int32_t delta)
 Scroll by a signed pixel delta (a finger drag), clamping at the ends.
ra8_err_t longstrip_fling (longstrip_t *wt, int32_t v0)
 Start a momentum fling with initial velocity v0.
bool longstrip_tick (longstrip_t *wt)
 Advance the fling one physics step (integer velocity + friction).
ra8_err_t longstrip_prefetch (longstrip_t *wt, uint16_t depth)
 Warm the cache with bands just beyond the viewport, in scroll order.
ra8_err_t longstrip_render (longstrip_t *wt, longstrip_render_stats_t *stats)
 Composite every visible band at the current scroll position.

Detailed Description

Continuous vertical-scroll (longstrip / manhwa) reading mode over an JOF band-tile atlas (#289).

Tag
[Ring 4 / Domain] {World: NS}

The third e-reader reading mode, beside reflowable EPUB text (reflow) and paged CBZ/manga (comic): a chapter is one continuous vertical strip – a sequence of tall image slices stacked seamlessly and read by scrolling, with no page boundaries. On RA8D2/RA8P1 there is no hardware JPEG decoder and a single slice can decode to tens of megabytes, so the strip is normalised at import into the shared JOF band-tile atlas (jof.h): a longstrip band is simply a JOF tile the full image width (tile_w == width, one tile column), so the JOF tile index is the band index – byte offset + exact height per band – giving O(1) random access to any scroll position with a single bounded read + decode. No parallel format is invented; this module is a thin scroll/geometry engine over JOF, paged through ra8_tile_cache.

What this module owns

  • Virtual-canvas geometry. Width W == info.width, height H == info.height, uniform band height band_h == info.tile_h. The scroll position is a single scalar scroll_y. y -> band is one division (y / band_h) – O(1), no cumulative-height scan, because JOF bands are fixed-height by construction.
  • Scroll state machine + fling. scroll_by for direct drags, fling + tick for momentum scrolling with integer deceleration, all clamped to [0, H - viewport_h]. No page snapping.
  • Bounded directional prefetch. prefetch warms the cache with the bands just beyond the visible window in the current scroll direction, depth-capped; eviction is the tile cache's own pinned-skip LRU. Constant resident memory regardless of strip height or fling distance.
  • Composite render. render blits each visible band's on-screen sub-window to the framebuffer through a caller-supplied blit seam (the production path binds ra8_gfx_blit / ra8_drw_blit_textured_rect; tests bind a recording blit). Bands are contiguous, so a fully-drawn visible range has zero seams and zero gaps.

Zero post-init allocation (NASA P10 Rule 3)

The engine allocates nothing. Band pixels live in the caller's tile-cache cells (SDRAM); the atlas bytes live behind the caller's pread seam; the engine state is one caller-owned longstrip_t. The tile cache bounds the resident decoded-pixel set.

Untrusted content (fail-closed)

The atlas arrives from untrusted EPUB/CBZ content. longstrip_open() validates the geometry through jof_parse() and additionally rejects any atlas that is not a single full-width band column, so the O(1) band math can never index outside the grid.

See also
jof.h The band-tile atlas format + reader this rides on.
ra8_tile_cache.h The LRU decode-on-miss band cache.
Since
0.1.0

Definition in file longstrip.h.

Typedef Documentation

◆ longstrip_blit_fn

typedef ra8_err_t(* longstrip_blit_fn) (void *ctx, const uint8_t *pixels, uint16_t src_w, uint16_t src_h, uint8_t bpp, int32_t dst_x, int32_t dst_y)

Composite one decoded band sub-window onto the display (DIP seam).

The engine computes each visible band's destination top-left (dst_y may be negative when the band is partly scrolled off the top) and hands the caller the whole decoded band; the sink clips to the framebuffer. Production binds ra8_gfx_blit (software) or ra8_drw_blit_textured_rect (DRW-accelerated, zero-copy from SDRAM); tests bind a recording sink to prove coverage.

Parameters
[in]ctxSink context (blit_ctx from the config).
[in]pixelsDecoded band pixels, tightly packed row-major.
[in]src_wBand width, pixels (== canvas width).
[in]src_hBand height, pixels (clamped for the last band).
[in]bppBytes per pixel (1 gray8 / 3 RGB888 / 4 RGBA8888).
[in]dst_xDestination left in the framebuffer (may be negative).
[in]dst_yDestination top in the framebuffer (may be negative).
Returns
k_ra8_ok on success; any error aborts the render with that code.
Since
0.1.0

Definition at line 114 of file longstrip.h.

Enumeration Type Documentation

◆ longstrip_limits_t

enum longstrip_limits_t : uint16_t

Fixed engine bounds (NASA P10 Rule 2: every loop provably bounded).

k_longstrip_max_visible_bands caps the visible-band loop: a viewport can straddle at most ceil(viewport_h / 1) + 1 bands in the degenerate band_h == 1 case, but a longstrip band is hundreds of pixels tall, so this is a comfortable ceiling that also bounds the render loop independent of the untrusted band height. k_longstrip_max_prefetch caps the prefetch depth per call.

Since
0.1.0
Enumerator
k_longstrip_max_visible_bands 

Visible + render loop bound.

k_longstrip_max_prefetch 

Max prefetch depth per call.

Definition at line 88 of file longstrip.h.

Function Documentation

◆ longstrip_band_at_y()

ra8_err_t longstrip_band_at_y ( const longstrip_t * wt,
uint32_t y,
uint16_t * out_band )
nodiscard

Report the band index containing canvas row y.

Parameters
[in]wtOpened strip.
[in]yCanvas row, pixels.
[out]out_bandReceives the band index in [0, band_count).
Returns
ra8_err_t
Return values
k_ra8_okBand reported.
k_ra8_err_null_ptrwt or out_band is NULL.
k_ra8_err_out_of_rangey is at/beyond canvas_h.
Precondition
wt was opened by longstrip_open.
out_band is writable.
Postcondition
On success *out_band < wt->band_count.
On any error *out_band is unmodified.
Note
Thread-safe (pure). O(1): a single division.
Since
0.1.0

Definition at line 209 of file longstrip.c.

References longstrip_t::band_h, longstrip_t::canvas_h, k_ra8_err_out_of_range, k_ra8_ok, RA8_CHECK_NULL_PTR, and s_tag.

◆ longstrip_clamp_scroll()

int32_t longstrip_clamp_scroll ( const longstrip_t * wt,
int32_t y )
nodiscard

Clamp a candidate scroll position to the strip's legal range.

Parameters
[in]wtOpened strip.
[in]yCandidate viewport-top position, pixels (may be out of range).
Returns
The clamped position in [0, wt->max_scroll].
Return values
0y was at/below the top, or the strip fits fully.
wt->max_scrolly was at/beyond the bottom.
Precondition
wt was opened by longstrip_open.
wt->max_scroll >= 0 (guaranteed by open).
Postcondition
The result is in [0, wt->max_scroll].
No engine state is modified.
Note
Thread-safe (pure over its inputs). Returns 0 on a NULL wt.
Since
0.1.0

Definition at line 195 of file longstrip.c.

References longstrip_t::max_scroll.

Referenced by longstrip_render(), longstrip_scroll_by(), longstrip_set_viewport(), longstrip_tick(), and longstrip_visible_bands().

◆ longstrip_fling()

ra8_err_t longstrip_fling ( longstrip_t * wt,
int32_t v0 )
nodiscard

Start a momentum fling with initial velocity v0.

Parameters
[in,out]wtOpened strip.
[in]v0Initial velocity, px/tick (down positive, up negative).
Returns
ra8_err_t
Return values
k_ra8_okFling armed.
k_ra8_err_null_ptrwt is NULL.
Precondition
wt was opened by longstrip_open.
A caller ticks the engine to animate the fling.
Postcondition
wt->velocity == v0.
wt->scroll_y is unchanged until the first longstrip_tick.
Note
Not thread-safe.
See also
longstrip_tick()
Since
0.1.0

Definition at line 284 of file longstrip.c.

References k_ra8_ok, RA8_CHECK_NULL_PTR, s_tag, and longstrip_t::velocity.

◆ longstrip_open()

ra8_err_t longstrip_open ( longstrip_t * wt,
const longstrip_cfg_t * cfg )
nodiscard

Open a longstrip strip over a parsed + validated JOF atlas.

Parses the atlas through jof_parse() and then fail-closed rejects anything that is not a single full-width band column (tile_w == width and tile_cols == 1), because the O(1) y -> band math relies on one band per row. On success the virtual-canvas geometry is cached and scroll_y/velocity are zeroed (top of strip, at rest).

Parameters
[out]wtEngine state to populate (need not be pre-zeroed).
[in]cfgConfiguration (see the struct contract).
Returns
ra8_err_t
Return values
k_ra8_okStrip opened; positioned at the top.
k_ra8_err_null_ptrwt, cfg, or a required cfg seam (pread, cache, blit) is NULL.
k_ra8_err_invalid_argviewport_w or viewport_h is zero.
k_ra8_err_not_supportedAtlas is not a full-width band column.
otherPropagated from jof_parse().
Precondition
cfg->cache was initialised with longstrip_tile_decode over the SAME atlas cfg->pread serves.
cfg->pread serves [0, cfg->atlas_size) of the atlas.
Postcondition
On success *wt satisfies the documented invariants.
On any error *wt must not be used.
Note
Not thread-safe.
See also
longstrip_render()
Since
0.1.0

Definition at line 154 of file longstrip.c.

References longstrip_cfg_t::atlas_size, internal_bind(), internal_check_ptrs(), jof_parse(), k_ra8_err_invalid_arg, k_ra8_err_not_supported, k_ra8_ok, longstrip_cfg_t::pread, longstrip_cfg_t::pread_ctx, jof_info_t::tile_w, longstrip_cfg_t::viewport_h, longstrip_cfg_t::viewport_w, and jof_info_t::width.

Referenced by internal_wire(), and ls_open_strip().

◆ longstrip_prefetch()

ra8_err_t longstrip_prefetch ( longstrip_t * wt,
uint16_t depth )
nodiscard

Warm the cache with bands just beyond the viewport, in scroll order.

Directional decode-ahead: with a non-negative velocity (or at rest) it warms up to depth bands below the visible range; with a negative velocity it warms above it. Each band is fetched and immediately released, so it becomes resident + MRU without holding a pin – the tile cache's LRU then bounds residency and reclaims stale prefetches. A reversal simply prefetches the other side on the next call, so stale-direction bands age out naturally.

Parameters
[in,out]wtOpened strip.
[in]depthBands to warm ahead (clamped to k_longstrip_max_prefetch).
Returns
ra8_err_t
Return values
k_ra8_okPrefetch attempted (misses that fail to decode are skipped, not fatal – the visible render still decodes on demand).
k_ra8_err_null_ptrwt is NULL.
Precondition
wt was opened by longstrip_open.
wt->cache has at least one unpinned cell to spare.
Postcondition
No engine geometry or scroll state changed.
At most min(depth, k_longstrip_max_prefetch) bands were touched.
Note
Not thread-safe.
Since
0.1.0

Definition at line 430 of file longstrip.c.

References longstrip_t::band_count, internal_warm_band(), k_longstrip_max_prefetch, k_ra8_ok, longstrip_visible_bands(), RA8_CHECK_NULL_PTR, s_tag, longstrip_t::scroll_y, and longstrip_t::velocity.

◆ longstrip_render()

ra8_err_t longstrip_render ( longstrip_t * wt,
longstrip_render_stats_t * stats )
nodiscard

Composite every visible band at the current scroll position.

For each band in the visible range, fetches it from the cache (decode-on-miss reads the JOF tile), computes its destination (dst_x, dst_y)dst_x centres the column in the viewport, dst_y = band_top - scroll_y (negative for the partly-scrolled top band) – blits it through the sink, then releases the pin. Because bands are contiguous and the whole visible range is drawn, the result is seamless with no gap; stats->skipped counts any band that failed to composite and must be zero for a clean frame.

Parameters
[in,out]wtOpened strip.
[out]statsReceives per-frame accounting (may be NULL to ignore).
Returns
ra8_err_t
Return values
k_ra8_okFrame attempted; stats->skipped == 0 means every visible band composited (a clean, seamless frame), > 0 means a band failed to page in.
k_ra8_err_null_ptrwt is NULL.
otherPropagated from the blit sink or the cache release (a genuine I/O failure aborts the frame).
Precondition
wt was opened by longstrip_open.
The blit sink clips to the framebuffer.
Postcondition
Each visible band was blitted at most once; all pins are released.
stats->covered_rows == min(viewport_h, canvas_h - scroll_y) when stats->skipped == 0.
Note
Not thread-safe.
Since
0.1.0

Definition at line 550 of file longstrip.c.

References longstrip_t::canvas_w, internal_draw_band(), k_ra8_ok, longstrip_clamp_scroll(), longstrip_visible_bands(), RA8_CHECK_NULL_PTR, s_tag, longstrip_t::scroll_y, and longstrip_t::viewport_w.

Referenced by ls_redraw(), priv_viewer_render_jof(), and priv_viewer_tile_jof().

◆ longstrip_scroll_by()

ra8_err_t longstrip_scroll_by ( longstrip_t * wt,
int32_t delta )
nodiscard

Scroll by a signed pixel delta (a finger drag), clamping at the ends.

Adds delta to scroll_y and clamps. When the clamp pins the position at an end, velocity is zeroed so a fling that ran into the boundary comes to rest instead of pushing further.

Parameters
[in,out]wtOpened strip.
[in]deltaSigned pixel delta (down positive, up negative).
Returns
ra8_err_t
Return values
k_ra8_okPosition updated.
k_ra8_err_null_ptrwt is NULL.
Precondition
wt was opened by longstrip_open.
The engine invariant 0 <= scroll_y <= max_scroll held on entry.
Postcondition
0 <= wt->scroll_y <= wt->max_scroll.
velocity is 0 if the new position pinned at an end.
Note
Not thread-safe.
Since
0.1.0

Definition at line 273 of file longstrip.c.

References internal_sat_add(), k_ra8_ok, longstrip_clamp_scroll(), longstrip_t::max_scroll, RA8_CHECK_NULL_PTR, s_tag, longstrip_t::scroll_y, and longstrip_t::velocity.

Referenced by internal_seek(), and ls_apply_nav().

◆ longstrip_set_viewport()

ra8_err_t longstrip_set_viewport ( longstrip_t * wt,
uint16_t viewport_w,
uint16_t viewport_h )
nodiscard

Resize the viewport of an open strip and re-derive the scroll clamp.

max_scroll is a function of the viewport height (canvas_h - viewport_h), so a viewport that changes after open – a resized window, or a paginated caller rendering a short final page – leaves the clamp stale. A stale clamp is not a cosmetic problem: it silently pins scroll_y short of the position the caller asked for, and the strip then composites a window it has already shown. This entry point recomputes the clamp and re-applies it to the current scroll_y so the invariant 0 <= scroll_y <= max_scroll still holds.

A paginated caller must set the viewport to the height of the page it is about to draw. For the final page of a strip whose height is not a whole multiple of the page height, that content height is exactly canvas_h - page_index * page_height, which makes the requested scroll position land exactly on the recomputed max_scroll instead of being clamped backwards into the previous page.

Parameters
[in,out]wtOpened strip to resize (non-NULL).
[in]viewport_wNew viewport width, pixels (non-zero).
[in]viewport_hNew viewport height, pixels (non-zero).
Returns
ra8_err_t
Return values
k_ra8_okViewport applied; max_scroll re-derived.
k_ra8_err_null_ptrwt is NULL.
k_ra8_err_invalid_argviewport_w or viewport_h is zero.
Precondition
wt was opened by longstrip_open.
The caller passes the viewport it is about to composite into.
Postcondition
wt->max_scroll == max(0, canvas_h - viewport_h).
0 <= wt->scroll_y <= wt->max_scroll.
Example:
// Paginated draw: page `p` of `page_h`-tall pages over an H-tall strip.
const uint16_t content_h = (uint16_t)((H - (p * page_h) < page_h)
? (H - (p * page_h)) : page_h);
(void)longstrip_set_viewport(&strip, strip_w, content_h);
ra8_err_t longstrip_set_viewport(longstrip_t *wt, uint16_t viewport_w, uint16_t viewport_h)
Resize the viewport of an open strip and re-derive the scroll clamp.
Definition longstrip.c:180
Note
Not thread-safe (mutates wt).
See also
longstrip_clamp_scroll()
Since
0.1.0

Definition at line 180 of file longstrip.c.

References longstrip_t::canvas_h, k_ra8_err_invalid_arg, k_ra8_ok, longstrip_clamp_scroll(), longstrip_t::max_scroll, RA8_CHECK_NULL_PTR, s_tag, longstrip_t::scroll_y, longstrip_t::viewport_h, and longstrip_t::viewport_w.

Referenced by internal_seek().

◆ longstrip_tick()

bool longstrip_tick ( longstrip_t * wt)

Advance the fling one physics step (integer velocity + friction).

Applies the current velocity to scroll_y (clamped), then decays |velocity| by one friction step toward zero. Motion stops when the velocity reaches zero OR the position pins against the end it is travelling toward. Call once per display tick until it returns false.

Parameters
[in,out]wtOpened strip.
Returns
true while the strip is still moving; false once it has come to rest (also false for a NULL wt).
Return values
trueThe fling advanced and still has velocity to run.
falseThe fling came to rest this tick, or wt is NULL.
Precondition
wt was opened by longstrip_open.
A prior longstrip_fling (or scroll) set the velocity.
Postcondition
0 <= wt->scroll_y <= wt->max_scroll.
|velocity| did not increase.
Note
Not thread-safe.
Since
0.1.0

Definition at line 383 of file longstrip.c.

References internal_apply_friction(), internal_fling_should_stop(), internal_sat_add(), longstrip_clamp_scroll(), longstrip_t::scroll_y, and longstrip_t::velocity.

◆ longstrip_tile_decode()

ra8_err_t longstrip_tile_decode ( void * ctx,
const ra8_tile_key_t * key,
uint8_t * cell,
uint32_t cell_bytes,
uint16_t * out_w,
uint16_t * out_h )
nodiscard

JOF-backed ra8_tile_decode_fn: page one band in on a cache miss.

Adapts ::ra8_tile_cache's decode-on-miss to jof_read_tile: the tile key's (tile_x, tile_y) select the band (tile_x is always 0 for a longstrip column), and the tile is read + decoded into the cache cell in bounded RAM. Bind this as the cache's decode with a longstrip_decode_ctx_t as decode_ctx.

Parameters
[in]ctxA longstrip_decode_ctx_t*.
[in]keyBand to decode (tile_y = band index).
[out]cellDestination cell pixels.
[in]cell_bytesCell capacity in bytes.
[out]out_wDecoded band width, pixels.
[out]out_hDecoded band height, pixels (clamped for last band).
Returns
ra8_err_t
Return values
k_ra8_okBand decoded into cell.
k_ra8_err_null_ptrctx, key, cell, out_w or out_h is NULL.
otherPropagated from jof_read_tile().
Precondition
ctx->info was parsed over ctx->pread's atlas.
cell holds cell_bytes writable bytes.
Postcondition
On success (*out_w) * (*out_h) * info.bpp cell bytes are valid.
On any error the cell contents are unspecified.
Note
Not thread-safe (shares the decode context's scratch).
See also
longstrip_open()
Since
0.1.0

Definition at line 53 of file longstrip.c.

References longstrip_decode_ctx_t::info, jof_read_tile(), longstrip_decode_ctx_t::pread, longstrip_decode_ctx_t::pread_ctx, RA8_CHECK_NULL_PTR, s_tag, longstrip_decode_ctx_t::scratch, longstrip_decode_ctx_t::scratch_cap, ra8_tile_key_t::tile_x, and ra8_tile_key_t::tile_y.

Referenced by internal_wire().

◆ longstrip_visible_bands()

ra8_err_t longstrip_visible_bands ( const longstrip_t * wt,
int32_t scroll_y,
uint16_t * out_first,
uint16_t * out_last )
nodiscard

Compute the inclusive visible band range for a scroll position.

The viewport [scroll_y, scroll_y + viewport_h) intersects bands first = scroll_y / band_h through last = (bottom - 1) / band_h, with last clamped to the final band. scroll_y is clamped internally, so an out-of-range value yields the nearest legal range.

Parameters
[in]wtOpened strip.
[in]scroll_yViewport-top position, pixels.
[out]out_firstReceives the first visible band index.
[out]out_lastReceives the last visible band index (>= first).
Returns
ra8_err_t
Return values
k_ra8_okRange reported.
k_ra8_err_null_ptrwt, out_first or out_last is NULL.
Precondition
wt was opened by longstrip_open.
out_first and out_last are writable.
Postcondition
*out_first <= *out_last < wt->band_count.
No engine state is modified.
Note
Thread-safe (pure).
Since
0.1.0

Definition at line 220 of file longstrip.c.

References longstrip_t::band_count, longstrip_t::band_h, longstrip_t::canvas_h, k_ra8_ok, longstrip_clamp_scroll(), RA8_CHECK_NULL_PTR, s_tag, and longstrip_t::viewport_h.

Referenced by longstrip_prefetch(), and longstrip_render().