Skip to main content

apple_metal/
lib.rs

1#![doc = include_str!("../README.md")]
2//!
3//! ---
4//!
5//! # API Documentation
6
7#![cfg_attr(docsrs, feature(doc_cfg))]
8#![allow(clippy::missing_const_for_fn)]
9
10use core::ffi::c_void;
11use core::ptr;
12
13pub(crate) mod advanced;
14pub(crate) mod argument;
15pub(crate) mod command;
16pub(crate) mod exhaustive;
17/// Groups `Metal` framework constants for `ffi`.
18pub mod ffi;
19pub(crate) mod metalfx;
20pub(crate) mod pipeline;
21pub(crate) mod render;
22pub(crate) mod state;
23pub(crate) mod util;
24
25/// Re-exports the `Metal` framework surface for this item.
26pub use advanced::*;
27/// Re-exports the `Metal` framework surface for this item.
28pub use argument::*;
29/// Re-exports the `Metal` framework surface for this item.
30pub use command::*;
31/// Re-exports the `Metal` framework surface for this item.
32pub use exhaustive::*;
33/// Re-exports the `Metal` framework surface for this item.
34pub use metalfx::*;
35/// Re-exports the `Metal` framework surface for this item.
36pub use pipeline::*;
37/// Re-exports the `Metal` framework surface for this item.
38pub use render::*;
39/// Re-exports the `Metal` framework surface for this item.
40pub use state::*;
41
42/// Common `MTLPixelFormat` constants.
43pub mod pixel_format {
44/// Mirrors the `Metal` framework constant `A8UNORM`.
45    pub const A8UNORM: usize = 1;
46/// Mirrors the `Metal` framework constant `R8UNORM`.
47    pub const R8UNORM: usize = 10;
48/// Mirrors the `Metal` framework constant `R8SNORM`.
49    pub const R8SNORM: usize = 12;
50/// Mirrors the `Metal` framework constant `R8UINT`.
51    pub const R8UINT: usize = 13;
52/// Mirrors the `Metal` framework constant `R8SINT`.
53    pub const R8SINT: usize = 14;
54/// Mirrors the `Metal` framework constant `R16UNORM`.
55    pub const R16UNORM: usize = 20;
56/// Mirrors the `Metal` framework constant `R16SNORM`.
57    pub const R16SNORM: usize = 22;
58/// Mirrors the `Metal` framework constant `R16UINT`.
59    pub const R16UINT: usize = 23;
60/// Mirrors the `Metal` framework constant `R16SINT`.
61    pub const R16SINT: usize = 24;
62/// Mirrors the `Metal` framework constant `R16FLOAT`.
63    pub const R16FLOAT: usize = 25;
64/// Mirrors the `Metal` framework constant `RG8UNORM`.
65    pub const RG8UNORM: usize = 30;
66/// Mirrors the `Metal` framework constant `RG8SNORM`.
67    pub const RG8SNORM: usize = 32;
68/// Mirrors the `Metal` framework constant `RG8UINT`.
69    pub const RG8UINT: usize = 33;
70/// Mirrors the `Metal` framework constant `RG8SINT`.
71    pub const RG8SINT: usize = 34;
72/// Mirrors the `Metal` framework constant `RGBA8UNORM`.
73    pub const RGBA8UNORM: usize = 70;
74/// Mirrors the `Metal` framework constant `RGBA8UNORM_SRGB`.
75    pub const RGBA8UNORM_SRGB: usize = 71;
76/// Mirrors the `Metal` framework constant `RGBA8SNORM`.
77    pub const RGBA8SNORM: usize = 72;
78/// Mirrors the `Metal` framework constant `RGBA8UINT`.
79    pub const RGBA8UINT: usize = 73;
80/// Mirrors the `Metal` framework constant `RGBA8SINT`.
81    pub const RGBA8SINT: usize = 74;
82/// Mirrors the `Metal` framework constant `BGRA8UNORM`.
83    pub const BGRA8UNORM: usize = 80;
84/// Mirrors the `Metal` framework constant `BGRA8UNORM_SRGB`.
85    pub const BGRA8UNORM_SRGB: usize = 81;
86/// Mirrors the `Metal` framework constant `R32FLOAT`.
87    pub const R32FLOAT: usize = 55;
88/// Mirrors the `Metal` framework constant `RG16FLOAT`.
89    pub const RG16FLOAT: usize = 65;
90/// Mirrors the `Metal` framework constant `RGBA16FLOAT`.
91    pub const RGBA16FLOAT: usize = 115;
92/// Mirrors the `Metal` framework constant `RGBA32FLOAT`.
93    pub const RGBA32FLOAT: usize = 125;
94/// Mirrors the `Metal` framework constant `DEPTH32FLOAT`.
95    pub const DEPTH32FLOAT: usize = 252;
96/// Mirrors the `Metal` framework constant `STENCIL8`.
97    pub const STENCIL8: usize = 253;
98/// Mirrors the `Metal` framework constant `BGRA10_XR`.
99    pub const BGRA10_XR: usize = 552;
100/// Mirrors the `Metal` framework constant `BGR10_XR`.
101    pub const BGR10_XR: usize = 554;
102}
103
104/// `MTLStorageMode` enum values — memory residency hints.
105pub mod storage_mode {
106/// Mirrors the `Metal` framework constant `SHARED`.
107    pub const SHARED: usize = 0;
108/// Mirrors the `Metal` framework constant `MANAGED`.
109    pub const MANAGED: usize = 1;
110/// Mirrors the `Metal` framework constant `PRIVATE`.
111    pub const PRIVATE: usize = 2;
112/// Mirrors the `Metal` framework constant `MEMORYLESS`.
113    pub const MEMORYLESS: usize = 3;
114}
115
116/// `MTLCPUCacheMode` enum values.
117pub mod cpu_cache_mode {
118/// Mirrors the `Metal` framework constant `DEFAULT_CACHE`.
119    pub const DEFAULT_CACHE: usize = 0;
120/// Mirrors the `Metal` framework constant `WRITE_COMBINED`.
121    pub const WRITE_COMBINED: usize = 1;
122}
123
124/// `MTLHazardTrackingMode` enum values.
125pub mod hazard_tracking_mode {
126/// Mirrors the `Metal` framework constant `DEFAULT`.
127    pub const DEFAULT: usize = 0;
128/// Mirrors the `Metal` framework constant `UNTRACKED`.
129    pub const UNTRACKED: usize = 1;
130/// Mirrors the `Metal` framework constant `TRACKED`.
131    pub const TRACKED: usize = 2;
132}
133
134/// `MTLResourceOptions` bitmask values.
135pub mod resource_options {
136/// Mirrors the `Metal` framework constant `CPU_CACHE_MODE_DEFAULT`.
137    pub const CPU_CACHE_MODE_DEFAULT: usize = 0;
138/// Mirrors the `Metal` framework constant `CPU_CACHE_MODE_WRITE_COMBINED`.
139    pub const CPU_CACHE_MODE_WRITE_COMBINED: usize = 1;
140/// Mirrors the `Metal` framework constant `STORAGE_MODE_SHARED`.
141    pub const STORAGE_MODE_SHARED: usize = 0;
142/// Mirrors the `Metal` framework constant `STORAGE_MODE_MANAGED`.
143    pub const STORAGE_MODE_MANAGED: usize = 1 << 4;
144/// Mirrors the `Metal` framework constant `STORAGE_MODE_PRIVATE`.
145    pub const STORAGE_MODE_PRIVATE: usize = 2 << 4;
146/// Mirrors the `Metal` framework constant `HAZARD_TRACKING_MODE_DEFAULT`.
147    pub const HAZARD_TRACKING_MODE_DEFAULT: usize = 0;
148/// Mirrors the `Metal` framework constant `HAZARD_TRACKING_MODE_UNTRACKED`.
149    pub const HAZARD_TRACKING_MODE_UNTRACKED: usize = 1 << 8;
150/// Mirrors the `Metal` framework constant `HAZARD_TRACKING_MODE_TRACKED`.
151    pub const HAZARD_TRACKING_MODE_TRACKED: usize = 2 << 8;
152}
153
154/// `MTLTextureUsage` bitmask.
155pub mod texture_usage {
156/// Mirrors the `Metal` framework constant `SHADER_READ`.
157    pub const SHADER_READ: usize = 0x01;
158/// Mirrors the `Metal` framework constant `SHADER_WRITE`.
159    pub const SHADER_WRITE: usize = 0x02;
160/// Mirrors the `Metal` framework constant `RENDER_TARGET`.
161    pub const RENDER_TARGET: usize = 0x04;
162}
163
164/// `MTLGPUFamily` — feature-family identifiers.
165pub mod gpu_family {
166/// Mirrors the `Metal` framework constant `APPLE1`.
167    pub const APPLE1: i64 = 1001;
168/// Mirrors the `Metal` framework constant `APPLE2`.
169    pub const APPLE2: i64 = 1002;
170/// Mirrors the `Metal` framework constant `APPLE3`.
171    pub const APPLE3: i64 = 1003;
172/// Mirrors the `Metal` framework constant `APPLE4`.
173    pub const APPLE4: i64 = 1004;
174/// Mirrors the `Metal` framework constant `APPLE5`.
175    pub const APPLE5: i64 = 1005;
176/// Mirrors the `Metal` framework constant `APPLE6`.
177    pub const APPLE6: i64 = 1006;
178/// Mirrors the `Metal` framework constant `APPLE7`.
179    pub const APPLE7: i64 = 1007;
180/// Mirrors the `Metal` framework constant `APPLE8`.
181    pub const APPLE8: i64 = 1008;
182/// Mirrors the `Metal` framework constant `APPLE9`.
183    pub const APPLE9: i64 = 1009;
184/// Mirrors the `Metal` framework constant `MAC1`.
185    pub const MAC1: i64 = 2001;
186/// Mirrors the `Metal` framework constant `MAC2`.
187    pub const MAC2: i64 = 2002;
188/// Mirrors the `Metal` framework constant `COMMON1`.
189    pub const COMMON1: i64 = 3001;
190/// Mirrors the `Metal` framework constant `COMMON2`.
191    pub const COMMON2: i64 = 3002;
192/// Mirrors the `Metal` framework constant `COMMON3`.
193    pub const COMMON3: i64 = 3003;
194/// Mirrors the `Metal` framework constant `METAL3`.
195    pub const METAL3: i64 = 5001;
196}
197
198// ---- Device ----
199
200/// Apple's `id<MTLDevice>` — handle to a Metal GPU.
201pub struct MetalDevice {
202    ptr: *mut c_void,
203    drop_on_release: bool,
204}
205
206// SAFETY: `id<MTLDevice>` is thread-safe: creation, capability queries, and
207// resource allocation all synchronize internally via ObjC ARC + Metal's own locks.
208unsafe impl Send for MetalDevice {}
209unsafe impl Sync for MetalDevice {}
210
211impl Drop for MetalDevice {
212    fn drop(&mut self) {
213        if self.drop_on_release && !self.ptr.is_null() {
214            unsafe { ffi::am_device_release(self.ptr) };
215            self.ptr = ptr::null_mut();
216        }
217    }
218}
219
220impl MetalDevice {
221    /// Return the system's default Metal device.
222    #[must_use]
223    pub fn system_default() -> Option<Self> {
224        let p = unsafe { ffi::am_device_system_default() };
225        if p.is_null() {
226            None
227        } else {
228            Some(unsafe { Self::from_retained_ptr(p) })
229        }
230    }
231
232    /// Raw `id<MTLDevice>` pointer.
233    #[must_use]
234    pub const fn as_ptr(&self) -> *mut c_void {
235        self.ptr
236    }
237
238    /// True if the GPU uses unified memory (Apple Silicon).
239    #[must_use]
240    pub fn has_unified_memory(&self) -> bool {
241        unsafe { ffi::am_device_has_unified_memory(self.ptr) }
242    }
243
244    /// Recommended maximum working-set size in bytes.
245    #[must_use]
246    pub fn recommended_max_working_set_size(&self) -> u64 {
247        unsafe { ffi::am_device_recommended_max_working_set_size(self.ptr) }
248    }
249
250    /// True if this device supports the requested feature family —
251    /// see [`gpu_family`].
252    #[must_use]
253    pub fn supports_family(&self, family: i64) -> bool {
254        unsafe { ffi::am_device_supports_family(self.ptr, family) }
255    }
256
257    /// Allocate a GPU-visible buffer of `length` bytes.
258    /// `options` is an `MTLResourceOptions` bitmask (see
259    /// [`resource_options`]).
260    #[must_use]
261    pub fn new_buffer(&self, length: usize, options: usize) -> Option<MetalBuffer> {
262        let p = unsafe { ffi::am_device_new_buffer(self.ptr, length, options) };
263        if p.is_null() {
264            None
265        } else {
266            Some(MetalBuffer { ptr: p })
267        }
268    }
269
270    /// Allocate a fresh `MTLTexture` matching `descriptor`.
271    #[must_use]
272    pub fn new_texture(&self, descriptor: TextureDescriptor) -> Option<MetalTexture> {
273        let p = unsafe {
274            ffi::am_device_new_texture_2d(
275                self.ptr,
276                descriptor.pixel_format,
277                descriptor.width,
278                descriptor.height,
279                descriptor.mipmapped,
280                descriptor.usage,
281                descriptor.storage_mode,
282            )
283        };
284        if p.is_null() {
285            None
286        } else {
287            Some(MetalTexture { ptr: p })
288        }
289    }
290
291    /// Create a new `MTLCommandQueue` to schedule GPU work.
292    #[must_use]
293    pub fn new_command_queue(&self) -> Option<CommandQueue> {
294        let p = unsafe { ffi::am_device_new_command_queue(self.ptr) };
295        if p.is_null() {
296            None
297        } else {
298            Some(CommandQueue { ptr: p })
299        }
300    }
301
302    /// Compile a Metal Shading Language source string into a runtime
303    /// `MTLLibrary`. On error, returns the localized Metal compiler
304    /// diagnostic.
305    ///
306    /// # Errors
307    ///
308    /// Returns the Metal compiler's localized error string on failure.
309    pub fn new_library_with_source(&self, source: &str) -> Result<MetalLibrary, String> {
310        let csrc = std::ffi::CString::new(source).map_err(|e| e.to_string())?;
311        let mut err_msg: *mut core::ffi::c_char = core::ptr::null_mut();
312        let p = unsafe {
313            ffi::am_device_new_library_with_source(self.ptr, csrc.as_ptr(), &mut err_msg)
314        };
315        if p.is_null() {
316            let msg = if err_msg.is_null() {
317                "MTLDevice.makeLibrary returned nil".to_string()
318            } else {
319                let s = unsafe { std::ffi::CStr::from_ptr(err_msg) }
320                    .to_string_lossy()
321                    .into_owned();
322                unsafe { libc::free(err_msg.cast()) };
323                s
324            };
325            Err(msg)
326        } else {
327            Ok(MetalLibrary { ptr: p })
328        }
329    }
330
331    /// Compile a kernel into a `MTLComputePipelineState` ready for
332    /// dispatch on a command buffer.
333    ///
334    /// # Errors
335    ///
336    /// Returns the Metal pipeline compiler's localized error string
337    /// on failure.
338    pub fn new_compute_pipeline_state(
339        &self,
340        function: &MetalFunction,
341    ) -> Result<ComputePipelineState, String> {
342        let mut err_msg: *mut core::ffi::c_char = core::ptr::null_mut();
343        let p = unsafe {
344            ffi::am_device_new_compute_pipeline_state(self.ptr, function.ptr, &mut err_msg)
345        };
346        if p.is_null() {
347            let msg = if err_msg.is_null() {
348                "MTLDevice.makeComputePipelineState returned nil".to_string()
349            } else {
350                let s = unsafe { std::ffi::CStr::from_ptr(err_msg) }
351                    .to_string_lossy()
352                    .into_owned();
353                unsafe { libc::free(err_msg.cast()) };
354                s
355            };
356            Err(msg)
357        } else {
358            Ok(ComputePipelineState { ptr: p })
359        }
360    }
361
362    /// Wrap a raw `id<MTLDevice>` pointer **without** taking ownership.
363    /// The returned handle will NOT release the underlying object on
364    /// drop.
365    ///
366    /// # Safety
367    ///
368    /// `ptr` must be a valid `id<MTLDevice>` whose lifetime is managed
369    /// by some other owner.
370    #[must_use]
371    pub unsafe fn from_raw_borrowed(ptr: *mut c_void) -> ManuallyDropDevice {
372        ManuallyDropDevice {
373            inner: Self {
374                ptr,
375                drop_on_release: false,
376            },
377        }
378    }
379}
380
381/// Borrowed [`MetalDevice`] that does not release on drop.
382pub struct ManuallyDropDevice {
383    inner: MetalDevice,
384}
385
386impl core::ops::Deref for ManuallyDropDevice {
387    type Target = MetalDevice;
388    fn deref(&self) -> &Self::Target {
389        &self.inner
390    }
391}
392
393// ---- Command queue + command buffer ----
394
395/// Apple's `id<MTLCommandQueue>` — schedules GPU work.
396pub struct CommandQueue {
397    ptr: *mut c_void,
398}
399
400// SAFETY: `id<MTLCommandQueue>` is documented by Apple as thread-safe; multiple
401// threads may independently create command buffers from the same queue.
402unsafe impl Send for CommandQueue {}
403unsafe impl Sync for CommandQueue {}
404
405impl Drop for CommandQueue {
406    fn drop(&mut self) {
407        if !self.ptr.is_null() {
408            unsafe { ffi::am_command_queue_release(self.ptr) };
409            self.ptr = ptr::null_mut();
410        }
411    }
412}
413
414impl CommandQueue {
415    /// Create a new command buffer for recording GPU commands.
416    #[must_use]
417    pub fn new_command_buffer(&self) -> Option<CommandBuffer> {
418        let p = unsafe { ffi::am_command_queue_new_command_buffer(self.ptr) };
419        if p.is_null() {
420            None
421        } else {
422            Some(CommandBuffer { ptr: p })
423        }
424    }
425
426    /// Raw `id<MTLCommandQueue>` pointer.
427    #[must_use]
428    pub const fn as_ptr(&self) -> *mut c_void {
429        self.ptr
430    }
431}
432
433/// Apple's `id<MTLCommandBuffer>` — a recorded batch of GPU commands.
434pub struct CommandBuffer {
435    ptr: *mut c_void,
436}
437
438// SAFETY: `id<MTLCommandBuffer>` is safe to move across threads.  Its `&self`
439// methods are read-only status queries; mutable encoding operations require
440// `&mut CommandEncoder` (a separate type), so Sync is sound.
441unsafe impl Send for CommandBuffer {}
442unsafe impl Sync for CommandBuffer {}
443
444impl Drop for CommandBuffer {
445    fn drop(&mut self) {
446        if !self.ptr.is_null() {
447            unsafe { ffi::am_command_buffer_release(self.ptr) };
448            self.ptr = ptr::null_mut();
449        }
450    }
451}
452
453impl CommandBuffer {
454    /// Submit the recorded commands for execution.
455    pub fn commit(&self) {
456        unsafe { ffi::am_command_buffer_commit(self.ptr) };
457    }
458
459    /// Block the current thread until all submitted commands finish.
460    pub fn wait_until_completed(&self) {
461        unsafe { ffi::am_command_buffer_wait_until_completed(self.ptr) };
462    }
463
464    /// Record a blit copy from `src` into `dst` for `size` bytes.
465    /// Convenience for GPU↔GPU byte copies.
466    #[must_use]
467    pub fn blit_copy_buffer(
468        &self,
469        src: &MetalBuffer,
470        src_offset: usize,
471        dst: &MetalBuffer,
472        dst_offset: usize,
473        size: usize,
474    ) -> bool {
475        unsafe {
476            ffi::am_command_buffer_blit_copy_buffer(
477                self.ptr,
478                src.as_ptr(),
479                src_offset,
480                dst.as_ptr(),
481                dst_offset,
482                size,
483            )
484        }
485    }
486
487    /// Record a 1-D compute dispatch: binds `pso`, sets `buffers` at
488    /// argument slots `0..buffers.len()`, and dispatches `threadgroups`
489    /// of `threads_per_group` threads.
490    #[must_use]
491    pub fn dispatch_compute_1d(
492        &self,
493        pso: &ComputePipelineState,
494        buffers: &[&MetalBuffer],
495        threadgroups: usize,
496        threads_per_group: usize,
497    ) -> bool {
498        let raw: Vec<*mut c_void> = buffers.iter().map(|b| b.as_ptr()).collect();
499        unsafe {
500            ffi::am_command_buffer_dispatch_compute_1d(
501                self.ptr,
502                pso.ptr,
503                raw.as_ptr(),
504                raw.len(),
505                threadgroups,
506                threads_per_group,
507            )
508        }
509    }
510
511    /// Raw `id<MTLCommandBuffer>` pointer.
512    #[must_use]
513    pub const fn as_ptr(&self) -> *mut c_void {
514        self.ptr
515    }
516}
517
518// ---- Library + Function + ComputePipelineState ----
519
520/// Apple's `id<MTLLibrary>` — compiled MSL source.
521pub struct MetalLibrary {
522    ptr: *mut c_void,
523}
524
525// SAFETY: `id<MTLLibrary>` is immutable after creation; all its methods are
526// thread-safe per Apple documentation.
527unsafe impl Send for MetalLibrary {}
528unsafe impl Sync for MetalLibrary {}
529
530impl Drop for MetalLibrary {
531    fn drop(&mut self) {
532        if !self.ptr.is_null() {
533            unsafe { ffi::am_library_release(self.ptr) };
534            self.ptr = ptr::null_mut();
535        }
536    }
537}
538
539impl MetalLibrary {
540    /// Look up a kernel function by its source name.
541    #[must_use]
542    pub fn new_function(&self, name: &str) -> Option<MetalFunction> {
543        let cname = std::ffi::CString::new(name).ok()?;
544        let p = unsafe { ffi::am_library_new_function(self.ptr, cname.as_ptr()) };
545        if p.is_null() {
546            None
547        } else {
548            Some(MetalFunction { ptr: p })
549        }
550    }
551
552    /// Raw `id<MTLLibrary>` pointer.
553    #[must_use]
554    pub const fn as_ptr(&self) -> *mut c_void {
555        self.ptr
556    }
557}
558
559/// Apple's `id<MTLFunction>` — a single compiled shader entry point.
560pub struct MetalFunction {
561    ptr: *mut c_void,
562}
563
564// SAFETY: `id<MTLFunction>` is immutable after creation and its handle is safe
565// to share across threads.
566unsafe impl Send for MetalFunction {}
567unsafe impl Sync for MetalFunction {}
568
569impl Drop for MetalFunction {
570    fn drop(&mut self) {
571        if !self.ptr.is_null() {
572            unsafe { ffi::am_function_release(self.ptr) };
573            self.ptr = ptr::null_mut();
574        }
575    }
576}
577
578impl MetalFunction {
579    /// Raw `id<MTLFunction>` pointer.
580    #[must_use]
581    pub const fn as_ptr(&self) -> *mut c_void {
582        self.ptr
583    }
584}
585
586/// Apple's `id<MTLComputePipelineState>` — a compiled compute kernel.
587pub struct ComputePipelineState {
588    ptr: *mut c_void,
589}
590
591// SAFETY: `id<MTLComputePipelineState>` is immutable after creation and
592// thread-safe per Apple documentation.
593unsafe impl Send for ComputePipelineState {}
594unsafe impl Sync for ComputePipelineState {}
595
596impl Drop for ComputePipelineState {
597    fn drop(&mut self) {
598        if !self.ptr.is_null() {
599            unsafe { ffi::am_compute_pipeline_state_release(self.ptr) };
600            self.ptr = ptr::null_mut();
601        }
602    }
603}
604
605impl ComputePipelineState {
606    /// Raw `id<MTLComputePipelineState>` pointer.
607    #[must_use]
608    pub const fn as_ptr(&self) -> *mut c_void {
609        self.ptr
610    }
611
612    pub(crate) const unsafe fn from_retained_ptr(ptr: *mut c_void) -> Self {
613        Self { ptr }
614    }
615}
616
617// ---- Buffer ----
618
619/// Apple's `id<MTLBuffer>` — a GPU-visible byte buffer.
620pub struct MetalBuffer {
621    ptr: *mut c_void,
622}
623
624// SAFETY: `id<MTLBuffer>` is a GPU resource handle.  Reference-count manipulation
625// is atomic (ObjC ARC); concurrent reads of immutable state (length, GPU address)
626// are safe.  CPU-side writes via `contents()` are the caller's responsibility to
627// synchronize — the same rule that applies to any `*mut` slice.
628unsafe impl Send for MetalBuffer {}
629unsafe impl Sync for MetalBuffer {}
630
631impl Drop for MetalBuffer {
632    fn drop(&mut self) {
633        if !self.ptr.is_null() {
634            unsafe { ffi::am_buffer_release(self.ptr) };
635            self.ptr = ptr::null_mut();
636        }
637    }
638}
639
640impl MetalBuffer {
641    /// Buffer length in bytes.
642    #[must_use]
643    pub fn length(&self) -> usize {
644        unsafe { ffi::am_buffer_length(self.ptr) }
645    }
646
647    /// Raw `void *` to the buffer's CPU-visible bytes. `None` for
648    /// `MTLStorageMode::Private` (GPU-only) buffers.
649    #[must_use]
650    pub fn contents(&self) -> Option<*mut c_void> {
651        let p = unsafe { ffi::am_buffer_contents(self.ptr) };
652        if p.is_null() {
653            None
654        } else {
655            Some(p)
656        }
657    }
658
659    /// Copy `src` into this buffer at byte offset `0`. Returns the
660    /// number of bytes actually written.
661    #[must_use]
662    pub fn write_bytes(&self, src: &[u8]) -> usize {
663        let Some(dst) = self.contents() else {
664            return 0;
665        };
666        let n = core::cmp::min(src.len(), self.length());
667        unsafe { core::ptr::copy_nonoverlapping(src.as_ptr(), dst.cast::<u8>(), n) };
668        n
669    }
670
671    /// Raw `id<MTLBuffer>` pointer.
672    #[must_use]
673    pub const fn as_ptr(&self) -> *mut c_void {
674        self.ptr
675    }
676}
677
678// ---- Texture descriptor + texture ----
679
680/// Configuration for `MetalDevice::new_texture`.
681#[derive(Debug, Clone, Copy)]
682pub struct TextureDescriptor {
683/// Mirrors the `Metal` framework property for `pixel_format`.
684    pub pixel_format: usize,
685/// Mirrors the `Metal` framework property for `width`.
686    pub width: usize,
687/// Mirrors the `Metal` framework property for `height`.
688    pub height: usize,
689/// Mirrors the `Metal` framework property for `mipmapped`.
690    pub mipmapped: bool,
691/// Mirrors the `Metal` framework property for `usage`.
692    pub usage: usize,
693/// Mirrors the `Metal` framework property for `storage_mode`.
694    pub storage_mode: usize,
695}
696
697impl TextureDescriptor {
698    /// Sensible defaults for a shader-read+write 2D texture in shared storage.
699    #[must_use]
700    pub const fn new_2d(width: usize, height: usize, pixel_format: usize) -> Self {
701        Self {
702            pixel_format,
703            width,
704            height,
705            mipmapped: false,
706            usage: texture_usage::SHADER_READ | texture_usage::SHADER_WRITE,
707            storage_mode: storage_mode::SHARED,
708        }
709    }
710}
711
712/// Apple's `id<MTLTexture>` — a GPU-resident 2D image.
713pub struct MetalTexture {
714    ptr: *mut c_void,
715}
716
717// SAFETY: `id<MTLTexture>` is a GPU resource handle.  ObjC ARC operations are
718// atomic; descriptor queries are read-only and thread-safe.
719unsafe impl Send for MetalTexture {}
720unsafe impl Sync for MetalTexture {}
721
722impl Drop for MetalTexture {
723    fn drop(&mut self) {
724        if !self.ptr.is_null() {
725            unsafe { ffi::am_texture_release(self.ptr) };
726            self.ptr = ptr::null_mut();
727        }
728    }
729}
730
731impl MetalTexture {
732    /// Texture width in pixels.
733    #[must_use]
734    pub fn width(&self) -> usize {
735        unsafe { ffi::am_texture_width(self.ptr) }
736    }
737
738    /// Texture height in pixels.
739    #[must_use]
740    pub fn height(&self) -> usize {
741        unsafe { ffi::am_texture_height(self.ptr) }
742    }
743
744    /// Underlying `MTLPixelFormat` enum value — see [`pixel_format`].
745    #[must_use]
746    pub fn pixel_format(&self) -> usize {
747        unsafe { ffi::am_texture_pixel_format(self.ptr) }
748    }
749
750    /// Raw `id<MTLTexture>` pointer.
751    #[must_use]
752    pub const fn as_ptr(&self) -> *mut c_void {
753        self.ptr
754    }
755
756    /// Wrap a raw `id<MTLTexture>` pointer. Pointer is taken without
757    /// retain.
758    ///
759    /// # Safety
760    ///
761    /// `ptr` must be a valid `id<MTLTexture>` whose ownership the
762    /// caller is transferring.
763    #[must_use]
764    pub const unsafe fn from_raw(ptr: *mut c_void) -> Self {
765        Self { ptr }
766    }
767}
768
769impl MetalDevice {
770    pub(crate) const unsafe fn from_retained_ptr(ptr: *mut c_void) -> Self {
771        Self {
772            ptr,
773            drop_on_release: true,
774        }
775    }
776}
777
778impl CommandQueue {
779    pub(crate) const unsafe fn from_retained_ptr(ptr: *mut c_void) -> Self {
780        Self { ptr }
781    }
782}
783
784impl CommandBuffer {
785    pub(crate) const unsafe fn from_retained_ptr(ptr: *mut c_void) -> Self {
786        Self { ptr }
787    }
788}
789
790impl MetalBuffer {
791    pub(crate) const unsafe fn from_retained_ptr(ptr: *mut c_void) -> Self {
792        Self { ptr }
793    }
794}
795
796// ---- IOSurface extension ----
797
798#[cfg(feature = "iosurface")]
799#[cfg_attr(docsrs, doc(cfg(feature = "iosurface")))]
800mod iosurface_ext {
801    use super::{ffi, pixel_format, MetalDevice, MetalTexture};
802    use apple_cf::iosurface::IOSurface;
803    use core::ffi::c_void;
804
805    /// Add Metal interop methods to [`IOSurface`].
806    pub trait IOSurfaceMetalExt {
807        /// Wrap the given plane of this `IOSurface` as a zero-copy
808        /// [`MetalTexture`] on the given device.
809        fn create_metal_texture(
810            &self,
811            device: &MetalDevice,
812            plane_index: usize,
813        ) -> Option<MetalTexture>;
814    }
815
816    impl IOSurfaceMetalExt for IOSurface {
817        fn create_metal_texture(
818            &self,
819            device: &MetalDevice,
820            plane_index: usize,
821        ) -> Option<MetalTexture> {
822            let format = pixel_format_for_fourcc(self.pixel_format(), plane_index)?;
823            let (width, height) = if plane_index == 0 {
824                (self.width(), self.height())
825            } else {
826                (self.width() / 2, self.height() / 2)
827            };
828            let p = unsafe {
829                ffi::am_device_new_texture_from_iosurface(
830                    device.as_ptr(),
831                    self.as_ptr().cast::<c_void>(),
832                    plane_index,
833                    format,
834                    width,
835                    height,
836                )
837            };
838            if p.is_null() {
839                None
840            } else {
841                Some(unsafe { MetalTexture::from_raw(p) })
842            }
843        }
844    }
845
846    fn pixel_format_for_fourcc(fourcc: u32, plane_index: usize) -> Option<usize> {
847        const BGRA: u32 = u32::from_be_bytes(*b"BGRA");
848        const L10R: u32 = u32::from_be_bytes(*b"l10r");
849        const YUV420V: u32 = u32::from_be_bytes(*b"420v");
850        const YUV420F: u32 = u32::from_be_bytes(*b"420f");
851
852        match (fourcc, plane_index) {
853            (BGRA, 0) => Some(pixel_format::BGRA8UNORM),
854            (L10R, 0) => Some(pixel_format::BGRA10_XR),
855            (YUV420V | YUV420F, 0) => Some(pixel_format::R8UNORM),
856            (YUV420V | YUV420F, 1) => Some(pixel_format::RG8UNORM),
857            _ => None,
858        }
859    }
860}
861
862/// Re-exports the `Metal` framework surface for this item.
863#[cfg(feature = "iosurface")]
864pub use iosurface_ext::IOSurfaceMetalExt;
865
866/// True if `fourcc` identifies a YCbCr biplanar (`Y` + `CbCr`) format.
867#[must_use]
868pub const fn is_ycbcr_biplanar(fourcc: u32) -> bool {
869    const YUV420V: u32 = u32::from_be_bytes(*b"420v");
870    const YUV420F: u32 = u32::from_be_bytes(*b"420f");
871    matches!(fourcc, YUV420V | YUV420F)
872}