318 lines
11 KiB
C
318 lines
11 KiB
C
// Tile-bank check on a real port: loads this port's bake of the host gate's
|
|
// fixture both ways, checks every pixel, and makes sure every other target's
|
|
// bake is refused. It is the on-machine half of scripts/check-tilebank.sh:
|
|
// that host gate proves the shared loader and the generic paste; this proves
|
|
// that assetbake's encoder for THIS target writes exactly the layout this
|
|
// port's native tile paste reads, and that this build refuses the others.
|
|
//
|
|
// DATA/tilebank/ is staged by tests/host/tileBankStage.py (the make rule in
|
|
// make/<port>.mk), from tests/host/tileBank.png:
|
|
// own.tbk this port's bake: the bank that must load
|
|
// refuse<n>.tbk n = 1, 2, ...: every other target's bake
|
|
// expect.bin the PNG decoded by PIL, independent of assetbake's
|
|
// encoders: tileCount LE16, palette[16] LE16 $0RGB, then
|
|
// tileCount * 64 colour indices (tiles in bank order, each
|
|
// row-major)
|
|
//
|
|
// The checks, all on the stage:
|
|
// refused each refuse<n>.tbk loads nothing through jlTileBankLoad and
|
|
// pastes nothing through jlTileBankLoadToSurface
|
|
// streamed jlTileBankLoadToSurface pastes every tile of own.tbk, the
|
|
// palette it returns is the PNG's, and every pixel of the stage
|
|
// is right: blocks below the tile count show the PNG's tiles in
|
|
// bank order, the rest keep FILL_COLOR
|
|
// pasted the same pixels again through jlTileBankLoad + jlTilePaste
|
|
// Both loads draw onto a stage cleared to the host harness's FILL_COLOR with
|
|
// the default palette and SCBs, exactly the surface whose hash the host
|
|
// harness prints, so both hashes must equal that one cross-port value.
|
|
//
|
|
// Result: one jlLog line, flushed before anything is shown,
|
|
// TILEBANK: PASS tiles=55 streamed=55 pasted=55 palette=ok streamHash=<hash> pasteHash=<hash> refused=4/4
|
|
// which scripts/verify-<port>-tilebank.sh reads (and holds the hashes against
|
|
// the host's), then the tiles in the bank's own colours over a lamp centred on
|
|
// the stage: LAMP_PASS_COLOR or LAMP_FAIL_COLOR in palette LAMP_PALETTE, with
|
|
// the rest of its rows left at FILL_COLOR. The MAME gates find the verdict
|
|
// there before they read the log. Any key exits.
|
|
//
|
|
// Portable C only: nothing here knows which port it runs on.
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <joey/joey.h>
|
|
#include <joey/file.h>
|
|
|
|
// ----- Constants -----
|
|
|
|
// The staged files (tests/host/tileBankStage.py names them).
|
|
#define OWN_BANK "tilebank/own.tbk"
|
|
#define EXPECT_NAME "tilebank/expect.bin"
|
|
#define REFUSE_BANK_FORMAT "tilebank/refuse%u.tbk"
|
|
#define BANK_NAME_BYTES 32
|
|
|
|
// expect.bin layout (see the header comment).
|
|
#define LE16_BYTES 2
|
|
#define BYTE_BITS 8
|
|
#define PALETTE_COLORS SURFACE_COLORS_PER_PALETTE
|
|
#define EXPECT_OFF_PALETTE LE16_BYTES
|
|
#define EXPECT_HEADER_BYTES (EXPECT_OFF_PALETTE + PALETTE_COLORS * LE16_BYTES)
|
|
#define TILE_PIXELS (TILE_PIXELS_PER_SIDE * TILE_PIXELS_PER_SIDE)
|
|
|
|
// The stage is cleared to this before every load. It MUST equal FILL_COLOR in
|
|
// tests/host/tileBankHost.c: the host's hash is of a surface cleared to it.
|
|
#define FILL_COLOR 9u
|
|
|
|
// No sprites here, so the port's default codegen cache is plenty.
|
|
#define CODEGEN_DEFAULT 0u
|
|
|
|
// The verdict lamp, centred on the stage. Its rows get their own palette so
|
|
// the lamp colours cannot collide with the bank's sixteen.
|
|
#define LAMP_W 64
|
|
#define LAMP_H 64
|
|
#define LAMP_X ((SURFACE_WIDTH - LAMP_W) / 2)
|
|
#define LAMP_Y ((SURFACE_HEIGHT - LAMP_H) / 2)
|
|
#define LAMP_PALETTE 1u
|
|
#define LAMP_PASS_COLOR 1u
|
|
#define LAMP_FAIL_COLOR 2u
|
|
#define LAMP_DARK_RGB 0x0000u
|
|
#define LAMP_PASS_RGB 0x00F0u
|
|
#define LAMP_FAIL_RGB 0x0F00u
|
|
|
|
// ----- Prototypes -----
|
|
|
|
static bool loadExpected(void);
|
|
static void pasteTiles(uint16_t count);
|
|
static bool readAll(FILE *fp, uint8_t *dst, uint32_t bytes);
|
|
static uint16_t readLe16(const uint8_t *bytes);
|
|
static bool refuseBankFound(char *name, uint16_t n);
|
|
static uint16_t refuseCheck(uint16_t *banks);
|
|
static bool runChecks(void);
|
|
static void showVerdict(bool pass);
|
|
static bool stageMatches(uint16_t tilesDrawn);
|
|
|
|
// ----- State -----
|
|
|
|
static uint16_t gBankPalette[PALETTE_COLORS];
|
|
static uint16_t gExpectCount = 0u;
|
|
static uint16_t gExpectPalette[PALETTE_COLORS];
|
|
static uint8_t *gExpectPixels = NULL;
|
|
static jlSurfaceT *gScreen = NULL;
|
|
static jlTileT *gTiles = NULL;
|
|
static bool *gValid = NULL;
|
|
|
|
|
|
// Read expect.bin, and size the tile buffers to its tile count.
|
|
static bool loadExpected(void) {
|
|
FILE *fp;
|
|
uint8_t head[EXPECT_HEADER_BYTES];
|
|
uint32_t pixelBytes;
|
|
uint16_t i;
|
|
bool ok;
|
|
|
|
fp = jlDataOpen(EXPECT_NAME, "rb");
|
|
if (fp == NULL) {
|
|
return false;
|
|
}
|
|
ok = readAll(fp, head, sizeof(head));
|
|
if (ok) {
|
|
gExpectCount = readLe16(head);
|
|
ok = gExpectCount > 0u;
|
|
}
|
|
if (ok) {
|
|
for (i = 0u; i < PALETTE_COLORS; i++) {
|
|
gExpectPalette[i] = readLe16(&head[EXPECT_OFF_PALETTE + i * LE16_BYTES]);
|
|
}
|
|
pixelBytes = (uint32_t)gExpectCount * TILE_PIXELS;
|
|
gExpectPixels = (uint8_t *)jlAlloc(pixelBytes);
|
|
gTiles = (jlTileT *)jlAlloc((uint32_t)gExpectCount * sizeof(jlTileT));
|
|
gValid = (bool *)jlAlloc((uint32_t)gExpectCount * sizeof(bool));
|
|
ok = gExpectPixels != NULL && gTiles != NULL && gValid != NULL && readAll(fp, gExpectPixels, pixelBytes);
|
|
}
|
|
fclose(fp);
|
|
return ok;
|
|
}
|
|
|
|
|
|
// Clear the stage, then paste gTiles[0..count) in the row-major block order
|
|
// jlTileBankLoadToSurface uses.
|
|
static void pasteTiles(uint16_t count) {
|
|
uint16_t i;
|
|
|
|
jlSurfaceClear(gScreen, FILL_COLOR);
|
|
for (i = 0u; i < count; i++) {
|
|
jlTilePaste(gScreen, (uint8_t)(i % TILE_BLOCKS_PER_ROW), (uint8_t)(i / TILE_BLOCKS_PER_ROW), &gTiles[i]);
|
|
}
|
|
}
|
|
|
|
|
|
// fread until done: one fread need not fill a large request (the IIgs libc
|
|
// stops short), so a short read is only an error when nothing more comes.
|
|
static bool readAll(FILE *fp, uint8_t *dst, uint32_t bytes) {
|
|
uint32_t done;
|
|
size_t got;
|
|
|
|
done = 0u;
|
|
while (done < bytes) {
|
|
got = fread(dst + done, 1, (size_t)(bytes - done), fp);
|
|
if (got == 0u) {
|
|
return false;
|
|
}
|
|
done += (uint32_t)got;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
static uint16_t readLe16(const uint8_t *bytes) {
|
|
return (uint16_t)(bytes[0] | (bytes[1] << BYTE_BITS));
|
|
}
|
|
|
|
|
|
// Put refuse bank n's name in name (BANK_NAME_BYTES long); true when that
|
|
// bank was staged.
|
|
static bool refuseBankFound(char *name, uint16_t n) {
|
|
FILE *fp;
|
|
|
|
snprintf(name, BANK_NAME_BYTES, REFUSE_BANK_FORMAT, (unsigned)n);
|
|
fp = jlDataOpen(name, "rb");
|
|
if (fp == NULL) {
|
|
return false;
|
|
}
|
|
fclose(fp);
|
|
return true;
|
|
}
|
|
|
|
|
|
// Every staged refuse bank, both ways: jlTileBankLoad must load nothing and
|
|
// mark nothing valid, jlTileBankLoadToSurface must paste nothing. Returns how
|
|
// many were refused both ways; *banks gets how many were staged.
|
|
static uint16_t refuseCheck(uint16_t *banks) {
|
|
char name[BANK_NAME_BYTES];
|
|
uint32_t fillHash;
|
|
uint16_t refused;
|
|
uint16_t loaded;
|
|
uint16_t streamed;
|
|
|
|
jlSurfaceClear(gScreen, FILL_COLOR);
|
|
fillHash = jlSurfaceHash(gScreen);
|
|
refused = 0u;
|
|
*banks = 0u;
|
|
while (refuseBankFound(name, (uint16_t)(*banks + 1u))) {
|
|
(*banks)++;
|
|
jlSurfaceClear(gScreen, FILL_COLOR);
|
|
loaded = jlTileBankLoad(name, gTiles, gExpectCount, gValid, NULL);
|
|
streamed = jlTileBankLoadToSurface(name, gScreen, NULL);
|
|
if (loaded == 0u && !gValid[0] && streamed == 0u && jlSurfaceHash(gScreen) == fillHash) {
|
|
refused++;
|
|
}
|
|
}
|
|
return refused;
|
|
}
|
|
|
|
|
|
// The checks in the header comment, reported as one log line. The paste runs
|
|
// last so its tiles are what the verdict screen shows.
|
|
static bool runChecks(void) {
|
|
uint16_t banks;
|
|
uint16_t refused;
|
|
uint16_t streamed;
|
|
uint16_t pasted;
|
|
uint32_t streamHash;
|
|
uint32_t pasteHash;
|
|
bool paletteOk;
|
|
bool streamOk;
|
|
bool pasteOk;
|
|
bool pass;
|
|
|
|
refused = refuseCheck(&banks);
|
|
|
|
jlSurfaceClear(gScreen, FILL_COLOR);
|
|
streamed = jlTileBankLoadToSurface(OWN_BANK, gScreen, gBankPalette);
|
|
streamOk = streamed == gExpectCount && stageMatches(streamed);
|
|
streamHash = jlSurfaceHash(gScreen);
|
|
paletteOk = memcmp(gBankPalette, gExpectPalette, sizeof(gBankPalette)) == 0;
|
|
|
|
pasted = jlTileBankLoad(OWN_BANK, gTiles, gExpectCount, gValid, NULL);
|
|
pasteTiles(pasted);
|
|
pasteOk = pasted == gExpectCount && stageMatches(pasted);
|
|
pasteHash = jlSurfaceHash(gScreen);
|
|
|
|
pass = streamOk && pasteOk && paletteOk && streamHash == pasteHash && banks > 0u && refused == banks;
|
|
jlLogF("TILEBANK: %s tiles=%u streamed=%u pasted=%u palette=%s streamHash=%08lX pasteHash=%08lX refused=%u/%u", pass ? "PASS" : "FAIL", (unsigned)gExpectCount, (unsigned)streamed, (unsigned)pasted, paletteOk ? "ok" : "wrong", (unsigned long)streamHash, (unsigned long)pasteHash, (unsigned)refused, (unsigned)banks);
|
|
return pass;
|
|
}
|
|
|
|
|
|
// Show the bank's tiles in its own colours, and the lamp.
|
|
static void showVerdict(bool pass) {
|
|
uint16_t colors[PALETTE_COLORS];
|
|
uint16_t i;
|
|
|
|
for (i = 0u; i < PALETTE_COLORS; i++) {
|
|
colors[i] = LAMP_DARK_RGB;
|
|
}
|
|
colors[LAMP_PASS_COLOR] = LAMP_PASS_RGB;
|
|
colors[LAMP_FAIL_COLOR] = LAMP_FAIL_RGB;
|
|
jlPaletteSet(gScreen, 0u, gBankPalette);
|
|
jlPaletteSet(gScreen, LAMP_PALETTE, colors);
|
|
jlScbSetRange(gScreen, LAMP_Y, LAMP_Y + LAMP_H - 1, LAMP_PALETTE);
|
|
jlFillRect(gScreen, LAMP_X, LAMP_Y, LAMP_W, LAMP_H, pass ? LAMP_PASS_COLOR : LAMP_FAIL_COLOR);
|
|
jlStagePresent();
|
|
}
|
|
|
|
|
|
// Every pixel of the stage: blocks below tilesDrawn must show the PNG's tile
|
|
// of that number, every other block FILL_COLOR. tilesDrawn never exceeds
|
|
// gExpectCount (the callers check the count first).
|
|
static bool stageMatches(uint16_t tilesDrawn) {
|
|
int16_t x;
|
|
int16_t y;
|
|
uint16_t block;
|
|
uint8_t want;
|
|
|
|
for (y = 0; y < SURFACE_HEIGHT; y++) {
|
|
for (x = 0; x < SURFACE_WIDTH; x++) {
|
|
block = (uint16_t)((y / TILE_PIXELS_PER_SIDE) * TILE_BLOCKS_PER_ROW + x / TILE_PIXELS_PER_SIDE);
|
|
want = FILL_COLOR;
|
|
if (block < tilesDrawn) {
|
|
want = gExpectPixels[(uint32_t)block * TILE_PIXELS + (y % TILE_PIXELS_PER_SIDE) * TILE_PIXELS_PER_SIDE + x % TILE_PIXELS_PER_SIDE];
|
|
}
|
|
if (jlSamplePixel(gScreen, x, y) != want) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
int main(void) {
|
|
jlConfigT config;
|
|
bool pass;
|
|
|
|
config.codegenBytes = CODEGEN_DEFAULT;
|
|
if (!jlInit(&config)) {
|
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
|
return EXIT_FAILURE;
|
|
}
|
|
gScreen = jlStageGet();
|
|
|
|
pass = loadExpected();
|
|
if (pass) {
|
|
pass = runChecks();
|
|
} else {
|
|
jlLog("TILEBANK: FAIL DATA/" EXPECT_NAME " is missing or short, or memory ran out");
|
|
}
|
|
// Flushed BEFORE the verdict shows: a gate that sees the lamp can read
|
|
// the log at once (on the IIgs the flush is what writes the file).
|
|
jlLogFlush();
|
|
showVerdict(pass);
|
|
|
|
jlWaitForAnyKey();
|
|
jlFree(gValid);
|
|
jlFree(gTiles);
|
|
jlFree(gExpectPixels);
|
|
jlShutdown();
|
|
return pass ? EXIT_SUCCESS : EXIT_FAILURE;
|
|
}
|