Merge pull request #99 from rust-windowing/damage

Add `Buffer::present_with_damage()` and `Buffer::age()`
This commit is contained in:
Ian Douglas Scott 2023-06-02 20:15:14 -07:00 committed by GitHub
commit efdfb529ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 416 additions and 171 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,
}; };
@ -92,6 +92,10 @@ impl<'a> BufferImpl<'a> {
&mut self.buffer &mut self.buffer
} }
pub fn age(&self) -> u8 {
0
}
pub fn present(self) -> Result<(), SoftBufferError> { pub fn present(self) -> Result<(), SoftBufferError> {
let data_provider = CGDataProvider::from_buffer(Arc::new(Buffer(self.buffer))); let data_provider = CGDataProvider::from_buffer(Arc::new(Buffer(self.buffer)));
let image = CGImage::new( let image = CGImage::new(
@ -124,6 +128,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

@ -36,6 +36,11 @@ pub enum SoftBufferError {
height: NonZeroU32, height: NonZeroU32,
}, },
#[error(
"Damage rect {}x{} at ({}, {}) out of range for backend.", .rect.width, .rect.height, .rect.x, .rect.y,
)]
DamageOutOfRange { rect: crate::Rect },
#[error("Platform error")] #[error("Platform error")]
PlatformError(Option<String>, Option<Box<dyn Error>>), PlatformError(Option<String>, Option<Box<dyn Error>>),

View file

@ -137,6 +137,15 @@ macro_rules! make_dispatch {
} }
} }
pub fn age(&self) -> u8 {
match self {
$(
$(#[$attr])*
Self::$name(inner) => inner.age(),
)*
}
}
pub fn present(self) -> Result<(), SoftBufferError> { pub fn present(self) -> Result<(), SoftBufferError> {
match self { match self {
$( $(
@ -145,6 +154,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),
)*
}
}
} }
}; };
} }
@ -220,6 +238,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: u32,
/// y coordinate of top left corner
pub y: u32,
/// width
pub width: NonZeroU32,
/// height
pub height: NonZeroU32,
}
/// 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.
@ -329,7 +360,7 @@ impl Surface {
/// Return a [`Buffer`] that the next frame should be rendered into. The size must /// Return a [`Buffer`] that the next frame should be rendered into. The size must
/// be set with [`Surface::resize`] first. The initial contents of the buffer may be zeroed, or /// be set with [`Surface::resize`] first. The initial contents of the buffer may be zeroed, or
/// may contain a previous frame. /// may contain a previous frame. Call [`Buffer::age`] to determine this.
pub fn buffer_mut(&mut self) -> Result<Buffer, SoftBufferError> { pub fn buffer_mut(&mut self) -> Result<Buffer, SoftBufferError> {
Ok(Buffer { Ok(Buffer {
buffer_impl: self.surface_impl.buffer_mut()?, buffer_impl: self.surface_impl.buffer_mut()?,
@ -380,6 +411,16 @@ pub struct Buffer<'a> {
} }
impl<'a> Buffer<'a> { impl<'a> Buffer<'a> {
/// Is age is the number of frames ago this buffer was last presented. So if the value is
/// `1`, it is the same as the last frame, and if it is `2`, it is the same as the frame
/// before that (for backends using double buffering). If the value is `0`, it is a new
/// buffer that has unspecified contents.
///
/// This can be used to update only a portion of the buffer.
pub fn age(&self) -> u8 {
self.buffer_impl.age()
}
/// Presents buffer to the window. /// Presents buffer to the window.
/// ///
/// # Platform dependent behavior /// # Platform dependent behavior
@ -395,6 +436,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 [`Self::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,
@ -57,6 +57,7 @@ pub struct OrbitalImpl {
handle: OrbitalWindowHandle, handle: OrbitalWindowHandle,
width: u32, width: u32,
height: u32, height: u32,
presented: bool,
} }
impl OrbitalImpl { impl OrbitalImpl {
@ -65,12 +66,18 @@ impl OrbitalImpl {
handle, handle,
width: 0, width: 0,
height: 0, height: 0,
presented: false,
}) })
} }
pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {
self.width = width.get(); let width = width.get();
self.height = height.get(); let height = height.get();
if width != self.width && height != self.height {
self.presented = false;
self.width = width;
self.height = height;
}
Ok(()) Ok(())
} }
@ -177,11 +184,19 @@ impl<'a> BufferImpl<'a> {
} }
} }
pub fn age(&self) -> u8 {
match self.pixels {
Pixels::Mapping(_) if self.imp.presented => 1,
_ => 0,
}
}
pub fn present(self) -> Result<(), SoftBufferError> { pub fn present(self) -> Result<(), SoftBufferError> {
match self.pixels { match self.pixels {
Pixels::Mapping(mapping) => { Pixels::Mapping(mapping) => {
drop(mapping); drop(mapping);
syscall::fsync(self.imp.window_fd()).expect("failed to sync orbital window"); syscall::fsync(self.imp.window_fd()).expect("failed to sync orbital window");
self.imp.presented = true;
} }
Pixels::Buffer(buffer) => { Pixels::Buffer(buffer) => {
self.imp self.imp
@ -191,4 +206,8 @@ impl<'a> BufferImpl<'a> {
Ok(()) Ok(())
} }
pub fn present_with_damage(self, _damage: &[Rect]) -> Result<(), SoftBufferError> {
self.present()
}
} }

View file

@ -90,6 +90,7 @@ pub(super) struct WaylandBuffer {
width: i32, width: i32,
height: i32, height: i32,
released: Arc<AtomicBool>, released: Arc<AtomicBool>,
pub age: u8,
} }
impl WaylandBuffer { impl WaylandBuffer {
@ -125,6 +126,7 @@ impl WaylandBuffer {
width, width,
height, height,
released, released,
age: 0,
} }
} }

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,
@ -125,72 +125,107 @@ impl WaylandImpl {
)); ));
}; };
Ok(BufferImpl(util::BorrowStack::new(self, |buffer| { let age = self.buffers.as_mut().unwrap().1.age;
Ok(unsafe { buffer.buffers.as_mut().unwrap().1.mapped_mut() }) Ok(BufferImpl {
})?)) stack: util::BorrowStack::new(self, |buffer| {
Ok(unsafe { buffer.buffers.as_mut().unwrap().1.mapped_mut() })
})?,
age,
})
} }
/// Fetch the buffer from the window. /// Fetch the buffer from the window.
pub fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> { pub fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> {
Err(SoftBufferError::Unimplemented) Err(SoftBufferError::Unimplemented)
} }
}
pub struct BufferImpl<'a>(util::BorrowStack<'a, WaylandImpl, [u32]>); fn present_with_damage(&mut self, damage: &[Rect]) -> Result<(), SoftBufferError> {
let _ = self
impl<'a> BufferImpl<'a> {
#[inline]
pub fn pixels(&self) -> &[u32] {
self.0.member()
}
#[inline]
pub fn pixels_mut(&mut self) -> &mut [u32] {
self.0.member_mut()
}
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 .display
.event_queue .event_queue
.borrow_mut() .borrow_mut()
.dispatch_pending(&mut State); .dispatch_pending(&mut State);
if let Some((front, back)) = &mut imp.buffers { if let Some((front, back)) = &mut self.buffers {
front.age = 1;
if back.age != 0 {
back.age += 1;
}
// Swap front and back buffer // Swap front and back buffer
std::mem::swap(front, back); std::mem::swap(front, back);
front.attach(&imp.surface); front.attach(&self.surface);
// FIXME: Proper damaging mechanism. // Like Mesa's EGL/WSI implementation, we damage the whole buffer with `i32::MAX` if
// // the compositor doesn't support `damage_buffer`.
// In order to propagate changes on compositors which track damage, for now damage the entire surface. // https://bugs.freedesktop.org/show_bug.cgi?id=78190
if imp.surface.version() < 4 { if self.surface.version() < 4 {
// FIXME: Accommodate scale factor since wl_surface::damage is in terms of surface coordinates while self.surface.damage(0, 0, i32::MAX, i32::MAX);
// 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 { } else {
// Introduced in version 4, it is an error to use this request in version 3 or lower. for rect in damage {
imp.surface.damage_buffer(0, 0, width.get(), height.get()); // Introduced in version 4, it is an error to use this request in version 3 or lower.
let (x, y, width, height) = (|| {
Some((
i32::try_from(rect.x).ok()?,
i32::try_from(rect.y).ok()?,
i32::try_from(rect.width.get()).ok()?,
i32::try_from(rect.height.get()).ok()?,
))
})()
.ok_or(SoftBufferError::DamageOutOfRange { rect: *rect })?;
self.surface.damage_buffer(x, y, width, height);
}
} }
imp.surface.commit(); self.surface.commit();
} }
let _ = imp.display.event_queue.borrow_mut().flush(); let _ = self.display.event_queue.borrow_mut().flush();
Ok(()) Ok(())
} }
} }
pub struct BufferImpl<'a> {
stack: util::BorrowStack<'a, WaylandImpl, [u32]>,
age: u8,
}
impl<'a> BufferImpl<'a> {
#[inline]
pub fn pixels(&self) -> &[u32] {
self.stack.member()
}
#[inline]
pub fn pixels_mut(&mut self) -> &mut [u32] {
self.stack.member_mut()
}
pub fn age(&self) -> u8 {
self.age
}
pub fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> {
self.stack.into_container().present_with_damage(damage)
}
pub fn present(self) -> Result<(), SoftBufferError> {
let imp = self.stack.into_container();
let (width, height) = imp
.size
.expect("Must set size of surface before calling `present()`");
imp.present_with_damage(&[Rect {
x: 0,
y: 0,
// We know width/height will be non-negative
width: width.try_into().unwrap(),
height: height.try_into().unwrap(),
}])
}
}
impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for State { impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for State {
fn event( fn event(
_: &mut State, _: &mut State,

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::marker::PhantomData; use std::marker::PhantomData;
use std::num::NonZeroU32; use std::num::NonZeroU32;
@ -42,11 +42,11 @@ pub struct WebImpl {
/// The buffer that we're drawing to. /// The buffer that we're drawing to.
buffer: Vec<u32>, buffer: Vec<u32>,
/// The current width of the canvas. /// Buffer has been presented.
width: u32, buffer_presented: bool,
/// The current height of the canvas. /// The current canvas width/height.
height: u32, size: Option<(NonZeroU32, NonZeroU32)>,
} }
impl WebImpl { impl WebImpl {
@ -78,8 +78,8 @@ impl WebImpl {
canvas, canvas,
ctx, ctx,
buffer: Vec::new(), buffer: Vec::new(),
width: 0, buffer_presented: false,
height: 0, size: None,
}) })
} }
@ -89,14 +89,14 @@ impl WebImpl {
width: NonZeroU32, width: NonZeroU32,
height: NonZeroU32, height: NonZeroU32,
) -> Result<(), SoftBufferError> { ) -> Result<(), SoftBufferError> {
let width = width.get(); if self.size != Some((width, height)) {
let height = height.get(); self.buffer_presented = false;
self.buffer.resize(total_len(width.get(), height.get()), 0);
self.canvas.set_width(width.get());
self.canvas.set_height(height.get());
self.size = Some((width, height));
}
self.buffer.resize(total_len(width, height), 0);
self.canvas.set_width(width);
self.canvas.set_height(height);
self.width = width;
self.height = height;
Ok(()) Ok(())
} }
@ -105,11 +105,75 @@ impl WebImpl {
Ok(BufferImpl { imp: self }) Ok(BufferImpl { imp: self })
} }
fn present_with_damage(&mut self, damage: &[Rect]) -> Result<(), SoftBufferError> {
let (width, _height) = self
.size
.expect("Must set size of surface before calling `present_with_damage()`");
// Create a bitmap from the buffer.
let bitmap: Vec<_> = self
.buffer
.iter()
.copied()
.flat_map(|pixel| [(pixel >> 16) as u8, (pixel >> 8) as u8, pixel as u8, 255])
.collect();
#[cfg(target_feature = "atomics")]
let result = {
use js_sys::{Uint8Array, Uint8ClampedArray};
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsValue;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = ImageData)]
type ImageDataExt;
#[wasm_bindgen(catch, constructor, js_class = ImageData)]
fn new(array: Uint8ClampedArray, sw: u32) -> Result<ImageDataExt, JsValue>;
}
let array = Uint8Array::new_with_length(bitmap.len() as u32);
array.copy_from(&bitmap);
let array = Uint8ClampedArray::new(&array);
ImageDataExt::new(array, width.get())
.map(JsValue::from)
.map(ImageData::unchecked_from_js)
};
#[cfg(not(target_feature = "atomics"))]
let result =
ImageData::new_with_u8_clamped_array(wasm_bindgen::Clamped(&bitmap), width.get());
// This should only throw an error if the buffer we pass's size is incorrect.
let image_data = result.unwrap();
for rect in damage {
// This can only throw an error if `data` is detached, which is impossible.
self.ctx
.put_image_data_with_dirty_x_and_dirty_y_and_dirty_width_and_dirty_height(
&image_data,
rect.x.into(),
rect.y.into(),
rect.x.into(),
rect.y.into(),
rect.width.get().into(),
rect.height.get().into(),
)
.unwrap();
}
self.buffer_presented = true;
Ok(())
}
/// Fetch the buffer from the window. /// Fetch the buffer from the window.
pub fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> { pub fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> {
let (width, height) = self
.size
.expect("Must set size of surface before calling `fetch()`");
let image_data = self let image_data = self
.ctx .ctx
.get_image_data(0., 0., self.width.into(), self.height.into()) .get_image_data(0., 0., width.get().into(), height.get().into())
.ok() .ok()
// TODO: Can also error if width or height are 0. // TODO: Can also error if width or height are 0.
.swbuf_err("`Canvas` contains pixels from a different origin")?; .swbuf_err("`Canvas` contains pixels from a different origin")?;
@ -157,49 +221,30 @@ impl<'a> BufferImpl<'a> {
&mut self.imp.buffer &mut self.imp.buffer
} }
pub fn age(&self) -> u8 {
if self.imp.buffer_presented {
1
} else {
0
}
}
/// Push the buffer to the canvas. /// Push the buffer to the canvas.
pub fn present(self) -> Result<(), SoftBufferError> { pub fn present(self) -> Result<(), SoftBufferError> {
// Create a bitmap from the buffer. let (width, height) = self
let bitmap: Vec<_> = self
.imp .imp
.buffer .size
.iter() .expect("Must set size of surface before calling `present()`");
.copied() self.imp.present_with_damage(&[Rect {
.flat_map(|pixel| [(pixel >> 16) as u8, (pixel >> 8) as u8, pixel as u8, 255]) x: 0,
.collect(); y: 0,
width,
height,
}])
}
#[cfg(target_feature = "atomics")] pub fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> {
let result = { self.imp.present_with_damage(damage)
use js_sys::{Uint8Array, Uint8ClampedArray};
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsValue;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = ImageData)]
type ImageDataExt;
#[wasm_bindgen(catch, constructor, js_class = ImageData)]
fn new(array: Uint8ClampedArray, sw: u32) -> Result<ImageDataExt, JsValue>;
}
let array = Uint8Array::new_with_length(bitmap.len() as u32);
array.copy_from(&bitmap);
let array = Uint8ClampedArray::new(&array);
ImageDataExt::new(array, self.imp.width)
.map(JsValue::from)
.map(ImageData::unchecked_from_js)
};
#[cfg(not(target_feature = "atomics"))]
let result =
ImageData::new_with_u8_clamped_array(wasm_bindgen::Clamped(&bitmap), self.imp.width);
// This should only throw an error if the buffer we pass's size is incorrect.
let image_data = result.unwrap();
// This can only throw an error if `data` is detached, which is impossible.
self.imp.ctx.put_image_data(&image_data, 0., 0.).unwrap();
Ok(())
} }
} }

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;
@ -27,6 +27,7 @@ struct Buffer {
pixels: NonNull<u32>, pixels: NonNull<u32>,
width: NonZeroI32, width: NonZeroI32,
height: NonZeroI32, height: NonZeroI32,
presented: bool,
} }
impl Drop for Buffer { impl Drop for Buffer {
@ -101,6 +102,7 @@ impl Buffer {
width, width,
height, height,
pixels, pixels,
presented: false,
} }
} }
@ -203,6 +205,30 @@ impl Win32Impl {
Ok(BufferImpl(self)) Ok(BufferImpl(self))
} }
fn present_with_damage(&mut self, damage: &[Rect]) -> Result<(), SoftBufferError> {
let buffer = self.buffer.as_mut().unwrap();
unsafe {
for rect in damage.iter().copied() {
let (x, y, width, height) = (|| {
Some((
i32::try_from(rect.x).ok()?,
i32::try_from(rect.y).ok()?,
i32::try_from(rect.width.get()).ok()?,
i32::try_from(rect.height.get()).ok()?,
))
})()
.ok_or(SoftBufferError::DamageOutOfRange { rect })?;
Gdi::BitBlt(self.dc, x, y, width, height, buffer.dc, x, y, Gdi::SRCCOPY);
}
// Validate the window.
Gdi::ValidateRect(self.window, ptr::null_mut());
}
buffer.presented = true;
Ok(())
}
/// Fetch the buffer from the window. /// Fetch the buffer from the window.
pub fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> { pub fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> {
let buffer = self.buffer.as_ref().unwrap(); let buffer = self.buffer.as_ref().unwrap();
@ -245,26 +271,27 @@ impl<'a> BufferImpl<'a> {
self.0.buffer.as_mut().unwrap().pixels_mut() self.0.buffer.as_mut().unwrap().pixels_mut()
} }
pub fn age(&self) -> u8 {
match self.0.buffer.as_ref() {
Some(buffer) if buffer.presented => 1,
_ => 0,
}
}
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, // We know width/height will be non-negative
0, width: buffer.width.try_into().unwrap(),
buffer.width.get(), height: buffer.height.try_into().unwrap(),
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,11 +6,16 @@
#![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};
use std::{fmt, io, mem, num::NonZeroU32, rc::Rc, slice}; use std::{
fmt, io, mem,
num::{NonZeroU16, NonZeroU32},
rc::Rc,
slice,
};
use x11_dl::xlib::Display; use x11_dl::xlib::Display;
use x11_dl::xlib_xcb::Xlib_xcb; use x11_dl::xlib_xcb::Xlib_xcb;
@ -110,11 +115,11 @@ pub struct X11Impl {
/// The buffer we draw to. /// The buffer we draw to.
buffer: Buffer, buffer: Buffer,
/// The current buffer width. /// Buffer has been presented.
width: u16, buffer_presented: bool,
/// The current buffer height. /// The current buffer width/height.
height: u16, size: Option<(NonZeroU16, NonZeroU16)>,
} }
/// The buffer that is being drawn to. /// The buffer that is being drawn to.
@ -256,8 +261,8 @@ impl X11Impl {
depth: geometry_reply.depth, depth: geometry_reply.depth,
visual_id, visual_id,
buffer, buffer,
width: 0, buffer_presented: false,
height: 0, size: None,
}) })
} }
@ -275,26 +280,22 @@ impl X11Impl {
); );
// Width and height should fit in u16. // Width and height should fit in u16.
let width: u16 = width let width: NonZeroU16 = width
.get()
.try_into() .try_into()
.or(Err(SoftBufferError::SizeOutOfRange { width, height }))?; .or(Err(SoftBufferError::SizeOutOfRange { width, height }))?;
let height: u16 = height let height: NonZeroU16 = height.try_into().or(Err(SoftBufferError::SizeOutOfRange {
.get() width: width.into(),
.try_into() height,
.or(Err(SoftBufferError::SizeOutOfRange { }))?;
width: NonZeroU32::new(width.into()).unwrap(),
height,
}))?;
if width != self.width || height != self.height { if self.size != Some((width, height)) {
self.buffer_presented = false;
self.buffer self.buffer
.resize(&self.display.connection, width, height) .resize(&self.display.connection, width.get(), height.get())
.swbuf_err("Failed to resize X11 buffer")?; .swbuf_err("Failed to resize X11 buffer")?;
// We successfully resized the buffer. // We successfully resized the buffer.
self.width = width; self.size = Some((width, height));
self.height = height;
} }
Ok(()) Ok(())
@ -315,6 +316,10 @@ impl X11Impl {
pub fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> { pub fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> {
log::trace!("fetch: window={:X}", self.window); log::trace!("fetch: window={:X}", self.window);
let (width, height) = self
.size
.expect("Must set size of surface before calling `fetch()`");
// TODO: Is it worth it to do SHM here? Probably not. // TODO: Is it worth it to do SHM here? Probably not.
let reply = self let reply = self
.display .display
@ -324,8 +329,8 @@ impl X11Impl {
self.window, self.window,
0, 0,
0, 0,
self.width, width.get(),
self.height, height.get(),
u32::MAX, u32::MAX,
) )
.swbuf_err("Failed to send image fetching request")? .swbuf_err("Failed to send image fetching request")?
@ -360,13 +365,25 @@ impl<'a> BufferImpl<'a> {
unsafe { self.0.buffer.buffer_mut() } unsafe { self.0.buffer.buffer_mut() }
} }
pub fn age(&self) -> u8 {
if self.0.buffer_presented {
1
} else {
0
}
}
/// 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;
let (surface_width, surface_height) = imp
.size
.expect("Must set size of surface before calling `present_with_damage()`");
log::trace!("present: window={:X}", imp.window); log::trace!("present: window={:X}", imp.window);
let result = match imp.buffer { match imp.buffer {
Buffer::Wire(ref wire) => { Buffer::Wire(ref wire) => {
// This is a suboptimal strategy, raise a stink in the debug logs. // This is a suboptimal strategy, raise a stink in the debug logs.
log::debug!("Falling back to non-SHM method for window drawing."); log::debug!("Falling back to non-SHM method for window drawing.");
@ -377,8 +394,8 @@ impl<'a> BufferImpl<'a> {
xproto::ImageFormat::Z_PIXMAP, xproto::ImageFormat::Z_PIXMAP,
imp.window, imp.window,
imp.gc, imp.gc,
imp.width, surface_width.get(),
imp.height, surface_height.get(),
0, 0,
0, 0,
0, 0,
@ -387,6 +404,7 @@ impl<'a> BufferImpl<'a> {
) )
.map(|c| c.ignore_error()) .map(|c| c.ignore_error())
.push_err() .push_err()
.swbuf_err("Failed to draw image to window")?;
} }
Buffer::Shm(ref mut shm) => { Buffer::Shm(ref mut shm) => {
@ -394,38 +412,69 @@ 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(|rect| {
imp.window, let (src_x, src_y, dst_x, dst_y, width, height) = (|| {
imp.gc, Some((
imp.width, u16::try_from(rect.x).ok()?,
imp.height, u16::try_from(rect.y).ok()?,
0, i16::try_from(rect.x).ok()?,
0, i16::try_from(rect.y).ok()?,
imp.width, u16::try_from(rect.width.get()).ok()?,
imp.height, u16::try_from(rect.height.get()).ok()?,
0, ))
0, })(
imp.depth, )
xproto::ImageFormat::Z_PIXMAP.into(), .ok_or(SoftBufferError::DamageOutOfRange { rect: *rect })?;
false, imp.display
segment_id, .connection
0, .shm_put_image(
) imp.window,
.push_err() imp.gc,
.map(|c| c.ignore_error()) surface_width.get(),
surface_height.get(),
src_x,
src_y,
width,
height,
dst_x,
dst_y,
imp.depth,
xproto::ImageFormat::Z_PIXMAP.into(),
false,
segment_id,
0,
)
.push_err()
.map(|c| c.ignore_error())
.swbuf_err("Failed to draw image to window")
})
.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)
}) .swbuf_err("Failed to draw image to window")
} else { })?;
Ok(())
} }
} }
}; }
result.swbuf_err("Failed to draw image to window") imp.buffer_presented = true;
Ok(())
}
pub fn present(self) -> Result<(), SoftBufferError> {
let (width, height) = self
.0
.size
.expect("Must set size of surface before calling `present()`");
self.present_with_damage(&[Rect {
x: 0,
y: 0,
width: width.into(),
height: height.into(),
}])
} }
} }