joeylib2/src/core/palette.c

126 lines
4.3 KiB
C

// Palette accessors.
//
// Library contract: color 0 of every palette is forced to black ($000).
// jlPaletteSet silently masks that entry regardless of what the caller
// provides.
#include <stddef.h>
#include <string.h>
#include "joey/palette.h"
#include "surfaceInternal.h"
// jlPaletteSet / jlPaletteGet reach palette row N via byte-pointer math
// + a single `<< 5` shift (= 32 bytes per row) to avoid a per-access
// multiply helper. That stride is only correct while a palette row is
// exactly SURFACE_COLORS_PER_PALETTE * sizeof(uint16_t) bytes; pin the
// relationship so a future palette-width change fails to compile here
// rather than silently reading the wrong row.
typedef char paletteRowStrideMatchesShift[((1u << 5) == (SURFACE_COLORS_PER_PALETTE * sizeof(uint16_t))) ? 1 : -1];
// Standard 16-color EGA palette in IIgs $0RGB format. Used as the
// per-surface default at allocation time (paletteInitDefault) so a
// program that draws without first calling jlPaletteSet still gets
// recognizable colors instead of an all-black palette. EGA index 6
// is the canonical "brown" hack ($0A50, half-green) so CGA monitors
// rendered the third primary as brown rather than dark yellow.
static const uint16_t kDefaultPaletteEga[SURFACE_COLORS_PER_PALETTE] = {
0x0000, // 0: Black
0x000A, // 1: Blue
0x00A0, // 2: Green
0x00AA, // 3: Cyan
0x0A00, // 4: Red
0x0A0A, // 5: Magenta
0x0A50, // 6: Brown
0x0AAA, // 7: Light Gray
0x0555, // 8: Dark Gray
0x055F, // 9: Light Blue
0x05F5, // 10: Light Green
0x05FF, // 11: Light Cyan
0x0F55, // 12: Light Red
0x0F5F, // 13: Light Magenta
0x0FF5, // 14: Yellow
0x0FFF // 15: White
};
// ----- Internal API -----
void paletteInitDefault(jlSurfaceT *s) {
uint8_t i;
if (s == NULL) {
return;
}
// kDefaultPaletteEga is already valid $0RGB with color 0 == 0x0000,
// so copy it directly into each palette row instead of routing
// through jlPaletteSet 16 times (which would re-mask every entry
// and re-run the stage-dirty check on a surface that is not yet
// the stage). gStagePaletteDirty already defaults to true so the
// default palette is still uploaded on first present.
for (i = 0; i < SURFACE_PALETTE_COUNT; i++) {
memcpy(s->palette[i], kDefaultPaletteEga, sizeof(kDefaultPaletteEga));
}
}
// ----- Public API (alphabetical) -----
void jlPaletteGet(const jlSurfaceT *s, uint8_t paletteIndex, uint16_t *out16) {
const uint16_t *row;
if (s == NULL || out16 == NULL) {
return;
}
if (paletteIndex >= SURFACE_PALETTE_COUNT) {
return;
}
/* Byte-pointer math + shift to skip the multiply helper -- see
* jlPaletteSet for the reasoning. */
row = (const uint16_t *)((const uint8_t *)s->palette + ((uint16_t)paletteIndex << 5));
memcpy(out16, row, SURFACE_COLORS_PER_PALETTE * sizeof(uint16_t));
}
void jlPaletteSet(jlSurfaceT *s, uint8_t paletteIndex, const uint16_t *colors16) {
uint16_t *row;
if (s == NULL || colors16 == NULL) {
return;
}
if (paletteIndex >= SURFACE_PALETTE_COUNT) {
return;
}
// Compute the row pointer via byte-pointer math + a single shift
// (16 entries * 2 bytes = 32 = 1 << 5) so the 2D-array indexing
// avoids a multiply helper. The caller contract (joey/palette.h)
// guarantees $0RGB input, so copy the row wholesale and force
// color 0 to black -- the only documented silent rewrite. The copy
// is an unrolled sequence of word assignments, NOT memcpy: the
// clang IIgs libc memcpy is a ~30 cyc/byte byte loop (finding #79)
// and measurably REGRESSED this op when tried (1443 -> 603
// ops/sec); 15 direct word stores beat both that and the old
// masked loop on every port.
row = (uint16_t *)((uint8_t *)s->palette + ((uint16_t)paletteIndex << 5));
row[0] = 0x0000;
row[1] = colors16[1];
row[2] = colors16[2];
row[3] = colors16[3];
row[4] = colors16[4];
row[5] = colors16[5];
row[6] = colors16[6];
row[7] = colors16[7];
row[8] = colors16[8];
row[9] = colors16[9];
row[10] = colors16[10];
row[11] = colors16[11];
row[12] = colors16[12];
row[13] = colors16[13];
row[14] = colors16[14];
row[15] = colors16[15];
if (s == gStage) {
gStagePaletteDirty = true;
}
}