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

@ -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 {