217 lines
7.2 KiB
C
217 lines
7.2 KiB
C
// 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_<port> 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 <in.spr> <out.spc>
|
|
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#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_<port>"
|
|
#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 <in.spr> <out.spc>\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;
|
|
}
|