diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 018ba13b..2af944fc 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -265,11 +265,23 @@ pub trait GfxFramebuffer: Debug { clear: Option<&Color>, ) -> Result, GfxError>; - fn copy_to_shm(self: Rc, shm: &[Cell]) -> Result<(), GfxError>; - fn format(&self) -> &'static Format; } +pub trait GfxInternalFramebuffer: GfxFramebuffer { + fn into_fb(self: Rc) -> Rc; + + fn staging_size(&self) -> usize; + + fn download( + self: Rc, + staging: &Rc, + callback: Rc, + mem: Rc, + damage: Region, + ) -> Result, GfxError>; +} + impl dyn GfxFramebuffer { pub fn clear( &self, @@ -593,13 +605,14 @@ pub trait GfxContext: Debug { fn gfx_api(&self) -> GfxApi; - fn create_fb( + fn create_internal_fb( self: Rc, + cpu_worker: &Rc, width: i32, height: i32, stride: i32, format: &'static Format, - ) -> Result, GfxError>; + ) -> Result, GfxError>; fn sync_obj_ctx(&self) -> Option<&Rc>; diff --git a/src/gfx_apis/gl/renderer/context.rs b/src/gfx_apis/gl/renderer/context.rs index e0ab21bb..32cf2392 100644 --- a/src/gfx_apis/gl/renderer/context.rs +++ b/src/gfx_apis/gl/renderer/context.rs @@ -5,7 +5,7 @@ use { format::{Format, XRGB8888}, gfx_api::{ AsyncShmGfxTexture, BufferResvUser, GfxContext, GfxError, GfxFormat, GfxFramebuffer, - GfxImage, ResetStatus, ShmGfxTexture, + GfxImage, GfxInternalFramebuffer, ResetStatus, ShmGfxTexture, }, gfx_apis::gl::{ egl::{context::EglContext, display::EglDisplay, image::EglImage}, @@ -316,13 +316,14 @@ impl GfxContext for GlRenderContext { GfxApi::OpenGl } - fn create_fb( + fn create_internal_fb( self: Rc, + _cpu_worker: &Rc, width: i32, height: i32, _stride: i32, format: &'static Format, - ) -> Result, GfxError> { + ) -> Result, GfxError> { let fb = self.ctx.with_current(|| unsafe { GlRenderBuffer::new(&self.ctx, width, height, format)?.create_framebuffer() })?; diff --git a/src/gfx_apis/gl/renderer/framebuffer.rs b/src/gfx_apis/gl/renderer/framebuffer.rs index 071f3a29..bbe98c16 100644 --- a/src/gfx_apis/gl/renderer/framebuffer.rs +++ b/src/gfx_apis/gl/renderer/framebuffer.rs @@ -1,7 +1,11 @@ use { crate::{ format::Format, - gfx_api::{AcquireSync, GfxApiOpt, GfxError, GfxFramebuffer, ReleaseSync, SyncFile}, + gfx_api::{ + AcquireSync, AsyncShmGfxTextureCallback, GfxApiOpt, GfxError, GfxFramebuffer, + GfxInternalFramebuffer, GfxStagingBuffer, PendingShmTransfer, ReleaseSync, ShmMemory, + SyncFile, + }, gfx_apis::gl::{ gl::{ frame_buffer::GlFrameBuffer, @@ -13,6 +17,7 @@ use { sys::{GL_ONE, GL_ONE_MINUS_SRC_ALPHA}, RenderError, }, + rect::Region, theme::Color, }, std::{ @@ -104,11 +109,31 @@ impl GfxFramebuffer for Framebuffer { self.render(acquire_sync, ops, clear).map_err(|e| e.into()) } - fn copy_to_shm(self: Rc, shm: &[Cell]) -> Result<(), GfxError> { - (*self).copy_to_shm(shm).map_err(|e| e.into()) - } - fn format(&self) -> &'static Format { self.gl.rb.format } } + +impl GfxInternalFramebuffer for Framebuffer { + fn into_fb(self: Rc) -> Rc { + self + } + + fn staging_size(&self) -> usize { + 0 + } + + fn download( + self: Rc, + _staging: &Rc, + _callback: Rc, + mem: Rc, + _damage: Region, + ) -> Result, GfxError> { + let mut res = Ok(()); + mem.access(&mut |mem| res = self.copy_to_shm(mem)) + .map_err(RenderError::AccessFailed)?; + res?; + Ok(None) + } +} diff --git a/src/gfx_apis/vulkan.rs b/src/gfx_apis/vulkan.rs index 1193acd9..14e2ca82 100644 --- a/src/gfx_apis/vulkan.rs +++ b/src/gfx_apis/vulkan.rs @@ -23,7 +23,7 @@ use { cpu_worker::{jobs::read_write::ReadWriteJobError, CpuWorker}, format::Format, gfx_api::{ - AsyncShmGfxTexture, GfxContext, GfxError, GfxFormat, GfxFramebuffer, GfxImage, + AsyncShmGfxTexture, GfxContext, GfxError, GfxFormat, GfxImage, GfxInternalFramebuffer, GfxStagingBuffer, ResetStatus, ShmGfxTexture, StagingBufferUsecase, STAGING_DOWNLOAD, STAGING_UPLOAD, }, @@ -316,16 +316,23 @@ impl GfxContext for Context { GfxApi::Vulkan } - fn create_fb( + fn create_internal_fb( self: Rc, + cpu_worker: &Rc, width: i32, height: i32, stride: i32, format: &'static Format, - ) -> Result, GfxError> { - let fb = self - .0 - .create_shm_texture(format, width, height, stride, &[], true, None)?; + ) -> Result, GfxError> { + let fb = self.0.create_shm_texture( + format, + width, + height, + stride, + &[], + true, + Some(cpu_worker), + )?; Ok(fb) } diff --git a/src/gfx_apis/vulkan/image.rs b/src/gfx_apis/vulkan/image.rs index b005c7bc..43fcb376 100644 --- a/src/gfx_apis/vulkan/image.rs +++ b/src/gfx_apis/vulkan/image.rs @@ -4,8 +4,8 @@ use { gfx_api::{ AcquireSync, AsyncShmGfxTexture, AsyncShmGfxTextureCallback, AsyncShmGfxTextureTransferCancellable, GfxApiOpt, GfxError, GfxFramebuffer, GfxImage, - GfxStagingBuffer, GfxTexture, PendingShmTransfer, ReleaseSync, ShmGfxTexture, - ShmMemory, SyncFile, + GfxInternalFramebuffer, GfxStagingBuffer, GfxTexture, PendingShmTransfer, ReleaseSync, + ShmGfxTexture, ShmMemory, SyncFile, }, gfx_apis::vulkan::{ allocator::VulkanAllocation, device::VulkanDevice, format::VulkanModifierLimits, @@ -510,17 +510,46 @@ impl GfxFramebuffer for VulkanImage { .map_err(|e| e.into()) } - fn copy_to_shm(self: Rc, shm: &[Cell]) -> Result<(), GfxError> { - self.renderer - .read_all_pixels(&self, shm) - .map_err(|e| e.into()) - } - fn format(&self) -> &'static Format { self.format } } +impl GfxInternalFramebuffer for VulkanImage { + fn into_fb(self: Rc) -> Rc { + self + } + + fn staging_size(&self) -> usize { + let VulkanImageMemory::Internal(shm) = &self.ty else { + unreachable!(); + }; + shm.size as _ + } + + fn download( + self: Rc, + staging: &Rc, + callback: Rc, + mem: Rc, + damage: Region, + ) -> Result, GfxError> { + let VulkanImageMemory::Internal(shm) = &self.ty else { + unreachable!(); + }; + let staging = staging.clone().into_vk(&self.renderer.device.device); + let pending = shm.async_transfer( + &self, + staging, + &mem, + damage, + callback, + TransferType::Download, + )?; + Ok(pending) + } +} + impl GfxTexture for VulkanImage { fn size(&self) -> (i32, i32) { (self.width as _, self.height as _) diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index e3feb025..053877ad 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -32,11 +32,10 @@ use { ash::{ vk, vk::{ - AccessFlags2, AttachmentLoadOp, AttachmentStoreOp, BufferImageCopy, - BufferMemoryBarrier2, ClearColorValue, ClearValue, CommandBuffer, - CommandBufferBeginInfo, CommandBufferSubmitInfo, CommandBufferUsageFlags, - CopyImageInfo2, DependencyInfo, DependencyInfoKHR, DescriptorImageInfo, DescriptorType, - Extent2D, Extent3D, Fence, ImageAspectFlags, ImageCopy2, ImageLayout, + AccessFlags2, AttachmentLoadOp, AttachmentStoreOp, ClearColorValue, ClearValue, + CommandBuffer, CommandBufferBeginInfo, CommandBufferSubmitInfo, + CommandBufferUsageFlags, CopyImageInfo2, DependencyInfoKHR, DescriptorImageInfo, + DescriptorType, Extent2D, Extent3D, ImageAspectFlags, ImageCopy2, ImageLayout, ImageMemoryBarrier2, ImageSubresourceLayers, ImageSubresourceRange, PipelineBindPoint, PipelineStageFlags2, Rect2D, RenderingAttachmentInfo, RenderingInfo, SemaphoreSubmitInfo, SemaphoreSubmitInfoKHR, ShaderStageFlags, SubmitInfo2, Viewport, @@ -49,7 +48,7 @@ use { std::{ cell::{Cell, RefCell}, fmt::{Debug, Formatter}, - mem, ptr, + mem, rc::Rc, slice, }, @@ -905,143 +904,6 @@ impl VulkanRenderer { frame.waiter.set(Some(future)); } - pub(super) fn read_all_pixels( - self: &Rc, - tex: &VulkanImage, - dst: &[Cell], - ) -> Result<(), VulkanError> { - let Some(shm_info) = &tex.format.shm_info else { - return Err(VulkanError::UnsupportedShmFormat(tex.format.name)); - }; - let size = tex.stride as u64 * tex.height as u64; - if size != dst.len() as u64 { - return Err(VulkanError::InvalidBufferSize); - } - let region = BufferImageCopy::default() - .buffer_row_length(tex.stride / shm_info.bpp) - .buffer_image_height(tex.height) - .image_subresource(ImageSubresourceLayers { - aspect_mask: ImageAspectFlags::COLOR, - mip_level: 0, - base_array_layer: 0, - layer_count: 1, - }) - .image_extent(Extent3D { - width: tex.width, - height: tex.height, - depth: 1, - }); - let staging = - self.device - .create_staging_buffer(&self.allocator, size, false, true, true)?; - let initial_tex_barrier; - let initial_buffer_barrier = BufferMemoryBarrier2::default() - .buffer(staging.buffer) - .offset(0) - .size(staging.size) - .dst_access_mask(AccessFlags2::TRANSFER_WRITE) - .dst_stage_mask(PipelineStageFlags2::TRANSFER); - let mut initial_barriers = DependencyInfo::default() - .buffer_memory_barriers(slice::from_ref(&initial_buffer_barrier)); - if tex.bridge.is_none() { - initial_tex_barrier = image_barrier() - .src_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) - .dst_queue_family_index(self.device.graphics_queue_idx) - .image(tex.image) - .old_layout(ImageLayout::GENERAL) - .new_layout(ImageLayout::TRANSFER_SRC_OPTIMAL) - .dst_access_mask(AccessFlags2::TRANSFER_READ) - .dst_stage_mask(PipelineStageFlags2::TRANSFER); - initial_barriers = - initial_barriers.image_memory_barriers(slice::from_ref(&initial_tex_barrier)); - } - let final_tex_barrier; - let final_buffer_barrier = BufferMemoryBarrier2::default() - .buffer(staging.buffer) - .offset(0) - .size(staging.size) - .src_access_mask(AccessFlags2::TRANSFER_WRITE) - .src_stage_mask(PipelineStageFlags2::TRANSFER) - .dst_access_mask(AccessFlags2::HOST_READ) - .dst_stage_mask(PipelineStageFlags2::HOST); - let mut final_barriers = DependencyInfo::default() - .buffer_memory_barriers(slice::from_ref(&final_buffer_barrier)); - if tex.bridge.is_none() { - final_tex_barrier = image_barrier() - .src_queue_family_index(self.device.graphics_queue_idx) - .dst_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) - .image(tex.image) - .old_layout(ImageLayout::TRANSFER_SRC_OPTIMAL) - .new_layout(ImageLayout::GENERAL) - .src_access_mask(AccessFlags2::TRANSFER_READ) - .src_stage_mask(PipelineStageFlags2::TRANSFER); - final_barriers = - final_barriers.image_memory_barriers(slice::from_ref(&final_tex_barrier)); - } - let buf = self.gfx_command_buffers.allocate()?; - let mut semaphores = vec![]; - let mut semaphore_infos = vec![]; - if let VulkanImageMemory::DmaBuf(buf) = &tex.ty { - for plane in &buf.template.dmabuf.planes { - let fd = dma_buf_export_sync_file(&plane.fd, DMA_BUF_SYNC_READ) - .map_err(VulkanError::IoctlExportSyncFile)?; - let semaphore = self.allocate_semaphore()?; - semaphore.import_sync_file(fd)?; - let semaphore_info = SemaphoreSubmitInfo::default() - .semaphore(semaphore.semaphore) - .stage_mask(PipelineStageFlags2::TOP_OF_PIPE); - semaphores.push(semaphore); - semaphore_infos.push(semaphore_info); - } - } - let command_buffer_info = CommandBufferSubmitInfo::default().command_buffer(buf.buffer); - let submit_info = SubmitInfo2::default() - .wait_semaphore_infos(&semaphore_infos) - .command_buffer_infos(slice::from_ref(&command_buffer_info)); - let begin_info = - CommandBufferBeginInfo::default().flags(CommandBufferUsageFlags::ONE_TIME_SUBMIT); - unsafe { - self.device - .device - .begin_command_buffer(buf.buffer, &begin_info) - .map_err(VulkanError::BeginCommandBuffer)?; - self.device - .device - .cmd_pipeline_barrier2(buf.buffer, &initial_barriers); - self.device.device.cmd_copy_image_to_buffer( - buf.buffer, - tex.image, - ImageLayout::TRANSFER_SRC_OPTIMAL, - staging.buffer, - &[region], - ); - self.device - .device - .cmd_pipeline_barrier2(buf.buffer, &final_barriers); - self.device - .device - .end_command_buffer(buf.buffer) - .map_err(VulkanError::EndCommandBuffer)?; - self.device - .device - .queue_submit2( - self.device.graphics_queue, - slice::from_ref(&submit_info), - Fence::null(), - ) - .map_err(VulkanError::Submit)?; - } - self.block(); - self.gfx_command_buffers.buffers.push(buf); - for semaphore in semaphores { - self.wait_semaphores.push(semaphore); - } - staging.download(|mem, size| unsafe { - ptr::copy_nonoverlapping(mem, dst.as_ptr() as _, size); - })?; - Ok(()) - } - pub fn execute( self: &Rc, fb: &VulkanImage, diff --git a/src/ifs/zwlr_screencopy_frame_v1.rs b/src/ifs/zwlr_screencopy_frame_v1.rs index 44afe8c5..41183bd8 100644 --- a/src/ifs/zwlr_screencopy_frame_v1.rs +++ b/src/ifs/zwlr_screencopy_frame_v1.rs @@ -2,6 +2,7 @@ use { crate::{ client::{Client, ClientError}, format::XRGB8888, + gfx_api::{AsyncShmGfxTextureCallback, GfxError, PendingShmTransfer}, ifs::{ wl_buffer::{WlBuffer, WlBufferError, WlBufferStorage}, wl_output::OutputGlobalOpt, @@ -9,6 +10,7 @@ use { leaks::Tracker, object::{Object, Version}, rect::Rect, + utils::errorfmt::ErrorFmt, wire::{zwlr_screencopy_frame_v1::*, WlBufferId, ZwlrScreencopyFrameV1Id}, }, std::{cell::Cell, ops::Deref, rc::Rc}, @@ -29,6 +31,7 @@ pub struct ZwlrScreencopyFrameV1 { pub with_damage: Cell, pub buffer: Cell>>, pub version: Version, + pub pending: Cell>, } impl ZwlrScreencopyFrameV1 { @@ -132,6 +135,7 @@ impl ZwlrScreencopyFrameV1 { node.screencopies.remove(&(self.client.id, self.id)); node.screencast_changed(); } + self.pending.take(); } } @@ -153,6 +157,22 @@ impl ZwlrScreencopyFrameV1RequestHandler for ZwlrScreencopyFrameV1 { } } +impl AsyncShmGfxTextureCallback for ZwlrScreencopyFrameV1 { + fn completed(self: Rc, res: Result<(), GfxError>) { + self.pending.take(); + match res { + Ok(_) => { + let now = self.client.state.now(); + self.send_ready(now.0.tv_sec as _, now.0.tv_nsec as _); + } + Err(e) => { + log::warn!("Could not perform shm screencopy: {}", ErrorFmt(e)); + self.send_failed(); + } + } + } +} + object_base! { self = ZwlrScreencopyFrameV1; version = self.version; diff --git a/src/ifs/zwlr_screencopy_manager_v1.rs b/src/ifs/zwlr_screencopy_manager_v1.rs index ae99a4b4..66520274 100644 --- a/src/ifs/zwlr_screencopy_manager_v1.rs +++ b/src/ifs/zwlr_screencopy_manager_v1.rs @@ -130,6 +130,7 @@ impl ZwlrScreencopyManagerV1 { with_damage: Cell::new(false), buffer: Cell::new(None), version: self.version, + pending: Default::default(), }); track!(self.client, frame); self.client.add_client_obj(&frame)?; diff --git a/src/it/test_gfx_api.rs b/src/it/test_gfx_api.rs index e6a0de03..87d33a81 100644 --- a/src/it/test_gfx_api.rs +++ b/src/it/test_gfx_api.rs @@ -6,8 +6,8 @@ use { gfx_api::{ AcquireSync, AsyncShmGfxTexture, AsyncShmGfxTextureCallback, CopyTexture, FillRect, FramebufferRect, GfxApiOpt, GfxContext, GfxError, GfxFormat, GfxFramebuffer, GfxImage, - GfxStagingBuffer, GfxTexture, GfxWriteModifier, PendingShmTransfer, ReleaseSync, - ResetStatus, ShmGfxTexture, ShmMemory, SyncFile, + GfxInternalFramebuffer, GfxStagingBuffer, GfxTexture, GfxWriteModifier, + PendingShmTransfer, ReleaseSync, ResetStatus, ShmGfxTexture, ShmMemory, SyncFile, }, rect::{Rect, Region}, theme::Color, @@ -165,13 +165,14 @@ impl GfxContext for TestGfxCtx { GfxApi::OpenGl } - fn create_fb( + fn create_internal_fb( self: Rc, + _cpu_worker: &Rc, width: i32, height: i32, stride: i32, format: &'static Format, - ) -> Result, GfxError> { + ) -> Result, GfxError> { assert!(stride >= width * 4); Ok(Rc::new(TestGfxFb { img: Rc::new(TestGfxImage::Shm(TestShmGfxImage { @@ -548,15 +549,34 @@ impl GfxFramebuffer for TestGfxFb { Ok(None) } - fn copy_to_shm(self: Rc, shm: &[Cell]) -> Result<(), GfxError> { - self.img.deref().read_pixels(shm) - } - fn format(&self) -> &'static Format { &ARGB8888 } } +impl GfxInternalFramebuffer for TestGfxFb { + fn into_fb(self: Rc) -> Rc { + self + } + + fn staging_size(&self) -> usize { + 0 + } + + fn download( + self: Rc, + _staging: &Rc, + _callback: Rc, + mem: Rc, + _damage: Region, + ) -> Result, GfxError> { + let mut res = Ok(()); + mem.access(&mut |mem| res = self.img.deref().read_pixels(mem)) + .map_err(TestGfxError::AccessFailed)?; + res.map(|_| None) + } +} + impl dyn GfxTexture { fn as_native(&self) -> &TestGfxImage { self.as_any() diff --git a/src/state.rs b/src/state.rs index 75a13882..f9222f65 100644 --- a/src/state.rs +++ b/src/state.rs @@ -27,8 +27,8 @@ use { forker::ForkerProxy, format::Format, gfx_api::{ - AcquireSync, BufferResv, GfxContext, GfxError, GfxFramebuffer, GfxTexture, ReleaseSync, - SampleRect, SyncFile, + AcquireSync, BufferResv, GfxContext, GfxError, GfxFramebuffer, GfxTexture, + PendingShmTransfer, ReleaseSync, SampleRect, SyncFile, STAGING_DOWNLOAD, }, gfx_apis::create_gfx_context, globals::{Globals, GlobalsError, RemovableWaylandGlobal, WaylandGlobal}, @@ -62,7 +62,7 @@ use { io_uring::IoUring, leaks::Tracker, logger::Logger, - rect::Rect, + rect::{Rect, Region}, renderer::Renderer, scale::Scale, security_context_acceptor::SecurityContextAcceptors, @@ -1014,25 +1014,32 @@ impl State { x_off: i32, y_off: i32, size: Option<(i32, i32)>, - capture: &ZwlrScreencopyFrameV1, - mem: &ClientMemOffset, + capture: &Rc, + mem: &Rc, stride: i32, format: &'static Format, transform: Transform, scale: Scale, - ) -> Result<(), ShmScreencopyError> { + ) -> Result, ShmScreencopyError> { let Some(ctx) = self.render_ctx.get() else { return Err(ShmScreencopyError::NoRenderContext); }; let fb = ctx - .create_fb(capture.rect.width(), capture.rect.height(), stride, format) + .clone() + .create_internal_fb( + &self.cpu_worker, + capture.rect.width(), + capture.rect.height(), + stride, + format, + ) .map_err(ShmScreencopyError::CreateTemporaryFb)?; self.perform_screencopy( src, None, acquire_sync, ReleaseSync::None, - &fb, + &fb.clone().into_fb(), AcquireSync::Unnecessary, ReleaseSync::None, transform, @@ -1045,14 +1052,16 @@ impl State { scale, ) .map_err(ShmScreencopyError::CopyToTemporary)?; - let acc = mem.access(|mem| fb.copy_to_shm(mem)); - match acc { - Ok(res) => res.map_err(ShmScreencopyError::ReadPixels), - Err(e) => { - capture.client.error(e); - Ok(()) - } - } + let staging = ctx.create_staging_buffer(fb.staging_size(), STAGING_DOWNLOAD); + let pending = fb + .download( + &staging, + capture.clone(), + mem.clone(), + Region::new2(capture.rect.at_point(0, 0)), + ) + .map_err(ShmScreencopyError::ReadPixels)?; + Ok(pending) } pub fn create_seat(self: &Rc, name: &str) -> Rc { diff --git a/src/tree/output.rs b/src/tree/output.rs index d0dffce6..92f85598 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -267,6 +267,7 @@ impl OutputNode { capture.send_failed(); continue; } + let mut ready = true; if let Some(storage) = wl_buffer.storage.borrow_mut().deref() { match storage { WlBufferStorage::Shm { mem, stride } => { @@ -284,10 +285,16 @@ impl OutputNode { self.global.persistent.transform.get(), self.global.persistent.scale.get(), ); - if let Err(e) = res { - log::warn!("Could not perform shm screencopy: {}", ErrorFmt(e)); - capture.send_failed(); - continue; + match res { + Ok(p) => { + ready = p.is_none(); + capture.pending.set(p); + } + Err(e) => { + log::warn!("Could not perform shm screencopy: {}", ErrorFmt(e)); + capture.send_failed(); + continue; + } } } WlBufferStorage::Dmabuf { fb, .. } => { @@ -327,7 +334,9 @@ impl OutputNode { if capture.with_damage.get() { capture.send_damage(); } - capture.send_ready(now.0.tv_sec as _, now.0.tv_nsec as _); + if ready { + capture.send_ready(now.0.tv_sec as _, now.0.tv_nsec as _); + } } self.screencast_changed(); }