1use std::ffi::{c_void, CStr};
72use std::marker::PhantomData;
73use std::ptr::NonNull;
74use std::sync::{Arc, Mutex, PoisonError};
75
76use crate::cm::IOSurface;
77use crate::FourCharCode;
78
79pub mod pixel_format {
83 use crate::FourCharCode;
84
85 pub const BGRA: FourCharCode = FourCharCode::from_bytes(*b"BGRA");
87
88 pub const L10R: FourCharCode = FourCharCode::from_bytes(*b"l10r");
90
91 pub const YCBCR_420V: FourCharCode = FourCharCode::from_bytes(*b"420v");
93
94 pub const YCBCR_420F: FourCharCode = FourCharCode::from_bytes(*b"420f");
96
97 #[must_use]
101 pub fn is_ycbcr_biplanar(format: impl Into<FourCharCode>) -> bool {
102 let f = format.into();
103 f.equals(YCBCR_420V) || f.equals(YCBCR_420F)
104 }
105
106 #[must_use]
110 pub fn is_full_range(format: impl Into<FourCharCode>) -> bool {
111 format.into().equals(YCBCR_420F)
112 }
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
119#[repr(u64)]
120pub enum MetalPixelFormat {
121 BGRA8Unorm = 80,
123 BGR10A2Unorm = 94,
125 R8Unorm = 10,
127 RG8Unorm = 30,
129}
130
131impl MetalPixelFormat {
132 #[must_use]
134 pub const fn raw(self) -> u64 {
135 self as u64
136 }
137
138 #[must_use]
140 pub const fn from_raw(value: u64) -> Option<Self> {
141 match value {
142 80 => Some(Self::BGRA8Unorm),
143 94 => Some(Self::BGR10A2Unorm),
144 10 => Some(Self::R8Unorm),
145 30 => Some(Self::RG8Unorm),
146 _ => None,
147 }
148 }
149}
150
151#[derive(Debug, Clone)]
153pub struct IOSurfaceInfo {
154 pub width: usize,
156 pub height: usize,
158 pub bytes_per_row: usize,
160 pub pixel_format: FourCharCode,
162 pub plane_count: usize,
164 pub planes: Vec<PlaneInfo>,
166}
167
168#[derive(Debug, Clone)]
170pub struct PlaneInfo {
171 pub index: usize,
173 pub width: usize,
175 pub height: usize,
177 pub bytes_per_row: usize,
179}
180
181#[derive(Debug, Clone, Copy)]
185pub struct TextureParams {
186 pub width: usize,
188 pub height: usize,
190 pub format: MetalPixelFormat,
192 pub plane: usize,
194}
195
196impl TextureParams {
197 #[must_use]
199 pub const fn metal_pixel_format(&self) -> u64 {
200 self.format.raw()
201 }
202}
203
204#[derive(Debug)]
206pub struct CapturedTextures<T> {
207 pub plane0: T,
209 pub plane1: Option<T>,
211 pub pixel_format: FourCharCode,
213 pub width: usize,
215 pub height: usize,
217}
218
219impl<T> CapturedTextures<T> {
220 #[must_use]
222 pub fn is_ycbcr(&self) -> bool {
223 pixel_format::is_ycbcr_biplanar(self.pixel_format)
224 }
225}
226
227pub const SHADER_SOURCE: &str = r"
249#include <metal_stdlib>
250using namespace metal;
251
252struct Uniforms {
253 float2 viewport_size;
254 float2 texture_size;
255 float time;
256 uint pixel_format;
257 float padding[2];
258};
259
260struct TexturedVertexOut {
261 float4 position [[position]];
262 float2 texcoord;
263};
264
265// Fullscreen quad vertex shader with aspect ratio correction
266vertex TexturedVertexOut vertex_fullscreen(uint vid [[vertex_id]], constant Uniforms& uniforms [[buffer(0)]]) {
267 TexturedVertexOut out;
268 float va = uniforms.viewport_size.x / uniforms.viewport_size.y;
269 float ta = uniforms.texture_size.x / uniforms.texture_size.y;
270 float sx = ta > va ? 1.0 : ta / va;
271 float sy = ta > va ? va / ta : 1.0;
272 float2 positions[4] = { float2(-sx, -sy), float2(sx, -sy), float2(-sx, sy), float2(sx, sy) };
273 float2 texcoords[4] = { float2(0.0, 1.0), float2(1.0, 1.0), float2(0.0, 0.0), float2(1.0, 0.0) };
274 out.position = float4(positions[vid], 0.0, 1.0);
275 out.texcoord = texcoords[vid];
276 return out;
277}
278
279// BGRA/RGB texture fragment shader
280fragment float4 fragment_textured(TexturedVertexOut in [[stage_in]], texture2d<float> tex [[texture(0)]]) {
281 constexpr sampler s(mag_filter::linear, min_filter::linear);
282 return tex.sample(s, in.texcoord);
283}
284
285// YCbCr to RGB conversion (BT.709 matrix for HD video)
286float4 ycbcr_to_rgb(float y, float2 cbcr, bool full_range) {
287 float y_adj = full_range ? y : (y - 16.0/255.0) * (255.0/219.0);
288 float2 cbcr_adj = full_range
289 ? cbcr - 0.5
290 : (cbcr - 16.0/255.0) * (255.0/224.0) - 0.5;
291 float cb = cbcr_adj.x;
292 float cr = cbcr_adj.y;
293 // BT.709 conversion matrix
294 float r = y_adj + 1.5748 * cr;
295 float g = y_adj - 0.1873 * cb - 0.4681 * cr;
296 float b = y_adj + 1.8556 * cb;
297 return float4(saturate(float3(r, g, b)), 1.0);
298}
299
300// YCbCr biplanar (420v/420f) fragment shader
301fragment float4 fragment_ycbcr(TexturedVertexOut in [[stage_in]],
302 texture2d<float> y_tex [[texture(0)]],
303 texture2d<float> cbcr_tex [[texture(1)]],
304 constant Uniforms& uniforms [[buffer(0)]]) {
305 constexpr sampler s(mag_filter::linear, min_filter::linear);
306 float y = y_tex.sample(s, in.texcoord).r;
307 float2 cbcr = cbcr_tex.sample(s, in.texcoord).rg;
308 bool full_range = (uniforms.pixel_format == 0x34323066); // '420f'
309 return ycbcr_to_rgb(y, cbcr, full_range);
310}
311
312// Colored vertex input/output for UI overlays
313struct ColoredVertex {
314 float2 position [[attribute(0)]];
315 float4 color [[attribute(1)]];
316};
317
318struct ColoredVertexOut {
319 float4 position [[position]];
320 float4 color;
321};
322
323// Colored vertex shader for UI elements (position in pixels, converted to NDC)
324vertex ColoredVertexOut vertex_colored(ColoredVertex in [[stage_in]], constant Uniforms& uniforms [[buffer(1)]]) {
325 ColoredVertexOut out;
326 float2 ndc = (in.position / uniforms.viewport_size) * 2.0 - 1.0;
327 ndc.y = -ndc.y;
328 out.position = float4(ndc, 0.0, 1.0);
329 out.color = in.color;
330 return out;
331}
332
333// Colored fragment shader for UI elements
334fragment float4 fragment_colored(ColoredVertexOut in [[stage_in]]) {
335 return in.color;
336}
337";
338
339#[repr(C)]
343#[derive(Debug, Clone, Copy, Default)]
344pub struct Uniforms {
345 pub viewport_size: [f32; 2],
347 pub texture_size: [f32; 2],
349 pub time: f32,
351 pub pixel_format: u32,
353 #[doc(hidden)]
355 pub _padding: [f32; 2],
356}
357
358impl Uniforms {
359 pub const BYTE_LEN: usize = 32;
361
362 #[must_use]
364 pub fn new(
365 viewport_width: f32,
366 viewport_height: f32,
367 texture_width: f32,
368 texture_height: f32,
369 ) -> Self {
370 Self {
371 viewport_size: [viewport_width, viewport_height],
372 texture_size: [texture_width, texture_height],
373 time: 0.0,
374 pixel_format: 0,
375 _padding: [0.0; 2],
376 }
377 }
378
379 #[must_use]
396 #[allow(clippy::cast_precision_loss)] pub fn from_captured_textures<T>(
398 viewport_width: f32,
399 viewport_height: f32,
400 textures: &CapturedTextures<T>,
401 ) -> Self {
402 Self {
403 viewport_size: [viewport_width, viewport_height],
404 texture_size: [textures.width as f32, textures.height as f32],
405 time: 0.0,
406 pixel_format: textures.pixel_format.as_u32(),
407 _padding: [0.0; 2],
408 }
409 }
410
411 #[must_use]
421 pub fn with_pixel_format(mut self, format: impl Into<FourCharCode>) -> Self {
422 self.pixel_format = format.into().as_u32();
423 self
424 }
425
426 #[must_use]
428 pub fn with_time(mut self, time: f32) -> Self {
429 self.time = time;
430 self
431 }
432
433 #[must_use]
435 #[allow(clippy::used_underscore_binding)]
436 pub fn to_bytes(self) -> [u8; Self::BYTE_LEN] {
437 let words = [
438 self.viewport_size[0].to_bits(),
439 self.viewport_size[1].to_bits(),
440 self.texture_size[0].to_bits(),
441 self.texture_size[1].to_bits(),
442 self.time.to_bits(),
443 self.pixel_format,
444 self._padding[0].to_bits(),
445 self._padding[1].to_bits(),
446 ];
447 let mut bytes = [0_u8; Self::BYTE_LEN];
448 for (chunk, word) in bytes.chunks_exact_mut(4).zip(words) {
449 chunk.copy_from_slice(&word.to_ne_bytes());
450 }
451 bytes
452 }
453}
454
455#[derive(Debug, Clone, PartialEq, Eq)]
457pub enum MetalError {
458 SwiftIntOverflow {
460 argument: &'static str,
462 value: usize,
464 },
465 IndexOutOfRange {
467 argument: &'static str,
469 index: usize,
471 max_exclusive: usize,
473 },
474 NativeCallRejected {
476 operation: &'static str,
478 },
479 CommandBufferAlreadyCommitted,
481 CommandBufferHasActiveEncoder,
483 RenderEncoderAlreadyActive,
485 RenderEncoderCreationFailed,
487 RenderEncoderEnded,
489 UnknownPixelFormat {
490 raw: u64,
491 },
492}
493
494impl std::fmt::Display for MetalError {
495 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
496 match self {
497 Self::SwiftIntOverflow { argument, value } => {
498 write!(f, "{argument} value {value} exceeds Swift Int")
499 }
500 Self::IndexOutOfRange {
501 argument,
502 index,
503 max_exclusive,
504 } => write!(f, "{argument} index {index} is outside 0..{max_exclusive}"),
505 Self::NativeCallRejected { operation } => {
506 write!(f, "native Metal rejected {operation}")
507 }
508 Self::CommandBufferAlreadyCommitted => f.write_str("command buffer already committed"),
509 Self::CommandBufferHasActiveEncoder => {
510 f.write_str("command buffer has an active encoder")
511 }
512 Self::RenderEncoderAlreadyActive => {
513 f.write_str("command buffer already has an active render encoder")
514 }
515 Self::RenderEncoderCreationFailed => {
516 f.write_str("native render command encoder creation failed")
517 }
518 Self::RenderEncoderEnded => f.write_str("render command encoder already ended"),
519 Self::UnknownPixelFormat { raw } => {
520 write!(
521 f,
522 "texture pixel format {raw} is not a known MetalPixelFormat"
523 )
524 }
525 }
526 }
527}
528
529impl std::error::Error for MetalError {}
530
531#[link(name = "Metal", kind = "framework")]
534extern "C" {}
535
536#[link(name = "QuartzCore", kind = "framework")]
537extern "C" {}
538
539extern "C" {
540 fn metal_create_system_default_device() -> *mut c_void;
542 fn metal_device_release(device: *mut c_void);
543 fn metal_device_get_name(device: *mut c_void) -> *const std::ffi::c_char;
544 fn metal_device_retain(device: *mut c_void) -> *mut c_void;
545 fn metal_string_free(ptr: *mut std::ffi::c_char);
546 fn metal_device_create_command_queue(device: *mut c_void) -> *mut c_void;
547 fn metal_device_create_render_pipeline_state(
548 device: *mut c_void,
549 desc: *mut c_void,
550 ) -> *mut c_void;
551
552 fn metal_create_texture_from_iosurface(
554 device: *mut c_void,
555 iosurface: *mut c_void,
556 plane: usize,
557 width: usize,
558 height: usize,
559 pixel_format: u64,
560 ) -> *mut c_void;
561 fn metal_texture_release(texture: *mut c_void);
562 fn metal_texture_retain(texture: *mut c_void) -> *mut c_void;
563 fn metal_texture_get_width(texture: *mut c_void) -> usize;
564 fn metal_texture_get_height(texture: *mut c_void) -> usize;
565 fn metal_texture_get_pixel_format(texture: *mut c_void) -> u64;
566
567 fn metal_command_queue_release(queue: *mut c_void);
569 fn metal_command_queue_command_buffer(queue: *mut c_void) -> *mut c_void;
570
571 fn metal_device_create_library_with_source(
573 device: *mut c_void,
574 source: *const std::ffi::c_char,
575 error_out: *mut *const std::ffi::c_char,
576 ) -> *mut c_void;
577 fn metal_library_release(library: *mut c_void);
578 fn metal_library_get_function(
579 library: *mut c_void,
580 name: *const std::ffi::c_char,
581 ) -> *mut c_void;
582 fn metal_function_release(function: *mut c_void);
583
584 fn metal_device_create_buffer(device: *mut c_void, length: usize, options: u64) -> *mut c_void;
586 fn metal_buffer_contents(buffer: *mut c_void) -> *mut c_void;
587 fn metal_buffer_length(buffer: *mut c_void) -> usize;
588 fn metal_buffer_did_modify_range(buffer: *mut c_void, location: usize, length: usize);
589 fn metal_buffer_release(buffer: *mut c_void);
590
591 fn metal_layer_create() -> *mut c_void;
593 fn metal_layer_set_device(layer: *mut c_void, device: *mut c_void);
594 fn metal_layer_set_pixel_format(layer: *mut c_void, format: u64);
595 fn metal_layer_set_drawable_size(layer: *mut c_void, width: f64, height: f64);
596 fn metal_layer_set_presents_with_transaction(layer: *mut c_void, value: bool);
597 fn metal_layer_next_drawable(layer: *mut c_void) -> *mut c_void;
598 fn metal_layer_release(layer: *mut c_void);
599
600 fn metal_drawable_texture(drawable: *mut c_void) -> *mut c_void;
602 fn metal_drawable_release(drawable: *mut c_void);
603
604 fn metal_command_buffer_present_drawable(cmd_buffer: *mut c_void, drawable: *mut c_void);
606 fn metal_command_buffer_commit(cmd_buffer: *mut c_void);
607 fn metal_command_buffer_retain(cmd_buffer: *mut c_void) -> *mut c_void;
608 fn metal_command_buffer_release(cmd_buffer: *mut c_void);
609
610 fn metal_render_pass_descriptor_create() -> *mut c_void;
612 fn metal_render_pass_set_color_attachment_texture(
613 desc: *mut c_void,
614 index: usize,
615 texture: *mut c_void,
616 ) -> bool;
617 fn metal_render_pass_set_color_attachment_load_action(
618 desc: *mut c_void,
619 index: usize,
620 action: u64,
621 ) -> bool;
622 fn metal_render_pass_set_color_attachment_store_action(
623 desc: *mut c_void,
624 index: usize,
625 action: u64,
626 ) -> bool;
627 fn metal_render_pass_set_color_attachment_clear_color(
628 desc: *mut c_void,
629 index: usize,
630 r: f64,
631 g: f64,
632 b: f64,
633 a: f64,
634 ) -> bool;
635 fn metal_render_pass_descriptor_release(desc: *mut c_void);
636
637 fn metal_vertex_descriptor_create() -> *mut c_void;
639 fn metal_vertex_descriptor_set_attribute(
640 desc: *mut c_void,
641 index: usize,
642 format: u64,
643 offset: usize,
644 buffer_index: usize,
645 ) -> bool;
646 fn metal_vertex_descriptor_set_layout(
647 desc: *mut c_void,
648 buffer_index: usize,
649 stride: usize,
650 step_function: u64,
651 ) -> bool;
652 fn metal_vertex_descriptor_release(desc: *mut c_void);
653
654 fn metal_render_pipeline_descriptor_create() -> *mut c_void;
656 fn metal_render_pipeline_descriptor_set_vertex_function(
657 desc: *mut c_void,
658 function: *mut c_void,
659 );
660 fn metal_render_pipeline_descriptor_set_fragment_function(
661 desc: *mut c_void,
662 function: *mut c_void,
663 );
664 fn metal_render_pipeline_descriptor_set_vertex_descriptor(
665 desc: *mut c_void,
666 vertex_descriptor: *mut c_void,
667 );
668 fn metal_render_pipeline_descriptor_set_color_attachment_pixel_format(
669 desc: *mut c_void,
670 index: usize,
671 format: u64,
672 ) -> bool;
673 fn metal_render_pipeline_descriptor_set_blending_enabled(
674 desc: *mut c_void,
675 index: usize,
676 enabled: bool,
677 ) -> bool;
678 fn metal_render_pipeline_descriptor_set_blend_operations(
679 desc: *mut c_void,
680 index: usize,
681 rgb_op: u64,
682 alpha_op: u64,
683 ) -> bool;
684 fn metal_render_pipeline_descriptor_set_blend_factors(
685 desc: *mut c_void,
686 index: usize,
687 src_rgb: u64,
688 dst_rgb: u64,
689 src_alpha: u64,
690 dst_alpha: u64,
691 ) -> bool;
692 fn metal_render_pipeline_descriptor_release(desc: *mut c_void);
693 fn metal_render_pipeline_state_release(state: *mut c_void);
694
695 fn metal_command_buffer_render_command_encoder(
697 cmd_buffer: *mut c_void,
698 render_pass: *mut c_void,
699 ) -> *mut c_void;
700 fn metal_render_encoder_set_pipeline_state(encoder: *mut c_void, state: *mut c_void);
701 fn metal_render_encoder_set_vertex_buffer(
702 encoder: *mut c_void,
703 buffer: *mut c_void,
704 offset: usize,
705 index: usize,
706 ) -> bool;
707 fn metal_render_encoder_set_fragment_buffer(
708 encoder: *mut c_void,
709 buffer: *mut c_void,
710 offset: usize,
711 index: usize,
712 ) -> bool;
713 fn metal_render_encoder_set_fragment_texture(
714 encoder: *mut c_void,
715 texture: *mut c_void,
716 index: usize,
717 ) -> bool;
718 fn metal_render_encoder_draw_primitives(
719 encoder: *mut c_void,
720 primitive_type: u64,
721 vertex_start: usize,
722 vertex_count: usize,
723 ) -> bool;
724 fn metal_render_encoder_end_encoding(encoder: *mut c_void);
725 fn metal_render_encoder_retain(encoder: *mut c_void) -> *mut c_void;
726 fn metal_render_encoder_release(encoder: *mut c_void);
727
728 fn nsview_set_wants_layer(view: *mut c_void);
730 fn nsview_set_layer(view: *mut c_void, layer: *mut c_void);
731}
732
733#[derive(Debug)]
739pub struct MetalDevice {
740 ptr: NonNull<c_void>,
741 owned: bool,
744}
745
746impl MetalDevice {
747 #[must_use]
751 pub fn system_default() -> Option<Self> {
752 let ptr = unsafe { metal_create_system_default_device() };
753 NonNull::new(ptr).map(|ptr| Self { ptr, owned: true })
754 }
755
756 #[must_use]
768 pub unsafe fn from_ptr(ptr: *mut c_void) -> Option<Self> {
769 NonNull::new(ptr).map(|ptr| Self { ptr, owned: false })
770 }
771
772 #[must_use]
782 pub unsafe fn from_ptr_retained(ptr: *mut c_void) -> Option<Self> {
783 if ptr.is_null() {
784 return None;
785 }
786 let retained = unsafe { metal_device_retain(ptr) };
787 NonNull::new(retained).map(|ptr| Self { ptr, owned: true })
788 }
789
790 #[must_use]
792 pub fn name(&self) -> String {
793 unsafe {
794 let name_ptr = metal_device_get_name(self.ptr.as_ptr());
795 if name_ptr.is_null() {
796 return String::new();
797 }
798 let name = CStr::from_ptr(name_ptr).to_string_lossy().into_owned();
799 metal_string_free(name_ptr.cast_mut());
801 name
802 }
803 }
804
805 #[must_use]
807 pub fn create_command_queue(&self) -> Option<MetalCommandQueue> {
808 let ptr = unsafe { metal_device_create_command_queue(self.ptr.as_ptr()) };
809 NonNull::new(ptr).map(|ptr| MetalCommandQueue { ptr })
810 }
811
812 pub fn create_library_with_source(&self, source: &str) -> Result<MetalLibrary, String> {
817 use std::ffi::CString;
818 let source_c = CString::new(source).map_err(|e| e.to_string())?;
819 let mut error_ptr: *const std::ffi::c_char = std::ptr::null();
820
821 let ptr = unsafe {
822 metal_device_create_library_with_source(
823 self.ptr.as_ptr(),
824 source_c.as_ptr(),
825 &raw mut error_ptr,
826 )
827 };
828
829 NonNull::new(ptr).map_or_else(
830 || {
831 let error = if error_ptr.is_null() {
832 "Unknown shader compilation error".to_string()
833 } else {
834 let msg = unsafe { CStr::from_ptr(error_ptr).to_string_lossy().into_owned() };
835 unsafe { metal_string_free(error_ptr.cast_mut()) };
837 msg
838 };
839 Err(error)
840 },
841 |ptr| Ok(MetalLibrary { ptr }),
842 )
843 }
844
845 #[must_use]
850 pub fn create_buffer(&self, length: usize, options: ResourceOptions) -> Option<MetalBuffer> {
851 let length = checked_swift_int(length)?;
852 let ptr = unsafe { metal_device_create_buffer(self.ptr.as_ptr(), length, options.0) };
853 NonNull::new(ptr).map(|ptr| MetalBuffer { ptr })
854 }
855
856 #[must_use]
868 pub fn create_buffer_with_bytes(&self, data: &[u8]) -> Option<MetalBuffer> {
869 let buffer =
870 self.create_buffer(data.len(), ResourceOptions::CPU_CACHE_MODE_DEFAULT_CACHE)?;
871 if data.is_empty() {
872 return Some(buffer);
873 }
874 let destination = NonNull::new(buffer.contents().cast::<u8>())?;
875 unsafe {
876 std::ptr::copy_nonoverlapping(data.as_ptr(), destination.as_ptr(), data.len());
877 }
878 Some(buffer)
879 }
880
881 #[must_use]
893 pub unsafe fn create_buffer_with_data<T: Copy>(&self, data: &T) -> Option<MetalBuffer> {
894 let size = std::mem::size_of::<T>();
895 let bytes =
896 unsafe { std::slice::from_raw_parts(std::ptr::from_ref(data).cast::<u8>(), size) };
897 self.create_buffer_with_bytes(bytes)
898 }
899
900 #[must_use]
902 pub fn create_render_pipeline_state(
903 &self,
904 descriptor: &MetalRenderPipelineDescriptor,
905 ) -> Option<MetalRenderPipelineState> {
906 let ptr = unsafe {
907 metal_device_create_render_pipeline_state(self.ptr.as_ptr(), descriptor.as_ptr())
908 };
909 NonNull::new(ptr).map(|ptr| MetalRenderPipelineState { ptr })
910 }
911
912 #[must_use]
914 pub fn as_ptr(&self) -> *mut c_void {
915 self.ptr.as_ptr()
916 }
917
918 #[must_use]
929 pub fn as_apple_metal(&self) -> BorrowedAppleMetalDevice<'_> {
930 BorrowedAppleMetalDevice {
931 inner: unsafe { apple_metal::MetalDevice::from_raw_borrowed(self.ptr.as_ptr()) },
932 _owner: PhantomData,
933 }
934 }
935}
936
937pub struct BorrowedAppleMetalDevice<'a> {
943 inner: apple_metal::ManuallyDropDevice,
944 _owner: PhantomData<&'a MetalDevice>,
945}
946
947impl std::ops::Deref for BorrowedAppleMetalDevice<'_> {
948 type Target = apple_metal::MetalDevice;
949
950 fn deref(&self) -> &Self::Target {
951 &self.inner
952 }
953}
954
955impl std::fmt::Debug for BorrowedAppleMetalDevice<'_> {
956 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
957 f.debug_struct("BorrowedAppleMetalDevice").finish()
958 }
959}
960
961impl Drop for MetalDevice {
962 fn drop(&mut self) {
963 if self.owned {
964 unsafe { metal_device_release(self.ptr.as_ptr()) }
965 }
966 }
967}
968
969unsafe impl Send for MetalDevice {}
972unsafe impl Sync for MetalDevice {}
973
974#[derive(Debug)]
980pub struct MetalTexture {
981 ptr: NonNull<c_void>,
982}
983
984impl MetalTexture {
985 #[must_use]
987 pub fn width(&self) -> usize {
988 unsafe { metal_texture_get_width(self.ptr.as_ptr()) }
989 }
990
991 #[must_use]
993 pub fn height(&self) -> usize {
994 unsafe { metal_texture_get_height(self.ptr.as_ptr()) }
995 }
996
997 #[allow(clippy::missing_errors_doc)]
999 pub fn pixel_format(&self) -> Result<MetalPixelFormat, MetalError> {
1000 let raw = unsafe { metal_texture_get_pixel_format(self.ptr.as_ptr()) };
1001 MetalPixelFormat::from_raw(raw).ok_or(MetalError::UnknownPixelFormat { raw })
1002 }
1003
1004 #[must_use]
1006 pub fn as_ptr(&self) -> *mut c_void {
1007 self.ptr.as_ptr()
1008 }
1009}
1010
1011impl Clone for MetalTexture {
1012 fn clone(&self) -> Self {
1013 let ptr = unsafe { metal_texture_retain(self.ptr.as_ptr()) };
1014 Self {
1019 ptr: NonNull::new(ptr).unwrap_or(self.ptr),
1020 }
1021 }
1022}
1023
1024impl Drop for MetalTexture {
1025 fn drop(&mut self) {
1026 unsafe { metal_texture_release(self.ptr.as_ptr()) }
1027 }
1028}
1029
1030unsafe impl Send for MetalTexture {}
1034unsafe impl Sync for MetalTexture {}
1035
1036#[derive(Debug)]
1040pub struct MetalCommandQueue {
1041 ptr: NonNull<c_void>,
1042}
1043
1044impl MetalCommandQueue {
1045 #[must_use]
1047 pub fn command_buffer(&self) -> Option<MetalCommandBuffer> {
1048 let ptr = unsafe { metal_command_queue_command_buffer(self.ptr.as_ptr()) };
1049 NonNull::new(ptr).map(|ptr| MetalCommandBuffer {
1050 ptr,
1051 state: Arc::new(Mutex::new(CommandBufferState::default())),
1052 })
1053 }
1054
1055 #[must_use]
1057 pub fn as_ptr(&self) -> *mut c_void {
1058 self.ptr.as_ptr()
1059 }
1060}
1061
1062impl Drop for MetalCommandQueue {
1063 fn drop(&mut self) {
1064 unsafe { metal_command_queue_release(self.ptr.as_ptr()) }
1065 }
1066}
1067
1068unsafe impl Send for MetalCommandQueue {}
1071unsafe impl Sync for MetalCommandQueue {}
1072
1073#[derive(Debug)]
1077pub struct MetalLibrary {
1078 ptr: NonNull<c_void>,
1079}
1080
1081impl MetalLibrary {
1082 #[must_use]
1084 pub fn get_function(&self, name: &str) -> Option<MetalFunction> {
1085 use std::ffi::CString;
1086 let name_c = CString::new(name).ok()?;
1087 let ptr = unsafe { metal_library_get_function(self.ptr.as_ptr(), name_c.as_ptr()) };
1088 NonNull::new(ptr).map(|ptr| MetalFunction { ptr })
1089 }
1090
1091 #[must_use]
1093 pub fn as_ptr(&self) -> *mut c_void {
1094 self.ptr.as_ptr()
1095 }
1096}
1097
1098impl Drop for MetalLibrary {
1099 fn drop(&mut self) {
1100 unsafe { metal_library_release(self.ptr.as_ptr()) }
1101 }
1102}
1103
1104unsafe impl Send for MetalLibrary {}
1107unsafe impl Sync for MetalLibrary {}
1108
1109#[derive(Debug)]
1113pub struct MetalFunction {
1114 ptr: NonNull<c_void>,
1115}
1116
1117impl MetalFunction {
1118 #[must_use]
1120 pub fn as_ptr(&self) -> *mut c_void {
1121 self.ptr.as_ptr()
1122 }
1123}
1124
1125impl Drop for MetalFunction {
1126 fn drop(&mut self) {
1127 unsafe { metal_function_release(self.ptr.as_ptr()) }
1128 }
1129}
1130
1131unsafe impl Send for MetalFunction {}
1134unsafe impl Sync for MetalFunction {}
1135
1136#[derive(Debug)]
1140pub struct MetalBuffer {
1141 ptr: NonNull<c_void>,
1142}
1143
1144#[derive(Debug, Clone, Copy, Default)]
1146pub struct ResourceOptions(u64);
1147
1148impl ResourceOptions {
1149 pub const CPU_CACHE_MODE_DEFAULT_CACHE: Self = Self(0);
1151 pub const STORAGE_MODE_SHARED: Self = Self(0);
1153 pub const STORAGE_MODE_MANAGED: Self = Self(1 << 4);
1155}
1156
1157impl MetalBuffer {
1158 #[must_use]
1160 pub fn contents(&self) -> *mut c_void {
1161 unsafe { metal_buffer_contents(self.ptr.as_ptr()) }
1162 }
1163
1164 #[must_use]
1166 pub fn length(&self) -> usize {
1167 unsafe { metal_buffer_length(self.ptr.as_ptr()) }
1168 }
1169
1170 pub fn did_modify_range(&self, range: std::ops::Range<usize>) {
1176 if range.end < range.start
1177 || checked_swift_int(range.start).is_none()
1178 || checked_swift_int(range.end).is_none()
1179 {
1180 return;
1181 }
1182 unsafe { metal_buffer_did_modify_range(self.ptr.as_ptr(), range.start, range.len()) }
1183 }
1184
1185 #[must_use]
1187 pub fn as_ptr(&self) -> *mut c_void {
1188 self.ptr.as_ptr()
1189 }
1190}
1191
1192impl Drop for MetalBuffer {
1193 fn drop(&mut self) {
1194 unsafe { metal_buffer_release(self.ptr.as_ptr()) }
1195 }
1196}
1197
1198unsafe impl Send for MetalBuffer {}
1203unsafe impl Sync for MetalBuffer {}
1204
1205#[derive(Debug)]
1209pub struct MetalLayer {
1210 ptr: NonNull<c_void>,
1211}
1212
1213impl MetalLayer {
1214 #[must_use]
1219 pub fn new() -> Self {
1220 let ptr = unsafe { metal_layer_create() };
1221 Self {
1222 ptr: NonNull::new(ptr).expect("metal_layer_create returned null"),
1223 }
1224 }
1225
1226 pub fn set_device(&self, device: &MetalDevice) {
1228 unsafe { metal_layer_set_device(self.ptr.as_ptr(), device.as_ptr()) }
1229 }
1230
1231 pub fn set_pixel_format(&self, format: MTLPixelFormat) {
1233 unsafe { metal_layer_set_pixel_format(self.ptr.as_ptr(), format.raw()) }
1234 }
1235
1236 pub fn set_drawable_size(&self, width: f64, height: f64) {
1238 unsafe { metal_layer_set_drawable_size(self.ptr.as_ptr(), width, height) }
1239 }
1240
1241 pub fn set_presents_with_transaction(&self, value: bool) {
1243 unsafe { metal_layer_set_presents_with_transaction(self.ptr.as_ptr(), value) }
1244 }
1245
1246 #[must_use]
1248 pub fn next_drawable(&self) -> Option<MetalDrawable> {
1249 let ptr = unsafe { metal_layer_next_drawable(self.ptr.as_ptr()) };
1250 NonNull::new(ptr).map(|ptr| MetalDrawable { ptr })
1251 }
1252
1253 #[must_use]
1255 pub fn as_ptr(&self) -> *mut c_void {
1256 self.ptr.as_ptr()
1257 }
1258}
1259
1260impl Default for MetalLayer {
1261 fn default() -> Self {
1262 Self::new()
1263 }
1264}
1265
1266impl Drop for MetalLayer {
1267 fn drop(&mut self) {
1268 unsafe { metal_layer_release(self.ptr.as_ptr()) }
1269 }
1270}
1271
1272#[derive(Debug)]
1276pub struct MetalDrawable {
1277 ptr: NonNull<c_void>,
1278}
1279
1280impl MetalDrawable {
1281 #[must_use]
1286 pub fn texture(&self) -> MetalTexture {
1287 let ptr = unsafe { metal_drawable_texture(self.ptr.as_ptr()) };
1288 let ptr = NonNull::new(ptr).expect("drawable texture is null");
1290 let retained = unsafe { metal_texture_retain(ptr.as_ptr()) };
1292 MetalTexture {
1293 ptr: NonNull::new(retained).unwrap_or(ptr),
1294 }
1295 }
1296
1297 #[must_use]
1299 pub fn as_ptr(&self) -> *mut c_void {
1300 self.ptr.as_ptr()
1301 }
1302}
1303
1304impl Drop for MetalDrawable {
1305 fn drop(&mut self) {
1306 unsafe { metal_drawable_release(self.ptr.as_ptr()) }
1307 }
1308}
1309
1310#[derive(Debug, Default)]
1313struct CommandBufferState {
1314 committed: bool,
1315 encoder_active: bool,
1316}
1317
1318#[derive(Debug)]
1320pub struct MetalCommandBuffer {
1321 ptr: NonNull<c_void>,
1322 state: Arc<Mutex<CommandBufferState>>,
1323}
1324
1325impl MetalCommandBuffer {
1326 pub fn render_command_encoder(
1333 &self,
1334 render_pass: &MetalRenderPassDescriptor,
1335 ) -> Result<MetalRenderCommandEncoder, MetalError> {
1336 let mut state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
1337 if state.committed {
1338 return Err(MetalError::CommandBufferAlreadyCommitted);
1339 }
1340 if state.encoder_active {
1341 return Err(MetalError::RenderEncoderAlreadyActive);
1342 }
1343 let ptr = unsafe {
1344 metal_command_buffer_render_command_encoder(self.ptr.as_ptr(), render_pass.as_ptr())
1345 };
1346 let ptr = NonNull::new(ptr).ok_or(MetalError::RenderEncoderCreationFailed)?;
1347 state.encoder_active = true;
1348 drop(state);
1349 Ok(MetalRenderCommandEncoder {
1350 ptr,
1351 state: Arc::new(RenderEncoderState {
1352 ended: Mutex::new(false),
1353 command: Arc::clone(&self.state),
1354 }),
1355 })
1356 }
1357
1358 pub fn present_drawable(&self, drawable: &MetalDrawable) -> Result<(), MetalError> {
1364 let state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
1365 if state.committed {
1366 return Err(MetalError::CommandBufferAlreadyCommitted);
1367 }
1368 unsafe { metal_command_buffer_present_drawable(self.ptr.as_ptr(), drawable.as_ptr()) }
1369 drop(state);
1370 Ok(())
1371 }
1372
1373 pub fn commit(&self) -> Result<(), MetalError> {
1379 let mut state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
1380 if state.committed {
1381 return Err(MetalError::CommandBufferAlreadyCommitted);
1382 }
1383 if state.encoder_active {
1384 return Err(MetalError::CommandBufferHasActiveEncoder);
1385 }
1386 unsafe { metal_command_buffer_commit(self.ptr.as_ptr()) }
1387 state.committed = true;
1388 drop(state);
1389 Ok(())
1390 }
1391
1392 #[must_use]
1394 pub fn as_ptr(&self) -> *mut c_void {
1395 self.ptr.as_ptr()
1396 }
1397}
1398
1399impl Clone for MetalCommandBuffer {
1400 fn clone(&self) -> Self {
1401 let ptr = unsafe { metal_command_buffer_retain(self.ptr.as_ptr()) };
1402 Self {
1403 ptr: NonNull::new(ptr).unwrap_or(self.ptr),
1404 state: Arc::clone(&self.state),
1405 }
1406 }
1407}
1408
1409impl Drop for MetalCommandBuffer {
1410 fn drop(&mut self) {
1411 unsafe { metal_command_buffer_release(self.ptr.as_ptr()) }
1412 }
1413}
1414
1415#[derive(Debug)]
1419pub struct MetalRenderPassDescriptor {
1420 ptr: NonNull<c_void>,
1421}
1422
1423#[derive(Debug, Clone, Copy, Default)]
1425#[repr(u64)]
1426pub enum MTLLoadAction {
1427 DontCare = 0,
1429 Load = 1,
1431 #[default]
1433 Clear = 2,
1434}
1435
1436#[derive(Debug, Clone, Copy, Default)]
1438#[repr(u64)]
1439pub enum MTLStoreAction {
1440 DontCare = 0,
1442 #[default]
1444 Store = 1,
1445}
1446
1447#[derive(Debug, Clone, Copy, Default)]
1449#[repr(u64)]
1450pub enum MTLPixelFormat {
1451 Invalid = 0,
1453 #[default]
1455 BGRA8Unorm = 80,
1456 BGR10A2Unorm = 94,
1458 R8Unorm = 10,
1460 RG8Unorm = 30,
1462}
1463
1464impl MTLPixelFormat {
1465 #[must_use]
1467 pub const fn raw(self) -> u64 {
1468 self as u64
1469 }
1470}
1471
1472#[derive(Debug, Clone, Copy, Default)]
1474#[repr(u64)]
1475pub enum MTLVertexFormat {
1476 Invalid = 0,
1478 #[default]
1480 Float2 = 29,
1481 Float3 = 30,
1483 Float4 = 31,
1485}
1486
1487impl MTLVertexFormat {
1488 #[must_use]
1490 pub const fn raw(self) -> u64 {
1491 self as u64
1492 }
1493}
1494
1495#[derive(Debug, Clone, Copy, Default)]
1497#[repr(u64)]
1498pub enum MTLVertexStepFunction {
1499 Constant = 0,
1501 #[default]
1503 PerVertex = 1,
1504 PerInstance = 2,
1506}
1507
1508impl MTLVertexStepFunction {
1509 #[must_use]
1511 pub const fn raw(self) -> u64 {
1512 self as u64
1513 }
1514}
1515
1516#[derive(Debug, Clone, Copy, Default)]
1518#[repr(u64)]
1519pub enum MTLPrimitiveType {
1520 Point = 0,
1522 Line = 1,
1524 LineStrip = 2,
1526 #[default]
1528 Triangle = 3,
1529 TriangleStrip = 4,
1531}
1532
1533impl MTLPrimitiveType {
1534 #[must_use]
1536 pub const fn raw(self) -> u64 {
1537 self as u64
1538 }
1539}
1540
1541#[derive(Debug, Clone, Copy, Default)]
1543#[repr(u64)]
1544pub enum MTLBlendOperation {
1545 #[default]
1547 Add = 0,
1548 Subtract = 1,
1550 ReverseSubtract = 2,
1552 Min = 3,
1554 Max = 4,
1556}
1557
1558#[derive(Debug, Clone, Copy, Default)]
1560#[repr(u64)]
1561pub enum MTLBlendFactor {
1562 Zero = 0,
1564 #[default]
1566 One = 1,
1567 SourceColor = 2,
1569 OneMinusSourceColor = 3,
1571 SourceAlpha = 4,
1573 OneMinusSourceAlpha = 5,
1575 DestinationColor = 6,
1577 OneMinusDestinationColor = 7,
1579 DestinationAlpha = 8,
1581 OneMinusDestinationAlpha = 9,
1583}
1584
1585impl MetalRenderPassDescriptor {
1586 #[must_use]
1591 pub fn new() -> Self {
1592 let ptr = unsafe { metal_render_pass_descriptor_create() };
1593 Self {
1594 ptr: NonNull::new(ptr).expect("render pass descriptor create failed"),
1595 }
1596 }
1597
1598 pub fn set_color_attachment_texture(
1604 &self,
1605 index: usize,
1606 texture: &MetalTexture,
1607 ) -> Result<(), MetalError> {
1608 let index = checked_slot("color attachment", index, MAX_COLOR_ATTACHMENTS)?;
1609 let accepted = unsafe {
1610 metal_render_pass_set_color_attachment_texture(
1611 self.ptr.as_ptr(),
1612 index,
1613 texture.as_ptr(),
1614 )
1615 };
1616 bridge_result(accepted, "set render-pass color texture")
1617 }
1618
1619 pub fn set_color_attachment_load_action(
1625 &self,
1626 index: usize,
1627 action: MTLLoadAction,
1628 ) -> Result<(), MetalError> {
1629 let index = checked_slot("color attachment", index, MAX_COLOR_ATTACHMENTS)?;
1630 let accepted = unsafe {
1631 metal_render_pass_set_color_attachment_load_action(
1632 self.ptr.as_ptr(),
1633 index,
1634 action as u64,
1635 )
1636 };
1637 bridge_result(accepted, "set render-pass load action")
1638 }
1639
1640 pub fn set_color_attachment_store_action(
1646 &self,
1647 index: usize,
1648 action: MTLStoreAction,
1649 ) -> Result<(), MetalError> {
1650 let index = checked_slot("color attachment", index, MAX_COLOR_ATTACHMENTS)?;
1651 let accepted = unsafe {
1652 metal_render_pass_set_color_attachment_store_action(
1653 self.ptr.as_ptr(),
1654 index,
1655 action as u64,
1656 )
1657 };
1658 bridge_result(accepted, "set render-pass store action")
1659 }
1660
1661 pub fn set_color_attachment_clear_color(
1667 &self,
1668 index: usize,
1669 r: f64,
1670 g: f64,
1671 b: f64,
1672 a: f64,
1673 ) -> Result<(), MetalError> {
1674 let index = checked_slot("color attachment", index, MAX_COLOR_ATTACHMENTS)?;
1675 let accepted = unsafe {
1676 metal_render_pass_set_color_attachment_clear_color(self.ptr.as_ptr(), index, r, g, b, a)
1677 };
1678 bridge_result(accepted, "set render-pass clear color")
1679 }
1680
1681 #[must_use]
1683 pub fn as_ptr(&self) -> *mut c_void {
1684 self.ptr.as_ptr()
1685 }
1686}
1687
1688impl Default for MetalRenderPassDescriptor {
1689 fn default() -> Self {
1690 Self::new()
1691 }
1692}
1693
1694impl Drop for MetalRenderPassDescriptor {
1695 fn drop(&mut self) {
1696 unsafe { metal_render_pass_descriptor_release(self.ptr.as_ptr()) }
1697 }
1698}
1699
1700#[derive(Debug)]
1704pub struct MetalVertexDescriptor {
1705 ptr: NonNull<c_void>,
1706}
1707
1708impl MetalVertexDescriptor {
1709 #[must_use]
1714 pub fn new() -> Self {
1715 let ptr = unsafe { metal_vertex_descriptor_create() };
1716 Self {
1717 ptr: NonNull::new(ptr).expect("vertex descriptor create failed"),
1718 }
1719 }
1720
1721 pub fn set_attribute(
1728 &self,
1729 index: usize,
1730 format: MTLVertexFormat,
1731 offset: usize,
1732 buffer_index: usize,
1733 ) -> Result<(), MetalError> {
1734 let index = checked_slot("vertex attribute", index, MAX_VERTEX_ATTRIBUTES)?;
1735 let offset = checked_swift_value("vertex attribute offset", offset)?;
1736 let buffer_index = checked_slot("vertex buffer", buffer_index, MAX_BUFFER_BINDINGS)?;
1737 let accepted = unsafe {
1738 metal_vertex_descriptor_set_attribute(
1739 self.ptr.as_ptr(),
1740 index,
1741 format.raw(),
1742 offset,
1743 buffer_index,
1744 )
1745 };
1746 bridge_result(accepted, "set vertex attribute")
1747 }
1748
1749 pub fn set_layout(
1756 &self,
1757 buffer_index: usize,
1758 stride: usize,
1759 step_function: MTLVertexStepFunction,
1760 ) -> Result<(), MetalError> {
1761 let buffer_index = checked_slot("vertex buffer", buffer_index, MAX_BUFFER_BINDINGS)?;
1762 let stride = checked_swift_value("vertex stride", stride)?;
1763 let accepted = unsafe {
1764 metal_vertex_descriptor_set_layout(
1765 self.ptr.as_ptr(),
1766 buffer_index,
1767 stride,
1768 step_function.raw(),
1769 )
1770 };
1771 bridge_result(accepted, "set vertex layout")
1772 }
1773
1774 #[must_use]
1776 pub fn as_ptr(&self) -> *mut c_void {
1777 self.ptr.as_ptr()
1778 }
1779}
1780
1781impl Default for MetalVertexDescriptor {
1782 fn default() -> Self {
1783 Self::new()
1784 }
1785}
1786
1787impl Drop for MetalVertexDescriptor {
1788 fn drop(&mut self) {
1789 unsafe { metal_vertex_descriptor_release(self.ptr.as_ptr()) }
1790 }
1791}
1792
1793#[derive(Debug)]
1797pub struct MetalRenderPipelineDescriptor {
1798 ptr: NonNull<c_void>,
1799}
1800
1801impl MetalRenderPipelineDescriptor {
1802 #[must_use]
1807 pub fn new() -> Self {
1808 let ptr = unsafe { metal_render_pipeline_descriptor_create() };
1809 Self {
1810 ptr: NonNull::new(ptr).expect("render pipeline descriptor create failed"),
1811 }
1812 }
1813
1814 pub fn set_vertex_function(&self, function: &MetalFunction) {
1816 unsafe {
1817 metal_render_pipeline_descriptor_set_vertex_function(
1818 self.ptr.as_ptr(),
1819 function.as_ptr(),
1820 );
1821 }
1822 }
1823
1824 pub fn set_fragment_function(&self, function: &MetalFunction) {
1826 unsafe {
1827 metal_render_pipeline_descriptor_set_fragment_function(
1828 self.ptr.as_ptr(),
1829 function.as_ptr(),
1830 );
1831 }
1832 }
1833
1834 pub fn set_vertex_descriptor(&self, descriptor: &MetalVertexDescriptor) {
1836 unsafe {
1837 metal_render_pipeline_descriptor_set_vertex_descriptor(
1838 self.ptr.as_ptr(),
1839 descriptor.as_ptr(),
1840 );
1841 }
1842 }
1843
1844 pub fn set_color_attachment_pixel_format(
1850 &self,
1851 index: usize,
1852 format: MTLPixelFormat,
1853 ) -> Result<(), MetalError> {
1854 let index = checked_slot("color attachment", index, MAX_COLOR_ATTACHMENTS)?;
1855 let accepted = unsafe {
1856 metal_render_pipeline_descriptor_set_color_attachment_pixel_format(
1857 self.ptr.as_ptr(),
1858 index,
1859 format.raw(),
1860 )
1861 };
1862 bridge_result(accepted, "set pipeline color format")
1863 }
1864
1865 pub fn set_blending_enabled(&self, index: usize, enabled: bool) -> Result<(), MetalError> {
1871 let index = checked_slot("color attachment", index, MAX_COLOR_ATTACHMENTS)?;
1872 let accepted = unsafe {
1873 metal_render_pipeline_descriptor_set_blending_enabled(self.ptr.as_ptr(), index, enabled)
1874 };
1875 bridge_result(accepted, "set pipeline blending")
1876 }
1877
1878 pub fn set_blend_operations(
1884 &self,
1885 index: usize,
1886 rgb_op: MTLBlendOperation,
1887 alpha_op: MTLBlendOperation,
1888 ) -> Result<(), MetalError> {
1889 let index = checked_slot("color attachment", index, MAX_COLOR_ATTACHMENTS)?;
1890 let accepted = unsafe {
1891 metal_render_pipeline_descriptor_set_blend_operations(
1892 self.ptr.as_ptr(),
1893 index,
1894 rgb_op as u64,
1895 alpha_op as u64,
1896 )
1897 };
1898 bridge_result(accepted, "set pipeline blend operations")
1899 }
1900
1901 pub fn set_blend_factors(
1907 &self,
1908 index: usize,
1909 src_rgb: MTLBlendFactor,
1910 dst_rgb: MTLBlendFactor,
1911 src_alpha: MTLBlendFactor,
1912 dst_alpha: MTLBlendFactor,
1913 ) -> Result<(), MetalError> {
1914 let index = checked_slot("color attachment", index, MAX_COLOR_ATTACHMENTS)?;
1915 let accepted = unsafe {
1916 metal_render_pipeline_descriptor_set_blend_factors(
1917 self.ptr.as_ptr(),
1918 index,
1919 src_rgb as u64,
1920 dst_rgb as u64,
1921 src_alpha as u64,
1922 dst_alpha as u64,
1923 )
1924 };
1925 bridge_result(accepted, "set pipeline blend factors")
1926 }
1927
1928 #[must_use]
1930 pub fn as_ptr(&self) -> *mut c_void {
1931 self.ptr.as_ptr()
1932 }
1933}
1934
1935impl Default for MetalRenderPipelineDescriptor {
1936 fn default() -> Self {
1937 Self::new()
1938 }
1939}
1940
1941impl Drop for MetalRenderPipelineDescriptor {
1942 fn drop(&mut self) {
1943 unsafe { metal_render_pipeline_descriptor_release(self.ptr.as_ptr()) }
1944 }
1945}
1946
1947#[derive(Debug)]
1951pub struct MetalRenderPipelineState {
1952 ptr: NonNull<c_void>,
1953}
1954
1955impl MetalRenderPipelineState {
1956 #[must_use]
1958 pub fn as_ptr(&self) -> *mut c_void {
1959 self.ptr.as_ptr()
1960 }
1961}
1962
1963impl Drop for MetalRenderPipelineState {
1964 fn drop(&mut self) {
1965 unsafe { metal_render_pipeline_state_release(self.ptr.as_ptr()) }
1966 }
1967}
1968
1969unsafe impl Send for MetalRenderPipelineState {}
1972unsafe impl Sync for MetalRenderPipelineState {}
1973
1974#[derive(Debug)]
1977struct RenderEncoderState {
1978 ended: Mutex<bool>,
1979 command: Arc<Mutex<CommandBufferState>>,
1980}
1981
1982impl RenderEncoderState {
1983 fn with_active(
1984 &self,
1985 operation: impl FnOnce() -> Result<(), MetalError>,
1986 ) -> Result<(), MetalError> {
1987 let ended = self.ended.lock().unwrap_or_else(PoisonError::into_inner);
1988 if *ended {
1989 return Err(MetalError::RenderEncoderEnded);
1990 }
1991 let result = operation();
1992 drop(ended);
1993 result
1994 }
1995
1996 fn end(&self, encoder: NonNull<c_void>) -> Result<(), MetalError> {
1997 let mut ended = self.ended.lock().unwrap_or_else(PoisonError::into_inner);
1998 if *ended {
1999 return Err(MetalError::RenderEncoderEnded);
2000 }
2001 unsafe { metal_render_encoder_end_encoding(encoder.as_ptr()) }
2002 *ended = true;
2003 drop(ended);
2004 let mut command = self.command.lock().unwrap_or_else(PoisonError::into_inner);
2005 command.encoder_active = false;
2006 drop(command);
2007 Ok(())
2008 }
2009
2010 fn end_on_drop(&self, encoder: NonNull<c_void>) {
2011 let mut ended = self.ended.lock().unwrap_or_else(PoisonError::into_inner);
2012 if *ended {
2013 return;
2014 }
2015 unsafe { metal_render_encoder_end_encoding(encoder.as_ptr()) }
2016 *ended = true;
2017 drop(ended);
2018 let mut command = self.command.lock().unwrap_or_else(PoisonError::into_inner);
2019 command.encoder_active = false;
2020 drop(command);
2021 }
2022}
2023
2024#[derive(Debug)]
2029pub struct MetalRenderCommandEncoder {
2030 ptr: NonNull<c_void>,
2031 state: Arc<RenderEncoderState>,
2032}
2033
2034impl MetalRenderCommandEncoder {
2035 pub fn set_render_pipeline_state(
2041 &self,
2042 pipeline: &MetalRenderPipelineState,
2043 ) -> Result<(), MetalError> {
2044 self.state.with_active(|| {
2045 unsafe {
2046 metal_render_encoder_set_pipeline_state(self.ptr.as_ptr(), pipeline.as_ptr());
2047 }
2048 Ok(())
2049 })
2050 }
2051
2052 pub fn set_vertex_buffer(
2059 &self,
2060 buffer: &MetalBuffer,
2061 offset: usize,
2062 index: usize,
2063 ) -> Result<(), MetalError> {
2064 self.state.with_active(|| {
2065 let offset = checked_swift_value("vertex buffer offset", offset)?;
2066 let index = checked_slot("vertex buffer", index, MAX_BUFFER_BINDINGS)?;
2067 let accepted = unsafe {
2068 metal_render_encoder_set_vertex_buffer(
2069 self.ptr.as_ptr(),
2070 buffer.as_ptr(),
2071 offset,
2072 index,
2073 )
2074 };
2075 bridge_result(accepted, "set vertex buffer")
2076 })
2077 }
2078
2079 pub fn set_fragment_buffer(
2086 &self,
2087 buffer: &MetalBuffer,
2088 offset: usize,
2089 index: usize,
2090 ) -> Result<(), MetalError> {
2091 self.state.with_active(|| {
2092 let offset = checked_swift_value("fragment buffer offset", offset)?;
2093 let index = checked_slot("fragment buffer", index, MAX_BUFFER_BINDINGS)?;
2094 let accepted = unsafe {
2095 metal_render_encoder_set_fragment_buffer(
2096 self.ptr.as_ptr(),
2097 buffer.as_ptr(),
2098 offset,
2099 index,
2100 )
2101 };
2102 bridge_result(accepted, "set fragment buffer")
2103 })
2104 }
2105
2106 pub fn set_fragment_texture(
2113 &self,
2114 texture: &MetalTexture,
2115 index: usize,
2116 ) -> Result<(), MetalError> {
2117 self.state.with_active(|| {
2118 let index = checked_slot("fragment texture", index, MAX_TEXTURE_BINDINGS)?;
2119 let accepted = unsafe {
2120 metal_render_encoder_set_fragment_texture(
2121 self.ptr.as_ptr(),
2122 texture.as_ptr(),
2123 index,
2124 )
2125 };
2126 bridge_result(accepted, "set fragment texture")
2127 })
2128 }
2129
2130 pub fn draw_primitives(
2137 &self,
2138 primitive_type: MTLPrimitiveType,
2139 vertex_start: usize,
2140 vertex_count: usize,
2141 ) -> Result<(), MetalError> {
2142 self.state.with_active(|| {
2143 let vertex_start = checked_swift_value("vertex start", vertex_start)?;
2144 let vertex_count = checked_swift_value("vertex count", vertex_count)?;
2145 let accepted = unsafe {
2146 metal_render_encoder_draw_primitives(
2147 self.ptr.as_ptr(),
2148 primitive_type.raw(),
2149 vertex_start,
2150 vertex_count,
2151 )
2152 };
2153 bridge_result(accepted, "draw primitives")
2154 })
2155 }
2156
2157 pub fn end_encoding(&self) -> Result<(), MetalError> {
2164 self.state.end(self.ptr)
2165 }
2166
2167 #[must_use]
2169 pub fn as_ptr(&self) -> *mut c_void {
2170 self.ptr.as_ptr()
2171 }
2172}
2173
2174impl Clone for MetalRenderCommandEncoder {
2175 fn clone(&self) -> Self {
2176 let ptr = unsafe { metal_render_encoder_retain(self.ptr.as_ptr()) };
2177 Self {
2178 ptr: NonNull::new(ptr).unwrap_or(self.ptr),
2179 state: Arc::clone(&self.state),
2180 }
2181 }
2182}
2183
2184impl Drop for MetalRenderCommandEncoder {
2185 fn drop(&mut self) {
2186 if Arc::strong_count(&self.state) == 1 {
2187 self.state.end_on_drop(self.ptr);
2188 }
2189 unsafe { metal_render_encoder_release(self.ptr.as_ptr()) }
2190 }
2191}
2192
2193pub type MetalCapturedTextures = CapturedTextures<MetalTexture>;
2197
2198pub trait IOSurfaceMetalExt {
2208 fn info(&self) -> IOSurfaceInfo;
2210 fn is_ycbcr_biplanar(&self) -> bool;
2212 fn texture_params(&self) -> Vec<TextureParams>;
2214 fn metal_textures<T, F>(&self, create_texture: F) -> Option<CapturedTextures<T>>
2216 where
2217 F: Fn(&TextureParams, *const c_void) -> Option<T>;
2218 fn create_metal_textures(&self, device: &MetalDevice) -> Option<MetalCapturedTextures>;
2220}
2221
2222impl IOSurfaceMetalExt for IOSurface {
2223 fn info(&self) -> IOSurfaceInfo {
2225 let width = self.width();
2226 let height = self.height();
2227 let bytes_per_row = self.bytes_per_row();
2228 let pix_format: FourCharCode = self.pixel_format().into();
2229 let plane_count = self.plane_count();
2230
2231 let planes = if plane_count > 0 {
2232 (0..plane_count)
2233 .map(|i| PlaneInfo {
2234 index: i,
2235 width: self.width_of_plane(i),
2236 height: self.height_of_plane(i),
2237 bytes_per_row: self.bytes_per_row_of_plane(i),
2238 })
2239 .collect()
2240 } else {
2241 vec![]
2242 };
2243
2244 IOSurfaceInfo {
2245 width,
2246 height,
2247 bytes_per_row,
2248 pixel_format: pix_format,
2249 plane_count,
2250 planes,
2251 }
2252 }
2253
2254 fn is_ycbcr_biplanar(&self) -> bool {
2256 pixel_format::is_ycbcr_biplanar(self.pixel_format())
2257 }
2258
2259 fn texture_params(&self) -> Vec<TextureParams> {
2265 let pix_format: FourCharCode = self.pixel_format().into();
2266 let plane_count = self.plane_count();
2267
2268 if pix_format == pixel_format::BGRA {
2269 vec![TextureParams {
2270 width: self.width(),
2271 height: self.height(),
2272 format: MetalPixelFormat::BGRA8Unorm,
2273 plane: 0,
2274 }]
2275 } else if pix_format == pixel_format::L10R {
2276 vec![TextureParams {
2277 width: self.width(),
2278 height: self.height(),
2279 format: MetalPixelFormat::BGR10A2Unorm,
2280 plane: 0,
2281 }]
2282 } else if pixel_format::is_ycbcr_biplanar(pix_format) && plane_count >= 2 {
2283 vec![
2284 TextureParams {
2286 width: self.width_of_plane(0),
2287 height: self.height_of_plane(0),
2288 format: MetalPixelFormat::R8Unorm,
2289 plane: 0,
2290 },
2291 TextureParams {
2293 width: self.width_of_plane(1),
2294 height: self.height_of_plane(1),
2295 format: MetalPixelFormat::RG8Unorm,
2296 plane: 1,
2297 },
2298 ]
2299 } else {
2300 vec![TextureParams {
2302 width: self.width(),
2303 height: self.height(),
2304 format: MetalPixelFormat::BGRA8Unorm,
2305 plane: 0,
2306 }]
2307 }
2308 }
2309
2310 fn metal_textures<T, F>(&self, create_texture: F) -> Option<CapturedTextures<T>>
2344 where
2345 F: Fn(&TextureParams, *const c_void) -> Option<T>,
2346 {
2347 let width = self.width();
2348 let height = self.height();
2349 let pix_format: FourCharCode = self.pixel_format().into();
2350
2351 if width == 0 || height == 0 {
2352 return None;
2353 }
2354
2355 let iosurface_ptr = self.as_ptr();
2356 let params = self.texture_params();
2357
2358 if params.len() == 1 {
2359 let texture = create_texture(¶ms[0], iosurface_ptr)?;
2361 Some(CapturedTextures {
2362 plane0: texture,
2363 plane1: None,
2364 pixel_format: pix_format,
2365 width,
2366 height,
2367 })
2368 } else if params.len() >= 2 {
2369 let y_texture = create_texture(¶ms[0], iosurface_ptr)?;
2371 let uv_texture = create_texture(¶ms[1], iosurface_ptr)?;
2372 Some(CapturedTextures {
2373 plane0: y_texture,
2374 plane1: Some(uv_texture),
2375 pixel_format: pix_format,
2376 width,
2377 height,
2378 })
2379 } else {
2380 None
2381 }
2382 }
2383
2384 fn create_metal_textures(&self, device: &MetalDevice) -> Option<MetalCapturedTextures> {
2404 let width = self.width();
2405 let height = self.height();
2406 let pix_format: FourCharCode = self.pixel_format().into();
2407
2408 if width == 0 || height == 0 {
2409 return None;
2410 }
2411
2412 let params = self.texture_params();
2413
2414 if params.len() == 1 {
2415 let texture = create_texture_for_plane(self, device, ¶ms[0])?;
2417 Some(CapturedTextures {
2418 plane0: texture,
2419 plane1: None,
2420 pixel_format: pix_format,
2421 width,
2422 height,
2423 })
2424 } else if params.len() >= 2 {
2425 let y_texture = create_texture_for_plane(self, device, ¶ms[0])?;
2427 let uv_texture = create_texture_for_plane(self, device, ¶ms[1])?;
2428 Some(CapturedTextures {
2429 plane0: y_texture,
2430 plane1: Some(uv_texture),
2431 pixel_format: pix_format,
2432 width,
2433 height,
2434 })
2435 } else {
2436 None
2437 }
2438 }
2439}
2440
2441fn create_texture_for_plane(
2446 surface: &IOSurface,
2447 device: &MetalDevice,
2448 params: &TextureParams,
2449) -> Option<MetalTexture> {
2450 checked_swift_int(params.plane)?;
2453 checked_swift_int(params.width)?;
2454 checked_swift_int(params.height)?;
2455 let ptr = unsafe {
2456 metal_create_texture_from_iosurface(
2457 device.as_ptr(),
2458 surface.as_ptr(),
2459 params.plane,
2460 params.width,
2461 params.height,
2462 params.format.raw(),
2463 )
2464 };
2465 NonNull::new(ptr).map(|ptr| MetalTexture { ptr })
2466}
2467
2468const fn checked_swift_int(value: usize) -> Option<usize> {
2474 if value > isize::MAX as usize {
2475 None
2476 } else {
2477 Some(value)
2478 }
2479}
2480
2481const MAX_COLOR_ATTACHMENTS: usize = 8;
2482const MAX_VERTEX_ATTRIBUTES: usize = 31;
2483const MAX_BUFFER_BINDINGS: usize = 31;
2484const MAX_TEXTURE_BINDINGS: usize = 128;
2485
2486fn checked_swift_value(argument: &'static str, value: usize) -> Result<usize, MetalError> {
2487 checked_swift_int(value).ok_or(MetalError::SwiftIntOverflow { argument, value })
2488}
2489
2490fn checked_slot(
2491 argument: &'static str,
2492 index: usize,
2493 max_exclusive: usize,
2494) -> Result<usize, MetalError> {
2495 let index = checked_swift_value(argument, index)?;
2496 if index >= max_exclusive {
2497 Err(MetalError::IndexOutOfRange {
2498 argument,
2499 index,
2500 max_exclusive,
2501 })
2502 } else {
2503 Ok(index)
2504 }
2505}
2506
2507fn bridge_result(accepted: bool, operation: &'static str) -> Result<(), MetalError> {
2508 if accepted {
2509 Ok(())
2510 } else {
2511 Err(MetalError::NativeCallRejected { operation })
2512 }
2513}
2514
2515#[link(name = "Foundation", kind = "framework")]
2518extern "C" {
2519 fn objc_autoreleasePoolPush() -> *mut c_void;
2520 fn objc_autoreleasePoolPop(pool: *mut c_void);
2521}
2522
2523struct AutoreleasePoolGuard {
2526 pool: *mut c_void,
2527}
2528
2529impl Drop for AutoreleasePoolGuard {
2530 fn drop(&mut self) {
2531 unsafe { objc_autoreleasePoolPop(self.pool) }
2532 }
2533}
2534
2535pub fn autoreleasepool<F, R>(f: F) -> R
2556where
2557 F: FnOnce() -> R,
2558{
2559 let _guard = AutoreleasePoolGuard {
2560 pool: unsafe { objc_autoreleasePoolPush() },
2561 };
2562 f()
2563}
2564
2565pub unsafe fn setup_metal_view(view: *mut c_void, layer: &MetalLayer) {
2587 unsafe {
2588 nsview_set_wants_layer(view);
2589 nsview_set_layer(view, layer.as_ptr());
2590 }
2591}