1
0
Fork 0
forked from wry/wry

Merge pull request #96 from mahkoh/jorth/direct-scanout

metal: implement direct scanout
This commit is contained in:
mahkoh 2024-02-19 15:32:15 +01:00 committed by GitHub
commit 60f2c6e49d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 873 additions and 215 deletions

View file

@ -511,6 +511,10 @@ impl Client {
self.send(&ClientMessage::SetGfxApi { device, api }); self.send(&ClientMessage::SetGfxApi { device, api });
} }
pub fn set_direct_scanout_enabled(&self, device: Option<DrmDevice>, enabled: bool) {
self.send(&ClientMessage::SetDirectScanoutEnabled { device, enabled });
}
pub fn connector_connected(&self, connector: Connector) -> bool { pub fn connector_connected(&self, connector: Connector) -> bool {
let res = self.send_with_response(&ClientMessage::ConnectorConnected { connector }); let res = self.send_with_response(&ClientMessage::ConnectorConnected { connector });
get_response!(res, false, ConnectorConnected { connected }); get_response!(res, false, ConnectorConnected { connected });

View file

@ -338,6 +338,10 @@ pub enum ClientMessage<'a> {
device: Option<DrmDevice>, device: Option<DrmDevice>,
api: GfxApi, api: GfxApi,
}, },
SetDirectScanoutEnabled {
device: Option<DrmDevice>,
enabled: bool,
},
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]

View file

@ -369,6 +369,11 @@ impl DrmDevice {
pub fn set_gfx_api(self, gfx_api: GfxApi) { pub fn set_gfx_api(self, gfx_api: GfxApi) {
get!().set_gfx_api(Some(self), gfx_api); get!().set_gfx_api(Some(self), gfx_api);
} }
/// Enables or disables direct scanout of client surfaces for this device.
pub fn set_direct_scanout_enabled(self, enabled: bool) {
get!().set_direct_scanout_enabled(Some(self), enabled);
}
} }
/// A graphics API. /// A graphics API.
@ -389,3 +394,12 @@ pub enum GfxApi {
pub fn set_gfx_api(gfx_api: GfxApi) { pub fn set_gfx_api(gfx_api: GfxApi) {
get!().set_gfx_api(None, gfx_api); get!().set_gfx_api(None, gfx_api);
} }
/// Enables or disables direct scanout of client surfaces.
///
/// The default is `true`.
///
/// This setting can be overwritten per-device with [DrmDevice::set_direct_scanout_enabled].
pub fn set_direct_scanout_enabled(enabled: bool) {
get!().set_direct_scanout_enabled(None, enabled);
}

View file

@ -1,6 +1,7 @@
use { use {
crate::{ crate::{
async_engine::SpawnedFuture, async_engine::SpawnedFuture,
drm_feedback::DrmFeedback,
fixed::Fixed, fixed::Fixed,
gfx_api::GfxFramebuffer, gfx_api::GfxFramebuffer,
ifs::wl_seat::wl_pointer::{CONTINUOUS, FINGER, HORIZONTAL_SCROLL, VERTICAL_SCROLL, WHEEL}, ifs::wl_seat::wl_pointer::{CONTINUOUS, FINGER, HORIZONTAL_SCROLL, VERTICAL_SCROLL, WHEEL},
@ -79,6 +80,9 @@ pub trait Connector {
fn damage(&self); fn damage(&self);
fn drm_dev(&self) -> Option<DrmDeviceId>; fn drm_dev(&self) -> Option<DrmDeviceId>;
fn set_enabled(&self, enabled: bool); fn set_enabled(&self, enabled: bool);
fn drm_feedback(&self) -> Option<Rc<DrmFeedback>> {
None
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -226,4 +230,5 @@ pub trait BackendDrmDevice {
fn set_gfx_api(&self, api: GfxApi); fn set_gfx_api(&self, api: GfxApi);
fn gtx_api(&self) -> GfxApi; fn gtx_api(&self) -> GfxApi;
fn version(&self) -> Result<DrmVersion, DrmError>; fn version(&self) -> Result<DrmVersion, DrmError>;
fn set_direct_scanout_enabled(&self, enabled: bool);
} }

View file

@ -11,6 +11,7 @@ use {
}, },
backends::metal::video::{MetalDrmDeviceData, MetalRenderContext, PendingDrmDevice}, backends::metal::video::{MetalDrmDeviceData, MetalRenderContext, PendingDrmDevice},
dbus::{DbusError, SignalHandler}, dbus::{DbusError, SignalHandler},
drm_feedback::DrmFeedback,
gfx_api::GfxError, gfx_api::GfxError,
libinput::{ libinput::{
consts::{ consts::{
@ -130,6 +131,7 @@ pub struct MetalBackend {
pause_handler: Cell<Option<SignalHandler>>, pause_handler: Cell<Option<SignalHandler>>,
resume_handler: Cell<Option<SignalHandler>>, resume_handler: Cell<Option<SignalHandler>>,
ctx: CloneCell<Option<Rc<MetalRenderContext>>>, ctx: CloneCell<Option<Rc<MetalRenderContext>>>,
default_feedback: CloneCell<Option<Rc<DrmFeedback>>>,
} }
impl Debug for MetalBackend { impl Debug for MetalBackend {
@ -253,6 +255,7 @@ pub async fn create(state: &Rc<State>) -> Result<Rc<MetalBackend>, MetalError> {
pause_handler: Default::default(), pause_handler: Default::default(),
resume_handler: Default::default(), resume_handler: Default::default(),
ctx: Default::default(), ctx: Default::default(),
default_feedback: Default::default(),
}); });
metal.pause_handler.set(Some({ metal.pause_handler.set(Some({
let mtl = metal.clone(); let mtl = metal.clone();

View file

@ -6,12 +6,14 @@ use {
ConnectorKernelId, DrmDeviceId, HardwareCursor, MonitorInfo, ConnectorKernelId, DrmDeviceId, HardwareCursor, MonitorInfo,
}, },
backends::metal::{MetalBackend, MetalError}, backends::metal::{MetalBackend, MetalError},
drm_feedback::DrmFeedback,
edid::Descriptor, edid::Descriptor,
format::{Format, ARGB8888, XRGB8888}, format::{Format, ARGB8888, XRGB8888},
gfx_api::{GfxContext, GfxFramebuffer, GfxTexture}, gfx_api::{BufferPoints, GfxApiOpt, GfxContext, GfxFramebuffer, GfxRenderPass, GfxTexture},
ifs::wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC}, ifs::wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC},
renderer::RenderResult, renderer::RenderResult,
state::State, state::State,
tree::OutputNode,
udev::UdevDevice, udev::UdevDevice,
utils::{ utils::{
asyncevent::AsyncEvent, bitflags::BitflagsExt, clonecell::CloneCell, asyncevent::AsyncEvent, bitflags::BitflagsExt, clonecell::CloneCell,
@ -19,6 +21,7 @@ use {
oserror::OsError, syncqueue::SyncQueue, oserror::OsError, syncqueue::SyncQueue,
}, },
video::{ video::{
dmabuf::DmaBufId,
drm::{ drm::{
drm_mode_modeinfo, Change, ConnectorStatus, ConnectorType, DrmBlob, DrmConnector, drm_mode_modeinfo, Change, ConnectorStatus, ConnectorType, DrmBlob, DrmConnector,
DrmCrtc, DrmEncoder, DrmError, DrmEvent, DrmFramebuffer, DrmMaster, DrmModeInfo, DrmCrtc, DrmEncoder, DrmError, DrmEvent, DrmFramebuffer, DrmMaster, DrmModeInfo,
@ -36,13 +39,14 @@ use {
jay_config::video::GfxApi, jay_config::video::GfxApi,
std::{ std::{
cell::{Cell, RefCell}, cell::{Cell, RefCell},
collections::VecDeque,
ffi::CString, ffi::CString,
fmt::{Debug, Formatter}, fmt::{Debug, Formatter},
mem, mem,
ops::DerefMut, ops::DerefMut,
rc::Rc, rc::{Rc, Weak},
}, },
uapi::{c, c::dev_t}, uapi::c::{self, dev_t},
}; };
pub struct PendingDrmDevice { pub struct PendingDrmDevice {
@ -77,6 +81,16 @@ pub struct MetalDrmDevice {
pub handle_events: HandleEvents, pub handle_events: HandleEvents,
pub ctx: CloneCell<Rc<MetalRenderContext>>, pub ctx: CloneCell<Rc<MetalRenderContext>>,
pub on_change: OnChange<crate::backend::DrmEvent>, pub on_change: OnChange<crate::backend::DrmEvent>,
pub direct_scanout_enabled: Cell<Option<bool>>,
}
impl MetalDrmDevice {
pub fn is_render_device(&self) -> bool {
if let Some(ctx) = self.backend.ctx.get() {
return ctx.dev_id == self.id;
}
false
}
} }
impl BackendDrmDevice for MetalDrmDevice { impl BackendDrmDevice for MetalDrmDevice {
@ -111,6 +125,10 @@ impl BackendDrmDevice for MetalDrmDevice {
fn version(&self) -> Result<DrmVersion, DrmError> { fn version(&self) -> Result<DrmVersion, DrmError> {
self.gbm.drm.version() self.gbm.drm.version()
} }
fn set_direct_scanout_enabled(&self, enabled: bool) {
self.direct_scanout_enabled.set(Some(enabled));
}
} }
pub struct HandleEvents { pub struct HandleEvents {
@ -202,6 +220,11 @@ pub struct MetalConnector {
pub cursor_buffers: CloneCell<Option<Rc<[RenderBuffer; 2]>>>, pub cursor_buffers: CloneCell<Option<Rc<[RenderBuffer; 2]>>>,
pub cursor_front_buffer: NumCell<usize>, pub cursor_front_buffer: NumCell<usize>,
pub cursor_swap_buffer: Cell<bool>, pub cursor_swap_buffer: Cell<bool>,
pub drm_feedback: CloneCell<Option<Rc<DrmFeedback>>>,
pub scanout_buffers: RefCell<AHashMap<DmaBufId, DirectScanoutCache>>,
pub active_framebuffers: RefCell<VecDeque<PresentFb>>,
pub direct_scanout_active: Cell<bool>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -310,11 +333,39 @@ impl<T> Debug for OnChange<T> {
} }
} }
#[derive(Debug)]
pub struct DirectScanoutCache {
tex: Weak<dyn GfxTexture>,
fb: Option<Rc<DrmFramebuffer>>,
}
#[derive(Debug)]
pub struct DirectScanoutData {
tex: Rc<dyn GfxTexture>,
fb: Rc<DrmFramebuffer>,
dma_buf_id: DmaBufId,
acquired: Cell<bool>,
}
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>,
}
impl MetalConnector { impl MetalConnector {
async fn present_loop(self: Rc<Self>) { async fn present_loop(self: Rc<Self>) {
loop { loop {
self.present_trigger.triggered().await; self.present_trigger.triggered().await;
self.present(); let _ = self.present(true);
} }
} }
@ -351,49 +402,193 @@ impl MetalConnector {
self.present_trigger.trigger(); self.present_trigger.trigger();
} }
pub fn present(&self) { fn trim_scanout_cache(&self) {
let crtc = match self.crtc.get() { self.scanout_buffers
Some(crtc) => crtc, .borrow_mut()
_ => return, .retain(|_, buffer| buffer.tex.strong_count() > 0);
}; }
if (!self.has_damage.get() && !self.cursor_changed.get()) || !self.can_present.get() {
return; fn prepare_direct_scanout(
&self,
pass: &GfxRenderPass,
plane: &Rc<MetalPlane>,
) -> Option<DirectScanoutData> {
if pass.ops.len() != 1 {
return None;
} }
if !crtc.active.value.get() { let GfxApiOpt::CopyTexture(ct) = &pass.ops[0] else {
return; return None;
};
if ct.source != BufferPoints::identity() {
return None;
} }
let plane = match self.primary_plane.get() { if ct.target.x1 != 0.0
Some(p) => p, || ct.target.y1 != 0.0
_ => return, || ct.target.x2 != plane.mode_w.get() as f32
|| ct.target.y2 != plane.mode_h.get() as f32
{
return None;
}
let Some(dmabuf) = ct.tex.dmabuf() else {
return None;
}; };
let buffers = match self.buffers.get() { let mut cache = self.scanout_buffers.borrow_mut();
Some(b) => b, if let Some(buffer) = cache.get(&dmabuf.id) {
_ => return, return buffer.fb.as_ref().map(|fb| DirectScanoutData {
}; tex: buffer.tex.upgrade().unwrap(),
let cursor = self.cursor_plane.get(); fb: fb.clone(),
let mut changes = self.master.change(); dma_buf_id: dmabuf.id,
if self.has_damage.get() { acquired: Default::default(),
if !self.backend.check_render_context(&self.dev) { });
return; }
let format = 'format: {
if let Some(f) = plane.formats.get(&dmabuf.format.drm) {
break 'format f;
} }
let buffer = &buffers[self.next_buffer.fetch_add(1) % buffers.len()]; if let Some(opaque) = dmabuf.format.opaque {
if let Some(node) = self.state.root.outputs.get(&self.connector_id) { if let Some(f) = plane.formats.get(&opaque.drm) {
let mut rr = self.render_result.borrow_mut(); break 'format f;
let render_fb = buffer.render_fb(); }
self.state.present_output( }
&node, return None;
&render_fb, };
&buffer.render_tex, if !format.modifiers.contains(&dmabuf.modifier) {
&mut rr, return None;
!self.cursor_enabled.get(), }
let data = match self.dev.master.add_fb(dmabuf, Some(format.format)) {
Ok(fb) => Some(DirectScanoutData {
tex: ct.tex.clone(),
fb: Rc::new(fb),
dma_buf_id: dmabuf.id,
acquired: Default::default(),
}),
Err(e) => {
log::debug!(
"Could not import dmabuf for direct scanout: {}",
ErrorFmt(e)
); );
None
}
};
cache.insert(
dmabuf.id,
DirectScanoutCache {
tex: Rc::downgrade(&ct.tex),
fb: data.as_ref().map(|dsd| dsd.fb.clone()),
},
);
data
}
fn direct_scanout_enabled(&self) -> bool {
self.dev
.direct_scanout_enabled
.get()
.unwrap_or(self.state.direct_scanout_enabled.get())
}
fn prepare_present_fb(
&self,
rr: &mut RenderResult,
buffer: &RenderBuffer,
plane: &Rc<MetalPlane>,
output: &OutputNode,
try_direct_scanout: bool,
) -> PresentFb {
self.trim_scanout_cache();
let buffer_fb = buffer.render_fb();
let render_hw_cursor = !self.cursor_enabled.get();
let pass = buffer_fb.create_render_pass(
output,
&self.state,
Some(output.global.pos.get()),
Some(rr),
output.global.preferred_scale.get(),
render_hw_cursor,
);
let try_direct_scanout = try_direct_scanout
&& !output.global.have_shm_screencopies()
&& self.direct_scanout_enabled()
// at least on AMD, using a FB on a different device for rendering will fail
// and destroy the render context. it's possible to work around this by waiting
// until the FB is no longer being scanned out, but if a notification pops up
// then we must be able to disable direct scanout immediately.
// https://gitlab.freedesktop.org/drm/amd/-/issues/3186
&& self.dev.is_render_device();
let mut direct_scanout_data = None;
if try_direct_scanout {
if let Some(dsd) = self.prepare_direct_scanout(&pass, plane) {
output.perform_screencopies(None, &dsd.tex, !render_hw_cursor);
direct_scanout_data = Some(dsd);
}
}
let direct_scanout_active = direct_scanout_data.is_some();
if self.direct_scanout_active.replace(direct_scanout_active) != direct_scanout_active {
let change = match direct_scanout_active {
true => "Enabling",
false => "Disabling",
};
log::debug!("{} direct scanout on {}", change, self.kernel_id());
}
let fb = match &direct_scanout_data {
None => {
self.next_buffer.fetch_add(1);
buffer_fb.perform_render_pass(pass);
if let Some(tex) = &buffer.dev_tex { if let Some(tex) = &buffer.dev_tex {
buffer.dev_fb.copy_texture(tex, 0, 0); buffer.dev_fb.copy_texture(tex, 0, 0);
} }
output.perform_screencopies(
Some(&*buffer_fb),
&buffer.render_tex,
!render_hw_cursor,
);
buffer.drm.clone()
}
Some(dsd) => dsd.fb.clone(),
};
PresentFb {
fb,
direct_scanout_data,
}
}
pub fn present(&self, try_direct_scanout: bool) -> Result<(), ()> {
let crtc = match self.crtc.get() {
Some(crtc) => crtc,
_ => return Ok(()),
};
if (!self.has_damage.get() && !self.cursor_changed.get()) || !self.can_present.get() {
return Ok(());
}
if !crtc.active.value.get() {
return Ok(());
}
let plane = match self.primary_plane.get() {
Some(p) => p,
_ => return Ok(()),
};
let buffers = match self.buffers.get() {
Some(b) => b,
_ => return Ok(()),
};
let cursor = self.cursor_plane.get();
let mut new_fb = None;
let mut changes = self.master.change();
if self.has_damage.get() {
if !self.backend.check_render_context(&self.dev) {
return Ok(());
}
if let Some(node) = self.state.root.outputs.get(&self.connector_id) {
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);
rr.dispatch_frame_requests();
changes.change_object(plane.id, |c| {
c.change(plane.fb_id, fb.fb.id().0 as _);
});
new_fb = Some(fb);
} }
changes.change_object(plane.id, |c| {
c.change(plane.fb_id, buffer.drm.id().0 as _);
});
} }
if self.cursor_changed.get() && cursor.is_some() { if self.cursor_changed.get() && cursor.is_some() {
let plane = cursor.unwrap(); let plane = cursor.unwrap();
@ -434,12 +629,66 @@ impl MetalConnector {
DrmError::Atomic(OsError(c::EACCES)) => { DrmError::Atomic(OsError(c::EACCES)) => {
log::debug!("Could not perform atomic commit, likely because we're no longer the DRM master"); log::debug!("Could not perform atomic commit, likely because we're no longer the DRM master");
} }
_ => log::error!("Could not set plane framebuffer: {}", ErrorFmt(e)), _ => '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;
}
}
}
log::error!("Could not set plane framebuffer: {}", ErrorFmt(e));
}
} }
Err(())
} else { } else {
if let Some(fb) = new_fb {
if let Some(dsd) = &fb.direct_scanout_data {
dsd.tex.reservations().acquire();
dsd.acquired.set(true);
}
self.active_framebuffers.borrow_mut().push_back(fb);
}
self.can_present.set(false); self.can_present.set(false);
self.has_damage.set(false); self.has_damage.set(false);
self.cursor_changed.set(false); self.cursor_changed.set(false);
Ok(())
}
}
pub fn update_drm_feedback(&self) {
let fb = self.compute_drm_feedback();
self.drm_feedback.set(fb);
}
fn compute_drm_feedback(&self) -> Option<Rc<DrmFeedback>> {
if !self.dev.is_render_device() {
return None;
}
let default = self.backend.default_feedback.get()?;
let plane = self.primary_plane.get()?;
let mut formats = vec![];
for (format, info) in &plane.formats {
for modifier in &info.modifiers {
formats.push((*format, *modifier));
}
}
match default.for_scanout(&self.state.drm_feedback_ids, self.dev.devnum, &formats) {
Ok(fb) => fb.map(Rc::new),
Err(e) => {
log::error!("Could not compute connector feedback: {}", ErrorFmt(e));
None
}
} }
} }
} }
@ -488,6 +737,10 @@ impl Connector for MetalConnector {
} }
} }
} }
fn drm_feedback(&self) -> Option<Rc<DrmFeedback>> {
self.drm_feedback.get()
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -522,7 +775,7 @@ pub enum PlaneType {
#[derive(Debug)] #[derive(Debug)]
pub struct PlaneFormat { pub struct PlaneFormat {
_format: &'static Format, format: &'static Format,
modifiers: IndexSet<Modifier>, modifiers: IndexSet<Modifier>,
} }
@ -538,6 +791,9 @@ pub struct MetalPlane {
pub assigned: Cell<bool>, pub assigned: Cell<bool>,
pub mode_w: Cell<i32>,
pub mode_h: Cell<i32>,
pub crtc_id: MutableProperty<DrmCrtc>, pub crtc_id: MutableProperty<DrmCrtc>,
pub crtc_x: MutableProperty<i32>, pub crtc_x: MutableProperty<i32>,
pub crtc_y: MutableProperty<i32>, pub crtc_y: MutableProperty<i32>,
@ -611,6 +867,10 @@ fn create_connector(
cursor_changed: Cell::new(false), cursor_changed: Cell::new(false),
cursor_front_buffer: Default::default(), cursor_front_buffer: Default::default(),
cursor_swap_buffer: Cell::new(false), cursor_swap_buffer: Cell::new(false),
drm_feedback: Default::default(),
scanout_buffers: Default::default(),
active_framebuffers: Default::default(),
direct_scanout_active: Cell::new(false),
}); });
let futures = ConnectorFutures { let futures = ConnectorFutures {
present: backend present: backend
@ -786,7 +1046,7 @@ fn create_plane(plane: DrmPlane, master: &Rc<DrmMaster>) -> Result<MetalPlane, D
formats.insert( formats.insert(
format.format, format.format,
PlaneFormat { PlaneFormat {
_format: f, format: f,
modifiers: format.modifiers, modifiers: format.modifiers,
}, },
); );
@ -798,7 +1058,7 @@ fn create_plane(plane: DrmPlane, master: &Rc<DrmMaster>) -> Result<MetalPlane, D
formats.insert( formats.insert(
format, format,
PlaneFormat { PlaneFormat {
_format: f, format: f,
modifiers: indexset![INVALID_MODIFIER], modifiers: indexset![INVALID_MODIFIER],
}, },
); );
@ -846,6 +1106,8 @@ fn create_plane(plane: DrmPlane, master: &Rc<DrmMaster>) -> Result<MetalPlane, D
src_h: props.get("SRC_H")?.map(|v| v as u32), src_h: props.get("SRC_H")?.map(|v| v as u32),
in_fence_fd: props.get("IN_FENCE_FD")?.id, in_fence_fd: props.get("IN_FENCE_FD")?.id,
assigned: Cell::new(false), assigned: Cell::new(false),
mode_w: Cell::new(0),
mode_h: Cell::new(0),
}) })
} }
@ -1164,6 +1426,7 @@ impl MetalBackend {
}, },
ctx: CloneCell::new(ctx), ctx: CloneCell::new(ctx),
on_change: Default::default(), on_change: Default::default(),
direct_scanout_enabled: Default::default(),
}); });
let (connectors, futures) = get_connectors(self, &dev, &resources.connectors)?; let (connectors, futures) = get_connectors(self, &dev, &resources.connectors)?;
@ -1294,6 +1557,12 @@ impl MetalBackend {
_ => return, _ => return,
}; };
connector.can_present.set(true); connector.can_present.set(true);
{
let mut scanout_buffers = connector.active_framebuffers.borrow_mut();
while scanout_buffers.len() > 1 {
scanout_buffers.pop_front();
}
}
if connector.has_damage.get() || connector.cursor_changed.get() { if connector.has_damage.get() || connector.cursor_changed.get() {
connector.schedule_present(); connector.schedule_present();
} }
@ -1465,6 +1734,14 @@ impl MetalBackend {
} }
let ctx = dev.ctx.get(); let ctx = dev.ctx.get();
self.state.set_render_ctx(Some(ctx.gfx.clone())); self.state.set_render_ctx(Some(ctx.gfx.clone()));
let fb = match DrmFeedback::new(&self.state.drm_feedback_ids, &*ctx.gfx) {
Ok(fb) => Some(Rc::new(fb)),
Err(e) => {
log::error!("Could not create feedback for new context: {}", ErrorFmt(e));
None
}
};
self.default_feedback.set(fb);
self.ctx.set(Some(ctx)); self.ctx.set(Some(ctx));
for dev in self.device_holder.drm_devices.lock().values() { for dev in self.device_holder.drm_devices.lock().values() {
self.re_init_drm_device(&dev); self.re_init_drm_device(&dev);
@ -1492,13 +1769,7 @@ impl MetalBackend {
dev_id: dev.id, dev_id: dev.id,
gfx, gfx,
})); }));
let mut is_render_ctx = false; if dev.is_render_device() {
if let Some(render_ctx) = self.ctx.get() {
if render_ctx.dev_id == dev.id {
is_render_ctx = true;
}
}
if is_render_ctx {
self.make_render_device(dev, true); self.make_render_device(dev, true);
} else { } else {
if let Some(dev) = self.device_holder.drm_devices.get(&dev.devnum) { if let Some(dev) = self.device_holder.drm_devices.get(&dev.devnum) {
@ -1562,6 +1833,7 @@ impl MetalBackend {
continue; continue;
} }
connector.send_hardware_cursor(); connector.send_hardware_cursor();
connector.update_drm_feedback();
} }
Ok(()) Ok(())
} }
@ -1692,14 +1964,19 @@ impl MetalBackend {
if cursor { if cursor {
usage |= GBM_BO_USE_LINEAR; usage |= GBM_BO_USE_LINEAR;
}; };
let dev_bo = dev let dev_bo = dev.gbm.create_bo(
.gbm &self.state.dma_buf_ids,
.create_bo(width, height, format, &possible_modifiers, usage); width,
height,
format,
&possible_modifiers,
usage,
);
let dev_bo = match dev_bo { let dev_bo = match dev_bo {
Ok(b) => b, Ok(b) => b,
Err(e) => return Err(MetalError::ScanoutBuffer(e)), Err(e) => return Err(MetalError::ScanoutBuffer(e)),
}; };
let drm_fb = match dev.master.add_fb(dev_bo.dmabuf()) { let drm_fb = match dev.master.add_fb(dev_bo.dmabuf(), None) {
Ok(fb) => Rc::new(fb), Ok(fb) => Rc::new(fb),
Err(e) => return Err(MetalError::Framebuffer(e)), Err(e) => return Err(MetalError::Framebuffer(e)),
}; };
@ -1740,11 +2017,14 @@ impl MetalBackend {
return Err(MetalError::MissingRenderModifier(format.name)); return Err(MetalError::MissingRenderModifier(format.name));
} }
usage = GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR; usage = GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR;
let render_bo = let render_bo = render_ctx.gfx.gbm().create_bo(
render_ctx &self.state.dma_buf_ids,
.gfx width,
.gbm() height,
.create_bo(width, height, format, &possible_modifiers, usage); format,
&possible_modifiers,
usage,
);
let render_bo = match render_bo { let render_bo = match render_bo {
Ok(b) => b, Ok(b) => b,
Err(e) => return Err(MetalError::ScanoutBuffer(e)), Err(e) => return Err(MetalError::ScanoutBuffer(e)),
@ -1909,6 +2189,8 @@ impl MetalBackend {
c.change(primary_plane.src_h.id, (mode.vdisplay as u64) << 16); c.change(primary_plane.src_h.id, (mode.vdisplay as u64) << 16);
}); });
primary_plane.assigned.set(true); primary_plane.assigned.set(true);
primary_plane.mode_w.set(mode.hdisplay as _);
primary_plane.mode_h.set(mode.vdisplay as _);
primary_plane.crtc_id.value.set(crtc.id); primary_plane.crtc_id.value.set(crtc.id);
primary_plane.crtc_x.value.set(0); primary_plane.crtc_x.value.set(0);
primary_plane.crtc_y.value.set(0); primary_plane.crtc_y.value.set(0);

View file

@ -393,9 +393,14 @@ impl XBackend {
panic!("Neither linear nor invalid modifier is supported"); panic!("Neither linear nor invalid modifier is supported");
}; };
for image in &mut images { for image in &mut images {
let bo = self let bo = self.gbm.create_bo(
.gbm &self.state.dma_buf_ids,
.create_bo(width, height, XRGB8888, modifier, usage)?; width,
height,
XRGB8888,
modifier,
usage,
)?;
let dma = bo.dmabuf(); let dma = bo.dmabuf();
assert!(dma.planes.len() == 1); assert!(dma.planes.len() == 1);
let plane = dma.planes.first().unwrap(); let plane = dma.planes.first().unwrap();
@ -984,6 +989,10 @@ impl BackendDrmDevice for XDrmDevice {
fn version(&self) -> Result<DrmVersion, DrmError> { fn version(&self) -> Result<DrmVersion, DrmError> {
self.backend.gbm.drm.version() self.backend.gbm.drm.version()
} }
fn set_direct_scanout_enabled(&self, enabled: bool) {
let _ = enabled;
}
} }
struct XOutput { struct XOutput {

View file

@ -5,7 +5,7 @@ use {
tools::tool_client::{with_tool_client, Handle, ToolClient}, tools::tool_client::{with_tool_client, Handle, ToolClient},
utils::{errorfmt::ErrorFmt, queue::AsyncQueue}, utils::{errorfmt::ErrorFmt, queue::AsyncQueue},
video::{ video::{
dmabuf::{DmaBuf, DmaBufPlane, PlaneVec}, dmabuf::{DmaBuf, DmaBufIds, DmaBufPlane, PlaneVec},
drm::Drm, drm::Drm,
gbm::{GbmDevice, GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING}, gbm::{GbmDevice, GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING},
}, },
@ -55,7 +55,7 @@ async fn run(screenshot: Rc<Screenshot>) {
fatal!("Could not take a screenshot: {}", e); fatal!("Could not take a screenshot: {}", e);
} }
}; };
let data = buf_to_qoi(&buf); let data = buf_to_qoi(&DmaBufIds::default(), &buf);
let filename = screenshot let filename = screenshot
.args .args
.filename .filename
@ -67,7 +67,7 @@ async fn run(screenshot: Rc<Screenshot>) {
} }
} }
pub fn buf_to_qoi(buf: &Dmabuf) -> Vec<u8> { pub fn buf_to_qoi(dma_buf_ids: &DmaBufIds, buf: &Dmabuf) -> Vec<u8> {
let drm = match Drm::reopen(buf.drm_dev.raw(), false) { let drm = match Drm::reopen(buf.drm_dev.raw(), false) {
Ok(drm) => drm, Ok(drm) => drm,
Err(e) => { Err(e) => {
@ -87,6 +87,7 @@ pub fn buf_to_qoi(buf: &Dmabuf) -> Vec<u8> {
fd: buf.fd.clone(), fd: buf.fd.clone(),
}); });
let dmabuf = DmaBuf { let dmabuf = DmaBuf {
id: dma_buf_ids.next(),
width: buf.width as _, width: buf.width as _,
height: buf.height as _, height: buf.height as _,
format: XRGB8888, format: XRGB8888,

View file

@ -201,6 +201,9 @@ fn start_compositor2(
default_gfx_api: Cell::new(GfxApi::OpenGl), default_gfx_api: Cell::new(GfxApi::OpenGl),
activation_tokens: Default::default(), activation_tokens: Default::default(),
toplevel_lists: Default::default(), toplevel_lists: Default::default(),
dma_buf_ids: Default::default(),
drm_feedback_ids: Default::default(),
direct_scanout_enabled: Cell::new(true),
}); });
state.tracker.register(ClientId::from_raw(0)); state.tracker.register(ClientId::from_raw(0));
create_dummy_output(&state); create_dummy_output(&state);

View file

@ -590,6 +590,21 @@ impl ConfigProxyHandler {
Ok(()) Ok(())
} }
fn handle_set_direct_scanout_enabled(
&self,
device: Option<DrmDevice>,
enabled: bool,
) -> Result<(), CphError> {
match device {
Some(dev) => self
.get_drm_device(dev)?
.dev
.set_direct_scanout_enabled(enabled),
_ => self.state.direct_scanout_enabled.set(enabled),
}
Ok(())
}
fn handle_get_default_workspace_capture(&self) { fn handle_get_default_workspace_capture(&self) {
self.respond(Response::GetDefaultWorkspaceCapture { self.respond(Response::GetDefaultWorkspaceCapture {
capture: self.state.default_workspace_capture.get(), capture: self.state.default_workspace_capture.get(),
@ -1320,6 +1335,9 @@ impl ConfigProxyHandler {
ClientMessage::SetGfxApi { device, api } => { ClientMessage::SetGfxApi { device, api } => {
self.handle_set_gfx_api(device, api).wrn("set_gfx_api")? self.handle_set_gfx_api(device, api).wrn("set_gfx_api")?
} }
ClientMessage::SetDirectScanoutEnabled { device, enabled } => self
.handle_set_direct_scanout_enabled(device, enabled)
.wrn("set_direct_scanout_enabled")?,
} }
Ok(()) Ok(())
} }

View file

@ -1,24 +1,45 @@
use { use {
crate::{gfx_api::GfxContext, utils::oserror::OsError}, crate::{gfx_api::GfxContext, utils::oserror::OsError, video::Modifier},
ahash::AHashMap,
byteorder::{NativeEndian, WriteBytesExt}, byteorder::{NativeEndian, WriteBytesExt},
std::{io::Write, rc::Rc}, std::{io::Write, rc::Rc},
thiserror::Error, thiserror::Error,
uapi::{c, OwnedFd}, uapi::{c, OwnedFd},
}; };
pub struct DrmFeedback { linear_ids!(DrmFeedbackIds, DrmFeedbackId);
#[derive(Debug)]
pub struct DrmFeedbackShared {
pub fd: Rc<OwnedFd>, pub fd: Rc<OwnedFd>,
pub size: usize, pub size: usize,
pub indices: Vec<u16>,
pub main_device: c::dev_t, pub main_device: c::dev_t,
pub indices: AHashMap<(u32, Modifier), u16>,
}
#[derive(Debug)]
pub struct DrmFeedback {
pub id: DrmFeedbackId,
pub shared: Rc<DrmFeedbackShared>,
pub tranches: Vec<DrmFeedbackTranche>,
}
#[derive(Clone, Debug)]
pub struct DrmFeedbackTranche {
pub device: c::dev_t,
pub indices: Vec<u16>,
pub scanout: bool,
} }
impl DrmFeedback { impl DrmFeedback {
pub fn new(ctx: &dyn GfxContext) -> Result<Self, DrmFeedbackError> { pub fn new(
let dev_t = uapi::fstat(ctx.gbm().drm.raw()) ids: &DrmFeedbackIds,
render_ctx: &dyn GfxContext,
) -> Result<Self, DrmFeedbackError> {
let main_device = uapi::fstat(render_ctx.gbm().drm.raw())
.map_err(OsError::from)? .map_err(OsError::from)?
.st_rdev; .st_rdev;
let data = create_fd_data(ctx); let (data, index_map) = create_fd_data(render_ctx);
let mut memfd = let mut memfd =
uapi::memfd_create("drm_feedback", c::MFD_CLOEXEC | c::MFD_ALLOW_SEALING).unwrap(); uapi::memfd_create("drm_feedback", c::MFD_CLOEXEC | c::MFD_ALLOW_SEALING).unwrap();
memfd.write_all(&data).unwrap(); memfd.write_all(&data).unwrap();
@ -28,27 +49,69 @@ impl DrmFeedback {
c::F_SEAL_SEAL | c::F_SEAL_GROW | c::F_SEAL_SHRINK | c::F_SEAL_WRITE, c::F_SEAL_SEAL | c::F_SEAL_GROW | c::F_SEAL_SHRINK | c::F_SEAL_WRITE,
) )
.unwrap(); .unwrap();
let num_indices = data.len() / 16;
let indices = (0..num_indices).map(|v| v as u16).collect();
Ok(Self { Ok(Self {
fd: Rc::new(memfd), id: ids.next(),
size: data.len(), tranches: vec![DrmFeedbackTranche {
indices, device: main_device,
main_device: dev_t, indices: (0..index_map.len()).map(|v| v as u16).collect(),
scanout: false,
}],
shared: Rc::new(DrmFeedbackShared {
fd: Rc::new(memfd),
size: data.len(),
main_device,
indices: index_map,
}),
}) })
} }
pub fn for_scanout(
&self,
ids: &DrmFeedbackIds,
devnum: c::dev_t,
formats: &[(u32, Modifier)],
) -> Result<Option<Self>, DrmFeedbackError> {
let mut tranches = vec![];
{
let mut indices = vec![];
for (format, modifier) in formats {
if let Some(idx) = self.shared.indices.get(&(*format, *modifier)) {
indices.push(*idx);
}
}
if indices.len() > 0 {
tranches.push(DrmFeedbackTranche {
device: devnum,
indices,
scanout: true,
});
} else {
return Ok(None);
}
}
tranches.extend(self.tranches.iter().cloned());
Ok(Some(Self {
id: ids.next(),
shared: self.shared.clone(),
tranches,
}))
}
} }
fn create_fd_data(ctx: &dyn GfxContext) -> Vec<u8> { fn create_fd_data(ctx: &dyn GfxContext) -> (Vec<u8>, AHashMap<(u32, Modifier), u16>) {
let mut vec = vec![]; let mut vec = vec![];
let mut map = AHashMap::new();
let mut pos = 0;
for (format, info) in &*ctx.formats() { for (format, info) in &*ctx.formats() {
for modifier in &info.read_modifiers { for modifier in &info.read_modifiers {
vec.write_u32::<NativeEndian>(*format).unwrap(); vec.write_u32::<NativeEndian>(*format).unwrap();
vec.write_u32::<NativeEndian>(0).unwrap(); vec.write_u32::<NativeEndian>(0).unwrap();
vec.write_u64::<NativeEndian>(*modifier).unwrap(); vec.write_u64::<NativeEndian>(*modifier).unwrap();
map.insert((*format, *modifier), pos);
pos += 1;
} }
} }
vec (vec, map)
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]

View file

@ -26,6 +26,7 @@ pub struct Format {
pub has_alpha: bool, pub has_alpha: bool,
pub shm_supported: bool, pub shm_supported: bool,
pub pipewire: SpaVideoFormat, pub pipewire: SpaVideoFormat,
pub opaque: Option<&'static Format>,
} }
impl PartialEq for Format { impl PartialEq for Format {
@ -87,7 +88,6 @@ pub fn map_wayland_format_id(id: u32) -> u32 {
} }
} }
#[allow(dead_code)]
pub static ARGB8888: &Format = &Format { pub static ARGB8888: &Format = &Format {
name: "argb8888", name: "argb8888",
bpp: 4, bpp: 4,
@ -100,6 +100,7 @@ pub static ARGB8888: &Format = &Format {
has_alpha: true, has_alpha: true,
shm_supported: true, shm_supported: true,
pipewire: SPA_VIDEO_FORMAT_BGRA, pipewire: SPA_VIDEO_FORMAT_BGRA,
opaque: Some(XRGB8888),
}; };
pub static XRGB8888: &Format = &Format { pub static XRGB8888: &Format = &Format {
@ -114,38 +115,43 @@ pub static XRGB8888: &Format = &Format {
has_alpha: false, has_alpha: false,
shm_supported: true, shm_supported: true,
pipewire: SPA_VIDEO_FORMAT_BGRx, pipewire: SPA_VIDEO_FORMAT_BGRx,
opaque: None,
};
static ABGR8888: &Format = &Format {
name: "abgr8888",
bpp: 4,
gl_format: GL_RGBA,
gl_type: GL_UNSIGNED_BYTE,
vk_format: vk::Format::R8G8B8A8_UNORM,
drm: fourcc_code('A', 'B', '2', '4'),
wl_id: None,
external_only_guess: false,
has_alpha: true,
shm_supported: true,
pipewire: SPA_VIDEO_FORMAT_RGBA,
opaque: Some(XBGR8888),
};
static XBGR8888: &Format = &Format {
name: "xbgr8888",
bpp: 4,
gl_format: GL_RGBA,
gl_type: GL_UNSIGNED_BYTE,
vk_format: vk::Format::R8G8B8A8_UNORM,
drm: fourcc_code('X', 'B', '2', '4'),
wl_id: None,
external_only_guess: false,
has_alpha: false,
shm_supported: true,
pipewire: SPA_VIDEO_FORMAT_RGBx,
opaque: None,
}; };
pub static FORMATS: &[Format] = &[ pub static FORMATS: &[Format] = &[
*ARGB8888, *ARGB8888, *XRGB8888, *ABGR8888,
*XRGB8888, *XBGR8888,
// *NV12, // *NV12,
Format {
name: "abgr8888",
bpp: 4,
gl_format: GL_RGBA,
gl_type: GL_UNSIGNED_BYTE,
vk_format: vk::Format::R8G8B8A8_UNORM,
drm: fourcc_code('A', 'B', '2', '4'),
wl_id: None,
external_only_guess: false,
has_alpha: true,
shm_supported: true,
pipewire: SPA_VIDEO_FORMAT_RGBA,
},
Format {
name: "xbgr8888",
bpp: 4,
gl_format: GL_RGBA,
gl_type: GL_UNSIGNED_BYTE,
vk_format: vk::Format::R8G8B8A8_UNORM,
drm: fourcc_code('X', 'B', '2', '4'),
wl_id: None,
external_only_guess: false,
has_alpha: false,
shm_supported: true,
pipewire: SPA_VIDEO_FORMAT_RGBx,
},
// Format { // Format {
// name: "nv12", // name: "nv12",
// bpp: 1, // wrong but only used for shm // bpp: 1, // wrong but only used for shm

View file

@ -9,6 +9,7 @@ use {
state::State, state::State,
theme::Color, theme::Color,
tree::Node, tree::Node,
utils::numcell::NumCell,
video::{dmabuf::DmaBuf, gbm::GbmDevice, Modifier}, video::{dmabuf::DmaBuf, gbm::GbmDevice, Modifier},
}, },
ahash::AHashMap, ahash::AHashMap,
@ -31,7 +32,12 @@ pub enum GfxApiOpt {
CopyTexture(CopyTexture), CopyTexture(CopyTexture),
} }
#[derive(Default, Debug, Copy, Clone)] pub struct GfxRenderPass {
pub ops: Vec<GfxApiOpt>,
pub clear: Option<Color>,
}
#[derive(Default, Debug, Copy, Clone, PartialEq)]
pub struct BufferPoint { pub struct BufferPoint {
pub x: f32, pub x: f32,
pub y: f32, pub y: f32,
@ -41,9 +47,25 @@ impl BufferPoint {
pub fn is_leq_1(&self) -> bool { pub fn is_leq_1(&self) -> bool {
self.x <= 1.0 && self.y <= 1.0 self.x <= 1.0 && self.y <= 1.0
} }
pub fn top_left() -> Self {
Self { x: 0.0, y: 0.0 }
}
pub fn top_right() -> Self {
Self { x: 1.0, y: 0.0 }
}
pub fn bottom_left() -> Self {
Self { x: 0.0, y: 1.0 }
}
pub fn bottom_right() -> Self {
Self { x: 1.0, y: 1.0 }
}
} }
#[derive(Default, Debug, Copy, Clone)] #[derive(Default, Debug, Copy, Clone, PartialEq)]
pub struct BufferPoints { pub struct BufferPoints {
pub top_left: BufferPoint, pub top_left: BufferPoint,
pub top_right: BufferPoint, pub top_right: BufferPoint,
@ -79,6 +101,15 @@ impl BufferPoints {
&& self.bottom_left.is_leq_1() && self.bottom_left.is_leq_1()
&& self.bottom_right.is_leq_1() && self.bottom_right.is_leq_1()
} }
pub fn identity() -> Self {
Self {
top_left: BufferPoint::top_left(),
top_right: BufferPoint::top_right(),
bottom_left: BufferPoint::bottom_left(),
bottom_right: BufferPoint::bottom_right(),
}
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -172,7 +203,7 @@ impl dyn GfxFramebuffer {
self.render(ops, clear); self.render(ops, clear);
} }
pub fn render_node( pub fn create_render_pass(
&self, &self,
node: &dyn Node, node: &dyn Node,
state: &State, state: &State,
@ -180,7 +211,7 @@ impl dyn GfxFramebuffer {
result: Option<&mut RenderResult>, result: Option<&mut RenderResult>,
scale: Scale, scale: Scale,
render_hardware_cursor: bool, render_hardware_cursor: bool,
) { ) -> GfxRenderPass {
let mut ops = self.take_render_ops(); let mut ops = self.take_render_ops();
let (width, height) = self.size(); let (width, height) = self.size();
let mut renderer = Renderer { let mut renderer = Renderer {
@ -221,7 +252,34 @@ impl dyn GfxFramebuffer {
} }
} }
let c = state.theme.colors.background.get(); let c = state.theme.colors.background.get();
self.render(ops, Some(&c)); GfxRenderPass {
ops,
clear: Some(c),
}
}
pub fn perform_render_pass(&self, pass: GfxRenderPass) {
self.render(pass.ops, pass.clear.as_ref())
}
pub fn render_node(
&self,
node: &dyn Node,
state: &State,
cursor_rect: Option<Rect>,
result: Option<&mut RenderResult>,
scale: Scale,
render_hardware_cursor: bool,
) {
let pass = self.create_render_pass(
node,
state,
cursor_rect,
result,
scale,
render_hardware_cursor,
);
self.perform_render_pass(pass);
} }
pub fn render_hardware_cursor(&self, cursor: &dyn Cursor, state: &State, scale: Scale) { pub fn render_hardware_cursor(&self, cursor: &dyn Cursor, state: &State, scale: Scale) {
@ -253,6 +311,38 @@ pub trait GfxImage {
fn height(&self) -> i32; fn height(&self) -> i32;
} }
#[derive(Default)]
pub struct TextureReservations {
reservations: NumCell<usize>,
on_release: Cell<Option<Box<dyn FnOnce()>>>,
}
impl TextureReservations {
pub fn has_reservation(&self) -> bool {
self.reservations.get() != 0
}
pub fn acquire(&self) {
self.reservations.fetch_add(1);
}
pub fn release(&self) {
if self.reservations.fetch_sub(1) == 1 {
if let Some(cb) = self.on_release.take() {
cb();
}
}
}
pub fn on_released<C: FnOnce() + 'static>(&self, cb: C) {
if self.has_reservation() {
self.on_release.set(Some(Box::new(cb)));
} else {
cb();
}
}
}
pub trait GfxTexture: Debug { pub trait GfxTexture: Debug {
fn size(&self) -> (i32, i32); fn size(&self) -> (i32, i32);
fn as_any(&self) -> &dyn Any; fn as_any(&self) -> &dyn Any;
@ -267,6 +357,8 @@ pub trait GfxTexture: Debug {
format: &'static Format, format: &'static Format,
shm: &[Cell<u8>], shm: &[Cell<u8>],
) -> Result<(), GfxError>; ) -> Result<(), GfxError>;
fn dmabuf(&self) -> Option<&DmaBuf>;
fn reservations(&self) -> &TextureReservations;
} }
pub trait GfxContext: Debug { pub trait GfxContext: Debug {

View file

@ -259,10 +259,8 @@ impl EglDisplay {
Ok(Rc::new(EglImage { Ok(Rc::new(EglImage {
dpy: self.clone(), dpy: self.clone(),
img, img,
width: buf.width,
height: buf.height,
external_only: format.external_only, external_only: format.external_only,
format: buf.format, dmabuf: buf.clone(),
})) }))
} }
} }

View file

@ -1,11 +1,11 @@
use { use {
crate::{ crate::{
format::Format,
gfx_apis::gl::egl::{ gfx_apis::gl::egl::{
display::EglDisplay, display::EglDisplay,
sys::{EGLImageKHR, EGL_FALSE}, sys::{EGLImageKHR, EGL_FALSE},
PROCS, PROCS,
}, },
video::dmabuf::DmaBuf,
}, },
std::rc::Rc, std::rc::Rc,
}; };
@ -13,10 +13,8 @@ use {
pub struct EglImage { pub struct EglImage {
pub dpy: Rc<EglDisplay>, pub dpy: Rc<EglDisplay>,
pub img: EGLImageKHR, pub img: EGLImageKHR,
pub width: i32,
pub height: i32,
pub external_only: bool, pub external_only: bool,
pub format: &'static Format, pub dmabuf: DmaBuf,
} }
impl Drop for EglImage { impl Drop for EglImage {

View file

@ -60,8 +60,8 @@ impl GlRenderBuffer {
_tex: None, _tex: None,
ctx: self.ctx.clone(), ctx: self.ctx.clone(),
fbo, fbo,
width: self.img.width, width: self.img.dmabuf.width,
height: self.img.height, height: self.img.dmabuf.height,
}; };
if status != GL_FRAMEBUFFER_COMPLETE { if status != GL_FRAMEBUFFER_COMPLETE {
return Err(RenderError::CreateFramebuffer); return Err(RenderError::CreateFramebuffer);

View file

@ -56,10 +56,10 @@ impl GlTexture {
ctx: ctx.clone(), ctx: ctx.clone(),
img: Some(img.clone()), img: Some(img.clone()),
tex, tex,
width: img.width, width: img.dmabuf.width,
height: img.height, height: img.dmabuf.height,
external_only: img.external_only, external_only: img.external_only,
format: img.format, format: img.dmabuf.format,
}) })
} }

View file

@ -186,6 +186,7 @@ impl GlRenderContext {
Ok(Rc::new(Texture { Ok(Rc::new(Texture {
ctx: self.clone(), ctx: self.clone(),
gl, gl,
resv: Default::default(),
})) }))
} }
} }

View file

@ -120,6 +120,6 @@ impl GfxFramebuffer for Framebuffer {
} }
fn format(&self) -> &'static Format { fn format(&self) -> &'static Format {
self.gl.rb.img.format self.gl.rb.img.dmabuf.format
} }
} }

View file

@ -17,17 +17,18 @@ pub struct Image {
impl Image { impl Image {
pub fn width(&self) -> i32 { pub fn width(&self) -> i32 {
self.gl.width self.gl.dmabuf.width
} }
pub fn height(&self) -> i32 { pub fn height(&self) -> i32 {
self.gl.height self.gl.dmabuf.height
} }
fn to_texture(self: &Rc<Self>) -> Result<Rc<Texture>, RenderError> { fn to_texture(self: &Rc<Self>) -> Result<Rc<Texture>, RenderError> {
Ok(Rc::new(Texture { Ok(Rc::new(Texture {
ctx: self.ctx.clone(), ctx: self.ctx.clone(),
gl: GlTexture::import_img(&self.ctx.ctx, &self.gl)?, gl: GlTexture::import_img(&self.ctx.ctx, &self.gl)?,
resv: Default::default(),
})) }))
} }

View file

@ -1,8 +1,9 @@
use { use {
crate::{ crate::{
format::Format, format::Format,
gfx_api::{GfxError, GfxTexture}, gfx_api::{GfxError, GfxTexture, TextureReservations},
gfx_apis::gl::{gl::texture::GlTexture, renderer::context::GlRenderContext, RenderError}, gfx_apis::gl::{gl::texture::GlTexture, renderer::context::GlRenderContext, RenderError},
video::dmabuf::DmaBuf,
}, },
std::{ std::{
any::Any, any::Any,
@ -15,6 +16,7 @@ use {
pub struct Texture { pub struct Texture {
pub(in crate::gfx_apis::gl) ctx: Rc<GlRenderContext>, pub(in crate::gfx_apis::gl) ctx: Rc<GlRenderContext>,
pub(in crate::gfx_apis::gl) gl: GlTexture, pub(in crate::gfx_apis::gl) gl: GlTexture,
pub(in crate::gfx_apis::gl) resv: TextureReservations,
} }
impl Debug for Texture { impl Debug for Texture {
@ -58,4 +60,12 @@ impl GfxTexture for Texture {
) -> Result<(), GfxError> { ) -> Result<(), GfxError> {
Err(RenderError::UnsupportedOperation.into()) Err(RenderError::UnsupportedOperation.into())
} }
fn dmabuf(&self) -> Option<&DmaBuf> {
self.gl.img.as_ref().map(|i| &i.dmabuf)
}
fn reservations(&self) -> &TextureReservations {
&self.resv
}
} }

View file

@ -1,17 +1,14 @@
use { use {
crate::{ crate::{
format::Format, format::Format,
gfx_api::{GfxApiOpt, GfxError, GfxFramebuffer, GfxImage, GfxTexture}, gfx_api::{GfxApiOpt, GfxError, GfxFramebuffer, GfxImage, GfxTexture, TextureReservations},
gfx_apis::vulkan::{ gfx_apis::vulkan::{
allocator::VulkanAllocation, device::VulkanDevice, format::VulkanMaxExtents, allocator::VulkanAllocation, device::VulkanDevice, format::VulkanMaxExtents,
renderer::VulkanRenderer, util::OnDrop, VulkanError, renderer::VulkanRenderer, util::OnDrop, VulkanError,
}, },
theme::Color, theme::Color,
utils::clonecell::CloneCell, utils::clonecell::CloneCell,
video::{ video::dmabuf::{DmaBuf, PlaneVec},
dmabuf::{DmaBuf, DmaBufPlane, PlaneVec},
Modifier,
},
}, },
ash::vk::{ ash::vk::{
BindImageMemoryInfo, BindImagePlaneMemoryInfo, ComponentMapping, ComponentSwizzle, BindImageMemoryInfo, BindImagePlaneMemoryInfo, ComponentMapping, ComponentSwizzle,
@ -36,12 +33,10 @@ use {
pub struct VulkanDmaBufImageTemplate { pub struct VulkanDmaBufImageTemplate {
pub(super) renderer: Rc<VulkanRenderer>, pub(super) renderer: Rc<VulkanRenderer>,
pub(super) format: &'static Format,
pub(super) width: u32, pub(super) width: u32,
pub(super) height: u32, pub(super) height: u32,
pub(super) modifier: Modifier,
pub(super) disjoint: bool, pub(super) disjoint: bool,
pub(super) planes: PlaneVec<DmaBufPlane>, pub(super) dmabuf: DmaBuf,
pub(super) render_max_extents: Option<VulkanMaxExtents>, pub(super) render_max_extents: Option<VulkanMaxExtents>,
pub(super) texture_max_extents: Option<VulkanMaxExtents>, pub(super) texture_max_extents: Option<VulkanMaxExtents>,
} }
@ -58,6 +53,7 @@ pub struct VulkanImage {
pub(super) is_undefined: Cell<bool>, pub(super) is_undefined: Cell<bool>,
pub(super) ty: VulkanImageMemory, pub(super) ty: VulkanImageMemory,
pub(super) render_ops: CloneCell<Vec<GfxApiOpt>>, pub(super) render_ops: CloneCell<Vec<GfxApiOpt>>,
pub(super) resv: TextureReservations,
} }
pub enum VulkanImageMemory { pub enum VulkanImageMemory {
@ -216,6 +212,7 @@ impl VulkanRenderer {
is_undefined: Cell::new(true), is_undefined: Cell::new(true),
ty: VulkanImageMemory::Internal(shm), ty: VulkanImageMemory::Internal(shm),
render_ops: Default::default(), render_ops: Default::default(),
resv: Default::default(),
})) }))
} }
@ -260,12 +257,10 @@ impl VulkanRenderer {
} }
Ok(Rc::new(VulkanDmaBufImageTemplate { Ok(Rc::new(VulkanDmaBufImageTemplate {
renderer: self.clone(), renderer: self.clone(),
format: dmabuf.format,
width, width,
height, height,
modifier: dmabuf.modifier,
disjoint, disjoint,
planes: dmabuf.planes.clone(), dmabuf: dmabuf.clone(),
render_max_extents: modifier.render_max_extents, render_max_extents: modifier.render_max_extents,
texture_max_extents: modifier.texture_max_extents, texture_max_extents: modifier.texture_max_extents,
})) }))
@ -332,6 +327,7 @@ impl VulkanDmaBufImageTemplate {
} }
let image = { let image = {
let plane_layouts: PlaneVec<_> = self let plane_layouts: PlaneVec<_> = self
.dmabuf
.planes .planes
.iter() .iter()
.map(|p| SubresourceLayout { .map(|p| SubresourceLayout {
@ -343,7 +339,7 @@ impl VulkanDmaBufImageTemplate {
}) })
.collect(); .collect();
let mut mod_info = ImageDrmFormatModifierExplicitCreateInfoEXT::builder() let mut mod_info = ImageDrmFormatModifierExplicitCreateInfoEXT::builder()
.drm_format_modifier(self.modifier) .drm_format_modifier(self.dmabuf.modifier)
.plane_layouts(&plane_layouts) .plane_layouts(&plane_layouts)
.build(); .build();
let mut memory_image_create_info = ExternalMemoryImageCreateInfo::builder() let mut memory_image_create_info = ExternalMemoryImageCreateInfo::builder()
@ -361,7 +357,7 @@ impl VulkanDmaBufImageTemplate {
}; };
let create_info = ImageCreateInfo::builder() let create_info = ImageCreateInfo::builder()
.image_type(ImageType::TYPE_2D) .image_type(ImageType::TYPE_2D)
.format(self.format.vk_format) .format(self.dmabuf.format.vk_format)
.mip_levels(1) .mip_levels(1)
.array_layers(1) .array_layers(1)
.tiling(ImageTiling::DRM_FORMAT_MODIFIER_EXT) .tiling(ImageTiling::DRM_FORMAT_MODIFIER_EXT)
@ -383,14 +379,14 @@ impl VulkanDmaBufImageTemplate {
}; };
let destroy_image = OnDrop(|| unsafe { device.device.destroy_image(image, None) }); let destroy_image = OnDrop(|| unsafe { device.device.destroy_image(image, None) });
let num_device_memories = match self.disjoint { let num_device_memories = match self.disjoint {
true => self.planes.len(), true => self.dmabuf.planes.len(),
false => 1, false => 1,
}; };
let mut device_memories = PlaneVec::new(); let mut device_memories = PlaneVec::new();
let mut free_device_memories = PlaneVec::new(); let mut free_device_memories = PlaneVec::new();
let mut bind_image_plane_memory_infos = PlaneVec::new(); let mut bind_image_plane_memory_infos = PlaneVec::new();
for plane_idx in 0..num_device_memories { for plane_idx in 0..num_device_memories {
let dma_buf_plane = &self.planes[plane_idx]; let dma_buf_plane = &self.dmabuf.planes[plane_idx];
let memory_fd_properties = unsafe { let memory_fd_properties = unsafe {
device.external_memory_fd.get_memory_fd_properties( device.external_memory_fd.get_memory_fd_properties(
ExternalMemoryHandleTypeFlags::DMA_BUF_EXT, ExternalMemoryHandleTypeFlags::DMA_BUF_EXT,
@ -467,8 +463,8 @@ impl VulkanDmaBufImageTemplate {
} }
let res = unsafe { device.device.bind_image_memory2(&bind_image_memory_infos) }; let res = unsafe { device.device.bind_image_memory2(&bind_image_memory_infos) };
res.map_err(VulkanError::BindImageMemory)?; res.map_err(VulkanError::BindImageMemory)?;
let texture_view = device.create_image_view(image, self.format, false)?; let texture_view = device.create_image_view(image, self.dmabuf.format, false)?;
let render_view = device.create_image_view(image, self.format, true)?; let render_view = device.create_image_view(image, self.dmabuf.format, true)?;
free_device_memories.drain(..).for_each(mem::forget); free_device_memories.drain(..).for_each(mem::forget);
mem::forget(destroy_image); mem::forget(destroy_image);
Ok(Rc::new(VulkanImage { Ok(Rc::new(VulkanImage {
@ -484,8 +480,9 @@ impl VulkanDmaBufImageTemplate {
template: self.clone(), template: self.clone(),
mems: device_memories, mems: device_memories,
}), }),
format: self.format, format: self.dmabuf.format,
is_undefined: Cell::new(true), is_undefined: Cell::new(true),
resv: Default::default(),
})) }))
} }
} }
@ -579,4 +576,15 @@ impl GfxTexture for VulkanImage {
.read_pixels(&self, x, y, width, height, stride, format, shm) .read_pixels(&self, x, y, width, height, stride, format, shm)
.map_err(|e| e.into()) .map_err(|e| e.into())
} }
fn dmabuf(&self) -> Option<&DmaBuf> {
match &self.ty {
VulkanImageMemory::DmaBuf(b) => Some(&b.template.dmabuf),
VulkanImageMemory::Internal(_) => None,
}
}
fn reservations(&self) -> &TextureReservations {
&self.resv
}
} }

View file

@ -532,7 +532,7 @@ impl VulkanRenderer {
flag: u32| flag: u32|
-> Result<(), VulkanError> { -> Result<(), VulkanError> {
if let VulkanImageMemory::DmaBuf(buf) = &img.ty { if let VulkanImageMemory::DmaBuf(buf) = &img.ty {
for plane in &buf.template.planes { for plane in &buf.template.dmabuf.planes {
let fd = dma_buf_export_sync_file(&plane.fd, flag) let fd = dma_buf_export_sync_file(&plane.fd, flag)
.map_err(VulkanError::IoctlExportSyncFile)?; .map_err(VulkanError::IoctlExportSyncFile)?;
let semaphore = self.allocate_semaphore()?; let semaphore = self.allocate_semaphore()?;
@ -573,7 +573,7 @@ impl VulkanRenderer {
}; };
let import = |img: &VulkanImage, flag: u32| { let import = |img: &VulkanImage, flag: u32| {
if let VulkanImageMemory::DmaBuf(buf) = &img.ty { if let VulkanImageMemory::DmaBuf(buf) = &img.ty {
for plane in &buf.template.planes { for plane in &buf.template.dmabuf.planes {
let res = dma_buf_import_sync_file(&plane.fd, flag, &syncfile) let res = dma_buf_import_sync_file(&plane.fd, flag, &syncfile)
.map_err(VulkanError::IoctlImportSyncFile); .map_err(VulkanError::IoctlImportSyncFile);
if let Err(e) = res { if let Err(e) = res {
@ -764,7 +764,7 @@ impl VulkanRenderer {
let mut semaphores = vec![]; let mut semaphores = vec![];
let mut semaphore_infos = vec![]; let mut semaphore_infos = vec![];
if let VulkanImageMemory::DmaBuf(buf) = &tex.ty { if let VulkanImageMemory::DmaBuf(buf) = &tex.ty {
for plane in &buf.template.planes { for plane in &buf.template.dmabuf.planes {
let fd = dma_buf_export_sync_file(&plane.fd, DMA_BUF_SYNC_READ) let fd = dma_buf_export_sync_file(&plane.fd, DMA_BUF_SYNC_READ)
.map_err(VulkanError::IoctlExportSyncFile)?; .map_err(VulkanError::IoctlExportSyncFile)?;
let semaphore = self.allocate_semaphore()?; let semaphore = self.allocate_semaphore()?;

View file

@ -236,9 +236,14 @@ impl JayScreencast {
} }
false => &format.write_modifiers, false => &format.write_modifiers,
}; };
let buffer = let buffer = ctx.gbm().create_bo(
ctx.gbm() &self.client.state.dma_buf_ids,
.create_bo(mode.width, mode.height, XRGB8888, modifiers, usage)?; mode.width,
mode.height,
XRGB8888,
modifiers,
usage,
)?;
let fb = ctx.clone().dmabuf_img(buffer.dmabuf())?.to_framebuffer()?; let fb = ctx.clone().dmabuf_img(buffer.dmabuf())?.to_framebuffer()?;
buffers.push(ScreencastBuffer { buffers.push(ScreencastBuffer {
dmabuf: buffer.dmabuf().clone(), dmabuf: buffer.dmabuf().clone(),

View file

@ -119,6 +119,7 @@ impl WlDrm {
None => return Err(WlDrmError::InvalidFormat(req.format)), None => return Err(WlDrmError::InvalidFormat(req.format)),
}; };
let mut dmabuf = DmaBuf { let mut dmabuf = DmaBuf {
id: self.client.state.dma_buf_ids.next(),
width: req.width, width: req.width,
height: req.height, height: req.height,
format, format,

View file

@ -202,9 +202,13 @@ impl WlOutputGlobal {
Ok(()) Ok(())
} }
pub fn have_shm_screencopies(&self) -> bool {
self.pending_captures.iter().any(|c| c.is_shm.get())
}
pub fn perform_screencopies( pub fn perform_screencopies(
&self, &self,
fb: &dyn GfxFramebuffer, fb: Option<&dyn GfxFramebuffer>,
tex: &Rc<dyn GfxTexture>, tex: &Rc<dyn GfxTexture>,
render_hardware_cursors: bool, render_hardware_cursors: bool,
) { ) {
@ -232,12 +236,13 @@ impl WlOutputGlobal {
wl_buffer.storage.borrow_mut().deref() wl_buffer.storage.borrow_mut().deref()
{ {
let acc = mem.access(|mem| { let acc = mem.access(|mem| {
fb.copy_to_shm( tex.clone().read_pixels(
rect.x1(), capture.rect.x1(),
rect.y1(), capture.rect.y1(),
rect.width(), capture.rect.width(),
rect.height(), capture.rect.height(),
XRGB8888, *stride,
wl_buffer.format,
mem, mem,
) )
}); });
@ -249,24 +254,25 @@ impl WlOutputGlobal {
} }
}; };
if res.is_err() { if res.is_err() {
let acc = mem.access(|mem| { if let Some(fb) = fb {
tex.clone().read_pixels( let acc = mem.access(|mem| {
capture.rect.x1(), fb.copy_to_shm(
capture.rect.y1(), rect.x1(),
capture.rect.width(), rect.y1(),
capture.rect.height(), rect.width(),
*stride, rect.height(),
wl_buffer.format, XRGB8888,
mem, mem,
) )
}); });
res = match acc { res = match acc {
Ok(res) => res, Ok(res) => res,
Err(e) => { Err(e) => {
capture.client.error(e); capture.client.error(e);
continue; continue;
} }
}; };
}
} }
if let Err(e) = res { if let Err(e) = res {
log::warn!("Could not read texture to memory: {}", ErrorFmt(e)); log::warn!("Could not read texture to memory: {}", ErrorFmt(e));

View file

@ -14,6 +14,7 @@ use {
crate::{ crate::{
backend::KeyState, backend::KeyState,
client::{Client, ClientError, RequestParser}, client::{Client, ClientError, RequestParser},
drm_feedback::DrmFeedback,
fixed::Fixed, fixed::Fixed,
gfx_api::{BufferPoint, BufferPoints}, gfx_api::{BufferPoint, BufferPoints},
ifs::{ ifs::{
@ -36,6 +37,7 @@ use {
}, },
wp_content_type_v1::ContentType, wp_content_type_v1::ContentType,
wp_presentation_feedback::WpPresentationFeedback, wp_presentation_feedback::WpPresentationFeedback,
zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
}, },
leaks::Tracker, leaks::Tracker,
object::Object, object::Object,
@ -53,7 +55,10 @@ use {
numcell::NumCell, numcell::NumCell,
smallmap::SmallMap, smallmap::SmallMap,
}, },
wire::{wl_surface::*, WlOutputId, WlSurfaceId, ZwpIdleInhibitorV1Id}, wire::{
wl_surface::*, WlOutputId, WlSurfaceId, ZwpIdleInhibitorV1Id,
ZwpLinuxDmabufFeedbackV1Id,
},
xkbcommon::ModifierState, xkbcommon::ModifierState,
xwayland::XWaylandEvent, xwayland::XWaylandEvent,
}, },
@ -259,6 +264,7 @@ pub struct WlSurface {
version: u32, version: u32,
pub has_content_type_manager: Cell<bool>, pub has_content_type_manager: Cell<bool>,
content_type: Cell<Option<ContentType>>, content_type: Cell<Option<ContentType>>,
pub drm_feedback: CopyHashMap<ZwpLinuxDmabufFeedbackV1Id, Rc<ZwpLinuxDmabufFeedbackV1>>,
} }
impl Debug for WlSurface { impl Debug for WlSurface {
@ -411,6 +417,7 @@ impl WlSurface {
version, version,
has_content_type_manager: Default::default(), has_content_type_manager: Default::default(),
content_type: Default::default(), content_type: Default::default(),
drm_feedback: Default::default(),
} }
} }
@ -762,7 +769,23 @@ impl WlSurface {
if let Some(buffer) = self.buffer.take() { if let Some(buffer) = self.buffer.take() {
old_raw_size = Some(buffer.rect); old_raw_size = Some(buffer.rect);
if !buffer.destroyed() { if !buffer.destroyed() {
buffer.send_release(); 'handle_release: {
if let Some(tex) = buffer.texture.get() {
let resv = tex.reservations();
if resv.has_reservation() {
let buffer = Rc::downgrade(&buffer);
resv.on_released(move || {
if let Some(buffer) = buffer.upgrade() {
if !buffer.destroyed() {
buffer.send_release();
}
}
});
break 'handle_release;
}
}
buffer.send_release();
}
} }
} }
if let Some(buffer) = buffer_change { if let Some(buffer) = buffer_change {
@ -1065,6 +1088,12 @@ impl WlSurface {
tl.tl_data().request_attention(tl.tl_as_node()); tl.tl_data().request_attention(tl.tl_as_node());
} }
} }
pub fn send_feedback(&self, fb: &DrmFeedback) {
for consumer in self.drm_feedback.lock().values() {
consumer.send_feedback(fb);
}
}
} }
object_base! { object_base! {
@ -1100,6 +1129,7 @@ impl Object for WlSurface {
self.fractional_scale.take(); self.fractional_scale.take();
self.tearing_control.take(); self.tearing_control.take();
self.constraints.clear(); self.constraints.clear();
self.drm_feedback.clear();
} }
} }

View file

@ -424,6 +424,10 @@ impl ToplevelNode for Xwindow {
self.display_link.borrow_mut().take(); self.display_link.borrow_mut().take();
self.x.surface.destroy_node(); self.x.surface.destroy_node();
} }
fn tl_scanout_surface(&self) -> Option<Rc<WlSurface>> {
Some(self.x.surface.clone())
}
} }
impl StackedNode for Xwindow { impl StackedNode for Xwindow {

View file

@ -8,7 +8,10 @@ use {
ifs::{ ifs::{
ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1,
wl_seat::{NodeSeatState, SeatId, WlSeatGlobal}, wl_seat::{NodeSeatState, SeatId, WlSeatGlobal},
wl_surface::xdg_surface::{XdgSurface, XdgSurfaceError, XdgSurfaceExt}, wl_surface::{
xdg_surface::{XdgSurface, XdgSurfaceError, XdgSurfaceExt},
WlSurface,
},
}, },
leaks::Tracker, leaks::Tracker,
object::Object, object::Object,
@ -544,6 +547,10 @@ impl ToplevelNode for XdgToplevel {
// self.map_tiled() // self.map_tiled()
// } // }
// } // }
fn tl_scanout_surface(&self) -> Option<Rc<WlSurface>> {
Some(self.xdg.surface.clone())
}
} }
impl XdgSurfaceExt for XdgToplevel { impl XdgSurfaceExt for XdgToplevel {

View file

@ -33,6 +33,7 @@ pub struct ZwlrScreencopyFrameV1 {
pub with_damage: Cell<bool>, pub with_damage: Cell<bool>,
pub output_link: Cell<Option<LinkedNode<Rc<Self>>>>, pub output_link: Cell<Option<LinkedNode<Rc<Self>>>>,
pub buffer: Cell<Option<Rc<WlBuffer>>>, pub buffer: Cell<Option<Rc<WlBuffer>>>,
pub is_shm: Cell<bool>,
pub version: u32, pub version: u32,
} }
@ -119,6 +120,14 @@ impl ZwlrScreencopyFrameV1 {
return Err(ZwlrScreencopyFrameV1Error::InvalidBufferStride); return Err(ZwlrScreencopyFrameV1Error::InvalidBufferStride);
} }
} }
let is_shm = match &*buffer.storage.borrow() {
None => false,
Some(s) => match s {
WlBufferStorage::Shm { .. } => true,
WlBufferStorage::Dmabuf(_) => false,
},
};
self.is_shm.set(is_shm);
self.buffer.set(Some(buffer)); self.buffer.set(Some(buffer));
if !with_damage { if !with_damage {
self.output.connector.connector.damage(); self.output.connector.connector.damage();

View file

@ -112,6 +112,7 @@ impl ZwlrScreencopyManagerV1 {
with_damage: Cell::new(false), with_damage: Cell::new(false),
output_link: Cell::new(None), output_link: Cell::new(None),
buffer: Cell::new(None), buffer: Cell::new(None),
is_shm: Cell::new(false),
version: self.version, version: self.version,
}); });
track!(self.client, frame); track!(self.client, frame);

View file

@ -114,6 +114,7 @@ impl ZwpLinuxBufferParamsV1 {
return Err(ZwpLinuxBufferParamsV1Error::InvalidModifier(modifier)); return Err(ZwpLinuxBufferParamsV1Error::InvalidModifier(modifier));
} }
let mut dmabuf = DmaBuf { let mut dmabuf = DmaBuf {
id: self.parent.client.state.dma_buf_ids.next(),
width, width,
height, height,
format: format.format, format: format.format,

View file

@ -1,13 +1,14 @@
use { use {
crate::{ crate::{
client::{Client, ClientError}, client::{Client, ClientError},
drm_feedback::DrmFeedback, drm_feedback::{DrmFeedback, DrmFeedbackId},
ifs::wl_surface::WlSurface,
leaks::Tracker, leaks::Tracker,
object::Object, object::Object,
utils::buffd::{MsgParser, MsgParserError}, utils::buffd::{MsgParser, MsgParserError},
wire::{zwp_linux_dmabuf_feedback_v1::*, ZwpLinuxDmabufFeedbackV1Id}, wire::{zwp_linux_dmabuf_feedback_v1::*, ZwpLinuxDmabufFeedbackV1Id},
}, },
std::rc::Rc, std::{cell::Cell, rc::Rc},
thiserror::Error, thiserror::Error,
uapi::{c, OwnedFd}, uapi::{c, OwnedFd},
}; };
@ -19,24 +20,37 @@ pub struct ZwpLinuxDmabufFeedbackV1 {
pub id: ZwpLinuxDmabufFeedbackV1Id, pub id: ZwpLinuxDmabufFeedbackV1Id,
pub client: Rc<Client>, pub client: Rc<Client>,
pub tracker: Tracker<Self>, pub tracker: Tracker<Self>,
pub last_feedback: Cell<Option<DrmFeedbackId>>,
pub surface: Option<Rc<WlSurface>>,
} }
impl ZwpLinuxDmabufFeedbackV1 { impl ZwpLinuxDmabufFeedbackV1 {
pub fn new(id: ZwpLinuxDmabufFeedbackV1Id, client: &Rc<Client>) -> Self { pub fn new(
id: ZwpLinuxDmabufFeedbackV1Id,
client: &Rc<Client>,
surface: Option<&Rc<WlSurface>>,
) -> Self {
Self { Self {
id, id,
client: client.clone(), client: client.clone(),
tracker: Default::default(), tracker: Default::default(),
last_feedback: Default::default(),
surface: surface.cloned(),
} }
} }
pub fn send_feedback(&self, feedback: &DrmFeedback) { pub fn send_feedback(&self, feedback: &DrmFeedback) {
self.send_format_table(&feedback.fd, feedback.size); if self.last_feedback.replace(Some(feedback.id)) == Some(feedback.id) {
self.send_main_device(feedback.main_device); return;
self.send_tranche_target_device(feedback.main_device); }
self.send_tranche_formats(&feedback.indices); self.send_format_table(&feedback.shared.fd, feedback.shared.size);
self.send_tranche_flags(0); self.send_main_device(feedback.shared.main_device);
self.send_tranche_done(); for tranch in &feedback.tranches {
self.send_tranche_target_device(tranch.device);
self.send_tranche_formats(&tranch.indices);
self.send_tranche_flags(if tranch.scanout { SCANOUT } else { 0 });
self.send_tranche_done();
}
self.send_done(); self.send_done();
} }
@ -96,6 +110,9 @@ impl ZwpLinuxDmabufFeedbackV1 {
.state .state
.drm_feedback_consumers .drm_feedback_consumers
.remove(&(self.client.id, self.id)); .remove(&(self.client.id, self.id));
if let Some(surface) = &self.surface {
surface.drm_feedback.remove(&self.id);
}
} }
} }

View file

@ -3,7 +3,7 @@ use {
client::{Client, ClientError}, client::{Client, ClientError},
globals::{Global, GlobalName}, globals::{Global, GlobalName},
ifs::{ ifs::{
zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, wl_surface::WlSurface, zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1,
zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
}, },
leaks::Tracker, leaks::Tracker,
@ -120,8 +120,9 @@ impl ZwpLinuxDmabufV1 {
fn get_feedback( fn get_feedback(
self: &Rc<Self>, self: &Rc<Self>,
id: ZwpLinuxDmabufFeedbackV1Id, id: ZwpLinuxDmabufFeedbackV1Id,
) -> Result<(), ZwpLinuxDmabufV1Error> { surface: Option<&Rc<WlSurface>>,
let fb = Rc::new(ZwpLinuxDmabufFeedbackV1::new(id, &self.client)); ) -> Result<Rc<ZwpLinuxDmabufFeedbackV1>, ZwpLinuxDmabufV1Error> {
let fb = Rc::new(ZwpLinuxDmabufFeedbackV1::new(id, &self.client, surface));
track!(self.client, fb); track!(self.client, fb);
self.client.add_client_obj(&fb)?; self.client.add_client_obj(&fb)?;
self.client self.client
@ -131,7 +132,7 @@ impl ZwpLinuxDmabufV1 {
if let Some(feedback) = self.client.state.drm_feedback.get() { if let Some(feedback) = self.client.state.drm_feedback.get() {
fb.send_feedback(&feedback); fb.send_feedback(&feedback);
} }
Ok(()) Ok(fb)
} }
fn get_default_feedback( fn get_default_feedback(
@ -139,7 +140,8 @@ impl ZwpLinuxDmabufV1 {
parser: MsgParser<'_, '_>, parser: MsgParser<'_, '_>,
) -> Result<(), ZwpLinuxDmabufV1Error> { ) -> Result<(), ZwpLinuxDmabufV1Error> {
let req: GetDefaultFeedback = self.client.parse(&**self, parser)?; let req: GetDefaultFeedback = self.client.parse(&**self, parser)?;
self.get_feedback(req.id) self.get_feedback(req.id, None)?;
Ok(())
} }
fn get_surface_feedback( fn get_surface_feedback(
@ -147,8 +149,10 @@ impl ZwpLinuxDmabufV1 {
parser: MsgParser<'_, '_>, parser: MsgParser<'_, '_>,
) -> Result<(), ZwpLinuxDmabufV1Error> { ) -> Result<(), ZwpLinuxDmabufV1Error> {
let req: GetSurfaceFeedback = self.client.parse(&**self, parser)?; let req: GetSurfaceFeedback = self.client.parse(&**self, parser)?;
let _surface = self.client.lookup(req.surface)?; let surface = self.client.lookup(req.surface)?;
self.get_feedback(req.id) let fb = self.get_feedback(req.id, Some(&surface))?;
surface.drm_feedback.set(req.id, fb);
Ok(())
} }
} }

View file

@ -85,7 +85,7 @@ impl TestClient {
pub async fn take_screenshot(&self) -> Result<Vec<u8>, TestError> { pub async fn take_screenshot(&self) -> Result<Vec<u8>, TestError> {
let dmabuf = self.jc.take_screenshot().await?; let dmabuf = self.jc.take_screenshot().await?;
let qoi = buf_to_qoi(&dmabuf); let qoi = buf_to_qoi(&self.server.state.dma_buf_ids, &dmabuf);
Ok(qoi) Ok(qoi)
} }

View file

@ -23,6 +23,7 @@ use {
copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell,
run_toplevel::RunToplevel, xrd::xrd, run_toplevel::RunToplevel, xrd::xrd,
}, },
video::dmabuf::DmaBufIds,
wheel::Wheel, wheel::Wheel,
wire_dbus::org, wire_dbus::org,
}, },
@ -84,6 +85,7 @@ async fn run_async(eng: Rc<AsyncEngine>, ring: Rc<IoUring>) {
screencasts: Default::default(), screencasts: Default::default(),
next_id: NumCell::new(1), next_id: NumCell::new(1),
render_ctxs: Default::default(), render_ctxs: Default::default(),
dma_buf_ids: Default::default(),
}); });
let _root = { let _root = {
let obj = state let obj = state
@ -143,6 +145,7 @@ struct PortalState {
screencasts: CopyHashMap<String, Rc<ScreencastSession>>, screencasts: CopyHashMap<String, Rc<ScreencastSession>>,
next_id: NumCell<u32>, next_id: NumCell<u32>,
render_ctxs: CopyHashMap<c::dev_t, Weak<PortalRenderCtx>>, render_ctxs: CopyHashMap<c::dev_t, Weak<PortalRenderCtx>>,
dma_buf_ids: Rc<DmaBufIds>,
} }
impl PortalState { impl PortalState {

View file

@ -247,7 +247,16 @@ async fn maybe_add_display(state: &Rc<PortalState>, name: &str) {
_ => return, _ => return,
}; };
let path = format!("{}/{}", state.xrd, name); let path = format!("{}/{}", state.xrd, name);
let con = match UsrCon::new(&state.ring, &state.wheel, &state.eng, &path, num).await { let con = match UsrCon::new(
&state.ring,
&state.wheel,
&state.eng,
&state.dma_buf_ids,
&path,
num,
)
.await
{
Ok(c) => c, Ok(c) => c,
Err(e) => { Err(e) => {
log::error!( log::error!(

View file

@ -710,6 +710,7 @@ impl WindowData {
} }
for _ in 0..NUM_BUFFERS { for _ in 0..NUM_BUFFERS {
let bo = match ctx.ctx.gbm().create_bo( let bo = match ctx.ctx.gbm().create_bo(
&self.dpy.state.dma_buf_ids,
width, width,
height, height,
ARGB8888, ARGB8888,

View file

@ -35,6 +35,15 @@ pub struct RenderResult {
pub presentation_feedbacks: Vec<Rc<WpPresentationFeedback>>, pub presentation_feedbacks: Vec<Rc<WpPresentationFeedback>>,
} }
impl RenderResult {
pub fn dispatch_frame_requests(&mut self) {
for fr in self.frame_requests.drain(..) {
fr.send_done();
let _ = fr.client.remove_obj(&*fr);
}
}
}
impl Debug for RenderResult { impl Debug for RenderResult {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RenderResult").finish_non_exhaustive() f.debug_struct("RenderResult").finish_non_exhaustive()

View file

@ -60,6 +60,7 @@ pub fn take_screenshot(state: &State) -> Result<Screenshot, ScreenshooterError>
}; };
let gbm = ctx.gbm(); let gbm = ctx.gbm();
let bo = gbm.create_bo( let bo = gbm.create_bo(
&state.dma_buf_ids,
extents.width(), extents.width(),
extents.height(), extents.height(),
XRGB8888, XRGB8888,

View file

@ -12,7 +12,7 @@ use {
config::ConfigProxy, config::ConfigProxy,
cursor::{Cursor, ServerCursors}, cursor::{Cursor, ServerCursors},
dbus::Dbus, dbus::Dbus,
drm_feedback::DrmFeedback, drm_feedback::{DrmFeedback, DrmFeedbackIds},
fixed::Fixed, fixed::Fixed,
forker::ForkerProxy, forker::ForkerProxy,
gfx_api::{GfxContext, GfxError, GfxFramebuffer, GfxTexture}, gfx_api::{GfxContext, GfxError, GfxFramebuffer, GfxTexture},
@ -50,7 +50,7 @@ use {
linkedlist::LinkedList, numcell::NumCell, queue::AsyncQueue, refcounted::RefCounted, linkedlist::LinkedList, numcell::NumCell, queue::AsyncQueue, refcounted::RefCounted,
run_toplevel::RunToplevel, run_toplevel::RunToplevel,
}, },
video::drm::Drm, video::{dmabuf::DmaBufIds, drm::Drm},
wheel::Wheel, wheel::Wheel,
wire::{ wire::{
ExtForeignToplevelListV1Id, JayRenderCtxId, JaySeatEventsId, JayWorkspaceWatcherId, ExtForeignToplevelListV1Id, JayRenderCtxId, JaySeatEventsId, JayWorkspaceWatcherId,
@ -142,6 +142,9 @@ pub struct State {
pub activation_tokens: CopyHashMap<ActivationToken, ()>, pub activation_tokens: CopyHashMap<ActivationToken, ()>,
pub toplevel_lists: pub toplevel_lists:
CopyHashMap<(ClientId, ExtForeignToplevelListV1Id), Rc<ExtForeignToplevelListV1>>, CopyHashMap<(ClientId, ExtForeignToplevelListV1Id), Rc<ExtForeignToplevelListV1>>,
pub dma_buf_ids: DmaBufIds,
pub drm_feedback_ids: DrmFeedbackIds,
pub direct_scanout_enabled: Cell<bool>,
} }
// impl Drop for State { // impl Drop for State {
@ -347,7 +350,7 @@ impl State {
'handle_new_feedback: { 'handle_new_feedback: {
if let Some(ctx) = &ctx { if let Some(ctx) = &ctx {
let feedback = match DrmFeedback::new(&**ctx) { let feedback = match DrmFeedback::new(&self.drm_feedback_ids, &**ctx) {
Ok(fb) => fb, Ok(fb) => fb,
Err(e) => { Err(e) => {
log::error!("Could not create new DRM feedback: {}", ErrorFmt(e)); log::error!("Could not create new DRM feedback: {}", ErrorFmt(e));
@ -749,11 +752,8 @@ impl State {
output.global.preferred_scale.get(), output.global.preferred_scale.get(),
render_hw_cursor, render_hw_cursor,
); );
for fr in rr.frame_requests.drain(..) { output.perform_screencopies(Some(&**fb), tex, !render_hw_cursor);
fr.send_done(); rr.dispatch_frame_requests();
let _ = fr.client.remove_obj(&*fr);
}
output.perform_screencopies(&**fb, tex, !render_hw_cursor);
} }
pub fn perform_screencopy( pub fn perform_screencopy(

View file

@ -80,7 +80,7 @@ pub async fn output_render_data(state: Rc<State>) {
impl OutputNode { impl OutputNode {
pub fn perform_screencopies( pub fn perform_screencopies(
&self, &self,
fb: &dyn GfxFramebuffer, fb: Option<&dyn GfxFramebuffer>,
tex: &Rc<dyn GfxTexture>, tex: &Rc<dyn GfxTexture>,
render_hardware_cursor: bool, render_hardware_cursor: bool,
) { ) {

View file

@ -5,6 +5,7 @@ use {
ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1,
wl_seat::{collect_kb_foci, collect_kb_foci2, NodeSeatState, SeatId}, wl_seat::{collect_kb_foci, collect_kb_foci2, NodeSeatState, SeatId},
wl_surface::WlSurface,
}, },
rect::Rect, rect::Rect,
state::State, state::State,
@ -161,6 +162,10 @@ pub trait ToplevelNode: Node {
fn tl_last_active_child(self: Rc<Self>) -> Rc<dyn ToplevelNode> { fn tl_last_active_child(self: Rc<Self>) -> Rc<dyn ToplevelNode> {
self.tl_into_dyn() self.tl_into_dyn()
} }
fn tl_scanout_surface(&self) -> Option<Rc<WlSurface>> {
None
}
} }
pub struct FullscreenedData { pub struct FullscreenedData {
@ -356,8 +361,8 @@ impl ToplevelData {
}); });
drop(data); drop(data);
self.is_fullscreen.set(true); self.is_fullscreen.set(true);
ws.set_fullscreen_node(&node);
node.tl_set_parent(ws.clone()); node.tl_set_parent(ws.clone());
ws.set_fullscreen_node(&node);
node.clone().tl_set_workspace(ws); node.clone().tl_set_workspace(ws);
node.clone() node.clone()
.tl_change_extents(&ws.output.get().global.pos.get()); .tl_change_extents(&ws.output.get().global.pos.get());

View file

@ -143,6 +143,11 @@ impl WorkspaceNode {
if plane_was_visible { if plane_was_visible {
self.plane_set_visible(false); self.plane_set_visible(false);
} }
if let Some(surface) = node.tl_scanout_surface() {
if let Some(fb) = self.output.get().global.connector.connector.drm_feedback() {
surface.send_feedback(&fb);
}
}
} }
pub fn remove_fullscreen_node(&self) { pub fn remove_fullscreen_node(&self) {
@ -151,6 +156,11 @@ impl WorkspaceNode {
if self.visible.get() { if self.visible.get() {
self.plane_set_visible(true); self.plane_set_visible(true);
} }
if let Some(surface) = node.tl_scanout_surface() {
if let Some(fb) = surface.client.state.drm_feedback.get() {
surface.send_feedback(&fb);
}
}
} }
} }

View file

@ -5,15 +5,18 @@ use {
uapi::{c::ioctl, OwnedFd, _IOW, _IOWR}, uapi::{c::ioctl, OwnedFd, _IOW, _IOWR},
}; };
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct DmaBufPlane { pub struct DmaBufPlane {
pub offset: u32, pub offset: u32,
pub stride: u32, pub stride: u32,
pub fd: Rc<OwnedFd>, pub fd: Rc<OwnedFd>,
} }
#[derive(Clone)] linear_ids!(DmaBufIds, DmaBufId);
#[derive(Debug, Clone)]
pub struct DmaBuf { pub struct DmaBuf {
pub id: DmaBufId,
pub width: i32, pub width: i32,
pub height: i32, pub height: i32,
pub format: &'static Format, pub format: &'static Format,

View file

@ -32,6 +32,7 @@ use {
use crate::{ use crate::{
backend, backend,
format::Format,
io_uring::{IoUring, IoUringError}, io_uring::{IoUring, IoUringError},
utils::{buf::Buf, errorfmt::ErrorFmt, stack::Stack, syncqueue::SyncQueue, vec_ext::VecExt}, utils::{buf::Buf, errorfmt::ErrorFmt, stack::Stack, syncqueue::SyncQueue, vec_ext::VecExt},
video::{ video::{
@ -306,7 +307,11 @@ impl DrmMaster {
} }
} }
pub fn add_fb(self: &Rc<Self>, dma: &DmaBuf) -> Result<DrmFramebuffer, DrmError> { pub fn add_fb(
self: &Rc<Self>,
dma: &DmaBuf,
format: Option<&Format>,
) -> Result<DrmFramebuffer, DrmError> {
let mut modifier = 0; let mut modifier = 0;
let mut flags = 0; let mut flags = 0;
if dma.modifier != INVALID_MODIFIER { if dma.modifier != INVALID_MODIFIER {
@ -330,7 +335,7 @@ impl DrmMaster {
self.raw(), self.raw(),
dma.width as _, dma.width as _,
dma.height as _, dma.height as _,
dma.format.drm, format.unwrap_or(dma.format).drm,
flags, flags,
handles, handles,
strides, strides,

View file

@ -5,7 +5,7 @@ use {
format::{formats, Format}, format::{formats, Format},
utils::oserror::OsError, utils::oserror::OsError,
video::{ video::{
dmabuf::{DmaBuf, DmaBufPlane, PlaneVec}, dmabuf::{DmaBuf, DmaBufIds, DmaBufPlane, PlaneVec},
drm::{Drm, DrmError}, drm::{Drm, DrmError},
Modifier, INVALID_MODIFIER, Modifier, INVALID_MODIFIER,
}, },
@ -150,8 +150,9 @@ impl GbmBoMap {
} }
} }
unsafe fn export_bo(bo: *mut Bo) -> Result<DmaBuf, GbmError> { unsafe fn export_bo(dmabuf_ids: &DmaBufIds, bo: *mut Bo) -> Result<DmaBuf, GbmError> {
Ok(DmaBuf { Ok(DmaBuf {
id: dmabuf_ids.next(),
width: gbm_bo_get_width(bo) as _, width: gbm_bo_get_width(bo) as _,
height: gbm_bo_get_height(bo) as _, height: gbm_bo_get_height(bo) as _,
modifier: gbm_bo_get_modifier(bo), modifier: gbm_bo_get_modifier(bo),
@ -199,6 +200,7 @@ impl GbmDevice {
pub fn create_bo<'a>( pub fn create_bo<'a>(
&self, &self,
dma_buf_ids: &DmaBufIds,
width: i32, width: i32,
height: i32, height: i32,
format: &Format, format: &Format,
@ -229,7 +231,7 @@ impl GbmDevice {
return Err(GbmError::CreateBo(OsError::default())); return Err(GbmError::CreateBo(OsError::default()));
} }
let bo = BoHolder { bo }; let bo = BoHolder { bo };
let dma = export_bo(bo.bo)?; let dma = export_bo(dma_buf_ids, bo.bo)?;
Ok(GbmBo { bo, dmabuf: dma }) Ok(GbmBo { bo, dmabuf: dma })
} }
} }

View file

@ -20,6 +20,7 @@ use {
oserror::OsError, oserror::OsError,
vec_ext::VecExt, vec_ext::VecExt,
}, },
video::dmabuf::DmaBufIds,
wheel::Wheel, wheel::Wheel,
wire::wl_display, wire::wl_display,
wl_usr::{ wl_usr::{
@ -77,6 +78,7 @@ pub struct UsrCon {
outgoing: Cell<Option<SpawnedFuture<()>>>, outgoing: Cell<Option<SpawnedFuture<()>>>,
pub owner: CloneCell<Option<Rc<dyn UsrConOwner>>>, pub owner: CloneCell<Option<Rc<dyn UsrConOwner>>>,
dead: Cell<bool>, dead: Cell<bool>,
dma_buf_ids: Rc<DmaBufIds>,
} }
pub trait UsrConOwner { pub trait UsrConOwner {
@ -88,6 +90,7 @@ impl UsrCon {
ring: &Rc<IoUring>, ring: &Rc<IoUring>,
wheel: &Rc<Wheel>, wheel: &Rc<Wheel>,
eng: &Rc<AsyncEngine>, eng: &Rc<AsyncEngine>,
dma_buf_ids: &Rc<DmaBufIds>,
path: &str, path: &str,
server_id: u32, server_id: u32,
) -> Result<Rc<Self>, UsrConError> { ) -> Result<Rc<Self>, UsrConError> {
@ -122,6 +125,7 @@ impl UsrCon {
outgoing: Default::default(), outgoing: Default::default(),
owner: Default::default(), owner: Default::default(),
dead: Cell::new(false), dead: Cell::new(false),
dma_buf_ids: dma_buf_ids.clone(),
}); });
slf.objects.set( slf.objects.set(
WL_DISPLAY_ID.into(), WL_DISPLAY_ID.into(),

View file

@ -123,6 +123,7 @@ impl UsrJayScreencast {
_ => return Err(UsrJayScreencastError::UnknownFormat(ev.format)), _ => return Err(UsrJayScreencastError::UnknownFormat(ev.format)),
}; };
self.pending_buffers.borrow_mut().push(DmaBuf { self.pending_buffers.borrow_mut().push(DmaBuf {
id: self.con.dma_buf_ids.next(),
width: ev.width, width: ev.width,
height: ev.height, height: ev.height,
format, format,