Та же программа на ash + winit + egui: орбитальная камера, три плоскости сетки, загрузка .obj, три режима отображения, единый проход динамического рендеринга с глубинным вложением и submit через synchronization2. Шейдеры общие с C++-версией — build.rs компилирует их из shaders/ и вшивает SPIR-V в бинарь. Пять крейтов повторяют карту целей CMake, но инвариант «весь Vulkan в одном месте» теперь проверяет компилятор: у simv-mesh и simv-editor нет ash в зависимостях. Расхождения, вынужденные языком, а не вкусом: * App переехал в исполняемый крейт — в C++ граф целей цикличен (core владеет vk, vk принимает core::Window&), и держится это лишь на том, что simv_vk видит заголовки core, не линкуя цель; Cargo такой цикл отвергает; * состояние сцены возвращается из замыкания интерфейса, а не ставится сеттерами: замыкание вызывается из метода рендерера, трогать рендерер оттуда нельзя; * выгрузка меша на видеокарту уехала из середины записи кадра, где C++-версия зовёт vkDeviceWaitIdle уже после захвата образа цепочки показа. Vulkan-контекст и цепочка показа написаны руками: замены vk-bootstrap в экосистеме Rust нет. Обёртки Buffer и Image, лежащие в C++-версии без применения, здесь задействованы всеми потребителями. 13 тестов, из них три перенесены из Catch2 дословно. cargo clippy -D warnings чист, слой валидации молчит. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SimVulcan на Rust
Порт редактора SimVulcan с C++20 на Rust. Делает то же самое: орбитальная камера над
тремя опорными плоскостями сетки, цветные оси XYZ, загрузка .obj и три режима
отображения модели. Тот же Vulkan 1.3 с динамическим рендерингом и synchronization2,
те же шейдеры.
Существует ради сравнения: обе версии лежат в репозитории рядом и собираются
независимо. Разбор различий с цифрами — в ../docs/rust_vs_cpp.md.
Требования
- Rust 1.82+ (проверялось на 1.97.1)
- Vulkan SDK 1.3.290+ — нужен только
glslangValidatorдля сборки шейдеров (ищется в%VULKAN_SDK%\Bin, затем вPATH) - Драйвер Vulkan 1.3 с
fillModeNonSolid
Сборка и запуск
cd rust
cargo build --release
./target/release/SimVulcan
Запускать можно из любого каталога. Шейдеры вшиты в исполняемый файл на этапе сборки,
а assets/meshes ищется подъёмом по предкам от самого бинаря — в отличие от
C++-версии, которой нужен запуск из её собственного каталога.
Рядом с исполняемым файлом создаётся pipeline_cache.bin — сериализованный
VkPipelineCache, он перечитывается при следующем запуске. Файл одноразовый: если
конвейеры повели себя странно, его можно удалить.
Слой валидации Khronos запрашивается во всех конфигурациях, включая --release, — как
и в C++-версии. Если слоя нет, приложение об этом предупредит и пойдёт дальше.
Для работы над кодом собирайте cargo build без --release. В профиле выпуска стоит
lto = "thin", и правка одной строки заставляет заново оптимизировать и слинковать весь
бинарь: 52 секунды против 5 в отладочной сборке. Замеры — в
../docs/rust_vs_cpp.md.
Тесты
cargo test
Чистая математика без видеокарты: габариты меша, сварка вершин, чтение Cube.obj,
пространство отсечения камеры. Тесты живут прямо в модулях (#[cfg(test)] mod tests),
отдельной цели под них нет.
Раскладка
Пять крейтов повторяют карту целей CMake из корня репозитория:
crates/simv-core/ журнал с категориями, окно → log, chrono, winit
crates/simv-mesh/ чтение .obj, сварка вершин, габариты → glam, tobj
crates/simv-vk/ весь Vulkan + build.rs (GLSL → SPIR-V) → ash, gpu-allocator,
egui-ash-renderer
crates/simv-editor/ камера, ввод, панели → egui
crates/simv-app/ App и точка входа → всё вышеперечисленное
Главный инвариант проекта — «весь 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&), и держится это лишь на том, чтоsimv_vkне линкуетsimv_core, а видит его заголовки. Cargo цикл между крейтами отвергает.- Состояние сцены возвращается из замыкания интерфейса, а не ставится сеттерами. Замыкание вызывается из метода рендерера, поэтому трогать рендерер оттуда нельзя.
- Загруженный меш выгружается на видеокарту после кадра, а не из середины записи
команд, как в C++, где колбэк панели дёргает
vkDeviceWaitIdleуже после захвата образа цепочки показа. BufferиImageзадействованы. В C++ обе обёртки собираются в цельsimv_vk, но ими не пользуется никто.- Нет pImpl. Приватные поля модуля дают ту же изоляцию, ради которой в C++ заведён
struct Renderer::Impl. - Цикл событий принадлежит winit.
ShouldClose/PollEventsзаменены методамиApplicationHandler.