diff --git a/docs/accel-fill-budget.md b/docs/accel-fill-budget.md new file mode 100644 index 0000000..0716343 --- /dev/null +++ b/docs/accel-fill-budget.md @@ -0,0 +1,108 @@ +# Акселератор: бюджет тактов и размеров 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 | 108–118 | +| проверка «есть ли полные чанки» | 15–20 | +| эпилог: 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) | ~53–63 | пролог 18 + SMC 6 + счётчик 7 + линия 15 + эпилог 7 (+~10 двухпроходность vertical) | +| Б (IX) | ~70 | пролог 18 + SMC 6 + цикл 39 + эпилог 7 | + +Диспетчер `_gfx_rectfill256` ≈ 40 байт (проверка ориентации 160–245Т, +break-even |w−h| ≥ ~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) и для программ с жёсткими требованиями к латентности + прерываний. diff --git a/libbgi/bgi256/_gfx_rectfill256.c b/libbgi/bgi256/_gfx_rectfill256.c index e104841..5fdd16d 100644 --- a/libbgi/bgi256/_gfx_rectfill256.c +++ b/libbgi/bgi256/_gfx_rectfill256.c @@ -13,6 +13,7 @@ * прямоугольниках (|w−h| < 4) или когда форма известна заранее — * выгоднее прямой вызов _gfx_recthfill256 / _gfx_rectvfill256 * (контракты — в их шапках: recthfill требует 1<=w<=256). + * Полный бюджет тактов/размеров всех вариантов: docs/accel-fill-budget.md. * * __sdcccall(1): x→HL, y→DE(E=y); w→4/5(ix), h→6/7(ix), color→8(ix). * Диспетчер сохраняет HL/DE и стек как есть; leaf делает свой diff --git a/libbgi/bgi256/_gfx_recthfill256.c b/libbgi/bgi256/_gfx_recthfill256.c index f2bd8a5..038692a 100644 --- a/libbgi/bgi256/_gfx_recthfill256.c +++ b/libbgi/bgi256/_gfx_recthfill256.c @@ -8,11 +8,17 @@ * Счётчик чанков — 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 армирует акселератор своим - * размером блока. + * против ~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Т проверки диспетчера diff --git a/libbgi/bgi256/_gfx_rectvfill256.c b/libbgi/bgi256/_gfx_rectvfill256.c index c9b5be6..eb23b85 100644 --- a/libbgi/bgi256/_gfx_rectvfill256.c +++ b/libbgi/bgi256/_gfx_rectvfill256.c @@ -8,11 +8,17 @@ * Счётчик чанков — precompute (v3): один раз на входе E = w>>4 (полных * чанков, ≤20 при w<=320) и w&15 — хвост (SMC в ld b,#n хвостовой * скобки). Заголовок чанка = dec e/jr nz (~46Т на чанк ≈ 2.9Т/линию - * против ~173Т/чанк у 16-бит арифметики через IX-слоты); precompute - * ~170Т однократно. Итог ~56Т/линию. Хвост — отдельная скобка со - * СВОИМ армированием (второй SMC-слот размера): армирование обязано - * быть в каждой скобке — в окне EI CBL-ISR армирует акселератор своим - * размером блока. + * против ~173Т/чанк у 16-бит арифметики через IX-слоты). Хвост — + * отдельная скобка со СВОИМ армированием (второй SMC-слот размера): + * армирование обязано быть в каждой скобке — в окне EI CBL-ISR + * армирует акселератор своим размером блока. + * + * ---- БЮДЖЕТ (полный разбор: docs/accel-fill-budget.md) ------------- + * подготовка ~340Т один раз на прямоугольник (пролог 102 + SMC 45 + + * precompute ~110 + эпилог 54); чанк 16 колонок — 46Т; колонка — 53Т. + * T(n) ≈ 340 + 46*(n/16) + 53*n; ~107 байт кода. Break-even против + * простейшего per-line di/ei (djnz, 72Т/линию, ~63 байта): n ≈ 9 — + * при n <= 8 линий простейший быстрее (максимум +140Т на n=1). * * Прямой вызов оправдан, когда форма известна заранее (высокие/узкие * прямоугольники, w <= h) — экономит ~160-245Т проверки диспетчера