From 129069996e13c50d030f35de4460c3b24817acd2 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 6 Jan 2023 13:08:17 -0800 Subject: [PATCH] Split `GraphicsContext` into `Context` and `Surface` A `Context` is created with a display handle, and a `Surface` is created with a `&Context` and a window handle. Thus multiple windows can be created from the same context without duplicating anything that can be shared. This API is broadly similar to `wgpu` or `glutin`. On Wayland, the `Context` contains the `EventQueue`, which is shared between windows, and the `WlShm` global. On X11, `Context::new` checks for the availability of XShm, and contains a bool representing that as well as the `XCBConnection`. The shared context data is stored within the window in an `Arc`. On other platforms, the display isn't used and `Context` is empty. This does however test that the display handle has the right type on those platforms and fail otherwise. Previously the code didn't test that. Closes https://github.com/rust-windowing/softbuffer/issues/37. --- README.md | 6 +- examples/animation.rs | 6 +- examples/fruit.rs | 6 +- examples/libxcb.rs | 16 ++- examples/rectangle.rs | 6 +- examples/winit.rs | 6 +- examples/winit_wrong_sized_buffer.rs | 6 +- src/error.rs | 10 +- src/lib.rs | 178 +++++++++++++++++++-------- src/wayland/mod.rs | 62 +++++++--- src/x11.rs | 152 ++++++++++++++--------- 11 files changed, 297 insertions(+), 157 deletions(-) diff --git a/README.md b/README.md index a35e8b00..f7844918 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,6 @@ For now, the priority for new platforms is: Example == ```rust,no_run -use softbuffer::GraphicsContext; use winit::event::{Event, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::WindowBuilder; @@ -61,7 +60,8 @@ use winit::window::WindowBuilder; fn main() { let event_loop = EventLoop::new(); let window = WindowBuilder::new().build(&event_loop).unwrap(); - let mut graphics_context = unsafe { GraphicsContext::new(&window, &window) }.unwrap(); + let context = unsafe { softbuffer::Context::new(&window) }.unwrap(); + let mut surface = unsafe { softbuffer::Surface::new(&context, &window) }.unwrap(); event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; @@ -86,7 +86,7 @@ fn main() { }) .collect::>(); - graphics_context.set_buffer(&buffer, width as u16, height as u16); + surface.set_buffer(&buffer, width as u16, height as u16); } Event::WindowEvent { event: WindowEvent::CloseRequested, diff --git a/examples/animation.rs b/examples/animation.rs index 554203eb..7fcc7402 100644 --- a/examples/animation.rs +++ b/examples/animation.rs @@ -1,7 +1,6 @@ use instant::Instant; #[cfg(not(target_arch = "wasm32"))] use rayon::prelude::*; -use softbuffer::GraphicsContext; use std::f64::consts::PI; use winit::event::{Event, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; @@ -25,7 +24,8 @@ fn main() { .unwrap(); } - let mut graphics_context = unsafe { GraphicsContext::new(&window, &window) }.unwrap(); + let context = unsafe { softbuffer::Context::new(&window) }.unwrap(); + let mut surface = unsafe { softbuffer::Surface::new(&context, &window) }.unwrap(); let mut old_size = (0, 0); let mut frames = pre_render_frames(0, 0); @@ -48,7 +48,7 @@ fn main() { }; let buffer = &frames[((elapsed * 60.0).round() as usize).clamp(0, 59)]; - graphics_context.set_buffer(buffer.as_slice(), width as u16, height as u16); + surface.set_buffer(buffer.as_slice(), width as u16, height as u16); } Event::MainEventsCleared => { window.request_redraw(); diff --git a/examples/fruit.rs b/examples/fruit.rs index 790a17c2..b64ae474 100644 --- a/examples/fruit.rs +++ b/examples/fruit.rs @@ -1,5 +1,4 @@ use image::GenericImageView; -use softbuffer::GraphicsContext; use winit::event::{Event, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::WindowBuilder; @@ -38,14 +37,15 @@ fn main() { .unwrap(); } - let mut graphics_context = unsafe { GraphicsContext::new(&window, &window) }.unwrap(); + let context = unsafe { softbuffer::Context::new(&window) }.unwrap(); + let mut surface = unsafe { softbuffer::Surface::new(&context, &window) }.unwrap(); event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; match event { Event::RedrawRequested(window_id) if window_id == window.id() => { - graphics_context.set_buffer(&buffer, fruit.width() as u16, fruit.height() as u16); + surface.set_buffer(&buffer, fruit.width() as u16, fruit.height() as u16); } Event::WindowEvent { event: WindowEvent::CloseRequested, diff --git a/examples/libxcb.rs b/examples/libxcb.rs index 8c2b691e..4498b005 100644 --- a/examples/libxcb.rs +++ b/examples/libxcb.rs @@ -3,7 +3,6 @@ #[cfg(all(feature = "x11", any(target_os = "linux", target_os = "freebsd")))] mod example { use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XcbDisplayHandle, XcbWindowHandle}; - use softbuffer::GraphicsContext; use x11rb::{ connection::Connection, protocol::{ @@ -54,13 +53,12 @@ mod example { // Create a new softbuffer context. // SAFETY: The display and window handles outlive the context. - let mut context = unsafe { - GraphicsContext::from_raw( - RawWindowHandle::Xcb(window_handle), - RawDisplayHandle::Xcb(display_handle), - ) - } - .unwrap(); + let context = + unsafe { softbuffer::Context::from_raw(RawDisplayHandle::Xcb(display_handle)) } + .unwrap(); + let mut surface = + unsafe { softbuffer::Surface::from_raw(&context, RawWindowHandle::Xcb(window_handle)) } + .unwrap(); // Register an atom for closing the window. let wm_protocols_atom = conn @@ -104,7 +102,7 @@ mod example { .collect::>(); // Draw the buffer. - context.set_buffer(&source, width, height); + surface.set_buffer(&source, width, height); } Event::ConfigureNotify(configure_notify) => { width = configure_notify.width; diff --git a/examples/rectangle.rs b/examples/rectangle.rs index ce06b1b0..17b8a201 100644 --- a/examples/rectangle.rs +++ b/examples/rectangle.rs @@ -1,4 +1,3 @@ -use softbuffer::GraphicsContext; use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::WindowBuilder; @@ -41,7 +40,8 @@ fn main() { .unwrap(); } - let mut graphics_context = unsafe { GraphicsContext::new(&window, &window) }.unwrap(); + let context = unsafe { softbuffer::Context::new(&window) }.unwrap(); + let mut surface = unsafe { softbuffer::Surface::new(&context, &window) }.unwrap(); let mut buffer = Vec::new(); let mut flag = false; @@ -66,7 +66,7 @@ fn main() { redraw(&mut buffer, width, height, flag); // Blit the offscreen buffer to the window's client area - graphics_context.set_buffer(&buffer, width as u16, height as u16); + surface.set_buffer(&buffer, width as u16, height as u16); } Event::WindowEvent { diff --git a/examples/winit.rs b/examples/winit.rs index c7822197..3ecb33c8 100644 --- a/examples/winit.rs +++ b/examples/winit.rs @@ -1,4 +1,3 @@ -use softbuffer::GraphicsContext; use winit::event::{Event, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::WindowBuilder; @@ -21,7 +20,8 @@ fn main() { .unwrap(); } - let mut graphics_context = unsafe { GraphicsContext::new(&window, &window) }.unwrap(); + let context = unsafe { softbuffer::Context::new(&window) }.unwrap(); + let mut surface = unsafe { softbuffer::Surface::new(&context, &window) }.unwrap(); event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; @@ -46,7 +46,7 @@ fn main() { }) .collect::>(); - graphics_context.set_buffer(&buffer, width as u16, height as u16); + surface.set_buffer(&buffer, width as u16, height as u16); } Event::WindowEvent { event: WindowEvent::CloseRequested, diff --git a/examples/winit_wrong_sized_buffer.rs b/examples/winit_wrong_sized_buffer.rs index 28f4c521..120e5bc0 100644 --- a/examples/winit_wrong_sized_buffer.rs +++ b/examples/winit_wrong_sized_buffer.rs @@ -1,4 +1,3 @@ -use softbuffer::GraphicsContext; use winit::event::{Event, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::WindowBuilder; @@ -24,7 +23,8 @@ fn main() { .unwrap(); } - let mut graphics_context = unsafe { GraphicsContext::new(&window, &window) }.unwrap(); + let context = unsafe { softbuffer::Context::new(&window) }.unwrap(); + let mut surface = unsafe { softbuffer::Surface::new(&context, &window) }.unwrap(); event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; @@ -45,7 +45,7 @@ fn main() { }) .collect::>(); - graphics_context.set_buffer(&buffer, BUFFER_WIDTH as u16, BUFFER_HEIGHT as u16); + surface.set_buffer(&buffer, BUFFER_WIDTH as u16, BUFFER_HEIGHT as u16); } Event::WindowEvent { event: WindowEvent::CloseRequested, diff --git a/src/error.rs b/src/error.rs index 439d8439..01ea1028 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,14 +5,20 @@ use thiserror::Error; #[derive(Error, Debug)] #[non_exhaustive] pub enum SoftBufferError { + #[error( + "The provided display returned an unsupported platform: {human_readable_display_platform_name}." + )] + UnsupportedDisplayPlatform { + human_readable_display_platform_name: &'static str, + display_handle: RawDisplayHandle, + }, #[error( "The provided window returned an unsupported platform: {human_readable_window_platform_name}, {human_readable_display_platform_name}." )] - UnsupportedPlatform { + UnsupportedWindowPlatform { human_readable_window_platform_name: &'static str, human_readable_display_platform_name: &'static str, window_handle: RawWindowHandle, - display_handle: RawDisplayHandle, }, #[error("The provided window handle is null.")] diff --git a/src/lib.rs b/src/lib.rs index 5e424082..384aee4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,9 @@ mod x11; mod error; +#[cfg(any(wayland_platform, x11_platform))] +use std::sync::Arc; + pub use error::SoftBufferError; use raw_window_handle::{ @@ -29,12 +32,9 @@ use raw_window_handle::{ /// 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 GraphicsContext { +pub struct Context { /// The inner static dispatch object. - /// - /// This is boxed so that `GraphicsContext` is the same size on every platform, which should - /// hopefully prevent surprises. - graphics_context_impl: Box, + context_impl: ContextDispatch, } /// A macro for creating the enum used to statically dispatch to the platform-specific implementation. @@ -42,17 +42,35 @@ macro_rules! make_dispatch { ( $( $(#[$attr:meta])* - $name: ident ($inner_ty: ty), + $name: ident ($context_inner: ty, $surface_inner : ty), )* ) => { - enum Dispatch { + enum ContextDispatch { $( $(#[$attr])* - $name($inner_ty), + $name($context_inner), )* } - impl Dispatch { + impl ContextDispatch { + fn variant_name(&self) -> &'static str { + match self { + $( + $(#[$attr])* + Self::$name(_) => stringify!($name), + )* + } + } + } + + enum SurfaceDispatch { + $( + $(#[$attr])* + $name($surface_inner), + )* + } + + impl SurfaceDispatch { unsafe fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) { match self { $( @@ -67,31 +85,91 @@ macro_rules! make_dispatch { make_dispatch! { #[cfg(x11_platform)] - X11(x11::X11Impl), + X11(Arc, x11::X11Impl), #[cfg(wayland_platform)] - Wayland(wayland::WaylandImpl), + Wayland(std::sync::Arc, wayland::WaylandImpl), #[cfg(target_os = "windows")] - Win32(win32::Win32Impl), + Win32((), win32::Win32Impl), #[cfg(target_os = "macos")] - CG(cg::CGImpl), + CG((), cg::CGImpl), #[cfg(target_arch = "wasm32")] - Web(web::WebImpl), + Web((), web::WebImpl), #[cfg(target_os = "redox")] - Orbital(orbital::OrbitalImpl), + Orbital((), orbital::OrbitalImpl), +} + +impl Context { + /// Creates a new instance of this struct, using the provided display. + /// + /// # Safety + /// + /// - Ensure that the provided object is valid for the lifetime of the Context + pub unsafe fn new(display: &D) -> Result { + unsafe { Self::from_raw(display.raw_display_handle()) } + } + + /// Creates a new instance of this struct, using the provided display handles + /// + /// # Safety + /// + /// - Ensure that the provided handle is valid for the lifetime of the Context + pub unsafe fn from_raw(raw_display_handle: RawDisplayHandle) -> Result { + let imple: ContextDispatch = match raw_display_handle { + #[cfg(x11_platform)] + RawDisplayHandle::Xlib(xlib_handle) => unsafe { + ContextDispatch::X11(Arc::new(x11::X11DisplayImpl::from_xlib(xlib_handle)?)) + }, + #[cfg(x11_platform)] + RawDisplayHandle::Xcb(xcb_handle) => unsafe { + ContextDispatch::X11(Arc::new(x11::X11DisplayImpl::from_xcb(xcb_handle)?)) + }, + #[cfg(wayland_platform)] + RawDisplayHandle::Wayland(wayland_handle) => unsafe { + ContextDispatch::Wayland(Arc::new(wayland::WaylandDisplayImpl::new( + wayland_handle, + )?)) + }, + #[cfg(target_os = "windows")] + RawDisplayHandle::Windows(_) => ContextDispatch::Win32(()), + #[cfg(target_os = "macos")] + RawDisplayHandle::AppKit(_) => ContextDispatch::CG(()), + #[cfg(target_arch = "wasm32")] + RawDisplayHandle::Web(_) => ContextDispatch::Web(()), + #[cfg(target_os = "redox")] + RawDisplayHandle::Orbital(_) => ContextDispatch::Orbital(()), + unimplemented_display_handle => { + return Err(SoftBufferError::UnsupportedDisplayPlatform { + human_readable_display_platform_name: display_handle_type_name( + &unimplemented_display_handle, + ), + display_handle: unimplemented_display_handle, + }) + } + }; + + Ok(Self { + context_impl: imple, + }) + } +} + +pub struct Surface { + /// This is boxed so that `Surface` is the same size on every platform. + surface_impl: Box, } -impl GraphicsContext { +impl Surface { /// Creates a new instance of this struct, using the provided window and display. /// /// # Safety /// /// - Ensure that the provided objects are valid to draw a 2D buffer to, and are valid for the - /// lifetime of the GraphicsContext - pub unsafe fn new( + /// lifetime of the Context + pub unsafe fn new( + context: &Context, window: &W, - display: &D, ) -> Result { - unsafe { Self::from_raw(window.raw_window_handle(), display.raw_display_handle()) } + unsafe { Self::from_raw(context, window.raw_window_handle()) } } /// Creates a new instance of this struct, using the provided raw window and display handles @@ -99,63 +177,61 @@ impl GraphicsContext { /// # Safety /// /// - Ensure that the provided handles are valid to draw a 2D buffer to, and are valid for the - /// lifetime of the GraphicsContext + /// lifetime of the Context pub unsafe fn from_raw( + context: &Context, raw_window_handle: RawWindowHandle, - raw_display_handle: RawDisplayHandle, ) -> Result { - let imple: Dispatch = match (raw_window_handle, raw_display_handle) { + let imple: SurfaceDispatch = match (&context.context_impl, raw_window_handle) { #[cfg(x11_platform)] ( + ContextDispatch::X11(xcb_display_handle), RawWindowHandle::Xlib(xlib_window_handle), - RawDisplayHandle::Xlib(xlib_display_handle), - ) => Dispatch::X11(unsafe { - x11::X11Impl::from_xlib(xlib_window_handle, xlib_display_handle)? + ) => SurfaceDispatch::X11(unsafe { + x11::X11Impl::from_xlib(xlib_window_handle, xcb_display_handle.clone())? }), #[cfg(x11_platform)] - ( - RawWindowHandle::Xcb(xcb_window_handle), - RawDisplayHandle::Xcb(xcb_display_handle), - ) => Dispatch::X11(unsafe { - x11::X11Impl::from_xcb(xcb_window_handle, xcb_display_handle)? - }), + (ContextDispatch::X11(xcb_display_handle), RawWindowHandle::Xcb(xcb_window_handle)) => { + SurfaceDispatch::X11(unsafe { + x11::X11Impl::from_xcb(xcb_window_handle, xcb_display_handle.clone())? + }) + } #[cfg(wayland_platform)] ( + ContextDispatch::Wayland(wayland_display_impl), RawWindowHandle::Wayland(wayland_window_handle), - RawDisplayHandle::Wayland(wayland_display_handle), - ) => Dispatch::Wayland(unsafe { - wayland::WaylandImpl::new(wayland_window_handle, wayland_display_handle)? + ) => SurfaceDispatch::Wayland(unsafe { + wayland::WaylandImpl::new(wayland_window_handle, wayland_display_impl.clone())? }), #[cfg(target_os = "windows")] - (RawWindowHandle::Win32(win32_handle), _) => { - Dispatch::Win32(unsafe { win32::Win32Impl::new(&win32_handle)? }) + (ContextDispatch::Win32(()), RawWindowHandle::Win32(win32_handle)) => { + SurfaceDispatch::Win32(unsafe { win32::Win32Impl::new(&win32_handle)? }) } #[cfg(target_os = "macos")] - (RawWindowHandle::AppKit(appkit_handle), _) => { - Dispatch::CG(unsafe { cg::CGImpl::new(appkit_handle)? }) + (ContextDispatch::CG(()), RawWindowHandle::AppKit(appkit_handle)) => { + SurfaceDispatch::CG(unsafe { cg::CGImpl::new(appkit_handle)? }) } #[cfg(target_arch = "wasm32")] - (RawWindowHandle::Web(web_handle), _) => Dispatch::Web(web::WebImpl::new(web_handle)?), + (ContextDispatch::Web(()), RawWindowHandle::Web(web_handle)) => { + SurfaceDispatch::Web(web::WebImpl::new(web_handle)?) + } #[cfg(target_os = "redox")] - (RawWindowHandle::Orbital(orbital_handle), _) => { - Dispatch::Orbital(orbital::OrbitalImpl::new(orbital_handle)?) + (ContextDispatch::Orbital(()), RawWindowHandle::Orbital(orbital_handle)) => { + SurfaceDispatch::Orbital(orbital::OrbitalImpl::new(orbital_handle)?) } - (unimplemented_window_handle, unimplemented_display_handle) => { - return Err(SoftBufferError::UnsupportedPlatform { + (unsupported_display_impl, unimplemented_window_handle) => { + return Err(SoftBufferError::UnsupportedWindowPlatform { human_readable_window_platform_name: window_handle_type_name( &unimplemented_window_handle, ), - human_readable_display_platform_name: display_handle_type_name( - &unimplemented_display_handle, - ), + human_readable_display_platform_name: unsupported_display_impl.variant_name(), window_handle: unimplemented_window_handle, - display_handle: unimplemented_display_handle, }) } }; Ok(Self { - graphics_context_impl: Box::new(imple), + surface_impl: Box::new(imple), }) } @@ -185,7 +261,7 @@ impl GraphicsContext { /// /// # Platform dependent behavior /// - /// This section of the documentation details how some platforms may behave when [`set_buffer`](GraphicsContext::set_buffer) + /// This section of the documentation details how some platforms may behave when [`set_buffer`](Surface::set_buffer) /// is called. /// /// ## Wayland @@ -203,7 +279,7 @@ impl GraphicsContext { } unsafe { - self.graphics_context_impl.set_buffer(buffer, width, height); + self.surface_impl.set_buffer(buffer, width, height); } } } diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index 356e0e73..4f96c4a0 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -1,5 +1,6 @@ use crate::{error::unwrap, SoftBufferError}; use raw_window_handle::{WaylandDisplayHandle, WaylandWindowHandle}; +use std::sync::{Arc, Mutex}; use wayland_client::{ backend::{Backend, ObjectId}, globals::{registry_queue_init, GlobalListContents}, @@ -12,19 +13,15 @@ use buffer::WaylandBuffer; struct State; -pub struct WaylandImpl { - event_queue: EventQueue, +pub struct WaylandDisplayImpl { + conn: Connection, + event_queue: Mutex>, qh: QueueHandle, - surface: wl_surface::WlSurface, shm: wl_shm::WlShm, - buffers: Option<(WaylandBuffer, WaylandBuffer)>, } -impl WaylandImpl { - pub unsafe fn new( - window_handle: WaylandWindowHandle, - display_handle: WaylandDisplayHandle, - ) -> Result { +impl WaylandDisplayImpl { + pub unsafe fn new(display_handle: WaylandDisplayHandle) -> Result { // SAFETY: Ensured by user let backend = unsafe { Backend::from_foreign_display(display_handle.display as *mut _) }; let conn = Connection::from_backend(backend); @@ -37,6 +34,27 @@ impl WaylandImpl { globals.bind(&qh, 1..=1, ()), "Failed to instantiate Wayland Shm", )?; + Ok(Self { + conn, + event_queue: Mutex::new(event_queue), + qh, + shm, + }) + } +} + +pub struct WaylandImpl { + display: Arc, + surface: wl_surface::WlSurface, + buffers: Option<(WaylandBuffer, WaylandBuffer)>, +} + +impl WaylandImpl { + pub unsafe fn new( + window_handle: WaylandWindowHandle, + display: Arc, + ) -> Result { + // SAFETY: Ensured by user let surface_id = unwrap( unsafe { ObjectId::from_ptr( @@ -47,14 +65,12 @@ impl WaylandImpl { "Failed to create proxy for surface ID.", )?; let surface = unwrap( - wl_surface::WlSurface::from_id(&conn, surface_id), + wl_surface::WlSurface::from_id(&display.conn, surface_id), "Failed to create proxy for surface ID.", )?; Ok(Self { - event_queue, - qh, + display, surface, - shm, buffers: Default::default(), }) } @@ -62,23 +78,31 @@ impl WaylandImpl { fn buffer(&mut self, width: i32, height: i32) -> &WaylandBuffer { self.buffers = Some(if let Some((front, mut back)) = self.buffers.take() { // Swap buffers; block if back buffer not released yet - while !back.released() { - self.event_queue.blocking_dispatch(&mut State).unwrap(); + if !back.released() { + let mut event_queue = self.display.event_queue.lock().unwrap(); + while !back.released() { + event_queue.blocking_dispatch(&mut State).unwrap(); + } } back.resize(width, height); (back, front) } else { // Allocate front and back buffer ( - WaylandBuffer::new(&self.shm, width, height, &self.qh), - WaylandBuffer::new(&self.shm, width, height, &self.qh), + WaylandBuffer::new(&self.display.shm, width, height, &self.display.qh), + WaylandBuffer::new(&self.display.shm, width, height, &self.display.qh), ) }); &self.buffers.as_ref().unwrap().0 } pub(super) unsafe fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) { - let _ = self.event_queue.dispatch_pending(&mut State); + let _ = self + .display + .event_queue + .lock() + .unwrap() + .dispatch_pending(&mut State); let surface = self.surface.clone(); let wayland_buffer = self.buffer(width.into(), height.into()); @@ -101,7 +125,7 @@ impl WaylandImpl { } self.surface.commit(); - let _ = self.event_queue.flush(); + let _ = self.display.event_queue.lock().unwrap().flush(); } } diff --git a/src/x11.rs b/src/x11.rs index f83a32a6..6a9cbaab 100644 --- a/src/x11.rs +++ b/src/x11.rs @@ -9,23 +9,90 @@ use crate::SoftBufferError; use nix::libc::{shmat, shmctl, shmdt, shmget, IPC_PRIVATE, IPC_RMID}; use raw_window_handle::{XcbDisplayHandle, XcbWindowHandle, XlibDisplayHandle, XlibWindowHandle}; use std::ptr::{null_mut, NonNull}; -use std::{fmt, io}; +use std::{fmt, io, sync::Arc}; use x11_dl::xlib::Display; use x11_dl::xlib_xcb::Xlib_xcb; -use x11rb::connection::{Connection, RequestConnection, SequenceNumber}; +use x11rb::connection::{Connection, SequenceNumber}; use x11rb::cookie::Cookie; use x11rb::errors::{ConnectionError, ReplyError, ReplyOrIdError}; use x11rb::protocol::shm::{self, ConnectionExt as _}; use x11rb::protocol::xproto::{self, ConnectionExt as _}; use x11rb::xcb_ffi::XCBConnection; -/// The handle to an X11 drawing context. -pub struct X11Impl { +pub struct X11DisplayImpl { /// The handle to the XCB connection. connection: XCBConnection, + /// SHM extension is available. + is_shm_available: bool, +} + +impl X11DisplayImpl { + pub(crate) unsafe fn from_xlib( + display_handle: XlibDisplayHandle, + ) -> Result { + // TODO: We should cache the shared libraries. + + // Try to open the XlibXCB shared library. + let lib_xcb = Xlib_xcb::open().swbuf_err("Failed to open XlibXCB shared library")?; + + // Validate the display handle to ensure we can use it. + if display_handle.display.is_null() { + return Err(SoftBufferError::IncompleteDisplayHandle); + } + + // Get the underlying XCB connection. + // SAFETY: The user has asserted that the display handle is valid. + let connection = + unsafe { (lib_xcb.XGetXCBConnection)(display_handle.display as *mut Display) }; + + // Construct the equivalent XCB display and window handles. + let mut xcb_display_handle = XcbDisplayHandle::empty(); + xcb_display_handle.connection = connection; + xcb_display_handle.screen = display_handle.screen; + + // SAFETY: If the user passed in valid Xlib handles, then these are valid XCB handles. + unsafe { Self::from_xcb(xcb_display_handle) } + } + + /// Create a new `X11Impl` from a `XcbWindowHandle` and `XcbDisplayHandle`. + /// + /// # Safety + /// + /// The `XcbWindowHandle` and `XcbDisplayHandle` must be valid. + pub(crate) unsafe fn from_xcb( + display_handle: XcbDisplayHandle, + ) -> Result { + // Check that the handle is valid. + if display_handle.connection.is_null() { + return Err(SoftBufferError::IncompleteDisplayHandle); + } + + // Wrap the display handle in an x11rb connection. + // SAFETY: We don't own the connection, so don't drop it. We also assert that the connection is valid. + let connection = { + let result = + unsafe { XCBConnection::from_raw_xcb_connection(display_handle.connection, false) }; + + result.swbuf_err("Failed to wrap XCB connection")? + }; + + let is_shm_available = is_shm_available(&connection); + + Ok(Self { + connection, + is_shm_available, + }) + } +} + +/// The handle to an X11 drawing context. +pub struct X11Impl { + /// X display this window belongs to. + display: Arc, + /// The window to draw to. window: xproto::Window, @@ -66,34 +133,14 @@ impl X11Impl { /// The `XlibWindowHandle` and `XlibDisplayHandle` must be valid. pub unsafe fn from_xlib( window_handle: XlibWindowHandle, - display_handle: XlibDisplayHandle, + display: Arc, ) -> Result { - // TODO: We should cache the shared libraries. - - // Try to open the XlibXCB shared library. - let lib_xcb = Xlib_xcb::open().swbuf_err("Failed to open XlibXCB shared library")?; - - // Validate the display handle to ensure we can use it. - if display_handle.display.is_null() { - return Err(SoftBufferError::IncompleteDisplayHandle); - } - - // Get the underlying XCB connection. - // SAFETY: The user has asserted that the display handle is valid. - let connection = - unsafe { (lib_xcb.XGetXCBConnection)(display_handle.display as *mut Display) }; - - // Construct the equivalent XCB display and window handles. - let mut xcb_display_handle = XcbDisplayHandle::empty(); - xcb_display_handle.connection = connection; - xcb_display_handle.screen = display_handle.screen; - let mut xcb_window_handle = XcbWindowHandle::empty(); xcb_window_handle.window = window_handle.window as _; xcb_window_handle.visual_id = window_handle.visual_id as _; // SAFETY: If the user passed in valid Xlib handles, then these are valid XCB handles. - unsafe { Self::from_xcb(xcb_window_handle, xcb_display_handle) } + unsafe { Self::from_xcb(xcb_window_handle, display) } } /// Create a new `X11Impl` from a `XcbWindowHandle` and `XcbDisplayHandle`. @@ -103,41 +150,28 @@ impl X11Impl { /// The `XcbWindowHandle` and `XcbDisplayHandle` must be valid. pub(crate) unsafe fn from_xcb( window_handle: XcbWindowHandle, - display_handle: XcbDisplayHandle, + display: Arc, ) -> Result { - // Check that the handles are valid. - if display_handle.connection.is_null() { - return Err(SoftBufferError::IncompleteDisplayHandle); - } - + // Check that the handle is valid. if window_handle.window == 0 { return Err(SoftBufferError::IncompleteWindowHandle); } - // Wrap the display handle in an x11rb connection. - // SAFETY: We don't own the connection, so don't drop it. We also assert that the connection is valid. - let connection = { - let result = - unsafe { XCBConnection::from_raw_xcb_connection(display_handle.connection, false) }; - - result.swbuf_err("Failed to wrap XCB connection")? - }; - let window = window_handle.window; - // Run in parallel: start getting the window depth and the SHM extension. - let geometry_token = connection + // Run in parallel: start getting the window depth. + let geometry_token = display + .connection .get_geometry(window) .swbuf_err("Failed to send geometry request")?; - connection - .prefetch_extension_information(shm::X11_EXTENSION_NAME) - .swbuf_err("Failed to send SHM query request")?; // Create a new graphics context to draw to. - let gc = connection + let gc = display + .connection .generate_id() .swbuf_err("Failed to generate GC ID")?; - connection + display + .connection .create_gc( gc, window, @@ -154,7 +188,7 @@ impl X11Impl { // See if SHM is available. let shm_info = { - let present = is_shm_available(&connection); + let present = display.is_shm_available; if present { // SHM is available. @@ -168,7 +202,7 @@ impl X11Impl { }; Ok(Self { - connection, + display, window, gc, depth: geometry_reply.depth, @@ -211,11 +245,11 @@ impl X11Impl { }; // If the X server is still processing the last image, wait for it to finish. - shm_info.finish_wait(&self.connection)?; + shm_info.finish_wait(&self.display.connection)?; // Get the SHM segment to use. let necessary_size = (width as usize) * (height as usize) * 4; - let (segment, segment_id) = shm_info.segment(&self.connection, necessary_size)?; + let (segment, segment_id) = shm_info.segment(&self.display.connection, necessary_size)?; // Copy the buffer into the segment. // SAFETY: The buffer is properly sized and we've ensured that the X server isn't reading from it. @@ -224,7 +258,8 @@ impl X11Impl { } // Put the image into the window. - self.connection + self.display + .connection .shm_put_image( self.window, self.gc, @@ -245,7 +280,7 @@ impl X11Impl { .ignore_error(); // Send a short request to act as a notification for when the X server is done processing the image. - shm_info.begin_wait(&self.connection)?; + shm_info.begin_wait(&self.display.connection)?; Ok(true) } @@ -257,7 +292,8 @@ impl X11Impl { width: u16, height: u16, ) -> Result<(), PushBufferError> { - self.connection + self.display + .connection .put_image( xproto::ImageFormat::Z_PIXMAP, self.window, @@ -426,10 +462,10 @@ impl Drop for X11Impl { // If we used SHM, make sure it's detached from the server. if let Some(mut shm) = self.shm.take() { // If we were in the middle of processing a buffer, wait for it to finish. - shm.finish_wait(&self.connection).ok(); + shm.finish_wait(&self.display.connection).ok(); if let Some((segment, seg_id)) = shm.seg.take() { - if let Ok(token) = self.connection.shm_detach(seg_id) { + if let Ok(token) = self.display.connection.shm_detach(seg_id) { token.ignore_error(); } @@ -439,7 +475,7 @@ impl Drop for X11Impl { } // Close the graphics context that we created. - if let Ok(token) = self.connection.free_gc(self.gc) { + if let Ok(token) = self.display.connection.free_gc(self.gc) { token.ignore_error(); } }