В операционной системе Embox (разработчиком которой я являюсь) какое-то время назад появилась поддержка OpenGL, но толковой проверки работоспособности не было, только отрисовка сцен с несколькими графическими примитивами. Я никогда особо не интересовался геймдевом, хотя, само собой, игры мне нравятся, и решил — вот хороший способ развлечься, а заодно проверить OpenGL и посмотреть, как игры взаимодействуют с ОС. В этой статье я расскажу о том, как собирал и запускал Quake3 на Embox. Точнее, будем запускать не сам Quake3, а основанный на нём ioquake3, у которого тоже открытый исходный код. Для простоты будем называть ioquake3 просто квейком :) Сразу оговорюсь, что в статье не анализируется сам исходный код Quake и его архитектура (про это можно почитать здесь, есть переводы на Хабре), а в этой статье речь пойдёт именно про то, как обеспечить запуск игры на новой операционной системе. Приводимые в статье фрагменты кода упрощены для лучшего понимания: пропущены проверки на наличие ошибок, используется псевдокод и так далее. Оригинальные исходники можно найти в нашем репозитории. Как ни странно, для сборки Quake3 нужно не так уж много библиотек. Нам потребуются: С первым пунктом и так всё понятно — без этих функций сложно обойтись при разработке на C, и использование этих вызовов вполне ожидаемо. Поэтому поддержка данных интерфейсов так или иначе есть практически во всех операционных системах, и в данном случае добавлять функционал практически не пришлось. Вот с остальными пришлось разбираться. Это было самое простое. Для сборки libcurl достаточно libc (конечно, часть фич будет недоступна, но они и не потребуется). Сконфигурить и собрать эту библиотеку статически очень просто. Обычно и приложения, и библиотеки линкуются динамически, но т.к. в Embox основным режимом является линковка в один образ, будем линковать всё статически. В зависимости от используемой системы сборки, конкретные шаги будут отличаться, но смысл примерно такой: Mesa — это фреймворк с открытым исходным кодом для работы с графикой, поддерживается ряд интерфейсов (OpenCL, Vulkan и прочие), но в данном случае нас интересует именно OpenGL. Портирование такого большого фреймворка — тема отдельной статьи. Ограничусь лишь тем, что в ОС Embox Mesa3D уже есть :) Само собой, сюда подойдёт любая реализация OpenGL. SDL — это кросс-платформенный фреймворк для работы с устройствами ввода, аудио и графикой. Пока что забиваем всё, кроме графики, а для отрисовки кадров, напишем функции-заглушки, чтобы увидеть, когда они начнут вызываться. Бэкэнды для работы с графикой задаются в SDL2-2.0.8/src/video/SDL_video.c. Выглядит это примерно так: Чтобы не заморачиваться с "нормальной" поддержкой новой платформы, просто добавим свой VideoBootStrap Для простоты можно взять что-нибудь за основу, например src/video/qnx/video.c или src/video/raspberry/SDL_rpivideo.c, но для начала сделаем реализацию вообще почти пустой: Добавляем свой VideoBootStrap в массив: В принципе, на этом этапе уже можно компилировать SDL. Как и с libcurl, детали компиляции будут зависеть от конкретной системы сборки, но так или иначе нужно сделать примерно следующее: Quake3 предполагает использование динамических библиотек, но мы будем линковать его статически, как и всё остальное. Для этого выставим некоторые переменные в Makefile Для простоты будем запускать на qemu/x86. Для этого нужно его поставить (здесь и далее будут команды для Debian, для других дистрибутивов пакеты могут называться по-другому). И сам запуск: Однако при запуске Quake сразу получаем ошибку Ошибка выводится не игрой, а операционной системой. Дебаг показал, что эта ошибка вызвана неполной поддержкой SIMD для x86 в QEMU: часть инструкций не поддерживается и генерирует исключение неизвестной команды (Invalid Opcode). upd: Как подсказал в комментах WGH, на самом деле проблема была в том, что я забыл явно включить поддержку SSE в cr0/cr4, так что с QEMU всё в порядке. Происходит это не в самом Quake, а в OpenLibM (это библиотека, которую мы используем для реализации математических функций — sin(), expf() и тому подобных). Патчим OpenLibm, чтобы __test_sse() не делала настоящую проверку на SSE, а просто считала, что поддержки нет. Перечисленных выше шагов хватает на запуск, в консоли виден такой вывод: Уже неплохо, Quake3 пытается запуститься и даже выводит сообщение об ошибке! Как видно, ему не хватает файлов в директории baseq3. Там содержатся звуки, текстуры и всякое такое. Заметьте, pak0.pk3 должен быть взять с лицензионного CD-диска (да, открытый исходный код не подразумевает бесплатное использование). Теперь можно передавать блочное устройство в qemu При старте системы замаунтим диск на /mnt и запустим quake3 в этой директории, на этот раз падает позже Эта опять ошибка с SIMD в Qemu. upd: Как подсказал в комментах WGH, на самом деле проблема была в том, что я забыл явно включить поддержку SSE в cr0/cr4, так что с QEMU всё в порядке. На этот раз инструкции используются в виртуальной машине Quake3 для x86. Проблема решилось заменой реализации для x86 на интерпретируемую ВМ (подробнее про виртуальную машину Quake3 и в принципе про архитектурные особенности можно почитать всё в той же статье). После этого начинают вызываться наши функции для SDL, но, само собой, ничего не происходит, т.к. эти функции пока что ничего не делают. Второй хэндлер нужен для того, чтобы сказать SDL, какие функции вызывать при работе с OpenGL. Для этого заводим массив и от запуска к запуску проверяем, каких вызовов не хватает, примерно так: За несколько перезапусков список становится достаточным полным, чтобы нарисовались заставка и меню. Благо, в Mesa есть все необходимые функции. Единственное — почему-то нет функции glGetString(), вместо неё пришлось использовать _mesa_GetString(). Теперь при запуске приложения появляется заставка, ура! Добавим поддержку клавиатуры и мыши в SDL. Для работы с событиями нужно добавить хэндлер Начнём с клавиатуры. Вешаем функцию на прерывание нажатия/отпускания клавиши. Эта функция должна запоминать событие (в простейшем случае, просто пишем в локальную переменную, по желанию можно использовать очереди), для простоты будем хранить только последнее событие. Затем в pumpEvents() обрабатываем событие и передаём его в SDL: В SDL используется свой enum для кодов клавиш, поэтому придётся преобразовать код клавиши ОС в код SDL. Список этих кодов определяется в файле SDL_scancode.h Например, ASCII-код преобразовать можно вот так (здесь не все ASCII-символы, но этих вполне хватит): На этом c клавиатурой всё, остальным будут заниматься SDL и сам Quake. Кстати, примерно тут выяснилось, что где-то в обработке нажатия клавиш quake использует инструкции, не поддерживаемые QEMU, приходится переключиться на интерпретируюмую виртуальную машину с виртуальной машины для x86, для этого добавляем BASE_CFLAGS += -DNO_VM_COMPILED в Makefile. После этого, наконец, можно торжественно "проскипать" заставки и даже запустить игру (закостылив некоторые error-ы :) ). Приятно удивило то, что всё отрисовывается как надо, хоть и с очень низким fps.  Теперь можно приступить к поддержке мыши. Для прерываний мыши понадобится ещё один хэндлер, и обработку событий потребуется немного усложнить. Ограничимся только левой клавишей мыши. Понятно, что аналогичным образом можно добавить правую клавишу, колёсико и т.п. После этого появляется возможность управлять камерой и стрелять, ура! Фактически, этого уже достаточно для того, чтобы играть :)  Круто, конечно, что есть управление и какая-то графика, но такой FPS совсем никуда не годится. Скорее всего, большая часть времени тратится на работу OpenGL (а он программный, и, более того, не используется SIMD), а реализация аппаратной поддержки — слишком долгая и сложная задача. Попытаемся ускорить игру "малой кровью". Собираем игру, все библиотеки и саму ОС с -O3 (если, вдруг, кто-то очитал до этого места, но не знает, что это за флаг — подробнее про флаги оптимизации GCC можно почитать здесь). Кроме того, используем минимальное разрешение — 320х240, чтобы облегчить работу процессору. KVM (Kernel-based Virtual Machine) позволяет использовать аппаратную виртуализацию (Intel VT и AMD-V) для повышения производительности. Qemu поддерживает этот механизм, для его использования нужно сделать следующее. Во-первых, нужно включить поддержку виртуализации в BIOS. У меня материнка Gigabyte B450M DS3H, и AMD-V включается через M.I.T. -> Advanced Frequency Settings -> Advanced CPU Core Settings -> SVM Mode -> Enabled (Gigabyte, что с тобой не так?). Затем ставим нужный пакет и добавляем соответствующий модуль Всё, теперь можно передавать qemu флаг -enable-kvm (или -no-kvm, чтобы не использовать аппаратное ускорение). Игра запустилась, графика отображается как нужно, управление работает. К сожалению, графика рисуется на CPU в один поток, ещё и без SIMD, из-за низкого fps (2-3 кадра в секунду) управлять очень неудобно. Процесс портирования был интересным. Может быть, в будущем получится запустить quake на платформе с аппаратным графическим ускорением, а пока останавливаюсь на том, что есть.