From 9d4fb76185d5e51ac76e1e6358302a2787129a57 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sat, 13 Dec 2025 23:49:39 +0100 Subject: [PATCH 1/3] Add Pixel struct This is much more intuitive to use than manually bit-packing into a u32. --- CHANGELOG.md | 2 + Cargo.toml | 13 +--- README.md | 8 +- benches/buffer_mut.rs | 4 +- examples/animation.rs | 16 ++-- examples/drm.rs | 7 +- examples/fruit.rs | 9 +-- examples/libxcb.rs | 4 +- examples/rectangle.rs | 6 +- examples/winit.rs | 8 +- examples/winit_multithread.rs | 8 +- examples/winit_wrong_sized_buffer.rs | 9 ++- src/backend_dispatch.rs | 8 +- src/backend_interface.rs | 8 +- src/backends/android.rs | 23 +++--- src/backends/cg.rs | 16 ++-- src/backends/kms.rs | 20 ++++- src/backends/orbital.rs | 21 ++--- src/backends/wayland/buffer.rs | 7 +- src/backends/wayland/mod.rs | 8 +- src/backends/web.rs | 23 +++--- src/backends/win32.rs | 18 ++--- src/backends/x11.rs | 65 ++++++++++++---- src/lib.rs | 40 ++++------ src/pixel.rs | 112 +++++++++++++++++++++++++++ src/util.rs | 7 +- 26 files changed, 306 insertions(+), 164 deletions(-) create mode 100644 src/pixel.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index b1e1b15..45aa392 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- **Breaking:** Add `Pixel` struct, and use that for pixels instead of `u32`. + # 0.4.7 - Fix documentation building on `docs.rs`. diff --git a/Cargo.toml b/Cargo.toml index 68238c0..e852ee0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ harness = false [features] default = ["kms", "x11", "x11-dlopen", "wayland", "wayland-dlopen"] -kms = ["bytemuck", "drm", "rustix"] +kms = ["drm", "rustix"] wayland = [ "wayland-backend", "wayland-client", @@ -28,14 +28,7 @@ wayland = [ "fastrand", ] wayland-dlopen = ["wayland-sys/dlopen"] -x11 = [ - "as-raw-xcb-connection", - "bytemuck", - "fastrand", - "rustix", - "tiny-xlib", - "x11rb", -] +x11 = ["as-raw-xcb-connection", "fastrand", "rustix", "tiny-xlib", "x11rb"] x11-dlopen = ["tiny-xlib/dlopen", "x11rb/dl-libxcb"] [dependencies] @@ -45,12 +38,10 @@ raw_window_handle = { package = "raw-window-handle", version = "0.6", features = tracing = { version = "0.1.41", default-features = false } [target.'cfg(target_os = "android")'.dependencies] -bytemuck = "1.12.3" ndk = "0.9.0" [target.'cfg(not(any(target_os = "android", target_vendor = "apple", target_os = "redox", target_family = "wasm", target_os = "windows")))'.dependencies] as-raw-xcb-connection = { version = "1.0.0", optional = true } -bytemuck = { version = "1.12.3", optional = true } drm = { version = "0.14.1", default-features = false, optional = true } fastrand = { version = "2.0.0", optional = true } memmap2 = { version = "0.9.0", optional = true } diff --git a/README.md b/README.md index 85faaae..936b12d 100644 --- a/README.md +++ b/README.md @@ -113,11 +113,11 @@ fn main() { for index in 0..(buffer.width().get() * buffer.height().get()) { let y = index / buffer.width().get(); let x = index % buffer.width().get(); - let red = x % 255; - let green = y % 255; - let blue = (x * y) % 255; + let red = (x % 255) as u8; + let green = (y % 255) as u8; + let blue = ((x * y) % 255) as u8; - buffer[index as usize] = blue | (green << 8) | (red << 16); + buffer[index as usize] = softbuffer::Pixel::new_rgb(red, green, blue); } buffer.present().unwrap(); diff --git a/benches/buffer_mut.rs b/benches/buffer_mut.rs index e383102..2023002 100644 --- a/benches/buffer_mut.rs +++ b/benches/buffer_mut.rs @@ -12,7 +12,7 @@ fn buffer_mut(c: &mut Criterion) { #[cfg(not(target_family = "wasm"))] { use criterion::black_box; - use softbuffer::{Context, Surface}; + use softbuffer::{Context, Pixel, Surface}; use std::num::NonZeroU32; use winit::event_loop::ControlFlow; use winit::platform::run_on_demand::EventLoopExtRunOnDemand; @@ -51,7 +51,7 @@ fn buffer_mut(c: &mut Criterion) { let mut buffer = surface.buffer_mut().unwrap(); b.iter(|| { for _ in 0..500 { - let x: &mut [u32] = &mut buffer; + let x: &mut [Pixel] = &mut buffer; black_box(x); } }); diff --git a/examples/animation.rs b/examples/animation.rs index 33501b7..96221ba 100644 --- a/examples/animation.rs +++ b/examples/animation.rs @@ -1,5 +1,6 @@ #[cfg(not(target_family = "wasm"))] use rayon::prelude::*; +use softbuffer::Pixel; use std::f64::consts::PI; use std::num::NonZeroU32; use web_time::Instant; @@ -94,7 +95,7 @@ fn main() { winit_app::run_app(event_loop, app); } -fn pre_render_frames(width: u32, height: u32) -> Vec> { +fn pre_render_frames(width: u32, height: u32) -> Vec> { let render = |frame_id| { let elapsed = ((frame_id as f64) / (60.0)) * 2.0 * PI; @@ -103,14 +104,11 @@ fn pre_render_frames(width: u32, height: u32) -> Vec> { .map(|(x, y)| { let y = (y as f64) / (height as f64); let x = (x as f64) / (width as f64); - let red = - ((((y + elapsed).sin() * 0.5 + 0.5) * 255.0).round() as u32).clamp(0, 255); - let green = - ((((x + elapsed).sin() * 0.5 + 0.5) * 255.0).round() as u32).clamp(0, 255); - let blue = - ((((y - elapsed).cos() * 0.5 + 0.5) * 255.0).round() as u32).clamp(0, 255); - - blue | (green << 8) | (red << 16) + let r = ((((y + elapsed).sin() * 0.5 + 0.5) * 255.0).round() as u32).clamp(0, 255); + let g = ((((x + elapsed).sin() * 0.5 + 0.5) * 255.0).round() as u32).clamp(0, 255); + let b = ((((y - elapsed).cos() * 0.5 + 0.5) * 255.0).round() as u32).clamp(0, 255); + + Pixel::new_rgb(r as u8, g as u8, b as u8) }) .collect::>() }; diff --git a/examples/drm.rs b/examples/drm.rs index 9d1b36e..a965b9b 100644 --- a/examples/drm.rs +++ b/examples/drm.rs @@ -15,7 +15,7 @@ mod imple { use drm::Device; use raw_window_handle::{DisplayHandle, DrmDisplayHandle, DrmWindowHandle, WindowHandle}; - use softbuffer::{Context, Surface}; + use softbuffer::{Context, Pixel, Surface}; use std::num::NonZeroU32; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd}; @@ -159,7 +159,7 @@ mod imple { Ok(()) } - fn draw_to_buffer(buf: &mut [u32], tick: usize) { + fn draw_to_buffer(buf: &mut [Pixel], tick: usize) { let scale = colorous::SINEBOW; let mut i = (tick as f64) / 20.0; while i > 1.0 { @@ -167,8 +167,7 @@ mod imple { } let color = scale.eval_continuous(i); - let pixel = ((color.r as u32) << 16) | ((color.g as u32) << 8) | (color.b as u32); - buf.fill(pixel); + buf.fill(Pixel::new_rgb(color.r, color.g, color.b)); } struct Card(std::fs::File); diff --git a/examples/fruit.rs b/examples/fruit.rs index f07fce2..e70d6bd 100644 --- a/examples/fruit.rs +++ b/examples/fruit.rs @@ -1,4 +1,5 @@ use image::GenericImageView; +use softbuffer::Pixel; use std::num::NonZeroU32; use winit::event::{KeyEvent, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; @@ -52,12 +53,8 @@ fn main() { let mut buffer = surface.buffer_mut().unwrap(); let width = fruit.width(); 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 * width + x) as usize] = color; + let pixel = Pixel::new_rgb(pixel.0[0], pixel.0[1], pixel.0[2]); + buffer[(y * width + x) as usize] = pixel; } buffer.present().unwrap(); diff --git a/examples/libxcb.rs b/examples/libxcb.rs index 03a5593..015e367 100644 --- a/examples/libxcb.rs +++ b/examples/libxcb.rs @@ -25,8 +25,6 @@ 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"); @@ -122,7 +120,7 @@ mod example { ) .unwrap(); let mut buffer = surface.buffer_mut().unwrap(); - buffer.fill(RED); + buffer.fill(softbuffer::Pixel::new_rgb(0xff, 0, 0)); buffer.present().unwrap(); } Event::ConfigureNotify(configure_notify) => { diff --git a/examples/rectangle.rs b/examples/rectangle.rs index b655866..50dc3d3 100644 --- a/examples/rectangle.rs +++ b/examples/rectangle.rs @@ -1,5 +1,5 @@ use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; -use softbuffer::Buffer; +use softbuffer::{Buffer, Pixel}; use std::num::NonZeroU32; use winit::event::{ElementState, KeyEvent, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; @@ -14,12 +14,12 @@ fn redraw(buffer: &mut Buffer<'_, impl HasDisplayHandle, impl HasWindowHandle>, for y in 0..height { for x in 0..width { let value = if flag && x >= 100 && x < width - 100 && y >= 100 && y < height - 100 { - 0x00ffffff + Pixel::new_rgb(0xff, 0xff, 0xff) } else { let red = (x & 0xff) ^ (y & 0xff); let green = (x & 0x7f) ^ (y & 0x7f); let blue = (x & 0x3f) ^ (y & 0x3f); - blue | (green << 8) | (red << 16) + Pixel::new_rgb(red as u8, green as u8, blue as u8) }; buffer[(y * width + x) as usize] = value; } diff --git a/examples/winit.rs b/examples/winit.rs index 044ae39..95d3c23 100644 --- a/examples/winit.rs +++ b/examples/winit.rs @@ -47,11 +47,11 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { let mut buffer = surface.buffer_mut().unwrap(); for y in 0..buffer.height().get() { for x in 0..buffer.width().get() { - let red = x % 255; - let green = y % 255; - let blue = (x * y) % 255; + let red = (x % 255) as u8; + let green = (y % 255) as u8; + let blue = ((x * y) % 255) as u8; let index = y * buffer.width().get() + x; - buffer[index as usize] = blue | (green << 8) | (red << 16); + buffer[index as usize] = softbuffer::Pixel::new_rgb(red, green, blue); } } diff --git a/examples/winit_multithread.rs b/examples/winit_multithread.rs index 3357a34..d6f728a 100644 --- a/examples/winit_multithread.rs +++ b/examples/winit_multithread.rs @@ -36,11 +36,11 @@ pub mod ex { let mut buffer = surface.buffer_mut().unwrap(); for y in 0..buffer.height().get() { for x in 0..buffer.width().get() { - let red = x % 255; - let green = y % 255; - let blue = (x * y) % 255; + let red = (x % 255) as u8; + let green = (y % 255) as u8; + let blue = ((x * y) % 255) as u8; let index = y * buffer.width().get() + x; - buffer[index as usize] = blue | (green << 8) | (red << 16); + buffer[index as usize] = softbuffer::Pixel::new_rgb(red, green, blue); } } diff --git a/examples/winit_wrong_sized_buffer.rs b/examples/winit_wrong_sized_buffer.rs index ec74c65..2524fee 100644 --- a/examples/winit_wrong_sized_buffer.rs +++ b/examples/winit_wrong_sized_buffer.rs @@ -1,3 +1,4 @@ +use softbuffer::Pixel; use std::num::NonZeroU32; use winit::event::{KeyEvent, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; @@ -39,11 +40,11 @@ fn main() { let width = buffer.width().get(); for y in 0..buffer.height().get() { for x in 0..width { - let red = x % 255; - let green = y % 255; - let blue = (x * y) % 255; + let red = (x % 255) as u8; + let green = (y % 255) as u8; + let blue = ((x * y) % 255) as u8; - let color = blue | (green << 8) | (red << 16); + let color = Pixel::new_rgb(red, green, blue); buffer[(y * width + x) as usize] = color; } } diff --git a/src/backend_dispatch.rs b/src/backend_dispatch.rs index 3bfe94b..18b3fd6 100644 --- a/src/backend_dispatch.rs +++ b/src/backend_dispatch.rs @@ -1,6 +1,6 @@ //! Implements `buffer_interface::*` traits for enums dispatching to backends -use crate::{backend_interface::*, backends, InitError, Rect, SoftBufferError}; +use crate::{backend_interface::*, backends, InitError, Pixel, Rect, SoftBufferError}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use std::fmt; @@ -117,7 +117,7 @@ macro_rules! make_dispatch { } } - fn fetch(&mut self) -> Result, SoftBufferError> { + fn fetch(&mut self) -> Result, SoftBufferError> { match self { $( $(#[$attr])* @@ -167,7 +167,7 @@ macro_rules! make_dispatch { } #[inline] - fn pixels(&self) -> &[u32] { + fn pixels(&self) -> &[Pixel] { match self { $( $(#[$attr])* @@ -177,7 +177,7 @@ macro_rules! make_dispatch { } #[inline] - fn pixels_mut(&mut self) -> &mut [u32] { + fn pixels_mut(&mut self) -> &mut [Pixel] { match self { $( $(#[$attr])* diff --git a/src/backend_interface.rs b/src/backend_interface.rs index b6a5666..ca2b58b 100644 --- a/src/backend_interface.rs +++ b/src/backend_interface.rs @@ -1,6 +1,6 @@ //! Interface implemented by backends -use crate::{InitError, Rect, SoftBufferError}; +use crate::{InitError, Pixel, Rect, SoftBufferError}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use std::num::NonZeroU32; @@ -29,7 +29,7 @@ pub(crate) trait SurfaceInterface Result, SoftBufferError>; /// Fetch the buffer from the window. - fn fetch(&mut self) -> Result, SoftBufferError> { + fn fetch(&mut self) -> Result, SoftBufferError> { Err(SoftBufferError::Unimplemented) } } @@ -37,8 +37,8 @@ pub(crate) trait SurfaceInterface NonZeroU32; fn height(&self) -> NonZeroU32; - fn pixels(&self) -> &[u32]; - fn pixels_mut(&mut self) -> &mut [u32]; + fn pixels(&self) -> &[Pixel]; + fn pixels_mut(&mut self) -> &mut [Pixel]; fn age(&self) -> u8; fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError>; fn present(self) -> Result<(), SoftBufferError>; diff --git a/src/backends/android.rs b/src/backends/android.rs index 12d489b..949d180 100644 --- a/src/backends/android.rs +++ b/src/backends/android.rs @@ -12,7 +12,7 @@ use raw_window_handle::AndroidNdkWindowHandle; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle}; use crate::error::InitError; -use crate::{util, BufferInterface, Rect, SoftBufferError, SurfaceInterface}; +use crate::{util, BufferInterface, Pixel, Rect, SoftBufferError, SurfaceInterface}; /// The handle to a window for software buffering. #[derive(Debug)] @@ -99,7 +99,8 @@ impl SurfaceInterface for Android )); } - let buffer = vec![0; native_window_buffer.width() * native_window_buffer.height()]; + let buffer = + vec![Pixel::default(); native_window_buffer.width() * native_window_buffer.height()]; Ok(BufferImpl { native_window_buffer, @@ -109,7 +110,7 @@ impl SurfaceInterface for Android } /// Fetch the buffer from the window. - fn fetch(&mut self) -> Result, SoftBufferError> { + fn fetch(&mut self) -> Result, SoftBufferError> { Err(SoftBufferError::Unimplemented) } } @@ -134,12 +135,12 @@ impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl } #[inline] - fn pixels(&self) -> &[u32] { + fn pixels(&self) -> &[Pixel] { &self.buffer } #[inline] - fn pixels_mut(&mut self) -> &mut [u32] { + fn pixels_mut(&mut self) -> &mut [Pixel] { &mut self.buffer } @@ -161,13 +162,13 @@ impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl // .lines() removed the stride assert_eq!(output.len(), input.len() * 4); + // Write RGB(A) to the output. + // TODO: Use `slice::write_copy_of_slice` once stable and in MSRV. for (i, pixel) in input.iter().enumerate() { - // Swizzle colors from BGR(A) to RGB(A) - let [b, g, r, a] = pixel.to_le_bytes(); - output[i * 4].write(r); - output[i * 4 + 1].write(g); - output[i * 4 + 2].write(b); - output[i * 4 + 3].write(a); + output[i * 4].write(pixel.r); + output[i * 4 + 1].write(pixel.g); + output[i * 4 + 2].write(pixel.b); + output[i * 4 + 3].write(pixel.a); } } Ok(()) diff --git a/src/backends/cg.rs b/src/backends/cg.rs index a5dc52a..4e6dd76 100644 --- a/src/backends/cg.rs +++ b/src/backends/cg.rs @@ -1,7 +1,7 @@ //! Softbuffer implementation using CoreGraphics. use crate::backend_interface::*; use crate::error::InitError; -use crate::{util, Rect, SoftBufferError}; +use crate::{util, Pixel, Rect, SoftBufferError}; use objc2::rc::Retained; use objc2::runtime::{AnyObject, Bool}; use objc2::{define_class, msg_send, AllocAnyThread, DefinedClass, MainThreadMarker, Message}; @@ -260,7 +260,7 @@ impl SurfaceInterface for CGImpl< fn buffer_mut(&mut self) -> Result, SoftBufferError> { Ok(BufferImpl { - buffer: util::PixelBuffer(vec![0; self.width * self.height]), + buffer: util::PixelBuffer(vec![Pixel::default(); self.width * self.height]), imp: self, }) } @@ -282,12 +282,12 @@ impl BufferInterface for BufferImpl<'_, } #[inline] - fn pixels(&self) -> &[u32] { + fn pixels(&self) -> &[Pixel] { &self.buffer } #[inline] - fn pixels_mut(&mut self) -> &mut [u32] { + fn pixels_mut(&mut self) -> &mut [Pixel] { &mut self.buffer } @@ -301,15 +301,15 @@ impl BufferInterface for BufferImpl<'_, data: NonNull, size: usize, ) { - let data = data.cast::(); - let slice = slice_from_raw_parts_mut(data.as_ptr(), size / size_of::()); + 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()); + let len = self.buffer.len() * size_of::(); + let buffer: *mut [Pixel] = Box::into_raw(self.buffer.0.into_boxed_slice()); // Convert slice pointer to thin pointer. let data_ptr = buffer.cast::(); diff --git a/src/backends/kms.rs b/src/backends/kms.rs index d18d633..88cc252 100644 --- a/src/backends/kms.rs +++ b/src/backends/kms.rs @@ -14,12 +14,15 @@ use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, Raw use std::collections::HashSet; use std::fmt; use std::marker::PhantomData; +use std::mem::size_of; use std::num::NonZeroU32; use std::os::unix::io::{AsFd, BorrowedFd}; +use std::slice; use std::sync::Arc; use crate::backend_interface::*; use crate::error::{InitError, SoftBufferError, SwResultExt}; +use crate::Pixel; #[derive(Debug)] pub(crate) struct KmsDisplayImpl { @@ -310,13 +313,22 @@ impl BufferInterface for BufferImpl<'_, D, W> { } #[inline] - fn pixels(&self) -> &[u32] { - bytemuck::cast_slice(self.mapping.as_ref()) + fn pixels(&self) -> &[Pixel] { + let ptr = self.mapping.as_ptr().cast::(); + let len = self.mapping.len() / size_of::(); + debug_assert_eq!(self.mapping.len() % size_of::(), 0); + // SAFETY: The `mapping` is a multiple of `Pixel`, and we assume that the buffer allocation + // is aligned to at least a multiple of 4 bytes. + unsafe { slice::from_raw_parts(ptr, len) } } #[inline] - fn pixels_mut(&mut self) -> &mut [u32] { - bytemuck::cast_slice_mut(self.mapping.as_mut()) + fn pixels_mut(&mut self) -> &mut [Pixel] { + let ptr = self.mapping.as_mut_ptr().cast::(); + let len = self.mapping.len() / size_of::(); + debug_assert_eq!(self.mapping.len() % size_of::(), 0); + // SAFETY: Same as above in `pixels`. + unsafe { slice::from_raw_parts_mut(ptr, len) } } #[inline] diff --git a/src/backends/orbital.rs b/src/backends/orbital.rs index 301baba..6cca2a5 100644 --- a/src/backends/orbital.rs +++ b/src/backends/orbital.rs @@ -3,7 +3,7 @@ use raw_window_handle::{HasDisplayHandle, HasWindowHandle, OrbitalWindowHandle, use std::{cmp, marker::PhantomData, num::NonZeroU32, slice, str}; use crate::backend_interface::*; -use crate::{util, Rect, SoftBufferError}; +use crate::{util, Pixel, Rect, SoftBufferError}; #[derive(Debug)] struct OrbitalMap { @@ -38,12 +38,12 @@ impl OrbitalMap { }) } - unsafe fn data(&self) -> &[u32] { - unsafe { slice::from_raw_parts(self.address as *const u32, self.size_unaligned / 4) } + unsafe fn data(&self) -> &[Pixel] { + unsafe { slice::from_raw_parts(self.address as *const Pixel, self.size_unaligned / 4) } } - unsafe fn data_mut(&mut self) -> &mut [u32] { - unsafe { slice::from_raw_parts_mut(self.address as *mut u32, self.size_unaligned / 4) } + unsafe fn data_mut(&mut self) -> &mut [Pixel] { + unsafe { slice::from_raw_parts_mut(self.address as *mut Pixel, self.size_unaligned / 4) } } } @@ -96,7 +96,7 @@ impl OrbitalImpl { (window_width, window_height) } - fn set_buffer(&self, buffer: &[u32], width_u32: u32, height_u32: u32) { + fn set_buffer(&self, buffer: &[Pixel], width_u32: u32, height_u32: u32) { // Read the current width and size let (window_width, window_height) = self.window_size(); @@ -106,7 +106,8 @@ impl OrbitalImpl { 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 + // Window buffer is u32 color data in BGRA format: + // https://docs.rs/orbclient/0.3.48/src/orbclient/color.rs.html#25-29 let window_data = unsafe { window_map.data_mut() }; // Copy each line, cropping to fit @@ -178,7 +179,7 @@ impl SurfaceInterface for Orbital ) } else { Pixels::Buffer(util::PixelBuffer(vec![ - 0; + Pixel::default(); self.width as usize * self.height as usize ])) @@ -209,7 +210,7 @@ impl BufferInterface for BufferImpl<'_, } #[inline] - fn pixels(&self) -> &[u32] { + fn pixels(&self) -> &[Pixel] { match &self.pixels { Pixels::Mapping(mapping) => unsafe { mapping.data() }, Pixels::Buffer(buffer) => buffer, @@ -217,7 +218,7 @@ impl BufferInterface for BufferImpl<'_, } #[inline] - fn pixels_mut(&mut self) -> &mut [u32] { + fn pixels_mut(&mut self) -> &mut [Pixel] { match &mut self.pixels { Pixels::Mapping(mapping) => unsafe { mapping.data_mut() }, Pixels::Buffer(buffer) => buffer, diff --git a/src/backends/wayland/buffer.rs b/src/backends/wayland/buffer.rs index 49e641f..5e38169 100644 --- a/src/backends/wayland/buffer.rs +++ b/src/backends/wayland/buffer.rs @@ -15,6 +15,7 @@ use wayland_client::{ }; use super::State; +use crate::Pixel; #[cfg(any(target_os = "linux", target_os = "freebsd"))] fn create_memfile() -> File { @@ -161,8 +162,10 @@ impl WaylandBuffer { 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()) } + pub unsafe fn mapped_mut(&mut self) -> &mut [Pixel] { + // SAFETY: The `mapping` is a multiple of `Pixel`, and we assume that the memmap allocation + // is aligned to at least a multiple of 4 bytes. + unsafe { slice::from_raw_parts_mut(self.map.as_mut_ptr().cast::(), self.len()) } } } diff --git a/src/backends/wayland/mod.rs b/src/backends/wayland/mod.rs index 2d37494..8f703b2 100644 --- a/src/backends/wayland/mod.rs +++ b/src/backends/wayland/mod.rs @@ -1,7 +1,7 @@ use crate::{ backend_interface::*, error::{InitError, SwResultExt}, - util, Rect, SoftBufferError, + util, Pixel, Rect, SoftBufferError, }; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle}; use std::{ @@ -265,7 +265,7 @@ impl Drop for WaylandImpl { #[derive(Debug)] pub struct BufferImpl<'a, D: ?Sized, W> { - stack: util::BorrowStack<'a, WaylandImpl, [u32]>, + stack: util::BorrowStack<'a, WaylandImpl, [Pixel]>, width: i32, height: i32, age: u8, @@ -281,12 +281,12 @@ impl BufferInterface for Buffe } #[inline] - fn pixels(&self) -> &[u32] { + fn pixels(&self) -> &[Pixel] { self.stack.member() } #[inline] - fn pixels_mut(&mut self) -> &mut [u32] { + fn pixels_mut(&mut self) -> &mut [Pixel] { self.stack.member_mut() } diff --git a/src/backends/web.rs b/src/backends/web.rs index 358e934..2149981 100644 --- a/src/backends/web.rs +++ b/src/backends/web.rs @@ -11,7 +11,7 @@ use web_sys::{OffscreenCanvas, OffscreenCanvasRenderingContext2d}; use crate::backend_interface::*; use crate::error::{InitError, SwResultExt}; -use crate::{util, NoDisplayHandle, NoWindowHandle, Rect, SoftBufferError}; +use crate::{util, NoDisplayHandle, NoWindowHandle, Pixel, Rect, SoftBufferError}; use std::marker::PhantomData; use std::num::NonZeroU32; @@ -135,7 +135,7 @@ impl WebImpl { }; // Create a bitmap from the buffer. - let bitmap: Vec<_> = self + let bitmap: Vec = self .buffer .chunks_exact(buffer_width.get() as usize) .skip(union_damage.y as usize) @@ -145,8 +145,7 @@ impl WebImpl { .skip(union_damage.x as usize) .take(union_damage.width.get() as usize) }) - .copied() - .flat_map(|pixel| [(pixel >> 16) as u8, (pixel >> 8) as u8, pixel as u8, 255]) + .flat_map(|pixel| [pixel.r, pixel.g, pixel.b, pixel.a]) .collect(); debug_assert_eq!( @@ -254,7 +253,8 @@ impl SurfaceInterface for WebImpl fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { if self.size != Some((width, height)) { self.buffer_presented = false; - self.buffer.resize(total_len(width.get(), height.get()), 0); + self.buffer + .resize(total_len(width.get(), height.get()), Pixel::default()); self.canvas.set_width(width.get()); self.canvas.set_height(height.get()); self.size = Some((width, height)); @@ -267,7 +267,7 @@ impl SurfaceInterface for WebImpl Ok(BufferImpl { imp: self }) } - fn fetch(&mut self) -> Result, SoftBufferError> { + fn fetch(&mut self) -> Result, SoftBufferError> { let (width, height) = self .size .expect("Must set size of surface before calling `fetch()`"); @@ -283,7 +283,12 @@ impl SurfaceInterface for WebImpl .data() .0 .chunks_exact(4) - .map(|chunk| u32::from_be_bytes([0, chunk[0], chunk[1], chunk[2]])) + .map(|chunk| Pixel { + r: chunk[0], + g: chunk[1], + b: chunk[2], + a: chunk[3], + }) .collect()) } } @@ -395,11 +400,11 @@ impl BufferInterface for BufferImpl<'_, .1 } - fn pixels(&self) -> &[u32] { + fn pixels(&self) -> &[Pixel] { &self.imp.buffer } - fn pixels_mut(&mut self) -> &mut [u32] { + fn pixels_mut(&mut self) -> &mut [Pixel] { &mut self.imp.buffer } diff --git a/src/backends/win32.rs b/src/backends/win32.rs index 75de23e..f5fa6c2 100644 --- a/src/backends/win32.rs +++ b/src/backends/win32.rs @@ -3,7 +3,7 @@ //! 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::{Pixel, Rect, SoftBufferError}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle}; use std::io; @@ -29,7 +29,7 @@ const ZERO_QUAD: Gdi::RGBQUAD = Gdi::RGBQUAD { struct Buffer { dc: Gdi::HDC, bitmap: Gdi::HBITMAP, - pixels: NonNull, + pixels: NonNull, width: NonZeroI32, height: NonZeroI32, presented: bool, @@ -86,13 +86,13 @@ impl Buffer { // XXX alignment? // XXX better to use CreateFileMapping, and pass hSection? // XXX test return value? - let mut pixels: *mut u32 = ptr::null_mut(); + let mut pixels: *mut Pixel = 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 _, + &mut pixels as *mut *mut Pixel as _, ptr::null_mut(), 0, ) @@ -115,7 +115,7 @@ impl Buffer { } #[inline] - fn pixels(&self) -> &[u32] { + fn pixels(&self) -> &[Pixel] { unsafe { slice::from_raw_parts( self.pixels.as_ptr(), @@ -125,7 +125,7 @@ impl Buffer { } #[inline] - fn pixels_mut(&mut self) -> &mut [u32] { + fn pixels_mut(&mut self) -> &mut [Pixel] { unsafe { slice::from_raw_parts_mut( self.pixels.as_ptr(), @@ -278,7 +278,7 @@ impl SurfaceInterface for Win32Im } /// Fetch the buffer from the window. - fn fetch(&mut self) -> Result, SoftBufferError> { + fn fetch(&mut self) -> Result, SoftBufferError> { Err(SoftBufferError::Unimplemented) } } @@ -296,12 +296,12 @@ impl BufferInterface for BufferImpl<'_, } #[inline] - fn pixels(&self) -> &[u32] { + fn pixels(&self) -> &[Pixel] { self.0.buffer.as_ref().unwrap().pixels() } #[inline] - fn pixels_mut(&mut self) -> &mut [u32] { + fn pixels_mut(&mut self) -> &mut [Pixel] { self.0.buffer.as_mut().unwrap().pixels_mut() } diff --git a/src/backends/x11.rs b/src/backends/x11.rs index 711b0b7..5685a34 100644 --- a/src/backends/x11.rs +++ b/src/backends/x11.rs @@ -7,7 +7,7 @@ use crate::backend_interface::*; use crate::error::{InitError, SwResultExt}; -use crate::{util, Rect, SoftBufferError}; +use crate::{util, Pixel, Rect, SoftBufferError}; use raw_window_handle::{ HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle, XcbDisplayHandle, XcbWindowHandle, @@ -21,7 +21,8 @@ use std::{ collections::HashSet, fmt, fs::File, - io, mem, + io, + mem::{self, size_of}, num::{NonZeroU16, NonZeroU32}, ptr::{null_mut, NonNull}, slice, @@ -350,7 +351,7 @@ impl SurfaceInterface fo Ok(BufferImpl(self)) } - fn fetch(&mut self) -> Result, SoftBufferError> { + fn fetch(&mut self) -> Result, SoftBufferError> { tracing::trace!("fetch: window={:X}", self.window); let (width, height) = self @@ -375,8 +376,15 @@ impl SurfaceInterface fo .swbuf_err("Failed to fetch image from window")?; if reply.depth == self.depth && reply.visual == self.visual_id { - let mut out = vec![0u32; reply.data.len() / 4]; - bytemuck::cast_slice_mut::(&mut out).copy_from_slice(&reply.data); + let mut out = vec![Pixel::default(); reply.data.len() / size_of::()]; + // SAFETY: `Pixel` can be re-interpreted as `[u8; 4]`. + let out_u8s = unsafe { + slice::from_raw_parts_mut( + out.as_mut_ptr().cast::(), + out.len() * size_of::(), + ) + }; + out_u8s.copy_from_slice(&reply.data); Ok(out) } else { Err(SoftBufferError::PlatformError( @@ -402,13 +410,13 @@ impl BufferInterface } #[inline] - fn pixels(&self) -> &[u32] { + fn pixels(&self) -> &[Pixel] { // SAFETY: We called `finish_wait` on the buffer, so it is safe to call `buffer()`. unsafe { self.0.buffer.buffer() } } #[inline] - fn pixels_mut(&mut self) -> &mut [u32] { + fn pixels_mut(&mut self) -> &mut [Pixel] { // SAFETY: We called `finish_wait` on the buffer, so it is safe to call `buffer_mut`. unsafe { self.0.buffer.buffer_mut() } } @@ -436,6 +444,14 @@ impl BufferInterface // This is a suboptimal strategy, raise a stink in the debug logs. tracing::debug!("Falling back to non-SHM method for window drawing."); + // SAFETY: `Pixel` can be re-interpreted as `[u8; 4]`. + let data = unsafe { + slice::from_raw_parts( + wire.as_ptr().cast::(), + wire.len() * size_of::(), + ) + }; + imp.display .connection() .put_image( @@ -448,7 +464,7 @@ impl BufferInterface 0, 0, imp.depth, - bytemuck::cast_slice(wire), + data, ) .map(|c| c.ignore_error()) .push_err() @@ -537,7 +553,10 @@ impl Buffer { match self { Buffer::Shm(ref mut shm) => shm.alloc_segment(conn, total_len(width, height)), Buffer::Wire(wire) => { - wire.resize(total_len(width, height) / 4, 0); + wire.resize( + total_len(width, height) / size_of::(), + Pixel::default(), + ); Ok(()) } } @@ -559,7 +578,7 @@ impl Buffer { /// /// `finish_wait()` must be called in between `shm::PutImage` requests and this function. #[inline] - unsafe fn buffer(&self) -> &[u32] { + unsafe fn buffer(&self) -> &[Pixel] { match self { Buffer::Shm(ref shm) => unsafe { shm.as_ref() }, Buffer::Wire(wire) => wire, @@ -572,7 +591,7 @@ impl Buffer { /// /// `finish_wait()` must be called in between `shm::PutImage` requests and this function. #[inline] - unsafe fn buffer_mut(&mut self) -> &mut [u32] { + unsafe fn buffer_mut(&mut self) -> &mut [Pixel] { match self { Buffer::Shm(ref mut shm) => unsafe { shm.as_mut() }, Buffer::Wire(wire) => wire, @@ -613,13 +632,20 @@ impl ShmBuffer { /// /// `finish_wait()` must be called before this function is. #[inline] - unsafe fn as_ref(&self) -> &[u32] { + unsafe fn as_ref(&self) -> &[Pixel] { match self.seg.as_ref() { Some((seg, _)) => { let buffer_size = seg.buffer_size(); // SAFETY: No other code should be able to access the segment. - bytemuck::cast_slice(unsafe { &seg.as_ref()[..buffer_size] }) + let segment = unsafe { &seg.as_ref()[..buffer_size] }; + + let ptr = segment.as_ptr().cast::(); + let len = segment.len() / size_of::(); + debug_assert_eq!(segment.len() % size_of::(), 0); + // SAFETY: The segment buffer is a multiple of `Pixel`, and we assume that the + // memmap allocation is aligned to at least a multiple of 4 bytes. + unsafe { slice::from_raw_parts(ptr, len) } } None => { // Nothing has been allocated yet. @@ -634,13 +660,20 @@ impl ShmBuffer { /// /// `finish_wait()` must be called before this function is. #[inline] - unsafe fn as_mut(&mut self) -> &mut [u32] { + unsafe fn as_mut(&mut self) -> &mut [Pixel] { match self.seg.as_mut() { Some((seg, _)) => { let buffer_size = seg.buffer_size(); // SAFETY: No other code should be able to access the segment. - bytemuck::cast_slice_mut(unsafe { &mut seg.as_mut()[..buffer_size] }) + let segment = unsafe { &mut seg.as_mut()[..buffer_size] }; + + let ptr = segment.as_mut_ptr().cast::(); + let len = segment.len() / size_of::(); + debug_assert_eq!(segment.len() % size_of::(), 0); + // SAFETY: The segment buffer is a multiple of `Pixel`, and we assume that the + // memmap allocation is aligned to at least a multiple of 4 bytes. + unsafe { slice::from_raw_parts_mut(ptr, len) } } None => { // Nothing has been allocated yet. @@ -1018,6 +1051,6 @@ fn total_len(width: u16, height: u16) -> usize { width .checked_mul(height) - .and_then(|len| len.checked_mul(4)) + .and_then(|len| len.checked_mul(size_of::())) .unwrap_or_else(|| panic!("Dimensions are too large: ({} x {})", width, height)) } diff --git a/src/lib.rs b/src/lib.rs index 7923c3e..008e77a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ mod backend_interface; use backend_interface::*; mod backends; mod error; +mod pixel; mod util; use std::cell::Cell; @@ -20,8 +21,9 @@ use std::num::NonZeroU32; use std::ops; use std::sync::Arc; -use error::InitError; -pub use error::SoftBufferError; +use self::error::InitError; +pub use self::error::SoftBufferError; +pub use self::pixel::Pixel; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle}; @@ -123,7 +125,7 @@ impl Surface { /// - On AppKit, UIKit, Redox and Wayland, this function is unimplemented. /// - On Web, this will fail if the content was supplied by /// a different origin depending on the sites CORS rules. - pub fn fetch(&mut self) -> Result, SoftBufferError> { + pub fn fetch(&mut self) -> Result, SoftBufferError> { self.surface_impl.fetch() } @@ -162,40 +164,28 @@ impl HasWindowHandle for Surface /// 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 derefs to a [slice] of [`Pixel`]s, 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 format of the buffer is as follows. There is one [`Pixel`] 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 +/// etc. (Row-major top to bottom left to right). /// /// # Platform dependent behavior +/// /// No-copy presentation is currently supported on: /// - Wayland /// - X, when XShm is available /// - Win32 /// - Orbital, when buffer size matches window size +/// - Web /// /// Currently [`Buffer::present`] must block copying image data on: -/// - Web /// - AppKit /// - UIKit /// @@ -273,17 +263,17 @@ impl Buffer<'_, D, W> { } impl ops::Deref for Buffer<'_, D, W> { - type Target = [u32]; + type Target = [Pixel]; #[inline] - fn deref(&self) -> &[u32] { + fn deref(&self) -> &[Pixel] { self.buffer_impl.pixels() } } impl ops::DerefMut for Buffer<'_, D, W> { #[inline] - fn deref_mut(&mut self) -> &mut [u32] { + fn deref_mut(&mut self) -> &mut [Pixel] { self.buffer_impl.pixels_mut() } } diff --git a/src/pixel.rs b/src/pixel.rs new file mode 100644 index 0000000..be83228 --- /dev/null +++ b/src/pixel.rs @@ -0,0 +1,112 @@ +/// A pixel. +/// +/// # Representation +/// +/// This is a set of `u8`'s in the order BGRX (first component blue, second green, third red and +/// last unset). +/// +/// If you're familiar with [the `rgb` crate](https://docs.rs/rgb/), you can treat this mostly as-if +/// it is `rgb::Bgra`, except that this type has an alignment of `4` for performance reasons, as +/// that makes copies faster on many platforms. +/// +/// # Example +/// +/// Construct a new pixel. +/// +/// ``` +/// # use softbuffer::Pixel; +/// # +/// let red = Pixel::new_rgb(0xff, 0x80, 0); +/// assert_eq!(red.r, 255); +/// assert_eq!(red.g, 128); +/// assert_eq!(red.b, 0); +/// assert_eq!(red.a, 0xff); +/// +/// let from_struct_literal = Pixel { r: 255, g: 0x80, b: 0, a: 0xff }; +/// assert_eq!(red, from_struct_literal); +/// ``` +/// +/// Convert a pixel to an array of `u8`s. +/// +/// ``` +/// # use softbuffer::Pixel; +/// # +/// let red = Pixel::new_rgb(0xff, 0, 0); +/// // SAFETY: `Pixel` can be reinterpreted as `[u8; 4]`. +/// let red = unsafe { core::mem::transmute::(red) }; +/// +/// // BGRX +/// assert_eq!(red[2], 255); +/// ``` +/// +/// Convert a pixel to an `u32`. +/// +/// ``` +/// # use softbuffer::Pixel; +/// # +/// let red = Pixel::new_rgb(0xff, 0, 0); +/// // SAFETY: `Pixel` can be reinterpreted as `u32`. +/// let red = unsafe { core::mem::transmute::(red) }; +/// +/// // BGRX +/// assert_eq!(red, u32::from_le_bytes([0x00, 0x00, 0xff, 0xff])); +/// ``` +#[repr(C)] +#[repr(align(4))] // May help the compiler to see that this is a u32 +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct Pixel { + /// The blue component. + pub b: u8, + /// The green component. + pub g: u8, + /// The red component. + pub r: u8, + + /// The alpha component. + /// + /// `0xff` here means opaque, whereas `0` means transparent. + /// + /// NOTE: Transparency is yet poorly supported, see [#17], until that is resolved, you will + /// probably want to set this to `0xff`. + /// + /// [#17]: https://github.com/rust-windowing/softbuffer/issues/17 + pub a: u8, +} + +impl Pixel { + /// Creates a new pixel from a red, a green and a blue component. + /// + /// The alpha component is set to opaque. + /// + /// # Example + /// + /// ``` + /// # use softbuffer::Pixel; + /// # + /// let red = Pixel::new_rgb(0xff, 0, 0); + /// assert_eq!(red.r, 255); + /// ``` + pub const fn new_rgb(r: u8, g: u8, b: u8) -> Self { + Self { r, g, b, a: 0xff } + } + + /// Creates a new pixel from a blue, a green and a red component. + /// + /// The alpha component is set to opaque. + /// + /// # Example + /// + /// ``` + /// # use softbuffer::Pixel; + /// # + /// let red = Pixel::new_bgr(0, 0, 0xff); + /// assert_eq!(red.r, 255); + /// ``` + pub const fn new_bgr(b: u8, g: u8, r: u8) -> Self { + Self { r, g, b, a: 0xff } + } + + // TODO: Once we have transparency, add `new_rgba` and `new_bgra` methods. +} + +// TODO: Implement `Add`/`Mul`/similar `std::ops` like `rgb` does? diff --git a/src/util.rs b/src/util.rs index 572de5e..4a663a5 100644 --- a/src/util.rs +++ b/src/util.rs @@ -6,8 +6,7 @@ use std::fmt; use std::num::NonZeroU32; use std::ops; -use crate::Rect; -use crate::SoftBufferError; +use crate::{Pixel, Rect, 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 @@ -97,7 +96,7 @@ pub(crate) fn union_damage(damage: &[Rect]) -> Option { /// A wrapper around a `Vec` of pixels that doesn't print the whole buffer on `Debug`. #[derive(PartialEq, Eq, Hash, Clone)] -pub(crate) struct PixelBuffer(pub Vec); +pub(crate) struct PixelBuffer(pub Vec); impl fmt::Debug for PixelBuffer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -106,7 +105,7 @@ impl fmt::Debug for PixelBuffer { } impl ops::Deref for PixelBuffer { - type Target = Vec; + type Target = Vec; fn deref(&self) -> &Self::Target { &self.0 } From 04b0ab22b3102220da556e3a3ef6792ff6e6a541 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sat, 13 Dec 2025 23:49:19 +0100 Subject: [PATCH 2/3] Make alpha component private --- src/backends/web.rs | 2 +- src/pixel.rs | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/backends/web.rs b/src/backends/web.rs index 2149981..4c395ae 100644 --- a/src/backends/web.rs +++ b/src/backends/web.rs @@ -145,7 +145,7 @@ impl WebImpl { .skip(union_damage.x as usize) .take(union_damage.width.get() as usize) }) - .flat_map(|pixel| [pixel.r, pixel.g, pixel.b, pixel.a]) + .flat_map(|pixel| [pixel.r, pixel.g, pixel.b, 255]) .collect(); debug_assert_eq!( diff --git a/src/pixel.rs b/src/pixel.rs index be83228..3f2185c 100644 --- a/src/pixel.rs +++ b/src/pixel.rs @@ -20,10 +20,6 @@ /// assert_eq!(red.r, 255); /// assert_eq!(red.g, 128); /// assert_eq!(red.b, 0); -/// assert_eq!(red.a, 0xff); -/// -/// let from_struct_literal = Pixel { r: 255, g: 0x80, b: 0, a: 0xff }; -/// assert_eq!(red, from_struct_literal); /// ``` /// /// Convert a pixel to an array of `u8`s. @@ -49,11 +45,11 @@ /// let red = unsafe { core::mem::transmute::(red) }; /// /// // BGRX -/// assert_eq!(red, u32::from_le_bytes([0x00, 0x00, 0xff, 0xff])); +/// assert_eq!(red, u32::from_le_bytes([0x00, 0x00, 0xff, 0x00])); /// ``` #[repr(C)] #[repr(align(4))] // May help the compiler to see that this is a u32 -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct Pixel { /// The blue component. pub b: u8, @@ -66,18 +62,22 @@ pub struct Pixel { /// /// `0xff` here means opaque, whereas `0` means transparent. /// - /// NOTE: Transparency is yet poorly supported, see [#17], until that is resolved, you will - /// probably want to set this to `0xff`. + /// NOTE: Transparency is not yet supported, see [#17], so this doesn't actually do anything. /// /// [#17]: https://github.com/rust-windowing/softbuffer/issues/17 - pub a: u8, + pub(crate) a: u8, +} + +impl Default for Pixel { + /// A black opaque pixel. + fn default() -> Self { + Self::new_rgb(0, 0, 0) + } } impl Pixel { /// Creates a new pixel from a red, a green and a blue component. /// - /// The alpha component is set to opaque. - /// /// # Example /// /// ``` @@ -87,13 +87,12 @@ impl Pixel { /// assert_eq!(red.r, 255); /// ``` pub const fn new_rgb(r: u8, g: u8, b: u8) -> Self { - Self { r, g, b, a: 0xff } + // FIXME(madsmtm): Change alpha to `0xff` once we support transparency. + Self { r, g, b, a: 0x00 } } /// Creates a new pixel from a blue, a green and a red component. /// - /// The alpha component is set to opaque. - /// /// # Example /// /// ``` @@ -103,7 +102,8 @@ impl Pixel { /// assert_eq!(red.r, 255); /// ``` pub const fn new_bgr(b: u8, g: u8, r: u8) -> Self { - Self { r, g, b, a: 0xff } + // FIXME(madsmtm): Change alpha to `0xff` once we support transparency. + Self { r, g, b, a: 0x00 } } // TODO: Once we have transparency, add `new_rgba` and `new_bgra` methods. From aebcfb2eb3d237c19ec6e721f5cd9db714c78149 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sat, 13 Dec 2025 23:27:47 +0100 Subject: [PATCH 3/3] Make the pixel format platform dependent The format is RGBX on WASM and Android and BGRX elsewhere. This should allow avoiding needless copying on these platforms. --- CHANGELOG.md | 1 + src/backends/android.rs | 1 + src/backends/web.rs | 2 + src/lib.rs | 2 +- src/pixel.rs | 117 ++++++++++++++++++++++++++++++++++------ 5 files changed, 107 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45aa392..e78737a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - **Breaking:** Add `Pixel` struct, and use that for pixels instead of `u32`. +- **Breaking:** The pixel format is now target-dependent. # 0.4.7 diff --git a/src/backends/android.rs b/src/backends/android.rs index 949d180..1918638 100644 --- a/src/backends/android.rs +++ b/src/backends/android.rs @@ -164,6 +164,7 @@ impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl // Write RGB(A) to the output. // TODO: Use `slice::write_copy_of_slice` once stable and in MSRV. + // TODO(madsmtm): Verify that this compiles down to an efficient copy. for (i, pixel) in input.iter().enumerate() { output[i * 4].write(pixel.r); output[i * 4 + 1].write(pixel.g); diff --git a/src/backends/web.rs b/src/backends/web.rs index 4c395ae..05c0821 100644 --- a/src/backends/web.rs +++ b/src/backends/web.rs @@ -145,6 +145,8 @@ impl WebImpl { .skip(union_damage.x as usize) .take(union_damage.width.get() as usize) }) + // TODO(madsmtm): Once we support alpha, add it here, and verify that this + // optimize away to nothing (and if it doesn't, unsafely cast instead). .flat_map(|pixel| [pixel.r, pixel.g, pixel.b, 255]) .collect(); diff --git a/src/lib.rs b/src/lib.rs index 008e77a..abe320a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,7 +23,7 @@ use std::sync::Arc; use self::error::InitError; pub use self::error::SoftBufferError; -pub use self::pixel::Pixel; +pub use self::pixel::{Pixel, PixelFormat, PIXEL_FORMAT}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle}; diff --git a/src/pixel.rs b/src/pixel.rs index 3f2185c..c881993 100644 --- a/src/pixel.rs +++ b/src/pixel.rs @@ -2,20 +2,18 @@ /// /// # Representation /// -/// This is a set of `u8`'s in the order BGRX (first component blue, second green, third red and -/// last unset). +/// This is a set of `u8`'s in the order defined by [`PIXEL_FORMAT`]. /// -/// If you're familiar with [the `rgb` crate](https://docs.rs/rgb/), you can treat this mostly as-if -/// it is `rgb::Bgra`, except that this type has an alignment of `4` for performance reasons, as -/// that makes copies faster on many platforms. +/// This type has an alignment of `4` for performance reasons, as that makes copies faster on many +/// platforms, and makes this type have the same in-memory representation as `u32`. /// /// # Example /// /// Construct a new pixel. /// /// ``` -/// # use softbuffer::Pixel; -/// # +/// use softbuffer::Pixel; +/// /// let red = Pixel::new_rgb(0xff, 0x80, 0); /// assert_eq!(red.r, 255); /// assert_eq!(red.g, 128); @@ -25,36 +23,93 @@ /// Convert a pixel to an array of `u8`s. /// /// ``` -/// # use softbuffer::Pixel; -/// # +/// use softbuffer::{Pixel, PixelFormat, PIXEL_FORMAT}; +/// /// let red = Pixel::new_rgb(0xff, 0, 0); /// // SAFETY: `Pixel` can be reinterpreted as `[u8; 4]`. /// let red = unsafe { core::mem::transmute::(red) }; /// -/// // BGRX -/// assert_eq!(red[2], 255); +/// match PIXEL_FORMAT { +/// PixelFormat::Rgbx => assert_eq!(red[0], 255), +/// PixelFormat::Bgrx => assert_eq!(red[2], 255), +/// } /// ``` /// /// Convert a pixel to an `u32`. /// /// ``` -/// # use softbuffer::Pixel; -/// # +/// use softbuffer::{Pixel, PixelFormat, PIXEL_FORMAT}; +/// /// let red = Pixel::new_rgb(0xff, 0, 0); /// // SAFETY: `Pixel` can be reinterpreted as `u32`. /// let red = unsafe { core::mem::transmute::(red) }; /// -/// // BGRX -/// assert_eq!(red, u32::from_le_bytes([0x00, 0x00, 0xff, 0x00])); +/// match PIXEL_FORMAT { +/// PixelFormat::Rgbx => assert_eq!(red, u32::from_le_bytes([0xff, 0x00, 0x00, 0x00])), +/// PixelFormat::Bgrx => assert_eq!(red, u32::from_le_bytes([0x00, 0x00, 0xff, 0x00])), +/// } +/// ``` +/// +/// Convert a slice of pixels to a slice of `u32`s. This might be useful for library authors that +/// want to keep rendering using BGRX. +/// +/// ``` +/// // Assume the user controls the following rendering function: +/// fn render(pixels: &mut [u32]) { +/// pixels.fill(u32::from_le_bytes([0xff, 0xff, 0x00, 0x00])); // Yellow in BGRX +/// } +/// +/// // Then we'd convert pixel data as follows: +/// use softbuffer::{Pixel, PixelFormat, PIXEL_FORMAT}; +/// +/// # let mut pixel_data = [Pixel::new_rgb(0, 0xff, 0xff)]; +/// let pixels: &mut [Pixel]; +/// # pixels = &mut pixel_data; +/// +/// if PIXEL_FORMAT == PixelFormat::Bgrx { +/// // Fast implementation when the pixel format is BGRX. +/// +/// // SAFETY: `Pixel` can be reinterpreted as `u32`. +/// let pixels = unsafe { std::mem::transmute::<&mut [Pixel], &mut [u32]>(pixels) }; +/// // CORRECTNESS: We just checked that the format is BGRX. +/// render(pixels); +/// } else { +/// // Fall back to slower implementation when the format is RGBX. +/// +/// // Render into temporary buffer. +/// let mut buffer = vec![0u32; pixels.len()]; +/// render(&mut buffer); +/// +/// // And copy from temporary buffer to actual pixel data. +/// for (old, new) in pixels.iter_mut().zip(buffer) { +/// let new = new.to_le_bytes(); +/// *old = Pixel::new_bgr(new[0], new[1], new[2]); +/// } +/// } +/// # +/// # assert_eq!(pixel_data, [Pixel::new_rgb(0, 0xff, 0xff)]); /// ``` #[repr(C)] #[repr(align(4))] // May help the compiler to see that this is a u32 #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct Pixel { + #[cfg(any(target_family = "wasm", target_os = "android"))] + /// The red component. + pub r: u8, + #[cfg(any(target_family = "wasm", target_os = "android"))] + /// The green component. + pub g: u8, + #[cfg(any(target_family = "wasm", target_os = "android"))] /// The blue component. pub b: u8, + + #[cfg(not(any(target_family = "wasm", target_os = "android")))] + /// The blue component. + pub b: u8, + #[cfg(not(any(target_family = "wasm", target_os = "android")))] /// The green component. pub g: u8, + #[cfg(not(any(target_family = "wasm", target_os = "android")))] /// The red component. pub r: u8, @@ -110,3 +165,35 @@ impl Pixel { } // TODO: Implement `Add`/`Mul`/similar `std::ops` like `rgb` does? + +// TODO: Implement `zerocopy` / `bytemuck` traits behind a feature flag? +// May not be that useful, since the representation is platform-specific. + +/// A pixel format that `softbuffer` may use. +/// +/// See [`PIXEL_FORMAT`]. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum PixelFormat { + /// The pixel format is `RGBX` (red, green, blue, unset). + Rgbx, + /// The pixel format is `BGRX` (blue, green, red, unset). + Bgrx, + // Intentionally exhaustive. +} + +/// The pixel format that `softbuffer` uses for the current target platform. +/// +/// Currently, this is BGRX (first component blue, second green, third red and last unset) on all +/// platforms except WebAssembly and Android targets, where it is RGBX, since the API on these +/// platforms only support that format. +/// +/// The format for a given platform may change in a non-breaking release if found to be more +/// performant. +/// +/// This distinction should only be relevant if you're bitcasting `Pixel` to/from a `u32`, to e.g. +/// avoid unnecessary copying, see [`Pixel`] for examples. +pub const PIXEL_FORMAT: PixelFormat = if cfg!(any(target_family = "wasm", target_os = "android")) { + PixelFormat::Rgbx +} else { + PixelFormat::Bgrx +};