Skip to content

Commit 084ae52

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 fd1dfdb commit 084ae52

File tree

5 files changed

+112
-16
lines changed

5 files changed

+112
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- **Breaking:** Removed generic type parameters `D` and `W` from `Buffer<'_>` struct.
77
- **Breaking:** Removed `Deref[Mut]` implementation on `Buffer<'_>`. Use `Buffer::pixels()` instead.
88
- **Breaking:** Add `Pixel` struct, and use that for pixels instead of `u32`.
9+
- **Breaking:** The pixel format is now target-dependent.
910

1011
# 0.4.7
1112

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: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/// A pixel format that `softbuffer` may use.
2+
///
3+
/// See [`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+
}
12+
13+
/// The pixel format that `softbuffer` uses for the current target platform.
14+
///
15+
/// Currently, this is BGRX (first component blue, second green, third red and last unset) on all
16+
/// platforms except WebAssembly and Android targets, where it is RGBX, since the API on these
17+
/// platforms only support that format.
18+
///
19+
/// The format for a given platform may change in a non-breaking release if found to be more
20+
/// performant.
21+
///
22+
/// This distinction should only be relevant if you're bitcasting `Pixel` to/from a `u32`, to e.g.
23+
/// avoid unnecessary copying, see [`Pixel`][crate::Pixel] for examples.
24+
pub const DEFAULT_PIXEL_FORMAT: PixelFormat =
25+
if cfg!(any(doc, target_family = "wasm", target_os = "android")) {
26+
PixelFormat::Rgbx
27+
} else {
28+
PixelFormat::Bgrx
29+
};

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ mod backend_interface;
1212
use backend_interface::*;
1313
mod backends;
1414
mod error;
15+
mod format;
1516
mod pixel;
1617
mod util;
1718

@@ -22,6 +23,7 @@ use std::sync::Arc;
2223

2324
use self::error::InitError;
2425
pub use self::error::SoftBufferError;
26+
pub use self::format::{PixelFormat, DEFAULT_PIXEL_FORMAT};
2527
pub use self::pixel::Pixel;
2628

2729
use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle};

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 `u8`'s in the order defined by [`DEFAULT_PIXEL_FORMAT`].
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+
/// [`DEFAULT_PIXEL_FORMAT`]: crate::DEFAULT_PIXEL_FORMAT
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, DEFAULT_PIXEL_FORMAT};
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 DEFAULT_PIXEL_FORMAT {
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, DEFAULT_PIXEL_FORMAT};
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 DEFAULT_PIXEL_FORMAT {
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, DEFAULT_PIXEL_FORMAT};
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 DEFAULT_PIXEL_FORMAT == PixelFormat::Bgrx {
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)