Nugget
Bare-metal libraries and examples for the original PlayStation
Loading...
Searching...
No Matches
fill-vram.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// GP0(0x02) FillVram quirks. See gpu-fbrect.c for the behavior list. Each
28// test seeds the working region with the sentinel, issues one fast-fill,
29// and asserts the resulting VRAM, so the failing quirk is named at the
30// call site rather than inferred from a raw pixel diff.
31
32// clang-format off
33
35
36// Sentinel-seeded background region covering every fill target below.
37#define FB_BG_X 0
38#define FB_BG_Y 0
39#define FB_BG_W 128
40#define FB_BG_H 64
41
42// Issue a GP0(0x02) fast-fill: solid |cmdColor| rectangle at (x, y) of size
43// w*h, written directly to VRAM. Deliberately hand-rolled (rather than the
44// gpu.h fastFill() struct helper) so the exact three command words are
45// visible next to the behavior under test.
46static void fbFill(int16_t x, int16_t y, int16_t w, int16_t h,
47 uint32_t cmdColor) {
48 waitGPU();
49 GPU_DATA = 0x02000000u | (cmdColor & 0x00ffffffu);
50 GPU_DATA = ((uint32_t)(uint16_t)y << 16) | (uint32_t)(uint16_t)x;
51 GPU_DATA = ((uint32_t)(uint16_t)h << 16) | (uint32_t)(uint16_t)w;
52 waitGPU();
53}
54
55// Reset to a known GPU state and seed the working region with the sentinel
56// so post-fill readback tells filled (color) from untouched (sentinel).
57static void fbSetup(void) {
58 rasterReset();
59 rasterClearTestRegion(FB_BG_X, FB_BG_Y, FB_BG_W, FB_BG_H);
60}
61
62) // CESTER_BODY
63
64// --------------------------------------------------------------------------
65// 1. Basic solid fill covers exactly [X, X+W) x [Y, Y+H).
66// --------------------------------------------------------------------------
67CESTER_TEST(fbrect_fills_solid_rect, gpu_fbrect,
68 fbSetup();
69 fbFill(16, 8, 32, 8, RASTER_CMD_RED); // 16-aligned X, even W
70 rasterFlushPrimitive();
71 ASSERT_PIXEL_EQ(RASTER_VRAM_RED, 16, 8); // top-left inclusive
72 ASSERT_PIXEL_EQ(RASTER_VRAM_RED, 47, 15); // bottom-right inclusive (16+32-1, 8+8-1)
73 ASSERT_PIXEL_UNTOUCHED(48, 8); // one column past the right edge
74 ASSERT_PIXEL_UNTOUCHED(16, 16); // one row past the bottom edge
75)
76
77// --------------------------------------------------------------------------
78// 2. The low 4 bits of X are ignored (16-pixel X granularity). A fill
79// requested at X=20 (0x14) actually starts at X=16, so columns 16..19 -
80// which the caller did not ask to fill - come out set.
81// --------------------------------------------------------------------------
82CESTER_TEST(fbrect_ignores_low_4_bits_of_x, gpu_fbrect,
83 fbSetup();
84 fbFill(20, 8, 32, 8, RASTER_CMD_RED);
85 rasterFlushPrimitive();
86 ASSERT_PIXEL_EQ(RASTER_VRAM_RED, 16, 10); // snapped-down start
87 ASSERT_PIXEL_EQ(RASTER_VRAM_RED, 19, 10); // inside the ignored nibble
88 ASSERT_PIXEL_EQ(RASTER_VRAM_RED, 20, 10); // the requested start, also filled
89)
90
91// --------------------------------------------------------------------------
92// 3. Fast-fill bypasses the drawing-area clip. A rasterized primitive would
93// be cut at the area edge; GP0(0x02) writes past it.
94// --------------------------------------------------------------------------
95CESTER_TEST(fbrect_ignores_draw_area_clip, gpu_fbrect,
96 fbSetup();
97 setDrawingArea(0, 0, 32, 32); // active area x<32, y<32
98 fbFill(16, 8, 32, 16, RASTER_CMD_RED); // spans x 16..47, y 8..23
99 rasterFlushPrimitive();
100 ASSERT_PIXEL_EQ(RASTER_VRAM_RED, 20, 12); // inside the area, filled
101 ASSERT_PIXEL_EQ(RASTER_VRAM_RED, 40, 12); // OUTSIDE the area, still filled
102)
103
104// --------------------------------------------------------------------------
105// 4a. Fast-fill ignores set-mask: it does not force bit 15 on the pixels it
106// writes (a rasterized primitive under set-mask would).
107// --------------------------------------------------------------------------
108CESTER_TEST(fbrect_ignores_set_mask, gpu_fbrect,
109 fbSetup();
110 rasterSetMaskCtrl(1, 0); // set-mask on
111 fbFill(16, 8, 32, 8, RASTER_CMD_RED);
112 rasterFlushPrimitive();
113 // With set-mask honored the value would be 0x801F; fast-fill writes 0x001F.
115)
116
117// --------------------------------------------------------------------------
118// 4b. Fast-fill ignores check-mask: it overwrites a mask-set (bit 15 = 1)
119// destination pixel that a rasterized primitive would have skipped.
120// --------------------------------------------------------------------------
121CESTER_TEST(fbrect_ignores_check_mask, gpu_fbrect,
122 fbSetup();
123 // Plant a mask-set green pixel with a rasterized rect under set-mask.
124 rasterSetMaskCtrl(1, 0);
125 rasterFlatRect(RASTER_CMD_GREEN, 16, 8, 32, 8);
126 rasterFlushPrimitive();
127 // Enable check-mask (rasterized prims would now skip that pixel) and
128 // fast-fill red over it.
129 rasterSetMaskCtrl(0, 1);
130 fbFill(16, 8, 32, 8, RASTER_CMD_RED);
131 rasterFlushPrimitive();
132 ASSERT_PIXEL_EQ(RASTER_VRAM_RED, 20, 10); // overwritten -> fill ignored check-mask
133)
134
135// --------------------------------------------------------------------------
136// 5. X = 0x0F (all low 4 bits set). psx-spx documents that old v0 GPUs write
137// every 2nd pixel to the wrong address here, while v2 GPUs ignore the bits
138// and fill contiguously from X=0. This asserts the clean v2 behavior; a
139// v0-hardware divergence is a separate hardware-capture item.
140// --------------------------------------------------------------------------
141CESTER_TEST(fbrect_x_0x0f_snaps_v2_contiguous, gpu_fbrect,
142 fbSetup();
143 fbFill(0x0F, 8, 32, 8, RASTER_CMD_RED);
144 rasterFlushPrimitive();
145 ASSERT_PIXEL_EQ(RASTER_VRAM_RED, 0, 10); // contiguous from x=0
146 ASSERT_PIXEL_EQ(RASTER_VRAM_RED, 1, 10); // a v0 GPU would skip this column
147 ASSERT_PIXEL_EQ(RASTER_VRAM_RED, 31, 10); // last filled column
149)
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 FB_BG_H
#define FB_BG_X
#define FB_BG_W
#define FB_BG_Y
#define GPU_DATA
Definition hwregs.h:52
#define ASSERT_PIXEL_UNTOUCHED(x_, y_)
Definition raster-helpers.h:486
#define RASTER_VRAM_RED
Definition raster-helpers.h:125
#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