Some progress
This commit is contained in:
@@ -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}"
|
||||||
|
)
|
||||||
@@ -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")
|
||||||
@@ -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"]
|
||||||
Executable
+3
@@ -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"
|
||||||
Executable
+18
@@ -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)"
|
||||||
Executable
+61
@@ -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"
|
||||||
@@ -22,4 +22,7 @@ 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()
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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