libbgi: chunked-заливки на мульти-триггере акселератора + сплит rectfill

Семантика FSM акселератора вскрыта по драйверу MAME (sprinter.cpp) и
подтверждена экспериментами в MAME (tests/accfill): армирование живёт до
следующего accel-опкода (мульти-триггер работает); под армированием
триггерит ЛЮБОЙ non-M1 доступ к памяти (fetch операнда djnz/out!);
вертикальный Fill двигает Port_Y и не возвращает; размер блока переживает
LD B,B.  Детали: memory/accel_multitrigger_fill.

- _bgi_clear_raw: 20 DI-скобок по 16 колонок вместо полного брекета на
  каждую из 320 колонок; армирование размера один раз на скобку.
- _gfx_rectfill256 разделён: диспетчер (проверка ориентации ~160-245Т,
  break-even |w-h| >= ~4) + leaf'ы _gfx_recthfill256/_gfx_rectvfill256
  для прямого вызова, когда форма известна заранее.
- Leaf'ы: чанки <=16 линий одной скобкой (~54-56Т/линию против ~250Т у
  per-line варианта); счётчик чанков precompute'ится в байт-регистр
  (dec e/jr nz ~46Т/чанк вместо 16-бит арифметики в IX-слотах
  ~173Т/чанк); хвост — отдельная скобка со своим армированием (CBL-ISR
  в окне EI армирует акселератор своим размером — не выносить).
- getpixel/putpixel/_bgi_read: уборка мёртвого закомментированного кода.
- tests/accfill: регресс chunked-заливок (B0 clear, B1 vert 1+3 чанка,
  B2 horz чанк+хвост, полноширинный bar 320x8 — путь w>=256).

Прежние реализации сохранены под #if 0 для отката/сравнения.
Проверено в MAME (все PASS); семантика эмуляции — перепроверить на
реальном Sprinter.  size-baseline: accfill добавлен, gfx_demo +54 Б
(обвязка диспетчера), остальные без роста.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 17:41:49 +03:00
parent e553e5e6c9
commit 2c414da465
11 changed files with 617 additions and 49 deletions
+199
View File
@@ -0,0 +1,199 @@
/*
* _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-слоты); precompute
* ~170Т однократно. Итог ~54Т/линию. Хвост — отдельная скобка со
* СВОИМ армированием (второй SMC-слот размера): армирование обязано
* быть в каждой скобке — в окне EI CBL-ISR армирует акселератор своим
* размером блока.
*
* Прямой вызов оправдан, когда форма известна заранее (широкие/низкие
* прямоугольники, 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