Files
VGMPLAY.WMF/doc/TODO_streaming_inflate.md
T

148 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TODO: Потоковая (streaming) распаковка inflate
**Дата:** 2026-03-28
**Статус:** Идея / Анализ завершён, реализация отложена
## Цель
Заменить текущую полную предварительную распаковку VGZ → мегабуфер
на потоковую (чанковую) распаковку с прерываниями EI, что даст:
- Мгновенный старт воспроизведения (не ждать 2–10 сек)
- Снятие ограничения 1 МБ (мегабуфер) на размер raw VGM
- Возможность играть VGZ файлы > 496 КБ (TAP) через кольцевой буфер
## Ключевые находки
### 1. ISR не использует Win 0 / Win 1 / Win 3
ISR-хэндлер и все данные (cmd_buf_a/b, isr_read_ptr, pos_ptr) живут
исключительно в Win 2 (0x8000–0xBFFF, _CODE + _DATA). Это означает:
**Win 0, Win 1, Win 3 можно свободно переключать при EI.**
### 2. Раскладка окон во время inflate-чанка
| Win | Адреса | Содержимое | Кто использует |
|-----|-----------------|---------------------------|--------------------------|
| 0 | 0x0000–0x3FFF | Source VGZ page (ротация) | inflate: чтение HL' |
| 1 | 0x4000–0x7FFF | Decode buffer (PAGE_DECODE)| inflate: запись DE |
| 2 | 0x8000–0xBFFF | Player code + ISR + DATA | **НЕ ТРОГАЕМ**, ISR тут |
| 3 | 0xC000–0xFFFF | Inflate code + Huffman | inflate: исполнение |
Win 2 сейчас занят decode buffer (#EA) во время inflate.
Для streaming нужно перенести decode buffer из Win 2 в Win 1.
### 3. Self-modifying code = бесплатное сохранение состояния
Все переменные inflate (`src_page`, `dst_page`, `dst`, `interim_dst`,
`this_was_last_block`, Huffman таблицы ~10 КБ) — self-modifying code
на inflate-странице (Win 3). При unmap/remap страницы значения
автоматически сохраняются. Стек (SP = 0xFFF0, адреса возврата
inflate_core → blocktype2 → decode_tableloop) тоже на inflate-странице.
**Нужно сохранять только CPU-регистры (18 байт):**
- AF, BC, DE, HL (8 байт main regs)
- AF', BC', DE', HL' (8 байт alt regs — bit-stream state!)
- IX (2 байта)
- SP хранится в C-переменной caller
### 4. DMA вместо LDIR для page flush
Текущий `copy_decoded_page` (LDIR 16 КБ) = **344064 T** ≈ 49 мс @ 7 МГц.
DMA RAM→RAM (TSCONF_DMA_RAM_RAM = 0x09): **~8000–16000 T** (20–40× быстрее).
Адресация: 22-bit [page(8)][H(6)][L(7)][0].
DMA копирует страницу→страницу без участия CPU и без маппинга окон.
Двойной буфер DMA: пока DMA копирует страницу N, inflate пишет в страницу N+1.
Не нужно ждать флаг TSCONF_DMA_ACT.
### 5. Backref: DMA для cross-page, LDIR для intra-page
~80–90% backrefs внутри текущего буфера → LDIR из Win 1 в Win 1.
Cross-page backrefs (distance > current_offset): DMA из физической
страницы history → физическую страницу decode buffer. Окна не трогаем.
Исключение: overlapping backrefs (distance < length, паттерн "repeat")
DMA не гарантирует побайтную семантику → fallback: temp remap Win 0 = history,
LDIR, restore Win 0 = src.
### 6. Overflow 258 байт — решение для Win 1
Если decode buffer в Win 1 (0x4000–0x7FFF), overflow LDIR пойдёт
в Win 2 (player code). **Нельзя.**
Решение: split-copy перед LDIR. Проверить DE + BC > 0x8000?
Если да — LDIR до границы, DMA flush, сброс DE = 0x4000, LDIR остаток.
Overhead: ~30 T на backref (проверка).
### 7. Yield point: decode_tableloop
Единственная разумная точка yield — вершина `decode_tableloop`.
Добавить yield_counter (self-mod var): dec → jp z, inflate_yield.
Caller задаёт количество 8-символьных батчей за чанк.
### 8. Оценка бюджета
| Параметр | Значение |
|----------------------------------|--------------------------|
| Потребление VGM (типичный) | ~2.5 КБ/сек (50 б/кадр) |
| Скорость inflate @ 7 МГц | ~50–100 КБ/сек |
| Нужный duty cycle | **2.5–5% CPU** |
| Overhead контекста (вход+выход) | ~600 T / 71680 T = 0.8% |
| Запас | 10–20× |
### 9. Кольцевой буфер выхода
```
Megabuffer pages: [P0][P1][P2][P3][P4][P5][P6][P7] ... ring
write_ptr → inflate пишет (producer)
read_ptr → player читает (consumer)
```
- Старт воспроизведения после первых 2–4 готовых страниц
- Нет ограничения 1 МБ: ring buffer кольцуется
- Max VGM raw: 496 КБ × 3 (сжатие) ≈ 1.5 МБ
## Контекст переключения (pseudo-code)
```asm
inflate_enter:
; Save Win 0/1/3 page numbers (from C vars or sys vars)
; OUT #10AF = src_page ; Win 0 = source VGZ
; OUT #11AF = PAGE_DECODE ; Win 1 = decode buffer
; OUT #13AF = inflate_page ; Win 3 = inflate code
; Restore saved CPU regs (18 bytes from save area on inflate page)
; JP resume_point
inflate_yield:
; Save CPU regs to save area on inflate page
; ld sp, (caller_sp)
; Restore Win 0/1/3 from saved values
; ret to main loop
```
## План реализации
1. [ ] Перенести decode buffer из Win 2 в Win 1
2. [ ] Добавить split-copy проверку для LDIR overflow в Win 1
3. [ ] Реализовать yield/resume в decode_tableloop
4. [ ] Заменить LDIR page flush на DMA
5. [ ] Реализовать DMA backref для cross-page
6. [ ] Добавить кольцевой буфер (producer/consumer) в main.c
7. [ ] Добавить inflate_enter / inflate_yield в inflate_call.s
8. [ ] Тестирование с файлами разных размеров и степеней сжатия
## Риски
- makeHuffmanTable: может занять десятки тысяч T-states (непредсказуемо).
При yield внутри него нужен глубокий стек save. Альтернатива:
не yield'ить во время построения таблиц, только во время decode.
- DMA overlap с ISR: ISR не использует DMA, конфликтов нет.
- Первый блок может быть dynamic Huffman → долгое построение таблиц перед
первыми декодированными данными. Mitigation: буферизовать 2–4 страницы
перед стартом воспроизведения.