use crate::backend::DrmDeviceId;
use crate::client::Client;
use crate::clientmem::ClientMem;
use crate::clientmem::ClientMemError;
use crate::clientmem::ClientMemOffset;
use crate::copy_device::CopyDevice;
use crate::copy_device::CopyDeviceError;
use crate::copy_device::CopyDeviceSrcObject;
use crate::format::ARGB8888;
use crate::format::Format;
use crate::gfx_api::GfxBuffer;
use crate::gfx_api::GfxContext;
use crate::gfx_api::GfxError;
use crate::gfx_api::GfxFramebuffer;
use crate::gfx_api::GfxTexture;
use crate::ifs::wl_surface::WlSurface;
use crate::ifs::wl_surface::prime::PrimeError;
use crate::leaks::Tracker;
use crate::object::ObjectDebugfs;
use crate::object::Version;
use crate::rect::Rect;
use crate::rect::Region;
use crate::state::DrmDevData;
use crate::state::GfxCtxChangedListener;
use crate::utils::errorfmt::ErrorFmt;
use crate::utils::event_listener::EventListener;
use crate::utils::fuse::fuse_inode::FuseInodeWithKey;
use crate::utils::liveness::Liveness;
use crate::utils::page_size::page_size;
use crate::utils::rc_eq::rc_opt_eq;
use crate::video::LINEAR_MODIFIER;
use crate::video::dmabuf::DmaBuf;
use crate::video::dmabuf::DmaBufPlane;
use crate::video::dmabuf::PlaneVec;
use crate::wire::WlBufferId;
use crate::wire::wl_buffer::*;
use jay_proc::GetLiveness;
use jay_proc::Object;
use std::cell::Cell;
use std::cell::RefCell;
use std::rc::Rc;
use std::slice;
use thiserror::Error;
use uapi::OwnedFd;
use uapi::c;
mod wl_buffer_dfs_g_fuse;
#[derive(Clone)]
pub enum WlBufferStorage {
Shm {
mem: Rc<ClientMemOffset>,
stride: i32,
dmabuf_buffer_params: DmabufBufferParams,
},
Dmabuf(WlBufferDmabufStorage),
}
#[derive(Clone)]
pub struct WlBufferDmabufStorage {
pub dmabuf: Rc<DmaBuf>,
pub tex: Option<Rc<dyn GfxTexture>>,
pub fb: Option<Rc<dyn GfxFramebuffer>>,
copy_obj: Option<Option<Rc<CopyDeviceSrcObject>>>,
}
#[derive(Clone)]
pub struct DmabufBufferParams {
size: usize,
udmabuf: Option<Rc<OwnedFd>>,
udmabuf_offset: usize,
udmabuf_size: usize,
udmabuf_impossible: bool,
host_buffer: Option<Rc<dyn GfxBuffer>>,
host_buffer_impossible: bool,
tex: Option<Rc<dyn GfxTexture>>,
tex_impossible: bool,
}
pub struct AttachedBuffer {
pub send_release: bool,
pub buf: Rc<WlBuffer>,
}
impl Drop for AttachedBuffer {
fn drop(&mut self) {
if self.send_release && !self.buf.destroyed() {
self.buf.send_release();
}
}
}
#[derive(Copy, Clone, PartialEq)]
enum Ty {
Shm,
DmaBuf,
Spb,
}
#[derive(Object, GetLiveness)]
#[debugfs]
pub struct WlBuffer {
pub id: WlBufferId,
destroyed: Cell<bool>,
version: Version,
pub client: Rc<Client>,
pub rect: Rect,
pub format: &'static Format,
pub client_dmabuf: Option<Rc<DmaBuf>>,
pub client_dmabuf_device: Option<Rc<DrmDevData>>,
pub exclusive_device: Option<DrmDeviceId>,
render_ctx_version: Cell<u32>,
pub storage: RefCell<Option<WlBufferStorage>>,
ty: Ty,
pub color: Option<[u32; 4]>,
pub width: i32,
pub height: i32,
_gfx_ctx_changed: EventListener<dyn GfxCtxChangedListener>,
pub tracker: Tracker<Self>,
pub had_buffer_texture: Cell<bool>,
liveness: Liveness,
}
pub struct SyntheticWlBuffer {
pub id: WlBufferId,
client: Rc<Client>,
}
impl WlBuffer {
pub fn destroyed(&self) -> bool {
self.destroyed.get()
}
pub fn is_shm(&self) -> bool {
self.ty == Ty::Shm
}
pub fn is_dmabuf(&self) -> bool {
self.ty == Ty::DmaBuf
}
fn new(
id: WlBufferId,
client: &Rc<Client>,
format: &'static Format,
width: i32,
height: i32,
client_dmabuf: Option<Rc<DmaBuf>>,
client_dmabuf_device: Option<Rc<DrmDevData>>,
exclusive_device: Option<DrmDeviceId>,
storage: Option<WlBufferStorage>,
ty: Ty,
color: Option<[u32; 4]>,
) -> Rc<Self> {
Rc::<Self>::new_cyclic(|slf| Self {
id,
destroyed: Cell::new(false),
version: Version(1),
client: client.clone(),
rect: Rect::new_sized_saturating(0, 0, width, height),
format,
width,
height,
client_dmabuf,
client_dmabuf_device,
exclusive_device,
render_ctx_version: Cell::new(client.state.render_ctx_version.get()),
storage: RefCell::new(storage),
ty,
tracker: Default::default(),
color,
_gfx_ctx_changed: EventListener::attached(slf.clone(), &client.state.gfx_ctx_changed),
had_buffer_texture: Default::default(),
liveness: Default::default(),
})
}
pub fn new_dmabuf(
id: WlBufferId,
client: &Rc<Client>,
format: &'static Format,
client_dmabuf: Rc<DmaBuf>,
hint_dev_t: Option<c::dev_t>,
) -> Rc<Self> {
let hint_dev = hint_dev_t.and_then(|d| client.state.drm_devs_by_dev_t.get(&d));
let device = client
.state
.find_dmabuf_device(&client_dmabuf, hint_dev.as_ref());
let mut exclusive_device = None;
if let Some(dev) = &hint_dev
&& rc_opt_eq(&hint_dev, &device)
{
exclusive_device = Some(dev.id);
}
Self::new(
id,
client,
format,
client_dmabuf.width,
client_dmabuf.height,
Some(client_dmabuf.clone()),
device,
exclusive_device,
Some(WlBufferStorage::Dmabuf(WlBufferDmabufStorage {
dmabuf: client_dmabuf,
tex: None,
fb: None,
copy_obj: None,
})),
Ty::DmaBuf,
None,
)
}
#[allow(clippy::too_many_arguments)]
pub fn new_shm(
id: WlBufferId,
client: &Rc<Client>,
offset: usize,
width: i32,
height: i32,
client_dmabuf: Option<Rc<DmaBuf>>,
stride: i32,
format: &'static Format,
mem: &Rc<ClientMem>,
udmabuf: Option<(&Rc<OwnedFd>, usize)>,
) -> Result<Rc<Self>, WlBufferError> {
let bytes = stride as u64 * height as u64;
let required = bytes + offset as u64;
if required > mem.len() as u64 {
return Err(WlBufferError::OutOfBounds);
}
let size = bytes as usize;
let mem = Rc::new(mem.offset(offset, size));
let min_row_size = width as u64 * format.bpp as u64;
if (stride as u64) < min_row_size {
return Err(WlBufferError::StrideTooSmall);
}
let udmabuf_impossible = !mem.pool().is_sealed_memfd();
let dmabuf_buffer_params = match udmabuf {
None => DmabufBufferParams {
size,
udmabuf: None,
udmabuf_offset: 0,
udmabuf_size: 0,
udmabuf_impossible,
host_buffer: None,
host_buffer_impossible: udmabuf_impossible,
tex: None,
tex_impossible: udmabuf_impossible,
},
Some((udmabuf, size)) => DmabufBufferParams {
size,
udmabuf: Some(udmabuf.clone()),
udmabuf_offset: offset,
udmabuf_size: size,
udmabuf_impossible: false,
host_buffer: None,
host_buffer_impossible: false,
tex: None,
tex_impossible: false,
},
};
Ok(Self::new(
id,
client,
format,
width,
height,
client_dmabuf,
None,
None,
Some(WlBufferStorage::Shm {
dmabuf_buffer_params,
mem,
stride,
}),
Ty::Shm,
None,
))
}
pub fn new_single_pixel(
id: WlBufferId,
client: &Rc<Client>,
r: u32,
g: u32,
b: u32,
a: u32,
) -> Rc<Self> {
Self::new(
id,
client,
ARGB8888,
1,
1,
None,
None,
None,
None,
Ty::Spb,
Some([r, g, b, a]),
)
}
pub fn handle_gfx_context_change_impl(&self) {
let ctx_version = self.client.state.render_ctx_version.get();
let up_to_date = self.render_ctx_version.replace(ctx_version) == ctx_version;
if up_to_date {
return;
}
let had_buffer_texture = self.handle_gfx_context_change_impl_();
self.had_buffer_texture.set(had_buffer_texture);
}
#[must_use]
fn handle_gfx_context_change_impl_(&self) -> bool {
let mut storage = self.storage.borrow_mut();
let Some(s) = &mut *storage else {
return false;
};
match s {
WlBufferStorage::Shm {
dmabuf_buffer_params:
DmabufBufferParams {
udmabuf_impossible,
host_buffer,
host_buffer_impossible,
tex,
tex_impossible,
..
},
..
} => {
host_buffer.take();
*host_buffer_impossible = *udmabuf_impossible;
let had_texture = tex.take().is_some();
*tex_impossible = *udmabuf_impossible;
return had_texture;
}
WlBufferStorage::Dmabuf(storage) => {
let had_texture = storage.tex.is_some();
storage.tex = None;
storage.fb = None;
storage.copy_obj = None;
had_texture
}
}
}
pub fn get_texture(&self, surface: &WlSurface) -> Option<Rc<dyn GfxTexture>> {
match &*self.storage.borrow() {
None => None,
Some(s) => match s {
WlBufferStorage::Shm {
dmabuf_buffer_params,
..
} => {
if let Some(tex) = &dmabuf_buffer_params.tex {
return Some(tex.clone());
}
surface.shm_textures.front().tex.get().map(|t| t as _)
}
WlBufferStorage::Dmabuf(storage) => storage.tex.clone(),
},
}
}
pub fn update_texture_or_log(&self, surface: &WlSurface, sync_shm: bool) {
if let Err(e) = self.update_texture(surface, sync_shm) {
log::warn!("Could not update texture: {}", ErrorFmt(e));
}
}
fn get_udmabuf(
&self,
mem: &Rc<ClientMemOffset>,
dmabuf_buffer_params: &mut DmabufBufferParams,
) -> Option<Rc<OwnedFd>> {
let DmabufBufferParams {
size,
udmabuf,
udmabuf_offset,
udmabuf_size,
udmabuf_impossible,
..
} = dmabuf_buffer_params;
if let Some(b) = udmabuf {
return Some(b.clone());
}
if *udmabuf_impossible {
return None;
}
let dev = self.client.state.udmabuf.get()?;
let mask = page_size() - 1;
let offset = mem.offset() & mask;
let base = mem.offset() & !mask;
let end = (mem.offset() + *size + mask) & !mask;
let len = end - base;
match dev.create_dmabuf_from_memfd(mem.pool().fd(), base, len) {
Ok(b) => {
let b = Rc::new(b);
*udmabuf_offset = offset;
*udmabuf_size = len;
*udmabuf = Some(b.clone());
Some(b)
}
Err(e) => {
*udmabuf_impossible = true;
log::debug!("Could not create udmabuf: {}", ErrorFmt(e));
None
}
}
}
pub fn get_gfx_buffer(
&self,
ctx: &Rc<dyn GfxContext>,
mem: &Rc<ClientMemOffset>,
dmabuf_buffer_params: &mut DmabufBufferParams,
) -> Option<Rc<dyn GfxBuffer>> {
let DmabufBufferParams {
host_buffer,
host_buffer_impossible,
..
} = dmabuf_buffer_params;
if let Some(hb) = host_buffer {
return Some(hb.clone());
}
if *host_buffer_impossible {
return None;
}
let udmabuf = self.get_udmabuf(mem, dmabuf_buffer_params)?;
let DmabufBufferParams {
udmabuf_offset,
udmabuf_size,
host_buffer,
host_buffer_impossible,
..
} = dmabuf_buffer_params;
let hb =
match ctx.create_dmabuf_buffer(&udmabuf, *udmabuf_offset, *udmabuf_size, self.format) {
Ok(hb) => hb,
Err(e) => {
*host_buffer_impossible = true;
log::debug!("Could not create gfx host buffer: {}", ErrorFmt(e));
return None;
}
};
*host_buffer = Some(hb.clone());
Some(hb)
}
pub fn import_udmabuf_texture(
&self,
ctx: &Rc<dyn GfxContext>,
mem: &Rc<ClientMemOffset>,
stride: i32,
dmabuf_buffer_params: &mut DmabufBufferParams,
) -> Option<Rc<dyn GfxTexture>> {
let DmabufBufferParams {
tex,
tex_impossible,
..
} = dmabuf_buffer_params;
if tex.is_some() {
return tex.clone();
}
if *tex_impossible {
return None;
}
let udmabuf = self.get_udmabuf(mem, dmabuf_buffer_params)?;
let DmabufBufferParams {
udmabuf_offset,
tex,
tex_impossible,
..
} = dmabuf_buffer_params;
let mut planes = PlaneVec::new();
planes.push(DmaBufPlane {
offset: *udmabuf_offset as _,
stride: stride as _,
fd: udmabuf,
});
let dmabuf = DmaBuf::new(
&self.client.state.dma_buf_ids,
self.width,
self.height,
self.format,
LINEAR_MODIFIER,
planes,
);
let tex_ = match ctx.clone().dmabuf_tex(&dmabuf) {
Ok(i) => i,
Err(e) => {
*tex_impossible = true;
log::debug!("Could not import udmabuf as GfxTexture: {}", ErrorFmt(e));
return None;
}
};
*tex = Some(tex_.clone());
log::debug!("Using zero-copy wl_shm path");
Some(tex_)
}
fn update_texture(&self, surface: &WlSurface, sync_copies: bool) -> Result<(), WlBufferError> {
let storage = &mut *self.storage.borrow_mut();
let Some(storage) = storage else {
return Ok(());
};
let state = &self.client.state;
let Some(ctx) = state.render_ctx.get() else {
return Ok(());
};
match storage {
WlBufferStorage::Shm {
mem,
stride,
dmabuf_buffer_params,
..
} => {
if !sync_copies {
return Ok(());
}
if ctx.fast_ram_access()
&& self
.import_udmabuf_texture(&ctx, mem, *stride, dmabuf_buffer_params)
.is_some()
{
return Ok(());
}
let tex = ctx.async_shmem_texture(
self.format,
self.width,
self.height,
*stride,
&state.cpu_worker,
)?;
mem.access(|mem| tex.clone().sync_upload(mem, Region::new(self.rect)))??;
surface.shm_textures.front().tex.set(Some(tex));
surface.shm_textures.front().damage.clear();
}
WlBufferStorage::Dmabuf(storage) => {
if sync_copies {
let copies = surface
.prepare_prime_copies(
&ctx,
state.render_ctx_prime_copy_device.get().as_ref(),
self,
storage,
slice::from_ref(&self.rect),
false,
)
.map_err(WlBufferError::PreparePrimeCopy)?;
if let Some((copies, psb)) = copies {
let mut syncs = psb.take_sync();
while syncs.len() > 1
&& let Some(sync) = syncs.pop()
{
sync.signaled_blocking("prime");
}
for copy in &copies {
let sync = copy
.execute(syncs.last(), None)
.map_err(WlBufferError::PerformPrimeCopy)?;
syncs.extend(sync);
}
while let Some(sync) = syncs.pop() {
sync.signaled_blocking("prime");
}
psb.clear_damage();
surface.prime.buffer.set(Some(psb));
}
}
if surface.prime.buffer.is_none() {
storage.ensure_tex(self, &ctx)?;
}
}
}
Ok(())
}
pub fn update_framebuffer(&self) -> Result<(), WlBufferError> {
let storage = &mut *self.storage.borrow_mut();
let Some(storage) = storage else {
return Ok(());
};
match storage {
WlBufferStorage::Shm { .. } => {
}
WlBufferStorage::Dmabuf(storage) => {
if let Some(ctx) = self.client.state.render_ctx.get() {
storage.ensure_fb(self, &ctx)?;
}
}
}
Ok(())
}
fn send_release(&self) {
self.client.event(Release { self_id: self.id });
}
pub fn client_copy_device(&self) -> Option<&Rc<CopyDevice>> {
if let Some(dev) = &self.client_dmabuf_device
&& let Some(dev) = &dev.copy_device
{
return Some(dev);
}
None
}
fn check_import(&self, target: Option<DrmDeviceId>) -> Result<(), WlBufferError> {
if self.exclusive_device.is_some() && self.exclusive_device != target {
return Err(WlBufferError::CrossDeviceImportDenied);
}
Ok(())
}
pub fn clone_synthetic(&self) -> SyntheticWlBuffer {
let id = self.client.new_synthetic_id();
let slf = Rc::<Self>::new_cyclic(|slf| Self {
id,
destroyed: Default::default(),
version: self.version,
client: self.client.clone(),
rect: self.rect,
format: self.format,
client_dmabuf: self.client_dmabuf.clone(),
client_dmabuf_device: self.client_dmabuf_device.clone(),
exclusive_device: self.exclusive_device,
render_ctx_version: self.render_ctx_version.clone(),
storage: self.storage.clone(),
ty: self.ty,
color: self.color,
width: self.width,
height: self.height,
_gfx_ctx_changed: EventListener::attached(
slf.clone(),
&self.client.state.gfx_ctx_changed,
),
tracker: Default::default(),
had_buffer_texture: self.had_buffer_texture.clone(),
liveness: Default::default(),
});
track!(self.client, slf);
slf.client.add_server_obj(&slf);
SyntheticWlBuffer {
id,
client: self.client.clone(),
}
}
}
impl Drop for SyntheticWlBuffer {
fn drop(&mut self) {
self.client.request(Destroy { self_id: self.id });
}
}
impl WlBufferDmabufStorage {
pub fn ensure_tex(
&mut self,
buf: &WlBuffer,
ctx: &Rc<dyn GfxContext>,
) -> Result<Rc<dyn GfxTexture>, WlBufferError> {
if let Some(tex) = &self.tex {
return Ok(tex.clone());
}
buf.check_import(ctx.drm_device_id())?;
let tex = ctx.clone().dmabuf_tex(&self.dmabuf)?;
Ok(self.tex.insert(tex).clone())
}
fn ensure_fb(
&mut self,
buf: &WlBuffer,
ctx: &Rc<dyn GfxContext>,
) -> Result<Rc<dyn GfxFramebuffer>, WlBufferError> {
if let Some(fb) = &self.fb {
return Ok(fb.clone());
}
buf.check_import(ctx.drm_device_id())?;
let fb = ctx.clone().dmabuf_fb(&self.dmabuf)?;
Ok(self.fb.insert(fb).clone())
}
pub fn ensure_copy_object(
&mut self,
buf: &WlBuffer,
render_dev: Option<&Rc<CopyDevice>>,
) -> Result<Option<Rc<CopyDeviceSrcObject>>, CopyDeviceError> {
if let Some(obj) = &self.copy_obj {
return Ok(obj.clone());
}
let client_dev = buf.client_copy_device();
let client_dev_id = client_dev.map(|d| d.drm_device_id());
let render_dev_id = render_dev.map(|d| d.drm_device_id());
if client_dev_id == render_dev_id {
return Ok(self.copy_obj.insert(None).clone());
}
let dev = if let Some(dev) = client_dev {
dev
} else if let Some(dev) = render_dev {
dev
} else {
return Ok(self.copy_obj.insert(None).clone());
};
if buf.exclusive_device.is_some() && buf.exclusive_device != Some(dev.drm_device_id()) {
return Err(CopyDeviceError::CrossDeviceImportDenied);
}
let obj = dev.create_src_object(&self.dmabuf)?;
Ok(self.copy_obj.insert(Some(Rc::new(obj))).clone())
}
}
impl ObjectDebugfs for WlBuffer {
fn object_debugfs(self: Rc<Self>, _client: &Rc<Client>) -> FuseInodeWithKey {
self.debugfs()
}
}
impl GfxCtxChangedListener for WlBuffer {
fn handle_gfx_context_change(self: Rc<Self>) {
self.handle_gfx_context_change_impl();
}
}
impl WlBufferRequestHandler for WlBuffer {
type Error = WlBufferError;
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.client.remove_obj(self);
self.destroyed.set(true);
Ok(())
}
}
#[derive(Debug, Error)]
pub enum WlBufferError {
#[error("The requested memory region is out of bounds for the pool")]
OutOfBounds,
#[error("The stride does not fit all pixels in a row")]
StrideTooSmall,
#[error("Could not access the client memory")]
ClientMemError(#[source] Box<ClientMemError>),
#[error("The graphics library could not import the client image")]
GfxError(#[from] GfxError),
#[error("Could not prepare prime copy")]
PreparePrimeCopy(#[source] PrimeError),
#[error("Could not perform a prime copy")]
PerformPrimeCopy(#[source] CopyDeviceError),
#[error("Cross-device import has been disabled due to set sampling device")]
CrossDeviceImportDenied,
}
efrom!(WlBufferError, ClientMemError);