1use std::fmt;
14
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub struct FrameSize {
18 pub width: u32,
19 pub height: u32,
20}
21
22impl FrameSize {
23 pub const fn new(width: u32, height: u32) -> Self {
24 Self { width, height }
25 }
26}
27
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
30#[non_exhaustive]
31pub enum PixelFormat {
32 Rgba8Unorm,
33 Rgba8UnormSrgb,
34 Bgra8Unorm,
35 Bgra8UnormSrgb,
36}
37
38#[derive(Clone, Copy, Debug, Eq, PartialEq)]
40pub struct FrameMetadata {
41 pub size: FrameSize,
42 pub format: PixelFormat,
43 pub generation: u64,
44}
45
46#[derive(Clone, Copy, Debug, Eq, PartialEq)]
48#[non_exhaustive]
49pub enum OwnedNativeFrameKind {
50 Dx12SharedTexture,
51 DmaBufImage,
52 MetalTexture,
53 IoSurface,
54}
55
56pub struct OwnedNativeFrame {
62 metadata: FrameMetadata,
63 image: OwnedNativeImage,
64 sync: FrameSync,
65}
66
67impl OwnedNativeFrame {
68 pub fn new(metadata: FrameMetadata, image: OwnedNativeImage, sync: FrameSync) -> Self {
69 Self {
70 metadata,
71 image,
72 sync,
73 }
74 }
75
76 pub fn metadata(&self) -> FrameMetadata {
77 self.metadata
78 }
79 pub fn kind(&self) -> OwnedNativeFrameKind {
80 self.image.kind()
81 }
82 pub fn image(&self) -> &OwnedNativeImage {
83 &self.image
84 }
85 pub fn sync(&self) -> &FrameSync {
86 &self.sync
87 }
88 pub fn into_parts(self) -> (FrameMetadata, OwnedNativeImage, FrameSync) {
89 (self.metadata, self.image, self.sync)
90 }
91}
92
93impl fmt::Debug for OwnedNativeFrame {
94 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
95 formatter
96 .debug_struct("OwnedNativeFrame")
97 .field("metadata", &self.metadata)
98 .field("kind", &self.kind())
99 .field("sync", &self.sync)
100 .finish_non_exhaustive()
101 }
102}
103
104#[non_exhaustive]
107pub enum OwnedNativeImage {
108 #[cfg(target_os = "windows")]
109 Dx12SharedTexture(Dx12SharedTexture),
110 #[cfg(target_os = "linux")]
111 DmaBufImage(DmaBufImage),
112 #[cfg(target_vendor = "apple")]
113 MetalTexture(MetalTexture),
114 #[cfg(target_vendor = "apple")]
115 IoSurface(IoSurface),
116}
117
118impl OwnedNativeImage {
119 pub fn kind(&self) -> OwnedNativeFrameKind {
120 match *self {
123 #[cfg(target_os = "windows")]
124 Self::Dx12SharedTexture(_) => OwnedNativeFrameKind::Dx12SharedTexture,
125 #[cfg(target_os = "linux")]
126 Self::DmaBufImage(_) => OwnedNativeFrameKind::DmaBufImage,
127 #[cfg(target_vendor = "apple")]
128 Self::MetalTexture(_) => OwnedNativeFrameKind::MetalTexture,
129 #[cfg(target_vendor = "apple")]
130 Self::IoSurface(_) => OwnedNativeFrameKind::IoSurface,
131 }
132 }
133}
134
135#[derive(Debug)]
141#[non_exhaustive]
142pub enum FrameSync {
143 None,
144 #[cfg(target_os = "windows")]
145 Dx12Fence {
146 fence: Dx12SharedFence,
147 value: u64,
148 },
149 #[cfg(target_os = "linux")]
150 VulkanSemaphore(std::os::fd::OwnedFd),
151 #[cfg(target_vendor = "apple")]
152 MetalSharedEvent {
153 event: objc2::rc::Retained<objc2::runtime::ProtocolObject<dyn objc2_metal::MTLSharedEvent>>,
154 value: u64,
155 },
156}
157
158#[cfg(target_os = "linux")]
159mod linux {
160 use std::os::fd::{FromRawFd, OwnedFd, RawFd};
161
162 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
163 pub struct DmaBufPlane {
164 pub buffer_index: u32,
165 pub offset: u64,
166 pub stride: u64,
167 }
168
169 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
170 #[non_exhaustive]
171 pub enum DmaBufError {
172 NoBuffers,
173 NoPlanes,
174 InvalidBufferIndex { plane: usize, buffer_index: u32 },
175 ZeroStride { plane: usize },
176 }
177
178 impl std::fmt::Display for DmaBufError {
179 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180 match self {
181 Self::NoBuffers => formatter.write_str("DMABUF image has no owned buffers"),
182 Self::NoPlanes => formatter.write_str("DMABUF image has no planes"),
183 Self::InvalidBufferIndex {
184 plane,
185 buffer_index,
186 } => write!(
187 formatter,
188 "DMABUF plane {plane} references absent buffer {buffer_index}"
189 ),
190 Self::ZeroStride { plane } => {
191 write!(formatter, "DMABUF plane {plane} has a zero stride")
192 }
193 }
194 }
195 }
196
197 impl std::error::Error for DmaBufError {}
198
199 pub struct DmaBufImage {
202 buffers: Vec<OwnedFd>,
203 planes: Vec<DmaBufPlane>,
204 drm_format: u32,
205 drm_modifier: u64,
206 }
207
208 impl DmaBufImage {
209 pub fn new(
210 buffers: Vec<OwnedFd>,
211 planes: Vec<DmaBufPlane>,
212 drm_format: u32,
213 drm_modifier: u64,
214 ) -> Result<Self, DmaBufError> {
215 if buffers.is_empty() {
216 return Err(DmaBufError::NoBuffers);
217 }
218 if planes.is_empty() {
219 return Err(DmaBufError::NoPlanes);
220 }
221 for (plane_index, plane) in planes.iter().enumerate() {
222 if plane.buffer_index as usize >= buffers.len() {
223 return Err(DmaBufError::InvalidBufferIndex {
224 plane: plane_index,
225 buffer_index: plane.buffer_index,
226 });
227 }
228 if plane.stride == 0 {
229 return Err(DmaBufError::ZeroStride { plane: plane_index });
230 }
231 }
232 Ok(Self {
233 buffers,
234 planes,
235 drm_format,
236 drm_modifier,
237 })
238 }
239
240 pub unsafe fn from_raw_owned_parts(
244 buffers: Vec<RawFd>,
245 planes: Vec<DmaBufPlane>,
246 drm_format: u32,
247 drm_modifier: u64,
248 ) -> Result<Self, DmaBufError> {
249 let buffers = buffers
250 .into_iter()
251 .map(|fd| unsafe { OwnedFd::from_raw_fd(fd) })
252 .collect();
253 Self::new(buffers, planes, drm_format, drm_modifier)
254 }
255
256 pub fn buffers(&self) -> &[OwnedFd] {
257 &self.buffers
258 }
259 pub fn planes(&self) -> &[DmaBufPlane] {
260 &self.planes
261 }
262 pub fn drm_format(&self) -> u32 {
263 self.drm_format
264 }
265 pub fn drm_modifier(&self) -> u64 {
266 self.drm_modifier
267 }
268 pub fn into_parts(self) -> (Vec<OwnedFd>, Vec<DmaBufPlane>, u32, u64) {
269 (
270 self.buffers,
271 self.planes,
272 self.drm_format,
273 self.drm_modifier,
274 )
275 }
276 pub fn into_buffers(self) -> Vec<OwnedFd> {
277 self.buffers
278 }
279 }
280}
281
282#[cfg(target_os = "linux")]
283pub use linux::{DmaBufError, DmaBufImage, DmaBufPlane};
284
285#[cfg(target_os = "windows")]
286mod windows_frame {
287 use std::{
288 os::windows::io::{AsHandle, BorrowedHandle, OwnedHandle},
289 sync::{
290 Arc,
291 atomic::{AtomicU64, Ordering},
292 },
293 };
294
295 static NEXT_ALLOCATION_KEY: AtomicU64 = AtomicU64::new(1);
296 static NEXT_FENCE_KEY: AtomicU64 = AtomicU64::new(1);
297
298 #[derive(Clone, Debug)]
299 pub struct Dx12SharedResource {
300 handle: Arc<OwnedHandle>,
301 allocation_key: u64,
302 }
303
304 impl Dx12SharedResource {
305 pub fn from_owned_handle(handle: OwnedHandle) -> Self {
306 Self {
307 handle: Arc::new(handle),
308 allocation_key: NEXT_ALLOCATION_KEY.fetch_add(1, Ordering::Relaxed),
309 }
310 }
311 pub fn allocation_key(&self) -> u64 {
312 self.allocation_key
313 }
314 pub fn as_handle(&self) -> BorrowedHandle<'_> {
315 self.handle.as_handle()
316 }
317 }
318
319 #[derive(Debug)]
320 pub struct Dx12SharedTexture {
321 resource: Dx12SharedResource,
322 }
323
324 impl Dx12SharedTexture {
325 pub fn new(resource: Dx12SharedResource) -> Self {
326 Self { resource }
327 }
328 pub fn allocation_key(&self) -> u64 {
329 self.resource.allocation_key()
330 }
331 pub fn resource(&self) -> &Dx12SharedResource {
332 &self.resource
333 }
334 pub fn into_resource(self) -> Dx12SharedResource {
335 self.resource
336 }
337 }
338
339 #[derive(Clone, Debug)]
340 pub struct Dx12SharedFence {
341 handle: Arc<OwnedHandle>,
342 fence_key: u64,
343 }
344
345 impl Dx12SharedFence {
346 pub fn from_owned_handle(handle: OwnedHandle) -> Self {
347 Self {
348 handle: Arc::new(handle),
349 fence_key: NEXT_FENCE_KEY.fetch_add(1, Ordering::Relaxed),
350 }
351 }
352 pub fn fence_key(&self) -> u64 {
353 self.fence_key
354 }
355 pub fn as_handle(&self) -> BorrowedHandle<'_> {
356 self.handle.as_handle()
357 }
358 }
359}
360
361#[cfg(target_os = "windows")]
362pub use windows_frame::{Dx12SharedFence, Dx12SharedResource, Dx12SharedTexture};
363
364#[cfg(target_vendor = "apple")]
365mod apple {
366 use objc2::{rc::Retained, runtime::ProtocolObject};
367 use objc2_core_foundation::CFRetained;
368 use objc2_io_surface::IOSurfaceRef;
369 use objc2_metal::MTLTexture;
370
371 pub struct MetalTexture {
372 texture: Retained<ProtocolObject<dyn MTLTexture>>,
373 }
374 impl MetalTexture {
375 pub fn new(texture: Retained<ProtocolObject<dyn MTLTexture>>) -> Self {
376 Self { texture }
377 }
378 pub fn texture(&self) -> &ProtocolObject<dyn MTLTexture> {
379 &self.texture
380 }
381 pub fn into_retained(self) -> Retained<ProtocolObject<dyn MTLTexture>> {
382 self.texture
383 }
384 }
385
386 pub struct IoSurface {
387 surface: CFRetained<IOSurfaceRef>,
388 }
389 impl IoSurface {
390 pub fn new(surface: CFRetained<IOSurfaceRef>) -> Self {
391 Self { surface }
392 }
393 pub fn surface(&self) -> &IOSurfaceRef {
394 &self.surface
395 }
396 pub fn into_retained(self) -> CFRetained<IOSurfaceRef> {
397 self.surface
398 }
399 }
400}
401
402#[cfg(target_vendor = "apple")]
403pub use apple::{IoSurface, MetalTexture};
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408 use static_assertions::assert_not_impl_any;
409
410 assert_not_impl_any!(OwnedNativeFrame: Copy, Clone);
411 assert_not_impl_any!(OwnedNativeImage: Copy, Clone);
412 assert_not_impl_any!(FrameSync: Copy, Clone);
413
414 #[test]
415 fn metadata_is_resource_free_and_copyable() {
416 let metadata = FrameMetadata {
417 size: FrameSize::new(960, 640),
418 format: PixelFormat::Bgra8Unorm,
419 generation: 42,
420 };
421 assert_eq!(metadata, metadata);
422 assert_eq!(metadata.size.width, 960);
423 }
424
425 #[cfg(target_os = "windows")]
426 #[test]
427 fn dx12_custody_clones_one_allocation_identity() {
428 use std::os::windows::io::{FromRawHandle, OwnedHandle};
429
430 let raw = unsafe {
431 windows::Win32::System::Threading::CreateEventW(None, true, false, None)
432 .expect("create test handle")
433 };
434 let owned = unsafe { OwnedHandle::from_raw_handle(raw.0) };
435 let resource = Dx12SharedResource::from_owned_handle(owned);
436 let clone = resource.clone();
437 assert_eq!(resource.allocation_key(), clone.allocation_key());
438
439 let texture = Dx12SharedTexture::new(clone);
440 assert_eq!(texture.allocation_key(), resource.allocation_key());
441 }
442
443 #[cfg(target_os = "linux")]
444 #[test]
445 fn invalid_dmabuf_layout_closes_taken_descriptor() {
446 use std::{io::Write, os::fd::IntoRawFd, os::unix::net::UnixStream};
447
448 let (taken, mut peer) = UnixStream::pair().expect("create descriptor pair");
449 let raw = taken.into_raw_fd();
450 let result = unsafe {
451 DmaBufImage::from_raw_owned_parts(
452 vec![raw],
453 vec![DmaBufPlane {
454 buffer_index: 1,
455 offset: 0,
456 stride: 4,
457 }],
458 0,
459 0,
460 )
461 };
462 let error = match result {
463 Err(error) => error,
464 Ok(_) => panic!("invalid table must fail"),
465 };
466 assert!(matches!(error, DmaBufError::InvalidBufferIndex { .. }));
467 assert!(peer.write_all(b"closed").is_err());
468 }
469}