1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
|
/*
* PSn00bSDK GPU library (common functions)
* (C) 2022 spicyjpeg - MPL licensed
*/
#include <stdint.h>
#include <sys/types.h>
#include <stdio.h>
#include <psxetc.h>
#include <psxapi.h>
#include <psxgpu.h>
#include <hwregs_c.h>
#define QUEUE_LENGTH 8
#define DMA_CHUNK_LENGTH 8
#define VSYNC_TIMEOUT 0x100000
/* Internal globals */
VIDEO_MODE _gpu_video_mode;
static void (*_vsync_callback)(void);
static void (*_drawsync_callback)(void);
static const u_long *volatile _draw_queue[QUEUE_LENGTH];
static volatile uint8_t _queue_head, _queue_tail, _queue_length;
static volatile uint32_t _vblank_counter, _last_hblank;
/* Interrupt handlers */
static void _vblank_handler(void) {
_vblank_counter++;
if (_vsync_callback)
_vsync_callback();
}
static void _gpu_dma_handler(void) {
//while (DMA_CHCR(2) & (1 << 24))
//__asm__ volatile("");
while (!(GPU_GP1 & (1 << 28)))
__asm__ volatile("");
if (_queue_length) {
DrawOTag2(_draw_queue[_queue_head++]);
_queue_length--;
_queue_head %= QUEUE_LENGTH;
} else {
GPU_GP1 = 0x04000000; // Disable DMA request
if (_drawsync_callback)
_drawsync_callback();
}
}
/* GPU reset and system initialization */
void ResetGraph(int mode) {
// Perform some basic system initialization when ResetGraph() is called for
// the first time.
static int setup_done = 0;
if (!setup_done) {
EnterCriticalSection();
DMA_DPCR = 0x03333333;
DMA_DICR = 0;
IRQ_MASK = 0;
InterruptCallback(0, &_vblank_handler);
DMACallback(2, &_gpu_dma_handler);
RestartCallback();
_96_remove();
_gpu_video_mode = (GPU_GP1 >> 20) & 1;
setup_done = 1;
ExitCriticalSection();
printf("psxgpu: setup done, default mode is %s\n", _gpu_video_mode ? "PAL" : "NTSC");
}
if (mode == 3) {
GPU_GP1 = 0x01000000; // Reset command buffer
return;
}
DMA_DPCR |= 0x0b000b00; // Enable DMA2 and DMA6
DMA_CHCR(2) = 0x00000201; // Stop DMA2
DMA_CHCR(6) = 0x00000200; // Stop DMA6
if (mode == 1) {
GPU_GP1 = 0x01000000; // Reset command buffer
return;
}
GPU_GP1 = 0x00000000; // Reset GPU
TIMER_CTRL(0) = 0x0500;
TIMER_CTRL(1) = 0x0500;
_queue_head = 0;
_queue_tail = 0;
_queue_length = 0;
_vblank_counter = 0;
_last_hblank = 0;
}
/* Syncing API */
// TODO: add support for no$psx's "halt" register
static void _vsync_halt(void) {
int counter = _vblank_counter;
for (int i = VSYNC_TIMEOUT; i; i--) {
if (counter != _vblank_counter)
return;
}
printf("psxgpu: VSync() timeout\n");
ChangeClearPAD(0);
ChangeClearRCnt(3, 0);
return;
}
int VSync(int mode) {
if (mode < 0)
return _vblank_counter;
uint32_t status = GPU_GP1;
// If mode = 0, wait for one vertical blank event to occur. If mode = 1, do
// not wait.
for (int i = ((mode < 2) ? (mode ^ 1) : mode); i; i--) {
_vsync_halt();
// If interlaced mode is enabled, wait until the GPU starts displaying
// the next field.
if (status & (1 << 22)) {
while (!((GPU_GP1 ^ status) & (1 << 31)))
__asm__ volatile("");
}
}
// Update the horizontal blank counter and return the time elapsed since
// the last time it was updated.
uint16_t counter = TIMER_VALUE(1);
uint16_t delta = counter - _last_hblank;
_last_hblank = counter;
return delta;
}
int DrawSync(int mode) {
if (mode)
return (DMA_BCR(2) >> 16);
// Wait for the queue to become empty, to make sure no .
// TODO: add a timeout
while (_queue_length)
__asm__ volatile("");
// Wait for any DMA transfer to finish if DMA is enabled.
if (GPU_GP1 & (3 << 29)) {
while (DMA_CHCR(2) & (1 << 24))
__asm__ volatile("");
while (!(GPU_GP1 & (1 << 28)))
__asm__ volatile("");
}
while (!(GPU_GP1 & (1 << 26)))
__asm__ volatile("");
return 0;
}
void *VSyncCallback(void (*func)(void)) {
EnterCriticalSection();
void *old_callback = _vsync_callback;
_vsync_callback = func;
ExitCriticalSection();
return old_callback;
}
void *DrawSyncCallback(void (*func)(void)) {
EnterCriticalSection();
void *old_callback = _drawsync_callback;
_drawsync_callback = func;
ExitCriticalSection();
return old_callback;
}
/* OT and primitive drawing API */
void ClearOTagR(u_long *ot, size_t length) {
DMA_MADR(6) = (uint32_t) &ot[length - 1];
DMA_BCR(6) = length & 0xffff;
DMA_CHCR(6) = 0x11000002;
//while (DMA_CHCR(6) & (1 << 24))
//__asm__ volatile("");
}
void ClearOTag(u_long *ot, size_t length) {
// DMA6 only supports writing to RAM in reverse order (last to first), so
// the OT has to be cleared in software here. This function is thus much
// slower than ClearOTagR().
// https://problemkaputt.de/psx-spx.htm#dmachannels
for (int i = 0; i < (length - 1); i++)
ot[i] = (u_long) &ot[i + 1] & 0x00ffffff;
ot[length - 1] = 0x00ffffff;
}
void DrawOTag(const u_long *ot) {
// If GPU DMA is currently busy, append the OT to the queue instead of
// drawing it immediately.
if (DMA_CHCR(2) & (1 << 24)) {
if (_queue_length >= QUEUE_LENGTH) {
printf("psxgpu: DrawOTag() failed, draw queue full\n");
return;
}
_draw_queue[_queue_tail++] = ot;
_queue_length++;
_queue_tail %= QUEUE_LENGTH;
return;
}
DrawOTag2(ot);
}
void DrawOTag2(const u_long *ot) {
GPU_GP1 = 0x04000002;
while (!(GPU_GP1 & (1 << 26)))
__asm__ volatile("");
DMA_MADR(2) = (uint32_t) ot;
DMA_BCR(2) = 0;
DMA_CHCR(2) = 0x01000401;
}
void DrawPrim(const u_long *pri) {
size_t length = getlen(pri);
DrawSync(0);
GPU_GP1 = 0x04000002;
// NOTE: if length >= DMA_CHUNK_LENGTH then it also has to be a multiple of
// DMA_CHUNK_LENGTH, otherwise the DMA channel will get stuck waiting for
// more data indefinitely.
DMA_MADR(2) = (uint32_t) &pri[1];
if (length < DMA_CHUNK_LENGTH)
DMA_BCR(2) = 0x00010000 | length;
else
DMA_BCR(2) = DMA_CHUNK_LENGTH | ((length / DMA_CHUNK_LENGTH) << 16);
DMA_CHCR(2) = 0x01000201;
}
void AddPrim(u_long *ot, const void *pri) {
addPrim(ot, pri);
}
/* Misc. functions */
VIDEO_MODE GetVideoMode(void) {
return _gpu_video_mode;
}
void SetVideoMode(VIDEO_MODE mode) {
uint32_t _mode, stat = GPU_GP1;
_gpu_video_mode = mode & 1;
_mode = (mode & 1) << 3;
_mode |= (stat >> 17) & 0x37; // GPUSTAT 17-22 -> cmd bits 0-5
_mode |= (stat >> 10) & 0x40; // GPUSTAT bit 16 -> cmd bit 6
_mode |= (stat >> 7) & 0x80; // GPUSTAT bit 14 -> cmd bit 7
GPU_GP1 = 0x08000000 | mode;
}
int GetODE(void) {
return (GPU_GP1 >> 31);
}
void SetDispMask(int mask) {
GPU_GP1 = 0x03000000 | (mask ? 0 : 1);
}
|