Skip to main content

j2k/
batch.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Bounded, fallible CPU batch facade for independent J2K/HTJ2K tiles.
4
5use alloc::vec::Vec;
6use std::sync::Arc;
7
8pub use j2k_core::TileBatchOptions;
9use j2k_core::{
10    BatchResultSlot, DecodeOutcome, DecoderContext, PixelFormat, Rect, TileBatchDecode,
11};
12
13use crate::{J2kCodec, J2kContext, J2kDecodeWarning, J2kError, J2kScratchPool};
14
15mod admission;
16mod allocation;
17mod direct;
18mod planning;
19mod scheduler;
20mod worker;
21
22use admission::BatchAllocationBudget;
23use direct::build_repeated_direct_color_region_plan;
24
25/// One full-tile decode request for [`decode_tiles_into`].
26pub type TileDecodeJob<'i, 'o> = j2k_core::TileDecodeJob<'i, 'o>;
27
28/// One ROI tile decode request for [`decode_tiles_region_into`].
29pub type TileRegionDecodeJob<'i, 'o> = j2k_core::TileRegionDecodeJob<'i, 'o>;
30
31/// One scaled tile decode request for [`decode_tiles_scaled_into`].
32pub type TileScaledDecodeJob<'i, 'o> = j2k_core::TileScaledDecodeJob<'i, 'o>;
33
34/// One ROI+scaled tile decode request for [`decode_tiles_region_scaled_into`].
35pub type TileRegionScaledDecodeJob<'i, 'o> = j2k_core::TileRegionScaledDecodeJob<'i, 'o>;
36
37/// Caller-owned output target for one context-reused J2K/HTJ2K tile decode helper.
38pub struct TileDecodeOutput<'o> {
39    /// Caller-owned output buffer.
40    pub out: &'o mut [u8],
41    /// Distance in bytes between output rows.
42    pub stride: usize,
43    /// Requested output pixel format.
44    pub fmt: PixelFormat,
45}
46
47/// Error returned by J2K CPU tile batches.
48///
49/// Tile-specific codec failures retain their caller input index. Allocation,
50/// spawning, worker panic, and result-integrity failures are reported as typed
51/// infrastructure errors without inventing a tile-zero decode failure.
52pub type TileBatchError = j2k_core::BatchDecodeError<J2kError>;
53
54type BatchOutcome = DecodeOutcome<J2kDecodeWarning>;
55type J2kBatchResultSlot = BatchResultSlot<BatchOutcome, J2kError>;
56
57/// One-shot parse-plus-decode of an independent J2K/HTJ2K tile into the
58/// caller's buffer, reusing both caller-owned [`DecoderContext`] and
59/// caller-owned [`J2kScratchPool`].
60#[doc(hidden)]
61pub fn decode_tile_into_in_context(
62    bytes: &[u8],
63    ctx: &mut DecoderContext<J2kContext>,
64    pool: &mut J2kScratchPool,
65    output: TileDecodeOutput<'_>,
66) -> Result<BatchOutcome, J2kError> {
67    let TileDecodeOutput { out, stride, fmt } = output;
68    <J2kCodec as TileBatchDecode>::decode_tile(ctx, pool, bytes, out, stride, fmt)
69}
70
71/// One-shot parse-plus-ROI-decode of an independent J2K/HTJ2K tile into the
72/// caller's buffer, reusing both caller-owned [`DecoderContext`] and
73/// caller-owned [`J2kScratchPool`].
74#[doc(hidden)]
75pub fn decode_tile_region_into_in_context(
76    bytes: &[u8],
77    ctx: &mut DecoderContext<J2kContext>,
78    pool: &mut J2kScratchPool,
79    output: TileDecodeOutput<'_>,
80    roi: Rect,
81) -> Result<BatchOutcome, J2kError> {
82    let TileDecodeOutput { out, stride, fmt } = output;
83    <J2kCodec as TileBatchDecode>::decode_tile_region(ctx, pool, bytes, out, stride, fmt, roi)
84}
85
86/// One-shot parse-plus-scaled-decode of an independent J2K/HTJ2K tile into the
87/// caller's buffer, reusing both caller-owned [`DecoderContext`] and
88/// caller-owned [`J2kScratchPool`].
89#[doc(hidden)]
90pub fn decode_tile_scaled_into_in_context(
91    bytes: &[u8],
92    ctx: &mut DecoderContext<J2kContext>,
93    pool: &mut J2kScratchPool,
94    output: TileDecodeOutput<'_>,
95    scale: j2k_core::Downscale,
96) -> Result<BatchOutcome, J2kError> {
97    let TileDecodeOutput { out, stride, fmt } = output;
98    <J2kCodec as TileBatchDecode>::decode_tile_scaled(ctx, pool, bytes, out, stride, fmt, scale)
99}
100
101/// One-shot parse-plus-ROI-scaled-decode of an independent J2K/HTJ2K tile
102/// into the caller's buffer, reusing both caller-owned [`DecoderContext`] and
103/// caller-owned [`J2kScratchPool`].
104#[doc(hidden)]
105pub fn decode_tile_region_scaled_into_in_context(
106    bytes: &[u8],
107    ctx: &mut DecoderContext<J2kContext>,
108    pool: &mut J2kScratchPool,
109    output: TileDecodeOutput<'_>,
110    roi: Rect,
111    scale: j2k_core::Downscale,
112) -> Result<BatchOutcome, J2kError> {
113    let TileDecodeOutput { out, stride, fmt } = output;
114    <J2kCodec as TileBatchDecode>::decode_tile_region_scaled(
115        ctx,
116        pool,
117        fmt,
118        TileRegionScaledDecodeJob {
119            input: bytes,
120            out,
121            stride,
122            roi,
123            scale,
124        },
125    )
126}
127
128/// Decode independent J2K/HTJ2K tiles into caller-owned output buffers.
129///
130/// Outcomes preserve caller input order. Generic native decoding is limited to
131/// four concurrent workers by the aggregate memory policy; requesting more
132/// workers reduces concurrency before spawning rather than weakening the
133/// native decoder's per-operation bound.
134pub fn decode_tiles_into(
135    jobs: &mut [TileDecodeJob<'_, '_>],
136    fmt: PixelFormat,
137    options: TileBatchOptions,
138) -> Result<Vec<BatchOutcome>, TileBatchError> {
139    scheduler::decode_batch(jobs, options, |worker, chunk, results| {
140        worker.decode_tile_jobs(chunk, results, fmt)
141    })
142}
143
144/// Decode independent J2K/HTJ2K tile regions into caller-owned output buffers.
145pub fn decode_tiles_region_into(
146    jobs: &mut [TileRegionDecodeJob<'_, '_>],
147    fmt: PixelFormat,
148    options: TileBatchOptions,
149) -> Result<Vec<BatchOutcome>, TileBatchError> {
150    scheduler::decode_batch(jobs, options, |worker, chunk, results| {
151        worker.decode_tile_region_jobs(chunk, results, fmt)
152    })
153}
154
155/// Decode independent J2K/HTJ2K tiles at reduced resolution into caller-owned
156/// output buffers.
157pub fn decode_tiles_scaled_into(
158    jobs: &mut [TileScaledDecodeJob<'_, '_>],
159    fmt: PixelFormat,
160    options: TileBatchOptions,
161) -> Result<Vec<BatchOutcome>, TileBatchError> {
162    scheduler::decode_batch(jobs, options, |worker, chunk, results| {
163        worker.decode_tile_scaled_jobs(chunk, results, fmt)
164    })
165}
166
167/// Decode independent J2K/HTJ2K tile regions at reduced resolution into
168/// caller-owned output buffers.
169pub fn decode_tiles_region_scaled_into(
170    jobs: &mut [TileRegionScaledDecodeJob<'_, '_>],
171    fmt: PixelFormat,
172    options: TileBatchOptions,
173) -> Result<Vec<BatchOutcome>, TileBatchError> {
174    if jobs.is_empty() {
175        return Ok(Vec::new());
176    }
177
178    let shared_direct_plan = build_repeated_direct_color_region_plan(jobs, fmt)
179        .map_err(|source| TileBatchError::Tile(j2k_core::TileBatchError { index: 0, source }))?;
180    let plan = scheduler::plan_direct_batch(jobs.len(), options)?;
181    let shared_plan_bytes = shared_direct_plan
182        .as_ref()
183        .map_or(
184            Ok(0),
185            direct::DirectColorRegionCache::retained_allocation_bytes,
186        )
187        .map_err(|source| TileBatchError::Tile(j2k_core::TileBatchError { index: 0, source }))?;
188    let allocation_budget = BatchAllocationBudget::with_baseline(shared_plan_bytes)?;
189    let results = scheduler::run_chunks_scoped(
190        jobs,
191        plan,
192        Some(Arc::clone(&allocation_budget)),
193        |worker, chunk, results| {
194            worker.decode_tile_region_scaled_jobs(chunk, results, fmt, shared_direct_plan.as_ref())
195        },
196    )?;
197
198    // The shared native plan must not overlap ordered-result allocation. All
199    // thread-owned contexts and scratch were already dropped by the scheduler.
200    drop(shared_direct_plan);
201    drop(allocation_budget);
202    scheduler::collect_results(results)
203}