# SimVulcan на Rust Порт редактора `SimVulcan` с C++20 на Rust. Делает то же самое: орбитальная камера над тремя опорными плоскостями сетки, цветные оси XYZ, загрузка `.obj` и три режима отображения модели. Тот же Vulkan 1.3 с динамическим рендерингом и synchronization2, те же шейдеры. Существует ради сравнения: обе версии лежат в репозитории рядом и собираются независимо. Разбор различий с цифрами — в [`../docs/rust_vs_cpp.md`](../docs/rust_vs_cpp.md). ## Требования * Rust 1.95+ (проверялось на 1.97.1). В `Cargo.toml` объявлено `rust-version = "1.82"`, но это значение недостижимо: залоченные `egui`, `egui-winit` и `epaint` 0.36.1 сами требуют 1.95, и на 1.82 Cargo обрывается на разрешении зависимостей * Vulkan SDK 1.3.290+ — нужен только `glslangValidator` для сборки шейдеров (ищется в `%VULKAN_SDK%\Bin`, затем в `PATH`) * Драйвер Vulkan 1.3 с `fillModeNonSolid` ## Сборка и запуск ```sh cd rust cargo build --release ./target/release/SimVulcan ``` Запускать можно из любого каталога. Шейдеры вшиты в исполняемый файл на этапе сборки, а `assets/meshes` ищется подъёмом по предкам от самого бинаря и только затем — от текущего каталога; в отличие от C++-версии, которой нужен запуск из её собственного каталога. В отличие от неё же, `assets/` никуда не копируется: порт находит копию в корне репозитория, поднимаясь от `target/<профиль>/`. Рядом с исполняемым файлом создаётся `pipeline_cache.bin` — сериализованный `VkPipelineCache`, он перечитывается при следующем запуске. Файл одноразовый: если конвейеры повели себя странно, его можно удалить. Слой валидации Khronos запрашивается во всех конфигурациях, включая `--release`, — как и в C++-версии. Если слоя нет, приложение об этом предупредит и пойдёт дальше. **Для работы над кодом собирайте `cargo build` без `--release`.** В профиле выпуска стоит `lto = "thin"`, и правка одной строки заставляет заново оптимизировать и слинковать весь бинарь: 52 секунды против 5 в отладочной сборке. Замеры — в [`../docs/rust_vs_cpp.md`](../docs/rust_vs_cpp.md). ## Тесты ```sh cargo test ``` Видеокарта не нужна: габариты меша, сварка вершин (три случая), чтение `Cube.obj` и его путь ошибки, пространство отсечения камеры и ограничители наклона и приближения, разбор категорий журнала — тринадцать тестов плюс один помеченный `#[ignore]` диагностический, тот самый, которым получена таблица расхождения загрузчиков. Чистой математикой это, впрочем, не является: тесты загрузчика читают настоящие файлы из `assets/meshes` через `$CARGO_MANIFEST_DIR`, поэтому им нужен рабочий клон репозитория. Тесты живут прямо в модулях (`#[cfg(test)] mod tests`), отдельной цели под них нет. ## Раскладка Пять крейтов повторяют карту целей CMake из корня репозитория: Ниже перечислены сторонние зависимости; вдобавок каждый крейт зависит от тех крейтов рабочего пространства, что указаны в скобках. ``` crates/simv-core/ журнал с категориями, окно → log, chrono, winit crates/simv-mesh/ чтение .obj, сварка, габариты → glam, tobj, log, thiserror (+ simv-core) crates/simv-vk/ весь Vulkan + build.rs → ash, ash-window, raw-window-handle, (GLSL → SPIR-V) gpu-allocator, egui, egui-winit, egui-ash-renderer, winit, glam, bytemuck, log, thiserror (+ simv-core, simv-mesh) crates/simv-editor/ камера, ввод, панели → egui, glam, log (+ simv-core, simv-mesh) crates/simv-app/ App и точка входа → четыре крейта выше + egui, winit, glam, log, anyhow ``` Главный инвариант проекта — «весь Vulkan живёт в одном месте» — здесь проверяет компилятор: у `simv-mesh` и `simv-editor` в зависимостях нет ни `ash`, ни `simv-vk`, так что нарушить границу нельзя даже по невнимательности. Шейдеры общие с C++-версией: `build.rs` крейта `simv-vk` компилирует `../../../shaders/editor/*.{vert,frag}` тем же `glslangValidator -V --target-env vulkan1.3` и кладёт SPIR-V в `OUT_DIR`, откуда он попадает в бинарь через `include_bytes!`. ## Чем отличается от C++-версии Снаружи — почти ничем, кроме внешнего вида панелей: вместо Dear ImGui взят **egui**. Причина: крейт `imgui` — это биндинги, и сборка всё равно тянула бы за собой исходники Dear ImGui на C++, что обесценило бы сравнение. Внутри отличия сведены в отдельный документ, здесь только самые заметные: * **`App` живёт в исполняемом крейте**, а не в `simv-core`. В C++ цикличны исходники (`core::App` владеет `vk::Renderer`, `vk::Renderer` принимает `core::Window&`), тогда как граф целей CMake ацикличен — потому CMake и молчит: `simv_vk` не линкует `simv_core`, а лишь видит его заголовки, и символы `Window` находятся при сборке исполняемого файла. Cargo такую схему отвергает. * **Состояние сцены возвращается из замыкания интерфейса**, а не ставится сеттерами. Замыкание вызывается из метода рендерера, поэтому трогать рендерер оттуда нельзя. * **Загруженный меш выгружается на видеокарту после кадра**, а не изнутри него, как в C++, где колбэк панели дёргает `vkDeviceWaitIdle` уже после захвата образа цепочки показа — хотя запись команд там ещё не началась, `vkBeginCommandBuffer` идёт позже. * **`Buffer` и `Image` задействованы.** В C++ обе обёртки собираются в цель `simv_vk`, но ими не пользуется никто. * **Нет pImpl.** Приватные поля модуля дают ту же изоляцию, ради которой в C++ заведён `struct Renderer::Impl`. * **Цикл событий принадлежит winit.** `ShouldClose`/`PollEvents` заменены методами `ApplicationHandler`.