1
0
Fork 0
forked from wry/wry

wayland: implement linux-drm-syncobj-v1

This commit is contained in:
Julian Orth 2024-03-21 20:54:21 +01:00
parent 816315170f
commit aaf73d6fdc
29 changed files with 1507 additions and 35 deletions

View file

@ -667,6 +667,7 @@ impl MetalConnector {
AcquireSync::None => None,
AcquireSync::Implicit => None,
AcquireSync::SyncFile { sync_file } => Some(sync_file.clone()),
AcquireSync::Unnecessary => None,
};
fb = dsd.fb.clone();
}

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,
},
@ -222,6 +223,7 @@ fn start_compositor2(
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)),
});
state.tracker.register(ClientId::from_raw(0));
create_dummy_output(&state);

View file

@ -10,7 +10,7 @@ use {
theme::Color,
tree::{Node, OutputNode},
utils::{clonecell::UnsafeCellCloneSafe, transform_ext::TransformExt},
video::{dmabuf::DmaBuf, gbm::GbmDevice, Modifier},
video::{dmabuf::DmaBuf, drm::sync_obj::SyncObjCtx, gbm::GbmDevice, Modifier},
},
ahash::AHashMap,
indexmap::IndexSet,
@ -167,6 +167,7 @@ pub enum AcquireSync {
None,
Implicit,
SyncFile { sync_file: SyncFile },
Unnecessary,
}
impl AcquireSync {
@ -191,6 +192,7 @@ impl Debug for AcquireSync {
AcquireSync::None => "None",
AcquireSync::Implicit => "Implicit",
AcquireSync::SyncFile { .. } => "SyncFile",
AcquireSync::Unnecessary => "Unnecessary",
};
f.debug_struct(name).finish_non_exhaustive()
}
@ -517,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

@ -376,7 +376,7 @@ fn render_texture(ctx: &GlRenderContext, tex: &CopyTexture) {
fn handle_explicit_sync(ctx: &GlRenderContext, texture: &Texture, sync: &AcquireSync) {
let sync_file = match sync {
AcquireSync::None | AcquireSync::Implicit => return,
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) {

View file

@ -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>,
@ -128,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(),
@ -271,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

@ -67,7 +67,7 @@ impl Framebuffer {
pub fn render(
&self,
ops: Vec<GfxApiOpt>,
mut ops: Vec<GfxApiOpt>,
clear: Option<&Color>,
) -> Result<Option<SyncFile>, RenderError> {
let gles = self.ctx.ctx.dpy.gles;
@ -89,6 +89,7 @@ impl Framebuffer {
}
Ok(fd)
});
ops.clear();
*self.ctx.gfx_ops.borrow_mut() = ops;
res
}
@ -100,9 +101,7 @@ 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) {

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),
@ -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

@ -574,6 +574,7 @@ impl VulkanRenderer {
.map_err(|e| VulkanError::Dupfd(e.into()))?;
import_sync_file(fd)?;
}
AcquireSync::Unnecessary => {}
}
}
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

@ -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::{AcquireSync, BufferResv, BufferResvUser, SampleRect, SyncFile},
gfx_api::{AcquireSync, BufferResv, BufferResvUser, ReleaseSync, SampleRect, SyncFile},
ifs::{
wl_buffer::WlBuffer,
wl_callback::WlCallback,
@ -25,9 +27,11 @@ use {
NodeSeatState, SeatId, WlSeatGlobal,
},
wl_surface::{
commit_timeline::{ClearReason, CommitTimeline, CommitTimelineError},
cursor::CursorSurface,
wl_subsurface::{PendingSubsurfaceData, SubsurfaceId, WlSubsurface},
wp_fractional_scale_v1::WpFractionalScaleV1,
wp_linux_drm_syncobj_surface_v1::WpLinuxDrmSyncobjSurfaceV1,
wp_tearing_control_v1::WpTearingControlV1,
wp_viewport::WpViewport,
x_surface::XSurface,
@ -57,7 +61,10 @@ use {
smallmap::SmallMap,
transform_ext::TransformExt,
},
video::dmabuf::DMA_BUF_SYNC_READ,
video::{
dmabuf::DMA_BUF_SYNC_READ,
drm::sync_obj::{SyncObj, SyncObjPoint},
},
wire::{
wl_surface::*, WlOutputId, WlSurfaceId, ZwpIdleInhibitorV1Id,
ZwpLinuxDmabufFeedbackV1Id,
@ -66,6 +73,7 @@ use {
xwayland::XWaylandEvent,
},
ahash::AHashMap,
isnt::std_1::primitive::IsntSliceExt,
jay_config::video::Transform,
std::{
cell::{Cell, RefCell},
@ -133,15 +141,46 @@ 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) {
@ -173,7 +212,7 @@ pub struct WlSurface {
pub client: Rc<Client>,
visible: Cell<bool>,
role: Cell<SurfaceRole>,
pending: RefCell<PendingState>,
pending: RefCell<Box<PendingState>>,
input_region: CloneCell<Option<Rc<Region>>>,
opaque_region: Cell<Option<Rc<Region>>>,
buffer_points: RefCell<BufferPoints>,
@ -209,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 {
@ -232,7 +274,7 @@ enum CommitAction {
}
trait SurfaceExt {
fn commit_requested(self: Rc<Self>, pending: &mut PendingState) -> CommitAction {
fn commit_requested(self: Rc<Self>, pending: &mut Box<PendingState>) -> CommitAction {
let _ = pending;
CommitAction::ContinueCommit
}
@ -329,11 +371,14 @@ struct PendingState {
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: PendingState,
state: Box<PendingState>,
}
impl PendingState {
@ -341,7 +386,9 @@ impl PendingState {
// discard state
if next.buffer.is_some() {
if let Some(Some(prev)) = self.buffer.take() {
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();
}
@ -371,6 +418,8 @@ impl PendingState {
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);
@ -478,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(),
}
}
@ -706,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()?;
@ -730,6 +783,7 @@ impl WlSurface {
self.client.remove_obj(self)?;
self.idle_inhibitors.clear();
self.constraints.take();
self.destroyed.set(true);
Ok(())
}
@ -796,6 +850,9 @@ impl WlSurface {
.surface
.apply_state(&mut subsurface.state)?;
}
if self.destroyed.get() {
return Ok(());
}
self.ext.get().before_apply_commit(pending)?;
let mut scale_changed = false;
if let Some(scale) = pending.scale.take() {
@ -835,10 +892,20 @@ impl WlSurface {
}
if let Some(buffer) = buffer_change {
buffer.update_texture_or_log();
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: AcquireSync::Implicit,
sync,
release_sync,
release,
};
self.buffer.set(Some(Rc::new(surface_buffer)));
self.buf_x.fetch_add(dx);
@ -990,12 +1057,34 @@ impl WlSurface {
let _req: Commit = self.parse(parser)?;
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.apply_state(pending)?;
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 {
@ -1215,6 +1304,7 @@ impl Object for WlSurface {
self.tearing_control.take();
self.constraints.clear();
self.drm_feedback.clear();
self.commit_timeline.clear(ClearReason::BreakLoops);
}
}
@ -1381,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

@ -313,7 +313,7 @@ impl Object for WlSubsurface {
simple_add_obj!(WlSubsurface);
impl SurfaceExt for WlSubsurface {
fn commit_requested(self: Rc<Self>, pending: &mut PendingState) -> CommitAction {
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) {

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

@ -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

@ -386,10 +386,6 @@ impl Renderer<'_> {
bounds: Option<&Rect>,
) {
if let Some(tex) = buffer.buffer.texture.get() {
let release_sync = match buffer.sync {
AcquireSync::Implicit => ReleaseSync::Implicit,
AcquireSync::None | AcquireSync::SyncFile { .. } => ReleaseSync::Explicit,
};
self.base.render_texture(
&tex,
x,
@ -400,7 +396,7 @@ impl Renderer<'_> {
bounds,
Some(buffer.clone()),
buffer.sync.clone(),
release_sync,
buffer.release_sync,
);
} else if let Some(color) = &buffer.buffer.color {
if let Some(rect) = Rect::new_sized(x, y, tsize.0, tsize.1) {

View file

@ -37,6 +37,7 @@ use {
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,
@ -59,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,
@ -163,6 +171,7 @@ pub struct State {
pub double_click_distance: Cell<i32>,
pub create_default_seat: Cell<bool>,
pub subsurface_ids: SubsurfaceIds,
pub wait_for_sync_obj: Rc<WaitForSyncObj>,
}
// impl Drop for State {
@ -367,6 +376,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 {
@ -435,11 +446,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.add_global(&Rc::new(WpLinuxDrmSyncobjManagerV1Global::new(
self.globals.name(),
)));
}
if let Some(config) = self.config.get() {
config.graphics_initialized();
}
}
}
@ -931,6 +949,16 @@ 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)]

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

@ -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

@ -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 {
}