mirror of
https://github.com/Tolik-Trek/DOOM2.git
synced 2026-06-15 09:01:34 +03:00
525 lines
29 KiB
Markdown
525 lines
29 KiB
Markdown
# Руководство по «doom-акселю» Sprinter (z84c15)
|
||
|
||
Расширенная версия аппаратного акселератора Sprinter, использующая
|
||
встроенный в ПЛИС блок **аппаратного растяжения/сжатия линий** (масштабатор).
|
||
Документ составлен по результатам анализа исходников проекта DOOM2
|
||
(`DOOM2.asm`, `D2_FRAM.asm`, `Shared_Includes/macroses/accelerator.z80`,
|
||
`Shared_Includes/constants/SP2000.inc`, `Bin/table_x.tbl`).
|
||
|
||
> Конфигурация `Config_ID.Sp97_DOOM EQU #FFF9`, тип акселератора `3`
|
||
> (`SP2000.inc`, строки ~1573, 1611). Это не «обычный» аксель из
|
||
> `accelerator.z80`, а его надстройка с портом масштабирования `SCALE`.
|
||
|
||
---
|
||
|
||
## 1. Что это такое
|
||
|
||
«doom-аксель» — это автомат в ПЛИС, который перехватывает на шине Z80
|
||
определённые **недокументированные команды-«пустышки»** (`LD r,r`) и команды
|
||
обращения к памяти, идущие сразу за ними. По этим командам он:
|
||
|
||
* копирует/заполняет блоки памяти и видеопамяти быстрее, чем это делает
|
||
процессор покомандно;
|
||
* **на лету масштабирует линию** (растягивает или сжимает по вертикали)
|
||
при выводе в графический экран — это и есть «doom»-расширение.
|
||
|
||
Ключевой приём DOOM: вертикальный столбец текстуры стены (64 тексела)
|
||
аппаратно растягивается/сжимается в столбец произвольной экранной высоты за
|
||
один проход, без таблиц переходов и без покомандного цикла на Z80.
|
||
|
||
Принцип работы автомата:
|
||
|
||
1. команда-триггер (`LD r,r`) переводит аксель в нужный **режим**;
|
||
2. **следующая** команда обращения к памяти (`LD A,(HL)`, `LD (DE),A`,
|
||
`LD (HL),A`, `PUSH`, …) выполняется уже «через аксель» и обрабатывает
|
||
**весь блок**, а не один байт;
|
||
3. команда `ACC_Off` (`LD B,B`) возвращает аксель в обычный режим.
|
||
|
||
Между шагом 1 и шагом 2 можно ставить любые команды, **не обращающиеся к
|
||
памяти данных** (загрузка регистров константами, работа с портами, `OUT`,
|
||
`IN`, арифметика). Именно так в коде между триггером и «рабочей» командой
|
||
успевают задать порт `SCALE`, `PORT_Y`, страницу видео-ОЗУ и т.д.
|
||
|
||
### 1.1. Что именно «слушает» аксель
|
||
|
||
Аксель физически подключён к **шине данных и шине адреса** процессора.
|
||
|
||
* Команда-триггер распознаётся по сигналу **`M1`** (цикл выборки опкода):
|
||
как только по `M1` с шины считывается код одной из команд активации —
|
||
аксель переходит в соответствующий режим.
|
||
* Дальше, **в зависимости от режима**, он ловит:
|
||
* `ACC_SetBlockSize` → значение на **шине данных** (длину берёт из байта,
|
||
который пересылает следующая команда загрузки);
|
||
* команды пересылки/заливки (работа с буфером акселя) → адрес на
|
||
**шине адреса** (по нему он сам прогоняет блок по памяти).
|
||
|
||
### 1.2. ВАЖНО: аксель работает только с SIMM-памятью
|
||
|
||
Аксель умеет обрабатывать данные **только из физической динамической
|
||
SIMM-памяти** компьютера. **Fast-RAM** — это быстрый статический **кэш**,
|
||
который как источник/приёмник блочной пересылки акселя **не работает**.
|
||
|
||
Fast-RAM **не «прибита» к нулевому окну** — это переключаемый кэш
|
||
(подробно — `Shared_Includes/constants/SP2000.inc`, метка `FastRAM`):
|
||
|
||
* `IN A,(FastRAM.ON = #FB)` — **включить** кэш (он появляется в нулевом
|
||
окне `#0000–#3FFF`);
|
||
* `IN A,(FastRAM.OFF = #7B)` — **выключить** (в нулевом окне снова
|
||
ПЗУ/обычная память);
|
||
* `OUT (FastRAM.SLOT0 = #5C)` — выбор страницы кэша, `bit0..1`
|
||
(работает только при `SYS_PORT.ROM`).
|
||
|
||
То есть статический кэш присутствует в окне `#0000–#3FFF` **только пока
|
||
включён** через `#FB`. Если в этот момент адресовать туда блочную
|
||
пересылку акселя — она **не сработает** (там не SIMM). Когда кэш выключен
|
||
(`#7B`), то же окно — обычная SIMM, и аксель с ним работает нормально.
|
||
Правило: операнд-источник/приёмник блочной пересылки не должен попадать
|
||
на **включённый** статический кэш.
|
||
|
||
Нюансы (важны для размещения кода):
|
||
|
||
* **Управлять** акселем из кода, исполняемого в Fast-RAM, **можно** — при
|
||
условии, что сама пересылка адресует SIMM (например, текстуры в страницах
|
||
SIMM и видео-ОЗУ в окне `#C000`).
|
||
* `ACC_SetBlockSize` из Fast-RAM работает **полноценно**, потому что длина
|
||
снимается с **шины данных** процессора, а не читается из памяти.
|
||
|
||
Именно поэтому в DOOM рендер (`D2_FRAM.asm`, `ORG #1000`, нулевое окно)
|
||
перебрасывается в Fast-RAM (`DOOM2.asm`: `IN A,(FastRAM.ON)` → `LDIR` →
|
||
код по `#1000`) и работает быстро, но **все** блочные пересылки/заливки
|
||
акселя адресуют SIMM: текстуры стен в страницах SIMM, экран в окне
|
||
`#C000`. Буфер строкового Z-буфера и т.п. тоже лежат в SIMM.
|
||
|
||
---
|
||
|
||
## 2. Команды-триггеры (макросы `accelerator.z80`)
|
||
|
||
Все триггеры — это легальные опкоды Z80, которые без акселя являются
|
||
безвредными NOP-ами (`LD r,r`). Аксель ловит их по коду на шине.
|
||
|
||
| Макрос | Команда | Опкод | Назначение |
|
||
|---|---|---|---|
|
||
| `ACC_Off` | `LD B,B` | `#40` | выключить аксель (обязательно после каждой операции) |
|
||
| `ACC_SetBlockSize` | `LD D,D` | `#52` | след. загрузка задаёт длину блока (0 → 256) |
|
||
| `ACC_FillOneByte` | `LD C,C` | `#49` | заполнить блок одним байтом (линейные адреса) |
|
||
| `ACC_FillScreenOneByte`| `LD E,E` | `#5B` | заполнить вертикальную линию экрана одним байтом |
|
||
| `ACC_CopyBlock` | `LD L,L` | `#6D` | копирование блока через буфер акселя (линейно) |
|
||
| `ACC_CopyScreenBlock` | `LD A,A` | `#7F` | копирование блока в экран **верт. линиями + масштаб** |
|
||
| `ACC_DoubleByte` | `LD H,H` | `#64` | дублирование байта в (Addr&#FE) и (Addr&#FE)+1 |
|
||
| `ACC_Halt` | `HALT` | `#76` | зарезервировано (ведёт себя как `LD B,B`) |
|
||
|
||
Важные следствия:
|
||
|
||
* `ACC_Off` нужен после **каждой** акселерированной операции. Без него
|
||
следующее обращение к памяти будет интерпретировано акселем.
|
||
* Опкоды `#40/#7F/#6D/...` — это `LD B,B`, `LD A,A`, `LD L,L`. Не вставляйте
|
||
«настоящие» `LD A,A`/`LD B,B` в горячий код рядом — они тоже триггеры.
|
||
|
||
---
|
||
|
||
## 3. Длина блока (`ACC_SetBlockSize`)
|
||
|
||
```asm
|
||
ACC_SetBlockSize
|
||
LD A,0 ; 0 трактуется как 256 байт
|
||
ACC_Off
|
||
```
|
||
|
||
Длину берёт **значение, перенесённое следующей командой загрузки**, в каком
|
||
угодно регистре:
|
||
|
||
```asm
|
||
ACC_SetBlockSize
|
||
LD B,#80 ; блок = 128
|
||
ACC_Off
|
||
|
||
ACC_SetBlockSize
|
||
LD C,119 ; блок = 119 (длина спрайта оружия, D2_FRAM.asm)
|
||
ACC_Off
|
||
```
|
||
|
||
Особый приём (`D2_FRAM.asm`, метка `HIGH_1`): запись `LD (DE),A` в режиме
|
||
`ACC_SetBlockSize` **одновременно** задаёт длину блока (из `A`) **и**
|
||
выполняет реальную запись `A` по адресу `(DE)`. В DOOM так одной командой
|
||
одновременно задаётся высота стены и пишется маркер в строковый Z-буфер:
|
||
|
||
```asm
|
||
ACC_SetBlockSize
|
||
LD (DE),A ; A = длина блока И запись в LINE-Z-Buffer
|
||
ACC_Off
|
||
```
|
||
|
||
Установленная длина **сохраняется** до следующего `ACC_SetBlockSize`, её не
|
||
нужно задавать перед каждой операцией.
|
||
|
||
---
|
||
|
||
## 4. Графический экран и `PORT_Y`
|
||
|
||
* `PORT_Y EQU #C4` (внешний `#89`) — **вертикальная** координата (номер
|
||
строки) точки на графическом экране.
|
||
* **Горизонталь** задаётся адресом в окне `#C000–#FFFF` (1 байт = 1 пиксель,
|
||
режим 8 бит/точку).
|
||
* Страница видео-ОЗУ выбирается через `SLOT3` (в DOOM: `#58` — выводимый
|
||
кадр, `#50` — служебная страница/Z-буфер, `#5C` — рабочая).
|
||
* В проекте два экранных буфера: `ScreenStartAddress = #C040` и
|
||
`ScreenStartAddress + #0140`; переключение — порт `SCREEN_SWITCH`
|
||
(`RGMOD`, `#C9`).
|
||
|
||
«Экранные» режимы акселя (`ACC_*Screen*`) при чтении/записи инкрементируют
|
||
**не адрес, а `PORT_Y`** — то есть обрабатывают **вертикальную линию**
|
||
(столбец) пикселей при фиксированном адресе-горизонтали. Это основа вывода
|
||
стен/пола/неба в DOOM.
|
||
|
||
---
|
||
|
||
## 5. Порт масштабирования `SCALE` — главное «doom»-расширение
|
||
|
||
### 5.1. Включение
|
||
|
||
Внутренний регистр акселя `ACEX.SCALE = #C7` (он же `PORT_SCALE = #FC`).
|
||
Перед использованием его нужно «открыть» через BIOS-функцию управления
|
||
дешифратором портов `DCP_CONFIG (#F4)`. Дешифрация порта масштабирования
|
||
выполняется по схеме **DCP (карта портов)** — её полное описание см. в
|
||
`Shared_Includes/Docs/SP2000.PDF` (раздел про карту портов / DCP).
|
||
Практически: порт выбирается, когда **младший байт адреса порта = 0**, а
|
||
**вся пара `BC` несёт 16-битный коэффициент** (см. §5.2). Из `DOOM2.asm`
|
||
(≈стр. 165–176):
|
||
|
||
```asm
|
||
;----[открыть порты масштабирования]----
|
||
LD A,1
|
||
LD HL, %0000'0100'0000'0000 ; значение
|
||
LD DE, %1111'1110'0110'1111 ; маска (0 = изменяемый бит) = #FE6F
|
||
LD BC, ACEX.SCALE*256 + BIOS.DCP_CONFIG ; B=#C7 (порт), C=#F4 (ф-ция)
|
||
RST ToBIOS
|
||
; Активация необратима до RESET
|
||
LD BC,#0100 ; коэффициент #0100 ...
|
||
OUT (C),C ; ... = 2.8-шаг 1.000 -> масштаб 1:1
|
||
```
|
||
|
||
> После активации обратной дороги нет — выключается только сбросом.
|
||
> Внимание: «масштаб 1:1» — это коэффициент **`#0100`**, а не `0`.
|
||
|
||
### 5.2. Как писать в `SCALE` (16-битный коэффициент)
|
||
|
||
В порт масштабирования пишется **16-битное число** по штатной схеме Z80
|
||
`OUT (C),r`: **старший байт** коэффициента кладётся в регистр `B` (он
|
||
выходит на линии адреса `A8..A15`), **младший байт** — это данные `OUT`
|
||
(содержимое записываемого регистра). `C` держим = 0 — это выбор порта
|
||
SCALE по DCP.
|
||
|
||
```asm
|
||
LD B, coeff_hi ; старший байт коэффициента -> A8..A15
|
||
LD C, 0 ; младший байт адреса порта = 0 -> выбор SCALE (DCP)
|
||
LD A, coeff_lo ; младший байт коэффициента
|
||
OUT (C),A ; 16-битный коэффициент = B*256 + A
|
||
```
|
||
|
||
Варианты из реального кода:
|
||
|
||
* `LD BC,#0100 : OUT (C),C` — коэффициент `#0100` (данные = `C` = 0,
|
||
старший байт `B` = 1). Это и есть **1:1**.
|
||
* `LD B,(hl_hi) … LD C,0 … OUT (C),L` (`TRACE_CONT`) — коэффициент
|
||
`B*256 + L`, где `B` взят из старшей плоскости таблицы, `L` — из младшей.
|
||
|
||
Важно: в форме `OUT (C),C` данные равны `C`, поэтому младший байт там
|
||
всегда 0; для произвольного младшего байта берите другой регистр
|
||
(`A`/`L`/…), а `C` оставляйте = 0 (выбор порта).
|
||
|
||
### 5.3. Раскладка битов коэффициента
|
||
|
||
16-битный коэффициент `coeff = B*256 + (младший байт)`:
|
||
|
||
| Биты | Поле | Смысл |
|
||
|---|---|---|
|
||
| `bit[15..10]` (6 бит) | смещение в буфере | стартовая позиция чтения в 256-байтовом буфере акселя, 0..63 (вертикальный клиппинг/центровка текстуры у близких и полноэкранных стен) |
|
||
| `bit[9..0]` (10 бит) | шаг **2.8** | дробный шаг чтения по буферу на каждый выходной пиксель: 2 бита целой части, 8 бит дробной (значение `/256`) |
|
||
|
||
* `шаг = 1.000` ⟺ `bit[9..0] = 256` ⟺ коэффициент `#0100` при нулевом
|
||
смещении — копирование **1:1** (именно его пишет DOOM как «масштаб 1:1»).
|
||
* `шаг < 1.0` — **растяжение** (исходный тексел повторяется на нескольких
|
||
выходных пикселях);
|
||
* `шаг > 1.0` — **сжатие** (часть текселей пропускается);
|
||
* `шаг = 0` — предельное растяжение (один тексел на всю линию).
|
||
|
||
Коэффициент **вычислим напрямую**:
|
||
|
||
```
|
||
coeff = (offset << 10) | round(step * 256) ; step в формате 2.8
|
||
```
|
||
|
||
Таблица `Bin/table_x.tbl` (грузится в `TABLE_X = #A000`, размер `#0800`) —
|
||
это лишь заранее посчитанный LUT по индексу высоты столбца. Парные плоскости
|
||
по 256 байт: плоскость `#A000` = **старший байт** коэффициента (→ `B`),
|
||
плоскость `#A100` = **младший байт** (→ данные `OUT`). Проверенные значения
|
||
(`coeff = #A000[idx]*256 + #A100[idx]`):
|
||
|
||
| idx | `#A000`→B | `#A100`→data | coeff | смещение `b15..10` | шаг 2.8 `b9..0` |
|
||
|---:|---:|---:|---:|---:|---:|
|
||
| 1 | 0 | 64 | `#0040` | 0 | 0.250 |
|
||
| 32 | 0 | 85 | `#0055` | 0 | 0.332 |
|
||
| 64 | 0 | 128 | `#0080` | 0 | 0.500 |
|
||
| 96 | 1 | 0 | `#0100` | 0 | **1.000 (1:1)** |
|
||
| 160 | 32 | 48 | `#2030` | 8 | 0.188 |
|
||
| 192 | 64 | 32 | `#4020` | 16 | 0.125 |
|
||
| 224 | 96 | 16 | `#6010` | 24 | 0.0625 |
|
||
| 255 | 124 | 0 | `#7C00` | 31 | 0.000 |
|
||
|
||
Разбор по биту 7 индекса: индексы `0..127` — стена частичной высоты
|
||
(смещение в буфере = 0), индексы `128..255` — стена «на весь экран»
|
||
(растёт смещение в буфере: 0,8,16,24,28,31 — текстура поднимается/клиппится
|
||
по вертикали). См. `D2_FRAM.asm`, метки `TRACE_CONT`, `HIGH_1`, `WALL`.
|
||
|
||
---
|
||
|
||
## 6. Буфер-линия акселя
|
||
|
||
У акселя есть собственное ОЗУ-буфер на одну линию/блок размером **ровно
|
||
256 байт**. Старшие 6 бит коэффициента (`bit[15..10]`, см. §5.3) задают
|
||
стартовое смещение чтения внутри этого буфера. Типовой цикл
|
||
«текстура → экран»:
|
||
|
||
1. **Заливка буфера (без масштаба):**
|
||
```asm
|
||
ACC_SetBlockSize
|
||
LD A,#40 ; 64 байта исходной текстуры
|
||
ACC_CopyBlock
|
||
LD A,(HL) ; читать 64 байта из (HL) в буфер акселя
|
||
ACC_Off ; столбец текстуры теперь в ОЗУ акселя
|
||
```
|
||
Чтение в буфер всегда **немасштабированное** (комментарий
|
||
«взять немасштабированно!» в `TRACE_CONT`).
|
||
|
||
2. **Задать масштаб** — 16-битный коэффициент (старший байт в `B`,
|
||
младший — данные `OUT`, `C` = 0); см. §5.2/§5.3.
|
||
|
||
3. **Задать длину выводимого блока** (`ACC_SetBlockSize`, число экранных
|
||
пикселей по вертикали).
|
||
|
||
4. **Вывод в экран с масштабом:**
|
||
```asm
|
||
OUT (PORT_Y),A ; стартовая строка
|
||
ACC_CopyScreenBlock
|
||
LD (DE),A ; буфер -> столбец экрана (DE = X), масштаб
|
||
ACC_Off
|
||
```
|
||
Аксель читает 64 тексела из буфера, применяет дробный шаг `SCALE` и
|
||
пишет `N` пикселей в столбец, инкрементируя `PORT_Y`.
|
||
|
||
---
|
||
|
||
## 7. Готовые рецепты из проекта
|
||
|
||
### 7.1. Столбец стены DOOM (растяжение/сжатие) — `D2_FRAM.asm`
|
||
|
||
```asm
|
||
; A = код высоты столбца (индекс в TABLE_X)
|
||
LD H,TABLE_X/256 ; #A0
|
||
LD L,A
|
||
LD B,(HL) ; СТАРШИЙ байт 16-битного коэффициента -> B
|
||
INC H ; #A1
|
||
LD L,(HL) ; МЛАДШИЙ байт коэффициента -> L
|
||
OUT (C),L ; SCALE = B*256+L (C уже = 0: выбор порта)
|
||
; ... буфер столбца текстуры уже залит ACC_CopyBlock-ом ...
|
||
HIGH_1: ; стена не на весь экран
|
||
ACC_SetBlockSize
|
||
LD (DE),A ; высота + маркер в LINE-Z-Buffer
|
||
ACC_Off
|
||
NEG
|
||
OUT (PORT_Y),A ; позиция начала пола
|
||
; ... заливка пола сплошным цветом (см. 7.2) ...
|
||
LD A,L
|
||
NEG
|
||
OUT (PORT_Y),A ; вернуться к началу стены
|
||
LD A,L
|
||
ADD A,A ; экранная высота стены = 2*L
|
||
ACC_SetBlockSize
|
||
LD (DE),A
|
||
ACC_CopyScreenBlock
|
||
LD (DE),A ; растянуть/сжать столбец в экран!
|
||
ACC_Off
|
||
```
|
||
|
||
Полноэкранный вариант (код высоты ≥ 128, бит 7 = 1):
|
||
|
||
```asm
|
||
XOR A
|
||
OUT (PORT_Y),A
|
||
ACC_SetBlockSize
|
||
LD (DE),A ; 256 строк (0 -> 256) + Z-buffer
|
||
ACC_Off
|
||
LD A,#58
|
||
OUT (SLOT3),A ; страница видео-ОЗУ
|
||
ACC_CopyScreenBlock
|
||
LD (DE),A ; весь столбец 256 px за раз
|
||
ACC_Off
|
||
```
|
||
|
||
### 7.2. Заливка вертикали сплошным цветом (пол/потолок) — `D2_FRAM.asm`
|
||
|
||
```asm
|
||
OUT (PORT_Y),A ; стартовая строка
|
||
ACC_SetBlockSize
|
||
LD (DE),A ; число пикселей (если ещё не задано)
|
||
ACC_FillScreenOneByte
|
||
LD (DE),A ; A = цвет, заполнить столбец
|
||
ACC_Off
|
||
```
|
||
|
||
### 7.3. Очистка графического экрана стеком — `DOOM2.asm CLEAR_GRAF_SCR`
|
||
|
||
```asm
|
||
LD A,#50
|
||
OUT (SLOT3),A
|
||
XOR A
|
||
OUT (PORT_Y),A
|
||
LD SP,ScreenStartAddress + 640
|
||
LD B,640/4
|
||
ACC_SetBlockSize
|
||
LD E,0 ; блок = 256
|
||
LD D,E
|
||
LOOP_CLS:
|
||
ACC_FillScreenOneByte
|
||
PUSH DE ; PUSH тоже «обращение к памяти» -> заливка
|
||
PUSH DE
|
||
ACC_Off
|
||
DJNZ LOOP_CLS
|
||
```
|
||
|
||
### 7.4. Очистка строкового Z-буфера — `D2_FRAM.asm CLEAR_Z_BUFER`
|
||
|
||
```asm
|
||
ACC_SetBlockSize
|
||
LD A,0 ; 256
|
||
ACC_FillOneByte
|
||
LD (DE),A ; линейная заливка нулём
|
||
INC D
|
||
ACC_SetBlockSize
|
||
LD B,#40 ; ещё 64 байта
|
||
ACC_FillOneByte
|
||
LD (DE),A
|
||
ACC_Off
|
||
```
|
||
|
||
### 7.5. Копирование картинки 320×256 в экран — `D2_FRAM.asm SET_PICTURE`
|
||
|
||
```asm
|
||
ld bc,#0100
|
||
OUT (C),C ; масштаб 1:1
|
||
ACC_SetBlockSize
|
||
LD A,0 ; 256
|
||
ACC_Off
|
||
OUT (PORT_Y),A
|
||
LOOP_PG:
|
||
ACC_CopyBlock
|
||
LD A,(HL) ; 256 байт из (HL) в буфер
|
||
ACC_CopyScreenBlock
|
||
LD (DE),A ; столбец экрана (256 px, без масштаба)
|
||
ACC_Off
|
||
INC DE ; следующий столбец (X+1)
|
||
INC H ; следующие 256 байт источника
|
||
DJNZ LOOP_PG
|
||
```
|
||
|
||
### 7.6. Горизонтальный спрайт без масштаба (оружие) — `D2_FRAM.asm WEAPON_OUT`
|
||
|
||
```asm
|
||
ACC_SetBlockSize
|
||
LD C,119 ; длина спрайта
|
||
ACC_Off
|
||
W_OUT_L:
|
||
LD A,#44
|
||
SUB H
|
||
OUT (PORT_Y),A
|
||
OUT (C),C ; масштаб 1:1, начать с 0
|
||
ACC_CopyBlock
|
||
LD A,(HL) ; строка спрайта -> буфер
|
||
ACC_Off
|
||
OUT (C),C
|
||
ACC_CopyBlock
|
||
LD (DE),A ; буфер -> экран
|
||
ACC_Off
|
||
INC H
|
||
BIT 7,H
|
||
JR Z,W_OUT_L
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Типовая последовательность (шпаргалка)
|
||
|
||
```
|
||
[OUT (SLOT3),video_page] ; выбрать страницу видео-ОЗУ
|
||
ACC_SetBlockSize / LD r,len / - ; длина блока (0 = 256)
|
||
ACC_CopyBlock / LD A,(HL) / OFF ; заполнить буфер акселя (немасштаб.)
|
||
LD B,hi / LD C,0 / LD A,lo / OUT (C),A ; 16-битный коэффициент B*256+lo
|
||
; (#0100 = 2.8-шаг 1.0 = 1:1)
|
||
ACC_SetBlockSize / LD r,out_len/- ; сколько пикселей выводить
|
||
OUT (PORT_Y),y_start ; стартовая строка
|
||
ACC_CopyScreenBlock / LD (DE),A /OFF; вывод столбца с масштабом
|
||
ACC_Off ; ОБЯЗАТЕЛЬНО
|
||
```
|
||
|
||
---
|
||
|
||
## 9. Подводные камни
|
||
|
||
* **Только SIMM-память.** Блочные пересылки/заливки акселя должны
|
||
адресовать динамическую SIMM-память. Fast-RAM — переключаемый
|
||
статический кэш (появляется в нулевом окне `#0000–#3FFF` только пока
|
||
включён через `IN A,(#FB)`); как источник/приёмник пересылки он
|
||
**не работает**. Управлять акселем из кода в Fast-RAM можно, а
|
||
`ACC_SetBlockSize` из Fast-RAM работает полноценно (длина — с шины
|
||
данных). См. §1.2.
|
||
* **Всегда `ACC_Off`** после операции, иначе ближайшее обращение к памяти
|
||
«съест» аксель и разрушит данные.
|
||
* Между триггером и рабочей командой **нельзя обращаться к памяти данных**
|
||
(можно: `LD r,const`, `OUT`, `IN`, арифметика, `EXX`, `EX AF,AF'`).
|
||
* В горячем коде не используйте «настоящие» `LD A,A`, `LD B,B`, `LD H,H`
|
||
и т.п. — это команды акселя.
|
||
* `SCALE` активируется один раз и навсегда (до RESET); порт декодируется по
|
||
младшему байту адреса = 0 — не выводите туда ничего «случайно»
|
||
(любой `OUT (C),r` при `C=0`).
|
||
* Длина блока `0` означает **256**, а не «ничего».
|
||
* В `ACC_SetBlockSize` команда-запись (`LD (DE),A`) и задаёт длину, и
|
||
реально пишет байт — учитывайте побочную запись (DOOM использует её
|
||
специально под Z-буфер).
|
||
* `SCALE` — **16-битный** коэффициент: старший байт едет в регистре `B`
|
||
(на адрес порта `A8..A15`), младший — данные `OUT`; `C` = 0 (выбор
|
||
порта). Не путать с «одним байтом». Для следующей линии с другой
|
||
высотой коэффициент нужно переустановить.
|
||
* Значение `SCALE` **вычислимо**: `coeff = (offset << 10) | round(step*256)`,
|
||
где `step` — формат 2.8. `table_x.tbl` — лишь удобный прекалк (плоскость
|
||
`#A000` → `B`, плоскость `#A100` → данные `OUT`).
|
||
* «Масштаб 1:1» — это коэффициент **`#0100`** (2.8-шаг = 1.000), **не `0`**.
|
||
Перед операциями без масштаба ставьте `LD BC,#0100 : OUT (C),C`, иначе
|
||
картинка/спрайт исказятся. Коэффициент `0` = предельное растяжение.
|
||
|
||
---
|
||
|
||
## 10. Карта сущностей
|
||
|
||
| Имя | Значение | Где |
|
||
|---|---|---|
|
||
| `PORT_Y` | `#C4` (внеш. `#89`) | вертикальная координата |
|
||
| `SCALE`/`PORT_SCALE` | `#C7` / `#FC`, внеш. `#XX00` | порт масштаба; 16-бит `B:data`, `b15..10`=смещение в буфере, `b9..0`=2.8-шаг |
|
||
| `RGMOD`/`SCREEN_SWITCH` | `#C9` | переключение экранных страниц |
|
||
| `SLOT3` | окно `#C000–#FFFF` | выбор страницы видео-ОЗУ |
|
||
| `BIOS.DCP_CONFIG` | `#F4` | открытие порта `SCALE` |
|
||
| `ACEX.SCALE` | `#C7` | внутр. номер порта масштаба |
|
||
| `TABLE_X` | `#A000`, разм. `#0800` | таблица коэффициентов (`table_x.tbl`) |
|
||
| `ScreenStartAddress` | `#C040` (+`#0140` 2-й буфер) | старт экрана |
|
||
| `Config_ID.Sp97_DOOM` | `#FFF9`, тип акселя `3` | идентификатор конфигурации |
|
||
|
||
Ключевые места в коде: `D2_FRAM.asm` — `TRACE`/`TRACE_CONT`/`HIGH_1`
|
||
(растеризация стен с масштабом), `WALL` (спрайты-монстры),
|
||
`CLEAR_Z_BUFER`, `SET_PICTURE`, `WEAPON_OUT`, `SKY_LOOP_1`;
|
||
`DOOM2.asm` — активация `SCALE` (≈стр. 165–176), `CLEAR_GRAF_SCR`,
|
||
`INIT_TABLE` (загрузка `table_x.tbl`).
|
||
|
||
Дешифрация порта масштабирования (схема DCP / карта портов) — подробно в
|
||
`Shared_Includes/Docs/SP2000.PDF` (раздел про карту портов / DCP);
|
||
конкретная инициализация декодера — вызов `BIOS.DCP_CONFIG` в `DOOM2.asm`
|
||
(маска `#FE6F`, значение `#0400`, порт `ACEX.SCALE #C7`).
|