1use crate::material::{
12 BlendMode, TextureMaterial, TexturedGouraudPacketMaterial, TexturedPacketMaterial,
13};
14use psx_hw::gpu::packet::{self, SEMI_TRANSPARENT};
15use psx_hw::gpu::{gp0, pack_color, pack_texcoord, pack_vertex, pack_xy};
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
19#[repr(C, align(4))]
20pub struct TriFlat {
21 pub tag: u32,
23 pub color_cmd: u32,
25 pub v0: u32,
27 pub v1: u32,
29 pub v2: u32,
31}
32
33impl TriFlat {
34 pub const WORDS: u8 = 4;
36
37 pub const fn new(verts: [(i16, i16); 3], r: u8, g: u8, b: u8) -> Self {
39 Self {
40 tag: 0,
41 color_cmd: gp0::polygon_opcode(false, false, false, false, false) | pack_color(r, g, b),
42 v0: pack_vertex(verts[0].0, verts[0].1),
43 v1: pack_vertex(verts[1].0, verts[1].1),
44 v2: pack_vertex(verts[2].0, verts[2].1),
45 }
46 }
47
48 pub const fn translucent(mut self) -> Self {
51 self.color_cmd |= SEMI_TRANSPARENT;
52 self
53 }
54}
55
56#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
58#[repr(C, align(4))]
59pub struct TriGouraud {
60 pub tag: u32,
62 pub color0_cmd: u32,
64 pub v0: u32,
66 pub color1: u32,
68 pub v1: u32,
70 pub color2: u32,
72 pub v2: u32,
74}
75
76impl TriGouraud {
77 pub const WORDS: u8 = 6;
79
80 pub const fn new(verts: [(i16, i16); 3], colors: [(u8, u8, u8); 3]) -> Self {
82 let (r0, g0, b0) = colors[0];
83 let (r1, g1, b1) = colors[1];
84 let (r2, g2, b2) = colors[2];
85 Self {
86 tag: 0,
87 color0_cmd: gp0::polygon_opcode(true, false, false, false, false)
88 | pack_color(r0, g0, b0),
89 v0: pack_vertex(verts[0].0, verts[0].1),
90 color1: pack_color(r1, g1, b1),
91 v1: pack_vertex(verts[1].0, verts[1].1),
92 color2: pack_color(r2, g2, b2),
93 v2: pack_vertex(verts[2].0, verts[2].1),
94 }
95 }
96
97 pub const fn translucent(mut self) -> Self {
100 self.color0_cmd |= SEMI_TRANSPARENT;
101 self
102 }
103}
104
105#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
107#[repr(C, align(4))]
108pub struct QuadFlat {
109 pub tag: u32,
111 pub color_cmd: u32,
113 pub v0: u32,
115 pub v1: u32,
117 pub v2: u32,
119 pub v3: u32,
121}
122
123impl QuadFlat {
124 pub const WORDS: u8 = 5;
126
127 pub const fn new(verts: [(i16, i16); 4], r: u8, g: u8, b: u8) -> Self {
129 Self {
130 tag: 0,
131 color_cmd: gp0::polygon_opcode(false, true, false, false, false) | pack_color(r, g, b),
132 v0: pack_vertex(verts[0].0, verts[0].1),
133 v1: pack_vertex(verts[1].0, verts[1].1),
134 v2: pack_vertex(verts[2].0, verts[2].1),
135 v3: pack_vertex(verts[3].0, verts[3].1),
136 }
137 }
138
139 pub const fn translucent(mut self) -> Self {
142 self.color_cmd |= SEMI_TRANSPARENT;
143 self
144 }
145
146 pub const fn rect(origin: (i16, i16), size: (u16, u16), color: (u8, u8, u8)) -> Self {
152 let (x0, y0) = origin;
153 let x1 = x0.wrapping_add(size.0 as i16);
154 let y1 = y0.wrapping_add(size.1 as i16);
155 Self::new(
156 [(x0, y0), (x1, y0), (x0, y1), (x1, y1)],
157 color.0,
158 color.1,
159 color.2,
160 )
161 }
162}
163
164#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
168#[repr(C, align(4))]
169pub struct RectFlat {
170 pub tag: u32,
172 pub color_cmd: u32,
174 pub xy: u32,
176 pub wh: u32,
178}
179
180impl RectFlat {
181 pub const WORDS: u8 = 3;
183
184 pub const fn new(x: i16, y: i16, w: u16, h: u16, r: u8, g: u8, b: u8) -> Self {
186 Self {
187 tag: 0,
188 color_cmd: packet::FLAT_RECT | pack_color(r, g, b),
189 xy: pack_vertex(x, y),
190 wh: pack_xy(w, h),
191 }
192 }
193}
194
195#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
202#[repr(C, align(4))]
203pub struct FillRect {
204 pub tag: u32,
206 pub color_command: u32,
208 pub origin: u32,
210 pub size: u32,
212}
213
214impl FillRect {
215 pub const WORDS: u8 = 3;
217
218 pub const fn new(origin: (u16, u16), size: (u16, u16), color: (u8, u8, u8)) -> Self {
220 Self {
221 tag: 0,
222 color_command: gp0::fill_rect(color.0, color.1, color.2),
223 origin: pack_xy(origin.0, origin.1),
224 size: pack_xy(size.0, size.1),
225 }
226 }
227}
228
229#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
236#[repr(C, align(4))]
237pub struct QuadGouraud {
238 pub tag: u32,
240 pub color0_cmd: u32,
242 pub v0: u32,
244 pub color1: u32,
246 pub v1: u32,
248 pub color2: u32,
250 pub v2: u32,
252 pub color3: u32,
254 pub v3: u32,
256}
257
258impl QuadGouraud {
259 pub const WORDS: u8 = 8;
261
262 pub const fn new(verts: [(i16, i16); 4], colors: [(u8, u8, u8); 4]) -> Self {
264 let (r0, g0, b0) = colors[0];
265 let (r1, g1, b1) = colors[1];
266 let (r2, g2, b2) = colors[2];
267 let (r3, g3, b3) = colors[3];
268 Self {
269 tag: 0,
270 color0_cmd: gp0::polygon_opcode(true, true, false, false, false)
271 | pack_color(r0, g0, b0),
272 v0: pack_vertex(verts[0].0, verts[0].1),
273 color1: pack_color(r1, g1, b1),
274 v1: pack_vertex(verts[1].0, verts[1].1),
275 color2: pack_color(r2, g2, b2),
276 v2: pack_vertex(verts[2].0, verts[2].1),
277 color3: pack_color(r3, g3, b3),
278 v3: pack_vertex(verts[3].0, verts[3].1),
279 }
280 }
281
282 pub const fn translucent(mut self) -> Self {
285 self.color0_cmd |= SEMI_TRANSPARENT;
286 self
287 }
288}
289
290#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
298#[repr(C, align(4))]
299pub struct QuadGouraudBlended {
300 pub tag: u32,
302 pub draw_mode: u32,
304 pub color0_cmd: u32,
306 pub v0: u32,
308 pub color1: u32,
310 pub v1: u32,
312 pub color2: u32,
314 pub v2: u32,
316 pub color3: u32,
318 pub v3: u32,
320}
321
322impl QuadGouraudBlended {
323 pub const WORDS: u8 = 9;
325
326 pub const fn new(
328 verts: [(i16, i16); 4],
329 colors: [(u8, u8, u8); 4],
330 blend_mode: BlendMode,
331 ) -> Self {
332 let (r0, g0, b0) = colors[0];
333 let (r1, g1, b1) = colors[1];
334 let (r2, g2, b2) = colors[2];
335 let (r3, g3, b3) = colors[3];
336 Self {
337 tag: 0,
338 draw_mode: TextureMaterial::blended(0, 0, (0, 0, 0), blend_mode).draw_mode_word(),
339 color0_cmd: gp0::polygon_opcode(true, true, false, true, false)
340 | pack_color(r0, g0, b0),
341 v0: pack_vertex(verts[0].0, verts[0].1),
342 color1: pack_color(r1, g1, b1),
343 v1: pack_vertex(verts[1].0, verts[1].1),
344 color2: pack_color(r2, g2, b2),
345 v2: pack_vertex(verts[2].0, verts[2].1),
346 color3: pack_color(r3, g3, b3),
347 v3: pack_vertex(verts[3].0, verts[3].1),
348 }
349 }
350}
351
352#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
356#[repr(C, align(4))]
357pub struct LineMono {
358 pub tag: u32,
360 pub color_cmd: u32,
362 pub v0: u32,
364 pub v1: u32,
366}
367
368impl LineMono {
369 pub const WORDS: u8 = 3;
371
372 pub const fn new(x0: i16, y0: i16, x1: i16, y1: i16, r: u8, g: u8, b: u8) -> Self {
374 Self {
375 tag: 0,
376 color_cmd: packet::FLAT_LINE | pack_color(r, g, b),
377 v0: pack_vertex(x0, y0),
378 v1: pack_vertex(x1, y1),
379 }
380 }
381
382 pub const fn translucent(mut self) -> Self {
385 self.color_cmd |= SEMI_TRANSPARENT;
386 self
387 }
388}
389
390#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
394#[repr(C, align(4))]
395pub struct LineGouraud {
396 pub tag: u32,
398 pub color0_command: u32,
400 pub v0: u32,
402 pub color1: u32,
404 pub v1: u32,
406}
407
408impl LineGouraud {
409 pub const WORDS: u8 = 4;
411
412 pub const fn new(
414 from: (i16, i16),
415 from_color: (u8, u8, u8),
416 to: (i16, i16),
417 to_color: (u8, u8, u8),
418 ) -> Self {
419 Self {
420 tag: 0,
421 color0_command: packet::SHADED_LINE
422 | pack_color(from_color.0, from_color.1, from_color.2),
423 v0: pack_vertex(from.0, from.1),
424 color1: pack_color(to_color.0, to_color.1, to_color.2),
425 v1: pack_vertex(to.0, to.1),
426 }
427 }
428
429 pub const fn translucent(mut self) -> Self {
432 self.color0_command |= SEMI_TRANSPARENT;
433 self
434 }
435}
436
437#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
445#[repr(C, align(4))]
446pub struct TriTextured {
447 pub tag: u32,
449 pub tex_window: u32,
451 pub color_cmd: u32,
453 pub v0: u32,
455 pub uv0_clut: u32,
457 pub v1: u32,
459 pub uv1_tpage: u32,
461 pub v2: u32,
463 pub uv2: u32,
465}
466
467impl TriTextured {
468 pub const WORDS: u8 = 8;
470
471 pub const fn new(
474 verts: [(i16, i16); 3],
475 uvs: [(u8, u8); 3],
476 clut: u16,
477 tpage: u16,
478 tint: (u8, u8, u8),
479 ) -> Self {
480 Self::with_material(verts, uvs, TextureMaterial::opaque(clut, tpage, tint))
481 }
482
483 pub const fn with_material(
485 verts: [(i16, i16); 3],
486 uvs: [(u8, u8); 3],
487 material: TextureMaterial,
488 ) -> Self {
489 let (u0, v0) = uvs[0];
490 let (u1, v1) = uvs[1];
491 let (u2, v2) = uvs[2];
492 let clut = material.clut_word();
493 let tpage = material.texture_page_word();
494 Self {
495 tag: 0,
496 tex_window: material.texture_window_word(),
497 color_cmd: material.flat_textured_polygon_header(false),
498 v0: pack_vertex(verts[0].0, verts[0].1),
499 uv0_clut: pack_texcoord(u0, v0, clut),
500 v1: pack_vertex(verts[1].0, verts[1].1),
501 uv1_tpage: pack_texcoord(u1, v1, tpage),
502 v2: pack_vertex(verts[2].0, verts[2].1),
503 uv2: pack_texcoord(u2, v2, 0),
504 }
505 }
506
507 #[deprecated(note = "identical to `with_material`")]
509 #[inline(always)]
510 pub const fn with_material_packet_texcoords(
511 verts: [(i16, i16); 3],
512 uvs: [(u8, u8); 3],
513 material: TextureMaterial,
514 ) -> Self {
515 Self::with_material(verts, uvs, material)
516 }
517
518 pub const fn with_material_packed_uv_words(
522 verts: [(i16, i16); 3],
523 uv_words: [u16; 3],
524 material: TextureMaterial,
525 ) -> Self {
526 let clut = material.clut_word();
527 let tpage = material.texture_page_word();
528 Self {
529 tag: 0,
530 tex_window: material.texture_window_word(),
531 color_cmd: material.flat_textured_polygon_header(false),
532 v0: pack_vertex(verts[0].0, verts[0].1),
533 uv0_clut: (uv_words[0] as u32) | ((clut as u32) << 16),
534 v1: pack_vertex(verts[1].0, verts[1].1),
535 uv1_tpage: (uv_words[1] as u32) | ((tpage as u32) << 16),
536 v2: pack_vertex(verts[2].0, verts[2].1),
537 uv2: uv_words[2] as u32,
538 }
539 }
540
541 pub const fn with_packet_material_packed_uv_words(
543 verts: [(i16, i16); 3],
544 uv_words: [u16; 3],
545 material: TexturedPacketMaterial,
546 ) -> Self {
547 Self {
548 tag: 0,
549 tex_window: material.tex_window_word,
550 color_cmd: material.color_command_word,
551 v0: pack_vertex(verts[0].0, verts[0].1),
552 uv0_clut: (uv_words[0] as u32) | material.clut_high_word,
553 v1: pack_vertex(verts[1].0, verts[1].1),
554 uv1_tpage: (uv_words[1] as u32) | material.tpage_high_word,
555 v2: pack_vertex(verts[2].0, verts[2].1),
556 uv2: uv_words[2] as u32,
557 }
558 }
559}
560
561#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
566#[repr(C, align(4))]
567pub struct ClassicTriTextured {
568 pub tag: u32,
570 pub color_cmd: u32,
572 pub v0: u32,
574 pub uv0_clut: u32,
576 pub v1: u32,
578 pub uv1_tpage: u32,
580 pub v2: u32,
582 pub uv2: u32,
584}
585
586impl ClassicTriTextured {
587 pub const WORDS: u8 = 7;
589
590 pub const fn with_staged_slot(
593 verts: [(i16, i16); 3],
594 uv_words: [u16; 3],
595 tint: u32,
596 clut: u16,
597 tpage: u16,
598 ot_slot: u16,
599 ) -> Self {
600 Self {
601 tag: ((Self::WORDS as u32) << 24) | ot_slot as u32,
602 color_cmd: packet::FLAT_TEXTURED_TRIANGLE | (tint & 0x00ff_ffff),
603 v0: pack_vertex(verts[0].0, verts[0].1),
604 uv0_clut: uv_words[0] as u32 | ((clut as u32) << 16),
605 v1: pack_vertex(verts[1].0, verts[1].1),
606 uv1_tpage: uv_words[1] as u32 | ((tpage as u32) << 16),
607 v2: pack_vertex(verts[2].0, verts[2].1),
608 uv2: uv_words[2] as u32,
609 }
610 }
611}
612
613#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
624#[repr(C, align(4))]
625pub struct TriTexturedGouraud {
626 pub tag: u32,
628 pub tex_window: u32,
630 pub color0_cmd: u32,
633 pub v0: u32,
635 pub uv0_clut: u32,
637 pub color1: u32,
639 pub v1: u32,
641 pub uv1_tpage: u32,
643 pub color2: u32,
645 pub v2: u32,
647 pub uv2: u32,
649}
650
651impl TriTexturedGouraud {
652 pub const WORDS: u8 = 10;
654
655 pub const fn new(
659 verts: [(i16, i16); 3],
660 uvs: [(u8, u8); 3],
661 colors: [(u8, u8, u8); 3],
662 clut: u16,
663 tpage: u16,
664 ) -> Self {
665 Self::with_material(verts, uvs, colors, TextureMaterial::new(clut, tpage))
666 }
667
668 pub const fn with_material(
670 verts: [(i16, i16); 3],
671 uvs: [(u8, u8); 3],
672 colors: [(u8, u8, u8); 3],
673 material: TextureMaterial,
674 ) -> Self {
675 let (r0, g0, b0) = colors[0];
676 let (r1, g1, b1) = colors[1];
677 let (r2, g2, b2) = colors[2];
678 let (u0, v0) = uvs[0];
679 let (u1, v1) = uvs[1];
680 let (u2, v2) = uvs[2];
681 let clut = material.clut_word();
682 let tpage = material.texture_page_word();
683 Self {
684 tag: 0,
685 tex_window: material.texture_window_word(),
686 color0_cmd: material.textured_polygon_command(true, false) | pack_color(r0, g0, b0),
687 v0: pack_vertex(verts[0].0, verts[0].1),
688 uv0_clut: pack_texcoord(u0, v0, clut),
689 color1: pack_color(r1, g1, b1),
690 v1: pack_vertex(verts[1].0, verts[1].1),
691 uv1_tpage: pack_texcoord(u1, v1, tpage),
692 color2: pack_color(r2, g2, b2),
693 v2: pack_vertex(verts[2].0, verts[2].1),
694 uv2: pack_texcoord(u2, v2, 0),
695 }
696 }
697
698 #[deprecated(note = "identical to `with_material`")]
700 #[inline(always)]
701 pub const fn with_material_packet_texcoords(
702 verts: [(i16, i16); 3],
703 uvs: [(u8, u8); 3],
704 colors: [(u8, u8, u8); 3],
705 material: TextureMaterial,
706 ) -> Self {
707 Self::with_material(verts, uvs, colors, material)
708 }
709
710 pub const fn with_material_packed_uv_words(
713 verts: [(i16, i16); 3],
714 uv_words: [u16; 3],
715 colors: [(u8, u8, u8); 3],
716 material: TextureMaterial,
717 ) -> Self {
718 let (r0, g0, b0) = colors[0];
719 let (r1, g1, b1) = colors[1];
720 let (r2, g2, b2) = colors[2];
721 let clut = material.clut_word();
722 let tpage = material.texture_page_word();
723 Self {
724 tag: 0,
725 tex_window: material.texture_window_word(),
726 color0_cmd: material.textured_polygon_command(true, false) | pack_color(r0, g0, b0),
727 v0: pack_vertex(verts[0].0, verts[0].1),
728 uv0_clut: (uv_words[0] as u32) | ((clut as u32) << 16),
729 color1: pack_color(r1, g1, b1),
730 v1: pack_vertex(verts[1].0, verts[1].1),
731 uv1_tpage: (uv_words[1] as u32) | ((tpage as u32) << 16),
732 color2: pack_color(r2, g2, b2),
733 v2: pack_vertex(verts[2].0, verts[2].1),
734 uv2: uv_words[2] as u32,
735 }
736 }
737
738 pub const fn with_packet_material_packed_uv_words(
741 verts: [(i16, i16); 3],
742 uv_words: [u16; 3],
743 colors: [(u8, u8, u8); 3],
744 material: TexturedGouraudPacketMaterial,
745 ) -> Self {
746 let (r0, g0, b0) = colors[0];
747 let (r1, g1, b1) = colors[1];
748 let (r2, g2, b2) = colors[2];
749 Self {
750 tag: 0,
751 tex_window: material.tex_window_word,
752 color0_cmd: material.color0_command_word | pack_color(r0, g0, b0),
753 v0: pack_vertex(verts[0].0, verts[0].1),
754 uv0_clut: (uv_words[0] as u32) | material.clut_high_word,
755 color1: pack_color(r1, g1, b1),
756 v1: pack_vertex(verts[1].0, verts[1].1),
757 uv1_tpage: (uv_words[1] as u32) | material.tpage_high_word,
758 color2: pack_color(r2, g2, b2),
759 v2: pack_vertex(verts[2].0, verts[2].1),
760 uv2: uv_words[2] as u32,
761 }
762 }
763
764 pub const fn with_staged_slot_prepacked_colors(
778 verts: [(i16, i16); 3],
779 uv_words: [u16; 3],
780 colors: [u32; 3],
781 clut_high_word: u32,
782 tpage_high_word: u32,
783 texture_window_word: u32,
784 ot_slot: u16,
785 ) -> Self {
786 debug_assert!((colors[0] | colors[1] | colors[2]) >> 24 == 0);
787 debug_assert!((clut_high_word | tpage_high_word) & 0xFFFF == 0);
788 Self {
789 tag: ((Self::WORDS as u32) << 24) | ot_slot as u32,
790 tex_window: texture_window_word,
791 color0_cmd: packet::SHADED_TEXTURED_TRIANGLE | colors[0],
792 v0: pack_vertex(verts[0].0, verts[0].1),
793 uv0_clut: uv_words[0] as u32 | clut_high_word,
794 color1: colors[1],
795 v1: pack_vertex(verts[1].0, verts[1].1),
796 uv1_tpage: uv_words[1] as u32 | tpage_high_word,
797 color2: colors[2],
798 v2: pack_vertex(verts[2].0, verts[2].1),
799 uv2: uv_words[2] as u32,
800 }
801 }
802
803 #[deprecated(note = "renamed to `with_staged_slot_prepacked_colors`, which is safe")]
810 #[inline(always)]
811 pub const unsafe fn with_staged_slot_prepacked_unchecked(
812 verts: [(i16, i16); 3],
813 uv_words: [u16; 3],
814 colors: [u32; 3],
815 clut_high_word: u32,
816 tpage_high_word: u32,
817 texture_window_word: u32,
818 ot_slot: u16,
819 ) -> Self {
820 Self::with_staged_slot_prepacked_colors(
821 verts,
822 uv_words,
823 colors,
824 clut_high_word,
825 tpage_high_word,
826 texture_window_word,
827 ot_slot,
828 )
829 }
830
831 pub const EMPTY: Self = Self {
835 tag: 0,
836 tex_window: 0,
837 color0_cmd: 0,
838 v0: 0,
839 uv0_clut: 0,
840 color1: 0,
841 v1: 0,
842 uv1_tpage: 0,
843 color2: 0,
844 v2: 0,
845 uv2: 0,
846 };
847
848 #[inline(always)]
851 pub fn set_positions(&mut self, verts: [(i16, i16); 3]) {
852 self.v0 = pack_vertex(verts[0].0, verts[0].1);
853 self.v1 = pack_vertex(verts[1].0, verts[1].1);
854 self.v2 = pack_vertex(verts[2].0, verts[2].1);
855 }
856
857 #[inline(always)]
861 pub fn copy_payload_from(&mut self, source: &Self) {
862 self.tex_window = source.tex_window;
863 self.color0_cmd = source.color0_cmd;
864 self.v0 = source.v0;
865 self.uv0_clut = source.uv0_clut;
866 self.color1 = source.color1;
867 self.v1 = source.v1;
868 self.uv1_tpage = source.uv1_tpage;
869 self.color2 = source.color2;
870 self.v2 = source.v2;
871 self.uv2 = source.uv2;
872 }
873
874 #[inline(always)]
877 pub fn set_colors(&mut self, colors: [(u8, u8, u8); 3]) {
878 let (r0, g0, b0) = colors[0];
879 let (r1, g1, b1) = colors[1];
880 let (r2, g2, b2) = colors[2];
881 self.color0_cmd = (self.color0_cmd & 0xFF00_0000) | pack_color(r0, g0, b0);
882 self.color1 = pack_color(r1, g1, b1);
883 self.color2 = pack_color(r2, g2, b2);
884 }
885}
886
887#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
891#[repr(C, align(4))]
892pub struct ClassicTriTexturedGouraud {
893 pub tag: u32,
895 pub color0_cmd: u32,
897 pub v0: u32,
899 pub uv0_clut: u32,
901 pub color1: u32,
903 pub v1: u32,
905 pub uv1_tpage: u32,
907 pub color2: u32,
909 pub v2: u32,
911 pub uv2: u32,
913}
914
915impl ClassicTriTexturedGouraud {
916 pub const WORDS: u8 = 9;
918
919 pub const fn with_staged_slot(
923 verts: [(i16, i16); 3],
924 uv_words: [u16; 3],
925 colors: [u32; 3],
926 clut: u16,
927 tpage: u16,
928 ot_slot: u16,
929 ) -> Self {
930 Self::with_staged_slot_prepacked_material(
931 verts,
932 uv_words,
933 colors,
934 (clut as u32) << 16,
935 (tpage as u32) << 16,
936 ot_slot,
937 )
938 }
939
940 pub const fn with_staged_slot_prepacked_material(
943 verts: [(i16, i16); 3],
944 uv_words: [u16; 3],
945 colors: [u32; 3],
946 clut_high_word: u32,
947 tpage_high_word: u32,
948 ot_slot: u16,
949 ) -> Self {
950 Self {
951 tag: ((Self::WORDS as u32) << 24) | ot_slot as u32,
952 color0_cmd: packet::SHADED_TEXTURED_TRIANGLE | (colors[0] & 0x00ff_ffff),
953 v0: pack_vertex(verts[0].0, verts[0].1),
954 uv0_clut: uv_words[0] as u32 | clut_high_word,
955 color1: colors[1] & 0x00ff_ffff,
956 v1: pack_vertex(verts[1].0, verts[1].1),
957 uv1_tpage: uv_words[1] as u32 | tpage_high_word,
958 color2: colors[2] & 0x00ff_ffff,
959 v2: pack_vertex(verts[2].0, verts[2].1),
960 uv2: uv_words[2] as u32,
961 }
962 }
963
964 pub const fn with_staged_slot_prepacked_colors(
971 verts: [(i16, i16); 3],
972 uv_words: [u16; 3],
973 colors: [u32; 3],
974 clut_high_word: u32,
975 tpage_high_word: u32,
976 ot_slot: u16,
977 ) -> Self {
978 debug_assert!((colors[0] | colors[1] | colors[2]) >> 24 == 0);
979 debug_assert!((clut_high_word | tpage_high_word) & 0xFFFF == 0);
980 Self {
981 tag: ((Self::WORDS as u32) << 24) | ot_slot as u32,
982 color0_cmd: packet::SHADED_TEXTURED_TRIANGLE | colors[0],
983 v0: pack_vertex(verts[0].0, verts[0].1),
984 uv0_clut: uv_words[0] as u32 | clut_high_word,
985 color1: colors[1],
986 v1: pack_vertex(verts[1].0, verts[1].1),
987 uv1_tpage: uv_words[1] as u32 | tpage_high_word,
988 color2: colors[2],
989 v2: pack_vertex(verts[2].0, verts[2].1),
990 uv2: uv_words[2] as u32,
991 }
992 }
993
994 #[deprecated(note = "renamed to `with_staged_slot_prepacked_colors`, which is safe")]
1001 #[inline(always)]
1002 pub const unsafe fn with_staged_slot_prepacked_unchecked(
1003 verts: [(i16, i16); 3],
1004 uv_words: [u16; 3],
1005 colors: [u32; 3],
1006 clut_high_word: u32,
1007 tpage_high_word: u32,
1008 ot_slot: u16,
1009 ) -> Self {
1010 Self::with_staged_slot_prepacked_colors(
1011 verts,
1012 uv_words,
1013 colors,
1014 clut_high_word,
1015 tpage_high_word,
1016 ot_slot,
1017 )
1018 }
1019}
1020
1021#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1025#[repr(C, align(4))]
1026pub struct ClassicQuadTexturedGouraud {
1027 pub tag: u32,
1029 pub color0_cmd: u32,
1031 pub v0: u32,
1033 pub uv0_clut: u32,
1035 pub color1: u32,
1037 pub v1: u32,
1039 pub uv1_tpage: u32,
1041 pub color2: u32,
1043 pub v2: u32,
1045 pub uv2: u32,
1047 pub color3: u32,
1049 pub v3: u32,
1051 pub uv3: u32,
1053}
1054
1055impl ClassicQuadTexturedGouraud {
1056 pub const WORDS: u8 = 12;
1058
1059 pub const fn with_staged_slot(
1061 verts: [(i16, i16); 4],
1062 uv_words: [u16; 4],
1063 colors: [u32; 4],
1064 clut: u16,
1065 tpage: u16,
1066 ot_slot: u16,
1067 ) -> Self {
1068 Self::with_staged_slot_prepacked_material(
1069 verts,
1070 uv_words,
1071 colors,
1072 (clut as u32) << 16,
1073 (tpage as u32) << 16,
1074 ot_slot,
1075 )
1076 }
1077
1078 pub const fn with_staged_slot_prepacked_material(
1081 verts: [(i16, i16); 4],
1082 uv_words: [u16; 4],
1083 colors: [u32; 4],
1084 clut_high_word: u32,
1085 tpage_high_word: u32,
1086 ot_slot: u16,
1087 ) -> Self {
1088 Self {
1089 tag: ((Self::WORDS as u32) << 24) | ot_slot as u32,
1090 color0_cmd: packet::SHADED_TEXTURED_QUAD | (colors[0] & 0x00ff_ffff),
1091 v0: pack_vertex(verts[0].0, verts[0].1),
1092 uv0_clut: uv_words[0] as u32 | clut_high_word,
1093 color1: colors[1] & 0x00ff_ffff,
1094 v1: pack_vertex(verts[1].0, verts[1].1),
1095 uv1_tpage: uv_words[1] as u32 | tpage_high_word,
1096 color2: colors[2] & 0x00ff_ffff,
1097 v2: pack_vertex(verts[2].0, verts[2].1),
1098 uv2: uv_words[2] as u32,
1099 color3: colors[3] & 0x00ff_ffff,
1100 v3: pack_vertex(verts[3].0, verts[3].1),
1101 uv3: uv_words[3] as u32,
1102 }
1103 }
1104
1105 pub const fn with_staged_slot_prepacked_colors(
1112 verts: [(i16, i16); 4],
1113 uv_words: [u16; 4],
1114 colors: [u32; 4],
1115 clut_high_word: u32,
1116 tpage_high_word: u32,
1117 ot_slot: u16,
1118 ) -> Self {
1119 debug_assert!((colors[0] | colors[1] | colors[2] | colors[3]) >> 24 == 0);
1120 debug_assert!((clut_high_word | tpage_high_word) & 0xFFFF == 0);
1121 Self {
1122 tag: ((Self::WORDS as u32) << 24) | ot_slot as u32,
1123 color0_cmd: packet::SHADED_TEXTURED_QUAD | colors[0],
1124 v0: pack_vertex(verts[0].0, verts[0].1),
1125 uv0_clut: uv_words[0] as u32 | clut_high_word,
1126 color1: colors[1],
1127 v1: pack_vertex(verts[1].0, verts[1].1),
1128 uv1_tpage: uv_words[1] as u32 | tpage_high_word,
1129 color2: colors[2],
1130 v2: pack_vertex(verts[2].0, verts[2].1),
1131 uv2: uv_words[2] as u32,
1132 color3: colors[3],
1133 v3: pack_vertex(verts[3].0, verts[3].1),
1134 uv3: uv_words[3] as u32,
1135 }
1136 }
1137
1138 #[deprecated(note = "renamed to `with_staged_slot_prepacked_colors`, which is safe")]
1145 #[inline(always)]
1146 pub const unsafe fn with_staged_slot_prepacked_unchecked(
1147 verts: [(i16, i16); 4],
1148 uv_words: [u16; 4],
1149 colors: [u32; 4],
1150 clut_high_word: u32,
1151 tpage_high_word: u32,
1152 ot_slot: u16,
1153 ) -> Self {
1154 Self::with_staged_slot_prepacked_colors(
1155 verts,
1156 uv_words,
1157 colors,
1158 clut_high_word,
1159 tpage_high_word,
1160 ot_slot,
1161 )
1162 }
1163}
1164
1165#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1180#[repr(C, align(4))]
1181pub struct QuadTexturedGouraud {
1182 pub tag: u32,
1184 pub tex_window: u32,
1186 pub color0_cmd: u32,
1188 pub v0: u32,
1190 pub uv0_clut: u32,
1192 pub color1: u32,
1194 pub v1: u32,
1196 pub uv1_tpage: u32,
1198 pub color2: u32,
1200 pub v2: u32,
1202 pub uv2: u32,
1204 pub color3: u32,
1206 pub v3: u32,
1208 pub uv3: u32,
1210}
1211
1212impl QuadTexturedGouraud {
1213 pub const WORDS: u8 = 13;
1215
1216 const QUAD_OPCODE_BIT: u32 = packet::QUAD;
1219
1220 pub const fn with_material(
1224 verts: [(i16, i16); 4],
1225 uvs: [(u8, u8); 4],
1226 colors: [(u8, u8, u8); 4],
1227 material: TextureMaterial,
1228 ) -> Self {
1229 let mut uv_words = [0u16; 4];
1230 let mut i = 0;
1231 while i < 4 {
1232 uv_words[i] = uvs[i].0 as u16 | (uvs[i].1 as u16) << 8;
1233 i += 1;
1234 }
1235 Self::with_packet_material_packed_uv_words(
1236 verts,
1237 uv_words,
1238 colors,
1239 material.textured_gouraud_packet_material(),
1240 )
1241 }
1242
1243 pub const fn with_packet_material_packed_uv_words(
1247 verts: [(i16, i16); 4],
1248 uv_words: [u16; 4],
1249 colors: [(u8, u8, u8); 4],
1250 material: TexturedGouraudPacketMaterial,
1251 ) -> Self {
1252 let (r0, g0, b0) = colors[0];
1253 let (r1, g1, b1) = colors[1];
1254 let (r2, g2, b2) = colors[2];
1255 let (r3, g3, b3) = colors[3];
1256 Self {
1257 tag: 0,
1258 tex_window: material.tex_window_word,
1259 color0_cmd: (material.color0_command_word | Self::QUAD_OPCODE_BIT)
1260 | pack_color(r0, g0, b0),
1261 v0: pack_vertex(verts[0].0, verts[0].1),
1262 uv0_clut: (uv_words[0] as u32) | material.clut_high_word,
1263 color1: pack_color(r1, g1, b1),
1264 v1: pack_vertex(verts[1].0, verts[1].1),
1265 uv1_tpage: (uv_words[1] as u32) | material.tpage_high_word,
1266 color2: pack_color(r2, g2, b2),
1267 v2: pack_vertex(verts[2].0, verts[2].1),
1268 uv2: uv_words[2] as u32,
1269 color3: pack_color(r3, g3, b3),
1270 v3: pack_vertex(verts[3].0, verts[3].1),
1271 uv3: uv_words[3] as u32,
1272 }
1273 }
1274
1275 pub const fn with_staged_slot_prepacked_colors(
1283 verts: [(i16, i16); 4],
1284 uv_words: [u16; 4],
1285 colors: [u32; 4],
1286 clut_high_word: u32,
1287 tpage_high_word: u32,
1288 texture_window_word: u32,
1289 ot_slot: u16,
1290 ) -> Self {
1291 debug_assert!((colors[0] | colors[1] | colors[2] | colors[3]) >> 24 == 0);
1292 debug_assert!((clut_high_word | tpage_high_word) & 0xFFFF == 0);
1293 Self {
1294 tag: ((Self::WORDS as u32) << 24) | ot_slot as u32,
1295 tex_window: texture_window_word,
1296 color0_cmd: packet::SHADED_TEXTURED_QUAD | colors[0],
1297 v0: pack_vertex(verts[0].0, verts[0].1),
1298 uv0_clut: uv_words[0] as u32 | clut_high_word,
1299 color1: colors[1],
1300 v1: pack_vertex(verts[1].0, verts[1].1),
1301 uv1_tpage: uv_words[1] as u32 | tpage_high_word,
1302 color2: colors[2],
1303 v2: pack_vertex(verts[2].0, verts[2].1),
1304 uv2: uv_words[2] as u32,
1305 color3: colors[3],
1306 v3: pack_vertex(verts[3].0, verts[3].1),
1307 uv3: uv_words[3] as u32,
1308 }
1309 }
1310
1311 #[deprecated(note = "renamed to `with_staged_slot_prepacked_colors`, which is safe")]
1318 #[inline(always)]
1319 pub const unsafe fn with_staged_slot_prepacked_unchecked(
1320 verts: [(i16, i16); 4],
1321 uv_words: [u16; 4],
1322 colors: [u32; 4],
1323 clut_high_word: u32,
1324 tpage_high_word: u32,
1325 texture_window_word: u32,
1326 ot_slot: u16,
1327 ) -> Self {
1328 Self::with_staged_slot_prepacked_colors(
1329 verts,
1330 uv_words,
1331 colors,
1332 clut_high_word,
1333 tpage_high_word,
1334 texture_window_word,
1335 ot_slot,
1336 )
1337 }
1338
1339 pub const EMPTY: Self = Self {
1344 tag: 0,
1345 tex_window: 0,
1346 color0_cmd: 0,
1347 v0: 0,
1348 uv0_clut: 0,
1349 color1: 0,
1350 v1: 0,
1351 uv1_tpage: 0,
1352 color2: 0,
1353 v2: 0,
1354 uv2: 0,
1355 color3: 0,
1356 v3: 0,
1357 uv3: 0,
1358 };
1359
1360 #[inline(always)]
1364 pub fn set_positions(&mut self, verts: [(i16, i16); 4]) {
1365 self.v0 = pack_vertex(verts[0].0, verts[0].1);
1366 self.v1 = pack_vertex(verts[1].0, verts[1].1);
1367 self.v2 = pack_vertex(verts[2].0, verts[2].1);
1368 self.v3 = pack_vertex(verts[3].0, verts[3].1);
1369 }
1370
1371 #[inline(always)]
1375 pub fn copy_payload_from(&mut self, source: &Self) {
1376 self.tex_window = source.tex_window;
1377 self.color0_cmd = source.color0_cmd;
1378 self.v0 = source.v0;
1379 self.uv0_clut = source.uv0_clut;
1380 self.color1 = source.color1;
1381 self.v1 = source.v1;
1382 self.uv1_tpage = source.uv1_tpage;
1383 self.color2 = source.color2;
1384 self.v2 = source.v2;
1385 self.uv2 = source.uv2;
1386 self.color3 = source.color3;
1387 self.v3 = source.v3;
1388 self.uv3 = source.uv3;
1389 }
1390
1391 #[inline(always)]
1394 pub fn set_colors(&mut self, colors: [(u8, u8, u8); 4]) {
1395 let (r0, g0, b0) = colors[0];
1396 let (r1, g1, b1) = colors[1];
1397 let (r2, g2, b2) = colors[2];
1398 let (r3, g3, b3) = colors[3];
1399 self.color0_cmd = (self.color0_cmd & 0xFF00_0000) | pack_color(r0, g0, b0);
1400 self.color1 = pack_color(r1, g1, b1);
1401 self.color2 = pack_color(r2, g2, b2);
1402 self.color3 = pack_color(r3, g3, b3);
1403 }
1404}
1405
1406#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1411#[repr(C, align(4))]
1412pub struct QuadTextured {
1413 pub tag: u32,
1415 pub color_cmd: u32,
1417 pub v0: u32,
1419 pub uv0_clut: u32,
1421 pub v1: u32,
1423 pub uv1_tpage: u32,
1425 pub v2: u32,
1427 pub uv2: u32,
1429 pub v3: u32,
1431 pub uv3: u32,
1433}
1434
1435impl QuadTextured {
1436 pub const WORDS: u8 = 9;
1438
1439 pub const fn new(
1441 verts: [(i16, i16); 4],
1442 uvs: [(u8, u8); 4],
1443 clut: u16,
1444 tpage: u16,
1445 tint: (u8, u8, u8),
1446 ) -> Self {
1447 Self::with_material(verts, uvs, TextureMaterial::opaque(clut, tpage, tint))
1448 }
1449
1450 pub const fn with_material(
1452 verts: [(i16, i16); 4],
1453 uvs: [(u8, u8); 4],
1454 material: TextureMaterial,
1455 ) -> Self {
1456 let (u0, v0) = uvs[0];
1457 let (u1, v1) = uvs[1];
1458 let (u2, v2) = uvs[2];
1459 let (u3, v3) = uvs[3];
1460 let clut = material.clut_word();
1461 let tpage = material.texture_page_word();
1462 Self {
1463 tag: 0,
1464 color_cmd: material.flat_textured_polygon_header(true),
1465 v0: pack_vertex(verts[0].0, verts[0].1),
1466 uv0_clut: pack_texcoord(u0, v0, clut),
1467 v1: pack_vertex(verts[1].0, verts[1].1),
1468 uv1_tpage: pack_texcoord(u1, v1, tpage),
1469 v2: pack_vertex(verts[2].0, verts[2].1),
1470 uv2: pack_texcoord(u2, v2, 0),
1471 v3: pack_vertex(verts[3].0, verts[3].1),
1472 uv3: pack_texcoord(u3, v3, 0),
1473 }
1474 }
1475}
1476
1477#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1484#[repr(C, align(4))]
1485pub struct QuadTexturedMaterial {
1486 pub tag: u32,
1488 pub tex_window: u32,
1490 pub color_cmd: u32,
1492 pub v0: u32,
1494 pub uv0_clut: u32,
1496 pub v1: u32,
1498 pub uv1_tpage: u32,
1500 pub v2: u32,
1502 pub uv2: u32,
1504 pub v3: u32,
1506 pub uv3: u32,
1508}
1509
1510impl QuadTexturedMaterial {
1511 pub const WORDS: u8 = 10;
1513
1514 pub const fn with_material(
1516 verts: [(i16, i16); 4],
1517 uvs: [(u8, u8); 4],
1518 material: TextureMaterial,
1519 ) -> Self {
1520 let (u0, v0) = uvs[0];
1521 let (u1, v1) = uvs[1];
1522 let (u2, v2) = uvs[2];
1523 let (u3, v3) = uvs[3];
1524 let clut = material.clut_word();
1525 let tpage = material.texture_page_word();
1526 Self {
1527 tag: 0,
1528 tex_window: material.texture_window_word(),
1529 color_cmd: material.flat_textured_polygon_header(true),
1530 v0: pack_vertex(verts[0].0, verts[0].1),
1531 uv0_clut: pack_texcoord(u0, v0, clut),
1532 v1: pack_vertex(verts[1].0, verts[1].1),
1533 uv1_tpage: pack_texcoord(u1, v1, tpage),
1534 v2: pack_vertex(verts[2].0, verts[2].1),
1535 uv2: pack_texcoord(u2, v2, 0),
1536 v3: pack_vertex(verts[3].0, verts[3].1),
1537 uv3: pack_texcoord(u3, v3, 0),
1538 }
1539 }
1540}
1541
1542#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1544#[repr(C, align(4))]
1545pub struct Sprite {
1546 pub tag: u32,
1548 pub color_cmd: u32,
1550 pub xy: u32,
1552 pub uv_clut: u32,
1554 pub wh: u32,
1556}
1557
1558impl Sprite {
1559 pub const WORDS: u8 = 4;
1561
1562 pub const fn new(
1566 x: i16,
1567 y: i16,
1568 w: u16,
1569 h: u16,
1570 uv: (u8, u8),
1571 clut: u16,
1572 r: u8,
1573 g: u8,
1574 b: u8,
1575 ) -> Self {
1576 let material = TextureMaterial::opaque(clut, 0, (r, g, b));
1577 Self::with_material(x, y, w, h, uv, material)
1578 }
1579
1580 pub const fn with_material(
1588 x: i16,
1589 y: i16,
1590 w: u16,
1591 h: u16,
1592 uv: (u8, u8),
1593 material: TextureMaterial,
1594 ) -> Self {
1595 Self {
1596 tag: 0,
1597 color_cmd: material.textured_rect_header(),
1598 xy: pack_vertex(x, y),
1599 uv_clut: pack_texcoord(uv.0, uv.1, material.clut_word()),
1600 wh: pack_xy(w, h),
1601 }
1602 }
1603}
1604
1605pub unsafe trait GpuPacket {
1618 const WORDS: u8;
1620}
1621
1622macro_rules! impl_gpu_packet {
1623 ($($ty:ty),+ $(,)?) => {
1624 $(
1625 unsafe impl GpuPacket for $ty {
1628 const WORDS: u8 = <$ty>::WORDS;
1629 }
1630 const _: () = {
1631 assert!(core::mem::size_of::<$ty>() >= 4 * (1 + <$ty>::WORDS as usize));
1632 assert!(core::mem::align_of::<$ty>() >= 4);
1633 };
1634 )+
1635 };
1636}
1637
1638impl_gpu_packet!(
1639 TriFlat,
1640 TriGouraud,
1641 QuadFlat,
1642 RectFlat,
1643 FillRect,
1644 QuadGouraud,
1645 QuadGouraudBlended,
1646 LineMono,
1647 LineGouraud,
1648 TriTextured,
1649 ClassicTriTextured,
1650 TriTexturedGouraud,
1651 ClassicTriTexturedGouraud,
1652 ClassicQuadTexturedGouraud,
1653 QuadTexturedGouraud,
1654 QuadTextured,
1655 QuadTexturedMaterial,
1656 Sprite,
1657);
1658
1659const _: () = {
1661 let words = [
1662 TriFlat::WORDS,
1663 TriGouraud::WORDS,
1664 QuadFlat::WORDS,
1665 RectFlat::WORDS,
1666 FillRect::WORDS,
1667 QuadGouraud::WORDS,
1668 QuadGouraudBlended::WORDS,
1669 LineMono::WORDS,
1670 LineGouraud::WORDS,
1671 TriTextured::WORDS,
1672 ClassicTriTextured::WORDS,
1673 TriTexturedGouraud::WORDS,
1674 ClassicTriTexturedGouraud::WORDS,
1675 ClassicQuadTexturedGouraud::WORDS,
1676 QuadTexturedGouraud::WORDS,
1677 QuadTextured::WORDS,
1678 QuadTexturedMaterial::WORDS,
1679 Sprite::WORDS,
1680 ];
1681 let mut i = 0;
1682 while i < words.len() {
1683 assert!(words[i] as usize <= crate::chain::MAX_NODE_WORDS);
1684 i += 1;
1685 }
1686};
1687
1688#[cfg(test)]
1689mod tests {
1690 use super::*;
1691
1692 #[test]
1693 fn blended_gouraud_quad_keeps_draw_mode_and_polygon_together() {
1694 let quad = QuadGouraudBlended::new(
1695 [(1, 2), (3, 4), (5, 6), (7, 8)],
1696 [
1697 (0x10, 0x20, 0x30),
1698 (0x40, 0x50, 0x60),
1699 (0x70, 0x80, 0x90),
1700 (0xa0, 0xb0, 0xc0),
1701 ],
1702 BlendMode::AddQuarter,
1703 );
1704 let words = unsafe {
1707 core::slice::from_raw_parts(
1708 (&quad as *const QuadGouraudBlended).cast::<u32>(),
1709 core::mem::size_of::<QuadGouraudBlended>() / core::mem::size_of::<u32>(),
1710 )
1711 };
1712 assert_eq!(words.len(), 10);
1713 assert_eq!(QuadGouraudBlended::WORDS, 9);
1714 assert_eq!(words[0], 0, "tag zero until OT insert");
1715 assert_eq!(words[1] >> 24, 0xe1, "draw-mode prefix");
1716 assert_eq!((words[1] >> 5) & 3, 3, "add-quarter ABR bits");
1717 assert_eq!(words[2] >> 24, 0x3a, "Gouraud+quad+semi-trans opcode");
1718 }
1719
1720 #[test]
1725 fn quad_textured_gouraud_serializes_to_gp0_3c_stream() {
1726 let material =
1727 TexturedGouraudPacketMaterial::from_texture(TextureMaterial::new(0x1234, 0x0105));
1728 let uvw = [0x0201u16, 0x3C04, 0x3C42, 0x0240];
1729 let cols = [
1730 (0x11u8, 0x22u8, 0x33u8),
1731 (0x44, 0x55, 0x66),
1732 (0x77, 0x88, 0x99),
1733 (0xAA, 0xBB, 0xCC),
1734 ];
1735 let verts = [(10i16, 20i16), (110, 20), (110, 90), (10, 90)];
1736 let quad =
1737 QuadTexturedGouraud::with_packet_material_packed_uv_words(verts, uvw, cols, material);
1738
1739 assert_eq!(core::mem::size_of::<QuadTexturedGouraud>(), 14 * 4);
1740 assert_eq!(QuadTexturedGouraud::WORDS, 13);
1741
1742 let words = unsafe {
1745 core::slice::from_raw_parts((&quad as *const QuadTexturedGouraud).cast::<u32>(), 14)
1746 };
1747 assert_eq!(words[0], 0, "tag zero until OT insert");
1748 assert_eq!(words[1], material.tex_window_word, "E2 window prefix");
1749 assert_eq!(words[2] >> 24, 0x3C, "Gouraud+textured+quad opcode");
1750 assert_eq!(words[2] & 0x00FF_FFFF, pack_color(0x11, 0x22, 0x33));
1751 assert_eq!(words[3], pack_vertex(10, 20));
1752 assert_eq!(words[4], 0x0201 | material.clut_high_word);
1753 assert_eq!(words[5], pack_color(0x44, 0x55, 0x66));
1754 assert_eq!(words[6], pack_vertex(110, 20));
1755 assert_eq!(words[7], 0x3C04 | material.tpage_high_word);
1756 assert_eq!(words[8], pack_color(0x77, 0x88, 0x99));
1757 assert_eq!(words[9], pack_vertex(110, 90));
1758 assert_eq!(words[10], 0x3C42);
1759 assert_eq!(words[11], pack_color(0xAA, 0xBB, 0xCC));
1760 assert_eq!(words[12], pack_vertex(10, 90));
1761 assert_eq!(words[13], 0x0240);
1762 }
1763
1764 #[test]
1765 fn classic_packets_omit_texture_window_and_stage_ot_slot() {
1766 let tri = ClassicTriTexturedGouraud::with_staged_slot(
1767 [(1, 2), (3, 4), (5, 6)],
1768 [0x0201, 0x0403, 0x0605],
1769 [0x0033_2211, 0x0066_5544, 0x0099_8877],
1770 0x1234,
1771 0x0105,
1772 77,
1773 );
1774 assert_eq!(core::mem::size_of::<ClassicTriTexturedGouraud>(), 10 * 4);
1775 assert_eq!(tri.tag, (9 << 24) | 77);
1776 assert_eq!(tri.color0_cmd, 0x3433_2211);
1777 assert_eq!(tri.uv0_clut, 0x1234_0201);
1778 assert_eq!(tri.uv1_tpage, 0x0105_0403);
1779
1780 let quad = ClassicQuadTexturedGouraud::with_staged_slot(
1781 [(1, 2), (3, 4), (5, 6), (7, 8)],
1782 [0x0201, 0x0403, 0x0605, 0x0807],
1783 [0x0033_2211, 0x0066_5544, 0x0099_8877, 0x00cc_bbaa],
1784 0x1234,
1785 0x0105,
1786 88,
1787 );
1788 assert_eq!(core::mem::size_of::<ClassicQuadTexturedGouraud>(), 13 * 4);
1789 assert_eq!(quad.tag, (12 << 24) | 88);
1790 assert_eq!(quad.color0_cmd, 0x3c33_2211);
1791 assert_eq!(quad.uv3, 0x0807);
1792 }
1793
1794 #[test]
1795 fn classic_unchecked_packets_match_checked_packets_for_valid_words() {
1796 let tri_verts = [(1, 2), (3, 4), (5, 6)];
1797 let tri_uvs = [0x0201, 0x0403, 0x0605];
1798 let tri_colors = [0x0033_2211, 0x0066_5544, 0x0099_8877];
1799 let checked_tri = ClassicTriTexturedGouraud::with_staged_slot_prepacked_material(
1800 tri_verts,
1801 tri_uvs,
1802 tri_colors,
1803 0x1234_0000,
1804 0x0105_0000,
1805 77,
1806 );
1807 let unchecked_tri = ClassicTriTexturedGouraud::with_staged_slot_prepacked_colors(
1808 tri_verts,
1809 tri_uvs,
1810 tri_colors,
1811 0x1234_0000,
1812 0x0105_0000,
1813 77,
1814 );
1815 let checked_tri_words = unsafe {
1818 core::slice::from_raw_parts(
1819 (&raw const checked_tri).cast::<u32>(),
1820 core::mem::size_of::<ClassicTriTexturedGouraud>() / core::mem::size_of::<u32>(),
1821 )
1822 };
1823 let unchecked_tri_words = unsafe {
1826 core::slice::from_raw_parts(
1827 (&raw const unchecked_tri).cast::<u32>(),
1828 core::mem::size_of::<ClassicTriTexturedGouraud>() / core::mem::size_of::<u32>(),
1829 )
1830 };
1831 assert_eq!(checked_tri_words, unchecked_tri_words);
1832
1833 let quad_verts = [(1, 2), (3, 4), (5, 6), (7, 8)];
1834 let quad_uvs = [0x0201, 0x0403, 0x0605, 0x0807];
1835 let quad_colors = [0x0033_2211, 0x0066_5544, 0x0099_8877, 0x00cc_bbaa];
1836 let checked_quad = ClassicQuadTexturedGouraud::with_staged_slot_prepacked_material(
1837 quad_verts,
1838 quad_uvs,
1839 quad_colors,
1840 0x1234_0000,
1841 0x0105_0000,
1842 88,
1843 );
1844 let unchecked_quad = ClassicQuadTexturedGouraud::with_staged_slot_prepacked_colors(
1845 quad_verts,
1846 quad_uvs,
1847 quad_colors,
1848 0x1234_0000,
1849 0x0105_0000,
1850 88,
1851 );
1852 let checked_quad_words = unsafe {
1855 core::slice::from_raw_parts(
1856 (&raw const checked_quad).cast::<u32>(),
1857 core::mem::size_of::<ClassicQuadTexturedGouraud>() / core::mem::size_of::<u32>(),
1858 )
1859 };
1860 let unchecked_quad_words = unsafe {
1863 core::slice::from_raw_parts(
1864 (&raw const unchecked_quad).cast::<u32>(),
1865 core::mem::size_of::<ClassicQuadTexturedGouraud>() / core::mem::size_of::<u32>(),
1866 )
1867 };
1868 assert_eq!(checked_quad_words, unchecked_quad_words);
1869 }
1870}