Nugget
Bare-metal libraries and examples for the original PlayStation
Loading...
Searching...
No Matches
copy.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(0x80) VRAM-to-VRAM copy behavior. See gpu-vram-move.c for the list.
28
29// clang-format off
30
32
33// VRAM pixel value with the mask bit (bit 15) set, for the mask tests.
34#define VM_RED_MASKED (RASTER_VRAM_RED | 0x8000u) // 0x801F
35#define VM_GREEN_MASKED (RASTER_VRAM_GREEN | 0x8000u) // 0x83E0
36
37// Issue a GP0(0x80) VRAM-to-VRAM copy: (w x h) region from (sx, sy) to
38// (dx, dy). Coordinates and sizes are absolute pixels.
39static void vramMove(int16_t sx, int16_t sy, int16_t dx, int16_t dy,
40 int16_t w, int16_t h) {
41 waitGPU();
42 GPU_DATA = 0x80000000u;
43 GPU_DATA = ((uint32_t)(uint16_t)sy << 16) | (uint32_t)(uint16_t)sx;
44 GPU_DATA = ((uint32_t)(uint16_t)dy << 16) | (uint32_t)(uint16_t)dx;
45 GPU_DATA = ((uint32_t)(uint16_t)h << 16) | (uint32_t)(uint16_t)w;
46 waitGPU();
47}
48
49) // CESTER_BODY
50
51// --------------------------------------------------------------------------
52// 1. The copy moves real pixel data, not a flat color. A two-color source
53// (green left half, blue right half) must arrive intact at the dest, and
54// the source must be left unchanged.
55// --------------------------------------------------------------------------
56CESTER_TEST(vram_move_copies_pixel_data, gpu_vram_move,
57 rasterReset();
58 rasterClearTestRegion(0, 0, 128, 64);
59 // Source at (16,8), 16x8: columns 16..23 green, 24..31 blue.
60 rasterFillRect(16, 8, 8, 8, RASTER_VRAM_GREEN);
61 rasterFillRect(24, 8, 8, 8, RASTER_VRAM_BLUE);
62 vramMove(16, 8, 16, 40, 16, 8); // copy down to (16,40)
63 rasterFlushPrimitive();
64 ASSERT_PIXEL_EQ(RASTER_VRAM_GREEN, 18, 44); // dest left half
65 ASSERT_PIXEL_EQ(RASTER_VRAM_BLUE, 28, 44); // dest right half
66 ASSERT_PIXEL_EQ(RASTER_VRAM_GREEN, 18, 10); // source unchanged
67 ASSERT_PIXEL_UNTOUCHED(8, 44); // outside the dest rect
68)
69
70// --------------------------------------------------------------------------
71// 2. Copy coordinates are absolute VRAM addresses: the drawing offset (E5)
72// does not shift them (a rasterized primitive would be shifted).
73// --------------------------------------------------------------------------
74CESTER_TEST(vram_move_coords_are_absolute, gpu_vram_move,
75 rasterReset();
76 rasterClearTestRegion(0, 0, 256, 96);
77 rasterFillRect(16, 8, 8, 8, RASTER_VRAM_RED);
78 setDrawingOffset(50, 50); // would move a primitive by (50,50)
79 vramMove(16, 8, 100, 8, 8, 8); // dest requested at absolute (100,8)
80 rasterFlushPrimitive();
81 ASSERT_PIXEL_EQ(RASTER_VRAM_RED, 102, 10); // landed at absolute (100,8)
82 ASSERT_PIXEL_UNTOUCHED(152, 60); // NOT at the offset-shifted spot
83)
84
85// --------------------------------------------------------------------------
86// 3. Unlike fast-fill, the copy honors set-mask: it forces bit 15 on every
87// pixel it writes. (Compare gpu-fbrect fbrect_ignores_set_mask.)
88// --------------------------------------------------------------------------
89CESTER_TEST(vram_move_honors_set_mask, gpu_vram_move,
90 rasterReset();
91 rasterClearTestRegion(0, 0, 128, 64);
92 rasterFillRect(16, 8, 8, 8, RASTER_VRAM_RED); // source bit15 = 0
93 rasterSetMaskCtrl(1, 0); // set-mask on
94 vramMove(16, 8, 100, 8, 8, 8);
95 rasterFlushPrimitive();
96 // Copy forces bit 15 -> 0x801F, not the source's 0x001F.
98)
99
100// --------------------------------------------------------------------------
101// 4. Unlike fast-fill, the copy honors check-mask: a destination pixel that
102// already has bit 15 set is protected from being overwritten. (Compare
103// gpu-fbrect fbrect_ignores_check_mask, which overwrote it.)
104// --------------------------------------------------------------------------
105CESTER_TEST(vram_move_honors_check_mask, gpu_vram_move,
106 rasterReset();
107 rasterClearTestRegion(0, 0, 128, 64);
108 rasterFillRect(16, 8, 8, 8, RASTER_VRAM_RED); // source
109 rasterFillRect(100, 8, 8, 8, VM_GREEN_MASKED); // dest: green, bit15 = 1
110 rasterSetMaskCtrl(0, 1); // check-mask on
111 vramMove(16, 8, 100, 8, 8, 8); // copy red over it
112 rasterFlushPrimitive();
113 // Destination is mask-protected, so it keeps its green, not the red copy.
115)
116
117// --------------------------------------------------------------------------
118// 5. A copy onto its own source rectangle still runs: with set-mask on it
119// rewrites every pixel with bit 15 forced, so source == dest is not a no-op.
120// --------------------------------------------------------------------------
121CESTER_TEST(vram_move_in_place_honors_set_mask, gpu_vram_move,
122 rasterReset();
123 rasterClearTestRegion(0, 0, 128, 64);
124 rasterFillRect(16, 8, 8, 8, RASTER_VRAM_RED); // bit15 = 0
125 rasterSetMaskCtrl(1, 0); // set-mask on
126 vramMove(16, 8, 16, 8, 8, 8); // source == dest
127 rasterFlushPrimitive();
129)
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 VM_RED_MASKED
#define VM_GREEN_MASKED
int16_t sx
Definition gte-edgecase.c:41
int16_t sy
Definition gte-rtps.c:38
#define GPU_DATA
Definition hwregs.h:52
#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_VRAM_RED
Definition raster-helpers.h:125
#define ASSERT_PIXEL_EQ(expected, x_, y_)
Definition raster-helpers.h:473