/* * _bgi_flip_rows_raw — построчная копия video->video с ПЕРЕВОРОТОМ ПО * ВЕРТИКАЛИ (mode 0x81). Строка src с Port_Y = y0 уходит в строку dst с * Port_Y = y1, дальше y0 растёт, а y1 УБЫВАЕТ: за h строк область * отражается сверху вниз. Ядро gfx_copy_page(GFX_COPY_VFLIP) — переворот * экрана для зелья инверсии (applications/PoP/docs/l9_invert_plan.md). * * Отличие от _bgi_scroll_rows_raw: там read и write идут на ОДНОМ Port_Y * (одна строка), поэтому OUT на строку один. Здесь строки разные, значит * Port_Y надо сменить МЕЖДУ read- и write-триггером — а это ровно тот * случай, где наивная схема ломается: accel перехватывает любое чтение * программной памяти, и fetch immediate-операнда OUT спурьезно * перезапустил бы read-burst, набив буфер кодом * (memory/accel_operand_fetch_retrigger). * * РЕШЕНИЕ — то же, что в _bgi_scroll_cols_raw (там оно уже в проде, * gfx_scroll_v): STOP (LD B,B) ПЕРЕД вторым OUT. Он разоружает accel * (m_acc_dir = 0), после чего операнд OUT безопасен; затем LD L,L снова * взводит горизонтальный режим — уже для записи. Порядок на строку: * {OUT y0 · arm · read · STOP · OUT y1 · arm · write · STOP} * Оба OUT выполняются только пока accel РАЗОРУЖЁН — это инвариант, на * котором всё держится; менять порядок нельзя. * * Убывающий y1 держим SMC-ячейкой, а не регистром: HL/DE заняты адресами, * B — счётчиком банды, C — растущим y0, и свободного 8-битного не * остаётся. Обновление ячейки (`dec a` + `ld (…),a`) стоит на строке * 17Т и идёт ПОСЛЕ STOP'а, то есть при разоружённом accel — безопасно. * * DI/EI — БАНДАМИ по 16 строк ВНУТРИ (как у scroll-ядер): держать DI на * весь проход нельзя (порвёт CBL-звук). Размер блока армируется в КАЖДОЙ * скобке — в окне EI между бандами CBL-ISR армирует accel своим размером. * * ВХОД (__sdcccall(1)): src->HL, dst->DE; стек: w 4(ix) — байт в строке * (размер accel-блока, 1..256, 0 = 256), h 5(ix) — строк 1..256 (0 = 256, * конвенция accel), y0 6(ix) — Port_Y ПЕРВОЙ строки src (инкремент), * y1 7(ix) — Port_Y ПЕРВОЙ строки dst (ДЕКРЕМЕНТ). Callee-pop 4 байта. * * Raw: клиппинга нет (вызывающий), W3 замаплен (_bgi_begin/_bgi_end * снаружи), банк 0x50 (запись бьёт VRAM + ОЗУ-копию — иначе перевёрнутый * фон не попадёт в копию и heal начнёт восстанавливать старую картинку). * Перекрытия src/dst нет by design — стороны в РАЗНЫХ видеостраницах. * Строку > 256 байт режет вызывающий. Клоббер: AF/BC; HL/DE/IX * сохраняются (LD L,L / LD D,D / LD B,B — accel-опкоды-no-op'ы). */ #include "../_bgi.h" void _bgi_flip_rows_raw(uint16_t src, uint16_t dst, uint8_t w, uint8_t h, uint8_t y0, uint8_t y1) __naked { (void)src; (void)dst; (void)w; (void)h; (void)y0; (void)y1; __asm push ix ld ix, #0 add ix, sp ;; HL = src, DE = dst (адреса-константы; строку задаёт Port_Y) ld a, 4 (ix) ld (fl_row_len), a ; SMC: размер блока <- w (0 = 256) ld (fl_row_len2), a ; ...и в хвостовую скобку ld a, 7 (ix) ld (fl_row_ydst), a ; SMC: Port_Y записи <- y1 (убывает) ld c, 6 (ix) ; C = бегущий Port_Y чтения (y0) ;; --- precompute: полных 16-банд (ix+5) + хвост (SMC); h=0 => 256 --- ld a, 5 (ix) and #0x0F ld (fl_row_tail), a ; хвост = h & 15 (0 и при h=0, и при h=256) ld a, 5 (ix) or a, a jr NZ, fl_row_shr ld a, #16 ; h=0 => 256 => ровно 16 полных банд jr fl_row_setfull fl_row_shr: rrca rrca rrca rrca and #0x0F ; full = h >> 4 fl_row_setfull: ld 5 (ix), a ; ix+5 = число полных 16-строчных банд fl_row_band: ld a, 5 (ix) or a, a jr Z, fl_row_tailband ; полные банды кончились dec 5 (ix) ; full -= 1 ld b, #16 ; 16 строк в банде di ld d, d ; 0x52 — режим размера блока (no-op для D) ld a, #0 fl_row_len = . - 1 ld b, b ; выключить — размер сохранён (B=16 цел) fl_row_next: ld a, c out (#0x89), a ; Port_Y = y0 (accel РАЗОРУЖЁН — операнд безопасен) inc c ; y0++ ld l, l ; 0x6D — арм гориз. копии ld a, (hl) ; read-burst: src-строка -> буфер ld b, b ; 0x40 — STOP (разоружить перед OUT y1) ld a, #0 fl_row_ydst = . - 1 out (#0x89), a ; Port_Y = y1 — accel разоружён dec a ld (fl_row_ydst), a ; y1-- (запись при разоружённом accel) ld l, l ; 0x6D — арм гориз. копии для записи ld (de), a ; write-burst: буфер -> dst-строка ld b, b ; 0x40 — стоп djnz fl_row_next ei ; окно прерываний между бандами jr fl_row_band ;; --- хвост h&15 строк: отдельная скобка со СВОИМ армированием --- fl_row_tailband: ;; y1 у хвоста — ПРОДОЛЖЕНИЕ, а не исходное значение: переносим ;; текущее из основной скобки (у неё своя immediate-ячейка). ld a, (fl_row_ydst) ld (fl_row_ydst2), a ld a, #0 fl_row_tail = . - 1 or a, a jr Z, fl_row_done ; хвоста нет ld b, a ; B = остаток строк (1..15) di ld d, d ld a, #0 fl_row_len2 = . - 1 ld b, b fl_row_trow: ld a, c out (#0x89), a inc c ld l, l ld a, (hl) ld b, b ld a, #0 fl_row_ydst2 = . - 1 out (#0x89), a dec a ld (fl_row_ydst2), a ld l, l ld (de), a ld b, b djnz fl_row_trow ei fl_row_done: pop ix ;; callee-pop 4 байта (w, h, y0, y1) pop hl ; ret-адрес pop af pop af jp (hl) __endasm; }