/* * accop — регресс БЛОЧНЫХ ЛОГИЧЕСКИХ ОПЕРАЦИЙ акселератора * (AND/OR/XOR/NOT) в libbgi: приёмник = приёмник источник, строками * и колонками. * * ЧТО ПРОВЕРЯЕТСЯ (по байтам, а не глазами — 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) не режим направления, а переключатель автомата * «буфер := память» -> «буфер = память»; burst запускает операндное * чтение (HL) в текущем режиме направления. Операция ОРТОГОНАЛЬНА * горизонтальному/вертикальному режиму — это и подтверждают T4-T7. */ #include #include #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 (;;) { } }