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 command;
15pub mod ffi;
16pub(crate) mod render;
17pub(crate) mod util;
18
19pub use advanced::*;
20pub use command::*;
21pub use render::*;
22
23pub mod pixel_format {
25 pub const A8UNORM: usize = 1;
26 pub const R8UNORM: usize = 10;
27 pub const R8SNORM: usize = 12;
28 pub const R8UINT: usize = 13;
29 pub const R8SINT: usize = 14;
30 pub const R16UNORM: usize = 20;
31 pub const R16SNORM: usize = 22;
32 pub const R16UINT: usize = 23;
33 pub const R16SINT: usize = 24;
34 pub const R16FLOAT: usize = 25;
35 pub const RG8UNORM: usize = 30;
36 pub const RG8SNORM: usize = 32;
37 pub const RG8UINT: usize = 33;
38 pub const RG8SINT: usize = 34;
39 pub const RGBA8UNORM: usize = 70;
40 pub const RGBA8UNORM_SRGB: usize = 71;
41 pub const RGBA8SNORM: usize = 72;
42 pub const RGBA8UINT: usize = 73;
43 pub const RGBA8SINT: usize = 74;
44 pub const BGRA8UNORM: usize = 80;
45 pub const BGRA8UNORM_SRGB: usize = 81;
46 pub const R32FLOAT: usize = 55;
47 pub const RG16FLOAT: usize = 65;
48 pub const RGBA16FLOAT: usize = 115;
49 pub const RGBA32FLOAT: usize = 125;
50 pub const DEPTH32FLOAT: usize = 252;
51 pub const STENCIL8: usize = 253;
52 pub const BGRA10_XR: usize = 552;
53 pub const BGR10_XR: usize = 554;
54}
55
56pub mod storage_mode {
58 pub const SHARED: usize = 0;
59 pub const MANAGED: usize = 1;
60 pub const PRIVATE: usize = 2;
61 pub const MEMORYLESS: usize = 3;
62}
63
64pub mod cpu_cache_mode {
66 pub const DEFAULT_CACHE: usize = 0;
67 pub const WRITE_COMBINED: usize = 1;
68}
69
70pub mod hazard_tracking_mode {
72 pub const DEFAULT: usize = 0;
73 pub const UNTRACKED: usize = 1;
74 pub const TRACKED: usize = 2;
75}
76
77pub mod resource_options {
79 pub const CPU_CACHE_MODE_DEFAULT: usize = 0;
80 pub const CPU_CACHE_MODE_WRITE_COMBINED: usize = 1;
81 pub const STORAGE_MODE_SHARED: usize = 0;
82 pub const STORAGE_MODE_MANAGED: usize = 1 << 4;
83 pub const STORAGE_MODE_PRIVATE: usize = 2 << 4;
84 pub const HAZARD_TRACKING_MODE_DEFAULT: usize = 0;
85 pub const HAZARD_TRACKING_MODE_UNTRACKED: usize = 1 << 8;
86 pub const HAZARD_TRACKING_MODE_TRACKED: usize = 2 << 8;
87}
88
89pub mod texture_usage {
91 pub const SHADER_READ: usize = 0x01;
92 pub const SHADER_WRITE: usize = 0x02;
93 pub const RENDER_TARGET: usize = 0x04;
94}
95
96pub mod gpu_family {
98 pub const APPLE1: i64 = 1001;
99 pub const APPLE2: i64 = 1002;
100 pub const APPLE3: i64 = 1003;
101 pub const APPLE4: i64 = 1004;
102 pub const APPLE5: i64 = 1005;
103 pub const APPLE6: i64 = 1006;
104 pub const APPLE7: i64 = 1007;
105 pub const APPLE8: i64 = 1008;
106 pub const APPLE9: i64 = 1009;
107 pub const MAC1: i64 = 2001;
108 pub const MAC2: i64 = 2002;
109 pub const COMMON1: i64 = 3001;
110 pub const COMMON2: i64 = 3002;
111 pub const COMMON3: i64 = 3003;
112 pub const METAL3: i64 = 5001;
113}
114
115pub struct MetalDevice {
119 ptr: *mut c_void,
120 drop_on_release: bool,
121}
122
123unsafe impl Send for MetalDevice {}
124unsafe impl Sync for MetalDevice {}
125
126impl Drop for MetalDevice {
127 fn drop(&mut self) {
128 if self.drop_on_release && !self.ptr.is_null() {
129 unsafe { ffi::am_device_release(self.ptr) };
130 self.ptr = ptr::null_mut();
131 }
132 }
133}
134
135impl MetalDevice {
136 #[must_use]
138 pub fn system_default() -> Option<Self> {
139 let p = unsafe { ffi::am_device_system_default() };
140 if p.is_null() {
141 None
142 } else {
143 Some(unsafe { Self::from_retained_ptr(p) })
144 }
145 }
146
147 #[must_use]
149 pub const fn as_ptr(&self) -> *mut c_void {
150 self.ptr
151 }
152
153 #[must_use]
155 pub fn has_unified_memory(&self) -> bool {
156 unsafe { ffi::am_device_has_unified_memory(self.ptr) }
157 }
158
159 #[must_use]
161 pub fn recommended_max_working_set_size(&self) -> u64 {
162 unsafe { ffi::am_device_recommended_max_working_set_size(self.ptr) }
163 }
164
165 #[must_use]
168 pub fn supports_family(&self, family: i64) -> bool {
169 unsafe { ffi::am_device_supports_family(self.ptr, family) }
170 }
171
172 #[must_use]
176 pub fn new_buffer(&self, length: usize, options: usize) -> Option<MetalBuffer> {
177 let p = unsafe { ffi::am_device_new_buffer(self.ptr, length, options) };
178 if p.is_null() {
179 None
180 } else {
181 Some(MetalBuffer { ptr: p })
182 }
183 }
184
185 #[must_use]
187 pub fn new_texture(&self, descriptor: TextureDescriptor) -> Option<MetalTexture> {
188 let p = unsafe {
189 ffi::am_device_new_texture_2d(
190 self.ptr,
191 descriptor.pixel_format,
192 descriptor.width,
193 descriptor.height,
194 descriptor.mipmapped,
195 descriptor.usage,
196 descriptor.storage_mode,
197 )
198 };
199 if p.is_null() {
200 None
201 } else {
202 Some(MetalTexture { ptr: p })
203 }
204 }
205
206 #[must_use]
208 pub fn new_command_queue(&self) -> Option<CommandQueue> {
209 let p = unsafe { ffi::am_device_new_command_queue(self.ptr) };
210 if p.is_null() {
211 None
212 } else {
213 Some(CommandQueue { ptr: p })
214 }
215 }
216
217 pub fn new_library_with_source(&self, source: &str) -> Result<MetalLibrary, String> {
225 let csrc = std::ffi::CString::new(source).map_err(|e| e.to_string())?;
226 let mut err_msg: *mut core::ffi::c_char = core::ptr::null_mut();
227 let p = unsafe {
228 ffi::am_device_new_library_with_source(self.ptr, csrc.as_ptr(), &mut err_msg)
229 };
230 if p.is_null() {
231 let msg = if err_msg.is_null() {
232 "MTLDevice.makeLibrary returned nil".to_string()
233 } else {
234 let s = unsafe { std::ffi::CStr::from_ptr(err_msg) }
235 .to_string_lossy()
236 .into_owned();
237 unsafe { libc::free(err_msg.cast()) };
238 s
239 };
240 Err(msg)
241 } else {
242 Ok(MetalLibrary { ptr: p })
243 }
244 }
245
246 pub fn new_compute_pipeline_state(
254 &self,
255 function: &MetalFunction,
256 ) -> Result<ComputePipelineState, String> {
257 let mut err_msg: *mut core::ffi::c_char = core::ptr::null_mut();
258 let p = unsafe {
259 ffi::am_device_new_compute_pipeline_state(self.ptr, function.ptr, &mut err_msg)
260 };
261 if p.is_null() {
262 let msg = if err_msg.is_null() {
263 "MTLDevice.makeComputePipelineState returned nil".to_string()
264 } else {
265 let s = unsafe { std::ffi::CStr::from_ptr(err_msg) }
266 .to_string_lossy()
267 .into_owned();
268 unsafe { libc::free(err_msg.cast()) };
269 s
270 };
271 Err(msg)
272 } else {
273 Ok(ComputePipelineState { ptr: p })
274 }
275 }
276
277 #[must_use]
286 pub unsafe fn from_raw_borrowed(ptr: *mut c_void) -> ManuallyDropDevice {
287 ManuallyDropDevice {
288 inner: Self {
289 ptr,
290 drop_on_release: false,
291 },
292 }
293 }
294}
295
296pub struct ManuallyDropDevice {
298 inner: MetalDevice,
299}
300
301impl core::ops::Deref for ManuallyDropDevice {
302 type Target = MetalDevice;
303 fn deref(&self) -> &Self::Target {
304 &self.inner
305 }
306}
307
308pub struct CommandQueue {
312 ptr: *mut c_void,
313}
314
315unsafe impl Send for CommandQueue {}
316unsafe impl Sync for CommandQueue {}
317
318impl Drop for CommandQueue {
319 fn drop(&mut self) {
320 if !self.ptr.is_null() {
321 unsafe { ffi::am_command_queue_release(self.ptr) };
322 self.ptr = ptr::null_mut();
323 }
324 }
325}
326
327impl CommandQueue {
328 #[must_use]
330 pub fn new_command_buffer(&self) -> Option<CommandBuffer> {
331 let p = unsafe { ffi::am_command_queue_new_command_buffer(self.ptr) };
332 if p.is_null() {
333 None
334 } else {
335 Some(CommandBuffer { ptr: p })
336 }
337 }
338
339 #[must_use]
341 pub const fn as_ptr(&self) -> *mut c_void {
342 self.ptr
343 }
344}
345
346pub struct CommandBuffer {
348 ptr: *mut c_void,
349}
350
351unsafe impl Send for CommandBuffer {}
352unsafe impl Sync for CommandBuffer {}
353
354impl Drop for CommandBuffer {
355 fn drop(&mut self) {
356 if !self.ptr.is_null() {
357 unsafe { ffi::am_command_buffer_release(self.ptr) };
358 self.ptr = ptr::null_mut();
359 }
360 }
361}
362
363impl CommandBuffer {
364 pub fn commit(&self) {
366 unsafe { ffi::am_command_buffer_commit(self.ptr) };
367 }
368
369 pub fn wait_until_completed(&self) {
371 unsafe { ffi::am_command_buffer_wait_until_completed(self.ptr) };
372 }
373
374 #[must_use]
377 pub fn blit_copy_buffer(
378 &self,
379 src: &MetalBuffer,
380 src_offset: usize,
381 dst: &MetalBuffer,
382 dst_offset: usize,
383 size: usize,
384 ) -> bool {
385 unsafe {
386 ffi::am_command_buffer_blit_copy_buffer(
387 self.ptr,
388 src.as_ptr(),
389 src_offset,
390 dst.as_ptr(),
391 dst_offset,
392 size,
393 )
394 }
395 }
396
397 #[must_use]
401 pub fn dispatch_compute_1d(
402 &self,
403 pso: &ComputePipelineState,
404 buffers: &[&MetalBuffer],
405 threadgroups: usize,
406 threads_per_group: usize,
407 ) -> bool {
408 let raw: Vec<*mut c_void> = buffers.iter().map(|b| b.as_ptr()).collect();
409 unsafe {
410 ffi::am_command_buffer_dispatch_compute_1d(
411 self.ptr,
412 pso.ptr,
413 raw.as_ptr(),
414 raw.len(),
415 threadgroups,
416 threads_per_group,
417 )
418 }
419 }
420
421 #[must_use]
423 pub const fn as_ptr(&self) -> *mut c_void {
424 self.ptr
425 }
426}
427
428pub struct MetalLibrary {
432 ptr: *mut c_void,
433}
434
435unsafe impl Send for MetalLibrary {}
436unsafe impl Sync for MetalLibrary {}
437
438impl Drop for MetalLibrary {
439 fn drop(&mut self) {
440 if !self.ptr.is_null() {
441 unsafe { ffi::am_library_release(self.ptr) };
442 self.ptr = ptr::null_mut();
443 }
444 }
445}
446
447impl MetalLibrary {
448 #[must_use]
450 pub fn new_function(&self, name: &str) -> Option<MetalFunction> {
451 let cname = std::ffi::CString::new(name).ok()?;
452 let p = unsafe { ffi::am_library_new_function(self.ptr, cname.as_ptr()) };
453 if p.is_null() {
454 None
455 } else {
456 Some(MetalFunction { ptr: p })
457 }
458 }
459
460 #[must_use]
462 pub const fn as_ptr(&self) -> *mut c_void {
463 self.ptr
464 }
465}
466
467pub struct MetalFunction {
469 ptr: *mut c_void,
470}
471
472unsafe impl Send for MetalFunction {}
473unsafe impl Sync for MetalFunction {}
474
475impl Drop for MetalFunction {
476 fn drop(&mut self) {
477 if !self.ptr.is_null() {
478 unsafe { ffi::am_function_release(self.ptr) };
479 self.ptr = ptr::null_mut();
480 }
481 }
482}
483
484impl MetalFunction {
485 #[must_use]
487 pub const fn as_ptr(&self) -> *mut c_void {
488 self.ptr
489 }
490}
491
492pub struct ComputePipelineState {
494 ptr: *mut c_void,
495}
496
497unsafe impl Send for ComputePipelineState {}
498unsafe impl Sync for ComputePipelineState {}
499
500impl Drop for ComputePipelineState {
501 fn drop(&mut self) {
502 if !self.ptr.is_null() {
503 unsafe { ffi::am_compute_pipeline_state_release(self.ptr) };
504 self.ptr = ptr::null_mut();
505 }
506 }
507}
508
509impl ComputePipelineState {
510 #[must_use]
512 pub const fn as_ptr(&self) -> *mut c_void {
513 self.ptr
514 }
515}
516
517pub struct MetalBuffer {
521 ptr: *mut c_void,
522}
523
524unsafe impl Send for MetalBuffer {}
525unsafe impl Sync for MetalBuffer {}
526
527impl Drop for MetalBuffer {
528 fn drop(&mut self) {
529 if !self.ptr.is_null() {
530 unsafe { ffi::am_buffer_release(self.ptr) };
531 self.ptr = ptr::null_mut();
532 }
533 }
534}
535
536impl MetalBuffer {
537 #[must_use]
539 pub fn length(&self) -> usize {
540 unsafe { ffi::am_buffer_length(self.ptr) }
541 }
542
543 #[must_use]
546 pub fn contents(&self) -> Option<*mut c_void> {
547 let p = unsafe { ffi::am_buffer_contents(self.ptr) };
548 if p.is_null() {
549 None
550 } else {
551 Some(p)
552 }
553 }
554
555 #[must_use]
558 pub fn write_bytes(&self, src: &[u8]) -> usize {
559 let Some(dst) = self.contents() else {
560 return 0;
561 };
562 let n = core::cmp::min(src.len(), self.length());
563 unsafe { core::ptr::copy_nonoverlapping(src.as_ptr(), dst.cast::<u8>(), n) };
564 n
565 }
566
567 #[must_use]
569 pub const fn as_ptr(&self) -> *mut c_void {
570 self.ptr
571 }
572}
573
574#[derive(Debug, Clone, Copy)]
578pub struct TextureDescriptor {
579 pub pixel_format: usize,
580 pub width: usize,
581 pub height: usize,
582 pub mipmapped: bool,
583 pub usage: usize,
584 pub storage_mode: usize,
585}
586
587impl TextureDescriptor {
588 #[must_use]
590 pub const fn new_2d(width: usize, height: usize, pixel_format: usize) -> Self {
591 Self {
592 pixel_format,
593 width,
594 height,
595 mipmapped: false,
596 usage: texture_usage::SHADER_READ | texture_usage::SHADER_WRITE,
597 storage_mode: storage_mode::SHARED,
598 }
599 }
600}
601
602pub struct MetalTexture {
604 ptr: *mut c_void,
605}
606
607unsafe impl Send for MetalTexture {}
608unsafe impl Sync for MetalTexture {}
609
610impl Drop for MetalTexture {
611 fn drop(&mut self) {
612 if !self.ptr.is_null() {
613 unsafe { ffi::am_texture_release(self.ptr) };
614 self.ptr = ptr::null_mut();
615 }
616 }
617}
618
619impl MetalTexture {
620 #[must_use]
622 pub fn width(&self) -> usize {
623 unsafe { ffi::am_texture_width(self.ptr) }
624 }
625
626 #[must_use]
628 pub fn height(&self) -> usize {
629 unsafe { ffi::am_texture_height(self.ptr) }
630 }
631
632 #[must_use]
634 pub fn pixel_format(&self) -> usize {
635 unsafe { ffi::am_texture_pixel_format(self.ptr) }
636 }
637
638 #[must_use]
640 pub const fn as_ptr(&self) -> *mut c_void {
641 self.ptr
642 }
643
644 #[must_use]
652 pub const unsafe fn from_raw(ptr: *mut c_void) -> Self {
653 Self { ptr }
654 }
655}
656
657impl MetalDevice {
658 pub(crate) const unsafe fn from_retained_ptr(ptr: *mut c_void) -> Self {
659 Self {
660 ptr,
661 drop_on_release: true,
662 }
663 }
664}
665
666impl CommandQueue {
667 pub(crate) const unsafe fn from_retained_ptr(ptr: *mut c_void) -> Self {
668 Self { ptr }
669 }
670}
671
672impl CommandBuffer {
673 pub(crate) const unsafe fn from_retained_ptr(ptr: *mut c_void) -> Self {
674 Self { ptr }
675 }
676}
677
678impl MetalBuffer {
679 pub(crate) const unsafe fn from_retained_ptr(ptr: *mut c_void) -> Self {
680 Self { ptr }
681 }
682}
683
684#[cfg(feature = "iosurface")]
687#[cfg_attr(docsrs, doc(cfg(feature = "iosurface")))]
688mod iosurface_ext {
689 use super::{ffi, pixel_format, MetalDevice, MetalTexture};
690 use apple_cf::iosurface::IOSurface;
691 use core::ffi::c_void;
692
693 pub trait IOSurfaceMetalExt {
695 fn create_metal_texture(
698 &self,
699 device: &MetalDevice,
700 plane_index: usize,
701 ) -> Option<MetalTexture>;
702 }
703
704 impl IOSurfaceMetalExt for IOSurface {
705 fn create_metal_texture(
706 &self,
707 device: &MetalDevice,
708 plane_index: usize,
709 ) -> Option<MetalTexture> {
710 let format = pixel_format_for_fourcc(self.pixel_format(), plane_index)?;
711 let (width, height) = if plane_index == 0 {
712 (self.width(), self.height())
713 } else {
714 (self.width() / 2, self.height() / 2)
715 };
716 let p = unsafe {
717 ffi::am_device_new_texture_from_iosurface(
718 device.as_ptr(),
719 self.as_ptr().cast::<c_void>(),
720 plane_index,
721 format,
722 width,
723 height,
724 )
725 };
726 if p.is_null() {
727 None
728 } else {
729 Some(unsafe { MetalTexture::from_raw(p) })
730 }
731 }
732 }
733
734 fn pixel_format_for_fourcc(fourcc: u32, plane_index: usize) -> Option<usize> {
735 const BGRA: u32 = u32::from_be_bytes(*b"BGRA");
736 const L10R: u32 = u32::from_be_bytes(*b"l10r");
737 const YUV420V: u32 = u32::from_be_bytes(*b"420v");
738 const YUV420F: u32 = u32::from_be_bytes(*b"420f");
739
740 match (fourcc, plane_index) {
741 (BGRA, 0) => Some(pixel_format::BGRA8UNORM),
742 (L10R, 0) => Some(pixel_format::BGRA10_XR),
743 (YUV420V | YUV420F, 0) => Some(pixel_format::R8UNORM),
744 (YUV420V | YUV420F, 1) => Some(pixel_format::RG8UNORM),
745 _ => None,
746 }
747 }
748}
749
750#[cfg(feature = "iosurface")]
751pub use iosurface_ext::IOSurfaceMetalExt;
752
753#[must_use]
755pub const fn is_ycbcr_biplanar(fourcc: u32) -> bool {
756 const YUV420V: u32 = u32::from_be_bytes(*b"420v");
757 const YUV420F: u32 = u32::from_be_bytes(*b"420f");
758 matches!(fourcc, YUV420V | YUV420F)
759}