diff --git a/examples/winit.rs b/examples/winit.rs index 7c903e23..f961d16a 100644 --- a/examples/winit.rs +++ b/examples/winit.rs @@ -24,7 +24,8 @@ fn main() { } let context = softbuffer::Context::new(window.clone()).unwrap(); - let mut surface = softbuffer::Surface::new(&context, window.clone()).unwrap(); + let mut surface = + softbuffer::Surface::new(&context, window.clone(), softbuffer::Format::BGRX).unwrap(); event_loop .run(move |event, elwt| { diff --git a/src/error.rs b/src/error.rs index c2994a7c..c8943a9d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -92,6 +92,9 @@ pub enum SoftBufferError { rect: crate::Rect, }, + /// The requested format is not supported by the display. + UnsupportedFormat { format: crate::Format }, + /// A platform-specific backend error occurred. /// /// The first field provides a human-readable description of the error. The second field @@ -138,6 +141,7 @@ impl fmt::Display for SoftBufferError { "Damage rect {}x{} at ({}, {}) out of range for backend.", rect.width, rect.height, rect.x, rect.y ), + Self::UnsupportedFormat { format } => write!(f, "Format {:?} unsupported on display.", format), Self::Unimplemented => write!(f, "This function is unimplemented on this platform."), } } diff --git a/src/format.rs b/src/format.rs new file mode 100644 index 00000000..060d960d --- /dev/null +++ b/src/format.rs @@ -0,0 +1,91 @@ +use crate::{Format, Rect, SoftBufferError}; +use std::num::NonZeroU32; + +pub struct ConvertFormat { + buffer: Vec, + buffer_presented: bool, + size: Option<(NonZeroU32, NonZeroU32)>, + in_fmt: Format, + out_fmt: Format, +} + +impl ConvertFormat { + pub fn new(in_fmt: Format, out_fmt: Format) -> Result { + // TODO select out_fmt from native_formats? + Ok(Self { + buffer: Vec::new(), + buffer_presented: false, + size: None, + in_fmt, + out_fmt, + }) + } + + pub fn pixels(&self) -> &[u32] { + &self.buffer + } + + pub fn pixels_mut(&mut self) -> &mut [u32] { + &mut self.buffer + } + + pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) { + if self.size == Some((width, height)) { + return; + } + self.size = Some((width, height)); + self.buffer_presented = false; + self.buffer + .resize((u32::from(width) * u32::from(height)) as usize, 0); + } + + pub fn age(&self) -> u8 { + if self.buffer_presented { + 1 + } else { + 0 + } + } + + // Can only damage be copied? Need to track damage for multiple buffers? if backend uses + pub fn present(&self, outputs: &mut [u32], damage: &[Rect]) { + assert_eq!(outputs.len(), self.buffer.len()); + convert_format(self.in_fmt, &self.buffer, self.out_fmt, outputs); + self.buffer_presented; + } +} + +fn convert_pixels [u8; 4]>(inputs: &[u32], outputs: &mut [u32], mut cb: F) { + for (input, output) in inputs.iter().zip(outputs.iter_mut()) { + *output = u32::from_ne_bytes(cb(input.to_ne_bytes())); + } +} + +// Convert between BGR* and RGB* +#[inline(always)] +fn swap_rb(mut bytes: [u8; 4]) -> [u8; 4] { + bytes.swap(0, 2); + bytes +} + +// Convert ***X to ***A format by setting alpha to 255 +#[inline(always)] +fn set_opaque(mut bytes: [u8; 4]) -> [u8; 4] { + bytes[3] = 255; + bytes +} + +fn convert_format(in_fmt: Format, inputs: &[u32], out_fmt: Format, outputs: &mut [u32]) { + use Format::*; + match (in_fmt, out_fmt) { + (RGBA, RGBA) | (RGBX, RGBX) | (BGRA, BGRA) | (BGRX, BGRX) => { + outputs.copy_from_slice(inputs) + } + (RGBX, RGBA) | (BGRX, BGRA) => convert_pixels(inputs, outputs, set_opaque), + (RGBX, BGRX) | (RGBA, BGRA) | (BGRX, RGBX) | (BGRA, RGBA) => { + convert_pixels(inputs, outputs, swap_rb) + } + (RGBX, BGRA) | (BGRX, RGBA) => convert_pixels(inputs, outputs, |x| set_opaque(swap_rb(x))), + (RGBA | BGRA, RGBX | BGRX) => unimplemented!("can't convert alpha to non-alpha format"), + } +} diff --git a/src/kms.rs b/src/kms.rs index 6e041e61..ffda4c25 100644 --- a/src/kms.rs +++ b/src/kms.rs @@ -18,6 +18,7 @@ use std::os::unix::io::{AsFd, BorrowedFd}; use std::rc::Rc; use crate::error::{InitError, SoftBufferError, SwResultExt}; +use crate::Format; #[derive(Debug)] pub(crate) struct KmsDisplayImpl { @@ -55,6 +56,10 @@ impl KmsDisplayImpl { _display: display, }) } + + pub(crate) fn formats(&self) -> Vec { + vec![Format::BGRX] + } } /// All the necessary types for the Drm/Kms backend. diff --git a/src/lib.rs b/src/lib.rs index 6ec267ca..3b6ed87b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,7 @@ mod win32; mod x11; mod error; +mod format; mod util; use std::marker::PhantomData; @@ -75,6 +76,15 @@ macro_rules! make_dispatch { )* } } + + pub fn formats(&mut self) -> Vec { + match self { + $( + $(#[$attr])* + Self::$name(inner) => inner.formats(), + )* + } + } } #[allow(clippy::large_enum_variant)] // it's boxed anyways @@ -256,7 +266,7 @@ pub struct Surface { impl Surface { /// Creates a new surface for the context for the provided window. - pub fn new(context: &Context, window: W) -> Result { + pub fn new(context: &Context, window: W, format: Format) -> Result { macro_rules! leap { ($e:expr) => {{ match ($e) { @@ -283,7 +293,7 @@ impl Surface { } #[cfg(wayland_platform)] ContextDispatch::Wayland(wayland_display_impl) => SurfaceDispatch::Wayland(leap!( - wayland::WaylandImpl::new(window, wayland_display_impl.clone()) + wayland::WaylandImpl::new(window, wayland_display_impl.clone(), format) )), #[cfg(kms_platform)] ContextDispatch::Kms(kms_display_impl) => { @@ -511,3 +521,14 @@ fn display_handle_type_name(handle: &RawDisplayHandle) -> &'static str { _ => "Unknown Name", //don't completely fail to compile if there is a new raw window handle type that's added at some point } } + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Format { + RGBA, + RGBX, + BGRA, + BGRX, +} + +// XXX format conversion: swap components? seperate buffer? +// damage, age diff --git a/src/wayland/buffer.rs b/src/wayland/buffer.rs index 0c8bf860..c4feb8ce 100644 --- a/src/wayland/buffer.rs +++ b/src/wayland/buffer.rs @@ -81,7 +81,13 @@ pub(super) struct WaylandBuffer { } impl WaylandBuffer { - pub fn new(shm: &wl_shm::WlShm, width: i32, height: i32, qh: &QueueHandle) -> Self { + pub fn new( + shm: &wl_shm::WlShm, + format: wl_shm::Format, + width: i32, + height: i32, + qh: &QueueHandle, + ) -> Self { // Calculate size to use for shm pool let pool_size = get_pool_size(width, height); @@ -93,15 +99,7 @@ impl WaylandBuffer { // Create wayland shm pool and buffer let pool = shm.create_pool(tempfile.as_fd(), pool_size, qh, ()); let released = Arc::new(AtomicBool::new(true)); - let buffer = pool.create_buffer( - 0, - width, - height, - width * 4, - wl_shm::Format::Xrgb8888, - qh, - released.clone(), - ); + let buffer = pool.create_buffer(0, width, height, width * 4, format, qh, released.clone()); Self { qh: qh.clone(), diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index 762202d3..e8cc9beb 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -1,6 +1,7 @@ use crate::{ error::{InitError, SwResultExt}, - util, Rect, SoftBufferError, + format::ConvertFormat, + util, Format, Rect, SoftBufferError, }; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle}; use std::{ @@ -12,19 +13,24 @@ use wayland_client::{ backend::{Backend, ObjectId}, globals::{registry_queue_init, GlobalListContents}, protocol::{wl_registry, wl_shm, wl_surface}, - Connection, Dispatch, EventQueue, Proxy, QueueHandle, + Connection, Dispatch, EventQueue, Proxy, QueueHandle, WEnum, }; mod buffer; use buffer::WaylandBuffer; -struct State; +#[derive(Default)] +struct State { + formats: Vec, +} +// formats pub struct WaylandDisplayImpl { conn: Option, event_queue: RefCell>, qh: QueueHandle, shm: wl_shm::WlShm, + formats: Vec, /// The object that owns the display handle. /// @@ -46,17 +52,24 @@ impl WaylandDisplayImpl { let backend = unsafe { Backend::from_foreign_display(wayland_handle.as_ptr().cast()) }; let conn = Connection::from_backend(backend); - let (globals, event_queue) = + let (globals, mut event_queue) = registry_queue_init(&conn).swbuf_err("Failed to make round trip to server")?; let qh = event_queue.handle(); let shm: wl_shm::WlShm = globals .bind(&qh, 1..=1, ()) .swbuf_err("Failed to instantiate Wayland Shm")?; + + let mut state = State::default(); + event_queue + .roundtrip(&mut state) + .swbuf_err("Failled to to make round trip to server")?; + Ok(Self { conn: Some(conn), event_queue: RefCell::new(event_queue), qh, shm, + formats: state.formats, _display: display, }) } @@ -64,6 +77,20 @@ impl WaylandDisplayImpl { fn conn(&self) -> &Connection { self.conn.as_ref().unwrap() } + + // XXX iterator? slice? + pub(crate) fn formats(&self) -> Vec { + self.formats + .iter() + .filter_map(|format| match format { + wl_shm::Format::Argb8888 => Some(Format::BGRA), + wl_shm::Format::Xrgb8888 => Some(Format::BGRX), + wl_shm::Format::Abgr8888 => Some(Format::RGBA), + wl_shm::Format::Xbgr8888 => Some(Format::RGBX), + _ => None, + }) + .collect() + } } impl Drop for WaylandDisplayImpl { @@ -78,6 +105,7 @@ pub struct WaylandImpl { surface: Option, buffers: Option<(WaylandBuffer, WaylandBuffer)>, size: Option<(NonZeroI32, NonZeroI32)>, + format: wl_shm::Format, /// The pointer to the window object. /// @@ -87,7 +115,11 @@ pub struct WaylandImpl { } impl WaylandImpl { - pub(crate) fn new(window: W, display: Rc>) -> Result> { + pub(crate) fn new( + window: W, + display: Rc>, + format: Format, + ) -> Result> { // Get the raw Wayland window. let raw = window.window_handle()?.as_raw(); let wayland_handle = match raw { @@ -109,6 +141,7 @@ impl WaylandImpl { surface: Some(surface), buffers: Default::default(), size: None, + format: wayland_format(format), _window: window, }) } @@ -135,12 +168,14 @@ impl WaylandImpl { if !back.released() { let mut event_queue = self.display.event_queue.borrow_mut(); while !back.released() { - event_queue.blocking_dispatch(&mut State).map_err(|err| { - SoftBufferError::PlatformError( - Some("Wayland dispatch failure".to_string()), - Some(Box::new(err)), - ) - })?; + event_queue + .blocking_dispatch(&mut State::default()) + .map_err(|err| { + SoftBufferError::PlatformError( + Some("Wayland dispatch failure".to_string()), + Some(Box::new(err)), + ) + })?; } } @@ -151,12 +186,14 @@ impl WaylandImpl { self.buffers = Some(( WaylandBuffer::new( &self.display.shm, + self.format, width.get(), height.get(), &self.display.qh, ), WaylandBuffer::new( &self.display.shm, + self.format, width.get(), height.get(), &self.display.qh, @@ -183,7 +220,7 @@ impl WaylandImpl { .display .event_queue .borrow_mut() - .dispatch_pending(&mut State); + .dispatch_pending(&mut State::default()); if let Some((front, back)) = &mut self.buffers { front.age = 1; @@ -291,12 +328,29 @@ impl Dispatch for State { impl Dispatch for State { fn event( - _: &mut State, + state: &mut State, _: &wl_shm::WlShm, - _: wl_shm::Event, + event: wl_shm::Event, _: &(), _: &Connection, _: &QueueHandle, ) { + match event { + wl_shm::Event::Format { format } => { + if let WEnum::Value(format) = format { + state.formats.push(format); + } + } + _ => unreachable!(), + } + } +} + +fn wayland_format(format: Format) -> wl_shm::Format { + match format { + Format::BGRA => wl_shm::Format::Argb8888, + Format::BGRX => wl_shm::Format::Xrgb8888, + Format::RGBA => wl_shm::Format::Abgr8888, + Format::RGBX => wl_shm::Format::Xbgr8888, } } diff --git a/src/web.rs b/src/web.rs index 1caa71e8..9d2a50bd 100644 --- a/src/web.rs +++ b/src/web.rs @@ -10,7 +10,7 @@ use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; use web_sys::{OffscreenCanvas, OffscreenCanvasRenderingContext2d}; use crate::error::{InitError, SwResultExt}; -use crate::{util, NoDisplayHandle, NoWindowHandle, Rect, SoftBufferError}; +use crate::{util, Format, NoDisplayHandle, NoWindowHandle, Rect, SoftBufferError}; use std::convert::TryInto; use std::marker::PhantomData; use std::num::NonZeroU32; @@ -41,6 +41,10 @@ impl WebDisplayImpl { _display: display, }) } + + pub(crate) fn formats(&self) -> Vec { + vec![Format::RGBA] + } } pub struct WebImpl { @@ -173,6 +177,7 @@ impl WebImpl { return Ok(()); }; + // XXX no-copy // Create a bitmap from the buffer. let bitmap: Vec<_> = self .buffer @@ -185,7 +190,6 @@ impl WebImpl { .take(union_damage.width.get() as usize) }) .copied() - .flat_map(|pixel| [(pixel >> 16) as u8, (pixel >> 8) as u8, pixel as u8, 255]) .collect(); debug_assert_eq!( diff --git a/src/x11.rs b/src/x11.rs index a5baa82a..c252dc8f 100644 --- a/src/x11.rs +++ b/src/x11.rs @@ -6,7 +6,7 @@ #![allow(clippy::uninlined_format_args)] use crate::error::{InitError, SwResultExt}; -use crate::{Rect, SoftBufferError}; +use crate::{Format, Rect, SoftBufferError}; use raw_window_handle::{ HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle, XcbDisplayHandle, XcbWindowHandle, @@ -113,6 +113,10 @@ impl X11DisplayImpl { _display: display, }) } + + pub(crate) fn formats(&self) -> Vec { + vec![Format::BGRX] + } } impl X11DisplayImpl {