Nugget
Bare-metal libraries and examples for the original PlayStation
Loading...
Searching...
No Matches
polyline.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(0x48) flat poly-line terminator detection. See gpu-polyline.c for the
28// rule and the probe method. Each test issues one poly-line whose fourth
29// word is the candidate under test and asserts whether an extra segment drew,
30// so the failing case names the terminator rule that diverged.
31
32// clang-format off
33
35
36// Background region seeded with the sentinel. Covers every vertex below.
37#define PL_BG_X 0
38#define PL_BG_Y 0
39#define PL_BG_W 96
40#define PL_BG_H 96
41
42// The always-drawn spine v0->v1 (horizontal, so the anchor pixel is exact).
43#define PL_V0X 10
44#define PL_V0Y 10
45#define PL_V1X 40
46#define PL_V1Y 10
47#define PL_SPINE_X 25 // a pixel on the spine; lit in every attempt
48#define PL_SPINE_Y 10
49
50// The probe vertex. Reached (and drawn) only if the candidate did NOT
51// terminate. Its position word ((y<<16)|x) must have a 0x00 high byte so
52// that, when the candidate DID terminate and this word is read as a fresh
53// GP0 command, it decodes as a NOP. y=40 -> (0x0028<<16) keeps byte 3 zero.
54#define PL_PROBE_X 70
55#define PL_PROBE_Y 40
56
57// Issue one flat poly-line: white spine v0->v1, the |candidate| word, the
58// on-screen probe vertex, then a guaranteed real terminator. Hang-safe in
59// both outcomes (see gpu-polyline.c). Deliberately hand-rolled so the exact
60// word sequence sits next to the behavior under test.
61static void plAttempt(uint32_t candidate) {
62 waitGPU();
63 GPU_DATA = 0x48000000u | (RASTER_CMD_WHITE);
64 GPU_DATA = ((uint32_t)(uint16_t)PL_V0Y << 16) | (uint32_t)(uint16_t)PL_V0X;
65 GPU_DATA = ((uint32_t)(uint16_t)PL_V1Y << 16) | (uint32_t)(uint16_t)PL_V1X;
66 GPU_DATA = candidate;
67 GPU_DATA = ((uint32_t)(uint16_t)PL_PROBE_Y << 16) |
68 (uint32_t)(uint16_t)PL_PROBE_X;
69 GPU_DATA = 0x50005000u; // guaranteed terminator for the non-terminating case
70}
71
72// Let the drawn segments raster to VRAM. A short horizontal line plus one
73// diagonal completes in microseconds; this loop is a wide safety margin.
74// Not a GPU-status poll: in the terminating case a dangling read-as-command
75// tail can hold the "ready for command" bit low, so polling could wedge -
76// GP1(0x00) below flushes that tail instead.
77static void plSettle(void) {
78 for (volatile int i = 0; i < 30000; i++) {
79 }
80}
81
82// Reset to a known GPU state and seed the working region with the sentinel
83// so post-draw readback tells drawn (white) from untouched (sentinel).
84static void plSetup(void) {
85 rasterReset();
86 rasterClearTestRegion(PL_BG_X, PL_BG_Y, PL_BG_W, PL_BG_H);
87}
88
89// Run one candidate: setup, issue, settle, then GP1(0x00) reset to flush any
90// dangling read-as-command tail before VRAM readback.
91static void plRun(uint32_t candidate) {
92 plSetup();
93 plAttempt(candidate);
94 plSettle();
95 rasterReset();
96}
97
98) // CESTER_BODY
99
100// --------------------------------------------------------------------------
101// 1. 0x50005000 terminates a two-vertex poly-line. Both nibbles equal 0x5, so
102// the list ends after v0->v1: the probe vertex is never drawn.
103// --------------------------------------------------------------------------
104CESTER_TEST(polyline_50005000_terminates, gpu_polyline,
105 plRun(0x50005000u);
107 ASSERT_PIXEL_UNTOUCHED(PL_PROBE_X, PL_PROBE_Y); // no extra segment
108)
109
110// --------------------------------------------------------------------------
111// 2. 0x55555555 also terminates. It is the terminator most code emits, and it
112// works precisely because 0x55555555 & 0xF000F000 == 0x50005000 - both the
113// bits-12-15 and bits-28-31 nibbles land on 0x5.
114// --------------------------------------------------------------------------
115CESTER_TEST(polyline_55555555_terminates, gpu_polyline,
116 plRun(0x55555555u);
119)
120
121// --------------------------------------------------------------------------
122// 3. 0x00500050 does NOT terminate. Neither nibble-3 nor nibble-7 is 0x5
123// (the 0x5 nibbles sit at bits 4-7 and 20-23), so the word is consumed as
124// vertex v2 = (80, 80): the extra v1->v2 and v2->probe segments draw, and
125// both the candidate's own vertex and the probe pixel come out lit.
126// --------------------------------------------------------------------------
127CESTER_TEST(polyline_00500050_is_a_vertex, gpu_polyline,
128 plRun(0x00500050u);
130 ASSERT_PIXEL_EQ(RASTER_VRAM_WHITE, 80, 80); // candidate used as v2
131 ASSERT_PIXEL_EQ(RASTER_VRAM_WHITE, PL_PROBE_X, PL_PROBE_Y); // probe drew -> not terminated
132)
133
134// --------------------------------------------------------------------------
135// 4. 0x50000000 does NOT terminate. Only nibble-7 (bits 28-31) is 0x5; nibble-3
136// is 0x0, so 0x50000000 & 0xF000F000 == 0x50000000 != 0x50005000. A single
137// matching nibble is insufficient, so the word is consumed as a vertex and
138// the probe pixel lights.
139// --------------------------------------------------------------------------
140CESTER_TEST(polyline_50000000_single_nibble_no_terminate, gpu_polyline,
141 plRun(0x50000000u);
143 ASSERT_PIXEL_EQ(RASTER_VRAM_WHITE, PL_PROBE_X, PL_PROBE_Y); // probe drew -> not terminated
144)
145
146// --------------------------------------------------------------------------
147// 5. The terminator is not checked on the command word. A gouraud poly-line
148// whose first color has a green byte of 0x5X makes the command word itself
149// match the pattern (0x58F858F8 & 0xF000F000 == 0x50005000); it still
150// starts a list, and the v0->v1 spine draws in that color.
151// --------------------------------------------------------------------------
152CESTER_TEST(polyline_command_word_is_not_a_terminator, gpu_polyline,
153 uint32_t color = rasterCmdColor(0x1f, 0x0b, 0x1f);
154 plSetup();
155 waitGPU();
156 GPU_DATA = 0x58000000u | color;
157 GPU_DATA = ((uint32_t)(uint16_t)PL_V0Y << 16) | (uint32_t)(uint16_t)PL_V0X;
158 GPU_DATA = color;
159 GPU_DATA = ((uint32_t)(uint16_t)PL_V1Y << 16) | (uint32_t)(uint16_t)PL_V1X;
160 GPU_DATA = 0x55555555u;
161 plSettle();
162 rasterReset();
163 ASSERT_PIXEL_EQ(rasterVram555(0x1f, 0x0b, 0x1f), PL_SPINE_X, PL_SPINE_Y);
164)
165
166// --------------------------------------------------------------------------
167// 6. Nor on the second vertex. A flat poly-line needs two vertices before it
168// can end, so a v1 word matching the pattern is still a vertex: its x and y
169// keep only their low 11 bits, (0x5000 | 40, 0x5000 | 10) lands on (40, 10).
170// --------------------------------------------------------------------------
171CESTER_TEST(polyline_second_vertex_is_not_a_terminator, gpu_polyline,
172 plSetup();
173 waitGPU();
174 GPU_DATA = 0x48000000u | (RASTER_CMD_WHITE);
175 GPU_DATA = ((uint32_t)(uint16_t)PL_V0Y << 16) | (uint32_t)(uint16_t)PL_V0X;
176 GPU_DATA = ((uint32_t)(0x5000u | PL_V1Y) << 16) | (uint32_t)(0x5000u | PL_V1X);
177 GPU_DATA = 0x55555555u;
178 plSettle();
179 rasterReset();
181)
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
int i
Definition gte-regio.c:299
#define GPU_DATA
Definition hwregs.h:52
#define PL_PROBE_X
#define PL_BG_Y
#define PL_PROBE_Y
#define PL_SPINE_Y
#define PL_BG_X
#define PL_V1X
#define PL_SPINE_X
#define PL_V0Y
#define PL_V1Y
#define PL_V0X
#define PL_BG_H
#define PL_BG_W
#define RASTER_VRAM_WHITE
Definition raster-helpers.h:131
#define ASSERT_PIXEL_UNTOUCHED(x_, y_)
Definition raster-helpers.h:486
#define RASTER_CMD_WHITE
Definition raster-helpers.h:130
#define ASSERT_PIXEL_EQ(expected, x_, y_)
Definition raster-helpers.h:473