aboutsummaryrefslogtreecommitdiff
path: root/libpsn00b/psxgpu/common.c
diff options
context:
space:
mode:
authorspicyjpeg <88942473+spicyjpeg@users.noreply.github.com>2022-06-29 12:40:46 +0200
committerspicyjpeg <88942473+spicyjpeg@users.noreply.github.com>2022-06-29 12:40:46 +0200
commit6120304537470e7e5ff94b3bf19a33787ca69083 (patch)
treef489c6dceae0846184dddd38444ef8519f799f7b /libpsn00b/psxgpu/common.c
parent578181ace1374e72cb93d69d2c201ce7a0a2300c (diff)
downloadpsn00bsdk-6120304537470e7e5ff94b3bf19a33787ca69083.tar.gz
Refactor libpsxgpu in C, add OT drawing queue
Diffstat (limited to 'libpsn00b/psxgpu/common.c')
-rw-r--r--libpsn00b/psxgpu/common.c293
1 files changed, 293 insertions, 0 deletions
diff --git a/libpsn00b/psxgpu/common.c b/libpsn00b/psxgpu/common.c
new file mode 100644
index 0000000..ee90225
--- /dev/null
+++ b/libpsn00b/psxgpu/common.c
@@ -0,0 +1,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);
+}