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