Skip to main content

j2k_native/j2c/decode/
workspace.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Reusable decoder workspace ownership, policy, and diagnostics.
4
5use super::{DecompositionStorage, OutputRegion, TileDecodeContext};
6#[cfg(feature = "parallel")]
7use crate::HtCodeBlockDecodeWorkspace;
8#[cfg(feature = "parallel")]
9use alloc::vec::Vec;
10
11#[cfg(feature = "parallel")]
12pub(in crate::j2c::decode) struct HtTaskWorkspace {
13    pub(in crate::j2c::decode) workspace: HtCodeBlockDecodeWorkspace,
14    // Componentwise maxima make orthogonal edge shapes reusable without growth.
15    pub(in crate::j2c::decode) prepared_width: u32,
16    pub(in crate::j2c::decode) prepared_height: u32,
17}
18
19#[cfg(feature = "parallel")]
20pub(in crate::j2c::decode) fn ht_task_workspace_bytes(
21    slots: &Vec<HtTaskWorkspace>,
22) -> crate::Result<usize> {
23    let mut bytes = slots
24        .capacity()
25        .checked_mul(core::mem::size_of::<HtTaskWorkspace>())
26        .ok_or(crate::ValidationError::ImageTooLarge)?;
27    for slot in slots {
28        bytes = bytes
29            .checked_add(slot.workspace.allocated_bytes()?)
30            .ok_or(crate::ValidationError::ImageTooLarge)?;
31    }
32    Ok(bytes)
33}
34
35/// CPU parallelism policy for native JPEG 2000 decode.
36#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
37pub enum CpuDecodeParallelism {
38    /// Allow a single tile decode to use internal code-block parallelism.
39    #[default]
40    Auto,
41    /// Keep code-block decode serial for callers that already parallelize tiles.
42    Serial,
43}
44
45/// Observable counters and retained ownership for a reusable decoder workspace.
46#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
47pub struct DecoderWorkspaceStats {
48    decode_calls: u64,
49    component_owner_reuses: u64,
50    tier1_owner_reuses: u64,
51    idwt_owner_reuses: u64,
52    scratch_capacity_retries: u64,
53    retained_component_bytes: usize,
54    retained_tier1_bytes: usize,
55    retained_idwt_bytes: usize,
56}
57
58impl DecoderWorkspaceStats {
59    /// Number of native image decode calls made with this workspace.
60    #[must_use]
61    pub const fn decode_calls(self) -> u64 {
62        self.decode_calls
63    }
64
65    /// Number of calls that began with reusable decoded-component owners.
66    #[must_use]
67    pub const fn component_owner_reuses(self) -> u64 {
68        self.component_owner_reuses
69    }
70
71    /// Number of calls that began with reusable Tier-1 allocations.
72    #[must_use]
73    pub const fn tier1_owner_reuses(self) -> u64 {
74        self.tier1_owner_reuses
75    }
76
77    /// Number of calls that began with reusable IDWT allocations.
78    #[must_use]
79    pub const fn idwt_owner_reuses(self) -> u64 {
80        self.idwt_owner_reuses
81    }
82
83    /// Number of retained-scratch evictions followed by a fresh retry.
84    #[must_use]
85    pub const fn scratch_capacity_retries(self) -> u64 {
86        self.scratch_capacity_retries
87    }
88
89    /// Retained component-owner capacity after the most recent completed core decode.
90    #[must_use]
91    pub const fn retained_component_bytes(self) -> usize {
92        self.retained_component_bytes
93    }
94
95    /// Retained classic and HT Tier-1 capacity after the most recent call.
96    #[must_use]
97    pub const fn retained_tier1_bytes(self) -> usize {
98        self.retained_tier1_bytes
99    }
100
101    /// Retained floating-point and exact-integer IDWT capacity after the most recent call.
102    #[must_use]
103    pub const fn retained_idwt_bytes(self) -> usize {
104        self.retained_idwt_bytes
105    }
106
107    /// Total retained lifetime-free decode scratch after the most recent call.
108    #[must_use]
109    pub const fn retained_scratch_bytes(self) -> usize {
110        self.retained_tier1_bytes
111            .saturating_add(self.retained_idwt_bytes)
112    }
113}
114
115/// Lifetime-free allocation owner that can be moved between borrowing decoder contexts.
116///
117/// Parsed packet and tile graphs remain in [`DecoderContext`] and are always
118/// released before this value is recovered. Decoded component, Tier-1, and
119/// IDWT allocations are retained for reuse with unrelated encoded inputs.
120#[derive(Default)]
121pub struct DecoderWorkspace {
122    tile_decode_context: TileDecodeContext,
123    pub(super) cpu_decode_parallelism: CpuDecodeParallelism,
124    stats: DecoderWorkspaceStats,
125}
126
127impl DecoderWorkspace {
128    /// Return reuse counters and retained allocation sizes.
129    #[must_use]
130    pub const fn stats(&self) -> DecoderWorkspaceStats {
131        self.stats
132    }
133}
134
135/// A decoder context for decoding JPEG2000 images.
136pub struct DecoderContext<'a> {
137    pub(crate) tile_decode_context: TileDecodeContext,
138    pub(crate) storage: DecompositionStorage<'a>,
139    pub(super) cpu_decode_parallelism: CpuDecodeParallelism,
140    workspace_stats: DecoderWorkspaceStats,
141}
142
143impl Default for DecoderContext<'_> {
144    fn default() -> Self {
145        Self {
146            tile_decode_context: TileDecodeContext::default(),
147            storage: DecompositionStorage::default(),
148            cpu_decode_parallelism: CpuDecodeParallelism::Auto,
149            workspace_stats: DecoderWorkspaceStats::default(),
150        }
151    }
152}
153
154impl DecoderContext<'_> {
155    /// Create a borrowing decoder context from a lifetime-free reusable workspace.
156    #[must_use]
157    pub fn from_workspace(workspace: DecoderWorkspace) -> Self {
158        Self {
159            tile_decode_context: workspace.tile_decode_context,
160            storage: DecompositionStorage::default(),
161            cpu_decode_parallelism: workspace.cpu_decode_parallelism,
162            workspace_stats: workspace.stats,
163        }
164    }
165
166    /// Release input-borrowing graph owners and recover the reusable workspace.
167    #[must_use]
168    pub fn into_workspace(mut self) -> DecoderWorkspace {
169        self.storage.release_all_allocations();
170        DecoderWorkspace {
171            tile_decode_context: self.tile_decode_context,
172            cpu_decode_parallelism: self.cpu_decode_parallelism,
173            stats: self.workspace_stats,
174        }
175    }
176
177    /// Return reuse counters for this context's lifetime-free workspace state.
178    #[must_use]
179    pub const fn workspace_stats(&self) -> DecoderWorkspaceStats {
180        self.workspace_stats
181    }
182
183    pub(super) fn record_decode_start(
184        &mut self,
185        retained_component_bytes: usize,
186        retained_tier1_bytes: usize,
187        retained_idwt_bytes: usize,
188    ) {
189        self.workspace_stats.decode_calls = self.workspace_stats.decode_calls.saturating_add(1);
190        if retained_component_bytes != 0 {
191            self.workspace_stats.component_owner_reuses = self
192                .workspace_stats
193                .component_owner_reuses
194                .saturating_add(1);
195        }
196        if retained_tier1_bytes != 0 {
197            self.workspace_stats.tier1_owner_reuses =
198                self.workspace_stats.tier1_owner_reuses.saturating_add(1);
199        }
200        if retained_idwt_bytes != 0 {
201            self.workspace_stats.idwt_owner_reuses =
202                self.workspace_stats.idwt_owner_reuses.saturating_add(1);
203        }
204    }
205
206    pub(super) fn record_scratch_capacity_retry(&mut self) {
207        self.workspace_stats.scratch_capacity_retries = self
208            .workspace_stats
209            .scratch_capacity_retries
210            .saturating_add(1);
211    }
212
213    pub(super) fn record_decode_complete(
214        &mut self,
215        retained_component_bytes: usize,
216        retained_tier1_bytes: usize,
217        retained_idwt_bytes: usize,
218    ) {
219        self.workspace_stats.retained_component_bytes = retained_component_bytes;
220        self.workspace_stats.retained_tier1_bytes = retained_tier1_bytes;
221        self.workspace_stats.retained_idwt_bytes = retained_idwt_bytes;
222    }
223
224    pub(crate) fn release_reusable_allocations(&mut self) {
225        self.tile_decode_context.release_all_allocations();
226        self.storage.release_all_allocations();
227    }
228
229    pub(crate) fn set_output_region(&mut self, output_region: Option<(u32, u32, u32, u32)>) {
230        self.tile_decode_context.output_region = output_region.map(OutputRegion::from_tuple);
231    }
232
233    pub(crate) fn set_round_irreversible_output(&mut self, enabled: bool) {
234        self.tile_decode_context.round_irreversible_output = enabled;
235    }
236
237    /// Return the native CPU decode parallelism policy.
238    #[must_use]
239    pub fn cpu_decode_parallelism(&self) -> CpuDecodeParallelism {
240        self.cpu_decode_parallelism
241    }
242
243    /// Set the native CPU decode parallelism policy.
244    pub fn set_cpu_decode_parallelism(&mut self, parallelism: CpuDecodeParallelism) {
245        self.cpu_decode_parallelism = parallelism;
246    }
247}