Skip to main content

vyre_libs/visual/downsample/
mod.rs

1//! 2× box-filter downsample for half-resolution blur.
2//!
3//! Averages each 2×2 block of pixels into one output pixel.
4//! Category A composition  -  pure IR. No Tier 2.5 primitives.
5
6use vyre_foundation::ir::{BufferAccess, BufferDecl, DataType, Expr, Node, Program};
7
8const OP_ID: &str = "vyre-libs::visual::downsample";
9
10/// Build a Program that 2× downsamples `input` into `output`.
11///
12/// - `input`:  `[u32; width * height]`  -  source pixels (packed RGBA)
13/// - `output`: `[u32; (width/2) * (height/2)]`  -  downsampled result
14/// - Width and height must be even.
15#[must_use]
16pub fn downsample_2x(input: &str, output: &str, width: u32, height: u32) -> Program {
17    let out_w = width / 2;
18    let out_h = height / 2;
19    let input_count = width.saturating_mul(height);
20    let output_count = out_w.saturating_mul(out_h);
21
22    Program::wrapped(
23        vec![
24            BufferDecl::storage(input, 0, BufferAccess::ReadOnly, DataType::U32)
25                .with_count(input_count),
26            BufferDecl::storage(output, 1, BufferAccess::ReadWrite, DataType::U32)
27                .with_count(output_count),
28        ],
29        super::PIXEL_WORKGROUP_SIZE,
30        vec![crate::region::wrap_anonymous(
31            OP_ID,
32            vec![
33                Node::let_bind("idx", Expr::gid_x()),
34                Node::if_then(
35                    Expr::lt(Expr::var("idx"), Expr::u32(output_count)),
36                    vec![
37                        Node::let_bind("ox", Expr::rem(Expr::var("idx"), Expr::u32(out_w.max(1)))),
38                        Node::let_bind("oy", Expr::div(Expr::var("idx"), Expr::u32(out_w.max(1)))),
39                        // Source pixel coordinates.
40                        Node::let_bind("sx", Expr::mul(Expr::var("ox"), Expr::u32(2))),
41                        Node::let_bind("sy", Expr::mul(Expr::var("oy"), Expr::u32(2))),
42                        // Load 4 source pixels.
43                        Node::let_bind(
44                            "p00",
45                            Expr::load(
46                                input,
47                                Expr::add(
48                                    Expr::mul(Expr::var("sy"), Expr::u32(width)),
49                                    Expr::var("sx"),
50                                ),
51                            ),
52                        ),
53                        Node::let_bind(
54                            "p10",
55                            Expr::load(
56                                input,
57                                Expr::add(
58                                    Expr::mul(Expr::var("sy"), Expr::u32(width)),
59                                    Expr::add(Expr::var("sx"), Expr::u32(1)),
60                                ),
61                            ),
62                        ),
63                        Node::let_bind(
64                            "p01",
65                            Expr::load(
66                                input,
67                                Expr::add(
68                                    Expr::mul(
69                                        Expr::add(Expr::var("sy"), Expr::u32(1)),
70                                        Expr::u32(width),
71                                    ),
72                                    Expr::var("sx"),
73                                ),
74                            ),
75                        ),
76                        Node::let_bind(
77                            "p11",
78                            Expr::load(
79                                input,
80                                Expr::add(
81                                    Expr::mul(
82                                        Expr::add(Expr::var("sy"), Expr::u32(1)),
83                                        Expr::u32(width),
84                                    ),
85                                    Expr::add(Expr::var("sx"), Expr::u32(1)),
86                                ),
87                            ),
88                        ),
89                        // Average each channel: (c0+c1+c2+c3+2) >> 2
90                        // R channel
91                        Node::let_bind(
92                            "r",
93                            Expr::shr(
94                                Expr::add(
95                                    Expr::add(
96                                        Expr::add(
97                                            Expr::bitand(Expr::var("p00"), Expr::u32(0xFF)),
98                                            Expr::bitand(Expr::var("p10"), Expr::u32(0xFF)),
99                                        ),
100                                        Expr::add(
101                                            Expr::bitand(Expr::var("p01"), Expr::u32(0xFF)),
102                                            Expr::bitand(Expr::var("p11"), Expr::u32(0xFF)),
103                                        ),
104                                    ),
105                                    Expr::u32(2),
106                                ),
107                                Expr::u32(2),
108                            ),
109                        ),
110                        // G channel
111                        Node::let_bind(
112                            "g",
113                            Expr::shr(
114                                Expr::add(
115                                    Expr::add(
116                                        Expr::add(
117                                            Expr::bitand(
118                                                Expr::shr(Expr::var("p00"), Expr::u32(8)),
119                                                Expr::u32(0xFF),
120                                            ),
121                                            Expr::bitand(
122                                                Expr::shr(Expr::var("p10"), Expr::u32(8)),
123                                                Expr::u32(0xFF),
124                                            ),
125                                        ),
126                                        Expr::add(
127                                            Expr::bitand(
128                                                Expr::shr(Expr::var("p01"), Expr::u32(8)),
129                                                Expr::u32(0xFF),
130                                            ),
131                                            Expr::bitand(
132                                                Expr::shr(Expr::var("p11"), Expr::u32(8)),
133                                                Expr::u32(0xFF),
134                                            ),
135                                        ),
136                                    ),
137                                    Expr::u32(2),
138                                ),
139                                Expr::u32(2),
140                            ),
141                        ),
142                        // B channel
143                        Node::let_bind(
144                            "b",
145                            Expr::shr(
146                                Expr::add(
147                                    Expr::add(
148                                        Expr::add(
149                                            Expr::bitand(
150                                                Expr::shr(Expr::var("p00"), Expr::u32(16)),
151                                                Expr::u32(0xFF),
152                                            ),
153                                            Expr::bitand(
154                                                Expr::shr(Expr::var("p10"), Expr::u32(16)),
155                                                Expr::u32(0xFF),
156                                            ),
157                                        ),
158                                        Expr::add(
159                                            Expr::bitand(
160                                                Expr::shr(Expr::var("p01"), Expr::u32(16)),
161                                                Expr::u32(0xFF),
162                                            ),
163                                            Expr::bitand(
164                                                Expr::shr(Expr::var("p11"), Expr::u32(16)),
165                                                Expr::u32(0xFF),
166                                            ),
167                                        ),
168                                    ),
169                                    Expr::u32(2),
170                                ),
171                                Expr::u32(2),
172                            ),
173                        ),
174                        // A channel
175                        Node::let_bind(
176                            "a",
177                            Expr::shr(
178                                Expr::add(
179                                    Expr::add(
180                                        Expr::add(
181                                            Expr::shr(Expr::var("p00"), Expr::u32(24)),
182                                            Expr::shr(Expr::var("p10"), Expr::u32(24)),
183                                        ),
184                                        Expr::add(
185                                            Expr::shr(Expr::var("p01"), Expr::u32(24)),
186                                            Expr::shr(Expr::var("p11"), Expr::u32(24)),
187                                        ),
188                                    ),
189                                    Expr::u32(2),
190                                ),
191                                Expr::u32(2),
192                            ),
193                        ),
194                        // Pack RGBA.
195                        Node::let_bind(
196                            "packed",
197                            Expr::bitor(
198                                Expr::bitor(
199                                    Expr::var("r"),
200                                    Expr::shl(Expr::var("g"), Expr::u32(8)),
201                                ),
202                                Expr::bitor(
203                                    Expr::shl(Expr::var("b"), Expr::u32(16)),
204                                    Expr::shl(Expr::var("a"), Expr::u32(24)),
205                                ),
206                            ),
207                        ),
208                        // Write output.
209                        Node::let_bind(
210                            "oidx",
211                            Expr::add(
212                                Expr::mul(Expr::var("oy"), Expr::u32(out_w)),
213                                Expr::var("ox"),
214                            ),
215                        ),
216                        Node::store(output, Expr::var("oidx"), Expr::var("packed")),
217                    ],
218                ),
219            ],
220        )],
221    )
222}
223
224inventory::submit! {
225    vyre_foundation::operation::OperationRegistration {
226        semantic_version: 1,
227        signature: None,
228        tier: vyre_foundation::operation::OperationTier::Library,
229        laws: &[],
230        tolerance: vyre_foundation::operation::TolerancePolicy::EXACT,
231        id: OP_ID,
232        build: Some(|| downsample_2x("input", "output", 4, 4)),
233        test_inputs: Some(|| {
234            // 4×4 all-white → 2×2 all-white
235            let input = vec![0xFFFF_FFFFu32; 16];
236            vec![vec![
237                crate::visual::byte_helpers::u32_words_to_le_bytes(&input),
238                vec![0u8; 16],
239            ]]
240        }),
241        expected_output: Some(|| {
242            let expected = vec![0xFFFF_FFFFu32; 4];
243            vec![vec![crate::visual::byte_helpers::u32_words_to_le_bytes(&expected)]]
244        }),
245        category: Some("visual"),
246    }
247}