Культовый Quake 1996 года получил нативный движок на DirectX 12 с DLSS и без врапперов
Скриншот: E1M1 на RTX 5090, нативный DX12
Сразу сознаюсь: в этой публикации нет ни единой строчки кода, написанной мной лично. Я всего лишь сформулировал промпт — короткий, грубый и изобилующий опечатками.
Всю остальную работу проделал Claude, пока я отлучался на кухню: он написал 7 300 новых строк на C, C++ и HLSL, реализовал 64-битную версию, с нуля спроектировал рендерер под DirectX 12, а также добавил звук, поддержку мыши и даже успешно запустил тест на моём лэптопе. На всё про всё ушло полтора часа — с 21:28 до 22:55.
Зачем вообще возникла эта идея
Последний месяц я экспериментирую с DLSS 5 через ReShade и RenoDX практически во всех проектах, до которых удается дотянуться: от классического Pong и Lada Racing Club до Heretic и Nintendo Switch. Схема интеграции со старым софтом неизменно удручает. Игра выводит графику через устаревшие API вроде DX8–9, OpenGL или вообще силами процессора (как в DOS), поверх неё встает dgVoodoo2 для трансляции в DX11, затем прослойка-фидер, потом RenoDX и, наконец, нейросеть. Получается хрупкая пирамида из костылей, которая норовит обрушиться при первом же переключении по Alt-Tab.
Кроме того, для работы DLSS 5 требуются векторы движения и карта глубины. Винтажные игры эту информацию не предоставляют, поэтому её приходится достраивать пиксельными шейдерами на лету, фактически угадывая происходящее. Отсюда и возникают те забавные визуальные артефакты, над которыми мы смеялись в разборе про Pong.
И вот, сидя вечерком перед монитором, я разглядывал директорию с досовской демоверсией Quake (почему-то притаившуюся внутри папки с Heroes 2 — не спрашивайте) и подумал: а что, если вместо громоздких врапперов взять и кардинально переписать графический движок?
Само собой, собственных навыков для реализации такой задачи у меня нет.
Промпт
Приводить исходный текст не стану — цензура SE7ENа подобного не переживет.
Что именно было сделано (на понятном языке)
Процесс стартовал не с написания кода. Сперва нейросеть развернула Linux в облачной среде, установила mingw-w64 (кросс-компилятор для сборки виндовых исполняемых файлов под Linux), а также Wine и vkd3d для запуска DirectX 12 в безъядерном окружении. Работоспособность проверили на примитивном разноцветном прямоугольнике, который успешно отобразился на экране.
Затем с GitHub были загружены оригинальные исходники Quake, открытые id Software по лицензии GPL еще в 1999 году.
Шаг 1. Переход на 64 бита
Изначально Quake создавался под 32-битную архитектуру, что создало определенные трудности. Вся игровая логика (поведение монстров, анимация дверей и арсенал) написана на скриптовом языке QuakeC, где каждая строка адресуется как 4-байтовое смещение от начала таблицы. В 64-битной системе некоторые строки располагаются в памяти настолько далеко, что 4 байта под смещение уже не хватает. В результате игра либо аварийно завершалась, либо выводила абракадабру вместо наименований карт.
Claude создал отдельную таблицу для движковых строк с отрицательными индексами и переписал под нее около тридцати фрагментов кода.
Заодно вскрылись две давние ошибки разработчиков из id Software. Загрузчик уровней записывал восемь значений в массив, рассчитанный всего на четыре элемента, а аналогичный выход за границы памяти обнаружился в системе частиц светящихся объектов. Компилятор образца 1996 года такие вольности пропускал мимо ушей, однако современный GCC вполне способен превратить этот дефект в «оптимизацию», из-за которой уровень загрузится некорректно.
Шаг 2. Избавление от балласта
Устаревшие компоненты канули в Лету: OpenGL, DirectDraw, DirectSound, DirectInput, протокол IPX, поддержка модемов, воспроизведение музыки с компакт-дисков и ассемблерные оптимизации под процессоры Pentium. Итого минус 8 000 строк кода. Функционал игры от этого практически не пострадал — разве что возможность сражаться по нуль-модемному кабелю ушла в прошлое.
Шаг 3. Создание рендерера на DirectX 12 с нуля
На этом этапе технические подробности в отчете ИИ стали напоминать мне китайскую грамоту, поэтому перескажу суть в общих чертах:
• Вся геометрия уровня загружается в видеопамять единожды при старте. В дальнейшем процессор на базе стандартных BSP-деревьев определяет лишь видимые стены и передает графическому чипу массив индексов.
• Освещение поверхностей скомпоновано в единый текстурный атлас размером 1024×1024 пикселя (в четыре слоя), а мерцание факелов и ламп обрабатывается пиксельным шейдером без перезагрузки данных.
• Вспышки от выстрелов и взрывов ракет рассчитываются покадрово для каждого пикселя.
• Кадры анимации противников хранятся в видеопамяти, а шейдер плавно интерполирует соседние фазы, избавив наконец монстров от дерганой анимации с частотой 10 кадров в секунду.
• Эффекты неба и воды имитируются прямо в шейдере по оригинальным формулам софтверного Quake, избегая нарезки на полигоны, характерной для GLQuake.
Больше всего меня восхитила одна крошечная деталь. Нейросеть сверила уровень яркости с палитрой colormap из PAK0 и выставила коэффициент пересвета, идентичный досовскому оригиналу (до 1.94 от стандартного значения). В GLQuake этот параметр жестко обрезался единицей, из-за чего картинка казалась плоской. Сам я об этой тонкости даже не догадывался.
Шаг 4. Ради чего всё затевалось
Теперь движок самостоятельно генерирует три необходимых для DLSS 5 буфера с интуитивно понятными именами, которые отчетливо видны в RenderDoc, Nsight и профильных утилитах:
|
Буфер |
Формат |
Что внутри |
|
SceneColor |
RGBA16F |
Трехмерная сцена без интерфейса, меню и консоли |
|
MotionVectors |
RG16F |
Корректные векторы движения: перемещение камеры, створок дверей, лифтов, анимированных врагов и оружия в руках |
|
SceneDepth |
D32 |
Карта глубины в формате reverse-Z без дальней плоскости отсечения |
Вдобавок были внедрены параметры r_jitter (субпиксельное смещение камеры на каждом кадре), r_scale (динамическое внутреннее разрешение для нужд апскейлера) и r_debugview для визуальной отладки векторов движения непосредственно на экране.
К слову, корректность векторов была проверена визуально: при движении вперед перспектива закономерно «расходится» от центра, а у пола и оружия просчитываются собственные смещения, независимые от стен.

Шаг 5. Финальные штрихи
Аудиоподсистема переведена на XAudio2, ввод с мыши обрабатывается через Raw Input, клавиатура реагирует на скан-коды (раскладка WASD функционирует даже при включенной русской раскладке), а геймпады задействуют API XInput. Окно жестко зафиксировано в разрешении 1600×1200 пикселей, поэтому Windows не искажает его при масштабировании интерфейса на 150%.
Подводные камни и непредвиденные трудности
Первый же запуск в облачной песочнице обернулся сбоем: встроенный компилятор шейдеров Wine спасовал перед синтаксисом HLSL. Переписывать код шейдеров ИИ не стал — вместо этого он извлек из дистрибутива Electron (легендарного движка Discord) оригинальную микрософтовскую библиотеку d3dcompiler_47.dll и продолжил тесты уже с ней. Логика безупречна: в моей системе Windows используется ровно этот же файл, но сам я до подобного трюка не додумался бы и за сто лет.
Затем ИИ принялся методично прогонять игру на программном рендерере (выдававшем от 5 до 40 кадров в секунду на двух ядрах), изучая заготовленные демки. В результате были обнаружены и устранены три забавных бага:
• При взгляде вниз оружие деформировалось в красную призму из-за перепутанного знака наклона модели.
• Игровое меню и консоль стали невидимыми, поскольку системный шрифт генерировался раньше загрузки палитры и оказывался абсолютно прозрачным.
• Окно запускалось на втором мониторе с разрешением 1600×1200, куда окно аналогичного размера с системной рамкой просто не помещалось. Теперь игра стабильно стартует на первичном дисплее.
Последний нюанс проявился уже на моем железе. Сообщив ассистенту, что рендеринг идет на встроенной графике, я получил логи, где черным по белому красовалось: Direct3D 12 on NVIDIA GeForce RTX 5090 Laptop GPU. Фокус в том, что мой второй монитор подключен к интегрированному чипсету, куда и транслировался готовый кадр. На всякий случай нейросеть прописала в исполняемый файл флаги NvOptimusEnablement и AmdPowerXpressRequestHighPerformance, чтобы мобильные драйверы безоговорочно перекладывали нагрузку на дискретную карту.
Автономное тестирование на моем компьютере
Этот эпизод заставил меня слегка поволноваться. Искусственный интеллект самостоятельно разместил скрипт в игровой папке, открыл файловый менеджер и дважды кликнул по исполняемому файлу. Игра проиграла деморолик (стабильные 60 FPS, ограниченные вертикальной синхронизацией), сделала пару скриншотов и закрылась, после чего ассистент проанализировал логи. Мое личное участие свелось к нулю — если не считать момента, когда меня попросили закрыть вручную запущенную мной ранее игру, так как файл был занят процессом.
Резюме
Итоговый исполняемый файл quake_dx12.exe весит около мегабайта, задействует исключительно базовые системные библиотеки Windows и напрямую взаимодействует с DirectX 12. Никаких прослоек вроде dgVoodoo и сторонних обёрток. Рядом уютно расположились файл pak0.pak из досовской демоверсии и исходный код (этого требует лицензия GPL).
Что сделал я? Нашел нужную папку, набросал корявый промпт с ошибками и один раз закрыл приложение.
Ну и прикрутил DLSS 5 — впрочем, об этом читайте в моих предыдущих заметках.
Мобильная версия пиксельного экшена Replaced получила первый тизер накануне релиза на смартфонах
JoyToy открыла предзаказ на фигурку Кора Фаэрона, первого капитана «Несущих Слово» по вселенной Warhammer: The Horus Heresy
Финн Джонс предложил возродить «Героев по найму» после завершения сериала «Сорвиголова: Рождённый заново»
Marvel продлила мультсериал «Человек-паук: Дружелюбный сосед» на четвёртый сезон ещё до премьеры первого
Аналитики сообщили, что выручка Saros на 20% ниже показателей Returnal на аналогичном этапе после релиза
Бен Аффлек назвал отвратительным процесс съёмок фильма Лига справедливости
Математика безумия: как я создавала логику для браузерной игры с синдромом Капгра и топорами
Инсайдер Cyberleek пообещал прекратить сливы по GTA 6, но пригрозил раскрыть концовку игры