1use crate::allocation::{
18 checked_add_allocation_bytes, checked_allocation_bytes, try_reserve_for_len_with_live_budget,
19};
20use crate::entropy::sequential::{PreparedDecodePlan, StripeBuffer, StripeLayout};
21use crate::error::JpegError;
22use alloc::vec::Vec;
23use j2k_core::ScratchPool as CoreScratchPool;
24
25#[derive(Debug, Default)]
26pub(crate) struct YCbCr420Rows {
27 pub(crate) cb_top: Vec<u8>,
28 pub(crate) cb_bot: Vec<u8>,
29 pub(crate) cr_top: Vec<u8>,
30 pub(crate) cr_bot: Vec<u8>,
31}
32
33#[derive(Debug, Default)]
34pub(crate) struct YCbCrGenericRows {
35 pub(crate) cb_up: Vec<u8>,
36 pub(crate) cr_up: Vec<u8>,
37}
38
39#[derive(Debug, Default)]
40pub(crate) struct RgbGenericRows {
41 pub(crate) r: Vec<u8>,
42 pub(crate) g: Vec<u8>,
43 pub(crate) b: Vec<u8>,
44 pub(crate) k: Vec<u8>,
45}
46
47#[derive(Debug, Default)]
48pub(crate) struct SinkRows {
49 pub(crate) top_row: Vec<u8>,
50 pub(crate) bottom_row: Vec<u8>,
51}
52
53#[derive(Debug, Default)]
58pub struct ScratchPool {
59 pub(crate) prev_dc: Vec<i32>,
60 pub(crate) stripe_a: StripeBuffer,
61 pub(crate) stripe_b: StripeBuffer,
62 pub(crate) stripe_c: StripeBuffer,
63 pub(crate) ycbcr_420_rows: YCbCr420Rows,
64 pub(crate) ycbcr_generic_rows: YCbCrGenericRows,
65 pub(crate) rgb_generic_rows: RgbGenericRows,
66 pub(crate) lossless_prev_row: Vec<u8>,
67 pub(crate) lossless_curr_row: Vec<u8>,
68 sink_rows: SinkRows,
69 detached_sink_bytes: usize,
70}
71
72#[derive(Clone, Copy)]
73struct SequentialScratchLayout {
74 stripe: StripeLayout,
75 component_count: usize,
76 row_width: usize,
77}
78
79impl SequentialScratchLayout {
80 fn for_plan(
81 plan: &PreparedDecodePlan,
82 mcus_per_row: u32,
83 block_size: u32,
84 ) -> Result<Self, JpegError> {
85 Ok(Self {
86 stripe: StripeLayout::for_plan(plan, mcus_per_row, block_size)?,
87 component_count: plan.sampling.len(),
88 row_width: plan.dimensions.0.div_ceil(8 / block_size.max(1)) as usize,
89 })
90 }
91
92 fn minimum_bytes(self, detached_sink_bytes: usize) -> Result<usize, JpegError> {
93 let mut total = checked_allocation_bytes::<i32>(self.component_count)?;
94 let stripe_bytes = self.stripe.allocation_bytes()?;
95 for _ in 0..3 {
96 total = checked_add_allocation_bytes(total, stripe_bytes)?;
97 }
98 let row_bytes = self
99 .row_width
100 .checked_mul(10)
101 .ok_or(JpegError::MemoryCapExceeded {
102 requested: usize::MAX,
103 cap: j2k_core::DEFAULT_MAX_HOST_ALLOCATION_BYTES,
104 })?;
105 total = checked_add_allocation_bytes(total, row_bytes)?;
106 checked_add_allocation_bytes(total, detached_sink_bytes)
107 }
108}
109
110impl ScratchPool {
111 #[must_use]
115 pub fn new() -> Self {
116 Self::default()
117 }
118
119 pub(crate) fn prepare_for(
122 &mut self,
123 plan: &PreparedDecodePlan,
124 mcus_per_row: u32,
125 block_size: u32,
126 max_bytes: usize,
127 ) -> Result<(), JpegError> {
128 let layout = SequentialScratchLayout::for_plan(plan, mcus_per_row, block_size)?;
129 let target_bytes = layout.minimum_bytes(self.detached_sink_bytes)?;
130 ensure_request_bytes(target_bytes, max_bytes)?;
131 if self.retained_bytes() > target_bytes
132 || self.projected_sequential_bytes(layout) > target_bytes
133 || self.sequential_requires_growth(layout)
134 {
135 self.release_retained_allocations();
136 }
137 ensure_request_bytes(self.projected_sequential_bytes(layout), target_bytes)?;
138 if let Err(error) = self.reserve_sequential_storage(layout, target_bytes) {
139 self.release_retained_allocations();
140 return Err(error);
141 }
142
143 self.prev_dc.resize(layout.component_count, 0);
144 resize_rows(&mut self.ycbcr_420_rows, layout.row_width);
145 resize_generic_rows(&mut self.ycbcr_generic_rows, layout.row_width);
146 resize_rgb_rows(&mut self.rgb_generic_rows, layout.row_width);
147 for dc in &mut self.prev_dc {
148 *dc = 0;
149 }
150 self.ensure_retained_capacity(target_bytes)?;
151 Ok(())
152 }
153
154 pub(crate) fn take_sink_rows(
155 &mut self,
156 row_len: usize,
157 max_bytes: usize,
158 ) -> Result<SinkRows, JpegError> {
159 if self.detached_sink_bytes != 0 {
160 return Err(JpegError::InternalInvariant {
161 reason: "scratch sink rows are already detached",
162 });
163 }
164 let sink_projected = projected_vec_bytes(&self.sink_rows.top_row, row_len)
165 .saturating_add(projected_vec_bytes(&self.sink_rows.bottom_row, row_len));
166 let without_sink = self
167 .retained_bytes()
168 .saturating_sub(vec_bytes(&self.sink_rows.top_row))
169 .saturating_sub(vec_bytes(&self.sink_rows.bottom_row));
170 if without_sink.saturating_add(sink_projected) > max_bytes
171 || self.sink_requires_growth(row_len)
172 {
173 self.release_retained_allocations();
174 }
175 let minimum = row_len.checked_mul(2).ok_or(JpegError::MemoryCapExceeded {
176 requested: usize::MAX,
177 cap: max_bytes,
178 })?;
179 ensure_request_bytes(minimum, max_bytes)?;
180 let mut live_bytes = self.retained_bytes();
181 let reserve_result = (|| {
182 try_reserve_for_len_with_live_budget(
183 &mut self.sink_rows.top_row,
184 row_len,
185 &mut live_bytes,
186 max_bytes,
187 )?;
188 try_reserve_for_len_with_live_budget(
189 &mut self.sink_rows.bottom_row,
190 row_len,
191 &mut live_bytes,
192 max_bytes,
193 )
194 })();
195 if let Err(error) = reserve_result {
196 self.release_retained_allocations();
197 return Err(error);
198 }
199 self.sink_rows.top_row.resize(row_len, 0);
200 self.sink_rows.bottom_row.resize(row_len, 0);
201 self.ensure_retained_capacity(max_bytes)?;
202 let rows = core::mem::take(&mut self.sink_rows);
203 self.detached_sink_bytes =
204 vec_bytes(&rows.top_row).saturating_add(vec_bytes(&rows.bottom_row));
205 Ok(rows)
206 }
207
208 pub(crate) fn prepare_lossless_rows(
209 &mut self,
210 predictor_row_len: usize,
211 sink_row_len: usize,
212 max_bytes: usize,
213 ) -> Result<SinkRows, JpegError> {
214 if self.detached_sink_bytes != 0 {
215 return Err(JpegError::InternalInvariant {
216 reason: "scratch sink rows are already detached",
217 });
218 }
219 if sink_row_len == 0 {
220 self.sink_rows = SinkRows::default();
221 }
222 let minimum = predictor_row_len
223 .checked_mul(2)
224 .and_then(|bytes| {
225 sink_row_len
226 .checked_mul(2)
227 .and_then(|sink| bytes.checked_add(sink))
228 })
229 .ok_or(JpegError::MemoryCapExceeded {
230 requested: usize::MAX,
231 cap: max_bytes,
232 })?;
233 ensure_request_bytes(minimum, max_bytes)?;
234 if self.projected_lossless_bytes(predictor_row_len, sink_row_len) > max_bytes
235 || self.lossless_requires_growth(predictor_row_len, sink_row_len)
236 {
237 self.release_retained_allocations();
238 }
239 ensure_request_bytes(
240 self.projected_lossless_bytes(predictor_row_len, sink_row_len),
241 max_bytes,
242 )?;
243 let mut live_bytes = self.retained_bytes();
244 let reserve_result = (|| {
245 try_reserve_for_len_with_live_budget(
246 &mut self.lossless_prev_row,
247 predictor_row_len,
248 &mut live_bytes,
249 max_bytes,
250 )?;
251 try_reserve_for_len_with_live_budget(
252 &mut self.lossless_curr_row,
253 predictor_row_len,
254 &mut live_bytes,
255 max_bytes,
256 )?;
257 try_reserve_for_len_with_live_budget(
258 &mut self.sink_rows.top_row,
259 sink_row_len,
260 &mut live_bytes,
261 max_bytes,
262 )?;
263 try_reserve_for_len_with_live_budget(
264 &mut self.sink_rows.bottom_row,
265 sink_row_len,
266 &mut live_bytes,
267 max_bytes,
268 )
269 })();
270 if let Err(error) = reserve_result {
271 self.release_retained_allocations();
272 return Err(error);
273 }
274 self.lossless_prev_row.resize(predictor_row_len, 0);
275 self.lossless_curr_row.resize(predictor_row_len, 0);
276 self.sink_rows.top_row.resize(sink_row_len, 0);
277 self.sink_rows.bottom_row.resize(sink_row_len, 0);
278 self.ensure_retained_capacity(max_bytes)?;
279 let rows = core::mem::take(&mut self.sink_rows);
280 self.detached_sink_bytes =
281 vec_bytes(&rows.top_row).saturating_add(vec_bytes(&rows.bottom_row));
282 Ok(rows)
283 }
284
285 pub(crate) fn restore_sink_rows(&mut self, rows: SinkRows) {
286 self.sink_rows = rows;
287 self.detached_sink_bytes = 0;
288 }
289
290 pub(crate) const fn detached_sink_bytes(&self) -> usize {
291 self.detached_sink_bytes
292 }
293
294 pub(crate) fn reconcile_external_workspace(
295 &mut self,
296 external_bytes: usize,
297 max_bytes: usize,
298 ) -> Result<(), JpegError> {
299 ensure_request_bytes(external_bytes, max_bytes)?;
300 if self.retained_bytes().saturating_add(external_bytes) > max_bytes {
301 self.release_retained_allocations();
302 }
303 ensure_request_bytes(
304 self.retained_bytes().saturating_add(external_bytes),
305 max_bytes,
306 )
307 }
308
309 pub(crate) fn release_for_external_workspace(
310 &mut self,
311 external_bytes: usize,
312 max_bytes: usize,
313 ) -> Result<(), JpegError> {
314 ensure_request_bytes(external_bytes, max_bytes)?;
315 self.release_retained_allocations();
316 ensure_request_bytes(
317 self.detached_sink_bytes.saturating_add(external_bytes),
318 max_bytes,
319 )
320 }
321
322 pub(crate) fn retained_bytes(&self) -> usize {
323 let mut total = vec_bytes(&self.prev_dc);
324 total = total
325 .saturating_add(self.stripe_a.retained_bytes())
326 .saturating_add(self.stripe_b.retained_bytes())
327 .saturating_add(self.stripe_c.retained_bytes())
328 .saturating_add(rows_bytes(&self.ycbcr_420_rows))
329 .saturating_add(generic_rows_bytes(&self.ycbcr_generic_rows))
330 .saturating_add(rgb_rows_bytes(&self.rgb_generic_rows))
331 .saturating_add(vec_bytes(&self.lossless_prev_row))
332 .saturating_add(vec_bytes(&self.lossless_curr_row))
333 .saturating_add(vec_bytes(&self.sink_rows.top_row))
334 .saturating_add(vec_bytes(&self.sink_rows.bottom_row))
335 .saturating_add(self.detached_sink_bytes);
336 total
337 }
338
339 fn projected_sequential_bytes(&self, layout: SequentialScratchLayout) -> usize {
340 let mut total = projected_vec_bytes(&self.prev_dc, layout.component_count);
341 total = total
342 .saturating_add(self.stripe_a.projected_bytes(layout.stripe))
343 .saturating_add(self.stripe_b.projected_bytes(layout.stripe))
344 .saturating_add(self.stripe_c.projected_bytes(layout.stripe))
345 .saturating_add(projected_rows_bytes(&self.ycbcr_420_rows, layout.row_width))
346 .saturating_add(projected_generic_rows_bytes(
347 &self.ycbcr_generic_rows,
348 layout.row_width,
349 ))
350 .saturating_add(projected_rgb_rows_bytes(
351 &self.rgb_generic_rows,
352 layout.row_width,
353 ))
354 .saturating_add(vec_bytes(&self.lossless_prev_row))
355 .saturating_add(vec_bytes(&self.lossless_curr_row))
356 .saturating_add(vec_bytes(&self.sink_rows.top_row))
357 .saturating_add(vec_bytes(&self.sink_rows.bottom_row))
358 .saturating_add(self.detached_sink_bytes);
359 total
360 }
361
362 fn projected_lossless_bytes(&self, predictor_row_len: usize, sink_row_len: usize) -> usize {
363 self.retained_bytes()
364 .saturating_sub(vec_bytes(&self.lossless_prev_row))
365 .saturating_sub(vec_bytes(&self.lossless_curr_row))
366 .saturating_sub(vec_bytes(&self.sink_rows.top_row))
367 .saturating_sub(vec_bytes(&self.sink_rows.bottom_row))
368 .saturating_add(projected_vec_bytes(
369 &self.lossless_prev_row,
370 predictor_row_len,
371 ))
372 .saturating_add(projected_vec_bytes(
373 &self.lossless_curr_row,
374 predictor_row_len,
375 ))
376 .saturating_add(projected_vec_bytes(&self.sink_rows.top_row, sink_row_len))
377 .saturating_add(projected_vec_bytes(
378 &self.sink_rows.bottom_row,
379 sink_row_len,
380 ))
381 }
382
383 fn reserve_sequential_storage(
384 &mut self,
385 layout: SequentialScratchLayout,
386 cap: usize,
387 ) -> Result<(), JpegError> {
388 let mut live_bytes = self.retained_bytes();
389 try_reserve_for_len_with_live_budget(
390 &mut self.prev_dc,
391 layout.component_count,
392 &mut live_bytes,
393 cap,
394 )?;
395 reserve_rows(
396 &mut self.ycbcr_420_rows,
397 layout.row_width,
398 &mut live_bytes,
399 cap,
400 )?;
401 reserve_generic_rows(
402 &mut self.ycbcr_generic_rows,
403 layout.row_width,
404 &mut live_bytes,
405 cap,
406 )?;
407 reserve_rgb_rows(
408 &mut self.rgb_generic_rows,
409 layout.row_width,
410 &mut live_bytes,
411 cap,
412 )?;
413 self.stripe_a
414 .resize_for(layout.stripe, &mut live_bytes, cap)?;
415 self.stripe_b
416 .resize_for(layout.stripe, &mut live_bytes, cap)?;
417 self.stripe_c
418 .resize_for(layout.stripe, &mut live_bytes, cap)?;
419 ensure_request_bytes(live_bytes, cap)
420 }
421
422 fn sequential_requires_growth(&self, layout: SequentialScratchLayout) -> bool {
423 self.prev_dc.capacity() < layout.component_count
424 || rows_require_growth(&self.ycbcr_420_rows, layout.row_width)
425 || generic_rows_require_growth(&self.ycbcr_generic_rows, layout.row_width)
426 || rgb_rows_require_growth(&self.rgb_generic_rows, layout.row_width)
427 || self.stripe_a.requires_growth(layout.stripe)
428 || self.stripe_b.requires_growth(layout.stripe)
429 || self.stripe_c.requires_growth(layout.stripe)
430 }
431
432 fn sink_requires_growth(&self, row_len: usize) -> bool {
433 self.sink_rows.top_row.capacity() < row_len
434 || self.sink_rows.bottom_row.capacity() < row_len
435 }
436
437 fn lossless_requires_growth(&self, predictor_row_len: usize, sink_row_len: usize) -> bool {
438 self.lossless_prev_row.capacity() < predictor_row_len
439 || self.lossless_curr_row.capacity() < predictor_row_len
440 || self.sink_rows.top_row.capacity() < sink_row_len
441 || self.sink_rows.bottom_row.capacity() < sink_row_len
442 }
443
444 fn release_retained_allocations(&mut self) {
445 self.prev_dc = Vec::new();
446 self.stripe_a = StripeBuffer::default();
447 self.stripe_b = StripeBuffer::default();
448 self.stripe_c = StripeBuffer::default();
449 self.ycbcr_420_rows = YCbCr420Rows::default();
450 self.ycbcr_generic_rows = YCbCrGenericRows::default();
451 self.rgb_generic_rows = RgbGenericRows::default();
452 self.lossless_prev_row = Vec::new();
453 self.lossless_curr_row = Vec::new();
454 self.sink_rows = SinkRows::default();
455 }
456
457 fn ensure_retained_capacity(&mut self, cap: usize) -> Result<(), JpegError> {
458 let requested = self.retained_bytes();
459 if requested > cap {
460 self.release_retained_allocations();
461 return Err(JpegError::MemoryCapExceeded { requested, cap });
462 }
463 Ok(())
464 }
465}
466
467fn ensure_request_bytes(requested: usize, cap: usize) -> Result<(), JpegError> {
468 if requested > cap {
469 return Err(JpegError::MemoryCapExceeded { requested, cap });
470 }
471 Ok(())
472}
473
474fn vec_bytes<T>(vec: &Vec<T>) -> usize {
475 vec.capacity().saturating_mul(core::mem::size_of::<T>())
476}
477
478fn projected_vec_bytes<T>(vec: &Vec<T>, target_len: usize) -> usize {
479 vec.capacity()
480 .max(target_len)
481 .saturating_mul(core::mem::size_of::<T>())
482}
483
484fn reserve_rows(
485 rows: &mut YCbCr420Rows,
486 width: usize,
487 live_bytes: &mut usize,
488 cap: usize,
489) -> Result<(), JpegError> {
490 try_reserve_for_len_with_live_budget(&mut rows.cb_top, width, live_bytes, cap)?;
491 try_reserve_for_len_with_live_budget(&mut rows.cb_bot, width, live_bytes, cap)?;
492 try_reserve_for_len_with_live_budget(&mut rows.cr_top, width, live_bytes, cap)?;
493 try_reserve_for_len_with_live_budget(&mut rows.cr_bot, width, live_bytes, cap)
494}
495
496fn resize_rows(rows: &mut YCbCr420Rows, width: usize) {
497 rows.cb_top.resize(width, 0);
498 rows.cb_bot.resize(width, 0);
499 rows.cr_top.resize(width, 0);
500 rows.cr_bot.resize(width, 0);
501}
502
503fn rows_bytes(rows: &YCbCr420Rows) -> usize {
504 vec_bytes(&rows.cb_top)
505 .saturating_add(vec_bytes(&rows.cb_bot))
506 .saturating_add(vec_bytes(&rows.cr_top))
507 .saturating_add(vec_bytes(&rows.cr_bot))
508}
509
510fn projected_rows_bytes(rows: &YCbCr420Rows, width: usize) -> usize {
511 projected_vec_bytes(&rows.cb_top, width)
512 .saturating_add(projected_vec_bytes(&rows.cb_bot, width))
513 .saturating_add(projected_vec_bytes(&rows.cr_top, width))
514 .saturating_add(projected_vec_bytes(&rows.cr_bot, width))
515}
516
517fn rows_require_growth(rows: &YCbCr420Rows, width: usize) -> bool {
518 rows.cb_top.capacity() < width
519 || rows.cb_bot.capacity() < width
520 || rows.cr_top.capacity() < width
521 || rows.cr_bot.capacity() < width
522}
523
524fn reserve_generic_rows(
525 rows: &mut YCbCrGenericRows,
526 width: usize,
527 live_bytes: &mut usize,
528 cap: usize,
529) -> Result<(), JpegError> {
530 try_reserve_for_len_with_live_budget(&mut rows.cb_up, width, live_bytes, cap)?;
531 try_reserve_for_len_with_live_budget(&mut rows.cr_up, width, live_bytes, cap)
532}
533
534fn resize_generic_rows(rows: &mut YCbCrGenericRows, width: usize) {
535 rows.cb_up.resize(width, 0);
536 rows.cr_up.resize(width, 0);
537}
538
539fn generic_rows_bytes(rows: &YCbCrGenericRows) -> usize {
540 vec_bytes(&rows.cb_up).saturating_add(vec_bytes(&rows.cr_up))
541}
542
543fn projected_generic_rows_bytes(rows: &YCbCrGenericRows, width: usize) -> usize {
544 projected_vec_bytes(&rows.cb_up, width).saturating_add(projected_vec_bytes(&rows.cr_up, width))
545}
546
547fn generic_rows_require_growth(rows: &YCbCrGenericRows, width: usize) -> bool {
548 rows.cb_up.capacity() < width || rows.cr_up.capacity() < width
549}
550
551fn reserve_rgb_rows(
552 rows: &mut RgbGenericRows,
553 width: usize,
554 live_bytes: &mut usize,
555 cap: usize,
556) -> Result<(), JpegError> {
557 try_reserve_for_len_with_live_budget(&mut rows.r, width, live_bytes, cap)?;
558 try_reserve_for_len_with_live_budget(&mut rows.g, width, live_bytes, cap)?;
559 try_reserve_for_len_with_live_budget(&mut rows.b, width, live_bytes, cap)?;
560 try_reserve_for_len_with_live_budget(&mut rows.k, width, live_bytes, cap)
561}
562
563fn resize_rgb_rows(rows: &mut RgbGenericRows, width: usize) {
564 rows.r.resize(width, 0);
565 rows.g.resize(width, 0);
566 rows.b.resize(width, 0);
567 rows.k.resize(width, 0);
568}
569
570fn rgb_rows_bytes(rows: &RgbGenericRows) -> usize {
571 vec_bytes(&rows.r)
572 .saturating_add(vec_bytes(&rows.g))
573 .saturating_add(vec_bytes(&rows.b))
574 .saturating_add(vec_bytes(&rows.k))
575}
576
577fn projected_rgb_rows_bytes(rows: &RgbGenericRows, width: usize) -> usize {
578 projected_vec_bytes(&rows.r, width)
579 .saturating_add(projected_vec_bytes(&rows.g, width))
580 .saturating_add(projected_vec_bytes(&rows.b, width))
581 .saturating_add(projected_vec_bytes(&rows.k, width))
582}
583
584fn rgb_rows_require_growth(rows: &RgbGenericRows, width: usize) -> bool {
585 rows.r.capacity() < width
586 || rows.g.capacity() < width
587 || rows.b.capacity() < width
588 || rows.k.capacity() < width
589}
590
591#[doc(hidden)]
592impl CoreScratchPool for ScratchPool {
593 fn bytes_allocated(&self) -> usize {
594 self.retained_bytes()
595 }
596
597 fn reset(&mut self) {
598 fn clear_stripe(stripe: &mut StripeBuffer) {
599 for plane in &mut stripe.planes {
600 plane.clear();
601 }
602 stripe.plane_strides.clear();
603 stripe.plane_rows.clear();
604 }
605
606 self.prev_dc.clear();
607 clear_stripe(&mut self.stripe_a);
608 clear_stripe(&mut self.stripe_b);
609 clear_stripe(&mut self.stripe_c);
610 self.ycbcr_420_rows.cb_top.clear();
611 self.ycbcr_420_rows.cb_bot.clear();
612 self.ycbcr_420_rows.cr_top.clear();
613 self.ycbcr_420_rows.cr_bot.clear();
614 self.ycbcr_generic_rows.cb_up.clear();
615 self.ycbcr_generic_rows.cr_up.clear();
616 self.rgb_generic_rows.r.clear();
617 self.rgb_generic_rows.g.clear();
618 self.rgb_generic_rows.b.clear();
619 self.rgb_generic_rows.k.clear();
620 self.lossless_prev_row.clear();
621 self.lossless_curr_row.clear();
622 self.sink_rows.top_row.clear();
623 self.sink_rows.bottom_row.clear();
624 }
625}
626
627#[cfg(test)]
628mod tests {
629 use super::{JpegError, ScratchPool};
630 use alloc::vec::Vec;
631
632 #[test]
633 fn sink_rows_have_an_exact_aggregate_cap_boundary() {
634 let mut pool = ScratchPool::new();
635 let rows = pool.take_sink_rows(8, 16).expect("exact boundary");
636 assert_eq!(rows.top_row.len(), 8);
637 assert_eq!(rows.bottom_row.len(), 8);
638 pool.restore_sink_rows(rows);
639
640 let mut pool = ScratchPool::new();
641 assert!(matches!(
642 pool.take_sink_rows(9, 17),
643 Err(JpegError::MemoryCapExceeded {
644 requested: 18,
645 cap: 17
646 })
647 ));
648 }
649
650 #[test]
651 fn same_or_smaller_sink_rows_reuse_capacity_within_cap() {
652 let mut pool = ScratchPool::new();
653 let rows = pool.take_sink_rows(8, 16).expect("seed sink rows");
654 let top_ptr = rows.top_row.as_ptr();
655 let bottom_ptr = rows.bottom_row.as_ptr();
656 pool.restore_sink_rows(rows);
657
658 let rows = pool.take_sink_rows(4, 16).expect("reuse smaller rows");
659 assert_eq!(rows.top_row.as_ptr(), top_ptr);
660 assert_eq!(rows.bottom_row.as_ptr(), bottom_ptr);
661 }
662
663 #[test]
664 fn stale_capacity_is_released_instead_of_rejecting_a_valid_request() {
665 let mut pool = ScratchPool::new();
666 let rows = pool.take_sink_rows(32, 64).expect("seed retained rows");
667 pool.restore_sink_rows(rows);
668 assert!(pool.retained_bytes() >= 64);
669
670 pool.reconcile_external_workspace(64, 64)
671 .expect("stale capacity must not change request acceptance");
672 assert_eq!(pool.retained_bytes(), 0);
673 }
674
675 #[test]
676 fn detached_rows_remain_part_of_the_live_pool_budget() {
677 let mut pool = ScratchPool::new();
678 let rows = pool.take_sink_rows(8, 16).expect("detach rows");
679 assert!(pool.retained_bytes() >= 16);
680 pool.restore_sink_rows(rows);
681 }
682
683 #[test]
684 fn actual_retained_capacity_is_checked_and_released() {
685 let mut pool = ScratchPool::new();
686 pool.sink_rows.top_row = Vec::with_capacity(17);
687 assert!(matches!(
688 pool.ensure_retained_capacity(16),
689 Err(JpegError::MemoryCapExceeded {
690 requested: 17,
691 cap: 16
692 })
693 ));
694 assert_eq!(pool.retained_bytes(), 0);
695 }
696
697 #[test]
698 fn non_pool_decode_releases_stale_capacity_even_when_it_would_fit() {
699 let mut pool = ScratchPool::new();
700 pool.sink_rows.top_row = Vec::with_capacity(64);
701
702 pool.release_for_external_workspace(1, 128)
703 .expect("external phase fits after stale storage is released");
704
705 assert_eq!(pool.retained_bytes(), 0);
706 }
707}