diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 4245f813..1b0fa1f7 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -821,7 +821,7 @@ pub trait GfxContext: Debug { fn create_dmabuf_buffer( &self, - dmabuf: &Rc, + dmabuf: &OwnedFd, offset: usize, size: usize, ) -> Result, GfxError> { diff --git a/src/gfx_apis/vulkan.rs b/src/gfx_apis/vulkan.rs index 3b1114bb..1c512929 100644 --- a/src/gfx_apis/vulkan.rs +++ b/src/gfx_apis/vulkan.rs @@ -398,7 +398,7 @@ impl GfxContext for Context { fn create_dmabuf_buffer( &self, - dmabuf: &Rc, + dmabuf: &OwnedFd, offset: usize, size: usize, ) -> Result, GfxError> { diff --git a/src/gfx_apis/vulkan/dmabuf_buffer.rs b/src/gfx_apis/vulkan/dmabuf_buffer.rs index c2912151..fa13aaaf 100644 --- a/src/gfx_apis/vulkan/dmabuf_buffer.rs +++ b/src/gfx_apis/vulkan/dmabuf_buffer.rs @@ -27,7 +27,7 @@ pub struct VulkanDmabufBuffer { impl VulkanDevice { pub fn create_dmabuf_buffer( self: &Rc, - dmabuf: &Rc, + dmabuf: &OwnedFd, offset: u64, size: u64, ) -> Result, VulkanError> { diff --git a/src/ifs/wl_buffer.rs b/src/ifs/wl_buffer.rs index f7897269..39b590c1 100644 --- a/src/ifs/wl_buffer.rs +++ b/src/ifs/wl_buffer.rs @@ -287,7 +287,7 @@ impl WlBuffer { if *udmabuf_impossible { return Ok(None); } - let Some(dev) = self.client.state.udmabuf() else { + let Some(dev) = self.client.state.udmabuf.get() else { return Ok(None); }; let mask = page_size() - 1; diff --git a/src/pango.rs b/src/pango.rs index 889ed260..0b4ac9b7 100644 --- a/src/pango.rs +++ b/src/pango.rs @@ -7,7 +7,10 @@ use { }, std::{cell::Cell, ptr, rc::Rc}, thiserror::Error, - uapi::{IntoUstr, c}, + uapi::{ + IntoUstr, + c::{self, memset}, + }, }; pub mod consts; @@ -26,6 +29,13 @@ unsafe extern "C" { width: c::c_int, height: c::c_int, ) -> *mut cairo_surface_t; + fn cairo_image_surface_create_for_data( + data: *mut u8, + format: cairo_format_t, + width: c::c_int, + height: c::c_int, + stride: c::c_int, + ) -> *mut cairo_surface_t; fn cairo_image_surface_get_height(surface: *mut cairo_surface_t) -> c::c_int; fn cairo_image_surface_get_stride(surface: *mut cairo_surface_t) -> c::c_int; fn cairo_image_surface_get_data(surface: *mut cairo_surface_t) -> *mut u8; @@ -41,6 +51,8 @@ unsafe extern "C" { fn cairo_set_operator(cr: *mut cairo_t, op: cairo_operator_t); fn cairo_set_source_rgba(cr: *mut cairo_t, red: f64, green: f64, blue: f64, alpha: f64); fn cairo_move_to(cr: *mut cairo_t, x: f64, y: f64); + + fn cairo_format_stride_for_width(format: cairo_format_t, width: c::c_int) -> c::c_int; } #[repr(transparent)] @@ -141,6 +153,30 @@ impl CairoImageSurface { } } + pub unsafe fn new_image_surface_with_data( + format: CairoFormat, + data: *mut u8, + width: i32, + height: i32, + stride: i32, + ) -> Result, PangoError> { + unsafe { + memset(data.cast(), 0, (stride * height) as usize); + let s = cairo_image_surface_create_for_data( + data, + format.raw() as _, + width as _, + height as _, + stride as _, + ); + let status = cairo_surface_status(s); + if status != 0 { + return Err(PangoError::CreateSurface(status as _)); + } + Ok(Rc::new(Self { s })) + } + } + pub fn create_context(self: &Rc) -> Result, PangoError> { unsafe { let c = cairo_create(self.s); @@ -372,3 +408,13 @@ impl Drop for PangoLayout { } } } + +pub fn cairo_size(format: CairoFormat, width: i32, height: i32) -> Option<(i32, usize)> { + let stride = unsafe { cairo_format_stride_for_width(format.raw() as _, width as _) }; + if stride < 0 { + return None; + } + let stride = stride as i32; + let size = height.checked_mul(stride)? as usize; + Some((stride, size)) +} diff --git a/src/state.rs b/src/state.rs index 204995d3..935f073b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -99,7 +99,7 @@ use { TearingMode, ToplevelData, ToplevelNode, ToplevelNodeBase, VrrMode, WorkspaceNode, generic_node_visitor, }, - udmabuf::{Udmabuf, UdmabufError}, + udmabuf::UdmabufHolder, utils::{ activation_token::ActivationToken, asyncevent::AsyncEvent, @@ -112,7 +112,6 @@ use { hash_map_ext::HashMapExt, linkedlist::LinkedList, numcell::NumCell, - oserror::OsError, queue::AsyncQueue, refcounted::RefCounted, run_toplevel::RunToplevel, @@ -152,7 +151,7 @@ use { time::Duration, }, thiserror::Error, - uapi::{OwnedFd, c}, + uapi::OwnedFd, }; pub struct State { @@ -290,7 +289,7 @@ pub struct State { pub xdg_surface_configure_events: AsyncQueue, pub workspace_display_order: Cell, pub outputs_without_hc: NumCell, - pub udmabuf: CloneCell>>>, + pub udmabuf: Rc, } // impl Drop for State { @@ -1568,25 +1567,6 @@ impl State { found_tree.clear(); node } - - pub fn udmabuf(&self) -> Option> { - if let Some(u) = self.udmabuf.get() { - return u; - } - match Udmabuf::new() { - Ok(u) => { - let u = Rc::new(u); - self.udmabuf.set(Some(Some(u.clone()))); - Some(u) - } - Err(UdmabufError::Open(OsError(c::EPERM))) => None, - Err(e) => { - log::error!("Could not create udmabuf device: {}", ErrorFmt(e)); - self.udmabuf.set(Some(None)); - None - } - } - } } #[derive(Debug, Error)] diff --git a/src/text.rs b/src/text.rs index 70a8b38a..752e2913 100644 --- a/src/text.rs +++ b/src/text.rs @@ -4,20 +4,24 @@ use { cpu_worker::{AsyncCpuWork, CpuJob, CpuWork, CpuWorker, PendingJob}, format::ARGB8888, gfx_api::{ - AsyncShmGfxTexture, AsyncShmGfxTextureCallback, GfxContext, GfxError, GfxStagingBuffer, - GfxTexture, PendingShmTransfer, STAGING_UPLOAD, + AsyncShmGfxTexture, AsyncShmGfxTextureCallback, GfxBuffer, GfxContext, GfxError, + GfxStagingBuffer, GfxTexture, PendingShmTransfer, STAGING_UPLOAD, }, pango::{ CairoContext, CairoImageSurface, PangoCairoContext, PangoError, PangoFontDescription, - PangoLayout, + PangoLayout, cairo_size, consts::{ - CAIRO_FORMAT_ARGB32, CAIRO_OPERATOR_SOURCE, PANGO_ELLIPSIZE_END, PANGO_SCALE, + CAIRO_FORMAT_ARGB32, CAIRO_OPERATOR_SOURCE, CairoFormat, PANGO_ELLIPSIZE_END, + PANGO_SCALE, }, }, rect::{Rect, Region}, + state::State, theme::Color, + udmabuf::UdmabufHolder, utils::{ - clonecell::CloneCell, double_buffered::DoubleBuffered, on_drop_event::OnDropEvent, + clonecell::CloneCell, double_buffered::DoubleBuffered, errorfmt::ErrorFmt, + on_drop_event::OnDropEvent, oserror::OsError, page_size::page_size, }, }, std::{ @@ -25,10 +29,20 @@ use { cell::{Cell, RefCell}, mem, ops::Neg, + ptr, rc::{Rc, Weak}, - sync::Arc, + slice, + sync::{ + Arc, + atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering::Relaxed}, + }, }, thiserror::Error, + uapi::{ + OwnedFd, + c::{self, off_t}, + ftruncate, + }, }; #[derive(Debug, Error)] @@ -41,14 +55,18 @@ pub enum TextError { PangoContext(#[source] PangoError), #[error("Could not create a pango layout")] CreateLayout(#[source] PangoError), - #[error("Could not access the cairo image data")] - ImageData(#[source] PangoError), #[error("Texture upload failed")] Upload(#[source] GfxError), #[error("Could not create a texture")] CreateTexture(#[source] GfxError), #[error("Rendering is not scheduled or not yet completed")] NotScheduled, + #[error("The size calculation overflowed")] + SizeOverflow, + #[error("Could not resize the memfd")] + ResizeMemfd(#[source] OsError), + #[error("Could not map the memfd")] + MapMemfd(#[source] OsError), } impl<'a> Config<'a> { @@ -107,8 +125,22 @@ struct Data { layout: PangoLayout, } -fn create_data(font: &str, width: i32, height: i32, scale: Option) -> Result { - let image = match CairoImageSurface::new_image_surface(CAIRO_FORMAT_ARGB32, width, height) { +const FORMAT: CairoFormat = CAIRO_FORMAT_ARGB32; + +fn create_data( + memfd: &Memfd, + font: &str, + width: i32, + height: i32, + scale: Option, +) -> Result { + let Some((stride, size)) = cairo_size(FORMAT, width, height) else { + return Err(TextError::SizeOverflow); + }; + let data = memfd.get_pointer_for_size(size)?; + let image = match unsafe { + CairoImageSurface::new_image_surface_with_data(FORMAT, data, width, height, stride) + } { Ok(s) => s, Err(e) => return Err(TextError::CreateImage(e)), }; @@ -139,12 +171,13 @@ fn create_data(font: &str, width: i32, height: i32, scale: Option) -> Resul } fn measure( + memfd: &Memfd, font: &str, text: &str, markup: bool, scale: Option, ) -> Result { - let data = create_data(font, 1, 1, scale)?; + let data = create_data(memfd, font, 1, 1, scale)?; if markup { data.layout.set_markup(text); } else { @@ -156,6 +189,7 @@ fn measure( } fn render( + memfd: &Memfd, x: i32, y: Option, width: i32, @@ -173,10 +207,9 @@ fn render( width, height, stride: width * 4, - data: vec![], }); } - let data = create_data(font, width, height, scale)?; + let data = create_data(memfd, font, width, height, scale)?; if ellipsize { data.layout .set_width((width - 2 * padding).max(0) * PANGO_SCALE); @@ -199,11 +232,11 @@ fn render( width, height, stride: data.image.stride(), - data: data.image.data().map_err(TextError::ImageData)?.to_vec(), }) } fn render_fitting( + memfd: &Memfd, height: Option, font: &str, text: &str, @@ -211,7 +244,7 @@ fn render_fitting( markup: bool, scale: Option, ) -> Result { - let measurement = measure(font, text, markup, scale)?; + let measurement = measure(memfd, font, text, markup, scale)?; let x = measurement.ink_rect.x1().neg(); let y = match height { Some(_) => None, @@ -220,7 +253,7 @@ fn render_fitting( let width = measurement.ink_rect.width(); let height = height.unwrap_or(measurement.ink_rect.height()); render( - x, y, width, height, 0, font, text, color, false, markup, scale, + memfd, x, y, width, height, 0, font, text, color, false, markup, scale, ) } @@ -233,11 +266,10 @@ struct RenderedText { width: i32, height: i32, stride: i32, - data: Vec>, } -#[derive(Default)] struct RenderWork { + memfd: Arc, config: Config<'static>, result: Option>, } @@ -265,7 +297,7 @@ impl RenderWork { color, markup, scale, - } => render_fitting(height, font, text, color, markup, scale), + } => render_fitting(&self.memfd, height, font, text, color, markup, scale), Config::Render { x, y, @@ -279,7 +311,18 @@ impl RenderWork { markup, scale, } => render( - x, y, width, height, padding, font, text, color, ellipsize, markup, scale, + &self.memfd, + x, + y, + width, + height, + padding, + font, + text, + color, + ellipsize, + markup, + scale, ), } } @@ -303,6 +346,7 @@ impl Drop for TextTexture { struct Shared { cpu_worker: Rc, ctx: Rc, + udmabuf: Rc, staging: CloneCell>>, textures: DoubleBuffered, pending_render: Cell>, @@ -312,6 +356,15 @@ struct Shared { waiter: Cell>>, busy: Cell, flip_is_noop: Cell, + memfd: Arc, + gfx_buffer: CloneCell>>>, +} + +struct Memfd { + fd: OwnedFd, + size: AtomicUsize, + size_changed: AtomicBool, + mapping: AtomicPtr, } impl Shared { @@ -325,6 +378,36 @@ impl Shared { waiter.completed(); } } + + fn get_gfx_buffer(&self) -> Option> { + if self.memfd.size_changed.load(Relaxed) { + self.gfx_buffer.take(); + self.memfd.size_changed.store(false, Relaxed); + } + if let Some(res) = self.gfx_buffer.get() { + return res; + } + let size = self.memfd.size.load(Relaxed); + let udmabuf = self.udmabuf.get()?; + let res = 'res: { + let dmabuf = match udmabuf.create_dmabuf_from_memfd(&self.memfd.fd, 0, size) { + Ok(b) => b, + Err(e) => { + log::error!("Could not create udmabuf: {}", ErrorFmt(e)); + break 'res None; + } + }; + match self.ctx.create_dmabuf_buffer(&dmabuf, 0, size) { + Ok(b) => Some(b), + Err(e) => { + log::debug!("Could not create GfxBuffer: {}", ErrorFmt(e)); + None + } + } + }; + self.gfx_buffer.set(Some(res.clone())); + res + } } #[derive(PartialEq, Default)] @@ -365,10 +448,14 @@ pub trait OnCompleted { } impl TextTexture { - pub fn new(cpu_worker: &Rc, ctx: &Rc) -> Self { + pub fn new(state: &Rc, ctx: &Rc) -> Self { + let memfd = uapi::memfd_create("text", c::MFD_CLOEXEC | c::MFD_ALLOW_SEALING) + .expect("Could not create memfd"); + let _ = uapi::fcntl_add_seals(memfd.raw(), c::F_SEAL_SHRINK); let data = Rc::new(Shared { - cpu_worker: cpu_worker.clone(), + cpu_worker: state.cpu_worker.clone(), ctx: ctx.clone(), + udmabuf: state.udmabuf.clone(), staging: Default::default(), textures: Default::default(), pending_render: Default::default(), @@ -378,6 +465,13 @@ impl TextTexture { waiter: Default::default(), busy: Default::default(), flip_is_noop: Default::default(), + memfd: Arc::new(Memfd { + fd: memfd, + size: Default::default(), + size_changed: Default::default(), + mapping: Default::default(), + }), + gfx_buffer: Default::default(), }); Self { data } } @@ -403,13 +497,18 @@ impl TextTexture { } let mut job = self.data.render_job.take().unwrap_or_else(|| { Box::new(RenderJob { - work: Default::default(), + work: RenderWork { + memfd: self.data.memfd.clone(), + config: Default::default(), + result: Default::default(), + }, data: Rc::downgrade(&self.data), }) }); job.work = RenderWork { config: config.to_static(), result: None, + ..job.work }; let pending = self.data.cpu_worker.submit(job); self.data.pending_render.set(Some(pending)); @@ -527,29 +626,36 @@ impl CpuJob for RenderJob { } }; let mut staging_opt = data.staging.take(); - if let Some(staging) = &staging_opt - && 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 = if let Some(gfx_buffer) = data.get_gfx_buffer() { + tex.clone() + .async_upload_from_buffer( + &gfx_buffer, + data.clone(), + Region::new(Rect::new_sized_unchecked(0, 0, rt.width, rt.height)), + ) + .map_err(TextError::Upload) + } else { + if let Some(staging) = &staging_opt + && staging.size() != tex.staging_size() + { + staging_opt = None; + } + let staging = staging_opt.get_or_insert_with(|| { + data.ctx + .create_staging_buffer(tex.staging_size(), STAGING_UPLOAD) + }); + tex.clone() + .async_upload( + &staging, + data.clone(), + Rc::new(data.memfd.data(rt.stride, rt.height)), + Region::new(Rect::new_sized_unchecked(0, 0, rt.width, rt.height)), + ) + .map_err(TextError::Upload) }; - let pending = tex - .clone() - .async_upload( - &staging, - data.clone(), - Rc::new(rt.data), - Region::new(Rect::new_sized_unchecked(0, 0, rt.width, rt.height)), - ) - .map_err(TextError::Upload); if pending.is_ok() { data.textures.back().tex.set(Some(tex)); - data.staging.set(Some(staging)); + data.staging.set(staging_opt); } match pending { Ok(Some(p)) => data.pending_upload.set(Some(p)), @@ -571,3 +677,66 @@ impl OnCompleted for OnDropEvent { // nothing } } + +impl Memfd { + fn get_pointer_for_size(&self, size: usize) -> Result<*mut u8, TextError> { + let old_size = self.size.load(Relaxed); + if old_size >= size { + return Ok(self.mapping.load(Relaxed)); + } + let Some(size) = size.checked_next_multiple_of(page_size()) else { + return Err(TextError::SizeOverflow); + }; + let Ok(isize) = off_t::try_from(size) else { + return Err(TextError::SizeOverflow); + }; + if let Err(e) = ftruncate(self.fd.raw(), isize) { + return Err(TextError::ResizeMemfd(e.into())); + } + let old_ptr = self.mapping.load(Relaxed); + let new_ptr = if old_ptr.is_null() { + unsafe { + c::mmap( + ptr::null_mut(), + size, + c::PROT_READ | c::PROT_WRITE, + c::MAP_SHARED, + self.fd.raw(), + 0, + ) + } + } else { + unsafe { c::mremap(old_ptr.cast(), old_size, size, c::MREMAP_MAYMOVE) } + }; + if new_ptr == c::MAP_FAILED { + return Err(TextError::MapMemfd(OsError::default())); + } + let new_ptr = new_ptr.cast(); + self.mapping.store(new_ptr, Relaxed); + self.size.store(size, Relaxed); + self.size_changed.store(true, Relaxed); + Ok(new_ptr) + } + + fn data(&self, stride: i32, height: i32) -> Vec> { + let size = (stride * height) as usize; + assert!(size <= self.size.load(Relaxed)); + if size == 0 { + return vec![]; + } + let mapping = self.mapping.load(Relaxed); + unsafe { slice::from_raw_parts(mapping.cast(), size).to_vec() } + } +} + +impl Drop for Memfd { + fn drop(&mut self) { + let ptr = self.mapping.load(Relaxed); + if ptr.is_null() { + return; + } + unsafe { + c::munmap(ptr.cast(), self.size.load(Relaxed)); + } + } +} diff --git a/src/tree/container.rs b/src/tree/container.rs index f3c34548..a5472d23 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -725,8 +725,7 @@ impl ContainerNode { let title = child.title.borrow_mut(); let tt = &mut *child.title_tex.borrow_mut(); for (scale, _) in scales.iter() { - let tex = tt - .get_or_insert_with(*scale, || TextTexture::new(&self.state.cpu_worker, &ctx)); + let tex = tt.get_or_insert_with(*scale, || TextTexture::new(&self.state, &ctx)); let mut th = th; let mut scalef = None; let mut width = rect.width(); diff --git a/src/tree/float.rs b/src/tree/float.rs index 66d5956c..3f84a5ab 100644 --- a/src/tree/float.rs +++ b/src/tree/float.rs @@ -215,8 +215,7 @@ impl FloatNode { let tr = self.title_rect.get(); let tt = &mut *self.title_textures.borrow_mut(); for (scale, _) in scales.iter() { - let tex = - tt.get_or_insert_with(*scale, || TextTexture::new(&self.state.cpu_worker, &ctx)); + let tex = tt.get_or_insert_with(*scale, || TextTexture::new(&self.state, &ctx)); let mut th = tr.height(); let mut scalef = None; let mut width = tr.width(); diff --git a/src/tree/output.rs b/src/tree/output.rs index 96851473..e6920ef0 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -530,7 +530,7 @@ impl OutputNode { let active_id = self.workspace.get().map(|w| w.id); for ws in self.workspaces.iter() { let tex = &mut *ws.title_texture.borrow_mut(); - let tex = tex.get_or_insert_with(|| TextTexture::new(&self.state.cpu_worker, &ctx)); + let tex = tex.get_or_insert_with(|| TextTexture::new(&self.state, &ctx)); let tc = match active_id == Some(ws.id) { true => theme.colors.focused_title_text.get(), false => theme.colors.unfocused_title_text.get(), @@ -548,7 +548,7 @@ impl OutputNode { let mut rd = self.render_data.borrow_mut(); let tex = rd.status.get_or_insert_with(|| OutputStatus { tex_x: 0, - tex: TextTexture::new(&self.state.cpu_worker, &ctx), + tex: TextTexture::new(&self.state, &ctx), }); let status = self.status.get(); let tc = self.state.theme.colors.bar_text.get(); diff --git a/src/tree/placeholder.rs b/src/tree/placeholder.rs index d391b002..264cce3d 100644 --- a/src/tree/placeholder.rs +++ b/src/tree/placeholder.rs @@ -111,8 +111,7 @@ impl PlaceholderNode { let rect = self.toplevel.pos.get(); let mut textures = self.textures.borrow_mut(); for (scale, _) in scales.iter() { - let tex = textures - .get_or_insert_with(*scale, || TextTexture::new(&self.state.cpu_worker, &ctx)); + let tex = textures.get_or_insert_with(*scale, || TextTexture::new(&self.state, &ctx)); let mut width = rect.width(); let mut height = rect.height(); if *scale != 1 { diff --git a/src/udmabuf.rs b/src/udmabuf.rs index d2dc2487..a5a76409 100644 --- a/src/udmabuf.rs +++ b/src/udmabuf.rs @@ -2,7 +2,10 @@ use { crate::{ allocator::{Allocator, AllocatorError, BufferObject, BufferUsage, MappedBuffer}, format::Format, - utils::{compat::IoctlNumber, oserror::OsError, page_size::page_size}, + utils::{ + clonecell::CloneCell, compat::IoctlNumber, errorfmt::ErrorFmt, oserror::OsError, + page_size::page_size, + }, video::{ LINEAR_MODIFIER, Modifier, dmabuf::{DmaBuf, DmaBufIds, DmaBufPlane, PlaneVec}, @@ -51,6 +54,32 @@ pub enum UdmabufError { Map(#[source] OsError), } +#[derive(Default)] +pub struct UdmabufHolder { + udmabuf: CloneCell>>>, +} + +impl UdmabufHolder { + pub fn get(&self) -> Option> { + if let Some(u) = self.udmabuf.get() { + return u; + } + match Udmabuf::new() { + Ok(u) => { + let u = Rc::new(u); + self.udmabuf.set(Some(Some(u.clone()))); + Some(u) + } + Err(UdmabufError::Open(OsError(c::EPERM))) => None, + Err(e) => { + log::error!("Could not create udmabuf device: {}", ErrorFmt(e)); + self.udmabuf.set(Some(None)); + None + } + } + } +} + pub struct Udmabuf { fd: OwnedFd, }