apple_cf/iosurface/mod.rs
1//! `IOSurface` - Hardware-accelerated surface
2//!
3//! Provides safe Rust bindings for Apple's `IOSurface` framework.
4//! `IOSurface` objects are framebuffers suitable for sharing across process boundaries
5//! and are the primary mechanism for zero-copy frame delivery in `ScreenCaptureKit`.
6//!
7//! # Safety
8//!
9//! Base address access is only available through lock guards to ensure proper
10//! memory synchronization. The surface must be locked before accessing pixel data.
11
12use super::ffi;
13use std::ffi::c_void;
14use std::fmt;
15use std::io;
16
17/// Lock options for `IOSurface`
18///
19/// This is a bitmask type that supports combining multiple options using the `|` operator.
20///
21/// # Examples
22///
23/// ```
24/// use apple_cf::iosurface::IOSurfaceLockOptions;
25///
26/// // Single option
27/// let read_only = IOSurfaceLockOptions::READ_ONLY;
28///
29/// // Combined options
30/// let combined = IOSurfaceLockOptions::READ_ONLY | IOSurfaceLockOptions::AVOID_SYNC;
31/// assert!(combined.contains(IOSurfaceLockOptions::READ_ONLY));
32/// assert!(combined.contains(IOSurfaceLockOptions::AVOID_SYNC));
33/// ```
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
35pub struct IOSurfaceLockOptions(u32);
36
37impl IOSurfaceLockOptions {
38 /// No special options (read-write lock with sync)
39 pub const NONE: Self = Self(0);
40
41 /// Read-only lock - use when you only need to read data.
42 /// This allows the system to keep caches valid.
43 pub const READ_ONLY: Self = Self(0x0000_0001);
44
45 /// Avoid synchronization - use with caution.
46 /// Skip waiting for pending operations before completing the lock.
47 pub const AVOID_SYNC: Self = Self(0x0000_0002);
48
49 /// Create from a raw u32 value
50 #[must_use]
51 pub const fn from_bits(bits: u32) -> Self {
52 Self(bits)
53 }
54
55 /// Convert to u32 for FFI
56 #[must_use]
57 pub const fn as_u32(self) -> u32 {
58 self.0
59 }
60
61 /// Check if these options contain the given option
62 #[must_use]
63 pub const fn contains(self, other: Self) -> bool {
64 (self.0 & other.0) == other.0
65 }
66
67 /// Check if this is a read-only lock
68 #[must_use]
69 pub const fn is_read_only(self) -> bool {
70 self.contains(Self::READ_ONLY)
71 }
72
73 /// Check if this avoids synchronization
74 #[must_use]
75 pub const fn is_avoid_sync(self) -> bool {
76 self.contains(Self::AVOID_SYNC)
77 }
78
79 /// Check if no options are set
80 #[must_use]
81 pub const fn is_empty(self) -> bool {
82 self.0 == 0
83 }
84}
85
86impl std::ops::BitOr for IOSurfaceLockOptions {
87 type Output = Self;
88
89 fn bitor(self, rhs: Self) -> Self::Output {
90 Self(self.0 | rhs.0)
91 }
92}
93
94impl std::ops::BitOrAssign for IOSurfaceLockOptions {
95 fn bitor_assign(&mut self, rhs: Self) {
96 self.0 |= rhs.0;
97 }
98}
99
100impl std::ops::BitAnd for IOSurfaceLockOptions {
101 type Output = Self;
102
103 fn bitand(self, rhs: Self) -> Self::Output {
104 Self(self.0 & rhs.0)
105 }
106}
107
108impl std::ops::BitAndAssign for IOSurfaceLockOptions {
109 fn bitand_assign(&mut self, rhs: Self) {
110 self.0 &= rhs.0;
111 }
112}
113
114impl From<IOSurfaceLockOptions> for u32 {
115 fn from(options: IOSurfaceLockOptions) -> Self {
116 options.0
117 }
118}
119
120/// Properties for a single plane in a multi-planar `IOSurface`
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub struct PlaneProperties {
123 /// Width of this plane in pixels
124 pub width: usize,
125 /// Height of this plane in pixels
126 pub height: usize,
127 /// Bytes per row for this plane
128 pub bytes_per_row: usize,
129 /// Bytes per element for this plane
130 pub bytes_per_element: usize,
131 /// Offset from the start of the surface allocation
132 pub offset: usize,
133 /// Size of this plane in bytes
134 pub size: usize,
135}
136
137/// Hardware-accelerated surface for efficient frame delivery
138///
139/// `IOSurface` is Apple's cross-process framebuffer type. It provides:
140/// - Zero-copy sharing between processes
141/// - Direct GPU texture creation via Metal
142/// - Multi-planar format support (YCbCr, etc.)
143///
144/// # Memory Access Safety
145///
146/// The surface must be locked before accessing pixel data. Use [`lock`](Self::lock)
147/// to get a RAII guard that ensures proper locking/unlocking.
148///
149/// # Examples
150///
151/// ```no_run
152/// use apple_cf::iosurface::{IOSurface, IOSurfaceLockOptions};
153///
154/// fn access_surface(surface: &IOSurface) -> Result<(), i32> {
155/// // Lock for read-only access
156/// let guard = surface.lock(IOSurfaceLockOptions::READ_ONLY)?;
157///
158/// // Access pixel data through the guard
159/// let data = guard.as_slice();
160/// println!("Surface has {} bytes", data.len());
161///
162/// // Surface automatically unlocked when guard drops
163/// Ok(())
164/// }
165/// ```
166pub struct IOSurface(*mut c_void);
167
168impl PartialEq for IOSurface {
169 fn eq(&self, other: &Self) -> bool {
170 self.0 == other.0
171 }
172}
173
174impl Eq for IOSurface {}
175
176impl std::hash::Hash for IOSurface {
177 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
178 unsafe {
179 let hash_value = ffi::io_surface_hash(self.0);
180 hash_value.hash(state);
181 }
182 }
183}
184
185impl IOSurface {
186 /// Create a new `IOSurface` with the given dimensions and pixel format
187 ///
188 /// # Arguments
189 ///
190 /// * `width` - Width in pixels
191 /// * `height` - Height in pixels
192 /// * `pixel_format` - Pixel format as a `FourCC` code (e.g., 0x42475241 for 'BGRA')
193 /// * `bytes_per_element` - Bytes per pixel (e.g., 4 for BGRA)
194 ///
195 /// # Returns
196 ///
197 /// `Some(IOSurface)` if creation succeeded, `None` otherwise.
198 ///
199 /// # Examples
200 ///
201 /// ```
202 /// use apple_cf::iosurface::IOSurface;
203 ///
204 /// // Create a 100x100 BGRA IOSurface
205 /// let surface = IOSurface::create(100, 100, 0x42475241, 4)
206 /// .expect("Failed to create IOSurface");
207 /// assert_eq!(surface.width(), 100);
208 /// assert_eq!(surface.height(), 100);
209 /// ```
210 #[must_use]
211 pub fn create(
212 width: usize,
213 height: usize,
214 pixel_format: u32,
215 bytes_per_element: usize,
216 ) -> Option<Self> {
217 let mut ptr: *mut c_void = std::ptr::null_mut();
218 let status = unsafe {
219 crate::ffi::io_surface_create(width, height, pixel_format, bytes_per_element, &mut ptr)
220 };
221 if status == 0 && !ptr.is_null() {
222 Some(Self(ptr))
223 } else {
224 None
225 }
226 }
227
228 /// Create an `IOSurface` with full properties including multi-planar support
229 ///
230 /// This is the general API for creating `IOSurface`s with any pixel format,
231 /// including multi-planar formats like YCbCr 4:2:0.
232 ///
233 /// # Arguments
234 ///
235 /// * `width` - Width in pixels
236 /// * `height` - Height in pixels
237 /// * `pixel_format` - Pixel format as `FourCC` (e.g., 0x42475241 for BGRA)
238 /// * `bytes_per_element` - Bytes per pixel element
239 /// * `bytes_per_row` - Bytes per row (should be 16-byte aligned for Metal)
240 /// * `alloc_size` - Total allocation size in bytes
241 /// * `planes` - Optional slice of plane info for multi-planar formats
242 ///
243 /// # Examples
244 ///
245 /// ```
246 /// use apple_cf::iosurface::PlaneProperties;
247 /// use apple_cf::iosurface::IOSurface;
248 ///
249 /// // Create a YCbCr 420v biplanar surface
250 /// let width = 1920usize;
251 /// let height = 1080usize;
252 /// let plane0_bpr = (width + 15) & !15; // 16-byte aligned
253 /// let plane1_bpr = (width + 15) & !15;
254 /// let plane0_size = plane0_bpr * height;
255 /// let plane1_size = plane1_bpr * (height / 2);
256 ///
257 /// let planes = [
258 /// PlaneProperties {
259 /// width,
260 /// height,
261 /// bytes_per_row: plane0_bpr,
262 /// bytes_per_element: 1,
263 /// offset: 0,
264 /// size: plane0_size,
265 /// },
266 /// PlaneProperties {
267 /// width: width / 2,
268 /// height: height / 2,
269 /// bytes_per_row: plane1_bpr,
270 /// bytes_per_element: 2,
271 /// offset: plane0_size,
272 /// size: plane1_size,
273 /// },
274 /// ];
275 ///
276 /// let surface = IOSurface::create_with_properties(
277 /// width,
278 /// height,
279 /// 0x34323076, // '420v'
280 /// 1,
281 /// plane0_bpr,
282 /// plane0_size + plane1_size,
283 /// Some(&planes),
284 /// );
285 /// ```
286 #[must_use]
287 #[allow(clippy::option_if_let_else)]
288 pub fn create_with_properties(
289 width: usize,
290 height: usize,
291 pixel_format: u32,
292 bytes_per_element: usize,
293 bytes_per_row: usize,
294 alloc_size: usize,
295 planes: Option<&[PlaneProperties]>,
296 ) -> Option<Self> {
297 let mut ptr: *mut c_void = std::ptr::null_mut();
298
299 let (
300 plane_count,
301 plane_widths,
302 plane_heights,
303 plane_row_bytes,
304 plane_elem_bytes,
305 plane_offsets,
306 plane_sizes,
307 ) = if let Some(p) = planes {
308 let widths: Vec<usize> = p.iter().map(|x| x.width).collect();
309 let heights: Vec<usize> = p.iter().map(|x| x.height).collect();
310 let row_bytes: Vec<usize> = p.iter().map(|x| x.bytes_per_row).collect();
311 let elem_bytes: Vec<usize> = p.iter().map(|x| x.bytes_per_element).collect();
312 let offsets: Vec<usize> = p.iter().map(|x| x.offset).collect();
313 let sizes: Vec<usize> = p.iter().map(|x| x.size).collect();
314 (
315 p.len(),
316 widths,
317 heights,
318 row_bytes,
319 elem_bytes,
320 offsets,
321 sizes,
322 )
323 } else {
324 (0, vec![], vec![], vec![], vec![], vec![], vec![])
325 };
326
327 let status = unsafe {
328 crate::ffi::io_surface_create_with_properties(
329 width,
330 height,
331 pixel_format,
332 bytes_per_element,
333 bytes_per_row,
334 alloc_size,
335 plane_count,
336 if plane_count > 0 {
337 plane_widths.as_ptr()
338 } else {
339 std::ptr::null()
340 },
341 if plane_count > 0 {
342 plane_heights.as_ptr()
343 } else {
344 std::ptr::null()
345 },
346 if plane_count > 0 {
347 plane_row_bytes.as_ptr()
348 } else {
349 std::ptr::null()
350 },
351 if plane_count > 0 {
352 plane_elem_bytes.as_ptr()
353 } else {
354 std::ptr::null()
355 },
356 if plane_count > 0 {
357 plane_offsets.as_ptr()
358 } else {
359 std::ptr::null()
360 },
361 if plane_count > 0 {
362 plane_sizes.as_ptr()
363 } else {
364 std::ptr::null()
365 },
366 &mut ptr,
367 )
368 };
369
370 if status == 0 && !ptr.is_null() {
371 Some(Self(ptr))
372 } else {
373 None
374 }
375 }
376
377 /// Wraps a +1 retained `IOSurfaceRef` and returns `None` for null.
378 pub fn from_raw(ptr: *mut c_void) -> Option<Self> {
379 if ptr.is_null() {
380 None
381 } else {
382 Some(Self(ptr))
383 }
384 }
385
386 /// Wraps a raw `IOSurfaceRef` by taking ownership without retaining it.
387 ///
388 /// # Safety
389 /// The caller must ensure `ptr` is a valid +1 retained `IOSurfaceRef`.
390 pub const unsafe fn from_ptr(ptr: *mut c_void) -> Self {
391 Self(ptr)
392 }
393
394 /// Get the raw pointer
395 #[must_use]
396 pub const fn as_ptr(&self) -> *mut c_void {
397 self.0
398 }
399
400 /// Get the width of the surface in pixels
401 #[must_use]
402 pub fn width(&self) -> usize {
403 unsafe { ffi::io_surface_get_width(self.0) }
404 }
405
406 /// Get the height of the surface in pixels
407 #[must_use]
408 pub fn height(&self) -> usize {
409 unsafe { ffi::io_surface_get_height(self.0) }
410 }
411
412 /// Get the bytes per row of the surface
413 #[must_use]
414 pub fn bytes_per_row(&self) -> usize {
415 unsafe { ffi::io_surface_get_bytes_per_row(self.0) }
416 }
417
418 /// Get the total allocation size of the surface in bytes
419 #[must_use]
420 pub fn alloc_size(&self) -> usize {
421 unsafe { ffi::io_surface_get_alloc_size(self.0) }
422 }
423
424 /// Get the data size of the surface in bytes (alias for `alloc_size`)
425 ///
426 /// This method provides API parity with `CVPixelBuffer::data_size()`.
427 #[must_use]
428 pub fn data_size(&self) -> usize {
429 self.alloc_size()
430 }
431
432 /// Get the pixel format of the surface (OSType/FourCC)
433 #[must_use]
434 pub fn pixel_format(&self) -> u32 {
435 unsafe { ffi::io_surface_get_pixel_format(self.0) }
436 }
437
438 /// Get the unique `IOSurfaceID` for this surface
439 #[must_use]
440 pub fn id(&self) -> u32 {
441 unsafe { ffi::io_surface_get_id(self.0) }
442 }
443
444 /// Get the modification seed value
445 ///
446 /// This value changes each time the surface is modified, useful for
447 /// detecting whether the surface contents have changed.
448 #[must_use]
449 pub fn seed(&self) -> u32 {
450 unsafe { ffi::io_surface_get_seed(self.0) }
451 }
452
453 /// Get the number of planes in this surface
454 ///
455 /// Multi-planar formats like YCbCr 420 have multiple planes:
456 /// - Plane 0: Y (luminance)
457 /// - Plane 1: `CbCr` (chrominance)
458 ///
459 /// Single-plane formats like BGRA return 0.
460 #[must_use]
461 pub fn plane_count(&self) -> usize {
462 unsafe { ffi::io_surface_get_plane_count(self.0) }
463 }
464
465 /// Get the width of a specific plane
466 ///
467 /// For YCbCr 4:2:0 formats, plane 1 (`CbCr`) is half the width of plane 0 (Y).
468 #[must_use]
469 pub fn width_of_plane(&self, plane_index: usize) -> usize {
470 unsafe { ffi::io_surface_get_width_of_plane(self.0, plane_index) }
471 }
472
473 /// Get the height of a specific plane
474 ///
475 /// For YCbCr 4:2:0 formats, plane 1 (`CbCr`) is half the height of plane 0 (Y).
476 #[must_use]
477 pub fn height_of_plane(&self, plane_index: usize) -> usize {
478 unsafe { ffi::io_surface_get_height_of_plane(self.0, plane_index) }
479 }
480
481 /// Get the bytes per row of a specific plane
482 #[must_use]
483 pub fn bytes_per_row_of_plane(&self, plane_index: usize) -> usize {
484 unsafe { ffi::io_surface_get_bytes_per_row_of_plane(self.0, plane_index) }
485 }
486
487 /// Get the bytes per element of the surface
488 #[must_use]
489 pub fn bytes_per_element(&self) -> usize {
490 unsafe { ffi::io_surface_get_bytes_per_element(self.0) }
491 }
492
493 /// Get the element width of the surface
494 #[must_use]
495 pub fn element_width(&self) -> usize {
496 unsafe { ffi::io_surface_get_element_width(self.0) }
497 }
498
499 /// Get the element height of the surface
500 #[must_use]
501 pub fn element_height(&self) -> usize {
502 unsafe { ffi::io_surface_get_element_height(self.0) }
503 }
504
505 /// Check if the surface is currently in use
506 #[must_use]
507 pub fn is_in_use(&self) -> bool {
508 unsafe { ffi::io_surface_is_in_use(self.0) }
509 }
510
511 /// Increment the use count of the surface
512 pub fn increment_use_count(&self) {
513 unsafe { ffi::io_surface_increment_use_count(self.0) }
514 }
515
516 /// Decrement the use count of the surface
517 pub fn decrement_use_count(&self) {
518 unsafe { ffi::io_surface_decrement_use_count(self.0) }
519 }
520
521 /// Get the base address (internal use only)
522 ///
523 /// # Safety
524 /// Caller must ensure the surface is locked before accessing the returned pointer.
525 pub(crate) fn base_address_raw(&self) -> *mut u8 {
526 unsafe { ffi::io_surface_get_base_address(self.0).cast::<u8>() }
527 }
528
529 /// Get the base address of a specific plane (internal use only)
530 ///
531 /// # Safety
532 /// Caller must ensure the surface is locked before accessing the returned pointer.
533 pub(crate) fn base_address_of_plane_raw(&self, plane_index: usize) -> Option<*mut u8> {
534 let plane_count = self.plane_count();
535 if plane_count == 0 || plane_index >= plane_count {
536 return None;
537 }
538 let ptr = unsafe { ffi::io_surface_get_base_address_of_plane(self.0, plane_index) };
539 if ptr.is_null() {
540 None
541 } else {
542 Some(ptr.cast::<u8>())
543 }
544 }
545
546 /// Lock the surface for CPU access (low-level API)
547 ///
548 /// Prefer using [`lock`](Self::lock) for RAII-style access.
549 ///
550 /// # Arguments
551 /// * `options` - Lock options (e.g., `IOSurfaceLockOptions::READ_ONLY`)
552 ///
553 /// # Errors
554 /// Returns `kern_return_t` error code if the lock fails.
555 pub fn lock_raw(&self, options: IOSurfaceLockOptions) -> Result<u32, i32> {
556 let mut seed: u32 = 0;
557 let status = unsafe { ffi::io_surface_lock(self.0, options.as_u32(), &mut seed) };
558 if status == 0 {
559 Ok(seed)
560 } else {
561 Err(status)
562 }
563 }
564
565 /// Unlock the surface after CPU access (low-level API)
566 ///
567 /// # Arguments
568 /// * `options` - Must match the options used in the corresponding `lock_raw()` call
569 ///
570 /// # Errors
571 /// Returns `kern_return_t` error code if the unlock fails.
572 pub fn unlock_raw(&self, options: IOSurfaceLockOptions) -> Result<u32, i32> {
573 let mut seed: u32 = 0;
574 let status = unsafe { ffi::io_surface_unlock(self.0, options.as_u32(), &mut seed) };
575 if status == 0 {
576 Ok(seed)
577 } else {
578 Err(status)
579 }
580 }
581
582 /// Lock the surface and return a guard for RAII-style access
583 ///
584 /// This is the recommended way to access surface memory. The guard ensures
585 /// the surface is properly unlocked when it goes out of scope.
586 ///
587 /// # Arguments
588 /// * `options` - Lock options (e.g., `IOSurfaceLockOptions::READ_ONLY`)
589 ///
590 /// # Errors
591 /// Returns `kern_return_t` error code if the lock fails.
592 ///
593 /// # Examples
594 ///
595 /// ```no_run
596 /// use apple_cf::iosurface::{IOSurface, IOSurfaceLockOptions};
597 ///
598 /// fn read_surface(surface: &IOSurface) -> Result<(), i32> {
599 /// let guard = surface.lock(IOSurfaceLockOptions::READ_ONLY)?;
600 /// let data = guard.as_slice();
601 /// println!("Read {} bytes", data.len());
602 /// Ok(())
603 /// }
604 /// ```
605 pub fn lock(&self, options: IOSurfaceLockOptions) -> Result<IOSurfaceLockGuard<'_>, i32> {
606 self.lock_raw(options)?;
607 Ok(IOSurfaceLockGuard {
608 surface: self,
609 options,
610 })
611 }
612
613 /// Lock the surface for read-only access
614 ///
615 /// This is a convenience method equivalent to `lock(IOSurfaceLockOptions::READ_ONLY)`.
616 ///
617 /// # Errors
618 /// Returns `kern_return_t` error code if the lock fails.
619 pub fn lock_read_only(&self) -> Result<IOSurfaceLockGuard<'_>, i32> {
620 self.lock(IOSurfaceLockOptions::READ_ONLY)
621 }
622
623 /// Lock the surface for read-write access
624 ///
625 /// This is a convenience method equivalent to `lock(IOSurfaceLockOptions::NONE)`.
626 ///
627 /// # Errors
628 /// Returns `kern_return_t` error code if the lock fails.
629 pub fn lock_read_write(&self) -> Result<IOSurfaceLockGuard<'_>, i32> {
630 self.lock(IOSurfaceLockOptions::NONE)
631 }
632}
633
634/// RAII guard for locked `IOSurface`
635///
636/// Provides safe access to surface memory while the lock is held.
637/// The surface is automatically unlocked when this guard is dropped.
638///
639/// # Memory Access
640///
641/// All base address access is through this guard to ensure proper locking.
642///
643/// # Examples
644///
645/// ```no_run
646/// use apple_cf::iosurface::{IOSurface, IOSurfaceLockOptions};
647///
648/// fn access_surface(surface: &IOSurface) -> Result<(), i32> {
649/// let guard = surface.lock(IOSurfaceLockOptions::READ_ONLY)?;
650///
651/// // Access the entire buffer
652/// let data = guard.as_slice();
653///
654/// // Access a specific row
655/// if let Some(row) = guard.row(0) {
656/// println!("First row: {} bytes", row.len());
657/// }
658///
659/// // Access a specific plane (for multi-planar formats)
660/// if let Some(plane_data) = guard.plane_data(0) {
661/// println!("Plane 0: {} bytes", plane_data.len());
662/// }
663///
664/// Ok(())
665/// }
666/// ```
667pub struct IOSurfaceLockGuard<'a> {
668 surface: &'a IOSurface,
669 options: IOSurfaceLockOptions,
670}
671
672impl IOSurfaceLockGuard<'_> {
673 /// Get the width of the surface in pixels
674 #[must_use]
675 pub fn width(&self) -> usize {
676 self.surface.width()
677 }
678
679 /// Get the height of the surface in pixels
680 #[must_use]
681 pub fn height(&self) -> usize {
682 self.surface.height()
683 }
684
685 /// Get the bytes per row of the surface
686 #[must_use]
687 pub fn bytes_per_row(&self) -> usize {
688 self.surface.bytes_per_row()
689 }
690
691 /// Get the total allocation size in bytes
692 #[must_use]
693 pub fn alloc_size(&self) -> usize {
694 self.surface.alloc_size()
695 }
696
697 /// Get the data size of the surface (alias for `alloc_size`)
698 #[must_use]
699 pub fn data_size(&self) -> usize {
700 self.alloc_size()
701 }
702
703 /// Get the pixel format of the surface
704 #[must_use]
705 pub fn pixel_format(&self) -> u32 {
706 self.surface.pixel_format()
707 }
708
709 /// Get the number of planes in the surface
710 #[must_use]
711 pub fn plane_count(&self) -> usize {
712 self.surface.plane_count()
713 }
714
715 /// Get the base address of the locked surface
716 ///
717 /// # Safety
718 ///
719 /// The returned pointer is only valid while this guard is held.
720 #[must_use]
721 pub fn base_address(&self) -> *const u8 {
722 self.surface.base_address_raw().cast_const()
723 }
724
725 /// Get the mutable base address (only valid for read-write locks)
726 ///
727 /// Returns `None` if this is a read-only lock.
728 ///
729 /// # Safety
730 ///
731 /// The returned pointer is only valid while this guard is held.
732 pub fn base_address_mut(&mut self) -> Option<*mut u8> {
733 if self.options.is_read_only() {
734 None
735 } else {
736 Some(self.surface.base_address_raw())
737 }
738 }
739
740 /// Get the base address of a specific plane
741 ///
742 /// For multi-planar formats like YCbCr 4:2:0:
743 /// - Plane 0: Y (luminance) data
744 /// - Plane 1: `CbCr` (chrominance) data
745 ///
746 /// Returns `None` if the plane index is out of bounds.
747 ///
748 /// # Safety
749 ///
750 /// The returned pointer is only valid while this guard is held.
751 pub fn base_address_of_plane(&self, plane_index: usize) -> Option<*const u8> {
752 self.surface
753 .base_address_of_plane_raw(plane_index)
754 .map(<*mut u8>::cast_const)
755 }
756
757 /// Get the mutable base address of a specific plane
758 ///
759 /// Returns `None` if this is a read-only lock or the plane index is out of bounds.
760 pub fn base_address_of_plane_mut(&mut self, plane_index: usize) -> Option<*mut u8> {
761 if self.options.is_read_only() {
762 return None;
763 }
764 self.surface.base_address_of_plane_raw(plane_index)
765 }
766
767 /// Get a slice view of the surface data
768 ///
769 /// The lock guard ensures the surface is locked for the lifetime of the slice.
770 #[must_use]
771 pub fn as_slice(&self) -> &[u8] {
772 let ptr = self.base_address();
773 let len = self.alloc_size();
774 if ptr.is_null() || len == 0 {
775 &[]
776 } else {
777 unsafe { std::slice::from_raw_parts(ptr, len) }
778 }
779 }
780
781 /// Get a mutable slice view of the surface data (only valid for read-write locks)
782 ///
783 /// Returns `None` if this is a read-only lock.
784 pub fn as_slice_mut(&mut self) -> Option<&mut [u8]> {
785 if self.options.is_read_only() {
786 return None;
787 }
788 let ptr = self.base_address_mut()?;
789 let len = self.alloc_size();
790 if ptr.is_null() || len == 0 {
791 Some(&mut [])
792 } else {
793 Some(unsafe { std::slice::from_raw_parts_mut(ptr, len) })
794 }
795 }
796
797 /// Get a specific row as a slice
798 ///
799 /// Returns `None` if the row index is out of bounds.
800 #[must_use]
801 pub fn row(&self, row_index: usize) -> Option<&[u8]> {
802 if row_index >= self.height() {
803 return None;
804 }
805 let ptr = self.base_address();
806 if ptr.is_null() {
807 return None;
808 }
809 unsafe {
810 let row_ptr = ptr.add(row_index * self.bytes_per_row());
811 Some(std::slice::from_raw_parts(row_ptr, self.bytes_per_row()))
812 }
813 }
814
815 /// Get a slice of plane data
816 ///
817 /// Returns the data for a specific plane as a byte slice. The slice size is
818 /// calculated from the plane's height and bytes per row.
819 ///
820 /// Returns `None` if the plane index is out of bounds.
821 #[must_use]
822 pub fn plane_data(&self, plane_index: usize) -> Option<&[u8]> {
823 let base = self.base_address_of_plane(plane_index)?;
824 let height = self.surface.height_of_plane(plane_index);
825 let bytes_per_row = self.surface.bytes_per_row_of_plane(plane_index);
826 Some(unsafe { std::slice::from_raw_parts(base, height * bytes_per_row) })
827 }
828
829 /// Get a specific row from a plane as a slice
830 ///
831 /// Returns `None` if the plane or row index is out of bounds.
832 #[must_use]
833 pub fn plane_row(&self, plane_index: usize, row_index: usize) -> Option<&[u8]> {
834 let height = self.surface.height_of_plane(plane_index);
835 if row_index >= height {
836 return None;
837 }
838 let base = self.base_address_of_plane(plane_index)?;
839 let bytes_per_row = self.surface.bytes_per_row_of_plane(plane_index);
840 Some(unsafe {
841 std::slice::from_raw_parts(base.add(row_index * bytes_per_row), bytes_per_row)
842 })
843 }
844
845 /// Access surface with a standard `std::io::Cursor`
846 ///
847 /// Returns a cursor over the surface data that implements `Read` and `Seek`.
848 ///
849 /// # Examples
850 ///
851 /// ```no_run
852 /// use std::io::{Read, Seek, SeekFrom};
853 /// use apple_cf::iosurface::{IOSurface, IOSurfaceLockOptions};
854 ///
855 /// fn read_surface(surface: &IOSurface) {
856 /// let guard = surface.lock(IOSurfaceLockOptions::READ_ONLY).unwrap();
857 /// let mut cursor = guard.cursor();
858 ///
859 /// // Read first 4 bytes
860 /// let mut pixel = [0u8; 4];
861 /// cursor.read_exact(&mut pixel).unwrap();
862 ///
863 /// // Seek to row 10
864 /// let offset = 10 * guard.bytes_per_row();
865 /// cursor.seek(SeekFrom::Start(offset as u64)).unwrap();
866 /// }
867 /// ```
868 #[must_use]
869 pub fn cursor(&self) -> io::Cursor<&[u8]> {
870 io::Cursor::new(self.as_slice())
871 }
872
873 /// Get raw pointer to surface data
874 #[must_use]
875 pub fn as_ptr(&self) -> *const u8 {
876 self.base_address()
877 }
878
879 /// Get mutable raw pointer to surface data (only valid for read-write locks)
880 ///
881 /// Returns `None` if this is a read-only lock.
882 pub fn as_mut_ptr(&mut self) -> Option<*mut u8> {
883 self.base_address_mut()
884 }
885
886 /// Check if this is a read-only lock
887 #[must_use]
888 pub const fn is_read_only(&self) -> bool {
889 self.options.is_read_only()
890 }
891
892 /// Get the lock options
893 #[must_use]
894 pub const fn options(&self) -> IOSurfaceLockOptions {
895 self.options
896 }
897}
898
899impl std::ops::Deref for IOSurfaceLockGuard<'_> {
900 type Target = [u8];
901
902 fn deref(&self) -> &Self::Target {
903 self.as_slice()
904 }
905}
906
907impl Drop for IOSurfaceLockGuard<'_> {
908 fn drop(&mut self) {
909 let _ = self.surface.unlock_raw(self.options);
910 }
911}
912
913impl std::fmt::Debug for IOSurfaceLockGuard<'_> {
914 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
915 f.debug_struct("IOSurfaceLockGuard")
916 .field("options", &self.options)
917 .field(
918 "surface_size",
919 &(self.surface.width(), self.surface.height()),
920 )
921 .finish()
922 }
923}
924
925impl Drop for IOSurface {
926 fn drop(&mut self) {
927 if !self.0.is_null() {
928 unsafe {
929 ffi::io_surface_release(self.0);
930 }
931 }
932 }
933}
934
935impl Clone for IOSurface {
936 fn clone(&self) -> Self {
937 unsafe {
938 let ptr = ffi::io_surface_retain(self.0);
939 Self(ptr)
940 }
941 }
942}
943
944// SAFETY: `IOSurfaceRef` is a Core Foundation type documented by Apple as safe
945// to share across threads (it is the primary cross-process frame-delivery
946// mechanism). Our wrapper holds only the opaque pointer; all mutation
947// (lock/unlock) goes through Apple's thread-safe primitives.
948unsafe impl Send for IOSurface {}
949unsafe impl Sync for IOSurface {}
950
951impl fmt::Debug for IOSurface {
952 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
953 f.debug_struct("IOSurface")
954 .field("id", &self.id())
955 .field("width", &self.width())
956 .field("height", &self.height())
957 .field("bytes_per_row", &self.bytes_per_row())
958 .field("pixel_format", &self.pixel_format())
959 .field("plane_count", &self.plane_count())
960 .finish()
961 }
962}
963
964impl fmt::Display for IOSurface {
965 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
966 write!(
967 f,
968 "IOSurface({}x{}, {} bytes/row)",
969 self.width(),
970 self.height(),
971 self.bytes_per_row()
972 )
973 }
974}