1#![doc = include_str!("../README.md")]
2#![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;
17pub mod ffi;
19pub(crate) mod metalfx;
20pub(crate) mod pipeline;
21pub(crate) mod render;
22pub(crate) mod state;
23pub(crate) mod util;
24
25pub use advanced::*;
27pub use argument::*;
29pub use command::*;
31pub use exhaustive::*;
33pub use metalfx::*;
35pub use pipeline::*;
37pub use render::*;
39pub use state::*;
41
42pub mod pixel_format {
44pub const A8UNORM: usize = 1;
46pub const R8UNORM: usize = 10;
48pub const R8SNORM: usize = 12;
50pub const R8UINT: usize = 13;
52pub const R8SINT: usize = 14;
54pub const R16UNORM: usize = 20;
56pub const R16SNORM: usize = 22;
58pub const R16UINT: usize = 23;
60pub const R16SINT: usize = 24;
62pub const R16FLOAT: usize = 25;
64pub const RG8UNORM: usize = 30;
66pub const RG8SNORM: usize = 32;
68pub const RG8UINT: usize = 33;
70pub const RG8SINT: usize = 34;
72pub const RGBA8UNORM: usize = 70;
74pub const RGBA8UNORM_SRGB: usize = 71;
76pub const RGBA8SNORM: usize = 72;
78pub const RGBA8UINT: usize = 73;
80pub const RGBA8SINT: usize = 74;
82pub const BGRA8UNORM: usize = 80;
84pub const BGRA8UNORM_SRGB: usize = 81;
86pub const R32FLOAT: usize = 55;
88pub const RG16FLOAT: usize = 65;
90pub const RGBA16FLOAT: usize = 115;
92pub const RGBA32FLOAT: usize = 125;
94pub const DEPTH32FLOAT: usize = 252;
96pub const STENCIL8: usize = 253;
98pub const BGRA10_XR: usize = 552;
100pub const BGR10_XR: usize = 554;
102}
103
104pub mod storage_mode {
106pub const SHARED: usize = 0;
108pub const MANAGED: usize = 1;
110pub const PRIVATE: usize = 2;
112pub const MEMORYLESS: usize = 3;
114}
115
116pub mod cpu_cache_mode {
118pub const DEFAULT_CACHE: usize = 0;
120pub const WRITE_COMBINED: usize = 1;
122}
123
124pub mod hazard_tracking_mode {
126pub const DEFAULT: usize = 0;
128pub const UNTRACKED: usize = 1;
130pub const TRACKED: usize = 2;
132}
133
134pub mod resource_options {
136pub const CPU_CACHE_MODE_DEFAULT: usize = 0;
138pub const CPU_CACHE_MODE_WRITE_COMBINED: usize = 1;
140pub const STORAGE_MODE_SHARED: usize = 0;
142pub const STORAGE_MODE_MANAGED: usize = 1 << 4;
144pub const STORAGE_MODE_PRIVATE: usize = 2 << 4;
146pub const HAZARD_TRACKING_MODE_DEFAULT: usize = 0;
148pub const HAZARD_TRACKING_MODE_UNTRACKED: usize = 1 << 8;
150pub const HAZARD_TRACKING_MODE_TRACKED: usize = 2 << 8;
152}
153
154pub mod texture_usage {
156pub const SHADER_READ: usize = 0x01;
158pub const SHADER_WRITE: usize = 0x02;
160pub const RENDER_TARGET: usize = 0x04;
162}
163
164pub mod gpu_family {
166pub const APPLE1: i64 = 1001;
168pub const APPLE2: i64 = 1002;
170pub const APPLE3: i64 = 1003;
172pub const APPLE4: i64 = 1004;
174pub const APPLE5: i64 = 1005;
176pub const APPLE6: i64 = 1006;
178pub const APPLE7: i64 = 1007;
180pub const APPLE8: i64 = 1008;
182pub const APPLE9: i64 = 1009;
184pub const MAC1: i64 = 2001;
186pub const MAC2: i64 = 2002;
188pub const COMMON1: i64 = 3001;
190pub const COMMON2: i64 = 3002;
192pub const COMMON3: i64 = 3003;
194pub const METAL3: i64 = 5001;
196}
197
198pub struct MetalDevice {
202 ptr: *mut c_void,
203 drop_on_release: bool,
204}
205
206unsafe 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 #[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 #[must_use]
234 pub const fn as_ptr(&self) -> *mut c_void {
235 self.ptr
236 }
237
238 #[must_use]
240 pub fn has_unified_memory(&self) -> bool {
241 unsafe { ffi::am_device_has_unified_memory(self.ptr) }
242 }
243
244 #[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 #[must_use]
253 pub fn supports_family(&self, family: i64) -> bool {
254 unsafe { ffi::am_device_supports_family(self.ptr, family) }
255 }
256
257 #[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 #[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 #[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 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 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 #[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
381pub 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
393pub struct CommandQueue {
397 ptr: *mut c_void,
398}
399
400unsafe 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 #[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 #[must_use]
428 pub const fn as_ptr(&self) -> *mut c_void {
429 self.ptr
430 }
431}
432
433pub struct CommandBuffer {
435 ptr: *mut c_void,
436}
437
438unsafe 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 pub fn commit(&self) {
456 unsafe { ffi::am_command_buffer_commit(self.ptr) };
457 }
458
459 pub fn wait_until_completed(&self) {
461 unsafe { ffi::am_command_buffer_wait_until_completed(self.ptr) };
462 }
463
464 #[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 #[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 #[must_use]
513 pub const fn as_ptr(&self) -> *mut c_void {
514 self.ptr
515 }
516}
517
518pub struct MetalLibrary {
522 ptr: *mut c_void,
523}
524
525unsafe 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 #[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 #[must_use]
554 pub const fn as_ptr(&self) -> *mut c_void {
555 self.ptr
556 }
557}
558
559pub struct MetalFunction {
561 ptr: *mut c_void,
562}
563
564unsafe 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 #[must_use]
581 pub const fn as_ptr(&self) -> *mut c_void {
582 self.ptr
583 }
584}
585
586pub struct ComputePipelineState {
588 ptr: *mut c_void,
589}
590
591unsafe 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 #[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
617pub struct MetalBuffer {
621 ptr: *mut c_void,
622}
623
624unsafe 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 #[must_use]
643 pub fn length(&self) -> usize {
644 unsafe { ffi::am_buffer_length(self.ptr) }
645 }
646
647 #[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 #[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 #[must_use]
673 pub const fn as_ptr(&self) -> *mut c_void {
674 self.ptr
675 }
676}
677
678#[derive(Debug, Clone, Copy)]
682pub struct TextureDescriptor {
683pub pixel_format: usize,
685pub width: usize,
687pub height: usize,
689pub mipmapped: bool,
691pub usage: usize,
693pub storage_mode: usize,
695}
696
697impl TextureDescriptor {
698 #[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
712pub struct MetalTexture {
714 ptr: *mut c_void,
715}
716
717unsafe 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 #[must_use]
734 pub fn width(&self) -> usize {
735 unsafe { ffi::am_texture_width(self.ptr) }
736 }
737
738 #[must_use]
740 pub fn height(&self) -> usize {
741 unsafe { ffi::am_texture_height(self.ptr) }
742 }
743
744 #[must_use]
746 pub fn pixel_format(&self) -> usize {
747 unsafe { ffi::am_texture_pixel_format(self.ptr) }
748 }
749
750 #[must_use]
752 pub const fn as_ptr(&self) -> *mut c_void {
753 self.ptr
754 }
755
756 #[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#[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 pub trait IOSurfaceMetalExt {
807 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#[cfg(feature = "iosurface")]
864pub use iosurface_ext::IOSurfaceMetalExt;
865
866#[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}