Files
CFDManager/rust/crates/simv-vk/src/image.rs
T
NotBigGhostandClaude Opus 5 f5c9e61589 Порт редактора SimVulcan на Rust
Та же программа на 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>
2026-08-30 20:58:19 +03:00

183 lines
6.2 KiB
Rust

//! Изображение с представлением — перенос `simv::vk::Image`.
//!
//! Как и `buffer.rs`, в C++-версии лежит без применения: глубинное вложение
//! `Renderer` создаёт там свои `VkImage`/`VkImageView` напрямую. Здесь на нём как раз
//! и построено глубинное вложение — см. заметку в `buffer.rs`.
use std::sync::{Arc, Mutex};
use ash::vk;
use gpu_allocator::vulkan::{Allocation, AllocationCreateDesc, AllocationScheme, Allocator};
use crate::context::Context;
use crate::error::{Ctx, Result, VkError};
pub struct ImageDesc {
pub extent: vk::Extent3D,
pub format: vk::Format,
pub usage: vk::ImageUsageFlags,
pub image_type: vk::ImageType,
pub view_type: vk::ImageViewType,
pub aspect: vk::ImageAspectFlags,
}
impl Default for ImageDesc {
fn default() -> Self {
Self {
extent: vk::Extent3D {
width: 1,
height: 1,
depth: 1,
},
format: vk::Format::R8G8B8A8_UNORM,
usage: vk::ImageUsageFlags::SAMPLED,
image_type: vk::ImageType::TYPE_2D,
view_type: vk::ImageViewType::TYPE_2D,
aspect: vk::ImageAspectFlags::COLOR,
}
}
}
pub struct Image {
device: ash::Device,
allocator: Arc<Mutex<Allocator>>,
image: vk::Image,
view: vk::ImageView,
allocation: Option<Allocation>,
extent: vk::Extent3D,
format: vk::Format,
aspect: vk::ImageAspectFlags,
/// Текущая раскладка. Отслеживается здесь, чтобы `transition_to` не выставлял
/// барьер на переход в ту же раскладку — так же, как в C++-версии.
layout: vk::ImageLayout,
}
impl Image {
pub fn new(ctx: &Context, name: &str, desc: &ImageDesc) -> Result<Self> {
let device = ctx.device().clone();
let ci = vk::ImageCreateInfo::default()
.image_type(desc.image_type)
.format(desc.format)
.extent(desc.extent)
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.tiling(vk::ImageTiling::OPTIMAL)
.usage(desc.usage)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.initial_layout(vk::ImageLayout::UNDEFINED);
let image = unsafe { device.create_image(&ci, None) }.ctx("vkCreateImage")?;
let requirements = unsafe { device.get_image_memory_requirements(image) };
let allocator = ctx.allocator().clone();
let allocation = {
let mut guard = allocator.lock().expect("аллокатор отравлен паникой");
guard.allocate(&AllocationCreateDesc {
name,
requirements,
location: gpu_allocator::MemoryLocation::GpuOnly,
linear: false,
allocation_scheme: AllocationScheme::GpuAllocatorManaged,
})
};
let allocation = match allocation {
Ok(a) => a,
Err(e) => {
unsafe { device.destroy_image(image, None) };
return Err(VkError::Other(format!("gpu-allocator: {e}")));
}
};
unsafe { device.bind_image_memory(image, allocation.memory(), allocation.offset()) }
.ctx("vkBindImageMemory")?;
let view_ci = vk::ImageViewCreateInfo::default()
.image(image)
.view_type(desc.view_type)
.format(desc.format)
.subresource_range(vk::ImageSubresourceRange {
aspect_mask: desc.aspect,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
});
let view = unsafe { device.create_image_view(&view_ci, None) }.ctx("vkCreateImageView")?;
Ok(Self {
device,
allocator,
image,
view,
allocation: Some(allocation),
extent: desc.extent,
format: desc.format,
aspect: desc.aspect,
layout: vk::ImageLayout::UNDEFINED,
})
}
pub fn image(&self) -> vk::Image {
self.image
}
pub fn view(&self) -> vk::ImageView {
self.view
}
pub fn extent(&self) -> vk::Extent3D {
self.extent
}
pub fn format(&self) -> vk::Format {
self.format
}
pub fn layout(&self) -> vk::ImageLayout {
self.layout
}
/// Барьер перехода раскладки на sync2. Повторный переход в ту же раскладку —
/// не операция.
pub fn transition_to(
&mut self,
cmd: vk::CommandBuffer,
new_layout: vk::ImageLayout,
src_stage: vk::PipelineStageFlags2,
src_access: vk::AccessFlags2,
dst_stage: vk::PipelineStageFlags2,
dst_access: vk::AccessFlags2,
) {
if new_layout == self.layout {
return;
}
let barrier = vk::ImageMemoryBarrier2::default()
.src_stage_mask(src_stage)
.src_access_mask(src_access)
.dst_stage_mask(dst_stage)
.dst_access_mask(dst_access)
.old_layout(self.layout)
.new_layout(new_layout)
.image(self.image)
.subresource_range(vk::ImageSubresourceRange {
aspect_mask: self.aspect,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
});
let barriers = [barrier];
let dependency = vk::DependencyInfo::default().image_memory_barriers(&barriers);
unsafe { self.device.cmd_pipeline_barrier2(cmd, &dependency) };
self.layout = new_layout;
}
}
impl Drop for Image {
fn drop(&mut self) {
unsafe { self.device.destroy_image_view(self.view, None) };
if let Some(allocation) = self.allocation.take() {
let _ = self
.allocator
.lock()
.map(|mut guard| guard.free(allocation));
}
unsafe { self.device.destroy_image(self.image, None) };
}
}