Skip to main content

phaneron_plugin_utils/
yadif.rs

1/*
2 * Phaneron media compositing software.
3 * Copyright (C) 2023 SuperFlyTV AB
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use std::collections::VecDeque;
20
21use phaneron_plugin::{traits::ProcessShader, types::NodeContext, types::VideoFrame, ShaderParams};
22
23#[derive(PartialEq, Eq)]
24pub enum YadifMode {
25    Field,
26    FrameNospatial,
27    FieldNospatial,
28}
29
30pub struct YadifConfig {
31    pub mode: YadifMode,
32    pub tff: bool,
33}
34
35pub struct Yadif {
36    config: YadifConfig,
37    send_field: bool,
38    skip_spatial: bool,
39    yadif_cl: YadifCl,
40    input: VecDeque<VideoFrame>,
41}
42
43impl Yadif {
44    pub fn new(context: &NodeContext, width: usize, height: usize, config: YadifConfig) -> Self {
45        let send_field =
46            config.mode == YadifMode::Field || config.mode == YadifMode::FieldNospatial;
47        let skip_spatial =
48            config.mode == YadifMode::FrameNospatial || config.mode == YadifMode::FieldNospatial;
49        let yadif_cl = YadifCl::new(context, width, height);
50        Self {
51            config,
52            send_field,
53            skip_spatial,
54            yadif_cl,
55            input: VecDeque::with_capacity(4), // 3 fields + last one pushed
56        }
57    }
58
59    pub fn run(&mut self, source: &VideoFrame) -> Vec<VideoFrame> {
60        self.input.push_front(source.clone());
61        if self.input.len() < 3 {
62            return vec![];
63        }
64
65        if self.input.len() > 3 {
66            self.input.pop_back();
67        }
68
69        let mut outputs: Vec<VideoFrame> = vec![];
70
71        let output = self.run_yadif(false);
72        outputs.push(output);
73
74        if self.send_field {
75            let output = self.run_yadif(true);
76            outputs.push(output);
77        }
78
79        outputs
80    }
81
82    fn run_yadif(&mut self, is_second: bool) -> VideoFrame {
83        self.yadif_cl.run(
84            self.input.iter().collect::<Vec<&VideoFrame>>().as_slice(),
85            u32::from(self.config.tff) ^ u32::from(!is_second),
86            u32::from(self.config.tff),
87            u32::from(self.skip_spatial),
88        )
89    }
90}
91
92pub struct YadifCl {
93    width: usize,
94    height: usize,
95    shader: Box<dyn ProcessShader>,
96}
97
98impl YadifCl {
99    fn new(context: &NodeContext, width: usize, height: usize) -> Self {
100        let kernel = include_str!("shaders/yadif.cl");
101        let shader = context.create_process_shader(kernel.into(), "yadif".into());
102
103        Self {
104            width,
105            height,
106            shader: Box::new(shader),
107        }
108    }
109
110    fn run(&self, inputs: &[&VideoFrame], parity: u32, tff: u32, skip_spatial: u32) -> VideoFrame {
111        let mut params = ShaderParams::default();
112        params.set_param_video_frame_input(inputs[0].clone());
113        params.set_param_video_frame_input(inputs[1].clone());
114        params.set_param_video_frame_input(inputs[2].clone());
115        params.set_param_u32_input(parity);
116        params.set_param_u32_input(tff);
117        params.set_param_u32_input(skip_spatial);
118        params.set_param_video_frame_output(self.width, self.height);
119
120        let outputs = self.shader.run(params, &[self.width, self.height]);
121
122        outputs[0].clone()
123    }
124}