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 {
44 pub const A8UNORM: usize = 1;
46 pub const R8UNORM: usize = 10;
48 pub const R8SNORM: usize = 12;
50 pub const R8UINT: usize = 13;
52 pub const R8SINT: usize = 14;
54 pub const R16UNORM: usize = 20;
56 pub const R16SNORM: usize = 22;
58 pub const R16UINT: usize = 23;
60 pub const R16SINT: usize = 24;
62 pub const R16FLOAT: usize = 25;
64 pub const RG8UNORM: usize = 30;
66 pub const RG8SNORM: usize = 32;
68 pub const RG8UINT: usize = 33;
70 pub const RG8SINT: usize = 34;
72 pub const RGBA8UNORM: usize = 70;
74 pub const RGBA8UNORM_SRGB: usize = 71;
76 pub const RGBA8SNORM: usize = 72;
78 pub const RGBA8UINT: usize = 73;
80 pub const RGBA8SINT: usize = 74;
82 pub const BGRA8UNORM: usize = 80;
84 pub const BGRA8UNORM_SRGB: usize = 81;
86 pub const R32FLOAT: usize = 55;
88 pub const RG16FLOAT: usize = 65;
90 pub const RGBA16FLOAT: usize = 115;
92 pub const RGBA32FLOAT: usize = 125;
94 pub const DEPTH32FLOAT: usize = 252;
96 pub const STENCIL8: usize = 253;
98 pub const BGRA10_XR: usize = 552;
100 pub const BGR10_XR: usize = 554;
102}
103
104pub mod storage_mode {
106 pub const SHARED: usize = 0;
108 pub const MANAGED: usize = 1;
110 pub const PRIVATE: usize = 2;
112 pub const MEMORYLESS: usize = 3;
114}
115
116pub mod cpu_cache_mode {
118 pub const DEFAULT_CACHE: usize = 0;
120 pub const WRITE_COMBINED: usize = 1;
122}
123
124pub mod hazard_tracking_mode {
126 pub const DEFAULT: usize = 0;
128 pub const UNTRACKED: usize = 1;
130 pub const TRACKED: usize = 2;
132}
133
134pub mod resource_options {
136 pub const CPU_CACHE_MODE_DEFAULT: usize = 0;
138 pub const CPU_CACHE_MODE_WRITE_COMBINED: usize = 1;
140 pub const STORAGE_MODE_SHARED: usize = 0;
142 pub const STORAGE_MODE_MANAGED: usize = 1 << 4;
144 pub const STORAGE_MODE_PRIVATE: usize = 2 << 4;
146 pub const HAZARD_TRACKING_MODE_DEFAULT: usize = 0;
148 pub const HAZARD_TRACKING_MODE_UNTRACKED: usize = 1 << 8;
150 pub const HAZARD_TRACKING_MODE_TRACKED: usize = 2 << 8;
152}
153
154pub mod texture_usage {
156 pub const SHADER_READ: usize = 0x01;
158 pub const SHADER_WRITE: usize = 0x02;
160 pub const RENDER_TARGET: usize = 0x04;
162}
163
164pub mod gpu_family {
166 pub const APPLE1: i64 = 1001;
168 pub const APPLE2: i64 = 1002;
170 pub const APPLE3: i64 = 1003;
172 pub const APPLE4: i64 = 1004;
174 pub const APPLE5: i64 = 1005;
176 pub const APPLE6: i64 = 1006;
178 pub const APPLE7: i64 = 1007;
180 pub const APPLE8: i64 = 1008;
182 pub const APPLE9: i64 = 1009;
184 pub const MAC1: i64 = 2001;
186 pub const MAC2: i64 = 2002;
188 pub const COMMON1: i64 = 3001;
190 pub const COMMON2: i64 = 3002;
192 pub const COMMON3: i64 = 3003;
194 pub 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 {}
445
446impl Drop for CommandBuffer {
447 fn drop(&mut self) {
448 if !self.ptr.is_null() {
449 unsafe { ffi::am_command_buffer_release(self.ptr) };
450 self.ptr = ptr::null_mut();
451 }
452 }
453}
454
455impl CommandBuffer {
456 pub fn commit(&self) {
458 unsafe { ffi::am_command_buffer_commit(self.ptr) };
459 }
460
461 pub fn wait_until_completed(&self) {
463 unsafe { ffi::am_command_buffer_wait_until_completed(self.ptr) };
464 }
465
466 #[must_use]
469 pub fn blit_copy_buffer(
470 &self,
471 src: &MetalBuffer,
472 src_offset: usize,
473 dst: &MetalBuffer,
474 dst_offset: usize,
475 size: usize,
476 ) -> bool {
477 unsafe {
478 ffi::am_command_buffer_blit_copy_buffer(
479 self.ptr,
480 src.as_ptr(),
481 src_offset,
482 dst.as_ptr(),
483 dst_offset,
484 size,
485 )
486 }
487 }
488
489 #[must_use]
493 pub fn dispatch_compute_1d(
494 &self,
495 pso: &ComputePipelineState,
496 buffers: &[&MetalBuffer],
497 threadgroups: usize,
498 threads_per_group: usize,
499 ) -> bool {
500 let raw: Vec<*mut c_void> = buffers.iter().map(|b| b.as_ptr()).collect();
501 unsafe {
502 ffi::am_command_buffer_dispatch_compute_1d(
503 self.ptr,
504 pso.ptr,
505 raw.as_ptr(),
506 raw.len(),
507 threadgroups,
508 threads_per_group,
509 )
510 }
511 }
512
513 #[must_use]
515 pub const fn as_ptr(&self) -> *mut c_void {
516 self.ptr
517 }
518}
519
520pub struct MetalLibrary {
524 ptr: *mut c_void,
525}
526
527unsafe impl Send for MetalLibrary {}
530unsafe impl Sync for MetalLibrary {}
531
532impl Drop for MetalLibrary {
533 fn drop(&mut self) {
534 if !self.ptr.is_null() {
535 unsafe { ffi::am_library_release(self.ptr) };
536 self.ptr = ptr::null_mut();
537 }
538 }
539}
540
541impl MetalLibrary {
542 #[must_use]
544 pub fn new_function(&self, name: &str) -> Option<MetalFunction> {
545 let cname = std::ffi::CString::new(name).ok()?;
546 let p = unsafe { ffi::am_library_new_function(self.ptr, cname.as_ptr()) };
547 if p.is_null() {
548 None
549 } else {
550 Some(MetalFunction { ptr: p })
551 }
552 }
553
554 #[must_use]
556 pub const fn as_ptr(&self) -> *mut c_void {
557 self.ptr
558 }
559}
560
561pub struct MetalFunction {
563 ptr: *mut c_void,
564}
565
566unsafe impl Send for MetalFunction {}
569unsafe impl Sync for MetalFunction {}
570
571impl Drop for MetalFunction {
572 fn drop(&mut self) {
573 if !self.ptr.is_null() {
574 unsafe { ffi::am_function_release(self.ptr) };
575 self.ptr = ptr::null_mut();
576 }
577 }
578}
579
580impl MetalFunction {
581 #[must_use]
583 pub const fn as_ptr(&self) -> *mut c_void {
584 self.ptr
585 }
586}
587
588pub struct ComputePipelineState {
590 ptr: *mut c_void,
591}
592
593unsafe impl Send for ComputePipelineState {}
596unsafe impl Sync for ComputePipelineState {}
597
598impl Drop for ComputePipelineState {
599 fn drop(&mut self) {
600 if !self.ptr.is_null() {
601 unsafe { ffi::am_compute_pipeline_state_release(self.ptr) };
602 self.ptr = ptr::null_mut();
603 }
604 }
605}
606
607impl ComputePipelineState {
608 #[must_use]
610 pub const fn as_ptr(&self) -> *mut c_void {
611 self.ptr
612 }
613
614 pub(crate) const unsafe fn from_retained_ptr(ptr: *mut c_void) -> Self {
615 Self { ptr }
616 }
617}
618
619pub struct MetalBuffer {
623 ptr: *mut c_void,
624}
625
626unsafe impl Send for MetalBuffer {}
635unsafe impl Sync for MetalBuffer {}
636
637impl Drop for MetalBuffer {
638 fn drop(&mut self) {
639 if !self.ptr.is_null() {
640 unsafe { ffi::am_buffer_release(self.ptr) };
641 self.ptr = ptr::null_mut();
642 }
643 }
644}
645
646impl MetalBuffer {
647 #[must_use]
649 pub fn length(&self) -> usize {
650 unsafe { ffi::am_buffer_length(self.ptr) }
651 }
652
653 #[must_use]
656 pub fn contents(&self) -> Option<*mut c_void> {
657 let p = unsafe { ffi::am_buffer_contents(self.ptr) };
658 if p.is_null() {
659 None
660 } else {
661 Some(p)
662 }
663 }
664
665 #[must_use]
668 pub fn write_bytes(&self, src: &[u8]) -> usize {
669 let Some(dst) = self.contents() else {
670 return 0;
671 };
672 let n = core::cmp::min(src.len(), self.length());
673 unsafe { core::ptr::copy_nonoverlapping(src.as_ptr(), dst.cast::<u8>(), n) };
674 n
675 }
676
677 #[must_use]
679 pub const fn as_ptr(&self) -> *mut c_void {
680 self.ptr
681 }
682}
683
684#[derive(Debug, Clone, Copy)]
688pub struct TextureDescriptor {
689 pub pixel_format: usize,
691 pub width: usize,
693 pub height: usize,
695 pub mipmapped: bool,
697 pub usage: usize,
699 pub storage_mode: usize,
701}
702
703impl TextureDescriptor {
704 #[must_use]
706 pub const fn new_2d(width: usize, height: usize, pixel_format: usize) -> Self {
707 Self {
708 pixel_format,
709 width,
710 height,
711 mipmapped: false,
712 usage: texture_usage::SHADER_READ | texture_usage::SHADER_WRITE,
713 storage_mode: storage_mode::SHARED,
714 }
715 }
716}
717
718pub struct MetalTexture {
720 ptr: *mut c_void,
721}
722
723unsafe impl Send for MetalTexture {}
726unsafe impl Sync for MetalTexture {}
727
728impl Drop for MetalTexture {
729 fn drop(&mut self) {
730 if !self.ptr.is_null() {
731 unsafe { ffi::am_texture_release(self.ptr) };
732 self.ptr = ptr::null_mut();
733 }
734 }
735}
736
737impl MetalTexture {
738 #[must_use]
740 pub fn width(&self) -> usize {
741 unsafe { ffi::am_texture_width(self.ptr) }
742 }
743
744 #[must_use]
746 pub fn height(&self) -> usize {
747 unsafe { ffi::am_texture_height(self.ptr) }
748 }
749
750 #[must_use]
752 pub fn pixel_format(&self) -> usize {
753 unsafe { ffi::am_texture_pixel_format(self.ptr) }
754 }
755
756 #[must_use]
758 pub const fn as_ptr(&self) -> *mut c_void {
759 self.ptr
760 }
761
762 #[must_use]
771 pub const unsafe fn from_raw(ptr: *mut c_void) -> Self {
772 Self { ptr }
773 }
774}
775
776impl MetalDevice {
777 pub(crate) const unsafe fn from_retained_ptr(ptr: *mut c_void) -> Self {
778 Self {
779 ptr,
780 drop_on_release: true,
781 }
782 }
783}
784
785impl CommandQueue {
786 pub(crate) const unsafe fn from_retained_ptr(ptr: *mut c_void) -> Self {
787 Self { ptr }
788 }
789}
790
791impl CommandBuffer {
792 pub(crate) const unsafe fn from_retained_ptr(ptr: *mut c_void) -> Self {
793 Self { ptr }
794 }
795}
796
797impl MetalBuffer {
798 pub(crate) const unsafe fn from_retained_ptr(ptr: *mut c_void) -> Self {
799 Self { ptr }
800 }
801}
802
803#[cfg(feature = "iosurface")]
806#[cfg_attr(docsrs, doc(cfg(feature = "iosurface")))]
807mod iosurface_ext {
808 use super::{ffi, pixel_format, MetalDevice, MetalTexture};
809 use apple_cf::iosurface::IOSurface;
810 use core::ffi::c_void;
811
812 pub trait IOSurfaceMetalExt {
814 fn create_metal_texture(
817 &self,
818 device: &MetalDevice,
819 plane_index: usize,
820 ) -> Option<MetalTexture>;
821 }
822
823 impl IOSurfaceMetalExt for IOSurface {
824 fn create_metal_texture(
825 &self,
826 device: &MetalDevice,
827 plane_index: usize,
828 ) -> Option<MetalTexture> {
829 let format = pixel_format_for_fourcc(self.pixel_format(), plane_index)?;
830 let (width, height) = if plane_index == 0 {
831 (self.width(), self.height())
832 } else {
833 (self.width() / 2, self.height() / 2)
834 };
835 let p = unsafe {
836 ffi::am_device_new_texture_from_iosurface(
837 device.as_ptr(),
838 self.as_ptr().cast::<c_void>(),
839 plane_index,
840 format,
841 width,
842 height,
843 )
844 };
845 if p.is_null() {
846 None
847 } else {
848 Some(unsafe { MetalTexture::from_raw(p) })
849 }
850 }
851 }
852
853 fn pixel_format_for_fourcc(fourcc: u32, plane_index: usize) -> Option<usize> {
854 const BGRA: u32 = u32::from_be_bytes(*b"BGRA");
855 const L10R: u32 = u32::from_be_bytes(*b"l10r");
856 const YUV420V: u32 = u32::from_be_bytes(*b"420v");
857 const YUV420F: u32 = u32::from_be_bytes(*b"420f");
858
859 match (fourcc, plane_index) {
860 (BGRA, 0) => Some(pixel_format::BGRA8UNORM),
861 (L10R, 0) => Some(pixel_format::BGRA10_XR),
862 (YUV420V | YUV420F, 0) => Some(pixel_format::R8UNORM),
863 (YUV420V | YUV420F, 1) => Some(pixel_format::RG8UNORM),
864 _ => None,
865 }
866 }
867}
868
869#[cfg(feature = "iosurface")]
871pub use iosurface_ext::IOSurfaceMetalExt;
872
873#[must_use]
875pub const fn is_ycbcr_biplanar(fourcc: u32) -> bool {
876 const YUV420V: u32 = u32::from_be_bytes(*b"420v");
877 const YUV420F: u32 = u32::from_be_bytes(*b"420f");
878 matches!(fourcc, YUV420V | YUV420F)
879}