diff --git a/CHANGELOG.md b/CHANGELOG.md index a0c9b006..c66f4601 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Unreleased - Added `AlphaMode` to allow configuring transparency and zero-copy on Web. Set it with `Surface::configure`. -- Added `Surface::supports_alpha_mode` for querying supported alpha modes. -- Added `PixelFormat` enum. +- Added `PixelFormat` enum. Set it with `Surface::configure`. +- Added `Surface::supported_pixel_formats` for querying supported alpha modes and pixel formats. - Added `Buffer::pixels()` for accessing the buffer's pixel data. - Added `Buffer::pixel_rows()` for iterating over rows of the buffer data. - Added `Buffer::pixels_iter()` for iterating over each pixel with its associated `x`/`y` coordinate. diff --git a/Cargo.toml b/Cargo.toml index d13d4307..4f263c60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -180,6 +180,9 @@ glam = "0.32.0" rand = "0.10.0" # Disable unnecessary formats. image = { version = "0.25.0", default-features = false, features = ["jpeg"] } +# For f16 support. TODO: Use standard library's f16 once stable: +# https://github.com/rust-lang/rust/issues/116909 +half = "2.7.1" [target.'cfg(not(target_family = "wasm"))'.dev-dependencies] # Use rayon in raytracing example on non-web platforms. diff --git a/examples/pixel_formats.rs b/examples/pixel_formats.rs new file mode 100644 index 00000000..f7a111a7 --- /dev/null +++ b/examples/pixel_formats.rs @@ -0,0 +1,408 @@ +//! An example to test rendering with different pixel formats. +//! +//! Press `1`, `2`, `3` or `4` to change the alpha mode to `Opaque`, `Ignored`, `Premultiplied` and +//! `Postmultiplied` respectively. +//! +//! The expected output is the same as in [the `transparency` example](./transparency.rs), though +//! sometimes with less color fidelity (because the pixel mode doesn't allow it). +use half::f16; +use softbuffer::{AlphaMode, Context, PixelFormat, Surface}; +use std::num::NonZeroU32; +use std::ops::{BitOr, Shl}; +use winit::event::{ElementState, KeyEvent, WindowEvent}; +use winit::event_loop::{ControlFlow, EventLoop}; +use winit::keyboard::{Key, NamedKey}; + +mod util; + +fn main() { + util::setup(); + + let event_loop = EventLoop::new().unwrap(); + + let context = Context::new(event_loop.owned_display_handle()).unwrap(); + + let mut format_index = 0; + let mut alpha_mode = AlphaMode::default(); + + let app = util::WinitAppBuilder::with_init( + |elwt| util::make_window(elwt, |w| w), + move |_elwt, window| Surface::new(&context, window.clone()).unwrap(), + ) + .with_event_handler(move |window, surface, window_id, event, elwt| { + elwt.set_control_flow(ControlFlow::Wait); + + if window_id != window.id() { + return; + } + + match event { + WindowEvent::Resized(size) => { + let Some(surface) = surface else { + tracing::error!("Resized fired before Resumed or after Suspended"); + return; + }; + + if let (Some(width), Some(height)) = + (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) + { + surface.resize(width, height).unwrap(); + } + } + WindowEvent::RedrawRequested => { + let Some(surface) = surface else { + tracing::error!("RedrawRequested fired before Resumed or after Suspended"); + return; + }; + + tracing::info!(pixel_format = ?surface.pixel_format(), "redraw"); + + let mut buffer = surface.next_buffer().unwrap(); + + let width = buffer.width().get() as usize; + let pixel_format = buffer.pixel_format(); + let bpp = pixel_format.bits_per_pixel() as usize; + + let byte_stride = buffer.byte_stride().get() as usize; + let pixels = buffer.pixels(); + // SAFETY: `Pixel` can be reinterpreted as 4 `u8`s. + let data_u8: &mut [u8] = unsafe { + std::slice::from_raw_parts_mut( + pixels.as_mut_ptr().cast::(), + pixels.len() * 4, + ) + }; + + let split = (width / 6) * bpp / 8; + let required_a = if alpha_mode == AlphaMode::Opaque { + 1.0 + } else { + 0.0 + }; + for row in data_u8.chunks_mut(byte_stride) { + let (left, row) = row.split_at_mut(split); + fill(left, pixel_format, 1.0, 0.5, 0.0, 1.0f32.max(required_a)); + let (left, row) = row.split_at_mut(split); + fill(left, pixel_format, 1.0, 1.0, 0.0, 0.5f32.max(required_a)); + let (left, row) = row.split_at_mut(split); + fill(left, pixel_format, 0.5, 0.5, 0.0, 0.5f32.max(required_a)); + let (left, row) = row.split_at_mut(split); + fill(left, pixel_format, 1.0, 0.5, 0.0, 0.5f32.max(required_a)); + let (left, row) = row.split_at_mut(split); + fill(left, pixel_format, 1.0, 0.5, 0.0, 0.0f32.max(required_a)); + fill(row, pixel_format, 0.0, 0.0, 0.0, 0.0f32.max(required_a)); + } + + buffer.present().unwrap(); + } + WindowEvent::CloseRequested + | WindowEvent::KeyboardInput { + event: + KeyEvent { + logical_key: Key::Named(NamedKey::Escape), + repeat: false, + .. + }, + .. + } => { + elwt.exit(); + } + WindowEvent::KeyboardInput { + event: + KeyEvent { + logical_key, + repeat: false, + state: ElementState::Pressed, + .. + }, + .. + } => { + let Some(surface) = surface else { + tracing::error!("KeyboardInput fired before Resumed or after Suspended"); + return; + }; + + match logical_key.to_text() { + Some("1") => alpha_mode = AlphaMode::Opaque, + Some("2") => alpha_mode = AlphaMode::Ignored, + Some("3") => alpha_mode = AlphaMode::Premultiplied, + Some("4") => alpha_mode = AlphaMode::Postmultiplied, + Some("n") => format_index += 1, + Some("r") => format_index = 0, + _ => return, + } + + if ALL_FORMATS.len() <= format_index { + format_index = 0; + } + let pixel_format = ALL_FORMATS[format_index]; + + if !surface + .supported_pixel_formats(alpha_mode) + .contains(&pixel_format) + { + tracing::warn!(?alpha_mode, ?pixel_format, "not supported by the backend"); + return; + } + + tracing::info!(?alpha_mode, ?pixel_format, "configure"); + let size = window.inner_size(); + let width = NonZeroU32::new(size.width).unwrap(); + let height = NonZeroU32::new(size.height).unwrap(); + surface + .configure(width, height, alpha_mode, pixel_format) + .unwrap(); + assert_eq!(surface.alpha_mode(), alpha_mode); + assert_eq!(surface.pixel_format(), pixel_format); + + window.set_transparent(matches!( + alpha_mode, + AlphaMode::Premultiplied | AlphaMode::Postmultiplied + )); + + window.request_redraw(); + } + _ => {} + } + }); + + util::run_app(event_loop, app); +} + +/// Fill a single row of data with the given red, green and blue pixel, in the given format. +fn fill(row: &mut [u8], format: PixelFormat, r: f32, g: f32, b: f32, a: f32) { + let l = (r + g + b) / 3.0; // Very simple lightness calculation + match format { + PixelFormat::Bgr8 => each_pixel::(row, &[b, g, r]), + PixelFormat::Rgb8 => each_pixel::(row, &[r, g, b]), + PixelFormat::Bgra8 => each_pixel::(row, &[b, g, r, a]), + PixelFormat::Rgba8 => each_pixel::(row, &[r, g, b, a]), + PixelFormat::Abgr8 => each_pixel::(row, &[a, b, g, r]), + PixelFormat::Argb8 => each_pixel::(row, &[a, r, g, b]), + PixelFormat::Bgr16 => each_pixel::(row, &[b, g, r]), + PixelFormat::Rgb16 => each_pixel::(row, &[r, g, b]), + PixelFormat::Bgra16 => each_pixel::(row, &[b, g, r, a]), + PixelFormat::Rgba16 => each_pixel::(row, &[r, g, b, a]), + PixelFormat::Abgr16 => each_pixel::(row, &[a, b, g, r]), + PixelFormat::Argb16 => each_pixel::(row, &[a, r, g, b]), + + // Grayscale formats. + PixelFormat::R1 => each_bitpacked_grayscale::<1>(row, l), + PixelFormat::R2 => each_bitpacked_grayscale::<2>(row, l), + PixelFormat::R4 => each_bitpacked_grayscale::<4>(row, l), + PixelFormat::R8 => each_pixel::(row, &[l]), + PixelFormat::R16 => each_pixel::(row, &[l]), + + // Packed formats. + PixelFormat::B2g3r3 => each_packed::(row, &[(b, 2), (g, 3), (r, 3)]), + PixelFormat::R3g3b2 => each_packed::(row, &[(r, 3), (g, 3), (b, 2)]), + + PixelFormat::B5g6r5 => each_packed::(row, &[(b, 5), (g, 6), (r, 5)]), + PixelFormat::R5g6b5 => each_packed::(row, &[(r, 5), (g, 6), (b, 5)]), + + PixelFormat::Bgra4 => each_packed::(row, &[(b, 4), (g, 4), (r, 4), (a, 4)]), + PixelFormat::Rgba4 => each_packed::(row, &[(r, 4), (g, 4), (b, 4), (a, 4)]), + PixelFormat::Abgr4 => each_packed::(row, &[(a, 4), (b, 4), (g, 4), (r, 4)]), + PixelFormat::Argb4 => each_packed::(row, &[(a, 4), (r, 4), (g, 4), (b, 4)]), + + PixelFormat::Bgr5a1 => each_packed::(row, &[(b, 5), (g, 5), (r, 5), (a, 1)]), + PixelFormat::Rgb5a1 => each_packed::(row, &[(r, 5), (g, 5), (b, 5), (a, 1)]), + PixelFormat::A1bgr5 => each_packed::(row, &[(a, 1), (b, 5), (g, 5), (r, 5)]), + PixelFormat::A1rgb5 => each_packed::(row, &[(a, 1), (r, 5), (g, 5), (b, 5)]), + + PixelFormat::Bgr10a2 => each_packed::(row, &[(b, 10), (g, 10), (r, 10), (a, 2)]), + PixelFormat::Rgb10a2 => each_packed::(row, &[(r, 10), (g, 10), (b, 10), (a, 2)]), + PixelFormat::A2bgr10 => each_packed::(row, &[(a, 2), (b, 10), (g, 10), (r, 10)]), + PixelFormat::A2rgb10 => each_packed::(row, &[(a, 2), (r, 10), (g, 10), (b, 10)]), + + // Floating point formats. + PixelFormat::Bgr16f => each_pixel::(row, &[b, g, r]), + PixelFormat::Rgb16f => each_pixel::(row, &[r, g, b]), + PixelFormat::Bgra16f => each_pixel::(row, &[b, g, r, a]), + PixelFormat::Rgba16f => each_pixel::(row, &[r, g, b, a]), + PixelFormat::Abgr16f => each_pixel::(row, &[a, b, g, r]), + PixelFormat::Argb16f => each_pixel::(row, &[a, r, g, b]), + PixelFormat::Bgr32f => each_pixel::(row, &[b, g, r]), + PixelFormat::Rgb32f => each_pixel::(row, &[r, g, b]), + PixelFormat::Bgra32f => each_pixel::(row, &[b, g, r, a]), + PixelFormat::Rgba32f => each_pixel::(row, &[r, g, b, a]), + PixelFormat::Abgr32f => each_pixel::(row, &[a, b, g, r]), + PixelFormat::Argb32f => each_pixel::(row, &[a, r, g, b]), + + _ => unimplemented!(), + } +} + +fn each_pixel(row: &mut [u8], components: &[f32]) { + // SAFETY: `u8`s can be re-interpreted as `Component`. + let ([], row, []) = (unsafe { row.align_to_mut::() }) else { + unreachable!("row was not properly aligned"); + }; + + for pixel in row.chunks_mut(components.len()) { + for (out_component, component) in pixel.iter_mut().zip(components) { + *out_component = T::from_f32(*component); + } + } +} + +/// Fill packed format data. +fn each_packed + BitOr>( + row: &mut [u8], + packed_components: &[(f32, u8)], +) { + // Pack components into a single pixel of type `T`. + let mut pixel = T::ZERO; + let mut shift = 0; + for (component, size) in packed_components.iter().rev() { + let q = T::quantize(*component, *size); + pixel = pixel | q << shift; + shift += size; + } + + // SAFETY: `u8`s can be re-interpreted as `Component`. + // We allow there to be leftover data at the end. + let ([], row, _) = (unsafe { row.align_to_mut::() }) else { + unreachable!("row was not properly aligned"); + }; + + row.fill(pixel); +} + +/// Convert multiple grayscale pixels packed into a single byte. Kind of a special case. +fn each_bitpacked_grayscale(row: &mut [u8], component: f32) { + let mut pixel = 0; + for shift in (0..8).step_by(BPP as usize) { + let q = u8::quantize(component, BPP); + pixel |= q << shift; + } + + row.fill(pixel); +} + +/// A trait for representing the different kinds of pixel data components. +/// +/// # Safety +/// +/// Must be able to be reinterpreted from `u8`s. +unsafe trait Component: Sized + Copy + std::fmt::Debug + PartialEq { + const ZERO: Self; + + fn from_f32(component: f32) -> Self; + + fn quantize(component: f32, size: u8) -> Self; +} + +unsafe impl Component for u8 { + const ZERO: Self = 0; + + fn from_f32(component: f32) -> Self { + (component * u8::MAX as f32) as u8 + } + + fn quantize(component: f32, size: u8) -> Self { + let max = (1 << size) - 1; + (component * max as f32).round() as Self + } +} + +unsafe impl Component for u16 { + const ZERO: Self = 0; + + fn from_f32(component: f32) -> Self { + (component * u16::MAX as f32) as u16 + } + + fn quantize(component: f32, size: u8) -> Self { + let max = (1 << size) - 1; + (component * max as f32).round() as Self + } +} + +unsafe impl Component for u32 { + const ZERO: Self = 0; + + fn from_f32(component: f32) -> Self { + (component * u32::MAX as f32) as u32 + } + + fn quantize(component: f32, size: u8) -> Self { + let max = (1 << size) - 1; + (component * max as f32).round() as Self + } +} + +unsafe impl Component for f16 { + const ZERO: Self = f16::from_f32_const(0.0); + + fn from_f32(component: f32) -> Self { + f16::from_f32(component) + } + + fn quantize(_component: f32, _size: u8) -> Self { + unimplemented!() + } +} + +unsafe impl Component for f32 { + const ZERO: Self = 0.0; + + fn from_f32(component: f32) -> Self { + component + } + + fn quantize(_component: f32, _size: u8) -> Self { + unimplemented!() + } +} + +const ALL_FORMATS: &[PixelFormat] = &[ + PixelFormat::Bgr8, + PixelFormat::Rgb8, + PixelFormat::Bgra8, + PixelFormat::Rgba8, + PixelFormat::Abgr8, + PixelFormat::Argb8, + PixelFormat::Bgr16, + PixelFormat::Rgb16, + PixelFormat::Bgra16, + PixelFormat::Rgba16, + PixelFormat::Abgr16, + PixelFormat::Argb16, + // Grayscale formats. + PixelFormat::R1, + PixelFormat::R2, + PixelFormat::R4, + PixelFormat::R8, + PixelFormat::R16, + // Packed formats. + PixelFormat::B2g3r3, + PixelFormat::R3g3b2, + PixelFormat::B5g6r5, + PixelFormat::R5g6b5, + PixelFormat::Bgra4, + PixelFormat::Rgba4, + PixelFormat::Abgr4, + PixelFormat::Argb4, + PixelFormat::Bgr5a1, + PixelFormat::Rgb5a1, + PixelFormat::A1bgr5, + PixelFormat::A1rgb5, + PixelFormat::Bgr10a2, + PixelFormat::Rgb10a2, + PixelFormat::A2bgr10, + PixelFormat::A2rgb10, + // Floating point formats. + PixelFormat::Bgr16f, + PixelFormat::Rgb16f, + PixelFormat::Bgra16f, + PixelFormat::Rgba16f, + PixelFormat::Abgr16f, + PixelFormat::Argb16f, + PixelFormat::Bgr32f, + PixelFormat::Rgb32f, + PixelFormat::Bgra32f, + PixelFormat::Rgba32f, + PixelFormat::Abgr32f, + PixelFormat::Argb32f, +]; diff --git a/examples/transparency.rs b/examples/transparency.rs index b58691a9..d3ad2372 100644 --- a/examples/transparency.rs +++ b/examples/transparency.rs @@ -141,7 +141,7 @@ fn main() { _ => return, }; - if !surface.supports_alpha_mode(alpha_mode) { + if surface.supported_pixel_formats(alpha_mode).is_empty() { tracing::warn!(?alpha_mode, "not supported by the backend"); return; } @@ -150,7 +150,9 @@ fn main() { let size = window.inner_size(); let width = NonZeroU32::new(size.width).unwrap(); let height = NonZeroU32::new(size.height).unwrap(); - surface.configure(width, height, alpha_mode).unwrap(); + surface + .configure(width, height, alpha_mode, surface.pixel_format()) + .unwrap(); assert_eq!(surface.alpha_mode(), alpha_mode); window.set_transparent(matches!( diff --git a/src/backend_dispatch.rs b/src/backend_dispatch.rs index e88f12c0..9e6cb55a 100644 --- a/src/backend_dispatch.rs +++ b/src/backend_dispatch.rs @@ -1,6 +1,8 @@ //! Implements `buffer_interface::*` traits for enums dispatching to backends -use crate::{backend_interface::*, backends, AlphaMode, InitError, Pixel, Rect, SoftBufferError}; +use crate::{ + backend_interface::*, backends, AlphaMode, InitError, Pixel, PixelFormat, Rect, SoftBufferError, +}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use std::fmt; @@ -100,29 +102,35 @@ macro_rules! make_dispatch { } #[inline] - fn supports_alpha_mode(&self, alpha_mode: AlphaMode) -> bool { + fn supported_pixel_formats(&self, alpha_mode: AlphaMode) -> &[PixelFormat] { match self { $( $(#[$attr])* - Self::$name(inner) => inner.supports_alpha_mode(alpha_mode), + Self::$name(inner) => inner.supported_pixel_formats(alpha_mode), )* } } - fn configure(&mut self, width: NonZeroU32, height: NonZeroU32, alpha_mode: AlphaMode) -> Result<(), SoftBufferError> { + fn configure( + &mut self, + width: NonZeroU32, + height: NonZeroU32, + alpha_mode: AlphaMode, + pixel_format: PixelFormat, + ) -> Result<(), SoftBufferError> { match self { $( $(#[$attr])* - Self::$name(inner) => inner.configure(width, height, alpha_mode), + Self::$name(inner) => inner.configure(width, height, alpha_mode, pixel_format), )* } } - fn next_buffer(&mut self, alpha_mode: AlphaMode) -> Result, SoftBufferError> { + fn next_buffer(&mut self, alpha_mode: AlphaMode, pixel_format: PixelFormat) -> Result, SoftBufferError> { match self { $( $(#[$attr])* - Self::$name(inner) => Ok(BufferDispatch::$name(inner.next_buffer(alpha_mode)?)), + Self::$name(inner) => Ok(BufferDispatch::$name(inner.next_buffer(alpha_mode, pixel_format)?)), )* } } diff --git a/src/backend_interface.rs b/src/backend_interface.rs index 239d3fdc..491ae52c 100644 --- a/src/backend_interface.rs +++ b/src/backend_interface.rs @@ -1,6 +1,6 @@ //! Interface implemented by backends -use crate::{AlphaMode, InitError, Pixel, Rect, SoftBufferError}; +use crate::{AlphaMode, InitError, Pixel, PixelFormat, Rect, SoftBufferError}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use std::num::NonZeroU32; @@ -26,7 +26,8 @@ pub(crate) trait SurfaceInterface &W; - fn supports_alpha_mode(&self, alpha_mode: AlphaMode) -> bool; + /// The supported pixel formats for the given alpha mode. + fn supported_pixel_formats(&self, alpha_mode: AlphaMode) -> &[PixelFormat]; /// Reconfigure the internal buffer(s). fn configure( @@ -34,10 +35,15 @@ pub(crate) trait SurfaceInterface Result<(), SoftBufferError>; /// Get the next buffer to render into. - fn next_buffer(&mut self, alpha_mode: AlphaMode) -> Result, SoftBufferError>; + fn next_buffer( + &mut self, + alpha_mode: AlphaMode, + pixel_format: PixelFormat, + ) -> Result, SoftBufferError>; /// Fetch the buffer from the window. fn fetch(&mut self) -> Result, SoftBufferError> { diff --git a/src/backends/android.rs b/src/backends/android.rs index 4404badd..2155e5c1 100644 --- a/src/backends/android.rs +++ b/src/backends/android.rs @@ -12,7 +12,9 @@ use raw_window_handle::AndroidNdkWindowHandle; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle}; use crate::error::InitError; -use crate::{util, AlphaMode, BufferInterface, Pixel, Rect, SoftBufferError, SurfaceInterface}; +use crate::{ + util, AlphaMode, BufferInterface, Pixel, PixelFormat, Rect, SoftBufferError, SurfaceInterface, +}; /// The handle to a window for software buffering. #[derive(Debug)] @@ -53,8 +55,12 @@ impl SurfaceInterface for Android } #[inline] - fn supports_alpha_mode(&self, alpha_mode: AlphaMode) -> bool { - matches!(alpha_mode, AlphaMode::Opaque | AlphaMode::Ignored) + fn supported_pixel_formats(&self, alpha_mode: AlphaMode) -> &[PixelFormat] { + if matches!(alpha_mode, AlphaMode::Opaque | AlphaMode::Ignored) { + &[PixelFormat::Rgba8] + } else { + &[] + } } /// Also changes the pixel format. @@ -63,6 +69,7 @@ impl SurfaceInterface for Android width: NonZeroU32, height: NonZeroU32, alpha_mode: AlphaMode, + _pixel_format: PixelFormat, ) -> Result<(), SoftBufferError> { let (width, height) = (|| { let width = NonZeroI32::try_from(width).ok()?; @@ -90,7 +97,11 @@ impl SurfaceInterface for Android Ok(()) } - fn next_buffer(&mut self, _alpha_mode: AlphaMode) -> Result, SoftBufferError> { + fn next_buffer( + &mut self, + _alpha_mode: AlphaMode, + _pixel_format: PixelFormat, + ) -> Result, SoftBufferError> { let native_window_buffer = self.native_window.lock(None).map_err(|err| { SoftBufferError::PlatformError( Some("Failed to lock ANativeWindow".to_owned()), diff --git a/src/backends/cg.rs b/src/backends/cg.rs index a84cbd7f..2c10c16f 100644 --- a/src/backends/cg.rs +++ b/src/backends/cg.rs @@ -1,6 +1,6 @@ //! Softbuffer implementation using CoreGraphics. use crate::error::InitError; -use crate::{backend_interface::*, AlphaMode}; +use crate::{backend_interface::*, AlphaMode, PixelFormat}; use crate::{util, Pixel, Rect, SoftBufferError}; use objc2::rc::Retained; use objc2::runtime::{AnyObject, Bool}; @@ -103,7 +103,8 @@ pub struct CGImpl { /// Can also be retrieved from `layer.superlayer()`. root_layer: SendCALayer, observer: Retained, - color_space: CFRetained, + rgb_color_space: CFRetained, + gray_color_space: CFRetained, /// The width of the underlying buffer. width: usize, /// The height of the underlying buffer. @@ -229,7 +230,8 @@ impl SurfaceInterface for CGImpl< layer.setOpaque(true); // Initialize color space here, to reduce work later on. - let color_space = CGColorSpace::new_device_rgb().unwrap(); + let rgb_color_space = CGColorSpace::new_device_rgb().unwrap(); + let gray_color_space = CGColorSpace::new_device_gray().unwrap(); // Grab initial width and height from the layer (whose properties have just been initialized // by the observer using `NSKeyValueObservingOptionInitial`). @@ -242,7 +244,8 @@ impl SurfaceInterface for CGImpl< layer: SendCALayer(layer), root_layer: SendCALayer(root_layer), observer, - color_space, + rgb_color_space, + gray_color_space, width, height, _display: PhantomData, @@ -256,8 +259,73 @@ impl SurfaceInterface for CGImpl< } #[inline] - fn supports_alpha_mode(&self, _alpha_mode: AlphaMode) -> bool { - true + fn supported_pixel_formats(&self, alpha_mode: AlphaMode) -> &[PixelFormat] { + // All alpha modes supported (ish). + // + // + // + match alpha_mode { + AlphaMode::Ignored | AlphaMode::Opaque | AlphaMode::Postmultiplied => { + &[ + // Some of these probably depend on host endianess? + PixelFormat::Rgb8, + PixelFormat::Bgra8, + PixelFormat::Rgba8, + PixelFormat::Abgr8, + PixelFormat::Argb8, + PixelFormat::Rgb16, + PixelFormat::Rgba16, + PixelFormat::Argb16, + // Grayscale formats are supported. + PixelFormat::R1, + PixelFormat::R2, + PixelFormat::R4, + PixelFormat::R8, + PixelFormat::R16, + // Packed formats only support RGB, and not `R3g3b2`. `Bgra4` etc. also seems to instead + // support the ordering `[G, B, A, R]`, `[B, A, R, G]` etc., which we can't use. + PixelFormat::R5g6b5, + PixelFormat::Rgb5a1, // TODO: Doesn't support premul alpha? + PixelFormat::A1rgb5, // TODO: Doesn't support premul alpha? + PixelFormat::Rgb10a2, + PixelFormat::A2rgb10, + // *BGR* versions of floats just produce black? + PixelFormat::Rgb16f, + PixelFormat::Rgba16f, + PixelFormat::Argb16f, + PixelFormat::Rgb32f, + PixelFormat::Rgba32f, + PixelFormat::Argb32f, + ] + } + AlphaMode::Premultiplied => { + // Same table as above, except for `PixelFormat::Rgb5a1` and `PixelFormat::A1rgb5`. + &[ + PixelFormat::Rgb8, + PixelFormat::Bgra8, + PixelFormat::Rgba8, + PixelFormat::Abgr8, + PixelFormat::Argb8, + PixelFormat::Rgb16, + PixelFormat::Rgba16, + PixelFormat::Argb16, + PixelFormat::R1, + PixelFormat::R2, + PixelFormat::R4, + PixelFormat::R8, + PixelFormat::R16, + PixelFormat::R5g6b5, + PixelFormat::Rgb10a2, + PixelFormat::A2rgb10, + PixelFormat::Rgb16f, + PixelFormat::Rgba16f, + PixelFormat::Argb16f, + PixelFormat::Rgb32f, + PixelFormat::Rgba32f, + PixelFormat::Argb32f, + ] + } + } } fn configure( @@ -265,6 +333,7 @@ impl SurfaceInterface for CGImpl< width: NonZeroU32, height: NonZeroU32, alpha_mode: AlphaMode, + _pixel_format: PixelFormat, ) -> Result<(), SoftBufferError> { let opaque = matches!(alpha_mode, AlphaMode::Opaque | AlphaMode::Ignored); self.layer.setOpaque(opaque); @@ -276,21 +345,45 @@ impl SurfaceInterface for CGImpl< Ok(()) } - fn next_buffer(&mut self, alpha_mode: AlphaMode) -> Result, SoftBufferError> { - let buffer_size = util::byte_stride(self.width as u32) as usize * self.height / 4; + fn next_buffer( + &mut self, + alpha_mode: AlphaMode, + pixel_format: PixelFormat, + ) -> Result, SoftBufferError> { + let num_bytes = util::byte_stride(self.width as u32, pixel_format.bits_per_pixel()) + as usize + * self.height; + // `CGBitmapInfo` consists of a combination of `CGImageAlphaInfo`, `CGImageComponentInfo` + // `CGImageByteOrderInfo` and `CGImagePixelFormatInfo`, see `CGBitmapInfoMake`. + // + // TODO: Use `CGBitmapInfo::new` once the next version of objc2-core-graphics is released. + let bitmap_info = CGBitmapInfo( + alpha_info(alpha_mode, pixel_format).0 + | component_info(pixel_format).0 + | byte_order_info(pixel_format).0 + | pixel_format_info(pixel_format).0, + ); + // Required that we use a different color space when using grayscale colors. + let color_space = if matches!( + pixel_format, + PixelFormat::R1 + | PixelFormat::R2 + | PixelFormat::R4 + | PixelFormat::R8 + | PixelFormat::R16 + ) { + &self.gray_color_space + } else { + &self.rgb_color_space + }; Ok(BufferImpl { - buffer: util::PixelBuffer(vec![Pixel::default(); buffer_size]), + buffer: util::PixelBuffer(vec![Pixel::default(); num_bytes / size_of::()]), width: self.width, height: self.height, - color_space: &self.color_space, - alpha_info: match (alpha_mode, cfg!(target_endian = "little")) { - (AlphaMode::Opaque | AlphaMode::Ignored, true) => CGImageAlphaInfo::NoneSkipFirst, - (AlphaMode::Opaque | AlphaMode::Ignored, false) => CGImageAlphaInfo::NoneSkipLast, - (AlphaMode::Premultiplied, true) => CGImageAlphaInfo::PremultipliedFirst, - (AlphaMode::Premultiplied, false) => CGImageAlphaInfo::PremultipliedLast, - (AlphaMode::Postmultiplied, true) => CGImageAlphaInfo::First, - (AlphaMode::Postmultiplied, false) => CGImageAlphaInfo::Last, - }, + color_space, + bitmap_info, + bits_per_component: bits_per_component(pixel_format), + bits_per_pixel: pixel_format.bits_per_pixel(), layer: &mut self.layer, }) } @@ -302,13 +395,15 @@ pub struct BufferImpl<'surface> { height: usize, color_space: &'surface CGColorSpace, buffer: util::PixelBuffer, - alpha_info: CGImageAlphaInfo, + bitmap_info: CGBitmapInfo, + bits_per_component: u8, + bits_per_pixel: u8, layer: &'surface mut SendCALayer, } impl BufferInterface for BufferImpl<'_> { fn byte_stride(&self) -> NonZeroU32 { - NonZeroU32::new(util::byte_stride(self.width as u32)).unwrap() + NonZeroU32::new(util::byte_stride(self.width as u32, self.bits_per_pixel)).unwrap() } fn width(&self) -> NonZeroU32 { @@ -353,26 +448,15 @@ impl BufferInterface for BufferImpl<'_> { } }; - // `CGBitmapInfo` consists of a combination of `CGImageAlphaInfo`, `CGImageComponentInfo` - // `CGImageByteOrderInfo` and `CGImagePixelFormatInfo` (see e.g. `CGBitmapInfoMake`). - // - // TODO: Use `CGBitmapInfo::new` once the next version of objc2-core-graphics is released. - let bitmap_info = CGBitmapInfo( - self.alpha_info.0 - | CGImageComponentInfo::Integer.0 - | CGImageByteOrderInfo::Order32Host.0 - | CGImagePixelFormatInfo::Packed.0, - ); - let image = unsafe { CGImage::new( self.width, self.height, - 8, - 32, - util::byte_stride(self.width as u32) as usize, + self.bits_per_component as usize, + self.bits_per_pixel as usize, + util::byte_stride(self.width as u32, self.bits_per_pixel) as usize, Some(self.color_space), - bitmap_info, + self.bitmap_info, Some(&data_provider), ptr::null(), false, @@ -415,3 +499,179 @@ impl Deref for SendCALayer { &self.0 } } + +fn alpha_info(alpha_mode: AlphaMode, pixel_format: PixelFormat) -> CGImageAlphaInfo { + let first = match alpha_mode { + AlphaMode::Opaque | AlphaMode::Ignored => CGImageAlphaInfo::NoneSkipFirst, + AlphaMode::Premultiplied => CGImageAlphaInfo::PremultipliedFirst, + AlphaMode::Postmultiplied => CGImageAlphaInfo::First, + }; + let last = match alpha_mode { + AlphaMode::Opaque | AlphaMode::Ignored => CGImageAlphaInfo::NoneSkipLast, + AlphaMode::Premultiplied => CGImageAlphaInfo::PremultipliedLast, + AlphaMode::Postmultiplied => CGImageAlphaInfo::Last, + }; + + match pixel_format { + // Byte-aligned RGB formats. + PixelFormat::Bgr8 | PixelFormat::Bgr16 => CGImageAlphaInfo::None, + PixelFormat::Rgb8 | PixelFormat::Rgb16 => CGImageAlphaInfo::None, + PixelFormat::Bgra8 | PixelFormat::Bgra16 => first, + PixelFormat::Rgba8 | PixelFormat::Rgba16 => last, + PixelFormat::Abgr8 | PixelFormat::Abgr16 => last, + PixelFormat::Argb8 | PixelFormat::Argb16 => first, + // Grayscale formats. + PixelFormat::R1 + | PixelFormat::R2 + | PixelFormat::R4 + | PixelFormat::R8 + | PixelFormat::R16 => CGImageAlphaInfo::None, + // Packed formats. + PixelFormat::B2g3r3 | PixelFormat::R3g3b2 => CGImageAlphaInfo::None, + PixelFormat::B5g6r5 | PixelFormat::R5g6b5 => CGImageAlphaInfo::None, + PixelFormat::Bgra4 | PixelFormat::Bgr5a1 | PixelFormat::Bgr10a2 => first, + PixelFormat::Rgba4 | PixelFormat::Rgb5a1 | PixelFormat::Rgb10a2 => last, + PixelFormat::Abgr4 | PixelFormat::A1bgr5 | PixelFormat::A2bgr10 => last, + PixelFormat::Argb4 | PixelFormat::A1rgb5 | PixelFormat::A2rgb10 => first, + // Floating point formats. + PixelFormat::Bgr16f | PixelFormat::Bgr32f => CGImageAlphaInfo::None, + PixelFormat::Rgb16f | PixelFormat::Rgb32f => CGImageAlphaInfo::None, + PixelFormat::Bgra16f | PixelFormat::Bgra32f => first, + PixelFormat::Rgba16f | PixelFormat::Rgba32f => last, + PixelFormat::Abgr16f | PixelFormat::Abgr32f => last, + PixelFormat::Argb16f | PixelFormat::Argb32f => first, + } +} + +fn component_info(pixel_format: PixelFormat) -> CGImageComponentInfo { + if matches!( + pixel_format, + PixelFormat::Bgr16f + | PixelFormat::Rgb16f + | PixelFormat::Bgra16f + | PixelFormat::Rgba16f + | PixelFormat::Abgr16f + | PixelFormat::Argb16f + | PixelFormat::Bgr32f + | PixelFormat::Rgb32f + | PixelFormat::Bgra32f + | PixelFormat::Rgba32f + | PixelFormat::Abgr32f + | PixelFormat::Argb32f + ) { + CGImageComponentInfo::Float + } else { + CGImageComponentInfo::Integer + } +} + +fn byte_order_info(pixel_format: PixelFormat) -> CGImageByteOrderInfo { + match pixel_format { + // Byte-aligned RGB formats. + PixelFormat::Bgr8 => unimplemented!(), + PixelFormat::Rgb8 => CGImageByteOrderInfo::OrderDefault, + PixelFormat::Bgra8 | PixelFormat::Abgr8 => CGImageByteOrderInfo::Order32Little, + PixelFormat::Rgba8 | PixelFormat::Argb8 => CGImageByteOrderInfo::Order32Big, + PixelFormat::Bgr16 | PixelFormat::Bgra16 | PixelFormat::Abgr16 => { + CGImageByteOrderInfo::Order16Big + } + PixelFormat::Rgb16 | PixelFormat::Rgba16 | PixelFormat::Argb16 => { + CGImageByteOrderInfo::Order16Little + } + + // Grayscale formats. + PixelFormat::R1 | PixelFormat::R2 | PixelFormat::R4 | PixelFormat::R8 => { + CGImageByteOrderInfo::OrderDefault + } + PixelFormat::R16 => CGImageByteOrderInfo::Order16Little, + + // Packed formats. + PixelFormat::B2g3r3 | PixelFormat::R3g3b2 => CGImageByteOrderInfo::OrderDefault, + PixelFormat::B5g6r5 => CGImageByteOrderInfo::Order16Big, + PixelFormat::R5g6b5 => CGImageByteOrderInfo::Order16Little, + PixelFormat::Bgra4 | PixelFormat::Abgr4 | PixelFormat::Bgr5a1 | PixelFormat::A1bgr5 => { + CGImageByteOrderInfo::Order16Big + } + PixelFormat::Rgba4 | PixelFormat::Argb4 | PixelFormat::Rgb5a1 | PixelFormat::A1rgb5 => { + CGImageByteOrderInfo::Order16Little + } + PixelFormat::Bgr10a2 | PixelFormat::A2bgr10 => CGImageByteOrderInfo::Order32Big, + PixelFormat::Rgb10a2 | PixelFormat::A2rgb10 => CGImageByteOrderInfo::Order32Little, + + // Floating point formats. + PixelFormat::Bgr16f | PixelFormat::Bgra16f | PixelFormat::Abgr16f => { + CGImageByteOrderInfo::Order16Big + } + PixelFormat::Rgb16f | PixelFormat::Rgba16f | PixelFormat::Argb16f => { + CGImageByteOrderInfo::Order16Little + } + PixelFormat::Bgr32f | PixelFormat::Bgra32f | PixelFormat::Abgr32f => { + CGImageByteOrderInfo::Order32Big + } + PixelFormat::Rgb32f | PixelFormat::Rgba32f | PixelFormat::Argb32f => { + CGImageByteOrderInfo::Order32Little + } + } +} + +fn pixel_format_info(pixel_format: PixelFormat) -> CGImagePixelFormatInfo { + match pixel_format { + PixelFormat::R5g6b5 => CGImagePixelFormatInfo::RGB565, + PixelFormat::Rgb5a1 | PixelFormat::A1rgb5 => CGImagePixelFormatInfo::RGB555, + PixelFormat::Rgb10a2 | PixelFormat::A2rgb10 => CGImagePixelFormatInfo::RGB101010, + // Probably not correct for some formats, but it's the best we can do. + _ => CGImagePixelFormatInfo::Packed, + } +} + +fn bits_per_component(pixel_format: PixelFormat) -> u8 { + match pixel_format { + // Byte-aligned RGB formats. + PixelFormat::Bgr8 + | PixelFormat::Rgb8 + | PixelFormat::Bgra8 + | PixelFormat::Rgba8 + | PixelFormat::Abgr8 + | PixelFormat::Argb8 => 8, + PixelFormat::Bgr16 + | PixelFormat::Rgb16 + | PixelFormat::Bgra16 + | PixelFormat::Rgba16 + | PixelFormat::Abgr16 + | PixelFormat::Argb16 => 16, + + // Grayscale formats. + PixelFormat::R1 => 1, + PixelFormat::R2 => 2, + PixelFormat::R4 => 4, + PixelFormat::R8 => 8, + PixelFormat::R16 => 16, + + // Packed formats. + PixelFormat::B2g3r3 | PixelFormat::R3g3b2 => 3, + PixelFormat::B5g6r5 | PixelFormat::R5g6b5 => 5, + PixelFormat::Bgra4 | PixelFormat::Rgba4 | PixelFormat::Abgr4 | PixelFormat::Argb4 => 4, + PixelFormat::Bgr5a1 | PixelFormat::Rgb5a1 | PixelFormat::A1bgr5 | PixelFormat::A1rgb5 => 5, + PixelFormat::Bgr10a2 + | PixelFormat::Rgb10a2 + | PixelFormat::A2bgr10 + | PixelFormat::A2rgb10 => 10, + + // Floating point formats. + PixelFormat::Bgr16f + | PixelFormat::Rgb16f + | PixelFormat::Bgra16f + | PixelFormat::Rgba16f + | PixelFormat::Abgr16f + | PixelFormat::Argb16f => 16, + PixelFormat::Bgr32f + | PixelFormat::Rgb32f + | PixelFormat::Bgra32f + | PixelFormat::Rgba32f + | PixelFormat::Abgr32f + | PixelFormat::Argb32f => 32, + } +} + +#[cfg(target_endian = "big")] +compile_error!("softbuffer's Apple implementation has not been tested on big endian"); diff --git a/src/backends/kms.rs b/src/backends/kms.rs index e82770c2..d04882df 100644 --- a/src/backends/kms.rs +++ b/src/backends/kms.rs @@ -21,7 +21,7 @@ use std::sync::Arc; use crate::backend_interface::*; use crate::error::{InitError, SoftBufferError, SwResultExt}; -use crate::{util, AlphaMode, Pixel}; +use crate::{util, AlphaMode, Pixel, PixelFormat}; #[derive(Debug, Clone)] struct DrmDevice<'surface> { @@ -226,11 +226,15 @@ impl SurfaceInterface fo } #[inline] - fn supports_alpha_mode(&self, alpha_mode: AlphaMode) -> bool { - matches!( + fn supported_pixel_formats(&self, alpha_mode: AlphaMode) -> &[PixelFormat] { + if matches!( alpha_mode, AlphaMode::Opaque | AlphaMode::Ignored | AlphaMode::Premultiplied - ) + ) { + &[PixelFormat::Bgra8] + } else { + &[] + } } fn configure( @@ -238,6 +242,7 @@ impl SurfaceInterface fo width: NonZeroU32, height: NonZeroU32, alpha_mode: AlphaMode, + _pixel_format: PixelFormat, ) -> Result<(), SoftBufferError> { // Don't resize if we don't have to. if let Some(buffer) = &self.buffer { @@ -271,7 +276,11 @@ impl SurfaceInterface fo } */ - fn next_buffer(&mut self, _alpha_mode: AlphaMode) -> Result, SoftBufferError> { + fn next_buffer( + &mut self, + _alpha_mode: AlphaMode, + _pixel_format: PixelFormat, + ) -> Result, SoftBufferError> { // Map the dumb buffer. let set = self .buffer diff --git a/src/backends/orbital.rs b/src/backends/orbital.rs index 23f7b451..25a2fb76 100644 --- a/src/backends/orbital.rs +++ b/src/backends/orbital.rs @@ -3,7 +3,7 @@ use raw_window_handle::{HasDisplayHandle, HasWindowHandle, OrbitalWindowHandle, use std::{cmp, marker::PhantomData, num::NonZeroU32, slice, str}; use crate::backend_interface::*; -use crate::{util, AlphaMode, Pixel, Rect, SoftBufferError}; +use crate::{util, AlphaMode, Pixel, PixelFormat, Rect, SoftBufferError}; #[derive(Debug)] struct OrbitalMap { @@ -102,8 +102,12 @@ impl SurfaceInterface for Orbital } #[inline] - fn supports_alpha_mode(&self, alpha_mode: AlphaMode) -> bool { - matches!(alpha_mode, AlphaMode::Opaque | AlphaMode::Ignored) + fn supported_pixel_formats(&self, alpha_mode: AlphaMode) -> &[PixelFormat] { + if matches!(alpha_mode, AlphaMode::Opaque | AlphaMode::Ignored) { + &[PixelFormat::Bgra8] + } else { + &[] + } } fn configure( @@ -111,6 +115,7 @@ impl SurfaceInterface for Orbital width: NonZeroU32, height: NonZeroU32, _alpha_mode: AlphaMode, + _pixel_format: PixelFormat, ) -> Result<(), SoftBufferError> { let width = width.get(); let height = height.get(); @@ -122,7 +127,11 @@ impl SurfaceInterface for Orbital Ok(()) } - fn next_buffer(&mut self, _alpha_mode: AlphaMode) -> Result, SoftBufferError> { + fn next_buffer( + &mut self, + _alpha_mode: AlphaMode, + _pixel_format: PixelFormat, + ) -> Result, SoftBufferError> { let (window_width, window_height) = window_size(self.window_fd()); let pixels = if self.width as usize == window_width && self.height as usize == window_height { diff --git a/src/backends/wayland/mod.rs b/src/backends/wayland/mod.rs index 4e1df783..6d703e7b 100644 --- a/src/backends/wayland/mod.rs +++ b/src/backends/wayland/mod.rs @@ -1,7 +1,7 @@ use crate::{ backend_interface::*, error::{InitError, SwResultExt}, - util, AlphaMode, Pixel, Rect, SoftBufferError, + util, AlphaMode, Pixel, PixelFormat, Rect, SoftBufferError, }; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle}; use std::{ @@ -143,11 +143,15 @@ impl SurfaceInterface } #[inline] - fn supports_alpha_mode(&self, alpha_mode: AlphaMode) -> bool { - matches!( + fn supported_pixel_formats(&self, alpha_mode: AlphaMode) -> &[PixelFormat] { + if matches!( alpha_mode, AlphaMode::Opaque | AlphaMode::Ignored | AlphaMode::Premultiplied - ) + ) { + &[PixelFormat::Bgra8] + } else { + &[] + } } fn configure( @@ -155,6 +159,7 @@ impl SurfaceInterface width: NonZeroU32, height: NonZeroU32, _alpha_mode: AlphaMode, + _pixel_format: PixelFormat, ) -> Result<(), SoftBufferError> { self.size = Some( (|| { @@ -167,7 +172,11 @@ impl SurfaceInterface Ok(()) } - fn next_buffer(&mut self, alpha_mode: AlphaMode) -> Result, SoftBufferError> { + fn next_buffer( + &mut self, + alpha_mode: AlphaMode, + _pixel_format: PixelFormat, + ) -> Result, SoftBufferError> { // This is documented as `0xXXRRGGBB` on a little-endian machine, which means a byte // order of `[B, G, R, X]`. let format = match alpha_mode { diff --git a/src/backends/web.rs b/src/backends/web.rs index f747a557..38cafaa1 100644 --- a/src/backends/web.rs +++ b/src/backends/web.rs @@ -11,7 +11,7 @@ use web_sys::{OffscreenCanvas, OffscreenCanvasRenderingContext2d}; use crate::backend_interface::*; use crate::error::{InitError, SwResultExt}; -use crate::{util, AlphaMode, Pixel, Rect, SoftBufferError}; +use crate::{util, AlphaMode, Pixel, PixelFormat, Rect, SoftBufferError}; use std::marker::PhantomData; use std::num::NonZeroU32; @@ -167,8 +167,12 @@ impl SurfaceInterface for WebImpl } #[inline] - fn supports_alpha_mode(&self, alpha_mode: AlphaMode) -> bool { - matches!(alpha_mode, AlphaMode::Opaque | AlphaMode::Postmultiplied) + fn supported_pixel_formats(&self, alpha_mode: AlphaMode) -> &[PixelFormat] { + if matches!(alpha_mode, AlphaMode::Opaque | AlphaMode::Postmultiplied) { + &[PixelFormat::Rgba8] + } else { + &[] + } } /// De-duplicates the error handling between `HtmlCanvasElement` and `OffscreenCanvas`. @@ -178,6 +182,7 @@ impl SurfaceInterface for WebImpl width: NonZeroU32, height: NonZeroU32, _alpha_mode: AlphaMode, + _pixel_format: PixelFormat, ) -> Result<(), SoftBufferError> { if self.size != Some((width, height)) { self.buffer_presented = false; @@ -193,7 +198,11 @@ impl SurfaceInterface for WebImpl Ok(()) } - fn next_buffer(&mut self, _alpha_mode: AlphaMode) -> Result, SoftBufferError> { + fn next_buffer( + &mut self, + _alpha_mode: AlphaMode, + _pixel_format: PixelFormat, + ) -> Result, SoftBufferError> { Ok(BufferImpl { canvas: &self.canvas, buffer: &mut self.buffer, diff --git a/src/backends/win32.rs b/src/backends/win32.rs index 566fbff9..15fa7f2c 100644 --- a/src/backends/win32.rs +++ b/src/backends/win32.rs @@ -3,7 +3,7 @@ //! This module converts the input buffer into a bitmap and then stretches it to the window. use crate::backend_interface::*; -use crate::{util, AlphaMode, Pixel, Rect, SoftBufferError}; +use crate::{util, AlphaMode, Pixel, PixelFormat, Rect, SoftBufferError}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle}; use std::io; @@ -232,9 +232,13 @@ impl SurfaceInterface for Win32Im } #[inline] - fn supports_alpha_mode(&self, alpha_mode: AlphaMode) -> bool { - // TODO: Support transparency. - matches!(alpha_mode, AlphaMode::Opaque | AlphaMode::Ignored) + fn supported_pixel_formats(&self, alpha_mode: AlphaMode) -> &[PixelFormat] { + if matches!(alpha_mode, AlphaMode::Opaque | AlphaMode::Ignored) { + &[PixelFormat::Bgra8] + } else { + // TODO: Support transparency. + &[] + } } fn configure( @@ -242,6 +246,7 @@ impl SurfaceInterface for Win32Im width: NonZeroU32, height: NonZeroU32, alpha_mode: AlphaMode, + _pixel_format: PixelFormat, ) -> Result<(), SoftBufferError> { let (width, height) = (|| { let width = NonZeroI32::try_from(width).ok()?; @@ -266,7 +271,11 @@ impl SurfaceInterface for Win32Im Ok(()) } - fn next_buffer(&mut self, _alpha_mode: AlphaMode) -> Result, SoftBufferError> { + fn next_buffer( + &mut self, + _alpha_mode: AlphaMode, + _pixel_format: PixelFormat, + ) -> Result, SoftBufferError> { let buffer = self .buffer .as_mut() diff --git a/src/backends/x11.rs b/src/backends/x11.rs index c784128a..cc760b52 100644 --- a/src/backends/x11.rs +++ b/src/backends/x11.rs @@ -7,7 +7,7 @@ use crate::backend_interface::*; use crate::error::{InitError, SwResultExt}; -use crate::{util, AlphaMode, Pixel, Rect, SoftBufferError}; +use crate::{util, AlphaMode, Pixel, PixelFormat, Rect, SoftBufferError}; use raw_window_handle::{ HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle, XcbDisplayHandle, XcbWindowHandle, @@ -312,8 +312,12 @@ impl SurfaceInterface fo } #[inline] - fn supports_alpha_mode(&self, alpha_mode: AlphaMode) -> bool { - matches!(alpha_mode, AlphaMode::Opaque | AlphaMode::Ignored) + fn supported_pixel_formats(&self, alpha_mode: AlphaMode) -> &[PixelFormat] { + if matches!(alpha_mode, AlphaMode::Opaque | AlphaMode::Ignored) { + &[PixelFormat::Bgra8] + } else { + &[] + } } fn configure( @@ -321,6 +325,7 @@ impl SurfaceInterface fo width: NonZeroU32, height: NonZeroU32, _alpha_mode: AlphaMode, + _pixel_format: PixelFormat, ) -> Result<(), SoftBufferError> { tracing::trace!( "resize: window={:X}, size={}x{}", @@ -354,7 +359,11 @@ impl SurfaceInterface fo Ok(()) } - fn next_buffer(&mut self, _alpha_mode: AlphaMode) -> Result, SoftBufferError> { + fn next_buffer( + &mut self, + _alpha_mode: AlphaMode, + _pixel_format: PixelFormat, + ) -> Result, SoftBufferError> { tracing::trace!("next_buffer: window={:X}", self.window); // Finish waiting on the previous `shm::PutImage` request, if any. diff --git a/src/error.rs b/src/error.rs index 2ca6f885..bb1b7624 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,7 +3,7 @@ use std::error::Error; use std::fmt; use std::num::NonZeroU32; -use crate::AlphaMode; +use crate::{AlphaMode, PixelFormat}; #[derive(Debug)] #[non_exhaustive] @@ -94,6 +94,14 @@ pub enum SoftBufferError { alpha_mode: AlphaMode, }, + /// The provided alpha mode and pixel format is not supported by the backend. + UnsupportedPixelFormat { + /// The alpha mode that it was not supported with. + alpha_mode: AlphaMode, + /// The pixel format that was not supported. + pixel_format: PixelFormat, + }, + /// A platform-specific backend error occurred. /// /// The first field provides a human-readable description of the error. The second field @@ -137,6 +145,10 @@ impl fmt::Display for SoftBufferError { f, "Alpha mode {alpha_mode:?} is not supported by the backend.", ), + Self::UnsupportedPixelFormat { alpha_mode, pixel_format } => write!( + f, + "pixel format {pixel_format:?} is not supported by the backend with alpha mode {alpha_mode:?}", + ), Self::PlatformError(msg, None) => write!(f, "Platform error: {msg:?}"), Self::PlatformError(msg, Some(err)) => write!(f, "Platform error: {msg:?}: {err}"), Self::Unimplemented => write!(f, "This function is unimplemented on this platform."), diff --git a/src/format.rs b/src/format.rs index b4a1dad1..4847953d 100644 --- a/src/format.rs +++ b/src/format.rs @@ -1,10 +1,10 @@ -/// A pixel format that Softbuffer may use. +/// The pixel format of a surface and pixel buffer. /// /// # Alpha /// /// These pixel formats all include the alpha channel in their name, but formats that ignore the -/// alpha channel are supported if you set [`AlphaMode::Ignored`]. This will make `RGBA` mean `RGBX` -/// and `BGRA` mean `BGRX`. +/// alpha channel are supported if you use [`AlphaMode::Ignored`]. This will make `Rgba8` mean +/// `Rgbx8`, for example. /// /// [`AlphaMode::Ignored`]: crate::AlphaMode::Ignored /// @@ -13,27 +13,549 @@ /// The [`Default::default`] implementation returns the pixel format that Softbuffer uses for the /// current target platform. /// -/// Currently, this is [`BGRA`][Self::Bgra] on all platforms except WebAssembly and Android, where -/// it is [`RGBA`][Self::Rgba], since the API on these platforms does not support BGRA. +/// Currently, this is [`Bgra8`][Self::Bgra8] on all platforms except WebAssembly and Android, where +/// it is [`Rgba8`][Self::Rgba8], since the API on these platforms does not support BGRA. /// /// The format for a given platform may change in a non-breaking release if found to be more /// performant. /// /// This distinction should only be relevant if you're bitcasting `Pixel` to/from a `u32`, to e.g. /// avoid unnecessary copying, see the documentation for [`Pixel`][crate::Pixel] for examples. +/// +/// # Byte order +/// +/// Non-packed formats (also called array formats) such as [`PixelFormat::Rgb8`] are stored with +/// each component in byte order (in this case R in byte 0, G in byte 1 and B in byte 2). +/// +/// The recommended way to work with these is to split rows in chunks of the number of components: +/// +/// ```no_run +/// // Fill a buffer that uses `PixelFormat::Rgb8` with red. +/// # let buffer: softbuffer::Buffer<'_> = todo!(); +/// +/// let byte_stride = buffer.byte_stride().get() as usize; +/// let pixels = buffer.pixels(); +/// // SAFETY: `Pixel` can be reinterpreted as 4 `u8`s. +/// let data_u8 = unsafe { std::slice::from_raw_parts_mut(pixels.as_mut_ptr().cast::(), pixels.len() * 4) }; +/// +/// for row in data_u8.chunks_mut(byte_stride) { +/// // Split and ignore remaining bytes in each row used to align rows to CPU cache lines. +/// let (row, _remaining) = row.as_chunks_mut::<3>(); +/// for [r, g, b] in row { +/// *r = 0xff; +/// *g = 0x00; +/// *b = 0x00; +/// } +/// } +/// ``` +/// +/// When components are larger than one byte, such as in [`PixelFormat::Rgba16`], the data for each +/// component is stored in the target platform's native endianess, but components are still ordered +/// in byte-order (so in this case, on a little endian system, it would be stored in memory as +/// `R1,R0,G1,G0,B1,B0,A1,A0`). +/// +/// The recommended way to work with these is to first transmute the data to the component type (in +/// this case `u16`), and then split rows in chunks by of the number of components: +/// +/// ```no_run +/// // Fill a buffer that uses `PixelFormat::Rgba16` with red. +/// # let buffer: softbuffer::Buffer<'_> = todo!(); +/// +/// let byte_stride = buffer.byte_stride().get() as usize; +/// let pixels = buffer.pixels(); +/// // SAFETY: `Pixel` can be reinterpreted as 2 `u16`s. +/// let data_u16 = unsafe { std::slice::from_raw_parts_mut(pixels.as_mut_ptr().cast::(), pixels.len() * 2) }; +/// +/// for row in data_u16.chunks_mut(byte_stride) { +/// // Split and ignore remaining bytes in each row used to align rows to CPU cache lines. +/// let (row, _remaining) = row.as_chunks_mut::<4>(); +/// for [r, g, b, a] in row { +/// *r = 0xffff; +/// *g = 0x0000; +/// *b = 0x0000; +/// *a = 0x0000; +/// } +/// } +/// ``` +/// +/// Packed formats such as [`PixelFormat::A1bgr5`] are stored as an integer with same size, in the +/// target platform's native endianess (in this case, on a little endian system, it would be stored +/// in memory as `0bGGGBBBBB` in byte 0 and `0bARRRRGG` in byte 1). +/// +/// The recommended way to work with these is to first transform the data to an integer that can +/// contain the entire pixel (in this case `u16`), and then work with the data as that integer: +/// +/// ```no_run +/// // Fill a buffer that uses `PixelFormat::A1bgr5` with red. +/// # let buffer: softbuffer::Buffer<'_> = todo!(); +/// +/// let byte_stride = buffer.byte_stride().get() as usize; +/// let pixels = buffer.pixels(); +/// // SAFETY: `Pixel` can be reinterpreted as 2 `u16`s. +/// let data_u16 = unsafe { std::slice::from_raw_parts_mut(pixels.as_mut_ptr().cast::(), pixels.len() * 2) }; +/// +/// for row in data_u16.chunks_mut(byte_stride) { +/// for pixel in row { +/// *pixel = 0b0_11111_00000_00000; +/// } +/// } +/// ``` +/// +/// Finally, some formats such as [`PixelFormat::R2`] have multiple pixels packed into a single +/// byte. These are stored with the first pixel in the most significant bits (so in this case +/// `0b00112233`). TODO: Verify this! +/// +/// The recommended way to work with these is to iterate over the data as an `u8`, and then use +/// bitwise operators to write each pixel (or write them in bulk if you can): +/// +/// ```no_run +/// // Fill a buffer that uses `PixelFormat::R2` with gray. +/// # let buffer: softbuffer::Buffer<'_> = todo!(); +/// +/// let byte_stride = buffer.byte_stride().get() as usize; +/// let pixels = buffer.pixels(); +/// // SAFETY: `Pixel` can be reinterpreted as 4 `u8`s. +/// let data_u8 = unsafe { std::slice::from_raw_parts_mut(pixels.as_mut_ptr().cast::(), pixels.len() * 4) }; +/// +/// for row in data_u8.chunks_mut(byte_stride) { +/// for pixels in row { +/// *pixels = 0b00000000; // Clear +/// for i in 0..3 { +/// let pixel: u8 = 0b10; +/// *pixels |= pixel << i * 2; +/// } +/// } +/// } +/// ``` +/// +/// See also the [Pixel Format Guide](https://afrantzis.com/pixel-format-guide/). +#[non_exhaustive] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] pub enum PixelFormat { - /// The pixel format is `RGBA` (red, green, blue, alpha). + // This uses roughly the same naming scheme as WebGPU does. + + // + // Byte-aligned RGB formats + // + /// 24-bit BGR, `u8` per channel. Laid out in memory as `B,G,R`. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Rgb888")] + #[doc(alias = "VK_FORMAT_B8G8R8_UNORM")] + Bgr8, + + /// 24-bit RGB, `u8` per channel. Laid out in memory as `R,G,B`. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Bgr888")] + #[doc(alias = "VK_FORMAT_R8G8B8_UNORM")] + Rgb8, + + /// 32-bit BGRA, `u8` per channel. Laid out in memory as `B,G,R,A`. + /// + /// **This is currently the default on macOS/iOS, KMS/DRM, Orbital, Wayland, Windows and X11**. + /// + /// ## Platform Support /// - /// This is currently the default on macOS/iOS, KMS/DRM, Orbital, Wayland, Windows and X11. + /// - macOS/iOS, KMS/DRM, Orbital, Wayland, Windows and X11: Supported. + /// - Android and Web: Not yet supported. #[cfg_attr(not(any(target_family = "wasm", target_os = "android")), default)] - Bgra, - /// The pixel format is `BGRA` (blue, green, red, alpha). + #[doc(alias = "Argb8888")] + #[doc(alias = "Xrgb8888")] + #[doc(alias = "VK_FORMAT_B8G8R8A8_UNORM")] + Bgra8, + + /// 32-bit RGBA, `u8` per channel. Laid out in memory as `R,G,B,A`. + /// + /// **This is currently the default on Android and Web**. + /// + /// ## Platform Support /// - /// This is currently the default on Android and Web. + /// - Android and Web: Supported. + /// - macOS/iOS, KMS/DRM, Orbital, Wayland, Windows and X11: Not yet supported. #[cfg_attr(any(target_family = "wasm", target_os = "android"), default)] - Rgba, - // Intentionally exhaustive for now. + #[doc(alias = "Abgr8888")] + #[doc(alias = "Xbgr8888")] + #[doc(alias = "VK_FORMAT_R8G8B8A8_UNORM")] + Rgba8, + + /// 32-bit ABGR, `u8` per channel. Laid out in memory as `A,B,G,R`. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Rgba8888")] + #[doc(alias = "Rgbx8888")] + Abgr8, + + /// 32-bit ARGB, `u8` per channel. Laid out in memory as `A,R,G,B`. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Bgra8888")] + #[doc(alias = "Bgrx8888")] + Argb8, + + /// 48-bit BGR, `u16` per channel. Laid out in memory as `B0,B1,G0,G1,R0,R1` (big endian) or + /// `B1,B0,G1,G0,R1,R0` (little endian). + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + Bgr16, + + /// 48-bit RGB, `u16` per channel. Laid out in memory as `R0,R1,G0,G1,B0,B1` (big endian) or + /// `R1,R0,G1,G0,B1,B0` (little endian). + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "VK_FORMAT_R16G16B16_UNORM")] + Rgb16, + + /// 64-bit BGRA, `u16` per channel. Laid out in memory as `B0,B1,G0,G1,R0,R1,A0,A1` (big endian) + /// or `B1,B0,G1,G0,R1,R0,A1,A0` (little endian). + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Argb16161616")] + #[doc(alias = "Xrgb16161616")] + Bgra16, + + /// 64-bit RGBA, `u16` per channel. Laid out in memory as `R0,R1,G0,G1,B0,B1,A0,A1` (big endian) + /// or `R1,R0,G1,G0,B1,B0,A1,A0` (little endian). + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Abgr16161616")] + #[doc(alias = "Xbgr16161616")] + #[doc(alias = "VK_FORMAT_R16G16B16A16_UNORM")] + Rgba16, + + /// 64-bit ABGR, `u16` per channel. Laid out in memory as `A0,A1,B0,B1,G0,G1,R0,R1` (big endian) + /// or `A1,A0,B1,B0,G1,G0,R1,R0` (little endian). + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Rgba16161616")] + #[doc(alias = "Rgbx16161616")] + Abgr16, + + /// 64-bit ARGB, `u16` per channel. Laid out in memory as `A1,A0,R0,R1,G0,G1,B0,B1` (big endian) + /// or `A1,A0,R1,R0,G1,G0,B1,B0` (little endian). + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Bgra16161616")] + #[doc(alias = "Bgrx16161616")] + Argb16, + + // + // Grayscale formats + // + /// 1-bit grayscale. 8 pixels are packed into a single byte as `0b0_1_2_3_4_5_6_7`. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + R1, + + /// 2-bit grayscale. 4 pixels are packed into a single byte as `0b00_11_22_33`. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + R2, + + /// 4-bit grayscale. 2 pixels are packed into a single byte as `0b0000_1111`. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + R4, + + /// 8-bit grayscale, `u8` per channel. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "VK_FORMAT_R8_UNORM")] + R8, + + /// 16-bit grayscale, `u16` per channel. Laid out in memory as `R0,R1` (big endian) or + /// `R1,R0` (little endian). + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "VK_FORMAT_R16_UNORM")] + R16, + + // TODO: R16f? + // TODO: R32f? + + // + // Packed RGB formats + // + /// 8-bit BGR. Packed into a `u8` as `0bBB_GGG_RR`. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Bgr233")] + B2g3r3, + + /// 8-bit RGB. Packed into a `u8` as `0bRRR_GGG_BB`. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Rgb332")] + R3g3b2, + + /// 16-bit BGR. Packed into a `u16` as `0bBBBBB_GGGGGG_RRRRR`. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Bgr565")] + #[doc(alias = "VK_FORMAT_B5G6R5_UNORM_PACK16")] + B5g6r5, + + /// 16-bit RGB. Packed into a `u16` as `0bRRRRR_GGGGGG_BBBBB`. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Rgb565")] + #[doc(alias = "VK_FORMAT_R5G6B5_UNORM_PACK16")] + R5g6b5, + + /// 16-bit BGRA. Packed into a `u16` as `0bBBBB_GGGG_RRRR_AAAA`. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Bgra4444")] + #[doc(alias = "Bgrx4444")] + #[doc(alias = "VK_FORMAT_B4G4R4A4_UNORM_PACK16")] + Bgra4, + + /// 16-bit RGBA. Packed into a `u16` as `0bRRRR_GGGG_BBBB_AAAA`. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Rgba4444")] + #[doc(alias = "Rgbx4444")] + #[doc(alias = "VK_FORMAT_R4G4B4A4_UNORM_PACK16")] + Rgba4, + + /// 16-bit ABGR. Packed into a `u16` as `0bAAAA_BBBB_GGGG_RRRR`. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Abgr4444")] + #[doc(alias = "Xbgr4444")] + Abgr4, + + /// 16-bit ARGB. Packed into a `u16` as `0bAAAA_RRRR_GGGG_BBBB`. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Argb4444")] + #[doc(alias = "Xrgb4444")] + Argb4, + + /// 16-bit BGRA. Packed into a `u16` as `0bBBBBB_GGGGG_RRRRR_A`. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Bgrx5551")] + #[doc(alias = "VK_FORMAT_B5G5R5A1_UNORM_PACK16")] + Bgr5a1, + + /// 16-bit RGBA. Packed into a `u16` as `0bRRRRR_GGGGG_BBBBB_A`. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Rgbx5551")] + #[doc(alias = "VK_FORMAT_R5G5B5A1_UNORM_PACK16")] + Rgb5a1, + + /// 16-bit ABGR. Packed into a `u16` as `0bA_BBBBB_GGGGG_RRRRR`. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Xbgr1555")] + A1bgr5, + + /// 16-bit ARGB. Packed into a `u16` as `0bA_RRRRR_GGGGG_BBBBB`. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Xrgb1555")] + #[doc(alias = "VK_FORMAT_A1R5G5B5_UNORM_PACK16")] + A1rgb5, + + /// 32-bit BGRA. Packed into a `u32` as `0bBBBBBBBBBB_GGGGGGGGGG_RRRRRRRRRR_AA`. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Bgra1010102")] + #[doc(alias = "Bgrx1010102")] + Bgr10a2, + + /// 32-bit RGBA. Packed into a `u32` as `0bRRRRRRRRRR_GGGGGGGGGG_BBBBBBBBBB_AA`. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Rgba1010102")] + #[doc(alias = "Rgbx1010102")] + Rgb10a2, + + /// 32-bit ABGR. Packed into a `u32` as `0bAA_BBBBBBBBBB_GGGGGGGGGG_RRRRRRRRRR`. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Abgr2101010")] + #[doc(alias = "Xbgr2101010")] + #[doc(alias = "VK_FORMAT_A2B10G10R10_UNORM_PACK32")] + A2bgr10, + + /// 32-bit ARGB. Packed into a `u32` as `0bAA_RRRRRRRRRR_GGGGGGGGGG_BBBBBBBBBB`. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Argb2101010")] + #[doc(alias = "Xrgb2101010")] + #[doc(alias = "VK_FORMAT_A2R10G10B10_UNORM_PACK32")] + A2rgb10, + + // + // Floating point RGB formats. + // + /// 48-bit floating-point BGR, `f16` per channel. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + Bgr16f, + + /// 48-bit floating-point RGB, `f16` per channel. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + Rgb16f, + + /// 64-bit floating-point BGRA, `f16` per channel. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Argb16161616f")] + #[doc(alias = "Xrgb16161616f")] + Bgra16f, + + /// 64-bit floating-point RGBA, `f16` per channel. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Abgr16161616f")] + #[doc(alias = "Xbgr16161616f")] + Rgba16f, + + /// 64-bit floating-point ABGR, `f16` per channel. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Rgba16161616f")] + #[doc(alias = "Rgbx16161616f")] + Abgr16f, + + /// 64-bit floating-point ARGB, `f16` per channel. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Bgra16161616f")] + #[doc(alias = "Bgrx16161616f")] + Argb16f, + + /// 96-bit floating-point BGR, `f32` per channel. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + Bgr32f, + + /// 96-bit floating-point RGB, `f32` per channel. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "VK_FORMAT_R32G32B32_SFLOAT")] + Rgb32f, + + /// 128-bit floating-point BGRA, `f32` per channel. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Argb32323232f")] + #[doc(alias = "Xrgb32323232f")] + Bgra32f, + + /// 128-bit floating-point RGBA, `f32` per channel. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Abgr32323232f")] + #[doc(alias = "Xbgr32323232f")] + #[doc(alias = "VK_FORMAT_R32G32B32A32_SFLOAT")] + Rgba32f, + + /// 128-bit floating-point ABGR, `f32` per channel. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Rgba32323232f")] + #[doc(alias = "Rgbx32323232f")] + Abgr32f, + + /// 128-bit floating-point ARGB, `f32` per channel. + /// + /// ## Platform Support + /// + /// Not yet supported by any backend. + #[doc(alias = "Bgra32323232f")] + #[doc(alias = "Bgrx32323232f")] + Argb32f, + // TODO: AYCbCr formats? } impl PixelFormat { @@ -42,4 +564,51 @@ impl PixelFormat { pub fn is_default(self) -> bool { self == Self::default() } + + /// The number of bits wide that a single pixel in a buffer would be. + #[doc(alias = "bpp")] + #[inline] + pub const fn bits_per_pixel(self) -> u8 { + match self { + Self::Rgb8 | Self::Bgr8 => 24, + Self::Bgra8 | Self::Rgba8 | Self::Abgr8 | Self::Argb8 => 32, + Self::Rgb16 | Self::Bgr16 => 48, + Self::Bgra16 | Self::Rgba16 | Self::Abgr16 | Self::Argb16 => 64, + Self::R1 => 1, + Self::R2 => 2, + Self::R4 => 4, + Self::R8 => 8, + Self::R16 => 16, + Self::B2g3r3 | Self::R3g3b2 => 8, + Self::B5g6r5 | Self::R5g6b5 => 16, + Self::Bgra4 | Self::Rgba4 | Self::Abgr4 | Self::Argb4 => 16, + Self::Bgr5a1 | Self::Rgb5a1 | Self::A1bgr5 | Self::A1rgb5 => 16, + Self::Bgr10a2 | Self::Rgb10a2 | Self::A2bgr10 | Self::A2rgb10 => 32, + Self::Bgr16f | Self::Rgb16f => 48, + Self::Bgra16f | Self::Rgba16f | Self::Abgr16f | Self::Argb16f => 64, + Self::Bgr32f | Self::Rgb32f => 96, + Self::Bgra32f | Self::Rgba32f | Self::Abgr32f | Self::Argb32f => 128, + } + } + + /// The number of components this format has. + #[inline] + pub const fn components(self) -> u8 { + match self { + Self::Rgb8 | Self::Bgr8 => 3, + Self::Bgra8 | Self::Rgba8 | Self::Abgr8 | Self::Argb8 => 4, + Self::Rgb16 | Self::Bgr16 => 3, + Self::Bgra16 | Self::Rgba16 | Self::Abgr16 | Self::Argb16 => 4, + Self::R1 | Self::R2 | Self::R4 | Self::R8 | Self::R16 => 1, + Self::B2g3r3 | Self::R3g3b2 => 3, + Self::B5g6r5 | Self::R5g6b5 => 3, + Self::Bgra4 | Self::Rgba4 | Self::Abgr4 | Self::Argb4 => 4, + Self::Bgr5a1 | Self::Rgb5a1 | Self::A1bgr5 | Self::A1rgb5 => 4, + Self::Bgr10a2 | Self::Rgb10a2 | Self::A2bgr10 | Self::A2rgb10 => 4, + Self::Bgr16f | Self::Rgb16f => 3, + Self::Bgra16f | Self::Rgba16f | Self::Abgr16f | Self::Argb16f => 4, + Self::Bgr32f | Self::Rgb32f => 3, + Self::Bgra32f | Self::Rgba32f | Self::Abgr32f | Self::Argb32f => 4, + } + } } diff --git a/src/lib.rs b/src/lib.rs index 5e24ede2..31958c0d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,6 +79,8 @@ pub struct Surface { surface_impl: Box>, /// The current alpha mode that the surface is configured with. alpha_mode: AlphaMode, + /// The current pixel format that the surface is configured with. + pixel_format: PixelFormat, _marker: PhantomData>, } @@ -99,6 +101,7 @@ impl Surface { Ok(surface_dispatch) => Ok(Self { alpha_mode: AlphaMode::default(), surface_impl: Box::new(surface_dispatch), + pixel_format: PixelFormat::default(), _marker: PhantomData, }), Err(InitError::Unsupported(window)) => { @@ -124,15 +127,25 @@ impl Surface { self.alpha_mode } + /// The current pixel format of the surface. + pub fn pixel_format(&self) -> PixelFormat { + self.pixel_format + } + /// Set the size of the buffer. /// /// This is a convenience method for reconfiguring when only the size changes, it is equivalent /// to: - /// ```ignore - /// surface.configure(width, height, surface.alpha_mode()); - /// ```` + /// ``` + /// # use std::num::NonZeroU32; + /// # use softbuffer::Surface; + /// # use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; + /// # fn wrapper(mut surface: Surface, width: NonZeroU32, height: NonZeroU32) { + /// surface.configure(width, height, surface.alpha_mode(), surface.pixel_format()); + /// # } + /// ``` pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { - self.configure(width, height, self.alpha_mode()) + self.configure(width, height, self.alpha_mode, self.pixel_format) } /// Reconfigure the surface's properties. @@ -152,35 +165,58 @@ impl Surface { width: NonZeroU32, height: NonZeroU32, alpha_mode: AlphaMode, + pixel_format: PixelFormat, ) -> Result<(), SoftBufferError> { if u32::MAX / 4 < width.get() { // Stride would be too large. return Err(SoftBufferError::SizeOutOfRange { width, height }); } - if !self.supports_alpha_mode(alpha_mode) { + let supported_pixel_formats = self.supported_pixel_formats(alpha_mode); + + if supported_pixel_formats.is_empty() { return Err(SoftBufferError::UnsupportedAlphaMode { alpha_mode }); } - self.surface_impl.configure(width, height, alpha_mode)?; + if !supported_pixel_formats.contains(&pixel_format) { + return Err(SoftBufferError::UnsupportedPixelFormat { + alpha_mode, + pixel_format, + }); + } + + self.surface_impl + .configure(width, height, alpha_mode, pixel_format)?; self.alpha_mode = alpha_mode; + self.pixel_format = pixel_format; Ok(()) } - /// Query if the given alpha mode is supported. + /// The pixel modes that are supported for a given alpha mode. /// - /// See [`AlphaMode`] for documentation on what this will (currently) return on each platform. + /// This returns an empty list if the alpha mode isn't supported. #[inline] - pub fn supports_alpha_mode(&self, alpha_mode: AlphaMode) -> bool { - // TODO: Once we get pixel formats, replace this with something like: - // fn supported_pixel_formats(&self, alpha_mode: AlphaMode) -> &[PixelFormat]; - // - // And return an empty list from that if the alpha mode isn't supported. - self.surface_impl.supports_alpha_mode(alpha_mode) + pub fn supported_pixel_formats(&self, alpha_mode: AlphaMode) -> &[PixelFormat] { + let formats = self.surface_impl.supported_pixel_formats(alpha_mode); + + debug_assert!( + formats.is_empty() || formats.contains(&PixelFormat::default()), + "must support at least default pixel format", + ); + + debug_assert!( + alpha_mode != AlphaMode::Opaque || !formats.is_empty(), + "opaque alpha mode must be supported", + ); + + formats } + // TODO: Add `fn supports_pixel_format(&self, alpha_mode: AlphaMode, pixel_format: PixelFormat) -> bool`? + // TODO: Add `fn supports_alpha_mode(&self, alpha_mode: AlphaMode) -> bool`? + /// Copies the window contents into a buffer. /// /// ## Platform Dependent Behavior @@ -206,28 +242,31 @@ impl Surface { /// `softbuffer`. Therefore it is the responsibility of the user to wait for the page flip before /// sending another frame. pub fn next_buffer(&mut self) -> Result, SoftBufferError> { - let alpha_mode = self.alpha_mode(); - - let mut buffer_impl = self.surface_impl.next_buffer(alpha_mode)?; + let mut buffer_impl = self + .surface_impl + .next_buffer(self.alpha_mode, self.pixel_format)?; debug_assert_eq!( buffer_impl.byte_stride().get() % 4, 0, - "stride must be a multiple of 4" + "stride must be a multiple of 4", + // Required for a row of `&mut [u32]` to be sound. ); debug_assert_eq!( buffer_impl.height().get() as usize * buffer_impl.byte_stride().get() as usize, buffer_impl.pixels_mut().len() * 4, - "buffer must be sized correctly" + "buffer must be sized correctly", ); debug_assert!( - buffer_impl.width().get() * 4 <= buffer_impl.byte_stride().get(), - "width * 4 must be less than or equal to stride" + buffer_impl.width().get() as usize * self.pixel_format.bits_per_pixel() as usize / 8 + <= buffer_impl.byte_stride().get() as usize, + "width * bpp / 8 must be less than or equal to stride", ); Ok(Buffer { buffer_impl, - alpha_mode, + alpha_mode: self.alpha_mode, + pixel_format: self.pixel_format, _marker: PhantomData, }) } @@ -318,19 +357,22 @@ impl HasWindowHandle for Surface pub struct Buffer<'surface> { buffer_impl: BufferDispatch<'surface>, alpha_mode: AlphaMode, + pixel_format: PixelFormat, _marker: PhantomData>, } impl Buffer<'_> { /// The number of bytes wide each row in the buffer is. /// - /// On some platforms, the buffer is slightly larger than `width * height * 4`, usually for - /// performance reasons to align each row such that they are never split across cache lines. + /// On some platforms, the buffer is slightly larger than + /// `width * height * pixel_format.bits_per_pixel() / 8`, usually for performance reasons to + /// align each row such that they are never split across cache lines. /// /// In your code, prefer to use [`Buffer::pixel_rows`] (which handles this correctly), or /// failing that, make sure to chunk rows by the stride instead of the width. /// - /// This is guaranteed to be `>= width * 4`, and is guaranteed to be a multiple of 4. + /// This is guaranteed to be `>= width * pixel_format.bits_per_pixel() / 8`, and is guaranteed + /// to be a multiple of 4. #[doc(alias = "pitch")] // SDL and Direct3D #[doc(alias = "bytes_per_row")] // WebGPU #[doc(alias = "row_stride")] @@ -354,6 +396,11 @@ impl Buffer<'_> { self.alpha_mode } + /// The pixel format that the buffer uses. + pub fn pixel_format(&self) -> PixelFormat { + self.pixel_format + } + /// `age` is the number of frames ago this buffer was last presented. So if the value is /// `1`, it is the same as the last frame, and if it is `2`, it is the same as the frame /// before that (for backends using double buffering). If the value is `0`, it is a new @@ -402,7 +449,10 @@ impl Buffer<'_> { /// Otherwise this is equivalent to [`Self::present`]. pub fn present_with_damage(mut self, damage: &[Rect]) -> Result<(), SoftBufferError> { // Verify that pixels are set as opaque if the alpha mode requires it. - if cfg!(debug_assertions) && self.alpha_mode == AlphaMode::Opaque { + if cfg!(debug_assertions) + && self.alpha_mode == AlphaMode::Opaque + && self.pixel_format == PixelFormat::default() + { // Only check inside `width`, pixels outside are allowed to be anything. let width = self.width().get() as usize; for (y, row) in self.pixel_rows().enumerate() { @@ -456,13 +506,9 @@ impl Buffer<'_> { /// # let width = std::num::NonZero::new(1).unwrap(); /// # let height = std::num::NonZero::new(1).unwrap(); /// - /// // At surface creation: - /// if surface.supports_alpha_mode(AlphaMode::Ignored) { - /// // Try to use `AlphaMode::Ignored` if possible. - /// surface.configure(width, height, AlphaMode::Ignored); - /// } else { - /// // Fall back to `AlphaMode::Opaque` if not. - /// surface.configure(width, height, AlphaMode::Opaque); + /// // After surface creation, try to configure RGBX if possible: + /// if surface.supported_pixel_formats(AlphaMode::Ignored).contains(&PixelFormat::Rgba8) { + /// surface.configure(width, height, AlphaMode::Ignored, PixelFormat::Rgba8).unwrap(); /// } /// /// // Each draw: @@ -471,7 +517,7 @@ impl Buffer<'_> { /// let width = buffer.width().get(); /// let height = buffer.height().get(); /// - /// if PixelFormat::Rgba.is_default() + /// if buffer.pixel_format() == PixelFormat::Rgba8 /// && buffer.byte_stride().get() == width * 4 /// && buffer.alpha_mode() == AlphaMode::Ignored /// { @@ -480,7 +526,7 @@ impl Buffer<'_> { /// // SAFETY: `Pixel` can be reinterpreted as `[u8; 4]`. /// let pixels = unsafe { std::mem::transmute::<&mut [Pixel], &mut [[u8; 4]]>(buffer.pixels()) }; /// // CORRECTNESS: We just checked that: - /// // - The format is RGBA. + /// // - The format is `Rgba8`. /// // - The `stride == width * 4`. /// // - The alpha channel is ignored (A -> X). /// // @@ -630,8 +676,8 @@ impl Buffer<'_> { /// See [the WhatWG spec][whatwg-premultiplied] for a good description of the difference between /// premultiplied and postmultiplied alpha. /// -/// Query [`Surface::supports_alpha_mode`] to figure out supported modes for the current platform / -/// surface. +/// Query [`Surface::supported_pixel_formats`] to figure out supported modes for the current +/// platform / surface. /// /// [whatwg-premultiplied]: https://html.spec.whatwg.org/multipage/canvas.html#premultiplied-alpha-and-the-2d-rendering-context #[doc(alias = "Transparency")] diff --git a/src/pixel.rs b/src/pixel.rs index a6a05907..a167c54f 100644 --- a/src/pixel.rs +++ b/src/pixel.rs @@ -43,8 +43,9 @@ /// let red = unsafe { core::mem::transmute::(red) }; /// /// match PixelFormat::default() { -/// PixelFormat::Bgra => assert_eq!(red[2], 255), -/// PixelFormat::Rgba => assert_eq!(red[0], 255), +/// PixelFormat::Bgra8 => assert_eq!(red[2], 255), +/// PixelFormat::Rgba8 => assert_eq!(red[0], 255), +/// format => unimplemented!("unknown default pixel format: {format:?}"), /// } /// ``` /// @@ -58,8 +59,9 @@ /// let red = unsafe { core::mem::transmute::(red) }; /// /// match PixelFormat::default() { -/// PixelFormat::Bgra => assert_eq!(red, u32::from_ne_bytes([0x00, 0x00, 0xff, 0xff])), -/// PixelFormat::Rgba => assert_eq!(red, u32::from_ne_bytes([0xff, 0x00, 0x00, 0xff])), +/// PixelFormat::Bgra8 => assert_eq!(red, u32::from_ne_bytes([0x00, 0x00, 0xff, 0xff])), +/// PixelFormat::Rgba8 => assert_eq!(red, u32::from_ne_bytes([0xff, 0x00, 0x00, 0xff])), +/// format => unimplemented!("unknown default pixel format: {format:?}"), /// } /// ``` #[repr(C)] diff --git a/src/util.rs b/src/util.rs index 169f3b6b..d10a311b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -102,7 +102,7 @@ pub(crate) fn to_i32_saturating(val: u32) -> i32 { /// TODO(madsmtm): This should take the pixel format / bit depth as input after: /// #[inline] -pub(crate) fn byte_stride(width: u32) -> u32 { +pub(crate) fn byte_stride(width: u32, bits_per_pixel: u8) -> u32 { let row_alignment = if cfg!(debug_assertions) { 16 // Use a higher alignment to help users catch issues with their stride calculations. } else { @@ -110,5 +110,5 @@ pub(crate) fn byte_stride(width: u32) -> u32 { }; // TODO: Use `next_multiple_of` when in MSRV. let mask = row_alignment * 4 - 1; - ((width * 32 + mask) & !mask) >> 3 + ((width * bits_per_pixel as u32 + mask) & !mask) >> 3 }