From 2c414da46550d97aee9875b9d0096ac9ed7dee03 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Fri, 10 Jul 2026 17:41:49 +0300 Subject: [PATCH] =?UTF-8?q?libbgi:=20chunked-=D0=B7=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B2=D0=BA=D0=B8=20=D0=BD=D0=B0=20=D0=BC=D1=83=D0=BB=D1=8C?= =?UTF-8?q?=D1=82=D0=B8-=D1=82=D1=80=D0=B8=D0=B3=D0=B3=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=20=D0=B0=D0=BA=D1=81=D0=B5=D0=BB=D0=B5=D1=80=D0=B0=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B0=20+=20=D1=81=D0=BF=D0=BB=D0=B8=D1=82=20rectfill?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Семантика 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 --- docs/size_baseline.tsv | 3 +- libbgi/_bgi.h | 10 +- libbgi/bgi256/_bgi_clear_raw.c | 50 +++++++- libbgi/bgi256/_gfx_rectfill256.c | 69 +++++++++-- libbgi/bgi256/_gfx_recthfill256.c | 199 ++++++++++++++++++++++++++++++ libbgi/bgi256/_gfx_rectvfill256.c | 198 +++++++++++++++++++++++++++++ libbgi/bgi256/getpixel.c | 18 --- libbgi/bgi256/putpixel.c | 18 --- libbgi/common/_bgi_read.c | 2 +- tests/accfill/Makefile | 4 + tests/accfill/accfill.c | 95 ++++++++++++++ 11 files changed, 617 insertions(+), 49 deletions(-) create mode 100644 libbgi/bgi256/_gfx_recthfill256.c create mode 100644 libbgi/bgi256/_gfx_rectvfill256.c create mode 100644 tests/accfill/Makefile create mode 100644 tests/accfill/accfill.c diff --git a/docs/size_baseline.tsv b/docs/size_baseline.tsv index 174b01c..857774d 100644 --- a/docs/size_baseline.tsv +++ b/docs/size_baseline.tsv @@ -1,4 +1,5 @@ # Эталон размеров _CODE (байт); обновление: python3 toolchain/size_check.py --update +accfill 3435 argv 3431 assrtest 3847 attrprob 4091 @@ -22,7 +23,7 @@ fdmax 6337 filetest 11624 gets 509 gfx_dbuf 4770 -gfx_demo 4207 +gfx_demo 4261 hello 4176 hello2 4251 irqtest 5881 diff --git a/libbgi/_bgi.h b/libbgi/_bgi.h index 176065e..8205397 100644 --- a/libbgi/_bgi.h +++ b/libbgi/_bgi.h @@ -102,9 +102,15 @@ void _gfx_hfill256_segment(void); void _gfx_vfill256_segment(void); /* Заливка прямоугольника цветом: x, y, w, h, color. - * Механизм: один раз задаётся Horizontal_Size, затем в цикле по Y - * акселератор копирует байт цвета на ширину строки (docs/part2/accelerator_doc.txt). */ + * _gfx_rectfill256 — диспетчер: выбирает ориентацию с меньшим числом + * выстрелов (~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_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) ------- */ extern uint8_t _gfx_g16_y; diff --git a/libbgi/bgi256/_bgi_clear_raw.c b/libbgi/bgi256/_bgi_clear_raw.c index 2f89870..fc2bf6d 100644 --- a/libbgi/bgi256/_bgi_clear_raw.c +++ b/libbgi/bgi256/_bgi_clear_raw.c @@ -1,12 +1,59 @@ /* * _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: вызывающий * обрамляет _bgi_begin/_bgi_end. color → A (__sdcccall(1)). */ #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 { __asm @@ -39,3 +86,4 @@ void _bgi_clear_raw(uint8_t color) __naked jr _clear_cyc0 __endasm; } +#endif diff --git a/libbgi/bgi256/_gfx_rectfill256.c b/libbgi/bgi256/_gfx_rectfill256.c index f396b74..e104841 100644 --- a/libbgi/bgi256/_gfx_rectfill256.c +++ b/libbgi/bgi256/_gfx_rectfill256.c @@ -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 горизонтальных - * Fill'ов шириной w (y++ на каждой строке); иначе w вертикальных Fill'ов - * высотой h (addr++ на каждой колонке). Инварианты (color=C, len=B) и - * бегущий addr=HL/y=E живут в регистрах через весь цикл — _gfx_*_segment - * их сохраняет; счётчик другой размерности крутится в его стек-слоте. + * ---- ЦЕНА ПРОВЕРКИ и когда звать leaf'ы напрямую ------------------- + * Проверка + tail-jump: ~160Т (быстрый путь w256), + * типично ~200Т ≈ 9.5 мкс @21МГц. Один выстрел-линия стоит ~55-65Т + * 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). - * Pre: W3 замаплен, DI активен вокруг выстрелов внутри segment'ов. - * Вызывающий управляет _bgi_begin/_bgi_end. w,h предполагаются >= 0. + * Диспетчер сохраняет HL/DE и стек как есть; leaf делает свой + * push ix и callee-pops. Pre/контракты — как у leaf'ов (W3 замаплен, + * без клиппинга, w,h >= 0). */ #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 не (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 { __asm @@ -89,3 +141,4 @@ void _gfx_rectfill256(int x, int y, int w, int h, uint8_t color) __naked jp (hl) __endasm; } +#endif diff --git a/libbgi/bgi256/_gfx_recthfill256.c b/libbgi/bgi256/_gfx_recthfill256.c new file mode 100644 index 0000000..f2bd8a5 --- /dev/null +++ b/libbgi/bgi256/_gfx_recthfill256.c @@ -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 diff --git a/libbgi/bgi256/_gfx_rectvfill256.c b/libbgi/bgi256/_gfx_rectvfill256.c new file mode 100644 index 0000000..c9b5be6 --- /dev/null +++ b/libbgi/bgi256/_gfx_rectvfill256.c @@ -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 diff --git a/libbgi/bgi256/getpixel.c b/libbgi/bgi256/getpixel.c index e1eddd1..41b1b7f 100644 --- a/libbgi/bgi256/getpixel.c +++ b/libbgi/bgi256/getpixel.c @@ -18,22 +18,4 @@ color_t getpixel(int x, int y) register uint8_t _gp_color = _bgi_read_raw(x, y); _bgi_end(); 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; } diff --git a/libbgi/bgi256/putpixel.c b/libbgi/bgi256/putpixel.c index 200bf27..40043fd 100644 --- a/libbgi/bgi256/putpixel.c +++ b/libbgi/bgi256/putpixel.c @@ -24,22 +24,4 @@ void putpixel(int x, int y, color_t color) _bgi_plot_raw(x, y, color); _bgi_end(); 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; } diff --git a/libbgi/common/_bgi_read.c b/libbgi/common/_bgi_read.c index d29ebe1..c00d7fa 100644 --- a/libbgi/common/_bgi_read.c +++ b/libbgi/common/_bgi_read.c @@ -8,7 +8,7 @@ unsigned _bgi_read(int x, int y) { - unsigned c; + register unsigned c; _bgi_begin(); c = _bgi_read_raw(x, y); _bgi_end(); diff --git a/tests/accfill/Makefile b/tests/accfill/Makefile new file mode 100644 index 0000000..a0c58ce --- /dev/null +++ b/tests/accfill/Makefile @@ -0,0 +1,4 @@ +PROJ_ROOT := $(abspath $(CURDIR)/../..) +EXAMPLE := accfill +EXTRA_FLAGS ?= --gfx 256 +include $(PROJ_ROOT)/app.mk diff --git a/tests/accfill/accfill.c b/tests/accfill/accfill.c new file mode 100644 index 0000000..c8b40c9 --- /dev/null +++ b/tests/accfill/accfill.c @@ -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 + + +/* Прямоугольник (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 (;;) { } +}