image_canvas/
shader.rs

1//! Conversion between texels, mostly color.
2//!
3//! Takes quite a lot of inspiration from how GPUs work. We have a primitive sampler unit, a
4//! fragment unit, and pipeline multiple texels in parallel.
5use alloc::{boxed::Box, vec::Vec};
6use core::{fmt, ops::Range};
7use image_texel::image::{AtomicImageRef, CellImageRef, ImageMut, ImageRef};
8use image_texel::{AsTexel, Texel, TexelBuffer};
9
10use crate::arch::ShuffleOps;
11use crate::bits::FromBits;
12use crate::color::Color;
13use crate::frame::{BytePlaneAtomics, BytePlaneCells, BytePlaneMut, BytePlaneRef};
14use crate::layout::{
15    BitEncoding, Block, CanvasLayout, PlaneBytes, SampleBits, SampleParts, Texel as TexelBits,
16};
17use crate::Canvas;
18
19/// State for converting colors between canvas.
20pub struct Converter {
21    inner: Box<ConverterRt>,
22}
23
24/// A planner / builder for converting frame colors.
25///
26/// This does the computation of [`Converter::run_to_completion`] step-by-step. You begin by
27/// supplying the layouts and can then alter the buffers on which it is run, make choices between
28/// alternative conversion routes introspect the plan, before executing it.
29pub struct ConverterRun<'data> {
30    /// The runtime state of the converter.
31    rt: &'data mut ConverterRt,
32    /// The computed choices of layout and color space information.
33    info: ConvertInfo,
34    /// The computed method by which to convert colors.
35    recolor: Option<RecolorOps>,
36    /// Additional parameters when the recoloring happens by int/bit shuffling.
37    int_shuffle_params: IntShuffleParameter,
38    /// Data selector that will be called when doing color transform.
39    convert_with: TexelConvertWith,
40    /// An owned collection of buffers to use.
41    buffers: ConverterBuffer<'data>,
42    /// The planes with the primary color information in this run.
43    color_planes: PlaneConfiguration,
44}
45
46struct PlaneConfiguration {
47    in_idx: PlaneIdx,
48    out_idx: PlaneIdx,
49}
50
51struct ColorLayout {
52    // FIXME: should be an array of up to 4 planes (or w/e we maximally support). Or a small-vec
53    // actually but an array might be easier. We can always default to a zero-sized plane.
54    in_layout: PlaneBytes,
55    out_layout: PlaneBytes,
56    in_color: Color,
57    out_color: Color,
58}
59
60#[derive(Default)]
61struct ConverterBuffer<'data> {
62    in_plane: Vec<PlaneSource<'data>>,
63    in_cell: Vec<CellSource<'data>>,
64    in_atomic: Vec<AtomicSource<'data>>,
65    out_plane: Vec<PlaneTarget<'data>>,
66    out_cell: Vec<CellTarget<'data>>,
67    out_atomic: Vec<AtomicTarget<'data>>,
68}
69
70#[derive(Clone, Copy, Debug)]
71enum PlaneIdx {
72    Sync(u16),
73    Cell(u16),
74    Atomic(u16),
75}
76
77/// An entry of data in a `ConverterRun`.
78pub struct ConverterPlaneHandle<'run> {
79    /// The index of the plane in the converter.
80    idx: PlaneIdx,
81    /// Does this configure an in or out plane, when writing into the handle.
82    direction_in: bool,
83    /// Back reference to the run we are configuring.
84    hdl: &'run mut PlaneConfiguration,
85}
86
87/// Runtime state utilized by operations.
88struct ConverterRt {
89    /// How many super-blocks to do at once.
90    ///
91    /// A super-texel is a unit determined by the shader which encompasses a whole number of input
92    /// and output blocks, i.e. a common multiple of both pixel counts.
93    chunk: usize,
94    /// The number of chunks to do at once.
95    ///
96    /// Each chunk is one consecutive set of super-texels so discontinuities can occur from one
97    /// chunk to the next. That allows us to specialize the texel index and texel fetch code for
98    /// the most common texel index schemes that occur as a result.
99    chunk_count: usize,
100
101    /// How many input texels are read in each super-block chunk.
102    chunk_per_fetch: usize,
103    /// How many out texels are written in each super-block chunk.
104    chunk_per_write: usize,
105
106    super_blocks: TexelBuffer<[u32; 2]>,
107    /// Buffer where we store input texels after reading them.
108    in_texels: TexelBuffer,
109    /// Texel coordinates of stored texels.
110    in_coords: TexelBuffer<[u32; 2]>,
111    /// Index in the input planes.
112    in_index_list: Vec<usize>,
113    /// Runs of texels to be read by anything reading input texels.
114    /// Each entry refers to a range of indices in `in_index` and a range of corresponding texels
115    /// in `in_texels`, or it can refer directly to the input image.
116    in_slices: TexelBuffer<[usize; 2]>,
117    /// Buffer where we store input texels before writing.
118    out_texels: TexelBuffer,
119    /// Texel coordinates of stored texels.
120    out_coords: TexelBuffer<[u32; 2]>,
121    /// Index in the input planes.
122    out_index_list: Vec<usize>,
123    /// Runs of texels to be read by anything writing output texels.
124    /// Each entry refers to a range of indices in `out_index` and a range of corresponding texels
125    /// in `out_texels`, or it can refer directly to the output image.
126    out_slices: TexelBuffer<[usize; 2]>,
127
128    /// The input texels, split into pixels in the color's natural order.
129    pixel_in_buffer: TexelBuffer,
130    /// The pixels in a color space in the middle of in and out, mostly CIE XYZ+Alpha.
131    neutral_color_buffer: TexelBuffer,
132    /// The output texels, split into pixels in the color's natural order.
133    pixel_out_buffer: TexelBuffer,
134}
135
136struct ConvertInfo {
137    /// The layout of all the consumed color frames.
138    layout: ColorLayout,
139    /// The selected way to represent pixels in common parameter space.
140    common_pixel: CommonPixel,
141    /// The selected common color space, midpoint for conversion.
142    #[allow(unused)]
143    common_color: CommonColor,
144    /// How pixels from blocks are ordered in the `pixel_buf`.
145    common_blocks: CommonPixelOrder,
146    /// The texel fetch we perform for input.
147    /// Note that this is not necessarily the underlying texel as we throw away parts of
148    /// interpretation, as long as it preserves size and alignment in a matter that produces the
149    /// correct bits on indexing.
150    in_kind: TexelKind,
151    /// The texel fetch we perform for output.
152    /// Note that this is not necessarily the underlying texel as we throw away parts of
153    /// interpretation, as long as it preserves size and alignment in a matter that produces the
154    /// correct bits on indexing.
155    out_kind: TexelKind,
156}
157
158/// Denotes the type we pass to the color decoder.
159///
160/// This is an internal type due to the type assigned to each color being an implementation detail.
161/// Consider that rgb565 can be passed as u16 or a special wrapper type for example. Or that
162/// `[f16; 2]` can be a `u32` or a `[u16; 2]` or a wrapper. Until there's indication that this
163/// needs stabilization it's kept secret.
164///
165/// For a valid layout it also fits to the indicated color components. There may be more than one
166/// pixel in each texel.
167#[derive(Clone, Copy, Debug)]
168pub enum TexelKind {
169    U8,
170    U8x2,
171    U8x3,
172    U8x4,
173    U8x6,
174    U16,
175    U16x2,
176    U16x3,
177    U16x4,
178    U16x6,
179    F32,
180    F32x2,
181    F32x3,
182    F32x4,
183    F32x6,
184}
185
186#[derive(Clone, Debug)]
187pub enum ConversionError {
188    InputLayoutDoesNotMatchPlan,
189    OutputLayoutDoesNotMatchPlan,
190    InputColorDoesNotMatchPlanes,
191    OutputColorDoesNotMatchPlanes,
192    UnsupportedInputLayout,
193    UnsupportedInputColor,
194    UnsupportedOutputLayout,
195    UnsupportedOutputColor,
196}
197
198pub(crate) trait GenericTexelAction<R = ()> {
199    fn run<T>(self, texel: Texel<T>) -> R;
200}
201
202/// FIXME(color): What about colors with more than three stimuli (e.g. scientific instruments such
203/// as Mars202 have 13 bands). In general, any observer function that's not a linear combination of
204/// the CIE XYZ can not be converted from/to color spaces defined through it without loss because
205/// 'observation' is assumed to measure spectrum under a wavelength response curve.
206///
207/// Examples that fall outside of this, 'yellow' cones in human vision and other awesome mutations
208/// such as partial color blindness, that's a shifted response curve of the common cone variant. Do
209/// we want to be able to represent those?
210#[derive(Clone, Copy, Debug)]
211enum CommonPixel {
212    F32x4,
213}
214
215#[derive(Clone, Copy, Debug)]
216enum CommonColor {
217    CieXyz,
218}
219
220/// The order of pixels in their super blocks, when in the internal pixel buffer. For 1×1 blocks
221/// (=pixels) this doesn't matter.
222#[derive(Clone, Copy, Debug)]
223enum CommonPixelOrder {
224    /// Blocks are expanded such that all pixels are in row order.
225    PixelsInRowOrder,
226}
227
228type PlaneSource<'data> = ImageRef<'data, PlaneBytes>;
229type CellSource<'data> = CellImageRef<'data, PlaneBytes>;
230type AtomicSource<'data> = AtomicImageRef<'data, PlaneBytes>;
231
232type PlaneTarget<'data> = ImageMut<'data, PlaneBytes>;
233type CellTarget<'data> = CellImageRef<'data, PlaneBytes>;
234type AtomicTarget<'data> = AtomicImageRef<'data, PlaneBytes>;
235
236struct Sources<'re, 'data> {
237    sync: &'re [PlaneSource<'data>],
238    cell: &'re [CellSource<'data>],
239    atomic: &'re [AtomicSource<'data>],
240}
241
242struct Targets<'re, 'data> {
243    sync: &'re mut [PlaneTarget<'data>],
244    cell: &'re [CellTarget<'data>],
245    atomic: &'re [AtomicTarget<'data>],
246}
247
248struct PlaneIo<'re, 'data> {
249    sources: Sources<'re, 'data>,
250    targets: Targets<'re, 'data>,
251}
252
253/// The function pointers doing the conversion.
254///
255/// Note how there are no types involved here. Instead, `TexelCoord` is a polymorphic buffer that
256/// each function can access with any type it sees feed. We expect the constructor to ensure only
257/// matching types are being used.
258struct ConvertOps<'rt> {
259    /// Convert in texel coordinates to an index in the color plane.
260    fill_in_index: fn(&ConvertInfo, &[[u32; 2]], &mut [usize], ChunkSpec),
261    /// Convert out texel coordinates to an index in the color plane.
262    fill_out_index: fn(&ConvertInfo, &[[u32; 2]], &mut [usize], ChunkSpec),
263
264    /// Expand all texels into pixels in normalized channel order.
265    expand: fn(&ConvertOps, &TexelBuffer, &mut TexelBuffer, PlaneIo),
266    /// Take pixels in normalized channel order and apply color conversion.
267    recolor: Option<RecolorOps>,
268    /// Join all pixels from normalized channel order to texels, clamping.
269    join: fn(&ConvertOps, &TexelBuffer, &mut TexelBuffer, PlaneIo),
270
271    /// Well-define bit/byte/channel shuffle operations on common texel combinations.
272    shuffle: ShuffleOps,
273
274    /** Parameter of ops that are available, dynamically. **/
275    /// The plane where we load data from. There may be other planes involved that are not color
276    /// data.
277    ///
278    /// FIXME: this should be an array for multi-planar data.
279    color_in_plane: PlaneIdx,
280    /// The plane we write into.
281    color_out_plane: PlaneIdx,
282
283    /// The parameters to an integer shuffle that replaces texel conversion.
284    int_shuffle_params: IntShuffleParameter,
285
286    /// The operations that converts from the full input texel to the output texel.
287    ///
288    /// These usually work on the interleaved buffer, i.e. a load and expansion has happened
289    /// before. However, they can indicate to skip those stages if the same information is also
290    /// available on the input / output buffer itself and loads would be purely memory copies.
291    texel: TexelConvertWith,
292
293    /// The layout and format indications valid for the specific runtime.
294    info: &'rt ConvertInfo,
295}
296
297struct TexelConvertWith {
298    ops: fn(&mut ConverterRt, &ConvertOps, PlaneIo),
299    should_defer_texel_read: bool,
300    should_defer_texel_write: bool,
301}
302
303struct RecolorOps {
304    from: fn(&ConvertInfo, &TexelBuffer, &mut TexelBuffer),
305    into: fn(&ConvertInfo, &TexelBuffer, &mut TexelBuffer),
306}
307
308struct IntShuffleOps {
309    call: fn(&mut ConverterRt, &ConvertOps, PlaneIo),
310    shuffle: [u8; 4],
311    should_defer_texel_read: bool,
312    should_defer_texel_write: bool,
313}
314
315#[derive(Default)]
316struct IntShuffleParameter {
317    shuffle: [u8; 4],
318}
319
320#[derive(Debug)]
321struct SuperTexel {
322    blocks: Range<u32>,
323    /// In blocks per super block.
324    in_per_super: u32,
325    /// Out blocks per super block.
326    out_per_super: u32,
327}
328
329pub(crate) struct ChunkSpec<'ch> {
330    pub chunks: &'ch mut [[usize; 2]],
331    pub chunk_size: usize,
332    pub should_defer_texel_ops: bool,
333}
334
335impl Converter {
336    pub fn new() -> Self {
337        Converter {
338            inner: Box::new(ConverterRt {
339                chunk: 1024,
340                chunk_count: 1,
341                chunk_per_fetch: 0,
342                chunk_per_write: 0,
343                super_blocks: TexelBuffer::default(),
344                in_texels: TexelBuffer::default(),
345                in_coords: TexelBuffer::default(),
346                in_index_list: vec![],
347                in_slices: TexelBuffer::default(),
348                out_texels: TexelBuffer::default(),
349                out_coords: TexelBuffer::default(),
350                out_index_list: vec![],
351                out_slices: TexelBuffer::default(),
352                pixel_in_buffer: TexelBuffer::default(),
353                neutral_color_buffer: TexelBuffer::default(),
354                pixel_out_buffer: TexelBuffer::default(),
355            }),
356        }
357    }
358
359    fn recolor_ops(lhs: &CanvasLayout, rhs: &CanvasLayout) -> Option<RecolorOps> {
360        match (lhs.color.as_ref()?, rhs.color.as_ref()?) {
361            (c0, c1) if c0 == c1 => None,
362            // Some more special methods?
363            (_, _) => Some(RecolorOps {
364                from: CommonColor::cie_xyz_from_info,
365                into: CommonColor::cie_xyz_into_info,
366            }),
367        }
368    }
369
370    /// Convert the color information of a whole frame into the colors of another.
371    ///
372    /// This is a combined operation that plans the internal operation for conversion, how to
373    /// schedule them across texels, how to read and write the data, and then runs all of it on
374    /// two owned canvases.
375    pub fn run_to_completion(
376        &mut self,
377        frame_in: &Canvas,
378        frame_out: &mut Canvas,
379    ) -> Result<(), ConversionError> {
380        let mut plan = self.plan(frame_in.layout().clone(), frame_out.layout().clone())?;
381        plan.use_input(frame_in);
382        plan.use_output(frame_out);
383        plan.run()
384    }
385
386    /// Build a converter of color information from one frame into another.
387    pub fn plan(
388        &mut self,
389        in_layout: CanvasLayout,
390        out_layout: CanvasLayout,
391    ) -> Result<ConverterRun<'_>, ConversionError> {
392        let info = ConvertInfo {
393            layout: ColorLayout::from_frames(&in_layout, &out_layout)?,
394            // FIXME(perf): not optimal in all cases, but necessary for accurate conversion.
395            // allow configuration / detect trivial conversion.
396            common_pixel: CommonPixel::F32x4,
397            // FIXME(color): currently the only case, we also go through this if any conversion is
398            // required, but of course in general a potential loss of accuracy. General enough?
399            common_color: CommonColor::CieXyz,
400            // FIXME(perf): optimal order? Or require block join to implement arbitrary reorder.
401            common_blocks: CommonPixelOrder::PixelsInRowOrder,
402            in_kind: TexelKind::from(in_layout.texel.bits),
403            out_kind: TexelKind::from(out_layout.texel.bits),
404        };
405
406        let recolor = Self::recolor_ops(&in_layout, &out_layout);
407
408        let int_shuffle = self
409            .inner
410            .convert_intbuf_with_nocolor_ops(&info)
411            .filter(|_| recolor.is_none());
412
413        let int_shuffle_params;
414        // Choose how we actually perform conversion.
415        let convert_with: TexelConvertWith = {
416            if let Some(int_ops) = &int_shuffle {
417                int_shuffle_params = IntShuffleParameter {
418                    shuffle: int_ops.shuffle,
419                };
420
421                TexelConvertWith {
422                    ops: int_ops.call,
423                    should_defer_texel_read: int_ops.should_defer_texel_read,
424                    should_defer_texel_write: int_ops.should_defer_texel_write,
425                }
426            } else {
427                int_shuffle_params = IntShuffleParameter::default();
428
429                TexelConvertWith {
430                    ops: ConverterRt::convert_texelbuf_with_ops,
431                    should_defer_texel_read: false,
432                    should_defer_texel_write: false,
433                }
434            }
435        };
436
437        Ok(ConverterRun {
438            rt: &mut self.inner,
439            info,
440            recolor,
441            int_shuffle_params,
442            convert_with,
443            buffers: ConverterBuffer::default(),
444            color_planes: PlaneConfiguration {
445                in_idx: PlaneIdx::Sync(0),
446                out_idx: PlaneIdx::Sync(0),
447            },
448        })
449    }
450}
451
452impl<'data> ConverterRun<'data> {
453    /// Define a canvas of input color data.
454    ///
455    /// This replaces any existing input data.
456    ///
457    /// Note on design: Calling this twice is pointless at the moment. There will be a method added
458    /// to choose the frames after adding them to the converter. Then the panic will be delayed to
459    /// runtime with methods of verifying your choice is appropriate to the color of the layouts,
460    /// such as one plane for rgb, two planes for separate alpha, three planes when yuv420
461    /// sub-sampling and so on.
462    ///
463    /// # Panics
464    ///
465    /// The frames must have the layout with which the converter was initialized. This is not
466    /// guaranteed to panic in future versions!
467    pub fn use_input(&mut self, frame_in: &'data Canvas) {
468        let idx = self.buffers.in_plane.len() as u16;
469        let plane = frame_in.plane(0).unwrap();
470        self.buffers.in_plane.push(plane.inner);
471        self.color_planes.in_idx = PlaneIdx::Sync(idx);
472    }
473
474    /// Define an owned output frame to write to.
475    ///
476    /// See [`Self::use_input`] for design and details and panics.
477    pub fn use_output(&mut self, frame_out: &'data mut Canvas) {
478        let idx = self.buffers.out_plane.len() as u16;
479        let plane = frame_out.plane_mut(0).unwrap();
480        self.buffers.out_plane.push(plane.inner);
481        self.color_planes.out_idx = PlaneIdx::Sync(idx);
482    }
483
484    /// Add a read-only slice plane to the input.
485    pub fn add_plane_in(&mut self, plane: BytePlaneRef<'data>) -> ConverterPlaneHandle<'_> {
486        let idx = self.buffers.in_plane.len() as u16;
487        self.buffers.in_plane.push(plane.inner);
488        ConverterPlaneHandle {
489            idx: PlaneIdx::Sync(idx),
490            direction_in: true,
491            hdl: &mut self.color_planes,
492        }
493    }
494
495    /// Add a cell plane to the input.
496    ///
497    /// Note that this plane may overlap be the same as planes added as an output, or overlap. This
498    /// will fail at runtime when there is no algorithm to map the color data between the two.
499    pub fn add_cell_in(&mut self, plane: BytePlaneCells<'data>) -> ConverterPlaneHandle<'_> {
500        let idx = self.buffers.in_cell.len() as u16;
501        self.buffers.in_cell.push(plane.inner);
502        ConverterPlaneHandle {
503            idx: PlaneIdx::Cell(idx),
504            direction_in: true,
505            hdl: &mut self.color_planes,
506        }
507    }
508
509    /// Add an atomic plane to the input.
510    pub fn add_atomic_in(&mut self, plane: BytePlaneAtomics<'data>) -> ConverterPlaneHandle<'_> {
511        let idx = self.buffers.in_atomic.len() as u16;
512        self.buffers.in_atomic.push(plane.inner);
513        ConverterPlaneHandle {
514            idx: PlaneIdx::Atomic(idx),
515            direction_in: true,
516            hdl: &mut self.color_planes,
517        }
518    }
519
520    pub fn add_plane_out(&mut self, plane: BytePlaneMut<'data>) -> ConverterPlaneHandle<'_> {
521        let idx = self.buffers.out_plane.len() as u16;
522        self.buffers.out_plane.push(plane.inner);
523        ConverterPlaneHandle {
524            idx: PlaneIdx::Sync(idx),
525            direction_in: false,
526            hdl: &mut self.color_planes,
527        }
528    }
529
530    pub fn add_cell_out(&mut self, plane: BytePlaneCells<'data>) -> ConverterPlaneHandle<'_> {
531        let idx = self.buffers.out_cell.len() as u16;
532        self.buffers.out_cell.push(plane.inner);
533        ConverterPlaneHandle {
534            idx: PlaneIdx::Cell(idx),
535            direction_in: false,
536            hdl: &mut self.color_planes,
537        }
538    }
539
540    pub fn add_atomic_out(&mut self, plane: BytePlaneAtomics<'data>) -> ConverterPlaneHandle<'_> {
541        let idx = self.buffers.out_atomic.len() as u16;
542        self.buffers.out_atomic.push(plane.inner);
543        ConverterPlaneHandle {
544            idx: PlaneIdx::Atomic(idx),
545            direction_in: false,
546            hdl: &mut self.color_planes,
547        }
548    }
549
550    /// Run on the first frames in input and output.
551    ///
552    /// This chooses the image planes based on colors. (With current design rationale this is
553    /// always a single plane but we'll stay forward compatible here, I think).
554    pub fn run(self) -> Result<(), ConversionError> {
555        let (ii, oi) = (self.color_planes.in_idx, self.color_planes.out_idx);
556        self.run_between(ii, oi)
557    }
558
559    /// Run on a chosen set of planes.
560    fn run_between(
561        mut self,
562        color_in_plane: PlaneIdx,
563        color_out_plane: PlaneIdx,
564    ) -> Result<(), ConversionError> {
565        if *self.layout_in(color_in_plane)? != self.info.layout.in_layout {
566            return Err(ConversionError::InputColorDoesNotMatchPlanes);
567        }
568
569        if *self.layout_out(color_out_plane)? != self.info.layout.out_layout {
570            return Err(ConversionError::OutputColorDoesNotMatchPlanes);
571        }
572
573        // We can not use this optimization with non-slice planes. FIXME: we really should be able
574        // to as it is a simple specialization of the implementation to read and write to different
575        // data. Indeed for atomic we could even take care to load texel units efficiently and to
576        // match the underlying atomic size as best we can.
577        if !matches!(color_in_plane, PlaneIdx::Sync(_))
578            || !matches!(color_out_plane, PlaneIdx::Sync(_))
579        {
580            self.convert_with = TexelConvertWith {
581                ops: ConverterRt::convert_texelbuf_with_ops,
582                should_defer_texel_read: false,
583                should_defer_texel_write: false,
584            };
585        }
586
587        let ops = ConvertOps {
588            fill_in_index: ConverterRt::index_from_in_info,
589            fill_out_index: ConverterRt::index_from_out_info,
590            expand: CommonPixel::expand_from_info,
591            recolor: self.recolor,
592            join: CommonPixel::join_from_info,
593            shuffle: ShuffleOps::default().with_arch(),
594            color_in_plane,
595            color_out_plane,
596            int_shuffle_params: self.int_shuffle_params,
597            texel: self.convert_with,
598            info: &self.info,
599        };
600
601        let plane_io = PlaneIo {
602            sources: Sources {
603                sync: &self.buffers.in_plane,
604                cell: &self.buffers.in_cell,
605                atomic: &self.buffers.in_atomic,
606            },
607            targets: Targets {
608                sync: &mut self.buffers.out_plane,
609                cell: &self.buffers.out_cell,
610                atomic: &self.buffers.out_atomic,
611            },
612        };
613
614        // FIXME: texel errors?
615        Ok(self.rt.with_filled_texels(&self.info, &ops, plane_io))
616    }
617
618    fn layout_in(&self, color_in_plane: PlaneIdx) -> Result<&PlaneBytes, ConversionError> {
619        Ok(match color_in_plane {
620            PlaneIdx::Sync(idx) => self
621                .buffers
622                .in_plane
623                .get(usize::from(idx))
624                .ok_or(ConversionError::InputLayoutDoesNotMatchPlan)?
625                .layout(),
626            PlaneIdx::Cell(idx) => self
627                .buffers
628                .in_cell
629                .get(usize::from(idx))
630                .ok_or(ConversionError::InputLayoutDoesNotMatchPlan)?
631                .layout(),
632            PlaneIdx::Atomic(idx) => self
633                .buffers
634                .in_atomic
635                .get(usize::from(idx))
636                .ok_or(ConversionError::InputLayoutDoesNotMatchPlan)?
637                .layout(),
638        })
639    }
640
641    fn layout_out(&self, color_out_plane: PlaneIdx) -> Result<&PlaneBytes, ConversionError> {
642        Ok(match color_out_plane {
643            PlaneIdx::Sync(idx) => self
644                .buffers
645                .out_plane
646                .get(usize::from(idx))
647                .ok_or(ConversionError::OutputLayoutDoesNotMatchPlan)?
648                .layout(),
649            PlaneIdx::Cell(idx) => self
650                .buffers
651                .out_cell
652                .get(usize::from(idx))
653                .ok_or(ConversionError::OutputLayoutDoesNotMatchPlan)?
654                .layout(),
655            PlaneIdx::Atomic(idx) => self
656                .buffers
657                .out_atomic
658                .get(usize::from(idx))
659                .ok_or(ConversionError::OutputLayoutDoesNotMatchPlan)?
660                .layout(),
661        })
662    }
663}
664
665impl ConverterPlaneHandle<'_> {
666    /// Define that this plane is the input (or output) color of the conversion.
667    ///
668    /// The last modification performed through a [`PlaneHandle`] overrules any previous
669    /// definition. This function should only be called if the color information is supplied by a
670    /// single plane. If the color information in the layout disagrees, running will return an
671    /// error.
672    pub fn set_as_color(self) {
673        if self.direction_in {
674            self.hdl.in_idx = self.idx;
675        } else {
676            self.hdl.out_idx = self.idx;
677        }
678    }
679}
680
681impl ConverterRt {
682    /// Convert all loaded texels, using the provided `ConvertOps` as dynamic function selection.
683    ///
684    /// Assumes that the caller resized all buffers appropriately (TODO: should be a better
685    /// contract for this, with explicit data flow of this invariant and what 'proper' size means,
686    /// because it depends on the chosen ops).
687    fn convert_texelbuf_with_ops(&mut self, ops: &ConvertOps, mut plane_io: PlaneIo) {
688        (ops.expand)(
689            ops,
690            &self.in_texels,
691            &mut self.pixel_in_buffer,
692            plane_io.borrow(),
693        );
694
695        let pixel_out = if let Some(ref recolor) = ops.recolor {
696            (recolor.from)(
697                &ops.info,
698                &self.pixel_in_buffer,
699                &mut self.neutral_color_buffer,
700            );
701            (recolor.into)(
702                &ops.info,
703                &self.neutral_color_buffer,
704                &mut self.pixel_out_buffer,
705            );
706            &self.pixel_out_buffer
707        } else {
708            &self.pixel_in_buffer
709        };
710
711        // FIXME: necessary to do a reorder of pixels here? Or let join do this?
712        (ops.join)(ops, pixel_out, &mut self.out_texels, plane_io.borrow());
713    }
714
715    /// Special case on `convert_texelbuf_with_ops`, when both buffers:
716    ///
717    /// * utilize an expansion-roundtrip-safe color/bit combination
718    /// * have the same bit depths on all channels
719    /// * do not require any color conversion between them
720    /// * as a consequence of these, have a common pixel-to-texel ratio of 1-to-1
721    ///
722    /// This avoids expanding them into `pixel_in_buffer` where they'd be represented as `f32x4`
723    /// and thus undergo an expensive `u8->f32->u8` cast chain.
724    fn convert_intbuf_with_nocolor_ops(&mut self, info: &ConvertInfo) -> Option<IntShuffleOps> {
725        // Not yet handled, we need independent channels and the same amount.
726        // FIXME(perf): we could use very similar code to expand pixels from blocks but that
727        // requires specialized shuffle methods.
728        // FIXME(perf): for simple linear combinations in non-linear space (e.g. both Rec.601
729        // and Rec.709 specify their YUV in the electric domain even though that's not
730        // accurate) we could do them here, too.
731        // FIXME(perf): Utilize a library for this, e.g. `dcv-color-primitives`, but those are
732        // heavy and may have different implementation goals.
733        // - `dvc-color-primitives` uses an unsafe globals to fetch the `fn` to use...
734        // - `dvc-color-primitives` also depends on `paste`, a proc-macro crate.
735        fn determine_shuffle(inp: SampleParts, outp: SampleParts) -> Option<[u8; 4]> {
736            let mut ch_from_common = [0x80u8; 4];
737            let mut ch_from_input = [0x80u8; 4];
738
739            for ((ch, common_pos), idx) in inp.channels().zip(0..4) {
740                if ch.is_some() {
741                    ch_from_input[common_pos as usize] = idx;
742                }
743            }
744
745            for ((ch, common_pos), idx) in outp.channels().zip(0..4) {
746                if ch.is_some() {
747                    ch_from_common[idx] = ch_from_input[common_pos as usize];
748                }
749            }
750
751            Some(ch_from_common)
752        }
753
754        let in_texel = &info.layout.in_layout.texel;
755        let out_texel = &info.layout.out_layout.texel;
756
757        if in_texel.block != Block::Pixel || out_texel.block != Block::Pixel {
758            return None;
759        }
760
761        // We can't handle color conversion inside the shuffles.
762        if info.layout.in_color != info.layout.out_color {
763            return None;
764        }
765
766        let shuffle = determine_shuffle(in_texel.parts, out_texel.parts)?;
767
768        trait Shuffle<T, const N: usize, const M: usize> {
769            fn run(_: &ConvertOps, _: &[[T; N]], _: &mut [[T; M]], _: [u8; 4]);
770        }
771
772        fn shuffle_with_texel<T, S: Shuffle<T, N, M>, const N: usize, const M: usize>(
773            that: &mut ConverterRt,
774            ops: &ConvertOps,
775            io: PlaneIo,
776        ) where
777            T: AsTexel,
778        {
779            debug_assert_eq!(
780                that.chunk, that.chunk_per_fetch,
781                "Inconsistent usage of channel shuffle, only applicable to matching texels"
782            );
783
784            debug_assert_eq!(
785                that.chunk, that.chunk_per_write,
786                "Inconsistent usage of channel shuffle, only applicable to matching texels"
787            );
788
789            let shuffle = ops.int_shuffle_params.shuffle;
790
791            let in_texel = T::texel().array::<N>();
792            let out_texel = T::texel().array::<M>();
793
794            let i_idx = ops.color_in_plane.into_index();
795            let o_idx = ops.color_out_plane.into_index();
796
797            let source_texels = io.sources.sync[i_idx].as_texels(in_texel);
798            let target_texels = io.targets.sync[o_idx].as_mut_texels(out_texel);
799
800            let in_texels = that.in_texels.as_texels(in_texel);
801            let out_texels = that.out_texels.as_mut_texels(out_texel);
802
803            let in_slices = that.in_slices.iter_mut();
804            let out_slices = that.out_slices.iter_mut();
805            let chunks = (0..in_texels.len()).step_by(that.chunk);
806
807            for ((islice, oslice), chunk_start) in in_slices.zip(out_slices).zip(chunks) {
808                let length = in_texels[chunk_start..].len().min(that.chunk);
809
810                let input_slice = if islice[1] > 0 {
811                    debug_assert!(length == islice[1]);
812                    let length = core::mem::replace(&mut islice[1], 0);
813                    &source_texels[islice[0]..][..length]
814                } else {
815                    &in_texels[chunk_start..][..length]
816                };
817
818                let output_slice = if oslice[1] > 0 {
819                    debug_assert!(length == oslice[1]);
820                    let length = core::mem::replace(&mut oslice[1], 0);
821                    &mut target_texels[oslice[0]..][..length]
822                } else {
823                    &mut out_texels[chunk_start..][..length]
824                };
825
826                S::run(ops, input_slice, output_slice, shuffle)
827            }
828        }
829
830        struct ShuffleInt8;
831        struct ShuffleInt16;
832
833        impl Shuffle<u8, 4, 4> for ShuffleInt8 {
834            fn run(ops: &ConvertOps, inp: &[[u8; 4]], outp: &mut [[u8; 4]], shuffle: [u8; 4]) {
835                outp.copy_from_slice(inp);
836                (ops.shuffle.shuffle_u8x4)(outp, shuffle);
837            }
838        }
839
840        impl Shuffle<u8, 3, 4> for ShuffleInt8 {
841            fn run(ops: &ConvertOps, inp: &[[u8; 3]], outp: &mut [[u8; 4]], shuffle: [u8; 4]) {
842                (ops.shuffle.shuffle_u8x3_to_u8x4)(inp, outp, shuffle);
843            }
844        }
845
846        impl Shuffle<u8, 4, 3> for ShuffleInt8 {
847            fn run(ops: &ConvertOps, inp: &[[u8; 4]], outp: &mut [[u8; 3]], shuffle: [u8; 4]) {
848                let shuffle = [shuffle[0], shuffle[1], shuffle[2]];
849                (ops.shuffle.shuffle_u8x4_to_u8x3)(inp, outp, shuffle);
850            }
851        }
852
853        impl Shuffle<u16, 4, 4> for ShuffleInt16 {
854            fn run(ops: &ConvertOps, inp: &[[u16; 4]], outp: &mut [[u16; 4]], shuffle: [u8; 4]) {
855                outp.copy_from_slice(inp);
856                (ops.shuffle.shuffle_u16x4)(outp, shuffle);
857            }
858        }
859
860        impl Shuffle<u16, 3, 4> for ShuffleInt16 {
861            fn run(ops: &ConvertOps, inp: &[[u16; 3]], outp: &mut [[u16; 4]], shuffle: [u8; 4]) {
862                (ops.shuffle.shuffle_u16x3_to_u16x4)(inp, outp, shuffle);
863            }
864        }
865
866        impl Shuffle<u16, 4, 3> for ShuffleInt16 {
867            fn run(ops: &ConvertOps, inp: &[[u16; 4]], outp: &mut [[u16; 3]], shuffle: [u8; 4]) {
868                let shuffle = [shuffle[0], shuffle[1], shuffle[2]];
869                (ops.shuffle.shuffle_u16x4_to_u16x3)(inp, outp, shuffle);
870            }
871        }
872
873        Some(match (in_texel.bits, out_texel.bits) {
874            (SampleBits::UInt8x4, SampleBits::UInt8x4)
875            | (SampleBits::Int8x4, SampleBits::Int8x4) => IntShuffleOps {
876                call: shuffle_with_texel::<u8, ShuffleInt8, 4, 4>,
877                shuffle,
878                should_defer_texel_read: true,
879                should_defer_texel_write: true,
880            },
881            (SampleBits::UInt8x3, SampleBits::UInt8x4)
882            | (SampleBits::Int8x3, SampleBits::Int8x4) => IntShuffleOps {
883                call: shuffle_with_texel::<u8, ShuffleInt8, 3, 4>,
884                shuffle,
885                should_defer_texel_read: true,
886                should_defer_texel_write: true,
887            },
888            (SampleBits::UInt8x4, SampleBits::UInt8x3)
889            | (SampleBits::Int8x4, SampleBits::Int8x3) => IntShuffleOps {
890                call: shuffle_with_texel::<u8, ShuffleInt8, 4, 3>,
891                shuffle,
892                should_defer_texel_read: true,
893                should_defer_texel_write: true,
894            },
895
896            // Simple U16 cases.
897            (SampleBits::UInt16x4, SampleBits::UInt16x4)
898            | (SampleBits::Int16x4, SampleBits::Int16x4) => IntShuffleOps {
899                call: shuffle_with_texel::<u16, ShuffleInt16, 4, 4>,
900                shuffle,
901                should_defer_texel_read: true,
902                should_defer_texel_write: true,
903            },
904            (SampleBits::UInt16x3, SampleBits::UInt16x4)
905            | (SampleBits::Int16x3, SampleBits::Int16x4) => IntShuffleOps {
906                call: shuffle_with_texel::<u16, ShuffleInt16, 3, 4>,
907                shuffle,
908                should_defer_texel_read: true,
909                should_defer_texel_write: true,
910            },
911            (SampleBits::UInt16x4, SampleBits::UInt16x3)
912            | (SampleBits::Int16x4, SampleBits::Int16x3) => IntShuffleOps {
913                call: shuffle_with_texel::<u16, ShuffleInt16, 4, 3>,
914                shuffle,
915                should_defer_texel_read: true,
916                should_defer_texel_write: true,
917            },
918            _ => return None,
919        })
920    }
921
922    /// Choose iteration order of texels, fill with texels and then put them back.
923    fn with_filled_texels(&mut self, info: &ConvertInfo, ops: &ConvertOps, mut frame_io: PlaneIo) {
924        // We *must* make progress.
925        assert!(self.chunk > 0);
926        assert!(self.chunk_count > 0);
927
928        // We use a notion of 'supertexels', the common multiple of input and output texel blocks.
929        // That is, if the input is a 2-by-2 pixel block and the output is single pixels then we
930        // have 4 times as many outputs as inputs, respectively coordinates.
931        //
932        // Anyways, first we fill the coordinate buffers, then calculate the planar indices.
933        let (sb_x, sb_y) = self.super_texel(info);
934        let mut blocks = Self::blocks(sb_x.blocks.clone(), sb_y.blocks.clone());
935
936        assert!(sb_x.in_per_super > 0);
937        assert!(sb_x.in_per_super > 0);
938        assert!(sb_x.out_per_super > 0);
939        assert!(sb_y.out_per_super > 0);
940
941        self.chunk_per_fetch = self.chunk * (sb_x.in_per_super * sb_y.in_per_super) as usize;
942        self.chunk_per_write = self.chunk * (sb_x.out_per_super * sb_y.out_per_super) as usize;
943
944        assert!(self.chunk_per_fetch > 0);
945        assert!(self.chunk_per_write > 0);
946
947        loop {
948            let at_once = self.chunk * self.chunk_count;
949            self.super_blocks.resize(at_once);
950            let actual = blocks(self.super_blocks.as_mut_slice());
951            self.super_blocks.resize(actual);
952
953            if self.super_blocks.is_empty() {
954                break;
955            }
956
957            self.generate_coords(info, ops, &sb_x, &sb_y);
958            self.reserve_buffers(info, ops);
959            // FIXME(planar): should be repeated for all planes?
960            self.read_texels(info, ops, frame_io.borrow());
961            (ops.texel.ops)(self, ops, frame_io.borrow());
962            // FIXME(planar): should be repeated for all planes?
963            self.write_texels(info, ops, frame_io.borrow());
964        }
965    }
966
967    fn super_texel(&self, info: &ConvertInfo) -> (SuperTexel, SuperTexel) {
968        let b0 = info.layout.in_layout.texel.block;
969        let b1 = info.layout.out_layout.texel.block;
970
971        let super_width = core::cmp::max(b0.width(), b1.width());
972        let super_height = core::cmp::max(b0.height(), b1.height());
973
974        // All currently supported texels are a power-of-two.
975        assert!(super_width % b0.width() == 0);
976        assert!(super_width % b1.width() == 0);
977        assert!(super_height % b0.height() == 0);
978        assert!(super_height % b1.height() == 0);
979
980        let sampled_with = |w, bs| w / bs + if w % bs == 0 { 0 } else { 1 };
981
982        let sb_width = sampled_with(info.layout.in_layout.width, super_width);
983        let sb_height = sampled_with(info.layout.in_layout.height, super_height);
984
985        (
986            SuperTexel {
987                blocks: 0..sb_height,
988                in_per_super: super_height / b0.height(),
989                out_per_super: super_height / b1.height(),
990            },
991            SuperTexel {
992                blocks: 0..sb_width,
993                in_per_super: super_width / b0.width(),
994                out_per_super: super_width / b1.width(),
995            },
996        )
997    }
998
999    fn generate_coords(
1000        &mut self,
1001        info: &ConvertInfo,
1002        ops: &ConvertOps,
1003        sb_x: &SuperTexel,
1004        sb_y: &SuperTexel,
1005    ) {
1006        fn is_trivial_super(sup: &SuperTexel) -> bool {
1007            sup.in_per_super == 1 && sup.out_per_super == 1
1008        }
1009
1010        self.in_coords.resize(0);
1011        self.out_coords.resize(0);
1012
1013        if is_trivial_super(sb_x) && is_trivial_super(sb_y) {
1014            // Faster than rustc having to look through and special case the iteration/clones
1015            // below. For some reason, it doesn't do well on `Range::zip()::flatten`.
1016
1017            // FIXME(perf): actually, we'd like to just reuse the `super_blocks` vector where ever
1018            // possible. This is a pure copy at the byte-level.
1019            self.in_coords.resize(self.super_blocks.len());
1020            self.out_coords.resize(self.super_blocks.len());
1021            self.in_coords
1022                .as_mut_slice()
1023                .copy_from_slice(&self.super_blocks);
1024            self.out_coords
1025                .as_mut_slice()
1026                .copy_from_slice(&self.super_blocks);
1027        } else {
1028            let in_chunk_len = (sb_x.in_per_super * sb_y.in_per_super) as usize;
1029            self.in_coords
1030                .resize(self.super_blocks.len() * in_chunk_len);
1031            let out_chunk_len = (sb_x.out_per_super * sb_y.out_per_super) as usize;
1032            self.out_coords
1033                .resize(self.super_blocks.len() * out_chunk_len);
1034
1035            // FIXME(perf): the other iteration order would serve us better. Then there is a larger
1036            // bulk of coordinates looped through at the same time, with less branching as a call
1037            // to std::vec::Vec::extend could rely on the exact length of the iterator.
1038            let mut in_chunks = self.in_coords.as_mut_slice().chunks_exact_mut(in_chunk_len);
1039            let mut out_chunks = self
1040                .out_coords
1041                .as_mut_slice()
1042                .chunks_exact_mut(out_chunk_len);
1043
1044            for &[bx, by] in self.super_blocks.as_slice().iter() {
1045                let (sx, sy) = (bx * sb_x.in_per_super, by * sb_y.in_per_super);
1046                if let Some(chunk) = in_chunks.next() {
1047                    Self::blocks(0..sb_x.in_per_super, 0..sb_y.in_per_super)(chunk);
1048                    for p in chunk.iter_mut() {
1049                        let [ix, iy] = *p;
1050                        *p = [sx + ix, sy + iy];
1051                    }
1052                }
1053
1054                let (sx, sy) = (bx * sb_x.out_per_super, by * sb_y.out_per_super);
1055                if let Some(chunk) = out_chunks.next() {
1056                    Self::blocks(0..sb_x.out_per_super, 0..sb_y.out_per_super)(chunk);
1057                    for p in chunk.iter_mut() {
1058                        let [ox, oy] = *p;
1059                        *p = [sx + ox, sy + oy];
1060                    }
1061                }
1062            }
1063        }
1064
1065        self.in_index_list.resize_with(self.in_coords.len(), || 0);
1066        self.out_index_list.resize_with(self.out_coords.len(), || 0);
1067
1068        self.in_slices.resize(self.chunk_count);
1069        self.out_slices.resize(self.chunk_count);
1070
1071        let in_chunk = ChunkSpec {
1072            chunks: self.in_slices.as_mut_slice(),
1073            chunk_size: self.chunk_per_fetch,
1074            should_defer_texel_ops: ops.texel.should_defer_texel_read,
1075        };
1076
1077        let out_chunk = ChunkSpec {
1078            chunks: self.out_slices.as_mut_slice(),
1079            chunk_size: self.chunk_per_write,
1080            should_defer_texel_ops: ops.texel.should_defer_texel_write,
1081        };
1082
1083        (ops.fill_in_index)(
1084            &info,
1085            self.in_coords.as_slice(),
1086            &mut self.in_index_list,
1087            in_chunk,
1088        );
1089
1090        (ops.fill_out_index)(
1091            &info,
1092            self.out_coords.as_slice(),
1093            &mut self.out_index_list,
1094            out_chunk,
1095        );
1096    }
1097
1098    fn reserve_buffers(&mut self, info: &ConvertInfo, ops: &ConvertOps) {
1099        struct ResizeAction<'data>(&'data mut TexelBuffer, usize);
1100
1101        impl GenericTexelAction for ResizeAction<'_> {
1102            fn run<T>(self, texel: Texel<T>) {
1103                self.0.resize_for_texel(self.1, texel)
1104            }
1105        }
1106
1107        let num_in_texels = self.in_coords.len();
1108        let in_block = info.layout.in_layout.texel.block;
1109        let in_pixels = (in_block.width() * in_block.height()) as usize * num_in_texels;
1110        info.in_kind
1111            .action(ResizeAction(&mut self.in_texels, num_in_texels));
1112
1113        let num_out_texels = self.out_coords.len();
1114        let out_block = info.layout.out_layout.texel.block;
1115        let out_pixels = (out_block.width() * out_block.height()) as usize * num_out_texels;
1116        info.out_kind
1117            .action(ResizeAction(&mut self.out_texels, num_out_texels));
1118
1119        debug_assert!(
1120            in_pixels == out_pixels,
1121            "Mismatching in super block layout: {} {}",
1122            in_pixels,
1123            out_pixels
1124        );
1125
1126        let pixels = in_pixels.max(out_pixels);
1127        info.common_pixel
1128            .action(ResizeAction(&mut self.pixel_in_buffer, pixels));
1129
1130        if let Some(_) = ops.recolor {
1131            info.common_pixel
1132                .action(ResizeAction(&mut self.neutral_color_buffer, pixels));
1133            info.common_pixel
1134                .action(ResizeAction(&mut self.pixel_out_buffer, pixels));
1135        }
1136    }
1137
1138    fn read_texels(&mut self, info: &ConvertInfo, ops: &ConvertOps, from: PlaneIo) {
1139        fn fetch_from_texel_array<T>(
1140            from: &PlaneSource,
1141            idx: &[usize],
1142            into: &mut TexelBuffer,
1143            range: Range<usize>,
1144            texel: Texel<T>,
1145        ) {
1146            into.resize_for_texel(idx.len(), texel);
1147            let idx = idx[range.clone()].iter();
1148            let texels = &mut into.as_mut_texels(texel)[range];
1149
1150            let texel_slice = from.as_texels(texel);
1151            for (&index, into) in idx.zip(texels) {
1152                if let Some(from) = texel_slice.get(index) {
1153                    *into = texel.copy_val(from);
1154                }
1155            }
1156        }
1157
1158        fn fetch_from_texel_cell<T>(
1159            from: &CellSource,
1160            idx: &[usize],
1161            into: &mut TexelBuffer,
1162            range: Range<usize>,
1163            texel: Texel<T>,
1164        ) {
1165            into.resize_for_texel(idx.len(), texel);
1166            let idx = idx[range.clone()].iter();
1167            let texels = &mut into.as_mut_texels(texel)[range];
1168            let texel_slice = from.as_texels(texel).as_slice_of_cells();
1169
1170            for (&index, into) in idx.zip(texels) {
1171                if let Some(from) = texel_slice.get(index) {
1172                    *into = texel.copy_cell(from);
1173                }
1174            }
1175        }
1176
1177        fn fetch_from_texel_atomics<T>(
1178            from: &AtomicSource,
1179            idx: &[usize],
1180            into: &mut TexelBuffer,
1181            range: Range<usize>,
1182            texel: Texel<T>,
1183        ) {
1184            into.resize_for_texel(idx.len(), texel);
1185            let idx = idx[range.clone()].iter();
1186            let texels = &mut into.as_mut_texels(texel)[range];
1187            let texel_slice = from.as_texels(texel);
1188
1189            for (&index, into) in idx.zip(texels) {
1190                if let Some(from) = texel_slice.get(index..index + 1) {
1191                    from.write_to_slice(core::slice::from_mut(into));
1192                }
1193            }
1194        }
1195
1196        struct ReadUnit<'plane, 'data> {
1197            from: &'plane PlaneSource<'data>,
1198            idx: &'plane [usize],
1199            into: &'plane mut TexelBuffer,
1200            range: Range<usize>,
1201        }
1202
1203        impl GenericTexelAction for ReadUnit<'_, '_> {
1204            fn run<T>(self, texel: Texel<T>) {
1205                fetch_from_texel_array(self.from, self.idx, self.into, self.range, texel)
1206            }
1207        }
1208
1209        struct ReadCell<'plane, 'data> {
1210            from: &'plane CellSource<'data>,
1211            idx: &'plane [usize],
1212            into: &'plane mut TexelBuffer,
1213            range: Range<usize>,
1214        }
1215
1216        impl GenericTexelAction for ReadCell<'_, '_> {
1217            fn run<T>(self, texel: Texel<T>) {
1218                fetch_from_texel_cell(self.from, self.idx, self.into, self.range, texel)
1219            }
1220        }
1221
1222        struct ReadAtomic<'plane, 'data> {
1223            from: &'plane AtomicSource<'data>,
1224            idx: &'plane [usize],
1225            into: &'plane mut TexelBuffer,
1226            range: Range<usize>,
1227        }
1228
1229        impl GenericTexelAction for ReadAtomic<'_, '_> {
1230            fn run<T>(self, texel: Texel<T>) {
1231                fetch_from_texel_atomics(self.from, self.idx, self.into, self.range, texel)
1232            }
1233        }
1234
1235        if ops.texel.should_defer_texel_read {
1236            debug_assert!(matches!(ops.color_in_plane, PlaneIdx::Sync(_)));
1237            // Also asserting that this isn't a multi-planar read. For now.
1238
1239            /* For deferred reading, we expect some functions to do the transfer for us allowing us
1240             * to leave the source texel blank, uninitialized, or in an otherwise unreadable state.
1241             * We should skip them. The protocol here is that each chunk has two indices; the index
1242             * in the plane texture and the index up-to-which the texels are to be ignored in the
1243             * `in_texels`.
1244             */
1245            let chunks = self.in_slices.as_mut_slice();
1246            let indexes = self.in_index_list.chunks(self.chunk_per_fetch);
1247            let range = (0..self.in_index_list.len()).step_by(self.chunk_per_fetch);
1248
1249            for (chunk, (indexes, start)) in chunks.iter_mut().zip(indexes.zip(range)) {
1250                let [_, available] = chunk;
1251
1252                if *available == indexes.len() {
1253                    continue;
1254                }
1255
1256                // Only use the input frame if all indexes are available in the layout.
1257                // For this reason read all texels individually into the texel buffer otherwise and
1258                // the indicate that no texels are available from the layout.
1259                *available = 0;
1260                info.in_kind.action(ReadUnit {
1261                    from: &from.sources.sync[ops.color_in_plane.into_index()],
1262                    idx: &self.in_index_list,
1263                    into: &mut self.in_texels,
1264                    range: start..start + indexes.len(),
1265                });
1266            }
1267        } else {
1268            // FIXME(planar):
1269            // FIXME(color): multi-planar texel fetch.
1270            match ops.color_in_plane {
1271                PlaneIdx::Sync(_) => {
1272                    info.in_kind.action(ReadUnit {
1273                        from: &from.sources.sync[ops.color_in_plane.into_index()],
1274                        idx: &self.in_index_list,
1275                        into: &mut self.in_texels,
1276                        range: 0..self.in_index_list.len(),
1277                    });
1278                }
1279                PlaneIdx::Cell(_) => {
1280                    info.in_kind.action(ReadCell {
1281                        from: &from.sources.cell[ops.color_in_plane.into_index()],
1282                        idx: &self.in_index_list,
1283                        into: &mut self.in_texels,
1284                        range: 0..self.in_index_list.len(),
1285                    });
1286                }
1287                PlaneIdx::Atomic(_) => {
1288                    info.in_kind.action(ReadAtomic {
1289                        from: &from.sources.atomic[ops.color_in_plane.into_index()],
1290                        idx: &self.in_index_list,
1291                        into: &mut self.in_texels,
1292                        range: 0..self.in_index_list.len(),
1293                    });
1294                }
1295            }
1296        }
1297    }
1298
1299    /// The job of this function is transferring texel information onto the target plane.
1300    fn write_texels(&mut self, info: &ConvertInfo, ops: &ConvertOps, into: PlaneIo) {
1301        fn write_for_texel_array<T>(
1302            into: &mut PlaneTarget,
1303            idx: &[usize],
1304            from: &TexelBuffer,
1305            range: Range<usize>,
1306            texel: Texel<T>,
1307        ) {
1308            // FIXME(planar):
1309            // FIXME(color): multi-planar texel write.
1310            let idx = idx[range.clone()].iter();
1311            let texels = &from.as_texels(texel)[range];
1312            let texel_slice = into.as_mut_texels(texel);
1313
1314            // The index structure and used texel type should match.
1315            debug_assert_eq!(idx.len(), texels.len());
1316
1317            for (&index, from) in idx.zip(texels) {
1318                if let Some(into) = texel_slice.get_mut(index) {
1319                    *into = texel.copy_val(from);
1320                }
1321            }
1322        }
1323
1324        fn write_for_texel_cells<T>(
1325            into: &CellTarget,
1326            idx: &[usize],
1327            from: &TexelBuffer,
1328            range: Range<usize>,
1329            texel: Texel<T>,
1330        ) {
1331            // FIXME(planar):
1332            // FIXME(color): multi-planar texel write.
1333            let idx = idx[range.clone()].iter();
1334            let texels = &from.as_texels(texel)[range];
1335            let texel_slice = into.as_texels(texel).as_slice_of_cells();
1336
1337            // The index structure and used texel type should match.
1338            debug_assert_eq!(idx.len(), texels.len());
1339            // FIXME: we can do this much faster as range copies or swizzled copies.
1340
1341            for (&index, from) in idx.zip(texels) {
1342                if let Some(into) = texel_slice.get(index) {
1343                    into.set(texel.copy_val(from));
1344                }
1345            }
1346        }
1347
1348        fn write_for_texel_atomics<T>(
1349            into: &AtomicTarget,
1350            idx: &[usize],
1351            from: &TexelBuffer,
1352            range: Range<usize>,
1353            texel: Texel<T>,
1354        ) {
1355            // FIXME(planar):
1356            // FIXME(color): multi-planar texel write.
1357            let idx = idx[range.clone()].iter();
1358            let texels = &from.as_texels(texel)[range];
1359            let texel_slice = into.as_texels(texel);
1360
1361            // The index structure and used texel type should match.
1362            debug_assert_eq!(idx.len(), texels.len());
1363            // FIXME: we can do this much faster as range copies or swizzled copies.
1364
1365            for (&index, from) in idx.zip(texels) {
1366                if let Some(into) = texel_slice.get(index..index + 1) {
1367                    into.read_from_slice(core::slice::from_ref(&from));
1368                }
1369            }
1370        }
1371
1372        struct WriteUnit<'plane, 'data> {
1373            into: &'plane mut PlaneTarget<'data>,
1374            idx: &'plane [usize],
1375            from: &'plane TexelBuffer,
1376            range: Range<usize>,
1377        }
1378
1379        impl GenericTexelAction for WriteUnit<'_, '_> {
1380            fn run<T>(self, texel: Texel<T>) {
1381                write_for_texel_array(self.into, self.idx, self.from, self.range, texel)
1382            }
1383        }
1384
1385        struct WriteCell<'plane, 'data> {
1386            into: &'plane CellTarget<'data>,
1387            idx: &'plane [usize],
1388            from: &'plane TexelBuffer,
1389            range: Range<usize>,
1390        }
1391
1392        impl GenericTexelAction for WriteCell<'_, '_> {
1393            fn run<T>(self, texel: Texel<T>) {
1394                write_for_texel_cells(self.into, self.idx, self.from, self.range, texel)
1395            }
1396        }
1397
1398        struct WriteAtomic<'plane, 'data> {
1399            into: &'plane AtomicTarget<'data>,
1400            idx: &'plane [usize],
1401            from: &'plane TexelBuffer,
1402            range: Range<usize>,
1403        }
1404
1405        impl GenericTexelAction for WriteAtomic<'_, '_> {
1406            fn run<T>(self, texel: Texel<T>) {
1407                write_for_texel_atomics(self.into, self.idx, self.from, self.range, texel)
1408            }
1409        }
1410
1411        if ops.texel.should_defer_texel_write {
1412            debug_assert!(matches!(ops.color_out_plane, PlaneIdx::Sync(_)));
1413
1414            /* For deferred writing, we expect some functions to have already done the transfer for
1415             * us and left the source texel blank, uninitialized, or in an otherwise unreadable
1416             * state. We must skip them. The protocol here is that each chunk has two indices; the
1417             * index in the plane texture and the index up-to-which the texels are to be ignored in
1418             * the `out_texels`.
1419             */
1420            let chunks = self.out_slices.as_slice();
1421            let indexes = self.out_index_list.chunks(self.chunk_per_write);
1422            let range = (0..self.out_index_list.len()).step_by(self.chunk_per_write);
1423
1424            for (&chunk, (indexes, start)) in chunks.iter().zip(indexes.zip(range)) {
1425                let [_, unwritten] = chunk;
1426                debug_assert!(unwritten <= indexes.len());
1427
1428                if unwritten > indexes.len() {
1429                    continue;
1430                }
1431
1432                if unwritten == 0 {
1433                    continue;
1434                }
1435
1436                let offset = indexes.len() - unwritten;
1437                info.out_kind.action(WriteUnit {
1438                    into: &mut into.targets.sync[ops.color_out_plane.into_index()],
1439                    idx: &self.out_index_list,
1440                    from: &self.out_texels,
1441                    range: start + offset..start + indexes.len(),
1442                });
1443            }
1444        } else {
1445            match ops.color_out_plane {
1446                PlaneIdx::Sync(_) => {
1447                    info.out_kind.action(WriteUnit {
1448                        into: &mut into.targets.sync[ops.color_out_plane.into_index()],
1449                        idx: &self.out_index_list,
1450                        from: &self.out_texels,
1451                        range: 0..self.out_index_list.len(),
1452                    });
1453                }
1454                PlaneIdx::Cell(_) => {
1455                    info.out_kind.action(WriteCell {
1456                        into: &into.targets.cell[ops.color_out_plane.into_index()],
1457                        idx: &self.out_index_list,
1458                        from: &self.out_texels,
1459                        range: 0..self.out_index_list.len(),
1460                    });
1461                }
1462                PlaneIdx::Atomic(_) => {
1463                    info.out_kind.action(WriteAtomic {
1464                        into: &into.targets.atomic[ops.color_out_plane.into_index()],
1465                        idx: &self.out_index_list,
1466                        from: &self.out_texels,
1467                        range: 0..self.out_index_list.len(),
1468                    });
1469                }
1470            }
1471        }
1472    }
1473
1474    /// Generate the coordinates of all blocks, in row order.
1475    /// Returns the actual number if the chunk is too large and all coordinates were generated..
1476    fn blocks(mut x: Range<u32>, mut y: Range<u32>) -> impl FnMut(&mut [[u32; 2]]) -> usize {
1477        // Why not: x.zip(move |x| core::iter::repeat(x).zip(y.clone())).flatten();
1478        // Because its codegen is abysmal, apparently, leading to 10-15% slowdown for rgba.
1479        // I'm assuming it is because our `actual` computation which is a `size_hint` that isn't
1480        // available to llvm in the other case.
1481        #[inline(never)]
1482        move |buffer| {
1483            let maximum = buffer.len();
1484            if x.start == x.end {
1485                return 0;
1486            }
1487
1488            if y.end == 0 {
1489                return 0;
1490            }
1491
1492            let lines_left = x.end - x.start;
1493            let line_len = y.end;
1494
1495            let pix_left = u64::from(lines_left) * u64::from(line_len) - u64::from(y.start);
1496            let actual = pix_left.min(maximum as u64);
1497
1498            for p in buffer[..actual as usize].iter_mut() {
1499                let cx = x.start;
1500                let cy = y.start;
1501                *p = [cx, cy];
1502                y.start += 1;
1503
1504                if y.start >= y.end {
1505                    y.start = 0;
1506                    x.start += 1;
1507                }
1508            }
1509
1510            return actual as usize;
1511        }
1512    }
1513
1514    fn index_from_in_info(
1515        info: &ConvertInfo,
1516        texel: &[[u32; 2]],
1517        idx: &mut [usize],
1518        chunks: ChunkSpec,
1519    ) {
1520        Self::index_from_layer(&info.layout.in_layout, texel, idx, chunks)
1521    }
1522
1523    fn index_from_out_info(
1524        info: &ConvertInfo,
1525        texel: &[[u32; 2]],
1526        idx: &mut [usize],
1527        chunks: ChunkSpec,
1528    ) {
1529        Self::index_from_layer(&info.layout.out_layout, texel, idx, chunks)
1530    }
1531
1532    fn index_from_layer(
1533        info: &PlaneBytes,
1534        texel: &[[u32; 2]],
1535        idx: &mut [usize],
1536        chunks: ChunkSpec,
1537    ) {
1538        // FIXME(perf): review performance. Could probably be vectorized by hand.
1539        info.fill_texel_indices_impl(idx, texel, chunks)
1540    }
1541}
1542
1543impl<'re, 'data> PlaneIo<'re, 'data> {
1544    pub fn borrow(&mut self) -> PlaneIo<'_, 'data> {
1545        PlaneIo {
1546            sources: Sources {
1547                sync: self.sources.sync,
1548                cell: self.sources.cell,
1549                atomic: self.sources.atomic,
1550            },
1551            targets: Targets {
1552                sync: &mut *self.targets.sync,
1553                cell: self.targets.cell,
1554                atomic: self.targets.atomic,
1555            },
1556        }
1557    }
1558}
1559
1560trait ExpandYuvLike<const IN: usize, const OUT: usize> {
1561    fn expand<T: Copy>(_: [T; IN], fill: T) -> [[T; 4]; OUT];
1562}
1563
1564impl CommonPixel {
1565    /// Create pixels from our aggregated block information.
1566    ///
1567    /// For each pixel in each texel block, our task is to extract all channels (at most 4) and
1568    /// convert their bit representation to the `CommonPixel` representation, then put them into
1569    /// the expected channel give by the color channel's normal form.
1570    fn expand_from_info(
1571        // FIXME(perf): similar to join_from_info we could use shuffle sometimes..
1572        ops: &ConvertOps,
1573        in_texel: &TexelBuffer,
1574        pixel_buf: &mut TexelBuffer,
1575        _: PlaneIo,
1576    ) {
1577        let info = &ops.info;
1578
1579        // FIXME(perf): some bit/part combinations require no reordering of bits and could skip
1580        // large parts of this phase, or be done vectorized, effectively amounting to a memcpy when
1581        // the expanded value has the same representation as the texel.
1582        let TexelBits { bits, parts, block } = info.layout.in_layout.texel;
1583
1584        match block {
1585            Block::Pixel => Self::expand_bits(
1586                info,
1587                [FromBits::for_pixel(bits, parts)],
1588                in_texel,
1589                pixel_buf,
1590            ),
1591            Block::Pack1x2 => {
1592                let bits = FromBits::for_pixels::<2>(bits, parts);
1593                Self::expand_bits(info, bits, in_texel, pixel_buf)
1594            }
1595            Block::Pack1x4 => {
1596                let bits = FromBits::for_pixels::<4>(bits, parts);
1597                Self::expand_bits(info, bits, in_texel, pixel_buf)
1598            }
1599            Block::Pack1x8 => {
1600                let bits = FromBits::for_pixels::<8>(bits, parts);
1601                Self::expand_bits(info, bits, in_texel, pixel_buf)
1602            }
1603            Block::Sub1x2 | Block::Sub1x4 | Block::Sub2x2 | Block::Sub2x4 | Block::Sub4x4 => {
1604                // On these blocks, all pixels take the *same* channels.
1605                Self::expand_bits(
1606                    info,
1607                    [FromBits::for_pixel(bits, parts)],
1608                    in_texel,
1609                    pixel_buf,
1610                );
1611                Self::expand_sub_blocks(pixel_buf, info, info.common_blocks);
1612            }
1613            Block::Yuv422 => {
1614                debug_assert!(matches!(info.layout.in_layout.texel.block, Block::Sub1x2));
1615                debug_assert!(matches!(
1616                    info.layout.in_layout.texel.parts.num_components(),
1617                    3
1618                ));
1619                Self::expand_yuv422(info, in_texel, pixel_buf);
1620            }
1621            Block::Yuv411 => {
1622                debug_assert!(matches!(info.layout.in_layout.texel.block, Block::Sub1x4));
1623                debug_assert!(matches!(
1624                    info.layout.in_layout.texel.parts.num_components(),
1625                    3
1626                ));
1627                Self::expand_yuv411(info, in_texel, pixel_buf);
1628            }
1629            // FIXME(color): BC1-6
1630            other => {
1631                debug_assert!(false, "{:?}", other);
1632            }
1633        }
1634    }
1635
1636    fn expand_bits<const N: usize>(
1637        info: &ConvertInfo,
1638        bits: [[FromBits; 4]; N],
1639        in_texel: &TexelBuffer,
1640        pixel_buf: &mut TexelBuffer,
1641    ) {
1642        const M: usize = SampleBits::MAX_COMPONENTS;
1643        let (encoding, len) = info.layout.in_layout.texel.bits.bit_encoding();
1644
1645        if encoding[..len as usize] == [BitEncoding::UInt; M][..len as usize] {
1646            return Self::expand_ints::<N>(info, bits, in_texel, pixel_buf);
1647        } else if encoding[..len as usize] == [BitEncoding::Float; M][..len as usize] {
1648            return Self::expand_floats(info, bits[0], in_texel, pixel_buf);
1649        } else {
1650            // FIXME(color): error treatment..
1651            debug_assert!(false, "{:?}", &encoding[..len as usize]);
1652        }
1653    }
1654
1655    /// Expand into pixel normal form, an n×m array based on super blocks.
1656    fn expand_sub_blocks(pixel_buf: &mut TexelBuffer, info: &ConvertInfo, order: CommonPixelOrder) {
1657        debug_assert!(matches!(order, CommonPixelOrder::PixelsInRowOrder));
1658        let block = info.layout.in_layout.texel.block;
1659        let (bwidth, bheight) = (block.width(), block.height());
1660
1661        let pixels = pixel_buf.as_mut_texels(<[f32; 4]>::texel());
1662        let texlen = pixels.len() / (bwidth as usize) / (bheight as usize);
1663        let block = bwidth as usize * bheight as usize;
1664
1665        for i in (0..texlen).rev() {
1666            let source = pixels[i];
1667            for target in &mut pixels[block * i..][..block] {
1668                *target = source;
1669            }
1670        }
1671
1672        // FIXME(color): reorder within super blocks.
1673    }
1674
1675    /// Expand integer components into shader floats.
1676    ///
1677    /// Prepares a replacement value for channels that were not present in the texel. This is, for
1678    /// all colors, `[0, 0, 0, 1]`. FIXME(color): possibly incorrect for non-`???A` colors.
1679    fn expand_ints<const N: usize>(
1680        info: &ConvertInfo,
1681        bits: [[FromBits; 4]; N],
1682        in_texel: &TexelBuffer,
1683        pixel_buf: &mut TexelBuffer,
1684    ) {
1685        struct ExpandAction<'data, T, const N: usize> {
1686            expand: Texel<T>,
1687            expand_fn: fn([u32; 4], &[FromBits; 4]) -> T,
1688            bits: [[FromBits; 4]; N],
1689            in_texel: &'data TexelBuffer,
1690            pixel_buf: &'data mut TexelBuffer,
1691        }
1692
1693        impl<Expanded, const N: usize> GenericTexelAction<()> for ExpandAction<'_, Expanded, N> {
1694            fn run<T>(self, texel: Texel<T>) -> () {
1695                let texel_slice = self.in_texel.as_texels(texel);
1696                let pixel_slice = self.pixel_buf.as_mut_texels(self.expand.array::<N>());
1697
1698                // FIXME(color): block expansion to multiple pixels.
1699                // FIXME(color): adjust the FromBits for multiple planes.
1700                for (texbits, expand) in texel_slice.iter().zip(pixel_slice) {
1701                    let pixels = self.bits.map(|bits| {
1702                        (self.expand_fn)(bits.map(|b| b.extract_as_lsb(texel, texbits)), &bits)
1703                    });
1704
1705                    *expand = pixels;
1706                }
1707            }
1708        }
1709
1710        match info.common_pixel {
1711            // FIXME(color): rescaling of channels, and their bit interpretation.
1712            // Should we scale so that they occupy the full dynamic range, and scale floats from [0;
1713            // 1.0) or the respective HDR upper bound, i.e. likely 100.0 to represent 10_000 cd/m².
1714            CommonPixel::F32x4 => info.in_kind.action(ExpandAction {
1715                expand: <[f32; 4]>::texel(),
1716                expand_fn: |num, bits| {
1717                    [0, 1, 2, 3].map(|idx| {
1718                        let max_val = bits[idx].mask() as u64;
1719                        num[idx] as f32 / max_val as f32
1720                    })
1721                },
1722                bits,
1723                in_texel,
1724                pixel_buf,
1725            }),
1726        }
1727
1728        // Replacement channels if any channel of common color was selected with 0-bits.
1729        // We want to avoid, for example, the conversion of a zero to NaN for alpha channel.
1730        // FIXME(perf): could be skipped if know that it ends up unused
1731        // FIXME(perf): should this have an SIMD-op?
1732        let expanded = <[f32; 4]>::texel().array::<N>();
1733        for (pixel_idx, bits) in bits.into_iter().enumerate() {
1734            for (idx, component) in (0..4).zip(bits) {
1735                if component.len > 0 {
1736                    continue;
1737                }
1738
1739                let default = if idx == 3 { 1.0 } else { 0.0 };
1740                for pix in pixel_buf.as_mut_texels(expanded) {
1741                    pix[pixel_idx][idx] = default;
1742                }
1743            }
1744        }
1745    }
1746
1747    fn expand_floats(
1748        info: &ConvertInfo,
1749        bits: [FromBits; 4],
1750        in_texel: &TexelBuffer,
1751        pixel_buf: &mut TexelBuffer,
1752    ) {
1753        debug_assert!(
1754            matches!(info.common_pixel, CommonPixel::F32x4),
1755            "Improper common choices {:?}",
1756            info.common_pixel
1757        );
1758        let destination = pixel_buf.as_mut_texels(<[f32; 4]>::texel());
1759
1760        // FIXME(color): Assumes that we only read f32 channels..
1761        let pitch = info.in_kind.size() / 4;
1762
1763        for (&ch, ch_idx) in bits.iter().zip(0..4) {
1764            match ch.len {
1765                0 => continue,
1766                32 => {}
1767                // FIXME(color): half-floats?
1768                _ => continue,
1769            }
1770
1771            let position = ch.begin / 32;
1772            let texels = in_texel.as_texels(<f32>::texel());
1773            let pitched = texels[position..].chunks(pitch);
1774
1775            for (pix, texel) in destination.iter_mut().zip(pitched) {
1776                pix[ch_idx] = texel[0];
1777            }
1778        }
1779    }
1780
1781    fn expand_yuv422(info: &ConvertInfo, in_texel: &TexelBuffer, pixel_buf: &mut TexelBuffer) {
1782        struct ExpandYuv422;
1783
1784        impl ExpandYuvLike<4, 2> for ExpandYuv422 {
1785            fn expand<T: Copy>(yuyv: [T; 4], fill: T) -> [[T; 4]; 2] {
1786                let [u, y1, v, y2] = yuyv;
1787
1788                [[y1, u, v, fill], [y2, u, v, fill]]
1789            }
1790        }
1791
1792        Self::expand_yuv_like::<ExpandYuv422, 4, 2>(
1793            info,
1794            in_texel,
1795            pixel_buf,
1796            <[u8; 4]>::texel(),
1797            <[u16; 4]>::texel(),
1798            <[f32; 4]>::texel(),
1799        )
1800    }
1801
1802    fn expand_yuy2(info: &ConvertInfo, in_texel: &TexelBuffer, pixel_buf: &mut TexelBuffer) {
1803        struct ExpandYuy2;
1804
1805        impl ExpandYuvLike<4, 2> for ExpandYuy2 {
1806            fn expand<T: Copy>(yuyv: [T; 4], fill: T) -> [[T; 4]; 2] {
1807                let [y1, u, y2, v] = yuyv;
1808
1809                [[y1, u, v, fill], [y2, u, v, fill]]
1810            }
1811        }
1812
1813        Self::expand_yuv_like::<ExpandYuy2, 4, 2>(
1814            info,
1815            in_texel,
1816            pixel_buf,
1817            <[u8; 4]>::texel(),
1818            <[u16; 4]>::texel(),
1819            <[f32; 4]>::texel(),
1820        )
1821    }
1822
1823    fn expand_yuv411(info: &ConvertInfo, in_texel: &TexelBuffer, pixel_buf: &mut TexelBuffer) {
1824        struct ExpandYuv411;
1825
1826        impl ExpandYuvLike<6, 4> for ExpandYuv411 {
1827            fn expand<T: Copy>(yuyv: [T; 6], fill: T) -> [[T; 4]; 4] {
1828                let [u, y1, y2, v, y3, y4] = yuyv;
1829
1830                [
1831                    [y1, u, v, fill],
1832                    [y2, u, v, fill],
1833                    [y3, u, v, fill],
1834                    [y4, u, v, fill],
1835                ]
1836            }
1837        }
1838
1839        Self::expand_yuv_like::<ExpandYuv411, 6, 4>(
1840            info,
1841            in_texel,
1842            pixel_buf,
1843            <[u8; 6]>::texel(),
1844            <[u16; 6]>::texel(),
1845            <[f32; 6]>::texel(),
1846        )
1847    }
1848
1849    fn expand_yuv_like<F, const N: usize, const M: usize>(
1850        info: &ConvertInfo,
1851        in_texel: &TexelBuffer,
1852        pixel_buf: &mut TexelBuffer,
1853        tex_u8: Texel<[u8; N]>,
1854        tex_u16: Texel<[u16; N]>,
1855        tex_f32: Texel<[f32; N]>,
1856    ) where
1857        F: ExpandYuvLike<N, M>,
1858    {
1859        // FIXME(perf): it makes sense to loop-remove this match into `ops` construction?
1860        // In particular, instruction cache if each case is treated separately should be decent..
1861        match info.layout.in_layout.texel.bits {
1862            SampleBits::UInt8x4 => {
1863                let texels = in_texel.as_texels(tex_u8).iter();
1864                match info.common_pixel {
1865                    CommonPixel::F32x4 => {
1866                        let pixels = pixel_buf
1867                            .as_mut_texels(<[f32; 4]>::texel())
1868                            .chunks_exact_mut(M);
1869                        debug_assert!(pixels.len() == texels.len());
1870
1871                        for (texel, pixel_chunk) in texels.zip(pixels) {
1872                            let pixels: &mut [_; M] = pixel_chunk.try_into().unwrap();
1873                            let expand = F::expand(*texel, u8::MAX);
1874                            let remap = |v: u8| (v as f32) / 255.0f32;
1875                            *pixels = expand.map(|v| v.map(remap));
1876                        }
1877                    }
1878                }
1879            }
1880            SampleBits::UInt16x4 => {
1881                let texels = in_texel.as_texels(tex_u16).iter();
1882                match info.common_pixel {
1883                    CommonPixel::F32x4 => {
1884                        let pixels = pixel_buf
1885                            .as_mut_texels(<[f32; 4]>::texel())
1886                            .chunks_exact_mut(M);
1887                        debug_assert!(pixels.len() == texels.len());
1888
1889                        for (texel, pixel_chunk) in texels.zip(pixels) {
1890                            let pixels: &mut [_; M] = pixel_chunk.try_into().unwrap();
1891                            let expand = F::expand(*texel, u16::MAX);
1892                            let remap = |v: u16| (v as f32) / 65535.0f32;
1893                            *pixels = expand.map(|v| v.map(remap));
1894                        }
1895                    }
1896                }
1897            }
1898            SampleBits::Float32x4 => {
1899                let texels = in_texel.as_texels(tex_f32).iter();
1900                match info.common_pixel {
1901                    CommonPixel::F32x4 => {
1902                        let pixels = pixel_buf
1903                            .as_mut_texels(<[f32; 4]>::texel())
1904                            .chunks_exact_mut(2);
1905                        debug_assert!(pixels.len() == texels.len());
1906
1907                        for (texel, pixel_chunk) in texels.zip(pixels) {
1908                            let pixels: &mut [_; M] = pixel_chunk.try_into().unwrap();
1909                            *pixels = F::expand(*texel, 1.0);
1910                        }
1911                    }
1912                }
1913            }
1914            other => {
1915                debug_assert!(false, "Bad YUV spec {:?}", other);
1916            }
1917        }
1918    }
1919
1920    fn join_from_info(
1921        ops: &ConvertOps,
1922        pixel_buf: &TexelBuffer,
1923        out_texels: &mut TexelBuffer,
1924        // FIXME(perf): see `join_bits` which could use it but requires chunk information.
1925        _: PlaneIo,
1926    ) {
1927        let info = &ops.info;
1928
1929        // FIXME(perf): some bit/part combinations require no reordering of bits and could skip
1930        // large parts of this phase, or be done vectorized, effectively amounting to a memcpy when
1931        // the expanded value had the same representation as the texel.
1932        let TexelBits { bits, parts, block } = info.layout.out_layout.texel;
1933
1934        match block {
1935            Block::Pixel => {
1936                let bits = FromBits::for_pixel(bits, parts);
1937                // TODO: pre-select SIMD version from `info.ops`?
1938                if let SampleBits::UInt8x4 = info.layout.out_layout.texel.bits {
1939                    return Self::join_uint8x4(ops, bits, pixel_buf, out_texels);
1940                } else if let SampleBits::UInt16x4 = info.layout.out_layout.texel.bits {
1941                    return Self::join_uint16x4(ops, bits, pixel_buf, out_texels);
1942                } else if let SampleBits::UInt8x3 = info.layout.out_layout.texel.bits {
1943                    return Self::join_uint8x3(ops, bits, pixel_buf, out_texels);
1944                } else if let SampleBits::UInt16x3 = info.layout.out_layout.texel.bits {
1945                    return Self::join_uint16x3(ops, bits, pixel_buf, out_texels);
1946                } else {
1947                    Self::join_bits(info, ops, [bits], pixel_buf, out_texels)
1948                }
1949            }
1950            Block::Pack1x2 => {
1951                let bits = FromBits::for_pixels::<2>(bits, parts);
1952                Self::join_bits(info, ops, bits, pixel_buf, out_texels)
1953            }
1954            Block::Pack1x4 => {
1955                let bits = FromBits::for_pixels::<4>(bits, parts);
1956                Self::join_bits(info, ops, bits, pixel_buf, out_texels)
1957            }
1958            Block::Pack1x8 => {
1959                let bits = FromBits::for_pixels::<8>(bits, parts);
1960                Self::join_bits(info, ops, bits, pixel_buf, out_texels)
1961            }
1962            Block::Sub1x2 | Block::Sub1x4 | Block::Sub2x2 | Block::Sub2x4 | Block::Sub4x4 => {
1963                // On these blocks, all pixels take the *same* channels.
1964                Self::join_bits(
1965                    info,
1966                    ops,
1967                    [FromBits::for_pixel(bits, parts)],
1968                    pixel_buf,
1969                    out_texels,
1970                );
1971                Self::join_sub_blocks(out_texels, info, info.common_blocks);
1972            }
1973            Block::Yuv422 => {
1974                // Debug assert: common_pixel
1975                debug_assert!(matches!(info.layout.out_layout.texel.block, Block::Sub1x2));
1976                debug_assert!(matches!(
1977                    info.layout.out_layout.texel.parts.num_components(),
1978                    3
1979                ));
1980                Self::join_yuv422(info, pixel_buf, out_texels)
1981            }
1982            other => {
1983                debug_assert!(false, "{:?}", other);
1984            }
1985        }
1986    }
1987
1988    // FIXME(perf): for single-plane, in particular integer cases, we could write directly into the
1989    // target buffer by chunks if this is available.
1990    fn join_bits<const N: usize>(
1991        info: &ConvertInfo,
1992        _: &ConvertOps,
1993        bits: [[FromBits; 4]; N],
1994        pixel_buf: &TexelBuffer,
1995        out_texels: &mut TexelBuffer,
1996    ) {
1997        const M: usize = SampleBits::MAX_COMPONENTS;
1998        let (encoding, len) = info.layout.out_layout.texel.bits.bit_encoding();
1999
2000        if encoding[..len as usize] == [BitEncoding::UInt; M][..len as usize] {
2001            return Self::join_ints(info, bits, pixel_buf, out_texels);
2002        } else if encoding[..len as usize] == [BitEncoding::Float; M][..len as usize] {
2003            return Self::join_floats(info, bits[0], pixel_buf, out_texels);
2004        } else {
2005            // FIXME(color): error treatment..
2006            debug_assert!(false, "{:?}", &encoding[..len as usize]);
2007        }
2008    }
2009
2010    // FIXME(color): int component bias
2011    fn join_ints<const N: usize>(
2012        info: &ConvertInfo,
2013        bits: [[FromBits; 4]; N],
2014        pixel_buf: &TexelBuffer,
2015        out_texels: &mut TexelBuffer,
2016    ) {
2017        struct JoinAction<'data, T, F: FnMut(&T, &FromBits, u8) -> u32, const N: usize> {
2018            join: Texel<T>,
2019            join_fn: F,
2020            bits: [[FromBits; 4]; N],
2021            out_texels: &'data mut TexelBuffer,
2022            pixel_buf: &'data TexelBuffer,
2023        }
2024
2025        impl<Expanded, F, const N: usize> GenericTexelAction<()> for JoinAction<'_, Expanded, F, N>
2026        where
2027            F: FnMut(&Expanded, &FromBits, u8) -> u32,
2028        {
2029            fn run<T>(mut self, texel: Texel<T>) -> () {
2030                let texel_slice = self.out_texels.as_mut_texels(texel);
2031                let pixel_slice = self.pixel_buf.as_texels(self.join.array::<N>());
2032
2033                debug_assert_eq!(texel_slice.len(), pixel_slice.len());
2034
2035                for ch in [0u8, 1, 2, 3] {
2036                    for (texbits, pixels) in texel_slice.iter_mut().zip(pixel_slice) {
2037                        for (pixel_bits, joined) in self.bits.iter().zip(pixels) {
2038                            let bits = pixel_bits[ch as usize];
2039                            // FIXME(color): adjust the FromBits for multiple planes.
2040                            let value = (self.join_fn)(joined, &bits, ch);
2041                            bits.insert_as_lsb(texel, texbits, value);
2042                        }
2043                    }
2044                }
2045            }
2046        }
2047
2048        match info.common_pixel {
2049            // FIXME(color): rescaling of channels, and their bit interpretation.
2050            // Should we scale so that they occupy the full dynamic range, and scale floats from [0;
2051            // 1.0) or the respective HDR upper bound, i.e. likely 100.0 to represent 10_000 cd/m².
2052            CommonPixel::F32x4 => info.out_kind.action(JoinAction {
2053                join: <[f32; 4]>::texel(),
2054                // FIXME: do the transform u32::from_ne_bytes(x.as_ne_bytes()) when appropriate.
2055                join_fn: |num, bits, idx| {
2056                    let max_val = bits.mask();
2057                    // Equivalent to `x.round() as u32` for positive-normal f32
2058                    let round = |x| (x + 0.5) as u32;
2059                    let raw = round(num[(idx & 0x3) as usize] * max_val as f32);
2060                    raw.min(max_val)
2061                },
2062                bits,
2063                out_texels,
2064                pixel_buf,
2065            }),
2066        }
2067    }
2068
2069    /// Expand into pixel normal form, an n×m array based on super blocks.
2070    fn join_sub_blocks(pixel_buf: &mut TexelBuffer, info: &ConvertInfo, order: CommonPixelOrder) {
2071        debug_assert!(matches!(order, CommonPixelOrder::PixelsInRowOrder));
2072        let block = info.layout.out_layout.texel.block;
2073        let (bwidth, bheight) = (block.width(), block.height());
2074
2075        let pixels = pixel_buf.as_mut_texels(<[f32; 4]>::texel());
2076        let texlen = pixels.len() / (bwidth as usize) / (bheight as usize);
2077        let block = bwidth as usize * bheight as usize;
2078
2079        // FIXME(color): reorder within super blocks.
2080
2081        for i in (0..texlen).rev() {
2082            let mut sum = [0.0, 0.0, 0.0, 0.0];
2083            // This is really not an optimal way to approximate the mean.
2084            for &[a, b, c, d] in &pixels[block * i..][..block] {
2085                sum[0] += a;
2086                sum[1] += b;
2087                sum[2] += c;
2088                sum[3] += d;
2089            }
2090            pixels[i] = sum.map(|i| i / block as f32);
2091        }
2092    }
2093
2094    /// Specialized join when channels are a uniform reordering of color channels, as u8.
2095    fn join_uint8x4(
2096        ops: &ConvertOps,
2097        bits: [FromBits; 4],
2098        pixel_buf: &TexelBuffer,
2099        out_texels: &mut TexelBuffer,
2100    ) {
2101        let src = pixel_buf.as_texels(f32::texel());
2102        let dst = out_texels.as_mut_texels(u8::texel());
2103
2104        // Do one quick SIMD cast to u8. Much faster than the general round and clamp.
2105        // Note: fma is for some reason a call to a libc function…
2106        for (tex, &pix) in dst.iter_mut().zip(src) {
2107            *tex = ((pix * (u8::MAX as f32)) + 0.5) as u8;
2108        }
2109
2110        // prepare re-ordering step. Note how we select 0x80 as invalid, which works perfectly with
2111        // an SSE shuffle instruction which encodes this as a negative offset. Trust llvm to do the
2112        // transform.
2113        let mut shuffle = [0x80u8; 4];
2114        for (idx, bits) in (0u8..4).zip(&bits) {
2115            if bits.len > 0 {
2116                shuffle[(bits.begin / 8) as usize] = idx;
2117            }
2118        }
2119
2120        (ops.shuffle.shuffle_u8x4)(out_texels.as_mut_texels(<[u8; 4]>::texel()), shuffle);
2121    }
2122
2123    fn join_uint16x4(
2124        ops: &ConvertOps,
2125        bits: [FromBits; 4],
2126        pixel_buf: &TexelBuffer,
2127        out_texels: &mut TexelBuffer,
2128    ) {
2129        let src = pixel_buf.as_texels(f32::texel());
2130        let dst = out_texels.as_mut_texels(u16::texel());
2131
2132        // Do one quick SIMD cast to u8. Faster than the general round and clamp.
2133        // Note: fma is for some reason a call to a libc function…
2134        for (tex, &pix) in dst.iter_mut().zip(src) {
2135            *tex = ((pix * (u16::MAX as f32)) + 0.5) as u16;
2136        }
2137
2138        // prepare re-ordering step. Note how we select 0x80 as invalid, which works perfectly with
2139        // an SSE shuffle instruction which encodes this as a negative offset. Trust llvm to do the
2140        // transform.
2141        let mut shuffle = [0x80u8; 4];
2142        for (idx, bits) in (0u8..4).zip(&bits) {
2143            if bits.len > 0 {
2144                shuffle[(bits.begin / 16) as usize] = idx;
2145            }
2146        }
2147
2148        (ops.shuffle.shuffle_u16x4)(out_texels.as_mut_texels(<[u16; 4]>::texel()), shuffle);
2149    }
2150
2151    fn join_uint8x3(
2152        _: &ConvertOps,
2153        bits: [FromBits; 4],
2154        pixel_buf: &TexelBuffer,
2155        out_texels: &mut TexelBuffer,
2156    ) {
2157        let src = pixel_buf.as_texels(<[f32; 4]>::texel());
2158        let dst = out_texels.as_mut_texels(<[u8; 3]>::texel());
2159
2160        // prepare re-ordering step. Note how we select 0x80 as invalid, which works perfectly with
2161        // an SSE shuffle instruction which encodes this as a negative offset. Trust llvm to do the
2162        // transform.
2163        let mut shuffle = [0x80u8; 3];
2164        for (idx, bits) in (0u8..4).zip(&bits) {
2165            if bits.len > 0 {
2166                shuffle[(bits.begin / 8) as usize] = idx;
2167            }
2168        }
2169
2170        for (tex, pix) in dst.iter_mut().zip(src) {
2171            *tex = shuffle.map(|i| {
2172                let val = *pix.get(i as usize).unwrap_or(&0.0);
2173                (val * u8::MAX as f32 + 0.5) as u8
2174            });
2175        }
2176    }
2177
2178    fn join_uint16x3(
2179        _: &ConvertOps,
2180        bits: [FromBits; 4],
2181        pixel_buf: &TexelBuffer,
2182        out_texels: &mut TexelBuffer,
2183    ) {
2184        let src = pixel_buf.as_texels(<[f32; 4]>::texel());
2185        let dst = out_texels.as_mut_texels(<[u16; 3]>::texel());
2186
2187        // prepare re-ordering step. Note how we select 0x80 as invalid, which works perfectly with
2188        // an SSE shuffle instruction which encodes this as a negative offset. Trust llvm to do the
2189        // transform.
2190        let mut shuffle = [0x80u8; 3];
2191        for (idx, bits) in (0u8..4).zip(&bits) {
2192            if bits.len > 0 {
2193                shuffle[(bits.begin / 16) as usize] = idx;
2194            }
2195        }
2196
2197        for (tex, pix) in dst.iter_mut().zip(src) {
2198            *tex = shuffle.map(|i| {
2199                let val = *pix.get(i as usize).unwrap_or(&0.0);
2200                (val * u16::MAX as f32 + 0.5) as u16
2201            });
2202        }
2203    }
2204
2205    fn join_floats(
2206        info: &ConvertInfo,
2207        bits: [FromBits; 4],
2208        pixel_buf: &TexelBuffer,
2209        out_texels: &mut TexelBuffer,
2210    ) {
2211        debug_assert!(
2212            matches!(info.common_pixel, CommonPixel::F32x4),
2213            "Improper common choices {:?}",
2214            info.common_pixel
2215        );
2216        let source = pixel_buf.as_texels(<[f32; 4]>::texel());
2217        // Assume that we only write floating channels..
2218        let pitch = info.out_kind.size() / 4;
2219
2220        for (&ch, ch_idx) in bits.iter().zip(0..4) {
2221            match ch.len {
2222                0 => continue,
2223                32 => {}
2224                // FIXME(color): half-floats?
2225                _ => continue,
2226            }
2227
2228            let position = ch.begin / 32;
2229            let texels = out_texels.as_mut_texels(<f32>::texel());
2230            let pitched = texels[position..].chunks_mut(pitch);
2231
2232            for (pix, texel) in source.iter().zip(pitched) {
2233                texel[0] = pix[ch_idx];
2234            }
2235        }
2236    }
2237
2238    fn join_yuv422(_: &ConvertInfo, _: &TexelBuffer, _: &mut TexelBuffer) {
2239        // FIXME(color): actually implement this..
2240        debug_assert!(false);
2241    }
2242
2243    fn action<R>(self, action: impl GenericTexelAction<R>) -> R {
2244        match self {
2245            CommonPixel::F32x4 => action.run(<[f32; 4]>::texel()),
2246        }
2247    }
2248}
2249
2250impl CommonColor {
2251    fn cie_xyz_from_info(info: &ConvertInfo, pixel: &TexelBuffer, xyz: &mut TexelBuffer) {
2252        // If we do color conversion, we always choose [f32; 4] representation.
2253        // Or, at least we should. Otherwise, do nothing..
2254        if !matches!(info.common_pixel, CommonPixel::F32x4) {
2255            // FIXME(color): report this error somehow?
2256            return;
2257        }
2258
2259        let texel = <[f32; 4]>::texel();
2260        let pixel = pixel.as_texels(texel);
2261        let xyz = xyz.as_mut_texels(texel);
2262        assert_eq!(
2263            pixel.len(),
2264            xyz.len(),
2265            "Setup create invalid conversion buffer"
2266        );
2267
2268        info.layout.in_color.to_xyz_slice(pixel, xyz)
2269    }
2270
2271    fn cie_xyz_into_info(info: &ConvertInfo, xyz: &TexelBuffer, pixel: &mut TexelBuffer) {
2272        // If we do color conversion, we always choose [f32; 4] representation.
2273        // Or, at least we should. Otherwise, do nothing..
2274        if !matches!(info.common_pixel, CommonPixel::F32x4) {
2275            // FIXME(color): report this error somehow?
2276            return;
2277        }
2278
2279        let texel = <[f32; 4]>::texel();
2280        let xyz = xyz.as_texels(texel);
2281        let pixel = pixel.as_mut_texels(texel);
2282        assert_eq!(
2283            pixel.len(),
2284            xyz.len(),
2285            "Setup create invalid conversion buffer"
2286        );
2287
2288        info.layout.out_color.from_xyz_slice(xyz, pixel)
2289    }
2290}
2291
2292impl ColorLayout {
2293    fn from_frames(
2294        canvas_in: &CanvasLayout,
2295        canvas_out: &CanvasLayout,
2296    ) -> Result<Self, ConversionError> {
2297        let mut in_color = canvas_in.color.clone();
2298        let mut out_color = canvas_out.color.clone();
2299
2300        if in_color.is_none() && out_color.is_none() {
2301            // We do allow this as a pure component swizzle but assuming a linear relationship for
2302            // any scalar conversion that happens between them (i.e. rescaling and float-int).
2303            in_color = Some(Color::Scalars {
2304                transfer: crate::color::Transfer::Linear,
2305            });
2306
2307            out_color = Some(Color::Scalars {
2308                transfer: crate::color::Transfer::Linear,
2309            });
2310        }
2311
2312        Ok(ColorLayout {
2313            in_layout: canvas_in
2314                .as_plane()
2315                .ok_or(ConversionError::UnsupportedInputLayout)?,
2316            out_layout: canvas_out
2317                .as_plane()
2318                .ok_or(ConversionError::UnsupportedInputLayout)?,
2319            in_color: in_color.ok_or(ConversionError::UnsupportedInputColor)?,
2320            out_color: out_color.ok_or(ConversionError::UnsupportedOutputColor)?,
2321        })
2322    }
2323}
2324
2325impl TexelKind {
2326    pub(crate) fn action<R>(self, action: impl GenericTexelAction<R>) -> R {
2327        match self {
2328            TexelKind::U8 => action.run(u8::texel()),
2329            TexelKind::U8x2 => action.run(<[u8; 2]>::texel()),
2330            TexelKind::U8x3 => action.run(<[u8; 3]>::texel()),
2331            TexelKind::U8x4 => action.run(<[u8; 4]>::texel()),
2332            TexelKind::U8x6 => action.run(<[u8; 6]>::texel()),
2333            TexelKind::U16 => action.run(<[u16; 1]>::texel()),
2334            TexelKind::U16x2 => action.run(<[u16; 2]>::texel()),
2335            TexelKind::U16x3 => action.run(<[u16; 3]>::texel()),
2336            TexelKind::U16x4 => action.run(<[u16; 4]>::texel()),
2337            TexelKind::U16x6 => action.run(<[u16; 6]>::texel()),
2338            TexelKind::F32 => action.run(<[f32; 1]>::texel()),
2339            TexelKind::F32x2 => action.run(<[f32; 2]>::texel()),
2340            TexelKind::F32x3 => action.run(<[f32; 3]>::texel()),
2341            TexelKind::F32x4 => action.run(<[f32; 4]>::texel()),
2342            TexelKind::F32x6 => action.run(<[f32; 6]>::texel()),
2343        }
2344    }
2345
2346    fn size(self) -> usize {
2347        struct ToSize;
2348
2349        impl GenericTexelAction<usize> for ToSize {
2350            fn run<T>(self, texel: image_texel::Texel<T>) -> usize {
2351                image_texel::layout::TexelLayout::from(texel).size()
2352            }
2353        }
2354
2355        TexelKind::from(self).action(ToSize)
2356    }
2357}
2358
2359impl PlaneIdx {
2360    fn into_index(self) -> usize {
2361        match self {
2362            PlaneIdx::Sync(i) => i.into(),
2363            PlaneIdx::Cell(i) => i.into(),
2364            PlaneIdx::Atomic(i) => i.into(),
2365        }
2366    }
2367}
2368
2369impl From<TexelBits> for TexelKind {
2370    fn from(texel: TexelBits) -> Self {
2371        Self::from(texel.bits)
2372    }
2373}
2374
2375impl From<SampleBits> for TexelKind {
2376    fn from(bits: SampleBits) -> Self {
2377        use SampleBits::*;
2378        // We only need to match size and align here.
2379        match bits {
2380            Int8 | UInt8 | UInt1x8 | UInt2x4 | UInt332 | UInt233 | UInt4x2 => TexelKind::U8,
2381            Int16 | UInt16 | UInt4x4 | UInt_444 | UInt444_ | UInt565 => TexelKind::U16,
2382            Int8x2 | UInt8x2 => TexelKind::U8x2,
2383            Int8x3 | UInt8x3 | UInt4x6 => TexelKind::U8x3,
2384            Int8x4 | UInt8x4 => TexelKind::U8x4,
2385            UInt8x6 => TexelKind::U8x6,
2386            Int16x2 | UInt16x2 => TexelKind::U16x2,
2387            Int16x3 | UInt16x3 => TexelKind::U16x3,
2388            Int16x4 | UInt16x4 => TexelKind::U16x4,
2389            UInt16x6 => TexelKind::U16x6,
2390            UInt1010102 | UInt2101010 | UInt101010_ | UInt_101010 => TexelKind::U16x2,
2391            Float16x4 => TexelKind::U16x4,
2392            Float32 => TexelKind::F32,
2393            Float32x2 => TexelKind::F32x2,
2394            Float32x3 => TexelKind::F32x3,
2395            Float32x4 => TexelKind::F32x4,
2396            Float32x6 => TexelKind::F32x6,
2397        }
2398    }
2399}
2400
2401impl fmt::Display for ConversionError {
2402    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2403        match self {
2404            ConversionError::InputLayoutDoesNotMatchPlan => {
2405                write!(
2406                    f,
2407                    "the planned input layout does not match the actual input"
2408                )
2409            }
2410            ConversionError::OutputLayoutDoesNotMatchPlan => {
2411                write!(
2412                    f,
2413                    "the planned output layout does not match the actual output"
2414                )
2415            }
2416            ConversionError::InputColorDoesNotMatchPlanes => {
2417                write!(f, "the planned input color does not match the actual color")
2418            }
2419            ConversionError::OutputColorDoesNotMatchPlanes => {
2420                write!(
2421                    f,
2422                    "the planned output color does not match the actual color"
2423                )
2424            }
2425            ConversionError::UnsupportedInputLayout => {
2426                write!(f, "conversion from the input layout is not supported")
2427            }
2428            ConversionError::UnsupportedInputColor => {
2429                write!(f, "conversion from the input color is not supported")
2430            }
2431            ConversionError::UnsupportedOutputLayout => {
2432                write!(f, "conversion into the output layout is not supported")
2433            }
2434            ConversionError::UnsupportedOutputColor => {
2435                write!(f, "conversion into the output color is not supported")
2436            }
2437        }
2438    }
2439}
2440
2441impl core::error::Error for ConversionError {}
2442
2443#[test]
2444fn from_bits() {
2445    let bits = FromBits::for_pixel(SampleBits::UInt332, SampleParts::Rgb);
2446    let (texel, value) = (u8::texel(), &0b01010110);
2447    assert_eq!(bits[0].extract_as_lsb(texel, value), 0b010);
2448    assert_eq!(bits[1].extract_as_lsb(texel, value), 0b101);
2449    assert_eq!(bits[2].extract_as_lsb(texel, value), 0b10);
2450    assert_eq!(bits[3].extract_as_lsb(texel, value), 0b0);
2451}
2452
2453#[test]
2454fn to_bits() {
2455    let bits = FromBits::for_pixel(SampleBits::UInt332, SampleParts::Rgb);
2456    let (texel, ref mut value) = (u8::texel(), 0);
2457    bits[0].insert_as_lsb(texel, value, 0b010);
2458    bits[1].insert_as_lsb(texel, value, 0b101);
2459    bits[2].insert_as_lsb(texel, value, 0b10);
2460    bits[3].insert_as_lsb(texel, value, 0b0);
2461    assert_eq!(*value, 0b01010110);
2462}