1use crate::allocation::{
4 checked_add_allocation_bytes, checked_allocation_bytes, ensure_allocation_bytes,
5 try_reserve_for_len_with_live_budget,
6};
7use crate::decoder::Decoder;
8use crate::error::JpegError;
9use crate::info::{ColorSpace, SofKind};
10use crate::internal::checkpoint::{
11 build_checkpoint_plan_from_validated_with_live_budget, checkpoint_count_summary, total_mcus,
12 validate_scan_bytes, DeviceCheckpoint,
13};
14use crate::Warning;
15use alloc::borrow::Cow;
16use alloc::vec::Vec;
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19#[doc(hidden)]
20pub struct DeviceComponentPlan {
22 pub h: u8,
24 pub v: u8,
26 pub output_index: usize,
28}
29
30#[derive(Debug, PartialEq, Eq)]
31#[doc(hidden)]
32pub struct DeviceDecodePlan<'a> {
38 pub dimensions: (u32, u32),
40 pub color_space: ColorSpace,
42 pub restart_interval: Option<u16>,
44 pub warnings: Vec<Warning>,
46 pub scan_bytes: Cow<'a, [u8]>,
48 pub components: Vec<DeviceComponentPlan>,
50 pub checkpoints: Vec<DeviceCheckpoint>,
52 pub matches_fast_420: bool,
54 pub matches_fast_422: bool,
56 pub matches_fast_444: bool,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub struct DeviceBatchSummary {
63 pub restart_interval: Option<u16>,
65 pub checkpoint_count: usize,
67 pub matches_fast_420: bool,
69 pub matches_fast_422: bool,
71 pub matches_fast_444: bool,
73}
74
75#[doc(hidden)]
77pub fn build_device_plan<'a>(
78 decoder: &'a Decoder<'a>,
79 cadence_mcus: u32,
80) -> Result<DeviceDecodePlan<'a>, JpegError> {
81 if !matches!(
82 decoder.info().sof_kind,
83 SofKind::Baseline8 | SofKind::Extended8
84 ) {
85 return Err(JpegError::NotImplemented {
86 sof: decoder.info().sof_kind,
87 });
88 }
89 let plan = &decoder.plan;
90 let restart_interval = plan.restart_interval.filter(|&interval| interval > 0);
91 let validated_scan = validate_scan_bytes(
92 &decoder.bytes[plan.scan_offset..],
93 restart_interval.is_some(),
94 plan.scan_offset,
95 )?;
96 let scan_bytes = Cow::Borrowed(validated_scan.payload());
97 let missing_eoi = validated_scan.is_missing_eoi();
98 let warning_count = decoder
99 .warnings
100 .len()
101 .checked_add(usize::from(missing_eoi))
102 .ok_or(JpegError::MemoryCapExceeded {
103 requested: usize::MAX,
104 cap: j2k_core::DEFAULT_MAX_HOST_ALLOCATION_BYTES,
105 })?;
106 let expected_checkpoint_count = checkpoint_count_summary(
107 total_mcus(plan),
108 cadence_mcus.max(1),
109 restart_interval.map(u32::from),
110 );
111 let retained_decoder_bytes = retained_decoder_allocation_bytes(decoder)?;
112 let output_bytes = device_plan_output_allocation_bytes(
113 expected_checkpoint_count,
114 warning_count,
115 plan.components.len(),
116 )?;
117 let terminated_copy_bytes =
118 validated_scan.terminated_copy_len(j2k_core::DEFAULT_MAX_HOST_ALLOCATION_BYTES)?;
119 ensure_allocation_bytes(checked_add_allocation_bytes(
120 checked_add_allocation_bytes(retained_decoder_bytes, output_bytes)?,
121 terminated_copy_bytes,
122 )?)?;
123
124 let mut live_bytes = retained_decoder_bytes;
125 let mut warnings = Vec::new();
126 try_reserve_for_len_with_live_budget(
127 &mut warnings,
128 warning_count,
129 &mut live_bytes,
130 j2k_core::DEFAULT_MAX_HOST_ALLOCATION_BYTES,
131 )?;
132 warnings.extend_from_slice(&decoder.warnings);
133 if missing_eoi {
134 warnings.push(Warning::MissingEoi);
135 }
136
137 let mut components = Vec::new();
138 try_reserve_for_len_with_live_budget(
139 &mut components,
140 plan.components.len(),
141 &mut live_bytes,
142 j2k_core::DEFAULT_MAX_HOST_ALLOCATION_BYTES,
143 )?;
144 components.extend(plan.components.iter().map(|component| DeviceComponentPlan {
145 h: component.h,
146 v: component.v,
147 output_index: component.output_index,
148 }));
149 let checkpoints = build_checkpoint_plan_from_validated_with_live_budget(
150 plan,
151 validated_scan,
152 cadence_mcus,
153 &mut live_bytes,
154 j2k_core::DEFAULT_MAX_HOST_ALLOCATION_BYTES,
155 )?;
156 if checkpoints.len() != expected_checkpoint_count {
157 return Err(JpegError::InternalInvariant {
158 reason: "device checkpoint count disagrees with its allocation plan",
159 });
160 }
161
162 Ok(DeviceDecodePlan {
163 dimensions: plan.dimensions,
164 color_space: plan.color_space,
165 restart_interval,
166 warnings,
167 scan_bytes,
168 components,
169 checkpoints,
170 matches_fast_420: plan.matches_fast_tile_shape(),
171 matches_fast_422: plan.matches_fast_rgb422_shape(),
172 matches_fast_444: plan.matches_fast_rgb444_shape(),
173 })
174}
175
176pub(super) fn retained_decoder_allocation_bytes(decoder: &Decoder<'_>) -> Result<usize, JpegError> {
177 let baseline = decoder.retained_allocation_bytes_excluding_cpu_checkpoint_cache()?;
178 let checkpoint_cache_bytes = decoder
179 .cpu_entropy_checkpoints
180 .lock()
181 .map_err(|_| JpegError::InternalInvariant {
182 reason: "CPU entropy checkpoint cache mutex poisoned",
183 })?
184 .retained_allocation_bytes()?;
185 checked_add_allocation_bytes(baseline, checkpoint_cache_bytes)
186}
187
188fn device_plan_output_allocation_bytes(
189 checkpoint_count: usize,
190 warning_count: usize,
191 component_count: usize,
192) -> Result<usize, JpegError> {
193 let mut total = checked_allocation_bytes::<DeviceCheckpoint>(checkpoint_count)?;
194 total =
195 checked_add_allocation_bytes(total, checked_allocation_bytes::<Warning>(warning_count)?)?;
196 checked_add_allocation_bytes(
197 total,
198 checked_allocation_bytes::<DeviceComponentPlan>(component_count)?,
199 )
200}
201
202#[doc(hidden)]
204pub fn summarize_device_batch(decoder: &Decoder<'_>, cadence_mcus: u32) -> DeviceBatchSummary {
205 if !matches!(
206 decoder.info().sof_kind,
207 SofKind::Baseline8 | SofKind::Extended8
208 ) {
209 return DeviceBatchSummary {
210 restart_interval: None,
211 checkpoint_count: 0,
212 matches_fast_420: false,
213 matches_fast_422: false,
214 matches_fast_444: false,
215 };
216 }
217 let plan = &decoder.plan;
218 let restart_interval = plan.restart_interval.filter(|&interval| interval > 0);
219 let checkpoint_count = checkpoint_count_summary(
220 total_mcus(plan),
221 cadence_mcus,
222 restart_interval.map(u32::from),
223 );
224
225 DeviceBatchSummary {
226 restart_interval,
227 checkpoint_count,
228 matches_fast_420: plan.matches_fast_tile_shape(),
229 matches_fast_422: plan.matches_fast_rgb422_shape(),
230 matches_fast_444: plan.matches_fast_rgb444_shape(),
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 #[test]
239 fn device_plan_output_rejects_aggregate_retained_vectors() {
240 let cap = j2k_core::DEFAULT_MAX_HOST_ALLOCATION_BYTES;
241 let checkpoint_count = (cap * 3 / 5) / core::mem::size_of::<DeviceCheckpoint>();
242 let warning_count = (cap * 3 / 5) / core::mem::size_of::<Warning>();
243 assert!(checked_allocation_bytes::<DeviceCheckpoint>(checkpoint_count).is_ok());
244 assert!(checked_allocation_bytes::<Warning>(warning_count).is_ok());
245 assert!(matches!(
246 device_plan_output_allocation_bytes(checkpoint_count, warning_count, 0),
247 Err(JpegError::MemoryCapExceeded { requested, cap: limit })
248 if requested > limit && limit == cap
249 ));
250 }
251
252 #[test]
253 fn device_plan_output_boundary_counts_all_three_payloads() {
254 let checkpoints = 3usize;
255 let warnings = 2usize;
256 let components = 4usize;
257 let expected = checkpoints * core::mem::size_of::<DeviceCheckpoint>()
258 + warnings * core::mem::size_of::<Warning>()
259 + components * core::mem::size_of::<DeviceComponentPlan>();
260 assert_eq!(
261 device_plan_output_allocation_bytes(checkpoints, warnings, components).unwrap(),
262 expected
263 );
264 }
265
266 #[test]
267 fn retained_decoder_bytes_include_populated_cpu_checkpoint_cache() {
268 let bytes = j2k_test_support::minimal_baseline_jpeg();
269 let decoder = Decoder::new(&bytes).unwrap();
270 let before = retained_decoder_allocation_bytes(&decoder).unwrap();
271 let retained_checkpoint_bytes = {
272 let mut cache = decoder.cpu_entropy_checkpoints.lock().unwrap();
273 cache.checkpoints.try_reserve_exact(4).unwrap();
274 cache.retained_allocation_bytes().unwrap()
275 };
276 let after = retained_decoder_allocation_bytes(&decoder).unwrap();
277
278 assert!(retained_checkpoint_bytes >= 4 * core::mem::size_of::<DeviceCheckpoint>());
279 assert_eq!(after, before + retained_checkpoint_bytes);
280 }
281}