172 lines
7.8 KiB
C
172 lines
7.8 KiB
C
// Sprites: rectangles of 8x8 tiles drawn at arbitrary pixel positions
|
|
// with color-0 transparency.
|
|
//
|
|
// A sprite's pixel data is `widthTiles * heightTiles * 32` bytes,
|
|
// 4bpp packed, laid out as a flat blob of 8x8 tiles. Tile order is
|
|
// row-major (tile (0,0), tile (1,0), ..., tile (widthTiles-1,0),
|
|
// tile (0,1), ...). Within each tile, rows are top-to-bottom and
|
|
// each row is 4 bytes (8 pixels at 4bpp packed; high nibble = left
|
|
// pixel).
|
|
//
|
|
// Color 0 is always transparent on draw (DESIGN.md contract). Use a
|
|
// tile-block draw if you need an opaque rectangle.
|
|
//
|
|
// Performance contract: spriteDraw should be at least as fast as the
|
|
// IIgs reference (the DESIGN.md spec calls for runtime-compiled draw
|
|
// code per CPU). v1 ships with an interpreted fallback that gives
|
|
// correct output everywhere; codegen lands per platform after the
|
|
// API stabilizes. spritePrewarm is a hint that the application is
|
|
// about to draw the sprite repeatedly -- a future codegen-enabled
|
|
// build will use it to compile shift variants ahead of the first
|
|
// draw. With the interpreter it is a no-op.
|
|
|
|
#ifndef JOEYLIB_SPRITE_H
|
|
#define JOEYLIB_SPRITE_H
|
|
|
|
#include "platform.h"
|
|
#include "surface.h"
|
|
#include "types.h"
|
|
|
|
// Sprites always write to a 4bpp packed SurfaceT, never to display
|
|
// memory directly (halPresent owns that path). The codegen emits 2
|
|
// shift variants on every platform: shift 0 for even x (sprite byte
|
|
// boundaries match destination byte boundaries) and shift 1 for odd
|
|
// x (each destination byte combines two adjacent sprite bytes'
|
|
// nibbles).
|
|
#define JOEY_SPRITE_SHIFT_COUNT 2
|
|
|
|
typedef enum {
|
|
SPRITE_FLAGS_NONE = 0
|
|
} SpriteFlagsE;
|
|
|
|
typedef struct SpriteT SpriteT;
|
|
|
|
// SpriteBackupT holds the destination bytes that lived under a sprite
|
|
// before it was drawn, so the application can restore them after the
|
|
// sprite moves. Sized for the largest backup the app will need; a
|
|
// stack-allocated SpriteBackupT plus a caller-owned byte buffer keeps
|
|
// the runtime allocation-free.
|
|
typedef struct {
|
|
SpriteT *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;
|
|
} SpriteBackupT;
|
|
|
|
// Wrap a tile-data blob in a SpriteT. The tile data must outlive the
|
|
// SpriteT; we do not copy it. Returns NULL if widthTiles or
|
|
// heightTiles is 0, or if the codegen arena cannot fit a placeholder
|
|
// entry for this sprite.
|
|
SpriteT *spriteCreate(const uint8_t *tileData, uint8_t widthTiles, uint8_t heightTiles, SpriteFlagsE flags);
|
|
|
|
// Release a SpriteT and any codegen entries cached for it. The tile
|
|
// data the sprite was constructed from is NOT freed -- the caller
|
|
// owns that buffer.
|
|
void spriteDestroy(SpriteT *sp);
|
|
|
|
// Compile the sprite's draw routines into the codegen arena. After
|
|
// this returns true, spriteDraw uses the compiled fast path on
|
|
// platforms where the emitter is wired (currently x86/DOS). Returns
|
|
// false if the arena is full (caller may run spriteCompact and
|
|
// retry), the platform doesn't have a real emitter yet, or the
|
|
// sprite has no source tile data (e.g., it was loaded already
|
|
// compiled via spriteLoadFile).
|
|
//
|
|
// Idempotent: calling on a sprite that's already compiled is a
|
|
// no-op and returns true.
|
|
bool spriteCompile(SpriteT *sp);
|
|
|
|
// Hint that this sprite will be drawn soon. Currently a wrapper
|
|
// around spriteCompile that ignores the return value, kept for API
|
|
// symmetry with the rest of the library and for callers that don't
|
|
// care about compile success.
|
|
void spritePrewarm(SpriteT *sp);
|
|
|
|
// Draw the sprite at pixel (x,y) on the destination surface. Pixels
|
|
// equal to color 0 in the sprite source are skipped (transparent).
|
|
// Off-surface portions are clipped.
|
|
void spriteDraw(SurfaceT *s, SpriteT *sp, int16_t x, int16_t y);
|
|
|
|
// Capture the destination region a subsequent spriteDraw at the same
|
|
// (x,y) would write to. backup->bytes must have at least
|
|
// (widthTiles*4) * (heightTiles*8) bytes of capacity for fully
|
|
// in-bounds draws; for clipped draws only the visible bytes are
|
|
// stored. The captured region's exact size is reported in
|
|
// backup->sizeBytes.
|
|
void spriteSaveUnder(const SurfaceT *s, SpriteT *sp, int16_t x, int16_t y, SpriteBackupT *backup);
|
|
|
|
// Repaint the destination region from a SpriteBackupT captured by a
|
|
// prior spriteSaveUnder. The backup must not have been invalidated
|
|
// by other writes that overlapped its captured region.
|
|
void spriteRestoreUnder(SurfaceT *s, const SpriteBackupT *backup);
|
|
|
|
// Combined save-then-draw entry point. The common animation pattern
|
|
// captures the destination bytes about to be overwritten, then draws
|
|
// the sprite. Both ops share validation, the destination ptr is
|
|
// computed once, and a single dirty-rect mark covers both. Saves
|
|
// roughly one full dispatcher chain (~150 cyc on IIgs ORCA-C) per
|
|
// frame versus calling spriteSaveUnder + spriteDraw separately.
|
|
//
|
|
// Identical semantics to:
|
|
// spriteSaveUnder(s, sp, x, y, backup);
|
|
// spriteDraw(s, sp, x, y);
|
|
// modulo: the dirty rect is marked once for the union (which here is
|
|
// just the draw rect, since save doesn't write).
|
|
void spriteSaveAndDraw(SurfaceT *s, SpriteT *sp, int16_t x, int16_t y, SpriteBackupT *backup);
|
|
|
|
// Snapshot an 8x8-aligned region of a SurfaceT into a new SpriteT.
|
|
// The captured pixel data is copied into a sprite-owned buffer so
|
|
// the source surface can be modified afterwards. Width and height
|
|
// are in TILES (each tile = 8x8 pixels). x and y are in pixels and
|
|
// must be aligned to a tile boundary (multiple of 8) on the source
|
|
// surface; misaligned coordinates return NULL.
|
|
SpriteT *spriteCreateFromSurface(const SurfaceT *src, int16_t x, int16_t y,
|
|
uint8_t widthTiles, uint8_t heightTiles, SpriteFlagsE flags);
|
|
|
|
// Load a sprite from a `.spr` file produced by the host-side
|
|
// joeysprite tool or by spriteSaveFile. Format is target-native for
|
|
// the compiled-code section:
|
|
// byte 0 widthTiles
|
|
// byte 1 heightTiles
|
|
// bytes 2-3 codeSize (LE16)
|
|
// bytes 4-5 tileBytes (LE16) = widthTiles*heightTiles*32
|
|
// ... offsets table (JOEY_SPRITE_SHIFT_COUNT *
|
|
// SPRITE_OP_COUNT * uint16_t LE)
|
|
// ... compiled code (codeSize bytes)
|
|
// ... raw tile data (tileBytes bytes; tile-major 4bpp)
|
|
// The runtime keeps both the compiled bytes (fast-path draws) and
|
|
// the tile data (interpreter clip path), so loaded sprites work for
|
|
// partially-off-surface draws without crashing.
|
|
SpriteT *spriteLoadFile(const char *path, SpriteFlagsE flags);
|
|
|
|
// Same as spriteLoadFile but parses bytes already in memory.
|
|
SpriteT *spriteFromCompiledMem(const uint8_t *data, uint32_t length, SpriteFlagsE flags);
|
|
|
|
// Persist a sprite to disk in `.spr` format. Sprites created via
|
|
// spriteCreate / spriteCreateFromSurface that have not been
|
|
// compiled yet are force-compiled here (so the resulting file can
|
|
// always be loaded back via spriteLoadFile). Returns false if the
|
|
// codegen arena is full or the platform's emitter is not yet
|
|
// implemented.
|
|
bool spriteSaveFile(SpriteT *sp, const char *path);
|
|
|
|
// Defragment the codegen arena. Walks live sprite slots and
|
|
// memmoves them down to consolidate free space; any holes left by
|
|
// destroyed sprites are reclaimed. Costs O(arena_used_bytes); call
|
|
// between levels rather than per frame. SpriteT pointers held by
|
|
// the application are NOT invalidated -- internal indirection
|
|
// through the slot record means draw calls automatically pick up
|
|
// the new code address on the next call.
|
|
void spriteCompact(void);
|
|
|
|
// Arena introspection. Used to gauge whether spriteCompact is
|
|
// worth running, or whether the codegenBytes budget needs to grow.
|
|
// Free space is (Total - Used), but it may be fragmented across
|
|
// holes until spriteCompact runs.
|
|
uint32_t spriteCodegenBytesUsed(void);
|
|
uint32_t spriteCodegenBytesTotal(void);
|
|
|
|
#endif
|