72ce66275e
Спрайтовая графика поверх accel block-copy (docs/sprite-api-design.md): - Ядро блиттинга: leaf'ы _bgi_blit_rows_raw (dst фикс, только src-страйд) / _bgi_copy_rows_raw (getimage) / _bgi_heal_rows_raw (src==dst). DI один на спрайт (санкция: малый спрайт под одним DI аудио не рвёт); src[0]-фикс снят (точная MAME подавляет CPU-байт триггера записи — на железе перепроверить; для heal был избыточен и снят безусловно). - Общие bracket-free ядра _gfx_blit_full/_gfx_heal_full (полная ширина: клип по экрану + split >256 для putimage) + лин _gfx_blit_sprite/ _gfx_heal_sprite (кадр ≤64, без split, 8-бит w/h) + noclip-варианты (клип-кода нет → полный codegen-win). Имя *_full (не *_clip) — «clip» двусмысленно (sprite-ядра тоже клипуют; различитель — ширина/split). - Движок retained-модели <sprite.h>: sprite_init/update/flip + inline move/frame/show/hide/touch; drawn[2] per-page внутри структуры; кадр — двухпроходно heal ВСЕ -> блит ВСЕ под одной W3-скобкой/банком на проход. - Флаг gfx_sprite_clip(on/off): приложение, само следящее за границами, отключает клип (~+19% на анимации; диспетч пока через if — funcptr далее). - putsprite/movesprite/gfx_blit/putimage(COPY)/getimage переведены на ядро. Тесты: examples/balls (движок, дабл-буфер, boundary-тест клипа), tests/sprites (RAM PASS, клип 4 края, атлас), tests/blitw (trig-leak), tests/spriteclip (hardware-probe: железо НЕ режет за краем -> клип нужен), tests/blitperf, tests/gfxbanks. size-baseline обновлён (53 программы). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
186 lines
7.2 KiB
C
186 lines
7.2 KiB
C
/*
|
||
* balls — демо спрайтового движка v2 (<sprite.h>): N шаров 16×16 летают
|
||
* над чёрно-белой шахматкой, отражаясь от краёв. Esc — выход.
|
||
*
|
||
* РАНЬШЕ (до движка) приложение вело drawn[2][N] координаты вручную и
|
||
* само соблюдало дисциплину «heal ВСЕ → блит ВСЕ» на скрытой странице.
|
||
* Теперь это работа движка: sprite_move меняет только состояние,
|
||
* sprite_flip(sp, N) за кадр делает draw-page=скрытая → heal все старые →
|
||
* блит все видимые (одна W3-скобка + один банк на проход) → vsync → флип.
|
||
* Учёт drawn[2] спрятан в sprite_t. Профиль/дизайн:
|
||
* docs/sprite-api-design.md §9.1.
|
||
*
|
||
* BOUNDARY-ТЕСТ клипа: диапазон движения выходит ЗА края экрана на
|
||
* половину спрайта — шары штатно наполовину уходят за все 4 края,
|
||
* включая ОТРИЦАТЕЛЬНЫЕ x/y слева/сверху. Клип _gfx_blit_clip (общий
|
||
* для putsprite и движка) обязан авто-обрезать: правый/нижний — урезать
|
||
* w/h, левый/верхний — сдвинуть начало источника (sx/sy) и урезать.
|
||
*
|
||
* Двойная буферизация: рисуем на скрытой странице, флип по vsync (иначе
|
||
* heal+блит на видимой ловятся лучом — мерцание). Палитру страницы 1
|
||
* зеркалим из 0 (initgraph грузит EGA только в палитру 0).
|
||
*
|
||
* Скорость привязана к КАДРАМ (50 Гц): один проход цикла = один шаг.
|
||
*/
|
||
|
||
#include <graphics.h>
|
||
#include <gfx.h>
|
||
#include <sprite.h>
|
||
#include <conio.h>
|
||
#include <time.h>
|
||
|
||
#define FPS_METER 1 /* 1 = счётчик кадров/с слева-сверху (диагностика) */
|
||
|
||
#define NBALLS 16 /* количество шаров */
|
||
#define BW 16 /* сторона спрайта */
|
||
#define CELL 16 /* клетка шахматки */
|
||
|
||
/* Диапазон отражения — на пол-спрайта ЗА каждым краем (boundary-тест):
|
||
* x ∈ [-8 .. 312], y ∈ [-8 .. 248]. */
|
||
#define MINY (0) /* -8: половина за левым/верхним краем */
|
||
#define MINX (0) /* -8: половина за левым/верхним краем */
|
||
#define MAXX (320 - BW) /* 312: половина за правым краем */
|
||
#define MAXY (256 - BW) /* 248: половина за нижним краем */
|
||
|
||
/* Цвета шаров (не совпадают с фоном BLACK/WHITE) */
|
||
static const uint8_t ball_colors[] = {
|
||
YELLOW, LIGHTRED, LIGHTGREEN, LIGHTCYAN,
|
||
LIGHTMAGENTA, LIGHTBLUE, RED, GREEN
|
||
};
|
||
#define NCOLORS ((uint8_t)sizeof(ball_colors))
|
||
|
||
static sprite_t sp[NBALLS]; /* спрайты — движок ведёт drawn[2] */
|
||
static int8_t vx[NBALLS], vy[NBALLS]; /* направление ±1 (физика — наша) */
|
||
static uint8_t spr[NCOLORS][4 + BW * BW]; /* по спрайту на цвет */
|
||
static uint8_t egapal[16 * 4];
|
||
|
||
static void build_sprites(void)
|
||
{
|
||
uint8_t c;
|
||
int x, y;
|
||
|
||
for (c = 0; c < NCOLORS; c++) {
|
||
uint8_t *p = spr[c];
|
||
|
||
*p++ = BW; *p++ = 0; *p++ = BW; *p++ = 0;
|
||
for (y = 0; y < BW; y++)
|
||
for (x = 0; x < BW; x++) {
|
||
int dx = 2 * x - (BW - 1), dy = 2 * y - (BW - 1); /* ×2: центр между пикселями */
|
||
*p++ = (dx * dx + dy * dy <= 15 * 15)
|
||
? ball_colors[c] : GFX_TRANSPARENT;
|
||
}
|
||
}
|
||
}
|
||
|
||
static void draw_board(void)
|
||
{
|
||
int cx, cy;
|
||
|
||
for (cy = 0; cy < 256; cy += CELL)
|
||
for (cx = 0; cx < 320; cx += CELL) {
|
||
setfillstyle(SOLID_FILL,
|
||
(((cx ^ cy) >> 4) & 1) ? WHITE : BLACK);
|
||
bar(cx, cy, cx + CELL - 1, cy + CELL - 1);
|
||
}
|
||
}
|
||
|
||
static void init_balls(void)
|
||
{
|
||
uint8_t i;
|
||
|
||
for (i = 0; i < NBALLS; i++) {
|
||
sprite_init(&sp[i], spr[i % NCOLORS]);
|
||
sp[i].x = (20 + i * 37) % (MAXX - 1) + 1;
|
||
sp[i].y = (20 + i * 53) % (MAXY - 1) + 1;
|
||
sprite_show(&sp[i]);
|
||
vx[i] = (i & 1) ? 1 : -1;
|
||
vy[i] = (i & 2) ? 1 : -1;
|
||
}
|
||
}
|
||
|
||
#if FPS_METER
|
||
/* Кадров за последнюю RTC-секунду — белым в чёрной плашке слева-сверху
|
||
* на ТЕКУЩЕЙ draw-странице (рисуем ДО sprite_update, чтобы шары легли
|
||
* поверх и heal учитывал плашку через ОЗУ-копию). */
|
||
static void show_fps(uint8_t v)
|
||
{
|
||
char buf[4];
|
||
|
||
buf[0] = (char)('0' + (v / 100) % 10);
|
||
buf[1] = (char)('0' + (v / 10) % 10);
|
||
buf[2] = (char)('0' + v % 10);
|
||
buf[3] = 0;
|
||
setcolor(WHITE);
|
||
setfillstyle(SOLID_FILL, BLACK);
|
||
bar(0, 0, 27, 9);
|
||
outtextxy(2, 1, buf);
|
||
}
|
||
#endif
|
||
|
||
int main(void)
|
||
{
|
||
uint8_t i, page, hidden;
|
||
#if FPS_METER
|
||
datetime_t dt;
|
||
uint8_t frames = 0, last_sec;
|
||
#endif
|
||
|
||
build_sprites();
|
||
initgraph();
|
||
gfx_sprite_clip(0);
|
||
|
||
/* Палитра страницы 1 = палитре страницы 0. */
|
||
gfx_pal_get(0, 0, 16, egapal);
|
||
gfx_pal_load(1, 0, 16, egapal);
|
||
|
||
init_balls();
|
||
|
||
/* Обе страницы: шахматка (в ОЗУ-копию — бэкап для heal) + шары в
|
||
* стартовых позициях (sprite_update рисует на текущей draw-странице
|
||
* и снимает её dirty-бит). */
|
||
for (page = 0; page < 2; page++) {
|
||
gfx_set_draw_page(page);
|
||
draw_board();
|
||
sprite_update(sp, NBALLS);
|
||
}
|
||
gfx_set_visible_page(0);
|
||
#if FPS_METER
|
||
getdatetime(&dt);
|
||
last_sec = dt.second;
|
||
#endif
|
||
|
||
for (;;) {
|
||
if (kbhit() && getch() == 27)
|
||
break;
|
||
|
||
/* Физика: обновить позиции (только состояние спрайтов). */
|
||
for (i = 0; i < NBALLS; i++) {
|
||
int nx = sp[i].x + vx[i];
|
||
int ny = sp[i].y + vy[i];
|
||
if (nx <= MINX || nx >= MAXX) vx[i] = -vx[i];
|
||
if (ny <= MINY || ny >= MAXY) vy[i] = -vy[i];
|
||
sprite_move(&sp[i], nx, ny);
|
||
}
|
||
|
||
/* Кадр: рисуем на скрытой странице. sprite_flip развёрнут, чтобы
|
||
* плашку FPS нарисовать на скрытой странице ДО sprite_update. */
|
||
hidden = gfx_get_visible_page() ^ 1;
|
||
gfx_set_draw_page(hidden);
|
||
#if FPS_METER
|
||
frames++;
|
||
getdatetime(&dt);
|
||
if (dt.second != last_sec) {
|
||
last_sec = dt.second;
|
||
show_fps(frames);
|
||
frames = 0;
|
||
}
|
||
#endif
|
||
sprite_update(sp, NBALLS);
|
||
gfx_wait_vsync();
|
||
gfx_set_visible_page(hidden); /* флип в vblank — без мерцания */
|
||
}
|
||
|
||
closegraph();
|
||
return 0;
|
||
}
|