From 73bf4465e2e7c14e25bfa874d1b37b867ed42b0c Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 3 Sep 2025 17:46:33 +0200 Subject: [PATCH] it: add fifo test --- src/it/test_client.rs | 8 +- src/it/test_ifs.rs | 1 + src/it/test_ifs/test_fifo_manager.rs | 76 +++++++++++++ src/it/test_ifs/test_registry.rs | 11 ++ src/it/test_transport.rs | 1 + src/it/testrun.rs | 1 + src/it/tests.rs | 2 + src/it/tests/t0050_fifo.rs | 153 +++++++++++++++++++++++++++ src/state.rs | 14 +++ 9 files changed, 264 insertions(+), 3 deletions(-) create mode 100644 src/it/test_ifs/test_fifo_manager.rs create mode 100644 src/it/tests/t0050_fifo.rs diff --git a/src/it/test_client.rs b/src/it/test_client.rs index 849344ad..49f2b8dc 100644 --- a/src/it/test_client.rs +++ b/src/it/test_client.rs @@ -8,9 +8,10 @@ use { test_ifs::{ test_compositor::TestCompositor, test_cursor_shape_manager::TestCursorShapeManager, test_data_device_manager::TestDataDeviceManager, - test_jay_compositor::TestJayCompositor, test_keyboard::TestKeyboard, - test_pointer::TestPointer, test_registry::TestRegistry, test_seat::TestSeat, - test_shm::TestShm, test_single_pixel_buffer_manager::TestSinglePixelBufferManager, + test_fifo_manager::TestFifoManager, test_jay_compositor::TestJayCompositor, + test_keyboard::TestKeyboard, test_pointer::TestPointer, + test_registry::TestRegistry, test_seat::TestSeat, test_shm::TestShm, + test_single_pixel_buffer_manager::TestSinglePixelBufferManager, test_subcompositor::TestSubcompositor, test_viewporter::TestViewporter, test_xdg_activation::TestXdgActivation, test_xdg_base::TestXdgWmBase, }, @@ -38,6 +39,7 @@ pub struct TestClient { pub activation: Rc, pub data_device_manager: Rc, pub cursor_shape_manager: Rc, + pub fifo_manager: Rc, } pub struct DefaultSeat { diff --git a/src/it/test_ifs.rs b/src/it/test_ifs.rs index ec7debca..f25bb4da 100644 --- a/src/it/test_ifs.rs +++ b/src/it/test_ifs.rs @@ -20,6 +20,7 @@ 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_fifo_manager; pub mod test_input_method; pub mod test_input_method_keyboard_grab; pub mod test_input_method_manager; diff --git a/src/it/test_ifs/test_fifo_manager.rs b/src/it/test_ifs/test_fifo_manager.rs new file mode 100644 index 00000000..c58583b2 --- /dev/null +++ b/src/it/test_ifs/test_fifo_manager.rs @@ -0,0 +1,76 @@ +use { + crate::{ + it::{ + test_error::TestError, test_ifs::test_surface::TestSurface, test_object::TestObject, + test_transport::TestTransport, + }, + wire::{WpFifoManagerV1Id, WpFifoV1Id, wp_fifo_manager_v1::*, wp_fifo_v1}, + }, + std::rc::Rc, +}; + +pub struct TestFifoManager { + pub id: WpFifoManagerV1Id, + pub tran: Rc, +} + +pub struct TestFifo { + pub id: WpFifoV1Id, + pub tran: Rc, +} + +impl TestFifoManager { + pub fn new(tran: &Rc) -> Self { + Self { + id: tran.id(), + tran: tran.clone(), + } + } + + pub fn get_fifo(&self, surface: &TestSurface) -> Result, TestError> { + let obj = Rc::new(TestFifo { + id: self.tran.id(), + tran: self.tran.clone(), + }); + self.tran.send(GetFifo { + self_id: self.id, + id: obj.id, + surface: surface.id, + })?; + self.tran.add_obj(obj.clone())?; + Ok(obj) + } + + #[expect(dead_code)] + pub fn destroy(&self) -> Result<(), TestError> { + self.tran.send(Destroy { self_id: self.id })?; + Ok(()) + } +} + +impl TestFifo { + pub fn set_barrier(&self) -> Result<(), TestError> { + self.tran.send(wp_fifo_v1::SetBarrier { self_id: self.id }) + } + + pub fn wait_barrier(&self) -> Result<(), TestError> { + self.tran.send(wp_fifo_v1::WaitBarrier { self_id: self.id }) + } + + #[expect(dead_code)] + pub fn destroy(&self) -> Result<(), TestError> { + self.tran.send(wp_fifo_v1::Destroy { self_id: self.id })?; + Ok(()) + } +} + +test_object! { + TestFifoManager, WpFifoManagerV1; +} + +test_object! { + TestFifo, WpFifoV1; +} + +impl TestObject for TestFifoManager {} +impl TestObject for TestFifo {} diff --git a/src/it/test_ifs/test_registry.rs b/src/it/test_ifs/test_registry.rs index cb012784..3e1d8003 100644 --- a/src/it/test_ifs/test_registry.rs +++ b/src/it/test_ifs/test_registry.rs @@ -11,6 +11,7 @@ use { test_data_control_manager::TestDataControlManager, test_data_device_manager::TestDataDeviceManager, test_dmabuf::TestDmabuf, test_ext_foreign_toplevel_list::TestExtForeignToplevelList, + test_fifo_manager::TestFifoManager, test_input_method_manager::TestInputMethodManager, test_jay_compositor::TestJayCompositor, test_shm::TestShm, test_single_pixel_buffer_manager::TestSinglePixelBufferManager, @@ -60,6 +61,7 @@ pub struct TestRegistrySingletons { pub zwp_input_method_manager_v2: u32, pub zwp_text_input_manager_v3: u32, pub wl_fixes: u32, + pub wp_fifo_manager_v1: u32, } pub struct TestRegistry { @@ -88,6 +90,7 @@ pub struct TestRegistry { pub input_method_manager: CloneCell>>, pub text_input_manager: CloneCell>>, pub wl_fixes: CloneCell>>, + pub fifo_manager: CloneCell>>, pub seats: CopyHashMap>, } @@ -160,6 +163,7 @@ impl TestRegistry { zwp_input_method_manager_v2, zwp_text_input_manager_v3, wl_fixes, + wp_fifo_manager_v1, }; self.singletons.set(Some(singletons.clone())); Ok(singletons) @@ -276,6 +280,13 @@ impl TestRegistry { TestTextInputManager ); create_singleton!(get_wl_fixes, wl_fixes, wl_fixes, 1, TestWlFixes); + create_singleton!( + get_fifo_manager, + fifo_manager, + wp_fifo_manager_v1, + 1, + TestFifoManager + ); pub fn bind( &self, diff --git a/src/it/test_transport.rs b/src/it/test_transport.rs index 2afefeca..b0ae8e3e 100644 --- a/src/it/test_transport.rs +++ b/src/it/test_transport.rs @@ -74,6 +74,7 @@ impl TestTransport { input_method_manager: Default::default(), text_input_manager: Default::default(), wl_fixes: Default::default(), + fifo_manager: Default::default(), seats: Default::default(), }); self.send(wl_display::GetRegistry { diff --git a/src/it/testrun.rs b/src/it/testrun.rs index 827be503..e0496761 100644 --- a/src/it/testrun.rs +++ b/src/it/testrun.rs @@ -91,6 +91,7 @@ impl TestRun { activation: registry.get_activation().await?, data_device_manager: registry.get_data_device_manager().await?, cursor_shape_manager: registry.get_cursor_shape_manager().await?, + fifo_manager: registry.get_fifo_manager().await?, registry, })) } diff --git a/src/it/tests.rs b/src/it/tests.rs index 609f1224..a9986f55 100644 --- a/src/it/tests.rs +++ b/src/it/tests.rs @@ -80,6 +80,7 @@ mod t0046_buffer_release; mod t0047_surface_damage; mod t0048_frame_callback; mod t0049_surface_damage_backend; +mod t0050_fifo; pub trait TestCase: Sync { fn name(&self) -> &'static str; @@ -148,5 +149,6 @@ pub fn tests() -> Vec<&'static dyn TestCase> { t0047_surface_damage, t0048_frame_callback, t0049_surface_damage_backend, + t0050_fifo, } } diff --git a/src/it/tests/t0050_fifo.rs b/src/it/tests/t0050_fifo.rs new file mode 100644 index 00000000..dd011138 --- /dev/null +++ b/src/it/tests/t0050_fifo.rs @@ -0,0 +1,153 @@ +use { + crate::{ + it::{ + test_error::{TestError, TestResult}, + testrun::TestRun, + }, + theme::Color, + }, + std::rc::Rc, +}; + +testcase!(); + +/// Test wp_fifo_v1 protocol implementation +/// This test verifies that the compositor correctly implements the fifo-v1 protocol +/// for synchronizing surface updates with display refresh cycles. +async fn test(run: Rc) -> TestResult { + run.backend.install_default()?; + let client = run.create_client().await?; + + // Create a visible window so fifo constraints can be tested + let window = client.create_window().await?; + window.map().await?; + client.sync().await; + + let fifo_manager = &client.fifo_manager; + let surface = &window.surface.surface; + let connector_id = run.backend.default_connector.id; + + // Get a fifo object for the surface + let fifo = fifo_manager.get_fifo(surface)?; + client.sync().await; + + // Test 1: Basic fifo barrier functionality without wait_barrier + // This should not block the commit - the old buffer should be released immediately + fifo.set_barrier()?; + let buffer1 = client.spbm.create_buffer(Color::from_srgb(255, 0, 0))?; + surface.attach(buffer1.id)?; + surface.commit()?; + client.sync().await; + + // Test 1a: Critical test - without wait_barrier, commit should be applied immediately + let buffer1a = client.spbm.create_buffer(Color::from_srgb(255, 128, 0))?; + + // Reset buffer tracking to detect when buffer1 gets released + buffer1.released.set(false); + + // Attach new buffer and commit WITHOUT wait_barrier + surface.attach(buffer1a.id)?; + surface.commit()?; + client.sync().await; + + // WITHOUT wait_barrier, the commit should be applied immediately even if barrier is set + // So buffer1 should be released immediately, without needing a vblank + if !buffer1.released.get() { + return Err(TestError::new( + "Without wait_barrier, commit should be applied immediately - buffer1 was not released", + )); + } + + // Test 2: Critical test - wait_barrier SHOULD block commit until next after_latch + // This contrasts with Test 1a above where commits were applied immediately + + // Set the barrier and immediately test wait_barrier (without intermediate commits that might clear it) + fifo.set_barrier()?; + fifo.wait_barrier()?; + + let buffer2_current = client.spbm.create_buffer(Color::from_srgb(0, 255, 0))?; + let buffer2_next = client.spbm.create_buffer(Color::from_srgb(0, 0, 255))?; + + // First attach current buffer + surface.attach(buffer2_current.id)?; + surface.commit()?; + client.sync().await; + + // Reset tracking for the buffer we want to monitor + buffer2_current.released.set(false); + + // Now attach the new buffer - this should trigger the wait_barrier blocking + surface.attach(buffer2_next.id)?; + surface.commit()?; + client.sync().await; + + // CRITICAL: The commit should be blocked, so buffer2_current should NOT be released yet + if buffer2_current.released.get() { + return Err(TestError::new( + "wait_barrier did not block the commit - buffer2_current was released immediately", + )); + } + + // The commit was successfully blocked! This proves wait_barrier works. + // Now trigger after_latch to clear the barrier and apply the queued commit + run.state.latch(connector_id); + client.sync().await; + + // After after_latch, the barrier should be cleared and the commit applied + if !buffer2_current.released.get() { + return Err(TestError::new( + "after_latch should have cleared barrier and applied commit - buffer2_current was not released", + )); + } + + // Test 3: Test tearing mode - barrier should be cleared on vblank instead of immediately + fifo.set_barrier()?; + fifo.wait_barrier()?; + + let buffer3_current = client.spbm.create_buffer(Color::from_srgb(128, 128, 0))?; + let buffer3_next = client.spbm.create_buffer(Color::from_srgb(0, 128, 128))?; + + // First attach current buffer + surface.attach(buffer3_current.id)?; + surface.commit()?; + client.sync().await; + + // Reset tracking for the buffer we want to monitor + buffer3_current.released.set(false); + + // Now attach the new buffer - this should trigger the wait_barrier blocking + surface.attach(buffer3_next.id)?; + surface.commit()?; + client.sync().await; + + // Verify the commit is blocked + if buffer3_current.released.get() { + return Err(TestError::new( + "wait_barrier did not block the commit in tearing test - buffer3_current was released immediately", + )); + } + + // Trigger latch with tearing=true - this should defer clearing to vblank + run.state.latch_tearing(connector_id); + client.sync().await; + + // With tearing=true, the barrier should NOT be cleared yet, commit should still be blocked + if buffer3_current.released.get() { + return Err(TestError::new( + "In tearing mode, latch should not clear barrier immediately - buffer3_current was released", + )); + } + + // Now trigger vblank - this should clear the barrier and apply the commit + run.state.vblank(connector_id); + client.sync().await; + + // After vblank, the commit should be applied + if !buffer3_current.released.get() { + return Err(TestError::new( + "vblank should have cleared barrier and applied commit - buffer3_current was not released", + )); + } + + Ok(()) +} diff --git a/src/state.rs b/src/state.rs index 56f38271..e8a7850b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1445,6 +1445,20 @@ impl State { } } + #[cfg(feature = "it")] + pub fn latch(&self, connector: ConnectorId) { + if let Some(output) = self.root.outputs.get(&connector) { + output.latched(false); + } + } + + #[cfg(feature = "it")] + pub fn latch_tearing(&self, connector: ConnectorId) { + if let Some(output) = self.root.outputs.get(&connector) { + output.latched(true); + } + } + #[cfg(feature = "it")] pub async fn idle(&self) { loop {