From 3619a51fbd630df7157c685bc50b029a224e2948 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 5 Oct 2024 16:49:22 +0200 Subject: [PATCH] gfx: add GfxStagingBuffer --- src/gfx_api.rs | 35 +++++++++++ src/gfx_apis/gl/renderer/texture.rs | 5 +- src/gfx_apis/vulkan.rs | 19 +++++- src/gfx_apis/vulkan/image.rs | 14 ++++- src/gfx_apis/vulkan/shm_image.rs | 39 +++++++----- src/gfx_apis/vulkan/staging.rs | 89 +++++++++++++++++++++++---- src/ifs/wl_buffer.rs | 1 + src/ifs/wl_surface.rs | 7 ++- src/ifs/wl_surface/commit_timeline.rs | 24 +++++++- src/it/test_gfx_api.rs | 5 +- src/text.rs | 20 +++++- 11 files changed, 215 insertions(+), 43 deletions(-) diff --git a/src/gfx_api.rs b/src/gfx_api.rs index a8214101..e98cd032 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -503,6 +503,17 @@ pub trait AsyncShmGfxTextureCallback { fn completed(self: Rc, res: Result<(), GfxError>); } +bitflags! { + StagingBufferUsecase: u32; + STAGING_UPLOAD = 1 << 0, + STAGING_DOWNLOAD = 1 << 1, +} + +pub trait GfxStagingBuffer { + fn size(&self) -> usize; + fn into_any(self: Rc) -> Rc; +} + pub trait AsyncShmGfxTextureUploadCancellable { fn cancel(&self, id: u64); } @@ -539,8 +550,13 @@ impl ShmMemory for Vec> { } pub trait AsyncShmGfxTexture: GfxTexture { + fn staging_size(&self) -> usize { + 0 + } + fn async_upload( self: Rc, + staging: &Rc, callback: Rc, mem: Rc, damage: Region, @@ -605,6 +621,25 @@ pub trait GfxContext: Debug { ) -> Result, GfxError>; fn sync_obj_ctx(&self) -> Option<&Rc>; + + fn create_staging_buffer( + &self, + size: usize, + usecase: StagingBufferUsecase, + ) -> Rc { + let _ = usecase; + struct Dummy(usize); + impl GfxStagingBuffer for Dummy { + fn size(&self) -> usize { + self.0 + } + + fn into_any(self: Rc) -> Rc { + self + } + } + Rc::new(Dummy(size)) + } } #[derive(Clone, Debug)] diff --git a/src/gfx_apis/gl/renderer/texture.rs b/src/gfx_apis/gl/renderer/texture.rs index 7add2b78..7a4e1103 100644 --- a/src/gfx_apis/gl/renderer/texture.rs +++ b/src/gfx_apis/gl/renderer/texture.rs @@ -2,8 +2,8 @@ use { crate::{ format::Format, gfx_api::{ - AsyncShmGfxTexture, AsyncShmGfxTextureCallback, GfxError, GfxTexture, PendingShmUpload, - ShmGfxTexture, ShmMemory, + AsyncShmGfxTexture, AsyncShmGfxTextureCallback, GfxError, GfxStagingBuffer, GfxTexture, + PendingShmUpload, ShmGfxTexture, ShmMemory, }, gfx_apis::gl::{ gl::texture::GlTexture, @@ -100,6 +100,7 @@ impl ShmGfxTexture for Texture { impl AsyncShmGfxTexture for Texture { fn async_upload( self: Rc, + _staging: &Rc, _callback: Rc, mem: Rc, _damage: Region, diff --git a/src/gfx_apis/vulkan.rs b/src/gfx_apis/vulkan.rs index 4562377b..0924f9ab 100644 --- a/src/gfx_apis/vulkan.rs +++ b/src/gfx_apis/vulkan.rs @@ -23,7 +23,8 @@ use { format::Format, gfx_api::{ AsyncShmGfxTexture, GfxContext, GfxError, GfxFormat, GfxFramebuffer, GfxImage, - ResetStatus, ShmGfxTexture, + GfxStagingBuffer, ResetStatus, ShmGfxTexture, StagingBufferUsecase, STAGING_DOWNLOAD, + STAGING_UPLOAD, }, gfx_apis::vulkan::{ image::VulkanImageMemory, instance::VulkanInstance, renderer::VulkanRenderer, @@ -203,6 +204,10 @@ pub enum VulkanError { AsyncCopyToStaging(#[source] ReadWriteJobError), #[error("The async shm texture is busy")] AsyncCopyBusy, + #[error("The staging buffer is busy")] + StagingBufferBusy, + #[error("The staging buffer does not support uploads")] + StagingBufferNoUpload, } impl From for GfxError { @@ -330,6 +335,18 @@ impl GfxContext for Context { fn sync_obj_ctx(&self) -> Option<&Rc> { Some(&self.0.device.sync_ctx) } + + fn create_staging_buffer( + &self, + size: usize, + usecase: StagingBufferUsecase, + ) -> Rc { + let upload = usecase.contains(STAGING_UPLOAD); + let download = usecase.contains(STAGING_DOWNLOAD); + self.0 + .device + .create_staging_shell(size as u64, upload, download) + } } impl Drop for Context { diff --git a/src/gfx_apis/vulkan/image.rs b/src/gfx_apis/vulkan/image.rs index 6826c2f6..6df5788a 100644 --- a/src/gfx_apis/vulkan/image.rs +++ b/src/gfx_apis/vulkan/image.rs @@ -4,7 +4,8 @@ use { gfx_api::{ AcquireSync, AsyncShmGfxTexture, AsyncShmGfxTextureCallback, AsyncShmGfxTextureUploadCancellable, GfxApiOpt, GfxError, GfxFramebuffer, GfxImage, - GfxTexture, PendingShmUpload, ReleaseSync, ShmGfxTexture, ShmMemory, SyncFile, + GfxStagingBuffer, GfxTexture, PendingShmUpload, ReleaseSync, ShmGfxTexture, ShmMemory, + SyncFile, }, gfx_apis::vulkan::{ allocator::VulkanAllocation, device::VulkanDevice, format::VulkanModifierLimits, @@ -575,8 +576,16 @@ impl ShmGfxTexture for VulkanImage { } impl AsyncShmGfxTexture for VulkanImage { + fn staging_size(&self) -> usize { + let VulkanImageMemory::Internal(shm) = &self.ty else { + unreachable!(); + }; + shm.size as _ + } + fn async_upload( self: Rc, + staging: &Rc, callback: Rc, mem: Rc, damage: Region, @@ -584,7 +593,8 @@ impl AsyncShmGfxTexture for VulkanImage { let VulkanImageMemory::Internal(shm) = &self.ty else { unreachable!(); }; - let pending = shm.async_upload(&self, &mem, damage, callback)?; + let staging = staging.clone().into_vk(&self.renderer.device.device); + let pending = shm.async_upload(&self, staging, &mem, damage, callback)?; Ok(pending) } diff --git a/src/gfx_apis/vulkan/shm_image.rs b/src/gfx_apis/vulkan/shm_image.rs index ced7ea0a..2b11f6ad 100644 --- a/src/gfx_apis/vulkan/shm_image.rs +++ b/src/gfx_apis/vulkan/shm_image.rs @@ -17,7 +17,7 @@ use { fence::VulkanFence, image::{QueueFamily, QueueState, QueueTransfer, VulkanImage, VulkanImageMemory}, renderer::{image_barrier, VulkanRenderer}, - staging::VulkanStagingBuffer, + staging::{VulkanStagingBuffer, VulkanStagingShell}, VulkanError, }, rect::{Rect, Region}, @@ -55,7 +55,7 @@ pub struct VulkanShmImageAsyncData { pub(super) busy: Cell, pub(super) io_job: Cell>>, pub(super) copy_job: Cell>>, - pub(super) staging: CloneCell>>, + pub(super) staging: CloneCell>>, pub(super) callback: Cell>>, pub(super) callback_id: Cell, pub(super) regions: RefCell>>, @@ -320,6 +320,7 @@ async fn await_upload( impl VulkanShmImageAsyncData { fn complete(&self, result: Result<(), VulkanError>) { self.busy.set(false); + self.staging.take().unwrap().busy.set(false); if let Some(cb) = self.callback.take() { cb.completed(result.map_err(|e| e.into())); } @@ -330,12 +331,13 @@ impl VulkanShmImage { pub fn async_upload( &self, img: &Rc, + staging: Rc, client_mem: &Rc, damage: Region, callback: Rc, ) -> Result, VulkanError> { let data = self.async_data.as_ref().unwrap(); - let res = self.try_async_upload(img, data, client_mem, damage); + let res = self.try_async_upload(img, staging, data, client_mem, damage); match res { Ok(()) => { let id = img.renderer.allocate_point(); @@ -350,6 +352,7 @@ impl VulkanShmImage { fn try_async_upload( &self, img: &Rc, + staging: Rc, data: &VulkanShmImageAsyncData, client_mem: &Rc, mut damage: Region, @@ -357,11 +360,19 @@ impl VulkanShmImage { if data.busy.get() { return Err(VulkanError::AsyncCopyBusy); } + if staging.busy.get() { + return Err(VulkanError::StagingBufferBusy); + } + if !staging.upload { + return Err(VulkanError::StagingBufferNoUpload); + } if self.size > client_mem.len() as u64 { return Err(VulkanError::InvalidBufferSize); } data.busy.set(true); data.data_copied.set(false); + staging.busy.set(true); + data.staging.set(Some(staging.clone())); if img.contents_are_undefined.get() { damage = Region::new2(Rect::new_sized(0, 0, img.width as _, img.height as _).unwrap()); } @@ -416,27 +427,22 @@ impl VulkanShmImage { self.async_release_from_gfx_queue(img, data)?; - if let Some(staging) = data.staging.get() { + if let Some(staging) = staging.staging.get() { return self.async_upload_initiate_copy(img, data, &staging, copies, client_mem); } let img2 = img.clone(); let client_mem = client_mem.clone(); - img.renderer.device.create_shm_staging( - &img.renderer, - &data.cpu, - self.size, - true, - false, - move |res| { + img.renderer + .device + .fill_staging_shell(&img.renderer, &data.cpu, staging, move |res| { let VulkanImageMemory::Internal(shm) = &img2.ty else { unreachable!(); }; if let Err(e) = shm.async_upload_after_allocation(&img2, &client_mem, res) { shm.async_data.as_ref().unwrap().complete(Err(e)); } - }, - ) + }) } fn async_release_from_gfx_queue( @@ -532,11 +538,10 @@ impl VulkanShmImage { &self, img: &Rc, client_mem: &Rc, - res: Result, + res: Result, VulkanError>, ) -> Result<(), VulkanError> { - let staging = Rc::new(res?); + let staging = res?; let data = self.async_data.as_ref().unwrap(); - data.staging.set(Some(staging.clone())); let copies = &*data.regions.borrow(); self.async_upload_initiate_copy(img, data, &staging, copies, client_mem) } @@ -637,7 +642,7 @@ impl VulkanShmImage { } img.renderer.check_defunct()?; let regions = &*data.regions.borrow(); - let staging = data.staging.get().unwrap(); + let staging = data.staging.get().unwrap().staging.get().unwrap(); staging.upload(|_, _| ())?; let Some((cmd, fence, sync_file, point)) = self.submit_buffer_to_image_copy(img, &staging, regions, true)? diff --git a/src/gfx_apis/vulkan/staging.rs b/src/gfx_apis/vulkan/staging.rs index 5a3faeac..d8fa685b 100644 --- a/src/gfx_apis/vulkan/staging.rs +++ b/src/gfx_apis/vulkan/staging.rs @@ -1,17 +1,24 @@ use { crate::{ cpu_worker::CpuWorker, + gfx_api::GfxStagingBuffer, gfx_apis::vulkan::{ allocator::{VulkanAllocation, VulkanAllocator}, device::VulkanDevice, renderer::VulkanRenderer, VulkanError, }, - utils::on_drop::{OnDrop, OnDrop2}, + utils::{ + clonecell::CloneCell, + on_drop::{OnDrop, OnDrop2}, + }, + }, + ash::{ + vk::{Buffer, BufferCreateInfo, BufferUsageFlags, MappedMemoryRange}, + Device, }, - ash::vk::{Buffer, BufferCreateInfo, BufferUsageFlags, MappedMemoryRange}, gpu_alloc::UsageFlags, - std::rc::Rc, + std::{any::Any, cell::Cell, rc::Rc}, }; pub struct VulkanStagingBuffer { @@ -22,6 +29,22 @@ pub struct VulkanStagingBuffer { } impl VulkanDevice { + pub(super) fn create_staging_shell( + self: &Rc, + size: u64, + upload: bool, + download: bool, + ) -> Rc { + Rc::new(VulkanStagingShell { + device: self.clone(), + staging: Default::default(), + size, + download, + upload, + busy: Cell::new(false), + }) + } + pub(super) fn create_staging_buffer( self: &Rc, allocator: &Rc, @@ -51,17 +74,15 @@ impl VulkanDevice { }) } - pub(super) fn create_shm_staging( + pub(super) fn fill_staging_shell( self: &Rc, renderer: &Rc, cpu: &Rc, - size: u64, - upload: bool, - download: bool, - cb: impl FnOnce(Result) + 'static, + shell: Rc, + cb: impl FnOnce(Result, VulkanError>) + 'static, ) -> Result<(), VulkanError> { - let (vk_usage, usage) = get_usage(upload, download, false); - let buffer = self.create_buffer(size, vk_usage)?; + let (vk_usage, usage) = get_usage(shell.upload, shell.download, false); + let buffer = self.create_buffer(shell.size, vk_usage)?; let memory_requirements = unsafe { self.device.get_buffer_memory_requirements(buffer) }; let slf = self.clone(); let destroy_buffer = @@ -77,12 +98,14 @@ impl VulkanDevice { res.map_err(VulkanError::BindBufferMemory)?; } destroy_buffer.forget(); - Ok(VulkanStagingBuffer { + let buffer = Rc::new(VulkanStagingBuffer { device: slf.clone(), allocation, buffer, - size, - }) + size: shell.size, + }); + shell.staging.set(Some(buffer.clone())); + Ok(buffer) }; renderer.shm_allocator.async_alloc( renderer, @@ -159,3 +182,43 @@ impl Drop for VulkanStagingBuffer { } } } + +pub(super) struct VulkanStagingShell { + pub(super) device: Rc, + pub(super) staging: CloneCell>>, + pub(super) size: u64, + pub(super) download: bool, + pub(super) upload: bool, + pub(super) busy: Cell, +} + +impl GfxStagingBuffer for VulkanStagingShell { + fn size(&self) -> usize { + self.size as _ + } + + fn into_any(self: Rc) -> Rc { + self + } +} + +impl VulkanStagingShell { + fn assert_device(&self, device: &Device) { + assert_eq!( + self.device.device.handle(), + device.handle(), + "Mixed vulkan device use" + ); + } +} + +impl dyn GfxStagingBuffer { + pub(super) fn into_vk(self: Rc, device: &Device) -> Rc { + let shell: Rc = self + .into_any() + .downcast() + .expect("Non-vulkan staging buffer passed into vulkan"); + shell.assert_device(device); + shell + } +} diff --git a/src/ifs/wl_buffer.rs b/src/ifs/wl_buffer.rs index 3068d572..d60de1c9 100644 --- a/src/ifs/wl_buffer.rs +++ b/src/ifs/wl_buffer.rs @@ -175,6 +175,7 @@ impl WlBuffer { WlBufferStorage::Shm { .. } => { return match surface { Some(s) => { + s.shm_staging.take(); s.shm_textures.back().tex.take(); s.shm_textures.front().tex.take().is_some() } diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 61df4dd0..f8559cb2 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -23,8 +23,8 @@ use { drm_feedback::DrmFeedback, fixed::Fixed, gfx_api::{ - AsyncShmGfxTexture, BufferResv, BufferResvUser, GfxError, ReleaseSync, SampleRect, - SyncFile, + AsyncShmGfxTexture, BufferResv, BufferResvUser, GfxError, GfxStagingBuffer, + ReleaseSync, SampleRect, SyncFile, }, ifs::{ wl_buffer::WlBuffer, @@ -276,6 +276,7 @@ pub struct WlSurface { pub buffer_abs_pos: Cell, pub need_extents_update: Cell, pub buffer: CloneCell>>, + pub shm_staging: CloneCell>>, pub shm_textures: DoubleBuffered, pub buf_x: NumCell, pub buf_y: NumCell, @@ -593,6 +594,7 @@ impl WlSurface { buffer_abs_pos: Cell::new(Default::default()), need_extents_update: Default::default(), buffer: Default::default(), + shm_staging: Default::default(), shm_textures: DoubleBuffered::new(DamageQueue::new().map(|damage| SurfaceShmTexture { tex: Default::default(), damage, @@ -1327,6 +1329,7 @@ impl WlSurface { } pub fn reset_shm_textures(&self) { + self.shm_staging.take(); for tex in &*self.shm_textures { tex.tex.take(); tex.damage.clear(); diff --git a/src/ifs/wl_surface/commit_timeline.rs b/src/ifs/wl_surface/commit_timeline.rs index 544d98a6..a973ddbb 100644 --- a/src/ifs/wl_surface/commit_timeline.rs +++ b/src/ifs/wl_surface/commit_timeline.rs @@ -1,6 +1,6 @@ use { crate::{ - gfx_api::{AsyncShmGfxTextureCallback, GfxError, PendingShmUpload}, + gfx_api::{AsyncShmGfxTextureCallback, GfxError, PendingShmUpload, STAGING_UPLOAD}, ifs::{ wl_buffer::WlBufferStorage, wl_surface::{PendingState, WlSurface, WlSurfaceError}, @@ -459,8 +459,28 @@ fn schedule_async_upload( back_tex } }; + let mut staging_opt = surface.shm_staging.get(); + if let Some(staging) = &staging_opt { + if staging.size() != back_tex.staging_size() { + staging_opt = None; + } + } + let staging = match staging_opt { + Some(s) => s, + None => { + let s = surface + .client + .state + .render_ctx + .get() + .ok_or(WlSurfaceError::NoRenderContext)? + .create_staging_buffer(back_tex.staging_size(), STAGING_UPLOAD); + surface.shm_staging.set(Some(s.clone())); + s + } + }; back_tex - .async_upload(node_ref.clone(), mem.clone(), back.damage.get()) + .async_upload(&staging, node_ref.clone(), mem.clone(), back.damage.get()) .map_err(WlSurfaceError::PrepareAsyncUpload) } diff --git a/src/it/test_gfx_api.rs b/src/it/test_gfx_api.rs index 95c52864..6ba86de9 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, - GfxTexture, GfxWriteModifier, PendingShmUpload, ReleaseSync, ResetStatus, - ShmGfxTexture, ShmMemory, SyncFile, + GfxStagingBuffer, GfxTexture, GfxWriteModifier, PendingShmUpload, ReleaseSync, + ResetStatus, ShmGfxTexture, ShmMemory, SyncFile, }, rect::{Rect, Region}, theme::Color, @@ -335,6 +335,7 @@ impl ShmGfxTexture for TestGfxImage { impl AsyncShmGfxTexture for TestGfxImage { fn async_upload( self: Rc, + _staging: &Rc, _callback: Rc, mem: Rc, _damage: Region, diff --git a/src/text.rs b/src/text.rs index 7eed4e21..a097a511 100644 --- a/src/text.rs +++ b/src/text.rs @@ -3,8 +3,8 @@ use { cpu_worker::{AsyncCpuWork, CpuJob, CpuWork, CpuWorker, PendingJob}, format::ARGB8888, gfx_api::{ - AsyncShmGfxTexture, AsyncShmGfxTextureCallback, GfxContext, GfxError, GfxTexture, - PendingShmUpload, + AsyncShmGfxTexture, AsyncShmGfxTextureCallback, GfxContext, GfxError, GfxStagingBuffer, + GfxTexture, PendingShmUpload, STAGING_UPLOAD, }, pango::{ consts::{ @@ -302,6 +302,7 @@ impl Drop for TextTexture { struct Shared { cpu_worker: Rc, ctx: Rc, + staging: CloneCell>>, textures: DoubleBuffered, pending_render: Cell>, pending_upload: Cell>, @@ -367,6 +368,7 @@ impl TextTexture { let data = Rc::new(Shared { cpu_worker: cpu_worker.clone(), ctx: ctx.clone(), + staging: Default::default(), textures: Default::default(), pending_render: Default::default(), pending_upload: Default::default(), @@ -528,9 +530,22 @@ impl CpuJob for RenderJob { } } }; + let mut staging_opt = data.staging.take(); + if let Some(staging) = &staging_opt { + if staging.size() != tex.staging_size() { + staging_opt = None; + } + } + let staging = match staging_opt { + Some(s) => s, + None => data + .ctx + .create_staging_buffer(tex.staging_size(), STAGING_UPLOAD), + }; let pending = tex .clone() .async_upload( + &staging, data.clone(), Rc::new(rt.data), Region::new2(Rect::new_sized_unchecked(0, 0, rt.width, rt.height)), @@ -538,6 +553,7 @@ impl CpuJob for RenderJob { .map_err(TextError::Upload); if pending.is_ok() { data.textures.back().tex.set(Some(tex)); + data.staging.set(Some(staging)); } match pending { Ok(Some(p)) => data.pending_upload.set(Some(p)),