libbgi: блочные AND/OR/XOR акселератора (блит строками и колонками)

Акселератор умеет не только копировать блок, но и совмещать его с
приёмником: опкод `and/or/xor (hl)` между триггерами чтения и записи
переводит внутренний автомат из «буфер := память» в «буфер <op>= память».
Операция ортогональна направлению (гориз. LD L,L / верт. LD A,A), значит
одинаково работает и для row-major картинок, и для column-major спрайтов
персонажей с бесплатным флипом.

Полный набор, по образцу существующего копирующего:
- ядра bgi256/_bgi_blit_rows_op_raw.c и _bgi_blit_cols_op_raw.c;
- строками: gfx_blit_op / gfx_blit_part_op (через _gfx_blit_full_op);
- колонками: gfx_blit_cols_op / gfx_blit_cols_part_wx_op (skip/rows/
  skipw/maxw и flip — как у копирующего близнеца);
- putimage(XOR/OR/AND_PUT) переведён на accel, попиксельным остался
  только NOT_PUT — закрыт пункт 2d-1 docs/TODO.md.

Одна функция на три операции: опкод патчится SMC, как размер блока и
страйд, — ветвления в цикле нет.  Колоночный путь дороже строкового на
один OUT Port_Y: вертикальный op-burst шагает Port_Y, и перед записью
его надо вернуть на верх колонки (STOP обязателен — иначе fetch операнда
OUT перезапустит burst, memory/accel_operand_fetch_retrigger).

Две оговорки (в шапках модулей):
- операция ЧИТАЕТ ОЗУ-копию экрана, а не видео-ОЗУ, поэтому при банках
  0x54/0x5C совмещается с чистым фоном, а не с нарисованным поверх;
- аппаратная прозрачность #FF совместима с AND (нейтраль) и OR (#FF|bg =
  #FF, запись подавляется), но НЕ с XOR: там источник обязан хранить
  прозрачный пиксель как 0x00.

Проверка: tests/accop (новый) — 8/8 PASS в MAME, побайтно, источник с
разными значениями по строкам И колонкам + рамка вокруг; tests/bgi_img
получил две байтовые самопроверки (COPY==источник, XOR дважды == фон),
обе PASS.  make size-check: роста нет (эталон принят заново — kbdpoll
+63 Б приехал из прошлой сессии, воспроизводится и без этих правок).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 15:03:47 +03:00
parent d154cb452c
commit a823e7ee9c
15 changed files with 829 additions and 23 deletions
+118
View File
@@ -0,0 +1,118 @@
/*
* _bgi_blit_rows_op_raw — accel-блит СТРОКАМИ с ЛОГИЧЕСКОЙ ОПЕРАЦИЕЙ:
* приёмник = приёмник <op> источник, где <op> = AND / OR / XOR. Клон
* _bgi_blit_rows_raw (dst фиксирован, строку выбирает Port_Y; src шагает
* на sstride), но между чтением источника и записью вставлен ТРЕТИЙ
* burst — чтение приёмника в режиме операции.
*
* КАК ЭТО РАБОТАЕТ (docs/new/06-accel.md §6.2 + пример «XOR блока данных»
* §6.3; сверено с драйвером MAME src/mame/sinclair/sprinter.cpp,
* accel_control_r/update_accel_buffer). Опкоды `AND (HL)` #A6,
* `OR (HL)` #B6, `XOR (HL)` #AE — не режим направления, а переключатель
* внутреннего автомата с «буфер := память» на «буфер <op>= память»; сам
* burst запускает операндное чтение (HL) в ТЕКУЩЕМ режиме направления.
* Следующее армирование (LD L,L / LD A,A / LD B,B) возвращает автомат к
* присваиванию.
*
* ЗДЕСЬ ВСЁ ГОРИЗОНТАЛЬНО — и источник (строка спрайта в ОЗУ), и приёмник
* (строка экрана), поэтому три burst'а идут ОДНОЙ цепочкой, ровно как в
* примере документации, без промежуточных STOP/OUT:
* LD L,L ; гориз. режим
* LD A,(DE) ; read-burst : w байт src -> буфер accel
* <op> (HL) ; op-burst : буфер <op>= строка экрана
* LD (HL),A ; write-burst: буфер -> строка экрана
* LD B,B ; стоп
* Port_Y в горизонтальном режиме не шагает (шагает внутренний адрес,
* который каждый burst берёт заново из регистра) — сбрасывать между
* burst'ами нечего. Это дешевле колоночного близнеца
* (_bgi_blit_cols_op_raw), где приёмник вертикальный и Port_Y после
* op-burst'а приходится возвращать.
*
* ОДНА функция на три операции: опкод патчится SMC при входе (байт op),
* ровно как размер блока и страйд. op — это и есть опкод (GFX_OP_AND/
* OR/XOR в <gfx.h>); проверку значения делает вызывающий.
*
* ЧТО ЧИТАЕТ op-burst: страницы #50..#5F на ЧТЕНИЕ всегда отдают ОЗУ-копию
* экрана, а не видео-ОЗУ (memory/sprinter_vram_transparency). При банке
* 0x54/0x5C (бит 2 — «не писать в ОЗУ-копию») операция считает ЧИСТЫЙ ФОН,
* а не то, что нарисовано поверх; нужна операция с видимой картинкой —
* рисовать банком 0x50 (GFX_BANK_NORMAL).
*
* ПРОЗРАЧНОСТЬ: аппаратная (банк 0x58/0x5C — не писать #FF) смотрит на
* РЕЗУЛЬТАТ, поэтому даром достаётся AND (#FF нейтрален) и OR (#FF | bg =
* #FF, запись подавляется), но НЕ XOR (#FF ^ bg = ~bg). Источник для XOR
* обязан хранить прозрачный пиксель как 0x00. Подробно — шапка
* common/_gfx_blit_full_op.c.
*
* ВХОД (__sdcccall(1)): src→HL, dst→DE (фикс); стек: w 4(ix), h 5(ix),
* sstride 6/7(ix), y0 8(ix), op 9(ix). Смысл w/h/sstride/y0 — как у
* _bgi_blit_rows_raw. Callee-pop 6 Б.
*
* Raw: без клиппинга (вызывающий); W3 замаплен (_bgi_begin/end снаружи);
* банк выставлен вызывающим. src ВНЕ W3. ОДИН DI на весь блит (втрое
* больше трафика, чем у копии — большие блиты режет вызывающий).
* Клоббер: AF/BC/DE/HL; IX сохраняется.
*/
#include "../_bgi.h"
void _bgi_blit_rows_op_raw(const uint8_t *src, uint8_t *dst,
uint8_t w, uint8_t h,
int sstride, uint8_t y0, uint8_t op) __naked
{
(void)src; (void)dst; (void)w; (void)h; (void)sstride; (void)y0; (void)op;
__asm
push ix
ld ix, #0
add ix, sp
;; SMC: размер блока <- w (immediate у ld a,#n требование FSM)
ld a, 4 (ix)
ld (bro_len_imm), a
;; SMC: src-страйд константа на вызов (8-битная adc-цепочка)
ld a, 6 (ix)
ld (bro_slo_imm), a
ld a, 7 (ix)
ld (bro_shi_imm), a
;; SMC: опкод операции and/or/xor (hl) (#A6/#B6/#AE)
ld a, 9 (ix)
ld (bro_op_imm), a
ld b, 5 (ix) ; B = счётчик строк (0 => 256: djnz)
ld c, 8 (ix) ; C = y
;; Операция существует только в форме <op> (HL) приёмник обязан
;; жить в HL; источник переезжает в DE (читается LD A,(DE)).
ex de, hl ; HL = dst (экран, фикс), DE = src (ОЗУ)
di ; один DI на весь блит
ld d, d ; 0x52 режим размера блока (no-op для D)
ld a, #0
bro_len_imm = . - 1
ld b, b ; 0x40 стоп (B=счётчик не тронут)
bro_row:
ld a, c
out (#0x89), a ; Port_Y = y
inc c
ld l, l ; 0x6D армировать гориз. копию строки
ld a, (de) ; read-burst : src -> буфер акселератора
xor a, (hl) ; SMC: op-burst буфер <op>= строка экрана
bro_op_imm = . - 1
ld (hl), a ; write-burst: буфер -> строка экрана
ld b, b ; 0x40 стоп
;; src += sstride (dst НЕ двигается строку выбирает Port_Y)
ld a, e
add a, #0
bro_slo_imm = . - 1
ld e, a
ld a, d
adc a, #0
bro_shi_imm = . - 1
ld d, a
djnz bro_row
ei ; закрыть DI-окно блита
pop ix
;; callee-pop 6 байт (w,h,sstride:2,y0,op)
pop hl ; ret-адрес
pop af
pop af
pop af
jp (hl)
__endasm;
}