// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License. // See LICENSE.md for license information. // SPDX-License-Identifier: MIT // // Palette-change probe (PALPROBE, a gate's program, not an example): how // fast palette and SCB changes reach the screen, and whether they reach it // right. // // It times, over PROBE_FRAMES frames each, a palette change followed by a // present, and an SCB change followed by a present, on a screen of four SCB // bands, and logs the rates. Then it paints the bands (colour 1 of palettes // 0..3 red, green, blue, white), makes one palette change (palette 2's // colour 1 to yellow) and one SCB change that keeps every band where it was // (band 3 to palette 0), logs "PALPROBE: ready" and holds the screen for // HOLD_FRAMES frames, so a gate can look at it: top to bottom red, green, // yellow, red. scripts/verify-amiga-palette.sh is that gate. #include #include #define PROBE_FRAMES 200u #define HOLD_FRAMES 400u #define BANDS 4u #define BAND_LINES 50u #define PAINT_COLOR 1u #define MS_PER_SECOND 1000ul #define COLOR_MASK 0x0FFFu #define PALETTE_MASK 3u #define SCB_SWAP_FIRST 0u #define SCB_SWAP_LAST (BAND_LINES - 1u) #define YELLOW 0xFF0u #define CHANGED_PALETTE 2u #define MOVED_BAND 3u #define MOVED_TO 0u static void check(void); static void timeChanges(const char *name, bool scb); // Paint the bands, make the two changes, and hold the screen. static void check(void) { static const uint16_t firsts[BANDS] = { 0xF00, 0x0F0, 0x00F, 0xFFF }; uint16_t colors[JL_SURFACE_COLORS_PER_PALETTE]; jlSurfaceT *stage; uint16_t band; uint16_t frame; stage = jlStageGet(); for (band = 0; band < BANDS; band++) { memset(colors, 0, sizeof(colors)); colors[PAINT_COLOR] = firsts[band]; jlPaletteSet(stage, (uint8_t)band, colors); jlScbSetRange(stage, (uint16_t)(band * BAND_LINES), (uint16_t)(band * BAND_LINES + BAND_LINES - 1u), (uint8_t)band); jlFillRect(stage, 0, (int16_t)(band * BAND_LINES), JL_SURFACE_WIDTH, BAND_LINES, PAINT_COLOR); } jlStagePresent(); jlWaitVBL(); memset(colors, 0, sizeof(colors)); colors[PAINT_COLOR] = YELLOW; jlPaletteSet(stage, CHANGED_PALETTE, colors); jlStagePresent(); jlScbSetRange(stage, (uint16_t)(MOVED_BAND * BAND_LINES), (uint16_t)(MOVED_BAND * BAND_LINES + BAND_LINES - 1u), MOVED_TO); jlStagePresent(); jlLogF("PALPROBE: ready"); for (frame = 0; frame < HOLD_FRAMES; frame++) { jlWaitVBL(); } } // PROBE_FRAMES frames of palette changes (or SCB changes), each presented. static void timeChanges(const char *name, bool scb) { uint16_t colors[JL_SURFACE_COLORS_PER_PALETTE]; jlSurfaceT *stage; uint16_t start; uint32_t ms0; uint32_t elapsed; unsigned long count; stage = jlStageGet(); memset(colors, 0, sizeof(colors)); count = 0; jlWaitVBL(); start = jlFrameCount(); ms0 = jlMillisElapsed(); while ((uint16_t)(jlFrameCount() - start) < PROBE_FRAMES) { if (scb) { jlScbSetRange(stage, SCB_SWAP_FIRST, SCB_SWAP_LAST, (uint8_t)(count & PALETTE_MASK)); } else { colors[PAINT_COLOR] = (uint16_t)(count & COLOR_MASK); jlPaletteSet(stage, (uint8_t)(count & PALETTE_MASK), colors); } jlStagePresent(); count++; } elapsed = jlMillisElapsed() - ms0; jlLogF("PALPROBE: %s: %lu in %lu ms = %lu/s", name, count, (unsigned long)elapsed, (unsigned long)(count * MS_PER_SECOND / elapsed)); } int main(void) { jlConfigT cfg; jlSurfaceT *stage; uint16_t band; cfg.codegenBytes = 0; cfg.noAudio = true; if (!jlInit(&cfg)) { return 1; } stage = jlStageGet(); for (band = 0; band < BANDS; band++) { jlScbSetRange(stage, (uint16_t)(band * BAND_LINES), (uint16_t)(band * BAND_LINES + BAND_LINES - 1u), (uint8_t)band); jlFillRect(stage, 0, (int16_t)(band * BAND_LINES), JL_SURFACE_WIDTH, BAND_LINES, (uint8_t)(band + 1u)); } jlStagePresent(); timeChanges("palette+present", false); timeChanges("scb+present", true); check(); jlLogF("PALPROBE: done"); jlShutdown(); return 0; }