Nugget
Loading...
Searching...
No Matches
tex-window-exhaustive.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// Texture windowing exhaustive probes. Naming:
28// WT_MASKxx_OFFxx_<probe> triangle path window
29// WR_MASKxx_OFFxx_<probe> rect path window
30// WQ_MASKxx_OFFxx_<probe> quad path window (verifies the rule
31// applies to 4-vert sweep too)
32// WS_ABR<n>_<probe> window × semi-trans
33// WX_<probe> window × bit-15 transparency
34
36
37// Draw a 32x16 8-bit textured triangle with UV matching screen, then
38// the test reads back at a given probe pixel position. Window state
39// is set just before the draw via the caller.
40static void drawWindowTri(uint8_t mask_x, uint8_t mask_y,
41 uint8_t off_x, uint8_t off_y) {
42 rasterReset();
43 rasterClearTestRegion(0, 0, 64, 16);
44 setTexpage(TEX8_TX, TEX8_TY, 1);
45 setTextureWindow(mask_x, mask_y, off_x, off_y);
46 rasterTexTri(TEX_MOD_NEUTRAL,
47 0, 0, 0, 0,
48 32, 0, 32, 0,
49 0, 8, 0, 8,
51 rasterFlushPrimitive();
52}
53
54static void drawWindowRect(uint8_t mask_x, uint8_t mask_y,
55 uint8_t off_x, uint8_t off_y) {
56 rasterReset();
57 rasterClearTestRegion(0, 0, 64, 16);
58 setTexpage(TEX8_TX, TEX8_TY, 1);
59 setTextureWindow(mask_x, mask_y, off_x, off_y);
60 rasterTexRect(TEX_MOD_NEUTRAL, 0, 0, 0, 0, 32, 8, CLUT8_FIELD);
61 rasterFlushPrimitive();
62}
63
64static void drawWindowQuad(uint8_t mask_x, uint8_t mask_y,
65 uint8_t off_x, uint8_t off_y) {
66 rasterReset();
67 rasterClearTestRegion(0, 0, 64, 16);
68 setTexpage(TEX8_TX, TEX8_TY, 1);
69 setTextureWindow(mask_x, mask_y, off_x, off_y);
70 rasterFlatTexQuad(TEX_MOD_NEUTRAL,
71 0, 0, 0, 0,
72 31, 0, 31, 0,
73 0, 7, 0, 7,
74 31, 7, 31, 7,
76 rasterFlushPrimitive();
77}
78
79// Window × semi-trans: 8-bit rect with mask_u=0x01, offset_u=0,
80// background pre-filled red. Window collapses u=8..15 to texels 0..7.
81// Probe x=8 - WITHOUT window, samples CLUT8[8] = vram555(8, 23, 0).
82// WITH window, samples CLUT8[0] = vram555(0, 31, 0). Bit-15 not set,
83// no blend - hardware should write the windowed texel value through.
84//
85// At ABR != 0 the rect command line above shouldn't blend (texel bit
86// 15 is 0). This series verifies the gate semantics under window.
87static void drawWindowSemi(uint8_t abr) {
88 rasterReset();
89 rasterFillRect(0, 0, 64, 16, RASTER_VRAM_RED);
90 setTexpageAbr(TEX8_TX, TEX8_TY, 1, abr);
91 setTextureWindow(0x01, 0, 0, 0);
92 rasterTexRectSemi(TEX_MOD_NEUTRAL, 0, 0, 0, 0, 16, 4, CLUT8_FIELD);
93 rasterFlushPrimitive();
94 setTexpage(0, 0, 0);
95}
96
97// Window × bit-15-mask transparency: same window as above (mask_u=0x01
98// collapses u=8..15 to 0..7), but CLUT8[0] has bit-15 SET. Without
99// the window, sampling u=8 gives CLUT8[8] (no mask, opaque). With the
100// window, sampling u=8 gives CLUT8[0] (mask SET, semi-trans gate
101// fires). Probes whether transparency applies on the windowed sample.
102static void drawWindowTransparency(uint8_t abr) {
103 rasterReset();
104 rasterFillRect(0, 0, 64, 16, RASTER_VRAM_RED);
105 uploadClut8MaskedAt0();
106 setTexpageAbr(TEX8_TX, TEX8_TY, 1, abr);
107 setTextureWindow(0x01, 0, 0, 0);
108 /* At x=8 in semi-trans rect path, hardware samples u=8 -> window
109 collapses to u=0 -> CLUT8[0] (now masked). Whether the gate
110 fires on the windowed or unwindowed value is the question. */
111 rasterTexRectSemi(TEX_MOD_NEUTRAL, 0, 0, 0, 0, 16, 4, CLUT8_FIELD);
112 rasterFlushPrimitive();
113 setTexpage(0, 0, 0);
114 /* Restore standard CLUT8 for subsequent tests. */
115 uploadClut8();
116}
117
118) // CESTER_BODY
119
120// ============================================================================
121// WT_MASK0X_OFF00: mask sweep at offset=0, single-axis (U).
122// Probe x=8 - the test position where the mask effect kicks in.
123// ============================================================================
124
125CESTER_TEST(wt_mask00_off00_identity, gpu_raster_phase15,
126 drawWindowTri(0x00, 0, 0, 0);
127 /* mask=0 = identity. x=8 samples texel u=8 normally. */
128 ASSERT_PIXEL_EQ(expectedClut8Color(8), 8, 0);
129)
130CESTER_TEST(wt_mask01_off00_wrap8, gpu_raster_phase15,
131 drawWindowTri(0x01, 0, 0, 0);
132 /* bit 3 cleared: x=8 -> u=8 -> &~0x08=0 -> CLUT8[0]. */
133 ASSERT_PIXEL_EQ(expectedClut8Color(0), 8, 0);
134)
135CESTER_TEST(wt_mask07_off00_x8, gpu_raster_phase15,
136 drawWindowTri(0x07, 0, 0, 0);
137 /* bits 3,4,5 cleared: x=8 -> u=8 -> &~0x38=0 -> CLUT8[0]. */
138 ASSERT_PIXEL_EQ(expectedClut8Color(0), 8, 0);
139)
140CESTER_TEST(wt_mask0f_off00_x8, gpu_raster_phase15,
141 drawWindowTri(0x0f, 0, 0, 0);
142 /* bits 3-6 cleared: x=8 -> u=8 -> &~0x78=0 -> CLUT8[0]. */
143 ASSERT_PIXEL_EQ(expectedClut8Color(0), 8, 0);
144)
145CESTER_TEST(wt_mask1f_off00_x8, gpu_raster_phase15,
146 drawWindowTri(0x1f, 0, 0, 0);
147 /* bits 3-7 cleared: x=8 -> u=8 -> &~0xf8=0 -> CLUT8[0]. */
148 ASSERT_PIXEL_EQ(expectedClut8Color(0), 8, 0);
149)
150CESTER_TEST(wt_mask1f_off00_x1, gpu_raster_phase15,
151 drawWindowTri(0x1f, 0, 0, 0);
152 /* x=1 -> u=1. &~0xf8 = 1 -> CLUT8[1]. */
153 ASSERT_PIXEL_EQ(expectedClut8Color(1), 1, 0);
154)
155CESTER_TEST(wt_mask1f_off00_x7, gpu_raster_phase15,
156 drawWindowTri(0x1f, 0, 0, 0);
157 /* x=7 -> u=7 -> &~0xf8 = 7 -> CLUT8[7]. */
158 ASSERT_PIXEL_EQ(expectedClut8Color(7), 7, 0);
159)
160
161// ============================================================================
162// WT_MASK0F_OFFxx: offset sweep at mask=0x0F (4-bit window).
163// Offset bits get OR'd into the high bits of u after masking.
164// ============================================================================
165
166CESTER_TEST(wt_mask0f_off01_x0, gpu_raster_phase15,
167 drawWindowTri(0x0f, 0, 0x01, 0);
168 /* x=0 -> u=0; filtered_u = 0 | (0x01 << 3) = 0x08 -> CLUT8[8]. */
169 ASSERT_PIXEL_EQ(expectedClut8Color(8), 0, 0);
170)
171CESTER_TEST(wt_mask0f_off03_x0, gpu_raster_phase15,
172 drawWindowTri(0x0f, 0, 0x03, 0);
173 /* filtered_u = 0 | 0x18 = 0x18 = 24 -> CLUT8[24]. */
174 ASSERT_PIXEL_EQ(expectedClut8Color(0x18), 0, 0);
175)
176CESTER_TEST(wt_mask0f_off0f_x0, gpu_raster_phase15,
177 drawWindowTri(0x0f, 0, 0x0f, 0);
178 /* filtered_u = 0 | 0x78 = 0x78 = 120. Per the formula CLUT8[120]
179 would apply, but the 8-bit fixture is only uploaded for u=0..63
180 so u=120 reads BEYOND the uploaded region into whatever VRAM
181 contains there. Hardware truth captured at 0x03e0; this is an
182 artefact of the test fixture not covering the full mask range,
183 not a window-formula divergence. The formula was already
184 verified within-range above. */
186)
187CESTER_TEST(wt_mask1f_off1f_x0, gpu_raster_phase15,
188 drawWindowTri(0x1f, 0, 0x1f, 0);
189 /* filtered_u = 0xf8 = 248. Outside uploaded texture range; hardware
190 truth captured. Same caveat as above. */
192)
193
194// ============================================================================
195// Combined U-V mask × offset. V is interesting because our fixture
196// pattern doesn't depend on V, so V-windowing alone should be a no-op
197// at the pixel level. But the V coords get applied to VRAM read
198// addressing - hardware truth is what matters.
199// ============================================================================
200
201CESTER_TEST(wt_maskU01_maskV01_off00_x8y4, gpu_raster_phase15,
202 drawWindowTri(0x01, 0x01, 0, 0);
203 /* (8, 4) -> u=8 & ~8 = 0, v=4 & ~8 = 4. CLUT8[0] (V not used). */
204 ASSERT_PIXEL_EQ(expectedClut8Color(0), 8, 4);
205)
206CESTER_TEST(wt_maskU01_maskV01_off11_x8y4, gpu_raster_phase15,
207 drawWindowTri(0x01, 0x01, 0x01, 0x01);
208 /* offset (1, 1) sets bit 3 of u and v.
209 u=8 & ~8 | 8 = 8; v=4 & ~8 | 8 = 12. CLUT8[8] = expectedClut8Color(8). */
210 ASSERT_PIXEL_EQ(expectedClut8Color(8), 8, 4);
211)
212
213// ============================================================================
214// Offset > mask. When offset bits are NOT covered by mask, only the
215// mask-covered bits of offset apply. psx-spx: filtered = (u & ~M) | (O & M).
216// ============================================================================
217
218CESTER_TEST(wt_mask01_off07_x0, gpu_raster_phase15,
219 drawWindowTri(0x01, 0, 0x07, 0);
220 /* mask=0x01 only covers bit 3. offset=0x07 has bits 3-5 set.
221 Only bit 3 of offset applies (the rest masked out).
222 filtered_u = 0 & ~8 | (0x07 << 3) & 8 = 0 | 8 = 8 -> CLUT8[8]. */
223 ASSERT_PIXEL_EQ(expectedClut8Color(8), 0, 0);
224)
225CESTER_TEST(wt_mask03_off1f_x0, gpu_raster_phase15,
226 drawWindowTri(0x03, 0, 0x1f, 0);
227 /* mask=0x03 covers bits 3-4. offset=0x1F has bits 3-7.
228 filtered_u = 0 | (0xf8 & 0x18) = 0x18 -> CLUT8[24]. */
229 ASSERT_PIXEL_EQ(expectedClut8Color(0x18), 0, 0);
230)
231
232// ============================================================================
233// Prim-type sweep: window applied to rect and quad. Same mask/offset
234// as the triangle reference probe; output should match.
235// ============================================================================
236
237CESTER_TEST(wr_mask01_off00_x8, gpu_raster_phase15,
238 drawWindowRect(0x01, 0, 0, 0);
239 /* Rect at (0,0) 32x8 with mask_u=0x01, x=8 -> u=8 -> wraps to 0. */
240 ASSERT_PIXEL_EQ(expectedClut8Color(0), 8, 0);
241)
242CESTER_TEST(wq_mask01_off00_x8, gpu_raster_phase15,
243 drawWindowQuad(0x01, 0, 0, 0);
244 /* Quad at (0,0)-(31,0)-(0,7)-(31,7) with mask_u=0x01, x=8 same as
245 triangle - wraps to u=0. */
246 ASSERT_PIXEL_EQ(expectedClut8Color(0), 8, 0);
247)
248CESTER_TEST(wr_mask03_off01_x12, gpu_raster_phase15,
249 drawWindowRect(0x03, 0, 0x01, 0);
250 /* x=12 -> u=12=0x0C, &~0x18 = 0x04, | 0x08 = 0x0C -> CLUT8[12]. */
251 ASSERT_PIXEL_EQ(expectedClut8Color(12), 12, 0);
252)
253CESTER_TEST(wq_mask03_off01_x12, gpu_raster_phase15,
254 drawWindowQuad(0x03, 0, 0x01, 0);
255 ASSERT_PIXEL_EQ(expectedClut8Color(12), 12, 0);
256)
257
258// ============================================================================
259// Window × semi-trans (window-filtered texel through ABR blend).
260// At x=8 with mask_u=0x01: windowed u=0, CLUT8[0] = vram555(0, 31, 0)
261// = 0x03e0 (bit-15 = 0 in standard fixture). With bit-15=0, semi-trans
262// gate does NOT fire - texel writes through opaquely. So all four
263// ABR modes should produce the same un-blended texel = 0x03e0.
264// ============================================================================
265
266CESTER_TEST(ws_window_semi_abr0, gpu_raster_phase15,
267 drawWindowSemi(0);
269)
270CESTER_TEST(ws_window_semi_abr1, gpu_raster_phase15,
271 drawWindowSemi(1);
273)
274CESTER_TEST(ws_window_semi_abr2, gpu_raster_phase15,
275 drawWindowSemi(2);
277)
278CESTER_TEST(ws_window_semi_abr3, gpu_raster_phase15,
279 drawWindowSemi(3);
281)
282
283// ============================================================================
284// Window × bit-15 transparency. With mask_u=0x01, x=8 collapses to
285// u=0, sampling CLUT8[0] which now has bit-15 SET. Question: does
286// the gate fire on the WINDOWED sample (yes -> blend with bg) or on
287// the unfiltered u=8 sample (no - CLUT8[8] has bit-15=0)?
288// ============================================================================
289
290CESTER_TEST(wx_window_transparency_abr0_x8, gpu_raster_phase15,
291 drawWindowTransparency(0);
292 /* If gate fires on windowed sample (bit-15=1 at CLUT8[0]): ABR=0
293 blend of red bg and CLUT8[0]. If gate fires on raw u=8 sample
294 (bit-15=0 at CLUT8[8] before window): no blend, output = ???
295 Hardware truth captured. */
297)
298CESTER_TEST(wx_window_transparency_abr0_x0, gpu_raster_phase15,
299 drawWindowTransparency(0);
300 /* Control: x=0 doesn't go through window (u=0 mapped to u=0).
301 Same as no-window case at x=0. */
303)
CESTER_BODY(static int s_got40;static int s_got80;static uint32_t s_cause;static uint32_t s_epc;static uint32_t s_from;static uint32_t *s_resume;static uint32_t *s_regs;static uint32_t(*s_customhandler)()=NULL;static uint32_t s_oldIMASK;static uint32_t s_oldDPCR;static uint32_t s_oldDICR;uint32_t handler(uint32_t *regs, uint32_t from) { if(from==0x40) s_got40=1;if(from==0x80) s_got80=1;uint32_t cause;uint32_t epc;s_from=from;asm("mfc0 %0, $13\nnop\nmfc0 %1, $14\nnop" :"=r"(cause), "=r"(epc));s_cause=cause;s_epc=epc;if(s_customhandler) { return s_customhandler();} else { return s_resume ?((uint32_t) s_resume) :(epc+4);} } void installExceptionHandlers(uint32_t(*handler)(uint32_t *regs, uint32_t from));void uninstallExceptionHandlers();uint32_t branchbranch1();uint32_t branchbranch2();uint32_t jumpjump1();uint32_t jumpjump2();uint32_t cpu_LWR_LWL_half(uint32_t buff[], uint32_t initial);uint32_t cpu_LWR_LWL_nodelay(uint32_t buff[], uint32_t initial);uint32_t cpu_LWR_LWL_delayed(uint32_t buff[], uint32_t initial);uint32_t cpu_LWR_LWL_load_different(uint32_t buff[], uint32_t initial);uint32_t cpu_LW_LWR(uint32_t buff[], uint32_t initial);uint32_t cpu_delayed_load(uint32_t buff[], uint32_t override);uint32_t cpu_delayed_load_cancelled(uint32_t buff[], uint32_t override);uint64_t cpu_delayed_load_load(uint32_t buff[], uint32_t override);uint32_t linkandload();uint32_t lwandlink();uint32_t nolink();static int s_interruptsWereEnabled;) CESTER_BEFORE_EACH(cpu_tests
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
#define WX_WINDOW_TRANS_ABR0
Definition raster-expected-phase15.h:60
#define WT_MASK1F_OFF1F_X0_HW
Definition raster-expected-phase15.h:68
#define WT_MASK0F_OFF0F_X0_HW
Definition raster-expected-phase15.h:67
#define WS_WINDOW_SEMI_NO_BLEND
Definition raster-expected-phase15.h:41
#define WX_WINDOW_TRANS_X0
Definition raster-expected-phase15.h:61
#define RASTER_VRAM_RED
Definition raster-helpers.h:124
#define ASSERT_PIXEL_EQ(expected, x_, y_)
Definition raster-helpers.h:472
#define CLUT8_FIELD
Definition texture-fixtures.h:84
#define TEX8_TY
Definition texture-fixtures.h:61
#define TEX8_TPAGE
Definition texture-fixtures.h:103
#define TEX8_TX
Definition texture-fixtures.h:60
#define TEX_MOD_NEUTRAL
Definition texture-fixtures.h:328