-
Notifications
You must be signed in to change notification settings - Fork 51
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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>( | ||
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", | ||
)?; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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>, | ||
) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
} | ||
} |
There was a problem hiding this comment.
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.