Та же программа на 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>
96 lines
3.9 KiB
Rust
96 lines
3.9 KiB
Rust
//! Компиляция GLSL в SPIR-V на этапе сборки — аналог `cmake/ShaderCompile.cmake`.
|
|
//!
|
|
//! Шейдеры берутся из общего с C++-версией каталога `shaders/` в корне репозитория:
|
|
//! обе сборки компилируют одни и те же файлы теми же ключами, поэтому расхождение в
|
|
//! картинке нельзя списать на шейдер.
|
|
//!
|
|
//! В отличие от CMake результат не копируется рядом с исполняемым файлом, а зашивается
|
|
//! в бинарь через `include_bytes!`. Отсюда исчезают и `FindSpvPath`, и POST_BUILD-копия,
|
|
//! и требование «запускать из каталога исполняемого файла».
|
|
|
|
use std::path::{Path, PathBuf};
|
|
use std::process::Command;
|
|
|
|
fn main() {
|
|
let manifest = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
|
|
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
|
// crates/simv-vk → rust → корень репозитория.
|
|
let shader_root = manifest.join("../../../shaders");
|
|
let shader_root = shader_root.canonicalize().unwrap_or(shader_root);
|
|
let editor_dir = shader_root.join("editor");
|
|
|
|
println!("cargo:rerun-if-env-changed=VULKAN_SDK");
|
|
println!("cargo:rerun-if-changed={}", editor_dir.display());
|
|
|
|
let compiler = find_glslang();
|
|
|
|
let mut sources: Vec<PathBuf> = std::fs::read_dir(&editor_dir)
|
|
.unwrap_or_else(|e| panic!("не читается каталог шейдеров {}: {e}", editor_dir.display()))
|
|
.filter_map(|entry| entry.ok().map(|e| e.path()))
|
|
.filter(|p| {
|
|
matches!(
|
|
p.extension().and_then(|e| e.to_str()),
|
|
Some("vert") | Some("frag")
|
|
)
|
|
})
|
|
.collect();
|
|
// Порядок обхода каталога зависит от файловой системы — сортируем, чтобы сборка
|
|
// была воспроизводимой.
|
|
sources.sort();
|
|
|
|
assert!(
|
|
!sources.is_empty(),
|
|
"в {} нет ни одного .vert/.frag",
|
|
editor_dir.display()
|
|
);
|
|
|
|
let out_editor = out_dir.join("editor");
|
|
std::fs::create_dir_all(&out_editor).unwrap();
|
|
|
|
for src in &sources {
|
|
println!("cargo:rerun-if-changed={}", src.display());
|
|
let name = src.file_name().unwrap().to_string_lossy().to_string();
|
|
let dst = out_editor.join(format!("{name}.spv"));
|
|
|
|
let output = Command::new(&compiler)
|
|
.arg("-V")
|
|
.args(["--target-env", "vulkan1.3"])
|
|
// glslangValidator требует -I<путь> без пробела.
|
|
.arg(format!("-I{}", shader_root.display()))
|
|
.arg("-o")
|
|
.arg(&dst)
|
|
.arg(src)
|
|
.output()
|
|
.unwrap_or_else(|e| panic!("не запускается {}: {e}", compiler.display()));
|
|
|
|
if !output.status.success() {
|
|
panic!(
|
|
"GLSL → SPIR-V не удалось для {}:\n{}{}",
|
|
src.display(),
|
|
String::from_utf8_lossy(&output.stdout),
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Ищем компилятор там же, где его ищет CMake: сначала в Vulkan SDK, потом в PATH.
|
|
fn find_glslang() -> PathBuf {
|
|
let exe = if cfg!(windows) {
|
|
"glslangValidator.exe"
|
|
} else {
|
|
"glslangValidator"
|
|
};
|
|
if let Ok(sdk) = std::env::var("VULKAN_SDK") {
|
|
let candidate = Path::new(&sdk).join("Bin").join(exe);
|
|
if candidate.exists() {
|
|
return candidate;
|
|
}
|
|
let candidate = Path::new(&sdk).join("bin").join(exe);
|
|
if candidate.exists() {
|
|
return candidate;
|
|
}
|
|
}
|
|
PathBuf::from(exe)
|
|
}
|