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