1
0
Fork 0
forked from wry/wry

video: add udmabuf allocator

This commit is contained in:
Julian Orth 2024-09-01 20:23:04 +02:00
parent 2579834a60
commit 62cd29056a
33 changed files with 883 additions and 256 deletions

View file

@ -10,6 +10,7 @@ tasks:
sudo rmmod bochs
sudo modprobe vkms
sudo chmod o+rw /dev/dri/card*
sudo chmod o+r /dev/udmabuf
- build: |
cd jay
cargo build --features it

57
src/allocator.rs Normal file
View file

@ -0,0 +1,57 @@
use {
crate::{
format::Format,
video::{
dmabuf::{DmaBuf, DmaBufIds},
drm::Drm,
Modifier,
},
},
std::{error::Error, rc::Rc},
thiserror::Error,
};
#[derive(Debug, Error)]
#[error(transparent)]
pub struct AllocatorError(#[from] pub Box<dyn Error>);
bitflags! {
BufferUsage: u32;
BO_USE_SCANOUT = 1 << 0,
BO_USE_CURSOR = 1 << 1,
BO_USE_RENDERING = 1 << 2,
BO_USE_WRITE = 1 << 3,
BO_USE_LINEAR = 1 << 4,
BO_USE_PROTECTED = 1 << 5,
}
pub trait Allocator {
fn drm(&self) -> Option<&Drm>;
fn create_bo(
&self,
dma_buf_ids: &DmaBufIds,
width: i32,
height: i32,
format: &'static Format,
modifiers: &[Modifier],
usage: BufferUsage,
) -> Result<Rc<dyn BufferObject>, AllocatorError>;
fn import_dmabuf(
&self,
dmabuf: &DmaBuf,
usage: BufferUsage,
) -> Result<Rc<dyn BufferObject>, AllocatorError>;
}
pub trait BufferObject {
fn dmabuf(&self) -> &DmaBuf;
fn map_read(self: Rc<Self>) -> Result<Box<dyn MappedBuffer>, AllocatorError>;
fn map_write(self: Rc<Self>) -> Result<Box<dyn MappedBuffer>, AllocatorError>;
}
pub trait MappedBuffer {
unsafe fn data(&self) -> &[u8];
#[cfg_attr(not(test), allow(dead_code))]
fn data_ptr(&self) -> *mut u8;
fn stride(&self) -> i32;
}

View file

@ -1,5 +1,6 @@
use {
crate::{
allocator::BufferObject,
async_engine::{Phase, SpawnedFuture},
backend::{
BackendDrmDevice, BackendDrmLease, BackendDrmLessee, BackendEvent, Connector,
@ -73,6 +74,7 @@ pub struct PendingDrmDevice {
pub struct MetalRenderContext {
pub dev_id: DrmDeviceId,
pub gfx: Rc<dyn GfxContext>,
pub gbm: Rc<GbmDevice>,
}
pub struct MetalDrmDevice {
@ -91,7 +93,7 @@ pub struct MetalDrmDevice {
pub cursor_width: u64,
pub cursor_height: u64,
pub supports_async_commit: bool,
pub gbm: GbmDevice,
pub gbm: Rc<GbmDevice>,
pub handle_events: HandleEvents,
pub ctx: CloneCell<Rc<MetalRenderContext>>,
pub on_change: OnChange<crate::backend::DrmEvent>,
@ -2162,6 +2164,11 @@ impl MetalBackend {
}
}
let gbm = match GbmDevice::new(master) {
Ok(g) => Rc::new(g),
Err(e) => return Err(MetalError::GbmDevice(e)),
};
let gfx = match self.state.create_gfx_context(master, None) {
Ok(r) => r,
Err(e) => return Err(MetalError::CreateRenderContex(e)),
@ -2169,13 +2176,9 @@ impl MetalBackend {
let ctx = Rc::new(MetalRenderContext {
dev_id: pending.id,
gfx,
gbm: gbm.clone(),
});
let gbm = match GbmDevice::new(master) {
Ok(g) => g,
Err(e) => return Err(MetalError::GbmDevice(e)),
};
let mut is_nvidia = false;
let mut is_amd = false;
match gbm.drm.version() {
@ -2547,7 +2550,8 @@ impl MetalBackend {
}
fn set_gfx_api(&self, dev: &MetalDrmDevice, api: GfxApi) {
if dev.ctx.get().gfx.gfx_api() == api {
let old_ctx = dev.ctx.get();
if old_ctx.gfx.gfx_api() == api {
return;
}
let gfx = match self.state.create_gfx_context(&dev.master, Some(api)) {
@ -2566,6 +2570,7 @@ impl MetalBackend {
dev.ctx.set(Rc::new(MetalRenderContext {
dev_id: dev.id,
gfx,
gbm: old_ctx.gbm.clone(),
}));
if dev.is_render_device() {
self.make_render_device(dev, true);
@ -2836,7 +2841,7 @@ impl MetalBackend {
return Err(MetalError::MissingRenderModifier(format.name));
}
usage = GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR;
let render_bo = render_ctx.gfx.gbm().create_bo(
let render_bo = render_ctx.gbm.create_bo(
&self.state.dma_buf_ids,
width,
height,

View file

@ -1,5 +1,6 @@
use {
crate::{
allocator::BufferObject,
async_engine::{Phase, SpawnedFuture},
backend::{
AxisSource, Backend, BackendDrmDevice, BackendEvent, Connector, ConnectorEvent,

View file

@ -1,23 +1,30 @@
use {
crate::{
allocator::{Allocator, AllocatorError, BO_USE_LINEAR, BO_USE_RENDERING},
cli::{GlobalArgs, ScreenshotArgs, ScreenshotFormat},
format::XRGB8888,
tools::tool_client::{with_tool_client, Handle, ToolClient},
udmabuf::{Udmabuf, UdmabufError},
utils::{errorfmt::ErrorFmt, queue::AsyncQueue, windows::WindowsExt},
video::{
dmabuf::{DmaBuf, DmaBufIds, DmaBufPlane, PlaneVec},
drm::Drm,
gbm::{GbmDevice, GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING},
drm::{Drm, DrmError},
gbm::{GbmDevice, GbmError},
},
wire::{
jay_compositor::TakeScreenshot,
jay_screenshot::{Dmabuf, Error},
jay_screenshot::{Dmabuf, Dmabuf2, DrmDev, Error, Plane},
},
},
chrono::Local,
jay_algorithms::qoi::xrgb8888_encode_qoi,
png::{BitDepth, ColorType, Encoder, SrgbRenderingIntent},
std::rc::Rc,
std::{
cell::{Cell, RefCell},
rc::Rc,
},
thiserror::Error,
uapi::OwnedFd,
};
pub fn main(global: GlobalArgs, args: ScreenshotArgs) {
@ -47,17 +54,62 @@ async fn run(screenshot: Rc<Screenshot>) {
Error::handle(tc, sid, result.clone(), |res, err| {
res.push(Err(err.msg.to_owned()));
});
Dmabuf::handle(tc, sid, result.clone(), |res, buf| {
res.push(Ok(buf));
Dmabuf::handle(tc, sid, result.clone(), |res, ev| {
let mut planes = PlaneVec::new();
planes.push(DmaBufPlane {
offset: ev.offset,
stride: ev.stride,
fd: ev.fd,
});
let buf = DmaBuf {
id: DmaBufIds::default().next(),
width: ev.width as _,
height: ev.height as _,
format: XRGB8888,
modifier: ((ev.modifier_hi as u64) << 32) | (ev.modifier_lo as u64),
planes,
};
res.push(Ok((buf, Some(ev.drm_dev))));
});
let buf = match result.pop().await {
let drm_dev = Rc::new(Cell::new(None));
let planes = Rc::new(RefCell::new(PlaneVec::new()));
DrmDev::handle(tc, sid, drm_dev.clone(), |res, buf| {
res.set(Some(buf.drm_dev));
});
Plane::handle(tc, sid, planes.clone(), |res, buf| {
res.borrow_mut().push(DmaBufPlane {
offset: buf.offset,
stride: buf.stride,
fd: buf.fd,
});
});
Dmabuf2::handle(
tc,
sid,
(drm_dev, planes, result.clone()),
|(dev, planes, res), ev| {
let buf = DmaBuf {
id: DmaBufIds::default().next(),
width: ev.width,
height: ev.height,
format: XRGB8888,
modifier: ev.modifier,
planes: planes.take(),
};
res.push(Ok((buf, dev.take())))
},
);
let (buf, drm_dev) = match result.pop().await {
Ok(b) => b,
Err(e) => {
fatal!("Could not take a screenshot: {}", e);
}
};
let format = screenshot.args.format;
let data = buf_to_bytes(&DmaBufIds::default(), &buf, format);
let data = match buf_to_bytes(drm_dev.as_ref(), &buf, format) {
Ok(d) => d,
Err(e) => fatal!("{}", ErrorFmt(e)),
};
let filename = match &screenshot.args.filename {
Some(f) => f.clone(),
_ => {
@ -74,48 +126,48 @@ async fn run(screenshot: Rc<Screenshot>) {
}
}
pub fn buf_to_bytes(dma_buf_ids: &DmaBufIds, buf: &Dmabuf, format: ScreenshotFormat) -> Vec<u8> {
let drm = match Drm::reopen(buf.drm_dev.raw(), false) {
Ok(drm) => drm,
Err(e) => {
fatal!("Could not open the drm device: {}", ErrorFmt(e));
}
};
let gbm = match GbmDevice::new(&drm) {
Ok(g) => g,
Err(e) => {
fatal!("Could not create a gbm device: {}", ErrorFmt(e));
}
};
let mut planes = PlaneVec::new();
planes.push(DmaBufPlane {
offset: buf.offset,
stride: buf.stride,
fd: buf.fd.clone(),
});
let dmabuf = DmaBuf {
id: dma_buf_ids.next(),
width: buf.width as _,
height: buf.height as _,
format: XRGB8888,
modifier: (buf.modifier_hi as u64) << 32 | (buf.modifier_lo as u64),
planes,
};
let bo = match gbm.import_dmabuf(&dmabuf, GBM_BO_USE_LINEAR | GBM_BO_USE_RENDERING) {
Ok(bo) => Rc::new(bo),
Err(e) => {
fatal!("Could not import screenshot dmabuf: {}", ErrorFmt(e));
}
};
let bo_map = match bo.map_read() {
Ok(map) => map,
Err(e) => {
fatal!("Could not map dmabuf: {}", ErrorFmt(e));
#[derive(Debug, Error)]
pub enum ScreenshotError {
#[error("Could not open the drm device")]
OpenDrmDevice(#[source] DrmError),
#[error("Could not create a gbm device")]
CreateGbmDevice(#[source] GbmError),
#[error("Could not create a udmabuf allocator")]
CreateUdmabuf(#[source] UdmabufError),
#[error("Could not import a dmabuf")]
ImportDmabuf(#[source] AllocatorError),
#[error("Could not map a dmabuf")]
MapDmabuf(#[source] AllocatorError),
}
pub fn buf_to_bytes(
drm_dev: Option<&Rc<OwnedFd>>,
buf: &DmaBuf,
format: ScreenshotFormat,
) -> Result<Vec<u8>, ScreenshotError> {
let allocator: Rc<dyn Allocator> = match drm_dev {
Some(drm_dev) => {
let drm = Drm::reopen(drm_dev.raw(), false).map_err(ScreenshotError::OpenDrmDevice)?;
GbmDevice::new(&drm)
.map(Rc::new)
.map_err(ScreenshotError::CreateGbmDevice)?
}
None => Udmabuf::new()
.map(Rc::new)
.map_err(ScreenshotError::CreateUdmabuf)?,
};
let bo = allocator
.import_dmabuf(buf, BO_USE_LINEAR | BO_USE_RENDERING)
.map_err(ScreenshotError::ImportDmabuf)?;
let bo_map = bo.map_read().map_err(ScreenshotError::MapDmabuf)?;
let data = unsafe { bo_map.data() };
if format == ScreenshotFormat::Qoi {
return xrgb8888_encode_qoi(data, buf.width, buf.height, bo_map.stride() as u32);
return Ok(xrgb8888_encode_qoi(
data,
buf.width as _,
buf.height as _,
bo_map.stride() as u32,
));
}
let mut out = vec![];
@ -128,12 +180,12 @@ pub fn buf_to_bytes(dma_buf_ids: &DmaBufIds, buf: &Dmabuf, format: ScreenshotFor
image_data.extend_from_slice(&[pixel[2], pixel[1], pixel[0], 255])
}
}
let mut encoder = Encoder::new(&mut out, buf.width, buf.height);
let mut encoder = Encoder::new(&mut out, buf.width as _, buf.height as _);
encoder.set_color(ColorType::Rgba);
encoder.set_depth(BitDepth::Eight);
encoder.set_srgb(SrgbRenderingIntent::Perceptual);
let mut writer = encoder.write_header().unwrap();
writer.write_image_data(&image_data).unwrap();
}
out
Ok(out)
}

View file

@ -36,9 +36,11 @@ impl DrmFeedback {
ids: &DrmFeedbackIds,
render_ctx: &dyn GfxContext,
) -> Result<Self, DrmFeedbackError> {
let main_device = uapi::fstat(render_ctx.gbm().drm.raw())
.map_err(OsError::from)?
.st_rdev;
let drm = match render_ctx.allocator().drm() {
Some(drm) => drm.raw(),
_ => return Err(DrmFeedbackError::NoDrmDevice),
};
let main_device = uapi::fstat(drm).map_err(OsError::from)?.st_rdev;
let (data, index_map) = create_fd_data(render_ctx);
let mut memfd =
uapi::memfd_create("drm_feedback", c::MFD_CLOEXEC | c::MFD_ALLOW_SEALING).unwrap();
@ -118,4 +120,6 @@ fn create_fd_data(ctx: &dyn GfxContext) -> (Vec<u8>, AHashMap<(u32, Modifier), u
pub enum DrmFeedbackError {
#[error("Could not stat drm device")]
Stat(#[from] OsError),
#[error("Graphics API does not have a DRM device")]
NoDrmDevice,
}

View file

@ -1,5 +1,6 @@
use {
crate::{
allocator::Allocator,
cursor::Cursor,
damage::DamageVisualizer,
fixed::Fixed,
@ -11,7 +12,7 @@ use {
theme::Color,
tree::{Node, OutputNode},
utils::{clonecell::UnsafeCellCloneSafe, transform_ext::TransformExt},
video::{dmabuf::DmaBuf, drm::sync_obj::SyncObjCtx, gbm::GbmDevice, Modifier},
video::{dmabuf::DmaBuf, drm::sync_obj::SyncObjCtx, Modifier},
},
ahash::AHashMap,
indexmap::IndexSet,
@ -533,7 +534,7 @@ pub trait GfxTexture: Debug {
pub trait GfxContext: Debug {
fn reset_status(&self) -> Option<ResetStatus>;
fn render_node(&self) -> Rc<CString>;
fn render_node(&self) -> Option<Rc<CString>>;
fn formats(&self) -> Rc<AHashMap<u32, GfxFormat>>;
@ -554,7 +555,7 @@ pub trait GfxContext: Debug {
damage: Option<&[Rect]>,
) -> Result<Rc<dyn GfxTexture>, GfxError>;
fn gbm(&self) -> &GbmDevice;
fn allocator(&self) -> Rc<dyn Allocator>;
fn gfx_api(&self) -> GfxApi;
@ -566,7 +567,7 @@ pub trait GfxContext: Debug {
format: &'static Format,
) -> Result<Rc<dyn GfxFramebuffer>, GfxError>;
fn sync_obj_ctx(&self) -> &Rc<SyncObjCtx>;
fn sync_obj_ctx(&self) -> Option<&Rc<SyncObjCtx>>;
}
#[derive(Debug)]

View file

@ -1,5 +1,6 @@
use {
crate::{
allocator::Allocator,
format::{Format, XRGB8888},
gfx_api::{
BufferResvUser, GfxApiOpt, GfxContext, GfxError, GfxFormat, GfxFramebuffer, GfxImage,
@ -240,8 +241,8 @@ impl GfxContext for GlRenderContext {
self.reset_status()
}
fn render_node(&self) -> Rc<CString> {
self.render_node()
fn render_node(&self) -> Option<Rc<CString>> {
Some(self.render_node())
}
fn formats(&self) -> Rc<AHashMap<u32, GfxFormat>> {
@ -278,8 +279,8 @@ impl GfxContext for GlRenderContext {
.map_err(|e| e.into())
}
fn gbm(&self) -> &GbmDevice {
&self.gbm
fn allocator(&self) -> Rc<dyn Allocator> {
self.gbm.clone()
}
fn gfx_api(&self) -> GfxApi {
@ -299,7 +300,7 @@ impl GfxContext for GlRenderContext {
Ok(Rc::new(Framebuffer { ctx: self, gl: fb }))
}
fn sync_obj_ctx(&self) -> &Rc<SyncObjCtx> {
&self.sync_ctx
fn sync_obj_ctx(&self) -> Option<&Rc<SyncObjCtx>> {
Some(&self.sync_ctx)
}
}

View file

@ -17,6 +17,7 @@ mod util;
use {
crate::{
allocator::Allocator,
async_engine::AsyncEngine,
format::Format,
gfx_api::{
@ -31,7 +32,7 @@ use {
video::{
dmabuf::DmaBuf,
drm::{sync_obj::SyncObjCtx, Drm, DrmError},
gbm::{GbmDevice, GbmError},
gbm::GbmError,
},
},
ahash::AHashMap,
@ -209,8 +210,8 @@ impl GfxContext for Context {
None
}
fn render_node(&self) -> Rc<CString> {
self.0.device.render_node.clone()
fn render_node(&self) -> Option<Rc<CString>> {
Some(self.0.device.render_node.clone())
}
fn formats(&self) -> Rc<AHashMap<u32, GfxFormat>> {
@ -255,8 +256,8 @@ impl GfxContext for Context {
Ok(tex as _)
}
fn gbm(&self) -> &GbmDevice {
&self.0.device.gbm
fn allocator(&self) -> Rc<dyn Allocator> {
self.0.device.gbm.clone()
}
fn gfx_api(&self) -> GfxApi {
@ -276,8 +277,8 @@ impl GfxContext for Context {
Ok(fb)
}
fn sync_obj_ctx(&self) -> &Rc<SyncObjCtx> {
&self.0.device.sync_ctx
fn sync_obj_ctx(&self) -> Option<&Rc<SyncObjCtx>> {
Some(&self.0.device.sync_ctx)
}
}

View file

@ -49,7 +49,7 @@ use {
pub struct VulkanDevice {
pub(super) physical_device: PhysicalDevice,
pub(super) render_node: Rc<CString>,
pub(super) gbm: GbmDevice,
pub(super) gbm: Rc<GbmDevice>,
pub(super) sync_ctx: Rc<SyncObjCtx>,
pub(super) instance: Rc<VulkanInstance>,
pub(super) device: Device,
@ -276,7 +276,7 @@ impl VulkanInstance {
physical_device: phy_dev,
render_node,
sync_ctx: Rc::new(SyncObjCtx::new(gbm.drm.fd())),
gbm,
gbm: Rc::new(gbm),
instance: self.clone(),
device,
external_memory_fd,

View file

@ -32,6 +32,7 @@ use {
};
pub const CREATE_EI_SESSION_SINCE: Version = Version(5);
pub const SCREENSHOT_SPLITUP_SINCE: Version = Version(6);
pub struct JayCompositorGlobal {
name: GlobalName,
@ -69,7 +70,7 @@ impl Global for JayCompositorGlobal {
}
fn version(&self) -> u32 {
5
6
}
fn required_caps(&self) -> ClientCaps {
@ -117,16 +118,30 @@ impl JayCompositor {
match take_screenshot(&self.client.state, include_cursor) {
Ok(s) => {
let dmabuf = s.bo.dmabuf();
let plane = &dmabuf.planes[0];
ss.send_dmabuf(
&s.drm,
&plane.fd,
dmabuf.width,
dmabuf.height,
plane.offset,
plane.stride,
dmabuf.modifier,
);
if self.version < SCREENSHOT_SPLITUP_SINCE {
if let Some(drm) = &s.drm {
let plane = &dmabuf.planes[0];
ss.send_dmabuf(
drm,
&plane.fd,
dmabuf.width,
dmabuf.height,
plane.offset,
plane.stride,
dmabuf.modifier,
);
} else {
ss.send_error("Buffer has no associated DRM device");
}
} else {
if let Some(drm) = &s.drm {
ss.send_drm_dev(drm);
}
for plane in &dmabuf.planes {
ss.send_plane(plane);
}
ss.send_dmabuf2(dmabuf);
}
}
Err(e) => {
let msg = ErrorFmt(e).to_string();

View file

@ -21,10 +21,16 @@ impl JayRenderCtx {
pub fn send_render_ctx(&self, ctx: Option<Rc<dyn GfxContext>>) {
let mut fd = None;
if let Some(ctx) = ctx {
match ctx.gbm().drm.dup_render() {
Ok(d) => fd = Some(d.fd().clone()),
Err(e) => {
log::error!("Could not dup drm fd: {}", ErrorFmt(e));
let allocator = ctx.allocator();
match allocator.drm() {
Some(drm) => match drm.dup_render() {
Ok(d) => fd = Some(d.fd().clone()),
Err(e) => {
log::error!("Could not dup drm fd: {}", ErrorFmt(e));
}
},
None => {
log::error!("Allocator does not have a DRM device");
}
}
} else {

View file

@ -1,5 +1,6 @@
use {
crate::{
allocator::{AllocatorError, BufferObject, BO_USE_LINEAR, BO_USE_RENDERING},
client::{Client, ClientError},
format::XRGB8888,
gfx_api::{GfxContext, GfxError, GfxFramebuffer, GfxTexture},
@ -16,17 +17,11 @@ use {
numcell::NumCell,
option_ext::OptionExt,
},
video::{
dmabuf::DmaBuf,
gbm::{GbmBo, GbmError, GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING},
Modifier, INVALID_MODIFIER, LINEAR_MODIFIER,
},
video::{dmabuf::DmaBuf, INVALID_MODIFIER, LINEAR_MODIFIER},
wire::{jay_screencast::*, JayScreencastId},
},
ahash::AHashSet,
indexmap::{indexset, IndexSet},
jay_config::video::Transform,
once_cell::sync::Lazy,
std::{
cell::{Cell, RefCell},
ops::DerefMut,
@ -108,7 +103,7 @@ struct Pending {
}
struct ScreencastBuffer {
_bo: GbmBo,
_bo: Rc<dyn BufferObject>,
dmabuf: DmaBuf,
fb: Rc<dyn GfxFramebuffer>,
free: bool,
@ -381,31 +376,27 @@ impl JayScreencast {
if width == 0 || height == 0 {
continue;
}
let mut usage = GBM_BO_USE_RENDERING;
let mut usage = BO_USE_RENDERING;
let modifiers = match self.linear.get() {
true if format.write_modifiers.contains(&LINEAR_MODIFIER) => {
static MODS: Lazy<IndexSet<Modifier>> =
Lazy::new(|| indexset![LINEAR_MODIFIER]);
&MODS
vec![LINEAR_MODIFIER]
}
true if format.write_modifiers.contains(&INVALID_MODIFIER) => {
usage |= GBM_BO_USE_LINEAR;
static MODS: Lazy<IndexSet<Modifier>> =
Lazy::new(|| indexset![INVALID_MODIFIER]);
&MODS
usage |= BO_USE_LINEAR;
vec![INVALID_MODIFIER]
}
true => return Err(JayScreencastError::Modifier),
false if format.write_modifiers.is_empty() => {
return Err(JayScreencastError::XRGB8888Writing)
}
false => &format.write_modifiers,
false => format.write_modifiers.iter().copied().collect(),
};
let buffer = ctx.gbm().create_bo(
let buffer = ctx.allocator().create_bo(
&self.client.state.dma_buf_ids,
width,
height,
XRGB8888,
modifiers,
&modifiers,
usage,
)?;
let fb = ctx.clone().dmabuf_img(buffer.dmabuf())?.to_framebuffer()?;
@ -681,7 +672,7 @@ pub enum JayScreencastError {
#[error("Buffer index {0} is out-of-bounds")]
OutOfBounds(u32),
#[error(transparent)]
GbmError(#[from] GbmError),
AllocatorError(#[from] AllocatorError),
#[error(transparent)]
GfxError(#[from] GfxError),
#[error("Render context does not support XRGB8888 format")]

View file

@ -3,6 +3,7 @@ use {
client::Client,
leaks::Tracker,
object::{Object, Version},
video::dmabuf::{DmaBuf, DmaBufPlane},
wire::{jay_screenshot::*, JayScreenshotId},
},
std::{convert::Infallible, rc::Rc},
@ -45,6 +46,31 @@ impl JayScreenshot {
msg,
});
}
pub fn send_drm_dev(&self, drm: &Rc<OwnedFd>) {
self.client.event(DrmDev {
self_id: self.id,
drm_dev: drm.clone(),
})
}
pub fn send_plane(&self, plane: &DmaBufPlane) {
self.client.event(Plane {
self_id: self.id,
fd: plane.fd.clone(),
offset: plane.offset,
stride: plane.stride,
})
}
pub fn send_dmabuf2(&self, buf: &DmaBuf) {
self.client.event(Dmabuf2 {
self_id: self.id,
width: buf.width,
height: buf.height,
modifier: buf.modifier,
})
}
}
impl JayScreenshotRequestHandler for JayScreenshot {

View file

@ -43,7 +43,9 @@ impl WlDrmGlobal {
track!(client, obj);
client.add_client_obj(&obj)?;
if let Some(rc) = client.state.render_ctx.get() {
obj.send_device(&rc.render_node());
if let Some(rn) = rc.render_node() {
obj.send_device(&rn);
}
obj.send_capabilities(PRIME);
}
Ok(())

View file

@ -202,7 +202,10 @@ impl Drop for SurfaceBuffer {
log::error!("Cannot signal release point because there is no render context");
return;
};
let ctx = ctx.sync_obj_ctx();
let Some(ctx) = ctx.sync_obj_ctx() else {
log::error!("Cannot signal release point because there is no syncobj context");
return;
};
if sync_files.is_not_empty() {
let res = ctx.import_sync_files(
&release.sync_obj,

View file

@ -1,5 +1,6 @@
use {
crate::{
allocator::Allocator,
async_engine::SpawnedFuture,
backend::{
AxisSource, Backend, BackendEvent, Connector, ConnectorEvent, ConnectorId,
@ -15,14 +16,18 @@ use {
test_error::TestResult, test_gfx_api::TestGfxCtx, test_utils::test_expected_event::TEEH,
},
state::State,
udmabuf::Udmabuf,
utils::{
clonecell::CloneCell, copyhashmap::CopyHashMap, on_change::OnChange, oserror::OsError,
syncqueue::SyncQueue,
clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt,
on_change::OnChange, oserror::OsError, syncqueue::SyncQueue,
},
video::{
drm::{ConnectorType, Drm},
gbm::{GbmDevice, GbmError},
},
video::drm::{ConnectorType, Drm},
},
bstr::ByteSlice,
std::{any::Any, cell::Cell, io, os::unix::ffi::OsStrExt, pin::Pin, rc::Rc},
std::{any::Any, cell::Cell, error::Error, io, os::unix::ffi::OsStrExt, pin::Pin, rc::Rc},
thiserror::Error,
uapi::c,
};
@ -37,6 +42,10 @@ pub enum TestBackendError {
OpenDrmNode(String, #[source] OsError),
#[error("Could not create a render context")]
RenderContext(#[source] GfxError),
#[error("Could not create a gbm device")]
CreateGbmDevice(#[source] GbmError),
#[error("Could not create any allocator")]
CreateAllocator,
}
pub struct TestBackend {
@ -124,17 +133,21 @@ impl TestBackend {
}
}
pub fn install_render_context(&self) -> TestResult {
pub fn install_render_context(&self, prefer_udmabuf: bool) -> TestResult {
if self.render_context_installed.get() {
return Ok(());
}
self.create_render_context()?;
self.create_render_context(prefer_udmabuf)?;
self.render_context_installed.set(true);
Ok(())
}
pub fn install_default(&self) -> TestResult {
self.install_render_context()?;
self.install_default2(true)
}
pub fn install_default2(&self, prefer_udmabuf: bool) -> TestResult {
self.install_render_context(prefer_udmabuf)?;
self.state
.backend_events
.push(BackendEvent::NewConnector(self.default_connector.clone()));
@ -150,47 +163,35 @@ impl TestBackend {
Ok(())
}
fn create_render_context(&self) -> Result<(), TestBackendError> {
let dri = match std::fs::read_dir("/dev/dri") {
Ok(d) => d,
Err(e) => return Err(TestBackendError::ReadDri(e)),
};
let mut files = vec![];
for f in dri {
let f = match f {
Ok(f) => f,
Err(e) => return Err(TestBackendError::ReadDri(e)),
fn create_render_context(&self, prefer_udmabuf: bool) -> Result<(), TestBackendError> {
macro_rules! constructor {
($c:expr) => {
(&|| {
$c.map(|a| Rc::new(a) as Rc<dyn Allocator>)
.map_err(|e| Box::new(e) as Box<dyn Error>)
}) as &dyn Fn() -> Result<Rc<dyn Allocator>, Box<dyn Error>>
};
files.push(f.path());
}
let node = 'node: {
for f in &files {
if let Some(file) = f.file_name() {
if file.as_bytes().starts_with_str("renderD") {
break 'node f;
}
let udmabuf = ("udmabuf", constructor!(Udmabuf::new()));
let gbm = ("GBM", constructor!(create_gbm_allocator()));
let allocators = match prefer_udmabuf {
true => [udmabuf, gbm],
false => [gbm, udmabuf],
};
let mut allocator = None::<Rc<dyn Allocator>>;
for (name, f) in allocators {
match f() {
Ok(a) => {
allocator = Some(a);
break;
}
Err(e) => {
log::error!("Could not create {name} allocator: {}", ErrorFmt(&*e));
}
}
for f in &files {
if let Some(file) = f.file_name() {
if file.as_bytes().starts_with_str("card") {
break 'node f;
}
}
}
return Err(TestBackendError::NoDrmNode);
};
let file = match uapi::open(node.as_path(), c::O_RDWR | c::O_CLOEXEC, 0) {
Ok(f) => Rc::new(f),
Err(e) => {
return Err(TestBackendError::OpenDrmNode(
node.as_os_str().as_bytes().as_bstr().to_string(),
e.into(),
))
}
};
let drm = Drm::open_existing(file);
let ctx = match TestGfxCtx::new(&drm) {
}
let allocator = allocator.ok_or(TestBackendError::CreateAllocator)?;
let ctx = match TestGfxCtx::new(allocator) {
Ok(ctx) => ctx,
Err(e) => return Err(TestBackendError::RenderContext(e)),
};
@ -199,6 +200,50 @@ impl TestBackend {
}
}
fn create_gbm_allocator() -> Result<GbmDevice, TestBackendError> {
let dri = match std::fs::read_dir("/dev/dri") {
Ok(d) => d,
Err(e) => return Err(TestBackendError::ReadDri(e)),
};
let mut files = vec![];
for f in dri {
let f = match f {
Ok(f) => f,
Err(e) => return Err(TestBackendError::ReadDri(e)),
};
files.push(f.path());
}
let node = 'node: {
for f in &files {
if let Some(file) = f.file_name() {
if file.as_bytes().starts_with_str("renderD") {
break 'node f;
}
}
}
for f in &files {
if let Some(file) = f.file_name() {
if file.as_bytes().starts_with_str("card") {
break 'node f;
}
}
}
return Err(TestBackendError::NoDrmNode);
};
let file = match uapi::open(node.as_path(), c::O_RDWR | c::O_CLOEXEC, 0) {
Ok(f) => Rc::new(f),
Err(e) => {
return Err(TestBackendError::OpenDrmNode(
node.as_os_str().as_bytes().as_bstr().to_string(),
e.into(),
))
}
};
let drm = Drm::open_existing(file);
let gbm = GbmDevice::new(&drm).map_err(TestBackendError::CreateGbmDevice)?;
Ok(gbm)
}
impl Backend for TestBackend {
fn run(self: Rc<Self>) -> SpawnedFuture<Result<(), Box<dyn std::error::Error>>> {
let future = (self.test_future)(&self.state);

View file

@ -25,7 +25,7 @@ use {
pub struct TestClient {
pub run: Rc<TestRun>,
pub server: Rc<Client>,
pub _server: Rc<Client>,
pub tran: Rc<TestTransport>,
pub registry: Rc<TestRegistry>,
pub jc: Rc<TestJayCompositor>,
@ -92,12 +92,8 @@ impl TestClient {
}
pub async fn take_screenshot(&self, include_cursor: bool) -> Result<Vec<u8>, TestError> {
let dmabuf = self.jc.take_screenshot(include_cursor).await?;
let qoi = buf_to_bytes(
&self.server.state.dma_buf_ids,
&dmabuf,
ScreenshotFormat::Qoi,
);
let (dmabuf, dev) = self.jc.take_screenshot(include_cursor).await?;
let qoi = buf_to_bytes(dev.as_ref(), &dmabuf, ScreenshotFormat::Qoi)?;
Ok(qoi)
}

View file

@ -1,5 +1,6 @@
use {
crate::{
allocator::{Allocator, AllocatorError, BufferObject, BufferUsage},
format::{Format, ARGB8888, XRGB8888},
gfx_api::{
CopyTexture, FillRect, FramebufferRect, GfxApiOpt, GfxContext, GfxError, GfxFormat,
@ -7,12 +8,7 @@ use {
},
rect::Rect,
theme::Color,
video::{
dmabuf::DmaBuf,
drm::{sync_obj::SyncObjCtx, Drm, DrmError},
gbm::{GbmBo, GbmDevice, GbmError},
LINEAR_MODIFIER,
},
video::{dmabuf::DmaBuf, drm::sync_obj::SyncObjCtx, LINEAR_MODIFIER},
},
ahash::AHashMap,
indexmap::IndexSet,
@ -32,15 +28,9 @@ use {
#[derive(Error, Debug)]
enum TestGfxError {
#[error("Could not map dmabuf")]
MapDmaBuf(#[source] GbmError),
MapDmaBuf(#[source] AllocatorError),
#[error("Could not import dmabuf")]
ImportDmaBuf(#[source] GbmError),
#[error("Could not create a gbm device")]
CreateGbmDevice(#[source] GbmError),
#[error("Could not retrieve the render node path")]
GetRenderNode(#[source] DrmError),
#[error("Drm device does not have a render node")]
NoRenderNode,
ImportDmaBuf(#[source] AllocatorError),
}
impl From<TestGfxError> for GfxError {
@ -51,19 +41,11 @@ impl From<TestGfxError> for GfxError {
pub struct TestGfxCtx {
formats: Rc<AHashMap<u32, GfxFormat>>,
sync_obj_ctx: Rc<SyncObjCtx>,
gbm: GbmDevice,
render_node: Rc<CString>,
allocator: Rc<dyn Allocator>,
}
impl TestGfxCtx {
pub fn new(drm: &Drm) -> Result<Rc<Self>, GfxError> {
let render_node = drm
.get_render_node()
.map_err(TestGfxError::GetRenderNode)?
.ok_or(TestGfxError::NoRenderNode)?;
let gbm = GbmDevice::new(drm).map_err(TestGfxError::CreateGbmDevice)?;
let ctx = Rc::new(SyncObjCtx::new(drm.fd()));
pub fn new(allocator: Rc<dyn Allocator>) -> Result<Rc<Self>, GfxError> {
let mut modifiers = IndexSet::new();
modifiers.insert(LINEAR_MODIFIER);
let mut formats = AHashMap::new();
@ -79,9 +61,7 @@ impl TestGfxCtx {
}
Ok(Rc::new(Self {
formats: Rc::new(formats),
sync_obj_ctx: ctx,
gbm,
render_node: Rc::new(render_node),
allocator,
}))
}
}
@ -97,8 +77,8 @@ impl GfxContext for TestGfxCtx {
None
}
fn render_node(&self) -> Rc<CString> {
self.render_node.clone()
fn render_node(&self) -> Option<Rc<CString>> {
None
}
fn formats(&self) -> Rc<AHashMap<u32, GfxFormat>> {
@ -109,9 +89,8 @@ impl GfxContext for TestGfxCtx {
Ok(Rc::new(TestGfxImage::DmaBuf(TestDmaBufGfxImage {
buf: buf.clone(),
bo: self
.gbm
.import_dmabuf(buf, 0)
.map(Rc::new)
.allocator
.import_dmabuf(buf, BufferUsage::none())
.map_err(TestGfxError::ImportDmaBuf)?,
})))
}
@ -142,8 +121,8 @@ impl GfxContext for TestGfxCtx {
})))
}
fn gbm(&self) -> &GbmDevice {
&self.gbm
fn allocator(&self) -> Rc<dyn Allocator> {
self.allocator.clone()
}
fn gfx_api(&self) -> GfxApi {
@ -170,8 +149,8 @@ impl GfxContext for TestGfxCtx {
}))
}
fn sync_obj_ctx(&self) -> &Rc<SyncObjCtx> {
&self.sync_obj_ctx
fn sync_obj_ctx(&self) -> Option<&Rc<SyncObjCtx>> {
None
}
}
@ -195,7 +174,7 @@ struct TestShmGfxImage {
struct TestDmaBufGfxImage {
buf: DmaBuf,
bo: Rc<GbmBo>,
bo: Rc<dyn BufferObject>,
}
impl TestGfxImage {
@ -242,7 +221,7 @@ impl TestGfxImage {
);
}
TestGfxImage::DmaBuf(d) => {
let map = d.bo.map_read().map_err(TestGfxError::MapDmaBuf)?;
let map = d.bo.clone().map_read().map_err(TestGfxError::MapDmaBuf)?;
unsafe {
copy(
map.stride(),
@ -478,7 +457,7 @@ impl GfxFramebuffer for TestGfxFb {
s.format,
),
TestGfxImage::DmaBuf(d) => {
let map = d.bo.map_read().map_err(TestGfxError::MapDmaBuf)?;
let map = d.bo.clone().map_read().map_err(TestGfxError::MapDmaBuf)?;
copy(
map.data_ptr(),
d.buf.width,
@ -511,7 +490,7 @@ impl GfxFramebuffer for TestGfxFb {
s.format,
)?,
TestGfxImage::DmaBuf(d) => {
let map = d.bo.map_write().map_err(TestGfxError::MapDmaBuf)?;
let map = d.bo.clone().map_write().map_err(TestGfxError::MapDmaBuf)?;
apply(
map.data_ptr(),
d.buf.width,

View file

@ -9,13 +9,14 @@ use {
testrun::ParseFull,
},
utils::{buffd::MsgParser, cell_ext::CellExt},
video::dmabuf::DmaBuf,
wire::{
jay_compositor::{self, *},
jay_screenshot::Dmabuf,
JayCompositorId,
},
},
std::{cell::Cell, rc::Rc},
uapi::OwnedFd,
};
pub struct TestJayCompositor {
@ -49,10 +50,16 @@ impl TestJayCompositor {
Ok(())
}
pub async fn take_screenshot(&self, include_cursor: bool) -> Result<Dmabuf, TestError> {
pub async fn take_screenshot(
&self,
include_cursor: bool,
) -> Result<(DmaBuf, Option<Rc<OwnedFd>>), TestError> {
let js = Rc::new(TestJayScreenshot {
id: self.tran.id(),
result: Cell::new(None),
state: self.tran.run.state.clone(),
drm_dev: Default::default(),
planes: Default::default(),
result: Default::default(),
});
self.tran.send(TakeScreenshot2 {
self_id: self.id,
@ -62,7 +69,7 @@ impl TestJayCompositor {
self.tran.add_obj(js.clone())?;
self.tran.sync().await;
match js.result.take() {
Some(Ok(res)) => Ok(res),
Some(Ok(res)) => Ok((res, js.drm_dev.take())),
Some(Err(res)) => bail!("Compositor could not take a screenshot: {}", res),
None => bail!("Compositor did not send a screenshot"),
}

View file

@ -165,7 +165,7 @@ impl TestRegistry {
get_jay_compositor,
jay_compositor,
jay_compositor,
1,
6,
TestJayCompositor
);
create_singleton!(get_compositor, compositor, wl_compositor, 6, TestCompositor);

View file

@ -1,21 +1,44 @@
use {
crate::{
format::XRGB8888,
it::{test_error::TestError, test_object::TestObject, testrun::ParseFull},
state::State,
utils::buffd::MsgParser,
video::dmabuf::{DmaBuf, DmaBufPlane, PlaneVec},
wire::{jay_screenshot::*, JayScreenshotId},
},
std::cell::Cell,
std::{
cell::{Cell, RefCell},
rc::Rc,
},
uapi::OwnedFd,
};
pub struct TestJayScreenshot {
pub id: JayScreenshotId,
pub result: Cell<Option<Result<Dmabuf, String>>>,
pub state: Rc<State>,
pub drm_dev: Cell<Option<Rc<OwnedFd>>>,
pub planes: RefCell<PlaneVec<DmaBufPlane>>,
pub result: Cell<Option<Result<DmaBuf, String>>>,
}
impl TestJayScreenshot {
fn handle_dmabuf(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
let ev = Dmabuf::parse_full(parser)?;
self.result.set(Some(Ok(ev)));
let mut planes = PlaneVec::new();
planes.push(DmaBufPlane {
offset: ev.offset,
stride: ev.stride,
fd: ev.fd,
});
self.result.set(Some(Ok(DmaBuf {
id: self.state.dma_buf_ids.next(),
width: ev.width as _,
height: ev.height as _,
format: XRGB8888,
modifier: ((ev.modifier_hi as u64) << 32) | (ev.modifier_lo as u64),
planes,
})));
Ok(())
}
@ -24,6 +47,35 @@ impl TestJayScreenshot {
self.result.set(Some(Err(ev.msg.to_string())));
Ok(())
}
fn handle_drm_dev(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
let ev = DrmDev::parse_full(parser)?;
self.drm_dev.set(Some(ev.drm_dev));
Ok(())
}
fn handle_plane(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
let ev = Plane::parse_full(parser)?;
self.planes.borrow_mut().push(DmaBufPlane {
offset: ev.offset,
stride: ev.stride,
fd: ev.fd,
});
Ok(())
}
fn handle_dmabuf2(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
let ev = Dmabuf2::parse_full(parser)?;
self.result.set(Some(Ok(DmaBuf {
id: self.state.dma_buf_ids.next(),
width: ev.width as _,
height: ev.height as _,
format: XRGB8888,
modifier: ev.modifier,
planes: self.planes.take(),
})));
Ok(())
}
}
test_object! {
@ -31,6 +83,9 @@ test_object! {
DMABUF => handle_dmabuf,
ERROR => handle_error,
DRM_DEV => handle_drm_dev,
PLANE => handle_plane,
DMABUF2 => handle_dmabuf2,
}
impl TestObject for TestJayScreenshot {}

View file

@ -79,7 +79,7 @@ impl TestRun {
let client = self.state.clients.get(client_id)?;
Ok(Rc::new(TestClient {
run: self.clone(),
server: client,
_server: client,
tran,
jc,
comp: registry.get_compositor().await?,
@ -106,7 +106,14 @@ impl TestRun {
}
pub async fn create_default_setup(&self) -> Result<DefaultSetup, TestError> {
self.backend.install_default()?;
self.create_default_setup2(true).await
}
pub async fn create_default_setup2(
&self,
prefer_udmabuf: bool,
) -> Result<DefaultSetup, TestError> {
self.backend.install_default2(prefer_udmabuf)?;
let seat = self.get_seat("default")?;
self.state.eng.yield_now().await;
let output = match self.state.root.outputs.lock().values().next() {

View file

@ -10,7 +10,7 @@ async fn test(run: Rc<TestRun>) -> TestResult {
tassert!(!run.cfg.graphics_initialized.get());
run.backend.install_render_context()?;
run.backend.install_render_context(true)?;
tassert!(run.cfg.graphics_initialized.get());

View file

@ -11,7 +11,7 @@ use {
testcase!();
async fn test(run: Rc<TestRun>) -> TestResult {
let _ds = run.create_default_setup().await?;
let _ds = run.create_default_setup2(false).await?;
struct Waiter(Cell<bool>);
impl SyncObjWaiter for Waiter {
@ -23,7 +23,11 @@ async fn test(run: Rc<TestRun>) -> TestResult {
let waiter = Rc::new(Waiter(Cell::new(false)));
let eng = run.state.render_ctx.get().unwrap();
let syncobj = match eng.sync_obj_ctx().create_sync_obj() {
let Some(ctx) = eng.sync_obj_ctx() else {
log::warn!("Cannot test explicit sync on this system: render context does not support sync objects");
return Ok(());
};
let syncobj = match ctx.create_sync_obj() {
Ok(s) => Rc::new(s),
Err(e) => {
log::warn!("Cannot test explicit sync on this system: {}", ErrorFmt(e));
@ -56,7 +60,7 @@ async fn test(run: Rc<TestRun>) -> TestResult {
client.sync().await;
tassert_eq!(waiter.0.get(), false);
eng.sync_obj_ctx().signal(&syncobj, SyncObjPoint(1))?;
ctx.signal(&syncobj, SyncObjPoint(1))?;
client.sync().await;
tassert_eq!(waiter.0.get(), true);

View file

@ -12,7 +12,7 @@ use {
testcase!();
async fn test(run: Rc<TestRun>) -> TestResult {
let ds = run.create_default_setup().await?;
let ds = run.create_default_setup2(false).await?;
let scanout_feedback = {
let Some(base_fb) = run.state.drm_feedback.get() else {

View file

@ -43,6 +43,7 @@ mod macros;
#[macro_use]
mod leaks;
mod acceptor;
mod allocator;
mod async_engine;
mod backend;
mod backends;
@ -91,6 +92,7 @@ mod time;
mod tools;
mod tree;
mod udev;
mod udmabuf;
mod user_session;
mod utils;
mod version;

View file

@ -1,5 +1,6 @@
use {
crate::{
allocator::{BufferObject, BO_USE_RENDERING},
async_engine::{Phase, SpawnedFuture},
cursor::KnownCursor,
fixed::Fixed,
@ -15,7 +16,6 @@ use {
asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap,
errorfmt::ErrorFmt, hash_map_ext::HashMapExt, rc_eq::rc_eq,
},
video::gbm::{GbmBo, GBM_BO_USE_RENDERING},
wire::{
wp_fractional_scale_v1::PreferredScale, zwlr_layer_surface_v1::Configure,
ZwpLinuxBufferParamsV1Id,
@ -722,14 +722,15 @@ impl WindowData {
log::error!("Render context cannot render to ARGB8888 format");
return;
}
let modifiers: Vec<_> = format.write_modifiers.iter().copied().collect();
for _ in 0..NUM_BUFFERS {
let bo = match ctx.ctx.gbm().create_bo(
let bo = match ctx.ctx.allocator().create_bo(
&self.dpy.state.dma_buf_ids,
width,
height,
ARGB8888,
&format.write_modifiers,
GBM_BO_USE_RENDERING,
&modifiers,
BO_USE_RENDERING,
) {
Ok(b) => b,
Err(e) => {
@ -844,13 +845,13 @@ pub struct GuiBuffer {
pub wl: Rc<UsrWlBuffer>,
pub window: Rc<WindowData>,
pub fb: Rc<dyn GfxFramebuffer>,
pub _bo: Option<GbmBo>,
pub _bo: Option<Rc<dyn BufferObject>>,
pub free: Cell<bool>,
pub _size: (i32, i32),
}
struct GuiBufferPending {
pub bo: Cell<Option<GbmBo>>,
pub bo: Cell<Option<Rc<dyn BufferObject>>>,
pub window: Rc<WindowData>,
pub fb: Rc<dyn GfxFramebuffer>,
pub params: Rc<UsrLinuxBufferParams>,

View file

@ -1,14 +1,11 @@
use {
crate::{
allocator::{AllocatorError, BufferObject, BO_USE_LINEAR, BO_USE_RENDERING},
format::XRGB8888,
gfx_api::GfxError,
scale::Scale,
state::State,
video::{
drm::DrmError,
gbm::{GbmBo, GbmError, GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING},
INVALID_MODIFIER, LINEAR_MODIFIER,
},
video::{drm::DrmError, INVALID_MODIFIER, LINEAR_MODIFIER},
},
jay_config::video::Transform,
std::{ops::Deref, rc::Rc},
@ -23,7 +20,7 @@ pub enum ScreenshooterError {
#[error("Display is empty")]
EmptyDisplay,
#[error(transparent)]
GbmError(#[from] GbmError),
AllocatorError(#[from] AllocatorError),
#[error(transparent)]
RenderError(#[from] GfxError),
#[error(transparent)]
@ -35,8 +32,8 @@ pub enum ScreenshooterError {
}
pub struct Screenshot {
pub drm: Rc<OwnedFd>,
pub bo: GbmBo,
pub drm: Option<Rc<OwnedFd>>,
pub bo: Rc<dyn BufferObject>,
}
pub fn take_screenshot(
@ -52,18 +49,18 @@ pub fn take_screenshot(
return Err(ScreenshooterError::EmptyDisplay);
}
let formats = ctx.formats();
let mut usage = GBM_BO_USE_RENDERING;
let mut usage = BO_USE_RENDERING;
let modifiers = match formats.get(&XRGB8888.drm) {
None => return Err(ScreenshooterError::XRGB8888),
Some(f) if f.write_modifiers.contains(&LINEAR_MODIFIER) => &[LINEAR_MODIFIER],
Some(f) if f.write_modifiers.contains(&INVALID_MODIFIER) => {
usage |= GBM_BO_USE_LINEAR;
usage |= BO_USE_LINEAR;
&[INVALID_MODIFIER]
}
Some(_) => return Err(ScreenshooterError::Linear),
};
let gbm = ctx.gbm();
let bo = gbm.create_bo(
let allocator = ctx.allocator();
let bo = allocator.create_bo(
&state.dma_buf_ids,
extents.width(),
extents.height(),
@ -83,6 +80,9 @@ pub fn take_screenshot(
false,
Transform::None,
)?;
let drm = gbm.drm.dup_render()?.fd().clone();
let drm = match allocator.drm() {
Some(drm) => Some(drm.dup_render()?.fd().clone()),
_ => None,
};
Ok(Screenshot { drm, bo })
}

View file

@ -429,7 +429,7 @@ impl State {
self.cursors.set(None);
self.drm_feedback.set(None);
self.wait_for_sync_obj
.set_ctx(ctx.as_ref().map(|c| c.sync_obj_ctx().clone()));
.set_ctx(ctx.as_ref().and_then(|c| c.sync_obj_ctx().cloned()));
'handle_new_feedback: {
if let Some(ctx) = &ctx {
@ -506,10 +506,12 @@ impl State {
if !self.render_ctx_ever_initialized.replace(true) {
self.add_global(&Rc::new(WlDrmGlobal::new(self.globals.name())));
self.add_global(&Rc::new(ZwpLinuxDmabufV1Global::new(self.globals.name())));
if ctx.sync_obj_ctx().supports_async_wait() && self.explicit_sync_enabled.get() {
self.add_global(&Rc::new(WpLinuxDrmSyncobjManagerV1Global::new(
self.globals.name(),
)));
if let Some(ctx) = ctx.sync_obj_ctx() {
if ctx.supports_async_wait() && self.explicit_sync_enabled.get() {
self.add_global(&Rc::new(WpLinuxDrmSyncobjManagerV1Global::new(
self.globals.name(),
)));
}
}
if let Some(config) = self.config.get() {
config.graphics_initialized();
@ -1042,7 +1044,11 @@ impl State {
log::error!("Cannot signal sync obj point because there is no render context");
return;
};
if let Err(e) = ctx.sync_obj_ctx().signal(sync_obj, point) {
let Some(ctx) = ctx.sync_obj_ctx() else {
log::error!("Cannot signal sync obj point because there is no syncobj context");
return;
};
if let Err(e) = ctx.signal(sync_obj, point) {
log::error!("Could not signal sync obj: {}", ErrorFmt(e));
}
}

271
src/udmabuf.rs Normal file
View file

@ -0,0 +1,271 @@
use {
crate::{
allocator::{Allocator, AllocatorError, BufferObject, BufferUsage, MappedBuffer},
format::Format,
utils::{oserror::OsError, page_size::page_size},
video::{
dmabuf::{DmaBuf, DmaBufIds, DmaBufPlane, PlaneVec},
drm::Drm,
Modifier, LINEAR_MODIFIER,
},
},
std::{ptr, rc::Rc},
thiserror::Error,
uapi::{
c,
c::{
ioctl, mmap, munmap, F_SEAL_SHRINK, MAP_SHARED, MFD_ALLOW_SEALING, O_RDONLY, PROT_READ,
PROT_WRITE,
},
map_err, open, OwnedFd, _IOW,
},
};
#[derive(Debug, Error)]
pub enum UdmabufError {
#[error("Could not open /dev/udmabuf")]
Open(#[source] OsError),
#[error("Only the linear modifier can be allocated")]
Modifier,
#[error("Format {0} is not supported")]
Format(&'static str),
#[error("Could not create a memfd")]
Memfd(#[source] OsError),
#[error("Size calculation overflowed")]
Overflow,
#[error("Could not resize the memfd")]
Truncate(#[source] OsError),
#[error("Could not seal the memfd")]
Seal(#[source] OsError),
#[error("Could not create a dmabuf")]
CreateDmabuf(#[source] OsError),
#[error("Only a single plane is supported")]
Planes,
#[error("Stride is invalid")]
Stride,
#[error("Could not stat the dmabuf")]
Stat(#[source] OsError),
#[error("Dmabuf is too small for required size")]
Size,
#[error("Could not map dmabuf")]
Map(#[source] OsError),
}
pub struct Udmabuf {
fd: OwnedFd,
}
impl Udmabuf {
pub fn new() -> Result<Self, UdmabufError> {
let fd = match open("/dev/udmabuf", O_RDONLY, 0) {
Ok(b) => b,
Err(e) => return Err(UdmabufError::Open(e.into())),
};
Ok(Self { fd })
}
}
impl Allocator for Udmabuf {
fn drm(&self) -> Option<&Drm> {
None
}
fn create_bo(
&self,
dma_buf_ids: &DmaBufIds,
width: i32,
height: i32,
format: &'static Format,
modifiers: &[Modifier],
_usage: BufferUsage,
) -> Result<Rc<dyn BufferObject>, AllocatorError> {
if !modifiers.contains(&LINEAR_MODIFIER) {
return Err(UdmabufError::Modifier.into());
}
let Some(shm_info) = &format.shm_info else {
return Err(UdmabufError::Format(format.name).into());
};
let height = height as u64;
let width = width as u64;
if height > 1 << 16 || width > 1 << 16 {
return Err(UdmabufError::Overflow.into());
}
let stride_mask = 255;
let stride = (width * shm_info.bpp as u64 + stride_mask) & !stride_mask;
let size_mask = page_size() as u64 - 1;
let size = (height * stride + size_mask) & !size_mask;
let memfd = match uapi::memfd_create("udmabuf", MFD_ALLOW_SEALING) {
Ok(f) => f,
Err(e) => return Err(UdmabufError::Memfd(e.into()).into()),
};
if let Err(e) = uapi::ftruncate(memfd.raw(), size as _) {
return Err(UdmabufError::Truncate(e.into()).into());
}
if let Err(e) = uapi::fcntl_add_seals(memfd.raw(), F_SEAL_SHRINK) {
return Err(UdmabufError::Seal(e.into()).into());
}
let mut cmd = udmabuf_create {
memfd: memfd.raw() as u32,
flags: 0,
offset: 0,
size: size as u64,
};
let dmabuf = unsafe { ioctl(self.fd.raw(), UDMABUF_CREATE, &mut cmd) };
let dmabuf = match map_err!(dmabuf) {
Ok(d) => OwnedFd::new(d),
Err(e) => return Err(UdmabufError::CreateDmabuf(e.into()).into()),
};
let mut planes = PlaneVec::new();
planes.push(DmaBufPlane {
offset: 0,
stride: stride as _,
fd: Rc::new(dmabuf),
});
let dmabuf = DmaBuf {
id: dma_buf_ids.next(),
width: width as _,
height: height as _,
format,
modifier: LINEAR_MODIFIER,
planes,
};
Ok(Rc::new(UdmabufBo {
buf: dmabuf,
size: size as _,
}))
}
fn import_dmabuf(
&self,
dmabuf: &DmaBuf,
_usage: BufferUsage,
) -> Result<Rc<dyn BufferObject>, AllocatorError> {
if dmabuf.planes.len() != 1 {
return Err(UdmabufError::Planes.into());
}
if dmabuf.modifier != LINEAR_MODIFIER {
return Err(UdmabufError::Modifier.into());
}
let plane = &dmabuf.planes[0];
let Some(shm_info) = &dmabuf.format.shm_info else {
return Err(UdmabufError::Format(dmabuf.format.name).into());
};
let height = dmabuf.height as u64;
let width = dmabuf.width as u64;
let stride = plane.stride as u64;
let offset = plane.offset as u64;
if height > 1 << 16 || width > 1 << 16 {
return Err(UdmabufError::Overflow.into());
}
if stride < width * shm_info.bpp as u64 {
return Err(UdmabufError::Stride.into());
}
let size = offset + stride * height;
if usize::try_from(size).is_err() {
return Err(UdmabufError::Overflow.into());
}
let stat = match uapi::fstat(plane.fd.raw()) {
Ok(s) => s,
Err(e) => return Err(UdmabufError::Stat(e.into()).into()),
};
if (stat.st_size as u64) < size {
return Err(UdmabufError::Size.into());
}
Ok(Rc::new(UdmabufBo {
buf: dmabuf.clone(),
size: size as usize,
}))
}
}
struct UdmabufBo {
buf: DmaBuf,
size: usize,
}
impl BufferObject for UdmabufBo {
fn dmabuf(&self) -> &DmaBuf {
&self.buf
}
fn map_read(self: Rc<Self>) -> Result<Box<dyn MappedBuffer>, AllocatorError> {
self.map_write()
}
fn map_write(self: Rc<Self>) -> Result<Box<dyn MappedBuffer>, AllocatorError> {
let plane = &self.buf.planes[0];
unsafe {
let res = mmap(
ptr::null_mut(),
self.size,
PROT_READ | PROT_WRITE,
MAP_SHARED,
plane.fd.raw(),
0,
);
if res == c::MAP_FAILED {
return Err(UdmabufError::Map(OsError::default()).into());
}
let offset = plane.offset as _;
let data =
std::slice::from_raw_parts_mut((res as *mut u8).add(offset), self.size - offset);
Ok(Box::new(UdmabufMap {
data,
stride: plane.stride as _,
ptr: res,
len: self.size,
_bo: self,
}))
}
}
}
struct UdmabufMap {
_bo: Rc<UdmabufBo>,
data: *mut [u8],
stride: i32,
ptr: *mut c::c_void,
len: usize,
}
impl Drop for UdmabufMap {
fn drop(&mut self) {
unsafe {
let res = munmap(self.ptr, self.len);
if let Err(e) = map_err!(res) {
log::error!("Could not unmap udmabuf: {}", OsError::from(e));
}
}
}
}
impl MappedBuffer for UdmabufMap {
unsafe fn data(&self) -> &[u8] {
&*self.data
}
fn data_ptr(&self) -> *mut u8 {
self.data as _
}
fn stride(&self) -> i32 {
self.stride
}
}
impl From<UdmabufError> for AllocatorError {
fn from(value: UdmabufError) -> Self {
Self(Box::new(value))
}
}
#[allow(non_camel_case_types)]
#[repr(C)]
struct udmabuf_create {
memfd: u32,
flags: u32,
offset: u64,
size: u64,
}
const UDMABUF_CREATE: u64 = _IOW::<udmabuf_create>(b'u' as u64, 0x42);

View file

@ -2,6 +2,10 @@
use {
crate::{
allocator::{
Allocator, AllocatorError, BufferObject, BufferUsage, MappedBuffer, BO_USE_CURSOR,
BO_USE_LINEAR, BO_USE_PROTECTED, BO_USE_RENDERING, BO_USE_SCANOUT, BO_USE_WRITE,
},
format::{formats, Format},
utils::oserror::OsError,
video::{
@ -38,6 +42,12 @@ pub enum GbmError {
NoModifier,
}
impl From<GbmError> for AllocatorError {
fn from(value: GbmError) -> Self {
Self(Box::new(value))
}
}
pub type Device = u8;
type Bo = u8;
@ -151,17 +161,16 @@ pub struct GbmBoMap {
stride: i32,
}
impl GbmBoMap {
pub unsafe fn data(&self) -> &[u8] {
impl MappedBuffer for GbmBoMap {
unsafe fn data(&self) -> &[u8] {
&*self.data
}
#[cfg_attr(not(feature = "it"), allow(dead_code))]
pub fn data_ptr(&self) -> *mut u8 {
fn data_ptr(&self) -> *mut u8 {
self.data as _
}
pub fn stride(&self) -> i32 {
fn stride(&self) -> i32 {
self.stride
}
}
@ -290,6 +299,56 @@ impl GbmDevice {
}
}
impl Allocator for GbmDevice {
fn drm(&self) -> Option<&Drm> {
Some(&self.drm)
}
fn create_bo(
&self,
dma_buf_ids: &DmaBufIds,
width: i32,
height: i32,
format: &'static Format,
modifiers: &[Modifier],
usage: BufferUsage,
) -> Result<Rc<dyn BufferObject>, AllocatorError> {
let usage = map_usage(usage);
self.create_bo(dma_buf_ids, width, height, format, modifiers, usage)
.map(|v| Rc::new(v) as _)
.map_err(|v| v.into())
}
fn import_dmabuf(
&self,
dmabuf: &DmaBuf,
usage: BufferUsage,
) -> Result<Rc<dyn BufferObject>, AllocatorError> {
let usage = map_usage(usage);
self.import_dmabuf(dmabuf, usage)
.map(|v| Rc::new(v) as _)
.map_err(|v| v.into())
}
}
fn map_usage(usage: BufferUsage) -> u32 {
let mut gbm = 0;
macro_rules! map {
($bu:ident to $gbu:ident) => {
if usage.contains($bu) {
gbm |= $gbu;
}
};
}
map!(BO_USE_SCANOUT to GBM_BO_USE_SCANOUT);
map!(BO_USE_CURSOR to GBM_BO_USE_CURSOR);
map!(BO_USE_RENDERING to GBM_BO_USE_RENDERING);
map!(BO_USE_WRITE to GBM_BO_USE_WRITE);
map!(BO_USE_LINEAR to GBM_BO_USE_LINEAR);
map!(BO_USE_PROTECTED to GBM_BO_USE_PROTECTED);
gbm
}
impl Drop for GbmDevice {
fn drop(&mut self) {
unsafe {
@ -299,15 +358,10 @@ impl Drop for GbmDevice {
}
impl GbmBo {
pub fn dmabuf(&self) -> &DmaBuf {
&self.dmabuf
}
pub fn map_read(self: &Rc<Self>) -> Result<GbmBoMap, GbmError> {
self.map2(GBM_BO_TRANSFER_READ)
}
#[cfg_attr(not(feature = "it"), allow(dead_code))]
pub fn map_write(self: &Rc<Self>) -> Result<GbmBoMap, GbmError> {
self.map2(GBM_BO_TRANSFER_READ_WRITE)
}
@ -340,6 +394,24 @@ impl GbmBo {
}
}
impl BufferObject for GbmBo {
fn dmabuf(&self) -> &DmaBuf {
&self.dmabuf
}
fn map_read(self: Rc<Self>) -> Result<Box<dyn MappedBuffer>, AllocatorError> {
GbmBo::map_read(&self)
.map(|v| Box::new(v) as _)
.map_err(|v| v.into())
}
fn map_write(self: Rc<Self>) -> Result<Box<dyn MappedBuffer>, AllocatorError> {
GbmBo::map_write(&self)
.map(|v| Box::new(v) as _)
.map_err(|v| v.into())
}
}
impl Drop for GbmBoMap {
fn drop(&mut self) {
unsafe {

View file

@ -14,3 +14,19 @@ event dmabuf {
event error {
msg: str,
}
event drm_dev (since = 6) {
drm_dev: fd,
}
event plane (since = 6) {
fd: fd,
offset: u32,
stride: u32,
}
event dmabuf2 (since = 6) {
width: i32,
height: i32,
modifier: pod(u64),
}