From b966a73682422b95c845abab0a171c41543c7137 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 3 Apr 2024 15:52:25 +0200 Subject: [PATCH] it: test direct-scanout feedback --- src/it/test_backend.rs | 7 + src/it/test_ifs.rs | 2 + src/it/test_ifs/test_dmabuf.rs | 72 ++++++++++ src/it/test_ifs/test_dmabuf_feedback.rs | 147 ++++++++++++++++++++ src/it/test_ifs/test_registry.rs | 6 +- src/it/test_transport.rs | 1 + src/it/tests.rs | 2 + src/it/tests/t0034_workspace_restoration.rs | 1 + src/it/tests/t0035_scanout_feedback.rs | 65 +++++++++ 9 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 src/it/test_ifs/test_dmabuf.rs create mode 100644 src/it/test_ifs/test_dmabuf_feedback.rs create mode 100644 src/it/tests/t0035_scanout_feedback.rs diff --git a/src/it/test_backend.rs b/src/it/test_backend.rs index 92e587c8..0c75066b 100644 --- a/src/it/test_backend.rs +++ b/src/it/test_backend.rs @@ -8,6 +8,7 @@ use { ScrollAxis, TransformMatrix, }, compositor::TestFuture, + drm_feedback::DrmFeedback, fixed::Fixed, gfx_api::GfxError, it::test_error::TestResult, @@ -57,6 +58,7 @@ impl TestBackend { idx: 1, }, events: Default::default(), + feedback: Default::default(), }); let default_mouse = Rc::new(TestBackendMouse { common: TestInputDeviceCommon { @@ -219,6 +221,7 @@ pub struct TestConnector { pub id: ConnectorId, pub kernel_id: ConnectorKernelId, pub events: OnChange, + pub feedback: CloneCell>>, } impl Connector for TestConnector { @@ -249,6 +252,10 @@ impl Connector for TestConnector { fn set_mode(&self, _mode: Mode) { // todo } + + fn drm_feedback(&self) -> Option> { + self.feedback.get() + } } pub struct TestMouseClick { diff --git a/src/it/test_ifs.rs b/src/it/test_ifs.rs index c2e2d576..8948b0ec 100644 --- a/src/it/test_ifs.rs +++ b/src/it/test_ifs.rs @@ -14,6 +14,8 @@ pub mod test_data_device_manager; pub mod test_data_offer; pub mod test_data_source; pub mod test_display; +pub mod test_dmabuf; +pub mod test_dmabuf_feedback; pub mod test_ext_foreign_toplevel_handle; pub mod test_ext_foreign_toplevel_list; pub mod test_jay_compositor; diff --git a/src/it/test_ifs/test_dmabuf.rs b/src/it/test_ifs/test_dmabuf.rs new file mode 100644 index 00000000..21f31103 --- /dev/null +++ b/src/it/test_ifs/test_dmabuf.rs @@ -0,0 +1,72 @@ +use { + crate::{ + it::{ + test_error::TestResult, + test_ifs::{test_dmabuf_feedback::TestDmabufFeedback, test_surface::TestSurface}, + test_object::TestObject, + test_transport::TestTransport, + }, + wire::{zwp_linux_dmabuf_v1::*, ZwpLinuxDmabufV1Id}, + }, + std::{cell::Cell, rc::Rc}, +}; + +pub struct TestDmabuf { + pub id: ZwpLinuxDmabufV1Id, + pub tran: Rc, + pub destroyed: Cell, +} + +impl TestDmabuf { + pub fn new(tran: &Rc) -> Self { + Self { + id: tran.id(), + tran: tran.clone(), + destroyed: Cell::new(false), + } + } + + pub fn destroy(&self) -> TestResult { + if !self.destroyed.replace(true) { + self.tran.send(Destroy { self_id: self.id })?; + } + Ok(()) + } + + #[allow(dead_code)] + pub fn get_default_feedback(&self) -> TestResult> { + let obj = Rc::new(TestDmabufFeedback::new(&self.tran)); + self.tran.add_obj(obj.clone())?; + self.tran.send(GetDefaultFeedback { + self_id: self.id, + id: obj.id, + })?; + Ok(obj) + } + + pub fn get_surface_feedback( + &self, + surface: &TestSurface, + ) -> TestResult> { + let obj = Rc::new(TestDmabufFeedback::new(&self.tran)); + self.tran.add_obj(obj.clone())?; + self.tran.send(GetSurfaceFeedback { + self_id: self.id, + id: obj.id, + surface: surface.id, + })?; + Ok(obj) + } +} + +impl Drop for TestDmabuf { + fn drop(&mut self) { + let _ = self.destroy(); + } +} + +test_object! { + TestDmabuf, ZwpLinuxDmabufV1; +} + +impl TestObject for TestDmabuf {} diff --git a/src/it/test_ifs/test_dmabuf_feedback.rs b/src/it/test_ifs/test_dmabuf_feedback.rs new file mode 100644 index 00000000..1abb4050 --- /dev/null +++ b/src/it/test_ifs/test_dmabuf_feedback.rs @@ -0,0 +1,147 @@ +use { + crate::{ + it::{ + test_error::TestResult, test_object::TestObject, test_transport::TestTransport, + test_utils::test_expected_event::TEEH, testrun::ParseFull, + }, + utils::buffd::MsgParser, + wire::{zwp_linux_dmabuf_feedback_v1::*, ZwpLinuxDmabufFeedbackV1Id}, + }, + std::{ + cell::{Cell, RefCell}, + mem, + ops::DerefMut, + rc::Rc, + }, + uapi::{c, OwnedFd}, +}; + +pub struct TestDmabufFeedback { + pub id: ZwpLinuxDmabufFeedbackV1Id, + pub tran: Rc, + pub destroyed: Cell, + pub feedback: TEEH, + pub pending_feedback: RefCell, +} + +#[derive(Default)] +pub struct PendingFeedback { + pub format_table: Option>, + pub format_table_size: usize, + pub main_device: c::dev_t, + pub tranches: Vec, + pub pending_tranche: Tranche, +} + +pub struct Feedback { + pub format_table: Rc, + pub format_table_size: usize, + pub main_device: c::dev_t, + pub tranches: Vec, +} + +#[derive(Default)] +pub struct Tranche { + pub target_device: c::dev_t, + pub formats: Vec, + pub flags: u32, +} + +impl TestDmabufFeedback { + pub fn new(tran: &Rc) -> Self { + Self { + id: tran.id(), + tran: tran.clone(), + destroyed: Cell::new(false), + feedback: Rc::new(Default::default()), + pending_feedback: RefCell::new(Default::default()), + } + } + + pub fn destroy(&self) -> TestResult { + if !self.destroyed.replace(true) { + self.tran.send(Destroy { self_id: self.id })?; + } + Ok(()) + } + + fn handle_done(&self, parser: MsgParser<'_, '_>) -> TestResult { + let _ev = Done::parse_full(parser)?; + let mut pending = mem::take(self.pending_feedback.borrow_mut().deref_mut()); + self.feedback.push(Feedback { + format_table: match pending.format_table.take() { + None => bail!("compositor did not send format table"), + Some(ft) => ft, + }, + format_table_size: pending.format_table_size, + main_device: pending.main_device, + tranches: pending.tranches, + }); + Ok(()) + } + + fn handle_format_table(&self, parser: MsgParser<'_, '_>) -> TestResult { + let ev = FormatTable::parse_full(parser)?; + let pending = &mut *self.pending_feedback.borrow_mut(); + pending.format_table = Some(ev.fd); + pending.format_table_size = ev.size as _; + Ok(()) + } + + fn handle_main_device(&self, parser: MsgParser<'_, '_>) -> TestResult { + let ev = MainDevice::parse_full(parser)?; + let pending = &mut *self.pending_feedback.borrow_mut(); + pending.main_device = ev.device; + Ok(()) + } + + fn handle_tranche_done(&self, parser: MsgParser<'_, '_>) -> TestResult { + let _ev = TrancheDone::parse_full(parser)?; + let pending = &mut *self.pending_feedback.borrow_mut(); + pending + .tranches + .push(mem::take(&mut pending.pending_tranche)); + Ok(()) + } + + fn handle_tranche_target_device(&self, parser: MsgParser<'_, '_>) -> TestResult { + let ev = TrancheTargetDevice::parse_full(parser)?; + let pending = &mut *self.pending_feedback.borrow_mut(); + pending.pending_tranche.target_device = ev.device; + Ok(()) + } + + fn handle_tranche_formats(&self, parser: MsgParser<'_, '_>) -> TestResult { + let ev = TrancheFormats::parse_full(parser)?; + let pending = &mut *self.pending_feedback.borrow_mut(); + pending.pending_tranche.formats = ev.indices.iter().copied().map(|v| v as usize).collect(); + Ok(()) + } + + fn handle_tranche_flags(&self, parser: MsgParser<'_, '_>) -> TestResult { + let ev = TrancheFlags::parse_full(parser)?; + let pending = &mut *self.pending_feedback.borrow_mut(); + pending.pending_tranche.flags = ev.flags; + Ok(()) + } +} + +impl Drop for TestDmabufFeedback { + fn drop(&mut self) { + let _ = self.destroy(); + } +} + +test_object! { + TestDmabufFeedback, ZwpLinuxDmabufFeedbackV1; + + DONE => handle_done, + FORMAT_TABLE => handle_format_table, + MAIN_DEVICE => handle_main_device, + TRANCHE_DONE => handle_tranche_done, + TRANCHE_TARGET_DEVICE => handle_tranche_target_device, + TRANCHE_FORMATS => handle_tranche_formats, + TRANCHE_FLAGS => handle_tranche_flags, +} + +impl TestObject for TestDmabufFeedback {} diff --git a/src/it/test_ifs/test_registry.rs b/src/it/test_ifs/test_registry.rs index 496f8606..1e8c917d 100644 --- a/src/it/test_ifs/test_registry.rs +++ b/src/it/test_ifs/test_registry.rs @@ -8,7 +8,7 @@ use { test_compositor::TestCompositor, test_content_type_manager::TestContentTypeManager, test_cursor_shape_manager::TestCursorShapeManager, test_data_control_manager::TestDataControlManager, - test_data_device_manager::TestDataDeviceManager, + test_data_device_manager::TestDataDeviceManager, test_dmabuf::TestDmabuf, test_ext_foreign_toplevel_list::TestExtForeignToplevelList, test_jay_compositor::TestJayCompositor, test_shm::TestShm, test_single_pixel_buffer_manager::TestSinglePixelBufferManager, @@ -47,6 +47,7 @@ pub struct TestRegistrySingletons { pub wp_linux_drm_syncobj_manager_v1: u32, pub wp_content_type_manager_v1: u32, pub zwlr_data_control_manager_v1: u32, + pub zwp_linux_dmabuf_v1: u32, } pub struct TestRegistry { @@ -68,6 +69,7 @@ pub struct TestRegistry { pub syncobj_manager: CloneCell>>, pub content_type_manager: CloneCell>>, pub data_control_manager: CloneCell>>, + pub dmabuf: CloneCell>>, pub seats: CopyHashMap>, } @@ -133,6 +135,7 @@ impl TestRegistry { wp_linux_drm_syncobj_manager_v1, wp_content_type_manager_v1, zwlr_data_control_manager_v1, + zwp_linux_dmabuf_v1, }; self.singletons.set(Some(singletons.clone())); Ok(singletons) @@ -212,6 +215,7 @@ impl TestRegistry { 2, TestDataControlManager ); + create_singleton!(get_dmabuf, dmabuf, zwp_linux_dmabuf_v1, 5, TestDmabuf); pub fn bind( &self, diff --git a/src/it/test_transport.rs b/src/it/test_transport.rs index d9336f28..9b7f85a2 100644 --- a/src/it/test_transport.rs +++ b/src/it/test_transport.rs @@ -66,6 +66,7 @@ impl TestTransport { syncobj_manager: Default::default(), content_type_manager: Default::default(), data_control_manager: Default::default(), + dmabuf: Default::default(), seats: Default::default(), }); self.send(wl_display::GetRegistry { diff --git a/src/it/tests.rs b/src/it/tests.rs index 6a59fd4c..1ceadc89 100644 --- a/src/it/tests.rs +++ b/src/it/tests.rs @@ -66,6 +66,7 @@ mod t0032_content_type; mod t0032_data_control; mod t0033_float_size_memoization; mod t0034_workspace_restoration; +mod t0035_scanout_feedback; pub trait TestCase: Sync { fn name(&self) -> &'static str; @@ -119,5 +120,6 @@ pub fn tests() -> Vec<&'static dyn TestCase> { t0032_data_control, t0033_float_size_memoization, t0034_workspace_restoration, + t0035_scanout_feedback, } } diff --git a/src/it/tests/t0034_workspace_restoration.rs b/src/it/tests/t0034_workspace_restoration.rs index 3f253e65..7699fc55 100644 --- a/src/it/tests/t0034_workspace_restoration.rs +++ b/src/it/tests/t0034_workspace_restoration.rs @@ -28,6 +28,7 @@ async fn test(run: Rc) -> TestResult { idx: 2, }, events: Default::default(), + feedback: Default::default(), }); let new_monitor_info = MonitorInfo { modes: vec![], diff --git a/src/it/tests/t0035_scanout_feedback.rs b/src/it/tests/t0035_scanout_feedback.rs new file mode 100644 index 00000000..25585a24 --- /dev/null +++ b/src/it/tests/t0035_scanout_feedback.rs @@ -0,0 +1,65 @@ +use { + crate::{ + ifs::zwp_linux_dmabuf_feedback_v1::SCANOUT, + it::{ + test_error::{TestErrorExt, TestResult}, + testrun::TestRun, + }, + }, + std::rc::Rc, +}; + +testcase!(); + +async fn test(run: Rc) -> TestResult { + let ds = run.create_default_setup().await?; + + let scanout_feedback = { + let Some(base_fb) = run.state.drm_feedback.get() else { + bail!("no base fb"); + }; + let Some(index) = base_fb.shared.indices.keys().copied().next() else { + bail!("no formats"); + }; + let fb = base_fb + .for_scanout(&run.state.drm_feedback_ids, 1234, &[index]) + .unwrap() + .unwrap(); + Rc::new(fb) + }; + + ds.connector.feedback.set(Some(scanout_feedback.clone())); + + let client1 = run.create_client().await?; + let win1 = client1.create_window().await?; + let dmabuf = client1.registry.get_dmabuf().await?; + let feedback = dmabuf.get_surface_feedback(&win1.surface)?; + let feedback = feedback.feedback.expect()?; + win1.map2().await?; + + client1.sync().await; + let fb = feedback.last().with_context(|| "feedback 1")?; + tassert_eq!(fb.tranches.len(), 1); + tassert_eq!(fb.tranches[0].flags, 0); + + run.cfg.set_fullscreen(ds.seat.id(), true)?; + + client1.sync().await; + let fb = feedback.last().with_context(|| "feedback 2")?; + tassert_eq!(fb.tranches.len(), 2); + tassert_eq!( + fb.tranches[0].target_device, + scanout_feedback.tranches[0].device + ); + tassert_eq!(fb.tranches[0].flags, SCANOUT); + tassert_eq!(fb.tranches[1].flags, 0); + + run.cfg.set_fullscreen(ds.seat.id(), false)?; + + client1.sync().await; + let fb = feedback.last().with_context(|| "feedback 2")?; + tassert_eq!(fb.tranches.len(), 1); + tassert_eq!(fb.tranches[0].flags, 0); + + Ok(()) +}