1use crate::math::{Mat3I16, Vec3I16, Vec3I32};
26use crate::ops;
27use crate::regs::pack_xy;
28use crate::{read_control, read_data, write_control, write_data};
29#[cfg(target_arch = "mips")]
30use core::arch::asm;
31
32#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
37#[repr(C)]
38pub struct Projected {
39 pub sx: i16,
41 pub sy: i16,
43 pub sz: u16,
45}
46
47#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
51#[repr(C)]
52pub struct ClassicNormalizedVector {
53 pub vector: Vec3I16,
56 pub xy_squared: i32,
58 pub squared: i32,
60}
61
62const fn isqrt_u32(n: u32) -> u32 {
64 let (mut lo, mut hi) = (0u32, 1u32 << 16);
65 while hi - lo > 1 {
66 let mid = (lo + hi) / 2;
67 if mid * mid <= n {
68 lo = mid;
69 } else {
70 hi = mid;
71 }
72 }
73 lo
74}
75
76const RSQRT_Q12_FROM_64: [i16; 192] = {
82 let mut table = [0i16; 192];
83 let mut i = 0;
84 while i < 192 {
85 table[i] = isqrt_u32((1u32 << 30) / (64 + i as u32)) as i16;
86 i += 1;
87 }
88 table
89};
90
91pub fn normalize_classic_q12_scheduled(input: Vec3I32) -> ClassicNormalizedVector {
107 let c = [
108 (input.x >> 12) as i16,
109 (input.y >> 12) as i16,
110 (input.z >> 12) as i16,
111 ];
112 let square = |v: i16| i32::from(v) * i32::from(v);
113 let xy_squared = square(c[0]).wrapping_add(square(c[1]));
114 let squared = xy_squared.wrapping_add(square(c[2]));
115 if squared <= 0 {
116 return ClassicNormalizedVector {
117 vector: Vec3I16::new(0, 0, 0),
118 xy_squared,
119 squared,
120 };
121 }
122 let even_zeros = (squared as u32).leading_zeros() & !1;
123 let in_range = if even_zeros >= 24 {
124 squared << (even_zeros - 24)
125 } else {
126 squared >> (24 - even_zeros)
127 };
128 let r = i32::from(RSQRT_Q12_FROM_64[(in_range - 64) as usize]);
129 let shift = (31 - even_zeros) / 2;
130 let scale = |v: i16| ((i32::from(v) * r) >> shift) as i16;
131 ClassicNormalizedVector {
132 vector: Vec3I16::new(scale(c[0]), scale(c[1]), scale(c[2])),
133 xy_squared,
134 squared,
135 }
136}
137
138#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
145#[repr(C)]
146pub struct AabbClipPlane {
147 pub normal: [i16; 3],
149 pub kind: u8,
151 pub signbits: u8,
153 pub distance: i32,
155}
156
157#[doc(alias = "SetRotMatrix")]
159pub fn load_rotation(m: &Mat3I16) {
160 write_control!(0, pack_xy(m.m[0][0], m.m[0][1]));
161 write_control!(1, pack_xy(m.m[0][2], m.m[1][0]));
162 write_control!(2, pack_xy(m.m[1][1], m.m[1][2]));
163 write_control!(3, pack_xy(m.m[2][0], m.m[2][1]));
164 write_control!(4, m.m[2][2] as i32 as u32);
165}
166
167#[doc(alias = "SetLightMatrix")]
169pub fn load_light_matrix(m: &Mat3I16) {
170 write_control!(8, pack_xy(m.m[0][0], m.m[0][1]));
171 write_control!(9, pack_xy(m.m[0][2], m.m[1][0]));
172 write_control!(10, pack_xy(m.m[1][1], m.m[1][2]));
173 write_control!(11, pack_xy(m.m[2][0], m.m[2][1]));
174 write_control!(12, m.m[2][2] as i32 as u32);
175}
176
177pub fn load_aabb_clip4(planes: &[AabbClipPlane; 4]) {
185 load_rotation(&Mat3I16 {
186 m: [planes[0].normal, planes[1].normal, planes[2].normal],
187 });
188 load_light_matrix(&Mat3I16 {
189 m: [planes[3].normal, [0; 3], [0; 3]],
190 });
191}
192
193#[doc(alias = "SetColorMatrix")]
195pub fn load_light_color_matrix(m: &Mat3I16) {
196 write_control!(16, pack_xy(m.m[0][0], m.m[0][1]));
197 write_control!(17, pack_xy(m.m[0][2], m.m[1][0]));
198 write_control!(18, pack_xy(m.m[1][1], m.m[1][2]));
199 write_control!(19, pack_xy(m.m[2][0], m.m[2][1]));
200 write_control!(20, m.m[2][2] as i32 as u32);
201}
202
203#[doc(alias = "SetTransMatrix")]
205pub fn load_translation(t: Vec3I32) {
206 write_control!(5, t.x as u32);
207 write_control!(6, t.y as u32);
208 write_control!(7, t.z as u32);
209}
210
211#[doc(alias = "SetBackColor")]
213pub fn load_background_color(c: Vec3I32) {
214 write_control!(13, c.x as u32);
215 write_control!(14, c.y as u32);
216 write_control!(15, c.z as u32);
217}
218
219#[doc(alias = "SetFarColor")]
222pub fn load_far_color(c: Vec3I32) {
223 write_control!(21, c.x as u32);
224 write_control!(22, c.y as u32);
225 write_control!(23, c.z as u32);
226}
227
228#[doc(alias = "SetGeomOffset")]
231pub fn set_screen_offset(ofx_15_16: i32, ofy_15_16: i32) {
232 write_control!(24, ofx_15_16 as u32);
233 write_control!(25, ofy_15_16 as u32);
234}
235
236#[doc(alias = "SetGeomScreen")]
239pub fn set_projection_plane(h: u16) {
240 write_control!(26, h as i32 as u32);
241}
242
243pub fn set_depth_cue(dqa: i16, dqb: i32) {
246 write_control!(27, dqa as i32 as u32);
247 write_control!(28, dqb as u32);
248}
249
250#[doc(alias = "AVSZ")]
253#[doc(alias = "ZSF3")]
254#[doc(alias = "ZSF4")]
255pub fn set_average_z_weights(zsf3: i16, zsf4: i16) {
256 write_control!(29, zsf3 as i32 as u32);
257 write_control!(30, zsf4 as i32 as u32);
258}
259
260#[doc(alias = "RotTransPers")]
267pub fn project_vertex(v: Vec3I16) -> Projected {
268 write_data!(0, v.xy_packed());
269 write_data!(1, v.z_packed());
270 unsafe { ops::project_single() };
273 let sxy = read_data!(14);
274 let sz = read_data!(19) as u16;
275 Projected {
276 sx: sxy as i16,
277 sy: (sxy >> 16) as i16,
278 sz,
279 }
280}
281
282#[doc(alias = "RotTransPers3")]
288pub fn project_triangle(v0: Vec3I16, v1: Vec3I16, v2: Vec3I16) -> [Projected; 3] {
289 write_data!(0, v0.xy_packed());
291 write_data!(1, v0.z_packed());
292 write_data!(2, v1.xy_packed());
293 write_data!(3, v1.z_packed());
294 write_data!(4, v2.xy_packed());
295 write_data!(5, v2.z_packed());
296 unsafe { ops::project_triple() };
299 let sxy0 = read_data!(12);
302 let sxy1 = read_data!(13);
303 let sxy2 = read_data!(14);
304 let sz1 = read_data!(17) as u16;
305 let sz2 = read_data!(18) as u16;
306 let sz3 = read_data!(19) as u16;
307 [
308 Projected {
309 sx: sxy0 as i16,
310 sy: (sxy0 >> 16) as i16,
311 sz: sz1,
312 },
313 Projected {
314 sx: sxy1 as i16,
315 sy: (sxy1 >> 16) as i16,
316 sz: sz2,
317 },
318 Projected {
319 sx: sxy2 as i16,
320 sy: (sxy2 >> 16) as i16,
321 sz: sz3,
322 },
323 ]
324}
325
326#[doc(alias = "RotTrans")]
331pub fn transform_vertex(v: Vec3I16) -> Vec3I32 {
332 write_data!(0, v.xy_packed());
333 write_data!(1, v.z_packed());
334 unsafe { ops::rotate_translate_v0() };
336 Vec3I32::new(
337 read_data!(25) as i32,
338 read_data!(26) as i32,
339 read_data!(27) as i32,
340 )
341}
342
343#[inline(always)]
348pub fn transform_vertex_scheduled(v: Vec3I16) -> Vec3I32 {
349 #[cfg(target_arch = "mips")]
350 {
351 transform_vertex_mips(v)
352 }
353 #[cfg(not(target_arch = "mips"))]
354 {
355 transform_vertex(v)
356 }
357}
358
359#[inline]
368pub fn compose_rotation_scheduled(left: &Mat3I16, right: &Mat3I16) -> Mat3I16 {
369 load_rotation(left);
370 load_translation(Vec3I32::ZERO);
371
372 let c0 = transform_vertex_scheduled(Vec3I16::new(right.m[0][0], right.m[1][0], right.m[2][0]));
373 let c1 = transform_vertex_scheduled(Vec3I16::new(right.m[0][1], right.m[1][1], right.m[2][1]));
374 let c2 = transform_vertex_scheduled(Vec3I16::new(right.m[0][2], right.m[1][2], right.m[2][2]));
375 let clamp = |value: i32| value.clamp(i16::MIN as i32, i16::MAX as i32) as i16;
376
377 Mat3I16 {
378 m: [
379 [clamp(c0.x), clamp(c1.x), clamp(c2.x)],
380 [clamp(c0.y), clamp(c1.y), clamp(c2.y)],
381 [clamp(c0.z), clamp(c1.z), clamp(c2.z)],
382 ],
383 }
384}
385
386#[inline(always)]
392pub fn project_vertex_scheduled(v: Vec3I16) -> Projected {
393 #[cfg(target_arch = "mips")]
394 {
395 project_vertex_mips(v)
396 }
397 #[cfg(not(target_arch = "mips"))]
398 {
399 project_vertex(v)
400 }
401}
402
403#[inline(always)]
409pub fn project_triangle_scheduled(v0: Vec3I16, v1: Vec3I16, v2: Vec3I16) -> [Projected; 3] {
410 #[cfg(target_arch = "mips")]
411 {
412 project_triangle_mips(v0, v1, v2)
413 }
414 #[cfg(not(target_arch = "mips"))]
415 {
416 project_triangle(v0, v1, v2)
417 }
418}
419
420#[must_use = "call read() to collect the projected triple"]
429#[doc(alias = "RTPT")]
430pub struct ProjectTripleInFlight(#[cfg(not(target_arch = "mips"))] [Projected; 3]);
431
432#[inline(always)]
439#[doc(alias = "RTPT")]
440pub fn start_project_triple(v0: Vec3I16, v1: Vec3I16, v2: Vec3I16) -> ProjectTripleInFlight {
441 #[cfg(target_arch = "mips")]
442 {
443 let v0_xy = v0.xy_packed();
444 let v0_z = v0.z_packed();
445 let v1_xy = v1.xy_packed();
446 let v1_z = v1.z_packed();
447 let v2_xy = v2.xy_packed();
448 let v2_z = v2.z_packed();
449 unsafe {
454 asm!(
455 ".word {w0}",
457 ".word {w1}",
458 ".word {w2}",
459 ".word {w3}",
460 ".word {w4}",
461 ".word {w5}",
462 ".word 0",
467 ".word 0",
468 ".word {w6}",
470 w0 = const psx_hw::gte::mtc2(8, 0),
471 w1 = const psx_hw::gte::mtc2(9, 1),
472 w2 = const psx_hw::gte::mtc2(10, 2),
473 w3 = const psx_hw::gte::mtc2(11, 3),
474 w4 = const psx_hw::gte::mtc2(12, 4),
475 w5 = const psx_hw::gte::mtc2(13, 5),
476 w6 = const psx_hw::gte::command::RTPT,
477 in("$8") v0_xy,
478 in("$9") v0_z,
479 in("$10") v1_xy,
480 in("$11") v1_z,
481 in("$12") v2_xy,
482 in("$13") v2_z,
483 options(nostack, nomem, preserves_flags),
484 );
485 }
486 ProjectTripleInFlight()
487 }
488 #[cfg(not(target_arch = "mips"))]
489 {
490 ProjectTripleInFlight(project_triangle(v0, v1, v2))
491 }
492}
493
494impl ProjectTripleInFlight {
495 #[inline(always)]
504 pub fn read(self) -> [Projected; 3] {
505 #[cfg(target_arch = "mips")]
506 {
507 let sxy0: u32;
508 let sxy1: u32;
509 let sxy2: u32;
510 let sz1: u32;
511 let sz2: u32;
512 let sz3: u32;
513 unsafe {
519 asm!(
520 ".word {w0}",
524 ".word {w1}",
525 ".word {w2}",
526 ".word {w3}",
527 ".word {w4}",
528 ".word {w5}",
529 ".word 0",
530 w0 = const psx_hw::gte::mfc2(8, 12),
531 w1 = const psx_hw::gte::mfc2(9, 13),
532 w2 = const psx_hw::gte::mfc2(10, 14),
533 w3 = const psx_hw::gte::mfc2(11, 17),
534 w4 = const psx_hw::gte::mfc2(12, 18),
535 w5 = const psx_hw::gte::mfc2(13, 19),
536 out("$8") sxy0,
537 out("$9") sxy1,
538 out("$10") sxy2,
539 out("$11") sz1,
540 out("$12") sz2,
541 out("$13") sz3,
542 options(nostack, nomem, preserves_flags),
543 );
544 }
545 [
546 Projected {
547 sx: sxy0 as i16,
548 sy: (sxy0 >> 16) as i16,
549 sz: sz1 as u16,
550 },
551 Projected {
552 sx: sxy1 as i16,
553 sy: (sxy1 >> 16) as i16,
554 sz: sz2 as u16,
555 },
556 Projected {
557 sx: sxy2 as i16,
558 sy: (sxy2 >> 16) as i16,
559 sz: sz3 as u16,
560 },
561 ]
562 }
563 #[cfg(not(target_arch = "mips"))]
564 {
565 self.0
566 }
567 }
568}
569
570#[cfg(target_arch = "mips")]
571#[inline(always)]
572fn project_vertex_mips(v: Vec3I16) -> Projected {
573 let mut sxy = v.xy_packed();
574 let mut sz = v.z_packed();
575 unsafe {
579 asm!(
580 ".word {w0}",
582 ".word {w1}",
583 ".word 0",
588 ".word 0",
589 ".word {w2}",
591 ".word {w3}",
594 ".word {w4}",
595 ".word 0",
596 w0 = const psx_hw::gte::mtc2(8, 0),
597 w1 = const psx_hw::gte::mtc2(9, 1),
598 w2 = const psx_hw::gte::command::RTPS,
599 w3 = const psx_hw::gte::mfc2(8, 14),
600 w4 = const psx_hw::gte::mfc2(9, 19),
601 inlateout("$8") sxy,
602 inlateout("$9") sz,
603 options(nostack, nomem, preserves_flags),
604 );
605 }
606 Projected {
607 sx: sxy as i16,
608 sy: (sxy >> 16) as i16,
609 sz: sz as u16,
610 }
611}
612
613#[cfg(target_arch = "mips")]
614#[inline(always)]
615fn project_triangle_mips(v0: Vec3I16, v1: Vec3I16, v2: Vec3I16) -> [Projected; 3] {
616 let v0_xy = v0.xy_packed();
617 let v0_z = v0.z_packed();
618 let v1_xy = v1.xy_packed();
619 let v1_z = v1.z_packed();
620 let v2_xy = v2.xy_packed();
621 let v2_z = v2.z_packed();
622 let sxy0: u32;
623 let sxy1: u32;
624 let sxy2: u32;
625 let sz1: u32;
626 let sz2: u32;
627 let sz3: u32;
628 unsafe {
633 asm!(
634 ".word {w0}",
636 ".word {w1}",
637 ".word {w2}",
638 ".word {w3}",
639 ".word {w4}",
640 ".word {w5}",
641 ".word 0",
646 ".word 0",
647 ".word {w6}",
649 ".word {w7}",
654 ".word {w8}",
655 ".word {w9}",
656 ".word {w10}",
657 ".word {w11}",
658 ".word {w12}",
659 ".word 0",
660 w0 = const psx_hw::gte::mtc2(8, 0),
661 w1 = const psx_hw::gte::mtc2(9, 1),
662 w2 = const psx_hw::gte::mtc2(10, 2),
663 w3 = const psx_hw::gte::mtc2(11, 3),
664 w4 = const psx_hw::gte::mtc2(12, 4),
665 w5 = const psx_hw::gte::mtc2(13, 5),
666 w6 = const psx_hw::gte::command::RTPT,
667 w7 = const psx_hw::gte::mfc2(8, 12),
668 w8 = const psx_hw::gte::mfc2(9, 13),
669 w9 = const psx_hw::gte::mfc2(10, 14),
670 w10 = const psx_hw::gte::mfc2(11, 17),
671 w11 = const psx_hw::gte::mfc2(12, 18),
672 w12 = const psx_hw::gte::mfc2(13, 19),
673 inlateout("$8") v0_xy => sxy0,
674 inlateout("$9") v0_z => sxy1,
675 inlateout("$10") v1_xy => sxy2,
676 inlateout("$11") v1_z => sz1,
677 inlateout("$12") v2_xy => sz2,
678 inlateout("$13") v2_z => sz3,
679 options(nostack, nomem, preserves_flags),
680 );
681 }
682 [
683 Projected {
684 sx: sxy0 as i16,
685 sy: (sxy0 >> 16) as i16,
686 sz: sz1 as u16,
687 },
688 Projected {
689 sx: sxy1 as i16,
690 sy: (sxy1 >> 16) as i16,
691 sz: sz2 as u16,
692 },
693 Projected {
694 sx: sxy2 as i16,
695 sy: (sxy2 >> 16) as i16,
696 sz: sz3 as u16,
697 },
698 ]
699}
700
701#[cfg(target_arch = "mips")]
702#[inline(always)]
703fn transform_vertex_mips(v: Vec3I16) -> Vec3I32 {
704 let xy = v.xy_packed();
705 let z = v.z_packed();
706 let mac1: u32;
707 let mac2: u32;
708 let mac3: u32;
709 unsafe {
714 asm!(
715 ".word {w0}",
717 ".word {w1}",
718 ".word 0",
725 ".word 0",
726 ".word {w2}",
728 ".word {w3}",
732 ".word {w4}",
733 ".word {w5}",
734 ".word 0",
735 w0 = const psx_hw::gte::mtc2(8, 0),
736 w1 = const psx_hw::gte::mtc2(9, 1),
737 w2 = const psx_hw::gte::command::ROTATE_TRANSLATE_V0,
738 w3 = const psx_hw::gte::mfc2(8, 25),
739 w4 = const psx_hw::gte::mfc2(9, 26),
740 w5 = const psx_hw::gte::mfc2(10, 27),
741 inlateout("$8") xy => mac1,
742 inlateout("$9") z => mac2,
743 lateout("$10") mac3,
744 options(nostack, nomem, preserves_flags),
745 );
746 }
747 Vec3I32::new(mac1 as i32, mac2 as i32, mac3 as i32)
748}
749
750#[derive(Clone, Copy)]
753pub struct TransformProbe {
754 pub out: Vec3I32,
759 pub x_settled: i32,
762 pub tr: [i32; 3],
767}
768
769#[inline(always)]
779pub fn transform_vertex_probed(v: Vec3I16) -> TransformProbe {
780 #[cfg(target_arch = "mips")]
781 {
782 let xy = v.xy_packed();
783 let z = v.z_packed();
784 let mac1: u32;
785 let mac2: u32;
786 let mac3: u32;
787 let mac1_settled: u32;
788 let trx: u32;
789 let try_: u32;
790 let trz: u32;
791 unsafe {
797 asm!(
798 ".word {w0}",
801 ".word {w1}",
802 ".word {w2}",
803 ".word {w3}",
804 ".word {w4}",
805 ".word {w5}",
806 ".word 0",
807 ".word 0",
812 ".word 0",
813 ".word 0",
814 ".word 0",
815 ".word {w6}",
816 ".word {w7}",
817 ".word {w8}",
818 ".word {w9}",
819 ".word 0",
820 w0 = const psx_hw::gte::mtc2(8, 0),
821 w1 = const psx_hw::gte::mtc2(9, 1),
822 w2 = const psx_hw::gte::command::ROTATE_TRANSLATE_V0,
823 w3 = const psx_hw::gte::mfc2(8, 25),
824 w4 = const psx_hw::gte::mfc2(9, 26),
825 w5 = const psx_hw::gte::mfc2(10, 27),
826 w6 = const psx_hw::gte::mfc2(11, 25),
827 w7 = const psx_hw::gte::cfc2(12, 5),
828 w8 = const psx_hw::gte::cfc2(13, 6),
829 w9 = const psx_hw::gte::cfc2(14, 7),
830 inlateout("$8") xy => mac1,
831 inlateout("$9") z => mac2,
832 lateout("$10") mac3,
833 lateout("$11") mac1_settled,
834 lateout("$12") trx,
835 lateout("$13") try_,
836 lateout("$14") trz,
837 options(nostack, nomem, preserves_flags),
838 );
839 }
840 TransformProbe {
841 out: Vec3I32::new(mac1 as i32, mac2 as i32, mac3 as i32),
842 x_settled: mac1_settled as i32,
843 tr: [trx as i32, try_ as i32, trz as i32],
844 }
845 }
846 #[cfg(not(target_arch = "mips"))]
847 {
848 let out = transform_vertex(v);
849 TransformProbe {
850 out,
851 x_settled: out.x,
852 tr: [
853 read_control!(5) as i32,
854 read_control!(6) as i32,
855 read_control!(7) as i32,
856 ],
857 }
858 }
859}
860
861pub fn average_z_triangle() -> u16 {
865 unsafe { ops::average_z3() };
868 read_data!(7) as u16
869}
870
871#[inline(always)]
878pub fn average_cached_z3(depths: [u16; 3]) -> u16 {
879 #[cfg(target_arch = "mips")]
880 {
881 let mut otz = depths[0] as u32;
882 unsafe {
887 asm!(
888 ".word {w0}",
891 ".word {w1}",
892 ".word {w2}",
893 ".word 0",
894 ".word 0",
895 ".word {w3}",
896 ".word {w4}",
898 ".word 0",
899 w0 = const psx_hw::gte::mtc2(8, 17),
900 w1 = const psx_hw::gte::mtc2(9, 18),
901 w2 = const psx_hw::gte::mtc2(10, 19),
902 w3 = const psx_hw::gte::command::AVSZ3,
903 w4 = const psx_hw::gte::mfc2(8, 7),
904 inlateout("$8") otz,
905 in("$9") depths[1] as u32,
906 in("$10") depths[2] as u32,
907 options(nostack, nomem, preserves_flags),
908 );
909 }
910 otz as u16
911 }
912 #[cfg(not(target_arch = "mips"))]
913 {
914 write_data!(17, depths[0] as u32);
915 write_data!(18, depths[1] as u32);
916 write_data!(19, depths[2] as u32);
917 unsafe { ops::average_z3() };
919 read_data!(7) as u16
920 }
921}
922
923#[inline(always)]
929#[doc(alias = "OTZ")]
930pub fn classic_ordering_depth3_from_sum(sum: u32) -> u16 {
931 #[cfg(target_arch = "mips")]
932 {
933 let mut otz = sum;
934 unsafe {
938 asm!(
939 "sll $9, $8, 2",
940 "addu $9, $9, $8",
941 "sll $10, $9, 4",
942 "addu $9, $10, $9",
943 "sll $9, $9, 2",
944 "addu $9, $9, $8",
945 "srl $8, $9, 12",
946 inlateout("$8") otz,
947 lateout("$9") _,
948 lateout("$10") _,
949 options(nostack, nomem, preserves_flags),
950 );
951 }
952 otz as u16
953 }
954 #[cfg(not(target_arch = "mips"))]
955 {
956 ((sum * 0x155) >> 12) as u16
957 }
958}
959
960#[inline(always)]
965pub fn average_cached_z4(depths: [u16; 4]) -> u16 {
966 #[cfg(target_arch = "mips")]
967 {
968 let mut otz = depths[0] as u32;
969 unsafe {
974 asm!(
975 ".word {w0}",
978 ".word {w1}",
979 ".word {w2}",
980 ".word {w3}",
981 ".word 0",
982 ".word 0",
983 ".word {w4}",
984 ".word {w5}",
986 ".word 0",
987 w0 = const psx_hw::gte::mtc2(8, 16),
988 w1 = const psx_hw::gte::mtc2(9, 17),
989 w2 = const psx_hw::gte::mtc2(10, 18),
990 w3 = const psx_hw::gte::mtc2(11, 19),
991 w4 = const psx_hw::gte::command::AVSZ4,
992 w5 = const psx_hw::gte::mfc2(8, 7),
993 inlateout("$8") otz,
994 in("$9") depths[1] as u32,
995 in("$10") depths[2] as u32,
996 in("$11") depths[3] as u32,
997 options(nostack, nomem, preserves_flags),
998 );
999 }
1000 otz as u16
1001 }
1002 #[cfg(not(target_arch = "mips"))]
1003 {
1004 write_data!(16, depths[0] as u32);
1005 write_data!(17, depths[1] as u32);
1006 write_data!(18, depths[2] as u32);
1007 write_data!(19, depths[3] as u32);
1008 unsafe { ops::average_z4() };
1010 read_data!(7) as u16
1011 }
1012}
1013
1014#[inline]
1022#[doc(alias = "OTZ")]
1023#[doc(alias = "AverageZ3")]
1024pub fn average_z3_ordering_depth(depths: [u16; 3], zsf3: i16) -> u16 {
1025 let sum = depths[0] as i64 + depths[1] as i64 + depths[2] as i64;
1031 ((sum * zsf3 as i64) >> 12).clamp(0, u16::MAX as i64) as u16
1033}
1034
1035#[inline]
1041#[doc(alias = "OTZ")]
1042#[doc(alias = "AverageZ4")]
1043pub fn average_z4_ordering_depth(depths: [u16; 4], zsf4: i16) -> u16 {
1044 let sum = depths[0] as i64 + depths[1] as i64 + depths[2] as i64 + depths[3] as i64;
1046 ((sum * zsf4 as i64) >> 12).clamp(0, u16::MAX as i64) as u16
1048}
1049
1050#[inline(always)]
1051fn aabb_outer_support(mins: [i16; 3], maxs: [i16; 3], signbits: u8) -> Vec3I16 {
1052 Vec3I16::new(
1053 if signbits & 1 != 0 { mins[0] } else { maxs[0] },
1054 if signbits & 2 != 0 { mins[1] } else { maxs[1] },
1055 if signbits & 4 != 0 { mins[2] } else { maxs[2] },
1056 )
1057}
1058
1059#[cfg(target_arch = "mips")]
1061const RT_V0_NO_TRANSLATION: u32 = psx_hw::gte::mvmva(
1062 psx_hw::gte::Matrix::Rotation,
1063 psx_hw::gte::Vector::V0,
1064 psx_hw::gte::Translation::None,
1065);
1066
1067#[cfg(target_arch = "mips")]
1069const LLM_V0_NO_TRANSLATION: u32 = psx_hw::gte::mvmva(
1070 psx_hw::gte::Matrix::Light,
1071 psx_hw::gte::Vector::V0,
1072 psx_hw::gte::Translation::None,
1073);
1074
1075#[cfg(target_arch = "mips")]
1077const fn read_mac(n: u32) -> u32 {
1078 psx_hw::gte::mfc2(8, 25 + n)
1079}
1080
1081#[cfg(target_arch = "mips")]
1082#[inline(always)]
1083fn aabb_dot_mvmva<const OP: u32, const READ_MAC: u32>(v: Vec3I16) -> i32 {
1084 let mut dot = v.xy_packed();
1085 unsafe {
1092 asm!(
1093 ".word {w0}",
1094 ".word {w1}",
1095 ".word 0",
1098 ".word 0",
1099 ".word {op}",
1100 ".word {read_mac}",
1101 ".word 0",
1102 w0 = const psx_hw::gte::mtc2(8, 0),
1103 w1 = const psx_hw::gte::mtc2(9, 1),
1104 op = const OP,
1105 read_mac = const READ_MAC,
1106 inlateout("$8") dot,
1107 in("$9") v.z_packed(),
1108 options(nostack, nomem, preserves_flags),
1109 );
1110 }
1111 dot as i32
1112}
1113
1114#[inline(always)]
1115fn aabb_clip_dot(v: Vec3I16, _plane: &AabbClipPlane, index: usize) -> i32 {
1116 #[cfg(target_arch = "mips")]
1117 {
1118 match index {
1119 0 => aabb_dot_mvmva::<{ RT_V0_NO_TRANSLATION }, { read_mac(0) }>(v),
1120 1 => aabb_dot_mvmva::<{ RT_V0_NO_TRANSLATION }, { read_mac(1) }>(v),
1121 2 => aabb_dot_mvmva::<{ RT_V0_NO_TRANSLATION }, { read_mac(2) }>(v),
1122 3 => aabb_dot_mvmva::<{ LLM_V0_NO_TRANSLATION }, { read_mac(0) }>(v),
1123 _ => 0,
1124 }
1125 }
1126 #[cfg(not(target_arch = "mips"))]
1127 {
1128 let _ = index;
1129 _plane.normal[0] as i32 * v.x as i32
1130 + _plane.normal[1] as i32 * v.y as i32
1131 + _plane.normal[2] as i32 * v.z as i32
1132 }
1133}
1134
1135#[inline]
1144pub fn classify_aabb_clip4(
1145 mins: [i16; 3],
1146 maxs: [i16; 3],
1147 planes: &[AabbClipPlane; 4],
1148 mut clip_flags: u8,
1149) -> i32 {
1150 let mut index = 0usize;
1151 while index < 4 {
1152 let flag = 1u8 << index;
1153 if clip_flags & flag != 0 {
1154 let plane = &planes[index];
1155 let outer = aabb_outer_support(mins, maxs, plane.signbits);
1156 if aabb_clip_dot(outer, plane, index) < plane.distance {
1157 return -1;
1158 }
1159 let inner = aabb_outer_support(maxs, mins, plane.signbits);
1160 if aabb_clip_dot(inner, plane, index) >= plane.distance {
1161 clip_flags &= !flag;
1162 }
1163 }
1164 index += 1;
1165 }
1166 clip_flags as i32
1167}
1168
1169#[inline(always)]
1176pub fn is_aabb_outside_clip4(
1177 mins: [i16; 3],
1178 maxs: [i16; 3],
1179 planes: &[AabbClipPlane; 4],
1180 clip_flags: u8,
1181) -> bool {
1182 let mut index = 0usize;
1183 while index < 4 {
1184 let flag = 1u8 << index;
1185 if clip_flags & flag != 0 {
1186 let plane = &planes[index];
1187 let outer = aabb_outer_support(mins, maxs, plane.signbits);
1188 if aabb_clip_dot(outer, plane, index) < plane.distance {
1189 return true;
1190 }
1191 }
1192 index += 1;
1193 }
1194 false
1195}
1196
1197#[inline]
1210#[doc(alias = "NCLIP")]
1211#[doc(alias = "MAC0")]
1212pub fn screen_area(vertices: [(i16, i16); 3]) -> i32 {
1213 let (sx0, sy0) = (vertices[0].0 as i32, vertices[0].1 as i32);
1214 let (sx1, sy1) = (vertices[1].0 as i32, vertices[1].1 as i32);
1215 let (sx2, sy2) = (vertices[2].0 as i32, vertices[2].1 as i32);
1216 (sx1 - sx0) * (sy2 - sy0) - (sy1 - sy0) * (sx2 - sx0)
1220}
1221
1222#[inline(always)]
1228#[doc(alias = "NCLIP")]
1229#[doc(alias = "MAC0")]
1230pub fn screen_area_scheduled(vertices: [(i16, i16); 3]) -> i32 {
1231 #[cfg(target_arch = "mips")]
1232 {
1233 let sxy0 = pack_xy(vertices[0].0, vertices[0].1);
1234 let sxy1 = pack_xy(vertices[1].0, vertices[1].1);
1235 let mut area = pack_xy(vertices[2].0, vertices[2].1);
1236 unsafe {
1240 asm!(
1241 ".word {w0}",
1243 ".word {w1}",
1244 ".word {w2}",
1245 ".word 0",
1247 ".word 0",
1248 ".word {w3}",
1250 ".word 0",
1251 ".word 0",
1252 ".word 0",
1253 ".word 0",
1254 ".word 0",
1255 ".word 0",
1256 ".word 0",
1257 ".word 0",
1258 ".word {w4}",
1260 ".word 0",
1261 w0 = const psx_hw::gte::mtc2(8, 12),
1262 w1 = const psx_hw::gte::mtc2(9, 13),
1263 w2 = const psx_hw::gte::mtc2(10, 14),
1264 w3 = const psx_hw::gte::command::NCLIP,
1265 w4 = const psx_hw::gte::mfc2(10, 24),
1266 in("$8") sxy0,
1267 in("$9") sxy1,
1268 inlateout("$10") area,
1269 options(nostack, nomem, preserves_flags),
1270 );
1271 }
1272 area as i32
1273 }
1274 #[cfg(not(target_arch = "mips"))]
1275 {
1276 screen_area(vertices)
1277 }
1278}
1279
1280#[inline(always)]
1287pub fn screen_area_and_unpack_model_face_scheduled(
1288 vertices: [(i16, i16); 3],
1289 corner_words: [u32; 3],
1290) -> (i32, [u16; 3], u8) {
1291 #[cfg(target_arch = "mips")]
1292 {
1293 let sxy0 = pack_xy(vertices[0].0, vertices[0].1);
1294 let sxy1 = pack_xy(vertices[1].0, vertices[1].1);
1295 let mut area = pack_xy(vertices[2].0, vertices[2].1);
1296 let mut uv0 = corner_words[0];
1297 let mut uv1 = corner_words[1];
1298 let mut uv2 = corner_words[2];
1299 let palette_bank: u32;
1300 unsafe {
1304 asm!(
1305 ".word {w0}",
1307 ".word {w1}",
1308 ".word {w2}",
1309 ".word 0",
1310 ".word 0",
1311 ".word {w3}",
1312 "srl $14, $11, 14",
1315 "andi $14, $14, 3",
1316 "srl $11, $11, 16",
1317 "srl $12, $12, 16",
1318 "srl $13, $13, 16",
1319 ".word 0",
1320 ".word 0",
1321 ".word 0",
1322 ".word {w4}",
1324 ".word 0",
1325 w0 = const psx_hw::gte::mtc2(8, 12),
1326 w1 = const psx_hw::gte::mtc2(9, 13),
1327 w2 = const psx_hw::gte::mtc2(10, 14),
1328 w3 = const psx_hw::gte::command::NCLIP,
1329 w4 = const psx_hw::gte::mfc2(10, 24),
1330 in("$8") sxy0,
1331 in("$9") sxy1,
1332 inlateout("$10") area,
1333 inlateout("$11") uv0,
1334 inlateout("$12") uv1,
1335 inlateout("$13") uv2,
1336 lateout("$14") palette_bank,
1337 options(nostack, nomem, preserves_flags),
1338 );
1339 }
1340 (
1341 area as i32,
1342 [uv0 as u16, uv1 as u16, uv2 as u16],
1343 palette_bank as u8,
1344 )
1345 }
1346 #[cfg(not(target_arch = "mips"))]
1347 {
1348 (
1349 screen_area(vertices),
1350 [
1351 (corner_words[0] >> 16) as u16,
1352 (corner_words[1] >> 16) as u16,
1353 (corner_words[2] >> 16) as u16,
1354 ],
1355 ((corner_words[0] >> 14) & 3) as u8,
1356 )
1357 }
1358}
1359
1360#[inline(always)]
1369pub fn screen_area_and_average_cached_z3_scheduled(
1370 vertices: [(i16, i16); 3],
1371 depths: [u16; 3],
1372) -> (i32, u16) {
1373 #[cfg(target_arch = "mips")]
1374 {
1375 let sxy0 = pack_xy(vertices[0].0, vertices[0].1);
1376 let sxy1 = pack_xy(vertices[1].0, vertices[1].1);
1377 let mut area = pack_xy(vertices[2].0, vertices[2].1);
1378 let mut otz = depths[0] as u32;
1379 unsafe {
1384 asm!(
1385 ".word {w0}",
1387 ".word {w1}",
1388 ".word {w2}",
1389 ".word 0",
1390 ".word 0",
1391 ".word {w3}",
1392 ".word {w4}",
1395 ".word {w5}",
1396 ".word {w6}",
1397 ".word 0",
1398 ".word 0",
1399 ".word 0",
1400 ".word 0",
1401 ".word 0",
1402 ".word {w7}",
1405 ".word {w8}",
1406 ".word {w9}",
1407 ".word 0",
1408 w0 = const psx_hw::gte::mtc2(8, 12),
1409 w1 = const psx_hw::gte::mtc2(9, 13),
1410 w2 = const psx_hw::gte::mtc2(10, 14),
1411 w3 = const psx_hw::gte::command::NCLIP,
1412 w4 = const psx_hw::gte::mtc2(11, 17),
1413 w5 = const psx_hw::gte::mtc2(12, 18),
1414 w6 = const psx_hw::gte::mtc2(13, 19),
1415 w7 = const psx_hw::gte::mfc2(10, 24),
1416 w8 = const psx_hw::gte::command::AVSZ3,
1417 w9 = const psx_hw::gte::mfc2(11, 7),
1418 in("$8") sxy0,
1419 in("$9") sxy1,
1420 inlateout("$10") area,
1421 inlateout("$11") otz,
1422 in("$12") depths[1] as u32,
1423 in("$13") depths[2] as u32,
1424 options(nostack, nomem, preserves_flags),
1425 );
1426 }
1427 (area as i32, otz as u16)
1428 }
1429 #[cfg(not(target_arch = "mips"))]
1430 {
1431 (screen_area(vertices), average_cached_z3(depths))
1432 }
1433}
1434
1435#[inline(always)]
1443#[doc(alias = "OTZ")]
1444pub fn screen_area_and_classic_ordering_depth3_scheduled(
1445 vertices: [(i16, i16); 3],
1446 depths: [u16; 3],
1447) -> (i32, u16) {
1448 #[cfg(target_arch = "mips")]
1449 {
1450 let sxy0 = pack_xy(vertices[0].0, vertices[0].1);
1451 let sxy1 = pack_xy(vertices[1].0, vertices[1].1);
1452 let mut area = pack_xy(vertices[2].0, vertices[2].1);
1453 let mut otz = depths[0] as u32;
1454 let depth1 = depths[1] as u32;
1455 let depth2 = depths[2] as u32;
1456 unsafe {
1461 asm!(
1462 ".word {w0}",
1464 ".word {w1}",
1465 ".word {w2}",
1466 ".word 0",
1467 ".word 0",
1468 ".word {w3}",
1470 "addu $11, $11, $12",
1473 "addu $11, $11, $13",
1474 "sll $12, $11, 2",
1475 "addu $12, $12, $11",
1476 "sll $13, $12, 4",
1477 "addu $12, $13, $12",
1478 "sll $12, $12, 2",
1479 "addu $12, $12, $11",
1480 ".word {w4}",
1483 "srl $11, $12, 12",
1484 w0 = const psx_hw::gte::mtc2(8, 12),
1485 w1 = const psx_hw::gte::mtc2(9, 13),
1486 w2 = const psx_hw::gte::mtc2(10, 14),
1487 w3 = const psx_hw::gte::command::NCLIP,
1488 w4 = const psx_hw::gte::mfc2(10, 24),
1489 in("$8") sxy0,
1490 in("$9") sxy1,
1491 inlateout("$10") area,
1492 inlateout("$11") otz,
1493 inlateout("$12") depth1 => _,
1494 inlateout("$13") depth2 => _,
1495 options(nostack, nomem, preserves_flags),
1496 );
1497 }
1498 (area as i32, otz as u16)
1499 }
1500 #[cfg(not(target_arch = "mips"))]
1501 {
1502 let sum = depths[0] as u32 + depths[1] as u32 + depths[2] as u32;
1503 (screen_area(vertices), ((sum * 0x155) >> 12) as u16)
1504 }
1505}
1506
1507pub fn is_screen_triangle_back_facing(vertices: [(i16, i16); 3]) -> bool {
1513 screen_area(vertices) <= 0
1514}
1515
1516#[doc(alias = "FLAG")]
1520pub fn error_flags() -> u32 {
1521 read_control!(31)
1522}
1523
1524#[deprecated(note = "renamed to `start_project_triple`")]
1526#[inline(always)]
1527pub fn rtpt_kick(v0: Vec3I16, v1: Vec3I16, v2: Vec3I16) -> ProjectTripleInFlight {
1528 start_project_triple(v0, v1, v2)
1529}
1530
1531#[deprecated(note = "renamed to `classic_ordering_depth3_from_sum`")]
1533#[inline(always)]
1534pub fn classic_otz3_from_sum(sum: u32) -> u16 {
1535 classic_ordering_depth3_from_sum(sum)
1536}
1537
1538#[deprecated(note = "renamed to `is_aabb_outside_clip4`")]
1540#[inline(always)]
1541pub fn aabb_outside_clip4(
1542 mins: [i16; 3],
1543 maxs: [i16; 3],
1544 planes: &[AabbClipPlane; 4],
1545 clip_flags: u8,
1546) -> bool {
1547 is_aabb_outside_clip4(mins, maxs, planes, clip_flags)
1548}
1549
1550#[deprecated(note = "renamed to `screen_area_scheduled`")]
1552#[inline(always)]
1553pub fn screen_area_mac0_scheduled(vertices: [(i16, i16); 3]) -> i32 {
1554 screen_area_scheduled(vertices)
1555}
1556
1557#[cfg(all(test, not(target_arch = "mips")))]
1558mod host_smoke {
1559 use super::*;
1567 use crate::host;
1568
1569 fn install_identity() {
1570 load_rotation(&Mat3I16::IDENTITY);
1571 load_translation(Vec3I32::ZERO);
1572 set_screen_offset(160 << 16, 120 << 16);
1573 set_projection_plane(200);
1574 }
1575
1576 #[test]
1577 fn rtps_through_host_shim_projects_an_in_front_vertex() {
1578 host::reset();
1579 install_identity();
1580 let projected = project_vertex(Vec3I16::new(0, 0, 1024));
1584 assert_eq!(projected.sx, 160);
1585 assert_eq!(projected.sy, 120);
1586 assert!(
1587 projected.sz > 0,
1588 "near-plane vertex must yield non-zero depth"
1589 );
1590 }
1591
1592 #[test]
1593 fn rtpt_through_host_shim_matches_three_separate_rtps_calls() {
1594 host::reset();
1595 install_identity();
1596 let a = Vec3I16::new(-256, 0, 1024);
1597 let b = Vec3I16::new(256, 0, 1024);
1598 let c = Vec3I16::new(0, 256, 1024);
1599
1600 let batch = project_triangle(a, b, c);
1601
1602 host::reset();
1603 install_identity();
1604 let p_a = project_vertex(a);
1605 let p_b = project_vertex(b);
1606 let p_c = project_vertex(c);
1607
1608 assert_eq!(batch[0], p_a);
1609 assert_eq!(batch[1], p_b);
1610 assert_eq!(batch[2], p_c);
1611 }
1612
1613 #[test]
1614 fn mvmva_transform_through_host_shim_applies_rt_and_tr() {
1615 host::reset();
1616 load_rotation(&Mat3I16::IDENTITY);
1617 load_translation(Vec3I32::new(10, -20, 30));
1618
1619 let transformed = transform_vertex(Vec3I16::new(100, 200, 300));
1620
1621 assert_eq!(transformed, Vec3I32::new(110, 180, 330));
1622 }
1623
1624 #[test]
1625 fn scheduled_rotation_compose_matches_cpu_matrix_product() {
1626 host::reset();
1627 let left = Mat3I16::rotate_z(37).mul(&Mat3I16::rotate_y(91));
1628 let right = Mat3I16::rotate_y(13)
1629 .mul(&Mat3I16::rotate_x(55))
1630 .scale_columns_q12([3072, 4096, 5120]);
1631
1632 assert_eq!(compose_rotation_scheduled(&left, &right), left.mul(&right));
1633 }
1634
1635 #[test]
1636 fn classic_normalize_matches_reference_table_results() {
1637 for (input, vector, xy_squared, squared) in [
1638 ([1, 0, 0], [4096, 0, 0], 1, 1),
1639 ([3, 4, 0], [2457, 3276, 0], 25, 25),
1640 ([10, 20, 30], [1097, 2195, 3293], 500, 1400),
1641 ([-10, 20, 30], [-1098, 2195, 3293], 500, 1400),
1642 ([100, 50, -20], [3610, 1805, -723], 12_500, 12_900),
1643 ([600, 0, 200], [3898, 0, 1299], 360_000, 400_000),
1644 ] {
1645 host::reset();
1646 let result = normalize_classic_q12_scheduled(Vec3I32::new(
1647 input[0] << 12,
1648 input[1] << 12,
1649 input[2] << 12,
1650 ));
1651 assert_eq!(
1652 result,
1653 ClassicNormalizedVector {
1654 vector: Vec3I16::new(vector[0], vector[1], vector[2]),
1655 xy_squared,
1656 squared,
1657 }
1658 );
1659 }
1660
1661 host::reset();
1662 assert_eq!(
1663 normalize_classic_q12_scheduled(Vec3I32::ZERO),
1664 ClassicNormalizedVector::default()
1665 );
1666 }
1667
1668 #[test]
1669 fn scheduled_nclip_matches_software_area_on_host() {
1670 let vertices = [(-320, 112), (47, -91), (511, 230)];
1671 assert_eq!(screen_area_scheduled(vertices), screen_area(vertices));
1672 }
1673
1674 #[test]
1675 fn scheduled_nclip_face_unpack_matches_packed_record() {
1676 let vertices = [(-320, 112), (47, -91), (511, 230)];
1677 let corners = [0xabcd_c123, 0x0123_4567, 0xfedc_89ab];
1678 assert_eq!(
1679 screen_area_and_unpack_model_face_scheduled(vertices, corners),
1680 (
1681 screen_area(vertices),
1682 [0xabcd, 0x0123, 0xfedc],
1683 ((0xc123 >> 14) & 3) as u8,
1684 ),
1685 );
1686 }
1687
1688 #[test]
1689 fn cached_average_z_matches_avsz_saturation_rules() {
1690 assert_eq!(average_z3_ordering_depth([100, 200, 300], 1_365), 199);
1691 assert_eq!(average_z4_ordering_depth([100, 200, 300, 400], 1_024), 250);
1692 assert_eq!(average_z3_ordering_depth([u16::MAX; 3], i16::MAX), u16::MAX);
1693 assert_eq!(average_z4_ordering_depth([u16::MAX; 4], -1), 0);
1694
1695 set_average_z_weights(1_365, 1_024);
1696 assert_eq!(average_cached_z3([100, 200, 300]), 199);
1697 assert_eq!(average_cached_z4([100, 200, 300, 400]), 250);
1698 assert_eq!(classic_ordering_depth3_from_sum(100 + 200 + 300), 49);
1699
1700 let vertices = [(-320, 112), (47, -91), (511, 230)];
1701 assert_eq!(
1702 screen_area_and_average_cached_z3_scheduled(vertices, [100, 200, 300]),
1703 (screen_area(vertices), 199),
1704 );
1705 assert_eq!(
1706 screen_area_and_classic_ordering_depth3_scheduled(vertices, [100, 200, 300]),
1707 (screen_area(vertices), 49),
1708 );
1709 }
1710
1711 #[test]
1712 fn four_plane_aabb_clip_matches_scalar_support_points() {
1713 let plane = |normal: [i16; 3], distance| AabbClipPlane {
1714 normal,
1715 kind: 0,
1716 signbits: (normal[0] < 0) as u8
1717 | (((normal[1] < 0) as u8) << 1)
1718 | (((normal[2] < 0) as u8) << 2),
1719 distance,
1720 };
1721 let planes = [
1722 plane([4096, 1024, -512], -3000),
1723 plane([-2048, 4096, 256], 7000),
1724 plane([300, -900, 4096], -12000),
1725 plane([-700, -500, -4096], 16000),
1726 ];
1727 load_aabb_clip4(&planes);
1728
1729 let scalar = |mins: [i16; 3], maxs: [i16; 3], mut flags: u8| {
1730 for (index, plane) in planes.iter().enumerate() {
1731 let flag = 1u8 << index;
1732 if flags & flag == 0 {
1733 continue;
1734 }
1735 let outer = [
1736 if plane.normal[0] < 0 {
1737 mins[0]
1738 } else {
1739 maxs[0]
1740 },
1741 if plane.normal[1] < 0 {
1742 mins[1]
1743 } else {
1744 maxs[1]
1745 },
1746 if plane.normal[2] < 0 {
1747 mins[2]
1748 } else {
1749 maxs[2]
1750 },
1751 ];
1752 let inner = [
1753 if plane.normal[0] < 0 {
1754 maxs[0]
1755 } else {
1756 mins[0]
1757 },
1758 if plane.normal[1] < 0 {
1759 maxs[1]
1760 } else {
1761 mins[1]
1762 },
1763 if plane.normal[2] < 0 {
1764 maxs[2]
1765 } else {
1766 mins[2]
1767 },
1768 ];
1769 let dot = |point: [i16; 3]| {
1770 plane.normal[0] as i32 * point[0] as i32
1771 + plane.normal[1] as i32 * point[1] as i32
1772 + plane.normal[2] as i32 * point[2] as i32
1773 };
1774 if dot(outer) < plane.distance {
1775 return -1;
1776 }
1777 if dot(inner) >= plane.distance {
1778 flags &= !flag;
1779 }
1780 }
1781 flags as i32
1782 };
1783
1784 for (mins, maxs, flags) in [
1785 ([-10, -20, -30], [40, 50, 60], 0x0f),
1786 ([100, 200, 300], [120, 240, 360], 0x0f),
1787 ([-32000, -100, 20], [-30000, 100, 80], 0x05),
1788 ] {
1789 let expected = scalar(mins, maxs, flags);
1790 assert_eq!(classify_aabb_clip4(mins, maxs, &planes, flags), expected);
1791 assert_eq!(
1792 is_aabb_outside_clip4(mins, maxs, &planes, flags),
1793 expected < 0,
1794 );
1795 }
1796 assert_eq!(core::mem::size_of::<AabbClipPlane>(), 12);
1797 assert_eq!(core::mem::align_of::<AabbClipPlane>(), 4);
1798 }
1799
1800 #[test]
1805 fn classic_normalize_matches_previous_results_digest() {
1806 let mut seed = 0x1234_5678u32;
1807 let mut next = move || {
1808 seed = seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
1809 seed
1810 };
1811 let mut hash = 0xCBF2_9CE4_8422_2325u64;
1812 let mut eat = |value: i32| {
1813 for byte in value.to_le_bytes() {
1814 hash ^= u64::from(byte);
1815 hash = hash.wrapping_mul(0x0000_0100_0000_01B3);
1816 }
1817 };
1818 for i in 0..40_000u32 {
1819 let mut component = || (next() as i32) >> (next() % 32);
1822 let input = Vec3I32::new(component(), component(), component());
1823 let input = if i % 7 == 0 {
1824 Vec3I32::new(input.x, 0, 0)
1825 } else {
1826 input
1827 };
1828 host::reset();
1829 let out = normalize_classic_q12_scheduled(input);
1830 eat(i32::from(out.vector.x));
1831 eat(i32::from(out.vector.y));
1832 eat(i32::from(out.vector.z));
1833 eat(out.xy_squared);
1834 eat(out.squared);
1835 }
1836 assert_eq!(hash, 0x803B_4A0D_7EA1_0CB5);
1837 }
1838
1839 const PREVIOUS_TABLE: [i16; 192] = [
1841 4096, 4064, 4033, 4003, 3973, 3944, 3916, 3888, 3861, 3835, 3809, 3783, 3758, 3734, 3710,
1842 3686, 3663, 3640, 3618, 3596, 3575, 3554, 3533, 3513, 3493, 3473, 3454, 3435, 3416, 3397,
1843 3379, 3361, 3344, 3327, 3310, 3293, 3276, 3260, 3244, 3228, 3213, 3197, 3182, 3167, 3153,
1844 3138, 3124, 3110, 3096, 3082, 3069, 3055, 3042, 3029, 3016, 3003, 2991, 2978, 2966, 2954,
1845 2942, 2930, 2919, 2907, 2896, 2885, 2873, 2862, 2852, 2841, 2830, 2820, 2809, 2799, 2789,
1846 2779, 2769, 2759, 2749, 2740, 2730, 2721, 2711, 2702, 2693, 2684, 2675, 2666, 2657, 2649,
1847 2640, 2631, 2623, 2615, 2606, 2598, 2590, 2582, 2574, 2566, 2558, 2550, 2543, 2535, 2528,
1848 2520, 2513, 2505, 2498, 2491, 2484, 2477, 2469, 2462, 2456, 2449, 2442, 2435, 2428, 2422,
1849 2415, 2409, 2402, 2396, 2389, 2383, 2377, 2371, 2364, 2358, 2352, 2346, 2340, 2334, 2328,
1850 2322, 2317, 2311, 2305, 2299, 2294, 2288, 2283, 2277, 2272, 2266, 2261, 2255, 2250, 2245,
1851 2239, 2234, 2229, 2224, 2219, 2214, 2209, 2204, 2199, 2194, 2189, 2184, 2179, 2174, 2170,
1852 2165, 2160, 2155, 2151, 2146, 2142, 2137, 2133, 2128, 2124, 2119, 2115, 2110, 2106, 2102,
1853 2097, 2093, 2089, 2084, 2080, 2076, 2072, 2068, 2064, 2060, 2056, 2052,
1854 ];
1855
1856 #[test]
1857 fn generated_rsqrt_table_matches_the_previous_table() {
1858 assert_eq!(RSQRT_Q12_FROM_64, PREVIOUS_TABLE);
1859 }
1860}