222 lines
7.8 KiB
C
222 lines
7.8 KiB
C
// 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 <stdio.h>
|
|
|
|
#include <joey/joey.h>
|
|
|
|
#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 must store rounded-up byte boundaries: x rounded down to
|
|
// even, width rounded up to even. Worst case for BALL_W=16 (already
|
|
// even) is 8 bytes per row + alignment slack of 1 byte; size for the
|
|
// pessimistic case so the buffer never overflows.
|
|
#define BALL_BACKUP_BYTES (((BALL_W + 2) >> 1) * 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;
|
|
int16_t oldX;
|
|
int16_t oldY;
|
|
uint16_t oldW;
|
|
uint16_t oldH;
|
|
int16_t unionX;
|
|
int16_t unionY;
|
|
int16_t unionRight;
|
|
int16_t unionBottom;
|
|
bool haveBackup;
|
|
|
|
config.hostMode = HOST_MODE_TAKEOVER;
|
|
config.codegenBytes = 8 * 1024;
|
|
config.maxSurfaces = 4;
|
|
config.audioBytes = 64 * 1024;
|
|
config.assetBytes = 128 * 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;
|
|
|
|
spriteSaveUnder(screen, ball, x, y, &backup);
|
|
spriteDraw(screen, ball, x, y);
|
|
stagePresentRect(backup.x, backup.y, backup.width, backup.height);
|
|
haveBackup = true;
|
|
|
|
for (;;) {
|
|
joeyInputPoll();
|
|
if (joeyKeyPressed(KEY_ESCAPE)) {
|
|
break;
|
|
}
|
|
|
|
// Stash the prior ball's region before restoring the bytes
|
|
// under it. Do all off-screen work (restore + move + draw)
|
|
// first, then waitVBL + ONE stagePresentRect covering both
|
|
// old and new regions. Putting waitVBL immediately before the
|
|
// present lets the present land 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).
|
|
oldX = backup.x;
|
|
oldY = backup.y;
|
|
oldW = backup.width;
|
|
oldH = backup.height;
|
|
|
|
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; }
|
|
|
|
spriteSaveUnder(screen, ball, x, y, &backup);
|
|
spriteDraw(screen, ball, x, y);
|
|
|
|
// Bounding box of (old rect) U (new rect). For typical
|
|
// small-step motion the rects overlap heavily so the union
|
|
// is barely larger than one ball.
|
|
unionX = (oldX < backup.x) ? oldX : backup.x;
|
|
unionY = (oldY < backup.y) ? oldY : backup.y;
|
|
unionRight = (int16_t)((oldX + oldW > backup.x + backup.width)
|
|
? (oldX + oldW)
|
|
: (backup.x + backup.width));
|
|
unionBottom = (int16_t)((oldY + oldH > backup.y + backup.height)
|
|
? (oldY + oldH)
|
|
: (backup.y + backup.height));
|
|
|
|
// VBL wait removed -- the demo runs at the native compute speed
|
|
// of save+restore+draw+presentRect so we can SEE the sprite
|
|
// pipeline's actual throughput. Expect tearing on the ball
|
|
// since the present can land mid-scan; that's the cost of
|
|
// showing real frame rate. Add joeyWaitVBL() back here for
|
|
// tear-free 60 Hz motion.
|
|
stagePresentRect(unionX, unionY,
|
|
(uint16_t)(unionRight - unionX),
|
|
(uint16_t)(unionBottom - unionY));
|
|
haveBackup = true;
|
|
}
|
|
|
|
spriteDestroy(ball);
|
|
joeyShutdown();
|
|
return 0;
|
|
}
|