/* * rpgprof — профилировочная копия rpgwalk (НЕ собирается по умолчанию). * Два инструмента в одном: * * 1. ВИЗУАЛЬНЫЙ профайлер — цвет ГРАНИЦЫ экрана меняется на входе в * каждую секцию кадра; луч бежит сверху вниз (~320 строк растра за * кадр 48.83 Гц, 1344 такта на строку), так что толщина цветной * полосы бордера ∝ времени секции. * ВНИМАНИЕ: бордер принимает только биты 1-2 порта #FE (бит 0 = * красный ИГНОРИРУЕТСЯ — проверено 2026-07-14) → доступны РОВНО * четыре цвета: 0 чёрный, 2 синий, 4 зелёный, 6 голубой: * синий (2) — логика (короткая) и blit-проход (банк 0x5C) * зелёный (4) — тик авто-анимаций (_spr_tick_fn) * голубой (6) — heal-проход (банк 0x50) * чёрный (0) — gfx_wait_vsync (простой до конца кадра) * Много чёрного = запас; синий+зелёный через весь бордер = не * влезаем. dev-MAME перерисовывает бордер крупными кусками (не * per-scanline) — полосы приблизительные, для точных цифр см. п.2. * * 2. ТАКТОВЫЙ профайлер — те же OUT'ы ловятся watchpoint'ом дебаггера * dev-MAME (рецепт: memory/mame_mcp_bridge и docs/sprite-api-design.md * §9д): wpiset по маске порта + printf totalcycles/wpdata + авто-g, * затем оп clog моста. Для уникальности wpdata при одинаковом цвете * можно кодировать секцию в битах 3+ (бордер их не видит): * напр. логика 0x12, blit 0x02 — оба синие, но различимы в логе. * * Сборка и запуск (из этого каталога; атласы уже собраны make'ом): * ../../bin/sprinter-cc --memory tiny --gfx 256 -o rpgprof.exe rpgprof.c * cd ../../mame/v306 && python3 make_disk.py IMG/mc.img \ * ../../examples/rpgwalk/rpgprof.exe \ * ../../examples/rpgwalk/bard1.atl ../../examples/rpgwalk/bard2.atl \ * ../../examples/rpgwalk/bard.pal * ./run_mame.sh (или ./run_bridge.sh для тактового профиля) * в CLI Sprinter: a:\rpgprof.exe * * my_update — копия libbgi sprite_update с воткнутыми маркерами; при * изменении оригинала синхронизировать вручную. */ #include #include #include #include #include #include #include #include "../../libbgi/_bgi.h" __sfr __at (0xFE) io_border; /* Копия sprite_update с суб-маркерами (см. шапку): 6 тик анимаций, * 3 heal-проход, 7 blit-проход. */ static void my_update(sprite_t *arr, uint8_t count) { uint8_t p = gfx_get_draw_page(); uint8_t dbit = p ? _SPR_DIRTY1 : _SPR_DIRTY0; uint8_t saved = gfx_get_bank(); uint8_t i; const uint8_t *rec; sprite_t *s; io_border = 4; /* зеленый: тик анимаций */ if (_spr_tick_fn) (*_spr_tick_fn)(arr, count); io_border = 6; /* голубой: heal */ gfx_set_bank(GFX_BANK_NORMAL); _bgi_begin(); for (i = 0, s = arr; i < count; i++, s++) { if (s->drawn[p].on && (s->flags & (dbit | SPR_ALWAYS))) { (*_gfx_heal_fn)(s->drawn[p].x, s->drawn[p].y, s->w, s->h); s->drawn[p].on = 0; } } _bgi_end(); /* Y-сортировка (как в sprite_update; входит в «синюю» blit-секцию * профиля — её цену видно по маркерам). */ io_border = 2; /* синий: blit (+ сортировка) */ rec = 0; if (_spr_ysort_fn && count <= _GFX_YSORT_MAX) rec = (*_spr_ysort_fn)(arr, count) + 2; gfx_set_bank(GFX_BANK_SPRITE); _bgi_begin(); { uint8_t mapped = 0; for (i = 0; i < count; i++) { if (rec) { s = *(sprite_t * const *)rec; /* Y-порядок */ rec += 4; } else s = arr + i; if (s->flags & (dbit | SPR_ALWAYS)) { if (s->flags & SPR_VISIBLE) { if (s->page != mapped && s->page) { _gfx_w0_cur = s->page; _io_page_w0 = s->page; mapped = s->page; } (*_gfx_blit_fn)(s->x, s->y, s->src, s->stride, s->w, s->h); s->drawn[p].x = s->x; s->drawn[p].y = s->y; s->drawn[p].on = 1; } s->flags &= ~dbit; } } if (mapped) _io_page_w0 = _gfx_w0_dss; } _bgi_end(); gfx_set_bank(saved); } #define NCHR 15 #define MINX 0 #define MAXX (320 - 16) #define MINY 14 #define MAXY (256 - 16) #define QX 80 #define QY 64 static atlas_t at1, at2; static sprite_t sp[NCHR]; static uint8_t dir[NCHR]; static uint8_t phase[NCHR]; static void set_dir(uint8_t i, uint8_t d) { dir[i] = d; // sprite_anim(&sp[i], (uint8_t)(d * 3), (uint8_t)(d * 3 + 2), // (uint8_t)(5 + (i & 3)), ANIM_PINGPONG); sprite_anim(&sp[i], (uint8_t)(d * 3), (uint8_t)(d * 3 + 2), (uint8_t)5, ANIM_PINGPONG); } static void go_edge(uint8_t i) { sprite_t *s = &sp[i]; int tx = s->x, ty = s->y; switch (dir[i]) { case 0: ty = MAXY; break; case 1: tx = MINX; break; case 2: tx = MAXX; break; default: ty = MINY; break; } sprite_moveto(s, tx, ty, 1, (uint8_t)(1 + (i & 1))); phase[i] = 0; } static void go_back_random(uint8_t i) { sprite_t *s = &sp[i]; int tx = s->x, ty = s->y, avail, dist; uint8_t d = dir[i]; d = (uint8_t)(3 - d); /* 0↔3, 1↔2 — разворот */ set_dir(i, d); switch (d) { case 0: avail = MAXY - s->y; dist = QY; break; case 1: avail = s->x - MINX; dist = QX; break; case 2: avail = MAXX - s->x; dist = QX; break; default: avail = s->y - MINY; dist = QY; break; } if (avail > dist) dist += rand() % (avail - dist + 1); else dist = avail; switch (d) { case 0: ty += dist; break; case 1: tx -= dist; break; case 2: tx += dist; break; default: ty -= dist; break; } sprite_moveto(s, tx, ty, 1, (uint8_t)(1 + (i & 1))); phase[i] = 1; } static void turn_90(uint8_t i) { uint8_t d = dir[i]; uint8_t vert = (d == 0 || d == 3); set_dir(i, vert ? (uint8_t)(1 + (rand() & 1)) : (uint8_t)((rand() & 1) ? 0 : 3)); go_edge(i); } // 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); // } static void show_fps(uint8_t v) { uint8_t color = BLACK; setfillstyle(SOLID_FILL, color); bar(0, 2, 60, 5); if(v < 30) { color = RED; } else if(v < 40) { color = BLUE; } else if(v < 47) { color = MAGENTA; } else if(v < 52) { color = CYAN; } else { color = YELLOW; } setfillstyle(SOLID_FILL, color); bar(0, 2, v, 5); } static void draw_field(void) { int i; srand(4321); setfillstyle(SOLID_FILL, GREEN); bar(0, 0, 319, 255); for (i = 0; i < 400; i++) putpixel(rand() % 320, rand() % 244 + 12, (i & 3) ? LIGHTGREEN : BROWN); for (i = 0; i < 24; i++) { int x = rand() % 312 + 4, y = rand() % 232 + 16; putpixel(x, y, (i & 1) ? WHITE : YELLOW); putpixel(x - 1, y, LIGHTGREEN); putpixel(x + 1, y, LIGHTGREEN); } setfillstyle(SOLID_FILL, BLACK); setcolor(BLACK); line(0, 0, 0, 7); line(10, 0, 10, 7); line(20, 0, 20, 7); line(30, 0, 30, 7); line(40, 0, 40, 7); line(50, 0, 50, 7); line(60, 0, 60, 7); setcolor(WHITE); outtextxy(52, 3, "RPGPROF: border = frame profile, ESC"); } int main(void) { uint8_t i, page, hidden; uint8_t frames = 0, last_sec, fps_val = 0, fps_draw = 0; datetime_t dt; if (atlas_load(&at1, "bard1.atl") != 0 && atlas_load(&at1, "a:\\bard1.atl") != 0) return 1; if (atlas_load(&at2, "bard2.atl") != 0 && atlas_load(&at2, "a:\\bard2.atl") != 0) return 1; initgraph(); gfx_sprite_clip(0); if (gfx_pal_fload(0, "bard.pal") < 0) gfx_pal_fload(0, "a:\\bard.pal"); gfx_pal_sync(); getdatetime(&dt); srand((unsigned)(dt.second * 77u + dt.minute)); for (i = 0; i < NCHR; i++) { /* Персонажи циклом из двух атласов: 0-3 at1, 4-7 at2, 8-11 at1… * (группы по 4 подряд — минимум смен W0-страницы за проход). */ atlas_sprite_init(&sp[i], (i & 4) ? &at2 : &at1, (uint8_t)(i & 3)); sp[i].x = 24 + (rand() % (MAXX - 48)); sp[i].y = MINY + 8 + (rand() % (MAXY - MINY - 16)); set_dir(i, (uint8_t)(rand() & 3)); go_edge(i); sprite_show(&sp[i]); } for (page = 0; page < 2; page++) { gfx_set_draw_page(page); draw_field(); sprite_update(sp, NCHR); } gfx_set_visible_page(0); getdatetime(&dt); srand((unsigned)(dt.second * 77u + dt.minute)); last_sec = dt.second; for (;;) { io_border = 2; /* синий: логика */ if (kbhit() && getch() == 27) break; for (i = 0; i < NCHR; i++) if (!sprite_moving(&sp[i])) { if (phase[i] == 0) go_back_random(i); else turn_90(i); } hidden = gfx_get_visible_page() ^ 1; gfx_set_draw_page(hidden); frames++; getdatetime(&dt); if (dt.second != last_sec) { last_sec = dt.second; fps_val = frames; frames = 0; fps_draw = 2; } if (fps_draw) { fps_draw--; show_fps(fps_val); } my_update(sp, NCHR); /* маркеры 2/4/6 внутри */ io_border = 0; /* черный: ждём vsync */ gfx_wait_vsync(); gfx_set_visible_page(hidden); } io_border = 0; closegraph(); atlas_free(&at2); atlas_free(&at1); return 0; }