Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to wayland backend, and build on FreeBSD #35

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ exclude = ["examples"]
thiserror = "1.0.30"
raw-window-handle = "0.5.0"

[target.'cfg(target_os = "linux")'.dependencies]
tempfile = "3.3.0"
wayland-client = {version = "0.29", features = ["use_system_lib"], default_features = false}
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
nix = "0.26.1"
wayland-backend = {version = "0.1.0-beta.14", features = ["client_system"]}
wayland-client = {version = "0.30.0-beta.14"}
x11-dl = "2.19.1"

[target.'cfg(target_os = "windows")'.dependencies.winapi]
Expand Down
8 changes: 4 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ extern crate core;
mod win32;
#[cfg(target_os = "macos")]
mod cg;
#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
mod x11;
#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
mod wayland;
#[cfg(target_arch = "wasm32")]
mod web;
Expand Down Expand Up @@ -42,9 +42,9 @@ impl<W: HasRawWindowHandle + HasRawDisplayHandle> GraphicsContext<W> {
let raw_display_handle = window.raw_display_handle();

let imple: Box<dyn GraphicsContextImpl> = match (raw_window_handle, raw_display_handle) {
#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
(RawWindowHandle::Xlib(xlib_window_handle), RawDisplayHandle::Xlib(xlib_display_handle)) => Box::new(x11::X11Impl::new(xlib_window_handle, xlib_display_handle)?),
#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
(RawWindowHandle::Wayland(wayland_window_handle), RawDisplayHandle::Wayland(wayland_display_handle)) => Box::new(wayland::WaylandImpl::new(wayland_window_handle, wayland_display_handle)?),
#[cfg(target_os = "windows")]
(RawWindowHandle::Win32(win32_handle), _) => Box::new(win32::Win32Impl::new(&win32_handle)?),
Expand Down
195 changes: 156 additions & 39 deletions src/wayland.rs
Original file line number Diff line number Diff line change
@@ -1,84 +1,201 @@
use crate::{error::unwrap, GraphicsContextImpl, SoftBufferError};
use nix::sys::memfd::{memfd_create, MemFdCreateFlag};
use raw_window_handle::{HasRawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle};
use tempfile::tempfile;
use wayland_client::{Display, sys::client::wl_display, GlobalManager, protocol::{wl_shm::WlShm, wl_buffer::WlBuffer, wl_surface::WlSurface}, Main, Proxy, EventQueue};
use crate::{GraphicsContextImpl, SoftBufferError, error::unwrap};
use std::{fs::File, os::unix::prelude::{AsRawFd, FileExt}, io::Write};
use std::{
ffi::CStr,
fs::File,
io::Write,
os::unix::prelude::{AsRawFd, FileExt, FromRawFd},
};
use wayland_client::{
backend::{Backend, ObjectId},
globals::{registry_queue_init, GlobalListContents},
protocol::{wl_buffer, wl_registry, wl_shm, wl_shm_pool, wl_surface},
Connection, Dispatch, EventQueue, Proxy, QueueHandle,
};

struct State;

pub struct WaylandImpl {
_event_queue: EventQueue,
surface: WlSurface,
shm: Main<WlShm>,
event_queue: EventQueue<State>,
qh: QueueHandle<State>,
surface: wl_surface::WlSurface,
shm: wl_shm::WlShm,
tempfile: File,
buffer: Option<WaylandBuffer>
buffer: Option<WaylandBuffer>,
}

struct WaylandBuffer{
struct WaylandBuffer {
width: i32,
height: i32,
buffer: Main<WlBuffer>
pool: wl_shm_pool::WlShmPool,
buffer: wl_buffer::WlBuffer,
}

impl WaylandImpl {
impl Drop for WaylandBuffer {
fn drop(&mut self) {
self.buffer.destroy();
self.pool.destroy();
}
}

pub unsafe fn new<W: HasRawWindowHandle>(window_handle: WaylandWindowHandle, display_handle: WaylandDisplayHandle) -> Result<Self, SoftBufferError<W>> {
let display = Display::from_external_display(display_handle.display as *mut wl_display);
let mut event_queue = display.create_event_queue();
let attached_display = (*display).clone().attach(event_queue.token());
let globals = GlobalManager::new(&attached_display);
unwrap(event_queue.sync_roundtrip(&mut (), |_, _, _| unreachable!()), "Failed to make round trip to server")?;
let shm = unwrap(globals.instantiate_exact::<WlShm>(1), "Failed to instantiate Wayland Shm")?;
let tempfile = unwrap(tempfile(), "Failed to create temporary file to store buffer.")?;
let surface = Proxy::from_c_ptr(window_handle.surface as _).into();
Ok(Self{
_event_queue: event_queue,
surface, shm, tempfile,
buffer: None
impl WaylandImpl {
pub unsafe fn new<W: HasRawWindowHandle>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should deny unsafe_op_in_unsafe_fn in the whole crate.

window_handle: WaylandWindowHandle,
display_handle: WaylandDisplayHandle,
) -> Result<Self, SoftBufferError<W>> {
let conn = Connection::from_backend(Backend::from_foreign_display(
display_handle.display as *mut _,
));
let (globals, event_queue) = unwrap(
registry_queue_init(&conn),
"Failed to make round trip to server",
)?;
let qh = event_queue.handle();
let shm: wl_shm::WlShm = unwrap(
globals.bind(&qh, 1..=1, ()),
"Failed to instantiate Wayland Shm",
)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should do a roundtrip after this to ensure it is initialized.

let name = CStr::from_bytes_with_nul_unchecked("softbuffer\0".as_bytes());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't this fail if a file of the same name exists?

let tempfile_fd = unwrap(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have a fallback or else this only works on Freebsd and Linux. See sctk for the fallback.

memfd_create(name, MemFdCreateFlag::MFD_CLOEXEC),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we seal the file if supported by the platform to be nice to compositors?

"Failed to create temporary file to store buffer.",
)?;
let tempfile = File::from_raw_fd(tempfile_fd);
let surface_id = unwrap(
ObjectId::from_ptr(
wl_surface::WlSurface::interface(),
window_handle.surface as _,
),
"Failed to create proxy for surface ID.",
)?;
let surface = unwrap(
wl_surface::WlSurface::from_id(&conn, surface_id),
"Failed to create proxy for surface ID.",
)?;
Ok(Self {
event_queue: event_queue,
qh,
surface,
shm,
tempfile,
buffer: None,
})
}

fn ensure_buffer_size(&mut self, width: i32, height: i32){
if !self.check_buffer_size_equals(width, height){
let pool = self.shm.create_pool(self.tempfile.as_raw_fd(), width*height*4);
let buffer = pool.create_buffer(0, width, height, width*4, wayland_client::protocol::wl_shm::Format::Xrgb8888);
self.buffer = Some(WaylandBuffer{
fn ensure_buffer_size(&mut self, width: i32, height: i32) {
if !self.check_buffer_size_equals(width, height) {
let pool =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will cause an fd leak, we don't destroy the old pool of it did exist.

self.shm
.create_pool(self.tempfile.as_raw_fd(), width * height * 4, &self.qh, ());
let buffer = pool.create_buffer(
0,
width,
height,
buffer
width * 4,
wayland_client::protocol::wl_shm::Format::Xrgb8888,
&self.qh,
(),
);
self.buffer = Some(WaylandBuffer {
width,
height,
pool,
buffer,
});
}
}

fn check_buffer_size_equals(&self, width: i32, height: i32) -> bool{
match &self.buffer{
fn check_buffer_size_equals(&self, width: i32, height: i32) -> bool {
match &self.buffer {
Some(buffer) => buffer.width == width && buffer.height == height,
None => false
None => false,
}
}

}

impl GraphicsContextImpl for WaylandImpl {
unsafe fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) {
self.ensure_buffer_size(width as i32, height as i32);
let wayland_buffer = self.buffer.as_mut().unwrap();
self.tempfile.write_at(std::slice::from_raw_parts(buffer.as_ptr() as *const u8, buffer.len()*4), 0).expect("Failed to write buffer to temporary file.");
self.tempfile.flush().expect("Failed to flush buffer to temporary file.");
self.tempfile.set_len(buffer.len() as u64 * 4)
.expect("Failed to truncate temporary file.");
self.tempfile
.write_at(
std::slice::from_raw_parts(buffer.as_ptr() as *const u8, buffer.len() * 4),
0,
)
.expect("Failed to write buffer to temporary file.");
self.tempfile
.flush()
.expect("Failed to flush buffer to temporary file.");
self.surface.attach(Some(&wayland_buffer.buffer), 0, 0);

// FIXME: Proper damaging mechanism.
//
// In order to propagate changes on compositors which track damage, for now damage the entire surface.
if self.surface.as_ref().version() < 4 {
if self.surface.version() < 4 {
// FIXME: Accommodate scale factor since wl_surface::damage is in terms of surface coordinates while
// wl_surface::damage_buffer is in buffer coordinates.
//
// i32::MAX is a valid damage box (most compositors interpret the damage box as "the entire surface")
self.surface.damage(0, 0, i32::MAX, i32::MAX);
} else {
// Introduced in version 4, it is an error to use this request in version 3 or lower.
self.surface.damage_buffer(0, 0, width as i32, height as i32);
self.surface
.damage_buffer(0, 0, width as i32, height as i32);
}

self.surface.commit();
let _ = self.event_queue.flush();
}
}

impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for State {
fn event(
_: &mut State,
_: &wl_registry::WlRegistry,
_: wl_registry::Event,
_: &GlobalListContents,
_: &Connection,
_: &QueueHandle<State>,
) {
// Ignore globals added after initialization
}
}
}

impl Dispatch<wl_shm::WlShm, ()> for State {
fn event(
_: &mut State,
_: &wl_shm::WlShm,
_: wl_shm::Event,
_: &(),
_: &Connection,
_: &QueueHandle<State>,
) {
}
}

impl Dispatch<wl_shm_pool::WlShmPool, ()> for State {
fn event(
_: &mut State,
_: &wl_shm_pool::WlShmPool,
_: wl_shm_pool::Event,
_: &(),
_: &Connection,
_: &QueueHandle<State>,
) {
}
}

impl Dispatch<wl_buffer::WlBuffer, ()> for State {
fn event(
_: &mut State,
_: &wl_buffer::WlBuffer,
_: wl_buffer::Event,
_: &(),
_: &Connection,
_: &QueueHandle<State>,
) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to handle release in the future, this needs a todo comment.

}
}