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:
@@ -33,7 +33,28 @@
|
||||
|
||||
# Часть I — libbgi
|
||||
|
||||
## I.0 РАЗВЕДКА ЖЕЛЕЗА — делать ПЕРВОЙ, до любого кода
|
||||
## I.0 РАЗВЕДКА ЖЕЛЕЗА — ЗАКРЫТА 2026-08-12
|
||||
|
||||
**Оба факта подтверждены, ядро и обёртка написаны и проверены в MAME**
|
||||
(`tests/pageflip`, 5/5 PASS — прямая копия, vflip, целость рамки, широкая
|
||||
копия 320 = два прохода, неприкосновенность источника).
|
||||
|
||||
- **Р1 подтверждён**: Port_Y можно менять между read- и write-триггером,
|
||||
если ПЕРЕД вторым OUT стоит STOP (`LD B,B`) — он разоружает accel, и fetch
|
||||
immediate-операнда OUT уже безопасен. Приём не новый: ровно так работает
|
||||
`_bgi_scroll_cols_raw` (ядро `gfx_scroll_v`), то есть он был в проде
|
||||
задолго до этой задачи — указал пользователь. Значит и обходной путь
|
||||
через ОЗУ-буфер не нужен;
|
||||
- **Р2 подтверждён**: обе страницы адресуемы одновременно, копия между ними
|
||||
идёт по разнице баз (`_gfx_addr_shadow_base` / `_gfx_addr_base`), строку
|
||||
выбирает Port_Y.
|
||||
|
||||
Сделано: `libbgi/bgi256/_bgi_flip_rows_raw.c` (ядро) +
|
||||
`libbgi/common/gfx_copy_page.c` (обёртка, режимы `GFX_COPY_DIRECT` /
|
||||
`GFX_COPY_VFLIP`) + `tests/pageflip`. Пункты I.1 и I.3 ниже — ЗАКРЫТЫ этим
|
||||
же коммитом; описание оставлено как контракт.
|
||||
|
||||
### Исходный текст разведки (для истории)
|
||||
|
||||
Два факта, на которых стоит вся схема, сейчас НЕ подтверждены артефактом.
|
||||
Пока их нет, остальные пункты не начинать (memory `defer_unexplained_quirks`).
|
||||
|
||||
@@ -41,6 +41,7 @@ malloc 4461
|
||||
mem_test 4555
|
||||
mouse 4382
|
||||
openenv 6124
|
||||
pageflip 4960
|
||||
palfile 5406
|
||||
ptime 5744
|
||||
rt_test 5187
|
||||
|
||||
|
@@ -163,6 +163,15 @@ void _bgi_scroll_rows_raw(uint16_t src, uint16_t dst,
|
||||
void _bgi_scroll_cols_raw(uint16_t src, uint16_t dst,
|
||||
uint8_t w, uint8_t h, uint8_t y0, uint8_t y1);
|
||||
|
||||
/* _bgi_flip_rows_raw — ядро gfx_copy_page(GFX_COPY_VFLIP): построчная копия
|
||||
* с ПЕРЕВОРОТОМ ПО ВЕРТИКАЛИ. Как scroll_rows, но Port_Y чтения растёт, а
|
||||
* Port_Y записи УБЫВАЕТ, поэтому на строку нужны два OUT — безопасные
|
||||
* благодаря STOP'у между read- и write-триггером (приём из scroll_cols).
|
||||
* w — байт/строка (0=256), h — строк 1..256 (0=256), y0 — Port_Y первой
|
||||
* строки src (++), y1 — Port_Y первой строки dst (--). */
|
||||
void _bgi_flip_rows_raw(uint16_t src, uint16_t dst,
|
||||
uint8_t w, uint8_t h, uint8_t y0, uint8_t y1);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* _bgi_flip_rows_raw — построчная копия video->video с ПЕРЕВОРОТОМ ПО
|
||||
* ВЕРТИКАЛИ (mode 0x81). Строка src с Port_Y = y0 уходит в строку dst с
|
||||
* Port_Y = y1, дальше y0 растёт, а y1 УБЫВАЕТ: за h строк область
|
||||
* отражается сверху вниз. Ядро gfx_copy_page(GFX_COPY_VFLIP) — переворот
|
||||
* экрана для зелья инверсии (applications/PoP/docs/l9_invert_plan.md).
|
||||
*
|
||||
* Отличие от _bgi_scroll_rows_raw: там read и write идут на ОДНОМ Port_Y
|
||||
* (одна строка), поэтому OUT на строку один. Здесь строки разные, значит
|
||||
* Port_Y надо сменить МЕЖДУ read- и write-триггером — а это ровно тот
|
||||
* случай, где наивная схема ломается: accel перехватывает любое чтение
|
||||
* программной памяти, и fetch immediate-операнда OUT спурьезно
|
||||
* перезапустил бы read-burst, набив буфер кодом
|
||||
* (memory/accel_operand_fetch_retrigger).
|
||||
*
|
||||
* РЕШЕНИЕ — то же, что в _bgi_scroll_cols_raw (там оно уже в проде,
|
||||
* gfx_scroll_v): STOP (LD B,B) ПЕРЕД вторым OUT. Он разоружает accel
|
||||
* (m_acc_dir = 0), после чего операнд OUT безопасен; затем LD L,L снова
|
||||
* взводит горизонтальный режим — уже для записи. Порядок на строку:
|
||||
* {OUT y0 · arm · read · STOP · OUT y1 · arm · write · STOP}
|
||||
* Оба OUT выполняются только пока accel РАЗОРУЖЁН — это инвариант, на
|
||||
* котором всё держится; менять порядок нельзя.
|
||||
*
|
||||
* Убывающий y1 держим SMC-ячейкой, а не регистром: HL/DE заняты адресами,
|
||||
* B — счётчиком банды, C — растущим y0, и свободного 8-битного не
|
||||
* остаётся. Обновление ячейки (`dec a` + `ld (…),a`) стоит на строке
|
||||
* 17Т и идёт ПОСЛЕ STOP'а, то есть при разоружённом accel — безопасно.
|
||||
*
|
||||
* DI/EI — БАНДАМИ по 16 строк ВНУТРИ (как у scroll-ядер): держать DI на
|
||||
* весь проход нельзя (порвёт CBL-звук). Размер блока армируется в КАЖДОЙ
|
||||
* скобке — в окне EI между бандами CBL-ISR армирует accel своим размером.
|
||||
*
|
||||
* ВХОД (__sdcccall(1)): src->HL, dst->DE; стек: w 4(ix) — байт в строке
|
||||
* (размер accel-блока, 1..256, 0 = 256), h 5(ix) — строк 1..256 (0 = 256,
|
||||
* конвенция accel), y0 6(ix) — Port_Y ПЕРВОЙ строки src (инкремент),
|
||||
* y1 7(ix) — Port_Y ПЕРВОЙ строки dst (ДЕКРЕМЕНТ). Callee-pop 4 байта.
|
||||
*
|
||||
* Raw: клиппинга нет (вызывающий), W3 замаплен (_bgi_begin/_bgi_end
|
||||
* снаружи), банк 0x50 (запись бьёт VRAM + ОЗУ-копию — иначе перевёрнутый
|
||||
* фон не попадёт в копию и heal начнёт восстанавливать старую картинку).
|
||||
* Перекрытия src/dst нет by design — стороны в РАЗНЫХ видеостраницах.
|
||||
* Строку > 256 байт режет вызывающий. Клоббер: AF/BC; HL/DE/IX
|
||||
* сохраняются (LD L,L / LD D,D / LD B,B — accel-опкоды-no-op'ы).
|
||||
*/
|
||||
|
||||
#include "../_bgi.h"
|
||||
|
||||
void _bgi_flip_rows_raw(uint16_t src, uint16_t dst,
|
||||
uint8_t w, uint8_t h, uint8_t y0, uint8_t y1) __naked {
|
||||
(void)src; (void)dst; (void)w; (void)h; (void)y0; (void)y1;
|
||||
__asm
|
||||
push ix
|
||||
ld ix, #0
|
||||
add ix, sp
|
||||
;; HL = src, DE = dst (адреса-константы; строку задаёт Port_Y)
|
||||
ld a, 4 (ix)
|
||||
ld (fl_row_len), a ; SMC: размер блока <- w (0 = 256)
|
||||
ld (fl_row_len2), a ; ...и в хвостовую скобку
|
||||
ld a, 7 (ix)
|
||||
ld (fl_row_ydst), a ; SMC: Port_Y записи <- y1 (убывает)
|
||||
ld c, 6 (ix) ; C = бегущий Port_Y чтения (y0)
|
||||
|
||||
;; --- precompute: полных 16-банд (ix+5) + хвост (SMC); h=0 => 256 ---
|
||||
ld a, 5 (ix)
|
||||
and #0x0F
|
||||
ld (fl_row_tail), a ; хвост = h & 15 (0 и при h=0, и при h=256)
|
||||
ld a, 5 (ix)
|
||||
or a, a
|
||||
jr NZ, fl_row_shr
|
||||
ld a, #16 ; h=0 => 256 => ровно 16 полных банд
|
||||
jr fl_row_setfull
|
||||
fl_row_shr:
|
||||
rrca
|
||||
rrca
|
||||
rrca
|
||||
rrca
|
||||
and #0x0F ; full = h >> 4
|
||||
fl_row_setfull:
|
||||
ld 5 (ix), a ; ix+5 = число полных 16-строчных банд
|
||||
|
||||
fl_row_band:
|
||||
ld a, 5 (ix)
|
||||
or a, a
|
||||
jr Z, fl_row_tailband ; полные банды кончились
|
||||
dec 5 (ix) ; full -= 1
|
||||
ld b, #16 ; 16 строк в банде
|
||||
di
|
||||
ld d, d ; 0x52 — режим размера блока (no-op для D)
|
||||
ld a, #0
|
||||
fl_row_len = . - 1
|
||||
ld b, b ; выключить — размер сохранён (B=16 цел)
|
||||
fl_row_next:
|
||||
ld a, c
|
||||
out (#0x89), a ; Port_Y = y0 (accel РАЗОРУЖЁН — операнд безопасен)
|
||||
inc c ; y0++
|
||||
ld l, l ; 0x6D — арм гориз. копии
|
||||
ld a, (hl) ; read-burst: src-строка -> буфер
|
||||
ld b, b ; 0x40 — STOP (разоружить перед OUT y1)
|
||||
ld a, #0
|
||||
fl_row_ydst = . - 1
|
||||
out (#0x89), a ; Port_Y = y1 — accel разоружён
|
||||
dec a
|
||||
ld (fl_row_ydst), a ; y1-- (запись при разоружённом accel)
|
||||
ld l, l ; 0x6D — арм гориз. копии для записи
|
||||
ld (de), a ; write-burst: буфер -> dst-строка
|
||||
ld b, b ; 0x40 — стоп
|
||||
djnz fl_row_next
|
||||
ei ; окно прерываний между бандами
|
||||
jr fl_row_band
|
||||
;; --- хвост h&15 строк: отдельная скобка со СВОИМ армированием ---
|
||||
fl_row_tailband:
|
||||
;; y1 у хвоста — ПРОДОЛЖЕНИЕ, а не исходное значение: переносим
|
||||
;; текущее из основной скобки (у неё своя immediate-ячейка).
|
||||
ld a, (fl_row_ydst)
|
||||
ld (fl_row_ydst2), a
|
||||
ld a, #0
|
||||
fl_row_tail = . - 1
|
||||
or a, a
|
||||
jr Z, fl_row_done ; хвоста нет
|
||||
ld b, a ; B = остаток строк (1..15)
|
||||
di
|
||||
ld d, d
|
||||
ld a, #0
|
||||
fl_row_len2 = . - 1
|
||||
ld b, b
|
||||
fl_row_trow:
|
||||
ld a, c
|
||||
out (#0x89), a
|
||||
inc c
|
||||
ld l, l
|
||||
ld a, (hl)
|
||||
ld b, b
|
||||
ld a, #0
|
||||
fl_row_ydst2 = . - 1
|
||||
out (#0x89), a
|
||||
dec a
|
||||
ld (fl_row_ydst2), a
|
||||
ld l, l
|
||||
ld (de), a
|
||||
ld b, b
|
||||
djnz fl_row_trow
|
||||
ei
|
||||
fl_row_done:
|
||||
pop ix
|
||||
;; callee-pop 4 байта (w, h, y0, y1)
|
||||
pop hl ; ret-адрес
|
||||
pop af
|
||||
pop af
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* gfx_copy_page — копия области из НЕактивной страницы в активную:
|
||||
* прямая или с ПЕРЕВОРОТОМ ПО ВЕРТИКАЛИ (mode-agnostic обёртка).
|
||||
*
|
||||
* Зачем. Два сценария, оба про «взять готовую картинку целиком»:
|
||||
* - GFX_COPY_DIRECT — второй странице дабл-буфера отдать то, что уже
|
||||
* нарисовано в первой, вместо повторной отрисовки сцены (у тайловых
|
||||
* движков это в разы дешевле и, главное, невидимо для игрока: он
|
||||
* видит только готовый кадр);
|
||||
* - GFX_COPY_VFLIP — перевернуть уже нарисованное вверх ногами (зелье
|
||||
* инверсии в порте Prince of Persia).
|
||||
*
|
||||
* Направление то же, что у gfx_scroll_*: источник — НЕактивная (теневая)
|
||||
* страница, приёмник — активная (draw). Банк на время копии —
|
||||
* GFX_BANK_NORMAL (0x50): запись бьёт VRAM И ОЗУ-копию. Это не деталь, а
|
||||
* инвариант: accel ЧИТАЕТ ОЗУ-копию, поэтому копируется чистый фон без
|
||||
* спрайтов, а если писать банком без тени, копия страницы-приёмника
|
||||
* останется старой и heal начнёт восстанавливать прошлую картинку.
|
||||
*
|
||||
* VFLIP отражает область ВНУТРИ СЕБЯ: строка area->y + i уходит в строку
|
||||
* area->y + area->h - 1 - i. Горизонтального зеркала здесь нет и не
|
||||
* будет — accel копирует блок только вперёд, побайтовый реверс стоил бы
|
||||
* 320 burst'ов на строку вместо двух (для персонажей зеркало делается
|
||||
* раскладкой спрайта, см. gfx_blit_cols).
|
||||
*
|
||||
* Ширина режется на burst'ы ≤ 255 байт (лимит accel-блока; 256 у нынешних
|
||||
* leaf'ов даёт мусор на втором проходе — тот же TODO, что в gfx_scroll_h).
|
||||
* Высоту и DI/EI-банды по 16 строк leaf делает сам.
|
||||
*/
|
||||
|
||||
#include "../include/gfx.h"
|
||||
#include "../include/sprite.h"
|
||||
#include "../_bgi.h"
|
||||
|
||||
void gfx_copy_page(const gfx_rect_t *area, uint8_t mode) {
|
||||
|
||||
uint8_t saved = gfx_get_bank();
|
||||
uint16_t w = area->w;
|
||||
uint8_t h = (uint8_t)area->h; /* 256 => 0 — конвенция accel */
|
||||
uint16_t src = _gfx_addr_shadow_base + (uint16_t)area->x;
|
||||
uint16_t dst = _gfx_addr_base + (uint16_t)area->x;
|
||||
uint8_t y0 = (uint8_t)area->y;
|
||||
/* Port_Y приёмника: при перевороте начинаем с НИЖНЕЙ строки области и
|
||||
* идём вверх; при прямой копии — та же строка, что у источника. */
|
||||
uint8_t y1 = (mode == GFX_COPY_VFLIP)
|
||||
? (uint8_t)(area->y + area->h - 1)
|
||||
: y0;
|
||||
|
||||
if (!w || !area->h) return;
|
||||
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
_bgi_begin();
|
||||
|
||||
while (w) {
|
||||
uint8_t cw = (w > 255) ? 255 : (uint8_t)w;
|
||||
if (mode == GFX_COPY_VFLIP)
|
||||
_bgi_flip_rows_raw(src, dst, cw, h, y0, y1);
|
||||
else
|
||||
_bgi_scroll_rows_raw(src, dst, cw, h, y0);
|
||||
src += cw; dst += cw; w -= cw;
|
||||
}
|
||||
|
||||
_bgi_end();
|
||||
gfx_set_bank(saved);
|
||||
}
|
||||
@@ -320,4 +320,15 @@ typedef struct {
|
||||
void gfx_scroll_h(const gfx_rect_t *area, int16_t dx, gfx_rect_t *dirty);
|
||||
void gfx_scroll_v(const gfx_rect_t *area, int16_t dy, gfx_rect_t *dirty);
|
||||
|
||||
/* Копия области из НЕактивной страницы в активную: прямая или с
|
||||
* ПЕРЕВОРОТОМ ПО ВЕРТИКАЛИ (строка area->y + i -> area->y + h - 1 - i).
|
||||
* Банк на время копии — 0x50, то есть приёмник обновляется И в VRAM, и в
|
||||
* ОЗУ-копии (иначе heal целевой страницы восстанавливал бы старый фон).
|
||||
* Источником служит ОЗУ-копия, поэтому переносится ЧИСТЫЙ фон — спрайты,
|
||||
* нарисованные банком без тени, не копируются.
|
||||
* Горизонтального зеркала нет: accel копирует блок только вперёд. */
|
||||
#define GFX_COPY_DIRECT 0
|
||||
#define GFX_COPY_VFLIP 1
|
||||
void gfx_copy_page(const gfx_rect_t *area, uint8_t mode);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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