diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54c0137..6401913 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,17 @@ jobs: - name: Check Formatting 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: name: Tests strategy: diff --git a/Cargo.toml b/Cargo.toml index 27cfe35..2ff4173 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ rust-version = "1.60.0" [features] 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"] 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] bytemuck = { version = "1.12.3", optional = true } +memmap2 = { version = "0.5.8", optional = true } nix = { version = "0.26.1", optional = true } wayland-backend = { version = "0.1.0", features = ["client_system"], optional = true } wayland-client = { version = "0.30.0", optional = true } @@ -40,6 +41,7 @@ version = "0.42.0" features = ["Win32_Graphics_Gdi", "Win32_UI_WindowsAndMessaging", "Win32_Foundation"] [target.'cfg(target_os = "macos")'.dependencies] +bytemuck = { version = "1.12.3", features = ["extern_crate_alloc"] } cocoa = "0.24.0" core-graphics = "0.22.3" foreign-types = "0.3.0" diff --git a/README.md b/README.md index f784491..135342b 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ For now, the priority for new platforms is: Example == ```rust,no_run +use std::num::NonZeroU32; use winit::event::{Event, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::WindowBuilder; @@ -72,21 +73,25 @@ fn main() { let size = window.inner_size(); (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); - - color as u32 - }) - .collect::>(); - - surface.set_buffer(&buffer, width as u16, height as u16); + surface + .resize( + NonZeroU32::new(width).unwrap(), + NonZeroU32::new(height).unwrap(), + ) + .unwrap(); + + 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; + + buffer[index as usize] = blue | (green << 8) | (red << 16); + } + + buffer.present().unwrap(); } Event::WindowEvent { event: WindowEvent::CloseRequested, diff --git a/examples/animation.rs b/examples/animation.rs index 7fcc740..f729a39 100644 --- a/examples/animation.rs +++ b/examples/animation.rs @@ -2,6 +2,7 @@ use instant::Instant; #[cfg(not(target_arch = "wasm32"))] use rayon::prelude::*; use std::f64::consts::PI; +use std::num::NonZeroU32; use winit::event::{Event, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::WindowBuilder; @@ -47,8 +48,17 @@ fn main() { frames = pre_render_frames(width as usize, height as usize); }; - let buffer = &frames[((elapsed * 60.0).round() as usize).clamp(0, 59)]; - surface.set_buffer(buffer.as_slice(), width as u16, height as u16); + let frame = &frames[((elapsed * 60.0).round() as usize).clamp(0, 59)]; + + 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 => { window.request_redraw(); diff --git a/examples/fruit.rs b/examples/fruit.rs index b64ae47..7e28e89 100644 --- a/examples/fruit.rs +++ b/examples/fruit.rs @@ -1,4 +1,5 @@ use image::GenericImageView; +use std::num::NonZeroU32; use winit::event::{Event, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::WindowBuilder; @@ -6,16 +7,6 @@ use winit::window::WindowBuilder; fn main() { //see fruit.jpg.license for the license of fruit.jpg 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::>(); let event_loop = EventLoop::new(); let window = WindowBuilder::new() @@ -45,7 +36,25 @@ fn main() { match event { 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::CloseRequested, diff --git a/examples/libxcb.rs b/examples/libxcb.rs index 4498b00..4ea97b0 100644 --- a/examples/libxcb.rs +++ b/examples/libxcb.rs @@ -3,6 +3,7 @@ #[cfg(all(feature = "x11", any(target_os = "linux", target_os = "freebsd")))] mod example { use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XcbDisplayHandle, XcbWindowHandle}; + use std::num::NonZeroU32; use x11rb::{ connection::Connection, protocol::{ @@ -12,6 +13,8 @@ mod example { xcb_ffi::XCBConnection, }; + const RED: u32 = 255 << 16; + pub(crate) fn run() { // Create a new XCB connection let (conn, screen) = XCBConnection::connect(None).expect("Failed to connect to X server"); @@ -96,13 +99,15 @@ mod example { match event { Event::Expose(_) => { // Draw a width x height red rectangle. - let red = 255 << 16; - let source = std::iter::repeat(red) - .take((width as usize * height as usize) as _) - .collect::>(); - - // Draw the buffer. - surface.set_buffer(&source, width, height); + surface + .resize( + NonZeroU32::new(width.into()).unwrap(), + NonZeroU32::new(height.into()).unwrap(), + ) + .unwrap(); + let mut buffer = surface.buffer_mut().unwrap(); + buffer.fill(RED); + buffer.present().unwrap(); } Event::ConfigureNotify(configure_notify) => { width = configure_notify.width; diff --git a/examples/rectangle.rs b/examples/rectangle.rs index 17b8a20..5443d3b 100644 --- a/examples/rectangle.rs +++ b/examples/rectangle.rs @@ -1,3 +1,4 @@ +use std::num::NonZeroU32; use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::WindowBuilder; @@ -43,7 +44,6 @@ fn main() { let context = unsafe { softbuffer::Context::new(&window) }.unwrap(); let mut surface = unsafe { softbuffer::Surface::new(&context, &window) }.unwrap(); - let mut buffer = Vec::new(); let mut flag = false; event_loop.run(move |event, _, control_flow| { @@ -54,19 +54,21 @@ fn main() { // Grab the window's client area dimensions let (width, height) = { 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 - if buffer.len() != width * height { - buffer.resize(width * height, 0); - } - - // Draw something in the offscreen buffer - redraw(&mut buffer, width, height, flag); - - // Blit the offscreen buffer to the window's client area - surface.set_buffer(&buffer, width as u16, height as u16); + // Resize surface if needed + surface + .resize( + NonZeroU32::new(width).unwrap(), + NonZeroU32::new(height).unwrap(), + ) + .unwrap(); + + // Draw something in the window + let mut buffer = surface.buffer_mut().unwrap(); + redraw(&mut buffer, width as usize, height as usize, flag); + buffer.present().unwrap(); } Event::WindowEvent { diff --git a/examples/winit.rs b/examples/winit.rs index 3ecb33c..18aa278 100644 --- a/examples/winit.rs +++ b/examples/winit.rs @@ -1,3 +1,4 @@ +use std::num::NonZeroU32; use winit::event::{Event, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::WindowBuilder; @@ -32,21 +33,26 @@ fn main() { let size = window.inner_size(); (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 - }) - .collect::>(); + 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; - 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::CloseRequested, diff --git a/examples/winit_wrong_sized_buffer.rs b/examples/winit_wrong_sized_buffer.rs index 120e5bc..09aff57 100644 --- a/examples/winit_wrong_sized_buffer.rs +++ b/examples/winit_wrong_sized_buffer.rs @@ -1,3 +1,4 @@ +use std::num::NonZeroU32; use winit::event::{Event, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::WindowBuilder; @@ -31,21 +32,25 @@ fn main() { match event { Event::RedrawRequested(window_id) if window_id == window.id() => { - let buffer = (0..(BUFFER_WIDTH * BUFFER_HEIGHT)) - .map(|index| { - let y = index / BUFFER_WIDTH; - let x = index % BUFFER_WIDTH; - let red = x % 255; - let green = y % 255; - let blue = (x * y) % 255; + surface + .resize( + NonZeroU32::new(BUFFER_WIDTH as u32).unwrap(), + NonZeroU32::new(BUFFER_HEIGHT as u32).unwrap(), + ) + .unwrap(); - let color = blue | (green << 8) | (red << 16); - - color as u32 - }) - .collect::>(); + 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; - surface.set_buffer(&buffer, BUFFER_WIDTH as u16, BUFFER_HEIGHT as u16); + let color = blue | (green << 8) | (red << 16); + buffer[y * BUFFER_WIDTH + x] = color; + } + } + buffer.present().unwrap(); } Event::WindowEvent { event: WindowEvent::CloseRequested, diff --git a/src/cg.rs b/src/cg.rs index 54573fe..fce628d 100644 --- a/src/cg.rs +++ b/src/cg.rs @@ -12,11 +12,23 @@ use cocoa::base::{id, nil}; use cocoa::quartzcore::{transaction, CALayer, ContentsGravity}; use foreign_types::ForeignType; +use std::num::NonZeroU32; use std::sync::Arc; +struct Buffer(Vec); + +impl AsRef<[u8]> for Buffer { + fn as_ref(&self) -> &[u8] { + bytemuck::cast_slice(&self.0) + } +} + pub struct CGImpl { layer: CALayer, window: id, + color_space: CGColorSpace, + width: u32, + height: u32, } impl CGImpl { @@ -35,22 +47,55 @@ impl CGImpl { view.addSubview_(subview); // retains subview (+1) = 2 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) { - let color_space = CGColorSpace::create_device_rgb(); - let data = - unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const u8, buffer.len() * 4) } - .to_vec(); - let data_provider = CGDataProvider::from_buffer(Arc::new(data)); + pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { + self.width = width.get(); + self.height = height.get(); + Ok(()) + } + + pub fn buffer_mut(&mut self) -> Result { + 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, +} + +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( - width as usize, - height as usize, + self.imp.width as usize, + self.imp.height as usize, 8, 32, - (width * 4) as usize, - &color_space, + (self.imp.width * 4) as usize, + &self.imp.color_space, kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, &data_provider, false, @@ -64,12 +109,15 @@ impl CGImpl { transaction::set_disable_actions(true); unsafe { - self.layer - .set_contents_scale(self.window.backingScaleFactor()); - self.layer.set_contents(image.as_ptr() as id); + self.imp + .layer + .set_contents_scale(self.imp.window.backingScaleFactor()); + self.imp.layer.set_contents(image.as_ptr() as id); }; transaction::commit(); + + Ok(()) } } diff --git a/src/error.rs b/src/error.rs index 01ea102..f84c649 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,8 +1,10 @@ use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; use std::error::Error; +use std::num::NonZeroU32; use thiserror::Error; #[derive(Error, Debug)] +#[allow(missing_docs)] // TODO #[non_exhaustive] pub enum SoftBufferError { #[error( @@ -27,6 +29,12 @@ pub enum SoftBufferError { #[error("The provided display handle is null.")] IncompleteDisplayHandle, + #[error("Surface size {width}x{height} out of range for backend.")] + SizeOutOfRange { + width: NonZeroU32, + height: NonZeroU32, + }, + #[error("Platform error")] PlatformError(Option, Option>), } diff --git a/src/lib.rs b/src/lib.rs index 6e424a3..f7dadf6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #![doc = include_str!("../README.md")] #![deny(unsafe_op_in_unsafe_fn)] +#![warn(missing_docs)] #[cfg(target_os = "macos")] #[macro_use] @@ -20,9 +21,13 @@ mod win32; mod x11; mod error; +mod util; +use std::marker::PhantomData; +use std::num::NonZeroU32; +use std::ops; #[cfg(any(wayland_platform, x11_platform))] -use std::sync::Arc; +use std::rc::Rc; pub use error::SoftBufferError; @@ -35,6 +40,7 @@ use raw_window_handle::{ pub struct Context { /// The inner static dispatch object. context_impl: ContextDispatch, + _marker: PhantomData<*mut ()>, } /// 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])* - $name: ident ($context_inner: ty, $surface_inner : ty), + $name: ident ($context_inner: ty, $surface_inner: ty, $buffer_inner: ty), )* ) => { enum ContextDispatch { @@ -71,11 +77,58 @@ macro_rules! make_dispatch { } 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 { $( $(#[$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 { + 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! { #[cfg(x11_platform)] - X11(Arc, x11::X11Impl), + X11(Rc, x11::X11Impl, x11::BufferImpl<'a>), #[cfg(wayland_platform)] - Wayland(std::sync::Arc, wayland::WaylandImpl), + Wayland(Rc, wayland::WaylandImpl, wayland::BufferImpl<'a>), #[cfg(target_os = "windows")] - Win32((), win32::Win32Impl), + Win32((), win32::Win32Impl, win32::BufferImpl<'a>), #[cfg(target_os = "macos")] - CG((), cg::CGImpl), + CG((), cg::CGImpl, cg::BufferImpl<'a>), #[cfg(target_arch = "wasm32")] - Web(web::WebDisplayImpl, web::WebImpl), + Web(web::WebDisplayImpl, web::WebImpl, web::BufferImpl<'a>), #[cfg(target_os = "redox")] - Orbital((), orbital::OrbitalImpl), + Orbital((), orbital::OrbitalImpl, orbital::BufferImpl<'a>), } impl Context { @@ -117,17 +172,15 @@ impl Context { let imple: ContextDispatch = match raw_display_handle { #[cfg(x11_platform)] 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)] 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)] RawDisplayHandle::Wayland(wayland_handle) => unsafe { - ContextDispatch::Wayland(Arc::new(wayland::WaylandDisplayImpl::new( - wayland_handle, - )?)) + ContextDispatch::Wayland(Rc::new(wayland::WaylandDisplayImpl::new(wayland_handle)?)) }, #[cfg(target_os = "windows")] RawDisplayHandle::Windows(_) => ContextDispatch::Win32(()), @@ -149,17 +202,20 @@ impl Context { Ok(Self { context_impl: imple, + _marker: PhantomData, }) } } +/// A surface for drawing to a window with software buffers. pub struct Surface { /// This is boxed so that `Surface` is the same size on every platform. surface_impl: Box, + _marker: PhantomData<*mut ()>, } 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 /// @@ -172,7 +228,7 @@ impl Surface { 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 /// @@ -232,38 +288,77 @@ impl Surface { Ok(Self { surface_impl: Box::new(imple), + _marker: PhantomData, }) } - /// Shows the given buffer with the given width and height on the window corresponding to this - /// 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 - /// 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. + /// Set the size of the buffer that will be returned by [`Surface::buffer_mut`]. /// - /// -------- - /// - /// Pixel format (u32): - /// - /// 00000000RRRRRRRRGGGGGGGGBBBBBBBB - /// - /// 0: Bit is 0 - /// R: Red channel - /// G: Green channel - /// B: Blue channel + /// 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. + pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { + self.surface_impl.resize(width, height) + } + + /// 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 + /// may contain a previous frame. + pub fn buffer_mut(&mut self) -> Result { + Ok(Buffer { + buffer_impl: self.surface_impl.buffer_mut()?, + _marker: PhantomData, + }) + } +} + +/// 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 /// - /// This section of the documentation details how some platforms may behave when [`set_buffer`](Surface::set_buffer) - /// is called. - /// /// ## Wayland /// /// 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 /// Wayland compositor before calling this function. + pub fn present(self) -> Result<(), SoftBufferError> { + self.buffer_impl.present() + } +} + +impl<'a> ops::Deref for Buffer<'a> { + type Target = [u32]; + #[inline] - pub fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) { - 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."); - } + fn deref(&self) -> &[u32] { + self.buffer_impl.pixels() + } +} - unsafe { - self.surface_impl.set_buffer(buffer, width, height); - } +impl<'a> ops::DerefMut for Buffer<'a> { + #[inline] + fn deref_mut(&mut self) -> &mut [u32] { + self.buffer_impl.pixels_mut() } } diff --git a/src/orbital.rs b/src/orbital.rs index 9d48126..f999b6a 100644 --- a/src/orbital.rs +++ b/src/orbital.rs @@ -1,11 +1,12 @@ use raw_window_handle::OrbitalWindowHandle; -use std::{cmp, slice, str}; +use std::{cmp, num::NonZeroU32, slice, str}; use crate::SoftBufferError; struct OrbitalMap { address: usize, size: usize, + size_unaligned: usize, } 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 { handle: OrbitalWindowHandle, + width: u32, + height: u32, } impl OrbitalImpl { pub fn new(handle: OrbitalWindowHandle) -> Result { - Ok(Self { handle }) + Ok(Self { + handle, + width: 0, + height: 0, + }) + } + + pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { + self.width = width.get(); + self.height = height.get(); + Ok(()) } - pub(crate) unsafe fn set_buffer(&mut self, buffer: &[u32], width_u16: u16, height_u16: u16) { - let window_fd = self.handle.window as usize; + fn window_fd(&self) -> usize { + self.handle.window as usize + } - // Read the current width and size + // Read the current width and size + fn window_size(&self) -> (usize, usize) { let mut window_width = 0; let mut window_height = 0; - { - let mut buf: [u8; 4096] = [0; 4096]; - let count = syscall::fpath(window_fd, &mut buf).unwrap(); - let path = str::from_utf8(&buf[..count]).unwrap(); - // orbital:/x/y/w/h/t - let mut parts = path.split('/').skip(3); - if let Some(w) = parts.next() { - window_width = w.parse::().unwrap_or(0); - } - if let Some(h) = parts.next() { - window_height = h.parse::().unwrap_or(0); - } + + let mut buf: [u8; 4096] = [0; 4096]; + let count = syscall::fpath(self.window_fd(), &mut buf).unwrap(); + let path = str::from_utf8(&buf[..count]).unwrap(); + // orbital:/x/y/w/h/t + let mut parts = path.split('/').skip(3); + if let Some(w) = parts.next() { + window_width = w.parse::().unwrap_or(0); + } + if let Some(h) = parts.next() { + window_height = h.parse::().unwrap_or(0); } + (window_width, window_height) + } + + pub fn buffer_mut(&mut self) -> Result { + 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 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"); // Window buffer is u32 color data in 0xAABBGGRR format - let window_data = unsafe { - slice::from_raw_parts_mut( - window_map.address as *mut u32, - window_width * window_height, - ) - }; + let window_data = unsafe { window_map.data_mut() }; // Copy each line, cropping to fit - let width = width_u16 as usize; - let height = height_u16 as usize; + let width = width_u32 as usize; + let height = height_u32 as usize; let min_width = cmp::min(width, window_width); let min_height = cmp::min(height, window_height); for y in 0..min_height { @@ -99,6 +141,49 @@ impl OrbitalImpl { } // 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), +} + +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(()) } } diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..cce639e --- /dev/null +++ b/src/util.rs @@ -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(container: &'a mut T, f: F) -> Result + 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]); + } +} diff --git a/src/wayland/buffer.rs b/src/wayland/buffer.rs index 594b382..e17961a 100644 --- a/src/wayland/buffer.rs +++ b/src/wayland/buffer.rs @@ -1,7 +1,9 @@ +use memmap2::MmapMut; use std::{ ffi::CStr, fs::File, - os::unix::prelude::{AsRawFd, FileExt, FromRawFd}, + os::unix::prelude::{AsRawFd, FromRawFd}, + slice, sync::{ atomic::{AtomicBool, Ordering}, Arc, @@ -69,9 +71,19 @@ fn create_memfile() -> File { 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 { qh: QueueHandle, tempfile: File, + map: MmapMut, pool: wl_shm_pool::WlShmPool, pool_size: i32, buffer: wl_buffer::WlBuffer, @@ -82,8 +94,15 @@ pub(super) struct WaylandBuffer { impl WaylandBuffer { pub fn new(shm: &wl_shm::WlShm, width: i32, height: i32, qh: &QueueHandle) -> 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 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 released = Arc::new(AtomicBool::new(true)); let buffer = pool.create_buffer( @@ -95,8 +114,10 @@ impl WaylandBuffer { qh, released.clone(), ); + Self { qh: qh.clone(), + map, tempfile, pool, pool_size, @@ -119,6 +140,7 @@ impl WaylandBuffer { let _ = self.tempfile.set_len(size as u64); self.pool.resize(size); self.pool_size = size; + self.map = unsafe { map_file(&self.tempfile) }; } // 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) { self.released.store(false, Ordering::SeqCst); surface.attach(Some(&self.buffer), 0, 0); @@ -152,6 +166,14 @@ impl WaylandBuffer { pub fn released(&self) -> bool { 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 { diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index 4f96c4a..2b95fd6 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -1,6 +1,10 @@ -use crate::{error::unwrap, SoftBufferError}; +use crate::{error::unwrap, util, SoftBufferError}; use raw_window_handle::{WaylandDisplayHandle, WaylandWindowHandle}; -use std::sync::{Arc, Mutex}; +use std::{ + cell::RefCell, + num::{NonZeroI32, NonZeroU32}, + rc::Rc, +}; use wayland_client::{ backend::{Backend, ObjectId}, globals::{registry_queue_init, GlobalListContents}, @@ -15,7 +19,7 @@ struct State; pub struct WaylandDisplayImpl { conn: Connection, - event_queue: Mutex>, + event_queue: RefCell>, qh: QueueHandle, shm: wl_shm::WlShm, } @@ -36,7 +40,7 @@ impl WaylandDisplayImpl { )?; Ok(Self { conn, - event_queue: Mutex::new(event_queue), + event_queue: RefCell::new(event_queue), qh, shm, }) @@ -44,15 +48,16 @@ impl WaylandDisplayImpl { } pub struct WaylandImpl { - display: Arc, + display: Rc, surface: wl_surface::WlSurface, buffers: Option<(WaylandBuffer, WaylandBuffer)>, + size: Option<(NonZeroI32, NonZeroI32)>, } impl WaylandImpl { pub unsafe fn new( window_handle: WaylandWindowHandle, - display: Arc, + display: Rc, ) -> Result { // SAFETY: Ensured by user let surface_id = unwrap( @@ -72,60 +77,119 @@ impl WaylandImpl { display, surface, buffers: Default::default(), + size: None, }) } - fn buffer(&mut self, width: i32, height: i32) -> &WaylandBuffer { - self.buffers = Some(if let Some((front, mut back)) = self.buffers.take() { - // Swap buffers; block if back buffer not released yet + pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { + self.size = Some( + (|| { + let width = NonZeroI32::try_from(width).ok()?; + let height = NonZeroI32::try_from(height).ok()?; + Some((width, height)) + })() + .ok_or(SoftBufferError::SizeOutOfRange { width, height })?, + ); + Ok(()) + } + + pub fn buffer_mut(&mut self) -> Result { + 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.lock().unwrap(); + let mut event_queue = self.display.event_queue.borrow_mut(); while !back.released() { - event_queue.blocking_dispatch(&mut State).unwrap(); + event_queue.blocking_dispatch(&mut State).map_err(|err| { + SoftBufferError::PlatformError( + Some("Wayland dispatch failure".to_string()), + Some(Box::new(err)), + ) + })?; } } - back.resize(width, height); - (back, front) + + // Resize, if buffer isn't large enough + back.resize(width.get(), height.get()); } 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 + 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() } - pub(super) unsafe fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) { - let _ = self + #[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 .event_queue - .lock() - .unwrap() + .borrow_mut() .dispatch_pending(&mut State); - let surface = self.surface.clone(); - let wayland_buffer = self.buffer(width.into(), height.into()); - wayland_buffer.write(buffer); - wayland_buffer.attach(&surface); - - // FIXME: Proper damaging mechanism. - // - // In order to propagate changes on compositors which track damage, for now damage the entire surface. - 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. + if let Some((front, back)) = &mut imp.buffers { + // Swap front and back buffer + std::mem::swap(front, back); + + front.attach(&imp.surface); + + // FIXME: Proper damaging mechanism. // - // i32::MAX is a valid damage box (most compositors interpret the damage box as "the entire surface") - self.surface.damage(0, 0, i32::MAX, i32::MAX); - } else { - // Introduced in version 4, it is an error to use this request in version 3 or lower. - self.surface - .damage_buffer(0, 0, width as i32, height as i32); + // In order to propagate changes on compositors which track damage, for now damage the entire surface. + if imp.surface.version() < 4 { + // FIXME: Accommodate scale factor since wl_surface::damage is in terms of surface coordinates while + // wl_surface::damage_buffer is in buffer coordinates. + // + // i32::MAX is a valid damage box (most compositors interpret the damage box as "the entire surface") + imp.surface.damage(0, 0, i32::MAX, i32::MAX); + } else { + // Introduced in version 4, it is an error to use this request in version 3 or lower. + imp.surface.damage_buffer(0, 0, width.get(), height.get()); + } + + imp.surface.commit(); } - self.surface.commit(); - let _ = self.display.event_queue.lock().unwrap().flush(); + let _ = imp.display.event_queue.borrow_mut().flush(); + + Ok(()) } } diff --git a/src/web.rs b/src/web.rs index f0e9f3d..49df5e7 100644 --- a/src/web.rs +++ b/src/web.rs @@ -1,3 +1,7 @@ +//! Implementation of software buffering for web targets. + +#![allow(clippy::uninlined_format_args)] + use raw_window_handle::WebWindowHandle; use wasm_bindgen::Clamped; use wasm_bindgen::JsCast; @@ -6,6 +10,8 @@ use web_sys::HtmlCanvasElement; use web_sys::ImageData; use crate::SoftBufferError; +use std::convert::TryInto; +use std::num::NonZeroU32; /// Display implementation for the web platform. /// @@ -36,8 +42,17 @@ impl WebDisplayImpl { } pub struct WebImpl { + /// The handle to the canvas that we're drawing to. canvas: HtmlCanvasElement, + + /// The 2D rendering context for the canvas. ctx: CanvasRenderingContext2d, + + /// The buffer that we're drawing to. + buffer: Vec, + + /// The current width of the canvas. + width: u32, } impl WebImpl { @@ -73,24 +88,82 @@ impl WebImpl { .dyn_into() .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) { - self.canvas.set_width(width.into()); - self.canvas.set_height(height.into()); + /// Resize the canvas to the given dimensions. + pub(crate) fn resize( + &mut self, + width: NonZeroU32, + height: NonZeroU32, + ) -> Result<(), SoftBufferError> { + let width = width.get(); + let height = height.get(); + + 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 { + Ok(BufferImpl { imp: self }) + } +} - let bitmap: Vec<_> = buffer +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() .copied() .flat_map(|pixel| [(pixel >> 16) as u8, (pixel >> 8) as u8, pixel as u8, 255]) .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 = - 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. - 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 + ); + }) +} diff --git a/src/win32.rs b/src/win32.rs index 3390bf7..8d9a350 100644 --- a/src/win32.rs +++ b/src/win32.rs @@ -2,33 +2,134 @@ //! //! 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 std::io; 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::Graphics::Gdi::{ - GetDC, StretchDIBits, ValidateRect, BITMAPINFOHEADER, BI_BITFIELDS, DIB_RGB_COLORS, HDC, - RGBQUAD, SRCCOPY, +use windows_sys::Win32::Graphics::Gdi; + +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, + 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::() 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. pub struct Win32Impl { /// The window handle. window: HWND, /// The device context for the window. - dc: HDC, + dc: Gdi::HDC, + + buffer: Option, } /// The Win32-compatible bitmap information. #[repr(C)] struct BitmapInfo { - pub bmi_header: BITMAPINFOHEADER, - pub bmi_colors: [RGBQUAD; 3], + bmi_header: Gdi::BITMAPINFOHEADER, + bmi_colors: [Gdi::RGBQUAD; 3], } impl Win32Impl { @@ -46,7 +147,7 @@ impl Win32Impl { // Get the handle to the device context. // SAFETY: We have confirmed that the window handle is valid. 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. 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) { - // Create a new bitmap info struct. - let bmi_header = BITMAPINFOHEADER { - biSize: mem::size_of::() as u32, - biWidth: width as i32, - biHeight: -(height as i32), - biPlanes: 1, - 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, - }; + pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { + let (width, height) = (|| { + let width = NonZeroI32::new(i32::try_from(width.get()).ok()?)?; + let height = NonZeroI32::new(i32::try_from(height.get()).ok()?)?; + Some((width, height)) + })() + .ok_or(SoftBufferError::SizeOutOfRange { width, height })?; - // Stretch the bitmap onto the window. - // SAFETY: - // - The bitmap information is valid. - // - The buffer is a valid pointer to image data. + if let Some(buffer) = self.buffer.as_ref() { + if buffer.width == width && buffer.height == height { + return Ok(()); + } + } + + self.buffer = Some(Buffer::new(self.dc, width, height)); + + Ok(()) + } + + pub fn buffer_mut(&mut self) -> Result { + 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 { - StretchDIBits( - self.dc, + Gdi::BitBlt( + imp.dc, 0, 0, - width as c_int, - height as c_int, + buffer.width.get(), + buffer.height.get(), + buffer.dc, 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, - ) - }; + Gdi::SRCCOPY, + ); + + // Validate the window. + Gdi::ValidateRect(imp.window, ptr::null_mut()); + } - // Validate the window. - unsafe { ValidateRect(self.window, std::ptr::null_mut()) }; + Ok(()) } } diff --git a/src/x11.rs b/src/x11.rs index 6a9cbaa..1f5386f 100644 --- a/src/x11.rs +++ b/src/x11.rs @@ -5,11 +5,11 @@ #![allow(clippy::uninlined_format_args)] -use crate::SoftBufferError; +use crate::{util, SoftBufferError}; use nix::libc::{shmat, shmctl, shmdt, shmget, IPC_PRIVATE, IPC_RMID}; use raw_window_handle::{XcbDisplayHandle, XcbWindowHandle, XlibDisplayHandle, XlibWindowHandle}; use std::ptr::{null_mut, NonNull}; -use std::{fmt, io, sync::Arc}; +use std::{fmt, io, mem, num::NonZeroU32, rc::Rc}; use x11_dl::xlib::Display; use x11_dl::xlib_xcb::Xlib_xcb; @@ -80,6 +80,9 @@ impl X11DisplayImpl { }; 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 { connection, @@ -91,7 +94,7 @@ impl X11DisplayImpl { /// The handle to an X11 drawing context. pub struct X11Impl { /// X display this window belongs to. - display: Arc, + display: Rc, /// The window to draw to. window: xproto::Window, @@ -102,11 +105,26 @@ pub struct X11Impl { /// The depth (bits per pixel) of the drawing context. depth: u8, - /// Information about SHM, if it is available. - shm: Option, + /// The buffer we draw to. + 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), +} + +struct ShmBuffer { /// The shared memory segment, paired with its ID. seg: Option<(ShmSegment, shm::Seg)>, @@ -133,7 +151,7 @@ impl X11Impl { /// The `XlibWindowHandle` and `XlibDisplayHandle` must be valid. pub unsafe fn from_xlib( window_handle: XlibWindowHandle, - display: Arc, + display: Rc, ) -> Result { let mut xcb_window_handle = XcbWindowHandle::empty(); xcb_window_handle.window = window_handle.window as _; @@ -150,8 +168,10 @@ impl X11Impl { /// The `XcbWindowHandle` and `XcbDisplayHandle` must be valid. pub(crate) unsafe fn from_xcb( window_handle: XcbWindowHandle, - display: Arc, + display: Rc, ) -> Result { + log::trace!("new: window_handle={:X}", window_handle.window,); + // Check that the handle is valid. if window_handle.window == 0 { return Err(SoftBufferError::IncompleteWindowHandle); @@ -187,18 +207,15 @@ impl X11Impl { .swbuf_err("Failed to get geometry reply")?; // See if SHM is available. - let shm_info = { - let present = display.is_shm_available; - - if present { - // SHM is available. - Some(ShmInfo { - seg: None, - done_processing: None, - }) - } else { - None - } + let buffer = if display.is_shm_available { + // SHM is available. + Buffer::Shm(ShmBuffer { + seg: None, + done_processing: None, + }) + } else { + // SHM is not available. + Buffer::Wire(Vec::new()) }; Ok(Self { @@ -206,119 +223,184 @@ impl X11Impl { window, gc, 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) { - // Draw the image to the buffer. - 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) - } - }); + /// Resize the internal buffer to the given width and height. + pub(crate) fn resize( + &mut self, + width: NonZeroU32, + height: NonZeroU32, + ) -> Result<(), SoftBufferError> { + log::trace!( + "resize: window={:X}, size={}x{}", + self.window, + width, + height + ); + + // Width and height should fit in u16. + 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, + }))?; - if let Err(e) = result { - log::error!("Failed to draw image to window: {}", e); + if width != self.width || height != self.height { + self.buffer + .resize(&self.display.connection, width, height) + .swbuf_err("Failed to resize X11 buffer")?; + + // We successfully resized the buffer. + self.width = width; + self.height = height; } + + Ok(()) } - /// 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, - buffer: &[u32], - width: u16, - height: u16, - ) -> Result { - let shm_info = match self.shm { - Some(ref mut info) => info, - None => return Ok(false), - }; + /// Get a mutable reference to the buffer. + pub(crate) fn buffer_mut(&mut self) -> Result { + log::trace!("buffer_mut: window={:X}", self.window); - // If the X server is still processing the last image, wait for it to finish. - shm_info.finish_wait(&self.display.connection)?; + Ok(BufferImpl(util::BorrowStack::new(self, |surface| { + let buffer = surface + .buffer + .buffer_mut(&surface.display.connection) + .swbuf_err("Failed to get mutable X11 buffer")?; - // Get the SHM segment to use. - let necessary_size = (width as usize) * (height as usize) * 4; - let (segment, segment_id) = shm_info.segment(&self.display.connection, necessary_size)?; + // Crop it down to the window size. + Ok(&mut buffer[..total_len(surface.width, surface.height) / 4]) + })?)) + } +} - // Copy the buffer into the segment. - // SAFETY: The buffer is properly sized and we've ensured that the X server isn't reading from it. - unsafe { - segment.copy(buffer); - } +pub struct BufferImpl<'a>(util::BorrowStack<'a, X11Impl, [u32]>); - // 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) +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") } +} - /// Put the given buffer into the window using the fallback wire transfer method. - fn set_buffer_fallback( +impl Buffer { + /// Resize the buffer to the given size. + fn resize( &mut self, - buffer: &[u32], + conn: &impl Connection, 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(); + 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(()) + } + } + } - 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 ShmInfo { +impl ShmBuffer { /// Allocate a new `ShmSegment` of the given size. - fn segment( + fn alloc_segment( &mut self, conn: &impl Connection, size: usize, - ) -> Result<(&mut ShmSegment, shm::Seg), PushBufferError> { + ) -> Result<(), PushBufferError> { // Round the size up to the next power of two to prevent frequent reallocations. let size = size.next_power_of_two(); @@ -334,12 +416,25 @@ impl ShmInfo { self.associate(conn, new_seg)?; } - // Get the segment and ID. - Ok(self - .seg - .as_mut() - .map(|(ref mut seg, id)| (seg, *id)) - .unwrap()) + Ok(()) + } + + /// Get the SHM buffer as a mutable reference. + fn as_mut(&mut self, conn: &impl Connection) -> Result<&mut [u32], PushBufferError> { + // 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. @@ -354,6 +449,9 @@ impl ShmInfo { // Take out the old one and detach it. 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(); // 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 /// - /// This function assumes that the size of `self`'s buffer is larger than or equal to `data.len()`. - /// In addition, no other processes should be reading from or writing to this memory. - unsafe fn copy(&mut self, data: &[T]) { - debug_assert!(data.len() * std::mem::size_of::() <= 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(), - ) - } + /// One must ensure that no other processes are reading from or writing to this memory. + unsafe fn as_mut(&mut self) -> &mut [i8] { + unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.size) } } /// Get the size of this shared memory segment. @@ -460,7 +548,7 @@ impl Drop for ShmSegment { impl Drop for X11Impl { fn drop(&mut self) { // 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. shm.finish_wait(&self.display.connection).ok(); @@ -563,11 +651,11 @@ impl From for PushBufferError { } /// Convenient wrapper to cast errors into SoftBufferError. -trait ResultExt { +trait SwResultExt { fn swbuf_err(self, msg: impl Into) -> Result; } -impl ResultExt for Result { +impl SwResultExt for Result { fn swbuf_err(self, msg: impl Into) -> Result { self.map_err(|e| { SoftBufferError::PlatformError(Some(msg.into()), Some(Box::new(LibraryError(e)))) @@ -575,6 +663,17 @@ impl ResultExt for Result { } } +/// Convenient wrapper to cast errors into PushBufferError. +trait PushResultExt { + fn push_err(self) -> Result; +} + +impl> PushResultExt for Result { + fn push_err(self) -> Result { + self.map_err(Into::into) + } +} + /// A wrapper around a library error. /// /// This prevents `x11-dl` and `x11rb` from becoming public dependencies, since users cannot downcast @@ -594,3 +693,15 @@ impl fmt::Display for LibraryError { } impl std::error::Error for LibraryError {} + +/// 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)) +}