Owned pixel buffer for no-copy presentation

This is based on the API that will be used for no-copy presentation. But
wraps it in `set_buffer`.

This also fixes the Wayland buffer code to set `self.width` and
`self.height` on resize, and set the length of the shared memory file
when the buffer is created.

Co-authored-by: jtnunley <jtnunley01@gmail.com>
This commit is contained in:
Ian Douglas Scott 2023-04-06 00:30:59 -07:00 committed by GitHub
parent e5d546ff9e
commit a09e4cf679
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1176 additions and 438 deletions

View file

@ -17,6 +17,17 @@ jobs:
- name: Check Formatting - name: Check Formatting
run: cargo +stable fmt --all -- --check run: cargo +stable fmt --all -- --check
miri-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: hecrj/setup-rust-action@v1
with:
rust-version: nightly
components: miri
- name: Run tests with miri
run: cargo +nightly miri test
tests: tests:
name: Tests name: Tests
strategy: strategy:

View file

@ -14,7 +14,7 @@ rust-version = "1.64.0"
[features] [features]
default = ["x11", "wayland", "wayland-dlopen"] default = ["x11", "wayland", "wayland-dlopen"]
wayland = ["wayland-backend", "wayland-client", "nix", "fastrand"] wayland = ["wayland-backend", "wayland-client", "memmap2", "nix", "fastrand"]
wayland-dlopen = ["wayland-sys/dlopen"] wayland-dlopen = ["wayland-sys/dlopen"]
x11 = ["bytemuck", "nix", "x11rb", "x11-dl"] x11 = ["bytemuck", "nix", "x11rb", "x11-dl"]
@ -25,6 +25,7 @@ thiserror = "1.0.30"
[target.'cfg(all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))))'.dependencies] [target.'cfg(all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))))'.dependencies]
bytemuck = { version = "1.12.3", optional = true } bytemuck = { version = "1.12.3", optional = true }
memmap2 = { version = "0.5.8", optional = true }
nix = { version = "0.26.1", optional = true } nix = { version = "0.26.1", optional = true }
wayland-backend = { version = "0.1.0", features = ["client_system"], optional = true } wayland-backend = { version = "0.1.0", features = ["client_system"], optional = true }
wayland-client = { version = "0.30.0", optional = true } wayland-client = { version = "0.30.0", optional = true }
@ -40,6 +41,7 @@ version = "0.45.0"
features = ["Win32_Graphics_Gdi", "Win32_UI_WindowsAndMessaging", "Win32_Foundation"] features = ["Win32_Graphics_Gdi", "Win32_UI_WindowsAndMessaging", "Win32_Foundation"]
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
bytemuck = { version = "1.12.3", features = ["extern_crate_alloc"] }
cocoa = "0.24.0" cocoa = "0.24.0"
core-graphics = "0.22.3" core-graphics = "0.22.3"
foreign-types = "0.3.0" foreign-types = "0.3.0"

View file

@ -58,6 +58,7 @@ To run an example with the web backend: `cargo run-wasm --example winit`
Example Example
== ==
```rust,no_run ```rust,no_run
use std::num::NonZeroU32;
use winit::event::{Event, WindowEvent}; use winit::event::{Event, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop}; use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder; use winit::window::WindowBuilder;
@ -77,21 +78,25 @@ fn main() {
let size = window.inner_size(); let size = window.inner_size();
(size.width, size.height) (size.width, size.height)
}; };
let buffer = (0..((width * height) as usize)) surface
.map(|index| { .resize(
let y = index / (width as usize); NonZeroU32::new(width).unwrap(),
let x = index % (width as usize); NonZeroU32::new(height).unwrap(),
let red = x % 255; )
let green = y % 255; .unwrap();
let blue = (x * y) % 255;
let color = blue | (green << 8) | (red << 16); let mut buffer = surface.buffer_mut().unwrap();
for index in 0..(width * height) {
let y = index / width;
let x = index % width;
let red = x % 255;
let green = y % 255;
let blue = (x * y) % 255;
color as u32 buffer[index as usize] = blue | (green << 8) | (red << 16);
}) }
.collect::<Vec<_>>();
surface.set_buffer(&buffer, width as u16, height as u16); buffer.present().unwrap();
} }
Event::WindowEvent { Event::WindowEvent {
event: WindowEvent::CloseRequested, event: WindowEvent::CloseRequested,

View file

@ -2,6 +2,7 @@ use instant::Instant;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use rayon::prelude::*; use rayon::prelude::*;
use std::f64::consts::PI; use std::f64::consts::PI;
use std::num::NonZeroU32;
use winit::event::{Event, WindowEvent}; use winit::event::{Event, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop}; use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder; use winit::window::WindowBuilder;
@ -47,8 +48,17 @@ fn main() {
frames = pre_render_frames(width as usize, height as usize); frames = pre_render_frames(width as usize, height as usize);
}; };
let buffer = &frames[((elapsed * 60.0).round() as usize).clamp(0, 59)]; let frame = &frames[((elapsed * 60.0).round() as usize).clamp(0, 59)];
surface.set_buffer(buffer.as_slice(), width as u16, height as u16);
surface
.resize(
NonZeroU32::new(width).unwrap(),
NonZeroU32::new(height).unwrap(),
)
.unwrap();
let mut buffer = surface.buffer_mut().unwrap();
buffer.copy_from_slice(frame);
buffer.present().unwrap();
} }
Event::MainEventsCleared => { Event::MainEventsCleared => {
window.request_redraw(); window.request_redraw();

View file

@ -1,4 +1,5 @@
use image::GenericImageView; use image::GenericImageView;
use std::num::NonZeroU32;
use winit::event::{Event, WindowEvent}; use winit::event::{Event, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop}; use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder; use winit::window::WindowBuilder;
@ -6,16 +7,6 @@ use winit::window::WindowBuilder;
fn main() { fn main() {
//see fruit.jpg.license for the license of fruit.jpg //see fruit.jpg.license for the license of fruit.jpg
let fruit = image::load_from_memory(include_bytes!("fruit.jpg")).unwrap(); let fruit = image::load_from_memory(include_bytes!("fruit.jpg")).unwrap();
let buffer = fruit
.pixels()
.map(|(_x, _y, pixel)| {
let red = pixel.0[0] as u32;
let green = pixel.0[1] as u32;
let blue = pixel.0[2] as u32;
blue | (green << 8) | (red << 16)
})
.collect::<Vec<_>>();
let event_loop = EventLoop::new(); let event_loop = EventLoop::new();
let window = WindowBuilder::new() let window = WindowBuilder::new()
@ -45,7 +36,25 @@ fn main() {
match event { match event {
Event::RedrawRequested(window_id) if window_id == window.id() => { Event::RedrawRequested(window_id) if window_id == window.id() => {
surface.set_buffer(&buffer, fruit.width() as u16, fruit.height() as u16); surface
.resize(
NonZeroU32::new(fruit.width()).unwrap(),
NonZeroU32::new(fruit.height()).unwrap(),
)
.unwrap();
let mut buffer = surface.buffer_mut().unwrap();
let width = fruit.width() as usize;
for (x, y, pixel) in fruit.pixels() {
let red = pixel.0[0] as u32;
let green = pixel.0[1] as u32;
let blue = pixel.0[2] as u32;
let color = blue | (green << 8) | (red << 16);
buffer[y as usize * width + x as usize] = color;
}
buffer.present().unwrap();
} }
Event::WindowEvent { Event::WindowEvent {
event: WindowEvent::CloseRequested, event: WindowEvent::CloseRequested,

View file

@ -3,6 +3,7 @@
#[cfg(all(feature = "x11", any(target_os = "linux", target_os = "freebsd")))] #[cfg(all(feature = "x11", any(target_os = "linux", target_os = "freebsd")))]
mod example { mod example {
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XcbDisplayHandle, XcbWindowHandle}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XcbDisplayHandle, XcbWindowHandle};
use std::num::NonZeroU32;
use x11rb::{ use x11rb::{
connection::Connection, connection::Connection,
protocol::{ protocol::{
@ -12,6 +13,8 @@ mod example {
xcb_ffi::XCBConnection, xcb_ffi::XCBConnection,
}; };
const RED: u32 = 255 << 16;
pub(crate) fn run() { pub(crate) fn run() {
// Create a new XCB connection // Create a new XCB connection
let (conn, screen) = XCBConnection::connect(None).expect("Failed to connect to X server"); let (conn, screen) = XCBConnection::connect(None).expect("Failed to connect to X server");
@ -96,13 +99,15 @@ mod example {
match event { match event {
Event::Expose(_) => { Event::Expose(_) => {
// Draw a width x height red rectangle. // Draw a width x height red rectangle.
let red = 255 << 16; surface
let source = std::iter::repeat(red) .resize(
.take((width as usize * height as usize) as _) NonZeroU32::new(width.into()).unwrap(),
.collect::<Vec<_>>(); NonZeroU32::new(height.into()).unwrap(),
)
// Draw the buffer. .unwrap();
surface.set_buffer(&source, width, height); let mut buffer = surface.buffer_mut().unwrap();
buffer.fill(RED);
buffer.present().unwrap();
} }
Event::ConfigureNotify(configure_notify) => { Event::ConfigureNotify(configure_notify) => {
width = configure_notify.width; width = configure_notify.width;

View file

@ -1,3 +1,4 @@
use std::num::NonZeroU32;
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}; use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop}; use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder; use winit::window::WindowBuilder;
@ -43,7 +44,6 @@ fn main() {
let context = unsafe { softbuffer::Context::new(&window) }.unwrap(); let context = unsafe { softbuffer::Context::new(&window) }.unwrap();
let mut surface = unsafe { softbuffer::Surface::new(&context, &window) }.unwrap(); let mut surface = unsafe { softbuffer::Surface::new(&context, &window) }.unwrap();
let mut buffer = Vec::new();
let mut flag = false; let mut flag = false;
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, _, control_flow| {
@ -54,19 +54,21 @@ fn main() {
// Grab the window's client area dimensions // Grab the window's client area dimensions
let (width, height) = { let (width, height) = {
let size = window.inner_size(); let size = window.inner_size();
(size.width as usize, size.height as usize) (size.width, size.height)
}; };
// Resize the off-screen buffer if the window size has changed // Resize surface if needed
if buffer.len() != width * height { surface
buffer.resize(width * height, 0); .resize(
} NonZeroU32::new(width).unwrap(),
NonZeroU32::new(height).unwrap(),
)
.unwrap();
// Draw something in the offscreen buffer // Draw something in the window
redraw(&mut buffer, width, height, flag); let mut buffer = surface.buffer_mut().unwrap();
redraw(&mut buffer, width as usize, height as usize, flag);
// Blit the offscreen buffer to the window's client area buffer.present().unwrap();
surface.set_buffer(&buffer, width as u16, height as u16);
} }
Event::WindowEvent { Event::WindowEvent {

View file

@ -1,3 +1,4 @@
use std::num::NonZeroU32;
use winit::event::{Event, WindowEvent}; use winit::event::{Event, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop}; use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder; use winit::window::WindowBuilder;
@ -32,21 +33,26 @@ fn main() {
let size = window.inner_size(); let size = window.inner_size();
(size.width, size.height) (size.width, size.height)
}; };
let buffer = (0..((width * height) as usize))
.map(|index| {
let y = index / (width as usize);
let x = index % (width as usize);
let red = x % 255;
let green = y % 255;
let blue = (x * y) % 255;
let color = blue | (green << 8) | (red << 16); surface
.resize(
NonZeroU32::new(width).unwrap(),
NonZeroU32::new(height).unwrap(),
)
.unwrap();
color as u32 let mut buffer = surface.buffer_mut().unwrap();
}) for index in 0..(width * height) {
.collect::<Vec<_>>(); let y = index / width;
let x = index % width;
let red = x % 255;
let green = y % 255;
let blue = (x * y) % 255;
surface.set_buffer(&buffer, width as u16, height as u16); buffer[index as usize] = blue | (green << 8) | (red << 16);
}
buffer.present().unwrap();
} }
Event::WindowEvent { Event::WindowEvent {
event: WindowEvent::CloseRequested, event: WindowEvent::CloseRequested,

View file

@ -1,3 +1,4 @@
use std::num::NonZeroU32;
use winit::event::{Event, WindowEvent}; use winit::event::{Event, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop}; use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder; use winit::window::WindowBuilder;
@ -31,21 +32,25 @@ fn main() {
match event { match event {
Event::RedrawRequested(window_id) if window_id == window.id() => { Event::RedrawRequested(window_id) if window_id == window.id() => {
let buffer = (0..(BUFFER_WIDTH * BUFFER_HEIGHT)) surface
.map(|index| { .resize(
let y = index / BUFFER_WIDTH; NonZeroU32::new(BUFFER_WIDTH as u32).unwrap(),
let x = index % BUFFER_WIDTH; NonZeroU32::new(BUFFER_HEIGHT as u32).unwrap(),
let red = x % 255; )
let green = y % 255; .unwrap();
let blue = (x * y) % 255;
let mut buffer = surface.buffer_mut().unwrap();
for y in 0..BUFFER_HEIGHT {
for x in 0..BUFFER_WIDTH {
let red = x as u32 % 255;
let green = y as u32 % 255;
let blue = (x as u32 * y as u32) % 255;
let color = blue | (green << 8) | (red << 16); let color = blue | (green << 8) | (red << 16);
buffer[y * BUFFER_WIDTH + x] = color;
color as u32 }
}) }
.collect::<Vec<_>>(); buffer.present().unwrap();
surface.set_buffer(&buffer, BUFFER_WIDTH as u16, BUFFER_HEIGHT as u16);
} }
Event::WindowEvent { Event::WindowEvent {
event: WindowEvent::CloseRequested, event: WindowEvent::CloseRequested,

View file

@ -12,11 +12,23 @@ use cocoa::base::{id, nil};
use cocoa::quartzcore::{transaction, CALayer, ContentsGravity}; use cocoa::quartzcore::{transaction, CALayer, ContentsGravity};
use foreign_types::ForeignType; use foreign_types::ForeignType;
use std::num::NonZeroU32;
use std::sync::Arc; use std::sync::Arc;
struct Buffer(Vec<u32>);
impl AsRef<[u8]> for Buffer {
fn as_ref(&self) -> &[u8] {
bytemuck::cast_slice(&self.0)
}
}
pub struct CGImpl { pub struct CGImpl {
layer: CALayer, layer: CALayer,
window: id, window: id,
color_space: CGColorSpace,
width: u32,
height: u32,
} }
impl CGImpl { impl CGImpl {
@ -35,22 +47,55 @@ impl CGImpl {
view.addSubview_(subview); // retains subview (+1) = 2 view.addSubview_(subview); // retains subview (+1) = 2
let _: () = msg_send![subview, release]; // releases subview (-1) = 1 let _: () = msg_send![subview, release]; // releases subview (-1) = 1
} }
Ok(Self { layer, window }) let color_space = CGColorSpace::create_device_rgb();
Ok(Self {
layer,
window,
color_space,
width: 0,
height: 0,
})
} }
pub(crate) unsafe fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) { pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {
let color_space = CGColorSpace::create_device_rgb(); self.width = width.get();
let data = self.height = height.get();
unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const u8, buffer.len() * 4) } Ok(())
.to_vec(); }
let data_provider = CGDataProvider::from_buffer(Arc::new(data));
pub fn buffer_mut(&mut self) -> Result<BufferImpl, SoftBufferError> {
Ok(BufferImpl {
buffer: vec![0; self.width as usize * self.height as usize],
imp: self,
})
}
}
pub struct BufferImpl<'a> {
imp: &'a mut CGImpl,
buffer: Vec<u32>,
}
impl<'a> BufferImpl<'a> {
#[inline]
pub fn pixels(&self) -> &[u32] {
&self.buffer
}
#[inline]
pub fn pixels_mut(&mut self) -> &mut [u32] {
&mut self.buffer
}
pub fn present(self) -> Result<(), SoftBufferError> {
let data_provider = CGDataProvider::from_buffer(Arc::new(Buffer(self.buffer)));
let image = CGImage::new( let image = CGImage::new(
width as usize, self.imp.width as usize,
height as usize, self.imp.height as usize,
8, 8,
32, 32,
(width * 4) as usize, (self.imp.width * 4) as usize,
&color_space, &self.imp.color_space,
kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
&data_provider, &data_provider,
false, false,
@ -64,12 +109,15 @@ impl CGImpl {
transaction::set_disable_actions(true); transaction::set_disable_actions(true);
unsafe { unsafe {
self.layer self.imp
.set_contents_scale(self.window.backingScaleFactor()); .layer
self.layer.set_contents(image.as_ptr() as id); .set_contents_scale(self.imp.window.backingScaleFactor());
self.imp.layer.set_contents(image.as_ptr() as id);
}; };
transaction::commit(); transaction::commit();
Ok(())
} }
} }

View file

@ -1,8 +1,10 @@
use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
use std::error::Error; use std::error::Error;
use std::num::NonZeroU32;
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug)] #[derive(Error, Debug)]
#[allow(missing_docs)] // TODO
#[non_exhaustive] #[non_exhaustive]
pub enum SoftBufferError { pub enum SoftBufferError {
#[error( #[error(
@ -27,6 +29,12 @@ pub enum SoftBufferError {
#[error("The provided display handle is null.")] #[error("The provided display handle is null.")]
IncompleteDisplayHandle, IncompleteDisplayHandle,
#[error("Surface size {width}x{height} out of range for backend.")]
SizeOutOfRange {
width: NonZeroU32,
height: NonZeroU32,
},
#[error("Platform error")] #[error("Platform error")]
PlatformError(Option<String>, Option<Box<dyn Error>>), PlatformError(Option<String>, Option<Box<dyn Error>>),
} }

View file

@ -1,5 +1,6 @@
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
#![deny(unsafe_op_in_unsafe_fn)] #![deny(unsafe_op_in_unsafe_fn)]
#![warn(missing_docs)]
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
#[macro_use] #[macro_use]
@ -20,9 +21,13 @@ mod win32;
mod x11; mod x11;
mod error; mod error;
mod util;
use std::marker::PhantomData;
use std::num::NonZeroU32;
use std::ops;
#[cfg(any(wayland_platform, x11_platform))] #[cfg(any(wayland_platform, x11_platform))]
use std::sync::Arc; use std::rc::Rc;
pub use error::SoftBufferError; pub use error::SoftBufferError;
@ -35,6 +40,7 @@ use raw_window_handle::{
pub struct Context { pub struct Context {
/// The inner static dispatch object. /// The inner static dispatch object.
context_impl: ContextDispatch, context_impl: ContextDispatch,
_marker: PhantomData<*mut ()>,
} }
/// A macro for creating the enum used to statically dispatch to the platform-specific implementation. /// A macro for creating the enum used to statically dispatch to the platform-specific implementation.
@ -42,7 +48,7 @@ macro_rules! make_dispatch {
( (
$( $(
$(#[$attr:meta])* $(#[$attr:meta])*
$name: ident ($context_inner: ty, $surface_inner : ty), $name: ident ($context_inner: ty, $surface_inner: ty, $buffer_inner: ty),
)* )*
) => { ) => {
enum ContextDispatch { enum ContextDispatch {
@ -71,11 +77,58 @@ macro_rules! make_dispatch {
} }
impl SurfaceDispatch { impl SurfaceDispatch {
unsafe fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) { pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {
match self { match self {
$( $(
$(#[$attr])* $(#[$attr])*
Self::$name(inner) => unsafe { inner.set_buffer(buffer, width, height) }, Self::$name(inner) => inner.resize(width, height),
)*
}
}
pub fn buffer_mut(&mut self) -> Result<BufferDispatch, SoftBufferError> {
match self {
$(
$(#[$attr])*
Self::$name(inner) => Ok(BufferDispatch::$name(inner.buffer_mut()?)),
)*
}
}
}
enum BufferDispatch<'a> {
$(
$(#[$attr])*
$name($buffer_inner),
)*
}
impl<'a> BufferDispatch<'a> {
#[inline]
pub fn pixels(&self) -> &[u32] {
match self {
$(
$(#[$attr])*
Self::$name(inner) => inner.pixels(),
)*
}
}
#[inline]
pub fn pixels_mut(&mut self) -> &mut [u32] {
match self {
$(
$(#[$attr])*
Self::$name(inner) => inner.pixels_mut(),
)*
}
}
pub fn present(self) -> Result<(), SoftBufferError> {
match self {
$(
$(#[$attr])*
Self::$name(inner) => inner.present(),
)* )*
} }
} }
@ -83,19 +136,21 @@ macro_rules! make_dispatch {
}; };
} }
// XXX empty enum with generic bound is invalid?
make_dispatch! { make_dispatch! {
#[cfg(x11_platform)] #[cfg(x11_platform)]
X11(Arc<x11::X11DisplayImpl>, x11::X11Impl), X11(Rc<x11::X11DisplayImpl>, x11::X11Impl, x11::BufferImpl<'a>),
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
Wayland(std::sync::Arc<wayland::WaylandDisplayImpl>, wayland::WaylandImpl), Wayland(Rc<wayland::WaylandDisplayImpl>, wayland::WaylandImpl, wayland::BufferImpl<'a>),
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
Win32((), win32::Win32Impl), Win32((), win32::Win32Impl, win32::BufferImpl<'a>),
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
CG((), cg::CGImpl), CG((), cg::CGImpl, cg::BufferImpl<'a>),
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
Web(web::WebDisplayImpl, web::WebImpl), Web(web::WebDisplayImpl, web::WebImpl, web::BufferImpl<'a>),
#[cfg(target_os = "redox")] #[cfg(target_os = "redox")]
Orbital((), orbital::OrbitalImpl), Orbital((), orbital::OrbitalImpl, orbital::BufferImpl<'a>),
} }
impl Context { impl Context {
@ -117,17 +172,15 @@ impl Context {
let imple: ContextDispatch = match raw_display_handle { let imple: ContextDispatch = match raw_display_handle {
#[cfg(x11_platform)] #[cfg(x11_platform)]
RawDisplayHandle::Xlib(xlib_handle) => unsafe { RawDisplayHandle::Xlib(xlib_handle) => unsafe {
ContextDispatch::X11(Arc::new(x11::X11DisplayImpl::from_xlib(xlib_handle)?)) ContextDispatch::X11(Rc::new(x11::X11DisplayImpl::from_xlib(xlib_handle)?))
}, },
#[cfg(x11_platform)] #[cfg(x11_platform)]
RawDisplayHandle::Xcb(xcb_handle) => unsafe { RawDisplayHandle::Xcb(xcb_handle) => unsafe {
ContextDispatch::X11(Arc::new(x11::X11DisplayImpl::from_xcb(xcb_handle)?)) ContextDispatch::X11(Rc::new(x11::X11DisplayImpl::from_xcb(xcb_handle)?))
}, },
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
RawDisplayHandle::Wayland(wayland_handle) => unsafe { RawDisplayHandle::Wayland(wayland_handle) => unsafe {
ContextDispatch::Wayland(Arc::new(wayland::WaylandDisplayImpl::new( ContextDispatch::Wayland(Rc::new(wayland::WaylandDisplayImpl::new(wayland_handle)?))
wayland_handle,
)?))
}, },
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
RawDisplayHandle::Windows(_) => ContextDispatch::Win32(()), RawDisplayHandle::Windows(_) => ContextDispatch::Win32(()),
@ -149,17 +202,20 @@ impl Context {
Ok(Self { Ok(Self {
context_impl: imple, context_impl: imple,
_marker: PhantomData,
}) })
} }
} }
/// 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.
surface_impl: Box<SurfaceDispatch>, surface_impl: Box<SurfaceDispatch>,
_marker: PhantomData<*mut ()>,
} }
impl Surface { impl Surface {
/// Creates a new instance of this struct, using the provided window and display. /// Creates a new surface for the context for the provided window.
/// ///
/// # Safety /// # Safety
/// ///
@ -172,7 +228,7 @@ impl Surface {
unsafe { Self::from_raw(context, window.raw_window_handle()) } unsafe { Self::from_raw(context, window.raw_window_handle()) }
} }
/// Creates a new instance of this struct, using the provided raw window and display handles /// Creates a new surface for the context for the provided raw window handle.
/// ///
/// # Safety /// # Safety
/// ///
@ -232,38 +288,77 @@ impl Surface {
Ok(Self { Ok(Self {
surface_impl: Box::new(imple), surface_impl: Box::new(imple),
_marker: PhantomData,
}) })
} }
/// Shows the given buffer with the given width and height on the window corresponding to this /// Set the size of the buffer that will be returned by [`Surface::buffer_mut`].
/// graphics context. Panics if buffer.len() ≠ width*height. If the size of the buffer does
/// not match the size of the window, the buffer is drawn in the upper-left corner of the window.
/// It is recommended in most production use cases to have the buffer fill the entire window.
/// Use your windowing library to find the size of the window.
/// ///
/// The format of the buffer is as follows. There is one u32 in the buffer for each pixel in /// If the size of the buffer does not match the size of the window, the buffer is drawn
/// the area to draw. The first entry is the upper-left most pixel. The second is one to the right /// in the upper-left corner of the window. It is recommended in most production use cases
/// etc. (Row-major top to bottom left to right one u32 per pixel). Within each u32 the highest /// to have the buffer fill the entire window. Use your windowing library to find the size
/// order 8 bits are to be set to 0. The next highest order 8 bits are the red channel, then the /// of the window.
/// green channel, and then the blue channel in the lowest-order 8 bits. See the examples for pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {
/// one way to build this format using bitwise operations. self.surface_impl.resize(width, height)
/// }
/// --------
/// /// Return a [`Buffer`] that the next frame should be rendered into. The size must
/// Pixel format (u32): /// be set with [`Surface::resize`] first. The initial contents of the buffer may be zeroed, or
/// /// may contain a previous frame.
/// 00000000RRRRRRRRGGGGGGGGBBBBBBBB pub fn buffer_mut(&mut self) -> Result<Buffer, SoftBufferError> {
/// Ok(Buffer {
/// 0: Bit is 0 buffer_impl: self.surface_impl.buffer_mut()?,
/// R: Red channel _marker: PhantomData,
/// G: Green channel })
/// B: Blue channel }
}
/// A buffer that can be written to by the CPU and presented to the window.
///
/// This derefs to a `[u32]`, which depending on the backend may be a mapping into shared memory
/// accessible to the display server, so presentation doesn't require any (client-side) copying.
///
/// This trusts the display server not to mutate the buffer, which could otherwise be unsound.
///
/// # Data representation
///
/// The format of the buffer is as follows. There is one `u32` in the buffer for each pixel in
/// the area to draw. The first entry is the upper-left most pixel. The second is one to the right
/// etc. (Row-major top to bottom left to right one `u32` per pixel). Within each `u32` the highest
/// order 8 bits are to be set to 0. The next highest order 8 bits are the red channel, then the
/// green channel, and then the blue channel in the lowest-order 8 bits. See the examples for
/// one way to build this format using bitwise operations.
///
/// --------
///
/// Pixel format (`u32`):
///
/// 00000000RRRRRRRRGGGGGGGGBBBBBBBB
///
/// 0: Bit is 0
/// R: Red channel
/// G: Green channel
/// B: Blue channel
///
/// # Platform dependent behavior
/// No-copy presentation is currently supported on:
/// - Wayland
/// - X, when XShm is available
/// - Win32
/// - Orbital, when buffer size matches window size
/// Currently [`Buffer::present`] must block copying image data on:
/// - Web
/// - macOS
pub struct Buffer<'a> {
buffer_impl: BufferDispatch<'a>,
_marker: PhantomData<*mut ()>,
}
impl<'a> Buffer<'a> {
/// Presents buffer to the window.
/// ///
/// # Platform dependent behavior /// # Platform dependent behavior
/// ///
/// This section of the documentation details how some platforms may behave when [`set_buffer`](Surface::set_buffer)
/// is called.
///
/// ## Wayland /// ## Wayland
/// ///
/// On Wayland, calling this function may send requests to the underlying `wl_surface`. The /// On Wayland, calling this function may send requests to the underlying `wl_surface`. The
@ -272,15 +367,24 @@ impl Surface {
/// ///
/// If the caller wishes to synchronize other surface/window changes, such requests must be sent to the /// If the caller wishes to synchronize other surface/window changes, such requests must be sent to the
/// Wayland compositor before calling this function. /// Wayland compositor before calling this function.
#[inline] pub fn present(self) -> Result<(), SoftBufferError> {
pub fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) { self.buffer_impl.present()
if (width as usize) * (height as usize) != buffer.len() { }
panic!("The size of the passed buffer is not the correct size. Its length must be exactly width*height."); }
}
unsafe { impl<'a> ops::Deref for Buffer<'a> {
self.surface_impl.set_buffer(buffer, width, height); type Target = [u32];
}
#[inline]
fn deref(&self) -> &[u32] {
self.buffer_impl.pixels()
}
}
impl<'a> ops::DerefMut for Buffer<'a> {
#[inline]
fn deref_mut(&mut self) -> &mut [u32] {
self.buffer_impl.pixels_mut()
} }
} }

View file

@ -1,11 +1,12 @@
use raw_window_handle::OrbitalWindowHandle; use raw_window_handle::OrbitalWindowHandle;
use std::{cmp, slice, str}; use std::{cmp, num::NonZeroU32, slice, str};
use crate::SoftBufferError; use crate::SoftBufferError;
struct OrbitalMap { struct OrbitalMap {
address: usize, address: usize,
size: usize, size: usize,
size_unaligned: usize,
} }
impl OrbitalMap { impl OrbitalMap {
@ -27,7 +28,19 @@ impl OrbitalMap {
)? )?
}; };
Ok(Self { address, size }) Ok(Self {
address,
size,
size_unaligned,
})
}
unsafe fn data(&self) -> &[u32] {
unsafe { slice::from_raw_parts(self.address as *const u32, self.size_unaligned / 4) }
}
unsafe fn data_mut(&self) -> &mut [u32] {
unsafe { slice::from_raw_parts_mut(self.address as *mut u32, self.size_unaligned / 4) }
} }
} }
@ -42,50 +55,79 @@ impl Drop for OrbitalMap {
pub struct OrbitalImpl { pub struct OrbitalImpl {
handle: OrbitalWindowHandle, handle: OrbitalWindowHandle,
width: u32,
height: u32,
} }
impl OrbitalImpl { impl OrbitalImpl {
pub fn new(handle: OrbitalWindowHandle) -> Result<Self, SoftBufferError> { pub fn new(handle: OrbitalWindowHandle) -> Result<Self, SoftBufferError> {
Ok(Self { handle }) Ok(Self {
handle,
width: 0,
height: 0,
})
} }
pub(crate) unsafe fn set_buffer(&mut self, buffer: &[u32], width_u16: u16, height_u16: u16) { pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {
let window_fd = self.handle.window as usize; self.width = width.get();
self.height = height.get();
Ok(())
}
// Read the current width and size fn window_fd(&self) -> usize {
self.handle.window as usize
}
// Read the current width and size
fn window_size(&self) -> (usize, usize) {
let mut window_width = 0; let mut window_width = 0;
let mut window_height = 0; let mut window_height = 0;
{
let mut buf: [u8; 4096] = [0; 4096]; let mut buf: [u8; 4096] = [0; 4096];
let count = syscall::fpath(window_fd, &mut buf).unwrap(); let count = syscall::fpath(self.window_fd(), &mut buf).unwrap();
let path = str::from_utf8(&buf[..count]).unwrap(); let path = str::from_utf8(&buf[..count]).unwrap();
// orbital:/x/y/w/h/t // orbital:/x/y/w/h/t
let mut parts = path.split('/').skip(3); let mut parts = path.split('/').skip(3);
if let Some(w) = parts.next() { if let Some(w) = parts.next() {
window_width = w.parse::<usize>().unwrap_or(0); window_width = w.parse::<usize>().unwrap_or(0);
}
if let Some(h) = parts.next() {
window_height = h.parse::<usize>().unwrap_or(0);
}
} }
if let Some(h) = parts.next() {
window_height = h.parse::<usize>().unwrap_or(0);
}
(window_width, window_height)
}
pub fn buffer_mut(&mut self) -> Result<BufferImpl, SoftBufferError> {
let (window_width, window_height) = self.window_size();
let pixels = if self.width as usize == window_width && self.height as usize == window_height
{
Pixels::Mapping(
unsafe { OrbitalMap::new(self.window_fd(), window_width * window_height * 4) }
.expect("failed to map orbital window"),
)
} else {
Pixels::Buffer(vec![0; self.width as usize * self.height as usize])
};
Ok(BufferImpl { imp: self, pixels })
}
fn set_buffer(&self, buffer: &[u32], width_u32: u32, height_u32: u32) {
// Read the current width and size
let (window_width, window_height) = self.window_size();
{ {
// Map window buffer // Map window buffer
let window_map = let window_map =
unsafe { OrbitalMap::new(window_fd, window_width * window_height * 4) } unsafe { OrbitalMap::new(self.window_fd(), window_width * window_height * 4) }
.expect("failed to map orbital window"); .expect("failed to map orbital window");
// Window buffer is u32 color data in 0xAABBGGRR format // Window buffer is u32 color data in 0xAABBGGRR format
let window_data = unsafe { let window_data = unsafe { window_map.data_mut() };
slice::from_raw_parts_mut(
window_map.address as *mut u32,
window_width * window_height,
)
};
// Copy each line, cropping to fit // Copy each line, cropping to fit
let width = width_u16 as usize; let width = width_u32 as usize;
let height = height_u16 as usize; let height = height_u32 as usize;
let min_width = cmp::min(width, window_width); let min_width = cmp::min(width, window_width);
let min_height = cmp::min(height, window_height); let min_height = cmp::min(height, window_height);
for y in 0..min_height { for y in 0..min_height {
@ -99,6 +141,49 @@ impl OrbitalImpl {
} }
// Tell orbital to show the latest window data // Tell orbital to show the latest window data
syscall::fsync(window_fd).expect("failed to sync orbital window"); syscall::fsync(self.window_fd()).expect("failed to sync orbital window");
}
}
enum Pixels {
Mapping(OrbitalMap),
Buffer(Vec<u32>),
}
pub struct BufferImpl<'a> {
imp: &'a mut OrbitalImpl,
pixels: Pixels,
}
impl<'a> BufferImpl<'a> {
#[inline]
pub fn pixels(&self) -> &[u32] {
match &self.pixels {
Pixels::Mapping(mapping) => unsafe { mapping.data() },
Pixels::Buffer(buffer) => buffer,
}
}
#[inline]
pub fn pixels_mut(&mut self) -> &mut [u32] {
match &mut self.pixels {
Pixels::Mapping(mapping) => unsafe { mapping.data_mut() },
Pixels::Buffer(buffer) => buffer,
}
}
pub fn present(self) -> Result<(), SoftBufferError> {
match self.pixels {
Pixels::Mapping(mapping) => {
drop(mapping);
syscall::fsync(self.imp.window_fd()).expect("failed to sync orbital window");
}
Pixels::Buffer(buffer) => {
self.imp
.set_buffer(&buffer, self.imp.width, self.imp.height);
}
}
Ok(())
} }
} }

63
src/util.rs Normal file
View file

@ -0,0 +1,63 @@
// Not needed on all platforms
#![allow(dead_code)]
use crate::SoftBufferError;
/// Takes a mutable reference to a container and a function deriving a
/// reference into it, and stores both, making it possible to get back the
/// reference to the container once the other reference is no longer needed.
///
/// This should be consistent with stacked borrow rules, and miri seems to
/// accept it at least in simple cases.
pub struct BorrowStack<'a, T: 'static + ?Sized, U: 'static + ?Sized> {
container: *mut T,
member: *mut U,
_phantom: std::marker::PhantomData<&'a mut T>,
}
impl<'a, T: 'static + ?Sized, U: 'static + ?Sized> BorrowStack<'a, T, U> {
pub fn new<F>(container: &'a mut T, f: F) -> Result<Self, SoftBufferError>
where
F: for<'b> FnOnce(&'b mut T) -> Result<&'b mut U, SoftBufferError>,
{
let container = container as *mut T;
let member = f(unsafe { &mut *container })? as *mut U;
Ok(Self {
container,
member,
_phantom: std::marker::PhantomData,
})
}
pub fn member(&self) -> &U {
unsafe { &*self.member }
}
pub fn member_mut(&mut self) -> &mut U {
unsafe { &mut *self.member }
}
pub fn into_container(self) -> &'a mut T {
// SAFETY: Since we consume self and no longer reference member, this
// mutable reference is unique.
unsafe { &mut *self.container }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_borrowstack_slice_int() {
fn f(mut stack: BorrowStack<[u32], u32>) {
assert_eq!(*stack.member(), 3);
*stack.member_mut() = 42;
assert_eq!(stack.into_container(), &[1, 2, 42, 4, 5]);
}
let mut v = vec![1, 2, 3, 4, 5];
f(BorrowStack::new(v.as_mut(), |v: &mut [u32]| Ok(&mut v[2])).unwrap());
assert_eq!(&v, &[1, 2, 42, 4, 5]);
}
}

View file

@ -1,7 +1,9 @@
use memmap2::MmapMut;
use std::{ use std::{
ffi::CStr, ffi::CStr,
fs::File, fs::File,
os::unix::prelude::{AsRawFd, FileExt, FromRawFd}, os::unix::prelude::{AsRawFd, FromRawFd},
slice,
sync::{ sync::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
Arc, Arc,
@ -69,9 +71,19 @@ fn create_memfile() -> File {
panic!("Failed to generate non-existant shm name") panic!("Failed to generate non-existant shm name")
} }
// Round size to use for pool for given dimentions, rounding up to power of 2
fn get_pool_size(width: i32, height: i32) -> i32 {
((width * height * 4) as u32).next_power_of_two() as i32
}
unsafe fn map_file(file: &File) -> MmapMut {
unsafe { MmapMut::map_mut(file.as_raw_fd()).expect("Failed to map shared memory") }
}
pub(super) struct WaylandBuffer { pub(super) struct WaylandBuffer {
qh: QueueHandle<State>, qh: QueueHandle<State>,
tempfile: File, tempfile: File,
map: MmapMut,
pool: wl_shm_pool::WlShmPool, pool: wl_shm_pool::WlShmPool,
pool_size: i32, pool_size: i32,
buffer: wl_buffer::WlBuffer, buffer: wl_buffer::WlBuffer,
@ -82,8 +94,15 @@ pub(super) struct WaylandBuffer {
impl WaylandBuffer { impl WaylandBuffer {
pub fn new(shm: &wl_shm::WlShm, width: i32, height: i32, qh: &QueueHandle<State>) -> Self { pub fn new(shm: &wl_shm::WlShm, width: i32, height: i32, qh: &QueueHandle<State>) -> Self {
// Calculate size to use for shm pool
let pool_size = get_pool_size(width, height);
// Create an `mmap` shared memory
let tempfile = create_memfile(); let tempfile = create_memfile();
let pool_size = width * height * 4; let _ = tempfile.set_len(pool_size as u64);
let map = unsafe { map_file(&tempfile) };
// Create wayland shm pool and buffer
let pool = shm.create_pool(tempfile.as_raw_fd(), pool_size, qh, ()); let pool = shm.create_pool(tempfile.as_raw_fd(), pool_size, qh, ());
let released = Arc::new(AtomicBool::new(true)); let released = Arc::new(AtomicBool::new(true));
let buffer = pool.create_buffer( let buffer = pool.create_buffer(
@ -95,8 +114,10 @@ impl WaylandBuffer {
qh, qh,
released.clone(), released.clone(),
); );
Self { Self {
qh: qh.clone(), qh: qh.clone(),
map,
tempfile, tempfile,
pool, pool,
pool_size, pool_size,
@ -119,6 +140,7 @@ impl WaylandBuffer {
let _ = self.tempfile.set_len(size as u64); let _ = self.tempfile.set_len(size as u64);
self.pool.resize(size); self.pool.resize(size);
self.pool_size = size; self.pool_size = size;
self.map = unsafe { map_file(&self.tempfile) };
} }
// Create buffer with correct size // Create buffer with correct size
@ -136,14 +158,6 @@ impl WaylandBuffer {
} }
} }
pub fn write(&self, buffer: &[u32]) {
let buffer =
unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const u8, buffer.len() * 4) };
self.tempfile
.write_all_at(buffer, 0)
.expect("Failed to write buffer to temporary file.");
}
pub fn attach(&self, surface: &wl_surface::WlSurface) { pub fn attach(&self, surface: &wl_surface::WlSurface) {
self.released.store(false, Ordering::SeqCst); self.released.store(false, Ordering::SeqCst);
surface.attach(Some(&self.buffer), 0, 0); surface.attach(Some(&self.buffer), 0, 0);
@ -152,6 +166,14 @@ impl WaylandBuffer {
pub fn released(&self) -> bool { pub fn released(&self) -> bool {
self.released.load(Ordering::SeqCst) self.released.load(Ordering::SeqCst)
} }
fn len(&self) -> usize {
self.width as usize * self.height as usize
}
pub unsafe fn mapped_mut(&mut self) -> &mut [u32] {
unsafe { slice::from_raw_parts_mut(self.map.as_mut_ptr() as *mut u32, self.len()) }
}
} }
impl Drop for WaylandBuffer { impl Drop for WaylandBuffer {

View file

@ -1,6 +1,10 @@
use crate::{error::unwrap, SoftBufferError}; use crate::{error::unwrap, util, SoftBufferError};
use raw_window_handle::{WaylandDisplayHandle, WaylandWindowHandle}; use raw_window_handle::{WaylandDisplayHandle, WaylandWindowHandle};
use std::sync::{Arc, Mutex}; use std::{
cell::RefCell,
num::{NonZeroI32, NonZeroU32},
rc::Rc,
};
use wayland_client::{ use wayland_client::{
backend::{Backend, ObjectId}, backend::{Backend, ObjectId},
globals::{registry_queue_init, GlobalListContents}, globals::{registry_queue_init, GlobalListContents},
@ -15,7 +19,7 @@ struct State;
pub struct WaylandDisplayImpl { pub struct WaylandDisplayImpl {
conn: Connection, conn: Connection,
event_queue: Mutex<EventQueue<State>>, event_queue: RefCell<EventQueue<State>>,
qh: QueueHandle<State>, qh: QueueHandle<State>,
shm: wl_shm::WlShm, shm: wl_shm::WlShm,
} }
@ -36,7 +40,7 @@ impl WaylandDisplayImpl {
)?; )?;
Ok(Self { Ok(Self {
conn, conn,
event_queue: Mutex::new(event_queue), event_queue: RefCell::new(event_queue),
qh, qh,
shm, shm,
}) })
@ -44,15 +48,16 @@ impl WaylandDisplayImpl {
} }
pub struct WaylandImpl { pub struct WaylandImpl {
display: Arc<WaylandDisplayImpl>, display: Rc<WaylandDisplayImpl>,
surface: wl_surface::WlSurface, surface: wl_surface::WlSurface,
buffers: Option<(WaylandBuffer, WaylandBuffer)>, buffers: Option<(WaylandBuffer, WaylandBuffer)>,
size: Option<(NonZeroI32, NonZeroI32)>,
} }
impl WaylandImpl { impl WaylandImpl {
pub unsafe fn new( pub unsafe fn new(
window_handle: WaylandWindowHandle, window_handle: WaylandWindowHandle,
display: Arc<WaylandDisplayImpl>, display: Rc<WaylandDisplayImpl>,
) -> Result<Self, SoftBufferError> { ) -> Result<Self, SoftBufferError> {
// SAFETY: Ensured by user // SAFETY: Ensured by user
let surface_id = unwrap( let surface_id = unwrap(
@ -72,60 +77,119 @@ impl WaylandImpl {
display, display,
surface, surface,
buffers: Default::default(), buffers: Default::default(),
size: None,
}) })
} }
fn buffer(&mut self, width: i32, height: i32) -> &WaylandBuffer { pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {
self.buffers = Some(if let Some((front, mut back)) = self.buffers.take() { self.size = Some(
// Swap buffers; block if back buffer not released yet (|| {
if !back.released() { let width = NonZeroI32::try_from(width).ok()?;
let mut event_queue = self.display.event_queue.lock().unwrap(); let height = NonZeroI32::try_from(height).ok()?;
while !back.released() { Some((width, height))
event_queue.blocking_dispatch(&mut State).unwrap(); })()
} .ok_or(SoftBufferError::SizeOutOfRange { width, height })?,
} );
back.resize(width, height); Ok(())
(back, front)
} else {
// Allocate front and back buffer
(
WaylandBuffer::new(&self.display.shm, width, height, &self.display.qh),
WaylandBuffer::new(&self.display.shm, width, height, &self.display.qh),
)
});
&self.buffers.as_ref().unwrap().0
} }
pub(super) unsafe fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) { pub fn buffer_mut(&mut self) -> Result<BufferImpl, SoftBufferError> {
let _ = self let (width, height) = self
.size
.expect("Must set size of surface before calling `buffer_mut()`");
if let Some((_front, back)) = &mut self.buffers {
// Block if back buffer not released yet
if !back.released() {
let mut event_queue = self.display.event_queue.borrow_mut();
while !back.released() {
event_queue.blocking_dispatch(&mut State).map_err(|err| {
SoftBufferError::PlatformError(
Some("Wayland dispatch failure".to_string()),
Some(Box::new(err)),
)
})?;
}
}
// Resize, if buffer isn't large enough
back.resize(width.get(), height.get());
} else {
// Allocate front and back buffer
self.buffers = Some((
WaylandBuffer::new(
&self.display.shm,
width.get(),
height.get(),
&self.display.qh,
),
WaylandBuffer::new(
&self.display.shm,
width.get(),
height.get(),
&self.display.qh,
),
));
};
Ok(BufferImpl(util::BorrowStack::new(self, |buffer| {
Ok(unsafe { buffer.buffers.as_mut().unwrap().1.mapped_mut() })
})?))
}
}
pub struct BufferImpl<'a>(util::BorrowStack<'a, WaylandImpl, [u32]>);
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
.lock() .borrow_mut()
.unwrap()
.dispatch_pending(&mut State); .dispatch_pending(&mut State);
let surface = self.surface.clone(); if let Some((front, back)) = &mut imp.buffers {
let wayland_buffer = self.buffer(width.into(), height.into()); // Swap front and back buffer
wayland_buffer.write(buffer); std::mem::swap(front, back);
wayland_buffer.attach(&surface);
// FIXME: Proper damaging mechanism. front.attach(&imp.surface);
//
// In order to propagate changes on compositors which track damage, for now damage the entire surface. // FIXME: Proper damaging mechanism.
if self.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") // In order to propagate changes on compositors which track damage, for now damage the entire surface.
self.surface.damage(0, 0, i32::MAX, i32::MAX); if imp.surface.version() < 4 {
} else { // FIXME: Accommodate scale factor since wl_surface::damage is in terms of surface coordinates while
// Introduced in version 4, it is an error to use this request in version 3 or lower. // wl_surface::damage_buffer is in buffer coordinates.
self.surface //
.damage_buffer(0, 0, width as i32, height as i32); // 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);
self.surface.commit(); } 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());
}
let _ = self.display.event_queue.lock().unwrap().flush(); imp.surface.commit();
}
let _ = imp.display.event_queue.borrow_mut().flush();
Ok(())
} }
} }

View file

@ -1,3 +1,7 @@
//! Implementation of software buffering for web targets.
#![allow(clippy::uninlined_format_args)]
use raw_window_handle::WebWindowHandle; use raw_window_handle::WebWindowHandle;
use wasm_bindgen::Clamped; use wasm_bindgen::Clamped;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
@ -6,6 +10,8 @@ use web_sys::HtmlCanvasElement;
use web_sys::ImageData; use web_sys::ImageData;
use crate::SoftBufferError; use crate::SoftBufferError;
use std::convert::TryInto;
use std::num::NonZeroU32;
/// Display implementation for the web platform. /// Display implementation for the web platform.
/// ///
@ -36,8 +42,17 @@ impl WebDisplayImpl {
} }
pub struct WebImpl { pub struct WebImpl {
/// The handle to the canvas that we're drawing to.
canvas: HtmlCanvasElement, canvas: HtmlCanvasElement,
/// The 2D rendering context for the canvas.
ctx: CanvasRenderingContext2d, ctx: CanvasRenderingContext2d,
/// The buffer that we're drawing to.
buffer: Vec<u32>,
/// The current width of the canvas.
width: u32,
} }
impl WebImpl { impl WebImpl {
@ -73,24 +88,82 @@ impl WebImpl {
.dyn_into() .dyn_into()
.expect("`getContext(\"2d\") didn't return a `CanvasRenderingContext2d`"); .expect("`getContext(\"2d\") didn't return a `CanvasRenderingContext2d`");
Ok(Self { canvas, ctx }) Ok(Self {
canvas,
ctx,
buffer: Vec::new(),
width: 0,
})
} }
pub(crate) unsafe fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) { /// Resize the canvas to the given dimensions.
self.canvas.set_width(width.into()); pub(crate) fn resize(
self.canvas.set_height(height.into()); &mut self,
width: NonZeroU32,
height: NonZeroU32,
) -> Result<(), SoftBufferError> {
let width = width.get();
let height = height.get();
let bitmap: Vec<_> = buffer self.buffer.resize(total_len(width, height), 0);
self.canvas.set_width(width);
self.canvas.set_height(height);
self.width = width;
Ok(())
}
/// Get a pointer to the mutable buffer.
pub(crate) fn buffer_mut(&mut self) -> Result<BufferImpl, SoftBufferError> {
Ok(BufferImpl { imp: self })
}
}
pub struct BufferImpl<'a> {
imp: &'a mut WebImpl,
}
impl<'a> BufferImpl<'a> {
pub fn pixels(&self) -> &[u32] {
&self.imp.buffer
}
pub fn pixels_mut(&mut self) -> &mut [u32] {
&mut self.imp.buffer
}
/// Push the buffer to the canvas.
pub fn present(self) -> Result<(), SoftBufferError> {
// Create a bitmap from the buffer.
let bitmap: Vec<_> = self
.imp
.buffer
.iter() .iter()
.copied() .copied()
.flat_map(|pixel| [(pixel >> 16) as u8, (pixel >> 8) as u8, pixel as u8, 255]) .flat_map(|pixel| [(pixel >> 16) as u8, (pixel >> 8) as u8, pixel as u8, 255])
.collect(); .collect();
// This should only throw an error if the buffer we pass's size is incorrect, which is checked in the outer `set_buffer` call. // This should only throw an error if the buffer we pass's size is incorrect.
let image_data = let image_data =
ImageData::new_with_u8_clamped_array(Clamped(&bitmap), width.into()).unwrap(); ImageData::new_with_u8_clamped_array(Clamped(&bitmap), self.imp.width).unwrap();
// This can only throw an error if `data` is detached, which is impossible. // This can only throw an error if `data` is detached, which is impossible.
self.ctx.put_image_data(&image_data, 0.0, 0.0).unwrap(); self.imp.ctx.put_image_data(&image_data, 0.0, 0.0).unwrap();
Ok(())
} }
} }
#[inline(always)]
fn total_len(width: u32, height: u32) -> usize {
// Convert width and height to `usize`, then multiply.
width
.try_into()
.ok()
.and_then(|w: usize| height.try_into().ok().and_then(|h| w.checked_mul(h)))
.unwrap_or_else(|| {
panic!(
"Overflow when calculating total length of buffer: {}x{}",
width, height
);
})
}

View file

@ -2,33 +2,134 @@
//! //!
//! 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::{util, SoftBufferError};
use raw_window_handle::Win32WindowHandle; use raw_window_handle::Win32WindowHandle;
use std::io; use std::io;
use std::mem; use std::mem;
use std::os::raw::c_int; use std::num::{NonZeroI32, NonZeroU32};
use std::ptr::{self, NonNull};
use std::slice;
use windows_sys::Win32::Foundation::HWND; use windows_sys::Win32::Foundation::HWND;
use windows_sys::Win32::Graphics::Gdi::{ use windows_sys::Win32::Graphics::Gdi;
GetDC, StretchDIBits, ValidateRect, BITMAPINFOHEADER, BI_BITFIELDS, DIB_RGB_COLORS, HDC,
RGBQUAD, SRCCOPY, const ZERO_QUAD: Gdi::RGBQUAD = Gdi::RGBQUAD {
rgbBlue: 0,
rgbGreen: 0,
rgbRed: 0,
rgbReserved: 0,
}; };
struct Buffer {
dc: Gdi::HDC,
bitmap: Gdi::HBITMAP,
pixels: NonNull<u32>,
width: NonZeroI32,
height: NonZeroI32,
}
impl Drop for Buffer {
fn drop(&mut self) {
unsafe {
Gdi::DeleteDC(self.dc);
Gdi::DeleteObject(self.bitmap);
}
}
}
impl Buffer {
fn new(window_dc: Gdi::HDC, width: NonZeroI32, height: NonZeroI32) -> Self {
let dc = unsafe { Gdi::CreateCompatibleDC(window_dc) };
assert!(dc != 0);
// Create a new bitmap info struct.
let bitmap_info = BitmapInfo {
bmi_header: Gdi::BITMAPINFOHEADER {
biSize: mem::size_of::<Gdi::BITMAPINFOHEADER>() as u32,
biWidth: width.get(),
biHeight: -height.get(),
biPlanes: 1,
biBitCount: 32,
biCompression: Gdi::BI_BITFIELDS,
biSizeImage: 0,
biXPelsPerMeter: 0,
biYPelsPerMeter: 0,
biClrUsed: 0,
biClrImportant: 0,
},
bmi_colors: [
Gdi::RGBQUAD {
rgbRed: 0xff,
..ZERO_QUAD
},
Gdi::RGBQUAD {
rgbGreen: 0xff,
..ZERO_QUAD
},
Gdi::RGBQUAD {
rgbBlue: 0xff,
..ZERO_QUAD
},
],
};
// XXX alignment?
// XXX better to use CreateFileMapping, and pass hSection?
// XXX test return value?
let mut pixels: *mut u32 = ptr::null_mut();
let bitmap = unsafe {
Gdi::CreateDIBSection(
dc,
&bitmap_info as *const BitmapInfo as *const _,
Gdi::DIB_RGB_COLORS,
&mut pixels as *mut *mut u32 as _,
0,
0,
)
};
assert!(bitmap != 0);
let pixels = NonNull::new(pixels).unwrap();
unsafe {
Gdi::SelectObject(dc, bitmap);
}
Self {
dc,
bitmap,
width,
height,
pixels,
}
}
fn pixels_mut(&mut self) -> &mut [u32] {
unsafe {
slice::from_raw_parts_mut(
self.pixels.as_ptr(),
i32::from(self.width) as usize * i32::from(self.height) as usize,
)
}
}
}
/// The handle to a window for software buffering. /// The handle to a window for software buffering.
pub struct Win32Impl { pub struct Win32Impl {
/// The window handle. /// The window handle.
window: HWND, window: HWND,
/// The device context for the window. /// The device context for the window.
dc: HDC, dc: Gdi::HDC,
buffer: Option<Buffer>,
} }
/// The Win32-compatible bitmap information. /// The Win32-compatible bitmap information.
#[repr(C)] #[repr(C)]
struct BitmapInfo { struct BitmapInfo {
pub bmi_header: BITMAPINFOHEADER, bmi_header: Gdi::BITMAPINFOHEADER,
pub bmi_colors: [RGBQUAD; 3], bmi_colors: [Gdi::RGBQUAD; 3],
} }
impl Win32Impl { impl Win32Impl {
@ -46,7 +147,7 @@ impl Win32Impl {
// Get the handle to the device context. // Get the handle to the device context.
// SAFETY: We have confirmed that the window handle is valid. // SAFETY: We have confirmed that the window handle is valid.
let hwnd = handle.hwnd as HWND; let hwnd = handle.hwnd as HWND;
let dc = unsafe { GetDC(hwnd) }; let dc = unsafe { Gdi::GetDC(hwnd) };
// GetDC returns null if there is a platform error. // GetDC returns null if there is a platform error.
if dc == 0 { if dc == 0 {
@ -56,72 +157,76 @@ impl Win32Impl {
)); ));
} }
Ok(Self { dc, window: hwnd }) Ok(Self {
dc,
window: hwnd,
buffer: None,
})
} }
pub(crate) unsafe fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) { pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {
// Create a new bitmap info struct. let (width, height) = (|| {
let bmi_header = BITMAPINFOHEADER { let width = NonZeroI32::new(i32::try_from(width.get()).ok()?)?;
biSize: mem::size_of::<BITMAPINFOHEADER>() as u32, let height = NonZeroI32::new(i32::try_from(height.get()).ok()?)?;
biWidth: width as i32, Some((width, height))
biHeight: -(height as i32), })()
biPlanes: 1, .ok_or(SoftBufferError::SizeOutOfRange { width, height })?;
biBitCount: 32,
biCompression: BI_BITFIELDS,
biSizeImage: 0,
biXPelsPerMeter: 0,
biYPelsPerMeter: 0,
biClrUsed: 0,
biClrImportant: 0,
};
let zero_quad = RGBQUAD {
rgbBlue: 0,
rgbGreen: 0,
rgbRed: 0,
rgbReserved: 0,
};
let bmi_colors = [
RGBQUAD {
rgbRed: 0xff,
..zero_quad
},
RGBQUAD {
rgbGreen: 0xff,
..zero_quad
},
RGBQUAD {
rgbBlue: 0xff,
..zero_quad
},
];
let bitmap_info = BitmapInfo {
bmi_header,
bmi_colors,
};
// Stretch the bitmap onto the window. if let Some(buffer) = self.buffer.as_ref() {
// SAFETY: if buffer.width == width && buffer.height == height {
// - The bitmap information is valid. return Ok(());
// - The buffer is a valid pointer to image data. }
unsafe { }
StretchDIBits(
self.dc,
0,
0,
width as c_int,
height as c_int,
0,
0,
width as c_int,
height as c_int,
buffer.as_ptr().cast(),
&bitmap_info as *const BitmapInfo as *const _,
DIB_RGB_COLORS,
SRCCOPY,
)
};
// Validate the window. self.buffer = Some(Buffer::new(self.dc, width, height));
unsafe { ValidateRect(self.window, std::ptr::null_mut()) };
Ok(())
}
pub fn buffer_mut(&mut self) -> Result<BufferImpl, SoftBufferError> {
Ok(BufferImpl(util::BorrowStack::new(self, |surface| {
Ok(surface
.buffer
.as_mut()
.expect("Must set size of surface before calling `buffer_mut()`")
.pixels_mut())
})?))
}
}
pub struct BufferImpl<'a>(util::BorrowStack<'a, Win32Impl, [u32]>);
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 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,
);
// Validate the window.
Gdi::ValidateRect(imp.window, ptr::null_mut());
}
Ok(())
} }
} }

View file

@ -5,11 +5,11 @@
#![allow(clippy::uninlined_format_args)] #![allow(clippy::uninlined_format_args)]
use crate::SoftBufferError; use crate::{util, 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, sync::Arc}; use std::{fmt, io, mem, num::NonZeroU32, rc::Rc};
use x11_dl::xlib::Display; use x11_dl::xlib::Display;
use x11_dl::xlib_xcb::Xlib_xcb; use x11_dl::xlib_xcb::Xlib_xcb;
@ -80,6 +80,9 @@ impl X11DisplayImpl {
}; };
let is_shm_available = is_shm_available(&connection); let is_shm_available = is_shm_available(&connection);
if !is_shm_available {
log::warn!("SHM extension is not available. Performance may be poor.");
}
Ok(Self { Ok(Self {
connection, connection,
@ -91,7 +94,7 @@ impl X11DisplayImpl {
/// The handle to an X11 drawing context. /// The handle to an X11 drawing context.
pub struct X11Impl { pub struct X11Impl {
/// X display this window belongs to. /// X display this window belongs to.
display: Arc<X11DisplayImpl>, display: Rc<X11DisplayImpl>,
/// The window to draw to. /// The window to draw to.
window: xproto::Window, window: xproto::Window,
@ -102,11 +105,26 @@ pub struct X11Impl {
/// The depth (bits per pixel) of the drawing context. /// The depth (bits per pixel) of the drawing context.
depth: u8, depth: u8,
/// Information about SHM, if it is available. /// The buffer we draw to.
shm: Option<ShmInfo>, buffer: Buffer,
/// The current buffer width.
width: u16,
/// The current buffer height.
height: u16,
} }
struct ShmInfo { /// The buffer that is being drawn to.
enum Buffer {
/// A buffer implemented using shared memory to prevent unnecessary copying.
Shm(ShmBuffer),
/// A normal buffer that we send over the wire.
Wire(Vec<u32>),
}
struct ShmBuffer {
/// The shared memory segment, paired with its ID. /// The shared memory segment, paired with its ID.
seg: Option<(ShmSegment, shm::Seg)>, seg: Option<(ShmSegment, shm::Seg)>,
@ -133,7 +151,7 @@ impl X11Impl {
/// The `XlibWindowHandle` and `XlibDisplayHandle` must be valid. /// The `XlibWindowHandle` and `XlibDisplayHandle` must be valid.
pub unsafe fn from_xlib( pub unsafe fn from_xlib(
window_handle: XlibWindowHandle, window_handle: XlibWindowHandle,
display: Arc<X11DisplayImpl>, display: Rc<X11DisplayImpl>,
) -> Result<Self, SoftBufferError> { ) -> Result<Self, SoftBufferError> {
let mut xcb_window_handle = XcbWindowHandle::empty(); let mut xcb_window_handle = XcbWindowHandle::empty();
xcb_window_handle.window = window_handle.window as _; xcb_window_handle.window = window_handle.window as _;
@ -150,8 +168,10 @@ impl X11Impl {
/// The `XcbWindowHandle` and `XcbDisplayHandle` must be valid. /// The `XcbWindowHandle` and `XcbDisplayHandle` must be valid.
pub(crate) unsafe fn from_xcb( pub(crate) unsafe fn from_xcb(
window_handle: XcbWindowHandle, window_handle: XcbWindowHandle,
display: Arc<X11DisplayImpl>, display: Rc<X11DisplayImpl>,
) -> Result<Self, SoftBufferError> { ) -> Result<Self, SoftBufferError> {
log::trace!("new: window_handle={:X}", window_handle.window,);
// Check that the handle is valid. // Check that the handle is valid.
if window_handle.window == 0 { if window_handle.window == 0 {
return Err(SoftBufferError::IncompleteWindowHandle); return Err(SoftBufferError::IncompleteWindowHandle);
@ -187,18 +207,15 @@ impl X11Impl {
.swbuf_err("Failed to get geometry reply")?; .swbuf_err("Failed to get geometry reply")?;
// See if SHM is available. // See if SHM is available.
let shm_info = { let buffer = if display.is_shm_available {
let present = display.is_shm_available; // SHM is available.
Buffer::Shm(ShmBuffer {
if present { seg: None,
// SHM is available. done_processing: None,
Some(ShmInfo { })
seg: None, } else {
done_processing: None, // SHM is not available.
}) Buffer::Wire(Vec::new())
} else {
None
}
}; };
Ok(Self { Ok(Self {
@ -206,119 +223,184 @@ impl X11Impl {
window, window,
gc, gc,
depth: geometry_reply.depth, depth: geometry_reply.depth,
shm: shm_info, buffer,
width: 0,
height: 0,
}) })
} }
pub(crate) unsafe fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) { /// Resize the internal buffer to the given width and height.
// Draw the image to the buffer. pub(crate) fn resize(
let result = unsafe { self.set_buffer_shm(buffer, width, height) }.and_then(|had_shm| {
if had_shm {
Ok(())
} else {
log::debug!("Falling back to non-SHM method");
self.set_buffer_fallback(buffer, width, height)
}
});
if let Err(e) = result {
log::error!("Failed to draw image to window: {}", e);
}
}
/// Put the given buffer into the window using the SHM method.
///
/// Returns `false` if SHM is not available.
///
/// # Safety
///
/// The buffer's length must be `width * height`.
unsafe fn set_buffer_shm(
&mut self, &mut self,
buffer: &[u32], width: NonZeroU32,
width: u16, height: NonZeroU32,
height: u16, ) -> Result<(), SoftBufferError> {
) -> Result<bool, PushBufferError> { log::trace!(
let shm_info = match self.shm { "resize: window={:X}, size={}x{}",
Some(ref mut info) => info, self.window,
None => return Ok(false), width,
}; height
);
// If the X server is still processing the last image, wait for it to finish. // Width and height should fit in u16.
shm_info.finish_wait(&self.display.connection)?; let width: u16 = width
.get()
.try_into()
.or(Err(SoftBufferError::SizeOutOfRange { width, height }))?;
let height: u16 = height
.get()
.try_into()
.or(Err(SoftBufferError::SizeOutOfRange {
width: NonZeroU32::new(width.into()).unwrap(),
height,
}))?;
// Get the SHM segment to use. if width != self.width || height != self.height {
let necessary_size = (width as usize) * (height as usize) * 4; self.buffer
let (segment, segment_id) = shm_info.segment(&self.display.connection, necessary_size)?; .resize(&self.display.connection, width, height)
.swbuf_err("Failed to resize X11 buffer")?;
// Copy the buffer into the segment. // We successfully resized the buffer.
// SAFETY: The buffer is properly sized and we've ensured that the X server isn't reading from it. self.width = width;
unsafe { self.height = height;
segment.copy(buffer);
} }
// Put the image into the window.
self.display
.connection
.shm_put_image(
self.window,
self.gc,
width,
height,
0,
0,
width,
height,
0,
0,
self.depth,
xproto::ImageFormat::Z_PIXMAP.into(),
false,
segment_id,
0,
)?
.ignore_error();
// Send a short request to act as a notification for when the X server is done processing the image.
shm_info.begin_wait(&self.display.connection)?;
Ok(true)
}
/// Put the given buffer into the window using the fallback wire transfer method.
fn set_buffer_fallback(
&mut self,
buffer: &[u32],
width: u16,
height: u16,
) -> Result<(), PushBufferError> {
self.display
.connection
.put_image(
xproto::ImageFormat::Z_PIXMAP,
self.window,
self.gc,
width,
height,
0,
0,
0,
self.depth,
bytemuck::cast_slice(buffer),
)?
.ignore_error();
Ok(()) Ok(())
} }
/// Get a mutable reference to the buffer.
pub(crate) fn buffer_mut(&mut self) -> Result<BufferImpl, SoftBufferError> {
log::trace!("buffer_mut: window={:X}", self.window);
Ok(BufferImpl(util::BorrowStack::new(self, |surface| {
let buffer = surface
.buffer
.buffer_mut(&surface.display.connection)
.swbuf_err("Failed to get mutable X11 buffer")?;
// Crop it down to the window size.
Ok(&mut buffer[..total_len(surface.width, surface.height) / 4])
})?))
}
} }
impl ShmInfo { pub struct BufferImpl<'a>(util::BorrowStack<'a, X11Impl, [u32]>);
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()
}
/// Push the buffer to the window.
pub fn present(self) -> Result<(), SoftBufferError> {
let imp = self.0.into_container();
log::trace!("present: window={:X}", imp.window);
let result = match imp.buffer {
Buffer::Wire(ref wire) => {
// This is a suboptimal strategy, raise a stink in the debug logs.
log::debug!("Falling back to non-SHM method for window drawing.");
imp.display
.connection
.put_image(
xproto::ImageFormat::Z_PIXMAP,
imp.window,
imp.gc,
imp.width,
imp.height,
0,
0,
0,
imp.depth,
bytemuck::cast_slice(wire),
)
.map(|c| c.ignore_error())
.push_err()
}
Buffer::Shm(ref mut shm) => {
// If the X server is still processing the last image, wait for it to finish.
shm.finish_wait(&imp.display.connection)
.and_then(|()| {
// 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,
)
.push_err()
.map(|c| c.ignore_error())
} else {
Ok(())
}
})
.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)
})
}
};
result.swbuf_err("Failed to draw image to window")
}
}
impl Buffer {
/// Resize the buffer to the given size.
fn resize(
&mut self,
conn: &impl Connection,
width: u16,
height: u16,
) -> Result<(), PushBufferError> {
match self {
Buffer::Shm(ref mut shm) => shm.alloc_segment(conn, total_len(width, height)),
Buffer::Wire(wire) => {
wire.resize(total_len(width, height), 0);
Ok(())
}
}
}
/// Get a mutable reference to the buffer.
fn buffer_mut(&mut self, conn: &impl Connection) -> Result<&mut [u32], PushBufferError> {
match self {
Buffer::Shm(ref mut shm) => shm.as_mut(conn),
Buffer::Wire(wire) => Ok(wire),
}
}
}
impl ShmBuffer {
/// Allocate a new `ShmSegment` of the given size. /// Allocate a new `ShmSegment` of the given size.
fn segment( fn alloc_segment(
&mut self, &mut self,
conn: &impl Connection, conn: &impl Connection,
size: usize, size: usize,
) -> Result<(&mut ShmSegment, shm::Seg), PushBufferError> { ) -> Result<(), PushBufferError> {
// Round the size up to the next power of two to prevent frequent reallocations. // Round the size up to the next power of two to prevent frequent reallocations.
let size = size.next_power_of_two(); let size = size.next_power_of_two();
@ -334,12 +416,25 @@ impl ShmInfo {
self.associate(conn, new_seg)?; self.associate(conn, new_seg)?;
} }
// Get the segment and ID. Ok(())
Ok(self }
.seg
.as_mut() /// Get the SHM buffer as a mutable reference.
.map(|(ref mut seg, id)| (seg, *id)) fn as_mut(&mut self, conn: &impl Connection) -> Result<&mut [u32], PushBufferError> {
.unwrap()) // Make sure that, if we're waiting for the X server to finish processing the last image,
// that we finish the wait.
self.finish_wait(conn)?;
match self.seg.as_mut() {
Some((seg, _)) => {
// SAFETY: No other code should be able to access the segment.
Ok(bytemuck::cast_slice_mut(unsafe { seg.as_mut() }))
}
None => {
// Nothing has been allocated yet.
Ok(&mut [])
}
}
} }
/// Associate an SHM segment with the server. /// Associate an SHM segment with the server.
@ -354,6 +449,9 @@ impl ShmInfo {
// Take out the old one and detach it. // Take out the old one and detach it.
if let Some((old_seg, old_id)) = self.seg.replace((seg, new_id)) { if let Some((old_seg, old_id)) = self.seg.replace((seg, new_id)) {
// Wait for the old segment to finish processing.
self.finish_wait(conn)?;
conn.shm_detach(old_id)?.ignore_error(); conn.shm_detach(old_id)?.ignore_error();
// Drop the old segment. // Drop the old segment.
@ -415,23 +513,13 @@ impl ShmSegment {
} }
} }
/// Copy data into this shared memory segment. /// Get this shared memory segment as a mutable reference.
/// ///
/// # Safety /// # Safety
/// ///
/// This function assumes that the size of `self`'s buffer is larger than or equal to `data.len()`. /// One must ensure that no other processes are reading from or writing to this memory.
/// In addition, no other processes should be reading from or writing to this memory. unsafe fn as_mut(&mut self) -> &mut [i8] {
unsafe fn copy<T: bytemuck::NoUninit>(&mut self, data: &[T]) { unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.size) }
debug_assert!(data.len() * std::mem::size_of::<T>() <= self.size,);
let incoming_data = bytemuck::cast_slice::<_, u8>(data);
unsafe {
std::ptr::copy_nonoverlapping(
incoming_data.as_ptr(),
self.ptr.as_ptr() as *mut u8,
incoming_data.len(),
)
}
} }
/// Get the size of this shared memory segment. /// Get the size of this shared memory segment.
@ -460,7 +548,7 @@ impl Drop for ShmSegment {
impl Drop for X11Impl { impl Drop for X11Impl {
fn drop(&mut self) { fn drop(&mut self) {
// If we used SHM, make sure it's detached from the server. // If we used SHM, make sure it's detached from the server.
if let Some(mut shm) = self.shm.take() { if let Buffer::Shm(mut shm) = mem::replace(&mut self.buffer, Buffer::Wire(Vec::new())) {
// If we were in the middle of processing a buffer, wait for it to finish. // If we were in the middle of processing a buffer, wait for it to finish.
shm.finish_wait(&self.display.connection).ok(); shm.finish_wait(&self.display.connection).ok();
@ -563,11 +651,11 @@ impl From<io::Error> for PushBufferError {
} }
/// Convenient wrapper to cast errors into SoftBufferError. /// Convenient wrapper to cast errors into SoftBufferError.
trait ResultExt<T, E> { trait SwResultExt<T, E> {
fn swbuf_err(self, msg: impl Into<String>) -> Result<T, SoftBufferError>; fn swbuf_err(self, msg: impl Into<String>) -> Result<T, SoftBufferError>;
} }
impl<T, E: std::error::Error + 'static> ResultExt<T, E> for Result<T, E> { impl<T, E: std::error::Error + 'static> SwResultExt<T, E> for Result<T, E> {
fn swbuf_err(self, msg: impl Into<String>) -> Result<T, SoftBufferError> { fn swbuf_err(self, msg: impl Into<String>) -> Result<T, SoftBufferError> {
self.map_err(|e| { self.map_err(|e| {
SoftBufferError::PlatformError(Some(msg.into()), Some(Box::new(LibraryError(e)))) SoftBufferError::PlatformError(Some(msg.into()), Some(Box::new(LibraryError(e))))
@ -575,6 +663,17 @@ impl<T, E: std::error::Error + 'static> ResultExt<T, E> for Result<T, E> {
} }
} }
/// Convenient wrapper to cast errors into PushBufferError.
trait PushResultExt<T, E> {
fn push_err(self) -> Result<T, PushBufferError>;
}
impl<T, E: Into<PushBufferError>> PushResultExt<T, E> for Result<T, E> {
fn push_err(self) -> Result<T, PushBufferError> {
self.map_err(Into::into)
}
}
/// A wrapper around a library error. /// A wrapper around a library error.
/// ///
/// This prevents `x11-dl` and `x11rb` from becoming public dependencies, since users cannot downcast /// This prevents `x11-dl` and `x11rb` from becoming public dependencies, since users cannot downcast
@ -594,3 +693,15 @@ impl<E: fmt::Display> fmt::Display for LibraryError<E> {
} }
impl<E: fmt::Debug + fmt::Display> std::error::Error for LibraryError<E> {} impl<E: fmt::Debug + fmt::Display> std::error::Error for LibraryError<E> {}
/// Get the length that a slice needs to be to hold a buffer of the given dimensions.
#[inline(always)]
fn total_len(width: u16, height: u16) -> usize {
let width: usize = width.into();
let height: usize = height.into();
width
.checked_mul(height)
.and_then(|len| len.checked_mul(4))
.unwrap_or_else(|| panic!("Dimensions are too large: ({} x {})", width, height))
}