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
+2 -1
View File
@@ -1,4 +1,5 @@
# Эталон размеров _CODE (байт); обновление: python3 toolchain/size_check.py --update # Эталон размеров _CODE (байт); обновление: python3 toolchain/size_check.py --update
accfill 3435
argv 3431 argv 3431
assrtest 3847 assrtest 3847
attrprob 4091 attrprob 4091
@@ -22,7 +23,7 @@ fdmax 6337
filetest 11624 filetest 11624
gets 509 gets 509
gfx_dbuf 4770 gfx_dbuf 4770
gfx_demo 4207 gfx_demo 4261
hello 4176 hello 4176
hello2 4251 hello2 4251
irqtest 5881 irqtest 5881
1 # Эталон размеров _CODE (байт); обновление: python3 toolchain/size_check.py --update
2 accfill
3 argv
4 assrtest
5 attrprob
23 filetest
24 gets
25 gfx_dbuf
26 gfx_demo
27 hello
28 hello2
29 irqtest
+8 -2
View File
@@ -102,9 +102,15 @@ void _gfx_hfill256_segment(void);
void _gfx_vfill256_segment(void); void _gfx_vfill256_segment(void);
/* Заливка прямоугольника цветом: x, y, w, h, color. /* Заливка прямоугольника цветом: x, y, w, h, color.
* Механизм: один раз задаётся Horizontal_Size, затем в цикле по Y * _gfx_rectfill256 — диспетчер: выбирает ориентацию с меньшим числом
* акселератор копирует байт цвета на ширину строки (docs/part2/accelerator_doc.txt). */ * выстрелов (~160-245Т на проверку) и tail-jump'ит в leaf. Если форма
* известна заранее или |w-h| < ~4, выгоднее звать leaf напрямую:
* _gfx_recthfill256 — h горизонтальных строк, ТРЕБУЕТ 1 <= w <= 256;
* _gfx_rectvfill256 — w вертикальных колонок, ТРЕБУЕТ 1 <= h <= 256.
* Контракты/расчёт break-even — в шапках bgi256/_gfx_rect*fill256.c. */
void _gfx_rectfill256(int x, int y, int w, int h, uint8_t color); void _gfx_rectfill256(int x, int y, int w, int h, uint8_t color);
void _gfx_recthfill256(int x, int y, int w, int h, uint8_t color);
void _gfx_rectvfill256(int x, int y, int w, int h, uint8_t color);
/* ---- Скретч 16-цветных примитивов (bgi16/_gfx_g16_state.c) ------- */ /* ---- Скретч 16-цветных примитивов (bgi16/_gfx_g16_state.c) ------- */
extern uint8_t _gfx_g16_y; extern uint8_t _gfx_g16_y;
+49 -1
View File
@@ -1,12 +1,59 @@
/* /*
* _bgi_clear_raw — залить всё поле 320×256 цветом color, mode 0x81. * _bgi_clear_raw — залить всё поле 320×256 цветом color, mode 0x81.
* 320 вертикальных Fill-burst'ов (по колонке 256 строк), Port_Y=0. *
* Chunked-схема (v2, 2026-07-10): 20 DI-скобок по 16 колонок (256 строк
* каждая) — армирование размера один раз на скобку, на колонку только
* ре-арм LD E,E + выстрел (см. _gfx_rectfill256.c и
* memory/accel_multitrigger_fill; проверено в MAME tests/accfill).
* Окно DI на чанк ≈ 0.6 мс (16×256 байт) — CBL/IM2 живут.
* Вертикальный Fill двигает Port_Y — re-OUT 0 перед каждой колонкой.
*
* Register-only asm, без глобального скретча. W3-naive: вызывающий * Register-only asm, без глобального скретча. W3-naive: вызывающий
* обрамляет _bgi_begin/_bgi_end. color → A (__sdcccall(1)). * обрамляет _bgi_begin/_bgi_end. color → A (__sdcccall(1)).
*/ */
#include "../_bgi.h" #include "../_bgi.h"
void _bgi_clear_raw(uint8_t color) __naked
{
__asm
.globl __gfx_addr_base
ld c, a ; C = цвет
ld hl, (__gfx_addr_base) ; HL = база draw-страницы
ld d, #0 ; y0 = 0
ld e, #20 ; 20 чанков × 16 колонок = 320
clr_chunk:
di
;; Армирование размера НЕ выносить из цикла (экономия ~0.1%):
;; в окне EI между чанками CBL-ISR сам армирует акселератор
;; своим размером блока каждый чанк обязан армировать заново.
ld d, d ; 0x52 режим размера блока (no-op для D)
ld a, #0 ; размер 256 (конвенция акселератора)
ld b, b ; выключить размер сохранён
ld b, #16 ; колонок в чанке
clr_col:
ld a, d
out (#0x89), a ; Port_Y = 0 (vertical Fill его сдвинул)
ld e, e ; 0x5B вертикальный Fill (no-op для E)
ld a, c
ld (hl), a ; выстрел: колонка 256 строк
ld b, b ; выключить (перед djnz fetch смещения!)
inc hl ; следующая колонка
djnz clr_col
ei ; окно для прерываний между чанками
dec e
jr NZ, clr_chunk
ret
__endasm;
}
#if 0
/* ================================================================
* Старый вариант (до 2026-07-10): полный брекет (DI+арм размера+
* режим+выстрел+выкл+EI) на КАЖДУЮ из 320 колонок. Оставлен
* закомментированным для отката/сравнения.
* ================================================================ */
void _bgi_clear_raw(uint8_t color) __naked void _bgi_clear_raw(uint8_t color) __naked
{ {
__asm __asm
@@ -39,3 +86,4 @@ void _bgi_clear_raw(uint8_t color) __naked
jr _clear_cyc0 jr _clear_cyc0
__endasm; __endasm;
} }
#endif
+61 -8
View File
@@ -1,19 +1,71 @@
/* /*
* _gfx_rectfill256 — заливка прямоугольника (x,y,w,h) цветом color, mode 0x81. * _gfx_rectfill256 — диспетчер заливки прямоугольника (x,y,w,h,color),
* mode 0x81: выбирает ориентацию с меньшим числом выстрелов и передаёт
* управление leaf-заливке (tail-jump, стек не трогается — ABI идентичен):
* w>h && w<=256 → _gfx_recthfill256 (h горизонтальных строк)
* иначе → _gfx_rectvfill256 (w вертикальных колонок)
* *
* Выбор ориентации burst'а: если w>h и w<=256 — гоним h горизонтальных * ---- ЦЕНА ПРОВЕРКИ и когда звать leaf'ы напрямую -------------------
* Fill'ов шириной w (y++ на каждой строке); иначе w вертикальных Fill'ов * Проверка + tail-jump: ~160Т (быстрый путь w<h) … ~245Т (w>256),
* высотой h (addr++ на каждой колонке). Инварианты (color=C, len=B) и * типично ~200Т ≈ 9.5 мкс @21МГц. Один выстрел-линия стоит ~55-65Т
* бегущий addr=HL/y=E живут в регистрах через весь цикл — _gfx_*_segment * CPU-оверхеда, т.е. проверка окупается, когда выбор ориентации
* их сохраняет; счётчик другой размерности крутится в его стек-слоте. * экономит |w−h| >= ~4 линии. На мелких/почти квадратных
* прямоугольниках (|w−h| < 4) или когда форма известна заранее —
* выгоднее прямой вызов _gfx_recthfill256 / _gfx_rectvfill256
* (контракты — в их шапках: recthfill требует 1<=w<=256).
* *
* __sdcccall(1): x→HL, y→DE(E=y); w→4/5(ix), h→6/7(ix), color→8(ix). * __sdcccall(1): x→HL, y→DE(E=y); w→4/5(ix), h→6/7(ix), color→8(ix).
* Pre: W3 замаплен, DI активен вокруг выстрелов внутри segment'ов. * Диспетчер сохраняет HL/DE и стек как есть; leaf делает свой
* Вызывающий управляет _bgi_begin/_bgi_end. w,h предполагаются >= 0. * push ix и callee-pops. Pre/контракты — как у leaf'ов (W3 замаплен,
* без клиппинга, w,h >= 0).
*/ */
#include "../_bgi.h" #include "../_bgi.h"
void _gfx_rectfill256(int x, int y, int w, int h, uint8_t color) __naked
{
__asm
.globl __gfx_recthfill256
.globl __gfx_rectvfill256
push ix
ld ix, #0
add ix, sp ; только чтобы прочитать w/h; HL/DE не трогаем
;; --- w>h && w<=256 ? (unsigned, w/h >= 0) ---
ld a, 4 (ix)
sub a, 6 (ix)
ld b, a ; (w-h) низкий
ld a, 5 (ix)
sbc a, 7 (ix) ; CF=1 если w<h
jr C, disp_v
or a, b
jr Z, disp_v ; w==h -> не (w>h)
ld a, 5 (ix) ; w high
cp a, #2
jr NC, disp_v ; w >= 512
dec a
jr NZ, disp_h ; high==0 -> w<256
ld a, 4 (ix)
or a, a
jr NZ, disp_v ; high==1,low!=0 -> w>256
; w==256 -> горизонталь (низкий байт 0 = «256»)
disp_h:
pop ix
jp __gfx_recthfill256 ; tail-jump: аргументы на стеке как есть
disp_v:
pop ix
jp __gfx_rectvfill256
__endasm;
}
#if 0
/* ================================================================
* Старый вариант (до 2026-07-10): полный брекет (арм+выстрел+выкл)
* на КАЖДУЮ линию через call _gfx_*fill256_segment — ~100Т+call на
* линию. Оставлен закомментированным для отката/сравнения.
* (Промежуточный monolith-вариант с chunked-ветками жил здесь
* 2026-07-10 и разъехался по _gfx_recthfill256.c/_gfx_rectvfill256.c.)
* ================================================================ */
void _gfx_rectfill256(int x, int y, int w, int h, uint8_t color) __naked void _gfx_rectfill256(int x, int y, int w, int h, uint8_t color) __naked
{ {
__asm __asm
@@ -89,3 +141,4 @@ void _gfx_rectfill256(int x, int y, int w, int h, uint8_t color) __naked
jp (hl) jp (hl)
__endasm; __endasm;
} }
#endif
+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
+198
View File
@@ -0,0 +1,198 @@
/*
* _gfx_rectvfill256 — заливка прямоугольника (x,y,w,h) ВЕРТИКАЛЬНЫМИ
* колонками (w выстрелов высотой h), mode 0x81. Chunked-схема: скобки
* DI/EI по ≤16 колонок, армирование размера один раз на скобку, на
* колонку — re-OUT Port_Y + LD E,E + выстрел (семантика мульти-триггера:
* memory/accel_multitrigger_fill, проверено в MAME tests/accfill).
*
* Счётчик чанков — 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 армирует акселератор своим
* размером блока.
*
* Прямой вызов оправдан, когда форма известна заранее (высокие/узкие
* прямоугольники, w <= h) — экономит ~160-245Т проверки диспетчера
* _gfx_rectfill256 (см. там же расчёт break-even).
*
* ---- КОНТРАКТ (ответственность вызывающего) ------------------------
* - 1 <= h <= 256 (h == 256 кодируется младшим байтом 0 — конвенция
* акселератора; h == 0 даст 256!). 0 <= w <= 320 (w == 0 — тихий
* выход; старший байт w интерпретируется как «+16 чанков», т.е.
* w > 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,
* C=цвет, D=y0 (константа; vertical Fill двигает Port_Y — re-OUT
* обязателен), E=счётчик чанков, B=djnz внутри скобки.
*/
#include "../_bgi.h"
void _gfx_rectvfill256(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 = y0 (константа)
ld a, 8 (ix)
ld c, a ; C = color
ld a, 6 (ix)
ld (rv_len), a ; SMC: размер блока <- h (0 = 256)
ld (rv_len2), a ; ...и в хвостовую скобку
;; --- precompute (один раз): E = w>>4, хвост w&15 -> SMC ---
ld a, 4 (ix)
and #0x0F
ld (rv_tail), a ; SMC: хвост <- w & 15
ld a, 4 (ix)
rrca
rrca
rrca
rrca
and #0x0F
ld e, a ; E = (w низкий) >> 4
ld a, 5 (ix)
or a, a
jr Z, rv_go ; w < 256
ld a, e
add a, #16 ; старший байт: w<=320 => ровно +16 чанков
ld e, a
rv_go:
ld a, e
or a, a
jr Z, rv_tail_go ; полных чанков нет
;; --- E полных чанков по 16 колонок ---
;; (армирование НЕ выносить из цикла: в окне EI CBL-ISR
;; армирует акселератор своим размером блока)
rv_chunk:
ld b, #16 ; колонок в чанке
di
ld d, d ; 0x52 режим размера блока (no-op для D=y0)
ld a, #0
rv_len = . - 1
ld b, b ; выключить размер сохранён
rv_col:
ld a, d
out (#0x89), a ; Port_Y = y0 (vertical Fill его сдвинул)
ld e, e ; 0x5B вертикальный Fill (no-op для E-счётчика)
ld a, c
ld (hl), a ; выстрел: колонка h строк
ld b, b ; выключить (перед djnz fetch смещения!)
inc hl ; следующая колонка
djnz rv_col
ei ; окно для прерываний между чанками
dec e
jr NZ, rv_chunk
;; --- хвост w&15 колонок: отдельная скобка со СВОИМ армированием ---
rv_tail_go:
ld a, #0
rv_tail = . - 1
or a, a
jr Z, rv_done ; хвоста нет
ld b, a
di
ld d, d
ld a, #0
rv_len2 = . - 1
ld b, b
rv_tcol:
ld a, d
out (#0x89), a
ld e, e
ld a, c
ld (hl), a
ld b, b
inc hl
djnz rv_tcol
ei
rv_done:
pop ix
pop hl
pop af
pop af
inc sp
jp (hl)
__endasm;
}
#if 0
/* ================================================================
* Предыдущий вариант (v2, 2026-07-10, до precompute): решение
* «16 или хвост» и вычитание w -= n на КАЖДЫЙ чанк через 16-бит
* арифметику в IX-слотах (~141-189Т на чанк ≈ 11Т/линию).
* Оставлен закомментированным для отката/сравнения.
* ================================================================ */
void _gfx_rectvfill256(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 = y0 (константа)
ld a, 8 (ix)
ld c, a ; C = color
ld a, 6 (ix)
ld (rv_len), a ; SMC: размер блока <- h (0 = 256)
rv_chunk:
ld a, 5 (ix)
or a, a
jr NZ, rv_full ; w >= 256 -> чанк 16
ld a, 4 (ix)
or a, a
jr Z, rv_done ; w == 0 -> готово
cp a, #16
jr NC, rv_full
ld b, a ; хвост: n = w
jr rv_go
rv_full:
ld b, #16 ; полный чанк
rv_go:
ld a, 4 (ix) ; w -= n
sub a, b
ld 4 (ix), a
ld a, 5 (ix)
sbc a, #0
ld 5 (ix), a
;; --- скобка: n колонок одним армированием размера ---
;; (армирование НЕ выносить из цикла: в окне EI CBL-ISR
;; армирует акселератор своим размером блока)
di
ld d, d ; 0x52 — режим размера блока (no-op для D=y0)
ld a, #0
rv_len = . - 1
ld b, b ; выключить — размер сохранён
rv_col:
ld a, d
out (#0x89), a ; Port_Y = y0 (vertical Fill его сдвинул)
ld e, e ; 0x5B — вертикальный Fill (no-op для E)
ld a, c
ld (hl), a ; выстрел: колонка h строк
ld b, b ; выключить (перед djnz — fetch смещения!)
inc hl ; следующая колонка
djnz rv_col
ei ; окно для прерываний между чанками
jr rv_chunk
rv_done:
pop ix
pop hl
pop af
pop af
inc sp
jp (hl)
__endasm;
}
#endif
-18
View File
@@ -18,22 +18,4 @@ color_t getpixel(int x, int y)
register uint8_t _gp_color = _bgi_read_raw(x, y); register uint8_t _gp_color = _bgi_read_raw(x, y);
_bgi_end(); _bgi_end();
return _gp_color; return _gp_color;
// _gp_y = (uint8_t)y;
// _gp_addr = (uint16_t)(_gfx_addr_base + (unsigned)x);
// __asm
// di
// in a, (#0xE2)
// ld (__gfx_saved_w3), a
// ld a, (__gfx_bank)
// out (#0xE2), a
// ld a, (_gp_y)
// out (#0x89), a
// ld hl, (_gp_addr)
// ld a, (hl)
// ld (_gp_color), a
// ld a, (__gfx_saved_w3)
// out (#0xE2), a
// ei
// __endasm;
// return _gp_color;
} }
-18
View File
@@ -24,22 +24,4 @@ void putpixel(int x, int y, color_t color)
_bgi_plot_raw(x, y, color); _bgi_plot_raw(x, y, color);
_bgi_end(); _bgi_end();
return; return;
// _pp_y = (uint8_t)y;
// _pp_addr = (uint16_t)(_gfx_addr_base + (unsigned)x);
// _pp_color = (uint8_t)color;
// __asm
// di
// in a, (#0xE2)
// ld (__gfx_saved_w3), a
// ld a, (__gfx_bank)
// out (#0xE2), a
// ld a, (_pp_y)
// out (#0x89), a
// ld hl, (_pp_addr)
// ld a, (_pp_color)
// ld (hl), a
// ld a, (__gfx_saved_w3)
// out (#0xE2), a
// ei
// __endasm;
} }
+1 -1
View File
@@ -8,7 +8,7 @@
unsigned _bgi_read(int x, int y) unsigned _bgi_read(int x, int y)
{ {
unsigned c; register unsigned c;
_bgi_begin(); _bgi_begin();
c = _bgi_read_raw(x, y); c = _bgi_read_raw(x, y);
_bgi_end(); _bgi_end();
+4
View File
@@ -0,0 +1,4 @@
PROJ_ROOT := $(abspath $(CURDIR)/../..)
EXAMPLE := accfill
EXTRA_FLAGS ?= --gfx 256
include $(PROJ_ROOT)/app.mk
+95
View File
@@ -0,0 +1,95 @@
/*
* accfill — регресс chunked-заливок libbgi на мульти-триггере акселератора
* (B0: cleardevice/_bgi_clear_raw; B1: bar → _gfx_rectvfill256, пути
* «1 чанк», «2 чанка + хвост»; B2: bar → _gfx_recthfill256, «чанк + хвост»;
* плюс полноширинный bar 320×8 — precompute-путь w>=256).
*
* Семантика FSM (из драйвера MAME src/mame/sinclair/sprinter.cpp;
* согласуется с примерами docs/part2/accelerator_doc.txt; сырые
* asm-эксперименты E1-E3, подтвердившие её, удалены — см. git history
* 2026-07-10 и memory/accel_multitrigger_fill):
* - армирование (LD C,C / LD E,E / ...) живёт до следующего accel-опкода —
* мульти-триггер поддерживается;
* - под армированием триггерит ЛЮБОЙ non-M1 доступ к памяти: в т.ч. fetch
* immediate/displacement (djnz/jr/ld a,#n) и стек → между триггерами
* допустимы ТОЛЬКО регистровые M1-опкоды, либо выключать (LD B,B);
* - вертикальный Fill двигает Port_Y (+len) и НЕ возвращает; адрес
* константен. Горизонтальный — двигает адрес, Port_Y на месте;
* - OUT — I/O, акселератор не триггерит (но fetch операнда порта — да,
* поэтому OUT делать при выключенном акселераторе);
* - размер блока хранится во внутреннем регистре и переживает LD B,B —
* армировать размер достаточно один раз на скобку.
*
* Верификация: getpixel по всем линиям прямоугольника + нетронутый фон
* в 1px вокруг. Результат (PASS/FAIL) печатается на экране.
*/
#include <graphics.h>
/* Прямоугольник (x0,y0,w,h) залит col целиком, а 1px вокруг — фон (0)?
* Сканируем все колонки в трёх строках и все строки в трёх колонках —
* ловит пропуск отдельного триггера (дырку в колонке/строке). */
static uint8_t rect_ok(int x0, int y0, int w, int h, color_t col)
{
int i;
for (i = 0; i < w; i++) {
if (getpixel(x0 + i, y0) != col) return 0;
if (getpixel(x0 + i, y0 + h / 2) != col) return 0;
if (getpixel(x0 + i, y0 + h - 1) != col) return 0;
}
for (i = 0; i < h; i++) {
if (getpixel(x0, y0 + i) != col) return 0;
if (getpixel(x0 + w / 2, y0 + i) != col) return 0;
if (getpixel(x0 + w - 1, y0 + i) != col) return 0;
}
/* вокруг — нетронутый фон */
if (getpixel(x0 - 1, y0) != 0) return 0;
if (getpixel(x0 + w, y0) != 0) return 0;
if (getpixel(x0, y0 - 1) != 0) return 0;
if (getpixel(x0, y0 + h) != 0) return 0;
return 1;
}
int main(void)
{
uint8_t b0;
initgraph();
cleardevice();
/* B0: cleardevice (chunked _bgi_clear_raw) — углы и центр чистые */
b0 = getpixel(0, 0) == 0 && getpixel(319, 0) == 0 &&
getpixel(0, 255) == 0 && getpixel(319, 255) == 0 &&
getpixel(160, 128) == 0;
setcolor(WHITE);
outtextxy(8, 8, "ACCEL multi-trigger fill test v3");
/* B1: bar → вертикальная ветка rectfill: 1 чанк (16 кол.) и 3 чанка (40) */
setfillstyle(SOLID_FILL, LIGHTBLUE);
bar(16, 60, 31, 123); /* 16×64 */
setfillstyle(SOLID_FILL, YELLOW);
bar(230, 60, 269, 119); /* 40×60 */
/* B2: bar → горизонтальная ветка rectfill: 2 чанка (20 строк) */
setfillstyle(SOLID_FILL, LIGHTMAGENTA);
bar(200, 18, 299, 37); /* 100×20 */
bar(0, 8, 319, 15); /* 320×8 */
outtextxy(8, 132, b0
? "B0 cleardevice chunked: PASS"
: "B0 cleardevice chunked: FAIL");
outtextxy(8, 144, (rect_ok(16, 60, 16, 64, LIGHTBLUE) &&
rect_ok(230, 60, 40, 60, YELLOW))
? "B1 bar vert 1+3 chunks: PASS"
: "B1 bar vert 1+3 chunks: FAIL");
outtextxy(8, 156, rect_ok(200, 18, 100, 20, LIGHTMAGENTA)
? "B2 bar horz 2 chunks: PASS"
: "B2 bar horz 2 chunks: FAIL");
outtextxy(8, 208, "done");
for (;;) { }
}