Nugget
Bare-metal libraries and examples for the original PlayStation
Loading...
Searching...
No Matches
cross-primitive-mask.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// Mask-bit set + check behavior across PRIMITIVE TYPES. Phase-2 only
28// tested triangles; this suite extends to rectangles, lines, textured
29// rectangles, and textured triangles at all three depths. Each
30// primitive type goes through its own writer code path in the soft
31// renderer, and the audit (writer-policy refactor track) treats mask
32// behavior as embedded in writers rather than templated. Cross-
33// primitive coverage matters for the refactor.
34//
35// Pattern: pass 1 draws with set-mask (E6=0x01), expecting pixels to
36// land with bit 15 forced. Pass 2 with check-mask (E6=0x02) overlays
37// a second primitive; pixels with bit 15 set should be preserved,
38// pixels with bit 15 clear (or sentinel) should be overwritten.
39//
40// Local sentinel 0x5555 (bit 15 clear) - same workaround as phase-2.
41
42#define MASK_SUITE_SENTINEL 0x5555u
43
44// clang-format off
45
47
48static void fillSuiteRegion(int16_t x, int16_t y, int16_t w, int16_t h) {
49 rasterFillRect(x, y, w, h, MASK_SUITE_SENTINEL);
50}
51
52// -- Rectangle (GP0 0x60) --
53
54static void drawRectSet(void) {
55 rasterReset();
56 fillSuiteRegion(0, 0, 16, 8);
57 sendGPUData(0xe6000001u);
58 /* 4x4 RED rect at (0,0). */
59 rasterFlatRect(RASTER_CMD_RED, 0, 0, 4, 4);
60 rasterFlushPrimitive();
61 sendGPUData(0xe6000000u);
62}
63
64static void drawRectSetThenCheck(void) {
65 rasterReset();
66 fillSuiteRegion(0, 0, 16, 8);
67 /* Pass 1: RED rect (0,0,4,4) with set-mask. */
68 sendGPUData(0xe6000001u);
69 rasterFlatRect(RASTER_CMD_RED, 0, 0, 4, 4);
70 rasterFlushPrimitive();
71 /* Pass 2: GREEN rect (2,0,6,4) with check-mask. Overlap region
72 (2..3, 0..3) should be preserved RED-with-mask. Non-overlap
73 (4..7, 0..3) should fill GREEN. */
74 sendGPUData(0xe6000002u);
75 rasterFlatRect(RASTER_CMD_GREEN, 2, 0, 6, 4);
76 rasterFlushPrimitive();
77 sendGPUData(0xe6000000u);
78}
79
80// -- Line (GP0 0x40) --
81
82static void drawLineSet(void) {
83 rasterReset();
84 fillSuiteRegion(0, 0, 16, 4);
85 sendGPUData(0xe6000001u);
86 /* Horizontal RED line from (0,0) to (5,0). */
87 rasterFlatLine(RASTER_CMD_RED, 0, 0, 5, 0);
88 rasterFlushPrimitive();
89 sendGPUData(0xe6000000u);
90}
91
92static void drawLineSetThenCheck(void) {
93 rasterReset();
94 fillSuiteRegion(0, 0, 16, 4);
95 sendGPUData(0xe6000001u);
96 rasterFlatLine(RASTER_CMD_RED, 0, 0, 5, 0);
97 rasterFlushPrimitive();
98 sendGPUData(0xe6000002u);
99 /* Overlay GREEN line (3,0)-(8,0). Overlap pixels (3..5) preserved,
100 new pixels (6..8) filled GREEN. */
101 rasterFlatLine(RASTER_CMD_GREEN, 3, 0, 8, 0);
102 rasterFlushPrimitive();
103 sendGPUData(0xe6000000u);
104}
105
106// -- Textured rectangle (GP0 0x64) - 4-bit CLUT --
107
108// GP0(0x64) variable-size textured rectangle. Word layout:
109// word 0: 0x64xxxxxx (24-bit modulation color)
110// word 1: y << 16 | x (top-left corner)
111// word 2: clut << 16 | (v0 << 8) | u0 (UV at top-left + CLUT)
112// word 3: h << 16 | w (size)
113//
114// Texpage state comes from the LAST GP0(E1), not embedded in the
115// command. So setTexpage() must precede the draw.
116static inline void rasterFlatTexRect(uint32_t cmdColor, int16_t x, int16_t y,
117 int16_t w, int16_t h, uint8_t u0,
118 uint8_t v0, uint16_t clut_field) {
119 waitGPU();
120 GPU_DATA = 0x64000000u | (cmdColor & 0x00ffffffu);
121 GPU_DATA = ((uint32_t)(uint16_t)y << 16) | (uint32_t)(uint16_t)x;
122 GPU_DATA = ((uint32_t)clut_field << 16) |
123 ((uint32_t)v0 << 8) | (uint32_t)u0;
124 GPU_DATA = ((uint32_t)(uint16_t)h << 16) | (uint32_t)(uint16_t)w;
125}
126
127static void drawTexRect4Set(void) {
128 rasterReset();
129 fillSuiteRegion(0, 0, 16, 8);
130 setTexpage(TEX4_TX, TEX4_TY, 0);
131 setTextureWindow(0, 0, 0, 0);
132 sendGPUData(0xe6000001u);
133 /* 4x4 textured rect at (0,0) with UV (1,1)-(4,4) at 4-bit. */
134 rasterFlatTexRect(TEX_MOD_NEUTRAL, 0, 0, 4, 4, 1, 1, CLUT4_FIELD);
135 rasterFlushPrimitive();
136 sendGPUData(0xe6000000u);
137}
138
139// -- Textured triangle (GP0 0x24) - 4-bit CLUT --
140
141static void drawTexTri4Set(void) {
142 rasterReset();
143 fillSuiteRegion(0, 0, 16, 8);
144 setTexpage(TEX4_TX, TEX4_TY, 0);
145 setTextureWindow(0, 0, 0, 0);
146 sendGPUData(0xe6000001u);
147 /* 4-pixel triangle at (0,0)(4,0)(0,4) with UV matching. */
148 rasterTexTri(TEX_MOD_NEUTRAL,
149 0, 0, 1, 1,
150 4, 0, 5, 1,
151 0, 4, 1, 5,
153 rasterFlushPrimitive();
154 sendGPUData(0xe6000000u);
155}
156
157// -- Textured triangle (GP0 0x24) - 8-bit CLUT --
158
159static void drawTexTri8Set(void) {
160 rasterReset();
161 fillSuiteRegion(0, 0, 16, 8);
162 setTexpage(TEX8_TX, TEX8_TY, 1);
163 setTextureWindow(0, 0, 0, 0);
164 sendGPUData(0xe6000001u);
165 rasterTexTri(TEX_MOD_NEUTRAL,
166 0, 0, 1, 1,
167 4, 0, 5, 1,
168 0, 4, 1, 5,
170 rasterFlushPrimitive();
171 sendGPUData(0xe6000000u);
172}
173
174// -- Textured triangle (GP0 0x24) - 15-bit direct --
175
176static void drawTexTri15Set(void) {
177 rasterReset();
178 fillSuiteRegion(0, 0, 16, 8);
179 setTexpage(TEX15_TX, TEX15_TY, 2);
180 setTextureWindow(0, 0, 0, 0);
181 sendGPUData(0xe6000001u);
182 rasterTexTri(TEX_MOD_NEUTRAL,
183 0, 0, 1, 1,
184 4, 0, 5, 1,
185 0, 4, 1, 5,
187 rasterFlushPrimitive();
188 sendGPUData(0xe6000000u);
189}
190
191) // CESTER_BODY
192
193// --------------------------------------------------------------------------
194// Rectangle mask-bit
195// --------------------------------------------------------------------------
196
197CESTER_TEST(rect_mask_set_origin_has_bit15, gpu_raster_phase5,
198 drawRectSet();
199 /* (0,0) drawn RED with mask -> 0x801f. */
200 ASSERT_PIXEL_EQ(0x801fu, 0, 0);
201)
202
203CESTER_TEST(rect_mask_set_interior_has_bit15, gpu_raster_phase5,
204 drawRectSet();
205 ASSERT_PIXEL_EQ(0x801fu, 2, 2);
206)
207
208CESTER_TEST(rect_mask_set_right_edge_excluded, gpu_raster_phase5,
209 drawRectSet();
210 ASSERT_PIXEL_EQ((unsigned)MASK_SUITE_SENTINEL, 4, 0);
211)
212
213CESTER_TEST(rect_mask_check_preserves_red_in_overlap, gpu_raster_phase5,
214 drawRectSetThenCheck();
215 /* (2, 0) in both rects. RED mask-set written first, GREEN
216 check-mask should skip. Expect 0x801f preserved. */
217 ASSERT_PIXEL_EQ(0x801fu, 2, 0);
218)
219
220CESTER_TEST(rect_mask_check_fills_green_in_non_overlap, gpu_raster_phase5,
221 drawRectSetThenCheck();
222 /* (6, 0) only in GREEN rect, sentinel before, mask bit clear,
223 should fill GREEN. */
225)
226
227CESTER_TEST(rect_mask_check_red_untouched_left, gpu_raster_phase5,
228 drawRectSetThenCheck();
229 /* (0, 0) RED-mask only (not in GREEN overlap). Stays 0x801f. */
230 ASSERT_PIXEL_EQ(0x801fu, 0, 0);
231)
232
233// --------------------------------------------------------------------------
234// Line mask-bit
235// --------------------------------------------------------------------------
236
237CESTER_TEST(line_mask_set_start_has_bit15, gpu_raster_phase5,
238 drawLineSet();
239 ASSERT_PIXEL_EQ(0x801fu, 0, 0);
240)
241
242CESTER_TEST(line_mask_set_mid_has_bit15, gpu_raster_phase5,
243 drawLineSet();
244 ASSERT_PIXEL_EQ(0x801fu, 3, 0);
245)
246
247CESTER_TEST(line_mask_set_end_inclusive, gpu_raster_phase5,
248 drawLineSet();
249 /* PSX lines are endpoint-inclusive (verified phase-2). */
250 ASSERT_PIXEL_EQ(0x801fu, 5, 0);
251)
252
253CESTER_TEST(line_mask_check_preserves_overlap, gpu_raster_phase5,
254 drawLineSetThenCheck();
255 ASSERT_PIXEL_EQ(0x801fu, 4, 0);
256)
257
258CESTER_TEST(line_mask_check_fills_new_pixel, gpu_raster_phase5,
259 drawLineSetThenCheck();
261)
262
263// --------------------------------------------------------------------------
264// Textured rectangle mask-bit (4-bit CLUT)
265// --------------------------------------------------------------------------
266
267CESTER_TEST(texrect4_mask_set_origin_has_bit15, gpu_raster_phase5,
268 drawTexRect4Set();
269 /* (0,0) of textured rect samples UV (1,1) -> CLUT[1] from 4-bit
270 fixture = vram555(1, 30, 0) = 0x03C1, with mask bit forced:
271 0x83C1. */
272 uint16_t expected = (uint16_t)(expectedClut4Color(1) | 0x8000);
273 ASSERT_PIXEL_EQ(expected, 0, 0);
274)
275
276CESTER_TEST(texrect4_mask_set_interior_has_bit15, gpu_raster_phase5,
277 drawTexRect4Set();
278 /* (2,2) of rect samples UV (3,3) -> CLUT[3] | 0x8000. */
279 uint16_t expected = (uint16_t)(expectedClut4Color(3) | 0x8000);
280 ASSERT_PIXEL_EQ(expected, 2, 2);
281)
282
283CESTER_TEST(texrect4_mask_set_right_edge_excluded, gpu_raster_phase5,
284 drawTexRect4Set();
285 ASSERT_PIXEL_EQ((unsigned)MASK_SUITE_SENTINEL, 4, 0);
286)
287
288// --------------------------------------------------------------------------
289// Textured triangle mask-bit at all three depths
290// --------------------------------------------------------------------------
291
292CESTER_TEST(textri4_mask_set_origin, gpu_raster_phase5,
293 drawTexTri4Set();
294 /* At screen (0,0), UV (1,1), CLUT[1] | 0x8000. */
295 uint16_t expected = (uint16_t)(expectedClut4Color(1) | 0x8000);
296 ASSERT_PIXEL_EQ(expected, 0, 0);
297)
298
299CESTER_TEST(textri4_mask_set_interior, gpu_raster_phase5,
300 drawTexTri4Set();
301 uint16_t expected = (uint16_t)(expectedClut4Color(2) | 0x8000);
302 ASSERT_PIXEL_EQ(expected, 1, 1);
303)
304
305CESTER_TEST(textri4_mask_set_right_edge_excluded, gpu_raster_phase5,
306 drawTexTri4Set();
307 ASSERT_PIXEL_EQ((unsigned)MASK_SUITE_SENTINEL, 4, 0);
308)
309
310CESTER_TEST(textri8_mask_set_origin, gpu_raster_phase5,
311 drawTexTri8Set();
312 uint16_t expected = (uint16_t)(expectedClut8Color(1) | 0x8000);
313 ASSERT_PIXEL_EQ(expected, 0, 0);
314)
315
316CESTER_TEST(textri8_mask_set_interior, gpu_raster_phase5,
317 drawTexTri8Set();
318 uint16_t expected = (uint16_t)(expectedClut8Color(2) | 0x8000);
319 ASSERT_PIXEL_EQ(expected, 1, 1);
320)
321
322CESTER_TEST(textri15_mask_set_origin, gpu_raster_phase5,
323 drawTexTri15Set();
324 /* 15-bit direct: texel at UV (1,1) is vram555(1, 1, 2) | 0x8000. */
325 uint16_t expected = (uint16_t)(expectedTex15Color(1, 1) | 0x8000);
326 ASSERT_PIXEL_EQ(expected, 0, 0);
327)
328
329CESTER_TEST(textri15_mask_set_interior, gpu_raster_phase5,
330 drawTexTri15Set();
331 uint16_t expected = (uint16_t)(expectedTex15Color(2, 2) | 0x8000);
332 ASSERT_PIXEL_EQ(expected, 1, 1);
333)
334
335CESTER_TEST(textri15_mask_set_right_edge_excluded, gpu_raster_phase5,
336 drawTexTri15Set();
337 ASSERT_PIXEL_EQ((unsigned)MASK_SUITE_SENTINEL, 4, 0);
338)
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
CESTER_BODY(static uint16_t s_cddaCapture;static int cddaSpuDma(uint32_t spuAddr, void *buf, uint32_t bytes, int isRead) { const uint16_t tsa=spuAddr > > 3;const uint16_t mode=isRead ? 0x30 :0x20;SPU_RAM_DTA=tsa;for(int i=0;i< 0xf01 &&SPU_RAM_DTA !=tsa;i++);SPU_CTRL=(SPU_CTRL &~0x30)|mode;for(volatile int i=0;i< 60;i++);for(int i=0;i< 0xf01 &&(SPU_STATUS &0x30) !=mode;i++);CDDA_SPU_DELAY=(CDDA_SPU_DELAY &0xf0ffffff)|(isRead ? 0x22000000 :0x20000000);DMA_CTRL[DMA_SPU].MADR=(uint32_t) buf &0x1fffffff;DMA_CTRL[DMA_SPU].BCR=((bytes > > 6)<< 16)|0x10;DMA_CTRL[DMA_SPU].CHCR=isRead ? 0x01000200 :0x01000201;int ok=0;for(unsigned t=0;t< 4000000u;t++) { if((DMA_CTRL[DMA_SPU].CHCR &0x01000000)==0) { ok=1;break;} } SPU_CTRL=SPU_CTRL &~0x30;for(volatile int i=0;i< 60;i++);return ok;} static int cddaSimpleCmd(uint8_t cmd) { uint8_t response[16];CDROM_REG0=0;CDROM_REG1=cmd;uint32_t timeout=2000000;int ok=waitCDRomIRQWithTimeout(&timeout);ackCDRomCause();readResponse(response);return ok;} static void cddaWaitPauseComplete() { initializeTime();uint32_t elapsed=0;while(elapsed< 5000000u) { uint32_t budget=200000u;int ok=waitCDRomIRQWithTimeout(&budget);elapsed=budget;if(!ok) continue;uint8_t cause=ackCDRomCause();uint8_t response[16];readResponse(response);if(cause==2) return;} } static int cddaAudioSetup(const uint8_t atv[4], int demute) { if(!resetCDRom()) return 0;DPCR|=0x000b0000;SPU_CTRL=0;for(volatile int i=0;i< 20000;i++);SPU_VOL_MAIN_LEFT=0;SPU_VOL_MAIN_RIGHT=0;SPU_KEY_OFF_LOW=0xffff;SPU_KEY_OFF_HIGH=0xffff;SPU_RAM_DTC=4;SPU_CTRL=0x8001;for(volatile int i=0;i< 20000;i++);for(int i=0;i< 0x400;i++) s_cddaCapture[i]=CDDA_MARKER;if(!cddaSpuDma(0x000, s_cddaCapture, 0x800, 0)) return 0;if(!cddaSimpleCmd(demute ? CDL_DEMUTE :CDL_MUTE)) return 0;CDROM_REG0=2;CDROM_REG2=atv[0];CDROM_REG3=atv[1];CDROM_REG0=3;CDROM_REG1=atv[2];CDROM_REG2=atv[3];CDROM_REG0=3;CDROM_REG3=0x20;CDROM_REG0=0;SPU_VOL_CD_LEFT=0x7fff;SPU_VOL_CD_RIGHT=0x7fff;SPU_CTRL=SPU_CTRL|0x8001;return 1;} static int cddaBufferPeak(unsigned *signChanges) { int peak=0;unsigned changes=0;int prev=0;for(int i=0;i< 0x200;i++) { int v=(int16_t) s_cddaCapture[i];int a=v< 0 ? -v :v;if(a > peak) peak=a;int sign=v >=0 ? 1 :-1;if(i &&sign !=prev) changes++;prev=sign;} *signChanges=changes;return peak;} static int cddaCapturePeak(unsigned *signChanges) { int peak=0;for(unsigned attempt=0;attempt< 8;attempt++) { if(attempt) { initializeTime();while(updateTime()< 250000u);} if(!cddaSpuDma(0x000, s_cddaCapture, 0x800, 1)) return -1;peak=cddaBufferPeak(signChanges);if(peak) { if(attempt) ramsyscall_printf("capture was empty %u time(s) before audio\n", attempt);break;} } return peak;} static int cddaPeak(const uint8_t atv[4], int demute, unsigned *signChanges) { if(!cddaAudioSetup(atv, demute)) return -1;if(!setMode(0)) return -1;CDROM_REG0=0;CDROM_REG2=0x02;if(!cddaSimpleCmd(CDL_PLAY)) return -1;initializeTime();while(updateTime()< 1500000u);int peak=cddaCapturePeak(signChanges);CDROM_REG0=0;CDROM_REG1=CDL_PAUSE;cddaWaitPauseComplete();CDROM_REG0=0;CDROM_REG2=0x00;CDROM_REG2=0x02;CDROM_REG2=0x16;if(cddaSimpleCmd(CDL_SETLOC) &&cddaSimpleCmd(CDL_SEEKL)) cddaWaitPauseComplete();return peak;}[0x400])
Definition cdda.c:37
#define MASK_SUITE_SENTINEL
Definition cross-primitive-mask.c:42
sendGPUData(0xe1000000)
#define GPU_DATA
Definition hwregs.h:52
volatile void * u0
Definition load-timings.c:875
#define RASTER_VRAM_GREEN
Definition raster-helpers.h:127
#define RASTER_CMD_RED
Definition raster-helpers.h:124
#define RASTER_CMD_GREEN
Definition raster-helpers.h:126
#define ASSERT_PIXEL_EQ(expected, x_, y_)
Definition raster-helpers.h:473
#define CLUT8_FIELD
Definition texture-fixtures.h:84
#define TEX15_TY
Definition texture-fixtures.h:66
#define TEX8_TY
Definition texture-fixtures.h:61
#define CLUT4_FIELD
Definition texture-fixtures.h:83
#define TEX15_TPAGE
Definition texture-fixtures.h:102
#define TEX8_TPAGE
Definition texture-fixtures.h:101
#define CLUT15_FIELD
Definition texture-fixtures.h:85
#define TEX15_TX
Definition texture-fixtures.h:65
#define TEX8_TX
Definition texture-fixtures.h:60
#define TEX_MOD_NEUTRAL
Definition texture-fixtures.h:308
#define TEX4_TY
Definition texture-fixtures.h:56
#define TEX4_TX
Definition texture-fixtures.h:55
#define TEX4_TPAGE
Definition texture-fixtures.h:100