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:
+71
-10
@@ -15,8 +15,8 @@
|
|||||||
# 0xA2 to auto-detect whether DSS already mapped W2
|
# 0xA2 to auto-detect whether DSS already mapped W2
|
||||||
# (program > 16 KB) or whether we need to allocate
|
# (program > 16 KB) or whether we need to allocate
|
||||||
# it ourselves (program < 16 KB). Covers 0..30 KB.
|
# it ourselves (program < 16 KB). Covers 0..30 KB.
|
||||||
# big: tiny + banked code in W1 [TODO]
|
# big: tiny + banked code in W1
|
||||||
# huge: small + banked code in W3 [TODO]
|
# huge: small + banked code in W3
|
||||||
# manual: explicit via --memory-manual / --code-loc / --data-loc
|
# manual: explicit via --memory-manual / --code-loc / --data-loc
|
||||||
# --memory-manual SPEC placement spec, only with --memory manual.
|
# --memory-manual SPEC placement spec, only with --memory manual.
|
||||||
# SPEC = comma-separated KEY=VAL list:
|
# SPEC = comma-separated KEY=VAL list:
|
||||||
@@ -33,6 +33,18 @@
|
|||||||
# -Wl FLAG extra linker flag (repeatable)
|
# -Wl FLAG extra linker flag (repeatable)
|
||||||
# --bank N=FILE.c compile FILE.c as bank N; repeatable; pulls crt0_banked
|
# --bank N=FILE.c compile FILE.c as bank N; repeatable; pulls crt0_banked
|
||||||
# automatically and adds -Wl-b_BANKN=0x{N}C000
|
# automatically and adds -Wl-b_BANKN=0x{N}C000
|
||||||
|
# --w3 FILE.c place FILE.c resident in window 3 (0xC000), called
|
||||||
|
# DIRECTLY (no trampoline). Repeatable. Defaults to
|
||||||
|
# --memory small; works in tiny|small|big|huge. DSS maps
|
||||||
|
# all image pages incl. W3. Compiled with --codeseg/
|
||||||
|
# --constseg W3CODE (writable statics stay in W2). In huge
|
||||||
|
# the resident page SHARES W3 with trampoline banks: crt0
|
||||||
|
# captures/restores it (W3_RESIDENT), mkexe gets -W.
|
||||||
|
# Rules: W3 code must NOT switch the W3 page; W1/W2 code that
|
||||||
|
# swaps W3 to another page must restore it; no writable data
|
||||||
|
# belongs in W3 (code + rodata only). From a __banked
|
||||||
|
# context the resident W3 code is unreachable (but resident
|
||||||
|
# -> __banked via the W1 trampoline is fine).
|
||||||
# --mkexe FLAG extra mkexe flag (repeatable; e.g. --mkexe -p --mkexe 0)
|
# --mkexe FLAG extra mkexe flag (repeatable; e.g. --mkexe -p --mkexe 0)
|
||||||
# --max-allocs N SDCC --max-allocs-per-node (default: 100000 — smaller/
|
# --max-allocs N SDCC --max-allocs-per-node (default: 100000 — smaller/
|
||||||
# faster code at the cost of compile time; pass a lower
|
# faster code at the cost of compile time; pass a lower
|
||||||
@@ -88,6 +100,11 @@ SOURCES=()
|
|||||||
LD_EXTRA=()
|
LD_EXTRA=()
|
||||||
MKEXE_EXTRA=()
|
MKEXE_EXTRA=()
|
||||||
BANK_SPECS=() # entries like "1=engine.c"
|
BANK_SPECS=() # entries like "1=engine.c"
|
||||||
|
W3_SPECS=() # entries like "mod.c" — резидентные модули окна W3 (--w3)
|
||||||
|
W3_RELS=() # заполняется при компиляции W3-модулей
|
||||||
|
W3_LD_FLAGS=() # -Wl-b_W3CODE=0xC000, если есть --w3
|
||||||
|
USER_SET_MEMORY="" # непусто, если --memory задан явно (для --w3 авто-small)
|
||||||
|
W3_RESIDENT_HUGE=0 # 1, если --w3 в режиме huge (резидент делит W3 с банками)
|
||||||
MAX_ALLOCS="100000" # sdcc --max-allocs-per-node; дефолт 100000 — агрессивная
|
MAX_ALLOCS="100000" # sdcc --max-allocs-per-node; дефолт 100000 — агрессивная
|
||||||
# регистровая аллокация (медленнее компиляция, меньше/
|
# регистровая аллокация (медленнее компиляция, меньше/
|
||||||
# быстрее код; как fast-сборки библиотек). --max-allocs N
|
# быстрее код; как fast-сборки библиотек). --max-allocs N
|
||||||
@@ -112,8 +129,9 @@ while [[ $# -gt 0 ]]; do
|
|||||||
-S) STACK_ADDR="$2"; shift 2;;
|
-S) STACK_ADDR="$2"; shift 2;;
|
||||||
--code-loc) USER_CODE_LOC="$2"; shift 2;;
|
--code-loc) USER_CODE_LOC="$2"; shift 2;;
|
||||||
--data-loc) USER_DATA_LOC="$2"; shift 2;;
|
--data-loc) USER_DATA_LOC="$2"; shift 2;;
|
||||||
--memory) MEMORY_MODE="$2"; shift 2;;
|
--memory) MEMORY_MODE="$2"; USER_SET_MEMORY=1; shift 2;;
|
||||||
--memory-manual) MEMORY_MANUAL="$2"; shift 2;;
|
--memory-manual) MEMORY_MANUAL="$2"; shift 2;;
|
||||||
|
--w3) W3_SPECS+=("$2"); shift 2;;
|
||||||
--stack-size) STACK_SIZE="$2"; shift 2;;
|
--stack-size) STACK_SIZE="$2"; shift 2;;
|
||||||
-Wl) LD_EXTRA+=("$2"); shift 2;;
|
-Wl) LD_EXTRA+=("$2"); shift 2;;
|
||||||
--bank) BANK_SPECS+=("$2"); shift 2;;
|
--bank) BANK_SPECS+=("$2"); shift 2;;
|
||||||
@@ -132,6 +150,24 @@ done
|
|||||||
[[ -z "$OUT" ]] && { echo "sprinter-cc: -o NAME is required" >&2; exit 1; }
|
[[ -z "$OUT" ]] && { echo "sprinter-cc: -o NAME is required" >&2; exit 1; }
|
||||||
[[ ${#SOURCES[@]} -eq 0 ]] && { echo "sprinter-cc: no input files" >&2; exit 1; }
|
[[ ${#SOURCES[@]} -eq 0 ]] && { echo "sprinter-cc: no input files" >&2; exit 1; }
|
||||||
|
|
||||||
|
# ------- --w3: резидентный код окна W3 (подход A) ----------------------------
|
||||||
|
# W3-модули едут в область W3CODE @0xC000 и вызываются напрямую (без
|
||||||
|
# трамплинов). Работает поверх small-лейаута: main-код с 0x4100 (W1/W2),
|
||||||
|
# W3 свободно, DSS маппит все страницы образа сам (проверено tests/w3probe).
|
||||||
|
# --w3 подразумевает small; с huge несовместим (там W3 занят банками).
|
||||||
|
if [[ ${#W3_SPECS[@]} -gt 0 ]]; then
|
||||||
|
[[ -z "$USER_SET_MEMORY" ]] && MEMORY_MODE="small" # --w3 → small по умолчанию
|
||||||
|
case "$MEMORY_MODE" in
|
||||||
|
tiny|small|big|huge) : ;; # поддержаны (huge — резидент делит W3 с банками)
|
||||||
|
*) echo "sprinter-cc: --w3 поддержан для tiny|small|big|huge (дано: $MEMORY_MODE)" >&2; exit 1;;
|
||||||
|
esac
|
||||||
|
# huge: резидентный W3-код (0xC000, HOME) сосуществует с трамплин-банками
|
||||||
|
# W3 — crt0_banked захватывает резидентную страницу и возвращает её
|
||||||
|
# дефолтом после загрузки банков (см. crt0_banked.s, W3_RESIDENT).
|
||||||
|
W3_RESIDENT_HUGE=0
|
||||||
|
[[ "$MEMORY_MODE" == "huge" ]] && W3_RESIDENT_HUGE=1
|
||||||
|
fi
|
||||||
|
|
||||||
# ------- BGI graphics driver selection (--gfx) -------------------------------
|
# ------- BGI graphics driver selection (--gfx) -------------------------------
|
||||||
# graphics.h — mode-agnostic слой; конкретный видеорежим задаёт driver-
|
# graphics.h — mode-agnostic слой; конкретный видеорежим задаёт driver-
|
||||||
# архив. Одновременно только один. drv16 пока не реализован.
|
# архив. Одновременно только один. drv16 пока не реализован.
|
||||||
@@ -186,7 +222,8 @@ case "$MEMORY_MODE" in
|
|||||||
huge)
|
huge)
|
||||||
# small layout + banked code in W3. crt0_banked auto-detects W2
|
# small layout + banked code in W3. crt0_banked auto-detects W2
|
||||||
# the same way crt0_small does, then loads banks from the .EXE.
|
# the same way crt0_small does, then loads banks from the .EXE.
|
||||||
MODE_CODE_LOC="0x4100"; MODE_DATA_LOC="0x8000"
|
# MODE_CODE_LOC="0x4100"; MODE_DATA_LOC="0x8000"
|
||||||
|
MODE_CODE_LOC="0x4100"; MODE_DATA_LOC="0"
|
||||||
;;
|
;;
|
||||||
manual)
|
manual)
|
||||||
# Defaults if SPEC omits a key.
|
# Defaults if SPEC omits a key.
|
||||||
@@ -280,6 +317,7 @@ asm_runtime() {
|
|||||||
local prefix=""
|
local prefix=""
|
||||||
[[ $DEBUG_RT -eq 1 ]] && prefix+="DEBUG_RT = 1"$'\n'
|
[[ $DEBUG_RT -eq 1 ]] && prefix+="DEBUG_RT = 1"$'\n'
|
||||||
[[ $BANK_W1 -eq 1 ]] && prefix+="BANK_W1 = 1"$'\n'
|
[[ $BANK_W1 -eq 1 ]] && prefix+="BANK_W1 = 1"$'\n'
|
||||||
|
[[ $W3_RESIDENT_HUGE -eq 1 ]] && prefix+="W3_RESIDENT = 1"$'\n'
|
||||||
if [[ -n "$prefix" ]]; then
|
if [[ -n "$prefix" ]]; then
|
||||||
local patched="$WORK/$(basename "$src" .s)_patched.s"
|
local patched="$WORK/$(basename "$src" .s)_patched.s"
|
||||||
{ printf "%s" "$prefix"; cat "$src"; } > "$patched"
|
{ printf "%s" "$prefix"; cat "$src"; } > "$patched"
|
||||||
@@ -305,9 +343,14 @@ if [[ -n "$STACK_SIZE" ]]; then
|
|||||||
printf " .module sprinter_heap_top\n ___sdcc_heap_end = 0x%04X\n .globl ___sdcc_heap_end\n" \
|
printf " .module sprinter_heap_top\n ___sdcc_heap_end = 0x%04X\n .globl ___sdcc_heap_end\n" \
|
||||||
"$_heap_top" > "$WORK/heap_top.s"
|
"$_heap_top" > "$WORK/heap_top.s"
|
||||||
HEAP_TOP_SRC="$WORK/heap_top.s"
|
HEAP_TOP_SRC="$WORK/heap_top.s"
|
||||||
[[ $VERBOSE -eq 1 ]] && echo " heap_top: custom HEAP_TOP=$(printf '0x%04X' $_heap_top) (stack reserve $_stack_sz bytes)"
|
HEAP_TOP_VAL=$(printf '0x%04X' "$_heap_top")
|
||||||
|
[[ $VERBOSE -eq 1 ]] && echo " heap_top: custom HEAP_TOP=$HEAP_TOP_VAL (stack reserve $_stack_sz bytes)"
|
||||||
else
|
else
|
||||||
HEAP_TOP_SRC="$RUNTIME/heap_top.s"
|
HEAP_TOP_SRC="$RUNTIME/heap_top.s"
|
||||||
|
# Дефолт из runtime/heap_top.s (для отчёта раскладки) — обычно 0xBB00.
|
||||||
|
HEAP_TOP_VAL=$(grep -oiE '___sdcc_heap_end[[:space:]]*=[[:space:]]*0x[0-9A-Fa-f]+' "$RUNTIME/heap_top.s" \
|
||||||
|
| grep -oiE '0x[0-9A-Fa-f]+' | head -1)
|
||||||
|
HEAP_TOP_VAL="${HEAP_TOP_VAL:-0xBB00}"
|
||||||
fi
|
fi
|
||||||
run "$SDASZ80" -o "$HEAP_TOP_REL" "$HEAP_TOP_SRC"
|
run "$SDASZ80" -o "$HEAP_TOP_REL" "$HEAP_TOP_SRC"
|
||||||
|
|
||||||
@@ -322,6 +365,20 @@ for src in "${SOURCES[@]}"; do
|
|||||||
USER_RELS+=("$rel")
|
USER_RELS+=("$rel")
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# 2b. --w3 resident modules → .rel. --codeseg/--constseg W3CODE кладёт код
|
||||||
|
# и rodata в W3 (0xC000); --dataseg НЕ трогаем — писучие статики
|
||||||
|
# остаются в обычном _DATA (W2). Прямые вызовы, без трамплинов.
|
||||||
|
if [[ ${#W3_SPECS[@]} -gt 0 ]]; then
|
||||||
|
for src in "${W3_SPECS[@]}"; do
|
||||||
|
rel="$WORK/w3_$(basename "$src" .c).rel"
|
||||||
|
run "$SDCC" "${CC_FLAGS[@]}" --codeseg W3CODE --constseg W3CODE \
|
||||||
|
-c -o "$rel" "$src"
|
||||||
|
W3_RELS+=("$rel")
|
||||||
|
done
|
||||||
|
W3_LD_FLAGS+=("-Wl-b_W3CODE=0xC000")
|
||||||
|
[[ $VERBOSE -eq 1 ]] && echo " w3: ${#W3_SPECS[@]} module(s) resident @0xC000 (direct call)"
|
||||||
|
fi
|
||||||
|
|
||||||
# 3. bank infrastructure — required whenever crt0_banked is in play.
|
# 3. bank infrastructure — required whenever crt0_banked is in play.
|
||||||
# Always assemble bank.s for _bank_pages + the bcall/bjump trampolines.
|
# Always assemble bank.s for _bank_pages + the bcall/bjump trampolines.
|
||||||
# If the user did not pass any --bank, generate a tiny stub providing
|
# If the user did not pass any --bank, generate a tiny stub providing
|
||||||
@@ -369,6 +426,7 @@ IHX="$WORK/$(basename "$OUT" .exe).ihx"
|
|||||||
LINK_FLAGS=(-mz80 --no-std-crt0 --std-c99 --opt-code-size
|
LINK_FLAGS=(-mz80 --no-std-crt0 --std-c99 --opt-code-size
|
||||||
--code-loc "$CODE_LOC" --data-loc "$DATA_LOC")
|
--code-loc "$CODE_LOC" --data-loc "$DATA_LOC")
|
||||||
LINK_FLAGS+=("${BANK_LD_FLAGS[@]}")
|
LINK_FLAGS+=("${BANK_LD_FLAGS[@]}")
|
||||||
|
LINK_FLAGS+=("${W3_LD_FLAGS[@]}")
|
||||||
for f in "${LD_EXTRA[@]}"; do LINK_FLAGS+=("$f"); done
|
for f in "${LD_EXTRA[@]}"; do LINK_FLAGS+=("$f"); done
|
||||||
# libsprinter.lib via -l/-L (sdcc passes -lsprinter through to sdldz80).
|
# libsprinter.lib via -l/-L (sdcc passes -lsprinter through to sdldz80).
|
||||||
#
|
#
|
||||||
@@ -379,12 +437,12 @@ for f in "${LD_EXTRA[@]}"; do LINK_FLAGS+=("$f"); done
|
|||||||
# the warning is just noise. In verbose mode show everything.
|
# the warning is just noise. In verbose mode show everything.
|
||||||
if [[ $VERBOSE -eq 1 ]]; then
|
if [[ $VERBOSE -eq 1 ]]; then
|
||||||
run "$SDCC" "${LINK_FLAGS[@]}" -o "$IHX" \
|
run "$SDCC" "${LINK_FLAGS[@]}" -o "$IHX" \
|
||||||
"$CRT0_REL" "$HEAP_TOP_REL" "${USER_RELS[@]}" "${BANK_RELS[@]}" \
|
"$CRT0_REL" "$HEAP_TOP_REL" "${USER_RELS[@]}" "${BANK_RELS[@]}" "${W3_RELS[@]}" \
|
||||||
"-L$LIB_DIR" "${GFX_LD[@]}" "$LIBC_LD"
|
"-L$LIB_DIR" "${GFX_LD[@]}" "$LIBC_LD"
|
||||||
else
|
else
|
||||||
# Drop the warning line + its two follow-up "Library:" lines.
|
# Drop the warning line + its two follow-up "Library:" lines.
|
||||||
run "$SDCC" "${LINK_FLAGS[@]}" -o "$IHX" \
|
run "$SDCC" "${LINK_FLAGS[@]}" -o "$IHX" \
|
||||||
"$CRT0_REL" "$HEAP_TOP_REL" "${USER_RELS[@]}" "${BANK_RELS[@]}" \
|
"$CRT0_REL" "$HEAP_TOP_REL" "${USER_RELS[@]}" "${BANK_RELS[@]}" "${W3_RELS[@]}" \
|
||||||
"-L$LIB_DIR" "${GFX_LD[@]}" "$LIBC_LD" 2>&1 \
|
"-L$LIB_DIR" "${GFX_LD[@]}" "$LIBC_LD" 2>&1 \
|
||||||
| awk '
|
| awk '
|
||||||
/^\?ASlink-Warning-Definition of public symbol/ { skip = 3 }
|
/^\?ASlink-Warning-Definition of public symbol/ { skip = 3 }
|
||||||
@@ -393,9 +451,11 @@ else
|
|||||||
'
|
'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Quick bank-size check (only meaningful if there are banks).
|
# Отчёт по раскладке памяти + проверка лимитов — для ЛЮБОЙ модели памяти
|
||||||
if [[ ${#BANK_SPECS[@]} -gt 0 ]] && [[ -f "${IHX%.ihx}.map" ]]; then
|
# (сколько свободно в W1/W2, W3 при --w3, и в каждом банке при --bank).
|
||||||
python3 "$CHECK_BANKS" "${IHX%.ihx}.map" || true
|
if [[ -f "${IHX%.ihx}.map" ]]; then
|
||||||
|
python3 "$CHECK_BANKS" "${IHX%.ihx}.map" \
|
||||||
|
--mode "$MEMORY_MODE" --heap-top "$HEAP_TOP_VAL" --stack "$STACK_ADDR" || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 5. mkexe → .exe. In BIG mode tell mkexe banks live at 0x4000 (W1).
|
# 5. mkexe → .exe. In BIG mode tell mkexe banks live at 0x4000 (W1).
|
||||||
@@ -404,6 +464,7 @@ fi
|
|||||||
MK_PREFIX=()
|
MK_PREFIX=()
|
||||||
[[ $VERBOSE -eq 1 ]] && MK_PREFIX+=(-v)
|
[[ $VERBOSE -eq 1 ]] && MK_PREFIX+=(-v)
|
||||||
[[ $BANK_W1 -eq 1 ]] && MK_PREFIX+=(-B 0x4000)
|
[[ $BANK_W1 -eq 1 ]] && MK_PREFIX+=(-B 0x4000)
|
||||||
|
[[ $W3_RESIDENT_HUGE -eq 1 ]] && MK_PREFIX+=(-W)
|
||||||
MK_PREFIX+=("${MKEXE_EXTRA[@]}")
|
MK_PREFIX+=("${MKEXE_EXTRA[@]}")
|
||||||
MK_PREFIX+=(-L "$LOAD_ADDR" -E "$ENTRY_ADDR" -S "$STACK_ADDR" -o "$OUT")
|
MK_PREFIX+=(-L "$LOAD_ADDR" -E "$ENTRY_ADDR" -S "$STACK_ADDR" -o "$OUT")
|
||||||
run "$MKEXE" "${MK_PREFIX[@]}" "$IHX"
|
run "$MKEXE" "${MK_PREFIX[@]}" "$IHX"
|
||||||
|
|||||||
@@ -259,6 +259,14 @@ putsprite новой (save-буфер не нужен). Низкий урове
|
|||||||
рисовать банком 0x50, спрайты/оверлеи — putsprite/0x5C; буферы
|
рисовать банком 0x50, спрайты/оверлеи — putsprite/0x5C; буферы
|
||||||
образов — вне W3 (< 0xC000). Тесты: tests/sprites, tests/gfxbanks,
|
образов — вне W3 (< 0xC000). Тесты: tests/sprites, tests/gfxbanks,
|
||||||
tests/bgi_img.
|
tests/bgi_img.
|
||||||
|
Скролл региона из НЕактивной страницы в активную (<sprite.h>):
|
||||||
|
`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
|
||||||
|
цели (банк 0x50). Открывшуюся полосу |d| не заполняют — возвращают в
|
||||||
|
*dirty (NULL = не нужно). Пример: examples/scroll.
|
||||||
Стиль линий (Ф2d): `setlinestyle(style,upattern,thick)`/`getlinesettings`
|
Стиль линий (Ф2d): `setlinestyle(style,upattern,thick)`/`getlinesettings`
|
||||||
(SOLID/DOTTED/CENTER/DASHED/USERBIT + NORM/THICK) — на line/rectangle/
|
(SOLID/DOTTED/CENTER/DASHED/USERBIT + NORM/THICK) — на line/rectangle/
|
||||||
drawpoly.
|
drawpoly.
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
|
|
||||||
1) если я и DATA и CODE размещаю в одном окне (W1 - #4000 или W2 - #8000, неважно),
|
|
||||||
то при вызове set_videomode глобальные переменные (errno, g_text_attr) не меняют
|
|
||||||
своих значений.
|
|
||||||
если же DATA и CODE находятся в разных окнах (не важно где DATA - в W1 или W2, главное
|
|
||||||
что не в том где CODE) - то при вызове se_videomode значения глобальных переменных меняются
|
|
||||||
|
|
||||||
То есть похоже что для DATA не назначается отдельный блок памяти а назначается только для CODE
|
|
||||||
Это полностью соответствует документации - если приложение менее 16К (как у нас) то ему выделяется
|
|
||||||
только одна страница. И получается что работа со второй страницей идет несанкционированно (ей память
|
|
||||||
не выделена).
|
|
||||||
|
|
||||||
Потому предлагается
|
|
||||||
1) сейчас размещать ВСЕ в одной странице (и DATA и CODE и стек) - в W2.
|
|
||||||
|
|
||||||
2) дальше - добавить в нашу обертку sprinter-cc режимы памяти -
|
|
||||||
-tiny - все приложение помещается в одну страницу - в W2 (и DATA и CODE и стек)
|
|
||||||
-small - приложение помещается в две страницы - CODE в W1, DATA и стек - в W2.
|
|
||||||
в этом режиме над отдельно выделять и маппить страницу в W2 для DATA и стека
|
|
||||||
-big - DATA, CODE и стек помещаются в одну страницу W2 как в -tiny, добавляется поддержка banked в W1,
|
|
||||||
страница W3 остается служебной и для работы с граффикой из banked code
|
|
||||||
-huge - приложение помещается в двух страницах как и -small но так же добавляется поддержка banked но
|
|
||||||
уже в страницу W3
|
|
||||||
|
|
||||||
Из документации -
|
|
||||||
|
|
||||||
> Теперь адресса #4000..#7FFF,#8000..#BFFF,#C000..#FFFF, когда ДСС
|
|
||||||
передаёт управление эти прогораммам, какие банки там нахадятся по
|
|
||||||
умолчанию?
|
|
||||||
|
|
||||||
В зависимости от адреса загрузки и размера приложения DSS выделяет
|
|
||||||
необходимое число страниц памяти. Так при размере меньше 16К будет
|
|
||||||
выделена
|
|
||||||
одна страница, при размере больше 16К - две, и т.д. В окна с
|
|
||||||
"неиспользуемым" адресном пространством будет подключатся
|
|
||||||
специальная страница #FF.Если приложению требуется памяти больше чем
|
|
||||||
зарезервировано в exe-файле, оно должно выделить себе дополнительный
|
|
||||||
блок памяти самостоятельно.
|
|
||||||
|
|
||||||
|
|
||||||
> В конфигурации спринтер
|
|
||||||
> по #0000..#3FFF, при работе ДСС находится сама ДСС с её Резетами,
|
|
||||||
> чтоб использывать когда сюда подставленна страница пользователся
|
|
||||||
> резеты не доступны!.
|
|
||||||
|
|
||||||
Это так в нижних 16K находится DSS / BIOS в остальных 48К
|
|
||||||
приложение, но с
|
|
||||||
определенными особенностями. Стек не должен быть выше #BFFF при
|
|
||||||
вызове DSS и ниже #8000 при вызове некоторых функций BIOS.
|
|
||||||
|
|
||||||
|
|
||||||
Так же посмотри вот сюда - возможно нам придется для режимов -small и -huge делать свой первичным загрузчиком -
|
|
||||||
Из документации -
|
|
||||||
|
|
||||||
Выполнение EXE-файла осуществляется по следующим пунктам:
|
|
||||||
1) Открывает exe-файл на чтение;
|
|
||||||
2) Считывает в рабочую область префикс exe-файла;
|
|
||||||
3) Выделяет блок памяти, требуемый для загрузки всего файла или первичного
|
|
||||||
загрузчика, если его размер не равен нулю;
|
|
||||||
4) Сохраняет стек;
|
|
||||||
5) Подключает страницы из выделенного блока;
|
|
||||||
6) Строит префикс запуска программы и устанавливает на него регистр IX;
|
|
||||||
7) Считывает файл по адресу указанному в смещении 16 (Адрес расположения кода в
|
|
||||||
памяти);
|
|
||||||
8) Закрывает exe-файл, если это не первичный загрузчик;
|
|
||||||
9) Устанавливает стек равным значению из смещения 20 (Адрес расположения стека);
|
|
||||||
10) Передает управление по адресу указанному в смещении 18 (Адрес запуска);
|
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
# Управление памятью в sprinter-cc
|
||||||
|
|
||||||
|
Документ описывает модель памяти Sprinter (Sp2000), режимы памяти обёртки
|
||||||
|
`sprinter-cc` и способы размещения кода/данных: single-page, split, банки
|
||||||
|
(трамплины) и резидентный код окна W3 (`--w3`).
|
||||||
|
|
||||||
|
Всё в этом документе подтверждено сборкой и прогоном в MAME v3.06 / DSS 1.71.57
|
||||||
|
(см. `tests/w3probe`, `tests/banktest`, `tests/banklocl`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Аппаратная модель памяти
|
||||||
|
|
||||||
|
Z80 видит 64 КБ, разбитые на **четыре окна по 16 КБ**. Каждое окно независимо
|
||||||
|
маппится на физическую страницу через порт-регистр страницы:
|
||||||
|
|
||||||
|
| Окно | Адреса | Порт страницы | Назначение по умолчанию |
|
||||||
|
|------|---------------|---------------|--------------------------|
|
||||||
|
| W0 | `0x0000-0x3FFF` | `0x82` | **DSS / BIOS** (RST-ы, системные вызовы) |
|
||||||
|
| W1 | `0x4000-0x7FFF` | `0xA2` | приложение |
|
||||||
|
| W2 | `0x8000-0xBFFF` | `0xC2` | приложение |
|
||||||
|
| W3 | `0xC000-0xFFFF` | `0xE2` | приложение / графика / банки |
|
||||||
|
|
||||||
|
- Запись в порт `0x{8/A/C/E}2` меняет физ-страницу окна; чтение возвращает
|
||||||
|
текущую страницу.
|
||||||
|
- **W0 занят DSS/BIOS** — там живут RST-обработчики (`RST #08` BIOS, `RST #10`
|
||||||
|
ESTEX). Пока в W0 стоит системная страница, вызовы доступны; подменять W0
|
||||||
|
нельзя без потери RST-ов.
|
||||||
|
- «Неиспользуемое» окно маппится на **специальную страницу `#FF`**: чтение даёт
|
||||||
|
`0xFF`, запись игнорируется. Это ключевая причина «молчаливой» порчи данных —
|
||||||
|
см. §7.
|
||||||
|
|
||||||
|
### Порты в C
|
||||||
|
|
||||||
|
`<sprinter.h>` даёт SFR-обёртки: `_io_page_w0..w3` (чтение/запись порта),
|
||||||
|
`sprinter_page_w0..w3(page)`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Как DSS загружает EXE
|
||||||
|
|
||||||
|
Из документации DSS, последовательность `EXEC`:
|
||||||
|
|
||||||
|
1. Открыть exe-файл на чтение.
|
||||||
|
2. Считать префикс exe в рабочую область.
|
||||||
|
3. **Выделить блок памяти** размером под весь файл (если `loader==0`) или под
|
||||||
|
первичный загрузчик (`loader>0`).
|
||||||
|
4. Сохранить стек.
|
||||||
|
5. **Подключить страницы** выделенного блока в окна (последовательно от окна
|
||||||
|
адреса загрузки: W1→W2→W3…).
|
||||||
|
6. Построить префикс запуска → регистр `IX`.
|
||||||
|
7. Считать файл по адресу загрузки (смещение 16 в заголовке).
|
||||||
|
8. Закрыть exe, **если это не первичный загрузчик** (`loader==0`).
|
||||||
|
9. Установить `SP` = значение из смещения 20 (адрес стека).
|
||||||
|
10. Передать управление по адресу из смещения 18 (entry).
|
||||||
|
|
||||||
|
**Следствия, на которых стоит вся схема памяти:**
|
||||||
|
|
||||||
|
- DSS выделяет **число страниц по размеру образа**: `<16 КБ` → 1 страница,
|
||||||
|
`16..32 КБ` → 2, `32..48 КБ` → 3. Лишние окна = страница `#FF`.
|
||||||
|
- Страницы маппятся **подряд** начиная с окна адреса загрузки. Образ, тянущийся
|
||||||
|
`0x4100..0xFFFF` (3 страницы), даёт W1+W2+W3 замапленными автоматически —
|
||||||
|
**это и есть база для резидентного кода W3** (§6, подход A).
|
||||||
|
- `loader>0` (multi-bank .exe) → DSS грузит только HOME-часть и **оставляет
|
||||||
|
файл открытым** (handle в `IX-3`), а crt0 дочитывает банки сам (§5).
|
||||||
|
|
||||||
|
Упаковкой в этот формат занимается `toolchain/mkexe`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Правила стека (критично)
|
||||||
|
|
||||||
|
- **`SP ≤ 0xBFFF`** при вызовах DSS (ESTEX, `RST #10`).
|
||||||
|
- **`SP ≥ 0x8000`** при вызовах некоторых функций BIOS (`RST #08`).
|
||||||
|
- Пересечение этих требований → **стек обязан жить в W2** (`0x8000-0xBFFF`).
|
||||||
|
По умолчанию `SP` инициализируется в `0xBFFE`.
|
||||||
|
|
||||||
|
Проверено (`tests/w3probe`): во всех режимах на входе `main` `SP ≈ 0xBFFC`, т.е.
|
||||||
|
в W2.
|
||||||
|
|
||||||
|
**Chicken-and-egg для split-режимов** (small/huge): DSS ставит `SP=0xBFFE` из
|
||||||
|
заголовка, но для программ `<16 КБ` окно W2 ещё не выделено (там `#FF`). Пуши
|
||||||
|
туда теряются, первый `call` возвращается в мусор. Поэтому `crt0_small`/
|
||||||
|
`crt0_banked` сначала работают на **загрузочном стеке в W1** (реальное ОЗУ),
|
||||||
|
маппят W2 и только потом переставляют `SP=0xBFFE`. Маппинг W2 делается через
|
||||||
|
**ESTEX `$3A SETWIN2`** (не BIOS `$C4`+OUT — тому нужен стек уже в W2).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Режимы памяти
|
||||||
|
|
||||||
|
Выбираются флагом `sprinter-cc --memory MODE` (по умолчанию `tiny`). Режим задаёт
|
||||||
|
адрес кода, размещение данных, crt0 и наличие банков.
|
||||||
|
|
||||||
|
| Режим | CODE | DATA/BSS | Стек | Банки | crt0 | Первичный загрузчик |
|
||||||
|
|--------|------|----------|------|-------|------|----------------------|
|
||||||
|
| `tiny` | W2 `0x8100` | за кодом (W2) | W2 | — | `crt0.s` | нет (1 страница) |
|
||||||
|
| `small` | W1 `0x4100` | за кодом (W1→W2) | W2 | — | `crt0_small.s` | да (сам маппит W2) |
|
||||||
|
| `big` | W2 `0x8100` | за кодом (W2) | W2 | W1 (трамплины) | `crt0_banked.s` (BANK_W1) | да |
|
||||||
|
| `huge` | W1 `0x4100` | за кодом (W1→W2) | W2 | W3 (трамплины) | `crt0_banked.s` | да |
|
||||||
|
| `manual` | явно | явно | W2 | — | `crt0.s` | зависит |
|
||||||
|
|
||||||
|
Во всех режимах **DATA цепляется линкером сразу за кодом** (`--data-loc 0`), а не
|
||||||
|
кладётся по фиксированному адресу. Раньше `huge` использовал фиксированный
|
||||||
|
`DATA=0x8000`, что ломалось при коде `>16 КБ` (код перетекал в W2 и накрывал
|
||||||
|
DATA); сейчас DATA динамически идёт за концом кода.
|
||||||
|
|
||||||
|
### 4.1 `tiny` — всё в одной странице
|
||||||
|
|
||||||
|
```
|
||||||
|
0x8000..0x80FF зарезервировано (startup-prefix)
|
||||||
|
0x8100 _start / _CODE … _DATA … _BSS … _HEAP
|
||||||
|
0xBB00 heap top (по умолчанию)
|
||||||
|
0xBFFE стек ↓
|
||||||
|
```
|
||||||
|
|
||||||
|
- Один блок 16 КБ, DSS маппит его в W2. W1 и W3 = `#FF`.
|
||||||
|
- Ничего выделять/маппить не надо; `crt0.s` предполагает, что W2 уже дан DSS.
|
||||||
|
- Практический потолок кода+данных+кучи+стека ≈ 14 КБ.
|
||||||
|
- `--code-loc 0x8100 --data-loc 0`; mkexe `-L 0x8100 -E 0x8100 -S 0xBFFE`.
|
||||||
|
|
||||||
|
### 4.2 `small` — CODE в W1, данные перетекают в W2
|
||||||
|
|
||||||
|
```
|
||||||
|
0x4100 _CODE … (W1)
|
||||||
|
… за кодом → _DATA _BSS _HEAP (W1, перетекает в W2)
|
||||||
|
0xBB00 heap top
|
||||||
|
0xBFFE стек ↓ (W2)
|
||||||
|
```
|
||||||
|
|
||||||
|
- Покрывает ~0..30 КБ (код+данные вместе).
|
||||||
|
- `crt0_small.s` авто-определяет W2: читает порт `0xC2`. Если `≠0xFF` — DSS уже
|
||||||
|
дал W2 (образ `>16 КБ`), маппить не надо. Если `=0xFF` — сам выделяет страницу
|
||||||
|
(`ESTEX $3D GETMEM`) и маппит (`ESTEX $3A SETWIN2`).
|
||||||
|
- `--code-loc 0x4100 --data-loc 0`.
|
||||||
|
|
||||||
|
### 4.3 `big` — tiny + банки в W1
|
||||||
|
|
||||||
|
- База как `tiny` (CODE+DATA+стек в W2, `0x8100`).
|
||||||
|
- **Свапаемые банки в W1** (`0x4000-0x7FFF`, порт `0xA2`), вызываются через
|
||||||
|
трамплины (§5). `crt0_banked.s` собирается с `BANK_W1=1`.
|
||||||
|
- mkexe получает `-B 0x4000` (банки живут в W1). Виртуальный адрес банка N =
|
||||||
|
`0x{N}4000`.
|
||||||
|
- W3 свободно — доступно под графику или резидентный код (`--w3`).
|
||||||
|
|
||||||
|
### 4.4 `huge` — small + банки в W3
|
||||||
|
|
||||||
|
- База как `small` (CODE `0x4100`, DATA за кодом, авто-детект W2).
|
||||||
|
- **Свапаемые банки в W3** (`0xC000-0xFFFF`, порт `0xE2`), через трамплины.
|
||||||
|
Виртуальный адрес банка N = `0x{N}C000`.
|
||||||
|
- `crt0_banked.s` (без `BANK_W1`) грузит банки из .exe после старта.
|
||||||
|
|
||||||
|
### 4.5 `manual` — явное размещение
|
||||||
|
|
||||||
|
`--memory manual --memory-manual SPEC`, где SPEC = список `KEY=VAL`:
|
||||||
|
`CODE=W1|W2`, `DATA=W1|W2|SAME`, `BANKED=W1|W3`. Плюс прямые `--code-loc` /
|
||||||
|
`--data-loc` / `-L` / `-E` / `-S` перекрывают дефолты любого режима.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Банки и трамплины (`--bank`)
|
||||||
|
|
||||||
|
Для `big`/`huge`. Модуль-банк собирается в отдельную область и линкуется по
|
||||||
|
**виртуальному 24-битному адресу** (`bank_id` в старшем байте):
|
||||||
|
|
||||||
|
```
|
||||||
|
sprinter-cc --memory huge --bank 1=engine.c --bank 2=audio.c -o app.exe main.c
|
||||||
|
```
|
||||||
|
|
||||||
|
- Банк N компилируется `--codeseg/--constseg/--dataseg BANKN`, линкуется
|
||||||
|
`-Wl-b_BANKN=0x{N}C000` (huge) или `0x{N}4000` (big).
|
||||||
|
- `main.c` обязан объявить `const uint8_t n_banks = N;` — `crt0_banked` читает
|
||||||
|
это **до gsinit**, поэтому только `const` (инициализатор ещё не скопирован).
|
||||||
|
- Функции банка помечаются `__banked` — SDCC генерирует вызов через трамплин
|
||||||
|
`___sdcc_bcall_ehl` (в `_CODE`/W1, всегда замаплен): он сохраняет текущую
|
||||||
|
страницу окна, маппит нужный банк (`_bank_pages[id]`), `jp` в функцию, по
|
||||||
|
возврату восстанавливает страницу.
|
||||||
|
- Загрузка: mkexe пакует `header + HOME + bank1(16К) + bank2(16К)…`,
|
||||||
|
`loader=размер HOME`; `crt0_banked` выделяет страницы (`GETMEM`), маппит и
|
||||||
|
дочитывает каждый банк `ESTEX READ` из открытого файла.
|
||||||
|
- Проверка размеров банков — `toolchain/check_banks.py` (часть отчёта
|
||||||
|
раскладки, см. §10).
|
||||||
|
|
||||||
|
Writable bank-local данные возможны, но с оговорками — см.
|
||||||
|
`memory/bank_local_data_pattern`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Резидентный код окна W3 (`--w3`)
|
||||||
|
|
||||||
|
**Альтернатива банкам без трамплинов.** Модуль размещается резидентно в W3
|
||||||
|
(`0xC000`) и вызывается **прямым `call`** — как обычная функция. Работает во
|
||||||
|
**всех режимах** (`tiny|small|big|huge`); по умолчанию подразумевает `small`.
|
||||||
|
|
||||||
|
```
|
||||||
|
sprinter-cc --w3 render.c -o app.exe main.c # → small
|
||||||
|
sprinter-cc --memory huge --w3 render.c --bank 1=lvl.c -o app.exe main.c
|
||||||
|
```
|
||||||
|
|
||||||
|
**Как работает (подход A):** образ с областью `W3CODE`@`0xC000` тянется до `0xC0xx`
|
||||||
|
(≥3 страницы), и DSS сам маппит W3 при загрузке (§2) — **загрузчик в crt0 не
|
||||||
|
нужен** (кроме huge). Прямые вызовы резолвятся линкером в реальные `0xC0xx`.
|
||||||
|
|
||||||
|
**Механизм сборки:**
|
||||||
|
- W3-модуль компилируется `--codeseg W3CODE --constseg W3CODE` — код и rodata в
|
||||||
|
W3. **`--dataseg` НЕ переопределяется**: писучие статики уходят в обычный
|
||||||
|
`_DATA` (W2). Отсюда правило «в W3 только код + rodata».
|
||||||
|
- Линк `-Wl-b_W3CODE=0xC000`.
|
||||||
|
|
||||||
|
**Раскладка страниц по режимам (проверено MAME):**
|
||||||
|
|
||||||
|
| Режим | W1 | W2 | W3 |
|
||||||
|
|-------|----|----|----|
|
||||||
|
| tiny | `#FF` (не исп.) | код+данные | **резидент** |
|
||||||
|
| small | код | данные | **резидент** |
|
||||||
|
| big | банк (трамплин) | код+данные | **резидент** |
|
||||||
|
| huge | код | данные | **резидент делит окно с трамплин-банками** |
|
||||||
|
|
||||||
|
**huge — особый случай (резидент + банки в одном окне W3):**
|
||||||
|
- `crt0_banked` под `.ifdef W3_RESIDENT` захватывает физ-страницу резидента
|
||||||
|
(`in a,(0xE2)`) сразу после загрузки DSS и **возвращает её дефолтом** после
|
||||||
|
цикла загрузки банков (иначе в W3 остался бы последний банк).
|
||||||
|
- Трамплин на каждый `__banked`-вызов сам сохраняет/восстанавливает страницу
|
||||||
|
W3 — поэтому дефолтная страница обязана быть резидентной.
|
||||||
|
- mkexe получает флаг `-W` (разрешить HOME тянуться в W3 при наличии W3-банков —
|
||||||
|
намеренное совмещение).
|
||||||
|
|
||||||
|
**Правила разработчика:**
|
||||||
|
- Код в W3 **не переключает** страницу W3.
|
||||||
|
- Код в W1/W2, свапающий W3 на другую страницу (скретч, графика), обязан
|
||||||
|
**вернуть исходную** (`in a,(0xE2)` → работа → `out (0xE2),a`); оборачивать в
|
||||||
|
`DI/EI`, если есть ISR, дёргающий W3.
|
||||||
|
- В W3 — **только код + rodata**, писучих переменных там быть не должно.
|
||||||
|
- Из `__banked`-контекста (пока в W3 замаплен банк) резидентный W3-код
|
||||||
|
**недостижим** транзитивно. Обратное — резидент → `__banked` через трамплин
|
||||||
|
W1 — **работает** (трамплин вернёт резидентную страницу перед `ret`).
|
||||||
|
|
||||||
|
Подробности и артефакты — `memory/w3_resident_code`, тест `tests/w3probe`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Куча и стек
|
||||||
|
|
||||||
|
- **Стек**: init `SP=0xBFFE`, растёт вниз. Меняется через `-S 0xADDR`.
|
||||||
|
- **Куча**: от конца `_BSS` вверх до `___sdcc_heap_end` (по умолчанию `0xBB00` →
|
||||||
|
~1278 байт под стек). `malloc` берёт `&___sdcc_heap_end` как потолок.
|
||||||
|
- `runtime/heap.s` — динамическая куча (авто-размер = зазор BSS…heap_top), НЕ
|
||||||
|
фиксированный `.ds`.
|
||||||
|
- `--stack-size N` регенерирует `heap_top` как `HEAP_TOP = 0xBFFF - N`, зажимая
|
||||||
|
рост кучи ради стека.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Семейство crt0
|
||||||
|
|
||||||
|
Выбирается режимом; `--crt0=TYPE` перекрывает.
|
||||||
|
|
||||||
|
| crt0 | Файл | Для чего |
|
||||||
|
|------|------|----------|
|
||||||
|
| `default` | `runtime/crt0.s` | tiny/manual; парсит argv, argv[0] через APPINFO |
|
||||||
|
| `minimal` | `runtime/crt0_minimal.s` | tiny без argv (меньше размер) |
|
||||||
|
| `small` | `runtime/crt0_small.s` | small; авто-детект/выделение W2 |
|
||||||
|
| `banked` | `runtime/crt0_banked.s` | big/huge; авто-детект W2 + загрузка банков |
|
||||||
|
|
||||||
|
`crt0`/`bank.s` собираются **пер-сборка** внутри `sprinter-cc` (с префиксами
|
||||||
|
`BANK_W1` / `W3_RESIDENT` / `DEBUG_RT`), а не бандлятся в библиотеку.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Типовые грабли
|
||||||
|
|
||||||
|
- **`static`-переменные читаются как `0xFF` / не меняются** — DATA попала в
|
||||||
|
невыделенное окно (`#FF`). Причина: код и данные в разных окнах при образе
|
||||||
|
`<16 КБ`, где DSS дал только одну страницу. Лечится правильным режимом
|
||||||
|
(`small`/`huge`) или единым окном (`tiny`). См.
|
||||||
|
`memory/sprinter_memory_modes`.
|
||||||
|
- **Крэш после первого `call` в split-режиме** — стек ещё в невыделенном W2.
|
||||||
|
Решает загрузочный стек в W1 (уже в crt0).
|
||||||
|
- **Банк «прыгает в мусор»** — таблица `_bank_pages[]` в `_DATA` занулилась
|
||||||
|
gsinit’ом; она обязана жить в `_CODE`. Уже исправлено в `bank.s`.
|
||||||
|
- **`--w3`: резидент недоступен из банка** — ожидаемо (см. §6); держи вход в
|
||||||
|
резидент только из W1/W2 или из самого W3-кода.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Отчёт по раскладке памяти (после линковки)
|
||||||
|
|
||||||
|
`sprinter-cc` печатает отчёт `toolchain/check_banks.py` для **любой** модели
|
||||||
|
памяти (по `.map`). Показывает, где легли код/данные и **сколько свободно**
|
||||||
|
в каждом окне и банке:
|
||||||
|
|
||||||
|
```
|
||||||
|
memory: huge — CODE в W1 (0x4100), DATA→W2, банки в W3
|
||||||
|
_CODE @ 0x4100 size 3767 (W1) → 0x4FB7
|
||||||
|
данные @ 0x4FB7 size 336 (W1) → 0x5107 [_DATA/_BSS/_INITIALIZED/…]
|
||||||
|
статика до 0x5107 — куча 0x5107..0xBB00 (27129 Б), стек 0xBB00..0xBFFE (1279 Б)
|
||||||
|
_W3CODE @ 0xC000 size 249 (резидент W3) → 0xC0F9, 16135 Б свободно до 0x10000 OK
|
||||||
|
_BANK1 @ 0x0001C000 size 219 / 16384 ( 1.3%) → 16165 Б свободно OK
|
||||||
|
```
|
||||||
|
|
||||||
|
- `_CODE` / `данные` — окно (W1/W2), размер, конец; строка `статика … куча …
|
||||||
|
стек …` = свободное место в W1/W2 (под кучу malloc до `___sdcc_heap_end` и
|
||||||
|
под стек).
|
||||||
|
- `_W3CODE` — только при `--w3`: остаток окна W3.
|
||||||
|
- `_BANKn` — только при `--bank`: занятость/остаток каждого 16 КБ-банка.
|
||||||
|
- Ненулевой выход (проглатывается `|| true`), если банк > 16 КБ или статика
|
||||||
|
заехала за `heap_top`/`0xC000`.
|
||||||
|
```
|
||||||
Binary file not shown.
@@ -0,0 +1,721 @@
|
|||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
; Balls mini-demo
|
||||||
|
; (c) Sayman 2015
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
; ïðîöåäóðà ìîäóëÿ ñîâñåì íå îïòèìàëüíà, âçÿòà èç èñõîäíèêà áèáëèîòåêè C.
|
||||||
|
;-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
tbuf_addr: equ 4000h
|
||||||
|
screen_addr: equ 0xc000
|
||||||
|
|
||||||
|
|
||||||
|
balls_pgn equ 1
|
||||||
|
bgspr_pgn equ 6
|
||||||
|
savescr_pgn equ 6
|
||||||
|
tbuf_pgn equ 1
|
||||||
|
|
||||||
|
MAX_BALLS equ 32 ; ïðè 96 øàðèêàõ ñòðóêòóðà balls_cfg áóäåò 576 áàéò
|
||||||
|
|
||||||
|
balls_pal_size equ 128
|
||||||
|
bgspr_pal_size equ 128
|
||||||
|
|
||||||
|
balls_struct:
|
||||||
|
.x equ 0 ; u16_t
|
||||||
|
.y equ 2 ; u8_t
|
||||||
|
.dx equ 3 ; i8_t
|
||||||
|
.dy equ 4 ; i8_t
|
||||||
|
.radius equ 5 ; u8_t
|
||||||
|
|
||||||
|
balls_struct_bkp:
|
||||||
|
.x equ 0 ; u16_t
|
||||||
|
.y equ 2 ; u8_t
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
include "dss.inc"
|
||||||
|
include "head.inc"
|
||||||
|
|
||||||
|
|
||||||
|
entry: push ix
|
||||||
|
call init
|
||||||
|
call read_bgspr
|
||||||
|
ld a,(pg_tbl.tbuf)
|
||||||
|
out (cpu_w1),a
|
||||||
|
ld a,(tmp_hndl)
|
||||||
|
call close
|
||||||
|
call read_bgpal
|
||||||
|
call read_balls_spr
|
||||||
|
call read_balls_pal
|
||||||
|
call vinit
|
||||||
|
call balls_reindex
|
||||||
|
call balls_init
|
||||||
|
call draw_bgspr
|
||||||
|
call CopySCR_1to2
|
||||||
|
ld a,0xc0
|
||||||
|
out (port_y),a
|
||||||
|
ld a,(pg_w3)
|
||||||
|
out (cpu_w3),a
|
||||||
|
call balls_main
|
||||||
|
sub a
|
||||||
|
jp quit0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
include "misc.asm"
|
||||||
|
include "math.inc"
|
||||||
|
|
||||||
|
|
||||||
|
rand16 ld de,.seed ; Seed is usually 0
|
||||||
|
ld a,d
|
||||||
|
ld h,e
|
||||||
|
ld l,253
|
||||||
|
or a
|
||||||
|
sbc hl,de
|
||||||
|
sbc a,0
|
||||||
|
sbc hl,de
|
||||||
|
ld d,0
|
||||||
|
sbc a,d
|
||||||
|
ld e,a
|
||||||
|
sbc hl,de
|
||||||
|
jr nc,.rand
|
||||||
|
inc hl
|
||||||
|
.rand ld (rand16+1),hl
|
||||||
|
ret
|
||||||
|
|
||||||
|
.seed: dw 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
inc_pg1: ld a,(ix)
|
||||||
|
out (cpu_w1),a
|
||||||
|
inc ix
|
||||||
|
ret
|
||||||
|
|
||||||
|
inc_pg3: ld a,(ix)
|
||||||
|
out (cpu_w3),a
|
||||||
|
inc ix
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
init: ld c,balls_pgn
|
||||||
|
ld hl,pg_tbl.balls
|
||||||
|
.loop0: push bc
|
||||||
|
push hl
|
||||||
|
call gmem
|
||||||
|
pop hl
|
||||||
|
pop bc
|
||||||
|
jp c,gmem_err
|
||||||
|
ld (hl),a
|
||||||
|
inc hl
|
||||||
|
dec c
|
||||||
|
jr nz,.loop0
|
||||||
|
ld c,bgspr_pgn
|
||||||
|
.loop1: push bc
|
||||||
|
push hl
|
||||||
|
call gmem
|
||||||
|
pop hl
|
||||||
|
pop bc
|
||||||
|
jp c,gmem_err
|
||||||
|
ld (hl),a
|
||||||
|
inc hl
|
||||||
|
dec c
|
||||||
|
jr nz,.loop1
|
||||||
|
ld c,savescr_pgn
|
||||||
|
.loop2: push bc
|
||||||
|
push hl
|
||||||
|
call gmem
|
||||||
|
pop hl
|
||||||
|
pop bc
|
||||||
|
jp c,gmem_err
|
||||||
|
ld (hl),a
|
||||||
|
inc hl
|
||||||
|
dec c
|
||||||
|
jr nz,.loop2
|
||||||
|
ld c,tbuf_pgn
|
||||||
|
.loop3: push bc
|
||||||
|
push hl
|
||||||
|
call gmem
|
||||||
|
pop hl
|
||||||
|
pop bc
|
||||||
|
jp c,gmem_err
|
||||||
|
ld (hl),a
|
||||||
|
inc hl
|
||||||
|
dec c
|
||||||
|
jr nz,.loop3
|
||||||
|
ex af,af
|
||||||
|
in a,(cpu_w1)
|
||||||
|
ld (pg_w1),a
|
||||||
|
in a,(cpu_w3)
|
||||||
|
ld (pg_w3),a
|
||||||
|
in a,(port_y)
|
||||||
|
ld (old_y),a
|
||||||
|
ex af,af
|
||||||
|
out (cpu_w1),a
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
set_scr_w3: out (cpu_w3),a
|
||||||
|
ret
|
||||||
|
|
||||||
|
set_scr_w1: out (cpu_w1),a
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
vinit: ld a,81h
|
||||||
|
ld bc,50h
|
||||||
|
rst 10h
|
||||||
|
call cls
|
||||||
|
call set_global_pal
|
||||||
|
call CopySCR_1to2
|
||||||
|
ld a,norm_scr
|
||||||
|
call set_scr_w3
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
CopySCR_1to2: sub a
|
||||||
|
out (port_y), a
|
||||||
|
ld hl, 0xc000
|
||||||
|
ld de, 0xc140
|
||||||
|
ld bc, 320
|
||||||
|
ld d, d
|
||||||
|
ld a, 0 ; 256 bytes
|
||||||
|
ld a, a
|
||||||
|
; ldir
|
||||||
|
.loop: REPT 64
|
||||||
|
ldi
|
||||||
|
ENDM
|
||||||
|
jp pe,.loop
|
||||||
|
|
||||||
|
ld b, b
|
||||||
|
ld hl, 0xc3E0
|
||||||
|
ld de, 0xc3E4
|
||||||
|
; ld bc, 0004h
|
||||||
|
ld a, a
|
||||||
|
ldi
|
||||||
|
ldi
|
||||||
|
ldi
|
||||||
|
ldi
|
||||||
|
; ldir
|
||||||
|
ld b, b
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
cls: ld hl,0xc3E0
|
||||||
|
ld de,0
|
||||||
|
ld d,d
|
||||||
|
ld a,0
|
||||||
|
ld b,b
|
||||||
|
ld b,4
|
||||||
|
.loop1: ld a,d
|
||||||
|
out (port_y),a
|
||||||
|
ld e,e
|
||||||
|
ld (hl),e
|
||||||
|
ld b,b
|
||||||
|
inc hl
|
||||||
|
djnz .loop1
|
||||||
|
|
||||||
|
ld hl,0xc000
|
||||||
|
ld de,0
|
||||||
|
ld bc,320
|
||||||
|
ld d,d
|
||||||
|
ld a,0
|
||||||
|
ld b,b
|
||||||
|
.loop2: ld a,d
|
||||||
|
out (port_y),a
|
||||||
|
ld e,e
|
||||||
|
ld (hl),e
|
||||||
|
ld b,b
|
||||||
|
inc hl
|
||||||
|
dec bc
|
||||||
|
ld a,b
|
||||||
|
or c
|
||||||
|
jr nz,.loop2
|
||||||
|
ret
|
||||||
|
|
||||||
|
set_global_pal: ld bc,0ffa4h
|
||||||
|
ld hl,pal
|
||||||
|
ld de,0
|
||||||
|
sub a
|
||||||
|
rst 8
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
read_bgspr: ld hl,data_files.bgspr
|
||||||
|
push hl
|
||||||
|
call open
|
||||||
|
pop hl
|
||||||
|
ld (open_err.err_file+1),hl
|
||||||
|
jp c,open_err
|
||||||
|
ld (rd_err.err_file+1),hl
|
||||||
|
ld (tmp_hndl),a
|
||||||
|
ld ix,pg_tbl.bgspr
|
||||||
|
.loop: call inc_pg1
|
||||||
|
ld a,(tmp_hndl)
|
||||||
|
ld hl,tbuf_addr
|
||||||
|
ld de,16320
|
||||||
|
push ix
|
||||||
|
call read
|
||||||
|
pop ix
|
||||||
|
jp c,rd_err
|
||||||
|
or a
|
||||||
|
ret nz
|
||||||
|
jr .loop
|
||||||
|
|
||||||
|
read_bgpal: ld hl,data_files.bgpal
|
||||||
|
push hl
|
||||||
|
call open
|
||||||
|
pop hl
|
||||||
|
ld (open_err.err_file+1),hl
|
||||||
|
jp c,open_err
|
||||||
|
ld (rd_err.err_file+1),hl
|
||||||
|
ld (tmp_hndl),a
|
||||||
|
ld hl,pal
|
||||||
|
ld de,bgspr_pal_size*4
|
||||||
|
call read
|
||||||
|
jp c,rd_err
|
||||||
|
ld a,(tmp_hndl)
|
||||||
|
call close
|
||||||
|
ret
|
||||||
|
|
||||||
|
read_balls_spr: ld hl,data_files.balls
|
||||||
|
push hl
|
||||||
|
call open
|
||||||
|
pop hl
|
||||||
|
ld (open_err.err_file+1),hl
|
||||||
|
jp c,open_err
|
||||||
|
ld (rd_err.err_file+1),hl
|
||||||
|
ld (tmp_hndl),a
|
||||||
|
ld hl,balls_spr
|
||||||
|
ld de,1024
|
||||||
|
call read
|
||||||
|
jp c,rd_err
|
||||||
|
ld a,(tmp_hndl)
|
||||||
|
call close
|
||||||
|
ret
|
||||||
|
|
||||||
|
read_balls_pal: ld hl,data_files.balls_pal
|
||||||
|
push hl
|
||||||
|
call open
|
||||||
|
pop hl
|
||||||
|
ld (open_err.err_file+1),hl
|
||||||
|
jp c,open_err
|
||||||
|
ld (rd_err.err_file+1),hl
|
||||||
|
ld (tmp_hndl),a
|
||||||
|
ld hl,pal+bgspr_pal_size*4
|
||||||
|
ld de,balls_pal_size*4
|
||||||
|
call read
|
||||||
|
jp c,rd_err
|
||||||
|
ld a,(tmp_hndl)
|
||||||
|
call close
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
balls_reindex: ld hl,balls_spr
|
||||||
|
ld c,128
|
||||||
|
ld de,1024
|
||||||
|
.loop0: ld a,(hl)
|
||||||
|
inc a
|
||||||
|
jr z,.loop1
|
||||||
|
dec a
|
||||||
|
add a,c
|
||||||
|
ld (hl),a
|
||||||
|
.loop1: inc hl
|
||||||
|
dec de
|
||||||
|
ld a,e
|
||||||
|
or d
|
||||||
|
jr nz,.loop0
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
balls_init: ld hl,balls_cfg
|
||||||
|
ld de,balls_cfg+1
|
||||||
|
ld bc,MAX_BALLS*6
|
||||||
|
xor a
|
||||||
|
ld (hl),a
|
||||||
|
ldir
|
||||||
|
ld hl,balls_bkp
|
||||||
|
ld de,balls_bkp+1
|
||||||
|
ld bc,MAX_BALLS*3
|
||||||
|
xor a
|
||||||
|
ld (hl),a
|
||||||
|
ldir
|
||||||
|
ld iy,MAX_BALLS
|
||||||
|
ld ix,balls_cfg
|
||||||
|
.loop0: ld de,320-16 ; ðàçìåðû êàæäîãî øàðèêà 16íà16
|
||||||
|
push de
|
||||||
|
call rand16 ; áåð¸ì ñëó÷àéíóþ êîîðäèíàòó X
|
||||||
|
pop de
|
||||||
|
inc hl
|
||||||
|
push de
|
||||||
|
call lmod
|
||||||
|
pop de
|
||||||
|
ex de,hl
|
||||||
|
.next0: ld (ix+balls_struct.x),e
|
||||||
|
ld (ix+balls_struct.x+1),d
|
||||||
|
ld de,256-16
|
||||||
|
push de
|
||||||
|
call rand16
|
||||||
|
pop de
|
||||||
|
inc hl
|
||||||
|
push de
|
||||||
|
call lmod
|
||||||
|
pop de
|
||||||
|
ex de,hl
|
||||||
|
.next1: ld (ix+balls_struct.y),e
|
||||||
|
call rand16
|
||||||
|
ld a,l
|
||||||
|
and 1
|
||||||
|
jr nz,.next2
|
||||||
|
ld a,-1
|
||||||
|
.next2: ld (ix+balls_struct.dx),a
|
||||||
|
call rand16
|
||||||
|
ld a,l
|
||||||
|
and 1
|
||||||
|
jr nz,.next3
|
||||||
|
ld a,-1
|
||||||
|
.next3: ld (ix+balls_struct.dy),a
|
||||||
|
ld de,6
|
||||||
|
add ix,de
|
||||||
|
dec iy
|
||||||
|
ld a,iyl
|
||||||
|
or iyh
|
||||||
|
jr nz,.loop0
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
draw_bgspr: ld ix,pg_tbl.bgspr
|
||||||
|
call inc_pg1
|
||||||
|
ld hl,tbuf_addr
|
||||||
|
ld c,51
|
||||||
|
ld a,0
|
||||||
|
.loop: ld de,screen_addr
|
||||||
|
out (port_y),a
|
||||||
|
ex af,af
|
||||||
|
di
|
||||||
|
ld d,d
|
||||||
|
ld a,0
|
||||||
|
ld l,l
|
||||||
|
ld a,(hl)
|
||||||
|
ld (de),a
|
||||||
|
ld b,b
|
||||||
|
inc h
|
||||||
|
inc d
|
||||||
|
ld d,d
|
||||||
|
ld a,64
|
||||||
|
ld l,l
|
||||||
|
ld a,(hl)
|
||||||
|
ld (de),a
|
||||||
|
ld b,b
|
||||||
|
ld a,64
|
||||||
|
add a,l
|
||||||
|
ld l,a
|
||||||
|
adc a,h
|
||||||
|
sub l
|
||||||
|
ld h,a
|
||||||
|
dec c
|
||||||
|
call z,nextpage
|
||||||
|
ex af,af
|
||||||
|
inc a
|
||||||
|
jr nz,.loop
|
||||||
|
ei
|
||||||
|
halt
|
||||||
|
di
|
||||||
|
ret
|
||||||
|
|
||||||
|
nextpage: call inc_pg1
|
||||||
|
ld hl,tbuf_addr
|
||||||
|
ld c,51
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
balls_main:
|
||||||
|
|
||||||
|
.loop1: ld iy,MAX_BALLS
|
||||||
|
ld ix,balls_cfg
|
||||||
|
call save_coords
|
||||||
|
push ix
|
||||||
|
push iy
|
||||||
|
.loop0: ld e,(ix+balls_struct.x)
|
||||||
|
ld d,(ix+balls_struct.x+1)
|
||||||
|
ld hl,320-16
|
||||||
|
call check_x
|
||||||
|
ld c,(ix+balls_struct.y)
|
||||||
|
ld hl,256-16
|
||||||
|
call check_y
|
||||||
|
ld a,(ix+balls_struct.dx)
|
||||||
|
ld h,0
|
||||||
|
ld l,a
|
||||||
|
rlca
|
||||||
|
jr nc,.a1
|
||||||
|
dec h
|
||||||
|
.a1: rrca
|
||||||
|
add hl,de
|
||||||
|
ex de,hl
|
||||||
|
ld (ix+balls_struct.x),e
|
||||||
|
ld (ix+balls_struct.x+1),d
|
||||||
|
ld a,(ix+balls_struct.dy)
|
||||||
|
add a,c
|
||||||
|
ld (ix+balls_struct.y),a
|
||||||
|
ld de,6
|
||||||
|
add ix,de
|
||||||
|
dec iy
|
||||||
|
ld a,iyl
|
||||||
|
or iyh
|
||||||
|
jr nz,.loop0
|
||||||
|
pop iy
|
||||||
|
pop ix
|
||||||
|
|
||||||
|
ld a,norm_scr|trans_scr|tmp_scr
|
||||||
|
call set_scr_w3
|
||||||
|
|
||||||
|
.loop2: ld hl,balls_spr
|
||||||
|
ld a,iyl
|
||||||
|
and 3
|
||||||
|
ld e,(ix+balls_struct.x)
|
||||||
|
ld d,(ix+balls_struct.x+1)
|
||||||
|
ld c,(ix+balls_struct.y)
|
||||||
|
call draw_balls
|
||||||
|
ld de,6
|
||||||
|
add ix,de
|
||||||
|
dec iy
|
||||||
|
ld a,iyl
|
||||||
|
or iyh
|
||||||
|
jr nz,.loop2
|
||||||
|
|
||||||
|
ei
|
||||||
|
halt
|
||||||
|
di
|
||||||
|
in a,(rgmod)
|
||||||
|
and 1
|
||||||
|
xor 1
|
||||||
|
out (rgmod),a
|
||||||
|
|
||||||
|
ld a,norm_scr
|
||||||
|
call set_scr_w3
|
||||||
|
ld iy,MAX_BALLS
|
||||||
|
ld ix,balls_bkp
|
||||||
|
.loop3: ld e,(ix+balls_struct.x)
|
||||||
|
ld d,(ix+balls_struct.x+1)
|
||||||
|
ld c,(ix+balls_struct.y)
|
||||||
|
call restore_bg
|
||||||
|
ld de,3
|
||||||
|
add ix,de
|
||||||
|
dec iy
|
||||||
|
ld a,iyl
|
||||||
|
or iyh
|
||||||
|
jr nz,.loop3
|
||||||
|
|
||||||
|
.blah: call check_key
|
||||||
|
jr c,._ret_
|
||||||
|
jp .loop1
|
||||||
|
._ret_: ret
|
||||||
|
|
||||||
|
save_coords: push ix
|
||||||
|
push iy
|
||||||
|
ld hl,balls_bkp
|
||||||
|
.loop: ld e,(ix+balls_struct.x)
|
||||||
|
ld d,(ix+balls_struct.x+1)
|
||||||
|
ld c,(ix+balls_struct.y)
|
||||||
|
ld (hl),e
|
||||||
|
inc hl
|
||||||
|
ld (hl),d
|
||||||
|
inc hl
|
||||||
|
ld (hl),c
|
||||||
|
inc hl
|
||||||
|
ld de,6
|
||||||
|
add ix,de
|
||||||
|
dec iy
|
||||||
|
ld a,iyh
|
||||||
|
or iyl
|
||||||
|
jr nz,.loop
|
||||||
|
pop iy
|
||||||
|
pop ix
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
restore_bg:
|
||||||
|
in a,(rgmod)
|
||||||
|
or a
|
||||||
|
ld a,0xc0
|
||||||
|
jr nz,.restore_to_0
|
||||||
|
ld a,0xc1
|
||||||
|
jr .restore_to_1
|
||||||
|
.restore_to_0: add a,d
|
||||||
|
ld d,a
|
||||||
|
ld h,d
|
||||||
|
ld l,e
|
||||||
|
jr .restore
|
||||||
|
.restore_to_1: add a,d
|
||||||
|
ld d,a
|
||||||
|
ld a,0x40
|
||||||
|
add a,e
|
||||||
|
ld e,a
|
||||||
|
adc a,d
|
||||||
|
sub e
|
||||||
|
ld d,a
|
||||||
|
ld h,d
|
||||||
|
ld l,e
|
||||||
|
.restore: ld a,c
|
||||||
|
out (port_y),a
|
||||||
|
ld b,16
|
||||||
|
di
|
||||||
|
.loop: ld d,d
|
||||||
|
ld a,16
|
||||||
|
ld l,l
|
||||||
|
ld a,(hl)
|
||||||
|
ld (de),a
|
||||||
|
ld b,b
|
||||||
|
in a,(port_y)
|
||||||
|
inc a
|
||||||
|
out (port_y),a
|
||||||
|
djnz .loop
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
draw_balls: ld b,a
|
||||||
|
in a,(rgmod)
|
||||||
|
or a
|
||||||
|
ld a,0xc1
|
||||||
|
jr z,.draw_to_1 ; åñëè âêëþ÷¸í 0é ýêðàí, ðèñóåì íà 1é
|
||||||
|
ld a,0xc0 ; èíà÷å (1é) ðèñóåì â 0é
|
||||||
|
.draw_to_0: add a,d
|
||||||
|
ld d,a
|
||||||
|
jr .draw
|
||||||
|
.draw_to_1: add a,d
|
||||||
|
ld d,a
|
||||||
|
ld a,0x40
|
||||||
|
add a,e
|
||||||
|
ld e,a
|
||||||
|
adc a,d
|
||||||
|
sub e
|
||||||
|
ld d,a
|
||||||
|
|
||||||
|
.draw: ld a,b
|
||||||
|
add a,a
|
||||||
|
add a,a
|
||||||
|
add a,a
|
||||||
|
add a,a
|
||||||
|
add a,l
|
||||||
|
ld l,a
|
||||||
|
adc a,h
|
||||||
|
sub l
|
||||||
|
ld h,a ; íîìåð ñïðàéòà*256 = àäðåñ ñïðàéòà â êàðòèíêå
|
||||||
|
ld a,c
|
||||||
|
out (port_y),a
|
||||||
|
ld b,16
|
||||||
|
di
|
||||||
|
.loop:
|
||||||
|
ld d,d
|
||||||
|
ld a,16
|
||||||
|
ld l,l
|
||||||
|
ld a,(hl)
|
||||||
|
ld a,a
|
||||||
|
ld (de),a
|
||||||
|
ld b,b
|
||||||
|
ld a,c
|
||||||
|
out (port_y),a
|
||||||
|
ld a,l
|
||||||
|
add a,64
|
||||||
|
ld l,a
|
||||||
|
ld a,0
|
||||||
|
adc a,h
|
||||||
|
ld h,a
|
||||||
|
inc de
|
||||||
|
djnz .loop
|
||||||
|
ret
|
||||||
|
|
||||||
|
check_x: ld a,e
|
||||||
|
or d
|
||||||
|
jr z,.next0
|
||||||
|
sbc hl,de
|
||||||
|
jr z,.next1
|
||||||
|
jr c,.next1
|
||||||
|
ret
|
||||||
|
.next0: ld a,1
|
||||||
|
ld (ix+balls_struct.dx),a
|
||||||
|
ret
|
||||||
|
.next1: ld a,-1
|
||||||
|
ld (ix+balls_struct.dx),a
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
check_y: ld a,c
|
||||||
|
or a
|
||||||
|
jr z,.next0
|
||||||
|
ld a,l
|
||||||
|
sub c
|
||||||
|
jr z,.next1
|
||||||
|
jr c,.next1
|
||||||
|
ret
|
||||||
|
.next0: ld a,1
|
||||||
|
ld (ix+balls_struct.dy),a
|
||||||
|
ret
|
||||||
|
.next1: ld a,-1
|
||||||
|
ld (ix+balls_struct.dy),a
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
sync: ei
|
||||||
|
halt
|
||||||
|
di
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
check_key: push hl
|
||||||
|
push de
|
||||||
|
push bc
|
||||||
|
push ix
|
||||||
|
push iy
|
||||||
|
ld c,scankey
|
||||||
|
rst 10h
|
||||||
|
pop iy
|
||||||
|
pop ix
|
||||||
|
pop bc
|
||||||
|
pop de
|
||||||
|
pop hl
|
||||||
|
xor 1bh ; êîä Esc...
|
||||||
|
ret nz
|
||||||
|
.esc: scf
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pg_tbl:
|
||||||
|
.balls: db 0
|
||||||
|
.bgspr: ds 6
|
||||||
|
.savescr: ds 6
|
||||||
|
.tbuf: db 0
|
||||||
|
|
||||||
|
pg_w0: db 0
|
||||||
|
pg_w1: db 0
|
||||||
|
pg_w2: db 0
|
||||||
|
pg_w3: db 0
|
||||||
|
old_y: db 0
|
||||||
|
|
||||||
|
|
||||||
|
data_files:
|
||||||
|
.balls: db "balls.spr",0
|
||||||
|
.balls_pal: db "balls.act",0
|
||||||
|
.bgspr: db "bgspr.spr",0
|
||||||
|
.bgpal: db "bgspr.act",0
|
||||||
|
|
||||||
|
tmp_hndl: db 0
|
||||||
|
|
||||||
|
|
||||||
|
open_err_str: db "Can't open file ",0
|
||||||
|
read_err_str: db "Failed to read file ",0
|
||||||
|
gmem_err_str: db "PANIC: Can not allocate memory!",cr,lf,0
|
||||||
|
vm_err_str: db "PANIC: Unable to set videomode!",cr,lf,0
|
||||||
|
|
||||||
|
crlf0: db " ",cr,lf,0
|
||||||
|
|
||||||
|
|
||||||
|
pal: equ ($/80h)*80h+80h
|
||||||
|
balls_spr: equ ((pal+1024)/80h)*80h+80h
|
||||||
|
balls_cfg: equ ((balls_spr+1024)/80h)*80h+80h
|
||||||
|
balls_bkp: equ (((balls_cfg)+6*MAX_BALLS)/80h)*80h+80h
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
@@ -0,0 +1,58 @@
|
|||||||
|
������������������������������������������������������� ���������������
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
���� ���������
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
�� ������
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
�� ���
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
Демка была написана как альтернатива аналогичной на evo-sdk (язык C) для zx-evo,
|
||||||
|
но написано на асме. При этом так же пока писал, разбирался в особенностях работы
|
||||||
|
графических режимов. Содержание архива:
|
||||||
|
|
||||||
|
balls.asm - исходник
|
||||||
|
dss.inc - различные константы
|
||||||
|
head.inc - заголовок для формирования exe файла
|
||||||
|
misc.inc - некоторые "прочие" процедурки.
|
||||||
|
math.inc - математика, причём тормозная (сдёрнутая из исходника либы для HTC)
|
||||||
|
make.bat - собиралка. Использование как make src_name.
|
||||||
|
sjasm.exe - компилятор. Этот меня устраивает.
|
||||||
|
balls.exe - основной exeшник. На экране 32 шарика.
|
||||||
|
balls64.exe - 64 шарика.
|
||||||
|
balls128.exe - 128 шариков.
|
||||||
|
balls225.exe - 225 шариков.
|
||||||
|
balls.ect - файл с палитрой для шариков. Хранится в формате 32бита. Выдернуто
|
||||||
|
из заголовка для bmp шариков.
|
||||||
|
balls.spr - собсвтенно файл с шариками (спрайты). Ранее это был обычный bmp.
|
||||||
|
bgspr.act - палитра для фоновой картинка. Так же 32бита выдернутые из bmp.
|
||||||
|
bgspr.spr - фоновая картинка, тоже бывшая bmp.
|
||||||
|
|
||||||
|
Спрайт шариков имеет разрешение 64*16 (т.е. 4 шарика в линию, каждый шарик
|
||||||
|
16*16), каждый шарик по 32 цвета, т.е. сама картнка с шариками 128 цветов.
|
||||||
|
Фоновая картинка имеет разрешение 320*256, 128 цветов.
|
||||||
|
Чтобы получить эти файлы я в фотошопе что нужно было вырезал, отмасштабировал,
|
||||||
|
"перецветовал" в 8бит на точку, 128 цветов. Шарики собирал по отдельности из
|
||||||
|
фоновой картинки (просто вырезал из оригинального файла "молекулы", масштабировал,
|
||||||
|
собирал в одну картинку и потом извращался с цветами. Потом выдернул палитру
|
||||||
|
через winhex для шариков и фона из их bmp файлов, а из самих bmp потом вырезал
|
||||||
|
весь заголовок, оставив только данные спрайтов.
|
||||||
|
Можно спокойно подсунуть свои спрайты фона и шариков.
|
||||||
|
В демке есть процедура переиндексирования цветов. Первым файлом загружается
|
||||||
|
фоновая картинка и палитра. Цвета в файле имеют индексы от 0 до 7fh.
|
||||||
|
Шарики загружаются уже после. Поскольку палитра 256 цветов, а первые 128 уже
|
||||||
|
заняты под фон, то после загрузки спрайта шариков я делаю переиндексацию. Просто
|
||||||
|
по всему размеру файла если цвет != 0xff, тогда цвет +=128.
|
||||||
|
|
||||||
|
за тормоза в математике сильно не пинать!!!
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
File diff suppressed because one or more lines are too long
@@ -0,0 +1,68 @@
|
|||||||
|
;-------------------------
|
||||||
|
;dss functions defines
|
||||||
|
;file functions
|
||||||
|
fopen equ 11h
|
||||||
|
fclose equ 12h
|
||||||
|
fread equ 13h
|
||||||
|
fwrite equ 14h
|
||||||
|
move_fp equ 15h
|
||||||
|
fgetattr equ 16h
|
||||||
|
fgetdt equ 17h
|
||||||
|
fsetdt equ 18h
|
||||||
|
fcreate equ 0ah
|
||||||
|
fcreaten equ 0bh
|
||||||
|
chdir equ 1dh
|
||||||
|
curdir equ 1eh
|
||||||
|
;memory functions
|
||||||
|
setwin1 equ 39h
|
||||||
|
getmem equ 3dh
|
||||||
|
setmem equ 3fh
|
||||||
|
freemem equ 3eh
|
||||||
|
;keyb functions
|
||||||
|
waitkey equ 30h
|
||||||
|
scankey equ 31h
|
||||||
|
quit equ 41h
|
||||||
|
getarg equ 43h
|
||||||
|
;vmode functions
|
||||||
|
setvmode equ 50h
|
||||||
|
getvmode equ 51h
|
||||||
|
selvpage equ 54h
|
||||||
|
;screen and text functions
|
||||||
|
pchar equ 5bh
|
||||||
|
pchars equ 5ch
|
||||||
|
;other
|
||||||
|
getver equ 0
|
||||||
|
;end dss defines
|
||||||
|
;-------------------------
|
||||||
|
;hardware defines
|
||||||
|
port_y equ 89h ;port for Y coord
|
||||||
|
_320p equ 81h ;320 pixels mode
|
||||||
|
rgmod equ 0c9h
|
||||||
|
border equ 0feh
|
||||||
|
rgscr equ 0e9h
|
||||||
|
rgacc equ 0a9h
|
||||||
|
|
||||||
|
norm_scr equ 50h
|
||||||
|
;trans_scr equ 54h
|
||||||
|
trans_scr equ 00001000b
|
||||||
|
tmp_scr equ 00000100b
|
||||||
|
|
||||||
|
|
||||||
|
e_cache equ 0fbh
|
||||||
|
d_cache equ 7bh
|
||||||
|
sys_port3c equ 3ch
|
||||||
|
sys_port7c equ 7ch
|
||||||
|
|
||||||
|
cpu_w0 equ 82h ;cpu window 0 = addr 0000h
|
||||||
|
cpu_w1 equ 0a2h ;... 1 = 4000h
|
||||||
|
cpu_w2 equ 0c2h ;... 2 = 8000h
|
||||||
|
cpu_w3 equ 0e2h ;... 3 = 0c000h
|
||||||
|
;end hardware defines
|
||||||
|
;-------------------------
|
||||||
|
;characters
|
||||||
|
cr equ 0dh
|
||||||
|
lf equ 0ah
|
||||||
|
tab equ 9
|
||||||
|
space equ 20h
|
||||||
|
;------------------------
|
||||||
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
; .Z80
|
||||||
|
; ASEG
|
||||||
|
; org 100h
|
||||||
|
org 8100h-512
|
||||||
|
|
||||||
|
db "EXE"
|
||||||
|
db 0
|
||||||
|
dw 200h
|
||||||
|
dw 0
|
||||||
|
dw 0
|
||||||
|
dw 0
|
||||||
|
dw 0
|
||||||
|
dw 0
|
||||||
|
dw entry
|
||||||
|
dw entry
|
||||||
|
dw 0bfffh
|
||||||
|
ds 490
|
||||||
|
|
||||||
|
; .PHASE 8100h
|
||||||
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
@echo off
|
||||||
|
if "%1" == "" goto error
|
||||||
|
|
||||||
|
if EXIST %1.exe (
|
||||||
|
del %1.exe
|
||||||
|
)
|
||||||
|
sjasm.exe -L %1.asm %1.exe %1.lst
|
||||||
|
if errorlevel 1 goto ERR
|
||||||
|
echo Ok!
|
||||||
|
goto END
|
||||||
|
|
||||||
|
:ERR
|
||||||
|
del %1.exe
|
||||||
|
pause
|
||||||
|
echo ®è¨¡ª¨ ª®¬¯¨«ï樨...
|
||||||
|
goto END
|
||||||
|
|
||||||
|
:error
|
||||||
|
echo usage: make sourcefile
|
||||||
|
:END
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
;Input: H = Multiplier, E = Multiplicand, L = 0, D = 0
|
||||||
|
;Output: HL = Product
|
||||||
|
mul8_8: ld b,7
|
||||||
|
sla h ; optimised 1st iteration
|
||||||
|
jr nc,$+3
|
||||||
|
ld l,e
|
||||||
|
.loop: add hl,hl ; unroll 7 times
|
||||||
|
jr nc,$+3 ; ...
|
||||||
|
add hl,de ; ...
|
||||||
|
djnz .loop
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
lmod: call ldiv
|
||||||
|
ex de,hl
|
||||||
|
ret
|
||||||
|
|
||||||
|
ldiv: xor a
|
||||||
|
ex af,af'
|
||||||
|
ex de,hl
|
||||||
|
jr dv1
|
||||||
|
|
||||||
|
adiv: ld a,h
|
||||||
|
xor d ;set sign flag for quotient
|
||||||
|
ld a,h ;get sign of dividend
|
||||||
|
ex af,af'
|
||||||
|
call negif
|
||||||
|
ex de,hl
|
||||||
|
call negif
|
||||||
|
dv1: ld b,1
|
||||||
|
ld a,h
|
||||||
|
or l
|
||||||
|
ret z
|
||||||
|
dv8: push hl
|
||||||
|
add hl,hl
|
||||||
|
jr c,dv2
|
||||||
|
ld a,d
|
||||||
|
cp h
|
||||||
|
jr c,dv2
|
||||||
|
jp nz,dv6
|
||||||
|
ld a,e
|
||||||
|
cp l
|
||||||
|
jr c,dv2
|
||||||
|
dv6: pop af
|
||||||
|
inc b
|
||||||
|
jp dv8
|
||||||
|
|
||||||
|
dv2: pop hl
|
||||||
|
ex de,hl
|
||||||
|
push hl
|
||||||
|
ld hl,0
|
||||||
|
ex (sp),hl
|
||||||
|
|
||||||
|
dv4: ld a,h
|
||||||
|
cp d
|
||||||
|
jr c,dv3
|
||||||
|
jp nz,dv5
|
||||||
|
ld a,l
|
||||||
|
cp e
|
||||||
|
jr c,dv3
|
||||||
|
|
||||||
|
dv5: sbc hl,de
|
||||||
|
dv3: ex (sp),hl
|
||||||
|
ccf
|
||||||
|
adc hl,hl
|
||||||
|
srl d
|
||||||
|
rr e
|
||||||
|
ex (sp),hl
|
||||||
|
djnz dv4
|
||||||
|
pop de
|
||||||
|
ex de,hl
|
||||||
|
ex af,af'
|
||||||
|
call m,negat
|
||||||
|
ex de,hl
|
||||||
|
or a ;test remainder sign bit
|
||||||
|
call m,negat
|
||||||
|
ex de,hl
|
||||||
|
ret
|
||||||
|
|
||||||
|
negif: bit 7,h
|
||||||
|
ret z
|
||||||
|
negat: ld b,h
|
||||||
|
ld c,l
|
||||||
|
ld hl,0
|
||||||
|
or a
|
||||||
|
sbc hl,bc
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
; îáùàÿ ïðîöåäóðà âûõîäà.
|
||||||
|
; â A ïîìåñòèòü êîä âîçâðàòà.
|
||||||
|
; Åñëè áûëà îøèáêà, òî ëó÷øå -1 (255)
|
||||||
|
; Åñëè îøèáêè íåáûëî, òî 0.
|
||||||
|
quit0: pop ix
|
||||||
|
ld b,a
|
||||||
|
ld c,quit
|
||||||
|
rst 10h
|
||||||
|
jp $ ; çàãëóøêà
|
||||||
|
|
||||||
|
;-[]------------------------------------------------------------
|
||||||
|
; misc procedures and functions...
|
||||||
|
;---------------------------------------------------------------
|
||||||
|
open: ld a,1
|
||||||
|
ld c,fopen
|
||||||
|
rst 10h
|
||||||
|
ret
|
||||||
|
|
||||||
|
close: ld c,fclose
|
||||||
|
rst 10h
|
||||||
|
ret
|
||||||
|
|
||||||
|
read: ld c,fread
|
||||||
|
rst 10h
|
||||||
|
ret
|
||||||
|
|
||||||
|
;dir_ret: ld hl,cur_dir
|
||||||
|
; ld c,chdir
|
||||||
|
; rst 10h
|
||||||
|
; ret
|
||||||
|
|
||||||
|
gmem: ld c,getmem
|
||||||
|
ld b,1
|
||||||
|
rst 10h
|
||||||
|
ret
|
||||||
|
|
||||||
|
; ïðîöåäóðà ñîõðàíåíèÿ ñòðàíèöû â óêàçííîì îêíå.
|
||||||
|
; C = îêíî (ïîðò)
|
||||||
|
; HL = êóäà ñîõðàíÿòü.
|
||||||
|
save_pg: in a,(c)
|
||||||
|
ld (hl),a
|
||||||
|
ret
|
||||||
|
|
||||||
|
; ïðîöåäóðà âîññòàíîâëåíèÿ ñòðàíèöû â óêàçííîì îêíå.
|
||||||
|
; C = îêíî (ïîðò)
|
||||||
|
; HL = îò êóäà âîññòàíîâèòü.
|
||||||
|
restore_pg: ld a,(hl)
|
||||||
|
out (c),a
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
; îáðàáîòêà îøèáêè îòêðûòèÿ ôàéëà
|
||||||
|
open_err:
|
||||||
|
ld hl,open_err_str
|
||||||
|
ld c,pchars
|
||||||
|
rst 10h
|
||||||
|
.err_file: ld hl,0
|
||||||
|
ld c,pchars
|
||||||
|
rst 10h
|
||||||
|
ld hl,crlf0
|
||||||
|
ld c,pchars
|
||||||
|
rst 10h
|
||||||
|
ld a,-1
|
||||||
|
jp quit0
|
||||||
|
|
||||||
|
|
||||||
|
; îáðàáîòêà îøèáêè ÷òåíèÿ ôàéëà
|
||||||
|
rd_err:
|
||||||
|
ld hl,read_err_str
|
||||||
|
ld c,pchars
|
||||||
|
rst 10h
|
||||||
|
.err_file: ld hl,0
|
||||||
|
ld c,pchars
|
||||||
|
rst 10h
|
||||||
|
ld hl,crlf0
|
||||||
|
ld c,pchars
|
||||||
|
rst 10h
|
||||||
|
ld a,-1
|
||||||
|
jp quit0
|
||||||
|
|
||||||
|
|
||||||
|
; îáðàáîòêà îøèáêè çàïðîñà ïàìÿòè
|
||||||
|
gmem_err: ld hl,gmem_err_str
|
||||||
|
ld c,pchars
|
||||||
|
rst 10h
|
||||||
|
ld a,-1
|
||||||
|
jp quit0
|
||||||
|
|
||||||
|
; îáðàáîòêà îøèáêè îòêðûòèÿ âèäåîðåæèìà
|
||||||
|
vm_err: ld hl,vm_err_str
|
||||||
|
ld c,pchars
|
||||||
|
rst 10h
|
||||||
|
jp quit0
|
||||||
|
|
||||||
|
IFDEF debug
|
||||||
|
debug_print: ld c,pchars
|
||||||
|
rst 10h
|
||||||
|
ret
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
; îáðàáîòêà îøèáêîê òîêåíîâ
|
||||||
|
;tok_err:
|
||||||
|
; ld hl,token_err_str
|
||||||
|
; ld c,pchars
|
||||||
|
; rst 10h
|
||||||
|
;.token_ptr: ld hl,0
|
||||||
|
; ld c,pchars
|
||||||
|
; rst 10h
|
||||||
|
; ld hl,crlf0
|
||||||
|
; ld c,pchars
|
||||||
|
; rst 10h
|
||||||
|
; ld a,-1
|
||||||
|
; jp quit0
|
||||||
Binary file not shown.
@@ -0,0 +1,4 @@
|
|||||||
|
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||||
|
EXAMPLE := scroll
|
||||||
|
EXTRA_FLAGS ?= --gfx 256
|
||||||
|
include $(PROJ_ROOT)/app.mk
|
||||||
Executable
+4
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
make clean
|
||||||
|
make run
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
# Руководство: скроллинг-примитивы для libbgi
|
||||||
|
|
||||||
|
Самодостаточное ТЗ на реализацию двух функций скролла в libbgi. Написано так,
|
||||||
|
чтобы реализовать с нуля без остального контекста. Итог изысканий по железу
|
||||||
|
Sprinter (см. §1) и разбора существующего кода libbgi (см. §3).
|
||||||
|
|
||||||
|
**Что делаем:** `gfx_scroll_h` (горизонтальный — нужен Loom для комнат шире
|
||||||
|
320 px) и `gfx_scroll_v` (вертикальный — для симметрии API; Loom пока не
|
||||||
|
требует, высота комнат 144 < 256).
|
||||||
|
|
||||||
|
**Где живёт:** расширение `C-Compiler/libbgi` — accel-leaf в `bgi256/`,
|
||||||
|
обёртка в `common/`, объявление в `include/gfx.h`.
|
||||||
|
|
||||||
|
**СНАЧАЛА прочти §7 (блокеры).** Два пункта надо проверить в dev-MAME до
|
||||||
|
написания рабочего кода — если они не пройдут, схема «чистого фона» едет.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Аппаратная модель (всё, что нужно знать)
|
||||||
|
|
||||||
|
**Режим `0x81`: 320×256×256, chunky — 1 байт/пиксель.** Пиксель (x,y): CPU-адрес
|
||||||
|
`0xC000 + x`, строку выбирает **Port_Y** (порт `0x89`) `= y`. Горизонталь — в
|
||||||
|
адресе, вертикаль — в Port_Y. (Ссылка: `libbgi/include/gfx.h` шапка.)
|
||||||
|
|
||||||
|
**Две видеостраницы различаются ТОЛЬКО базой строки:** page0 → `0xC000`,
|
||||||
|
page1 → `0xC140` (шаг `0x140` = 320). Обе адресуемы в W3 **одновременно**;
|
||||||
|
Port_Y и номера банков — общие для обеих. Значит:
|
||||||
|
> `dst_addr = src_addr + 0x140` копирует page0→page1 без ремаппинга W3.
|
||||||
|
> Со сдвигом: `dst = src ± 0x140 + dx`.
|
||||||
|
|
||||||
|
**Банки (значение W3-страницы, порт `0xE2` = `0x50..0x5F`)** — режим ЗАПИСИ,
|
||||||
|
действует на все примитивы до смены (`libbgi/include/gfx.h`):
|
||||||
|
| Банк | Имя | Запись | Чтение |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `0x50` | `GFX_BANK_NORMAL` | VRAM **+ ОЗУ-копия** | ОЗУ-копия |
|
||||||
|
| `0x54` | `GFX_BANK_NOSHADOW` | только VRAM | ОЗУ-копия |
|
||||||
|
| `0x58` | `GFX_BANK_TRANSPARENT` | VRAM, байт `0xFF` не пишется | ОЗУ-копия |
|
||||||
|
| `0x5C` | `GFX_BANK_SPRITE` | VRAM, `0xFF` не пишется | ОЗУ-копия |
|
||||||
|
|
||||||
|
Чтение `#50..#5F` **всегда** отдаёт ОЗУ-копию (видео-ОЗУ write-only). `0xFF` —
|
||||||
|
аппаратно-прозрачный цвет (`GFX_TRANSPARENT`).
|
||||||
|
|
||||||
|
**Акселератор** (`C-Compiler/docs/converted/accel_r.txt`): внутр. ОЗУ-буфер
|
||||||
|
1..256 байт. Управление опкодами-«NOP»:
|
||||||
|
- `LD B,B` — стоп; `LD D,D` затем `LD A,n` — размер блока (`n=0` → 256);
|
||||||
|
- `LD L,L` — **горизонтальная** копия (буфер ← `LD A,(HL)`; буфер → `LD (DE),A`);
|
||||||
|
- `LD A,A` (`0x7F`) — **вертикальная** копия (линии экрана, Port_Y шагает внутри
|
||||||
|
burst'а);
|
||||||
|
- `LD C,C` / `LD E,E` — fill (гориз./верт.).
|
||||||
|
|
||||||
|
Скорость ≈ **байт / 7 мкс**. Полный экран 320×256 ≈ **26 мс** (это **>1 кадра**
|
||||||
|
@ 50 Гц — держать в уме, §9). На время работы акселератора — **DI** (меняется
|
||||||
|
система команд, ISR сломается).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Ключевой инсайт: чистый фон достаётся бесплатно
|
||||||
|
|
||||||
|
- Спрайты рисуются банком с битом 2 (`0x54`/`0x5C`) → **только VRAM**,
|
||||||
|
ОЗУ-копию не трогают.
|
||||||
|
- ⇒ ОЗУ-копия (`0x50`) любой страницы — это фон **без спрайтов**, всегда.
|
||||||
|
- ⇒ скролл-копия из `0x50` в `0x50` читает чистый фон и пишет VRAM+тень — это
|
||||||
|
**одновременно скролл И heal** целевой страницы (её старые спрайты затираются
|
||||||
|
сдвинутым фоном).
|
||||||
|
|
||||||
|
Ровно так работает `gfx_heal` (частный случай `src==dst`, §3). Скролл — тот же
|
||||||
|
механизм, но `src != dst`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Что уже есть в libbgi — образцы для копирования
|
||||||
|
|
||||||
|
| Файл | Что | Зачем образец |
|
||||||
|
|---|---|---|
|
||||||
|
| `bgi256/_bgi_copy_rows_raw.c` | гориз. leaf (`LD L,L`), src/dst + страйды, Port_Y на строку | ядро всех блитов; страйды, SMC, DI |
|
||||||
|
| `bgi256/_bgi_heal_rows_raw.c` | `src==dst==экран`, страйды 0 | спец-случай heal |
|
||||||
|
| `bgi256/_bgi_blit_cols_raw.c` | **верт. leaf** (`LD A,A`), колонка=burst, Port_Y сброс на колонку, `dst+=1` | **прямой образец для `scroll_h`** |
|
||||||
|
| `common/_gfx_heal_full.c` | клип по экрану + нарезка полос >256 + вызов leaf | образец обёртки-ядра |
|
||||||
|
| `common/gfx_heal.c` | save-банк → `0x50` → `_bgi_begin` → ядро → `_bgi_end` → restore | образец верхней обёртки |
|
||||||
|
| `docs/converted/accel_r.txt` | screen→screen верт. копия LDIR-идиомом, `dst=src+0x140` | канонический скролл-паттерн |
|
||||||
|
|
||||||
|
Правило из `_bgi_copy_rows_raw`: **одна сторона копии — видео (в W3, строку даёт
|
||||||
|
Port_Y), другая может быть линейным буфером ВНЕ W3** (`< 0xC000`). Для скролла
|
||||||
|
обе стороны — видео (см. §4).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. `gfx_scroll_h` — горизонтальный (наш, приоритет)
|
||||||
|
|
||||||
|
**Идиом: вертикальный режим `LD A,A`, проход по колонкам.** Высота playfield
|
||||||
|
(144) ≤ 256 → одна колонка = один burst, **резать 320-строку на 256+64 НЕ надо**
|
||||||
|
(главная причина брать верт. режим). Это доковый screen→screen паттерн
|
||||||
|
(`accel_r.txt`) + `_bgi_blit_cols_raw`.
|
||||||
|
|
||||||
|
Параметры на колонку:
|
||||||
|
- `src = base(src_page) + x` — банк `0x50`, чтение = ОЗУ-копия = чистый фон;
|
||||||
|
- `dst = base(dst_page) + x + dx` — банк `0x50`, запись = VRAM + тень dst;
|
||||||
|
- `base`: page0 `0xC000`, page1 `0xC140`;
|
||||||
|
- размер блока = высота playfield (`h`, для Loom 144);
|
||||||
|
- Port_Y start = верх playfield (`y0`, для Loom 0);
|
||||||
|
- копируем `(w − |dx|)` колонок; вакантные `|dx|` колонок **не трогаем**.
|
||||||
|
|
||||||
|
**Знак:** зафиксировать `dx > 0` = сдвиг вправо (открывается слева), `dx < 0` =
|
||||||
|
влево; вернуть открывшийся край в `*dirty`.
|
||||||
|
|
||||||
|
**Порядок (по образцу докового LDIR-примера + `_bgi_blit_cols_raw`):** банда
|
||||||
|
колонок под DI → `LD D,D`/`LD A,h` (block) → `LD A,A` (верт. режим) → на колонку
|
||||||
|
`{ read col в accel; write col из accel; src++; dst++ }` → `EI`. При straight-copy
|
||||||
|
Port_Y авто-шагает (доковый LDIR его не трогает) — но `_bgi_blit_cols_raw`
|
||||||
|
сбрасывает Port_Y=`y0` на каждую колонку; **проверить, нужен ли сброс** (§7.1
|
||||||
|
покажет).
|
||||||
|
|
||||||
|
**Возврат:** `*dirty` = прямоугольник `|dx|` колонок с открывшегося края;
|
||||||
|
полосу не чистим — её дозаполняет вызывающий (в Loom — декодер EGA-страйпов).
|
||||||
|
|
||||||
|
```c
|
||||||
|
/* Горизонтальный скролл playfield. Копирует чистый фон (ОЗУ-копия src_page)
|
||||||
|
со сдвигом dx в dst_page (обе стороны банк 0x50: запись бьёт VRAM+тень).
|
||||||
|
dx>0 — вправо. Вакантную полосу НЕ заполняет — возвращает в *dirty. */
|
||||||
|
void gfx_scroll_h(uint8_t src_page, uint8_t dst_page,
|
||||||
|
const gfx_rect_t *area, int dx, gfx_rect_t *dirty);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. `gfx_scroll_v` — вертикальный (симметрия; Loom не нужен)
|
||||||
|
|
||||||
|
**Асимметрия железа:** горизонтальный сдвиг — это смещение CPU-адреса (src и dst
|
||||||
|
независимы) → легко. **Вертикальный сдвиг — это смещение Port_Y, а Port_Y один
|
||||||
|
на burst** → в простом LDIR-идиоме read и write идут с одного Port_Y, сдвига по
|
||||||
|
Y нет. Поэтому `scroll_v` сложнее `scroll_h`. Два пути:
|
||||||
|
|
||||||
|
- **(A) менять Port_Y между fill и flush одного burst'а**: `OUT Port_Y=y_src`;
|
||||||
|
read col в accel; `OUT Port_Y=y_src+dy`; write col из accel. Требует, чтобы
|
||||||
|
accel-буфер пережил `OUT` между чтением и записью — **не доказано** доком/
|
||||||
|
тестами (§7.3). Если пройдёт — симметрично `scroll_h`, верт. режим.
|
||||||
|
- **(B) безопасный fallback**: grab (video→RAM-буфер вне W3, гориз. режим),
|
||||||
|
затем blit (буфер→video со сдвигом Y — буферная сторона CPU-адресуема, сдвиг
|
||||||
|
любой). Два прохода + буфер, но заведомо работает.
|
||||||
|
|
||||||
|
**Рекомендация:** реализовать (A), если §7.3 пройдёт; иначе (B). Делать
|
||||||
|
**последним** — для Loom не критично. API симметричный (`dy` вместо `dx`):
|
||||||
|
|
||||||
|
```c
|
||||||
|
void gfx_scroll_v(uint8_t src_page, uint8_t dst_page,
|
||||||
|
const gfx_rect_t *area, int dy, gfx_rect_t *dirty);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Общие требования реализации
|
||||||
|
|
||||||
|
- **DI по банде, НЕ один DI на весь проход.** Полный playfield ~14 мс под DI
|
||||||
|
порвёт будущий CBL-звук. Одна колонка (144 Б ≈ 20 мкс) — безопасное DI-окно;
|
||||||
|
резать проход на банды колонок/строк с `EI` между (как `_bgi_copy_rows_raw`
|
||||||
|
режет ≤16 строк). Пока звука нет — не критично, но заложить сразу.
|
||||||
|
- **Клип по `area`** (образец — `_gfx_heal_full`).
|
||||||
|
- **Буфер (если путь (B))** — вне W3 (`< 0xC000`).
|
||||||
|
- **Верхняя обёртка**: save банк → `gfx_set_bank(GFX_BANK_NORMAL)` →
|
||||||
|
`_bgi_begin()` → ядро → `_bgi_end()` → restore (образец — `gfx_heal.c`).
|
||||||
|
- **Размещение**: leaf `bgi256/_bgi_scroll_cols_raw.c` (+ `_rows_raw` для (A)),
|
||||||
|
обёртки `common/gfx_scroll_h.c` / `gfx_scroll_v.c`, объявления в
|
||||||
|
`include/gfx.h`. `gfx_rect_t` — если ещё нет, определить там же
|
||||||
|
(`int x,y,w,h`).
|
||||||
|
- **Для Loom (не требование к примитиву, но контекст)**: вызывающий держит
|
||||||
|
`dx` кратным 8 (ширина EGA-страйпа) → `dirty` = целые страйпы. Обёртка этому
|
||||||
|
не мешает, но и не навязывает.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. ПРОВЕРИТЬ ПЕРВЫМ — блокеры (dev-MAME)
|
||||||
|
|
||||||
|
1. **Верт. режим (`LD A,A`) в банке `0x50` читает ОЗУ-копию (чистый фон) и
|
||||||
|
пишет VRAM+тень.** Гориз. heal это доказывает построчно; для верт. прохода —
|
||||||
|
прогон: нарисовать фон, поверх спрайт банком `0x5C`, сделать `scroll_h` на
|
||||||
|
dx>0, убедиться что спрайт **не размазался** (копировался фон, не VRAM со
|
||||||
|
спрайтом). Заодно выяснить, нужен ли сброс Port_Y на колонку (§4).
|
||||||
|
2. **ОЗУ-копия per-page или общая.** `C-Compiler/applications/PoP/docs/double_buffer_plan.md`
|
||||||
|
пишет «теневая копия одна — общая». Если тень физически одна (а не адресуется
|
||||||
|
по базе `0xC000/0xC140` как VRAM), page0 и page1 не удержат фон **разных**
|
||||||
|
положений камеры во время скролла → пинг-понг (§8) сломается. Проверить:
|
||||||
|
записать разный фон в page0 и page1 банком `0x50`, сверить чтение обеих.
|
||||||
|
3. **(только для `scroll_v`, путь A)** accel-буфер переживает `OUT Port_Y` между
|
||||||
|
fill и flush одного burst'а. Доковый straight-copy этого не проверяет.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Порядок кадра в Loom (контекст использования `scroll_h`)
|
||||||
|
|
||||||
|
Пинг-понг двух страниц; спрайты в VRAM-only банк, поэтому тень каждой страницы
|
||||||
|
остаётся чистым фоном:
|
||||||
|
|
||||||
|
1. `gfx_set_draw_page(B)` — B = скрытая;
|
||||||
|
2. `gfx_scroll_h(A, B, area, dx, &dirty)` — B получает сдвинутый чистый фон в
|
||||||
|
VRAM+тень; старые спрайты B затёрты (copy = heal);
|
||||||
|
3. декодировать EGA-страйпы фона в `dirty`, **банк `0x50`** (иначе тень B
|
||||||
|
останется с дырой — следующий скролл из B прочитает мусор);
|
||||||
|
4. вывести актора/спрайты в B, банк `0x5C`;
|
||||||
|
5. `gfx_wait_vsync()` → `gfx_set_visible_page(B)`.
|
||||||
|
|
||||||
|
Следующий кадр: роли A/B меняются, читаем уже чистую тень B. Смаза нет by design
|
||||||
|
(§2). При `dx==0` (камера стоит) — обычный per-sprite `gfx_heal`, а не полная
|
||||||
|
копия.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Бюджет и проверка корректности
|
||||||
|
|
||||||
|
- 320×256 ≈ 26 мс (**>кадр**); playfield 320×144 ≈ **14–15 мс** (<кадр); скролл
|
||||||
|
копирует `(320−dx)` колонок → чуть меньше. Итого копия ~кадр + докод полосы +
|
||||||
|
спрайты; целевой бюджет ~2 кадра/шаг (plan.md §6). **Замерить на PoC-1**
|
||||||
|
маркерами `OUT (0xFE)` (до появления звука порт свободен).
|
||||||
|
- Корректность — визуально/дифф-тестом в dev-MAME. Фон-декодер уже проверен
|
||||||
|
диффом против ScummVM; скролл проверять на реальной комнате шире 320.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Связанные документы
|
||||||
|
|
||||||
|
`plan.md §6` (графика, скроллинг), `toolchain-requirements.md §1` (этот
|
||||||
|
примитив), `decisions.md`. Железо: `C-Compiler/docs/converted/accel_r.txt`,
|
||||||
|
`libbgi/include/gfx.h`, `C-Compiler/docs/sprite-api-design.md`,
|
||||||
|
`C-Compiler/docs/memory-management.md`.
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* scroll — визуальная проверка скролл-примитивов libbgi (gfx_scroll_h /
|
||||||
|
* gfx_scroll_v). Копирует регион из НЕактивной страницы в активную со
|
||||||
|
* сдвигом и переключает страницы.
|
||||||
|
*
|
||||||
|
* ДИАГНОСТИКА. Страница 0 — диагональные полосы color=((x+y)>>3)&15
|
||||||
|
* (любой сдвиг ломает непрерывность диагоналей; фаза по Y ловит
|
||||||
|
* вертикальный «съезд» Port_Y-квирка). Страница 1 — сплошной серый
|
||||||
|
* фон-маркер: после скролла на нём проступают ровно скопированные
|
||||||
|
* прямоугольники, по их границам видно корректность (позицию/высоту).
|
||||||
|
*
|
||||||
|
* Два региона в одном кадре: слева ГОРИЗОНТАЛЬНЫЙ скролл (dx>0 — картинка
|
||||||
|
* вправо, открывается серая полоса слева), справа ВЕРТИКАЛЬНЫЙ (dy>0 —
|
||||||
|
* вниз, открывается серая полоса сверху). Если верт. Port_Y-квирк не
|
||||||
|
* вылечен — правый прямоугольник съедет вниз/опустеет сверху. Esc —
|
||||||
|
* выход.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <graphics.h>
|
||||||
|
#include <gfx.h>
|
||||||
|
#include <sprite.h>
|
||||||
|
#include <conio.h>
|
||||||
|
|
||||||
|
static uint8_t egapal[16 * 4];
|
||||||
|
|
||||||
|
__sfr __at (0xFE) io_border;
|
||||||
|
|
||||||
|
/* Диагональные полосы 16-цветной палитры (8-пиксельные ступени). */
|
||||||
|
static void draw_diagonals(void)
|
||||||
|
{
|
||||||
|
int bx, by;
|
||||||
|
for (by = 0; by < 256; by += 8)
|
||||||
|
for (bx = 0; bx < 320; bx += 8) {
|
||||||
|
setfillstyle(SOLID_FILL, (((bx + by) >> 3) & 15));
|
||||||
|
bar(bx, by, bx + 7, by + 7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Регион горизонтального скролла (слева) и вертикального (справа). */
|
||||||
|
static gfx_rect_t area_h = { 0, 56, 320, 144 };
|
||||||
|
// static gfx_rect_t area_h = { 16, 48, 80, 80 };
|
||||||
|
// static gfx_rect_t area_v = { 176, 48, 80, 80 };
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
uint8_t hidden;
|
||||||
|
|
||||||
|
initgraph();
|
||||||
|
|
||||||
|
/* Палитра страницы 1 = палитре страницы 0 (initgraph грузит EGA
|
||||||
|
* только в палитру 0). */
|
||||||
|
gfx_pal_get(0, 0, 16, egapal);
|
||||||
|
gfx_pal_load(1, 0, 16, egapal);
|
||||||
|
|
||||||
|
/* Страница 0 — диагонали (источник фона). */
|
||||||
|
gfx_set_draw_page(0);
|
||||||
|
draw_diagonals();
|
||||||
|
|
||||||
|
gfx_set_draw_page(1);
|
||||||
|
draw_diagonals();
|
||||||
|
|
||||||
|
gfx_set_visible_page(0);
|
||||||
|
|
||||||
|
/* Рисуем на скрытой странице (1), потом флип. */
|
||||||
|
hidden = gfx_get_visible_page() ^ 1; /* = 1 */
|
||||||
|
gfx_set_draw_page(hidden);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (kbhit() && getch() == 27)
|
||||||
|
break;
|
||||||
|
io_border = 2;
|
||||||
|
gfx_scroll_v(&area_h, 2, 0); /* вправо на 48 px */
|
||||||
|
// gfx_scroll_v(&area_v, 2, 0); /* вниз на 48 px */
|
||||||
|
io_border = 0;
|
||||||
|
gfx_wait_vsync();
|
||||||
|
gfx_wait_vsync();
|
||||||
|
|
||||||
|
hidden = gfx_get_visible_page() ^ 1;
|
||||||
|
gfx_set_draw_page(hidden ^ 1);
|
||||||
|
gfx_set_visible_page(hidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
closegraph();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ extern uint8_t _bgi_prevmode; /* видеорежим до initgraph */
|
|||||||
/* CPU-адрес колонки 0 текущей draw-страницы: 0xC000 (page 0) или
|
/* CPU-адрес колонки 0 текущей draw-страницы: 0xC000 (page 0) или
|
||||||
* 0xC140 (page 1); каждый примитив использует его вместо константы. */
|
* 0xC140 (page 1); каждый примитив использует его вместо константы. */
|
||||||
extern uint16_t _gfx_addr_base;
|
extern uint16_t _gfx_addr_base;
|
||||||
|
extern uint16_t _gfx_addr_shadow_base;
|
||||||
|
|
||||||
/* ---- Низкоуровневый видеорежим (libc/video) ---------------------- */
|
/* ---- Низкоуровневый видеорежим (libc/video) ---------------------- */
|
||||||
uint8_t _videomode_raw_get(void);
|
uint8_t _videomode_raw_get(void);
|
||||||
@@ -108,6 +109,30 @@ void _bgi_heal_rows_raw(uint8_t *scr, int y0, uint8_t w, uint8_t h);
|
|||||||
void _bgi_hspan (int x, int y, int len, uint8_t color);
|
void _bgi_hspan (int x, int y, int len, uint8_t color);
|
||||||
void _bgi_clearall(uint8_t color);
|
void _bgi_clearall(uint8_t color);
|
||||||
|
|
||||||
|
|
||||||
|
/* Скролл-ядра video->video (банк 0x50: копия = скролл + heal цели; без
|
||||||
|
* скобки/клиппинга; DI/EI бандами по 16 ВНУТРИ; перекрытия нет — стороны
|
||||||
|
* в разных страницах). Контракты — в шапках bgi256/_bgi_scroll_*_raw.c.
|
||||||
|
*
|
||||||
|
* _bgi_scroll_rows_raw — ядро gfx_scroll_h: сдвиг ТОЛЬКО по X (разница
|
||||||
|
* адресов src/dst), построчно, БЕЗ страйдов — один OUT Port_Y на строку
|
||||||
|
* (~54Т/строку, на 5-7% быстрее cols). w — байт/строка (0=256), h —
|
||||||
|
* строк 1..256 (0=256, конвенция accel), y0 — стартовый Port_Y (++ на строку). */
|
||||||
|
void _bgi_scroll_rows_raw(uint16_t src, uint16_t dst,
|
||||||
|
uint8_t w, uint8_t h, uint8_t y0);
|
||||||
|
|
||||||
|
/* _bgi_scroll_cols_raw — ядро gfx_scroll_v: сдвиг по X И/ИЛИ Y за один
|
||||||
|
* проход БЕЗ буфера. Колонка = вертикальный accel-burst (LD A,A): читаем
|
||||||
|
* с Port_Y=y0, пишем с Port_Y=y1 (сдвиг dy = y1−y0; dx — в адресах). STOP
|
||||||
|
* между read и write делает промежуточный OUT Port_Y безопасным. w —
|
||||||
|
* число КОЛОНОК 1..255 (0=выход), h — высота колонки = размер accel-блока
|
||||||
|
* (0=256), y0/y1 — Port_Y чтения/записи. */
|
||||||
|
void _bgi_scroll_cols_raw(uint16_t src, uint16_t dst,
|
||||||
|
uint8_t w, uint8_t h, uint8_t y0, uint8_t y1);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Ядро блиттинга gfx_blit_part/gfx_blit/gfx_heal (Фаза B) — публичные,
|
/* Ядро блиттинга gfx_blit_part/gfx_blit/gfx_heal (Фаза B) — публичные,
|
||||||
* прототипы в <gfx.h>; спрайтовые обёртки putsprite/movesprite — в
|
* прототипы в <gfx.h>; спрайтовые обёртки putsprite/movesprite — в
|
||||||
* <graphics.h>. Реализации: common/gfx_blit_part.c и соседи. */
|
* <graphics.h>. Реализации: common/gfx_blit_part.c и соседи. */
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* _bgi_scroll_cols_raw — accel-скролл КОЛОНКАМИ video->video (mode 0x81).
|
||||||
|
* УНИВЕРСАЛЕН: делает и горизонтальный, и ВЕРТИКАЛЬНЫЙ сдвиг за один
|
||||||
|
* проход, БЕЗ буфера-посредника. Ядро gfx_scroll_v (вертикаль); может
|
||||||
|
* служить и горизонтали (gfx_scroll_h держит свой rows-вариант — он
|
||||||
|
* на ~5-7% быстрее, т.к. на строку нужен один OUT Port_Y, а не два).
|
||||||
|
*
|
||||||
|
* КАК РАБОТАЕТ. Колонка = один вертикальный accel-burst (LD A,A, 0x7F —
|
||||||
|
* «копия блока для вертикальных линий»): read набирает h байт колонки src
|
||||||
|
* с Port_Y=y0 (Port_Y авто-инкрементится внутри burst'а: y0→y0+h), write
|
||||||
|
* выгружает их в колонку dst с Port_Y=y1 (y1→y1+h). Вертикальный сдвиг =
|
||||||
|
* разница y0/y1; горизонтальный = разница адресов src/dst (dx). Оба сразу
|
||||||
|
* = произвольный (dx,dy). dst/src двигаются на +1 (следующий x) за колонку.
|
||||||
|
*
|
||||||
|
* ПОЧЕМУ РАБОТАЕТ смена Port_Y между read и write (в отличие от наивного
|
||||||
|
* rows-варианта, где это ломало accel). Ключ — STOP (LD B,B) ПЕРЕД
|
||||||
|
* OUT Port_Y=y1: он разоружает accel (m_acc_dir=0), поэтому fetch операнда
|
||||||
|
* OUT (immediate байт 0x89 из программной памяти) НЕ перезапускает
|
||||||
|
* read-burst. Затем LD A,A снова взводит вертикальный режим для записи.
|
||||||
|
* Оба OUT'а (y0 и y1) выполняются только пока accel РАЗОРУЖЁН. Порядок на
|
||||||
|
* колонку: {OUT y0 · arm · read · STOP · OUT y1 · arm · write · STOP}.
|
||||||
|
* (Подробно про квирк перезапуска — memory/accel_operand_fetch_retrigger.)
|
||||||
|
*
|
||||||
|
* DI/EI — БАНДАМИ по 16 колонок ВНУТРИ (как _gfx_recthfill256): держать DI
|
||||||
|
* на весь скролл нельзя (порвёт CBL-звук). Размер блока (h) армируется в
|
||||||
|
* КАЖДОЙ скобке (в окне EI CBL-ISR мог бы переармировать accel). Единая
|
||||||
|
* тело-петля для полных банд и хвоста (счётчик колонок decrement'ится в
|
||||||
|
* стек-слоте); y0 держим в C (LD A,C 4Т вместо LD A,#imm 7Т на колонку),
|
||||||
|
* y1 — SMC-immediate (грузится только пока разоружены — безопасно).
|
||||||
|
*
|
||||||
|
* ВХОД (__sdcccall(1)): src->HL, dst->DE; стек: w 4(ix) — число КОЛОНОК
|
||||||
|
* 1..255 (0 = тихий выход; ровно 256 режет вызывающий), h 5(ix) — высота
|
||||||
|
* колонки = размер accel-блока 1..256 (0 = 256, конвенция accel),
|
||||||
|
* y0 6(ix) — Port_Y чтения, y1 7(ix) — Port_Y записи. Callee-pop 4 байта.
|
||||||
|
*
|
||||||
|
* Raw: клиппинга нет (вызывающий), W3 замаплен (_bgi_begin/end снаружи),
|
||||||
|
* банк 0x50 (запись бьёт VRAM+тень — скролл И heal цели). Перекрытия
|
||||||
|
* src/dst нет by design — стороны в РАЗНЫХ видеостраницах. Колонок >255 и
|
||||||
|
* колонки >256 строк режет вызывающий. Клоббер: AF/BC; HL/DE/IX
|
||||||
|
* сохраняются (LD A,A/LD D,D — accel-опкоды-no-op'ы, регистры не трогают).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../_bgi.h"
|
||||||
|
|
||||||
|
void _bgi_scroll_cols_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, 5 (ix)
|
||||||
|
ld (sc_col_len), a ; SMC: размер accel-блока <- h (0 = 256)
|
||||||
|
ld a, 7 (ix)
|
||||||
|
ld (sc_col_y1), a ; SMC: Port_Y записи <- y1
|
||||||
|
ld c, 6 (ix) ; C = Port_Y чтения (y0) — LD A,C дешевле imm
|
||||||
|
sc_col_band:
|
||||||
|
ld a, 4 (ix) ; колонок осталось
|
||||||
|
or a, a
|
||||||
|
jr Z, sc_col_done ; 0 => выход
|
||||||
|
cp a, #16
|
||||||
|
jr NC, sc_col_full ; >=16 — полная банда
|
||||||
|
ld b, a ; <16 — хвост: B = остаток
|
||||||
|
jr sc_col_go
|
||||||
|
sc_col_full:
|
||||||
|
ld b, #16 ; 16 колонок в банде
|
||||||
|
sc_col_go:
|
||||||
|
ld a, 4 (ix)
|
||||||
|
sub a, b
|
||||||
|
ld 4 (ix), a ; колонок -= B
|
||||||
|
di
|
||||||
|
ld d, d ; 0x52 — режим размера блока (no-op для D)
|
||||||
|
ld a, #0
|
||||||
|
sc_col_len = . - 1
|
||||||
|
ld b, b ; выключить — размер сохранён (B=счётчик цел)
|
||||||
|
sc_col_next:
|
||||||
|
ld a, c ; Port_Y чтения (y0)
|
||||||
|
out (#0x89), a ; accel РАЗОРУЖЁН — операнд OUT безопасен
|
||||||
|
ld a, a ; 0x7F — арм вертикальной копии
|
||||||
|
ld a, (hl) ; read-burst: колонка src -> буфер (Port_Y y0..)
|
||||||
|
ld b, b ; 0x40 — STOP (разоружить перед OUT y1)
|
||||||
|
ld a, #0
|
||||||
|
sc_col_y1 = . - 1
|
||||||
|
out (#0x89), a ; Port_Y записи (y1) — accel разоружён
|
||||||
|
ld a, a ; 0x7F — арм вертикальной копии
|
||||||
|
ld (de), a ; write-burst: буфер -> колонка dst (Port_Y y1..)
|
||||||
|
ld b, b ; 0x40 — STOP
|
||||||
|
inc hl ; src += 1 (следующий x)
|
||||||
|
inc de ; dst += 1
|
||||||
|
djnz sc_col_next
|
||||||
|
ei ; окно прерываний между бандами
|
||||||
|
jr sc_col_band
|
||||||
|
sc_col_done:
|
||||||
|
pop ix
|
||||||
|
;; callee-pop 4 байта (w, h, y0, y1)
|
||||||
|
pop hl ; ret-адрес
|
||||||
|
pop af
|
||||||
|
pop af
|
||||||
|
jp (hl)
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* _bgi_scroll_rows_raw — БЫСТРАЯ построчная копия video->video со сдвигом
|
||||||
|
* ТОЛЬКО по X для gfx_scroll_h (mode 0x81). Специализация _bgi_copy_rows_raw
|
||||||
|
* для скролла: обе стороны — видео, ОБА страйда НУЛЕВЫЕ (CPU-адрес строки
|
||||||
|
* фиксирован, строку выбирает Port_Y), поэтому адресной арифметики в цикле
|
||||||
|
* НЕТ ВООБЩЕ — ни страйдовых add (как у copy_rows), ни даже inc адресов
|
||||||
|
* (как у heal). От _bgi_heal_rows_raw отличается тем, что src != dst
|
||||||
|
* (HL — колонка-старт src-страницы, DE — dst-страницы со сдвигом dx):
|
||||||
|
* горизонтальный сдвиг целиком в разнице адресов, задаётся вызывающим.
|
||||||
|
*
|
||||||
|
* Вертикального сдвига НЕТ (read и write одной строки на одном Port_Y) —
|
||||||
|
* для сдвига по Y есть _bgi_scroll_cols_raw (колонки, вертикальный режим).
|
||||||
|
* Зато rows на ~5-7% быстрее cols: на строку один OUT Port_Y (54Т/строку),
|
||||||
|
* а cols нужен OUT на чтение И на запись колонки.
|
||||||
|
*
|
||||||
|
* ПОРЯДОК на строку: {OUT Port_Y · arm(LD L,L) · read(LD A,(HL)) ·
|
||||||
|
* write(LD (DE),A) · STOP(LD B,B)}. Между read- и write-триггером —
|
||||||
|
* НИЧЕГО: на Sprinter accel перехватывает любое чтение программной памяти,
|
||||||
|
* а fetch операнда любой инструкции там (immediate у OUT/ADD) спурьезно
|
||||||
|
* перезапустил бы read-burst и набил буфер кодом. Port_Y ставится ДО
|
||||||
|
* arm'а (accel разоружён STOP'ом прошлой строки — операнд OUT безопасен).
|
||||||
|
* (memory/accel_operand_fetch_retrigger.)
|
||||||
|
*
|
||||||
|
* DI/EI — БАНДАМИ по 16 строк ВНУТРИ (как _gfx_recthfill256): накрывать DI
|
||||||
|
* весь сдвиг нельзя (порвёт CBL-звук). Армирование размера блока — в
|
||||||
|
* КАЖДОЙ скобке (в окне EI CBL-ISR армирует accel своим размером). Число
|
||||||
|
* полных 16-банд и хвост считаем ОДИН раз на входе (precompute) — в петле
|
||||||
|
* банд остаётся дешёвый dec (без per-band `sub 16` через IX-слот).
|
||||||
|
* Регистры упёрты (HL src, DE dst, B djnz, C бегущий Port_Y) — быстрее уже
|
||||||
|
* некуда: out(c) без стопов потребовал бы Port_Y-регистра, которого нет.
|
||||||
|
*
|
||||||
|
* ВХОД (__sdcccall(1)): src->HL, dst->DE; стек: w 4(ix) — байт в строке
|
||||||
|
* (размер accel-блока, 1..256, 0=256), h 5(ix) — строк 1..256 (0 = 256,
|
||||||
|
* КОНВЕНЦИЯ accel — как у w), y0 6(ix) — стартовый Port_Y (инкремент на
|
||||||
|
* строку). Callee-pop 3 байта.
|
||||||
|
*
|
||||||
|
* Raw: клиппинга нет (вызывающий), W3 замаплен (_bgi_begin/end снаружи),
|
||||||
|
* банк 0x50 (запись бьёт VRAM+тень — скролл И heal цели). Перекрытия
|
||||||
|
* src/dst нет by design — стороны в РАЗНЫХ видеостраницах. Строку >256
|
||||||
|
* байт режет вызывающий. Клоббер: AF/BC; HL/DE/IX сохраняются
|
||||||
|
* (адреса-константы не трогаются — LD L,L/LD D,D это accel-опкоды-no-op'ы).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../_bgi.h"
|
||||||
|
|
||||||
|
void _bgi_scroll_rows_raw(uint16_t src, uint16_t dst,
|
||||||
|
uint8_t w, uint8_t h, uint8_t y0) __naked {
|
||||||
|
(void)src; (void)dst; (void)w; (void)h; (void)y0;
|
||||||
|
__asm
|
||||||
|
push ix
|
||||||
|
ld ix, #0
|
||||||
|
add ix, sp
|
||||||
|
;; HL = src, DE = dst (адреса-константы; строку задаёт Port_Y)
|
||||||
|
ld a, 4 (ix)
|
||||||
|
ld (sc_row_len), a ; SMC: размер блока <- w (0 = 256)
|
||||||
|
ld (sc_row_len2), a ; ...и в хвостовую скобку
|
||||||
|
ld c, 6 (ix) ; C = бегущий Port_Y (y0)
|
||||||
|
|
||||||
|
;; --- precompute: полных 16-банд (ix+5) + хвост (SMC); h=0 => 256 ---
|
||||||
|
ld a, 5 (ix)
|
||||||
|
and #0x0F
|
||||||
|
ld (sc_row_tail), a ; хвост = h & 15 (0 и при h=0, и при h=256)
|
||||||
|
ld a, 5 (ix)
|
||||||
|
or a, a
|
||||||
|
jr NZ, sc_row_shr
|
||||||
|
ld a, #16 ; h=0 => 256 => ровно 16 полных банд
|
||||||
|
jr sc_row_setfull
|
||||||
|
sc_row_shr:
|
||||||
|
rrca
|
||||||
|
rrca
|
||||||
|
rrca
|
||||||
|
rrca
|
||||||
|
and #0x0F ; full = h >> 4
|
||||||
|
sc_row_setfull:
|
||||||
|
ld 5 (ix), a ; ix+5 = число полных 16-строчных банд
|
||||||
|
|
||||||
|
sc_row_band:
|
||||||
|
ld a, 5 (ix)
|
||||||
|
or a, a
|
||||||
|
jr Z, sc_row_tailband ; полные банды кончились
|
||||||
|
dec 5 (ix) ; full -= 1
|
||||||
|
ld b, #16 ; 16 строк в банде
|
||||||
|
di
|
||||||
|
ld d, d ; 0x52 — режим размера блока (no-op для D)
|
||||||
|
ld a, #0
|
||||||
|
sc_row_len = . - 1
|
||||||
|
ld b, b ; выключить — размер сохранён (B=16 цел)
|
||||||
|
sc_row_next:
|
||||||
|
ld a, c
|
||||||
|
out (#0x89), a ; Port_Y = y (accel ВЫКЛЮЧЕН — операнд безопасен)
|
||||||
|
inc c ; y++
|
||||||
|
ld l, l ; 0x6D — арм гориз. копии (no-op для L)
|
||||||
|
ld a, (hl) ; read-burst: src-строка -> буфер
|
||||||
|
ld (de), a ; write-burst: буфер -> dst-строка (вплотную!)
|
||||||
|
ld b, b ; 0x40 — стоп
|
||||||
|
djnz sc_row_next
|
||||||
|
ei ; окно прерываний между бандами
|
||||||
|
jr sc_row_band
|
||||||
|
;; --- хвост h&15 строк: отдельная скобка со СВОИМ армированием ---
|
||||||
|
sc_row_tailband:
|
||||||
|
ld a, #0
|
||||||
|
sc_row_tail = . - 1
|
||||||
|
or a, a
|
||||||
|
jr Z, sc_row_done ; хвоста нет
|
||||||
|
ld b, a ; B = остаток строк (1..15)
|
||||||
|
di
|
||||||
|
ld d, d
|
||||||
|
ld a, #0
|
||||||
|
sc_row_len2 = . - 1
|
||||||
|
ld b, b
|
||||||
|
sc_row_trow:
|
||||||
|
ld a, c
|
||||||
|
out (#0x89), a
|
||||||
|
inc c
|
||||||
|
ld l, l
|
||||||
|
ld a, (hl)
|
||||||
|
ld (de), a
|
||||||
|
ld b, b
|
||||||
|
djnz sc_row_trow
|
||||||
|
ei
|
||||||
|
sc_row_done:
|
||||||
|
pop ix
|
||||||
|
;; callee-pop 3 байта (w, h, y0)
|
||||||
|
pop hl ; ret-адрес
|
||||||
|
pop af
|
||||||
|
inc sp
|
||||||
|
jp (hl)
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ uint8_t _gfx_bank = 0x50;
|
|||||||
* (CPU 0xC000+), page 1 — 320..639 (CPU 0xC140+), остаток — дескрипторы
|
* (CPU 0xC000+), page 1 — 320..639 (CPU 0xC140+), остаток — дескрипторы
|
||||||
* режима/палитра, их не трогаем. */
|
* режима/палитра, их не трогаем. */
|
||||||
uint16_t _gfx_addr_base = 0xC000;
|
uint16_t _gfx_addr_base = 0xC000;
|
||||||
|
uint16_t _gfx_addr_shadow_base = 0xC140;
|
||||||
|
|
||||||
/* Клип спрайтов по экрану: 1 = включён (безопасно, дефолт), 0 = выключен
|
/* Клип спрайтов по экрану: 1 = включён (безопасно, дефолт), 0 = выключен
|
||||||
* (приложение само гарантирует, что спрайты не выходят за края — даёт
|
* (приложение само гарантирует, что спрайты не выходят за края — даёт
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,5 +10,11 @@ void gfx_set_draw_page(uint8_t page)
|
|||||||
_gfx_draw_page = page & 1;
|
_gfx_draw_page = page & 1;
|
||||||
/* Прямые константы короче (0xC000 + (cond ? 0x140 : 0)) на 3
|
/* Прямые константы короче (0xC000 + (cond ? 0x140 : 0)) на 3
|
||||||
* инструкции Z80 — SDCC не сворачивает сложение констант. */
|
* инструкции 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -308,4 +308,16 @@ void atlas_sprite_init(sprite_t *s, const atlas_t *a, uint8_t idx);
|
|||||||
void gfx_w0_map(uint8_t page);
|
void gfx_w0_map(uint8_t page);
|
||||||
void gfx_w0_unmap(void);
|
void gfx_w0_unmap(void);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int16_t x, y;
|
||||||
|
uint16_t w, h;
|
||||||
|
} gfx_rect_t;
|
||||||
|
|
||||||
|
/* Скролл региона area из НЕактивной страницы в активную со сдвигом на
|
||||||
|
* dx/dy (0 = чистая копия/heal, >0 = в сторону увеличения координат,
|
||||||
|
* <0 = уменьшения). Открывшуюся полосу |d| НЕ заполняют — возвращают
|
||||||
|
* в *dirty (может быть NULL). Банк 0x50: копия = скролл + heal цели. */
|
||||||
|
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);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -121,6 +121,17 @@ w2_join:
|
|||||||
ld (_estex_block_id), a
|
ld (_estex_block_id), a
|
||||||
ld (_estex_startup_ix), ix
|
ld (_estex_startup_ix), ix
|
||||||
|
|
||||||
|
.ifdef W3_RESIDENT
|
||||||
|
;; --w3 в HUGE: резидентный код окна W3 (0xC000) — часть HOME-образа,
|
||||||
|
;; замаплен DSS. Загрузка банков ниже подменяет W3 своими страницами;
|
||||||
|
;; запомним физ-страницу резидента, чтобы вернуть её дефолтом после
|
||||||
|
;; цикла — тогда прямые вызовы в резидентный W3-код снова корректны.
|
||||||
|
;; (Трамплин bank.s сам сохраняет/восстанавливает W3 на каждый __banked
|
||||||
|
;; вызов, поэтому дефолтная страница обязана быть резидентной.)
|
||||||
|
in a, (#0xE2)
|
||||||
|
ld (_w3_resident_page), a
|
||||||
|
.endif
|
||||||
|
|
||||||
;; ---- Allocate _n_banks pages via ESTEX GETMEM ----
|
;; ---- Allocate _n_banks pages via ESTEX GETMEM ----
|
||||||
ld a, (_n_banks)
|
ld a, (_n_banks)
|
||||||
or a, a
|
or a, a
|
||||||
@@ -182,6 +193,14 @@ load_bank_done:
|
|||||||
rst #0x10
|
rst #0x10
|
||||||
;; Ignore CF from close.
|
;; Ignore CF from close.
|
||||||
|
|
||||||
|
.ifdef W3_RESIDENT
|
||||||
|
;; Вернуть резидентную страницу W3 дефолтом (цикл выше оставил в W3
|
||||||
|
;; последний банк). После этого прямые call'ы в резидентный код 0xC0xx
|
||||||
|
;; корректны, а трамплины будут сохранять/возвращать именно её.
|
||||||
|
ld a, (_w3_resident_page)
|
||||||
|
out (#0xE2), a
|
||||||
|
.endif
|
||||||
|
|
||||||
skip_bank_load:
|
skip_bank_load:
|
||||||
;; ---- Standard SDCC init path ----
|
;; ---- Standard SDCC init path ----
|
||||||
call gsinit
|
call gsinit
|
||||||
@@ -411,6 +430,10 @@ _estex_block_id::
|
|||||||
.ds 1
|
.ds 1
|
||||||
_bank_block_id::
|
_bank_block_id::
|
||||||
.ds 1
|
.ds 1
|
||||||
|
.ifdef W3_RESIDENT
|
||||||
|
_w3_resident_page:
|
||||||
|
.ds 1
|
||||||
|
.endif
|
||||||
|
|
||||||
.area _DATA
|
.area _DATA
|
||||||
_argc::
|
_argc::
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# w3probe — проверка --w3 (резидентный код окна W3, прямой вызов без
|
||||||
|
# трамплинов) во ВСЕХ режимах памяти. Все пробы переиспользуют w3res.c
|
||||||
|
# (резидентный W3-модуль: w3_show/w3_probe/w3_sig).
|
||||||
|
#
|
||||||
|
# small : код 0x4100 (W1/W2), W3 резидент. w3probe.exe
|
||||||
|
# tiny : код 0x8100 (W2), W1 не исп., W3 резидент. w3tiny.exe
|
||||||
|
# big : код 0x8100 (W2), банки в W1, W3 резидент. w3big.exe
|
||||||
|
# huge : код 0x4100, W3 ДЕЛЯТ резидент + трамплин-банки. w3huge.exe
|
||||||
|
#
|
||||||
|
# Проверено в MAME (2026-07-21): все режимы работают, включая huge —
|
||||||
|
# резидент↔W3-банк через трамплин в W1, crt0_banked возвращает резидентную
|
||||||
|
# страницу дефолтом. Раскладка страниц: tiny W1=FF (не исп.), big/huge W1=F1.
|
||||||
|
|
||||||
|
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||||
|
CC := $(PROJ_ROOT)/bin/sprinter-cc
|
||||||
|
MAKE_HDD := $(PROJ_ROOT)/toolchain/make_hdd.sh
|
||||||
|
HDD_IMG := $(PROJ_ROOT)/mame/v306/IMG/test_hdd.chd
|
||||||
|
|
||||||
|
EXES := w3probe.exe w3tiny.exe w3big.exe w3huge.exe
|
||||||
|
|
||||||
|
all: $(EXES)
|
||||||
|
|
||||||
|
w3probe.exe: w3probe.c w3res.c
|
||||||
|
$(CC) --memory small --w3 w3res.c -o $@ w3probe.c
|
||||||
|
|
||||||
|
w3tiny.exe: w3tiny.c w3res.c
|
||||||
|
$(CC) --memory tiny --w3 w3res.c -o $@ w3tiny.c
|
||||||
|
|
||||||
|
w3big.exe: w3big.c w3res.c bigbank.c
|
||||||
|
$(CC) --memory big --w3 w3res.c --bank 1=bigbank.c -o $@ w3big.c
|
||||||
|
|
||||||
|
w3huge.exe: w3huge.c w3res.c w3huge_res.c hugebank.c
|
||||||
|
$(CC) --memory huge --w3 w3res.c --w3 w3huge_res.c --bank 1=hugebank.c -o $@ w3huge.c
|
||||||
|
|
||||||
|
# Упаковать все четыре на HDD (D:) для прогона в MAME. После — рестарт MAME.
|
||||||
|
hdd: $(EXES)
|
||||||
|
$(MAKE_HDD) $(HDD_IMG) $(EXES)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf .sprinter-cc-* $(EXES)
|
||||||
|
|
||||||
|
.PHONY: all hdd clean
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/* bigbank.c — свапаемый банк в W1 (big mode, трамплин через порт 0xA2).
|
||||||
|
* Отдельно от резидентного W3-кода (w3res.c) — проверяем сосуществование. */
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sprinter.h>
|
||||||
|
|
||||||
|
void bigbank_func(void) __banked
|
||||||
|
{
|
||||||
|
printf("W1-BANK: bigbank_func @ ~0x4xxx (trampoline), W1 phys=0x%02X\n",
|
||||||
|
_io_page_w1);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/* hugebank.c — СВАПАЕМЫЙ банк в окне W3 (huge mode, трамплин через 0xE2).
|
||||||
|
* Отдельная страница, подменяет резидентный код на время __banked-вызова;
|
||||||
|
* трамплин обязан вернуть резидентную страницу по выходу. */
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sprinter.h>
|
||||||
|
|
||||||
|
void hugebank_func(void) __banked
|
||||||
|
{
|
||||||
|
printf("W3-BANK: hugebank_func (trampoline), W3 phys now=0x%02X\n",
|
||||||
|
_io_page_w3);
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/* w3big.c — проба --w3 в режиме big.
|
||||||
|
* big: код 0x8100 (W2), свапаемые банки в W1 (трамплины), W3 свободно под
|
||||||
|
* резидент. Проверяем сосуществование: W1-банк (трамплин) + W3-резидент
|
||||||
|
* (прямой вызов) в одной программе. */
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sprinter.h>
|
||||||
|
|
||||||
|
/* crt0_banked читает это ДО gsinit — только `const`. */
|
||||||
|
const uint8_t n_banks = 1;
|
||||||
|
|
||||||
|
void bigbank_func(void) __banked; /* W1-банк, трамплин */
|
||||||
|
extern uint8_t w3_probe(uint8_t x); /* W3-резидент, прямо */
|
||||||
|
extern void w3_show(void);
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
printf("BIG : main @ 0x%04X | W1=%02X W2=%02X W3=%02X\n",
|
||||||
|
(unsigned)&main, _io_page_w1, _io_page_w2, _io_page_w3);
|
||||||
|
bigbank_func(); /* W1 через трамплин */
|
||||||
|
w3_show(); /* W3 напрямую */
|
||||||
|
printf("BIG : w3_probe(0x42)=0x%02X (expect 0xBD), exit to DSS.\n",
|
||||||
|
(unsigned)w3_probe(0x42));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/* w3huge.c — проба --w3 в режиме huge (самый сложный случай).
|
||||||
|
* huge: код 0x4100 (W1/W2), окно W3 ДЕЛЯТ резидентный код (0xC000, прямой
|
||||||
|
* вызов) и свапаемые трамплин-банки. crt0_banked захватывает резидентную
|
||||||
|
* страницу и возвращает её дефолтом после загрузки банков; трамплин
|
||||||
|
* сохраняет/восстанавливает W3 на каждый __banked-вызов.
|
||||||
|
*
|
||||||
|
* Ключевая проверка: w3_show() (резидент) вызывается ДО и ПОСЛЕ
|
||||||
|
* hugebank_func() (банк). Второй вызов проходит только если трамплин
|
||||||
|
* вернул резидентную страницу в W3. */
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sprinter.h>
|
||||||
|
|
||||||
|
const uint8_t n_banks = 1; /* один свапаемый W3-банк */
|
||||||
|
|
||||||
|
void hugebank_func(void) __banked; /* W3-банк, трамплин */
|
||||||
|
extern uint8_t w3_probe(uint8_t x); /* W3-резидент, прямой вызов */
|
||||||
|
extern void w3_show(void); /* W3-резидент, прямой вызов */
|
||||||
|
extern void w3_resident_calls_bank(void); /* W3-резидент, зовёт банк */
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
printf("HUGE: main @ 0x%04X | W1=%02X W2=%02X W3(resident)=%02X\n",
|
||||||
|
(unsigned)&main, _io_page_w1, _io_page_w2, _io_page_w3);
|
||||||
|
w3_show(); /* резидент напрямую (до банка) */
|
||||||
|
hugebank_func(); /* HOME → W3-банк (свап+возврат) */
|
||||||
|
w3_resident_calls_bank(); /* резидент → W3-банк (трамплин W1) */
|
||||||
|
w3_show(); /* резидент СНОВА — возврат стр. */
|
||||||
|
printf("HUGE: w3_probe(0x42)=0x%02X (expect 0xBD), exit to DSS.\n",
|
||||||
|
(unsigned)w3_probe(0x42));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/* w3huge_res.c — второй резидентный W3-модуль для huge-пробы.
|
||||||
|
*
|
||||||
|
* Содержит РЕЗИДЕНТНУЮ функцию (W3CODE @0xC000, прямой вызов), которая
|
||||||
|
* сама зовёт W3 __banked-функцию (свапаемый банк, трамплин). Этот случай
|
||||||
|
* ДОЛЖЕН работать: трамплин ___sdcc_bcall_ehl живёт в _CODE (W1, всегда
|
||||||
|
* замаплен) — он сохраняет резидентную страницу W3, маппит банк, а по
|
||||||
|
* возврату восстанавливает резидента. Адрес возврата (0xC0xx) снова
|
||||||
|
* валиден, и управление корректно возвращается в резидентную функцию.
|
||||||
|
*
|
||||||
|
* (Контраст с запретом «из __banked-контекста нельзя дёрнуть резидент»:
|
||||||
|
* здесь наоборот — резидент зовёт банк, и это ок.) */
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sprinter.h>
|
||||||
|
|
||||||
|
void hugebank_func(void) __banked; /* W3-банк, трамплин */
|
||||||
|
|
||||||
|
void w3_resident_calls_bank(void)
|
||||||
|
{
|
||||||
|
printf("W3-RES: w3_resident_calls_bank @ 0x%04X -> calling W3 __banked...\n",
|
||||||
|
(unsigned)&w3_resident_calls_bank);
|
||||||
|
hugebank_func(); /* резидент → банк через трамплин W1 */
|
||||||
|
printf("W3-RES: returned into resident OK (W3 phys now=0x%02X)\n",
|
||||||
|
_io_page_w3);
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/* w3probe.c — явная проба подхода A для --w3.
|
||||||
|
*
|
||||||
|
* main (W1/W2) зовёт функцию w3_show() в W3, которая печатает свой
|
||||||
|
* собственный адрес (должен быть 0xC0xx) через printf. Затем main
|
||||||
|
* проверяет прямой вызов с возвратом и ЧИСТО выходит в DSS (return 0),
|
||||||
|
* чтобы вернулось приглашение шелла. */
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
extern const uint8_t w3_sig; /* rodata в W3 */
|
||||||
|
extern uint8_t w3_probe(uint8_t x); /* код в W3, прямой вызов */
|
||||||
|
extern void w3_show(void); /* код в W3, зовёт printf */
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
printf("HOME: main @ 0x%04X | w3_probe @ 0x%04X | w3_show @ 0x%04X\n",
|
||||||
|
(unsigned)&main, (unsigned)&w3_probe, (unsigned)&w3_show);
|
||||||
|
|
||||||
|
w3_show(); /* W3-код печатает свой адрес */
|
||||||
|
|
||||||
|
printf("HOME: w3_probe(0x42) = 0x%02X (expect 0xBD)\n",
|
||||||
|
(unsigned)w3_probe(0x42));
|
||||||
|
|
||||||
|
printf("HOME: done, exiting to DSS.\n");
|
||||||
|
return 0; /* чистый выход → приглашение DSS */
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/* w3res.c — резидентный модуль окна W3 (проба подхода A, явная версия).
|
||||||
|
*
|
||||||
|
* Компилируется с --codeseg W3CODE --constseg W3CODE (БЕЗ --dataseg),
|
||||||
|
* линкуется по -Wl-b_W3CODE=0xC000. Здесь только код + rodata. */
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* rodata в W3 (для проверки, что область реально по 0xC000). */
|
||||||
|
const uint8_t w3_sig = 0x5A;
|
||||||
|
|
||||||
|
/* Функция, живущая в W3. Печатает СВОЙ адрес через printf — а printf
|
||||||
|
* лежит в libc (_CODE = W1/W2). Тем самым проверяем оба направления:
|
||||||
|
* W3-код исполняется по 0xC0xx И умеет звать код в W1/W2 обычным call'ом. */
|
||||||
|
void w3_show(void)
|
||||||
|
{
|
||||||
|
printf("W3 : w3_show @ 0x%04X | w3_sig @ 0x%04X = 0x%02X\n",
|
||||||
|
(unsigned)&w3_show, (unsigned)&w3_sig, (unsigned)w3_sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Прямой вызов с возвратом значения: x^0xFF. */
|
||||||
|
uint8_t w3_probe(uint8_t x)
|
||||||
|
{
|
||||||
|
return (uint8_t)(x ^ 0xFF);
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/* w3tiny.c — проба --w3 в режиме tiny.
|
||||||
|
* tiny: код с 0x8100 (W2), W1 не используется, W3 свободно под резидент.
|
||||||
|
* Образ тянется 0x8100..0xC0xx (2 страницы) → DSS маппит W2 и W3. */
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
extern uint8_t w3_probe(uint8_t x);
|
||||||
|
extern void w3_show(void);
|
||||||
|
|
||||||
|
uint8_t w1_p, w2_p, w3_p; /* физ-страницы окон (заполняются asm) */
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
__asm
|
||||||
|
in a,(#0xA2)
|
||||||
|
ld (_w1_p), a
|
||||||
|
in a,(#0xC2)
|
||||||
|
ld (_w2_p), a
|
||||||
|
in a,(#0xE2)
|
||||||
|
ld (_w3_p), a
|
||||||
|
__endasm;
|
||||||
|
|
||||||
|
printf("TINY: main @ 0x%04X | pages W1=%02X W2=%02X W3=%02X\n",
|
||||||
|
(unsigned)&main, w1_p, w2_p, w3_p);
|
||||||
|
w3_show();
|
||||||
|
printf("TINY: w3_probe(0x42)=0x%02X (expect 0xBD), exit to DSS.\n",
|
||||||
|
(unsigned)w3_probe(0x42));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+119
-50
@@ -1,65 +1,140 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Parse an SDCC .map file produced by sdldz80 and verify that every named
|
Отчёт по раскладке памяти образа + проверка лимитов, по .map-файлу
|
||||||
bank fits inside its 16 KB window.
|
sdldz80. Печатается для ЛЮБОЙ модели памяти (не только при --bank):
|
||||||
|
|
||||||
The Sprinter toolchain expects each `_BANKn` area (with n >= 1) to occupy
|
- _CODE — основной код (W1 0x4100 или W2 0x8100), размер, конец;
|
||||||
at most 16384 bytes — that is the size of CPU window 3 where banked code
|
- данные — _DATA/_BSS/_INITIALIZED/… цепочкой за кодом, окно, конец;
|
||||||
runs at execution time. The SDCC linker itself does not enforce this
|
- статика/куча/стек — где кончается статика и сколько свободно под кучу
|
||||||
limit, so we catch it post-link.
|
(до ___sdcc_heap_end) и стек;
|
||||||
|
- _W3CODE — резидентный код окна W3 (--w3), если есть;
|
||||||
|
- _BANKn — банки (big/huge), каждый ≤ 16 КБ (размер окна W3/W1).
|
||||||
|
|
||||||
We also surface the HOME budget: anything left between the end of _CODE
|
Раскладка режимов — docs/memory-management.md §4. Линкер сам лимиты не
|
||||||
and the start of window 2 (0x8000) is leftover space for adding code/data
|
проверяет — ловим здесь пост-линк.
|
||||||
without banking.
|
|
||||||
|
|
||||||
Usage: check_banks.py <path-to.map>
|
Usage: check_banks.py <path.map> [--mode MODE] [--heap-top 0xNNNN]
|
||||||
|
[--stack 0xNNNN]
|
||||||
|
|
||||||
Exits non-zero with a clear message if any bank is over its limit.
|
Выходит с ненулевым кодом, если банк не влез в окно или статика заехала
|
||||||
|
за потолок кучи/окна.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
BANK_LIMIT = 16 * 1024
|
BANK_LIMIT = 16 * 1024 # окно W3/W1 под банк
|
||||||
# HOME upper bound depends on layout:
|
W_CEILING = 0xC000 # верх W2 (дальше — только W3/банки)
|
||||||
# HUGE: CODE at 0x4100 (W1), HOME may spill into W2 → ceiling 0xC000
|
|
||||||
# BIG: CODE at 0x8100 (W2), HOME stays in W2 → ceiling 0xC000
|
# Области .map, составляющие HOME (код + данные), в порядке цепочки линкера.
|
||||||
# tiny/small: same ceiling — anything in W3 is bank territory.
|
# Всё, что не банк и не резидент W3 и лежит < 0xC000.
|
||||||
# We pick the ceiling per-image based on where _CODE lives, so we don't
|
_BANK_RE = re.compile(r"_BANK\d+$")
|
||||||
# falsely flag W2-resident code as "spilled into stack/heap".
|
|
||||||
HOME_CEILING = 0xC000
|
|
||||||
|
def win_label(addr):
|
||||||
|
"""Окно по адресу (0-based физического образа игнорируем — берём Z80-view)."""
|
||||||
|
a = addr & 0xFFFF
|
||||||
|
if 0x4000 <= a < 0x8000:
|
||||||
|
return "W1"
|
||||||
|
if 0x8000 <= a < 0xC000:
|
||||||
|
return "W2"
|
||||||
|
if 0xC000 <= a <= 0xFFFF:
|
||||||
|
return "W3"
|
||||||
|
return "??"
|
||||||
|
|
||||||
|
|
||||||
def parse_map(path):
|
def parse_map(path):
|
||||||
"""
|
"""{area_name: (addr, size)} для строк вида `_CODE 00004100 00000313 = ...`."""
|
||||||
Returns a dict {area_name: (addr, size)} for area lines like:
|
|
||||||
_CODE 00004100 00000313 = ...
|
|
||||||
"""
|
|
||||||
line_re = re.compile(r"^\s*(_\w+)\s+([0-9A-Fa-f]{8})\s+([0-9A-Fa-f]{8})\s*=")
|
line_re = re.compile(r"^\s*(_\w+)\s+([0-9A-Fa-f]{8})\s+([0-9A-Fa-f]{8})\s*=")
|
||||||
areas = {}
|
areas = {}
|
||||||
with open(path) as f:
|
with open(path) as f:
|
||||||
for ln in f:
|
for ln in f:
|
||||||
m = line_re.match(ln)
|
m = line_re.match(ln)
|
||||||
if not m:
|
if m:
|
||||||
continue
|
areas[m.group(1)] = (int(m.group(2), 16), int(m.group(3), 16))
|
||||||
name = m.group(1)
|
|
||||||
addr = int(m.group(2), 16)
|
|
||||||
size = int(m.group(3), 16)
|
|
||||||
areas[name] = (addr, size)
|
|
||||||
return areas
|
return areas
|
||||||
|
|
||||||
|
|
||||||
def main():
|
MODE_DESC = {
|
||||||
if len(sys.argv) != 2:
|
"tiny": "CODE+DATA в W2 (0x8100)",
|
||||||
sys.exit("usage: check_banks.py <path-to.map>")
|
"small": "CODE в W1 (0x4100), DATA→W2",
|
||||||
|
"big": "CODE+DATA в W2 (0x8100), банки в W1",
|
||||||
|
"huge": "CODE в W1 (0x4100), DATA→W2, банки в W3",
|
||||||
|
"manual": "ручная раскладка",
|
||||||
|
}
|
||||||
|
|
||||||
areas = parse_map(sys.argv[1])
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("mapfile")
|
||||||
|
ap.add_argument("--mode", default="")
|
||||||
|
ap.add_argument("--heap-top", default="0xBB00")
|
||||||
|
ap.add_argument("--stack", default="0xBFFE")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
areas = parse_map(args.mapfile)
|
||||||
|
heap_top = int(args.heap_top, 0)
|
||||||
|
stack_top = int(args.stack, 0)
|
||||||
|
|
||||||
fails = []
|
fails = []
|
||||||
bank_names = sorted(
|
|
||||||
n for n in areas if re.fullmatch(r"_BANK\d+", n)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
desc = MODE_DESC.get(args.mode, "")
|
||||||
|
hdr = f"memory: {args.mode}" if args.mode else "memory:"
|
||||||
|
if desc:
|
||||||
|
hdr += f" — {desc}"
|
||||||
|
print(hdr)
|
||||||
|
|
||||||
|
# --- HOME: код + данные (всё < 0xC000, кроме банков) --------------
|
||||||
|
home = {n: v for n, v in areas.items()
|
||||||
|
if not _BANK_RE.match(n) and n != "_W3CODE" and (v[0] & 0xFFFF) < W_CEILING}
|
||||||
|
|
||||||
|
if "_CODE" in areas:
|
||||||
|
caddr, csize = areas["_CODE"]
|
||||||
|
cend = caddr + csize
|
||||||
|
print(f" _CODE @ 0x{caddr & 0xFFFF:04X} size {csize:>6} "
|
||||||
|
f"({win_label(caddr)}) → 0x{cend & 0xFFFF:04X}")
|
||||||
|
|
||||||
|
# данные = все HOME-области, кроме _CODE (цепляются за кодом)
|
||||||
|
data = {n: v for n, v in home.items() if n != "_CODE"}
|
||||||
|
if data:
|
||||||
|
dstart = min(v[0] for v in data.values())
|
||||||
|
dend = max(v[0] + v[1] for v in data.values())
|
||||||
|
print(f" данные @ 0x{dstart & 0xFFFF:04X} size {dend - dstart:>6} "
|
||||||
|
f"({win_label(dstart)}) → 0x{dend & 0xFFFF:04X}"
|
||||||
|
f" [_DATA/_BSS/_INITIALIZED/…]")
|
||||||
|
else:
|
||||||
|
dend = cend
|
||||||
|
|
||||||
|
# статика/куча/стек
|
||||||
|
static_end = max(v[0] + v[1] for v in home.values())
|
||||||
|
heap_free = heap_top - static_end
|
||||||
|
stack_room = stack_top + 1 - heap_top
|
||||||
|
print(f" статика до 0x{static_end & 0xFFFF:04X} — куча "
|
||||||
|
f"0x{static_end & 0xFFFF:04X}..0x{heap_top & 0xFFFF:04X} ({heap_free} Б), "
|
||||||
|
f"стек 0x{heap_top & 0xFFFF:04X}..0x{stack_top & 0xFFFF:04X} ({stack_room} Б)")
|
||||||
|
if static_end > heap_top:
|
||||||
|
print(f" ОШИБКА: статика заехала за heap_top 0x{heap_top & 0xFFFF:04X} "
|
||||||
|
f"(нет места под кучу/стек)")
|
||||||
|
fails.append(("_HOME", static_end))
|
||||||
|
elif static_end > W_CEILING:
|
||||||
|
print(f" ОШИБКА: статика вышла за 0x{W_CEILING:04X}")
|
||||||
|
fails.append(("_HOME", static_end))
|
||||||
|
|
||||||
|
# --- Резидентный код окна W3 (--w3) -------------------------------
|
||||||
|
if "_W3CODE" in areas:
|
||||||
|
waddr, wsize = areas["_W3CODE"]
|
||||||
|
wend = waddr + wsize
|
||||||
|
wfree = 0x10000 - wend
|
||||||
|
marker = "OK"
|
||||||
|
if wend > 0x10000:
|
||||||
|
marker = "OVERFLOW"
|
||||||
|
fails.append(("_W3CODE", wsize))
|
||||||
|
print(f" _W3CODE @ 0x{waddr & 0xFFFF:04X} size {wsize:>6} "
|
||||||
|
f"(резидент W3) → 0x{wend & 0xFFFF:04X}, {wfree} Б свободно до 0x10000 {marker}")
|
||||||
|
|
||||||
|
# --- Банки (big/huge) ---------------------------------------------
|
||||||
|
bank_names = sorted(n for n in areas if _BANK_RE.match(n))
|
||||||
for name in bank_names:
|
for name in bank_names:
|
||||||
addr, size = areas[name]
|
addr, size = areas[name]
|
||||||
pct = size * 100.0 / BANK_LIMIT
|
pct = size * 100.0 / BANK_LIMIT
|
||||||
@@ -67,24 +142,18 @@ def main():
|
|||||||
if size > BANK_LIMIT:
|
if size > BANK_LIMIT:
|
||||||
marker = "OVERFLOW"
|
marker = "OVERFLOW"
|
||||||
fails.append((name, size))
|
fails.append((name, size))
|
||||||
print(f" {name:<12} @ 0x{addr:08X} size {size:>5} / {BANK_LIMIT} ({pct:5.1f}%) {marker}")
|
# виртуальный адрес: старший байт = номер банка, младшие 16 — окно
|
||||||
|
free = BANK_LIMIT - size
|
||||||
# HOME budget — _CODE can land in W1 (huge/small) or W2 (big/tiny).
|
print(f" {name:<12} @ 0x{addr:08X} size {size:>6} / {BANK_LIMIT} "
|
||||||
if "_CODE" in areas:
|
f"({pct:4.1f}%) → {free} Б свободно {marker}")
|
||||||
addr, size = areas["_CODE"]
|
|
||||||
end = addr + size
|
|
||||||
budget_remaining = HOME_CEILING - end
|
|
||||||
which_window = "W2 (0x8000-0xBFFF)" if addr >= 0x8000 else "HOME (0x4100-0xBFFF)"
|
|
||||||
print(f" _CODE @ 0x{addr:08X} size {size:>5} → ends at 0x{end:04X}, "
|
|
||||||
f"{which_window} has {budget_remaining} bytes free before 0x{HOME_CEILING:04X}")
|
|
||||||
if budget_remaining < 0:
|
|
||||||
print(f" ERROR: _CODE extends past 0x{HOME_CEILING:04X} (stack/heap territory)")
|
|
||||||
fails.append(("_CODE", size))
|
|
||||||
|
|
||||||
if fails:
|
if fails:
|
||||||
print()
|
print()
|
||||||
for name, size in fails:
|
for name, size in fails:
|
||||||
print(f" {name} too big: {size} bytes (limit {BANK_LIMIT})")
|
if name in ("_HOME", "_W3CODE"):
|
||||||
|
print(f" {name}: раскладка не влезла (конец 0x{size & 0xFFFF:04X})")
|
||||||
|
else:
|
||||||
|
print(f" {name} слишком большой: {size} Б (лимит {BANK_LIMIT})")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+13
-5
@@ -277,11 +277,14 @@ int main(int argc, char **argv) {
|
|||||||
uint32_t pad_byte = 0xFF;
|
uint32_t pad_byte = 0xFF;
|
||||||
uint32_t bank_base = DEFAULT_BANK_BASE;
|
uint32_t bank_base = DEFAULT_BANK_BASE;
|
||||||
int verbose = 0;
|
int verbose = 0;
|
||||||
|
int allow_w3_home = 0; /* -W: HOME может тянуться в W3 при W3-банках
|
||||||
|
(резидентный код --w3 в HUGE делит окно) */
|
||||||
|
|
||||||
for (int i = 1; i < argc; i++) {
|
for (int i = 1; i < argc; i++) {
|
||||||
const char *a = argv[i];
|
const char *a = argv[i];
|
||||||
if (!strcmp(a, "-h") || !strcmp(a, "--help")) { usage(stdout); return 0; }
|
if (!strcmp(a, "-h") || !strcmp(a, "--help")) { usage(stdout); return 0; }
|
||||||
if (!strcmp(a, "-v")) { verbose = 1; continue; }
|
if (!strcmp(a, "-v")) { verbose = 1; continue; }
|
||||||
|
if (!strcmp(a, "-W")) { allow_w3_home = 1; continue; }
|
||||||
if (!strcmp(a, "-o") && i + 1 < argc) { out_path = argv[++i]; continue; }
|
if (!strcmp(a, "-o") && i + 1 < argc) { out_path = argv[++i]; continue; }
|
||||||
if (!strcmp(a, "-L") && i + 1 < argc) {
|
if (!strcmp(a, "-L") && i + 1 < argc) {
|
||||||
if (parse_addr(argv[++i], &load_addr) < 0) { fprintf(stderr, "mkexe: bad -L\n"); return 1; }
|
if (parse_addr(argv[++i], &load_addr) < 0) { fprintf(stderr, "mkexe: bad -L\n"); return 1; }
|
||||||
@@ -386,12 +389,17 @@ int main(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* HOME must not extend past 0xBFFF (W3 = bank territory in HUGE,
|
/* HOME может тянуться в W3 (0xC000..0xFFFF) — это резидентный код
|
||||||
free in BIG but still off-limits to HOME). */
|
окна W3 (--w3, подход A): DSS грузит непрерывный образ и маппит
|
||||||
if (home->hi >= 0xC000u) {
|
W1/W2/W3. Запрещаем ТОЛЬКО когда W3 реально занят трамплин-банками
|
||||||
|
(HUGE: bank_base=0xC000, max_bank>0) — тогда HOME и банки
|
||||||
|
столкнулись бы в одном окне. */
|
||||||
|
if (home->hi >= 0xC000u && max_bank > 0 && bank_base == DEFAULT_BANK_BASE
|
||||||
|
&& !allow_w3_home) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"mkexe: HOME image extends to 0x%04X, past window 2 end (0xBFFF).\n"
|
"mkexe: HOME image extends to 0x%04X, into W3 (0xC000+) which\n"
|
||||||
" Code grew too big for HOME — move some .c files into a bank.\n",
|
" holds trampoline banks in HUGE mode — collision.\n"
|
||||||
|
" Move some .c files into a bank, or pass -W for --w3 residents.\n",
|
||||||
home->hi);
|
home->hi);
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user