167 lines
5.7 KiB
C
167 lines
5.7 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 (JL_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[JL_SURFACE_COLORS_PER_PALETTE];
|
|
uint16_t pal;
|
|
uint16_t col;
|
|
uint16_t blue;
|
|
|
|
for (pal = 0u; pal < JL_SURFACE_PALETTE_COUNT; pal++) {
|
|
for (col = 0u; col < JL_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 % JL_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) % JL_SURFACE_PALETTE_COUNT);
|
|
run++;
|
|
}
|
|
jlScbSet(screen, line, runPalette);
|
|
runLeft--;
|
|
}
|
|
for (line = IRREGULAR_END; line < JL_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, JL_SURFACE_HEIGHT, (uint8_t)(stripe % JL_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;
|
|
config.noAudio = true;
|
|
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;
|
|
}
|