libbgi: переворот экрана акселератором (gfx_copy_page + _bgi_flip_rows_raw)
Первый шаг L9-INVERT. Разведка железа закрыта экспериментом: Port_Y можно менять между read- и write-триггером, если перед вторым OUT стоит STOP — приём уже был в проде в _bgi_scroll_cols_raw (указал пользователь), так что обходной путь через ОЗУ-буфер не понадобился. - _bgi_flip_rows_raw: построчная копия video->video с реверсом Port_Y приёмника (DI/EI бандами по 16 строк, размер блока армируется в каждой скобке, убывающий y1 держится SMC-ячейкой); - gfx_copy_page(area, GFX_COPY_DIRECT|GFX_COPY_VFLIP): копия области из неактивной страницы в активную; банк 0x50, то есть приёмник обновляется и в VRAM, и в ОЗУ-копии — иначе heal восстанавливал бы старый фон; - tests/pageflip: 5/5 PASS в MAME (прямая, vflip, рамка, широкая 320 двумя проходами, источник цел). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := pageflip
|
||||
EXTRA_FLAGS ?= --gfx 256
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* pageflip — регресс gfx_copy_page: копия области из НЕактивной страницы в
|
||||
* активную, прямая (GFX_COPY_DIRECT) и с переворотом по вертикали
|
||||
* (GFX_COPY_VFLIP, ядро _bgi_flip_rows_raw).
|
||||
*
|
||||
* ЗАЧЕМ ИМЕННО ТАКОЙ ТЕСТ. Переворот строит на квирке акселератора: между
|
||||
* read- и write-триггером надо СМЕНИТЬ Port_Y, а fetch операнда любой
|
||||
* инструкции в этом окне перезапускает read-burst и набивает буфер кодом
|
||||
* (memory/accel_operand_fetch_retrigger). Ядро обходит это STOP'ом перед
|
||||
* вторым OUT — приём взят из _bgi_scroll_cols_raw, где он уже в проде.
|
||||
* Ошибка здесь даёт не «немного не так», а сдвинутые/мусорные строки,
|
||||
* поэтому проверка ПОБАЙТНАЯ, а не на глаз.
|
||||
*
|
||||
* Что проверяется:
|
||||
* T1 DIRECT: строка i источника легла в строку i приёмника;
|
||||
* T2 VFLIP: строка i источника легла в строку (h-1-i) приёмника;
|
||||
* T3 рамка вокруг области цела — burst не вылез за пределы;
|
||||
* T4 ШИРОКАЯ копия (320 байт = два прохода по 255+65): вылезший или
|
||||
* потерянный второй проход виден по краям строки;
|
||||
* T5 ИСТОЧНИК не тронут — копия односторонняя.
|
||||
*
|
||||
* Паттерн намеренно разный и по строкам, и по колонкам: цвет строки несёт
|
||||
* её номер (сдвиг по Y виден сразу), а маркеры на первом/последнем пикселе
|
||||
* ловят сдвиг по X и потерю хвоста широкой копии.
|
||||
*
|
||||
* ВИД НА ЭКРАНЕ: сами области выглядят чёрными — палитра по умолчанию
|
||||
* задаёт лишь 16 первых цветов, а тест намеренно пишет индексы 0x21..0x60,
|
||||
* чтобы значения были различимы побайтно. Это ожидаемо: проверка идёт
|
||||
* getpixel'ом по ИНДЕКСАМ, а не глазами.
|
||||
*
|
||||
* getpixel читает ОЗУ-КОПИЮ экрана — ту самую, которую обязана обновить
|
||||
* копия (банк 0x50). Значит T1/T2 заодно подтверждают инвариант «после
|
||||
* gfx_copy_page heal целевой страницы восстановит НОВЫЙ фон, а не старый»:
|
||||
* без записи в тень тест бы их провалил.
|
||||
*/
|
||||
|
||||
#include <graphics.h>
|
||||
#include <gfx.h>
|
||||
#include <sprite.h>
|
||||
|
||||
#define AX 8 /* область-образец: левый верхний угол */
|
||||
#define AY 40
|
||||
#define AW 64
|
||||
#define AH 32
|
||||
|
||||
#define WX 0 /* широкая область (два прохода по ширине) */
|
||||
#define WY 120
|
||||
#define WW 320
|
||||
#define WH 16
|
||||
|
||||
#define ROW_C0 0x41 /* цвет строки i = ROW_C0 + i */
|
||||
#define MARK_L 0x21 /* маркер левого края строки */
|
||||
#define MARK_R 0x22 /* маркер правого края */
|
||||
#define FRAME 0x2F /* рамка вокруг области */
|
||||
#define DIRT 0x35 /* чем забиваем приёмник до копии */
|
||||
|
||||
/* Нарисовать образец в ТЕКУЩЕЙ draw-странице: каждая строка своим цветом,
|
||||
* по краям маркеры. */
|
||||
static void draw_pattern(int x0, int y0, int w, int h)
|
||||
{
|
||||
int y;
|
||||
for (y = 0; y < h; y++) {
|
||||
setfillstyle(SOLID_FILL, (uint8_t)(ROW_C0 + y));
|
||||
bar(x0, y0 + y, x0 + w - 1, y0 + y);
|
||||
putpixel(x0, y0 + y, MARK_L);
|
||||
putpixel(x0 + w - 1, y0 + y, MARK_R);
|
||||
}
|
||||
}
|
||||
|
||||
/* Забить приёмник мусором + рамка вокруг (её никто не должен трогать). */
|
||||
static void draw_dirt(int x0, int y0, int w, int h)
|
||||
{
|
||||
setfillstyle(SOLID_FILL, FRAME);
|
||||
bar(x0 - 1, y0 - 1, x0 + w, y0 + h);
|
||||
setfillstyle(SOLID_FILL, DIRT);
|
||||
bar(x0, y0, x0 + w - 1, y0 + h - 1);
|
||||
}
|
||||
|
||||
/* Строка src_y образца обязана лежать в строке dst_y приёмника. */
|
||||
static uint8_t row_matches(int x0, int y0, int w, int src_y, int dst_y)
|
||||
{
|
||||
int x;
|
||||
uint8_t want = (uint8_t)(ROW_C0 + src_y);
|
||||
if ((uint8_t)getpixel(x0, y0 + dst_y) != MARK_L) return 0;
|
||||
if ((uint8_t)getpixel(x0 + w - 1, y0 + dst_y) != MARK_R) return 0;
|
||||
for (x = 1; x < w - 1; x++)
|
||||
if ((uint8_t)getpixel(x0 + x, y0 + dst_y) != want) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static uint8_t check_direct(int x0, int y0, int w, int h)
|
||||
{
|
||||
int y;
|
||||
for (y = 0; y < h; y++)
|
||||
if (!row_matches(x0, y0, w, y, y)) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static uint8_t check_vflip(int x0, int y0, int w, int h)
|
||||
{
|
||||
int y;
|
||||
for (y = 0; y < h; y++)
|
||||
if (!row_matches(x0, y0, w, y, h - 1 - y)) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static uint8_t check_frame(int x0, int y0, int w, int h)
|
||||
{
|
||||
int x, y;
|
||||
for (x = -1; x <= w; x++) {
|
||||
if ((uint8_t)getpixel(x0 + x, y0 - 1) != FRAME) return 0;
|
||||
if ((uint8_t)getpixel(x0 + x, y0 + h) != FRAME) return 0;
|
||||
}
|
||||
for (y = 0; y < h; y++) {
|
||||
if ((uint8_t)getpixel(x0 - 1, y0 + y) != FRAME) return 0;
|
||||
if ((uint8_t)getpixel(x0 + w, y0 + y) != FRAME) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void say(int y, const char *s, uint8_t ok)
|
||||
{
|
||||
setcolor(WHITE);
|
||||
outtextxy(8, y, (char *)s);
|
||||
setcolor(ok ? GREEN : RED);
|
||||
outtextxy(200, y, ok ? (char *)"PASS" : (char *)"FAIL");
|
||||
}
|
||||
|
||||
void main(void)
|
||||
{
|
||||
gfx_rect_t area;
|
||||
uint8_t t[5];
|
||||
uint8_t i;
|
||||
|
||||
initgraph();
|
||||
gfx_set_visible_page(0);
|
||||
gfx_pal_sync(); /* у страницы 1 своя палитра */
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
|
||||
/* Образец — в страницу 1 (она будет НЕактивной, то есть источником). */
|
||||
gfx_set_draw_page(1);
|
||||
setfillstyle(SOLID_FILL, BLACK);
|
||||
bar(0, 0, 319, 255);
|
||||
draw_pattern(AX, AY, AW, AH);
|
||||
draw_pattern(WX, WY, WW, WH);
|
||||
|
||||
/* Приёмник — страница 0. */
|
||||
gfx_set_draw_page(0);
|
||||
setfillstyle(SOLID_FILL, BLACK);
|
||||
bar(0, 0, 319, 255);
|
||||
draw_dirt(AX, AY, AW, AH);
|
||||
|
||||
/* T1: прямая копия. */
|
||||
area.x = AX; area.y = AY; area.w = AW; area.h = AH;
|
||||
gfx_copy_page(&area, GFX_COPY_DIRECT);
|
||||
t[0] = check_direct(AX, AY, AW, AH);
|
||||
|
||||
/* T2/T3: та же область с переворотом (поверх результата T1 — значит
|
||||
* непереписанная строка сразу видна как «не отразилось»). */
|
||||
gfx_copy_page(&area, GFX_COPY_VFLIP);
|
||||
t[1] = check_vflip(AX, AY, AW, AH);
|
||||
t[2] = check_frame(AX, AY, AW, AH);
|
||||
|
||||
/* T4: широкая копия — 320 байт не влезают в один accel-блок. */
|
||||
setfillstyle(SOLID_FILL, DIRT);
|
||||
bar(WX, WY, WX + WW - 1, WY + WH - 1);
|
||||
area.x = WX; area.y = WY; area.w = WW; area.h = WH;
|
||||
gfx_copy_page(&area, GFX_COPY_VFLIP);
|
||||
t[3] = check_vflip(WX, WY, WW, WH);
|
||||
|
||||
/* T5: источник обязан остаться нетронутым. */
|
||||
gfx_set_draw_page(1);
|
||||
t[4] = check_direct(AX, AY, AW, AH);
|
||||
gfx_set_draw_page(0);
|
||||
|
||||
setcolor(WHITE);
|
||||
outtextxy(8, 8, "gfx_copy_page test v1");
|
||||
say(150, "T1 direct copy", t[0]);
|
||||
say(162, "T2 vflip copy", t[1]);
|
||||
say(174, "T3 frame intact", t[2]);
|
||||
say(186, "T4 vflip 320 wide", t[3]);
|
||||
say(198, "T5 source intact", t[4]);
|
||||
|
||||
for (i = 0; i < 5; i++)
|
||||
if (!t[i]) break;
|
||||
setcolor(WHITE);
|
||||
outtextxy(8, 216, i == 5 ? (char *)"ALL PASS" : (char *)"SOME FAILED");
|
||||
outtextxy(8, 228, "done");
|
||||
|
||||
for (;;) { }
|
||||
}
|
||||
Reference in New Issue
Block a user