Files
Sprinter-SDCC/docs/accel-fill-budget.md
T
snark13 5f0c46f0ea docs: идея span-примитивов для узких прямоугольников (TODO + fill-budget)
При узкой стороне <= 8 линий chunked-rectfill проигрывает циклу
_bgi_hspan_raw/_bgi_vspan_raw (подготовка ~340Т впустую, break-even
n~9): либо fast-path в диспетчере, либо рецепт для пользователя —
решать по профилю.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 18:02:16 +03:00

8.5 KiB
Raw Blame History

Акселератор: бюджет тактов и размеров chunked-заливок (rectfill/clear)

2026-07-10, после рефактора 2c414da (chunked-заливки v3 на мульти-триггере акселератора). Семантика FSM акселератора — memory/accel_multitrigger_fill, регресс-тест — tests/accfill. Такты — стандартные Z80; пересчёт в мкс — для 21 МГц без учёта wait-state'ов видеопамяти.

Архитектура v3 (что считаем)

_gfx_rectvfill256 / _gfx_recthfill256: заливка чанками ≤16 линий на одну DI/EI-скобку. В скобке размер блока армируется один раз; на линию — OUT Port_Y (при выключенном акселераторе) + 1 опкод режима (LD E,E / LD C,C) + выстрел + LD B,B. Счётчик чанков precompute'ится один раз на входе в байт-регистр E (n>>4), хвост n&15 SMC-патчится в отдельную хвостовую скобку со своим армированием (в окне EI между чанками CBL-ISR армирует акселератор своим размером блока — поэтому армирование в каждой скобке).

Бюджет v3

1) Подготовка — один раз на прямоугольник (~325–340Т)

блок тактов
пролог: push ix / ld ix,#0 / add ix,sp + база+x + цвет 102
SMC размера в обе скобки (19+13+13) 45
precompute: хвост n&15 → SMC + чанки n>>4 → E 108118
проверка «есть ли полные чанки» 1520
эпилог: pop ix / 3×pop / inc sp / jp (hl) 54

2) На блок 16 линий — 46Т

ld b,#16 (7) + di (4) + арм ld d,d / ld a,#n / ld b,b (15) + ei (4) + dec e / jr nz (16) = 46Т ≈ 2.9Т/линию. Хвостовая скобка ≈ 45Т (заход 22 + арм 23).

3) На линию — 53Т (vertical) / 51Т (horizontal)

ld a,d (4) + out (11) + режим (4) + ld a,c (4) + ld (hl),a (7) + ld b,b (4) + inc hl (6) / inc d (4) + djnz (13).

Формула: T(n) ≈ 340 + 46·⌈n/16⌉ + 53·n (vertical; horizontal 51·n).

Альтернативы: каждая линия в своей DI/EI-скобке

А — байтовый счётчик в B + djnz (арм на линию, всё в одном цикле): подготовка 210Т, линия 72Т (h) / 74Т (v). Ограничение: счётчик ≤ 256 — для vertical с w до 320 нужен двухпроходный костыль (+10 байт). Бонус: DI-окно = 1 линия (≤37 мкс) — минимальная латентность прерываний.

Б — 16-бит счётчик в IX-слотах (стиль per-line v1, без call): подготовка ~190Т, линия ~206Т (135Т из них — декремент+проверка 16-бит числа через 19Т-обращения к IX-слотам).

Тотализатор (vertical, тактов на прямоугольник n линий)

линий v3 А (djnz) Б (IX)
1 ~420 ~280 ~395
4 ~580 ~500 ~1015
8 ~790 ~785 ~1840
9 ~845 ~855 ~2045
16 ~1215 ~1365 ~3490
64 ~3895 ~4820 ~13 375
256 ~14 620 ~18 640 ~52 930

Break-even v3 против А: n ≈ 9 линий (340+53n+2.9n = 210+72n → n≈8.3). При n ≤ 8 простейший А быстрее (максимум выигрыша +140Т ≈ 6.7 мкс на n=1 — precompute уходит впустую); при n ≥ 9 v3 впереди, дальше ~19Т/линию (~26% CPU-оверхеда; полный экран — экономия ~4000Т ≈ 190 мкс). Б проигрывает v3 уже с n = 2 — от него и уходили.

Размер кода (активные инструкции, на один leaf)

вариант байт разбивка
v3 (текущий) ~107 пролог 18 + SMC 9 + precompute 26 + вход 4 + чанк-цикл 21 + хвост 22 + эпилог 7
А (djnz) ~5363 пролог 18 + SMC 6 + счётчик 7 + линия 15 + эпилог 7 (+~10 двухпроходность vertical)
Б (IX) ~70 пролог 18 + SMC 6 + цикл 39 + эпилог 7

Диспетчер _gfx_rectfill256 ≈ 40 байт (проверка ориентации 160245Т, break-even |wh| ≥ ~4 линии — при известной форме звать leaf напрямую). Переход пары leaf'ов на А дал бы ~−100 байт на программу ценой −26% скорости на n ≥ 9.

Решение: оставлена v3 — в графическом коде такты дороже сотни байт; для узких прямоугольников (≤8 линий) есть span-примитивы _bgi_hspan_raw/_bgi_vspan_raw (одна линия одной скобкой без precompute).

Идеи (не реализовано)

  • Отказаться от поддержки сторон > 256 точек. Сейчас vertical тащит ветку «старший байт w = +16 чанков» (precompute) ради 257..320; если ограничить контракт стороной ≤ 256 и оставить пользователю выводить широкие прямоугольники в два приёма самому — упрощается precompute (~−15Т подготовки, −5..8 байт), исчезает UB-зона w>511, контракты обеих leaf-функций становятся симметричными. Цена: bar() на полную ширину экрана обязан сам разбить вызов на два (или это делает диспетчер — тогда экономии в диспетчерном пути нет, только в прямых вызовах leaf'ов).

  • Два варианта рисования + переключение опцией сборки. Реализовать оба: поблочный (v3, быстрый при n ≥ 9, DI-окно ≤0.7 мс) и построчный (А: per-line di/ei + djnz, 100 байт на пару leaf'ов, быстрый при n ≤ 8, DI-окно = 1 линия ≤37 мкс — идеально при активном CBL) — и выбирать опцией сборки в духе существующего GFX_NOCHECK / safe-fast вариантов libbgi. Кандидат для size-critical сборок (small 32КБ, как mdview2) и для программ с жёсткими требованиями к латентности прерываний.

  • Span-примитивы для узких прямоугольников (узкая сторона ≤ 8). Break-even n≈9 означает: прямоугольник в 2..8 линий по узкой стороне дешевле нарисовать циклом готовых span-примитивов (_bgi_hspan_raw/_bgi_vspan_raw — одна линия одной скобкой, без подготовки/precompute), чем через rectfill, где ~340Т подготовки уходят впустую. Варианты: fast-path в диспетчере _gfx_rectfill256 («узкая сторона ≤ 8 → цикл span'ов»; удорожает проверку диспетчера на ~20-30Т) или оставить на усмотрение пользователя, задокументировав рецепт. Делать по результатам профиля — если такие прямоугольники встречаются в горячем коде (рамки, тонкие полосы, разделители). Продублировано в docs/TODO.md (GFX: оптимизации).