pureikyubu 1.8 — заметки о релизе
Версия 1.8 — релиз производительности, и большая часть прироста получилась не добавлением работы, а её устранением. У Gekko появился рекомпилятор в x86-64, поднимающий эмулируемый процессор выше 150 MIPS; периодические потоки устройств перестали крутиться в ожидании на тайм-базе, а агрегатные линии прерываний DSP, дискового интерфейса и последовательного порта были переписаны по документации на железо. Последнее важнее цифр: вместе с грозой прерываний ушли в прошлое чёрные экраны — Zelda: The Wind Waker доходит до титульного экрана, а Animal Crossing наконец покидает свой обработчик исключений. В релизе также появились сжатые образы дисков RVZ, полноценное окно настройки контроллеров в SDL-сборке и новая иконка приложения.
На этой странице
Главное
- Рекомпилятор Gekko. Новый компилятор базовых блоков Gekko в x86-64
(
src/gekkojit.*) держит гостевые GPR в регистрах хоста и использует общий с интерпретатором кэш декодирования для всего, что не транслируется. Замеры на дифференциальном стенде: 39.7 MIPS у исходного интерпретатора, 58.6 у интерпретатора с настоящим инлайном и 155.9 с рекомпилятором; на коде, насыщенном АЛУ, выигрыш 8.6x. Команды отладчикаjit 0/jit 1переключают движки на ходу. - Paired-Single на SSE. Все арифметические инструкции PS и обе
квантизованные загрузки/сохранения (
psq_l/psq_st) транслируются в SSE/SSE2. Нагрузка с преобладанием PS растёт с 59 MIPS (интерпретатор) до 435 MIPS, нагрузка из квантизованных загрузок — с 57 до 191. - Больше никакого активного ожидания. Развёртка VI и опрос последовательного
порта теперь выполняются потоком процессора на том же тике, который он и так продвигает, а
потоки CP, AI DMA и DSP блокируются на событиях вместо вращения на
Core->GetTicks().pong.dolвырос с 1.47x до 3.66x реального времени, Ikaruga — с 2.16x до 4.58x, Luigi's Mansion — с 0.62x (полная остановка) до 4.26x. - Агрегатные линии прерываний заработали. Линии Processor Interface у DSP и дискового интерфейса объединяли биты причин со своими же битами масок по И, а это всегда ноль; теперь обе следуют ИЛИ защёлкнутых и немаскированных причин. Последовательный порт опрашивается по расписанию видеолиний вместо фиксированного тика, что убирает шторм прерываний, запирающий игры внутри обработчика.
- Игры. Zelda: The Wind Waker доходит до титульного экрана, Metroid Prime показывает титульный экран; Animal Crossing проходит рукопожатие ARQ в звуковом драйвере, читает диск и показывает логотип Nintendo.
- Образы RVZ. Сжатые образы GameCube в контейнере RVZ из Dolphin (Zstandard) монтируются только для чтения — так же, как ISO или GCM.
- Настройка контроллеров в SDL. У SDL-сборки появился собственный бэкенд контроллеров и полное окно «Configure Controller N»: привязки клавиатуры и геймпада рядом, аналоговые стики и триггеры, кнопки Clear / Default / OK / Cancel.
- Новая иконка. Иконка-куб перекрашена в синюю палитру логотипа и заново
сгенерирована как
pureikyubu.ico(16–256 px, 32 bpp); SDL-сборки встраивают те же пиксели и вызываютSDL_SetWindowIcon.
Добавлено
Gekko
- Рекомпилятор базовых блоков Gekko в x86-64 (
src/gekkojit.h,src/gekkojit.cpp). Каждый блок запоминает состояние трансляции (счётчик поколений), при котором он был скомпилирован, поэтому запись в BAT,SDR1,HID0/HID2илиMSR, а такжеrfi,tlbie,icbi,dcbstили сброс кэша отбрасывают все блоки за O(1). Нетранслированные инструкции идут черезInterpreter::ExecuteDecodedи общий с интерпретатором кэш декодирования, так что оба движка декодируют одинаково по построению. - Кэш декодирования на 8192 записи с ключом из pc и выбранного слова инструкции и
прямоадресуемый TLB на 2048 трансляций страниц вместо
unordered_map, выделявшего поTLBEntryна каждую отображённую страницу. - Трансляция Paired-Single (
src/gekkojit_ps.cpp, отключается через-DGEKKO_JIT_PS=0):add,sub,mul,div,res,rsqrte,madd,msub,nmadd,nmsub,muls0/1,madds0/1,sum0/1,sel,mr,neg,abs,nabs,merge00/01/10/11и формы с записью результата (_d). Кодер x86-64, роли регистров хоста и смещенияGekkoRegsпереехали вsrc/gekkojit_x64.h. - Квантизованные парные загрузки и сохранения (
psq_l/psq_st): все восемь форм — индексные и неиндексные, с обновлением и без, W = 0 и W = 1; правилаHID2[PSE],HID2[LSQE]иRA != 0для форм с обновлением решаются на этапе компиляции. GQR читается во время выполнения вJit::PsqLoad/Jit::PsqStore, поэтому его запись ничего не инвалидирует. - Команда отладчика
jit 0/jit 1; 32-битные и не-x86 хосты, а также сборки с-DGEKKO_JIT_DISABLEDоткатываются на интерпретатор.
Образы дисков
- Поддержка только для чтения контейнера RVZ из Dolphin (
src/rvz.cpp): заголовок контейнера и его контрольные суммы SHA-1, таблицы записей данных и групп, методы сжатияNoneиZstandard, а также «упакованное» представление, восстанавливающее участки псевдослучайного мусора диска из seed генератора LFG. Группы bzip2/LZMA и образы Wii помечаются как неподдерживаемые. Zstandard добавлен вthirdparty/zstd. .rvzпринимается везде, где принимался образ диска: в командной строке, в селекторе игр и в файловых диалогах.
Ввод и интерфейс
- Бэкенд контроллеров SDL (
src/padsdl.cpp): контроллером можно управлять одновременно с клавиатуры (SDL_GetKeyboardState, скан-коды SDL вVKEY_FOR_*) и с геймпада SDL (идентификаторы кнопок и осейGCKEY_FOR_*); N-й подключённый геймпад управляет N-м портом. Левый стик управляет основным стиком, правый — стиком C, триггеры — аналоговыми L/R (цифровые L/R срабатывают на 50%); клавиши 50%/100% на клавиатуре продолжают работать, а значения обоих источников складываются и ограничиваются, а не переполняютint8_t. - Окно настройки контроллеров в SDL-интерфейсе (
uisdl.cpp, портировано из Win32-процедурыPADConfigDialogProc): Options → Controllers → Port 1..4 открывает «Configure Controller N» с таблицей Buttons слева и таблицами Control Stick и C Stick справа, у каждой свои столбцы Control | Keyboard | Gamepad. Щелчок по привязке включает захват: следующая клавиша, кнопка геймпада или отклонение стика/триггера становится новой привязкой, а событие проглатывается; Esc отменяет, модификаторы и F1–F12 для клавиатуры пропускаются. - Геймпады открываются и закрываются потоком, который прокачивает события SDL
(
PADUpdateControllers), а поток эмуляции читает их через снимок со спин-блокировкой;PADLoadConfigобъявлена публично, чтобы диалог мог применить привязки без перезапуска эмуляции. - Новая иконка приложения: рисунок куба перекрашен в палитру логотипа
(
src/res/pureikyubu_icon.svg) и встроен вsrc/res/pureikyubu_icon.hдляSDL_SetWindowIcon(у SDL-порта нет ресурсов.rc);pureikyubu.icoзаново сгенерирован в размерах 16–256 px, 32 bpp.
Командная строка и инструменты
pureikyubu <файл>(или--image <файл>) сразу загружает и запускает образ вместо остановки в селекторе игр, а--helpпечатает список ключей в консоль и в журнал отчёта, так чтоEMU_LOG=<файл> pureikyubu --helpтоже работает.--bench <файл> [секунды]прогоняет эмулятор без участия человека и сообщает пропускную способность иGekko::CpuStats(транслированные и интерпретированные инструкции, длина базовых блоков, повторные трансляции и их причины, заполнения строк кэша и каждое обращение, уходящее из кэша в PI/MEM);BENCH_PROFILE=1добавляет разбивку по тактам хоста, аBENCH_STATS=1— гистограмму гостевых инструкций.- В дифференциальном стенде Gekko появились режимы
BENCH_SPIN*(воспроизводящие и разбирающие взаимные помехи потоков на тайм-базе),BENCH_PS_TEST,BENCH_FUZZ_PSи смесь нагрузкиpsq;build_win.batсобирает стенд компилятором MSVC, чтобы оба компилятора можно было сравнить на одной нагрузке.
Тесты
- Тесты последовательного интерфейса по спецификации: 17 новых случаев — круговой прогон SIPOLL и его независимые половины, выходные буферы каналов, входные буферы только для чтения, позиции бит COMCSR и TSTART, интервал опроса, бюджет и привязка к вертикальному гашению, включение отдельных каналов, а также прерывание статуса чтения и его маска.
- Тесты Command Processor по спецификации (
testing/gfx_cp_spec_test.cpp, 19 случаев), написанные поspecs/architecture/command-processor.md: позиции полей VCD/VAT, счётчики байт компонент и цвета в VAT, порядок вершинных атрибутов иgx_vtxsize. - Пять тестов агрегации линии прерывания DSP (каждая причина поднимает линию только через свою маску, реальное завершение ARAM DMA доходит до PI, замаскированная причина защёлкивается без линии, подтверждение снимает её, а прерывание почтового ящика доходит до PI), плюс исправленный тест векторов DSP и тест защёлкнутого запроса CPU→DSP. Всего в наборе 367 тестов.
Изменено
Производительность и потоки
- Развёртка VI и опрос последовательного порта выполняются потоком процессора на том же
тике, который он и так продвигает (
GekkoCore::Tick/TickNвызываютFlipper::Update), поэтому тайминг VI точный, а не сэмплируемый потоком. - Поток Command Processor блокируется на новом переносимом
Event(utils.h), который поток процессора сигналит, когда подошла порция записей FIFO (CommandProcessor::TickSync); слив по-прежнему идёт на эмулируемой скоростиtickPerFifo. - Поток AI DMA просыпается по
DSP::AITickSync(примерно каждые 6750 тиков на 48 кГц) и паркуется черезSuspend(), когда DMA не взведён. Поток DSP просыпается поDspCore::TickSyncраз вDspWakeTicksи сливает порцию, поэтому эмулируемый DSP больше не тормозит сам себя, перезакрепляя дедлайн на каждом опросе; удержание почтового ящика теперь измеряется в тиках (DspCore::MailboxHoldTicks). mtmsrиrfiсбрасывают скомпилированные блоки, только когдаMSR[IR]/[DR]действительно меняются; ОС переключаетMSR[EE]вокруг каждой критической секции, и полный сброс кэша из-за этого стоил около 85 000 лишних трансляций в секунду.- Трансляция адресов, тик и помощники условий переходов теперь действительно
инлайнятся (
GEKKO_INLINE).
Поведение и конфигурация
- Windows- и SDL-сборки хранят настройки раздельно:
DefaultSettingsWin.json/SettingsWin.jsonиDefaultSettingsSdl.json/SettingsSdl.json. Оба порта запускаются из одного каталога и раньше перезаписывали конфигурацию друг друга; теперь каждый исполняемый файл встраивает свои имена файлов. - Последовательный интерфейс следует документированной раскладке регистров:
SICnOUTBUF— видимый процессору регистр, а теневой читает движок передачи;SICOMCSRзащёлкивает CHANNEL и INLNGTH при обычной записи, аTSTARTчитается как бит ожидания; регистр опроса напрямую управляетSIPoll(line). - Потоки устройств Flipper и эмулятор используют одну и ту же абстракцию
Event, поэтому код таймингов переносим между Win32 и SDL.
Исправлено
Прерывания и таймеры
- Агрегатные линии прерываний DSP/DI. У каждой причины в CDCR и DI_SR есть
собственный бит маски, и пары расположены не рядом; оба помощника пересчёта объединяли
группу причин с группой масок по И, то есть всегда получали ноль, и линии вообще никогда не
поднимались. Animal Crossing зависала в ожидании ARQ, потому что завершение ARAM DMA не
доходило до процессора. Теперь обе линии следуют ИЛИ пар
(причина && её собственная маска)и пересчитываются изDSPUpdateInt()/DIUpdateInt()при любом изменении причины или маски. - Снятая причина может опустить линию. Линии сбрасывались только внутри
одной ветви
write_cdcr/write_sr; снятие одной причины при защёлкнутой другой оставляло линию поднятой, и снять её было уже нечем, поэтому процессор заново входил во внешний обработчик на каждомrfi. Animal Crossing выполняла около 11 500 исключений в секунду. - Шторм опроса последовательного интерфейса. Эмулятор опрашивал порт по
фиксированному интервалу в 0x10000 тиков (примерно в десять раз чаще кадровой частоты
железа) вместо расписания видеолиний SIPOLL, поэтому
RDSTINTподнимался непрерывно. На Animal Crossing эмулируемое время за один и тот же интервал реального времени выросло с 3.8 с до 74 с, число входов в исключения упало с 165 000 до 2 213, а прерывания VI — с 0 до 2 178; у Zelda число исключений снизилось с 49 635 до 2 157. - Векторы микрокода DSP. База векторов прерываний бралась из живого бита
use-rom регистра CDCR, а не из выполняющейся программы; микрокод Zelda ставит вектор CPU→DSP
по адресу 0x000E в IRAM, и векторы загрузчика заставляли его отбрасывать команду. Теперь
бит 1 CDCR защёлкивается как запрос и повторяется, пока ядро не примет его через вентиль
TE3/ET, поэтому запрос, пришедший при закрытом
te3, больше не теряется. - Потоки DSP/AI.
DspAIOpenочищал состояние черезmemset, затирая иEvent, которого ждёт поток AI (ожидание на обнулённом дескрипторе возвращается немедленно, поэтому поток незаметно возвращался к вращению); теперь очистка выполняется явнымDspAIControl::Reset.
Графика
- Вывод изображения (issue #361).
pe.cppвыключал выход VI после первого токенаGXDrawDone/PE, из-за чего развёртка прекращалась до конца запуска в играх, которые продолжают выдавать кадры; этот вентиль не имел смысла на железе и удалён. В SDL-сборке картинка VI писалась в то же окно, что несёт контекст OpenGL, и два презентера чередовались; пока работает GL-бэкенд, окно принадлежит ему, а VI по-прежнему декодирует XFB и считает кадры. - Command Processor.
CP_FIFO_COUNTбыл устаревшим программным регистром, а теперь это живая занятость FIFO («расстояние между указателем записи и указателем чтения»). Бит статуса точки останова зависел отCP_ENABLE[FIFOBRKINT]вместо[FIFOBRK], поэтому игра, взводившая точку останова без прерывания, никогда не видела флаг.CommandProcessor::ResetFifoProcessor()сбрасывает командный буфер CP, чтобы новый поток начинался с чистого состояния. - VI.
VI_DTV_STATUS(0xCC00206E) реализован вместо попадания в ветвь по умолчанию, где он возвращал 0 и заставлялVIGetTvFormat()из SDK всегда сообщать NTSC. - Декодер текстур (issue #359). Формат IA4 обходил тайлы сначала по
столбцам, тогда как тайлы изображения хранятся построчно, поэтому карта шире одного тайла
выходила транспонированной (кодировщик отчёта повторял тот же обход — вот почему галерея
этого не показывала); отладочный дамп текстур
gxtexdumpчитал поля цвета после того, как декодер сериализовал тексели в порядок байт R,G,B,A, нужный GL, что проворачивало каналы у всех не-серых форматов; C14X2 маскировал индекс палитры по 0x3ff вместо 0x3fff; а прозрачный четвёртый цвет трёхцветного режима CMPR терял RGB среднего значения крайних цветов. - Загрузка текстур.
DecodeTextureсравнивает описание и хеш FNV-1a сырых байтов текстуры (плюс поколение TLUT для палитры) и сохраняет GL-изображение, когда ничего не изменилось; загрузка переиспользует хранилище изображения черезglTexSubImage2D, параметры сэмплера и цепочка mip повторно применяются только при реальном измененииTX_SETMODE0/1, аTX_INVTAGS(GXInvalidateTexAll) помечает все карты на повторное декодирование.
Интерфейс и платформа
- Пункт меню
File → Reopen, до этого мёртвый, привязан к последнему загруженному образу (настройкаLASTFILE, которую и так ведёт селектор), а ускорителем назначена F3. - Записи в регистры входного буфера SI больше не останавливают эмуляцию.
- Исходники Gekko теперь корректный UTF-8 (два байта CP1252 в комментариях
gekko.cpp/gekkoc.cppнормализованы). - Модульные тесты DSP линкуют заглушку счётчиков
(
testing/dsp_test_support.cpp), раз исходники DSP собираются безgekko.cpp, а удержание почтового ящика терпит нулевойCore, который используют тесты.
Известные проблемы
- Bump mapping, косвенное текстурирование, окружение Z-текстуры и Cpu2Efb пока не эмулируются.
- Дизеринг PE и несколько полей флагов
SUнамеренно не реализованы и описаны вwiki/gfx.md. - Полная поддержка микрокода JAudio (issue #71) всё ещё в работе; оставшиеся расхождения DSP
с железом записаны в
testing/Readme.md. - Поддержка RVZ только для чтения: группы bzip2/LZMA и образы Wii не реализованы.
- Рекомпилятору нужен 64-битный хост x86; 32-битные и не-x86 сборки, а также
GEKKO_JIT_DISABLEDоткатываются на интерпретатор. - В Linux-сборке по-прежнему нет звука и ввода.
- Часть игр всё ещё не рисует картинку или зависает; совместимость — работа в процессе.
Сборка
Windows. Откройте scripts/VS2026/pureikyubu.sln в Visual Studio
2026 и нажмите Build. Есть конфигурации Debug и Release как для Win32-, так и для
SDL-интерфейса.
Linux.
sudo apt install libglew-dev
git clone https://github.com/emu-russia/pureikyubu.git
cd pureikyubu
git submodule update --init
cd build && cmake .. && make
./pureikyubu pong.dol
Тесты.
MSBuild scripts/VS2026/pureikyubu_test.slnx -p:Configuration=Release -p:Platform=x64
vstest.console.exe scripts/VS2026/x64/Release/pureikyubu_test.dll /Platform:x64
Предыдущий релиз по-прежнему доступен: Release Notes 1.7 · Заметки о релизе 1.7. Подробная история работы после релиза 1.6 — в дайджесте.