| 1 | use crate::{error::GlobalError, globals::GlobalData, registry::GlobalProxy}; |
| 2 | use memmap2::{Mmap, MmapOptions}; |
| 3 | use std::{fmt, mem, os::unix::io::BorrowedFd, slice, sync::Mutex}; |
| 4 | use wayland_client::{ |
| 5 | globals::GlobalList, |
| 6 | protocol::{wl_buffer, wl_surface}, |
| 7 | Connection, Dispatch, Proxy, QueueHandle, WEnum, |
| 8 | }; |
| 9 | use wayland_protocols::wp::linux_dmabuf::zv1::client::{ |
| 10 | zwp_linux_buffer_params_v1, |
| 11 | zwp_linux_dmabuf_feedback_v1::{self, TrancheFlags}, |
| 12 | zwp_linux_dmabuf_v1, |
| 13 | }; |
| 14 | |
| 15 | // Workaround until `libc` updates to FreeBSD 12 ABI |
| 16 | #[cfg (target_os = "freebsd" )] |
| 17 | type dev_t = u64; |
| 18 | #[cfg (not(target_os = "freebsd" ))] |
| 19 | use libc::dev_t; |
| 20 | |
| 21 | /// A preference tranche of dmabuf formats |
| 22 | #[derive (Debug)] |
| 23 | pub struct DmabufFeedbackTranche { |
| 24 | /// `dev_t` value for preferred target device. May be scan-out or |
| 25 | /// renderer device. |
| 26 | pub device: dev_t, |
| 27 | /// Flags for tranche |
| 28 | pub flags: WEnum<TrancheFlags>, |
| 29 | /// Indices of formats in the format table |
| 30 | pub formats: Vec<u16>, |
| 31 | } |
| 32 | |
| 33 | impl Default for DmabufFeedbackTranche { |
| 34 | fn default() -> DmabufFeedbackTranche { |
| 35 | DmabufFeedbackTranche { |
| 36 | device: 0, |
| 37 | flags: WEnum::Value(TrancheFlags::empty()), |
| 38 | formats: Vec::new(), |
| 39 | } |
| 40 | } |
| 41 | } |
| 42 | |
| 43 | /// A single dmabuf format/modifier pair |
| 44 | // Must have correct representation to be able to mmap format table |
| 45 | #[repr (C)] |
| 46 | pub struct DmabufFormat { |
| 47 | /// Fourcc format |
| 48 | pub format: u32, |
| 49 | _padding: u32, |
| 50 | /// Modifier, or `DRM_FORMAT_MOD_INVALID` for implict modifier |
| 51 | pub modifier: u64, |
| 52 | } |
| 53 | |
| 54 | impl fmt::Debug for DmabufFormat { |
| 55 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 56 | f&mut DebugStruct<'_, '_>.debug_struct("DmabufFormat" ) |
| 57 | .field("format" , &self.format) |
| 58 | .field(name:"modifier" , &self.modifier) |
| 59 | .finish() |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | /// Description of supported and preferred dmabuf formats |
| 64 | #[derive (Default)] |
| 65 | pub struct DmabufFeedback { |
| 66 | format_table: Option<(Mmap, usize)>, |
| 67 | main_device: dev_t, |
| 68 | tranches: Vec<DmabufFeedbackTranche>, |
| 69 | } |
| 70 | |
| 71 | impl fmt::Debug for DmabufFeedback { |
| 72 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 73 | f&mut DebugStruct<'_, '_>.debug_struct("DmabufFeedback" ) |
| 74 | .field("format_table" , &self.format_table()) |
| 75 | .field("main_device" , &self.main_device) |
| 76 | .field(name:"tranches" , &self.tranches) |
| 77 | .finish() |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | impl DmabufFeedback { |
| 82 | /// Format/modifier pairs |
| 83 | pub fn format_table(&self) -> &[DmabufFormat] { |
| 84 | self.format_table.as_ref().map_or(&[], |(mmap: &Mmap, len: &usize)| unsafe { |
| 85 | slice::from_raw_parts(data:mmap.as_ptr() as *const DmabufFormat, *len) |
| 86 | }) |
| 87 | } |
| 88 | |
| 89 | /// `dev_t` value for main device. Buffers must be importable from main device. |
| 90 | pub fn main_device(&self) -> dev_t { |
| 91 | self.main_device |
| 92 | } |
| 93 | |
| 94 | /// Tranches in descending order of preference |
| 95 | pub fn tranches(&self) -> &[DmabufFeedbackTranche] { |
| 96 | &self.tranches |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | #[doc (hidden)] |
| 101 | #[derive (Debug, Default)] |
| 102 | pub struct DmabufFeedbackData { |
| 103 | pending: Mutex<DmabufFeedback>, |
| 104 | pending_tranche: Mutex<DmabufFeedbackTranche>, |
| 105 | } |
| 106 | |
| 107 | #[doc (hidden)] |
| 108 | #[derive (Debug)] |
| 109 | pub struct DmaBufferData; |
| 110 | |
| 111 | /// A handler for [`zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1`] |
| 112 | #[derive (Debug)] |
| 113 | pub struct DmabufState { |
| 114 | zwp_linux_dmabuf: GlobalProxy<zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1>, |
| 115 | modifiers: Vec<DmabufFormat>, |
| 116 | } |
| 117 | |
| 118 | impl DmabufState { |
| 119 | /// Bind `zwp_linux_dmabuf_v1` global version 3 or 4, if it exists. |
| 120 | /// |
| 121 | /// This does not fail if the global does not exist. |
| 122 | pub fn new<D>(globals: &GlobalList, qh: &QueueHandle<D>) -> Self |
| 123 | where |
| 124 | D: Dispatch<zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, GlobalData> + 'static, |
| 125 | { |
| 126 | // Mesa (at least the latest version) also requires version 3 or 4 |
| 127 | let zwp_linux_dmabuf = GlobalProxy::from(globals.bind(qh, 3..=5, GlobalData)); |
| 128 | Self { zwp_linux_dmabuf, modifiers: Vec::new() } |
| 129 | } |
| 130 | |
| 131 | /// Only populated in version `<4` |
| 132 | /// |
| 133 | /// On version `4`, use [`DmabufState::get_surface_feedback`]. |
| 134 | pub fn modifiers(&self) -> &[DmabufFormat] { |
| 135 | &self.modifiers |
| 136 | } |
| 137 | |
| 138 | /// Supported protocol version, if any |
| 139 | pub fn version(&self) -> Option<u32> { |
| 140 | Some(self.zwp_linux_dmabuf.get().ok()?.version()) |
| 141 | } |
| 142 | |
| 143 | /// Create a params object for constructing a buffer |
| 144 | /// |
| 145 | /// Errors if `zwp_linux_dmabuf_v1` does not exist or has unsupported |
| 146 | /// version. An application can then fallback to using `shm` buffers. |
| 147 | pub fn create_params<D>(&self, qh: &QueueHandle<D>) -> Result<DmabufParams, GlobalError> |
| 148 | where |
| 149 | D: Dispatch<zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, GlobalData> + 'static, |
| 150 | { |
| 151 | let zwp_linux_dmabuf = self.zwp_linux_dmabuf.get()?; |
| 152 | let params = zwp_linux_dmabuf.create_params(qh, GlobalData); |
| 153 | Ok(DmabufParams { params }) |
| 154 | } |
| 155 | |
| 156 | /// Get default dmabuf feedback. Requires version `4`. |
| 157 | /// |
| 158 | /// On version `3`, use [`DmabufState::modifiers`]. |
| 159 | pub fn get_default_feedback<D>( |
| 160 | &self, |
| 161 | qh: &QueueHandle<D>, |
| 162 | ) -> Result<zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, GlobalError> |
| 163 | where |
| 164 | D: Dispatch<zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, DmabufFeedbackData> |
| 165 | + 'static, |
| 166 | { |
| 167 | let zwp_linux_dmabuf = self.zwp_linux_dmabuf.with_min_version(4)?; |
| 168 | Ok(zwp_linux_dmabuf.get_default_feedback(qh, DmabufFeedbackData::default())) |
| 169 | } |
| 170 | |
| 171 | /// Get default dmabuf feedback for given surface. Requires version `4`. |
| 172 | /// |
| 173 | /// On version `3`, use [`DmabufState::modifiers`]. |
| 174 | pub fn get_surface_feedback<D>( |
| 175 | &self, |
| 176 | surface: &wl_surface::WlSurface, |
| 177 | qh: &QueueHandle<D>, |
| 178 | ) -> Result<zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, GlobalError> |
| 179 | where |
| 180 | D: Dispatch<zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, DmabufFeedbackData> |
| 181 | + 'static, |
| 182 | { |
| 183 | let zwp_linux_dmabuf = self.zwp_linux_dmabuf.with_min_version(4)?; |
| 184 | Ok(zwp_linux_dmabuf.get_surface_feedback(surface, qh, DmabufFeedbackData::default())) |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | pub trait DmabufHandler: Sized { |
| 189 | fn dmabuf_state(&mut self) -> &mut DmabufState; |
| 190 | |
| 191 | /// Server has sent dmabuf feedback information. This may be received multiple |
| 192 | /// times by a `ZwpLinuxDmabufFeedbackV1` object. |
| 193 | fn dmabuf_feedback( |
| 194 | &mut self, |
| 195 | conn: &Connection, |
| 196 | qh: &QueueHandle<Self>, |
| 197 | proxy: &zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, |
| 198 | feedback: DmabufFeedback, |
| 199 | ); |
| 200 | |
| 201 | /// `wl_buffer` associated with `params` has been created successfully. |
| 202 | fn created( |
| 203 | &mut self, |
| 204 | conn: &Connection, |
| 205 | qh: &QueueHandle<Self>, |
| 206 | params: &zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, |
| 207 | buffer: wl_buffer::WlBuffer, |
| 208 | ); |
| 209 | |
| 210 | /// Failed to create `wl_buffer` for `params`. |
| 211 | fn failed( |
| 212 | &mut self, |
| 213 | conn: &Connection, |
| 214 | qh: &QueueHandle<Self>, |
| 215 | params: &zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, |
| 216 | ); |
| 217 | |
| 218 | /// Compositor has released a `wl_buffer` created through [`DmabufParams`]. |
| 219 | fn released(&mut self, conn: &Connection, qh: &QueueHandle<Self>, buffer: &wl_buffer::WlBuffer); |
| 220 | } |
| 221 | |
| 222 | /// Builder for a dmabuf backed buffer |
| 223 | #[derive (Debug)] |
| 224 | pub struct DmabufParams { |
| 225 | params: zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, |
| 226 | } |
| 227 | |
| 228 | impl DmabufParams { |
| 229 | /// Add a plane |
| 230 | /// |
| 231 | /// In version `4`, it is a protocol error if `format`/`modifier` pair wasn't |
| 232 | /// advertised as supported. |
| 233 | /// |
| 234 | /// `modifier` should be the same for all planes. It is a protocol error in version `5` if |
| 235 | /// they differ. |
| 236 | pub fn add(&self, fd: BorrowedFd<'_>, plane_idx: u32, offset: u32, stride: u32, modifier: u64) { |
| 237 | let modifier_hi = (modifier >> 32) as u32; |
| 238 | let modifier_lo = (modifier & 0xffffffff) as u32; |
| 239 | self.params.add(fd, plane_idx, offset, stride, modifier_hi, modifier_lo); |
| 240 | } |
| 241 | |
| 242 | /// Create buffer. |
| 243 | /// |
| 244 | /// [`DmabufHandler::created`] or [`DmabufHandler::failed`] will be invoked when the |
| 245 | /// operation succeeds or fails. |
| 246 | pub fn create( |
| 247 | self, |
| 248 | width: i32, |
| 249 | height: i32, |
| 250 | format: u32, |
| 251 | flags: zwp_linux_buffer_params_v1::Flags, |
| 252 | ) -> zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1 { |
| 253 | self.params.create(width, height, format, flags); |
| 254 | self.params |
| 255 | } |
| 256 | |
| 257 | /// Create buffer immediately. |
| 258 | /// |
| 259 | /// On failure buffer is invalid, and server may raise protocol error or |
| 260 | /// send [`DmabufHandler::failed`]. |
| 261 | pub fn create_immed<D>( |
| 262 | self, |
| 263 | width: i32, |
| 264 | height: i32, |
| 265 | format: u32, |
| 266 | flags: zwp_linux_buffer_params_v1::Flags, |
| 267 | qh: &QueueHandle<D>, |
| 268 | ) -> (wl_buffer::WlBuffer, zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1) |
| 269 | where |
| 270 | D: Dispatch<wl_buffer::WlBuffer, DmaBufferData> + 'static, |
| 271 | { |
| 272 | let buffer = self.params.create_immed(width, height, format, flags, qh, DmaBufferData); |
| 273 | (buffer, self.params) |
| 274 | } |
| 275 | } |
| 276 | |
| 277 | impl<D> Dispatch<zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, GlobalData, D> for DmabufState |
| 278 | where |
| 279 | D: Dispatch<zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, GlobalData> + DmabufHandler, |
| 280 | { |
| 281 | fn event( |
| 282 | state: &mut D, |
| 283 | proxy: &zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, |
| 284 | event: zwp_linux_dmabuf_v1::Event, |
| 285 | _: &GlobalData, |
| 286 | _: &Connection, |
| 287 | _: &QueueHandle<D>, |
| 288 | ) { |
| 289 | match event { |
| 290 | zwp_linux_dmabuf_v1::Event::Format { format: _ } => { |
| 291 | // Formats are duplicated by modifier events since version 3. |
| 292 | // Ignore this event, like Mesa does. |
| 293 | } |
| 294 | zwp_linux_dmabuf_v1::Event::Modifier { format, modifier_hi, modifier_lo } => { |
| 295 | if proxy.version() < 4 { |
| 296 | let modifier = (u64::from(modifier_hi) << 32) | u64::from(modifier_lo); |
| 297 | state.dmabuf_state().modifiers.push(DmabufFormat { |
| 298 | format, |
| 299 | _padding: 0, |
| 300 | modifier, |
| 301 | }); |
| 302 | } |
| 303 | } |
| 304 | _ => unreachable!(), |
| 305 | } |
| 306 | } |
| 307 | } |
| 308 | |
| 309 | impl<D> Dispatch<zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, DmabufFeedbackData, D> |
| 310 | for DmabufState |
| 311 | where |
| 312 | D: Dispatch<zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, DmabufFeedbackData> |
| 313 | + DmabufHandler, |
| 314 | { |
| 315 | fn event( |
| 316 | state: &mut D, |
| 317 | proxy: &zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, |
| 318 | event: zwp_linux_dmabuf_feedback_v1::Event, |
| 319 | data: &DmabufFeedbackData, |
| 320 | conn: &Connection, |
| 321 | qh: &QueueHandle<D>, |
| 322 | ) { |
| 323 | match event { |
| 324 | zwp_linux_dmabuf_feedback_v1::Event::Done => { |
| 325 | let feedback = mem::take(&mut *data.pending.lock().unwrap()); |
| 326 | state.dmabuf_feedback(conn, qh, proxy, feedback); |
| 327 | } |
| 328 | zwp_linux_dmabuf_feedback_v1::Event::FormatTable { fd, size } => { |
| 329 | let size = size as usize; |
| 330 | let mmap = unsafe { |
| 331 | MmapOptions::new().map_copy_read_only(&fd).expect("Failed to map format table" ) |
| 332 | }; |
| 333 | assert!(mmap.len() >= size); |
| 334 | let entry_size = mem::size_of::<DmabufFormat>(); |
| 335 | assert!((size % entry_size) == 0); |
| 336 | let len = size / entry_size; |
| 337 | data.pending.lock().unwrap().format_table = Some((mmap, len)); |
| 338 | } |
| 339 | zwp_linux_dmabuf_feedback_v1::Event::MainDevice { device } => { |
| 340 | let device = dev_t::from_ne_bytes(device.try_into().unwrap()); |
| 341 | data.pending.lock().unwrap().main_device = device; |
| 342 | } |
| 343 | zwp_linux_dmabuf_feedback_v1::Event::TrancheDone => { |
| 344 | let tranche = mem::take(&mut *data.pending_tranche.lock().unwrap()); |
| 345 | data.pending.lock().unwrap().tranches.push(tranche); |
| 346 | } |
| 347 | zwp_linux_dmabuf_feedback_v1::Event::TrancheTargetDevice { device } => { |
| 348 | let device = dev_t::from_ne_bytes(device.try_into().unwrap()); |
| 349 | data.pending_tranche.lock().unwrap().device = device; |
| 350 | } |
| 351 | zwp_linux_dmabuf_feedback_v1::Event::TrancheFormats { indices } => { |
| 352 | assert!((indices.len() % 2) == 0); |
| 353 | let indices = |
| 354 | indices.chunks(2).map(|i| u16::from_ne_bytes([i[0], i[1]])).collect::<Vec<_>>(); |
| 355 | data.pending_tranche.lock().unwrap().formats = indices; |
| 356 | } |
| 357 | zwp_linux_dmabuf_feedback_v1::Event::TrancheFlags { flags } => { |
| 358 | data.pending_tranche.lock().unwrap().flags = flags; |
| 359 | } |
| 360 | _ => unreachable!(), |
| 361 | } |
| 362 | } |
| 363 | } |
| 364 | |
| 365 | impl<D> Dispatch<zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, GlobalData, D> for DmabufState |
| 366 | where |
| 367 | D: Dispatch<zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, GlobalData> |
| 368 | + Dispatch<wl_buffer::WlBuffer, DmaBufferData> |
| 369 | + DmabufHandler |
| 370 | + 'static, |
| 371 | { |
| 372 | fn event( |
| 373 | state: &mut D, |
| 374 | proxy: &zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, |
| 375 | event: zwp_linux_buffer_params_v1::Event, |
| 376 | _: &GlobalData, |
| 377 | conn: &Connection, |
| 378 | qh: &QueueHandle<D>, |
| 379 | ) { |
| 380 | match event { |
| 381 | zwp_linux_buffer_params_v1::Event::Created { buffer } => { |
| 382 | state.created(conn, qh, params:proxy, buffer); |
| 383 | } |
| 384 | zwp_linux_buffer_params_v1::Event::Failed => { |
| 385 | state.failed(conn, qh, params:proxy); |
| 386 | } |
| 387 | _ => unreachable!(), |
| 388 | } |
| 389 | } |
| 390 | |
| 391 | wayland_client::event_created_child!(D, zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, [ |
| 392 | zwp_linux_buffer_params_v1::EVT_CREATED_OPCODE => (wl_buffer::WlBuffer, DmaBufferData) |
| 393 | ]); |
| 394 | } |
| 395 | |
| 396 | impl<D> Dispatch<wl_buffer::WlBuffer, DmaBufferData, D> for DmabufState |
| 397 | where |
| 398 | D: Dispatch<wl_buffer::WlBuffer, DmaBufferData> + DmabufHandler, |
| 399 | { |
| 400 | fn event( |
| 401 | state: &mut D, |
| 402 | proxy: &wl_buffer::WlBuffer, |
| 403 | event: wl_buffer::Event, |
| 404 | _: &DmaBufferData, |
| 405 | conn: &Connection, |
| 406 | qh: &QueueHandle<D>, |
| 407 | ) { |
| 408 | match event { |
| 409 | wl_buffer::Event::Release => state.released(conn, qh, buffer:proxy), |
| 410 | _ => unreachable!(), |
| 411 | } |
| 412 | } |
| 413 | } |
| 414 | |
| 415 | #[macro_export ] |
| 416 | macro_rules! delegate_dmabuf { |
| 417 | ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { |
| 418 | $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: |
| 419 | [ |
| 420 | $crate::reexports::protocols::wp::linux_dmabuf::zv1::client::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1: $crate::globals::GlobalData |
| 421 | ] => $crate::dmabuf::DmabufState |
| 422 | ); |
| 423 | $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: |
| 424 | [ |
| 425 | $crate::reexports::protocols::wp::linux_dmabuf::zv1::client::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1: $crate::globals::GlobalData |
| 426 | ] => $crate::dmabuf::DmabufState |
| 427 | ); |
| 428 | $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: |
| 429 | [ |
| 430 | $crate::reexports::protocols::wp::linux_dmabuf::zv1::client::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1: $crate::dmabuf::DmabufFeedbackData |
| 431 | ] => $crate::dmabuf::DmabufState |
| 432 | ); |
| 433 | $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: |
| 434 | [ |
| 435 | $crate::reexports::client::protocol::wl_buffer::WlBuffer: $crate::dmabuf::DmaBufferData |
| 436 | ] => $crate::dmabuf::DmabufState |
| 437 | ); |
| 438 | }; |
| 439 | } |
| 440 | |