Nugget
Bare-metal libraries and examples for the original PlayStation
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
35// clang-format off
36
38
39/* Writes one VRAM pixel and flushes the texture cache, for the probes
40 that sample outside the fixture uploads. */
41static void writeVramPixel(uint16_t x, uint16_t y, uint16_t value) {
42 waitGPU();
43 GPU_DATA = 0xa0000000u;
44 GPU_DATA = ((uint32_t)y << 16) | x;
45 GPU_DATA = (1u << 16) | 1u;
47 waitGPU();
48 GPU_DATA = 0x01000000u;
49}
50
51// Draw a 32x16 8-bit textured triangle with UV matching screen, then
52// the test reads back at a given probe pixel position. Window state
53// is set just before the draw via the caller.
54static void drawWindowTri(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 rasterTexTri(TEX_MOD_NEUTRAL,
61 0, 0, 0, 0,
62 32, 0, 32, 0,
63 0, 8, 0, 8,
65 rasterFlushPrimitive();
66}
67
68static void drawWindowRect(uint8_t mask_x, uint8_t mask_y,
69 uint8_t off_x, uint8_t off_y) {
70 rasterReset();
71 rasterClearTestRegion(0, 0, 64, 16);
72 setTexpage(TEX8_TX, TEX8_TY, 1);
73 setTextureWindow(mask_x, mask_y, off_x, off_y);
74 rasterTexRect(TEX_MOD_NEUTRAL, 0, 0, 0, 0, 32, 8, CLUT8_FIELD);
75 rasterFlushPrimitive();
76}
77
78static void drawWindowQuad(uint8_t mask_x, uint8_t mask_y,
79 uint8_t off_x, uint8_t off_y) {
80 rasterReset();
81 rasterClearTestRegion(0, 0, 64, 16);
82 setTexpage(TEX8_TX, TEX8_TY, 1);
83 setTextureWindow(mask_x, mask_y, off_x, off_y);
84 rasterFlatTexQuad(TEX_MOD_NEUTRAL,
85 0, 0, 0, 0,
86 31, 0, 31, 0,
87 0, 7, 0, 7,
88 31, 7, 31, 7,
90 rasterFlushPrimitive();
91}
92
93// Window × semi-trans: 8-bit rect with mask_u=0x01, offset_u=0,
94// background pre-filled red. Window collapses u=8..15 to texels 0..7.
95// Probe x=8 - WITHOUT window, samples CLUT8[8] = vram555(8, 23, 0).
96// WITH window, samples CLUT8[0] = vram555(0, 31, 0). Bit-15 not set,
97// no blend - hardware should write the windowed texel value through.
98//
99// At ABR != 0 the rect command line above shouldn't blend (texel bit
100// 15 is 0). This series verifies the gate semantics under window.
101static void drawWindowSemi(uint8_t abr) {
102 rasterReset();
103 rasterFillRect(0, 0, 64, 16, RASTER_VRAM_RED);
104 setTexpageAbr(TEX8_TX, TEX8_TY, 1, abr);
105 setTextureWindow(0x01, 0, 0, 0);
106 rasterTexRectSemi(TEX_MOD_NEUTRAL, 0, 0, 0, 0, 16, 4, CLUT8_FIELD);
107 rasterFlushPrimitive();
108 setTexpage(0, 0, 0);
109}
110
111// Window × bit-15-mask transparency: same window as above (mask_u=0x01
112// collapses u=8..15 to 0..7), but CLUT8[0] has bit-15 SET. Without
113// the window, sampling u=8 gives CLUT8[8] (no mask, opaque). With the
114// window, sampling u=8 gives CLUT8[0] (mask SET, semi-trans gate
115// fires). Probes whether transparency applies on the windowed sample.
116static void drawWindowTransparency(uint8_t abr) {
117 rasterReset();
118 rasterFillRect(0, 0, 64, 16, RASTER_VRAM_RED);
119 uploadClut8MaskedAt0();
120 setTexpageAbr(TEX8_TX, TEX8_TY, 1, abr);
121 setTextureWindow(0x01, 0, 0, 0);
122 /* At x=8 in semi-trans rect path, hardware samples u=8 -> window
123 collapses to u=0 -> CLUT8[0] (now masked). Whether the gate
124 fires on the windowed or unwindowed value is the question. */
125 rasterTexRectSemi(TEX_MOD_NEUTRAL, 0, 0, 0, 0, 16, 4, CLUT8_FIELD);
126 rasterFlushPrimitive();
127 setTexpage(0, 0, 0);
128 /* Restore standard CLUT8 for subsequent tests. */
129 uploadClut8();
130}
131
132) // CESTER_BODY
133
134// ============================================================================
135// WT_MASK0X_OFF00: mask sweep at offset=0, single-axis (U).
136// Probe x=8 - the test position where the mask effect kicks in.
137// ============================================================================
138
139CESTER_TEST(wt_mask00_off00_identity, gpu_raster_phase15,
140 drawWindowTri(0x00, 0, 0, 0);
141 /* mask=0 = identity. x=8 samples texel u=8 normally. */
142 ASSERT_PIXEL_EQ(expectedClut8Color(8), 8, 0);
143)
144CESTER_TEST(wt_mask01_off00_wrap8, gpu_raster_phase15,
145 drawWindowTri(0x01, 0, 0, 0);
146 /* bit 3 cleared: x=8 -> u=8 -> &~0x08=0 -> CLUT8[0]. */
147 ASSERT_PIXEL_EQ(expectedClut8Color(0), 8, 0);
148)
149CESTER_TEST(wt_mask07_off00_x8, gpu_raster_phase15,
150 drawWindowTri(0x07, 0, 0, 0);
151 /* bits 3,4,5 cleared: x=8 -> u=8 -> &~0x38=0 -> CLUT8[0]. */
152 ASSERT_PIXEL_EQ(expectedClut8Color(0), 8, 0);
153)
154CESTER_TEST(wt_mask0f_off00_x8, gpu_raster_phase15,
155 drawWindowTri(0x0f, 0, 0, 0);
156 /* bits 3-6 cleared: x=8 -> u=8 -> &~0x78=0 -> CLUT8[0]. */
157 ASSERT_PIXEL_EQ(expectedClut8Color(0), 8, 0);
158)
159CESTER_TEST(wt_mask1f_off00_x8, gpu_raster_phase15,
160 drawWindowTri(0x1f, 0, 0, 0);
161 /* bits 3-7 cleared: x=8 -> u=8 -> &~0xf8=0 -> CLUT8[0]. */
162 ASSERT_PIXEL_EQ(expectedClut8Color(0), 8, 0);
163)
164CESTER_TEST(wt_mask1f_off00_x1, gpu_raster_phase15,
165 drawWindowTri(0x1f, 0, 0, 0);
166 /* x=1 -> u=1. &~0xf8 = 1 -> CLUT8[1]. */
167 ASSERT_PIXEL_EQ(expectedClut8Color(1), 1, 0);
168)
169CESTER_TEST(wt_mask1f_off00_x7, gpu_raster_phase15,
170 drawWindowTri(0x1f, 0, 0, 0);
171 /* x=7 -> u=7 -> &~0xf8 = 7 -> CLUT8[7]. */
172 ASSERT_PIXEL_EQ(expectedClut8Color(7), 7, 0);
173)
174
175// ============================================================================
176// WT_MASK0F_OFFxx: offset sweep at mask=0x0F (4-bit window).
177// Offset bits get OR'd into the high bits of u after masking.
178// ============================================================================
179
180CESTER_TEST(wt_mask0f_off01_x0, gpu_raster_phase15,
181 drawWindowTri(0x0f, 0, 0x01, 0);
182 /* x=0 -> u=0; filtered_u = 0 | (0x01 << 3) = 0x08 -> CLUT8[8]. */
183 ASSERT_PIXEL_EQ(expectedClut8Color(8), 0, 0);
184)
185CESTER_TEST(wt_mask0f_off03_x0, gpu_raster_phase15,
186 drawWindowTri(0x0f, 0, 0x03, 0);
187 /* filtered_u = 0 | 0x18 = 0x18 = 24 -> CLUT8[24]. */
188 ASSERT_PIXEL_EQ(expectedClut8Color(0x18), 0, 0);
189)
190CESTER_TEST(wt_mask0f_off0f_x0, gpu_raster_phase15,
191 writeVramPixel(TEX8_VRAM_BASE_X + 60, 0, WT_U120_PROBE_INDEX);
192 drawWindowTri(0x0f, 0, 0x0f, 0);
193 /* filtered_u = 0 | 0x78 = 0x78 = 120, past the u=0..63 fixture;
194 the texel there was written above. */
196)
197CESTER_TEST(wt_mask1f_off1f_x0, gpu_raster_phase15,
198 writeVramPixel(TEX8_VRAM_BASE_X + 124, 0, WT_U248_PROBE_INDEX);
199 drawWindowTri(0x1f, 0, 0x1f, 0);
200 /* filtered_u = 0xf8 = 248, past the fixture; written above. */
202)
203
204// ============================================================================
205// Combined U-V mask × offset. V is interesting because our fixture
206// pattern doesn't depend on V, so V-windowing alone should be a no-op
207// at the pixel level. But the V coords get applied to VRAM read
208// addressing - hardware truth is what matters.
209// ============================================================================
210
211CESTER_TEST(wt_maskU01_maskV01_off00_x8y4, gpu_raster_phase15,
212 drawWindowTri(0x01, 0x01, 0, 0);
213 /* (8, 4) -> u=8 & ~8 = 0, v=4 & ~8 = 4. CLUT8[0] (V not used). */
214 ASSERT_PIXEL_EQ(expectedClut8Color(0), 8, 4);
215)
216CESTER_TEST(wt_maskU01_maskV01_off11_x8y4, gpu_raster_phase15,
217 drawWindowTri(0x01, 0x01, 0x01, 0x01);
218 /* offset (1, 1) sets bit 3 of u and v.
219 u=8 & ~8 | 8 = 8; v=4 & ~8 | 8 = 12. CLUT8[8] = expectedClut8Color(8). */
220 ASSERT_PIXEL_EQ(expectedClut8Color(8), 8, 4);
221)
222
223// ============================================================================
224// Offset > mask. When offset bits are NOT covered by mask, only the
225// mask-covered bits of offset apply. psx-spx: filtered = (u & ~M) | (O & M).
226// ============================================================================
227
228CESTER_TEST(wt_mask01_off07_x0, gpu_raster_phase15,
229 drawWindowTri(0x01, 0, 0x07, 0);
230 /* mask=0x01 only covers bit 3. offset=0x07 has bits 3-5 set.
231 Only bit 3 of offset applies (the rest masked out).
232 filtered_u = 0 & ~8 | (0x07 << 3) & 8 = 0 | 8 = 8 -> CLUT8[8]. */
233 ASSERT_PIXEL_EQ(expectedClut8Color(8), 0, 0);
234)
235CESTER_TEST(wt_mask03_off1f_x0, gpu_raster_phase15,
236 drawWindowTri(0x03, 0, 0x1f, 0);
237 /* mask=0x03 covers bits 3-4. offset=0x1F has bits 3-7.
238 filtered_u = 0 | (0xf8 & 0x18) = 0x18 -> CLUT8[24]. */
239 ASSERT_PIXEL_EQ(expectedClut8Color(0x18), 0, 0);
240)
241
242// ============================================================================
243// Prim-type sweep: window applied to rect and quad. Same mask/offset
244// as the triangle reference probe; output should match.
245// ============================================================================
246
247CESTER_TEST(wr_mask01_off00_x8, gpu_raster_phase15,
248 drawWindowRect(0x01, 0, 0, 0);
249 /* Rect at (0,0) 32x8 with mask_u=0x01, x=8 -> u=8 -> wraps to 0. */
250 ASSERT_PIXEL_EQ(expectedClut8Color(0), 8, 0);
251)
252CESTER_TEST(wq_mask01_off00_x8, gpu_raster_phase15,
253 drawWindowQuad(0x01, 0, 0, 0);
254 /* Quad at (0,0)-(31,0)-(0,7)-(31,7) with mask_u=0x01, x=8 same as
255 triangle - wraps to u=0. */
256 ASSERT_PIXEL_EQ(expectedClut8Color(0), 8, 0);
257)
258CESTER_TEST(wr_mask03_off01_x12, gpu_raster_phase15,
259 drawWindowRect(0x03, 0, 0x01, 0);
260 /* x=12 -> u=12=0x0C, &~0x18 = 0x04, | 0x08 = 0x0C -> CLUT8[12]. */
261 ASSERT_PIXEL_EQ(expectedClut8Color(12), 12, 0);
262)
263CESTER_TEST(wq_mask03_off01_x12, gpu_raster_phase15,
264 drawWindowQuad(0x03, 0, 0x01, 0);
265 ASSERT_PIXEL_EQ(expectedClut8Color(12), 12, 0);
266)
267
268// ============================================================================
269// Window × semi-trans (window-filtered texel through ABR blend).
270// At x=8 with mask_u=0x01: windowed u=0, CLUT8[0] = vram555(0, 31, 0)
271// = 0x03e0 (bit-15 = 0 in standard fixture). With bit-15=0, semi-trans
272// gate does NOT fire - texel writes through opaquely. So all four
273// ABR modes should produce the same un-blended texel = 0x03e0.
274// ============================================================================
275
276CESTER_TEST(ws_window_semi_abr0, gpu_raster_phase15,
277 drawWindowSemi(0);
279)
280CESTER_TEST(ws_window_semi_abr1, gpu_raster_phase15,
281 drawWindowSemi(1);
283)
284CESTER_TEST(ws_window_semi_abr2, gpu_raster_phase15,
285 drawWindowSemi(2);
287)
288CESTER_TEST(ws_window_semi_abr3, gpu_raster_phase15,
289 drawWindowSemi(3);
291)
292
293// ============================================================================
294// Window × bit-15 transparency. With mask_u=0x01, x=8 collapses to
295// u=0, sampling CLUT8[0] which now has bit-15 SET. Question: does
296// the gate fire on the WINDOWED sample (yes -> blend with bg) or on
297// the unfiltered u=8 sample (no - CLUT8[8] has bit-15=0)?
298// ============================================================================
299
300CESTER_TEST(wx_window_transparency_abr0_x8, gpu_raster_phase15,
301 drawWindowTransparency(0);
302 /* If gate fires on windowed sample (bit-15=1 at CLUT8[0]): ABR=0
303 blend of red bg and CLUT8[0]. If gate fires on raw u=8 sample
304 (bit-15=0 at CLUT8[8] before window): no blend, output = ???
305 Hardware truth captured. */
307)
308CESTER_TEST(wx_window_transparency_abr0_x0, gpu_raster_phase15,
309 drawWindowTransparency(0);
310 /* Control: x=0 doesn't go through window (u=0 mapped to u=0).
311 Same as no-window case at x=0. */
313)
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 GPU_DATA
Definition hwregs.h:52
#define WX_WINDOW_TRANS_ABR0
Definition raster-expected-phase15.h:60
#define WT_MASK1F_OFF1F_X0_HW
Definition raster-expected-phase15.h:70
#define WT_MASK0F_OFF0F_X0_HW
Definition raster-expected-phase15.h:69
#define WT_U248_PROBE_INDEX
Definition raster-expected-phase15.h:68
#define WT_U120_PROBE_INDEX
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:125
#define ASSERT_PIXEL_EQ(expected, x_, y_)
Definition raster-helpers.h:473
static int value
Definition syscalls.h:553
#define CLUT8_FIELD
Definition texture-fixtures.h:84
#define TEX8_TY
Definition texture-fixtures.h:61
#define TEX8_VRAM_BASE_X
Definition texture-fixtures.h:62
#define TEX8_TPAGE
Definition texture-fixtures.h:101
#define TEX8_TX
Definition texture-fixtures.h:60
#define TEX_MOD_NEUTRAL
Definition texture-fixtures.h:308