libbgi: vflip-блиты для row-major картинок (шаг I.2 плана L9-INVERT)

gfx_blit_noclip_vflip / gfx_blit_part_noclip_vflip / gfx_blit_part_vflip.
Ассемблера не потребовалось: у row-major вертикальное зеркало — это порядок
строк, а он задаётся ЗНАКОМ src-страйда, который _bgi_blit_rows_raw и так
принимает знаковым и патчит SMC.  Даём адрес последней строки и -stride.

Клип у part_vflip свой: обрезка сверху экрана съедает НИЖНИЕ строки
источника, и первая строка обязана считаться от высоты ДО клипа (поймано
тестом — сначала формула брала уже укороченную).

tests/pageflip расширен до 8 проверок, все PASS в MAME.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 17:35:50 +03:00
parent 70c1668e9f
commit 3f45430454
5 changed files with 198 additions and 4 deletions
+36
View File
@@ -0,0 +1,36 @@
/*
* gfx_blit_noclip_vflip — блит картинки БЕЗ клипа, ЗЕРКАЛЬНО ПО ВЕРТИКАЛИ:
* последняя строка источника ложится в верхнюю строку прямоугольника,
* первая — в нижнюю. (x,y) — по-прежнему ЛЕВЫЙ ВЕРХНИЙ угол результата,
* так что вызывающему не надо менять арифметику позиции.
*
* Ассемблера здесь нет и не нужно: у row-major картинки вертикальное
* зеркало — это порядок строк, а он задаётся ЗНАКОМ src-страйда.
* `_bgi_blit_rows_raw` берёт `sstride` знаковым 16-битным и патчит его SMC
* в adc-цепочку, поэтому достаточно отдать ему адрес ПОСЛЕДНЕЙ строки и
* -stride. Цена кадра ровно та же, что у обычного блита — accel копирует
* каждую строку вперёд, назад идёт только выбор строки.
*
* (Для column-major картинок — gfx_blit_cols* — так НЕ выйдет: там зеркало
* по вертикали пришлось бы делать внутри колонки, а accel копирует блок
* только вперёд. Бесплатное зеркало у них горизонтальное.)
*
* Ограничения те же, что у gfx_blit_noclip: w,h ≤ 255, картинка целиком в
* пределах экрана, банк ставит вызывающий.
*/
#include "../_bgi.h"
void gfx_blit_noclip_vflip(int x, int y, const void *img)
{
const uint8_t *p = (const uint8_t *)img;
uint16_t stride = (uint16_t)(p[0] | (p[1] << 8));
uint8_t h = p[2];
if (!stride || !h) return;
_bgi_begin();
_bgi_blit_rows_raw(p + 4 + (uint16_t)(h - 1) * stride,
(uint8_t *)(_gfx_addr_base + x),
p[0], h, -(int)stride, (uint8_t)y);
_bgi_end();
}
@@ -0,0 +1,32 @@
/*
* gfx_blit_part_noclip_vflip — под-прямоугольник картинки БЕЗ клипа,
* ЗЕРКАЛЬНО ПО ВЕРТИКАЛИ. Пара к gfx_blit_part_noclip; про механику
* зеркала (знак src-страйда) — см. gfx_blit_noclip_vflip.
*
* (sx,sy,w,h) задают кусок ИСТОЧНИКА в обычной системе координат, как у
* не-зеркального двойника: sy — верхняя строка куска. На экран этот кусок
* ложится перевёрнутым, (x,y) — левый верхний угол результата. Значит
* первой рисуется строка sy+h-1 источника.
*
* Тайловому слою это позволяет не трогать выбор куска атласа вообще:
* меняется только вызываемая функция и экранный y (его считает
* вызывающий — у него поле, у библиотеки экран).
*/
#include "../_bgi.h"
void gfx_blit_part_noclip_vflip(int x, int y, const void *img,
uint8_t sx, uint8_t sy, uint8_t w, uint8_t h)
{
const uint8_t *p = (const uint8_t *)img;
uint16_t stride = (uint16_t)(p[0] | (p[1] << 8));
const uint8_t *src;
if (!stride || !w || !h) return;
/* Стартуем с ПОСЛЕДНЕЙ строки куска и идём вверх. */
src = p + 4 + (uint16_t)(sy + h - 1) * stride + sx;
_bgi_begin();
_bgi_blit_rows_raw(src, (uint8_t *)(_gfx_addr_base + x),
w, h, -(int)stride, (uint8_t)y);
_bgi_end();
}
+52
View File
@@ -0,0 +1,52 @@
/*
* gfx_blit_part_vflip — под-прямоугольник картинки С КЛИПОМ по экрану,
* ЗЕРКАЛЬНО ПО ВЕРТИКАЛИ. Пара к gfx_blit_part; механика зеркала — см.
* gfx_blit_noclip_vflip (знак src-страйда).
*
* Клип здесь СВОЙ, а не через _gfx_blit_full: общее ядро режет источник в
* прямом порядке строк, а у зеркала обрезка сверху экрана съедает НИЖНИЕ
* строки источника — считать это внутри общего ядра дороже, чем восемь
* строк арифметики здесь (функция и так вызывается только у края экрана,
* в тайловом слое основной путь — noclip-двойники).
*
* Соответствие строк: экранная (y + i) <- источник (sy + h - 1 - i).
* Значит обрезка сверху (y < 0) сдвигает старт по i, а обрезка снизу
* просто укорачивает h.
*/
#include "../include/gfx.h"
#include "../_bgi.h"
void gfx_blit_part_vflip(int x, int y, const void *img,
int sx, int sy, int w, int h)
{
const uint8_t *p = (const uint8_t *)img;
uint16_t stride = (uint16_t)(p[0] | (p[1] << 8));
int skip;
int h0 = h; /* высота ДО клипа — от неё считается первая
* строка источника (h ниже укорачивается) */
if (!stride || w <= 0 || h <= 0) return;
/* --- клип по X: источник сдвигается вместе с экраном --- */
if (x < 0) { sx -= x; w += x; x = 0; }
if (x + w > GFX_WIDTH) w = GFX_WIDTH - x;
if (w <= 0) return;
/* --- клип по Y: сверху съедаются НИЖНИЕ строки источника --- */
if (y < 0) { skip = -y; h -= skip; y = 0; } else { skip = 0; }
if (y + h > GFX_HEIGHT) h = GFX_HEIGHT - y;
if (h <= 0) return;
/* Полосы шире одного accel-блока режет вызывающий у noclip-двойников;
* здесь ширина приходит от тайла (≤ 64), поэтому хватает одной. */
if (w > 255) w = 255;
_bgi_begin();
/* Экранная строка (y+i) <- источник (sy + h0 - 1 - i). Обрезка сверху
* сдвигает старт на skip строк ВНИЗ по источнику, обрезка снизу только
* укорачивает h и первой строки не касается. */
_bgi_blit_rows_raw(p + 4 + (uint16_t)(sy + h0 - 1 - skip) * stride + (uint16_t)sx,
(uint8_t *)(_gfx_addr_base + x),
(uint8_t)w, (uint8_t)h, -(int)stride, (uint8_t)y);
_bgi_end();
}
+15
View File
@@ -99,6 +99,21 @@ void gfx_blit_noclip(int x, int y, const void *img);
* 16-бит, а не на пиксели: замер — 11 658 тактов на heal 22×22). */
void gfx_heal_noclip(int x, int y, uint8_t w, uint8_t h);
/* ЗЕРКАЛО ПО ВЕРТИКАЛИ (row-major картинки): последняя строка источника
* ложится в верхнюю строку прямоугольника. (x,y) — по-прежнему левый
* ВЕРХНИЙ угол результата, поэтому вызывающему не надо менять арифметику
* позиции — только выбрать другую функцию. Стоят ровно столько же, сколько
* не-зеркальные двойники: у row-major вертикальное зеркало это порядок
* строк, а он задаётся знаком src-страйда (accel копирует строку вперёд).
* Для column-major (gfx_blit_cols*) такого варианта НЕТ и быть не может —
* там пришлось бы реверсить внутри колонки; бесплатное зеркало у них
* горизонтальное (флаг flip). */
void gfx_blit_noclip_vflip(int x, int y, const void *img);
void gfx_blit_part_noclip_vflip(int x, int y, const void *img,
uint8_t sx, uint8_t sy, uint8_t w, uint8_t h);
void gfx_blit_part_vflip(int x, int y, const void *img,
int sx, int sy, int w, int h);
/* Блит под-прямоугольника (sx,sy,w,h) картинки (атлас кадров:
* кадр N ленты — sx = N*FRAME_W). Под-прямоугольник обязан лежать
* внутри img; по экрану — клиппится. */