// Sprite demo: bounces a 16x16 ball using the SpriteT API. The ball // art is authored as a 16x16 4bpp packed image in this file (one row // per source line) and converted at startup into the 2x2 tile layout // SpriteT expects. spriteDraw handles transparent-color-0; we use // spriteSaveUnder/RestoreUnder to undraw the previous frame's ball // without redrawing the whole screen. #include #include #define BALL_W 16 #define BALL_H 16 #define BALL_TILES_X (BALL_W / 8) #define BALL_TILES_Y (BALL_H / 8) #define BALL_TILE_BYTES (BALL_TILES_X * BALL_TILES_Y * TILE_BYTES) // SaveUnder rounds x down to the platform's storage alignment: 2 px // for chunky 4bpp (1 extra byte/row worst case), 8 px for planar // 4-plane (4 extra bytes/row worst case -- one per plane). The +4 // covers the planar case and is a no-op overhead on chunky. #define BALL_BACKUP_BYTES (((BALL_W >> 1) + 4) * BALL_H) #define BALL_PALETTE_IDX 0 #define COLOR_BG 0 // Authored layout: 16 pixels wide x 16 rows = 8 bytes/row x 16 rows. // 0 = transparent // 2 = ball body (yellow) // 3 = highlight (white) // High nibble of each byte is the LEFT pixel. static const uint8_t gBallAuthored[BALL_H * (BALL_W / 2)] = { 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, // row 0 0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x00, // row 1 0x02, 0x22, 0x32, 0x22, 0x22, 0x22, 0x22, 0x20, // row 2 0x02, 0x23, 0x32, 0x22, 0x22, 0x22, 0x22, 0x20, // row 3 0x22, 0x33, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, // row 4 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, // row 5 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, // row 6 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, // row 7 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, // row 8 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, // row 9 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, // row 10 0x02, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x20, // row 11 0x02, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x20, // row 12 0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x00, // row 13 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, // row 14 0x00, 0x00, 0x00, 0x22, 0x22, 0x00, 0x00, 0x00 // row 15 }; static uint8_t gBallTiles[BALL_TILE_BYTES]; static uint8_t gBallBackup[BALL_BACKUP_BYTES]; // Repack from authored "wide row" layout to tile-major SpriteT // layout: tile (0,0), tile (1,0), tile (0,1), tile (1,1), each tile // internally stored row-by-row at 4 bytes per row. static void repackBallTiles(void) { uint16_t tx; uint16_t ty; uint16_t row; uint16_t b; uint8_t *dst; for (ty = 0; ty < BALL_TILES_Y; ty++) { for (tx = 0; tx < BALL_TILES_X; tx++) { dst = &gBallTiles[(ty * BALL_TILES_X + tx) * TILE_BYTES]; for (row = 0; row < 8; row++) { for (b = 0; b < 4; b++) { dst[row * 4 + b] = gBallAuthored[((ty * 8) + row) * (BALL_W / 2) + (tx * 4) + b]; } } } } } static void buildPalette(SurfaceT *screen) { uint16_t colors[16]; uint16_t i; for (i = 0; i < 16; i++) { colors[i] = 0x0000; } colors[2] = 0x0FF0; // yellow body colors[3] = 0x0FFF; // white highlight paletteSet(screen, BALL_PALETTE_IDX, colors); } int main(void) { JoeyConfigT config; SurfaceT *screen; SpriteT *ball; SpriteBackupT backup; int16_t x; int16_t y; int16_t vx; int16_t vy; bool haveBackup; config.hostMode = HOST_MODE_TAKEOVER; /* Amiga planar emits 8 pre-shifted DRAW variants per sprite (one * per x % 8 alignment) so the codegen arena needs roughly 8x what * the chunky two-shift case asks for. 32 KB fits a 16x16 ball * with all variants. */ config.codegenBytes = 32UL * 1024; config.maxSurfaces = 4; config.audioBytes = 64UL * 1024; config.assetBytes = 128UL * 1024; if (!joeyInit(&config)) { fprintf(stderr, "joeyInit failed: %s\n", joeyLastError()); return 1; } screen = stageGet(); if (screen == NULL) { fprintf(stderr, "stageGet returned NULL\n"); joeyShutdown(); return 1; } repackBallTiles(); ball = spriteCreate(gBallTiles, BALL_TILES_X, BALL_TILES_Y, SPRITE_FLAGS_NONE); if (ball == NULL) { fprintf(stderr, "spriteCreate failed\n"); joeyShutdown(); return 1; } // Compile draw routines into the codegen arena. Returns false on // platforms that don't have a real emitter or if the arena is // full -- either way the demo still runs via the interpreter // path in spriteDraw. (void)spriteCompile(ball); buildPalette(screen); scbSetRange(screen, 0, SURFACE_HEIGHT - 1, BALL_PALETTE_IDX); surfaceClear(screen, COLOR_BG); stagePresent(); backup.bytes = gBallBackup; x = 40; y = 30; vx = 2; vy = 1; haveBackup = false; spriteSaveAndDraw(screen, ball, x, y, &backup); stagePresent(); haveBackup = true; for (;;) { joeyInputPoll(); if (joeyKeyPressed(KEY_ESCAPE)) { break; } // Do all off-screen work first (restore + move + draw), then // ONE stagePresent flushes the union of dirty bands set by // restoreUnder + draw. Add a joeyWaitVBL() before the present // to land it inside the VBL window so the CRT never sees a // half-updated framebuffer (matters most on single-buffered // chunky targets like IIgs SHR; on planar c2p platforms it // also avoids c2p racing the raster). VBL wait is omitted // here so the demo runs at the sprite pipeline's native // throughput -- expect tearing on the ball. if (haveBackup) { spriteRestoreUnder(screen, &backup); } x = (int16_t)(x + vx); y = (int16_t)(y + vy); if (x <= 0) { x = 0; vx = (int16_t)-vx; } if (x >= SURFACE_WIDTH - BALL_W) { x = SURFACE_WIDTH - BALL_W; vx = (int16_t)-vx; } if (y <= 0) { y = 0; vy = (int16_t)-vy; } if (y >= SURFACE_HEIGHT - BALL_H) { y = SURFACE_HEIGHT - BALL_H; vy = (int16_t)-vy; } spriteSaveAndDraw(screen, ball, x, y, &backup); stagePresent(); haveBackup = true; } spriteDestroy(ball); joeyShutdown(); return 0; }