13 Commits

Author SHA1 Message Date
snark13 52636a5d6e libbgi: выделить графику BGI в отдельную библиотеку + тест спрайтов
Графика вынесена из libc/ в новую библиотеку libbgi/:
  - common/  — mode-agnostic математика и состояние (один исходник,
    .rel попадает в оба driver-архива);
  - bgi256/ + bgi16/ — mode-specific leaf'ы (raw-плот/чтение/спаны);
  - include/ — graphics.h + gfx.h; _bgi.h — внутренний заголовок.
Собираются lib/bgi256.lib (и bgi16.lib в Фазе 2); выбор режима
линковкой через sprinter-cc --gfx 256|16.  libc/ теперь без графики.

tests/bgi_img — тест спрайтов getimage/putimage/imagesize (5 операций
COPY/XOR/OR/AND/NOT + XOR-round-trip + self-check imagesize).
Проверен автотестом в MAME.

Примечание: make size-check пока красный (gfx_dbuf/gfx_demo выросли
после реорга) — закрыть по завершении миграции libbgi.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 14:18:33 +03:00
snark13 3bf50f7ff7 docs: единый справочник по автотестам в MAME; убрать метод AUTORUN.BAT
- docs/mame-autotest.md — исчерпывающий документ: запуск, ввод команд,
  скриншоты, завершение сессий, анализ, раскладка клавиатуры, все квирки.
  Одного этого документа достаточно, чтобы работать с MAME в режиме
  автотестирования.
- mame_interactive.py теперь единственный инструмент: авто-запускает exe
  вводом пути (a:\<exe>+Enter), --step опционален (доп. ввод в программу),
  умные дефолты снимков/таймаута.
- удалён mame_auto_test.py (старый метод через AUTORUN.BAT chainload) и
  все упоминания AUTORUN.BAT в доках; интерактивный ввод его заменил.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 16:41:45 +03:00
snark13 657c6d2955 libc: BGI Фаза 2d-3 — settextstyle (масштаб и направление текста)
settextstyle/gettextsettings/textwidth/textheight: DEFAULT_FONT 8×8,
целочисленный масштаб 1..10, HORIZ/VERT (поворот 90° CCW), прозрачный
фон. Свой scaled-рендер (_bgi_draw_scaled) читает глиф через leaf
_bgi_font_rows (interleaved системный шрифт) и рисует блоки size×size
raw в одной W3-скобке. outtext/outtextxy переведены на него. Проверено
в MAME (tests/bgitest): размеры 1..4 + вертикальный текст.

Доки обновлены (Ф2d-1/2/3 готовы; осталось viewport/клиппинг).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 16:18:48 +03:00
snark13 b26560c409 libc: BGI Фаза 2d-2 — setlinestyle (стили и толщина линий)
setlinestyle/getlinesettings: SOLID/DOTTED/CENTER/DASHED/USERBIT +
NORM/THICK. _bgi_styled_line — Брезенхэм с 16-битной маской (пропуск
пикселя по биту) и дублированием ±1 перпендикулярно оси для THICK;
SOLID+NORM идёт быстрым путём (accel _bgi_lineseg). line/lineto/linerel/
rectangle/drawpoly переведены на него. Проверено в MAME (tests/bgitest).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 16:14:03 +03:00
snark13 25cb7ba554 libc: BGI Фаза 2d-1 — getimage/putimage/imagesize (спрайты)
Растровые образы: imagesize (4 байта заголовка w,h + w*h пикселей),
getimage (захват прямоугольника), putimage с COPY/XOR/OR/AND/NOT_PUT.
Блит идёт raw в одной W3-скобке — добавлен _gfx_getpixel256_raw в gfx +
leaf _bgi_read_raw в drv256. Проверено в MAME (tests/bgitest): захват
спрайта, 3 COPY-копии, XOR/OR/COPY поверх фона.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 16:09:51 +03:00
snark13 dc37a14010 libc: BGI Фаза 2c — floodfill/pieslice/sector
floodfill — скан-строчная заливка области до границы (self-bracket
чтение: корректно, но медленно; raw-оптимизация в TODO).
pieslice/sector — залитые сектора круга/эллипса: границу (центр→дуга→
центр) прогоняем через _bgi_poly_edge и заливаем min/max по строкам,
как fillpoly (для >180° возможен перелив — упрощение). Проверено в
MAME (tests/bgitest): floodfill круга, круговая диаграмма, штрих-сектор.

Доки/справочник/память обновлены (Ф2a-c готовы; Ф2d = images/viewport/
text-style/line-style — осталось).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 16:04:04 +03:00
snark13 ecb419efda libc: BGI Фаза 2b — заливки (setfillstyle/bar/bar3d/fillpoly/fillellipse)
setfillstyle/getfillsettings + 10 стандартных 8×8 паттернов Borland.
bar теперь честно учитывает стиль заливки; bar3d (3D-брусок), fillpoly
(scanline min/max по строкам через брезенхэмовский проход рёбер),
fillellipse (полуширина строки через целочисленный _bgi_isqrt — без
32-бит). Общий _bgi_fill_span (SOLID/EMPTY/паттерн) с клипом, поверх
raw-hline в одной W3-скобке. Проверено в MAME (tests/bgitest): solid/
hatch бары, bar3d со slash, синий fillellipse, xhatch-треугольник.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 15:58:06 +03:00
snark13 5a48f7fafb libc: BGI Фаза 2a — arc/ellipse/drawpoly
Дуги/эллипсы через целочисленную тригонометрию Q7 (_bgi_trig.c, ×128) +
общий рисователь полилинией (_bgi_arc_draw.c). arc(x,y,st,end,r),
ellipse(x,y,st,end,xr,yr), drawpoly(n,pts). Проверено в MAME (tests/
bgitest): окружность/эллипс/дуга/полигон рисуются корректно.

ВАЖНО: тригонометрию считаем в int (Q7), НЕ через (long)…>>8 — первая
версия на 32-бит арифметике рисовала эллипс прямоугольником (SDCC/z80
криво собирает 32-бит; см. memory/avoid_32bit_arith_z80). Q7 даёт
радиус×значение ≤ 255×128 < 32767 — всё влезает в 16 бит.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 15:50:00 +03:00
snark13 e761d21505 libc: graphics.h — Turbo-C BGI-слой, Фаза 1 (режим 256)
Функционально-совместимый с Turbo-C <graphics.h> поверх gfx.h.
Архитектура: mode-agnostic математика (libc/bgi/*.c → sprinter.lib) +
driver-leaf'ы per-режим (libc/bgi/drv256/*.c → sprinter_gfx256.lib).
Режим выбирается линковкой: sprinter-cc --gfx 256 (16 — позже, тем же
leaf-split'ом). Один код работает в любом режиме без правок.

API: initgraph/closegraph/graphresult/cleardevice, set/get color+bkcolor,
getmaxx/y/color, put/getpixel, moveto/moverel/getx/gety, line/lineto/
linerel, rectangle, bar, circle, outtext/outtextxy. initgraph грузит
EGA-палитру 0..15. Пакетные примитивы (circle) — одна W3-скобка на
примитив (иначе на порядок медленнее). Проверено в MAME (tests/bgitest).

Попутно: gfx_getpixel256 в libc/gfx. size-check без регресса, baseline
обновлён. Детали: memory/bgi_two_lib_design, docs/TODO.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 15:40:49 +03:00
snark13 72466f7cad toolchain: скриптовый интерактивный ввод в DSS через MAME
mame_interactive.py печатает произвольный текст в командную строку DSS,
дёргая поля AT/PS-2-клавиатуры :kbd:ms_naturl через Lua set_value
(at_keyboard сам генерит scancode'ы → SIO Z84C015 → DSS). Раньше
инъекция шла в ZX-матрицу :IO_LINE*, которую DSS не читает — отсюда
«нет эффекта». Полная раскладка char→(port,mask,shift) с авто-Shift.

Квирки: attotime.seconds целое (субсекунды через attoseconds/1e18),
клавишу держать коротко (~0.06с, иначе автоповтор), дискета без
AUTORUN.BAT → приглашение C:\>. Проверено end-to-end: dir<Enter> и
запуск теста набором a:\rt_test.exe<Enter> (Shift для ':' и '\').

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 12:18:37 +03:00
snark13 d652f89240 toolchain: автотест .exe в MAME без участия человека
AUTORUN.BAT chainload из system.bat (правится пользователем один раз)
+ mame_auto_test.py: кладёт .exe и сгенерированный AUTORUN.BAT на
дискету, гоняет MAME с Lua-таймингом (register_periodic +
manager.machine.time) для скриншотов и выхода по таймауту.

Natural keyboard (Lua natkeyboard:post/post_coded, -autoboot_command)
и прямая инъекция через ioport.fields[...]:set_value() не работают на
этом драйвере — перепробовано разными способами; AUTORUN.BAT chainload
оказался единственным надёжным путём запуска без участия человека.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-08 11:56:54 +03:00
snark13 46bd9ad0f1 gfx/time: vsync через polling бита кадра, sleep/delayms без halt-подсчёта
gfx_wait_vsync() и sleep() раньше предполагали, что КАЖДОЕ прерывание
на векторе 0xFF — кадровый тик; с CBL/клавиатурой на том же векторе
это уже не так.

gfx_wait_vsync(): вместо halt — polling бита 5 порта 0xFE (реальная
позиция луча, см. MAME kbd_fe_r), доступного пока включён CBL bit7
порта 0x004E. Разделяемое владение портом с CBL через
_cbl_port_ref/unref (тот же ref-counting паттерн, что у IM2-таблицы) —
cbl_close() возвращает "немой" режим вместо полного выключения, если
gfx ещё держит ссылку. Fallback на halt при таймауте.

sleep()/delayms(): калиброванный busy-wait по духу delayms.asm вместо
подсчёта halt-пробуждений. Калибровка одна на кадр (не на секунду —
не переполняет uint16_t и не требует умножения/32-бит арифметики),
общий движок libc/time/_sleep_calib.c для обеих функций. Fallback на
старое поведение при EBUSY (фрейм-хук занят другим irq_install()).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-08 10:47:57 +03:00
snark13 5086c47f0f libc: IM2 Phase 2b — звук CBL/COVOX через callback fill(), без кольца libc
CBL уже имеет аппаратный буфер 256 Б (2×128, двойная буферизация на
стороне железа) — держать поверх него ещё одно кольцо в libc было бы
лишней копией. cbl_open(freq, fmt, pump_mode, underrun_mode, fill)
регистрирует callback, вызываемый из ISR за очередным блоком; он сам
пропихивает данные приложения (откуда угодно) через cbl_push_otir()/
cbl_push_accel() — без промежуточного буфера.

- два насоса: OTIR (порт 0x4F) и ACCEL (акселератор, спец-страница
  EMM 0xFD@0xC000); OTIR+16-бит запрещён (EINVAL) — по исходнику MAME
  порт данных физически не может собрать 16-бит сэмпл из пары байт;
- форматы CBL_FMT_MONO8/16/STEREO8/16, частоты 7.8..109к;
- CBL_UNDERRUN_APP (по умолчанию, недолив не наша забота) /
  CBL_UNDERRUN_SILENCE (буфер тишины malloc'ится только в этом режиме);
- tests/cbltest: матрица 64 комбинации (2 насоса × 8 форматов × 4
  частоты); tests/cblwav: banked-стрим речи с дискеты (физстраницы
  кэшированы заранее — mem_get_page нельзя звать из fill()/ISR);
  tests/cblstream: единственный случай с собственным кольцом уровня
  приложения (диск нельзя читать из fill()).

Verified в MAME 2026-07-07 — все три теста работают.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 21:21:54 +03:00
182 changed files with 4587 additions and 1135 deletions
+2 -2
View File
@@ -3,7 +3,7 @@
# ===========================================================================
# `build/` directories anywhere in the tree
# (top-level build/, lib/build/, toolchain/*/build/, ...)
# (top-level build/, libc/build/, libbgi/build/, toolchain/*/build/, ...)
build/
# sprinter-cc per-example intermediate directory
@@ -40,7 +40,7 @@ tests/*/*.cdb
tests/*/*.mem
tests/*/*.rst
# libc archive (built from libc/, see lib/Makefile)
# libc + libbgi archives (built by libc/Makefile + libbgi/Makefile)
lib/*.lib
# Host-built mkexe binary + test outputs (input fixtures *.bin/*.ihx kept)
+15 -8
View File
@@ -6,20 +6,26 @@ Target-слой SDCC 4.5 (z80) для компьютера Sprinter Sp2000: crt0
## Сборка и проверка
```
make # tools + lib + все тесты (43) + examples
make -C lib # только libc → lib/sprinter.lib
make # tools + lib + libbgi + все тесты (45) + examples
make -C libc # только libc → lib/sprinter.lib
make -C libbgi # только BGI → lib/bgi256.lib (и bgi16.lib в Фазе 2)
make floppy # упаковать все .exe в mame/v306/IMG/mc.img
make size-check # размерный регресс: _CODE vs docs/size_baseline.tsv
make size-baseline # принять текущие размеры эталоном
```
Графика (BGI) — отдельная библиотека libbgi/ (см. ниже). Программа,
использующая graphics.h, собирается с `--gfx 256` (или `--gfx 16` в
Фазе 2): sprinter-cc подлинкует lib/bgi256.lib и добавит -I libbgi/include.
Одиночный тест: `cd tests/<имя> && make run` (пакует ТОЛЬКО этот exe
+ EXTRA_DATA на дискету и запускает MAME). Тесты в MAME гоняет
пользователь — готовь дискету и проси прогнать.
После правок libc: пересборка от чистого листа (`make -C lib clean`)
не обязательна — stale .rel чистятся автоматически; `make size-check`
обязателен (рост _CODE без причины — регрессия).
После правок libc/libbgi: пересборка от чистого листа (`make -C libc clean`
/ `make -C libbgi clean`) не обязательна — stale .rel чистятся
автоматически; `make size-check` обязателен (рост _CODE без причины —
регрессия).
## Правила libc
@@ -29,7 +35,7 @@ make size-baseline # принять текущие размеры эталон
(`_`-префикс); общие статики — в отдельные data-модули
(`_xxx_state.c`); internal-заголовки (`_file.h`, `_gfx.h`, …) —
рядом с исходниками, НЕ в libc/include.
- Имя файла = имя функции. lib/Makefile собирает wildcard'ом —
- Имя файла = имя функции. libc/Makefile собирает wildcard'ом —
ничего регистрировать не надо.
- Комментарии — на русском; шапка модуля объясняет что/зачем + ABI.
- File-scope переменные НЕ инициализировать `= 0` (crt0 зануляет
@@ -54,12 +60,13 @@ make size-baseline # принять текущие размеры эталон
лимит 8 файловых манипуляторов, 9-й OPEN ВЕШАЕТ DSS (_fd_guard);
ENV $46: A=0 = NOT FOUND.
- Перед обвинением компилятора/железа — подтвердить артефактом
(сгенерированный .asm в lib/build/, дамп, репро) — см.
(сгенерированный .asm в libc/build/ или libbgi/build/, дамп, репро) — см.
memory/defer_unexplained_quirks.
## Структура
- `libc/<area>/*.c` — модули libc; `libc/include/` — публичные заголовки
- `libc/<area>/*.c` — модули libc (ядро, БЕЗ графики); `libc/include/` — публичные заголовки libc
- `libbgi/` — графика BGI (отдельная библиотека): `common/` — mode-agnostic (один исходник, .rel в обеих driver-библиотеках), `bgi256/` + `bgi16/` — mode-specific leaf'ы (реальные реализации, без обёрток); `include/` — graphics.h + gfx.h; `_bgi.h` — внутренний заголовок. Собирает `lib/bgi256.lib``bgi16.lib` в Фазе 2). Выбор режима линковкой: `--gfx 256` / `--gfx 16`.
- `runtime/` — crt0-семейство, heap, bank (bank.s собирается per-build)
- `bin/sprinter-cc` — обёртка компилятора; `toolchain/mkexe` — упаковщик
- `tests/` — по одному API/фиче; `examples/` — реальные приложения
+10 -6
View File
@@ -2,7 +2,7 @@
#
# make build host tools, libc archive, all tests, all apps
# make tools build only host tools (mkexe)
# make lib build lib/sprinter.lib (libc archive used by sprinter-cc)
# make lib build lib/sprinter.lib (libc) + lib/bgi256.lib (libbgi)
# make tests build all libc feature tests under tests/
# make examples build all real applications under examples/
# make floppy package every .exe + test fixtures into mame/v306/IMG/mc.img
@@ -16,10 +16,11 @@
TESTS := hello hello2 simple banked bankedbg banktest strtest cat seek \
malloc mem_test argv errno rt_test openenv ls conio conio2 \
attrprob timedir mouse banklocl stdlib assrtest ptime stattest \
filetest fdmax fbench solidt irqtest dec_test gets stest2 winrest \
filetest fdmax fbench solidt irqtest cbltest cblwav dec_test gets stest2 winrest \
bios_text text_palette \
gfx_demo gfx_d16 gfx_text gfx_mous gfx_dbuf
gfx_demo gfx_dbuf
# gfx_d16 / gfx_text / gfx_mous — 16-цветные; убраны до Фазы 2 (bgi16.lib
# ещё не собирается). Вернуть мигрированными на BGI --gfx 16.
# Larger end-user applications under examples/.
APPS := mdview mdview2
@@ -34,6 +35,7 @@ ALL_EXES := $(TEST_EXES) $(APP_EXES)
DATA_FILES := \
tests/cat/test.txt \
tests/seek/big.txt \
tests/cblwav/speech.pcm \
examples/mdview/SAMPLE.MD
.PHONY: all tools lib tests examples check clean sdcc floppy \
@@ -45,7 +47,8 @@ tools:
$(MAKE) -C toolchain/mkexe
lib:
$(MAKE) -C lib
$(MAKE) -C libc
$(MAKE) -C libbgi
check: tools
$(MAKE) -C toolchain/mkexe check
@@ -80,7 +83,8 @@ size-baseline:
clean:
$(MAKE) -C toolchain/mkexe clean
$(MAKE) -C lib clean
$(MAKE) -C libc clean
$(MAKE) -C libbgi clean
@for t in $(TESTS); do $(MAKE) -C tests/$$t clean; done
@for a in $(APPS); do $(MAKE) -C examples/$$a clean; done
+6 -1
View File
@@ -62,8 +62,13 @@ $(EXAMPLE).exe: $(SOURCES) $(MKEXE) $(LIB) $(RUNTIME_DEPS)
$(MKEXE):
$(MAKE) -C $(PROJ_ROOT)/toolchain/mkexe
# $(LIB) = lib/sprinter.lib (libc). Графика (bgi256.lib) собирает
# libbgi/Makefile; гоним и его — иначе standalone `make` в тесте с
# --gfx 256 не найдёт bgi256.lib при линковке. Оба инкрементальные,
# на повторном запуске ничего не пересобирают.
$(LIB):
$(MAKE) -C $(PROJ_ROOT)/lib
$(MAKE) -C $(PROJ_ROOT)/libc
$(MAKE) -C $(PROJ_ROOT)/libbgi
clean:
rm -rf .sprinter-cc-* $(EXAMPLE).exe
+17 -3
View File
@@ -64,6 +64,7 @@ CHECK_BANKS="$PROJ_ROOT/toolchain/check_banks.py"
RUNTIME="$PROJ_ROOT/runtime"
LIB_DIR="$PROJ_ROOT/lib"
INC_DIR="$PROJ_ROOT/libc/include"
BGI_INC_DIR="$PROJ_ROOT/libbgi/include" # gfx.h, graphics.h (BGI)
# ------- Defaults ------------------------------------------------------------
OUT=""
@@ -86,6 +87,7 @@ LD_EXTRA=()
MKEXE_EXTRA=()
BANK_SPECS=() # entries like "1=engine.c"
MAX_ALLOCS="" # if set, passed to sdcc as --max-allocs-per-node
GFX_MODE="" # "256" → link BGI graphics.h driver sprinter_gfx256.lib
# ------- Parse args ----------------------------------------------------------
usage() {
@@ -110,6 +112,7 @@ while [[ $# -gt 0 ]]; do
--bank) BANK_SPECS+=("$2"); shift 2;;
--mkexe) MKEXE_EXTRA+=("$2"); shift 2;;
--max-allocs) MAX_ALLOCS="$2"; shift 2;;
--gfx) GFX_MODE="$2"; shift 2;;
--debug) DEBUG_RT=1; shift;;
-v) VERBOSE=1; shift;;
-h|--help) usage 0;;
@@ -121,6 +124,17 @@ done
[[ -z "$OUT" ]] && { echo "sprinter-cc: -o NAME is required" >&2; exit 1; }
[[ ${#SOURCES[@]} -eq 0 ]] && { echo "sprinter-cc: no input files" >&2; exit 1; }
# ------- BGI graphics driver selection (--gfx) -------------------------------
# graphics.h — mode-agnostic слой; конкретный видеорежим задаёт driver-
# архив. Одновременно только один. drv16 пока не реализован.
GFX_LD=()
case "$GFX_MODE" in
"") ;; # graphics.h не используется
256) GFX_LD=("-lbgi256");; # libbgi/bgi256 → lib/bgi256.lib
16) echo "sprinter-cc: --gfx 16 ещё не реализован (пока только 256)" >&2; exit 1;;
*) echo "sprinter-cc: --gfx: ожидается 256 (или 16), дано: $GFX_MODE" >&2; exit 1;;
esac
# ------- Resolve memory mode → CODE_LOC / DATA_LOC ---------------------------
# tiny : CODE in W2 (0x8100), DATA auto after code (= W2)
# small : CODE in W1 (0x4100), DATA in W2 (0x8000) — crt0 must alloc W2
@@ -275,7 +289,7 @@ run "$SDASZ80" -o "$HEAP_TOP_REL" "$HEAP_TOP_SRC"
# 2. user sources → .rel (HOME)
USER_RELS=()
CC_FLAGS=(-mz80 --no-std-crt0 --std-c99 --opt-code-size -I "$INC_DIR" "${USER_INCS[@]}")
CC_FLAGS=(-mz80 --no-std-crt0 --std-c99 --opt-code-size -I "$INC_DIR" -I "$BGI_INC_DIR" "${USER_INCS[@]}")
[[ $DEBUG_RT -eq 1 ]] && CC_FLAGS+=(-DDEBUG_RT)
[[ -n "$MAX_ALLOCS" ]] && CC_FLAGS+=(--max-allocs-per-node "$MAX_ALLOCS")
for src in "${SOURCES[@]}"; do
@@ -342,12 +356,12 @@ for f in "${LD_EXTRA[@]}"; do LINK_FLAGS+=("$f"); done
if [[ $VERBOSE -eq 1 ]]; then
run "$SDCC" "${LINK_FLAGS[@]}" -o "$IHX" \
"$CRT0_REL" "$HEAP_TOP_REL" "${USER_RELS[@]}" "${BANK_RELS[@]}" \
"-L$LIB_DIR" "-lsprinter"
"-L$LIB_DIR" "${GFX_LD[@]}" "-lsprinter"
else
# Drop the warning line + its two follow-up "Library:" lines.
run "$SDCC" "${LINK_FLAGS[@]}" -o "$IHX" \
"$CRT0_REL" "$HEAP_TOP_REL" "${USER_RELS[@]}" "${BANK_RELS[@]}" \
"-L$LIB_DIR" "-lsprinter" 2>&1 \
"-L$LIB_DIR" "${GFX_LD[@]}" "-lsprinter" 2>&1 \
| awk '
/^\?ASlink-Warning-Definition of public symbol/ { skip = 3 }
skip > 0 { skip--; next }
+56 -8
View File
@@ -24,14 +24,42 @@ first-fit-decreasing, уважение `#pragma codeseg BANKn`, перелинк
## ОБЯЗАТЕЛЬНОЕ ДЛЯ V2
### Turbo-C-style graphics API (BGI-like)
### Turbo-C-style graphics API (BGI-like) — Фаза 1 ГОТОВА (256, 2026-07-08)
Расширить `gfx_*` до уровня Turbo-C `<graphics.h>`: initgraph/
closegraph/cleardevice-алиасы, current point (moveto/lineto/linerel),
setcolor/setbkcolor, getpixel, circle/arc/ellipse/pieslice, drawpoly/
fillpoly, floodfill, bar/bar3d, outtext[xy]/settextstyle/textwidth,
imagesize/getimage/putimage (COPY/XOR/AND/OR/NOT_PUT), setviewport/
клиппинг, setactivepage/setvisualpage (2 страницы есть), setlinestyle.
Архитектура: `graphics.h` — mode-agnostic слой (libc/bgi/*.c в
sprinter.lib), режим задаёт driver-архив; выбор линковкой через
`sprinter-cc --gfx 256` (16 — позже, тем же leaf-split'ом; см.
memory/bgi_two_lib_design).
**Фаза 1 (256, реализовано и проверено в MAME — tests/bgitest):**
initgraph/closegraph/graphresult/cleardevice, setcolor/getcolor/
setbkcolor/getbkcolor, getmaxx/getmaxy/getmaxcolor, putpixel/getpixel,
moveto/moverel/getx/gety, line/lineto/linerel, rectangle, bar, circle,
outtext/outtextxy. initgraph грузит EGA-палитру 0..15. Пакетные
примитивы (circle) — одна W3-скобка на весь примитив (raw-плот), иначе
на порядок медленнее.
**Фаза 2a/2b/2c ГОТОВЫ (2026-07-08, проверено в MAME tests/bgitest):**
- 2a: arc, ellipse, drawpoly (Q7-тригонометрия, БЕЗ 32-бит).
- 2b: setfillstyle/getfillsettings + 10 паттернов Borland, bar (с
паттерном), bar3d, fillpoly (scanline min/max), fillellipse (isqrt).
- 2c: floodfill (scanline span; медленный — self-bracket чтение, но
корректный), pieslice, sector.
**Фаза 2d-1/2/3 ГОТОВЫ (2026-07-08, MAME tests/bgitest):**
- 2d-1: getimage/putimage/imagesize (COPY/XOR/OR/AND/NOT_PUT), raw-блит
в одной W3-скобке (_gfx_getpixel256_raw).
- 2d-2: setlinestyle/getlinesettings (SOLID/DOTTED/CENTER/DASHED/USERBIT
+ NORM/THICK) — line/lineto/linerel/rectangle/drawpoly.
- 2d-3: settextstyle/gettextsettings/textwidth/textheight — масштаб
1..10, HORIZ/VERT, прозрачный фон (свой scaled-рендер поверх 8×8).
**Фаза 2d (осталось):** setviewport/clearviewport + клиппинг (инвазивно —
трогает все примитивы), settextjustify, setaspectratio (пиксели 320×256
неквадратные — круги визуально эллиптичны), setfillpattern (USER_FILL),
setactivepage/setvisualpage (2 страницы в gfx уже есть). Оптимизации:
floodfill на raw-чтении; ellipse/arc в одной W3-сессии. Потом drv16 +
sprinter_gfx16.lib (--gfx 16).
Acceptance: типичная BGI-программа переносится без существенных
правок. Референс: Turbo C 2.x BGIDEMO.
@@ -71,7 +99,11 @@ Quick wins:
- [ ] factoring parse_argv из crt0/crt0_banked в общий argv.s
- [ ] `restore SP on EXIT` (паттерн z88dk +pps) — проверить нужность
- [ ] CI: MAME с -aviwrite для screenshot-сравнения без человека
- [x] ~~CI: MAME с -aviwrite для screenshot-сравнения без человека~~
`toolchain/mame_interactive.py`: авто-запуск .exe вводом с
эмуляцией клавиатуры + Lua-таймер для скриншотов/выхода; сравнение
визуальное (Claude читает скриншот), не автоматический diff.
Полный справочник: docs/mame-autotest.md.
- [ ] linker duplicate-symbol warnings: сейчас фильтруются в
sprinter-cc (наши overrides _puts/___sdcc_heap/_asctime/…);
радикально — --nostdlib с ручным списком модулей z80.lib
@@ -84,6 +116,22 @@ Quick wins:
dual-write тест → если работает, кэшировать Port_Y в putpixel
(~8× меньше OUT для Брезенхэма); если нет — вычистить из доков.
- [ ] fdmax: лимит манипуляторов и зависание 9-го OPEN — MAME vs железо.
- [ ] CBL: щелчок перед первым проигрыванием звука за сессию (tests/
cbltest, tests/cblwav) — воспроизводится ТОЛЬКО на первом запуске
программы за сессию MAME, не зависит от содержимого потока
(тон/тишина/речь одинаково). Похоже на разовый прогрев звуковой
подсистемы MAME при первой активации канала — на реальном железе
скорее всего отсутствует. Проверить и закрыть либо описать как
реальный аппаратный эффект.
- [ ] **CBL: помехи в звуке при движении мыши** (tests/cblstream,
2026-07-07) — при потоковом воспроизведении движение мыши даёт
слышимые артефакты. Гипотеза (НЕ подтверждена): мышь может
делить SIO-канал/детект-бит с клавиатурой (порт 0x19 бит 0,
см. _irq_tramp.c), и трамплин классифицирует байты мыши как
«клавиатура» → сразу chain на DSS (0x0038), пропуская проверку
CBL-хука для ЭТОГО прерывания — при движении мыши часть тиков,
которые должны были обслужить CBL-насос, уходят мимо, кольцо
недоливается. Нужно исследование (MAME-дамп/лог трамплина).
## Known quirks (зафиксированы, обходы в libc)
+154 -6
View File
@@ -1,7 +1,13 @@
# IM2 Interrupt Handlers — Design Document
**Status: Phase 1 РЕАЛИЗОВАН 2026-07-06, ИСПРАВЛЕН после отладки**
(libc/irq: irq_install/irq_remove + трамплин; тест tests/irqtest).
**Status: Phase 1 (2026-07-06), Phase 2a CTC (2026-07-07), Phase 2b CBL
(2026-07-07, редизайн на callback без кольца — см. ниже) РЕАЛИЗОВАНЫ**
(libc/irq: irq_install/irq_remove + трамплин, irq_ctc_install/remove;
libc/cbl: cbl_open/close/push_otir/push_accel + fill-callback; тесты
tests/irqtest, tests/cbltest, tests/cblwav, tests/cblstream).
Callback-редизайн **verified в MAME 2026-07-07**: все три CBL-теста
(cbltest — матрица 64 комбинации; cblwav — banked-стрим, голос слышен;
cblstream — собственное кольцо приложения) работают.
## Результаты verification (2026-07-06, по docs/samples и исходникам MAME)
@@ -77,9 +83,151 @@ SIO_CTC_KEY.asm):
I/IM 1; atexit-уборка глушит CTC ОБЯЗАТЕЛЬНО (иначе после выхода
прерывания кГц-частоты душат шелл).
Phase 2b (CBL/COVOX handler — вернуть фильтр 0xFE.7 при активном CBL,
sample-буфер; фундамент Audio API), ISA-вектора, цепочки нескольких
хендлеров — не реализовано.
## Phase 2b CBL/COVOX audio (РЕАЛИЗОВАН 2026-07-07, редизайн без кольца)
`libc/include/cbl.h`: `cbl_open(freq_code, fmt, pump_mode,
underrun_mode, fill)` / `cbl_close()` / `cbl_push_otir(src,n)` /
`cbl_push_accel(src,n)` / `cbl_requests()` / `cbl_underruns()`. По
`docs/samples/Пример для CBL.asm`, разделу «Звук через COVOX-Blaster»
в `docs/converted/Forum.txt`, официальной документации "5.3
COVOX-Blaster" (see below) и `docs/converted/accel_r.txt`:
**Архитектурный редизайн (2026-07-07)**: у CBL УЖЕ ЕСТЬ собственный
аппаратный буфер 256 Б, разбитый на ДВЕ половины по 128 Б (double
buffering целиком на стороне железа — см. официальную доку: "Блок ОЗУ
256 байт условно разбит на две банки по 128 байт, и бит 7 порта #FE
указывает какая из банок ОЗУ выводится в ЦАП... используется программой
вывода для определения, нужно ли подгружать следующие 128 байт").
Держать ЕЩЁ ОДНО кольцо в libc поверх этого — лишний второй буфер и
лишняя копия. Библиотека теперь НЕ хранит кольца: при cbl_open()
регистрируется callback `fill(n)`, который ISR вызывает НАПРЯМУЮ, а
callback сам пропихивает n байт ИЗ ДАННЫХ ПРИЛОЖЕНИЯ (статический
массив, банковая EMM-страница, файл — что угодно) прямо в CBL через
`cbl_push_otir()`/`cbl_push_accel()` — без промежуточной копии в libc.
Пример из офиц. документации делает ровно это: `OUTI` читает прямо из
HL, указывающего в банковую страницу с WAV-данными, без всякого
стейджинга.
`fill(n)` вызывается ИЗ ISR — ОБЯЗАН быть быстрым: никаких
ESTEX/BIOS/gfx-вызовов (тот же констрейнт, что у `irq_install()`-
хендлера). В частности, диск (read() — ESTEX) читать из fill() НЕЛЬЗЯ
— см. tests/cblstream ниже, где под это заведено собственное кольцо
уровня приложения. Возвращает ненулевое при успехе, 0 — недолив.
- **Control-порт 0x004E — 16-битный** (`ld bc,#0x004E` / `out (c),a`,
не 8-битный `out (n),a`). Биты: 7 = CBL on, 6 = stereo, 5 = 16-bit,
4 = interrupt enable, 3..0 = код частоты (8=7.8125 кГц…F=109.375 кГц,
0/1 — legacy-режим без сэмплирования). `cbl_open` шлёт
`0x90|fmt|freq` (on + int + формат + частота) под DI, `cbl_close`
шлёт `0`.
- **Формат — CBL_FMT_MONO8/MONO16/STEREO8/STEREO16** (2026-07-07):
биты 5(16-бит)/6(stereo) напрямую соответствуют константам, можно
OR'ить с freq. Блок запроса — 128 Б для 8-бит, 256 Б для 16-бит
(НЕ зависит от моно/стерео — см. Forum.txt: «для каждых 128 байт
(256 в режиме 16 бит)»); тишина — 0x80 (8-бит unsigned) или 0x0000
(16-бит signed). 8-бит сэмплы центр 0x80, 16-бит центр 0x0000,
stereo — чередование L/R. Runtime-размер блока — `_cbl_block`
(128 или 256), заполняется в `cbl_open` по fmt.
- **Два насоса, выбор — CBL_PUMP_OTIR/CBL_PUMP_ACCEL** (2026-07-07):
- **OTIR** (`_cbl_pump_otir`) — блок в порт 0x4F через `otir`; B=младший
байт `_cbl_block` (128 остаётся 128, 256 идёт как 0 — Z80 OTIR:
B=0 значит 256 итераций). Проверено в MAME (звук слышен, 0
underrun).
- **ACCEL** (`_cbl_pump_accel`) — запись через акселератор в
спец-страницу EMM 0xFD, замапленную в окно W3 на 0xC000 (Forum.txt:
«запись данных в COVOX-Blaster... 128/256 байт с адреса 0xC000»).
Последовательность (по accel_r.txt + рабочему прецеденту
libc/gfx/_gfx_hfill256.c): `LD D,D` (режим размера блока) →
immediate `LD A,n` (SMC-патч, 0 значит 256 — тот же трюк, что и в
OTIR) → `LD L,L` (режим "копирование блока") → `LD A,(HL)` /
`LD (DE),A` (блочное чтение источника → блочная запись в
0xC000@стр.0xFD) → `LD B,B` (выкл). Акселератор НЕ продвигает
HL/DE сам — advance после пересылки делается вручную (`add hl,
(block)`). W3 сохраняется/восстанавливается вокруг переключения
(как `bank_read`/`bank_write`); доп. DI/EI не нужны — весь насос
целиком уже внутри ISR (прерывания замаскированы до EI/RETI
трамплина). **Verified в MAME 2026-07-07** — tests/cbltest, все
32 accel-комбинации матрицы прошли без ошибок/underrun.
- Общий каприз с CBL-примером из docs/samples: там для установки
размера блока акселератора используется `LD C,128` СРАЗУ ЗА
`LD D,D` — это противоречит и accel_r.txt («далее следует команда
типа LD A,dat»), и нашему же подтверждённому на gfx констрейнту
(CLAUDE.md: «block-size ОБЯЗАН быть immediate операндом LD A,n»).
Мы взяли ВЕРИФИЦИРОВАННЫЙ вариант (LD A,n), а не пример — вероятно,
у автора размер уже был установлен раньше (заметка в accel_r.txt:
«если размер блока был установлен ранее, его можно не
устанавливать»), и `LD C,128` в примере готовит BC для последующего
`ADD HL,BC`, а не для акселератора.
- **Data-порт 0x4F** (для OTIR-насоса), блок 128/256 байт.
- **Признак запроса блока — бит 7 порта 0xFE — валиден ТОЛЬКО когда
CBL реально активен.** При выключенном CBL MAME (`kbd_fe_r`)
подтягивает этот бит к 1 всегда (`data |= 0xE0`) — поэтому трамплин
проверяет бит 0xFE.7 не напрямую, а через индирекцию
`_irq_cbl_hook`: пока `cbl_open` не установил хук, кадровые
прерывания даже не читают порт 0xFE (см. Phase 1 — по этой же
причине первая версия фильтра была убрана). Официальная документация
описывает тот же бит как "старший бит счётчика" адреса аппаратного
буфера — по нему же программа определяет, какую половину доливать.
- **Насос (`_cbl_pump_otir`/`_cbl_pump_accel`) теперь тривиален**:
`_cbl_reqs++; ok = _cbl_fill ? _cbl_fill(_cbl_block) : 0; if (!ok) {
_cbl_undr++; ...}`. Обычные (не `__naked`) Си-функции — можно, т.к.
трамплин уже сохраняет ВЕСЬ контекст (оба регистровых набора + IX/IY)
вокруг вызова хука, что бы функция ни наделала с регистрами.
- **Поведение при недоливе — CBL_UNDERRUN_APP/SILENCE** (4-й параметр
cbl_open, 2026-07-07): по умолчанию (`APP`, 0) недолив — не забота
библиотеки, буфер тишины НЕ аллоцируется вовсе, в CBL доигрывает то,
что уже лежало в его аппаратном буфере. `SILENCE` (1) — насос сам
пропихивает тишину (`_cbl_silence`, malloc'ится В cbl_open() ТОЛЬКО
в этом режиме, размером `_cbl_block`, залит 0x80/0x0000 по формату) —
тот же приём, что раньше был жёстко вшит в насос, теперь опционален.
`cbl_underruns()` считает недоливы в ОБОИХ режимах — это только
диагностика.
- **CBL-путь НЕ чейнится к DSS** — личное прерывание CBL, полный сейв
контекста (основной набор + теневой AF/BC/DE/HL + IX/IY, т.к. `fill()`
может клобберить что угодно) → `EI`/`RETI` напрямую, без 0x0038.
- `cbl_open` держит те же анти-повторные гарантии, что и irq/ctc:
занятый хук → EBUSY, `atexit(cbl_close)` регистрируется один раз,
`_irq_table_ref()`/`_irq_table_unref()` для общей IM2-таблицы.
- **tests/cbltest**: полная матрица (2 насоса × 8 форматов × 4 частоты
= 64 комбинации) пилообразного тона, ~1 с каждая; `fill_tone()`
всегда возвращает 1 (period-64 тон никогда не "кончается") —
CBL_UNDERRUN_APP без буфера тишины достаточно. Main не поллит
ничего — просто ждёт halt()'ом. OTIR+16-бит (16/64) пропускаются
заранее (cbl_open вернул бы EINVAL).
- **tests/cblwav**: потоковая речь (78 КБ) с ДИСКЕТЫ через banked EMM
(диск слишком медленный для realtime — клип предзагружается в RAM
ДО cbl_open). `fill_speech()` делает `bank_read()` из уже загруженной
страницы в стейджинг и `cbl_push_otir()` — физические номера страниц
кэшированы в массиве ЗАРАНЕЕ (на этапе загрузки, в main-контексте):
`mem_get_page()` — BIOS-вызов, сам управляет EI/DI, и звать его ИЗ
fill() (то есть из ISR) нельзя — его `ei` при возврате может
преждевременно снять маску прерываний, пока мы ещё внутри ISR.
- **tests/cblstream**: без banked-предзагрузки — чтение с диска
ОДНОВРЕМЕННО с воспроизведением; рассчитан на быстрый носитель (HDD).
Единственный из трёх тестов, где приложению НУЖНО собственное кольцо:
`read()` — ESTEX-вызов, а `fill()` зовётся из ISR, где ESTEX/BIOS под
запретом — поэтому диск читается ТОЛЬКО в main (в кольцо уровня
приложения), а `fill_stream()` лишь копирует уже готовые байты и
пропихивает `cbl_push_otir()`. НЕ входит в общую сборку (`make`/
`make floppy`) — только `cd tests/cblstream && make run`, свой образ
диска.
ISA-вектора, цепочки нескольких хендлеров на одном векторе — не
реализовано (см. «Phase 2 (когда понадобится)» ниже).
**Щелчок перед первым звуком за сессию (2026-07-07, A/B/C-стенд)**:
на РЕАЛЬНОМ файле (tests/cblwav, потоковая речь) перед началом
воспроизведения был слышен щелчок/призвук. Диагностика через
tests/cblwav (banked-стрим с пилой, затем с чистой тишиной вместо
речи, в одном запуске) показала: щелчок слышен ТОЛЬКО на первом
запуске программы после старта MAME и НЕ зависит от содержимого потока
(тон / тишина / речь — одинаково). Проверенное на macOS (`afplay`)
воспроизведение исходного speech.pcm — чистое, артефактов в самом
файле нет. Разбивка `cbl_open()` на две записи в порт 0x004E (сначала
код частоты, потом enable, с паузой) не повлияла — отменена. Вывод:
это одноразовый прогрев звуковой подсистемы MAME при первой активации
канала CBL за сессию эмулятора, не баг протокола/драйвера; на реальном
железе, скорее всего, отсутствует (см. docs/TODO.md).
Этот документ собирает всё, что мы знаем о прерываниях Sprinter и план реализации user-задаваемых ISR через Z80 IM 2 mode. Когда возьмёмся за реализацию — читать этот файл, чтобы не повторять research.
@@ -247,7 +395,7 @@ User's ISR НЕ должен:
## Phase 2 (когда понадобится)
- CBL/COVOX prerequisite handler (для audio playback)
- ~~CBL/COVOX prerequisite handler~~ — реализован, см. «Phase 2b» выше
- ISA interrupt handler (для ZX-Bus карт)
- Multiple user handler chain (e.g. tick + sound)
+11 -3
View File
@@ -32,9 +32,15 @@ SDCC-версию) и дальше только добавляет Sprinter-ра
## 3. Свои заголовки (SDCC-аналога нет — затенения нет)
conio.h, dir.h, dos.h, errno.h, fcntl.h, gfx.h, mouse.h, palette.h,
conio.h, dir.h, dos.h, errno.h, fcntl.h, mouse.h, palette.h,
sprinter*.h, unistd.h, bios/*.
Графика вынесена из libc в отдельную библиотеку libbgi/: её публичные
заголовки gfx.h (mode-agnostic BGI_GFX) и graphics.h (BGI API) живут в
libbgi/include/ и пробрасываются через -I libbgi/include (sprinter-cc
добавляет его автоматически). Внутренний заголовок графики —
libbgi/_bgi.h (слияние старых libc/gfx/_gfx.h и libc/bgi/_bgi.h).
## Правила
- новый стандартный заголовок — сначала пробовать паттерн 1
@@ -42,5 +48,7 @@ sprinter*.h, unistd.h, bios/*.
реализацию (как puts/putchar на ESTEX);
- в заголовках паттерна 2 — комментарий, какие декларации обслуживают
z80.lib;
- internal-заголовки libc (`_conio.h`, `_file.h`, `_gfx.h`, …) живут
РЯДОМ с исходниками в libc/<area>/, не в libc/include.
- internal-заголовки libc (`_conio.h`, `_file.h`, …) живут
РЯДОМ с исходниками в libc/<area>/, не в libc/include. Internal
графики — в libbgi/_bgi.h (корень libbgi/, подключается из common/ и
bgi256/bgi16/ как `#include "../_bgi.h"`).
+173 -9
View File
@@ -88,11 +88,50 @@ in-place регистр, латиница + кириллица CP866.
| `int isatty(fd)` | fd <= 0 (файловые манипуляторы DSS с 1) |
| `int mkdir/rmdir/chdir(path)` | ESTEX $1B/$1C/$1D |
| `char *getcwd(buf, size)` | буфер 256 байт, size игнорируется |
| `void sleep(seconds)` | 50 halt/с по кадровому IRQ |
| `void sleep(seconds)` | калиброванный busy-wait (см. ниже) |
| `void delayms(uint16_t ms)` | то же, гранулярность — миллисекунды (см. ниже) |
**Лимит: 8 одновременных манипуляторов**; 9-й OPEN вешает DSS —
libc отказывает сама (EMFILE, предохранитель _fd_guard).
**`sleep()` (2026-07-07, редизайн)**: старая версия считала halt-
пробуждения (50 = 1 c), предполагая, что КАЖДОЕ прерывание — кадровый
тик; с CBL/клавиатурой на векторе 0xFF это уже не так (CBL прерывает
намного чаще кадра — sleep() возвращался бы раньше срока). Теперь:
лениво, один раз калибруется кратковременным `irq_install()` против
РЕАЛЬНОГО кадрового тика (трамплин зовёт хук только на настоящих
кадровых прерываниях — клавиатура/CBL уходят в свои ветки раньше),
считая, сколько итераций тесного цикла умещается в один КАДР (не в
секунду — калибровка на секунду переполняла `uint16_t`, был баг:
`sleep(5)` отрабатывал быстрее секунды, найден пользователем на
реальном прогоне и исправлен). `sleep(seconds)` — вложенный цикл:
внешний по секундам, внутренний ровно 50 раз калиброванный busy-wait —
без единого прерывания и БЕЗ умножения/32-битной арифметики (по духу
`docs/samples/delayms.asm`, который тоже калибрует на 1 мс, а не на
1 с). Погрешность ~5-10% (калибровочный и рабочий циклы не тактово-
идентичны). Fallback на старое "50 halt" поведение, если фрейм-хук уже
занят другим `irq_install()`-клиентом (EBUSY).
**Cтоит дороже по размеру** (~+580 Б) — тянет весь модуль `irq`
(install/remove/трамплин/IM2-таблицу), даже если программа больше
ничего из irq не использует.
**`delayms(ms)`** — тот же движок (`libc/time/_sleep_calib.c`), общий с
`sleep()`: калибровка одна на двоих (первый вызов ЛЮБОЙ из функций
калибрует, вторая просто использует готовое). Производная величина
"итераций на 1 мс" — одно 16-битное деление (`__divuint`, НЕ
`__mullong`) на константу 20, вычисляется один раз при калибровке;
`delayms(ms)` — простой цикл `ms` раз, без умножения вообще. Fallback
при EBUSY — грубый (одно кадровое `halt`), точной альтернативы для
миллисекундной гранулярности без калибровки нет.
**Квирк общий для sleep()/delayms()**: калибровка сама стоит ~20-40 мс
(синхронизация на границу кадра + сам замер) — ПЕРВЫЙ вызов ЛЮБОЙ из
двух функций в программе превысит запрошенное время на эту величину;
для `delayms()` с маленьким `ms` это заметно (`delayms(5)` на первом
вызове может растянуться на ~25-45 мс). Все последующие вызовы точны.
Оставлено как есть по решению пользователя — не стали усложнять API
отдельным calibrate()-примитивом.
## <errno.h>
`errno` (int), коды = коды DSS (EOK..EUNKERR) + POSIX-имена
@@ -141,20 +180,80 @@ wrchar rdchar`; режимы `gettextmode/settextmode` (0x02=40×32,
`bios_fillchar/fillattr/fillcharattr`, `bios_clearwin[_ch]`,
`bios_scrollwin`. Строка s — в #4000#BFFF; place продвигается.
## <gfx.h> — графика (0x81: 320×256×256; 0x82: 640×256×16)
## <gfx.h> — графика, mode-agnostic API (BGI_GFX); живёт в libbgi/include
Рисование (putpixel/line/bar/circle/…) вынесено в BGI — см. `<graphics.h>`
и driver-библиотеки lib/bgi256.lib / lib/bgi16.lib (выбор режима линковкой:
`sprinter-cc --gfx 256` / `--gfx 16`). В gfx.h остались только функции
БЕЗ BGI-аналога (mode-agnostic, живут в libbgi/common/, .rel в обеих
driver-библиотеках):
Setup: `gfx_init(mode,page)→prev`, `gfx_done(prev)`.
Страницы/банк: `gfx_set/get_visible_page`, `gfx_set/get_draw_page`
(double buffering), `gfx_set/get_bank` (0x50..0x5F, 0x58 = FF-
прозрачность), `gfx_wait_vsync()`.
Примитивы (суффикс _256 / _16): `clear putpixel hline vline line
rect fill_rect` — через акселератор (hline/vline burst до 256 байт;
в 0x82 vline через RMW). Координаты int, клиппинг по краям.
Текст: `gfx_putchar256/16, gfx_text256/16` (8×8, fg/bg; в 0x82 x —
чётный), `gfx_load_default_font`, `gfx_set_font(ptr)` (interleaved
font[row*256+char]).
прозрачность).
`gfx_wait_vsync()` (2026-07-07, редизайн): ждёт переход бита 5 порта
0xFE из 1 в 0 — реальное аппаратное состояние луча (Y>256 → начало
кадра, см. MAME sprinter.cpp kbd_fe_r), а не прерывание — поэтому не
путается с клавиатурой/CBL/CTC, деляющими вектор 0xFF. Бит доступен
только пока включён `cbl_mode()` (bit7 порта 0x004E) — если приложение
уже играет через `cbl_open()`, бит достаётся бесплатно; иначе
`gfx_wait_vsync()` лениво занимает bit7 "немым" кодом частоты через
`_cbl_port_ref()`/`_cbl_port_unref()` (см. `<cbl.h>`, `_cbl_port.c`) —
разделяемое владение портом 0x004E, безопасное при любом порядке
использования с реальным CBL-звуком. Фолбэк на одно кадровое
прерывание (`halt`), если бит не ведёт себя как ожидается за разумное
число попыток.
Шрифт: `gfx_load_default_font`, `gfx_set_font(ptr)` (interleaved
font[row*256+char]) — грузится лениво при первом использовании BGI-текста.
Палитра: `gfx_pal_load/set/get/get_color/reset` (страницы 0..3).
Константы режимов: `GFX_MODE_320x256x256` (0x81), `GFX_MODE_640x256x16`
(0x82); размеры `GFX_WIDTH/HEIGHT` (320/256), `GFX_WIDTH_16/HEIGHT_16`
(640/256). Рисование через `<graphics.h>` (BGI).
## <graphics.h> — Turbo-C BGI (функц. совместимость), Фаза 1, режим 256
Слой поверх `<gfx.h>` со «текущим» цветом/позицией. Режим задаётся
driver-либой на линковке: `sprinter-cc --gfx 256` (→ 320×256×256; 16 —
позже). API mode-agnostic: код не меняется при смене режима.
Setup: `initgraph()` (без аргументов — режим фиксирован либой; грузит
EGA-палитру 0..15, цвет=WHITE, фон=BLACK, CP=(0,0)),
`closegraph()`, `graphresult()`, `cleardevice()`.
Границы/цвет: `getmaxx/getmaxy` (319/255), `getmaxcolor` (255),
`setcolor/getcolor`, `setbkcolor/getbkcolor`. Константы BLACK..WHITE.
Точки: `putpixel(x,y,c)`, `getpixel(x,y)`.
Позиция/линии: `moveto/moverel/getx/gety`, `lineto/linerel` (двигают
CP), `line(x1,y1,x2,y2)` (не двигает).
Фигуры: `rectangle` (контур), `bar` (заливка стилем), `circle`.
Дуги (Ф2a): `arc`, `ellipse(x,y,st,end,xr,yr)`, `drawpoly(n,pts)`.
Текст 8×8: `outtextxy(x,y,s)`, `outtext(s)` (двигает CP).
Заливки (Ф2b): `setfillstyle(pattern,color)`/`getfillsettings` (10
паттернов Borland: SOLID/EMPTY/LINE/…/HATCH/XHATCH/…), `bar3d`,
`fillpoly(n,pts)`, `fillellipse(x,y,xr,yr)`.
Заливка областей (Ф2c): `floodfill(x,y,border)` (медленно, но верно),
`pieslice(x,y,st,end,r)`, `sector(x,y,st,end,xr,yr)`.
Спрайты (Ф2d): `imagesize/getimage/putimage` (COPY/XOR/OR/AND/NOT_PUT;
формат буфера: uint16 w,h + w*h байт).
Стиль линий (Ф2d): `setlinestyle(style,upattern,thick)`/`getlinesettings`
(SOLID/DOTTED/CENTER/DASHED/USERBIT + NORM/THICK) — на line/rectangle/
drawpoly.
Стиль текста (Ф2d): `settextstyle(font,dir,size)`/`gettextsettings`,
`textwidth`/`textheight` — масштаб 1..10, HORIZ/VERT, прозрачный фон
(только DEFAULT_FONT 8×8).
Реализация: libbgi/common/*.c (mode-agnostic math + BGI public API +
BGI_GFX) + libbgi/bgi256/*.c (256-цветные leaf'ы) → lib/bgi256.lib
(Фаза 2 добавит libbgi/bgi16/*.c → lib/bgi16.lib; common .rel одни и
те же в обоих архивах). Leaf'ы — реальные реализации (БЕЗ обёрток:
putpixel/getpixel полностью inline, _bgi_plot_raw/_bgi_hspan_raw/...
поглощают акселераторный asm). Пакетные примитивы — одна W3-скобка
на примитив; тригонометрия/эллипсы целочисленные (Q7/isqrt, БЕЗ 32-бит).
Cross-lib: libbgi всегда линкуется с libc; _cbl_port_ref/unref
объявлены extern в libbgi/_bgi.h (сверять с libc/cbl/_cbl.h).
Ф2d (осталось): setviewport/клиппинг, settextjustify, setaspectratio —
см. docs/TODO.md.
## <irq.h> — user-ISR кадрового прерывания (IM 2)
| Сигнатура | Описание |
@@ -168,6 +267,71 @@ font[row*256+char]).
Handler'у нельзя: ESTEX/BIOS-вызовы, gfx_*/своп окон, акселератор,
banked-функции; только volatile-глобалы и быстрая работа (<1 мс).
## <cbl.h> — потоковый звук CBL/COVOX (вектор 0xFF, свой ISR)
Без собственного кольца (2026-07-07): у CBL уже есть аппаратный буфер
256 Б (2×128, двойная буферизация на стороне железа — см. официальную
доку "5.3 COVOX-Blaster"); библиотека просто зовёт `fill()` приложения
ИЗ ISR, а оно само пропихивает данные (откуда угодно) через
`cbl_push_otir/accel` — без промежуточной копии.
| Сигнатура | Описание |
|---|---|
| `int cbl_open(freq_code, fmt, pump_mode, underrun_mode, fill)` | включить CBL (`0x90\|fmt\|freq`), зарегистрировать callback; 0 / -1+errno (EBUSY повтор, EINVAL — freq/fmt/pump_mode/underrun_mode плохие или OTIR+16-бит, ENOMEM — буфер тишины) |
| `void cbl_close(void)` | выключить CBL, снять хук, освободить буфер тишины (если был); висит на atexit |
| `void cbl_push_otir(const void *src, uint16_t n)` | пропихнуть n байт через `otir` в порт 0x4F; звать ИЗ fill() |
| `void cbl_push_accel(const void *src, uint16_t n)` | то же акселератором (страница EMM 0xFD@0xC000); единственный путь для 16-бит |
| `uint16_t cbl_requests(void)` | счётчик запросов блока ISR (~fs/(сэмплов в блоке) в секунду) |
| `uint16_t cbl_underruns(void)` | счётчик недоливов (`fill` вернул 0 или не задан) |
| `CBL_FREQ_7K8 .. CBL_FREQ_109K` | коды частоты (биты 3..0 control-порта) |
| `CBL_FMT_MONO8/MONO16/STEREO8/STEREO16` | формат (биты 5/6 control-порта); блок 128 Б (8-бит) или 256 Б (16-бит), не зависит от моно/стерео; тишина 0x80 (8-бит) / 0x0000 (16-бит, знаковый) |
| `CBL_PUMP_OTIR / CBL_PUMP_ACCEL` | способ выдачи — см. ниже |
| `CBL_UNDERRUN_APP / CBL_UNDERRUN_SILENCE` | поведение при недоливе — см. ниже |
`typedef int (*cbl_fill_fn)(uint16_t n);` — callback, зовётся ИЗ ISR за
очередным блоком (n = `_cbl_block`, 128/256). **Обязан быть быстрым**
— никаких ESTEX/BIOS/gfx-вызовов (тот же констрейнт, что у
`irq_install()`-хендлера); вернуть ненулевое, если реально пропихнул n
байт. Диск (read()) читать из fill() НЕЛЬЗЯ — см. tests/cblstream,
где под это заведено кольцо уровня приложения.
Два насоса (3-й параметр `cbl_open`):
- **OTIR** (`_cbl_pump_otir`) — `cbl_push_otir()` в порт 0x4F; базовый.
**НЕ умеет 16-бит**`cbl_open(..., MONO16/STEREO16, CBL_PUMP_OTIR,
...)` вернёт EINVAL: по исходнику MAME (sprinter.cpp) порт данных
ВСЕГДА кладёт байт как есть в один слот, не собирая пару байт в
16-бит значение и не сверяясь с 16-бит флагом вообще.
- **ACCEL** (`_cbl_pump_accel`) — `cbl_push_accel()` через акселератор
в спец-страницу EMM 0xFD, замапленную в окно W3 на 0xC000 (см.
docs/converted/accel_r.txt, Forum.txt); размер блока патчится SMC
(`LD D,D` + immediate `LD A,n` + `LD L,L` — тот же паттерн, что и в
libc/gfx/_gfx_hfill256.c); единственный путь для 16-бит.
**Verified в MAME 2026-07-07** (tests/cbltest, вся accel-половина
матрицы прошла без ошибок/underrun).
Поведение при недоливе (4-й параметр `cbl_open`):
- **CBL_UNDERRUN_APP** (по умолчанию, 0) — не забота библиотеки, буфер
тишины НЕ аллоцируется, в CBL доигрывает то, что уже лежало в его
аппаратном буфере.
- **CBL_UNDERRUN_SILENCE** (1) — насос сам пропихивает тишину; буфер
(128/256 Б по формату) аллоцируется malloc'ом ВНУТРИ `cbl_open()`
только в этом режиме.
`cbl_underruns()` считает недоливы в обоих режимах — диагностика,
поведение не меняет.
Приватное прерывание, к DSS не чейнится; бит 7 порта 0xFE (запрос
блока) читается только пока `cbl_open` не закрыт (при выключенном CBL
бит всегда 1 — MAME-квирк).
**Разделяемое владение портом 0x004E** (2026-07-07): бит 5 порта 0xFE
(позиция луча — см. `<gfx.h>` `gfx_wait_vsync()`) доступен только пока
включён bit7 порта 0x004E, независимо от того, играет ли реальный
звук. `_cbl_port_ref()`/`_cbl_port_unref()` (internal, `_cbl_port.c`)
дают gfx-модулю занять bit7 "немым" кодом частоты (не заводящим таймер
CBL — без звука/прерываний), не мешая реальной `cbl_open()`-сессии,
если она уже идёт (и наоборот — `cbl_close()` возвращает "немой" режим
вместо полного выключения порта, если gfx его ещё держит).
## <palette.h> — низкий уровень (BIOS $A4/$A6)
`pal_load pal_get pal_set_color pal_get_color` (страница 0..7,
+1 -1
View File
@@ -75,7 +75,7 @@ fgetpos/fsetpos (хвост П2). bdos/brk/ioctl — отказ решение
(stdio.h, time.h — обязаны дублировать сигнатуры z80.lib);
из sprinter_compat.h убраны макросы min/max (конфликтовали с
функциями из stdlib.h; в Solid-C это тоже функции)
- [x] lib/Makefile: stale .rel чистятся сверкой списка перед упаковкой;
- [x] libc/Makefile: stale .rel чистятся сверкой списка перед упаковкой;
штамп .modules триггерит перелинковку при смене состава (и сносит
архив — mtime на exFAT грубый)
- [x] Все extra-тесты в top-level TESTS (43 программы: + hello2, simple,
+242
View File
@@ -0,0 +1,242 @@
# Автотестирование в MAME
Единый справочник: как запускать программы Sprinter в эмуляторе MAME
**без участия человека**, вводить команды, снимать скриншоты, завершать
сессию и анализировать результат. Если нужно что-то про автотесты в
MAME — смотреть сюда.
Весь механизм собран в одном инструменте: **`toolchain/mame_interactive.py`**.
---
## 1. TL;DR
```bash
# собрать .exe (пример)
make -C tests/bgitest
# запустить в MAME, снять экран, выйти по таймауту
python3 toolchain/mame_interactive.py tests/bgitest/bgitest.exe \
--snap 12,14 --timeout 16
```
Инструмент сам:
1. проверит, что нет висящих копий MAME;
2. положит `.exe` на дискету A: (`mame/v306/IMG/mc.img`);
3. запустит MAME с драйвером `sprinter`;
4. дождётся приглашения `C:\>` и **напечатает `a:\bgitest.exe` + Enter**
(эмулируя нажатия клавиш);
5. снимет скриншоты в указанные секунды эмулированного времени;
6. завершит сессию по таймауту;
7. выведет пути к PNG-скриншотам.
Скриншоты лежат в `mame/v306/snap_auto/sprinter/` (`0000.png`, `0001.png`,
…). Их читает Claude визуально (инструментом Read) — текст с экрана
программно НЕ распознаётся.
---
## 2. Инструмент: `mame_interactive.py`
```
python3 toolchain/mame_interactive.py [exe] [--data f ...] \
[--launch-at T] [--step "T:TEXT" ...] [--snap t1,t2,...] [--timeout N]
```
| Аргумент | Назначение |
|----------|-----------|
| `exe` | `.exe` кладётся на A: и **авто-запускается** (печатается `a:\<exe>`+Enter в момент `--launch-at`). Без `exe` работаем на голой командной строке. |
| `--data f ...` | доп. файлы на дискету A: (данные для теста). |
| `--launch-at T` | секунда, когда печатается запуск `exe` (по умолчанию **8**). |
| `--step "T:TEXT"` | в момент `T` сек напечатать `TEXT`. Можно много раз — диалог с уже запущенной программой. В `TEXT`: `\n`=Enter, `\t`=Tab; заглавные и символы через Shift — автоматически. |
| `--snap t1,t2,...` | секунды эмуляции для скриншотов. По умолчанию: `launch_at+4` и `+6` (для `exe`), либо сразу после последнего ввода. |
| `--timeout N` | секунд эмуляции до принудительного выхода. По умолчанию — чуть позже последнего скриншота. |
**Важно:** все времена — это **секунды эмулированного времени от старта
машины** (не от нажатий, их «нет»). Загрузка DSS до `C:\>` занимает
~7 секунд, поэтому `--launch-at 8` и скриншоты с ~12 с.
### Типовые рецепты
```bash
# 1. Запустить тест и снять результат (самый частый случай)
python3 toolchain/mame_interactive.py tests/rt_test/rt_test.exe \
--snap 12,14 --timeout 16
# 2. Набрать команду на голой командной строке (без exe)
python3 toolchain/mame_interactive.py --step "8:dir\n" \
--snap 10,11 --timeout 12
# 3. Запустить программу и ответить на её ввод (например, выбор пункта меню)
python3 toolchain/mame_interactive.py tests/menu/menu.exe \
--step "13:2\n" --snap 15 --timeout 17
# 4. Тест с файлом-данными на дискете
python3 toolchain/mame_interactive.py examples/mdview2/mdview2.exe \
--data doc.md --step "13:mdview2 doc.md\n" --snap 16 --timeout 18
```
---
## 3. Как это работает внутри
### 3.1 Запуск MAME
Нативный `mame.arm` (arm64) в `mame/v306/`, драйвер `sprinter`, BIOS
v3.06. Полный набор аргументов зашит в `COMMON_ARGS` внутри скрипта:
две дискеты (A: наш `mc.img`, B: DSS 1.71u), два HDD-образа (система +
медиа), CD-ROM, ZX-Bus карта NeoGS, `-video opengl -window`,
`-snapshot_directory`, `-autoboot_script <сгенерированный .lua>`.
### 3.2 Ввод с клавиатуры — ключевой момент
У Sprinter в MAME **две** клавиатуры:
- `IO_LINE0..7` — легаси ZX-Spectrum-матрица (порт `0xFE`). DSS её для
командной строки **НЕ читает**.
- `root:kbd:ms_naturl`**настоящая AT/PS-2 клавиатура**, подключённая
последовательно к SIO Z84C015 (`sprinter.cpp:2037`). Именно её DSS
читает как поток scancode'ов.
Поэтому **не работают** (проверено многократно): `natkeyboard:post`,
`-autoboot_command`, а также `set_value` по полям `:IO_LINE*`. Всё это
бьёт в ZX-матрицу, которую DSS игнорирует.
**Работает** — прямое управление полями AT-клавиатуры из Lua:
```lua
manager.machine.ioport.ports[":kbd:ms_naturl:P1.4"].fields["D"]:set_value(1) -- нажать
... подождать ~0.06с ...
manager.machine.ioport.ports[":kbd:ms_naturl:P1.4"].fields["D"]:set_value(0) -- отпустить
```
`at_keyboard` сам сгенерит make/break scancode'ы → SIO → DSS.
Скрипт хранит раскладку `char → (порт, битовая маска)` (словарь `PHYS` +
`SHIFTED` для Shift-символов) и разворачивает строку в список
timed-событий `(время, порт, маска, значение)`. Backslash `\` в
AT-клавиатуре есть (поле `P2.1`/0x4) — путь `a:\name.exe` вводится
полностью.
### 3.3 Тайминг (Lua)
Генерируется `_interactive_gen.lua`, который через
`emu.register_periodic` на каждом кадре сверяет **эмулированное время** и
проигрывает события ввода, снимает скриншоты и завершает сессию.
Время берётся как `t.seconds + t.attoseconds/1e18`, потому что
`attotime.seconds`**целое** (дробную часть отбрасывает); если считать
по нему, все события схлопнутся в 1-секундную сетку.
Старт отсчёта — `emu.add_machine_reset_notifier` (НЕ `emu.register_start`
— он deprecated).
### 3.4 Скриншоты
`manager.machine.video:snapshot()` пишет PNG в каталог из
`-snapshot_directory` (`mame/v306/snap_auto/sprinter/`). Скрипт перед
запуском чистит этот каталог, потом печатает пути к готовым файлам.
Claude открывает их инструментом Read (визуальный анализ).
### 3.5 Завершение сессии
Два рубежа, чтобы MAME гарантированно не «завис»:
- в Lua: при `elapsed >= timeout``manager.machine:exit()` (чистый
выход);
- снаружи: shell-обёртка `timeout <N+8> ./mame.arm …` как страховка.
---
## 4. Предпосылки (окружение)
- **MAME**: `mame/v306/mame.arm` + образы в `mame/v306/IMG/`
(`mc.img` — наш перезаписываемый A:, `dss171u.img`, HDD `.chd`, CD
`.iso`). Каталог `mame/` целиком в `.gitignore` — поэтому инструмент
живёт в `toolchain/`, а не рядом с MAME.
- **Загрузка должна доходить до `C:\>`.** `system.bat` на системном
диске НЕ должен автоматически запускать Flex Navigator или приложение —
иначе мы не попадём на командную строку и ввод уйдёт в чужую программу.
(Это файл на HDD-образе, вне репозитория; правится один раз.)
- **Нет висящих копий MAME.** Несколько одновременных инстансов пишут в
один `mc.img` и дают недостоверный результат. Скрипт проверяет это сам
(`pgrep`), но при ручных запусках MAME — проверяйте `ps aux | grep mame`.
---
## 5. Как выбирать времена
- **Загрузка до `C:\>`:** ~7 секунд → `--launch-at 8` безопасно.
- **Набор пути `a:\name.exe`:** ~13 символов × 0.14с ≈ 1.8с → команда
уходит около 9.8с, программа стартует ~10с.
- **Скриншот:** давайте программе дорисоваться. Быстрая программа —
снимать с ~12с; если рисует долго/по частям, снимайте несколько кадров
(`--snap 12,16,20`) и смотрите, где картинка «дособралась».
- **Диалог с программой (`--step`):** времена шагов ставьте ПОСЛЕ старта
программы (например, запуск на 8с, ответ на ввод на 13–15с).
---
## 6. Анализ результата
- Скриншоты — **единственный** способ проверки: программного чтения
текстового/графического VRAM нет, OCR нет. Claude читает PNG глазами
(инструмент Read с картинкой).
- Лог MAME фильтруется по строкам `[interactive]` (моменты снимков и
выхода) — видно, в какие секунды сделаны кадры.
- Если картинка «не дособралась» — снять более поздний кадр (увеличить
`--snap`/`--timeout`).
---
## 7. Раскладка клавиатуры (справочно)
Раскладка снята дампом ioport-полей `:kbd:ms_naturl:*` живой машины.
Она зашита в `PHYS`/`SHIFTED` внутри `mame_interactive.py`. Поддержаны:
буквы (a–z, A–Z через Shift), цифры, пробел, Enter (`\n`), Tab (`\t`),
и символы ``- = [ ] \ ; ' , . / ` `` плюс их Shift-версии
`! @ # $ % ^ & * ( ) _ + { } | : " < > ? ~`.
Если понадобится клавиша вне списка — снять её поле дампом (пример
Lua-пробы ниже) и добавить в `PHYS`:
```lua
-- дамп всех полей клавиатуры в лог
for tag, port in pairs(manager.machine.ioport.ports) do
if tostring(tag):find("kbd") then
for fname, field in pairs(port.fields) do
print(string.format("%s mask=0x%x %q", tag, field.mask, fname))
end
end
end
```
---
## 8. Квирки и грабли (все, на которые уже наступали)
- **`attotime.seconds` — целое.** Субсекундный тайминг только через
`seconds + attoseconds/1e18`.
- **Автоповтор (typematic).** Клавишу держать коротко (~0.06с). Если
держать ~1с — `d` превратится в `dddddd`.
- **Слипание scancode'ов.** Между символами ~0.14с.
- **Не та клавиатура.** Ввод — только в `:kbd:ms_naturl`, НЕ в
`:IO_LINE*`, НЕ через `natkeyboard`/`-autoboot_command`.
- **Загрузка мимо `C:\>`.** Если `system.bat` что-то автозапускает —
ввод уходит в чужую программу; вернуть чистую командную строку.
- **Висящие копии MAME.** Всегда проверять перед запуском.
- **macOS-специфика (справочно):** известный баг MAME
(mamedev/mame#10612 — потеря ввода в fullscreen при движении мыши на
старте) к нам НЕ относится: работаем в `-window`, ввод скриптовый.
---
## 9. На будущее (заметки, ещё не в инструменте)
- **Быстрый накопитель.** Тестам, которым важна скорость диска (напр.
потоковое чтение), имеет смысл копировать файлы с медленной дискеты A:
на HDD `C:\TEMP` перед запуском.
- **Видео+звук.** MAME умеет писать AVI (`-aviwrite`) — для тестов с
анимацией/звуком, где скриншотов мало. Пока не подключено к скрипту.
- **Ручная отладка ввода.** Запуск MAME с `-console` даёт интерактивный
Lua-REPL — удобно нащупывать поля/тайминги вживую перед скриптованием.
- **Второй видеорежим/варианты BIOS** — при необходимости менять
`COMMON_ARGS`.
```
+8 -3
View File
@@ -6,8 +6,13 @@ banked 1056
bankedbg 1067
banklocl 4832
banktest 3767
bgi_img 6218
bgitest 4196
bios_text 4470
cat 915
cblstream 6277
cbltest 6407
cblwav 6486
conio 4634
conio2 3946
dec_test 860
@@ -17,13 +22,13 @@ fdmax 6337
filetest 11624
gets 509
gfx_d16 3319
gfx_dbuf 2396
gfx_dbuf 2544
gfx_demo 3219
gfx_mous 5542
gfx_text 2560
hello 4176
hello2 4251
irqtest 5834
irqtest 5881
ls 4919
malloc 4462
mdview 19122
@@ -32,7 +37,7 @@ mem_test 4558
mouse 4394
openenv 6127
ptime 5896
rt_test 4103
rt_test 4906
seek 4177
simple 963
solidt 12381
1 # Эталон размеров _CODE (байт); обновление: python3 toolchain/size_check.py --update
6 bankedbg
7 banklocl
8 banktest
9 bgi_img
10 bgitest
11 bios_text
12 cat
13 cblstream
14 cbltest
15 cblwav
16 conio
17 conio2
18 dec_test
22 filetest
23 gets
24 gfx_d16
25 gfx_dbuf
26 gfx_demo
27 gfx_mous
28 gfx_text
29 hello
30 hello2
31 irqtest
32 ls
33 malloc
34 mdview
37 mouse
38 openenv
39 ptime
40 rt_test
41 seek
42 simple
43 solidt
-84
View File
@@ -1,84 +0,0 @@
# Build lib/libsprinter.lib — the Sprinter target libc archive.
#
# Includes all libc/*.c modules plus the runtime helpers that get
# auto-pulled by SDCC's codegen (heap for malloc, bank trampolines for
# __banked). The crt0 family is NOT in the lib — they are always
# explicitly linked, never DCE-eligible.
#
# Each .c file becomes its own .rel inside the archive. The linker
# pulls only those .rel files whose exported symbols are referenced,
# giving free dead-code elimination at module granularity.
PROJ_ROOT := $(abspath $(CURDIR)/..)
SDCC_BIN := $(PROJ_ROOT)/third_party/sdcc/bin
SDCC := $(SDCC_BIN)/sdcc
SDAR := $(SDCC_BIN)/sdar
SDASZ80 := $(SDCC_BIN)/sdasz80
INC := -I$(PROJ_ROOT)/libc/include
CC_FLAGS := -mz80 --no-std-crt0 --std-c99 --opt-code-size $(INC)
BUILD := $(PROJ_ROOT)/lib/build
# All libc C modules — every .c under libc/<area>/ becomes its own .rel.
# One PUBLIC function per file (see docs/libc-split-asm-cases.md): the
# linker pulls whole .rel modules, so file granularity == DCE granularity.
LIBC_C := $(patsubst $(PROJ_ROOT)/%,%,$(wildcard $(PROJ_ROOT)/libc/*/*.c))
# Runtime modules to bundle (pulled by symbol references from libc-using code).
# NOTE: runtime/bank.s is NOT bundled — its trampoline depends on the banking
# window (W1 for BIG, W3 for HUGE) which is decided per-build by sprinter-cc.
# That tool assembles bank.s on each banked build with the right BANK_W1 flag.
RUNTIME_S := runtime/heap.s
LIBC_RELS := $(patsubst libc/%.c,$(BUILD)/%.rel,$(LIBC_C))
RUNTIME_RELS := $(patsubst runtime/%.s,$(BUILD)/%.rel,$(RUNTIME_S))
ALL_RELS := $(LIBC_RELS) $(RUNTIME_RELS)
# Штамп списка модулей: обновляется при любом изменении состава
# (добавление/удаление/переименование исходника) — иначе удаление
# исходника не триггерит перелинковку и stale .rel остаётся в архиве.
# Сразу сносим и архив: на exFAT (SAM8) гранулярность mtime грубая,
# и «список изменился» через сравнение времён можно проглядеть.
MODLIST := $(BUILD)/.modules
ifneq ($(shell cat $(MODLIST) 2>/dev/null),$(ALL_RELS))
_ := $(shell mkdir -p $(BUILD); echo "$(ALL_RELS)" > $(MODLIST); rm -f sprinter.lib)
endif
LIB := sprinter.lib
all: $(LIB)
# Pattern rule for C modules — preserves the libc/io|mem|stdio path
# so .rel members keep their natural names inside the archive.
$(BUILD)/%.rel: $(PROJ_ROOT)/libc/%.c
@mkdir -p $(dir $@)
$(SDCC) $(CC_FLAGS) -c -o $@ $<
# Runtime .s → .rel
$(BUILD)/%.rel: $(PROJ_ROOT)/runtime/%.s
@mkdir -p $(dir $@)
$(SDASZ80) -o $@ $<
# Archive — sdar with `rcs` = replace/create/symtab.
# Перед упаковкой удаляем stale .rel: wildcard не знает об удалённых/
# переименованных исходниках, и осиротевший .rel из build/ иначе жил бы
# вечно (уже кусало: старый file.rel после сплита).
$(LIB): $(ALL_RELS) $(MODLIST)
@find $(BUILD) -name '*.rel' -print | sort > $(BUILD)/.have
@printf '%s\n' $(ALL_RELS) | sort > $(BUILD)/.want
@comm -23 $(BUILD)/.have $(BUILD)/.want | while read f; do \
echo " stale: $$f (исходник удалён) — rm"; \
rm -f "$$f" "$${f%.rel}.asm" "$${f%.rel}.lst" "$${f%.rel}.sym"; \
done
rm -f $@
$(SDAR) -rcs $@ $(ALL_RELS)
@echo
@echo " Built $@ with $(words $(ALL_RELS)) modules:"
@$(SDAR) -t $@ | sed 's/^/ /'
clean:
rm -rf $(BUILD) $(LIB)
.PHONY: all clean
+79
View File
@@ -0,0 +1,79 @@
# Build lib/bgi256.lib — Sprinter BGI graphics driver (320×256×256).
#
# libbgi/ — самостоятельная графическая библиотека, отделённая от libc.
# Две driver-библиотеки собираются из одной исходной базы:
# bgi256.lib = common/*.rel + bgi256/*.rel (эта — Фаза 1)
# bgi16.lib = common/*.rel + bgi16/*.rel (Фаза 2; пока не собирается)
# common/ — mode-agnostic: .rel компилируется ОДИН раз и кладётся в ОБА
# архива (один исходник для функций, общих 256- и 16-цветной версиям).
# bgi256/ — mode-specific leaf'ы (реальные реализации, без обёрток).
#
# Каждый .c — отдельный .rel в архиве (1 публичная функция = 1 модуль);
# линкер тянет только нужные .rel → DCE на уровне файлов. См. план
# «Рефакторинг GFX → BGI» и memory/bgi_two_lib_design.
#
# make build lib/bgi256.lib
# make clean remove build/ + lib/bgi256.lib
#
# (Фаза 2 добавит bgi16.lib = common + bgi16; common .rel переиспользуются.)
PROJ_ROOT := $(abspath $(CURDIR)/..)
SDCC_BIN := $(PROJ_ROOT)/third_party/sdcc/bin
SDCC := $(SDCC_BIN)/sdcc
SDAR := $(SDCC_BIN)/sdar
# -I libbgi/include (gfx.h, graphics.h) + -I libc/include (palette.h,
# sprinter_exit.h — публичные заголовки libc; libbgi всегда линкуется с
# libc). SDCC stdint и пр. — из собственного include-пути SDCC.
INC := -I$(CURDIR)/include -I$(PROJ_ROOT)/libc/include
CC_FLAGS := -mz80 --no-std-crt0 --std-c99 --opt-code-size $(INC)
BUILD := $(CURDIR)/build
COMMON_C := $(wildcard $(CURDIR)/common/*.c)
BGI256_C := $(wildcard $(CURDIR)/bgi256/*.c)
# BGI16_C := $(wildcard $(CURDIR)/bgi16/*.c) # Фаза 2
COMMON_RELS := $(patsubst $(CURDIR)/common/%.c,$(BUILD)/common/%.rel,$(COMMON_C))
BGI256_RELS := $(patsubst $(CURDIR)/bgi256/%.c,$(BUILD)/bgi256/%.rel,$(BGI256_C))
KNOWN_RELS := $(COMMON_RELS) $(BGI256_RELS)
LIB_DIR := $(PROJ_ROOT)/lib
LIB256 := $(LIB_DIR)/bgi256.lib
# Штамп списка модулей — иначе удаление/переименование исходника не
# триггерит перелинковку и stale .rel остаётся в архиве. На exFAT
# грубая mtime — сравниваем по списку, не по времени.
MODLIST := $(BUILD)/.modules
ifneq ($(shell cat $(MODLIST) 2>/dev/null),$(KNOWN_RELS))
_ := $(shell mkdir -p $(BUILD); echo "$(KNOWN_RELS)" > $(MODLIST); rm -f $(LIB256))
endif
all: $(LIB256)
# Pattern rule для common/ и bgi256/ (один уровень под build/).
$(BUILD)/common/%.rel: $(CURDIR)/common/%.c
@mkdir -p $(dir $@)
$(SDCC) $(CC_FLAGS) -c -o $@ $<
$(BUILD)/bgi256/%.rel: $(CURDIR)/bgi256/%.c
@mkdir -p $(dir $@)
$(SDCC) $(CC_FLAGS) -c -o $@ $<
# Archive — sdar rcs = replace/create/symtab. Перед упаковкой удаляем
# stale .rel (wildcard не знает об удалённых исходниках).
$(LIB256): $(COMMON_RELS) $(BGI256_RELS) $(MODLIST)
@find $(BUILD) -name '*.rel' -print | sort > $(BUILD)/.have
@printf '%s\n' $(KNOWN_RELS) | sort > $(BUILD)/.want
@comm -23 $(BUILD)/.have $(BUILD)/.want | while read f; do \
echo " stale: $$f (исходник удалён) — rm"; \
rm -f "$$f" "$${f%.rel}.asm" "$${f%.rel}.lst" "$${f%.rel}.sym"; \
done
rm -f $@
$(SDAR) -rcs $@ $(COMMON_RELS) $(BGI256_RELS)
@echo " Built $@ with $(words $(COMMON_RELS)) common + $(words $(BGI256_RELS)) bgi256 modules."
clean:
rm -rf $(BUILD) $(LIB256)
.PHONY: all clean
+197
View File
@@ -0,0 +1,197 @@
/*
* _bgi.h — внутренности графической подсистемы BGI (НЕ публичный заголовок).
*
* Слияние старых libc/bgi/_bgi.h и libc/gfx/_gfx.h. Графика вынесена из
* libc в самостоятельную библиотеку libbgi/ (см. docs + memory
* bgi_two_lib_design). Из libbgi собираются две driver-библиотеки
* (каждый .c → отдельный .rel):
* bgi256.lib ← common/ + bgi256/ (320×256×256, mode 0x81)
* bgi16.lib ← common/ + bgi16/ (640×256×16, mode 0x82)
* common/ — mode-agnostic: .rel компилируется ОДИН раз и кладётся в
* ОБА архива (один исходник для функций, общих 256- и 16-цветной
* версиям). bgi256/ и bgi16/ — mode-specific leaf'ы (реальные
* реализации, БЕЗ обёрток-делегатов). Режим выбирается линковкой
* (--gfx 256 / --gfx 16), одновременно один.
*
* Публичные leaf-символы (_bgi_plot_raw, _bgi_hspan_raw, _bgi_read_raw,
* _bgi_vspan_raw, _bgi_clear_raw, _bgi_mode_set) одинаково объявлены в
* обоих driver-каталогах, поэтому common-математика не знает режима —
* нужный .rel тянет линкер из выбранной библиотеки.
*/
#ifndef _BGI_INTERNAL_H
#define _BGI_INTERNAL_H
#include <stdint.h>
#include <graphics.h>
#include <gfx.h> /* GFX_MODE_*, размеры — для внутренних таблиц */
/* ---- Состояние BGI (_bgi_state.c) -------------------------------- */
extern uint8_t _bgi_fg; /* текущий цвет рисования */
extern uint8_t _bgi_bg; /* фоновый цвет */
extern int _bgi_cx; /* текущая позиция X (CP) */
extern int _bgi_cy; /* текущая позиция Y */
extern int _bgi_maxx; /* правая граница (319 / 639) */
extern int _bgi_maxy; /* нижняя граница (255) */
extern int _bgi_maxcolor; /* макс. индекс цвета (255 / 15) */
extern uint8_t _bgi_prevmode; /* видеорежим до initgraph */
extern int _bgi_result; /* код graphresult */
/* ---- Общее gfx-состояние (common/_gfx_state.c) ------------------ *
* Пишут gfx_set_visible_page / gfx_set_draw_page /
* gfx_set_bank; читают все mode-специфичные примитивы. */
extern uint8_t _gfx_visible_page;
extern uint8_t _gfx_draw_page;
/* Байт страницы W3 (0x50..0x5F) — какой видеобанк мапится при записи.
* См. memory/sprinter_vram_transparency.md (0x50 normal, 0x54 temp,
* 0x58 transparent, 0x5C оба). */
extern uint8_t _gfx_bank;
/* CPU-адрес колонки 0 текущей draw-страницы: 0xC000 (page 0) или
* 0xC140 (page 1); каждый примитив использует его вместо константы. */
extern uint16_t _gfx_addr_base;
/* ---- Низкоуровневый видеорежим (libc/video) ---------------------- */
uint8_t _videomode_raw_get(void);
int _videomode_raw_set(uint8_t mode);
/* ---- W3-скобки (common/_bgi_begin.c, _bgi_end.c) ----------------- *
* begin: DI + сохранить текущий W3 и замапить _gfx_bank;
* end: восстановить W3 + EI. Не реентерабельно (сейв в статике). */
extern uint8_t _gfx_saved_w3;
void _bgi_begin(void);
void _bgi_end(void);
/* ---- LEAF-операции драйвера (bgi256/ или bgi16/) ----------------- *
* Каждая self-contained: raw-варианты НЕ ставят свою W3-скобку
* (вызывающий обязан обернуть в _bgi_begin/_bgi_end); self-bracket
* (_bgi_read/_bgi_hspan/_bgi_clearall) ставят скобку сами. */
uint8_t _bgi_mode_set(void); /* войти в режим + палитра + границы */
void _bgi_plot_raw (int x, int y, uint8_t color); /* точка, без скобки */
uint8_t _bgi_read_raw (int x, int y); /* чтение, без скобки */
void _bgi_hspan_raw(int x, int y, int len, uint8_t color); /* H-отрезок, без скобки */
void _bgi_vspan_raw(int x, int y, int len, uint8_t color); /* V-отрезок, без скобки */
void _bgi_clear_raw(uint8_t color); /* заливка экрана, без скобки */
/* Self-bracket версии (begin + raw + end) — для разовых вызовов из
* математики, которой не нужен пакетный режим (floodfill, cleardevice).
* _bgi_read возвращает цвет пикселя как unsigned (в SDCC __sdcccall(1)
* 8-битный возврат идёт в A, расширяется до int). */
unsigned _bgi_read (int x, int y);
void _bgi_hspan (int x, int y, int len, uint8_t color);
void _bgi_clearall(uint8_t color);
/* ---- Скретч акселератора 256 (bgi256/_gfx_acc256.c) -------------- *
* Общий для hspan/vspan/clear; однопоточно, IRQ выключены между
* _bgi_begin/_bgi_end. */
extern uint8_t _gfx_acc_color;
extern uint8_t _gfx_acc_y;
extern uint8_t _gfx_acc_len; /* 0 = 256 — конвенция акселератора */
extern uint16_t _gfx_acc_addr;
/* Горизонтальный Fill-burst: _gfx_acc_len байт с _gfx_acc_addr на
* строке _gfx_acc_y (W3 замаплен, DI активен). */
void _gfx_hfill256_chunk(void);
/* ---- Скретч 16-цветных примитивов (bgi16/_gfx_g16_state.c) ------- */
extern uint8_t _gfx_g16_y;
extern uint8_t _gfx_g16_byte; /* nibble | (nibble<<4) */
extern uint8_t _gfx_g16_nibble; /* цвет в нужной половине байта */
extern uint8_t _gfx_g16_mask; /* маска, сохраняющая ДРУГУЮ половину */
extern uint8_t _gfx_g16_len; /* размер accel-блока (0 = 256) */
extern uint16_t _gfx_g16_addr;
/* Горизонтальный Fill-burst байтами _gfx_g16_byte (bgi16/_gfx_hfill16.c). */
void _gfx_hfill16_chunk(void);
/* RMW одного нибла по (_gfx_g16_addr, _gfx_g16_y) (bgi16/_gfx_rmw16.c). */
void _gfx_rmw16_pixel(void);
/* ---- Шрифт (common/_gfx_font_state.c, _gfx_font_ensure.c) -------- *
* Формат: 256 глифов × 8 рядов, INTERLEAVED — offset = row*256 + char;
* биты MSB-first. См. memory/sprinter_font_format. */
#define _GFX_FONT_BYTES 2048
extern uint8_t _gfx_font_buf[_GFX_FONT_BYTES];
extern const uint8_t *_gfx_font_ptr;
extern uint8_t _gfx_font_loaded;
/* Ленивая инициализация: грузит системный шрифт при первом использовании. */
void _gfx_font_ensure(void);
/* 8 байт строк глифа c (грузит шрифт при первом использовании) —
* mode-agnostic, просто читает _gfx_font_ptr. common/_bgi_font_rows.c. */
void _bgi_font_rows(unsigned char c, uint8_t *rows8);
/* ---- Тригонометрия для дуг (common/_bgi_trig.c) ------------------ *
* Угол в градусах; результат sin/cos × 128 (Q7 — чтобы радиус×значение
* влезало в int без 32-бит арифметики). */
int _bgi_sin128(int deg);
int _bgi_cos128(int deg);
/* Общий рисователь дуги/контура эллипса (common/_bgi_arc_draw.c):
* полилиния по точкам от stangle до endangle с полуосями xr,yr вокруг
* (cx,cy). */
void _bgi_ellipse_arc(int cx, int cy, int stangle, int endangle,
int xr, int yr, uint8_t color);
/* Целочисленный sqrt (0..~32767) для fillellipse (common/_bgi_isqrt.c). */
int _bgi_isqrt(int n);
/* ---- Состояние линий (common/_bgi_line_state.c) ------------------ */
extern uint8_t _bgi_line_style; /* SOLID_LINE..USERBIT_LINE */
extern uint8_t _bgi_line_thick; /* 1 (NORM) или 3 (THICK) */
extern uint16_t _bgi_line_pattern; /* маска для USERBIT_LINE */
/* Отрезок текущим стилем/толщиной линии (common/_bgi_styled_line.c). */
void _bgi_styled_line(int x0, int y0, int x1, int y1, uint8_t color);
/* Отрезок (x0,y0)->(x1,y1) БЕЗ стиля: ортогонал → hspan/vspan,
* диагональ → Брезенхэм over _bgi_plot_raw, своя W3-скобка
* (common/_bgi_lineseg.c). Реальная логика, не обёртка. */
void _bgi_lineseg(int x0, int y0, int x1, int y1, uint8_t color);
/* ---- Состояние заливки (common/_bgi_fill_state.c) ---------------- */
extern uint8_t _bgi_fill_pattern; /* *_FILL */
extern uint8_t _bgi_fill_color;
/* Залить строку [x0..x1] на y ТЕКУЩИМ стилем заливки (паттерн+цвет),
* с клипом по экрану. Вызывать между _bgi_begin/_bgi_end. */
void _bgi_fill_span(int x0, int x1, int y);
/* ---- Стиль текста (common/_bgi_text_state.c) --------------------- */
extern uint8_t _bgi_text_dir; /* HORIZ_DIR / VERT_DIR */
extern uint8_t _bgi_text_size; /* масштаб 1..10 */
/* Нарисовать строку s в (x,y) текущим стилем текста (масштаб/направление,
* цвет _bgi_fg, прозрачный фон) — common/_bgi_draw_scaled.c. */
void _bgi_draw_scaled(int x, int y, const char *s);
/* ---- Скан-таблицы min/max X по строкам для fillpoly -------------- *
* (common/_bgi_poly_state.c). GFX_HEIGHT — высота экрана (256 в обоих
* режимах), см. <gfx.h>. */
extern int _bgi_span_min[GFX_HEIGHT];
extern int _bgi_span_max[GFX_HEIGHT];
/* Прогнать ребро (Брезенхэм) обновляя _bgi_span_min/max
* (common/_bgi_poly_edge.c). */
void _bgi_poly_edge(int x0, int y0, int x1, int y1);
/* ---- Cross-library: внутренние символы libc/cbl ------------------ *
* libbgi ВСЕГДА линкуется вместе с libc (sprinter.lib), поэтому
* зависимость от внутренних символов libc допустима. gfx_wait_vsync
* держит bit7 порта 0x004E через рефкаунт _cbl_port_ref/unref, чтобы
* бит 5 порта 0xFE (позиция луча) был доступен.
* ВНИМАНИЕ: это ВНУТРЕННИЕ символы libc/cbl — при изменении сигнатур
* СВЕРЯТЬ с libc/cbl/_cbl.h и синхронизировать. (По тому же паттерну,
* каким здесь живут _videomode_raw_* из libc/video.) */
extern void _cbl_port_ref(void);
extern void _cbl_port_unref(void);
#endif
@@ -1,11 +1,11 @@
/*
* _gfx_clear16_raw залить всё поле 640×256 цветом color, mode 0x82.
* _bgi_clear_raw залить всё поле 640×256 цветом color, mode 0x82.
* Построчный hfill: 256 строк × 2 burst'а (256 + 64 байта). W3-naive.
*/
#include "_gfx.h"
#include "../_bgi.h"
void _gfx_clear16_raw(uint8_t color)
void _bgi_clear_raw(uint8_t color)
{
_gfx_g16_byte = (uint8_t)((color & 0x0F) | ((color & 0x0F) << 4));
for (int y = 0; y < GFX_HEIGHT_16; y++) {
@@ -1,12 +1,12 @@
/*
* _gfx_hline16_raw горизонтальная линия len пикселей от (x,y),
* _bgi_hspan_raw горизонтальная линия len пикселей от (x,y),
* mode 0x82: невыровненные крайние ниблы через RMW, середина
* Fill-burst'ами акселератора по целым байтам. W3-naive.
*/
#include "_gfx.h"
#include "../_bgi.h"
void _gfx_hline16_raw(int x, int y, int len, uint8_t color)
void _bgi_hspan_raw(int x, int y, int len, uint8_t color)
{
if ((unsigned)y >= GFX_HEIGHT_16) return;
if (x < 0) { len += x; x = 0; }
@@ -1,12 +1,12 @@
/*
* _gfx_putpixel16_raw пиксель (x,y) цветом 0..15, mode 0x82.
* _bgi_plot_raw пиксель (x,y) цветом 0..15, mode 0x82.
* Байт по адресу _gfx_addr_base + x/2 держит два пикселя: high nibble =
* ЛЕВЫЙ (чётный x), low = ПРАВЫЙ (нечётный). W3-naive.
*/
#include "_gfx.h"
#include "../_bgi.h"
void _gfx_putpixel16_raw(int x, int y, uint8_t color)
void _bgi_plot_raw(int x, int y, uint8_t color)
{
if ((unsigned)x >= GFX_WIDTH_16 || (unsigned)y >= GFX_HEIGHT_16) return;
_gfx_g16_y = (uint8_t)y;
@@ -1,13 +1,13 @@
/*
* _gfx_vline16_raw вертикальная линия len пикселей от (x,y),
* _bgi_vspan_raw вертикальная линия len пикселей от (x,y),
* mode 0x82. Вертикального accel-пути нет: байт покрывает два
* горизонтальных пикселя, Fill затёр бы соседний нибл поэтому
* RMW на каждую строку. W3-naive.
*/
#include "_gfx.h"
#include "../_bgi.h"
void _gfx_vline16_raw(int x, int y, int len, uint8_t color)
void _bgi_vspan_raw(int x, int y, int len, uint8_t color)
{
if ((unsigned)x >= GFX_WIDTH_16) return;
if (y < 0) { len += y; y = 0; }
@@ -4,7 +4,7 @@
* между W3-begin/end.
*/
#include "_gfx.h"
#include "../_bgi.h"
uint8_t _gfx_g16_y;
uint8_t _gfx_g16_byte; /* nibble | (nibble<<4) — цвет в обе половины */
@@ -10,7 +10,7 @@
* как новый размер блока.
*/
#include "_gfx.h"
#include "../_bgi.h"
void _gfx_hfill16_chunk(void) __naked
{
@@ -6,7 +6,7 @@
* Pre: W3 замаплен, DI активен.
*/
#include "_gfx.h"
#include "../_bgi.h"
void _gfx_rmw16_pixel(void) __naked
{
@@ -1,12 +1,12 @@
/*
* _gfx_clear256_raw залить всё поле 320×256 цветом color, mode 0x81.
* _bgi_clear_raw залить всё поле 320×256 цветом color, mode 0x81.
* Построчный hfill: 256 строк × 2 burst'а (256 + 64 байта) = 512.
* W3-naive: вызывающий обрамляет begin/end.
*/
#include "_gfx.h"
#include "../_bgi.h"
void _gfx_clear256_raw(uint8_t color)
void _bgi_clear_raw(uint8_t color)
{
_gfx_acc_color = color;
for (int y = 0; y < GFX_HEIGHT; y++) {
@@ -1,12 +1,12 @@
/*
* _gfx_hline256_raw горизонтальная линия len пикселей от (x,y),
* _bgi_hspan_raw горизонтальная линия len пикселей от (x,y),
* mode 0x81, через Fill-burst'ы акселератора (до 256 байт за выстрел).
* W3-naive: вызывающий обрамляет begin/end.
*/
#include "_gfx.h"
#include "../_bgi.h"
void _gfx_hline256_raw(int x, int y, int len, uint8_t color)
void _bgi_hspan_raw(int x, int y, int len, uint8_t color)
{
if ((unsigned)y >= GFX_HEIGHT) return;
if (x < 0) { len += x; x = 0; }
+44
View File
@@ -0,0 +1,44 @@
/*
* _bgi_mode_set (drv256) — вход в графический режим 320×256×256.
*
* Загружает EGA-совместимые цвета 0..15 в палитру 0, чтобы setcolor(RED)
* и прочие BGI-константы давали ожидаемый цвет. Заполняет границы
* экрана в состоянии BGI. Возвращает предыдущий видеорежим.
*/
#include "../_bgi.h"
#include <gfx.h>
/* 16 EGA-цветов, формат записи палитры (B, G, R, pad). */
static const uint8_t _bgi_ega_pal[16 * 4] = {
0, 0, 0, 0, /* 0 BLACK */
168, 0, 0, 0, /* 1 BLUE */
0, 168, 0, 0, /* 2 GREEN */
168, 168, 0, 0, /* 3 CYAN */
0, 0, 168, 0, /* 4 RED */
168, 0, 168, 0, /* 5 MAGENTA */
0, 84, 168, 0, /* 6 BROWN */
168, 168, 168, 0, /* 7 LIGHTGRAY */
84, 84, 84, 0, /* 8 DARKGRAY */
255, 84, 84, 0, /* 9 LIGHTBLUE */
84, 255, 84, 0, /* 10 LIGHTGREEN */
255, 255, 84, 0, /* 11 LIGHTCYAN */
84, 84, 255, 0, /* 12 LIGHTRED */
255, 84, 255, 0, /* 13 LIGHTMAGENTA */
84, 255, 255, 0, /* 14 YELLOW */
255, 255, 255, 0, /* 15 WHITE */
};
uint8_t _bgi_mode_set(void)
{
uint8_t prev = _videomode_raw_get();
_videomode_raw_set(GFX_MODE_320x256x256);
_gfx_bank = 0x50;
gfx_set_visible_page(0);
gfx_set_draw_page(0);
gfx_pal_load(0, 0, 16, _bgi_ega_pal);
_bgi_maxx = GFX_WIDTH - 1;
_bgi_maxy = GFX_HEIGHT - 1;
_bgi_maxcolor = 255;
return prev;
}
@@ -1,5 +1,5 @@
/*
* _gfx_putpixel256_raw пиксель (x,y) цветом color, mode 0x81.
* _bgi_plot_raw пиксель (x,y) цветом color, mode 0x81.
*
* "Raw" = W3-naive: вызывающий уже сделал _gfx_w3_video_begin (W3 на
* видеобанке, DI). Адресация: CPU _gfx_addr_base + x, Port_Y (0x89) = y.
@@ -8,13 +8,13 @@
* Брезенхэма не топтал внешнее состояние акселератора.
*/
#include "_gfx.h"
#include "../_bgi.h"
static uint8_t _gfx_pp_y;
static uint16_t _gfx_pp_addr;
static uint8_t _gfx_pp_color;
void _gfx_putpixel256_raw(int x, int y, uint8_t color)
void _bgi_plot_raw(int x, int y, uint8_t color)
{
if ((unsigned)x >= GFX_WIDTH || (unsigned)y >= GFX_HEIGHT) return;
_gfx_pp_y = (uint8_t)y;
+27
View File
@@ -0,0 +1,27 @@
/*
* _bgi_read_raw — прочитать пиксель (x,y) БЕЗ своей W3-скобки
* (вызывающий уже сделал _gfx_w3_video_begin). Для блиттинга image:
* getimage/putimage читают тысячи пикселей в одной скобке.
* За пределами экрана возвращает 0.
*/
#include "../_bgi.h"
static uint8_t _gfx_gpr_y;
static uint16_t _gfx_gpr_addr;
static uint8_t _gfx_gpr_color;
uint8_t _bgi_read_raw(int x, int y)
{
if ((unsigned)x >= GFX_WIDTH || (unsigned)y >= GFX_HEIGHT) return 0;
_gfx_gpr_y = (uint8_t)y;
_gfx_gpr_addr = (uint16_t)(_gfx_addr_base + (unsigned)x);
__asm
ld a, (__gfx_gpr_y)
out (#0x89), a
ld hl, (__gfx_gpr_addr)
ld a, (hl)
ld (__gfx_gpr_color), a
__endasm;
return _gfx_gpr_color;
}
@@ -1,10 +1,10 @@
/*
* _gfx_vline256_raw вертикальная линия len пикселей от (x,y),
* _bgi_vspan_raw вертикальная линия len пикселей от (x,y),
* mode 0x81. Вертикальный Fill акселератора сам инкрементирует Port_Y,
* так что вся колонка (высота 256) один burst. W3-naive.
*/
#include "_gfx.h"
#include "../_bgi.h"
/* Вертикальный Fill: _gfx_acc_len пикселей в колонке _gfx_acc_addr,
* верхняя строка _gfx_acc_y. Про SMC и правила FSM акселератора
@@ -33,7 +33,7 @@ static void vfill_chunk(void) __naked
__endasm;
}
void _gfx_vline256_raw(int x, int y, int len, uint8_t color)
void _bgi_vspan_raw(int x, int y, int len, uint8_t color)
{
if ((unsigned)x >= GFX_WIDTH) return;
if (y < 0) { len += y; y = 0; }
@@ -4,7 +4,7 @@
* GFX работает с выключенными прерываниями между W3-begin/end.
*/
#include "_gfx.h"
#include "../_bgi.h"
uint8_t _gfx_acc_color;
uint8_t _gfx_acc_y;
@@ -15,7 +15,7 @@
* как новый размер блока.
*/
#include "_gfx.h"
#include "../_bgi.h"
void _gfx_hfill256_chunk(void) __naked
{
+33
View File
@@ -0,0 +1,33 @@
/*
* getpixel (bgi256) — цвет пикселя (x,y), mode 0x81. Полностью inline
* (bounds + W3-скобка + чтение пикселя), без вызовов _bgi_read/raw.
* Возвращает 0 вне экрана.
*/
#include "../_bgi.h"
static uint8_t _gp_y;
static uint16_t _gp_addr;
static uint8_t _gp_color;
unsigned getpixel(int x, int y)
{
if ((unsigned)x >= GFX_WIDTH || (unsigned)y >= GFX_HEIGHT) return 0;
_gp_y = (uint8_t)y;
_gp_addr = (uint16_t)(_gfx_addr_base + (unsigned)x);
__asm
di
in a, (#0xE2)
ld (__gfx_saved_w3), a
ld a, (__gfx_bank)
out (#0xE2), a
ld a, (_gp_y)
out (#0x89), a
ld hl, (_gp_addr)
ld a, (hl)
ld (_gp_color), a
ld a, (__gfx_saved_w3)
out (#0xE2), a
ei
__endasm;
return _gp_color;
}
+39
View File
@@ -0,0 +1,39 @@
/*
* putpixel (bgi256) — точка (x,y) цветом color, mode 0x81.
* ПОЛНОСТЬЬЮ inline: bounds + W3-скобка + запись пикселя в теле, БЕЗ
* вызовов _bgi_plot / gfx_putpixel256 / _bgi_plot_raw (устранены
* прокладки старой цепочки putpixel→_bgi_plot→gfx_putpixel256→raw).
* Адресация: CPU _gfx_addr_base + x, Port_Y (0x89) = y; W3 мапится на
* _gfx_bank, сейв восстанавливается. Bounds — compile-time GFX_WIDTH/
* HEIGHT (меньше/быстрее, ≡ runtime _bgi_maxx/_bgi_maxy).
*/
#include "../_bgi.h"
/* File-static скретч (как в _bgi_plot_raw, но свой — putpixel не должен
* топтать скретч акселератора/plot_raw, если звать вперемешку). */
static uint8_t _pp_y;
static uint16_t _pp_addr;
static uint8_t _pp_color;
void putpixel(int x, int y, int color)
{
if ((unsigned)x >= GFX_WIDTH || (unsigned)y >= GFX_HEIGHT) return;
_pp_y = (uint8_t)y;
_pp_addr = (uint16_t)(_gfx_addr_base + (unsigned)x);
_pp_color = (uint8_t)color;
__asm
di
in a, (#0xE2)
ld (__gfx_saved_w3), a
ld a, (__gfx_bank)
out (#0xE2), a
ld a, (_pp_y)
out (#0x89), a
ld hl, (_pp_addr)
ld a, (_pp_color)
ld (hl), a
ld a, (__gfx_saved_w3)
out (#0xE2), a
ei
__endasm;
}
+33
View File
@@ -0,0 +1,33 @@
/*
* _bgi_ellipse_arc — контур дуги/эллипса полилинией.
*
* Точки параметризованы углом (BGI: 0°=восток, против часовой, ось Y
* вниз): px = cx + xr*cos(a), py = cy - yr*sin(a). Соседние выборки
* соединяются отрезками — контур сплошной при любом радиусе.
*
* Тригонометрия в Q7 (×128): xr*cos128 ≤ 255×128 < 32767, помещается в
* int — БЕЗ 32-битной арифметики (её SDCC/Z80 собирает криво: раньше
* эллипс через (long)…>>8 рисовался прямоугольником).
*/
#include "../_bgi.h"
void _bgi_ellipse_arc(int cx, int cy, int stangle, int endangle,
int xr, int yr, uint8_t color)
{
int a, x, y, px, py, first;
if (endangle < stangle) endangle += 360; /* дуга через 0° */
px = 0;
py = 0;
first = 1;
for (a = stangle; a <= endangle; a++) {
x = cx + ((xr * _bgi_cos128(a)) >> 7);
y = cy - ((yr * _bgi_sin128(a)) >> 7);
if (!first) _bgi_lineseg(px, py, x, y, color);
px = x;
py = y;
first = 0;
}
}
+24
View File
@@ -0,0 +1,24 @@
/*
* _bgi_begin — открыть W3-сессию для пакетного рисования: DI, сохранить
* текущую страницу W3 и замапить _gfx_bank (видеобанк 0x50..0x5F).
* Композитные примитивы обрамляют одной парой _bgi_begin/_bgi_end весь
* внутренний цикл — W3-танец оплачивается один раз на операцию, а не
* на пиксель. НЕ реентерабельно (сейв в _gfx_saved_w3): между begin и
* end IRQ выключены.
*
* Real код (поглощён из старого libc/gfx/_gfx_w3_video_begin.c) — не
* обёртка. Mode-agnostic: использует только _gfx_bank/_gfx_saved_w3.
*/
#include "../_bgi.h"
void _bgi_begin(void) __naked
{
__asm
di
in a, (#0xE2)
ld (__gfx_saved_w3), a
ld a, (__gfx_bank)
out (#0xE2), a
ret
__endasm;
}
+13
View File
@@ -0,0 +1,13 @@
/*
* _bgi_clearall — залить весь экран цветом, self-bracket (своя W3-скобка).
* cleardevice зовёт это фоновым цветом. Реальная композиция
* _bgi_begin + _bgi_clear_raw + _bgi_end.
*/
#include "../_bgi.h"
void _bgi_clearall(uint8_t color)
{
_bgi_begin();
_bgi_clear_raw(color);
_bgi_end();
}
+48
View File
@@ -0,0 +1,48 @@
/*
* _bgi_draw_scaled — строка растровым шрифтом 8×8 с целочисленным
* масштабом и направлением, цветом _bgi_fg, прозрачным фоном.
*
* HORIZ_DIR: пиксель глифа (c,r) → экран (x+c*size, y+r*size).
* VERT_DIR (поворот 90° против часовой): (c,r) → (x+r*size, y-c*size).
* Каждый «пиксель» глифа — блок size×size из raw-плотов, всё в одной
* W3-скобке.
*/
#include "../_bgi.h"
void _bgi_draw_scaled(int x, int y, const char *s)
{
uint8_t rows[8];
uint8_t b, fg, size, dir;
int r, c, bx, by, px, py;
fg = _bgi_fg;
size = _bgi_text_size ? _bgi_text_size : 1;
dir = _bgi_text_dir;
_bgi_begin();
while (*s) {
_bgi_font_rows((unsigned char)*s, rows);
for (r = 0; r < 8; r++) {
b = rows[r];
for (c = 0; c < 8; c++) {
if (!((b >> (7 - c)) & 1)) continue;
for (by = 0; by < size; by++) {
for (bx = 0; bx < size; bx++) {
if (dir == VERT_DIR) {
px = x + r * size + by;
py = y - c * size - bx;
} else {
px = x + c * size + bx;
py = y + r * size + by;
}
_bgi_plot_raw(px, py, fg);
}
}
}
}
if (dir == VERT_DIR) y -= 8 * size;
else x += 8 * size;
s++;
}
_bgi_end();
}
+15
View File
@@ -0,0 +1,15 @@
/*
* _bgi_end — закрыть W3-сессию: восстановить сохранённую страницу W3 и
* EI. Real код (поглощён из старого libc/gfx/_gfx_w3_video_end.c).
*/
#include "../_bgi.h"
void _bgi_end(void) __naked
{
__asm
ld a, (__gfx_saved_w3)
out (#0xE2), a
ei
ret
__endasm;
}
+47
View File
@@ -0,0 +1,47 @@
/*
* _bgi_fill_span — залить строку [x0..x1] на y текущим стилем заливки.
*
* SOLID → быстрый raw-hline; EMPTY → hline фоновым цветом; паттерн →
* поточечно по 8×8-битовой маске (строка паттерна = pat[y&7], бит x&7).
* Клип по экрану внутри. ВЫЗЫВАТЬ между _bgi_begin/_bgi_end.
*/
#include "../_bgi.h"
/* Стандартные 8×8-паттерны Borland, индексы LINE_FILL..CLOSE_DOT_FILL. */
static const uint8_t _bgi_pat[10][8] = {
{ 0xFF,0xFF,0x00,0x00,0xFF,0xFF,0x00,0x00 }, /* LINE_FILL */
{ 0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80 }, /* LTSLASH_FILL */
{ 0xE0,0xC1,0x83,0x07,0x0E,0x1C,0x38,0x70 }, /* SLASH_FILL */
{ 0xF0,0x78,0x3C,0x1E,0x0F,0x87,0xC3,0xE1 }, /* BKSLASH_FILL */
{ 0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01 }, /* LTBKSLASH_FILL */
{ 0xFF,0x88,0x88,0x88,0xFF,0x88,0x88,0x88 }, /* HATCH_FILL */
{ 0x81,0x42,0x24,0x18,0x18,0x24,0x42,0x81 }, /* XHATCH_FILL */
{ 0xCC,0x33,0xCC,0x33,0xCC,0x33,0xCC,0x33 }, /* INTERLEAVE_FILL */
{ 0x80,0x00,0x08,0x00,0x80,0x00,0x08,0x00 }, /* WIDE_DOT_FILL */
{ 0x88,0x00,0x22,0x00,0x88,0x00,0x22,0x00 }, /* CLOSE_DOT_FILL */
};
void _bgi_fill_span(int x0, int x1, int y)
{
int x;
uint8_t p, row;
if (x1 < x0) { x = x0; x0 = x1; x1 = x; }
if (y < 0 || y > _bgi_maxy) return;
if (x0 < 0) x0 = 0;
if (x1 > _bgi_maxx) x1 = _bgi_maxx;
if (x1 < x0) return;
p = _bgi_fill_pattern;
if (p == EMPTY_FILL) { _bgi_hspan_raw(x0, y, x1 - x0 + 1, _bgi_bg); return; }
if (p == SOLID_FILL || p == USER_FILL || p > CLOSE_DOT_FILL) {
_bgi_hspan_raw(x0, y, x1 - x0 + 1, _bgi_fill_color);
return;
}
/* Паттерн LINE_FILL..CLOSE_DOT_FILL. */
row = _bgi_pat[p - LINE_FILL][y & 7];
for (x = x0; x <= x1; x++)
if ((row >> (x & 7)) & 1)
_bgi_plot_raw(x, y, _bgi_fill_color);
}
+10
View File
@@ -0,0 +1,10 @@
/*
* _bgi_fill_state — текущий стиль заливки BGI (паттерн + цвет).
* Значения по умолчанию выставляет initgraph (SOLID_FILL/WHITE).
* Без инициализации (crt0 зануляет _DATA).
*/
#include "../_bgi.h"
uint8_t _bgi_fill_pattern;
uint8_t _bgi_fill_color;
+20
View File
@@ -0,0 +1,20 @@
/*
* _bgi_font_rows — 8 байт строк глифа символа c (mode-agnostic: просто
* читает шрифт через _gfx_font_ptr). Шрифт системный, interleaved:
* offset = row*256 + char (MSB-first), см. memory/sprinter_font_format.
* Грузится лениво (_gfx_font_ensure) при первом использовании.
*
* Раньше жил в drv256/ по недосмотру — перенесён в common/, т.к. логика
* не зависит от видеорежима (один и тот же .rel в bgi256.lib и bgi16.lib).
*/
#include "../_bgi.h"
void _bgi_font_rows(unsigned char c, uint8_t *rows8)
{
const uint8_t *f;
int r;
_gfx_font_ensure();
f = _gfx_font_ptr;
for (r = 0; r < 8; r++)
rows8[r] = f[r * 256 + c];
}
+13
View File
@@ -0,0 +1,13 @@
/*
* _bgi_hspan — горизонтальный отрезок len пикселей от (x,y), self-bracket
* (своя W3-скобка). Для floodfill (заливка строки вне пакетной сессии).
* Реальная композиция _bgi_begin + _bgi_hspan_raw + _bgi_end.
*/
#include "../_bgi.h"
void _bgi_hspan(int x, int y, int len, uint8_t color)
{
_bgi_begin();
_bgi_hspan_raw(x, y, len, color);
_bgi_end();
}
+15
View File
@@ -0,0 +1,15 @@
/*
* _bgi_isqrt — целочисленный floor(sqrt(n)) для n в 0..~32767.
* Линейный поиск (n≤180² → ≤180 шагов) — для fillellipse достаточно,
* без float и 32-бит.
*/
#include "../_bgi.h"
int _bgi_isqrt(int n)
{
int x = 0;
if (n < 0) return 0;
while ((x + 1) * (x + 1) <= n) x++;
return x;
}
+9
View File
@@ -0,0 +1,9 @@
/*
* _bgi_line_state — текущий стиль/толщина/маска линий BGI.
* initgraph ставит SOLID_LINE / NORM / 0xFFFF. Без инициализации.
*/
#include "../_bgi.h"
uint8_t _bgi_line_style;
uint8_t _bgi_line_thick;
uint16_t _bgi_line_pattern;
+49
View File
@@ -0,0 +1,49 @@
/*
* _bgi_lineseg — отрезок (x0,y0)->(x1,y1) БЕЗ стиля/толщины (SOLID+NORM).
* Real логика (поглощена из старого libc/gfx/gfx_line256.c), не обёртка.
*
* Ортогональные отрезки уходят на driver-leaf'ы _bgi_hspan_raw /
* _bgi_vspan_raw (акселератор); наклонные — Брезенхэм поточечно через
* _bgi_plot_raw. Каждый путь ставит свою W3-скобку (_bgi_begin/_bgi_end)
* — вызывать _bgi_lineseg ВНЕ чужой скобки (W3-сейв не реентерабелен).
* Вызывают: _bgi_styled_line (SOLID+NORM), _bgi_arc_draw, fillpoly,
* sector — все вне скобки.
*/
#include "../_bgi.h"
void _bgi_lineseg(int x0, int y0, int x1, int y1, uint8_t color)
{
/* Горизонталь — один H-burst. */
if (y0 == y1) {
int x = x0 <= x1 ? x0 : x1;
int w = (x0 <= x1 ? x1 - x0 : x0 - x1) + 1;
_bgi_begin();
_bgi_hspan_raw(x, y0, w, color);
_bgi_end();
return;
}
/* Вертикаль — один V-burst (256) или RMW-цикл (16). */
if (x0 == x1) {
int y = y0 <= y1 ? y0 : y1;
int h = (y0 <= y1 ? y1 - y0 : y0 - y1) + 1;
_bgi_begin();
_bgi_vspan_raw(x0, y, h, color);
_bgi_end();
return;
}
/* Наклонный — Брезенхэм в одной W3-скобке. */
int dx = x1 - x0; int sx = dx < 0 ? -1 : 1; if (dx < 0) dx = -dx;
int dy = y1 - y0; int sy = dy < 0 ? -1 : 1; if (dy < 0) dy = -dy;
int err = (dx > dy ? dx : -dy) / 2;
int x = x0, y = y0;
_bgi_begin();
for (;;) {
_bgi_plot_raw(x, y, color);
if (x == x1 && y == y1) break;
int e2 = err;
if (e2 > -dx) { err -= dy; x += sx; }
if (e2 < dy) { err += dx; y += sy; }
}
_bgi_end();
}
+30
View File
@@ -0,0 +1,30 @@
/*
* _bgi_poly_edge — прогнать ребро (x0,y0)->(x1,y1) по Брезенхэму,
* обновляя _bgi_span_min/_bgi_span_max для каждой затронутой строки.
* Только целочисленные сложения (без 32-бит). Строки вне экрана
* пропускаются. Заливка потом идёт от min до max по строке.
*/
#include "../_bgi.h"
void _bgi_poly_edge(int x0, int y0, int x1, int y1)
{
int dx, dy, sx, sy, err, e2;
dx = x1 - x0; if (dx < 0) dx = -dx;
dy = y1 - y0; if (dy < 0) dy = -dy;
sx = (x0 < x1) ? 1 : -1;
sy = (y0 < y1) ? 1 : -1;
err = dx - dy;
for (;;) {
if (y0 >= 0 && y0 < GFX_HEIGHT) {
if (x0 < _bgi_span_min[y0]) _bgi_span_min[y0] = x0;
if (x0 > _bgi_span_max[y0]) _bgi_span_max[y0] = x0;
}
if (x0 == x1 && y0 == y1) break;
e2 = 2 * err;
if (e2 > -dy) { err -= dy; x0 += sx; }
if (e2 < dx) { err += dx; y0 += sy; }
}
}
+11
View File
@@ -0,0 +1,11 @@
/*
* _bgi_poly_state — скан-таблицы min/max X по строкам для fillpoly.
* По строке экрана хранит крайние левый/правый X контура — заливка
* идёт от min до max (выпуклая оболочка строки). ~1 КБ в _DATA,
* однопоточно (рисование не реентерабельно).
*/
#include "../_bgi.h"
int _bgi_span_min[GFX_HEIGHT];
int _bgi_span_max[GFX_HEIGHT];
+16
View File
@@ -0,0 +1,16 @@
/*
* _bgi_read — цвет пикселя (x,y), self-bracket (своя W3-скобка).
* Для разовых чтений из математики, которой не нужен пакетный режим
* (floodfill). getpixel тоже зовёт это. Реальная композиция
* _bgi_begin + _bgi_read_raw + _bgi_end — не обёртка над обёрткой.
*/
#include "../_bgi.h"
unsigned _bgi_read(int x, int y)
{
unsigned c;
_bgi_begin();
c = _bgi_read_raw(x, y);
_bgi_end();
return c;
}
+19
View File
@@ -0,0 +1,19 @@
/*
* _bgi_state.c — общее состояние BGI-слоя.
*
* File-scope переменные НЕ инициализируются (crt0 зануляет _DATA;
* см. memory/sdcc_static_storage_gotcha). Осмысленные значения
* выставляет initgraph().
*/
#include "../_bgi.h"
uint8_t _bgi_fg;
uint8_t _bgi_bg;
int _bgi_cx;
int _bgi_cy;
int _bgi_maxx;
int _bgi_maxy;
int _bgi_maxcolor;
uint8_t _bgi_prevmode;
int _bgi_result;
+58
View File
@@ -0,0 +1,58 @@
/*
* _bgi_styled_line — отрезок текущим стилем и толщиной линии.
*
* SOLID+NORM → быстрый _bgi_lineseg (accel). Иначе — Брезенхэм с
* 16-битной маской (бит на шаг; когда бит 0 — пропуск) и, для THICK,
* дублированием перпендикулярно основной оси (±1). Всё raw в одной
* W3-скобке.
*/
#include "../_bgi.h"
/* Маски стандартных стилей (SOLID/DOTTED/CENTER/DASHED). */
static const uint16_t _bgi_line_pat[4] = { 0xFFFF, 0xCCCC, 0xFF18, 0xF8F8 };
void _bgi_styled_line(int x0, int y0, int x1, int y1, uint8_t color)
{
uint16_t pat;
uint8_t bit, style, thick;
int dx, dy, sx, sy, err, e2, major_x;
style = _bgi_line_style;
thick = _bgi_line_thick;
if (style == SOLID_LINE && thick <= 1) {
_bgi_lineseg(x0, y0, x1, y1, color);
return;
}
pat = (style == USERBIT_LINE) ? _bgi_line_pattern
: _bgi_line_pat[style & 3];
dx = x1 - x0; if (dx < 0) dx = -dx;
dy = y1 - y0; if (dy < 0) dy = -dy;
sx = (x0 < x1) ? 1 : -1;
sy = (y0 < y1) ? 1 : -1;
err = dx - dy;
major_x = (dx >= dy);
bit = 0;
_bgi_begin();
for (;;) {
if ((pat >> (bit & 15)) & 1) {
_bgi_plot_raw(x0, y0, color);
if (thick > 1) {
if (major_x) {
_bgi_plot_raw(x0, y0 - 1, color);
_bgi_plot_raw(x0, y0 + 1, color);
} else {
_bgi_plot_raw(x0 - 1, y0, color);
_bgi_plot_raw(x0 + 1, y0, color);
}
}
}
bit++;
if (x0 == x1 && y0 == y1) break;
e2 = 2 * err;
if (e2 > -dy) { err -= dy; x0 += sx; }
if (e2 < dx) { err += dx; y0 += sy; }
}
_bgi_end();
}
+8
View File
@@ -0,0 +1,8 @@
/*
* _bgi_text_state — направление и масштаб текста BGI.
* initgraph ставит HORIZ_DIR / size 1. Без инициализации.
*/
#include "../_bgi.h"
uint8_t _bgi_text_dir;
uint8_t _bgi_text_size;
+43
View File
@@ -0,0 +1,43 @@
/*
* _bgi_trig — целочисленная тригонометрия для дуг/эллипсов BGI.
*
* Угол в градусах (Turbo-C); sin/cos масштабированы на 128 (Q7):
* _bgi_sin128(90) = 128. Масштаб 128 (а не 256) выбран так, чтобы
* произведение радиус×значение помещалось в 16-бит int для любого
* экранного радиуса (255×128 = 32640 < 32767) — и НЕ приходилось
* трогать 32-битную арифметику, которую SDCC/Z80 собирает криво
* (см. memory/avoid_32bit_arith_z80; ловили эллипс-в-прямоугольник).
*/
#include "../_bgi.h"
/* sin(0°..90°) × 128. */
static const int _bgi_sintab[91] = {
0, 2, 4, 7, 9, 11, 13, 16, 18, 20,
22, 24, 27, 29, 31, 33, 35, 37, 40, 42,
44, 46, 48, 50, 52, 54, 56, 58, 60, 62,
64, 66, 68, 70, 72, 73, 75, 77, 79, 81,
82, 84, 86, 87, 89, 91, 92, 94, 95, 97,
98, 99, 101, 102, 104, 105, 106, 107, 109, 110,
111, 112, 113, 114, 115, 116, 117, 118, 119, 119,
120, 121, 122, 122, 123, 124, 124, 125, 125, 126,
126, 126, 127, 127, 127, 128, 128, 128, 128, 128,
128,
};
/* sin(угол°) × 128, угол приводится к 0..359. */
int _bgi_sin128(int deg)
{
deg %= 360;
if (deg < 0) deg += 360;
if (deg <= 90) return _bgi_sintab[deg];
if (deg <= 180) return _bgi_sintab[180 - deg];
if (deg <= 270) return -_bgi_sintab[deg - 180];
return -_bgi_sintab[360 - deg];
}
/* cos(угол°) × 128 = sin(угол+90). */
int _bgi_cos128(int deg)
{
return _bgi_sin128(deg + 90);
}
@@ -4,7 +4,7 @@
* использовании (чисто графическая программа BIOS-вызов не платит).
*/
#include "_gfx.h"
#include "../_bgi.h"
void _gfx_font_ensure(void)
{
@@ -9,7 +9,7 @@
* _gfx_font_ensure(); программы могут подменить шрифт gfx_set_font().
*/
#include "_gfx.h"
#include "../_bgi.h"
uint8_t _gfx_font_buf[_GFX_FONT_BYTES];
const uint8_t *_gfx_font_ptr = _gfx_font_buf;
@@ -1,11 +1,11 @@
/*
* _gfx_state общее состояние графики (модуль только с данными).
*
* Пишут gfx_init / gfx_set_visible_page / gfx_set_draw_page /
* Пишут gfx_set_visible_page / gfx_set_draw_page /
* gfx_set_bank; читают все mode-специфичные примитивы.
*/
#include "_gfx.h"
#include "../_bgi.h"
uint8_t _gfx_visible_page;
uint8_t _gfx_draw_page;
@@ -5,6 +5,6 @@
* прерывания выключенными между _gfx_w3_video_begin и _end.
*/
#include "_gfx.h"
#include "../_bgi.h"
uint8_t _gfx_saved_w3;
+7
View File
@@ -0,0 +1,7 @@
/* arc — дуга окружности радиуса r от stangle до endangle (градусы). */
#include "../_bgi.h"
void arc(int x, int y, int stangle, int endangle, int radius)
{
_bgi_ellipse_arc(x, y, stangle, endangle, radius, radius, _bgi_fg);
}
+18
View File
@@ -0,0 +1,18 @@
/*
* bar — залитый прямоугольник (left,top)-(right,bottom) включительно
* ТЕКУЩИМ стилем заливки (setfillstyle), без рамки — как в Turbo-C.
*/
#include "../_bgi.h"
void bar(int left, int top, int right, int bottom)
{
int y, t;
if (right < left) { t = left; left = right; right = t; }
if (bottom < top) { t = top; top = bottom; bottom = t; }
_bgi_begin();
for (y = top; y <= bottom; y++)
_bgi_fill_span(left, right, y);
_bgi_end();
}
+27
View File
@@ -0,0 +1,27 @@
/*
* bar3d — трёхмерный брусок: передняя грань залита текущим стилем
* заливки, рёбра — текущим цветом рисования. depth — глубина (вправо-
* вверх), topflag != 0 рисует верхнюю грань.
*/
#include "../_bgi.h"
void bar3d(int left, int top, int right, int bottom, int depth, int topflag)
{
int t;
if (right < left) { t = left; left = right; right = t; }
if (bottom < top) { t = top; top = bottom; bottom = t; }
bar(left, top, right, bottom); /* передняя грань — заливка */
rectangle(left, top, right, bottom); /* рамка переда — тек. цвет */
if (depth != 0) {
line(right, top, right + depth, top - depth);
line(right, bottom, right + depth, bottom - depth);
line(right + depth, top - depth, right + depth, bottom - depth);
if (topflag) {
line(left, top, left + depth, top - depth);
line(left + depth, top - depth, right + depth, top - depth);
}
}
}
+40
View File
@@ -0,0 +1,40 @@
/*
* circle — окружность (midpoint) с центром (cx,cy) радиуса r,
* текущим цветом. Целочисленный алгоритм, 8 октантов за шаг.
*
* Вся окружность рисуется в ОДНОЙ W3-скобке (_bgi_begin/_bgi_end) +
* raw-плот — иначе скобка на каждый пиксель делает большие окружности
* мучительно медленными (тысячи DI/EI).
*/
#include "../_bgi.h"
void circle(int cx, int cy, int r)
{
int x, y, d;
uint8_t c = _bgi_fg;
if (r < 0) return;
x = 0;
y = r;
d = 1 - r;
_bgi_begin();
while (x <= y) {
_bgi_plot_raw(cx + x, cy + y, c);
_bgi_plot_raw(cx - x, cy + y, c);
_bgi_plot_raw(cx + x, cy - y, c);
_bgi_plot_raw(cx - x, cy - y, c);
_bgi_plot_raw(cx + y, cy + x, c);
_bgi_plot_raw(cx - y, cy + x, c);
_bgi_plot_raw(cx + y, cy - x, c);
_bgi_plot_raw(cx - y, cy - x, c);
if (d < 0) {
d += 2 * x + 3;
} else {
d += 2 * (x - y) + 5;
y--;
}
x++;
}
_bgi_end();
}
+9
View File
@@ -0,0 +1,9 @@
/* cleardevice — очистить экран фоновым цветом, вернуть CP в (0,0). */
#include "../_bgi.h"
void cleardevice(void)
{
_bgi_clearall(_bgi_bg);
_bgi_cx = 0;
_bgi_cy = 0;
}
+8
View File
@@ -0,0 +1,8 @@
/* closegraph — вернуть текстовый режим, действовавший до initgraph.
* _bgi_mode_done удалён — _videomode_raw_set (mode-agnostic) */
#include "../_bgi.h"
void closegraph(void)
{
_videomode_raw_set(_bgi_prevmode);
}
+17
View File
@@ -0,0 +1,17 @@
/*
* drawpoly — ломаная по numpoints точкам (массив x0,y0,x1,y1,…),
* текущим цветом. Как в Turbo-C, контур НЕ замыкается автоматически:
* чтобы получить замкнутый полигон, повторите первую точку последней.
*/
#include "../_bgi.h"
void drawpoly(int numpoints, const int *polypoints)
{
int i;
if (numpoints < 2) return;
for (i = 0; i < numpoints - 1; i++) {
_bgi_styled_line(polypoints[2 * i], polypoints[2 * i + 1],
polypoints[2 * i + 2], polypoints[2 * i + 3],
_bgi_fg);
}
}
+11
View File
@@ -0,0 +1,11 @@
/*
* ellipse — контур эллипса/эллиптической дуги с полуосями xradius,
* yradius от stangle до endangle. Полный эллипс: stangle=0,endangle=360.
*/
#include "../_bgi.h"
void ellipse(int x, int y, int stangle, int endangle,
int xradius, int yradius)
{
_bgi_ellipse_arc(x, y, stangle, endangle, xradius, yradius, _bgi_fg);
}
+31
View File
@@ -0,0 +1,31 @@
/*
* fillellipse — залитый эллипс с полуосями rx,ry, центр (cx,cy).
* Нутро — текущим стилем заливки, контур — текущим цветом.
*
* По строкам: полуширина w = rx*sqrt(ry²-dy²)/ry (целочисленно через
* _bgi_isqrt). ry²,dy² ≤ ~180² < 32767, rx*s ≤ rx*ry < 32767 — без
* 32-бит (см. memory/avoid_32bit_arith_z80).
*/
#include "../_bgi.h"
void fillellipse(int cx, int cy, int rx, int ry)
{
int dy, s, w;
if (rx < 0 || ry < 0) return;
_bgi_begin();
if (ry == 0) {
_bgi_fill_span(cx - rx, cx + rx, cy);
} else {
for (dy = -ry; dy <= ry; dy++) {
s = _bgi_isqrt(ry * ry - dy * dy); /* 0..ry */
w = (rx * s) / ry;
_bgi_fill_span(cx - w, cx + w, cy + dy);
}
}
_bgi_end();
/* Контур текущим цветом. */
ellipse(cx, cy, 0, 360, rx, ry);
}
+54
View File
@@ -0,0 +1,54 @@
/*
* fillpoly — залитый многоугольник по numpoints точкам {x0,y0,…}.
* Нутро — текущим стилем заливки, контур — текущим цветом. Полигон
* авто-замыкается (последняя точка соединяется с первой).
*
* Заливка: для каждой строки берём крайние X контура (min/max) и
* заполняем между ними — точно для выпуклых, для вогнутых = выпуклая
* оболочка по строкам (упрощение). Крайние X даёт брезенхэмовский
* проход рёбер (_bgi_poly_edge) — без 32-бит арифметики.
*/
#include "../_bgi.h"
void fillpoly(int numpoints, const int *polypoints)
{
int i, j, y, ymin, ymax, xa, ya, xb, yb;
if (numpoints < 2) return;
ymin = 32767;
ymax = -32768;
for (i = 0; i < numpoints; i++) {
y = polypoints[2 * i + 1];
if (y < ymin) ymin = y;
if (y > ymax) ymax = y;
}
if (ymin < 0) ymin = 0;
if (ymax > GFX_HEIGHT - 1) ymax = GFX_HEIGHT - 1;
if (ymax < ymin) return;
for (y = ymin; y <= ymax; y++) {
_bgi_span_min[y] = 32767;
_bgi_span_max[y] = -32768;
}
for (i = 0; i < numpoints; i++) {
j = (i + 1) % numpoints;
_bgi_poly_edge(polypoints[2 * i], polypoints[2 * i + 1],
polypoints[2 * j], polypoints[2 * j + 1]);
}
_bgi_begin();
for (y = ymin; y <= ymax; y++)
if (_bgi_span_max[y] >= _bgi_span_min[y])
_bgi_fill_span(_bgi_span_min[y], _bgi_span_max[y], y);
_bgi_end();
/* Контур текущим цветом (замкнутый). */
for (i = 0; i < numpoints; i++) {
j = (i + 1) % numpoints;
xa = polypoints[2 * i]; ya = polypoints[2 * i + 1];
xb = polypoints[2 * j]; yb = polypoints[2 * j + 1];
_bgi_lineseg(xa, ya, xb, yb, _bgi_fg);
}
}
+70
View File
@@ -0,0 +1,70 @@
/*
* floodfill — заливка связной области с (x,y) до границы цвета border.
*
* Скан-строчный (span) flood: заполняем горизонтальный отрезок текущим
* цветом заливки, затем ищем новые «затравки» в строках сверху/снизу.
* Стоп-условие пикселя: вне экрана / цвет == border / цвет уже == цвет
* заливки (иначе не сойдётся). Паттерн в floodfill не применяется —
* заливаем сплошным _bgi_fill_color (упрощение).
*
* Чтение/запись — self-bracket (_bgi_read/_bgi_hspan): чуть медленнее,
* зато не держим DI на всю (возможно большую) заливку.
*
* Стек затравок ограничен FF_STACK; при переполнении лишние затравки
* отбрасываются (заливка может остаться неполной на очень «рваных»
* фигурах) — компромисс по памяти.
*/
#include "../_bgi.h"
#define FF_STACK 300
static int ff_sx[FF_STACK];
static int ff_sy[FF_STACK];
static int ff_sp;
/* 1 = пиксель нельзя заливать (стоп): вне экрана / border / уже залит. */
static int ff_stop(int x, int y, int border)
{
int c;
if (x < 0 || x > _bgi_maxx || y < 0 || y > _bgi_maxy) return 1;
c = _bgi_read(x, y);
return (c == border || c == _bgi_fill_color);
}
static void ff_push(int x, int y)
{
if (ff_sp < FF_STACK) { ff_sx[ff_sp] = x; ff_sy[ff_sp] = y; ff_sp++; }
}
void floodfill(int x, int y, int border)
{
int xl, xr, xi, yy, d;
uint8_t fc = _bgi_fill_color;
if (ff_stop(x, y, border)) return;
ff_sp = 0;
ff_push(x, y);
while (ff_sp > 0) {
ff_sp--;
x = ff_sx[ff_sp];
y = ff_sy[ff_sp];
if (ff_stop(x, y, border)) continue;
for (xl = x; !ff_stop(xl - 1, y, border); ) xl--;
for (xr = x; !ff_stop(xr + 1, y, border); ) xr++;
_bgi_hspan(xl, y, xr - xl + 1, fc);
for (d = -1; d <= 1; d += 2) {
yy = y + d;
xi = xl;
while (xi <= xr) {
while (xi <= xr && ff_stop(xi, yy, border)) xi++;
if (xi > xr) break;
while (xi <= xr && !ff_stop(xi, yy, border)) xi++;
ff_push(xi - 1, yy); /* правый край найденного пробега */
}
}
}
}
+7
View File
@@ -0,0 +1,7 @@
/* getbkcolor — текущий фоновый цвет. */
#include "../_bgi.h"
int getbkcolor(void)
{
return _bgi_bg;
}
+7
View File
@@ -0,0 +1,7 @@
/* getcolor — текущий цвет рисования. */
#include "../_bgi.h"
int getcolor(void)
{
return _bgi_fg;
}
+9
View File
@@ -0,0 +1,9 @@
/* getfillsettings — прочитать текущий паттерн и цвет заливки. */
#include "../_bgi.h"
void getfillsettings(struct fillsettingstype *fillinfo)
{
if (!fillinfo) return;
fillinfo->pattern = _bgi_fill_pattern;
fillinfo->color = _bgi_fill_color;
}
+27
View File
@@ -0,0 +1,27 @@
/*
* getimage — сохранить прямоугольник экрана в буфер bitmap.
* Формат: uint16 width, uint16 height, затем w*h байт (по строкам).
* Чтение raw в одной W3-скобке.
*/
#include "../_bgi.h"
void getimage(int left, int top, int right, int bottom, void *bitmap)
{
uint8_t *p = (uint8_t *)bitmap;
int w = right - left + 1;
int h = bottom - top + 1;
int x, y;
if (w <= 0 || h <= 0) return;
*p++ = (uint8_t)w;
*p++ = (uint8_t)(w >> 8);
*p++ = (uint8_t)h;
*p++ = (uint8_t)(h >> 8);
_bgi_begin();
for (y = 0; y < h; y++)
for (x = 0; x < w; x++)
*p++ = (uint8_t)_bgi_read_raw(left + x, top + y);
_bgi_end();
}
+10
View File
@@ -0,0 +1,10 @@
/* getlinesettings — прочитать текущий стиль/маску/толщину линий. */
#include "../_bgi.h"
void getlinesettings(struct linesettingstype *lineinfo)
{
if (!lineinfo) return;
lineinfo->linestyle = _bgi_line_style;
lineinfo->upattern = _bgi_line_pattern;
lineinfo->thickness = _bgi_line_thick;
}
+7
View File
@@ -0,0 +1,7 @@
/* getmaxcolor — максимальный индекс цвета (255 в 256-цветном режиме). */
#include "../_bgi.h"
int getmaxcolor(void)
{
return _bgi_maxcolor;
}
+7
View File
@@ -0,0 +1,7 @@
/* getmaxx — максимальная координата X (ширина-1). */
#include "../_bgi.h"
int getmaxx(void)
{
return _bgi_maxx;
}
+7
View File
@@ -0,0 +1,7 @@
/* getmaxy — максимальная координата Y (высота-1). */
#include "../_bgi.h"
int getmaxy(void)
{
return _bgi_maxy;
}
+12
View File
@@ -0,0 +1,12 @@
/* gettextsettings — текущий стиль текста (justify не реализован → 0). */
#include "../_bgi.h"
void gettextsettings(struct textsettingstype *textinfo)
{
if (!textinfo) return;
textinfo->font = DEFAULT_FONT;
textinfo->direction = _bgi_text_dir;
textinfo->charsize = _bgi_text_size;
textinfo->horiz = 0;
textinfo->vert = 0;
}
+7
View File
@@ -0,0 +1,7 @@
/* getx — текущая позиция X. */
#include "../_bgi.h"
int getx(void)
{
return _bgi_cx;
}
+7
View File
@@ -0,0 +1,7 @@
/* gety — текущая позиция Y. */
#include "../_bgi.h"
int gety(void)
{
return _bgi_cy;
}
@@ -2,7 +2,7 @@
* gfx_get_bank текущий байт страницы W3 (0x50..0x5F).
*/
#include "_gfx.h"
#include "../_bgi.h"
uint8_t gfx_get_bank(void)
{
@@ -2,7 +2,7 @@
* gfx_get_draw_page кэшированный номер рисуемой страницы (0/1).
*/
#include "_gfx.h"
#include "../_bgi.h"
uint8_t gfx_get_draw_page(void)
{
@@ -2,7 +2,7 @@
* gfx_get_visible_page кэшированный номер видимой страницы (0/1).
*/
#include "_gfx.h"
#include "../_bgi.h"
uint8_t gfx_get_visible_page(void)
{
@@ -3,7 +3,7 @@
* через BIOS WIN_GET_ZG (fn 0xB8) в _gfx_font_buf и включить его.
*/
#include "_gfx.h"
#include "../_bgi.h"
/* BIOS WIN_GET_ZG (0xB8): DE = приёмник, возвращает 2 КБ. */
static void bios_get_zg(uint8_t *dest) __naked
@@ -3,7 +3,7 @@
* обёртка над pal_get (libc/video) не на горячем пути.
*/
#include "_gfx.h"
#include "../_bgi.h"
#include <palette.h>
void gfx_pal_get(uint8_t pal_num, uint8_t start, uint8_t count,
@@ -3,7 +3,7 @@
* R/G/B (любой указатель может быть NULL); обёртка над pal_get_color.
*/
#include "_gfx.h"
#include "../_bgi.h"
#include <palette.h>
void gfx_pal_get_color(uint8_t pal_num, uint8_t idx,
@@ -8,7 +8,7 @@
* палитру (следующая печать в текстовом режиме вешалась).
*/
#include "_gfx.h"
#include "../_bgi.h"
#include <palette.h>
static uint8_t gpl_num_;
@@ -3,7 +3,7 @@
* (BIOS $A6, type = PAL_GRAPH).
*/
#include "_gfx.h"
#include "../_bgi.h"
#include <palette.h>
void gfx_pal_reset(void)
@@ -3,7 +3,7 @@
* четвёрку (B, G, R, 0) и зовёт gfx_pal_load(pal_num, idx, 1, ...).
*/
#include "_gfx.h"
#include "../_bgi.h"
void gfx_pal_set(uint8_t pal_num, uint8_t idx,
uint8_t r, uint8_t g, uint8_t b)
@@ -2,7 +2,7 @@
* gfx_set_bank задать байт страницы W3 (0x50..0x5F) для gfx-записей.
*/
#include "_gfx.h"
#include "../_bgi.h"
void gfx_set_bank(uint8_t bank)
{
@@ -3,7 +3,7 @@
* _gfx_addr_base, который читают все примитивы.
*/
#include "_gfx.h"
#include "../_bgi.h"
void gfx_set_draw_page(uint8_t page)
{
@@ -3,7 +3,7 @@
* storage должен жить, пока шрифт активен). Формат см. _gfx.h.
*/
#include "_gfx.h"
#include "../_bgi.h"
void gfx_set_font(const uint8_t *font)
{
@@ -6,7 +6,7 @@
* (один из свопов показывается чёрным). Через syscall DSS доволен.
*/
#include "_gfx.h"
#include "../_bgi.h"
void gfx_set_visible_page(uint8_t page)
{
+72
View File
@@ -0,0 +1,72 @@
/*
* gfx_wait_vsync — ждать переход бита 5 порта 0xFE из 1 в 0: выход из
* нижнего blank/border (Y > 256 — см. MAME sprinter.cpp kbd_fe_r:
* `vpos() >= BORDER_TOP+SCREEN_YSIZE`) — начало нового кадра, идеальный
* момент для свопа страниц без разрывов. Это чтение РЕАЛЬНОГО
* состояния луча (не событие/прерывание), поэтому не подвержено
* путанице с другими источниками вектора 0xFF (клавиатура, CBL, CTC) —
* подробнее см. memory/gfx_wait_vsync_redesign.
*
* Бит доступен ТОЛЬКО пока включён cbl_mode() (bit7 порта 0x004E) —
* см. libc/cbl/_cbl_port.c: если приложение уже играет через
* cbl_open(), берём бит бесплатно (порт не трогаем); иначе
* лениво занимаем bit7 "немым" кодом частоты через _cbl_port_ref()
* при первом вызове, освобождаем в atexit.
*
* Fallback: если ни разу не увидели ожидаемое состояние за разумное
* число попыток (иное железо, бит ведёт себя не как в MAME) —
* откатываемся на одно кадровое прерывание (halt), чтобы не
* зависнуть намертво.
*/
#include "../_bgi.h"
#include <sprinter_exit.h>
static uint8_t vsync_ready;
static void vsync_cleanup(void)
{
_cbl_port_unref();
}
void gfx_wait_vsync(void)
{
if (!vsync_ready) {
vsync_ready = 1;
_cbl_port_ref();
atexit(vsync_cleanup);
}
__asm
;; --- фаза 1: дождаться bit5==1 (гарантированно в blank) ---
ld bc, #0 ; ~65536 попыток на фазу таймаут
_gwv_wait1:
in a, (#0xFE)
bit 5, a
jr nz, _gwv_have1
dec bc
ld a, b
or c
jr nz, _gwv_wait1
jr _gwv_fallback ; bit5 ни разу не встал за таймаут
_gwv_have1:
;; --- фаза 2: дождаться bit5==0 (сам переход начало кадра) ---
ld bc, #0
_gwv_wait0:
in a, (#0xFE)
bit 5, a
jr z, _gwv_done
dec bc
ld a, b
or c
jr nz, _gwv_wait0
;; таймаут и здесь переходим к тому же фолбэку
_gwv_fallback:
ei
halt ; аварийный путь: одно кадровое прерывание
_gwv_done:
__endasm;
}
+9
View File
@@ -0,0 +1,9 @@
/* graphresult — код последней ошибки; вызов сбрасывает его в grOk. */
#include "../_bgi.h"
int graphresult(void)
{
int r = _bgi_result;
_bgi_result = grOk;
return r;
}
+17
View File
@@ -0,0 +1,17 @@
/*
* imagesize — размер буфера под образ (left,top)-(right,bottom) в байтах:
* 4 (заголовок w,h) + w*h (по байту на пиксель в режиме 256).
*
* Результат 16-бит unsigned (как в Turbo-C): образы ≥64 КБ не влезают.
* Произведение w*h считаем в unsigned — для валидных (<64 КБ) образов
* умещается точно; без 32-бит.
*/
#include "../_bgi.h"
unsigned imagesize(int left, int top, int right, int bottom)
{
int w = right - left + 1;
int h = bottom - top + 1;
if (w <= 0 || h <= 0) return 0;
return (unsigned)4 + (unsigned)w * (unsigned)h;
}
+25
View File
@@ -0,0 +1,25 @@
/*
* initgraph — перейти в графический режим и сбросить состояние BGI.
*
* Режим определяется driver-библиотекой (для sprinter_gfx256 —
* 320×256×256). По умолчанию: цвет WHITE, фон BLACK, позиция (0,0).
*/
#include "../_bgi.h"
void initgraph(void)
{
_bgi_prevmode = _bgi_mode_set();
_bgi_fg = WHITE;
_bgi_bg = BLACK;
_bgi_cx = 0;
_bgi_cy = 0;
_bgi_result = grOk;
_bgi_fill_pattern = SOLID_FILL;
_bgi_fill_color = WHITE;
_bgi_line_style = SOLID_LINE;
_bgi_line_thick = NORM_WIDTH;
_bgi_line_pattern = 0xFFFF;
_bgi_text_dir = HORIZ_DIR;
_bgi_text_size = 1;
}
+7
View File
@@ -0,0 +1,7 @@
/* line — отрезок (x1,y1)->(x2,y2) текущим цветом; CP не меняется. */
#include "../_bgi.h"
void line(int x1, int y1, int x2, int y2)
{
_bgi_styled_line(x1, y1, x2, y2, _bgi_fg);
}
+11
View File
@@ -0,0 +1,11 @@
/* linerel — линия от CP до CP+(dx,dy); CP сдвигается в конец. */
#include "../_bgi.h"
void linerel(int dx, int dy)
{
int nx = _bgi_cx + dx;
int ny = _bgi_cy + dy;
_bgi_styled_line(_bgi_cx, _bgi_cy, nx, ny, _bgi_fg);
_bgi_cx = nx;
_bgi_cy = ny;
}
+9
View File
@@ -0,0 +1,9 @@
/* lineto — линия от CP до (x,y) текущим цветом; CP := (x,y). */
#include "../_bgi.h"
void lineto(int x, int y)
{
_bgi_styled_line(_bgi_cx, _bgi_cy, x, y, _bgi_fg);
_bgi_cx = x;
_bgi_cy = y;
}
+8
View File
@@ -0,0 +1,8 @@
/* moverel — сдвинуть текущую позицию относительно. */
#include "../_bgi.h"
void moverel(int dx, int dy)
{
_bgi_cx += dx;
_bgi_cy += dy;
}
+8
View File
@@ -0,0 +1,8 @@
/* moveto — задать текущую позицию (CP). */
#include "../_bgi.h"
void moveto(int x, int y)
{
_bgi_cx = x;
_bgi_cy = y;
}
+12
View File
@@ -0,0 +1,12 @@
/*
* outtext — строка в текущей позиции (CP) текущим стилем текста; CP
* сдвигается на textwidth (гориз.) или вверх на textheight (верт.).
*/
#include "../_bgi.h"
void outtext(const char *text)
{
_bgi_draw_scaled(_bgi_cx, _bgi_cy, text);
if (_bgi_text_dir == VERT_DIR) _bgi_cy -= textheight(text);
else _bgi_cx += textwidth(text);
}

Some files were not shown because too many files have changed in this diff Show More