Files
VGMPLAY.WMF/doc/TODO_streaming_inflate.md

7.4 KiB
Raw Permalink Blame History

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)

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 страницы перед стартом воспроизведения.