diff --git a/Cargo.toml b/Cargo.toml index fdce087..e8783dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,16 +17,19 @@ name = "buffer_mut" harness = false [features] -default = ["kms", "x11", "x11-dlopen", "wayland", "wayland-dlopen"] +default = ["kms", "x11", "x11-dlopen", "wayland", "wayland-dlopen","compatibility"] kms = ["bytemuck", "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-dlopen = ["tiny-xlib/dlopen", "x11rb/dl-libxcb"] +compatibility = [] [dependencies] log = "0.4.17" raw_window_handle = { package = "raw-window-handle", version = "0.6", features = ["std"] } +num = "0.4.3" +duplicate = "1.0.0" [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 } @@ -43,7 +46,7 @@ x11rb = { version = "0.13.0", features = ["allow-unsafe-code", "shm"], optional [target.'cfg(target_os = "windows")'.dependencies.windows-sys] version = "0.59.0" -features = ["Win32_Graphics_Gdi", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging", "Win32_Foundation"] +features = ["Win32_Graphics_Gdi", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging", "Win32_Foundation", "Win32_UI_ColorSystem"] [target.'cfg(target_vendor = "apple")'.dependencies] bytemuck = { version = "1.12.3", features = ["extern_crate_alloc"] } diff --git a/benches/buffer_mut.rs b/benches/buffer_mut.rs index beaea99..f69f906 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; + let x: &mut [u32] = &mut buffer.pixels_mut(); black_box(x); } }); diff --git a/src/backend_dispatch.rs b/src/backend_dispatch.rs index 208f82c..7a1de50 100644 --- a/src/backend_dispatch.rs +++ b/src/backend_dispatch.rs @@ -1,16 +1,17 @@ //! Implements `buffer_interface::*` traits for enums dispatching to backends -use crate::{backend_interface::*, backends, InitError, Rect, SoftBufferError}; +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_dispatch { +macro_rules! make_enum { ( - <$dgen: ident, $wgen: ident> => + <$dgen: ident, $wgen: ident, $alpha: ident> => $( $(#[$attr:meta])* $name: ident @@ -24,6 +25,21 @@ macro_rules! make_dispatch { )* } + #[allow(clippy::large_enum_variant)] // it's boxed anyways + pub(crate) enum SurfaceDispatch<$dgen, $wgen, $alpha> { + $( + $(#[$attr])* + $name($surface_inner), + )* + } + + pub(crate) enum BufferDispatch<'a, $dgen, $wgen, $alpha> { + $( + $(#[$attr])* + $name($buffer_inner), + )* + } + impl ContextDispatch { pub fn variant_name(&self) -> &'static str { match self { @@ -54,18 +70,21 @@ macro_rules! make_dispatch { Err(InitError::Unsupported(display)) } } + }; +} - #[allow(clippy::large_enum_variant)] // it's boxed anyways - pub(crate) enum SurfaceDispatch<$dgen, $wgen> { - $( - $(#[$attr])* - $name($surface_inner), - )* - } - - impl SurfaceInterface for SurfaceDispatch { +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{ type Context = ContextDispatch; - type Buffer<'a> = BufferDispatch<'a, D, W> where Self: 'a; + type Buffer<'a> = BufferDispatch<'a, D, W, $alpha> where Self: 'a; fn new(window: W, display: &Self::Context) -> Result> where @@ -79,6 +98,18 @@ macro_rules! make_dispatch { } } + fn new_with_alpha(window: W, display: &Self::Context) -> Result> + where + W: Sized, + Self: Sized { + match display { + $( + $(#[$attr])* + ContextDispatch::$name(inner) => Ok(Self::$name(<$surface_inner>::new_with_alpha(window, inner)?)), + )* + } + } + fn window(&self) -> &W { match self { $( @@ -97,7 +128,7 @@ macro_rules! make_dispatch { } } - fn buffer_mut(&mut self) -> Result, SoftBufferError> { + fn buffer_mut(&mut self) -> Result, SoftBufferError> { match self { $( $(#[$attr])* @@ -116,14 +147,8 @@ macro_rules! make_dispatch { } } - pub(crate) enum BufferDispatch<'a, $dgen, $wgen> { - $( - $(#[$attr])* - $name($buffer_inner), - )* - } - - impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferDispatch<'a, D, W> { + + impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface<$alpha> for BufferDispatch<'a, D, W, $alpha> { #[inline] fn pixels(&self) -> &[u32] { match self { @@ -144,6 +169,15 @@ macro_rules! make_dispatch { } } + fn pixels_rgb_mut(&mut self) -> &mut[<$alpha as BufferReturn>::Output]{ + match self { + $( + $(#[$attr])* + Self::$name(inner) => inner.pixels_rgb_mut(), + )* + } + } + fn age(&self) -> u8 { match self { $( @@ -176,8 +210,8 @@ macro_rules! make_dispatch { // XXX empty enum with generic bound is invalid? -make_dispatch! { - => +make_enum!{ + => #[cfg(x11_platform)] X11(Arc>, backends::x11::X11Impl, backends::x11::BufferImpl<'a, D, W>), #[cfg(wayland_platform)] @@ -185,11 +219,34 @@ make_dispatch! { #[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>), + Win32(D, backends::win32::Win32Impl, backends::win32::BufferImpl<'a, D, W, A>), #[cfg(target_vendor = "apple")] - CoreGraphics(D, backends::cg::CGImpl, backends::cg::BufferImpl<'a, D, W>), + CoreGraphics(D, backends::cg::CGImpl, backends::cg::BufferImpl<'a, D, W, A>), #[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>), } + +#[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 13e3555..3bdd70e 100644 --- a/src/backend_interface.rs +++ b/src/backend_interface.rs @@ -1,9 +1,10 @@ //! Interface implemented by backends -use crate::{InitError, Rect, SoftBufferError}; +use crate::{BufferReturn, InitError, Rect, SoftBufferError}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; -use std::num::NonZeroU32; +use std::{fmt::Debug, num::NonZeroU32}; +use num::cast::AsPrimitive; pub(crate) trait ContextInterface { fn new(display: D) -> Result> @@ -12,13 +13,17 @@ pub(crate) trait ContextInterface { Self: Sized; } -pub(crate) trait SurfaceInterface { +pub(crate) trait SurfaceInterface { type Context: ContextInterface; - type Buffer<'a>: BufferInterface + type Buffer<'a>: BufferInterface where Self: 'a; fn new(window: W, context: &Self::Context) -> Result> + where + W: Sized, + Self: Sized; + fn new_with_alpha(window: W, context: &Self::Context) -> Result> where W: Sized, Self: Sized; @@ -34,10 +39,147 @@ 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"] 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"] fn pixels_mut(&mut self) -> &mut [u32]; + 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 { + ( + $( + $(#[$attr:meta])* + $first_vis:vis $first:ident,$second_vis:vis $second:ident,$third_vis:vis $third:ident,$forth_vis:vis $forth:ident + )* + ) => { + $( + $(#[$attr])* + #[repr(C)] + pub struct RGBX{ + $forth_vis $forth: u8, + $third_vis $third: u8, + $second_vis $second: u8, + $first_vis $first: u8, + } + )* + }; +} + +macro_rules! define_rgba_little_endian { + ( + $( + $(#[$attr:meta])* + $first_vis:vis $first:ident,$second_vis:vis $second:ident,$third_vis:vis $third:ident,$forth_vis:vis $forth:ident + )* + ) => { + $( + $(#[$attr])* + #[repr(C)] + pub struct RGBA{ + $forth_vis $forth: u8, + $third_vis $third: u8, + $second_vis $second: u8, + $first_vis $first: u8, + } + )* + }; +} + +define_rgbx_little_endian!{ + #[cfg(x11_platform)] + x,pub r,pub g,pub b + #[cfg(wayland_platform)] + x,pub r,pub g,pub b + #[cfg(kms_platform)] + x,pub r,pub g,pub b + #[cfg(target_os = "windows")] + x,pub r,pub g,pub b + #[cfg(target_vendor = "apple")] + x,pub r,pub g,pub b + #[cfg(target_arch = "wasm32")] + x,pub r,pub g,pub b + #[cfg(target_os = "redox")] + x,pub r,pub g,pub b +} + +define_rgba_little_endian!{ + #[cfg(x11_platform)] + pub a,pub r,pub g,pub b + #[cfg(wayland_platform)] + pub a,pub r,pub g,pub b + #[cfg(kms_platform)] + pub a,pub r,pub g,pub b + #[cfg(target_os = "windows")] + pub a,pub r,pub g,pub b + #[cfg(target_vendor = "apple")] + pub a,pub r,pub g,pub b + #[cfg(target_arch = "wasm32")] + pub a,pub r,pub g,pub b + #[cfg(target_os = "redox")] + pub a,pub r,pub g,pub b +} + +impl RGBX{ + #[inline] + /// Creates new RGBX from r,g,b values. + /// Takes any primitive value that can be converted to a u8 using the ```as``` keyword + /// If the value is greater than the u8::MAX the function will return an error + pub fn new(r: T,g: T,b: T) -> Result + where + T: AsPrimitive + std::cmp::PartialOrd, + u8: AsPrimitive + { + let MAX_U8 = 255.as_(); + 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 }) + } + } + + /// Creates new RGBX from r,g,b values. + /// Takes any primitive value that can be converted to a u8 using the ```as``` keyword + /// Unlike ```RGBX::new``` this function does not care if the value you provide is greater than u8. It will silently ignore any higher bits, taking only the last 8 bits. + #[inline] + pub fn new_unchecked(r: T,g: T,b: T) -> Self + where + T: AsPrimitive + { + Self { r: r.as_(), g: g.as_(), b: b.as_(), x: 255 } + } +} + +impl RGBA{ + #[inline] + /// Creates new RGBX from r,g,b values. + /// Takes any primitive value that can be converted to a u8 using the ```as``` keyword + /// If the value is greater than the u8::MAX the function will return an error + pub fn new(r: T,g: T,b: T,a: T) -> Result + where + T: AsPrimitive + std::cmp::PartialOrd, + u8: AsPrimitive + { + let max_u8 = 255.as_(); + if r > max_u8 || g > max_u8 || b > max_u8 || a > max_u8{ + Err(SoftBufferError::PrimitiveOutsideOfU8Range) + }else{ + Ok(Self { r: r.as_(), g: g.as_(), b: b.as_(), a: a.as_() }) + } + } + + /// Creates new RGBX from r,g,b values. + /// Takes any primitive value that can be converted to a u8 using the ```as``` keyword + /// Unlike ```RGBX::new``` this function does not care if the value you provide is greater than u8. It will silently ignore any higher bits, taking only the last 8 bits. + #[inline] + pub fn new_unchecked(r: T,g: T,b: T, a: T) -> Self + where + T: AsPrimitive + { + Self { r: r.as_(), g: g.as_(), b: b.as_(), a: a.as_() } + } +} diff --git a/src/backends/cg.rs b/src/backends/cg.rs index ba957d8..36bfea4 100644 --- a/src/backends/cg.rs +++ b/src/backends/cg.rs @@ -1,12 +1,13 @@ -use crate::backend_interface::*; +use crate::{backend_interface::*, BufferReturn, WithAlpha, WithoutAlpha}; use crate::error::InitError; use crate::{Rect, SoftBufferError}; use core_graphics::base::{ - kCGBitmapByteOrder32Little, kCGImageAlphaNoneSkipFirst, kCGRenderingIntentDefault, + kCGBitmapByteOrder32Little, kCGImageAlphaNoneSkipFirst, kCGRenderingIntentDefault, kCGImageAlphaFirst }; 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}; @@ -19,6 +20,7 @@ use objc2_foundation::{ use objc2_quartz_core::{kCAGravityTopLeft, CALayer, CATransaction}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle}; +// use core::slice::SlicePattern; use std::ffi::c_void; use std::marker::PhantomData; use std::num::NonZeroU32; @@ -113,7 +115,7 @@ impl Observer { } } -pub struct CGImpl { +pub struct CGImpl { /// Our layer. layer: SendCALayer, /// The layer that our layer was created from. @@ -127,10 +129,10 @@ pub struct CGImpl { /// The height of the underlying buffer. height: usize, window_handle: W, - _display: PhantomData, + _display: PhantomData<(D,A)>, } -impl Drop for CGImpl { +impl Drop for CGImpl { fn drop(&mut self) { // SAFETY: Registered in `new`, must be removed before the observer is deallocated. unsafe { @@ -142,9 +144,15 @@ impl Drop for CGImpl { } } -impl SurfaceInterface for CGImpl { +#[duplicate_item( + TY; + [ WithAlpha ]; + [ WithoutAlpha ]; + )] +impl SurfaceInterface for CGImpl +{ type Context = D; - type Buffer<'a> = BufferImpl<'a, D, W> where Self: 'a; + type Buffer<'a> = BufferImpl<'a, D, W, TY> where Self: 'a; fn new(window_src: W, _display: &D) -> Result> { // `NSView`/`UIView` can only be accessed from the main thread. @@ -264,6 +272,124 @@ impl SurfaceInterface for CGImpl< }) } + 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, + }) + } + #[inline] fn window(&self) -> &W { &self.window_handle @@ -275,20 +401,26 @@ impl SurfaceInterface for CGImpl< 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, + _marker: PhantomData }) } } -pub struct BufferImpl<'a, D, W> { - imp: &'a mut CGImpl, +pub struct BufferImpl<'a, D, W, A> { + imp: &'a mut CGImpl, buffer: Vec, + _marker: PhantomData, } - -impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl<'a, D, W> { +#[duplicate_item( + TY platform_alpha_mode; + [ WithAlpha ] [kCGImageAlphaFirst]; + [ WithoutAlpha ] [kCGImageAlphaNoneSkipFirst]; + )] +impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl<'a, D, W, TY> { #[inline] fn pixels(&self) -> &[u32] { &self.buffer @@ -299,6 +431,10 @@ impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl &mut 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 { 0 } @@ -313,7 +449,7 @@ impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl 32, self.imp.width * 4, &self.imp.color_space.0, - kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, + kCGBitmapByteOrder32Little | platform_alpha_mode, &data_provider, false, kCGRenderingIntentDefault, diff --git a/src/backends/win32.rs b/src/backends/win32.rs index 084ad47..0ff1ca8 100644 --- a/src/backends/win32.rs +++ b/src/backends/win32.rs @@ -2,8 +2,9 @@ //! //! This module converts the input buffer into a bitmap and then stretches it to the window. -use crate::backend_interface::*; +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; @@ -16,7 +17,8 @@ use std::sync::{mpsc, Mutex, OnceLock}; use std::thread; use windows_sys::Win32::Foundation::HWND; -use windows_sys::Win32::Graphics::Gdi; +use windows_sys::Win32::Graphics::Gdi::{self, BITMAPINFO, CIEXYZTRIPLE, CIEXYZ}; +use windows_sys::Win32::UI::ColorSystem::LCS_WINDOWS_COLOR_SPACE; const ZERO_QUAD: Gdi::RGBQUAD = Gdi::RGBQUAD { rgbBlue: 0, @@ -113,6 +115,97 @@ impl Buffer { } } + fn new_with_alpha(window_dc: Gdi::HDC, width: NonZeroI32, height: NonZeroI32) -> Self { + let dc = Allocator::get().allocate(window_dc); + assert!(!dc.is_null()); + + // Create a new bitmap info struct. + let bitmap_info = BitmapInfoV4 { + bmi_header: Gdi::BITMAPV4HEADER { + bV4Size: mem::size_of::() as u32, + bV4Width: width.get(), + bV4Height: -height.get(), + bV4Planes: 1, + bV4BitCount: 32, + bV4V4Compression: Gdi::BI_BITFIELDS, + bV4SizeImage: 0, + bV4XPelsPerMeter: 0, + bV4YPelsPerMeter: 0, + bV4ClrUsed: 0, + bV4ClrImportant: 0, + bV4RedMask: 0x00ff0000, + bV4GreenMask: 0x0000ff00, + bV4BlueMask: 0xff0000ff, + bV4AlphaMask: 0xff000000, + bV4CSType: LCS_WINDOWS_COLOR_SPACE as u32, + bV4Endpoints: CIEXYZTRIPLE{ + ciexyzRed: CIEXYZ{ + ciexyzX: 0, + ciexyzY: 0, + ciexyzZ: 0, + }, + ciexyzGreen: CIEXYZ{ + ciexyzX: 0, + ciexyzY: 0, + ciexyzZ: 0, + }, + ciexyzBlue: CIEXYZ{ + ciexyzX: 0, + ciexyzY: 0, + ciexyzZ: 0, + }, + }, + bV4GammaRed: 0, + bV4GammaGreen: 0, + bV4GammaBlue: 0, + }, + bmi_colors: [ + Gdi::RGBQUAD { + rgbRed: 0xff, + ..ZERO_QUAD + }, + Gdi::RGBQUAD { + rgbGreen: 0xff, + ..ZERO_QUAD + }, + Gdi::RGBQUAD { + rgbBlue: 0xff, + ..ZERO_QUAD + }, + ], + }; + + // XXX alignment? + // XXX better to use CreateFileMapping, and pass hSection? + // XXX test return value? + let mut pixels: *mut u32 = ptr::null_mut(); + let bitmap = unsafe { + Gdi::CreateDIBSection( + dc, + &bitmap_info as *const BitmapInfoV4 as *const _, + Gdi::DIB_RGB_COLORS, + &mut pixels as *mut *mut u32 as _, + ptr::null_mut(), + 0, + ) + }; + assert!(!bitmap.is_null()); + let pixels = NonNull::new(pixels).unwrap(); + + unsafe { + Gdi::SelectObject(dc, bitmap); + } + + Self { + dc, + bitmap, + width, + height, + pixels, + presented: false, + } + } + #[inline] fn pixels(&self) -> &[u32] { unsafe { @@ -135,7 +228,7 @@ impl Buffer { } /// The handle to a window for software buffering. -pub struct Win32Impl { +pub struct Win32Impl { /// The window handle. window: OnlyUsedFromOrigin, @@ -154,9 +247,10 @@ pub struct Win32Impl { /// /// We don't use this, but other code might. _display: PhantomData, + _marker: PhantomData, } -impl Drop for Win32Impl { +impl Drop for Win32Impl { fn drop(&mut self) { // Release our resources. Allocator::get().release(self.window.0, self.dc.0); @@ -170,7 +264,14 @@ struct BitmapInfo { bmi_colors: [Gdi::RGBQUAD; 3], } -impl Win32Impl { +/// The Win32-compatible bitmap information. +#[repr(C)] +struct BitmapInfoV4 { + bmi_header: Gdi::BITMAPV4HEADER, + bmi_colors: [Gdi::RGBQUAD; 3], +} + +impl Win32Impl { fn present_with_damage(&mut self, damage: &[Rect]) -> Result<(), SoftBufferError> { let buffer = self.buffer.as_mut().unwrap(); unsafe { @@ -206,9 +307,14 @@ impl Win32Impl { } } -impl SurfaceInterface for Win32Impl { +#[duplicate_item( + TY internal_buffer_function; + [ WithAlpha ] [new_with_alpha]; + [ WithoutAlpha ] [new]; + )] +impl SurfaceInterface for Win32Impl { type Context = D; - type Buffer<'a> = BufferImpl<'a, D, W> where Self: 'a; + type Buffer<'a> = BufferImpl<'a, D, W, TY> where Self: 'a; /// Create a new `Win32Impl` from a `Win32WindowHandle`. fn new(window: W, _display: &D) -> Result> { @@ -238,6 +344,45 @@ impl SurfaceInterface for Win32Im buffer: None, handle: window, _display: PhantomData, + _marker: PhantomData, + }) + } + + fn new_with_alpha( + window: W, + context: &Self::Context, + ) -> Result> + where + 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, }) } @@ -260,12 +405,12 @@ impl SurfaceInterface for Win32Im } } - self.buffer = Some(Buffer::new(self.dc.0, width, height)); + self.buffer = Some(Buffer::internal_buffer_function(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()`"); } @@ -279,9 +424,14 @@ impl SurfaceInterface for Win32Im } } -pub struct BufferImpl<'a, D, W>(&'a mut Win32Impl); +pub struct BufferImpl<'a, D, W, A>(&'a mut Win32Impl); -impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl<'a, D, W> { +#[duplicate_item( + TY; + [ WithAlpha ]; + [ WithoutAlpha ]; + )] +impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl<'a, D, W, TY> { #[inline] fn pixels(&self) -> &[u32] { self.0.buffer.as_ref().unwrap().pixels() @@ -292,6 +442,14 @@ impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl self.0.buffer.as_mut().unwrap().pixels_mut() } + fn pixels_rgb_mut(&mut self) -> &mut [::Output] { + unsafe { + std::mem::transmute::<&mut [u32], &mut [::Output]>( + self.0.buffer.as_mut().unwrap().pixels_mut(), + ) + } + } + fn age(&self) -> u8 { match self.0.buffer.as_ref() { Some(buffer) if buffer.presented => 1, diff --git a/src/error.rs b/src/error.rs index eaec856..4aee4cc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -100,6 +100,9 @@ pub enum SoftBufferError { /// actual error type. PlatformError(Option, Option>), + /// An Error returned from RGBX/RGBA::new() if called with numbers that are higher than u8::MAX + PrimitiveOutsideOfU8Range, + /// This function is unimplemented on this platform. Unimplemented, } @@ -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::PrimitiveOutsideOfU8Range => write!(f, "The passed in primitive value is greater than what can fit in a u8"), Self::Unimplemented => write!(f, "This function is unimplemented on this platform."), } } diff --git a/src/lib.rs b/src/lib.rs index d19eafa..3631f45 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,8 +22,10 @@ use std::sync::Arc; 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; @@ -71,20 +73,35 @@ pub struct Rect { pub height: NonZeroU32, } +pub trait BufferReturn { + type Output: Sized; +} +pub enum WithoutAlpha{} + +impl BufferReturn for WithoutAlpha{ + type Output = RGBX; +} +pub enum WithAlpha{} + + +impl BufferReturn for WithAlpha{ + type Output = RGBA; +} + /// A surface for drawing to a window with software buffers. -pub struct Surface { +pub struct Surface { /// This is boxed so that `Surface` is the same size on every platform. - surface_impl: Box>, + surface_impl: Box>, _marker: PhantomData>, } -impl 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) -> 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(); @@ -97,6 +114,14 @@ impl Surface { Err(InitError::Failure(f)) => Err(f), } } +} + +#[duplicate_item( + TY; + [ WithAlpha ]; + [ WithoutAlpha ]; + )] +impl Surface { /// Get a reference to the underlying window handle. pub fn window(&self) -> &W { @@ -134,7 +159,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, @@ -142,14 +167,45 @@ impl Surface { } } -impl AsRef for 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> { + match SurfaceDispatch::new_with_alpha(window, &context.context_impl) { + Ok(surface_dispatch) => Ok(Self { + surface_impl: Box::new(surface_dispatch), + _marker: PhantomData + }), + Err(InitError::Unsupported(window)) => { + let raw = window.window_handle()?.as_raw(); + Err(SoftBufferError::UnsupportedWindowPlatform { + human_readable_window_platform_name: window_handle_type_name(&raw), + human_readable_display_platform_name: context.context_impl.variant_name(), + window_handle: raw, + }) + } + Err(InitError::Failure(f)) => Err(f), + } + } +} + +#[duplicate_item( + TY; + [ WithAlpha ]; + [ WithoutAlpha ]; + )] +impl AsRef for Surface { #[inline] fn as_ref(&self) -> &W { self.window() } } -impl HasWindowHandle for Surface { +#[duplicate_item( + TY; + [ WithAlpha ]; + [ WithoutAlpha ]; + )] +impl HasWindowHandle for Surface { #[inline] fn window_handle( &self, @@ -196,12 +252,17 @@ impl HasWindowHandle for Surface /// - Web /// - AppKit /// - UIKit -pub struct Buffer<'a, D, W> { - buffer_impl: BufferDispatch<'a, D, W>, +pub struct Buffer<'a, D, W, A> { + buffer_impl: BufferDispatch<'a, D, W, A>, _marker: PhantomData<(Arc, Cell<()>)>, } -impl<'a, D: HasDisplayHandle, W: HasWindowHandle> Buffer<'a, D, W> { +#[duplicate_item( + TY; + [ WithAlpha ]; + [ WithoutAlpha ]; + )] +impl<'a, D: HasDisplayHandle, W: HasWindowHandle> Buffer<'a, D, W, TY> { /// 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 @@ -244,7 +305,29 @@ impl<'a, D: HasDisplayHandle, W: HasWindowHandle> Buffer<'a, D, W> { } } -impl<'a, D: HasDisplayHandle, W: HasWindowHandle> ops::Deref for Buffer<'a, D, W> { +#[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] { + 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] { + self.buffer_impl.pixels_mut() + } +} + +#[duplicate_item( + TY; + [ WithAlpha ]; + [ WithoutAlpha ]; + )] +#[cfg(feature = "compatibility")] +impl<'a, D: HasDisplayHandle, W: HasWindowHandle> ops::Deref for Buffer<'a, D, W, TY> { type Target = [u32]; #[inline] @@ -253,13 +336,51 @@ impl<'a, D: HasDisplayHandle, W: HasWindowHandle> ops::Deref for Buffer<'a, D, W } } -impl<'a, D: HasDisplayHandle, W: HasWindowHandle> ops::DerefMut 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> { #[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]; + + #[inline] + fn deref(&self) -> &[::Output] { + // self.buffer_impl.pixels() + todo!() + } +} + +#[duplicate_item( + TY; + [ WithAlpha ]; + [ WithoutAlpha ]; + )] +#[cfg(not(feature = "compatibility"))] +impl<'a, D: HasDisplayHandle, W: HasWindowHandle> ops::DerefMut for Buffer<'a, D, W,TY> { + // type Target = [crate::RGBX]; + #[inline] + fn deref_mut(&mut self) -> &mut [::Output] { + // self.buffer_impl.pixels_mut() + self.buffer_impl.pixels_rgb_mut() + } +} + + /// There is no display handle. #[derive(Debug)] #[allow(dead_code)] @@ -330,7 +451,7 @@ fn __assert_send() { is_send::>(); is_sync::>(); is_send::>(); - is_send::>(); + is_send::>(); /// ```compile_fail /// use softbuffer::Surface;