Skip to main content

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}