Skip to content

Commit aebcfb2

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 04b0ab2 commit aebcfb2

File tree

5 files changed

+107
-16
lines changed

5 files changed

+107
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Unreleased
22

33
- **Breaking:** Add `Pixel` struct, and use that for pixels instead of `u32`.
4+
- **Breaking:** The pixel format is now target-dependent.
45

56
# 0.4.7
67

src/backends/android.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl
164164

165165
// Write RGB(A) to the output.
166166
// TODO: Use `slice::write_copy_of_slice` once stable and in MSRV.
167+
// TODO(madsmtm): Verify that this compiles down to an efficient copy.
167168
for (i, pixel) in input.iter().enumerate() {
168169
output[i * 4].write(pixel.r);
169170
output[i * 4 + 1].write(pixel.g);

src/backends/web.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ impl<D: HasDisplayHandle, W: HasWindowHandle> WebImpl<D, W> {
145145
.skip(union_damage.x as usize)
146146
.take(union_damage.width.get() as usize)
147147
})
148+
// TODO(madsmtm): Once we support alpha, add it here, and verify that this
149+
// optimize away to nothing (and if it doesn't, unsafely cast instead).
148150
.flat_map(|pixel| [pixel.r, pixel.g, pixel.b, 255])
149151
.collect();
150152

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use std::sync::Arc;
2323

2424
use self::error::InitError;
2525
pub use self::error::SoftBufferError;
26-
pub use self::pixel::Pixel;
26+
pub use self::pixel::{Pixel, PixelFormat, PIXEL_FORMAT};
2727

2828
use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle};
2929

src/pixel.rs

Lines changed: 102 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,18 @@
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 [`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, as
10-
/// that makes copies faster on many platforms.
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`.
119
///
1210
/// # Example
1311
///
1412
/// Construct a new pixel.
1513
///
1614
/// ```
17-
/// # use softbuffer::Pixel;
18-
/// #
15+
/// use softbuffer::Pixel;
16+
///
1917
/// let red = Pixel::new_rgb(0xff, 0x80, 0);
2018
/// assert_eq!(red.r, 255);
2119
/// assert_eq!(red.g, 128);
@@ -25,36 +23,93 @@
2523
/// Convert a pixel to an array of `u8`s.
2624
///
2725
/// ```
28-
/// # use softbuffer::Pixel;
29-
/// #
26+
/// use softbuffer::{Pixel, PixelFormat, PIXEL_FORMAT};
27+
///
3028
/// let red = Pixel::new_rgb(0xff, 0, 0);
3129
/// // SAFETY: `Pixel` can be reinterpreted as `[u8; 4]`.
3230
/// let red = unsafe { core::mem::transmute::<Pixel, [u8; 4]>(red) };
3331
///
34-
/// // BGRX
35-
/// assert_eq!(red[2], 255);
32+
/// match PIXEL_FORMAT {
33+
/// PixelFormat::Rgbx => assert_eq!(red[0], 255),
34+
/// PixelFormat::Bgrx => assert_eq!(red[2], 255),
35+
/// }
3636
/// ```
3737
///
3838
/// Convert a pixel to an `u32`.
3939
///
4040
/// ```
41-
/// # use softbuffer::Pixel;
42-
/// #
41+
/// use softbuffer::{Pixel, PixelFormat, PIXEL_FORMAT};
42+
///
4343
/// let red = Pixel::new_rgb(0xff, 0, 0);
4444
/// // SAFETY: `Pixel` can be reinterpreted as `u32`.
4545
/// let red = unsafe { core::mem::transmute::<Pixel, u32>(red) };
4646
///
47-
/// // BGRX
48-
/// assert_eq!(red, u32::from_le_bytes([0x00, 0x00, 0xff, 0x00]));
47+
/// match PIXEL_FORMAT {
48+
/// PixelFormat::Rgbx => assert_eq!(red, u32::from_le_bytes([0xff, 0x00, 0x00, 0x00])),
49+
/// PixelFormat::Bgrx => assert_eq!(red, u32::from_le_bytes([0x00, 0x00, 0xff, 0x00])),
50+
/// }
51+
/// ```
52+
///
53+
/// Convert a slice of pixels to a slice of `u32`s. This might be useful for library authors that
54+
/// want to keep rendering using BGRX.
55+
///
56+
/// ```
57+
/// // Assume the user controls the following rendering function:
58+
/// fn render(pixels: &mut [u32]) {
59+
/// pixels.fill(u32::from_le_bytes([0xff, 0xff, 0x00, 0x00])); // Yellow in BGRX
60+
/// }
61+
///
62+
/// // Then we'd convert pixel data as follows:
63+
/// use softbuffer::{Pixel, PixelFormat, PIXEL_FORMAT};
64+
///
65+
/// # let mut pixel_data = [Pixel::new_rgb(0, 0xff, 0xff)];
66+
/// let pixels: &mut [Pixel];
67+
/// # pixels = &mut pixel_data;
68+
///
69+
/// if PIXEL_FORMAT == PixelFormat::Bgrx {
70+
/// // Fast implementation when the pixel format is BGRX.
71+
///
72+
/// // SAFETY: `Pixel` can be reinterpreted as `u32`.
73+
/// let pixels = unsafe { std::mem::transmute::<&mut [Pixel], &mut [u32]>(pixels) };
74+
/// // CORRECTNESS: We just checked that the format is BGRX.
75+
/// render(pixels);
76+
/// } else {
77+
/// // Fall back to slower implementation when the format is RGBX.
78+
///
79+
/// // Render into temporary buffer.
80+
/// let mut buffer = vec![0u32; pixels.len()];
81+
/// render(&mut buffer);
82+
///
83+
/// // And copy from temporary buffer to actual pixel data.
84+
/// for (old, new) in pixels.iter_mut().zip(buffer) {
85+
/// let new = new.to_le_bytes();
86+
/// *old = Pixel::new_bgr(new[0], new[1], new[2]);
87+
/// }
88+
/// }
89+
/// #
90+
/// # assert_eq!(pixel_data, [Pixel::new_rgb(0, 0xff, 0xff)]);
4991
/// ```
5092
#[repr(C)]
5193
#[repr(align(4))] // May help the compiler to see that this is a u32
5294
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
5395
pub struct Pixel {
96+
#[cfg(any(target_family = "wasm", target_os = "android"))]
97+
/// The red component.
98+
pub r: u8,
99+
#[cfg(any(target_family = "wasm", target_os = "android"))]
100+
/// The green component.
101+
pub g: u8,
102+
#[cfg(any(target_family = "wasm", target_os = "android"))]
54103
/// The blue component.
55104
pub b: u8,
105+
106+
#[cfg(not(any(target_family = "wasm", target_os = "android")))]
107+
/// The blue component.
108+
pub b: u8,
109+
#[cfg(not(any(target_family = "wasm", target_os = "android")))]
56110
/// The green component.
57111
pub g: u8,
112+
#[cfg(not(any(target_family = "wasm", target_os = "android")))]
58113
/// The red component.
59114
pub r: u8,
60115

@@ -110,3 +165,35 @@ impl Pixel {
110165
}
111166

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

0 commit comments

Comments
 (0)