Some progress

This commit is contained in:
2026-06-19 14:24:09 -05:00
parent 4e491d8332
commit 1f67e817ae
55 changed files with 2033 additions and 12 deletions
+106
View File
@@ -0,0 +1,106 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Resolve YAUL_INSTALL_ROOT (already set by the toolchain file, but guard
# in case cmake/targets/ is loaded standalone for IDE tooling).
if(NOT DEFINED YAUL_INSTALL_ROOT)
if(DEFINED ENV{YAUL_INSTALL_ROOT})
set(YAUL_INSTALL_ROOT "$ENV{YAUL_INSTALL_ROOT}")
else()
set(YAUL_INSTALL_ROOT "/opt/yaul")
endif()
endif()
# Yaul installs headers/libs under the arch-prefix sysroot subdirectory:
# ${YAUL_INSTALL_ROOT}/sh2eb-elf/include/
# ${YAUL_INSTALL_ROOT}/sh2eb-elf/lib/
# Cross-compiled zlib and libzip are installed to the same sysroot.
set(_YAUL_SYSROOT "${YAUL_INSTALL_ROOT}/sh2eb-elf")
set(_YAUL_BIN "${YAUL_INSTALL_ROOT}/bin")
# ---------------------------------------------------------------------------
# Bypass system find_package calls for libraries we cross-compile into the
# sh2eb-elf sysroot and install into ${_YAUL_SYSROOT}.
# ---------------------------------------------------------------------------
# zlib
if(NOT TARGET ZLIB::ZLIB)
add_library(ZLIB::ZLIB INTERFACE IMPORTED GLOBAL)
set_target_properties(ZLIB::ZLIB PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${_YAUL_SYSROOT}/include"
INTERFACE_LINK_LIBRARIES "${_YAUL_SYSROOT}/lib/libz.a"
)
endif()
set(ZLIB_FOUND TRUE CACHE BOOL "" FORCE)
set(ZLIB_INCLUDE_DIR "${_YAUL_SYSROOT}/include" CACHE PATH "" FORCE)
set(ZLIB_LIBRARY "${_YAUL_SYSROOT}/lib/libz.a" CACHE FILEPATH "" FORCE)
# libzip — pre-installed into the sh2eb-elf sysroot; skip Findlibzip.cmake.
set(libzip_FOUND TRUE CACHE BOOL "libzip found (Saturn sysroot)" FORCE)
find_path(_sat_zip_inc NAMES zip.h
PATHS "${_YAUL_SYSROOT}/include"
NO_DEFAULT_PATH
)
if(_sat_zip_inc)
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE "${_sat_zip_inc}")
endif()
# ---------------------------------------------------------------------------
# Compile definitions
# ---------------------------------------------------------------------------
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_SATURN
DUSK_INPUT_GAMEPAD
DUSK_PLATFORM_ENDIAN_BIG
DUSK_DISPLAY_WIDTH=320
DUSK_DISPLAY_HEIGHT=224
DUSK_THREAD_NONE
)
# ---------------------------------------------------------------------------
# Compile options
# ---------------------------------------------------------------------------
target_compile_options(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
-m2 -mb
-fno-builtin
-fomit-frame-pointer
-w
)
# ---------------------------------------------------------------------------
# Include paths
# ---------------------------------------------------------------------------
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
"${_YAUL_SYSROOT}/include"
"${_YAUL_SYSROOT}/include/yaul"
)
# ---------------------------------------------------------------------------
# Link libraries
# ---------------------------------------------------------------------------
target_link_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
"${_YAUL_SYSROOT}/lib"
)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
"${_YAUL_SYSROOT}/lib/libyaul.a"
"${_YAUL_SYSROOT}/lib/libzip.a"
"${_YAUL_SYSROOT}/lib/libz.a"
m
)
# ---------------------------------------------------------------------------
# Post-build: ELF → binary image
# sh2eb-elf-objcopy converts the ELF to a flat binary that IP.BIN loads
# into Saturn Work RAM.
# ---------------------------------------------------------------------------
set(DUSK_SAT_BIN "${CMAKE_BINARY_DIR}/Dusk.bin")
add_custom_command(TARGET ${DUSK_BINARY_TARGET_NAME} POST_BUILD
COMMAND "${_YAUL_BIN}/sh2eb-elf-objcopy"
-O binary
"$<TARGET_FILE:${DUSK_BINARY_TARGET_NAME}>"
"${DUSK_SAT_BIN}"
COMMENT "Converting ${DUSK_BINARY_TARGET_NAME} ELF → ${DUSK_SAT_BIN}"
)
+59
View File
@@ -0,0 +1,59 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
#
# CMake toolchain file for Sega Saturn (Hitachi SH-2, big-endian)
# using the Yaul homebrew SDK. Set YAUL_INSTALL_ROOT or the
# YAUL_INSTALL_ROOT environment variable before invoking cmake.
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR sh2)
# Resolve Yaul install root
if(NOT DEFINED YAUL_INSTALL_ROOT)
if(DEFINED ENV{YAUL_INSTALL_ROOT})
set(YAUL_INSTALL_ROOT "$ENV{YAUL_INSTALL_ROOT}"
CACHE PATH "Yaul SDK root" FORCE)
else()
set(YAUL_INSTALL_ROOT "/opt/yaul"
CACHE PATH "Yaul SDK root" FORCE)
endif()
endif()
# Yaul SH-2 cross-compiler prefix is sh2eb-elf (big-endian SH-2 ELF).
# Binaries live in ${YAUL_INSTALL_ROOT}/bin/; headers/libs under
# ${YAUL_INSTALL_ROOT}/sh2eb-elf/{include,lib}/.
set(_YAUL_BIN "${YAUL_INSTALL_ROOT}/bin")
set(CMAKE_C_COMPILER "${_YAUL_BIN}/sh2eb-elf-gcc")
set(CMAKE_CXX_COMPILER "${_YAUL_BIN}/sh2eb-elf-g++")
set(CMAKE_AR "${_YAUL_BIN}/sh2eb-elf-ar")
set(CMAKE_RANLIB "${_YAUL_BIN}/sh2eb-elf-ranlib")
set(CMAKE_STRIP "${_YAUL_BIN}/sh2eb-elf-strip")
set(CMAKE_OBJCOPY "${_YAUL_BIN}/sh2eb-elf-objcopy")
set(CMAKE_LINKER "${_YAUL_BIN}/sh2eb-elf-ld")
set(CMAKE_CROSSCOMPILING TRUE)
# Tell CMake not to try to run built executables
set(CMAKE_CROSSCOMPILING_EMULATOR "" CACHE STRING "" FORCE)
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
# Sysroot — Yaul installs headers/libs under the arch-prefix subdirectory
set(_YAUL_SYSROOT "${YAUL_INSTALL_ROOT}/sh2eb-elf")
set(CMAKE_FIND_ROOT_PATH "${_YAUL_SYSROOT}")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
# SH-2 core flags: big-endian (-mb), SH-2 ISA (-m2), no FPU
set(_SAT_C_FLAGS "-m2 -mb -fno-builtin -fomit-frame-pointer")
set(CMAKE_C_FLAGS_INIT "${_SAT_C_FLAGS}")
# Yaul provides its own startup code and linker script.
# The kernel.ld script maps Saturn Work RAM (0x06000000+).
set(_YAUL_LD "${YAUL_INSTALL_ROOT}/share/yaul/ip/kernel.ld")
set(CMAKE_EXE_LINKER_FLAGS_INIT
"-T\"${_YAUL_LD}\" -Wl,--start-group -Wl,--end-group -nostartfiles")
+248
View File
@@ -0,0 +1,248 @@
FROM ubuntu:22.04
LABEL org.opencontainers.image.description="Dusk Engine — Sega Saturn build environment (sh2eb-elf cross-compiler + Yaul SDK)"
ENV DEBIAN_FRONTEND=noninteractive
ENV YAUL_INSTALL_ROOT=/opt/yaul
# All variables required by Yaul's env.mk
ENV YAUL_ARCH_SH_PREFIX=sh2eb-elf
ENV YAUL_PROG_SH_PREFIX=sh2eb-elf
ENV YAUL_ARCH_M68K_PREFIX=m68keb-elf
ENV YAUL_BUILD_ROOT=/tmp/yaul-build
ENV YAUL_BUILD=build
ENV YAUL_OPTION_MALLOC_IMPL=tlsf
ENV PATH="${YAUL_INSTALL_ROOT}/bin:${PATH}"
# Toolchain source versions
ARG BINUTILS_VER=2.40
ARG GCC_VER=12.3.0
ARG NEWLIB_VER=4.3.0.20230120
# ---------------------------------------------------------------------------
# 1. Host build tools
# ---------------------------------------------------------------------------
RUN apt-get update && apt-get install -y \
build-essential \
cmake \
git \
wget \
curl \
xz-utils \
python3 \
python3-pip \
python3-polib \
python3-pil \
python3-dotenv \
texinfo \
bison \
flex \
libgmp-dev \
libmpfr-dev \
libmpc-dev \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p "${YAUL_INSTALL_ROOT}"
# ---------------------------------------------------------------------------
# 2. Download cross-compiler sources
# ---------------------------------------------------------------------------
RUN cd /tmp && \
wget -q "https://ftp.gnu.org/gnu/binutils/binutils-${BINUTILS_VER}.tar.xz" && \
wget -q "https://ftp.gnu.org/gnu/gcc/gcc-${GCC_VER}/gcc-${GCC_VER}.tar.xz" && \
wget -q "ftp://sourceware.org/pub/newlib/newlib-${NEWLIB_VER}.tar.gz" && \
tar xf "binutils-${BINUTILS_VER}.tar.xz" && \
tar xf "gcc-${GCC_VER}.tar.xz" && \
tar xf "newlib-${NEWLIB_VER}.tar.gz" && \
rm "binutils-${BINUTILS_VER}.tar.xz" "gcc-${GCC_VER}.tar.xz" "newlib-${NEWLIB_VER}.tar.gz"
# Download GCC prerequisites (gmp, mpfr, mpc if not packaged)
RUN cd /tmp/gcc-${GCC_VER} && contrib/download_prerequisites
# ---------------------------------------------------------------------------
# 3. sh2eb-elf binutils (SH-2 big-endian)
# ---------------------------------------------------------------------------
RUN mkdir -p /tmp/build-sh-binutils && cd /tmp/build-sh-binutils && \
/tmp/binutils-${BINUTILS_VER}/configure \
--target=sh2eb-elf \
--prefix="${YAUL_INSTALL_ROOT}" \
--disable-nls \
--disable-multilib \
--disable-werror \
&& make -j"$(nproc)" && make install && \
rm -rf /tmp/build-sh-binutils
# ---------------------------------------------------------------------------
# 4. sh2eb-elf GCC stage 1 (compiler only, no C library yet)
# ---------------------------------------------------------------------------
RUN mkdir -p /tmp/build-sh-gcc1 && cd /tmp/build-sh-gcc1 && \
/tmp/gcc-${GCC_VER}/configure \
--target=sh2eb-elf \
--prefix="${YAUL_INSTALL_ROOT}" \
--enable-languages=c,c++ \
--without-headers \
--with-newlib \
--disable-nls \
--disable-shared \
--disable-multilib \
--disable-decimal-float \
--disable-threads \
--disable-libatomic \
--disable-libgomp \
--disable-libquadmath \
--disable-libssp \
--disable-libvtv \
--disable-libstdcxx \
&& make -j"$(nproc)" all-gcc all-target-libgcc \
&& make install-gcc install-target-libgcc && \
rm -rf /tmp/build-sh-gcc1
# ---------------------------------------------------------------------------
# 5. newlib for sh2eb-elf (C runtime for embedded SH-2)
# ---------------------------------------------------------------------------
RUN mkdir -p /tmp/build-sh-newlib && cd /tmp/build-sh-newlib && \
/tmp/newlib-${NEWLIB_VER}/configure \
--target=sh2eb-elf \
--prefix="${YAUL_INSTALL_ROOT}" \
--disable-newlib-supplied-syscalls \
--enable-newlib-reent-small \
&& make -j"$(nproc)" && make install && \
rm -rf /tmp/build-sh-newlib
# ---------------------------------------------------------------------------
# 6. sh2eb-elf GCC stage 2 (full build with newlib)
# ---------------------------------------------------------------------------
RUN mkdir -p /tmp/build-sh-gcc2 && cd /tmp/build-sh-gcc2 && \
/tmp/gcc-${GCC_VER}/configure \
--target=sh2eb-elf \
--prefix="${YAUL_INSTALL_ROOT}" \
--enable-languages=c,c++ \
--with-newlib \
--disable-nls \
--disable-shared \
--disable-multilib \
--disable-libssp \
--disable-libgomp \
--disable-libquadmath \
&& make -j"$(nproc)" && make install && \
rm -rf /tmp/build-sh-gcc2
# ---------------------------------------------------------------------------
# 7. m68k-elf binutils (Saturn 68EC000 sound CPU)
# ---------------------------------------------------------------------------
RUN mkdir -p /tmp/build-m68k-binutils && cd /tmp/build-m68k-binutils && \
/tmp/binutils-${BINUTILS_VER}/configure \
--target=m68k-elf \
--prefix="${YAUL_INSTALL_ROOT}" \
--disable-nls \
--disable-multilib \
--disable-werror \
&& make -j"$(nproc)" && make install && \
rm -rf /tmp/build-m68k-binutils
# ---------------------------------------------------------------------------
# 8. m68k-elf GCC (compiler only; Yaul provides its own sound startup)
# ---------------------------------------------------------------------------
RUN mkdir -p /tmp/build-m68k-gcc && cd /tmp/build-m68k-gcc && \
/tmp/gcc-${GCC_VER}/configure \
--target=m68k-elf \
--prefix="${YAUL_INSTALL_ROOT}" \
--enable-languages=c \
--without-headers \
--with-newlib \
--disable-nls \
--disable-shared \
--disable-multilib \
--disable-libssp \
&& make -j"$(nproc)" all-gcc && make install-gcc && \
rm -rf /tmp/build-m68k-gcc
# Clean up source tarballs/trees
RUN rm -rf /tmp/binutils-${BINUTILS_VER} /tmp/gcc-${GCC_VER} /tmp/newlib-${NEWLIB_VER}
# ---------------------------------------------------------------------------
# 9. Create m68keb-elf symlinks
# Yaul expects YAUL_ARCH_M68K_PREFIX=m68keb-elf but we built m68k-elf.
# m68k is always big-endian, so m68k-elf == m68keb-elf semantically.
# ---------------------------------------------------------------------------
RUN for tool in "${YAUL_INSTALL_ROOT}/bin/m68k-elf-"*; do \
base="$(basename "$tool")"; \
newname="${YAUL_INSTALL_ROOT}/bin/m68keb-elf-${base#m68k-elf-}"; \
ln -sf "$tool" "$newname"; \
done
# ---------------------------------------------------------------------------
# 10. Clone and install libyaul
# ---------------------------------------------------------------------------
RUN git clone --depth 1 --recurse-submodules \
https://github.com/yaul-org/libyaul.git /tmp/yaul && \
cd /tmp/yaul && \
YAUL_INSTALL_ROOT="${YAUL_INSTALL_ROOT}" \
YAUL_ARCH_SH_PREFIX=sh2eb-elf \
YAUL_PROG_SH_PREFIX=sh2eb-elf \
YAUL_ARCH_M68K_PREFIX=m68keb-elf \
YAUL_BUILD_ROOT=/tmp/yaul-build \
YAUL_BUILD=build \
YAUL_OPTION_MALLOC_IMPL=tlsf \
make install && \
rm -rf /tmp/yaul /tmp/yaul-build
# ---------------------------------------------------------------------------
# 11. Cross-compile zlib for sh2eb-elf
# Install into ${YAUL_INSTALL_ROOT}/sh2eb-elf/ to match the Yaul sysroot
# layout: headers at .../sh2eb-elf/include, libs at .../sh2eb-elf/lib.
# ---------------------------------------------------------------------------
RUN wget -q https://zlib.net/zlib-1.3.1.tar.gz -O /tmp/zlib.tar.gz && \
tar xf /tmp/zlib.tar.gz -C /tmp && \
cd /tmp/zlib-1.3.1 && \
CC="${YAUL_INSTALL_ROOT}/bin/sh2eb-elf-gcc" \
AR="${YAUL_INSTALL_ROOT}/bin/sh2eb-elf-ar" \
RANLIB="${YAUL_INSTALL_ROOT}/bin/sh2eb-elf-ranlib" \
CFLAGS="-m2 -mb -fno-builtin -O2" \
./configure \
--prefix="${YAUL_INSTALL_ROOT}/sh2eb-elf" \
--static \
&& make -j"$(nproc)" && make install && \
rm -rf /tmp/zlib-1.3.1 /tmp/zlib.tar.gz
# ---------------------------------------------------------------------------
# 12. Cross-compile libzip for sh2eb-elf
# ---------------------------------------------------------------------------
RUN printf '%s\n' \
'set(CMAKE_SYSTEM_NAME Generic)' \
'set(CMAKE_SYSTEM_PROCESSOR sh2)' \
"set(CMAKE_C_COMPILER \"${YAUL_INSTALL_ROOT}/bin/sh2eb-elf-gcc\")" \
"set(CMAKE_AR \"${YAUL_INSTALL_ROOT}/bin/sh2eb-elf-ar\")" \
"set(CMAKE_RANLIB \"${YAUL_INSTALL_ROOT}/bin/sh2eb-elf-ranlib\")" \
'set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)' \
'set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)' \
'set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)' \
'set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)' \
> /tmp/sat-xc.cmake
RUN wget -q https://libzip.org/download/libzip-1.10.1.tar.gz -O /tmp/libzip.tar.gz && \
tar xf /tmp/libzip.tar.gz -C /tmp && \
cmake -S /tmp/libzip-1.10.1 -B /tmp/libzip-build \
-DCMAKE_TOOLCHAIN_FILE=/tmp/sat-xc.cmake \
-DCMAKE_INSTALL_PREFIX="${YAUL_INSTALL_ROOT}/sh2eb-elf" \
-DCMAKE_FIND_ROOT_PATH="${YAUL_INSTALL_ROOT}/sh2eb-elf" \
-DCMAKE_C_FLAGS="-m2 -mb -fno-builtin -O2" \
-DZLIB_LIBRARY="${YAUL_INSTALL_ROOT}/sh2eb-elf/lib/libz.a" \
-DZLIB_INCLUDE_DIR="${YAUL_INSTALL_ROOT}/sh2eb-elf/include" \
-DENABLE_BZIP2=OFF \
-DENABLE_LZMA=OFF \
-DENABLE_ZSTD=OFF \
-DENABLE_OPENSSL=OFF \
-DENABLE_GNUTLS=OFF \
-DBUILD_SHARED_LIBS=OFF \
-DBUILD_EXAMPLES=OFF \
-DBUILD_DOCUMENTATION=OFF \
-DBUILD_REGRESS=OFF \
-DBUILD_TOOLS=OFF \
&& cmake --build /tmp/libzip-build -- -j"$(nproc)" \
&& cmake --install /tmp/libzip-build \
&& rm -rf /tmp/libzip-1.10.1 /tmp/libzip.tar.gz /tmp/libzip-build /tmp/sat-xc.cmake
WORKDIR /workdir
VOLUME ["/workdir"]
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
docker build -t dusk-saturn -f docker/saturn/Dockerfile .
docker run --rm -v "$(pwd):/workdir" dusk-saturn /bin/bash -c "./scripts/build-saturn.sh"
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
set -e
if [ -z "$YAUL_INSTALL_ROOT" ]; then
if [ -d "/opt/yaul" ]; then
export YAUL_INSTALL_ROOT=/opt/yaul
else
echo "YAUL_INSTALL_ROOT is not set. Please set it to your Yaul SDK installation."
exit 1
fi
fi
mkdir -p build-saturn
cmake -S . -B build-saturn \
-DDUSK_TARGET_SYSTEM=saturn \
-DCMAKE_TOOLCHAIN_FILE="cmake/toolchains/saturn.cmake" \
-DYAUL_INSTALL_ROOT="${YAUL_INSTALL_ROOT}"
cmake --build build-saturn -- -j"$(nproc)"
+61
View File
@@ -0,0 +1,61 @@
#!/bin/bash
# psp-debug.sh — build (optional), reset, and run Dusk on a live PSP.
#
# Prerequisites:
# usbhostfs_pc running (served from the build-psp/ dir, as host0:).
# PSP must be running psplink (visible on PSP screen).
#
# Usage:
# ./scripts/psp-debug.sh # reset + run
# ./scripts/psp-debug.sh --build # rebuild via docker first, then run
#
# PSP stdout streams to this terminal via pspsh (logDebug, printf, etc.).
# Run from a real terminal — pspsh needs stdin connected to a TTY.
#
# IMPORTANT: crashes put the PSP hardware in a bad state.
# Always reset before relaunching. If reset doesn't respond, power-cycle the PSP.
set -euo pipefail
PSPSH="pspsh"
PRX_HOST="host0:/Dusk.prx"
RESET_SLEEP=3
# ---- rebuild (optional) -----------------------------------------------------
if [[ "${1:-}" == "--build" || "${1:-}" == "-b" ]]; then
echo "==> Building PSP (docker)..."
"$(dirname "$0")/build-psp-docker.sh"
echo ""
fi
# ---- sanity checks ----------------------------------------------------------
if ! command -v "$PSPSH" &>/dev/null; then
echo "ERROR: pspsh not found in PATH"
echo " Add /home/yourwishes/pspdev/bin to PATH or set PSPSH= in this script."
exit 1
fi
if ! nc -z localhost 10000 2>/dev/null; then
echo "ERROR: psplink is not reachable on localhost:10000."
echo " Ensure usbhostfs_pc is running and the PSP shows the psplink prompt."
exit 1
fi
# ---- reset any running process ----------------------------------------------
echo "==> Resetting PSP..."
"$PSPSH" -e "reset"
echo " Waiting ${RESET_SLEEP}s for PSP to settle..."
sleep "$RESET_SLEEP"
# ---- launch + stream output -------------------------------------------------
# Pre-send the exec command, then relay /dev/tty so pspsh keeps a live stdin.
# PSP stdout flows through pspsh directly to this terminal.
# Ctrl-C to stop.
echo "==> Launching $PRX_HOST"
echo " (PSP stdout streams here — Ctrl-C to stop)"
echo "------------------------------------------------------------"
{ printf 'exec %s\n' "$PRX_HOST"; cat /dev/tty; } | "$PSPSH"
+4 -1
View File
@@ -21,5 +21,8 @@ elseif(DUSK_TARGET_SYSTEM STREQUAL "vita")
elseif(DUSK_TARGET_SYSTEM STREQUAL "wii" OR DUSK_TARGET_SYSTEM STREQUAL "gamecube") elseif(DUSK_TARGET_SYSTEM STREQUAL "wii" OR DUSK_TARGET_SYSTEM STREQUAL "gamecube")
add_subdirectory(duskdolphin) add_subdirectory(duskdolphin)
elseif(DUSK_TARGET_SYSTEM STREQUAL "saturn")
add_subdirectory(dusksat)
endif() endif()
+3
View File
@@ -10,6 +10,9 @@
#ifdef DUSK_THREAD_PTHREAD #ifdef DUSK_THREAD_PTHREAD
#define THREAD_LOCAL __thread #define THREAD_LOCAL __thread
#elif defined(DUSK_THREAD_NONE)
/* Single-threaded platforms: no thread-local storage qualifier needed. */
#define THREAD_LOCAL
#endif #endif
#ifndef THREAD_LOCAL #ifndef THREAD_LOCAL
+3 -1
View File
@@ -10,7 +10,7 @@
#ifdef DUSK_THREAD_PTHREAD #ifdef DUSK_THREAD_PTHREAD
#include <pthread.h> #include <pthread.h>
#else #elif !defined(DUSK_THREAD_NONE)
#error "At least one threading implementation must be defined." #error "At least one threading implementation must be defined."
#endif #endif
@@ -18,6 +18,8 @@ typedef struct threadlock_t {
#ifdef DUSK_THREAD_PTHREAD #ifdef DUSK_THREAD_PTHREAD
pthread_mutex_t mutex; pthread_mutex_t mutex;
pthread_cond_t cond; pthread_cond_t cond;
#else
uint8_t unused; /* DUSK_THREAD_NONE: no real mutex on single-threaded platforms */
#endif #endif
} threadmutex_t; } threadmutex_t;
@@ -35,6 +35,19 @@ typedef struct {
static dolphintexentry_t dolphinTexTable[DOLPHIN_RTEXTURE_MAX]; static dolphintexentry_t dolphinTexTable[DOLPHIN_RTEXTURE_MAX];
static uint16_t dolphinTexNext = 1; /* 0 = white fallback */ 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 -------------------------------------------------------- */ /* ---- Camera state -------------------------------------------------------- */
static Mtx44 dolphinProj; static Mtx44 dolphinProj;
@@ -293,6 +306,109 @@ static void draw3DQuad(const ropquad3d_t *q) {
GX_End(); 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 --------------------------------------------------------------- */ /* ---- Flush --------------------------------------------------------------- */
errorret_t renderDolphinFlush(ropbuffer_t *buf) { errorret_t renderDolphinFlush(ropbuffer_t *buf) {
@@ -351,6 +467,10 @@ errorret_t renderDolphinFlush(ropbuffer_t *buf) {
draw3DQuad((const ropquad3d_t *)hdr); draw3DQuad((const ropquad3d_t *)hdr);
break; break;
case ROP_DRAW_TILEMAP_CHUNK:
draw2DTilemapChunk((const roptilemapc_t *)hdr);
break;
default: default:
break; break;
} }
@@ -363,6 +483,12 @@ errorret_t renderDolphinFlush(ropbuffer_t *buf) {
/* ---- Dispose ------------------------------------------------------------- */ /* ---- Dispose ------------------------------------------------------------- */
void renderDolphinDispose(void) { 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++) { for(uint16_t i = 0; i < dolphinTexNext; i++) {
dolphintexentry_t *e = &dolphinTexTable[i]; dolphintexentry_t *e = &dolphinTexTable[i];
if(e->cpuIndices) { free(e->cpuIndices); e->cpuIndices = NULL; } if(e->cpuIndices) { free(e->cpuIndices); e->cpuIndices = NULL; }
@@ -9,6 +9,7 @@
#include "error/error.h" #include "error/error.h"
#include "display/render/ropbuffer.h" #include "display/render/ropbuffer.h"
#include "display/render/rtexture.h" #include "display/render/rtexture.h"
#include "display/render/rtilemapchunk.h"
#include "display/color.h" #include "display/color.h"
errorret_t renderDolphinInit(void); errorret_t renderDolphinInit(void);
@@ -22,3 +23,11 @@ rtexture_t renderDolphinTextureCreate(
void renderDolphinTextureDispose(rtexture_t tex); void renderDolphinTextureDispose(rtexture_t tex);
color_t *renderDolphinTextureGetPalette(rtexture_t tex); color_t *renderDolphinTextureGetPalette(rtexture_t tex);
uint8_t *renderDolphinTextureGetIndices(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 #pragma once
#include "display/render/renderdolphin.h" #include "display/render/renderdolphin.h"
#define renderPlatformTextureCreate renderDolphinTextureCreate #define renderPlatformTextureCreate renderDolphinTextureCreate
#define renderPlatformTextureDispose renderDolphinTextureDispose #define renderPlatformTextureDispose renderDolphinTextureDispose
#define renderPlatformTextureGetPalette renderDolphinTextureGetPalette #define renderPlatformTextureGetPalette renderDolphinTextureGetPalette
#define renderPlatformTextureGetIndices renderDolphinTextureGetIndices #define renderPlatformTextureGetIndices renderDolphinTextureGetIndices
#define renderPlatformTilemapChunkCreate renderDolphinTilemapChunkCreate
#define renderPlatformTilemapChunkDispose renderDolphinTilemapChunkDispose
+16 -5
View File
@@ -9,26 +9,32 @@
#include "display/render/renderpsp.h" #include "display/render/renderpsp.h"
#include "display/display.h" #include "display/display.h"
#include "assert/assert.h" #include "assert/assert.h"
#include "log/log.h"
#include <pspgu.h> #include <pspgu.h>
#include <pspgum.h> #include <pspgum.h>
#include <pspdisplay.h> #include <pspdisplay.h>
#include <pspge.h> #include <pspge.h>
#define FRAME_SIZE (PSP_SCREEN_W * PSP_SCREEN_H * 4) /* GU framebuffer stride must be power-of-two; 512 is the standard for 480-wide. */
#define VRAM_ADDR(offset) ((void *)((uintptr_t)sceGeEdramGetAddr() + (offset))) #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]; static uint32_t __attribute__((aligned(64))) displayList[0x10000];
errorret_t displayPSPInit(void) { errorret_t displayPSPInit(void) {
logDebug("[PSP] displayPSPInit: start\n");
DISPLAY.whichBuffer = 0; DISPLAY.whichBuffer = 0;
sceGuInit(); sceGuInit();
sceGuStart(GU_DIRECT, displayList); sceGuStart(GU_DIRECT, displayList);
/* Draw buffer: frame 0 at offset 0, frame 1 at FRAME_SIZE */ /* Draw buffer: frame 0 at offset 0, frame 1 at FRAME_SIZE */
sceGuDrawBuffer(GU_PSM_8888, VRAM_ADDR(0), 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_SCREEN_W); sceGuDispBuffer(PSP_SCREEN_W, PSP_SCREEN_H, VRAM_ADDR(FRAME_SIZE), PSP_BUF_W);
sceGuDepthBuffer(VRAM_ADDR(FRAME_SIZE * 2), PSP_SCREEN_W); sceGuDepthBuffer(VRAM_ADDR(FRAME_SIZE * 2), PSP_BUF_W);
sceGuOffset(2048 - PSP_SCREEN_W / 2, 2048 - PSP_SCREEN_H / 2); sceGuOffset(2048 - PSP_SCREEN_W / 2, 2048 - PSP_SCREEN_H / 2);
sceGuViewport(2048, 2048, PSP_SCREEN_W, PSP_SCREEN_H); 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); sceDisplaySetMode(0, PSP_SCREEN_W, PSP_SCREEN_H);
sceGuDisplay(GU_TRUE); sceGuDisplay(GU_TRUE);
logDebug("[PSP] displayPSPInit: GU setup done, calling renderPSPInit\n");
errorChain(renderPSPInit()); errorChain(renderPSPInit());
logDebug("[PSP] displayPSPInit: done\n");
errorOk(); errorOk();
} }
errorret_t displayPSPFlush(ropbuffer_t *buf) { errorret_t displayPSPFlush(ropbuffer_t *buf) {
logDebug("[PSP] displayPSPFlush: enter\n");
assertNotNull(buf, "PSP flush: null ropbuffer"); assertNotNull(buf, "PSP flush: null ropbuffer");
errorChain(renderPSPFlush(buf)); errorChain(renderPSPFlush(buf));
logDebug("[PSP] displayPSPFlush: done\n");
errorOk(); errorOk();
} }
errorret_t displayPSPSwap(void) { errorret_t displayPSPSwap(void) {
logDebug("[PSP] displayPSPSwap\n");
sceGuSwapBuffers(); sceGuSwapBuffers();
errorOk(); errorOk();
} }
+23
View File
@@ -10,6 +10,7 @@
#include "display/color.h" #include "display/color.h"
#include "assert/assert.h" #include "assert/assert.h"
#include "util/memory.h" #include "util/memory.h"
#include "log/log.h"
#include <malloc.h> #include <malloc.h>
#include <psputils.h> /* sceKernelDcacheWritebackRange */ #include <psputils.h> /* sceKernelDcacheWritebackRange */
#include <pspgu.h> #include <pspgu.h>
@@ -80,6 +81,7 @@ static uint16_t texturePow2(uint16_t n) {
/* ---- Init ---------------------------------------------------------------- */ /* ---- Init ---------------------------------------------------------------- */
errorret_t renderPSPInit(void) { errorret_t renderPSPInit(void) {
logDebug("[PSP] renderPSPInit: start\n");
/* White 1×1 fallback: index 0 → palette[0] = white */ /* White 1×1 fallback: index 0 → palette[0] = white */
psptexentry_t *e = &pspTexTable[0]; psptexentry_t *e = &pspTexTable[0];
e->cpuIndices = (uint8_t *)memalign(16, 1); e->cpuIndices = (uint8_t *)memalign(16, 1);
@@ -93,6 +95,7 @@ errorret_t renderPSPInit(void) {
memoryZero(e->palette, 256 * sizeof(color_t)); memoryZero(e->palette, 256 * sizeof(color_t));
e->palette[0] = COLOR_WHITE; e->palette[0] = COLOR_WHITE;
e->w = 1; e->h = 1; e->tbw = 8; e->w = 1; e->h = 1; e->tbw = 8;
logDebug("[PSP] renderPSPInit: done\n");
errorOk(); errorOk();
} }
@@ -202,6 +205,7 @@ static void draw2DSprite(const ropsprite_t *s) {
} }
static void draw3DQuad(const ropquad3d_t *q) { static void draw3DQuad(const ropquad3d_t *q) {
logDebug("[PSP] draw3DQuad: enter tex=%u\n", (unsigned)q->texture);
uint32_t abgr = toABGR(q->tint); uint32_t abgr = toABGR(q->tint);
float u0 = q->uvX / 255.0f, v0 = q->uvY / 255.0f; float u0 = q->uvX / 255.0f, v0 = q->uvY / 255.0f;
float u1 = (q->uvX + q->uvW) / 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 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 brx = cx+rx-ux, bry = cy+ry-uy, brz = cz+rz-uz;
logDebug("[PSP] draw3DQuad: bindTexture\n");
bindTexture(q->texture); bindTexture(q->texture);
logDebug("[PSP] draw3DQuad: getMemory\n");
GuVert3D *verts = (GuVert3D *)sceGuGetMemory(6 * sizeof(GuVert3D)); 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"); assertNotNull(verts, "PSP: failed to allocate 3D quad vertices");
verts[0] = (GuVert3D){u0,v0, abgr, tlx,tly,tlz}; 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[4] = (GuVert3D){u1,v1, abgr, brx,bry,brz};
verts[5] = (GuVert3D){u1,v0, abgr, trx,try_,trz}; verts[5] = (GuVert3D){u1,v0, abgr, trx,try_,trz};
logDebug("[PSP] draw3DQuad: sceGuDrawArray\n");
sceGuDrawArray( sceGuDrawArray(
GU_TRIANGLES, GU_TRIANGLES,
GU_TEXTURE_32BITF | GU_COLOR_8888 | GU_VERTEX_32BITF | GU_TRANSFORM_3D, GU_TEXTURE_32BITF | GU_COLOR_8888 | GU_VERTEX_32BITF | GU_TRANSFORM_3D,
6, 0, verts 6, 0, verts
); );
logDebug("[PSP] draw3DQuad: done\n");
} }
/* ---- Flush --------------------------------------------------------------- */ /* ---- Flush --------------------------------------------------------------- */
errorret_t renderPSPFlush(ropbuffer_t *buf) { errorret_t renderPSPFlush(ropbuffer_t *buf) {
logDebug("[PSP] renderPSPFlush: byteCount=%u count=%u\n",
(unsigned)buf->byteCount, (unsigned)buf->count);
sceGuStart(GU_DIRECT, displayList); sceGuStart(GU_DIRECT, displayList);
sceGuEnable(GU_TEXTURE_2D); sceGuEnable(GU_TEXTURE_2D);
@@ -245,12 +257,14 @@ errorret_t renderPSPFlush(ropbuffer_t *buf) {
sceGuDepthFunc(GU_GEQUAL); /* PSP uses reversed depth */ sceGuDepthFunc(GU_GEQUAL); /* PSP uses reversed depth */
uint32_t offset = 0; uint32_t offset = 0;
uint32_t opIdx = 0;
while(offset < buf->byteCount) { while(offset < buf->byteCount) {
const ropheader_t *hdr = (const ropheader_t *)(buf->data + offset); const ropheader_t *hdr = (const ropheader_t *)(buf->data + offset);
ropop_t op = (ropop_t)hdr->op; ropop_t op = (ropop_t)hdr->op;
switch(op) { switch(op) {
case ROP_CLEAR: { case ROP_CLEAR: {
logDebug("[PSP] op[%u] ROP_CLEAR\n", (unsigned)opIdx);
const ropclear_t *c = (const ropclear_t *)hdr; const ropclear_t *c = (const ropclear_t *)hdr;
uint32_t abgr = toABGR(c->color); uint32_t abgr = toABGR(c->color);
sceGuClearColor(abgr); sceGuClearColor(abgr);
@@ -259,10 +273,12 @@ errorret_t renderPSPFlush(ropbuffer_t *buf) {
break; break;
} }
case ROP_DRAW_SPRITE: case ROP_DRAW_SPRITE:
logDebug("[PSP] op[%u] ROP_DRAW_SPRITE\n", (unsigned)opIdx);
draw2DSprite((const ropsprite_t *)hdr); draw2DSprite((const ropsprite_t *)hdr);
break; break;
case ROP_SET_PROJECTION: { case ROP_SET_PROJECTION: {
logDebug("[PSP] op[%u] ROP_SET_PROJECTION\n", (unsigned)opIdx);
const ropprojection_t *p = (const ropprojection_t *)hdr; const ropprojection_t *p = (const ropprojection_t *)hdr;
sceGumMatrixMode(GU_PROJECTION); sceGumMatrixMode(GU_PROJECTION);
sceGumLoadIdentity(); sceGumLoadIdentity();
@@ -280,6 +296,7 @@ errorret_t renderPSPFlush(ropbuffer_t *buf) {
break; break;
} }
case ROP_SET_VIEW: { case ROP_SET_VIEW: {
logDebug("[PSP] op[%u] ROP_SET_VIEW\n", (unsigned)opIdx);
const ropview_t *v = (const ropview_t *)hdr; const ropview_t *v = (const ropview_t *)hdr;
ScePspFVector3 eye = {(float)v->eyeX, (float)v->eyeY, (float)v->eyeZ}; ScePspFVector3 eye = {(float)v->eyeX, (float)v->eyeY, (float)v->eyeZ};
ScePspFVector3 target = {(float)v->tgtX, (float)v->tgtY, (float)v->tgtZ}; ScePspFVector3 target = {(float)v->tgtX, (float)v->tgtY, (float)v->tgtZ};
@@ -292,17 +309,23 @@ errorret_t renderPSPFlush(ropbuffer_t *buf) {
break; break;
} }
case ROP_DRAW_QUAD_3D: case ROP_DRAW_QUAD_3D:
logDebug("[PSP] op[%u] ROP_DRAW_QUAD_3D\n", (unsigned)opIdx);
draw3DQuad((const ropquad3d_t *)hdr); draw3DQuad((const ropquad3d_t *)hdr);
break; break;
default: default:
logDebug("[PSP] op[%u] unknown op=%u offset=%u\n",
(unsigned)opIdx, (unsigned)op, (unsigned)offset);
break; break;
} }
offset += ropOpSize(op); offset += ropOpSize(op);
opIdx++;
} }
logDebug("[PSP] renderPSPFlush: sceGuFinish\n");
sceGuFinish(); sceGuFinish();
sceGuSync(0, 0); sceGuSync(0, 0);
logDebug("[PSP] renderPSPFlush: done\n");
errorOk(); errorOk();
} }
+2 -1
View File
@@ -11,10 +11,11 @@ void logDebug(const char_t *message, ...) {
va_list args; va_list args;
va_start(args, message); va_start(args, message);
// print to stdout // print to stdout — fflush so pspsh sees it immediately even on crash
va_list copy; va_list copy;
va_copy(copy, args); va_copy(copy, args);
vprintf(message, copy); vprintf(message, copy);
fflush(stdout);
va_end(copy); va_end(copy);
// print to file // print to file
+22
View 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)
+9
View File
@@ -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
)
+14
View File
@@ -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
+20
View File
@@ -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();
}
+17
View File
@@ -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);
+11
View File
@@ -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)
+16
View File
@@ -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
+88
View File
@@ -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();
}
+23
View File
@@ -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
+446
View File
@@ -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;
}
+24
View File
@@ -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);
+9
View File
@@ -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>
+9
View File
@@ -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
)
+11
View File
@@ -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
+56
View File
@@ -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();
}
+11
View File
@@ -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);
+9
View File
@@ -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
)
+31
View File
@@ -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);
}
+9
View File
@@ -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
)
+19
View File
@@ -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;
+43
View File
@@ -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;
}
+36
View File
@@ -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);
+10
View File
@@ -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
)
+30
View File
@@ -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)
+39
View File
@@ -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();
}
+28
View File
@@ -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);
+87
View File
@@ -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();
}
+32
View File
@@ -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");
}
+9
View File
@@ -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
)
+12
View File
@@ -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
+23
View File
@@ -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;
}
+12
View File
@@ -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);
+9
View File
@@ -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
)
+14
View File
@@ -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
+41
View File
@@ -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;
}
+19
View File
@@ -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);