Files
Sprinter-SDCC/tests/accop/accop.c
T
snark13 860468f3c5 libbgi: NOT_PUT тоже через акселератор + изыскания по виду Тени
NOT.  У акселератора нет режима «инвертировать буфер»: буфер меняется
только на ЧТЕНИИ и только опкодами AND/OR/XOR (HL) (драйвер MAME,
update_accel_buffer), а `CPL` автомат вообще не распознаёт — инвертируется
регистр CPU, не буфер.  Зато ~src = src XOR #FF, поэтому NOT собирается из
уже имеющегося: буфер := src, XOR-burst по константному блоку единиц
(common/_bgi_ones256.c), запись.  Ядра _bgi_blit_{rows,cols}_not_raw.c;
между burst'ами меняется HL, поэтому каждая смена — под СТОПом (иначе fetch
операнда перезапустит burst).  В колоночном варианте второй OUT Port_Y не
нужен: op-burst идёт по блоку единиц горизонтально, а Port_Y шагает только
вертикальный.

Наружу — тем же op-параметром (GFX_OP_NOT), диспетчер один раз на вызов, в
цикл по полосам/колонкам не заходит.  putimage лишился попиксельного пути
ЦЕЛИКОМ: все пять операций BGI идут через акселератор и клиппируются
одинаково.  tests/accop дополнен T9/T10 (NOT строками и колонками) —
10/10 PASS в MAME; tests/bgi_img P1/P2 по-прежнему PASS.

tests/convbench (новый) — замер побайтной конвертации атласа
«прозрачный #FF -> 0x00» (источник для XOR-блита): 145.3 такта/байт, то
есть 2.5× от 59 номинальных T-states цикла.  0.11 с на страницу 16 КБ,
3.2 с на все 28 страниц, 1.3 с по фактическому объёму данных (186 КБ).

applications/PoP/docs/shadow_render.md — вид Тени (два блиттера OR+XOR)
ОТЛОЖЕН по решению пользователя: пока рисуем обычной копией атласами Кида.
В документе собрано, почему в лоб не выходит (XOR несовместим с
прозрачностью #FF; операция читает ОЗУ-копию, поэтому два прохода
оригинала вырождаются в один XOR — нужен однопроходный композит
s | bg ^ s(сдвиг)), замер стоимости источника и четыре варианта.  Ключ к
выбору — список кадров, которыми тень реально пользуется; снимать по факту,
когда пойдут уровни 5/6/12.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 15:41:06 +03:00

218 lines
10 KiB
C
Raw Permalink 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.
/*
* accop — регресс БЛОЧНЫХ ЛОГИЧЕСКИХ ОПЕРАЦИЙ акселератора
* (AND/OR/XOR/NOT) в libbgi: приёмник = приёмник <op> источник, строками
* и колонками.
*
* ЧТО ПРОВЕРЯЕТСЯ (по байтам, а не глазами — getpixel читает ту же
* ОЗУ-копию экрана, что и сам op-burst):
* T1-T3 строками (gfx_blit_op → _bgi_blit_rows_op_raw): OR / XOR / AND;
* T4-T6 колонками (gfx_blit_cols_op → _bgi_blit_cols_op_raw): то же.
* Колоночный путь — самый рискованный: приёмник читается и
* пишется ВЕРТИКАЛЬНО, а вертикальный burst ШАГАЕТ Port_Y, так
* что между операцией и записью Port_Y надо вернуть на верх
* колонки (иначе колонка уедет вниз на h пикселей);
* T7 колонками + flip (зеркало и операция должны быть ортогональны);
* T8 прозрачность #FF при OR и банке 0x58: #FF | bg = #FF, запись
* подавляется железом => обычный спрайтовый атлас годится для OR
* как есть (для XOR — нет, см. шапку _gfx_blit_full_op.c);
* T9-T10 NOT (приёмник = ~источник) строками и колонками: у железа нет
* режима «инвертировать буфер», это XOR по блоку единиц —
* цепочка из трёх burst'ов со сменой HL между ними.
*
* Источник НЕ константный: пиксель = (col*IH + row) & 0x0F, то есть у
* соседей по строке И по колонке разные значения — сдвиг на пиксель,
* перепутанный порядок байт в буфере акселератора или уехавший Port_Y
* дают FAIL, а не «случайно совпало». Вокруг каждого прямоугольника
* проверяется рамка в 1px: лишний burst/переполнение блока её порвёт.
*
* Семантика FSM (драйвер MAME src/mame/sinclair/sprinter.cpp,
* accel_control_r/update_accel_buffer + docs/new/06-accel.md §6.2):
* опкоды AND/OR/XOR (HL) не режим направления, а переключатель автомата
* «буфер := память» -> «буфер <op>= память»; burst запускает операндное
* чтение (HL) в текущем режиме направления. Операция ОРТОГОНАЛЬНА
* горизонтальному/вертикальному режиму — это и подтверждают T4-T7.
*/
#include <graphics.h>
#include <gfx.h>
#define IW 12 /* ширина картинки, пикс */
#define IH 8 /* высота картинки, пикс */
#define BG_OR 0x30 /* фон под OR: 0x30 | v = 0x30+v — все разные */
#define BG_XOR 0x3C /* фон под XOR: 0x3C ^ v — все разные */
#define BG_AND 0x3F /* фон под AND: 0x3F & v = v — все разные */
#define FRAME 0x21 /* рамка вокруг: не должна быть тронута */
/* getimage-формат: uint16 w, uint16 h, дальше пиксели. */
static uint8_t img_rows[4 + IW * IH]; /* построчно (row-major) */
static uint8_t img_cols[4 + IW * IH]; /* поколоночно (col-major) */
static uint8_t img_tr[4 + IW * IH]; /* row-major с дырами 0xFF */
/* Значение пикселя картинки в (col,row) — одинаково для всех трёх
* буферов, меняется только раскладка в памяти. */
static uint8_t pix(int col, int row)
{
return (uint8_t)((col * IH + row) & 0x0F);
}
static void make_imgs(void)
{
int col, row;
img_rows[0] = IW; img_rows[1] = 0; img_rows[2] = IH; img_rows[3] = 0;
img_cols[0] = IW; img_cols[1] = 0; img_cols[2] = IH; img_cols[3] = 0;
img_tr[0] = IW; img_tr[1] = 0; img_tr[2] = IH; img_tr[3] = 0;
for (col = 0; col < IW; col++) {
for (row = 0; row < IH; row++) {
uint8_t v = pix(col, row);
img_rows[4 + row * IW + col] = v;
img_cols[4 + col * IH + row] = v;
/* дыры прозрачности: первая строка и первая колонка */
img_tr[4 + row * IW + col] = (row == 0 || col == 0) ? 0xFF : v;
}
}
}
/* Фон: рамка FRAME по периметру + прямоугольник bg внутри. */
static void prep(int x0, int y0, uint8_t bg)
{
setfillstyle(SOLID_FILL, FRAME);
bar(x0 - 1, y0 - 1, x0 + IW, y0 + IH);
setfillstyle(SOLID_FILL, bg);
bar(x0, y0, x0 + IW - 1, y0 + IH - 1);
}
/* Ожидаемый результат операции op над bg и пикселем картинки. */
static uint8_t want_op(uint8_t bg, uint8_t v, uint8_t op)
{
if (op == GFX_OP_OR) return (uint8_t)(bg | v);
if (op == GFX_OP_XOR) return (uint8_t)(bg ^ v);
if (op == GFX_OP_NOT) return (uint8_t)~v; /* приёмник не участвует */
return (uint8_t)(bg & v);
}
/* Прямоугольник совпал попиксельно И рамка вокруг цела?
* flip != 0 — экранная колонка col взята из исходной (IW-1-col). */
static uint8_t check(int x0, int y0, uint8_t bg, uint8_t op, uint8_t flip)
{
int col, row;
for (col = 0; col < IW; col++)
for (row = 0; row < IH; row++) {
uint8_t v = pix(flip ? (IW - 1 - col) : col, row);
if ((uint8_t)getpixel(x0 + col, y0 + row) != want_op(bg, v, op))
return 0;
}
for (col = -1; col <= IW; col++) {
if ((uint8_t)getpixel(x0 + col, y0 - 1) != FRAME) return 0;
if ((uint8_t)getpixel(x0 + col, y0 + IH) != FRAME) return 0;
}
for (row = 0; row < IH; row++) {
if ((uint8_t)getpixel(x0 - 1, y0 + row) != FRAME) return 0;
if ((uint8_t)getpixel(x0 + IW, y0 + row) != FRAME) return 0;
}
return 1;
}
/* T8: OR с прозрачным 0xFF под банком 0x58 — дыры обязаны остаться фоном. */
static uint8_t check_transp(int x0, int y0, uint8_t bg)
{
int col, row;
for (col = 0; col < IW; col++)
for (row = 0; row < IH; row++) {
uint8_t want = (row == 0 || col == 0)
? bg /* #FF|bg = #FF, подавлено */
: (uint8_t)(bg | pix(col, row));
if ((uint8_t)getpixel(x0 + col, y0 + row) != want)
return 0;
}
return 1;
}
static void say(int y, const char *name, uint8_t ok)
{
char line[40];
int i = 0;
while (name[i] && i < 30) { line[i] = name[i]; i++; }
line[i++] = ':';
line[i++] = ' ';
if (ok) { line[i++] = 'P'; line[i++] = 'A'; line[i++] = 'S'; line[i++] = 'S'; }
else { line[i++] = 'F'; line[i++] = 'A'; line[i++] = 'I'; line[i++] = 'L'; }
line[i] = 0;
setcolor(ok ? LIGHTGREEN : LIGHTRED);
outtextxy(8, y, line);
}
/* Прогон одной операции: подготовить фон, блитнуть, проверить.
* kind: 0 — строками, 1 — колонками, 2 — колонками с флипом. */
static uint8_t run(int x0, int y0, uint8_t bg, uint8_t op, uint8_t kind)
{
prep(x0, y0, bg);
if (kind == 0)
gfx_blit_op(x0, y0, img_rows, op);
else
gfx_blit_cols_op(x0, y0, img_cols, (uint8_t)(kind == 2), op);
return check(x0, y0, bg, op, (uint8_t)(kind == 2));
}
int main(void)
{
uint8_t t[10];
int i;
initgraph();
cleardevice();
make_imgs();
/* Банк 0x50: запись идёт и в видео-ОЗУ, и в ОЗУ-копию, поэтому
* getpixel (читает копию) видит ровно то, что записано, а op-burst
* (читает её же) — реальный фон. Это дефолт, ставим явно. */
gfx_set_bank(GFX_BANK_NORMAL);
t[0] = run( 8, 180, BG_OR, GFX_OP_OR, 0);
t[1] = run( 40, 180, BG_XOR, GFX_OP_XOR, 0);
t[2] = run( 72, 180, BG_AND, GFX_OP_AND, 0);
t[3] = run(104, 180, BG_OR, GFX_OP_OR, 1);
t[4] = run(136, 180, BG_XOR, GFX_OP_XOR, 1);
t[5] = run(168, 180, BG_AND, GFX_OP_AND, 1);
t[6] = run(200, 180, BG_XOR, GFX_OP_XOR, 2); /* колонки + зеркало */
/* T8: прозрачность #FF при OR — банк с подавлением записи #FF. */
prep(232, 180, BG_OR);
gfx_set_bank(GFX_BANK_TRANSPARENT);
gfx_blit_op(232, 180, img_tr, GFX_OP_OR);
gfx_set_bank(GFX_BANK_NORMAL);
t[7] = check_transp(232, 180, BG_OR);
/* T9/T10: NOT (приёмник = ~источник) — строками и колонками. Фон
* заведомо НЕ равен ~v, иначе проверка ничего не поймает. */
t[8] = run( 8, 218, BG_XOR, GFX_OP_NOT, 0);
t[9] = run( 40, 218, BG_OR, GFX_OP_NOT, 1);
setcolor(WHITE);
outtextxy(8, 8, "ACCEL block AND/OR/XOR test v1");
say( 28, "T1 rows OR", t[0]);
say( 40, "T2 rows XOR", t[1]);
say( 52, "T3 rows AND", t[2]);
say( 64, "T4 cols OR", t[3]);
say( 76, "T5 cols XOR", t[4]);
say( 88, "T6 cols AND", t[5]);
say(100, "T7 cols XOR flip", t[6]);
say(112, "T8 rows OR transp", t[7]);
say(124, "T9 rows NOT", t[8]);
say(136, "T10 cols NOT", t[9]);
for (i = 0; i < 10; i++)
if (!t[i]) break;
setcolor(WHITE);
outtextxy(8, 152, i == 10 ? "ALL PASS" : "SOME FAILED");
outtextxy(8, 164, "done");
for (;;) { }
}