Skip to content

Commit c33b777

Browse files
committed
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.
1 parent eeb2c95 commit c33b777

File tree

5 files changed

+117
-16
lines changed

5 files changed

+117
-16
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
# Unreleased
22

3+
- Added `PixelFormat` enum.
34
- Added `Buffer::pixels()` for accessing the buffer's pixel data.
45
- Added `Buffer::pixel_rows()` for iterating over rows of the buffer data.
56
- Added `Buffer::pixels_iter()` for iterating over each pixel with its associated `x`/`y` coordinate.
67
- **Breaking:** Removed generic type parameters `D` and `W` from `Buffer<'_>` struct.
78
- **Breaking:** Removed `Deref[Mut]` implementation on `Buffer<'_>`. Use `Buffer::pixels()` instead.
89
- **Breaking:** Add `Pixel` struct, and use that for pixels instead of `u32`.
10+
- **Breaking:** The pixel format is now target-dependent. Access `PixelFormat::default()` to see which format is used on the current platform.
911
- **Breaking:** Removed unintentional Cargo features for Softbuffer's optional dependencies.
1012
- **Breaking:** Disable the DRM/KMS backend by default.
1113
- **Breaking:** Removed `DamageOutOfRange` error case. If the damage value is greater than the backend supports, it is instead clamped to an appropriate value.

src/backends/android.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,8 @@ impl BufferInterface for BufferImpl<'_> {
166166
assert_eq!(output.len(), input.len() * 4);
167167

168168
// Write RGB(A) to the output.
169-
// TODO: Use `slice::write_copy_of_slice` once stable and in MSRV and once the pixel
170-
// structure is of the RGBA format.
169+
// TODO: Use `slice::write_copy_of_slice` once stable and in MSRV.
170+
// TODO(madsmtm): Verify that this compiles down to an efficient copy.
171171
for (i, pixel) in input.iter().enumerate() {
172172
output[i * 4].write(pixel.r);
173173
output[i * 4 + 1].write(pixel.g);

src/format.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/// A pixel format that Softbuffer may use.
2+
///
3+
/// # Default
4+
///
5+
/// The [`Default::default`] implementation returns the pixel format that Softbuffer uses for the
6+
/// current target platform.
7+
///
8+
/// Currently, this is [`BGRX`][Self::Bgrx] on all platforms except WebAssembly and Android, where
9+
/// it is [`RGBX`][Self::Rgbx], since the API on these platforms does not support BGRX.
10+
///
11+
/// The format for a given platform may change in a non-breaking release if found to be more
12+
/// performant.
13+
///
14+
/// This distinction should only be relevant if you're bitcasting `Pixel` to/from a `u32`, to e.g.
15+
/// avoid unnecessary copying, see the documentation for [`Pixel`][crate::Pixel] for examples.
16+
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
17+
pub enum PixelFormat {
18+
/// The pixel format is `RGBX` (red, green, blue, unset).
19+
///
20+
/// This is currently the default on macOS/iOS, KMS/DRM, Orbital, Wayland, Windows and X11.
21+
#[cfg_attr(not(any(target_family = "wasm", target_os = "android")), default)]
22+
Bgrx,
23+
/// The pixel format is `BGRX` (blue, green, red, unset).
24+
///
25+
/// This is currently the default on Android and Web.
26+
#[cfg_attr(any(target_family = "wasm", target_os = "android"), default)]
27+
Rgbx,
28+
// Intentionally exhaustive for now.
29+
}
30+
31+
impl PixelFormat {
32+
/// Check whether the given pixel format is the default format that Softbuffer uses.
33+
#[inline]
34+
pub fn is_default(self) -> bool {
35+
self == Self::default()
36+
}
37+
}

src/lib.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod backend_dispatch;
1010
mod backend_interface;
1111
mod backends;
1212
mod error;
13+
mod format;
1314
mod pixel;
1415
mod util;
1516

@@ -26,6 +27,7 @@ use self::backend_interface::*;
2627
pub use self::backends::web::SurfaceExtWeb;
2728
use self::error::InitError;
2829
pub use self::error::SoftBufferError;
30+
pub use self::format::PixelFormat;
2931
pub use self::pixel::Pixel;
3032

3133
/// An instance of this struct contains the platform-specific data that must be managed in order to
@@ -309,6 +311,46 @@ impl Buffer<'_> {
309311
/// # let buffer: Buffer<'_> = unimplemented!();
310312
/// buffer.pixels().fill(Pixel::new_rgb(0xff, 0x00, 0x00));
311313
/// ```
314+
///
315+
/// Render to a slice of `[u8; 4]`s. This might be useful for library authors that want to
316+
/// provide a simple API that provides RGBX rendering.
317+
///
318+
/// ```no_run
319+
/// use softbuffer::{Pixel, PixelFormat};
320+
///
321+
/// // Assume the user controls the following rendering function:
322+
/// fn render(pixels: &mut [[u8; 4]], width: u32, height: u32) {
323+
/// pixels.fill([0xff, 0xff, 0x00, 0x00]); // Yellow in RGBX
324+
/// }
325+
///
326+
/// // Then we'd convert pixel data as follows:
327+
///
328+
/// # let buffer: softbuffer::Buffer<'_> = todo!();
329+
/// # #[cfg(false)]
330+
/// let buffer = surface.buffer_mut();
331+
///
332+
/// let width = buffer.width().get();
333+
/// let height = buffer.height().get();
334+
///
335+
/// // Use fast, zero-copy implementation when possible, and fall back to slower version when not.
336+
/// if PixelFormat::Rgbx.is_default() {
337+
/// // SAFETY: `Pixel` can be reinterpreted as `[u8; 4]`.
338+
/// let pixels = unsafe { std::mem::transmute::<&mut [Pixel], &mut [[u8; 4]]>(buffer.pixels()) };
339+
/// // CORRECTNESS: We just checked that the format is RGBX.
340+
/// render(pixels, width, height);
341+
/// } else {
342+
/// // Render into temporary buffer.
343+
/// let mut temporary = vec![[0; 4]; width as usize * height as usize];
344+
/// render(&mut temporary, width, height);
345+
///
346+
/// // And copy from temporary buffer to actual pixel data.
347+
/// for (tmp, actual) in temporary.iter_mut().zip(buffer.pixels()) {
348+
/// *actual = Pixel::new_rgb(tmp[0], tmp[1], tmp[2]);
349+
/// }
350+
/// }
351+
///
352+
/// buffer.present();
353+
/// ```
312354
pub fn pixels(&mut self) -> &mut [Pixel] {
313355
self.buffer_impl.pixels_mut()
314356
}

src/pixel.rs

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@
22
///
33
/// # Representation
44
///
5-
/// This is a set of `u8`'s in the order BGRX (first component blue, second green, third red and
6-
/// last unset).
5+
/// This is a set of 4 `u8`'s laid out in the order defined by [`PixelFormat::default()`].
76
///
8-
/// If you're familiar with [the `rgb` crate](https://docs.rs/rgb/), you can treat this mostly as-if
9-
/// it is `rgb::Bgra<u8>`, except that this type has an alignment of `4` for performance reasons.
7+
/// This type has an alignment of `4` for performance reasons, as that makes copies faster on many
8+
/// platforms, and makes this type have the same in-memory representation as `u32`.
9+
///
10+
/// [`PixelFormat::default()`]: crate::PixelFormat#default
1011
///
1112
/// # Example
1213
///
1314
/// Construct a new pixel.
1415
///
1516
/// ```
16-
/// # use softbuffer::Pixel;
17-
/// #
17+
/// use softbuffer::Pixel;
18+
///
1819
/// let red = Pixel::new_rgb(0xff, 0x80, 0);
1920
/// assert_eq!(red.r, 255);
2021
/// assert_eq!(red.g, 128);
@@ -28,36 +29,52 @@
2829
/// Convert a pixel to an array of `u8`s.
2930
///
3031
/// ```
31-
/// # use softbuffer::Pixel;
32-
/// #
32+
/// use softbuffer::{Pixel, PixelFormat};
33+
///
3334
/// let red = Pixel::new_rgb(0xff, 0, 0);
3435
/// // SAFETY: `Pixel` can be reinterpreted as `[u8; 4]`.
3536
/// let red = unsafe { core::mem::transmute::<Pixel, [u8; 4]>(red) };
3637
///
37-
/// // BGRX
38-
/// assert_eq!(red[2], 255);
38+
/// match PixelFormat::default() {
39+
/// PixelFormat::Bgrx => assert_eq!(red[2], 255),
40+
/// PixelFormat::Rgbx => assert_eq!(red[0], 255),
41+
/// }
3942
/// ```
4043
///
4144
/// Convert a pixel to an `u32`.
4245
///
4346
/// ```
44-
/// # use softbuffer::Pixel;
45-
/// #
47+
/// use softbuffer::{Pixel, PixelFormat};
48+
///
4649
/// let red = Pixel::new_rgb(0xff, 0, 0);
4750
/// // SAFETY: `Pixel` can be reinterpreted as `u32`.
4851
/// let red = unsafe { core::mem::transmute::<Pixel, u32>(red) };
4952
///
50-
/// // BGRX
51-
/// assert_eq!(red, u32::from_le_bytes([0x00, 0x00, 0xff, 0x00]));
53+
/// match PixelFormat::default() {
54+
/// PixelFormat::Bgrx => assert_eq!(red, u32::from_ne_bytes([0x00, 0x00, 0xff, 0x00])),
55+
/// PixelFormat::Rgbx => assert_eq!(red, u32::from_ne_bytes([0xff, 0x00, 0x00, 0x00])),
56+
/// }
5257
/// ```
5358
#[repr(C)]
5459
#[repr(align(4))] // Help the compiler to see that this is a u32
5560
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
5661
pub struct Pixel {
62+
#[cfg_attr(docsrs, doc(auto_cfg = false))]
63+
#[cfg(any(doc, target_family = "wasm", target_os = "android"))]
64+
/// The red component.
65+
pub r: u8,
66+
#[cfg(not(any(doc, target_family = "wasm", target_os = "android")))]
5767
/// The blue component.
5868
pub b: u8,
69+
5970
/// The green component.
6071
pub g: u8,
72+
73+
#[cfg_attr(docsrs, doc(auto_cfg = false))]
74+
#[cfg(any(doc, target_family = "wasm", target_os = "android"))]
75+
/// The blue component.
76+
pub b: u8,
77+
#[cfg(not(any(doc, target_family = "wasm", target_os = "android")))]
6178
/// The red component.
6279
pub r: u8,
6380

@@ -106,3 +123,6 @@ impl Pixel {
106123
}
107124

108125
// TODO: Implement `Add`/`Mul`/similar `std::ops` like `rgb` does?
126+
127+
// TODO: Implement `zerocopy` / `bytemuck` traits behind a feature flag?
128+
// May not be that useful, since the representation is platform-specific.

0 commit comments

Comments
 (0)