diff --git a/CHANGELOG.md b/CHANGELOG.md index 7097bdd0..a6c594a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Added `Buffer::pixel_rows()` for iterating over rows of the buffer data. - Added `Buffer::pixels_iter()` for iterating over each pixel with its associated `x`/`y` coordinate. - **Breaking:** Removed generic type parameters `D` and `W` from `Buffer<'_>` struct. +- **Breaking:** Changed `NonZeroU32` to `u32`, and handles zero-sized buffers internally. # 0.4.7 diff --git a/README.md b/README.md index 5c59ee24..3f8d6dd4 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,6 @@ To run the Android-specific example on an Android phone: `cargo apk r --example ## Example ```rust,no_run -use std::num::NonZeroU32; use std::rc::Rc; use winit::event::{Event, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; @@ -102,12 +101,7 @@ fn main() { return; }; let size = window.inner_size(); - surface - .resize( - NonZeroU32::new(size.width).unwrap(), - NonZeroU32::new(size.height).unwrap(), - ) - .unwrap(); + surface.resize(size.width, size.height).unwrap(); let mut buffer = surface.buffer_mut().unwrap(); for (x, y, pixel) in buffer.pixels_iter() { diff --git a/benches/buffer_mut.rs b/benches/buffer_mut.rs index b9b8e137..1be01863 100644 --- a/benches/buffer_mut.rs +++ b/benches/buffer_mut.rs @@ -8,7 +8,6 @@ fn buffer_mut(c: &mut criterion::Criterion) { use criterion::black_box; use softbuffer::{Context, Surface}; - use std::num::NonZeroU32; use winit::event_loop::ControlFlow; use winit::platform::run_on_demand::EventLoopExtRunOnDemand; @@ -27,12 +26,7 @@ fn buffer_mut(c: &mut criterion::Criterion) { let mut surface = Surface::new(&context, &window).unwrap(); let size = window.inner_size(); - surface - .resize( - NonZeroU32::new(size.width).unwrap(), - NonZeroU32::new(size.height).unwrap(), - ) - .unwrap(); + surface.resize(size.width, size.height).unwrap(); c.bench_function("buffer_mut()", |b| { b.iter(|| { diff --git a/examples/animation.rs b/examples/animation.rs index 4a30856b..04a66256 100644 --- a/examples/animation.rs +++ b/examples/animation.rs @@ -1,7 +1,6 @@ #[cfg(not(target_family = "wasm"))] use rayon::prelude::*; use std::f64::consts::PI; -use std::num::NonZeroU32; use web_time::Instant; use winit::event::{KeyEvent, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; @@ -46,11 +45,7 @@ fn main() { return; }; - if let (Some(width), Some(height)) = - (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) - { - surface.resize(width, height).unwrap(); - } + surface.resize(size.width, size.height).unwrap(); } WindowEvent::RedrawRequested => { let Some(surface) = surface else { @@ -62,7 +57,7 @@ fn main() { let mut buffer = surface.buffer_mut().unwrap(); - let size = (buffer.width().get(), buffer.height().get()); + let size = (buffer.width(), buffer.height()); if size != *old_size { *old_size = size; *frames = pre_render_frames(size.0, size.1); diff --git a/examples/drm.rs b/examples/drm.rs index 78c0af5c..4234dce0 100644 --- a/examples/drm.rs +++ b/examples/drm.rs @@ -19,7 +19,6 @@ mod imple { use raw_window_handle::{DisplayHandle, DrmDisplayHandle, DrmWindowHandle, WindowHandle}; use softbuffer::{Context, Surface}; - use std::num::NonZeroU32; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd}; use std::path::Path; use std::time::{Duration, Instant}; @@ -114,10 +113,7 @@ mod imple { // Resize the surface. let (width, height) = mode.size(); - surface.resize( - NonZeroU32::new(width as u32).unwrap(), - NonZeroU32::new(height as u32).unwrap(), - )?; + surface.resize(width as u32, height as u32)?; // Start drawing to it. let start = Instant::now(); diff --git a/examples/fruit.rs b/examples/fruit.rs index 4f76b44a..b8bdc2ff 100644 --- a/examples/fruit.rs +++ b/examples/fruit.rs @@ -1,5 +1,4 @@ use image::GenericImageView; -use std::num::NonZeroU32; use winit::event::{KeyEvent, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::keyboard::{Key, NamedKey}; @@ -27,12 +26,7 @@ fn main() { // Intentionally only set the size of the surface once, at creation. // This is needed if the window chooses to ignore the size we passed in above, and for the // platforms softbuffer supports that don't yet extract the size from the window. - surface - .resize( - NonZeroU32::new(width).unwrap(), - NonZeroU32::new(height).unwrap(), - ) - .unwrap(); + surface.resize(width, height).unwrap(); surface }, ) diff --git a/examples/libxcb.rs b/examples/libxcb.rs index 1f9f8c95..e8a431cb 100644 --- a/examples/libxcb.rs +++ b/examples/libxcb.rs @@ -117,12 +117,7 @@ mod example { match event { Event::Expose(_) => { // Draw a width x height red rectangle. - surface - .resize( - NonZeroU32::new(width.into()).unwrap(), - NonZeroU32::new(height.into()).unwrap(), - ) - .unwrap(); + surface.resize(width.into(), height.into()).unwrap(); let mut buffer = surface.buffer_mut().unwrap(); buffer.fill(RED); buffer.present().unwrap(); diff --git a/examples/raytracing/game.rs b/examples/raytracing/game.rs index 554477ad..8952218d 100644 --- a/examples/raytracing/game.rs +++ b/examples/raytracing/game.rs @@ -56,8 +56,8 @@ impl Game { // https://github.com/rust-windowing/softbuffer/issues/177 let scale_factor = scale_factor * 4.0; - let width = buffer.width().get() as f32 / scale_factor; - let height = buffer.height().get() as f32 / scale_factor; + let width = buffer.width() as f32 / scale_factor; + let height = buffer.height() as f32 / scale_factor; let dist_to_focus = 10.0; let aperture = 0.1; @@ -108,7 +108,7 @@ impl Game { } // Upscale by `scale_factor`. - let width = (buffer.width().get() as f32 / scale_factor) as usize; + let width = (buffer.width() as f32 / scale_factor) as usize; buffer.pixels_iter().for_each(|(x, y, pixel)| { let x = (x as f32 / scale_factor) as usize; let y = (y as f32 / scale_factor) as usize; @@ -128,8 +128,8 @@ impl Game { color: u32, } - let width = buffer.width().get() as f32 / scale_factor; - let height = buffer.height().get() as f32 / scale_factor; + let width = buffer.width() as f32 / scale_factor; + let height = buffer.height() as f32 / scale_factor; let rects = &[ Rect { left: 10.0, diff --git a/examples/raytracing/main.rs b/examples/raytracing/main.rs index 2eed2d7e..b47245c5 100644 --- a/examples/raytracing/main.rs +++ b/examples/raytracing/main.rs @@ -3,7 +3,6 @@ //! Note that this is quite slow, you probably don't want to do realtime CPU raytracing in practice. //! //! [Ray Tracing in One Weekend]: https://raytracing.github.io/books/RayTracingInOneWeekend.html -use std::num::NonZeroU32; use winit::event::{DeviceEvent, ElementState, KeyEvent, WindowEvent}; use winit::event_loop::EventLoop; use winit::keyboard::{Key, KeyCode, NamedKey, PhysicalKey}; @@ -32,10 +31,7 @@ fn main() { move |_elwt, window| { let mut surface = softbuffer::Surface::new(&context, window.clone()).unwrap(); surface - .resize( - NonZeroU32::new(window.inner_size().width).unwrap(), - NonZeroU32::new(window.inner_size().height).unwrap(), - ) + .resize(window.inner_size().width, window.inner_size().height) .unwrap(); let game = Game::new(); (surface, game) @@ -53,11 +49,7 @@ fn main() { return; }; - if let (Some(width), Some(height)) = - (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) - { - surface.resize(width, height).unwrap(); - } + surface.resize(size.width, size.height).unwrap(); } WindowEvent::RedrawRequested => { let Some((surface, game)) = surface else { diff --git a/examples/rectangle.rs b/examples/rectangle.rs index 1124ea0c..abb60301 100644 --- a/examples/rectangle.rs +++ b/examples/rectangle.rs @@ -1,5 +1,4 @@ use softbuffer::Buffer; -use std::num::NonZeroU32; use winit::event::{ElementState, KeyEvent, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::keyboard::{Key, NamedKey}; @@ -7,8 +6,8 @@ use winit::keyboard::{Key, NamedKey}; mod util; fn redraw(buffer: &mut Buffer<'_>, flag: bool) { - let width = buffer.width().get(); - let height = buffer.height().get(); + let width = buffer.width(); + let height = buffer.height(); for (x, y, pixel) in buffer.pixels_iter() { *pixel = if flag && x >= 100 && x < width - 100 && y >= 100 && y < height - 100 { 0x00ffffff @@ -55,12 +54,8 @@ fn main() { return; }; - if let (Some(width), Some(height)) = - (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) - { - // Resize surface - surface.resize(width, height).unwrap(); - } + // Resize surface + surface.resize(size.width, size.height).unwrap(); } WindowEvent::RedrawRequested => { let Some(surface) = surface else { diff --git a/examples/winit.rs b/examples/winit.rs index 240a04fc..60f2141f 100644 --- a/examples/winit.rs +++ b/examples/winit.rs @@ -1,4 +1,3 @@ -use std::num::NonZeroU32; use winit::event::{KeyEvent, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::keyboard::{Key, NamedKey}; @@ -33,11 +32,7 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { return; }; - if let (Some(width), Some(height)) = - (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) - { - surface.resize(width, height).unwrap(); - } + surface.resize(size.width, size.height).unwrap(); } WindowEvent::RedrawRequested => { let Some(surface) = surface else { diff --git a/examples/winit_multithread.rs b/examples/winit_multithread.rs index 52791f14..37dfb7fc 100644 --- a/examples/winit_multithread.rs +++ b/examples/winit_multithread.rs @@ -4,8 +4,8 @@ mod util; #[cfg(not(target_family = "wasm"))] pub mod ex { - use std::num::NonZeroU32; use std::sync::{mpsc, Arc, Mutex}; + use winit::dpi::PhysicalSize; use winit::event::{KeyEvent, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop, OwnedDisplayHandle}; use winit::keyboard::{Key, NamedKey}; @@ -16,12 +16,12 @@ pub mod ex { type Surface = softbuffer::Surface>; fn render_thread( - do_render: mpsc::Receiver<(Arc>, NonZeroU32, NonZeroU32)>, + do_render: mpsc::Receiver<(Arc>, PhysicalSize)>, done: mpsc::Sender<()>, ) { loop { tracing::info!("waiting for render..."); - let Ok((surface, width, height)) = do_render.recv() else { + let Ok((surface, size)) = do_render.recv() else { tracing::info!("main thread destroyed"); break; }; @@ -29,7 +29,7 @@ pub mod ex { // Perform the rendering. let mut surface = surface.lock().unwrap(); tracing::info!("resizing..."); - surface.resize(width, height).unwrap(); + surface.resize(size.width, size.height).unwrap(); let mut buffer = surface.buffer_mut().unwrap(); for (x, y, pixel) in buffer.pixels_iter() { @@ -93,13 +93,9 @@ pub mod ex { let size = window.inner_size(); tracing::info!("got size: {size:?}"); - if let (Some(width), Some(height)) = - (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) - { - // Start the render and then finish it. - start_render.send((surface.clone(), width, height)).unwrap(); - finish_render.recv().unwrap(); - } + // Start the render and then finish it. + start_render.send((surface.clone(), size)).unwrap(); + finish_render.recv().unwrap(); } WindowEvent::CloseRequested | WindowEvent::KeyboardInput { diff --git a/examples/winit_wrong_sized_buffer.rs b/examples/winit_wrong_sized_buffer.rs index 0160f771..ac15c4db 100644 --- a/examples/winit_wrong_sized_buffer.rs +++ b/examples/winit_wrong_sized_buffer.rs @@ -1,4 +1,7 @@ -use std::num::NonZeroU32; +//! A window with a surface that is 200 pixels less wide and 400 pixels less tall. +//! +//! This is useful for testing that zero-sized buffers work, as well as testing buffer vs. window +//! size discrepancies in general. use winit::event::{KeyEvent, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::keyboard::{Key, NamedKey}; @@ -14,11 +17,14 @@ fn main() { let app = util::WinitAppBuilder::with_init( |elwt| util::make_window(elwt, |w| w), move |_elwt, window| { + let size = window.inner_size(); + let mut surface = softbuffer::Surface::new(&context, window.clone()).unwrap(); - // Intentionally set the size of the surface to something different than the size of the window. - surface - .resize(NonZeroU32::new(256).unwrap(), NonZeroU32::new(128).unwrap()) - .unwrap(); + + let width = size.width.saturating_sub(200); + let height = size.height.saturating_sub(400); + tracing::info!("size initially at: {width}/{height}"); + surface.resize(width, height).unwrap(); surface }, ) @@ -30,6 +36,17 @@ fn main() { } match event { + WindowEvent::Resized(size) => { + let Some(surface) = surface else { + tracing::warn!("RedrawRequested fired before Resumed or after Suspended"); + return; + }; + + let width = size.width.saturating_sub(200); + let height = size.height.saturating_sub(400); + tracing::info!("resized to: {width}/{height}"); + surface.resize(width, height).unwrap(); + } WindowEvent::RedrawRequested => { let Some(surface) = surface else { tracing::warn!("RedrawRequested fired before Resumed or after Suspended"); @@ -43,6 +60,7 @@ fn main() { let blue = (x * y) % 255; *pixel = blue | (green << 8) | (red << 16); } + buffer.present().unwrap(); } WindowEvent::CloseRequested diff --git a/src/backend_dispatch.rs b/src/backend_dispatch.rs index 9eaf7798..7f9e701a 100644 --- a/src/backend_dispatch.rs +++ b/src/backend_dispatch.rs @@ -4,7 +4,6 @@ use crate::{backend_interface::*, backends, InitError, Rect, SoftBufferError}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use std::fmt; -use std::num::NonZeroU32; /// A macro for creating the enum used to statically dispatch to the platform-specific implementation. macro_rules! make_dispatch { @@ -99,7 +98,7 @@ macro_rules! make_dispatch { } } - fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { + fn resize(&mut self, width: u32, height: u32) -> Result<(), SoftBufferError> { match self { $( $(#[$attr])* @@ -147,7 +146,7 @@ macro_rules! make_dispatch { impl BufferInterface for BufferDispatch<'_> { #[inline] - fn width(&self) -> NonZeroU32 { + fn width(&self) -> u32 { match self { $( $(#[$attr])* @@ -157,7 +156,7 @@ macro_rules! make_dispatch { } #[inline] - fn height(&self) -> NonZeroU32 { + fn height(&self) -> u32 { match self { $( $(#[$attr])* diff --git a/src/backend_interface.rs b/src/backend_interface.rs index b6a56661..35416fea 100644 --- a/src/backend_interface.rs +++ b/src/backend_interface.rs @@ -3,7 +3,6 @@ use crate::{InitError, Rect, SoftBufferError}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; -use std::num::NonZeroU32; pub(crate) trait ContextInterface { fn new(display: D) -> Result> @@ -25,7 +24,7 @@ pub(crate) trait SurfaceInterface &W; /// Resize the internal buffer to the given width and height. - fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError>; + fn resize(&mut self, width: u32, height: u32) -> Result<(), SoftBufferError>; /// Get a mutable reference to the buffer. fn buffer_mut(&mut self) -> Result, SoftBufferError>; /// Fetch the buffer from the window. @@ -35,8 +34,8 @@ pub(crate) trait SurfaceInterface NonZeroU32; - fn height(&self) -> NonZeroU32; + fn width(&self) -> u32; + fn height(&self) -> u32; fn pixels(&self) -> &[u32]; fn pixels_mut(&mut self) -> &mut [u32]; fn age(&self) -> u8; diff --git a/src/backends/android.rs b/src/backends/android.rs index 54dcae07..ffb8b428 100644 --- a/src/backends/android.rs +++ b/src/backends/android.rs @@ -1,7 +1,7 @@ //! Implementation of software buffering for Android. use std::marker::PhantomData; -use std::num::{NonZeroI32, NonZeroU32}; +use std::mem::MaybeUninit; use ndk::{ hardware_buffer_format::HardwareBufferFormat, @@ -18,6 +18,8 @@ use crate::{util, BufferInterface, Rect, SoftBufferError, SurfaceInterface}; #[derive(Debug)] pub struct AndroidImpl { native_window: NativeWindow, + width: u32, + height: u32, window: W, _display: PhantomData, } @@ -42,6 +44,8 @@ impl SurfaceInterface for Android Ok(Self { native_window, + width: 0, + height: 0, _display: PhantomData, window, }) @@ -53,18 +57,15 @@ impl SurfaceInterface for Android } /// Also changes the pixel format to [`HardwareBufferFormat::R8G8B8A8_UNORM`]. - fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { - let (width, height) = (|| { - let width = NonZeroI32::try_from(width).ok()?; - let height = NonZeroI32::try_from(height).ok()?; - Some((width, height)) - })() - .ok_or(SoftBufferError::SizeOutOfRange { width, height })?; + fn resize(&mut self, width: u32, height: u32) -> Result<(), SoftBufferError> { + let (width_i32, height_i32) = util::convert_size::(width, height) + .map_err(|_| SoftBufferError::SizeOutOfRange { width, height })?; + // Make the Window's buffer be at least 1 pixel wide/high. self.native_window .set_buffers_geometry( - width.into(), - height.into(), + width_i32.max(1), + height_i32.max(1), // Default is typically R5G6B5 16bpp, switch to 32bpp Some(HardwareBufferFormat::R8G8B8X8_UNORM), ) @@ -73,7 +74,11 @@ impl SurfaceInterface for Android Some("Failed to set buffer geometry on ANativeWindow".to_owned()), Some(Box::new(err)), ) - }) + })?; + self.width = width; + self.height = height; + + Ok(()) } fn buffer_mut(&mut self) -> Result, SoftBufferError> { @@ -99,10 +104,12 @@ impl SurfaceInterface for Android )); } - let buffer = vec![0; native_window_buffer.width() * native_window_buffer.height()]; + let buffer = vec![0; self.width as usize * self.height as usize]; Ok(BufferImpl { native_window_buffer, + width: self.width, + height: self.height, buffer: util::PixelBuffer(buffer), }) } @@ -116,6 +123,8 @@ impl SurfaceInterface for Android #[derive(Debug)] pub struct BufferImpl<'a> { native_window_buffer: NativeWindowBufferLockGuard<'a>, + width: u32, + height: u32, buffer: util::PixelBuffer, } @@ -123,12 +132,12 @@ pub struct BufferImpl<'a> { unsafe impl Send for BufferImpl<'_> {} impl BufferInterface for BufferImpl<'_> { - fn width(&self) -> NonZeroU32 { - NonZeroU32::new(self.native_window_buffer.width() as u32).unwrap() + fn width(&self) -> u32 { + self.width } - fn height(&self) -> NonZeroU32 { - NonZeroU32::new(self.native_window_buffer.height() as u32).unwrap() + fn height(&self) -> u32 { + self.height } #[inline] @@ -148,7 +157,14 @@ impl BufferInterface for BufferImpl<'_> { // TODO: This function is pretty slow this way fn present(mut self) -> Result<(), SoftBufferError> { - let input_lines = self.buffer.chunks(self.native_window_buffer.width()); + if self.width == 0 || self.height == 0 { + for line in self.native_window_buffer.lines().unwrap() { + line.fill(MaybeUninit::new(0x00000000)); + } + return Ok(()); + } + + let input_lines = self.buffer.chunks(self.width as usize); for (output, input) in self .native_window_buffer .lines() diff --git a/src/backends/cg.rs b/src/backends/cg.rs index 2cc27a6e..b27378fd 100644 --- a/src/backends/cg.rs +++ b/src/backends/cg.rs @@ -21,7 +21,6 @@ use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle}; use std::ffi::c_void; use std::marker::PhantomData; use std::mem::size_of; -use std::num::NonZeroU32; use std::ops::Deref; use std::ptr::{self, slice_from_raw_parts_mut, NonNull}; @@ -105,9 +104,9 @@ pub struct CGImpl { observer: Retained, color_space: CFRetained, /// The width of the underlying buffer. - width: usize, + width: u32, /// The height of the underlying buffer. - height: usize, + height: u32, window_handle: W, _display: PhantomData, } @@ -228,20 +227,13 @@ impl SurfaceInterface for CGImpl< // Initialize color space here, to reduce work later on. let color_space = CGColorSpace::new_device_rgb().unwrap(); - // Grab initial width and height from the layer (whose properties have just been initialized - // by the observer using `NSKeyValueObservingOptionInitial`). - let size = layer.bounds().size; - let scale_factor = layer.contentsScale(); - let width = (size.width * scale_factor) as usize; - let height = (size.height * scale_factor) as usize; - Ok(Self { layer: SendCALayer(layer), root_layer: SendCALayer(root_layer), observer, color_space, - width, - height, + width: 0, + height: 0, _display: PhantomData, window_handle: window_src, }) @@ -252,15 +244,15 @@ impl SurfaceInterface for CGImpl< &self.window_handle } - fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { - self.width = width.get() as usize; - self.height = height.get() as usize; + fn resize(&mut self, width: u32, height: u32) -> Result<(), SoftBufferError> { + self.width = width; + self.height = height; Ok(()) } fn buffer_mut(&mut self) -> Result, SoftBufferError> { Ok(BufferImpl { - buffer: util::PixelBuffer(vec![0; self.width * self.height]), + buffer: util::PixelBuffer(vec![0; self.width as usize * self.height as usize]), width: self.width, height: self.height, color_space: &self.color_space, @@ -271,20 +263,20 @@ impl SurfaceInterface for CGImpl< #[derive(Debug)] pub struct BufferImpl<'a> { - width: usize, - height: usize, + width: u32, + height: u32, color_space: &'a CGColorSpace, buffer: util::PixelBuffer, layer: &'a mut SendCALayer, } impl BufferInterface for BufferImpl<'_> { - fn width(&self) -> NonZeroU32 { - NonZeroU32::new(self.width as u32).unwrap() + fn width(&self) -> u32 { + self.width } - fn height(&self) -> NonZeroU32 { - NonZeroU32::new(self.height as u32).unwrap() + fn height(&self) -> u32 { + self.height } #[inline] @@ -302,57 +294,65 @@ impl BufferInterface for BufferImpl<'_> { } fn present(self) -> Result<(), SoftBufferError> { - unsafe extern "C-unwind" fn release( - _info: *mut c_void, - data: NonNull, - size: usize, - ) { - let data = data.cast::(); - let slice = slice_from_raw_parts_mut(data.as_ptr(), size / size_of::()); - // SAFETY: This is the same slice that we passed to `Box::into_raw` below. - drop(unsafe { Box::from_raw(slice) }) - } + let image = if !self.buffer.is_empty() { + unsafe extern "C-unwind" fn release( + _info: *mut c_void, + data: NonNull, + size: usize, + ) { + let data = data.cast::(); + let slice = slice_from_raw_parts_mut(data.as_ptr(), size / size_of::()); + // SAFETY: This is the same slice that we passed to `Box::into_raw` below. + drop(unsafe { Box::from_raw(slice) }) + } - let data_provider = { - let len = self.buffer.len() * size_of::(); - let buffer: *mut [u32] = Box::into_raw(self.buffer.0.into_boxed_slice()); - // Convert slice pointer to thin pointer. - let data_ptr = buffer.cast::(); + let data_provider = { + let len = self.buffer.len() * size_of::(); + let buffer: *mut [u32] = Box::into_raw(self.buffer.0.into_boxed_slice()); + // Convert slice pointer to thin pointer. + let data_ptr = buffer.cast::(); + + // SAFETY: The data pointer and length are valid. + // The info pointer can safely be NULL, we don't use it in the `release` callback. + unsafe { + CGDataProvider::with_data(ptr::null_mut(), data_ptr, len, Some(release)) + .unwrap() + } + }; + + // `CGBitmapInfo` consists of a combination of `CGImageAlphaInfo`, `CGImageComponentInfo` + // `CGImageByteOrderInfo` and `CGImagePixelFormatInfo` (see e.g. `CGBitmapInfoMake`). + // + // TODO: Use `CGBitmapInfo::new` once the next version of objc2-core-graphics is released. + let bitmap_info = CGBitmapInfo( + CGImageAlphaInfo::NoneSkipFirst.0 + | CGImageComponentInfo::Integer.0 + | CGImageByteOrderInfo::Order32Little.0 + | CGImagePixelFormatInfo::Packed.0, + ); - // SAFETY: The data pointer and length are valid. - // The info pointer can safely be NULL, we don't use it in the `release` callback. - unsafe { - CGDataProvider::with_data(ptr::null_mut(), data_ptr, len, Some(release)).unwrap() + let image = unsafe { + CGImage::new( + self.width as usize, + self.height as usize, + 8, + 32, + self.width as usize * 4, + Some(self.color_space), + bitmap_info, + Some(&data_provider), + ptr::null(), + false, + CGColorRenderingIntent::RenderingIntentDefault, + ) } - }; + .unwrap(); - // `CGBitmapInfo` consists of a combination of `CGImageAlphaInfo`, `CGImageComponentInfo` - // `CGImageByteOrderInfo` and `CGImagePixelFormatInfo` (see e.g. `CGBitmapInfoMake`). - // - // TODO: Use `CGBitmapInfo::new` once the next version of objc2-core-graphics is released. - let bitmap_info = CGBitmapInfo( - CGImageAlphaInfo::NoneSkipFirst.0 - | CGImageComponentInfo::Integer.0 - | CGImageByteOrderInfo::Order32Little.0 - | CGImagePixelFormatInfo::Packed.0, - ); - - let image = unsafe { - CGImage::new( - self.width, - self.height, - 8, - 32, - self.width * 4, - Some(self.color_space), - bitmap_info, - Some(&data_provider), - ptr::null(), - false, - CGColorRenderingIntent::RenderingIntentDefault, - ) - } - .unwrap(); + Some(image) + } else { + // Buffer is empty -> clear contents. + None + }; // The CALayer has a default action associated with a change in the layer contents, causing // a quarter second fade transition to happen every time a new buffer is applied. This can @@ -360,8 +360,9 @@ impl BufferInterface for BufferImpl<'_> { CATransaction::begin(); CATransaction::setDisableActions(true); + let contents = image.as_ref().map(|i| i.as_ref()); // SAFETY: The contents is `CGImage`, which is a valid class for `contents`. - unsafe { self.layer.setContents(Some(image.as_ref())) }; + unsafe { self.layer.setContents(contents) }; CATransaction::commit(); Ok(()) diff --git a/src/backends/kms.rs b/src/backends/kms.rs index 4b18dbdf..b308d9aa 100644 --- a/src/backends/kms.rs +++ b/src/backends/kms.rs @@ -107,8 +107,11 @@ pub(crate) struct BufferImpl<'a> { /// This is used to change the front buffer. first_is_front: &'a mut bool, - /// The current size. - size: (NonZeroU32, NonZeroU32), + /// The current width. + width: u32, + + /// The current height. + height: u32, /// The device file descriptor. device: DrmDevice<'a>, @@ -222,7 +225,7 @@ impl SurfaceInterface fo &self.window_handle } - fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { + fn resize(&mut self, width: u32, height: u32) -> Result<(), SoftBufferError> { // Don't resize if we don't have to. if let Some(buffer) = &self.buffer { let (buffer_width, buffer_height) = buffer.size(); @@ -277,7 +280,8 @@ impl SurfaceInterface fo Ok(BufferImpl { mapping, - size, + width: size.0, + height: size.1, first_is_front: &mut set.first_is_front, front_fb, crtc_handle: self.crtc.handle(), @@ -305,12 +309,12 @@ impl Drop for KmsImpl { } impl BufferInterface for BufferImpl<'_> { - fn width(&self) -> NonZeroU32 { - self.size.0 + fn width(&self) -> u32 { + self.width } - fn height(&self) -> NonZeroU32 { - self.size.1 + fn height(&self) -> u32 { + self.height } #[inline] @@ -338,11 +342,11 @@ impl BufferInterface for BufferImpl<'_> { rect.x.try_into().map_err(|_| err())?, rect.y.try_into().map_err(|_| err())?, rect.x - .checked_add(rect.width.get()) + .checked_add(rect.width) .and_then(|x| x.try_into().ok()) .ok_or_else(err)?, rect.y - .checked_add(rect.height.get()) + .checked_add(rect.height) .and_then(|y| y.try_into().ok()) .ok_or_else(err)?, )) @@ -386,13 +390,13 @@ impl BufferInterface for BufferImpl<'_> { #[inline] fn present(self) -> Result<(), SoftBufferError> { - let (width, height) = self.size; - self.present_with_damage(&[crate::Rect { + let rect = crate::Rect { x: 0, y: 0, - width, - height, - }]) + width: self.width, + height: self.height, + }; + self.present_with_damage(&[rect]) } } @@ -400,12 +404,12 @@ impl SharedBuffer { /// Create a new buffer set. pub(crate) fn new( display: &KmsDisplayImpl, - width: NonZeroU32, - height: NonZeroU32, + width: u32, + height: u32, ) -> Result { let db = display .device - .create_dumb_buffer((width.get(), height.get()), DrmFourcc::Xrgb8888, 32) + .create_dumb_buffer((width, height), DrmFourcc::Xrgb8888, 32) .swbuf_err("failed to create dumb buffer")?; let fb = display .device @@ -414,20 +418,11 @@ impl SharedBuffer { Ok(SharedBuffer { fb, db, age: 0 }) } - - /// Get the size of this buffer. - pub(crate) fn size(&self) -> (NonZeroU32, NonZeroU32) { - let (width, height) = self.db.size(); - - NonZeroU32::new(width) - .and_then(|width| NonZeroU32::new(height).map(|height| (width, height))) - .expect("buffer size is zero") - } } impl Buffers { /// Get the size of this buffer. - pub(crate) fn size(&self) -> (NonZeroU32, NonZeroU32) { - self.buffers[0].size() + pub(crate) fn size(&self) -> (u32, u32) { + self.buffers[0].db.size() } } diff --git a/src/backends/orbital.rs b/src/backends/orbital.rs index adcd3db1..8e387850 100644 --- a/src/backends/orbital.rs +++ b/src/backends/orbital.rs @@ -1,6 +1,6 @@ use crate::error::InitError; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, OrbitalWindowHandle, RawWindowHandle}; -use std::{cmp, marker::PhantomData, num::NonZeroU32, slice, str}; +use std::{cmp, marker::PhantomData, slice, str}; use crate::backend_interface::*; use crate::{util, Rect, SoftBufferError}; @@ -105,9 +105,7 @@ impl SurfaceInterface for Orbital &self.window_handle } - fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { - let width = width.get(); - let height = height.get(); + fn resize(&mut self, width: u32, height: u32) -> Result<(), SoftBufferError> { if width != self.width || height != self.height { self.presented = false; self.width = width; @@ -157,12 +155,12 @@ pub struct BufferImpl<'a> { } impl BufferInterface for BufferImpl<'_> { - fn width(&self) -> NonZeroU32 { - NonZeroU32::new(self.width as u32).unwrap() + fn width(&self) -> u32 { + self.width } - fn height(&self) -> NonZeroU32 { - NonZeroU32::new(self.height as u32).unwrap() + fn height(&self) -> u32 { + self.height } #[inline] diff --git a/src/backends/wayland/buffer.rs b/src/backends/wayland/buffer.rs index e28653a1..2a50e4af 100644 --- a/src/backends/wayland/buffer.rs +++ b/src/backends/wayland/buffer.rs @@ -74,9 +74,10 @@ pub(super) struct WaylandBuffer { map: MmapMut, pool: wl_shm_pool::WlShmPool, pool_size: i32, - buffer: wl_buffer::WlBuffer, - pub width: i32, - pub height: i32, + // width == 0 || height == 0 -> None + buffer: Option, + width: i32, + height: i32, released: Arc, pub age: u8, } @@ -91,18 +92,9 @@ impl WaylandBuffer { let _ = tempfile.set_len(pool_size as u64); let map = unsafe { map_file(&tempfile) }; - // Create wayland shm pool and buffer + // Create wayland shm pool. let pool = shm.create_pool(tempfile.as_fd(), pool_size, qh, ()); let released = Arc::new(AtomicBool::new(true)); - let buffer = pool.create_buffer( - 0, - width, - height, - width * 4, - wl_shm::Format::Xrgb8888, - qh, - released.clone(), - ); Self { qh: qh.clone(), @@ -110,7 +102,7 @@ impl WaylandBuffer { tempfile, pool, pool_size, - buffer, + buffer: None, width, height, released, @@ -122,10 +114,12 @@ impl WaylandBuffer { // If size is the same, there's nothing to do if self.width != width || self.height != height { // Destroy old buffer - self.buffer.destroy(); + if let Some(buffer) = &mut self.buffer { + buffer.destroy(); + } // Grow pool, if needed - let size = ((width * height * 4) as u32).next_power_of_two() as i32; + let size = get_pool_size(width, height); if size > self.pool_size { let _ = self.tempfile.set_len(size as u64); self.pool.resize(size); @@ -134,15 +128,19 @@ impl WaylandBuffer { } // Create buffer with correct size - self.buffer = self.pool.create_buffer( - 0, - width, - height, - width * 4, - wl_shm::Format::Xrgb8888, - &self.qh, - self.released.clone(), - ); + self.buffer = if width != 0 && height != 0 { + Some(self.pool.create_buffer( + 0, + width, + height, + width * 4, + wl_shm::Format::Xrgb8888, + &self.qh, + self.released.clone(), + )) + } else { + None + }; self.width = width; self.height = height; } @@ -150,7 +148,8 @@ impl WaylandBuffer { pub fn attach(&self, surface: &wl_surface::WlSurface) { self.released.store(false, Ordering::SeqCst); - surface.attach(Some(&self.buffer), 0, 0); + // Zero-sized + surface.attach(self.buffer.as_ref(), 0, 0); } pub fn released(&self) -> bool { @@ -172,7 +171,9 @@ impl WaylandBuffer { impl Drop for WaylandBuffer { fn drop(&mut self) { - self.buffer.destroy(); + if let Some(buffer) = &mut self.buffer { + buffer.destroy(); + } self.pool.destroy(); } } diff --git a/src/backends/wayland/mod.rs b/src/backends/wayland/mod.rs index d55e9639..5cb2d25e 100644 --- a/src/backends/wayland/mod.rs +++ b/src/backends/wayland/mod.rs @@ -1,14 +1,10 @@ use crate::{ backend_interface::*, error::{InitError, SwResultExt}, - util::BorrowStack, - Rect, SoftBufferError, + util, Rect, SoftBufferError, }; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle}; -use std::{ - num::{NonZeroI32, NonZeroU32}, - sync::{Arc, Mutex}, -}; +use std::sync::{Arc, Mutex}; use wayland_client::{ backend::{Backend, ObjectId}, globals::{registry_queue_init, GlobalListContents}, @@ -81,7 +77,8 @@ pub struct WaylandImpl { display: Arc>, surface: Option, buffers: Option<(WaylandBuffer, WaylandBuffer)>, - size: Option<(NonZeroI32, NonZeroI32)>, + width: i32, + height: i32, /// The pointer to the window object. /// @@ -119,7 +116,8 @@ impl SurfaceInterface display: display.clone(), surface: Some(surface), buffers: Default::default(), - size: None, + width: 0, + height: 0, window_handle: window, }) } @@ -129,23 +127,13 @@ impl SurfaceInterface &self.window_handle } - 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 })?, - ); + fn resize(&mut self, width: u32, height: u32) -> Result<(), SoftBufferError> { + (self.width, self.height) = util::convert_size::(width, height) + .map_err(|_| SoftBufferError::SizeOutOfRange { width, height })?; Ok(()) } fn buffer_mut(&mut self) -> Result, SoftBufferError> { - 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() { @@ -164,36 +152,25 @@ impl SurfaceInterface } } - // Resize, if buffer isn't large enough - back.resize(width.get(), height.get()); + // Resize (if buffer isn't large enough). + back.resize(self.width, self.height); } 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, - ), + WaylandBuffer::new(&self.display.shm, self.width, self.height, &self.display.qh), + WaylandBuffer::new(&self.display.shm, self.width, self.height, &self.display.qh), )); - }; + } let (front, back) = self.buffers.as_mut().unwrap(); - - let width = back.width; - let height = back.height; + let width = self.width; + let height = self.height; let age = back.age; Ok(BufferImpl { event_queue: &self.display.event_queue, surface: self.surface.as_ref().unwrap(), front, - back: BorrowStack::new(back, |buffer| buffer.mapped_mut()), + back: util::BorrowStack::new(back, |buffer| buffer.mapped_mut()), width, height, age, @@ -213,19 +190,19 @@ pub struct BufferImpl<'a> { event_queue: &'a Mutex>, surface: &'a wl_surface::WlSurface, front: &'a mut WaylandBuffer, - back: BorrowStack<'a, WaylandBuffer, [u32]>, + back: util::BorrowStack<'a, WaylandBuffer, [u32]>, width: i32, height: i32, age: u8, } impl BufferInterface for BufferImpl<'_> { - fn width(&self) -> NonZeroU32 { - NonZeroU32::new(self.width as u32).unwrap() + fn width(&self) -> u32 { + self.width as u32 } - fn height(&self) -> NonZeroU32 { - NonZeroU32::new(self.height as usize as u32).unwrap() + fn height(&self) -> u32 { + self.height as u32 } #[inline] @@ -273,8 +250,8 @@ impl BufferInterface for BufferImpl<'_> { Some(( i32::try_from(rect.x).ok()?, i32::try_from(rect.y).ok()?, - i32::try_from(rect.width.get()).ok()?, - i32::try_from(rect.height.get()).ok()?, + i32::try_from(rect.width).ok()?, + i32::try_from(rect.height).ok()?, )) })() .ok_or(SoftBufferError::DamageOutOfRange { rect: *rect })?; @@ -298,9 +275,9 @@ impl BufferInterface for BufferImpl<'_> { self.present_with_damage(&[Rect { x: 0, y: 0, - // We know width/height will be non-negative and non-zero. - width: (width as u32).try_into().unwrap(), - height: (height as u32).try_into().unwrap(), + // We know width/height will be non-negative. + width: width.try_into().unwrap(), + height: height.try_into().unwrap(), }]) } } diff --git a/src/backends/web.rs b/src/backends/web.rs index 488bab74..dee4fcb7 100644 --- a/src/backends/web.rs +++ b/src/backends/web.rs @@ -13,7 +13,6 @@ use crate::backend_interface::*; use crate::error::{InitError, SwResultExt}; use crate::{util, NoDisplayHandle, NoWindowHandle, Rect, SoftBufferError}; use std::marker::PhantomData; -use std::num::NonZeroU32; /// Display implementation for the web platform. /// @@ -54,8 +53,11 @@ pub struct WebImpl { /// Buffer has been presented. buffer_presented: bool, - /// The current canvas width/height. - size: Option<(NonZeroU32, NonZeroU32)>, + /// The current canvas width. + width: u32, + + /// The current canvas height. + height: u32, /// The underlying window handle. window_handle: W, @@ -86,7 +88,8 @@ impl WebImpl { canvas: Canvas::Canvas { canvas, ctx }, buffer: util::PixelBuffer(Vec::new()), buffer_presented: false, - size: None, + width: 0, + height: 0, window_handle: window, _display: PhantomData, }) @@ -102,7 +105,8 @@ impl WebImpl { canvas: Canvas::OffscreenCanvas { canvas, ctx }, buffer: util::PixelBuffer(Vec::new()), buffer_presented: false, - size: None, + width: 0, + height: 0, window_handle: window, _display: PhantomData, }) @@ -168,13 +172,14 @@ impl SurfaceInterface for WebImpl /// De-duplicates the error handling between `HtmlCanvasElement` and `OffscreenCanvas`. /// Resize the canvas to the given dimensions. - fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { - if self.size != Some((width, height)) { + fn resize(&mut self, width: u32, height: u32) -> Result<(), SoftBufferError> { + if self.width != width && self.height != height { self.buffer_presented = false; - self.buffer.resize(total_len(width.get(), height.get()), 0); - self.canvas.set_width(width.get()); - self.canvas.set_height(height.get()); - self.size = Some((width, height)); + self.buffer.resize(total_len(width, height), 0); + self.canvas.set_width(width); + self.canvas.set_height(height); + self.width = width; + self.height = height; } Ok(()) @@ -185,18 +190,20 @@ impl SurfaceInterface for WebImpl canvas: &self.canvas, buffer: &mut self.buffer, buffer_presented: &mut self.buffer_presented, - size: self.size, + width: self.width, + height: self.height, }) } fn fetch(&mut self) -> Result, SoftBufferError> { - let (width, height) = self - .size - .expect("Must set size of surface before calling `fetch()`"); + if self.width == 0 || self.height == 0 { + // `getImageData` throws an `IndexSizeError` if the width or height is zero. + return Ok(vec![]); + } let image_data = self .canvas - .get_image_data(0., 0., width.get().into(), height.get().into()) + .get_image_data(0., 0., self.width.into(), self.height.into()) .ok() // TODO: Can also error if width or height are 0. .swbuf_err("`Canvas` contains pixels from a different origin")?; @@ -295,6 +302,13 @@ impl Canvas { ), } } + + fn clear_rect(&self, x: f64, y: f64, width: f64, height: f64) { + match self { + Self::Canvas { ctx, .. } => ctx.clear_rect(x, y, width, height), + Self::OffscreenCanvas { ctx, .. } => ctx.clear_rect(x, y, width, height), + } + } } #[derive(Debug)] @@ -302,20 +316,17 @@ pub struct BufferImpl<'a> { canvas: &'a Canvas, buffer: &'a mut util::PixelBuffer, buffer_presented: &'a mut bool, - size: Option<(NonZeroU32, NonZeroU32)>, + width: u32, + height: u32, } impl BufferInterface for BufferImpl<'_> { - fn width(&self) -> NonZeroU32 { - self.size - .expect("must set size of surface before calling `width()` on the buffer") - .0 + fn width(&self) -> u32 { + self.width } - fn height(&self) -> NonZeroU32 { - self.size - .expect("must set size of surface before calling `height()` on the buffer") - .1 + fn height(&self) -> u32 { + self.height } fn pixels(&self) -> &[u32] { @@ -336,38 +347,44 @@ impl BufferInterface for BufferImpl<'_> { /// Push the buffer to the canvas. fn present(self) -> Result<(), SoftBufferError> { - let (width, height) = self - .size - .expect("Must set size of surface before calling `present()`"); - self.present_with_damage(&[Rect { + let rect = Rect { x: 0, y: 0, - width, - height, - }]) + width: self.width, + height: self.height, + }; + self.present_with_damage(&[rect]) } fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> { - let (buffer_width, _buffer_height) = self - .size - .expect("Must set size of surface before calling `present_with_damage()`"); - let union_damage = if let Some(rect) = util::union_damage(damage) { rect } else { return Ok(()); }; + if self.buffer.0.is_empty() { + self.canvas.clear_rect( + union_damage.x.into(), + union_damage.y.into(), + union_damage.width.into(), + union_damage.height.into(), + ); + *self.buffer_presented = true; + return Ok(()); + } + // Create a bitmap from the buffer. let bitmap: Vec<_> = self .buffer - .chunks_exact(buffer_width.get() as usize) + .0 + .chunks_exact(self.width as usize) .skip(union_damage.y as usize) - .take(union_damage.height.get() as usize) + .take(union_damage.height as usize) .flat_map(|row| { row.iter() .skip(union_damage.x as usize) - .take(union_damage.width.get() as usize) + .take(union_damage.width as usize) }) .copied() .flat_map(|pixel| [(pixel >> 16) as u8, (pixel >> 8) as u8, pixel as u8, 255]) @@ -375,7 +392,7 @@ impl BufferInterface for BufferImpl<'_> { debug_assert_eq!( bitmap.len() as u32, - union_damage.width.get() * union_damage.height.get() * 4 + union_damage.width * union_damage.height * 4 ); #[cfg(target_feature = "atomics")] @@ -405,7 +422,7 @@ impl BufferInterface for BufferImpl<'_> { #[cfg(not(target_feature = "atomics"))] let result = ImageData::new_with_u8_clamped_array( wasm_bindgen::Clamped(&bitmap), - union_damage.width.get(), + union_damage.width, ); // This should only throw an error if the buffer we pass's size is incorrect. let image_data = result.unwrap(); @@ -419,8 +436,8 @@ impl BufferInterface for BufferImpl<'_> { union_damage.y.into(), (rect.x - union_damage.x).into(), (rect.y - union_damage.y).into(), - rect.width.get().into(), - rect.height.get().into(), + rect.width.into(), + rect.height.into(), ) .unwrap(); } diff --git a/src/backends/win32.rs b/src/backends/win32.rs index f20318ed..af06740c 100644 --- a/src/backends/win32.rs +++ b/src/backends/win32.rs @@ -3,15 +3,13 @@ //! This module converts the input buffer into a bitmap and then stretches it to the window. use crate::backend_interface::*; -use crate::{Rect, SoftBufferError}; +use crate::{util, Rect, SoftBufferError}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle}; use std::io; use std::marker::PhantomData; use std::mem; -use std::num::{NonZeroI32, NonZeroU32}; use std::ptr::{self, NonNull}; -use std::slice; use std::sync::{mpsc, Mutex, OnceLock}; use std::thread; @@ -29,9 +27,7 @@ const ZERO_QUAD: Gdi::RGBQUAD = Gdi::RGBQUAD { struct Buffer { dc: Gdi::HDC, bitmap: Gdi::HBITMAP, - pixels: NonNull, - width: NonZeroI32, - height: NonZeroI32, + pixels: NonNull<[u32]>, presented: bool, } @@ -48,7 +44,7 @@ impl Drop for Buffer { } impl Buffer { - fn new(window_dc: Gdi::HDC, width: NonZeroI32, height: NonZeroI32) -> Self { + fn new(window_dc: Gdi::HDC, width: i32, height: i32) -> Self { let dc = Allocator::get().allocate(window_dc); assert!(!dc.is_null()); @@ -56,8 +52,9 @@ impl Buffer { let bitmap_info = BitmapInfo { bmi_header: Gdi::BITMAPINFOHEADER { biSize: mem::size_of::() as u32, - biWidth: width.get(), - biHeight: -height.get(), + biWidth: width, + // Negative height -> origin is the upper-left corner. + biHeight: -height, biPlanes: 1, biBitCount: 32, biCompression: Gdi::BI_BITFIELDS, @@ -99,6 +96,7 @@ impl Buffer { }; assert!(!bitmap.is_null()); let pixels = NonNull::new(pixels).unwrap(); + let pixels = NonNull::slice_from_raw_parts(pixels, width as usize * height as usize); unsafe { Gdi::SelectObject(dc, bitmap); @@ -107,32 +105,10 @@ impl Buffer { Self { dc, bitmap, - width, - height, pixels, presented: false, } } - - #[inline] - fn pixels(&self) -> &[u32] { - unsafe { - slice::from_raw_parts( - self.pixels.as_ptr(), - i32::from(self.width) as usize * i32::from(self.height) as usize, - ) - } - } - - #[inline] - 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. @@ -145,8 +121,16 @@ pub struct Win32Impl { dc: OnlyUsedFromOrigin, /// The buffer used to hold the image. + /// + /// No buffer -> width or height is zero. buffer: Option, + /// The width of the buffer. + width: u32, + + /// The height of the buffer. + height: u32, + /// The handle for the window. /// /// This should be kept alive in order to keep `window` valid. @@ -204,6 +188,8 @@ impl SurfaceInterface for Win32Im dc: dc.into(), window: hwnd.into(), buffer: None, + width: 0, + height: 0, handle: window, _display: PhantomData, }) @@ -214,35 +200,34 @@ impl SurfaceInterface for Win32Im &self.handle } - fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { - let (width, height) = (|| { - let width = NonZeroI32::try_from(width).ok()?; - let height = NonZeroI32::try_from(height).ok()?; - Some((width, height)) - })() - .ok_or(SoftBufferError::SizeOutOfRange { width, height })?; - - if let Some(buffer) = self.buffer.as_ref() { - if buffer.width == width && buffer.height == height { - return Ok(()); - } + fn resize(&mut self, width: u32, height: u32) -> Result<(), SoftBufferError> { + let (width_i32, height_i32) = util::convert_size::(width, height) + .map_err(|_| SoftBufferError::SizeOutOfRange { width, height })?; + + if self.width == width && self.height == height { + return Ok(()); } - self.buffer = Some(Buffer::new(self.dc.0, width, height)); + // Attempting to create a zero-sized Gdi::HBITMAP returns NULL, so we handle this case + // ourselves. + self.buffer = if width_i32 != 0 && height_i32 != 0 { + Some(Buffer::new(self.dc.0, width_i32, height_i32)) + } else { + None + }; + self.width = width; + self.height = height; Ok(()) } fn buffer_mut(&mut self) -> Result, SoftBufferError> { - let buffer = self - .buffer - .as_mut() - .expect("Must set size of surface before calling `buffer_mut()`"); - Ok(BufferImpl { window: &self.window, dc: &self.dc, - buffer, + buffer: &mut self.buffer, + width: self.width, + height: self.height, }) } @@ -256,76 +241,93 @@ impl SurfaceInterface for Win32Im pub struct BufferImpl<'a> { window: &'a OnlyUsedFromOrigin, dc: &'a OnlyUsedFromOrigin, - buffer: &'a mut Buffer, + buffer: &'a mut Option, + width: u32, + height: u32, } impl BufferInterface for BufferImpl<'_> { - fn width(&self) -> NonZeroU32 { - self.buffer.width.try_into().unwrap() + fn width(&self) -> u32 { + self.width } - fn height(&self) -> NonZeroU32 { - self.buffer.height.try_into().unwrap() + fn height(&self) -> u32 { + self.height } #[inline] fn pixels(&self) -> &[u32] { - self.buffer.pixels() + if let Some(buffer) = &self.buffer { + unsafe { buffer.pixels.as_ref() } + } else { + &[] + } } #[inline] fn pixels_mut(&mut self) -> &mut [u32] { - self.buffer.pixels_mut() + if let Some(buffer) = &mut self.buffer { + unsafe { buffer.pixels.as_mut() } + } else { + &mut [] + } } fn age(&self) -> u8 { - if self.buffer.presented { - 1 - } else { - 0 + match self.buffer.as_ref() { + Some(buffer) if buffer.presented => 1, + _ => 0, } } fn present(self) -> Result<(), SoftBufferError> { - let (width, height) = (self.buffer.width, self.buffer.height); - self.present_with_damage(&[Rect { + let rect = Rect { x: 0, y: 0, - // We know width/height will be non-negative - width: width.try_into().unwrap(), - height: height.try_into().unwrap(), - }]) + width: self.width, + height: self.height, + }; + self.present_with_damage(&[rect]) } fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> { - unsafe { + if let Some(buffer) = self.buffer { for rect in damage.iter().copied() { let (x, y, width, height) = (|| { Some(( i32::try_from(rect.x).ok()?, i32::try_from(rect.y).ok()?, - i32::try_from(rect.width.get()).ok()?, - i32::try_from(rect.height.get()).ok()?, + i32::try_from(rect.width).ok()?, + i32::try_from(rect.height).ok()?, )) })() .ok_or(SoftBufferError::DamageOutOfRange { rect })?; - Gdi::BitBlt( - self.dc.0, - x, - y, - width, - height, - self.buffer.dc, - x, - y, - Gdi::SRCCOPY, - ); + + unsafe { + Gdi::BitBlt( + self.dc.0, + x, + y, + width, + height, + self.dc.0, + x, + y, + Gdi::SRCCOPY, + ) + }; } - // Validate the window. - Gdi::ValidateRect(self.window.0, ptr::null_mut()); + buffer.presented = true; + } else { + // No buffer -> don't draw anything, this is consistent with having a zero-sized buffer. + // + // Once we implement though, + // we'll probably want to clear the window here instead. } - self.buffer.presented = true; + + // Validate the window. + unsafe { Gdi::ValidateRect(self.window.0, ptr::null_mut()) }; Ok(()) } diff --git a/src/backends/x11.rs b/src/backends/x11.rs index cc448c09..371d006a 100644 --- a/src/backends/x11.rs +++ b/src/backends/x11.rs @@ -22,7 +22,7 @@ use std::{ fmt, fs::File, io, mem, - num::{NonZeroU16, NonZeroU32}, + num::NonZeroU32, ptr::{null_mut, NonNull}, slice, sync::Arc, @@ -149,8 +149,11 @@ pub struct X11Impl { /// Buffer has been presented. buffer_presented: bool, - /// The current buffer width/height. - size: Option<(NonZeroU16, NonZeroU16)>, + /// The current buffer width. + width: u16, + + /// The current buffer height. + height: u16, /// Keep the window alive. window_handle: W, @@ -300,7 +303,8 @@ impl SurfaceInterface fo visual_id, buffer, buffer_presented: false, - size: None, + width: 0, + height: 0, window_handle: window_src, }) } @@ -310,7 +314,7 @@ impl SurfaceInterface fo &self.window_handle } - fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { + fn resize(&mut self, width: u32, height: u32) -> Result<(), SoftBufferError> { tracing::trace!( "resize: window={:X}, size={}x{}", self.window, @@ -319,22 +323,18 @@ impl SurfaceInterface fo ); // Width and height should fit in u16. - let width: NonZeroU16 = width - .try_into() - .or(Err(SoftBufferError::SizeOutOfRange { width, height }))?; - let height: NonZeroU16 = height.try_into().or(Err(SoftBufferError::SizeOutOfRange { - width: width.into(), - height, - }))?; - - if self.size != Some((width, height)) { + let (width, height) = util::convert_size::(width, height) + .map_err(|_| SoftBufferError::SizeOutOfRange { width, height })?; + + if self.width != width && self.height != height { self.buffer_presented = false; self.buffer - .resize(self.display.connection(), width.get(), height.get()) + .resize(self.display.connection(), width, height) .swbuf_err("Failed to resize X11 buffer")?; // We successfully resized the buffer. - self.size = Some((width, height)); + self.width = width; + self.height = height; } Ok(()) @@ -354,17 +354,14 @@ impl SurfaceInterface fo depth: self.depth, buffer: &mut self.buffer, buffer_presented: &mut self.buffer_presented, - size: self.size, + width: self.width, + height: self.height, }) } fn fetch(&mut self) -> Result, SoftBufferError> { tracing::trace!("fetch: window={:X}", self.window); - let (width, height) = self - .size - .expect("Must set size of surface before calling `fetch()`"); - // TODO: Is it worth it to do SHM here? Probably not. let reply = self .display @@ -374,8 +371,8 @@ impl SurfaceInterface fo self.window, 0, 0, - width.get(), - height.get(), + self.width, + self.height, u32::MAX, ) .swbuf_err("Failed to send image fetching request")? @@ -404,16 +401,17 @@ pub struct BufferImpl<'a> { depth: u8, buffer: &'a mut Buffer, buffer_presented: &'a mut bool, - size: Option<(NonZeroU16, NonZeroU16)>, + width: u16, + height: u16, } impl BufferInterface for BufferImpl<'_> { - fn width(&self) -> NonZeroU32 { - self.size.unwrap().0.into() + fn width(&self) -> u32 { + self.width as u32 } - fn height(&self) -> NonZeroU32 { - self.size.unwrap().1.into() + fn height(&self) -> u32 { + self.height as u32 } #[inline] @@ -438,10 +436,6 @@ impl BufferInterface for BufferImpl<'_> { /// Push the buffer to the window. fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> { - let (surface_width, surface_height) = self - .size - .expect("Must set size of surface before calling `present_with_damage()`"); - tracing::trace!("present: window={:X}", self.window); match self.buffer { @@ -454,8 +448,8 @@ impl BufferInterface for BufferImpl<'_> { xproto::ImageFormat::Z_PIXMAP, self.window, self.gc, - surface_width.get(), - surface_height.get(), + self.width, + self.height, 0, 0, 0, @@ -481,8 +475,8 @@ impl BufferInterface for BufferImpl<'_> { u16::try_from(rect.y).ok()?, i16::try_from(rect.x).ok()?, i16::try_from(rect.y).ok()?, - u16::try_from(rect.width.get()).ok()?, - u16::try_from(rect.height.get()).ok()?, + u16::try_from(rect.width).ok()?, + u16::try_from(rect.height).ok()?, )) })( ) @@ -491,8 +485,8 @@ impl BufferInterface for BufferImpl<'_> { .shm_put_image( self.window, self.gc, - surface_width.get(), - surface_height.get(), + self.width, + self.height, src_x, src_y, width, @@ -524,15 +518,13 @@ impl BufferInterface for BufferImpl<'_> { } fn present(self) -> Result<(), SoftBufferError> { - let (width, height) = self - .size - .expect("Must set size of surface before calling `present()`"); - self.present_with_damage(&[Rect { + let rect = Rect { x: 0, y: 0, - width: width.into(), - height: height.into(), - }]) + width: self.width.into(), + height: self.height.into(), + }; + self.present_with_damage(&[rect]) } } diff --git a/src/error.rs b/src/error.rs index b79be6b6..4405fe7f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,6 @@ use raw_window_handle::{HandleError, RawDisplayHandle, RawWindowHandle}; use std::error::Error; use std::fmt; -use std::num::NonZeroU32; #[derive(Debug)] #[non_exhaustive] @@ -80,10 +79,10 @@ pub enum SoftBufferError { /// The provided size is outside of the range supported by the backend. SizeOutOfRange { /// The width that was out of range. - width: NonZeroU32, + width: u32, /// The height that was out of range. - height: NonZeroU32, + height: u32, }, /// The provided damage rect is outside of the range supported by the backend. diff --git a/src/lib.rs b/src/lib.rs index 34110a2b..2c5f8446 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,6 @@ mod util; use std::cell::Cell; use std::marker::PhantomData; -use std::num::NonZeroU32; use std::ops; use std::sync::Arc; @@ -67,9 +66,9 @@ pub struct Rect { /// y coordinate of top left corner pub y: u32, /// width - pub width: NonZeroU32, + pub width: u32, /// height - pub height: NonZeroU32, + pub height: u32, } /// A surface for drawing to a window with software buffers. @@ -111,7 +110,7 @@ impl Surface { /// 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> { + pub fn resize(&mut self, width: u32, height: u32) -> Result<(), SoftBufferError> { self.surface_impl.resize(width, height) } @@ -127,9 +126,13 @@ impl Surface { self.surface_impl.fetch() } - /// 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. Call [`Buffer::age`] to determine this. + /// Return a [`Buffer`] that the next frame should be rendered into. + /// + /// The buffer is initially empty, you'll want to set an appropriate size for it with + /// [`Surface::resize`]. + /// + /// The initial contents of the buffer may be zeroed, or may contain a previous frame. Call + /// [`Buffer::age`] to determine this. /// /// ## Platform Dependent Behavior /// @@ -209,10 +212,10 @@ pub struct Buffer<'a> { impl Buffer<'_> { /// The amount of pixels wide the buffer is. - pub fn width(&self) -> NonZeroU32 { + pub fn width(&self) -> u32 { let width = self.buffer_impl.width(); debug_assert_eq!( - width.get() as usize * self.buffer_impl.height().get() as usize, + width as usize * self.buffer_impl.height() as usize, self.len(), "buffer must be sized correctly" ); @@ -220,10 +223,10 @@ impl Buffer<'_> { } /// The amount of pixels tall the buffer is. - pub fn height(&self) -> NonZeroU32 { + pub fn height(&self) -> u32 { let height = self.buffer_impl.height(); debug_assert_eq!( - height.get() as usize * self.buffer_impl.width().get() as usize, + height as usize * self.buffer_impl.width() as usize, self.len(), "buffer must be sized correctly" ); @@ -326,11 +329,11 @@ impl Buffer<'_> { pub fn pixel_rows( &mut self, ) -> impl DoubleEndedIterator + ExactSizeIterator { - let width = self.width().get() as usize; + let width = self.width() as usize; let pixels = self.buffer_impl.pixels_mut(); assert_eq!(pixels.len() % width, 0, "buffer must be multiple of width"); - // NOTE: This won't panic because `width` is `NonZeroU32` - pixels.chunks_mut(width) + // Clamp width to be at least 1 - when the width is 0, the pixel buffer is empty. + pixels.chunks_mut(width.max(1)) } /// Iterate over each pixel in the data. diff --git a/src/util.rs b/src/util.rs index f0f3ab41..b59d42b4 100644 --- a/src/util.rs +++ b/src/util.rs @@ -3,7 +3,6 @@ use std::cmp; use std::fmt; -use std::num::NonZeroU32; use std::ops; use crate::Rect; @@ -73,8 +72,8 @@ pub(crate) fn union_damage(damage: &[Rect]) -> Option { .map(|rect| Region { left: rect.x, top: rect.y, - right: rect.x + rect.width.get(), - bottom: rect.y + rect.height.get(), + right: rect.x + rect.width, + bottom: rect.y + rect.height, }) .reduce(|mut prev, next| { prev.left = cmp::min(prev.left, next.left); @@ -87,9 +86,13 @@ pub(crate) fn union_damage(damage: &[Rect]) -> Option { Some(Rect { x: region.left, y: region.top, - width: NonZeroU32::new(region.right - region.left) + width: region + .right + .checked_sub(region.left) .expect("`right` must always be bigger then `left`"), - height: NonZeroU32::new(region.bottom - region.top) + height: region + .bottom + .checked_sub(region.top) .expect("`bottom` must always be bigger then `top`"), }) } @@ -117,6 +120,12 @@ impl ops::DerefMut for PixelBuffer { } } +pub(crate) fn convert_size>(width: u32, height: u32) -> Result<(T, T), T::Error> { + let width = T::try_from(width)?; + let height = T::try_from(height)?; + Ok((width, height)) +} + #[cfg(test)] mod tests { use super::*;