Files
Sprinter-SDCC/tests/convbench/convbench.c
T
snark13 860468f3c5 libbgi: NOT_PUT тоже через акселератор + изыскания по виду Тени
NOT.  У акселератора нет режима «инвертировать буфер»: буфер меняется
только на ЧТЕНИИ и только опкодами AND/OR/XOR (HL) (драйвер MAME,
update_accel_buffer), а `CPL` автомат вообще не распознаёт — инвертируется
регистр CPU, не буфер.  Зато ~src = src XOR #FF, поэтому NOT собирается из
уже имеющегося: буфер := src, XOR-burst по константному блоку единиц
(common/_bgi_ones256.c), запись.  Ядра _bgi_blit_{rows,cols}_not_raw.c;
между burst'ами меняется HL, поэтому каждая смена — под СТОПом (иначе fetch
операнда перезапустит burst).  В колоночном варианте второй OUT Port_Y не
нужен: op-burst идёт по блоку единиц горизонтально, а Port_Y шагает только
вертикальный.

Наружу — тем же op-параметром (GFX_OP_NOT), диспетчер один раз на вызов, в
цикл по полосам/колонкам не заходит.  putimage лишился попиксельного пути
ЦЕЛИКОМ: все пять операций BGI идут через акселератор и клиппируются
одинаково.  tests/accop дополнен T9/T10 (NOT строками и колонками) —
10/10 PASS в MAME; tests/bgi_img P1/P2 по-прежнему PASS.

tests/convbench (новый) — замер побайтной конвертации атласа
«прозрачный #FF -> 0x00» (источник для XOR-блита): 145.3 такта/байт, то
есть 2.5× от 59 номинальных T-states цикла.  0.11 с на страницу 16 КБ,
3.2 с на все 28 страниц, 1.3 с по фактическому объёму данных (186 КБ).

applications/PoP/docs/shadow_render.md — вид Тени (два блиттера OR+XOR)
ОТЛОЖЕН по решению пользователя: пока рисуем обычной копией атласами Кида.
В документе собрано, почему в лоб не выходит (XOR несовместим с
прозрачностью #FF; операция читает ОЗУ-копию, поэтому два прохода
оригинала вырождаются в один XOR — нужен однопроходный композит
s | bg ^ s(сдвиг)), замер стоимости источника и четыре варианта.  Ключ к
выбору — список кадров, которыми тень реально пользуется; снимать по факту,
когда пойдут уровни 5/6/12.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 15:41:06 +03:00

112 lines
5.8 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* convbench — сколько стоит на Z80 побайтная конвертация атласа
* «прозрачный #FF -> 0x00» (заготовка теневого набора спрайтов для
* XOR-блита, см. memory/accel_block_ops).
*
* Зачем: тень PoP рисуется XOR'ом, а XOR несовместим с аппаратной
* прозрачностью #FF (она смотрит на РЕЗУЛЬТАТ: #FF ^ bg = ~bg, не
* подавляется). Источник для XOR обязан хранить прозрачный пиксель как
* 0x00. Вопрос — можно ли готовить такой набор в рантайме, переписывая
* страницы атласа Кида при загрузке уровня.
*
* ОКНА: в бою источник и приёмник — РАЗНЫЕ EMM-страницы, а окно под
* атласы одно (W0), поэтому конвертация гоняется «страница-источник в W0
* -> страница-приёмник в W3» целыми страницами по 16 КБ (переключать
* окно побайтно нельзя). На такты самого цикла это не влияет — обе
* стороны обычное ОЗУ, — поэтому здесь мерим в W2, на обычных массивах.
*
* Мерим сам горячий цикл, без обвязки: маркеры OUT в порт #FE (значение
* 0x01 до, 0x02 после) — снимаются в MAME watchpoint'ом по IO-записи с
* печатью totalcycles (memory/z80_profiling_method). Прогоняем
* BLOCKS раз по BLK байт, чтобы обвязка потерялась в шуме.
*
* Цикл — безветвочный (ветка на байт стоила бы дороже самой работы):
* прозрачный #FF отличается от цветов Кида (0x70..0x7F) битом 7, а маску
* из знакового бита даёт классическая пара ADD A,A / SBC A,A.
*
* ЗАМЕР 2026-08-11 (MAME, watchpoint по IO-записи в #FE, кадр = 430 080
* тактов): 4 760 382 такта на 32 КБ = **145.3 такта/байт** — это 2.5× от
* 59 номинальных T-states цикла (wait-state'ы ОЗУ, memory/
* sprinter_wait_states_2x). В пересчёте:
* 1 страница атласа (16 КБ) — 5.5 кадра = 0.11 с
* 28 страниц (весь атлас Кида) — 155 кадров = 3.2 с
* Потолок разгона этого цикла — примерно вдвое (раскрутка убирает djnz;
* чтение через SP по 2 байта + таблица 256 Б вместо ADD/SBC/CPL/AND),
* то есть ~1.5-1.7 с на весь атлас — порядок тот же.
*/
#include <stdint.h>
#include <stdio.h>
#define BLK 2048u /* байт за проход */
#define BLOCKS 16u /* проходов (итого 32 КБ) */
static uint8_t src[BLK];
static uint8_t dst[BLK];
/* Конвертировать pages×256 байт: #FF -> 0x00, остальные как есть.
* ВХОД (__sdcccall(1)): s→HL, d→DE, pages в A (4(ix)). Callee-pop 1 Б.
*
* Внутренний цикл — 256 байт через djnz, копия байта в C (стек стоил бы
* push/pop = 21 T на байт, недокументированные IXL/IXH не берём):
* ld a,(hl) 7 · ld c,a 4 · add a,a 4 · sbc a,a 4 · cpl 4 · and c 4 ·
* ld (de),a 7 · inc hl 6 · inc de 6 · djnz 13 = 59 T/байт номинально.
* Счётчик блоков живёт в стек-слоте 4(ix) — трогается раз на 256 байт. */
static void conv(const uint8_t *s, uint8_t *d, uint8_t pages) __naked
{
(void)s; (void)d; (void)pages;
__asm
push ix
ld ix, #0
add ix, sp
cv_page:
ld b, #0 ; B = 256 байт в блоке (0 => 256)
cv_loop:
ld a, (hl) ; 7 байт источника
ld c, a ; 4 копия (A нужен дважды)
add a, a ; 4 CF = бит 7 (1 = прозрачный #FF)
sbc a, a ; 4 A = #FF при CF, иначе 0x00
cpl ; 4 A = маска «оставить байт»
and a, c ; 4 байт & маска -> 0 для #FF
ld (de), a ; 7
inc hl ; 6
inc de ; 6
djnz cv_loop ; 13
dec 4 (ix) ; блоков осталось
jr NZ, cv_page
pop ix
pop hl ; ret
inc sp ; callee-pop 1 байт (pages)
jp (hl)
__endasm;
}
int main(void)
{
uint16_t i;
for (i = 0; i < BLK; i++)
src[i] = (uint8_t)((i & 3) ? (0x70 + (i & 15)) : 0xFF);
__asm
ld a, #1
out (#0xFE), a ; маркер «старт»
__endasm;
for (i = 0; i < BLOCKS; i++)
conv(src, dst, (uint8_t)(BLK / 256u));
__asm
ld a, #2
out (#0xFE), a ; маркер «стоп»
__endasm;
/* Самопроверка: конвертация действительно сделала то, что обещала. */
printf("conv %u x %u bytes\n", (unsigned)BLOCKS, (unsigned)BLK);
printf("src[0]=%02X dst[0]=%02X (want 00)\n",
(unsigned)src[0], (unsigned)dst[0]);
printf("src[1]=%02X dst[1]=%02X (want same)\n",
(unsigned)src[1], (unsigned)dst[1]);
for (;;) { }
}