Repository: slint-ui/slint (language: rust)

Issue:
LinuxKMS: Fix support for triple buffering with drm
At the moment the linuxkms backend  uses double buffering with vsync when posting buffers via the drm interface. After issuing the page flip ioctl, the loop sleeps until the vblank event, and only then resumes rendering the next frame.

This means that by the time the next frame is rendered, the previous front buffer is available as back buffer and - for example with a 60hz refresh rate - the frame rates at either very near 60fps or they drop straight down to 30fps or lower.

Instead, we should attempt to render the next frame right away and before the next swapbuffers call wait for the vblank event id it hasn't happened yet.

For the dumb buffer handling a third buffer is needed.


Discussion / hints:

Fix patch:
diff --git a/internal/backends/linuxkms/calloop_backend.rs b/internal/backends/linuxkms/calloop_backend.rs
index ac2790a7e99..db13598be7d 100644
--- a/internal/backends/linuxkms/calloop_backend.rs
+++ b/internal/backends/linuxkms/calloop_backend.rs
@@ -261,7 +261,6 @@ impl i_slint_core::platform::Platform for Backend {
             }
 
             if let Some(adapter) = self.window.borrow().as_ref() {
-                adapter.register_event_loop(event_loop.handle())?;
                 adapter.clone().render_if_needed(mouse_position_property.as_ref())?;
             };
 
diff --git a/internal/backends/linuxkms/display.rs b/internal/backends/linuxkms/display.rs
index 3ddd6c303a6..bea47ddd83a 100644
--- a/internal/backends/linuxkms/display.rs
+++ b/internal/backends/linuxkms/display.rs
@@ -2,20 +2,11 @@
 // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
 
 use i_slint_core::api::PhysicalSize;
-use i_slint_core::platform::PlatformError;
 
 #[allow(unused)]
 pub trait Presenter {
-    fn is_ready_to_present(&self) -> bool;
-    fn register_page_flip_handler(
-        &self,
-        event_loop_handle: crate::calloop_backend::EventLoopHandle,
-    ) -> Result<(), PlatformError>;
     // Present updated front-buffer to the screen
-    fn present_with_next_frame_callback(
-        &self,
-        ready_for_next_animation_frame: Box<dyn FnOnce()>,
-    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
+    fn present(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
 }
 
 #[cfg(any(feature = "renderer-skia-opengl", feature = "renderer-femtovg"))]
@@ -103,63 +94,21 @@ impl RenderingRotation {
     feature = "renderer-software",
     feature = "renderer-skia-opengl"
 ))]
-pub(crate) mod timeranimations {
-    use i_slint_core::platform::PlatformError;
-    use std::cell::Cell;
-    use std::rc::{Rc, Weak};
+pub(crate) mod noop_presenter {
+    use std::rc::Rc;
 
-    pub(crate) struct TimerBasedAnimationDriver {
-        timer: i_slint_core::timers::Timer,
-        next_animation_frame_callback: Cell<Option<Box<dyn FnOnce()>>>,
-    }
+    // Used when the underlying renderer/display takes care of the presentation to the display
+    // and (hopefully) implements vsync.
+    pub(crate) struct NoopPresenter {}
 
-    impl TimerBasedAnimationDriver {
+    impl NoopPresenter {
         pub(crate) fn new() -> Rc<Self> {
-            Rc::new_cyclic(|self_weak: &Weak<Self>| {
-                let self_weak = self_weak.clone();
-                let timer = i_slint_core::timers::Timer::default();
-                timer.start(
-                    i_slint_core::timers::TimerMode::Repeated,
-                    std::time::Duration::from_millis(16),
-                    move || {
-                        let Some(this) = self_weak.upgrade() else { return };
-                        // Stop the timer and let the callback decide if we need to continue. It will set
-                        // `needs_redraw` to true of animations should continue, render() will be called,
-                        // present_with_next_frame_callback() will be called and then the timer restarted.
-                        this.timer.stop();
-                        if let Some(next_animation_frame_callback) =
-                            this.next_animation_frame_callback.take()
-                        {
-                            next_animation_frame_callback();
-                        }
-                    },
-                );
-                // Activate it only when we present a frame.
-                timer.stop();
-
-                Self { timer, next_animation_frame_callback: Default::default() }
-            })
+            Rc::new(Self {})
         }
     }
 
-    impl crate::display::Presenter for TimerBasedAnimationDriver {
-        fn is_ready_to_present(&self) -> bool {
-            true
-        }
-
-        fn register_page_flip_handler(
-            &self,
-            _event_loop_handle: crate::calloop_backend::EventLoopHandle,
-        ) -> Result<(), PlatformError> {
-            Ok(())
-        }
-
-        fn present_with_next_frame_callback(
-            &self,
-            ready_for_next_animation_frame: Box<dyn FnOnce()>,
-        ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
-            self.next_animation_frame_callback.set(Some(ready_for_next_animation_frame));
-            self.timer.restart();
+    impl crate::display::Presenter for NoopPresenter {
+        fn present(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
             Ok(())
         }
     }
diff --git a/internal/backends/linuxkms/display/gbmdisplay.rs b/internal/backends/linuxkms/display/gbmdisplay.rs
index 5c1e16eb09c..c1d23ec4308 100644
--- a/internal/backends/linuxkms/display/gbmdisplay.rs
+++ b/internal/backends/linuxkms/display/gbmdisplay.rs
@@ -72,17 +72,7 @@ impl GbmDisplay {
 }
 
 impl super::Presenter for GbmDisplay {
-    fn register_page_flip_handler(
-        &self,
-        event_loop_handle: crate::calloop_backend::EventLoopHandle,
-    ) -> Result<(), PlatformError> {
-        self.drm_output.register_page_flip_handler(event_loop_handle)
-    }
-
-    fn present_with_next_frame_callback(
-        &self,
-        ready_for_next_animation_frame: Box<dyn FnOnce()>,
-    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
+    fn present(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
         let mut front_buffer = unsafe {
             self.gbm_surface
                 .lock_front_buffer()
@@ -112,11 +102,7 @@ impl super::Presenter for GbmDisplay {
             })
             .map_err(|e| format!("Error setting userdata on gbm surface front buffer: {e}"))?;
 
-        self.drm_output.present(front_buffer, fb, ready_for_next_animation_frame)
-    }
-
-    fn is_ready_to_present(&self) -> bool {
-        self.drm_output.is_ready_to_present()
+        self.drm_output.present(front_buffer, fb)
     }
 }
 
diff --git a/internal/backends/linuxkms/display/swdisplay/dumbbuffer.rs b/internal/backends/linuxkms/display/swdisplay/dumbbuffer.rs
index 3e30fbbd640..fb210a5f9a3 100644
--- a/internal/backends/linuxkms/display/swdisplay/dumbbuffer.rs
+++ b/internal/backends/linuxkms/display/swdisplay/dumbbuffer.rs
@@ -10,8 +10,9 @@ use i_slint_core::platform::PlatformError;
 
 pub struct DumbBufferDisplay {
     drm_output: DrmOutput,
-    front_buffer: RefCell<DumbBuffer>,
-    back_buffer: RefCell<DumbBuffer>,
+    front_buffer: RefCell<DumbBuffer>,     // Currently displayed
+    back_buffer: RefCell<DumbBuffer>,      // Next to be rendered into
+    in_flight_buffer: RefCell<DumbBuffer>, // Buffer currently on the way to the display, to become front_buffer
 }
 
 impl DumbBufferDisplay {
@@ -32,8 +33,16 @@ impl DumbBufferDisplay {
             front_buffer.borrow().bpp,
         )?
         .into();
+        let in_flight_buffer = DumbBuffer::allocate_with_format(
+            &drm_output.drm_device,
+            drm_output.size(),
+            front_buffer.borrow().format,
+            front_buffer.borrow().depth,
+            front_buffer.borrow().bpp,
+        )?
+        .into();
 
-        Ok(Rc::new(Self { drm_output, front_buffer, back_buffer }))
+        Ok(Rc::new(Self { drm_output, front_buffer, back_buffer, in_flight_buffer }))
     }
 }
 
@@ -66,37 +75,27 @@ impl super::SoftwareBufferDisplay for DumbBufferDisplay {
 }
 
 impl crate::display::Presenter for DumbBufferDisplay {
-    fn register_page_flip_handler(
-        &self,
-        event_loop_handle: crate::calloop_backend::EventLoopHandle,
-    ) -> Result<(), PlatformError> {
-        self.drm_output.register_page_flip_handler(event_loop_handle)
-    }
+    fn present(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
+        self.drm_output.wait_for_page_flip();
 
-    fn present_with_next_frame_callback(
-        &self,
-        ready_for_next_animation_frame: Box<dyn FnOnce()>,
-    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
-        // TODO: dirty framebuffer
-        self.front_buffer.swap(&self.back_buffer);
-        self.front_buffer.borrow_mut().age = 1;
-        {
-            let mut back_buffer = self.back_buffer.borrow_mut();
-            if back_buffer.age != 0 {
-                back_buffer.age += 1;
+        self.back_buffer.swap(&self.front_buffer);
+        self.front_buffer.swap(&self.in_flight_buffer);
+
+        self.in_flight_buffer.borrow_mut().age = 1;
+        for buffer in [&self.back_buffer, &self.front_buffer] {
+            let mut buffer_borrow = buffer.borrow_mut();
+            if buffer_borrow.age != 0 {
+                buffer_borrow.age += 1;
             }
         }
+
+        // TODO: dirty framebuffer
         self.drm_output.present(
-            self.front_buffer.borrow().buffer_handle,
-            self.front_buffer.borrow().fb_handle,
-            ready_for_next_animation_frame,
+            self.in_flight_buffer.borrow().buffer_handle,
+            self.in_flight_buffer.borrow().fb_handle,
         )?;
         Ok(())
     }
-
-    fn is_ready_to_present(&self) -> bool {
-        self.drm_output.is_ready_to_present()
-    }
 }
 
 struct DumbBuffer {
diff --git a/internal/backends/linuxkms/display/swdisplay/linuxfb.rs b/internal/backends/linuxkms/display/swdisplay/linuxfb.rs
index 5d9c8f38d91..3ee4b600649 100644
--- a/internal/backends/linuxkms/display/swdisplay/linuxfb.rs
+++ b/internal/backends/linuxkms/display/swdisplay/linuxfb.rs
@@ -12,7 +12,7 @@ pub struct LinuxFBDisplay {
     back_buffer: RefCell<Box<[u8]>>,
     width: u32,
     height: u32,
-    presenter: Rc<crate::display::timeranimations::TimerBasedAnimationDriver>,
+    presenter: Rc<crate::display::noop_presenter::NoopPresenter>,
     first_frame: Cell<bool>,
     format: drm::buffer::DrmFourcc,
 }
@@ -98,7 +98,7 @@ impl LinuxFBDisplay {
             back_buffer,
             width,
             height,
-            presenter: crate::display::timeranimations::TimerBasedAnimationDriver::new(),
+            presenter: crate::display::noop_presenter::NoopPresenter::new(),
             first_frame: Cell::new(true),
             format,
         }))
diff --git a/internal/backends/linuxkms/display/vulkandisplay.rs b/internal/backends/linuxkms/display/vulkandisplay.rs
index 1a24490571b..6ecf16b12a9 100644
--- a/internal/backends/linuxkms/display/vulkandisplay.rs
+++ b/internal/backends/linuxkms/display/vulkandisplay.rs
@@ -173,6 +173,6 @@ pub fn create_vulkan_display() -> Result<VulkanDisplay, PlatformError> {
         queue_family_index,
         surface: vulkan_surface,
         size,
-        presenter: crate::display::timeranimations::TimerBasedAnimationDriver::new(),
+        presenter: crate::display::noop_presenter::NoopPresenter::new(),
     })
 }
diff --git a/internal/backends/linuxkms/drmoutput.rs b/internal/backends/linuxkms/drmoutput.rs
index daed5d7e673..6a20078660e 100644
--- a/internal/backends/linuxkms/drmoutput.rs
+++ b/internal/backends/linuxkms/drmoutput.rs
@@ -30,7 +30,6 @@ enum PageFlipState {
     InitialBufferPosted,
     WaitingForPageFlip {
         _buffer_to_keep_alive_until_flip: Box<dyn Buffer>,
-        ready_for_next_animation_frame: Box<dyn FnOnce()>,
     },
     ReadyForNextBuffer,
 }
@@ -42,7 +41,6 @@ pub struct DrmOutput {
     crtc: drm::control::crtc::Handle,
     last_buffer: Cell<Option<Box<dyn Buffer>>>,
     page_flip_state: Rc<RefCell<PageFlipState>>,
-    page_flip_event_source_registered: Cell<bool>,
 }
 
 impl DrmOutput {
@@ -198,7 +196,6 @@ impl DrmOutput {
             crtc,
             last_buffer: Cell::default(),
             page_flip_state: Default::default(),
-            page_flip_event_source_registered: Cell::new(false),
         })
     }
 
@@ -206,17 +203,14 @@ impl DrmOutput {
         &self,
         front_buffer: impl Buffer + 'static,
         framebuffer_handle: drm::control::framebuffer::Handle,
-        ready_for_next_animation_frame: Box<dyn FnOnce()>,
     ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
         if let Some(last_buffer) = self.last_buffer.replace(Some(Box::new(front_buffer))) {
             self.drm_device
                 .page_flip(self.crtc, framebuffer_handle, drm::control::PageFlipFlags::EVENT, None)
                 .map_err(|e| format!("Error presenting framebuffer on screen: {e}"))?;
 
-            *self.page_flip_state.borrow_mut() = PageFlipState::WaitingForPageFlip {
-                _buffer_to_keep_alive_until_flip: last_buffer,
-                ready_for_next_animation_frame,
-            };
+            *self.page_flip_state.borrow_mut() =
+                PageFlipState::WaitingForPageFlip { _buffer_to_keep_alive_until_flip: last_buffer };
         } else {
             self.drm_device
                 .set_crtc(
@@ -228,66 +222,34 @@ impl DrmOutput {
                 )
                 .map_err(|e| format!("Error presenting framebuffer on screen: {e}"))?;
             *self.page_flip_state.borrow_mut() = PageFlipState::InitialBufferPosted;
-
-            // We can render the next frame right away, if needed, since we have at least two buffers. The callback
-            // will decide (will check if animation is running). However invoke the callback through the event loop
-            // instead of directly, so that if it decides to set `needs_redraw` to true, the event loop will process it.
-            i_slint_core::timers::Timer::single_shot(std::time::Duration::default(), move || {
-                ready_for_next_animation_frame();
-            })
         }
 
         Ok(())
     }
 
-    pub fn register_page_flip_handler(
-        &self,
-        event_loop_handle: crate::calloop_backend::EventLoopHandle,
-    ) -> Result<(), PlatformError> {
-        if self.page_flip_event_source_registered.replace(true) {
-            return Ok(());
-        }
-
-        let source = calloop::generic::Generic::new_with_error::<std::io::Error>(
-            self.drm_device.0.clone(),
-            calloop::Interest::READ,
-            calloop::Mode::Level,
-        );
-
-        event_loop_handle
-            .insert_source(source, {
-                let drm_device = self.drm_device.clone();
-                let page_flip_state = self.page_flip_state.clone();
-
-                move |_, _, _| {
-                    if drm_device
-                        .receive_events()?
-                        .any(|event| matches!(event, drm::control::Event::PageFlip(..)))
-                    {
-                        if let PageFlipState::WaitingForPageFlip {
-                            ready_for_next_animation_frame,
-                            ..
-                        } = page_flip_state.replace(PageFlipState::ReadyForNextBuffer)
-                        {
-                            ready_for_next_animation_frame();
-                        }
-                    }
-                    Ok(calloop::PostAction::Continue)
-                }
-            })
-            .map_err(|e| {
-                PlatformError::Other(format!("Error registering page flip handler: {e}"))
-            })?;
-        Ok(())
-    }
-
-    pub fn is_ready_to_present(&self) -> bool {
-        matches!(
+    pub fn wait_for_page_flip(&self) {
+        if matches!(
             *self.page_flip_state.borrow(),
             PageFlipState::NoFrameBufferPosted
                 | PageFlipState::InitialBufferPosted
                 | PageFlipState::ReadyForNextBuffer
-        )
+        ) {
+            return;
+        }
+
+        loop {
+            let Ok(mut event_it) = self.drm_device.receive_events() else {
+                return;
+            };
+
+            if event_it.any(|event| matches!(event, drm::control::Event::PageFlip(..))) {
+                if let PageFlipState::WaitingForPageFlip { .. } =
+                    self.page_flip_state.replace(PageFlipState::ReadyForNextBuffer)
+                {
+                    return;
+                }
+            }
+        }
     }
 
     pub fn size(&self) -> (u32, u32) {
diff --git a/internal/backends/linuxkms/fullscreenwindowadapter.rs b/internal/backends/linuxkms/fullscreenwindowadapter.rs
index 630f2638647..19fd18d80c2 100644
--- a/internal/backends/linuxkms/fullscreenwindowadapter.rs
+++ b/internal/backends/linuxkms/fullscreenwindowadapter.rs
@@ -20,25 +20,18 @@ use crate::display::RenderingRotation;
 
 pub trait FullscreenRenderer {
     fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer;
-    fn is_ready_to_present(&self) -> bool;
     fn render_and_present(
         &self,
         rotation: RenderingRotation,
         draw_mouse_cursor_callback: &dyn Fn(&mut dyn ItemRenderer),
-        ready_for_next_animation_frame: Box<dyn FnOnce()>,
     ) -> Result<(), PlatformError>;
     fn size(&self) -> PhysicalWindowSize;
-    fn register_page_flip_handler(
-        &self,
-        event_loop_handle: crate::calloop_backend::EventLoopHandle,
-    ) -> Result<(), PlatformError>;
 }
 
 pub struct FullscreenWindowAdapter {
     window: i_slint_core::api::Window,
     renderer: Box<dyn FullscreenRenderer>,
     redraw_requested: Cell<bool>,
-    needs_redraw_after_present: Cell<bool>,
     rotation: RenderingRotation,
 }
 
@@ -92,7 +85,6 @@ impl FullscreenWindowAdapter {
             window: i_slint_core::api::Window::new(self_weak.clone()),
             renderer,
             redraw_requested: Cell::new(true),
-            needs_redraw_after_present: Cell::new(false),
             rotation,
         }))
     }
@@ -101,56 +93,42 @@ impl FullscreenWindowAdapter {
         self: Rc<Self>,
         mouse_position: Pin<&Property<Option<LogicalPosition>>>,
     ) -> Result<(), PlatformError> {
-        if !self.renderer.is_ready_to_present() {
-            return Ok(());
-        }
         if self.redraw_requested.replace(false) {
-            self.renderer.render_and_present(
-                self.rotation,
-                &|item_renderer| {
-                    if let Some(mouse_position) = mouse_position.get() {
-                        let cursor_image = mouse_cursor_image();
-                        item_renderer.save_state();
-                        item_renderer.translate(
-                            i_slint_core::lengths::logical_point_from_api(mouse_position)
-                                .to_vector(),
-                        );
-                        item_renderer.draw_image_direct(mouse_cursor_image());
-                        item_renderer.restore_state();
-                        let cursor_rect = LogicalRect::new(
-                            euclid::point2(mouse_position.x, mouse_position.y),
-                            euclid::Size2D::from_untyped(cursor_image.size().cast()),
-                        );
-                        self.renderer.as_core_renderer().mark_dirty_region(cursor_rect.into());
-                    }
-                },
-                Box::new({
-                    let self_weak = Rc::downgrade(&self);
-                    move || {
-                        let Some(this) = self_weak.upgrade() else {
-                            return;
-                        };
-                        if this.needs_redraw_after_present.replace(false) {
-                            this.request_redraw();
-                        }
-                    }
-                }),
-            )?;
+            self.renderer.render_and_present(self.rotation, &|item_renderer| {
+                if let Some(mouse_position) = mouse_position.get() {
+                    let cursor_image = mouse_cursor_image();
+                    item_renderer.save_state();
+                    item_renderer.translate(
+                        i_slint_core::lengths::logical_point_from_api(mouse_position).to_vector(),
+                    );
+                    item_renderer.draw_image_direct(mouse_cursor_image());
+                    item_renderer.restore_state();
+                    let cursor_rect = LogicalRect::new(
+                        euclid::point2(mouse_position.x, mouse_position.y),
+                        euclid::Size2D::from_untyped(cursor_image.size().cast()),
+                    );
+                    self.renderer.as_core_renderer().mark_dirty_region(cursor_rect.into());
+                }
+            })?;
             // Check once after rendering if we have running animations and
             // remember that to trigger a redraw after the frame is on the screen.
             // Timers might have been updated if the event loop is woken up
             // due to other reasons, which would also reset has_active_animations.
-            self.needs_redraw_after_present.set(self.window.has_active_animations());
+            if self.window.has_active_animations() {
+                let self_weak = Rc::downgrade(&self);
+                i_slint_core::timers::Timer::single_shot(
+                    std::time::Duration::default(),
+                    move || {
+                        let Some(this) = self_weak.upgrade() else {
+                            return;
+                        };
+                        this.request_redraw();
+                    },
+                )
+            }
         }
         Ok(())
     }
-
-    pub fn register_event_loop(
-        &self,
-        event_loop_handle: crate::calloop_backend::EventLoopHandle,
-    ) -> Result<(), PlatformError> {
-        self.renderer.register_page_flip_handler(event_loop_handle)
-    }
 }
 
 fn mouse_cursor_image() -> Image {
diff --git a/internal/backends/linuxkms/renderer/femtovg.rs b/internal/backends/linuxkms/renderer/femtovg.rs
index c513b3f77a0..27e8a13e963 100644
--- a/internal/backends/linuxkms/renderer/femtovg.rs
+++ b/internal/backends/linuxkms/renderer/femtovg.rs
@@ -26,10 +26,11 @@ pub struct FemtoVGRendererAdapter {
 struct GlContextWrapper {
     glutin_context: glutin::context::PossiblyCurrentContext,
     glutin_surface: glutin::surface::Surface<glutin::surface::WindowSurface>,
+    gbm_display: Rc<GbmDisplay>,
 }
 
 impl GlContextWrapper {
-    fn new(gbm_display: &GbmDisplay) -> Result<Self, PlatformError> {
+    fn new(gbm_display: &Rc<GbmDisplay>) -> Result<Self, PlatformError> {
         let (width, height) = gbm_display.drm_output.size();
         let width: std::num::NonZeroU32 = width.try_into().map_err(|_| {
             format!("Attempting to create window surface with an invalid width: {}", width)
@@ -105,7 +106,11 @@ impl GlContextWrapper {
             .into()
     })?;
 
-        Ok(Self { glutin_context: context, glutin_surface: surface })
+        Ok(Self {
+            glutin_context: context,
+            glutin_surface: surface,
+            gbm_display: gbm_display.clone(),
+        })
     }
 }
 
@@ -122,6 +127,9 @@ unsafe impl i_slint_renderer_femtovg::OpenGLInterface for GlContextWrapper {
     }
 
     fn swap_buffers(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
+        // Make sure the in-flight font-buffer from the previous swap_buffers call has been
+        // posted to the screen.
+        self.gbm_display.drm_output.wait_for_page_flip();
         self.glutin_surface.swap_buffers(&self.glutin_context).map_err(
             |glutin_error| -> PlatformError {
                 format!("FemtoVG: Error swapping buffers: {glutin_error}").into()
@@ -169,15 +177,10 @@ impl crate::fullscreenwindowadapter::FullscreenRenderer for FemtoVGRendererAdapt
         &self.renderer
     }
 
-    fn is_ready_to_present(&self) -> bool {
-        self.gbm_display.is_ready_to_present()
-    }
-
     fn render_and_present(
         &self,
         rotation: RenderingRotation,
         draw_mouse_cursor_callback: &dyn Fn(&mut dyn ItemRenderer),
-        ready_for_next_animation_frame: Box<dyn FnOnce()>,
     ) -> Result<(), PlatformError> {
         let size = self.size();
         self.renderer.render_transformed_with_post_callback(
@@ -188,18 +191,11 @@ impl crate::fullscreenwindowadapter::FullscreenRenderer for FemtoVGRendererAdapt
                 draw_mouse_cursor_callback(item_renderer);
             }),
         )?;
-        self.gbm_display.present_with_next_frame_callback(ready_for_next_animation_frame)?;
+        self.gbm_display.present()?;
         Ok(())
     }
     fn size(&self) -> i_slint_core::api::PhysicalSize {
         let (width, height) = self.gbm_display.drm_output.size();
         i_slint_core::api::PhysicalSize::new(width, height)
     }
-
-    fn register_page_flip_handler(
-        &self,
-        event_loop_handle: crate::calloop_backend::EventLoopHandle,
-    ) -> Result<(), PlatformError> {
-        self.gbm_display.clone().register_page_flip_handler(event_loop_handle)
-    }
 }
diff --git a/internal/backends/linuxkms/renderer/skia.rs b/internal/backends/linuxkms/renderer/skia.rs
index be450313111..2e076f6322c 100644
--- a/internal/backends/linuxkms/renderer/skia.rs
+++ b/internal/backends/linuxkms/renderer/skia.rs
@@ -70,10 +70,18 @@ impl SkiaRendererAdapter {
             renderer: i_slint_renderer_skia::SkiaRenderer::new_with_surface(Box::new(
                 skia_gl_surface,
             )),
-            presenter: display,
+            presenter: display.clone(),
             size,
         });
 
+        renderer.renderer.set_pre_present_callback(Some(Box::new({
+            move || {
+                // Make sure the in-flight font-buffer from the previous swap_buffers call has been
+                // posted to the screen.
+                display.drm_output.wait_for_page_flip();
+            }
+        })));
+
         eprintln!("Using Skia OpenGL renderer");
 
         Ok(renderer)
@@ -132,15 +140,10 @@ impl crate::fullscreenwindowadapter::FullscreenRenderer for SkiaRendererAdapter
         &self.renderer
     }
 
-    fn is_ready_to_present(&self) -> bool {
-        self.presenter.is_ready_to_present()
-    }
-
     fn render_and_present(
         &self,
         rotation: RenderingRotation,
         draw_mouse_cursor_callback: &dyn Fn(&mut dyn ItemRenderer),
-        ready_for_next_animation_frame: Box<dyn FnOnce()>,
     ) -> Result<(), PlatformError> {
         self.renderer.render_transformed_with_post_callback(
             rotation.degrees(),
@@ -150,19 +153,12 @@ impl crate::fullscreenwindowadapter::FullscreenRenderer for SkiaRendererAdapter
                 draw_mouse_cursor_callback(item_renderer);
             }),
         )?;
-        self.presenter.present_with_next_frame_callback(ready_for_next_animation_frame)?;
+        self.presenter.present()?;
         Ok(())
     }
     fn size(&self) -> i_slint_core::api::PhysicalSize {
         self.size
     }
-
-    fn register_page_flip_handler(
-        &self,
-        event_loop_handle: crate::calloop_backend::EventLoopHandle,
-    ) -> Result<(), PlatformError> {
-        self.presenter.clone().register_page_flip_handler(event_loop_handle)
-    }
 }
 struct DrmDumbBufferAccess {
     display: Rc<dyn crate::display::swdisplay::SoftwareBufferDisplay>,
diff --git a/internal/backends/linuxkms/renderer/sw.rs b/internal/backends/linuxkms/renderer/sw.rs
index 63f0f8af459..632eb28ec20 100644
--- a/internal/backends/linuxkms/renderer/sw.rs
+++ b/internal/backends/linuxkms/renderer/sw.rs
@@ -90,15 +90,10 @@ impl crate::fullscreenwindowadapter::FullscreenRenderer for SoftwareRendererAdap
         &self.renderer
     }
 
-    fn is_ready_to_present(&self) -> bool {
-        self.presenter.is_ready_to_present()
-    }
-
     fn render_and_present(
         &self,
         rotation: RenderingRotation,
         _draw_mouse_cursor_callback: &dyn Fn(&mut dyn i_slint_core::item_rendering::ItemRenderer),
-        ready_for_next_animation_frame: Box<dyn FnOnce()>,
     ) -> Result<(), PlatformError> {
         self.display.map_back_buffer(&mut |pixels, age, format| {
             self.renderer.set_repaint_buffer_type(match age {
@@ -143,18 +138,11 @@ impl crate::fullscreenwindowadapter::FullscreenRenderer for SoftwareRendererAdap
 
             Ok(())
         })?;
-        self.presenter.present_with_next_frame_callback(ready_for_next_animation_frame)?;
+        self.presenter.present()?;
         Ok(())
     }
 
     fn size(&self) -> i_slint_core::api::PhysicalSize {
         self.size
     }
-
-    fn register_page_flip_handler(
-        &self,
-        event_loop_handle: crate::calloop_backend::EventLoopHandle,
-    ) -> Result<(), PlatformError> {
-        self.presenter.register_page_flip_handler(event_loop_handle)
-    }
 }


Test patch: