Введение
В прошлом году в рамках празднования 30-летней годовщины Doom мы продемонстрировали его порт для платы Sparkfun Thing Plus Matter MGM240P. В этом году мы портируем Quake на плату Arduino Nano Matter.
Порт не только включал в себя все оригинальные элементы Doom, например, звуки и музыку, но и имел мультиплеер на основе BLE, а также работал с частотой 35 FPS c улучшенным разрешением 320 x 240 пикселей (разрешение оригинала — 320×200 пикселей).
Изображение 1. Мультиплеер Doom на плате Sparkfun Thing Plus Matter MGM240P
Как понятно из названия, основой платы Sparkfun Thing Plus Matter MGM240P стал модуль Silicon Labs MGM240P, созданный на основе системы на чипе (SoC) EFR32MG24. Наряду с радиоканалом на 2,4 ГГц SoC EFR32MG24 имеет ядро Cortex M33 на 80 МГц с 1,5 МБ флэш-памяти и 256 КБ ОЗУ, благодаря чему он становится идеальным выбором для использования с Matter.
Несмотря на то, что Doom требовал 4 МБ ОЗУ, порт доказал, что 256 КБ ОЗУ вполне достаточно: их хватило не только для работы всего движка, но и для обработки стека BLE и увеличения разрешения на 20%.
Doom уже портировали на множество устройств и микроконтроллеров, поэтому мы решили, что на этот раз стоит взяться за задачу потруднее. Мы хотели найти более сложную игру, требующую больше ОЗУ, с красивой графикой и звуком, и реализовать её порт.
После Doom вышло множество других шутеров от первого лица, но они не обеспечили достаточный объём технических улучшений, чтобы считать их «истинным потомком Doom», хотя в некоторые из них было очень интересно играть. Всего через три года после Doom вышел Quake, совершивший огромный шаг в технологиях, сравнимый с переходом от Wolfstein 3D к Doom.
В Quake был реализован полностью трёхмерный мир, позволяющий создавать произвольную геометрию карт с мостами, множеством этажей, наклонными поверхностями и динамическими точечными эффектами освещения. Более того, враги, оружие и бонусы теперь стали полностью трёхмерными текстурированными полигональными объектами, в отличие от Doom, где они были простыми 2D-спрайтами. Такие 3D-объекты могли похвастаться динамическим освещением по Гуро, повышавшим реализм. Кроме того, в игру был добавлен движок частиц для реализации дыма, взрывов и так далее.
Изображение 2. В Quake бонусы, оружие и враги были текстурированными 3D-объектами. В Doom они были 2D-спрайтами.
Изображение 3. Quake появилось динамическое и статическое освещение
Изображение 4. Игрок может ходить и по мосту, и под ним. В Doom это было невозможно.
В отличие от Doom, в Quake имелся движок скриптинга, позволявший моддерам модифицировать поведение монстров, оружия и мира.
Но за эти улучшения приходилось расплачиваться. Например, Doom с приличной скоростью мог работать на 486DX с 33 МГц и 4 МБ ОЗУ (согласно https://thandor.net/benchmark/32, достигая в demo 3 средней частоты кадров 18 FPS). На такой же конфигурации Quake просто не запустился бы.
Если увеличить ОЗУ до 8 МБ, то на той же машине Quake работал бы, как слайд-шоу, с частотой 3,7 FPS (см. https://thandor.net/benchmark/33). Для запуска Quake с той же средней частотой 18 FPS и разрешением требовался CPU класса Pentium.
Новая задача
8 МБ ОЗУ? Процессор класса Pentium? Звучит само по себе сложно, особенно учитывая, что ОЗУ у нас примерно в тридцать раз меньше!
Вкратце перечислим цели нашей новой сложной задачи:
- Основная цель — запустить полную shareware-версию Quake, то есть с поддержкой всех уровней, в том числе и начальную карту с выбором эпизодов/уровней сложности.
- Не разрешается добавление дополнительной ОЗУ за исключением того, что есть у микроконтроллера.
- Была выпущена новая плата для разработки, в которой использована та же серия MGM240, что и на плате Sparkfun MGM240 Matter: Arduino Nano Matter, в комплекте которой применена система MGM240S. Не стоит и говорить, что мы хотим обеспечить поддержку обеих плат: и Sparkfun, и Arduino Nano Matter.
- Порт должен работать с исходным разрешением 320×200 пикселей со стандартными настройками панели состояния (высота 48 пикселей).
- Должно поддерживаться аудио с 11025 Гц, 8 битами на сэмпл, в том числе статические и динамические звуки, а также звуки окружающей среды.
- Графическую детализацию карт (то есть геометрию, количество узлов/граней и так далее) и врагов снижать нельзя.
- Управление игрока должно поддерживать прыжки, повороты камеры вверх и вниз.
- Поведение врагов и игровая логика должны быть реализованы на 100% (поведение монстров, триггеры, сообщения, переключение уровней и так далее).
- Никаких дополнительных схем, которые бы снимали с CPU нагрузку по генерации графики или звука.
- Должны поддерживаться все эффекты: частицы, искажение изображения в воде и параллакс движущегося неба.
Также есть необязательные функции, которые, однако, хотелось бы реализовать.
- Консоль должна сохраниться, хотя бы для отображения сообщений от системы.
- Читы должны быть доступны тем или иным образом.
- Полная поддержка воспроизведения демо.
- Поддержка сохранений, в том числе точных состояний мира, врагов и игрока.
- Настройки, в том числе смена раскладки управления.
Ещё есть функции, совершенно не входящие в наш объём работ:
- Мультиплеер, потому что он сильно повысит потребление памяти.
- Музыка, потому что у нас нет CD-плеера.
- Запись демо.
Что допускается при решении задачи:
- Любой объём внешней флэш-памяти для хранения файла pak0.pak (это аналог DOOM1.WAD из Doom). Так как файл оригинала имеет размер 18 МБ, нам потребуется 32 МБ флэш-памяти.
- Любые внешние модули или интегральные схемы, не выполняющие вычислительные задачи, например, зарядные устройства аккумуляторов, усилители звука и поддержка клавиатуры.
- pak0.pak допускается модифицировать, чтобы упростить обработку и ускорить загрузку данных. Однако в этом случае не допускаются изменения в детализации графики. Нет ограничений на окончательный размер файла pak0.pak, достаточно того, что он уместится в 32 МБ.
- Звуковые эффекты в pak0.pak можно ресэмплировать до 8 битов, 11025 Гц. Это приведёт к снижению качества, потому что некоторые сэмплы имеют глубину 16 битов или частоту, отличающуюся от 11025 Гц.
- Quake чрезвычайно активно использует CPU, поэтому допускается любой разгон. Стоит учесть, что в конце 90-х геймеры тоже разгоняли свои PC!
- Так как мы не реализуем мультиплеер, радиоподсистема не будет использоваться. Благодаря этому 20 КБ ОЗУ секвенсера и контроллера частоты кадров радио становятся доступны как ОЗУ общего назначения; в справочном руководстве по EFR32xG24 о такой возможности говорится на странице 42. (https://www.silabs.com/documents/public/reference-manuals/efr32xg24-rm.pdf). Таким образом, у нас есть 276 КБ ОЗУ вместо 256 КБ!
Оборудование
Поддерживается два типа оборудования. Первый — это прошлогодняя плата, благодаря которой мы запустили Doom на плате Sparkfun Thing Plus Matter MGM240P. Единственное отличие здесь будет заключаться в модулях флэш-памяти: мы заменили их на две интегральные схемы по 16 МБ каждая, получив 32 МБ.
Изображение 5. Одна из плат, которые мы использовали для запуска Doom. После замены схем флэш-памяти на модули по 16 МБ она сможет запускать и Quake!
Второй тип совершенно новый, он изготовлен на основе платы Arduino Nano Matter. Мы поднапрягли фантазию и назвали эту плату «The Gamepad». В посте будет рассказываться о ней.
Мы дополнили предыдущую конструкцию, добавив ещё восемь кнопок (теперь их стало 16), два аналоговых джойстика и два звукоусилителя Class-D.
Изображение 6. Новая плата The Gamepad в формате геймпада с дополнительными кнопками и двумя аналоговыми стиками
Изображение 7. Обратная сторона платы.
Обе платы спроектированы только из монтируемых в отверстия компонентов, так что устройство может воспроизвести даже новичок. Недостатком этого стала более высокая цена по сравнению с использованием SMD-компонентов.
Схема «The Gamepad»:
Все файлы KiCAD выложены в репозиторий Github: https://github.com/next-hack/TheGamepadDesignFiles
Программное обеспечение
Наш порт мы разрабатывали на основе кодовой базы SDLQuake1.09 (https://www.libsdl.org/projects/quake/) — порта WinQuake на Simple Direct-media Layer Library: единственное отличие от исходников оригинала заключается в нескольких дополнительных файлах для поддержки видео и аудио SDL.
Мы выбрали эту кодовую базу для удобства разработки, оптимизации и тестирования на современной машине с Windows. Снизив потребление памяти до нужного нам, мы начали портирование на целевые платформы.
Трудности: память
Должны признать, что по сравнению с этим проектом портирование Doom на MG240 было детской забавой.
Ниже приведён неполный список проблем, с которыми мы столкнулись. Более подробный список проблем и их решений можно посмотреть в полной статье на next-hack.com:
- Quake активно использует стек. В частности, он задействует около 64 КБ под эффект подводного искажения и около 180 КБ под отрисовку модели мира, что суммарно даёт более 240 КБ стека. И это ещё даже не считая использование стека другими функциями.
- Как говорилось выше, в Quake появился похожий на C скриптовый язык QuakeC, позволяющий моделировать поведение врагов, мира и оружия. Эти файлы скриптов компилируются при помощи QuakeC Compiler, генерирующего байт-код, который выполняется в виртуальной машине, реализованной в Quake. Для этого необходимо более 410 КБ ОЗУ, плюс ещё несколько КБ для самой виртуальной машины.
- Каждая сущность (враги, бонусы, триггеры, двери, маркеры и так далее) используют примерно по 800 байтов ОЗУ. Quake статически распределяет 600 сущностей, то есть суммарно 480 КБ ОЗУ.
- В Quake используется клиент-серверная архитектура, в которой и сервер, и клиент работают одновременно, даже при однопользовательской игре. То есть не требуется только множество дополнительных буферов для клиент-серверного взаимодействия, но и сторона клиента должна хранить данные рендеринга каждой сущности (по 180 байтов на каждую). Значит, каждому объекту в локальной однопользовательской игре требуется чуть меньше 1 КБ ОЗУ (800+180).
- Чтобы определить, находится ли отрисовываемый пиксель спереди (то есть он видим) или позади (невидим) относительно уже отрисованного, применяется z-буфер, в котором хранится 16-битное (обратное) значение каждого отрисовываемого на экран пикселя. То есть необходим буфер в два раза больше, чем количество 3D-пикселей. В нашем случае разрешение составляет 320×200, а строка состояния имеет высоту 48 пикселей. Таким образом, нам нужно 95 КБ (97280 байтов) только под один этот буфер.
- Для разрешения 320×200 требуется буфер кадров, то есть ещё дополнительно 64000 Б, или 128000 Б, если используется двойная буферизация (в данном случае мы её не использовали).
- Буфер размером 600 КБ используется под кэш поверхностей, в котором хранятся недавно отрисованные поверхности. Это позволяет пропускать вычисления статического+динамического освещения в каждом кадре, что сильно повышает скорость. Промахи кэша возникают относительно редко, потому что перемещения игрока относительно малы, размер кэша велик, а динамические источники освещения встречаются не так часто.
- Небо реализовано как два слоя размером 128×128 пикселей; один слой перемещается поверх второго, частично прозрачного. Для этого Quake требуется 48 КБ ОЗУ (два отдельных слоя плюс один отрендеренный).
- 3D-модели загружаются в ОЗУ. К сожалению, их данные не используются в исходном виде (это позволило бы нам задействовать флэш-память без модификации моделей) и требуется дополнительная обработка. Более того, даже после такой обработки часть данных моделей всё равно не остаётся неизменной, например, для динамического освещения или для отслеживания того, была ли поверхность/узел/лист «посещён» движком рендеринга.
Таким образом, требуется много ОЗУ, даже если мы сохраним/оставим всё неизменяемое во внешней/внутренней флэш-памяти. Если просто суммировать всё перечисленное выше, то мы получим примерно 1,4 МБ. И это даже без учёта всего остального: данных и буферов звуковых каналов, клиент-серверных буферов, буфера консоли, буфера клавиш, переменных консоли, консольных команд, данных частиц и, самое главное — данных моделей, которые могут увеличить общий размер используемой памяти ещё на несколько мегабайтов.
«Простых» оптимизаций памяти, которые мы реализовали для запуска Doom, было недостаточно; подробнее об этом мы написали в статье на next-hack.com.
Трудности: скорость
В версии для PC и в портах Quake все данные доступны из ОЗУ (а то и из кэша данных CPU), которое даже в 1996 году имело достаточно большую пропускную способность и низкие задержки. На самом деле, пропускная способность последовательного чтения сильно варьировалась, но для 64-битной EDO DRAM на 40 МГц (уже продававшейся в 1996 году) можно было достичь максимальной пропускной способности в 320 МБ/с. Задержки произвольных операций чтения находились в интервале 110-130 нс.
У нас же было всего 276 КБ ОЗУ и 1,5 МБ флэш-памяти. В нашем чипе есть относительно большой кэш команд, но нет кэша данных. Считывание данных из внутренней флэш-памяти всегда вызывает по крайней мере один такт ожидания. В случае невыровненного доступа время ожидания может увеличиться. Максимальная скорость последовательного считывания данных составляет 181 МБ/с в случае разгона, при этом во внутренней флэш-памяти можно сохранить только ограниченный объём данных, поэтому мы должны отдавать приоритет геометрическим данным (координатам вершин, BSP-дереву и так далее). Всё остальное, включая текстуры и звук, грузится с внешних интегральных схем флэш-памяти SPI, имеющих более низкую пропускную способность (суммарно максимум 17 МБ/с) и очень большие задержки (порядка нескольких микросекунд).
Более того, в этом порте мы не можем использовать двойную буферизацию (как мы делали в Doom), потому что это займёт слишком много памяти. Это снижает производительность, потому что нам приходится ждать, пока DMA отправит n-ную строку, прежде чем мы сможем её модифицировать. Кроме того, у нас нет ОЗУ для кэша поверхностей, поэтому нам приходится вычислять освещение текстур на лету.
Чтобы частично решить эти проблемы, мы разогнали MCU. Выяснилось, что MGM240 нормально работает на частоте более 136 МГц (может и быстрее, но мы решили немного подстраховаться, чтобы стабильность не зависела от конкретного устройства). Разумеется, Silicon labs не рекомендует заниматься разгоном (как и все производители интегральных схем), но наш проект не связан с безопасностью и от него не зависят жизни, это всего лишь игра. Кроме того, разгон MCU достаточно часто применяется в любительских и хакерских проектах (например, в порте Doom для RP2040 Kilograham разогнал двухъядерный Cortex M0+ с 133 до 270 МГц).
Разгон обеспечил нам рост производительности в 1,7 раза, но до играбельности оставалось по-прежнему очень далеко: частота кадров находилась в интервале 7-10 FPS.
Нам нужны были стратегии оптимизации. Среди прочего мы выбрали:
- Снижение количества операций доступа к внешней флэш-памяти реализацией буфера кэша текстур после сортировки поверхностей по текстурам.
- Реализацию асинхронной DMA-загрузки следующей текстуры, пока CPU рендерит текущую поверхность с предыдущей загруженной (мы применили похожий подход в порте Doom, но в том случае использовали столбцы. В рост производительности в Quake оказался гораздо более существенным).
- При рендеринге врагов/оружия мы применяли последовательное чтение SPI, когда ожидалось использование больших объёмов данных (близких объектов) и произвольное чтение данных, когда нужно было отрисовать только несколько пикселей (дальние объекты).
- Реализация некоторых функций на ассемблере (например, билинейную интерполяцию для отрисовки поверхностей).
- Частичное обновление экрана: когда панель состояния обновлять не нужно, мы пропускали её рендеринг и сразу отправляли её на дисплей. Эту функцию мы также параллельно реализовали в порте Doom, благодаря чему он теперь достигает частоты 35 FPS даже при разрешении 320×240 пикселей.
После таких улучшений частота кадров увеличилась в 2,5-4,5 раза (4,25-7,7 раза, если учитывать решение без разгона). Теперь частота кадров колеблется от 17,7 до 45,6 FPS (timedemo для demo3 имеет со включенным звуком оценку 28,0 FPS).
Дополнительные инструменты
Для этого порта нам пришлось разработать ещё четыре инструмента, выполняющие следующие функции:
- Генерация таблиц констант (например, синуса и других данных).
- Преобразования между Quake C и стандартным C. Это было реализовано при помощи модификации Quake C Compiler, разработанного Id Software.
- Генерация геттеров-сеттеров для сущностей, чтобы каждая из них занимала гораздо меньше ОЗУ.
- Конвертер Quake Pak, оптимизирующий данные Quake для рендеринга.
Все четыре инструмента реализованы как консольные приложения Windows, использующие Code::Blocks. Их исходники включены в репозиторий Github, но код нужно сильно подчистить.
Чтобы играть в Quake, обязательно нужно использовать конвертер Quake Pak, преобразующий shareware-файл pak0.pak. Преобразованный файл нужно переименовать в pak0.pak и записать на карту microSD, чтобы загрузить во внешнюю флэш-память, следуя командам на экране. Остальные три инструмента добавлены только для справки.
Порт в работе!
Запущенный порт и полное видео можно посмотреть на YouTube!
Изображение 8. Первые кадры demo 3. Полностью реализовано воспроизведение демо.
Изображение 9. Консоль реализована, её можно активировать из меню Options. Мы вставили в качестве чита «showfps 1», чтобы можно было выводить в левую нижнюю часть экрана текущую частоту кадров.
Изображение 10. Начальная карта, комната выбора эпизода. Поддерживается только первый эпизод.
Изображение 11. Первый уровень. Частота кадров сильно скачет, но первоначально равна 27 FPS.
Изображение 12. В маленьких помещениях частота кадров превышает 30 FPS.
Изображение 13. На открытых пространствах частота кадров падает (в этом случае 20,8 FPS).
Изображение 14. В некоторых случаях частота кадров снова превышает 30 FPS.
Предыдущие порты и сравнение производительности
Наряду со множеством портов/улучшений Quake на PC/MAC существует несколько портов Quake на микроконтроллеры. Например:
1) Порт на RISC-V: https://www.elektormagazine.com/articles/start-playfully-with-riscv
2) Порт на Cortex M7 MCU: https://github.com/FantomJAC/quakembd
Однако в этих системах было доступно как минимум 8 МБ ОЗУ и они имели достаточно мощные CPU (двухъядерный RISC-V на 400 МГц и суперскалярный Cortex M7 на 480 МГц), а значит, основные усилия, вероятно, были приложены к разработке слоя аппаратной абстракции, без необходимости радикального изменения исходников.
Судя по видео, эти порты Quake работают очень быстро. Это вполне ожидаемо, учитывая большой объём ОЗУ и вычислительную мощь.
Гораздо более впечатляющую работу проделал для GBA Рэнди Линден: https://www.xda-developers.com/how-quake-ported-game-boy-advance/.
Несмотря на гораздо меньшую скорость и разрешение всего 120×160 пикселей (GBA имеет разрешение 240×160, но в порте каждый 3D-пиксель состоит из двух горизонтальных пикселей), программисту удалось частично запустить Quake всего на 384 КБ ОЗУ и ARM 7TDMI с частотой 16,7 МГц. Производительность, которой Рэнди удалось достичь благодаря сотням тысяч строк оптимизированного ассемблерного кода, просто потрясает.
Судя по статье и видео, порту Quake не хватает следующего (стоит отметить, что мы говорим о порте Quake, а не о производном от него Cyboid с расширенными возможностями, который также показан в статье и видео):
- ИИ врагов: они реагируют, только если по ним выстрелить.
- Звук, похоже, отсутствует, хотя в статье и говорится о звуке и музыке.
- Переключение уровней выполняется вручную. В этом можно также убедиться, посмотрев, как игрок заходит в телепорт E1M1, но карта не меняется.
- Судя по видео и фото, отсутствует билинейная интерполяция для статического освещения. Кажется, эта функция была добавлена в Cyboid.
- Непонятно, вся ли логика игры присутствует (впрочем, всплывающий текст отображается, а лифты работают).
- Консоль, как и демо, не отображается, так что мы думаем, что они не реализованы.
Изображение 15. Кадр из видео Modern Vintage Gamer, в котором отображается первоначальная частота кадров в 9,5 FPS (частота кадров умножается на десять и указывается рядом с бронёй). Разрешение составляет всего 120×160, потому что каждый пиксель удвоен по горизонтали, а у освещения отсутствует билинейная фильтрация; впрочем, несмотря на эти ограничения, такая скорость на ARM7TDMI с 16,7 МГц — очень большое достижение.
Идентичное сравнение выполнять сложно. По сравнению с портом для GBA, наш работает быстрее, но оборудование GBA гораздо более медленное. Тем не менее, порт для GBA имеет гораздо меньшее разрешение, использует примерно на 40% больше ОЗУ и не реализовал все функции Quake.
С другой стороны, упомянутый выше порт для RISC-V работает гораздо быстрее нашего (в видео частота составляет от 30 до 75 FPS), но устройство имеет 8 МБ ОЗУ и двухъядерный процессор RISC-V на 400 МГц (впрочем, мы полагаем, что использовано только одно ядро). Наличие такой большой памяти позволяет реализовать кэш поверхностей, а процессор RISC-V на 400 МГц примерно в 2-3 раза быстрее нашего разогнанного MCU. То же самое относится к порту для Cortex M7.
А что насчёт обычного PC? Наша оценка timedemo составила 28,0 FPS, что быстрее, чем на Pentium 100 МГц (26,7 FPS) и лучше, чем 6x86MX PR200 (27,4 FPS). Однако наши значения были получены с включенным звуком, а бенчмарки https://thandor.net/benchmark/33 выполнялись с ключом -nosound.
Изображение 16. Часть таблицы результатов бенчмарка в https://thandor.net/benchmark/33. Наша оценка в 28,0 FPS выше, чем у Pentium 100 и 6x86MX PR200, но мы всё равно отстаём от Pentium 120 и от 6x86MX PR233.
Как показано в видео по ссылке, во время геймплея частота кадров иногда падает ниже 20 FPS, но основную часть времени она сильно выше этой границы, даже достигая очень высоких пиков, превышающих 30 FPS. Реальная частота кадров зависит от множества факторов, например, от сложности уровня и сцены (то есть от количества отрисовываемых поверхностей), количества видимых объектов, частиц и даже от количества одновременно активных аудиоканалов.
Мы считаем, что именно нехватка ОЗУ, не позволившая использовать систему кэширования поверхностей из оригинала, а также заставившая нас в каждом кадре загружать всю графику из внешней флэш-памяти, не дала нам достичь ещё большей частоты кадров. Кроме того, бенчмарки в https://thandor.net/benchmark/33, вероятно, выполнены с кодом, содержащим ассемблерные подпрограммы, который, по словам Джона Кармака, вдвое быстрее кода на C (https://github.com/id-Software/Quake).
Выводы
Мы считаем, что выполнили все требования, в том числе и «необязательные, но желательные»:
- Реализован полный графический движок: статическое/динамическое освещение, затенение по Гуро врагов/оружия, mip-текстурирование, завихряющиеся поверхности, подвижное небо, подводные эффекты искажения, частицы и так далее.
- Звуки тоже поддерживаются, в том числе статические, динамические эффекты и звуки окружающей среды с позиционированием.
- Поддерживаются все shareware-уровни с полной игровой логикой и поведением врагов, в том числе и финальным боссом первого эпизода Chthon.
- Мы сохранили консоль. В неё можно зайти через меню Options. Впрочем, мы убрали огромную историю на 16 экранов, которая не имеет особого смысла для обычного геймплея.
- Даже читы поддерживаются, их можно вводить в консоли нажатием кнопок вверх-вниз и кнопкой стрельбы.
- Сохранения игр тоже поддерживаются, можно сохранять и восстанавливать состояние целиком.
- Поддерживается воспроизведение демо.
- Управление можно перенастраивать, и настройки сохраняются автоматически.
Изображение 17. Реализована система меню, а настройки сохраняются во внешнюю флэш-память.
Насколько мы знаем, этот порт Quake пока использует наименьший объём ОЗУ. Игра работает с вполне комфортной скоростью (в среднем бенчмарки показывают примерно 27 FPS, но реальное среднее зависит от сложности сцены и может быть выше или ниже), используя всего 276 КБ ОЗУ в системе, стоящей всего малую долю цены компьютера, который бы потребовался для Quake в 1996 году.
Изображение 18. В среднем частота кадров позволяет играть с удовольствием.
Мы считаем, что возможности для оптимизации по-прежнему есть, можно увеличить скорость, а если добавить больше флэш-памяти, то, возможно, удастся запустить и полную версию игры.
А Doom можно запустить?
О да, на плате Arduino Nano Matter можно запустить и Doom. С мультиплеером через BLE и на полной скорости!
Изображение 19. Два устройства с запущенным Doom. Правое работает от аккумулятора.