128 lines
6.3 KiB
C
128 lines
6.3 KiB
C
// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
|
// See LICENSE.md for license information.
|
|
// SPDX-License-Identifier: MIT
|
|
//
|
|
// 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 the IIgs, DOS, DVX, and the hosted ports;
|
|
// native planar (4 separate bitplanes on the Amiga and X68000,
|
|
// word-interleaved planes on the 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 the manual's "Surfaces and
|
|
// Presenting" (docs/ApiSurface.adoc).
|
|
|
|
#ifndef JOEYLIB_SURFACE_H
|
|
#define JOEYLIB_SURFACE_H
|
|
|
|
#include "platform.h"
|
|
#include "types.h"
|
|
|
|
// ----- Fixed surface dimensions (library contract) -----
|
|
|
|
#define JL_SURFACE_WIDTH 320
|
|
#define JL_SURFACE_HEIGHT 200
|
|
#define JL_SURFACE_BYTES_PER_ROW 160
|
|
#define JL_SURFACE_PIXELS_SIZE (JL_SURFACE_BYTES_PER_ROW * JL_SURFACE_HEIGHT)
|
|
|
|
#define JL_SURFACE_PALETTE_COUNT 16
|
|
#define JL_SURFACE_COLORS_PER_PALETTE 16
|
|
#define JL_SURFACE_PALETTE_ENTRIES (JL_SURFACE_PALETTE_COUNT * JL_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);
|
|
|
|
// Copy a pixel rectangle from src into dst at the SAME position (no
|
|
// repositioning, so planar ports never shift bits). The rect is
|
|
// clipped to the surface, then widened OUTWARD to the 16-pixel group
|
|
// grid ON EVERY PORT -- the copied region is x rounded down and
|
|
// x + w rounded up to multiples of 16 (same region rule as
|
|
// jlSpriteSaveUnder), so up to 15 extra pixels on each side are
|
|
// copied. Two consequences the caller owns: a snapped erase can
|
|
// clobber on-screen content up to 15 px outside the requested rect,
|
|
// and it silently invalidates any live jlSpriteBackupT whose
|
|
// recorded region it touches. Copies pixels only: SCBs and palettes
|
|
// are untouched. The intended use is restoring background
|
|
// rectangles from a pristine copy of the scene (the clean-buffer
|
|
// pattern) instead of per-sprite save/restore -- erase ALL regions,
|
|
// then redraw ALL sprites; do not mix with backup-restore in the
|
|
// same region.
|
|
void jlSurfaceCopyRect(jlSurfaceT *dst, const jlSurfaceT *src, int16_t x, int16_t y, uint16_t w, uint16_t h);
|
|
|
|
// Write a rectangle of color indices onto a surface at (x, y): src holds
|
|
// h rows of w bytes, row r starting at src + r * srcStride, one pixel's
|
|
// color index in each (0..15; the high nibble is ignored). With doubleWide
|
|
// each byte fills two pixels side by side, so the rectangle is 2w pixels
|
|
// wide on the surface (a 160-wide image on the 320-wide screen). Clipped
|
|
// to the surface, pixel by pixel. Whatever a port stores pixels as, the
|
|
// same bytes give the same pixels on every port.
|
|
void jlSurfacePasteIndexed(jlSurfaceT *dst, int16_t x, int16_t y, uint16_t w, uint16_t h, const uint8_t *src, uint16_t srcStride, bool doubleWide);
|
|
|
|
// The reverse of jlSurfacePasteIndexed: read a rectangle of a surface at
|
|
// (x, y) into dst as color indices, h rows of w bytes, row r starting at
|
|
// dst + r * dstStride, one pixel's color index (0..15) in each. With
|
|
// doubleWide the rectangle is 2w pixels wide on the surface and each byte
|
|
// takes the left pixel of its pair. A byte whose pixel is off the surface
|
|
// reads 0, as jlSamplePixel does.
|
|
void jlSurfaceSnapIndexed(const jlSurfaceT *src, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t *dst, uint16_t dstStride, bool doubleWide);
|
|
|
|
// 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, has an SCB byte above 15, or the
|
|
// read fails; a read that fails part way can leave dst's pixels partly
|
|
// overwritten, but its SCBs and palettes are then untouched. Color 0
|
|
// of every palette loaded is set to black, as jlPaletteSet does. dst
|
|
// keeps its identity (no reallocation). Neither file call sets
|
|
// jlLastError.
|
|
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, and X68000 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
|