From aa18dbce91e931fb5e0954f9b80827cf8149094f Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 22 Jan 2026 22:25:26 +0100 Subject: [PATCH 1/4] Remove bytemuck dependency --- Cargo.toml | 5 +---- src/backends/kms.rs | 9 ++++++++- src/backends/x11.rs | 30 +++++++++++++++++++++++++----- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b18d9371..4959d886 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,6 @@ wayland-dlopen = ["wayland-sys/dlopen", "winit/wayland-dlopen"] # Enable the X11 backend. x11 = [ "dep:as-raw-xcb-connection", - "dep:bytemuck", "dep:fastrand", "dep:rustix", "dep:tiny-xlib", @@ -45,7 +44,7 @@ x11 = [ x11-dlopen = ["tiny-xlib/dlopen", "x11rb/dl-libxcb"] # Enable the KMS/DRM backend. -kms = ["dep:bytemuck", "dep:drm", "dep:rustix"] +kms = ["dep:drm", "dep:rustix"] # Common dependencies. [dependencies] @@ -54,13 +53,11 @@ tracing = { version = "0.1.41", default-features = false } # Android dependencies. [target.'cfg(target_os = "android")'.dependencies] -bytemuck = "1.12.3" ndk = "0.9.0" # Wayland, X11 and KMS/DRM dependencies. [target.'cfg(not(any(target_os = "android", target_vendor = "apple", target_os = "redox", target_family = "wasm", target_os = "windows")))'.dependencies] # Common -bytemuck = { version = "1.12.3", optional = true } fastrand = { version = "2.0.0", optional = true } rustix = { version = "1.0.1", default-features = false, optional = true, features = [ "fs", diff --git a/src/backends/kms.rs b/src/backends/kms.rs index a3a982d1..d40efd6d 100644 --- a/src/backends/kms.rs +++ b/src/backends/kms.rs @@ -13,8 +13,10 @@ use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, Raw use std::collections::HashSet; use std::fmt; +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::*; @@ -315,7 +317,12 @@ impl BufferInterface for BufferImpl<'_> { #[inline] fn pixels_mut(&mut self) -> &mut [u32] { - bytemuck::cast_slice_mut(self.mapping.as_mut()) + 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: `&mut [u8]` can be reinterpreted as `&mut [u32]`, assuming that the allocation + // is aligned to at least a multiple of 4 bytes. + unsafe { slice::from_raw_parts_mut(ptr, len) } } #[inline] diff --git a/src/backends/x11.rs b/src/backends/x11.rs index 1ea9cecb..b6f79d6c 100644 --- a/src/backends/x11.rs +++ b/src/backends/x11.rs @@ -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, @@ -383,8 +384,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![0; reply.data.len() / size_of::()]; + // SAFETY: `u32` 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( @@ -443,6 +451,11 @@ impl BufferInterface for BufferImpl<'_> { // This is a suboptimal strategy, raise a stink in the debug logs. tracing::debug!("Falling back to non-SHM method for window drawing."); + // SAFETY: `u32` can be re-interpreted as `[u8; 4]`. + let data = unsafe { + slice::from_raw_parts(wire.as_ptr().cast::(), wire.len() * size_of::()) + }; + self.connection .put_image( xproto::ImageFormat::Z_PIXMAP, @@ -454,7 +467,7 @@ impl BufferInterface for BufferImpl<'_> { 0, 0, self.depth, - bytemuck::cast_slice(wire), + data, ) .map(|c| c.ignore_error()) .push_err() @@ -610,7 +623,14 @@ impl ShmBuffer { 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 `u32`, 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. From 58bacc841b52a8d7c171f3b26432fbfdeb8f12d4 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Fri, 23 Jan 2026 13:41:30 +0100 Subject: [PATCH 2/4] Add Pixel struct This is much more intuitive to use than manually bit-packing into a u32. --- CHANGELOG.md | 1 + README.md | 13 ++-- benches/buffer_mut.rs | 10 +-- examples/animation.rs | 22 +++--- examples/drm.rs | 7 +- examples/fruit.rs | 13 ++-- examples/libxcb.rs | 9 +-- examples/raytracing/game.rs | 22 +++--- examples/raytracing/main.rs | 5 +- examples/rectangle.rs | 10 +-- examples/winit.rs | 7 +- examples/winit_multithread.rs | 9 +-- examples/winit_wrong_sized_buffer.rs | 7 +- src/backend_dispatch.rs | 6 +- src/backend_interface.rs | 6 +- src/backends/android.rs | 22 +++--- src/backends/cg.rs | 14 ++-- src/backends/kms.rs | 11 +-- src/backends/orbital.rs | 15 ++-- src/backends/wayland/buffer.rs | 12 ++- src/backends/wayland/mod.rs | 4 +- src/backends/web.rs | 18 +++-- src/backends/win32.rs | 14 ++-- src/backends/x11.rs | 38 +++++---- src/lib.rs | 110 +++++++++++++------------- src/pixel.rs | 111 +++++++++++++++++++++++++++ src/util.rs | 6 +- 27 files changed, 322 insertions(+), 200 deletions(-) create mode 100644 src/pixel.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 8eb2b664..2d9b634e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - 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:** Removed `Deref[Mut]` implementation on `Buffer<'_>`. Use `Buffer::pixels()` instead. +- **Breaking:** Add `Pixel` struct, and use that for pixels instead of `u32`. - **Breaking:** Removed unintentional Cargo features for Softbuffer's optional dependencies. # 0.4.7 diff --git a/README.md b/README.md index 5c59ee24..64e7d48e 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ To run the Android-specific example on an Android phone: `cargo apk r --example ```rust,no_run use std::num::NonZeroU32; use std::rc::Rc; +use softbuffer::{Context, Pixel, Surface}; use winit::event::{Event, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::Window; @@ -79,14 +80,14 @@ mod util; fn main() { let event_loop = EventLoop::new().unwrap(); - let context = softbuffer::Context::new(event_loop.owned_display_handle()).unwrap(); + let context = Context::new(event_loop.owned_display_handle()).unwrap(); let mut app = util::WinitAppBuilder::with_init( |elwt| { let window = elwt.create_window(Window::default_attributes()); Rc::new(window.unwrap()) }, - |_elwt, window| softbuffer::Surface::new(&context, window.clone()).unwrap(), + |_elwt, window| Surface::new(&context, window.clone()).unwrap(), ) .with_event_handler(|window, surface, window_id, event, elwt| { elwt.set_control_flow(ControlFlow::Wait); @@ -111,11 +112,11 @@ fn main() { let mut buffer = surface.buffer_mut().unwrap(); for (x, y, pixel) in buffer.pixels_iter() { - 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; - *pixel = blue | (green << 8) | (red << 16); + *pixel = Pixel::new_rgb(red, green, blue); } buffer.present().unwrap(); diff --git a/benches/buffer_mut.rs b/benches/buffer_mut.rs index 01609a81..38c74a94 100644 --- a/benches/buffer_mut.rs +++ b/benches/buffer_mut.rs @@ -7,7 +7,7 @@ )))] fn buffer_mut(c: &mut criterion::Criterion) { 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; @@ -43,16 +43,16 @@ fn buffer_mut(c: &mut criterion::Criterion) { c.bench_function("pixels()", |b| { let mut buffer = surface.buffer_mut().unwrap(); b.iter(|| { - let pixels: &mut [u32] = buffer.pixels(); + let pixels: &mut [Pixel] = buffer.pixels(); black_box(pixels); }); }); - c.bench_function("fill u32", |b| { + c.bench_function("fill pixels", |b| { let mut buffer = surface.buffer_mut().unwrap(); b.iter(|| { let buffer = black_box(&mut buffer); - buffer.pixels().fill(0x00000000); + buffer.pixels().fill(Pixel::default()); }); }); @@ -64,7 +64,7 @@ fn buffer_mut(c: &mut criterion::Criterion) { let red = (x & 0xff) ^ (y & 0xff); let green = (x & 0x7f) ^ (y & 0x7f); let blue = (x & 0x3f) ^ (y & 0x3f); - *pixel = blue | (green << 8) | (red << 16); + *pixel = Pixel::new_rgb(red as u8, green as u8, blue as u8); } }); }); diff --git a/examples/animation.rs b/examples/animation.rs index 1603883d..c793b4b2 100644 --- a/examples/animation.rs +++ b/examples/animation.rs @@ -1,5 +1,6 @@ #[cfg(not(target_family = "wasm"))] use rayon::prelude::*; +use softbuffer::{Context, Pixel, Surface}; use std::f64::consts::PI; use std::num::NonZeroU32; use web_time::Instant; @@ -15,7 +16,7 @@ fn main() { let event_loop = EventLoop::new().unwrap(); let start = Instant::now(); - let context = softbuffer::Context::new(event_loop.owned_display_handle()).unwrap(); + let context = Context::new(event_loop.owned_display_handle()).unwrap(); let app = util::WinitAppBuilder::with_init( |event_loop| { @@ -26,9 +27,7 @@ fn main() { (window, old_size, frames) }, - move |_elwft, (window, _old_size, _frames)| { - softbuffer::Surface::new(&context, window.clone()).unwrap() - }, + move |_elwft, (window, _old_size, _frames)| Surface::new(&context, window.clone()).unwrap(), ) .with_event_handler(move |state, surface, window_id, event, elwt| { let (window, old_size, frames) = state; @@ -95,7 +94,7 @@ fn main() { util::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; @@ -104,14 +103,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 4588d7bc..17b77980 100644 --- a/examples/drm.rs +++ b/examples/drm.rs @@ -17,7 +17,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}; @@ -161,7 +161,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 { @@ -169,8 +169,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 c4fef130..e8164d6b 100644 --- a/examples/fruit.rs +++ b/examples/fruit.rs @@ -1,4 +1,5 @@ use image::GenericImageView; +use softbuffer::{Context, Pixel, Surface}; use std::num::NonZeroU32; use winit::event::{KeyEvent, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; @@ -14,7 +15,7 @@ fn main() { let (width, height) = (fruit.width(), fruit.height()); let event_loop = EventLoop::new().unwrap(); - let context = softbuffer::Context::new(event_loop.owned_display_handle()).unwrap(); + let context = Context::new(event_loop.owned_display_handle()).unwrap(); let app = util::WinitAppBuilder::with_init( move |elwt| { @@ -23,7 +24,7 @@ fn main() { }) }, move |_elwt, window| { - let mut surface = softbuffer::Surface::new(&context, window.clone()).unwrap(); + let mut surface = Surface::new(&context, window.clone()).unwrap(); // 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. @@ -53,12 +54,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.pixels()[(y * width + x) as usize] = color; + let pixel = Pixel::new_rgb(pixel.0[0], pixel.0[1], pixel.0[2]); + buffer.pixels()[(y * width + x) as usize] = pixel; } buffer.present().unwrap(); diff --git a/examples/libxcb.rs b/examples/libxcb.rs index 90ae51bf..f84ac2c6 100644 --- a/examples/libxcb.rs +++ b/examples/libxcb.rs @@ -17,6 +17,7 @@ mod example { DisplayHandle, RawDisplayHandle, RawWindowHandle, WindowHandle, XcbDisplayHandle, XcbWindowHandle, }; + use softbuffer::{Context, Pixel, Surface}; use std::{env, num::NonZeroU32, ptr::NonNull}; use x11rb::{ connection::Connection, @@ -27,8 +28,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"); @@ -78,8 +77,8 @@ mod example { unsafe { DisplayHandle::borrow_raw(RawDisplayHandle::Xcb(display_handle)) }; let window_handle = unsafe { WindowHandle::borrow_raw(RawWindowHandle::Xcb(window_handle)) }; - let context = softbuffer::Context::new(display_handle).unwrap(); - let mut surface = softbuffer::Surface::new(&context, window_handle).unwrap(); + let context = Context::new(display_handle).unwrap(); + let mut surface = Surface::new(&context, window_handle).unwrap(); // Register an atom for closing the window. let wm_protocols_atom = conn @@ -124,7 +123,7 @@ mod example { ) .unwrap(); let mut buffer = surface.buffer_mut().unwrap(); - buffer.pixels().fill(RED); + buffer.pixels().fill(Pixel::new_rgb(0xff, 0, 0)); buffer.present().unwrap(); } Event::ConfigureNotify(configure_notify) => { diff --git a/examples/raytracing/game.rs b/examples/raytracing/game.rs index 554477ad..8f4abccd 100644 --- a/examples/raytracing/game.rs +++ b/examples/raytracing/game.rs @@ -2,7 +2,7 @@ use std::time::{Duration, Instant}; use rand::rngs::SmallRng; use rand::{Rng, SeedableRng}; -use softbuffer::Buffer; +use softbuffer::{Buffer, Pixel}; use crate::camera::Camera; use crate::vec3::{Color, Point3, Vec3}; @@ -71,9 +71,9 @@ impl Game { dist_to_focus, ); - let mut pixels = vec![0; width as usize * height as usize]; + let mut pixels = vec![Pixel::default(); width as usize * height as usize]; - let each_pixel = |rng: &mut SmallRng, i, pixel: &mut u32| { + let each_pixel = |rng: &mut SmallRng, i, pixel: &mut Pixel| { let y = i % (width as usize); let x = i / (width as usize); let mut pixel_color = Color::default(); @@ -125,7 +125,7 @@ impl Game { right: f32, top: f32, bottom: f32, - color: u32, + color: Pixel, } let width = buffer.width().get() as f32 / scale_factor; @@ -136,14 +136,14 @@ impl Game { right: width - 10.0, top: height - 90.0, bottom: height - 10.0, - color: 0x00eeaaaa, + color: Pixel::new_rgb(0xee, 0xaa, 0xaa), }, Rect { left: 30.0, right: 70.0, top: height - 70.0, bottom: height - 30.0, - color: 0x00aaaaee, + color: Pixel::new_rgb(0xaa, 0xaa, 0xee), }, ]; @@ -193,7 +193,7 @@ impl Game { } } -fn color_to_pixel(pixel_color: Color, samples_per_pixel: i32) -> u32 { +fn color_to_pixel(pixel_color: Color, samples_per_pixel: i32) -> Pixel { let mut r = pixel_color.x; let mut g = pixel_color.y; let mut b = pixel_color.z; @@ -203,7 +203,9 @@ fn color_to_pixel(pixel_color: Color, samples_per_pixel: i32) -> u32 { g = f32::sqrt(scale * g); b = f32::sqrt(scale * b); - (256.0 * b.clamp(0.0, 0.999)) as u32 - | (((256.0 * g.clamp(0.0, 0.999)) as u32) << 8) - | (((256.0 * r.clamp(0.0, 0.999)) as u32) << 16) + Pixel::new_rgb( + (256.0 * r.clamp(0.0, 0.999)) as u8, + (256.0 * g.clamp(0.0, 0.999)) as u8, + (256.0 * b.clamp(0.0, 0.999)) as u8, + ) } diff --git a/examples/raytracing/main.rs b/examples/raytracing/main.rs index 2eed2d7e..2d8115a4 100644 --- a/examples/raytracing/main.rs +++ b/examples/raytracing/main.rs @@ -3,6 +3,7 @@ //! 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 softbuffer::{Context, Surface}; use std::num::NonZeroU32; use winit::event::{DeviceEvent, ElementState, KeyEvent, WindowEvent}; use winit::event_loop::EventLoop; @@ -25,12 +26,12 @@ fn main() { util::setup(); let event_loop = EventLoop::new().unwrap(); - let context = softbuffer::Context::new(event_loop.owned_display_handle()).unwrap(); + let context = Context::new(event_loop.owned_display_handle()).unwrap(); let app = util::WinitAppBuilder::with_init( |elwt| util::make_window(elwt, |w| w), move |_elwt, window| { - let mut surface = softbuffer::Surface::new(&context, window.clone()).unwrap(); + let mut surface = Surface::new(&context, window.clone()).unwrap(); surface .resize( NonZeroU32::new(window.inner_size().width).unwrap(), diff --git a/examples/rectangle.rs b/examples/rectangle.rs index 1124ea0c..69e4e4fe 100644 --- a/examples/rectangle.rs +++ b/examples/rectangle.rs @@ -1,4 +1,4 @@ -use softbuffer::Buffer; +use softbuffer::{Buffer, Context, Pixel, Surface}; use std::num::NonZeroU32; use winit::event::{ElementState, KeyEvent, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; @@ -11,12 +11,12 @@ fn redraw(buffer: &mut Buffer<'_>, flag: bool) { let height = buffer.height().get(); for (x, y, pixel) in buffer.pixels_iter() { *pixel = 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) }; } } @@ -25,7 +25,7 @@ fn main() { util::setup(); let event_loop = EventLoop::new().unwrap(); - let context = softbuffer::Context::new(event_loop.owned_display_handle()).unwrap(); + let context = Context::new(event_loop.owned_display_handle()).unwrap(); let app = util::WinitAppBuilder::with_init( |elwt| { @@ -37,7 +37,7 @@ fn main() { (window, flag) }, - move |_elwt, (window, _flag)| softbuffer::Surface::new(&context, window.clone()).unwrap(), + move |_elwt, (window, _flag)| Surface::new(&context, window.clone()).unwrap(), ) .with_event_handler(|state, surface, window_id, event, elwt| { let (window, flag) = state; diff --git a/examples/winit.rs b/examples/winit.rs index 240a04fc..61c0066e 100644 --- a/examples/winit.rs +++ b/examples/winit.rs @@ -1,3 +1,4 @@ +use softbuffer::{Context, Pixel, Surface}; use std::num::NonZeroU32; use winit::event::{KeyEvent, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; @@ -13,11 +14,11 @@ fn main() { } pub(crate) fn entry(event_loop: EventLoop<()>) { - let context = softbuffer::Context::new(event_loop.owned_display_handle()).unwrap(); + let context = Context::new(event_loop.owned_display_handle()).unwrap(); let app = util::WinitAppBuilder::with_init( |elwt| util::make_window(elwt, |w| w), - move |_elwt, window| softbuffer::Surface::new(&context, window.clone()).unwrap(), + move |_elwt, window| Surface::new(&context, window.clone()).unwrap(), ) .with_event_handler(|window, surface, window_id, event, elwt| { elwt.set_control_flow(ControlFlow::Wait); @@ -50,7 +51,7 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { let red = x % 255; let green = y % 255; let blue = (x * y) % 255; - *pixel = blue | (green << 8) | (red << 16); + *pixel = Pixel::new_rgb(red as u8, green as u8, blue as u8); } buffer.present().unwrap(); diff --git a/examples/winit_multithread.rs b/examples/winit_multithread.rs index 52791f14..f0d56524 100644 --- a/examples/winit_multithread.rs +++ b/examples/winit_multithread.rs @@ -4,6 +4,7 @@ mod util; #[cfg(not(target_family = "wasm"))] pub mod ex { + use softbuffer::{Context, Pixel}; use std::num::NonZeroU32; use std::sync::{mpsc, Arc, Mutex}; use winit::event::{KeyEvent, WindowEvent}; @@ -36,7 +37,7 @@ pub mod ex { let red = x % 255; let green = y % 255; let blue = (x * y) % 255; - *pixel = blue | (green << 8) | (red << 16); + *pixel = Pixel::new_rgb(red as u8, green as u8, blue as u8); } tracing::info!("presenting..."); @@ -48,7 +49,7 @@ pub mod ex { } pub fn entry(event_loop: EventLoop<()>) { - let context = softbuffer::Context::new(event_loop.owned_display_handle()).unwrap(); + let context = Context::new(event_loop.owned_display_handle()).unwrap(); let app = util::WinitAppBuilder::with_init( |elwt| { @@ -71,9 +72,7 @@ pub mod ex { }, move |_elwt, (window, _start_render, _finish_render)| { tracing::info!("making surface..."); - Arc::new(Mutex::new( - softbuffer::Surface::new(&context, window.clone()).unwrap(), - )) + Arc::new(Mutex::new(Surface::new(&context, window.clone()).unwrap())) }, ) .with_event_handler(|state, surface, window_id, event, elwt| { diff --git a/examples/winit_wrong_sized_buffer.rs b/examples/winit_wrong_sized_buffer.rs index 0160f771..253b19f9 100644 --- a/examples/winit_wrong_sized_buffer.rs +++ b/examples/winit_wrong_sized_buffer.rs @@ -1,3 +1,4 @@ +use softbuffer::{Context, Pixel, Surface}; use std::num::NonZeroU32; use winit::event::{KeyEvent, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; @@ -9,12 +10,12 @@ fn main() { util::setup(); let event_loop = EventLoop::new().unwrap(); - let context = softbuffer::Context::new(event_loop.owned_display_handle()).unwrap(); + let context = Context::new(event_loop.owned_display_handle()).unwrap(); let app = util::WinitAppBuilder::with_init( |elwt| util::make_window(elwt, |w| w), move |_elwt, window| { - let mut surface = softbuffer::Surface::new(&context, window.clone()).unwrap(); + let mut surface = 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()) @@ -41,7 +42,7 @@ fn main() { let red = x % 255; let green = y % 255; let blue = (x * y) % 255; - *pixel = blue | (green << 8) | (red << 16); + *pixel = Pixel::new_rgb(red as u8, green as u8, blue as u8); } buffer.present().unwrap(); } diff --git a/src/backend_dispatch.rs b/src/backend_dispatch.rs index f2a94540..98fa9049 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_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 1eafbf82..1d0f8b53 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,7 +37,7 @@ pub(crate) trait SurfaceInterface NonZeroU32; fn height(&self) -> NonZeroU32; - fn pixels_mut(&mut self) -> &mut [u32]; + 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 cc23b9c6..81b675fb 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, @@ -108,7 +109,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) } } @@ -132,7 +133,7 @@ impl BufferInterface for BufferImpl<'_> { } #[inline] - fn pixels_mut(&mut self) -> &mut [u32] { + fn pixels_mut(&mut self) -> &mut [Pixel] { &mut self.buffer } @@ -154,13 +155,14 @@ impl 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 and once the pixel + // structure is of the RGBA format. 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 f45779f6..f4d675e1 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]), width: self.width, height: self.height, color_space: &self.color_space, @@ -288,7 +288,7 @@ impl BufferInterface for BufferImpl<'_> { } #[inline] - fn pixels_mut(&mut self) -> &mut [u32] { + fn pixels_mut(&mut self) -> &mut [Pixel] { &mut self.buffer } @@ -302,15 +302,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 d40efd6d..b3a95c26 100644 --- a/src/backends/kms.rs +++ b/src/backends/kms.rs @@ -21,6 +21,7 @@ use std::sync::Arc; use crate::backend_interface::*; use crate::error::{InitError, SoftBufferError, SwResultExt}; +use crate::Pixel; #[derive(Debug, Clone)] struct DrmDevice<'a> { @@ -316,11 +317,11 @@ impl BufferInterface for BufferImpl<'_> { } #[inline] - fn pixels_mut(&mut self) -> &mut [u32] { - 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: `&mut [u8]` can be reinterpreted as `&mut [u32]`, assuming that the allocation + 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: `&mut [u8]` can be reinterpreted as `&mut [Pixel]`, assuming that the allocation // is aligned to at least a multiple of 4 bytes. unsafe { slice::from_raw_parts_mut(ptr, len) } } diff --git a/src/backends/orbital.rs b/src/backends/orbital.rs index 89a718db..7a86303d 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,8 +38,8 @@ impl OrbitalMap { }) } - 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) } } } @@ -122,7 +122,7 @@ impl SurfaceInterface for Orbital ) } else { Pixels::Buffer(util::PixelBuffer(vec![ - 0; + Pixel::default(); self.width as usize * self.height as usize ])) @@ -162,7 +162,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, @@ -216,7 +216,7 @@ fn window_size(window_fd: usize) -> (usize, usize) { (window_width, window_height) } -fn set_buffer(window_fd: usize, buffer: &[u32], width_u32: u32, height_u32: u32) { +fn set_buffer(window_fd: usize, buffer: &[Pixel], width_u32: u32, height_u32: u32) { // Read the current width and size let (window_width, window_height) = window_size(window_fd); @@ -226,7 +226,8 @@ fn set_buffer(window_fd: usize, buffer: &[u32], width_u32: u32, height_u32: u32) unsafe { OrbitalMap::new(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 diff --git a/src/backends/wayland/buffer.rs b/src/backends/wayland/buffer.rs index e28653a1..dc611b92 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 { @@ -99,6 +100,8 @@ impl WaylandBuffer { width, height, width * 4, + // This is documented as `0xXXRRGGBB` on a little-endian machine, which means a byte + // order of `[B, G, R, X]`. wl_shm::Format::Xrgb8888, qh, released.clone(), @@ -162,11 +165,12 @@ impl WaylandBuffer { } #[inline] - pub fn mapped_mut(&mut self) -> &mut [u32] { + pub fn mapped_mut(&mut self) -> &mut [Pixel] { debug_assert!(self.len() * 4 <= self.map.len()); - // SAFETY: We're casting a `&mut [u8]` to `&mut [u32]`, the alignment of the memory region - // is at least 4, and the size of the region is large enough. - unsafe { slice::from_raw_parts_mut(self.map.as_mut_ptr() as *mut u32, self.len()) } + // SAFETY: We're casting a `&mut [u8]` to `&mut [Pixel]`, and we assume that the memmap + // allocation s aligned to at least a multiple of 4 bytes, and that the size of the + // region is large enough. + unsafe { slice::from_raw_parts_mut(self.map.as_mut_ptr() as *mut Pixel, self.len()) } } } diff --git a/src/backends/wayland/mod.rs b/src/backends/wayland/mod.rs index 77b7b876..1682f76f 100644 --- a/src/backends/wayland/mod.rs +++ b/src/backends/wayland/mod.rs @@ -1,7 +1,7 @@ use crate::{ backend_interface::*, error::{InitError, SwResultExt}, - Rect, SoftBufferError, + Pixel, Rect, SoftBufferError, }; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle}; use std::{ @@ -228,7 +228,7 @@ impl BufferInterface for BufferImpl<'_> { } #[inline] - fn pixels_mut(&mut self) -> &mut [u32] { + fn pixels_mut(&mut self) -> &mut [Pixel] { self.back.mapped_mut() } diff --git a/src/backends/web.rs b/src/backends/web.rs index a829e5b4..beb6de7f 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; @@ -171,7 +171,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)); @@ -189,7 +190,7 @@ impl SurfaceInterface for WebImpl }) } - 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()`"); @@ -205,7 +206,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: 0, + }) .collect()) } } @@ -318,7 +324,7 @@ impl BufferInterface for BufferImpl<'_> { .1 } - fn pixels_mut(&mut self) -> &mut [u32] { + fn pixels_mut(&mut self) -> &mut [Pixel] { self.buffer } @@ -366,7 +372,7 @@ impl BufferInterface for BufferImpl<'_> { .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, 0xff]) .collect(); debug_assert_eq!( diff --git a/src/backends/win32.rs b/src/backends/win32.rs index 40bd3bd0..bf002d79 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_mut(&mut self) -> &mut [u32] { + fn pixels_mut(&mut self) -> &mut [Pixel] { unsafe { slice::from_raw_parts_mut( self.pixels.as_ptr(), @@ -237,7 +237,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) } } @@ -259,7 +259,7 @@ impl BufferInterface for BufferImpl<'_> { } #[inline] - fn pixels_mut(&mut self) -> &mut [u32] { + fn pixels_mut(&mut self) -> &mut [Pixel] { self.buffer.pixels_mut() } diff --git a/src/backends/x11.rs b/src/backends/x11.rs index b6f79d6c..c4c060c8 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, @@ -359,7 +359,7 @@ impl SurfaceInterface fo }) } - fn fetch(&mut self) -> Result, SoftBufferError> { + fn fetch(&mut self) -> Result, SoftBufferError> { tracing::trace!("fetch: window={:X}", self.window); let (width, height) = self @@ -384,12 +384,12 @@ 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![0; reply.data.len() / size_of::()]; - // SAFETY: `u32` can be re-interpreted as `[u8; 4]`. + 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.len() * size_of::(), ) }; out_u8s.copy_from_slice(&reply.data); @@ -425,7 +425,7 @@ impl BufferInterface for BufferImpl<'_> { } #[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.buffer.buffer_mut() } } @@ -451,9 +451,12 @@ impl BufferInterface for BufferImpl<'_> { // This is a suboptimal strategy, raise a stink in the debug logs. tracing::debug!("Falling back to non-SHM method for window drawing."); - // SAFETY: `u32` can be re-interpreted as `[u8; 4]`. + // SAFETY: `Pixel` can be re-interpreted as `[u8; 4]`. let data = unsafe { - slice::from_raw_parts(wire.as_ptr().cast::(), wire.len() * size_of::()) + slice::from_raw_parts( + wire.as_ptr().cast::(), + wire.len() * size_of::(), + ) }; self.connection @@ -554,7 +557,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(()) } } @@ -576,7 +582,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, @@ -617,7 +623,7 @@ 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(); @@ -625,10 +631,10 @@ impl ShmBuffer { // SAFETY: No other code should be able to access the segment. 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 `u32`, and we assume that the + 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) } } @@ -999,6 +1005,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 d54d2b9d..a93d05f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,11 +7,10 @@ extern crate core; mod backend_dispatch; -use backend_dispatch::*; mod backend_interface; -use backend_interface::*; mod backends; mod error; +mod pixel; mod util; use std::cell::Cell; @@ -19,13 +18,15 @@ use std::marker::PhantomData; use std::num::NonZeroU32; use std::sync::Arc; -use error::InitError; -pub use error::SoftBufferError; - use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle}; +use self::backend_dispatch::*; +use self::backend_interface::*; #[cfg(target_family = "wasm")] -pub use backends::web::SurfaceExtWeb; +pub use self::backends::web::SurfaceExtWeb; +use self::error::InitError; +pub use self::error::SoftBufferError; +pub use self::pixel::Pixel; /// An instance of this struct contains the platform-specific data that must be managed in order to /// write to a window on that platform. @@ -122,7 +123,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() } @@ -168,11 +169,18 @@ 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 buffer data is laid out in row-major order (split into horizontal lines), with origin in the +/// top-left corner. There is one [`Pixel`] (4 bytes) in the buffer for each pixel in the area to +/// draw. +/// /// # Reading buffer data /// /// Reading from buffer data may perform very poorly, as the underlying storage of zero-copy @@ -182,48 +190,29 @@ impl HasWindowHandle for Surface /// As such, when rendering, you should always set the pixel in its entirety: /// /// ``` -/// # let pixel = &mut 0x00ffffff; -/// # let (blue, green, red) = (0x11, 0x22, 0x33); -/// *pixel = blue | (green << 8) | (red << 16); -/// # assert_eq!(*pixel, 0x00332211); +/// # use softbuffer::Pixel; +/// # let pixel = &mut Pixel::default(); +/// # let (red, green, blue) = (0x11, 0x22, 0x33); +/// *pixel = Pixel::new_rgb(red, green, blue); /// ``` /// /// Instead of e.g. something like: /// /// ``` -/// # let pixel = &mut 0x00ffffff; -/// # let (blue, green, red) = (0x11, 0x22, 0x33); +/// # use softbuffer::Pixel; +/// # let pixel = &mut Pixel::default(); +/// # let (red, green, blue) = (0x11, 0x22, 0x33); /// // DISCOURAGED! -/// *pixel &= 0x00000000; // Clear -/// *pixel |= blue; // Set blue pixel -/// *pixel |= green << 8; // Set green pixel -/// *pixel |= red << 16; // Set red pixel -/// # assert_eq!(*pixel, 0x00332211); +/// *pixel = Pixel::default(); // Clear +/// pixel.r |= red; +/// pixel.g |= green; +/// pixel.b |= blue; /// ``` /// /// To discourage reading from the buffer, `&self -> &[u8]` methods are intentionally not provided. /// -/// # Data representation -/// -/// The format of the buffer is as follows. There is one `u32` in the buffer for each pixel in -/// the area to draw. The first entry is the upper-left most pixel. The second is one to the right -/// etc. (Row-major top to bottom left to right one `u32` per pixel). Within each `u32` the highest -/// order 8 bits are to be set to 0. The next highest order 8 bits are the red channel, then the -/// green channel, and then the blue channel in the lowest-order 8 bits. See the examples for -/// one way to build this format using bitwise operations. -/// -/// -------- -/// -/// Pixel format (`u32`): -/// -/// 00000000RRRRRRRRGGGGGGGGBBBBBBBB -/// -/// 0: Bit is 0 -/// R: Red channel -/// G: Green channel -/// B: Blue channel -/// /// # Platform dependent behavior +/// /// No-copy presentation is currently supported on: /// - Wayland /// - X, when XShm is available @@ -307,10 +296,11 @@ impl Buffer<'_> { /// Clear the buffer with red. /// /// ```no_run - /// # let buffer: softbuffer::Buffer<'_> = unimplemented!(); - /// buffer.pixels().fill(0x00ff0000); + /// # use softbuffer::{Buffer, Pixel}; + /// # let buffer: Buffer<'_> = unimplemented!(); + /// buffer.pixels().fill(Pixel::new_rgb(0xff, 0x00, 0x00)); /// ``` - pub fn pixels(&mut self) -> &mut [u32] { + pub fn pixels(&mut self) -> &mut [Pixel] { self.buffer_impl.pixels_mut() } @@ -323,12 +313,13 @@ impl Buffer<'_> { /// Fill each row with alternating black and white. /// /// ```no_run - /// # let buffer: softbuffer::Buffer<'_> = unimplemented!(); + /// # use softbuffer::{Buffer, Pixel}; + /// # let buffer: Buffer<'_> = unimplemented!(); /// for (y, row) in buffer.pixel_rows().enumerate() { /// if y % 2 == 0 { - /// row.fill(0x00ffffff); + /// row.fill(Pixel::new_rgb(0xff, 0xff, 0xff)); /// } else { - /// row.fill(0x00000000); + /// row.fill(Pixel::new_rgb(0x00, 0x00, 0x00)); /// } /// } /// ``` @@ -336,7 +327,8 @@ impl Buffer<'_> { /// Fill a red rectangle while skipping over regions that don't need to be modified. /// /// ```no_run - /// # let buffer: softbuffer::Buffer<'_> = unimplemented!(); + /// # use softbuffer::{Buffer, Pixel}; + /// # let buffer: Buffer<'_> = unimplemented!(); /// let x = 100; /// let y = 200; /// let width = 10; @@ -344,7 +336,7 @@ impl Buffer<'_> { /// /// for row in buffer.pixel_rows().skip(y).take(height) { /// for pixel in row.iter_mut().skip(x).take(width) { - /// *pixel = 0x00ff0000; + /// *pixel = Pixel::new_rgb(0xff, 0x00, 0x00); /// } /// } /// ``` @@ -354,18 +346,18 @@ impl Buffer<'_> { /// [`pixels_iter`]: Self::pixels_iter /// /// ```no_run - /// # let buffer: softbuffer::Buffer<'_> = unimplemented!(); - /// # let pixel_value = |x, y| 0x00000000; + /// # use softbuffer::{Buffer, Pixel}; + /// # let buffer: Buffer<'_> = unimplemented!(); /// for (y, row) in buffer.pixel_rows().enumerate() { /// for (x, pixel) in row.iter_mut().enumerate() { - /// *pixel = pixel_value(x, y); + /// *pixel = Pixel::new_rgb((x % 0xff) as u8, (y % 0xff) as u8, 0x00); /// } /// } /// ``` #[inline] pub fn pixel_rows( &mut self, - ) -> impl DoubleEndedIterator + ExactSizeIterator { + ) -> impl DoubleEndedIterator + ExactSizeIterator { let width = self.width().get() as usize; let pixels = self.pixels(); assert_eq!(pixels.len() % width, 0, "buffer must be multiple of width"); @@ -383,7 +375,8 @@ impl Buffer<'_> { /// Draw a red rectangle with a margin of 10 pixels, and fill the background with blue. /// /// ```no_run - /// # let buffer: softbuffer::Buffer<'_> = unimplemented!(); + /// # use softbuffer::{Buffer, Pixel}; + /// # let buffer: Buffer<'_> = unimplemented!(); /// let width = buffer.width().get(); /// let height = buffer.height().get(); /// let left = 10; @@ -394,10 +387,10 @@ impl Buffer<'_> { /// for (x, y, pixel) in buffer.pixels_iter() { /// if (left..=right).contains(&x) && (top..=bottom).contains(&y) { /// // Inside rectangle. - /// *pixel = 0x00ff0000; + /// *pixel = Pixel::new_rgb(0xff, 0x00, 0x00); /// } else { /// // Outside rectangle. - /// *pixel = 0x000000ff; + /// *pixel = Pixel::new_rgb(0x00, 0x00, 0xff); /// }; /// } /// ``` @@ -405,16 +398,17 @@ impl Buffer<'_> { /// Iterate over the pixel data in reverse, and draw a red rectangle in the top-left corner. /// /// ```no_run - /// # let buffer: softbuffer::Buffer<'_> = unimplemented!(); + /// # use softbuffer::{Buffer, Pixel}; + /// # let buffer: Buffer<'_> = unimplemented!(); /// // Only reverses iteration order, x and y are still relative to the top-left corner. /// for (x, y, pixel) in buffer.pixels_iter().rev() { /// if x <= 100 && y <= 100 { - /// *pixel = 0x00ff0000; + /// *pixel = Pixel::new_rgb(0xff, 0x00, 0x00); /// } /// } /// ``` #[inline] - pub fn pixels_iter(&mut self) -> impl DoubleEndedIterator { + pub fn pixels_iter(&mut self) -> impl DoubleEndedIterator { self.pixel_rows().enumerate().flat_map(|(y, pixels)| { pixels .iter_mut() diff --git a/src/pixel.rs b/src/pixel.rs new file mode 100644 index 00000000..6d0c6242 --- /dev/null +++ b/src/pixel.rs @@ -0,0 +1,111 @@ +/// A RGBA 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. +/// +/// # 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))] // Help the compiler to see that this is a u32 +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +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 { + /// Create 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 } + } + + /// Create 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(madsmtm): 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 5c29071e..a21e2cac 100644 --- a/src/util.rs +++ b/src/util.rs @@ -6,7 +6,7 @@ use std::fmt; use std::num::NonZeroU32; use std::ops; -use crate::Rect; +use crate::{Pixel, Rect}; /// Calculates the smallest `Rect` necessary to represent all damaged `Rect`s. pub(crate) fn union_damage(damage: &[Rect]) -> Option { @@ -45,7 +45,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 { @@ -54,7 +54,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 a1cd8ad69d879df75c9c112e9603024c0f50e274 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 19 Jan 2026 17:26:54 +0100 Subject: [PATCH 3/4] Make alpha component private --- src/pixel.rs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/pixel.rs b/src/pixel.rs index 6d0c6242..c78be6ec 100644 --- a/src/pixel.rs +++ b/src/pixel.rs @@ -19,10 +19,10 @@ /// 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); +/// # // 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. @@ -48,7 +48,7 @@ /// 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))] // Help the compiler to see that this is a u32 @@ -65,18 +65,15 @@ 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 Pixel { /// Create a new pixel from a red, a green and a blue component. /// - /// The alpha component is set to opaque. - /// /// # Example /// /// ``` @@ -86,13 +83,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 } } /// Create a new pixel from a blue, a green and a red component. /// - /// The alpha component is set to opaque. - /// /// # Example /// /// ``` @@ -102,7 +98,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(madsmtm): Once we have transparency, add `new_rgba` and `new_bgra` methods. From 692d03cf3e0618bc86c546b85520c1c07a03976c Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Fri, 23 Jan 2026 19:47:57 +0100 Subject: [PATCH 4/4] 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 | 2 + src/backends/android.rs | 4 +- src/format.rs | 37 +++++++++++++++++ src/lib.rs | 2 + src/pixel.rs | 92 ++++++++++++++++++++++++++++++++++------- 5 files changed, 121 insertions(+), 16 deletions(-) create mode 100644 src/format.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d9b634e..fb96ae2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,13 @@ # Unreleased +- Added `PixelFormat` enum. - Added `Buffer::pixels()` for accessing the buffer's pixel data. - 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:** Removed `Deref[Mut]` implementation on `Buffer<'_>`. Use `Buffer::pixels()` instead. - **Breaking:** Add `Pixel` struct, and use that for pixels instead of `u32`. +- **Breaking:** The pixel format is now target-dependent. Access `PixelFormat::default()` to see which format is used on the current platform. - **Breaking:** Removed unintentional Cargo features for Softbuffer's optional dependencies. # 0.4.7 diff --git a/src/backends/android.rs b/src/backends/android.rs index 81b675fb..18916772 100644 --- a/src/backends/android.rs +++ b/src/backends/android.rs @@ -156,8 +156,8 @@ impl BufferInterface for BufferImpl<'_> { 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 and once the pixel - // structure is of the RGBA format. + // 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/format.rs b/src/format.rs new file mode 100644 index 00000000..b8374c81 --- /dev/null +++ b/src/format.rs @@ -0,0 +1,37 @@ +/// A pixel format that Softbuffer may use. +/// +/// # Default +/// +/// The [`Default::default`] implementation returns the pixel format that Softbuffer uses for the +/// current target platform. +/// +/// Currently, this is [`BGRX`][Self::Bgrx] on all platforms except WebAssembly and Android, where +/// it is [`RGBX`][Self::Rgbx], since the API on these platforms does not support BGRX. +/// +/// 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 the documentation for [`Pixel`][crate::Pixel] for examples. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +pub enum PixelFormat { + /// The pixel format is `RGBX` (red, green, blue, unset). + /// + /// This is currently the default on macOS/iOS, KMS/DRM, Orbital, Wayland, Windows and X11. + #[cfg_attr(not(any(target_family = "wasm", target_os = "android")), default)] + Bgrx, + /// The pixel format is `BGRX` (blue, green, red, unset). + /// + /// This is currently the default on Android and Web. + #[cfg_attr(any(target_family = "wasm", target_os = "android"), default)] + Rgbx, + // Intentionally exhaustive for now. +} + +impl PixelFormat { + /// Check whether the given pixel format is the default format that Softbuffer uses. + #[inline] + pub fn is_default(self) -> bool { + self == Self::default() + } +} diff --git a/src/lib.rs b/src/lib.rs index a93d05f5..be8169a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ mod backend_dispatch; mod backend_interface; mod backends; mod error; +mod format; mod pixel; mod util; @@ -26,6 +27,7 @@ use self::backend_interface::*; pub use self::backends::web::SurfaceExtWeb; use self::error::InitError; pub use self::error::SoftBufferError; +pub use self::format::PixelFormat; pub use self::pixel::Pixel; /// An instance of this struct contains the platform-specific data that must be managed in order to diff --git a/src/pixel.rs b/src/pixel.rs index c78be6ec..5c3949ad 100644 --- a/src/pixel.rs +++ b/src/pixel.rs @@ -2,19 +2,20 @@ /// /// # 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 4 `u8`'s laid out in the order defined by [`PixelFormat::default()`]. /// -/// 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. +/// 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`. +/// +/// [`PixelFormat::default()`]: crate::PixelFormat#default /// /// # 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); @@ -28,36 +29,96 @@ /// Convert a pixel to an array of `u8`s. /// /// ``` -/// # use softbuffer::Pixel; -/// # +/// use softbuffer::{Pixel, PixelFormat}; +/// /// 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 PixelFormat::default() { +/// PixelFormat::Bgrx => assert_eq!(red[2], 255), +/// PixelFormat::Rgbx => assert_eq!(red[0], 255), +/// } /// ``` /// /// Convert a pixel to an `u32`. /// /// ``` -/// # use softbuffer::Pixel; -/// # +/// use softbuffer::{Pixel, PixelFormat}; +/// /// 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 PixelFormat::default() { +/// PixelFormat::Bgrx => assert_eq!(red, u32::from_ne_bytes([0x00, 0x00, 0xff, 0x00])), +/// PixelFormat::Rgbx => assert_eq!(red, u32::from_ne_bytes([0xff, 0x00, 0x00, 0x00])), +/// } +/// ``` +/// +/// Render to a slice of `[u8; 4]`s. This might be useful for library authors that want to provide a +/// simple API that provides RGBX rendering. +/// +/// ```no_run +/// use softbuffer::{Pixel, PixelFormat}; +/// +/// // Assume the user controls the following rendering function: +/// fn render(pixels: &mut [[u8; 4]], width: u32, height: u32) { +/// pixels.fill([0xff, 0xff, 0x00, 0x00]); // Yellow in RGBX +/// } +/// +/// // Then we'd convert pixel data as follows: +/// +/// # let buffer: softbuffer::Buffer<'_> = todo!(); +/// # #[cfg(false)] +/// let buffer = surface.buffer_mut(); +/// +/// let width = buffer.width().get(); +/// let height = buffer.height().get(); +/// +/// // Use fast, zero-copy implementation when possible, and fall back to slower version when not. +/// if PixelFormat::Rgbx.is_default() { +/// // SAFETY: `Pixel` can be reinterpreted as `[u8; 4]`. +/// let pixels = unsafe { std::mem::transmute::<&mut [Pixel], &mut [[u8; 4]]>(buffer.pixels()) }; +/// // CORRECTNESS: We just checked that the format is RGBX. +/// render(pixels, width, height); +/// } else { +/// // Render into temporary buffer. +/// let mut temporary = vec![[0; 4]; width as usize * height as usize]; +/// render(&mut temporary, width, height); +/// +/// // And copy from temporary buffer to actual pixel data. +/// for (tmp, actual) in temporary.iter_mut().zip(buffer.pixels()) { +/// *actual = Pixel::new_rgb(tmp[0], tmp[1], tmp[2]); +/// } +/// } +/// +/// buffer.present(); /// ``` #[repr(C)] #[repr(align(4))] // Help the compiler to see that this is a u32 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] pub struct Pixel { + #[cfg_attr(docsrs, doc(auto_cfg = false))] + #[cfg(any(doc, target_family = "wasm", target_os = "android"))] + /// The red component. + pub r: u8, + #[cfg_attr(docsrs, doc(auto_cfg = false))] + #[cfg(any(doc, target_family = "wasm", target_os = "android"))] + /// The green component. + pub g: u8, + #[cfg_attr(docsrs, doc(auto_cfg = false))] + #[cfg(any(doc, target_family = "wasm", target_os = "android"))] + /// The blue component. + pub b: u8, + + #[cfg(not(any(doc, target_family = "wasm", target_os = "android")))] /// The blue component. pub b: u8, + #[cfg(not(any(doc, target_family = "wasm", target_os = "android")))] /// The green component. pub g: u8, + #[cfg(not(any(doc, target_family = "wasm", target_os = "android")))] /// The red component. pub r: u8, @@ -106,3 +167,6 @@ 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.