95c22be9bd
Скролл региона video->video (неактивная страница -> активная, банк 0x50: копия = скролл + heal цели): - gfx_scroll_h / _bgi_scroll_rows_raw — горизонтальный, построчно без страйдов (~54Т/строку), DI/EI бандами по 16 строк, h=0=>256; - gfx_scroll_v / _bgi_scroll_cols_raw — верт. И/ИЛИ гориз. за один проход без буфера (колонка = accel-burst LD A,A, STOP между read/write делает промежуточный OUT Port_Y безопасным), банды по 16 колонок; - _gfx_addr_shadow_base (адрес неактивной страницы) + gfx_rect_t. Пример examples/scroll. check_banks.py + sprinter-cc: отчёт раскладки памяти для ЛЮБОЙ модели (W1/W2 код/данные, остаток кучи/стека, W3 при --w3, банки при --bank), не только при --bank. Док docs/memory-management.md §10. --w3 (резидентный код окна W3) + сопутствующее: crt0_banked W3_RESIDENT, mkexe -W, tests/w3probe. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
130 lines
6.9 KiB
C
130 lines
6.9 KiB
C
/*
|
||
* _bgi_scroll_rows_raw — БЫСТРАЯ построчная копия video->video со сдвигом
|
||
* ТОЛЬКО по X для gfx_scroll_h (mode 0x81). Специализация _bgi_copy_rows_raw
|
||
* для скролла: обе стороны — видео, ОБА страйда НУЛЕВЫЕ (CPU-адрес строки
|
||
* фиксирован, строку выбирает Port_Y), поэтому адресной арифметики в цикле
|
||
* НЕТ ВООБЩЕ — ни страйдовых add (как у copy_rows), ни даже inc адресов
|
||
* (как у heal). От _bgi_heal_rows_raw отличается тем, что src != dst
|
||
* (HL — колонка-старт src-страницы, DE — dst-страницы со сдвигом dx):
|
||
* горизонтальный сдвиг целиком в разнице адресов, задаётся вызывающим.
|
||
*
|
||
* Вертикального сдвига НЕТ (read и write одной строки на одном Port_Y) —
|
||
* для сдвига по Y есть _bgi_scroll_cols_raw (колонки, вертикальный режим).
|
||
* Зато rows на ~5-7% быстрее cols: на строку один OUT Port_Y (54Т/строку),
|
||
* а cols нужен OUT на чтение И на запись колонки.
|
||
*
|
||
* ПОРЯДОК на строку: {OUT Port_Y · arm(LD L,L) · read(LD A,(HL)) ·
|
||
* write(LD (DE),A) · STOP(LD B,B)}. Между read- и write-триггером —
|
||
* НИЧЕГО: на Sprinter accel перехватывает любое чтение программной памяти,
|
||
* а fetch операнда любой инструкции там (immediate у OUT/ADD) спурьезно
|
||
* перезапустил бы read-burst и набил буфер кодом. Port_Y ставится ДО
|
||
* arm'а (accel разоружён STOP'ом прошлой строки — операнд OUT безопасен).
|
||
* (memory/accel_operand_fetch_retrigger.)
|
||
*
|
||
* DI/EI — БАНДАМИ по 16 строк ВНУТРИ (как _gfx_recthfill256): накрывать DI
|
||
* весь сдвиг нельзя (порвёт CBL-звук). Армирование размера блока — в
|
||
* КАЖДОЙ скобке (в окне EI CBL-ISR армирует accel своим размером). Число
|
||
* полных 16-банд и хвост считаем ОДИН раз на входе (precompute) — в петле
|
||
* банд остаётся дешёвый dec (без per-band `sub 16` через IX-слот).
|
||
* Регистры упёрты (HL src, DE dst, B djnz, C бегущий Port_Y) — быстрее уже
|
||
* некуда: out(c) без стопов потребовал бы Port_Y-регистра, которого нет.
|
||
*
|
||
* ВХОД (__sdcccall(1)): src->HL, dst->DE; стек: w 4(ix) — байт в строке
|
||
* (размер accel-блока, 1..256, 0=256), h 5(ix) — строк 1..256 (0 = 256,
|
||
* КОНВЕНЦИЯ accel — как у w), y0 6(ix) — стартовый Port_Y (инкремент на
|
||
* строку). Callee-pop 3 байта.
|
||
*
|
||
* Raw: клиппинга нет (вызывающий), W3 замаплен (_bgi_begin/end снаружи),
|
||
* банк 0x50 (запись бьёт VRAM+тень — скролл И heal цели). Перекрытия
|
||
* src/dst нет by design — стороны в РАЗНЫХ видеостраницах. Строку >256
|
||
* байт режет вызывающий. Клоббер: AF/BC; HL/DE/IX сохраняются
|
||
* (адреса-константы не трогаются — LD L,L/LD D,D это accel-опкоды-no-op'ы).
|
||
*/
|
||
|
||
#include "../_bgi.h"
|
||
|
||
void _bgi_scroll_rows_raw(uint16_t src, uint16_t dst,
|
||
uint8_t w, uint8_t h, uint8_t y0) __naked {
|
||
(void)src; (void)dst; (void)w; (void)h; (void)y0;
|
||
__asm
|
||
push ix
|
||
ld ix, #0
|
||
add ix, sp
|
||
;; HL = src, DE = dst (адреса-константы; строку задаёт Port_Y)
|
||
ld a, 4 (ix)
|
||
ld (sc_row_len), a ; SMC: размер блока <- w (0 = 256)
|
||
ld (sc_row_len2), a ; ...и в хвостовую скобку
|
||
ld c, 6 (ix) ; C = бегущий Port_Y (y0)
|
||
|
||
;; --- precompute: полных 16-банд (ix+5) + хвост (SMC); h=0 => 256 ---
|
||
ld a, 5 (ix)
|
||
and #0x0F
|
||
ld (sc_row_tail), a ; хвост = h & 15 (0 и при h=0, и при h=256)
|
||
ld a, 5 (ix)
|
||
or a, a
|
||
jr NZ, sc_row_shr
|
||
ld a, #16 ; h=0 => 256 => ровно 16 полных банд
|
||
jr sc_row_setfull
|
||
sc_row_shr:
|
||
rrca
|
||
rrca
|
||
rrca
|
||
rrca
|
||
and #0x0F ; full = h >> 4
|
||
sc_row_setfull:
|
||
ld 5 (ix), a ; ix+5 = число полных 16-строчных банд
|
||
|
||
sc_row_band:
|
||
ld a, 5 (ix)
|
||
or a, a
|
||
jr Z, sc_row_tailband ; полные банды кончились
|
||
dec 5 (ix) ; full -= 1
|
||
ld b, #16 ; 16 строк в банде
|
||
di
|
||
ld d, d ; 0x52 — режим размера блока (no-op для D)
|
||
ld a, #0
|
||
sc_row_len = . - 1
|
||
ld b, b ; выключить — размер сохранён (B=16 цел)
|
||
sc_row_next:
|
||
ld a, c
|
||
out (#0x89), a ; Port_Y = y (accel ВЫКЛЮЧЕН — операнд безопасен)
|
||
inc c ; y++
|
||
ld l, l ; 0x6D — арм гориз. копии (no-op для L)
|
||
ld a, (hl) ; read-burst: src-строка -> буфер
|
||
ld (de), a ; write-burst: буфер -> dst-строка (вплотную!)
|
||
ld b, b ; 0x40 — стоп
|
||
djnz sc_row_next
|
||
ei ; окно прерываний между бандами
|
||
jr sc_row_band
|
||
;; --- хвост h&15 строк: отдельная скобка со СВОИМ армированием ---
|
||
sc_row_tailband:
|
||
ld a, #0
|
||
sc_row_tail = . - 1
|
||
or a, a
|
||
jr Z, sc_row_done ; хвоста нет
|
||
ld b, a ; B = остаток строк (1..15)
|
||
di
|
||
ld d, d
|
||
ld a, #0
|
||
sc_row_len2 = . - 1
|
||
ld b, b
|
||
sc_row_trow:
|
||
ld a, c
|
||
out (#0x89), a
|
||
inc c
|
||
ld l, l
|
||
ld a, (hl)
|
||
ld (de), a
|
||
ld b, b
|
||
djnz sc_row_trow
|
||
ei
|
||
sc_row_done:
|
||
pop ix
|
||
;; callee-pop 3 байта (w, h, y0)
|
||
pop hl ; ret-адрес
|
||
pop af
|
||
inc sp
|
||
jp (hl)
|
||
__endasm;
|
||
}
|