block_compression/
decode.rs

1//! CPU based decoding.
2
3#[cfg(any(feature = "bc15", feature = "bc6h", feature = "bc7"))]
4mod block;
5
6#[cfg(feature = "bc7")]
7#[cfg_attr(docsrs, doc(cfg(feature = "bc7")))]
8pub use self::block::decode_block_bc7;
9#[cfg(feature = "bc15")]
10#[cfg_attr(docsrs, doc(cfg(feature = "bc15")))]
11pub use self::block::{
12    decode_block_bc1, decode_block_bc2, decode_block_bc3, decode_block_bc4, decode_block_bc5,
13};
14#[cfg(feature = "bc6h")]
15#[cfg_attr(docsrs, doc(cfg(feature = "bc6h")))]
16pub use self::block::{decode_block_bc6h, decode_block_bc6h_float};
17#[cfg(feature = "bc6h")]
18use crate::BC6HSettings;
19#[cfg(feature = "bc7")]
20use crate::BC7Settings;
21#[cfg(any(feature = "bc15", feature = "bc6h", feature = "bc7"))]
22use crate::CompressionVariant;
23
24/// Trait to decode a BC variant into RGBA8 data.
25#[cfg(any(feature = "bc15", feature = "bc6h", feature = "bc7"))]
26trait BlockRgba8Decoder {
27    fn decode_block_rgba8(compressed: &[u8], decompressed: &mut [u8], pitch: usize);
28    fn block_byte_size() -> u32;
29}
30
31/// Trait to decode a BC variant into RGBA16F data.
32#[cfg(feature = "bc6h")]
33trait BlockRgba16fDecoder {
34    fn decode_block_rgba16f(compressed: &[u8], decompressed: &mut [half::f16], pitch: usize);
35    fn block_byte_size() -> u32;
36}
37
38/// Trait to decode a BC variant into RGBA32F data.
39#[cfg(feature = "bc6h")]
40trait BlockRgba32fDecoder {
41    fn decode_block_rgba32f(compressed: &[u8], decompressed: &mut [f32], pitch: usize);
42    fn block_byte_size() -> u32;
43}
44
45#[cfg(feature = "bc15")]
46struct BC1Decoder;
47#[cfg(feature = "bc15")]
48struct BC2Decoder;
49#[cfg(feature = "bc15")]
50struct BC3Decoder;
51#[cfg(feature = "bc15")]
52struct BC4Decoder;
53#[cfg(feature = "bc15")]
54struct BC5Decoder;
55#[cfg(feature = "bc6h")]
56struct BC6HDecoder;
57#[cfg(feature = "bc7")]
58struct BC7Decoder;
59
60#[cfg(feature = "bc15")]
61impl BlockRgba8Decoder for BC1Decoder {
62    #[inline(always)]
63    fn decode_block_rgba8(compressed: &[u8], decompressed: &mut [u8], pitch: usize) {
64        decode_block_bc1(compressed, decompressed, pitch)
65    }
66
67    fn block_byte_size() -> u32 {
68        CompressionVariant::BC1.block_byte_size()
69    }
70}
71
72#[cfg(feature = "bc15")]
73impl BlockRgba8Decoder for BC2Decoder {
74    #[inline(always)]
75    fn decode_block_rgba8(compressed: &[u8], decompressed: &mut [u8], pitch: usize) {
76        decode_block_bc2(compressed, decompressed, pitch)
77    }
78
79    fn block_byte_size() -> u32 {
80        CompressionVariant::BC2.block_byte_size()
81    }
82}
83
84#[cfg(feature = "bc15")]
85impl BlockRgba8Decoder for BC3Decoder {
86    #[inline(always)]
87    fn decode_block_rgba8(compressed: &[u8], decompressed: &mut [u8], pitch: usize) {
88        decode_block_bc3(compressed, decompressed, pitch)
89    }
90
91    fn block_byte_size() -> u32 {
92        CompressionVariant::BC3.block_byte_size()
93    }
94}
95
96#[cfg(feature = "bc15")]
97impl BlockRgba8Decoder for BC4Decoder {
98    #[inline(always)]
99    fn decode_block_rgba8(compressed: &[u8], decompressed: &mut [u8], pitch: usize) {
100        const PITCH: usize = 4;
101        let mut buffer = [0u8; 16];
102        decode_block_bc4(compressed, &mut buffer, 4);
103
104        // Convert R8 to RGBA8
105        for y in 0..4 {
106            for x in 0..4 {
107                let out_pos = y * pitch + x * 4;
108                let in_pos = y * PITCH + x;
109
110                decompressed[out_pos] = buffer[in_pos];
111                decompressed[out_pos + 1] = 0;
112                decompressed[out_pos + 2] = 0;
113                decompressed[out_pos + 3] = 0;
114            }
115        }
116    }
117
118    fn block_byte_size() -> u32 {
119        CompressionVariant::BC4.block_byte_size()
120    }
121}
122
123#[cfg(feature = "bc15")]
124impl BlockRgba8Decoder for BC5Decoder {
125    #[inline(always)]
126    fn decode_block_rgba8(compressed: &[u8], decompressed: &mut [u8], pitch: usize) {
127        const PITCH: usize = 8;
128        let mut buffer = [0u8; 32];
129        decode_block_bc5(compressed, &mut buffer, PITCH);
130
131        // Convert RG8 to RGBA8
132        for y in 0..4 {
133            for x in 0..4 {
134                let out_pos = y * pitch + x * 4;
135                let in_pos = y * PITCH + x * 2;
136
137                decompressed[out_pos] = buffer[in_pos];
138                decompressed[out_pos + 1] = buffer[in_pos + 1];
139                decompressed[out_pos + 2] = 0;
140                decompressed[out_pos + 3] = 0;
141            }
142        }
143    }
144
145    fn block_byte_size() -> u32 {
146        CompressionVariant::BC5.block_byte_size()
147    }
148}
149
150#[cfg(feature = "bc6h")]
151fn linear_to_srgb(linear: f32) -> u8 {
152    let v = if linear <= 0.0031308 {
153        linear * 12.92
154    } else {
155        1.055 * linear.powf(1.0 / 2.4) - 0.055
156    };
157
158    (v.clamp(0.0, 1.0) * 255.0).round() as u8
159}
160
161#[cfg(feature = "bc6h")]
162impl BlockRgba8Decoder for BC6HDecoder {
163    #[inline(always)]
164    fn decode_block_rgba8(compressed: &[u8], decompressed: &mut [u8], pitch: usize) {
165        const PITCH: usize = 12;
166        let mut buffer = [0.0_f32; 48];
167        decode_block_bc6h_float(compressed, &mut buffer, PITCH, false);
168
169        // Convert RGB16F to RGBA8
170        for y in 0..4 {
171            for x in 0..4 {
172                let out_pos = y * pitch + x * 4;
173                let in_pos = y * PITCH + x * 3;
174
175                decompressed[out_pos] = linear_to_srgb(buffer[in_pos]) as _;
176                decompressed[out_pos + 1] = linear_to_srgb(buffer[in_pos + 1]) as _;
177                decompressed[out_pos + 2] = linear_to_srgb(buffer[in_pos + 2]) as _;
178                decompressed[out_pos + 3] = 0;
179            }
180        }
181    }
182
183    fn block_byte_size() -> u32 {
184        CompressionVariant::BC6H(BC6HSettings::basic()).block_byte_size()
185    }
186}
187
188#[cfg(feature = "bc7")]
189impl BlockRgba8Decoder for BC7Decoder {
190    #[inline(always)]
191    fn decode_block_rgba8(compressed: &[u8], decompressed: &mut [u8], pitch: usize) {
192        decode_block_bc7(compressed, decompressed, pitch)
193    }
194
195    fn block_byte_size() -> u32 {
196        CompressionVariant::BC7(BC7Settings::alpha_basic()).block_byte_size()
197    }
198}
199
200#[cfg(any(feature = "bc15", feature = "bc6h", feature = "bc7"))]
201fn decompress_rgba8<D: BlockRgba8Decoder>(
202    width: u32,
203    height: u32,
204    blocks_data: &[u8],
205    rgba_data: &mut [u8],
206) {
207    let blocks_x = width.div_ceil(4);
208    let blocks_y = height.div_ceil(4);
209    let block_byte_size = D::block_byte_size() as usize;
210    let output_row_pitch = width as usize * 4; // Always RGBA
211
212    for by in 0..blocks_y {
213        for bx in 0..blocks_x {
214            let block_index = (by * blocks_x + bx) as usize;
215            let block_offset = block_index * block_byte_size;
216
217            if block_offset + block_byte_size > blocks_data.len() {
218                break;
219            }
220
221            let output_offset = (by * 4 * output_row_pitch as u32 + bx * 16) as usize;
222
223            if output_offset < rgba_data.len() {
224                D::decode_block_rgba8(
225                    &blocks_data[block_offset..block_offset + block_byte_size],
226                    &mut rgba_data[output_offset..],
227                    output_row_pitch,
228                );
229            }
230        }
231    }
232}
233
234#[cfg(feature = "bc6h")]
235impl BlockRgba16fDecoder for BC6HDecoder {
236    #[inline(always)]
237    fn decode_block_rgba16f(compressed: &[u8], decompressed: &mut [half::f16], pitch: usize) {
238        decode_block_bc6h(compressed, decompressed, pitch, false);
239    }
240
241    fn block_byte_size() -> u32 {
242        CompressionVariant::BC6H(BC6HSettings::basic()).block_byte_size()
243    }
244}
245
246#[cfg(feature = "bc6h")]
247fn decompress_rgba16f<D: BlockRgba16fDecoder>(
248    width: u32,
249    height: u32,
250    blocks_data: &[u8],
251    rgba_data: &mut [half::f16],
252) {
253    let blocks_x = width.div_ceil(4);
254    let blocks_y = height.div_ceil(4);
255    let block_byte_size = D::block_byte_size() as usize;
256    let output_row_pitch = width as usize * 4; // Always RGBA16f
257
258    for by in 0..blocks_y {
259        for bx in 0..blocks_x {
260            let block_index = (by * blocks_x + bx) as usize;
261            let block_offset = block_index * block_byte_size;
262
263            if block_offset + block_byte_size > blocks_data.len() {
264                break;
265            }
266
267            let output_offset = (by * 4 * output_row_pitch as u32 + bx * 16) as usize;
268
269            if output_offset < rgba_data.len() {
270                D::decode_block_rgba16f(
271                    &blocks_data[block_offset..block_offset + block_byte_size],
272                    &mut rgba_data[output_offset..],
273                    output_row_pitch,
274                );
275            }
276        }
277    }
278}
279
280#[cfg(feature = "bc6h")]
281impl BlockRgba32fDecoder for BC6HDecoder {
282    #[inline(always)]
283    fn decode_block_rgba32f(compressed: &[u8], decompressed: &mut [f32], pitch: usize) {
284        decode_block_bc6h_float(compressed, decompressed, pitch, false);
285    }
286
287    fn block_byte_size() -> u32 {
288        CompressionVariant::BC6H(BC6HSettings::basic()).block_byte_size()
289    }
290}
291
292#[cfg(feature = "bc6h")]
293fn decompress_rgba32f<D: BlockRgba32fDecoder>(
294    width: u32,
295    height: u32,
296    blocks_data: &[u8],
297    rgba_data: &mut [f32],
298) {
299    let blocks_x = width.div_ceil(4);
300    let blocks_y = height.div_ceil(4);
301    let block_byte_size = D::block_byte_size() as usize;
302    let output_row_pitch = width as usize * 4; // Always RGBA32f
303
304    for by in 0..blocks_y {
305        for bx in 0..blocks_x {
306            let block_index = (by * blocks_x + bx) as usize;
307            let block_offset = block_index * block_byte_size;
308
309            if block_offset + block_byte_size > blocks_data.len() {
310                break;
311            }
312
313            let output_offset = (by * 4 * output_row_pitch as u32 + bx * 16) as usize;
314
315            if output_offset < rgba_data.len() {
316                D::decode_block_rgba32f(
317                    &blocks_data[block_offset..block_offset + block_byte_size],
318                    &mut rgba_data[output_offset..],
319                    output_row_pitch,
320                );
321            }
322        }
323    }
324}
325
326/// Helper function to easily decompress block data into RGBA8 data.
327///
328/// # Panics
329/// - The `blocks_data` has not the expected size (`variant.blocks_byte_size()`)
330/// - The `rgba_data` has not the expected size (`width * height * 4`)
331#[cfg(any(feature = "bc15", feature = "bc6h", feature = "bc7"))]
332#[cfg_attr(
333    docsrs,
334    doc(cfg(any(feature = "bc15", feature = "bc6h", feature = "bc7")))
335)]
336pub fn decompress_blocks_as_rgba8(
337    variant: CompressionVariant,
338    width: u32,
339    height: u32,
340    blocks_data: &[u8],
341    rgba_data: &mut [u8],
342) {
343    let expected_input_size = variant.blocks_byte_size(width, height);
344    assert_eq!(
345        blocks_data.len(),
346        expected_input_size,
347        "the input bitstream slice has not the expected size"
348    );
349
350    let expected_output_size = width as usize * height as usize * 4;
351    assert_eq!(
352        rgba_data.len(),
353        expected_output_size,
354        "the output slice has not the expected size"
355    );
356
357    match variant {
358        #[cfg(feature = "bc15")]
359        CompressionVariant::BC1 => {
360            decompress_rgba8::<BC1Decoder>(width, height, blocks_data, rgba_data)
361        }
362        #[cfg(feature = "bc15")]
363        CompressionVariant::BC2 => {
364            decompress_rgba8::<BC2Decoder>(width, height, blocks_data, rgba_data)
365        }
366        #[cfg(feature = "bc15")]
367        CompressionVariant::BC3 => {
368            decompress_rgba8::<BC3Decoder>(width, height, blocks_data, rgba_data)
369        }
370        #[cfg(feature = "bc15")]
371        CompressionVariant::BC4 => {
372            decompress_rgba8::<BC4Decoder>(width, height, blocks_data, rgba_data)
373        }
374        #[cfg(feature = "bc15")]
375        CompressionVariant::BC5 => {
376            decompress_rgba8::<BC5Decoder>(width, height, blocks_data, rgba_data)
377        }
378        #[cfg(feature = "bc6h")]
379        CompressionVariant::BC6H(..) => {
380            decompress_rgba8::<BC6HDecoder>(width, height, blocks_data, rgba_data)
381        }
382        #[cfg(feature = "bc7")]
383        CompressionVariant::BC7(..) => {
384            decompress_rgba8::<BC7Decoder>(width, height, blocks_data, rgba_data)
385        }
386    }
387}
388
389/// Helper function to easily decompress block data into RGBA16F data. Only BCH6 is currently supported.
390///
391/// # Panics
392/// - The `blocks_data` has not the expected size (`variant.blocks_byte_size()`)
393/// - The `rgba_data` has not the expected size (`width * height * 4`)
394/// - If `variant` is any other value than BC6H.
395#[cfg(feature = "bc6h")]
396#[cfg_attr(docsrs, doc(cfg(feature = "bc6h")))]
397pub fn decompress_blocks_as_rgba16f(
398    variant: CompressionVariant,
399    width: u32,
400    height: u32,
401    blocks_data: &[u8],
402    rgba_data: &mut [half::f16],
403) {
404    let expected_input_size = variant.blocks_byte_size(width, height);
405
406    assert_eq!(
407        blocks_data.len(),
408        expected_input_size,
409        "the input bitstream slice has not the expected size"
410    );
411
412    let expected_output_size = width as usize * height as usize * 4;
413    assert_eq!(
414        rgba_data.len(),
415        expected_output_size,
416        "the output slice has not the expected size"
417    );
418
419    match variant {
420        CompressionVariant::BC6H(..) => {
421            decompress_rgba16f::<BC6HDecoder>(width, height, blocks_data, rgba_data)
422        }
423        #[allow(unreachable_patterns)]
424        _ => {
425            panic!("unsupported compression variant");
426        }
427    }
428}
429
430/// Helper function to easily decompress block data into RGBA32F data. Only BCH6 is currently supported.
431///
432/// # Panics
433/// - The `blocks_data` has not the expected size (`variant.blocks_byte_size()`)
434/// - The `rgba_data` has not the expected size (`width * height * 4`)
435/// - If `variant` is any other value than BC6H.
436#[cfg(feature = "bc6h")]
437#[cfg_attr(docsrs, doc(cfg(feature = "bc6h")))]
438pub fn decompress_blocks_as_rgba32f(
439    variant: CompressionVariant,
440    width: u32,
441    height: u32,
442    blocks_data: &[u8],
443    rgba_data: &mut [f32],
444) {
445    let expected_input_size = variant.blocks_byte_size(width, height);
446    assert_eq!(
447        blocks_data.len(),
448        expected_input_size,
449        "the input bitstream slice has not the expected size"
450    );
451
452    let expected_output_size = width as usize * height as usize * 4;
453    assert_eq!(
454        rgba_data.len(),
455        expected_output_size,
456        "the output slice has not the expected size"
457    );
458
459    match variant {
460        CompressionVariant::BC6H(..) => {
461            decompress_rgba32f::<BC6HDecoder>(width, height, blocks_data, rgba_data)
462        }
463        #[allow(unreachable_patterns)]
464        _ => {
465            panic!("unsupported compression variant");
466        }
467    }
468}