// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License. // See LICENSE.md for license information. // SPDX-License-Identifier: MIT // // indexedHost.c - Host-cc harness for jlSurfacePasteIndexed and // jlSurfaceSnapIndexed (src/core/surface.c clipping, the port's rectangle // writer and reader behind it). Each case is checked against a reference // built one pixel at a time with jlDrawPixel and jlSamplePixel: // paste the surface after a paste equals a copy of the same background // with every on-surface pixel of the rectangle plotted by hand // (high nibbles in the source ignored, doubleWide pairs, a // doubleWide byte cut by the left or right edge showing its one // on-surface pixel) // snap every byte of the rectangle equals jlSamplePixel of its column // (0 off the surface), and the bytes between w and the stride are // left alone // Edge cases (odd columns, both edges, rows past both ends, w 65535, the // int16_t limits) run first, then seeded random rectangles. // // The planar ports' paste and snap (src/m68k/planarIndexed.c, plain C) run // here too, on host buffers in both of their layouts -- four separate // planes (Amiga, X68000) and the ST's interleaved plane words -- given // on-surface rectangles as the core hands them over, and checked against // the same kind of pixel-by-pixel reference. // // Built by scripts/check-indexed.sh with -DJOEYLIB_PLATFORM_BLANK. #include #include #include #include #include "joey/joey.h" #include "surfaceInternal.h" #define SRC_BYTES 81920u #define RANDOM_CASES 600u #define RANDOM_SEED 0x1D2C3B4Au #define RANDOM_X_SPAN 700 #define RANDOM_X_LOW (-360) #define RANDOM_Y_SPAN 440 #define RANDOM_Y_LOW (-220) #define RANDOM_W_MAX 200u #define RANDOM_H_MAX 220u #define RANDOM_PAD_MAX 5u #define CANARY 0xA5u #define BACK_X_MUL 7 #define BACK_Y_MUL 3 #define PLANAR_PLANES 4u #define PLANAR_ROW_BYTES (JL_SURFACE_WIDTH / 8) #define PLANAR_CASES 500u #define PLANAR_H_MAX 24u #define ST_ROW_BYTES 160u #define ST_GROUP_BYTES 8u typedef struct LayoutT { const char *name; uint8_t *planes[PLANAR_PLANES]; uint16_t rowBytes; uint16_t pairStep; } LayoutT; typedef struct CaseT { int16_t x; int16_t y; uint16_t w; uint16_t h; uint16_t pad; bool doubleWide; } CaseT; static const CaseT kEdgeCases[] = { { 0, 0, 160u, 200u, 0u, true }, { 0, 0, 320u, 200u, 0u, false }, { 1, 3, 17u, 5u, 2u, false }, { 1, 3, 16u, 5u, 0u, false }, { 1, 3, 17u, 5u, 1u, true }, { -1, 10, 20u, 4u, 0u, true }, { -1, 10, 20u, 4u, 3u, false }, { -2, 10, 20u, 4u, 0u, true }, { -3, 10, 2u, 4u, 0u, true }, { -3, 10, 1u, 4u, 0u, true }, { 319, 50, 8u, 3u, 0u, true }, { 318, 50, 8u, 3u, 0u, true }, { 317, 50, 8u, 3u, 0u, true }, { 319, 50, 8u, 3u, 0u, false }, { 320, 50, 8u, 3u, 0u, true }, { -9, -5, 170u, 210u, 4u, true }, { -9, -5, 340u, 210u, 4u, false }, { 40, 198, 10u, 10u, 0u, false }, { 40, -10, 10u, 10u, 0u, true }, { 40, -11, 10u, 10u, 0u, true }, { 40, 200, 10u, 10u, 0u, true }, { 5, 5, 0u, 10u, 2u, true }, { 5, 5, 10u, 0u, 0u, true }, { -301, 7, 65535u, 1u, 0u, true }, { -301, 7, 65535u, 1u, 0u, false }, {-32768, -32768, 9u, 2u, 0u, true }, { 32767, 32767, 9u, 2u, 0u, false } }; #define EDGE_CASE_COUNT ((uint16_t)(sizeof(kEdgeCases) / sizeof(kEdgeCases[0]))) static uint8_t gSrc[SRC_BYTES]; static uint8_t gSnap[SRC_BYTES]; static uint32_t gRandom = RANDOM_SEED; static uint8_t gSeparate[PLANAR_PLANES][PLANAR_ROW_BYTES * JL_SURFACE_HEIGHT]; static uint8_t gInterleaved[ST_ROW_BYTES * JL_SURFACE_HEIGHT]; static uint8_t gPlanarBack[PLANAR_PLANES][ST_ROW_BYTES * JL_SURFACE_HEIGHT]; static uint8_t gRef[JL_SURFACE_HEIGHT][JL_SURFACE_WIDTH]; static bool check(const char *name, bool ok, const char *detail); static bool comparePaste(const jlSurfaceT *got, const jlSurfaceT *want, const CaseT *c, char *detail, size_t size); static bool compareSnap(const jlSurfaceT *back, const CaseT *c, char *detail, size_t size); static void fillBackground(jlSurfaceT *s); static uint32_t nextRandom(void); static void pasteReference(jlSurfaceT *s, const CaseT *c, uint16_t stride); static uint8_t planarGet(const LayoutT *l, uint16_t x, uint16_t y); static uint8_t *planarByte(const LayoutT *l, uint16_t p, uint16_t x, uint16_t y); static bool planarCase(LayoutT *l, const CaseT *c, char *detail, size_t size); static void planarSet(const LayoutT *l, uint16_t x, uint16_t y, uint8_t color); static void planarStore(const LayoutT *l, bool save); static bool runCase(jlSurfaceT *stage, jlSurfaceT *ref, const jlSurfaceT *back, const CaseT *c, char *detail, size_t size); static bool runCases(jlSurfaceT *stage, jlSurfaceT *ref, const jlSurfaceT *back, bool random); static bool runPlanar(LayoutT *l); static uint8_t sampleWide(const jlSurfaceT *s, int32_t x, int32_t y); static bool check(const char *name, bool ok, const char *detail) { printf(" %s: %s%s%s\n", name, ok ? "PASS" : "FAIL", detail[0] != '\0' ? " -- " : "", detail); return ok; } static bool comparePaste(const jlSurfaceT *got, const jlSurfaceT *want, const CaseT *c, char *detail, size_t size) { int16_t x; int16_t y; uint8_t a; uint8_t b; if (jlSurfaceHash(got) == jlSurfaceHash(want)) { return true; } for (y = 0; y < JL_SURFACE_HEIGHT; y++) { for (x = 0; x < JL_SURFACE_WIDTH; x++) { a = jlSamplePixel(got, x, y); b = jlSamplePixel(want, x, y); if (a != b) { snprintf(detail, size, "paste at (%d,%d) w %u h %u%s: pixel (%d,%d) is %u, want %u", c->x, c->y, c->w, c->h, c->doubleWide ? " doubleWide" : "", x, y, a, b); return false; } } } snprintf(detail, size, "paste at (%d,%d) w %u h %u: hashes differ, pixels equal", c->x, c->y, c->w, c->h); return false; } static bool compareSnap(const jlSurfaceT *back, const CaseT *c, char *detail, size_t size) { const uint8_t *row; uint32_t stride; uint32_t r; uint32_t i; uint8_t want; stride = (uint32_t)c->w + c->pad; for (r = 0; r < c->h; r++) { row = gSnap + r * stride; for (i = 0; i < c->w; i++) { want = sampleWide(back, (int32_t)c->x + (int32_t)(i << (c->doubleWide ? 1u : 0u)), (int32_t)c->y + (int32_t)r); if (row[i] != want) { snprintf(detail, size, "snap at (%d,%d) w %u h %u%s: byte %lu of row %lu is %u, want %u", c->x, c->y, c->w, c->h, c->doubleWide ? " doubleWide" : "", (unsigned long)i, (unsigned long)r, row[i], want); return false; } } for (; i < stride; i++) { if (row[i] != CANARY) { snprintf(detail, size, "snap at (%d,%d) w %u h %u: wrote past w at byte %lu of row %lu", c->x, c->y, c->w, c->h, (unsigned long)i, (unsigned long)r); return false; } } } return true; } static void fillBackground(jlSurfaceT *s) { int16_t x; int16_t y; for (y = 0; y < JL_SURFACE_HEIGHT; y++) { for (x = 0; x < JL_SURFACE_WIDTH; x++) { jlDrawPixel(s, x, y, (uint8_t)((x * BACK_X_MUL + y * BACK_Y_MUL) & 0x0F)); } } } static uint32_t nextRandom(void) { gRandom ^= gRandom << 13; gRandom ^= gRandom >> 17; gRandom ^= gRandom << 5; return gRandom; } static void pasteReference(jlSurfaceT *s, const CaseT *c, uint16_t stride) { uint32_t r; uint32_t i; uint32_t k; uint32_t span; int32_t px; int32_t py; uint8_t color; span = c->doubleWide ? 2u : 1u; for (r = 0; r < c->h; r++) { py = (int32_t)c->y + (int32_t)r; if (py < 0 || py >= JL_SURFACE_HEIGHT) { continue; } for (i = 0; i < c->w; i++) { color = (uint8_t)(gSrc[r * stride + i] & 0x0Fu); for (k = 0; k < span; k++) { px = (int32_t)c->x + (int32_t)(i * span + k); if (px >= 0 && px < JL_SURFACE_WIDTH) { jlDrawPixel(s, (int16_t)px, (int16_t)py, color); } } } } } static uint8_t planarGet(const LayoutT *l, uint16_t x, uint16_t y) { uint16_t p; uint8_t bit; uint8_t color; bit = (uint8_t)(0x80u >> (x & 7u)); color = 0u; for (p = 0; p < PLANAR_PLANES; p++) { if ((*planarByte(l, p, x, y) & bit) != 0u) { color = (uint8_t)(color | (1u << p)); } } return color; } static uint8_t *planarByte(const LayoutT *l, uint16_t p, uint16_t x, uint16_t y) { uint16_t c; c = (uint16_t)(x >> 3); return l->planes[p] + (uint32_t)y * l->rowBytes + (uint32_t)(c >> 1) * l->pairStep + (c & 1u); } // One on-surface rectangle: pasted on the planes and, a pixel at a time, on // gRef, then compared everywhere; snapped back from the planes and compared // with gRef's pixels at the rectangle's columns. static bool planarCase(LayoutT *l, const CaseT *c, char *detail, size_t size) { uint32_t bytes; uint32_t i; uint16_t stride; uint16_t r; uint16_t x; uint16_t y; uint16_t span; uint16_t k; uint8_t got; stride = (uint16_t)(c->w + c->pad); bytes = (uint32_t)stride * c->h; span = c->doubleWide ? 2u : 1u; for (i = 0; i < bytes; i++) { gSrc[i] = (uint8_t)nextRandom(); } planarStore(l, false); for (y = 0; y < JL_SURFACE_HEIGHT; y++) { for (x = 0; x < JL_SURFACE_WIDTH; x++) { gRef[y][x] = (uint8_t)((x * BACK_X_MUL + y * BACK_Y_MUL) & 0x0F); } } jlPlanarPasteIndexed(l->planes, l->rowBytes, l->pairStep, (uint16_t)c->x, (uint16_t)c->y, c->w, c->h, gSrc, stride, c->doubleWide); for (r = 0; r < c->h; r++) { for (i = 0; i < c->w; i++) { for (k = 0; k < span; k++) { gRef[c->y + r][c->x + i * span + k] = (uint8_t)(gSrc[r * stride + i] & 0x0Fu); } } } for (y = 0; y < JL_SURFACE_HEIGHT; y++) { for (x = 0; x < JL_SURFACE_WIDTH; x++) { got = planarGet(l, x, y); if (got != gRef[y][x]) { snprintf(detail, size, "%s paste at (%d,%d) w %u h %u%s: pixel (%u,%u) is %u, want %u", l->name, c->x, c->y, c->w, c->h, c->doubleWide ? " doubleWide" : "", x, y, got, gRef[y][x]); return false; } } } memset(gSnap, CANARY, bytes); jlPlanarSnapIndexed((const uint8_t *const *)l->planes, l->rowBytes, l->pairStep, (uint16_t)c->x, (uint16_t)c->y, c->w, c->h, gSnap, stride, c->doubleWide); for (r = 0; r < c->h; r++) { for (i = 0; i < stride; i++) { got = gSnap[r * stride + i]; if (i < c->w ? got != gRef[c->y + r][c->x + i * span] : got != CANARY) { snprintf(detail, size, "%s snap at (%d,%d) w %u h %u%s: byte %lu of row %u is %u", l->name, c->x, c->y, c->w, c->h, c->doubleWide ? " doubleWide" : "", (unsigned long)i, r, got); return false; } } } return true; } static void planarSet(const LayoutT *l, uint16_t x, uint16_t y, uint8_t color) { uint8_t *b; uint16_t p; uint8_t bit; bit = (uint8_t)(0x80u >> (x & 7u)); for (p = 0; p < PLANAR_PLANES; p++) { b = planarByte(l, p, x, y); if (((color >> p) & 1u) != 0u) { *b = (uint8_t)(*b | bit); } else { *b = (uint8_t)(*b & ~bit); } } } // Save the layout's background to gPlanarBack, or put it back. static void planarStore(const LayoutT *l, bool save) { uint8_t *buffer; size_t bytes; uint16_t p; if (l->pairStep == ST_GROUP_BYTES) { buffer = gInterleaved; bytes = sizeof(gInterleaved); if (save) { memcpy(gPlanarBack[0], buffer, bytes); } else { memcpy(buffer, gPlanarBack[0], bytes); } return; } for (p = 0; p < PLANAR_PLANES; p++) { bytes = sizeof(gSeparate[p]); if (save) { memcpy(gPlanarBack[p], gSeparate[p], bytes); } else { memcpy(gSeparate[p], gPlanarBack[p], bytes); } } } static bool runCase(jlSurfaceT *stage, jlSurfaceT *ref, const jlSurfaceT *back, const CaseT *c, char *detail, size_t size) { uint32_t bytes; uint32_t i; uint16_t stride; stride = (uint16_t)(c->w + c->pad); bytes = (uint32_t)stride * c->h; for (i = 0; i < bytes; i++) { gSrc[i] = (uint8_t)nextRandom(); } jlSurfaceCopy(stage, back); jlSurfaceCopy(ref, back); jlSurfacePasteIndexed(stage, c->x, c->y, c->w, c->h, gSrc, stride, c->doubleWide); pasteReference(ref, c, stride); if (!comparePaste(stage, ref, c, detail, size)) { return false; } memset(gSnap, CANARY, bytes); jlSurfaceSnapIndexed(back, c->x, c->y, c->w, c->h, gSnap, stride, c->doubleWide); return compareSnap(back, c, detail, size); } static bool runCases(jlSurfaceT *stage, jlSurfaceT *ref, const jlSurfaceT *back, bool random) { CaseT c; char detail[200]; uint16_t count; uint16_t n; detail[0] = '\0'; count = random ? (uint16_t)RANDOM_CASES : EDGE_CASE_COUNT; for (n = 0; n < count; n++) { if (random) { c.x = (int16_t)(RANDOM_X_LOW + (int32_t)(nextRandom() % RANDOM_X_SPAN)); c.y = (int16_t)(RANDOM_Y_LOW + (int32_t)(nextRandom() % RANDOM_Y_SPAN)); c.w = (uint16_t)(nextRandom() % (RANDOM_W_MAX + 1u)); c.h = (uint16_t)(nextRandom() % (RANDOM_H_MAX + 1u)); c.pad = (uint16_t)(nextRandom() % (RANDOM_PAD_MAX + 1u)); c.doubleWide = (nextRandom() & 1u) != 0u; } else { c = kEdgeCases[n]; } if (!runCase(stage, ref, back, &c, detail, sizeof(detail))) { break; } } if (n == count) { snprintf(detail, sizeof(detail), "%u rectangles", count); } return check(random ? "random rectangles" : "edge rectangles", n == count, detail); } static bool runPlanar(LayoutT *l) { CaseT c; char detail[200]; char name[48]; uint16_t x; uint16_t y; uint16_t n; uint16_t maxW; detail[0] = '\0'; for (y = 0; y < JL_SURFACE_HEIGHT; y++) { for (x = 0; x < JL_SURFACE_WIDTH; x++) { planarSet(l, x, y, (uint8_t)((x * BACK_X_MUL + y * BACK_Y_MUL) & 0x0F)); } } planarStore(l, true); for (n = 0; n < PLANAR_CASES; n++) { c.doubleWide = (nextRandom() & 1u) != 0u; c.x = (int16_t)(nextRandom() % JL_SURFACE_WIDTH); if (c.doubleWide && c.x == JL_SURFACE_WIDTH - 1) { c.x--; } maxW = (uint16_t)((JL_SURFACE_WIDTH - c.x) >> (c.doubleWide ? 1u : 0u)); // Mostly narrow (the edge columns), now and then the whole span left. c.w = (uint16_t)(1u + nextRandom() % ((n & 3u) == 0u ? maxW : (maxW < 20u ? maxW : 20u))); c.y = (int16_t)(nextRandom() % JL_SURFACE_HEIGHT); c.h = (uint16_t)(1u + nextRandom() % PLANAR_H_MAX); if (c.y + c.h > JL_SURFACE_HEIGHT) { c.h = (uint16_t)(JL_SURFACE_HEIGHT - c.y); } c.pad = (uint16_t)(nextRandom() % (RANDOM_PAD_MAX + 1u)); if (!planarCase(l, &c, detail, sizeof(detail))) { break; } } if (n == PLANAR_CASES) { snprintf(detail, sizeof(detail), "%u rectangles", n); } snprintf(name, sizeof(name), "planar %s", l->name); return check(name, n == PLANAR_CASES, detail); } static uint8_t sampleWide(const jlSurfaceT *s, int32_t x, int32_t y) { if (x < 0 || x >= JL_SURFACE_WIDTH || y < 0 || y >= JL_SURFACE_HEIGHT) { return 0u; } return jlSamplePixel(s, (int16_t)x, (int16_t)y); } int main(void) { LayoutT separate; LayoutT interleaved; jlConfigT config; jlSurfaceT *stage; jlSurfaceT *ref; jlSurfaceT *back; uint16_t p; bool ok; config.codegenBytes = 0u; config.noAudio = true; if (!jlInit(&config)) { printf("indexedHost: FAIL -- jlInit: %s\n", jlLastError()); return 1; } stage = jlStageGet(); ref = jlSurfaceCreate(); back = jlSurfaceCreate(); if (ref == NULL || back == NULL) { printf("indexedHost: FAIL -- jlSurfaceCreate: %s\n", jlLastError()); jlShutdown(); return 1; } fillBackground(back); printf("indexedHost: indexed paste and snap, chunky and planar, against pixel-by-pixel references\n"); ok = true; ok &= runCases(stage, ref, back, false); ok &= runCases(stage, ref, back, true); separate.name = "separate planes"; separate.rowBytes = PLANAR_ROW_BYTES; separate.pairStep = PLANAR_SEPARATE_PAIR_STEP; interleaved.name = "interleaved planes"; interleaved.rowBytes = ST_ROW_BYTES; interleaved.pairStep = ST_GROUP_BYTES; for (p = 0; p < PLANAR_PLANES; p++) { separate.planes[p] = gSeparate[p]; interleaved.planes[p] = gInterleaved + p * sizeof(uint16_t); } ok &= runPlanar(&separate); ok &= runPlanar(&interleaved); jlSurfaceDestroy(back); jlSurfaceDestroy(ref); jlShutdown(); printf("indexedHost: %s\n", ok ? "PASS" : "FAIL"); return ok ? 0 : 1; }