406 lines
12 KiB
Text
406 lines
12 KiB
Text
== Sprites
|
|
|
|
Declared in `<joey/sprite.h>`.
|
|
|
|
A sprite is a rectangle of 8x8 tiles drawn at an arbitrary pixel position with
|
|
colour-0 transparency. Sprite pixel data is `widthTiles * heightTiles * 32`
|
|
bytes of 4bpp packed data, laid out as a flat blob of tiles in row-major order.
|
|
|
|
The distinguishing feature is that *sprite blitters are compiled at run time*.
|
|
The library emits native 65816, 68000 or x86 code for each sprite, including
|
|
pre-shifted variants so smooth horizontal motion needs no runtime bit shifting.
|
|
|
|
[IMPORTANT]
|
|
====
|
|
`jlSpriteDraw` does *not* compile on demand. An uncompiled sprite draws through
|
|
an interpreter roughly *forty times slower*, and nothing reports this. Always
|
|
call `jlSpriteCompile` or `jlSpritePrewarm` during loading.
|
|
====
|
|
|
|
=== jlSpriteT and jlSpriteBackupT
|
|
|
|
[source,c]
|
|
----
|
|
typedef struct jlSpriteT jlSpriteT; // opaque
|
|
|
|
typedef struct {
|
|
jlSpriteT *sprite;
|
|
int16_t x;
|
|
int16_t y;
|
|
uint16_t width; // pixels
|
|
uint16_t height; // pixels
|
|
uint8_t *bytes; // caller-owned, capacity >= sizeBytes
|
|
uint16_t sizeBytes;
|
|
} jlSpriteBackupT;
|
|
----
|
|
|
|
You supply `bytes` and its capacity; the library fills in the rest to describe
|
|
what it actually captured. On planar ports the captured region is the
|
|
*16-pixel-aligned window* containing the sprite, not the sprite rectangle, and
|
|
the `x`, `width`, `height` and `sizeBytes` fields report that true geometry. It
|
|
is the recorded window you must not overwrite before restoring.
|
|
|
|
=== JOEY_SPRITE_BACKUP_BYTES
|
|
|
|
[source,c]
|
|
----
|
|
#define JOEY_SPRITE_BACKUP_BYTES(_wT, _hT) /* ... */
|
|
----
|
|
|
|
Worst-case backup capacity for a `widthTiles` by `heightTiles` sprite at any x
|
|
position, on every port. Use it to size your backing array.
|
|
|
|
[source,c]
|
|
----
|
|
// 2-byte aligned by construction -- see the warning below.
|
|
static uint16_t gBacking[JOEY_SPRITE_BACKUP_BYTES(4, 4) / 2];
|
|
----
|
|
|
|
[WARNING]
|
|
====
|
|
Give the backing array *at least 2-byte alignment*. An odd buffer address
|
|
routes save and restore onto slow interpreted paths on the planar ports,
|
|
because the 68000 cannot do word accesses at odd addresses. Declaring it
|
|
`uint16_t`, as above, guarantees this.
|
|
====
|
|
|
|
=== jlSpriteBankLoad
|
|
|
|
[source,c]
|
|
----
|
|
uint16_t jlSpriteBankLoad(const char *path, jlSpriteT **outCels,
|
|
uint16_t maxCels, uint16_t *outPalette);
|
|
----
|
|
|
|
Loads cels from a baked `.spr` file. Each cel becomes a freshly allocated
|
|
`jlSpriteT`, released with `jlSpriteDestroy`. `.spr` banks are cross-target --
|
|
the same file works on every platform.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`path` |`const char *` |File path.
|
|
|`outCels` |`jlSpriteT **` |Receives the sprites. Caller-owned array.
|
|
|`maxCels` |`uint16_t` |Capacity of `outCels`; the file's cel count is capped
|
|
to this.
|
|
|`outPalette` |`uint16_t *` |Optional, may be `NULL`. Receives the embedded
|
|
16-entry `$0RGB` palette if present; otherwise left unchanged.
|
|
|===
|
|
|
|
*Returns:* the number of cels written. Returns 0 if the file is missing, the
|
|
magic or target byte is wrong, or no cel could be allocated.
|
|
|
|
=== jlSpriteBankLoadPrecompiled
|
|
|
|
[source,c]
|
|
----
|
|
uint16_t jlSpriteBankLoadPrecompiled(const char *path, jlSpriteT **outCels,
|
|
uint16_t maxCels, uint16_t *outPalette);
|
|
----
|
|
|
|
Loads cels from a `.spc` pre-compiled bank built for *this* platform. Identical
|
|
contract to `jlSpriteBankLoad`, except each cel arrives already compiled: its
|
|
native routines are copied straight into the codegen arena and it draws at full
|
|
speed with no runtime compilation.
|
|
|
|
A cel that does not fit the arena loads uncompiled and falls back to the
|
|
interpreter -- never an error. This makes it a drop-in, faster-starting
|
|
replacement for `jlSpriteBankLoad` plus a compile pass.
|
|
|
|
*Parameters:* as `jlSpriteBankLoad`.
|
|
|
|
*Returns:* the number of cels written, or 0 if the file is missing or its
|
|
magic, target or shift count does not match this build.
|
|
|
|
=== jlSpriteBankSavePrecompiled
|
|
|
|
[source,c]
|
|
----
|
|
bool jlSpriteBankSavePrecompiled(const char *path, jlSpriteT **cels,
|
|
uint16_t count, const uint16_t *palette);
|
|
----
|
|
|
|
Writes an already-compiled bank to a `.spc` cache file -- the runtime twin of
|
|
the offline `spritebake`. Call it after loading and compiling so a later launch
|
|
can skip the compile. This lets you ship the small cross-platform `.spr` and
|
|
cache the large per-platform compiled form on first run.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`path` |`const char *` |Destination path.
|
|
|`cels` |`jlSpriteT **` |The sprites to cache. Uncompiled ones are cached
|
|
as-is and will compile again next run.
|
|
|`count` |`uint16_t` |How many sprites.
|
|
|`palette` |`const uint16_t *` |Optional, may be `NULL`. Embedded if given.
|
|
|===
|
|
|
|
*Returns:* `true` on success; `false` if the destination is not writable, which
|
|
is harmless -- the program simply keeps compiling at startup.
|
|
|
|
=== jlSpriteCreate
|
|
|
|
[source,c]
|
|
----
|
|
jlSpriteT *jlSpriteCreate(const uint8_t *tileData, uint8_t widthTiles,
|
|
uint8_t heightTiles);
|
|
----
|
|
|
|
Wraps a tile-data blob in a sprite.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`tileData` |`const uint8_t *` |`widthTiles * heightTiles * 32` bytes. *Not
|
|
copied* -- it must outlive the sprite, and you keep ownership.
|
|
|`widthTiles` |`uint8_t` |Width in tiles. Must be non-zero.
|
|
|`heightTiles` |`uint8_t` |Height in tiles. Must be non-zero.
|
|
|===
|
|
|
|
*Returns:* the sprite, or `NULL` if either dimension is 0 or the codegen arena
|
|
cannot fit a placeholder entry.
|
|
|
|
=== jlSpriteDestroy
|
|
|
|
[source,c]
|
|
----
|
|
void jlSpriteDestroy(jlSpriteT *sp);
|
|
----
|
|
|
|
Releases a sprite and any codegen entries cached for it. The tile data it was
|
|
built from is *not* freed -- that buffer is yours.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`sp` |`jlSpriteT *` |Sprite to release.
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
=== jlSpriteCompile
|
|
|
|
[source,c]
|
|
----
|
|
bool jlSpriteCompile(jlSpriteT *sp);
|
|
----
|
|
|
|
Compiles the sprite's draw routines into the codegen arena. Idempotent --
|
|
compiling an already-compiled sprite is a no-op that returns `true`.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`sp` |`jlSpriteT *` |Sprite to compile.
|
|
|===
|
|
|
|
*Returns:* `true` if the sprite is compiled. `false` if the arena is full (you
|
|
may call `jlSpriteCompact` and retry) or the sprite has no source tile data.
|
|
|
|
=== jlSpriteCompiledSize
|
|
|
|
[source,c]
|
|
----
|
|
uint32_t jlSpriteCompiledSize(const jlSpriteT *sp);
|
|
----
|
|
|
|
Diagnostic. The right way to size `jlConfigT.codegenBytes` for real artwork on
|
|
the current platform, rather than guessing.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`sp` |`const jlSpriteT *` |Sprite to measure.
|
|
|===
|
|
|
|
*Returns:* bytes occupied in the codegen arena, summed over all compiled shift
|
|
variants. Returns 0 if the sprite was never compiled.
|
|
|
|
=== jlSpritePrewarm
|
|
|
|
[source,c]
|
|
----
|
|
void jlSpritePrewarm(jlSpriteT *sp);
|
|
----
|
|
|
|
Hints that a sprite will be drawn soon. A wrapper around `jlSpriteCompile` that
|
|
ignores the return value, for callers that do not need to handle failure.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`sp` |`jlSpriteT *` |Sprite to compile.
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
=== jlSpriteDraw
|
|
|
|
[source,c]
|
|
----
|
|
void jlSpriteDraw(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y);
|
|
----
|
|
|
|
Draws the sprite at a pixel position. Source pixels of colour 0 are skipped.
|
|
Off-surface portions are clipped.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`s` |`jlSurfaceT *` |Destination surface.
|
|
|`sp` |`jlSpriteT *` |Sprite to draw.
|
|
|`x`, `y` |`int16_t` |Top-left pixel position.
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
=== jlSpriteSaveUnder
|
|
|
|
[source,c]
|
|
----
|
|
void jlSpriteSaveUnder(const jlSurfaceT *s, jlSpriteT *sp,
|
|
int16_t x, int16_t y, jlSpriteBackupT *backup);
|
|
----
|
|
|
|
Captures the destination region that a `jlSpriteDraw` at the same position
|
|
would write, so it can be restored later.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`s` |`const jlSurfaceT *` |Source surface.
|
|
|`sp` |`jlSpriteT *` |Sprite whose footprint to capture.
|
|
|`x`, `y` |`int16_t` |Position the sprite will be drawn at.
|
|
|`backup` |`jlSpriteBackupT *` |Receives the capture. You must set `bytes` to
|
|
a buffer of at least `JOEY_SPRITE_BACKUP_BYTES(w, h)` capacity for fully
|
|
in-bounds draws. For clipped draws only the visible bytes are stored.
|
|
|===
|
|
|
|
*Returns:* nothing. The captured geometry is reported in the backup's `x`,
|
|
`width`, `height` and `sizeBytes` fields.
|
|
|
|
=== jlSpriteRestoreUnder
|
|
|
|
[source,c]
|
|
----
|
|
void jlSpriteRestoreUnder(jlSurfaceT *s, const jlSpriteBackupT *backup);
|
|
----
|
|
|
|
Repaints the destination from a previous capture.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`s` |`jlSurfaceT *` |Destination surface.
|
|
|`backup` |`const jlSpriteBackupT *` |A backup from `jlSpriteSaveUnder`, not
|
|
invalidated by other writes overlapping its recorded window.
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
=== jlSpriteSaveAndDraw
|
|
|
|
[source,c]
|
|
----
|
|
void jlSpriteSaveAndDraw(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y,
|
|
jlSpriteBackupT *backup);
|
|
----
|
|
|
|
Captures then draws, in one call. Identical in effect to `jlSpriteSaveUnder`
|
|
followed by `jlSpriteDraw`, but validation is shared, the destination pointer
|
|
is computed once, and one dirty-rectangle mark covers both. Saves roughly a
|
|
full dispatcher chain per sprite per frame.
|
|
|
|
*Parameters:* as `jlSpriteSaveUnder`. *Returns:* nothing.
|
|
|
|
=== The Trusted Variants
|
|
|
|
[source,c]
|
|
----
|
|
void jlSpriteDrawTrusted(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y);
|
|
void jlSpriteSaveUnderTrusted(const jlSurfaceT *s, jlSpriteT *sp,
|
|
int16_t x, int16_t y, jlSpriteBackupT *backup);
|
|
void jlSpriteRestoreUnderTrusted(jlSurfaceT *s, const jlSpriteBackupT *backup);
|
|
void jlSpriteSaveAndDrawTrusted(jlSurfaceT *s, jlSpriteT *sp,
|
|
int16_t x, int16_t y, jlSpriteBackupT *backup);
|
|
----
|
|
|
|
Expert inner-loop entry points. Each has the same effect as its plain
|
|
counterpart but *skips the per-call geometry validation* -- roughly eleven
|
|
range and alignment checks plus the NULL and sprite-slot gates.
|
|
|
|
Use them only in a game that pre-compiles its sprites and already clamps
|
|
positions on screen. Anything the fast path does not recognise falls back to
|
|
the fully validated call, so a conservative caller degrades safely rather than
|
|
corrupting -- but the guarantees below are still yours to keep.
|
|
|
|
[WARNING]
|
|
====
|
|
Behaviour is *undefined* -- out-of-bounds writes, crashes -- unless all of the
|
|
following hold:
|
|
|
|
* `s`, `backup`, `backup->sprite` and `backup->bytes` are non-`NULL`;
|
|
* the sprite is fully on-surface at `(x, y)`, because *no clipping happens*;
|
|
* for the restore, the backup came from a save that took the compiled path:
|
|
16-pixel-aligned x and width on planar ports, even on chunky, height equal
|
|
to the sprite's pixel height, and a buffer at least 2-byte aligned;
|
|
* the recorded window is fully on the destination surface.
|
|
|
|
Use the plain calls anywhere those invariants are not statically guaranteed.
|
|
====
|
|
|
|
The canonical pattern is a trusted save-and-draw this frame, a trusted restore
|
|
next frame.
|
|
|
|
=== jlSpriteCreateFromSurface
|
|
|
|
[source,c]
|
|
----
|
|
jlSpriteT *jlSpriteCreateFromSurface(const jlSurfaceT *src, int16_t x, int16_t y,
|
|
uint8_t widthTiles, uint8_t heightTiles);
|
|
----
|
|
|
|
Snapshots a tile-aligned region of a surface into a new sprite. The pixels are
|
|
copied into a sprite-owned buffer, so the source may be modified afterwards.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`src` |`const jlSurfaceT *` |Source surface.
|
|
|`x`, `y` |`int16_t` |Top-left in *pixels*, and both must be multiples of 8.
|
|
|`widthTiles`, `heightTiles` |`uint8_t` |Size in *tiles*, each 8x8 pixels.
|
|
|===
|
|
|
|
*Returns:* the new sprite, or `NULL` if the coordinates are not tile-aligned.
|
|
|
|
=== jlSpriteCompact
|
|
|
|
[source,c]
|
|
----
|
|
void jlSpriteCompact(void);
|
|
----
|
|
|
|
Defragments the codegen arena, consolidating free space and reclaiming holes
|
|
left by destroyed sprites. Costs time proportional to the bytes in use, so call
|
|
it between levels rather than per frame.
|
|
|
|
Sprite pointers you hold are *not* invalidated: internal indirection means draw
|
|
calls pick up new code addresses automatically.
|
|
|
|
*Parameters:* none. *Returns:* nothing.
|
|
|
|
=== jlSpriteCodegenBytesUsed / jlSpriteCodegenBytesTotal
|
|
|
|
[source,c]
|
|
----
|
|
uint32_t jlSpriteCodegenBytesUsed(void);
|
|
uint32_t jlSpriteCodegenBytesTotal(void);
|
|
----
|
|
|
|
Arena introspection, for judging whether to compact or to raise
|
|
`codegenBytes`.
|
|
|
|
*Parameters:* none.
|
|
|
|
*Returns:* bytes in use, and total arena size. Free space is the difference,
|
|
though it may be fragmented across holes until `jlSpriteCompact` runs.
|