Найдены 8791 тактов на вызов блита: uint16_t габарит в pop_blit_b
Точки на цепочку вызовов (libbgi живёт в резиденте W1, адреса однозначны —
пересборка не нужна) разложили постоянные накладные блита:
pop_blit_b ДО вызова ядра ....... 6 683 <- НАШ код, 77% накладных
gfx_blit_noclip + _bgi_begin
+ _gfx_blit_sprite_noclip ..... 1 938
пролог _bgi_blit_rows_raw ....... 457
строчный цикл ................... 389/строку (= 198 + 5,96*32, сходится
с регрессией)
эпилог + _bgi_end + возврат ..... 1 355
«вне цикла» ..................... 8 725 при ЛЮБОЙ высоте (h=9..60)
Причина в pop_blit_b, подтверждена чтением .asm: `w`/`h` объявлены
uint16_t, 16-битные значения не влезли в регистры, и SDCC увёл функцию в
14-байтовый стековый кадр (`ld iy,#-14 / add iy,sp / ld sp,iy`), после чего
`w = img[0] | (img[1] << 8)` развернулось в ДВА ДЕСЯТКА IX-относительных
пересылок между ячейками -7..-13 кадра.
Правка: габарит читается БАЙТАМИ. Корректно по построению — обе ветки и
так требовали w<256 && h<256 (эти проверки теперь убраны как тождественные),
а кадры атласов не крупнее 32x63; формат .atl допускает больше, такой кадр
уходит на общий путь (blit_b_oversize).
Замер A/B на той же детерминированной сцене, те же выборки:
gfx_blit_noclip 13 679 -> 11 530 (-16%)
blit_b_clip 18 853 -> 15 340 (-19%)
Стековый кадр 14 -> 6 байт, _CODE -42 Б. Тайл ~107 000 -> ~95 000.
Все 8 наборов tests-host проходят, комната 23 в MAME рисуется корректно.
Плюс TASKS_OPEN.md: OPT-BLIT — руководство на следующую сессию (где ещё
uint16->uint8, как искать IX-спиллы по asm, что НЕ делать, и грабли с
несколькими экземплярами MAME на один error.log).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -923,3 +923,86 @@ HP/минуты, номера «особых» комнат и уровней (
|
||||
`../docs/ideas_backlog.md` (оба дают доли процента кадра).
|
||||
- **OPT-1** (хирургический редрой шва) — решено НЕ делать, стоимость
|
||||
транзиентная; разбор в [`BUGS_CLOSED.md`](BUGS_CLOSED.md).
|
||||
|
||||
---
|
||||
|
||||
## OPT-BLIT — цена отрисовки: чистка кодогена (СЛЕДУЮЩАЯ СЕССИЯ)
|
||||
|
||||
Открыто 2026-08-13 по итогам разбора зелёного блока. Контекст и все замеры
|
||||
— в memory `blit_cost_model` и `sdcc_z80_stack_locals_hot_loop`, протокол
|
||||
разбора — в коммитах `f89b7dd` / `b27b313` / этом.
|
||||
|
||||
**Что уже сделано и чем это подтверждено.** Цена блита разложена регрессией
|
||||
по 339 замерам: `такты = 8791 + 198,2*h + 5,96*(w*h)` (такты MAME = системный
|
||||
клок ~21,5 МГц, НЕ такты Z80 — растровый кадр 430 000). Байт на пределе
|
||||
железа (через акселератор дважды по 3 такта), строка ~198, а вот 8 791 на
|
||||
ВЫЗОВ оказались нашим кодогеном: `pop_blit_b` держал `w`/`h` как `uint16_t`,
|
||||
из-за чего вся функция уезжала в 14-байтовый стековый кадр и `w = img[0] |
|
||||
(img[1] << 8)` разворачивалось в два десятка IX-относительных пересылок.
|
||||
После перехода на байтовый габарит: noclip 13 679 -> 11 530 (-16%),
|
||||
клипованный 18 853 -> 15 340 (-19%), `_CODE` -42 Б.
|
||||
|
||||
### а) Где ещё `uint16_t` можно сделать `uint8_t`
|
||||
|
||||
Габариты спрайтов и всё, что из них считается. Признак: значение заведомо
|
||||
<= 255, но объявлено 16-битным «на всякий случай», и живёт в горячем пути.
|
||||
Начинать с:
|
||||
- `blit_b_clip` (`pop_tile.c`) — `dw`/`dh`/`sx`/`sy` объявлены `int`, хотя
|
||||
ядра принимают `uint8_t`; это ВТОРОЙ по частоте путь (33% блитов).
|
||||
- `pop_cd_touch` / `cd_cols_of` — четыре `int`-аргумента на вызов.
|
||||
- `_gfx_blit_sprite_noclip` и обёртки libbgi: `stride` уже `uint16_t`, а
|
||||
`w`/`h` — `uint8_t`; проверить, не расширяются ли они обратно у вызывающих.
|
||||
- Кандидаты в `pop_room.c`/`pop_bg.c`: локальные `int x, dby, dmy` в
|
||||
`draw_tile` — часть из них помещается в байт, но осторожно: координаты
|
||||
бывают отрицательными.
|
||||
|
||||
**Как проверять:** после каждой правки смотреть пролог функции в
|
||||
`.sprinter-cc-roomtest/*.asm` — исчез ли `ld iy,#-N / add iy,sp / ld sp,iy`
|
||||
и сколько осталось `-N (ix)` в теле.
|
||||
|
||||
### б) Проход по asm за неоптимальным IX-доступом
|
||||
|
||||
Механическая проверка, даёт больше всего за единицу усилий:
|
||||
|
||||
```
|
||||
grep -c "(ix)" .sprinter-cc-roomtest/*.asm # где сгущается
|
||||
grep -n "ld iy, #-" .sprinter-cc-roomtest/*.asm # крупные стековые кадры
|
||||
grep -n "pop.*\n.*pop.*\n.*push" ... # чтение спилла через стек
|
||||
```
|
||||
|
||||
Признаки беды (все три встречались сегодня): пролог с `iy`-кадром больше
|
||||
~6 байт; пары `pop bc / pop hl / push hl / push bc` в теле цикла (SDCC
|
||||
читает спиленный указатель через стек вместо `ld l,-N(ix)`); повторный
|
||||
пересчёт адреса `arr[i]` под каждое поле структуры.
|
||||
|
||||
Приоритет по частоте вызова: `pop_blit_b` (сделан) -> `blit_b_clip` ->
|
||||
`pop_cd_touch` -> `draw_tile` -> `pop_redraw_needed`.
|
||||
|
||||
### Что НЕ делать (проверено сегодня, отрицательный результат)
|
||||
|
||||
- **Не откладывать запекание на другой кадр.** Запекание пишет ОЗУ-копию
|
||||
фона, из которой восстанавливает `heal`; отложенное даёт призрак плиты на
|
||||
месте дыры. Идея «бюджет одного запекания на кадр» снята.
|
||||
- **Не ускорять передачу пикселей** — она на пределе железа (3+3 такта на
|
||||
байт), см. модель выше.
|
||||
- **Не искать проблему в W3-скобке** (`_bgi_begin`/`_bgi_end` — по пять
|
||||
инструкций) и не списывать разброс на прерывания (внутри размерной
|
||||
группы разброс 3 такта, код прямолинейный).
|
||||
|
||||
### Хвосты этой сессии
|
||||
|
||||
- Пакетная пометка `pop_cd_touch` (`pop_cd_batch_begin/end`, скобка в
|
||||
`draw_tile`) — сделана, но выигрыш замером НЕ подтверждён: в захваченных
|
||||
кадрах скобка не срабатывала (блиты шли из холодной отрисовки комнаты).
|
||||
Переснять на кадрах ЗАПЕКАНИЯ.
|
||||
- Снять временную оснастку: `pop_dbg_m9..m16`, `pop_dbg_b1..b6`,
|
||||
`pop_dbg_wh`, `pop_dbg_kind`, `pop_dbg_rdmax*`, `mame/v306/run_bridge_log.sh`.
|
||||
- Цель по кадру не достигнута: зелёный пик был 792 012 при цели 400 000.
|
||||
После сегодняшних правок не перемерян — начать сессию с контрольного
|
||||
замера, а не с новых правок.
|
||||
|
||||
**ГРАБЛИ (стоили сегодня часа):** проверять, что запущен РОВНО ОДИН MAME
|
||||
(`pgrep -f mame.arm | wc -l`) — несколько экземпляров пишут в один
|
||||
`error.log`, мост говорит с одним, замеры собираются с другого, и точки
|
||||
«не срабатывают». И не обрезать `error.log`, пока MAME его держит: она
|
||||
пишет по старому смещению, в начале остаётся дыра из нулей.
|
||||
|
||||
@@ -363,10 +363,22 @@ static void blit_b_clip(const uint8_t *img, int x, int top, int w, int h)
|
||||
if (!pop_t_fclip_on) pop_cd_touch(dx, dy, dw, dh);
|
||||
}
|
||||
|
||||
/* Кадр шире или выше 255 — общий (медленный) путь. Вынесен отдельно, чтобы
|
||||
* 16-битная арифметика габарита не жила в горячем pop_blit_b: у нас таких
|
||||
* кадров нет вовсе (максимум 32x63), но контракт формата .atl их допускает. */
|
||||
static void blit_b_oversize(const uint8_t *img, int x, int ybottom)
|
||||
{
|
||||
uint16_t w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
||||
uint16_t h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
||||
int top = ybottom - (int)h + 1 + POP_YOFF;
|
||||
if (pop_upside) top = FLIP_TOP(top, h);
|
||||
if (w && h) blit_b_clip(img, x, top, (int)w, (int)h);
|
||||
}
|
||||
|
||||
void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
|
||||
{
|
||||
const uint8_t *img;
|
||||
uint16_t w, h;
|
||||
uint8_t w, h; /* БАЙТЫ, а не uint16_t — см. ниже */
|
||||
int top;
|
||||
if (idx >= a->count)
|
||||
return;
|
||||
@@ -384,9 +396,24 @@ void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
|
||||
img = (const uint8_t *)atlas_image(a, idx);
|
||||
gfx_w0_map(a->page);
|
||||
pop_dbg_b2(); /* ВРЕМЕННО */
|
||||
pop_dbg_wh((uint16_t)(((uint16_t)img[0] << 8) | img[2])); /* ВРЕМЕННО: w,h */
|
||||
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
||||
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
||||
/* Габарит читаем БАЙТАМИ. С `uint16_t w = img[0] | (img[1] << 8)` SDCC
|
||||
* разворачивал каждую такую строку в два десятка IX-относительных
|
||||
* пересылок: 16-битные w/h не влезали в регистры, и функция целиком
|
||||
* уезжала в стековый кадр (пролог `ld iy,#-14 / add iy,sp / ld sp,iy`,
|
||||
* дальше всё через -N(ix) по 19 тактов). Замер 2026-08-13: до вызова
|
||||
* ядра блита уходило ~6 700 тактов при том, что работы там — прочитать
|
||||
* четыре байта заголовка и сравнить границы.
|
||||
* Байтовый габарит корректен по построению: обе ветки ниже И ТАК требуют
|
||||
* w < 256 && h < 256 (иначе ядро не примет), а кадры наших атласов не
|
||||
* крупнее 32x63. Формат .atl допускает больше — такой кадр уходит на
|
||||
* общий путь выше. */
|
||||
if (img[1] | img[3]) { /* кадр больше 255 — редкий путь */
|
||||
blit_b_oversize(img, x, ybottom);
|
||||
gfx_w0_unmap();
|
||||
return;
|
||||
}
|
||||
w = img[0];
|
||||
h = img[2];
|
||||
top = ybottom - (int)h + 1 + POP_YOFF; /* +YOFF: центрирование */
|
||||
/* Переворот применяем ЗДЕСЬ, до клипа: дальше вся геометрия (окно
|
||||
* fore-слоя, полоса у потолка, отсев по экрану) считается уже в
|
||||
@@ -406,7 +433,8 @@ void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
|
||||
gfx_w0_unmap();
|
||||
return;
|
||||
}
|
||||
if (x >= 0 && top >= 0 && w < 256 && h < 256 &&
|
||||
/* w<256 && h<256 больше не проверяем — гарантировано типом. */
|
||||
if (x >= 0 && top >= 0 &&
|
||||
x + (int)w <= 320 && top + (int)h <= 256) {
|
||||
pop_dbg_b6(); /* ВРЕМЕННО: пометка «шли в noclip» */
|
||||
if (pop_upside) gfx_blit_noclip_vflip(x, top, img);
|
||||
|
||||
Reference in New Issue
Block a user