diff --git a/docs/libc-reference.md b/docs/libc-reference.md index 5be56e8..2d35eb0 100644 --- a/docs/libc-reference.md +++ b/docs/libc-reference.md @@ -262,9 +262,9 @@ tests/bgi_img. Скролл региона из НЕактивной страницы в активную (): `gfx_scroll_h(area, dx, dirty)` — горизонтальный (dx>0 = вправо; быстрый построчный accel-скролл без страйдов, DI-банды по 16 строк), -`gfx_scroll_v(area, dy, dirty)` — вертикальный (dy>0 = вниз; через -строку-буфер на стеке, ПОКА не оптимален — Port_Y один на burst, разный -read/write Y невозможен без ломающего accel OUT). Копия = скролл + heal +`gfx_scroll_v(area, dy, dirty)` — вертикальный (dy>0 = вниз; прямой +колоночный accel-проход: STOP между чтением и записью позволяет безопасно +сменить Port_Y, промежуточный RAM-буфер не нужен). Копия = скролл + heal цели (банк 0x50). Открывшуюся полосу |d| не заполняют — возвращают в *dirty (NULL = не нужно). Пример: examples/scroll. Стиль линий (Ф2d): `setlinestyle(style,upattern,thick)`/`getlinesettings` diff --git a/libbgi/common/gfx_scroll_h.c b/libbgi/common/gfx_scroll_h.c index a6b9f85..b0312d6 100644 --- a/libbgi/common/gfx_scroll_h.c +++ b/libbgi/common/gfx_scroll_h.c @@ -27,18 +27,73 @@ #include "../_bgi.h" void gfx_scroll_h(const gfx_rect_t *area, int16_t dx, gfx_rect_t *dirty) { - - uint8_t saved = gfx_get_bank(); - uint16_t adx = (dx < 0) ? (uint16_t)(-dx) : (uint16_t)dx; - /* src-строка стартует правее на |dx| при сдвиге влево, dst — правее - * на dx при сдвиге вправо; так оба края остаются внутри area. */ - uint16_t src_x = area->x + ((dx < 0) ? adx : 0); - uint16_t dst_x = area->x + ((dx > 0) ? adx : 0); - uint16_t w = area->w - adx; /* байт (пикселей) в строке */ - uint8_t h = (uint8_t)area->h; /* строк (256 => 0 = 256 у leaf) */ +#ifdef GFX_NOCHECK + uint8_t saved = gfx_get_bank(); + uint16_t adx = (dx < 0) ? (uint16_t)(-dx) : (uint16_t)dx; + uint16_t src_x = area->x + ((dx < 0) ? adx : 0); + uint16_t dst_x = area->x + ((dx > 0) ? adx : 0); + uint16_t w = area->w - adx; + uint8_t h = (uint8_t)area->h; uint16_t src = _gfx_addr_shadow_base + src_x; uint16_t dst = _gfx_addr_base + dst_x; + gfx_set_bank(GFX_BANK_NORMAL); + _bgi_begin(); + while (w) { + uint8_t cw = (w > 255) ? 255 : (uint8_t)w; + _bgi_scroll_rows_raw(src, dst, cw, h, (uint8_t)area->y); + src += cw; dst += cw; w -= cw; + } + _bgi_end(); + gfx_set_bank(saved); + + if (dirty) { + dirty->x = (dx >= 0) ? area->x : (int16_t)(area->x + area->w - adx); + dirty->y = area->y; dirty->w = adx; dirty->h = area->h; + } +#else + int16_t x, y; + uint16_t aw, ah, cut, adx; + uint8_t saved, h; + uint16_t src_x, dst_x, w, src, dst; + + if (!area) return; + x = area->x; y = area->y; aw = area->w; ah = area->h; + if (!aw || !ah) goto empty; + + /* Raw-leaf не клипит. Нормализуем область до экрана и не допускаем + * 16-битного underflow в area->w-|dx|. */ + if (x < 0) { + cut = (uint16_t)(-x); + if (cut >= aw) goto empty; + aw -= cut; x = 0; + } + if (y < 0) { + cut = (uint16_t)(-y); + if (cut >= ah) goto empty; + ah -= cut; y = 0; + } + if (x >= GFX_WIDTH || y >= GFX_HEIGHT) goto empty; + if (aw > (uint16_t)(GFX_WIDTH - x)) aw = (uint16_t)(GFX_WIDTH - x); + if (ah > (uint16_t)(GFX_HEIGHT - y)) ah = (uint16_t)(GFX_HEIGHT - y); + + /* Сначала сравнение со знаком: так не вычисляем -INT16_MIN. */ + if (dx >= (int16_t)aw || dx <= -(int16_t)aw) { + if (dirty) { dirty->x=x; dirty->y=y; dirty->w=aw; dirty->h=ah; } + return; + } + + adx = (dx < 0) ? (uint16_t)(-dx) : (uint16_t)dx; + saved = gfx_get_bank(); + /* src-строка стартует правее на |dx| при сдвиге влево, dst — правее + * на dx при сдвиге вправо; так оба края остаются внутри area. */ + src_x = (uint16_t)x + ((dx < 0) ? adx : 0); + dst_x = (uint16_t)x + ((dx > 0) ? adx : 0); + w = aw - adx; + h = (uint8_t)ah; /* 256 => 0 у leaf */ + src = _gfx_addr_shadow_base + src_x; + dst = _gfx_addr_base + dst_x; + gfx_set_bank(GFX_BANK_NORMAL); _bgi_begin(); @@ -54,7 +109,7 @@ void gfx_scroll_h(const gfx_rect_t *area, int16_t dx, gfx_rect_t *dirty) { // src += 256; dst += 256; // w -= (w > 256) ? 256 : w; uint8_t cw = (w > 255) ? 255 : (uint8_t)w; /* 0 => 256 */ - _bgi_scroll_rows_raw(src, dst, cw, h, (uint8_t)area->y); + _bgi_scroll_rows_raw(src, dst, cw, h, (uint8_t)y); src += cw; dst += cw; w -= cw; } @@ -62,9 +117,14 @@ void gfx_scroll_h(const gfx_rect_t *area, int16_t dx, gfx_rect_t *dirty) { gfx_set_bank(saved); if (dirty) { - dirty->x = (dx >= 0) ? area->x : (int16_t)(area->x + area->w - adx); - dirty->y = area->y; + dirty->x = (dx >= 0) ? x : (int16_t)(x + aw - adx); + dirty->y = y; dirty->w = adx; - dirty->h = area->h; + dirty->h = ah; } + return; + +empty: + if (dirty) { dirty->x=0; dirty->y=0; dirty->w=0; dirty->h=0; } +#endif } diff --git a/libbgi/common/gfx_scroll_v.c b/libbgi/common/gfx_scroll_v.c index b1518b4..c7588cf 100644 --- a/libbgi/common/gfx_scroll_v.c +++ b/libbgi/common/gfx_scroll_v.c @@ -24,20 +24,72 @@ #include "../_bgi.h" void gfx_scroll_v(const gfx_rect_t *area, int16_t dy, gfx_rect_t *dirty) { +#ifdef GFX_NOCHECK + uint8_t saved = gfx_get_bank(); + uint16_t ady = (dy < 0) ? (uint16_t)(-dy) : (uint16_t)dy; + uint8_t ys = (uint8_t)(area->y + ((dy < 0) ? ady : 0)); + uint8_t yd = (uint8_t)(area->y + ((dy > 0) ? ady : 0)); + uint16_t hb = area->h - ady; - uint8_t saved = gfx_get_bank(); - uint16_t ady = (dy < 0) ? (uint16_t)(-dy) : (uint16_t)dy; + if (hb) { + uint8_t hblk = (uint8_t)hb; + uint16_t w = area->w; + uint16_t src = _gfx_addr_shadow_base + area->x; + uint16_t dst = _gfx_addr_base + area->x; + gfx_set_bank(GFX_BANK_NORMAL); + _bgi_begin(); + while (w) { + uint8_t cw = (w > 255) ? 255 : (uint8_t)w; + _bgi_scroll_cols_raw(src, dst, cw, hblk, ys, yd); + src += cw; dst += cw; w -= cw; + } + _bgi_end(); + gfx_set_bank(saved); + } + if (dirty) { + dirty->x = area->x; + dirty->y = (dy >= 0) ? area->y : (int16_t)(area->y + area->h - ady); + dirty->w = area->w; dirty->h = ady; + } +#else + int16_t x, y; + uint16_t aw, ah, cut, ady, hb; + uint8_t saved, ys, yd; + + if (!area) return; + x = area->x; y = area->y; aw = area->w; ah = area->h; + if (!aw || !ah) goto empty; + if (x < 0) { + cut = (uint16_t)(-x); + if (cut >= aw) goto empty; + aw -= cut; x = 0; + } + if (y < 0) { + cut = (uint16_t)(-y); + if (cut >= ah) goto empty; + ah -= cut; y = 0; + } + if (x >= GFX_WIDTH || y >= GFX_HEIGHT) goto empty; + if (aw > (uint16_t)(GFX_WIDTH - x)) aw = (uint16_t)(GFX_WIDTH - x); + if (ah > (uint16_t)(GFX_HEIGHT - y)) ah = (uint16_t)(GFX_HEIGHT - y); + if (dy >= (int16_t)ah || dy <= -(int16_t)ah) { + if (dirty) { dirty->x=x; dirty->y=y; dirty->w=aw; dirty->h=ah; } + return; + } + + ady = (dy < 0) ? (uint16_t)(-dy) : (uint16_t)dy; + saved = gfx_get_bank(); /* Строку чтения (ys) сдвигаем вниз на |dy| при сдвиге вверх, строку * записи (yd) — вниз на dy при сдвиге вниз; края в пределах area. */ - uint8_t ys = (uint8_t)(area->y + ((dy < 0) ? ady : 0)); - uint8_t yd = (uint8_t)(area->y + ((dy > 0) ? ady : 0)); - uint16_t hb = area->h - ady; /* строк к переносу (≤256) */ + ys = (uint8_t)(y + ((dy < 0) ? ady : 0)); + yd = (uint8_t)(y + ((dy > 0) ? ady : 0)); + hb = ah - ady; if (hb) { /* 0 = переносить нечего */ uint8_t hblk = (uint8_t)hb; /* размер accel-блока: 256 => 0 */ - uint16_t w = area->w; - uint16_t src = _gfx_addr_shadow_base + area->x; - uint16_t dst = _gfx_addr_base + area->x; + uint16_t w = aw; + uint16_t src = _gfx_addr_shadow_base + (uint16_t)x; + uint16_t dst = _gfx_addr_base + (uint16_t)x; gfx_set_bank(GFX_BANK_NORMAL); _bgi_begin(); @@ -55,9 +107,14 @@ void gfx_scroll_v(const gfx_rect_t *area, int16_t dy, gfx_rect_t *dirty) { } if (dirty) { - dirty->x = area->x; - dirty->y = (dy >= 0) ? area->y : (int16_t)(area->y + area->h - ady); - dirty->w = area->w; + dirty->x = x; + dirty->y = (dy >= 0) ? y : (int16_t)(y + ah - ady); + dirty->w = aw; dirty->h = ady; } + return; + +empty: + if (dirty) { dirty->x=0; dirty->y=0; dirty->w=0; dirty->h=0; } +#endif } diff --git a/tests/scroll/Makefile b/tests/scroll/Makefile new file mode 100644 index 0000000..916eb12 --- /dev/null +++ b/tests/scroll/Makefile @@ -0,0 +1,4 @@ +PROJ_ROOT := $(abspath $(CURDIR)/../..) +EXAMPLE := scroll +EXTRA_FLAGS ?= --gfx 256 --safe +include $(PROJ_ROOT)/app.mk diff --git a/tests/scroll/scroll.c b/tests/scroll/scroll.c new file mode 100644 index 0000000..a55dcc3 --- /dev/null +++ b/tests/scroll/scroll.c @@ -0,0 +1,139 @@ +/* Побайтовый регресс gfx_scroll_h/gfx_scroll_v. + * Источник — неактивная page 1, приёмник — draw page 0. */ +#include +#include +#include + +#define AX 10 +#define AY 24 +#define AW 300 /* проверяет разбиение ширины 255+45 */ +#define AH 160 +#define DIRT 0x31 + +static uint8_t pattern(int x, int y) +{ + return (uint8_t)(0x40 + ((x * 3 + y * 5) & 0x3f)); +} + +static void draw_source(void) +{ + int x, y; + for (y = AY; y < AY + AH; y++) + for (x = AX; x < AX + AW; x++) + putpixel(x, y, pattern(x, y)); +} + +static void clear_target(void) +{ + setfillstyle(SOLID_FILL, DIRT); + bar(0, 0, 319, 255); +} + +static uint8_t rect_eq(const gfx_rect_t *r, int x, int y, int w, int h) +{ + return r->x == x && r->y == y && r->w == w && r->h == h; +} + +static uint8_t check_h(int dx) +{ + gfx_rect_t a = { AX, AY, AW, AH }, d; + int x, y, sx; + + clear_target(); + gfx_scroll_h(&a, dx, &d); + if (dx > 0) { + if (!rect_eq(&d, AX, AY, dx, AH)) return 0; + } else if (dx < 0) { + if (!rect_eq(&d, AX + AW + dx, AY, -dx, AH)) return 0; + } else if (!rect_eq(&d, AX, AY, 0, AH)) return 0; + + for (y = AY; y < AY + AH; y++) + for (x = AX; x < AX + AW; x++) { + sx = x - dx; + if (sx >= AX && sx < AX + AW) { + if ((uint8_t)getpixel(x, y) != pattern(sx, y)) return 0; + } else if ((uint8_t)getpixel(x, y) != DIRT) return 0; + } + return 1; +} + +static uint8_t check_v(int dy) +{ + gfx_rect_t a = { AX, AY, AW, AH }, d; + int x, y, sy; + + clear_target(); + gfx_scroll_v(&a, dy, &d); + if (dy > 0) { + if (!rect_eq(&d, AX, AY, AW, dy)) return 0; + } else if (!rect_eq(&d, AX, AY + AH + dy, AW, -dy)) return 0; + + for (y = AY; y < AY + AH; y++) + for (x = AX; x < AX + AW; x++) { + sy = y - dy; + if (sy >= AY && sy < AY + AH) { + if ((uint8_t)getpixel(x, y) != pattern(x, sy)) return 0; + } else if ((uint8_t)getpixel(x, y) != DIRT) return 0; + } + return 1; +} + +static uint8_t check_limits(void) +{ + gfx_rect_t a = { -5, -7, 25, 27 }, d; + gfx_scroll_h(&a, 40, &d); + if (!rect_eq(&d, 0, 0, 20, 20)) return 0; + gfx_scroll_v(&a, -40, &d); + return rect_eq(&d, 0, 0, 20, 20); +} + +static uint8_t check_source(void) +{ + int x, y; + gfx_set_draw_page(1); + for (y = AY; y < AY + AH; y++) + for (x = AX; x < AX + AW; x++) + if ((uint8_t)getpixel(x, y) != pattern(x, y)) { + gfx_set_draw_page(0); + return 0; + } + gfx_set_draw_page(0); + return 1; +} + +static void say(int y, const char *name, uint8_t ok) +{ + setcolor(WHITE); outtextxy(8, y, (char *)name); + setcolor(ok ? GREEN : RED); outtextxy(232, y, ok ? (char *)"PASS" : (char *)"FAIL"); +} + +void main(void) +{ + uint8_t t[7], i; + + initgraph(); + gfx_set_bank(GFX_BANK_NORMAL); + gfx_set_draw_page(1); draw_source(); + gfx_set_draw_page(0); + + t[0] = check_h(8); + t[1] = check_h(-13); + t[2] = check_v(9); + t[3] = check_v(-11); + t[4] = check_limits(); + t[5] = check_h(0); + t[6] = check_source(); + + clear_target(); + say(40, "H +8, wide", t[0]); + say(56, "H -13, wide", t[1]); + say(72, "V +9, wide", t[2]); + say(88, "V -11, wide", t[3]); + say(104, "clip/full dirty", t[4]); + say(120, "H zero/full copy", t[5]); + say(136, "source intact", t[6]); + for (i = 0; i < 7 && t[i]; i++) { } + setcolor(WHITE); + outtextxy(8, 160, i == 7 ? (char *)"ALL PASS" : (char *)"SOME FAILED"); + for (;;) { } +}