Nugget
Bare-metal libraries and examples for the original PlayStation
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
37// clang-format off
38
40
41// Quad Q: 4x4 axis-aligned square, vertices in scan order
42// (0,0),(4,0),(0,4),(4,4), BLUE.
43static void rasterDrawQuadQ(void) {
44 rasterReset();
45 rasterClearTestRegion(0, 0, 16, 16);
46 rasterFlatQuad(RASTER_CMD_BLUE, 0, 0, 4, 0, 0, 4, 4, 4);
47 rasterFlushPrimitive();
48}
49
50// Quad R: same square but with vertices in reversed winding -
51// (4,4),(0,4),(4,0),(0,0). The destination geometry is identical but the
52// rasterizer's decomposition sees a different triangle pair. Captures
53// whether winding affects the fill set on hardware.
54static void rasterDrawQuadR(void) {
55 rasterReset();
56 rasterClearTestRegion(0, 0, 16, 16);
57 rasterFlatQuad(RASTER_CMD_BLUE, 4, 4, 0, 4, 4, 0, 0, 0);
58 rasterFlushPrimitive();
59}
60
61// Quad S: 4-vertex polygon where the 4th vertex creates a non-convex
62// shape (the second-triangle decomposition draws OUTSIDE the apparent
63// quad). Vertices (0,0),(4,0),(0,4),(2,2). The two-triangle split
64// produces tri (4,0)-(2,2)-(0,4) (smaller, interior) and tri
65// (0,0)-(4,0)-(0,4) (large, full upper-left). Documents what hardware
66// does with degenerate-concave 4-vertex input.
67static void rasterDrawQuadS(void) {
68 rasterReset();
69 rasterClearTestRegion(0, 0, 16, 16);
70 rasterFlatQuad(RASTER_CMD_GREEN, 0, 0, 4, 0, 0, 4, 2, 2);
71 rasterFlushPrimitive();
72}
73
74) // CESTER_BODY
75
76// --------------------------------------------------------------------------
77// Quad Q: 4x4 axis-aligned, scan-order winding
78// --------------------------------------------------------------------------
79
80CESTER_TEST(quadQ_pixel_0_0, gpu_raster_phase1,
81 rasterDrawQuadQ();
83)
84
85CESTER_TEST(quadQ_pixel_3_0_top_right, gpu_raster_phase1,
86 rasterDrawQuadQ();
88)
89
90CESTER_TEST(quadQ_pixel_4_0_right_edge, gpu_raster_phase1,
91 rasterDrawQuadQ();
93)
94
95CESTER_TEST(quadQ_pixel_0_3_bottom_left, gpu_raster_phase1,
96 rasterDrawQuadQ();
98)
99
100CESTER_TEST(quadQ_pixel_3_3_interior_corner, gpu_raster_phase1,
101 rasterDrawQuadQ();
103)
104
105CESTER_TEST(quadQ_pixel_4_4_outside_bottom_right, gpu_raster_phase1,
106 rasterDrawQuadQ();
108)
109
110CESTER_TEST(quadQ_pixel_0_4_bottom_edge, gpu_raster_phase1,
111 rasterDrawQuadQ();
113)
114
115CESTER_TEST(quadQ_pixel_2_2_diagonal_seam, gpu_raster_phase1,
116 rasterDrawQuadQ();
118)
119
120// --------------------------------------------------------------------------
121// Quad R: same destination geometry, reversed winding
122// --------------------------------------------------------------------------
123//
124// Expected: identical fill set to Quad Q if hardware is winding-agnostic
125// for flat untextured. If results differ, the rasterizer is reading
126// winding for fill-rule purposes.
127
128CESTER_TEST(quadR_reversed_winding_pixel_0_0, gpu_raster_phase1,
129 rasterDrawQuadR();
131)
132
133CESTER_TEST(quadR_reversed_winding_pixel_3_3, gpu_raster_phase1,
134 rasterDrawQuadR();
136)
137
138CESTER_TEST(quadR_reversed_winding_pixel_2_2_seam, gpu_raster_phase1,
139 rasterDrawQuadR();
141)
142
143CESTER_TEST(quadR_reversed_winding_pixel_4_4_outside, gpu_raster_phase1,
144 rasterDrawQuadR();
146)
147
148// --------------------------------------------------------------------------
149// Quad S: non-convex 4-vertex (4th vertex inside triangle of first three)
150// --------------------------------------------------------------------------
151//
152// Pure characterization: no EXPECT macros (added once hardware run lands).
153// Reads back a 5x5 grid; emits OBS lines for every pixel. cester assertions
154// are absent here because the behavior is fully undefined by psx-spx and
155// the test exists only to capture ground truth.
156
157CESTER_TEST(quadS_nonconvex_dump_5x5, gpu_raster_phase1,
158 rasterDrawQuadS();
159 for (int y = 0; y < 5; y++) {
160 for (int x = 0; x < 5; x++) {
161 uint16_t v = rasterReadPixel((int16_t)x, (int16_t)y);
162 ramsyscall_printf("OBS quadS x=%d y=%d val=0x%04x\n",
163 x, y, (unsigned)v);
164 }
165 }
166 // Assert at least one pixel is non-sentinel so the test fails if
167 // the GPU drew nothing at all (which would itself be a finding).
169 (unsigned)rasterReadPixel(0, 0));
170)
CESTER_TEST(cddaLevelHalf, test_instances, static const uint8_t atv[4]={0x40, 0x00, 0x40, 0x00};unsigned changes=0;int peak=cddaPeak(atv, 1, &changes);ramsyscall_printf("CD-DA level at half ATV: peak %i, %u sign changes\n", peak, changes);cester_assert_int_ge(peak, 11300);cester_assert_int_le(peak, 11800);cester_assert_uint_ge(changes, 4);) CESTER_TEST(cddaLevelClipped
ramsyscall_printf("T21 phase=%s t=%u v=%02x cause=%u size=%u stat=%02x\n", phase, t/1000, response[0], cause, size,(nopCause==3 &&nopSize >=1) ? stat[0] :0xff)
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:39
#define EXPECT_QUAD_Q_PIXEL_0_3
Definition raster-expected.h:200
#define EXPECT_QUAD_Q_PIXEL_3_3
Definition raster-expected.h:201
#define EXPECT_QUAD_Q_PIXEL_0_4
Definition raster-expected.h:203
#define EXPECT_QUAD_Q_PIXEL_4_4
Definition raster-expected.h:202
#define EXPECT_QUAD_Q_PIXEL_2_2
Definition raster-expected.h:204
#define EXPECT_QUAD_Q_PIXEL_3_0
Definition raster-expected.h:198
#define EXPECT_QUAD_Q_PIXEL_4_0
Definition raster-expected.h:199
#define EXPECT_QUAD_Q_PIXEL_0_0
Definition raster-expected.h:197
#define RASTER_CMD_BLUE
Definition raster-helpers.h:128
#define RASTER_CMD_GREEN
Definition raster-helpers.h:126
#define ASSERT_PIXEL_EQ(expected, x_, y_)
Definition raster-helpers.h:473
#define RASTER_SENTINEL
Definition raster-helpers.h:95