Та же программа на 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>
183 lines
6.2 KiB
Rust
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) };
|
|
}
|
|
}
|