860468f3c5
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>
218 lines
10 KiB
C
218 lines
10 KiB
C
/*
|
||
* 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 (;;) { }
|
||
}
|