screencapturekit/
metal.rs

1//! Metal texture helpers for `IOSurface`
2//!
3//! This module provides utilities for creating Metal textures from `IOSurface`
4//! with zero-copy GPU access. This is the most efficient way to use captured
5//! frames with Metal rendering.
6//!
7//! ## Features
8//!
9//! - Zero-copy texture creation from `IOSurface`
10//! - Automatic pixel format detection and Metal format mapping
11//! - Multi-plane support for YCbCr formats (420v, 420f)
12//! - Native Metal device and texture types (no external crate needed)
13//! - Embedded Metal shaders for common rendering scenarios
14//!
15//! ## When to Use
16//!
17//! Use this module when you need:
18//! - **Real-time rendering** - Display captured frames in a Metal view
19//! - **GPU processing** - Apply compute shaders to captured content
20//! - **Zero-copy performance** - Avoid CPU-GPU memory transfers
21//!
22//! For CPU-based processing, use [`CVPixelBuffer`](crate::cv::CVPixelBuffer) with lock guards instead.
23//!
24//! ## Workflow
25//!
26//! 1. Get `IOSurface` from captured frame via [`CMSampleBuffer::image_buffer()`](crate::cm::CMSampleBuffer::image_buffer)
27//! 2. Create Metal textures with [`IOSurface::create_metal_textures()`](crate::cm::IOSurface::create_metal_textures)
28//! 3. Render using the built-in shaders or your own
29//!
30//! ## Example
31//!
32//! ```no_run
33//! use screencapturekit::metal::MetalDevice;
34//! use screencapturekit::cm::{CMSampleBuffer, IOSurface};
35//!
36//! // Get the system default Metal device
37//! let device = MetalDevice::system_default().expect("No Metal device");
38//!
39//! // In your frame handler
40//! fn handle_frame(sample: &CMSampleBuffer, device: &MetalDevice) {
41//!     if let Some(pixel_buffer) = sample.image_buffer() {
42//!         if let Some(surface) = pixel_buffer.io_surface() {
43//!             // Create textures directly - no closures or factories needed
44//!             if let Some(textures) = surface.create_metal_textures(device) {
45//!                 if textures.is_ycbcr() {
46//!                     // Use YCbCr shader with plane0 (Y) and plane1 (CbCr)
47//!                     println!("YCbCr texture: {}x{}",
48//!                         textures.plane0.width(), textures.plane0.height());
49//!                 } else {
50//!                     // Use single-plane shader (BGRA, l10r)
51//!                     println!("Single-plane texture: {}x{}",
52//!                         textures.plane0.width(), textures.plane0.height());
53//!                 }
54//!             }
55//!         }
56//!     }
57//! }
58//! ```
59//!
60//! ## Built-in Shaders
61//!
62//! The [`SHADER_SOURCE`] constant contains Metal shaders for common rendering scenarios:
63//!
64//! | Function | Description |
65//! |----------|-------------|
66//! | `vertex_fullscreen` | Aspect-ratio-preserving fullscreen quad |
67//! | `fragment_textured` | BGRA/L10R single-texture rendering |
68//! | `fragment_ycbcr` | YCbCr biplanar (420v/420f) to RGB conversion |
69//! | `vertex_colored` / `fragment_colored` | UI overlay rendering |
70
71use std::ffi::{c_void, CStr};
72use std::ptr::NonNull;
73
74use crate::cm::IOSurface;
75use crate::FourCharCode;
76
77/// Pixel format constants using [`FourCharCode`]
78///
79/// These match the values returned by `IOSurface::pixel_format()`.
80pub mod pixel_format {
81    use crate::FourCharCode;
82
83    /// BGRA 8-bit per channel (32-bit total)
84    pub const BGRA: FourCharCode = FourCharCode::from_bytes(*b"BGRA");
85
86    /// 10-bit RGB (ARGB2101010, also known as l10r)
87    pub const L10R: FourCharCode = FourCharCode::from_bytes(*b"l10r");
88
89    /// YCbCr 4:2:0 biplanar, video range
90    pub const YCBCR_420V: FourCharCode = FourCharCode::from_bytes(*b"420v");
91
92    /// YCbCr 4:2:0 biplanar, full range
93    pub const YCBCR_420F: FourCharCode = FourCharCode::from_bytes(*b"420f");
94
95    /// Check if a pixel format is a YCbCr biplanar format
96    ///
97    /// Accepts either a `FourCharCode` or a raw `u32`.
98    #[must_use]
99    pub fn is_ycbcr_biplanar(format: impl Into<FourCharCode>) -> bool {
100        let f = format.into();
101        f.equals(YCBCR_420V) || f.equals(YCBCR_420F)
102    }
103
104    /// Check if a pixel format uses full range (vs video range)
105    ///
106    /// Accepts either a `FourCharCode` or a raw `u32`.
107    #[must_use]
108    pub fn is_full_range(format: impl Into<FourCharCode>) -> bool {
109        format.into().equals(YCBCR_420F)
110    }
111}
112
113/// Metal pixel format enum matching `MTLPixelFormat` values
114///
115/// This provides a Rust-native enum for common Metal pixel formats used in screen capture.
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
117#[repr(u64)]
118pub enum MetalPixelFormat {
119    /// 8-bit normalized unsigned integer per channel (BGRA order)
120    BGRA8Unorm = 80,
121    /// 10-bit RGB with 2-bit alpha (BGR order)
122    BGR10A2Unorm = 94,
123    /// 8-bit normalized unsigned integer (single channel, for Y plane)
124    R8Unorm = 10,
125    /// 8-bit normalized unsigned integer per channel (two channels, for `CbCr` plane)
126    RG8Unorm = 30,
127}
128
129impl MetalPixelFormat {
130    /// Get the raw `MTLPixelFormat` value
131    #[must_use]
132    pub const fn raw(self) -> u64 {
133        self as u64
134    }
135
136    /// Create from a raw `MTLPixelFormat` value
137    #[must_use]
138    pub const fn from_raw(value: u64) -> Option<Self> {
139        match value {
140            80 => Some(Self::BGRA8Unorm),
141            94 => Some(Self::BGR10A2Unorm),
142            10 => Some(Self::R8Unorm),
143            30 => Some(Self::RG8Unorm),
144            _ => None,
145        }
146    }
147}
148
149/// Information about an `IOSurface` for Metal texture creation
150#[derive(Debug, Clone)]
151pub struct IOSurfaceInfo {
152    /// Width in pixels
153    pub width: usize,
154    /// Height in pixels
155    pub height: usize,
156    /// Bytes per row
157    pub bytes_per_row: usize,
158    /// Pixel format
159    pub pixel_format: FourCharCode,
160    /// Number of planes (0 for single-plane formats, 2 for YCbCr biplanar)
161    pub plane_count: usize,
162    /// Per-plane information
163    pub planes: Vec<PlaneInfo>,
164}
165
166/// Information about a single plane within an `IOSurface`
167#[derive(Debug, Clone)]
168pub struct PlaneInfo {
169    /// Plane index
170    pub index: usize,
171    /// Width in pixels
172    pub width: usize,
173    /// Height in pixels
174    pub height: usize,
175    /// Bytes per row
176    pub bytes_per_row: usize,
177}
178
179impl IOSurface {
180    /// Get detailed information about this `IOSurface` for Metal texture creation
181    #[must_use]
182    pub fn info(&self) -> IOSurfaceInfo {
183        let width = self.width();
184        let height = self.height();
185        let bytes_per_row = self.bytes_per_row();
186        let pix_format: FourCharCode = self.pixel_format().into();
187        let plane_count = self.plane_count();
188
189        let planes = if plane_count > 0 {
190            (0..plane_count)
191                .map(|i| PlaneInfo {
192                    index: i,
193                    width: self.width_of_plane(i),
194                    height: self.height_of_plane(i),
195                    bytes_per_row: self.bytes_per_row_of_plane(i),
196                })
197                .collect()
198        } else {
199            vec![]
200        };
201
202        IOSurfaceInfo {
203            width,
204            height,
205            bytes_per_row,
206            pixel_format: pix_format,
207            plane_count,
208            planes,
209        }
210    }
211
212    /// Check if this `IOSurface` uses a YCbCr biplanar format
213    #[must_use]
214    pub fn is_ycbcr_biplanar(&self) -> bool {
215        pixel_format::is_ycbcr_biplanar(self.pixel_format())
216    }
217}
218
219/// Metal texture descriptor parameters for creating textures from `IOSurface`
220///
221/// This provides the information needed to configure a Metal `MTLTextureDescriptor`.
222#[derive(Debug, Clone, Copy)]
223pub struct TextureParams {
224    /// Width in pixels
225    pub width: usize,
226    /// Height in pixels
227    pub height: usize,
228    /// Recommended Metal pixel format
229    pub format: MetalPixelFormat,
230    /// Plane index for multi-planar surfaces
231    pub plane: usize,
232}
233
234impl TextureParams {
235    /// Get the raw `MTLPixelFormat` value for use with Metal APIs
236    #[must_use]
237    pub const fn metal_pixel_format(&self) -> u64 {
238        self.format.raw()
239    }
240}
241
242impl IOSurface {
243    /// Get texture parameters for creating Metal textures from this `IOSurface`
244    ///
245    /// Returns texture parameters for each plane needed to render this surface.
246    /// - Single-plane formats (BGRA, L10R): Returns 1 texture param
247    /// - YCbCr biplanar formats: Returns 2 texture params (Y and `CbCr` planes)
248    #[must_use]
249    pub fn texture_params(&self) -> Vec<TextureParams> {
250        let pix_format: FourCharCode = self.pixel_format().into();
251        let plane_count = self.plane_count();
252
253        if pix_format == pixel_format::BGRA {
254            vec![TextureParams {
255                width: self.width(),
256                height: self.height(),
257                format: MetalPixelFormat::BGRA8Unorm,
258                plane: 0,
259            }]
260        } else if pix_format == pixel_format::L10R {
261            vec![TextureParams {
262                width: self.width(),
263                height: self.height(),
264                format: MetalPixelFormat::BGR10A2Unorm,
265                plane: 0,
266            }]
267        } else if pixel_format::is_ycbcr_biplanar(pix_format) && plane_count >= 2 {
268            vec![
269                // Plane 0: Y (luminance) - R8Unorm
270                TextureParams {
271                    width: self.width_of_plane(0),
272                    height: self.height_of_plane(0),
273                    format: MetalPixelFormat::R8Unorm,
274                    plane: 0,
275                },
276                // Plane 1: CbCr (chrominance) - RG8Unorm
277                TextureParams {
278                    width: self.width_of_plane(1),
279                    height: self.height_of_plane(1),
280                    format: MetalPixelFormat::RG8Unorm,
281                    plane: 1,
282                },
283            ]
284        } else {
285            // Fallback to BGRA
286            vec![TextureParams {
287                width: self.width(),
288                height: self.height(),
289                format: MetalPixelFormat::BGRA8Unorm,
290                plane: 0,
291            }]
292        }
293    }
294}
295
296/// Result of creating Metal textures from an `IOSurface`
297#[derive(Debug)]
298pub struct CapturedTextures<T> {
299    /// Primary texture (BGRA/L10R for single-plane, Y plane for YCbCr)
300    pub plane0: T,
301    /// Secondary texture (`CbCr` plane for YCbCr formats)
302    pub plane1: Option<T>,
303    /// The pixel format of the source surface
304    pub pixel_format: FourCharCode,
305    /// Width in pixels
306    pub width: usize,
307    /// Height in pixels
308    pub height: usize,
309}
310
311impl<T> CapturedTextures<T> {
312    /// Check if this capture uses a YCbCr biplanar format
313    #[must_use]
314    pub fn is_ycbcr(&self) -> bool {
315        pixel_format::is_ycbcr_biplanar(self.pixel_format)
316    }
317}
318
319impl IOSurface {
320    /// Create Metal textures from this `IOSurface` using a closure
321    ///
322    /// This is a zero-copy operation - the textures share memory with the `IOSurface`.
323    ///
324    /// The closure receives `TextureParams` and the raw `IOSurfaceRef` pointer,
325    /// and should return the created texture.
326    ///
327    /// # Example
328    ///
329    /// ```no_run
330    /// use screencapturekit::cm::IOSurface;
331    /// use std::ffi::c_void;
332    ///
333    /// fn example(surface: &IOSurface) {
334    ///     let textures = surface.metal_textures(|params, _iosurface_ptr| {
335    ///         // Create Metal texture using params.width, params.height, params.format
336    ///         // Return Some(texture) or None
337    ///         Some(()) // placeholder
338    ///     });
339    ///
340    ///     if let Some(textures) = textures {
341    ///         if textures.is_ycbcr() {
342    ///             // Use YCbCr shader with plane0 (Y) and plane1 (CbCr)
343    ///         }
344    ///     }
345    /// }
346    /// ```
347    ///
348    /// # Safety
349    ///
350    /// The closure receives a raw `IOSurfaceRef` pointer. The pointer is valid
351    /// for the duration of the closure call.
352    pub fn metal_textures<T, F>(&self, create_texture: F) -> Option<CapturedTextures<T>>
353    where
354        F: Fn(&TextureParams, *const c_void) -> Option<T>,
355    {
356        let width = self.width();
357        let height = self.height();
358        let pix_format: FourCharCode = self.pixel_format().into();
359
360        if width == 0 || height == 0 {
361            return None;
362        }
363
364        let iosurface_ptr = self.as_ptr();
365        let params = self.texture_params();
366
367        if params.len() == 1 {
368            // Single-plane format
369            let texture = create_texture(&params[0], iosurface_ptr)?;
370            Some(CapturedTextures {
371                plane0: texture,
372                plane1: None,
373                pixel_format: pix_format,
374                width,
375                height,
376            })
377        } else if params.len() >= 2 {
378            // YCbCr biplanar format
379            let y_texture = create_texture(&params[0], iosurface_ptr)?;
380            let uv_texture = create_texture(&params[1], iosurface_ptr)?;
381            Some(CapturedTextures {
382                plane0: y_texture,
383                plane1: Some(uv_texture),
384                pixel_format: pix_format,
385                width,
386                height,
387            })
388        } else {
389            None
390        }
391    }
392}
393
394/// Metal shader source for rendering captured frames
395///
396/// This shader supports:
397/// - BGRA and BGR10A2 single-plane formats
398/// - YCbCr 4:2:0 biplanar formats (420v and 420f)
399/// - Aspect-ratio-preserving fullscreen quad
400///
401/// ## Uniforms
402///
403/// The shader expects a `Uniforms` buffer:
404/// - `viewport_size: float2` - Current viewport dimensions
405/// - `texture_size: float2` - Source texture dimensions
406/// - `time: float` - Animation time (optional)
407/// - `pixel_format: uint` - `FourCC` pixel format code
408///
409/// ## Usage
410///
411/// 1. Compile shader with `device.new_library_with_source(SHADER_SOURCE, ...)`
412/// 2. Create pipeline with `vertex_fullscreen` + `fragment_textured` (for BGRA/L10R)
413/// 3. Or use `vertex_fullscreen` + `fragment_ycbcr` (for 420v/420f)
414/// 4. Bind plane0 to texture slot 0, plane1 to texture slot 1 (for YCbCr)
415pub const SHADER_SOURCE: &str = r"
416#include <metal_stdlib>
417using namespace metal;
418
419struct Uniforms {
420    float2 viewport_size;
421    float2 texture_size;
422    float time;
423    uint pixel_format;
424    float padding[2];
425};
426
427struct TexturedVertexOut {
428    float4 position [[position]];
429    float2 texcoord;
430};
431
432// Fullscreen quad vertex shader with aspect ratio correction
433vertex TexturedVertexOut vertex_fullscreen(uint vid [[vertex_id]], constant Uniforms& uniforms [[buffer(0)]]) {
434    TexturedVertexOut out;
435    float va = uniforms.viewport_size.x / uniforms.viewport_size.y;
436    float ta = uniforms.texture_size.x / uniforms.texture_size.y;
437    float sx = ta > va ? 1.0 : ta / va;
438    float sy = ta > va ? va / ta : 1.0;
439    float2 positions[4] = { float2(-sx, -sy), float2(sx, -sy), float2(-sx, sy), float2(sx, sy) };
440    float2 texcoords[4] = { float2(0.0, 1.0), float2(1.0, 1.0), float2(0.0, 0.0), float2(1.0, 0.0) };
441    out.position = float4(positions[vid], 0.0, 1.0);
442    out.texcoord = texcoords[vid];
443    return out;
444}
445
446// BGRA/RGB texture fragment shader
447fragment float4 fragment_textured(TexturedVertexOut in [[stage_in]], texture2d<float> tex [[texture(0)]]) {
448    constexpr sampler s(mag_filter::linear, min_filter::linear);
449    return tex.sample(s, in.texcoord);
450}
451
452// YCbCr to RGB conversion (BT.709 matrix for HD video)
453float4 ycbcr_to_rgb(float y, float2 cbcr, bool full_range) {
454    float y_adj = full_range ? y : (y - 16.0/255.0) * (255.0/219.0);
455    float cb = cbcr.x - 0.5;
456    float cr = cbcr.y - 0.5;
457    // BT.709 conversion matrix
458    float r = y_adj + 1.5748 * cr;
459    float g = y_adj - 0.1873 * cb - 0.4681 * cr;
460    float b = y_adj + 1.8556 * cb;
461    return float4(saturate(float3(r, g, b)), 1.0);
462}
463
464// YCbCr biplanar (420v/420f) fragment shader
465fragment float4 fragment_ycbcr(TexturedVertexOut in [[stage_in]], 
466    texture2d<float> y_tex [[texture(0)]], 
467    texture2d<float> cbcr_tex [[texture(1)]],
468    constant Uniforms& uniforms [[buffer(0)]]) {
469    constexpr sampler s(mag_filter::linear, min_filter::linear);
470    float y = y_tex.sample(s, in.texcoord).r;
471    float2 cbcr = cbcr_tex.sample(s, in.texcoord).rg;
472    bool full_range = (uniforms.pixel_format == 0x34323066); // '420f'
473    return ycbcr_to_rgb(y, cbcr, full_range);
474}
475
476// Colored vertex input/output for UI overlays
477struct ColoredVertex {
478    float2 position [[attribute(0)]];
479    float4 color [[attribute(1)]];
480};
481
482struct ColoredVertexOut {
483    float4 position [[position]];
484    float4 color;
485};
486
487// Colored vertex shader for UI elements (position in pixels, converted to NDC)
488vertex ColoredVertexOut vertex_colored(ColoredVertex in [[stage_in]], constant Uniforms& uniforms [[buffer(1)]]) {
489    ColoredVertexOut out;
490    float2 ndc = (in.position / uniforms.viewport_size) * 2.0 - 1.0;
491    ndc.y = -ndc.y;
492    out.position = float4(ndc, 0.0, 1.0);
493    out.color = in.color;
494    return out;
495}
496
497// Colored fragment shader for UI elements
498fragment float4 fragment_colored(ColoredVertexOut in [[stage_in]]) {
499    return in.color;
500}
501";
502
503/// Uniforms structure for Metal shaders
504///
505/// This matches the layout expected by `SHADER_SOURCE`.
506#[repr(C)]
507#[derive(Debug, Clone, Copy, Default)]
508pub struct Uniforms {
509    /// Viewport width and height
510    pub viewport_size: [f32; 2],
511    /// Texture width and height
512    pub texture_size: [f32; 2],
513    /// Animation time (optional)
514    pub time: f32,
515    /// Pixel format (raw u32 for GPU compatibility)
516    pub pixel_format: u32,
517    /// Padding for alignment
518    #[doc(hidden)]
519    pub _padding: [f32; 2],
520}
521
522impl Uniforms {
523    /// Create uniforms for a given viewport and texture size
524    #[must_use]
525    pub fn new(
526        viewport_width: f32,
527        viewport_height: f32,
528        texture_width: f32,
529        texture_height: f32,
530    ) -> Self {
531        Self {
532            viewport_size: [viewport_width, viewport_height],
533            texture_size: [texture_width, texture_height],
534            time: 0.0,
535            pixel_format: 0,
536            _padding: [0.0; 2],
537        }
538    }
539
540    /// Create uniforms from viewport size and captured textures
541    ///
542    /// Automatically extracts texture dimensions and pixel format.
543    ///
544    /// # Example
545    ///
546    /// ```no_run
547    /// use screencapturekit::metal::{MetalDevice, Uniforms};
548    /// use screencapturekit::cm::IOSurface;
549    ///
550    /// fn example(surface: &IOSurface, device: &MetalDevice) {
551    ///     if let Some(textures) = surface.create_metal_textures(device) {
552    ///         let uniforms = Uniforms::from_captured_textures(1920.0, 1080.0, &textures);
553    ///     }
554    /// }
555    /// ```
556    #[must_use]
557    #[allow(clippy::cast_precision_loss)] // Screen dimensions will fit in f32
558    pub fn from_captured_textures<T>(
559        viewport_width: f32,
560        viewport_height: f32,
561        textures: &CapturedTextures<T>,
562    ) -> Self {
563        Self {
564            viewport_size: [viewport_width, viewport_height],
565            texture_size: [textures.width as f32, textures.height as f32],
566            time: 0.0,
567            pixel_format: textures.pixel_format.as_u32(),
568            _padding: [0.0; 2],
569        }
570    }
571
572    /// Set the pixel format
573    ///
574    /// Accepts either a `FourCharCode` or a raw `u32`:
575    /// ```no_run
576    /// use screencapturekit::metal::{Uniforms, pixel_format};
577    ///
578    /// let uniforms = Uniforms::new(1920.0, 1080.0, 1920.0, 1080.0)
579    ///     .with_pixel_format(pixel_format::BGRA);
580    /// ```
581    #[must_use]
582    pub fn with_pixel_format(mut self, format: impl Into<FourCharCode>) -> Self {
583        self.pixel_format = format.into().as_u32();
584        self
585    }
586
587    /// Set the animation time
588    #[must_use]
589    pub fn with_time(mut self, time: f32) -> Self {
590        self.time = time;
591        self
592    }
593}
594
595// MARK: - FFI Declarations
596
597#[link(name = "Metal", kind = "framework")]
598extern "C" {}
599
600#[link(name = "QuartzCore", kind = "framework")]
601extern "C" {}
602
603extern "C" {
604    // Device
605    fn metal_create_system_default_device() -> *mut c_void;
606    fn metal_device_release(device: *mut c_void);
607    fn metal_device_get_name(device: *mut c_void) -> *const std::ffi::c_char;
608    fn metal_device_create_command_queue(device: *mut c_void) -> *mut c_void;
609    fn metal_device_create_render_pipeline_state(
610        device: *mut c_void,
611        desc: *mut c_void,
612    ) -> *mut c_void;
613
614    // Texture
615    fn metal_create_texture_from_iosurface(
616        device: *mut c_void,
617        iosurface: *mut c_void,
618        plane: usize,
619        width: usize,
620        height: usize,
621        pixel_format: u64,
622    ) -> *mut c_void;
623    fn metal_texture_release(texture: *mut c_void);
624    fn metal_texture_retain(texture: *mut c_void) -> *mut c_void;
625    fn metal_texture_get_width(texture: *mut c_void) -> usize;
626    fn metal_texture_get_height(texture: *mut c_void) -> usize;
627    fn metal_texture_get_pixel_format(texture: *mut c_void) -> u64;
628
629    // Command Queue
630    fn metal_command_queue_release(queue: *mut c_void);
631    fn metal_command_queue_command_buffer(queue: *mut c_void) -> *mut c_void;
632
633    // Library/Function
634    fn metal_device_create_library_with_source(
635        device: *mut c_void,
636        source: *const std::ffi::c_char,
637        error_out: *mut *const std::ffi::c_char,
638    ) -> *mut c_void;
639    fn metal_library_release(library: *mut c_void);
640    fn metal_library_get_function(
641        library: *mut c_void,
642        name: *const std::ffi::c_char,
643    ) -> *mut c_void;
644    fn metal_function_release(function: *mut c_void);
645
646    // Buffer
647    fn metal_device_create_buffer(device: *mut c_void, length: usize, options: u64) -> *mut c_void;
648    fn metal_buffer_contents(buffer: *mut c_void) -> *mut c_void;
649    fn metal_buffer_length(buffer: *mut c_void) -> usize;
650    fn metal_buffer_did_modify_range(buffer: *mut c_void, location: usize, length: usize);
651    fn metal_buffer_release(buffer: *mut c_void);
652
653    // Layer
654    fn metal_layer_create() -> *mut c_void;
655    fn metal_layer_set_device(layer: *mut c_void, device: *mut c_void);
656    fn metal_layer_set_pixel_format(layer: *mut c_void, format: u64);
657    fn metal_layer_set_drawable_size(layer: *mut c_void, width: f64, height: f64);
658    fn metal_layer_set_presents_with_transaction(layer: *mut c_void, value: bool);
659    fn metal_layer_next_drawable(layer: *mut c_void) -> *mut c_void;
660    fn metal_layer_release(layer: *mut c_void);
661
662    // Drawable
663    fn metal_drawable_texture(drawable: *mut c_void) -> *mut c_void;
664    fn metal_drawable_release(drawable: *mut c_void);
665
666    // Command Buffer
667    fn metal_command_buffer_present_drawable(cmd_buffer: *mut c_void, drawable: *mut c_void);
668    fn metal_command_buffer_commit(cmd_buffer: *mut c_void);
669    fn metal_command_buffer_release(cmd_buffer: *mut c_void);
670
671    // Render Pass
672    fn metal_render_pass_descriptor_create() -> *mut c_void;
673    fn metal_render_pass_set_color_attachment_texture(
674        desc: *mut c_void,
675        index: usize,
676        texture: *mut c_void,
677    );
678    fn metal_render_pass_set_color_attachment_load_action(
679        desc: *mut c_void,
680        index: usize,
681        action: u64,
682    );
683    fn metal_render_pass_set_color_attachment_store_action(
684        desc: *mut c_void,
685        index: usize,
686        action: u64,
687    );
688    fn metal_render_pass_set_color_attachment_clear_color(
689        desc: *mut c_void,
690        index: usize,
691        r: f64,
692        g: f64,
693        b: f64,
694        a: f64,
695    );
696    fn metal_render_pass_descriptor_release(desc: *mut c_void);
697
698    // Vertex Descriptor
699    fn metal_vertex_descriptor_create() -> *mut c_void;
700    fn metal_vertex_descriptor_set_attribute(
701        desc: *mut c_void,
702        index: usize,
703        format: u64,
704        offset: usize,
705        buffer_index: usize,
706    );
707    fn metal_vertex_descriptor_set_layout(
708        desc: *mut c_void,
709        buffer_index: usize,
710        stride: usize,
711        step_function: u64,
712    );
713    fn metal_vertex_descriptor_release(desc: *mut c_void);
714
715    // Render Pipeline Descriptor
716    fn metal_render_pipeline_descriptor_create() -> *mut c_void;
717    fn metal_render_pipeline_descriptor_set_vertex_function(
718        desc: *mut c_void,
719        function: *mut c_void,
720    );
721    fn metal_render_pipeline_descriptor_set_fragment_function(
722        desc: *mut c_void,
723        function: *mut c_void,
724    );
725    fn metal_render_pipeline_descriptor_set_vertex_descriptor(
726        desc: *mut c_void,
727        vertex_descriptor: *mut c_void,
728    );
729    fn metal_render_pipeline_descriptor_set_color_attachment_pixel_format(
730        desc: *mut c_void,
731        index: usize,
732        format: u64,
733    );
734    fn metal_render_pipeline_descriptor_set_blending_enabled(
735        desc: *mut c_void,
736        index: usize,
737        enabled: bool,
738    );
739    fn metal_render_pipeline_descriptor_set_blend_operations(
740        desc: *mut c_void,
741        index: usize,
742        rgb_op: u64,
743        alpha_op: u64,
744    );
745    fn metal_render_pipeline_descriptor_set_blend_factors(
746        desc: *mut c_void,
747        index: usize,
748        src_rgb: u64,
749        dst_rgb: u64,
750        src_alpha: u64,
751        dst_alpha: u64,
752    );
753    fn metal_render_pipeline_descriptor_release(desc: *mut c_void);
754    fn metal_render_pipeline_state_release(state: *mut c_void);
755
756    // Render Command Encoder
757    fn metal_command_buffer_render_command_encoder(
758        cmd_buffer: *mut c_void,
759        render_pass: *mut c_void,
760    ) -> *mut c_void;
761    fn metal_render_encoder_set_pipeline_state(encoder: *mut c_void, state: *mut c_void);
762    fn metal_render_encoder_set_vertex_buffer(
763        encoder: *mut c_void,
764        buffer: *mut c_void,
765        offset: usize,
766        index: usize,
767    );
768    fn metal_render_encoder_set_fragment_buffer(
769        encoder: *mut c_void,
770        buffer: *mut c_void,
771        offset: usize,
772        index: usize,
773    );
774    fn metal_render_encoder_set_fragment_texture(
775        encoder: *mut c_void,
776        texture: *mut c_void,
777        index: usize,
778    );
779    fn metal_render_encoder_draw_primitives(
780        encoder: *mut c_void,
781        primitive_type: u64,
782        vertex_start: usize,
783        vertex_count: usize,
784    );
785    fn metal_render_encoder_end_encoding(encoder: *mut c_void);
786    fn metal_render_encoder_release(encoder: *mut c_void);
787
788    // NSView helpers
789    fn nsview_set_wants_layer(view: *mut c_void);
790    fn nsview_set_layer(view: *mut c_void, layer: *mut c_void);
791}
792
793// MARK: - Metal Device
794
795/// A Metal device (GPU)
796///
797/// This is a wrapper around `MTLDevice` that provides safe access to Metal functionality.
798#[derive(Debug)]
799pub struct MetalDevice {
800    ptr: NonNull<c_void>,
801}
802
803impl MetalDevice {
804    /// Get the system default Metal device
805    ///
806    /// Returns `None` if no Metal device is available.
807    #[must_use]
808    pub fn system_default() -> Option<Self> {
809        let ptr = unsafe { metal_create_system_default_device() };
810        NonNull::new(ptr).map(|ptr| Self { ptr })
811    }
812
813    /// Create a `MetalDevice` from a raw `MTLDevice` pointer
814    ///
815    /// This is useful when you already have a device from another source
816    /// (e.g., the `metal` crate) and want to use it for texture creation.
817    ///
818    /// # Safety
819    ///
820    /// The pointer must be a valid `MTLDevice` pointer. The device will NOT
821    /// be released when this wrapper is dropped - use `from_ptr_retained` if
822    /// you want the wrapper to own the device.
823    #[must_use]
824    pub unsafe fn from_ptr(ptr: *mut c_void) -> Option<Self> {
825        NonNull::new(ptr).map(|ptr| Self { ptr })
826    }
827
828    /// Create a `MetalDevice` from a raw `MTLDevice` pointer, retaining it
829    ///
830    /// # Safety
831    ///
832    /// The pointer must be a valid `MTLDevice` pointer.
833    #[must_use]
834    pub unsafe fn from_ptr_retained(ptr: *mut c_void) -> Option<Self> {
835        if ptr.is_null() {
836            return None;
837        }
838        // We don't have a retain function exposed, so we create from system default
839        // and verify it's the same device
840        NonNull::new(ptr).map(|ptr| Self { ptr })
841    }
842
843    /// Get the name of this device
844    #[must_use]
845    pub fn name(&self) -> String {
846        unsafe {
847            let name_ptr = metal_device_get_name(self.ptr.as_ptr());
848            if name_ptr.is_null() {
849                return String::new();
850            }
851            CStr::from_ptr(name_ptr).to_string_lossy().into_owned()
852        }
853    }
854
855    /// Create a command queue for this device
856    #[must_use]
857    pub fn create_command_queue(&self) -> Option<MetalCommandQueue> {
858        let ptr = unsafe { metal_device_create_command_queue(self.ptr.as_ptr()) };
859        NonNull::new(ptr).map(|ptr| MetalCommandQueue { ptr })
860    }
861
862    /// Create a shader library from source code
863    ///
864    /// # Errors
865    /// Returns an error message if shader compilation fails.
866    pub fn create_library_with_source(&self, source: &str) -> Result<MetalLibrary, String> {
867        use std::ffi::CString;
868        let source_c = CString::new(source).map_err(|e| e.to_string())?;
869        let mut error_ptr: *const std::ffi::c_char = std::ptr::null();
870
871        let ptr = unsafe {
872            metal_device_create_library_with_source(
873                self.ptr.as_ptr(),
874                source_c.as_ptr(),
875                &mut error_ptr,
876            )
877        };
878
879        NonNull::new(ptr).map_or_else(
880            || {
881                let error = if error_ptr.is_null() {
882                    "Unknown shader compilation error".to_string()
883                } else {
884                    unsafe { CStr::from_ptr(error_ptr).to_string_lossy().into_owned() }
885                };
886                Err(error)
887            },
888            |ptr| Ok(MetalLibrary { ptr }),
889        )
890    }
891
892    /// Create a buffer
893    #[must_use]
894    pub fn create_buffer(&self, length: usize, options: ResourceOptions) -> Option<MetalBuffer> {
895        let ptr = unsafe { metal_device_create_buffer(self.ptr.as_ptr(), length, options.0) };
896        NonNull::new(ptr).map(|ptr| MetalBuffer { ptr })
897    }
898
899    /// Create a buffer and populate it with the given data
900    ///
901    /// This is a convenience method that creates a buffer, copies the data,
902    /// and returns the buffer. Useful for uniform buffers or vertex data.
903    ///
904    /// # Example
905    ///
906    /// ```no_run
907    /// use screencapturekit::metal::{MetalDevice, Uniforms};
908    ///
909    /// fn example() {
910    ///     let device = MetalDevice::system_default().expect("No Metal device");
911    ///     let uniforms = Uniforms::new(1920.0, 1080.0, 1920.0, 1080.0);
912    ///     let buffer = device.create_buffer_with_data(&uniforms);
913    /// }
914    /// ```
915    #[must_use]
916    pub fn create_buffer_with_data<T>(&self, data: &T) -> Option<MetalBuffer> {
917        let size = std::mem::size_of::<T>();
918        let buffer = self.create_buffer(size, ResourceOptions::CPU_CACHE_MODE_DEFAULT_CACHE)?;
919        unsafe {
920            std::ptr::copy_nonoverlapping(
921                std::ptr::addr_of!(*data).cast::<u8>(),
922                buffer.contents().cast(),
923                size,
924            );
925        }
926        Some(buffer)
927    }
928
929    /// Create a render pipeline state from a descriptor
930    #[must_use]
931    pub fn create_render_pipeline_state(
932        &self,
933        descriptor: &MetalRenderPipelineDescriptor,
934    ) -> Option<MetalRenderPipelineState> {
935        let ptr = unsafe {
936            metal_device_create_render_pipeline_state(self.ptr.as_ptr(), descriptor.as_ptr())
937        };
938        NonNull::new(ptr).map(|ptr| MetalRenderPipelineState { ptr })
939    }
940
941    /// Get the raw pointer to the underlying `MTLDevice`
942    #[must_use]
943    pub fn as_ptr(&self) -> *mut c_void {
944        self.ptr.as_ptr()
945    }
946}
947
948impl Drop for MetalDevice {
949    fn drop(&mut self) {
950        unsafe { metal_device_release(self.ptr.as_ptr()) }
951    }
952}
953
954unsafe impl Send for MetalDevice {}
955unsafe impl Sync for MetalDevice {}
956
957// MARK: - Metal Texture
958
959/// A Metal texture
960///
961/// This is a wrapper around `MTLTexture` that provides safe access.
962#[derive(Debug)]
963pub struct MetalTexture {
964    ptr: NonNull<c_void>,
965}
966
967impl MetalTexture {
968    /// Get the width of this texture
969    #[must_use]
970    pub fn width(&self) -> usize {
971        unsafe { metal_texture_get_width(self.ptr.as_ptr()) }
972    }
973
974    /// Get the height of this texture
975    #[must_use]
976    pub fn height(&self) -> usize {
977        unsafe { metal_texture_get_height(self.ptr.as_ptr()) }
978    }
979
980    /// Get the pixel format of this texture
981    #[must_use]
982    pub fn pixel_format(&self) -> MetalPixelFormat {
983        let raw = unsafe { metal_texture_get_pixel_format(self.ptr.as_ptr()) };
984        MetalPixelFormat::from_raw(raw).unwrap_or(MetalPixelFormat::BGRA8Unorm)
985    }
986
987    /// Get the raw pointer to the underlying `MTLTexture`
988    #[must_use]
989    pub fn as_ptr(&self) -> *mut c_void {
990        self.ptr.as_ptr()
991    }
992}
993
994impl Clone for MetalTexture {
995    fn clone(&self) -> Self {
996        let ptr = unsafe { metal_texture_retain(self.ptr.as_ptr()) };
997        Self {
998            ptr: NonNull::new(ptr).expect("metal_texture_retain returned null"),
999        }
1000    }
1001}
1002
1003impl Drop for MetalTexture {
1004    fn drop(&mut self) {
1005        unsafe { metal_texture_release(self.ptr.as_ptr()) }
1006    }
1007}
1008
1009unsafe impl Send for MetalTexture {}
1010unsafe impl Sync for MetalTexture {}
1011
1012// MARK: - Metal Command Queue
1013
1014/// A Metal command queue
1015#[derive(Debug)]
1016pub struct MetalCommandQueue {
1017    ptr: NonNull<c_void>,
1018}
1019
1020impl MetalCommandQueue {
1021    /// Create a command buffer
1022    #[must_use]
1023    pub fn command_buffer(&self) -> Option<MetalCommandBuffer> {
1024        let ptr = unsafe { metal_command_queue_command_buffer(self.ptr.as_ptr()) };
1025        NonNull::new(ptr).map(|ptr| MetalCommandBuffer { ptr })
1026    }
1027
1028    /// Get the raw pointer to the underlying `MTLCommandQueue`
1029    #[must_use]
1030    pub fn as_ptr(&self) -> *mut c_void {
1031        self.ptr.as_ptr()
1032    }
1033}
1034
1035impl Drop for MetalCommandQueue {
1036    fn drop(&mut self) {
1037        unsafe { metal_command_queue_release(self.ptr.as_ptr()) }
1038    }
1039}
1040
1041unsafe impl Send for MetalCommandQueue {}
1042unsafe impl Sync for MetalCommandQueue {}
1043
1044// MARK: - Metal Library
1045
1046/// A Metal shader library
1047#[derive(Debug)]
1048pub struct MetalLibrary {
1049    ptr: NonNull<c_void>,
1050}
1051
1052impl MetalLibrary {
1053    /// Get a function from this library by name
1054    #[must_use]
1055    pub fn get_function(&self, name: &str) -> Option<MetalFunction> {
1056        use std::ffi::CString;
1057        let name_c = CString::new(name).ok()?;
1058        let ptr = unsafe { metal_library_get_function(self.ptr.as_ptr(), name_c.as_ptr()) };
1059        NonNull::new(ptr).map(|ptr| MetalFunction { ptr })
1060    }
1061
1062    /// Get the raw pointer to the underlying `MTLLibrary`
1063    #[must_use]
1064    pub fn as_ptr(&self) -> *mut c_void {
1065        self.ptr.as_ptr()
1066    }
1067}
1068
1069impl Drop for MetalLibrary {
1070    fn drop(&mut self) {
1071        unsafe { metal_library_release(self.ptr.as_ptr()) }
1072    }
1073}
1074
1075unsafe impl Send for MetalLibrary {}
1076unsafe impl Sync for MetalLibrary {}
1077
1078// MARK: - Metal Function
1079
1080/// A Metal shader function
1081#[derive(Debug)]
1082pub struct MetalFunction {
1083    ptr: NonNull<c_void>,
1084}
1085
1086impl MetalFunction {
1087    /// Get the raw pointer to the underlying `MTLFunction`
1088    #[must_use]
1089    pub fn as_ptr(&self) -> *mut c_void {
1090        self.ptr.as_ptr()
1091    }
1092}
1093
1094impl Drop for MetalFunction {
1095    fn drop(&mut self) {
1096        unsafe { metal_function_release(self.ptr.as_ptr()) }
1097    }
1098}
1099
1100unsafe impl Send for MetalFunction {}
1101unsafe impl Sync for MetalFunction {}
1102
1103// MARK: - Metal Buffer
1104
1105/// A Metal buffer for vertex/uniform data
1106#[derive(Debug)]
1107pub struct MetalBuffer {
1108    ptr: NonNull<c_void>,
1109}
1110
1111/// Resource options for buffer creation
1112#[derive(Debug, Clone, Copy, Default)]
1113pub struct ResourceOptions(u64);
1114
1115impl ResourceOptions {
1116    /// CPU cache mode default, storage mode shared
1117    pub const CPU_CACHE_MODE_DEFAULT_CACHE: Self = Self(0);
1118    /// Storage mode shared (CPU and GPU can access)
1119    pub const STORAGE_MODE_SHARED: Self = Self(0);
1120    /// Storage mode managed (CPU writes, GPU reads)
1121    pub const STORAGE_MODE_MANAGED: Self = Self(1 << 4);
1122}
1123
1124impl MetalBuffer {
1125    /// Get a pointer to the buffer contents
1126    #[must_use]
1127    pub fn contents(&self) -> *mut c_void {
1128        unsafe { metal_buffer_contents(self.ptr.as_ptr()) }
1129    }
1130
1131    /// Get the length of the buffer in bytes
1132    #[must_use]
1133    pub fn length(&self) -> usize {
1134        unsafe { metal_buffer_length(self.ptr.as_ptr()) }
1135    }
1136
1137    /// Notify that a range of the buffer was modified (for managed storage mode)
1138    pub fn did_modify_range(&self, range: std::ops::Range<usize>) {
1139        unsafe { metal_buffer_did_modify_range(self.ptr.as_ptr(), range.start, range.len()) }
1140    }
1141
1142    /// Get the raw pointer
1143    #[must_use]
1144    pub fn as_ptr(&self) -> *mut c_void {
1145        self.ptr.as_ptr()
1146    }
1147}
1148
1149impl Drop for MetalBuffer {
1150    fn drop(&mut self) {
1151        unsafe { metal_buffer_release(self.ptr.as_ptr()) }
1152    }
1153}
1154
1155unsafe impl Send for MetalBuffer {}
1156unsafe impl Sync for MetalBuffer {}
1157
1158// MARK: - Metal Layer
1159
1160/// A `CAMetalLayer` for rendering to a window
1161#[derive(Debug)]
1162pub struct MetalLayer {
1163    ptr: NonNull<c_void>,
1164}
1165
1166impl MetalLayer {
1167    /// Create a new Metal layer
1168    ///
1169    /// # Panics
1170    /// Panics if layer creation fails (should not happen on macOS with Metal support).
1171    #[must_use]
1172    pub fn new() -> Self {
1173        let ptr = unsafe { metal_layer_create() };
1174        Self {
1175            ptr: NonNull::new(ptr).expect("metal_layer_create returned null"),
1176        }
1177    }
1178
1179    /// Set the device for this layer
1180    pub fn set_device(&self, device: &MetalDevice) {
1181        unsafe { metal_layer_set_device(self.ptr.as_ptr(), device.as_ptr()) }
1182    }
1183
1184    /// Set the pixel format
1185    pub fn set_pixel_format(&self, format: MTLPixelFormat) {
1186        unsafe { metal_layer_set_pixel_format(self.ptr.as_ptr(), format.raw()) }
1187    }
1188
1189    /// Set the drawable size
1190    pub fn set_drawable_size(&self, width: f64, height: f64) {
1191        unsafe { metal_layer_set_drawable_size(self.ptr.as_ptr(), width, height) }
1192    }
1193
1194    /// Set whether to present with transaction
1195    pub fn set_presents_with_transaction(&self, value: bool) {
1196        unsafe { metal_layer_set_presents_with_transaction(self.ptr.as_ptr(), value) }
1197    }
1198
1199    /// Get the next drawable
1200    #[must_use]
1201    pub fn next_drawable(&self) -> Option<MetalDrawable> {
1202        let ptr = unsafe { metal_layer_next_drawable(self.ptr.as_ptr()) };
1203        NonNull::new(ptr).map(|ptr| MetalDrawable { ptr })
1204    }
1205
1206    /// Get the raw pointer (for attaching to a view)
1207    #[must_use]
1208    pub fn as_ptr(&self) -> *mut c_void {
1209        self.ptr.as_ptr()
1210    }
1211}
1212
1213impl Default for MetalLayer {
1214    fn default() -> Self {
1215        Self::new()
1216    }
1217}
1218
1219impl Drop for MetalLayer {
1220    fn drop(&mut self) {
1221        unsafe { metal_layer_release(self.ptr.as_ptr()) }
1222    }
1223}
1224
1225// MARK: - Metal Drawable
1226
1227/// A drawable from a Metal layer
1228#[derive(Debug)]
1229pub struct MetalDrawable {
1230    ptr: NonNull<c_void>,
1231}
1232
1233impl MetalDrawable {
1234    /// Get the texture for this drawable
1235    ///
1236    /// # Panics
1237    /// Panics if the drawable has no texture (should not happen for valid drawables).
1238    #[must_use]
1239    pub fn texture(&self) -> MetalTexture {
1240        let ptr = unsafe { metal_drawable_texture(self.ptr.as_ptr()) };
1241        // Texture is borrowed from drawable, need to retain it
1242        let ptr = unsafe { metal_texture_retain(ptr) };
1243        MetalTexture {
1244            ptr: NonNull::new(ptr).expect("drawable texture is null"),
1245        }
1246    }
1247
1248    /// Get the raw pointer
1249    #[must_use]
1250    pub fn as_ptr(&self) -> *mut c_void {
1251        self.ptr.as_ptr()
1252    }
1253}
1254
1255impl Drop for MetalDrawable {
1256    fn drop(&mut self) {
1257        unsafe { metal_drawable_release(self.ptr.as_ptr()) }
1258    }
1259}
1260
1261// MARK: - Command Buffer
1262
1263/// A Metal command buffer
1264#[derive(Debug)]
1265pub struct MetalCommandBuffer {
1266    ptr: NonNull<c_void>,
1267}
1268
1269impl MetalCommandBuffer {
1270    /// Create a render command encoder
1271    #[must_use]
1272    pub fn render_command_encoder(
1273        &self,
1274        render_pass: &MetalRenderPassDescriptor,
1275    ) -> Option<MetalRenderCommandEncoder> {
1276        let ptr = unsafe {
1277            metal_command_buffer_render_command_encoder(self.ptr.as_ptr(), render_pass.as_ptr())
1278        };
1279        NonNull::new(ptr).map(|ptr| MetalRenderCommandEncoder { ptr })
1280    }
1281
1282    /// Present a drawable
1283    pub fn present_drawable(&self, drawable: &MetalDrawable) {
1284        unsafe { metal_command_buffer_present_drawable(self.ptr.as_ptr(), drawable.as_ptr()) }
1285    }
1286
1287    /// Commit the command buffer
1288    pub fn commit(&self) {
1289        unsafe { metal_command_buffer_commit(self.ptr.as_ptr()) }
1290    }
1291
1292    /// Get the raw pointer
1293    #[must_use]
1294    pub fn as_ptr(&self) -> *mut c_void {
1295        self.ptr.as_ptr()
1296    }
1297}
1298
1299impl Drop for MetalCommandBuffer {
1300    fn drop(&mut self) {
1301        unsafe { metal_command_buffer_release(self.ptr.as_ptr()) }
1302    }
1303}
1304
1305// MARK: - Render Pass Descriptor
1306
1307/// A render pass descriptor
1308#[derive(Debug)]
1309pub struct MetalRenderPassDescriptor {
1310    ptr: NonNull<c_void>,
1311}
1312
1313/// Load action for render pass attachments
1314#[derive(Debug, Clone, Copy, Default)]
1315#[repr(u64)]
1316pub enum MTLLoadAction {
1317    /// Don't care about existing contents
1318    DontCare = 0,
1319    /// Load existing contents
1320    Load = 1,
1321    /// Clear to a value
1322    #[default]
1323    Clear = 2,
1324}
1325
1326/// Store action for render pass attachments
1327#[derive(Debug, Clone, Copy, Default)]
1328#[repr(u64)]
1329pub enum MTLStoreAction {
1330    /// Don't care about storing
1331    DontCare = 0,
1332    /// Store the results
1333    #[default]
1334    Store = 1,
1335}
1336
1337/// Pixel format
1338#[derive(Debug, Clone, Copy, Default)]
1339#[repr(u64)]
1340pub enum MTLPixelFormat {
1341    /// Invalid format
1342    Invalid = 0,
1343    /// BGRA 8-bit unsigned normalized
1344    #[default]
1345    BGRA8Unorm = 80,
1346    /// BGR 10-bit, A 2-bit unsigned normalized
1347    BGR10A2Unorm = 94,
1348    /// R 8-bit unsigned normalized
1349    R8Unorm = 10,
1350    /// RG 8-bit unsigned normalized
1351    RG8Unorm = 30,
1352}
1353
1354impl MTLPixelFormat {
1355    /// Get the raw value
1356    #[must_use]
1357    pub const fn raw(self) -> u64 {
1358        self as u64
1359    }
1360}
1361
1362/// Vertex format for vertex attributes
1363#[derive(Debug, Clone, Copy, Default)]
1364#[repr(u64)]
1365pub enum MTLVertexFormat {
1366    /// Invalid format
1367    Invalid = 0,
1368    /// Two 32-bit floats
1369    #[default]
1370    Float2 = 29,
1371    /// Three 32-bit floats
1372    Float3 = 30,
1373    /// Four 32-bit floats
1374    Float4 = 31,
1375}
1376
1377impl MTLVertexFormat {
1378    /// Get the raw value
1379    #[must_use]
1380    pub const fn raw(self) -> u64 {
1381        self as u64
1382    }
1383}
1384
1385/// Vertex step function
1386#[derive(Debug, Clone, Copy, Default)]
1387#[repr(u64)]
1388pub enum MTLVertexStepFunction {
1389    /// Constant value (same for all vertices)
1390    Constant = 0,
1391    /// Step once per vertex (default)
1392    #[default]
1393    PerVertex = 1,
1394    /// Step once per instance
1395    PerInstance = 2,
1396}
1397
1398impl MTLVertexStepFunction {
1399    /// Get the raw value
1400    #[must_use]
1401    pub const fn raw(self) -> u64 {
1402        self as u64
1403    }
1404}
1405
1406/// Primitive type for drawing
1407#[derive(Debug, Clone, Copy, Default)]
1408#[repr(u64)]
1409pub enum MTLPrimitiveType {
1410    /// Points
1411    Point = 0,
1412    /// Lines
1413    Line = 1,
1414    /// Line strip
1415    LineStrip = 2,
1416    /// Triangles
1417    #[default]
1418    Triangle = 3,
1419    /// Triangle strip
1420    TriangleStrip = 4,
1421}
1422
1423impl MTLPrimitiveType {
1424    /// Get the raw value
1425    #[must_use]
1426    pub const fn raw(self) -> u64 {
1427        self as u64
1428    }
1429}
1430
1431/// Blend operation
1432#[derive(Debug, Clone, Copy, Default)]
1433#[repr(u64)]
1434pub enum MTLBlendOperation {
1435    /// Add source and destination
1436    #[default]
1437    Add = 0,
1438    /// Subtract destination from source
1439    Subtract = 1,
1440    /// Subtract source from destination
1441    ReverseSubtract = 2,
1442    /// Minimum of source and destination
1443    Min = 3,
1444    /// Maximum of source and destination
1445    Max = 4,
1446}
1447
1448/// Blend factor
1449#[derive(Debug, Clone, Copy, Default)]
1450#[repr(u64)]
1451pub enum MTLBlendFactor {
1452    /// 0
1453    Zero = 0,
1454    /// 1
1455    #[default]
1456    One = 1,
1457    /// Source color
1458    SourceColor = 2,
1459    /// 1 - source color
1460    OneMinusSourceColor = 3,
1461    /// Source alpha
1462    SourceAlpha = 4,
1463    /// 1 - source alpha
1464    OneMinusSourceAlpha = 5,
1465    /// Destination color
1466    DestinationColor = 6,
1467    /// 1 - destination color
1468    OneMinusDestinationColor = 7,
1469    /// Destination alpha
1470    DestinationAlpha = 8,
1471    /// 1 - destination alpha
1472    OneMinusDestinationAlpha = 9,
1473}
1474
1475impl MetalRenderPassDescriptor {
1476    /// Create a new render pass descriptor
1477    ///
1478    /// # Panics
1479    /// Panics if descriptor creation fails (should not happen).
1480    #[must_use]
1481    pub fn new() -> Self {
1482        let ptr = unsafe { metal_render_pass_descriptor_create() };
1483        Self {
1484            ptr: NonNull::new(ptr).expect("render pass descriptor create failed"),
1485        }
1486    }
1487
1488    /// Set the texture for a color attachment
1489    pub fn set_color_attachment_texture(&self, index: usize, texture: &MetalTexture) {
1490        unsafe {
1491            metal_render_pass_set_color_attachment_texture(
1492                self.ptr.as_ptr(),
1493                index,
1494                texture.as_ptr(),
1495            );
1496        }
1497    }
1498
1499    /// Set the load action for a color attachment
1500    pub fn set_color_attachment_load_action(&self, index: usize, action: MTLLoadAction) {
1501        unsafe {
1502            metal_render_pass_set_color_attachment_load_action(
1503                self.ptr.as_ptr(),
1504                index,
1505                action as u64,
1506            );
1507        }
1508    }
1509
1510    /// Set the store action for a color attachment
1511    pub fn set_color_attachment_store_action(&self, index: usize, action: MTLStoreAction) {
1512        unsafe {
1513            metal_render_pass_set_color_attachment_store_action(
1514                self.ptr.as_ptr(),
1515                index,
1516                action as u64,
1517            );
1518        }
1519    }
1520
1521    /// Set the clear color for a color attachment
1522    pub fn set_color_attachment_clear_color(&self, index: usize, r: f64, g: f64, b: f64, a: f64) {
1523        unsafe {
1524            metal_render_pass_set_color_attachment_clear_color(
1525                self.ptr.as_ptr(),
1526                index,
1527                r,
1528                g,
1529                b,
1530                a,
1531            );
1532        }
1533    }
1534
1535    /// Get the raw pointer
1536    #[must_use]
1537    pub fn as_ptr(&self) -> *mut c_void {
1538        self.ptr.as_ptr()
1539    }
1540}
1541
1542impl Default for MetalRenderPassDescriptor {
1543    fn default() -> Self {
1544        Self::new()
1545    }
1546}
1547
1548impl Drop for MetalRenderPassDescriptor {
1549    fn drop(&mut self) {
1550        unsafe { metal_render_pass_descriptor_release(self.ptr.as_ptr()) }
1551    }
1552}
1553
1554// MARK: - Vertex Descriptor
1555
1556/// A vertex descriptor for specifying vertex buffer layout
1557#[derive(Debug)]
1558pub struct MetalVertexDescriptor {
1559    ptr: NonNull<c_void>,
1560}
1561
1562impl MetalVertexDescriptor {
1563    /// Create a new vertex descriptor
1564    ///
1565    /// # Panics
1566    /// Panics if descriptor creation fails (should not happen).
1567    #[must_use]
1568    pub fn new() -> Self {
1569        let ptr = unsafe { metal_vertex_descriptor_create() };
1570        Self {
1571            ptr: NonNull::new(ptr).expect("vertex descriptor create failed"),
1572        }
1573    }
1574
1575    /// Set an attribute's format, offset, and buffer index
1576    pub fn set_attribute(
1577        &self,
1578        index: usize,
1579        format: MTLVertexFormat,
1580        offset: usize,
1581        buffer_index: usize,
1582    ) {
1583        unsafe {
1584            metal_vertex_descriptor_set_attribute(
1585                self.ptr.as_ptr(),
1586                index,
1587                format.raw(),
1588                offset,
1589                buffer_index,
1590            );
1591        }
1592    }
1593
1594    /// Set a buffer layout's stride and step function
1595    pub fn set_layout(
1596        &self,
1597        buffer_index: usize,
1598        stride: usize,
1599        step_function: MTLVertexStepFunction,
1600    ) {
1601        unsafe {
1602            metal_vertex_descriptor_set_layout(
1603                self.ptr.as_ptr(),
1604                buffer_index,
1605                stride,
1606                step_function.raw(),
1607            );
1608        }
1609    }
1610
1611    /// Get the raw pointer
1612    #[must_use]
1613    pub fn as_ptr(&self) -> *mut c_void {
1614        self.ptr.as_ptr()
1615    }
1616}
1617
1618impl Default for MetalVertexDescriptor {
1619    fn default() -> Self {
1620        Self::new()
1621    }
1622}
1623
1624impl Drop for MetalVertexDescriptor {
1625    fn drop(&mut self) {
1626        unsafe { metal_vertex_descriptor_release(self.ptr.as_ptr()) }
1627    }
1628}
1629
1630// MARK: - Render Pipeline Descriptor
1631
1632/// A render pipeline descriptor
1633#[derive(Debug)]
1634pub struct MetalRenderPipelineDescriptor {
1635    ptr: NonNull<c_void>,
1636}
1637
1638impl MetalRenderPipelineDescriptor {
1639    /// Create a new render pipeline descriptor
1640    ///
1641    /// # Panics
1642    /// Panics if descriptor creation fails (should not happen).
1643    #[must_use]
1644    pub fn new() -> Self {
1645        let ptr = unsafe { metal_render_pipeline_descriptor_create() };
1646        Self {
1647            ptr: NonNull::new(ptr).expect("render pipeline descriptor create failed"),
1648        }
1649    }
1650
1651    /// Set the vertex function
1652    pub fn set_vertex_function(&self, function: &MetalFunction) {
1653        unsafe {
1654            metal_render_pipeline_descriptor_set_vertex_function(
1655                self.ptr.as_ptr(),
1656                function.as_ptr(),
1657            );
1658        }
1659    }
1660
1661    /// Set the fragment function
1662    pub fn set_fragment_function(&self, function: &MetalFunction) {
1663        unsafe {
1664            metal_render_pipeline_descriptor_set_fragment_function(
1665                self.ptr.as_ptr(),
1666                function.as_ptr(),
1667            );
1668        }
1669    }
1670
1671    /// Set the vertex descriptor for vertex buffer layout
1672    pub fn set_vertex_descriptor(&self, descriptor: &MetalVertexDescriptor) {
1673        unsafe {
1674            metal_render_pipeline_descriptor_set_vertex_descriptor(
1675                self.ptr.as_ptr(),
1676                descriptor.as_ptr(),
1677            );
1678        }
1679    }
1680
1681    /// Set color attachment pixel format
1682    pub fn set_color_attachment_pixel_format(&self, index: usize, format: MTLPixelFormat) {
1683        unsafe {
1684            metal_render_pipeline_descriptor_set_color_attachment_pixel_format(
1685                self.ptr.as_ptr(),
1686                index,
1687                format.raw(),
1688            );
1689        }
1690    }
1691
1692    /// Set blending enabled for a color attachment
1693    pub fn set_blending_enabled(&self, index: usize, enabled: bool) {
1694        unsafe {
1695            metal_render_pipeline_descriptor_set_blending_enabled(
1696                self.ptr.as_ptr(),
1697                index,
1698                enabled,
1699            );
1700        }
1701    }
1702
1703    /// Set blend operations
1704    pub fn set_blend_operations(
1705        &self,
1706        index: usize,
1707        rgb_op: MTLBlendOperation,
1708        alpha_op: MTLBlendOperation,
1709    ) {
1710        unsafe {
1711            metal_render_pipeline_descriptor_set_blend_operations(
1712                self.ptr.as_ptr(),
1713                index,
1714                rgb_op as u64,
1715                alpha_op as u64,
1716            );
1717        }
1718    }
1719
1720    /// Set blend factors
1721    pub fn set_blend_factors(
1722        &self,
1723        index: usize,
1724        src_rgb: MTLBlendFactor,
1725        dst_rgb: MTLBlendFactor,
1726        src_alpha: MTLBlendFactor,
1727        dst_alpha: MTLBlendFactor,
1728    ) {
1729        unsafe {
1730            metal_render_pipeline_descriptor_set_blend_factors(
1731                self.ptr.as_ptr(),
1732                index,
1733                src_rgb as u64,
1734                dst_rgb as u64,
1735                src_alpha as u64,
1736                dst_alpha as u64,
1737            );
1738        }
1739    }
1740
1741    /// Get the raw pointer
1742    #[must_use]
1743    pub fn as_ptr(&self) -> *mut c_void {
1744        self.ptr.as_ptr()
1745    }
1746}
1747
1748impl Default for MetalRenderPipelineDescriptor {
1749    fn default() -> Self {
1750        Self::new()
1751    }
1752}
1753
1754impl Drop for MetalRenderPipelineDescriptor {
1755    fn drop(&mut self) {
1756        unsafe { metal_render_pipeline_descriptor_release(self.ptr.as_ptr()) }
1757    }
1758}
1759
1760// MARK: - Render Pipeline State
1761
1762/// A compiled render pipeline state
1763#[derive(Debug)]
1764pub struct MetalRenderPipelineState {
1765    ptr: NonNull<c_void>,
1766}
1767
1768impl MetalRenderPipelineState {
1769    /// Get the raw pointer
1770    #[must_use]
1771    pub fn as_ptr(&self) -> *mut c_void {
1772        self.ptr.as_ptr()
1773    }
1774}
1775
1776impl Drop for MetalRenderPipelineState {
1777    fn drop(&mut self) {
1778        unsafe { metal_render_pipeline_state_release(self.ptr.as_ptr()) }
1779    }
1780}
1781
1782unsafe impl Send for MetalRenderPipelineState {}
1783unsafe impl Sync for MetalRenderPipelineState {}
1784
1785// MARK: - Render Command Encoder
1786
1787/// A render command encoder
1788#[derive(Debug)]
1789pub struct MetalRenderCommandEncoder {
1790    ptr: NonNull<c_void>,
1791}
1792
1793impl MetalRenderCommandEncoder {
1794    /// Set the render pipeline state
1795    pub fn set_render_pipeline_state(&self, state: &MetalRenderPipelineState) {
1796        unsafe { metal_render_encoder_set_pipeline_state(self.ptr.as_ptr(), state.as_ptr()) }
1797    }
1798
1799    /// Set a vertex buffer
1800    pub fn set_vertex_buffer(&self, buffer: &MetalBuffer, offset: usize, index: usize) {
1801        unsafe {
1802            metal_render_encoder_set_vertex_buffer(
1803                self.ptr.as_ptr(),
1804                buffer.as_ptr(),
1805                offset,
1806                index,
1807            );
1808        }
1809    }
1810
1811    /// Set a fragment buffer
1812    pub fn set_fragment_buffer(&self, buffer: &MetalBuffer, offset: usize, index: usize) {
1813        unsafe {
1814            metal_render_encoder_set_fragment_buffer(
1815                self.ptr.as_ptr(),
1816                buffer.as_ptr(),
1817                offset,
1818                index,
1819            );
1820        }
1821    }
1822
1823    /// Set a fragment texture
1824    pub fn set_fragment_texture(&self, texture: &MetalTexture, index: usize) {
1825        unsafe {
1826            metal_render_encoder_set_fragment_texture(self.ptr.as_ptr(), texture.as_ptr(), index);
1827        }
1828    }
1829
1830    /// Draw primitives
1831    pub fn draw_primitives(
1832        &self,
1833        primitive_type: MTLPrimitiveType,
1834        vertex_start: usize,
1835        vertex_count: usize,
1836    ) {
1837        unsafe {
1838            metal_render_encoder_draw_primitives(
1839                self.ptr.as_ptr(),
1840                primitive_type.raw(),
1841                vertex_start,
1842                vertex_count,
1843            );
1844        }
1845    }
1846
1847    /// End encoding
1848    pub fn end_encoding(&self) {
1849        unsafe { metal_render_encoder_end_encoding(self.ptr.as_ptr()) }
1850    }
1851
1852    /// Get the raw pointer
1853    #[must_use]
1854    pub fn as_ptr(&self) -> *mut c_void {
1855        self.ptr.as_ptr()
1856    }
1857}
1858
1859impl Drop for MetalRenderCommandEncoder {
1860    fn drop(&mut self) {
1861        unsafe { metal_render_encoder_release(self.ptr.as_ptr()) }
1862    }
1863}
1864
1865// MARK: - IOSurface Metal Extension
1866
1867/// Result of creating Metal textures from an `IOSurface`
1868pub type MetalCapturedTextures = CapturedTextures<MetalTexture>;
1869
1870impl IOSurface {
1871    /// Create Metal textures from this `IOSurface` using the provided device
1872    ///
1873    /// This is a zero-copy operation - the textures share memory with the `IOSurface`.
1874    ///
1875    /// # Example
1876    ///
1877    /// ```no_run
1878    /// use screencapturekit::metal::MetalDevice;
1879    /// use screencapturekit::cm::IOSurface;
1880    ///
1881    /// fn example(surface: &IOSurface) {
1882    ///     let device = MetalDevice::system_default().expect("No Metal device");
1883    ///     if let Some(textures) = surface.create_metal_textures(&device) {
1884    ///         if textures.is_ycbcr() {
1885    ///             // Use YCbCr shader with plane0 (Y) and plane1 (CbCr)
1886    ///         }
1887    ///     }
1888    /// }
1889    /// ```
1890    #[must_use]
1891    pub fn create_metal_textures(&self, device: &MetalDevice) -> Option<MetalCapturedTextures> {
1892        let width = self.width();
1893        let height = self.height();
1894        let pix_format: FourCharCode = self.pixel_format().into();
1895
1896        if width == 0 || height == 0 {
1897            return None;
1898        }
1899
1900        let params = self.texture_params();
1901
1902        if params.len() == 1 {
1903            // Single-plane format
1904            let texture = self.create_texture_for_plane(device, &params[0])?;
1905            Some(CapturedTextures {
1906                plane0: texture,
1907                plane1: None,
1908                pixel_format: pix_format,
1909                width,
1910                height,
1911            })
1912        } else if params.len() >= 2 {
1913            // YCbCr biplanar format
1914            let y_texture = self.create_texture_for_plane(device, &params[0])?;
1915            let uv_texture = self.create_texture_for_plane(device, &params[1])?;
1916            Some(CapturedTextures {
1917                plane0: y_texture,
1918                plane1: Some(uv_texture),
1919                pixel_format: pix_format,
1920                width,
1921                height,
1922            })
1923        } else {
1924            None
1925        }
1926    }
1927
1928    fn create_texture_for_plane(
1929        &self,
1930        device: &MetalDevice,
1931        params: &TextureParams,
1932    ) -> Option<MetalTexture> {
1933        let ptr = unsafe {
1934            metal_create_texture_from_iosurface(
1935                device.as_ptr(),
1936                self.as_ptr(),
1937                params.plane,
1938                params.width,
1939                params.height,
1940                params.format.raw(),
1941            )
1942        };
1943        NonNull::new(ptr).map(|ptr| MetalTexture { ptr })
1944    }
1945}
1946
1947// MARK: - Autorelease Pool
1948
1949#[link(name = "Foundation", kind = "framework")]
1950extern "C" {
1951    fn objc_autoreleasePoolPush() -> *mut c_void;
1952    fn objc_autoreleasePoolPop(pool: *mut c_void);
1953}
1954
1955/// Execute a closure within an autorelease pool
1956///
1957/// This is equivalent to `@autoreleasepool { ... }` in Objective-C/Swift.
1958/// Use this when running code that creates temporary Objective-C objects
1959/// that need to be released promptly.
1960///
1961/// # Example
1962///
1963/// ```no_run
1964/// use screencapturekit::metal::autoreleasepool;
1965///
1966/// autoreleasepool(|| {
1967///     // Code that creates temporary Objective-C objects
1968///     println!("Inside autorelease pool");
1969/// });
1970/// ```
1971pub fn autoreleasepool<F, R>(f: F) -> R
1972where
1973    F: FnOnce() -> R,
1974{
1975    unsafe {
1976        let pool = objc_autoreleasePoolPush();
1977        let result = f();
1978        objc_autoreleasePoolPop(pool);
1979        result
1980    }
1981}
1982
1983// MARK: - NSView Helpers
1984
1985/// Set up an `NSView` for Metal rendering
1986///
1987/// This sets `wantsLayer = YES` and assigns the Metal layer to the view.
1988///
1989/// # Safety
1990///
1991/// The `view` pointer must be a valid `NSView` pointer.
1992///
1993/// # Example
1994///
1995/// ```no_run
1996/// use screencapturekit::metal::{setup_metal_view, MetalLayer};
1997/// use std::ffi::c_void;
1998///
1999/// fn example(ns_view: *mut c_void) {
2000///     let layer = MetalLayer::new();
2001///     unsafe { setup_metal_view(ns_view, &layer); }
2002/// }
2003/// ```
2004pub unsafe fn setup_metal_view(view: *mut c_void, layer: &MetalLayer) {
2005    nsview_set_wants_layer(view);
2006    nsview_set_layer(view, layer.as_ptr());
2007}