249 lines
9.1 KiB
C
249 lines
9.1 KiB
C
// Tiles as 8x8 surface regions. The whole API is byte-shoveling
|
|
// between jlSurfaceT regions (or between a jlSurfaceT region and a
|
|
// stack jlTileT buffer); no separate tileset container, no allocator.
|
|
//
|
|
// Block coords (bx, by) map to pixel (bx*8, by*8). At 4bpp packed
|
|
// each tile row is 4 bytes wide, so byte-aligned row copies are the
|
|
// inner loop for everything except the masked / transparent variant,
|
|
// which has to read-modify-write each byte to preserve destination
|
|
// pixels under transparent (color-0 by convention) source nibbles.
|
|
|
|
#include <stddef.h>
|
|
|
|
#include "joey/tile.h"
|
|
#include "port.h"
|
|
#include "surfaceInternal.h"
|
|
|
|
// The chunky tile inner loops live in src/generic/genericTile.c (the
|
|
// jlpGenericTile* ops); this TU is validation, dispatch, and dirty
|
|
// marking only -- it never touches pixel bytes.
|
|
|
|
// Block -> pixel coordinate for the dirty-rect bookkeeping below.
|
|
// Spelled as a shift because ORCA-C does not strength-reduce the *8
|
|
// into one -- it emits the ~150-cycle software multiply helper, the
|
|
// same trap SURFACE_ROW_OFFSET in surfaceInternal.h documents
|
|
// (PERF-AUDIT #48). The shift count 3 assumes TILE_PIXELS_PER_SIDE is
|
|
// 8; the guard keeps the two in sync.
|
|
#if TILE_PIXELS_PER_SIDE != 8
|
|
#error "TILE_BLOCK_TO_PIXEL assumes TILE_PIXELS_PER_SIDE == 8"
|
|
#endif
|
|
#define TILE_BLOCK_TO_PIXEL(_b) ((uint16_t)((uint16_t)(_b) << 3))
|
|
|
|
// Prototype (helper below is the only static in this TU).
|
|
static void drawTextUnion(uint8_t cx, uint8_t cy, uint8_t *bounds);
|
|
|
|
|
|
// jlDrawText's per-glyph dirty-union tracking, hoisted out of the
|
|
// glyph loop into a noinline helper: keeping five live union locals
|
|
// inside the loop overflows the llvm-mos w65816 register allocator
|
|
// ("ran out of registers" hard error). bounds[] = {minCx, maxCx,
|
|
// minCy, maxCy, drewAny}. noinline is portable gcc/clang syntax.
|
|
static void __attribute__((noinline)) drawTextUnion(uint8_t cx, uint8_t cy, uint8_t *bounds) {
|
|
if (bounds[4] == 0u) {
|
|
bounds[4] = 1u;
|
|
bounds[0] = cx;
|
|
bounds[1] = cx;
|
|
bounds[2] = cy;
|
|
} else {
|
|
if (cx < bounds[0]) {
|
|
bounds[0] = cx;
|
|
}
|
|
if (cx > bounds[1]) {
|
|
bounds[1] = cx;
|
|
}
|
|
}
|
|
// cy never decreases, so the last drawn glyph's row is always the
|
|
// union's bottom edge.
|
|
bounds[3] = cy;
|
|
}
|
|
|
|
|
|
// ----- Public API (alphabetical) -----
|
|
|
|
void jlDrawText(jlSurfaceT *dst, uint8_t bx, uint8_t by, const jlSurfaceT *fontSurface, const uint16_t *asciiMap, const char *str) {
|
|
uint16_t entry;
|
|
uint8_t cx;
|
|
uint8_t cy;
|
|
uint8_t ch;
|
|
uint8_t srcBx;
|
|
uint8_t srcBy;
|
|
uint8_t bounds[5];
|
|
|
|
if (dst == NULL || fontSurface == NULL || asciiMap == NULL || str == NULL) {
|
|
return;
|
|
}
|
|
cx = bx;
|
|
cy = by;
|
|
// Entry sanitation (PERF-AUDIT #22 verifier correction): the main
|
|
// loop below hands cx/cy straight to jlpTileCopyMasked, which does
|
|
// no bounds checking, so an out-of-range start must be disposed of
|
|
// here -- the wrap logic only guarantees in-range cx/cy after the
|
|
// first wrap. The historical contract is preserved bit-for-bit
|
|
// (the chk:drawText-offgrid golden covers it): characters landing
|
|
// on an out-of-range position are consumed without drawing while
|
|
// the cursor advances and wraps exactly as for drawn characters.
|
|
// For the documented bx in [0,39] / by in [0,24] inputs this whole
|
|
// loop costs two compares.
|
|
while (cx >= TILE_BLOCKS_PER_ROW || cy >= TILE_BLOCKS_PER_COL) {
|
|
if (*str == '\0') {
|
|
return;
|
|
}
|
|
str++;
|
|
cx++;
|
|
if (cx >= TILE_BLOCKS_PER_ROW) {
|
|
cx = 0;
|
|
cy++;
|
|
if (cy >= TILE_BLOCKS_PER_COL) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
bounds[4] = 0u;
|
|
while (*str != '\0') {
|
|
ch = (uint8_t)*str++;
|
|
entry = asciiMap[ch];
|
|
if (entry != TILE_NO_GLYPH) {
|
|
srcBx = (uint8_t)(entry & 0x00FFu);
|
|
srcBy = (uint8_t)((entry >> 8) & 0x00FFu);
|
|
// Only the asciiMap-supplied source block still needs
|
|
// validation per glyph: cx/cy are in range by the entry
|
|
// sanitation + wrap invariant and the NULL checks ran once
|
|
// above, so the public jlTileCopyMasked wrapper (which
|
|
// would re-check all of it and issue a dirty mark per
|
|
// glyph) is skipped (PERF-AUDIT #22).
|
|
if (srcBx < TILE_BLOCKS_PER_ROW && srcBy < TILE_BLOCKS_PER_COL) {
|
|
jlpTileCopyMasked(dst, cx, cy, fontSurface, srcBx, srcBy, 0u);
|
|
drawTextUnion(cx, cy, bounds);
|
|
}
|
|
}
|
|
cx++;
|
|
if (cx >= TILE_BLOCKS_PER_ROW) {
|
|
cx = 0;
|
|
cy++;
|
|
if (cy >= TILE_BLOCKS_PER_COL) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// One union dirty mark for the whole string instead of an 8-row
|
|
// mark per glyph (PERF-AUDIT #22). Over-approximates when the
|
|
// string wraps rows, which is safe: dirty state only widens what
|
|
// present uploads.
|
|
if (bounds[4] != 0u) {
|
|
surfaceMarkDirtyRect(dst, (int16_t)TILE_BLOCK_TO_PIXEL(bounds[0]), (int16_t)TILE_BLOCK_TO_PIXEL(bounds[2]), TILE_BLOCK_TO_PIXEL(bounds[1] - bounds[0] + 1u), TILE_BLOCK_TO_PIXEL(bounds[3] - bounds[2] + 1u));
|
|
}
|
|
}
|
|
|
|
|
|
void jlTileCopy(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy) {
|
|
uint16_t dstPixelX;
|
|
uint16_t dstPixelY;
|
|
|
|
if (dst == NULL || src == NULL) {
|
|
return;
|
|
}
|
|
if (dstBx >= TILE_BLOCKS_PER_ROW || dstBy >= TILE_BLOCKS_PER_COL ||
|
|
srcBx >= TILE_BLOCKS_PER_ROW || srcBy >= TILE_BLOCKS_PER_COL) {
|
|
return;
|
|
}
|
|
dstPixelX = TILE_BLOCK_TO_PIXEL(dstBx);
|
|
dstPixelY = TILE_BLOCK_TO_PIXEL(dstBy);
|
|
// Single op: machine asm/planar override or the generic chunky default.
|
|
jlpTileCopy(dst, dstBx, dstBy, src, srcBx, srcBy);
|
|
surfaceMarkDirtyRect(dst, (int16_t)dstPixelX, (int16_t)dstPixelY,
|
|
TILE_PIXELS_PER_SIDE, TILE_PIXELS_PER_SIDE);
|
|
}
|
|
|
|
|
|
void jlTileCopyMasked(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy, uint8_t transparentIndex) {
|
|
uint16_t dstPixelX;
|
|
uint16_t dstPixelY;
|
|
|
|
if (dst == NULL || src == NULL) {
|
|
return;
|
|
}
|
|
if (dstBx >= TILE_BLOCKS_PER_ROW || dstBy >= TILE_BLOCKS_PER_COL ||
|
|
srcBx >= TILE_BLOCKS_PER_ROW || srcBy >= TILE_BLOCKS_PER_COL) {
|
|
return;
|
|
}
|
|
dstPixelX = TILE_BLOCK_TO_PIXEL(dstBx);
|
|
dstPixelY = TILE_BLOCK_TO_PIXEL(dstBy);
|
|
jlpTileCopyMasked(dst, dstBx, dstBy, src, srcBx, srcBy, transparentIndex);
|
|
surfaceMarkDirtyRect(dst, (int16_t)dstPixelX, (int16_t)dstPixelY,
|
|
TILE_PIXELS_PER_SIDE, TILE_PIXELS_PER_SIDE);
|
|
}
|
|
|
|
|
|
void jlTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex) {
|
|
uint16_t pixelX;
|
|
uint16_t pixelY;
|
|
|
|
if (s == NULL) {
|
|
return;
|
|
}
|
|
if (bx >= TILE_BLOCKS_PER_ROW || by >= TILE_BLOCKS_PER_COL) {
|
|
return;
|
|
}
|
|
pixelX = TILE_BLOCK_TO_PIXEL(bx);
|
|
pixelY = TILE_BLOCK_TO_PIXEL(by);
|
|
// Single compile-time-selected op: machine asm/planar override or the
|
|
// generic chunky default.
|
|
jlpTileFill(s, bx, by, colorIndex);
|
|
surfaceMarkDirtyRect(s, (int16_t)pixelX, (int16_t)pixelY,
|
|
TILE_PIXELS_PER_SIDE, TILE_PIXELS_PER_SIDE);
|
|
}
|
|
|
|
|
|
void jlTilePaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, const jlTileT *in) {
|
|
uint16_t pixelX;
|
|
uint16_t pixelY;
|
|
|
|
if (dst == NULL || in == NULL) {
|
|
return;
|
|
}
|
|
if (bx >= TILE_BLOCKS_PER_ROW || by >= TILE_BLOCKS_PER_COL) {
|
|
return;
|
|
}
|
|
pixelX = TILE_BLOCK_TO_PIXEL(bx);
|
|
pixelY = TILE_BLOCK_TO_PIXEL(by);
|
|
// Single op: machine asm/planar override or the generic chunky default.
|
|
jlpTilePaste(dst, bx, by, &in->pixels[0]);
|
|
surfaceMarkDirtyRect(dst, (int16_t)pixelX, (int16_t)pixelY,
|
|
TILE_PIXELS_PER_SIDE, TILE_PIXELS_PER_SIDE);
|
|
}
|
|
|
|
|
|
void jlTilePasteMono(jlSurfaceT *dst, uint8_t bx, uint8_t by, const jlTileT *in, uint8_t fgColor, uint8_t bgColor) {
|
|
uint16_t pixelX;
|
|
uint16_t pixelY;
|
|
|
|
if (dst == NULL || in == NULL) {
|
|
return;
|
|
}
|
|
if (bx >= TILE_BLOCKS_PER_ROW || by >= TILE_BLOCKS_PER_COL) {
|
|
return;
|
|
}
|
|
fgColor &= 0x0Fu;
|
|
bgColor &= 0x0Fu;
|
|
pixelX = TILE_BLOCK_TO_PIXEL(bx);
|
|
pixelY = TILE_BLOCK_TO_PIXEL(by);
|
|
// Single compile-time op: planar ports colorize in their own tile format
|
|
// (Amiga/ST); chunky ports colorize into a chunky tile then jlpTilePaste.
|
|
// Replaces the old runtime pixels==NULL planar/chunky branch.
|
|
jlpTilePasteMono(dst, bx, by, &in->pixels[0], fgColor, bgColor);
|
|
surfaceMarkDirtyRect(dst, (int16_t)pixelX, (int16_t)pixelY,
|
|
TILE_PIXELS_PER_SIDE, TILE_PIXELS_PER_SIDE);
|
|
}
|
|
|
|
|
|
void jlTileSnap(const jlSurfaceT *src, uint8_t bx, uint8_t by, jlTileT *out) {
|
|
if (src == NULL || out == NULL) {
|
|
return;
|
|
}
|
|
if (bx >= TILE_BLOCKS_PER_ROW || by >= TILE_BLOCKS_PER_COL) {
|
|
return;
|
|
}
|
|
// Read-only single op: machine asm/planar override or the generic chunky
|
|
// default. No dirty mark.
|
|
jlpTileSnap(src, bx, by, &out->pixels[0]);
|
|
}
|