1
0
Fork 0
forked from wry/wry

Merge pull request #137 from mahkoh/jorth/explicit-sync

Implement explicit sync
This commit is contained in:
mahkoh 2024-03-28 18:00:46 +01:00 committed by GitHub
commit 9b1a85b27d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
80 changed files with 2923 additions and 592 deletions

View file

@ -42,6 +42,7 @@ The following features have been implemented and should work:
- Selecting the primary device in multi-GPU systems - Selecting the primary device in multi-GPU systems
- An OpenGL backend - An OpenGL backend
- A Vulkan backend - A Vulkan backend
- Explicit sync
### Missing Features ### Missing Features

View file

@ -80,6 +80,34 @@ fn write_egl_procs<W: Write>(f: &mut W) -> anyhow::Result<()> {
&[("target", "GLenum"), ("image", "GLeglImageOES")][..], &[("target", "GLenum"), ("image", "GLeglImageOES")][..],
), ),
("glGetGraphicsResetStatusKHR", "GLenum", &[][..]), ("glGetGraphicsResetStatusKHR", "GLenum", &[][..]),
(
"eglCreateSyncKHR",
"EGLSyncKHR",
&[
("dpy", "EGLDisplay"),
("ty", "EGLenum"),
("attrib_list", "*const EGLint"),
][..],
),
(
"eglDestroySyncKHR",
"EGLBoolean",
&[("dpy", "EGLDisplay"), ("sync", "EGLSyncKHR")][..],
),
(
"eglDupNativeFenceFDANDROID",
"EGLint",
&[("dpy", "EGLDisplay"), ("sync", "EGLSyncKHR")][..],
),
(
"eglWaitSyncKHR",
"EGLint",
&[
("dpy", "EGLDisplay"),
("sync", "EGLSyncKHR"),
("flags", "EGLint"),
][..],
),
]; ];
writeln!(f, "use std::ptr;")?; writeln!(f, "use std::ptr;")?;

View file

@ -813,7 +813,7 @@ struct Extension {
#[derive(Debug)] #[derive(Debug)]
enum NamedType { enum NamedType {
Struct(Rc<Struct>), Struct(Rc<Struct>),
Bitmask(Rc<Bitmask>), Bitmask(#[allow(dead_code)] Rc<Bitmask>),
Enum(Rc<Enum>), Enum(Rc<Enum>),
} }

View file

@ -810,6 +810,10 @@ impl Client {
self.send(&ClientMessage::SetIdle { timeout }) self.send(&ClientMessage::SetIdle { timeout })
} }
pub fn set_explicit_sync_enabled(&self, enabled: bool) {
self.send(&ClientMessage::SetExplicitSyncEnabled { enabled })
}
pub fn set_seat(&self, device: InputDevice, seat: Seat) { pub fn set_seat(&self, device: InputDevice, seat: Seat) {
self.send(&ClientMessage::SetSeat { device, seat }) self.send(&ClientMessage::SetSeat { device, seat })
} }

View file

@ -428,6 +428,9 @@ pub enum ClientMessage<'a> {
workspace: WorkspaceSource, workspace: WorkspaceSource,
connector: Connector, connector: Connector,
}, },
SetExplicitSyncEnabled {
enabled: bool,
},
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]

View file

@ -222,3 +222,12 @@ pub fn workspaces() -> Vec<Workspace> {
pub fn set_idle(timeout: Option<Duration>) { pub fn set_idle(timeout: Option<Duration>) {
get!().set_idle(timeout.unwrap_or_default()) get!().set_idle(timeout.unwrap_or_default())
} }
/// Enables or disables explicit sync.
///
/// Calling this after the compositor has started has no effect.
///
/// The default is `true`.
pub fn set_explicit_sync_enabled(enabled: bool) {
get!().set_explicit_sync_enabled(enabled);
}

View file

@ -3,7 +3,7 @@ use {
async_engine::SpawnedFuture, async_engine::SpawnedFuture,
drm_feedback::DrmFeedback, drm_feedback::DrmFeedback,
fixed::Fixed, fixed::Fixed,
gfx_api::GfxFramebuffer, gfx_api::{GfxFramebuffer, SyncFile},
ifs::wl_seat::wl_pointer::{CONTINUOUS, FINGER, HORIZONTAL_SCROLL, VERTICAL_SCROLL, WHEEL}, ifs::wl_seat::wl_pointer::{CONTINUOUS, FINGER, HORIZONTAL_SCROLL, VERTICAL_SCROLL, WHEEL},
libinput::consts::DeviceCapability, libinput::consts::DeviceCapability,
video::drm::{ConnectorType, DrmError, DrmVersion}, video::drm::{ConnectorType, DrmError, DrmVersion},
@ -106,6 +106,7 @@ pub trait HardwareCursor: Debug {
fn get_buffer(&self) -> Rc<dyn GfxFramebuffer>; fn get_buffer(&self) -> Rc<dyn GfxFramebuffer>;
fn set_position(&self, x: i32, y: i32); fn set_position(&self, x: i32, y: i32);
fn swap_buffer(&self); fn swap_buffer(&self);
fn set_sync_file(&self, sync_file: Option<SyncFile>);
fn commit(&self); fn commit(&self);
fn size(&self) -> (i32, i32); fn size(&self) -> (i32, i32);
} }

View file

@ -114,6 +114,14 @@ pub enum MetalError {
MissingDevModifier(&'static str), MissingDevModifier(&'static str),
#[error("Device GFX API cannot read any buffers writable by the render GFX API (format {0})")] #[error("Device GFX API cannot read any buffers writable by the render GFX API (format {0})")]
MissingRenderModifier(&'static str), MissingRenderModifier(&'static str),
#[error("Could not render the frame")]
RenderFrame(#[source] GfxError),
#[error("Could not copy frame to output device")]
CopyToOutput(#[source] GfxError),
#[error("Could not perform atomic commit")]
Commit(#[source] DrmError),
#[error("Could not clear framebuffer")]
Clear(#[source] GfxError),
} }
pub struct MetalBackend { pub struct MetalBackend {

View file

@ -9,7 +9,10 @@ use {
drm_feedback::DrmFeedback, drm_feedback::DrmFeedback,
edid::Descriptor, edid::Descriptor,
format::{Format, ARGB8888, XRGB8888}, format::{Format, ARGB8888, XRGB8888},
gfx_api::{GfxApiOpt, GfxContext, GfxFramebuffer, GfxRenderPass, GfxTexture}, gfx_api::{
AcquireSync, BufferResv, GfxApiOpt, GfxContext, GfxFramebuffer, GfxRenderPass,
GfxTexture, ReleaseSync, SyncFile,
},
ifs::wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC}, ifs::wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC},
renderer::RenderResult, renderer::RenderResult,
state::State, state::State,
@ -227,6 +230,7 @@ pub struct MetalConnector {
pub cursor_buffers: CloneCell<Option<Rc<[RenderBuffer; 3]>>>, pub cursor_buffers: CloneCell<Option<Rc<[RenderBuffer; 3]>>>,
pub cursor_front_buffer: NumCell<usize>, pub cursor_front_buffer: NumCell<usize>,
pub cursor_swap_buffer: Cell<bool>, pub cursor_swap_buffer: Cell<bool>,
pub cursor_sync_file: CloneCell<Option<SyncFile>>,
pub drm_feedback: CloneCell<Option<Rc<DrmFeedback>>>, pub drm_feedback: CloneCell<Option<Rc<DrmFeedback>>>,
pub scanout_buffers: RefCell<AHashMap<DmaBufId, DirectScanoutCache>>, pub scanout_buffers: RefCell<AHashMap<DmaBufId, DirectScanoutCache>>,
@ -244,6 +248,7 @@ pub struct MetalHardwareCursor {
pub cursor_x_pending: Cell<i32>, pub cursor_x_pending: Cell<i32>,
pub cursor_y_pending: Cell<i32>, pub cursor_y_pending: Cell<i32>,
pub cursor_buffers: Rc<[RenderBuffer; 3]>, pub cursor_buffers: Rc<[RenderBuffer; 3]>,
pub sync_file: CloneCell<Option<SyncFile>>,
pub have_changes: Cell<bool>, pub have_changes: Cell<bool>,
} }
@ -270,6 +275,11 @@ impl HardwareCursor for MetalHardwareCursor {
self.have_changes.set(true); self.have_changes.set(true);
} }
fn set_sync_file(&self, sync_file: Option<SyncFile>) {
self.sync_file.set(sync_file);
self.have_changes.set(true);
}
fn commit(&self) { fn commit(&self) {
if self.generation != self.connector.cursor_generation.get() { if self.generation != self.connector.cursor_generation.get() {
return; return;
@ -285,6 +295,7 @@ impl HardwareCursor for MetalHardwareCursor {
if self.cursor_swap_buffer.take() { if self.cursor_swap_buffer.take() {
self.connector.cursor_swap_buffer.set(true); self.connector.cursor_swap_buffer.set(true);
} }
self.connector.cursor_sync_file.set(self.sync_file.take());
self.connector.cursor_changed.set(true); self.connector.cursor_changed.set(true);
if self.connector.can_present.get() { if self.connector.can_present.get() {
self.connector.schedule_present(); self.connector.schedule_present();
@ -350,9 +361,10 @@ pub struct DirectScanoutCache {
#[derive(Debug)] #[derive(Debug)]
pub struct DirectScanoutData { pub struct DirectScanoutData {
tex: Rc<dyn GfxTexture>, tex: Rc<dyn GfxTexture>,
acquire_sync: AcquireSync,
_resv: Option<Rc<dyn BufferResv>>,
fb: Rc<DrmFramebuffer>, fb: Rc<DrmFramebuffer>,
dma_buf_id: DmaBufId, dma_buf_id: DmaBufId,
acquired: Cell<bool>,
position: DirectScanoutPosition, position: DirectScanoutPosition,
} }
@ -366,25 +378,20 @@ pub struct DirectScanoutPosition {
pub crtc_height: i32, pub crtc_height: i32,
} }
impl Drop for DirectScanoutData {
fn drop(&mut self) {
if self.acquired.replace(false) {
self.tex.reservations().release();
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct PresentFb { pub struct PresentFb {
fb: Rc<DrmFramebuffer>, fb: Rc<DrmFramebuffer>,
direct_scanout_data: Option<DirectScanoutData>, direct_scanout_data: Option<DirectScanoutData>,
sync_file: Option<SyncFile>,
} }
impl MetalConnector { impl MetalConnector {
async fn present_loop(self: Rc<Self>) { async fn present_loop(self: Rc<Self>) {
loop { loop {
self.present_trigger.triggered().await; self.present_trigger.triggered().await;
let _ = self.present(true); if let Err(e) = self.present(true) {
log::error!("Could not present: {}", ErrorFmt(e));
}
} }
} }
@ -402,6 +409,7 @@ impl MetalConnector {
cursor_x_pending: Cell::new(self.cursor_x.get()), cursor_x_pending: Cell::new(self.cursor_x.get()),
cursor_y_pending: Cell::new(self.cursor_y.get()), cursor_y_pending: Cell::new(self.cursor_y.get()),
cursor_buffers: cp.clone(), cursor_buffers: cp.clone(),
sync_file: Default::default(),
have_changes: Cell::new(false), have_changes: Cell::new(false),
}) as _), }) as _),
_ => None, _ => None,
@ -480,6 +488,10 @@ impl MetalConnector {
} }
ct ct
}; };
if let AcquireSync::None = ct.acquire_sync {
// Cannot perform scanout without sync.
return None;
}
if ct.source.buffer_transform != ct.target.output_transform { if ct.source.buffer_transform != ct.target.output_transform {
// Rotations and mirroring are not supported. // Rotations and mirroring are not supported.
return None; return None;
@ -532,9 +544,10 @@ impl MetalConnector {
if let Some(buffer) = cache.get(&dmabuf.id) { if let Some(buffer) = cache.get(&dmabuf.id) {
return buffer.fb.as_ref().map(|fb| DirectScanoutData { return buffer.fb.as_ref().map(|fb| DirectScanoutData {
tex: buffer.tex.upgrade().unwrap(), tex: buffer.tex.upgrade().unwrap(),
acquire_sync: ct.acquire_sync.clone(),
_resv: ct.buffer_resv.clone(),
fb: fb.clone(), fb: fb.clone(),
dma_buf_id: dmabuf.id, dma_buf_id: dmabuf.id,
acquired: Default::default(),
position, position,
}); });
} }
@ -556,9 +569,10 @@ impl MetalConnector {
let data = match self.dev.master.add_fb(dmabuf, Some(format.format)) { let data = match self.dev.master.add_fb(dmabuf, Some(format.format)) {
Ok(fb) => Some(DirectScanoutData { Ok(fb) => Some(DirectScanoutData {
tex: ct.tex.clone(), tex: ct.tex.clone(),
acquire_sync: ct.acquire_sync.clone(),
_resv: ct.buffer_resv.clone(),
fb: Rc::new(fb), fb: Rc::new(fb),
dma_buf_id: dmabuf.id, dma_buf_id: dmabuf.id,
acquired: Default::default(),
position, position,
}), }),
Err(e) => { Err(e) => {
@ -593,7 +607,7 @@ impl MetalConnector {
plane: &Rc<MetalPlane>, plane: &Rc<MetalPlane>,
output: &OutputNode, output: &OutputNode,
try_direct_scanout: bool, try_direct_scanout: bool,
) -> PresentFb { ) -> Result<PresentFb, MetalError> {
self.trim_scanout_cache(); self.trim_scanout_cache();
let buffer_fb = buffer.render_fb(); let buffer_fb = buffer.render_fb();
let render_hw_cursor = !self.cursor_enabled.get(); let render_hw_cursor = !self.cursor_enabled.get();
@ -636,25 +650,36 @@ impl MetalConnector {
}; };
log::debug!("{} direct scanout on {}", change, self.kernel_id()); log::debug!("{} direct scanout on {}", change, self.kernel_id());
} }
let fb = match &direct_scanout_data { let sync_file;
let fb;
match &direct_scanout_data {
None => { None => {
let sf = buffer_fb
.perform_render_pass(pass)
.map_err(MetalError::RenderFrame)?;
sync_file = buffer.copy_to_dev(sf)?;
self.next_buffer.fetch_add(1); self.next_buffer.fetch_add(1);
buffer_fb.perform_render_pass(pass);
if let Some(tex) = &buffer.dev_tex {
buffer.dev_fb.copy_texture(tex, 0, 0);
}
output.perform_screencopies(&buffer.render_tex, !render_hw_cursor, 0, 0, None); output.perform_screencopies(&buffer.render_tex, !render_hw_cursor, 0, 0, None);
buffer.drm.clone() fb = buffer.drm.clone();
}
Some(dsd) => {
sync_file = match &dsd.acquire_sync {
AcquireSync::None => None,
AcquireSync::Implicit => None,
AcquireSync::SyncFile { sync_file } => Some(sync_file.clone()),
AcquireSync::Unnecessary => None,
};
fb = dsd.fb.clone();
} }
Some(dsd) => dsd.fb.clone(),
}; };
PresentFb { Ok(PresentFb {
fb, fb,
direct_scanout_data, direct_scanout_data,
} sync_file,
})
} }
pub fn present(&self, try_direct_scanout: bool) -> Result<(), ()> { pub fn present(&self, try_direct_scanout: bool) -> Result<(), MetalError> {
let crtc = match self.crtc.get() { let crtc = match self.crtc.get() {
Some(crtc) => crtc, Some(crtc) => crtc,
_ => return Ok(()), _ => return Ok(()),
@ -684,7 +709,7 @@ impl MetalConnector {
let buffer = &buffers[self.next_buffer.get() % buffers.len()]; let buffer = &buffers[self.next_buffer.get() % buffers.len()];
let mut rr = self.render_result.borrow_mut(); let mut rr = self.render_result.borrow_mut();
let fb = let fb =
self.prepare_present_fb(&mut rr, buffer, &plane, &node, try_direct_scanout); self.prepare_present_fb(&mut rr, buffer, &plane, &node, try_direct_scanout)?;
rr.dispatch_frame_requests(); rr.dispatch_frame_requests();
let (crtc_x, crtc_y, crtc_w, crtc_h, src_width, src_height) = let (crtc_x, crtc_y, crtc_w, crtc_h, src_width, src_height) =
match &fb.direct_scanout_data { match &fb.direct_scanout_data {
@ -705,6 +730,7 @@ impl MetalConnector {
) )
} }
}; };
let in_fence = fb.sync_file.as_ref().map(|s| s.raw()).unwrap_or(-1);
changes.change_object(plane.id, |c| { changes.change_object(plane.id, |c| {
c.change(plane.fb_id, fb.fb.id().0 as _); c.change(plane.fb_id, fb.fb.id().0 as _);
c.change(plane.src_w.id, (src_width as u64) << 16); c.change(plane.src_w.id, (src_width as u64) << 16);
@ -713,24 +739,28 @@ impl MetalConnector {
c.change(plane.crtc_y.id, crtc_y as u64); c.change(plane.crtc_y.id, crtc_y as u64);
c.change(plane.crtc_w.id, crtc_w as u64); c.change(plane.crtc_w.id, crtc_w as u64);
c.change(plane.crtc_h.id, crtc_h as u64); c.change(plane.crtc_h.id, crtc_h as u64);
c.change(plane.in_fence_fd, in_fence as u64);
}); });
new_fb = Some(fb); new_fb = Some(fb);
} }
} }
let mut cursor_swap_buffer = false;
let mut cursor_sync_file = None;
if self.cursor_changed.get() && cursor.is_some() { if self.cursor_changed.get() && cursor.is_some() {
let plane = cursor.unwrap(); let plane = cursor.unwrap();
if self.cursor_enabled.get() { if self.cursor_enabled.get() {
let swap_buffer = self.cursor_swap_buffer.take(); cursor_swap_buffer = self.cursor_swap_buffer.get();
if swap_buffer { let mut front_buffer = self.cursor_front_buffer.get();
self.cursor_front_buffer.fetch_add(1); if cursor_swap_buffer {
front_buffer = front_buffer.wrapping_add(1);
cursor_sync_file = self.cursor_sync_file.get();
} }
let buffers = self.cursor_buffers.get().unwrap(); let buffers = self.cursor_buffers.get().unwrap();
let buffer = &buffers[self.cursor_front_buffer.get() % buffers.len()]; let buffer = &buffers[front_buffer % buffers.len()];
if swap_buffer { if cursor_swap_buffer {
if let Some(tex) = &buffer.dev_tex { cursor_sync_file = buffer.copy_to_dev(cursor_sync_file)?;
buffer.dev_fb.copy_texture(tex, 0, 0);
}
} }
let in_fence = cursor_sync_file.as_ref().map(|s| s.raw()).unwrap_or(-1);
let (width, height) = buffer.dev_fb.physical_size(); let (width, height) = buffer.dev_fb.physical_size();
changes.change_object(plane.id, |c| { changes.change_object(plane.id, |c| {
c.change(plane.fb_id, buffer.drm.id().0 as _); c.change(plane.fb_id, buffer.drm.id().0 as _);
@ -743,6 +773,7 @@ impl MetalConnector {
c.change(plane.src_y.id, 0); c.change(plane.src_y.id, 0);
c.change(plane.src_w.id, (width as u64) << 16); c.change(plane.src_w.id, (width as u64) << 16);
c.change(plane.src_h.id, (height as u64) << 16); c.change(plane.src_h.id, (height as u64) << 16);
c.change(plane.in_fence_fd, in_fence as u64);
}); });
} else { } else {
changes.change_object(plane.id, |c| { changes.change_object(plane.id, |c| {
@ -752,40 +783,37 @@ impl MetalConnector {
} }
} }
if let Err(e) = changes.commit(DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT, 0) { if let Err(e) = changes.commit(DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT, 0) {
match e { if let DrmError::Atomic(OsError(c::EACCES)) = e {
DrmError::Atomic(OsError(c::EACCES)) => { log::debug!("Could not perform atomic commit, likely because we're no longer the DRM master");
log::debug!("Could not perform atomic commit, likely because we're no longer the DRM master"); return Ok(());
} }
_ => 'handle_failure: { if let Some(fb) = &new_fb {
if let Some(fb) = &new_fb { if let Some(dsd) = &fb.direct_scanout_data {
if let Some(dsd) = &fb.direct_scanout_data { if self.present(false).is_ok() {
if self.present(false).is_ok() { let mut cache = self.scanout_buffers.borrow_mut();
let mut cache = self.scanout_buffers.borrow_mut(); if let Some(buffer) = cache.remove(&dsd.dma_buf_id) {
if let Some(buffer) = cache.remove(&dsd.dma_buf_id) { cache.insert(
cache.insert( dsd.dma_buf_id,
dsd.dma_buf_id, DirectScanoutCache {
DirectScanoutCache { tex: buffer.tex,
tex: buffer.tex, fb: None,
fb: None, },
}, );
);
}
break 'handle_failure;
}
} }
return Ok(());
} }
log::error!("Could not set plane framebuffer: {}", ErrorFmt(e));
} }
} }
Err(()) Err(MetalError::Commit(e))
} else { } else {
if let Some(fb) = new_fb { if let Some(fb) = new_fb {
if let Some(dsd) = &fb.direct_scanout_data {
dsd.tex.reservations().acquire();
dsd.acquired.set(true);
}
self.next_framebuffer.set(Some(fb)); self.next_framebuffer.set(Some(fb));
} }
if cursor_swap_buffer {
self.cursor_swap_buffer.set(false);
self.cursor_front_buffer.fetch_add(1);
self.cursor_sync_file.take();
}
self.can_present.set(false); self.can_present.set(false);
self.has_damage.set(false); self.has_damage.set(false);
self.cursor_changed.set(false); self.cursor_changed.set(false);
@ -1036,6 +1064,7 @@ fn create_connector(
cursor_changed: Cell::new(false), cursor_changed: Cell::new(false),
cursor_front_buffer: Default::default(), cursor_front_buffer: Default::default(),
cursor_swap_buffer: Cell::new(false), cursor_swap_buffer: Cell::new(false),
cursor_sync_file: Default::default(),
drm_feedback: Default::default(), drm_feedback: Default::default(),
scanout_buffers: Default::default(), scanout_buffers: Default::default(),
active_framebuffer: Default::default(), active_framebuffer: Default::default(),
@ -2166,7 +2195,7 @@ impl MetalBackend {
Ok(fb) => fb, Ok(fb) => fb,
Err(e) => return Err(MetalError::ImportFb(e)), Err(e) => return Err(MetalError::ImportFb(e)),
}; };
dev_fb.clear(); dev_fb.clear().map_err(MetalError::Clear)?;
let (dev_tex, render_tex, render_fb) = if dev.id == render_ctx.dev_id { let (dev_tex, render_tex, render_fb) = if dev.id == render_ctx.dev_id {
let render_tex = match dev_img.to_texture() { let render_tex = match dev_img.to_texture() {
Ok(fb) => fb, Ok(fb) => fb,
@ -2215,7 +2244,7 @@ impl MetalBackend {
Ok(fb) => fb, Ok(fb) => fb,
Err(e) => return Err(MetalError::ImportFb(e)), Err(e) => return Err(MetalError::ImportFb(e)),
}; };
render_fb.clear(); render_fb.clear().map_err(MetalError::Clear)?;
let render_tex = match render_img.to_texture() { let render_tex = match render_img.to_texture() {
Ok(fb) => fb, Ok(fb) => fb,
Err(e) => return Err(MetalError::ImportTexture(e)), Err(e) => return Err(MetalError::ImportTexture(e)),
@ -2435,6 +2464,16 @@ impl RenderBuffer {
.clone() .clone()
.unwrap_or_else(|| self.dev_fb.clone()) .unwrap_or_else(|| self.dev_fb.clone())
} }
fn copy_to_dev(&self, sync_file: Option<SyncFile>) -> Result<Option<SyncFile>, MetalError> {
let Some(tex) = &self.dev_tex else {
return Ok(sync_file);
};
let acquire_point = AcquireSync::from_sync_file(sync_file);
self.dev_fb
.copy_texture(tex, acquire_point, ReleaseSync::Implicit, 0, 0)
.map_err(MetalError::CopyToOutput)
}
} }
fn modes_equal(a: &DrmModeInfo, b: &DrmModeInfo) -> bool { fn modes_equal(a: &DrmModeInfo, b: &DrmModeInfo) -> bool {

View file

@ -738,13 +738,17 @@ impl XBackend {
image.last_serial.set(serial); image.last_serial.set(serial);
if let Some(node) = self.state.root.outputs.get(&output.id) { if let Some(node) = self.state.root.outputs.get(&output.id) {
self.state.present_output( let res = self.state.present_output(
&node, &node,
&image.fb.get(), &image.fb.get(),
&image.tex.get(), &image.tex.get(),
&mut self.render_result.borrow_mut(), &mut self.render_result.borrow_mut(),
true, true,
); );
if let Err(e) = res {
log::error!("Could not render screen: {}", ErrorFmt(e));
return;
}
} }
let pp = PresentPixmap { let pp = PresentPixmap {

View file

@ -2,7 +2,11 @@ use {
crate::{ crate::{
async_engine::SpawnedFuture, async_engine::SpawnedFuture,
client::{error::LookupError, objects::Objects}, client::{error::LookupError, objects::Objects},
ifs::{wl_display::WlDisplay, wl_registry::WlRegistry, wl_surface::WlSurface}, ifs::{
wl_display::WlDisplay,
wl_registry::WlRegistry,
wl_surface::{commit_timeline::CommitTimelines, WlSurface},
},
leaks::Tracker, leaks::Tracker,
object::{Interface, Object, ObjectId, WL_DISPLAY_ID}, object::{Interface, Object, ObjectId, WL_DISPLAY_ID},
state::State, state::State,
@ -149,6 +153,7 @@ impl Clients {
last_xwayland_serial: Cell::new(0), last_xwayland_serial: Cell::new(0),
surfaces_by_xwayland_serial: Default::default(), surfaces_by_xwayland_serial: Default::default(),
activation_tokens: Default::default(), activation_tokens: Default::default(),
commit_timelines: Rc::new(CommitTimelines::new(&global.wait_for_sync_obj)),
}); });
track!(data, data); track!(data, data);
let display = Rc::new(WlDisplay::new(&data)); let display = Rc::new(WlDisplay::new(&data));
@ -220,6 +225,7 @@ impl Drop for ClientHolder {
self.data.shutdown.clear(); self.data.shutdown.clear();
self.data.surfaces_by_xwayland_serial.clear(); self.data.surfaces_by_xwayland_serial.clear();
self.data.remove_activation_tokens(); self.data.remove_activation_tokens();
self.data.commit_timelines.clear();
} }
} }
@ -260,6 +266,7 @@ pub struct Client {
pub last_xwayland_serial: Cell<u64>, pub last_xwayland_serial: Cell<u64>,
pub surfaces_by_xwayland_serial: CopyHashMap<u64, Rc<WlSurface>>, pub surfaces_by_xwayland_serial: CopyHashMap<u64, Rc<WlSurface>>,
pub activation_tokens: RefCell<VecDeque<ActivationToken>>, pub activation_tokens: RefCell<VecDeque<ActivationToken>>,
pub commit_timelines: Rc<CommitTimelines>,
} }
pub const NUM_CACHED_SERIAL_RANGES: usize = 64; pub const NUM_CACHED_SERIAL_RANGES: usize = 64;

View file

@ -19,6 +19,7 @@ use {
xdg_surface::{xdg_toplevel::XdgToplevel, XdgSurface}, xdg_surface::{xdg_toplevel::XdgToplevel, XdgSurface},
WlSurface, WlSurface,
}, },
wp_linux_drm_syncobj_timeline_v1::WpLinuxDrmSyncobjTimelineV1,
xdg_positioner::XdgPositioner, xdg_positioner::XdgPositioner,
xdg_wm_base::XdgWmBase, xdg_wm_base::XdgWmBase,
}, },
@ -29,8 +30,9 @@ use {
}, },
wire::{ wire::{
JayOutputId, JayScreencastId, JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId, JayOutputId, JayScreencastId, JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId,
WlPointerId, WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId, XdgPositionerId, WlPointerId, WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId,
XdgSurfaceId, XdgToplevelId, XdgWmBaseId, ZwpPrimarySelectionSourceV1Id, WpLinuxDrmSyncobjTimelineV1Id, XdgPositionerId, XdgSurfaceId, XdgToplevelId,
XdgWmBaseId, ZwpPrimarySelectionSourceV1Id,
}, },
}, },
std::{cell::RefCell, mem, rc::Rc}, std::{cell::RefCell, mem, rc::Rc},
@ -56,6 +58,7 @@ pub struct Objects {
pub xdg_wm_bases: CopyHashMap<XdgWmBaseId, Rc<XdgWmBase>>, pub xdg_wm_bases: CopyHashMap<XdgWmBaseId, Rc<XdgWmBase>>,
pub seats: CopyHashMap<WlSeatId, Rc<WlSeat>>, pub seats: CopyHashMap<WlSeatId, Rc<WlSeat>>,
pub screencasts: CopyHashMap<JayScreencastId, Rc<JayScreencast>>, pub screencasts: CopyHashMap<JayScreencastId, Rc<JayScreencast>>,
pub timelines: CopyHashMap<WpLinuxDrmSyncobjTimelineV1Id, Rc<WpLinuxDrmSyncobjTimelineV1>>,
ids: RefCell<Vec<usize>>, ids: RefCell<Vec<usize>>,
} }
@ -83,6 +86,7 @@ impl Objects {
xdg_wm_bases: Default::default(), xdg_wm_bases: Default::default(),
seats: Default::default(), seats: Default::default(),
screencasts: Default::default(), screencasts: Default::default(),
timelines: Default::default(),
ids: RefCell::new(vec![]), ids: RefCell::new(vec![]),
} }
} }

View file

@ -38,6 +38,7 @@ use {
oserror::OsError, queue::AsyncQueue, refcounted::RefCounted, run_toplevel::RunToplevel, oserror::OsError, queue::AsyncQueue, refcounted::RefCounted, run_toplevel::RunToplevel,
tri::Try, tri::Try,
}, },
video::drm::wait_for_sync_obj::WaitForSyncObj,
wheel::{Wheel, WheelError}, wheel::{Wheel, WheelError},
xkbcommon::XkbContext, xkbcommon::XkbContext,
}, },
@ -221,6 +222,9 @@ fn start_compositor2(
double_click_interval_usec: Cell::new(400 * 1000), double_click_interval_usec: Cell::new(400 * 1000),
double_click_distance: Cell::new(5), double_click_distance: Cell::new(5),
create_default_seat: Cell::new(true), create_default_seat: Cell::new(true),
subsurface_ids: Default::default(),
wait_for_sync_obj: Rc::new(WaitForSyncObj::new(&ring, &engine)),
explicit_sync_enabled: Cell::new(true),
}); });
state.tracker.register(ClientId::from_raw(0)); state.tracker.register(ClientId::from_raw(0));
create_dummy_output(&state); create_dummy_output(&state);

View file

@ -800,6 +800,10 @@ impl ConfigProxyHandler {
self.state.idle.set_timeout(timeout); self.state.idle.set_timeout(timeout);
} }
fn handle_set_explicit_sync_enabled(&self, enabled: bool) {
self.state.explicit_sync_enabled.set(enabled);
}
fn handle_connector_connected(&self, connector: Connector) -> Result<(), CphError> { fn handle_connector_connected(&self, connector: Connector) -> Result<(), CphError> {
let connector = self.get_connector(connector)?; let connector = self.get_connector(connector)?;
self.respond(Response::ConnectorConnected { self.respond(Response::ConnectorConnected {
@ -1725,6 +1729,9 @@ impl ConfigProxyHandler {
} => self } => self
.handle_move_to_output(workspace, connector) .handle_move_to_output(workspace, connector)
.wrn("move_to_output")?, .wrn("move_to_output")?,
ClientMessage::SetExplicitSyncEnabled { enabled } => {
self.handle_set_explicit_sync_enabled(enabled)
}
} }
Ok(()) Ok(())
} }

View file

@ -2,7 +2,7 @@ use {
crate::{ crate::{
fixed::Fixed, fixed::Fixed,
format::ARGB8888, format::ARGB8888,
gfx_api::{GfxContext, GfxError, GfxTexture}, gfx_api::{AcquireSync, GfxContext, GfxError, GfxTexture, ReleaseSync},
rect::Rect, rect::Rect,
renderer::Renderer, renderer::Renderer,
scale::Scale, scale::Scale,
@ -378,6 +378,9 @@ fn render_img(image: &InstantiatedCursorImage, renderer: &mut Renderer, x: Fixed
None, None,
scale, scale,
None, None,
None,
AcquireSync::None,
ReleaseSync::None,
); );
} }
} }
@ -389,9 +392,18 @@ impl Cursor for StaticCursor {
fn render_hardware_cursor(&self, renderer: &mut Renderer) { fn render_hardware_cursor(&self, renderer: &mut Renderer) {
if let Some(img) = self.image.scales.get(&renderer.scale()) { if let Some(img) = self.image.scales.get(&renderer.scale()) {
renderer renderer.base.render_texture(
.base &img.tex,
.render_texture(&img.tex, 0, 0, None, None, renderer.scale(), None); 0,
0,
None,
None,
renderer.scale(),
None,
None,
AcquireSync::None,
ReleaseSync::None,
);
} }
} }
@ -419,9 +431,18 @@ impl Cursor for AnimatedCursor {
fn render_hardware_cursor(&self, renderer: &mut Renderer) { fn render_hardware_cursor(&self, renderer: &mut Renderer) {
let img = &self.images[self.idx.get()]; let img = &self.images[self.idx.get()];
if let Some(img) = img.scales.get(&renderer.scale()) { if let Some(img) = img.scales.get(&renderer.scale()) {
renderer renderer.base.render_texture(
.base &img.tex,
.render_texture(&img.tex, 0, 0, None, None, renderer.scale(), None); 0,
0,
None,
None,
renderer.scale(),
None,
None,
AcquireSync::None,
ReleaseSync::None,
);
} }
} }

View file

@ -9,8 +9,8 @@ use {
state::State, state::State,
theme::Color, theme::Color,
tree::{Node, OutputNode}, tree::{Node, OutputNode},
utils::{numcell::NumCell, transform_ext::TransformExt}, utils::{clonecell::UnsafeCellCloneSafe, transform_ext::TransformExt},
video::{dmabuf::DmaBuf, gbm::GbmDevice, Modifier}, video::{dmabuf::DmaBuf, drm::sync_obj::SyncObjCtx, gbm::GbmDevice, Modifier},
}, },
ahash::AHashMap, ahash::AHashMap,
indexmap::IndexSet, indexmap::IndexSet,
@ -21,9 +21,12 @@ use {
error::Error, error::Error,
ffi::CString, ffi::CString,
fmt::{Debug, Formatter}, fmt::{Debug, Formatter},
ops::Deref,
rc::Rc, rc::Rc,
sync::atomic::{AtomicU64, Ordering::Relaxed},
}, },
thiserror::Error, thiserror::Error,
uapi::OwnedFd,
}; };
pub enum GfxApiOpt { pub enum GfxApiOpt {
@ -141,6 +144,72 @@ pub struct CopyTexture {
pub tex: Rc<dyn GfxTexture>, pub tex: Rc<dyn GfxTexture>,
pub source: SampleRect, pub source: SampleRect,
pub target: FramebufferRect, pub target: FramebufferRect,
pub buffer_resv: Option<Rc<dyn BufferResv>>,
pub acquire_sync: AcquireSync,
pub release_sync: ReleaseSync,
}
#[derive(Clone, Debug)]
pub struct SyncFile(pub Rc<OwnedFd>);
impl Deref for SyncFile {
type Target = Rc<OwnedFd>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
unsafe impl UnsafeCellCloneSafe for SyncFile {}
#[derive(Clone)]
pub enum AcquireSync {
None,
Implicit,
SyncFile { sync_file: SyncFile },
Unnecessary,
}
impl AcquireSync {
pub fn from_sync_file(sync_file: Option<SyncFile>) -> Self {
match sync_file {
None => Self::Implicit,
Some(sync_file) => Self::SyncFile { sync_file },
}
}
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum ReleaseSync {
None,
Implicit,
Explicit,
}
impl Debug for AcquireSync {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let name = match self {
AcquireSync::None => "None",
AcquireSync::Implicit => "Implicit",
AcquireSync::SyncFile { .. } => "SyncFile",
AcquireSync::Unnecessary => "Unnecessary",
};
f.debug_struct(name).finish_non_exhaustive()
}
}
pub trait BufferResv: Debug {
fn set_sync_file(&self, user: BufferResvUser, sync_file: &SyncFile);
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct BufferResvUser(u64);
impl Default for BufferResvUser {
fn default() -> Self {
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
Self(NEXT_ID.fetch_add(1, Relaxed))
}
} }
#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
@ -158,7 +227,11 @@ pub trait GfxFramebuffer: Debug {
fn physical_size(&self) -> (i32, i32); fn physical_size(&self) -> (i32, i32);
fn render(&self, ops: Vec<GfxApiOpt>, clear: Option<&Color>); fn render(
&self,
ops: Vec<GfxApiOpt>,
clear: Option<&Color>,
) -> Result<Option<SyncFile>, GfxError>;
fn copy_to_shm( fn copy_to_shm(
self: Rc<Self>, self: Rc<Self>,
@ -175,13 +248,13 @@ pub trait GfxFramebuffer: Debug {
} }
impl dyn GfxFramebuffer { impl dyn GfxFramebuffer {
pub fn clear(&self) { pub fn clear(&self) -> Result<Option<SyncFile>, GfxError> {
self.clear_with(0.0, 0.0, 0.0, 0.0); self.clear_with(0.0, 0.0, 0.0, 0.0)
} }
pub fn clear_with(&self, r: f32, g: f32, b: f32, a: f32) { pub fn clear_with(&self, r: f32, g: f32, b: f32, a: f32) -> Result<Option<SyncFile>, GfxError> {
let ops = self.take_render_ops(); let ops = self.take_render_ops();
self.render(ops, Some(&Color { r, g, b, a })); self.render(ops, Some(&Color { r, g, b, a }))
} }
pub fn logical_size(&self, transform: Transform) -> (i32, i32) { pub fn logical_size(&self, transform: Transform) -> (i32, i32) {
@ -206,13 +279,31 @@ impl dyn GfxFramebuffer {
} }
} }
pub fn copy_texture(&self, texture: &Rc<dyn GfxTexture>, x: i32, y: i32) { pub fn copy_texture(
&self,
texture: &Rc<dyn GfxTexture>,
acquire_sync: AcquireSync,
release_sync: ReleaseSync,
x: i32,
y: i32,
) -> Result<Option<SyncFile>, GfxError> {
let mut ops = self.take_render_ops(); let mut ops = self.take_render_ops();
let scale = Scale::from_int(1); let scale = Scale::from_int(1);
let mut renderer = self.renderer_base(&mut ops, scale, Transform::None); let mut renderer = self.renderer_base(&mut ops, scale, Transform::None);
renderer.render_texture(texture, x, y, None, None, scale, None); renderer.render_texture(
texture,
x,
y,
None,
None,
scale,
None,
None,
acquire_sync,
release_sync,
);
let clear = self.format().has_alpha.then_some(&Color::TRANSPARENT); let clear = self.format().has_alpha.then_some(&Color::TRANSPARENT);
self.render(ops, clear); self.render(ops, clear)
} }
pub fn render_custom( pub fn render_custom(
@ -220,11 +311,11 @@ impl dyn GfxFramebuffer {
scale: Scale, scale: Scale,
clear: Option<&Color>, clear: Option<&Color>,
f: &mut dyn FnMut(&mut RendererBase), f: &mut dyn FnMut(&mut RendererBase),
) { ) -> Result<Option<SyncFile>, GfxError> {
let mut ops = self.take_render_ops(); let mut ops = self.take_render_ops();
let mut renderer = self.renderer_base(&mut ops, scale, Transform::None); let mut renderer = self.renderer_base(&mut ops, scale, Transform::None);
f(&mut renderer); f(&mut renderer);
self.render(ops, clear); self.render(ops, clear)
} }
pub fn create_render_pass( pub fn create_render_pass(
@ -295,7 +386,7 @@ impl dyn GfxFramebuffer {
} }
} }
pub fn perform_render_pass(&self, pass: GfxRenderPass) { pub fn perform_render_pass(&self, pass: GfxRenderPass) -> Result<Option<SyncFile>, GfxError> {
self.render(pass.ops, pass.clear.as_ref()) self.render(pass.ops, pass.clear.as_ref())
} }
@ -307,7 +398,7 @@ impl dyn GfxFramebuffer {
result: Option<&mut RenderResult>, result: Option<&mut RenderResult>,
scale: Scale, scale: Scale,
render_hardware_cursor: bool, render_hardware_cursor: bool,
) { ) -> Result<Option<SyncFile>, GfxError> {
self.render_node( self.render_node(
node, node,
state, state,
@ -330,7 +421,7 @@ impl dyn GfxFramebuffer {
render_hardware_cursor: bool, render_hardware_cursor: bool,
black_background: bool, black_background: bool,
transform: Transform, transform: Transform,
) { ) -> Result<Option<SyncFile>, GfxError> {
let pass = self.create_render_pass( let pass = self.create_render_pass(
node, node,
state, state,
@ -341,7 +432,7 @@ impl dyn GfxFramebuffer {
black_background, black_background,
transform, transform,
); );
self.perform_render_pass(pass); self.perform_render_pass(pass)
} }
pub fn render_hardware_cursor( pub fn render_hardware_cursor(
@ -350,7 +441,7 @@ impl dyn GfxFramebuffer {
state: &State, state: &State,
scale: Scale, scale: Scale,
transform: Transform, transform: Transform,
) { ) -> Result<Option<SyncFile>, GfxError> {
let mut ops = self.take_render_ops(); let mut ops = self.take_render_ops();
let mut renderer = Renderer { let mut renderer = Renderer {
base: self.renderer_base(&mut ops, scale, transform), base: self.renderer_base(&mut ops, scale, transform),
@ -363,7 +454,7 @@ impl dyn GfxFramebuffer {
}, },
}; };
cursor.render_hardware_cursor(&mut renderer); cursor.render_hardware_cursor(&mut renderer);
self.render(ops, Some(&Color::TRANSPARENT)); self.render(ops, Some(&Color::TRANSPARENT))
} }
} }
@ -376,38 +467,6 @@ pub trait GfxImage {
fn height(&self) -> i32; fn height(&self) -> i32;
} }
#[derive(Default)]
pub struct TextureReservations {
reservations: NumCell<usize>,
on_release: Cell<Option<Box<dyn FnOnce()>>>,
}
impl TextureReservations {
pub fn has_reservation(&self) -> bool {
self.reservations.get() != 0
}
pub fn acquire(&self) {
self.reservations.fetch_add(1);
}
pub fn release(&self) {
if self.reservations.fetch_sub(1) == 1 {
if let Some(cb) = self.on_release.take() {
cb();
}
}
}
pub fn on_released<C: FnOnce() + 'static>(&self, cb: C) {
if self.has_reservation() {
self.on_release.set(Some(Box::new(cb)));
} else {
cb();
}
}
}
pub trait GfxTexture: Debug { pub trait GfxTexture: Debug {
fn size(&self) -> (i32, i32); fn size(&self) -> (i32, i32);
fn as_any(&self) -> &dyn Any; fn as_any(&self) -> &dyn Any;
@ -423,7 +482,6 @@ pub trait GfxTexture: Debug {
shm: &[Cell<u8>], shm: &[Cell<u8>],
) -> Result<(), GfxError>; ) -> Result<(), GfxError>;
fn dmabuf(&self) -> Option<&DmaBuf>; fn dmabuf(&self) -> Option<&DmaBuf>;
fn reservations(&self) -> &TextureReservations;
fn format(&self) -> &'static Format; fn format(&self) -> &'static Format;
} }
@ -461,6 +519,8 @@ pub trait GfxContext: Debug {
stride: i32, stride: i32,
format: &'static Format, format: &'static Format,
) -> Result<Rc<dyn GfxFramebuffer>, GfxError>; ) -> Result<Rc<dyn GfxFramebuffer>, GfxError>;
fn sync_obj_ctx(&self) -> &Rc<SyncObjCtx>;
} }
#[derive(Debug)] #[derive(Debug)]

View file

@ -68,8 +68,8 @@ macro_rules! dynload {
use { use {
crate::{ crate::{
gfx_api::{ gfx_api::{
CopyTexture, FillRect, FramebufferRect, GfxApiOpt, GfxContext, GfxError, GfxTexture, AcquireSync, CopyTexture, FillRect, GfxApiOpt, GfxContext, GfxError, GfxTexture,
SampleRect, ReleaseSync, SyncFile,
}, },
gfx_apis::gl::{ gfx_apis::gl::{
gl::texture::image_target, gl::texture::image_target,
@ -80,8 +80,9 @@ use {
}, },
}, },
theme::Color, theme::Color,
utils::{rc_eq::rc_eq, vecstorage::VecStorage}, utils::{errorfmt::ErrorFmt, rc_eq::rc_eq, vecstorage::VecStorage},
video::{ video::{
dmabuf::DMA_BUF_SYNC_READ,
drm::{Drm, DrmError}, drm::{Drm, DrmError},
gbm::GbmError, gbm::GbmError,
}, },
@ -181,6 +182,14 @@ enum RenderError {
NoSupportedFormats, NoSupportedFormats,
#[error("Cannot convert a shm texture into a framebuffer")] #[error("Cannot convert a shm texture into a framebuffer")]
ShmTextureToFb, ShmTextureToFb,
#[error("Could not create EGLSyncKHR")]
CreateEglSync,
#[error("Could not destroy EGLSyncKHR")]
DestroyEglSync,
#[error("Could not export sync file")]
ExportSyncFile,
#[error("Could not insert wait for EGLSyncKHR")]
WaitSync,
} }
#[derive(Default)] #[derive(Default)]
@ -190,7 +199,7 @@ struct GfxGlState {
copy_tex: VecStorage<&'static CopyTexture>, copy_tex: VecStorage<&'static CopyTexture>,
} }
fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) { fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) -> Option<SyncFile> {
let mut state = fb.ctx.gl_state.borrow_mut(); let mut state = fb.ctx.gl_state.borrow_mut();
let state = &mut *state; let state = &mut *state;
let mut fill_rect = state.fill_rect.take(); let mut fill_rect = state.fill_rect.take();
@ -256,9 +265,30 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) {
} }
} }
for tex in &*copy_tex { for tex in &*copy_tex {
render_texture(&fb.ctx, &tex.tex.as_gl(), &tex.target, &tex.source) render_texture(&fb.ctx, tex);
} }
} }
if fb.ctx.ctx.dpy.explicit_sync {
let file = match fb.ctx.ctx.export_sync_file() {
Ok(f) => SyncFile(Rc::new(f)),
Err(e) => {
log::error!("Could not create sync file: {}", ErrorFmt(e));
return None;
}
};
let user = fb.ctx.buffer_resv_user;
for op in ops {
if let GfxApiOpt::CopyTexture(ct) = op {
if ct.release_sync == ReleaseSync::Explicit {
if let Some(resv) = &ct.buffer_resv {
resv.set_sync_file(user, &file);
}
}
}
}
return Some(file);
}
None
} }
fn fill_boxes3(ctx: &GlRenderContext, boxes: &[[f32; 2]], color: &Color) { fn fill_boxes3(ctx: &GlRenderContext, boxes: &[[f32; 2]], color: &Color) {
@ -280,15 +310,13 @@ fn fill_boxes3(ctx: &GlRenderContext, boxes: &[[f32; 2]], color: &Color) {
} }
} }
fn render_texture( fn render_texture(ctx: &GlRenderContext, tex: &CopyTexture) {
ctx: &GlRenderContext, let texture = tex.tex.as_gl();
texture: &Texture,
target_rect: &FramebufferRect,
src: &SampleRect,
) {
assert!(rc_eq(&ctx.ctx, &texture.ctx.ctx)); assert!(rc_eq(&ctx.ctx, &texture.ctx.ctx));
let gles = ctx.ctx.dpy.gles; let gles = ctx.ctx.dpy.gles;
unsafe { unsafe {
handle_explicit_sync(ctx, texture, &tex.acquire_sync);
(gles.glActiveTexture)(GL_TEXTURE0); (gles.glActiveTexture)(GL_TEXTURE0);
let target = image_target(texture.gl.external_only); let target = image_target(texture.gl.external_only);
@ -321,8 +349,8 @@ fn render_texture(
(gles.glUniform1i)(prog.tex, 0); (gles.glUniform1i)(prog.tex, 0);
let texcoord = src.to_points(); let texcoord = tex.source.to_points();
let pos = target_rect.to_points(); let pos = tex.target.to_points();
(gles.glVertexAttribPointer)( (gles.glVertexAttribPointer)(
prog.texcoord as _, prog.texcoord as _,
@ -346,6 +374,36 @@ fn render_texture(
} }
} }
fn handle_explicit_sync(ctx: &GlRenderContext, texture: &Texture, sync: &AcquireSync) {
let sync_file = match sync {
AcquireSync::None | AcquireSync::Implicit | AcquireSync::Unnecessary => return,
AcquireSync::SyncFile { sync_file } => sync_file,
};
let sync_file = match uapi::fcntl_dupfd_cloexec(sync_file.raw(), 0) {
Ok(s) => s,
Err(e) => {
log::error!("Could not dup sync file: {}", ErrorFmt(e));
return;
}
};
if ctx.ctx.dpy.explicit_sync {
let sync = match ctx.ctx.create_sync(Some(sync_file)) {
Ok(s) => s,
Err(e) => {
log::error!("Could import sync file: {}", ErrorFmt(e));
return;
}
};
sync.wait();
} else {
if let Some(img) = &texture.gl.img {
if let Err(e) = img.dmabuf.import_sync_file(DMA_BUF_SYNC_READ, &sync_file) {
log::error!("Could not import sync file into dmabuf: {}", ErrorFmt(e));
}
}
}
}
impl dyn GfxTexture { impl dyn GfxTexture {
fn as_gl(&self) -> &Texture { fn as_gl(&self) -> &Texture {
self.as_any() self.as_any()

View file

@ -24,9 +24,10 @@ use {
PROCS, PROCS,
}, },
ext::{ ext::{
get_display_ext, get_gl_ext, DisplayExt, GlExt, EXT_CREATE_CONTEXT_ROBUSTNESS, get_display_ext, get_gl_ext, DisplayExt, GlExt, ANDROID_NATIVE_FENCE_SYNC,
EXT_IMAGE_DMA_BUF_IMPORT_MODIFIERS, GL_OES_EGL_IMAGE, GL_OES_EGL_IMAGE_EXTERNAL, EXT_CREATE_CONTEXT_ROBUSTNESS, EXT_IMAGE_DMA_BUF_IMPORT_MODIFIERS,
KHR_IMAGE_BASE, KHR_NO_CONFIG_CONTEXT, KHR_SURFACELESS_CONTEXT, GL_OES_EGL_IMAGE, GL_OES_EGL_IMAGE_EXTERNAL, KHR_FENCE_SYNC, KHR_IMAGE_BASE,
KHR_NO_CONFIG_CONTEXT, KHR_SURFACELESS_CONTEXT, KHR_WAIT_SYNC,
MESA_CONFIGLESS_CONTEXT, MESA_CONFIGLESS_CONTEXT,
}, },
proc::ExtProc, proc::ExtProc,
@ -65,6 +66,7 @@ pub struct EglDisplay {
pub formats: AHashMap<u32, EglFormat>, pub formats: AHashMap<u32, EglFormat>,
pub gbm: Rc<GbmDevice>, pub gbm: Rc<GbmDevice>,
pub dpy: EGLDisplay, pub dpy: EGLDisplay,
pub explicit_sync: bool,
} }
impl EglDisplay { impl EglDisplay {
@ -99,6 +101,7 @@ impl EglDisplay {
formats: AHashMap::new(), formats: AHashMap::new(),
gbm: Rc::new(gbm), gbm: Rc::new(gbm),
dpy, dpy,
explicit_sync: false,
}; };
let mut major = 0; let mut major = 0;
let mut minor = 0; let mut minor = 0;
@ -122,6 +125,9 @@ impl EglDisplay {
return Err(RenderError::SurfacelessContext); return Err(RenderError::SurfacelessContext);
} }
dpy.formats = query_formats(procs, dpy.dpy)?; dpy.formats = query_formats(procs, dpy.dpy)?;
dpy.explicit_sync = dpy
.exts
.contains(KHR_FENCE_SYNC | KHR_WAIT_SYNC | ANDROID_NATIVE_FENCE_SYNC);
Ok(Rc::new(dpy)) Ok(Rc::new(dpy))
} }

View file

@ -6,6 +6,7 @@ pub type EGLBoolean = c::c_uint;
#[allow(dead_code)] #[allow(dead_code)]
pub type EGLuint64KHR = u64; pub type EGLuint64KHR = u64;
pub type EGLAttrib = isize; pub type EGLAttrib = isize;
pub type EGLSyncKHR = *mut u8;
egl_transparent!(EGLDisplay); egl_transparent!(EGLDisplay);
egl_transparent!(EGLSurface); egl_transparent!(EGLSurface);
@ -83,6 +84,8 @@ pub const EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT: EGLint = 0x3449;
pub const EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT: EGLint = 0x344A; pub const EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT: EGLint = 0x344A;
pub const EGL_IMAGE_PRESERVED_KHR: EGLint = 0x30D2; pub const EGL_IMAGE_PRESERVED_KHR: EGLint = 0x30D2;
pub const EGL_LINUX_DMA_BUF_EXT: EGLint = 0x3270; pub const EGL_LINUX_DMA_BUF_EXT: EGLint = 0x3270;
pub const EGL_SYNC_NATIVE_FENCE_ANDROID: EGLenum = 0x3144;
pub const EGL_SYNC_NATIVE_FENCE_FD_ANDROID: EGLint = 0x3145;
dynload! { dynload! {
EGL: Egl from "libEGL.so" { EGL: Egl from "libEGL.so" {

View file

@ -78,6 +78,9 @@ bitflags! {
KHR_SURFACELESS_CONTEXT = 1 << 5, KHR_SURFACELESS_CONTEXT = 1 << 5,
IMG_CONTEXT_PRIORITY = 1 << 6, IMG_CONTEXT_PRIORITY = 1 << 6,
EXT_CREATE_CONTEXT_ROBUSTNESS = 1 << 7, EXT_CREATE_CONTEXT_ROBUSTNESS = 1 << 7,
KHR_FENCE_SYNC = 1 << 8,
KHR_WAIT_SYNC = 1 << 9,
ANDROID_NATIVE_FENCE_SYNC = 1 << 10,
} }
pub(crate) unsafe fn get_display_ext(dpy: EGLDisplay) -> DisplayExt { pub(crate) unsafe fn get_display_ext(dpy: EGLDisplay) -> DisplayExt {
@ -96,6 +99,9 @@ pub(crate) unsafe fn get_display_ext(dpy: EGLDisplay) -> DisplayExt {
"EGL_EXT_create_context_robustness", "EGL_EXT_create_context_robustness",
EXT_CREATE_CONTEXT_ROBUSTNESS, EXT_CREATE_CONTEXT_ROBUSTNESS,
), ),
("EGL_KHR_fence_sync", KHR_FENCE_SYNC),
("EGL_KHR_wait_sync", KHR_WAIT_SYNC),
("EGL_ANDROID_native_fence_sync", ANDROID_NATIVE_FENCE_SYNC),
]; ];
match get_dpy_extensions(dpy) { match get_dpy_extensions(dpy) {
Some(exts) => get_typed_ext(&exts, DisplayExt::none(), &map), Some(exts) => get_typed_ext(&exts, DisplayExt::none(), &map),

View file

@ -1,4 +1,5 @@
pub(super) mod context; pub(super) mod context;
pub(super) mod framebuffer; pub(super) mod framebuffer;
pub(super) mod image; pub(super) mod image;
pub(super) mod sync;
pub(super) mod texture; pub(super) mod texture;

View file

@ -2,8 +2,8 @@ use {
crate::{ crate::{
format::{Format, XRGB8888}, format::{Format, XRGB8888},
gfx_api::{ gfx_api::{
GfxApiOpt, GfxContext, GfxError, GfxFormat, GfxFramebuffer, GfxImage, GfxTexture, BufferResvUser, GfxApiOpt, GfxContext, GfxError, GfxFormat, GfxFramebuffer, GfxImage,
ResetStatus, GfxTexture, ResetStatus,
}, },
gfx_apis::gl::{ gfx_apis::gl::{
egl::{context::EglContext, display::EglDisplay, image::EglImage}, egl::{context::EglContext, display::EglDisplay, image::EglImage},
@ -14,7 +14,11 @@ use {
renderer::{framebuffer::Framebuffer, image::Image}, renderer::{framebuffer::Framebuffer, image::Image},
GfxGlState, RenderError, Texture, GfxGlState, RenderError, Texture,
}, },
video::{dmabuf::DmaBuf, drm::Drm, gbm::GbmDevice}, video::{
dmabuf::DmaBuf,
drm::{sync_obj::SyncObjCtx, Drm},
gbm::GbmDevice,
},
}, },
ahash::AHashMap, ahash::AHashMap,
jay_config::video::GfxApi, jay_config::video::GfxApi,
@ -53,6 +57,7 @@ pub(crate) struct TexProgs {
pub(in crate::gfx_apis::gl) struct GlRenderContext { pub(in crate::gfx_apis::gl) struct GlRenderContext {
pub(crate) ctx: Rc<EglContext>, pub(crate) ctx: Rc<EglContext>,
pub gbm: Rc<GbmDevice>, pub gbm: Rc<GbmDevice>,
pub sync_ctx: Rc<SyncObjCtx>,
pub(crate) render_node: Rc<CString>, pub(crate) render_node: Rc<CString>,
@ -65,6 +70,8 @@ pub(in crate::gfx_apis::gl) struct GlRenderContext {
pub(crate) gfx_ops: RefCell<Vec<GfxApiOpt>>, pub(crate) gfx_ops: RefCell<Vec<GfxApiOpt>>,
pub(in crate::gfx_apis::gl) gl_state: RefCell<GfxGlState>, pub(in crate::gfx_apis::gl) gl_state: RefCell<GfxGlState>,
pub(in crate::gfx_apis::gl) buffer_resv_user: BufferResvUser,
} }
impl Debug for GlRenderContext { impl Debug for GlRenderContext {
@ -126,6 +133,7 @@ impl GlRenderContext {
Ok(Self { Ok(Self {
ctx: ctx.clone(), ctx: ctx.clone(),
gbm: ctx.dpy.gbm.clone(), gbm: ctx.dpy.gbm.clone(),
sync_ctx: Rc::new(SyncObjCtx::new(ctx.dpy.gbm.drm.fd())),
render_node: node.clone(), render_node: node.clone(),
@ -141,6 +149,8 @@ impl GlRenderContext {
gfx_ops: Default::default(), gfx_ops: Default::default(),
gl_state: Default::default(), gl_state: Default::default(),
buffer_resv_user: Default::default(),
}) })
} }
@ -186,7 +196,6 @@ impl GlRenderContext {
Ok(Rc::new(Texture { Ok(Rc::new(Texture {
ctx: self.clone(), ctx: self.clone(),
gl, gl,
resv: Default::default(),
format, format,
})) }))
} }
@ -268,4 +277,8 @@ impl GfxContext for GlRenderContext {
})?; })?;
Ok(Rc::new(Framebuffer { ctx: self, gl: fb })) Ok(Rc::new(Framebuffer { ctx: self, gl: fb }))
} }
fn sync_obj_ctx(&self) -> &Rc<SyncObjCtx> {
&self.sync_ctx
}
} }

View file

@ -1,7 +1,7 @@
use { use {
crate::{ crate::{
format::Format, format::Format,
gfx_api::{GfxApiOpt, GfxError, GfxFramebuffer}, gfx_api::{GfxApiOpt, GfxError, GfxFramebuffer, SyncFile},
gfx_apis::gl::{ gfx_apis::gl::{
gl::{ gl::{
frame_buffer::GlFrameBuffer, frame_buffer::GlFrameBuffer,
@ -10,6 +10,7 @@ use {
renderer::context::GlRenderContext, renderer::context::GlRenderContext,
run_ops, run_ops,
sys::{GL_ONE, GL_ONE_MINUS_SRC_ALPHA}, sys::{GL_ONE, GL_ONE_MINUS_SRC_ALPHA},
RenderError,
}, },
theme::Color, theme::Color,
}, },
@ -64,9 +65,13 @@ impl Framebuffer {
}); });
} }
pub fn render(&self, ops: Vec<GfxApiOpt>, clear: Option<&Color>) { pub fn render(
&self,
mut ops: Vec<GfxApiOpt>,
clear: Option<&Color>,
) -> Result<Option<SyncFile>, RenderError> {
let gles = self.ctx.ctx.dpy.gles; let gles = self.ctx.ctx.dpy.gles;
let _ = self.ctx.ctx.with_current(|| { let res = self.ctx.ctx.with_current(|| {
unsafe { unsafe {
(gles.glBindFramebuffer)(GL_FRAMEBUFFER, self.gl.fbo); (gles.glBindFramebuffer)(GL_FRAMEBUFFER, self.gl.fbo);
(gles.glViewport)(0, 0, self.gl.width, self.gl.height); (gles.glViewport)(0, 0, self.gl.width, self.gl.height);
@ -76,13 +81,17 @@ impl Framebuffer {
} }
(gles.glBlendFunc)(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); (gles.glBlendFunc)(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
} }
run_ops(self, &ops); let fd = run_ops(self, &ops);
unsafe { if fd.is_none() {
(gles.glFlush)(); unsafe {
(gles.glFlush)();
}
} }
Ok(()) Ok(fd)
}); });
ops.clear();
*self.ctx.gfx_ops.borrow_mut() = ops; *self.ctx.gfx_ops.borrow_mut() = ops;
res
} }
} }
@ -92,17 +101,19 @@ impl GfxFramebuffer for Framebuffer {
} }
fn take_render_ops(&self) -> Vec<GfxApiOpt> { fn take_render_ops(&self) -> Vec<GfxApiOpt> {
let mut ops = mem::take(&mut *self.ctx.gfx_ops.borrow_mut()); mem::take(&mut *self.ctx.gfx_ops.borrow_mut())
ops.clear();
ops
} }
fn physical_size(&self) -> (i32, i32) { fn physical_size(&self) -> (i32, i32) {
(self.gl.width, self.gl.height) (self.gl.width, self.gl.height)
} }
fn render(&self, ops: Vec<GfxApiOpt>, clear: Option<&Color>) { fn render(
self.render(ops, clear); &self,
ops: Vec<GfxApiOpt>,
clear: Option<&Color>,
) -> Result<Option<SyncFile>, GfxError> {
self.render(ops, clear).map_err(|e| e.into())
} }
fn copy_to_shm( fn copy_to_shm(

View file

@ -27,7 +27,6 @@ impl Image {
Ok(Rc::new(Texture { Ok(Rc::new(Texture {
ctx: self.ctx.clone(), ctx: self.ctx.clone(),
gl: GlTexture::import_img(&self.ctx.ctx, &self.gl)?, gl: GlTexture::import_img(&self.ctx.ctx, &self.gl)?,
resv: Default::default(),
format: self.gl.dmabuf.format, format: self.gl.dmabuf.format,
})) }))
} }

View file

@ -0,0 +1,107 @@
use {
crate::{
gfx_apis::gl::{
egl::context::EglContext,
sys::{
EGLBoolean, EGLSyncKHR, EGL_NONE, EGL_SYNC_NATIVE_FENCE_ANDROID,
EGL_SYNC_NATIVE_FENCE_FD_ANDROID, EGL_TRUE,
},
RenderError,
},
utils::errorfmt::ErrorFmt,
},
std::rc::Rc,
uapi::OwnedFd,
};
pub struct EglSync {
ctx: Rc<EglContext>,
sync: EGLSyncKHR,
}
impl EglContext {
pub fn export_sync_file(self: &Rc<Self>) -> Result<OwnedFd, RenderError> {
self.create_sync(None)?.export_sync_file()
}
pub fn create_sync(self: &Rc<Self>, file: Option<OwnedFd>) -> Result<EglSync, RenderError> {
let mut attribs = [EGL_NONE; 3];
if let Some(file) = &file {
attribs[0] = EGL_SYNC_NATIVE_FENCE_FD_ANDROID;
attribs[1] = file.raw();
}
self.with_current(|| unsafe {
let sync = self.dpy.procs.eglCreateSyncKHR(
self.dpy.dpy,
EGL_SYNC_NATIVE_FENCE_ANDROID,
attribs.as_ptr(),
);
if sync.is_null() {
Err(RenderError::CreateEglSync)
} else {
if let Some(file) = file {
file.unwrap();
}
Ok(EglSync {
ctx: self.clone(),
sync,
})
}
})
}
}
impl EglSync {
pub fn wait(&self) {
let res = self.ctx.with_current(|| unsafe {
let res = self
.ctx
.dpy
.procs
.eglWaitSyncKHR(self.ctx.dpy.dpy, self.sync, 0);
if res as EGLBoolean == EGL_TRUE {
Ok(())
} else {
Err(RenderError::WaitSync)
}
});
if let Err(e) = res {
log::warn!("Could not insert wait point: {}", ErrorFmt(e));
}
}
pub fn export_sync_file(&self) -> Result<OwnedFd, RenderError> {
self.ctx.with_current(|| unsafe {
let fd = self
.ctx
.dpy
.procs
.eglDupNativeFenceFDANDROID(self.ctx.dpy.dpy, self.sync);
if fd == -1 {
Err(RenderError::ExportSyncFile)
} else {
Ok(OwnedFd::new(fd))
}
})
}
}
impl Drop for EglSync {
fn drop(&mut self) {
let res = self.ctx.with_current(|| unsafe {
let res = self
.ctx
.dpy
.procs
.eglDestroySyncKHR(self.ctx.dpy.dpy, self.sync);
if res == EGL_TRUE {
Ok(())
} else {
Err(RenderError::DestroyEglSync)
}
});
if let Err(e) = res {
log::error!("{}", ErrorFmt(e));
}
}
}

View file

@ -1,7 +1,7 @@
use { use {
crate::{ crate::{
format::Format, format::Format,
gfx_api::{GfxError, GfxTexture, TextureReservations}, gfx_api::{GfxError, GfxTexture},
gfx_apis::gl::{ gfx_apis::gl::{
gl::texture::GlTexture, gl::texture::GlTexture,
renderer::{context::GlRenderContext, framebuffer::Framebuffer}, renderer::{context::GlRenderContext, framebuffer::Framebuffer},
@ -20,7 +20,6 @@ use {
pub struct Texture { pub struct Texture {
pub(in crate::gfx_apis::gl) ctx: Rc<GlRenderContext>, pub(in crate::gfx_apis::gl) ctx: Rc<GlRenderContext>,
pub(in crate::gfx_apis::gl) gl: GlTexture, pub(in crate::gfx_apis::gl) gl: GlTexture,
pub(in crate::gfx_apis::gl) resv: TextureReservations,
pub(in crate::gfx_apis::gl) format: &'static Format, pub(in crate::gfx_apis::gl) format: &'static Format,
} }
@ -79,10 +78,6 @@ impl GfxTexture for Texture {
self.gl.img.as_ref().map(|i| &i.dmabuf) self.gl.img.as_ref().map(|i| &i.dmabuf)
} }
fn reservations(&self) -> &TextureReservations {
&self.resv
}
fn format(&self) -> &'static Format { fn format(&self) -> &'static Format {
self.format self.format
} }

View file

@ -28,7 +28,7 @@ use {
utils::oserror::OsError, utils::oserror::OsError,
video::{ video::{
dmabuf::DmaBuf, dmabuf::DmaBuf,
drm::{Drm, DrmError}, drm::{sync_obj::SyncObjCtx, Drm, DrmError},
gbm::{GbmDevice, GbmError}, gbm::{GbmDevice, GbmError},
}, },
}, },
@ -93,7 +93,7 @@ pub enum VulkanError {
LoadImageProperties(#[source] vk::Result), LoadImageProperties(#[source] vk::Result),
#[error("Device does not support rending and texturing from the XRGB8888 format")] #[error("Device does not support rending and texturing from the XRGB8888 format")]
XRGB8888, XRGB8888,
#[error("Device does not support syncobj import")] #[error("Device does not support sync obj import")]
SyncobjImport, SyncobjImport,
#[error("Could not start a command buffer")] #[error("Could not start a command buffer")]
BeginCommandBuffer(vk::Result), BeginCommandBuffer(vk::Result),
@ -153,8 +153,6 @@ pub enum VulkanError {
IoctlExportSyncFile(#[source] OsError), IoctlExportSyncFile(#[source] OsError),
#[error("Could not import a sync file into a semaphore")] #[error("Could not import a sync file into a semaphore")]
ImportSyncFile(#[source] vk::Result), ImportSyncFile(#[source] vk::Result),
#[error("Could not import a sync file into a dma-buf")]
IoctlImportSyncFile(#[source] OsError),
#[error("Could not export a sync file from a semaphore")] #[error("Could not export a sync file from a semaphore")]
ExportSyncFile(#[source] vk::Result), ExportSyncFile(#[source] vk::Result),
#[error("Could not fetch the render node of the device")] #[error("Could not fetch the render node of the device")]
@ -175,6 +173,8 @@ pub enum VulkanError {
height: i32, height: i32,
stride: i32, stride: i32,
}, },
#[error(transparent)]
GfxError(GfxError),
} }
impl From<VulkanError> for GfxError { impl From<VulkanError> for GfxError {
@ -270,6 +270,10 @@ impl GfxContext for Context {
.create_shm_texture(format, width, height, stride, &[], true)?; .create_shm_texture(format, width, height, stride, &[], true)?;
Ok(fb) Ok(fb)
} }
fn sync_obj_ctx(&self) -> &Rc<SyncObjCtx> {
&self.0.device.sync_ctx
}
} }
impl Drop for Context { impl Drop for Context {

View file

@ -10,7 +10,10 @@ use {
util::OnDrop, util::OnDrop,
VulkanError, VulkanError,
}, },
video::{drm::Drm, gbm::GbmDevice}, video::{
drm::{sync_obj::SyncObjCtx, Drm},
gbm::GbmDevice,
},
}, },
ahash::AHashMap, ahash::AHashMap,
arrayvec::ArrayVec, arrayvec::ArrayVec,
@ -43,6 +46,7 @@ pub struct VulkanDevice {
pub(super) physical_device: PhysicalDevice, pub(super) physical_device: PhysicalDevice,
pub(super) render_node: Rc<CString>, pub(super) render_node: Rc<CString>,
pub(super) gbm: GbmDevice, pub(super) gbm: GbmDevice,
pub(super) sync_ctx: Rc<SyncObjCtx>,
pub(super) instance: Rc<VulkanInstance>, pub(super) instance: Rc<VulkanInstance>,
pub(super) device: Device, pub(super) device: Device,
pub(super) external_memory_fd: ExternalMemoryFd, pub(super) external_memory_fd: ExternalMemoryFd,
@ -279,6 +283,7 @@ impl VulkanInstance {
Ok(Rc::new(VulkanDevice { Ok(Rc::new(VulkanDevice {
physical_device: phy_dev, physical_device: phy_dev,
render_node, render_node,
sync_ctx: Rc::new(SyncObjCtx::new(gbm.drm.fd())),
gbm, gbm,
instance: self.clone(), instance: self.clone(),
device, device,

View file

@ -1,5 +1,8 @@
use { use {
crate::gfx_apis::vulkan::{device::VulkanDevice, VulkanError}, crate::{
gfx_api::SyncFile,
gfx_apis::vulkan::{device::VulkanDevice, VulkanError},
},
ash::vk::{ ash::vk::{
ExportFenceCreateInfo, ExternalFenceHandleTypeFlags, Fence, FenceCreateInfo, ExportFenceCreateInfo, ExternalFenceHandleTypeFlags, Fence, FenceCreateInfo,
FenceGetFdInfoKHR, FenceGetFdInfoKHR,
@ -38,12 +41,12 @@ impl VulkanDevice {
} }
impl VulkanFence { impl VulkanFence {
pub fn export_syncfile(&self) -> Result<Rc<OwnedFd>, VulkanError> { pub fn export_sync_file(&self) -> Result<SyncFile, VulkanError> {
let info = FenceGetFdInfoKHR::builder() let info = FenceGetFdInfoKHR::builder()
.fence(self.fence) .fence(self.fence)
.handle_type(ExternalFenceHandleTypeFlags::SYNC_FD); .handle_type(ExternalFenceHandleTypeFlags::SYNC_FD);
let res = unsafe { self.device.external_fence_fd.get_fence_fd(&info) }; let res = unsafe { self.device.external_fence_fd.get_fence_fd(&info) };
res.map_err(VulkanError::ExportSyncFile) res.map_err(VulkanError::ExportSyncFile)
.map(|fd| Rc::new(OwnedFd::new(fd))) .map(|fd| SyncFile(Rc::new(OwnedFd::new(fd))))
} }
} }

View file

@ -1,7 +1,7 @@
use { use {
crate::{ crate::{
format::Format, format::Format,
gfx_api::{GfxApiOpt, GfxError, GfxFramebuffer, GfxImage, GfxTexture, TextureReservations}, gfx_api::{GfxApiOpt, GfxError, GfxFramebuffer, GfxImage, GfxTexture, SyncFile},
gfx_apis::vulkan::{ gfx_apis::vulkan::{
allocator::VulkanAllocation, device::VulkanDevice, format::VulkanMaxExtents, allocator::VulkanAllocation, device::VulkanDevice, format::VulkanMaxExtents,
renderer::VulkanRenderer, util::OnDrop, VulkanError, renderer::VulkanRenderer, util::OnDrop, VulkanError,
@ -53,7 +53,6 @@ pub struct VulkanImage {
pub(super) is_undefined: Cell<bool>, pub(super) is_undefined: Cell<bool>,
pub(super) ty: VulkanImageMemory, pub(super) ty: VulkanImageMemory,
pub(super) render_ops: CloneCell<Vec<GfxApiOpt>>, pub(super) render_ops: CloneCell<Vec<GfxApiOpt>>,
pub(super) resv: TextureReservations,
} }
pub enum VulkanImageMemory { pub enum VulkanImageMemory {
@ -212,7 +211,6 @@ impl VulkanRenderer {
is_undefined: Cell::new(true), is_undefined: Cell::new(true),
ty: VulkanImageMemory::Internal(shm), ty: VulkanImageMemory::Internal(shm),
render_ops: Default::default(), render_ops: Default::default(),
resv: Default::default(),
})) }))
} }
@ -482,7 +480,6 @@ impl VulkanDmaBufImageTemplate {
}), }),
format: self.dmabuf.format, format: self.dmabuf.format,
is_undefined: Cell::new(true), is_undefined: Cell::new(true),
resv: Default::default(),
})) }))
} }
} }
@ -528,8 +525,14 @@ impl GfxFramebuffer for VulkanImage {
(self.width as _, self.height as _) (self.width as _, self.height as _)
} }
fn render(&self, ops: Vec<GfxApiOpt>, clear: Option<&Color>) { fn render(
self.renderer.execute(self, &ops, clear).unwrap(); &self,
ops: Vec<GfxApiOpt>,
clear: Option<&Color>,
) -> Result<Option<SyncFile>, GfxError> {
self.renderer
.execute(self, &ops, clear)
.map_err(|e| e.into())
} }
fn copy_to_shm( fn copy_to_shm(
@ -587,10 +590,6 @@ impl GfxTexture for VulkanImage {
} }
} }
fn reservations(&self) -> &TextureReservations {
&self.resv
}
fn format(&self) -> &'static Format { fn format(&self) -> &'static Format {
self.format self.format
} }

View file

@ -146,8 +146,7 @@ impl Drop for VulkanInstance {
const REQUIRED_INSTANCE_EXTENSIONS: &[&CStr] = &[ExtDebugUtilsFn::name()]; const REQUIRED_INSTANCE_EXTENSIONS: &[&CStr] = &[ExtDebugUtilsFn::name()];
const VALIDATION_LAYER: &CStr = const VALIDATION_LAYER: &CStr = c"VK_LAYER_KHRONOS_validation";
unsafe { CStr::from_bytes_with_nul_unchecked(b"VK_LAYER_KHRONOS_validation\0") };
pub type Extensions = AHashMap<CString, u32>; pub type Extensions = AHashMap<CString, u32>;

View file

@ -2,7 +2,10 @@ use {
crate::{ crate::{
async_engine::SpawnedFuture, async_engine::SpawnedFuture,
format::Format, format::Format,
gfx_api::{GfxApiOpt, GfxFormat, GfxFramebuffer, GfxTexture}, gfx_api::{
AcquireSync, BufferResv, BufferResvUser, GfxApiOpt, GfxFormat, GfxFramebuffer,
GfxTexture, ReleaseSync, SyncFile,
},
gfx_apis::vulkan::{ gfx_apis::vulkan::{
allocator::VulkanAllocator, allocator::VulkanAllocator,
command::{VulkanCommandBuffer, VulkanCommandPool}, command::{VulkanCommandBuffer, VulkanCommandPool},
@ -21,10 +24,7 @@ use {
io_uring::IoUring, io_uring::IoUring,
theme::Color, theme::Color,
utils::{copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, stack::Stack}, utils::{copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, stack::Stack},
video::dmabuf::{ video::dmabuf::{dma_buf_export_sync_file, DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE},
dma_buf_export_sync_file, dma_buf_import_sync_file, DMA_BUF_SYNC_READ,
DMA_BUF_SYNC_WRITE,
},
}, },
ahash::AHashMap, ahash::AHashMap,
ash::{ ash::{
@ -66,6 +66,14 @@ pub struct VulkanRenderer {
pub(super) pending_frames: CopyHashMap<u64, Rc<PendingFrame>>, pub(super) pending_frames: CopyHashMap<u64, Rc<PendingFrame>>,
pub(super) allocator: Rc<VulkanAllocator>, pub(super) allocator: Rc<VulkanAllocator>,
pub(super) last_point: NumCell<u64>, pub(super) last_point: NumCell<u64>,
pub(super) buffer_resv_user: BufferResvUser,
}
pub(super) struct UsedTexture {
tex: Rc<VulkanImage>,
resv: Option<Rc<dyn BufferResv>>,
acquire_sync: AcquireSync,
release_sync: ReleaseSync,
} }
#[derive(Default)] #[derive(Default)]
@ -73,20 +81,20 @@ pub(super) struct Memory {
sample: Vec<Rc<VulkanImage>>, sample: Vec<Rc<VulkanImage>>,
flush: Vec<Rc<VulkanImage>>, flush: Vec<Rc<VulkanImage>>,
flush_staging: Vec<(Rc<VulkanImage>, VulkanStagingBuffer)>, flush_staging: Vec<(Rc<VulkanImage>, VulkanStagingBuffer)>,
textures: Vec<Rc<VulkanImage>>, textures: Vec<UsedTexture>,
image_barriers: Vec<ImageMemoryBarrier2>, image_barriers: Vec<ImageMemoryBarrier2>,
shm_barriers: Vec<BufferMemoryBarrier2>, shm_barriers: Vec<BufferMemoryBarrier2>,
wait_semaphores: Vec<Rc<VulkanSemaphore>>, wait_semaphores: Vec<Rc<VulkanSemaphore>>,
wait_semaphore_infos: Vec<SemaphoreSubmitInfo>, wait_semaphore_infos: Vec<SemaphoreSubmitInfo>,
release_fence: Option<Rc<VulkanFence>>, release_fence: Option<Rc<VulkanFence>>,
release_syncfile: Option<Rc<OwnedFd>>, release_sync_file: Option<SyncFile>,
} }
pub(super) struct PendingFrame { pub(super) struct PendingFrame {
point: u64, point: u64,
renderer: Rc<VulkanRenderer>, renderer: Rc<VulkanRenderer>,
cmd: Cell<Option<Rc<VulkanCommandBuffer>>>, cmd: Cell<Option<Rc<VulkanCommandBuffer>>>,
_textures: Vec<Rc<VulkanImage>>, _textures: Vec<UsedTexture>,
_staging: Vec<(Rc<VulkanImage>, VulkanStagingBuffer)>, _staging: Vec<(Rc<VulkanImage>, VulkanStagingBuffer)>,
wait_semaphores: Cell<Vec<Rc<VulkanSemaphore>>>, wait_semaphores: Cell<Vec<Rc<VulkanSemaphore>>>,
waiter: Cell<Option<SpawnedFuture<()>>>, waiter: Cell<Option<SpawnedFuture<()>>>,
@ -157,6 +165,7 @@ impl VulkanDevice {
pending_frames: Default::default(), pending_frames: Default::default(),
allocator, allocator,
last_point: Default::default(), last_point: Default::default(),
buffer_resv_user: Default::default(),
})) }))
} }
} }
@ -177,7 +186,12 @@ impl VulkanRenderer {
} }
} }
} }
memory.textures.push(tex); memory.textures.push(UsedTexture {
tex,
resv: c.buffer_resv.clone(),
acquire_sync: c.acquire_sync.clone(),
release_sync: c.release_sync,
});
} }
} }
} }
@ -530,14 +544,13 @@ impl VulkanRenderer {
let import = |infos: &mut Vec<SemaphoreSubmitInfoKHR>, let import = |infos: &mut Vec<SemaphoreSubmitInfoKHR>,
semaphores: &mut Vec<Rc<VulkanSemaphore>>, semaphores: &mut Vec<Rc<VulkanSemaphore>>,
img: &VulkanImage, img: &VulkanImage,
sync: &AcquireSync,
flag: u32| flag: u32|
-> Result<(), VulkanError> { -> Result<(), VulkanError> {
if let VulkanImageMemory::DmaBuf(buf) = &img.ty { if let VulkanImageMemory::DmaBuf(buf) = &img.ty {
for plane in &buf.template.dmabuf.planes { let mut import_sync_file = |fd: OwnedFd| -> Result<(), VulkanError> {
let fd = dma_buf_export_sync_file(&plane.fd, flag)
.map_err(VulkanError::IoctlExportSyncFile)?;
let semaphore = self.allocate_semaphore()?; let semaphore = self.allocate_semaphore()?;
semaphore.import_syncfile(fd)?; semaphore.import_sync_file(fd)?;
infos.push( infos.push(
SemaphoreSubmitInfo::builder() SemaphoreSubmitInfo::builder()
.semaphore(semaphore.semaphore) .semaphore(semaphore.semaphore)
@ -545,6 +558,23 @@ impl VulkanRenderer {
.build(), .build(),
); );
semaphores.push(semaphore); semaphores.push(semaphore);
Ok(())
};
match sync {
AcquireSync::None => {}
AcquireSync::Implicit { .. } => {
for plane in &buf.template.dmabuf.planes {
let fd = dma_buf_export_sync_file(&plane.fd, flag)
.map_err(VulkanError::IoctlExportSyncFile)?;
import_sync_file(fd)?;
}
}
AcquireSync::SyncFile { sync_file } => {
let fd = uapi::fcntl_dupfd_cloexec(sync_file.raw(), 0)
.map_err(|e| VulkanError::Dupfd(e.into()))?;
import_sync_file(fd)?;
}
AcquireSync::Unnecessary => {}
} }
} }
Ok(()) Ok(())
@ -553,7 +583,8 @@ impl VulkanRenderer {
import( import(
&mut memory.wait_semaphore_infos, &mut memory.wait_semaphore_infos,
&mut memory.wait_semaphores, &mut memory.wait_semaphores,
texture, &texture.tex,
&texture.acquire_sync,
DMA_BUF_SYNC_READ, DMA_BUF_SYNC_READ,
)?; )?;
} }
@ -561,33 +592,43 @@ impl VulkanRenderer {
&mut memory.wait_semaphore_infos, &mut memory.wait_semaphore_infos,
&mut memory.wait_semaphores, &mut memory.wait_semaphores,
fb, fb,
&AcquireSync::Implicit,
DMA_BUF_SYNC_WRITE, DMA_BUF_SYNC_WRITE,
)?; )?;
Ok(()) Ok(())
} }
fn import_release_semaphore(&self, fb: &VulkanImage) { fn import_release_semaphore(&self, fb: &VulkanImage) {
let memory = self.memory.borrow(); let memory = &mut *self.memory.borrow_mut();
let syncfile = match memory.release_syncfile.as_ref() { let sync_file = match memory.release_sync_file.as_ref() {
Some(syncfile) => syncfile, Some(sync_file) => sync_file,
_ => return, _ => return,
}; };
let import = |img: &VulkanImage, flag: u32| { let import =
if let VulkanImageMemory::DmaBuf(buf) = &img.ty { |img: &VulkanImage, sync: ReleaseSync, resv: Option<Rc<dyn BufferResv>>, flag: u32| {
for plane in &buf.template.dmabuf.planes { if sync == ReleaseSync::None {
let res = dma_buf_import_sync_file(&plane.fd, flag, &syncfile) return;
.map_err(VulkanError::IoctlImportSyncFile); }
if let Err(e) = res { if let Some(resv) = resv {
log::error!("Could not import syncfile into dmabuf: {}", ErrorFmt(e)); resv.set_sync_file(self.buffer_resv_user, sync_file);
log::warn!("Relying on implicit sync"); } else if sync == ReleaseSync::Implicit {
if let VulkanImageMemory::DmaBuf(buf) = &img.ty {
if let Err(e) = buf.template.dmabuf.import_sync_file(flag, sync_file) {
log::error!("Could not import sync file into dmabuf: {}", ErrorFmt(e));
log::warn!("Relying on implicit sync");
}
} }
} }
} };
}; for texture in &mut memory.textures {
for texture in &memory.textures { import(
import(texture, DMA_BUF_SYNC_WRITE); &texture.tex,
texture.release_sync,
texture.resv.take(),
DMA_BUF_SYNC_READ,
);
} }
import(fb, DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE); import(fb, ReleaseSync::Implicit, None, DMA_BUF_SYNC_WRITE);
} }
fn submit(&self, buf: CommandBuffer) -> Result<(), VulkanError> { fn submit(&self, buf: CommandBuffer) -> Result<(), VulkanError> {
@ -610,15 +651,15 @@ impl VulkanRenderer {
) )
.map_err(VulkanError::Submit)?; .map_err(VulkanError::Submit)?;
} }
let release_syncfile = match release_fence.export_syncfile() { let release_sync_file = match release_fence.export_sync_file() {
Ok(s) => Some(s), Ok(s) => Some(s),
Err(e) => { Err(e) => {
log::error!("Could not export syncfile from fence: {}", ErrorFmt(e)); log::error!("Could not export sync file from fence: {}", ErrorFmt(e));
None None
} }
}; };
memory.release_fence = Some(release_fence); memory.release_fence = Some(release_fence);
memory.release_syncfile = release_syncfile; memory.release_sync_file = release_sync_file;
Ok(()) Ok(())
} }
@ -650,7 +691,7 @@ impl VulkanRenderer {
}); });
self.pending_frames.set(frame.point, frame.clone()); self.pending_frames.set(frame.point, frame.clone());
let future = self.device.instance.eng.spawn(await_release( let future = self.device.instance.eng.spawn(await_release(
memory.release_syncfile.take(), memory.release_sync_file.clone(),
self.device.instance.ring.clone(), self.device.instance.ring.clone(),
frame.clone(), frame.clone(),
self.clone(), self.clone(),
@ -692,7 +733,15 @@ impl VulkanRenderer {
&[], &[],
true, true,
)?; )?;
(&*tmp_tex as &dyn GfxFramebuffer).copy_texture(&(tex.clone() as _), x, y); (&*tmp_tex as &dyn GfxFramebuffer)
.copy_texture(
&(tex.clone() as _),
AcquireSync::None,
ReleaseSync::None,
x,
y,
)
.map_err(VulkanError::GfxError)?;
self.read_all_pixels(&tmp_tex, stride, dst) self.read_all_pixels(&tmp_tex, stride, dst)
} }
@ -769,7 +818,7 @@ impl VulkanRenderer {
let fd = dma_buf_export_sync_file(&plane.fd, DMA_BUF_SYNC_READ) let fd = dma_buf_export_sync_file(&plane.fd, DMA_BUF_SYNC_READ)
.map_err(VulkanError::IoctlExportSyncFile)?; .map_err(VulkanError::IoctlExportSyncFile)?;
let semaphore = self.allocate_semaphore()?; let semaphore = self.allocate_semaphore()?;
semaphore.import_syncfile(fd)?; semaphore.import_sync_file(fd)?;
let semaphore_info = SemaphoreSubmitInfo::builder() let semaphore_info = SemaphoreSubmitInfo::builder()
.semaphore(semaphore.semaphore) .semaphore(semaphore.semaphore)
.stage_mask(PipelineStageFlags2::TOP_OF_PIPE) .stage_mask(PipelineStageFlags2::TOP_OF_PIPE)
@ -831,9 +880,9 @@ impl VulkanRenderer {
fb: &VulkanImage, fb: &VulkanImage,
opts: &[GfxApiOpt], opts: &[GfxApiOpt],
clear: Option<&Color>, clear: Option<&Color>,
) -> Result<(), VulkanError> { ) -> Result<Option<SyncFile>, VulkanError> {
let res = self.try_execute(fb, opts, clear); let res = self.try_execute(fb, opts, clear);
{ let sync_file = {
let mut memory = self.memory.borrow_mut(); let mut memory = self.memory.borrow_mut();
memory.flush.clear(); memory.flush.clear();
memory.textures.clear(); memory.textures.clear();
@ -841,9 +890,9 @@ impl VulkanRenderer {
memory.sample.clear(); memory.sample.clear();
memory.wait_semaphores.clear(); memory.wait_semaphores.clear();
memory.release_fence.take(); memory.release_fence.take();
memory.release_syncfile.take(); memory.release_sync_file.take()
} };
res res.map(|_| sync_file)
} }
fn allocate_command_buffer(&self) -> Result<Rc<VulkanCommandBuffer>, VulkanError> { fn allocate_command_buffer(&self) -> Result<Rc<VulkanCommandBuffer>, VulkanError> {
@ -964,14 +1013,14 @@ fn image_barrier() -> ImageMemoryBarrier2Builder<'static> {
} }
async fn await_release( async fn await_release(
syncfile: Option<Rc<OwnedFd>>, sync_file: Option<SyncFile>,
ring: Rc<IoUring>, ring: Rc<IoUring>,
frame: Rc<PendingFrame>, frame: Rc<PendingFrame>,
renderer: Rc<VulkanRenderer>, renderer: Rc<VulkanRenderer>,
) { ) {
let mut is_released = false; let mut is_released = false;
if let Some(syncfile) = syncfile { if let Some(sync_file) = sync_file {
if let Err(e) = ring.readable(&syncfile).await { if let Err(e) = ring.readable(&sync_file).await {
log::error!( log::error!(
"Could not wait for release semaphore to be signaled: {}", "Could not wait for release semaphore to be signaled: {}",
ErrorFmt(e) ErrorFmt(e)

View file

@ -36,9 +36,9 @@ impl VulkanDevice {
} }
impl VulkanSemaphore { impl VulkanSemaphore {
pub fn import_syncfile(&self, syncfile: OwnedFd) -> Result<(), VulkanError> { pub fn import_sync_file(&self, sync_file: OwnedFd) -> Result<(), VulkanError> {
let fd_info = ImportSemaphoreFdInfoKHR::builder() let fd_info = ImportSemaphoreFdInfoKHR::builder()
.fd(syncfile.raw()) .fd(sync_file.raw())
.flags(SemaphoreImportFlags::TEMPORARY) .flags(SemaphoreImportFlags::TEMPORARY)
.handle_type(ExternalSemaphoreHandleTypeFlags::SYNC_FD) .handle_type(ExternalSemaphoreHandleTypeFlags::SYNC_FD)
.semaphore(self.semaphore); .semaphore(self.semaphore);
@ -47,8 +47,8 @@ impl VulkanSemaphore {
.external_semaphore_fd .external_semaphore_fd
.import_semaphore_fd(&fd_info) .import_semaphore_fd(&fd_info)
}; };
mem::forget(syncfile);
res.map_err(VulkanError::ImportSyncFile)?; res.map_err(VulkanError::ImportSyncFile)?;
mem::forget(sync_file);
Ok(()) Ok(())
} }
} }

View file

@ -38,6 +38,8 @@ pub mod wp_content_type_v1;
pub mod wp_cursor_shape_device_v1; pub mod wp_cursor_shape_device_v1;
pub mod wp_cursor_shape_manager_v1; pub mod wp_cursor_shape_manager_v1;
pub mod wp_fractional_scale_manager_v1; pub mod wp_fractional_scale_manager_v1;
pub mod wp_linux_drm_syncobj_manager_v1;
pub mod wp_linux_drm_syncobj_timeline_v1;
pub mod wp_presentation; pub mod wp_presentation;
pub mod wp_presentation_feedback; pub mod wp_presentation_feedback;
pub mod wp_single_pixel_buffer_manager_v1; pub mod wp_single_pixel_buffer_manager_v1;

View file

@ -173,7 +173,7 @@ impl JayScreencast {
let mut buffer = self.buffers.borrow_mut(); let mut buffer = self.buffers.borrow_mut();
for (idx, buffer) in buffer.deref_mut().iter_mut().enumerate() { for (idx, buffer) in buffer.deref_mut().iter_mut().enumerate() {
if buffer.free { if buffer.free {
self.client.state.perform_screencopy( let res = self.client.state.perform_screencopy(
texture, texture,
&buffer.fb, &buffer.fb,
on.global.pos.get(), on.global.pos.get(),
@ -183,12 +183,20 @@ impl JayScreencast {
size, size,
on.global.persistent.transform.get(), on.global.persistent.transform.get(),
); );
self.client.event(Ready { match res {
self_id: self.id, Ok(_) => {
idx: idx as _, self.client.event(Ready {
}); self_id: self.id,
buffer.free = false; idx: idx as _,
return; });
buffer.free = false;
return;
}
Err(e) => {
log::error!("Could not perform screencopy: {}", ErrorFmt(e));
break;
}
}
} }
} }
self.missed_frame.set(true); self.missed_frame.set(true);

View file

@ -35,7 +35,7 @@ pub struct WlBuffer {
pub client: Rc<Client>, pub client: Rc<Client>,
pub rect: Rect, pub rect: Rect,
pub format: &'static Format, pub format: &'static Format,
dmabuf: Option<DmaBuf>, pub dmabuf: Option<DmaBuf>,
render_ctx_version: Cell<u32>, render_ctx_version: Cell<u32>,
pub storage: RefCell<Option<WlBufferStorage>>, pub storage: RefCell<Option<WlBufferStorage>>,
pub color: Option<Color>, pub color: Option<Color>,

View file

@ -18,6 +18,7 @@ use {
buffd::{MsgParser, MsgParserError}, buffd::{MsgParser, MsgParserError},
clonecell::CloneCell, clonecell::CloneCell,
copyhashmap::CopyHashMap, copyhashmap::CopyHashMap,
errorfmt::ErrorFmt,
linkedlist::LinkedList, linkedlist::LinkedList,
transform_ext::TransformExt, transform_ext::TransformExt,
}, },
@ -243,7 +244,7 @@ impl WlOutputGlobal {
if let Some(WlBufferStorage::Shm { mem, stride }) = if let Some(WlBufferStorage::Shm { mem, stride }) =
wl_buffer.storage.borrow_mut().deref() wl_buffer.storage.borrow_mut().deref()
{ {
self.state.perform_shm_screencopy( let res = self.state.perform_shm_screencopy(
tex, tex,
self.pos.get(), self.pos.get(),
x_off, x_off,
@ -255,6 +256,11 @@ impl WlOutputGlobal {
wl_buffer.format, wl_buffer.format,
Transform::None, Transform::None,
); );
if let Err(e) = res {
log::warn!("Could not perform shm screencopy: {}", ErrorFmt(e));
capture.send_failed();
continue;
}
} else { } else {
let fb = match wl_buffer.famebuffer.get() { let fb = match wl_buffer.famebuffer.get() {
Some(fb) => fb, Some(fb) => fb,
@ -264,7 +270,7 @@ impl WlOutputGlobal {
continue; continue;
} }
}; };
self.state.perform_screencopy( let res = self.state.perform_screencopy(
tex, tex,
&fb, &fb,
self.pos.get(), self.pos.get(),
@ -274,6 +280,11 @@ impl WlOutputGlobal {
size, size,
Transform::None, Transform::None,
); );
if let Err(e) = res {
log::warn!("Could not perform screencopy: {}", ErrorFmt(e));
capture.send_failed();
continue;
}
} }
if capture.with_damage.get() { if capture.with_damage.get() {
capture.send_damage(); capture.send_damage();

View file

@ -299,13 +299,21 @@ impl WlSeatGlobal {
if extents.intersects(&Rect::new_sized(-x_rel, -y_rel, width, height).unwrap()) { if extents.intersects(&Rect::new_sized(-x_rel, -y_rel, width, height).unwrap()) {
if render { if render {
let buffer = hc.get_buffer(); let buffer = hc.get_buffer();
buffer.render_hardware_cursor( let res = buffer.render_hardware_cursor(
cursor.deref(), cursor.deref(),
&self.state, &self.state,
scale, scale,
transform, transform,
); );
hc.swap_buffer(); match res {
Ok(sync_file) => {
hc.set_sync_file(sync_file);
hc.swap_buffer();
}
Err(e) => {
log::error!("Could not render hardware cursor: {}", ErrorFmt(e));
}
}
} }
hc.set_enabled(true); hc.set_enabled(true);
let mode = output.global.mode.get(); let mode = output.global.mode.get();

View file

@ -1,7 +1,9 @@
pub mod commit_timeline;
pub mod cursor; pub mod cursor;
pub mod ext_session_lock_surface_v1; pub mod ext_session_lock_surface_v1;
pub mod wl_subsurface; pub mod wl_subsurface;
pub mod wp_fractional_scale_v1; pub mod wp_fractional_scale_v1;
pub mod wp_linux_drm_syncobj_surface_v1;
pub mod wp_tearing_control_v1; pub mod wp_tearing_control_v1;
pub mod wp_viewport; pub mod wp_viewport;
pub mod x_surface; pub mod x_surface;
@ -16,7 +18,7 @@ use {
client::{Client, ClientError, RequestParser}, client::{Client, ClientError, RequestParser},
drm_feedback::DrmFeedback, drm_feedback::DrmFeedback,
fixed::Fixed, fixed::Fixed,
gfx_api::SampleRect, gfx_api::{AcquireSync, BufferResv, BufferResvUser, ReleaseSync, SampleRect, SyncFile},
ifs::{ ifs::{
wl_buffer::WlBuffer, wl_buffer::WlBuffer,
wl_callback::WlCallback, wl_callback::WlCallback,
@ -25,11 +27,16 @@ use {
NodeSeatState, SeatId, WlSeatGlobal, NodeSeatState, SeatId, WlSeatGlobal,
}, },
wl_surface::{ wl_surface::{
cursor::CursorSurface, wl_subsurface::WlSubsurface, commit_timeline::{ClearReason, CommitTimeline, CommitTimelineError},
cursor::CursorSurface,
wl_subsurface::{PendingSubsurfaceData, SubsurfaceId, WlSubsurface},
wp_fractional_scale_v1::WpFractionalScaleV1, wp_fractional_scale_v1::WpFractionalScaleV1,
wp_tearing_control_v1::WpTearingControlV1, wp_viewport::WpViewport, wp_linux_drm_syncobj_surface_v1::WpLinuxDrmSyncobjSurfaceV1,
x_surface::XSurface, xdg_surface::XdgSurfaceError, wp_tearing_control_v1::WpTearingControlV1,
zwlr_layer_surface_v1::ZwlrLayerSurfaceV1Error, wp_viewport::WpViewport,
x_surface::XSurface,
xdg_surface::{PendingXdgSurfaceData, XdgSurfaceError},
zwlr_layer_surface_v1::{PendingLayerSurfaceData, ZwlrLayerSurfaceV1Error},
}, },
wp_content_type_v1::ContentType, wp_content_type_v1::ContentType,
wp_presentation_feedback::WpPresentationFeedback, wp_presentation_feedback::WpPresentationFeedback,
@ -48,11 +55,16 @@ use {
cell_ext::CellExt, cell_ext::CellExt,
clonecell::CloneCell, clonecell::CloneCell,
copyhashmap::CopyHashMap, copyhashmap::CopyHashMap,
errorfmt::ErrorFmt,
linkedlist::LinkedList, linkedlist::LinkedList,
numcell::NumCell, numcell::NumCell,
smallmap::SmallMap, smallmap::SmallMap,
transform_ext::TransformExt, transform_ext::TransformExt,
}, },
video::{
dmabuf::DMA_BUF_SYNC_READ,
drm::sync_obj::{SyncObj, SyncObjPoint},
},
wire::{ wire::{
wl_surface::*, WlOutputId, WlSurfaceId, ZwpIdleInhibitorV1Id, wl_surface::*, WlOutputId, WlSurfaceId, ZwpIdleInhibitorV1Id,
ZwpLinuxDmabufFeedbackV1Id, ZwpLinuxDmabufFeedbackV1Id,
@ -61,9 +73,11 @@ use {
xwayland::XWaylandEvent, xwayland::XWaylandEvent,
}, },
ahash::AHashMap, ahash::AHashMap,
isnt::std_1::primitive::IsntSliceExt,
jay_config::video::Transform, jay_config::video::Transform,
std::{ std::{
cell::{Cell, RefCell}, cell::{Cell, RefCell},
collections::hash_map::{Entry, OccupiedEntry},
fmt::{Debug, Formatter}, fmt::{Debug, Formatter},
mem, mem,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
@ -127,13 +141,78 @@ impl NodeVisitorBase for SurfaceSendPreferredTransformVisitor {
} }
} }
struct SurfaceBufferExplicitRelease {
sync_obj: Rc<SyncObj>,
point: SyncObjPoint,
}
pub struct SurfaceBuffer {
pub buffer: Rc<WlBuffer>,
sync_files: SmallMap<BufferResvUser, SyncFile, 1>,
pub sync: AcquireSync,
pub release_sync: ReleaseSync,
release: Option<SurfaceBufferExplicitRelease>,
}
impl Drop for SurfaceBuffer {
fn drop(&mut self) {
let sync_files = self.sync_files.take();
if let Some(release) = &self.release {
let Some(ctx) = self.buffer.client.state.render_ctx.get() else {
log::error!("Cannot signal release point because there is no render context");
return;
};
let ctx = ctx.sync_obj_ctx();
if sync_files.is_not_empty() {
let res = ctx.import_sync_files(
&release.sync_obj,
release.point,
sync_files.iter().map(|f| &f.1),
);
match res {
Ok(_) => return,
Err(e) => {
log::error!("Could not import sync files into sync obj: {}", ErrorFmt(e));
}
}
}
if let Err(e) = ctx.signal(&release.sync_obj, release.point) {
log::error!("Could not signal release point: {}", ErrorFmt(e));
}
return;
}
if let Some(dmabuf) = &self.buffer.dmabuf {
for (_, sync_file) in &sync_files {
if let Err(e) = dmabuf.import_sync_file(DMA_BUF_SYNC_READ, sync_file) {
log::error!("Could not import sync file: {}", ErrorFmt(e));
}
}
}
if !self.buffer.destroyed() {
self.buffer.send_release();
}
}
}
impl Debug for SurfaceBuffer {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SurfaceBuffer").finish_non_exhaustive()
}
}
impl BufferResv for SurfaceBuffer {
fn set_sync_file(&self, user: BufferResvUser, sync_file: &SyncFile) {
self.sync_files.insert(user, sync_file.clone());
}
}
pub struct WlSurface { pub struct WlSurface {
pub id: WlSurfaceId, pub id: WlSurfaceId,
pub node_id: SurfaceNodeId, pub node_id: SurfaceNodeId,
pub client: Rc<Client>, pub client: Rc<Client>,
visible: Cell<bool>, visible: Cell<bool>,
role: Cell<SurfaceRole>, role: Cell<SurfaceRole>,
pending: PendingState, pending: RefCell<Box<PendingState>>,
input_region: CloneCell<Option<Rc<Region>>>, input_region: CloneCell<Option<Rc<Region>>>,
opaque_region: Cell<Option<Rc<Region>>>, opaque_region: Cell<Option<Rc<Region>>>,
buffer_points: RefCell<BufferPoints>, buffer_points: RefCell<BufferPoints>,
@ -145,7 +224,7 @@ pub struct WlSurface {
pub extents: Cell<Rect>, pub extents: Cell<Rect>,
pub buffer_abs_pos: Cell<Rect>, pub buffer_abs_pos: Cell<Rect>,
pub need_extents_update: Cell<bool>, pub need_extents_update: Cell<bool>,
pub buffer: CloneCell<Option<Rc<WlBuffer>>>, pub buffer: CloneCell<Option<Rc<SurfaceBuffer>>>,
pub buf_x: NumCell<i32>, pub buf_x: NumCell<i32>,
pub buf_y: NumCell<i32>, pub buf_y: NumCell<i32>,
pub children: RefCell<Option<Box<ParentData>>>, pub children: RefCell<Option<Box<ParentData>>>,
@ -169,6 +248,9 @@ pub struct WlSurface {
pub has_content_type_manager: Cell<bool>, pub has_content_type_manager: Cell<bool>,
content_type: Cell<Option<ContentType>>, content_type: Cell<Option<ContentType>>,
pub drm_feedback: CopyHashMap<ZwpLinuxDmabufFeedbackV1Id, Rc<ZwpLinuxDmabufFeedbackV1>>, pub drm_feedback: CopyHashMap<ZwpLinuxDmabufFeedbackV1Id, Rc<ZwpLinuxDmabufFeedbackV1>>,
sync_obj_surface: CloneCell<Option<Rc<WpLinuxDrmSyncobjSurfaceV1>>>,
destroyed: Cell<bool>,
commit_timeline: CommitTimeline,
} }
impl Debug for WlSurface { impl Debug for WlSurface {
@ -185,12 +267,6 @@ struct BufferPoints {
y2: f32, y2: f32,
} }
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum CommitContext {
RootCommit,
ChildCommit,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum CommitAction { enum CommitAction {
ContinueCommit, ContinueCommit,
@ -198,13 +274,21 @@ enum CommitAction {
} }
trait SurfaceExt { trait SurfaceExt {
fn pre_commit(self: Rc<Self>, ctx: CommitContext) -> Result<CommitAction, WlSurfaceError> { fn commit_requested(self: Rc<Self>, pending: &mut Box<PendingState>) -> CommitAction {
let _ = ctx; let _ = pending;
Ok(CommitAction::ContinueCommit) CommitAction::ContinueCommit
} }
fn post_commit(self: Rc<Self>) { fn before_apply_commit(
// nothing self: Rc<Self>,
pending: &mut PendingState,
) -> Result<(), WlSurfaceError> {
let _ = pending;
Ok(())
}
fn after_apply_commit(self: Rc<Self>, pending: &mut PendingState) {
let _ = pending;
} }
fn is_some(&self) -> bool { fn is_some(&self) -> bool {
@ -246,6 +330,17 @@ trait SurfaceExt {
fn into_xsurface(self: Rc<Self>) -> Option<Rc<XSurface>> { fn into_xsurface(self: Rc<Self>) -> Option<Rc<XSurface>> {
None None
} }
fn consume_pending_child(
&self,
surface: &WlSurface,
child: SubsurfaceId,
consume: &mut dyn FnMut(
OccupiedEntry<SubsurfaceId, CommittedSubsurface>,
) -> Result<(), WlSurfaceError>,
) -> Result<(), WlSurfaceError> {
surface.pending.borrow_mut().consume_child(child, consume)
}
} }
pub struct NoneSurfaceExt; pub struct NoneSurfaceExt;
@ -258,20 +353,122 @@ impl SurfaceExt for NoneSurfaceExt {
#[derive(Default)] #[derive(Default)]
struct PendingState { struct PendingState {
buffer: Cell<Option<Option<Rc<WlBuffer>>>>, buffer: Option<Option<Rc<WlBuffer>>>,
offset: Cell<(i32, i32)>, offset: (i32, i32),
opaque_region: Cell<Option<Option<Rc<Region>>>>, opaque_region: Option<Option<Rc<Region>>>,
input_region: Cell<Option<Option<Rc<Region>>>>, input_region: Option<Option<Rc<Region>>>,
frame_request: RefCell<Vec<Rc<WlCallback>>>, frame_request: Vec<Rc<WlCallback>>,
damage: Cell<bool>, damage: bool,
presentation_feedback: RefCell<Vec<Rc<WpPresentationFeedback>>>, presentation_feedback: Vec<Rc<WpPresentationFeedback>>,
src_rect: Cell<Option<Option<[Fixed; 4]>>>, src_rect: Option<Option<[Fixed; 4]>>,
dst_size: Cell<Option<Option<(i32, i32)>>>, dst_size: Option<Option<(i32, i32)>>,
scale: Cell<Option<i32>>, scale: Option<i32>,
transform: Cell<Option<Transform>>, transform: Option<Transform>,
xwayland_serial: Cell<Option<u64>>, xwayland_serial: Option<u64>,
tearing: Cell<Option<bool>>, tearing: Option<bool>,
content_type: Cell<Option<Option<ContentType>>>, content_type: Option<Option<ContentType>>,
subsurface: Option<Box<PendingSubsurfaceData>>,
xdg_surface: Option<Box<PendingXdgSurfaceData>>,
layer_surface: Option<Box<PendingLayerSurfaceData>>,
subsurfaces: AHashMap<SubsurfaceId, CommittedSubsurface>,
acquire_point: Option<(Rc<SyncObj>, SyncObjPoint)>,
release_point: Option<(Rc<SyncObj>, SyncObjPoint)>,
explicit_sync: bool,
}
struct CommittedSubsurface {
subsurface: Rc<WlSubsurface>,
state: Box<PendingState>,
}
impl PendingState {
fn merge(&mut self, next: &mut Self, client: &Rc<Client>) {
// discard state
if next.buffer.is_some() {
if let Some((sync_obj, point)) = self.release_point.take() {
client.state.signal_point(&sync_obj, point);
} else if let Some(Some(prev)) = self.buffer.take() {
if !prev.destroyed() {
prev.send_release();
}
}
}
for fb in self.presentation_feedback.drain(..) {
fb.send_discarded();
let _ = client.remove_obj(&*fb);
}
// overwrite state
macro_rules! opt {
($name:ident) => {
if let Some(n) = next.$name.take() {
self.$name = Some(n);
}
};
}
opt!(buffer);
opt!(opaque_region);
opt!(input_region);
opt!(src_rect);
opt!(dst_size);
opt!(scale);
opt!(transform);
opt!(xwayland_serial);
opt!(tearing);
opt!(content_type);
opt!(acquire_point);
opt!(release_point);
{
let (dx1, dy1) = self.offset;
let (dx2, dy2) = mem::take(&mut next.offset);
self.offset = (dx1 + dx2, dy1 + dy2);
}
self.frame_request.append(&mut next.frame_request);
self.damage |= mem::take(&mut next.damage);
mem::swap(
&mut self.presentation_feedback,
&mut next.presentation_feedback,
);
macro_rules! merge_ext {
($name:ident) => {
if let Some(e) = &mut self.$name {
if let Some(n) = &mut next.$name {
e.merge(n);
}
} else {
self.$name = next.$name.take();
}
};
}
merge_ext!(subsurface);
merge_ext!(xdg_surface);
merge_ext!(layer_surface);
for (id, mut state) in next.subsurfaces.drain() {
match self.subsurfaces.entry(id) {
Entry::Occupied(mut o) => {
o.get_mut().state.merge(&mut state.state, client);
}
Entry::Vacant(v) => {
v.insert(state);
}
}
}
}
fn consume_child(
&mut self,
child: SubsurfaceId,
consume: impl FnOnce(
OccupiedEntry<SubsurfaceId, CommittedSubsurface>,
) -> Result<(), WlSurfaceError>,
) -> Result<(), WlSurfaceError> {
match self.subsurfaces.entry(child) {
Entry::Occupied(oe) => consume(oe),
_ => Ok(()),
}
}
} }
#[derive(Default)] #[derive(Default)]
@ -330,6 +527,9 @@ impl WlSurface {
has_content_type_manager: Default::default(), has_content_type_manager: Default::default(),
content_type: Default::default(), content_type: Default::default(),
drm_feedback: Default::default(), drm_feedback: Default::default(),
sync_obj_surface: Default::default(),
destroyed: Cell::new(false),
commit_timeline: client.commit_timelines.create_timeline(),
} }
} }
@ -400,8 +600,8 @@ impl WlSurface {
pub fn add_presentation_feedback(&self, fb: &Rc<WpPresentationFeedback>) { pub fn add_presentation_feedback(&self, fb: &Rc<WpPresentationFeedback>) {
self.pending self.pending
.presentation_feedback
.borrow_mut() .borrow_mut()
.presentation_feedback
.push(fb.clone()); .push(fb.clone());
} }
@ -558,6 +758,7 @@ impl WlSurface {
fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> { fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> {
let _req: Destroy = self.parse(parser)?; let _req: Destroy = self.parse(parser)?;
self.commit_timeline.clear(ClearReason::Destroy);
self.unset_dnd_icons(); self.unset_dnd_icons();
self.unset_cursors(); self.unset_cursors();
self.ext.get().on_surface_destroy()?; self.ext.get().on_surface_destroy()?;
@ -571,11 +772,7 @@ impl WlSurface {
} }
*children = None; *children = None;
} }
if let Some(buffer) = self.buffer.set(None) { self.buffer.set(None);
if !buffer.destroyed() {
buffer.send_release();
}
}
if let Some(xwayland_serial) = self.xwayland_serial.get() { if let Some(xwayland_serial) = self.xwayland_serial.get() {
self.client self.client
.surfaces_by_xwayland_serial .surfaces_by_xwayland_serial
@ -586,30 +783,32 @@ impl WlSurface {
self.client.remove_obj(self)?; self.client.remove_obj(self)?;
self.idle_inhibitors.clear(); self.idle_inhibitors.clear();
self.constraints.take(); self.constraints.take();
self.destroyed.set(true);
Ok(()) Ok(())
} }
fn attach(self: &Rc<Self>, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> { fn attach(self: &Rc<Self>, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> {
let req: Attach = self.parse(parser)?; let req: Attach = self.parse(parser)?;
let pending = &mut *self.pending.borrow_mut();
if self.version >= OFFSET_SINCE { if self.version >= OFFSET_SINCE {
if req.x != 0 || req.y != 0 { if req.x != 0 || req.y != 0 {
return Err(WlSurfaceError::OffsetInAttach); return Err(WlSurfaceError::OffsetInAttach);
} }
} else { } else {
self.pending.offset.set((req.x, req.y)); pending.offset = (req.x, req.y);
} }
let buf = if req.buffer.is_some() { let buf = if req.buffer.is_some() {
Some(self.client.lookup(req.buffer)?) Some(self.client.lookup(req.buffer)?)
} else { } else {
None None
}; };
self.pending.buffer.set(Some(buf)); pending.buffer = Some(buf);
Ok(()) Ok(())
} }
fn damage(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> { fn damage(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> {
let _req: Damage = self.parse(parser)?; let _req: Damage = self.parse(parser)?;
self.pending.damage.set(true); self.pending.borrow_mut().damage = true;
Ok(()) Ok(())
} }
@ -618,7 +817,7 @@ impl WlSurface {
let cb = Rc::new(WlCallback::new(req.callback, &self.client)); let cb = Rc::new(WlCallback::new(req.callback, &self.client));
track!(self.client, cb); track!(self.client, cb);
self.client.add_client_obj(&cb)?; self.client.add_client_obj(&cb)?;
self.pending.frame_request.borrow_mut().push(cb); self.pending.borrow_mut().frame_request.push(cb);
Ok(()) Ok(())
} }
@ -629,7 +828,7 @@ impl WlSurface {
} else { } else {
None None
}; };
self.pending.opaque_region.set(Some(region)); self.pending.borrow_mut().opaque_region = Some(region);
Ok(()) Ok(())
} }
@ -640,39 +839,37 @@ impl WlSurface {
} else { } else {
None None
}; };
self.pending.input_region.set(Some(region)); self.pending.borrow_mut().input_region = Some(region);
Ok(()) Ok(())
} }
fn do_commit(self: &Rc<Self>, ctx: CommitContext) -> Result<(), WlSurfaceError> { fn apply_state(self: &Rc<Self>, pending: &mut PendingState) -> Result<(), WlSurfaceError> {
let ext = self.ext.get(); for (_, mut subsurface) in pending.subsurfaces.drain() {
if ext.clone().pre_commit(ctx)? == CommitAction::AbortCommit { subsurface
.subsurface
.surface
.apply_state(&mut subsurface.state)?;
}
if self.destroyed.get() {
return Ok(()); return Ok(());
} }
{ self.ext.get().before_apply_commit(pending)?;
let children = self.children.borrow();
if let Some(children) = children.deref() {
for child in children.subsurfaces.values() {
child.surface.do_commit(CommitContext::ChildCommit)?;
}
}
}
let mut scale_changed = false; let mut scale_changed = false;
if let Some(scale) = self.pending.scale.take() { if let Some(scale) = pending.scale.take() {
scale_changed = true; scale_changed = true;
self.buffer_scale.set(scale); self.buffer_scale.set(scale);
} }
let mut buffer_transform_changed = false; let mut buffer_transform_changed = false;
if let Some(transform) = self.pending.transform.take() { if let Some(transform) = pending.transform.take() {
buffer_transform_changed = true; buffer_transform_changed = true;
self.buffer_transform.set(transform); self.buffer_transform.set(transform);
} }
let mut viewport_changed = false; let mut viewport_changed = false;
if let Some(dst_size) = self.pending.dst_size.take() { if let Some(dst_size) = pending.dst_size.take() {
viewport_changed = true; viewport_changed = true;
self.dst_size.set(dst_size); self.dst_size.set(dst_size);
} }
if let Some(src_rect) = self.pending.src_rect.take() { if let Some(src_rect) = pending.src_rect.take() {
viewport_changed = true; viewport_changed = true;
self.src_rect.set(src_rect); self.src_rect.set(src_rect);
} }
@ -687,34 +884,30 @@ impl WlSurface {
} }
let mut buffer_changed = false; let mut buffer_changed = false;
let mut old_raw_size = None; let mut old_raw_size = None;
let (dx, dy) = self.pending.offset.take(); let (dx, dy) = mem::take(&mut pending.offset);
if let Some(buffer_change) = self.pending.buffer.take() { if let Some(buffer_change) = pending.buffer.take() {
buffer_changed = true; buffer_changed = true;
if let Some(buffer) = self.buffer.take() { if let Some(buffer) = self.buffer.take() {
old_raw_size = Some(buffer.rect); old_raw_size = Some(buffer.buffer.rect);
if !buffer.destroyed() {
'handle_release: {
if let Some(tex) = buffer.texture.get() {
let resv = tex.reservations();
if resv.has_reservation() {
let buffer = Rc::downgrade(&buffer);
resv.on_released(move || {
if let Some(buffer) = buffer.upgrade() {
if !buffer.destroyed() {
buffer.send_release();
}
}
});
break 'handle_release;
}
}
buffer.send_release();
}
}
} }
if let Some(buffer) = buffer_change { if let Some(buffer) = buffer_change {
buffer.update_texture_or_log(); buffer.update_texture_or_log();
self.buffer.set(Some(buffer)); let (sync, release_sync) = match pending.explicit_sync {
false => (AcquireSync::Implicit, ReleaseSync::Implicit),
true => (AcquireSync::Unnecessary, ReleaseSync::Explicit),
};
let release = pending
.release_point
.take()
.map(|(sync_obj, point)| SurfaceBufferExplicitRelease { sync_obj, point });
let surface_buffer = SurfaceBuffer {
buffer,
sync_files: Default::default(),
sync,
release_sync,
release,
};
self.buffer.set(Some(Rc::new(surface_buffer)));
self.buf_x.fetch_add(dx); self.buf_x.fetch_add(dx);
self.buf_y.fetch_add(dy); self.buf_y.fetch_add(dy);
if (dx, dy) != (0, 0) { if (dx, dy) != (0, 0) {
@ -764,8 +957,10 @@ impl WlSurface {
} }
if let Some(buffer) = self.buffer.get() { if let Some(buffer) = self.buffer.get() {
if new_size.is_none() { if new_size.is_none() {
let (mut width, mut height) = let (mut width, mut height) = self
self.buffer_transform.get().maybe_swap(buffer.rect.size()); .buffer_transform
.get()
.maybe_swap(buffer.buffer.rect.size());
let scale = self.buffer_scale.get(); let scale = self.buffer_scale.get();
if scale != 1 { if scale != 1 {
width = (width + scale - 1) / scale; width = (width + scale - 1) / scale;
@ -773,12 +968,14 @@ impl WlSurface {
} }
new_size = Some((width, height)); new_size = Some((width, height));
} }
if transform_changed || Some(buffer.rect) != old_raw_size { if transform_changed || Some(buffer.buffer.rect) != old_raw_size {
let (x1, y1, x2, y2) = if self.src_rect.is_none() { let (x1, y1, x2, y2) = if self.src_rect.is_none() {
(0.0, 0.0, 1.0, 1.0) (0.0, 0.0, 1.0, 1.0)
} else { } else {
let (width, height) = let (width, height) = self
self.buffer_transform.get().maybe_swap(buffer.rect.size()); .buffer_transform
.get()
.maybe_swap(buffer.buffer.rect.size());
let width = width as f32; let width = width as f32;
let height = height as f32; let height = height as f32;
let x1 = buffer_points.x1 / width; let x1 = buffer_points.x1 / width;
@ -806,34 +1003,32 @@ impl WlSurface {
self.buffer_abs_pos self.buffer_abs_pos
.set(self.buffer_abs_pos.get().with_size(width, height).unwrap()); .set(self.buffer_abs_pos.get().with_size(width, height).unwrap());
} }
{ self.frame_requests
let mut pfr = self.pending.frame_request.borrow_mut(); .borrow_mut()
self.frame_requests.borrow_mut().extend(pfr.drain(..)); .extend(pending.frame_request.drain(..));
}
{ {
let mut fbs = self.presentation_feedback.borrow_mut(); let mut fbs = self.presentation_feedback.borrow_mut();
for fb in fbs.drain(..) { for fb in fbs.drain(..) {
fb.send_discarded(); fb.send_discarded();
let _ = self.client.remove_obj(&*fb); let _ = self.client.remove_obj(&*fb);
} }
let mut pfbs = self.pending.presentation_feedback.borrow_mut(); mem::swap(fbs.deref_mut(), &mut pending.presentation_feedback);
mem::swap(fbs.deref_mut(), pfbs.deref_mut());
} }
{ {
if let Some(region) = self.pending.input_region.take() { if let Some(region) = pending.input_region.take() {
self.input_region.set(region); self.input_region.set(region);
} }
if let Some(region) = self.pending.opaque_region.take() { if let Some(region) = pending.opaque_region.take() {
self.opaque_region.set(region); self.opaque_region.set(region);
} }
} }
if let Some(tearing) = self.pending.tearing.take() { if let Some(tearing) = pending.tearing.take() {
self.tearing.set(tearing); self.tearing.set(tearing);
} }
if let Some(content_type) = self.pending.content_type.take() { if let Some(content_type) = pending.content_type.take() {
self.content_type.set(content_type); self.content_type.set(content_type);
} }
if let Some(xwayland_serial) = self.pending.xwayland_serial.take() { if let Some(xwayland_serial) = pending.xwayland_serial.take() {
self.xwayland_serial.set(Some(xwayland_serial)); self.xwayland_serial.set(Some(xwayland_serial));
self.client self.client
.surfaces_by_xwayland_serial .surfaces_by_xwayland_serial
@ -853,23 +1048,49 @@ impl WlSurface {
cursor.update_hardware_cursor(); cursor.update_hardware_cursor();
} }
} }
ext.post_commit(); self.ext.get().after_apply_commit(pending);
self.client.state.damage(); self.client.state.damage();
Ok(()) Ok(())
} }
fn commit(self: &Rc<Self>, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> { fn commit(self: &Rc<Self>, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> {
let _req: Commit = self.parse(parser)?; let _req: Commit = self.parse(parser)?;
self.do_commit(CommitContext::RootCommit)?; let ext = self.ext.get();
let pending = &mut *self.pending.borrow_mut();
self.verify_explicit_sync(pending)?;
if ext.commit_requested(pending) == CommitAction::ContinueCommit {
self.commit_timeline.commit(self, pending)?;
}
Ok(()) Ok(())
} }
fn verify_explicit_sync(&self, pending: &mut PendingState) -> Result<(), WlSurfaceError> {
pending.explicit_sync = self.sync_obj_surface.is_some();
if !pending.explicit_sync {
return Ok(());
}
let have_new_buffer = match &pending.buffer {
None => false,
Some(b) => b.is_some(),
};
match (
pending.release_point.is_some(),
pending.acquire_point.is_some(),
have_new_buffer,
) {
(true, true, true) => Ok(()),
(false, false, false) => Ok(()),
(_, _, true) => Err(WlSurfaceError::MissingSyncPoints),
(_, _, false) => Err(WlSurfaceError::UnexpectedSyncPoints),
}
}
fn set_buffer_transform(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> { fn set_buffer_transform(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> {
let req: SetBufferTransform = self.parse(parser)?; let req: SetBufferTransform = self.parse(parser)?;
let Some(tf) = Transform::from_wl(req.transform) else { let Some(tf) = Transform::from_wl(req.transform) else {
return Err(WlSurfaceError::UnknownBufferTransform(req.transform)); return Err(WlSurfaceError::UnknownBufferTransform(req.transform));
}; };
self.pending.transform.set(Some(tf)); self.pending.borrow_mut().transform = Some(tf);
Ok(()) Ok(())
} }
@ -878,19 +1099,19 @@ impl WlSurface {
if req.scale < 1 { if req.scale < 1 {
return Err(WlSurfaceError::NonPositiveBufferScale); return Err(WlSurfaceError::NonPositiveBufferScale);
} }
self.pending.scale.set(Some(req.scale)); self.pending.borrow_mut().scale = Some(req.scale);
Ok(()) Ok(())
} }
fn damage_buffer(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> { fn damage_buffer(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> {
let _req: DamageBuffer = self.parse(parser)?; let _req: DamageBuffer = self.parse(parser)?;
self.pending.damage.set(true); self.pending.borrow_mut().damage = true;
Ok(()) Ok(())
} }
fn offset(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> { fn offset(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> {
let req: Offset = self.parse(parser)?; let req: Offset = self.parse(parser)?;
self.pending.offset.set((req.x, req.y)); self.pending.borrow_mut().offset = (req.x, req.y);
Ok(()) Ok(())
} }
@ -1021,7 +1242,7 @@ impl WlSurface {
} }
pub fn set_content_type(&self, content_type: Option<ContentType>) { pub fn set_content_type(&self, content_type: Option<ContentType>) {
self.pending.content_type.set(Some(content_type)); self.pending.borrow_mut().content_type = Some(content_type);
} }
pub fn request_activation(&self) { pub fn request_activation(&self) {
@ -1035,6 +1256,18 @@ impl WlSurface {
consumer.send_feedback(fb); consumer.send_feedback(fb);
} }
} }
fn consume_pending_child(
&self,
child: SubsurfaceId,
mut consume: impl FnMut(
OccupiedEntry<SubsurfaceId, CommittedSubsurface>,
) -> Result<(), WlSurfaceError>,
) -> Result<(), WlSurfaceError> {
self.ext
.get()
.consume_pending_child(self, child, &mut consume)
}
} }
object_base! { object_base! {
@ -1064,13 +1297,14 @@ impl Object for WlSurface {
self.buffer.set(None); self.buffer.set(None);
self.toplevel.set(None); self.toplevel.set(None);
self.idle_inhibitors.clear(); self.idle_inhibitors.clear();
self.pending.presentation_feedback.borrow_mut().clear(); mem::take(self.pending.borrow_mut().deref_mut());
self.presentation_feedback.borrow_mut().clear(); self.presentation_feedback.borrow_mut().clear();
self.viewporter.take(); self.viewporter.take();
self.fractional_scale.take(); self.fractional_scale.take();
self.tearing_control.take(); self.tearing_control.take();
self.constraints.clear(); self.constraints.clear();
self.drm_feedback.clear(); self.drm_feedback.clear();
self.commit_timeline.clear(ClearReason::BreakLoops);
} }
} }
@ -1237,8 +1471,15 @@ pub enum WlSurfaceError {
ViewportOutsideBuffer, ViewportOutsideBuffer,
#[error("attach request must not contain offset")] #[error("attach request must not contain offset")]
OffsetInAttach, OffsetInAttach,
#[error(transparent)]
CommitTimelineError(Box<CommitTimelineError>),
#[error("Explicit sync buffer is attached but acquire or release points are not set")]
MissingSyncPoints,
#[error("No buffer is attached but acquire or release point is set")]
UnexpectedSyncPoints,
} }
efrom!(WlSurfaceError, ClientError); efrom!(WlSurfaceError, ClientError);
efrom!(WlSurfaceError, XdgSurfaceError); efrom!(WlSurfaceError, XdgSurfaceError);
efrom!(WlSurfaceError, ZwlrLayerSurfaceV1Error); efrom!(WlSurfaceError, ZwlrLayerSurfaceV1Error);
efrom!(WlSurfaceError, MsgParserError); efrom!(WlSurfaceError, MsgParserError);
efrom!(WlSurfaceError, CommitTimelineError);

View file

@ -0,0 +1,295 @@
use {
crate::{
ifs::wl_surface::{PendingState, WlSurface, WlSurfaceError},
utils::{
clonecell::CloneCell,
copyhashmap::CopyHashMap,
linkedlist::{LinkedList, LinkedNode, NodeRef},
numcell::NumCell,
},
video::drm::{
sync_obj::{SyncObj, SyncObjPoint},
wait_for_sync_obj::{SyncObjWaiter, WaitForSyncObj, WaitForSyncObjHandle},
DrmError,
},
},
isnt::std_1::primitive::IsntSliceExt,
smallvec::SmallVec,
std::{
cell::{Cell, RefCell},
mem,
ops::{Deref, DerefMut},
rc::Rc,
},
thiserror::Error,
};
const MAX_TIMELINE_DEPTH: usize = 256;
linear_ids!(CommitTimelineIds, CommitTimelineId, u64);
pub struct CommitTimelines {
next_id: CommitTimelineIds,
wfs: Rc<WaitForSyncObj>,
depth: NumCell<usize>,
gc: CopyHashMap<CommitTimelineId, LinkedList<Entry>>,
}
pub struct CommitTimeline {
shared: Rc<CommitTimelines>,
own_timeline: Rc<Inner>,
effective_timeline: CloneCell<Rc<Inner>>,
effective_timeline_id: Cell<CommitTimelineId>,
}
struct Inner {
id: CommitTimelineId,
entries: LinkedList<Entry>,
}
fn add_entry(
list: &LinkedList<Entry>,
shared: &Rc<CommitTimelines>,
kind: EntryKind,
) -> NodeRef<Entry> {
shared.depth.fetch_add(1);
let link = list.add_last(Entry {
link: Cell::new(None),
shared: shared.clone(),
kind,
});
let noderef = link.to_ref();
noderef.link.set(Some(link));
noderef
}
#[derive(Debug, Error)]
pub enum CommitTimelineError {
#[error(transparent)]
ImmediateCommit(WlSurfaceError),
#[error("Could not apply a delayed commit")]
DelayedCommit(#[source] WlSurfaceError),
#[error("Could not register a wait")]
RegisterWait(#[source] DrmError),
#[error("Syncobj wait failed")]
Wait(#[source] DrmError),
#[error("The client has too many pending commits")]
Depth,
}
impl CommitTimelines {
pub fn new(wfs: &Rc<WaitForSyncObj>) -> Self {
Self {
next_id: Default::default(),
depth: NumCell::new(0),
wfs: wfs.clone(),
gc: Default::default(),
}
}
pub fn create_timeline(self: &Rc<Self>) -> CommitTimeline {
let id = self.next_id.next();
let timeline = Rc::new(Inner {
id,
entries: Default::default(),
});
CommitTimeline {
shared: self.clone(),
own_timeline: timeline.clone(),
effective_timeline: CloneCell::new(timeline),
effective_timeline_id: Cell::new(id),
}
}
pub fn clear(&self) {
for (_, list) in self.gc.lock().drain() {
break_loops(&list);
}
}
}
pub enum ClearReason {
BreakLoops,
Destroy,
}
fn break_loops(list: &LinkedList<Entry>) {
for entry in list.iter() {
entry.link.take();
}
}
impl CommitTimeline {
pub fn clear(&self, reason: ClearReason) {
match reason {
ClearReason::BreakLoops => break_loops(&self.own_timeline.entries),
ClearReason::Destroy => {
if self.own_timeline.entries.is_not_empty() {
let list = LinkedList::new();
list.append_all(&self.own_timeline.entries);
add_entry(&list, &self.shared, EntryKind::Gc(self.own_timeline.id));
self.shared.gc.set(self.own_timeline.id, list);
}
}
}
}
pub(super) fn commit(
&self,
surface: &Rc<WlSurface>,
pending: &mut Box<PendingState>,
) -> Result<(), CommitTimelineError> {
let mut points = SmallVec::new();
consume_acquire_points(pending, &mut points);
if points.is_empty() && self.own_timeline.entries.is_empty() {
return surface
.apply_state(pending)
.map_err(CommitTimelineError::ImmediateCommit);
}
if self.shared.depth.get() >= MAX_TIMELINE_DEPTH {
return Err(CommitTimelineError::Depth);
}
set_effective_timeline(self, pending, &self.own_timeline);
let noderef = add_entry(
&self.own_timeline.entries,
&self.shared,
EntryKind::Commit(Commit {
surface: surface.clone(),
pending: RefCell::new(mem::take(pending)),
sync_obj: NumCell::new(points.len()),
wait_handles: Cell::new(Default::default()),
}),
);
if points.is_not_empty() {
let mut wait_handles = SmallVec::new();
let noderef = Rc::new(noderef);
for (sync_obj, point) in points {
let handle = self
.shared
.wfs
.wait(&sync_obj, point, true, noderef.clone())
.map_err(CommitTimelineError::RegisterWait)?;
wait_handles.push(handle);
}
let EntryKind::Commit(commit) = &noderef.kind else {
unreachable!();
};
commit.wait_handles.set(wait_handles);
}
Ok(())
}
}
impl SyncObjWaiter for NodeRef<Entry> {
fn done(self: Rc<Self>, result: Result<(), DrmError>) {
let EntryKind::Commit(commit) = &self.kind else {
unreachable!();
};
if let Err(e) = result {
commit.surface.client.error(CommitTimelineError::Wait(e));
return;
}
commit.sync_obj.fetch_sub(1);
if let Err(e) = flush_from(self.deref().clone()) {
commit
.surface
.client
.error(CommitTimelineError::DelayedCommit(e));
}
}
}
struct Entry {
link: Cell<Option<LinkedNode<Entry>>>,
shared: Rc<CommitTimelines>,
kind: EntryKind,
}
enum EntryKind {
Commit(Commit),
Wait(Cell<bool>),
Signal(NodeRef<Entry>),
Gc(CommitTimelineId),
}
struct Commit {
surface: Rc<WlSurface>,
pending: RefCell<Box<PendingState>>,
sync_obj: NumCell<usize>,
wait_handles: Cell<SmallVec<[WaitForSyncObjHandle; 1]>>,
}
fn flush_from(mut point: NodeRef<Entry>) -> Result<(), WlSurfaceError> {
let mut gc_list = None;
while point.maybe_apply(&mut gc_list)? {
point.shared.depth.fetch_sub(1);
let _link = point.link.take();
match point.next() {
None => break,
Some(n) => point = n,
}
}
Ok(())
}
impl NodeRef<Entry> {
fn maybe_apply(&self, gc_list: &mut Option<LinkedList<Entry>>) -> Result<bool, WlSurfaceError> {
if self.prev().is_some() {
return Ok(false);
}
match &self.kind {
EntryKind::Commit(c) => {
if c.sync_obj.get() > 0 {
return Ok(false);
}
c.surface.apply_state(c.pending.borrow_mut().deref_mut())?;
Ok(true)
}
EntryKind::Wait(signaled) => Ok(signaled.get()),
EntryKind::Signal(s) => match &s.kind {
EntryKind::Wait(signaled) => {
signaled.set(true);
flush_from(s.clone())?;
Ok(true)
}
_ => unreachable!(),
},
EntryKind::Gc(id) => {
*gc_list = self.shared.gc.remove(id);
Ok(true)
}
}
}
}
type Point = (Rc<SyncObj>, SyncObjPoint);
fn consume_acquire_points(pending: &mut PendingState, points: &mut SmallVec<[Point; 1]>) {
if let Some(point) = pending.acquire_point.take() {
points.push(point);
}
for ss in pending.subsurfaces.values_mut() {
consume_acquire_points(&mut ss.state, points);
}
}
fn set_effective_timeline(
timeline: &CommitTimeline,
pending: &PendingState,
effective: &Rc<Inner>,
) {
if timeline.effective_timeline_id.replace(effective.id) != effective.id {
let prev = timeline.effective_timeline.set(effective.clone());
if prev.entries.is_not_empty() {
let noderef = add_entry(
&effective.entries,
&timeline.shared,
EntryKind::Wait(Cell::new(false)),
);
add_entry(&prev.entries, &timeline.shared, EntryKind::Signal(noderef));
}
}
for ss in pending.subsurfaces.values() {
set_effective_timeline(&ss.subsurface.surface.commit_timeline, &ss.state, effective);
}
}

View file

@ -2,21 +2,25 @@ use {
crate::{ crate::{
client::ClientError, client::ClientError,
ifs::wl_surface::{ ifs::wl_surface::{
CommitAction, CommitContext, StackElement, SurfaceExt, SurfaceRole, WlSurface, CommitAction, CommittedSubsurface, PendingState, StackElement, SurfaceExt, SurfaceRole,
WlSurfaceError, WlSurfaceId, WlSurface, WlSurfaceError, WlSurfaceId,
}, },
leaks::Tracker, leaks::Tracker,
object::Object, object::Object,
rect::Rect, rect::Rect,
utils::{ utils::{
buffd::{MsgParser, MsgParserError}, buffd::{MsgParser, MsgParserError},
linkedlist::LinkedNode, clonecell::CloneCell,
linkedlist::{LinkedNode, NodeRef},
numcell::NumCell, numcell::NumCell,
option_ext::OptionExt,
}, },
wire::{wl_subsurface::*, WlSubsurfaceId}, wire::{wl_subsurface::*, WlSubsurfaceId},
}, },
std::{ std::{
cell::{Cell, RefCell}, cell::{Cell, RefCell, RefMut},
collections::hash_map::{Entry, OccupiedEntry},
mem,
ops::Deref, ops::Deref,
rc::Rc, rc::Rc,
}, },
@ -28,36 +32,39 @@ const BAD_SURFACE: u32 = 0;
const MAX_SUBSURFACE_DEPTH: u32 = 100; const MAX_SUBSURFACE_DEPTH: u32 = 100;
linear_ids!(SubsurfaceIds, SubsurfaceId, u64);
pub struct WlSubsurface { pub struct WlSubsurface {
id: WlSubsurfaceId, id: WlSubsurfaceId,
unique_id: SubsurfaceId,
pub surface: Rc<WlSurface>, pub surface: Rc<WlSurface>,
pub(super) parent: Rc<WlSurface>, pub(super) parent: Rc<WlSurface>,
pub position: Cell<Rect>, pub position: Cell<Rect>,
sync_requested: Cell<bool>, sync_requested: Cell<bool>,
sync_ancestor: Cell<bool>, sync_ancestor: Cell<bool>,
node: RefCell<Option<LinkedNode<StackElement>>>, node: RefCell<Option<LinkedNode<StackElement>>>,
latest_node: CloneCell<Option<NodeRef<StackElement>>>,
depth: NumCell<u32>, depth: NumCell<u32>,
pending: PendingSubsurfaceData,
pub tracker: Tracker<Self>, pub tracker: Tracker<Self>,
} }
#[derive(Default)] #[derive(Default)]
struct PendingSubsurfaceData { pub struct PendingSubsurfaceData {
node: RefCell<Option<LinkedNode<StackElement>>>, node: Option<LinkedNode<StackElement>>,
position: Cell<Option<(i32, i32)>>, position: Option<(i32, i32)>,
} }
fn update_children_sync(surface: &WlSubsurface, sync: bool) { impl PendingSubsurfaceData {
let children = surface.surface.children.borrow(); pub fn merge(&mut self, next: &mut Self) {
if let Some(children) = &*children { macro_rules! opt {
for child in children.subsurfaces.values() { ($name:ident) => {
let was_sync = child.sync(); if let Some(n) = next.$name.take() {
child.sync_ancestor.set(sync); self.$name = Some(n);
let is_sync = child.sync(); }
if was_sync != is_sync { };
update_children_sync(child, sync);
}
} }
opt!(node);
opt!(position);
} }
} }
@ -85,18 +92,25 @@ impl WlSubsurface {
pub fn new(id: WlSubsurfaceId, surface: &Rc<WlSurface>, parent: &Rc<WlSurface>) -> Self { pub fn new(id: WlSubsurfaceId, surface: &Rc<WlSurface>, parent: &Rc<WlSurface>) -> Self {
Self { Self {
id, id,
unique_id: surface.client.state.subsurface_ids.next(),
surface: surface.clone(), surface: surface.clone(),
parent: parent.clone(), parent: parent.clone(),
position: Cell::new(Default::default()), position: Cell::new(Default::default()),
sync_requested: Cell::new(false), sync_requested: Cell::new(true),
sync_ancestor: Cell::new(false), sync_ancestor: Cell::new(false),
node: RefCell::new(None), node: RefCell::new(None),
latest_node: Default::default(),
depth: NumCell::new(0), depth: NumCell::new(0),
pending: Default::default(),
tracker: Default::default(), tracker: Default::default(),
} }
} }
fn pending(&self) -> RefMut<Box<PendingSubsurfaceData>> {
RefMut::map(self.surface.pending.borrow_mut(), |m| {
m.subsurface.get_or_insert_default_ext()
})
}
pub fn install(self: &Rc<Self>) -> Result<(), WlSubsurfaceError> { pub fn install(self: &Rc<Self>) -> Result<(), WlSubsurfaceError> {
if self.surface.id == self.parent.id { if self.surface.id == self.parent.id {
return Err(WlSubsurfaceError::OwnParent(self.surface.id)); return Err(WlSubsurfaceError::OwnParent(self.surface.id));
@ -128,7 +142,8 @@ impl WlSubsurface {
sub_surface: self.clone(), sub_surface: self.clone(),
}) })
}; };
*self.pending.node.borrow_mut() = Some(node); self.latest_node.set(Some(node.to_ref()));
self.pending().node = Some(node);
self.surface.set_toplevel(self.parent.toplevel.get()); self.surface.set_toplevel(self.parent.toplevel.get());
self.sync_ancestor.set(sync_ancestor); self.sync_ancestor.set(sync_ancestor);
self.depth.set(depth); self.depth.set(depth);
@ -140,8 +155,12 @@ impl WlSubsurface {
fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSubsurfaceError> { fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSubsurfaceError> {
let _req: Destroy = self.surface.client.parse(self, parser)?; let _req: Destroy = self.surface.client.parse(self, parser)?;
self.surface.unset_ext(); self.surface.unset_ext();
*self.pending.node.borrow_mut() = None; self.parent.consume_pending_child(self.unique_id, |oe| {
self.surface.apply_state(&mut oe.remove().state)
})?;
self.surface.pending.borrow_mut().subsurface.take();
*self.node.borrow_mut() = None; *self.node.borrow_mut() = None;
self.latest_node.take();
{ {
let mut children = self.parent.children.borrow_mut(); let mut children = self.parent.children.borrow_mut();
if let Some(children) = &mut *children { if let Some(children) = &mut *children {
@ -164,7 +183,7 @@ impl WlSubsurface {
fn set_position(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSubsurfaceError> { fn set_position(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSubsurfaceError> {
let req: SetPosition = self.surface.client.parse(self, parser)?; let req: SetPosition = self.surface.client.parse(self, parser)?;
self.pending.position.set(Some((req.x, req.y))); self.pending().position = Some((req.x, req.y));
Ok(()) Ok(())
} }
@ -188,19 +207,17 @@ impl WlSubsurface {
Some(s) => s, Some(s) => s,
_ => return Err(WlSubsurfaceError::NotASibling(sibling, self.surface.id)), _ => return Err(WlSubsurfaceError::NotASibling(sibling, self.surface.id)),
}; };
let node = match sibling.pending.node.borrow().deref() { let sibling_node = match sibling.latest_node.get() {
Some(n) => n.to_ref(), Some(n) => n,
_ => match sibling.node.borrow().deref() { _ => return Ok(()),
Some(n) => n.to_ref(),
_ => return Ok(()),
},
}; };
match above { match above {
true => node.append(element), true => sibling_node.append(element),
_ => node.prepend(element), _ => sibling_node.prepend(element),
} }
}; };
self.pending.node.borrow_mut().replace(node); self.latest_node.set(Some(node.to_ref()));
self.pending().node.replace(node);
} }
Ok(()) Ok(())
} }
@ -221,24 +238,56 @@ impl WlSubsurface {
self.sync_requested.get() || self.sync_ancestor.get() self.sync_requested.get() || self.sync_ancestor.get()
} }
fn update_sync(&self, sync: bool) { fn update_sync(&self, sync: bool) -> Result<(), WlSurfaceError> {
let was_sync = self.sync(); let was_sync = self.sync();
self.sync_requested.set(sync); self.sync_requested.set(sync);
let is_sync = self.sync(); let is_sync = self.sync();
if was_sync != is_sync { if was_sync != is_sync {
update_children_sync(self, is_sync); self.handle_sync_change(is_sync)?;
} }
Ok(())
}
fn handle_sync_change(&self, is_sync: bool) -> Result<(), WlSurfaceError> {
if !is_sync {
self.on_desync()?;
}
let children = self.surface.children.borrow();
if let Some(children) = &*children {
for child in children.subsurfaces.values() {
let was_sync = child.sync();
child.sync_ancestor.set(is_sync);
let is_sync = child.sync();
if was_sync != is_sync {
child.handle_sync_change(is_sync)?;
}
}
}
Ok(())
}
fn on_desync(&self) -> Result<(), WlSurfaceError> {
let committed = self
.parent
.pending
.borrow_mut()
.subsurfaces
.remove(&self.unique_id);
if let Some(mut ps) = committed {
self.surface.apply_state(&mut ps.state)?;
}
Ok(())
} }
fn set_sync(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSubsurfaceError> { fn set_sync(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSubsurfaceError> {
let _req: SetSync = self.surface.client.parse(self, parser)?; let _req: SetSync = self.surface.client.parse(self, parser)?;
self.update_sync(true); self.update_sync(true)?;
Ok(()) Ok(())
} }
fn set_desync(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSubsurfaceError> { fn set_desync(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSubsurfaceError> {
let _req: SetDesync = self.surface.client.parse(self, parser)?; let _req: SetDesync = self.surface.client.parse(self, parser)?;
self.update_sync(false); self.update_sync(false)?;
Ok(()) Ok(())
} }
} }
@ -256,31 +305,44 @@ object_base! {
impl Object for WlSubsurface { impl Object for WlSubsurface {
fn break_loops(&self) { fn break_loops(&self) {
*self.pending.node.borrow_mut() = None;
*self.node.borrow_mut() = None; *self.node.borrow_mut() = None;
self.latest_node.take();
} }
} }
simple_add_obj!(WlSubsurface); simple_add_obj!(WlSubsurface);
impl SurfaceExt for WlSubsurface { impl SurfaceExt for WlSubsurface {
fn pre_commit(self: Rc<Self>, ctx: CommitContext) -> Result<CommitAction, WlSurfaceError> { fn commit_requested(self: Rc<Self>, pending: &mut Box<PendingState>) -> CommitAction {
if ctx == CommitContext::RootCommit && self.sync() { if self.sync() {
log::debug!("Aborting commit due to sync"); let mut parent_pending = self.parent.pending.borrow_mut();
return Ok(CommitAction::AbortCommit); match parent_pending.subsurfaces.entry(self.unique_id) {
Entry::Occupied(mut o) => {
o.get_mut().state.merge(pending, &self.surface.client);
}
Entry::Vacant(v) => {
v.insert(CommittedSubsurface {
subsurface: self.clone(),
state: mem::take(&mut *pending),
});
}
}
return CommitAction::AbortCommit;
} }
Ok(CommitAction::ContinueCommit) CommitAction::ContinueCommit
} }
fn post_commit(self: Rc<Self>) { fn after_apply_commit(self: Rc<Self>, pending: &mut PendingState) {
if let Some(v) = self.pending.node.take() { if let Some(pending) = &mut pending.subsurface {
v.pending.set(false); if let Some(v) = pending.node.take() {
self.node.borrow_mut().replace(v); v.pending.set(false);
} self.node.borrow_mut().replace(v);
if let Some((x, y)) = self.pending.position.take() { }
self.position if let Some((x, y)) = pending.position.take() {
.set(self.surface.buffer_abs_pos.get().at_point(x, y)); self.position
self.parent.need_extents_update.set(true); .set(self.surface.buffer_abs_pos.get().at_point(x, y));
self.parent.need_extents_update.set(true);
}
} }
} }
@ -295,6 +357,21 @@ impl SurfaceExt for WlSubsurface {
fn into_subsurface(self: Rc<Self>) -> Option<Rc<WlSubsurface>> { fn into_subsurface(self: Rc<Self>) -> Option<Rc<WlSubsurface>> {
Some(self) Some(self)
} }
fn consume_pending_child(
&self,
surface: &WlSurface,
child: SubsurfaceId,
consume: &mut dyn FnMut(
OccupiedEntry<SubsurfaceId, CommittedSubsurface>,
) -> Result<(), WlSurfaceError>,
) -> Result<(), WlSurfaceError> {
self.parent
.consume_pending_child(self.unique_id, |mut oe| {
oe.get_mut().state.consume_child(child, &mut *consume)
})?;
surface.pending.borrow_mut().consume_child(child, consume)
}
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]

View file

@ -0,0 +1,103 @@
use {
crate::{
client::{Client, ClientError},
ifs::wl_surface::WlSurface,
leaks::Tracker,
object::Object,
utils::buffd::{MsgParser, MsgParserError},
video::drm::sync_obj::SyncObjPoint,
wire::{wp_linux_drm_syncobj_surface_v1::*, WpLinuxDrmSyncobjSurfaceV1Id},
},
std::rc::Rc,
thiserror::Error,
};
pub struct WpLinuxDrmSyncobjSurfaceV1 {
id: WpLinuxDrmSyncobjSurfaceV1Id,
client: Rc<Client>,
surface: Rc<WlSurface>,
pub tracker: Tracker<Self>,
}
impl WpLinuxDrmSyncobjSurfaceV1 {
pub fn new(
id: WpLinuxDrmSyncobjSurfaceV1Id,
client: &Rc<Client>,
surface: &Rc<WlSurface>,
) -> Self {
Self {
id,
client: client.clone(),
tracker: Default::default(),
surface: surface.clone(),
}
}
pub fn install(self: &Rc<Self>) -> Result<(), WpLinuxDrmSyncobjSurfaceV1Error> {
if self.surface.sync_obj_surface.is_some() {
return Err(WpLinuxDrmSyncobjSurfaceV1Error::Exists);
}
self.surface.sync_obj_surface.set(Some(self.clone()));
Ok(())
}
fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), WpLinuxDrmSyncobjSurfaceV1Error> {
let _req: Destroy = self.client.parse(self, parser)?;
self.surface.sync_obj_surface.take();
let pending = &mut *self.surface.pending.borrow_mut();
pending.release_point.take();
pending.acquire_point.take();
self.client.remove_obj(self)?;
Ok(())
}
fn set_acquire_point(
&self,
parser: MsgParser<'_, '_>,
) -> Result<(), WpLinuxDrmSyncobjSurfaceV1Error> {
let req: SetAcquirePoint = self.client.parse(self, parser)?;
let point = point(req.point_hi, req.point_lo);
let timeline = self.client.lookup(req.timeline)?;
self.surface.pending.borrow_mut().acquire_point = Some((timeline.sync_obj.clone(), point));
Ok(())
}
fn set_release_point(
&self,
parser: MsgParser<'_, '_>,
) -> Result<(), WpLinuxDrmSyncobjSurfaceV1Error> {
let req: SetReleasePoint = self.client.parse(self, parser)?;
let point = point(req.point_hi, req.point_lo);
let timeline = self.client.lookup(req.timeline)?;
self.surface.pending.borrow_mut().release_point = Some((timeline.sync_obj.clone(), point));
Ok(())
}
}
fn point(hi: u32, lo: u32) -> SyncObjPoint {
SyncObjPoint((hi as u64) << 32 | (lo as u64))
}
object_base! {
self = WpLinuxDrmSyncobjSurfaceV1;
DESTROY => destroy,
SET_ACQUIRE_POINT => set_acquire_point,
SET_RELEASE_POINT => set_release_point,
}
impl Object for WpLinuxDrmSyncobjSurfaceV1 {}
simple_add_obj!(WpLinuxDrmSyncobjSurfaceV1);
#[derive(Debug, Error)]
pub enum WpLinuxDrmSyncobjSurfaceV1Error {
#[error("Parsing failed")]
MsgParserError(#[source] Box<MsgParserError>),
#[error(transparent)]
ClientError(Box<ClientError>),
#[error("The surface already has a syncobj extension attached")]
Exists,
}
efrom!(WpLinuxDrmSyncobjSurfaceV1Error, MsgParserError);
efrom!(WpLinuxDrmSyncobjSurfaceV1Error, ClientError);

View file

@ -39,13 +39,13 @@ impl WpTearingControlV1 {
ASYNC => true, ASYNC => true,
_ => return Err(WpTearingControlV1Error::UnknownPresentationHint(req.hint)), _ => return Err(WpTearingControlV1Error::UnknownPresentationHint(req.hint)),
}; };
self.surface.pending.tearing.set(Some(tearing)); self.surface.pending.borrow_mut().tearing = Some(tearing);
Ok(()) Ok(())
} }
fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), WpTearingControlV1Error> { fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), WpTearingControlV1Error> {
let _req: Destroy = self.surface.client.parse(self, parser)?; let _req: Destroy = self.surface.client.parse(self, parser)?;
self.surface.pending.tearing.set(Some(false)); self.surface.pending.borrow_mut().tearing = Some(false);
self.surface.tearing_control.take(); self.surface.tearing_control.take();
self.surface.client.remove_obj(self)?; self.surface.client.remove_obj(self)?;
Ok(()) Ok(())

View file

@ -38,8 +38,9 @@ impl WpViewport {
fn destroy(&self, msg: MsgParser<'_, '_>) -> Result<(), WpViewportError> { fn destroy(&self, msg: MsgParser<'_, '_>) -> Result<(), WpViewportError> {
let _req: Destroy = self.client.parse(self, msg)?; let _req: Destroy = self.client.parse(self, msg)?;
self.surface.pending.src_rect.set(Some(None)); let pending = &mut *self.surface.pending.borrow_mut();
self.surface.pending.dst_size.set(Some(None)); pending.src_rect = Some(None);
pending.dst_size = Some(None);
self.surface.viewporter.take(); self.surface.viewporter.take();
self.client.remove_obj(self)?; self.client.remove_obj(self)?;
Ok(()) Ok(())
@ -56,7 +57,7 @@ impl WpViewport {
} }
Some([req.x, req.y, req.width, req.height]) Some([req.x, req.y, req.width, req.height])
}; };
self.surface.pending.src_rect.set(Some(rect)); self.surface.pending.borrow_mut().src_rect = Some(rect);
Ok(()) Ok(())
} }
@ -69,7 +70,7 @@ impl WpViewport {
} else { } else {
Some((req.width, req.height)) Some((req.width, req.height))
}; };
self.surface.pending.dst_size.set(Some(size)); self.surface.pending.borrow_mut().dst_size = Some(size);
Ok(()) Ok(())
} }
} }

View file

@ -2,7 +2,7 @@ use {
crate::{ crate::{
ifs::wl_surface::{ ifs::wl_surface::{
x_surface::{xwayland_surface_v1::XwaylandSurfaceV1, xwindow::Xwindow}, x_surface::{xwayland_surface_v1::XwaylandSurfaceV1, xwindow::Xwindow},
SurfaceExt, WlSurface, WlSurfaceError, PendingState, SurfaceExt, WlSurface, WlSurfaceError,
}, },
leaks::Tracker, leaks::Tracker,
tree::ToplevelNode, tree::ToplevelNode,
@ -23,7 +23,7 @@ pub struct XSurface {
} }
impl SurfaceExt for XSurface { impl SurfaceExt for XSurface {
fn post_commit(self: Rc<Self>) { fn after_apply_commit(self: Rc<Self>, _pending: &mut PendingState) {
if let Some(xwindow) = self.xwindow.get() { if let Some(xwindow) = self.xwindow.get() {
xwindow.map_status_changed(); xwindow.map_status_changed();
} }

View file

@ -32,7 +32,7 @@ impl XwaylandSurfaceV1 {
return Err(XwaylandSurfaceV1Error::NonMonotonicSerial); return Err(XwaylandSurfaceV1Error::NonMonotonicSerial);
} }
self.client.last_xwayland_serial.set(serial); self.client.last_xwayland_serial.set(serial);
self.x.surface.pending.xwayland_serial.set(Some(serial)); self.x.surface.pending.borrow_mut().xwayland_serial = Some(serial);
Ok(()) Ok(())
} }

View file

@ -10,7 +10,7 @@ use {
xdg_popup::{XdgPopup, XdgPopupError}, xdg_popup::{XdgPopup, XdgPopupError},
xdg_toplevel::{XdgToplevel, WM_CAPABILITIES_SINCE}, xdg_toplevel::{XdgToplevel, WM_CAPABILITIES_SINCE},
}, },
CommitAction, CommitContext, SurfaceExt, SurfaceRole, WlSurface, WlSurfaceError, PendingState, SurfaceExt, SurfaceRole, WlSurface, WlSurfaceError,
}, },
xdg_wm_base::XdgWmBase, xdg_wm_base::XdgWmBase,
}, },
@ -23,10 +23,15 @@ use {
clonecell::CloneCell, clonecell::CloneCell,
copyhashmap::CopyHashMap, copyhashmap::CopyHashMap,
numcell::NumCell, numcell::NumCell,
option_ext::OptionExt,
}, },
wire::{xdg_surface::*, WlSurfaceId, XdgPopupId, XdgSurfaceId}, wire::{xdg_surface::*, WlSurfaceId, XdgPopupId, XdgSurfaceId},
}, },
std::{cell::Cell, fmt::Debug, rc::Rc}, std::{
cell::{Cell, RefMut},
fmt::Debug,
rc::Rc,
},
thiserror::Error, thiserror::Error,
}; };
@ -65,14 +70,26 @@ pub struct XdgSurface {
pub absolute_desired_extents: Cell<Rect>, pub absolute_desired_extents: Cell<Rect>,
ext: CloneCell<Option<Rc<dyn XdgSurfaceExt>>>, ext: CloneCell<Option<Rc<dyn XdgSurfaceExt>>>,
popups: CopyHashMap<XdgPopupId, Rc<XdgPopup>>, popups: CopyHashMap<XdgPopupId, Rc<XdgPopup>>,
pending: PendingXdgSurfaceData,
pub workspace: CloneCell<Option<Rc<WorkspaceNode>>>, pub workspace: CloneCell<Option<Rc<WorkspaceNode>>>,
pub tracker: Tracker<Self>, pub tracker: Tracker<Self>,
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
struct PendingXdgSurfaceData { pub struct PendingXdgSurfaceData {
geometry: Cell<Option<Rect>>, geometry: Option<Rect>,
}
impl PendingXdgSurfaceData {
pub fn merge(&mut self, next: &mut Self) {
macro_rules! opt {
($name:ident) => {
if let Some(n) = next.$name.take() {
self.$name = Some(n);
}
};
}
opt!(geometry);
}
} }
pub trait XdgSurfaceExt: Debug { pub trait XdgSurfaceExt: Debug {
@ -103,7 +120,6 @@ impl XdgSurface {
absolute_desired_extents: Cell::new(Default::default()), absolute_desired_extents: Cell::new(Default::default()),
ext: Default::default(), ext: Default::default(),
popups: Default::default(), popups: Default::default(),
pending: Default::default(),
workspace: Default::default(), workspace: Default::default(),
tracker: Default::default(), tracker: Default::default(),
} }
@ -270,6 +286,12 @@ impl XdgSurface {
Ok(()) Ok(())
} }
fn pending(&self) -> RefMut<Box<PendingXdgSurfaceData>> {
RefMut::map(self.surface.pending.borrow_mut(), |p| {
p.xdg_surface.get_or_insert_default_ext()
})
}
fn set_window_geometry(&self, parser: MsgParser<'_, '_>) -> Result<(), XdgSurfaceError> { fn set_window_geometry(&self, parser: MsgParser<'_, '_>) -> Result<(), XdgSurfaceError> {
let req: SetWindowGeometry = self.surface.client.parse(self, parser)?; let req: SetWindowGeometry = self.surface.client.parse(self, parser)?;
if req.height == 0 && req.width == 0 { if req.height == 0 && req.width == 0 {
@ -280,7 +302,7 @@ impl XdgSurface {
return Err(XdgSurfaceError::NonPositiveWidthHeight); return Err(XdgSurfaceError::NonPositiveWidthHeight);
} }
let extents = Rect::new_sized(req.x, req.y, req.width, req.height).unwrap(); let extents = Rect::new_sized(req.x, req.y, req.width, req.height).unwrap();
self.pending.geometry.set(Some(extents)); self.pending().geometry = Some(extents);
Ok(()) Ok(())
} }
@ -357,7 +379,10 @@ impl Object for XdgSurface {
dedicated_add_obj!(XdgSurface, XdgSurfaceId, xdg_surfaces); dedicated_add_obj!(XdgSurface, XdgSurfaceId, xdg_surfaces);
impl SurfaceExt for XdgSurface { impl SurfaceExt for XdgSurface {
fn pre_commit(self: Rc<Self>, _ctx: CommitContext) -> Result<CommitAction, WlSurfaceError> { fn before_apply_commit(
self: Rc<Self>,
pending: &mut PendingState,
) -> Result<(), WlSurfaceError> {
{ {
let ase = self.acked_serial.get(); let ase = self.acked_serial.get();
let rse = self.requested_serial.get(); let rse = self.requested_serial.get();
@ -368,17 +393,18 @@ impl SurfaceExt for XdgSurface {
} }
self.send_configure(rse); self.send_configure(rse);
} }
// return CommitAction::AbortCommit;
} }
} }
if let Some(geometry) = self.pending.geometry.take() { if let Some(pending) = &mut pending.xdg_surface {
self.geometry.set(Some(geometry)); if let Some(geometry) = pending.geometry.take() {
self.update_extents(); self.geometry.set(Some(geometry));
self.update_extents();
}
} }
Ok(CommitAction::ContinueCommit) Ok(())
} }
fn post_commit(self: Rc<Self>) { fn after_apply_commit(self: Rc<Self>, _pending: &mut PendingState) {
if let Some(ext) = self.ext.get() { if let Some(ext) = self.ext.get() {
ext.post_commit(); ext.post_commit();
} }

View file

@ -646,7 +646,8 @@ impl ToplevelNodeBase for XdgToplevel {
impl XdgSurfaceExt for XdgToplevel { impl XdgSurfaceExt for XdgToplevel {
fn initial_configure(self: Rc<Self>) -> Result<(), XdgSurfaceError> { fn initial_configure(self: Rc<Self>) -> Result<(), XdgSurfaceError> {
self.send_configure(0, 0); let rect = self.xdg.absolute_desired_extents.get();
self.send_configure(rect.width(), rect.height());
Ok(()) Ok(())
} }

View file

@ -3,9 +3,7 @@ use {
client::{Client, ClientError}, client::{Client, ClientError},
ifs::{ ifs::{
wl_seat::NodeSeatState, wl_seat::NodeSeatState,
wl_surface::{ wl_surface::{PendingState, SurfaceExt, SurfaceRole, WlSurface, WlSurfaceError},
CommitAction, CommitContext, SurfaceExt, SurfaceRole, WlSurface, WlSurfaceError,
},
zwlr_layer_shell_v1::{ZwlrLayerShellV1, OVERLAY}, zwlr_layer_shell_v1::{ZwlrLayerShellV1, OVERLAY},
}, },
leaks::Tracker, leaks::Tracker,
@ -19,10 +17,16 @@ use {
cell_ext::CellExt, cell_ext::CellExt,
linkedlist::LinkedNode, linkedlist::LinkedNode,
numcell::NumCell, numcell::NumCell,
option_ext::OptionExt,
}, },
wire::{zwlr_layer_surface_v1::*, WlSurfaceId, ZwlrLayerSurfaceV1Id}, wire::{zwlr_layer_surface_v1::*, WlSurfaceId, ZwlrLayerSurfaceV1Id},
}, },
std::{cell::Cell, ops::Deref, rc::Rc}, std::{
cell::{Cell, RefMut},
mem,
ops::Deref,
rc::Rc,
},
thiserror::Error, thiserror::Error,
}; };
@ -50,7 +54,6 @@ pub struct ZwlrLayerSurfaceV1 {
pos: Cell<Rect>, pos: Cell<Rect>,
mapped: Cell<bool>, mapped: Cell<bool>,
layer: Cell<u32>, layer: Cell<u32>,
pending: Pending,
requested_serial: NumCell<u32>, requested_serial: NumCell<u32>,
acked_serial: Cell<Option<u32>>, acked_serial: Cell<Option<u32>>,
size: Cell<(i32, i32)>, size: Cell<(i32, i32)>,
@ -63,14 +66,33 @@ pub struct ZwlrLayerSurfaceV1 {
} }
#[derive(Default)] #[derive(Default)]
struct Pending { pub struct PendingLayerSurfaceData {
size: Cell<Option<(i32, i32)>>, size: Option<(i32, i32)>,
anchor: Cell<Option<u32>>, anchor: Option<u32>,
exclusive_zone: Cell<Option<i32>>, exclusive_zone: Option<i32>,
margin: Cell<Option<(i32, i32, i32, i32)>>, margin: Option<(i32, i32, i32, i32)>,
keyboard_interactivity: Cell<Option<u32>>, keyboard_interactivity: Option<u32>,
layer: Cell<Option<u32>>, layer: Option<u32>,
any: Cell<bool>, any: bool,
}
impl PendingLayerSurfaceData {
pub fn merge(&mut self, next: &mut Self) {
macro_rules! opt {
($name:ident) => {
if let Some(n) = next.$name.take() {
self.$name = Some(n);
}
};
}
opt!(size);
opt!(anchor);
opt!(exclusive_zone);
opt!(margin);
opt!(keyboard_interactivity);
opt!(layer);
self.any |= mem::take(&mut next.any);
}
} }
impl ZwlrLayerSurfaceV1 { impl ZwlrLayerSurfaceV1 {
@ -95,7 +117,6 @@ impl ZwlrLayerSurfaceV1 {
pos: Default::default(), pos: Default::default(),
mapped: Cell::new(false), mapped: Cell::new(false),
layer: Cell::new(layer), layer: Cell::new(layer),
pending: Default::default(),
requested_serial: Default::default(), requested_serial: Default::default(),
acked_serial: Cell::new(None), acked_serial: Cell::new(None),
size: Cell::new((0, 0)), size: Cell::new((0, 0)),
@ -131,15 +152,20 @@ impl ZwlrLayerSurfaceV1 {
self.client.event(Closed { self_id: self.id }); self.client.event(Closed { self_id: self.id });
} }
fn pending(&self) -> RefMut<Box<PendingLayerSurfaceData>> {
RefMut::map(self.surface.pending.borrow_mut(), |m| {
m.layer_surface.get_or_insert_default_ext()
})
}
fn set_size(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrLayerSurfaceV1Error> { fn set_size(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrLayerSurfaceV1Error> {
let req: SetSize = self.client.parse(self, parser)?; let req: SetSize = self.client.parse(self, parser)?;
if req.width > u16::MAX as u32 || req.height > u16::MAX as u32 { if req.width > u16::MAX as u32 || req.height > u16::MAX as u32 {
return Err(ZwlrLayerSurfaceV1Error::ExcessiveSize); return Err(ZwlrLayerSurfaceV1Error::ExcessiveSize);
} }
self.pending let mut pending = self.pending();
.size pending.size = Some((req.width as _, req.height as _));
.set(Some((req.width as _, req.height as _))); pending.any = true;
self.pending.any.set(true);
Ok(()) Ok(())
} }
@ -148,24 +174,25 @@ impl ZwlrLayerSurfaceV1 {
if req.anchor & !(LEFT | RIGHT | TOP | BOTTOM) != 0 { if req.anchor & !(LEFT | RIGHT | TOP | BOTTOM) != 0 {
return Err(ZwlrLayerSurfaceV1Error::UnknownAnchor(req.anchor)); return Err(ZwlrLayerSurfaceV1Error::UnknownAnchor(req.anchor));
} }
self.pending.anchor.set(Some(req.anchor)); let mut pending = self.pending();
self.pending.any.set(true); pending.anchor = Some(req.anchor);
pending.any = true;
Ok(()) Ok(())
} }
fn set_exclusive_zone(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrLayerSurfaceV1Error> { fn set_exclusive_zone(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrLayerSurfaceV1Error> {
let req: SetExclusiveZone = self.client.parse(self, parser)?; let req: SetExclusiveZone = self.client.parse(self, parser)?;
self.pending.exclusive_zone.set(Some(req.zone)); let mut pending = self.pending();
self.pending.any.set(true); pending.exclusive_zone = Some(req.zone);
pending.any = true;
Ok(()) Ok(())
} }
fn set_margin(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrLayerSurfaceV1Error> { fn set_margin(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrLayerSurfaceV1Error> {
let req: SetMargin = self.client.parse(self, parser)?; let req: SetMargin = self.client.parse(self, parser)?;
self.pending let mut pending = self.pending();
.margin pending.margin = Some((req.top, req.right, req.bottom, req.left));
.set(Some((req.top, req.right, req.bottom, req.left))); pending.any = true;
self.pending.any.set(true);
Ok(()) Ok(())
} }
@ -179,10 +206,9 @@ impl ZwlrLayerSurfaceV1 {
req.keyboard_interactivity, req.keyboard_interactivity,
)); ));
} }
self.pending let mut pending = self.pending();
.keyboard_interactivity pending.keyboard_interactivity = Some(req.keyboard_interactivity);
.set(Some(req.keyboard_interactivity)); pending.any = true;
self.pending.any.set(true);
Ok(()) Ok(())
} }
@ -210,29 +236,31 @@ impl ZwlrLayerSurfaceV1 {
if req.layer > OVERLAY { if req.layer > OVERLAY {
return Err(ZwlrLayerSurfaceV1Error::UnknownLayer(req.layer)); return Err(ZwlrLayerSurfaceV1Error::UnknownLayer(req.layer));
} }
self.pending.layer.set(Some(req.layer)); let mut pending = self.pending();
self.pending.any.set(true); pending.layer = Some(req.layer);
pending.any = true;
Ok(()) Ok(())
} }
fn pre_commit(&self) -> Result<(), ZwlrLayerSurfaceV1Error> { fn pre_commit(&self, pending: &mut PendingState) -> Result<(), ZwlrLayerSurfaceV1Error> {
let mut send_configure = self.pending.any.replace(false); let pending = pending.layer_surface.get_or_insert_default_ext();
if let Some(size) = self.pending.size.take() { let mut send_configure = mem::replace(&mut pending.any, false);
if let Some(size) = pending.size.take() {
self.size.set(size); self.size.set(size);
} }
if let Some(anchor) = self.pending.anchor.take() { if let Some(anchor) = pending.anchor.take() {
self.anchor.set(anchor); self.anchor.set(anchor);
} }
if let Some(ez) = self.pending.exclusive_zone.take() { if let Some(ez) = pending.exclusive_zone.take() {
self.exclusive_zone.set(ez); self.exclusive_zone.set(ez);
} }
if let Some(margin) = self.pending.margin.take() { if let Some(margin) = pending.margin.take() {
self.margin.set(margin); self.margin.set(margin);
} }
if let Some(ki) = self.pending.keyboard_interactivity.take() { if let Some(ki) = pending.keyboard_interactivity.take() {
self.keyboard_interactivity.set(ki); self.keyboard_interactivity.set(ki);
} }
if let Some(layer) = self.pending.layer.take() { if let Some(layer) = pending.layer.take() {
self.layer.set(layer); self.layer.set(layer);
} }
{ {
@ -313,15 +341,18 @@ impl ZwlrLayerSurfaceV1 {
} }
impl SurfaceExt for ZwlrLayerSurfaceV1 { impl SurfaceExt for ZwlrLayerSurfaceV1 {
fn pre_commit(self: Rc<Self>, _ctx: CommitContext) -> Result<CommitAction, WlSurfaceError> { fn before_apply_commit(
self.deref().pre_commit()?; self: Rc<Self>,
Ok(CommitAction::ContinueCommit) pending: &mut PendingState,
) -> Result<(), WlSurfaceError> {
self.deref().pre_commit(pending)?;
Ok(())
} }
fn post_commit(self: Rc<Self>) { fn after_apply_commit(self: Rc<Self>, _pending: &mut PendingState) {
let buffer = self.surface.buffer.get(); let buffer_is_some = self.surface.buffer.is_some();
if self.mapped.get() { if self.mapped.get() {
if buffer.is_none() { if !buffer_is_some {
self.destroy_node(); self.destroy_node();
} else { } else {
let pos = self.pos.get(); let pos = self.pos.get();
@ -330,7 +361,7 @@ impl SurfaceExt for ZwlrLayerSurfaceV1 {
self.compute_position(); self.compute_position();
} }
} }
} else if buffer.is_some() { } else if buffer_is_some {
let layer = &self.output.layers[self.layer.get() as usize]; let layer = &self.output.layers[self.layer.get() as usize];
self.link.set(Some(layer.add_last(self.clone()))); self.link.set(Some(layer.add_last(self.clone())));
self.mapped.set(true); self.mapped.set(true);

View file

@ -0,0 +1,130 @@
use {
crate::{
client::{Client, ClientError},
globals::{Global, GlobalName},
ifs::{
wl_surface::wp_linux_drm_syncobj_surface_v1::{
WpLinuxDrmSyncobjSurfaceV1, WpLinuxDrmSyncobjSurfaceV1Error,
},
wp_linux_drm_syncobj_timeline_v1::WpLinuxDrmSyncobjTimelineV1,
},
leaks::Tracker,
object::Object,
utils::buffd::{MsgParser, MsgParserError},
video::drm::sync_obj::SyncObj,
wire::{wp_linux_drm_syncobj_manager_v1::*, WpLinuxDrmSyncobjManagerV1Id},
},
std::rc::Rc,
thiserror::Error,
};
pub struct WpLinuxDrmSyncobjManagerV1Global {
pub name: GlobalName,
}
pub struct WpLinuxDrmSyncobjManagerV1 {
pub id: WpLinuxDrmSyncobjManagerV1Id,
pub client: Rc<Client>,
pub tracker: Tracker<Self>,
}
impl WpLinuxDrmSyncobjManagerV1Global {
pub fn new(name: GlobalName) -> Self {
Self { name }
}
fn bind_(
self: Rc<Self>,
id: WpLinuxDrmSyncobjManagerV1Id,
client: &Rc<Client>,
_version: u32,
) -> Result<(), WpLinuxDrmSyncobjManagerV1Error> {
let obj = Rc::new(WpLinuxDrmSyncobjManagerV1 {
id,
client: client.clone(),
tracker: Default::default(),
});
track!(client, obj);
client.add_client_obj(&obj)?;
Ok(())
}
}
global_base!(
WpLinuxDrmSyncobjManagerV1Global,
WpLinuxDrmSyncobjManagerV1,
WpLinuxDrmSyncobjManagerV1Error
);
impl Global for WpLinuxDrmSyncobjManagerV1Global {
fn singleton(&self) -> bool {
true
}
fn version(&self) -> u32 {
1
}
}
simple_add_global!(WpLinuxDrmSyncobjManagerV1Global);
impl WpLinuxDrmSyncobjManagerV1 {
fn destroy(&self, msg: MsgParser<'_, '_>) -> Result<(), WpLinuxDrmSyncobjManagerV1Error> {
let _req: Destroy = self.client.parse(self, msg)?;
self.client.remove_obj(self)?;
Ok(())
}
fn get_surface(&self, msg: MsgParser<'_, '_>) -> Result<(), WpLinuxDrmSyncobjManagerV1Error> {
let req: GetSurface = self.client.parse(self, msg)?;
let surface = self.client.lookup(req.surface)?;
let sync = Rc::new(WpLinuxDrmSyncobjSurfaceV1::new(
req.id,
&self.client,
&surface,
));
track!(self.client, sync);
sync.install()?;
self.client.add_client_obj(&sync)?;
Ok(())
}
fn import_timeline(
&self,
msg: MsgParser<'_, '_>,
) -> Result<(), WpLinuxDrmSyncobjManagerV1Error> {
let req: ImportTimeline = self.client.parse(self, msg)?;
let sync_obj = Rc::new(SyncObj::new(&req.fd));
let sync = Rc::new(WpLinuxDrmSyncobjTimelineV1::new(
req.id,
&self.client,
&sync_obj,
));
self.client.add_client_obj(&sync)?;
Ok(())
}
}
object_base! {
self = WpLinuxDrmSyncobjManagerV1;
DESTROY => destroy,
GET_SURFACE => get_surface,
IMPORT_TIMELINE => import_timeline,
}
impl Object for WpLinuxDrmSyncobjManagerV1 {}
simple_add_obj!(WpLinuxDrmSyncobjManagerV1);
#[derive(Debug, Error)]
pub enum WpLinuxDrmSyncobjManagerV1Error {
#[error("Parsing failed")]
MsgParserError(#[source] Box<MsgParserError>),
#[error(transparent)]
ClientError(Box<ClientError>),
#[error(transparent)]
WpLinuxDrmSyncobjSurfaceV1Error(#[from] WpLinuxDrmSyncobjSurfaceV1Error),
}
efrom!(WpLinuxDrmSyncobjManagerV1Error, MsgParserError);
efrom!(WpLinuxDrmSyncobjManagerV1Error, ClientError);

View file

@ -0,0 +1,64 @@
use {
crate::{
client::{Client, ClientError},
leaks::Tracker,
object::Object,
utils::buffd::{MsgParser, MsgParserError},
video::drm::sync_obj::SyncObj,
wire::{wp_linux_drm_syncobj_timeline_v1::*, WpLinuxDrmSyncobjTimelineV1Id},
},
std::rc::Rc,
thiserror::Error,
};
pub struct WpLinuxDrmSyncobjTimelineV1 {
id: WpLinuxDrmSyncobjTimelineV1Id,
client: Rc<Client>,
pub sync_obj: Rc<SyncObj>,
pub tracker: Tracker<Self>,
}
impl WpLinuxDrmSyncobjTimelineV1 {
pub fn new(
id: WpLinuxDrmSyncobjTimelineV1Id,
client: &Rc<Client>,
sync_obj: &Rc<SyncObj>,
) -> Self {
Self {
id,
client: client.clone(),
tracker: Default::default(),
sync_obj: sync_obj.clone(),
}
}
fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), WpLinuxDrmSyncobjTimelineV1Error> {
let _destroy: Destroy = self.client.parse(self, parser)?;
self.client.remove_obj(self)?;
Ok(())
}
}
object_base! {
self = WpLinuxDrmSyncobjTimelineV1;
DESTROY => destroy,
}
impl Object for WpLinuxDrmSyncobjTimelineV1 {}
dedicated_add_obj!(
WpLinuxDrmSyncobjTimelineV1,
WpLinuxDrmSyncobjTimelineV1Id,
timelines
);
#[derive(Debug, Error)]
pub enum WpLinuxDrmSyncobjTimelineV1Error {
#[error("Parsing failed")]
MsgParserError(#[source] Box<MsgParserError>),
#[error(transparent)]
ClientError(Box<ClientError>),
}
efrom!(WpLinuxDrmSyncobjTimelineV1Error, MsgParserError);
efrom!(WpLinuxDrmSyncobjTimelineV1Error, ClientError);

View file

@ -71,7 +71,7 @@ impl XdgToplevelDragV1 {
return Err(XdgToplevelDragV1Error::AlreadyDragged); return Err(XdgToplevelDragV1Error::AlreadyDragged);
} }
if let Some(prev) = self.toplevel.set(Some(toplevel)) { if let Some(prev) = self.toplevel.set(Some(toplevel)) {
if prev.xdg.surface.buffer.get().is_some() { if prev.xdg.surface.buffer.is_some() {
return Err(XdgToplevelDragV1Error::ToplevelAttached); return Err(XdgToplevelDragV1Error::ToplevelAttached);
} }
if prev.id != req.toplevel { if prev.id != req.toplevel {

View file

@ -37,6 +37,7 @@ async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
buffer.fill(Color::from_rgba_straight(255, 255, 255, 255)); buffer.fill(Color::from_rgba_straight(255, 255, 255, 255));
child.attach(buffer.id)?; child.attach(buffer.id)?;
child.commit()?;
parent.map().await?; parent.map().await?;
@ -45,10 +46,12 @@ async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
client.compare_screenshot("1").await?; client.compare_screenshot("1").await?;
sub.place_below(parent.surface.id)?; sub.place_below(parent.surface.id)?;
child.commit()?;
parent.map().await?; parent.map().await?;
client.compare_screenshot("2").await?; client.compare_screenshot("2").await?;
sub.place_above(parent.surface.id)?; sub.place_above(parent.surface.id)?;
child.commit()?;
parent.map().await?; parent.map().await?;
client.compare_screenshot("1").await?; client.compare_screenshot("1").await?;

View file

@ -29,6 +29,7 @@ async fn test(run: Rc<TestRun>) -> TestResult {
nss.set_position(100, 100)?; nss.set_position(100, 100)?;
let buffer = client.shm.create_buffer(100, 100)?; let buffer = client.shm.create_buffer(100, 100)?;
ns.attach(buffer.id)?; ns.attach(buffer.id)?;
ns.commit()?;
run.cfg.set_fullscreen(ds.seat.id(), true)?; run.cfg.set_fullscreen(ds.seat.id(), true)?;
client.sync().await; client.sync().await;

View file

@ -21,15 +21,14 @@ use {
ptl_screencast::{add_screencast_dbus_members, ScreencastSession}, ptl_screencast::{add_screencast_dbus_members, ScreencastSession},
}, },
utils::{ utils::{
buf::Buf,
clone3::{fork_with_pidfd, Forked}, clone3::{fork_with_pidfd, Forked},
copyhashmap::CopyHashMap, copyhashmap::CopyHashMap,
errorfmt::ErrorFmt, errorfmt::ErrorFmt,
line_logger::log_lines,
numcell::NumCell, numcell::NumCell,
oserror::OsError, oserror::OsError,
process_name::set_process_name, process_name::set_process_name,
run_toplevel::RunToplevel, run_toplevel::RunToplevel,
vecdeque_ext::VecDequeExt,
xrd::xrd, xrd::xrd,
}, },
video::dmabuf::DmaBufIds, video::dmabuf::DmaBufIds,
@ -38,7 +37,6 @@ use {
}, },
log::Level, log::Level,
std::{ std::{
collections::VecDeque,
rc::{Rc, Weak}, rc::{Rc, Weak},
sync::Arc, sync::Arc,
}, },
@ -104,25 +102,14 @@ impl PortalStartup {
let ring = ring.clone(); let ring = ring.clone();
let logger = logger.clone(); let logger = logger.clone();
async move { async move {
let mut buf = VecDeque::<u8>::new(); let res = log_lines(&ring, &self.logs, |left, right| {
let mut buf2 = Buf::new(1024); logger.write_raw(left);
let mut done = false; logger.write_raw(right);
while !done { logger.write_raw(b" (portal)\n");
match ring.read(&self.logs, buf2.clone()).await { })
Ok(n) if n > 0 => buf.extend(&buf2[..n]), .await;
Ok(_) => done = true, if let Err(e) = res {
Err(e) => { log::error!("Could not read portal logs: {}", ErrorFmt(e));
log::error!("Could not read portal logs: {}", ErrorFmt(e));
return;
}
};
while let Some(pos) = buf.iter().position(|b| b == &b'\n') {
let (left, right) = buf.get_slices(..pos);
logger.write_raw(left);
logger.write_raw(right);
logger.write_raw(b" (portal)\n");
buf.drain(..=pos);
}
} }
} }
}); });

View file

@ -4,7 +4,7 @@ use {
cursor::KnownCursor, cursor::KnownCursor,
fixed::Fixed, fixed::Fixed,
format::ARGB8888, format::ARGB8888,
gfx_api::{GfxContext, GfxFramebuffer}, gfx_api::{AcquireSync, GfxContext, GfxFramebuffer, ReleaseSync},
ifs::zwlr_layer_shell_v1::OVERLAY, ifs::zwlr_layer_shell_v1::OVERLAY,
portal::ptl_display::{PortalDisplay, PortalOutput, PortalSeat}, portal::ptl_display::{PortalDisplay, PortalOutput, PortalSeat},
renderer::renderer_base::RendererBase, renderer::renderer_base::RendererBase,
@ -222,6 +222,9 @@ impl GuiElement for Button {
None, None,
r.scale(), r.scale(),
None, None,
None,
AcquireSync::None,
ReleaseSync::None,
); );
} }
} }
@ -323,6 +326,9 @@ impl GuiElement for Label {
None, None,
r.scale(), r.scale(),
None, None,
None,
AcquireSync::None,
ReleaseSync::None,
); );
} }
} }
@ -626,6 +632,19 @@ impl WindowData {
} }
return; return;
}; };
let res = buf
.fb
.render_custom(self.scale.get(), Some(&Color::from_gray(0)), &mut |r| {
if let Some(content) = self.content.get() {
content.render_at(r, 0.0, 0.0)
}
});
if let Err(e) = res {
log::error!("Could not render frame: {}", ErrorFmt(e));
return;
}
self.frame_missed.set(false); self.frame_missed.set(false);
self.surface.frame({ self.surface.frame({
@ -641,13 +660,6 @@ impl WindowData {
self.have_frame.set(false); self.have_frame.set(false);
buf.free.set(false); buf.free.set(false);
buf.fb
.render_custom(self.scale.get(), Some(&Color::from_gray(0)), &mut |r| {
if let Some(content) = self.content.get() {
content.render_at(r, 0.0, 0.0)
}
});
self.surface.attach(&buf.wl); self.surface.attach(&buf.wl);
self.surface.commit(); self.surface.commit();
} }

View file

@ -1,11 +1,11 @@
use { use {
crate::{ crate::{
gfx_api::{GfxApiOpt, SampleRect}, gfx_api::{AcquireSync, GfxApiOpt, ReleaseSync, SampleRect},
ifs::{ ifs::{
wl_buffer::WlBuffer,
wl_callback::WlCallback, wl_callback::WlCallback,
wl_surface::{ wl_surface::{
xdg_surface::XdgSurface, zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, WlSurface, xdg_surface::XdgSurface, zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, SurfaceBuffer,
WlSurface,
}, },
wp_presentation_feedback::WpPresentationFeedback, wp_presentation_feedback::WpPresentationFeedback,
}, },
@ -151,13 +151,33 @@ impl Renderer<'_> {
let scale = output.global.persistent.scale.get(); let scale = output.global.persistent.scale.get();
for title in &rd.titles { for title in &rd.titles {
let (x, y) = self.base.scale_point(x + title.tex_x, y + title.tex_y); let (x, y) = self.base.scale_point(x + title.tex_x, y + title.tex_y);
self.base self.base.render_texture(
.render_texture(&title.tex, x, y, None, None, scale, None); &title.tex,
x,
y,
None,
None,
scale,
None,
None,
AcquireSync::None,
ReleaseSync::None,
);
} }
if let Some(status) = &rd.status { if let Some(status) = &rd.status {
let (x, y) = self.base.scale_point(x + status.tex_x, y + status.tex_y); let (x, y) = self.base.scale_point(x + status.tex_x, y + status.tex_y);
self.base self.base.render_texture(
.render_texture(&status.tex.texture, x, y, None, None, scale, None); &status.tex.texture,
x,
y,
None,
None,
scale,
None,
None,
AcquireSync::None,
ReleaseSync::None,
);
} }
} }
if let Some(ws) = output.workspace.get() { if let Some(ws) = output.workspace.get() {
@ -193,8 +213,18 @@ impl Renderer<'_> {
let (tex_width, tex_height) = tex.texture.size(); let (tex_width, tex_height) = tex.texture.size();
let x = x + (pos.width() - tex_width) / 2; let x = x + (pos.width() - tex_width) / 2;
let y = y + (pos.height() - tex_height) / 2; let y = y + (pos.height() - tex_height) / 2;
self.base self.base.render_texture(
.render_texture(&tex.texture, x, y, None, None, self.base.scale, None); &tex.texture,
x,
y,
None,
None,
self.base.scale,
None,
None,
AcquireSync::None,
ReleaseSync::None,
);
} }
} }
@ -231,6 +261,9 @@ impl Renderer<'_> {
None, None,
self.base.scale, self.base.scale,
None, None,
None,
AcquireSync::None,
ReleaseSync::None,
); );
} }
} }
@ -345,14 +378,14 @@ impl Renderer<'_> {
pub fn render_buffer( pub fn render_buffer(
&mut self, &mut self,
buffer: &WlBuffer, buffer: &Rc<SurfaceBuffer>,
x: i32, x: i32,
y: i32, y: i32,
tpoints: SampleRect, tpoints: SampleRect,
tsize: (i32, i32), tsize: (i32, i32),
bounds: Option<&Rect>, bounds: Option<&Rect>,
) { ) {
if let Some(tex) = buffer.texture.get() { if let Some(tex) = buffer.buffer.texture.get() {
self.base.render_texture( self.base.render_texture(
&tex, &tex,
x, x,
@ -361,8 +394,11 @@ impl Renderer<'_> {
Some(tsize), Some(tsize),
self.base.scale, self.base.scale,
bounds, bounds,
Some(buffer.clone()),
buffer.sync.clone(),
buffer.release_sync,
); );
} else if let Some(color) = &buffer.color { } else if let Some(color) = &buffer.buffer.color {
if let Some(rect) = Rect::new_sized(x, y, tsize.0, tsize.1) { if let Some(rect) = Rect::new_sized(x, y, tsize.0, tsize.1) {
let rect = match bounds { let rect = match bounds {
None => rect, None => rect,
@ -409,8 +445,18 @@ impl Renderer<'_> {
self.base.fill_boxes(&title_underline, &uc); self.base.fill_boxes(&title_underline, &uc);
if let Some(title) = floating.title_textures.get(&self.base.scale) { if let Some(title) = floating.title_textures.get(&self.base.scale) {
let (x, y) = self.base.scale_point(x + bw, y + bw); let (x, y) = self.base.scale_point(x + bw, y + bw);
self.base self.base.render_texture(
.render_texture(&title.texture, x, y, None, None, self.base.scale, None); &title.texture,
x,
y,
None,
None,
self.base.scale,
None,
None,
AcquireSync::None,
ReleaseSync::None,
);
} }
let body = Rect::new_sized( let body = Rect::new_sized(
x + bw, x + bw,

View file

@ -1,6 +1,9 @@
use { use {
crate::{ crate::{
gfx_api::{CopyTexture, FillRect, FramebufferRect, GfxApiOpt, GfxTexture, SampleRect}, gfx_api::{
AcquireSync, BufferResv, CopyTexture, FillRect, FramebufferRect, GfxApiOpt, GfxTexture,
ReleaseSync, SampleRect,
},
rect::Rect, rect::Rect,
scale::Scale, scale::Scale,
theme::Color, theme::Color,
@ -130,6 +133,9 @@ impl RendererBase<'_> {
tsize: Option<(i32, i32)>, tsize: Option<(i32, i32)>,
tscale: Scale, tscale: Scale,
bounds: Option<&Rect>, bounds: Option<&Rect>,
buffer_resv: Option<Rc<dyn BufferResv>>,
acquire_sync: AcquireSync,
release_sync: ReleaseSync,
) { ) {
let mut texcoord = tpoints.unwrap_or_else(SampleRect::identity); let mut texcoord = tpoints.unwrap_or_else(SampleRect::identity);
@ -154,18 +160,23 @@ impl RendererBase<'_> {
} }
} }
let target = FramebufferRect::new(
target_x[0] as f32,
target_y[0] as f32,
target_x[1] as f32,
target_y[1] as f32,
self.transform,
self.fb_width,
self.fb_height,
);
self.ops.push(GfxApiOpt::CopyTexture(CopyTexture { self.ops.push(GfxApiOpt::CopyTexture(CopyTexture {
tex: texture.clone(), tex: texture.clone(),
source: texcoord, source: texcoord,
target: FramebufferRect::new( target,
target_x[0] as f32, buffer_resv,
target_y[0] as f32, acquire_sync,
target_x[1] as f32, release_sync,
target_y[1] as f32,
self.transform,
self.fb_width,
self.fb_height,
),
})); }));
} }
} }

View file

@ -78,7 +78,7 @@ pub fn take_screenshot(state: &State) -> Result<Screenshot, ScreenshooterError>
true, true,
false, false,
Transform::None, Transform::None,
); )?;
let drm = gbm.drm.dup_render()?.fd().clone(); let drm = gbm.drm.dup_render()?.fd().clone();
Ok(Screenshot { drm, bo }) Ok(Screenshot { drm, bo })
} }

View file

@ -17,7 +17,10 @@ use {
fixed::Fixed, fixed::Fixed,
forker::ForkerProxy, forker::ForkerProxy,
format::Format, format::Format,
gfx_api::{GfxContext, GfxError, GfxFramebuffer, GfxTexture, SampleRect}, gfx_api::{
AcquireSync, GfxContext, GfxError, GfxFramebuffer, GfxTexture, ReleaseSync, SampleRect,
SyncFile,
},
gfx_apis::create_gfx_context, gfx_apis::create_gfx_context,
globals::{Globals, GlobalsError, WaylandGlobal}, globals::{Globals, GlobalsError, WaylandGlobal},
ifs::{ ifs::{
@ -30,9 +33,11 @@ use {
wl_output::{OutputId, PersistentOutputState}, wl_output::{OutputId, PersistentOutputState},
wl_seat::{SeatIds, WlSeatGlobal}, wl_seat::{SeatIds, WlSeatGlobal},
wl_surface::{ wl_surface::{
wl_subsurface::SubsurfaceIds,
zwp_idle_inhibitor_v1::{IdleInhibitorId, IdleInhibitorIds, ZwpIdleInhibitorV1}, zwp_idle_inhibitor_v1::{IdleInhibitorId, IdleInhibitorIds, ZwpIdleInhibitorV1},
NoneSurfaceExt, WlSurface, NoneSurfaceExt, WlSurface,
}, },
wp_linux_drm_syncobj_manager_v1::WpLinuxDrmSyncobjManagerV1Global,
zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1, zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1,
zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1Global, zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1Global,
@ -55,7 +60,14 @@ use {
linkedlist::LinkedList, numcell::NumCell, queue::AsyncQueue, refcounted::RefCounted, linkedlist::LinkedList, numcell::NumCell, queue::AsyncQueue, refcounted::RefCounted,
run_toplevel::RunToplevel, run_toplevel::RunToplevel,
}, },
video::{dmabuf::DmaBufIds, drm::Drm}, video::{
dmabuf::DmaBufIds,
drm::{
sync_obj::{SyncObj, SyncObjPoint},
wait_for_sync_obj::WaitForSyncObj,
Drm,
},
},
wheel::Wheel, wheel::Wheel,
wire::{ wire::{
ExtForeignToplevelListV1Id, JayRenderCtxId, JaySeatEventsId, JayWorkspaceWatcherId, ExtForeignToplevelListV1Id, JayRenderCtxId, JaySeatEventsId, JayWorkspaceWatcherId,
@ -80,6 +92,7 @@ use {
sync::Arc, sync::Arc,
time::Duration, time::Duration,
}, },
thiserror::Error,
}; };
pub struct State { pub struct State {
@ -157,6 +170,9 @@ pub struct State {
pub double_click_interval_usec: Cell<u64>, pub double_click_interval_usec: Cell<u64>,
pub double_click_distance: Cell<i32>, pub double_click_distance: Cell<i32>,
pub create_default_seat: Cell<bool>, pub create_default_seat: Cell<bool>,
pub subsurface_ids: SubsurfaceIds,
pub wait_for_sync_obj: Rc<WaitForSyncObj>,
pub explicit_sync_enabled: Cell<bool>,
} }
// impl Drop for State { // impl Drop for State {
@ -361,6 +377,8 @@ impl State {
self.render_ctx_version.fetch_add(1); self.render_ctx_version.fetch_add(1);
self.cursors.set(None); self.cursors.set(None);
self.drm_feedback.set(None); self.drm_feedback.set(None);
self.wait_for_sync_obj
.set_ctx(ctx.as_ref().map(|c| c.sync_obj_ctx().clone()));
'handle_new_feedback: { 'handle_new_feedback: {
if let Some(ctx) = &ctx { if let Some(ctx) = &ctx {
@ -406,7 +424,7 @@ impl State {
} }
fn visit_surface(&mut self, node: &Rc<WlSurface>) { fn visit_surface(&mut self, node: &Rc<WlSurface>) {
if let Some(buffer) = node.buffer.get() { if let Some(buffer) = node.buffer.get() {
buffer.handle_gfx_context_change(); buffer.buffer.handle_gfx_context_change();
} }
node.node_visit_children(self); node.node_visit_children(self);
} }
@ -429,11 +447,18 @@ impl State {
seat.render_ctx_changed(); seat.render_ctx_changed();
} }
if ctx.is_some() && !self.render_ctx_ever_initialized.replace(true) { if let Some(ctx) = &ctx {
self.add_global(&Rc::new(WlDrmGlobal::new(self.globals.name()))); if !self.render_ctx_ever_initialized.replace(true) {
self.add_global(&Rc::new(ZwpLinuxDmabufV1Global::new(self.globals.name()))); self.add_global(&Rc::new(WlDrmGlobal::new(self.globals.name())));
if let Some(config) = self.config.get() { self.add_global(&Rc::new(ZwpLinuxDmabufV1Global::new(self.globals.name())));
config.graphics_initialized(); if ctx.sync_obj_ctx().supports_async_wait() && self.explicit_sync_enabled.get() {
self.add_global(&Rc::new(WpLinuxDrmSyncobjManagerV1Global::new(
self.globals.name(),
)));
}
if let Some(config) = self.config.get() {
config.graphics_initialized();
}
} }
} }
@ -767,17 +792,18 @@ impl State {
tex: &Rc<dyn GfxTexture>, tex: &Rc<dyn GfxTexture>,
rr: &mut RenderResult, rr: &mut RenderResult,
render_hw_cursor: bool, render_hw_cursor: bool,
) { ) -> Result<Option<SyncFile>, GfxError> {
fb.render_output( let sync_file = fb.render_output(
output, output,
self, self,
Some(output.global.pos.get()), Some(output.global.pos.get()),
Some(rr), Some(rr),
output.global.persistent.scale.get(), output.global.persistent.scale.get(),
render_hw_cursor, render_hw_cursor,
); )?;
output.perform_screencopies(tex, !render_hw_cursor, 0, 0, None); output.perform_screencopies(tex, !render_hw_cursor, 0, 0, None);
rr.dispatch_frame_requests(); rr.dispatch_frame_requests();
Ok(sync_file)
} }
pub fn perform_screencopy( pub fn perform_screencopy(
@ -790,7 +816,7 @@ impl State {
y_off: i32, y_off: i32,
size: Option<(i32, i32)>, size: Option<(i32, i32)>,
transform: Transform, transform: Transform,
) { ) -> Result<Option<SyncFile>, GfxError> {
let mut ops = target.take_render_ops(); let mut ops = target.take_render_ops();
let mut renderer = Renderer { let mut renderer = Renderer {
base: target.renderer_base(&mut ops, Scale::from_int(1), Transform::None), base: target.renderer_base(&mut ops, Scale::from_int(1), Transform::None),
@ -812,6 +838,9 @@ impl State {
size, size,
Scale::from_int(1), Scale::from_int(1),
None, None,
None,
AcquireSync::None,
ReleaseSync::Implicit,
); );
if render_hardware_cursors { if render_hardware_cursors {
for seat in self.globals.lock_seats().values() { for seat in self.globals.lock_seats().values() {
@ -825,7 +854,7 @@ impl State {
} }
} }
} }
target.render(ops, Some(&Color::SOLID_BLACK)); target.render(ops, Some(&Color::SOLID_BLACK))
} }
fn have_hardware_cursor(&self) -> bool { fn have_hardware_cursor(&self) -> bool {
@ -851,7 +880,7 @@ impl State {
stride: i32, stride: i32,
format: &'static Format, format: &'static Format,
transform: Transform, transform: Transform,
) { ) -> Result<(), ShmScreencopyError> {
let (src_width, src_height) = src.size(); let (src_width, src_height) = src.size();
let mut needs_copy = capture.rect.x1() < x_off let mut needs_copy = capture.rect.x1() < x_off
|| capture.rect.x2() > x_off + src_width || capture.rect.x2() > x_off + src_width
@ -866,20 +895,11 @@ impl State {
} }
let acc = if needs_copy { let acc = if needs_copy {
let Some(ctx) = self.render_ctx.get() else { let Some(ctx) = self.render_ctx.get() else {
log::warn!("Cannot perform shm screencopy because there is no render context"); return Err(ShmScreencopyError::NoRenderContext);
return;
}; };
let fb = let fb = ctx
match ctx.create_fb(capture.rect.width(), capture.rect.height(), stride, format) { .create_fb(capture.rect.width(), capture.rect.height(), stride, format)
Ok(f) => f, .map_err(ShmScreencopyError::CreateTemporaryFb)?;
Err(e) => {
log::warn!(
"Could not create temporary fb for screencopy: {}",
ErrorFmt(e)
);
return;
}
};
self.perform_screencopy( self.perform_screencopy(
src, src,
&fb, &fb,
@ -889,7 +909,8 @@ impl State {
y_off - capture.rect.y1(), y_off - capture.rect.y1(),
size, size,
transform, transform,
); )
.map_err(ShmScreencopyError::CopyToTemporary)?;
mem.access(|mem| { mem.access(|mem| {
fb.copy_to_shm( fb.copy_to_shm(
0, 0,
@ -914,16 +935,12 @@ impl State {
) )
}) })
}; };
let res = match acc { match acc {
Ok(res) => res, Ok(res) => res.map_err(ShmScreencopyError::ReadPixels),
Err(e) => { Err(e) => {
capture.client.error(e); capture.client.error(e);
return; Ok(())
} }
};
if let Err(e) = res {
log::warn!("Could not read texture to memory: {}", ErrorFmt(e));
capture.send_failed();
} }
} }
@ -933,4 +950,26 @@ impl State {
self.globals.add_global(self, &seat); self.globals.add_global(self, &seat);
seat seat
} }
pub fn signal_point(&self, sync_obj: &SyncObj, point: SyncObjPoint) {
let Some(ctx) = self.render_ctx.get() else {
log::error!("Cannot signal sync obj point because there is no render context");
return;
};
if let Err(e) = ctx.sync_obj_ctx().signal(sync_obj, point) {
log::error!("Could not signal sync obj: {}", ErrorFmt(e));
}
}
}
#[derive(Debug, Error)]
pub enum ShmScreencopyError {
#[error("There is no render context")]
NoRenderContext,
#[error("Could not create a bridge framebuffer")]
CreateTemporaryFb(#[source] GfxError),
#[error("Could not copy texture to bridge framebuffer")]
CopyToTemporary(#[source] GfxError),
#[error("Could not read pixels from texture")]
ReadPixels(#[source] GfxError),
} }

View file

@ -16,6 +16,7 @@ pub mod errorfmt;
pub mod fdcloser; pub mod fdcloser;
pub mod gfx_api_ext; pub mod gfx_api_ext;
pub mod hex; pub mod hex;
pub mod line_logger;
pub mod linkedlist; pub mod linkedlist;
pub mod log_on_drop; pub mod log_on_drop;
pub mod mmap; pub mod mmap;

36
src/utils/line_logger.rs Normal file
View file

@ -0,0 +1,36 @@
use {
crate::{
io_uring::{IoUring, IoUringError},
utils::{buf::Buf, vecdeque_ext::VecDequeExt},
},
isnt::std_1::collections::IsntVecDequeExt,
std::{collections::VecDeque, rc::Rc},
uapi::OwnedFd,
};
pub async fn log_lines(
ring: &IoUring,
fd: &Rc<OwnedFd>,
mut f: impl FnMut(&[u8], &[u8]),
) -> Result<(), IoUringError> {
let mut buf = VecDeque::<u8>::new();
let mut buf2 = Buf::new(1024);
let mut done = false;
while !done {
let n = ring.read(fd, buf2.clone()).await?;
buf.extend(&buf2[..n]);
if n == 0 {
done = true;
}
while let Some(pos) = buf.iter().position(|b| b == &b'\n') {
let (left, right) = buf.get_slices(..pos);
f(left, right);
buf.drain(..=pos);
}
}
if buf.is_not_empty() {
let (left, right) = buf.as_slices();
f(left, right);
}
Ok(())
}

View file

@ -46,6 +46,23 @@ impl<T> LinkedList<T> {
} }
} }
pub fn append_all(&self, other: &LinkedList<T>) {
if other.is_empty() {
return;
}
unsafe {
let o_root = other.root.data.as_ref();
let o_first = o_root.next.get();
let o_last = o_root.prev.get();
let s_first = self.root.data;
let s_last = s_first.as_ref().prev.get();
o_first.as_ref().prev.set(s_last);
s_last.as_ref().next.set(o_first);
o_last.as_ref().next.set(s_first);
s_first.as_ref().prev.set(o_last);
}
}
fn endpoint(&self, ep: NonNull<NodeData<T>>) -> Option<NodeRef<T>> { fn endpoint(&self, ep: NonNull<NodeData<T>>) -> Option<NodeRef<T>> {
unsafe { unsafe {
if ep != self.root.data { if ep != self.root.data {
@ -57,11 +74,14 @@ impl<T> LinkedList<T> {
} }
} }
#[allow(dead_code)]
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.last().is_none() self.last().is_none()
} }
pub fn is_not_empty(&self) -> bool {
!self.is_empty()
}
pub fn last(&self) -> Option<NodeRef<T>> { pub fn last(&self) -> Option<NodeRef<T>> {
unsafe { self.endpoint(self.root.data.as_ref().prev.get()) } unsafe { self.endpoint(self.root.data.as_ref().prev.get()) }
} }

View file

@ -58,6 +58,10 @@ impl<K: Eq, V, const N: usize> SmallMap<K, V, N> {
unsafe { self.m.get().deref().is_empty() } unsafe { self.m.get().deref().is_empty() }
} }
pub fn is_not_empty(&self) -> bool {
!self.is_empty()
}
pub fn remove(&self, k: &K) -> Option<V> { pub fn remove(&self, k: &K) -> Option<V> {
unsafe { self.m.get().deref_mut().remove(k) } unsafe { self.m.get().deref_mut().remove(k) }
} }

View file

@ -48,6 +48,13 @@ impl DmaBuf {
} }
false false
} }
pub fn import_sync_file(&self, flags: u32, sync_file: &OwnedFd) -> Result<(), OsError> {
for plane in &self.planes {
dma_buf_import_sync_file(&plane.fd, flags, sync_file)?;
}
Ok(())
}
} }
const DMA_BUF_BASE: u64 = b'b' as _; const DMA_BUF_BASE: u64 = b'b' as _;

View file

@ -1,4 +1,6 @@
pub mod sync_obj;
mod sys; mod sys;
pub mod wait_for_sync_obj;
use { use {
crate::{ crate::{
@ -52,7 +54,7 @@ pub use sys::{
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum DrmError { pub enum DrmError {
#[error("Could not reopen a node")] #[error("Could not reopen a node")]
ReopenNode(#[source] crate::utils::oserror::OsError), ReopenNode(#[source] OsError),
#[error("Could not retrieve the render node name")] #[error("Could not retrieve the render node name")]
RenderNodeName(#[source] OsError), RenderNodeName(#[source] OsError),
#[error("Could not retrieve the device node name")] #[error("Could not retrieve the device node name")]
@ -113,6 +115,28 @@ pub enum DrmError {
Version(#[source] OsError), Version(#[source] OsError),
#[error("Format of IN_FORMATS property is invalid")] #[error("Format of IN_FORMATS property is invalid")]
InFormats, InFormats,
#[error("Could not import a sync obj")]
ImportSyncObj(#[source] OsError),
#[error("Could not create a sync obj")]
CreateSyncObj(#[source] OsError),
#[error("Could not export a sync obj")]
ExportSyncObj(#[source] OsError),
#[error("Could not register an eventfd with a sync obj")]
RegisterEventfd(#[source] OsError),
#[error("Could not create an eventfd")]
EventFd(#[source] OsError),
#[error("Could not read from an eventfd")]
ReadEventFd(#[source] IoUringError),
#[error("No sync obj context available")]
NoSyncObjContextAvailable,
#[error("Could not signal the sync obj")]
SignalSyncObj(#[source] OsError),
#[error("Could not transfer a sync obj point")]
TransferPoint(#[source] OsError),
#[error("Could not merge two sync files")]
Merge(#[source] OsError),
#[error("Could not import a sync file into a sync obj")]
ImportSyncFile(#[source] OsError),
} }
fn render_node_name(fd: c::c_int) -> Result<Ustring, DrmError> { fn render_node_name(fd: c::c_int) -> Result<Ustring, DrmError> {

238
src/video/drm/sync_obj.rs Normal file
View file

@ -0,0 +1,238 @@
use {
crate::{
gfx_api::SyncFile,
utils::{
clonecell::{CloneCell, UnsafeCellCloneSafe},
copyhashmap::CopyHashMap,
errorfmt::ErrorFmt,
linkedlist::{LinkedList, LinkedNode},
oserror::OsError,
},
video::drm::{
sys::{
sync_ioc_merge, sync_obj_create, sync_obj_destroy, sync_obj_eventfd,
sync_obj_fd_to_handle, sync_obj_handle_to_fd, sync_obj_signal, sync_obj_transfer,
DRM_SYNCOBJ_FD_TO_HANDLE_FLAGS_IMPORT_SYNC_FILE,
DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE,
},
DrmError,
},
},
std::{
rc::Rc,
sync::atomic::{AtomicU64, Ordering::Relaxed},
},
uapi::{c, OwnedFd},
};
static SYNCOBJ_ID: AtomicU64 = AtomicU64::new(0);
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct SyncObjId(u64);
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
struct SyncObjHandle(u32);
unsafe impl UnsafeCellCloneSafe for SyncObjHandle {}
pub struct SyncObj {
id: SyncObjId,
fd: Rc<OwnedFd>,
importers: LinkedList<Rc<Handles>>,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct SyncObjPoint(pub u64);
impl SyncObj {
pub fn new(fd: &Rc<OwnedFd>) -> Self {
Self {
id: SyncObjId(SYNCOBJ_ID.fetch_add(1, Relaxed)),
fd: fd.clone(),
importers: Default::default(),
}
}
}
impl Drop for SyncObj {
fn drop(&mut self) {
let mut links = vec![];
for importer in self.importers.iter() {
if let Some(handle) = importer.handles.remove(&self.id) {
destroy(&importer.drm, handle);
}
if let Some(link) = importer.links.remove(&self.id) {
links.push(link);
}
}
}
}
struct Handles {
drm: Rc<OwnedFd>,
handles: CopyHashMap<SyncObjId, SyncObjHandle>,
links: CopyHashMap<SyncObjId, LinkedNode<Rc<Handles>>>,
}
pub struct SyncObjCtx {
inner: Rc<Handles>,
dummy: CloneCell<Option<Rc<SyncObj>>>,
}
impl SyncObjCtx {
pub fn new(drm: &Rc<OwnedFd>) -> Self {
Self {
inner: Rc::new(Handles {
drm: drm.clone(),
handles: Default::default(),
links: Default::default(),
}),
dummy: Default::default(),
}
}
fn get_handle(&self, sync_obj: &SyncObj) -> Result<SyncObjHandle, DrmError> {
if let Some(handle) = self.inner.handles.get(&sync_obj.id) {
return Ok(handle);
}
let handle = sync_obj_fd_to_handle(self.inner.drm.raw(), sync_obj.fd.raw(), 0, 0)
.map_err(DrmError::ImportSyncObj)?;
let handle = SyncObjHandle(handle);
let link = sync_obj.importers.add_last(self.inner.clone());
self.inner.handles.set(sync_obj.id, handle);
self.inner.links.set(sync_obj.id, link);
Ok(handle)
}
pub fn create_sync_obj(&self) -> Result<SyncObj, DrmError> {
let handle = sync_obj_create(self.inner.drm.raw(), 0).map_err(DrmError::CreateSyncObj)?;
let handle = SyncObjHandle(handle);
let fd = sync_obj_handle_to_fd(self.inner.drm.raw(), handle.0, 0);
if fd.is_err() {
destroy(&self.inner.drm, handle);
}
let fd = fd.map_err(DrmError::ExportSyncObj).map(Rc::new)?;
let sync_obj = SyncObj::new(&fd);
let link = sync_obj.importers.add_last(self.inner.clone());
self.inner.handles.set(sync_obj.id, handle);
self.inner.links.set(sync_obj.id, link);
Ok(sync_obj)
}
pub fn wait_for_point(
&self,
eventfd: &OwnedFd,
sync_obj: &SyncObj,
point: SyncObjPoint,
signaled: bool,
) -> Result<(), DrmError> {
let handle = self.get_handle(sync_obj)?;
let flags = match signaled {
true => 0,
false => DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE,
};
sync_obj_eventfd(
self.inner.drm.raw(),
eventfd.raw(),
handle.0,
point.0,
flags,
)
.map_err(DrmError::RegisterEventfd)
}
pub fn supports_async_wait(&self) -> bool {
self.supports_async_wait_().is_ok()
}
fn supports_async_wait_(&self) -> Result<(), DrmError> {
let sync_obj = self.create_sync_obj()?;
let eventfd = uapi::eventfd(0, c::EFD_CLOEXEC)
.map_err(OsError::from)
.map_err(DrmError::EventFd)?;
self.wait_for_point(&eventfd, &sync_obj, SyncObjPoint(1), true)?;
Ok(())
}
pub fn signal(&self, sync_obj: &SyncObj, point: SyncObjPoint) -> Result<(), DrmError> {
let handle = self.get_handle(sync_obj)?;
sync_obj_signal(self.inner.drm.raw(), handle.0, point.0).map_err(DrmError::SignalSyncObj)
}
pub fn import_sync_files<'a, I>(
&self,
sync_obj: &SyncObj,
point: SyncObjPoint,
sync_files: I,
) -> Result<(), DrmError>
where
I: IntoIterator<Item = &'a SyncFile>,
{
let mut sync_files = sync_files.into_iter();
let Some(first) = sync_files.next() else {
return self.signal(sync_obj, point);
};
let mut stash;
let mut fd = &*first.0;
for next in sync_files {
stash = sync_ioc_merge(fd.raw(), next.raw()).map_err(DrmError::Merge)?;
fd = &stash;
}
let dummy = self.get_dummy()?;
sync_obj_fd_to_handle(
self.inner.drm.raw(),
fd.raw(),
DRM_SYNCOBJ_FD_TO_HANDLE_FLAGS_IMPORT_SYNC_FILE,
self.get_handle(&dummy)?.0,
)
.map_err(DrmError::ImportSyncFile)?;
self.transfer(&dummy, SyncObjPoint(0), sync_obj, point)
}
fn transfer(
&self,
src_sync_obj: &SyncObj,
src_point: SyncObjPoint,
dst_sync_obj: &SyncObj,
dst_point: SyncObjPoint,
) -> Result<(), DrmError> {
let src_handle = self.get_handle(src_sync_obj)?;
let dst_handle = self.get_handle(dst_sync_obj)?;
sync_obj_transfer(
self.inner.drm.raw(),
src_handle.0,
src_point.0,
dst_handle.0,
dst_point.0,
0,
)
.map_err(DrmError::TransferPoint)
}
fn get_dummy(&self) -> Result<Rc<SyncObj>, DrmError> {
match self.dummy.get() {
Some(d) => Ok(d),
None => {
let d = Rc::new(self.create_sync_obj()?);
self.dummy.set(Some(d.clone()));
Ok(d)
}
}
}
}
impl Drop for SyncObjCtx {
fn drop(&mut self) {
self.inner.links.clear();
let mut map = self.inner.handles.lock();
for (_, handle) in map.drain() {
destroy(&self.inner.drm, handle);
}
}
}
fn destroy(drm: &OwnedFd, handle: SyncObjHandle) {
if let Err(e) = sync_obj_destroy(drm.raw(), handle.0) {
log::error!("Could not destroy sync obj: {}", ErrorFmt(e));
}
}

View file

@ -1156,3 +1156,201 @@ pub struct drm_format_modifier {
} }
unsafe impl Pod for drm_format_modifier {} unsafe impl Pod for drm_format_modifier {}
// pub const DRM_SYNCOBJ_CREATE_SIGNALED: u32 = 1 << 0;
#[repr(C)]
struct drm_syncobj_create {
handle: u32,
flags: u32,
}
const DRM_IOCTL_SYNCOBJ_CREATE: u64 = drm_iowr::<drm_syncobj_create>(0xBF);
pub fn sync_obj_create(drm: c::c_int, flags: u32) -> Result<u32, OsError> {
let mut res = drm_syncobj_create { handle: 0, flags };
unsafe {
ioctl(drm, DRM_IOCTL_SYNCOBJ_CREATE, &mut res)?;
}
Ok(res.handle)
}
#[repr(C)]
struct drm_syncobj_destroy {
handle: u32,
pad: u32,
}
const DRM_IOCTL_SYNCOBJ_DESTROY: u64 = drm_iowr::<drm_syncobj_destroy>(0xC0);
pub fn sync_obj_destroy(drm: c::c_int, handle: u32) -> Result<(), OsError> {
let mut res = drm_syncobj_destroy { handle, pad: 0 };
unsafe {
ioctl(drm, DRM_IOCTL_SYNCOBJ_DESTROY, &mut res)?;
}
Ok(())
}
pub const DRM_SYNCOBJ_FD_TO_HANDLE_FLAGS_IMPORT_SYNC_FILE: u32 = 1 << 0;
// pub const DRM_SYNCOBJ_HANDLE_TO_FD_FLAGS_EXPORT_SYNC_FILE: u32 = 1 << 0;
#[repr(C)]
struct drm_syncobj_handle {
handle: u32,
flags: u32,
fd: i32,
pad: u32,
}
const DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD: u64 = drm_iowr::<drm_syncobj_handle>(0xC1);
const DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE: u64 = drm_iowr::<drm_syncobj_handle>(0xC2);
pub fn sync_obj_handle_to_fd(drm: c::c_int, handle: u32, flags: u32) -> Result<OwnedFd, OsError> {
let mut res = drm_syncobj_handle {
handle,
flags,
fd: 0,
pad: 0,
};
unsafe {
ioctl(drm, DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD, &mut res)?;
}
Ok(OwnedFd::new(res.fd))
}
pub fn sync_obj_fd_to_handle(
drm: c::c_int,
fd: c::c_int,
flags: u32,
handle: u32,
) -> Result<u32, OsError> {
let mut res = drm_syncobj_handle {
handle,
flags,
fd,
pad: 0,
};
unsafe {
ioctl(drm, DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE, &mut res)?;
}
Ok(res.handle)
}
// pub const DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL: u32 = 1 << 0;
// pub const DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT: u32 = 1 << 1;
pub const DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE: u32 = 1 << 2;
#[repr(C)]
struct drm_syncobj_eventfd {
handle: u32,
flags: u32,
point: u64,
fd: i32,
pad: u32,
}
const DRM_IOCTL_SYNCOBJ_EVENTFD: u64 = drm_iowr::<drm_syncobj_eventfd>(0xCF);
pub fn sync_obj_eventfd(
drm: c::c_int,
eventfd: c::c_int,
handle: u32,
point: u64,
flags: u32,
) -> Result<(), OsError> {
let mut res = drm_syncobj_eventfd {
handle,
flags,
point,
fd: eventfd,
pad: 0,
};
unsafe {
ioctl(drm, DRM_IOCTL_SYNCOBJ_EVENTFD, &mut res)?;
}
Ok(())
}
#[repr(C)]
struct drm_syncobj_timeline_array {
handles: u64,
points: u64,
count_handles: u32,
flags: u32,
}
const DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL: u64 = drm_iowr::<drm_syncobj_timeline_array>(0xCD);
pub fn sync_obj_signal(drm: c::c_int, handle: u32, point: u64) -> Result<(), OsError> {
let mut res = drm_syncobj_timeline_array {
handles: &handle as *const u32 as u64,
points: &point as *const u64 as u64,
count_handles: 1,
flags: 0,
};
unsafe {
ioctl(drm, DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL, &mut res)?;
}
Ok(())
}
#[repr(C)]
struct drm_syncobj_transfer {
src_handle: u32,
dst_handle: u32,
src_point: u64,
dst_point: u64,
flags: u32,
pad: u32,
}
const DRM_IOCTL_SYNCOBJ_TRANSFER: u64 = drm_iowr::<drm_syncobj_transfer>(0xCC);
pub fn sync_obj_transfer(
drm: c::c_int,
src_handle: u32,
src_point: u64,
dst_handle: u32,
dst_point: u64,
flags: u32,
) -> Result<(), OsError> {
let mut res = drm_syncobj_transfer {
src_handle,
dst_handle,
src_point,
dst_point,
flags,
pad: 0,
};
unsafe {
ioctl(drm, DRM_IOCTL_SYNCOBJ_TRANSFER, &mut res)?;
}
Ok(())
}
#[repr(C)]
struct sync_merge_data {
name: [u8; 32],
fd2: i32,
fence: i32,
flags: u32,
pad: u32,
}
const SYNC_IOC_MAGIC: u64 = b'>' as _;
const SYNC_IOC_MERGE: u64 = uapi::_IOWR::<sync_merge_data>(SYNC_IOC_MAGIC, 3);
pub fn sync_ioc_merge(left: c::c_int, right: c::c_int) -> Result<OwnedFd, OsError> {
let mut res = sync_merge_data {
name: [0; 32],
fd2: right,
fence: 0,
flags: 0,
pad: 0,
};
unsafe {
ioctl(left, SYNC_IOC_MERGE, &mut res)?;
}
Ok(OwnedFd::new(res.fence))
}

View file

@ -0,0 +1,199 @@
use {
crate::{
async_engine::{AsyncEngine, SpawnedFuture},
io_uring::IoUring,
utils::{
asyncevent::AsyncEvent, buf::Buf, clonecell::CloneCell, copyhashmap::CopyHashMap,
numcell::NumCell, oserror::OsError, stack::Stack,
},
video::drm::{
sync_obj::{SyncObj, SyncObjCtx, SyncObjPoint},
DrmError,
},
},
std::{cell::Cell, rc::Rc},
uapi::{c, OwnedFd},
};
pub struct WaitForSyncObj {
inner: Rc<Inner>,
eng: Rc<AsyncEngine>,
}
pub trait SyncObjWaiter {
fn done(self: Rc<Self>, result: Result<(), DrmError>);
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
struct JobId(u64);
pub struct WaitForSyncObjHandle {
inner: Rc<Inner>,
id: JobId,
}
struct Inner {
ctx: CloneCell<Option<Rc<SyncObjCtx>>>,
next_id: NumCell<u64>,
ring: Rc<IoUring>,
busy: CopyHashMap<JobId, BusyWaiter>,
idle: Stack<Waiter>,
}
struct BusyWaiter {
waiter: Waiter,
job: Job,
sow: Rc<dyn SyncObjWaiter>,
}
struct Waiter {
_task: SpawnedFuture<()>,
inner: Rc<WaiterInner>,
}
#[derive(Clone)]
struct Job {
id: JobId,
sync_obj: Rc<SyncObj>,
point: SyncObjPoint,
signaled: bool,
}
struct WaiterInner {
inner: Rc<Inner>,
eventfd: Rc<OwnedFd>,
next: Cell<Option<Job>>,
trigger: AsyncEvent,
}
impl Drop for WaitForSyncObjHandle {
fn drop(&mut self) {
let _ = self.inner.busy.remove(&self.id);
}
}
impl WaitForSyncObj {
pub fn new(ring: &Rc<IoUring>, eng: &Rc<AsyncEngine>) -> Self {
Self {
inner: Rc::new(Inner {
ctx: Default::default(),
next_id: Default::default(),
ring: ring.clone(),
busy: Default::default(),
idle: Default::default(),
}),
eng: eng.clone(),
}
}
pub fn set_ctx(&self, ctx: Option<Rc<SyncObjCtx>>) {
self.inner.ctx.set(ctx);
let busy_waiters: Vec<_> = self.inner.busy.lock().drain().map(|(_, w)| w).collect();
for waiter in busy_waiters {
let res = self.submit_job(
waiter.job.id,
&waiter.job.sync_obj,
waiter.job.point,
waiter.job.signaled,
waiter.sow.clone(),
);
if res.is_err() {
waiter.sow.done(res);
}
}
}
pub fn wait(
&self,
sync_obj: &Rc<SyncObj>,
point: SyncObjPoint,
signaled: bool,
sow: Rc<dyn SyncObjWaiter>,
) -> Result<WaitForSyncObjHandle, DrmError> {
let job_id = JobId(self.inner.next_id.fetch_add(1));
self.submit_job(job_id, sync_obj, point, signaled, sow)?;
Ok(WaitForSyncObjHandle {
inner: self.inner.clone(),
id: job_id,
})
}
fn submit_job(
&self,
job_id: JobId,
sync_obj: &Rc<SyncObj>,
point: SyncObjPoint,
signaled: bool,
sow: Rc<dyn SyncObjWaiter>,
) -> Result<(), DrmError> {
let waiter = match self.inner.idle.pop() {
Some(w) => w,
None => {
let eventfd = uapi::eventfd(0, c::EFD_CLOEXEC)
.map_err(OsError::from)
.map_err(DrmError::EventFd)?;
let waiter = Rc::new(WaiterInner {
inner: self.inner.clone(),
eventfd: Rc::new(eventfd),
next: Cell::new(None),
trigger: Default::default(),
});
Waiter {
_task: self.eng.spawn(waiter.clone().run()),
inner: waiter,
}
}
};
let job = Job {
id: job_id,
sync_obj: sync_obj.clone(),
point,
signaled,
};
let waiter = BusyWaiter {
waiter,
job: job.clone(),
sow: sow.clone(),
};
waiter.waiter.inner.next.set(Some(job));
waiter.waiter.inner.trigger.trigger();
self.inner.busy.set(job_id, waiter);
Ok(())
}
}
impl Drop for WaitForSyncObj {
fn drop(&mut self) {
self.inner.busy.clear();
self.inner.idle.take();
}
}
impl WaiterInner {
async fn run(self: Rc<Self>) {
let mut buf = Buf::new(8);
loop {
self.trigger.triggered().await;
let job = self.next.take().unwrap();
let res = self.wait(&mut buf, &job).await;
if let Some(waiter) = self.inner.busy.remove(&job.id) {
waiter.sow.done(res);
self.inner.idle.push(waiter.waiter);
}
}
}
async fn wait(&self, buf: &mut Buf, job: &Job) -> Result<(), DrmError> {
let ctx = match self.inner.ctx.get() {
None => return Err(DrmError::NoSyncObjContextAvailable),
Some(c) => c,
};
ctx.wait_for_point(&self.eventfd, &job.sync_obj, job.point, job.signaled)?;
self.inner
.ring
.read(&self.eventfd, buf.clone())
.await
.map(drop)
.map_err(DrmError::ReadEventFd)
}
}

View file

@ -18,7 +18,7 @@ use {
io_uring::IoUringError, io_uring::IoUringError,
state::State, state::State,
user_session::import_environment, user_session::import_environment,
utils::{buf::Buf, errorfmt::ErrorFmt, oserror::OsError}, utils::{errorfmt::ErrorFmt, line_logger::log_lines, oserror::OsError},
wire::WlSurfaceId, wire::WlSurfaceId,
xcon::XconError, xcon::XconError,
xwayland::{ xwayland::{
@ -217,29 +217,12 @@ pub fn build_args() -> (String, Vec<String>) {
async fn log_xwayland(state: Rc<State>, stderr: OwnedFd) { async fn log_xwayland(state: Rc<State>, stderr: OwnedFd) {
let stderr = Rc::new(stderr); let stderr = Rc::new(stderr);
let mut buf = vec![]; let res = log_lines(&state.ring, &stderr, |left, right| {
let mut buf2 = Buf::new(128); log::info!("Xwayland: {}{}", left.as_bstr(), right.as_bstr());
let mut done = false; })
while !done { .await;
loop { if let Err(e) = res {
match state.ring.read(&stderr, buf2.clone()).await { log::error!("Could not read from stderr fd: {}", ErrorFmt(e));
Ok(n) if n > 0 => {
buf.extend_from_slice(&buf2[..n]);
}
Ok(_) => {
done = true;
break;
}
Err(e) => {
log::error!("Could not read from stderr fd: {}", ErrorFmt(e));
return;
}
}
}
for line in buf.lines() {
log::info!("Xwayland: {}", line.as_bstr());
}
buf.clear();
} }
} }

View file

@ -289,6 +289,7 @@ pub struct Config {
pub render_device: Option<DrmDeviceMatch>, pub render_device: Option<DrmDeviceMatch>,
pub inputs: Vec<Input>, pub inputs: Vec<Input>,
pub idle: Option<Duration>, pub idle: Option<Duration>,
pub explicit_sync_enabled: Option<bool>,
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]

View file

@ -95,6 +95,7 @@ impl Parser for ConfigParser<'_> {
_, _,
idle_val, idle_val,
), ),
(explicit_sync,),
) = ext.extract(( ) = ext.extract((
( (
opt(val("keymap")), opt(val("keymap")),
@ -120,6 +121,7 @@ impl Parser for ConfigParser<'_> {
opt(val("$schema")), opt(val("$schema")),
opt(val("idle")), opt(val("idle")),
), ),
(recover(opt(bol("explicit-sync"))),),
))?; ))?;
let mut keymap = None; let mut keymap = None;
if let Some(value) = keymap_val { if let Some(value) = keymap_val {
@ -271,6 +273,7 @@ impl Parser for ConfigParser<'_> {
gfx_api, gfx_api,
drm_devices, drm_devices,
direct_scanout_enabled: direct_scanout.despan(), direct_scanout_enabled: direct_scanout.despan(),
explicit_sync_enabled: explicit_sync.despan(),
render_device, render_device,
inputs, inputs,
idle, idle,

View file

@ -19,7 +19,8 @@ use {
is_reload, is_reload,
keyboard::{Keymap, ModifiedKeySym}, keyboard::{Keymap, ModifiedKeySym},
logging::set_log_level, logging::set_log_level,
on_devices_enumerated, on_idle, quit, reload, set_default_workspace_capture, set_idle, on_devices_enumerated, on_idle, quit, reload, set_default_workspace_capture,
set_explicit_sync_enabled, set_idle,
status::{set_i3bar_separator, set_status, set_status_command, unset_status_command}, status::{set_i3bar_separator, set_status, set_status_command, unset_status_command},
switch_to_vt, switch_to_vt,
theme::{reset_colors, reset_font, reset_sizes, set_font}, theme::{reset_colors, reset_font, reset_sizes, set_font},
@ -789,6 +790,9 @@ fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) {
if let Some(dse) = config.direct_scanout_enabled { if let Some(dse) = config.direct_scanout_enabled {
set_direct_scanout_enabled(dse); set_direct_scanout_enabled(dse);
} }
if let Some(ese) = config.explicit_sync_enabled {
set_explicit_sync_enabled(ese);
}
on_new_drm_device({ on_new_drm_device({
let state = state.clone(); let state = state.clone();
move |d| { move |d| {

View file

@ -96,9 +96,7 @@ impl<'a> Lexer<'a> {
self.skip_ws(); self.skip_ws();
let Some(c) = get!(0) else { let c = get!(0)?;
return None;
};
let pos = self.pos; let pos = self.pos;
macro_rules! span { macro_rules! span {

View file

@ -498,6 +498,10 @@
"type": "boolean", "type": "boolean",
"description": "Configured whether the compositor attempts direct scanout.\n" "description": "Configured whether the compositor attempts direct scanout.\n"
}, },
"explicit-sync": {
"type": "boolean",
"description": "Configures whether the compositor supports explicit sync.\n\nThis cannot be changed after the compositor has started.\n\nThe default is `true`.\n"
},
"render-device": { "render-device": {
"description": "Selects the device to use for rendering in a system with multiple GPUs.\n\nThe first device that matches will be used.\n\n- Example:\n\n ```toml\n render-device.name = \"dedicated\"\n\n [[drm-devices]]\n name = \"dedicated\"\n match = { pci-vendor = 0x1002, pci-model = 0x73ff }\n ```\n", "description": "Selects the device to use for rendering in a system with multiple GPUs.\n\nThe first device that matches will be used.\n\n- Example:\n\n ```toml\n render-device.name = \"dedicated\"\n\n [[drm-devices]]\n name = \"dedicated\"\n match = { pci-vendor = 0x1002, pci-model = 0x73ff }\n ```\n",
"$ref": "#/$defs/DrmDeviceMatch" "$ref": "#/$defs/DrmDeviceMatch"

View file

@ -887,6 +887,16 @@ The table has the following fields:
The value of this field should be a boolean. The value of this field should be a boolean.
- `explicit-sync` (optional):
Configures whether the compositor supports explicit sync.
This cannot be changed after the compositor has started.
The default is `true`.
The value of this field should be a boolean.
- `render-device` (optional): - `render-device` (optional):
Selects the device to use for rendering in a system with multiple GPUs. Selects the device to use for rendering in a system with multiple GPUs.

View file

@ -1875,6 +1875,15 @@ Config:
required: false required: false
description: | description: |
Configured whether the compositor attempts direct scanout. Configured whether the compositor attempts direct scanout.
explicit-sync:
kind: boolean
required: false
description: |
Configures whether the compositor supports explicit sync.
This cannot be changed after the compositor has started.
The default is `true`.
render-device: render-device:
ref: DrmDeviceMatch ref: DrmDeviceMatch
required: false required: false

View file

@ -0,0 +1,15 @@
# requests
msg destroy = 0 {
}
msg get_surface = 1 {
id: id(wp_linux_drm_syncobj_surface_v1),
surface: id(wl_surface),
}
msg import_timeline = 2 {
id: id(wp_linux_drm_syncobj_timeline_v1),
fd: fd,
}

View file

@ -0,0 +1,17 @@
# requests
msg destroy = 0 {
}
msg set_acquire_point = 1 {
timeline: id(wp_linux_drm_syncobj_timeline_v1),
point_hi: u32,
point_lo: u32,
}
msg set_release_point = 2 {
timeline: id(wp_linux_drm_syncobj_timeline_v1),
point_hi: u32,
point_lo: u32,
}

View file

@ -0,0 +1,5 @@
# requests
msg destroy = 0 {
}