Some progress
This commit is contained in:
+4
-1
@@ -21,5 +21,8 @@ elseif(DUSK_TARGET_SYSTEM STREQUAL "vita")
|
||||
|
||||
elseif(DUSK_TARGET_SYSTEM STREQUAL "wii" OR DUSK_TARGET_SYSTEM STREQUAL "gamecube")
|
||||
add_subdirectory(duskdolphin)
|
||||
|
||||
|
||||
elseif(DUSK_TARGET_SYSTEM STREQUAL "saturn")
|
||||
add_subdirectory(dusksat)
|
||||
|
||||
endif()
|
||||
@@ -10,6 +10,9 @@
|
||||
|
||||
#ifdef DUSK_THREAD_PTHREAD
|
||||
#define THREAD_LOCAL __thread
|
||||
#elif defined(DUSK_THREAD_NONE)
|
||||
/* Single-threaded platforms: no thread-local storage qualifier needed. */
|
||||
#define THREAD_LOCAL
|
||||
#endif
|
||||
|
||||
#ifndef THREAD_LOCAL
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
#ifdef DUSK_THREAD_PTHREAD
|
||||
#include <pthread.h>
|
||||
#else
|
||||
#elif !defined(DUSK_THREAD_NONE)
|
||||
#error "At least one threading implementation must be defined."
|
||||
#endif
|
||||
|
||||
@@ -18,6 +18,8 @@ typedef struct threadlock_t {
|
||||
#ifdef DUSK_THREAD_PTHREAD
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
#else
|
||||
uint8_t unused; /* DUSK_THREAD_NONE: no real mutex on single-threaded platforms */
|
||||
#endif
|
||||
} threadmutex_t;
|
||||
|
||||
|
||||
@@ -35,6 +35,19 @@ typedef struct {
|
||||
static dolphintexentry_t dolphinTexTable[DOLPHIN_RTEXTURE_MAX];
|
||||
static uint16_t dolphinTexNext = 1; /* 0 = white fallback */
|
||||
|
||||
/* ---- Chunk table --------------------------------------------------------- */
|
||||
|
||||
#define DOLPHIN_CHUNK_MAX 256
|
||||
|
||||
typedef struct {
|
||||
void *dispList;
|
||||
uint32_t dispListSize;
|
||||
rtexture_t tileset;
|
||||
} dolphinchunkentry_t;
|
||||
|
||||
static dolphinchunkentry_t dolphinChunkTable[DOLPHIN_CHUNK_MAX];
|
||||
static uint16_t dolphinChunkNext = 1; /* 0 = RTILEMAPCHUNK_INVALID */
|
||||
|
||||
/* ---- Camera state -------------------------------------------------------- */
|
||||
|
||||
static Mtx44 dolphinProj;
|
||||
@@ -293,6 +306,109 @@ static void draw3DQuad(const ropquad3d_t *q) {
|
||||
GX_End();
|
||||
}
|
||||
|
||||
/* ---- Tilemap chunks ------------------------------------------------------ */
|
||||
|
||||
rtilemapchunk_t renderDolphinTilemapChunkCreate(
|
||||
uint16_t chunkW, uint16_t chunkH,
|
||||
uint16_t tileW, uint16_t tileH,
|
||||
rtexture_t tileset,
|
||||
const uint8_t *tileIndices
|
||||
) {
|
||||
assertTrue(dolphinChunkNext < DOLPHIN_CHUNK_MAX, "Dolphin chunk table full");
|
||||
assertTrue(tileW > 0 && tileH > 0, "Tile dimensions must be > 0");
|
||||
assertTrue(
|
||||
tileset < DOLPHIN_RTEXTURE_MAX && dolphinTexTable[tileset].cpuIndices,
|
||||
"Dolphin chunk: invalid tileset handle"
|
||||
);
|
||||
|
||||
uint16_t texW = dolphinTexTable[tileset].w;
|
||||
uint16_t texH = dolphinTexTable[tileset].h;
|
||||
assertTrue(texW >= tileW && texH >= tileH, "Tileset smaller than one tile");
|
||||
uint16_t tilesPerRow = texW / tileW;
|
||||
|
||||
rtilemapchunk_t handle = (rtilemapchunk_t)dolphinChunkNext++;
|
||||
dolphinchunkentry_t *e = &dolphinChunkTable[handle];
|
||||
e->tileset = tileset;
|
||||
|
||||
/* Allocate display list buffer. Each tile = 4 GX_QUADS verts.
|
||||
* Per vertex: 4B pos(XY/S16) + 4B color(RGBA8) + 8B uv(ST/F32) = 16B.
|
||||
* Add 64 bytes of header/padding margin; align to 32. */
|
||||
uint32_t tileCount = (uint32_t)chunkW * chunkH;
|
||||
uint32_t alignedSize = (tileCount * 4u * 16u + 64u + 31u) & ~31u;
|
||||
|
||||
e->dispList = memalign(32, alignedSize);
|
||||
assertNotNull(e->dispList, "Dolphin: failed to allocate chunk display list");
|
||||
memset(e->dispList, 0, alignedSize);
|
||||
|
||||
/* Set up the vertex descriptor that will be baked into the display list.
|
||||
* Matches setup2D() exactly; done here so chunks can be created before
|
||||
* the first flush. */
|
||||
GX_ClearVtxDesc();
|
||||
GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
|
||||
GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT);
|
||||
GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT);
|
||||
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0);
|
||||
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
|
||||
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
|
||||
|
||||
GX_BeginDispList(e->dispList, alignedSize);
|
||||
GX_Begin(GX_QUADS, GX_VTXFMT0, (uint16_t)(tileCount * 4));
|
||||
for(uint32_t ci = 0; ci < tileCount; ci++) {
|
||||
uint8_t idx = tileIndices[ci];
|
||||
uint16_t tileCol = idx % tilesPerRow;
|
||||
uint16_t tileRow = idx / tilesPerRow;
|
||||
|
||||
int16_t px0 = (int16_t)((ci % chunkW) * tileW);
|
||||
int16_t py0 = (int16_t)((ci / chunkW) * tileH);
|
||||
int16_t px1 = (int16_t)(px0 + tileW);
|
||||
int16_t py1 = (int16_t)(py0 + tileH);
|
||||
|
||||
float u0 = (float)(tileCol * tileW) / (float)texW;
|
||||
float v0 = (float)(tileRow * tileH) / (float)texH;
|
||||
float u1 = u0 + (float)tileW / (float)texW;
|
||||
float v1 = v0 + (float)tileH / (float)texH;
|
||||
|
||||
GX_Position2s16(px0, py0); GX_Color4u8(255,255,255,255); GX_TexCoord2f32(u0,v0);
|
||||
GX_Position2s16(px1, py0); GX_Color4u8(255,255,255,255); GX_TexCoord2f32(u1,v0);
|
||||
GX_Position2s16(px1, py1); GX_Color4u8(255,255,255,255); GX_TexCoord2f32(u1,v1);
|
||||
GX_Position2s16(px0, py1); GX_Color4u8(255,255,255,255); GX_TexCoord2f32(u0,v1);
|
||||
}
|
||||
GX_End();
|
||||
e->dispListSize = GX_EndDispList();
|
||||
DCFlushRange(e->dispList, alignedSize);
|
||||
|
||||
/* Restore 2D vertex state for subsequent draws. */
|
||||
dolphinIs3D = 0;
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
void renderDolphinTilemapChunkDispose(rtilemapchunk_t chunk) {
|
||||
if(chunk == RTILEMAPCHUNK_INVALID || chunk >= DOLPHIN_CHUNK_MAX) return;
|
||||
dolphinchunkentry_t *e = &dolphinChunkTable[chunk];
|
||||
if(e->dispList) { free(e->dispList); e->dispList = NULL; }
|
||||
}
|
||||
|
||||
static void draw2DTilemapChunk(const roptilemapc_t *t) {
|
||||
if(t->chunk == RTILEMAPCHUNK_INVALID || t->chunk >= dolphinChunkNext) return;
|
||||
dolphinchunkentry_t *e = &dolphinChunkTable[t->chunk];
|
||||
if(!e->dispList) return;
|
||||
|
||||
if(dolphinIs3D) setup2D();
|
||||
|
||||
bindTexture(e->tileset);
|
||||
setTintChannel(COLOR_WHITE);
|
||||
|
||||
/* Load the scroll offset as a translation into PNMTX1, call the display
|
||||
* list, then restore PNMTX0 (identity, set by setup2D). */
|
||||
Mtx translate;
|
||||
guMtxTrans(translate, (float)t->x, (float)t->y, 0.0f);
|
||||
GX_LoadPosMtxImm(translate, GX_PNMTX1);
|
||||
GX_SetCurrentMtx(GX_PNMTX1);
|
||||
GX_CallDispList(e->dispList, e->dispListSize);
|
||||
GX_SetCurrentMtx(GX_PNMTX0);
|
||||
}
|
||||
|
||||
/* ---- Flush --------------------------------------------------------------- */
|
||||
|
||||
errorret_t renderDolphinFlush(ropbuffer_t *buf) {
|
||||
@@ -351,6 +467,10 @@ errorret_t renderDolphinFlush(ropbuffer_t *buf) {
|
||||
draw3DQuad((const ropquad3d_t *)hdr);
|
||||
break;
|
||||
|
||||
case ROP_DRAW_TILEMAP_CHUNK:
|
||||
draw2DTilemapChunk((const roptilemapc_t *)hdr);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -363,6 +483,12 @@ errorret_t renderDolphinFlush(ropbuffer_t *buf) {
|
||||
/* ---- Dispose ------------------------------------------------------------- */
|
||||
|
||||
void renderDolphinDispose(void) {
|
||||
for(uint16_t i = 1; i < dolphinChunkNext; i++) {
|
||||
dolphinchunkentry_t *e = &dolphinChunkTable[i];
|
||||
if(e->dispList) { free(e->dispList); e->dispList = NULL; }
|
||||
}
|
||||
dolphinChunkNext = 1;
|
||||
|
||||
for(uint16_t i = 0; i < dolphinTexNext; i++) {
|
||||
dolphintexentry_t *e = &dolphinTexTable[i];
|
||||
if(e->cpuIndices) { free(e->cpuIndices); e->cpuIndices = NULL; }
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "error/error.h"
|
||||
#include "display/render/ropbuffer.h"
|
||||
#include "display/render/rtexture.h"
|
||||
#include "display/render/rtilemapchunk.h"
|
||||
#include "display/color.h"
|
||||
|
||||
errorret_t renderDolphinInit(void);
|
||||
@@ -22,3 +23,11 @@ rtexture_t renderDolphinTextureCreate(
|
||||
void renderDolphinTextureDispose(rtexture_t tex);
|
||||
color_t *renderDolphinTextureGetPalette(rtexture_t tex);
|
||||
uint8_t *renderDolphinTextureGetIndices(rtexture_t tex);
|
||||
|
||||
rtilemapchunk_t renderDolphinTilemapChunkCreate(
|
||||
uint16_t chunkW, uint16_t chunkH,
|
||||
uint16_t tileW, uint16_t tileH,
|
||||
rtexture_t tileset,
|
||||
const uint8_t *tileIndices
|
||||
);
|
||||
void renderDolphinTilemapChunkDispose(rtilemapchunk_t chunk);
|
||||
|
||||
@@ -8,7 +8,10 @@
|
||||
#pragma once
|
||||
#include "display/render/renderdolphin.h"
|
||||
|
||||
#define renderPlatformTextureCreate renderDolphinTextureCreate
|
||||
#define renderPlatformTextureDispose renderDolphinTextureDispose
|
||||
#define renderPlatformTextureGetPalette renderDolphinTextureGetPalette
|
||||
#define renderPlatformTextureGetIndices renderDolphinTextureGetIndices
|
||||
#define renderPlatformTextureCreate renderDolphinTextureCreate
|
||||
#define renderPlatformTextureDispose renderDolphinTextureDispose
|
||||
#define renderPlatformTextureGetPalette renderDolphinTextureGetPalette
|
||||
#define renderPlatformTextureGetIndices renderDolphinTextureGetIndices
|
||||
|
||||
#define renderPlatformTilemapChunkCreate renderDolphinTilemapChunkCreate
|
||||
#define renderPlatformTilemapChunkDispose renderDolphinTilemapChunkDispose
|
||||
|
||||
@@ -9,26 +9,32 @@
|
||||
#include "display/render/renderpsp.h"
|
||||
#include "display/display.h"
|
||||
#include "assert/assert.h"
|
||||
#include "log/log.h"
|
||||
#include <pspgu.h>
|
||||
#include <pspgum.h>
|
||||
#include <pspdisplay.h>
|
||||
#include <pspge.h>
|
||||
|
||||
#define FRAME_SIZE (PSP_SCREEN_W * PSP_SCREEN_H * 4)
|
||||
#define VRAM_ADDR(offset) ((void *)((uintptr_t)sceGeEdramGetAddr() + (offset)))
|
||||
/* GU framebuffer stride must be power-of-two; 512 is the standard for 480-wide. */
|
||||
#define PSP_BUF_W 512
|
||||
#define FRAME_SIZE (PSP_BUF_W * PSP_SCREEN_H * 4)
|
||||
/* sceGuDrawBuffer/DispBuffer/DepthBuffer take VRAM-relative byte offsets,
|
||||
* NOT absolute virtual addresses — do NOT add sceGeEdramGetAddr() here. */
|
||||
#define VRAM_ADDR(offset) ((void *)(uintptr_t)(offset))
|
||||
|
||||
static uint32_t __attribute__((aligned(64))) displayList[0x10000];
|
||||
|
||||
errorret_t displayPSPInit(void) {
|
||||
logDebug("[PSP] displayPSPInit: start\n");
|
||||
DISPLAY.whichBuffer = 0;
|
||||
|
||||
sceGuInit();
|
||||
sceGuStart(GU_DIRECT, displayList);
|
||||
|
||||
/* Draw buffer: frame 0 at offset 0, frame 1 at FRAME_SIZE */
|
||||
sceGuDrawBuffer(GU_PSM_8888, VRAM_ADDR(0), PSP_SCREEN_W);
|
||||
sceGuDispBuffer(PSP_SCREEN_W, PSP_SCREEN_H, VRAM_ADDR(FRAME_SIZE), PSP_SCREEN_W);
|
||||
sceGuDepthBuffer(VRAM_ADDR(FRAME_SIZE * 2), PSP_SCREEN_W);
|
||||
sceGuDrawBuffer(GU_PSM_8888, VRAM_ADDR(0), PSP_BUF_W);
|
||||
sceGuDispBuffer(PSP_SCREEN_W, PSP_SCREEN_H, VRAM_ADDR(FRAME_SIZE), PSP_BUF_W);
|
||||
sceGuDepthBuffer(VRAM_ADDR(FRAME_SIZE * 2), PSP_BUF_W);
|
||||
|
||||
sceGuOffset(2048 - PSP_SCREEN_W / 2, 2048 - PSP_SCREEN_H / 2);
|
||||
sceGuViewport(2048, 2048, PSP_SCREEN_W, PSP_SCREEN_H);
|
||||
@@ -52,18 +58,23 @@ errorret_t displayPSPInit(void) {
|
||||
|
||||
sceDisplaySetMode(0, PSP_SCREEN_W, PSP_SCREEN_H);
|
||||
sceGuDisplay(GU_TRUE);
|
||||
logDebug("[PSP] displayPSPInit: GU setup done, calling renderPSPInit\n");
|
||||
|
||||
errorChain(renderPSPInit());
|
||||
logDebug("[PSP] displayPSPInit: done\n");
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t displayPSPFlush(ropbuffer_t *buf) {
|
||||
logDebug("[PSP] displayPSPFlush: enter\n");
|
||||
assertNotNull(buf, "PSP flush: null ropbuffer");
|
||||
errorChain(renderPSPFlush(buf));
|
||||
logDebug("[PSP] displayPSPFlush: done\n");
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t displayPSPSwap(void) {
|
||||
logDebug("[PSP] displayPSPSwap\n");
|
||||
sceGuSwapBuffers();
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "display/color.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "log/log.h"
|
||||
#include <malloc.h>
|
||||
#include <psputils.h> /* sceKernelDcacheWritebackRange */
|
||||
#include <pspgu.h>
|
||||
@@ -80,6 +81,7 @@ static uint16_t texturePow2(uint16_t n) {
|
||||
/* ---- Init ---------------------------------------------------------------- */
|
||||
|
||||
errorret_t renderPSPInit(void) {
|
||||
logDebug("[PSP] renderPSPInit: start\n");
|
||||
/* White 1×1 fallback: index 0 → palette[0] = white */
|
||||
psptexentry_t *e = &pspTexTable[0];
|
||||
e->cpuIndices = (uint8_t *)memalign(16, 1);
|
||||
@@ -93,6 +95,7 @@ errorret_t renderPSPInit(void) {
|
||||
memoryZero(e->palette, 256 * sizeof(color_t));
|
||||
e->palette[0] = COLOR_WHITE;
|
||||
e->w = 1; e->h = 1; e->tbw = 8;
|
||||
logDebug("[PSP] renderPSPInit: done\n");
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -202,6 +205,7 @@ static void draw2DSprite(const ropsprite_t *s) {
|
||||
}
|
||||
|
||||
static void draw3DQuad(const ropquad3d_t *q) {
|
||||
logDebug("[PSP] draw3DQuad: enter tex=%u\n", (unsigned)q->texture);
|
||||
uint32_t abgr = toABGR(q->tint);
|
||||
float u0 = q->uvX / 255.0f, v0 = q->uvY / 255.0f;
|
||||
float u1 = (q->uvX + q->uvW) / 255.0f;
|
||||
@@ -216,9 +220,12 @@ static void draw3DQuad(const ropquad3d_t *q) {
|
||||
float blx = cx-rx-ux, bly = cy-ry-uy, blz = cz-rz-uz;
|
||||
float brx = cx+rx-ux, bry = cy+ry-uy, brz = cz+rz-uz;
|
||||
|
||||
logDebug("[PSP] draw3DQuad: bindTexture\n");
|
||||
bindTexture(q->texture);
|
||||
|
||||
logDebug("[PSP] draw3DQuad: getMemory\n");
|
||||
GuVert3D *verts = (GuVert3D *)sceGuGetMemory(6 * sizeof(GuVert3D));
|
||||
logDebug("[PSP] draw3DQuad: verts=0x%08x\n", (unsigned)verts);
|
||||
assertNotNull(verts, "PSP: failed to allocate 3D quad vertices");
|
||||
|
||||
verts[0] = (GuVert3D){u0,v0, abgr, tlx,tly,tlz};
|
||||
@@ -228,16 +235,21 @@ static void draw3DQuad(const ropquad3d_t *q) {
|
||||
verts[4] = (GuVert3D){u1,v1, abgr, brx,bry,brz};
|
||||
verts[5] = (GuVert3D){u1,v0, abgr, trx,try_,trz};
|
||||
|
||||
logDebug("[PSP] draw3DQuad: sceGuDrawArray\n");
|
||||
sceGuDrawArray(
|
||||
GU_TRIANGLES,
|
||||
GU_TEXTURE_32BITF | GU_COLOR_8888 | GU_VERTEX_32BITF | GU_TRANSFORM_3D,
|
||||
6, 0, verts
|
||||
);
|
||||
logDebug("[PSP] draw3DQuad: done\n");
|
||||
}
|
||||
|
||||
/* ---- Flush --------------------------------------------------------------- */
|
||||
|
||||
errorret_t renderPSPFlush(ropbuffer_t *buf) {
|
||||
logDebug("[PSP] renderPSPFlush: byteCount=%u count=%u\n",
|
||||
(unsigned)buf->byteCount, (unsigned)buf->count);
|
||||
|
||||
sceGuStart(GU_DIRECT, displayList);
|
||||
|
||||
sceGuEnable(GU_TEXTURE_2D);
|
||||
@@ -245,12 +257,14 @@ errorret_t renderPSPFlush(ropbuffer_t *buf) {
|
||||
sceGuDepthFunc(GU_GEQUAL); /* PSP uses reversed depth */
|
||||
|
||||
uint32_t offset = 0;
|
||||
uint32_t opIdx = 0;
|
||||
while(offset < buf->byteCount) {
|
||||
const ropheader_t *hdr = (const ropheader_t *)(buf->data + offset);
|
||||
ropop_t op = (ropop_t)hdr->op;
|
||||
|
||||
switch(op) {
|
||||
case ROP_CLEAR: {
|
||||
logDebug("[PSP] op[%u] ROP_CLEAR\n", (unsigned)opIdx);
|
||||
const ropclear_t *c = (const ropclear_t *)hdr;
|
||||
uint32_t abgr = toABGR(c->color);
|
||||
sceGuClearColor(abgr);
|
||||
@@ -259,10 +273,12 @@ errorret_t renderPSPFlush(ropbuffer_t *buf) {
|
||||
break;
|
||||
}
|
||||
case ROP_DRAW_SPRITE:
|
||||
logDebug("[PSP] op[%u] ROP_DRAW_SPRITE\n", (unsigned)opIdx);
|
||||
draw2DSprite((const ropsprite_t *)hdr);
|
||||
break;
|
||||
|
||||
case ROP_SET_PROJECTION: {
|
||||
logDebug("[PSP] op[%u] ROP_SET_PROJECTION\n", (unsigned)opIdx);
|
||||
const ropprojection_t *p = (const ropprojection_t *)hdr;
|
||||
sceGumMatrixMode(GU_PROJECTION);
|
||||
sceGumLoadIdentity();
|
||||
@@ -280,6 +296,7 @@ errorret_t renderPSPFlush(ropbuffer_t *buf) {
|
||||
break;
|
||||
}
|
||||
case ROP_SET_VIEW: {
|
||||
logDebug("[PSP] op[%u] ROP_SET_VIEW\n", (unsigned)opIdx);
|
||||
const ropview_t *v = (const ropview_t *)hdr;
|
||||
ScePspFVector3 eye = {(float)v->eyeX, (float)v->eyeY, (float)v->eyeZ};
|
||||
ScePspFVector3 target = {(float)v->tgtX, (float)v->tgtY, (float)v->tgtZ};
|
||||
@@ -292,17 +309,23 @@ errorret_t renderPSPFlush(ropbuffer_t *buf) {
|
||||
break;
|
||||
}
|
||||
case ROP_DRAW_QUAD_3D:
|
||||
logDebug("[PSP] op[%u] ROP_DRAW_QUAD_3D\n", (unsigned)opIdx);
|
||||
draw3DQuad((const ropquad3d_t *)hdr);
|
||||
break;
|
||||
|
||||
default:
|
||||
logDebug("[PSP] op[%u] unknown op=%u offset=%u\n",
|
||||
(unsigned)opIdx, (unsigned)op, (unsigned)offset);
|
||||
break;
|
||||
}
|
||||
offset += ropOpSize(op);
|
||||
opIdx++;
|
||||
}
|
||||
|
||||
logDebug("[PSP] renderPSPFlush: sceGuFinish\n");
|
||||
sceGuFinish();
|
||||
sceGuSync(0, 0);
|
||||
logDebug("[PSP] renderPSPFlush: done\n");
|
||||
errorOk();
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,11 @@ void logDebug(const char_t *message, ...) {
|
||||
va_list args;
|
||||
va_start(args, message);
|
||||
|
||||
// print to stdout
|
||||
// print to stdout — fflush so pspsh sees it immediately even on crash
|
||||
va_list copy;
|
||||
va_copy(copy, args);
|
||||
vprintf(message, copy);
|
||||
fflush(stdout);
|
||||
va_end(copy);
|
||||
|
||||
// print to file
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_include_directories(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
)
|
||||
|
||||
target_sources(${DUSK_BINARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
)
|
||||
|
||||
add_subdirectory(asset)
|
||||
add_subdirectory(display)
|
||||
add_subdirectory(input)
|
||||
add_subdirectory(log)
|
||||
add_subdirectory(network)
|
||||
add_subdirectory(save)
|
||||
add_subdirectory(system)
|
||||
add_subdirectory(time)
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_BINARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}/assetsat.c
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "assetsat.h"
|
||||
|
||||
typedef assetsat_t assetplatform_t;
|
||||
|
||||
#define assetInitPlatform assetInitSaturn
|
||||
#define assetDisposePlatform assetDisposeSaturn
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "assetsat.h"
|
||||
#include "log/log.h"
|
||||
|
||||
errorret_t assetInitSaturn(void) {
|
||||
logDebug("[Saturn] assetInitSaturn: initializing CD-Block\n");
|
||||
/* TODO: cd_block_init() */
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetDisposeSaturn(void) {
|
||||
/* TODO: cd_block_deinit() */
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include <cd-block.h>
|
||||
|
||||
typedef struct {
|
||||
uint8_t unused;
|
||||
} assetsat_t;
|
||||
|
||||
errorret_t assetInitSaturn(void);
|
||||
errorret_t assetDisposeSaturn(void);
|
||||
@@ -0,0 +1,11 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_BINARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}/displaysat.c
|
||||
)
|
||||
|
||||
add_subdirectory(render)
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "display/displaysat.h"
|
||||
|
||||
typedef displaysat_t displayplatform_t;
|
||||
|
||||
#define displayPlatformInit displaySaturnInit
|
||||
#define displayPlatformFlush displaySaturnFlush
|
||||
#define displayPlatformSwap displaySaturnSwap
|
||||
#define displayPlatformDispose displaySaturnDispose
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "display/displaysat.h"
|
||||
#include "display/render/rendersat.h"
|
||||
#include "display/display.h"
|
||||
#include "assert/assert.h"
|
||||
#include "log/log.h"
|
||||
#include <vdp1/cmdt.h>
|
||||
#include <vdp2/cram.h>
|
||||
#include <vdp2/tvmd.h>
|
||||
#include <vdp2/scrn.h>
|
||||
#include <vdp2/vram.h>
|
||||
|
||||
errorret_t displaySaturnInit(void) {
|
||||
logDebug("[Saturn] displaySaturnInit: start\n");
|
||||
DISPLAY.whichBuffer = 0;
|
||||
|
||||
/*
|
||||
* TV mode: NTSC, 320×224, non-interlaced.
|
||||
* Yaul's vdp2_tvmd_display_res_set() configures the sync standard and
|
||||
* horizontal/vertical resolution.
|
||||
*
|
||||
* TODO: replace with the Yaul typed call when integrating the full SDK:
|
||||
* vdp2_tvmd_display_res_set(VDP2_TVMD_INTERLACE_NONE,
|
||||
* VDP2_TVMD_HORZ_NORMAL_A,
|
||||
* VDP2_TVMD_VERT_224);
|
||||
* vdp2_tvmd_display_set();
|
||||
*/
|
||||
|
||||
/*
|
||||
* VDP2 scroll planes: disable all NBG/RBG planes for now; game content is
|
||||
* drawn entirely via VDP1 sprites. Tilemap chunks will re-enable NBG0
|
||||
* when the VDP2 tilemap backend is implemented.
|
||||
*
|
||||
* TODO:
|
||||
* vdp2_scrn_display_set(VDP2_SCRN_DISP_NBG0, false);
|
||||
* vdp2_scrn_display_set(VDP2_SCRN_DISP_NBG1, false);
|
||||
* ...
|
||||
*/
|
||||
|
||||
/*
|
||||
* VDP1 initialisation: the hardware starts drawing from VRAM offset 0.
|
||||
* We place our command table there and texture data afterward.
|
||||
*
|
||||
* TODO:
|
||||
* vdp1_vram_partitions_set(
|
||||
* VDP1_VRAM_CYCP_..., // cycle patterns
|
||||
* ...
|
||||
* );
|
||||
*/
|
||||
|
||||
logDebug("[Saturn] displaySaturnInit: calling renderSaturnInit\n");
|
||||
errorChain(renderSaturnInit());
|
||||
|
||||
logDebug("[Saturn] displaySaturnInit: done\n");
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t displaySaturnFlush(ropbuffer_t *buf) {
|
||||
assertNotNull(buf, "Saturn flush: null ropbuffer");
|
||||
errorChain(renderSaturnFlush(buf));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t displaySaturnSwap(void) {
|
||||
logDebug("[Saturn] displaySaturnSwap\n");
|
||||
/*
|
||||
* Wait for VDP1 to finish rendering the current frame then swap buffers.
|
||||
*
|
||||
* TODO:
|
||||
* vdp1_sync_render();
|
||||
* vdp1_sync();
|
||||
* vdp2_sync();
|
||||
* vdp2_sync_wait();
|
||||
*/
|
||||
DISPLAY.whichBuffer ^= 1;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void displaySaturnDispose(void) {
|
||||
logDebug("[Saturn] displaySaturnDispose\n");
|
||||
renderSaturnDispose();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "display/displaystate.h"
|
||||
#include "display/render/ropbuffer.h"
|
||||
|
||||
#define SAT_SCREEN_W DUSK_DISPLAY_WIDTH
|
||||
#define SAT_SCREEN_H DUSK_DISPLAY_HEIGHT
|
||||
|
||||
typedef struct {
|
||||
int_t whichBuffer;
|
||||
} displaysat_t;
|
||||
|
||||
errorret_t displaySaturnInit(void);
|
||||
errorret_t displaySaturnFlush(ropbuffer_t *buf);
|
||||
errorret_t displaySaturnSwap(void);
|
||||
void displaySaturnDispose(void);
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_BINARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}/rendersat.c
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "display/render/rendersat.h"
|
||||
|
||||
#define renderPlatformTextureCreate renderSaturnTextureCreate
|
||||
#define renderPlatformTextureDispose renderSaturnTextureDispose
|
||||
#define renderPlatformTextureGetPalette renderSaturnTextureGetPalette
|
||||
#define renderPlatformTextureGetIndices renderSaturnTextureGetIndices
|
||||
@@ -0,0 +1,446 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "display/render/rendersat.h"
|
||||
#include "display/render/rop.h"
|
||||
#include "display/color.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "log/log.h"
|
||||
#include <vdp1/cmdt.h>
|
||||
#include <vdp1/vram.h>
|
||||
#include <vdp2/cram.h>
|
||||
|
||||
/*
|
||||
* VDP1 renders sprites and polygons from a command table stored in VRAM.
|
||||
* VDP2 handles the scroll plane background (used for tilemap chunks).
|
||||
*
|
||||
* VRAM layout (VDP1, 4MB total):
|
||||
* 0x000000 – 0x00FFFF : VDP1 command table (64KB = 2048 entries max)
|
||||
* 0x010000 – 0x1FFFFF : texture pool (1984KB)
|
||||
* Framebuffers are managed automatically by the VDP1 hardware in the
|
||||
* upper half of VRAM when double-buffering is enabled.
|
||||
*
|
||||
* VDP2 CRAM (4KB) holds palettes:
|
||||
* Each 256-color palette occupies 512 bytes (256 × 2-byte RGB1555 entries).
|
||||
* We reserve one palette slot per texture handle (up to SAT_PALETTE_MAX).
|
||||
*/
|
||||
|
||||
/* ---- Limits -------------------------------------------------------------- */
|
||||
|
||||
#define SAT_RTEXTURE_MAX 128
|
||||
#define SAT_CMDT_MAX 1024
|
||||
#define SAT_TEXTURE_VRAM_BASE 0x010000u /* byte offset in VDP1 VRAM */
|
||||
#define SAT_TEXTURE_VRAM_SIZE (0x200000u - SAT_TEXTURE_VRAM_BASE)
|
||||
|
||||
/* ---- VDP1 command table entry (hardware layout, 32 bytes) ---------------- */
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint16_t ctrl; /* Command type and draw flags */
|
||||
uint16_t link; /* Link to next command (entry index) */
|
||||
uint16_t pmod; /* Draw mode: color mode, mesh, pre-clip, etc. */
|
||||
uint16_t colr; /* Palette base address in CRAM (word units /8) */
|
||||
uint16_t srca; /* Texture source address in VDP1 VRAM (/8) */
|
||||
uint16_t size; /* Texture size: ((w/8) << 8) | h */
|
||||
int16_t xa, ya; /* Vertex A (top-left for normal sprite) */
|
||||
int16_t xb, yb; /* Vertex B (bottom-right / or second vertex) */
|
||||
int16_t xc, yc; /* Vertex C (distorted sprite only) */
|
||||
int16_t xd, yd; /* Vertex D (distorted sprite only) */
|
||||
uint16_t grda; /* Gouraud shading address (unused = 0) */
|
||||
uint16_t _pad;
|
||||
} satcmd_t;
|
||||
|
||||
_Static_assert(sizeof(satcmd_t) == 32, "satcmd_t must be 32 bytes");
|
||||
|
||||
/* CTRL command type bits (bits 2:0) */
|
||||
#define SATCMD_CTRL_NORMAL_SPRITE (0x0000u) /* aligned rect */
|
||||
#define SATCMD_CTRL_SCALED_SPRITE (0x0001u)
|
||||
#define SATCMD_CTRL_DISTORTED_SPRITE (0x0002u) /* arbitrary quad */
|
||||
#define SATCMD_CTRL_POLYGON (0x0004u) /* solid polygon */
|
||||
#define SATCMD_CTRL_SYSCLIP (0x0009u) /* system clipping */
|
||||
#define SATCMD_CTRL_END (0x8000u) /* end of list */
|
||||
|
||||
/* PMOD draw mode */
|
||||
#define SATCMD_PMOD_TRANS (0x0000u) /* transparent pixel 0 */
|
||||
#define SATCMD_PMOD_8BPP_CBANK (0x0038u) /* 256-color, color bank */
|
||||
#define SATCMD_PMOD_ECD (0x0080u) /* extend color depth */
|
||||
#define SATCMD_PMOD_SPD (0x0040u) /* do not skip index 0 */
|
||||
|
||||
/* ---- Texture table ------------------------------------------------------- */
|
||||
|
||||
typedef struct {
|
||||
uint8_t *cpuIndices; /* w*h source indices */
|
||||
color_t palette[256];
|
||||
uint16_t w, h;
|
||||
uint32_t vramByteOffset; /* byte offset into VDP1 VRAM pool */
|
||||
uint16_t cramWordOffset; /* word offset into VDP2 CRAM for palette */
|
||||
} sattexentry_t;
|
||||
|
||||
static sattexentry_t satTexTable[SAT_RTEXTURE_MAX];
|
||||
static uint16_t satTexNext = 1; /* 0 = white fallback */
|
||||
static uint32_t satTexVramUsed = 0;
|
||||
static uint16_t satTexCramUsed = 0; /* in 256-entry slots */
|
||||
|
||||
/* ---- Command table buffer ------------------------------------------------ */
|
||||
|
||||
static satcmd_t satCmdBuf[SAT_CMDT_MAX];
|
||||
static uint16_t satCmdCount;
|
||||
|
||||
/* ---- Projection state ---------------------------------------------------- */
|
||||
|
||||
static float satFovY = 0.0f; /* 0 = ortho */
|
||||
static float satAspect = 1.0f;
|
||||
static float satNearZ = 1.0f;
|
||||
static float satFarZ = 1000.0f;
|
||||
|
||||
static float satViewEyeX = 0.0f, satViewEyeY = 0.0f, satViewEyeZ = 1.0f;
|
||||
static float satViewTgtX = 0.0f, satViewTgtY = 0.0f, satViewTgtZ = 0.0f;
|
||||
|
||||
/* ---- Helpers ------------------------------------------------------------- */
|
||||
|
||||
/* Convert color_t RGBA → VDP2 CRAM RGB1555 (1 MSB unused, RGB555). */
|
||||
static uint16_t toRGB1555(color_t c) {
|
||||
return (uint16_t)(
|
||||
((uint16_t)(c.b >> 3) << 10) |
|
||||
((uint16_t)(c.g >> 3) << 5) |
|
||||
((uint16_t)(c.r >> 3))
|
||||
);
|
||||
}
|
||||
|
||||
/* Write palette into VDP2 CRAM at the texture's slot.
|
||||
* CRAM is mapped at 0x25F00000 (Saturn memory map).
|
||||
* Each palette slot is 512 bytes = 256 × uint16_t. */
|
||||
static void uploadPalette(sattexentry_t *e) {
|
||||
volatile uint16_t *cram = (volatile uint16_t *)0x25F00000;
|
||||
uint32_t base = (uint32_t)e->cramWordOffset * 256u;
|
||||
for(uint32_t i = 0; i < 256; i++) {
|
||||
cram[base + i] = toRGB1555(e->palette[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy indices row-by-row into VDP1 VRAM.
|
||||
* VDP1 VRAM is at 0x05C00000. Textures must be stored starting on an
|
||||
* 8-byte boundary; we keep our pool 8-byte aligned already. */
|
||||
static void uploadIndices(sattexentry_t *e) {
|
||||
volatile uint8_t *vram =
|
||||
(volatile uint8_t *)(0x05C00000u + SAT_TEXTURE_VRAM_BASE + e->vramByteOffset);
|
||||
uint32_t total = (uint32_t)e->w * e->h;
|
||||
for(uint32_t i = 0; i < total; i++) {
|
||||
vram[i] = e->cpuIndices[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Return a fresh command slot or NULL if full. */
|
||||
static satcmd_t *allocCmd(void) {
|
||||
if(satCmdCount >= SAT_CMDT_MAX) return NULL;
|
||||
satcmd_t *c = &satCmdBuf[satCmdCount++];
|
||||
memoryZero(c, sizeof(satcmd_t));
|
||||
return c;
|
||||
}
|
||||
|
||||
/* ---- Init ---------------------------------------------------------------- */
|
||||
|
||||
errorret_t renderSaturnInit(void) {
|
||||
logDebug("[Saturn] renderSaturnInit\n");
|
||||
memoryZero(satTexTable, sizeof(satTexTable));
|
||||
satTexNext = 1;
|
||||
satTexVramUsed = 0;
|
||||
satTexCramUsed = 0;
|
||||
satCmdCount = 0;
|
||||
|
||||
/* White 1×1 fallback: slot 0 */
|
||||
sattexentry_t *e = &satTexTable[0];
|
||||
e->cpuIndices = (uint8_t *)malloc(1);
|
||||
assertNotNull(e->cpuIndices, "Saturn: failed to allocate fallback index buffer");
|
||||
e->cpuIndices[0] = 0;
|
||||
memoryZero(e->palette, 256 * sizeof(color_t));
|
||||
e->palette[0] = COLOR_WHITE;
|
||||
e->w = 1; e->h = 1;
|
||||
e->vramByteOffset = satTexVramUsed;
|
||||
e->cramWordOffset = satTexCramUsed;
|
||||
satTexVramUsed += 8; /* 8-byte minimum alignment */
|
||||
satTexCramUsed++;
|
||||
uploadIndices(e);
|
||||
uploadPalette(e);
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
/* ---- Texture ------------------------------------------------------------- */
|
||||
|
||||
rtexture_t renderSaturnTextureCreate(
|
||||
uint16_t w, uint16_t h,
|
||||
const uint8_t *indices, const color_t *palette
|
||||
) {
|
||||
assertTrue(satTexNext < SAT_RTEXTURE_MAX, "Saturn texture table full");
|
||||
|
||||
uint32_t byteCount = (uint32_t)w * h;
|
||||
/* Round up to 8-byte boundary for SRCA alignment. */
|
||||
uint32_t vramBytes = (byteCount + 7u) & ~7u;
|
||||
assertTrue(
|
||||
satTexVramUsed + vramBytes <= SAT_TEXTURE_VRAM_SIZE,
|
||||
"Saturn VDP1 texture VRAM exhausted"
|
||||
);
|
||||
|
||||
rtexture_t handle = (rtexture_t)satTexNext++;
|
||||
sattexentry_t *e = &satTexTable[handle];
|
||||
|
||||
e->cpuIndices = (uint8_t *)malloc(byteCount);
|
||||
assertNotNull(e->cpuIndices, "Saturn: failed to allocate cpu index buffer");
|
||||
memoryCopy(e->cpuIndices, indices, byteCount);
|
||||
memoryCopy(e->palette, palette, 256 * sizeof(color_t));
|
||||
e->w = w; e->h = h;
|
||||
e->vramByteOffset = satTexVramUsed;
|
||||
e->cramWordOffset = satTexCramUsed;
|
||||
|
||||
satTexVramUsed += vramBytes;
|
||||
satTexCramUsed++;
|
||||
|
||||
uploadIndices(e);
|
||||
uploadPalette(e);
|
||||
return handle;
|
||||
}
|
||||
|
||||
void renderSaturnTextureDispose(rtexture_t tex) {
|
||||
if(tex == RTEXTURE_NONE || tex >= SAT_RTEXTURE_MAX) return;
|
||||
sattexentry_t *e = &satTexTable[tex];
|
||||
if(e->cpuIndices) { free(e->cpuIndices); e->cpuIndices = NULL; }
|
||||
}
|
||||
|
||||
color_t *renderSaturnTextureGetPalette(rtexture_t tex) {
|
||||
if(tex == RTEXTURE_NONE || tex >= SAT_RTEXTURE_MAX) return NULL;
|
||||
return satTexTable[tex].palette;
|
||||
}
|
||||
|
||||
uint8_t *renderSaturnTextureGetIndices(rtexture_t tex) {
|
||||
if(tex == RTEXTURE_NONE || tex >= SAT_RTEXTURE_MAX) return NULL;
|
||||
return satTexTable[tex].cpuIndices;
|
||||
}
|
||||
|
||||
/* ---- Flush --------------------------------------------------------------- */
|
||||
|
||||
/* Fill in the SRCA/CMDSIZE/CMDCOLR fields from a texture handle. */
|
||||
static void cmdSetTexture(satcmd_t *cmd, rtexture_t tex) {
|
||||
sattexentry_t *e = (tex < SAT_RTEXTURE_MAX && satTexTable[tex].cpuIndices)
|
||||
? &satTexTable[tex]
|
||||
: &satTexTable[0];
|
||||
|
||||
/* SRCA = byte offset from VDP1 VRAM base / 8. */
|
||||
uint32_t srcByteAddr = SAT_TEXTURE_VRAM_BASE + e->vramByteOffset;
|
||||
cmd->srca = (uint16_t)(srcByteAddr / 8u);
|
||||
|
||||
/* SIZE = ((width/8) << 8) | height (each axis limited to 0-255 after /8). */
|
||||
cmd->size = (uint16_t)(((e->w / 8u) << 8) | e->h);
|
||||
|
||||
/* COLR = CRAM word address of palette / 16 (256-color bank mode).
|
||||
* Each 256-entry slot is 512 bytes = 256 words. Word offset / 16. */
|
||||
uint32_t cramWordBase = (uint32_t)e->cramWordOffset * 256u;
|
||||
cmd->colr = (uint16_t)(cramWordBase / 16u);
|
||||
|
||||
cmd->pmod = SATCMD_PMOD_8BPP_CBANK; /* 256-color, index 0 transparent */
|
||||
}
|
||||
|
||||
static void flush2DSprite(const ropsprite_t *s) {
|
||||
satcmd_t *cmd = allocCmd();
|
||||
if(!cmd) return;
|
||||
|
||||
sattexentry_t *e = (s->texture < SAT_RTEXTURE_MAX && satTexTable[s->texture].cpuIndices)
|
||||
? &satTexTable[s->texture]
|
||||
: &satTexTable[0];
|
||||
|
||||
cmd->ctrl = SATCMD_CTRL_NORMAL_SPRITE;
|
||||
cmd->link = 0;
|
||||
cmdSetTexture(cmd, s->texture);
|
||||
|
||||
/* VDP1 normal sprite: XA/YA = top-left, XB/YB = size (w-1, h-1). */
|
||||
cmd->xa = (int16_t)s->x;
|
||||
cmd->ya = (int16_t)s->y;
|
||||
cmd->xb = (int16_t)(s->w > 0 ? s->w - 1 : 0);
|
||||
cmd->yb = (int16_t)(s->h > 0 ? s->h - 1 : 0);
|
||||
|
||||
/* UV sub-region: the VDP1 always draws the full texture, so to support
|
||||
* sprite atlases we would need a clipped intermediate. For now we treat
|
||||
* the full texture as the sprite frame (atlas sub-rect is a TODO). */
|
||||
(void)e; /* suppress unused warning for e->w/h if UV clipping is added */
|
||||
}
|
||||
|
||||
/*
|
||||
* Project a 3D world-space point onto the VDP1 2D screen.
|
||||
* Uses a simple perspective divide; view/projection state is kept CPU-side.
|
||||
*/
|
||||
static void project(
|
||||
float wx, float wy, float wz,
|
||||
float *sx, float *sy
|
||||
) {
|
||||
/* Translate relative to eye. */
|
||||
float rx = wx - satViewEyeX;
|
||||
float ry = wy - satViewEyeY;
|
||||
float rz = wz - satViewEyeZ;
|
||||
|
||||
/* Rotate view to look at target (approximated: no full matrix here). */
|
||||
/* TODO: replace with a proper view-matrix multiply for non-axis-aligned cameras. */
|
||||
float fwd_z = satViewTgtZ - satViewEyeZ;
|
||||
(void)fwd_z; /* simple pass-through for now */
|
||||
|
||||
float half_w = (float)DUSK_DISPLAY_WIDTH * 0.5f;
|
||||
float half_h = (float)DUSK_DISPLAY_HEIGHT * 0.5f;
|
||||
|
||||
if(satFovY > 0.0f && rz != 0.0f) {
|
||||
float focal = half_h / (satFovY * 0.5f);
|
||||
*sx = half_w + (rx / rz) * focal;
|
||||
*sy = half_h - (ry / rz) * focal;
|
||||
} else {
|
||||
*sx = half_w + rx;
|
||||
*sy = half_h - ry;
|
||||
}
|
||||
}
|
||||
|
||||
static void flush3DQuad(const ropquad3d_t *q) {
|
||||
satcmd_t *cmd = allocCmd();
|
||||
if(!cmd) return;
|
||||
|
||||
cmd->ctrl = SATCMD_CTRL_DISTORTED_SPRITE;
|
||||
cmd->link = 0;
|
||||
cmdSetTexture(cmd, q->texture);
|
||||
|
||||
float cx = (float)q->cx, cy = (float)q->cy, cz = (float)q->cz;
|
||||
float rx = (float)q->rx, ry = (float)q->ry, rz = (float)q->rz;
|
||||
float ux = (float)q->ux, uy = (float)q->uy, uz = (float)q->uz;
|
||||
|
||||
/* Corners: TL = center - right + up, etc. */
|
||||
float tlx = cx-rx+ux, tly = cy-ry+uy, tlz = cz-rz+uz;
|
||||
float trx = cx+rx+ux, try_ = cy+ry+uy, trz = cz+rz+uz;
|
||||
float blx = cx-rx-ux, bly = cy-ry-uy, blz = cz-rz-uz;
|
||||
float brx = cx+rx-ux, bry = cy+ry-uy, brz = cz+rz-uz;
|
||||
|
||||
float sxa, sya, sxb, syb, sxc, syc, sxd, syd;
|
||||
project(tlx, tly, tlz, &sxa, &sya);
|
||||
project(trx, try_, trz, &sxb, &syb);
|
||||
project(brx, bry, brz, &sxc, &syc);
|
||||
project(blx, bly, blz, &sxd, &syd);
|
||||
|
||||
cmd->xa = (int16_t)sxa; cmd->ya = (int16_t)sya;
|
||||
cmd->xb = (int16_t)sxb; cmd->yb = (int16_t)syb;
|
||||
cmd->xc = (int16_t)sxc; cmd->yc = (int16_t)syc;
|
||||
cmd->xd = (int16_t)sxd; cmd->yd = (int16_t)syd;
|
||||
}
|
||||
|
||||
/* Submit the finished command table to VDP1 VRAM and trigger rendering. */
|
||||
static void submitCmdTable(void) {
|
||||
/* Append end-of-list sentinel. */
|
||||
if(satCmdCount < SAT_CMDT_MAX) {
|
||||
satcmd_t *end = &satCmdBuf[satCmdCount];
|
||||
memoryZero(end, sizeof(satcmd_t));
|
||||
end->ctrl = SATCMD_CTRL_END;
|
||||
}
|
||||
|
||||
/* DMA or CPU-copy the command table to VDP1 VRAM at offset 0x000000.
|
||||
* VDP1 VRAM starts at 0x05C00000 in the Saturn memory map. */
|
||||
volatile satcmd_t *vdp1CmdTable = (volatile satcmd_t *)0x05C00000u;
|
||||
uint32_t count = satCmdCount + 1u; /* include the END entry */
|
||||
for(uint32_t i = 0; i < count; i++) {
|
||||
vdp1CmdTable[i] = satCmdBuf[i];
|
||||
}
|
||||
|
||||
/* Set VDP1 command table top address to 0x000000 (the default). */
|
||||
volatile uint16_t *vdp1Regs = (volatile uint16_t *)0x25D00000u;
|
||||
/* VDP1 MODR (Mode Register) — ensure draw mode is correct */
|
||||
vdp1Regs[0] = 0x0000; /* PTMR: plot trigger — VDP1 draws on frame change */
|
||||
/* EWDR, EWLR, EWRR — Erase/Write window (full screen) */
|
||||
vdp1Regs[2] = 0x0000; /* EWDR: erase write data (black) */
|
||||
vdp1Regs[3] = 0x0000; /* EWLR: top-left (0,0) */
|
||||
vdp1Regs[4] = (uint16_t)(((DUSK_DISPLAY_HEIGHT - 1) << 9) |
|
||||
((DUSK_DISPLAY_WIDTH / 2) - 1)); /* EWRR */
|
||||
}
|
||||
|
||||
errorret_t renderSaturnFlush(ropbuffer_t *buf) {
|
||||
logDebug("[Saturn] renderSaturnFlush: count=%u\n", (unsigned)buf->count);
|
||||
|
||||
satCmdCount = 0;
|
||||
|
||||
uint32_t offset = 0;
|
||||
while(offset < buf->byteCount) {
|
||||
const ropheader_t *hdr = (const ropheader_t *)(buf->data + offset);
|
||||
ropop_t op = (ropop_t)hdr->op;
|
||||
|
||||
switch(op) {
|
||||
case ROP_CLEAR: {
|
||||
const ropclear_t *c = (const ropclear_t *)hdr;
|
||||
/* Set VDP2 back-screen color. */
|
||||
volatile uint16_t *cram = (volatile uint16_t *)0x25F00000u;
|
||||
cram[0] = toRGB1555(c->color);
|
||||
|
||||
/* Issue a VDP1 system clipping command to reset the clip window. */
|
||||
satcmd_t *clip = allocCmd();
|
||||
if(clip) {
|
||||
clip->ctrl = SATCMD_CTRL_SYSCLIP;
|
||||
clip->link = 0;
|
||||
clip->xa = 0;
|
||||
clip->ya = 0;
|
||||
clip->xb = (int16_t)(DUSK_DISPLAY_WIDTH - 1);
|
||||
clip->yb = (int16_t)(DUSK_DISPLAY_HEIGHT - 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ROP_DRAW_SPRITE:
|
||||
flush2DSprite((const ropsprite_t *)hdr);
|
||||
break;
|
||||
|
||||
case ROP_SET_PROJECTION: {
|
||||
const ropprojection_t *p = (const ropprojection_t *)hdr;
|
||||
satFovY = fixedToFloat(p->fovY);
|
||||
satAspect = fixedToFloat(p->aspect);
|
||||
satNearZ = fixedToFloat(p->nearZ);
|
||||
satFarZ = fixedToFloat(p->farZ);
|
||||
break;
|
||||
}
|
||||
|
||||
case ROP_SET_VIEW: {
|
||||
const ropview_t *v = (const ropview_t *)hdr;
|
||||
satViewEyeX = (float)v->eyeX;
|
||||
satViewEyeY = (float)v->eyeY;
|
||||
satViewEyeZ = (float)v->eyeZ;
|
||||
satViewTgtX = (float)v->tgtX;
|
||||
satViewTgtY = (float)v->tgtY;
|
||||
satViewTgtZ = (float)v->tgtZ;
|
||||
break;
|
||||
}
|
||||
|
||||
case ROP_DRAW_QUAD_3D:
|
||||
flush3DQuad((const ropquad3d_t *)hdr);
|
||||
break;
|
||||
|
||||
case ROP_DRAW_TILEMAP_CHUNK:
|
||||
/* TODO: Saturn tilemap chunks drive VDP2 scroll plane registers.
|
||||
* For now we fall through and emit nothing; a proper implementation
|
||||
* writes tile indices to VDP2 VRAM and sets scroll offsets. */
|
||||
break;
|
||||
|
||||
default:
|
||||
logDebug("[Saturn] unknown ROP op=%u\n", (unsigned)op);
|
||||
break;
|
||||
}
|
||||
|
||||
offset += ropOpSize(op);
|
||||
}
|
||||
|
||||
submitCmdTable();
|
||||
errorOk();
|
||||
}
|
||||
|
||||
/* ---- Dispose ------------------------------------------------------------- */
|
||||
|
||||
void renderSaturnDispose(void) {
|
||||
for(uint16_t i = 0; i < satTexNext; i++) {
|
||||
sattexentry_t *e = &satTexTable[i];
|
||||
if(e->cpuIndices) { free(e->cpuIndices); e->cpuIndices = NULL; }
|
||||
}
|
||||
satTexNext = 1;
|
||||
satTexVramUsed = 0;
|
||||
satTexCramUsed = 0;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "display/render/ropbuffer.h"
|
||||
#include "display/render/rtexture.h"
|
||||
#include "display/color.h"
|
||||
|
||||
errorret_t renderSaturnInit(void);
|
||||
errorret_t renderSaturnFlush(ropbuffer_t *buf);
|
||||
void renderSaturnDispose(void);
|
||||
|
||||
rtexture_t renderSaturnTextureCreate(
|
||||
uint16_t w, uint16_t h,
|
||||
const uint8_t *indices, const color_t *palette
|
||||
);
|
||||
void renderSaturnTextureDispose(rtexture_t tex);
|
||||
color_t *renderSaturnTextureGetPalette(rtexture_t tex);
|
||||
uint8_t *renderSaturnTextureGetIndices(rtexture_t tex);
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <yaul.h>
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_BINARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}/inputsat.c
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "input/inputsat.h"
|
||||
|
||||
#define inputInitPlatform inputInitSaturn
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "input/input.h"
|
||||
#include <smpc/peripheral.h>
|
||||
|
||||
/*
|
||||
* Saturn standard digital pad buttons (smpc_peripheral_digital_t).
|
||||
* Yaul exposes them via smpc_peripheral_digital_port() and
|
||||
* smpc_peripheral_digital_get().
|
||||
*
|
||||
* Button bitmask in the SMPC peripheral data word:
|
||||
* bit 11 = Right bit 10 = Left bit 9 = Down bit 8 = Up
|
||||
* bit 7 = Start bit 6 = A bit 5 = C bit 4 = B
|
||||
* bit 3 = R bit 2 = X bit 1 = Y bit 0 = Z
|
||||
*
|
||||
* We use Yaul's SMPC_PERIPHERAL_DIGITAL_* macros where available.
|
||||
*/
|
||||
|
||||
inputbuttondata_t INPUT_BUTTON_DATA[] = {
|
||||
{ .name = "a", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 6 } },
|
||||
{ .name = "b", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 4 } },
|
||||
{ .name = "c", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 5 } },
|
||||
{ .name = "x", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 2 } },
|
||||
{ .name = "y", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 1 } },
|
||||
{ .name = "z", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 0 } },
|
||||
{ .name = "start", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 7 } },
|
||||
{ .name = "up", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 8 } },
|
||||
{ .name = "down", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 9 } },
|
||||
{ .name = "left", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 10 } },
|
||||
{ .name = "right", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 11 } },
|
||||
{ .name = "l", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 15 } },
|
||||
{ .name = "r", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 3 } },
|
||||
{ .name = "accept", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 6 } }, /* A */
|
||||
{ .name = "cancel", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 4 } }, /* B */
|
||||
{ .name = NULL }
|
||||
};
|
||||
|
||||
errorret_t inputInitSaturn(void) {
|
||||
#define X(buttonName, buttonAction) \
|
||||
inputBind(inputButtonGetByName(buttonName), buttonAction);
|
||||
X("up", INPUT_ACTION_UP);
|
||||
X("down", INPUT_ACTION_DOWN);
|
||||
X("left", INPUT_ACTION_LEFT);
|
||||
X("right", INPUT_ACTION_RIGHT);
|
||||
X("accept", INPUT_ACTION_ACCEPT);
|
||||
X("cancel", INPUT_ACTION_CANCEL);
|
||||
X("start", INPUT_ACTION_RAGEQUIT);
|
||||
#undef X
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
|
||||
errorret_t inputInitSaturn(void);
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_BINARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}/log.c
|
||||
)
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "log/log.h"
|
||||
#include <stdio.h>
|
||||
|
||||
/*
|
||||
* On Saturn, stdout goes to the debug serial port (via Yaul's dbgio module).
|
||||
* With a comm link or emulator (Mednafen, SSF) this is visible on the host.
|
||||
*
|
||||
* TODO: add dbgio_init() in systemSaturnInit() and replace vprintf with
|
||||
* dbgio_printf() for hardware-accurate serial output.
|
||||
*/
|
||||
|
||||
void logDebug(const char_t *message, ...) {
|
||||
va_list args;
|
||||
va_start(args, message);
|
||||
vprintf(message, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void logError(const char_t *message, ...) {
|
||||
va_list args;
|
||||
va_start(args, message);
|
||||
vfprintf(stderr, message, args);
|
||||
va_end(args);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_BINARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}/networksat.c
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "networksat.h"
|
||||
|
||||
#define networkPlatformInit networkSaturnInit
|
||||
#define networkPlatformUpdate networkSaturnUpdate
|
||||
#define networkPlatformDispose networkSaturnDispose
|
||||
#define networkPlatformIsConnected networkSaturnIsConnected
|
||||
#define networkPlatformRequestConnection networkSaturnRequestConnection
|
||||
#define networkPlatformRequestDisconnection networkSaturnRequestDisconnection
|
||||
#define networkPlatformGetInfo networkSaturnGetInfo
|
||||
|
||||
typedef networksat_t networkplatform_t;
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "network/network.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
errorret_t networkSaturnInit(void) { errorOk(); }
|
||||
errorret_t networkSaturnUpdate(void) { errorOk(); }
|
||||
errorret_t networkSaturnDispose(void) { errorOk(); }
|
||||
|
||||
bool_t networkSaturnIsConnected(void) { return false; }
|
||||
|
||||
void networkSaturnRequestConnection(
|
||||
void (*onConnected)(void *user),
|
||||
void (*onFailed)(errorret_t error, void *user),
|
||||
void (*onDisconnect)(errorret_t error, void *user),
|
||||
void *user
|
||||
) {
|
||||
(void)onConnected; (void)onDisconnect; (void)user;
|
||||
errorret_t err = errorThrowImpl(
|
||||
NULL, ERROR_NOT_OK,
|
||||
__FILE__, __func__, __LINE__,
|
||||
"Network not supported on Saturn"
|
||||
);
|
||||
if(onFailed) onFailed(err, user);
|
||||
}
|
||||
|
||||
void networkSaturnRequestDisconnection(
|
||||
void (*onComplete)(void *user),
|
||||
void *user
|
||||
) {
|
||||
if(onComplete) onComplete(user);
|
||||
}
|
||||
|
||||
networkinfo_t networkSaturnGetInfo(void) {
|
||||
networkinfo_t info;
|
||||
memoryZero(&info, sizeof(info));
|
||||
return info;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "network/networkinfo.h"
|
||||
|
||||
/*
|
||||
* Saturn networking is not supported (the NetLink modem cartridge is too
|
||||
* rare to target). All functions are no-ops; network-dependent features
|
||||
* will gracefully degrade via the existing NETWORK_STATE_DISCONNECTED path.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
uint8_t unused;
|
||||
} networksat_t;
|
||||
|
||||
errorret_t networkSaturnInit(void);
|
||||
errorret_t networkSaturnUpdate(void);
|
||||
errorret_t networkSaturnDispose(void);
|
||||
bool_t networkSaturnIsConnected(void);
|
||||
void networkSaturnRequestConnection(
|
||||
void (*onConnected)(void *user),
|
||||
void (*onFailed)(errorret_t error, void *user),
|
||||
void (*onDisconnect)(errorret_t error, void *user),
|
||||
void *user
|
||||
);
|
||||
void networkSaturnRequestDisconnection(
|
||||
void (*onComplete)(void *user),
|
||||
void *user
|
||||
);
|
||||
networkinfo_t networkSaturnGetInfo(void);
|
||||
@@ -0,0 +1,10 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_BINARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}/savesat.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/savestreamsat.c
|
||||
)
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "save/savesat.h"
|
||||
#include "save/savestreamsat.h"
|
||||
|
||||
typedef savesat_t saveplatform_t;
|
||||
typedef savestreamsat_t saveplatformstream_t;
|
||||
|
||||
#define saveInitPlatform saveInitSaturn
|
||||
#define saveDisposePlatform saveDisposeSaturn
|
||||
#define saveDeletePlatform saveDeleteSaturn
|
||||
|
||||
#define saveStreamOpenReadPlatform(stream, slot) \
|
||||
saveStreamOpenReadSaturn(&(stream)->platform, &(stream)->found, slot)
|
||||
#define saveStreamOpenWritePlatform(stream, slot) \
|
||||
saveStreamOpenWriteSaturn(&(stream)->platform, slot)
|
||||
#define saveStreamClosePlatform(stream) \
|
||||
saveStreamCloseSaturn(&(stream)->platform)
|
||||
#define saveStreamReadBytesPlatform(stream, buf, len) \
|
||||
saveStreamReadBytesSaturn(&(stream)->platform, buf, len)
|
||||
#define saveStreamWriteBytesPlatform(stream, buf, len) \
|
||||
saveStreamWriteBytesSaturn(&(stream)->platform, buf, len)
|
||||
#define saveStreamSeekPlatform(stream, pos) \
|
||||
saveStreamSeekSaturn(&(stream)->platform, pos)
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "save/save.h"
|
||||
#include "save/savesat.h"
|
||||
#include "log/log.h"
|
||||
|
||||
/*
|
||||
* TODO: use Yaul's bup_* API for backup RAM access.
|
||||
* Reference: <bup/bup.h> in the Yaul SDK.
|
||||
*
|
||||
* bup_init(BUP_DEV_INTERNAL); // or BUP_DEV_EXTERNAL for cart
|
||||
* bup_stat_t stat;
|
||||
* bup_stat(BUP_DEV_INTERNAL, &stat);
|
||||
*
|
||||
* Write: bup_write(BUP_DEV_INTERNAL, &dir, data, size, BUP_MODE_NEW);
|
||||
* Read: bup_read(BUP_DEV_INTERNAL, filename, data, size);
|
||||
* Del: bup_delete(BUP_DEV_INTERNAL, filename);
|
||||
*/
|
||||
|
||||
errorret_t saveInitSaturn(void) {
|
||||
logDebug("[Saturn] saveInitSaturn\n");
|
||||
/* TODO: bup_init(BUP_DEV_INTERNAL); */
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveDisposeSaturn(void) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveDeleteSaturn(const uint8_t slot) {
|
||||
logDebug("[Saturn] saveDeleteSaturn: slot=%u\n", (unsigned)slot);
|
||||
/* TODO: bup_delete(BUP_DEV_INTERNAL, filename_for_slot(slot)); */
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "save/savefile.h"
|
||||
|
||||
/*
|
||||
* Saturn saves use the internal backup RAM (32KB) via Yaul's bup module.
|
||||
* All saves share the same cartridge/internal device; slot is encoded in
|
||||
* the save file name (e.g. "DUSK00", "DUSK01", …).
|
||||
*/
|
||||
|
||||
#ifndef SAVE_SAT_TITLE_ID
|
||||
#define SAVE_SAT_TITLE_ID "DUSK"
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
uint8_t unused;
|
||||
} savesat_t;
|
||||
|
||||
errorret_t saveInitSaturn(void);
|
||||
errorret_t saveDisposeSaturn(void);
|
||||
errorret_t saveDeleteSaturn(const uint8_t slot);
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "save/save.h"
|
||||
#include "save/savestreamsat.h"
|
||||
#include "util/memory.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* Saturn backup RAM (bup) does not support partial reads/seeks; data must
|
||||
* be read or written as a single contiguous block. We buffer the entire
|
||||
* save slot in heap memory and serialize to/from the bup device on open/close.
|
||||
*
|
||||
* Maximum save size = sizeof(savefile_t). Adjust SAVE_SAT_MAX if needed.
|
||||
*/
|
||||
|
||||
#define SAVE_SAT_MAX sizeof(savefile_t)
|
||||
|
||||
errorret_t saveStreamOpenReadSaturn(
|
||||
savestreamsat_t *p, bool_t *found, const uint8_t slot
|
||||
) {
|
||||
p->buf = (uint8_t *)malloc(SAVE_SAT_MAX);
|
||||
if(!p->buf) errorThrow("Saturn: failed to allocate save read buffer");
|
||||
p->size = SAVE_SAT_MAX;
|
||||
p->pos = 0;
|
||||
p->slot = slot;
|
||||
p->writing = false;
|
||||
|
||||
/*
|
||||
* TODO: read from bup device into p->buf:
|
||||
* int32_t ret = bup_read(BUP_DEV_INTERNAL, filename, p->buf, SAVE_SAT_MAX);
|
||||
* *found = (ret >= 0);
|
||||
*/
|
||||
*found = false; /* stub: always report no save */
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveStreamOpenWriteSaturn(savestreamsat_t *p, const uint8_t slot) {
|
||||
p->buf = (uint8_t *)malloc(SAVE_SAT_MAX);
|
||||
if(!p->buf) errorThrow("Saturn: failed to allocate save write buffer");
|
||||
memoryZero(p->buf, SAVE_SAT_MAX);
|
||||
p->size = SAVE_SAT_MAX;
|
||||
p->pos = 0;
|
||||
p->slot = slot;
|
||||
p->writing = true;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void saveStreamCloseSaturn(savestreamsat_t *p) {
|
||||
if(p->writing && p->buf) {
|
||||
/*
|
||||
* TODO: write p->buf to bup device:
|
||||
* bup_dir_t dir;
|
||||
* bup_write(BUP_DEV_INTERNAL, &dir, p->buf, SAVE_SAT_MAX, BUP_MODE_NEW);
|
||||
*/
|
||||
}
|
||||
if(p->buf) { free(p->buf); p->buf = NULL; }
|
||||
}
|
||||
|
||||
errorret_t saveStreamReadBytesSaturn(
|
||||
savestreamsat_t *p, void *buf, const size_t len
|
||||
) {
|
||||
if(p->pos + len > p->size) errorThrow("Saturn: read past end of save buffer");
|
||||
memoryCopy(buf, p->buf + p->pos, len);
|
||||
p->pos += len;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveStreamWriteBytesSaturn(
|
||||
savestreamsat_t *p, const void *buf, const size_t len
|
||||
) {
|
||||
if(p->pos + len > p->size) errorThrow("Saturn: write past end of save buffer");
|
||||
memoryCopy(p->buf + p->pos, buf, len);
|
||||
p->pos += len;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveStreamSeekSaturn(savestreamsat_t *p, const size_t pos) {
|
||||
if(pos > p->size) errorThrow("Saturn: seek out of bounds");
|
||||
p->pos = pos;
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
uint8_t *buf;
|
||||
size_t size;
|
||||
size_t pos;
|
||||
uint8_t slot;
|
||||
bool_t writing;
|
||||
} savestreamsat_t;
|
||||
|
||||
errorret_t saveStreamOpenReadSaturn(
|
||||
savestreamsat_t *p, bool_t *found, const uint8_t slot
|
||||
);
|
||||
errorret_t saveStreamOpenWriteSaturn(savestreamsat_t *p, const uint8_t slot);
|
||||
void saveStreamCloseSaturn(savestreamsat_t *p);
|
||||
errorret_t saveStreamReadBytesSaturn(
|
||||
savestreamsat_t *p, void *buf, const size_t len
|
||||
);
|
||||
errorret_t saveStreamWriteBytesSaturn(
|
||||
savestreamsat_t *p, const void *buf, const size_t len
|
||||
);
|
||||
errorret_t saveStreamSeekSaturn(savestreamsat_t *p, const size_t pos);
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "moduleplatformsat.h"
|
||||
|
||||
#define modulePlatformPlatform modulePlatformSaturn
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
|
||||
static void modulePlatformSaturn(void) {
|
||||
moduleBaseEval("var SATURN = true;\n");
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_BINARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}/systemsat.c
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "system/systemsat.h"
|
||||
|
||||
#define systemInitPlatform systemInitSaturn
|
||||
#define systemGetActiveDialogTypePlatform systemGetActiveDialogTypeSaturn
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "system/systemsat.h"
|
||||
#include "log/log.h"
|
||||
|
||||
errorret_t systemInitSaturn(void) {
|
||||
logDebug("[Saturn] systemInitSaturn\n");
|
||||
/*
|
||||
* TODO: initialize SMPC peripheral scanning so input reads work.
|
||||
* smpc_peripheral_init();
|
||||
* smpc_peripheral_intback_issue();
|
||||
*/
|
||||
errorOk();
|
||||
}
|
||||
|
||||
systemdialogtype_t systemGetActiveDialogTypeSaturn(void) {
|
||||
return SYSTEM_DIALOG_TYPE_NONE;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "system/system.h"
|
||||
|
||||
errorret_t systemInitSaturn(void);
|
||||
systemdialogtype_t systemGetActiveDialogTypeSaturn(void);
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_BINARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}/timesat.c
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "time/timesat.h"
|
||||
|
||||
#define timeTickPlatform timeTickSaturn
|
||||
#define timeGetDeltaPlatform timeGetDeltaSaturn
|
||||
#define timeGetRealPlatform timeGetRealSaturn
|
||||
#define timeGetRealTimeZonePlatform timeGetRealTimeZoneSaturn
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "time/timesat.h"
|
||||
#include <smpc/smc.h>
|
||||
|
||||
#define SAT_FPS 60.0
|
||||
|
||||
static double_t satTimeLast = 0.0;
|
||||
static double_t satTimeDelta = 0.0;
|
||||
static double_t satTimeAcc = 0.0; /* accumulated seconds (frame counter) */
|
||||
|
||||
void timeTickSaturn(void) {
|
||||
double_t now = satTimeAcc + (1.0 / SAT_FPS);
|
||||
satTimeDelta = now - satTimeAcc;
|
||||
satTimeLast = satTimeAcc;
|
||||
satTimeAcc = now;
|
||||
}
|
||||
|
||||
double_t timeGetDeltaSaturn(void) {
|
||||
return satTimeDelta;
|
||||
}
|
||||
|
||||
double_t timeGetRealSaturn(void) {
|
||||
/*
|
||||
* TODO: read the SMPC RTC for actual wall-clock time:
|
||||
* smpc_rtc_t rtc;
|
||||
* smpc_smc_rtc_read(&rtc);
|
||||
* return rtcToUnixSeconds(&rtc);
|
||||
*/
|
||||
return satTimeAcc;
|
||||
}
|
||||
|
||||
double_t timeGetRealTimeZoneSaturn(void) {
|
||||
/* Saturn RTC stores local time; timezone offset is not available. */
|
||||
return 0.0;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
|
||||
/*
|
||||
* Time is tracked via a frame counter (60fps assumed for NTSC).
|
||||
* The SMPC RTC provides calendar time via smpc_rtc_t.
|
||||
*/
|
||||
|
||||
void timeTickSaturn(void);
|
||||
double_t timeGetDeltaSaturn(void);
|
||||
double_t timeGetRealSaturn(void);
|
||||
double_t timeGetRealTimeZoneSaturn(void);
|
||||
Reference in New Issue
Block a user