// 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 #include #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; } }