use j2k_core::PixelFormat;
use j2k_native::{
HtCodeBlockPayloadRanges, J2kDirectGrayscalePlan, J2kDirectGrayscaleStep, J2kWaveletTransform,
};
#[cfg(feature = "cuda-runtime")]
use j2k_native::{J2kClassicCodeBlockPayload, J2kCodestreamRange};
use crate::{allocation::HostPhaseBudget, Error};
mod accessors;
mod classic;
mod ht;
mod required_regions;
mod shared;
#[cfg(test)]
mod tests;
#[cfg(feature = "cuda-runtime")]
use self::classic::referenced::{
append_referenced_classic_subband, referenced_classic_payload_bytes,
};
use self::{
classic::append_classic_subband,
ht::{append_ht_subband, append_referenced_ht_subband, referenced_payload_bytes},
required_regions::required_regions_for_direct_plan,
shared::{convert_store_step, CudaPlanOwners},
};
const EMPTY_CUDA_COEFFICIENT_PLAN: &str = "strict CUDA plan contains no coefficient bands";
const MIXED_TRANSFORMS_UNSUPPORTED: &str = "strict CUDA HTJ2K plan contains mixed DWT transforms";
const PLAN_PAYLOAD_TOO_LARGE: &str = "strict CUDA HTJ2K plan payload is too large";
const PLAN_OUTPUT_RECT_MISMATCH: &str =
"strict CUDA HTJ2K plan store does not fit the requested output rectangle";
const REFERENCED_PLAN_CLASSIC_UNSUPPORTED: &str =
"prepared CUDA HTJ2K plan unexpectedly contains classic code blocks";
#[cfg(feature = "cuda-runtime")]
const REFERENCED_CLASSIC_PLAN_HT_UNSUPPORTED: &str =
"prepared CUDA classic plan unexpectedly contains HT code blocks";
const REFERENCED_PLAN_PAYLOAD_MISMATCH: &str =
"prepared CUDA HTJ2K geometry does not match referenced payload ranges";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub(crate) enum CudaHtj2kTransform {
Reversible53,
Irreversible97,
}
pub(crate) type CudaHtj2kBandId = u32;
impl CudaHtj2kTransform {
pub(crate) fn from_native(value: J2kWaveletTransform) -> Self {
match value {
J2kWaveletTransform::Reversible53 => Self::Reversible53,
J2kWaveletTransform::Irreversible97 => Self::Irreversible97,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C)]
pub(crate) struct CudaHtj2kCodeBlock {
pub(crate) subband_index: u32,
pub(crate) payload_offset: u64,
pub(crate) payload_len: u32,
pub(crate) cleanup_length: u32,
pub(crate) refinement_length: u32,
pub(crate) output_x: u32,
pub(crate) output_y: u32,
pub(crate) width: u32,
pub(crate) height: u32,
pub(crate) output_stride: u32,
pub(crate) missing_bit_planes: u8,
pub(crate) number_of_coding_passes: u8,
pub(crate) num_bitplanes: u8,
pub(crate) stripe_causal: u8,
pub(crate) dequantization_step: f32,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C)]
pub(crate) struct CudaHtj2kSubband {
pub(crate) band_id: CudaHtj2kBandId,
pub(crate) x0: u32,
pub(crate) y0: u32,
pub(crate) x1: u32,
pub(crate) y1: u32,
pub(crate) width: u32,
pub(crate) height: u32,
pub(crate) code_block_start: u32,
pub(crate) code_block_count: u32,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C)]
pub(crate) struct CudaClassicCodeBlock {
pub(crate) subband_index: u32,
pub(crate) payload_offset: u64,
pub(crate) payload_len: u32,
pub(crate) segment_start: u32,
pub(crate) segment_count: u32,
pub(crate) output_x: u32,
pub(crate) output_y: u32,
pub(crate) width: u32,
pub(crate) height: u32,
pub(crate) output_stride: u32,
pub(crate) missing_bit_planes: u8,
pub(crate) number_of_coding_passes: u8,
pub(crate) total_bitplanes: u8,
pub(crate) sub_band_type: u8,
pub(crate) style_flags: u32,
pub(crate) strict: bool,
pub(crate) dequantization_step: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub(crate) struct CudaClassicSegment {
pub(crate) data_offset: u32,
pub(crate) data_length: u32,
pub(crate) start_coding_pass: u8,
pub(crate) end_coding_pass: u8,
pub(crate) use_arithmetic: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub(crate) struct CudaClassicSubband {
pub(crate) band_id: CudaHtj2kBandId,
pub(crate) width: u32,
pub(crate) height: u32,
pub(crate) code_block_start: u32,
pub(crate) code_block_count: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub(crate) struct CudaHtj2kIdwtStep {
pub(crate) output_band_id: CudaHtj2kBandId,
pub(crate) transform: CudaHtj2kTransform,
pub(crate) rect: CudaHtj2kRect,
pub(crate) ll_band_id: CudaHtj2kBandId,
pub(crate) ll_rect: CudaHtj2kRect,
pub(crate) hl_band_id: CudaHtj2kBandId,
pub(crate) hl_rect: CudaHtj2kRect,
pub(crate) lh_band_id: CudaHtj2kBandId,
pub(crate) lh_rect: CudaHtj2kRect,
pub(crate) hh_band_id: CudaHtj2kBandId,
pub(crate) hh_rect: CudaHtj2kRect,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C)]
pub(crate) struct CudaHtj2kStoreStep {
pub(crate) input_band_id: CudaHtj2kBandId,
pub(crate) input_rect: CudaHtj2kRect,
pub(crate) source_x: u32,
pub(crate) source_y: u32,
pub(crate) copy_width: u32,
pub(crate) copy_height: u32,
pub(crate) output_width: u32,
pub(crate) output_height: u32,
pub(crate) output_x: u32,
pub(crate) output_y: u32,
pub(crate) addend: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub(crate) struct CudaHtj2kRect {
pub(crate) x0: u32,
pub(crate) y0: u32,
pub(crate) x1: u32,
pub(crate) y1: u32,
}
#[derive(Debug)]
pub(crate) struct CudaHtj2kDecodePlan {
#[cfg_attr(
not(feature = "cuda-runtime"),
expect(
dead_code,
reason = "output dimensions are consumed only by CUDA decode routes"
)
)]
dimensions: (u32, u32),
#[cfg_attr(
not(feature = "cuda-runtime"),
expect(
dead_code,
reason = "sample metadata is consumed only by CUDA decode routes"
)
)]
bit_depth: u8,
#[cfg_attr(
not(feature = "cuda-runtime"),
expect(
dead_code,
reason = "output format is consumed only by CUDA decode routes"
)
)]
output_format: PixelFormat,
#[cfg_attr(
not(feature = "cuda-runtime"),
expect(
dead_code,
reason = "output origin is consumed only by CUDA decode routes"
)
)]
output_origin: (u32, u32),
#[cfg_attr(
not(feature = "cuda-runtime"),
expect(
dead_code,
reason = "transform metadata is consumed only by CUDA decode routes"
)
)]
transform: CudaHtj2kTransform,
payload: Vec<u8>,
code_blocks: Vec<CudaHtj2kCodeBlock>,
classic_code_blocks: Vec<CudaClassicCodeBlock>,
classic_segments: Vec<CudaClassicSegment>,
#[cfg_attr(
not(feature = "cuda-runtime"),
expect(
dead_code,
reason = "classic subband metadata is consumed only by CUDA decode routes"
)
)]
classic_subbands: Vec<CudaClassicSubband>,
#[cfg_attr(
not(feature = "cuda-runtime"),
expect(
dead_code,
reason = "subband metadata is consumed only by CUDA decode routes"
)
)]
subbands: Vec<CudaHtj2kSubband>,
#[cfg_attr(
not(feature = "cuda-runtime"),
expect(
dead_code,
reason = "IDWT metadata is consumed only by CUDA decode routes"
)
)]
idwt_steps: Vec<CudaHtj2kIdwtStep>,
#[cfg_attr(
not(feature = "cuda-runtime"),
expect(
dead_code,
reason = "store metadata is consumed only by CUDA decode routes"
)
)]
store_steps: Vec<CudaHtj2kStoreStep>,
}
impl CudaHtj2kDecodePlan {
#[cfg(feature = "cuda-runtime")]
#[expect(
clippy::too_many_arguments,
reason = "explicit retained classic tile inputs"
)]
pub(crate) fn from_referenced_classic_tile_grayscale_plan_into_shared(
plan: &J2kDirectGrayscalePlan,
payloads: &[J2kClassicCodeBlockPayload],
ranges: &[J2kCodestreamRange],
encoded: &[u8],
output_format: PixelFormat,
output_origin: (u32, u32),
output_dimensions: (u32, u32),
shared_payload: &mut Vec<u8>,
host_budget: &mut HostPhaseBudget,
) -> Result<Self, Error> {
let payload_bytes = referenced_classic_payload_bytes(encoded, payloads, ranges)?;
if payload_bytes != 0 {
host_budget.try_vec_reserve(shared_payload, payload_bytes)?;
}
let (mut owners, _) = CudaPlanOwners::from_referenced_plan(plan)?;
let mut payloads = payloads.iter();
for step in &plan.steps {
match step {
J2kDirectGrayscaleStep::HtSubBand(_) => {
return Err(Error::UnsupportedCudaRequest {
reason: REFERENCED_CLASSIC_PLAN_HT_UNSUPPORTED,
});
}
J2kDirectGrayscaleStep::ClassicSubBand(subband) => {
append_referenced_classic_subband(
&mut owners,
subband,
None,
&mut payloads,
ranges,
encoded,
shared_payload,
)?;
}
J2kDirectGrayscaleStep::Idwt(step) => owners.append_idwt(*step)?,
J2kDirectGrayscaleStep::Store(step) => {
owners
.store_steps
.push(shared::convert_referenced_tile_store_step(
*step,
output_dimensions,
)?);
}
}
}
if payloads.next().is_some() {
return Err(Error::UnsupportedCudaRequest {
reason: REFERENCED_PLAN_PAYLOAD_MISMATCH,
});
}
owners.finish(plan, output_format, output_origin, output_dimensions)
}
#[expect(
clippy::too_many_arguments,
reason = "the tile adapter explicitly carries source bytes, output geometry, shared arena, and allocation budget"
)]
pub(crate) fn from_referenced_tile_grayscale_plan_into_shared(
plan: &J2kDirectGrayscalePlan,
payloads: &[HtCodeBlockPayloadRanges],
encoded: &[u8],
output_format: PixelFormat,
output_origin: (u32, u32),
output_dimensions: (u32, u32),
shared_payload: &mut Vec<u8>,
host_budget: &mut HostPhaseBudget,
) -> Result<Self, Error> {
let payload_bytes = referenced_payload_bytes(encoded, payloads)?;
if payload_bytes != 0 {
host_budget.try_vec_reserve(shared_payload, payload_bytes)?;
}
let (mut owners, _) = CudaPlanOwners::from_referenced_plan(plan)?;
let mut payloads = payloads.iter();
for step in &plan.steps {
match step {
J2kDirectGrayscaleStep::HtSubBand(subband) => {
append_referenced_ht_subband(
&mut owners,
subband,
None,
&mut payloads,
encoded,
shared_payload,
)?;
}
J2kDirectGrayscaleStep::ClassicSubBand(_) => {
return Err(Error::UnsupportedCudaRequest {
reason: REFERENCED_PLAN_CLASSIC_UNSUPPORTED,
});
}
J2kDirectGrayscaleStep::Idwt(step) => owners.append_idwt(*step)?,
J2kDirectGrayscaleStep::Store(step) => {
owners
.store_steps
.push(shared::convert_referenced_tile_store_step(
*step,
output_dimensions,
)?);
}
}
}
if payloads.next().is_some() {
return Err(Error::UnsupportedCudaRequest {
reason: REFERENCED_PLAN_PAYLOAD_MISMATCH,
});
}
owners.finish(plan, output_format, output_origin, output_dimensions)
}
pub(crate) fn from_grayscale_direct_plan(
plan: &J2kDirectGrayscalePlan,
output_format: PixelFormat,
output_origin: (u32, u32),
) -> Result<Self, Error> {
Self::from_grayscale_direct_plan_region(plan, output_format, output_origin, plan.dimensions)
}
pub(crate) fn from_grayscale_direct_plan_region(
plan: &J2kDirectGrayscalePlan,
output_format: PixelFormat,
output_origin: (u32, u32),
output_dimensions: (u32, u32),
) -> Result<Self, Error> {
let (mut owners, retained_plan_capacity) = CudaPlanOwners::from_plan(plan)?;
let required_regions = if output_origin == (0, 0) && output_dimensions == plan.dimensions {
None
} else {
Some(required_regions_for_direct_plan(
plan,
retained_plan_capacity,
)?)
};
for step in &plan.steps {
match step {
J2kDirectGrayscaleStep::HtSubBand(subband) => {
append_ht_subband(&mut owners, subband, required_regions.as_ref())?;
}
J2kDirectGrayscaleStep::ClassicSubBand(subband) => {
append_classic_subband(&mut owners, subband, required_regions.as_ref())?;
}
J2kDirectGrayscaleStep::Idwt(step) => owners.append_idwt(*step)?,
J2kDirectGrayscaleStep::Store(step) => {
owners.store_steps.push(convert_store_step(
*step,
output_origin,
output_dimensions,
)?);
}
}
}
owners.finish(plan, output_format, output_origin, output_dimensions)
}
}