Nugget
Loading...
Searching...
No Matches
quad-decomposition.c
Go to the documentation of this file.
1/*
2
3MIT License
4
5Copyright (c) 2026 PCSX-Redux authors
6
7Permission is hereby granted, free of charge, to any person obtaining a copy
8of this software and associated documentation files (the "Software"), to deal
9in the Software without restriction, including without limitation the rights
10to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11copies of the Software, and to permit persons to whom the Software is
12furnished to do so, subject to the following conditions:
13
14The above copyright notice and this permission notice shall be included in all
15copies or substantial portions of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23SOFTWARE.
24
25*/
26
27// Quad decomposition suite. The PS1 GPU decomposes 4-vertex polygons
28// into two triangles internally; the audit at soft.cc:2493-2496 says
29// the soft renderer splits as (1,3,2) + (0,1,2). Hardware truth here
30// determines:
31// - Whether the diagonal seam pixel is drawn once, twice, or not at all.
32// - Whether the second triangle's edge rule sees the seam edge as
33// top-or-bottom (depends on winding).
34// - Whether 4-vertex coordinate ordering changes the fill set for
35// otherwise-identical destination geometry.
36
38
39// Quad Q: 4x4 axis-aligned square, vertices in scan order
40// (0,0),(4,0),(0,4),(4,4), BLUE.
41static void rasterDrawQuadQ(void) {
42 rasterReset();
43 rasterClearTestRegion(0, 0, 16, 16);
44 rasterFlatQuad(RASTER_CMD_BLUE, 0, 0, 4, 0, 0, 4, 4, 4);
45 rasterFlushPrimitive();
46}
47
48// Quad R: same square but with vertices in reversed winding -
49// (4,4),(0,4),(4,0),(0,0). The destination geometry is identical but the
50// rasterizer's decomposition sees a different triangle pair. Captures
51// whether winding affects the fill set on hardware.
52static void rasterDrawQuadR(void) {
53 rasterReset();
54 rasterClearTestRegion(0, 0, 16, 16);
55 rasterFlatQuad(RASTER_CMD_BLUE, 4, 4, 0, 4, 4, 0, 0, 0);
56 rasterFlushPrimitive();
57}
58
59// Quad S: 4-vertex polygon where the 4th vertex creates a non-convex
60// shape (the second-triangle decomposition draws OUTSIDE the apparent
61// quad). Vertices (0,0),(4,0),(0,4),(2,2). The two-triangle split
62// produces tri (4,0)-(2,2)-(0,4) (smaller, interior) and tri
63// (0,0)-(4,0)-(0,4) (large, full upper-left). Documents what hardware
64// does with degenerate-concave 4-vertex input.
65static void rasterDrawQuadS(void) {
66 rasterReset();
67 rasterClearTestRegion(0, 0, 16, 16);
68 rasterFlatQuad(RASTER_CMD_GREEN, 0, 0, 4, 0, 0, 4, 2, 2);
69 rasterFlushPrimitive();
70}
71
72) // CESTER_BODY
73
74// --------------------------------------------------------------------------
75// Quad Q: 4x4 axis-aligned, scan-order winding
76// --------------------------------------------------------------------------
77
78CESTER_TEST(quadQ_pixel_0_0, gpu_raster_phase1,
79 rasterDrawQuadQ();
81)
82
83CESTER_TEST(quadQ_pixel_3_0_top_right, gpu_raster_phase1,
84 rasterDrawQuadQ();
86)
87
88CESTER_TEST(quadQ_pixel_4_0_right_edge, gpu_raster_phase1,
89 rasterDrawQuadQ();
91)
92
93CESTER_TEST(quadQ_pixel_0_3_bottom_left, gpu_raster_phase1,
94 rasterDrawQuadQ();
96)
97
98CESTER_TEST(quadQ_pixel_3_3_interior_corner, gpu_raster_phase1,
99 rasterDrawQuadQ();
101)
102
103CESTER_TEST(quadQ_pixel_4_4_outside_bottom_right, gpu_raster_phase1,
104 rasterDrawQuadQ();
106)
107
108CESTER_TEST(quadQ_pixel_0_4_bottom_edge, gpu_raster_phase1,
109 rasterDrawQuadQ();
111)
112
113CESTER_TEST(quadQ_pixel_2_2_diagonal_seam, gpu_raster_phase1,
114 rasterDrawQuadQ();
116)
117
118// --------------------------------------------------------------------------
119// Quad R: same destination geometry, reversed winding
120// --------------------------------------------------------------------------
121//
122// Expected: identical fill set to Quad Q if hardware is winding-agnostic
123// for flat untextured. If results differ, the rasterizer is reading
124// winding for fill-rule purposes.
125
126CESTER_TEST(quadR_reversed_winding_pixel_0_0, gpu_raster_phase1,
127 rasterDrawQuadR();
129)
130
131CESTER_TEST(quadR_reversed_winding_pixel_3_3, gpu_raster_phase1,
132 rasterDrawQuadR();
134)
135
136CESTER_TEST(quadR_reversed_winding_pixel_2_2_seam, gpu_raster_phase1,
137 rasterDrawQuadR();
139)
140
141CESTER_TEST(quadR_reversed_winding_pixel_4_4_outside, gpu_raster_phase1,
142 rasterDrawQuadR();
144)
145
146// --------------------------------------------------------------------------
147// Quad S: non-convex 4-vertex (4th vertex inside triangle of first three)
148// --------------------------------------------------------------------------
149//
150// Pure characterization: no EXPECT macros (added once hardware run lands).
151// Reads back a 5x5 grid; emits OBS lines for every pixel. cester assertions
152// are absent here because the behavior is fully undefined by psx-spx and
153// the test exists only to capture ground truth.
154
155CESTER_TEST(quadS_nonconvex_dump_5x5, gpu_raster_phase1,
156 rasterDrawQuadS();
157 for (int y = 0; y < 5; y++) {
158 for (int x = 0; x < 5; x++) {
159 uint16_t v = rasterReadPixel((int16_t)x, (int16_t)y);
160 ramsyscall_printf("OBS quadS x=%d y=%d val=0x%04x\n",
161 x, y, (unsigned)v);
162 }
163 }
164 // Assert at least one pixel is non-sentinel so the test fails if
165 // the GPU drew nothing at all (which would itself be a finding).
167 (unsigned)rasterReadPixel(0, 0));
168)
CESTER_TEST(cpu_cop0_basic_write_bp, cpu_tests, uint32_t expectedEPC;uint32_t t;volatile uint32_t *ptr=(volatile uint32_t *) 0x58; *ptr=1;__asm__ volatile("" " lui %0, 0b1100101010000000\n" " mtc0 %0, $7\n" " li %0, 0x58\n" " mtc0 %0, $5\n" " li %0, 0xfffffff0\n" " mtc0 %0, $9\n" :"=r"(t));cester_assert_uint_eq(1, *ptr);__asm__ volatile("la %0, 1f\n1:\nsw $0, 0x58($0)" :"=r"(expectedEPC));__asm__ volatile("mtc0 $0, $7\n");cester_assert_uint_eq(0, *ptr);cester_assert_uint_eq(1, s_got40);cester_assert_uint_eq(0, s_got80);cester_assert_uint_eq(0x40, s_from);cester_assert_uint_eq(expectedEPC, s_epc);) CESTER_TEST(cpu_cop0_kseg_write_bp
ramsyscall_printf("=== e01_kseg1_reads_no_fill ===\n")
cester_assert_uint_ne((unsigned) RASTER_SENTINEL,(unsigned) rasterReadPixel(0, 0))
CESTER_BODY(static void rasterDrawQuadQ(void) { rasterReset();rasterClearTestRegion(0, 0, 16, 16);rasterFlatQuad(RASTER_CMD_BLUE, 0, 0, 4, 0, 0, 4, 4, 4);rasterFlushPrimitive();} static void rasterDrawQuadR(void) { rasterReset();rasterClearTestRegion(0, 0, 16, 16);rasterFlatQuad(RASTER_CMD_BLUE, 4, 4, 0, 4, 4, 0, 0, 0);rasterFlushPrimitive();} static void rasterDrawQuadS(void) { rasterReset();rasterClearTestRegion(0, 0, 16, 16);rasterFlatQuad(RASTER_CMD_GREEN, 0, 0, 4, 0, 0, 4, 2, 2);rasterFlushPrimitive();})
Definition quad-decomposition.c:37
#define EXPECT_QUAD_Q_PIXEL_0_3
Definition raster-expected.h:199
#define EXPECT_QUAD_Q_PIXEL_3_3
Definition raster-expected.h:200
#define EXPECT_QUAD_Q_PIXEL_0_4
Definition raster-expected.h:202
#define EXPECT_QUAD_Q_PIXEL_4_4
Definition raster-expected.h:201
#define EXPECT_QUAD_Q_PIXEL_2_2
Definition raster-expected.h:203
#define EXPECT_QUAD_Q_PIXEL_3_0
Definition raster-expected.h:197
#define EXPECT_QUAD_Q_PIXEL_4_0
Definition raster-expected.h:198
#define EXPECT_QUAD_Q_PIXEL_0_0
Definition raster-expected.h:196
#define RASTER_CMD_BLUE
Definition raster-helpers.h:127
#define RASTER_CMD_GREEN
Definition raster-helpers.h:125
#define ASSERT_PIXEL_EQ(expected, x_, y_)
Definition raster-helpers.h:472
#define RASTER_SENTINEL
Definition raster-helpers.h:94