/* * _gfx_recthfill256 — заливка прямоугольника (x,y,w,h) ГОРИЗОНТАЛЬНЫМИ * строками (h выстрелов шириной w), mode 0x81. Chunked-схема: скобки * DI/EI по ≤16 строк, армирование размера один раз на скобку, на строку — * OUT Port_Y + LD C,C + выстрел (семантика мульти-триггера: * memory/accel_multitrigger_fill, проверено в MAME tests/accfill). * * Счётчик чанков — precompute (v3): один раз на входе E = h>>4 (полных * чанков, ≤16 при h<=256) и h&15 — хвост (SMC в ld b,#n хвостовой * скобки). Заголовок чанка = dec e/jr nz (~46Т на чанк ≈ 2.9Т/линию * против ~173Т/чанк у 16-бит арифметики через IX-слоты). Хвост — * отдельная скобка со СВОИМ армированием (второй SMC-слот размера): * армирование обязано быть в каждой скобке — в окне EI CBL-ISR * армирует акселератор своим размером блока. * * ---- БЮДЖЕТ (полный разбор: docs/accel-fill-budget.md) ------------- * подготовка ~340Т один раз на прямоугольник (пролог 102 + SMC 45 + * precompute ~110 + эпилог 54); чанк 16 строк — 46Т; строка — 51Т. * T(n) ≈ 340 + 46*(n/16) + 51*n; ~107 байт кода. Break-even против * простейшего per-line di/ei (djnz, 70Т/линию, ~53 байта): n ≈ 9 — * при n <= 8 линий простейший быстрее (максимум +140Т на n=1). * * Прямой вызов оправдан, когда форма известна заранее (широкие/низкие * прямоугольники, w > h) — экономит ~160-245Т проверки диспетчера * _gfx_rectfill256 (см. там же расчёт break-even). * * ---- КОНТРАКТ (ответственность вызывающего) ------------------------ * - 1 <= w <= 256 (w == 256 кодируется младшим байтом 0 — конвенция * акселератора; w == 0 даст 256!). Для w > 256 использовать * _gfx_rectvfill256 (или диспетчер). 0 <= h <= 256 (h == 0 — тихий * выход; старший байт h интерпретируется как «+16 чанков», т.е. * h > 511 — UB). Клиппинга НЕТ, координаты обязаны быть валидны. * - W3 замаплен (_bgi_begin у вызывающего). * * __sdcccall(1): x→HL, y→DE(E=y); w→4/5(ix), h→6/7(ix), color→8(ix); * __naked → callee-pops 5 байт. Инварианты в регистрах: HL=addr * (константа — строка задаётся Port_Y), C=цвет, D=бегущий y, * E=счётчик чанков, B=djnz внутри скобки. */ #include "../_bgi.h" void _gfx_recthfill256(int x, int y, int w, int h, uint8_t color) __naked { __asm .globl __gfx_addr_base push ix ld ix, #0 add ix, sp ld bc, (__gfx_addr_base) add hl, bc ; HL = base + x (константа) ld d, e ; D = бегущий y ld a, 8 (ix) ld c, a ; C = color ld a, 4 (ix) ld (rh_len), a ; SMC: размер блока <- w (0 = 256) ld (rh_len2), a ; ...и в хвостовую скобку ;; --- precompute (один раз): E = h>>4, хвост h&15 -> SMC --- ld a, 6 (ix) and #0x0F ld (rh_tail), a ; SMC: хвост <- h & 15 ld a, 6 (ix) rrca rrca rrca rrca and #0x0F ld e, a ; E = (h низкий) >> 4 ld a, 7 (ix) or a, a jr Z, rh_go ; h < 256 ld a, e add a, #16 ; старший байт: h==256 => ровно +16 чанков ld e, a rh_go: ld a, e or a, a jr Z, rh_tail_go ; полных чанков нет ;; --- E полных чанков по 16 строк --- ;; (армирование НЕ выносить из цикла: в окне EI CBL-ISR ;; армирует акселератор своим размером блока) rh_chunk: ld b, #16 ; строк в чанке di ld d, d ; 0x52 — режим размера блока (no-op для D=y) ld a, #0 rh_len = . - 1 ld b, b ; выключить — размер сохранён rh_row: ld a, d out (#0x89), a ; Port_Y = y (акселератор ВЫКЛЮЧЕН) ld c, c ; 0x49 — горизонтальный Fill (no-op для C) ld a, c ld (hl), a ; выстрел: строка w байт ld b, b ; выключить (перед djnz — fetch смещения!) inc d ; y++ djnz rh_row ei ; окно для прерываний между чанками dec e jr NZ, rh_chunk ;; --- хвост h&15 строк: отдельная скобка со СВОИМ армированием --- rh_tail_go: ld a, #0 rh_tail = . - 1 or a, a jr Z, rh_done ; хвоста нет ld b, a di ld d, d ld a, #0 rh_len2 = . - 1 ld b, b rh_trow: ld a, d out (#0x89), a ld c, c ld a, c ld (hl), a ld b, b inc d djnz rh_trow ei rh_done: pop ix pop hl pop af pop af inc sp jp (hl) __endasm; } #if 0 /* ================================================================ * Предыдущий вариант (v2, 2026-07-10, до precompute): решение * «16 или хвост» и вычитание h -= n на КАЖДЫЙ чанк через 16-бит * арифметику в IX-слотах (~141-189Т на чанк ≈ 11Т/линию). * Оставлен закомментированным для отката/сравнения. * ================================================================ */ void _gfx_recthfill256(int x, int y, int w, int h, uint8_t color) __naked { __asm .globl __gfx_addr_base push ix ld ix, #0 add ix, sp ld bc, (__gfx_addr_base) add hl, bc ; HL = base + x (константа) ld d, e ; D = бегущий y ld a, 8 (ix) ld c, a ; C = color ld a, 4 (ix) ld (rh_len), a ; SMC: размер блока <- w (0 = 256) rh_chunk: ld a, 7 (ix) or a, a jr NZ, rh_full ; h >= 256 -> чанк 16 ld a, 6 (ix) or a, a jr Z, rh_done ; h == 0 -> готово cp a, #16 jr NC, rh_full ld b, a ; хвост: n = h jr rh_go rh_full: ld b, #16 ; полный чанк rh_go: ld a, 6 (ix) ; h -= n sub a, b ld 6 (ix), a ld a, 7 (ix) sbc a, #0 ld 7 (ix), a ;; --- скобка: n строк одним армированием размера --- ;; (армирование НЕ выносить из цикла: в окне EI CBL-ISR ;; армирует акселератор своим размером блока) di ld d, d ; 0x52 — режим размера блока (no-op для D=y) ld a, #0 rh_len = . - 1 ld b, b ; выключить — размер сохранён rh_row: ld a, d out (#0x89), a ; Port_Y = y (акселератор ВЫКЛЮЧЕН) ld c, c ; 0x49 — горизонтальный Fill (no-op для C) ld a, c ld (hl), a ; выстрел: строка w байт ld b, b ; выключить (перед djnz — fetch смещения!) inc d ; y++ djnz rh_row ei ; окно для прерываний между чанками jr rh_chunk rh_done: pop ix pop hl pop af pop af inc sp jp (hl) __endasm; } #endif