Nugget
Loading...
Searching...
No Matches
lines.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// Exhaustive line probes. Naming convention:
28// LS_<oct> flat shallow line in octant <oct> (1..8)
29// LT_<oct> flat steep line in octant <oct>
30// LR_<axis> reverse-direction (right-to-left or bottom-to-top)
31// LC_<dir> clipped at draw-area edge
32// LZ_<n> zero-length at position n
33// LG gouraud line
34// LP polyline 3-vertex
35// LST semi-trans line
36//
37// Octant numbering (standard):
38// 1: dx > 0, dy > 0, |dx| > |dy| (shallow down-right)
39// 2: dx > 0, dy > 0, |dy| > |dx| (steep down-right)
40// 3: dx < 0, dy > 0, |dy| > |dx| (steep down-left)
41// 4: dx < 0, dy > 0, |dx| > |dy| (shallow down-left)
42// 5: dx < 0, dy < 0, |dx| > |dy| (shallow up-left)
43// 6: dx < 0, dy < 0, |dy| > |dx| (steep up-left)
44// 7: dx > 0, dy < 0, |dy| > |dx| (steep up-right)
45// 8: dx > 0, dy < 0, |dx| > |dy| (shallow up-right)
46//
47// Phase-2 already covered octant 1 (lineShallow) and the D45/DN45 axis
48// boundaries. Phase-10 fills 2-8 plus polylines / gouraud / semi-trans.
49
51
52// Octant 2: steep down-right (dx>0, dy>0, dy > dx). (0,0) -> (3, 10).
53static void drawLS_oct2(void) {
54 rasterReset();
55 rasterClearTestRegion(0, 0, 16, 16);
56 rasterFlatLine(RASTER_CMD_RED, 0, 0, 3, 10);
57 rasterFlushPrimitive();
58}
59
60// Octant 3: steep down-left (dx<0, dy>0, dy > |dx|). (10,0) -> (7, 10).
61static void drawLS_oct3(void) {
62 rasterReset();
63 rasterClearTestRegion(0, 0, 16, 16);
64 rasterFlatLine(RASTER_CMD_GREEN, 10, 0, 7, 10);
65 rasterFlushPrimitive();
66}
67
68// Octant 4: shallow down-left. (10, 0) -> (0, 3).
69static void drawLS_oct4(void) {
70 rasterReset();
71 rasterClearTestRegion(0, 0, 16, 8);
72 rasterFlatLine(RASTER_CMD_BLUE, 10, 0, 0, 3);
73 rasterFlushPrimitive();
74}
75
76// Octant 5: shallow up-left. (10, 10) -> (0, 7).
77static void drawLS_oct5(void) {
78 rasterReset();
79 rasterClearTestRegion(0, 0, 16, 16);
80 rasterFlatLine(RASTER_CMD_WHITE, 10, 10, 0, 7);
81 rasterFlushPrimitive();
82}
83
84// Octant 6: steep up-left. (10, 10) -> (7, 0).
85static void drawLS_oct6(void) {
86 rasterReset();
87 rasterClearTestRegion(0, 0, 16, 16);
88 rasterFlatLine(RASTER_CMD_RED, 10, 10, 7, 0);
89 rasterFlushPrimitive();
90}
91
92// Octant 7: steep up-right. (0, 10) -> (3, 0).
93static void drawLS_oct7(void) {
94 rasterReset();
95 rasterClearTestRegion(0, 0, 16, 16);
96 rasterFlatLine(RASTER_CMD_GREEN, 0, 10, 3, 0);
97 rasterFlushPrimitive();
98}
99
100// Octant 8: shallow up-right. (0, 3) -> (10, 0).
101static void drawLS_oct8(void) {
102 rasterReset();
103 rasterClearTestRegion(0, 0, 16, 8);
104 rasterFlatLine(RASTER_CMD_BLUE, 0, 3, 10, 0);
105 rasterFlushPrimitive();
106}
107
108// Reverse-direction horizontal: (10, 5) -> (5, 5).
109static void drawLR_horiz(void) {
110 rasterReset();
111 rasterClearTestRegion(0, 0, 16, 8);
112 rasterFlatLine(RASTER_CMD_RED, 10, 5, 5, 5);
113 rasterFlushPrimitive();
114}
115
116// Reverse-direction vertical: (5, 10) -> (5, 5).
117static void drawLR_vert(void) {
118 rasterReset();
119 rasterClearTestRegion(0, 0, 8, 16);
120 rasterFlatLine(RASTER_CMD_GREEN, 5, 10, 5, 5);
121 rasterFlushPrimitive();
122}
123
124// Clipped right: line extends past draw-area X (1024). Draw area is
125// the default 1024x512 - so clip at X=1023 implicitly. We narrow the
126// draw area to test clipping at a closer boundary.
127static void drawLC_right(void) {
128 rasterReset();
129 rasterClearTestRegion(0, 0, 24, 8);
130 setDrawingArea(0, 0, 12, 8); /* draw-X clipped at 12 (exclusive) */
131 rasterFlatLine(RASTER_CMD_WHITE, 0, 4, 20, 4);
132 rasterFlushPrimitive();
133 /* Restore default draw area for subsequent tests. */
136}
137
138// Clipped bottom: line extends past draw-area Y.
139static void drawLC_bottom(void) {
140 rasterReset();
141 rasterClearTestRegion(0, 0, 8, 24);
142 setDrawingArea(0, 0, 8, 12);
143 rasterFlatLine(RASTER_CMD_BLUE, 4, 0, 4, 20);
144 rasterFlushPrimitive();
147}
148
149// Zero-length lines at different positions.
150static void drawLZ_at(int16_t x, int16_t y) {
151 rasterReset();
152 rasterClearTestRegion(0, 0, 32, 16);
153 rasterFlatLine(RASTER_CMD_RED, x, y, x, y);
154 rasterFlushPrimitive();
155}
156
157// Gouraud line: red -> blue over 10 pixels horizontally.
158static void drawLG(void) {
159 rasterReset();
160 rasterClearTestRegion(0, 0, 16, 8);
161 rasterGouraudLine(RASTER_CMD_RED, 0, 5,
162 RASTER_CMD_BLUE, 10, 5);
163 rasterFlushPrimitive();
164}
165
166// Polyline 3-vertex: (0, 0) -> (5, 5) -> (10, 0). Two diagonal
167// segments meeting at the apex.
168static void drawLP(void) {
169 rasterReset();
170 rasterClearTestRegion(0, 0, 16, 16);
171 rasterFlatPolyline3(RASTER_CMD_GREEN,
172 0, 0,
173 5, 5,
174 10, 0);
175 rasterFlushPrimitive();
176}
177
178// Semi-trans line: GP0(0x42) over a red-filled background.
179static void drawLST(void) {
180 rasterReset();
181 rasterFillRect(0, 0, 16, 8, RASTER_VRAM_RED);
182 rasterFlatLineSemi(RASTER_CMD_GREEN, 0, 4, 10, 4);
183 rasterFlushPrimitive();
184}
185
186) // CESTER_BODY
187
188// ==========================================================================
189// Octant 2: steep down-right. (0, 0) -> (3, 10).
190// Hardware steps Y the longer axis; X advances every ~3.33 rows.
191// ==========================================================================
192
193CESTER_TEST(ls_oct2_start, gpu_raster_phase10,
194 drawLS_oct2();
196)
197CESTER_TEST(ls_oct2_mid_y5, gpu_raster_phase10,
198 drawLS_oct2();
199 /* At y=5, x ~ 0 + 5*(3/10) = 1.5 -> Bresenham picks 1 or 2. */
201)
202CESTER_TEST(ls_oct2_mid_y5_x2, gpu_raster_phase10,
203 drawLS_oct2();
205)
206CESTER_TEST(ls_oct2_end, gpu_raster_phase10,
207 drawLS_oct2();
209)
210
211// ==========================================================================
212// Octant 3: steep down-left. (10, 0) -> (7, 10).
213// ==========================================================================
214
215CESTER_TEST(ls_oct3_start, gpu_raster_phase10,
216 drawLS_oct3();
218)
219CESTER_TEST(ls_oct3_mid, gpu_raster_phase10,
220 drawLS_oct3();
222)
223CESTER_TEST(ls_oct3_end, gpu_raster_phase10,
224 drawLS_oct3();
226)
227
228// ==========================================================================
229// Octant 4: shallow down-left. (10, 0) -> (0, 3).
230// ==========================================================================
231
232CESTER_TEST(ls_oct4_start, gpu_raster_phase10,
233 drawLS_oct4();
235)
236CESTER_TEST(ls_oct4_mid, gpu_raster_phase10,
237 drawLS_oct4();
239)
240CESTER_TEST(ls_oct4_end, gpu_raster_phase10,
241 drawLS_oct4();
243)
244
245// ==========================================================================
246// Octant 5: shallow up-left. (10, 10) -> (0, 7).
247// ==========================================================================
248
249CESTER_TEST(ls_oct5_start, gpu_raster_phase10,
250 drawLS_oct5();
252)
253CESTER_TEST(ls_oct5_mid, gpu_raster_phase10,
254 drawLS_oct5();
256)
257CESTER_TEST(ls_oct5_end, gpu_raster_phase10,
258 drawLS_oct5();
260)
261
262// ==========================================================================
263// Octant 6: steep up-left. (10, 10) -> (7, 0).
264// ==========================================================================
265
266CESTER_TEST(ls_oct6_start, gpu_raster_phase10,
267 drawLS_oct6();
269)
270CESTER_TEST(ls_oct6_mid, gpu_raster_phase10,
271 drawLS_oct6();
273)
274CESTER_TEST(ls_oct6_end, gpu_raster_phase10,
275 drawLS_oct6();
277)
278
279// ==========================================================================
280// Octant 7: steep up-right. (0, 10) -> (3, 0).
281// ==========================================================================
282
283CESTER_TEST(ls_oct7_start, gpu_raster_phase10,
284 drawLS_oct7();
286)
287CESTER_TEST(ls_oct7_mid, gpu_raster_phase10,
288 drawLS_oct7();
290)
291CESTER_TEST(ls_oct7_end, gpu_raster_phase10,
292 drawLS_oct7();
294)
295
296// ==========================================================================
297// Octant 8: shallow up-right. (0, 3) -> (10, 0).
298// ==========================================================================
299
300CESTER_TEST(ls_oct8_start, gpu_raster_phase10,
301 drawLS_oct8();
303)
304CESTER_TEST(ls_oct8_mid, gpu_raster_phase10,
305 drawLS_oct8();
307)
308CESTER_TEST(ls_oct8_end, gpu_raster_phase10,
309 drawLS_oct8();
311)
312
313// ==========================================================================
314// Reverse-direction lines. Hardware should draw the same pixel set as
315// the forward-direction equivalent.
316// ==========================================================================
317
318CESTER_TEST(lr_horiz_start_10_5, gpu_raster_phase10,
319 drawLR_horiz();
321)
322CESTER_TEST(lr_horiz_end_5_5, gpu_raster_phase10,
323 drawLR_horiz();
324 /* End vertex of a reverse-direction line - drawn or excluded? */
326)
327CESTER_TEST(lr_horiz_mid_7_5, gpu_raster_phase10,
328 drawLR_horiz();
330)
331
332CESTER_TEST(lr_vert_start_5_10, gpu_raster_phase10,
333 drawLR_vert();
335)
336CESTER_TEST(lr_vert_end_5_5, gpu_raster_phase10,
337 drawLR_vert();
339)
340CESTER_TEST(lr_vert_mid_5_7, gpu_raster_phase10,
341 drawLR_vert();
343)
344
345// ==========================================================================
346// Clipping at draw-area edges.
347// ==========================================================================
348
349CESTER_TEST(lc_right_inside_8, gpu_raster_phase10,
350 drawLC_right();
351 /* Pixel inside the clipped draw area. */
353)
354CESTER_TEST(lc_right_just_inside_edge_11, gpu_raster_phase10,
355 drawLC_right();
356 /* x=11 - last column inside draw-area (X1=12 exclusive)? */
358)
359CESTER_TEST(lc_right_clipped_15, gpu_raster_phase10,
360 drawLC_right();
361 /* x=15 - past draw-area, must not be drawn. */
363)
364
365CESTER_TEST(lc_bottom_inside_8, gpu_raster_phase10,
366 drawLC_bottom();
368)
369CESTER_TEST(lc_bottom_just_inside_edge_11, gpu_raster_phase10,
370 drawLC_bottom();
372)
373CESTER_TEST(lc_bottom_clipped_15, gpu_raster_phase10,
374 drawLC_bottom();
376)
377
378// ==========================================================================
379// Zero-length at different positions. Should be exactly one pixel.
380// ==========================================================================
381
382CESTER_TEST(lz_at_origin, gpu_raster_phase10,
383 drawLZ_at(0, 0);
385)
386CESTER_TEST(lz_at_origin_neighbor, gpu_raster_phase10,
387 drawLZ_at(0, 0);
389)
390CESTER_TEST(lz_at_5_5, gpu_raster_phase10,
391 drawLZ_at(5, 5);
393)
394CESTER_TEST(lz_at_5_5_neighbor, gpu_raster_phase10,
395 drawLZ_at(5, 5);
397)
398
399// ==========================================================================
400// Gouraud line: red -> blue across 10 pixels at y=5.
401// Per-pixel color interpolation along the Bresenham step.
402// ==========================================================================
403
404CESTER_TEST(lg_start_pure_red, gpu_raster_phase10,
405 drawLG();
407)
408CESTER_TEST(lg_mid_5, gpu_raster_phase10,
409 drawLG();
410 ASSERT_PIXEL_EQ(LG_MID, 5, 5);
411)
412CESTER_TEST(lg_end_10, gpu_raster_phase10,
413 drawLG();
414 ASSERT_PIXEL_EQ(LG_END, 10, 5);
415)
416
417// ==========================================================================
418// Polyline: 3 vertices, 2 segments. Apex (5, 5) is shared between
419// segments - must be drawn exactly once, not double-written or skipped.
420// ==========================================================================
421
422CESTER_TEST(lp_first_segment_start, gpu_raster_phase10,
423 drawLP();
425)
426CESTER_TEST(lp_first_segment_mid, gpu_raster_phase10,
427 drawLP();
429)
430CESTER_TEST(lp_apex, gpu_raster_phase10,
431 drawLP();
432 /* Shared vertex between segment 1 and segment 2. */
434)
435CESTER_TEST(lp_second_segment_mid, gpu_raster_phase10,
436 drawLP();
438)
439CESTER_TEST(lp_second_segment_end, gpu_raster_phase10,
440 drawLP();
441 ASSERT_PIXEL_EQ(LP_END, 10, 0);
442)
443
444// ==========================================================================
445// Semi-trans line: GP0(0x42). Per psx-spx the line mask gating is the
446// drawing-area mask bit (E6), not the per-pixel texel mask. Lines have
447// no texture so the semi-trans always applies the blend equation.
448// ==========================================================================
449
450CESTER_TEST(lst_mid_blended, gpu_raster_phase10,
451 drawLST();
452 /* Semi-trans green over red background. Hardware truth captured;
453 likely a 0.5*red + 0.5*green blend = (R=15, G=15, B=0)
454 approximately = vram555(15, 15, 0) = 0x01ef. */
456)
457CESTER_TEST(lst_neighbor_unblended, gpu_raster_phase10,
458 drawLST();
459 /* Outside the line - background red preserved. */
461)
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 LS_OCT2_END
Definition raster-expected-phase10.h:47
#define LS_OCT7_MID
Definition raster-expected-phase10.h:68
#define LC_RIGHT_JUST_INSIDE
Definition raster-expected-phase10.h:85
#define LS_OCT3_END
Definition raster-expected-phase10.h:52
#define LS_OCT6_MID
Definition raster-expected-phase10.h:64
#define LS_OCT5_END
Definition raster-expected-phase10.h:60
#define LS_OCT3_MID
Definition raster-expected-phase10.h:51
#define LG_START
Definition raster-expected-phase10.h:95
#define LS_OCT6_END
Definition raster-expected-phase10.h:65
#define LR_VERT_END
Definition raster-expected-phase10.h:81
#define LST_MID
Definition raster-expected-phase10.h:110
#define LS_OCT5_MID
Definition raster-expected-phase10.h:59
#define LP_END
Definition raster-expected-phase10.h:100
#define LS_OCT4_MID
Definition raster-expected-phase10.h:55
#define LS_OCT2_Y5_X1
Definition raster-expected-phase10.h:45
#define LS_OCT2_Y5_X2
Definition raster-expected-phase10.h:46
#define LS_OCT4_END
Definition raster-expected-phase10.h:56
#define LG_MID
Definition raster-expected-phase10.h:96
#define LC_BOTTOM_JUST_INSIDE
Definition raster-expected-phase10.h:86
#define LS_OCT8_END
Definition raster-expected-phase10.h:74
#define LR_HORIZ_END
Definition raster-expected-phase10.h:80
#define LS_OCT8_MID
Definition raster-expected-phase10.h:73
#define LG_END
Definition raster-expected-phase10.h:97
#define LS_OCT7_END
Definition raster-expected-phase10.h:69
#define RASTER_VRAM_GREEN
Definition raster-helpers.h:126
#define RASTER_DRAW_AREA_Y2
Definition raster-helpers.h:69
#define RASTER_VRAM_BLUE
Definition raster-helpers.h:128
#define RASTER_VRAM_WHITE
Definition raster-helpers.h:130
#define RASTER_DRAW_AREA_X1
Definition raster-helpers.h:66
#define ASSERT_PIXEL_UNTOUCHED(x_, y_)
Definition raster-helpers.h:485
#define RASTER_DRAW_AREA_Y1
Definition raster-helpers.h:67
#define RASTER_VRAM_RED
Definition raster-helpers.h:124
#define RASTER_CMD_BLUE
Definition raster-helpers.h:127
#define RASTER_CMD_RED
Definition raster-helpers.h:123
#define RASTER_CMD_WHITE
Definition raster-helpers.h:129
#define RASTER_CMD_GREEN
Definition raster-helpers.h:125
#define ASSERT_PIXEL_EQ(expected, x_, y_)
Definition raster-helpers.h:472
#define RASTER_DRAW_AREA_X2
Definition raster-helpers.h:68