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
This commit is contained in:
Ian Douglas Scott 2023-04-21 10:37:48 -07:00
parent f12aa534e1
commit c1d6716eec
7 changed files with 186 additions and 78 deletions

View file

@ -1,4 +1,4 @@
use crate::SoftBufferError; use crate::{Rect, SoftBufferError};
use core_graphics::base::{ use core_graphics::base::{
kCGBitmapByteOrder32Little, kCGImageAlphaNoneSkipFirst, kCGRenderingIntentDefault, kCGBitmapByteOrder32Little, kCGImageAlphaNoneSkipFirst, kCGRenderingIntentDefault,
}; };
@ -119,6 +119,10 @@ impl<'a> BufferImpl<'a> {
Ok(()) Ok(())
} }
pub fn present_with_damage(self, _damage: &[Rect]) -> Result<(), SoftBufferError> {
self.present()
}
} }
impl Drop for CGImpl { impl Drop for CGImpl {

View file

@ -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. /// A surface for drawing to a window with software buffers.
pub struct Surface { pub struct Surface {
/// This is boxed so that `Surface` is the same size on every platform. /// 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> { pub fn present(self) -> Result<(), SoftBufferError> {
self.buffer_impl.present() 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> { impl<'a> ops::Deref for Buffer<'a> {

View file

@ -1,7 +1,7 @@
use raw_window_handle::OrbitalWindowHandle; use raw_window_handle::OrbitalWindowHandle;
use std::{cmp, num::NonZeroU32, slice, str}; use std::{cmp, num::NonZeroU32, slice, str};
use crate::SoftBufferError; use crate::{Rect, SoftBufferError};
struct OrbitalMap { struct OrbitalMap {
address: usize, address: usize,
@ -186,4 +186,8 @@ impl<'a> BufferImpl<'a> {
Ok(()) Ok(())
} }
pub fn present_with_damage(self, _damage: &[Rect]) -> Result<(), SoftBufferError> {
self.present()
}
} }

View file

@ -1,4 +1,4 @@
use crate::{error::SwResultExt, util, SoftBufferError}; use crate::{error::SwResultExt, util, Rect, SoftBufferError};
use raw_window_handle::{WaylandDisplayHandle, WaylandWindowHandle}; use raw_window_handle::{WaylandDisplayHandle, WaylandWindowHandle};
use std::{ use std::{
cell::RefCell, cell::RefCell,
@ -129,6 +129,45 @@ impl WaylandImpl {
Ok(unsafe { buffer.buffers.as_mut().unwrap().1.mapped_mut() }) 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]>); pub struct BufferImpl<'a>(util::BorrowStack<'a, WaylandImpl, [u32]>);
@ -144,45 +183,21 @@ impl<'a> BufferImpl<'a> {
self.0.member_mut() 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> { pub fn present(self) -> Result<(), SoftBufferError> {
let imp = self.0.into_container(); let imp = self.0.into_container();
let (width, height) = imp let (width, height) = imp
.size .size
.expect("Must set size of surface before calling `present()`"); .expect("Must set size of surface before calling `present()`");
imp.present_with_damage(&[Rect {
let _ = imp x: 0,
.display y: 0,
.event_queue width: width.get(),
.borrow_mut() height: height.get(),
.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(())
} }
} }

View file

@ -9,7 +9,7 @@ use web_sys::HtmlCanvasElement;
use web_sys::ImageData; use web_sys::ImageData;
use crate::error::SwResultExt; use crate::error::SwResultExt;
use crate::SoftBufferError; use crate::{Rect, SoftBufferError};
use std::convert::TryInto; use std::convert::TryInto;
use std::num::NonZeroU32; use std::num::NonZeroU32;
@ -153,6 +153,10 @@ impl<'a> BufferImpl<'a> {
Ok(()) Ok(())
} }
pub fn present_with_damage(self, _damage: &[Rect]) -> Result<(), SoftBufferError> {
self.present()
}
} }
#[inline(always)] #[inline(always)]

View file

@ -2,7 +2,7 @@
//! //!
//! This module converts the input buffer into a bitmap and then stretches it to the window. //! 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 raw_window_handle::Win32WindowHandle;
use std::io; use std::io;
@ -202,6 +202,36 @@ impl Win32Impl {
Ok(BufferImpl(self)) 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); pub struct BufferImpl<'a>(&'a mut Win32Impl);
@ -220,23 +250,16 @@ impl<'a> BufferImpl<'a> {
pub fn present(self) -> Result<(), SoftBufferError> { pub fn present(self) -> Result<(), SoftBufferError> {
let imp = self.0; let imp = self.0;
let buffer = imp.buffer.as_ref().unwrap(); let buffer = imp.buffer.as_ref().unwrap();
unsafe { imp.present_with_damage(&[Rect {
Gdi::BitBlt( x: 0,
imp.dc, y: 0,
0, width: buffer.width.get(),
0, height: buffer.height.get(),
buffer.width.get(), }])
buffer.height.get(), }
buffer.dc,
0,
0,
Gdi::SRCCOPY,
);
// Validate the window. pub fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> {
Gdi::ValidateRect(imp.window, ptr::null_mut()); let imp = self.0;
} imp.present_with_damage(damage)
Ok(())
} }
} }

View file

@ -6,7 +6,7 @@
#![allow(clippy::uninlined_format_args)] #![allow(clippy::uninlined_format_args)]
use crate::error::SwResultExt; use crate::error::SwResultExt;
use crate::SoftBufferError; use crate::{Rect, SoftBufferError};
use nix::libc::{shmat, shmctl, shmdt, shmget, IPC_PRIVATE, IPC_RMID}; use nix::libc::{shmat, shmctl, shmdt, shmget, IPC_PRIVATE, IPC_RMID};
use raw_window_handle::{XcbDisplayHandle, XcbWindowHandle, XlibDisplayHandle, XlibWindowHandle}; use raw_window_handle::{XcbDisplayHandle, XcbWindowHandle, XlibDisplayHandle, XlibWindowHandle};
use std::ptr::{null_mut, NonNull}; use std::ptr::{null_mut, NonNull};
@ -295,7 +295,7 @@ impl<'a> BufferImpl<'a> {
} }
/// Push the buffer to the window. /// 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; let imp = self.0;
log::trace!("present: window={:X}", imp.window); 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. // SAFETY: We know that we called finish_wait() before this.
// Put the image into the window. // Put the image into the window.
if let Some((_, segment_id)) = shm.seg { if let Some((_, segment_id)) = shm.seg {
imp.display damage
.connection .iter()
.shm_put_image( .try_for_each(
imp.window, |Rect {
imp.gc, x,
imp.width, y,
imp.height, width,
0, height,
0, }| {
imp.width, imp.display
imp.height, .connection
0, .shm_put_image(
0, imp.window,
imp.depth, imp.gc,
xproto::ImageFormat::Z_PIXMAP.into(), imp.width,
false, imp.height,
segment_id, *x as u16,
0, *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(|()| { .and_then(|()| {
// Send a short request to act as a notification for when the X server is done processing the image. // 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) shm.begin_wait(&imp.display.connection)
@ -361,6 +372,17 @@ impl<'a> BufferImpl<'a> {
result.swbuf_err("Failed to draw image to window") 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 { impl Buffer {