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
- An OpenGL backend
- A Vulkan backend
- Explicit sync
### Missing Features

View file

@ -80,6 +80,34 @@ fn write_egl_procs<W: Write>(f: &mut W) -> anyhow::Result<()> {
&[("target", "GLenum"), ("image", "GLeglImageOES")][..],
),
("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;")?;

View file

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

View file

@ -810,6 +810,10 @@ impl Client {
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) {
self.send(&ClientMessage::SetSeat { device, seat })
}

View file

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

View file

@ -222,3 +222,12 @@ pub fn workspaces() -> Vec<Workspace> {
pub fn set_idle(timeout: Option<Duration>) {
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,
drm_feedback::DrmFeedback,
fixed::Fixed,
gfx_api::GfxFramebuffer,
gfx_api::{GfxFramebuffer, SyncFile},
ifs::wl_seat::wl_pointer::{CONTINUOUS, FINGER, HORIZONTAL_SCROLL, VERTICAL_SCROLL, WHEEL},
libinput::consts::DeviceCapability,
video::drm::{ConnectorType, DrmError, DrmVersion},
@ -106,6 +106,7 @@ pub trait HardwareCursor: Debug {
fn get_buffer(&self) -> Rc<dyn GfxFramebuffer>;
fn set_position(&self, x: i32, y: i32);
fn swap_buffer(&self);
fn set_sync_file(&self, sync_file: Option<SyncFile>);
fn commit(&self);
fn size(&self) -> (i32, i32);
}

View file

@ -114,6 +114,14 @@ pub enum MetalError {
MissingDevModifier(&'static str),
#[error("Device GFX API cannot read any buffers writable by the render GFX API (format {0})")]
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 {

View file

@ -9,7 +9,10 @@ use {
drm_feedback::DrmFeedback,
edid::Descriptor,
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},
renderer::RenderResult,
state::State,
@ -227,6 +230,7 @@ pub struct MetalConnector {
pub cursor_buffers: CloneCell<Option<Rc<[RenderBuffer; 3]>>>,
pub cursor_front_buffer: NumCell<usize>,
pub cursor_swap_buffer: Cell<bool>,
pub cursor_sync_file: CloneCell<Option<SyncFile>>,
pub drm_feedback: CloneCell<Option<Rc<DrmFeedback>>>,
pub scanout_buffers: RefCell<AHashMap<DmaBufId, DirectScanoutCache>>,
@ -244,6 +248,7 @@ pub struct MetalHardwareCursor {
pub cursor_x_pending: Cell<i32>,
pub cursor_y_pending: Cell<i32>,
pub cursor_buffers: Rc<[RenderBuffer; 3]>,
pub sync_file: CloneCell<Option<SyncFile>>,
pub have_changes: Cell<bool>,
}
@ -270,6 +275,11 @@ impl HardwareCursor for MetalHardwareCursor {
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) {
if self.generation != self.connector.cursor_generation.get() {
return;
@ -285,6 +295,7 @@ impl HardwareCursor for MetalHardwareCursor {
if self.cursor_swap_buffer.take() {
self.connector.cursor_swap_buffer.set(true);
}
self.connector.cursor_sync_file.set(self.sync_file.take());
self.connector.cursor_changed.set(true);
if self.connector.can_present.get() {
self.connector.schedule_present();
@ -350,9 +361,10 @@ pub struct DirectScanoutCache {
#[derive(Debug)]
pub struct DirectScanoutData {
tex: Rc<dyn GfxTexture>,
acquire_sync: AcquireSync,
_resv: Option<Rc<dyn BufferResv>>,
fb: Rc<DrmFramebuffer>,
dma_buf_id: DmaBufId,
acquired: Cell<bool>,
position: DirectScanoutPosition,
}
@ -366,25 +378,20 @@ pub struct DirectScanoutPosition {
pub crtc_height: i32,
}
impl Drop for DirectScanoutData {
fn drop(&mut self) {
if self.acquired.replace(false) {
self.tex.reservations().release();
}
}
}
#[derive(Debug)]
pub struct PresentFb {
fb: Rc<DrmFramebuffer>,
direct_scanout_data: Option<DirectScanoutData>,
sync_file: Option<SyncFile>,
}
impl MetalConnector {
async fn present_loop(self: Rc<Self>) {
loop {
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_y_pending: Cell::new(self.cursor_y.get()),
cursor_buffers: cp.clone(),
sync_file: Default::default(),
have_changes: Cell::new(false),
}) as _),
_ => None,
@ -480,6 +488,10 @@ impl MetalConnector {
}
ct
};
if let AcquireSync::None = ct.acquire_sync {
// Cannot perform scanout without sync.
return None;
}
if ct.source.buffer_transform != ct.target.output_transform {
// Rotations and mirroring are not supported.
return None;
@ -532,9 +544,10 @@ impl MetalConnector {
if let Some(buffer) = cache.get(&dmabuf.id) {
return buffer.fb.as_ref().map(|fb| DirectScanoutData {
tex: buffer.tex.upgrade().unwrap(),
acquire_sync: ct.acquire_sync.clone(),
_resv: ct.buffer_resv.clone(),
fb: fb.clone(),
dma_buf_id: dmabuf.id,
acquired: Default::default(),
position,
});
}
@ -556,9 +569,10 @@ impl MetalConnector {
let data = match self.dev.master.add_fb(dmabuf, Some(format.format)) {
Ok(fb) => Some(DirectScanoutData {
tex: ct.tex.clone(),
acquire_sync: ct.acquire_sync.clone(),
_resv: ct.buffer_resv.clone(),
fb: Rc::new(fb),
dma_buf_id: dmabuf.id,
acquired: Default::default(),
position,
}),
Err(e) => {
@ -593,7 +607,7 @@ impl MetalConnector {
plane: &Rc<MetalPlane>,
output: &OutputNode,
try_direct_scanout: bool,
) -> PresentFb {
) -> Result<PresentFb, MetalError> {
self.trim_scanout_cache();
let buffer_fb = buffer.render_fb();
let render_hw_cursor = !self.cursor_enabled.get();
@ -636,25 +650,36 @@ impl MetalConnector {
};
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 => {
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);
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);
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,
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() {
Some(crtc) => crtc,
_ => return Ok(()),
@ -684,7 +709,7 @@ impl MetalConnector {
let buffer = &buffers[self.next_buffer.get() % buffers.len()];
let mut rr = self.render_result.borrow_mut();
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();
let (crtc_x, crtc_y, crtc_w, crtc_h, src_width, src_height) =
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| {
c.change(plane.fb_id, fb.fb.id().0 as _);
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_w.id, crtc_w 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);
}
}
let mut cursor_swap_buffer = false;
let mut cursor_sync_file = None;
if self.cursor_changed.get() && cursor.is_some() {
let plane = cursor.unwrap();
if self.cursor_enabled.get() {
let swap_buffer = self.cursor_swap_buffer.take();
if swap_buffer {
self.cursor_front_buffer.fetch_add(1);
cursor_swap_buffer = self.cursor_swap_buffer.get();
let mut front_buffer = self.cursor_front_buffer.get();
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 buffer = &buffers[self.cursor_front_buffer.get() % buffers.len()];
if swap_buffer {
if let Some(tex) = &buffer.dev_tex {
buffer.dev_fb.copy_texture(tex, 0, 0);
}
let buffer = &buffers[front_buffer % buffers.len()];
if cursor_swap_buffer {
cursor_sync_file = buffer.copy_to_dev(cursor_sync_file)?;
}
let in_fence = cursor_sync_file.as_ref().map(|s| s.raw()).unwrap_or(-1);
let (width, height) = buffer.dev_fb.physical_size();
changes.change_object(plane.id, |c| {
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_w.id, (width as u64) << 16);
c.change(plane.src_h.id, (height as u64) << 16);
c.change(plane.in_fence_fd, in_fence as u64);
});
} else {
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) {
match e {
DrmError::Atomic(OsError(c::EACCES)) => {
log::debug!("Could not perform atomic commit, likely because we're no longer the DRM master");
}
_ => 'handle_failure: {
if let Some(fb) = &new_fb {
if let Some(dsd) = &fb.direct_scanout_data {
if self.present(false).is_ok() {
let mut cache = self.scanout_buffers.borrow_mut();
if let Some(buffer) = cache.remove(&dsd.dma_buf_id) {
cache.insert(
dsd.dma_buf_id,
DirectScanoutCache {
tex: buffer.tex,
fb: None,
},
);
}
break 'handle_failure;
}
if let DrmError::Atomic(OsError(c::EACCES)) = e {
log::debug!("Could not perform atomic commit, likely because we're no longer the DRM master");
return Ok(());
}
if let Some(fb) = &new_fb {
if let Some(dsd) = &fb.direct_scanout_data {
if self.present(false).is_ok() {
let mut cache = self.scanout_buffers.borrow_mut();
if let Some(buffer) = cache.remove(&dsd.dma_buf_id) {
cache.insert(
dsd.dma_buf_id,
DirectScanoutCache {
tex: buffer.tex,
fb: None,
},
);
}
return Ok(());
}
log::error!("Could not set plane framebuffer: {}", ErrorFmt(e));
}
}
Err(())
Err(MetalError::Commit(e))
} else {
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));
}
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.has_damage.set(false);
self.cursor_changed.set(false);
@ -1036,6 +1064,7 @@ fn create_connector(
cursor_changed: Cell::new(false),
cursor_front_buffer: Default::default(),
cursor_swap_buffer: Cell::new(false),
cursor_sync_file: Default::default(),
drm_feedback: Default::default(),
scanout_buffers: Default::default(),
active_framebuffer: Default::default(),
@ -2166,7 +2195,7 @@ impl MetalBackend {
Ok(fb) => fb,
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 render_tex = match dev_img.to_texture() {
Ok(fb) => fb,
@ -2215,7 +2244,7 @@ impl MetalBackend {
Ok(fb) => fb,
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() {
Ok(fb) => fb,
Err(e) => return Err(MetalError::ImportTexture(e)),
@ -2435,6 +2464,16 @@ impl RenderBuffer {
.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 {

View file

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

View file

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

View file

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

View file

@ -38,6 +38,7 @@ use {
oserror::OsError, queue::AsyncQueue, refcounted::RefCounted, run_toplevel::RunToplevel,
tri::Try,
},
video::drm::wait_for_sync_obj::WaitForSyncObj,
wheel::{Wheel, WheelError},
xkbcommon::XkbContext,
},
@ -221,6 +222,9 @@ fn start_compositor2(
double_click_interval_usec: Cell::new(400 * 1000),
double_click_distance: Cell::new(5),
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));
create_dummy_output(&state);

View file

@ -800,6 +800,10 @@ impl ConfigProxyHandler {
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> {
let connector = self.get_connector(connector)?;
self.respond(Response::ConnectorConnected {
@ -1725,6 +1729,9 @@ impl ConfigProxyHandler {
} => self
.handle_move_to_output(workspace, connector)
.wrn("move_to_output")?,
ClientMessage::SetExplicitSyncEnabled { enabled } => {
self.handle_set_explicit_sync_enabled(enabled)
}
}
Ok(())
}

View file

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

View file

@ -9,8 +9,8 @@ use {
state::State,
theme::Color,
tree::{Node, OutputNode},
utils::{numcell::NumCell, transform_ext::TransformExt},
video::{dmabuf::DmaBuf, gbm::GbmDevice, Modifier},
utils::{clonecell::UnsafeCellCloneSafe, transform_ext::TransformExt},
video::{dmabuf::DmaBuf, drm::sync_obj::SyncObjCtx, gbm::GbmDevice, Modifier},
},
ahash::AHashMap,
indexmap::IndexSet,
@ -21,9 +21,12 @@ use {
error::Error,
ffi::CString,
fmt::{Debug, Formatter},
ops::Deref,
rc::Rc,
sync::atomic::{AtomicU64, Ordering::Relaxed},
},
thiserror::Error,
uapi::OwnedFd,
};
pub enum GfxApiOpt {
@ -141,6 +144,72 @@ pub struct CopyTexture {
pub tex: Rc<dyn GfxTexture>,
pub source: SampleRect,
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)]
@ -158,7 +227,11 @@ pub trait GfxFramebuffer: Debug {
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(
self: Rc<Self>,
@ -175,13 +248,13 @@ pub trait GfxFramebuffer: Debug {
}
impl dyn GfxFramebuffer {
pub fn clear(&self) {
self.clear_with(0.0, 0.0, 0.0, 0.0);
pub fn clear(&self) -> Result<Option<SyncFile>, GfxError> {
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();
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) {
@ -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 scale = Scale::from_int(1);
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);
self.render(ops, clear);
self.render(ops, clear)
}
pub fn render_custom(
@ -220,11 +311,11 @@ impl dyn GfxFramebuffer {
scale: Scale,
clear: Option<&Color>,
f: &mut dyn FnMut(&mut RendererBase),
) {
) -> Result<Option<SyncFile>, GfxError> {
let mut ops = self.take_render_ops();
let mut renderer = self.renderer_base(&mut ops, scale, Transform::None);
f(&mut renderer);
self.render(ops, clear);
self.render(ops, clear)
}
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())
}
@ -307,7 +398,7 @@ impl dyn GfxFramebuffer {
result: Option<&mut RenderResult>,
scale: Scale,
render_hardware_cursor: bool,
) {
) -> Result<Option<SyncFile>, GfxError> {
self.render_node(
node,
state,
@ -330,7 +421,7 @@ impl dyn GfxFramebuffer {
render_hardware_cursor: bool,
black_background: bool,
transform: Transform,
) {
) -> Result<Option<SyncFile>, GfxError> {
let pass = self.create_render_pass(
node,
state,
@ -341,7 +432,7 @@ impl dyn GfxFramebuffer {
black_background,
transform,
);
self.perform_render_pass(pass);
self.perform_render_pass(pass)
}
pub fn render_hardware_cursor(
@ -350,7 +441,7 @@ impl dyn GfxFramebuffer {
state: &State,
scale: Scale,
transform: Transform,
) {
) -> Result<Option<SyncFile>, GfxError> {
let mut ops = self.take_render_ops();
let mut renderer = Renderer {
base: self.renderer_base(&mut ops, scale, transform),
@ -363,7 +454,7 @@ impl dyn GfxFramebuffer {
},
};
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;
}
#[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 {
fn size(&self) -> (i32, i32);
fn as_any(&self) -> &dyn Any;
@ -423,7 +482,6 @@ pub trait GfxTexture: Debug {
shm: &[Cell<u8>],
) -> Result<(), GfxError>;
fn dmabuf(&self) -> Option<&DmaBuf>;
fn reservations(&self) -> &TextureReservations;
fn format(&self) -> &'static Format;
}
@ -461,6 +519,8 @@ pub trait GfxContext: Debug {
stride: i32,
format: &'static Format,
) -> Result<Rc<dyn GfxFramebuffer>, GfxError>;
fn sync_obj_ctx(&self) -> &Rc<SyncObjCtx>;
}
#[derive(Debug)]

View file

@ -68,8 +68,8 @@ macro_rules! dynload {
use {
crate::{
gfx_api::{
CopyTexture, FillRect, FramebufferRect, GfxApiOpt, GfxContext, GfxError, GfxTexture,
SampleRect,
AcquireSync, CopyTexture, FillRect, GfxApiOpt, GfxContext, GfxError, GfxTexture,
ReleaseSync, SyncFile,
},
gfx_apis::gl::{
gl::texture::image_target,
@ -80,8 +80,9 @@ use {
},
},
theme::Color,
utils::{rc_eq::rc_eq, vecstorage::VecStorage},
utils::{errorfmt::ErrorFmt, rc_eq::rc_eq, vecstorage::VecStorage},
video::{
dmabuf::DMA_BUF_SYNC_READ,
drm::{Drm, DrmError},
gbm::GbmError,
},
@ -181,6 +182,14 @@ enum RenderError {
NoSupportedFormats,
#[error("Cannot convert a shm texture into a framebuffer")]
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)]
@ -190,7 +199,7 @@ struct GfxGlState {
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 state = &mut *state;
let mut fill_rect = state.fill_rect.take();
@ -256,9 +265,30 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) {
}
}
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) {
@ -280,15 +310,13 @@ fn fill_boxes3(ctx: &GlRenderContext, boxes: &[[f32; 2]], color: &Color) {
}
}
fn render_texture(
ctx: &GlRenderContext,
texture: &Texture,
target_rect: &FramebufferRect,
src: &SampleRect,
) {
fn render_texture(ctx: &GlRenderContext, tex: &CopyTexture) {
let texture = tex.tex.as_gl();
assert!(rc_eq(&ctx.ctx, &texture.ctx.ctx));
let gles = ctx.ctx.dpy.gles;
unsafe {
handle_explicit_sync(ctx, texture, &tex.acquire_sync);
(gles.glActiveTexture)(GL_TEXTURE0);
let target = image_target(texture.gl.external_only);
@ -321,8 +349,8 @@ fn render_texture(
(gles.glUniform1i)(prog.tex, 0);
let texcoord = src.to_points();
let pos = target_rect.to_points();
let texcoord = tex.source.to_points();
let pos = tex.target.to_points();
(gles.glVertexAttribPointer)(
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 {
fn as_gl(&self) -> &Texture {
self.as_any()

View file

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

View file

@ -6,6 +6,7 @@ pub type EGLBoolean = c::c_uint;
#[allow(dead_code)]
pub type EGLuint64KHR = u64;
pub type EGLAttrib = isize;
pub type EGLSyncKHR = *mut u8;
egl_transparent!(EGLDisplay);
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_IMAGE_PRESERVED_KHR: EGLint = 0x30D2;
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! {
EGL: Egl from "libEGL.so" {

View file

@ -78,6 +78,9 @@ bitflags! {
KHR_SURFACELESS_CONTEXT = 1 << 5,
IMG_CONTEXT_PRIORITY = 1 << 6,
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 {
@ -96,6 +99,9 @@ pub(crate) unsafe fn get_display_ext(dpy: EGLDisplay) -> DisplayExt {
"EGL_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) {
Some(exts) => get_typed_ext(&exts, DisplayExt::none(), &map),

View file

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

View file

@ -2,8 +2,8 @@ use {
crate::{
format::{Format, XRGB8888},
gfx_api::{
GfxApiOpt, GfxContext, GfxError, GfxFormat, GfxFramebuffer, GfxImage, GfxTexture,
ResetStatus,
BufferResvUser, GfxApiOpt, GfxContext, GfxError, GfxFormat, GfxFramebuffer, GfxImage,
GfxTexture, ResetStatus,
},
gfx_apis::gl::{
egl::{context::EglContext, display::EglDisplay, image::EglImage},
@ -14,7 +14,11 @@ use {
renderer::{framebuffer::Framebuffer, image::Image},
GfxGlState, RenderError, Texture,
},
video::{dmabuf::DmaBuf, drm::Drm, gbm::GbmDevice},
video::{
dmabuf::DmaBuf,
drm::{sync_obj::SyncObjCtx, Drm},
gbm::GbmDevice,
},
},
ahash::AHashMap,
jay_config::video::GfxApi,
@ -53,6 +57,7 @@ pub(crate) struct TexProgs {
pub(in crate::gfx_apis::gl) struct GlRenderContext {
pub(crate) ctx: Rc<EglContext>,
pub gbm: Rc<GbmDevice>,
pub sync_ctx: Rc<SyncObjCtx>,
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(in crate::gfx_apis::gl) gl_state: RefCell<GfxGlState>,
pub(in crate::gfx_apis::gl) buffer_resv_user: BufferResvUser,
}
impl Debug for GlRenderContext {
@ -126,6 +133,7 @@ impl GlRenderContext {
Ok(Self {
ctx: ctx.clone(),
gbm: ctx.dpy.gbm.clone(),
sync_ctx: Rc::new(SyncObjCtx::new(ctx.dpy.gbm.drm.fd())),
render_node: node.clone(),
@ -141,6 +149,8 @@ impl GlRenderContext {
gfx_ops: Default::default(),
gl_state: Default::default(),
buffer_resv_user: Default::default(),
})
}
@ -186,7 +196,6 @@ impl GlRenderContext {
Ok(Rc::new(Texture {
ctx: self.clone(),
gl,
resv: Default::default(),
format,
}))
}
@ -268,4 +277,8 @@ impl GfxContext for GlRenderContext {
})?;
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 {
crate::{
format::Format,
gfx_api::{GfxApiOpt, GfxError, GfxFramebuffer},
gfx_api::{GfxApiOpt, GfxError, GfxFramebuffer, SyncFile},
gfx_apis::gl::{
gl::{
frame_buffer::GlFrameBuffer,
@ -10,6 +10,7 @@ use {
renderer::context::GlRenderContext,
run_ops,
sys::{GL_ONE, GL_ONE_MINUS_SRC_ALPHA},
RenderError,
},
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 _ = self.ctx.ctx.with_current(|| {
let res = self.ctx.ctx.with_current(|| {
unsafe {
(gles.glBindFramebuffer)(GL_FRAMEBUFFER, self.gl.fbo);
(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);
}
run_ops(self, &ops);
unsafe {
(gles.glFlush)();
let fd = run_ops(self, &ops);
if fd.is_none() {
unsafe {
(gles.glFlush)();
}
}
Ok(())
Ok(fd)
});
ops.clear();
*self.ctx.gfx_ops.borrow_mut() = ops;
res
}
}
@ -92,17 +101,19 @@ impl GfxFramebuffer for Framebuffer {
}
fn take_render_ops(&self) -> Vec<GfxApiOpt> {
let mut ops = mem::take(&mut *self.ctx.gfx_ops.borrow_mut());
ops.clear();
ops
mem::take(&mut *self.ctx.gfx_ops.borrow_mut())
}
fn physical_size(&self) -> (i32, i32) {
(self.gl.width, self.gl.height)
}
fn render(&self, ops: Vec<GfxApiOpt>, clear: Option<&Color>) {
self.render(ops, clear);
fn render(
&self,
ops: Vec<GfxApiOpt>,
clear: Option<&Color>,
) -> Result<Option<SyncFile>, GfxError> {
self.render(ops, clear).map_err(|e| e.into())
}
fn copy_to_shm(

View file

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

View file

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

View file

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

View file

@ -1,5 +1,8 @@
use {
crate::gfx_apis::vulkan::{device::VulkanDevice, VulkanError},
crate::{
gfx_api::SyncFile,
gfx_apis::vulkan::{device::VulkanDevice, VulkanError},
},
ash::vk::{
ExportFenceCreateInfo, ExternalFenceHandleTypeFlags, Fence, FenceCreateInfo,
FenceGetFdInfoKHR,
@ -38,12 +41,12 @@ impl VulkanDevice {
}
impl VulkanFence {
pub fn export_syncfile(&self) -> Result<Rc<OwnedFd>, VulkanError> {
pub fn export_sync_file(&self) -> Result<SyncFile, VulkanError> {
let info = FenceGetFdInfoKHR::builder()
.fence(self.fence)
.handle_type(ExternalFenceHandleTypeFlags::SYNC_FD);
let res = unsafe { self.device.external_fence_fd.get_fence_fd(&info) };
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 {
crate::{
format::Format,
gfx_api::{GfxApiOpt, GfxError, GfxFramebuffer, GfxImage, GfxTexture, TextureReservations},
gfx_api::{GfxApiOpt, GfxError, GfxFramebuffer, GfxImage, GfxTexture, SyncFile},
gfx_apis::vulkan::{
allocator::VulkanAllocation, device::VulkanDevice, format::VulkanMaxExtents,
renderer::VulkanRenderer, util::OnDrop, VulkanError,
@ -53,7 +53,6 @@ pub struct VulkanImage {
pub(super) is_undefined: Cell<bool>,
pub(super) ty: VulkanImageMemory,
pub(super) render_ops: CloneCell<Vec<GfxApiOpt>>,
pub(super) resv: TextureReservations,
}
pub enum VulkanImageMemory {
@ -212,7 +211,6 @@ impl VulkanRenderer {
is_undefined: Cell::new(true),
ty: VulkanImageMemory::Internal(shm),
render_ops: Default::default(),
resv: Default::default(),
}))
}
@ -482,7 +480,6 @@ impl VulkanDmaBufImageTemplate {
}),
format: self.dmabuf.format,
is_undefined: Cell::new(true),
resv: Default::default(),
}))
}
}
@ -528,8 +525,14 @@ impl GfxFramebuffer for VulkanImage {
(self.width as _, self.height as _)
}
fn render(&self, ops: Vec<GfxApiOpt>, clear: Option<&Color>) {
self.renderer.execute(self, &ops, clear).unwrap();
fn render(
&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(
@ -587,10 +590,6 @@ impl GfxTexture for VulkanImage {
}
}
fn reservations(&self) -> &TextureReservations {
&self.resv
}
fn format(&self) -> &'static Format {
self.format
}

View file

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

View file

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

View file

@ -36,9 +36,9 @@ impl VulkanDevice {
}
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()
.fd(syncfile.raw())
.fd(sync_file.raw())
.flags(SemaphoreImportFlags::TEMPORARY)
.handle_type(ExternalSemaphoreHandleTypeFlags::SYNC_FD)
.semaphore(self.semaphore);
@ -47,8 +47,8 @@ impl VulkanSemaphore {
.external_semaphore_fd
.import_semaphore_fd(&fd_info)
};
mem::forget(syncfile);
res.map_err(VulkanError::ImportSyncFile)?;
mem::forget(sync_file);
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_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_feedback;
pub mod wp_single_pixel_buffer_manager_v1;

View file

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

View file

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

View file

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

View file

@ -1,7 +1,9 @@
pub mod commit_timeline;
pub mod cursor;
pub mod ext_session_lock_surface_v1;
pub mod wl_subsurface;
pub mod wp_fractional_scale_v1;
pub mod wp_linux_drm_syncobj_surface_v1;
pub mod wp_tearing_control_v1;
pub mod wp_viewport;
pub mod x_surface;
@ -16,7 +18,7 @@ use {
client::{Client, ClientError, RequestParser},
drm_feedback::DrmFeedback,
fixed::Fixed,
gfx_api::SampleRect,
gfx_api::{AcquireSync, BufferResv, BufferResvUser, ReleaseSync, SampleRect, SyncFile},
ifs::{
wl_buffer::WlBuffer,
wl_callback::WlCallback,
@ -25,11 +27,16 @@ use {
NodeSeatState, SeatId, WlSeatGlobal,
},
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_tearing_control_v1::WpTearingControlV1, wp_viewport::WpViewport,
x_surface::XSurface, xdg_surface::XdgSurfaceError,
zwlr_layer_surface_v1::ZwlrLayerSurfaceV1Error,
wp_linux_drm_syncobj_surface_v1::WpLinuxDrmSyncobjSurfaceV1,
wp_tearing_control_v1::WpTearingControlV1,
wp_viewport::WpViewport,
x_surface::XSurface,
xdg_surface::{PendingXdgSurfaceData, XdgSurfaceError},
zwlr_layer_surface_v1::{PendingLayerSurfaceData, ZwlrLayerSurfaceV1Error},
},
wp_content_type_v1::ContentType,
wp_presentation_feedback::WpPresentationFeedback,
@ -48,11 +55,16 @@ use {
cell_ext::CellExt,
clonecell::CloneCell,
copyhashmap::CopyHashMap,
errorfmt::ErrorFmt,
linkedlist::LinkedList,
numcell::NumCell,
smallmap::SmallMap,
transform_ext::TransformExt,
},
video::{
dmabuf::DMA_BUF_SYNC_READ,
drm::sync_obj::{SyncObj, SyncObjPoint},
},
wire::{
wl_surface::*, WlOutputId, WlSurfaceId, ZwpIdleInhibitorV1Id,
ZwpLinuxDmabufFeedbackV1Id,
@ -61,9 +73,11 @@ use {
xwayland::XWaylandEvent,
},
ahash::AHashMap,
isnt::std_1::primitive::IsntSliceExt,
jay_config::video::Transform,
std::{
cell::{Cell, RefCell},
collections::hash_map::{Entry, OccupiedEntry},
fmt::{Debug, Formatter},
mem,
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 id: WlSurfaceId,
pub node_id: SurfaceNodeId,
pub client: Rc<Client>,
visible: Cell<bool>,
role: Cell<SurfaceRole>,
pending: PendingState,
pending: RefCell<Box<PendingState>>,
input_region: CloneCell<Option<Rc<Region>>>,
opaque_region: Cell<Option<Rc<Region>>>,
buffer_points: RefCell<BufferPoints>,
@ -145,7 +224,7 @@ pub struct WlSurface {
pub extents: Cell<Rect>,
pub buffer_abs_pos: Cell<Rect>,
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_y: NumCell<i32>,
pub children: RefCell<Option<Box<ParentData>>>,
@ -169,6 +248,9 @@ pub struct WlSurface {
pub has_content_type_manager: Cell<bool>,
content_type: Cell<Option<ContentType>>,
pub drm_feedback: CopyHashMap<ZwpLinuxDmabufFeedbackV1Id, Rc<ZwpLinuxDmabufFeedbackV1>>,
sync_obj_surface: CloneCell<Option<Rc<WpLinuxDrmSyncobjSurfaceV1>>>,
destroyed: Cell<bool>,
commit_timeline: CommitTimeline,
}
impl Debug for WlSurface {
@ -185,12 +267,6 @@ struct BufferPoints {
y2: f32,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum CommitContext {
RootCommit,
ChildCommit,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum CommitAction {
ContinueCommit,
@ -198,13 +274,21 @@ enum CommitAction {
}
trait SurfaceExt {
fn pre_commit(self: Rc<Self>, ctx: CommitContext) -> Result<CommitAction, WlSurfaceError> {
let _ = ctx;
Ok(CommitAction::ContinueCommit)
fn commit_requested(self: Rc<Self>, pending: &mut Box<PendingState>) -> CommitAction {
let _ = pending;
CommitAction::ContinueCommit
}
fn post_commit(self: Rc<Self>) {
// nothing
fn before_apply_commit(
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 {
@ -246,6 +330,17 @@ trait SurfaceExt {
fn into_xsurface(self: Rc<Self>) -> Option<Rc<XSurface>> {
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;
@ -258,20 +353,122 @@ impl SurfaceExt for NoneSurfaceExt {
#[derive(Default)]
struct PendingState {
buffer: Cell<Option<Option<Rc<WlBuffer>>>>,
offset: Cell<(i32, i32)>,
opaque_region: Cell<Option<Option<Rc<Region>>>>,
input_region: Cell<Option<Option<Rc<Region>>>>,
frame_request: RefCell<Vec<Rc<WlCallback>>>,
damage: Cell<bool>,
presentation_feedback: RefCell<Vec<Rc<WpPresentationFeedback>>>,
src_rect: Cell<Option<Option<[Fixed; 4]>>>,
dst_size: Cell<Option<Option<(i32, i32)>>>,
scale: Cell<Option<i32>>,
transform: Cell<Option<Transform>>,
xwayland_serial: Cell<Option<u64>>,
tearing: Cell<Option<bool>>,
content_type: Cell<Option<Option<ContentType>>>,
buffer: Option<Option<Rc<WlBuffer>>>,
offset: (i32, i32),
opaque_region: Option<Option<Rc<Region>>>,
input_region: Option<Option<Rc<Region>>>,
frame_request: Vec<Rc<WlCallback>>,
damage: bool,
presentation_feedback: Vec<Rc<WpPresentationFeedback>>,
src_rect: Option<Option<[Fixed; 4]>>,
dst_size: Option<Option<(i32, i32)>>,
scale: Option<i32>,
transform: Option<Transform>,
xwayland_serial: Option<u64>,
tearing: Option<bool>,
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)]
@ -330,6 +527,9 @@ impl WlSurface {
has_content_type_manager: Default::default(),
content_type: 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>) {
self.pending
.presentation_feedback
.borrow_mut()
.presentation_feedback
.push(fb.clone());
}
@ -558,6 +758,7 @@ impl WlSurface {
fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> {
let _req: Destroy = self.parse(parser)?;
self.commit_timeline.clear(ClearReason::Destroy);
self.unset_dnd_icons();
self.unset_cursors();
self.ext.get().on_surface_destroy()?;
@ -571,11 +772,7 @@ impl WlSurface {
}
*children = None;
}
if let Some(buffer) = self.buffer.set(None) {
if !buffer.destroyed() {
buffer.send_release();
}
}
self.buffer.set(None);
if let Some(xwayland_serial) = self.xwayland_serial.get() {
self.client
.surfaces_by_xwayland_serial
@ -586,30 +783,32 @@ impl WlSurface {
self.client.remove_obj(self)?;
self.idle_inhibitors.clear();
self.constraints.take();
self.destroyed.set(true);
Ok(())
}
fn attach(self: &Rc<Self>, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> {
let req: Attach = self.parse(parser)?;
let pending = &mut *self.pending.borrow_mut();
if self.version >= OFFSET_SINCE {
if req.x != 0 || req.y != 0 {
return Err(WlSurfaceError::OffsetInAttach);
}
} else {
self.pending.offset.set((req.x, req.y));
pending.offset = (req.x, req.y);
}
let buf = if req.buffer.is_some() {
Some(self.client.lookup(req.buffer)?)
} else {
None
};
self.pending.buffer.set(Some(buf));
pending.buffer = Some(buf);
Ok(())
}
fn damage(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> {
let _req: Damage = self.parse(parser)?;
self.pending.damage.set(true);
self.pending.borrow_mut().damage = true;
Ok(())
}
@ -618,7 +817,7 @@ impl WlSurface {
let cb = Rc::new(WlCallback::new(req.callback, &self.client));
track!(self.client, cb);
self.client.add_client_obj(&cb)?;
self.pending.frame_request.borrow_mut().push(cb);
self.pending.borrow_mut().frame_request.push(cb);
Ok(())
}
@ -629,7 +828,7 @@ impl WlSurface {
} else {
None
};
self.pending.opaque_region.set(Some(region));
self.pending.borrow_mut().opaque_region = Some(region);
Ok(())
}
@ -640,39 +839,37 @@ impl WlSurface {
} else {
None
};
self.pending.input_region.set(Some(region));
self.pending.borrow_mut().input_region = Some(region);
Ok(())
}
fn do_commit(self: &Rc<Self>, ctx: CommitContext) -> Result<(), WlSurfaceError> {
let ext = self.ext.get();
if ext.clone().pre_commit(ctx)? == CommitAction::AbortCommit {
fn apply_state(self: &Rc<Self>, pending: &mut PendingState) -> Result<(), WlSurfaceError> {
for (_, mut subsurface) in pending.subsurfaces.drain() {
subsurface
.subsurface
.surface
.apply_state(&mut subsurface.state)?;
}
if self.destroyed.get() {
return Ok(());
}
{
let children = self.children.borrow();
if let Some(children) = children.deref() {
for child in children.subsurfaces.values() {
child.surface.do_commit(CommitContext::ChildCommit)?;
}
}
}
self.ext.get().before_apply_commit(pending)?;
let mut scale_changed = false;
if let Some(scale) = self.pending.scale.take() {
if let Some(scale) = pending.scale.take() {
scale_changed = true;
self.buffer_scale.set(scale);
}
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;
self.buffer_transform.set(transform);
}
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;
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;
self.src_rect.set(src_rect);
}
@ -687,34 +884,30 @@ impl WlSurface {
}
let mut buffer_changed = false;
let mut old_raw_size = None;
let (dx, dy) = self.pending.offset.take();
if let Some(buffer_change) = self.pending.buffer.take() {
let (dx, dy) = mem::take(&mut pending.offset);
if let Some(buffer_change) = pending.buffer.take() {
buffer_changed = true;
if let Some(buffer) = self.buffer.take() {
old_raw_size = Some(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();
}
}
old_raw_size = Some(buffer.buffer.rect);
}
if let Some(buffer) = buffer_change {
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_y.fetch_add(dy);
if (dx, dy) != (0, 0) {
@ -764,8 +957,10 @@ impl WlSurface {
}
if let Some(buffer) = self.buffer.get() {
if new_size.is_none() {
let (mut width, mut height) =
self.buffer_transform.get().maybe_swap(buffer.rect.size());
let (mut width, mut height) = self
.buffer_transform
.get()
.maybe_swap(buffer.buffer.rect.size());
let scale = self.buffer_scale.get();
if scale != 1 {
width = (width + scale - 1) / scale;
@ -773,12 +968,14 @@ impl WlSurface {
}
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() {
(0.0, 0.0, 1.0, 1.0)
} else {
let (width, height) =
self.buffer_transform.get().maybe_swap(buffer.rect.size());
let (width, height) = self
.buffer_transform
.get()
.maybe_swap(buffer.buffer.rect.size());
let width = width as f32;
let height = height as f32;
let x1 = buffer_points.x1 / width;
@ -806,34 +1003,32 @@ impl WlSurface {
self.buffer_abs_pos
.set(self.buffer_abs_pos.get().with_size(width, height).unwrap());
}
{
let mut pfr = self.pending.frame_request.borrow_mut();
self.frame_requests.borrow_mut().extend(pfr.drain(..));
}
self.frame_requests
.borrow_mut()
.extend(pending.frame_request.drain(..));
{
let mut fbs = self.presentation_feedback.borrow_mut();
for fb in fbs.drain(..) {
fb.send_discarded();
let _ = self.client.remove_obj(&*fb);
}
let mut pfbs = self.pending.presentation_feedback.borrow_mut();
mem::swap(fbs.deref_mut(), pfbs.deref_mut());
mem::swap(fbs.deref_mut(), &mut pending.presentation_feedback);
}
{
if let Some(region) = self.pending.input_region.take() {
if let Some(region) = pending.input_region.take() {
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);
}
}
if let Some(tearing) = self.pending.tearing.take() {
if let Some(tearing) = pending.tearing.take() {
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);
}
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.client
.surfaces_by_xwayland_serial
@ -853,23 +1048,49 @@ impl WlSurface {
cursor.update_hardware_cursor();
}
}
ext.post_commit();
self.ext.get().after_apply_commit(pending);
self.client.state.damage();
Ok(())
}
fn commit(self: &Rc<Self>, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> {
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(())
}
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> {
let req: SetBufferTransform = self.parse(parser)?;
let Some(tf) = Transform::from_wl(req.transform) else {
return Err(WlSurfaceError::UnknownBufferTransform(req.transform));
};
self.pending.transform.set(Some(tf));
self.pending.borrow_mut().transform = Some(tf);
Ok(())
}
@ -878,19 +1099,19 @@ impl WlSurface {
if req.scale < 1 {
return Err(WlSurfaceError::NonPositiveBufferScale);
}
self.pending.scale.set(Some(req.scale));
self.pending.borrow_mut().scale = Some(req.scale);
Ok(())
}
fn damage_buffer(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> {
let _req: DamageBuffer = self.parse(parser)?;
self.pending.damage.set(true);
self.pending.borrow_mut().damage = true;
Ok(())
}
fn offset(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> {
let req: Offset = self.parse(parser)?;
self.pending.offset.set((req.x, req.y));
self.pending.borrow_mut().offset = (req.x, req.y);
Ok(())
}
@ -1021,7 +1242,7 @@ impl WlSurface {
}
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) {
@ -1035,6 +1256,18 @@ impl WlSurface {
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! {
@ -1064,13 +1297,14 @@ impl Object for WlSurface {
self.buffer.set(None);
self.toplevel.set(None);
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.viewporter.take();
self.fractional_scale.take();
self.tearing_control.take();
self.constraints.clear();
self.drm_feedback.clear();
self.commit_timeline.clear(ClearReason::BreakLoops);
}
}
@ -1237,8 +1471,15 @@ pub enum WlSurfaceError {
ViewportOutsideBuffer,
#[error("attach request must not contain offset")]
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, XdgSurfaceError);
efrom!(WlSurfaceError, ZwlrLayerSurfaceV1Error);
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::{
client::ClientError,
ifs::wl_surface::{
CommitAction, CommitContext, StackElement, SurfaceExt, SurfaceRole, WlSurface,
WlSurfaceError, WlSurfaceId,
CommitAction, CommittedSubsurface, PendingState, StackElement, SurfaceExt, SurfaceRole,
WlSurface, WlSurfaceError, WlSurfaceId,
},
leaks::Tracker,
object::Object,
rect::Rect,
utils::{
buffd::{MsgParser, MsgParserError},
linkedlist::LinkedNode,
clonecell::CloneCell,
linkedlist::{LinkedNode, NodeRef},
numcell::NumCell,
option_ext::OptionExt,
},
wire::{wl_subsurface::*, WlSubsurfaceId},
},
std::{
cell::{Cell, RefCell},
cell::{Cell, RefCell, RefMut},
collections::hash_map::{Entry, OccupiedEntry},
mem,
ops::Deref,
rc::Rc,
},
@ -28,36 +32,39 @@ const BAD_SURFACE: u32 = 0;
const MAX_SUBSURFACE_DEPTH: u32 = 100;
linear_ids!(SubsurfaceIds, SubsurfaceId, u64);
pub struct WlSubsurface {
id: WlSubsurfaceId,
unique_id: SubsurfaceId,
pub surface: Rc<WlSurface>,
pub(super) parent: Rc<WlSurface>,
pub position: Cell<Rect>,
sync_requested: Cell<bool>,
sync_ancestor: Cell<bool>,
node: RefCell<Option<LinkedNode<StackElement>>>,
latest_node: CloneCell<Option<NodeRef<StackElement>>>,
depth: NumCell<u32>,
pending: PendingSubsurfaceData,
pub tracker: Tracker<Self>,
}
#[derive(Default)]
struct PendingSubsurfaceData {
node: RefCell<Option<LinkedNode<StackElement>>>,
position: Cell<Option<(i32, i32)>>,
pub struct PendingSubsurfaceData {
node: Option<LinkedNode<StackElement>>,
position: Option<(i32, i32)>,
}
fn update_children_sync(surface: &WlSubsurface, sync: bool) {
let children = surface.surface.children.borrow();
if let Some(children) = &*children {
for child in children.subsurfaces.values() {
let was_sync = child.sync();
child.sync_ancestor.set(sync);
let is_sync = child.sync();
if was_sync != is_sync {
update_children_sync(child, sync);
}
impl PendingSubsurfaceData {
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!(node);
opt!(position);
}
}
@ -85,18 +92,25 @@ impl WlSubsurface {
pub fn new(id: WlSubsurfaceId, surface: &Rc<WlSurface>, parent: &Rc<WlSurface>) -> Self {
Self {
id,
unique_id: surface.client.state.subsurface_ids.next(),
surface: surface.clone(),
parent: parent.clone(),
position: Cell::new(Default::default()),
sync_requested: Cell::new(false),
sync_requested: Cell::new(true),
sync_ancestor: Cell::new(false),
node: RefCell::new(None),
latest_node: Default::default(),
depth: NumCell::new(0),
pending: 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> {
if self.surface.id == self.parent.id {
return Err(WlSubsurfaceError::OwnParent(self.surface.id));
@ -128,7 +142,8 @@ impl WlSubsurface {
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.sync_ancestor.set(sync_ancestor);
self.depth.set(depth);
@ -140,8 +155,12 @@ impl WlSubsurface {
fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSubsurfaceError> {
let _req: Destroy = self.surface.client.parse(self, parser)?;
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.latest_node.take();
{
let mut children = self.parent.children.borrow_mut();
if let Some(children) = &mut *children {
@ -164,7 +183,7 @@ impl WlSubsurface {
fn set_position(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSubsurfaceError> {
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(())
}
@ -188,19 +207,17 @@ impl WlSubsurface {
Some(s) => s,
_ => return Err(WlSubsurfaceError::NotASibling(sibling, self.surface.id)),
};
let node = match sibling.pending.node.borrow().deref() {
Some(n) => n.to_ref(),
_ => match sibling.node.borrow().deref() {
Some(n) => n.to_ref(),
_ => return Ok(()),
},
let sibling_node = match sibling.latest_node.get() {
Some(n) => n,
_ => return Ok(()),
};
match above {
true => node.append(element),
_ => node.prepend(element),
true => sibling_node.append(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(())
}
@ -221,24 +238,56 @@ impl WlSubsurface {
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();
self.sync_requested.set(sync);
let is_sync = self.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> {
let _req: SetSync = self.surface.client.parse(self, parser)?;
self.update_sync(true);
self.update_sync(true)?;
Ok(())
}
fn set_desync(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSubsurfaceError> {
let _req: SetDesync = self.surface.client.parse(self, parser)?;
self.update_sync(false);
self.update_sync(false)?;
Ok(())
}
}
@ -256,31 +305,44 @@ object_base! {
impl Object for WlSubsurface {
fn break_loops(&self) {
*self.pending.node.borrow_mut() = None;
*self.node.borrow_mut() = None;
self.latest_node.take();
}
}
simple_add_obj!(WlSubsurface);
impl SurfaceExt for WlSubsurface {
fn pre_commit(self: Rc<Self>, ctx: CommitContext) -> Result<CommitAction, WlSurfaceError> {
if ctx == CommitContext::RootCommit && self.sync() {
log::debug!("Aborting commit due to sync");
return Ok(CommitAction::AbortCommit);
fn commit_requested(self: Rc<Self>, pending: &mut Box<PendingState>) -> CommitAction {
if self.sync() {
let mut parent_pending = self.parent.pending.borrow_mut();
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>) {
if let Some(v) = self.pending.node.take() {
v.pending.set(false);
self.node.borrow_mut().replace(v);
}
if let Some((x, y)) = self.pending.position.take() {
self.position
.set(self.surface.buffer_abs_pos.get().at_point(x, y));
self.parent.need_extents_update.set(true);
fn after_apply_commit(self: Rc<Self>, pending: &mut PendingState) {
if let Some(pending) = &mut pending.subsurface {
if let Some(v) = pending.node.take() {
v.pending.set(false);
self.node.borrow_mut().replace(v);
}
if let Some((x, y)) = pending.position.take() {
self.position
.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>> {
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)]

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,
_ => return Err(WpTearingControlV1Error::UnknownPresentationHint(req.hint)),
};
self.surface.pending.tearing.set(Some(tearing));
self.surface.pending.borrow_mut().tearing = Some(tearing);
Ok(())
}
fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), WpTearingControlV1Error> {
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.client.remove_obj(self)?;
Ok(())

View file

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

View file

@ -2,7 +2,7 @@ use {
crate::{
ifs::wl_surface::{
x_surface::{xwayland_surface_v1::XwaylandSurfaceV1, xwindow::Xwindow},
SurfaceExt, WlSurface, WlSurfaceError,
PendingState, SurfaceExt, WlSurface, WlSurfaceError,
},
leaks::Tracker,
tree::ToplevelNode,
@ -23,7 +23,7 @@ pub struct 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() {
xwindow.map_status_changed();
}

View file

@ -32,7 +32,7 @@ impl XwaylandSurfaceV1 {
return Err(XwaylandSurfaceV1Error::NonMonotonicSerial);
}
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(())
}

View file

@ -10,7 +10,7 @@ use {
xdg_popup::{XdgPopup, XdgPopupError},
xdg_toplevel::{XdgToplevel, WM_CAPABILITIES_SINCE},
},
CommitAction, CommitContext, SurfaceExt, SurfaceRole, WlSurface, WlSurfaceError,
PendingState, SurfaceExt, SurfaceRole, WlSurface, WlSurfaceError,
},
xdg_wm_base::XdgWmBase,
},
@ -23,10 +23,15 @@ use {
clonecell::CloneCell,
copyhashmap::CopyHashMap,
numcell::NumCell,
option_ext::OptionExt,
},
wire::{xdg_surface::*, WlSurfaceId, XdgPopupId, XdgSurfaceId},
},
std::{cell::Cell, fmt::Debug, rc::Rc},
std::{
cell::{Cell, RefMut},
fmt::Debug,
rc::Rc,
},
thiserror::Error,
};
@ -65,14 +70,26 @@ pub struct XdgSurface {
pub absolute_desired_extents: Cell<Rect>,
ext: CloneCell<Option<Rc<dyn XdgSurfaceExt>>>,
popups: CopyHashMap<XdgPopupId, Rc<XdgPopup>>,
pending: PendingXdgSurfaceData,
pub workspace: CloneCell<Option<Rc<WorkspaceNode>>>,
pub tracker: Tracker<Self>,
}
#[derive(Default, Debug)]
struct PendingXdgSurfaceData {
geometry: Cell<Option<Rect>>,
pub struct PendingXdgSurfaceData {
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 {
@ -103,7 +120,6 @@ impl XdgSurface {
absolute_desired_extents: Cell::new(Default::default()),
ext: Default::default(),
popups: Default::default(),
pending: Default::default(),
workspace: Default::default(),
tracker: Default::default(),
}
@ -270,6 +286,12 @@ impl XdgSurface {
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> {
let req: SetWindowGeometry = self.surface.client.parse(self, parser)?;
if req.height == 0 && req.width == 0 {
@ -280,7 +302,7 @@ impl XdgSurface {
return Err(XdgSurfaceError::NonPositiveWidthHeight);
}
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(())
}
@ -357,7 +379,10 @@ impl Object for XdgSurface {
dedicated_add_obj!(XdgSurface, XdgSurfaceId, xdg_surfaces);
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 rse = self.requested_serial.get();
@ -368,17 +393,18 @@ impl SurfaceExt for XdgSurface {
}
self.send_configure(rse);
}
// return CommitAction::AbortCommit;
}
}
if let Some(geometry) = self.pending.geometry.take() {
self.geometry.set(Some(geometry));
self.update_extents();
if let Some(pending) = &mut pending.xdg_surface {
if let Some(geometry) = pending.geometry.take() {
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() {
ext.post_commit();
}

View file

@ -646,7 +646,8 @@ impl ToplevelNodeBase for XdgToplevel {
impl XdgSurfaceExt for XdgToplevel {
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(())
}

View file

@ -3,9 +3,7 @@ use {
client::{Client, ClientError},
ifs::{
wl_seat::NodeSeatState,
wl_surface::{
CommitAction, CommitContext, SurfaceExt, SurfaceRole, WlSurface, WlSurfaceError,
},
wl_surface::{PendingState, SurfaceExt, SurfaceRole, WlSurface, WlSurfaceError},
zwlr_layer_shell_v1::{ZwlrLayerShellV1, OVERLAY},
},
leaks::Tracker,
@ -19,10 +17,16 @@ use {
cell_ext::CellExt,
linkedlist::LinkedNode,
numcell::NumCell,
option_ext::OptionExt,
},
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,
};
@ -50,7 +54,6 @@ pub struct ZwlrLayerSurfaceV1 {
pos: Cell<Rect>,
mapped: Cell<bool>,
layer: Cell<u32>,
pending: Pending,
requested_serial: NumCell<u32>,
acked_serial: Cell<Option<u32>>,
size: Cell<(i32, i32)>,
@ -63,14 +66,33 @@ pub struct ZwlrLayerSurfaceV1 {
}
#[derive(Default)]
struct Pending {
size: Cell<Option<(i32, i32)>>,
anchor: Cell<Option<u32>>,
exclusive_zone: Cell<Option<i32>>,
margin: Cell<Option<(i32, i32, i32, i32)>>,
keyboard_interactivity: Cell<Option<u32>>,
layer: Cell<Option<u32>>,
any: Cell<bool>,
pub struct PendingLayerSurfaceData {
size: Option<(i32, i32)>,
anchor: Option<u32>,
exclusive_zone: Option<i32>,
margin: Option<(i32, i32, i32, i32)>,
keyboard_interactivity: Option<u32>,
layer: Option<u32>,
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 {
@ -95,7 +117,6 @@ impl ZwlrLayerSurfaceV1 {
pos: Default::default(),
mapped: Cell::new(false),
layer: Cell::new(layer),
pending: Default::default(),
requested_serial: Default::default(),
acked_serial: Cell::new(None),
size: Cell::new((0, 0)),
@ -131,15 +152,20 @@ impl ZwlrLayerSurfaceV1 {
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> {
let req: SetSize = self.client.parse(self, parser)?;
if req.width > u16::MAX as u32 || req.height > u16::MAX as u32 {
return Err(ZwlrLayerSurfaceV1Error::ExcessiveSize);
}
self.pending
.size
.set(Some((req.width as _, req.height as _)));
self.pending.any.set(true);
let mut pending = self.pending();
pending.size = Some((req.width as _, req.height as _));
pending.any = true;
Ok(())
}
@ -148,24 +174,25 @@ impl ZwlrLayerSurfaceV1 {
if req.anchor & !(LEFT | RIGHT | TOP | BOTTOM) != 0 {
return Err(ZwlrLayerSurfaceV1Error::UnknownAnchor(req.anchor));
}
self.pending.anchor.set(Some(req.anchor));
self.pending.any.set(true);
let mut pending = self.pending();
pending.anchor = Some(req.anchor);
pending.any = true;
Ok(())
}
fn set_exclusive_zone(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrLayerSurfaceV1Error> {
let req: SetExclusiveZone = self.client.parse(self, parser)?;
self.pending.exclusive_zone.set(Some(req.zone));
self.pending.any.set(true);
let mut pending = self.pending();
pending.exclusive_zone = Some(req.zone);
pending.any = true;
Ok(())
}
fn set_margin(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrLayerSurfaceV1Error> {
let req: SetMargin = self.client.parse(self, parser)?;
self.pending
.margin
.set(Some((req.top, req.right, req.bottom, req.left)));
self.pending.any.set(true);
let mut pending = self.pending();
pending.margin = Some((req.top, req.right, req.bottom, req.left));
pending.any = true;
Ok(())
}
@ -179,10 +206,9 @@ impl ZwlrLayerSurfaceV1 {
req.keyboard_interactivity,
));
}
self.pending
.keyboard_interactivity
.set(Some(req.keyboard_interactivity));
self.pending.any.set(true);
let mut pending = self.pending();
pending.keyboard_interactivity = Some(req.keyboard_interactivity);
pending.any = true;
Ok(())
}
@ -210,29 +236,31 @@ impl ZwlrLayerSurfaceV1 {
if req.layer > OVERLAY {
return Err(ZwlrLayerSurfaceV1Error::UnknownLayer(req.layer));
}
self.pending.layer.set(Some(req.layer));
self.pending.any.set(true);
let mut pending = self.pending();
pending.layer = Some(req.layer);
pending.any = true;
Ok(())
}
fn pre_commit(&self) -> Result<(), ZwlrLayerSurfaceV1Error> {
let mut send_configure = self.pending.any.replace(false);
if let Some(size) = self.pending.size.take() {
fn pre_commit(&self, pending: &mut PendingState) -> Result<(), ZwlrLayerSurfaceV1Error> {
let pending = pending.layer_surface.get_or_insert_default_ext();
let mut send_configure = mem::replace(&mut pending.any, false);
if let Some(size) = pending.size.take() {
self.size.set(size);
}
if let Some(anchor) = self.pending.anchor.take() {
if let Some(anchor) = pending.anchor.take() {
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);
}
if let Some(margin) = self.pending.margin.take() {
if let Some(margin) = pending.margin.take() {
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);
}
if let Some(layer) = self.pending.layer.take() {
if let Some(layer) = pending.layer.take() {
self.layer.set(layer);
}
{
@ -313,15 +341,18 @@ impl ZwlrLayerSurfaceV1 {
}
impl SurfaceExt for ZwlrLayerSurfaceV1 {
fn pre_commit(self: Rc<Self>, _ctx: CommitContext) -> Result<CommitAction, WlSurfaceError> {
self.deref().pre_commit()?;
Ok(CommitAction::ContinueCommit)
fn before_apply_commit(
self: Rc<Self>,
pending: &mut PendingState,
) -> Result<(), WlSurfaceError> {
self.deref().pre_commit(pending)?;
Ok(())
}
fn post_commit(self: Rc<Self>) {
let buffer = self.surface.buffer.get();
fn after_apply_commit(self: Rc<Self>, _pending: &mut PendingState) {
let buffer_is_some = self.surface.buffer.is_some();
if self.mapped.get() {
if buffer.is_none() {
if !buffer_is_some {
self.destroy_node();
} else {
let pos = self.pos.get();
@ -330,7 +361,7 @@ impl SurfaceExt for ZwlrLayerSurfaceV1 {
self.compute_position();
}
}
} else if buffer.is_some() {
} else if buffer_is_some {
let layer = &self.output.layers[self.layer.get() as usize];
self.link.set(Some(layer.add_last(self.clone())));
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);
}
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);
}
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));
child.attach(buffer.id)?;
child.commit()?;
parent.map().await?;
@ -45,10 +46,12 @@ async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
client.compare_screenshot("1").await?;
sub.place_below(parent.surface.id)?;
child.commit()?;
parent.map().await?;
client.compare_screenshot("2").await?;
sub.place_above(parent.surface.id)?;
child.commit()?;
parent.map().await?;
client.compare_screenshot("1").await?;

View file

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

View file

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

View file

@ -4,7 +4,7 @@ use {
cursor::KnownCursor,
fixed::Fixed,
format::ARGB8888,
gfx_api::{GfxContext, GfxFramebuffer},
gfx_api::{AcquireSync, GfxContext, GfxFramebuffer, ReleaseSync},
ifs::zwlr_layer_shell_v1::OVERLAY,
portal::ptl_display::{PortalDisplay, PortalOutput, PortalSeat},
renderer::renderer_base::RendererBase,
@ -222,6 +222,9 @@ impl GuiElement for Button {
None,
r.scale(),
None,
None,
AcquireSync::None,
ReleaseSync::None,
);
}
}
@ -323,6 +326,9 @@ impl GuiElement for Label {
None,
r.scale(),
None,
None,
AcquireSync::None,
ReleaseSync::None,
);
}
}
@ -626,6 +632,19 @@ impl WindowData {
}
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.surface.frame({
@ -641,13 +660,6 @@ impl WindowData {
self.have_frame.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.commit();
}

View file

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

View file

@ -1,6 +1,9 @@
use {
crate::{
gfx_api::{CopyTexture, FillRect, FramebufferRect, GfxApiOpt, GfxTexture, SampleRect},
gfx_api::{
AcquireSync, BufferResv, CopyTexture, FillRect, FramebufferRect, GfxApiOpt, GfxTexture,
ReleaseSync, SampleRect,
},
rect::Rect,
scale::Scale,
theme::Color,
@ -130,6 +133,9 @@ impl RendererBase<'_> {
tsize: Option<(i32, i32)>,
tscale: Scale,
bounds: Option<&Rect>,
buffer_resv: Option<Rc<dyn BufferResv>>,
acquire_sync: AcquireSync,
release_sync: ReleaseSync,
) {
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 {
tex: texture.clone(),
source: texcoord,
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,
),
target,
buffer_resv,
acquire_sync,
release_sync,
}));
}
}

View file

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

View file

@ -17,7 +17,10 @@ use {
fixed::Fixed,
forker::ForkerProxy,
format::Format,
gfx_api::{GfxContext, GfxError, GfxFramebuffer, GfxTexture, SampleRect},
gfx_api::{
AcquireSync, GfxContext, GfxError, GfxFramebuffer, GfxTexture, ReleaseSync, SampleRect,
SyncFile,
},
gfx_apis::create_gfx_context,
globals::{Globals, GlobalsError, WaylandGlobal},
ifs::{
@ -30,9 +33,11 @@ use {
wl_output::{OutputId, PersistentOutputState},
wl_seat::{SeatIds, WlSeatGlobal},
wl_surface::{
wl_subsurface::SubsurfaceIds,
zwp_idle_inhibitor_v1::{IdleInhibitorId, IdleInhibitorIds, ZwpIdleInhibitorV1},
NoneSurfaceExt, WlSurface,
},
wp_linux_drm_syncobj_manager_v1::WpLinuxDrmSyncobjManagerV1Global,
zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1,
zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1Global,
@ -55,7 +60,14 @@ use {
linkedlist::LinkedList, numcell::NumCell, queue::AsyncQueue, refcounted::RefCounted,
run_toplevel::RunToplevel,
},
video::{dmabuf::DmaBufIds, drm::Drm},
video::{
dmabuf::DmaBufIds,
drm::{
sync_obj::{SyncObj, SyncObjPoint},
wait_for_sync_obj::WaitForSyncObj,
Drm,
},
},
wheel::Wheel,
wire::{
ExtForeignToplevelListV1Id, JayRenderCtxId, JaySeatEventsId, JayWorkspaceWatcherId,
@ -80,6 +92,7 @@ use {
sync::Arc,
time::Duration,
},
thiserror::Error,
};
pub struct State {
@ -157,6 +170,9 @@ pub struct State {
pub double_click_interval_usec: Cell<u64>,
pub double_click_distance: Cell<i32>,
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 {
@ -361,6 +377,8 @@ impl State {
self.render_ctx_version.fetch_add(1);
self.cursors.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: {
if let Some(ctx) = &ctx {
@ -406,7 +424,7 @@ impl State {
}
fn visit_surface(&mut self, node: &Rc<WlSurface>) {
if let Some(buffer) = node.buffer.get() {
buffer.handle_gfx_context_change();
buffer.buffer.handle_gfx_context_change();
}
node.node_visit_children(self);
}
@ -429,11 +447,18 @@ impl State {
seat.render_ctx_changed();
}
if ctx.is_some() && !self.render_ctx_ever_initialized.replace(true) {
self.add_global(&Rc::new(WlDrmGlobal::new(self.globals.name())));
self.add_global(&Rc::new(ZwpLinuxDmabufV1Global::new(self.globals.name())));
if let Some(config) = self.config.get() {
config.graphics_initialized();
if let Some(ctx) = &ctx {
if !self.render_ctx_ever_initialized.replace(true) {
self.add_global(&Rc::new(WlDrmGlobal::new(self.globals.name())));
self.add_global(&Rc::new(ZwpLinuxDmabufV1Global::new(self.globals.name())));
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>,
rr: &mut RenderResult,
render_hw_cursor: bool,
) {
fb.render_output(
) -> Result<Option<SyncFile>, GfxError> {
let sync_file = fb.render_output(
output,
self,
Some(output.global.pos.get()),
Some(rr),
output.global.persistent.scale.get(),
render_hw_cursor,
);
)?;
output.perform_screencopies(tex, !render_hw_cursor, 0, 0, None);
rr.dispatch_frame_requests();
Ok(sync_file)
}
pub fn perform_screencopy(
@ -790,7 +816,7 @@ impl State {
y_off: i32,
size: Option<(i32, i32)>,
transform: Transform,
) {
) -> Result<Option<SyncFile>, GfxError> {
let mut ops = target.take_render_ops();
let mut renderer = Renderer {
base: target.renderer_base(&mut ops, Scale::from_int(1), Transform::None),
@ -812,6 +838,9 @@ impl State {
size,
Scale::from_int(1),
None,
None,
AcquireSync::None,
ReleaseSync::Implicit,
);
if render_hardware_cursors {
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 {
@ -851,7 +880,7 @@ impl State {
stride: i32,
format: &'static Format,
transform: Transform,
) {
) -> Result<(), ShmScreencopyError> {
let (src_width, src_height) = src.size();
let mut needs_copy = capture.rect.x1() < x_off
|| capture.rect.x2() > x_off + src_width
@ -866,20 +895,11 @@ impl State {
}
let acc = if needs_copy {
let Some(ctx) = self.render_ctx.get() else {
log::warn!("Cannot perform shm screencopy because there is no render context");
return;
return Err(ShmScreencopyError::NoRenderContext);
};
let fb =
match ctx.create_fb(capture.rect.width(), capture.rect.height(), stride, format) {
Ok(f) => f,
Err(e) => {
log::warn!(
"Could not create temporary fb for screencopy: {}",
ErrorFmt(e)
);
return;
}
};
let fb = ctx
.create_fb(capture.rect.width(), capture.rect.height(), stride, format)
.map_err(ShmScreencopyError::CreateTemporaryFb)?;
self.perform_screencopy(
src,
&fb,
@ -889,7 +909,8 @@ impl State {
y_off - capture.rect.y1(),
size,
transform,
);
)
.map_err(ShmScreencopyError::CopyToTemporary)?;
mem.access(|mem| {
fb.copy_to_shm(
0,
@ -914,16 +935,12 @@ impl State {
)
})
};
let res = match acc {
Ok(res) => res,
match acc {
Ok(res) => res.map_err(ShmScreencopyError::ReadPixels),
Err(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);
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 gfx_api_ext;
pub mod hex;
pub mod line_logger;
pub mod linkedlist;
pub mod log_on_drop;
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>> {
unsafe {
if ep != self.root.data {
@ -57,11 +74,14 @@ impl<T> LinkedList<T> {
}
}
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.last().is_none()
}
pub fn is_not_empty(&self) -> bool {
!self.is_empty()
}
pub fn last(&self) -> Option<NodeRef<T>> {
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() }
}
pub fn is_not_empty(&self) -> bool {
!self.is_empty()
}
pub fn remove(&self, k: &K) -> Option<V> {
unsafe { self.m.get().deref_mut().remove(k) }
}

View file

@ -48,6 +48,13 @@ impl DmaBuf {
}
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 _;

View file

@ -1,4 +1,6 @@
pub mod sync_obj;
mod sys;
pub mod wait_for_sync_obj;
use {
crate::{
@ -52,7 +54,7 @@ pub use sys::{
#[derive(Debug, Error)]
pub enum DrmError {
#[error("Could not reopen a node")]
ReopenNode(#[source] crate::utils::oserror::OsError),
ReopenNode(#[source] OsError),
#[error("Could not retrieve the render node name")]
RenderNodeName(#[source] OsError),
#[error("Could not retrieve the device node name")]
@ -113,6 +115,28 @@ pub enum DrmError {
Version(#[source] OsError),
#[error("Format of IN_FORMATS property is invalid")]
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> {

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 {}
// 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,
state::State,
user_session::import_environment,
utils::{buf::Buf, errorfmt::ErrorFmt, oserror::OsError},
utils::{errorfmt::ErrorFmt, line_logger::log_lines, oserror::OsError},
wire::WlSurfaceId,
xcon::XconError,
xwayland::{
@ -217,29 +217,12 @@ pub fn build_args() -> (String, Vec<String>) {
async fn log_xwayland(state: Rc<State>, stderr: OwnedFd) {
let stderr = Rc::new(stderr);
let mut buf = vec![];
let mut buf2 = Buf::new(128);
let mut done = false;
while !done {
loop {
match state.ring.read(&stderr, buf2.clone()).await {
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();
let res = log_lines(&state.ring, &stderr, |left, right| {
log::info!("Xwayland: {}{}", left.as_bstr(), right.as_bstr());
})
.await;
if let Err(e) = res {
log::error!("Could not read from stderr fd: {}", ErrorFmt(e));
}
}

View file

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

View file

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

View file

@ -19,7 +19,8 @@ use {
is_reload,
keyboard::{Keymap, ModifiedKeySym},
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},
switch_to_vt,
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 {
set_direct_scanout_enabled(dse);
}
if let Some(ese) = config.explicit_sync_enabled {
set_explicit_sync_enabled(ese);
}
on_new_drm_device({
let state = state.clone();
move |d| {

View file

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

View file

@ -498,6 +498,10 @@
"type": "boolean",
"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": {
"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"

View file

@ -887,6 +887,16 @@ The table has the following fields:
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):
Selects the device to use for rendering in a system with multiple GPUs.

View file

@ -1875,6 +1875,15 @@ Config:
required: false
description: |
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:
ref: DrmDeviceMatch
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 {
}