Дайджест: что изменилось после релиза 1.6

Диапазон ревизий pureikyubu-16 (17 августа 2023) → текущая вершина разработки (11 сентября 2026) · 168 коммитов · pull request'ы #302–#349

Дайджест заканчивается релизом 1.8. О том, что добавил 1.9 — встроенный Game Boy Advance и Game Boy, программный конвейер GFX, новый отладчик, MCP-сервер, headless-сборка, рекомпилятор DSP и аудит безопасности, — читайте в заметках о релизе 1.9.

Релиз 1.6 был снимком эмулятора, который умел работать только под Win32, использовал фиксированный конвейер OpenGL и почти не имел автоматических тестов. Всё, что ниже, произошло после него: у эмулятора появился второй интерфейс пользователя, графический конвейер на шейдерах, стенд модульных тестов, прогоняющий настоящий аппаратный конвейер, загрузка Linux с работающим Broadband Adapter, проверенный по железу звук и длинный список исправлений, которые превратили несколько чёрных экранов в играбельную картинку.

Коротко о главном

Графика

GLSL вместо фиксированного конвейера

XF и TEV эмулируются двумя статическими шейдерами, которые получают всё содержимое регистров через uniform-переменные. Вместе с этим появились все восемь текстурных карт, mip-уровни и исправленные режимы сэмплера.

Тесты

340 тестов GFX + 177 тестов DSP

У обеих подсистем теперь есть наборы CppUnitTest, которые линкуют исходники эмулятора напрямую, плюс HTML-отчёт с 262 отрендеренными эталонными картинками.

Эмуляция

Поведение по документации на железо

ARAM и AI приведены в соответствие с документацией; GQR и paired-single декодируются так, как биты пронумерованы в мануале ISA; декрементер смоделирован как на железе.

Платформы

SDL-сборка выросла

В SDL-интерфейсе появились звук, полноценный селектор игр, диалоговые окна и файловый браузер; сборка работает под Windows и Linux.

Хронология

Графический конвейер: XF и TEV как шейдеры

Графический бэкенд Flipper больше не использует фиксированный конвейер OpenGL. Две программируемые ступени Flipper отображены на две программируемые ступени OpenGL 3.3 core:

Оба шейдера статические и получают состояние регистров через uniform-переменные, поэтому игра может перепрограммировать регистры GFX без перекомпиляции шейдеров. Заодно был перестроен текстурный блок: все восемь текстурных карт декодируются и привязываются независимо (раньше была только одна), исправлены режимы wrap и фильтрации сэмплера (magnification и minification были перепутаны) и генерируются mip-уровни. Растеризатор накапливает вершины в VBO и рисует через glDrawArrays/glDrawElements, потому что в core-профиле OpenGL нет GL_QUADS.

Второй рефакторинг привёл Command Processor к тому виду, который описывает железо: CP больше не залезает внутрь графических блоков. Он передаёт слова команд в XF (CPRegLoadBegin/CPRegLoadData, bypass-слова SU и строки вершин команды рисования), опрашивает готовность XF и получает результаты чтения регистров через CP_XF_DATAL/CP_XF_DATAH. Конвейер теперь строго CP → XF → SU → RAS, а загрузка регистров стала пословной, поэтому частичная загрузка больше не останавливает эмулятор.

Luigi's Mansion на шейдерном конвейере XF/TEV
Luigi's Mansion на шейдерном конвейере.Освещение и комбинирование текстур, которые раньше были зашиты в бэкенд OpenGL, теперь выполняются в эмулируемых шейдерах XF и TEV.

Стенд тестов для всего конвейера GFX

Раньше у графической подсистемы не было автоматических тестов вообще — проверить её можно было только запустив эмулятор и посмотрев на картинку. Новый стенд создаёт окно с настоящим контекстом OpenGL, прогоняет эмулируемый CP через окно регистров PI (то есть display list'ы разбираются по-настоящему), читает EFB обратно и сравнивает пиксели. Поведение вершинной ступени проверяется через transform feedback, а Report::Section/Report::Image публикуют HTML-отчёт рядом с DLL тестов.

Покрытие охватывает CP, XF, SU/RAS, TX, TEV и PE: поток команд и форматы вершин, матрицы, проекцию, вьюпорт, освещение, texgen и dual transform, примитивы, отсечение, scissor, размер линий и точек, привязки TREF, все форматы текселей и палитры, фильтры, режимы wrap, выбор mip, комбинирование цвета и альфы, константы K, туман, Z-окружение и indirect/bump-текстурирование, факторы и уравнения смешивания, логические операции, сравнения глубины, маски записи и очистку копированием. Проходит 340 тестов, а в отчёте — 262 отрендеренные картинки в 43 разделах.

Тесты сразу себя окупили. Среди найденных и исправленных ошибок:

Звук: DSP измеряется по железу

Ядро DSP было проверено по векторам, снятым с самого DSP. В наборе есть тесты для каждой непараллельной инструкции, независимая эталонная модель правил флагов C V Z N E U, 1822 эталонных вектора АЛУ, 435 эталонных векторов для кольцевых буферов, тесты упакованных/параллельных слов и режимов адресации, протокола почтовых ящиков CPU↔DSP и полное покрытие декодирования IROM вместе с опубликованным дизассемблером (testing/DspIrom.md).

И тесты, и разбор дизассемблера выявили настоящие ошибки:

Проход по документации на железо переименовал регистры контроллера ARAM (ACRS/ACWE/…), исправил 32-байтный блочный перенос и маски адресов, переименовал AIDCR в CDCR, применил громкость потока AIVR к каналу DVD-звука (sample × volume / 256, 0x00 — тишина, 0xFF — полная шкала) и защёлкнул операнд упакованной половины памяти в ядре DSP. ARAM DMA теперь выполняется целиком внутри записи младшего слова AMBL, как в движках SI, EXI и DVD, а не нарезается рабочим потоком — именно из-за нарезки терялись запросы звукового DMA Metroid Prime и игра зависала сразу после вступительного ролика.

Gekko, MMU и интерфейс памяти

Расширения GQR и paired-single декодировались с неверной нумерацией бит: в мануале ISA поля регистра нумеруются от старшего бита (LD_SCALE 24:29, LD_TYPE 16:18, ST_SCALE 8:13, ST_TYPE 0:2), поэтому каждый ненулевой GQR интерпретировался неправильно. Заодно исправлены правила преобразования (загрузка масштабируется на 2−S, сохранение вычисляет clamp(trunc(F × 2S)), NaN сохраняется как положительное насыщение, денормаль типа 0 — как 0.0) и условия HID2: индексные формы psq_* требуют только HID2[PSE], неиндексные — PSE и LSQE. Математика преобразований вынесена в чистый заголовок src/gqr.h и покрыта тестами.

Обход хеш-таблицы MMU читал элементы таблицы страниц прямо из памяти, тогда как процессор пишет их через кэш с обратной записью, поэтому только что созданные PTE были не видны, и ядро бесконечно зацикливалось на одном и том же DSI-исключении; теперь ReadHashPte/WriteHashPte делают обход когерентным с кэшем, как на железе. mtcrf присваивал поля операндов в неверном порядке (поле RS использовалось как маска CR, а поле CRM — как номер регистра, что читало gpr[128] за границей массива), а lswi/lswx сохраняли последнее накопленное слово только когда число байт не кратно четырём, что ломало копирование структур, сгенерированное GCC.

Декрементер теперь смоделирован так, как это делает железо: исключение вызывает опустошение (underflow), оно генерируется один раз на опустошение, запрос защёлкивается и переживает период с снятым MSR[EE], положительное значение счётчика снимает отложенный запрос, а при сбросе счётчик отрицательный. Это исправило и тик таймера GC-Linux, и лайвлок IPL/Bootrom, где чисто уровневый запрос бесконечно возвращался в вектор 0x900. Наконец, записи в регистры входного буфера SI принимаются вместо остановки эмуляции, потому что драйвер gcn-si в Linux сбрасывает их при инициализации.

Загрузка GC-Linux

Ядро GC-Linux загружается до консоли gcnfb
GC-Linux на pureikyubu.Ядро 2004 года (2.6.10-rc2-mm4-isobel-nbdc7) загружается до консоли gcnfb; драйвер Broadband Adapter регистрируется, все четыре порта SI определяются.

Загрузка сборки GC-Linux 2004 года и стала тем тестом, который нашёл большинство описанных выше ошибок процессора и интерфейсов: каждая из них останавливала ядро на своей стадии. После исправления mtcrf, когерентного с кэшем обхода MMU, защёлкивающегося декрементера, правильного lswi и не останавливающих SI ядро поднимает консоль gcnfb и печатает чистый dmesg. Драйвер Broadband Adapter инициализируется, а консоль кадрового буфера работает в режиме 640×480×16, NTSC/PAL60 480i.

Интерфейс пользователя и отладчик

SDL-сборка больше не заглушка. У неё собственный интерфейс на ImGui (uisdl.cpp/cuisdl.cpp), встроенный файловый браузер, диалоговые окна и отдельное окно видеовыхода, события которого фильтруются, чтобы перекрытый селектор не реагировал на клики, предназначенные игре. Селектор игр из Win32-версии был портирован:

Селектор игр pureikyubu с библиотекой игр GameCube
Селектор игр.Названия, баннеры, Game ID, размеры и комментарии собираются из образов дисков; переход по первой букве работает по столбцу Title — тому, что пользователь действительно видит.

В отладочном интерфейсе появился сервер JDI (JSON Debugger Interface) и собственное представление для него, экземпляр Flipper передаётся через дерево отладчика, так что каждый блок можно осмотреть на месте, а отладчик умеет загружать меню IPL напрямую — это сильно ускорило работу со звуком.

Совместимость

Metroid Prime

Вступительный ролик оставался на чёрном экране, пока играл звук. Проигрыватель роликов не вызывает GXDrawDone: он рисует, копирует кадр в XFB движком копирования и ждёт обратного хода луча. Теперь копирование всего кадра в дисплейный буфер само является границей кадра, частичные копии по-прежнему ждут PE_FINISH, а кадр выводится, только если в нём есть ещё не показанное содержимое. После этого игра доходит до титульного экрана.

The Legend of Zelda: The Wind Waker

Проходит загрузку. Матричная память XF и цветовые регистры теперь стартуют из состояния, которое задаёт GXInit (проверено по прогону настоящего bootrom), включая единичные матрицы в GX_IDENTITY и GX_DTTIDENTITY, которые игра выбирает по индексу, не загружая их; без этого все текстурные координаты схлопывались в (0, 0).

Ikaruga

Ролик THP декодировался в шахматку из float-фрагментов и рендерился розовым. Исправлено нумерацией полей GQR, разделением констант K ревизии B и цветовых регистров (у них общие id, но разное хранилище) и группировкой одноканальных селекторов K по каналам.

Luigi's Mansion

Работает на шейдерном конвейере и используется как дымовой тест для освещения и комбинирования текстур.

Меню IPL GameCube

Новый ключ --ipl загружает меню консоли без образа диска, а --no-disc запускает эмуляцию с открытым лотком DVD. Это сильно ускорило отладку звука.

PONG и homebrew

Загрузчик DOL/ELF исправлен, и PONG снова запускается — и напрямую, и из селектора игр.

Меню IPL GameCube, отрисованное pureikyubu
Меню IPL.Загружено с ключом --ipl; вращающийся куб отрисован шейдерами XF/TEV.

Инструменты и система сборки

Документация и репозиторий

Что ещё не сделано

Дайджест составлен по истории git между тегом pureikyubu-16 и вершиной разработки. Источники — сообщения коммитов, описания pull request'ов и страницы wiki.