// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file.
use std::{
cmp::min,
collections::{BTreeMap, BTreeSet},
fmt::Debug,
ops::Range,
sync::atomic::{AtomicUsize, Ordering},
};
mod borrowed_buffers; pub(crate) mod decode; mod predict; mod transforms; mod tree;
use borrowed_buffers::with_buffers; pubuse decode::ModularStreamId; use decode::decode_modular_subbitstream; pubuse predict::Predictor; use transforms::{TransformStepChunk, make_grids}; pubuse tree::Tree;
// Two rows on top, two pixels to the left, two pixels to the right. const IMAGE_PADDING: (usize, usize) = (4, 2); const IMAGE_OFFSET: (usize, usize) = (2, 2);
#[derive(Clone, PartialEq, Eq, Copy)] struct ChannelInfo { // The index of the output channel in the render pipeline.
output_channel_idx: Option<usize>, // width, height
size: (usize, usize),
shift: Option<(usize, usize)>, // None for meta-channels
bit_depth: BitDepth,
}
fn is_shift_in_range(&self, min: usize, max: usize) -> bool { // This might be called with max < min, in which case we just return false. // This matches libjxl behaviour. self.shift.is_some_and(|(a, b)| { let shift = a.min(b);
min <= shift && shift <= max
})
}
// All the information on a specific buffer needed by Modular decoding. #[derive(Debug)] pub(crate) struct ModularChannel { // Actual pixel buffer. pub data: Image<i32>, // Holds additional information such as the weighted predictor's error channel's last row for // the transform chunk that produced this buffer.
auxiliary_data: Option<Image<i32>>, // Shift of the channel (None if this is a meta-channel).
shift: Option<(usize, usize)>,
bit_depth: BitDepth,
}
// Note: this type uses interior mutability to get mutable references to multiple buffers at once. // In principle, this is not needed, but the overhead should be minimal so using `unsafe` here is // probably not worth it. #[derive(Debug)] struct ModularBuffer {
data: AtomicRefCell<Option<ModularChannel>>, // Number of times this buffer will be used, *including* when it is used for output.
remaining_uses: AtomicUsize, // Transform steps that "strongly" or "weakly" use the image data in this buffer. // A "strong" usage always triggers a re-render if the image data changes. // A "weak" usage only triggers a re-render if the buffer is final, or if the // current re-render was not only caused by weak re-renders.
used_by_transforms_strong: Vec<usize>,
used_by_transforms_weak: Vec<usize>,
size: (usize, usize),
status: AtomicUsize,
}
// Iterator over (transform_id, is_strong_use) fn users(&self, include_weak: bool) -> impl Iterator<Item = (usize, bool)> { let strong = self.used_by_transforms_strong.iter().map(|x| (*x, true)); let weak = if include_weak {
&self.used_by_transforms_weak[..]
} else {
&[]
}
.iter()
.map(|x| (*x, false));
strong.chain(weak)
}
// Gives out a copy of the buffer + auxiliary buffer, marking the buffer as used. // If this was the last usage of the buffer, does not actually copy the buffer. fn get_buffer(&self, can_consume: bool) -> Result<ModularChannel> { if !can_consume { return ModularChannel::try_clone(self.data.borrow().as_ref().unwrap());
} letmut ret = None; let _ = self.remaining_uses.fetch_update(
Ordering::Release,
Ordering::Acquire,
|remaining_pre| { let remaining = remaining_pre.checked_sub(1).unwrap(); if ret.is_none() { if remaining == 0 {
ret = Some(Ok(self.data.borrow_mut().take().unwrap()))
} else {
ret = self.data.borrow().as_ref().map(ModularChannel::try_clone);
}
} elseif remaining == 0 {
*self.data.borrow_mut() = None;
}
Some(remaining)
},
);
Ok(ret.transpose()?.unwrap())
}
fn mark_used(&self, can_consume: bool) { if !can_consume { return;
} let _ = self.remaining_uses.fetch_update(
Ordering::Release,
Ordering::Acquire,
|remaining_pre: usize| { let remaining = remaining_pre.checked_sub(1).unwrap(); if remaining == 0 {
*self.data.borrow_mut() = None;
}
Some(remaining)
},
);
}
}
#[derive(Debug)] struct ModularBufferInfo {
info: ChannelInfo, // The index of coded channel in the bit-stream, or -1 for non-coded channels.
coded_channel_id: isize, #[cfg_attr(not(feature = "tracing"), allow(dead_code))]
description: String,
grid_kind: ModularGridKind,
grid_shape: (usize, usize),
buffer_grid: Vec<ModularBuffer>,
}
fn get_grid_rect(
&self,
frame_header: &FrameHeader,
output_grid_kind: ModularGridKind,
output_grid_pos: (usize, usize),
) -> Rect { let chan_size = self.info.size; if output_grid_kind == ModularGridKind::None {
assert_eq!(self.grid_kind, output_grid_kind); return Rect {
origin: (0, 0),
size: chan_size,
};
} let shift = self.info.shift.unwrap(); let grid_dim = output_grid_kind.grid_dim(frame_header, shift); let bx = output_grid_pos.0 * grid_dim.0; let by = output_grid_pos.1 * grid_dim.1; let size = (
(chan_size.0 - bx).min(grid_dim.0),
(chan_size.1 - by).min(grid_dim.1),
); let origin = match (output_grid_kind, self.grid_kind) {
(ModularGridKind::Lf, ModularGridKind::Lf)
| (ModularGridKind::Hf, ModularGridKind::Hf) => (0, 0),
(_, ModularGridKind::None) => (bx, by),
(ModularGridKind::Hf, ModularGridKind::Lf) => { let lf_grid_dim = self.grid_kind.grid_dim(frame_header, shift);
(bx % lf_grid_dim.0, by % lf_grid_dim.1)
}
_ => unreachable!("invalid combination of output grid kind and buffer grid kind"),
}; if size.0 == 0 || size.1 == 0 {
Rect {
origin: (0, 0),
size: (0, 0),
}
} else {
Rect { origin, size }
}
}
}
/// A modular image is a sequence of channels to which one or more transforms might have been /// applied. We represent a modular image as a list of buffers, some of which are coded in the /// bitstream; other buffers are obtained as the output of one of the transformation steps. /// Some buffers are marked as `output`: those are the buffers corresponding to the pre-transform /// image channels. /// The buffers are internally divided in grids, matching the sizes of the groups they are coded /// in (with appropriate shifts), or the size of the data produced by applying the appropriate /// transforms to each of the groups in the input of the transforms. #[derive(Debug)] pubstruct FullModularImage {
buffer_info: Vec<ModularBufferInfo>,
transform_steps: Vec<TransformStepChunk>, // List of buffer indices of the channels of the modular image encoded in each kind of section. // In order, LfGlobal, LfGroup, HfGroup(pass 0), ..., HfGroup(last pass).
section_buffer_indices: Vec<Vec<usize>>,
modular_color_channels: usize,
can_do_partial_render: bool,
can_do_early_partial_render: bool,
decoded_section0_channels: usize,
needed_section0_channels_for_early_render: usize,
global_header: Option<GroupHeader>,
buffers_for_channels: Vec<usize>, // Buffers to _start rendering from_ on the next call to process_output. // This is initially set to LF global and LF buffers, and populated with HF buffers // just before we start decoding them.
ready_buffers_dry_run: BTreeSet<(usize, usize)>,
ready_buffers: BTreeSet<(usize, usize)>, // Whether each channel is used or not by the render pipeline.
pipeline_used_channels: Vec<bool>,
log_group_dim: usize,
num_groups: (usize, usize),
}
for (idx, ecups) in frame_header.ec_upsampling.iter().enumerate() { let shift_ec = ecups.ceil_log2(); let shift_color = frame_header.upsampling.ceil_log2(); let shift = shift_ec
.checked_sub(shift_color)
.expect("ec_upsampling >= upsampling should be checked in frame header") as usize; let size = frame_header.size_upsampled(); let size = (
size.0.div_ceil(*ecups as usize),
size.1.div_ceil(*ecups as usize),
);
channels.push(ChannelInfo {
output_channel_idx: Some(3 + idx),
size,
shift: Some((shift, shift)),
bit_depth: image_metadata.bit_depth,
});
}
#[cfg(feature = "tracing")] for (i, ch) in channels.iter().enumerate() {
trace!("Modular channel {i}: {ch:?}");
}
trace!("reading modular header"); let header = GroupHeader::read(br)?;
// Disallow progressive rendering with multi-channel palette transforms // or delta-palette. let has_problematic_palette_transform = header.transforms.iter().any(|x| {
x.id == TransformId::Palette
&& (x.num_channels > 1 || x.predictor_id != Predictor::Zero as u32)
});
let has_squeeze_transform = header
.transforms
.iter()
.any(|x| x.id == TransformId::Squeeze);
let (mut buffer_info, transform_steps) =
transforms::apply::meta_apply_transforms(&channels, &header)?;
// Assign each (channel, group) pair present in the bitstream to the section in which it // will be decoded. letmut section_buffer_indices: Vec<Vec<usize>> = vec![];
// Ensure that the channel list in each group is sorted by actual channel ID. for list in section_buffer_indices.iter_mut() {
list.sort_by_key(|x| buffer_info[*x].coded_channel_id);
}
trace!(?section_buffer_indices); #[cfg(feature = "tracing")] for (section, indices) in section_buffer_indices.iter().enumerate() { let section_name = match section { 0 => "LF global".to_string(), 1 => "LF groups".to_string(),
_ => format!("HF groups, pass {}", section - 2),
};
trace!("Coded modular channels in {section_name}"); for i in indices { let bi = &buffer_info[*i];
trace!( "Channel {i} {:?} coded id: {}",
bi.info, bi.coded_channel_id
);
}
}
let transform_steps = make_grids(
frame_header,
transform_steps,
§ion_buffer_indices,
&mut buffer_info,
);
#[cfg(feature = "tracing")] for (i, bi) in buffer_info.iter().enumerate() {
trace!( "Channel {i} {:?} coded_id: {} '{}' {:?} grid {:?}",
bi.info, bi.coded_channel_id, bi.description, bi.grid_kind, bi.grid_shape
); for (pos, buf) in bi.buffer_grid.iter().enumerate() {
trace!( "Channel {i} grid {pos} ({}, {}) size: {:?}, uses: {:?}, used_by: s {:?} w {:?}",
pos % bi.grid_shape.0,
pos / bi.grid_shape.0,
buf.size,
buf.remaining_uses,
buf.used_by_transforms_strong,
buf.used_by_transforms_weak,
);
}
}
#[cfg(feature = "tracing")] for (i, ts) in transform_steps.iter().enumerate() {
trace!("Transform {i}: {ts:?}");
}
letmut buffers_for_channels = vec![];
for (i, c) in buffer_info.iter().enumerate() { iflet Some(c) = c.info.output_channel_idx { if buffers_for_channels.len() <= c {
buffers_for_channels.resize(c + 1, 0);
}
buffers_for_channels[c] = i;
}
}
for b inself.section_buffer_indices[0]
.iter()
.take(self.decoded_section0_channels)
{ ifself.buffer_info[*b].buffer_grid[0].get_status() == BUFFER_STATUS_FINAL_RENDER { continue;
} // If we did a partial decode, we cannot be 100% sure of whether we correctly // decoded all the sections. Thus, mark the sections as partially decoded. self.buffer_info[*b].buffer_grid[0].set_status(if allow_partial {
BUFFER_STATUS_PARTIAL_RENDER
} else {
BUFFER_STATUS_FINAL_RENDER
}); self.ready_buffers_dry_run.insert((*b, 0));
}
Ok(())
}
pubfn mark_group_to_be_read(&mutself, section_id: usize, group: usize) { for b inself.section_buffer_indices[section_id].iter() { self.buffer_info[*b].buffer_grid[group].set_status(BUFFER_STATUS_FINAL_RENDER); self.ready_buffers_dry_run.insert((*b, group));
}
}
#[allow(clippy::type_complexity)] #[instrument(level = "debug", skip(self, frame_header, global_tree, br), ret)] pubfn read_stream(
&mutself,
stream: ModularStreamId,
frame_header: &FrameHeader,
global_tree: &Option<Tree>,
br: &mut BitReader,
) -> Result<()> { ifself.buffer_info.is_empty() {
info!("No modular channels to decode"); return Ok(());
} let (section_id, grid) = match stream {
ModularStreamId::ModularLF(group) => (1, group),
ModularStreamId::ModularHF { pass, group } => (2 + pass, group),
_ => {
unreachable!( "read_stream should only be used for streams that are part of the main Modular image"
);
}
};
if grid_is_none { let (shift_x, shift_y) = self.buffer_info[buf].info.shift.unwrap_or((0, 0)); let log_group_dim = self.log_group_dim; let gx = grid % self.num_groups.0; let gy = grid / self.num_groups.0;
let rect = Rect {
origin: (gx << log_group_dim, gy << log_group_dim),
size: (1 << log_group_dim, 1 << log_group_dim),
}; let rect = rect.downsample((shift_x as u8, shift_y as u8)); let full_size = self.buffer_info[buf].buffer_grid[grid_idx].size; let rect = rect.clip(full_size);
if rect.origin != (0, 0) || rect.size != full_size { letmut cropped = Image::new(rect.size)?; let src_view = image.get_rect(rect); for y in0..rect.size.1 {
cropped.row_mut(y).copy_from_slice(src_view.row(y));
}
image = cropped;
}
}
for c in channels[1..].iter() {
pass_to_pipeline(*c, grid, is_final, Some(image.try_clone()?))?;
}
pass_to_pipeline(channels[0], grid, is_final, Some(image))?;
}
}
Ok(())
}
// If `dry_run` is true, this call does not modify any state, and the calls to `pass_to_pipeline` // will have None as an image. Otherwise, the image will always be `Some(..)`. // It is *required* to do a dry run before doing an actual run after any event that might have // readied some buffers. pubfn process_output(
&mutself,
frame_header: &FrameHeader,
dry_run: bool,
pass_to_pipeline: &mutdyn FnMut(usize, usize, bool, Option<Image<i32>>) -> Result<()>,
) -> Result<()> { // TODO(veluca): consider using `used_channel_mask` to avoid running transforms that produce // channels that are not used.
let ready_buffers = if dry_run {
std::mem::take(&mutself.ready_buffers_dry_run)
} else {
assert!(self.ready_buffers_dry_run.is_empty());
std::mem::take(&mutself.ready_buffers)
};
for (buf, grid) in ready_buffers { ifself.buffer_info[buf].info.output_channel_idx.is_some() {
buffers_to_output.push((buf, grid));
} for (t, is_strong_dep) inself.buffer_info[buf].buffer_grid[grid].users(true) { let layer = self.transform_steps[t].layer; let layer = to_process_by_layer.entry(layer).or_default(); let is_strong = layer.entry(t).or_default();
*is_strong |= is_strong_dep;
} if dry_run { self.ready_buffers.insert((buf, grid));
}
}
// When doing a dry run, run the same logic as the real execution, but // without modifying the actual buffer status -- instead, we use local // overrides. // This allows us to know what buffers will be produced before producing any. letmut status_overrides = BTreeMap::new();
if dependency_status == BUFFER_STATUS_NOT_RENDERED { continue;
} let is_final = dependency_status == BUFFER_STATUS_FINAL_RENDER;
letmut previous_output_status = None; for (b, g) in tfm.outputs(&self.buffer_info) { let status = get_status(&mut status_overrides, b, g); if previous_output_status.is_none() {
previous_output_status = Some(status);
}
assert_eq!(Some(status), previous_output_status); if dry_run {
status_overrides.insert((b, g), dependency_status);
} else { self.buffer_info[b].buffer_grid[g].set_status(dependency_status);
}
} let previous_output_status = previous_output_status.unwrap();
if !dry_run {
tfm.do_run(frame_header, &self.buffer_info, is_final)?;
}
// If this was the first _or_ the last render, trigger a re-render across weak edges // even if the render was caused by a weak edge. // This is necessary to finish drawing those renders correctly. let is_strong = is_strong
|| (previous_output_status == BUFFER_STATUS_NOT_RENDERED
|| dependency_status == BUFFER_STATUS_FINAL_RENDER); for (buf, grid) inself.transform_steps[t].outputs(&self.buffer_info) { ifself.buffer_info[buf].info.output_channel_idx.is_some() {
buffers_to_output.push((buf, grid));
} for (t, is_strong_dep) in self.buffer_info[buf].buffer_grid[grid].users(is_strong)
{
new_dirty_transforms.push((t, is_strong_dep));
}
}
}
for (t, is_strong_dep) in new_dirty_transforms.drain(..) { let layer = self.transform_steps[t].layer; let layer = to_process_by_layer.entry(layer).or_default(); let is_strong = layer.entry(t).or_default();
*is_strong |= is_strong_dep;
}
}
// Pass all the output buffers to the render pipeline. for (buf, grid) in buffers_to_output { ifself.buffer_info[buf].grid_kind == ModularGridKind::None { for g in0..self.num_groups.0 * self.num_groups.1 { self.maybe_output(buf, g, dry_run, pass_to_pipeline)?;
}
} else { self.maybe_output(buf, grid, dry_run, pass_to_pipeline)?;
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.