1#![forbid(unsafe_code)]
29#![allow(dead_code)]
30#![allow(clippy::doc_markdown)]
31#![allow(clippy::too_many_arguments)]
32#![allow(clippy::cast_possible_truncation)]
33#![allow(clippy::cast_sign_loss)]
34#![allow(clippy::cast_possible_wrap)]
35#![allow(clippy::similar_names)]
36#![allow(clippy::module_name_repetitions)]
37#![allow(clippy::struct_excessive_bools)]
38
39use super::block::{BlockModeInfo, InterMode, IntraMode as Av1IntraMode};
40use crate::error::{CodecError, CodecResult};
41use crate::frame::VideoFrame;
42use crate::intra::{
43 BitDepth, BlockDimensions, DcPredictor, DirectionalPredictor, HorizontalPredictor, IntraMode,
44 IntraPredContext, IntraPredictor, PaethPredictor, SmoothHPredictor, SmoothPredictor,
45 SmoothVPredictor, VerticalPredictor,
46};
47
48pub const SUBPEL_BITS: u8 = 3;
54
55pub const SUBPEL_SCALE: i32 = 1 << SUBPEL_BITS;
57
58pub const INTERP_TAPS: usize = 8;
60
61pub const MAX_OBMC_SIZE: usize = 128;
63
64pub const WARP_PARAMS: usize = 6;
66
67#[derive(Debug)]
73pub struct PredictionEngine {
74 current_frame: Option<VideoFrame>,
76 reference_frames: Vec<Option<VideoFrame>>,
78 bit_depth: u8,
80 intra_context: IntraPredContext,
82 mc_buffer: Vec<u16>,
84}
85
86impl PredictionEngine {
87 pub fn new(width: u32, height: u32, bit_depth: u8) -> Self {
89 let intra_bd = match bit_depth {
90 8 => BitDepth::Bits8,
91 10 => BitDepth::Bits10,
92 12 => BitDepth::Bits12,
93 _ => BitDepth::Bits8,
94 };
95
96 Self {
97 current_frame: None,
98 reference_frames: vec![None; 8],
99 bit_depth,
100 intra_context: IntraPredContext::new(width as usize, height as usize, intra_bd),
101 mc_buffer: vec![0; MAX_OBMC_SIZE * MAX_OBMC_SIZE],
102 }
103 }
104
105 pub fn predict_block(
107 &mut self,
108 mode_info: &BlockModeInfo,
109 x: u32,
110 y: u32,
111 plane: u8,
112 dst: &mut [u16],
113 stride: usize,
114 ) -> CodecResult<()> {
115 if mode_info.is_inter {
116 self.predict_inter(mode_info, x, y, plane, dst, stride)
117 } else {
118 self.predict_intra(mode_info, x, y, plane, dst, stride)
119 }
120 }
121
122 fn predict_intra(
124 &mut self,
125 mode_info: &BlockModeInfo,
126 _x: u32,
127 _y: u32,
128 plane: u8,
129 dst: &mut [u16],
130 stride: usize,
131 ) -> CodecResult<()> {
132 let bsize = mode_info.block_size;
133 let width = bsize.width() as usize;
134 let height = bsize.height() as usize;
135
136 let mode = if plane == 0 {
138 mode_info.intra_mode
139 } else {
140 mode_info.uv_mode
141 };
142
143 let intra_mode = self.map_intra_mode(mode);
145
146 if mode.is_directional() && plane == 0 {
155 let _angle_delta = mode_info.angle_delta[0];
156 }
158
159 self.apply_intra_mode(intra_mode, mode, width, height, dst, stride)?;
161
162 if mode_info.filter_intra_mode > 0 && plane == 0 {
164 self.apply_filter_intra(dst, width, height, stride, mode_info.filter_intra_mode)?;
165 }
166
167 Ok(())
168 }
169
170 fn map_intra_mode(&self, mode: Av1IntraMode) -> IntraMode {
172 match mode {
173 Av1IntraMode::DcPred => IntraMode::Dc,
174 Av1IntraMode::VPred => IntraMode::Vertical,
175 Av1IntraMode::HPred => IntraMode::Horizontal,
176 Av1IntraMode::D45Pred => IntraMode::D45,
177 Av1IntraMode::D135Pred => IntraMode::D135,
178 Av1IntraMode::D113Pred => IntraMode::D113,
179 Av1IntraMode::D157Pred => IntraMode::D157,
180 Av1IntraMode::D203Pred => IntraMode::D203,
181 Av1IntraMode::D67Pred => IntraMode::D67,
182 Av1IntraMode::SmoothPred => IntraMode::Smooth,
183 Av1IntraMode::SmoothVPred => IntraMode::SmoothV,
184 Av1IntraMode::SmoothHPred => IntraMode::SmoothH,
185 Av1IntraMode::PaethPred => IntraMode::Paeth,
186 }
187 }
188
189 fn apply_intra_mode(
191 &self,
192 intra_mode: IntraMode,
193 _av1_mode: Av1IntraMode,
194 width: usize,
195 height: usize,
196 dst: &mut [u16],
197 stride: usize,
198 ) -> CodecResult<()> {
199 let block_dims = BlockDimensions::new(width, height);
200 let bit_depth = self.intra_context.bit_depth();
201
202 match intra_mode {
203 IntraMode::Dc => {
204 let predictor = DcPredictor::new(bit_depth);
205 predictor.predict(&self.intra_context, dst, stride, block_dims);
206 }
207 IntraMode::Vertical => {
208 let predictor = VerticalPredictor::new();
209 predictor.predict(&self.intra_context, dst, stride, block_dims);
210 }
211 IntraMode::Horizontal => {
212 let predictor = HorizontalPredictor::new();
213 predictor.predict(&self.intra_context, dst, stride, block_dims);
214 }
215 IntraMode::Smooth => {
216 let predictor = SmoothPredictor::new();
217 predictor.predict(&self.intra_context, dst, stride, block_dims);
218 }
219 IntraMode::SmoothV => {
220 let predictor = SmoothVPredictor::new();
221 predictor.predict(&self.intra_context, dst, stride, block_dims);
222 }
223 IntraMode::SmoothH => {
224 let predictor = SmoothHPredictor::new();
225 predictor.predict(&self.intra_context, dst, stride, block_dims);
226 }
227 IntraMode::Paeth => {
228 let predictor = PaethPredictor::new();
229 predictor.predict(&self.intra_context, dst, stride, block_dims);
230 }
231 IntraMode::D45
232 | IntraMode::D135
233 | IntraMode::D113
234 | IntraMode::D157
235 | IntraMode::D203
236 | IntraMode::D67 => {
237 let angle = self.intra_mode_to_angle(intra_mode);
239 let bit_depth = self.intra_context.bit_depth();
240 let predictor = DirectionalPredictor::new(angle, bit_depth);
241 predictor.predict(&self.intra_context, dst, stride, block_dims);
242 }
243 IntraMode::FilterIntra => {
244 let predictor = DcPredictor::new(bit_depth);
246 predictor.predict(&self.intra_context, dst, stride, block_dims);
247 }
248 }
249
250 Ok(())
251 }
252
253 fn intra_mode_to_angle(&self, mode: IntraMode) -> u16 {
255 match mode {
256 IntraMode::D45 => 45,
257 IntraMode::D67 => 67,
258 IntraMode::D113 => 113,
259 IntraMode::D135 => 135,
260 IntraMode::D157 => 157,
261 IntraMode::D203 => 203,
262 _ => 0,
263 }
264 }
265
266 fn apply_angle_delta(&mut self, _ctx: &mut IntraPredContext, _delta: i8) {
268 }
271
272 fn apply_filter_intra(
274 &self,
275 dst: &mut [u16],
276 width: usize,
277 height: usize,
278 stride: usize,
279 _mode: u8,
280 ) -> CodecResult<()> {
281 for y in 0..height {
284 for x in 0..width {
285 let idx = y * stride + x;
286 if idx < dst.len() {
287 dst[idx] = self.apply_filter_tap(dst, x, y, width, height, stride);
289 }
290 }
291 }
292 Ok(())
293 }
294
295 fn apply_filter_tap(
297 &self,
298 src: &[u16],
299 x: usize,
300 y: usize,
301 width: usize,
302 height: usize,
303 stride: usize,
304 ) -> u16 {
305 let mut sum = 0u32;
306 let mut count = 0u32;
307
308 for dy in -1i32..=1 {
310 for dx in -1i32..=1 {
311 let nx = (x as i32 + dx) as usize;
312 let ny = (y as i32 + dy) as usize;
313
314 if nx < width && ny < height {
315 let idx = ny * stride + nx;
316 if idx < src.len() {
317 sum += u32::from(src[idx]);
318 count += 1;
319 }
320 }
321 }
322 }
323
324 sum.checked_div(count)
325 .map_or(src[y * stride + x], |v| v as u16)
326 }
327
328 fn predict_inter(
330 &mut self,
331 mode_info: &BlockModeInfo,
332 x: u32,
333 y: u32,
334 plane: u8,
335 dst: &mut [u16],
336 stride: usize,
337 ) -> CodecResult<()> {
338 let bsize = mode_info.block_size;
339 let width = bsize.width() as usize;
340 let height = bsize.height() as usize;
341
342 if mode_info.is_compound() {
343 self.predict_compound(mode_info, x, y, plane, dst, stride, width, height)
345 } else {
346 self.predict_single_ref(mode_info, x, y, plane, dst, stride, width, height)
348 }
349 }
350
351 fn predict_single_ref(
353 &mut self,
354 mode_info: &BlockModeInfo,
355 x: u32,
356 y: u32,
357 plane: u8,
358 dst: &mut [u16],
359 stride: usize,
360 width: usize,
361 height: usize,
362 ) -> CodecResult<()> {
363 let ref_idx = mode_info.ref_frames[0];
364 if ref_idx < 0 || ref_idx >= self.reference_frames.len() as i8 {
365 return Err(CodecError::InvalidBitstream(
366 "Invalid reference frame".to_string(),
367 ));
368 }
369
370 let ref_frame = &self.reference_frames[ref_idx as usize];
371 if ref_frame.is_none() {
372 return Err(CodecError::InvalidBitstream(
373 "Reference frame not available".to_string(),
374 ));
375 }
376
377 let mv = self.get_motion_vector(mode_info, 0);
379
380 self.motion_compensate(
382 ref_frame
383 .as_ref()
384 .expect("ref_frame is Some: checked is_none() above"),
385 x,
386 y,
387 mv,
388 plane,
389 dst,
390 stride,
391 width,
392 height,
393 mode_info.interp_filter[0],
394 )?;
395
396 if mode_info.motion_mode == 1 {
398 self.apply_obmc(mode_info, x, y, plane, dst, stride, width, height)?;
399 }
400
401 if mode_info.motion_mode == 2 {
403 self.apply_warped_motion(mode_info, x, y, plane, dst, stride, width, height)?;
404 }
405
406 Ok(())
407 }
408
409 fn predict_compound(
411 &mut self,
412 mode_info: &BlockModeInfo,
413 x: u32,
414 y: u32,
415 plane: u8,
416 dst: &mut [u16],
417 stride: usize,
418 width: usize,
419 height: usize,
420 ) -> CodecResult<()> {
421 let mut pred0 = vec![0u16; width * height];
423 let mut pred1 = vec![0u16; width * height];
424
425 if mode_info.ref_frames[0] >= 0 {
427 let mv0 = self.get_motion_vector(mode_info, 0);
428 let ref0 = &self.reference_frames[mode_info.ref_frames[0] as usize];
429 if let Some(ref frame) = ref0 {
430 self.motion_compensate(
431 frame,
432 x,
433 y,
434 mv0,
435 plane,
436 &mut pred0,
437 width,
438 width,
439 height,
440 mode_info.interp_filter[0],
441 )?;
442 }
443 }
444
445 if mode_info.ref_frames[1] >= 0 {
447 let mv1 = self.get_motion_vector(mode_info, 1);
448 let ref1 = &self.reference_frames[mode_info.ref_frames[1] as usize];
449 if let Some(ref frame) = ref1 {
450 self.motion_compensate(
451 frame,
452 x,
453 y,
454 mv1,
455 plane,
456 &mut pred1,
457 width,
458 width,
459 height,
460 mode_info.interp_filter[1],
461 )?;
462 }
463 }
464
465 self.blend_compound_predictions(
467 &pred0,
468 &pred1,
469 dst,
470 stride,
471 width,
472 height,
473 mode_info.compound_type,
474 );
475
476 Ok(())
477 }
478
479 fn get_motion_vector(&self, mode_info: &BlockModeInfo, ref_idx: usize) -> [i32; 2] {
481 match mode_info.inter_mode {
482 InterMode::NewMv => [
483 i32::from(mode_info.mv[ref_idx][0]),
484 i32::from(mode_info.mv[ref_idx][1]),
485 ],
486 InterMode::NearestMv | InterMode::NearMv => {
487 [0, 0]
489 }
490 InterMode::GlobalMv => {
491 [0, 0]
493 }
494 }
495 }
496
497 #[allow(clippy::too_many_lines)]
499 fn motion_compensate(
500 &self,
501 ref_frame: &VideoFrame,
502 x: u32,
503 y: u32,
504 mv: [i32; 2],
505 plane: u8,
506 dst: &mut [u16],
507 stride: usize,
508 width: usize,
509 height: usize,
510 _interp_filter: u8,
511 ) -> CodecResult<()> {
512 let ref_x = (x as i32 * SUBPEL_SCALE) + mv[1];
514 let ref_y = (y as i32 * SUBPEL_SCALE) + mv[0];
515
516 let int_x = ref_x >> SUBPEL_BITS;
518 let int_y = ref_y >> SUBPEL_BITS;
519 let frac_x = (ref_x & (SUBPEL_SCALE - 1)) as usize;
520 let frac_y = (ref_y & (SUBPEL_SCALE - 1)) as usize;
521
522 let plane_idx = plane as usize;
524 let (ref_data, ref_stride) = if plane_idx < ref_frame.planes.len() {
525 (
526 &ref_frame.planes[plane_idx].data[..],
527 ref_frame.planes[plane_idx].stride,
528 )
529 } else {
530 return Err(CodecError::Internal("Invalid plane index".to_string()));
531 };
532
533 if frac_x == 0 && frac_y == 0 {
535 self.copy_block(
537 ref_data, ref_stride, int_x, int_y, dst, stride, width, height,
538 );
539 } else if frac_y == 0 {
540 self.interp_horizontal(
542 ref_data, ref_stride, int_x, int_y, frac_x, dst, stride, width, height,
543 );
544 } else if frac_x == 0 {
545 self.interp_vertical(
547 ref_data, ref_stride, int_x, int_y, frac_y, dst, stride, width, height,
548 );
549 } else {
550 self.interp_2d(
552 ref_data, ref_stride, int_x, int_y, frac_x, frac_y, dst, stride, width, height,
553 );
554 }
555
556 Ok(())
557 }
558
559 fn copy_block(
561 &self,
562 src: &[u8],
563 src_stride: usize,
564 x: i32,
565 y: i32,
566 dst: &mut [u16],
567 dst_stride: usize,
568 width: usize,
569 height: usize,
570 ) {
571 for row in 0..height {
572 let src_y = (y + row as i32).max(0) as usize;
573 let src_start = src_y * src_stride + x.max(0) as usize;
574
575 for col in 0..width {
576 if src_start + col < src.len() {
577 let dst_idx = row * dst_stride + col;
578 if dst_idx < dst.len() {
579 dst[dst_idx] = u16::from(src[src_start + col]);
580 }
581 }
582 }
583 }
584 }
585
586 fn interp_horizontal(
588 &self,
589 src: &[u8],
590 src_stride: usize,
591 x: i32,
592 y: i32,
593 frac: usize,
594 dst: &mut [u16],
595 dst_stride: usize,
596 width: usize,
597 height: usize,
598 ) {
599 let filter = self.get_interp_filter(frac);
601
602 for row in 0..height {
603 let src_y = (y + row as i32).max(0) as usize;
604
605 for col in 0..width {
606 let mut sum = 0i32;
607
608 for tap in 0..INTERP_TAPS {
610 let src_x =
611 (x + col as i32 + tap as i32 - INTERP_TAPS as i32 / 2).max(0) as usize;
612 let src_idx = src_y * src_stride + src_x;
613
614 if src_idx < src.len() {
615 sum += i32::from(src[src_idx]) * filter[tap];
616 }
617 }
618
619 let dst_idx = row * dst_stride + col;
620 if dst_idx < dst.len() {
621 dst[dst_idx] = ((sum + 64) >> 7).clamp(0, (1 << self.bit_depth) - 1) as u16;
622 }
623 }
624 }
625 }
626
627 fn interp_vertical(
629 &self,
630 src: &[u8],
631 src_stride: usize,
632 x: i32,
633 y: i32,
634 frac: usize,
635 dst: &mut [u16],
636 dst_stride: usize,
637 width: usize,
638 height: usize,
639 ) {
640 let filter = self.get_interp_filter(frac);
641
642 for row in 0..height {
643 for col in 0..width {
644 let mut sum = 0i32;
645
646 for tap in 0..INTERP_TAPS {
647 let src_y =
648 (y + row as i32 + tap as i32 - INTERP_TAPS as i32 / 2).max(0) as usize;
649 let src_x = (x + col as i32).max(0) as usize;
650 let src_idx = src_y * src_stride + src_x;
651
652 if src_idx < src.len() {
653 sum += i32::from(src[src_idx]) * filter[tap];
654 }
655 }
656
657 let dst_idx = row * dst_stride + col;
658 if dst_idx < dst.len() {
659 dst[dst_idx] = ((sum + 64) >> 7).clamp(0, (1 << self.bit_depth) - 1) as u16;
660 }
661 }
662 }
663 }
664
665 fn interp_2d(
667 &self,
668 src: &[u8],
669 src_stride: usize,
670 x: i32,
671 y: i32,
672 frac_x: usize,
673 frac_y: usize,
674 dst: &mut [u16],
675 dst_stride: usize,
676 width: usize,
677 height: usize,
678 ) {
679 let mut temp = vec![0u16; (width + INTERP_TAPS) * (height + INTERP_TAPS)];
681 let temp_stride = width + INTERP_TAPS;
682
683 self.interp_horizontal(
685 src,
686 src_stride,
687 x,
688 y - INTERP_TAPS as i32 / 2,
689 frac_x,
690 &mut temp,
691 temp_stride,
692 width,
693 height + INTERP_TAPS,
694 );
695
696 let filter_y = self.get_interp_filter(frac_y);
698
699 for row in 0..height {
700 for col in 0..width {
701 let mut sum = 0i32;
702
703 for tap in 0..INTERP_TAPS {
704 let temp_idx = (row + tap) * temp_stride + col;
705 if temp_idx < temp.len() {
706 sum += i32::from(temp[temp_idx]) * filter_y[tap];
707 }
708 }
709
710 let dst_idx = row * dst_stride + col;
711 if dst_idx < dst.len() {
712 dst[dst_idx] = ((sum + 64) >> 7).clamp(0, (1 << self.bit_depth) - 1) as u16;
713 }
714 }
715 }
716 }
717
718 fn get_interp_filter(&self, frac: usize) -> [i32; INTERP_TAPS] {
720 match frac {
722 0 => [0, 0, 0, 128, 0, 0, 0, 0],
723 1 => [0, 0, 16, 112, 16, 0, 0, 0],
724 2 => [0, 0, 32, 96, 32, 0, 0, 0],
725 3 => [0, 0, 48, 80, 48, 0, 0, 0],
726 4 => [0, 0, 64, 64, 64, 0, 0, 0],
727 5 => [0, 0, 48, 80, 48, 0, 0, 0],
728 6 => [0, 0, 32, 96, 32, 0, 0, 0],
729 7 => [0, 0, 16, 112, 16, 0, 0, 0],
730 _ => [0, 0, 0, 128, 0, 0, 0, 0],
731 }
732 }
733
734 fn blend_compound_predictions(
736 &self,
737 pred0: &[u16],
738 pred1: &[u16],
739 dst: &mut [u16],
740 stride: usize,
741 width: usize,
742 height: usize,
743 compound_type: u8,
744 ) {
745 for row in 0..height {
746 for col in 0..width {
747 let src_idx = row * width + col;
748 let dst_idx = row * stride + col;
749
750 if src_idx < pred0.len() && src_idx < pred1.len() && dst_idx < dst.len() {
751 let blended = if compound_type == 0 {
753 (u32::from(pred0[src_idx]) + u32::from(pred1[src_idx]) + 1) >> 1
754 } else {
755 u32::from(pred0[src_idx])
757 };
758
759 dst[dst_idx] = blended as u16;
760 }
761 }
762 }
763 }
764
765 fn apply_obmc(
767 &mut self,
768 _mode_info: &BlockModeInfo,
769 _x: u32,
770 _y: u32,
771 _plane: u8,
772 dst: &mut [u16],
773 stride: usize,
774 width: usize,
775 height: usize,
776 ) -> CodecResult<()> {
777 self.smooth_boundaries(dst, stride, width, height);
780 Ok(())
781 }
782
783 fn smooth_boundaries(&self, dst: &mut [u16], stride: usize, width: usize, height: usize) {
785 for row in 0..height {
787 for col in 0..width {
788 if row == 0 || col == 0 || row == height - 1 || col == width - 1 {
789 let idx = row * stride + col;
790 if idx < dst.len() {
791 let current = dst[idx];
793 dst[idx] = ((u32::from(current) * 3 + 2) >> 2) as u16;
794 }
795 }
796 }
797 }
798 }
799
800 fn apply_warped_motion(
802 &mut self,
803 _mode_info: &BlockModeInfo,
804 _x: u32,
805 _y: u32,
806 _plane: u8,
807 dst: &mut [u16],
808 stride: usize,
809 width: usize,
810 height: usize,
811 ) -> CodecResult<()> {
812 for row in 0..height {
815 for col in 0..width {
816 let idx = row * stride + col;
817 if idx < dst.len() {
818 dst[idx] = dst[idx];
820 }
821 }
822 }
823 Ok(())
824 }
825
826 pub fn set_reference_frame(&mut self, idx: usize, frame: VideoFrame) {
828 if idx < self.reference_frames.len() {
829 self.reference_frames[idx] = Some(frame);
830 }
831 }
832
833 pub fn set_current_frame(&mut self, frame: VideoFrame) {
835 self.current_frame = Some(frame);
836 }
837}
838
839#[cfg(test)]
844mod tests {
845 use super::*;
846 use crate::frame::{FrameType, VideoFrame};
847 use oximedia_core::{PixelFormat, Rational, Timestamp};
848
849 fn create_test_frame(width: u32, height: u32) -> VideoFrame {
850 let mut frame = VideoFrame::new(PixelFormat::Yuv420p, width, height);
851 frame.allocate();
852 frame.frame_type = FrameType::Key;
853 frame.timestamp = Timestamp::new(0, Rational::new(1, 30));
854 frame
855 }
856
857 fn create_test_mode_info() -> BlockModeInfo {
858 BlockModeInfo::new()
859 }
860
861 #[test]
862 fn test_prediction_engine_creation() {
863 let engine = PredictionEngine::new(1920, 1080, 8);
864 assert_eq!(engine.bit_depth, 8);
865 }
866
867 #[test]
868 fn test_map_intra_mode() {
869 let engine = PredictionEngine::new(64, 64, 8);
870
871 assert_eq!(engine.map_intra_mode(Av1IntraMode::DcPred), IntraMode::Dc);
872 assert_eq!(
873 engine.map_intra_mode(Av1IntraMode::VPred),
874 IntraMode::Vertical
875 );
876 assert_eq!(
877 engine.map_intra_mode(Av1IntraMode::HPred),
878 IntraMode::Horizontal
879 );
880 }
881
882 #[test]
883 fn test_predict_intra() {
884 let mut engine = PredictionEngine::new(64, 64, 8);
885 let frame = create_test_frame(64, 64);
886 engine.set_current_frame(frame);
887
888 let mode_info = create_test_mode_info();
889 let mut dst = vec![0u16; 16 * 16];
890
891 let result = engine.predict_intra(&mode_info, 0, 0, 0, &mut dst, 16);
893 assert!(result.is_ok());
894 }
895
896 #[test]
897 fn test_get_interp_filter() {
898 let engine = PredictionEngine::new(64, 64, 8);
899
900 let filter = engine.get_interp_filter(0);
901 assert_eq!(filter[3], 128); let filter_half = engine.get_interp_filter(4);
904 assert!(filter_half[3] > 0);
905 }
906
907 #[test]
908 fn test_set_reference_frame() {
909 let mut engine = PredictionEngine::new(64, 64, 8);
910 let frame = create_test_frame(64, 64);
911
912 engine.set_reference_frame(0, frame);
913 assert!(engine.reference_frames[0].is_some());
914 }
915
916 #[test]
917 fn test_constants() {
918 assert_eq!(SUBPEL_BITS, 3);
919 assert_eq!(SUBPEL_SCALE, 8);
920 assert_eq!(INTERP_TAPS, 8);
921 assert_eq!(WARP_PARAMS, 6);
922 }
923}