1#[cfg(feature = "cuda-runtime")]
19mod cuda;
20mod error;
21
22pub use error::{CudaRuntimeFailure, CudaTranscodeError, CUDA_UNAVAILABLE};
23
24use j2k_transcode::{
25 DctGridI16ToHtj2k97CodeBlockBatch, DctGridI16ToHtj2k97CodeBlockJob, DctGridToDwt53Job,
26 DctGridToDwt97Job, DctGridToHtj2k97CodeBlockJob, DctGridToReversibleDwt53Job,
27 DctToWaveletStageAccelerator, DctToWaveletStageCounterEvent as CounterEvent,
28 DctToWaveletStageCounters, Dwt53TwoDimensional, Dwt97BatchStageTimings, Dwt97TwoDimensional,
29 Htj2k97CodeBlockOptions, PreencodedHtj2k97CompactBatch, PreencodedHtj2k97CompactBatchGroups,
30 PreencodedHtj2k97Component, PrequantizedHtj2k97Component, ReversibleDwt53FirstLevel,
31 TranscodeStageDispatchMode, TranscodeStageError,
32};
33
34const DEFAULT_AUTO_MIN_SAMPLES: usize = 224 * 224;
39const DEFAULT_AUTO_REVERSIBLE_BATCH_MIN_JOBS: usize = 32;
40const DEFAULT_AUTO_REVERSIBLE_BATCH_MIN_SAMPLES: usize = 224 * 224 * 32;
41const DEFAULT_AUTO_DWT97_BATCH_MIN_JOBS: usize = 32;
42const DEFAULT_AUTO_DWT97_BATCH_MIN_SAMPLES: usize = 224 * 224 * 32;
43const DISABLE_COMPACT_PREENCODED_ENV: &str = "J2K_CUDA_DISABLE_COMPACT_PREENCODED";
44
45#[derive(Debug, Clone)]
47pub struct CudaDctToWaveletStageAccelerator {
48 mode: TranscodeStageDispatchMode,
49 min_auto_samples: usize,
50 min_auto_reversible_batch_jobs: usize,
51 min_auto_reversible_batch_samples: usize,
52 min_auto_dwt97_batch_jobs: usize,
53 min_auto_dwt97_batch_samples: usize,
54 counters: DctToWaveletStageCounters,
55 last_dwt97_batch_stage_timings: Option<Dwt97BatchStageTimings>,
56 resident_ht_encode: bool,
57 #[cfg(feature = "cuda-runtime")]
58 session: Option<cuda::CudaTranscodeSession>,
59}
60
61impl CudaDctToWaveletStageAccelerator {
62 #[must_use]
65 pub const fn new_explicit() -> Self {
66 Self::with_mode(TranscodeStageDispatchMode::Explicit, 0)
67 }
68
69 #[must_use]
73 pub const fn new_explicit_resident_ht_encode() -> Self {
74 let mut accelerator = Self::with_mode(TranscodeStageDispatchMode::Explicit, 0);
75 accelerator.resident_ht_encode = true;
76 accelerator
77 }
78
79 #[must_use]
82 pub const fn for_auto() -> Self {
83 let mut accelerator =
84 Self::with_mode(TranscodeStageDispatchMode::Auto, DEFAULT_AUTO_MIN_SAMPLES);
85 accelerator.min_auto_reversible_batch_jobs = DEFAULT_AUTO_REVERSIBLE_BATCH_MIN_JOBS;
86 accelerator.min_auto_reversible_batch_samples = DEFAULT_AUTO_REVERSIBLE_BATCH_MIN_SAMPLES;
87 accelerator.min_auto_dwt97_batch_jobs = DEFAULT_AUTO_DWT97_BATCH_MIN_JOBS;
88 accelerator.min_auto_dwt97_batch_samples = DEFAULT_AUTO_DWT97_BATCH_MIN_SAMPLES;
89 accelerator
90 }
91
92 const fn with_mode(mode: TranscodeStageDispatchMode, min_auto_samples: usize) -> Self {
93 Self {
94 mode,
95 min_auto_samples,
96 min_auto_reversible_batch_jobs: 0,
97 min_auto_reversible_batch_samples: 0,
98 min_auto_dwt97_batch_jobs: 0,
99 min_auto_dwt97_batch_samples: 0,
100 counters: DctToWaveletStageCounters::new(),
101 last_dwt97_batch_stage_timings: None,
102 resident_ht_encode: false,
103 #[cfg(feature = "cuda-runtime")]
104 session: None,
105 }
106 }
107
108 #[cfg(feature = "cuda-runtime")]
109 fn cuda_session(&mut self) -> &mut cuda::CudaTranscodeSession {
110 self.session
111 .get_or_insert_with(cuda::CudaTranscodeSession::default)
112 }
113
114 #[must_use]
117 pub const fn with_auto_reversible_batch_thresholds(
118 mut self,
119 min_jobs: usize,
120 min_samples: usize,
121 ) -> Self {
122 self.min_auto_reversible_batch_jobs = min_jobs;
123 self.min_auto_reversible_batch_samples = min_samples;
124 self
125 }
126
127 #[must_use]
130 pub const fn with_auto_dwt97_batch_thresholds(
131 mut self,
132 min_jobs: usize,
133 min_samples: usize,
134 ) -> Self {
135 self.min_auto_dwt97_batch_jobs = min_jobs;
136 self.min_auto_dwt97_batch_samples = min_samples;
137 self
138 }
139
140 #[must_use]
142 pub const fn reversible_dwt53_attempts(&self) -> usize {
143 self.counters.reversible_dwt53_attempts()
144 }
145
146 #[must_use]
148 pub const fn reversible_dwt53_dispatches(&self) -> usize {
149 self.counters.reversible_dwt53_dispatches()
150 }
151
152 #[must_use]
154 pub const fn reversible_dwt53_batch_attempts(&self) -> usize {
155 self.counters.reversible_dwt53_batch_attempts()
156 }
157
158 #[must_use]
160 pub const fn reversible_dwt53_batch_dispatches(&self) -> usize {
161 self.counters.reversible_dwt53_batch_dispatches()
162 }
163
164 #[must_use]
166 pub const fn dwt53_attempts(&self) -> usize {
167 self.counters.dwt53_attempts()
168 }
169
170 #[must_use]
172 pub const fn dwt53_dispatches(&self) -> usize {
173 self.counters.dwt53_dispatches()
174 }
175
176 #[must_use]
178 pub const fn dwt97_attempts(&self) -> usize {
179 self.counters.dwt97_attempts()
180 }
181
182 #[must_use]
184 pub const fn dwt97_dispatches(&self) -> usize {
185 self.counters.dwt97_dispatches()
186 }
187
188 #[must_use]
190 pub const fn dwt97_batch_attempts(&self) -> usize {
191 self.counters.dwt97_batch_attempts()
192 }
193
194 #[must_use]
196 pub const fn dwt97_batch_dispatches(&self) -> usize {
197 self.counters.dwt97_batch_dispatches()
198 }
199
200 #[must_use]
202 pub const fn htj2k97_codeblock_batch_attempts(&self) -> usize {
203 self.counters.htj2k97_codeblock_batch_attempts()
204 }
205
206 #[must_use]
208 pub const fn htj2k97_codeblock_batch_dispatches(&self) -> usize {
209 self.counters.htj2k97_codeblock_batch_dispatches()
210 }
211
212 #[cfg(feature = "cuda-runtime")]
216 fn recover<T>(&self, error: CudaTranscodeError) -> Result<Option<T>, TranscodeStageError> {
217 self.mode.recover(error, CudaTranscodeError::is_recoverable)
218 }
219}
220
221fn reversible_batch_total_samples(jobs: &[DctGridToReversibleDwt53Job<'_>]) -> usize {
222 jobs.iter().fold(0usize, |total, job| {
223 total.saturating_add(job.width.saturating_mul(job.height))
224 })
225}
226
227fn dwt97_batch_total_samples(jobs: &[DctGridToDwt97Job<'_>]) -> usize {
228 jobs.iter().fold(0usize, |total, job| {
229 total.saturating_add(job.width.saturating_mul(job.height))
230 })
231}
232
233fn htj2k97_codeblock_batch_total_samples(jobs: &[DctGridToHtj2k97CodeBlockJob<'_>]) -> usize {
234 jobs.iter().fold(0usize, |total, job| {
235 total.saturating_add(job.width.saturating_mul(job.height))
236 })
237}
238
239fn htj2k97_i16_codeblock_batch_total_samples(
240 jobs: &[DctGridI16ToHtj2k97CodeBlockJob<'_>],
241) -> usize {
242 jobs.iter().fold(0usize, |total, job| {
243 total.saturating_add(job.width.saturating_mul(job.height))
244 })
245}
246
247fn htj2k97_i16_codeblock_batch_group_total_samples(
248 groups: &[DctGridI16ToHtj2k97CodeBlockBatch<'_, '_>],
249) -> usize {
250 groups.iter().fold(0usize, |total, group| {
251 total.saturating_add(htj2k97_i16_codeblock_batch_total_samples(group.jobs))
252 })
253}
254
255impl Default for CudaDctToWaveletStageAccelerator {
256 fn default() -> Self {
257 Self::for_auto()
258 }
259}
260
261#[doc(hidden)]
262impl DctToWaveletStageAccelerator for CudaDctToWaveletStageAccelerator {
263 fn supports_dwt97_batch(&self) -> bool {
264 true
265 }
266
267 fn supports_htj2k97_codeblock_batch(&self) -> bool {
271 true
272 }
273
274 fn supports_htj2k97_i16_preencoded_batch(&self) -> bool {
275 self.resident_ht_encode
276 }
277
278 fn supports_htj2k97_compact_preencoded_batch(&self) -> bool {
279 self.resident_ht_encode && std::env::var_os(DISABLE_COMPACT_PREENCODED_ENV).is_none()
280 }
281
282 fn dct_grid_to_reversible_dwt53(
283 &mut self,
284 job: DctGridToReversibleDwt53Job<'_>,
285 ) -> Result<Option<ReversibleDwt53FirstLevel>, TranscodeStageError> {
286 self.counters
287 .record(CounterEvent::ReversibleDwt53Attempt, 1);
288
289 if self.mode.is_auto() && job.width.saturating_mul(job.height) < self.min_auto_samples {
290 return Ok(None);
291 }
292
293 #[cfg(not(feature = "cuda-runtime"))]
294 {
295 let _ = job;
296 self.mode.unavailable()
297 }
298
299 #[cfg(feature = "cuda-runtime")]
300 {
301 match cuda::dispatch_reversible_dwt53(self.cuda_session(), job) {
302 Ok(output) => {
303 self.counters
304 .record(CounterEvent::ReversibleDwt53Dispatch, 1);
305 Ok(Some(output))
306 }
307 Err(error) => self.recover(error),
308 }
309 }
310 }
311
312 fn dct_grid_to_reversible_dwt53_batch(
313 &mut self,
314 jobs: &[DctGridToReversibleDwt53Job<'_>],
315 ) -> Result<Option<Vec<ReversibleDwt53FirstLevel>>, TranscodeStageError> {
316 self.counters
317 .record(CounterEvent::ReversibleDwt53BatchAttempt, 1);
318
319 if jobs.is_empty() {
320 return Ok(Some(Vec::new()));
321 }
322 if self.mode.is_auto()
323 && (jobs.len() < self.min_auto_reversible_batch_jobs
324 || reversible_batch_total_samples(jobs) < self.min_auto_reversible_batch_samples)
325 {
326 return Ok(None);
327 }
328
329 #[cfg(not(feature = "cuda-runtime"))]
330 {
331 let _ = jobs;
332 self.mode.unavailable()
333 }
334
335 #[cfg(feature = "cuda-runtime")]
336 {
337 match cuda::dispatch_reversible_dwt53_batch(self.cuda_session(), jobs) {
338 Ok(output) => {
339 self.counters
340 .record(CounterEvent::ReversibleDwt53BatchDispatch, 1);
341 Ok(Some(output))
342 }
343 Err(error) => self.recover(error),
344 }
345 }
346 }
347
348 fn dct_grid_to_dwt53(
349 &mut self,
350 job: DctGridToDwt53Job<'_>,
351 ) -> Result<Option<Dwt53TwoDimensional<f64>>, TranscodeStageError> {
352 self.counters.record(CounterEvent::Dwt53Attempt, 1);
353
354 if self.mode.is_auto() && job.width.saturating_mul(job.height) < self.min_auto_samples {
355 return Ok(None);
356 }
357
358 #[cfg(not(feature = "cuda-runtime"))]
359 {
360 let _ = job;
361 self.mode.unavailable()
362 }
363
364 #[cfg(feature = "cuda-runtime")]
365 {
366 match cuda::dispatch_dwt53(job) {
367 Ok(output) => {
368 self.counters.record(CounterEvent::Dwt53Dispatch, 1);
369 Ok(Some(output))
370 }
371 Err(error) => self.recover(error),
372 }
373 }
374 }
375
376 fn dct_grid_to_dwt97(
377 &mut self,
378 job: DctGridToDwt97Job<'_>,
379 ) -> Result<Option<Dwt97TwoDimensional<f64>>, TranscodeStageError> {
380 self.counters.record(CounterEvent::Dwt97Attempt, 1);
381
382 if self.mode.is_auto() && job.width.saturating_mul(job.height) < self.min_auto_samples {
383 return Ok(None);
384 }
385
386 #[cfg(not(feature = "cuda-runtime"))]
387 {
388 let _ = job;
389 self.mode.unavailable()
390 }
391
392 #[cfg(feature = "cuda-runtime")]
393 {
394 match cuda::dispatch_dwt97(self.cuda_session(), job) {
395 Ok(output) => {
396 self.counters.record(CounterEvent::Dwt97Dispatch, 1);
397 Ok(Some(output))
398 }
399 Err(error) => self.recover(error),
400 }
401 }
402 }
403
404 fn dct_grid_to_dwt97_batch(
405 &mut self,
406 jobs: &[DctGridToDwt97Job<'_>],
407 ) -> Result<Option<Vec<Dwt97TwoDimensional<f64>>>, TranscodeStageError> {
408 self.counters.record(CounterEvent::Dwt97BatchAttempt, 1);
409 self.last_dwt97_batch_stage_timings = None;
410
411 if jobs.is_empty() {
412 return Ok(Some(Vec::new()));
413 }
414 if self.mode.is_auto()
415 && (jobs.len() < self.min_auto_dwt97_batch_jobs
416 || dwt97_batch_total_samples(jobs) < self.min_auto_dwt97_batch_samples)
417 {
418 return Ok(None);
419 }
420
421 #[cfg(not(feature = "cuda-runtime"))]
422 {
423 let _ = jobs;
424 self.mode.unavailable()
425 }
426
427 #[cfg(feature = "cuda-runtime")]
428 {
429 match cuda::dispatch_dwt97_batch(self.cuda_session(), jobs) {
430 Ok((output, timings)) => {
431 self.counters.record(CounterEvent::Dwt97BatchDispatch, 1);
432 self.last_dwt97_batch_stage_timings = Some(timings);
433 Ok(Some(output))
434 }
435 Err(error) => self.recover(error),
436 }
437 }
438 }
439
440 fn dct_grid_to_htj2k97_codeblock_batch(
441 &mut self,
442 jobs: &[DctGridToHtj2k97CodeBlockJob<'_>],
443 options: Htj2k97CodeBlockOptions,
444 ) -> Result<Option<Vec<PrequantizedHtj2k97Component>>, TranscodeStageError> {
445 self.counters.record(CounterEvent::Dwt97BatchAttempt, 1);
448 self.counters
449 .record(CounterEvent::Htj2k97CodeblockBatchAttempt, 1);
450 self.last_dwt97_batch_stage_timings = None;
451
452 if jobs.is_empty() {
453 return Ok(Some(Vec::new()));
454 }
455 if self.mode.is_auto()
456 && (jobs.len() < self.min_auto_dwt97_batch_jobs
457 || htj2k97_codeblock_batch_total_samples(jobs) < self.min_auto_dwt97_batch_samples)
458 {
459 return Ok(None);
460 }
461
462 #[cfg(not(feature = "cuda-runtime"))]
463 {
464 let _ = (jobs, options);
465 self.mode.unavailable()
466 }
467
468 #[cfg(feature = "cuda-runtime")]
469 {
470 match cuda::dispatch_htj2k97_codeblock_batch(self.cuda_session(), jobs, options) {
471 Ok((output, timings)) => {
472 self.counters.record(CounterEvent::Dwt97BatchDispatch, 1);
473 self.counters
474 .record(CounterEvent::Htj2k97CodeblockBatchDispatch, 1);
475 self.last_dwt97_batch_stage_timings = Some(timings);
476 Ok(Some(output))
477 }
478 Err(error) => self.recover(error),
479 }
480 }
481 }
482
483 fn dct_grid_to_htj2k97_preencoded_batch(
484 &mut self,
485 jobs: &[DctGridToHtj2k97CodeBlockJob<'_>],
486 options: Htj2k97CodeBlockOptions,
487 ) -> Result<Option<Vec<PreencodedHtj2k97Component>>, TranscodeStageError> {
488 if !self.resident_ht_encode {
489 return Ok(None);
490 }
491
492 self.counters.record(CounterEvent::Dwt97BatchAttempt, 1);
493 self.counters
494 .record(CounterEvent::Htj2k97CodeblockBatchAttempt, 1);
495 self.last_dwt97_batch_stage_timings = None;
496
497 if jobs.is_empty() {
498 return Ok(Some(Vec::new()));
499 }
500 if self.mode.is_auto()
501 && (jobs.len() < self.min_auto_dwt97_batch_jobs
502 || htj2k97_codeblock_batch_total_samples(jobs) < self.min_auto_dwt97_batch_samples)
503 {
504 return Ok(None);
505 }
506
507 #[cfg(not(feature = "cuda-runtime"))]
508 {
509 let _ = (jobs, options);
510 self.mode.unavailable()
511 }
512
513 #[cfg(feature = "cuda-runtime")]
514 {
515 match cuda::dispatch_htj2k97_preencoded_batch(self.cuda_session(), jobs, options) {
516 Ok((output, timings)) => {
517 self.counters.record(CounterEvent::Dwt97BatchDispatch, 1);
518 self.counters
519 .record(CounterEvent::Htj2k97CodeblockBatchDispatch, 1);
520 self.last_dwt97_batch_stage_timings = Some(timings);
521 Ok(Some(output))
522 }
523 Err(error) => self.recover(error),
524 }
525 }
526 }
527
528 fn dct_grid_i16_to_htj2k97_preencoded_batch(
529 &mut self,
530 jobs: &[DctGridI16ToHtj2k97CodeBlockJob<'_>],
531 options: Htj2k97CodeBlockOptions,
532 ) -> Result<Option<Vec<PreencodedHtj2k97Component>>, TranscodeStageError> {
533 if !self.resident_ht_encode {
534 return Ok(None);
535 }
536
537 self.counters.record(CounterEvent::Dwt97BatchAttempt, 1);
538 self.counters
539 .record(CounterEvent::Htj2k97CodeblockBatchAttempt, 1);
540 self.last_dwt97_batch_stage_timings = None;
541
542 if jobs.is_empty() {
543 return Ok(Some(Vec::new()));
544 }
545 if self.mode.is_auto()
546 && (jobs.len() < self.min_auto_dwt97_batch_jobs
547 || htj2k97_i16_codeblock_batch_total_samples(jobs)
548 < self.min_auto_dwt97_batch_samples)
549 {
550 return Ok(None);
551 }
552
553 #[cfg(not(feature = "cuda-runtime"))]
554 {
555 let _ = (jobs, options);
556 self.mode.unavailable()
557 }
558
559 #[cfg(feature = "cuda-runtime")]
560 {
561 match cuda::dispatch_htj2k97_preencoded_i16_batch(self.cuda_session(), jobs, options) {
562 Ok((output, timings)) => {
563 self.counters.record(CounterEvent::Dwt97BatchDispatch, 1);
564 self.counters
565 .record(CounterEvent::Htj2k97CodeblockBatchDispatch, 1);
566 self.last_dwt97_batch_stage_timings = Some(timings);
567 Ok(Some(output))
568 }
569 Err(error) => self.recover(error),
570 }
571 }
572 }
573
574 fn dct_grid_i16_to_htj2k97_compact_preencoded_batch(
575 &mut self,
576 jobs: &[DctGridI16ToHtj2k97CodeBlockJob<'_>],
577 options: Htj2k97CodeBlockOptions,
578 ) -> Result<Option<PreencodedHtj2k97CompactBatch>, TranscodeStageError> {
579 if !self.resident_ht_encode {
580 return Ok(None);
581 }
582
583 self.counters.record(CounterEvent::Dwt97BatchAttempt, 1);
584 self.counters
585 .record(CounterEvent::Htj2k97CodeblockBatchAttempt, 1);
586 self.last_dwt97_batch_stage_timings = None;
587
588 if jobs.is_empty() {
589 return Ok(Some(PreencodedHtj2k97CompactBatch {
590 payload: Vec::new(),
591 components: Vec::new(),
592 }));
593 }
594 if self.mode.is_auto()
595 && (jobs.len() < self.min_auto_dwt97_batch_jobs
596 || htj2k97_i16_codeblock_batch_total_samples(jobs)
597 < self.min_auto_dwt97_batch_samples)
598 {
599 return Ok(None);
600 }
601
602 #[cfg(not(feature = "cuda-runtime"))]
603 {
604 let _ = (jobs, options);
605 self.mode.unavailable()
606 }
607
608 #[cfg(feature = "cuda-runtime")]
609 {
610 match cuda::dispatch_htj2k97_compact_preencoded_i16_batch(
611 self.cuda_session(),
612 jobs,
613 options,
614 ) {
615 Ok((output, timings)) => {
616 self.counters.record(CounterEvent::Dwt97BatchDispatch, 1);
617 self.counters
618 .record(CounterEvent::Htj2k97CodeblockBatchDispatch, 1);
619 self.last_dwt97_batch_stage_timings = Some(timings);
620 Ok(Some(output))
621 }
622 Err(error) => self.recover(error),
623 }
624 }
625 }
626
627 fn dct_grid_i16_to_htj2k97_preencoded_batch_groups(
628 &mut self,
629 groups: &[DctGridI16ToHtj2k97CodeBlockBatch<'_, '_>],
630 options: Htj2k97CodeBlockOptions,
631 ) -> Result<Option<Vec<Vec<PreencodedHtj2k97Component>>>, TranscodeStageError> {
632 if !self.resident_ht_encode {
633 return Ok(None);
634 }
635
636 self.counters
637 .record(CounterEvent::Dwt97BatchAttempt, groups.len());
638 self.counters
639 .record(CounterEvent::Htj2k97CodeblockBatchAttempt, groups.len());
640 self.last_dwt97_batch_stage_timings = None;
641
642 if groups.is_empty() {
643 return Ok(Some(Vec::new()));
644 }
645 let total_jobs = groups.iter().map(|group| group.jobs.len()).sum::<usize>();
646 if self.mode.is_auto()
647 && (total_jobs < self.min_auto_dwt97_batch_jobs
648 || htj2k97_i16_codeblock_batch_group_total_samples(groups)
649 < self.min_auto_dwt97_batch_samples)
650 {
651 return Ok(None);
652 }
653
654 #[cfg(not(feature = "cuda-runtime"))]
655 {
656 let _ = (groups, options);
657 self.mode.unavailable()
658 }
659
660 #[cfg(feature = "cuda-runtime")]
661 {
662 match cuda::dispatch_htj2k97_preencoded_i16_batch_groups(
663 self.cuda_session(),
664 groups,
665 options,
666 ) {
667 Ok((output, timings)) => {
668 self.counters
669 .record(CounterEvent::Dwt97BatchDispatch, groups.len());
670 self.counters.record(
671 CounterEvent::Htj2k97CodeblockBatchDispatch,
672 timings.ht_codeblock_dispatches,
673 );
674 self.last_dwt97_batch_stage_timings = Some(timings);
675 Ok(Some(output))
676 }
677 Err(error) => self.recover(error),
678 }
679 }
680 }
681
682 fn dct_grid_i16_to_htj2k97_compact_preencoded_batch_groups(
683 &mut self,
684 groups: &[DctGridI16ToHtj2k97CodeBlockBatch<'_, '_>],
685 options: Htj2k97CodeBlockOptions,
686 ) -> Result<Option<PreencodedHtj2k97CompactBatchGroups>, TranscodeStageError> {
687 if !self.resident_ht_encode {
688 return Ok(None);
689 }
690
691 self.counters
692 .record(CounterEvent::Dwt97BatchAttempt, groups.len());
693 self.counters
694 .record(CounterEvent::Htj2k97CodeblockBatchAttempt, groups.len());
695 self.last_dwt97_batch_stage_timings = None;
696
697 if groups.is_empty() {
698 return Ok(Some(PreencodedHtj2k97CompactBatchGroups {
699 payload: Vec::new(),
700 groups: Vec::new(),
701 }));
702 }
703 let total_jobs = groups.iter().map(|group| group.jobs.len()).sum::<usize>();
704 if self.mode.is_auto()
705 && (total_jobs < self.min_auto_dwt97_batch_jobs
706 || htj2k97_i16_codeblock_batch_group_total_samples(groups)
707 < self.min_auto_dwt97_batch_samples)
708 {
709 return Ok(None);
710 }
711
712 #[cfg(not(feature = "cuda-runtime"))]
713 {
714 let _ = (groups, options);
715 self.mode.unavailable()
716 }
717
718 #[cfg(feature = "cuda-runtime")]
719 {
720 match cuda::dispatch_htj2k97_compact_preencoded_i16_batch_groups(
721 self.cuda_session(),
722 groups,
723 options,
724 ) {
725 Ok((output, timings)) => {
726 self.counters
727 .record(CounterEvent::Dwt97BatchDispatch, groups.len());
728 self.counters.record(
729 CounterEvent::Htj2k97CodeblockBatchDispatch,
730 timings.ht_codeblock_dispatches,
731 );
732 self.last_dwt97_batch_stage_timings = Some(timings);
733 Ok(Some(output))
734 }
735 Err(error) => self.recover(error),
736 }
737 }
738 }
739
740 fn last_dwt97_batch_stage_timings(&self) -> Option<Dwt97BatchStageTimings> {
741 self.last_dwt97_batch_stage_timings
742 }
743}
744
745#[cfg(test)]
746mod tests {
747 use super::*;
748 use std::sync::Mutex;
749
750 static ENV_LOCK: Mutex<()> = Mutex::new(());
751
752 fn test_htj2k97_codeblock_options() -> Htj2k97CodeBlockOptions {
753 Htj2k97CodeBlockOptions {
754 bit_depth: 8,
755 guard_bits: 2,
756 code_block_width_exp: 4,
757 code_block_height_exp: 4,
758 irreversible_quantization_scale: 1.0,
759 irreversible_quantization_subband_scales:
760 j2k_transcode::IrreversibleQuantizationSubbandScales::default(),
761 }
762 }
763
764 #[test]
765 fn explicit_mode_without_cuda_runtime_errors_on_reversible_job() {
766 let mut accelerator = CudaDctToWaveletStageAccelerator::new_explicit();
769 let blocks: Vec<[i16; 64]> = vec![[0i16; 64]];
770 let job = DctGridToReversibleDwt53Job {
771 dequantized_blocks: &blocks,
772 block_cols: 1,
773 block_rows: 1,
774 width: 8,
775 height: 8,
776 };
777 let result = accelerator.dct_grid_to_reversible_dwt53(job);
778 #[cfg(not(feature = "cuda-runtime"))]
779 assert!(matches!(
780 result,
781 Err(TranscodeStageError::DeviceUnavailable)
782 ));
783 let _ = result;
784 assert_eq!(accelerator.reversible_dwt53_attempts(), 1);
785 }
786
787 #[test]
788 fn auto_mode_falls_back_to_scalar_for_small_jobs() {
789 let mut accelerator = CudaDctToWaveletStageAccelerator::for_auto();
792 let blocks: Vec<[i16; 64]> = vec![[0i16; 64]];
793 let job = DctGridToReversibleDwt53Job {
794 dequantized_blocks: &blocks,
795 block_cols: 1,
796 block_rows: 1,
797 width: 8,
798 height: 8,
799 };
800 assert!(matches!(
801 accelerator.dct_grid_to_reversible_dwt53(job),
802 Ok(None)
803 ));
804 }
805
806 #[test]
807 fn empty_batches_return_empty_without_dispatch() {
808 let mut accelerator = CudaDctToWaveletStageAccelerator::new_explicit();
809 assert!(matches!(
810 accelerator.dct_grid_to_reversible_dwt53_batch(&[]),
811 Ok(Some(values)) if values.is_empty()
812 ));
813 assert!(matches!(
814 accelerator.dct_grid_to_dwt97_batch(&[]),
815 Ok(Some(values)) if values.is_empty()
816 ));
817 }
818
819 #[test]
820 fn compact_preencoded_support_obeys_cuda_env_gate() {
821 let _guard = ENV_LOCK.lock().expect("env lock");
822 let previous = std::env::var_os(DISABLE_COMPACT_PREENCODED_ENV);
823 std::env::remove_var(DISABLE_COMPACT_PREENCODED_ENV);
824 let accelerator = CudaDctToWaveletStageAccelerator::new_explicit_resident_ht_encode();
825 assert!(accelerator.supports_htj2k97_i16_preencoded_batch());
826 assert!(accelerator.supports_htj2k97_compact_preencoded_batch());
827
828 std::env::set_var(DISABLE_COMPACT_PREENCODED_ENV, "1");
829 let accelerator = CudaDctToWaveletStageAccelerator::new_explicit_resident_ht_encode();
830 assert!(accelerator.supports_htj2k97_i16_preencoded_batch());
831 assert!(!accelerator.supports_htj2k97_compact_preencoded_batch());
832
833 if let Some(previous) = previous {
834 std::env::set_var(DISABLE_COMPACT_PREENCODED_ENV, previous);
835 } else {
836 std::env::remove_var(DISABLE_COMPACT_PREENCODED_ENV);
837 }
838 }
839
840 #[test]
841 fn auto_mode_declines_under_amortized_reversible_batches() {
842 let mut accelerator = CudaDctToWaveletStageAccelerator::for_auto()
843 .with_auto_reversible_batch_thresholds(2, 224 * 224 * 2);
844 let blocks = vec![[0i16; 64]; 256 * 256 / 64];
845 let job = DctGridToReversibleDwt53Job {
846 dequantized_blocks: &blocks,
847 block_cols: 32,
848 block_rows: 32,
849 width: 256,
850 height: 256,
851 };
852
853 assert!(matches!(
854 accelerator.dct_grid_to_reversible_dwt53_batch(&[job]),
855 Ok(None)
856 ));
857 assert_eq!(accelerator.reversible_dwt53_batch_attempts(), 1);
858 assert_eq!(accelerator.reversible_dwt53_batch_dispatches(), 0);
859 }
860
861 #[test]
862 fn auto_mode_declines_under_amortized_dwt97_batches() {
863 let mut accelerator = CudaDctToWaveletStageAccelerator::for_auto()
864 .with_auto_dwt97_batch_thresholds(2, 224 * 224 * 2);
865 let blocks = vec![[[0.0f64; 8]; 8]; 256 * 256 / 64];
866 let job = DctGridToDwt97Job {
867 blocks: &blocks,
868 block_cols: 32,
869 block_rows: 32,
870 width: 256,
871 height: 256,
872 };
873
874 assert!(matches!(
875 accelerator.dct_grid_to_dwt97_batch(&[job]),
876 Ok(None)
877 ));
878 assert_eq!(accelerator.dwt97_batch_attempts(), 1);
879 assert_eq!(accelerator.dwt97_batch_dispatches(), 0);
880 }
881
882 #[test]
883 fn auto_mode_declines_under_amortized_htj2k97_codeblock_batches() {
884 let mut accelerator = CudaDctToWaveletStageAccelerator::for_auto()
885 .with_auto_dwt97_batch_thresholds(2, 224 * 224 * 2);
886 let blocks = vec![[[0.0f64; 8]; 8]; 256 * 256 / 64];
887 let job = DctGridToHtj2k97CodeBlockJob {
888 blocks: &blocks,
889 block_cols: 32,
890 block_rows: 32,
891 width: 256,
892 height: 256,
893 x_rsiz: 1,
894 y_rsiz: 1,
895 };
896
897 let result = accelerator
898 .dct_grid_to_htj2k97_codeblock_batch(&[job], test_htj2k97_codeblock_options());
899 assert!(matches!(result, Ok(None)));
900 assert_eq!(accelerator.dwt97_batch_attempts(), 1);
901 assert_eq!(accelerator.dwt97_batch_dispatches(), 0);
902 assert_eq!(accelerator.htj2k97_codeblock_batch_attempts(), 1);
903 assert_eq!(accelerator.htj2k97_codeblock_batch_dispatches(), 0);
904 }
905}