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