Skip to main content

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