Skip to main content

j2k_transcode/jpeg_to_htj2k/
batch.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use super::{
4    component_sampling_for_jpeg, dct_blocks_to_8x8_f64, decompose_97_from_first_level,
5    decomposition_levels_for_components,
6    encode_precomputed_htj2k_53_with_accelerator_and_max_host_bytes,
7    encode_precomputed_htj2k_97_batch_owned_with_accelerator_and_max_host_bytes,
8    encode_precomputed_htj2k_97_with_accelerator_and_max_host_bytes,
9    encode_preencoded_htj2k_97_compact_owned_with_accelerator_and_max_host_bytes,
10    encode_preencoded_htj2k_97_owned_with_accelerator_and_max_host_bytes,
11    encode_prequantized_htj2k_97_with_accelerator_and_max_host_bytes,
12    encoded_transcode_retained_bytes, error_metrics_i32_with_live_budget, extract_dct_blocks,
13    flatten_integer_wavelet, float97_reference_coefficients,
14    float_direct_97_wavelet_from_component, float_reference_coefficients,
15    integer_dct_job_for_component, integer_direct_wavelet_from_component,
16    integer_reference_coefficients, integer_wavelet_from_first_level, j2k_dwt97_from_wavelet,
17    j2k_dwt_from_integer_wavelet, jpeg_to_htj2k_with_scratch, map_encode_error,
18    rounded_wavelet97_i32, transcode_path_name, validate_component_block_grid,
19    validate_jpeg_transcode_workspace, validate_transcode_options, BatchTranscodeReport,
20    ComponentWavelet97, CpuOnlyJ2kEncodeStageAccelerator, DctExtractOptions,
21    DctGridI16ToHtj2k97CodeBlockBatch, DctGridI16ToHtj2k97CodeBlockJob, DctGridToDwt97Job,
22    DctGridToHtj2k97CodeBlockJob, DctToWaveletStageAccelerator, Dwt97BatchStageTimings,
23    EncodedTranscode, EncodedTranscodeBatch, HostLiveBudget, Htj2k97CodeBlockOptions,
24    IndexedParallelIterator, Instant, IntegerWavelet, IntoParallelIterator,
25    IntoParallelRefIterator, J2kEncodeDispatchReport, J2kEncodeStageAccelerator, JpegDctImage,
26    JpegTileBatchInput, JpegToHtj2kCoefficientPath, JpegToHtj2kEncodeOptions, JpegToHtj2kError,
27    JpegToHtj2kOptions, JpegToHtj2kScratch, JpegToHtj2kTranscoder, ParallelIterator,
28    PrecomputedHtj2k53Component, PrecomputedHtj2k53Image, PrecomputedHtj2k97Component,
29    PrecomputedHtj2k97Image, PreencodedHtj2k97CompactComponent, PreencodedHtj2k97CompactImage,
30    PreencodedHtj2k97Component, PreencodedHtj2k97Image, PrequantizedHtj2k97Component,
31    PrequantizedHtj2k97Image, TranscodeComponentReport, TranscodeReport, TranscodeTimingReport,
32    TranscodeValidationClassification,
33};
34use crate::allocation::try_vec_with_capacity;
35mod group_budget;
36mod workspace;
37use self::workspace::{validate_batch_workspace, BatchWorkspaceKind};
38mod result_slots;
39use self::result_slots::BatchResultSlots;
40mod prepare;
41pub(super) use self::prepare::{
42    batch_component_groups, float97_batch_component_groups, prepare_float97_batch_tile,
43    prepare_integer_batch_tile, BatchComponentRef, Float97BatchTile, Float97PrecomputedBatchRecord,
44    IntegerBatchTile,
45};
46mod transform;
47pub(super) use self::transform::{
48    add_dwt97_batch_stage_timings, htj2k97_codeblock_options, i16_htj2k97_jobs_for_batch_group,
49    record_accelerator_attempt, record_accelerator_dispatch, record_batch_attempt,
50    record_batch_dispatch, record_cpu_fallback, transform_float97_batch_tiles,
51    transform_integer_batch_tiles,
52};
53mod accelerated_storage;
54#[cfg(test)]
55pub(super) use self::accelerated_storage::store_compact_preencoded_component;
56pub(super) use self::accelerated_storage::{
57    try_store_grouped_i16_preencoded_float97_batches, try_store_prequantized_float97_batch_group,
58};
59mod storage;
60pub(super) use self::storage::{store_float97_batch_wavelet, store_integer_batch_wavelet};
61mod encode;
62pub(super) use self::encode::{
63    add_encode_timing_counters_from_result, encode_float97_prepared_tiles,
64    encode_integer_prepared_tiles, record_encode_dispatch_delta,
65};
66mod actual_live;
67use self::actual_live::{
68    validate_float97_prepared_collection, validate_integer_prepared_collection,
69};
70mod individual;
71use self::individual::transcode_tile_batch_individually;
72
73/// Transcode many JPEG tiles into HTJ2K codestreams.
74pub fn jpeg_to_htj2k_batch(
75    tiles: &[JpegTileBatchInput<'_>],
76    options: &JpegToHtj2kOptions,
77) -> Result<EncodedTranscodeBatch, JpegToHtj2kError> {
78    JpegToHtj2kTranscoder::default().transcode_batch(tiles, options)
79}
80
81enum BatchExecutionRoute {
82    Integer53,
83    AcceleratedFloat97,
84    Individual,
85}
86
87fn validate_batch_route(
88    tiles: &[JpegTileBatchInput<'_>],
89    options: &JpegToHtj2kOptions,
90    accelerator: &impl DctToWaveletStageAccelerator,
91) -> Result<BatchExecutionRoute, JpegToHtj2kError> {
92    validate_transcode_options(options)?;
93    let (route, workspace_kind) = match options.coefficient_path {
94        JpegToHtj2kCoefficientPath::IntegerDirect53 => {
95            (BatchExecutionRoute::Integer53, BatchWorkspaceKind::Integer)
96        }
97        JpegToHtj2kCoefficientPath::FloatDirectLinear97
98            if accelerator.supports_dwt97_batch()
99                || accelerator.supports_htj2k97_codeblock_batch() =>
100        {
101            (
102                BatchExecutionRoute::AcceleratedFloat97,
103                BatchWorkspaceKind::AcceleratedFloat97,
104            )
105        }
106        JpegToHtj2kCoefficientPath::FloatDirectLinear53
107        | JpegToHtj2kCoefficientPath::FloatDirectLinear97 => (
108            BatchExecutionRoute::Individual,
109            BatchWorkspaceKind::Individual,
110        ),
111    };
112    validate_batch_workspace(tiles, options, workspace_kind)?;
113    Ok(route)
114}
115
116pub(super) fn jpeg_tile_batch_to_htj2k_with_scratch<
117    A: DctToWaveletStageAccelerator,
118    E: J2kEncodeStageAccelerator,
119>(
120    tiles: &[JpegTileBatchInput<'_>],
121    options: &JpegToHtj2kOptions,
122    scratch: &mut JpegToHtj2kScratch,
123    accelerator: &mut A,
124    encode_accelerator: &mut E,
125) -> Result<EncodedTranscodeBatch, JpegToHtj2kError> {
126    match validate_batch_route(tiles, options, accelerator)? {
127        BatchExecutionRoute::Integer53 => {}
128        BatchExecutionRoute::AcceleratedFloat97 => {
129            return jpeg_float97_tile_batch_to_htj2k_with_scratch(
130                tiles,
131                options,
132                scratch,
133                accelerator,
134                encode_accelerator,
135            );
136        }
137        BatchExecutionRoute::Individual => {
138            return transcode_tile_batch_individually(
139                tiles,
140                options,
141                scratch,
142                accelerator,
143                encode_accelerator,
144            );
145        }
146    }
147
148    let extract_start = Instant::now();
149    let mut prepared_results = try_vec_with_capacity(tiles.len())?;
150    tiles
151        .par_iter()
152        .enumerate()
153        .map(|(tile_index, tile)| {
154            (
155                tile_index,
156                prepare_integer_batch_tile(tile_index, tile.bytes, options),
157            )
158        })
159        .collect_into_vec(&mut prepared_results);
160    validate_integer_prepared_collection(&prepared_results, prepared_results.capacity(), scratch)?;
161    let extract_us = extract_start.elapsed().as_micros();
162    let mut tile_results = BatchResultSlots::try_new(tiles.len())?;
163    let mut prepared_tiles = try_vec_with_capacity(tiles.len())?;
164    for (tile_index, result) in prepared_results {
165        match result {
166            Ok(prepared) => prepared_tiles.push(prepared),
167            Err(error) => tile_results.insert(tile_index, Err(error))?,
168        }
169    }
170
171    let transform_start = Instant::now();
172    let mut timings = TranscodeTimingReport::default();
173    let (reversible_dwt53_batches, reversible_dwt53_batch_jobs) = transform_integer_batch_tiles(
174        &mut prepared_tiles,
175        options,
176        scratch,
177        accelerator,
178        &mut timings,
179    )?;
180    let transform_us = transform_start.elapsed().as_micros();
181    timings.jpeg_dct_extract_us = extract_us;
182    timings.dct_to_wavelet_total_us = transform_us;
183    timings.tile_count = prepared_tiles.len();
184
185    let encode_start = Instant::now();
186    let mut encode_external = HostLiveBudget::process_cap();
187    encode_external.add_bytes(scratch.retained_bytes()?)?;
188    encode_external.add_bytes(tile_results.retained_slot_bytes()?)?;
189    let encoded_tiles = encode_integer_prepared_tiles(
190        prepared_tiles,
191        options,
192        encode_accelerator,
193        encode_external.live_bytes(),
194    )?;
195    for (tile_index, encoded) in encoded_tiles {
196        add_encode_timing_counters_from_result(&mut timings, &encoded);
197        tile_results.insert(tile_index, encoded)?;
198    }
199    let encode_us = encode_start.elapsed().as_micros();
200    timings.htj2k_encode_us = encode_us;
201
202    let output_tiles =
203        tile_results.into_results_with_live_budget(scratch.retained_bytes()?, |result| {
204            result
205                .as_ref()
206                .map_or(Ok(0), encoded_transcode_retained_bytes)
207        })?;
208    Ok(batch_output(
209        output_tiles,
210        BatchTranscodeReport {
211            tile_count: tiles.len(),
212            successful_tiles: 0,
213            failed_tiles: 0,
214            transformed_components: reversible_dwt53_batch_jobs,
215            reversible_dwt53_batches,
216            reversible_dwt53_batch_jobs,
217            extract_us,
218            transform_us,
219            encode_us,
220            timings,
221            coefficient_path: options.coefficient_path,
222        },
223    ))
224}
225
226pub(super) fn jpeg_float97_tile_batch_to_htj2k_with_scratch<
227    A: DctToWaveletStageAccelerator,
228    E: J2kEncodeStageAccelerator,
229>(
230    tiles: &[JpegTileBatchInput<'_>],
231    options: &JpegToHtj2kOptions,
232    scratch: &mut JpegToHtj2kScratch,
233    accelerator: &mut A,
234    encode_accelerator: &mut E,
235) -> Result<EncodedTranscodeBatch, JpegToHtj2kError> {
236    let extract_start = Instant::now();
237    let mut prepared_results = try_vec_with_capacity(tiles.len())?;
238    tiles
239        .par_iter()
240        .enumerate()
241        .map(|(tile_index, tile)| {
242            (
243                tile_index,
244                prepare_float97_batch_tile(tile_index, tile.bytes, options),
245            )
246        })
247        .collect_into_vec(&mut prepared_results);
248    validate_float97_prepared_collection(&prepared_results, prepared_results.capacity(), scratch)?;
249    let extract_us = extract_start.elapsed().as_micros();
250    let mut tile_results = BatchResultSlots::try_new(tiles.len())?;
251    let mut prepared_tiles = try_vec_with_capacity(tiles.len())?;
252    for (tile_index, result) in prepared_results {
253        match result {
254            Ok(prepared) => prepared_tiles.push(prepared),
255            Err(error) => tile_results.insert(tile_index, Err(error))?,
256        }
257    }
258
259    let transform_start = Instant::now();
260    let mut timings = TranscodeTimingReport::default();
261    let (_dwt97_batches, dwt97_batch_jobs) = transform_float97_batch_tiles(
262        &mut prepared_tiles,
263        options,
264        scratch,
265        accelerator,
266        &mut timings,
267    )?;
268    let transform_us = transform_start.elapsed().as_micros();
269    timings.jpeg_dct_extract_us = extract_us;
270    timings.dct_to_wavelet_total_us = transform_us;
271    timings.tile_count = prepared_tiles.len();
272
273    let encode_start = Instant::now();
274    let mut encode_external = HostLiveBudget::process_cap();
275    encode_external.add_bytes(scratch.retained_bytes()?)?;
276    encode_external.add_bytes(tile_results.retained_slot_bytes()?)?;
277    let encoded_tiles = encode_float97_prepared_tiles(
278        prepared_tiles,
279        options,
280        encode_accelerator,
281        encode_external.live_bytes(),
282    )?;
283    for (tile_index, encoded) in encoded_tiles {
284        add_encode_timing_counters_from_result(&mut timings, &encoded);
285        tile_results.insert(tile_index, encoded)?;
286    }
287    let encode_us = encode_start.elapsed().as_micros();
288    timings.htj2k_encode_us = encode_us;
289
290    let output_tiles =
291        tile_results.into_results_with_live_budget(scratch.retained_bytes()?, |result| {
292            result
293                .as_ref()
294                .map_or(Ok(0), encoded_transcode_retained_bytes)
295        })?;
296    Ok(batch_output(
297        output_tiles,
298        BatchTranscodeReport {
299            tile_count: tiles.len(),
300            successful_tiles: 0,
301            failed_tiles: 0,
302            transformed_components: dwt97_batch_jobs,
303            reversible_dwt53_batches: 0,
304            reversible_dwt53_batch_jobs: 0,
305            extract_us,
306            transform_us,
307            encode_us,
308            timings,
309            coefficient_path: options.coefficient_path,
310        },
311    ))
312}
313
314pub(super) fn batch_output(
315    tiles: Vec<Result<EncodedTranscode, JpegToHtj2kError>>,
316    mut report: BatchTranscodeReport,
317) -> EncodedTranscodeBatch {
318    report.successful_tiles = tiles.iter().filter(|tile| tile.is_ok()).count();
319    report.failed_tiles = tiles.len().saturating_sub(report.successful_tiles);
320    EncodedTranscodeBatch { tiles, report }
321}