Вангеры 3D: пример использования Emscripten в Rust
Трасировка лучей в оригинальной игре.
Вангеры в 2022 году продолжают удивлять, прежде всего людьми которые когда-то полюбили эту игру, но сегодня не забывают и вкладывают много сил, делая её лучше, современнее. Оригинальная игра получила обновление с поддержкой полноценного 3D, да не абы какого, а кросс платформенного — работающего на всех системах и даже в браузере. Статья о том, как нетипичная связка технологий помогла запустить обновление в браузере.
Данный материал логическое продолжение предыдущей статьи Vange-rs: взгляд на реализацию WebAssembly в Rust. У читателей могло сложиться впечатление, что я обиделся на Rust из-за того что комьюнити напрасно отказывается от emscripten, ради горячо любимого wasm32-unknown-unknown.
Это совсем не так, я в значительной степени впечатлен уровнем интеграции wasm в Rust, в частности легкость которую дает wasm-bindgen для организации взаимодействия между js и Rust. Объективно это лучшее решение для проектов написанных на Rust (RIIR).
В то же время, Emscripten нацелен больше на C/C++ проекты, поэтому достаточно закономерно, что он используется значительно реже в Rust. Тем не менее wasm32-unknown-emscripten работает хорошо, а большинство крейтов компилируются без ошибок из коробки. Так, Vange-rs зависит от 211 крейтов, и только 4 имели проблемы совместимости с Emscripten (winit, glow, ron, wgpu). Сложности начинаются, когда нужно задействовать API браузера, все крейты, которые оборачивают это API, построены на базе wasm-bindgen, а он не поддерживает Emscripten. Поэтому, если Rust проект выполняет не только вычисления, но и подразумевает какой-то визуальный интерфейс или взаимодействие с пользователем, то скорее всего скомпилировать в WebAssembly средствами Emsripten его не получится.
Однако, это правило действует и наоборот: если Ваш проект написан на C/C++ и связывает (link) (статически/динамически) библиотеки на Rust, то это автоматически означает что вы не сможете его скомпилировать в WebAssembly и запустить в браузере, используя wasm32-unknown-unknown. Потому что, ABI unknown не совпадает с ABI Emscripten или wasi, и нет компилятора C/C++ в ABI unknown.
Для такого рода проектов, можно и нужно использовать Emscripten.
А есть ли такие проекты?
В прошлой статье для экспериментов был использован проект vange-rs, от автора wgpu @kvark. Он включает в себя современный GPU рендер на основе нескольких оригинальных техник, самая перспективная из которых делается при помощи трассировки лучей. Этот проект был взят не просто так. В сообществе Вангеров зародилась идея внедрить рендер на базе vange-rs в саму игру. Эта амбициозная цель настолько всем понравилась, что вокруг неё собралась небольшая команда сильных программистов и сочувствующих.
Взяв за основу vange-rs, Никита Пряничников, создал общую абстракцию рендеринга, позволяющую переключать оригинальный ренедер Вангеров и динамически подключаемый из vange-rs. @kvark сделал из своего проекта ffi библиотеку, реализующую эту абстракцию. Таким образом, используя статическое связывание, удалось завести рендер vange-rs в оригинальных Вангерах.
Но, не всё так просто. vange-rs на данный момент используется только как рендеринг поверхности, все остальное обрисовывается используя оригинальный код Вангеров. А работает это следующим образом. При разработке HD версии, Никите удалось разделить рендер Вангеров на два слоя: нижний слой карты и верхний слой с мехосами, эффектами, интерфейсом и пр. Эти два слоя объединяются в единое изображение перед выводом кадра на экран.
Слоистый рендер
vange-rs выступает как замена оригинального слоя карты, а слой интерфейса отображается, используя оригинальный код. Чтобы добиться визуального совпадения, пришлось синхронизировать камеры vange-rs и Вангeров, а так же регулярно пересылать все изменения, которые вносит игра в карту (например, следы от колёс). Алгоритм работы с картой весьма хитрый и размазан по всему коду оригинальной игры. Так, отображение большинства эффектов влияет на ландшафт и вносит в него изменения. Поэтому, оригинальный рендер карты выполняется всегда, а рендер 3D использует данные 2D карты в своей работе. Была проделана большая работа, чтобы выделить все места, где происходит изменение ландшафта, и выполнить синхронизацию. Большое спасибо ребятам за проделанный труд!
Как такое возомжно?
Интересный факт: кажется что изображения выше не должны работать в слоистом рендере, но работают! Код Вангеров, как и сама игра, удивителяет. Верхний слой с мехосами учитывает ландшафт своего расположения и отображается соответствующим образом. Мехосы находятся выше по иерархии слоев, но отображаются правильно.
Разгадка
Итак, 3D рендер в Вангерах, это та самая связка C++ и Rust, которую можно скомпилировать в WebAssembly используя Emscripten или wasi. Более того, Вангеры уже были скомпилированны в WebAssembly с помощью Emscripten. Осталось только добавить поддержку Emscripten в wgpu. Обратите внимание, как мало потребовалось правок, чтобы этого добиться. wgpu не использует браузерные API для GL бэкенда, а Emscripten подменяет нативный GL своей реализацией на WebGL-2. Благодаря этому С/C++ проекты, использующие wgpu (wgpu-native), будут работать в браузере через GLES-3 бэкенд. Т.е. wgpu становится альтернативой bgfx или sokol в браузере. Можно взять условный glfw/SDL1/2 подключить к нему wgpu-native и быть уверенным что проект заработает в браузере.
Например, другие проекты KD-Lab: Периметр и Самогонки используют старый рендер на DX9, из-за чего работают только на windows. Сообщество хочет заменить этот рендер на что-то более современное, например wgpu или bgfx. Решение ещё не принято, но теперь есть уверенность, что если это произойдёт то эти игры будут доступны в браузере.
Вангеры 3D в браузере
Вернемся к нашей любимой игре. Вот последовательность шагов которая требуется что бы добавить поддержку vange-rs в браузер.
Компилируем librusty_vangers.a используя Emscripten triple:
cd ${VANGE_RS_ROOT}/ffi
cargo build --target wasm32-unknown emscripten --release --features emscripten
Добавляем эту библиотеку при связывании основной игры:
# CMakeLists.txt
target_link_directories(vangers-web PUBLIC "${VANGE_RS_ROOT}/target/wasm32-unknown-emscripten/release/")
target_link_libraries(vangers-web rusty_vangers)
И это всё! В точности так же, как и с другими компиляторами gcc/clang.
Emscripten помимо всего прочего имеет мощный оптимизатор WebAssembly:
vange-rs web version: 25Mb // из предыдщуей статьи
librusty_vangers.a (rust)
debug: 151 Mb
release: 25 Mb
vangers (c++):
relase: 1.8Mb
optimized and linked with Vangers:
debug: 77 Mb
release: 3.7 Mb
Всего лишь 3.7Mb (1.9Mb — 3D рендер), занимает полная игра Вангеров + 3D рендер на Rust.
Сейчас идёт активная разработка нового рендера, поэтому совместимость отстает. Для запуска в браузере потребуется поддержка WebGL-2 и текстур размером 16384 пикселей (у меня работает только в Chrome на ПК). Чтобы запустить в браузере:
Перейдите на сайт игры Вангеров
Откройте »Настройки» → »Дополнительно»
Включите »3D режим»
В игре настройте камеру с наклоном или вращением, что бы увидеть потрясающее 3D
Клавиша »\» динамически переключает режим отображения игры
Периметр и самогонки
Открытое обращение сообщества, к правообладателям:
За последний год с разрешения разработчиков исходные коды других игр KD-Lab, «Периметр» и «Самогонки» были опубликованы в Open Source. Сообщество поклонников этих игр активно адаптирует эти игры под современные компьютеры и операционные системы. Если на Хабре есть представители издателей игры (как я понимаю, 1С), то мы будем рады, если вы захотите делать обновления игр в Стиме на базе разработок сообщества.
Отказ от ответственности
Прошу не рассматривать статью как рекламную. Все выше изложенное является моим личным мнением и никак не связано с официальной позицией компании GamePix. Рынок России для GamePix не является целевым, поэтому выражаю им огромную благодарность за возможность портирования и публикации Вангеров в браузере.