- PVSM.RU - https://www.pvsm.ru -
Вангеры - это одна из самых почитаемых и технологичных игр своего времени (1998 год), и она продолжает жить и развиваться. Благодаря сплоченному сообществу игра получила множество усовершенствований: HD, 60 FPS, новые сетевые режимы и много другое. Vange-rs один из интереснейших проектов по Вангерам. Это rust версия игры, основной изюминкой которой является 3D рендер основанный на wgpu.

wgpu - кроссплатформенное, безопасное графическое API написанное исключительно на rust. Может использовать Vulkan, Metal, D3D12, D3D11, OpenGL ES3 нативно, а также WebGPU и WebGL в WebAssembly.
Можно сказать, что этот проект показывает всю мощь и возможности библиотеки. Vange-rs содержит исчерпывающее описание [1] технологий применимых для рендеринга миров Вангеров. Даже сама возможность покататься на мехосе в полноценном 3D выглядит очень круто. И вдвойне было бы круто сделать это через браузер.
Тем более это должно быть не сложно, ведь Rust позиционируется как современный язык с поддержкой WebAssembly, однако не все так просто.
Документация Rust обещает нам поддержку аж целых трёх возможных вариантов компиляции в WebAssembly. (Tier 2 [2]: rustc гарантированно собирается).
wasm32-unknown-emscripten
wasm32-unknown-unknown
wasm32-unknown-wasi
wasm32 - архитектура, unknown - вендор, emscripten | unknown | wasi - операционная система. Для всех платформ заявлена поддержка стандартной библиотеки (функции работы с файловой системой, временем и пр.).
Emscripten [3], пожалуй, старейший и самый развитый инструмент для компиляции из C/C++ в WebAssembly. Из коробки поддерживаются практически все функции posix, виртуальная файловая система, OpenGL ES3. Формально используя emscripten можно скомпилировать проект в рабочий WebAssembly без каких-либо изменений. По факту это совсем не так.
Чтобы скомпилировать проект с использованием emscripten достаточно передать флаг --target wasm32-unknown-emscripten к вызову cargo.
Rust выполняет компиляцию кода приложения, а на последнем этапе использует emcc для линковки в WebAssembly. К сожалению, в компилятор зашит [4] флаг, вызывающий ошибку в случае обнаружения не реализованных символов (ERROR_ON_UNDEFINED_SYMBOLS=1). Из-за этого даже hello-world проект на данный момент не может быть собран [5] с помощью emscripten.
Конфигурационные флаги из rustc добавляются всегда в конец, поэтому ситуацию невозможно исправить даже через RUSTFLAGS. И если проблему символов ещё можно обойти, добавив соответствующую реализацию в cpp файл (как указано по ссылке), то ASSERTIONS=1 будет с нами всегда. Т.е. собрать проект без отладочного кода средствами rust невозможно!
Emscripten позволяет изменить это поведение через переменную окружения о которой мало кто знает:
EMMAKEN_CFLAGS="-s ERROR_ON_UNDEFINED_SYMBOLS=0 --no-entry -s ASSERTIONS=0"
Таким образом эту проблему можно решить, но этого будет мало. Большинство библиотек, которые имеют поддержку WebAssembly пытаются взаимодействовать с браузером через библиотеки-обертки, такие как web-sys [6] и stdweb [7]. Они предоставляют API браузера, например, WebGL через систему типов Rust. web-sys основан на wasm-bindgen и поэтому он не совместим с emscripten.
The wasm-bindgen project is designed to target the wasm32-unknown-unknown target in Rust. This target is a "bare bones" target for Rust which emits WebAssembly as output.
Если вы попробуете запустить web-sys проект использует --target wasm32-unknown-emscripten, то произойдет ошибка:
cannot call wasm-bindgen imported functions on non-wasm targets
wasm-bindgen для работы требует изменения полученного wasm файла, а конкретно "функции заглушки" заменяются на вызовы импортированных функций из js, вот что пишет один из авторов о возможной поддержке emscripten:
AFAIK emscripten wants its own JS shims and all that, and having two systems of managing shims won't mix well.
...
wasm-bindgen doesn't support the emscripten wasm target at this time. I haven't ever tested it myself so I don't know how far off we would be from getting it to work, but it's expected that emscripten doesn't work at the moment.
Таким образом wasm-bindgen фактически не совместим с emscripten, значит и все библиотеки основанные на web-sys тоже. На данный момент это вообще все библиотеки. Поскольку stdweb фактически мертв:
winit [8] (библиотека для работы с окнами) отказывается от поддержки stdweb
rust [9] не генерирует правильный wasm для stdweb, начиная с nightly-2019-11-25. Уже 2 года как.
Я пробовал использовать stdweb в связке с winit, и ошибка rust (ссылка выше) не дает сделать это. А вот ещё почему vange-rs не будет работать с emscripten:
wgpu [10] пока не поддерживает emscripten
glow [11] пока не поддерживает emscripten
ron [12] не компилируется для emscripten из-за ошибки

Я понял одну вещь, rust сообщество пытается использовать emscripten неправильно. Сильное заявление, но это так. Вся соль emscripten в том, что он предоставляет идентичное окружение что и обычная *nix система, и поэтому пытаться использовать его совместно с обертками вроде web-sys или stdweb не нужно.
Например, в библиотеке wgpu для OpenGL ES3 бэкенда на линуксе используются библиотеки khronos-egl [13] и glow [14]. Когда происходит компиляция в WebAssembly khronos-egl отключается и вместо него используются обертки web-sys и glow. Но, для emscripten это делать не нужно, поскольку он эмулирует полноценную систему с OpenGL ES3. Таким образом нужно просто использовать тот же код что и для нативной платформы. Если думать таким образом, то можно легко добавить поддержку emscripten и в wgpu [15] и в glow [16].
Сделав это, мы наконец-то сможем скомпилировать vange-rs. Пришлось полностью отказаться от библиотеки winit, поскольку мы используем emscripten, то и создавать окно нам нет необходимости. В случае браузера это всего лишь canvas который мы создадим в index.html. Остается передать WebGL контекст в wgpu. Библиотека имеет достаточно высокую степень абстракции чтобы принять в качестве хэндла окна заглушку.
struct WebWindow {
}
unsafe impl HasRawWindowHandle for WebWindow {
fn raw_window_handle(&self) -> RawWindowHandle {
RawWindowHandle::Web(WebHandle::empty())
}
}
// ...
let window = WebWindow {};
let surface = unsafe { instance.create_surface(&window) };
wgpu будет считать, что окно создано, хотя по факту его нет, есть только контекст WebGL (OpenGL ES3), который вернется через нативный krhonos-egl. Удалив winit мы лишились управления с клавиатуры, поскольку библиотека делала это за нас, но добавить пару обработчиков мы легко сможем вернуть его. Таким образом это действительно работает, хоть и с большими оговорками.
К сожалению, во многих обсуждениях я видел мнение что rust отказывается от wasm32-unknown-emscripten в пользу wasm32-unknown-unknown. Я считаю это мнение не обоснованным, так как у emscripten достаточно много своих плюсов, которые возможно никогда не появятся в unknown, например поддержка asyncify [17], 3 вида файловых систем на выбор, сетевой стэк и пр.
Все внимание сообщества сосредоточено на обожаемых web-sys и wasm-bindgen, ну они-то должны работать из коробки? Ммм, нет... Собирается все действительно без особых проблем, передаем флаг --target wasm32-unknown-unknown и запускаем в браузере:
panic: operation not supported on this platform
Функции работы с файловой системы не поддерживаются.

Очевидно, что игра не сможет запуститься без файлов. Напомню, что в emscripten это решено довольно просто: по умолчанию используется файловая система в памяти, она реализует стандартные функции работы с файлами, в неё можно загрузить образ диска перед запуском. Это вполне логичное решение и, как кажется, в Rust можно было бы применить схожий подход (использование виртуальной файловой системы), тогда такой код работал бы:
File::create(file).expect(&format!("Unable to create {}", file))
.write(include_bytes!("../file"))
.expect(&format!("Unable to write in {}", file));
У нас нет файлов в файловой системе, но мы могли бы их создать: загрузить через wasm-bidngen или просто вставив в дата секцию. Однако, это не работает, потому что реализация файловой системы в случае с unknown просто отсутствует [18]. И не только файловой системы, так же отсутствует какая-либо реализация для потоков или времени. Даже env_logger не будет работать из коробки, потому что он пытается получить текущее время, а эта функция не реализована.
Весь драматизм в том, что unknown используется не только на архитектуре wasm32, и нельзя сделать исправление только для wasm32. Т.е. варианта два, либо переписать vange-rs полностью отказавшись от библиотек и кода работающих с файловой системой, либо исправить rustc добавив в него поддержку файловой системы в памяти.
Очевидно что мы выбираем второе [19]. Было не просто, особенно если учесть, что я прочитал только избранные главы из rust book. Но в итоге получилось ведь!
Комичный случай, я несколько дней безуспешно пытался собрать компилятор rust, используя команду:
./x.py build library/std --target wasm32-unknown-unknown
Команда отрабатывала, но использовать это компилятор я не мог, из-за ошибки:
error[E0463]: can't find crate for `core`
Оказалось, что нужно передавать одновременно 2 архитектуры (--target x86_64-unknown-linux-gnu --target wasm32-unknown-unknown):
You need to use --target x86_64-unknown-linux-gnu --target wasm32-unknown-unknown". If you don't include the host in --target you can't build build scripts and proc macros that need to be compiled for the host.
Сделав это, я наконец-то получил рабочий компилятор с виртуальной файловой системой. В моем репозитории можно скачать собранный вариант для x86_64-unknown-linux-gnu [20]. Собрав или скачав компилятор, просто регистрируем его в rustup и применяем для проекта:
rustup toolchain link memfs memfs/stage1
cd <project>
rustup override set memfs
А дальше собираем как обычно через сargo build. Вы удивитесь, но winit тоже не способен ловить события клавиатуры из-за ошибки [21], так что, как и в случае emscripten приходится реализовывать этот функционал самостоятельно, до тех пор, пока не поправят winit.
Решив эти проблемы, мы наконец-то получаем рабочую версию vange-rs для браузера!
Поскольку wasm-bindgen не поддерживает ничего кроме unknown, то мы имеем все те же проблемы что и у emscripten. Кроме того, wasi предназначен для node.js, а не для браузера. Поэтому экспериментировать с ним я не стал.
Вероятно, rust еще слишком молод, и поддержка WebAssembly находится на экспериментальном уровне. Не смотря на большое количество потребовавших усилий оба варианты компиляции (emscripten, unknown) работают! Какой из них проще - решать вам, для unknown придется собрать компилятор, а в случае с emscripten придется отказаться от большинства библиотек. Но результат в обоих случаях вас порадует, ведь нет ничего невозможного.
Огромное спасибо сообществу Вангеров [22] и Дмитрию (@kvark [23]) - автору проекта vange-rs и wgpu, за саму возможность покататься на мехосах в 3D и помощь в исправлении ошибок WebGL.
Ссылки
vange-rs в браузере [24]
исходный код [25] (emscripten, unknown)
rust-memfs [19]
wgpu [26]
Автор: Александр
Источник [27]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/3d/370388
Ссылки в тексте:
[1] описание: https://github.com/kvark/vange-rs/wiki/Rendering-Techniques
[2] Tier 2: https://doc.rust-lang.org/nightly/rustc/platform-support.html#tier-2
[3] Emscripten: https://emscripten.org/
[4] зашит: https://github.com/rust-lang/rust/blob/master/compiler/rustc_target/src/spec/wasm32_unknown_emscripten.rs#L20-L23
[5] не может быть собран: https://stackoverflow.com/questions/67474533/error-in-compiling-rust-into-webassembly-using-emscripten-on-windows
[6] web-sys: https://rustwasm.github.io/wasm-bindgen/web-sys/index.html
[7] stdweb: https://github.com/koute/stdweb
[8] winit: https://github.com/rust-windowing/winit/issues/1662
[9] rust: https://github.com/rust-lang/rust/issues/66916
[10] wgpu: https://github.com/gfx-rs/wgpu/discussions/2179
[11] glow: https://github.com/grovesNL/glow/issues/189
[12] ron: https://github.com/ron-rs/ron/issues/335
[13] khronos-egl: https://crates.io/crates/khronos-egl/4.1.0
[14] glow: https://github.com/grovesNL/glow
[15] wgpu: https://github.com/gfx-rs/wgpu/pull/2233
[16] glow: https://github.com/grovesNL/glow/pull/195
[17] asyncify: https://emscripten.org/docs/porting/asyncify.html
[18] отсутствует: https://github.com/rust-lang/rust/blob/master/library/std/src/sys/unsupported/fs.rs#L178
[19] второе: https://github.com/caiiiycuk/rust-memfs
[20] x86_64-unknown-linux-gnu: https://github.com/caiiiycuk/rust-memfs/releases
[21] ошибки: https://github.com/rust-windowing/winit/issues/2090
[22] сообществу Вангеров: https://t.me/vangers
[23] @kvark: https://habr.com/ru/users/kvark/
[24] vange-rs в браузере: https://caiiiycuk.github.io/vangers-web/vange-rs/
[25] исходный код: https://github.com/caiiiycuk/vange-rs
[26] wgpu: https://github.com/gfx-rs/wgpu
[27] Источник: https://habr.com/ru/post/594611/?utm_source=habrahabr&utm_medium=rss&utm_campaign=594611
Нажмите здесь для печати.