a823e7ee9c
Акселератор умеет не только копировать блок, но и совмещать его с приёмником: опкод `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>
136 lines
8.6 KiB
C
136 lines
8.6 KiB
C
/*
|
||
* _bgi_blit_cols_op_raw — accel-блит КОЛОНКАМИ с ЛОГИЧЕСКОЙ ОПЕРАЦИЕЙ:
|
||
* приёмник = приёмник <op> источник, где <op> = AND / OR / XOR. Клон
|
||
* _bgi_blit_cols_raw (тот же column-major источник, тот же бесплатный
|
||
* горизонтальный флип), но между чтением источника и записью вставлен
|
||
* ТРЕТИЙ burst — чтение приёмника в режиме операции.
|
||
*
|
||
* КАК ЭТО РАБОТАЕТ (docs/new/06-accel.md §6.2, подтверждено драйвером MAME
|
||
* src/mame/sinclair/sprinter.cpp — accel_control_r/update_accel_buffer).
|
||
* Опкоды `AND (HL)` #A6, `OR (HL)` #B6, `XOR (HL)` #AE НЕ являются
|
||
* отдельным режимом акселератора: их M1-выборка переводит внутренний
|
||
* автомат из «буфер := память» в «буфер <op>= память», а сам burst
|
||
* запускается операндным чтением (HL) — в ТЕКУЩЕМ режиме направления
|
||
* (гориз. LD L,L / верт. LD A,A). То есть операция ОРТОГОНАЛЬНА
|
||
* направлению: для column-major спрайтов работает так же, как копия.
|
||
* Следующее армирование (LD L,L / LD A,A / LD B,B) возвращает автомат в
|
||
* «буфер := память».
|
||
*
|
||
* ОДНА функция на три операции: опкод операции патчится SMC при входе
|
||
* (байт op), ровно как размер блока и страйд. op — это и есть опкод
|
||
* (GFX_OP_AND/OR/XOR в <gfx.h>); проверку значения делает вызывающий.
|
||
*
|
||
* ПОСЛЕДОВАТЕЛЬНОСТЬ НА КОЛОНКУ (три burst'а вместо двух):
|
||
* OUT Port_Y = y0 ; accel разоружён — операнд OUT безопасен
|
||
* LD L,L / LD A,(DE) ; гориз. чтение: h смежных байт src -> буфер
|
||
* LD A,A / <op> (HL) ; верт. чтение: буфер <op>= видео-колонка
|
||
* LD B,B ; СТОП — разоружить перед вторым OUT
|
||
* OUT Port_Y = y0 ; вернуть Y: верт. burst сдвинул его на +h
|
||
* LD A,A / LD (HL),A ; верт. запись: буфер -> видео-колонка
|
||
* LD B,B
|
||
* Второй OUT обязателен: вертикальный burst ШАГАЕТ Port_Y (§6.1), и после
|
||
* op-чтения он стоит на y0+h. СТОП перед OUT обязателен по квирку
|
||
* memory/accel_operand_fetch_retrigger (fetch операнда под армированным
|
||
* accel перезапускает burst). Буфер и размер блока переживают LD B,B
|
||
* (§6.3) — это и позволяет разнести чтение и запись.
|
||
*
|
||
* ЧТО ЧИТАЕТ op-burst: страницы #50..#5F на ЧТЕНИЕ всегда отдают ОЗУ-копию
|
||
* экрана, а не видео-ОЗУ (memory/sprinter_vram_transparency). Значит при
|
||
* банке 0x54/0x5C (бит 2 — «не писать в ОЗУ-копию») операция считает
|
||
* ЧИСТЫЙ ФОН, а не то, что уже нарисовано поверх него. Нужна операция с
|
||
* реально видимым содержимым — вызывающий обязан рисовать банком 0x50
|
||
* (GFX_BANK_NORMAL), тогда копия и видео-ОЗУ совпадают.
|
||
*
|
||
* ПРОЗРАЧНОСТЬ. Аппаратная прозрачность (банки 0x58/0x5C — не писать байт
|
||
* #FF) действует на РЕЗУЛЬТАТ, а не на источник, поэтому даром достаётся
|
||
* только двум операциям:
|
||
* AND — #FF нейтрален сам по себе (#FF & bg = bg);
|
||
* OR — #FF | bg = #FF, и запись такого байта подавляется банком 0x58/0x5C;
|
||
* XOR — НЕ работает: #FF ^ bg = ~bg, инверсия фона на прозрачных пикселях.
|
||
* Для XOR источник обязан иметь прозрачный индекс 0x00 (нейтральный к XOR
|
||
* и к OR) и рисоваться банком без подавления. Подробно — шапка
|
||
* common/gfx_blit_cols_part_wx_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_cols_raw. Callee-pop 6 Б.
|
||
*
|
||
* Raw: без клиппинга (вызывающий); W3 замаплен (_bgi_begin/end снаружи);
|
||
* банк выставлен вызывающим. src ВНЕ W3. ОДИН DI на весь блит (втрое
|
||
* больше трафика, чем у копии — для больших спрайтов резать вызывающему).
|
||
* Клоббер: AF/BC/DE/HL; IX сохраняется.
|
||
*/
|
||
|
||
#include "../_bgi.h"
|
||
|
||
void _bgi_blit_cols_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: размер accel-блока <- h (высота колонки)
|
||
ld a, 5 (ix)
|
||
ld (bo_len_imm), a
|
||
;; SMC: src-страйд между колонками (8-бит adc-цепочка, ± на вызов)
|
||
ld a, 6 (ix)
|
||
ld (bo_slo_imm), a
|
||
ld a, 7 (ix)
|
||
ld (bo_shi_imm), a
|
||
;; SMC: опкод операции — and/or/xor (hl) (#A6/#B6/#AE)
|
||
ld a, 9 (ix)
|
||
ld (bo_op_imm), a
|
||
ld b, 4 (ix) ; B = число колонок (0 => 256: djnz)
|
||
ld c, 8 (ix) ; C = y0 (Port_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
|
||
bo_len_imm = . - 1
|
||
ld b, b ; 0x40 — стоп (B=счётчик не тронут)
|
||
bo_col:
|
||
ld a, c
|
||
out (#0x89), a ; Port_Y = y0 (верх колонки)
|
||
;; 1) ЧТЕНИЕ ИСТОЧНИКА — ГОРИЗОНТАЛЬНОЙ копией (LD L,L): колонка
|
||
;; спрайта лежит в ОЗУ СМЕЖНО, шагает адрес, не Port_Y.
|
||
ld l, l ; 0x6D — гориз. режим
|
||
ld a, (de) ; read-burst: h байт src -> буфер accel
|
||
;; 2) ОПЕРАЦИЯ с приёмником — ВЕРТИКАЛЬНЫМ чтением (LD A,A):
|
||
;; буфер <op>= видео-колонка; Port_Y уезжает на y0+h.
|
||
ld a, a ; 0x7F — верт. режим
|
||
xor a, (hl) ; SMC: and/or/xor (hl) — op-burst
|
||
bo_op_imm = . - 1
|
||
ld b, b ; 0x40 — стоп (разоружить перед OUT)
|
||
;; 3) ЗАПИСЬ — снова с верха колонки.
|
||
ld a, c
|
||
out (#0x89), a ; Port_Y = y0 (accel разоружён)
|
||
ld a, a ; 0x7F — верт. режим
|
||
ld (hl), a ; write-burst: буфер -> видео-колонка
|
||
ld b, b ; 0x40 — стоп
|
||
;; src += sstride (следующая колонка источника)
|
||
ld a, e
|
||
add a, #0
|
||
bo_slo_imm = . - 1
|
||
ld e, a
|
||
ld a, d
|
||
adc a, #0
|
||
bo_shi_imm = . - 1
|
||
ld d, a
|
||
inc hl ; dst += 1 (следующий x)
|
||
djnz bo_col
|
||
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;
|
||
}
|