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

Трассировка лучей (vange-rs)
Трассировка лучей (vange-rs)

wgpu - кроссплатформенное, безопасное графическое API написанное исключительно на rust. Может использовать Vulkan, Metal, D3D12, D3D11, OpenGL ES3 нативно, а также WebGPU и WebGL в WebAssembly.

Можно сказать, что этот проект показывает всю мощь и возможности библиотеки. Vange-rs содержит исчерпывающее описание технологий применимых для рендеринга миров Вангеров. Даже сама возможность покататься на мехосе в полноценном 3D выглядит очень круто. И вдвойне было бы круто сделать это через браузер.

Тем более это должно быть не сложно, ведь Rust позиционируется как современный язык с поддержкой WebAssembly, однако не все так просто.

Мир безграничных возможностей

Документация Rust обещает нам поддержку аж целых трёх возможных вариантов компиляции в WebAssembly. (Tier 2: rustc гарантированно собирается).

  • wasm32-unknown-emscripten

  • wasm32-unknown-unknown

  • wasm32-unknown-wasi

wasm32 - архитектура, unknown - вендор, emscripten | unknown | wasi - операционная система. Для всех платформ заявлена поддержка стандартной библиотеки (функции работы с файловой системой, временем и пр.).

wasm32-unknown-emscripten

Emscripten, пожалуй, старейший и самый развитый инструмент для компиляции из C/C++ в WebAssembly. Из коробки поддерживаются практически все функции posix, виртуальная файловая система, OpenGL ES3. Формально используя emscripten можно скомпилировать проект в рабочий WebAssembly без каких-либо изменений. По факту это совсем не так.

Чтобы скомпилировать проект с использованием emscripten достаточно передать флаг --target wasm32-unknown-emscripten к вызову cargo.

Rust выполняет компиляцию кода приложения, а на последнем этапе использует emcc для линковки в WebAssembly. К сожалению, в компилятор зашит флаг, вызывающий ошибку в случае обнаружения не реализованных символов (ERROR_ON_UNDEFINED_SYMBOLS=1). Из-за этого даже hello-world проект на данный момент не может быть собран с помощью emscripten.

Конфигурационные флаги из rustc добавляются всегда в конец, поэтому ситуацию невозможно исправить даже через RUSTFLAGS. И если проблему символов ещё можно обойти, добавив соответствующую реализацию в cpp файл (как указано по ссылке), то ASSERTIONS=1 будет с нами всегда. Т.е. собрать проект без отладочного кода средствами rust невозможно!

Emscripten позволяет изменить это поведение через переменную окружения о которой мало кто знает:

EMMAKEN_CFLAGS="-s ERROR_ON_UNDEFINED_SYMBOLS=0 --no-entry -s ASSERTIONS=0" 

Таким образом эту проблему можно решить, но этого будет мало. Большинство библиотек, которые имеют поддержку WebAssembly пытаются взаимодействовать с браузером через библиотеки-обертки, такие как web-sys и stdweb. Они предоставляют 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 (библиотека для работы с окнами) отказывается от поддержки stdweb

  • rust не генерирует правильный wasm для stdweb, начиная с nightly-2019-11-25. Уже 2 года как.

Я пробовал использовать stdweb в связке с winit, и ошибка rust (ссылка выше) не дает сделать это. А вот ещё почему vange-rs не будет работать с emscripten:

  • wgpu пока не поддерживает emscripten

  • glow пока не поддерживает emscripten

  • ron не компилируется для emscripten из-за ошибки

Экспертное мнение о поддержке emscripten в rust
Экспертное мнение о поддержке emscripten в rust

Я понял одну вещь, rust сообщество пытается использовать emscripten неправильно. Сильное заявление, но это так. Вся соль emscripten в том, что он предоставляет идентичное окружение что и обычная *nix система, и поэтому пытаться использовать его совместно с обертками вроде web-sys или stdweb не нужно.

Например, в библиотеке wgpu для OpenGL ES3 бэкенда на линуксе используются библиотеки khronos-egl и glow. Когда происходит компиляция в WebAssembly khronos-egl отключается и вместо него используются обертки web-sys и glow. Но, для emscripten это делать не нужно, поскольку он эмулирует полноценную систему с OpenGL ES3. Таким образом нужно просто использовать тот же код что и для нативной платформы. Если думать таким образом, то можно легко добавить поддержку emscripten и в wgpu и в glow.

Сделав это, мы наконец-то сможем скомпилировать 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, 3 вида файловых систем на выбор, сетевой стэк и пр.

wasm32-unknown-unknown

Все внимание сообщества сосредоточено на обожаемых 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 просто отсутствует. И не только файловой системы, так же отсутствует какая-либо реализация для потоков или времени. Даже env_logger не будет работать из коробки, потому что он пытается получить текущее время, а эта функция не реализована.

Весь драматизм в том, что unknown используется не только на архитектуре wasm32, и нельзя сделать исправление только для wasm32. Т.е. варианта два, либо переписать vange-rs полностью отказавшись от библиотек и кода работающих с файловой системой, либо исправить rustc добавив в него поддержку файловой системы в памяти.

Очевидно что мы выбираем второе. Было не просто, особенно если учесть, что я прочитал только избранные главы из 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. Собрав или скачав компилятор, просто регистрируем его в rustup и применяем для проекта:

rustup toolchain link memfs memfs/stage1 
cd <project>
rustup override set memfs 

А дальше собираем как обычно через сargo build. Вы удивитесь, но winit тоже не способен ловить события клавиатуры из-за ошибки, так что, как и в случае emscripten приходится реализовывать этот функционал самостоятельно, до тех пор, пока не поправят winit.

Решив эти проблемы, мы наконец-то получаем рабочую версию vange-rs для браузера!

wasm32-unknown-wasi

Поскольку wasm-bindgen не поддерживает ничего кроме unknown, то мы имеем все те же проблемы что и у emscripten. Кроме того, wasi предназначен для node.js, а не для браузера. Поэтому экспериментировать с ним я не стал.

Вместо выводов

Вероятно, rust еще слишком молод, и поддержка WebAssembly находится на экспериментальном уровне. Не смотря на большое количество потребовавших усилий оба варианты компиляции (emscripten, unknown) работают! Какой из них проще - решать вам, для unknown придется собрать компилятор, а в случае с emscripten придется отказаться от большинства библиотек. Но результат в обоих случаях вас порадует, ведь нет ничего невозможного.

Огромное спасибо сообществу Вангеров и Дмитрию (@kvark) - автору проекта vange-rs и wgpu, за саму возможность покататься на мехосах в 3D и помощь в исправлении ошибок WebGL.

Ссылки

Комментарии (12)


  1. lain8dono
    10.12.2021 15:18
    +4

    wgpu помимо прочего ещё и реализация WebGPU. Это основная цель проекта.

    wgpu поверх OpenGL из emscripten поверх WebGL звучит как что-то нехорошее. WebGL 2 и так поддерживается как платформа. При том это минорная платформа со своими особенностями и костылям. А лишний слой будет лишь дополнительным источником багов.


    1. Caiiiycuk Автор
      10.12.2021 15:26

      В статье wgpu это больше практический пример, идея больше в том что emscripten тоже можно использовать для каких-то проектов.


      1. lain8dono
        10.12.2021 17:08

        Можно, но это будет именно что очень специфическое использование. Для эстетов.

        Если очень хочется wasm в браузере, то https://rustwasm.github.io/docs/book/


        1. Caiiiycuk Автор
          10.12.2021 17:27

          Будет иметь специфическое использование, потому что уровень поддержки на данный момент такой, что хочется закрыть глаза и убежать. Emscripten не про то что бы взять и написать проект для wasm, он про то что бы взять готовый проект и скомпилирвовать его с минимальными правками, в иделе без правок вообще. unknown такую проблему не решает, поэтому не вижу противоречия в том что бы они оба существовали и развивались, а вы?


    1. lain8dono
      10.12.2021 17:10
      +3

      Дима помимо прочего ещё и сам WebGPU делает. Убедитесь сами: https://gpuweb.github.io/gpuweb/


      1. Caiiiycuk Автор
        10.12.2021 17:29
        +2

        WebGPU наше будущее, WebGL настоящее. wgpu прекрасен всем, особенно тем что поддерживает оба варианта. Кто знает через сколько лет WebGPU доползет до сафари, напомню что WebGL 2 стандарт появился в 2011 году, в сафари его включили по умолчанию пару месяцев назад. Так что так.


        1. lain8dono
          10.12.2021 17:39
          +1

          WebGPU наше будущее

          Настоящее. Игры и движки появляются потихоньку.


          1. domix32
            10.12.2021 20:02

            Пол года с момента адаптации не сказать что прям много. Так что пока все еще будущее, примерно каким был раст до 2015.


          1. raamid
            11.12.2021 02:19

            Может я чего-то не понимаю, но Veloren, ссылку на который вы дали, предлагает скачать лаунчер, чтобы поиграть. Это ведь уже не веб. Тогда откуда там WebGPU?


            1. lain8dono
              11.12.2021 03:27
              +3

              А кто вам сказал, что WebGPU это обязательно про веб? Пусть название не вводит вас в заблуждение. Этот графический API получился настолько удачным, что его имеет смысл прямо сейчас вне браузерной среды.

              Правда картинка могла немного устареть. https://gfx-rs.github.io/2020/11/16/big-picture.html


            1. lain8dono
              11.12.2021 03:39
              +3

              Вкратце суть такая:

              WebGPU - это стандарт https://gpuweb.github.io/gpuweb/

              wgpu - это реализация стандарта от Mozilla и биндинги для Rust https://github.com/gfx-rs/wgpu

              А в итоге получилась очень удобная и понятная приблуда для графики. Ещё и максимально кроссплатформенная. На мой вкус это всё проще чем OpenGL (и быстрее!), но при этом без длинного списка минусов OpenGL. Возможность работы в браузере прямо вот сейчас - это просто маленький бонус, который нужен не всем.


              1. Caiiiycuk Автор
                11.12.2021 07:32

                Ну если вы работаете в компании которая занимается выпуском игр для браузера этот бонус совсем не маленький. Ещё мог бы порекомендовать sokol gfx очень приятная штука правда на C.