Merge pull request #99 from rust-windowing/damage
Add `Buffer::present_with_damage()` and `Buffer::age()`
This commit is contained in:
commit
efdfb529ef
9 changed files with 416 additions and 171 deletions
10
src/cg.rs
10
src/cg.rs
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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>>),
|
||||||
|
|
||||||
|
|
|
||||||
57
src/lib.rs
57
src/lib.rs
|
|
@ -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> {
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
153
src/web.rs
153
src/web.rs
|
|
@ -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(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
63
src/win32.rs
63
src/win32.rs
|
|
@ -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(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
155
src/x11.rs
155
src/x11.rs
|
|
@ -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(),
|
||||||
|
}])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue