use { crate::{ format::{Format, FormatShmInfo}, gfx_api::SyncFile, gfx_apis::vulkan::{ allocator::VulkanAllocation, command::VulkanCommandBuffer, fence::VulkanFence, image::{VulkanImage, VulkanImageMemory}, renderer::{image_barrier, VulkanRenderer}, staging::VulkanStagingBuffer, util::OnDrop, VulkanError, }, rect::Rect, utils::errorfmt::ErrorFmt, }, ash::vk::{ AccessFlags2, BufferImageCopy2, BufferMemoryBarrier2, CommandBufferBeginInfo, CommandBufferSubmitInfo, CommandBufferUsageFlags, CopyBufferToImageInfo2, DependencyInfoKHR, DeviceSize, Extent3D, ImageAspectFlags, ImageCreateInfo, ImageLayout, ImageSubresourceLayers, ImageSubresourceRange, ImageTiling, ImageType, ImageUsageFlags, ImageViewCreateInfo, ImageViewType, Offset3D, PipelineStageFlags2, SampleCountFlags, SharingMode, SubmitInfo2, }, gpu_alloc::UsageFlags, isnt::std_1::primitive::IsntSliceExt, std::{cell::Cell, ptr, rc::Rc, slice}, }; pub struct VulkanShmImage { pub(super) _size: DeviceSize, pub(super) stride: u32, pub(super) _allocation: VulkanAllocation, pub(super) shm_info: &'static FormatShmInfo, } impl VulkanShmImage { pub fn upload( &self, img: &Rc, buffer: &[Cell], damage: Option<&[Rect]>, ) -> Result<(), VulkanError> { if let Some(damage) = damage { if damage.is_empty() { return Ok(()); } } let copy = |full: bool, off, x, y, width, height| { let mut builder = BufferImageCopy2::default() .buffer_offset(off) .image_offset(Offset3D { x, y, z: 0 }) .image_extent(Extent3D { width, height, depth: 1, }) .image_subresource(ImageSubresourceLayers { aspect_mask: ImageAspectFlags::COLOR, mip_level: 0, base_array_layer: 0, layer_count: 1, }); if full { builder = builder .buffer_image_height(img.height) .buffer_row_length(img.stride / self.shm_info.bpp); } builder }; let mut total_size; let cpy_one; let mut cpy_many; let cpy; if let Some(damage) = damage { total_size = 0; cpy_many = Vec::with_capacity(damage.len()); for damage in damage { let Some(damage) = Rect::new( damage.x1().max(0), damage.y1().max(0), damage.x2().min(img.width as i32), damage.y2().min(img.height as i32), ) else { continue; }; if damage.is_empty() { continue; } cpy_many.push(copy( false, total_size as DeviceSize, damage.x1(), damage.y1(), damage.width() as u32, damage.height() as u32, )); total_size += damage.width() as u32 * damage.height() as u32 * self.shm_info.bpp; } cpy = &cpy_many[..]; } else { cpy_one = copy(true, 0, 0, 0, img.width, img.height); cpy = slice::from_ref(&cpy_one); total_size = img.height * img.stride; } let staging = img.renderer.device.create_staging_buffer( &img.renderer.allocator, total_size as u64, true, false, true, )?; staging.upload(|mem, _| unsafe { let buf = buffer.as_ptr() as *const u8; if damage.is_some() { let mut off = 0; for cpy in cpy { let x = cpy.image_offset.x as usize; let y = cpy.image_offset.y as usize; let width = cpy.image_extent.width as usize; let height = cpy.image_extent.height as usize; let stride = self.stride as usize; let bpp = self.shm_info.bpp as usize; for dy in 0..height { let lo = (y + dy) * stride + x * bpp; let len = width * bpp; ptr::copy_nonoverlapping(buf.add(lo), mem.add(off), len); off += len; } } } else { ptr::copy_nonoverlapping(buf, mem, total_size as usize); } })?; let memory_barrier = |sam, ssm, dam, dsm| { BufferMemoryBarrier2::default() .buffer(staging.buffer) .offset(0) .size(staging.size) .src_access_mask(sam) .src_stage_mask(ssm) .dst_access_mask(dam) .dst_stage_mask(dsm) }; let initial_image_barrier = image_barrier() .image(img.image) .src_access_mask(AccessFlags2::SHADER_SAMPLED_READ) .src_stage_mask(PipelineStageFlags2::FRAGMENT_SHADER) .old_layout(if img.is_undefined.get() { ImageLayout::UNDEFINED } else { ImageLayout::SHADER_READ_ONLY_OPTIMAL }) .new_layout(ImageLayout::TRANSFER_DST_OPTIMAL) .dst_access_mask(AccessFlags2::TRANSFER_WRITE) .dst_stage_mask(PipelineStageFlags2::TRANSFER); let initial_buffer_barrier = memory_barrier( AccessFlags2::HOST_WRITE, PipelineStageFlags2::HOST, AccessFlags2::TRANSFER_READ, PipelineStageFlags2::TRANSFER, ); let initial_dep_info = DependencyInfoKHR::default() .buffer_memory_barriers(slice::from_ref(&initial_buffer_barrier)) .image_memory_barriers(slice::from_ref(&initial_image_barrier)); let final_image_barrier = image_barrier() .image(img.image) .src_access_mask(AccessFlags2::TRANSFER_WRITE) .src_stage_mask(PipelineStageFlags2::TRANSFER) .old_layout(ImageLayout::TRANSFER_DST_OPTIMAL) .new_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL) .dst_access_mask(AccessFlags2::SHADER_SAMPLED_READ) .dst_stage_mask(PipelineStageFlags2::FRAGMENT_SHADER); let final_buffer_barrier = memory_barrier( AccessFlags2::TRANSFER_READ, PipelineStageFlags2::TRANSFER, AccessFlags2::HOST_WRITE, PipelineStageFlags2::HOST, ); let final_dep_info = DependencyInfoKHR::default() .buffer_memory_barriers(slice::from_ref(&final_buffer_barrier)) .image_memory_barriers(slice::from_ref(&final_image_barrier)); let cpy_info = CopyBufferToImageInfo2::default() .src_buffer(staging.buffer) .dst_image(img.image) .dst_image_layout(ImageLayout::TRANSFER_DST_OPTIMAL) .regions(cpy); let cmd = img.renderer.allocate_command_buffer()?; let dev = &img.renderer.device.device; let command_buffer_info = CommandBufferSubmitInfo::default().command_buffer(cmd.buffer); let submit_info = SubmitInfo2::default().command_buffer_infos(slice::from_ref(&command_buffer_info)); let begin_info = CommandBufferBeginInfo::default().flags(CommandBufferUsageFlags::ONE_TIME_SUBMIT); let release_fence = img.renderer.device.create_fence()?; unsafe { dev.begin_command_buffer(cmd.buffer, &begin_info) .map_err(VulkanError::BeginCommandBuffer)?; dev.cmd_pipeline_barrier2(cmd.buffer, &initial_dep_info); dev.cmd_copy_buffer_to_image2(cmd.buffer, &cpy_info); dev.cmd_pipeline_barrier2(cmd.buffer, &final_dep_info); dev.end_command_buffer(cmd.buffer) .map_err(VulkanError::EndCommandBuffer)?; dev.queue_submit2( img.renderer.device.graphics_queue, slice::from_ref(&submit_info), release_fence.fence, ) .map_err(VulkanError::Submit)?; } img.is_undefined.set(false); let release_sync_file = match release_fence.export_sync_file() { Ok(s) => s, Err(e) => { log::error!("Could not export sync file from fence: {}", ErrorFmt(e)); img.renderer.block(); return Ok(()); } }; let point = img.renderer.allocate_point(); let future = img.renderer.eng.spawn(await_upload( point, img.clone(), cmd, release_sync_file, release_fence, staging, )); img.renderer.pending_uploads.set(point, future); Ok(()) } } async fn await_upload( id: u64, img: Rc, buf: Rc, sync_file: SyncFile, _fence: Rc, _staging: VulkanStagingBuffer, ) { let res = img.renderer.ring.readable(&sync_file.0).await; if let Err(e) = res { log::error!( "Could not wait for sync file to become readable: {}", ErrorFmt(e) ); img.renderer.block(); } img.renderer.command_buffers.push(buf); img.renderer.pending_uploads.remove(&id); } impl VulkanRenderer { pub fn create_shm_texture( self: &Rc, format: &'static Format, width: i32, height: i32, stride: i32, data: &[Cell], for_download: bool, ) -> Result, VulkanError> { let Some(shm_info) = &format.shm_info else { return Err(VulkanError::UnsupportedShmFormat(format.name)); }; if width <= 0 || height <= 0 || stride <= 0 { return Err(VulkanError::NonPositiveImageSize); } let width = width as u32; let height = height as u32; let stride = stride as u32; if stride % shm_info.bpp != 0 || stride / shm_info.bpp < width { return Err(VulkanError::InvalidStride); } let vk_format = self .device .formats .get(&format.drm) .ok_or(VulkanError::FormatNotSupported)?; let shm = vk_format.shm.as_ref().ok_or(VulkanError::ShmNotSupported)?; if width > shm.limits.max_width || height > shm.limits.max_height { return Err(VulkanError::ImageTooLarge); } let size = stride.checked_mul(height).ok_or(VulkanError::ShmOverflow)?; let usage = ImageUsageFlags::TRANSFER_SRC | match for_download { true => ImageUsageFlags::COLOR_ATTACHMENT, false => ImageUsageFlags::SAMPLED | ImageUsageFlags::TRANSFER_DST, }; let create_info = ImageCreateInfo::default() .image_type(ImageType::TYPE_2D) .format(format.vk_format) .mip_levels(1) .array_layers(1) .tiling(ImageTiling::OPTIMAL) .samples(SampleCountFlags::TYPE_1) .sharing_mode(SharingMode::EXCLUSIVE) .initial_layout(ImageLayout::UNDEFINED) .extent(Extent3D { width, height, depth: 1, }) .usage(usage); let image = unsafe { self.device.device.create_image(&create_info, None) }; let image = image.map_err(VulkanError::CreateImage)?; let destroy_image = OnDrop(|| unsafe { self.device.device.destroy_image(image, None) }); let memory_requirements = unsafe { self.device.device.get_image_memory_requirements(image) }; let allocation = self.allocator .alloc(&memory_requirements, UsageFlags::FAST_DEVICE_ACCESS, false)?; let res = unsafe { self.device .device .bind_image_memory(image, allocation.memory, allocation.offset) }; res.map_err(VulkanError::BindImageMemory)?; let image_view_create_info = ImageViewCreateInfo::default() .image(image) .format(format.vk_format) .view_type(ImageViewType::TYPE_2D) .subresource_range(ImageSubresourceRange { aspect_mask: ImageAspectFlags::COLOR, base_mip_level: 0, level_count: 1, base_array_layer: 0, layer_count: 1, }); let view = unsafe { self.device .device .create_image_view(&image_view_create_info, None) }; let view = view.map_err(VulkanError::CreateImageView)?; let shm = VulkanShmImage { _size: size as u64, stride, _allocation: allocation, shm_info, }; destroy_image.forget(); let img = Rc::new(VulkanImage { renderer: self.clone(), format, width, height, stride, texture_view: view, render_view: None, image, is_undefined: Cell::new(true), ty: VulkanImageMemory::Internal(shm), render_ops: Default::default(), bridge: None, }); let shm = match &img.ty { VulkanImageMemory::DmaBuf(_) => unreachable!(), VulkanImageMemory::Internal(s) => s, }; if data.is_not_empty() { shm.upload(&img, data, None)?; } Ok(img) } }