joeylib2/examples/scb/scb.c
2026-10-13 14:01:46 -05:00

166 lines
5.6 KiB
C

// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
// See LICENSE.md for license information.
// SPDX-License-Identifier: MIT
//
// SCB stress screen: the per-scanline palette under the heaviest load a
// program can reasonably put on it. Written for the ports that honour the SCB
// by reloading ONE hardware palette as the beam scans (the X68000's text-plane
// present, the Atari ST's Timer B), where every palette change is a deadline
// an interrupt has to meet. Portable C only: nothing here knows which port it
// runs on.
//
// All sixteen palettes are in use, and colour i of each palette differs from
// colour i of every other palette, so a line shown in the wrong palette shows
// the wrong colour everywhere but in colour 0 (black in every palette by
// library contract). Twenty 16-pixel vertical stripes run the full height,
// stripe s in colour s mod 16, so every line uses every colour.
//
// Colour i of palette p is $0RGB with red = p, green = i and
// blue = (p * 7 + i) mod 16.
//
// The SCB, in three zones:
// lines 0- 63 a new palette every line: 0, 1, ... 15, four times over
// lines 64-135 runs of irregular length: run k is 1 + (k * 5 mod 7) lines
// (1, 6, 4, 2, 7, 5, 3, repeating) in palette (k * 7 + 3) mod 16,
// the last run cut short by the zone's end
// lines 136-199 two palettes alternating every line: 6 on even lines, 9 on
// odd ones
// Every boundary is a palette change, the frame wrap included (line 199 is
// palette 9, line 0 palette 0): 147 changes a frame, back to back for the 64
// lines of each outer zone. scripts/verify-x68000-scb.sh computes the same
// screen from the same rules -- change both together.
//
// Every pass of the loop redraws and presents the whole stage, so the palette
// reloads land while the present is streaming into video memory, and sets all
// sixteen palettes again -- to the same colours, so the picture never changes
// -- so a port that keeps per-band state rebuilds it every pass while the
// frame it describes is on screen. Typed characters are read and dropped, so a
// gate can keep the keyboard interrupt busy during the check; ESC exits.
#include <stdio.h>
#include <stdlib.h>
#include <joey/joey.h>
// ----- Constants -----
#define STRIPE_WIDTH 16u // two bytes of a plane: whole-byte fills
#define STRIPE_COUNT (SURFACE_WIDTH / STRIPE_WIDTH)
#define COLOR_CHANNEL_MASK 0x0Fu
#define RED_SHIFT 8u
#define GREEN_SHIFT 4u
#define BLUE_PALETTE_STEP 7u
// Zone boundaries: [0, EVERY_LINE_END) changes every line,
// [EVERY_LINE_END, IRREGULAR_END) runs irregularly, the rest alternates.
#define EVERY_LINE_END 64u
#define IRREGULAR_END 136u
#define RUN_LENGTH_STEP 5u
#define RUN_LENGTH_SPAN 7u
#define RUN_PALETTE_STEP 7u
#define RUN_PALETTE_BASE 3u
#define EVEN_LINE_PALETTE 6u
#define ODD_LINE_PALETTE 9u
#define CODEGEN_DEFAULT 0u
// ----- Prototypes -----
static void buildPalettes(jlSurfaceT *screen);
static void buildScb(jlSurfaceT *screen);
static void drawStripes(jlSurfaceT *screen);
static bool escapeTyped(void);
// ----- Helpers (alphabetical) -----
static void buildPalettes(jlSurfaceT *screen) {
uint16_t colors[SURFACE_COLORS_PER_PALETTE];
uint16_t pal;
uint16_t col;
uint16_t blue;
for (pal = 0u; pal < SURFACE_PALETTE_COUNT; pal++) {
for (col = 0u; col < SURFACE_COLORS_PER_PALETTE; col++) {
blue = (uint16_t)((pal * BLUE_PALETTE_STEP + col) & COLOR_CHANNEL_MASK);
colors[col] = (uint16_t)((pal << RED_SHIFT) | (col << GREEN_SHIFT) | blue);
}
jlPaletteSet(screen, (uint8_t)pal, colors);
}
}
static void buildScb(jlSurfaceT *screen) {
uint16_t line;
uint16_t run;
uint16_t runLeft;
uint8_t runPalette;
for (line = 0u; line < EVERY_LINE_END; line++) {
jlScbSet(screen, line, (uint8_t)(line % SURFACE_PALETTE_COUNT));
}
run = 0u;
runLeft = 0u;
runPalette = 0u;
for (line = EVERY_LINE_END; line < IRREGULAR_END; line++) {
if (runLeft == 0u) {
runLeft = (uint16_t)(1u + (run * RUN_LENGTH_STEP) % RUN_LENGTH_SPAN);
runPalette = (uint8_t)((run * RUN_PALETTE_STEP + RUN_PALETTE_BASE) % SURFACE_PALETTE_COUNT);
run++;
}
jlScbSet(screen, line, runPalette);
runLeft--;
}
for (line = IRREGULAR_END; line < SURFACE_HEIGHT; line++) {
jlScbSet(screen, line, (uint8_t)(((line & 1u) != 0u) ? ODD_LINE_PALETTE : EVEN_LINE_PALETTE));
}
}
static void drawStripes(jlSurfaceT *screen) {
uint16_t stripe;
for (stripe = 0u; stripe < STRIPE_COUNT; stripe++) {
jlFillRect(screen, (int16_t)(stripe * STRIPE_WIDTH), 0, STRIPE_WIDTH, SURFACE_HEIGHT, (uint8_t)(stripe % SURFACE_COLORS_PER_PALETTE));
}
}
// Drain the typed-character queue; true if ESC was among them.
static bool escapeTyped(void) {
int ch;
bool escape;
escape = false;
for (ch = jlInputGetChar(); ch >= 0; ch = jlInputGetChar()) {
if (ch == JL_CHAR_ESCAPE) {
escape = true;
}
}
return escape;
}
int main(void) {
jlConfigT config;
jlSurfaceT *screen;
config.codegenBytes = CODEGEN_DEFAULT;
if (!jlInit(&config)) {
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
return EXIT_FAILURE;
}
screen = jlStageGet();
buildScb(screen);
for (;;) {
drawStripes(screen);
buildPalettes(screen);
jlStagePresent();
jlInputPoll();
if (escapeTyped()) {
break;
}
}
jlShutdown();
return EXIT_SUCCESS;
}