Skip to content

Commit ff136ea

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 ff136ea

File tree

5 files changed

+116
-17
lines changed

5 files changed

+116
-17
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 `DEFAULT_PIXEL_FORMAT` to see which format to use 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: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// A pixel format that `softbuffer` may use.
2+
///
3+
/// See [`DEFAULT_PIXEL_FORMAT`][crate::DEFAULT_PIXEL_FORMAT].
4+
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
5+
pub enum PixelFormat {
6+
/// The pixel format is `RGBX` (red, green, blue, unset).
7+
Rgbx,
8+
/// The pixel format is `BGRX` (blue, green, red, unset).
9+
Bgrx,
10+
// Intentionally exhaustive for now.
11+
}

src/lib.rs

Lines changed: 3 additions & 1 deletion
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,7 +27,8 @@ use self::backend_interface::*;
2627
pub use self::backends::web::SurfaceExtWeb;
2728
use self::error::InitError;
2829
pub use self::error::SoftBufferError;
29-
pub use self::pixel::Pixel;
30+
pub use self::format::PixelFormat;
31+
pub use self::pixel::{Pixel, DEFAULT_PIXEL_FORMAT};
3032

3133
/// An instance of this struct contains the platform-specific data that must be managed in order to
3234
/// write to a window on that platform.

src/pixel.rs

Lines changed: 98 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
1+
use crate::PixelFormat;
2+
13
/// A RGBA pixel.
24
///
35
/// # Representation
46
///
5-
/// This is a set of `u8`'s in the order BGRX (first component blue, second green, third red and
6-
/// last unset).
7+
/// This is a set of 4 `u8`'s laid out in the order defined by [`DEFAULT_PIXEL_FORMAT`].
8+
///
9+
/// This type has an alignment of `4` for performance reasons, as that makes copies faster on many
10+
/// platforms, and makes this type have the same in-memory representation as `u32`.
711
///
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.
12+
/// [`DEFAULT_PIXEL_FORMAT`]: crate::DEFAULT_PIXEL_FORMAT
1013
///
1114
/// # Example
1215
///
1316
/// Construct a new pixel.
1417
///
1518
/// ```
16-
/// # use softbuffer::Pixel;
17-
/// #
19+
/// use softbuffer::Pixel;
20+
///
1821
/// let red = Pixel::new_rgb(0xff, 0x80, 0);
1922
/// assert_eq!(red.r, 255);
2023
/// assert_eq!(red.g, 128);
@@ -28,36 +31,96 @@
2831
/// Convert a pixel to an array of `u8`s.
2932
///
3033
/// ```
31-
/// # use softbuffer::Pixel;
32-
/// #
34+
/// use softbuffer::{Pixel, PixelFormat, DEFAULT_PIXEL_FORMAT};
35+
///
3336
/// let red = Pixel::new_rgb(0xff, 0, 0);
3437
/// // SAFETY: `Pixel` can be reinterpreted as `[u8; 4]`.
3538
/// let red = unsafe { core::mem::transmute::<Pixel, [u8; 4]>(red) };
3639
///
37-
/// // BGRX
38-
/// assert_eq!(red[2], 255);
40+
/// match DEFAULT_PIXEL_FORMAT {
41+
/// PixelFormat::Rgbx => assert_eq!(red[0], 255),
42+
/// PixelFormat::Bgrx => assert_eq!(red[2], 255),
43+
/// }
3944
/// ```
4045
///
4146
/// Convert a pixel to an `u32`.
4247
///
4348
/// ```
44-
/// # use softbuffer::Pixel;
45-
/// #
49+
/// use softbuffer::{Pixel, PixelFormat, DEFAULT_PIXEL_FORMAT};
50+
///
4651
/// let red = Pixel::new_rgb(0xff, 0, 0);
4752
/// // SAFETY: `Pixel` can be reinterpreted as `u32`.
4853
/// let red = unsafe { core::mem::transmute::<Pixel, u32>(red) };
4954
///
50-
/// // BGRX
51-
/// assert_eq!(red, u32::from_le_bytes([0x00, 0x00, 0xff, 0x00]));
55+
/// match DEFAULT_PIXEL_FORMAT {
56+
/// PixelFormat::Rgbx => assert_eq!(red, u32::from_le_bytes([0xff, 0x00, 0x00, 0x00])),
57+
/// PixelFormat::Bgrx => assert_eq!(red, u32::from_le_bytes([0x00, 0x00, 0xff, 0x00])),
58+
/// }
59+
/// ```
60+
///
61+
/// Convert a slice of pixels to a slice of `u32`s. This might be useful for library authors that
62+
/// want to keep rendering using BGRX.
63+
///
64+
/// ```
65+
/// // Assume the user controls the following rendering function:
66+
/// fn render(pixels: &mut [u32]) {
67+
/// pixels.fill(u32::from_le_bytes([0xff, 0xff, 0x00, 0x00])); // Yellow in BGRX
68+
/// }
69+
///
70+
/// // Then we'd convert pixel data as follows:
71+
/// use softbuffer::{Pixel, PixelFormat, DEFAULT_PIXEL_FORMAT};
72+
///
73+
/// # let mut pixel_data = [Pixel::new_rgb(0, 0xff, 0xff)];
74+
/// let pixels: &mut [Pixel];
75+
/// # pixels = &mut pixel_data;
76+
///
77+
/// if DEFAULT_PIXEL_FORMAT == PixelFormat::Bgrx {
78+
/// // Fast implementation when the pixel format is BGRX.
79+
///
80+
/// // SAFETY: `Pixel` can be reinterpreted as `u32`.
81+
/// let pixels = unsafe { std::mem::transmute::<&mut [Pixel], &mut [u32]>(pixels) };
82+
/// // CORRECTNESS: We just checked that the format is BGRX.
83+
/// render(pixels);
84+
/// } else {
85+
/// // Fall back to slower implementation when the format is RGBX.
86+
///
87+
/// // Render into temporary buffer.
88+
/// let mut buffer = vec![0u32; pixels.len()];
89+
/// render(&mut buffer);
90+
///
91+
/// // And copy from temporary buffer to actual pixel data.
92+
/// for (old, new) in pixels.iter_mut().zip(buffer) {
93+
/// let new = new.to_le_bytes();
94+
/// *old = Pixel::new_bgr(new[0], new[1], new[2]);
95+
/// }
96+
/// }
97+
/// #
98+
/// # assert_eq!(pixel_data, [Pixel::new_rgb(0, 0xff, 0xff)]);
5299
/// ```
53100
#[repr(C)]
54101
#[repr(align(4))] // Help the compiler to see that this is a u32
55102
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
56103
pub struct Pixel {
104+
#[cfg_attr(docsrs, doc(auto_cfg = false))]
105+
#[cfg(any(doc, target_family = "wasm", target_os = "android"))]
106+
/// The red component.
107+
pub r: u8,
108+
#[cfg_attr(docsrs, doc(auto_cfg = false))]
109+
#[cfg(any(doc, target_family = "wasm", target_os = "android"))]
110+
/// The green component.
111+
pub g: u8,
112+
#[cfg_attr(docsrs, doc(auto_cfg = false))]
113+
#[cfg(any(doc, target_family = "wasm", target_os = "android"))]
57114
/// The blue component.
58115
pub b: u8,
116+
117+
#[cfg(not(any(doc, target_family = "wasm", target_os = "android")))]
118+
/// The blue component.
119+
pub b: u8,
120+
#[cfg(not(any(doc, target_family = "wasm", target_os = "android")))]
59121
/// The green component.
60122
pub g: u8,
123+
#[cfg(not(any(doc, target_family = "wasm", target_os = "android")))]
61124
/// The red component.
62125
pub r: u8,
63126

@@ -106,3 +169,24 @@ impl Pixel {
106169
}
107170

108171
// TODO: Implement `Add`/`Mul`/similar `std::ops` like `rgb` does?
172+
173+
// TODO: Implement `zerocopy` / `bytemuck` traits behind a feature flag?
174+
// May not be that useful, since the representation is platform-specific.
175+
176+
/// The pixel format that `softbuffer` uses for the current target platform.
177+
///
178+
/// Currently, this is BGRX (first component blue, second green, third red and last unset) on all
179+
/// platforms except WebAssembly and Android, where it is RGBX, since the API on these platforms
180+
/// does not support BGRX.
181+
///
182+
/// The format for a given platform may change in a non-breaking release if found to be more
183+
/// performant.
184+
///
185+
/// This distinction should only be relevant if you're bitcasting `Pixel` to/from a `u32`, to e.g.
186+
/// avoid unnecessary copying, see the documentation for [`Pixel`] for examples.
187+
pub const DEFAULT_PIXEL_FORMAT: PixelFormat =
188+
if cfg!(any(doc, target_family = "wasm", target_os = "android")) {
189+
PixelFormat::Rgbx
190+
} else {
191+
PixelFormat::Bgrx
192+
};

0 commit comments

Comments
 (0)