gfx_scroll_v: колоночный accel-проход без буфера-посредника + тест scroll

Вертикальный скролл перестал ходить через строку-буфер на стеке: колонку
читаем с Port_Y=ys, пишем с Port_Y=yd, а STOP между read- и write-триггером
делает промежуточный OUT Port_Y безопасным.  Один проход вместо
grab→blit-через-буфер.

Справочник libc приведён в соответствие (там же строка про новую точку
входа cbl_open_silence).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 21:59:28 +03:00
parent 3ea4546656
commit e2730f78e0
5 changed files with 287 additions and 27 deletions
+3 -3
View File
@@ -262,9 +262,9 @@ tests/bgi_img.
Скролл региона из НЕактивной страницы в активную (<sprite.h>): Скролл региона из НЕактивной страницы в активную (<sprite.h>):
`gfx_scroll_h(area, dx, dirty)` — горизонтальный (dx>0 = вправо; быстрый `gfx_scroll_h(area, dx, dirty)` — горизонтальный (dx>0 = вправо; быстрый
построчный accel-скролл без страйдов, DI-банды по 16 строк), построчный accel-скролл без страйдов, DI-банды по 16 строк),
`gfx_scroll_v(area, dy, dirty)` — вертикальный (dy>0 = вниз; через `gfx_scroll_v(area, dy, dirty)` — вертикальный (dy>0 = вниз; прямой
строку-буфер на стеке, ПОКА не оптимален — Port_Y один на burst, разный колоночный accel-проход: STOP между чтением и записью позволяет безопасно
read/write Y невозможен без ломающего accel OUT). Копия = скролл + heal сменить Port_Y, промежуточный RAM-буфер не нужен). Копия = скролл + heal
цели (банк 0x50). Открывшуюся полосу |d| не заполняют — возвращают в цели (банк 0x50). Открывшуюся полосу |d| не заполняют — возвращают в
*dirty (NULL = не нужно). Пример: examples/scroll. *dirty (NULL = не нужно). Пример: examples/scroll.
Стиль линий (Ф2d): `setlinestyle(style,upattern,thick)`/`getlinesettings` Стиль линий (Ф2d): `setlinestyle(style,upattern,thick)`/`getlinesettings`
+73 -13
View File
@@ -27,18 +27,73 @@
#include "../_bgi.h" #include "../_bgi.h"
void gfx_scroll_h(const gfx_rect_t *area, int16_t dx, gfx_rect_t *dirty) { void gfx_scroll_h(const gfx_rect_t *area, int16_t dx, gfx_rect_t *dirty) {
#ifdef GFX_NOCHECK
uint8_t saved = gfx_get_bank(); uint8_t saved = gfx_get_bank();
uint16_t adx = (dx < 0) ? (uint16_t)(-dx) : (uint16_t)dx; uint16_t adx = (dx < 0) ? (uint16_t)(-dx) : (uint16_t)dx;
/* src-строка стартует правее на |dx| при сдвиге влево, dst — правее uint16_t src_x = area->x + ((dx < 0) ? adx : 0);
* на dx при сдвиге вправо; так оба края остаются внутри area. */ uint16_t dst_x = area->x + ((dx > 0) ? adx : 0);
uint16_t src_x = area->x + ((dx < 0) ? adx : 0); uint16_t w = area->w - adx;
uint16_t dst_x = area->x + ((dx > 0) ? adx : 0); uint8_t h = (uint8_t)area->h;
uint16_t w = area->w - adx; /* байт (пикселей) в строке */
uint8_t h = (uint8_t)area->h; /* строк (256 => 0 = 256 у leaf) */
uint16_t src = _gfx_addr_shadow_base + src_x; uint16_t src = _gfx_addr_shadow_base + src_x;
uint16_t dst = _gfx_addr_base + dst_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); gfx_set_bank(GFX_BANK_NORMAL);
_bgi_begin(); _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; // src += 256; dst += 256;
// w -= (w > 256) ? 256 : w; // w -= (w > 256) ? 256 : w;
uint8_t cw = (w > 255) ? 255 : (uint8_t)w; /* 0 => 256 */ 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; 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); gfx_set_bank(saved);
if (dirty) { if (dirty) {
dirty->x = (dx >= 0) ? area->x : (int16_t)(area->x + area->w - adx); dirty->x = (dx >= 0) ? x : (int16_t)(x + aw - adx);
dirty->y = area->y; dirty->y = y;
dirty->w = adx; 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
} }
+68 -11
View File
@@ -24,20 +24,72 @@
#include "../_bgi.h" #include "../_bgi.h"
void gfx_scroll_v(const gfx_rect_t *area, int16_t dy, gfx_rect_t *dirty) { 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(); if (hb) {
uint16_t ady = (dy < 0) ? (uint16_t)(-dy) : (uint16_t)dy; 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| при сдвиге вверх, строку /* Строку чтения (ys) сдвигаем вниз на |dy| при сдвиге вверх, строку
* записи (yd) — вниз на dy при сдвиге вниз; края в пределах area. */ * записи (yd) — вниз на dy при сдвиге вниз; края в пределах area. */
uint8_t ys = (uint8_t)(area->y + ((dy < 0) ? ady : 0)); ys = (uint8_t)(y + ((dy < 0) ? ady : 0));
uint8_t yd = (uint8_t)(area->y + ((dy > 0) ? ady : 0)); yd = (uint8_t)(y + ((dy > 0) ? ady : 0));
uint16_t hb = area->h - ady; /* строк к переносу (≤256) */ hb = ah - ady;
if (hb) { /* 0 = переносить нечего */ if (hb) { /* 0 = переносить нечего */
uint8_t hblk = (uint8_t)hb; /* размер accel-блока: 256 => 0 */ uint8_t hblk = (uint8_t)hb; /* размер accel-блока: 256 => 0 */
uint16_t w = area->w; uint16_t w = aw;
uint16_t src = _gfx_addr_shadow_base + area->x; uint16_t src = _gfx_addr_shadow_base + (uint16_t)x;
uint16_t dst = _gfx_addr_base + area->x; uint16_t dst = _gfx_addr_base + (uint16_t)x;
gfx_set_bank(GFX_BANK_NORMAL); gfx_set_bank(GFX_BANK_NORMAL);
_bgi_begin(); _bgi_begin();
@@ -55,9 +107,14 @@ void gfx_scroll_v(const gfx_rect_t *area, int16_t dy, gfx_rect_t *dirty) {
} }
if (dirty) { if (dirty) {
dirty->x = area->x; dirty->x = x;
dirty->y = (dy >= 0) ? area->y : (int16_t)(area->y + area->h - ady); dirty->y = (dy >= 0) ? y : (int16_t)(y + ah - ady);
dirty->w = area->w; dirty->w = aw;
dirty->h = ady; dirty->h = ady;
} }
return;
empty:
if (dirty) { dirty->x=0; dirty->y=0; dirty->w=0; dirty->h=0; }
#endif
} }
+4
View File
@@ -0,0 +1,4 @@
PROJ_ROOT := $(abspath $(CURDIR)/../..)
EXAMPLE := scroll
EXTRA_FLAGS ?= --gfx 256 --safe
include $(PROJ_ROOT)/app.mk
+139
View File
@@ -0,0 +1,139 @@
/* Побайтовый регресс gfx_scroll_h/gfx_scroll_v.
* Источник — неактивная page 1, приёмник — draw page 0. */
#include <graphics.h>
#include <gfx.h>
#include <sprite.h>
#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 (;;) { }
}