From 56ae973dca3489f2ed896093ab829c9ed5f330eb Mon Sep 17 00:00:00 2001 From: John Nunley Date: Sat, 1 Jun 2024 11:53:12 -0700 Subject: [PATCH] feat: Implement Send and Sync for softbuffer types Previously, types in softbuffer were !Send and !Sync. However it would be nice if types could be shared across threads. Therefore I've made the following changes: - Context is Send+Sync iff D is Send+Sync - Surface is Send iff D is Send+Sync and W is Send - Buffer<'x, D, W> is Send iff D if Send+Sync and W is Send Materially, I've made the following changes across the backends: - X11, Wayland and KMS use Arc for their displays instead of Rc. - MacOS uses MainThreadBound to secure windowing resources. This restriction was already implicitly enforced anyhow. - Windows creates a thread specifically for allocating and then deallocating device contexts. This lets us get around the thread affinity problem. Closes #205 Signed-off-by: John Nunley Co-authored-by: Mads Marquart --- Cargo.toml | 4 +- examples/utils/winit_app.rs | 13 +-- examples/winit_multithread.rs | 133 ++++++++++++++++++++++++++++ src/backend_dispatch.rs | 8 +- src/backends/cg.rs | 36 +++++--- src/backends/kms.rs | 12 +-- src/backends/orbital.rs | 10 ++- src/backends/wayland/mod.rs | 33 ++++--- src/backends/win32.rs | 157 +++++++++++++++++++++++++++++++++- src/backends/x11.rs | 15 ++-- src/lib.rs | 37 +++++++- src/util.rs | 2 + 12 files changed, 400 insertions(+), 60 deletions(-) create mode 100644 examples/winit_multithread.rs diff --git a/Cargo.toml b/Cargo.toml index 583050d..7605cda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,14 +43,14 @@ x11rb = { version = "0.13.0", features = ["allow-unsafe-code", "shm"], optional [target.'cfg(target_os = "windows")'.dependencies.windows-sys] version = "0.52.0" -features = ["Win32_Graphics_Gdi", "Win32_UI_WindowsAndMessaging", "Win32_Foundation"] +features = ["Win32_Graphics_Gdi", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging", "Win32_Foundation"] [target.'cfg(target_os = "macos")'.dependencies] bytemuck = { version = "1.12.3", features = ["extern_crate_alloc"] } core-graphics = "0.23.1" foreign-types = "0.5.0" objc2 = "0.5.1" -objc2-foundation = { version = "0.2.0", features = ["NSThread"] } +objc2-foundation = { version = "0.2.0", features = ["dispatch", "NSThread"] } objc2-app-kit = { version = "0.2.0", features = ["NSResponder", "NSView", "NSWindow"] } objc2-quartz-core = { version = "0.2.0", features = ["CALayer", "CATransaction"] } diff --git a/examples/utils/winit_app.rs b/examples/utils/winit_app.rs index 6e45d88..b8618bf 100644 --- a/examples/utils/winit_app.rs +++ b/examples/utils/winit_app.rs @@ -13,12 +13,13 @@ mod winit_app { pub(crate) fn run_app(event_loop: EventLoop<()>, mut app: impl ApplicationHandler<()> + 'static) { #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] event_loop.run_app(&mut app).unwrap(); - + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] winit::platform::web::EventLoopExtWebSys::spawn_app(event_loop, app); } /// Create a window from a set of window attributes. + #[allow(dead_code)] pub(crate) fn make_window(elwt: &ActiveEventLoop, f: impl FnOnce(WindowAttributes) -> WindowAttributes) -> Rc { let attributes = f(WindowAttributes::default()); #[cfg(target_arch = "wasm32")] @@ -29,7 +30,7 @@ mod winit_app { let window = elwt.create_window(attributes); Rc::new(window.unwrap()) } - + /// Easily constructable winit application. pub(crate) struct WinitApp { /// Closure to initialize state. @@ -51,7 +52,7 @@ mod winit_app { _marker: PhantomData>, } - impl WinitAppBuilder + impl WinitAppBuilder where Init: FnMut(&ActiveEventLoop) -> T, { /// Create with an "init" closure. @@ -70,7 +71,7 @@ mod winit_app { } } - impl WinitApp + impl WinitApp where Init: FnMut(&ActiveEventLoop) -> T, Handler: FnMut(&mut T, Event<()>, &ActiveEventLoop) { @@ -84,7 +85,7 @@ mod winit_app { } } - impl ApplicationHandler for WinitApp + impl ApplicationHandler for WinitApp where Init: FnMut(&ActiveEventLoop) -> T, Handler: FnMut(&mut T, Event<()>, &ActiveEventLoop) { @@ -97,7 +98,7 @@ mod winit_app { let state = self.state.take(); debug_assert!(state.is_some()); drop(state); - } + } fn window_event( &mut self, diff --git a/examples/winit_multithread.rs b/examples/winit_multithread.rs new file mode 100644 index 0000000..52731c5 --- /dev/null +++ b/examples/winit_multithread.rs @@ -0,0 +1,133 @@ +//! `Surface` implements `Send`. This makes sure that multithreading can work here. + +#[cfg(not(target_family = "wasm"))] +mod ex { + use std::num::NonZeroU32; + use std::sync::{mpsc, Arc, Mutex}; + use winit::event::{Event, KeyEvent, WindowEvent}; + use winit::event_loop::{ControlFlow, EventLoop}; + use winit::keyboard::{Key, NamedKey}; + use winit::window::Window; + + include!("utils/winit_app.rs"); + + type Surface = softbuffer::Surface, Arc>; + + fn render_thread( + window: Arc, + surface: Arc>, + do_render: mpsc::Receiver<()>, + done: mpsc::Sender<()>, + ) { + loop { + println!("waiting for render..."); + if do_render.recv().is_err() { + // Main thread is dead. + break; + } + + // Perform the rendering. + let mut surface = surface.lock().unwrap(); + if let (Some(width), Some(height)) = { + let size = window.inner_size(); + println!("got size: {size:?}"); + (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) + } { + println!("resizing..."); + surface.resize(width, height).unwrap(); + + let mut buffer = surface.buffer_mut().unwrap(); + for y in 0..height.get() { + for x in 0..width.get() { + let red = x % 255; + let green = y % 255; + let blue = (x * y) % 255; + let index = y as usize * width.get() as usize + x as usize; + buffer[index] = blue | (green << 8) | (red << 16); + } + } + + println!("presenting..."); + buffer.present().unwrap(); + } + + // We're done, tell the main thread to keep going. + done.send(()).ok(); + } + } + + pub(super) fn entry() { + let event_loop = EventLoop::new().unwrap(); + + let app = winit_app::WinitAppBuilder::with_init(|elwt| { + let attributes = Window::default_attributes(); + #[cfg(target_arch = "wasm32")] + let attributes = + winit::platform::web::WindowAttributesExtWebSys::with_append(attributes, true); + let window = Arc::new(elwt.create_window(attributes).unwrap()); + + let context = softbuffer::Context::new(window.clone()).unwrap(); + let surface = { + println!("making surface..."); + let surface = softbuffer::Surface::new(&context, window.clone()).unwrap(); + Arc::new(Mutex::new(surface)) + }; + + // Spawn a thread to handle rendering. + let (start_render, do_render) = mpsc::channel(); + let (render_done, finish_render) = mpsc::channel(); + println!("starting thread..."); + std::thread::spawn({ + let window = window.clone(); + let surface = surface.clone(); + move || render_thread(window, surface, do_render, render_done) + }); + + (window, surface, start_render, finish_render) + }) + .with_event_handler(|state, event, elwt| { + let (window, _surface, start_render, finish_render) = state; + elwt.set_control_flow(ControlFlow::Wait); + + match event { + Event::WindowEvent { + window_id, + event: WindowEvent::RedrawRequested, + } if window_id == window.id() => { + // Start the render and then finish it. + start_render.send(()).unwrap(); + finish_render.recv().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); + } +} + +#[cfg(target_family = "wasm")] +mod ex { + pub(crate) fn entry() { + eprintln!("winit_multithreaded doesn't work on WASM"); + } +} + +fn main() { + ex::entry(); +} diff --git a/src/backend_dispatch.rs b/src/backend_dispatch.rs index e23a0c2..d8d23aa 100644 --- a/src/backend_dispatch.rs +++ b/src/backend_dispatch.rs @@ -5,7 +5,7 @@ use crate::{backend_interface::*, backends, InitError, Rect, SoftBufferError}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use std::num::NonZeroU32; #[cfg(any(wayland_platform, x11_platform, kms_platform))] -use std::rc::Rc; +use std::sync::Arc; /// A macro for creating the enum used to statically dispatch to the platform-specific implementation. macro_rules! make_dispatch { @@ -179,11 +179,11 @@ macro_rules! make_dispatch { make_dispatch! { => #[cfg(x11_platform)] - X11(Rc>, backends::x11::X11Impl, backends::x11::BufferImpl<'a, D, W>), + X11(Arc>, backends::x11::X11Impl, backends::x11::BufferImpl<'a, D, W>), #[cfg(wayland_platform)] - Wayland(Rc>, backends::wayland::WaylandImpl, backends::wayland::BufferImpl<'a, D, W>), + Wayland(Arc>, backends::wayland::WaylandImpl, backends::wayland::BufferImpl<'a, D, W>), #[cfg(kms_platform)] - Kms(Rc>, backends::kms::KmsImpl, backends::kms::BufferImpl<'a, D, W>), + 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>), #[cfg(target_os = "macos")] diff --git a/src/backends/cg.rs b/src/backends/cg.rs index 5f4f403..4a5db6a 100644 --- a/src/backends/cg.rs +++ b/src/backends/cg.rs @@ -14,7 +14,7 @@ use foreign_types::ForeignType; use objc2::msg_send; use objc2::rc::Id; use objc2_app_kit::{NSAutoresizingMaskOptions, NSView, NSWindow}; -use objc2_foundation::MainThreadMarker; +use objc2_foundation::{MainThreadBound, MainThreadMarker}; use objc2_quartz_core::{kCAGravityTopLeft, CALayer, CATransaction}; use std::marker::PhantomData; @@ -30,9 +30,9 @@ impl AsRef<[u8]> for Buffer { } pub struct CGImpl { - layer: Id, - window: Id, - color_space: CGColorSpace, + layer: MainThreadBound>, + window: MainThreadBound>, + color_space: SendCGColorSpace, size: Option<(NonZeroU32, NonZeroU32)>, window_handle: W, _display: PhantomData, @@ -84,9 +84,9 @@ impl SurfaceInterface for CGImpl< unsafe { view.addSubview(&subview) }; let color_space = CGColorSpace::create_device_rgb(); Ok(Self { - layer, - window, - color_space, + layer: MainThreadBound::new(layer, mtm), + window: MainThreadBound::new(window, mtm), + color_space: SendCGColorSpace(color_space), size: None, _display: PhantomData, window_handle: window_src, @@ -144,27 +144,30 @@ impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl 8, 32, (width.get() * 4) as usize, - &self.imp.color_space, + &self.imp.color_space.0, kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, &data_provider, false, kCGRenderingIntentDefault, ); + // TODO: Use run_on_main() instead. + let mtm = MainThreadMarker::new().ok_or(SoftBufferError::PlatformError( + Some("can only access AppKit / macOS handles from the main thread".to_string()), + None, + ))?; + // The CALayer has a default action associated with a change in the layer contents, causing // a quarter second fade transition to happen every time a new buffer is applied. This can // be mitigated by wrapping the operation in a transaction and disabling all actions. CATransaction::begin(); CATransaction::setDisableActions(true); - self.imp - .layer - .setContentsScale(self.imp.window.backingScaleFactor()); + let layer = self.imp.layer.get(mtm); + layer.setContentsScale(self.imp.window.get(mtm).backingScaleFactor()); unsafe { - self.imp - .layer - .setContents((image.as_ptr() as *mut AnyObject).as_ref()); + layer.setContents((image.as_ptr() as *mut AnyObject).as_ref()); }; CATransaction::commit(); @@ -176,3 +179,8 @@ impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl self.present() } } + +struct SendCGColorSpace(CGColorSpace); +// SAFETY: `CGColorSpace` is immutable, and can freely be shared between threads. +unsafe impl Send for SendCGColorSpace {} +unsafe impl Sync for SendCGColorSpace {} diff --git a/src/backends/kms.rs b/src/backends/kms.rs index 681c699..901fc73 100644 --- a/src/backends/kms.rs +++ b/src/backends/kms.rs @@ -15,7 +15,7 @@ use std::collections::HashSet; use std::marker::PhantomData; use std::num::NonZeroU32; use std::os::unix::io::{AsFd, BorrowedFd}; -use std::rc::Rc; +use std::sync::Arc; use crate::backend_interface::*; use crate::error::{InitError, SoftBufferError, SwResultExt}; @@ -38,7 +38,7 @@ impl AsFd for KmsDisplayImpl { impl Device for KmsDisplayImpl {} impl CtrlDevice for KmsDisplayImpl {} -impl ContextInterface for Rc> { +impl ContextInterface for Arc> { fn new(display: D) -> Result> where D: Sized, @@ -54,7 +54,7 @@ impl ContextInterface for Rc> // SAFETY: Invariants guaranteed by the user. let fd = unsafe { BorrowedFd::borrow_raw(fd) }; - Ok(Rc::new(KmsDisplayImpl { + Ok(Arc::new(KmsDisplayImpl { fd, _display: display, })) @@ -65,7 +65,7 @@ impl ContextInterface for Rc> #[derive(Debug)] pub(crate) struct KmsImpl { /// The display implementation. - display: Rc>, + display: Arc>, /// The connectors to use. connectors: Vec, @@ -133,11 +133,11 @@ struct SharedBuffer { } impl SurfaceInterface for KmsImpl { - type Context = Rc>; + type Context = Arc>; type Buffer<'a> = BufferImpl<'a, D, W> where Self: 'a; /// Create a new KMS backend. - fn new(window: W, display: &Rc>) -> Result> { + fn new(window: W, display: &Arc>) -> Result> { // Make sure that the window handle is valid. let plane_handle = match window.window_handle()?.as_raw() { RawWindowHandle::Drm(drm) => match NonZeroU32::new(drm.plane) { diff --git a/src/backends/orbital.rs b/src/backends/orbital.rs index 7fb5c16..96c3db1 100644 --- a/src/backends/orbital.rs +++ b/src/backends/orbital.rs @@ -56,7 +56,7 @@ impl Drop for OrbitalMap { } pub struct OrbitalImpl { - handle: OrbitalWindowHandle, + handle: ThreadSafeWindowHandle, width: u32, height: u32, presented: bool, @@ -64,9 +64,13 @@ pub struct OrbitalImpl { _display: PhantomData, } +struct ThreadSafeWindowHandle(OrbitalWindowHandle); +unsafe impl Send for ThreadSafeWindowHandle {} +unsafe impl Sync for ThreadSafeWindowHandle {} + impl OrbitalImpl { fn window_fd(&self) -> usize { - self.handle.window.as_ptr() as usize + self.handle.0.window.as_ptr() as usize } // Read the current width and size @@ -134,7 +138,7 @@ impl SurfaceInterface for Orbital }; Ok(Self { - handle, + handle: ThreadSafeWindowHandle(handle), width: 0, height: 0, presented: false, diff --git a/src/backends/wayland/mod.rs b/src/backends/wayland/mod.rs index 32be3d4..2dda8d2 100644 --- a/src/backends/wayland/mod.rs +++ b/src/backends/wayland/mod.rs @@ -5,9 +5,8 @@ use crate::{ }; use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle}; use std::{ - cell::RefCell, num::{NonZeroI32, NonZeroU32}, - rc::Rc, + sync::{Arc, Mutex}, }; use wayland_client::{ backend::{Backend, ObjectId}, @@ -23,7 +22,7 @@ struct State; pub struct WaylandDisplayImpl { conn: Option, - event_queue: RefCell>, + event_queue: Mutex>, qh: QueueHandle, shm: wl_shm::WlShm, @@ -40,7 +39,7 @@ impl WaylandDisplayImpl { } } -impl ContextInterface for Rc> { +impl ContextInterface for Arc> { fn new(display: D) -> Result> where D: Sized, @@ -59,9 +58,9 @@ impl ContextInterface for Rc Drop for WaylandDisplayImpl { } pub struct WaylandImpl { - display: Rc>, + display: Arc>, surface: Option, buffers: Option<(WaylandBuffer, WaylandBuffer)>, size: Option<(NonZeroI32, NonZeroI32)>, @@ -98,7 +97,8 @@ impl WaylandImpl { let _ = self .display .event_queue - .borrow_mut() + .lock() + .unwrap_or_else(|x| x.into_inner()) .dispatch_pending(&mut State); if let Some((front, back)) = &mut self.buffers { @@ -136,7 +136,12 @@ impl WaylandImpl { self.surface().commit(); } - let _ = self.display.event_queue.borrow_mut().flush(); + let _ = self + .display + .event_queue + .lock() + .unwrap_or_else(|x| x.into_inner()) + .flush(); Ok(()) } @@ -145,10 +150,10 @@ impl WaylandImpl { impl SurfaceInterface for WaylandImpl { - type Context = Rc>; + type Context = Arc>; type Buffer<'a> = BufferImpl<'a, D, W> where Self: 'a; - fn new(window: W, display: &Rc>) -> Result> { + fn new(window: W, display: &Arc>) -> Result> { // Get the raw Wayland window. let raw = window.window_handle()?.as_raw(); let wayland_handle = match raw { @@ -199,7 +204,11 @@ impl SurfaceInterface if let Some((_front, back)) = &mut self.buffers { // Block if back buffer not released yet if !back.released() { - let mut event_queue = self.display.event_queue.borrow_mut(); + let mut event_queue = self + .display + .event_queue + .lock() + .unwrap_or_else(|x| x.into_inner()); while !back.released() { event_queue.blocking_dispatch(&mut State).map_err(|err| { SoftBufferError::PlatformError( diff --git a/src/backends/win32.rs b/src/backends/win32.rs index 120b55a..4cc65f3 100644 --- a/src/backends/win32.rs +++ b/src/backends/win32.rs @@ -12,6 +12,8 @@ use std::mem; use std::num::{NonZeroI32, NonZeroU32}; use std::ptr::{self, NonNull}; use std::slice; +use std::sync::{mpsc, Mutex, OnceLock}; +use std::thread; use windows_sys::Win32::Foundation::HWND; use windows_sys::Win32::Graphics::Gdi; @@ -32,18 +34,21 @@ struct Buffer { presented: bool, } +unsafe impl Send for Buffer {} + impl Drop for Buffer { fn drop(&mut self) { unsafe { - Gdi::DeleteDC(self.dc); Gdi::DeleteObject(self.bitmap); } + + Allocator::get().deallocate(self.dc); } } impl Buffer { fn new(window_dc: Gdi::HDC, width: NonZeroI32, height: NonZeroI32) -> Self { - let dc = unsafe { Gdi::CreateCompatibleDC(window_dc) }; + let dc = Allocator::get().allocate(window_dc); assert!(dc != 0); // Create a new bitmap info struct. @@ -151,6 +156,13 @@ pub struct Win32Impl { _display: PhantomData, } +impl Drop for Win32Impl { + fn drop(&mut self) { + // Release our resources. + Allocator::get().release(self.window, self.dc); + } +} + /// The Win32-compatible bitmap information. #[repr(C)] struct BitmapInfo { @@ -199,7 +211,7 @@ impl SurfaceInterface for Win32Im // 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 = unsafe { Gdi::GetDC(hwnd) }; + let dc = Allocator::get().get_dc(hwnd); // GetDC returns null if there is a platform error. if dc == 0 { @@ -294,3 +306,142 @@ impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl imp.present_with_damage(damage) } } + +/// Allocator for device contexts. +/// +/// Device contexts can only be allocated or freed on the thread that originated them. +/// So we spawn a thread specifically for allocating and freeing device contexts. +/// This is the interface to that thread. +struct Allocator { + /// The channel for sending commands. + sender: Mutex>, +} + +impl Allocator { + /// Get the global instance of the allocator. + fn get() -> &'static Allocator { + static ALLOCATOR: OnceLock = OnceLock::new(); + ALLOCATOR.get_or_init(|| { + let (sender, receiver) = mpsc::channel::(); + + // Create a thread responsible for DC handling. + thread::Builder::new() + .name(concat!("softbuffer_", env!("CARGO_PKG_VERSION"), "_dc_allocator").into()) + .spawn(move || { + while let Ok(command) = receiver.recv() { + command.handle(); + } + }) + .expect("failed to spawn the DC allocator thread"); + + Allocator { + sender: Mutex::new(sender), + } + }) + } + + /// Send a command to the allocator thread. + fn send_command(&self, cmd: Command) { + self.sender.lock().unwrap().send(cmd).unwrap(); + } + + /// Get the device context for a window. + fn get_dc(&self, window: HWND) -> Gdi::HDC { + let (callback, waiter) = mpsc::sync_channel(1); + + // Send command to the allocator. + self.send_command(Command::GetDc { window, callback }); + + // Wait for the response back. + waiter.recv().unwrap() + } + + /// Allocate a new device context. + fn allocate(&self, dc: Gdi::HDC) -> Gdi::HDC { + let (callback, waiter) = mpsc::sync_channel(1); + + // Send command to the allocator. + self.send_command(Command::Allocate { dc, callback }); + + // Wait for the response back. + waiter.recv().unwrap() + } + + /// Deallocate a device context. + fn deallocate(&self, dc: Gdi::HDC) { + self.send_command(Command::Deallocate(dc)); + } + + /// Release a device context. + fn release(&self, owner: HWND, dc: Gdi::HDC) { + self.send_command(Command::Release { dc, owner }); + } +} + +/// Commands to be sent to the allocator. +enum Command { + /// Call `GetDc` to get the device context for the provided window. + GetDc { + /// The window to provide a device context for. + window: HWND, + + /// Send back the device context. + callback: mpsc::SyncSender, + }, + + /// Allocate a new device context using `GetCompatibleDc`. + Allocate { + /// The DC to be compatible with. + dc: Gdi::HDC, + + /// Send back the device context. + callback: mpsc::SyncSender, + }, + + /// Deallocate a device context. + Deallocate(Gdi::HDC), + + /// Release a window-associated device context. + Release { + /// The device context to release. + dc: Gdi::HDC, + + /// The window that owns this device context. + owner: HWND, + }, +} + +impl Command { + /// Handle this command. + /// + /// This should be called on the allocator thread. + fn handle(self) { + match self { + Self::GetDc { window, callback } => { + // Get the DC and send it back. + let dc = unsafe { Gdi::GetDC(window) }; + callback.send(dc).ok(); + } + + Self::Allocate { dc, callback } => { + // Allocate a DC and send it back. + let dc = unsafe { Gdi::CreateCompatibleDC(dc) }; + callback.send(dc).ok(); + } + + Self::Deallocate(dc) => { + // Deallocate this DC. + unsafe { + Gdi::DeleteDC(dc); + } + } + + Self::Release { dc, owner } => { + // Release this DC. + unsafe { + Gdi::ReleaseDC(owner, dc); + } + } + } + } +} diff --git a/src/backends/x11.rs b/src/backends/x11.rs index 16bdab4..d3b6080 100644 --- a/src/backends/x11.rs +++ b/src/backends/x11.rs @@ -24,8 +24,8 @@ use std::{ io, mem, num::{NonZeroU16, NonZeroU32}, ptr::{null_mut, NonNull}, - rc::Rc, slice, + sync::Arc, }; use as_raw_xcb_connection::AsRawXcbConnection; @@ -54,7 +54,7 @@ pub struct X11DisplayImpl { _display: D, } -impl ContextInterface for Rc> { +impl ContextInterface for Arc> { /// Create a new `X11DisplayImpl`. fn new(display: D) -> Result> where @@ -107,7 +107,7 @@ impl ContextInterface for Rc> let supported_visuals = supported_visuals(&connection); - Ok(Rc::new(X11DisplayImpl { + Ok(Arc::new(X11DisplayImpl { connection: Some(connection), is_shm_available, supported_visuals, @@ -127,7 +127,7 @@ impl X11DisplayImpl { /// The handle to an X11 drawing context. pub struct X11Impl { /// X display this window belongs to. - display: Rc>, + display: Arc>, /// The window to draw to. window: xproto::Window, @@ -183,11 +183,11 @@ struct ShmBuffer { } impl SurfaceInterface for X11Impl { - type Context = Rc>; + type Context = Arc>; type Buffer<'a> = BufferImpl<'a, D, W> where Self: 'a; /// Create a new `X11Impl` from a `HasWindowHandle`. - fn new(window_src: W, display: &Rc>) -> Result> { + fn new(window_src: W, display: &Arc>) -> Result> { // Get the underlying raw window handle. let raw = window_src.window_handle()?.as_raw(); let window_handle = match raw { @@ -687,6 +687,9 @@ struct ShmSegment { buffer_size: usize, } +// SAFETY: We respect Rust's mutability rules for the inner allocation. +unsafe impl Send for ShmSegment {} + impl ShmSegment { /// Create a new `ShmSegment` with the given size. fn new(size: usize, buffer_size: usize) -> io::Result { diff --git a/src/lib.rs b/src/lib.rs index b62127c..06cd6db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,9 +13,11 @@ mod backends; mod error; mod util; +use std::cell::Cell; use std::marker::PhantomData; use std::num::NonZeroU32; use std::ops; +use std::sync::Arc; use error::InitError; pub use error::SoftBufferError; @@ -28,10 +30,11 @@ pub use backends::web::SurfaceExtWeb; /// An instance of this struct contains the platform-specific data that must be managed in order to /// write to a window on that platform. pub struct Context { - _marker: PhantomData<*mut ()>, - /// The inner static dispatch object. context_impl: ContextDispatch, + + /// This is Send+Sync IFF D is Send+Sync. + _marker: PhantomData>, } impl Context { @@ -71,7 +74,7 @@ pub struct Rect { pub struct Surface { /// This is boxed so that `Surface` is the same size on every platform. surface_impl: Box>, - _marker: PhantomData<*mut ()>, + _marker: PhantomData>, } impl Surface { @@ -193,7 +196,7 @@ impl HasWindowHandle for Surface /// - macOS pub struct Buffer<'a, D, W> { buffer_impl: BufferDispatch<'a, D, W>, - _marker: PhantomData<*mut ()>, + _marker: PhantomData<(Arc, Cell<()>)>, } impl<'a, D: HasDisplayHandle, W: HasWindowHandle> Buffer<'a, D, W> { @@ -315,3 +318,29 @@ 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 } } + +#[cfg(not(target_family = "wasm"))] +fn __assert_send() { + fn is_send() {} + fn is_sync() {} + + is_send::>(); + is_sync::>(); + is_send::>(); + is_send::>(); + + /// ```compile_fail + /// use softbuffer::Surface; + /// + /// fn __is_sync() {} + /// __is_sync::>(); + /// ``` + fn __surface_not_sync() {} + /// ```compile_fail + /// use softbuffer::Buffer; + /// + /// fn __is_sync() {} + /// __is_sync::>(); + /// ``` + fn __buffer_not_sync() {} +} diff --git a/src/util.rs b/src/util.rs index b1219e1..de46e3f 100644 --- a/src/util.rs +++ b/src/util.rs @@ -19,6 +19,8 @@ pub struct BorrowStack<'a, T: 'a + ?Sized, U: 'a + ?Sized> { _phantom: std::marker::PhantomData<&'a mut T>, } +unsafe impl<'a, T: 'a + Send + ?Sized, U: 'a + Send + ?Sized> Send for BorrowStack<'a, T, U> {} + impl<'a, T: 'a + ?Sized, U: 'a + ?Sized> BorrowStack<'a, T, U> { pub fn new(container: &'a mut T, f: F) -> Result where