diff --git a/Cargo.toml b/Cargo.toml index e8783dda..3f241d63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,10 +18,10 @@ harness = false [features] default = ["kms", "x11", "x11-dlopen", "wayland", "wayland-dlopen","compatibility"] -kms = ["bytemuck", "drm", "rustix"] +kms = ["drm", "rustix"] wayland = ["wayland-backend", "wayland-client", "wayland-sys", "memmap2", "rustix", "fastrand"] wayland-dlopen = ["wayland-sys/dlopen"] -x11 = ["as-raw-xcb-connection", "bytemuck", "fastrand", "rustix", "tiny-xlib", "x11rb"] +x11 = ["as-raw-xcb-connection", "fastrand", "rustix", "tiny-xlib", "x11rb"] x11-dlopen = ["tiny-xlib/dlopen", "x11rb/dl-libxcb"] compatibility = [] @@ -29,11 +29,11 @@ compatibility = [] log = "0.4.17" raw_window_handle = { package = "raw-window-handle", version = "0.6", features = ["std"] } num = "0.4.3" -duplicate = "1.0.0" +bytemuck = "1.12.3" [target.'cfg(all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))))'.dependencies] as-raw-xcb-connection = { version = "1.0.0", optional = true } -bytemuck = { version = "1.12.3", optional = true } +# bytemuck = { version = "1.12.3", optional = true } drm = { version = "0.12.0", default-features = false, optional = true } fastrand = { version = "2.0.0", optional = true } memmap2 = { version = "0.9.0", optional = true } @@ -91,6 +91,7 @@ colorous = "1.0.12" criterion = { version = "0.4.0", default-features = false, features = ["cargo_bench_support"] } web-time = "1.0.0" winit = "0.30.0" +tiny-skia = "0.11.4" [dev-dependencies.image] version = "0.25.0" diff --git a/benches/buffer_mut.rs b/benches/buffer_mut.rs index f69f906f..fb3c92fa 100644 --- a/benches/buffer_mut.rs +++ b/benches/buffer_mut.rs @@ -53,7 +53,7 @@ fn buffer_mut(c: &mut Criterion) { let mut buffer = surface.buffer_mut().unwrap(); b.iter(|| { for _ in 0..500 { - let x: &mut [u32] = &mut buffer.pixels_mut(); + let x: &mut [u32] = &mut buffer.pixels_platform_dependent_mut(); black_box(x); } }); diff --git a/examples/winit_tiny_skia.rs b/examples/winit_tiny_skia.rs new file mode 100644 index 00000000..916c8b94 --- /dev/null +++ b/examples/winit_tiny_skia.rs @@ -0,0 +1,121 @@ +use softbuffer::RGBA; +use std::num::NonZeroU32; +use winit::event::{Event, KeyEvent, WindowEvent}; +use winit::event_loop::{ControlFlow, EventLoop}; +use winit::keyboard::{Key, NamedKey}; + +#[path = "utils/winit_app.rs"] +mod winit_app; + +use tiny_skia::{BlendMode, LineCap, Paint, PathBuilder, PixmapMut, Stroke, StrokeDash, Transform}; + +fn main() { + let event_loop = EventLoop::new().unwrap(); + + let app = winit_app::WinitAppBuilder::with_init(|elwt| { + let window = winit_app::make_window(elwt, |w| w.with_transparent(true)); + + let context = softbuffer::Context::new(window.clone()).unwrap(); + let surface = softbuffer::Surface::new_with_alpha(&context, window.clone()).unwrap(); + + (window, surface) + }) + .with_event_handler(|state, event, elwt| { + let (window, surface) = state; + elwt.set_control_flow(ControlFlow::Wait); + + match event { + Event::WindowEvent { + window_id, + event: WindowEvent::Resized(size), + } if window_id == window.id() => { + if let (Some(width), Some(height)) = + (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) + { + surface.resize(width, height).unwrap(); + } + } + Event::WindowEvent { + window_id, + event: WindowEvent::RedrawRequested, + } if window_id == window.id() => { + let size = window.inner_size(); + if let (Some(width), Some(height)) = + (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) + { + let mut buffer = surface.buffer_mut().unwrap(); + + //We draw the background of our window in softbuffer writing to individual pixels + for y in 0..height.get() { + for x in 0..width.get() { + const SCALE_FACTOR: u32 = 3; + let red = (x/SCALE_FACTOR) % 255; + let green = (y/SCALE_FACTOR) % 255; + let blue = ((x/SCALE_FACTOR) * (y/SCALE_FACTOR)) % 255; + let alpha = if blue > 255/2{ + 255 + }else{ + 0 + }; + let index = y as usize * width.get() as usize + x as usize; + buffer.pixels_rgb_mut()[index] = softbuffer::RGBA::new_unchecked(red,green, blue, alpha); + } + } + + // buffer.fill(RGBA::new_unchecked(50,0,50, 200)); // Alternatively we could fill with a solid color + + //using tiny_skia that accepts the u8 rgba format, we draw a star on top of our background + buffer.pixel_u8_slice_rgba(|u8_buffer_rgba| { + let mut pixmap = + PixmapMut::from_bytes(u8_buffer_rgba, width.get(), height.get()) + .unwrap(); + let mut paint = Paint::default(); + // paint.set_color_rgba8(255, 0, 255, 0); // <-- We could set the color, but because we are using BlendMode::Clear the color does not matter + paint.anti_alias = true; + paint.blend_mode = BlendMode::Clear; // <-- Set Blend mode so that we can draw transparent pixels + + let path = { + let mut pb = PathBuilder::new(); + let RADIUS: f32 = (width.get().min(height.get()) / 2) as f32; + let CENTER: f32 = (width.get().min(height.get()) / 2) as f32; + pb.move_to(CENTER + RADIUS, CENTER); + for i in 1..8 { + let a = 2.6927937 * i as f32; + pb.line_to(CENTER + RADIUS * a.cos(), CENTER + RADIUS * a.sin()); + } + pb.finish().unwrap() + }; + + let mut stroke = Stroke::default(); + stroke.width = 24.0; + stroke.line_cap = LineCap::Round; + stroke.dash = StrokeDash::new(vec![20.0, 40.0], 0.0); + + pixmap.stroke_path(&path, &paint, &stroke, Transform::identity(), None); + }); + + + buffer.present().unwrap(); + } + } + Event::WindowEvent { + event: + WindowEvent::CloseRequested + | WindowEvent::KeyboardInput { + event: + KeyEvent { + logical_key: Key::Named(NamedKey::Escape), + .. + }, + .. + }, + window_id, + } if window_id == window.id() => { + elwt.exit(); + } + _ => {} + } + }); + + winit_app::run_app(event_loop, app); +} diff --git a/src/backend_dispatch.rs b/src/backend_dispatch.rs index 7a1de501..53fd8770 100644 --- a/src/backend_dispatch.rs +++ b/src/backend_dispatch.rs @@ -3,13 +3,12 @@ use crate::{backend_interface::*, backends, InitError, Rect, SoftBufferError, BufferReturn, WithAlpha, WithoutAlpha}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; -use duplicate::duplicate_item; use std::num::NonZeroU32; #[cfg(any(wayland_platform, x11_platform, kms_platform))] use std::sync::Arc; /// A macro for creating the enum used to statically dispatch to the platform-specific implementation. -macro_rules! make_enum { +macro_rules! make_dispatch { ( <$dgen: ident, $wgen: ident, $alpha: ident> => $( @@ -33,58 +32,9 @@ macro_rules! make_enum { )* } - pub(crate) enum BufferDispatch<'a, $dgen, $wgen, $alpha> { - $( - $(#[$attr])* - $name($buffer_inner), - )* - } - - impl ContextDispatch { - pub fn variant_name(&self) -> &'static str { - match self { - $( - $(#[$attr])* - Self::$name(_) => stringify!($name), - )* - } - } - } - - impl ContextInterface for ContextDispatch { - fn new(mut display: D) -> Result> - where - D: Sized, - { - $( - $(#[$attr])* - match <$context_inner as ContextInterface>::new(display) { - Ok(x) => { - return Ok(Self::$name(x)); - } - Err(InitError::Unsupported(d)) => display = d, - Err(InitError::Failure(f)) => return Err(InitError::Failure(f)), - } - )* - - Err(InitError::Unsupported(display)) - } - } - }; -} - -macro_rules! make_dispatch { - ( - <$dgen: ident, $wgen: ident, $alpha: ident> => - $( - $(#[$attr:meta])* - $name: ident - ($context_inner: ty, $surface_inner: ty, $buffer_inner: ty), - )* - ) => { - impl SurfaceInterface for SurfaceDispatch{ + impl SurfaceInterface for SurfaceDispatch{ type Context = ContextDispatch; - type Buffer<'a> = BufferDispatch<'a, D, W, $alpha> where Self: 'a; + type Buffer<'a> = BufferDispatch<'a, D, W, A> where Self: 'a; fn new(window: W, display: &Self::Context) -> Result> where @@ -147,8 +97,14 @@ macro_rules! make_dispatch { } } - - impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface<$alpha> for BufferDispatch<'a, D, W, $alpha> { + pub(crate) enum BufferDispatch<'a, $dgen, $wgen, $alpha> { + $( + $(#[$attr])* + $name($buffer_inner), + )* + } + + impl<'a, D: HasDisplayHandle, W: HasWindowHandle,A: BufferReturn> BufferInterface for BufferDispatch<'a, D, W, A> { #[inline] fn pixels(&self) -> &[u32] { match self { @@ -169,6 +125,15 @@ macro_rules! make_dispatch { } } + fn pixels_rgb(&self) -> &[<$alpha as BufferReturn>::Output]{ + match self { + $( + $(#[$attr])* + Self::$name(inner) => inner.pixels_rgb(), + )* + } + } + fn pixels_rgb_mut(&mut self) -> &mut[<$alpha as BufferReturn>::Output]{ match self { $( @@ -205,12 +170,44 @@ macro_rules! make_dispatch { } } } + + impl ContextDispatch { + pub fn variant_name(&self) -> &'static str { + match self { + $( + $(#[$attr])* + Self::$name(_) => stringify!($name), + )* + } + } + } + + impl ContextInterface for ContextDispatch { + fn new(mut display: D) -> Result> + where + D: Sized, + { + $( + $(#[$attr])* + match <$context_inner as ContextInterface>::new(display) { + Ok(x) => { + return Ok(Self::$name(x)); + } + Err(InitError::Unsupported(d)) => display = d, + Err(InitError::Failure(f)) => return Err(InitError::Failure(f)), + } + )* + + Err(InitError::Unsupported(display)) + } + } }; } + // XXX empty enum with generic bound is invalid? -make_enum!{ +make_dispatch! { => #[cfg(x11_platform)] X11(Arc>, backends::x11::X11Impl, backends::x11::BufferImpl<'a, D, W>), @@ -226,27 +223,4 @@ make_enum!{ Web(backends::web::WebDisplayImpl, backends::web::WebImpl, backends::web::BufferImpl<'a, D, W>), #[cfg(target_os = "redox")] Orbital(D, backends::orbital::OrbitalImpl, backends::orbital::BufferImpl<'a, D, W>), -} - -#[duplicate_item( - TY; - [ WithAlpha ]; - [ WithoutAlpha ]; - )] -make_dispatch! { - => - #[cfg(x11_platform)] - X11(Arc>, backends::x11::X11Impl, backends::x11::BufferImpl<'a, D, W>), - #[cfg(wayland_platform)] - Wayland(Arc>, backends::wayland::WaylandImpl, backends::wayland::BufferImpl<'a, D, W>), - #[cfg(kms_platform)] - Kms(Arc>, backends::kms::KmsImpl, backends::kms::BufferImpl<'a, D, W>), - #[cfg(target_os = "windows")] - Win32(D, backends::win32::Win32Impl, backends::win32::BufferImpl<'a, D, W, TY>), - #[cfg(target_vendor = "apple")] - CoreGraphics(D, backends::cg::CGImpl, backends::cg::BufferImpl<'a, D, W, TY>), - #[cfg(target_arch = "wasm32")] - Web(backends::web::WebDisplayImpl, backends::web::WebImpl, backends::web::BufferImpl<'a, D, W>), - #[cfg(target_os = "redox")] - Orbital(D, backends::orbital::OrbitalImpl, backends::orbital::BufferImpl<'a, D, W>), } \ No newline at end of file diff --git a/src/backend_interface.rs b/src/backend_interface.rs index 3bdd70eb..de6b5a1e 100644 --- a/src/backend_interface.rs +++ b/src/backend_interface.rs @@ -1,6 +1,6 @@ //! Interface implemented by backends -use crate::{BufferReturn, InitError, Rect, SoftBufferError}; +use crate::{formats::RGBFormat, BufferReturn, InitError, Rect, SoftBufferError}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use std::{fmt::Debug, num::NonZeroU32}; @@ -40,16 +40,19 @@ pub(crate) trait SurfaceInterface { - #[deprecated = "Left for backwards compatibility. Will panic in the future. Switch to using the pixels_rgb or pixels_rgba methods for better cross platform portability"] + // #[deprecated = "Left for backwards compatibility. Will panic in the future. Switch to using the pixels_rgb or pixels_rgba methods for better cross platform portability"] fn pixels(&self) -> &[u32]; - #[deprecated = "Left for backwards compatibility. Will panic in the future. Switch to using the pixels_rgb_mut or pixels_rgba_mut methods for better cross platform portability"] + // #[deprecated = "Left for backwards compatibility. Will panic in the future. Switch to using the pixels_rgb_mut or pixels_rgba_mut methods for better cross platform portability"] fn pixels_mut(&mut self) -> &mut [u32]; + fn pixels_rgb(&self) -> &[::Output]; fn pixels_rgb_mut(&mut self) -> &mut[::Output]; fn age(&self) -> u8; fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError>; fn present(self) -> Result<(), SoftBufferError>; } + + macro_rules! define_rgbx_little_endian { ( $( @@ -60,6 +63,7 @@ macro_rules! define_rgbx_little_endian { $( $(#[$attr])* #[repr(C)] + #[derive(Copy,Clone)] pub struct RGBX{ $forth_vis $forth: u8, $third_vis $third: u8, @@ -80,6 +84,7 @@ macro_rules! define_rgba_little_endian { $( $(#[$attr])* #[repr(C)] + #[derive(Copy,Clone)] pub struct RGBA{ $forth_vis $forth: u8, $third_vis $third: u8, @@ -138,7 +143,7 @@ impl RGBX{ if r > MAX_U8 || g > MAX_U8 || b > MAX_U8{ Err(SoftBufferError::PrimitiveOutsideOfU8Range) }else{ - Ok(Self { r: r.as_(), g: g.as_(), b: b.as_(), x: 255 }) + Ok(Self { r: r.as_(), g: g.as_(), b: b.as_(), x: 0 }) } } @@ -152,6 +157,14 @@ impl RGBX{ { Self { r: r.as_(), g: g.as_(), b: b.as_(), x: 255 } } + + // pub fn new_from_u32(u32: u32) -> Self{ + // todo!() + // } + + // pub fn as_u32(&self) -> &u32{ + // unsafe{std::mem::transmute(self)} + // } } impl RGBA{ @@ -183,3 +196,81 @@ impl RGBA{ Self { r: r.as_(), g: g.as_(), b: b.as_(), a: a.as_() } } } + +//TODO, change this to be a different impl based on platform +impl RGBFormat for RGBA{ + fn to_rgba_format(self) -> crate::formats::RGBA { + crate::formats::RGBA{ + a: self.a, + b: self.b, + g: self.g, + r: self.r, + } + } + + fn from_rgba_format(rgba: crate::formats::RGBA) -> Self { + Self{ + b: rgba.b, + g: rgba.g, + r: rgba.r, + a: rgba.a, + } + } + + fn to_rgba_u8_format(self) -> crate::formats::RGBAu8 { + crate::formats::RGBAu8{ + a: self.a, + b: self.b, + g: self.g, + r: self.r, + } + } + + fn from_rgba_u8_format(rgba: crate::formats::RGBAu8) -> Self { + Self{ + b: rgba.b, + g: rgba.g, + r: rgba.r, + a: rgba.a, + } + } + +} + +impl RGBFormat for RGBX{ + fn to_rgba_format(self) -> crate::formats::RGBA { + crate::formats::RGBA{ + a: self.x, + b: self.b, + g: self.g, + r: self.r, + } + } + + fn from_rgba_format(rgba: crate::formats::RGBA) -> Self { + Self{ + b: rgba.b, + g: rgba.g, + r: rgba.r, + x: rgba.a, + } + } + + fn to_rgba_u8_format(self) -> crate::formats::RGBAu8 { + crate::formats::RGBAu8{ + a: self.x, + b: self.b, + g: self.g, + r: self.r, + } + } + + fn from_rgba_u8_format(rgba: crate::formats::RGBAu8) -> Self { + Self{ + b: rgba.b, + g: rgba.g, + r: rgba.r, + x: rgba.a, + } + } +} \ No newline at end of file diff --git a/src/backends/cg.rs b/src/backends/cg.rs index 36bfea41..e393d8c2 100644 --- a/src/backends/cg.rs +++ b/src/backends/cg.rs @@ -7,7 +7,6 @@ use core_graphics::base::{ use core_graphics::color_space::CGColorSpace; use core_graphics::data_provider::CGDataProvider; use core_graphics::image::CGImage; -use duplicate::duplicate_item; use foreign_types::ForeignType; use objc2::rc::Retained; use objc2::runtime::{AnyObject, Bool}; @@ -144,15 +143,10 @@ impl Drop for CGImpl { } } -#[duplicate_item( - TY; - [ WithAlpha ]; - [ WithoutAlpha ]; - )] -impl SurfaceInterface for CGImpl +impl SurfaceInterface for CGImpl { type Context = D; - type Buffer<'a> = BufferImpl<'a, D, W, TY> where Self: 'a; + type Buffer<'a> = BufferImpl<'a, D, W, A> where Self: 'a; fn new(window_src: W, _display: &D) -> Result> { // `NSView`/`UIView` can only be accessed from the main thread. @@ -273,121 +267,7 @@ impl SurfaceInterface for CGI } fn new_with_alpha(window_src: W, _display: &D) -> Result> { - // `NSView`/`UIView` can only be accessed from the main thread. - let _mtm = MainThreadMarker::new().ok_or(SoftBufferError::PlatformError( - Some("can only access Core Graphics handles from the main thread".to_string()), - None, - ))?; - - let root_layer = match window_src.window_handle()?.as_raw() { - RawWindowHandle::AppKit(handle) => { - // SAFETY: The pointer came from `WindowHandle`, which ensures that the - // `AppKitWindowHandle` contains a valid pointer to an `NSView`. - // - // We use `NSObject` here to avoid importing `objc2-app-kit`. - let view: &NSObject = unsafe { handle.ns_view.cast().as_ref() }; - - // Force the view to become layer backed - let _: () = unsafe { msg_send![view, setWantsLayer: Bool::YES] }; - - // SAFETY: `-[NSView layer]` returns an optional `CALayer` - let layer: Option> = unsafe { msg_send_id![view, layer] }; - layer.expect("failed making the view layer-backed") - } - RawWindowHandle::UiKit(handle) => { - // SAFETY: The pointer came from `WindowHandle`, which ensures that the - // `UiKitWindowHandle` contains a valid pointer to an `UIView`. - // - // We use `NSObject` here to avoid importing `objc2-ui-kit`. - let view: &NSObject = unsafe { handle.ui_view.cast().as_ref() }; - - // SAFETY: `-[UIView layer]` returns `CALayer` - let layer: Retained = unsafe { msg_send_id![view, layer] }; - layer - } - _ => return Err(InitError::Unsupported(window_src)), - }; - - // Add a sublayer, to avoid interfering with the root layer, since setting the contents of - // e.g. a view-controlled layer is brittle. - let layer = CALayer::new(); - root_layer.addSublayer(&layer); - - // Set the anchor point and geometry. Softbuffer's uses a coordinate system with the origin - // in the top-left corner. - // - // NOTE: This doesn't really matter unless we start modifying the `position` of our layer - // ourselves, but it's nice to have in place. - layer.setAnchorPoint(CGPoint::new(0.0, 0.0)); - layer.setGeometryFlipped(true); - - // Do not use auto-resizing mask. - // - // This is done to work around a bug in macOS 14 and above, where views using auto layout - // may end up setting fractional values as the bounds, and that in turn doesn't propagate - // properly through the auto-resizing mask and with contents gravity. - // - // Instead, we keep the bounds of the layer in sync with the root layer using an observer, - // see below. - // - // layer.setAutoresizingMask(kCALayerHeightSizable | kCALayerWidthSizable); - - let observer = Observer::new(&layer); - // Observe changes to the root layer's bounds and scale factor, and apply them to our layer. - // - // The previous implementation updated the scale factor inside `resize`, but this works - // poorly with transactions, and is generally inefficient. Instead, we update the scale - // factor only when needed because the super layer's scale factor changed. - // - // Note that inherent in this is an explicit design decision: We control the `bounds` and - // `contentsScale` of the layer directly, and instead let the `resize` call that the user - // controls only be the size of the underlying buffer. - // - // SAFETY: Observer deregistered in `Drop` before the observer object is deallocated. - unsafe { - root_layer.addObserver_forKeyPath_options_context( - &observer, - ns_string!("contentsScale"), - NSKeyValueObservingOptions::NSKeyValueObservingOptionNew - | NSKeyValueObservingOptions::NSKeyValueObservingOptionInitial, - ptr::null_mut(), - ); - root_layer.addObserver_forKeyPath_options_context( - &observer, - ns_string!("bounds"), - NSKeyValueObservingOptions::NSKeyValueObservingOptionNew - | NSKeyValueObservingOptions::NSKeyValueObservingOptionInitial, - ptr::null_mut(), - ); - } - - // Set the content so that it is placed in the top-left corner if it does not have the same - // size as the surface itself. - // - // TODO(madsmtm): Consider changing this to `kCAGravityResize` to stretch the content if - // resized to something that doesn't fit, see #177. - layer.setContentsGravity(unsafe { kCAGravityTopLeft }); - - // Initialize color space here, to reduce work later on. - let color_space = CGColorSpace::create_device_rgb(); - - // Grab initial width and height from the layer (whose properties have just been initialized - // by the observer using `NSKeyValueObservingOptionInitial`). - let size = layer.bounds().size; - let scale_factor = layer.contentsScale(); - let width = (size.width * scale_factor) as usize; - let height = (size.height * scale_factor) as usize; - - Ok(Self { - layer: SendCALayer(layer), - root_layer: SendCALayer(root_layer), - observer, - color_space: SendCGColorSpace(color_space), - width, - height, - _display: PhantomData, - window_handle: window_src, - }) + Self::new(window_src, _display) } #[inline] @@ -401,7 +281,7 @@ impl SurfaceInterface for CGI Ok(()) } - fn buffer_mut(&mut self) -> Result, SoftBufferError> { + fn buffer_mut(&mut self) -> Result, SoftBufferError> { Ok(BufferImpl { buffer: vec![0; self.width * self.height], imp: self, @@ -415,12 +295,8 @@ pub struct BufferImpl<'a, D, W, A> { buffer: Vec, _marker: PhantomData, } -#[duplicate_item( - TY platform_alpha_mode; - [ WithAlpha ] [kCGImageAlphaFirst]; - [ WithoutAlpha ] [kCGImageAlphaNoneSkipFirst]; - )] -impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl<'a, D, W, TY> { + +impl<'a, D: HasDisplayHandle, W: HasWindowHandle, A: BufferReturn> BufferInterface for BufferImpl<'a, D, W, A> { #[inline] fn pixels(&self) -> &[u32] { &self.buffer @@ -431,8 +307,12 @@ impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for Buffer &mut self.buffer } - fn pixels_rgb_mut(&mut self) -> &mut[::Output] { - unsafe{std::mem::transmute::<& mut [u32],&mut [::Output]>(&mut self.buffer[..])} + fn pixels_rgb(&self) -> &[::Output] { + unsafe{std::mem::transmute::<&[u32],& [::Output]>(&self.buffer[..])} + } + + fn pixels_rgb_mut(&mut self) -> &mut[::Output] { + unsafe{std::mem::transmute::<& mut [u32],&mut [::Output]>(&mut self.buffer[..])} } fn age(&self) -> u8 { @@ -442,6 +322,13 @@ impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for Buffer fn present(self) -> Result<(), SoftBufferError> { let data_provider = CGDataProvider::from_buffer(Arc::new(Buffer(self.buffer))); + + let bitmap_mode = if A::ALPHA_MODE{ + kCGImageAlphaFirst + }else{ + kCGImageAlphaNoneSkipFirst + }; + let image = CGImage::new( self.imp.width, self.imp.height, @@ -449,7 +336,7 @@ impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for Buffer 32, self.imp.width * 4, &self.imp.color_space.0, - kCGBitmapByteOrder32Little | platform_alpha_mode, + kCGBitmapByteOrder32Little | bitmap_mode, &data_provider, false, kCGRenderingIntentDefault, diff --git a/src/backends/win32.rs b/src/backends/win32.rs index 0ff1ca80..b9c9f787 100644 --- a/src/backends/win32.rs +++ b/src/backends/win32.rs @@ -4,7 +4,6 @@ use crate::{backend_interface::*, BufferReturn, WithAlpha, WithoutAlpha}; use crate::{Rect, SoftBufferError}; -use duplicate::duplicate_item; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle}; use std::io; @@ -307,14 +306,9 @@ impl Win32Impl { } } -#[duplicate_item( - TY internal_buffer_function; - [ WithAlpha ] [new_with_alpha]; - [ WithoutAlpha ] [new]; - )] -impl SurfaceInterface for Win32Impl { +impl SurfaceInterface for Win32Impl { type Context = D; - type Buffer<'a> = BufferImpl<'a, D, W, TY> where Self: 'a; + type Buffer<'a> = BufferImpl<'a, D, W, A> where Self: 'a; /// Create a new `Win32Impl` from a `Win32WindowHandle`. fn new(window: W, _display: &D) -> Result> { @@ -356,34 +350,7 @@ impl SurfaceInterface for Win W: Sized, Self: Sized, { - let raw = window.window_handle()?.as_raw(); - let handle = match raw { - RawWindowHandle::Win32(handle) => handle, - _ => return Err(crate::InitError::Unsupported(window)), - }; - - // Get the handle to the device context. - // SAFETY: We have confirmed that the window handle is valid. - let hwnd = handle.hwnd.get() as HWND; - let dc = Allocator::get().get_dc(hwnd); - - // GetDC returns null if there is a platform error. - if dc.is_null() { - return Err(SoftBufferError::PlatformError( - Some("Device Context is null".into()), - Some(Box::new(io::Error::last_os_error())), - ) - .into()); - } - - Ok(Self { - dc: dc.into(), - window: hwnd.into(), - buffer: None, - handle: window, - _display: PhantomData, - _marker: PhantomData, - }) + Self::new(window,context) } #[inline] @@ -405,12 +372,16 @@ impl SurfaceInterface for Win } } - self.buffer = Some(Buffer::internal_buffer_function(self.dc.0, width, height)); + if A::ALPHA_MODE{ + self.buffer = Some(Buffer::new_with_alpha(self.dc.0, width, height)); + }else{ + self.buffer = Some(Buffer::new(self.dc.0, width, height)); + }; Ok(()) } - fn buffer_mut(&mut self) -> Result, SoftBufferError> { + fn buffer_mut(&mut self) -> Result, SoftBufferError> { if self.buffer.is_none() { panic!("Must set size of surface before calling `buffer_mut()`"); } @@ -426,12 +397,7 @@ impl SurfaceInterface for Win pub struct BufferImpl<'a, D, W, A>(&'a mut Win32Impl); -#[duplicate_item( - TY; - [ WithAlpha ]; - [ WithoutAlpha ]; - )] -impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl<'a, D, W, TY> { +impl<'a, D: HasDisplayHandle, W: HasWindowHandle, A: BufferReturn> BufferInterface for BufferImpl<'a, D, W, A> { #[inline] fn pixels(&self) -> &[u32] { self.0.buffer.as_ref().unwrap().pixels() @@ -442,9 +408,9 @@ impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for Buffer self.0.buffer.as_mut().unwrap().pixels_mut() } - fn pixels_rgb_mut(&mut self) -> &mut [::Output] { + fn pixels_rgb_mut(&mut self) -> &mut [::Output] { unsafe { - std::mem::transmute::<&mut [u32], &mut [::Output]>( + std::mem::transmute::<&mut [u32], &mut [::Output]>( self.0.buffer.as_mut().unwrap().pixels_mut(), ) } diff --git a/src/formats.rs b/src/formats.rs new file mode 100644 index 00000000..b279d386 --- /dev/null +++ b/src/formats.rs @@ -0,0 +1,56 @@ +pub trait RGBFormat{ + fn to_rgba_format(self) -> crate::formats::RGBA; + fn from_rgba_format(rgba: crate::formats::RGBA) -> Self; + fn to_rgba_u8_format(self) -> crate::formats::RGBAu8; + fn from_rgba_u8_format(rgba: crate::formats::RGBAu8) -> Self; +} + +//When wanting the bytes in a specific order by u8, you no longer care about endianness +#[repr(C)] +#[derive(Copy, Clone)] +pub struct RGBAu8{ + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} + +#[cfg(target_endian = "little")] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct RGBA{ + pub a: u8, + pub b: u8, + pub g: u8, + pub r: u8, +} + +#[cfg(target_endian = "big")] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct RGBA{ + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} + +#[cfg(target_endian = "little")] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct ARGB{ + pub b: u8, + pub g: u8, + pub r: u8, + pub a: u8, +} + +#[cfg(target_endian = "big")] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct ARGB{ + pub a: u8, + pub r: u8, + pub g: u8, + pub b: u8, +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 3631f453..689b114b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,8 +10,10 @@ mod backend_dispatch; use backend_dispatch::*; mod backend_interface; use backend_interface::*; +use formats::RGBFormat; mod backends; mod error; +mod formats; mod util; use std::cell::Cell; @@ -20,12 +22,11 @@ use std::num::NonZeroU32; use std::ops; use std::sync::Arc; +pub use backend_interface::{RGBA, RGBX}; use error::InitError; pub use error::SoftBufferError; -pub use backend_interface::{RGBX,RGBA}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle}; -use duplicate::duplicate_item; #[cfg(target_arch = "wasm32")] pub use backends::web::SurfaceExtWeb; @@ -74,18 +75,21 @@ pub struct Rect { } pub trait BufferReturn { - type Output: Sized; + type Output: RGBFormat + Copy; + const ALPHA_MODE: bool; } -pub enum WithoutAlpha{} +pub enum WithoutAlpha {} -impl BufferReturn for WithoutAlpha{ +impl BufferReturn for WithoutAlpha { type Output = RGBX; + const ALPHA_MODE: bool = false; } -pub enum WithAlpha{} +pub enum WithAlpha {} - -impl BufferReturn for WithAlpha{ +impl BufferReturn for WithAlpha { type Output = RGBA; + + const ALPHA_MODE: bool = true; } /// A surface for drawing to a window with software buffers. @@ -97,11 +101,14 @@ pub struct Surface { impl Surface { /// Creates a new surface for the context for the provided window. - pub fn new(context: &Context, window: W) -> Result, SoftBufferError> { + pub fn new( + context: &Context, + window: W, + ) -> Result, SoftBufferError> { match SurfaceDispatch::new(window, &context.context_impl) { Ok(surface_dispatch) => Ok(Self { surface_impl: Box::new(surface_dispatch), - _marker: PhantomData + _marker: PhantomData, }), Err(InitError::Unsupported(window)) => { let raw = window.window_handle()?.as_raw(); @@ -116,13 +123,7 @@ impl Surface { } } -#[duplicate_item( - TY; - [ WithAlpha ]; - [ WithoutAlpha ]; - )] -impl Surface { - +impl Surface { /// Get a reference to the underlying window handle. pub fn window(&self) -> &W { self.surface_impl.window() @@ -159,7 +160,7 @@ impl Surface { /// - On DRM/KMS, there is no reliable and sound way to wait for the page flip to happen from within /// `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> { + pub fn buffer_mut(&mut self) -> Result, SoftBufferError> { Ok(Buffer { buffer_impl: self.surface_impl.buffer_mut()?, _marker: PhantomData, @@ -169,11 +170,14 @@ impl Surface { impl Surface { /// Creates a new surface for the context for the provided window. - pub fn new_with_alpha(context: &Context, window: W) -> Result, SoftBufferError> { + pub fn new_with_alpha( + context: &Context, + window: W, + ) -> Result, SoftBufferError> { match SurfaceDispatch::new_with_alpha(window, &context.context_impl) { Ok(surface_dispatch) => Ok(Self { surface_impl: Box::new(surface_dispatch), - _marker: PhantomData + _marker: PhantomData, }), Err(InitError::Unsupported(window)) => { let raw = window.window_handle()?.as_raw(); @@ -188,24 +192,16 @@ impl Surface { } } -#[duplicate_item( - TY; - [ WithAlpha ]; - [ WithoutAlpha ]; - )] -impl AsRef for Surface { +impl AsRef for Surface { #[inline] fn as_ref(&self) -> &W { self.window() } } -#[duplicate_item( - TY; - [ WithAlpha ]; - [ WithoutAlpha ]; - )] -impl HasWindowHandle for Surface { +impl HasWindowHandle + for Surface +{ #[inline] fn window_handle( &self, @@ -257,12 +253,7 @@ pub struct Buffer<'a, D, W, A> { _marker: PhantomData<(Arc, Cell<()>)>, } -#[duplicate_item( - TY; - [ WithAlpha ]; - [ WithoutAlpha ]; - )] -impl<'a, D: HasDisplayHandle, W: HasWindowHandle> Buffer<'a, D, W, TY> { +impl<'a, D: HasDisplayHandle, W: HasWindowHandle, A: BufferReturn> Buffer<'a, D, W, A> { /// Is 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 @@ -305,29 +296,103 @@ impl<'a, D: HasDisplayHandle, W: HasWindowHandle> Buffer<'a, D, W, TY> { } } -#[duplicate_item( - TY; - [ WithAlpha ]; - [ WithoutAlpha ]; - )] -impl<'a, D: HasDisplayHandle, W: HasWindowHandle> Buffer<'a, D, W, TY>{ - #[deprecated = "Left for backwards compatibility. Will panic in the future. Switch to using the pixels_rgb or pixels_rgba methods for better cross platform portability"] - pub fn pixels(&self)-> &[u32] { +macro_rules! cast_to_format_helper { + ($self:ident , $src:ident , $dst:ty, $func:ident , $to_func:ident , $from_func:ident) => { + { + let temp = $self.buffer_impl.pixels_mut(); + for element in temp.iter_mut() { + unsafe { + let temp_as_concrete_type = + std::mem::transmute::<&mut u32, &mut <$src as BufferReturn>::Output>(element); + let temp_as_destination_type = temp_as_concrete_type.$to_func(); + *element = std::mem::transmute(temp_as_destination_type); + } + } + $func(temp); + for element in temp.iter_mut() { + unsafe { + let temp_as_concrete_type = + std::mem::transmute::<&mut u32, &mut $dst>(element); + let temp_as_destination_type = + &mut <$src as BufferReturn>::Output::$from_func(*temp_as_concrete_type); + *element = *std::mem::transmute::<&mut <$src as BufferReturn>::Output, &mut u32>( + temp_as_destination_type, + ); + } + } + } + }; +} + +impl<'a, D: HasDisplayHandle, W: HasWindowHandle, A: BufferReturn> Buffer<'a, D, W, A> { + /// Gets a ```&[u32]``` of the buffer of pixels + /// The layout of the pixels is dependent on the platform that you are on + /// It is recommended to deref pixels to a ```&[RGBA]``` ```&[RGBX]``` struct as that will automatically handle the differences across platforms for free + /// If you need a ```&[u32]``` of a specific format, there are helper functions to get those, and conversions are automatic based on platform. + /// If using the format for your native platform, there is no cost. + pub fn pixels_platform_dependent(&self) -> &[u32] { self.buffer_impl.pixels() } - #[deprecated = "Left for backwards compatibility. Will panic in the future. Switch to using the pixels_rgb_mut or pixels_rgba_mut methods for better cross platform portability"] - pub fn pixels_mut(&mut self)-> &mut [u32] { + /// Gets a ```&mut [u32]``` of the buffer of pixels + /// The layout of the pixels is dependent on the platform that you are on + /// It is recommended to deref pixels to a ```&mut [RGBA]``` ```&mut [RGBX]``` struct as that will automatically handle the differences across platforms for free + /// If you need a ```&mut [u32]``` of a specific format, there are helper functions to get those, and conversions are automatic based on platform. + /// If using the format for your native platform, there is no cost. + pub fn pixels_platform_dependent_mut(&mut self) -> &mut [u32] { self.buffer_impl.pixels_mut() } + + /// Access the platform dependent representation of the pixel buffer. + /// Will return either ```&[RGBX]``` or ```&[RGBA]``` depending on if called on a surface with alpha enabled or not. + /// This is the generally recommended method of accessing the pixel buffer as it is a zero cost abstraction, that + /// automatically handles any platform dependent ordering of the r,g,b,a fields for you. + /// + /// Alternative to using Deref on buffer. + pub fn pixels_rgb(&mut self) -> &[::Output]{ + self.buffer_impl.pixels_rgb() + } + + /// Access the platform dependent representation of the pixel buffer. + /// Will return either ```&[RGBX]``` or ```&[RGBA]``` depending on if called on a surface with alpha enabled or not. + /// This is the generally recommended method of accessing the pixel buffer as it is a zero cost abstraction, that + /// automatically handles any platform dependent ordering of the r,g,b,a fields for you. + /// + /// Alternative to using Deref on buffer. + pub fn pixels_rgb_mut(&mut self) -> &mut[::Output]{ + self.buffer_impl.pixels_rgb_mut() + } + + /// Gives a ```&mut [u32]``` slice in the RGBA u32 format. + /// Endianness is adjusted based on platform automatically. + /// If using the format for your native platform, there is no cost. + /// Useful when using other crates that require a specific format. + /// + /// This takes a closure that gives you the required ```&mut [u32]```. + /// The closure is necessary because if conversion is required for your platform, we need to convert back to the platform native format before presenting to the buffer. + pub fn pixel_u32slice_rgba(&mut self, f: F) { + cast_to_format_helper!(self,A,formats::RGBA,f,to_rgba_format,from_rgba_format) + } + + /// Gives a ```&mut [u32]``` slice in the RGBA u8 format. + /// If using the format for your native platform, there is no cost. + /// Useful when using other crates that require a specific format. + /// + /// This takes a closure that gives you the required ```&mut [u32]```. + /// The closure is necessary because if conversion is required for your platform, we need to convert back to the platform native format before presenting to the buffer + pub fn pixel_u8_slice_rgba(&mut self, f: F) { + let wrapper = |x: &mut [u32]|{ + f(bytemuck::cast_slice_mut(x)) + }; + cast_to_format_helper!(self,A,formats::RGBAu8,wrapper,to_rgba_u8_format,from_rgba_u8_format) + } } -#[duplicate_item( - TY; - [ WithAlpha ]; - [ WithoutAlpha ]; - )] + + #[cfg(feature = "compatibility")] -impl<'a, D: HasDisplayHandle, W: HasWindowHandle> ops::Deref for Buffer<'a, D, W, TY> { +impl<'a, D: HasDisplayHandle, W: HasWindowHandle, A: BufferReturn> ops::Deref + for Buffer<'a, D, W, A> +{ type Target = [u32]; #[inline] @@ -336,51 +401,39 @@ impl<'a, D: HasDisplayHandle, W: HasWindowHandle> ops::Deref for Buffer<'a, D, W } } -#[duplicate_item( - TY; - [ WithAlpha ]; - [ WithoutAlpha ]; - )] #[cfg(feature = "compatibility")] -impl<'a, D: HasDisplayHandle, W: HasWindowHandle> ops::DerefMut for Buffer<'a, D, W, TY> { +impl<'a, D: HasDisplayHandle, W: HasWindowHandle, A: BufferReturn> ops::DerefMut + for Buffer<'a, D, W, A> +{ #[inline] fn deref_mut(&mut self) -> &mut [u32] { self.buffer_impl.pixels_mut() } } -#[duplicate_item( - TY; - [ WithAlpha ]; - [ WithoutAlpha ]; - )] #[cfg(not(feature = "compatibility"))] -impl<'a, D: HasDisplayHandle, W: HasWindowHandle> ops::Deref for Buffer<'a, D, W, TY> { - type Target = [::Output]; +impl<'a, D: HasDisplayHandle, W: HasWindowHandle, A: BufferReturn> ops::Deref + for Buffer<'a, D, W, A> +{ + type Target = [::Output]; #[inline] - fn deref(&self) -> &[::Output] { - // self.buffer_impl.pixels() - todo!() + fn deref(&self) -> &[::Output] { + self.buffer_impl.pixels_rgb() } } -#[duplicate_item( - TY; - [ WithAlpha ]; - [ WithoutAlpha ]; - )] #[cfg(not(feature = "compatibility"))] -impl<'a, D: HasDisplayHandle, W: HasWindowHandle> ops::DerefMut for Buffer<'a, D, W,TY> { +impl<'a, D: HasDisplayHandle, W: HasWindowHandle, A: BufferReturn> ops::DerefMut + for Buffer<'a, D, W, A> +{ // type Target = [crate::RGBX]; #[inline] - fn deref_mut(&mut self) -> &mut [::Output] { - // self.buffer_impl.pixels_mut() + fn deref_mut(&mut self) -> &mut [::Output] { self.buffer_impl.pixels_rgb_mut() } } - /// There is no display handle. #[derive(Debug)] #[allow(dead_code)] @@ -451,7 +504,7 @@ fn __assert_send() { is_send::>(); is_sync::>(); is_send::>(); - is_send::>(); + is_send::>(); /// ```compile_fail /// use softbuffer::Surface;