Skip to main content

videotoolbox/frame_processor/
mod.rs

1#![allow(
2    clippy::cast_possible_wrap,
3    clippy::missing_const_for_fn,
4    clippy::missing_errors_doc,
5    clippy::too_many_arguments
6)]
7
8//! `VTFrameProcessor` capability queries plus retained wrappers for
9//! `VTFrameProcessorFrame` / `VTFrameProcessorOpticalFlow` and
10//! synchronous processing helpers for all public pipelines this crate
11//! exposes.
12
13use core::ffi::c_void;
14
15use apple_cf::{cm::CMTime, cv::CVPixelBuffer};
16use apple_metal::CommandBuffer;
17
18use crate::{ffi, VTError};
19
20extern "C" {
21    fn vt_super_resolution_is_supported() -> bool;
22    fn vt_motion_blur_is_supported() -> bool;
23    fn vt_temporal_noise_filter_is_supported() -> bool;
24    fn vt_frame_rate_conversion_is_supported() -> bool;
25    fn vt_low_latency_super_resolution_is_supported() -> bool;
26    fn vt_low_latency_frame_interpolation_is_supported() -> bool;
27    fn vt_optical_flow_is_supported() -> bool;
28
29    fn vt_super_resolution_supported_scale_factors(out_buf: *mut u32, max: usize) -> usize;
30    fn vt_low_latency_super_resolution_supported_scale_factors(
31        frame_width: isize,
32        frame_height: isize,
33        out_buf: *mut f32,
34        max: usize,
35    ) -> usize;
36
37    fn vt_super_resolution_model_status(
38        frame_width: isize,
39        frame_height: isize,
40        scale_factor: isize,
41        use_precomputed_flow: bool,
42        input_type: isize,
43        quality_prioritization: isize,
44        revision: isize,
45    ) -> i32;
46    fn vt_super_resolution_model_percentage_available(
47        frame_width: isize,
48        frame_height: isize,
49        scale_factor: isize,
50        use_precomputed_flow: bool,
51        input_type: isize,
52        quality_prioritization: isize,
53        revision: isize,
54    ) -> f32;
55    fn vt_super_resolution_download_model(
56        frame_width: isize,
57        frame_height: isize,
58        scale_factor: isize,
59        use_precomputed_flow: bool,
60        input_type: isize,
61        quality_prioritization: isize,
62        revision: isize,
63    ) -> i32;
64
65    fn vt_super_resolution_start(
66        frame_width: isize,
67        frame_height: isize,
68        scale_factor: isize,
69        use_precomputed_flow: bool,
70        input_type: isize,
71        quality_prioritization: isize,
72        revision: isize,
73        out: *mut *mut c_void,
74    ) -> i32;
75    fn vt_motion_blur_start(
76        frame_width: isize,
77        frame_height: isize,
78        use_precomputed_flow: bool,
79        quality_prioritization: isize,
80        revision: isize,
81        out: *mut *mut c_void,
82    ) -> i32;
83    fn vt_temporal_noise_filter_start(
84        frame_width: isize,
85        frame_height: isize,
86        source_pixel_format: u32,
87        out: *mut *mut c_void,
88    ) -> i32;
89    fn vt_frame_rate_conversion_start(
90        frame_width: isize,
91        frame_height: isize,
92        use_precomputed_flow: bool,
93        quality_prioritization: isize,
94        revision: isize,
95        out: *mut *mut c_void,
96    ) -> i32;
97    fn vt_low_latency_super_resolution_start(
98        frame_width: isize,
99        frame_height: isize,
100        scale_factor: f32,
101        out: *mut *mut c_void,
102    ) -> i32;
103    fn vt_low_latency_frame_interpolation_start(
104        frame_width: isize,
105        frame_height: isize,
106        number_of_interpolated_frames: isize,
107        out: *mut *mut c_void,
108    ) -> i32;
109    fn vt_optical_flow_start(
110        frame_width: isize,
111        frame_height: isize,
112        quality_prioritization: isize,
113        revision: isize,
114        out: *mut *mut c_void,
115    ) -> i32;
116
117    fn vt_frame_processor_frame_create(
118        buffer: *mut c_void,
119        presentation_time_stamp: ffi::CMTime,
120        out: *mut *mut c_void,
121    ) -> i32;
122    fn vt_frame_processor_frame_release(frame: *mut c_void);
123
124    fn vt_frame_processor_optical_flow_create(
125        forward_flow: *mut c_void,
126        backward_flow: *mut c_void,
127        out: *mut *mut c_void,
128    ) -> i32;
129    fn vt_frame_processor_optical_flow_release(flow: *mut c_void);
130
131    fn vt_frame_processor_process_super_resolution(
132        processor: *mut c_void,
133        source_frame: *mut c_void,
134        previous_frame: *mut c_void,
135        previous_output_frame: *mut c_void,
136        optical_flow: *mut c_void,
137        submission_mode: i32,
138        destination_frame: *mut c_void,
139    ) -> i32;
140    fn vt_frame_processor_process_super_resolution_with_command_buffer(
141        processor: *mut c_void,
142        command_buffer: *mut c_void,
143        source_frame: *mut c_void,
144        previous_frame: *mut c_void,
145        previous_output_frame: *mut c_void,
146        optical_flow: *mut c_void,
147        submission_mode: i32,
148        destination_frame: *mut c_void,
149    ) -> i32;
150    fn vt_frame_processor_process_motion_blur(
151        processor: *mut c_void,
152        source_frame: *mut c_void,
153        next_frame: *mut c_void,
154        previous_frame: *mut c_void,
155        next_optical_flow: *mut c_void,
156        previous_optical_flow: *mut c_void,
157        motion_blur_strength: isize,
158        submission_mode: i32,
159        destination_frame: *mut c_void,
160    ) -> i32;
161    fn vt_frame_processor_process_motion_blur_with_command_buffer(
162        processor: *mut c_void,
163        command_buffer: *mut c_void,
164        source_frame: *mut c_void,
165        next_frame: *mut c_void,
166        previous_frame: *mut c_void,
167        next_optical_flow: *mut c_void,
168        previous_optical_flow: *mut c_void,
169        motion_blur_strength: isize,
170        submission_mode: i32,
171        destination_frame: *mut c_void,
172    ) -> i32;
173    fn vt_frame_processor_process_temporal_noise_filter(
174        processor: *mut c_void,
175        source_frame: *mut c_void,
176        next_frames: *const *mut c_void,
177        next_frame_count: usize,
178        previous_frames: *const *mut c_void,
179        previous_frame_count: usize,
180        destination_frame: *mut c_void,
181        filter_strength: f32,
182        has_discontinuity: bool,
183    ) -> i32;
184    fn vt_frame_processor_process_temporal_noise_filter_with_command_buffer(
185        processor: *mut c_void,
186        command_buffer: *mut c_void,
187        source_frame: *mut c_void,
188        next_frames: *const *mut c_void,
189        next_frame_count: usize,
190        previous_frames: *const *mut c_void,
191        previous_frame_count: usize,
192        destination_frame: *mut c_void,
193        filter_strength: f32,
194        has_discontinuity: bool,
195    ) -> i32;
196    fn vt_frame_processor_process_frame_rate_conversion(
197        processor: *mut c_void,
198        source_frame: *mut c_void,
199        next_frame: *mut c_void,
200        optical_flow: *mut c_void,
201        interpolation_phase: *const f32,
202        interpolation_phase_count: usize,
203        submission_mode: i32,
204        destination_frames: *const *mut c_void,
205        destination_frame_count: usize,
206    ) -> i32;
207    fn vt_frame_processor_process_frame_rate_conversion_with_command_buffer(
208        processor: *mut c_void,
209        command_buffer: *mut c_void,
210        source_frame: *mut c_void,
211        next_frame: *mut c_void,
212        optical_flow: *mut c_void,
213        interpolation_phase: *const f32,
214        interpolation_phase_count: usize,
215        submission_mode: i32,
216        destination_frames: *const *mut c_void,
217        destination_frame_count: usize,
218    ) -> i32;
219    fn vt_frame_processor_process_low_latency_super_resolution(
220        processor: *mut c_void,
221        source_frame: *mut c_void,
222        destination_frame: *mut c_void,
223    ) -> i32;
224    fn vt_frame_processor_process_low_latency_super_resolution_with_command_buffer(
225        processor: *mut c_void,
226        command_buffer: *mut c_void,
227        source_frame: *mut c_void,
228        destination_frame: *mut c_void,
229    ) -> i32;
230    fn vt_frame_processor_process_low_latency_frame_interpolation(
231        processor: *mut c_void,
232        source_frame: *mut c_void,
233        previous_frame: *mut c_void,
234        interpolation_phase: *const f32,
235        interpolation_phase_count: usize,
236        destination_frames: *const *mut c_void,
237        destination_frame_count: usize,
238    ) -> i32;
239    fn vt_frame_processor_process_low_latency_frame_interpolation_with_command_buffer(
240        processor: *mut c_void,
241        command_buffer: *mut c_void,
242        source_frame: *mut c_void,
243        previous_frame: *mut c_void,
244        interpolation_phase: *const f32,
245        interpolation_phase_count: usize,
246        destination_frames: *const *mut c_void,
247        destination_frame_count: usize,
248    ) -> i32;
249    fn vt_frame_processor_process_optical_flow(
250        processor: *mut c_void,
251        source_frame: *mut c_void,
252        next_frame: *mut c_void,
253        submission_mode: i32,
254        destination_optical_flow: *mut c_void,
255    ) -> i32;
256    fn vt_frame_processor_process_optical_flow_with_command_buffer(
257        processor: *mut c_void,
258        command_buffer: *mut c_void,
259        source_frame: *mut c_void,
260        next_frame: *mut c_void,
261        submission_mode: i32,
262        destination_optical_flow: *mut c_void,
263    ) -> i32;
264
265    fn vt_frame_processor_end(processor: *mut c_void);
266    fn vt_frame_processor_release(processor: *mut c_void);
267}
268
269/// Per-effect availability snapshot.
270#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
271#[allow(clippy::struct_excessive_bools)]
272pub struct FrameProcessorCapabilities {
273    pub super_resolution: bool,
274    pub motion_blur: bool,
275    pub temporal_noise_filter: bool,
276    pub frame_rate_conversion: bool,
277    pub low_latency_super_resolution: bool,
278    pub low_latency_frame_interpolation: bool,
279    pub optical_flow: bool,
280}
281
282/// Submission ordering hint for processors that accept random vs
283/// sequential frame order.
284#[derive(Debug, Clone, Copy, PartialEq, Eq)]
285#[repr(i32)]
286pub enum FrameProcessorSubmissionMode {
287    Random = 1,
288    Sequential = 2,
289}
290
291/// Submission ordering hint for frame-rate conversion.
292#[derive(Debug, Clone, Copy, PartialEq, Eq)]
293#[repr(i32)]
294pub enum FrameRateConversionSubmissionMode {
295    Random = 1,
296    Sequential = 2,
297    SequentialReferencesUnchanged = 3,
298}
299
300/// `VTFrameRateConversionConfiguration.QualityPrioritization`.
301#[derive(Debug, Clone, Copy, PartialEq, Eq)]
302#[repr(isize)]
303pub enum VTFrameRateConversionConfigurationQualityPrioritization {
304    Normal = ffi::VTFrameRateConversionConfigurationQualityPrioritizationNormal,
305    Quality = ffi::VTFrameRateConversionConfigurationQualityPrioritizationQuality,
306}
307
308/// `VTFrameRateConversionConfiguration.Revision`.
309#[derive(Debug, Clone, Copy, PartialEq, Eq)]
310#[repr(isize)]
311pub enum VTFrameRateConversionConfigurationRevision {
312    Revision1 = ffi::VTFrameRateConversionConfigurationRevision1,
313}
314
315/// `VTMotionBlurConfiguration.QualityPrioritization`.
316#[derive(Debug, Clone, Copy, PartialEq, Eq)]
317#[repr(isize)]
318pub enum VTMotionBlurConfigurationQualityPrioritization {
319    Normal = ffi::VTMotionBlurConfigurationQualityPrioritizationNormal,
320    Quality = ffi::VTMotionBlurConfigurationQualityPrioritizationQuality,
321}
322
323/// `VTMotionBlurConfiguration.Revision`.
324#[derive(Debug, Clone, Copy, PartialEq, Eq)]
325#[repr(isize)]
326pub enum VTMotionBlurConfigurationRevision {
327    Revision1 = ffi::VTMotionBlurConfigurationRevision1,
328}
329
330/// `VTOpticalFlowConfiguration.QualityPrioritization`.
331#[derive(Debug, Clone, Copy, PartialEq, Eq)]
332#[repr(isize)]
333pub enum VTOpticalFlowConfigurationQualityPrioritization {
334    Normal = ffi::VTOpticalFlowConfigurationQualityPrioritizationNormal,
335    Quality = ffi::VTOpticalFlowConfigurationQualityPrioritizationQuality,
336}
337
338/// `VTOpticalFlowConfiguration.Revision`.
339#[derive(Debug, Clone, Copy, PartialEq, Eq)]
340#[repr(isize)]
341pub enum VTOpticalFlowConfigurationRevision {
342    Revision1 = ffi::VTOpticalFlowConfigurationRevision1,
343}
344
345/// `VTSuperResolutionScalerConfiguration.InputType`.
346#[derive(Debug, Clone, Copy, PartialEq, Eq)]
347#[repr(isize)]
348pub enum VTSuperResolutionScalerConfigurationInputType {
349    Video = ffi::VTSuperResolutionScalerConfigurationInputTypeVideo,
350    Image = ffi::VTSuperResolutionScalerConfigurationInputTypeImage,
351}
352
353/// `VTSuperResolutionScalerConfiguration.QualityPrioritization`.
354#[derive(Debug, Clone, Copy, PartialEq, Eq)]
355#[repr(isize)]
356pub enum VTSuperResolutionScalerConfigurationQualityPrioritization {
357    Normal = ffi::VTSuperResolutionScalerConfigurationQualityPrioritizationNormal,
358}
359
360/// `VTSuperResolutionScalerConfiguration.Revision`.
361#[derive(Debug, Clone, Copy, PartialEq, Eq)]
362#[repr(isize)]
363pub enum VTSuperResolutionScalerConfigurationRevision {
364    Revision1 = ffi::VTSuperResolutionScalerConfigurationRevision1,
365}
366
367/// Explicit configuration for `VTSuperResolutionScalerConfiguration`.
368#[derive(Debug, Clone, Copy, PartialEq, Eq)]
369pub struct SuperResolutionConfiguration {
370    pub frame_width: usize,
371    pub frame_height: usize,
372    pub scale_factor: usize,
373    pub use_precomputed_flow: bool,
374    pub input_type: VTSuperResolutionScalerConfigurationInputType,
375    pub quality_prioritization: VTSuperResolutionScalerConfigurationQualityPrioritization,
376    pub revision: VTSuperResolutionScalerConfigurationRevision,
377}
378
379impl SuperResolutionConfiguration {
380    #[must_use]
381    pub const fn new(frame_width: usize, frame_height: usize, scale_factor: usize) -> Self {
382        Self {
383            frame_width,
384            frame_height,
385            scale_factor,
386            use_precomputed_flow: false,
387            input_type: VTSuperResolutionScalerConfigurationInputType::Video,
388            quality_prioritization:
389                VTSuperResolutionScalerConfigurationQualityPrioritization::Normal,
390            revision: VTSuperResolutionScalerConfigurationRevision::Revision1,
391        }
392    }
393}
394
395/// Explicit configuration for `VTMotionBlurConfiguration`.
396#[derive(Debug, Clone, Copy, PartialEq, Eq)]
397pub struct MotionBlurConfiguration {
398    pub frame_width: usize,
399    pub frame_height: usize,
400    pub use_precomputed_flow: bool,
401    pub quality_prioritization: VTMotionBlurConfigurationQualityPrioritization,
402    pub revision: VTMotionBlurConfigurationRevision,
403}
404
405impl MotionBlurConfiguration {
406    #[must_use]
407    pub const fn new(frame_width: usize, frame_height: usize) -> Self {
408        Self {
409            frame_width,
410            frame_height,
411            use_precomputed_flow: false,
412            quality_prioritization: VTMotionBlurConfigurationQualityPrioritization::Normal,
413            revision: VTMotionBlurConfigurationRevision::Revision1,
414        }
415    }
416}
417
418/// Explicit configuration for `VTFrameRateConversionConfiguration`.
419#[derive(Debug, Clone, Copy, PartialEq, Eq)]
420pub struct FrameRateConversionConfiguration {
421    pub frame_width: usize,
422    pub frame_height: usize,
423    pub use_precomputed_flow: bool,
424    pub quality_prioritization: VTFrameRateConversionConfigurationQualityPrioritization,
425    pub revision: VTFrameRateConversionConfigurationRevision,
426}
427
428impl FrameRateConversionConfiguration {
429    #[must_use]
430    pub const fn new(frame_width: usize, frame_height: usize) -> Self {
431        Self {
432            frame_width,
433            frame_height,
434            use_precomputed_flow: false,
435            quality_prioritization: VTFrameRateConversionConfigurationQualityPrioritization::Normal,
436            revision: VTFrameRateConversionConfigurationRevision::Revision1,
437        }
438    }
439}
440
441/// Explicit configuration for `VTOpticalFlowConfiguration`.
442#[derive(Debug, Clone, Copy, PartialEq, Eq)]
443pub struct OpticalFlowConfiguration {
444    pub frame_width: usize,
445    pub frame_height: usize,
446    pub quality_prioritization: VTOpticalFlowConfigurationQualityPrioritization,
447    pub revision: VTOpticalFlowConfigurationRevision,
448}
449
450impl OpticalFlowConfiguration {
451    #[must_use]
452    pub const fn new(frame_width: usize, frame_height: usize) -> Self {
453        Self {
454            frame_width,
455            frame_height,
456            quality_prioritization: VTOpticalFlowConfigurationQualityPrioritization::Normal,
457            revision: VTOpticalFlowConfigurationRevision::Revision1,
458        }
459    }
460}
461
462/// Super-resolution model availability.
463#[derive(Debug, Clone, Copy, PartialEq, Eq)]
464#[repr(i32)]
465pub enum SuperResolutionModelStatus {
466    DownloadRequired = 0,
467    Downloading = 1,
468    Ready = 2,
469}
470
471impl SuperResolutionModelStatus {
472    fn from_raw(raw: i32) -> Option<Self> {
473        match raw {
474            0 => Some(Self::DownloadRequired),
475            1 => Some(Self::Downloading),
476            2 => Some(Self::Ready),
477            _ => None,
478        }
479    }
480}
481
482/// Query Apple's `VTFrameProcessor*Configuration.isSupported` for each wrapped effect.
483#[must_use]
484pub fn frame_processor_capabilities() -> FrameProcessorCapabilities {
485    unsafe {
486        FrameProcessorCapabilities {
487            super_resolution: vt_super_resolution_is_supported(),
488            motion_blur: vt_motion_blur_is_supported(),
489            temporal_noise_filter: vt_temporal_noise_filter_is_supported(),
490            frame_rate_conversion: vt_frame_rate_conversion_is_supported(),
491            low_latency_super_resolution: vt_low_latency_super_resolution_is_supported(),
492            low_latency_frame_interpolation: vt_low_latency_frame_interpolation_is_supported(),
493            optical_flow: vt_optical_flow_is_supported(),
494        }
495    }
496}
497
498/// Integer scale factors accepted by `VTSuperResolutionScalerConfiguration`.
499#[must_use]
500pub fn super_resolution_supported_scale_factors() -> Vec<u32> {
501    const MAX: usize = 16;
502    let mut buf = vec![0u32; MAX];
503    let n = unsafe { vt_super_resolution_supported_scale_factors(buf.as_mut_ptr(), MAX) };
504    buf.truncate(n);
505    buf
506}
507
508/// Floating-point scale factors accepted by `VTLowLatencySuperResolutionScalerConfiguration`
509/// for a specific input size.
510#[must_use]
511pub fn low_latency_super_resolution_supported_scale_factors(
512    frame_width: usize,
513    frame_height: usize,
514) -> Vec<f32> {
515    const MAX: usize = 16;
516    let mut buf = vec![0f32; MAX];
517    let n = unsafe {
518        vt_low_latency_super_resolution_supported_scale_factors(
519            frame_width as isize,
520            frame_height as isize,
521            buf.as_mut_ptr(),
522            MAX,
523        )
524    };
525    buf.truncate(n);
526    buf
527}
528
529/// Query the model-download state for a super-resolution configuration.
530#[must_use]
531pub fn super_resolution_model_status(
532    frame_width: usize,
533    frame_height: usize,
534    scale_factor: usize,
535    use_precomputed_flow: bool,
536    input_is_image: bool,
537) -> Option<SuperResolutionModelStatus> {
538    super_resolution_model_status_for_configuration(default_super_resolution_configuration(
539        frame_width,
540        frame_height,
541        scale_factor,
542        use_precomputed_flow,
543        input_is_image,
544    ))
545}
546
547/// Query the model-download state for an explicit super-resolution configuration.
548#[must_use]
549pub fn super_resolution_model_status_for_configuration(
550    configuration: SuperResolutionConfiguration,
551) -> Option<SuperResolutionModelStatus> {
552    SuperResolutionModelStatus::from_raw(unsafe {
553        vt_super_resolution_model_status(
554            configuration.frame_width as isize,
555            configuration.frame_height as isize,
556            configuration.scale_factor as isize,
557            configuration.use_precomputed_flow,
558            configuration.input_type as isize,
559            configuration.quality_prioritization as isize,
560            configuration.revision as isize,
561        )
562    })
563}
564
565/// Query the model-download percentage for a super-resolution configuration.
566#[must_use]
567pub fn super_resolution_model_percentage_available(
568    frame_width: usize,
569    frame_height: usize,
570    scale_factor: usize,
571    use_precomputed_flow: bool,
572    input_is_image: bool,
573) -> Option<f32> {
574    super_resolution_model_percentage_available_for_configuration(
575        default_super_resolution_configuration(
576            frame_width,
577            frame_height,
578            scale_factor,
579            use_precomputed_flow,
580            input_is_image,
581        ),
582    )
583}
584
585/// Query the model-download percentage for an explicit super-resolution configuration.
586#[must_use]
587pub fn super_resolution_model_percentage_available_for_configuration(
588    configuration: SuperResolutionConfiguration,
589) -> Option<f32> {
590    let value = unsafe {
591        vt_super_resolution_model_percentage_available(
592            configuration.frame_width as isize,
593            configuration.frame_height as isize,
594            configuration.scale_factor as isize,
595            configuration.use_precomputed_flow,
596            configuration.input_type as isize,
597            configuration.quality_prioritization as isize,
598            configuration.revision as isize,
599        )
600    };
601    (value >= 0.0).then_some(value)
602}
603
604/// Trigger background model download for a super-resolution configuration and block
605/// until the completion handler fires.
606///
607/// # Errors
608///
609/// Returns [`VTError::ApiFailed`] when `VideoToolbox` reports an error.
610pub fn download_super_resolution_model(
611    frame_width: usize,
612    frame_height: usize,
613    scale_factor: usize,
614    use_precomputed_flow: bool,
615    input_is_image: bool,
616) -> Result<(), VTError> {
617    download_super_resolution_model_for_configuration(default_super_resolution_configuration(
618        frame_width,
619        frame_height,
620        scale_factor,
621        use_precomputed_flow,
622        input_is_image,
623    ))
624}
625
626/// Trigger background model download for an explicit super-resolution configuration.
627///
628/// # Errors
629///
630/// Returns [`VTError::ApiFailed`] when `VideoToolbox` reports an error.
631pub fn download_super_resolution_model_for_configuration(
632    configuration: SuperResolutionConfiguration,
633) -> Result<(), VTError> {
634    let status = unsafe {
635        vt_super_resolution_download_model(
636            configuration.frame_width as isize,
637            configuration.frame_height as isize,
638            configuration.scale_factor as isize,
639            configuration.use_precomputed_flow,
640            configuration.input_type as isize,
641            configuration.quality_prioritization as isize,
642            configuration.revision as isize,
643        )
644    };
645    api_result("downloadConfigurationModelWithCompletionHandler", status)
646}
647
648/// Retained `VTFrameProcessorFrame` wrapper.
649pub struct FrameProcessorFrame {
650    inner: *mut c_void,
651    buffer: CVPixelBuffer,
652    presentation_time_stamp: CMTime,
653}
654
655unsafe impl Send for FrameProcessorFrame {}
656unsafe impl Sync for FrameProcessorFrame {}
657
658impl Drop for FrameProcessorFrame {
659    fn drop(&mut self) {
660        if !self.inner.is_null() {
661            unsafe { vt_frame_processor_frame_release(self.inner) };
662            self.inner = core::ptr::null_mut();
663        }
664    }
665}
666
667impl FrameProcessorFrame {
668    /// Wrap an IOSurface-backed `CVPixelBuffer` and presentation timestamp.
669    ///
670    /// # Errors
671    ///
672    /// Returns [`VTError::InvalidArgument`] when the buffer is not backed by
673    /// `IOSurface`, or [`VTError::ApiFailed`] if the Swift bridge rejects it.
674    pub fn new(buffer: &CVPixelBuffer, presentation_time_stamp: CMTime) -> Result<Self, VTError> {
675        if !buffer.is_backed_by_io_surface() {
676            return Err(VTError::InvalidArgument(
677                "VTFrameProcessorFrame requires an IOSurface-backed CVPixelBuffer".to_string(),
678            ));
679        }
680
681        let mut out = core::ptr::null_mut();
682        let status = unsafe {
683            vt_frame_processor_frame_create(
684                buffer.as_ptr(),
685                to_ffi_time(presentation_time_stamp),
686                &raw mut out,
687            )
688        };
689        if status != 0 || out.is_null() {
690            return Err(VTError::ApiFailed {
691                api: "VTFrameProcessorFrame.init(buffer:presentationTimeStamp:)",
692                status,
693            });
694        }
695
696        Ok(Self {
697            inner: out,
698            buffer: buffer.clone(),
699            presentation_time_stamp,
700        })
701    }
702
703    #[must_use]
704    pub fn buffer(&self) -> &CVPixelBuffer {
705        &self.buffer
706    }
707
708    #[must_use]
709    pub const fn presentation_time_stamp(&self) -> CMTime {
710        self.presentation_time_stamp
711    }
712
713    #[must_use]
714    pub const fn as_ptr(&self) -> *mut c_void {
715        self.inner
716    }
717}
718
719/// Retained `VTFrameProcessorOpticalFlow` wrapper.
720pub struct FrameProcessorOpticalFlow {
721    inner: *mut c_void,
722    forward_flow: CVPixelBuffer,
723    backward_flow: CVPixelBuffer,
724}
725
726unsafe impl Send for FrameProcessorOpticalFlow {}
727unsafe impl Sync for FrameProcessorOpticalFlow {}
728
729impl Drop for FrameProcessorOpticalFlow {
730    fn drop(&mut self) {
731        if !self.inner.is_null() {
732            unsafe { vt_frame_processor_optical_flow_release(self.inner) };
733            self.inner = core::ptr::null_mut();
734        }
735    }
736}
737
738impl FrameProcessorOpticalFlow {
739    /// Wrap forward/backward IOSurface-backed flow buffers.
740    ///
741    /// # Errors
742    ///
743    /// Returns [`VTError::InvalidArgument`] when either buffer is not backed by
744    /// `IOSurface`, or [`VTError::ApiFailed`] if the Swift bridge rejects it.
745    pub fn new(
746        forward_flow: &CVPixelBuffer,
747        backward_flow: &CVPixelBuffer,
748    ) -> Result<Self, VTError> {
749        if !forward_flow.is_backed_by_io_surface() || !backward_flow.is_backed_by_io_surface() {
750            return Err(VTError::InvalidArgument(
751                "VTFrameProcessorOpticalFlow requires IOSurface-backed CVPixelBuffers".to_string(),
752            ));
753        }
754
755        let mut out = core::ptr::null_mut();
756        let status = unsafe {
757            vt_frame_processor_optical_flow_create(
758                forward_flow.as_ptr(),
759                backward_flow.as_ptr(),
760                &raw mut out,
761            )
762        };
763        if status != 0 || out.is_null() {
764            return Err(VTError::ApiFailed {
765                api: "VTFrameProcessorOpticalFlow.init(forwardFlow:backwardFlow:)",
766                status,
767            });
768        }
769
770        Ok(Self {
771            inner: out,
772            forward_flow: forward_flow.clone(),
773            backward_flow: backward_flow.clone(),
774        })
775    }
776
777    #[must_use]
778    pub fn forward_flow(&self) -> &CVPixelBuffer {
779        &self.forward_flow
780    }
781
782    #[must_use]
783    pub fn backward_flow(&self) -> &CVPixelBuffer {
784        &self.backward_flow
785    }
786
787    #[must_use]
788    pub const fn as_ptr(&self) -> *mut c_void {
789        self.inner
790    }
791}
792
793/// Active `VTFrameProcessor` session for one specific pipeline.
794pub struct FrameProcessor {
795    inner: *mut c_void,
796}
797
798unsafe impl Send for FrameProcessor {}
799unsafe impl Sync for FrameProcessor {}
800
801impl Drop for FrameProcessor {
802    fn drop(&mut self) {
803        if !self.inner.is_null() {
804            unsafe {
805                vt_frame_processor_end(self.inner);
806                vt_frame_processor_release(self.inner);
807            }
808            self.inner = core::ptr::null_mut();
809        }
810    }
811}
812
813impl FrameProcessor {
814    fn from_status(status: i32, ptr: *mut c_void) -> Result<Self, VTError> {
815        if status != 0 || ptr.is_null() {
816            return Err(VTError::SessionCreateFailed(status));
817        }
818        Ok(Self { inner: ptr })
819    }
820
821    /// Start a session configured for the super-resolution scaler.
822    pub fn start_super_resolution(
823        frame_width: usize,
824        frame_height: usize,
825        scale_factor: usize,
826        use_precomputed_flow: bool,
827        input_is_image: bool,
828    ) -> Result<Self, VTError> {
829        Self::start_super_resolution_with_configuration(default_super_resolution_configuration(
830            frame_width,
831            frame_height,
832            scale_factor,
833            use_precomputed_flow,
834            input_is_image,
835        ))
836    }
837
838    /// Start a session configured with the full `VTSuperResolutionScalerConfiguration` surface.
839    pub fn start_super_resolution_with_configuration(
840        configuration: SuperResolutionConfiguration,
841    ) -> Result<Self, VTError> {
842        let mut out = core::ptr::null_mut();
843        let status = unsafe {
844            vt_super_resolution_start(
845                configuration.frame_width as isize,
846                configuration.frame_height as isize,
847                configuration.scale_factor as isize,
848                configuration.use_precomputed_flow,
849                configuration.input_type as isize,
850                configuration.quality_prioritization as isize,
851                configuration.revision as isize,
852                &raw mut out,
853            )
854        };
855        Self::from_status(status, out)
856    }
857
858    /// Start a motion-blur session.
859    pub fn start_motion_blur(
860        frame_width: usize,
861        frame_height: usize,
862        use_precomputed_flow: bool,
863    ) -> Result<Self, VTError> {
864        let mut configuration = MotionBlurConfiguration::new(frame_width, frame_height);
865        configuration.use_precomputed_flow = use_precomputed_flow;
866        Self::start_motion_blur_with_configuration(configuration)
867    }
868
869    /// Start a motion-blur session with the full `VTMotionBlurConfiguration` surface.
870    pub fn start_motion_blur_with_configuration(
871        configuration: MotionBlurConfiguration,
872    ) -> Result<Self, VTError> {
873        let mut out = core::ptr::null_mut();
874        let status = unsafe {
875            vt_motion_blur_start(
876                configuration.frame_width as isize,
877                configuration.frame_height as isize,
878                configuration.use_precomputed_flow,
879                configuration.quality_prioritization as isize,
880                configuration.revision as isize,
881                &raw mut out,
882            )
883        };
884        Self::from_status(status, out)
885    }
886
887    /// Start a temporal-noise-filter session.
888    pub fn start_temporal_noise_filter(
889        frame_width: usize,
890        frame_height: usize,
891        source_pixel_format: u32,
892    ) -> Result<Self, VTError> {
893        let mut out = core::ptr::null_mut();
894        let status = unsafe {
895            vt_temporal_noise_filter_start(
896                frame_width as isize,
897                frame_height as isize,
898                source_pixel_format,
899                &raw mut out,
900            )
901        };
902        Self::from_status(status, out)
903    }
904
905    /// Start a frame-rate-conversion session.
906    pub fn start_frame_rate_conversion(
907        frame_width: usize,
908        frame_height: usize,
909        use_precomputed_flow: bool,
910    ) -> Result<Self, VTError> {
911        let mut configuration = FrameRateConversionConfiguration::new(frame_width, frame_height);
912        configuration.use_precomputed_flow = use_precomputed_flow;
913        Self::start_frame_rate_conversion_with_configuration(configuration)
914    }
915
916    /// Start a frame-rate-conversion session with the full `VTFrameRateConversionConfiguration` surface.
917    pub fn start_frame_rate_conversion_with_configuration(
918        configuration: FrameRateConversionConfiguration,
919    ) -> Result<Self, VTError> {
920        let mut out = core::ptr::null_mut();
921        let status = unsafe {
922            vt_frame_rate_conversion_start(
923                configuration.frame_width as isize,
924                configuration.frame_height as isize,
925                configuration.use_precomputed_flow,
926                configuration.quality_prioritization as isize,
927                configuration.revision as isize,
928                &raw mut out,
929            )
930        };
931        Self::from_status(status, out)
932    }
933
934    /// Start a low-latency super-resolution session.
935    pub fn start_low_latency_super_resolution(
936        frame_width: usize,
937        frame_height: usize,
938        scale_factor: f32,
939    ) -> Result<Self, VTError> {
940        let mut out = core::ptr::null_mut();
941        let status = unsafe {
942            vt_low_latency_super_resolution_start(
943                frame_width as isize,
944                frame_height as isize,
945                scale_factor,
946                &raw mut out,
947            )
948        };
949        Self::from_status(status, out)
950    }
951
952    /// Start a low-latency frame-interpolation session.
953    pub fn start_low_latency_frame_interpolation(
954        frame_width: usize,
955        frame_height: usize,
956        number_of_interpolated_frames: usize,
957    ) -> Result<Self, VTError> {
958        let mut out = core::ptr::null_mut();
959        let status = unsafe {
960            vt_low_latency_frame_interpolation_start(
961                frame_width as isize,
962                frame_height as isize,
963                number_of_interpolated_frames as isize,
964                &raw mut out,
965            )
966        };
967        Self::from_status(status, out)
968    }
969
970    /// Start an optical-flow session.
971    pub fn start_optical_flow(frame_width: usize, frame_height: usize) -> Result<Self, VTError> {
972        Self::start_optical_flow_with_configuration(OpticalFlowConfiguration::new(
973            frame_width,
974            frame_height,
975        ))
976    }
977
978    /// Start an optical-flow session with the full `VTOpticalFlowConfiguration` surface.
979    pub fn start_optical_flow_with_configuration(
980        configuration: OpticalFlowConfiguration,
981    ) -> Result<Self, VTError> {
982        let mut out = core::ptr::null_mut();
983        let status = unsafe {
984            vt_optical_flow_start(
985                configuration.frame_width as isize,
986                configuration.frame_height as isize,
987                configuration.quality_prioritization as isize,
988                configuration.revision as isize,
989                &raw mut out,
990            )
991        };
992        Self::from_status(status, out)
993    }
994
995    /// Explicitly end the session before drop.
996    pub fn end_session(&self) {
997        unsafe { vt_frame_processor_end(self.inner) };
998    }
999
1000    /// Process one super-resolution submission.
1001    pub fn process_super_resolution(
1002        &self,
1003        source_frame: &FrameProcessorFrame,
1004        previous_frame: Option<&FrameProcessorFrame>,
1005        previous_output_frame: Option<&FrameProcessorFrame>,
1006        optical_flow: Option<&FrameProcessorOpticalFlow>,
1007        submission_mode: FrameProcessorSubmissionMode,
1008        destination_frame: &FrameProcessorFrame,
1009    ) -> Result<(), VTError> {
1010        api_result("VTSuperResolutionScalerParameters", unsafe {
1011            vt_frame_processor_process_super_resolution(
1012                self.inner,
1013                source_frame.as_ptr(),
1014                frame_ptr(previous_frame),
1015                frame_ptr(previous_output_frame),
1016                flow_ptr(optical_flow),
1017                submission_mode as i32,
1018                destination_frame.as_ptr(),
1019            )
1020        })
1021    }
1022
1023    /// Queue one super-resolution submission into an existing Metal command buffer.
1024    pub fn process_super_resolution_with_command_buffer(
1025        &self,
1026        command_buffer: &CommandBuffer,
1027        source_frame: &FrameProcessorFrame,
1028        previous_frame: Option<&FrameProcessorFrame>,
1029        previous_output_frame: Option<&FrameProcessorFrame>,
1030        optical_flow: Option<&FrameProcessorOpticalFlow>,
1031        submission_mode: FrameProcessorSubmissionMode,
1032        destination_frame: &FrameProcessorFrame,
1033    ) -> Result<(), VTError> {
1034        process_with_command_buffer(
1035            "process(with:parameters:) super-resolution",
1036            command_buffer,
1037            |command_buffer| unsafe {
1038                vt_frame_processor_process_super_resolution_with_command_buffer(
1039                    self.inner,
1040                    command_buffer,
1041                    source_frame.as_ptr(),
1042                    frame_ptr(previous_frame),
1043                    frame_ptr(previous_output_frame),
1044                    flow_ptr(optical_flow),
1045                    submission_mode as i32,
1046                    destination_frame.as_ptr(),
1047                )
1048            },
1049        )
1050    }
1051
1052    /// Process one motion-blur submission.
1053    #[allow(clippy::too_many_arguments)]
1054    pub fn process_motion_blur(
1055        &self,
1056        source_frame: &FrameProcessorFrame,
1057        next_frame: Option<&FrameProcessorFrame>,
1058        previous_frame: Option<&FrameProcessorFrame>,
1059        next_optical_flow: Option<&FrameProcessorOpticalFlow>,
1060        previous_optical_flow: Option<&FrameProcessorOpticalFlow>,
1061        motion_blur_strength: usize,
1062        submission_mode: FrameProcessorSubmissionMode,
1063        destination_frame: &FrameProcessorFrame,
1064    ) -> Result<(), VTError> {
1065        api_result("VTMotionBlurParameters", unsafe {
1066            vt_frame_processor_process_motion_blur(
1067                self.inner,
1068                source_frame.as_ptr(),
1069                frame_ptr(next_frame),
1070                frame_ptr(previous_frame),
1071                flow_ptr(next_optical_flow),
1072                flow_ptr(previous_optical_flow),
1073                motion_blur_strength as isize,
1074                submission_mode as i32,
1075                destination_frame.as_ptr(),
1076            )
1077        })
1078    }
1079
1080    /// Queue one motion-blur submission into an existing Metal command buffer.
1081    #[allow(clippy::too_many_arguments)]
1082    pub fn process_motion_blur_with_command_buffer(
1083        &self,
1084        command_buffer: &CommandBuffer,
1085        source_frame: &FrameProcessorFrame,
1086        next_frame: Option<&FrameProcessorFrame>,
1087        previous_frame: Option<&FrameProcessorFrame>,
1088        next_optical_flow: Option<&FrameProcessorOpticalFlow>,
1089        previous_optical_flow: Option<&FrameProcessorOpticalFlow>,
1090        motion_blur_strength: usize,
1091        submission_mode: FrameProcessorSubmissionMode,
1092        destination_frame: &FrameProcessorFrame,
1093    ) -> Result<(), VTError> {
1094        process_with_command_buffer(
1095            "process(with:parameters:) motion-blur",
1096            command_buffer,
1097            |command_buffer| unsafe {
1098                vt_frame_processor_process_motion_blur_with_command_buffer(
1099                    self.inner,
1100                    command_buffer,
1101                    source_frame.as_ptr(),
1102                    frame_ptr(next_frame),
1103                    frame_ptr(previous_frame),
1104                    flow_ptr(next_optical_flow),
1105                    flow_ptr(previous_optical_flow),
1106                    motion_blur_strength as isize,
1107                    submission_mode as i32,
1108                    destination_frame.as_ptr(),
1109                )
1110            },
1111        )
1112    }
1113
1114    /// Process one temporal-noise-filter submission.
1115    pub fn process_temporal_noise_filter(
1116        &self,
1117        source_frame: &FrameProcessorFrame,
1118        next_frames: &[FrameProcessorFrame],
1119        previous_frames: &[FrameProcessorFrame],
1120        destination_frame: &FrameProcessorFrame,
1121        filter_strength: f32,
1122        has_discontinuity: bool,
1123    ) -> Result<(), VTError> {
1124        let next_ptrs = frame_slice_ptrs(next_frames);
1125        let previous_ptrs = frame_slice_ptrs(previous_frames);
1126        api_result("VTTemporalNoiseFilterParameters", unsafe {
1127            vt_frame_processor_process_temporal_noise_filter(
1128                self.inner,
1129                source_frame.as_ptr(),
1130                next_ptrs.as_ptr(),
1131                next_ptrs.len(),
1132                previous_ptrs.as_ptr(),
1133                previous_ptrs.len(),
1134                destination_frame.as_ptr(),
1135                filter_strength,
1136                has_discontinuity,
1137            )
1138        })
1139    }
1140
1141    /// Queue one temporal-noise-filter submission into an existing Metal command buffer.
1142    pub fn process_temporal_noise_filter_with_command_buffer(
1143        &self,
1144        command_buffer: &CommandBuffer,
1145        source_frame: &FrameProcessorFrame,
1146        next_frames: &[FrameProcessorFrame],
1147        previous_frames: &[FrameProcessorFrame],
1148        destination_frame: &FrameProcessorFrame,
1149        filter_strength: f32,
1150        has_discontinuity: bool,
1151    ) -> Result<(), VTError> {
1152        let next_ptrs = frame_slice_ptrs(next_frames);
1153        let previous_ptrs = frame_slice_ptrs(previous_frames);
1154        process_with_command_buffer(
1155            "process(with:parameters:) temporal-noise-filter",
1156            command_buffer,
1157            |command_buffer| unsafe {
1158                vt_frame_processor_process_temporal_noise_filter_with_command_buffer(
1159                    self.inner,
1160                    command_buffer,
1161                    source_frame.as_ptr(),
1162                    next_ptrs.as_ptr(),
1163                    next_ptrs.len(),
1164                    previous_ptrs.as_ptr(),
1165                    previous_ptrs.len(),
1166                    destination_frame.as_ptr(),
1167                    filter_strength,
1168                    has_discontinuity,
1169                )
1170            },
1171        )
1172    }
1173
1174    /// Process one frame-rate-conversion submission.
1175    pub fn process_frame_rate_conversion(
1176        &self,
1177        source_frame: &FrameProcessorFrame,
1178        next_frame: Option<&FrameProcessorFrame>,
1179        optical_flow: Option<&FrameProcessorOpticalFlow>,
1180        interpolation_phase: &[f32],
1181        submission_mode: FrameRateConversionSubmissionMode,
1182        destination_frames: &[FrameProcessorFrame],
1183    ) -> Result<(), VTError> {
1184        validate_parallel_lengths(
1185            "interpolation_phase",
1186            interpolation_phase,
1187            destination_frames,
1188        )?;
1189        let destination_ptrs = frame_slice_ptrs(destination_frames);
1190        api_result("VTFrameRateConversionParameters", unsafe {
1191            vt_frame_processor_process_frame_rate_conversion(
1192                self.inner,
1193                source_frame.as_ptr(),
1194                frame_ptr(next_frame),
1195                flow_ptr(optical_flow),
1196                interpolation_phase.as_ptr(),
1197                interpolation_phase.len(),
1198                submission_mode as i32,
1199                destination_ptrs.as_ptr(),
1200                destination_ptrs.len(),
1201            )
1202        })
1203    }
1204
1205    /// Queue one frame-rate-conversion submission into an existing Metal command buffer.
1206    pub fn process_frame_rate_conversion_with_command_buffer(
1207        &self,
1208        command_buffer: &CommandBuffer,
1209        source_frame: &FrameProcessorFrame,
1210        next_frame: Option<&FrameProcessorFrame>,
1211        optical_flow: Option<&FrameProcessorOpticalFlow>,
1212        interpolation_phase: &[f32],
1213        submission_mode: FrameRateConversionSubmissionMode,
1214        destination_frames: &[FrameProcessorFrame],
1215    ) -> Result<(), VTError> {
1216        validate_parallel_lengths(
1217            "interpolation_phase",
1218            interpolation_phase,
1219            destination_frames,
1220        )?;
1221        let destination_ptrs = frame_slice_ptrs(destination_frames);
1222        process_with_command_buffer(
1223            "process(with:parameters:) frame-rate-conversion",
1224            command_buffer,
1225            |command_buffer| unsafe {
1226                vt_frame_processor_process_frame_rate_conversion_with_command_buffer(
1227                    self.inner,
1228                    command_buffer,
1229                    source_frame.as_ptr(),
1230                    frame_ptr(next_frame),
1231                    flow_ptr(optical_flow),
1232                    interpolation_phase.as_ptr(),
1233                    interpolation_phase.len(),
1234                    submission_mode as i32,
1235                    destination_ptrs.as_ptr(),
1236                    destination_ptrs.len(),
1237                )
1238            },
1239        )
1240    }
1241
1242    /// Process one low-latency super-resolution submission.
1243    pub fn process_low_latency_super_resolution(
1244        &self,
1245        source_frame: &FrameProcessorFrame,
1246        destination_frame: &FrameProcessorFrame,
1247    ) -> Result<(), VTError> {
1248        api_result("VTLowLatencySuperResolutionScalerParameters", unsafe {
1249            vt_frame_processor_process_low_latency_super_resolution(
1250                self.inner,
1251                source_frame.as_ptr(),
1252                destination_frame.as_ptr(),
1253            )
1254        })
1255    }
1256
1257    /// Queue one low-latency super-resolution submission into an existing Metal command buffer.
1258    pub fn process_low_latency_super_resolution_with_command_buffer(
1259        &self,
1260        command_buffer: &CommandBuffer,
1261        source_frame: &FrameProcessorFrame,
1262        destination_frame: &FrameProcessorFrame,
1263    ) -> Result<(), VTError> {
1264        process_with_command_buffer(
1265            "process(with:parameters:) low-latency-super-resolution",
1266            command_buffer,
1267            |command_buffer| unsafe {
1268                vt_frame_processor_process_low_latency_super_resolution_with_command_buffer(
1269                    self.inner,
1270                    command_buffer,
1271                    source_frame.as_ptr(),
1272                    destination_frame.as_ptr(),
1273                )
1274            },
1275        )
1276    }
1277
1278    /// Process one low-latency frame-interpolation submission.
1279    pub fn process_low_latency_frame_interpolation(
1280        &self,
1281        source_frame: &FrameProcessorFrame,
1282        previous_frame: &FrameProcessorFrame,
1283        interpolation_phase: &[f32],
1284        destination_frames: &[FrameProcessorFrame],
1285    ) -> Result<(), VTError> {
1286        validate_parallel_lengths(
1287            "interpolation_phase",
1288            interpolation_phase,
1289            destination_frames,
1290        )?;
1291        let destination_ptrs = frame_slice_ptrs(destination_frames);
1292        api_result("VTLowLatencyFrameInterpolationParameters", unsafe {
1293            vt_frame_processor_process_low_latency_frame_interpolation(
1294                self.inner,
1295                source_frame.as_ptr(),
1296                previous_frame.as_ptr(),
1297                interpolation_phase.as_ptr(),
1298                interpolation_phase.len(),
1299                destination_ptrs.as_ptr(),
1300                destination_ptrs.len(),
1301            )
1302        })
1303    }
1304
1305    /// Queue one low-latency frame-interpolation submission into an existing Metal command buffer.
1306    pub fn process_low_latency_frame_interpolation_with_command_buffer(
1307        &self,
1308        command_buffer: &CommandBuffer,
1309        source_frame: &FrameProcessorFrame,
1310        previous_frame: &FrameProcessorFrame,
1311        interpolation_phase: &[f32],
1312        destination_frames: &[FrameProcessorFrame],
1313    ) -> Result<(), VTError> {
1314        validate_parallel_lengths(
1315            "interpolation_phase",
1316            interpolation_phase,
1317            destination_frames,
1318        )?;
1319        let destination_ptrs = frame_slice_ptrs(destination_frames);
1320        process_with_command_buffer(
1321            "process(with:parameters:) low-latency-frame-interpolation",
1322            command_buffer,
1323            |command_buffer| unsafe {
1324                vt_frame_processor_process_low_latency_frame_interpolation_with_command_buffer(
1325                    self.inner,
1326                    command_buffer,
1327                    source_frame.as_ptr(),
1328                    previous_frame.as_ptr(),
1329                    interpolation_phase.as_ptr(),
1330                    interpolation_phase.len(),
1331                    destination_ptrs.as_ptr(),
1332                    destination_ptrs.len(),
1333                )
1334            },
1335        )
1336    }
1337
1338    /// Process one optical-flow submission.
1339    pub fn process_optical_flow(
1340        &self,
1341        source_frame: &FrameProcessorFrame,
1342        next_frame: &FrameProcessorFrame,
1343        submission_mode: FrameProcessorSubmissionMode,
1344        destination_optical_flow: &FrameProcessorOpticalFlow,
1345    ) -> Result<(), VTError> {
1346        api_result("VTOpticalFlowParameters", unsafe {
1347            vt_frame_processor_process_optical_flow(
1348                self.inner,
1349                source_frame.as_ptr(),
1350                next_frame.as_ptr(),
1351                submission_mode as i32,
1352                destination_optical_flow.as_ptr(),
1353            )
1354        })
1355    }
1356
1357    /// Queue one optical-flow submission into an existing Metal command buffer.
1358    pub fn process_optical_flow_with_command_buffer(
1359        &self,
1360        command_buffer: &CommandBuffer,
1361        source_frame: &FrameProcessorFrame,
1362        next_frame: &FrameProcessorFrame,
1363        submission_mode: FrameProcessorSubmissionMode,
1364        destination_optical_flow: &FrameProcessorOpticalFlow,
1365    ) -> Result<(), VTError> {
1366        process_with_command_buffer(
1367            "process(with:parameters:) optical-flow",
1368            command_buffer,
1369            |command_buffer| unsafe {
1370                vt_frame_processor_process_optical_flow_with_command_buffer(
1371                    self.inner,
1372                    command_buffer,
1373                    source_frame.as_ptr(),
1374                    next_frame.as_ptr(),
1375                    submission_mode as i32,
1376                    destination_optical_flow.as_ptr(),
1377                )
1378            },
1379        )
1380    }
1381
1382    /// Raw `VTFrameProcessor` pointer.
1383    #[must_use]
1384    pub const fn as_ptr(&self) -> *mut c_void {
1385        self.inner
1386    }
1387}
1388
1389fn default_super_resolution_configuration(
1390    frame_width: usize,
1391    frame_height: usize,
1392    scale_factor: usize,
1393    use_precomputed_flow: bool,
1394    input_is_image: bool,
1395) -> SuperResolutionConfiguration {
1396    let mut configuration =
1397        SuperResolutionConfiguration::new(frame_width, frame_height, scale_factor);
1398    configuration.use_precomputed_flow = use_precomputed_flow;
1399    configuration.input_type = if input_is_image {
1400        VTSuperResolutionScalerConfigurationInputType::Image
1401    } else {
1402        VTSuperResolutionScalerConfigurationInputType::Video
1403    };
1404    configuration
1405}
1406
1407fn to_ffi_time(time: CMTime) -> ffi::CMTime {
1408    time
1409}
1410
1411fn frame_ptr(frame: Option<&FrameProcessorFrame>) -> *mut c_void {
1412    frame.map_or(core::ptr::null_mut(), FrameProcessorFrame::as_ptr)
1413}
1414
1415fn flow_ptr(flow: Option<&FrameProcessorOpticalFlow>) -> *mut c_void {
1416    flow.map_or(core::ptr::null_mut(), FrameProcessorOpticalFlow::as_ptr)
1417}
1418
1419fn frame_slice_ptrs(frames: &[FrameProcessorFrame]) -> Vec<*mut c_void> {
1420    frames.iter().map(FrameProcessorFrame::as_ptr).collect()
1421}
1422
1423fn validate_parallel_lengths<T>(
1424    label: &'static str,
1425    left: &[f32],
1426    right: &[T],
1427) -> Result<(), VTError> {
1428    if left.len() == right.len() {
1429        Ok(())
1430    } else {
1431        Err(VTError::InvalidArgument(format!(
1432            "{label} length ({}) must match destination frame count ({})",
1433            left.len(),
1434            right.len()
1435        )))
1436    }
1437}
1438
1439fn process_with_command_buffer(
1440    api: &'static str,
1441    command_buffer: &CommandBuffer,
1442    process: impl FnOnce(*mut c_void) -> i32,
1443) -> Result<(), VTError> {
1444    let status = command_buffer
1445        .encode_foreign(|foreign| process(foreign.command_buffer()))
1446        .map_err(VTError::CommandBuffer)?;
1447    api_result(api, status)
1448}
1449
1450fn api_result(api: &'static str, status: i32) -> Result<(), VTError> {
1451    if status == 0 {
1452        Ok(())
1453    } else {
1454        Err(VTError::ApiFailed { api, status })
1455    }
1456}