From 33b09c50ea5f28ced5413e019b96bebe9566210b Mon Sep 17 00:00:00 2001 From: Luca Versari Date: Thu, 19 Sep 2024 03:07:44 +0200 Subject: [PATCH] Draft interface for a render pipeline. --- jxl/src/image.rs | 2 +- jxl/src/lib.rs | 1 + jxl/src/render/mod.rs | 140 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 jxl/src/render/mod.rs diff --git a/jxl/src/image.rs b/jxl/src/image.rs index 0803be3..5f3a10a 100644 --- a/jxl/src/image.rs +++ b/jxl/src/image.rs @@ -4,7 +4,7 @@ mod private { pub trait Sealed {} } -pub trait ImageDataType: private::Sealed + Copy + Default { +pub trait ImageDataType: private::Sealed + Copy + Default + 'static { fn data_type_id() -> usize; } diff --git a/jxl/src/lib.rs b/jxl/src/lib.rs index bc36143..97f0a76 100644 --- a/jxl/src/lib.rs +++ b/jxl/src/lib.rs @@ -10,4 +10,5 @@ pub mod error; pub mod headers; pub mod icc; pub mod image; +pub mod render; mod util; diff --git a/jxl/src/render/mod.rs b/jxl/src/render/mod.rs new file mode 100644 index 0000000..df6b64b --- /dev/null +++ b/jxl/src/render/mod.rs @@ -0,0 +1,140 @@ +use crate::{ + error::Result, + image::{ImageDataType, ImageRectMut}, +}; + +/// These are the only stages that are assumed to have observable effects, i.e. calls to process_row +/// for other stages may be omitted if it can be shown they can't affect any Input stage process_row +/// call that happens inside image boundaries. +pub trait RenderPipelineInputStage { + fn uses_channel(&self, c: usize) -> bool; + + /// `row` has as many entries as the number of used channels for the stage, i.e. the channels + /// for which `uses_channel` returns true. + fn process_row_chunk(&mut self, position: (usize, usize), xsize: usize, row: &[&[InputT]]); +} + +/// Modifies channels in-place. +pub trait RenderPipelineInPlaceStage { + fn uses_channel(&self, c: usize) -> bool; + /// `row` has as many entries as the number of used channels for the stage, i.e. the channels + /// for which `uses_channel` returns true. + fn process_row_chunk(&mut self, position: (usize, usize), xsize: usize, row: &mut [&mut [T]]); +} + +/// Modifies data and writes it to a new buffer, of possibly different type. +pub trait RenderPipelineInOutStage { + /// Amount of padding required on the input side. + const BORDER: (usize, usize); + /// log2 of the number of rows/columns produced for each row/column of input. + const SHIFT: (usize, usize); + + fn uses_channel(&self, c: usize) -> bool; + + /// For each channel: + /// - the input slice contains 1 + Self::BORDER.1 * 2 slices, each of length + /// xsize + Self::BORDER.0 * 2, i.e. covering one input row and up to BORDER pixels of + /// padding on either side. + /// - the output slice contains 1 << SHIFT.1 slices, each of length xsize << SHIFT.0, the + /// corresponding output pixels. + /// + /// `input` and `output` have as many entries as the number of used channels for the stage, + /// i.e. the channels for which `uses_channel` returns true. + fn process_row_chunk( + &mut self, + position: (usize, usize), + xsize: usize, + input: &[&[&[InputT]]], + output: &mut [&mut [&mut [OutputT]]], + ); +} + +/// Does not directly modify the current image pixels, but extends the current image with +/// additional data. +/// ` uses_channel` is assumed to be always true, i.e. such a stage must extend all channels at +/// once. +pub trait RenderPipelineExtendStage { + fn new_size(&self) -> (usize, usize); + fn original_data_origin(&self) -> (usize, usize); + /// The given buffer must always be entirely outside of the original image. + fn fill_padding_row_chunk( + &mut self, + new_position: (usize, usize), + xsize: usize, + row: &mut [&mut [T]], + ); +} + +pub trait RenderPipelineBuilder: Sized { + type RenderPipeline: RenderPipeline; + + fn new(num_channels: usize, size: (usize, usize), group_size: usize) -> Self; + + fn add_input_stage>( + self, + stage: Stage, + ) -> Result; + + fn add_inplace_stage>( + self, + stage: Stage, + ) -> Result; + + fn add_inout_stage< + InT: ImageDataType, + OutT: ImageDataType, + Stage: RenderPipelineInOutStage, + >( + self, + stage: Stage, + ) -> Result; + + fn add_extend_stage>( + self, + stage: Stage, + ) -> Result; + + fn build(self) -> Self::RenderPipeline; +} + +#[allow(dead_code)] +pub struct GroupFillInfo { + group_id: usize, + num_filled_passes: usize, + fill_fn: F, +} + +fn fake_fill_fn(_: &mut [ImageRectMut]) { + panic!("can only use fill_input_same_type if the inputs are of the same type"); +} + +pub trait RenderPipeline { + type Builder: RenderPipelineBuilder; + + /// Feeds input into the pipeline. Takes as input a vector specifying, for each group that will + /// be filled in, how many passes are filled and how to fill in each channel. + fn fill_input_same_type(&mut self, group_fill_info: Vec>) + where + F: FnOnce(&mut [ImageRectMut]) + Send, + { + self.fill_input_two_types( + group_fill_info + .into_iter() + .map(|x| GroupFillInfo { + fill_fn: (x.fill_fn, fake_fill_fn), + group_id: x.group_id, + num_filled_passes: x.num_filled_passes, + }) + .collect(), + ); + } + + /// Same as fill_input_same_type, but the inputs might have different types. + /// Which type is used for which channel is determined by the stages in the render pipeline. + fn fill_input_two_types( + &mut self, + group_fill_info: Vec>, + ) where + F1: FnOnce(&mut [ImageRectMut]) + Send, + F2: FnOnce(&mut [ImageRectMut]) + Send; +}