Files
Sprinter-SDCC/libbgi/bgi256/_gfx_recthfill256.c
T
snark13 2c414da465 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>
2026-07-10 17:41:49 +03:00

200 lines
8.5 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* _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