diff --git a/CHANGELOG.md b/CHANGELOG.md index 252e7e0a..dd8c2e85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # 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 `Buffer::pixels()` for accessing the buffer's pixel data. - Added `Buffer::pixel_rows()` for iterating over rows of the buffer data. diff --git a/Cargo.toml b/Cargo.toml index 4959d886..60fa461c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,6 +86,7 @@ drm = { version = "0.14.1", default-features = false, optional = true } [target.'cfg(target_os = "windows")'.dependencies] windows-sys = { version = "0.61.2", features = [ "Win32_Graphics_Gdi", + "Win32_UI_ColorSystem", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging", "Win32_Foundation", diff --git a/examples/transparency.rs b/examples/transparency.rs new file mode 100644 index 00000000..e349f2d1 --- /dev/null +++ b/examples/transparency.rs @@ -0,0 +1,166 @@ +//! An example to test transparent rendering. +//! +//! Press `o`, `i`, `m` or `t` to change the alpha mode to `Opaque`, `Ignored`, `Premultiplied` and +//! `Postmultiplied` respectively. +//! +//! This should render 6 rectangular areas. For details on the terminology, see: +//! +//! +//! (255, 127, 0, 255): +//! - Opaque/Ignored: Completely-opaque orange. +//! - Postmultiplied: Completely-opaque orange. +//! - Premultiplied: Completely-opaque orange. +//! +//! (255, 255, 0, 127): +//! - Opaque/Ignored: Completely-opaque yellow. +//! - Postmultiplied: Halfway-opaque yellow. +//! - Premultiplied: Additive halfway-opaque yellow. +//! +//! (127, 127, 0, 127): +//! - Opaque/Ignored: Completely-opaque dark yellow. +//! - Postmultiplied: Halfway-opaque dark yellow. +//! - Premultiplied: Halfway-opaque yellow. +//! +//! (255, 127, 0, 127): +//! - Opaque/Ignored: Completely-opaque orange. +//! - Postmultiplied: Halfway-opaque orange. +//! - Premultiplied: Additive halfway-opaque orange. +//! +//! (255, 127, 0, 0): +//! - Opaque/Ignored: Completely-opaque orange. +//! - Postmultiplied: Fully-transparent orange. +//! - Premultiplied: Additive fully-transparent orange. +//! +//! (0, 0, 0, 0): +//! - Opaque/Ignored: Completely-opaque black. +//! - Postmultiplied: Fully-transparent. +//! - Premultiplied: Fully-transparent. +use softbuffer::{AlphaMode, Context, Pixel, Surface}; +use std::num::NonZeroU32; +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 app = util::WinitAppBuilder::with_init( + |elwt| util::make_window(elwt, |w| w), + move |_elwt, window| Surface::new(&context, window.clone()).unwrap(), + ) + .with_event_handler(|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!(alpha_mode = ?surface.alpha_mode(), "redraw"); + + let alpha_mode = surface.alpha_mode(); + let mut buffer = surface.buffer_mut().unwrap(); + let width = buffer.width().get(); + for (x, _, pixel) in buffer.pixels_iter() { + let rectangle_number = (x * 6) / width; + *pixel = match rectangle_number { + 0 => Pixel::new_rgba(255, 127, 0, 255), + 1 => Pixel::new_rgba(255, 255, 0, 127), + 2 => Pixel::new_rgba(127, 127, 0, 127), + 3 => Pixel::new_rgba(255, 127, 0, 127), + 4 => Pixel::new_rgba(255, 127, 0, 0), + _ => Pixel::new_rgba(0, 0, 0, 0), + }; + + // Convert `AlphaMode::Opaque` -> `AlphaMode::Ignored`. + if alpha_mode == AlphaMode::Opaque { + pixel.a = 255; + }; + } + + 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; + }; + + let alpha_mode = match logical_key.to_text() { + Some("o") => AlphaMode::Opaque, + Some("i") => AlphaMode::Ignored, + Some("m") => AlphaMode::Premultiplied, + Some("t") => AlphaMode::Postmultiplied, + _ => return, + }; + + if !surface.supports_alpha_mode(alpha_mode) { + tracing::warn!(?alpha_mode, "not supported by the backend"); + return; + } + + tracing::info!(?alpha_mode, "set alpha"); + 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(); + assert_eq!(surface.alpha_mode(), alpha_mode); + + window.set_transparent(matches!( + alpha_mode, + AlphaMode::Premultiplied | AlphaMode::Postmultiplied + )); + + window.request_redraw(); + } + _ => {} + } + }); + + util::run_app(event_loop, app); +} diff --git a/src/backend_dispatch.rs b/src/backend_dispatch.rs index 30a8f6d4..a3071395 100644 --- a/src/backend_dispatch.rs +++ b/src/backend_dispatch.rs @@ -1,6 +1,6 @@ //! Implements `buffer_interface::*` traits for enums dispatching to backends -use crate::{backend_interface::*, backends, InitError, Pixel, Rect, SoftBufferError}; +use crate::{backend_interface::*, backends, AlphaMode, InitError, Pixel, Rect, SoftBufferError}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use std::fmt; @@ -99,20 +99,30 @@ macro_rules! make_dispatch { } } - fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { + #[inline] + fn supports_alpha_mode(&self, alpha_mode: AlphaMode) -> bool { + match self { + $( + $(#[$attr])* + Self::$name(inner) => inner.supports_alpha_mode(alpha_mode), + )* + } + } + + fn configure(&mut self, width: NonZeroU32, height: NonZeroU32, alpha_mode: AlphaMode) -> Result<(), SoftBufferError> { match self { $( $(#[$attr])* - Self::$name(inner) => inner.resize(width, height), + Self::$name(inner) => inner.configure(width, height, alpha_mode), )* } } - fn buffer_mut(&mut self) -> Result, SoftBufferError> { + fn buffer_mut(&mut self, alpha_mode: AlphaMode) -> Result, SoftBufferError> { match self { $( $(#[$attr])* - Self::$name(inner) => Ok(BufferDispatch::$name(inner.buffer_mut()?)), + Self::$name(inner) => Ok(BufferDispatch::$name(inner.buffer_mut(alpha_mode)?)), )* } } diff --git a/src/backend_interface.rs b/src/backend_interface.rs index 54822acd..146bf83b 100644 --- a/src/backend_interface.rs +++ b/src/backend_interface.rs @@ -1,6 +1,6 @@ //! Interface implemented by backends -use crate::{InitError, Pixel, Rect, SoftBufferError}; +use crate::{AlphaMode, InitError, Pixel, Rect, SoftBufferError}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use std::num::NonZeroU32; @@ -22,12 +22,23 @@ pub(crate) trait SurfaceInterface &W; - /// Resize the internal buffer to the given width and height. - fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError>; + + fn supports_alpha_mode(&self, alpha_mode: AlphaMode) -> bool; + + /// Reconfigure the internal buffer(s). + fn configure( + &mut self, + width: NonZeroU32, + height: NonZeroU32, + alpha_mode: AlphaMode, + ) -> Result<(), SoftBufferError>; + /// Get a mutable reference to the buffer. - fn buffer_mut(&mut self) -> Result, SoftBufferError>; + fn buffer_mut(&mut self, alpha_mode: AlphaMode) -> Result, SoftBufferError>; + /// Fetch the buffer from the window. fn fetch(&mut self) -> Result, SoftBufferError> { Err(SoftBufferError::Unimplemented) diff --git a/src/backends/android.rs b/src/backends/android.rs index 8a521b8d..e18d2af5 100644 --- a/src/backends/android.rs +++ b/src/backends/android.rs @@ -12,7 +12,7 @@ use raw_window_handle::AndroidNdkWindowHandle; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle}; use crate::error::InitError; -use crate::{util, BufferInterface, Pixel, Rect, SoftBufferError, SurfaceInterface}; +use crate::{util, AlphaMode, BufferInterface, Pixel, Rect, SoftBufferError, SurfaceInterface}; /// The handle to a window for software buffering. #[derive(Debug)] @@ -52,8 +52,18 @@ impl SurfaceInterface for Android &self.window } - /// Also changes the pixel format to [`HardwareBufferFormat::R8G8B8A8_UNORM`]. - fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { + #[inline] + fn supports_alpha_mode(&self, alpha_mode: AlphaMode) -> bool { + matches!(alpha_mode, AlphaMode::Opaque | AlphaMode::Ignored) + } + + /// Also changes the pixel format. + fn configure( + &mut self, + width: NonZeroU32, + height: NonZeroU32, + alpha_mode: AlphaMode, + ) -> Result<(), SoftBufferError> { let (width, height) = (|| { let width = NonZeroI32::try_from(width).ok()?; let height = NonZeroI32::try_from(height).ok()?; @@ -61,22 +71,26 @@ impl SurfaceInterface for Android })() .ok_or(SoftBufferError::SizeOutOfRange { width, height })?; + // Default is typically R5G6B5 16bpp, switch to 32bpp + let pixel_format = match alpha_mode { + AlphaMode::Opaque | AlphaMode::Ignored => HardwareBufferFormat::R8G8B8X8_UNORM, + AlphaMode::Premultiplied => todo!("HardwareBufferFormat::R8G8B8A8_UNORM"), + _ => unimplemented!(), + }; + self.native_window - .set_buffers_geometry( - width.into(), - height.into(), - // Default is typically R5G6B5 16bpp, switch to 32bpp - Some(HardwareBufferFormat::R8G8B8X8_UNORM), - ) + .set_buffers_geometry(width.into(), height.into(), Some(pixel_format)) .map_err(|err| { SoftBufferError::PlatformError( Some("Failed to set buffer geometry on ANativeWindow".to_owned()), Some(Box::new(err)), ) - }) + })?; + + Ok(()) } - fn buffer_mut(&mut self) -> Result, SoftBufferError> { + fn buffer_mut(&mut self, _alpha_mode: AlphaMode) -> 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 1048596f..3b85292d 100644 --- a/src/backends/cg.rs +++ b/src/backends/cg.rs @@ -1,6 +1,6 @@ //! Softbuffer implementation using CoreGraphics. -use crate::backend_interface::*; use crate::error::InitError; +use crate::{backend_interface::*, AlphaMode}; use crate::{util, Pixel, Rect, SoftBufferError}; use objc2::rc::Retained; use objc2::runtime::{AnyObject, Bool}; @@ -252,19 +252,42 @@ impl SurfaceInterface for CGImpl< &self.window_handle } - fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { + #[inline] + fn supports_alpha_mode(&self, _alpha_mode: AlphaMode) -> bool { + true + } + + fn configure( + &mut self, + width: NonZeroU32, + height: NonZeroU32, + alpha_mode: AlphaMode, + ) -> Result<(), SoftBufferError> { + let opaque = matches!(alpha_mode, AlphaMode::Opaque | AlphaMode::Ignored); + self.layer.setOpaque(opaque); + // TODO: Set opaque-ness on root layer too? Is that our responsibility, or Winit's? + // self.root_layer.setOpaque(opaque); + self.width = width.get() as usize; self.height = height.get() as usize; Ok(()) } - fn buffer_mut(&mut self) -> Result, SoftBufferError> { + fn buffer_mut(&mut self, alpha_mode: AlphaMode) -> Result, SoftBufferError> { let buffer_size = util::byte_stride(self.width as u32) as usize * self.height / 4; Ok(BufferImpl { buffer: util::PixelBuffer(vec![Pixel::default(); buffer_size]), 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, + }, layer: &mut self.layer, }) } @@ -276,6 +299,7 @@ pub struct BufferImpl<'a> { height: usize, color_space: &'a CGColorSpace, buffer: util::PixelBuffer, + alpha_info: CGImageAlphaInfo, layer: &'a mut SendCALayer, } @@ -331,9 +355,9 @@ impl BufferInterface for BufferImpl<'_> { // // TODO: Use `CGBitmapInfo::new` once the next version of objc2-core-graphics is released. let bitmap_info = CGBitmapInfo( - CGImageAlphaInfo::NoneSkipFirst.0 + self.alpha_info.0 | CGImageComponentInfo::Integer.0 - | CGImageByteOrderInfo::Order32Little.0 + | CGImageByteOrderInfo::Order32Host.0 | CGImagePixelFormatInfo::Packed.0, ); diff --git a/src/backends/kms.rs b/src/backends/kms.rs index 91184cb6..6dd69c70 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::Pixel; +use crate::{AlphaMode, Pixel}; #[derive(Debug, Clone)] struct DrmDevice<'a> { @@ -225,7 +225,20 @@ impl SurfaceInterface fo &self.window_handle } - fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { + #[inline] + fn supports_alpha_mode(&self, alpha_mode: AlphaMode) -> bool { + matches!( + alpha_mode, + AlphaMode::Opaque | AlphaMode::Ignored | AlphaMode::Premultiplied + ) + } + + fn configure( + &mut self, + width: NonZeroU32, + height: NonZeroU32, + alpha_mode: AlphaMode, + ) -> Result<(), SoftBufferError> { // Don't resize if we don't have to. if let Some(buffer) = &self.buffer { let (buffer_width, buffer_height) = buffer.size(); @@ -234,9 +247,15 @@ impl SurfaceInterface fo } } + let format = match alpha_mode { + AlphaMode::Opaque | AlphaMode::Ignored => DrmFourcc::Xrgb8888, + AlphaMode::Premultiplied => DrmFourcc::Argb8888, + _ => unimplemented!(), + }; + // Create a new buffer set. - let front_buffer = SharedBuffer::new(&self.display, width, height)?; - let back_buffer = SharedBuffer::new(&self.display, width, height)?; + let front_buffer = SharedBuffer::new(&self.display, width, height, format)?; + let back_buffer = SharedBuffer::new(&self.display, width, height, format)?; self.buffer = Some(Buffers { first_is_front: true, @@ -252,7 +271,7 @@ impl SurfaceInterface fo } */ - fn buffer_mut(&mut self) -> Result, SoftBufferError> { + fn buffer_mut(&mut self, _alpha_mode: AlphaMode) -> Result, SoftBufferError> { // Map the dumb buffer. let set = self .buffer @@ -409,10 +428,11 @@ impl SharedBuffer { display: &KmsDisplayImpl, width: NonZeroU32, height: NonZeroU32, + format: DrmFourcc, ) -> Result { let db = display .device - .create_dumb_buffer((width.get(), height.get()), DrmFourcc::Xrgb8888, 32) + .create_dumb_buffer((width.get(), height.get()), format, 32) .swbuf_err("failed to create dumb buffer")?; let fb = display .device diff --git a/src/backends/orbital.rs b/src/backends/orbital.rs index 43e8faea..2c4f6974 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, Pixel, Rect, SoftBufferError}; +use crate::{util, AlphaMode, Pixel, Rect, SoftBufferError}; #[derive(Debug)] struct OrbitalMap { @@ -101,7 +101,17 @@ impl SurfaceInterface for Orbital &self.window_handle } - fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { + #[inline] + fn supports_alpha_mode(&self, alpha_mode: AlphaMode) -> bool { + matches!(alpha_mode, AlphaMode::Opaque | AlphaMode::Ignored) + } + + fn configure( + &mut self, + width: NonZeroU32, + height: NonZeroU32, + _alpha_mode: AlphaMode, + ) -> Result<(), SoftBufferError> { let width = width.get(); let height = height.get(); if width != self.width || height != self.height { @@ -112,7 +122,7 @@ impl SurfaceInterface for Orbital Ok(()) } - fn buffer_mut(&mut self) -> Result, SoftBufferError> { + fn buffer_mut(&mut self, _alpha_mode: AlphaMode) -> 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/buffer.rs b/src/backends/wayland/buffer.rs index 60a1bb0e..2a8a7c42 100644 --- a/src/backends/wayland/buffer.rs +++ b/src/backends/wayland/buffer.rs @@ -79,12 +79,19 @@ pub(super) struct WaylandBuffer { buffer: wl_buffer::WlBuffer, pub width: i32, pub height: i32, + format: wl_shm::Format, released: Arc, pub age: u8, } impl WaylandBuffer { - pub fn new(shm: &wl_shm::WlShm, width: i32, height: i32, qh: &QueueHandle) -> Self { + pub fn new( + shm: &wl_shm::WlShm, + width: i32, + height: i32, + format: wl_shm::Format, + qh: &QueueHandle, + ) -> Self { // Calculate size to use for shm pool let pool_size = get_pool_size(width, height); @@ -101,9 +108,7 @@ impl WaylandBuffer { width, height, util::byte_stride(width as u32) as i32, - // This is documented as `0xXXRRGGBB` on a little-endian machine, which means a byte - // order of `[B, G, R, X]`. - wl_shm::Format::Xrgb8888, + format, qh, released.clone(), ); @@ -117,14 +122,15 @@ impl WaylandBuffer { buffer, width, height, + format, released, age: 0, } } - pub fn resize(&mut self, width: i32, height: i32) { - // If size is the same, there's nothing to do - if self.width != width || self.height != height { + pub fn configure(&mut self, width: i32, height: i32, format: wl_shm::Format) { + // If info is the same, there's nothing to do + if self.width != width || self.height != height || self.format != format { // Destroy old buffer self.buffer.destroy(); @@ -143,12 +149,13 @@ impl WaylandBuffer { width, height, util::byte_stride(width as u32) as i32, - wl_shm::Format::Xrgb8888, + format, &self.qh, self.released.clone(), ); self.width = width; self.height = height; + self.format = format; } } diff --git a/src/backends/wayland/mod.rs b/src/backends/wayland/mod.rs index ad3ce9a1..b4f1c233 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, Pixel, Rect, SoftBufferError, + util, AlphaMode, Pixel, Rect, SoftBufferError, }; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle}; use std::{ @@ -81,7 +81,6 @@ pub struct WaylandImpl { surface: Option, buffers: Option<(WaylandBuffer, WaylandBuffer)>, size: Option<(NonZeroI32, NonZeroI32)>, - /// The pointer to the window object. /// /// This has to be dropped *after* the `surface` field, because the `surface` field implicitly @@ -128,7 +127,20 @@ impl SurfaceInterface &self.window_handle } - fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { + #[inline] + fn supports_alpha_mode(&self, alpha_mode: AlphaMode) -> bool { + matches!( + alpha_mode, + AlphaMode::Opaque | AlphaMode::Ignored | AlphaMode::Premultiplied + ) + } + + fn configure( + &mut self, + width: NonZeroU32, + height: NonZeroU32, + _alpha_mode: AlphaMode, + ) -> Result<(), SoftBufferError> { self.size = Some( (|| { let width = NonZeroI32::try_from(width).ok()?; @@ -140,7 +152,15 @@ impl SurfaceInterface Ok(()) } - fn buffer_mut(&mut self) -> Result, SoftBufferError> { + fn buffer_mut(&mut self, alpha_mode: AlphaMode) -> 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 { + AlphaMode::Opaque | AlphaMode::Ignored => wl_shm::Format::Xrgb8888, + AlphaMode::Premultiplied => wl_shm::Format::Argb8888, + _ => unimplemented!(), + }; + let (width, height) = self .size .expect("Must set size of surface before calling `buffer_mut()`"); @@ -163,8 +183,8 @@ impl SurfaceInterface } } - // Resize, if buffer isn't large enough - back.resize(width.get(), height.get()); + // Resize if buffer isn't large enough. + back.configure(width.get(), height.get(), format); } else { // Allocate front and back buffer self.buffers = Some(( @@ -172,12 +192,14 @@ impl SurfaceInterface &self.display.shm, width.get(), height.get(), + format, &self.display.qh, ), WaylandBuffer::new( &self.display.shm, width.get(), height.get(), + format, &self.display.qh, ), )); diff --git a/src/backends/web.rs b/src/backends/web.rs index f3eea049..b0aef9df 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, NoDisplayHandle, NoWindowHandle, Pixel, Rect, SoftBufferError}; +use crate::{util, AlphaMode, NoDisplayHandle, NoWindowHandle, Pixel, Rect, SoftBufferError}; use std::marker::PhantomData; use std::num::NonZeroU32; @@ -166,9 +166,19 @@ impl SurfaceInterface for WebImpl &self.window_handle } + #[inline] + fn supports_alpha_mode(&self, alpha_mode: AlphaMode) -> bool { + matches!(alpha_mode, AlphaMode::Opaque | AlphaMode::Postmultiplied) + } + /// De-duplicates the error handling between `HtmlCanvasElement` and `OffscreenCanvas`. /// Resize the canvas to the given dimensions. - fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { + fn configure( + &mut self, + width: NonZeroU32, + height: NonZeroU32, + _alpha_mode: AlphaMode, + ) -> Result<(), SoftBufferError> { if self.size != Some((width, height)) { self.buffer_presented = false; self.buffer @@ -178,10 +188,12 @@ impl SurfaceInterface for WebImpl self.size = Some((width, height)); } + // TODO: Re-resolve the canvas context with `alpha` value. + Ok(()) } - fn buffer_mut(&mut self) -> Result, SoftBufferError> { + fn buffer_mut(&mut self, _alpha_mode: AlphaMode) -> Result, SoftBufferError> { Ok(BufferImpl { canvas: &self.canvas, buffer: &mut self.buffer, @@ -202,6 +214,7 @@ impl SurfaceInterface for WebImpl // TODO: Can also error if width or height are 0. .swbuf_err("`Canvas` contains pixels from a different origin")?; + // TODO: How should we handle alpha modes here? Ok(image_data .data() .0 @@ -238,6 +251,7 @@ impl SurfaceExtWeb for crate::Surface { Ok(Self { surface_impl: Box::new(imple), + alpha_mode: AlphaMode::default(), _marker: PhantomData, }) } @@ -250,6 +264,7 @@ impl SurfaceExtWeb for crate::Surface { Ok(Self { surface_impl: Box::new(imple), + alpha_mode: AlphaMode::default(), _marker: PhantomData, }) } @@ -358,32 +373,13 @@ impl BufferInterface for BufferImpl<'_> { .size .expect("Must set size of surface before calling `present_with_damage()`"); - let union_damage = if let Some(rect) = util::union_damage(damage) { - rect - } else { - return Ok(()); + let bitmap: &[u8] = { + let ptr = self.buffer.as_ptr(); + let len = self.buffer.len(); + // SAFETY: `Pixel` can be reinterpreted as 4 `u8`s. + unsafe { std::slice::from_raw_parts(ptr.cast::(), len * 4) } }; - // Create a bitmap from the buffer. - let bitmap: Vec<_> = self - .buffer - .chunks_exact(buffer_width.get() as usize) - .skip(union_damage.y as usize) - .take(union_damage.height.get() as usize) - .flat_map(|row| { - row.iter() - .skip(union_damage.x as usize) - .take(union_damage.width.get() as usize) - }) - .copied() - .flat_map(|pixel| [pixel.r, pixel.g, pixel.b, 0xff]) - .collect(); - - debug_assert_eq!( - bitmap.len() as u32, - union_damage.width.get() * union_damage.height.get() * 4 - ); - #[cfg(target_feature = "atomics")] #[allow(non_local_definitions)] let result = { @@ -402,17 +398,15 @@ impl BufferInterface for BufferImpl<'_> { } let array = Uint8Array::new_with_length(bitmap.len() as u32); - array.copy_from(&bitmap); + array.copy_from(bitmap); let array = Uint8ClampedArray::new(&array); - ImageDataExt::new(array, union_damage.width.get()) + ImageDataExt::new(array, buffer_width.get()) .map(JsValue::from) .map(ImageData::unchecked_from_js) }; #[cfg(not(target_feature = "atomics"))] - let result = ImageData::new_with_u8_clamped_array( - wasm_bindgen::Clamped(&bitmap), - union_damage.width.get(), - ); + let result = + ImageData::new_with_u8_clamped_array(wasm_bindgen::Clamped(bitmap), buffer_width.get()); // This should only throw an error if the buffer we pass's size is incorrect. let image_data = result.unwrap(); @@ -421,10 +415,10 @@ impl BufferInterface for BufferImpl<'_> { self.canvas .put_image_data( &image_data, - union_damage.x.into(), - union_damage.y.into(), - (rect.x - union_damage.x).into(), - (rect.y - union_damage.y).into(), + 0.0, + 0.0, + rect.x.into(), + rect.y.into(), rect.width.get().into(), rect.height.get().into(), ) diff --git a/src/backends/win32.rs b/src/backends/win32.rs index df20fdfd..80d89d0d 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::{Pixel, Rect, SoftBufferError}; +use crate::{AlphaMode, Pixel, Rect, SoftBufferError}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle}; use std::io; @@ -17,6 +17,7 @@ use std::thread; use windows_sys::Win32::Foundation::HWND; use windows_sys::Win32::Graphics::Gdi; +use windows_sys::Win32::UI::ColorSystem::LCS_WINDOWS_COLOR_SPACE; const ZERO_QUAD: Gdi::RGBQUAD = Gdi::RGBQUAD { rgbBlue: 0, @@ -48,24 +49,53 @@ impl Drop for Buffer { } impl Buffer { - fn new(window_dc: Gdi::HDC, width: NonZeroI32, height: NonZeroI32) -> Self { + fn new(window_dc: Gdi::HDC, width: NonZeroI32, height: NonZeroI32, alpha: bool) -> Self { let dc = Allocator::get().allocate(window_dc); assert!(!dc.is_null()); // Create a new bitmap info struct. let bitmap_info = BitmapInfo { - bmi_header: Gdi::BITMAPINFOHEADER { - biSize: size_of::() as u32, - biWidth: width.get(), - biHeight: -height.get(), - biPlanes: 1, - biBitCount: 32, - biCompression: Gdi::BI_BITFIELDS, - biSizeImage: 0, - biXPelsPerMeter: 0, - biYPelsPerMeter: 0, - biClrUsed: 0, - biClrImportant: 0, + bmi_header: Gdi::BITMAPV5HEADER { + bV5Size: size_of::() as u32, + bV5Width: width.get(), + bV5Height: -height.get(), + bV5Planes: 1, + bV5BitCount: 32, + bV5Compression: Gdi::BI_BITFIELDS, + bV5SizeImage: 0, + bV5XPelsPerMeter: 0, + bV5YPelsPerMeter: 0, + bV5ClrUsed: 0, + bV5ClrImportant: 0, + bV5RedMask: 0x00ff0000, + bV5GreenMask: 0x0000ff00, + bV5BlueMask: 0x000000ff, + bV5AlphaMask: if alpha { 0xff000000 } else { 0x00000000 }, + bV5CSType: LCS_WINDOWS_COLOR_SPACE as u32, + bV5Endpoints: Gdi::CIEXYZTRIPLE { + ciexyzRed: Gdi::CIEXYZ { + ciexyzX: 0, + ciexyzY: 0, + ciexyzZ: 0, + }, + ciexyzGreen: Gdi::CIEXYZ { + ciexyzX: 0, + ciexyzY: 0, + ciexyzZ: 0, + }, + ciexyzBlue: Gdi::CIEXYZ { + ciexyzX: 0, + ciexyzY: 0, + ciexyzZ: 0, + }, + }, + bV5GammaRed: 0, + bV5GammaGreen: 0, + bV5GammaBlue: 0, + bV5Intent: 0, + bV5ProfileData: 0, + bV5ProfileSize: 0, + bV5Reserved: 0, }, bmi_colors: [ Gdi::RGBQUAD { @@ -155,7 +185,7 @@ impl Drop for Win32Impl { /// The Win32-compatible bitmap information. #[repr(C)] struct BitmapInfo { - bmi_header: Gdi::BITMAPINFOHEADER, + bmi_header: Gdi::BITMAPV5HEADER, bmi_colors: [Gdi::RGBQUAD; 3], } @@ -201,7 +231,18 @@ impl SurfaceInterface for Win32Im &self.handle } - fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { + #[inline] + fn supports_alpha_mode(&self, alpha_mode: AlphaMode) -> bool { + // TODO: Support transparency. + matches!(alpha_mode, AlphaMode::Opaque | AlphaMode::Ignored) + } + + fn configure( + &mut self, + width: NonZeroU32, + height: NonZeroU32, + alpha_mode: AlphaMode, + ) -> Result<(), SoftBufferError> { let (width, height) = (|| { let width = NonZeroI32::try_from(width).ok()?; let height = NonZeroI32::try_from(height).ok()?; @@ -215,12 +256,17 @@ impl SurfaceInterface for Win32Im } } - self.buffer = Some(Buffer::new(self.dc.0, width, height)); + let alpha = match alpha_mode { + AlphaMode::Opaque | AlphaMode::Ignored => false, + AlphaMode::Premultiplied => true, + _ => unimplemented!(), + }; + self.buffer = Some(Buffer::new(self.dc.0, width, height, alpha)); Ok(()) } - fn buffer_mut(&mut self) -> Result, SoftBufferError> { + fn buffer_mut(&mut self, _alpha_mode: AlphaMode) -> Result, SoftBufferError> { let buffer = self .buffer .as_mut() @@ -296,6 +342,8 @@ impl BufferInterface for BufferImpl<'_> { )) })() .ok_or(SoftBufferError::DamageOutOfRange { rect })?; + + // TODO: Draw with something else to make transparency work. Gdi::BitBlt( self.dc.0, x, diff --git a/src/backends/x11.rs b/src/backends/x11.rs index 17a8f55f..22d3e07c 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, Pixel, Rect, SoftBufferError}; +use crate::{util, AlphaMode, Pixel, Rect, SoftBufferError}; use raw_window_handle::{ HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle, XcbDisplayHandle, XcbWindowHandle, @@ -311,7 +311,17 @@ impl SurfaceInterface fo &self.window_handle } - fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { + #[inline] + fn supports_alpha_mode(&self, alpha_mode: AlphaMode) -> bool { + matches!(alpha_mode, AlphaMode::Opaque | AlphaMode::Ignored) + } + + fn configure( + &mut self, + width: NonZeroU32, + height: NonZeroU32, + _alpha_mode: AlphaMode, + ) -> Result<(), SoftBufferError> { tracing::trace!( "resize: window={:X}, size={}x{}", self.window, @@ -344,7 +354,7 @@ impl SurfaceInterface fo Ok(()) } - fn buffer_mut(&mut self) -> Result, SoftBufferError> { + fn buffer_mut(&mut self, _alpha_mode: AlphaMode) -> Result, SoftBufferError> { tracing::trace!("buffer_mut: 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 b79be6b6..317f0d4a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,6 +3,8 @@ use std::error::Error; use std::fmt; use std::num::NonZeroU32; +use crate::AlphaMode; + #[derive(Debug)] #[non_exhaustive] /// A sum type of all of the errors that can occur during the operation of this crate. @@ -86,6 +88,12 @@ pub enum SoftBufferError { height: NonZeroU32, }, + /// The provided alpha mode is not supported by the backend. + UnsupportedAlphaMode { + /// The alpha mode that was not supported. + alpha_mode: AlphaMode, + }, + /// The provided damage rect is outside of the range supported by the backend. DamageOutOfRange { /// The damage rect that was out of range. @@ -131,6 +139,10 @@ impl fmt::Display for SoftBufferError { f, "Surface size {width}x{height} out of range for backend.", ), + Self::UnsupportedAlphaMode { alpha_mode } => write!( + f, + "Alpha mode {alpha_mode:?} is not supported by the backend.", + ), Self::PlatformError(msg, None) => write!(f, "Platform error: {msg:?}"), Self::PlatformError(msg, Some(err)) => write!(f, "Platform error: {msg:?}: {err}"), Self::DamageOutOfRange { rect } => write!( diff --git a/src/format.rs b/src/format.rs index b8374c81..557651c4 100644 --- a/src/format.rs +++ b/src/format.rs @@ -15,16 +15,16 @@ /// avoid unnecessary copying, see the documentation for [`Pixel`][crate::Pixel] for examples. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] pub enum PixelFormat { - /// The pixel format is `RGBX` (red, green, blue, unset). + /// The pixel format is `RGBA` (red, green, blue, alpha). /// /// This is currently the default on macOS/iOS, KMS/DRM, Orbital, Wayland, Windows and X11. #[cfg_attr(not(any(target_family = "wasm", target_os = "android")), default)] - Bgrx, - /// The pixel format is `BGRX` (blue, green, red, unset). + Bgra, + /// The pixel format is `BGRA` (blue, green, red, alpha). /// /// This is currently the default on Android and Web. #[cfg_attr(any(target_family = "wasm", target_os = "android"), default)] - Rgbx, + Rgba, // Intentionally exhaustive for now. } diff --git a/src/lib.rs b/src/lib.rs index fc96bd00..ba1c2a00 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,6 +79,8 @@ pub struct Rect { pub struct Surface { /// This is boxed so that `Surface` is the same size on every platform. surface_impl: Box>, + /// The current alpha mode that the surface is configured with. + alpha_mode: AlphaMode, _marker: PhantomData>, } @@ -87,6 +89,7 @@ impl Surface { pub fn new(context: &Context, window: W) -> Result { match SurfaceDispatch::new(window, &context.context_impl) { Ok(surface_dispatch) => Ok(Self { + alpha_mode: AlphaMode::default(), surface_impl: Box::new(surface_dispatch), _marker: PhantomData, }), @@ -107,6 +110,11 @@ impl Surface { self.surface_impl.window() } + /// The current alpha mode of the surface. + pub fn alpha_mode(&self) -> AlphaMode { + self.alpha_mode + } + /// Set the size of the buffer that will be returned by [`Surface::buffer_mut`]. /// /// If the size of the buffer does not match the size of the window, the buffer is drawn @@ -114,16 +122,44 @@ impl Surface { /// to have the buffer fill the entire window. Use your windowing library to find the size /// of the window. pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { + self.configure(width, height, self.alpha_mode()) + } + + /// Reconfigure the surface's properties. + // TODO(madsmtm): What are the exact semantics of configuring? Should it always re-create the + // buffers, or should that only be done in `buffer_mut` if properties changed? + pub fn configure( + &mut self, + width: NonZeroU32, + height: NonZeroU32, + alpha_mode: AlphaMode, + ) -> Result<(), SoftBufferError> { if u32::MAX / 4 < width.get() { // Stride would be too large. return Err(SoftBufferError::SizeOutOfRange { width, height }); } - self.surface_impl.resize(width, height)?; + if !self.supports_alpha_mode(alpha_mode) { + return Err(SoftBufferError::UnsupportedAlphaMode { alpha_mode }); + } + + self.surface_impl.configure(width, height, alpha_mode)?; + + self.alpha_mode = alpha_mode; Ok(()) } + /// Query if the given alpha mode is 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) + } + /// Copies the window contents into a buffer. /// /// ## Platform Dependent Behavior @@ -146,7 +182,9 @@ impl Surface { /// `softbuffer`. Therefore it is the responsibility of the user to wait for the page flip before /// sending another frame. pub fn buffer_mut(&mut self) -> Result, SoftBufferError> { - let mut buffer_impl = self.surface_impl.buffer_mut()?; + let alpha_mode = self.alpha_mode(); + + let mut buffer_impl = self.surface_impl.buffer_mut(alpha_mode)?; debug_assert_eq!( buffer_impl.byte_stride().get() % 4, @@ -165,6 +203,8 @@ impl Surface { Ok(Buffer { buffer_impl, + #[cfg(debug_assertions)] + alpha_mode, _marker: PhantomData, }) } @@ -248,6 +288,8 @@ impl HasWindowHandle for Surface #[derive(Debug)] pub struct Buffer<'a> { buffer_impl: BufferDispatch<'a>, + #[cfg(debug_assertions)] + alpha_mode: AlphaMode, _marker: PhantomData>, } @@ -301,7 +343,19 @@ impl Buffer<'_> { /// /// If the caller wishes to synchronize other surface/window changes, such requests must be sent to the /// Wayland compositor before calling this function. - pub fn present(self) -> Result<(), SoftBufferError> { + pub fn present(mut self) -> Result<(), SoftBufferError> { + // Verify that pixels are set as opaque if the alpha mode requires it. + #[cfg(debug_assertions)] + if self.alpha_mode == AlphaMode::Opaque { + // 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() { + for (x, pixel) in row.iter().take(width).enumerate() { + assert_eq!(pixel.a, 0xff, "pixel at ({x}, {y}) was not opaque"); + } + } + } + self.buffer_impl.present() } @@ -454,6 +508,86 @@ impl Buffer<'_> { } } +/// Specifies how the alpha channel of the surface should be handled by the compositor. +/// +/// 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. +/// +/// [whatwg-premultiplied]: https://html.spec.whatwg.org/multipage/canvas.html#premultiplied-alpha-and-the-2d-rendering-context +#[doc(alias = "Transparency")] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +pub enum AlphaMode { + /// The alpha channel must be set as opaque. + /// + /// If it isn't, the alpha channel may be ignored, or the contents may be drawn transparent, + /// depending on the platform. Softbuffer contains consistency checks for this internally, so + /// using this mode with a transparent alpha channel may panic with `cfg!(debug_assertions)` + /// enabled. + /// + /// **This is the default.** + // + // NOTE: We use a separate enum value here instead of just making a platform-specific default, + // because even though we'll want to effectively use a `*multiplied` internally on some + // platforms, we'll still want to hint to the compositor that the surface is opaque. + #[default] + Opaque, + /// The alpha channel is ignored, and the contents are treated as opaque. + /// + /// ## Platform Dependent Behavior + /// + /// - Web: Cannot be supported (in a zero-copy manner). + Ignored, + /// The non-alpha channels are expected to already have been multiplied by the alpha channel. + /// + /// ## Platform Dependent Behavior + /// + /// - Wayland and DRM/KMS: Supported. + /// - macOS/iOS: Supported, but currently doesn't work with additive values (maybe only as the + /// root layer?). Make sure that components are `<= alpha` if you want to be cross-platform. + /// - Android, Orbital, Web, Windows and X11: Not yet supported. + #[doc(alias = "Associated")] + Premultiplied, + /// The non-alpha channels are not expected to already be multiplied by the alpha channel; + /// instead, the compositor will multiply the non-alpha channels by the alpha channel during + /// compositing. + /// + /// Also known as "straight alpha". + /// + /// ## Platform Dependent Behavior + /// + /// - Web and macOS/iOS: Supported. + /// - Android, KMS/DRM, Orbital, Windows, X11: Not yet supported. + #[doc(alias = "Straight")] + #[doc(alias = "Unassociated")] + Postmultiplied, + // Intentionally exhaustive, there are probably no other alpha modes that make sense. +} + +/// Convenience helpers. +impl AlphaMode { + /// Check if this is [`AlphaMode::Opaque`]. + pub fn is_opaque(self) -> bool { + matches!(self, Self::Opaque) + } + + /// Check if this is [`AlphaMode::Ignored`]. + pub fn is_ignored(self) -> bool { + matches!(self, Self::Ignored) + } + + /// Check if this is [`AlphaMode::Premultiplied`]. + pub fn is_pre_multiplied(self) -> bool { + matches!(self, Self::Premultiplied) + } + + /// Check if this is [`AlphaMode::Postmultiplied`]. + pub fn is_post_multiplied(self) -> bool { + matches!(self, Self::Postmultiplied) + } +} + /// There is no display handle. #[derive(Debug)] #[allow(dead_code)] diff --git a/src/pixel.rs b/src/pixel.rs index 3d60424a..2d1944ed 100644 --- a/src/pixel.rs +++ b/src/pixel.rs @@ -1,4 +1,4 @@ -/// A RGBA pixel. +/// A 32-bit pixel with 4 components. /// /// # Representation /// @@ -9,6 +9,11 @@ /// /// [`PixelFormat::default()`]: crate::PixelFormat#default /// +/// # Default +/// +/// The [`Default`] impl returns a transparent black pixel. Beware that this might not be what you +/// want if using [`AlphaMode::Opaque`] (which is the default). +/// /// # Example /// /// Construct a new pixel. @@ -20,10 +25,10 @@ /// assert_eq!(red.r, 255); /// assert_eq!(red.g, 128); /// assert_eq!(red.b, 0); -/// # // assert_eq!(red.a, 0xff); -/// # -/// # // let from_struct_literal = Pixel { r: 255, g: 0x80, b: 0, a: 0xff }; -/// # // assert_eq!(red, from_struct_literal); +/// assert_eq!(red.a, 0xff); +/// +/// let from_struct_literal = Pixel { r: 255, g: 0x80, b: 0, a: 0xff }; +/// assert_eq!(red, from_struct_literal); /// ``` /// /// Convert a pixel to an array of `u8`s. @@ -36,8 +41,8 @@ /// let red = unsafe { core::mem::transmute::(red) }; /// /// match PixelFormat::default() { -/// PixelFormat::Bgrx => assert_eq!(red[2], 255), -/// PixelFormat::Rgbx => assert_eq!(red[0], 255), +/// PixelFormat::Bgra => assert_eq!(red[2], 255), +/// PixelFormat::Rgba => assert_eq!(red[0], 255), /// } /// ``` /// @@ -51,8 +56,8 @@ /// let red = unsafe { core::mem::transmute::(red) }; /// /// match PixelFormat::default() { -/// PixelFormat::Bgrx => assert_eq!(red, u32::from_ne_bytes([0x00, 0x00, 0xff, 0x00])), -/// PixelFormat::Rgbx => assert_eq!(red, u32::from_ne_bytes([0xff, 0x00, 0x00, 0x00])), +/// 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])), /// } /// ``` /// @@ -60,7 +65,7 @@ /// simple API that provides RGBX rendering. /// /// ```no_run -/// use softbuffer::{Pixel, PixelFormat}; +/// use softbuffer::{AlphaMode, Pixel, PixelFormat}; /// /// // Assume the user controls the following rendering function: /// fn render(pixels: &mut [[u8; 4]], width: u32, height: u32) { @@ -69,6 +74,9 @@ /// /// // Then we'd convert pixel data as follows: /// +/// # let alpha_mode: AlphaMode = todo!(); +/// # #[cfg(false)] +/// let alpha_mode = surface.alpha_mode(); /// # let buffer: softbuffer::Buffer<'_> = todo!(); /// # #[cfg(false)] /// let buffer = surface.buffer_mut(); @@ -77,10 +85,16 @@ /// let height = buffer.height().get(); /// /// // Use fast, zero-copy implementation when possible, and fall back to slower version when not. -/// if PixelFormat::Rgbx.is_default() && buffer.byte_stride().get() == width * 4 { +/// if PixelFormat::Rgba.is_default() +/// && buffer.byte_stride().get() == width * 4 +/// && alpha_mode == AlphaMode::Ignored +/// { /// // 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 RGBX, and that `stride == width * 4`. +/// // CORRECTNESS: We just checked that: +/// // - The format is RGBA. +/// // - The `stride == width * 4`. +/// // - The alpha value can be ignored (A -> X). /// render(pixels, width, height); /// } else { /// // Render into temporary buffer. @@ -128,14 +142,12 @@ pub struct Pixel { /// /// `0xff` here means opaque, whereas `0` means transparent. /// - /// NOTE: Transparency is not yet supported, see [#17], so this doesn't actually do anything. - /// - /// [#17]: https://github.com/rust-windowing/softbuffer/issues/17 - pub(crate) a: u8, + /// Make sure to set this correctly according to the [`AlphaMode`][crate::AlphaMode]. + pub a: u8, } impl Pixel { - /// Create a new pixel from a red, a green and a blue component. + /// Create a new opaque pixel from a red, a green and a blue component. /// /// # Example /// @@ -146,11 +158,10 @@ impl Pixel { /// assert_eq!(red.r, 255); /// ``` pub const fn new_rgb(r: u8, g: u8, b: u8) -> Self { - // FIXME(madsmtm): Change alpha to `0xff` once we support transparency. - Self { r, g, b, a: 0x00 } + Self { r, g, b, a: 0xff } } - /// Create a new pixel from a blue, a green and a red component. + /// Create a new opaque pixel from a blue, a green and a red component. /// /// # Example /// @@ -161,11 +172,38 @@ impl Pixel { /// assert_eq!(red.r, 255); /// ``` pub const fn new_bgr(b: u8, g: u8, r: u8) -> Self { - // FIXME(madsmtm): Change alpha to `0xff` once we support transparency. - Self { r, g, b, a: 0x00 } + Self { r, g, b, a: 0xff } } - // TODO(madsmtm): Once we have transparency, add `new_rgba` and `new_bgra` methods. + /// Create a new pixel from a red, a green, a blue and an alpha component. + /// + /// # Example + /// + /// ``` + /// # use softbuffer::Pixel; + /// # + /// let red = Pixel::new_rgba(0xff, 0, 0, 0x7f); + /// assert_eq!(red.r, 255); + /// assert_eq!(red.a, 127); + /// ``` + pub const fn new_rgba(r: u8, g: u8, b: u8, a: u8) -> Self { + Self { r, g, b, a } + } + + /// Create a new pixel from a blue, a green, a red and an alpha component. + /// + /// # Example + /// + /// ``` + /// # use softbuffer::Pixel; + /// # + /// let red = Pixel::new_bgra(0, 0, 0xff, 0x7f); + /// assert_eq!(red.r, 255); + /// assert_eq!(red.a, 127); + /// ``` + pub const fn new_bgra(b: u8, g: u8, r: u8, a: u8) -> Self { + Self { r, g, b, a } + } } // TODO: Implement `Add`/`Mul`/similar `std::ops` like `rgb` does?