1#![allow(
11 clippy::missing_safety_doc,
12 clippy::too_many_lines,
13 clippy::doc_markdown,
14 clippy::module_name_repetitions,
15 non_camel_case_types,
16 clippy::upper_case_acronyms,
17 clippy::duplicated_attributes,
18 clippy::missing_const_for_fn,
19 clippy::ptr_as_ptr
20)]
21
22use core::ffi::c_void;
23use core::ptr;
24use std::ffi::CString;
25
26use crate::iosurface::IOSurface;
27
28type id = *mut c_void;
31type SEL = *const c_void;
32type Class = *const c_void;
33
34#[link(name = "Metal", kind = "framework")]
35#[link(name = "Foundation", kind = "framework")]
36#[link(name = "objc")]
37extern "C" {
38 fn MTLCreateSystemDefaultDevice() -> id;
39 fn objc_msgSend();
40 fn sel_registerName(name: *const i8) -> SEL;
41 fn objc_getClass(name: *const i8) -> Class;
42 fn objc_release(obj: id);
43}
44
45unsafe fn sel(name: &str) -> SEL {
46 let c = CString::new(name).expect("selector name has no NUL");
47 sel_registerName(c.as_ptr())
48}
49
50unsafe fn class_named(name: &str) -> Class {
51 let c = CString::new(name).expect("class name has no NUL");
52 objc_getClass(c.as_ptr())
53}
54
55type MsgSend0 = unsafe extern "C" fn(id, SEL) -> id;
57type MsgSendUsize = unsafe extern "C" fn(id, SEL) -> usize;
58type MsgSendU32 = unsafe extern "C" fn(id, SEL) -> u32;
59type MsgSendTextureFromIOSurface =
60 unsafe extern "C" fn(id, SEL, id, *mut c_void, usize) -> id;
61type MsgSendTextureDescriptorInit =
62 unsafe extern "C" fn(id, SEL, u32, usize, usize, bool) -> id;
63type MsgSendSetUsage = unsafe extern "C" fn(id, SEL, usize);
64type MsgSendSetStorage = unsafe extern "C" fn(id, SEL, usize);
65
66pub mod pixel_format {
68 pub const BGRA8UNORM: u32 = 80;
69 pub const RGBA8UNORM: u32 = 70;
70 pub const R8UNORM: u32 = 10;
71 pub const RG8UNORM: u32 = 30;
72 pub const BGRA10_XR: u32 = 552;
73 pub const BGR10_XR: u32 = 554;
74}
75
76const MTL_TEXTURE_USAGE_SHADER_READ: usize = 0x01;
77const MTL_TEXTURE_USAGE_SHADER_WRITE: usize = 0x02;
78const MTL_STORAGE_MODE_SHARED: usize = 0;
79
80pub struct MetalDevice {
84 ptr: id,
85}
86
87unsafe impl Send for MetalDevice {}
88unsafe impl Sync for MetalDevice {}
89
90impl Drop for MetalDevice {
91 fn drop(&mut self) {
92 if !self.ptr.is_null() {
93 unsafe { objc_release(self.ptr) };
94 self.ptr = ptr::null_mut();
95 }
96 }
97}
98
99impl MetalDevice {
100 #[must_use]
102 pub fn system_default() -> Option<Self> {
103 let p = unsafe { MTLCreateSystemDefaultDevice() };
104 if p.is_null() {
105 None
106 } else {
107 Some(Self { ptr: p })
108 }
109 }
110
111 #[must_use]
114 pub const fn as_ptr(&self) -> *mut c_void {
115 self.ptr
116 }
117}
118
119pub struct MetalTexture {
123 ptr: id,
124}
125
126unsafe impl Send for MetalTexture {}
127unsafe impl Sync for MetalTexture {}
128
129impl Drop for MetalTexture {
130 fn drop(&mut self) {
131 if !self.ptr.is_null() {
132 unsafe { objc_release(self.ptr) };
133 self.ptr = ptr::null_mut();
134 }
135 }
136}
137
138impl MetalTexture {
139 #[must_use]
141 pub fn width(&self) -> usize {
142 unsafe {
143 let m: MsgSendUsize = core::mem::transmute(objc_msgSend as *const c_void);
144 m(self.ptr, sel("width"))
145 }
146 }
147
148 #[must_use]
150 pub fn height(&self) -> usize {
151 unsafe {
152 let m: MsgSendUsize = core::mem::transmute(objc_msgSend as *const c_void);
153 m(self.ptr, sel("height"))
154 }
155 }
156
157 #[must_use]
159 pub fn pixel_format(&self) -> u32 {
160 unsafe {
161 let m: MsgSendU32 = core::mem::transmute(objc_msgSend as *const c_void);
162 m(self.ptr, sel("pixelFormat"))
163 }
164 }
165
166 #[must_use]
168 pub const fn as_ptr(&self) -> *mut c_void {
169 self.ptr
170 }
171}
172
173pub trait IOSurfaceMetalExt {
177 fn create_metal_texture(&self, device: &MetalDevice, plane_index: usize)
180 -> Option<MetalTexture>;
181}
182
183impl IOSurfaceMetalExt for IOSurface {
184 fn create_metal_texture(
185 &self,
186 device: &MetalDevice,
187 plane_index: usize,
188 ) -> Option<MetalTexture> {
189 let format = pixel_format_for_fourcc(self.pixel_format(), plane_index)?;
190 let (width, height) = if plane_index == 0 {
191 (self.width(), self.height())
192 } else {
193 (self.width() / 2, self.height() / 2)
194 };
195
196 unsafe {
197 let desc_class = class_named("MTLTextureDescriptor");
198 let m_alloc: MsgSend0 = core::mem::transmute(objc_msgSend as *const c_void);
199 let raw_desc = m_alloc(desc_class.cast_mut(), sel("alloc"));
200 let init: MsgSendTextureDescriptorInit =
201 core::mem::transmute(objc_msgSend as *const c_void);
202 let desc = init(
203 raw_desc,
204 sel("texture2DDescriptorWithPixelFormat:width:height:mipmapped:"),
205 format,
206 width,
207 height,
208 false,
209 );
210 if desc.is_null() {
211 return None;
212 }
213 let set_usage: MsgSendSetUsage =
214 core::mem::transmute(objc_msgSend as *const c_void);
215 set_usage(
216 desc,
217 sel("setUsage:"),
218 MTL_TEXTURE_USAGE_SHADER_READ | MTL_TEXTURE_USAGE_SHADER_WRITE,
219 );
220 let set_storage: MsgSendSetStorage =
221 core::mem::transmute(objc_msgSend as *const c_void);
222 set_storage(desc, sel("setStorageMode:"), MTL_STORAGE_MODE_SHARED);
223
224 let m_tx: MsgSendTextureFromIOSurface =
225 core::mem::transmute(objc_msgSend as *const c_void);
226 let tx = m_tx(
227 device.ptr,
228 sel("newTextureWithDescriptor:iosurface:plane:"),
229 desc,
230 self.as_ptr().cast::<c_void>(),
231 plane_index,
232 );
233 objc_release(desc);
234 if tx.is_null() {
235 None
236 } else {
237 Some(MetalTexture { ptr: tx })
238 }
239 }
240 }
241}
242
243fn pixel_format_for_fourcc(fourcc: u32, plane_index: usize) -> Option<u32> {
245 const BGRA: u32 = u32::from_be_bytes(*b"BGRA");
246 const L10R: u32 = u32::from_be_bytes(*b"l10r");
247 const YUV420V: u32 = u32::from_be_bytes(*b"420v");
248 const YUV420F: u32 = u32::from_be_bytes(*b"420f");
249
250 match (fourcc, plane_index) {
251 (BGRA, 0) => Some(pixel_format::BGRA8UNORM),
252 (L10R, 0) => Some(pixel_format::BGRA10_XR),
253 (YUV420V | YUV420F, 0) => Some(pixel_format::R8UNORM),
254 (YUV420V | YUV420F, 1) => Some(pixel_format::RG8UNORM),
255 _ => None,
256 }
257}
258
259#[must_use]
261pub const fn is_ycbcr_biplanar(fourcc: u32) -> bool {
262 const YUV420V: u32 = u32::from_be_bytes(*b"420v");
263 const YUV420F: u32 = u32::from_be_bytes(*b"420f");
264 matches!(fourcc, YUV420V | YUV420F)
265}