Files
Sprinter-SDCC/docs/accel-fill-budget.md
T
snark13 580837d2ee docs: бюджет тактов/размеров chunked-заливок + идеи дальнейших упрощений
docs/accel-fill-budget.md: полный разбор v3 (подготовка ~340Т на
прямоугольник, 46Т на чанк 16 линий, 53Т/51Т на линию, ~107 байт/leaf)
против per-line di/ei вариантов (djnz 72Т/линию ~63 байта; 16-бит IX
206Т/линию); break-even n≈9 линий, тотализатор, решение остаться на v3.
Краткие выжимки — в шапки _gfx_rect*fill256.

Идеи на будущее (там же): ограничить контракт стороной <=256 (широкие
прямоугольники пользователь выводит в два приёма); реализовать оба
варианта (поблочный/построчный) с переключением опцией сборки в духе
GFX_NOCHECK.

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

109 lines
7.4 KiB
Markdown
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.
# Акселератор: бюджет тактов и размеров 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 байт (проверка ориентации 160–245Т,
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) и для программ с жёсткими требованиями к латентности
прерываний.