Skip to content

Commit f91b515

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 5e29659 commit f91b515

File tree

5 files changed

+121
-16
lines changed

5 files changed

+121
-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

1012
# 0.4.7
1113

src/backends/android.rs

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

158158
// Write RGB(A) to the output.
159-
// TODO: Use `slice::write_copy_of_slice` once stable and in MSRV and once the pixel
160-
// structure is of the RGBA format.
159+
// TODO: Use `slice::write_copy_of_slice` once stable and in MSRV.
160+
// TODO(madsmtm): Verify that this compiles down to an efficient copy.
161161
for (i, pixel) in input.iter().enumerate() {
162162
output[i * 4].write(pixel.r);
163163
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(any(target_family = "wasm", target_os = "android"), default)]
22+
Rgbx,
23+
/// The pixel format is `BGRX` (blue, green, red, unset).
24+
///
25+
/// This is currently the default on Android and Web.
26+
#[cfg_attr(not(any(target_family = "wasm", target_os = "android")), default)]
27+
Bgrx,
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: 2 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

src/pixel.rs

Lines changed: 78 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,96 @@
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::Rgbx => assert_eq!(red[0], 255),
40+
/// PixelFormat::Bgrx => assert_eq!(red[2], 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::Rgbx => assert_eq!(red, u32::from_le_bytes([0xff, 0x00, 0x00, 0x00])),
55+
/// PixelFormat::Bgrx => assert_eq!(red, u32::from_le_bytes([0x00, 0x00, 0xff, 0x00])),
56+
/// }
57+
/// ```
58+
///
59+
/// Convert a slice of pixels to a slice of `u32`s. This might be useful for library authors that
60+
/// want to keep rendering using BGRX.
61+
///
62+
/// ```
63+
/// // Assume the user controls the following rendering function:
64+
/// fn render(pixels: &mut [u32]) {
65+
/// pixels.fill(u32::from_le_bytes([0xff, 0xff, 0x00, 0x00])); // Yellow in BGRX
66+
/// }
67+
///
68+
/// // Then we'd convert pixel data as follows:
69+
/// use softbuffer::{Pixel, PixelFormat};
70+
///
71+
/// # let mut pixel_data = [Pixel::new_rgb(0, 0xff, 0xff)];
72+
/// let pixels: &mut [Pixel];
73+
/// # pixels = &mut pixel_data;
74+
///
75+
/// if PixelFormat::Bgrx.is_default() {
76+
/// // Fast implementation when the pixel format is BGRX.
77+
///
78+
/// // SAFETY: `Pixel` can be reinterpreted as `u32`.
79+
/// let pixels = unsafe { std::mem::transmute::<&mut [Pixel], &mut [u32]>(pixels) };
80+
/// // CORRECTNESS: We just checked that the format is BGRX.
81+
/// render(pixels);
82+
/// } else {
83+
/// // Fall back to slower implementation when the format is RGBX.
84+
///
85+
/// // Render into temporary buffer.
86+
/// let mut buffer = vec![0u32; pixels.len()];
87+
/// render(&mut buffer);
88+
///
89+
/// // And copy from temporary buffer to actual pixel data.
90+
/// for (old, new) in pixels.iter_mut().zip(buffer) {
91+
/// let new = new.to_le_bytes();
92+
/// *old = Pixel::new_bgr(new[0], new[1], new[2]);
93+
/// }
94+
/// }
95+
/// #
96+
/// # assert_eq!(pixel_data, [Pixel::new_rgb(0, 0xff, 0xff)]);
5297
/// ```
5398
#[repr(C)]
5499
#[repr(align(4))] // Help the compiler to see that this is a u32
55100
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
56101
pub struct Pixel {
102+
#[cfg_attr(docsrs, doc(auto_cfg = false))]
103+
#[cfg(any(doc, target_family = "wasm", target_os = "android"))]
104+
/// The red component.
105+
pub r: u8,
106+
#[cfg_attr(docsrs, doc(auto_cfg = false))]
107+
#[cfg(any(doc, target_family = "wasm", target_os = "android"))]
108+
/// The green component.
109+
pub g: u8,
110+
#[cfg_attr(docsrs, doc(auto_cfg = false))]
111+
#[cfg(any(doc, target_family = "wasm", target_os = "android"))]
112+
/// The blue component.
113+
pub b: u8,
114+
115+
#[cfg(not(any(doc, target_family = "wasm", target_os = "android")))]
57116
/// The blue component.
58117
pub b: u8,
118+
#[cfg(not(any(doc, target_family = "wasm", target_os = "android")))]
59119
/// The green component.
60120
pub g: u8,
121+
#[cfg(not(any(doc, target_family = "wasm", target_os = "android")))]
61122
/// The red component.
62123
pub r: u8,
63124

@@ -106,3 +167,6 @@ impl Pixel {
106167
}
107168

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

0 commit comments

Comments
 (0)