Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft interface for a render pipeline. #15

Merged
merged 1 commit into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion jxl/src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
1 change: 1 addition & 0 deletions jxl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ pub mod error;
pub mod headers;
pub mod icc;
pub mod image;
pub mod render;
mod util;
140 changes: 140 additions & 0 deletions jxl/src/render/mod.rs
Original file line number Diff line number Diff line change
@@ -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<InputT: ImageDataType> {
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<T: ImageDataType> {
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<InputT: ImageDataType, OutputT: ImageDataType> {
/// 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<T: ImageDataType> {
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<T: ImageDataType, Stage: RenderPipelineInputStage<T>>(
self,
stage: Stage,
) -> Result<Self>;

fn add_inplace_stage<T: ImageDataType, Stage: RenderPipelineInPlaceStage<T>>(
self,
stage: Stage,
) -> Result<Self>;

fn add_inout_stage<
InT: ImageDataType,
OutT: ImageDataType,
Stage: RenderPipelineInOutStage<InT, OutT>,
>(
self,
stage: Stage,
) -> Result<Self>;

fn add_extend_stage<T: ImageDataType, Stage: RenderPipelineExtendStage<T>>(
self,
stage: Stage,
) -> Result<Self>;

fn build(self) -> Self::RenderPipeline;
}

#[allow(dead_code)]
pub struct GroupFillInfo<F> {
group_id: usize,
num_filled_passes: usize,
fill_fn: F,
}

fn fake_fill_fn(_: &mut [ImageRectMut<u8>]) {
panic!("can only use fill_input_same_type if the inputs are of the same type");
}

pub trait RenderPipeline {
type Builder: RenderPipelineBuilder<RenderPipeline = Self>;

/// 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<T: ImageDataType, F>(&mut self, group_fill_info: Vec<GroupFillInfo<F>>)
where
F: FnOnce(&mut [ImageRectMut<T>]) + 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<T1: ImageDataType, T2: ImageDataType, F1, F2>(
&mut self,
group_fill_info: Vec<GroupFillInfo<(F1, F2)>>,
) where
F1: FnOnce(&mut [ImageRectMut<T1>]) + Send,
F2: FnOnce(&mut [ImageRectMut<T2>]) + Send;
}