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}