Skip to main content

apple_metal/
advanced.rs

1use crate::{
2    ffi,
3    util::{c_string, take_optional_string, take_string},
4    CommandQueue, ComputePipelineState, MetalBuffer, MetalDevice, MetalFunction, MetalTexture,
5    TextureDescriptor,
6};
7use core::ffi::c_void;
8use core::ops::Range;
9use std::path::Path;
10
11macro_rules! opaque_handle {
12    ($(#[$meta:meta])* pub struct $name:ident;) => {
13        $(#[$meta])*
14/// Mirrors the `Metal` framework counterpart for this type.
15        pub struct $name {
16            ptr: *mut c_void,
17        }
18
19        // SAFETY: Metal ObjC objects use atomic reference counting and are safe
20        // to move across threads.  All `&self` methods on these types either read
21        // immutable state or call ObjC methods documented as thread-safe by Apple.
22        unsafe impl Send for $name {}
23        unsafe impl Sync for $name {}
24
25        impl Drop for $name {
26            fn drop(&mut self) {
27                if !self.ptr.is_null() {
28                    // SAFETY: `ptr` is a non-null, +1-retained ObjC handle
29                    // exclusively owned by this struct.  Setting it to null
30                    // immediately prevents any subsequent release.
31                    unsafe { ffi::am_object_release(self.ptr) };
32                    self.ptr = core::ptr::null_mut();
33                }
34            }
35        }
36
37        impl $name {
38/// Mirrors the `Metal` framework constant `fn`.
39            #[must_use]
40            pub const fn as_ptr(&self) -> *mut c_void {
41                self.ptr
42            }
43
44            fn wrap(ptr: *mut c_void) -> Option<Self> {
45                if ptr.is_null() {
46                    None
47                } else {
48                    Some(Self { ptr })
49                }
50            }
51        }
52    };
53}
54
55/// `MTLIndirectCommandType` bit values.
56pub mod indirect_command_type {
57    /// Mirrors the `Metal` framework constant `DRAW`.
58    pub const DRAW: usize = 1 << 0;
59    /// Mirrors the `Metal` framework constant `DRAW_INDEXED`.
60    pub const DRAW_INDEXED: usize = 1 << 1;
61    /// Mirrors the `Metal` framework constant `CONCURRENT_DISPATCH`.
62    pub const CONCURRENT_DISPATCH: usize = 1 << 5;
63    /// Mirrors the `Metal` framework constant `CONCURRENT_DISPATCH_THREADS`.
64    pub const CONCURRENT_DISPATCH_THREADS: usize = 1 << 6;
65}
66
67/// `MTLCounterSamplingPoint` enum values.
68pub mod counter_sampling_point {
69    /// Mirrors the `Metal` framework constant `AT_STAGE_BOUNDARY`.
70    pub const AT_STAGE_BOUNDARY: usize = 0;
71    /// Mirrors the `Metal` framework constant `AT_DRAW_BOUNDARY`.
72    pub const AT_DRAW_BOUNDARY: usize = 1;
73    /// Mirrors the `Metal` framework constant `AT_DISPATCH_BOUNDARY`.
74    pub const AT_DISPATCH_BOUNDARY: usize = 2;
75    /// Mirrors the `Metal` framework constant `AT_TILE_DISPATCH_BOUNDARY`.
76    pub const AT_TILE_DISPATCH_BOUNDARY: usize = 3;
77    /// Mirrors the `Metal` framework constant `AT_BLIT_BOUNDARY`.
78    pub const AT_BLIT_BOUNDARY: usize = 4;
79}
80
81/// `MTLLogLevel` enum values.
82pub mod log_level {
83    /// Mirrors the `Metal` framework constant `UNDEFINED`.
84    pub const UNDEFINED: usize = 0;
85    /// Mirrors the `Metal` framework constant `DEBUG`.
86    pub const DEBUG: usize = 1;
87    /// Mirrors the `Metal` framework constant `INFO`.
88    pub const INFO: usize = 2;
89    /// Mirrors the `Metal` framework constant `NOTICE`.
90    pub const NOTICE: usize = 3;
91    /// Mirrors the `Metal` framework constant `ERROR`.
92    pub const ERROR: usize = 4;
93    /// Mirrors the `Metal` framework constant `FAULT`.
94    pub const FAULT: usize = 5;
95}
96
97/// `MTLPurgeableState` enum values.
98pub mod purgeable_state {
99    /// Mirrors the `Metal` framework constant `KEEP_CURRENT`.
100    pub const KEEP_CURRENT: usize = 1;
101    /// Mirrors the `Metal` framework constant `NON_VOLATILE`.
102    pub const NON_VOLATILE: usize = 2;
103    /// Mirrors the `Metal` framework constant `VOLATILE`.
104    pub const VOLATILE: usize = 3;
105    /// Mirrors the `Metal` framework constant `EMPTY`.
106    pub const EMPTY: usize = 4;
107}
108
109/// `MTLCaptureDestination` enum values.
110pub mod capture_destination {
111    /// Mirrors the `Metal` framework constant `DEVELOPER_TOOLS`.
112    pub const DEVELOPER_TOOLS: usize = 1;
113    /// Mirrors the `Metal` framework constant `GPU_TRACE_DOCUMENT`.
114    pub const GPU_TRACE_DOCUMENT: usize = 2;
115}
116
117/// `MTLIntersectionFunctionSignature` bit values.
118pub mod intersection_function_signature {
119    /// Mirrors the `Metal` framework constant `NONE`.
120    pub const NONE: usize = 0;
121    /// Mirrors the `Metal` framework constant `INSTANCING`.
122    pub const INSTANCING: usize = 1 << 0;
123    /// Mirrors the `Metal` framework constant `TRIANGLE_DATA`.
124    pub const TRIANGLE_DATA: usize = 1 << 1;
125    /// Mirrors the `Metal` framework constant `WORLD_SPACE_DATA`.
126    pub const WORLD_SPACE_DATA: usize = 1 << 2;
127}
128
129opaque_handle!(
130    /// Apple's `id<MTLHeap>` — shared GPU allocation arena.
131    pub struct Heap;
132);
133opaque_handle!(
134    /// Apple's `id<MTLEvent>` backed by `MTLSharedEvent`.
135    pub struct Event;
136);
137opaque_handle!(
138    /// Apple's `id<MTLFence>` — intra-queue synchronization primitive.
139    pub struct Fence;
140);
141opaque_handle!(
142    /// Apple's `id<MTLDynamicLibrary>` — device-linked Metal code bundle.
143    pub struct DynamicLibrary;
144);
145opaque_handle!(
146    /// Apple's `id<MTLBinaryArchive>` — persistent pipeline cache.
147    pub struct BinaryArchive;
148);
149opaque_handle!(
150    /// Apple's `id<MTLArgumentEncoder>` — writes argument-buffer bindings.
151    pub struct ArgumentEncoder;
152);
153opaque_handle!(
154    /// Apple's `id<MTLIndirectCommandBuffer>` — stores GPU-executable commands.
155    pub struct IndirectCommandBuffer;
156);
157opaque_handle!(
158    /// Apple's `id<MTLAccelerationStructure>` — storage for ray tracing data.
159    pub struct AccelerationStructure;
160);
161opaque_handle!(
162    /// Apple's `id<MTLIntersectionFunctionTable>` — table of ray intersection functions.
163    pub struct IntersectionFunctionTable;
164);
165opaque_handle!(
166    /// Apple's `id<MTLVisibleFunctionTable>` — table of callable function handles.
167    pub struct VisibleFunctionTable;
168);
169opaque_handle!(
170    /// Apple's `id<MTLCounterSampleBuffer>` — storage for GPU counter samples.
171    pub struct CounterSampleBuffer;
172);
173opaque_handle!(
174    /// Apple's `id<MTLLogState>` — shader logging configuration.
175    pub struct LogState;
176);
177opaque_handle!(
178    /// Apple's `id<MTLResidencySet>` — explicit residency tracking set.
179    pub struct ResidencySet;
180);
181opaque_handle!(
182    /// Apple's `MTLCaptureManager` singleton.
183    pub struct CaptureManager;
184);
185opaque_handle!(
186    /// Apple's `id<MTLCaptureScope>` — named capture region.
187    pub struct CaptureScope;
188);
189
190impl MetalDevice {
191    /// Human-readable device name.
192    #[must_use]
193    pub fn name(&self) -> String {
194        unsafe { take_string(ffi::am_device_name(self.as_ptr())) }
195    }
196
197    /// Global `IORegistry` identifier for the device.
198    #[must_use]
199    pub fn registry_id(&self) -> u64 {
200        unsafe { ffi::am_device_registry_id(self.as_ptr()) }
201    }
202
203    /// Whether this device supports Metal dynamic libraries.
204    #[must_use]
205    pub fn supports_dynamic_libraries(&self) -> bool {
206        unsafe { ffi::am_device_supports_dynamic_libraries(self.as_ptr()) }
207    }
208
209    /// Whether this device supports render-stage dynamic libraries.
210    #[must_use]
211    pub fn supports_render_dynamic_libraries(&self) -> bool {
212        unsafe { ffi::am_device_supports_render_dynamic_libraries(self.as_ptr()) }
213    }
214
215    /// Whether this device supports compute ray tracing.
216    #[must_use]
217    pub fn supports_raytracing(&self) -> bool {
218        unsafe { ffi::am_device_supports_raytracing(self.as_ptr()) }
219    }
220
221    /// Query support for a hardware counter sampling point.
222    #[must_use]
223    pub fn supports_counter_sampling(&self, sampling_point: usize) -> bool {
224        unsafe { ffi::am_device_supports_counter_sampling(self.as_ptr(), sampling_point) }
225    }
226
227    /// Return the names of all counter sets exposed by this device.
228    #[must_use]
229    pub fn counter_set_names(&self) -> Vec<String> {
230        let count = unsafe { ffi::am_device_counter_set_count(self.as_ptr()) };
231        (0..count)
232            .filter_map(|index| unsafe {
233                take_optional_string(ffi::am_device_counter_set_name_at(self.as_ptr(), index))
234            })
235            .collect()
236    }
237
238    /// Create a command queue with an explicit maximum in-flight command-buffer count.
239    #[must_use]
240    pub fn new_command_queue_with_max_command_buffer_count(
241        &self,
242        max_command_buffer_count: usize,
243    ) -> Option<CommandQueue> {
244        let ptr = unsafe {
245            ffi::am_device_new_command_queue_with_max_command_buffer_count(
246                self.as_ptr(),
247                max_command_buffer_count,
248            )
249        };
250        if ptr.is_null() {
251            None
252        } else {
253            Some(unsafe { CommandQueue::from_retained_ptr(ptr) })
254        }
255    }
256
257    /// Create a command queue that uses `log_state` for shader logging.
258    #[must_use]
259    pub fn new_command_queue_with_log_state(
260        &self,
261        max_command_buffer_count: usize,
262        log_state: &LogState,
263    ) -> Option<CommandQueue> {
264        let ptr = unsafe {
265            ffi::am_device_new_command_queue_with_log_state(
266                self.as_ptr(),
267                max_command_buffer_count,
268                log_state.as_ptr(),
269            )
270        };
271        if ptr.is_null() {
272            None
273        } else {
274            Some(unsafe { CommandQueue::from_retained_ptr(ptr) })
275        }
276    }
277
278    /// Create a heap with the requested size and storage mode.
279    #[must_use]
280    pub fn new_heap(&self, size: usize, storage_mode: usize) -> Option<Heap> {
281        Heap::wrap(unsafe { ffi::am_device_new_heap(self.as_ptr(), size, storage_mode) })
282    }
283
284    /// Create a new fence.
285    #[must_use]
286    pub fn new_fence(&self) -> Option<Fence> {
287        Fence::wrap(unsafe { ffi::am_device_new_fence(self.as_ptr()) })
288    }
289
290    /// Create a new shared event.
291    #[must_use]
292    pub fn new_shared_event(&self) -> Option<Event> {
293        Event::wrap(unsafe { ffi::am_device_new_shared_event(self.as_ptr()) })
294    }
295
296    /// Compile `source` as a Metal dynamic library with the given `install_name`.
297    ///
298    /// # Errors
299    ///
300    /// Returns Metal's localized compiler or linker error on failure.
301    pub fn new_dynamic_library_with_source(
302        &self,
303        source: &str,
304        install_name: &str,
305    ) -> Result<DynamicLibrary, String> {
306        let source = c_string(source)?;
307        let install_name = c_string(install_name)?;
308        let mut err: *mut core::ffi::c_char = core::ptr::null_mut();
309        let ptr = unsafe {
310            ffi::am_device_new_dynamic_library_with_source(
311                self.as_ptr(),
312                source.as_ptr(),
313                install_name.as_ptr(),
314                &mut err,
315            )
316        };
317        DynamicLibrary::wrap(ptr).ok_or_else(|| unsafe {
318            take_optional_string(err)
319                .unwrap_or_else(|| "MTLDevice.makeDynamicLibrary(source:) returned nil".to_string())
320        })
321    }
322
323    /// Load a serialized Metal dynamic library from `path`.
324    ///
325    /// # Errors
326    ///
327    /// Returns Metal's localized file or linker error on failure.
328    pub fn load_dynamic_library(&self, path: &Path) -> Result<DynamicLibrary, String> {
329        let path = c_string(path.to_string_lossy().as_ref())?;
330        let mut err: *mut core::ffi::c_char = core::ptr::null_mut();
331        let ptr = unsafe {
332            ffi::am_device_new_dynamic_library_with_url(self.as_ptr(), path.as_ptr(), &mut err)
333        };
334        DynamicLibrary::wrap(ptr).ok_or_else(|| unsafe {
335            take_optional_string(err)
336                .unwrap_or_else(|| "MTLDevice.makeDynamicLibrary(URL:) returned nil".to_string())
337        })
338    }
339
340    /// Create a binary archive, optionally loading it from `path` first.
341    ///
342    /// # Errors
343    ///
344    /// Returns Metal's localized archive creation error on failure.
345    pub fn new_binary_archive(&self, path: Option<&Path>) -> Result<BinaryArchive, String> {
346        let owned_path = path
347            .map(|path| c_string(path.to_string_lossy().as_ref()))
348            .transpose()?;
349        let raw_path = owned_path
350            .as_ref()
351            .map_or(core::ptr::null(), |path| path.as_c_str().as_ptr());
352        let mut err: *mut core::ffi::c_char = core::ptr::null_mut();
353        let ptr = unsafe { ffi::am_device_new_binary_archive(self.as_ptr(), raw_path, &mut err) };
354        BinaryArchive::wrap(ptr).ok_or_else(|| unsafe {
355            take_optional_string(err)
356                .unwrap_or_else(|| "MTLDevice.makeBinaryArchive returned nil".to_string())
357        })
358    }
359
360    /// Create a new indirect command buffer.
361    #[must_use]
362    pub fn new_indirect_command_buffer(
363        &self,
364        command_types: usize,
365        max_command_count: usize,
366        max_vertex_buffer_bind_count: usize,
367        max_fragment_buffer_bind_count: usize,
368        max_kernel_buffer_bind_count: usize,
369        options: usize,
370    ) -> Option<IndirectCommandBuffer> {
371        IndirectCommandBuffer::wrap(unsafe {
372            ffi::am_device_new_indirect_command_buffer(
373                self.as_ptr(),
374                command_types,
375                max_command_count,
376                max_vertex_buffer_bind_count,
377                max_fragment_buffer_bind_count,
378                max_kernel_buffer_bind_count,
379                options,
380            )
381        })
382    }
383
384    /// Allocate storage for a ray-tracing acceleration structure.
385    #[must_use]
386    pub fn new_acceleration_structure_with_size(
387        &self,
388        size: usize,
389    ) -> Option<AccelerationStructure> {
390        AccelerationStructure::wrap(unsafe {
391            ffi::am_device_new_acceleration_structure_with_size(self.as_ptr(), size)
392        })
393    }
394
395    /// Create a counter sample buffer for the named counter set.
396    ///
397    /// # Errors
398    ///
399    /// Returns Metal's localized counter-sample-buffer error on failure.
400    pub fn new_counter_sample_buffer(
401        &self,
402        counter_set_name: &str,
403        sample_count: usize,
404        storage_mode: usize,
405        label: Option<&str>,
406    ) -> Result<CounterSampleBuffer, String> {
407        let counter_set_name = c_string(counter_set_name)?;
408        let label = label.map(c_string).transpose()?;
409        let raw_label = label
410            .as_ref()
411            .map_or(core::ptr::null(), |label| label.as_c_str().as_ptr());
412        let mut err: *mut core::ffi::c_char = core::ptr::null_mut();
413        let ptr = unsafe {
414            ffi::am_device_new_counter_sample_buffer(
415                self.as_ptr(),
416                counter_set_name.as_ptr(),
417                sample_count,
418                storage_mode,
419                raw_label,
420                &mut err,
421            )
422        };
423        CounterSampleBuffer::wrap(ptr).ok_or_else(|| unsafe {
424            take_optional_string(err)
425                .unwrap_or_else(|| "MTLDevice.makeCounterSampleBuffer returned nil".to_string())
426        })
427    }
428
429    /// Create a shader log state.
430    ///
431    /// # Errors
432    ///
433    /// Returns Metal's localized log-state creation error on failure.
434    pub fn new_log_state(&self, level: usize, buffer_size: isize) -> Result<LogState, String> {
435        let mut err: *mut core::ffi::c_char = core::ptr::null_mut();
436        let ptr =
437            unsafe { ffi::am_device_new_log_state(self.as_ptr(), level, buffer_size, &mut err) };
438        LogState::wrap(ptr).ok_or_else(|| unsafe {
439            take_optional_string(err)
440                .unwrap_or_else(|| "MTLDevice.makeLogState returned nil".to_string())
441        })
442    }
443
444    /// Create a residency set.
445    ///
446    /// # Errors
447    ///
448    /// Returns Metal's localized residency-set creation error on failure.
449    pub fn new_residency_set(
450        &self,
451        label: Option<&str>,
452        initial_capacity: usize,
453    ) -> Result<ResidencySet, String> {
454        let label = label.map(c_string).transpose()?;
455        let raw_label = label
456            .as_ref()
457            .map_or(core::ptr::null(), |label| label.as_c_str().as_ptr());
458        let mut err: *mut core::ffi::c_char = core::ptr::null_mut();
459        let ptr = unsafe {
460            ffi::am_device_new_residency_set(self.as_ptr(), raw_label, initial_capacity, &mut err)
461        };
462        ResidencySet::wrap(ptr).ok_or_else(|| unsafe {
463            take_optional_string(err)
464                .unwrap_or_else(|| "MTLDevice.makeResidencySet returned nil".to_string())
465        })
466    }
467}
468
469impl CommandQueue {
470    /// Add `residency_set` to the queue-wide residency list.
471    pub fn add_residency_set(&self, residency_set: &ResidencySet) {
472        unsafe { ffi::am_command_queue_add_residency_set(self.as_ptr(), residency_set.as_ptr()) };
473    }
474
475    /// Remove `residency_set` from the queue-wide residency list.
476    pub fn remove_residency_set(&self, residency_set: &ResidencySet) {
477        unsafe {
478            ffi::am_command_queue_remove_residency_set(self.as_ptr(), residency_set.as_ptr());
479        };
480    }
481}
482
483impl MetalBuffer {
484    /// Notify Metal that CPU writes modified the given managed-memory byte range.
485    pub fn did_modify_range(&self, range: Range<usize>) {
486        unsafe {
487            ffi::am_buffer_did_modify_range(
488                self.as_ptr(),
489                range.start,
490                range.end.saturating_sub(range.start),
491            );
492        };
493    }
494
495    /// Create a 2D texture view that shares this buffer's storage.
496    #[must_use]
497    pub fn new_texture_view_2d(
498        &self,
499        pixel_format: usize,
500        width: usize,
501        height: usize,
502        bytes_per_row: usize,
503        offset: usize,
504    ) -> Option<MetalTexture> {
505        let ptr = unsafe {
506            ffi::am_buffer_new_texture_view_2d(
507                self.as_ptr(),
508                pixel_format,
509                width,
510                height,
511                bytes_per_row,
512                offset,
513            )
514        };
515        if ptr.is_null() {
516            None
517        } else {
518            Some(unsafe { MetalTexture::from_raw(ptr) })
519        }
520    }
521}
522
523impl MetalTexture {
524    /// Texture depth in pixels.
525    #[must_use]
526    pub fn depth(&self) -> usize {
527        unsafe { ffi::am_texture_depth(self.as_ptr()) }
528    }
529
530    /// Number of mipmap levels.
531    #[must_use]
532    pub fn mipmap_level_count(&self) -> usize {
533        unsafe { ffi::am_texture_mipmap_level_count(self.as_ptr()) }
534    }
535
536    /// Number of array slices.
537    #[must_use]
538    pub fn array_length(&self) -> usize {
539        unsafe { ffi::am_texture_array_length(self.as_ptr()) }
540    }
541
542    /// `MTLTextureUsage` bitmask.
543    #[must_use]
544    pub fn usage(&self) -> usize {
545        unsafe { ffi::am_texture_usage(self.as_ptr()) }
546    }
547
548    /// `MTLStorageMode` enum value.
549    #[must_use]
550    pub fn storage_mode(&self) -> usize {
551        unsafe { ffi::am_texture_storage_mode(self.as_ptr()) }
552    }
553
554    /// Upload bytes into a 2D region of the texture.
555    #[must_use]
556    pub fn replace_region_2d(
557        &self,
558        bytes: &[u8],
559        bytes_per_row: usize,
560        origin: (usize, usize),
561        size: (usize, usize),
562        mipmap_level: usize,
563    ) -> bool {
564        let required = bytes_per_row.saturating_mul(size.1);
565        if bytes.len() < required {
566            return false;
567        }
568        unsafe {
569            ffi::am_texture_replace_region_2d(
570                self.as_ptr(),
571                origin.0,
572                origin.1,
573                size.0,
574                size.1,
575                mipmap_level,
576                bytes.as_ptr(),
577                bytes_per_row,
578            )
579        }
580    }
581
582    /// Read bytes from a 2D region of the texture into `out`.
583    #[must_use]
584    pub fn read_bytes_2d(
585        &self,
586        out: &mut [u8],
587        bytes_per_row: usize,
588        origin: (usize, usize),
589        size: (usize, usize),
590        mipmap_level: usize,
591    ) -> bool {
592        unsafe {
593            ffi::am_texture_get_bytes_2d(
594                self.as_ptr(),
595                out.as_mut_ptr(),
596                out.len(),
597                bytes_per_row,
598                origin.0,
599                origin.1,
600                size.0,
601                size.1,
602                mipmap_level,
603            )
604        }
605    }
606
607    /// Create a texture view with a compatible pixel format.
608    #[must_use]
609    pub fn new_view(&self, pixel_format: usize) -> Option<Self> {
610        let ptr = unsafe { ffi::am_texture_new_view(self.as_ptr(), pixel_format) };
611        if ptr.is_null() {
612            None
613        } else {
614            Some(unsafe { Self::from_raw(ptr) })
615        }
616    }
617}
618
619impl ComputePipelineState {
620    /// Thread execution width for this compute pipeline.
621    #[must_use]
622    pub fn thread_execution_width(&self) -> usize {
623        unsafe { ffi::am_compute_pipeline_state_thread_execution_width(self.as_ptr()) }
624    }
625
626    /// Maximum threads per threadgroup.
627    #[must_use]
628    pub fn max_total_threads_per_threadgroup(&self) -> usize {
629        unsafe { ffi::am_compute_pipeline_state_max_total_threads_per_threadgroup(self.as_ptr()) }
630    }
631
632    /// Allocate a visible function table for this pipeline.
633    #[must_use]
634    pub fn new_visible_function_table(
635        &self,
636        function_count: usize,
637    ) -> Option<VisibleFunctionTable> {
638        VisibleFunctionTable::wrap(unsafe {
639            ffi::am_compute_pipeline_state_new_visible_function_table(self.as_ptr(), function_count)
640        })
641    }
642
643    /// Allocate an intersection function table for this pipeline.
644    #[must_use]
645    pub fn new_intersection_function_table(
646        &self,
647        function_count: usize,
648    ) -> Option<IntersectionFunctionTable> {
649        IntersectionFunctionTable::wrap(unsafe {
650            ffi::am_compute_pipeline_state_new_intersection_function_table(
651                self.as_ptr(),
652                function_count,
653            )
654        })
655    }
656}
657
658impl MetalFunction {
659    /// Create an argument encoder for the argument buffer bound at `buffer_index`.
660    #[must_use]
661    pub fn new_argument_encoder(&self, buffer_index: usize) -> Option<ArgumentEncoder> {
662        ArgumentEncoder::wrap(unsafe {
663            ffi::am_function_new_argument_encoder(self.as_ptr(), buffer_index)
664        })
665    }
666}
667
668impl Heap {
669    /// Heap size in bytes.
670    #[must_use]
671    pub fn size(&self) -> usize {
672        unsafe { ffi::am_heap_size(self.as_ptr()) }
673    }
674
675    /// Bytes currently used by heap-backed resources.
676    #[must_use]
677    pub fn used_size(&self) -> usize {
678        unsafe { ffi::am_heap_used_size(self.as_ptr()) }
679    }
680
681    /// Current heap allocation size in bytes.
682    #[must_use]
683    pub fn current_allocated_size(&self) -> usize {
684        unsafe { ffi::am_heap_current_allocated_size(self.as_ptr()) }
685    }
686
687    /// Largest allocatable block in the heap for the given alignment.
688    #[must_use]
689    pub fn max_available_size(&self, alignment: usize) -> usize {
690        unsafe { ffi::am_heap_max_available_size(self.as_ptr(), alignment) }
691    }
692
693    /// Allocate a buffer from this heap.
694    #[must_use]
695    pub fn new_buffer(&self, length: usize, options: usize) -> Option<MetalBuffer> {
696        let ptr = unsafe { ffi::am_heap_new_buffer(self.as_ptr(), length, options) };
697        if ptr.is_null() {
698            None
699        } else {
700            Some(unsafe { MetalBuffer::from_retained_ptr(ptr) })
701        }
702    }
703
704    /// Allocate a texture from this heap.
705    #[must_use]
706    pub fn new_texture(&self, descriptor: TextureDescriptor) -> Option<MetalTexture> {
707        let ptr = unsafe {
708            ffi::am_heap_new_texture_2d(
709                self.as_ptr(),
710                descriptor.pixel_format,
711                descriptor.width,
712                descriptor.height,
713                descriptor.mipmapped,
714                descriptor.usage,
715                descriptor.storage_mode,
716            )
717        };
718        if ptr.is_null() {
719            None
720        } else {
721            Some(unsafe { MetalTexture::from_raw(ptr) })
722        }
723    }
724
725    /// Allocate acceleration-structure storage from this heap.
726    #[must_use]
727    pub fn new_acceleration_structure_with_size(
728        &self,
729        size: usize,
730    ) -> Option<AccelerationStructure> {
731        AccelerationStructure::wrap(unsafe {
732            ffi::am_heap_new_acceleration_structure_with_size(self.as_ptr(), size)
733        })
734    }
735
736    /// Set the heap purgeable state.
737    #[must_use]
738    pub fn set_purgeable_state(&self, state: usize) -> usize {
739        unsafe { ffi::am_heap_set_purgeable_state(self.as_ptr(), state) }
740    }
741}
742
743impl Event {
744    /// Current `signaledValue`.
745    #[must_use]
746    pub fn signaled_value(&self) -> u64 {
747        unsafe { ffi::am_event_signaled_value(self.as_ptr()) }
748    }
749
750    /// Update the event's `signaledValue`.
751    pub fn set_signaled_value(&self, value: u64) {
752        unsafe { ffi::am_event_set_signaled_value(self.as_ptr(), value) };
753    }
754
755    /// Wait until the event reaches at least `value`.
756    #[must_use]
757    pub fn wait_until_signaled_value(&self, value: u64, timeout_ms: u64) -> bool {
758        unsafe { ffi::am_event_wait_until_signaled_value(self.as_ptr(), value, timeout_ms) }
759    }
760}
761
762impl DynamicLibrary {
763    /// Install name embedded into the dynamic library.
764    #[must_use]
765    pub fn install_name(&self) -> String {
766        unsafe { take_string(ffi::am_dynamic_library_install_name(self.as_ptr())) }
767    }
768
769    /// Serialize this dynamic library to `path`.
770    ///
771    /// # Errors
772    ///
773    /// Returns Metal's localized serialization error on failure.
774    pub fn serialize_to_file(&self, path: &Path) -> Result<(), String> {
775        let path = c_string(path.to_string_lossy().as_ref())?;
776        let mut err: *mut core::ffi::c_char = core::ptr::null_mut();
777        let ok = unsafe {
778            ffi::am_dynamic_library_serialize_to_url(self.as_ptr(), path.as_ptr(), &mut err)
779        };
780        if ok {
781            Ok(())
782        } else {
783            Err(unsafe {
784                take_optional_string(err)
785                    .unwrap_or_else(|| "MTLDynamicLibrary.serialize(to:) failed".to_string())
786            })
787        }
788    }
789}
790
791impl BinaryArchive {
792    /// Add a compute pipeline descriptor built from `function` to the archive.
793    ///
794    /// # Errors
795    ///
796    /// Returns Metal's localized archive error on failure.
797    pub fn add_compute_function(&self, function: &MetalFunction) -> Result<(), String> {
798        let mut err: *mut core::ffi::c_char = core::ptr::null_mut();
799        let ok = unsafe {
800            ffi::am_binary_archive_add_compute_function(self.as_ptr(), function.as_ptr(), &mut err)
801        };
802        if ok {
803            Ok(())
804        } else {
805            Err(unsafe {
806                take_optional_string(err).unwrap_or_else(|| {
807                    "MTLBinaryArchive.addComputePipelineFunctions failed".to_string()
808                })
809            })
810        }
811    }
812
813    /// Add a render pipeline descriptor built from `vertex` and `fragment` to the archive.
814    ///
815    /// # Errors
816    ///
817    /// Returns Metal's localized archive error on failure.
818    pub fn add_render_functions(
819        &self,
820        vertex: &MetalFunction,
821        fragment: &MetalFunction,
822        color_pixel_format: usize,
823        sample_count: usize,
824    ) -> Result<(), String> {
825        let mut err: *mut core::ffi::c_char = core::ptr::null_mut();
826        let ok = unsafe {
827            ffi::am_binary_archive_add_render_functions(
828                self.as_ptr(),
829                vertex.as_ptr(),
830                fragment.as_ptr(),
831                color_pixel_format,
832                sample_count,
833                &mut err,
834            )
835        };
836        if ok {
837            Ok(())
838        } else {
839            Err(unsafe {
840                take_optional_string(err).unwrap_or_else(|| {
841                    "MTLBinaryArchive.addRenderPipelineFunctions failed".to_string()
842                })
843            })
844        }
845    }
846
847    /// Serialize the archive to `path`.
848    ///
849    /// # Errors
850    ///
851    /// Returns Metal's localized serialization error on failure.
852    pub fn serialize_to_file(&self, path: &Path) -> Result<(), String> {
853        let path = c_string(path.to_string_lossy().as_ref())?;
854        let mut err: *mut core::ffi::c_char = core::ptr::null_mut();
855        let ok = unsafe {
856            ffi::am_binary_archive_serialize_to_url(self.as_ptr(), path.as_ptr(), &mut err)
857        };
858        if ok {
859            Ok(())
860        } else {
861            Err(unsafe {
862                take_optional_string(err)
863                    .unwrap_or_else(|| "MTLBinaryArchive.serialize(to:) failed".to_string())
864            })
865        }
866    }
867}
868
869impl ArgumentEncoder {
870    /// Number of bytes required to encode the argument layout.
871    #[must_use]
872    pub fn encoded_length(&self) -> usize {
873        unsafe { ffi::am_argument_encoder_encoded_length(self.as_ptr()) }
874    }
875
876    /// Required alignment for the encoded argument data.
877    #[must_use]
878    pub fn alignment(&self) -> usize {
879        unsafe { ffi::am_argument_encoder_alignment(self.as_ptr()) }
880    }
881
882    /// Set the destination argument buffer.
883    pub fn set_argument_buffer(&self, buffer: &MetalBuffer, offset: usize) {
884        unsafe {
885            ffi::am_argument_encoder_set_argument_buffer(self.as_ptr(), buffer.as_ptr(), offset);
886        };
887    }
888
889    /// Encode a buffer binding at `index`.
890    pub fn set_buffer(&self, buffer: &MetalBuffer, offset: usize, index: usize) {
891        unsafe {
892            ffi::am_argument_encoder_set_buffer(self.as_ptr(), buffer.as_ptr(), offset, index);
893        };
894    }
895
896    /// Encode a texture binding at `index`.
897    pub fn set_texture(&self, texture: &MetalTexture, index: usize) {
898        unsafe { ffi::am_argument_encoder_set_texture(self.as_ptr(), texture.as_ptr(), index) };
899    }
900}
901
902impl IndirectCommandBuffer {
903    /// Size of the indirect command buffer in bytes.
904    #[must_use]
905    pub fn size(&self) -> usize {
906        unsafe { ffi::am_indirect_command_buffer_size(self.as_ptr()) }
907    }
908
909    /// Reset commands in `range` back to empty state.
910    pub fn reset_range(&self, range: Range<usize>) {
911        unsafe {
912            ffi::am_indirect_command_buffer_reset_range(
913                self.as_ptr(),
914                range.start,
915                range.end.saturating_sub(range.start),
916            );
917        };
918    }
919}
920
921impl AccelerationStructure {
922    /// Allocated storage size in bytes.
923    #[must_use]
924    pub fn size(&self) -> usize {
925        unsafe { ffi::am_acceleration_structure_size(self.as_ptr()) }
926    }
927}
928
929impl IntersectionFunctionTable {
930    /// Populate `index` with the built-in opaque triangle intersection function.
931    pub fn set_opaque_triangle_intersection_function(&self, signature: usize, index: usize) {
932        unsafe {
933            ffi::am_intersection_function_table_set_opaque_triangle(
934                self.as_ptr(),
935                signature,
936                index,
937            );
938        };
939    }
940}
941
942impl CounterSampleBuffer {
943    /// Number of samples available in the buffer.
944    #[must_use]
945    pub fn sample_count(&self) -> usize {
946        unsafe { ffi::am_counter_sample_buffer_sample_count(self.as_ptr()) }
947    }
948
949    /// Resolve raw counter bytes for `range`.
950    #[must_use]
951    pub fn resolve_range(&self, range: Range<usize>) -> Option<Vec<u8>> {
952        let mut out_len = 0usize;
953        let ptr = unsafe {
954            ffi::am_counter_sample_buffer_resolve_range(
955                self.as_ptr(),
956                range.start,
957                range.end.saturating_sub(range.start),
958                &mut out_len,
959            )
960        };
961        if ptr.is_null() {
962            None
963        } else {
964            let bytes = unsafe { core::slice::from_raw_parts(ptr.cast::<u8>(), out_len) }.to_vec();
965            unsafe { libc::free(ptr.cast()) };
966            Some(bytes)
967        }
968    }
969}
970
971impl ResidencySet {
972    /// Add `buffer` to the set.
973    pub fn add_buffer(&self, buffer: &MetalBuffer) {
974        unsafe { ffi::am_residency_set_add_buffer(self.as_ptr(), buffer.as_ptr()) };
975    }
976
977    /// Add `texture` to the set.
978    pub fn add_texture(&self, texture: &MetalTexture) {
979        unsafe { ffi::am_residency_set_add_texture(self.as_ptr(), texture.as_ptr()) };
980    }
981
982    /// Add `heap` to the set.
983    pub fn add_heap(&self, heap: &Heap) {
984        unsafe { ffi::am_residency_set_add_heap(self.as_ptr(), heap.as_ptr()) };
985    }
986
987    /// Remove `buffer` from the set.
988    pub fn remove_buffer(&self, buffer: &MetalBuffer) {
989        unsafe { ffi::am_residency_set_remove_buffer(self.as_ptr(), buffer.as_ptr()) };
990    }
991
992    /// Remove `texture` from the set.
993    pub fn remove_texture(&self, texture: &MetalTexture) {
994        unsafe { ffi::am_residency_set_remove_texture(self.as_ptr(), texture.as_ptr()) };
995    }
996
997    /// Remove `heap` from the set.
998    pub fn remove_heap(&self, heap: &Heap) {
999        unsafe { ffi::am_residency_set_remove_heap(self.as_ptr(), heap.as_ptr()) };
1000    }
1001
1002    /// Remove all pending and committed allocations.
1003    pub fn remove_all_allocations(&self) {
1004        unsafe { ffi::am_residency_set_remove_all_allocations(self.as_ptr()) };
1005    }
1006
1007    /// Whether the set currently contains `buffer`.
1008    #[must_use]
1009    pub fn contains_buffer(&self, buffer: &MetalBuffer) -> bool {
1010        unsafe { ffi::am_residency_set_contains_buffer(self.as_ptr(), buffer.as_ptr()) }
1011    }
1012
1013    /// Whether the set currently contains `texture`.
1014    #[must_use]
1015    pub fn contains_texture(&self, texture: &MetalTexture) -> bool {
1016        unsafe { ffi::am_residency_set_contains_texture(self.as_ptr(), texture.as_ptr()) }
1017    }
1018
1019    /// Number of allocations in the set.
1020    #[must_use]
1021    pub fn allocation_count(&self) -> usize {
1022        unsafe { ffi::am_residency_set_allocation_count(self.as_ptr()) }
1023    }
1024
1025    /// Commit pending add/remove changes.
1026    pub fn commit(&self) {
1027        unsafe { ffi::am_residency_set_commit(self.as_ptr()) };
1028    }
1029
1030    /// Request residency for all committed allocations.
1031    pub fn request_residency(&self) {
1032        unsafe { ffi::am_residency_set_request_residency(self.as_ptr()) };
1033    }
1034
1035    /// End residency for all committed allocations.
1036    pub fn end_residency(&self) {
1037        unsafe { ffi::am_residency_set_end_residency(self.as_ptr()) };
1038    }
1039}
1040
1041impl CaptureManager {
1042    /// Retrieve the process-global capture manager.
1043    #[must_use]
1044    pub fn shared() -> Option<Self> {
1045        Self::wrap(unsafe { ffi::am_capture_manager_shared() })
1046    }
1047
1048    /// Query whether the given capture destination is supported.
1049    #[must_use]
1050    pub fn supports_destination(&self, destination: usize) -> bool {
1051        unsafe { ffi::am_capture_manager_supports_destination(self.as_ptr(), destination) }
1052    }
1053
1054    /// Whether a capture is currently in progress.
1055    #[must_use]
1056    pub fn is_capturing(&self) -> bool {
1057        unsafe { ffi::am_capture_manager_is_capturing(self.as_ptr()) }
1058    }
1059
1060    /// Create a capture scope that captures all command queues on `device`.
1061    #[must_use]
1062    pub fn new_capture_scope_with_device(&self, device: &MetalDevice) -> Option<CaptureScope> {
1063        CaptureScope::wrap(unsafe {
1064            ffi::am_capture_manager_new_scope_with_device(self.as_ptr(), device.as_ptr())
1065        })
1066    }
1067
1068    /// Create a capture scope restricted to `command_queue`.
1069    #[must_use]
1070    pub fn new_capture_scope_with_command_queue(
1071        &self,
1072        command_queue: &CommandQueue,
1073    ) -> Option<CaptureScope> {
1074        CaptureScope::wrap(unsafe {
1075            ffi::am_capture_manager_new_scope_with_command_queue(
1076                self.as_ptr(),
1077                command_queue.as_ptr(),
1078            )
1079        })
1080    }
1081}
1082
1083impl CaptureScope {
1084    /// Mark the start of the capture scope.
1085    pub fn begin(&self) {
1086        unsafe { ffi::am_capture_scope_begin(self.as_ptr()) };
1087    }
1088
1089    /// Mark the end of the capture scope.
1090    pub fn end(&self) {
1091        unsafe { ffi::am_capture_scope_end(self.as_ptr()) };
1092    }
1093}
1094
1095impl ArgumentEncoder {
1096    pub(crate) const unsafe fn from_retained_ptr(ptr: *mut c_void) -> Self {
1097        Self { ptr }
1098    }
1099}