From b70ce60bf05593f510f3bc9180267f2571cf5a52 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 23 Nov 2025 21:28:35 +0100 Subject: [PATCH 1/2] Add Pixel struct This is much more intuitive to use than manually bit-packing into a u32. The format is RGBX on WASM and Android and BGRX elsewhere. This should allow avoiding needless copying on these platforms. --- 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 | 25 ++--- src/backends/cg.rs | 18 ++-- src/backends/kms.rs | 20 +++- src/backends/orbital.rs | 26 +++-- src/backends/wayland/buffer.rs | 7 +- src/backends/wayland/mod.rs | 8 +- src/backends/web.rs | 26 +++-- src/backends/win32.rs | 18 ++-- src/backends/x11.rs | 67 ++++++++---- src/lib.rs | 40 +++----- src/pixel.rs | 146 +++++++++++++++++++++++++++ 25 files changed, 346 insertions(+), 165 deletions(-) create mode 100644 src/pixel.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index e806e037..99c2ed0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- **Breaking:** Add `Pixel` struct, and use that for pixels instead of `u32`. + - Update to `objc2` 0.6.0. - Bump MSRV to Rust 1.71. - Make `Context` cloneable. diff --git a/Cargo.toml b/Cargo.toml index 76f2eeca..9a4f519b 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(all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))))'.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 85faaaee..936b12d8 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 eacc58ea..65ac6d9f 100644 --- a/benches/buffer_mut.rs +++ b/benches/buffer_mut.rs @@ -12,7 +12,7 @@ fn buffer_mut(c: &mut Criterion) { #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] { 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 cfc41747..a0bec669 100644 --- a/examples/animation.rs +++ b/examples/animation.rs @@ -1,5 +1,6 @@ #[cfg(not(target_arch = "wasm32"))] 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 18bd61ee..635fe7a4 100644 --- a/examples/drm.rs +++ b/examples/drm.rs @@ -6,7 +6,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}; @@ -150,7 +150,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 { @@ -158,8 +158,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 f07fce2d..e70d6bd5 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 53efc5d3..c5660792 100644 --- a/examples/libxcb.rs +++ b/examples/libxcb.rs @@ -16,8 +16,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"); @@ -113,7 +111,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 b6558666..50dc3d3e 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 044ae391..95d3c232 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 36ae2b2c..974444f5 100644 --- a/examples/winit_multithread.rs +++ b/examples/winit_multithread.rs @@ -42,11 +42,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 ec74c652..2524fee4 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 5a34a7c5..37cc4ead 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::num::NonZeroU32; @@ -107,7 +107,7 @@ macro_rules! make_dispatch { } } - fn fetch(&mut self) -> Result, SoftBufferError> { + fn fetch(&mut self) -> Result, SoftBufferError> { match self { $( $(#[$attr])* @@ -146,7 +146,7 @@ macro_rules! make_dispatch { } #[inline] - fn pixels(&self) -> &[u32] { + fn pixels(&self) -> &[Pixel] { match self { $( $(#[$attr])* @@ -156,7 +156,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 b6a56661..ca2b58bf 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 3dab9ace..3c44a38d 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::{BufferInterface, Rect, SoftBufferError, SurfaceInterface}; +use crate::{BufferInterface, Pixel, Rect, SoftBufferError, SurfaceInterface}; /// The handle to a window for software buffering. pub struct AndroidImpl { @@ -98,7 +98,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,14 +109,14 @@ 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) } } pub struct BufferImpl<'a, D: ?Sized, W> { native_window_buffer: NativeWindowBufferLockGuard<'a>, - buffer: Vec, + buffer: Vec, marker: PhantomData<(&'a D, &'a W)>, } @@ -132,12 +133,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 } @@ -159,13 +160,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 6e8999d5..c4665935 100644 --- a/src/backends/cg.rs +++ b/src/backends/cg.rs @@ -1,6 +1,6 @@ //! Softbuffer implementation using CoreGraphics. -use crate::backend_interface::*; use crate::error::InitError; +use crate::{backend_interface::*, Pixel}; use crate::{Rect, SoftBufferError}; use objc2::rc::Retained; use objc2::runtime::{AnyObject, Bool}; @@ -258,7 +258,7 @@ impl SurfaceInterface for CGImpl< fn buffer_mut(&mut self) -> Result, SoftBufferError> { Ok(BufferImpl { - buffer: vec![0; self.width * self.height].into(), + buffer: vec![Pixel::default(); self.width * self.height].into(), imp: self, }) } @@ -266,7 +266,7 @@ impl SurfaceInterface for CGImpl< pub struct BufferImpl<'a, D, W> { imp: &'a mut CGImpl, - buffer: Box<[u32]>, + buffer: Box<[Pixel]>, } impl BufferInterface for BufferImpl<'_, D, W> { @@ -279,12 +279,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 } @@ -298,15 +298,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); + let len = self.buffer.len() * size_of::(); + let buffer: *mut [Pixel] = Box::into_raw(self.buffer); // Convert slice pointer to thin pointer. let data_ptr = buffer.cast::(); diff --git a/src/backends/kms.rs b/src/backends/kms.rs index 3008e8c2..636065e2 100644 --- a/src/backends/kms.rs +++ b/src/backends/kms.rs @@ -13,12 +13,15 @@ use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, Raw use std::collections::HashSet; 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 { @@ -302,13 +305,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 cea6feb0..8d59081b 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::{Rect, SoftBufferError}; +use crate::{Pixel, Rect, SoftBufferError}; struct OrbitalMap { address: usize, @@ -37,12 +37,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) } } } @@ -93,7 +93,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(); @@ -103,7 +103,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 @@ -174,7 +175,10 @@ impl SurfaceInterface for Orbital .expect("failed to map orbital window"), ) } else { - Pixels::Buffer(vec![0; self.width as usize * self.height as usize]) + Pixels::Buffer(vec![ + Pixel::default(); + self.width as usize * self.height as usize + ]) }; Ok(BufferImpl { imp: self, pixels }) } @@ -182,7 +186,7 @@ impl SurfaceInterface for Orbital enum Pixels { Mapping(OrbitalMap), - Buffer(Vec), + Buffer(Vec), } pub struct BufferImpl<'a, D, W> { @@ -200,7 +204,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, @@ -208,7 +212,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 407dd742..49399b09 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 { @@ -160,8 +161,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 d497484a..0fbb6ddc 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::{ @@ -262,7 +262,7 @@ impl Drop for WaylandImpl { } 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, @@ -278,12 +278,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 3b8efa19..0e8cc788 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; @@ -48,7 +48,7 @@ pub struct WebImpl { canvas: Canvas, /// The buffer that we're drawing to. - buffer: Vec, + buffer: Vec, /// Buffer has been presented. buffer_presented: bool, @@ -133,7 +133,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) @@ -143,8 +143,8 @@ 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]) + // TODO: Verify that this optimizes away to nothing (and if it doesn't, unsafely cast instead). + .flat_map(|pixel| [pixel.r, pixel.g, pixel.b, pixel.a]) .collect(); debug_assert_eq!( @@ -252,7 +252,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)); @@ -265,7 +266,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()`"); @@ -281,7 +282,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()) } } @@ -392,11 +398,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 3436f556..46181f0a 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; @@ -28,7 +28,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, @@ -85,13 +85,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, ) @@ -114,7 +114,7 @@ impl Buffer { } #[inline] - fn pixels(&self) -> &[u32] { + fn pixels(&self) -> &[Pixel] { unsafe { slice::from_raw_parts( self.pixels.as_ptr(), @@ -124,7 +124,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(), @@ -276,7 +276,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) } } @@ -293,12 +293,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 f1429711..86cc5a50 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::{Rect, SoftBufferError}; +use crate::{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, @@ -160,7 +161,7 @@ enum Buffer { Shm(ShmBuffer), /// A normal buffer that we send over the wire. - Wire(Vec), + Wire(Vec), } struct ShmBuffer { @@ -346,7 +347,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 @@ -371,8 +372,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( @@ -397,13 +405,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() } } @@ -431,6 +439,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( @@ -443,7 +459,7 @@ impl BufferInterface 0, 0, imp.depth, - bytemuck::cast_slice(wire), + data, ) .map(|c| c.ignore_error()) .push_err() @@ -532,7 +548,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(()) } } @@ -554,7 +573,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, @@ -567,7 +586,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, @@ -608,13 +627,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. @@ -629,13 +655,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. @@ -1009,6 +1042,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 03246c04..a8c493c2 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}; @@ -122,7 +124,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() } @@ -161,40 +163,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 /// @@ -271,17 +261,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 00000000..e4be2ba6 --- /dev/null +++ b/src/pixel.rs @@ -0,0 +1,146 @@ +/// A pixel. +/// +/// # Representation +/// +/// This is a set of `u8`'s in the order BGRX (first component blue, second green, third red and +/// last unset), except on WebAssembly and Android targets, there it is RGBX, since the API on these +/// platforms only support that format. This distinction should only be relevant if you're +/// bitcasting `Pixel` to/from a `u32`. +/// +/// If you're familiar with [the `rgb` crate](https://docs.rs/rgb/), you can treat this mostly as-if +/// it was defined as follows: +/// +/// ```ignore +/// #[cfg(any(target_family = "wasm", target_os = "android"))] +/// type Pixel = rgb::Rgba; +/// #[cfg(not(any(target_family = "wasm", target_os = "android")))] +/// type Pixel = rgb::Bgra; +/// ``` +/// +/// # 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) }; +/// +/// if cfg!(any(target_family = "wasm", target_os = "android")) { +/// // RGBX +/// assert_eq!(red[0], 255); +/// } else { +/// // 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) }; +/// +/// if cfg!(any(target_family = "wasm", target_os = "android")) { +/// // RGBX +/// assert_eq!(red, u32::from_ne_bytes([0xff, 0x00, 0x00, 0xff])); +/// } else { +/// // BGRX +/// assert_eq!(red, u32::from_ne_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 { + #[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, + + /// 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? + +// TODO: Implement `zerocopy` traits behind a feature flag? +// May not be that useful, since the representation is platform-specific. From 7c3eba9d91bebb1b232fffab4e1bde05c7303c27 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 23 Nov 2025 17:22:44 +0100 Subject: [PATCH 2/2] Make alpha component private --- src/pixel.rs | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/pixel.rs b/src/pixel.rs index e4be2ba6..b21ca885 100644 --- a/src/pixel.rs +++ b/src/pixel.rs @@ -28,10 +28,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. @@ -63,15 +59,15 @@ /// /// if cfg!(any(target_family = "wasm", target_os = "android")) { /// // RGBX -/// assert_eq!(red, u32::from_ne_bytes([0xff, 0x00, 0x00, 0xff])); +/// assert_eq!(red, u32::from_ne_bytes([0xff, 0x00, 0x00, 0x00])); /// } else { /// // BGRX -/// assert_eq!(red, u32::from_ne_bytes([0x00, 0x00, 0xff, 0xff])); +/// assert_eq!(red, u32::from_ne_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 { #[cfg(any(target_family = "wasm", target_os = "android"))] /// The red component. @@ -97,17 +93,28 @@ 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 { + r: 0, + g: 0, + b: 0, + a: 0xff, + } + } } impl Pixel { /// Creates a new pixel from a red, a green and a blue component. /// - /// The alpha component is set to opaque. + /// The pixel is opaque. /// /// # Example /// @@ -123,7 +130,7 @@ impl Pixel { /// Creates a new pixel from a blue, a green and a red component. /// - /// The alpha component is set to opaque. + /// The pixel is opaque. /// /// # Example ///