libbgi: скролл-примитивы + --w3 + отчёт раскладки памяти

Скролл региона video->video (неактивная страница -> активная, банк 0x50:
копия = скролл + heal цели):
- gfx_scroll_h / _bgi_scroll_rows_raw — горизонтальный, построчно без
  страйдов (~54Т/строку), DI/EI бандами по 16 строк, h=0=>256;
- gfx_scroll_v / _bgi_scroll_cols_raw — верт. И/ИЛИ гориз. за один проход
  без буфера (колонка = accel-burst LD A,A, STOP между read/write делает
  промежуточный OUT Port_Y безопасным), банды по 16 колонок;
- _gfx_addr_shadow_base (адрес неактивной страницы) + gfx_rect_t.
Пример examples/scroll.

check_banks.py + sprinter-cc: отчёт раскладки памяти для ЛЮБОЙ модели
(W1/W2 код/данные, остаток кучи/стека, W3 при --w3, банки при --bank),
не только при --bank.  Док docs/memory-management.md §10.

--w3 (резидентный код окна W3) + сопутствующее: crt0_banked W3_RESIDENT,
mkexe -W, tests/w3probe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 19:38:00 +03:00
parent d1bc97c589
commit 95c22be9bd
46 changed files with 4125 additions and 133 deletions
+1
View File
@@ -18,6 +18,7 @@ uint8_t _gfx_bank = 0x50;
* (CPU 0xC000+), page 1 — 320..639 (CPU 0xC140+), остаток — дескрипторы
* режима/палитра, их не трогаем. */
uint16_t _gfx_addr_base = 0xC000;
uint16_t _gfx_addr_shadow_base = 0xC140;
/* Клип спрайтов по экрану: 1 = включён (безопасно, дефолт), 0 = выключен
* (приложение само гарантирует, что спрайты не выходят за края — даёт
+64
View File
@@ -0,0 +1,64 @@
/*
* gfx_scroll_h — ГОРИЗОНТАЛЬНЫЙ скролл региона (mode-agnostic обёртка).
*
* Берёт чистый фон из НЕактивной (теневой) страницы и копирует его со
* сдвигом dx по X в активную (draw) страницу. Обе стороны — банк 0x50
* (GFX_BANK_NORMAL): запись бьёт VRAM + ОЗУ-копию, т.е. копия
* ОДНОВРЕМЕННО скроллит И «лечит» целевую страницу (старые спрайты
* затираются сдвинутым фоном; scroll-impl-guide.md §2).
*
* dx > 0 — сдвиг в сторону УВЕЛИЧЕНИЯ X (изображение едет вправо,
* открывается полоса слева); dx < 0 — влево (открывается справа);
* dx == 0 — чистая копия фона (полный heal целевой страницы). В обеих
* страницах остаёмся в пределах area. Открывшуюся |dx|-полосу НЕ
* заполняем — возвращаем её в *dirty (её дозаполняет вызывающий).
*
* РЕАЛИЗАЦИЯ — построчно, через _bgi_scroll_rows_raw (без страйдов, один
* OUT Port_Y на строку — на ~5-7% быстрее колоночного _bgi_scroll_cols_raw,
* которому на колонку нужен OUT и на чтение, и на запись). Горизонтальный
* сдвиг — чистая разница CPU-адресов src/dst (src_x vs dst_x), Port_Y для
* read и write один (одна строка). DI/EI бандами по 16 строк — ВНУТРИ
* leaf'а; он же режет высоту на банды и принимает h 1..256 (0=256), так что
* здесь делим только ширину >256 (лимит accel-блока). Перекрытий src/dst
* нет: стороны в разных страницах.
*/
#include "../include/sprite.h"
#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) */
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();
/* Ширина >256 байт — банды ≤256 (размер accel-блока, 0=256). Высоту и
* DI/EI-банды по 16 строк leaf делает сам (h: 0=256). Между бандами по
* ширине CPU-адрес двигаем на 256 (Port_Y выбирает строку). */
while (w) {
uint8_t cw = (w > 256) ? 256 : (uint8_t)w; /* 0 => 256 */
_bgi_scroll_rows_raw(src, dst, cw, h, (uint8_t)area->y);
src += 256; dst += 256;
w -= (w > 256) ? 256 : w;
}
_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;
}
}
+63
View File
@@ -0,0 +1,63 @@
/*
* gfx_scroll_v — ВЕРТИКАЛЬНЫЙ скролл региона (mode-agnostic обёртка).
*
* Симметрична gfx_scroll_h, но сдвиг по Y. Берёт чистый фон из НЕактивной
* (теневой) страницы и копирует его со сдвигом dy по Y в активную (draw)
* страницу. Банк 0x50: копия = скролл + heal целевой страницы (старые
* спрайты затираются сдвинутым фоном; см. gfx_scroll_h / guide §2).
*
* dy > 0 — сдвиг в сторону УВЕЛИЧЕНИЯ Y (изображение едет вниз,
* открывается полоса сверху); dy < 0 — вверх (открывается снизу);
* dy == 0 — чистая копия (полный heal). Открывшуюся |dy|-полосу НЕ
* заполняем — возвращаем в *dirty.
*
* РЕАЛИЗАЦИЯ — колонками, через _bgi_scroll_cols_raw (вертикальный режим
* accel): колонку читаем с Port_Y=ys, пишем с Port_Y=yd (= ys+dy) — сдвиг
* по Y без буфера-посредника (STOP между read и write делает промежуточный
* OUT Port_Y безопасным; см. шапку leaf'а). Один проход, вдвое быстрее
* прежнего grab→blit-через-буфер. Ширину >255 режем на банды колонок
* (leaf сам делит их на DI-скобки по 16). Высота колонки h = area->h|dy|
* (≤256, 0=256 у accel). Перекрытия src/dst нет: стороны в разных страницах.
*/
#include "../include/sprite.h"
#include "../_bgi.h"
void gfx_scroll_v(const gfx_rect_t *area, int16_t dy, gfx_rect_t *dirty) {
uint8_t saved = gfx_get_bank();
uint16_t ady = (dy < 0) ? (uint16_t)(-dy) : (uint16_t)dy;
/* Строку чтения (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) */
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;
gfx_set_bank(GFX_BANK_NORMAL);
_bgi_begin();
/* >255 колонок — банды (leaf 8-битный по числу колонок; 0=выход,
* поэтому ≤255, а не 256). Высота колонки одна на весь проход. */
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;
}
}
+7 -1
View File
@@ -10,5 +10,11 @@ void gfx_set_draw_page(uint8_t page)
_gfx_draw_page = page & 1;
/* Прямые константы короче (0xC000 + (cond ? 0x140 : 0)) на 3
* инструкции Z80 — SDCC не сворачивает сложение констант. */
_gfx_addr_base = _gfx_draw_page ? 0xC140 : 0xC000;
if(_gfx_draw_page) {
_gfx_addr_base = 0xC140;
_gfx_addr_shadow_base = 0xC000;
} else {
_gfx_addr_base = 0xC000;
_gfx_addr_shadow_base = 0xC140;
}
}