From 8a5f1e1e37f5ed01c042dc8d7cfd304350de1089 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 3 Sep 2025 12:15:05 +0200 Subject: [PATCH] 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(()) +}