Nugget
Bare-metal libraries and examples for the original PlayStation
Loading...
Searching...
No Matches
draw-area.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// Drawing-area + drawing-offset interactions. GP0(E3) sets the area
28// upper-left, GP0(E4) the area lower-right (inclusive). GP0(E5) sets
29// the offset added to every primitive vertex.
30//
31// Documents:
32// - Offset (50, 50): primitive at logical (0,0) draws at VRAM (50,50).
33// - Offset combined with draw-area clip.
34// - Primitive at negative logical coords after offset.
35
36// clang-format off
37
39
40// Offset-A: Triangle A geometry (0,0),(4,0),(0,4) with drawing offset
41// (50, 50). Expected to fill exactly the same pixel set as Triangle A
42// from triangle-edges.c, shifted by (50, 50).
43static void rasterDrawOffsetA(void) {
44 rasterReset();
45 rasterClearTestRegion(48, 48, 16, 16);
46 // Also clear the original origin so we can assert it stays sentinel.
47 rasterClearTestRegion(0, 0, 8, 8);
48 setDrawingOffset(50, 50);
49 rasterFlatTri(RASTER_CMD_RED, 0, 0, 4, 0, 0, 4);
50 rasterFlushPrimitive();
51}
52
53// Offset-B: Drawing offset that pushes geometry partially OFF the draw
54// area. Logical (-3, -3), (5, -3), (-3, 5) with offset (3, 3) gives
55// destination triangle (0,0), (8,0), (0,8). The drawing-area clip is
56// (0, 0)..(1024, 512) so nothing should be cut. This documents that
57// offset itself does not introduce clipping when destinations are
58// non-negative.
59static void rasterDrawOffsetB(void) {
60 rasterReset();
61 rasterClearTestRegion(0, 0, 16, 16);
62 setDrawingOffset(3, 3);
63 rasterFlatTri(RASTER_CMD_GREEN, -3, -3, 5, -3, -3, 5);
64 rasterFlushPrimitive();
65}
66
67// Offset-C: Drawing offset PLUS shrunk draw area. The primitive lands
68// inside VRAM but partially outside the active draw area. Should clip
69// to the area, not silently extend past it.
70// Draw area: (10, 10)..(20, 20)
71// Offset: (10, 10)
72// Primitive: triangle (0,0),(8,0),(0,8) -> VRAM (10,10),(18,10),(10,18)
73// All of the destination is inside the draw area, so the triangle
74// should render fully. This is a sanity check that offset + draw area
75// compose additively.
76static void rasterDrawOffsetC(void) {
77 rasterReset();
78 rasterClearTestRegion(0, 0, 32, 32);
79 setDrawingArea(10, 10, 20, 20);
80 setDrawingOffset(10, 10);
81 rasterFlatTri(RASTER_CMD_BLUE, 0, 0, 8, 0, 0, 8);
82 rasterFlushPrimitive();
83}
84
85) // CESTER_BODY
86
87// --------------------------------------------------------------------------
88// Offset A: triangle shifted by (50, 50)
89// --------------------------------------------------------------------------
90
91CESTER_TEST(offsetA_origin_top_left_at_50_50, gpu_raster_phase1,
92 rasterDrawOffsetA();
94)
95
96CESTER_TEST(offsetA_origin_top_inner_53_50, gpu_raster_phase1,
97 rasterDrawOffsetA();
99)
100
101CESTER_TEST(offsetA_right_edge_54_50_excluded, gpu_raster_phase1,
102 rasterDrawOffsetA();
104)
105
106CESTER_TEST(offsetA_bottom_edge_50_54_excluded, gpu_raster_phase1,
107 rasterDrawOffsetA();
109)
110
111CESTER_TEST(offsetA_origin_0_0_untouched, gpu_raster_phase1,
112 rasterDrawOffsetA();
113 // With offset 50,50, the logical (0,0) corner of the triangle should
114 // NOT write VRAM (0,0). Sentinel confirms the offset took effect.
116)
117
118// --------------------------------------------------------------------------
119// Offset B: negative logical coordinates compensated by positive offset
120// --------------------------------------------------------------------------
121
122CESTER_TEST(offsetB_negative_compensated_pixel_0_0, gpu_raster_phase1,
123 rasterDrawOffsetB();
124 // Logical (-3, -3) + offset (3, 3) = VRAM (0, 0). The triangle
125 // (-3,-3)(5,-3)(-3,5) -> (0,0)(8,0)(0,8). Top-left rule says (0,0)
126 // is drawn (inclusive corner).
128)
129
130CESTER_TEST(offsetB_negative_compensated_pixel_7_0, gpu_raster_phase1,
131 rasterDrawOffsetB();
132 // Last drawn pixel of the top edge under top-left rule.
134)
135
136CESTER_TEST(offsetB_negative_compensated_pixel_8_0, gpu_raster_phase1,
137 rasterDrawOffsetB();
138 // Right-vertex corner; top-left rule excludes.
140)
141
142CESTER_TEST(offsetB_negative_compensated_pixel_0_8, gpu_raster_phase1,
143 rasterDrawOffsetB();
144 // Bottom-vertex; bottom edge excluded.
146)
147
148// --------------------------------------------------------------------------
149// Offset C: offset + shifted draw area compose additively
150// --------------------------------------------------------------------------
151
152CESTER_TEST(offsetC_compose_pixel_10_10_inside_area, gpu_raster_phase1,
153 rasterDrawOffsetC();
155)
156
157CESTER_TEST(offsetC_compose_pixel_17_10_top_inner, gpu_raster_phase1,
158 rasterDrawOffsetC();
159 // Logical (7,0) + offset 10 = VRAM (17,10). Inside draw area (ends
160 // at 20). Under top-left rule for triangle (0,0)(8,0)(0,8), pixel
161 // x=7 y=0 is the last included on the top edge.
163)
164
165CESTER_TEST(offsetC_compose_pixel_18_10_right_vertex, gpu_raster_phase1,
166 rasterDrawOffsetC();
167 // Right-vertex corner excluded under top-left rule.
169)
170
171CESTER_TEST(offsetC_compose_pixel_9_9_before_draw_area, gpu_raster_phase1,
172 rasterDrawOffsetC();
173 // Just outside the shifted draw area (which starts at 10).
175)
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 EXPECT_OFFSET_PIXEL_50_50
Definition raster-expected.h:236
#define EXPECT_OFFSET_PIXEL_54_50
Definition raster-expected.h:238
#define EXPECT_OFFSET_PIXEL_53_50
Definition raster-expected.h:237
#define EXPECT_OFFSET_PIXEL_0_0
Definition raster-expected.h:240
#define EXPECT_OFFSET_PIXEL_50_54
Definition raster-expected.h:239
#define RASTER_VRAM_GREEN
Definition raster-helpers.h:127
#define RASTER_VRAM_BLUE
Definition raster-helpers.h:129
#define ASSERT_PIXEL_UNTOUCHED(x_, y_)
Definition raster-helpers.h:486
#define RASTER_CMD_BLUE
Definition raster-helpers.h:128
#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