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::ops::{Deref, DerefMut};
12use core::ptr;
13use std::sync::{Arc, Mutex, MutexGuard};
14
15pub(crate) mod advanced;
16pub(crate) mod argument;
17pub(crate) mod command;
18pub(crate) mod exhaustive;
19pub mod ffi;
21pub(crate) mod metalfx;
22pub(crate) mod pipeline;
23pub(crate) mod render;
24pub(crate) mod state;
25pub(crate) mod util;
26
27pub use advanced::*;
29pub use argument::*;
31pub use command::*;
33pub use exhaustive::*;
35pub use metalfx::*;
37pub use pipeline::*;
39pub use render::*;
41pub use state::*;
43
44pub mod pixel_format {
46 pub const A8UNORM: usize = 1;
48 pub const R8UNORM: usize = 10;
50 pub const R8SNORM: usize = 12;
52 pub const R8UINT: usize = 13;
54 pub const R8SINT: usize = 14;
56 pub const R16UNORM: usize = 20;
58 pub const R16SNORM: usize = 22;
60 pub const R16UINT: usize = 23;
62 pub const R16SINT: usize = 24;
64 pub const R16FLOAT: usize = 25;
66 pub const RG8UNORM: usize = 30;
68 pub const RG8SNORM: usize = 32;
70 pub const RG8UINT: usize = 33;
72 pub const RG8SINT: usize = 34;
74 pub const RGBA8UNORM: usize = 70;
76 pub const RGBA8UNORM_SRGB: usize = 71;
78 pub const RGBA8SNORM: usize = 72;
80 pub const RGBA8UINT: usize = 73;
82 pub const RGBA8SINT: usize = 74;
84 pub const BGRA8UNORM: usize = 80;
86 pub const BGRA8UNORM_SRGB: usize = 81;
88 pub const R32FLOAT: usize = 55;
90 pub const RG16FLOAT: usize = 65;
92 pub const RGBA16FLOAT: usize = 115;
94 pub const RGBA32FLOAT: usize = 125;
96 pub const DEPTH32FLOAT: usize = 252;
98 pub const STENCIL8: usize = 253;
100 pub const BGRA10_XR: usize = 552;
102 pub const BGR10_XR: usize = 554;
104}
105
106pub mod storage_mode {
108 pub const SHARED: usize = 0;
110 pub const MANAGED: usize = 1;
112 pub const PRIVATE: usize = 2;
114 pub const MEMORYLESS: usize = 3;
116}
117
118pub mod cpu_cache_mode {
120 pub const DEFAULT_CACHE: usize = 0;
122 pub const WRITE_COMBINED: usize = 1;
124}
125
126pub mod hazard_tracking_mode {
128 pub const DEFAULT: usize = 0;
130 pub const UNTRACKED: usize = 1;
132 pub const TRACKED: usize = 2;
134}
135
136pub mod resource_options {
138 pub const CPU_CACHE_MODE_DEFAULT: usize = 0;
140 pub const CPU_CACHE_MODE_WRITE_COMBINED: usize = 1;
142 pub const STORAGE_MODE_SHARED: usize = 0;
144 pub const STORAGE_MODE_MANAGED: usize = 1 << 4;
146 pub const STORAGE_MODE_PRIVATE: usize = 2 << 4;
148 pub const HAZARD_TRACKING_MODE_DEFAULT: usize = 0;
150 pub const HAZARD_TRACKING_MODE_UNTRACKED: usize = 1 << 8;
152 pub const HAZARD_TRACKING_MODE_TRACKED: usize = 2 << 8;
154}
155
156pub mod texture_usage {
158 pub const SHADER_READ: usize = 0x01;
160 pub const SHADER_WRITE: usize = 0x02;
162 pub const RENDER_TARGET: usize = 0x04;
164}
165
166pub mod gpu_family {
168 pub const APPLE1: i64 = 1001;
170 pub const APPLE2: i64 = 1002;
172 pub const APPLE3: i64 = 1003;
174 pub const APPLE4: i64 = 1004;
176 pub const APPLE5: i64 = 1005;
178 pub const APPLE6: i64 = 1006;
180 pub const APPLE7: i64 = 1007;
182 pub const APPLE8: i64 = 1008;
184 pub const APPLE9: i64 = 1009;
186 pub const MAC1: i64 = 2001;
188 pub const MAC2: i64 = 2002;
190 pub const COMMON1: i64 = 3001;
192 pub const COMMON2: i64 = 3002;
194 pub const COMMON3: i64 = 3003;
196 pub const METAL3: i64 = 5001;
198}
199
200pub struct MetalDevice {
204 ptr: *mut c_void,
205 drop_on_release: bool,
206}
207
208unsafe impl Send for MetalDevice {}
211unsafe impl Sync for MetalDevice {}
212
213impl Drop for MetalDevice {
214 fn drop(&mut self) {
215 if self.drop_on_release && !self.ptr.is_null() {
216 unsafe { ffi::am_device_release(self.ptr) };
217 self.ptr = ptr::null_mut();
218 }
219 }
220}
221
222impl MetalDevice {
223 #[must_use]
225 pub fn system_default() -> Option<Self> {
226 let p = unsafe { ffi::am_device_system_default() };
227 if p.is_null() {
228 None
229 } else {
230 Some(unsafe { Self::from_retained_ptr(p) })
231 }
232 }
233
234 #[must_use]
236 pub const fn as_ptr(&self) -> *mut c_void {
237 self.ptr
238 }
239
240 #[must_use]
242 pub fn has_unified_memory(&self) -> bool {
243 unsafe { ffi::am_device_has_unified_memory(self.ptr) }
244 }
245
246 #[must_use]
248 pub fn recommended_max_working_set_size(&self) -> u64 {
249 unsafe { ffi::am_device_recommended_max_working_set_size(self.ptr) }
250 }
251
252 #[must_use]
255 pub fn supports_family(&self, family: i64) -> bool {
256 unsafe { ffi::am_device_supports_family(self.ptr, family) }
257 }
258
259 #[must_use]
263 pub fn new_buffer(&self, length: usize, options: usize) -> Option<MetalBuffer> {
264 if length > isize::MAX as usize {
265 return None;
266 }
267 let p = unsafe { ffi::am_device_new_buffer(self.ptr, length, options) };
268 if p.is_null() {
269 None
270 } else {
271 Some(unsafe { MetalBuffer::from_retained_ptr(p) })
272 }
273 }
274
275 #[must_use]
277 pub fn new_texture(&self, descriptor: TextureDescriptor) -> Option<MetalTexture> {
278 if [
279 descriptor.pixel_format,
280 descriptor.width,
281 descriptor.height,
282 descriptor.usage,
283 descriptor.storage_mode,
284 ]
285 .into_iter()
286 .any(|value| value > isize::MAX as usize)
287 {
288 return None;
289 }
290 let p = unsafe {
291 ffi::am_device_new_texture_2d(
292 self.ptr,
293 descriptor.pixel_format,
294 descriptor.width,
295 descriptor.height,
296 descriptor.mipmapped,
297 descriptor.usage,
298 descriptor.storage_mode,
299 )
300 };
301 if p.is_null() {
302 None
303 } else {
304 Some(MetalTexture { ptr: p })
305 }
306 }
307
308 #[must_use]
310 pub fn new_command_queue(&self) -> Option<CommandQueue> {
311 let p = unsafe { ffi::am_device_new_command_queue(self.ptr) };
312 if p.is_null() {
313 None
314 } else {
315 Some(CommandQueue { ptr: p })
316 }
317 }
318
319 pub fn new_library_with_source(&self, source: &str) -> Result<MetalLibrary, String> {
327 let csrc = std::ffi::CString::new(source).map_err(|e| e.to_string())?;
328 let mut err_msg: *mut core::ffi::c_char = core::ptr::null_mut();
329 let p = unsafe {
330 ffi::am_device_new_library_with_source(self.ptr, csrc.as_ptr(), &mut err_msg)
331 };
332 if p.is_null() {
333 let msg = if err_msg.is_null() {
334 "MTLDevice.makeLibrary returned nil".to_string()
335 } else {
336 let s = unsafe { std::ffi::CStr::from_ptr(err_msg) }
337 .to_string_lossy()
338 .into_owned();
339 unsafe { libc::free(err_msg.cast()) };
340 s
341 };
342 Err(msg)
343 } else {
344 Ok(MetalLibrary { ptr: p })
345 }
346 }
347
348 pub fn new_compute_pipeline_state(
356 &self,
357 function: &MetalFunction,
358 ) -> Result<ComputePipelineState, String> {
359 let mut err_msg: *mut core::ffi::c_char = core::ptr::null_mut();
360 let p = unsafe {
361 ffi::am_device_new_compute_pipeline_state(self.ptr, function.ptr, &mut err_msg)
362 };
363 if p.is_null() {
364 let msg = if err_msg.is_null() {
365 "MTLDevice.makeComputePipelineState returned nil".to_string()
366 } else {
367 let s = unsafe { std::ffi::CStr::from_ptr(err_msg) }
368 .to_string_lossy()
369 .into_owned();
370 unsafe { libc::free(err_msg.cast()) };
371 s
372 };
373 Err(msg)
374 } else {
375 Ok(ComputePipelineState { ptr: p })
376 }
377 }
378
379 #[must_use]
388 pub unsafe fn from_raw_borrowed(ptr: *mut c_void) -> ManuallyDropDevice {
389 ManuallyDropDevice {
390 inner: Self {
391 ptr,
392 drop_on_release: false,
393 },
394 }
395 }
396}
397
398pub struct ManuallyDropDevice {
400 inner: MetalDevice,
401}
402
403impl core::ops::Deref for ManuallyDropDevice {
404 type Target = MetalDevice;
405 fn deref(&self) -> &Self::Target {
406 &self.inner
407 }
408}
409
410pub struct CommandQueue {
414 ptr: *mut c_void,
415}
416
417unsafe impl Send for CommandQueue {}
420unsafe impl Sync for CommandQueue {}
421
422impl Drop for CommandQueue {
423 fn drop(&mut self) {
424 if !self.ptr.is_null() {
425 unsafe { ffi::am_command_queue_release(self.ptr) };
426 self.ptr = ptr::null_mut();
427 }
428 }
429}
430
431impl CommandQueue {
432 #[must_use]
434 pub fn new_command_buffer(&self) -> Option<CommandBuffer> {
435 let p = unsafe { ffi::am_command_queue_new_command_buffer(self.ptr) };
436 if p.is_null() {
437 None
438 } else {
439 Some(unsafe { CommandBuffer::from_retained_ptr(p) })
440 }
441 }
442
443 #[must_use]
445 pub const fn as_ptr(&self) -> *mut c_void {
446 self.ptr
447 }
448}
449
450#[derive(Clone)]
452pub struct CommandBuffer {
453 pub(crate) inner: Arc<CommandBufferInner>,
454}
455
456pub(crate) struct CommandBufferInner {
457 pub(crate) ptr: *mut c_void,
458 pub(crate) state: Mutex<CommandBufferState>,
459}
460
461#[derive(Debug, Clone, Copy, PartialEq, Eq)]
462pub(crate) enum CommandBufferPhase {
463 Recording,
464 Enqueued,
465 Committed,
466 Completed,
467 Error,
468}
469
470pub(crate) struct CommandBufferState {
471 pub(crate) phase: CommandBufferPhase,
472 pub(crate) active_encoder: bool,
473}
474
475unsafe impl Send for CommandBufferInner {}
478unsafe impl Sync for CommandBufferInner {}
479
480impl Drop for CommandBufferInner {
481 fn drop(&mut self) {
482 if !self.ptr.is_null() {
483 unsafe { ffi::am_command_buffer_release(self.ptr) };
484 self.ptr = ptr::null_mut();
485 }
486 }
487}
488
489impl CommandBuffer {
490 #[must_use]
496 pub fn as_ptr(&self) -> *mut c_void {
497 self.inner.ptr
498 }
499}
500
501pub struct MetalLibrary {
505 ptr: *mut c_void,
506}
507
508unsafe impl Send for MetalLibrary {}
511unsafe impl Sync for MetalLibrary {}
512
513impl Drop for MetalLibrary {
514 fn drop(&mut self) {
515 if !self.ptr.is_null() {
516 unsafe { ffi::am_library_release(self.ptr) };
517 self.ptr = ptr::null_mut();
518 }
519 }
520}
521
522impl MetalLibrary {
523 #[must_use]
525 pub fn new_function(&self, name: &str) -> Option<MetalFunction> {
526 let cname = std::ffi::CString::new(name).ok()?;
527 let p = unsafe { ffi::am_library_new_function(self.ptr, cname.as_ptr()) };
528 if p.is_null() {
529 None
530 } else {
531 Some(MetalFunction { ptr: p })
532 }
533 }
534
535 #[must_use]
537 pub const fn as_ptr(&self) -> *mut c_void {
538 self.ptr
539 }
540}
541
542pub struct MetalFunction {
544 ptr: *mut c_void,
545}
546
547unsafe impl Send for MetalFunction {}
550unsafe impl Sync for MetalFunction {}
551
552impl Drop for MetalFunction {
553 fn drop(&mut self) {
554 if !self.ptr.is_null() {
555 unsafe { ffi::am_function_release(self.ptr) };
556 self.ptr = ptr::null_mut();
557 }
558 }
559}
560
561impl MetalFunction {
562 #[must_use]
564 pub const fn as_ptr(&self) -> *mut c_void {
565 self.ptr
566 }
567}
568
569pub struct ComputePipelineState {
571 ptr: *mut c_void,
572}
573
574unsafe impl Send for ComputePipelineState {}
577unsafe impl Sync for ComputePipelineState {}
578
579impl Drop for ComputePipelineState {
580 fn drop(&mut self) {
581 if !self.ptr.is_null() {
582 unsafe { ffi::am_compute_pipeline_state_release(self.ptr) };
583 self.ptr = ptr::null_mut();
584 }
585 }
586}
587
588impl ComputePipelineState {
589 #[must_use]
591 pub const fn as_ptr(&self) -> *mut c_void {
592 self.ptr
593 }
594
595 pub(crate) const unsafe fn from_retained_ptr(ptr: *mut c_void) -> Self {
596 Self { ptr }
597 }
598}
599
600#[derive(Debug, Clone, PartialEq, Eq)]
604pub enum MetalBufferAccessError {
605 CpuInaccessibleStorage { storage_mode: usize },
607 MappingUnavailable,
609 MappingLockPoisoned,
611 RangeOutOfBounds {
613 offset: usize,
614 length: usize,
615 buffer_length: usize,
616 },
617 InvalidRange,
619 ManagedStorageRequired { storage_mode: usize },
621}
622
623impl core::fmt::Display for MetalBufferAccessError {
624 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
625 match self {
626 Self::CpuInaccessibleStorage { storage_mode } => {
627 write!(
628 formatter,
629 "storage mode {storage_mode} is not CPU-addressable"
630 )
631 }
632 Self::MappingUnavailable => formatter.write_str("Metal did not provide a CPU mapping"),
633 Self::MappingLockPoisoned => formatter.write_str("buffer mapping lock is poisoned"),
634 Self::RangeOutOfBounds {
635 offset,
636 length,
637 buffer_length,
638 } => write!(
639 formatter,
640 "byte range {offset}..{} exceeds buffer length {buffer_length}",
641 offset.saturating_add(*length)
642 ),
643 Self::InvalidRange => formatter.write_str("range end precedes range start"),
644 Self::ManagedStorageRequired { storage_mode } => {
645 write!(
646 formatter,
647 "managed storage required, got mode {storage_mode}"
648 )
649 }
650 }
651 }
652}
653
654impl std::error::Error for MetalBufferAccessError {}
655
656pub struct MetalBufferReadMapping<'a> {
658 pointer: core::ptr::NonNull<u8>,
659 length: usize,
660 _mapping_lock: MutexGuard<'a, ()>,
661}
662
663impl Deref for MetalBufferReadMapping<'_> {
664 type Target = [u8];
665
666 fn deref(&self) -> &Self::Target {
667 unsafe { core::slice::from_raw_parts(self.pointer.as_ptr(), self.length) }
668 }
669}
670
671pub struct MetalBufferWriteMapping<'a> {
673 buffer: &'a MetalBuffer,
674 pointer: core::ptr::NonNull<u8>,
675 length: usize,
676 storage_mode: usize,
677 _mapping_lock: MutexGuard<'a, ()>,
678}
679
680impl Deref for MetalBufferWriteMapping<'_> {
681 type Target = [u8];
682
683 fn deref(&self) -> &Self::Target {
684 unsafe { core::slice::from_raw_parts(self.pointer.as_ptr(), self.length) }
685 }
686}
687
688impl DerefMut for MetalBufferWriteMapping<'_> {
689 fn deref_mut(&mut self) -> &mut Self::Target {
690 unsafe { core::slice::from_raw_parts_mut(self.pointer.as_ptr(), self.length) }
691 }
692}
693
694impl Drop for MetalBufferWriteMapping<'_> {
695 fn drop(&mut self) {
696 if self.storage_mode == storage_mode::MANAGED {
697 unsafe {
698 ffi::am_buffer_did_modify_range(self.buffer.as_ptr(), 0, self.length);
699 }
700 }
701 }
702}
703
704#[derive(Clone)]
706pub struct MetalBuffer {
707 inner: Arc<MetalBufferInner>,
708}
709
710struct MetalBufferInner {
711 ptr: *mut c_void,
712 mapping_lock: Mutex<()>,
713}
714
715unsafe impl Send for MetalBufferInner {}
718unsafe impl Sync for MetalBufferInner {}
719
720impl Drop for MetalBufferInner {
721 fn drop(&mut self) {
722 if !self.ptr.is_null() {
723 unsafe { ffi::am_buffer_release(self.ptr) };
724 self.ptr = ptr::null_mut();
725 }
726 }
727}
728
729#[allow(clippy::missing_errors_doc)]
730impl MetalBuffer {
731 #[must_use]
733 pub fn length(&self) -> usize {
734 unsafe { ffi::am_buffer_length(self.as_ptr()) }
735 }
736
737 #[must_use]
739 pub fn storage_mode(&self) -> usize {
740 unsafe { ffi::am_buffer_storage_mode(self.as_ptr()) }
741 }
742
743 #[must_use]
745 pub fn is_cpu_accessible(&self) -> bool {
746 matches!(
747 self.storage_mode(),
748 storage_mode::SHARED | storage_mode::MANAGED
749 )
750 }
751
752 #[must_use]
757 pub fn new_staging_buffer(&self) -> Option<Self> {
758 let pointer = unsafe { ffi::am_buffer_new_staging_buffer(self.as_ptr()) };
759 if pointer.is_null() {
760 None
761 } else {
762 Some(unsafe { Self::from_retained_ptr(pointer) })
763 }
764 }
765
766 pub unsafe fn map_read(&self) -> Result<MetalBufferReadMapping<'_>, MetalBufferAccessError> {
778 let storage_mode = self.ensure_cpu_accessible()?;
779 let mapping_lock = self.lock_mapping()?;
780 let pointer = core::ptr::NonNull::new(ffi::am_buffer_contents(self.as_ptr()).cast::<u8>())
781 .ok_or(MetalBufferAccessError::MappingUnavailable)?;
782 let _ = storage_mode;
783 Ok(MetalBufferReadMapping {
784 pointer,
785 length: self.length(),
786 _mapping_lock: mapping_lock,
787 })
788 }
789
790 pub unsafe fn map_write(&self) -> Result<MetalBufferWriteMapping<'_>, MetalBufferAccessError> {
803 let storage_mode = self.ensure_cpu_accessible()?;
804 let mapping_lock = self.lock_mapping()?;
805 let pointer = core::ptr::NonNull::new(ffi::am_buffer_contents(self.as_ptr()).cast::<u8>())
806 .ok_or(MetalBufferAccessError::MappingUnavailable)?;
807 Ok(MetalBufferWriteMapping {
808 buffer: self,
809 pointer,
810 length: self.length(),
811 storage_mode,
812 _mapping_lock: mapping_lock,
813 })
814 }
815
816 pub unsafe fn write_bytes(
823 &self,
824 offset: usize,
825 src: &[u8],
826 ) -> Result<(), MetalBufferAccessError> {
827 let end = self.checked_range_end(offset, src.len())?;
828 let mut mapping = self.map_write()?;
829 mapping[offset..end].copy_from_slice(src);
830 drop(mapping);
831 Ok(())
832 }
833
834 pub unsafe fn read_bytes(
841 &self,
842 offset: usize,
843 destination: &mut [u8],
844 ) -> Result<(), MetalBufferAccessError> {
845 let end = self.checked_range_end(offset, destination.len())?;
846 let mapping = self.map_read()?;
847 destination.copy_from_slice(&mapping[offset..end]);
848 drop(mapping);
849 Ok(())
850 }
851
852 #[must_use]
857 pub fn as_ptr(&self) -> *mut c_void {
858 self.inner.ptr
859 }
860
861 fn ensure_cpu_accessible(&self) -> Result<usize, MetalBufferAccessError> {
862 let storage_mode = self.storage_mode();
863 if matches!(storage_mode, storage_mode::SHARED | storage_mode::MANAGED) {
864 Ok(storage_mode)
865 } else {
866 Err(MetalBufferAccessError::CpuInaccessibleStorage { storage_mode })
867 }
868 }
869
870 pub(crate) fn checked_range_end(
871 &self,
872 offset: usize,
873 length: usize,
874 ) -> Result<usize, MetalBufferAccessError> {
875 let end =
876 offset
877 .checked_add(length)
878 .ok_or_else(|| MetalBufferAccessError::RangeOutOfBounds {
879 offset,
880 length,
881 buffer_length: self.length(),
882 })?;
883 let buffer_length = self.length();
884 if end > buffer_length {
885 Err(MetalBufferAccessError::RangeOutOfBounds {
886 offset,
887 length,
888 buffer_length,
889 })
890 } else {
891 Ok(end)
892 }
893 }
894
895 pub(crate) fn lock_mapping(&self) -> Result<MutexGuard<'_, ()>, MetalBufferAccessError> {
896 self.inner
897 .mapping_lock
898 .lock()
899 .map_err(|_| MetalBufferAccessError::MappingLockPoisoned)
900 }
901}
902
903#[derive(Debug, Clone, Copy)]
907pub struct TextureDescriptor {
908 pub pixel_format: usize,
910 pub width: usize,
912 pub height: usize,
914 pub mipmapped: bool,
916 pub usage: usize,
918 pub storage_mode: usize,
920}
921
922impl TextureDescriptor {
923 #[must_use]
925 pub const fn new_2d(width: usize, height: usize, pixel_format: usize) -> Self {
926 Self {
927 pixel_format,
928 width,
929 height,
930 mipmapped: false,
931 usage: texture_usage::SHADER_READ | texture_usage::SHADER_WRITE,
932 storage_mode: storage_mode::SHARED,
933 }
934 }
935}
936
937pub struct MetalTexture {
939 ptr: *mut c_void,
940}
941
942unsafe impl Send for MetalTexture {}
945unsafe impl Sync for MetalTexture {}
946
947impl Drop for MetalTexture {
948 fn drop(&mut self) {
949 if !self.ptr.is_null() {
950 unsafe { ffi::am_texture_release(self.ptr) };
951 self.ptr = ptr::null_mut();
952 }
953 }
954}
955
956impl MetalTexture {
957 #[must_use]
959 pub fn width(&self) -> usize {
960 unsafe { ffi::am_texture_width(self.ptr) }
961 }
962
963 #[must_use]
965 pub fn height(&self) -> usize {
966 unsafe { ffi::am_texture_height(self.ptr) }
967 }
968
969 #[must_use]
971 pub fn pixel_format(&self) -> usize {
972 unsafe { ffi::am_texture_pixel_format(self.ptr) }
973 }
974
975 #[must_use]
977 pub fn texture_type(&self) -> usize {
978 unsafe { ffi::am_texture_type(self.ptr) }
979 }
980
981 #[must_use]
983 pub const fn as_ptr(&self) -> *mut c_void {
984 self.ptr
985 }
986
987 #[must_use]
996 pub const unsafe fn from_raw(ptr: *mut c_void) -> Self {
997 Self { ptr }
998 }
999}
1000
1001impl MetalDevice {
1002 pub(crate) const unsafe fn from_retained_ptr(ptr: *mut c_void) -> Self {
1003 Self {
1004 ptr,
1005 drop_on_release: true,
1006 }
1007 }
1008}
1009
1010impl CommandQueue {
1011 pub(crate) const unsafe fn from_retained_ptr(ptr: *mut c_void) -> Self {
1012 Self { ptr }
1013 }
1014}
1015
1016impl CommandBuffer {
1017 pub(crate) unsafe fn from_retained_ptr(ptr: *mut c_void) -> Self {
1018 Self {
1019 inner: Arc::new(CommandBufferInner {
1020 ptr,
1021 state: Mutex::new(CommandBufferState {
1022 phase: CommandBufferPhase::Recording,
1023 active_encoder: false,
1024 }),
1025 }),
1026 }
1027 }
1028}
1029
1030impl MetalBuffer {
1031 pub(crate) unsafe fn from_retained_ptr(ptr: *mut c_void) -> Self {
1032 Self {
1033 inner: Arc::new(MetalBufferInner {
1034 ptr,
1035 mapping_lock: Mutex::new(()),
1036 }),
1037 }
1038 }
1039}
1040
1041#[cfg(feature = "iosurface")]
1044#[cfg_attr(docsrs, doc(cfg(feature = "iosurface")))]
1045mod iosurface_ext {
1046 use super::{ffi, pixel_format, MetalDevice, MetalTexture};
1047 use apple_cf::iosurface::IOSurface;
1048 use core::ffi::c_void;
1049
1050 #[derive(Debug, Clone, PartialEq, Eq)]
1052 pub enum IOSurfaceMetalError {
1053 InvalidPlane {
1055 plane_index: usize,
1056 plane_count: usize,
1057 },
1058 UnsupportedPixelFormat { fourcc: u32, plane_index: usize },
1060 EmptyPlane {
1062 plane_index: usize,
1063 width: usize,
1064 height: usize,
1065 bytes_per_row: usize,
1066 },
1067 IncompatiblePlaneLayout {
1069 plane_index: usize,
1070 bytes_per_row: usize,
1071 minimum_bytes_per_row: usize,
1072 },
1073 IntegerOutOfRange { field: &'static str, value: usize },
1075 NativeCreationFailed,
1077 }
1078
1079 impl core::fmt::Display for IOSurfaceMetalError {
1080 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1081 match self {
1082 Self::InvalidPlane {
1083 plane_index,
1084 plane_count,
1085 } => write!(
1086 formatter,
1087 "IOSurface plane {plane_index} is outside the available count {plane_count}"
1088 ),
1089 Self::UnsupportedPixelFormat {
1090 fourcc,
1091 plane_index,
1092 } => write!(
1093 formatter,
1094 "IOSurface format {fourcc:#010x} plane {plane_index} is unsupported"
1095 ),
1096 Self::EmptyPlane {
1097 plane_index,
1098 width,
1099 height,
1100 bytes_per_row,
1101 } => write!(
1102 formatter,
1103 "IOSurface plane {plane_index} has invalid layout {width}x{height}, row {bytes_per_row}"
1104 ),
1105 Self::IncompatiblePlaneLayout {
1106 plane_index,
1107 bytes_per_row,
1108 minimum_bytes_per_row,
1109 } => write!(
1110 formatter,
1111 "IOSurface plane {plane_index} row {bytes_per_row} is shorter than {minimum_bytes_per_row}"
1112 ),
1113 Self::IntegerOutOfRange { field, value } => {
1114 write!(formatter, "{field} value {value} exceeds native Int")
1115 }
1116 Self::NativeCreationFailed => {
1117 formatter.write_str("Metal could not create the IOSurface-backed texture")
1118 }
1119 }
1120 }
1121 }
1122
1123 impl std::error::Error for IOSurfaceMetalError {}
1124
1125 #[derive(Clone, Copy)]
1126 struct PlaneTextureFormat {
1127 pixel_format: usize,
1128 bytes_per_pixel: usize,
1129 }
1130
1131 pub trait IOSurfaceMetalExt {
1133 fn create_metal_texture(
1144 &self,
1145 device: &MetalDevice,
1146 plane_index: usize,
1147 ) -> Result<MetalTexture, IOSurfaceMetalError>;
1148 }
1149
1150 impl IOSurfaceMetalExt for IOSurface {
1151 fn create_metal_texture(
1152 &self,
1153 device: &MetalDevice,
1154 plane_index: usize,
1155 ) -> Result<MetalTexture, IOSurfaceMetalError> {
1156 let plane_count = self.plane_count();
1157 let (width, height, bytes_per_row) = if plane_count == 0 {
1158 if plane_index != 0 {
1159 return Err(IOSurfaceMetalError::InvalidPlane {
1160 plane_index,
1161 plane_count: 1,
1162 });
1163 }
1164 (self.width(), self.height(), self.bytes_per_row())
1165 } else {
1166 if plane_index >= plane_count {
1167 return Err(IOSurfaceMetalError::InvalidPlane {
1168 plane_index,
1169 plane_count,
1170 });
1171 }
1172 (
1173 self.width_of_plane(plane_index),
1174 self.height_of_plane(plane_index),
1175 self.bytes_per_row_of_plane(plane_index),
1176 )
1177 };
1178 if width == 0 || height == 0 || bytes_per_row == 0 {
1179 return Err(IOSurfaceMetalError::EmptyPlane {
1180 plane_index,
1181 width,
1182 height,
1183 bytes_per_row,
1184 });
1185 }
1186 let format = pixel_format_for_fourcc(self.pixel_format(), plane_index)?;
1187 let minimum_bytes_per_row = width.checked_mul(format.bytes_per_pixel).ok_or(
1188 IOSurfaceMetalError::IntegerOutOfRange {
1189 field: "minimum plane row bytes",
1190 value: usize::MAX,
1191 },
1192 )?;
1193 if bytes_per_row < minimum_bytes_per_row || bytes_per_row % format.bytes_per_pixel != 0
1194 {
1195 return Err(IOSurfaceMetalError::IncompatiblePlaneLayout {
1196 plane_index,
1197 bytes_per_row,
1198 minimum_bytes_per_row,
1199 });
1200 }
1201 for (field, value) in [
1202 ("plane index", plane_index),
1203 ("plane width", width),
1204 ("plane height", height),
1205 ("pixel format", format.pixel_format),
1206 ] {
1207 if value > isize::MAX as usize {
1208 return Err(IOSurfaceMetalError::IntegerOutOfRange { field, value });
1209 }
1210 }
1211 let p = unsafe {
1212 ffi::am_device_new_texture_from_iosurface(
1213 device.as_ptr(),
1214 self.as_ptr().cast::<c_void>(),
1215 plane_index,
1216 format.pixel_format,
1217 width,
1218 height,
1219 )
1220 };
1221 if p.is_null() {
1222 Err(IOSurfaceMetalError::NativeCreationFailed)
1223 } else {
1224 Ok(unsafe { MetalTexture::from_raw(p) })
1225 }
1226 }
1227 }
1228
1229 fn pixel_format_for_fourcc(
1230 fourcc: u32,
1231 plane_index: usize,
1232 ) -> Result<PlaneTextureFormat, IOSurfaceMetalError> {
1233 const BGRA: u32 = u32::from_be_bytes(*b"BGRA");
1234 const YUV420V: u32 = u32::from_be_bytes(*b"420v");
1235 const YUV420F: u32 = u32::from_be_bytes(*b"420f");
1236
1237 match (fourcc, plane_index) {
1238 (BGRA, 0) => Ok(PlaneTextureFormat {
1239 pixel_format: pixel_format::BGRA8UNORM,
1240 bytes_per_pixel: 4,
1241 }),
1242 (YUV420V | YUV420F, 0) => Ok(PlaneTextureFormat {
1243 pixel_format: pixel_format::R8UNORM,
1244 bytes_per_pixel: 1,
1245 }),
1246 (YUV420V | YUV420F, 1) => Ok(PlaneTextureFormat {
1247 pixel_format: pixel_format::RG8UNORM,
1248 bytes_per_pixel: 2,
1249 }),
1250 _ => Err(IOSurfaceMetalError::UnsupportedPixelFormat {
1251 fourcc,
1252 plane_index,
1253 }),
1254 }
1255 }
1256
1257 #[cfg(test)]
1258 mod tests {
1259 use super::*;
1260
1261 #[test]
1262 fn packed_ten_bit_surface_is_not_guessed() {
1263 let fourcc = u32::from_be_bytes(*b"l10r");
1264 assert!(matches!(
1265 pixel_format_for_fourcc(fourcc, 0),
1266 Err(IOSurfaceMetalError::UnsupportedPixelFormat { .. })
1267 ));
1268 }
1269
1270 #[test]
1271 fn odd_chroma_plane_uses_two_bytes_per_actual_plane_pixel() {
1272 let fourcc = u32::from_be_bytes(*b"420v");
1273 let format = pixel_format_for_fourcc(fourcc, 1).expect("chroma format");
1274 assert_eq!(format.pixel_format, pixel_format::RG8UNORM);
1275 assert_eq!(3 * format.bytes_per_pixel, 6);
1276 }
1277 }
1278}
1279
1280#[cfg(feature = "iosurface")]
1282pub use iosurface_ext::{IOSurfaceMetalError, IOSurfaceMetalExt};
1283
1284#[must_use]
1286pub const fn is_ycbcr_biplanar(fourcc: u32) -> bool {
1287 const YUV420V: u32 = u32::from_be_bytes(*b"420v");
1288 const YUV420F: u32 = u32::from_be_bytes(*b"420f");
1289 matches!(fourcc, YUV420V | YUV420F)
1290}