From c1d6716eecbc696cbf9f2845c4eb1dd7f9ce3923 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 21 Apr 2023 10:37:48 -0700 Subject: [PATCH] Add a `Buffer::present_with_damage()` method Supported on Wayland, X11, and Win32. Fixes https://github.com/rust-windowing/softbuffer/issues/39. try_for_each --- src/cg.rs | 6 +++- src/lib.rs | 36 ++++++++++++++++++++ src/orbital.rs | 6 +++- src/wayland/mod.rs | 85 +++++++++++++++++++++++++++------------------- src/web.rs | 6 +++- src/win32.rs | 59 ++++++++++++++++++++++---------- src/x11.rs | 66 +++++++++++++++++++++++------------ 7 files changed, 186 insertions(+), 78 deletions(-) diff --git a/src/cg.rs b/src/cg.rs index fce628d..f88db20 100644 --- a/src/cg.rs +++ b/src/cg.rs @@ -1,4 +1,4 @@ -use crate::SoftBufferError; +use crate::{Rect, SoftBufferError}; use core_graphics::base::{ kCGBitmapByteOrder32Little, kCGImageAlphaNoneSkipFirst, kCGRenderingIntentDefault, }; @@ -119,6 +119,10 @@ impl<'a> BufferImpl<'a> { Ok(()) } + + pub fn present_with_damage(self, _damage: &[Rect]) -> Result<(), SoftBufferError> { + self.present() + } } impl Drop for CGImpl { diff --git a/src/lib.rs b/src/lib.rs index f7dadf6..053482e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -132,6 +132,15 @@ macro_rules! make_dispatch { )* } } + + pub fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> { + match self { + $( + $(#[$attr])* + Self::$name(inner) => inner.present_with_damage(damage), + )* + } + } } }; } @@ -207,6 +216,19 @@ impl Context { } } +/// A rectangular region of the buffer coordinate space. +#[derive(Clone, Copy, Debug)] +pub struct Rect { + /// x coordinate of top left corner + pub x: i32, + /// y coordinate of top left corner + pub y: i32, + /// width + pub width: i32, + /// height + pub height: i32, +} + /// A surface for drawing to a window with software buffers. pub struct Surface { /// This is boxed so that `Surface` is the same size on every platform. @@ -370,6 +392,20 @@ impl<'a> Buffer<'a> { pub fn present(self) -> Result<(), SoftBufferError> { self.buffer_impl.present() } + + /// Presents buffer to the window, with damage regions. + /// + /// # Platform dependent behavior + /// + /// Supported on: + /// - Wayland + /// - X, when XShm is available + /// - Win32 + /// + /// Otherwise this is equivalent to [`present`]. + pub fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> { + self.buffer_impl.present_with_damage(damage) + } } impl<'a> ops::Deref for Buffer<'a> { diff --git a/src/orbital.rs b/src/orbital.rs index f999b6a..a38d0c3 100644 --- a/src/orbital.rs +++ b/src/orbital.rs @@ -1,7 +1,7 @@ use raw_window_handle::OrbitalWindowHandle; use std::{cmp, num::NonZeroU32, slice, str}; -use crate::SoftBufferError; +use crate::{Rect, SoftBufferError}; struct OrbitalMap { address: usize, @@ -186,4 +186,8 @@ impl<'a> BufferImpl<'a> { Ok(()) } + + pub fn present_with_damage(self, _damage: &[Rect]) -> Result<(), SoftBufferError> { + self.present() + } } diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index 2550e24..5922379 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -1,4 +1,4 @@ -use crate::{error::SwResultExt, util, SoftBufferError}; +use crate::{error::SwResultExt, util, Rect, SoftBufferError}; use raw_window_handle::{WaylandDisplayHandle, WaylandWindowHandle}; use std::{ cell::RefCell, @@ -129,6 +129,45 @@ impl WaylandImpl { Ok(unsafe { buffer.buffers.as_mut().unwrap().1.mapped_mut() }) })?)) } + + fn present_with_damage(&mut self, damage: &[Rect]) -> Result<(), SoftBufferError> { + let _ = self + .display + .event_queue + .borrow_mut() + .dispatch_pending(&mut State); + + if let Some((front, back)) = &mut self.buffers { + // Swap front and back buffer + std::mem::swap(front, back); + + front.attach(&self.surface); + + // Like Mesa's EGL/WSI implementation, we damage the whole buffer with `i32::MAX` if + // the compositor doesn't support `damage_buffer`. + // https://bugs.freedesktop.org/show_bug.cgi?id=78190 + if self.surface.version() < 4 { + self.surface.damage(0, 0, i32::MAX, i32::MAX); + } else { + for Rect { + x, + y, + width, + height, + } in damage + { + // Introduced in version 4, it is an error to use this request in version 3 or lower. + self.surface.damage_buffer(*x, *y, *width, *height); + } + } + + self.surface.commit(); + } + + let _ = self.display.event_queue.borrow_mut().flush(); + + Ok(()) + } } pub struct BufferImpl<'a>(util::BorrowStack<'a, WaylandImpl, [u32]>); @@ -144,45 +183,21 @@ impl<'a> BufferImpl<'a> { self.0.member_mut() } + pub fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> { + self.0.into_container().present_with_damage(damage) + } + pub fn present(self) -> Result<(), SoftBufferError> { let imp = self.0.into_container(); - let (width, height) = imp .size .expect("Must set size of surface before calling `present()`"); - - let _ = imp - .display - .event_queue - .borrow_mut() - .dispatch_pending(&mut State); - - if let Some((front, back)) = &mut imp.buffers { - // Swap front and back buffer - std::mem::swap(front, back); - - front.attach(&imp.surface); - - // FIXME: Proper damaging mechanism. - // - // In order to propagate changes on compositors which track damage, for now damage the entire surface. - if imp.surface.version() < 4 { - // FIXME: Accommodate scale factor since wl_surface::damage is in terms of surface coordinates while - // wl_surface::damage_buffer is in buffer coordinates. - // - // i32::MAX is a valid damage box (most compositors interpret the damage box as "the entire surface") - imp.surface.damage(0, 0, i32::MAX, i32::MAX); - } else { - // Introduced in version 4, it is an error to use this request in version 3 or lower. - imp.surface.damage_buffer(0, 0, width.get(), height.get()); - } - - imp.surface.commit(); - } - - let _ = imp.display.event_queue.borrow_mut().flush(); - - Ok(()) + imp.present_with_damage(&[Rect { + x: 0, + y: 0, + width: width.get(), + height: height.get(), + }]) } } diff --git a/src/web.rs b/src/web.rs index c8286d1..76f7c19 100644 --- a/src/web.rs +++ b/src/web.rs @@ -9,7 +9,7 @@ use web_sys::HtmlCanvasElement; use web_sys::ImageData; use crate::error::SwResultExt; -use crate::SoftBufferError; +use crate::{Rect, SoftBufferError}; use std::convert::TryInto; use std::num::NonZeroU32; @@ -153,6 +153,10 @@ impl<'a> BufferImpl<'a> { Ok(()) } + + pub fn present_with_damage(self, _damage: &[Rect]) -> Result<(), SoftBufferError> { + self.present() + } } #[inline(always)] diff --git a/src/win32.rs b/src/win32.rs index c040a21..1e02174 100644 --- a/src/win32.rs +++ b/src/win32.rs @@ -2,7 +2,7 @@ //! //! This module converts the input buffer into a bitmap and then stretches it to the window. -use crate::SoftBufferError; +use crate::{Rect, SoftBufferError}; use raw_window_handle::Win32WindowHandle; use std::io; @@ -202,6 +202,36 @@ impl Win32Impl { Ok(BufferImpl(self)) } + + fn present_with_damage(&self, damage: &[Rect]) -> Result<(), SoftBufferError> { + let buffer = self.buffer.as_ref().unwrap(); + unsafe { + for Rect { + x, + y, + width, + height, + } in damage + { + Gdi::BitBlt( + self.dc, + *x, + *y, + *width, + *height, + buffer.dc, + *x, + *y, + Gdi::SRCCOPY, + ); + } + + // Validate the window. + Gdi::ValidateRect(self.window, ptr::null_mut()); + } + + Ok(()) + } } pub struct BufferImpl<'a>(&'a mut Win32Impl); @@ -220,23 +250,16 @@ impl<'a> BufferImpl<'a> { pub fn present(self) -> Result<(), SoftBufferError> { let imp = self.0; let buffer = imp.buffer.as_ref().unwrap(); - unsafe { - Gdi::BitBlt( - imp.dc, - 0, - 0, - buffer.width.get(), - buffer.height.get(), - buffer.dc, - 0, - 0, - Gdi::SRCCOPY, - ); + imp.present_with_damage(&[Rect { + x: 0, + y: 0, + width: buffer.width.get(), + height: buffer.height.get(), + }]) + } - // Validate the window. - Gdi::ValidateRect(imp.window, ptr::null_mut()); - } - - Ok(()) + pub fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> { + let imp = self.0; + imp.present_with_damage(damage) } } diff --git a/src/x11.rs b/src/x11.rs index 8d47476..fd70727 100644 --- a/src/x11.rs +++ b/src/x11.rs @@ -6,7 +6,7 @@ #![allow(clippy::uninlined_format_args)] use crate::error::SwResultExt; -use crate::SoftBufferError; +use crate::{Rect, SoftBufferError}; use nix::libc::{shmat, shmctl, shmdt, shmget, IPC_PRIVATE, IPC_RMID}; use raw_window_handle::{XcbDisplayHandle, XcbWindowHandle, XlibDisplayHandle, XlibWindowHandle}; use std::ptr::{null_mut, NonNull}; @@ -295,7 +295,7 @@ impl<'a> BufferImpl<'a> { } /// Push the buffer to the window. - pub fn present(self) -> Result<(), SoftBufferError> { + pub fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> { let imp = self.0; log::trace!("present: window={:X}", imp.window); @@ -328,27 +328,38 @@ impl<'a> BufferImpl<'a> { // SAFETY: We know that we called finish_wait() before this. // Put the image into the window. if let Some((_, segment_id)) = shm.seg { - imp.display - .connection - .shm_put_image( - imp.window, - imp.gc, - imp.width, - imp.height, - 0, - 0, - imp.width, - imp.height, - 0, - 0, - imp.depth, - xproto::ImageFormat::Z_PIXMAP.into(), - false, - segment_id, - 0, + damage + .iter() + .try_for_each( + |Rect { + x, + y, + width, + height, + }| { + imp.display + .connection + .shm_put_image( + imp.window, + imp.gc, + imp.width, + imp.height, + *x as u16, + *y as u16, + *width as u16, + *height as u16, + *x as i16, + *y as i16, + imp.depth, + xproto::ImageFormat::Z_PIXMAP.into(), + false, + segment_id, + 0, + ) + .push_err() + .map(|c| c.ignore_error()) + }, ) - .push_err() - .map(|c| c.ignore_error()) .and_then(|()| { // Send a short request to act as a notification for when the X server is done processing the image. shm.begin_wait(&imp.display.connection) @@ -361,6 +372,17 @@ impl<'a> BufferImpl<'a> { result.swbuf_err("Failed to draw image to window") } + + pub fn present(self) -> Result<(), SoftBufferError> { + let width = self.0.width.into(); + let height = self.0.height.into(); + self.present_with_damage(&[Rect { + x: 0, + y: 0, + width, + height, + }]) + } } impl Buffer {