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

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

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

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 19:38:00 +03:00
parent d1bc97c589
commit 95c22be9bd
46 changed files with 4125 additions and 133 deletions
+8
View File
@@ -259,6 +259,14 @@ putsprite новой (save-буфер не нужен). Низкий урове
рисовать банком 0x50, спрайты/оверлеи — putsprite/0x5C; буферы
образов — вне W3 (< 0xC000). Тесты: tests/sprites, tests/gfxbanks,
tests/bgi_img.
Скролл региона из НЕактивной страницы в активную (<sprite.h>):
`gfx_scroll_h(area, dx, dirty)` — горизонтальный (dx>0 = вправо; быстрый
построчный accel-скролл без страйдов, DI-банды по 16 строк),
`gfx_scroll_v(area, dy, dirty)` — вертикальный (dy>0 = вниз; через
строку-буфер на стеке, ПОКА не оптимален — Port_Y один на burst, разный
read/write Y невозможен без ломающего accel OUT). Копия = скролл + heal
цели (банк 0x50). Открывшуюся полосу |d| не заполняют — возвращают в
*dirty (NULL = не нужно). Пример: examples/scroll.
Стиль линий (Ф2d): `setlinestyle(style,upattern,thick)`/`getlinesettings`
(SOLID/DOTTED/CENTER/DASHED/USERBIT + NORM/THICK) — на line/rectangle/
drawpoly.
-67
View File
@@ -1,67 +0,0 @@
1) если я и DATA и CODE размещаю в одном окне (W1 - #4000 или W2 - #8000, неважно),
то при вызове set_videomode глобальные переменные (errno, g_text_attr) не меняют
своих значений.
если же DATA и CODE находятся в разных окнах (не важно где DATA - в W1 или W2, главное
что не в том где CODE) - то при вызове se_videomode значения глобальных переменных меняются
То есть похоже что для DATA не назначается отдельный блок памяти а назначается только для CODE
Это полностью соответствует документации - если приложение менее 16К (как у нас) то ему выделяется
только одна страница. И получается что работа со второй страницей идет несанкционированно (ей память
не выделена).
Потому предлагается
1) сейчас размещать ВСЕ в одной странице (и DATA и CODE и стек) - в W2.
2) дальше - добавить в нашу обертку sprinter-cc режимы памяти -
-tiny - все приложение помещается в одну страницу - в W2 (и DATA и CODE и стек)
-small - приложение помещается в две страницы - CODE в W1, DATA и стек - в W2.
в этом режиме над отдельно выделять и маппить страницу в W2 для DATA и стека
-big - DATA, CODE и стек помещаются в одну страницу W2 как в -tiny, добавляется поддержка banked в W1,
страница W3 остается служебной и для работы с граффикой из banked code
-huge - приложение помещается в двух страницах как и -small но так же добавляется поддержка banked но
уже в страницу W3
Из документации -
> Теперь адресса #4000..#7FFF,#8000..#BFFF,#C000..#FFFF, когда ДСС
передаёт управление эти прогораммам, какие банки там нахадятся по
умолчанию?
В зависимости от адреса загрузки и размера приложения DSS выделяет
необходимое число страниц памяти. Так при размере меньше 16К будет
выделена
одна страница, при размере больше 16К - две, и т.д. В окна с
"неиспользуемым" адресном пространством будет подключатся
специальная страница #FF.Если приложению требуется памяти больше чем
зарезервировано в exe-файле, оно должно выделить себе дополнительный
блок памяти самостоятельно.
> В конфигурации спринтер
> по #0000..#3FFF, при работе ДСС находится сама ДСС с её Резетами,
> чтоб использывать когда сюда подставленна страница пользователся
> резеты не доступны!.
Это так в нижних 16K находится DSS / BIOS в остальных 48К
приложение, но с
определенными особенностями. Стек не должен быть выше #BFFF при
вызове DSS и ниже #8000 при вызове некоторых функций BIOS.
Так же посмотри вот сюда - возможно нам придется для режимов -small и -huge делать свой первичным загрузчиком -
Из документации -
Выполнение EXE-файла осуществляется по следующим пунктам:
1) Открывает exe-файл на чтение;
2) Считывает в рабочую область префикс exe-файла;
3) Выделяет блок памяти, требуемый для загрузки всего файла или первичного
загрузчика, если его размер не равен нулю;
4) Сохраняет стек;
5) Подключает страницы из выделенного блока;
6) Строит префикс запуска программы и устанавливает на него регистр IX;
7) Считывает файл по адресу указанному в смещении 16 (Адрес расположения кода в
памяти);
8) Закрывает exe-файл, если это не первичный загрузчик;
9) Устанавливает стек равным значению из смещения 20 (Адрес расположения стека);
10) Передает управление по адресу указанному в смещении 18 (Адрес запуска);
+308
View File
@@ -0,0 +1,308 @@
# Управление памятью в sprinter-cc
Документ описывает модель памяти Sprinter (Sp2000), режимы памяти обёртки
`sprinter-cc` и способы размещения кода/данных: single-page, split, банки
(трамплины) и резидентный код окна W3 (`--w3`).
Всё в этом документе подтверждено сборкой и прогоном в MAME v3.06 / DSS 1.71.57
(см. `tests/w3probe`, `tests/banktest`, `tests/banklocl`).
---
## 1. Аппаратная модель памяти
Z80 видит 64 КБ, разбитые на **четыре окна по 16 КБ**. Каждое окно независимо
маппится на физическую страницу через порт-регистр страницы:
| Окно | Адреса | Порт страницы | Назначение по умолчанию |
|------|---------------|---------------|--------------------------|
| W0 | `0x0000-0x3FFF` | `0x82` | **DSS / BIOS** (RST-ы, системные вызовы) |
| W1 | `0x4000-0x7FFF` | `0xA2` | приложение |
| W2 | `0x8000-0xBFFF` | `0xC2` | приложение |
| W3 | `0xC000-0xFFFF` | `0xE2` | приложение / графика / банки |
- Запись в порт `0x{8/A/C/E}2` меняет физ-страницу окна; чтение возвращает
текущую страницу.
- **W0 занят DSS/BIOS** — там живут RST-обработчики (`RST #08` BIOS, `RST #10`
ESTEX). Пока в W0 стоит системная страница, вызовы доступны; подменять W0
нельзя без потери RST-ов.
- «Неиспользуемое» окно маппится на **специальную страницу `#FF`**: чтение даёт
`0xFF`, запись игнорируется. Это ключевая причина «молчаливой» порчи данных —
см. §7.
### Порты в C
`<sprinter.h>` даёт SFR-обёртки: `_io_page_w0..w3` (чтение/запись порта),
`sprinter_page_w0..w3(page)`.
---
## 2. Как DSS загружает EXE
Из документации DSS, последовательность `EXEC`:
1. Открыть exe-файл на чтение.
2. Считать префикс exe в рабочую область.
3. **Выделить блок памяти** размером под весь файл (если `loader==0`) или под
первичный загрузчик (`loader>0`).
4. Сохранить стек.
5. **Подключить страницы** выделенного блока в окна (последовательно от окна
адреса загрузки: W1→W2→W3…).
6. Построить префикс запуска → регистр `IX`.
7. Считать файл по адресу загрузки (смещение 16 в заголовке).
8. Закрыть exe, **если это не первичный загрузчик** (`loader==0`).
9. Установить `SP` = значение из смещения 20 (адрес стека).
10. Передать управление по адресу из смещения 18 (entry).
**Следствия, на которых стоит вся схема памяти:**
- DSS выделяет **число страниц по размеру образа**: `<16 КБ` → 1 страница,
`16..32 КБ` → 2, `32..48 КБ` → 3. Лишние окна = страница `#FF`.
- Страницы маппятся **подряд** начиная с окна адреса загрузки. Образ, тянущийся
`0x4100..0xFFFF` (3 страницы), даёт W1+W2+W3 замапленными автоматически —
**это и есть база для резидентного кода W3** (§6, подход A).
- `loader>0` (multi-bank .exe) → DSS грузит только HOME-часть и **оставляет
файл открытым** (handle в `IX-3`), а crt0 дочитывает банки сам (§5).
Упаковкой в этот формат занимается `toolchain/mkexe`.
---
## 3. Правила стека (критично)
- **`SP ≤ 0xBFFF`** при вызовах DSS (ESTEX, `RST #10`).
- **`SP ≥ 0x8000`** при вызовах некоторых функций BIOS (`RST #08`).
- Пересечение этих требований → **стек обязан жить в W2** (`0x8000-0xBFFF`).
По умолчанию `SP` инициализируется в `0xBFFE`.
Проверено (`tests/w3probe`): во всех режимах на входе `main` `SP ≈ 0xBFFC`, т.е.
в W2.
**Chicken-and-egg для split-режимов** (small/huge): DSS ставит `SP=0xBFFE` из
заголовка, но для программ `<16 КБ` окно W2 ещё не выделено (там `#FF`). Пуши
туда теряются, первый `call` возвращается в мусор. Поэтому `crt0_small`/
`crt0_banked` сначала работают на **загрузочном стеке в W1** (реальное ОЗУ),
маппят W2 и только потом переставляют `SP=0xBFFE`. Маппинг W2 делается через
**ESTEX `$3A SETWIN2`** (не BIOS `$C4`+OUT — тому нужен стек уже в W2).
---
## 4. Режимы памяти
Выбираются флагом `sprinter-cc --memory MODE` (по умолчанию `tiny`). Режим задаёт
адрес кода, размещение данных, crt0 и наличие банков.
| Режим | CODE | DATA/BSS | Стек | Банки | crt0 | Первичный загрузчик |
|--------|------|----------|------|-------|------|----------------------|
| `tiny` | W2 `0x8100` | за кодом (W2) | W2 | — | `crt0.s` | нет (1 страница) |
| `small` | W1 `0x4100` | за кодом (W1→W2) | W2 | — | `crt0_small.s` | да (сам маппит W2) |
| `big` | W2 `0x8100` | за кодом (W2) | W2 | W1 (трамплины) | `crt0_banked.s` (BANK_W1) | да |
| `huge` | W1 `0x4100` | за кодом (W1→W2) | W2 | W3 (трамплины) | `crt0_banked.s` | да |
| `manual` | явно | явно | W2 | — | `crt0.s` | зависит |
Во всех режимах **DATA цепляется линкером сразу за кодом** (`--data-loc 0`), а не
кладётся по фиксированному адресу. Раньше `huge` использовал фиксированный
`DATA=0x8000`, что ломалось при коде `>16 КБ` (код перетекал в W2 и накрывал
DATA); сейчас DATA динамически идёт за концом кода.
### 4.1 `tiny` — всё в одной странице
```
0x8000..0x80FF зарезервировано (startup-prefix)
0x8100 _start / _CODE … _DATA … _BSS … _HEAP
0xBB00 heap top (по умолчанию)
0xBFFE стек ↓
```
- Один блок 16 КБ, DSS маппит его в W2. W1 и W3 = `#FF`.
- Ничего выделять/маппить не надо; `crt0.s` предполагает, что W2 уже дан DSS.
- Практический потолок кода+данных+кучи+стека ≈ 14 КБ.
- `--code-loc 0x8100 --data-loc 0`; mkexe `-L 0x8100 -E 0x8100 -S 0xBFFE`.
### 4.2 `small` — CODE в W1, данные перетекают в W2
```
0x4100 _CODE … (W1)
… за кодом → _DATA _BSS _HEAP (W1, перетекает в W2)
0xBB00 heap top
0xBFFE стек ↓ (W2)
```
- Покрывает ~0..30 КБ (код+данные вместе).
- `crt0_small.s` авто-определяет W2: читает порт `0xC2`. Если `≠0xFF` — DSS уже
дал W2 (образ `>16 КБ`), маппить не надо. Если `=0xFF` — сам выделяет страницу
(`ESTEX $3D GETMEM`) и маппит (`ESTEX $3A SETWIN2`).
- `--code-loc 0x4100 --data-loc 0`.
### 4.3 `big` — tiny + банки в W1
- База как `tiny` (CODE+DATA+стек в W2, `0x8100`).
- **Свапаемые банки в W1** (`0x4000-0x7FFF`, порт `0xA2`), вызываются через
трамплины (§5). `crt0_banked.s` собирается с `BANK_W1=1`.
- mkexe получает `-B 0x4000` (банки живут в W1). Виртуальный адрес банка N =
`0x{N}4000`.
- W3 свободно — доступно под графику или резидентный код (`--w3`).
### 4.4 `huge` — small + банки в W3
- База как `small` (CODE `0x4100`, DATA за кодом, авто-детект W2).
- **Свапаемые банки в W3** (`0xC000-0xFFFF`, порт `0xE2`), через трамплины.
Виртуальный адрес банка N = `0x{N}C000`.
- `crt0_banked.s` (без `BANK_W1`) грузит банки из .exe после старта.
### 4.5 `manual` — явное размещение
`--memory manual --memory-manual SPEC`, где SPEC = список `KEY=VAL`:
`CODE=W1|W2`, `DATA=W1|W2|SAME`, `BANKED=W1|W3`. Плюс прямые `--code-loc` /
`--data-loc` / `-L` / `-E` / `-S` перекрывают дефолты любого режима.
---
## 5. Банки и трамплины (`--bank`)
Для `big`/`huge`. Модуль-банк собирается в отдельную область и линкуется по
**виртуальному 24-битному адресу** (`bank_id` в старшем байте):
```
sprinter-cc --memory huge --bank 1=engine.c --bank 2=audio.c -o app.exe main.c
```
- Банк N компилируется `--codeseg/--constseg/--dataseg BANKN`, линкуется
`-Wl-b_BANKN=0x{N}C000` (huge) или `0x{N}4000` (big).
- `main.c` обязан объявить `const uint8_t n_banks = N;``crt0_banked` читает
это **до gsinit**, поэтому только `const` (инициализатор ещё не скопирован).
- Функции банка помечаются `__banked` — SDCC генерирует вызов через трамплин
`___sdcc_bcall_ehl``_CODE`/W1, всегда замаплен): он сохраняет текущую
страницу окна, маппит нужный банк (`_bank_pages[id]`), `jp` в функцию, по
возврату восстанавливает страницу.
- Загрузка: mkexe пакует `header + HOME + bank1(16К) + bank2(16К)…`,
`loader=размер HOME`; `crt0_banked` выделяет страницы (`GETMEM`), маппит и
дочитывает каждый банк `ESTEX READ` из открытого файла.
- Проверка размеров банков — `toolchain/check_banks.py` (часть отчёта
раскладки, см. §10).
Writable bank-local данные возможны, но с оговорками — см.
`memory/bank_local_data_pattern`.
---
## 6. Резидентный код окна W3 (`--w3`)
**Альтернатива банкам без трамплинов.** Модуль размещается резидентно в W3
(`0xC000`) и вызывается **прямым `call`** — как обычная функция. Работает во
**всех режимах** (`tiny|small|big|huge`); по умолчанию подразумевает `small`.
```
sprinter-cc --w3 render.c -o app.exe main.c # → small
sprinter-cc --memory huge --w3 render.c --bank 1=lvl.c -o app.exe main.c
```
**Как работает (подход A):** образ с областью `W3CODE`@`0xC000` тянется до `0xC0xx`
(≥3 страницы), и DSS сам маппит W3 при загрузке (§2) — **загрузчик в crt0 не
нужен** (кроме huge). Прямые вызовы резолвятся линкером в реальные `0xC0xx`.
**Механизм сборки:**
- W3-модуль компилируется `--codeseg W3CODE --constseg W3CODE` — код и rodata в
W3. **`--dataseg` НЕ переопределяется**: писучие статики уходят в обычный
`_DATA` (W2). Отсюда правило «в W3 только код + rodata».
- Линк `-Wl-b_W3CODE=0xC000`.
**Раскладка страниц по режимам (проверено MAME):**
| Режим | W1 | W2 | W3 |
|-------|----|----|----|
| tiny | `#FF` (не исп.) | код+данные | **резидент** |
| small | код | данные | **резидент** |
| big | банк (трамплин) | код+данные | **резидент** |
| huge | код | данные | **резидент делит окно с трамплин-банками** |
**huge — особый случай (резидент + банки в одном окне W3):**
- `crt0_banked` под `.ifdef W3_RESIDENT` захватывает физ-страницу резидента
(`in a,(0xE2)`) сразу после загрузки DSS и **возвращает её дефолтом** после
цикла загрузки банков (иначе в W3 остался бы последний банк).
- Трамплин на каждый `__banked`-вызов сам сохраняет/восстанавливает страницу
W3 — поэтому дефолтная страница обязана быть резидентной.
- mkexe получает флаг `-W` (разрешить HOME тянуться в W3 при наличии W3-банков —
намеренное совмещение).
**Правила разработчика:**
- Код в W3 **не переключает** страницу W3.
- Код в W1/W2, свапающий W3 на другую страницу (скретч, графика), обязан
**вернуть исходную** (`in a,(0xE2)` → работа → `out (0xE2),a`); оборачивать в
`DI/EI`, если есть ISR, дёргающий W3.
- В W3 — **только код + rodata**, писучих переменных там быть не должно.
- Из `__banked`-контекста (пока в W3 замаплен банк) резидентный W3-код
**недостижим** транзитивно. Обратное — резидент → `__banked` через трамплин
W1 — **работает** (трамплин вернёт резидентную страницу перед `ret`).
Подробности и артефакты — `memory/w3_resident_code`, тест `tests/w3probe`.
---
## 7. Куча и стек
- **Стек**: init `SP=0xBFFE`, растёт вниз. Меняется через `-S 0xADDR`.
- **Куча**: от конца `_BSS` вверх до `___sdcc_heap_end` (по умолчанию `0xBB00`
~1278 байт под стек). `malloc` берёт `&___sdcc_heap_end` как потолок.
- `runtime/heap.s` — динамическая куча (авто-размер = зазор BSS…heap_top), НЕ
фиксированный `.ds`.
- `--stack-size N` регенерирует `heap_top` как `HEAP_TOP = 0xBFFF - N`, зажимая
рост кучи ради стека.
---
## 8. Семейство crt0
Выбирается режимом; `--crt0=TYPE` перекрывает.
| crt0 | Файл | Для чего |
|------|------|----------|
| `default` | `runtime/crt0.s` | tiny/manual; парсит argv, argv[0] через APPINFO |
| `minimal` | `runtime/crt0_minimal.s` | tiny без argv (меньше размер) |
| `small` | `runtime/crt0_small.s` | small; авто-детект/выделение W2 |
| `banked` | `runtime/crt0_banked.s` | big/huge; авто-детект W2 + загрузка банков |
`crt0`/`bank.s` собираются **пер-сборка** внутри `sprinter-cc` (с префиксами
`BANK_W1` / `W3_RESIDENT` / `DEBUG_RT`), а не бандлятся в библиотеку.
---
## 9. Типовые грабли
- **`static`-переменные читаются как `0xFF` / не меняются** — DATA попала в
невыделенное окно (`#FF`). Причина: код и данные в разных окнах при образе
`<16 КБ`, где DSS дал только одну страницу. Лечится правильным режимом
(`small`/`huge`) или единым окном (`tiny`). См.
`memory/sprinter_memory_modes`.
- **Крэш после первого `call` в split-режиме** — стек ещё в невыделенном W2.
Решает загрузочный стек в W1 (уже в crt0).
- **Банк «прыгает в мусор»** — таблица `_bank_pages[]` в `_DATA` занулилась
gsinit’ом; она обязана жить в `_CODE`. Уже исправлено в `bank.s`.
- **`--w3`: резидент недоступен из банка** — ожидаемо (см. §6); держи вход в
резидент только из W1/W2 или из самого W3-кода.
---
## 10. Отчёт по раскладке памяти (после линковки)
`sprinter-cc` печатает отчёт `toolchain/check_banks.py` для **любой** модели
памяти (по `.map`). Показывает, где легли код/данные и **сколько свободно**
в каждом окне и банке:
```
memory: huge — CODE в W1 (0x4100), DATA→W2, банки в W3
_CODE @ 0x4100 size 3767 (W1) → 0x4FB7
данные @ 0x4FB7 size 336 (W1) → 0x5107 [_DATA/_BSS/_INITIALIZED/…]
статика до 0x5107 — куча 0x5107..0xBB00 (27129 Б), стек 0xBB00..0xBFFE (1279 Б)
_W3CODE @ 0xC000 size 249 (резидент W3) → 0xC0F9, 16135 Б свободно до 0x10000 OK
_BANK1 @ 0x0001C000 size 219 / 16384 ( 1.3%) → 16165 Б свободно OK
```
- `_CODE` / `данные` — окно (W1/W2), размер, конец; строка `статика … куча …
стек …` = свободное место в W1/W2 (под кучу malloc до `___sdcc_heap_end` и
под стек).
- `_W3CODE` — только при `--w3`: остаток окна W3.
- `_BANKn` — только при `--bank`: занятость/остаток каждого 16 КБ-банка.
- Ненулевой выход (проглатывается `|| true`), если банк > 16 КБ или статика
заехала за `heap_top`/`0xC000`.
```
Binary file not shown.
+721
View File
@@ -0,0 +1,721 @@
;-------------------------------------------------------------------------------
; Balls mini-demo
; (c) Sayman 2015
;-------------------------------------------------------------------------------
; ïðîöåäóðà ìîäóëÿ ñîâñåì íå îïòèìàëüíà, âçÿòà èç èñõîäíèêà áèáëèîòåêè C.
;-------------------------------------------------------------------------------
tbuf_addr: equ 4000h
screen_addr: equ 0xc000
balls_pgn equ 1
bgspr_pgn equ 6
savescr_pgn equ 6
tbuf_pgn equ 1
MAX_BALLS equ 32 ; ïðè 96 øàðèêàõ ñòðóêòóðà balls_cfg áóäåò 576 áàéò
balls_pal_size equ 128
bgspr_pal_size equ 128
balls_struct:
.x equ 0 ; u16_t
.y equ 2 ; u8_t
.dx equ 3 ; i8_t
.dy equ 4 ; i8_t
.radius equ 5 ; u8_t
balls_struct_bkp:
.x equ 0 ; u16_t
.y equ 2 ; u8_t
include "dss.inc"
include "head.inc"
entry: push ix
call init
call read_bgspr
ld a,(pg_tbl.tbuf)
out (cpu_w1),a
ld a,(tmp_hndl)
call close
call read_bgpal
call read_balls_spr
call read_balls_pal
call vinit
call balls_reindex
call balls_init
call draw_bgspr
call CopySCR_1to2
ld a,0xc0
out (port_y),a
ld a,(pg_w3)
out (cpu_w3),a
call balls_main
sub a
jp quit0
include "misc.asm"
include "math.inc"
rand16 ld de,.seed ; Seed is usually 0
ld a,d
ld h,e
ld l,253
or a
sbc hl,de
sbc a,0
sbc hl,de
ld d,0
sbc a,d
ld e,a
sbc hl,de
jr nc,.rand
inc hl
.rand ld (rand16+1),hl
ret
.seed: dw 0
inc_pg1: ld a,(ix)
out (cpu_w1),a
inc ix
ret
inc_pg3: ld a,(ix)
out (cpu_w3),a
inc ix
ret
init: ld c,balls_pgn
ld hl,pg_tbl.balls
.loop0: push bc
push hl
call gmem
pop hl
pop bc
jp c,gmem_err
ld (hl),a
inc hl
dec c
jr nz,.loop0
ld c,bgspr_pgn
.loop1: push bc
push hl
call gmem
pop hl
pop bc
jp c,gmem_err
ld (hl),a
inc hl
dec c
jr nz,.loop1
ld c,savescr_pgn
.loop2: push bc
push hl
call gmem
pop hl
pop bc
jp c,gmem_err
ld (hl),a
inc hl
dec c
jr nz,.loop2
ld c,tbuf_pgn
.loop3: push bc
push hl
call gmem
pop hl
pop bc
jp c,gmem_err
ld (hl),a
inc hl
dec c
jr nz,.loop3
ex af,af
in a,(cpu_w1)
ld (pg_w1),a
in a,(cpu_w3)
ld (pg_w3),a
in a,(port_y)
ld (old_y),a
ex af,af
out (cpu_w1),a
ret
set_scr_w3: out (cpu_w3),a
ret
set_scr_w1: out (cpu_w1),a
ret
vinit: ld a,81h
ld bc,50h
rst 10h
call cls
call set_global_pal
call CopySCR_1to2
ld a,norm_scr
call set_scr_w3
ret
CopySCR_1to2: sub a
out (port_y), a
ld hl, 0xc000
ld de, 0xc140
ld bc, 320
ld d, d
ld a, 0 ; 256 bytes
ld a, a
; ldir
.loop: REPT 64
ldi
ENDM
jp pe,.loop
ld b, b
ld hl, 0xc3E0
ld de, 0xc3E4
; ld bc, 0004h
ld a, a
ldi
ldi
ldi
ldi
; ldir
ld b, b
ret
cls: ld hl,0xc3E0
ld de,0
ld d,d
ld a,0
ld b,b
ld b,4
.loop1: ld a,d
out (port_y),a
ld e,e
ld (hl),e
ld b,b
inc hl
djnz .loop1
ld hl,0xc000
ld de,0
ld bc,320
ld d,d
ld a,0
ld b,b
.loop2: ld a,d
out (port_y),a
ld e,e
ld (hl),e
ld b,b
inc hl
dec bc
ld a,b
or c
jr nz,.loop2
ret
set_global_pal: ld bc,0ffa4h
ld hl,pal
ld de,0
sub a
rst 8
ret
read_bgspr: ld hl,data_files.bgspr
push hl
call open
pop hl
ld (open_err.err_file+1),hl
jp c,open_err
ld (rd_err.err_file+1),hl
ld (tmp_hndl),a
ld ix,pg_tbl.bgspr
.loop: call inc_pg1
ld a,(tmp_hndl)
ld hl,tbuf_addr
ld de,16320
push ix
call read
pop ix
jp c,rd_err
or a
ret nz
jr .loop
read_bgpal: ld hl,data_files.bgpal
push hl
call open
pop hl
ld (open_err.err_file+1),hl
jp c,open_err
ld (rd_err.err_file+1),hl
ld (tmp_hndl),a
ld hl,pal
ld de,bgspr_pal_size*4
call read
jp c,rd_err
ld a,(tmp_hndl)
call close
ret
read_balls_spr: ld hl,data_files.balls
push hl
call open
pop hl
ld (open_err.err_file+1),hl
jp c,open_err
ld (rd_err.err_file+1),hl
ld (tmp_hndl),a
ld hl,balls_spr
ld de,1024
call read
jp c,rd_err
ld a,(tmp_hndl)
call close
ret
read_balls_pal: ld hl,data_files.balls_pal
push hl
call open
pop hl
ld (open_err.err_file+1),hl
jp c,open_err
ld (rd_err.err_file+1),hl
ld (tmp_hndl),a
ld hl,pal+bgspr_pal_size*4
ld de,balls_pal_size*4
call read
jp c,rd_err
ld a,(tmp_hndl)
call close
ret
balls_reindex: ld hl,balls_spr
ld c,128
ld de,1024
.loop0: ld a,(hl)
inc a
jr z,.loop1
dec a
add a,c
ld (hl),a
.loop1: inc hl
dec de
ld a,e
or d
jr nz,.loop0
ret
balls_init: ld hl,balls_cfg
ld de,balls_cfg+1
ld bc,MAX_BALLS*6
xor a
ld (hl),a
ldir
ld hl,balls_bkp
ld de,balls_bkp+1
ld bc,MAX_BALLS*3
xor a
ld (hl),a
ldir
ld iy,MAX_BALLS
ld ix,balls_cfg
.loop0: ld de,320-16 ; ðàçìåðû êàæäîãî øàðèêà 16íà16
push de
call rand16 ; áåð¸ì ñëó÷àéíóþ êîîðäèíàòó X
pop de
inc hl
push de
call lmod
pop de
ex de,hl
.next0: ld (ix+balls_struct.x),e
ld (ix+balls_struct.x+1),d
ld de,256-16
push de
call rand16
pop de
inc hl
push de
call lmod
pop de
ex de,hl
.next1: ld (ix+balls_struct.y),e
call rand16
ld a,l
and 1
jr nz,.next2
ld a,-1
.next2: ld (ix+balls_struct.dx),a
call rand16
ld a,l
and 1
jr nz,.next3
ld a,-1
.next3: ld (ix+balls_struct.dy),a
ld de,6
add ix,de
dec iy
ld a,iyl
or iyh
jr nz,.loop0
ret
draw_bgspr: ld ix,pg_tbl.bgspr
call inc_pg1
ld hl,tbuf_addr
ld c,51
ld a,0
.loop: ld de,screen_addr
out (port_y),a
ex af,af
di
ld d,d
ld a,0
ld l,l
ld a,(hl)
ld (de),a
ld b,b
inc h
inc d
ld d,d
ld a,64
ld l,l
ld a,(hl)
ld (de),a
ld b,b
ld a,64
add a,l
ld l,a
adc a,h
sub l
ld h,a
dec c
call z,nextpage
ex af,af
inc a
jr nz,.loop
ei
halt
di
ret
nextpage: call inc_pg1
ld hl,tbuf_addr
ld c,51
ret
balls_main:
.loop1: ld iy,MAX_BALLS
ld ix,balls_cfg
call save_coords
push ix
push iy
.loop0: ld e,(ix+balls_struct.x)
ld d,(ix+balls_struct.x+1)
ld hl,320-16
call check_x
ld c,(ix+balls_struct.y)
ld hl,256-16
call check_y
ld a,(ix+balls_struct.dx)
ld h,0
ld l,a
rlca
jr nc,.a1
dec h
.a1: rrca
add hl,de
ex de,hl
ld (ix+balls_struct.x),e
ld (ix+balls_struct.x+1),d
ld a,(ix+balls_struct.dy)
add a,c
ld (ix+balls_struct.y),a
ld de,6
add ix,de
dec iy
ld a,iyl
or iyh
jr nz,.loop0
pop iy
pop ix
ld a,norm_scr|trans_scr|tmp_scr
call set_scr_w3
.loop2: ld hl,balls_spr
ld a,iyl
and 3
ld e,(ix+balls_struct.x)
ld d,(ix+balls_struct.x+1)
ld c,(ix+balls_struct.y)
call draw_balls
ld de,6
add ix,de
dec iy
ld a,iyl
or iyh
jr nz,.loop2
ei
halt
di
in a,(rgmod)
and 1
xor 1
out (rgmod),a
ld a,norm_scr
call set_scr_w3
ld iy,MAX_BALLS
ld ix,balls_bkp
.loop3: ld e,(ix+balls_struct.x)
ld d,(ix+balls_struct.x+1)
ld c,(ix+balls_struct.y)
call restore_bg
ld de,3
add ix,de
dec iy
ld a,iyl
or iyh
jr nz,.loop3
.blah: call check_key
jr c,._ret_
jp .loop1
._ret_: ret
save_coords: push ix
push iy
ld hl,balls_bkp
.loop: ld e,(ix+balls_struct.x)
ld d,(ix+balls_struct.x+1)
ld c,(ix+balls_struct.y)
ld (hl),e
inc hl
ld (hl),d
inc hl
ld (hl),c
inc hl
ld de,6
add ix,de
dec iy
ld a,iyh
or iyl
jr nz,.loop
pop iy
pop ix
ret
restore_bg:
in a,(rgmod)
or a
ld a,0xc0
jr nz,.restore_to_0
ld a,0xc1
jr .restore_to_1
.restore_to_0: add a,d
ld d,a
ld h,d
ld l,e
jr .restore
.restore_to_1: add a,d
ld d,a
ld a,0x40
add a,e
ld e,a
adc a,d
sub e
ld d,a
ld h,d
ld l,e
.restore: ld a,c
out (port_y),a
ld b,16
di
.loop: ld d,d
ld a,16
ld l,l
ld a,(hl)
ld (de),a
ld b,b
in a,(port_y)
inc a
out (port_y),a
djnz .loop
ret
draw_balls: ld b,a
in a,(rgmod)
or a
ld a,0xc1
jr z,.draw_to_1 ; åñëè âêëþ÷¸í 0é ýêðàí, ðèñóåì íà 1é
ld a,0xc0 ; èíà÷å (1é) ðèñóåì â 0é
.draw_to_0: add a,d
ld d,a
jr .draw
.draw_to_1: add a,d
ld d,a
ld a,0x40
add a,e
ld e,a
adc a,d
sub e
ld d,a
.draw: ld a,b
add a,a
add a,a
add a,a
add a,a
add a,l
ld l,a
adc a,h
sub l
ld h,a ; íîìåð ñïðàéòà*256 = àäðåñ ñïðàéòà â êàðòèíêå
ld a,c
out (port_y),a
ld b,16
di
.loop:
ld d,d
ld a,16
ld l,l
ld a,(hl)
ld a,a
ld (de),a
ld b,b
ld a,c
out (port_y),a
ld a,l
add a,64
ld l,a
ld a,0
adc a,h
ld h,a
inc de
djnz .loop
ret
check_x: ld a,e
or d
jr z,.next0
sbc hl,de
jr z,.next1
jr c,.next1
ret
.next0: ld a,1
ld (ix+balls_struct.dx),a
ret
.next1: ld a,-1
ld (ix+balls_struct.dx),a
ret
check_y: ld a,c
or a
jr z,.next0
ld a,l
sub c
jr z,.next1
jr c,.next1
ret
.next0: ld a,1
ld (ix+balls_struct.dy),a
ret
.next1: ld a,-1
ld (ix+balls_struct.dy),a
ret
sync: ei
halt
di
ret
check_key: push hl
push de
push bc
push ix
push iy
ld c,scankey
rst 10h
pop iy
pop ix
pop bc
pop de
pop hl
xor 1bh ; êîä Esc...
ret nz
.esc: scf
ret
pg_tbl:
.balls: db 0
.bgspr: ds 6
.savescr: ds 6
.tbuf: db 0
pg_w0: db 0
pg_w1: db 0
pg_w2: db 0
pg_w3: db 0
old_y: db 0
data_files:
.balls: db "balls.spr",0
.balls_pal: db "balls.act",0
.bgspr: db "bgspr.spr",0
.bgpal: db "bgspr.act",0
tmp_hndl: db 0
open_err_str: db "Can't open file ",0
read_err_str: db "Failed to read file ",0
gmem_err_str: db "PANIC: Can not allocate memory!",cr,lf,0
vm_err_str: db "PANIC: Unable to set videomode!",cr,lf,0
crlf0: db " ",cr,lf,0
pal: equ ($/80h)*80h+80h
balls_spr: equ ((pal+1024)/80h)*80h+80h
balls_cfg: equ ((balls_spr+1024)/80h)*80h+80h
balls_bkp: equ (((balls_cfg)+6*MAX_BALLS)/80h)*80h+80h
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.
+58
View File
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
+38
View File
@@ -0,0 +1,38 @@
Демка была написана как альтернатива аналогичной на evo-sdk (язык C) для zx-evo,
но написано на асме. При этом так же пока писал, разбирался в особенностях работы
графических режимов. Содержание архива:
balls.asm - исходник
dss.inc - различные константы
head.inc - заголовок для формирования exe файла
misc.inc - некоторые "прочие" процедурки.
math.inc - математика, причём тормозная (сдёрнутая из исходника либы для HTC)
make.bat - собиралка. Использование как make src_name.
sjasm.exe - компилятор. Этот меня устраивает.
balls.exe - основной exeшник. На экране 32 шарика.
balls64.exe - 64 шарика.
balls128.exe - 128 шариков.
balls225.exe - 225 шариков.
balls.ect - файл с палитрой для шариков. Хранится в формате 32бита. Выдернуто
из заголовка для bmp шариков.
balls.spr - собсвтенно файл с шариками (спрайты). Ранее это был обычный bmp.
bgspr.act - палитра для фоновой картинка. Так же 32бита выдернутые из bmp.
bgspr.spr - фоновая картинка, тоже бывшая bmp.
Спрайт шариков имеет разрешение 64*16 (т.е. 4 шарика в линию, каждый шарик
16*16), каждый шарик по 32 цвета, т.е. сама картнка с шариками 128 цветов.
Фоновая картинка имеет разрешение 320*256, 128 цветов.
Чтобы получить эти файлы я в фотошопе что нужно было вырезал, отмасштабировал,
"перецветовал" в 8бит на точку, 128 цветов. Шарики собирал по отдельности из
фоновой картинки (просто вырезал из оригинального файла "молекулы", масштабировал,
собирал в одну картинку и потом извращался с цветами. Потом выдернул палитру
через winhex для шариков и фона из их bmp файлов, а из самих bmp потом вырезал
весь заголовок, оставив только данные спрайтов.
Можно спокойно подсунуть свои спрайты фона и шариков.
В демке есть процедура переиндексирования цветов. Первым файлом загружается
фоновая картинка и палитра. Цвета в файле имеют индексы от 0 до 7fh.
Шарики загружаются уже после. Поскольку палитра 256 цветов, а первые 128 уже
заняты под фон, то после загрузки спрайта шариков я делаю переиндексацию. Просто
по всему размеру файла если цвет != 0xff, тогда цвет +=128.
за тормоза в математике сильно не пинать!!!
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

File diff suppressed because one or more lines are too long
+68
View File
@@ -0,0 +1,68 @@
;-------------------------
;dss functions defines
;file functions
fopen equ 11h
fclose equ 12h
fread equ 13h
fwrite equ 14h
move_fp equ 15h
fgetattr equ 16h
fgetdt equ 17h
fsetdt equ 18h
fcreate equ 0ah
fcreaten equ 0bh
chdir equ 1dh
curdir equ 1eh
;memory functions
setwin1 equ 39h
getmem equ 3dh
setmem equ 3fh
freemem equ 3eh
;keyb functions
waitkey equ 30h
scankey equ 31h
quit equ 41h
getarg equ 43h
;vmode functions
setvmode equ 50h
getvmode equ 51h
selvpage equ 54h
;screen and text functions
pchar equ 5bh
pchars equ 5ch
;other
getver equ 0
;end dss defines
;-------------------------
;hardware defines
port_y equ 89h ;port for Y coord
_320p equ 81h ;320 pixels mode
rgmod equ 0c9h
border equ 0feh
rgscr equ 0e9h
rgacc equ 0a9h
norm_scr equ 50h
;trans_scr equ 54h
trans_scr equ 00001000b
tmp_scr equ 00000100b
e_cache equ 0fbh
d_cache equ 7bh
sys_port3c equ 3ch
sys_port7c equ 7ch
cpu_w0 equ 82h ;cpu window 0 = addr 0000h
cpu_w1 equ 0a2h ;... 1 = 4000h
cpu_w2 equ 0c2h ;... 2 = 8000h
cpu_w3 equ 0e2h ;... 3 = 0c000h
;end hardware defines
;-------------------------
;characters
cr equ 0dh
lf equ 0ah
tab equ 9
space equ 20h
;------------------------
+20
View File
@@ -0,0 +1,20 @@
; .Z80
; ASEG
; org 100h
org 8100h-512
db "EXE"
db 0
dw 200h
dw 0
dw 0
dw 0
dw 0
dw 0
dw entry
dw entry
dw 0bfffh
ds 490
; .PHASE 8100h
+20
View File
@@ -0,0 +1,20 @@
@echo off
if "%1" == "" goto error
if EXIST %1.exe (
del %1.exe
)
sjasm.exe -L %1.asm %1.exe %1.lst
if errorlevel 1 goto ERR
echo Ok!
goto END
:ERR
del %1.exe
pause
echo ®è¨¡ª¨ ª®¬¯¨«ï樨...
goto END
:error
echo usage: make sourcefile
:END
+89
View File
@@ -0,0 +1,89 @@
;Input: H = Multiplier, E = Multiplicand, L = 0, D = 0
;Output: HL = Product
mul8_8: ld b,7
sla h ; optimised 1st iteration
jr nc,$+3
ld l,e
.loop: add hl,hl ; unroll 7 times
jr nc,$+3 ; ...
add hl,de ; ...
djnz .loop
ret
lmod: call ldiv
ex de,hl
ret
ldiv: xor a
ex af,af'
ex de,hl
jr dv1
adiv: ld a,h
xor d ;set sign flag for quotient
ld a,h ;get sign of dividend
ex af,af'
call negif
ex de,hl
call negif
dv1: ld b,1
ld a,h
or l
ret z
dv8: push hl
add hl,hl
jr c,dv2
ld a,d
cp h
jr c,dv2
jp nz,dv6
ld a,e
cp l
jr c,dv2
dv6: pop af
inc b
jp dv8
dv2: pop hl
ex de,hl
push hl
ld hl,0
ex (sp),hl
dv4: ld a,h
cp d
jr c,dv3
jp nz,dv5
ld a,l
cp e
jr c,dv3
dv5: sbc hl,de
dv3: ex (sp),hl
ccf
adc hl,hl
srl d
rr e
ex (sp),hl
djnz dv4
pop de
ex de,hl
ex af,af'
call m,negat
ex de,hl
or a ;test remainder sign bit
call m,negat
ex de,hl
ret
negif: bit 7,h
ret z
negat: ld b,h
ld c,l
ld hl,0
or a
sbc hl,bc
ret
+113
View File
@@ -0,0 +1,113 @@
; îáùàÿ ïðîöåäóðà âûõîäà.
; â A ïîìåñòèòü êîä âîçâðàòà.
; Åñëè áûëà îøèáêà, òî ëó÷øå -1 (255)
; Åñëè îøèáêè íåáûëî, òî 0.
quit0: pop ix
ld b,a
ld c,quit
rst 10h
jp $ ; çàãëóøêà
;-[]------------------------------------------------------------
; misc procedures and functions...
;---------------------------------------------------------------
open: ld a,1
ld c,fopen
rst 10h
ret
close: ld c,fclose
rst 10h
ret
read: ld c,fread
rst 10h
ret
;dir_ret: ld hl,cur_dir
; ld c,chdir
; rst 10h
; ret
gmem: ld c,getmem
ld b,1
rst 10h
ret
; ïðîöåäóðà ñîõðàíåíèÿ ñòðàíèöû â óêàçííîì îêíå.
; C = îêíî (ïîðò)
; HL = êóäà ñîõðàíÿòü.
save_pg: in a,(c)
ld (hl),a
ret
; ïðîöåäóðà âîññòàíîâëåíèÿ ñòðàíèöû â óêàçííîì îêíå.
; C = îêíî (ïîðò)
; HL = îò êóäà âîññòàíîâèòü.
restore_pg: ld a,(hl)
out (c),a
ret
; îáðàáîòêà îøèáêè îòêðûòèÿ ôàéëà
open_err:
ld hl,open_err_str
ld c,pchars
rst 10h
.err_file: ld hl,0
ld c,pchars
rst 10h
ld hl,crlf0
ld c,pchars
rst 10h
ld a,-1
jp quit0
; îáðàáîòêà îøèáêè ÷òåíèÿ ôàéëà
rd_err:
ld hl,read_err_str
ld c,pchars
rst 10h
.err_file: ld hl,0
ld c,pchars
rst 10h
ld hl,crlf0
ld c,pchars
rst 10h
ld a,-1
jp quit0
; îáðàáîòêà îøèáêè çàïðîñà ïàìÿòè
gmem_err: ld hl,gmem_err_str
ld c,pchars
rst 10h
ld a,-1
jp quit0
; îáðàáîòêà îøèáêè îòêðûòèÿ âèäåîðåæèìà
vm_err: ld hl,vm_err_str
ld c,pchars
rst 10h
jp quit0
IFDEF debug
debug_print: ld c,pchars
rst 10h
ret
ENDIF
; îáðàáîòêà îøèáêîê òîêåíîâ
;tok_err:
; ld hl,token_err_str
; ld c,pchars
; rst 10h
;.token_ptr: ld hl,0
; ld c,pchars
; rst 10h
; ld hl,crlf0
; ld c,pchars
; rst 10h
; ld a,-1
; jp quit0
Binary file not shown.