// Surface type and allocation. // // All surfaces are 320x200 16-color images. In-memory storage is // target-native: chunky 4bpp packed (two pixels per byte, high nibble // is the left pixel) on IIgs and DOS; native planar (4 separate // bitplanes on Amiga, word-interleaved planes on Atari ST) on the // 68k ports. Public API entry points speak in color indices (0..15) // regardless, so game code does not see the storage format. // // Each surface carries its own 200-entry SCB (scanline control byte) // table and a 16-by-16 $0RGB palette set. See docs/DESIGN.md section 6. #ifndef JOEYLIB_SURFACE_H #define JOEYLIB_SURFACE_H #include "platform.h" #include "types.h" // ----- Fixed surface dimensions (library contract) ----- #define SURFACE_WIDTH 320 #define SURFACE_HEIGHT 200 #define SURFACE_BYTES_PER_ROW 160 #define SURFACE_PIXELS_SIZE (SURFACE_BYTES_PER_ROW * SURFACE_HEIGHT) #define SURFACE_PALETTE_COUNT 16 #define SURFACE_COLORS_PER_PALETTE 16 #define SURFACE_PALETTE_ENTRIES (SURFACE_PALETTE_COUNT * SURFACE_COLORS_PER_PALETTE) // ----- Opaque public type ----- typedef struct jlSurfaceT jlSurfaceT; // Allocate a new offscreen surface. Returns NULL on failure (jlLastError // describes the reason). jlSurfaceT *jlSurfaceCreate(void); // Release an offscreen surface previously returned by jlSurfaceCreate. // Passing NULL is a no-op. Passing the stage is a no-op. void jlSurfaceDestroy(jlSurfaceT *s); // The library-owned stage: the back-buffer surface that jlStagePresent // flips to the display. Always valid between jlInit and jlShutdown. // On IIgs the stage's pixel buffer is pinned to bank $01 SHR space with // shadow inhibited so writes are full-speed (2.8 MHz) and isolated from // the displayed framebuffer until the next jlStagePresent. jlSurfaceT *jlStageGet(void); // Copy pixels, SCBs, and palettes from src into dst. Both must be valid // surfaces. void jlSurfaceCopy(jlSurfaceT *dst, const jlSurfaceT *src); // Persist a surface to disk in target-native form: 32000 bytes of // pixels followed by 200 bytes of SCB followed by 512 bytes of $0RGB // palette table (16 palettes x 16 colors, native uint16_t). Returns // false if the file can't be opened or the write fails. The same // platform that wrote the file must read it -- no endianness // conversion is performed. bool jlSurfaceSaveFile(const jlSurfaceT *src, const char *path); // Read a surface from disk, overwriting dst. Returns false if the // file is missing, the wrong size, or the read fails. dst keeps its // identity (no reallocation). bool jlSurfaceLoadFile(jlSurfaceT *dst, const char *path); // 32-bit content hash of the surface. NOT FNV-1a: a two-stream 16-bit // multiplicative hash -- per input byte b, lo = lo * 31 + b and // hi = hi * 251 + b (mod 2^16), seeded lo = 0xACE1 / hi = 0x1357, and // combined as (hi << 16) | lo. The hashed domain, in order: // 1. the packed chunky pixel byte stream (32000 bytes, two 4-bit // pixels per byte, high nibble = left pixel, row-major), // 2. the 200 SCB bytes, // 3. the 256 palette uint16 entries, each folded high byte then // low byte. // SURFACE_HASH_MIX_BYTE in src/core/surfaceInternal.h is the single // source of truth for the per-byte mix; a port override (the planar // Amiga/ST readers) must reuse it byte-for-byte and feed the identical // byte sequence, so the same logical pixels produce the same hash on // every port regardless of internal storage format. Used by the UBER // validation harness to pixel-compare ports against an IIgs golden // reference. uint32_t jlSurfaceHash(const jlSurfaceT *s); #endif