// spritebake -- offline pre-compiler for JoeyLib sprite blit routines. // // Reads a cross-platform .spr cel bank (as tools/assetbake/assetbake.py // produces), runs the SAME per-CPU emitters + staging the runtime JIT uses // (src/codegen/spriteStage.c, linked with one src/*/spriteEmit*.c chosen at // compile time), and writes a per-platform .spc holding the pre-emitted // native routine images + slot-relative routineOffsets. The runtime loader // (jlSpriteBankLoadPrecompiled) then skips jlSpriteCompile entirely. // // Built once per target by make/tools.mk with -DJOEYLIB_PLATFORM_ so // JOEY_SPRITE_SHIFT_COUNT and the emitted machine code match that target, // while running on the build host -- the emitters use only fixed/gated // compile-time constants, so host output is byte-identical to the target JIT. // // spritebake #include #include #include #include #include "joey/tile.h" #include "spriteInternal.h" #include "spritePrecompiled.h" #include "spriteStage.h" #define SPR_MAGIC "JSP1" #define SPR_HEADER_BYTES 44 #define SPR_OFF_WIDTH 6 #define SPR_OFF_HEIGHT 7 #define SPR_OFF_CELLS 8 #define SPR_OFF_PALETTE 12 #define SPR_PALETTE_BYTES 32 #if defined(JOEYLIB_PLATFORM_AMIGA) #define SPC_TARGET 1 #elif defined(JOEYLIB_PLATFORM_ATARIST) #define SPC_TARGET 2 #elif defined(JOEYLIB_PLATFORM_DOS) #define SPC_TARGET 3 #elif defined(JOEYLIB_PLATFORM_IIGS) #define SPC_TARGET 4 #else #error "spritebake: build with -DJOEYLIB_PLATFORM_" #endif #if defined(JOEYLIB_PLATFORM_ATARIST) // The ST emitter bakes the address of this walker into each shifted-DRAW // thunk. On the host bake tool the value is don't-care (the .spc loader // re-stamps the real target address at load), so a never-called stub just // resolves the link -- the real walker is src/atarist/spriteWalk.s on target. void stSpriteDrawShiftWalker(void) {} #endif static void putLE16(FILE *f, uint16_t v) { fputc((int)(v & 0xFFu), f); fputc((int)((v >> 8) & 0xFFu), f); } static void putLE32(FILE *f, uint32_t v) { fputc((int)(v & 0xFFu), f); fputc((int)((v >> 8) & 0xFFu), f); fputc((int)((v >> 16) & 0xFFu), f); fputc((int)((v >> 24) & 0xFFu), f); } int main(int argc, char **argv) { FILE *in; FILE *out; uint8_t header[SPR_HEADER_BYTES]; uint8_t widthTiles; uint8_t heightTiles; uint16_t cellCount; uint32_t tileBytes; uint8_t *staging; uint16_t cel; if (argc != 3) { fprintf(stderr, "usage: spritebake \n"); return 2; } in = fopen(argv[1], "rb"); if (in == NULL) { fprintf(stderr, "spritebake: cannot open %s\n", argv[1]); return 1; } if (fread(header, 1, SPR_HEADER_BYTES, in) != SPR_HEADER_BYTES || memcmp(header, SPR_MAGIC, 4) != 0 || header[SPC_OFF_TARGET] != 0u) { fprintf(stderr, "spritebake: %s is not a JSP1 cross-target .spr\n", argv[1]); fclose(in); return 1; } widthTiles = header[SPR_OFF_WIDTH]; heightTiles = header[SPR_OFF_HEIGHT]; cellCount = (uint16_t)(header[SPR_OFF_CELLS] | (header[SPR_OFF_CELLS + 1] << 8)); if (widthTiles == 0u || heightTiles == 0u || cellCount == 0u) { fprintf(stderr, "spritebake: degenerate geometry in %s\n", argv[1]); fclose(in); return 1; } tileBytes = (uint32_t)widthTiles * heightTiles * TILE_BYTES; staging = (uint8_t *)malloc(SPRITE_STAGING_BYTES); if (staging == NULL) { fclose(in); return 1; } out = fopen(argv[2], "wb"); if (out == NULL) { fprintf(stderr, "spritebake: cannot write %s\n", argv[2]); free(staging); fclose(in); return 1; } // .spc header: same 44-byte prefix, but JSC1 + per-machine target byte, // plus the baked shift count at byte 10 for the loader to validate. fwrite(SPC_MAGIC, 1, 4, out); fputc(SPC_TARGET, out); fputc(header[SPC_OFF_HAS_PALETTE], out); fputc(widthTiles, out); fputc(heightTiles, out); putLE16(out, cellCount); fputc((int)JOEY_SPRITE_SHIFT_COUNT, out); fputc(0, out); // reserved fwrite(header + SPR_OFF_PALETTE, 1, SPR_PALETTE_BYTES, out); for (cel = 0; cel < cellCount; cel++) { uint8_t *tileData; jlSpriteT sp; uint32_t total; uint16_t relocOffsets[JOEY_SPRITE_SHIFT_COUNT]; uint16_t relocCount; uint8_t shift; uint8_t op; tileData = (uint8_t *)malloc(tileBytes); if (tileData == NULL) { fprintf(stderr, "spritebake: OOM cel %u\n", (unsigned)cel); goto fail; } if (fread(tileData, 1, tileBytes, in) != tileBytes) { fprintf(stderr, "spritebake: %s truncated at cel %u\n", argv[1], (unsigned)cel); free(tileData); goto fail; } // Run the identical staging the runtime JIT would: same emitters, // same tier order, same offsets -> byte-identical image. memset(&sp, 0, sizeof(sp)); sp.tileData = tileData; sp.widthTiles = widthTiles; sp.heightTiles = heightTiles; sp.widthPx = (uint16_t)(widthTiles * TILE_PIXELS_PER_SIDE); sp.heightPx = (uint16_t)(heightTiles * TILE_PIXELS_PER_SIDE); total = spriteStageAndPlan(staging, &sp, false); // ST shifted-phase DRAW thunks bake an absolute jmp to the shared // walker; record its patch site so the loader re-stamps the runtime // address. Every other target/op is fully position-independent. relocCount = 0u; #if defined(JOEYLIB_PLATFORM_ATARIST) for (shift = 0; shift < JOEY_SPRITE_SHIFT_COUNT; shift++) { uint16_t drawOff = sp.routineOffsets[shift][SPRITE_OP_DRAW]; if ((shift & 7u) != 0u && drawOff != SPRITE_NOT_COMPILED) { relocOffsets[relocCount] = (uint16_t)(drawOff + SPC_ST_WALKER_JMP_OFFSET); relocCount++; } } #endif // celMeta: imageSize, routineOffsets[shift][op], relocCount. putLE32(out, total); for (shift = 0; shift < JOEY_SPRITE_SHIFT_COUNT; shift++) { for (op = 0; op < SPRITE_OP_COUNT; op++) { putLE16(out, sp.routineOffsets[shift][op]); } } putLE16(out, relocCount); // Raw tile blob (kept for the interpreter/clip fallback + dims), then // the pre-emitted routine image, then the reloc site list. fwrite(tileData, 1, tileBytes, out); if (total != 0u) { fwrite(staging, 1, total, out); } for (uint16_t r = 0; r < relocCount; r++) { putLE16(out, relocOffsets[r]); } free(tileData); } if (fclose(out) != 0) { fprintf(stderr, "spritebake: write error on %s\n", argv[2]); free(staging); fclose(in); return 1; } free(staging); fclose(in); printf("spritebake: %s -> %s (%u cels, target %d, %d shifts)\n", argv[1], argv[2], (unsigned)cellCount, SPC_TARGET, (int)JOEY_SPRITE_SHIFT_COUNT); return 0; fail: fclose(out); free(staging); fclose(in); return 1; }