From 8a5f1e1e37f5ed01c042dc8d7cfd304350de1089 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 3 Sep 2025 12:15:05 +0200 Subject: [PATCH 1/3] it: add frame callback test --- src/it/test_ifs/test_surface.rs | 20 +++++- src/it/tests.rs | 2 + src/it/tests/t0048_frame_callback.rs | 91 ++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 src/it/tests/t0048_frame_callback.rs diff --git a/src/it/test_ifs/test_surface.rs b/src/it/test_ifs/test_surface.rs index 6f15dbd1..107bca33 100644 --- a/src/it/test_ifs/test_surface.rs +++ b/src/it/test_ifs/test_surface.rs @@ -3,14 +3,14 @@ use { ifs::wl_surface::WlSurface, it::{ test_error::{TestError, TestResult}, - test_ifs::test_region::TestRegion, + test_ifs::{test_callback::TestCallback, test_region::TestRegion}, test_object::TestObject, test_transport::TestTransport, test_utils::test_expected_event::TEEH, testrun::ParseFull, }, utils::buffd::MsgParser, - wire::{WlBufferId, WlSurfaceId, wl_surface::*}, + wire::{WlBufferId, WlCallbackId, WlSurfaceId, wl_surface::*}, }, std::{cell::Cell, rc::Rc}, }; @@ -89,6 +89,22 @@ impl TestSurface { Ok(()) } + pub fn frame(&self) -> Result, TestError> { + let id: WlCallbackId = self.tran.id(); + let callback = Rc::new(TestCallback { + id, + _tran: self.tran.clone(), + handler: Cell::new(None), + done: Cell::new(false), + }); + self.tran.add_obj(callback.clone())?; + self.tran.send(Frame { + self_id: self.id, + callback: callback.id, + })?; + Ok(callback) + } + pub fn commit(&self) -> Result<(), TestError> { self.tran.send(Commit { self_id: self.id })?; Ok(()) diff --git a/src/it/tests.rs b/src/it/tests.rs index 84e59eed..d793510c 100644 --- a/src/it/tests.rs +++ b/src/it/tests.rs @@ -78,6 +78,7 @@ mod t0044_stacked_focus; mod t0045_content_type; mod t0046_buffer_release; mod t0047_surface_damage; +mod t0048_frame_callback; pub trait TestCase: Sync { fn name(&self) -> &'static str; @@ -144,5 +145,6 @@ pub fn tests() -> Vec<&'static dyn TestCase> { t0045_content_type, t0046_buffer_release, t0047_surface_damage, + t0048_frame_callback, } } diff --git a/src/it/tests/t0048_frame_callback.rs b/src/it/tests/t0048_frame_callback.rs new file mode 100644 index 00000000..fd11ce90 --- /dev/null +++ b/src/it/tests/t0048_frame_callback.rs @@ -0,0 +1,91 @@ +use { + crate::it::{test_error::TestResult, testrun::TestRun}, + std::rc::Rc, +}; + +testcase!(); + +/// Test wl_surface.frame callbacks +/// This test verifies that the compositor sends wl_callback.done events after the vblank event. +/// According to the Wayland protocol, frame callbacks should be fired to indicate when +/// it's a good time to start drawing the next frame, and they should be posted after +/// the compositor has finished presenting the previous frame (i.e., after vblank). +async fn test(run: Rc) -> TestResult { + run.backend.install_default()?; + let client = run.create_client().await?; + + // Create a visible window so frame callbacks can be triggered + let window = client.create_window().await?; + window.map().await?; + client.sync().await; + + // Test 1: Basic frame callback functionality + let surface = &window.surface.surface; + let callback1 = surface.frame()?; + surface.commit()?; + client.sync().await; + + // Manually trigger vblank event to simulate frame completion + let connector_id = run.backend.default_connector.id; + + // Trigger vblank manually - this processes frame callbacks + run.state.vblank(connector_id); + client.sync().await; + + // The frame callback should have been fired after vblank + tassert!(callback1.done.get()); + + // Test 2: Multiple frame callbacks + let callback2 = surface.frame()?; + let callback3 = surface.frame()?; + surface.commit()?; + client.sync().await; + + // Before triggering vblank, callbacks should not be done yet + tassert!(!callback2.done.get()); + tassert!(!callback3.done.get()); + + // Trigger vblank manually - this processes frame callbacks + run.state.vblank(connector_id); + client.sync().await; + + // Both callbacks should be done after vblank + tassert!(callback2.done.get()); + tassert!(callback3.done.get()); + + // Test 3: Frame callbacks on invisible surface should not be processed + // Create a new surface but don't make it visible + let invisible_surface = client.comp.create_surface().await?; + let buffer = client + .spbm + .create_buffer(crate::theme::Color::from_srgb(255, 0, 0))?; + invisible_surface.attach(buffer.id)?; + + let callback_invisible = invisible_surface.frame()?; + invisible_surface.commit()?; + client.sync().await; + + // Trigger vblank manually - this processes frame callbacks + run.state.vblank(connector_id); + client.sync().await; + + // Frame callback on invisible surface should not be processed + tassert!(!callback_invisible.done.get()); + + // Test 4: Frame callback timing - verify they happen after vblank + let callback_timing = surface.frame()?; + surface.commit()?; + client.sync().await; + + // The callback should not be done immediately after commit + tassert!(!callback_timing.done.get()); + + // Trigger vblank manually - this processes frame callbacks + run.state.vblank(connector_id); + client.sync().await; + + // Now it should be done + tassert!(callback_timing.done.get()); + + Ok(()) +} From f45cbed53b90d46614f78c8f3dbca1601da0f9ab Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 3 Sep 2025 12:45:05 +0200 Subject: [PATCH 2/3] it: verify that surface damage damages connector --- src/it/test_backend.rs | 6 +- src/it/tests.rs | 2 + src/it/tests/t0034_workspace_restoration.rs | 2 + src/it/tests/t0049_surface_damage_backend.rs | 125 +++++++++++++++++++ 4 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 src/it/tests/t0049_surface_damage_backend.rs diff --git a/src/it/test_backend.rs b/src/it/test_backend.rs index 161cc532..3bbbb3d7 100644 --- a/src/it/test_backend.rs +++ b/src/it/test_backend.rs @@ -27,7 +27,7 @@ use { state::State, udmabuf::Udmabuf, utils::{ - clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, + clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, on_change::OnChange, oserror::OsError, syncqueue::SyncQueue, }, video::{ @@ -84,6 +84,7 @@ impl TestBackend { events: Default::default(), feedback: Default::default(), idle: Default::default(), + damage_calls: NumCell::new(0), }); let default_mouse = Rc::new(TestBackendMouse { common: TestInputDeviceCommon { @@ -318,6 +319,7 @@ pub struct TestConnector { pub events: OnChange, pub feedback: CloneCell>>, pub idle: TEEH, + pub damage_calls: NumCell, } impl Connector for TestConnector { @@ -338,7 +340,7 @@ impl Connector for TestConnector { } fn damage(&self) { - // nothing + self.damage_calls.fetch_add(1); } fn drm_dev(&self) -> Option { diff --git a/src/it/tests.rs b/src/it/tests.rs index d793510c..609f1224 100644 --- a/src/it/tests.rs +++ b/src/it/tests.rs @@ -79,6 +79,7 @@ mod t0045_content_type; mod t0046_buffer_release; mod t0047_surface_damage; mod t0048_frame_callback; +mod t0049_surface_damage_backend; pub trait TestCase: Sync { fn name(&self) -> &'static str; @@ -146,5 +147,6 @@ pub fn tests() -> Vec<&'static dyn TestCase> { t0046_buffer_release, t0047_surface_damage, t0048_frame_callback, + t0049_surface_damage_backend, } } diff --git a/src/it/tests/t0034_workspace_restoration.rs b/src/it/tests/t0034_workspace_restoration.rs index 9adf3192..8fa6907b 100644 --- a/src/it/tests/t0034_workspace_restoration.rs +++ b/src/it/tests/t0034_workspace_restoration.rs @@ -7,6 +7,7 @@ use { format::XRGB8888, ifs::wl_output::OutputId, it::{test_backend::TestConnector, test_error::TestResult, testrun::TestRun}, + utils::numcell::NumCell, video::drm::ConnectorType, }, std::rc::Rc, @@ -35,6 +36,7 @@ async fn test(run: Rc) -> TestResult { events: Default::default(), feedback: Default::default(), idle: Default::default(), + damage_calls: NumCell::new(0), }); let new_monitor_info = MonitorInfo { modes: vec![], diff --git a/src/it/tests/t0049_surface_damage_backend.rs b/src/it/tests/t0049_surface_damage_backend.rs new file mode 100644 index 00000000..d25d3a13 --- /dev/null +++ b/src/it/tests/t0049_surface_damage_backend.rs @@ -0,0 +1,125 @@ +use { + crate::it::{test_error::TestResult, testrun::TestRun}, + std::rc::Rc, +}; + +testcase!(); + +/// Test that committing damage on a visible surface causes the backend connector to be damaged. +/// This test verifies that surface damage triggers backend connector damage tracking AND +/// that the frontend actually calls into the backend connector's damage method, +/// ensuring the rendering pipeline knows when to update the display. +async fn test(run: Rc) -> TestResult { + run.backend.install_default()?; + let client = run.create_client().await?; + + // Get connector for tracking backend damage state + let connector_id = run.backend.default_connector.id; + let connector_data = run.state.connectors.get(&connector_id).unwrap(); + + // Create a visible window with mapped surface + let window = client.create_window().await?; + let buffer = client + .spbm + .create_buffer(crate::theme::Color::from_srgb(0, 255, 0))?; + window.surface.attach(buffer.id)?; + window.map().await?; + client.sync().await; + + // Test 1: Ensure initially the backend is not damaged + connector_data.damaged.set(false); + run.backend.default_connector.damage_calls.set(0); + tassert!(!connector_data.damaged.get()); + tassert_eq!(run.backend.default_connector.damage_calls.get(), 0); + + // Test 2: Add surface damage and commit - this should trigger backend connector damage + window.surface.damage(10, 10, 50, 50)?; + window.surface.commit()?; + client.sync().await; + + // Critical test: Verify the backend connector is now damaged AND the backend method was called + tassert!(connector_data.damaged.get()); + tassert!(run.backend.default_connector.damage_calls.get() > 0); + + // Test 3: Reset damage state and test buffer damage + connector_data.damaged.set(false); + let previous_calls = run.backend.default_connector.damage_calls.get(); + tassert!(!connector_data.damaged.get()); + + // Add buffer damage and commit - this should also trigger backend connector damage + window.surface.damage_buffer(0, 0, 1, 1)?; // Damage entire 1x1 buffer + window.surface.commit()?; + client.sync().await; + + // Verify the backend connector is damaged again AND more backend calls were made + tassert!(connector_data.damaged.get()); + tassert!(run.backend.default_connector.damage_calls.get() > previous_calls); + + // Test 4: Test that invisible surfaces do not trigger backend damage + let invisible_surface = client.comp.create_surface().await?; + let invisible_buffer = client + .spbm + .create_buffer(crate::theme::Color::from_srgb(255, 255, 0))?; + invisible_surface.attach(invisible_buffer.id)?; + invisible_surface.commit()?; // Initial commit to attach buffer + client.sync().await; + + // Reset damage state + connector_data.damaged.set(false); + let invisible_calls_before = run.backend.default_connector.damage_calls.get(); + tassert!(!connector_data.damaged.get()); + + // Add damage to invisible surface and commit + invisible_surface.damage(20, 20, 30, 30)?; + invisible_surface.commit()?; + client.sync().await; + + // Invisible surface damage should NOT trigger backend connector damage or backend calls + tassert!(!connector_data.damaged.get()); + tassert_eq!( + run.backend.default_connector.damage_calls.get(), + invisible_calls_before + ); + + // Test 5: Test multiple damage areas on visible surface + connector_data.damaged.set(false); + let multi_calls_before = run.backend.default_connector.damage_calls.get(); + tassert!(!connector_data.damaged.get()); + + // Add multiple damage rectangles to visible surface + window.surface.damage(5, 5, 10, 10)?; + window.surface.damage(25, 25, 15, 15)?; + window.surface.damage_buffer(0, 0, 1, 1)?; + window.surface.commit()?; + client.sync().await; + + // Multiple damage areas on visible surface should trigger backend connector damage and calls + tassert!(connector_data.damaged.get()); + tassert!(run.backend.default_connector.damage_calls.get() > multi_calls_before); + + // Test 6: Test that damage without commit does not trigger backend damage + connector_data.damaged.set(false); + let no_commit_calls_before = run.backend.default_connector.damage_calls.get(); + tassert!(!connector_data.damaged.get()); + + // Add damage but don't commit + window.surface.damage(40, 40, 20, 20)?; + client.sync().await; + + // Damage without commit should NOT trigger backend connector damage or backend calls + tassert!(!connector_data.damaged.get()); + tassert_eq!( + run.backend.default_connector.damage_calls.get(), + no_commit_calls_before + ); + + // Now commit the pending damage + window.surface.commit()?; + client.sync().await; + + // After commit, backend connector should be damaged and backend called + tassert!(connector_data.damaged.get()); + tassert!(run.backend.default_connector.damage_calls.get() > no_commit_calls_before); + + Ok(()) +} From 73bf4465e2e7c14e25bfa874d1b37b867ed42b0c Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 3 Sep 2025 17:46:33 +0200 Subject: [PATCH 3/3] 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 {