Compare commits

..

98 Commits

Author SHA1 Message Date
888918face Refactoring editor 2026-03-31 10:56:44 -05:00
0885da8d44 Fixed dynamic updates on scene rendering 2026-03-29 19:08:58 -05:00
8af961c6d3 Fixed knulli rendering 2026-03-29 18:53:42 -05:00
ef5febdde3 Fixed dolphin rendering. 2026-03-29 18:42:59 -05:00
6d7fbd3926 Change to square only 2026-03-29 17:58:13 -05:00
2680d373d8 Fixed boot.dol in wii 2026-03-29 16:51:54 -05:00
2b2ddb3cf2 Fixed spritebatch flickering on Dolphin 2026-03-29 16:10:39 -05:00
85ff95296b Fix Linux again 2026-03-29 15:19:15 -05:00
314a2de41a Fixed text on PSP 2026-03-29 14:45:40 -05:00
26fafab47a Fix copy issues 2026-03-29 14:25:10 -05:00
e56ff20e2d Attempting to fix PSP alpha textures 2026-03-29 13:38:55 -05:00
55d44f229d Fixed crash on PSP 2026-03-29 10:35:57 -05:00
1c5e50cc4d Test text rendering 2026-03-29 10:15:22 -05:00
ea898da6c2 Fix compile 2026-03-28 21:52:52 -05:00
dbb7e9f53c Getting shaders working with lua. 2026-03-28 21:50:59 -05:00
cbb68a399d Fix compile error 2026-03-28 15:43:38 -05:00
0e794f28b1 Disable paletted textures for now 2026-03-28 15:40:30 -05:00
87d2d9123e Re-implement RGBA textures 2026-03-28 15:21:33 -05:00
6823a4ddb5 Try again again 2026-03-28 11:35:11 -05:00
20a7c70081 Fixiing weird action path missing? 2026-03-28 11:26:25 -05:00
9caa33b3bb Restore all builds 2026-03-28 11:14:15 -05:00
2d7e61460a fix 2026-03-28 11:05:36 -05:00
a4b7fb3f44 Try again 2026-03-28 11:04:42 -05:00
70056cf4ca Temp only build knulli
Some checks failed
Build Dusk / build-knulli (push) Failing after 21s
2026-03-28 11:02:43 -05:00
5f4ab71ade Add knulli build 2026-03-28 11:02:34 -05:00
f3adb3257b Cleanup knulli 2026-03-28 11:00:18 -05:00
438edda7fd Fixed knulli 2026-03-28 10:56:40 -05:00
d5b0441e6f Fixed GLES support (partially), PSP still not working 2026-03-28 10:51:50 -05:00
9ba0ceb000 Moved texture setting around 2026-03-28 09:48:24 -05:00
9474a68995 Slightly more accurate, likely going to have to change how paletted textures work 2026-03-27 21:01:29 -05:00
09c35f0aa6 Builds on knulli 2026-03-27 20:48:43 -05:00
a2113442cb Builds on knulli 2026-03-27 15:59:26 -05:00
d91808487f Allow texture to be NULL.
Some checks failed
Build Dusk / run-tests (push) Failing after 14s
Build Dusk / build-linux (push) Failing after 20s
Build Dusk / build-psp (push) Failing after 16s
Build Dusk / build-gamecube (push) Failing after 18s
Build Dusk / build-wii (push) Failing after 19s
2026-03-27 13:46:18 -05:00
933949cc19 Progress on PSP paletted textures
Some checks failed
Build Dusk / run-tests (push) Failing after 19s
Build Dusk / build-linux (push) Failing after 17s
Build Dusk / build-psp (push) Failing after 21s
Build Dusk / build-gamecube (push) Failing after 19s
Build Dusk / build-wii (push) Failing after 15s
2026-03-27 08:04:34 -05:00
407620387d Test paletted stuff
Some checks failed
Build Dusk / run-tests (push) Failing after 26s
Build Dusk / build-linux (push) Failing after 25s
Build Dusk / build-psp (push) Failing after 18s
Build Dusk / build-gamecube (push) Failing after 18s
Build Dusk / build-wii (push) Failing after 18s
2026-03-26 14:48:20 -05:00
98947dea26 starting textures
Some checks failed
Build Dusk / run-tests (push) Failing after 16s
Build Dusk / build-linux (push) Failing after 17s
Build Dusk / build-psp (push) Failing after 18s
Build Dusk / build-gamecube (push) Failing after 17s
Build Dusk / build-wii (push) Failing after 17s
2026-03-23 19:42:24 -05:00
ebff7af9b5 fix
Some checks failed
Build Dusk / run-tests (push) Failing after 17s
Build Dusk / build-linux (push) Failing after 20s
Build Dusk / build-psp (push) Failing after 20s
Build Dusk / build-gamecube (push) Failing after 19s
Build Dusk / build-wii (push) Failing after 17s
2026-03-23 15:37:53 -05:00
b23c4b83ae played around with color, will likely stick to textures.
Some checks failed
Build Dusk / run-tests (push) Failing after 13s
Build Dusk / build-linux (push) Failing after 15s
Build Dusk / build-psp (push) Failing after 14s
Build Dusk / build-gamecube (push) Failing after 13s
Build Dusk / build-wii (push) Failing after 14s
2026-03-22 23:53:23 -05:00
c0cff40628 Merge pull request 'shader' (#2) from shader into main
Some checks failed
Build Dusk / run-tests (push) Failing after 14s
Build Dusk / build-linux (push) Failing after 14s
Build Dusk / build-psp (push) Failing after 14s
Build Dusk / build-gamecube (push) Failing after 13s
Build Dusk / build-wii (push) Failing after 13s
Reviewed-on: #2
2026-03-23 04:33:20 +00:00
97513e354c Dolphin shaders
Some checks failed
Build Dusk / run-tests (pull_request) Failing after 13s
Build Dusk / build-linux (pull_request) Failing after 18s
Build Dusk / build-psp (pull_request) Failing after 18s
Build Dusk / build-gamecube (pull_request) Failing after 16s
Build Dusk / build-wii (pull_request) Failing after 13s
2026-03-22 23:32:43 -05:00
c277ae7aff DOlphin shader prog 2026-03-22 18:14:56 -05:00
e1835e6282 Merge pull request 'Pull shader code into main' (#1) from shader into main
Some checks failed
Build Dusk / build-linux (push) Has been cancelled
Build Dusk / build-psp (push) Has been cancelled
Build Dusk / run-tests (push) Has been cancelled
Build Dusk / build-gamecube (push) Has been cancelled
Build Dusk / build-wii (push) Has been cancelled
Reviewed-on: #1
2026-03-22 15:46:37 +00:00
5ac21db997 Shaders adapted for Legacy GL
Some checks failed
Build Dusk / run-tests (pull_request) Failing after 24s
Build Dusk / build-linux (pull_request) Failing after 18s
Build Dusk / build-psp (pull_request) Failing after 18s
Build Dusk / build-gamecube (pull_request) Failing after 15s
Build Dusk / build-wii (pull_request) Failing after 16s
2026-03-22 10:44:28 -05:00
ca0e9fc3b2 Implement spritebatch properly. 2026-03-22 09:13:42 -05:00
66ebcb1608 shader prog 2026-03-17 17:05:39 -05:00
ff92a78dda Shader first pass 2026-03-17 08:42:43 -05:00
7356286fe0 Adjust how deadzones work
Some checks failed
Build Dusk / run-tests (push) Failing after 15s
Build Dusk / build-linux (push) Failing after 21s
Build Dusk / build-psp (push) Failing after 17s
Build Dusk / build-gamecube (push) Failing after 17s
Build Dusk / build-wii (push) Failing after 17s
2026-03-11 13:00:11 -05:00
54e8e68f86 Update build to use checkout v6
Some checks failed
Build Dusk / run-tests (push) Failing after 18s
Build Dusk / build-linux (push) Failing after 17s
Build Dusk / build-psp (push) Failing after 18s
Build Dusk / build-gamecube (push) Failing after 13s
Build Dusk / build-wii (push) Failing after 16s
2026-03-11 10:42:54 -05:00
d21cd7f78b Update error and debug logging methods
Some checks failed
Build Dusk / run-tests (push) Failing after 14s
Build Dusk / build-linux (push) Failing after 15s
Build Dusk / build-psp (push) Failing after 22s
Build Dusk / build-gamecube (push) Failing after 18s
Build Dusk / build-wii (push) Failing after 15s
2026-03-11 10:33:43 -05:00
1d7516982a Fixed dolphin input
Some checks failed
Build Dusk / run-tests (push) Failing after 15s
Build Dusk / build-linux (push) Failing after 15s
Build Dusk / build-psp (push) Failing after 15s
Build Dusk / build-gamecube (push) Failing after 17s
Build Dusk / build-wii (push) Failing after 14s
2026-03-11 08:11:49 -05:00
c77a11442c Fix input on linux 2026-03-11 07:56:03 -05:00
5bd43a4643 Fix Dolphin crash 2026-03-11 07:27:06 -05:00
9b87dfa1a9 Only exec action on main 2026-03-10 21:59:15 -05:00
2e3173ea40 Enable all jobs.
Some checks failed
Build Dusk / run-tests (push) Failing after 14s
Build Dusk / build-linux (push) Failing after 16s
Build Dusk / build-psp (push) Failing after 15s
Build Dusk / build-gamecube (push) Failing after 16s
Build Dusk / build-wii (push) Failing after 14s
2026-03-10 21:53:13 -05:00
68eac7cf83 Build wii
Some checks failed
Build Dusk / build-wii (push) Failing after 14s
2026-03-10 21:48:12 -05:00
6709505630 Build gamecube
Some checks failed
Build Dusk / build-gamecube (push) Failing after 16s
2026-03-10 21:43:59 -05:00
af6e962a5d Try rename
Some checks failed
Build Dusk / build-psp (push) Failing after 13s
2026-03-10 21:39:00 -05:00
18e6bdabaa test 2
Some checks failed
Build Dusk / build-psp (push) Failing after 14s
2026-03-10 21:35:50 -05:00
9743942eae Try zip PSP
Some checks failed
Build Dusk / build-psp (push) Failing after 17s
2026-03-10 21:33:27 -05:00
23062137a8 Disable tests for now.
Some checks failed
Build Dusk / build-linux (push) Failing after 14s
2026-03-10 21:23:08 -05:00
46f7fb5ccd Use v6
Some checks failed
Build Dusk / run-tests (push) Failing after 14s
Build Dusk / build-linux (push) Failing after 28s
2026-03-10 21:21:37 -05:00
9c90c49a6b Test build linux
Some checks failed
Build Dusk / run-tests (push) Failing after 14s
Build Dusk / build-linux (push) Failing after 28s
2026-03-10 21:19:55 -05:00
4517b63557 Fixed compiling
Some checks failed
Build Dusk / run-tests (push) Failing after 15s
2026-03-10 21:11:12 -05:00
58c239f4b4 Fixing tests more.
Some checks failed
Build Dusk / run-tests (push) Failing after 14s
2026-03-10 20:53:28 -05:00
cc8845ba3e Run linux tests
Some checks failed
Build Dusk / run-tests (push) Failing after 14s
2026-03-10 20:51:58 -05:00
6b69ce2901 Try github
Some checks failed
Build Dusk / run-tests (push) Failing after 36s
2026-03-10 20:46:29 -05:00
55300ed21c test2
Some checks failed
Build Dusk / run-tests (push) Successful in 30s
Build Dusk / build-linux (push) Failing after 6s
2026-03-10 16:48:14 -05:00
7346dd4339 Test
Some checks failed
Build Dusk / run-tests (push) Successful in 25s
Build Dusk / build-linux (push) Failing after 7s
2026-03-10 16:45:24 -05:00
2caf3b92ce Try github workspace
Some checks failed
Build Dusk / run-tests (push) Successful in 6s
Build Dusk / build-linux (push) Failing after 7s
2026-03-10 16:30:46 -05:00
af2cd72a1f Try mount rather than volume
Some checks failed
Build Dusk / run-tests (push) Successful in 4s
Build Dusk / build-linux (push) Failing after 4s
2026-03-10 16:30:08 -05:00
3d455ec1f8 Remove volume
Some checks failed
Build Dusk / run-tests (push) Failing after 3s
Build Dusk / build-linux (push) Failing after 3s
2026-03-10 16:26:29 -05:00
15982d7735 Try realpath over pwd
Some checks failed
Build Dusk / run-tests (push) Failing after 4s
Build Dusk / build-linux (push) Failing after 6s
2026-03-10 16:24:41 -05:00
5ae3542bd9 where am I?
Some checks failed
Build Dusk / run-tests (push) Failing after 4s
Build Dusk / build-linux (push) Failing after 5s
2026-03-10 16:23:32 -05:00
b1b02ae24b Test lsla
Some checks failed
Build Dusk / run-tests (push) Successful in 4s
Build Dusk / build-linux (push) Failing after 4s
2026-03-10 16:21:31 -05:00
f0964e2c92 Test runner
Some checks failed
Build Dusk / run-tests (push) Failing after 2m18s
Build Dusk / build-linux (push) Failing after 6s
2026-03-10 16:16:53 -05:00
e9661d2998 ADd check
Some checks failed
Build Dusk / run-tests (push) Failing after 2m21s
2026-03-10 16:01:44 -05:00
ea6468f2a9 Use git runner temp
Some checks failed
Build Dusk / run-tests (push) Failing after 29s
2026-03-10 15:36:28 -05:00
a2b38d3b83 Test Docker user
Some checks failed
Build Dusk / run-tests (push) Failing after 25s
2026-03-10 15:34:53 -05:00
d67ef02941 Use script system
All checks were successful
Build Dusk / run-tests (push) Successful in 1m15s
Build Dusk / build-linux (push) Successful in 1m6s
2026-03-10 15:15:31 -05:00
549ebe25d8 Let's get it building on linux in gitea
All checks were successful
Build Dusk / run-tests (push) Successful in 2m16s
Build Dusk / build-linux (push) Successful in 1m23s
2026-03-10 15:10:18 -05:00
9a98348582 Renders on Dolphin also. 2026-03-10 15:07:50 -05:00
c5f5b025a6 Game no longer crashes on Dolphin 2026-03-09 08:05:26 -05:00
23eaffa3a7 Fix some dolphin stuff. 2026-03-08 19:55:48 -05:00
c161809248 Renders on PSP identically. 2026-03-08 19:51:00 -05:00
4bf26dc818 Let's get this rendering on PSP and Dolphin. 2026-03-08 15:46:38 -05:00
5dd22fad6c Fixed some bugs. 2026-03-08 13:55:11 -05:00
2c3fdf7803 Add compile time endianess 2026-03-08 13:44:52 -05:00
e984b9f5d7 Asset compartmentalized 2026-03-08 13:29:40 -05:00
a3c2e37b17 Fixed errors 2026-03-08 12:01:22 -05:00
edf1b5a0a3 Technically working 2026-03-08 11:35:21 -05:00
8efdf59ebd More fixes 2026-03-08 10:20:55 -05:00
5c4537b2fa input prog 2026-03-07 22:11:11 -06:00
71e6079054 More code moving 2026-03-07 12:09:40 -06:00
dd048d9b0d Moved a bunch of code around 2026-03-07 09:35:56 -06:00
93074d653e idk 2026-03-06 16:34:45 -06:00
9139c4350a Moved all files. 2026-03-06 14:01:21 -06:00
38ce768168 kms 2026-03-06 13:40:27 -06:00
82b3dc576c remove un-needed files 2026-03-03 12:29:04 -06:00
397 changed files with 8501 additions and 3979 deletions

View File

@@ -1,156 +0,0 @@
name: Build Dusk
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
run-tests:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: https://git.wish.moe/YourWishes/checkout@main
- name: Install dependencies
run: |
apt-get update
apt-get install -y build-essential cmake python3 python3-pip python3-polib python3-pil libsdl2-dev libgl1-mesa-dev libzip-dev python3-dotenv python3-pyqt5 python3-opengl liblua5.3-dev
- name: Configure CMake for tests
run: cmake -S . -B build -DDUSK_BUILD_TESTS=ON -DDUSK_TARGET_SYSTEM=linux
- name: Build tests
run: cmake --build build -- -j$(nproc)
- name: Run tests
run: ctest --output-on-failure --test-dir build
build-linux:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: https://git.wish.moe/YourWishes/checkout@main
- name: Install dependencies
run: |
apt-get update
apt-get install -y build-essential cmake python3 python3-pip python3-polib python3-pil libsdl2-dev libgl1-mesa-dev libzip-dev python3-dotenv python3-pyqt5 python3-opengl liblua5.3-dev
- name: Configure CMake
run: cmake -S . -B build -DDUSK_TARGET_SYSTEM=linux
- name: Build
run: cmake --build build -- -j$(nproc)
- name: List build output
run: ls -lh build
- name: Upload Linux binary
uses: https://git.wish.moe/YourWishes/upload-artifact@v3/node20
with:
name: dusk-linux
path: build/Dusk
if-no-files-found: error
build-psp:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: https://git.wish.moe/YourWishes/checkout@main
- name: Install dependencies
run: |
apt-get update
apt-get install -y build-essential cmake python3 python3-pip python3-polib python3-pil libsdl2-dev libgl1-mesa-dev libzip-dev python3-dotenv python3-pyqt5 python3-opengl liblua5.3-dev
- name: Configure CMake
run: cmake -S . -B build -DDUSK_TARGET_SYSTEM=psp -DENABLE_TESTS=OFF
- name: Build
run: cmake --build build -- -j$(nproc)
- name: Move EBOOT.PBP to Dusk subfolder
run: |
mkdir -p build/gitea/Dusk
mv build/EBOOT.PBP build/gitea/Dusk/EBOOT.PBP
- name: List build output
run: ls -lh build/gitea/Dusk
- name: Upload PSP binary
uses: https://git.wish.moe/YourWishes/upload-artifact@v3/node20
with:
name: dusk-psp
path: build/gitea/
if-no-files-found: error
build-dolphin:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: https://git.wish.moe/YourWishes/checkout@main
- name: Install dependencies
run: |
# Install devkit pacman
if ! [ $(id -u) = 0 ]; then
echo "Need root privilege to install!"
exit 1
fi
# ensure apt is set up to work with https sources
apt-get install apt-transport-https
# Store devkitPro gpg key locally if we don't have it already
if ! [ -f /usr/share/keyring/devkitpro-pub.gpg ]; then
mkdir -p /usr/share/keyring/
wget -U "dkp apt" -O /usr/share/keyring/devkitpro-pub.gpg https://apt.devkitpro.org/devkitpro-pub.gpg
fi
# Add the devkitPro apt repository if we don't have it set up already
if ! [ -f /etc/apt/sources.list.d/devkitpro.list ]; then
echo "deb [signed-by=/usr/share/keyring/devkitpro-pub.gpg] https://apt.devkitpro.org stable main" > /etc/apt/sources.list.d/devkitpro.list
fi
apt-get update
apt-get install devkitpro-pacman --yes
apt-get install --yes build-essential cmake python3 python3-pip python3-polib python3-pil python3-dotenv python3-pyqt5 python3-opengl
sudo dkp-pacman -Syu --noconfirm
sudo dkp-pacman -S gamecube-dev wii-dev ppc-liblzma ppc-libzip --needed --noconfirm
- name: Build GameCube
run: |
export DEVKITPRO=/opt/devkitpro
export DEVKITPPC=/opt/devkitpro/devkitPPC
export PATH="$DEVKITPPC/bin:$DEVKITPRO/tools/bin:$PATH"
mkdir -p build-gamecube
cmake -S. -Bbuild-gamecube -DDUSK_TARGET_SYSTEM=gamecube -DCMAKE_TOOLCHAIN_FILE="$DEVKITPRO/cmake/GameCube.cmake"
cd build-gamecube
make -j$(nproc) VERBOSE=1
- name: Copy GameCube
run: |
ls -l
mkdir -p build/gitea/GameCube/Dusk
mv build-gamecube/Dusk.dol build/gitea/GameCube/Dusk/Dusk.dol
mv build-gamecube/dusk.dsk build/gitea/GameCube/Dusk/dusk.dsk
- name: Upload GameCube Binary
uses: https://git.wish.moe/YourWishes/upload-artifact@v3/node20
with:
name: dusk-gamecube
path: build/gitea/GameCube
if-no-files-found: error
- name: Build Wii
run: |
export DEVKITPRO=/opt/devkitpro
export DEVKITPPC=/opt/devkitpro/devkitPPC
export PATH="$DEVKITPPC/bin:$DEVKITPRO/tools/bin:$PATH"
mkdir -p build-wii
cmake -S. -Bbuild-wii -DDUSK_TARGET_SYSTEM=wii -DCMAKE_TOOLCHAIN_FILE="$DEVKITPRO/cmake/Wii.cmake"
cd build-wii
make -j$(nproc) VERBOSE=1
- name: Copy Wii
run: |
ls -l
mkdir -p build/gitea/Wii/apps/Dusk
mv build-wii/Dusk.dol build/gitea/Wii/apps/Dusk/boot.dol
mv build-wii/dusk.dsk build/gitea/Wii/apps/Dusk/dusk.dsk
cp .ci/dolphin/meta.xml build/gitea/Wii/apps/Dusk/meta.xml
- name: Upload Wii Binary
uses: https://git.wish.moe/YourWishes/upload-artifact@v3/node20
with:
name: dusk-wii
path: build/gitea/Wii
if-no-files-found: error

117
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,117 @@
name: Build Dusk
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
run-tests:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker
uses: docker/setup-docker-action@v5
- name: Run tests in Docker
run: ./scripts/test-linux-docker.sh
build-linux:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker
uses: docker/setup-docker-action@v5
- name: Build Linux
run: ./scripts/build-linux-docker.sh
- name: Upload Linux binary
uses: actions/upload-artifact@v6
with:
name: dusk-linux
path: build-linux/Dusk
if-no-files-found: error
build-psp:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker
uses: docker/setup-docker-action@v5
- name: Build psp
run: ./scripts/build-psp-docker.sh
- name: Move EBOOT.PBP to Dusk subfolder
run: |
mkdir -p ./git-artifcats/Dusk/PSP/GAME/Dusk
cp build-psp/EBOOT.PBP ./git-artifcats/Dusk/PSP/GAME/Dusk/EBOOT.PBP
- name: Upload psp binary
uses: actions/upload-artifact@v6
with:
name: dusk-psp
path: ./git-artifcats/Dusk
if-no-files-found: error
build-knulli:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker
uses: docker/setup-docker-action@v5
- name: Build knulli
run: ./scripts/build-knulli-docker.sh
- name: Move output to Dusk subfolder
run: |
mkdir -p ./git-artifcats/Dusk
cp -r build-knulli/dusk ./git-artifcats/Dusk
- name: Upload knulli binary
uses: actions/upload-artifact@v6
with:
name: dusk-knulli
path: ./git-artifcats/Dusk
if-no-files-found: error
build-gamecube:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker
uses: docker/setup-docker-action@v5
- name: Build GameCube
run: ./scripts/build-gamecube-docker.sh
- name: Copy output files.
run: |
mkdir -p ./git-artifcats/Dusk
cp build-gamecube/Dusk.dol ./git-artifcats/Dusk/Dusk.dol
cp build-gamecube/dusk.dsk ./git-artifcats/Dusk/dusk.dsk
- name: Upload GameCube binary
uses: actions/upload-artifact@v6
with:
name: dusk-gamecube
path: ./git-artifcats/Dusk
if-no-files-found: error
build-wii:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker
uses: docker/setup-docker-action@v5
- name: Build Wii
run: ./scripts/build-wii-docker.sh
- name: Copy output files.
run: |
mkdir -p ./git-artifcats/Dusk/apps/Dusk
cp build-wii/Dusk.dol ./git-artifcats/Dusk/apps/Dusk/boot.dol
cp build-wii/dusk.dsk ./git-artifcats/Dusk/apps/Dusk/dusk.dsk
cp docker/dolphin/meta.xml ./git-artifcats/Dusk/apps/Dusk/meta.xml
- name: Upload Wii binary
uses: actions/upload-artifact@v6
with:
name: dusk-wii
path: ./git-artifcats/Dusk
if-no-files-found: error

View File

@@ -10,7 +10,7 @@ set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
option(ENABLE_TESTS "Enable tests" OFF)
option(DUSK_BUILD_TESTS "Enable tests" OFF)
# Prep cache
set(DUSK_CACHE_TARGET "dusk-target")
@@ -32,7 +32,6 @@ set(DUSK_LIBRARY_TARGET_NAME "DuskCore" CACHE INTERNAL ${DUSK_CACHE_TARGET})
set(DUSK_BINARY_TARGET_NAME "Dusk" CACHE INTERNAL ${DUSK_CACHE_TARGET})
set(DUSK_ASSETS_ZIP "${DUSK_BUILD_DIR}/dusk.dsk" CACHE INTERNAL ${DUSK_CACHE_TARGET})
# Set default target system
if(NOT DEFINED DUSK_TARGET_SYSTEM)
set(DUSK_TARGET_SYSTEM "linux")
endif()
@@ -52,14 +51,14 @@ project(${DUSK_LIBRARY_TARGET_NAME}
)
# Either, create library and binary separately (used for tests), or make them
# one in the same so all code is in the binary.
if(ENABLE_TESTS)
# one in the same so all code is in the binary only.
# Binary Executable
add_executable(${DUSK_BINARY_TARGET_NAME} ${DUSK_SOURCES_DIR}/dusk/null.c)
if(DUSK_BUILD_TESTS)
# MainLibrary
add_library(${DUSK_LIBRARY_TARGET_NAME} STATIC)
# Binary Executable
add_executable(${DUSK_BINARY_TARGET_NAME} ${DUSK_SOURCES_DIR}/null.c)
# Link library to binary
target_link_libraries(${DUSK_BINARY_TARGET_NAME}
PUBLIC
@@ -67,9 +66,14 @@ if(ENABLE_TESTS)
)
else()
set(DUSK_LIBRARY_TARGET_NAME "${DUSK_BINARY_TARGET_NAME}" CACHE INTERNAL ${DUSK_CACHE_TARGET})
add_executable(${DUSK_BINARY_TARGET_NAME} ${DUSK_SOURCES_DIR}/null.c)
endif()
# Definitions
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
DUSK_TARGET_SYSTEM="${DUSK_TARGET_SYSTEM}"
)
# Toolchains
include(cmake/targets/${DUSK_TARGET_SYSTEM}.cmake)
@@ -90,7 +94,7 @@ target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
)
# Handle tests
if(ENABLE_TESTS)
if(DUSK_BUILD_TESTS)
enable_testing()
add_subdirectory(test)
endif()

View File

@@ -0,0 +1,12 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
sdl2.c
psp.c
dolphin.c
)

View File

@@ -0,0 +1,92 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "dolphin.h"
void displayInitDolphin(void) {
VIDEO_Init();
DISPLAY.screenMode = VIDEO_GetPreferredMode(NULL);
DISPLAY.frameBuffer[0] = MEM_K0_TO_K1(
SYS_AllocateFramebuffer(DISPLAY.screenMode)
);
DISPLAY.frameBuffer[1] = MEM_K0_TO_K1(
SYS_AllocateFramebuffer(DISPLAY.screenMode)
);
VIDEO_Configure(DISPLAY.screenMode);
VIDEO_SetNextFramebuffer(DISPLAY.frameBuffer[DISPLAY.whichFrameBuffer]);
// VIDEO_SetPostRetraceCallback(copy_buffers);
VIDEO_SetBlack(FALSE);
VIDEO_Flush();
VIDEO_WaitVSync();
if(DISPLAY.screenMode->viTVMode & VI_NON_INTERLACE) VIDEO_WaitVSync();
DISPLAY.fifoBuffer = memalign(32, DISPLAY_FIFO_SIZE);
memoryZero(DISPLAY.fifoBuffer, DISPLAY_FIFO_SIZE);
GX_Init(DISPLAY.fifoBuffer, DISPLAY_FIFO_SIZE);
// This seems to be mostly related to interlacing vs progressive
GX_SetViewport(
0, 0,
DISPLAY.screenMode->fbWidth, DISPLAY.screenMode->efbHeight,
0, 1
);
float_t yscale = GX_GetYScaleFactor(
DISPLAY.screenMode->efbHeight, DISPLAY.screenMode->xfbHeight
);
uint32_t xfbHeight = GX_SetDispCopyYScale(yscale);
GX_SetScissor(
0, 0,
DISPLAY.screenMode->fbWidth, DISPLAY.screenMode->efbHeight
);
GX_SetDispCopySrc(
0, 0,
DISPLAY.screenMode->fbWidth, DISPLAY.screenMode->efbHeight
);
GX_SetDispCopyDst(DISPLAY.screenMode->fbWidth, xfbHeight);
GX_SetCopyFilter(
DISPLAY.screenMode->aa,
DISPLAY.screenMode->sample_pattern,
GX_TRUE,
DISPLAY.screenMode->vfilter
);
GX_SetFieldMode(
DISPLAY.screenMode->field_rendering,
(
(DISPLAY.screenMode->viHeight == 2 * DISPLAY.screenMode->xfbHeight) ?
GX_ENABLE :
GX_DISABLE
)
);
// Setup cull modes
GX_SetCullMode(GX_CULL_NONE);
GX_SetZMode(GX_FALSE, GX_ALWAYS, GX_FALSE);
GX_CopyDisp(DISPLAY.frameBuffer[DISPLAY.whichFrameBuffer], GX_TRUE);
GX_SetDispCopyGamma(GX_GM_1_0);
GX_ClearVtxDesc();
GX_SetVtxDesc(GX_VA_POS, GX_INDEX16);
GX_SetVtxDesc(GX_VA_CLR0, GX_INDEX16);
GX_SetVtxDesc(GX_VA_TEX0, GX_INDEX16);
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_U8, 0);
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
}
void displayDolphinSwap(void) {
GX_DrawDone();
DISPLAY.whichFrameBuffer ^= 1;
GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
GX_SetColorUpdate(GX_TRUE);
GX_CopyDisp(DISPLAY.frameBuffer[DISPLAY.whichFrameBuffer], GX_TRUE);
VIDEO_SetNextFramebuffer(DISPLAY.frameBuffer[DISPLAY.whichFrameBuffer]);
VIDEO_Flush();
VIDEO_WaitVSync();
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#include "display/displaydefs.h"
typedef struct {
void *frameBuffer[2];// Double-Bufferred
int whichFrameBuffer;
GXRModeObj *screenMode;
void *fifoBuffer;
} displaydolphin_t;
/**
* Initializes the display for Dolphin.
*/
void displayDolphinInit(void);
/**
* Swaps the back buffer to the front for Dolphin.
*/
void displayDolphinSwap(void);

12
archive/platform/psp.c Normal file
View File

@@ -0,0 +1,12 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "psp.h"
void displayInitPSP(void) {
DISPLAY.usingShaderedPalettes = false;
}

13
archive/platform/psp.h Normal file
View File

@@ -0,0 +1,13 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
/**
* Initializes the display for PSP.
*/
void displayInitPSP(void);

32
archive/platform/sdl2.c Normal file
View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "sdl2.h"
void displaySDL2Update(void) {
}
void displaySDL2Swap(void) {
SDL_GL_SwapWindow(DISPLAY.window);
GLenum err;
while((err = glGetError()) != GL_NO_ERROR) {
debugPrint("GL Error: %d\n", err);
}
}
void displaySDL2Dispose(void) {
if(DISPLAY.glContext) {
SDL_GL_DeleteContext(DISPLAY.glContext);
DISPLAY.glContext = NULL;
}
if(DISPLAY.window) {
SDL_DestroyWindow(DISPLAY.window);
DISPLAY.window = NULL;
}
SDL_Quit();
}

35
archive/platform/sdl2.h Normal file
View File

@@ -0,0 +1,35 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct {
SDL_Window *window;
SDL_GLContext glContext;
bool_t usingShaderedPalettes;
} displaysdl2_t;
/**
* Initializes the display for SDL2.
*/
void displaySDL2Init(void);
/**
* Updates the display for SDL2.
*/
void displaySDL2Update(void);
/**
* Swaps the display buffers for SDL2.
*/
void displaySDL2Swap(void);
/**
* Disposes of the display for SDL2.
*/
void displaySDL2Dispose(void);

View File

@@ -4,7 +4,7 @@ module('scene')
module('locale')
-- Default Input bindings.
if PLATFORM == "psp" then
if PSP then
inputBind("up", INPUT_ACTION_UP)
inputBind("down", INPUT_ACTION_DOWN)
inputBind("left", INPUT_ACTION_LEFT)
@@ -30,7 +30,7 @@ elseif DOLPHIN then
inputBind("lstick_left", INPUT_ACTION_LEFT)
inputBind("lstick_right", INPUT_ACTION_RIGHT)
else
elseif LINUX then
if INPUT_KEYBOARD then
inputBind("w", INPUT_ACTION_UP)
inputBind("s", INPUT_ACTION_DOWN)
@@ -50,10 +50,29 @@ else
inputBind("escape", INPUT_ACTION_RAGEQUIT)
end
if INPUT_GAMEPAD then
inputBind("gamepad_up", INPUT_ACTION_UP)
inputBind("gamepad_down", INPUT_ACTION_DOWN)
inputBind("gamepad_left", INPUT_ACTION_LEFT)
inputBind("gamepad_right", INPUT_ACTION_RIGHT)
inputBind("gamepad_a", INPUT_ACTION_ACCEPT)
inputBind("gamepad_b", INPUT_ACTION_CANCEL)
inputBind("gamepad_back", INPUT_ACTION_RAGEQUIT)
inputBind("gamepad_lstick_up", INPUT_ACTION_UP)
inputBind("gamepad_lstick_down", INPUT_ACTION_DOWN)
inputBind("gamepad_lstick_left", INPUT_ACTION_LEFT)
inputBind("gamepad_lstick_right", INPUT_ACTION_RIGHT)
end
if INPUT_POINTER then
inputBind("mouse_x", INPUT_ACTION_POINTERX)
inputBind("mouse_y", INPUT_ACTION_POINTERY)
end
else
print("Unknown platform, no default input bindings set.")
end
sceneSet('scene/minesweeper.lua')

View File

@@ -9,13 +9,14 @@ module('text')
module('tileset')
module('texture')
module('input')
module('shader')
CELL_STATE_DEFAULT = 0
CELL_STATE_HOVER = 1
CELL_STATE_DOWN = 2
CELL_STATE_DISABLED = 3
screenSetBackground(colorBlack())
screenSetBackground(colorCornflowerBlue())
camera = cameraCreate(CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC)
-- tilesetUi = tilesetGetByName("ui")
@@ -62,7 +63,7 @@ function cellDraw(x, y, type)
slice = cellSliceDisabled
end
spriteBatchPush(textureCell,
spriteBatchPush(
x, y,
x + tilesetCell.tileWidth, y + tilesetCell.tileHeight,
colorWhite(),
@@ -171,36 +172,58 @@ function borderDraw(x, y, innerWidth, innerHeight)
)
end
x = 0
y = 0
function sceneDispose()
end
function sceneUpdate()
x = x + inputAxis(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT)
y = y + inputAxis(INPUT_ACTION_UP, INPUT_ACTION_DOWN)
end
function sceneRender()
-- Update camera
cameraPushMatrix(camera)
camera.bottom = screenGetHeight()
camera.bottom = 0
camera.top = screenGetHeight()
camera.right = screenGetWidth()
-- Update mouse position
if INPUT_POINTER then
mouseX = inputGetValue(INPUT_ACTION_POINTERX) * screenGetWidth()
mouseY = inputGetValue(INPUT_ACTION_POINTERY) * screenGetHeight()
shaderBind(SHADER_UNLIT)
proj = cameraGetProjectionMatrix(camera)
shaderSetMatrix(SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj)
view = cameraGetViewMatrix(camera)
shaderSetMatrix(SHADER_UNLIT, SHADER_UNLIT_VIEW, view)
-- Draw cursor
shaderSetTexture(SHADER_UNLIT, SHADER_UNLIT_TEXTURE, nil)
spriteBatchPush(
nil,
mouseX - 2, mouseY - 2,
mouseX + 2, mouseY + 2,
colorRed(),
0, 0,
1, 1
x, y,
x + 32, y + 32,
colorWhite()
)
end
spriteBatchFlush()
textDraw(10, 10, "Hello World\nHow are you?", colorRed())
spriteBatchFlush()
-- Update mouse position
-- if INPUT_POINTER then
-- mouseX = inputGetValue(INPUT_ACTION_POINTERX) * screenGetWidth()
-- mouseY = inputGetValue(INPUT_ACTION_POINTERY) * screenGetHeight()
-- -- Draw cursor
-- spriteBatchPush(
-- nil,
-- mouseX - 2, mouseY - 2,
-- mouseX + 2, mouseY + 2,
-- colorRed(),
-- 0, 0,
-- 1, 1
-- )
-- end
textDraw(10, 10, "Hello World")
-- textDraw(10, 10, "Hello World")
-- centerX = math.floor(screenGetWidth() / 2)
-- centerY = math.floor(screenGetHeight() / 2)
@@ -238,7 +261,4 @@ function sceneRender()
-- )
-- end
-- end
spriteBatchFlush()
cameraPopMatrix()
end

Binary file not shown.

Binary file not shown.

BIN
assets/ui/minogram.dtx Normal file

Binary file not shown.

View File

@@ -1,63 +0,0 @@
message(FATAL_ERROR "Configure Dolphin")
if(DUSK_TARGET_SYSTEM STREQUAL "gamecube" OR DUSK_TARGET_SYSTEM STREQUAL "wii")
# Override to make library and binary be the same.
set(DUSK_LIBRARY_TARGET_NAME "${DUSK_LIBRARY_TARGET_NAME}.elf" CACHE INTERNAL ${DUSK_CACHE_TARGET})
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti -fno-exceptions")
# configure_file(opengl.pc.in opengl.pc @ONLY)
find_package(PkgConfig REQUIRED)
pkg_check_modules(zip IMPORTED_TARGET libzip)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DOLPHIN
)
# Disable all warnings
target_compile_options(${DUSK_LIBRARY_TARGET_NAME} PRIVATE -w)
# Custom flags for cglm
set(CGLM_SHARED OFF CACHE BOOL "Build cglm shared" FORCE)
set(CGLM_STATIC ON CACHE BOOL "Build cglm static" FORCE)
find_package(cglm REQUIRED)
# Compile lua
include(FetchContent)
FetchContent_Declare(
liblua
URL https://www.lua.org/ftp/lua-5.5.0.tar.gz
)
FetchContent_MakeAvailable(liblua)
set(LUA_SRC_DIR "${liblua_SOURCE_DIR}/src")
set(LUA_C_FILES
lapi.c lauxlib.c lbaselib.c lcode.c lcorolib.c lctype.c ldblib.c ldebug.c
ldo.c ldump.c lfunc.c lgc.c linit.c liolib.c llex.c lmathlib.c lmem.c
loadlib.c lobject.c lopcodes.c loslib.c lparser.c lstate.c lstring.c
lstrlib.c ltable.c ltablib.c ltm.c lundump.c lutf8lib.c lvm.c lzio.c
)
list(TRANSFORM LUA_C_FILES PREPEND "${LUA_SRC_DIR}/")
add_library(liblua STATIC ${LUA_C_FILES})
target_include_directories(liblua PUBLIC "${LUA_SRC_DIR}")
target_compile_definitions(liblua PRIVATE LUA_USE_C89)
add_library(lua::lua ALIAS liblua)
set(Lua_FOUND TRUE CACHE BOOL "Lua found" FORCE)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
cglm
liblua
m
fat
PkgConfig::zip
)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
DISPLAY_WINDOW_WIDTH_DEFAULT=640
DISPLAY_WINDOW_HEIGHT_DEFAULT=480
DISPLAY_WIDTH=640
DISPLAY_HEIGHT=480
DISPLAY_SIZE_DYNAMIC=0
INPUT_GAMEPAD=1
THREAD_PTHREAD=1
TIME_FIXED=1
)

View File

@@ -1 +0,0 @@
include(cmake/configure/gamecube.cmake)

View File

@@ -1,26 +0,0 @@
find_package(SDL2 REQUIRED)
find_package(OpenGL REQUIRED)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
SDL2
pthread
OpenGL::GL
GL
m
)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
DISPLAY_SDL2=1
DISPLAY_WINDOW_WIDTH_DEFAULT=1080
DISPLAY_WINDOW_HEIGHT_DEFAULT=810
DISPLAY_SCREEN_HEIGHT_DEFAULT=270
DISPLAY_SHADER=1
INPUT_SDL2=1
INPUT_KEYBOARD=1
INPUT_POINTER=1
INPUT_GAMEPAD=1
THREAD_PTHREAD=1
TIME_SDL2=1
TIME_FIXED=0
)

View File

@@ -1,36 +0,0 @@
find_package(pspsdk REQUIRED)
find_package(SDL2 REQUIRED)
find_package(OpenGL REQUIRED)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${SDL2_LIBRARIES}
SDL2
pthread
OpenGL::GL
zip
bz2
z
mbedtls
mbedcrypto
lzma
m
)
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
${SDL2_INCLUDE_DIRS}
)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
DISPLAY_SDL2=1
DISPLAY_WINDOW_WIDTH_DEFAULT=480
DISPLAY_WINDOW_HEIGHT_DEFAULT=272
DISPLAY_WIDTH=480
DISPLAY_HEIGHT=272
DISPLAY_SIZE_DYNAMIC=0
DISPLAY_COLOR_TABLE=1
INPUT_SDL2=1
INPUT_GAMEPAD=1
THREAD_PTHREAD=1
TIME_FIXED=1
)

View File

@@ -1 +0,0 @@
include(cmake/configure/dolphin.cmake)

View File

@@ -1,106 +0,0 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
if(NOT TARGET pspsdk)
message(STATUS "Looking for PSPSDK...")
set(PSPSDK_FOUND FALSE CACHE INTERNAL "PSPSDK found")
set(PSPSDK_DOWNLOAD_DIR "${CMAKE_BINARY_DIR}/_pspsdk")
set(PSPSDK_SEARCH_ROOTS
"${PSPSDK_ROOT}"
"$ENV{PSPDEV}"
"$ENV{HOME}/pspdev"
"/usr/local/pspdev"
"/opt/pspdev"
"/usr/pspdev"
"${PSPSDK_DOWNLOAD_DIR}/pspdev"
)
foreach(root IN LISTS PSPSDK_SEARCH_ROOTS)
list(APPEND PSPSDK_BIN_HINTS "${root}/bin")
list(APPEND PSPSDK_INCLUDE_HINTS "${root}/include")
list(APPEND PSPSDK_LIB_HINTS "${root}/lib")
endforeach()
# Find PSP GCC
find_program(PSPSDK_PSP_GCC NAMES psp-gcc HINTS ${PSPSDK_BIN_HINTS})
# If we did not find it, download it.
if(NOT PSPSDK_PSP_GCC)
message(STATUS "psp-gcc not found in system paths. Downloading PSPSDK tarball...")
file(DOWNLOAD
"https://github.com/pspdev/pspdev/releases/download/v20260101/pspdev-ubuntu-latest-x86_64.tar.gz"
"${CMAKE_BINARY_DIR}/pspsdk.tar.gz"
EXPECTED_HASH SHA256=68fb6063323e695a43415a151b3dd9ded61d00605f02d20146cc6933c11830f8
SHOW_PROGRESS
)
# Make output dir
file(MAKE_DIRECTORY "${PSPSDK_DOWNLOAD_DIR}")
# Extract the tarball
execute_process(
COMMAND
${CMAKE_COMMAND} -E tar xzf "${CMAKE_BINARY_DIR}/pspsdk.tar.gz"
WORKING_DIRECTORY
"${PSPSDK_DOWNLOAD_DIR}"
RESULT_VARIABLE
tar_result
)
if(NOT tar_result EQUAL 0)
message(FATAL_ERROR "Failed to extract PSPSDK tarball")
endif()
# Retry discovery with extracted fallback
find_program(PSPSDK_PSP_GCC NAMES psp-gcc HINTS ${PSPSDK_BIN_HINTS})
endif()
if(PSPSDK_PSP_GCC)
get_filename_component(PSPSDK_BIN_ROOT "${PSPSDK_PSP_GCC}" DIRECTORY)
get_filename_component(PSPSDK_ROOT "${PSPSDK_BIN_ROOT}" DIRECTORY)
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
set(ENV{PSPDEV} "${PSPSDK_ROOT}")
set(CMAKE_TOOLCHAIN_FILE "${PSPSDK_ROOT}/psp/share/pspdev.cmake")
set(BUILD_PRX ON CACHE BOOL "Build PRX modules")
include("${PSPSDK_ROOT}/psp/share/pspdev.cmake")
set(CMAKE_C_COMPILER ${PSPSDK_BIN_ROOT}/psp-gcc)
set(CMAKE_CXX_COMPILER ${PSPSDK_BIN_ROOT}/psp-g++)
if(NOT DEFINED PSP)
message(FATAL_ERROR "PSP environment variable is not set correctly.")
endif()
add_library(pspsdk INTERFACE IMPORTED)
set_target_properties(pspsdk PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${PSPSDK_INCLUDE_DIR}"
INTERFACE_LINK_DIRECTORIES "${PSPSDK_LIB_DIR}"
)
target_include_directories(pspsdk
INTERFACE
${PSPDEV}/psp/include
${PSPDEV}/psp/sdk/include
)
target_link_directories(pspsdk
INTERFACE
${PSPDEV}/lib
${PSPDEV}/psp/lib
${PSPDEV}/psp/sdk/lib
)
target_link_libraries(pspsdk INTERFACE
pspdebug
pspdisplay
pspge
pspctrl
pspgu
pspaudio
pspaudiolib
psputility
pspvfpu
pspvram
psphprm
)
endif()
endif()

View File

@@ -1,12 +1,17 @@
set(DUSK_LIBRARY_TARGET_NAME "${DUSK_LIBRARY_TARGET_NAME}.elf" CACHE INTERNAL ${DUSK_CACHE_TARGET})
# Target definitions
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_DOLPHIN
DUSK_INPUT_GAMEPAD
DUSK_DISPLAY_WIDTH=640
DUSK_DISPLAY_HEIGHT=480
)
# Custom compiler flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti -fno-exceptions")
# configure_file(opengl.pc.in opengl.pc @ONLY)
# Need PkgConfig
find_package(PkgConfig REQUIRED)
pkg_check_modules(zip IMPORTED_TARGET libzip)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DOLPHIN
)
# Disable all warnings
target_compile_options(${DUSK_LIBRARY_TARGET_NAME} PRIVATE -w)
@@ -37,6 +42,7 @@ target_compile_definitions(liblua PRIVATE LUA_USE_C89)
add_library(lua::lua ALIAS liblua)
set(Lua_FOUND TRUE CACHE BOOL "Lua found" FORCE)
# Link libraries
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
cglm
liblua
@@ -46,7 +52,6 @@ target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
)
# Postbuild
set(DUSK_BINARY_TARGET_NAME_DOL "${DUSK_BUILD_DIR}/Dusk.dol")
add_custom_command(TARGET ${DUSK_BINARY_TARGET_NAME} POST_BUILD
COMMAND elf2dol

View File

@@ -0,0 +1,5 @@
include(cmake/targets/dolphin.cmake)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_GAMECUBE
)

View File

@@ -0,0 +1,45 @@
# Find link platform-specific libraries
set(OpenGL_GL_PREFERENCE LEGACY)
find_package(SDL2 REQUIRED)
find_library(EGL_LIB EGL REQUIRED)
find_library(GL_LIB GL REQUIRED)
find_package(OpenGL REQUIRED)
# Setup endianess at compile time to optimize.
include(TestBigEndian)
test_big_endian(IS_BIG_ENDIAN)
if(IS_BIG_ENDIAN)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_PLATFORM_ENDIAN_BIG
)
else()
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_PLATFORM_ENDIAN_LITTLE
)
endif()
# Link required libraries.
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
SDL2
pthread
OpenGL::GLES2
${GL_LIB}
${EGL_LIB}
m
)
# Define platform-specific macros.
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_SDL2
DUSK_OPENGL
DUSK_OPENGL_ES
DUSK_LINUX
DUSK_DISPLAY_SIZE_DYNAMIC
DUSK_DISPLAY_WIDTH_DEFAULT=640
DUSK_DISPLAY_HEIGHT_DEFAULT=480
DUSK_DISPLAY_SCREEN_HEIGHT=240
DUSK_INPUT_KEYBOARD
DUSK_INPUT_POINTER
DUSK_INPUT_GAMEPAD
DUSK_TIME_DYNAMIC
)

View File

@@ -2,6 +2,20 @@
find_package(SDL2 REQUIRED)
find_package(OpenGL REQUIRED)
# Setup endianess at compile time to optimize.
include(TestBigEndian)
test_big_endian(IS_BIG_ENDIAN)
if(IS_BIG_ENDIAN)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_PLATFORM_ENDIAN_BIG
)
else()
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_PLATFORM_ENDIAN_LITTLE
)
endif()
# Link required libraries.
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
SDL2
pthread
@@ -10,4 +24,18 @@ target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
m
)
# TEST
# Define platform-specific macros.
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_SDL2
DUSK_OPENGL
# DUSK_OPENGL_LEGACY
DUSK_LINUX
DUSK_DISPLAY_SIZE_DYNAMIC
DUSK_DISPLAY_WIDTH_DEFAULT=640
DUSK_DISPLAY_HEIGHT_DEFAULT=480
DUSK_DISPLAY_SCREEN_HEIGHT=240
DUSK_INPUT_KEYBOARD
DUSK_INPUT_POINTER
DUSK_INPUT_GAMEPAD
DUSK_TIME_DYNAMIC
)

View File

@@ -1,4 +1,3 @@
# find_package(pspsdk REQUIRED)
find_package(SDL2 REQUIRED)
find_package(OpenGL REQUIRED)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
@@ -13,11 +12,35 @@ target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
mbedcrypto
lzma
m
pspdebug
pspdisplay
pspge
pspctrl
pspgu
pspaudio
pspaudiolib
psputility
pspvfpu
pspvram
psphprm
)
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
${SDL2_INCLUDE_DIRS}
)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_SDL2
DUSK_OPENGL
DUSK_PSP
DUSK_INPUT_GAMEPAD
DUSK_PLATFORM_ENDIAN_LITTLE
DUSK_OPENGL_LEGACY
DUSK_DISPLAY_WIDTH=480
DUSK_DISPLAY_HEIGHT=272
)
# Postbuild, create .pbp file for PSP.
create_pbp_file(
TARGET "${DUSK_BINARY_TARGET_NAME}"

View File

@@ -1 +1,5 @@
include(./dolphin.cmake)
include(cmake/targets/dolphin.cmake)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_WII
)

View File

@@ -0,0 +1,29 @@
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
set(CMAKE_ASM_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_SYSROOT /)
set(CMAKE_C_COMPILER_TARGET aarch64-linux-gnu)
set(CMAKE_CXX_COMPILER_TARGET aarch64-linux-gnu)
set(CMAKE_FIND_ROOT_PATH
/usr/aarch64-linux-gnu
/usr/lib/aarch64-linux-gnu
/usr/include/aarch64-linux-gnu
)
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)
set(ENV{PKG_CONFIG_LIBDIR} "/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/share/pkgconfig")
set(ENV{PKG_CONFIG_PATH} "/usr/lib/aarch64-linux-gnu/pkgconfig")
set(CMAKE_PREFIX_PATH "/usr/aarch64-linux-gnu;/usr/lib/aarch64-linux-gnu")
# Optional: helps some Find modules
set(SDL2_DIR "/usr/lib/aarch64-linux-gnu/cmake/SDL2" CACHE PATH "")

35
docker/knulli/Dockerfile Normal file
View File

@@ -0,0 +1,35 @@
FROM debian:trixie
ENV DEBIAN_FRONTEND=noninteractive
WORKDIR /workdir
RUN dpkg --add-architecture arm64 && \
apt-get update && \
apt-get install -y --no-install-recommends \
crossbuild-essential-arm64 \
ca-certificates \
pkg-config \
cmake \
make \
ninja-build \
git \
file \
python3 \
python3-pip \
python3-polib \
python3-pil \
python3-dotenv \
python3-pyqt5 \
python3-opengl \
liblua5.4-dev:arm64 \
xz-utils:arm64 \
libbz2-dev:arm64 \
zlib1g-dev:arm64 \
libzip-dev:arm64 \
libssl-dev:arm64 \
libsdl2-dev:arm64 \
liblzma-dev:arm64 \
libopengl0:arm64 \
libgl1:arm64 \
libegl1:arm64 \
libgles2:arm64 \
libgl1-mesa-dev:arm64 && \
rm -rf /var/lib/apt/lists/*

24
docker/linux/Dockerfile Normal file
View File

@@ -0,0 +1,24 @@
FROM ubuntu:latest
WORKDIR /workdir
RUN apt-get update
RUN apt-get install -y \
build-essential \
cmake \
python3 \
python3-pip \
python3-polib \
python3-pil \
libsdl2-dev \
libgl1-mesa-dev \
libzip-dev \
python3-dotenv \
python3-pyqt5 \
python3-opengl \
liblua5.3-dev \
xz-utils \
libbz2-dev \
zlib1g-dev \
libzip-dev \
libbz2-dev \
git \
libssl-dev

View File

@@ -2,6 +2,6 @@ FROM pspdev/pspdev:latest
WORKDIR /workdir
RUN apk add --no-cache \
python3 \
py3-pip
py3-pip \
py3-dotenv
VOLUME ["/workdir"]
CMD [ "/bin/bash", "-c", "./scripts/build-psp.sh" ]

41
editor/.gitignore vendored Normal file
View File

@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

8
editor/next.config.ts Normal file
View File

@@ -0,0 +1,8 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
reactCompiler: true,
};
export default nextConfig;

23
editor/package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "dusk-editor",
"version": "0.1.0",
"private": true,
"scripts": {
"start:dev": "next dev",
"build:prod": "next build",
"start:prod": "next start"
},
"dependencies": {
"next": "16.2.1",
"react": "19.2.4",
"react-dom": "19.2.4",
"sass": "^1.98.0"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"babel-plugin-react-compiler": "1.0.0",
"typescript": "^5"
}
}

23
editor/src/app/layout.tsx Normal file
View File

@@ -0,0 +1,23 @@
import type { Metadata } from "next";
import "@/styles/styles.scss";
import Navbar from "@/components/Navbar/Navbar";
export const metadata: Metadata = {
title: "Dusk Editor",
description: "Editor for the Dusk Game Engine",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body>
<Navbar />
{children}
</body>
</html>
);
}

9
editor/src/app/page.tsx Normal file
View File

@@ -0,0 +1,9 @@
import Link from "next/link";
export default function Home() {
return (
<div>
<Link href="/texture-creator">Go to Texture Creator</Link>
</div>
);
}

View File

@@ -0,0 +1,17 @@
'use client';
import React, { createContext, useContext, useState } from "react";
type TexturePageContextType = {
};
const TexturePageContext = createContext<TexturePageContextType | undefined>(undefined);
const TextureCreatorPage: React.FC<{ children: React.ReactNode }> = (props) => {
return (
<TexturePageContext.Provider value={{ }}>
<Typography</Typography>
</TexturePageContext.Provider>
);
}
export default TextureCreatorPage;

View File

@@ -0,0 +1,12 @@
import React from 'react';
import * as styles from './Navbar.module.scss';
const Navbar:React.FC<{}> = (props) => {
return (
<nav className={styles.navbar}>
navbar
</nav>
);
}
export default Navbar;

View File

@@ -0,0 +1,14 @@
const Typography:React.FC<{
element?:string|React.JSXElementConstructor<any>;
}> = (props) => {
const Element = props.element || 'p';
return (
<Element>
{props.children}
</Element>
)
};
export default Typography;

View File

@@ -0,0 +1,17 @@
import React, { createContext, useContext, useState } from "react";
type TextureContextType = {
};
const TextureContext = createContext<TextureContextType | undefined>(undefined);
const TextureCreator: React.FC<{ children: React.ReactNode }> = (props) => {
return (
<TextureContext.Provider value={{ }}>
{props.children}
</TextureContext.Provider>
);
}
export default TextureCreator;

View File

@@ -0,0 +1,4 @@
a {
color: inherit;
text-decoration: none;
}

View File

@@ -0,0 +1,3 @@
* {
box-sizing: border-box;
}

View File

@@ -0,0 +1,8 @@
body {
margin: 0;
padding: 0;
max-width: 100vw;
overflow-x: hidden;
font-size: 16px;
font-family: Arial, Helvetica, sans-serif;
}

View File

@@ -0,0 +1,6 @@
html {
margin: 0;
padding: 0;
max-width: 100vw;
overflow-x: hidden;
}

View File

@@ -0,0 +1,3 @@
:root {
}

View File

@@ -0,0 +1,12 @@
// Elements
@use './elements/root.scss';
@use './elements/all.scss';
@use './elements/a.scss';
@use './elements/html.scss';
@use './elements/body.scss';
// Objects
// Components
// Utilities

34
editor/tsconfig.json Normal file
View File

@@ -0,0 +1,34 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts",
"**/*.mts"
],
"exclude": ["node_modules"]
}

3
scripts/build-knulli-docker.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
docker build -t dusk-knulli -f docker/knulli/Dockerfile .
docker run --rm -v $(pwd):/workdir dusk-knulli /bin/bash -c "./scripts/build-knulli.sh"

24
scripts/build-knulli.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/bash
cmake -S . -B build-knulli -G Ninja \
-DDUSK_BUILD_TESTS=ON \
-DDUSK_TARGET_SYSTEM=knulli \
-DCMAKE_TOOLCHAIN_FILE=./cmake/toolchains/aarch64-linux-gnu.cmake \
-DCMAKE_BUILD_TYPE=Release
cmake --build build-knulli -- -j$(nproc)
# Copy necessary libs out
mkdir -p ./build-knulli/dusk
cp ./build-knulli/Dusk ./build-knulli/dusk/Dusk
cp ./build-knulli/dusk.dsk ./build-knulli/dusk/dusk.dsk
echo '#!/bin/bash' > build-knulli/dusk/Dusk.sh
echo 'cd "$(dirname "$(readlink -f "$0")")"' >> build-knulli/dusk/Dusk.sh
echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(dirname "$(readlink -f "$0")")' >> build-knulli/dusk/Dusk.sh
echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/gl4es' >> build-knulli/dusk/Dusk.sh
echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib' >> build-knulli/dusk/Dusk.sh
echo '$(dirname "$(readlink -f "$0")")/Dusk' >> build-knulli/dusk/Dusk.sh
chmod +x build-knulli/dusk/Dusk.sh
cp /usr/lib/aarch64-linux-gnu/liblua5.4.so.0 build-knulli/dusk/
# cp /usr/lib/aarch64-linux-gnu/libSDL2-2.0.so.0 build-knulli/dusk/
# cp /usr/lib/aarch64-linux-gnu/libGL.so.1 build-knulli/dusk/
# cp /usr/lib/aarch64-linux-gnu/libEGL.so.1 build-knulli/dusk/
# cp /usr/lib/aarch64-linux-gnu/libGLESv2.so.2 build-knulli/dusk/

3
scripts/build-linux-docker.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
docker build -t dusk-linux -f docker/linux/Dockerfile .
docker run --rm -v $(pwd):/workdir dusk-linux /bin/bash -c "./scripts/build-linux.sh"

3
scripts/build-linux.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
cmake -S . -B build-linux -DDUSK_BUILD_TESTS=ON -DDUSK_TARGET_SYSTEM=linux
cmake --build build-linux -- -j$(nproc)

View File

@@ -1,3 +1,3 @@
#!/bin/bash
docker build -t dusk-psp -f docker/psp/Dockerfile .
docker run --rm -v $(pwd):/workdir dusk-psp
docker run --rm -v $(pwd):/workdir dusk-psp /bin/bash -c "./scripts/build-psp.sh"

View File

@@ -6,5 +6,7 @@ fi
mkdir -p build-psp
cd build-psp
cmake .. -DDUSK_TARGET_SYSTEM=psp -DCMAKE_TOOLCHAIN_FILE=$PSPDEV/psp/share/pspdev.cmake
psp-cmake -DDUSK_TARGET_SYSTEM=psp -DCMAKE_TOOLCHAIN_FILE=$PSPDEV/psp/share/pspdev.cmake -DBUILD_PRX=1 ..
make -j$(nproc)
# psp-cmake -DDUSK_TARGET_SYSTEM=psp -DCMAKE_TOOLCHAIN_FILE=$PSPDEV/psp/share/pspdev.cmake -DBUILD_PRX=1 -DCMAKE_BUILD_TYPE=Debug ..
# make

3
scripts/test-linux-docker.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
docker build -t dusk-linux -f docker/linux/Dockerfile .
docker run --rm -v $(pwd):/workdir dusk-linux /bin/bash -c "./scripts/test-linux.sh"

4
scripts/test-linux.sh Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
cmake -S . -B build-tests -DDUSK_BUILD_TESTS=ON -DDUSK_TARGET_SYSTEM=linux
cmake --build build-tests -- -j$(nproc)
ctest --output-on-failure --test-dir build-tests

View File

@@ -1,71 +1,22 @@
# Copyright (c) 2025 Dominic Masters
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Required Libraries
if(NOT cglm_FOUND)
find_package(cglm REQUIRED)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC cglm)
add_subdirectory(dusk)
add_subdirectory(duskrpg)
if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "knulli")
add_subdirectory(dusklinux)
add_subdirectory(dusksdl2)
add_subdirectory(duskgl)
elseif(DUSK_TARGET_SYSTEM STREQUAL "psp")
add_subdirectory(duskpsp)
add_subdirectory(dusksdl2)
add_subdirectory(duskgl)
elseif(DUSK_TARGET_SYSTEM STREQUAL "gamecube" OR DUSK_TARGET_SYSTEM STREQUAL "wii")
add_subdirectory(duskdolphin)
endif()
if(NOT libzip_FOUND)
find_package(libzip REQUIRED)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC zip)
endif()
if(NOT Lua_FOUND)
find_package(Lua REQUIRED)
if(Lua_FOUND AND NOT TARGET Lua::Lua)
add_library(Lua::Lua INTERFACE IMPORTED)
set_target_properties(
Lua::Lua
PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${LUA_INCLUDE_DIR}"
INTERFACE_LINK_LIBRARIES "${LUA_LIBRARIES}"
)
endif()
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC Lua::Lua)
endif()
# Includes
target_include_directories(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
${CMAKE_CURRENT_LIST_DIR}
)
# Main Binary Source
target_sources(${DUSK_BINARY_TARGET_NAME}
PUBLIC
main.c
)
# Defs
dusk_env_to_h(duskdefs.env duskdefs.h)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
DUSK_TARGET_SYSTEM="${DUSK_TARGET_SYSTEM}"
)
# Subdirs
add_subdirectory(assert)
add_subdirectory(asset)
add_subdirectory(debug)
add_subdirectory(display)
add_subdirectory(engine)
add_subdirectory(error)
add_subdirectory(event)
add_subdirectory(input)
add_subdirectory(item)
add_subdirectory(locale)
add_subdirectory(map)
add_subdirectory(scene)
add_subdirectory(script)
add_subdirectory(story)
add_subdirectory(time)
add_subdirectory(ui)
add_subdirectory(util)
# if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "psp")
# add_subdirectory(thread)
# endif()

View File

@@ -1,345 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "asset.h"
#include "util/memory.h"
#include "util/string.h"
#include "assert/assert.h"
#include "asset/assettype.h"
#include "engine/engine.h"
#include "debug/debug.h"
#include "util/string.h"
errorret_t assetInit(void) {
memoryZero(&ASSET, sizeof(asset_t));
#if DOLPHIN
// Init FAT driver.
if(!fatInitDefault()) errorThrow("Failed to initialize FAT filesystem.");
char_t **dolphinSearchPath = (char_t **)ASSET_DOLPHIN_PATHS;
char_t foundPath[FILENAME_MAX];
foundPath[0] = '\0';
do {
// Try open dir
DIR *pdir = opendir(*dolphinSearchPath);
if(pdir == NULL) continue;
// Scan if file is present
while(true) {
struct dirent* pent = readdir(pdir);
if(pent == NULL) break;
if(stringCompareInsensitive(pent->d_name, ASSET_FILE) != 0) {
continue;
}
// Copy out filename
snprintf(
foundPath,
FILENAME_MAX,
"%s/%s",
*dolphinSearchPath,
ASSET_FILE
);
break;
}
// Close dir.
closedir(pdir);
// Did we find the file here?
if(foundPath[0] != '\0') break;
} while(*(++dolphinSearchPath) != NULL);
if(foundPath[0] != '\0') {
}
// Did we find the asset file?
if(foundPath[0] == '\0') {
errorThrow("Failed to find asset file on FAT filesystem.");
}
ASSET.zip = zip_open(foundPath, ZIP_RDONLY, NULL);
if(ASSET.zip == NULL) {
errorThrow("Failed to open asset file on FAT filesystem.");
}
errorOk();
#endif
// Engine may have been provided the launch path
if(ENGINE.argc > 0) {
// Get the directory of the executable
char_t buffer[FILENAME_MAX];
stringCopy(buffer, ENGINE.argv[0], FILENAME_MAX);
size_t len = strlen(buffer);
// Normalize slashes
for(size_t i = 0; i < FILENAME_MAX; i++) {
if(buffer[i] == '\0') break;
if(buffer[i] == '\\') buffer[i] = '/';
}
// Now find the last slash
char_t *end = buffer + len - 1;
do {
end--;
if(*end == '/') {
*end = '\0';
break;
}
} while(end != buffer);
// Did we find a slash?
if(end != buffer) {
// We found the directory, set as system path
stringCopy(ASSET.systemPath, buffer, FILENAME_MAX);
}
}
// Default system path, intended to be overridden by the platform
stringCopy(ASSET.systemPath, ".", FILENAME_MAX);
// PSP specific asset loading.
#if PSP
assertTrue(ENGINE.argc >= 1, "PSP requires launch argument.");
// PSP is given either the prx OR the PBP file.
// In the format of "ms0:/PSP/GAME/DUSK/EBOOT.PBP" or "host0:/Dusk.prx"
// IF the file is the PBP file, we are loading directly on the PSP itself.
// IF the file is the .prx then we are debugging and fopen will return
// relative filepaths correctly, e.g. host0:/dusk.dsk will be on host.
if(
stringEndsWithCaseInsensitive(ENGINE.argv[0], ".pbp") ||
ASSET_PBP_READ_PBP_FROM_HOST
) {
const char_t *pbpPath = (
ASSET_PBP_READ_PBP_FROM_HOST ? "./EBOOT.PBP" : ENGINE.argv[0]
);
ASSET.pbpFile = fopen(pbpPath, "rb");
if(ASSET.pbpFile == NULL) {
errorThrow("Failed to open PBP file: %s", pbpPath);
}
// Get size of PBP file.
if(fseek(ASSET.pbpFile, 0, SEEK_END) != 0) {
fclose(ASSET.pbpFile);
errorThrow("Failed to seek to end of PBP file : %s", pbpPath);
}
size_t pbpSize = ftell(ASSET.pbpFile);
// Rewind to start
if(fseek(ASSET.pbpFile, 0, SEEK_SET) != 0) {
fclose(ASSET.pbpFile);
errorThrow("Failed to seek to start of PBP file : %s", pbpPath);
}
// Read the PBP header
size_t read = fread(
&ASSET.pbpHeader,
1,
sizeof(assetpbp_t),
ASSET.pbpFile
);
if(read != sizeof(assetpbp_t)) {
fclose(ASSET.pbpFile);
errorThrow("Failed to read PBP header", pbpPath);
}
if(memoryCompare(
ASSET.pbpHeader.signature,
ASSET_PBP_SIGNATURE,
sizeof(ASSET_PBP_SIGNATURE)
) != 0) {
fclose(ASSET.pbpFile);
errorThrow("Invalid PBP signature in file: %s", pbpPath);
}
// If we seek to the PSAR offset, we can read the WAD file from there
if(fseek(ASSET.pbpFile, ASSET.pbpHeader.psarOffset, SEEK_SET) != 0) {
fclose(ASSET.pbpFile);
errorThrow("Failed to seek to PSAR offset in PBP file: %s", pbpPath);
}
zip_uint64_t zipPsarOffset = (zip_uint64_t)ASSET.pbpHeader.psarOffset;
zip_int64_t zipPsarSize = (zip_int64_t)(
pbpSize - ASSET.pbpHeader.psarOffset
);
zip_source_t *psarSource = zip_source_filep_create(
ASSET.pbpFile,
zipPsarOffset,
zipPsarSize,
NULL
);
if(psarSource == NULL) {
fclose(ASSET.pbpFile);
errorThrow("Failed to create zip source in PBP file: %s", pbpPath);
}
ASSET.zip = zip_open_from_source(
psarSource,
ZIP_RDONLY,
NULL
);
if(ASSET.zip == NULL) {
zip_source_free(psarSource);
fclose(ASSET.pbpFile);
errorThrow("Failed to open zip from PBP file: %s", pbpPath);
}
errorOk();
}
#endif
// Open zip file
char_t searchPath[FILENAME_MAX];
const char_t **path = ASSET_SEARCH_PATHS;
do {
sprintf(
searchPath,
*path,
ASSET.systemPath,
ASSET_FILE
);
// Try open
ASSET.zip = zip_open(searchPath, ZIP_RDONLY, NULL);
if(ASSET.zip == NULL) continue;
break;// Found!
} while(*(++path) != NULL);
// Did we open the asset?
if(ASSET.zip == NULL) errorThrow("Failed to open asset file.");
errorOk();
}
bool_t assetFileExists(const char_t *filename) {
assertStrLenMax(filename, FILENAME_MAX, "Filename too long.");
zip_int64_t idx = zip_name_locate(ASSET.zip, filename, 0);
if(idx < 0) return false;
return true;
}
errorret_t assetLoad(const char_t *filename, void *output) {
assertStrLenMax(filename, FILENAME_MAX, "Filename too long.");
assertNotNull(output, "Output pointer cannot be NULL.");
// Determine the asset type by reading the extension
const assettypedef_t *def = NULL;
for(uint_fast8_t i = 0; i < ASSET_TYPE_COUNT; i++) {
const assettypedef_t *cmp = &ASSET_TYPE_DEFINITIONS[i];
assertNotNull(cmp, "Asset type definition cannot be NULL.");
if(cmp->extension == NULL) continue;
if(!stringEndsWithCaseInsensitive(filename, cmp->extension)) continue;
def = cmp;
break;
}
if(def == NULL) {
errorThrow("Unknown asset type for file: %s", filename);
}
// Get file size of the asset.
zip_stat_t st;
zip_stat_init(&st);
if(!zip_stat(ASSET.zip, filename, 0, &st) == 0) {
errorThrow("Failed to stat asset file: %s", filename);
}
// Minimum file size.
zip_int64_t fileSize = (zip_int64_t)st.size;
if(fileSize <= 0) {
errorThrow("Asset file is empty: %s", filename);
}
// Try to open the file
zip_file_t *file = zip_fopen(ASSET.zip, filename, 0);
if(file == NULL) {
errorThrow("Failed to open asset file: %s", filename);
}
// Load the asset data
switch(def->loadStrategy) {
case ASSET_LOAD_STRAT_ENTIRE:
assertNotNull(def->entire, "Asset load function cannot be NULL.");
// Must have more to read
if(fileSize <= 0) {
zip_fclose(file);
errorThrow("No data remaining to read for asset: %s", filename);
}
if(fileSize > def->dataSize) {
zip_fclose(file);
errorThrow(
"Asset file has too much data remaining after header: %s",
filename
);
}
// Create space to read the entire asset data
void *data = memoryAllocate(fileSize);
if(!data) {
zip_fclose(file);
errorThrow("Failed to allocate memory for asset data of file: %s", filename);
}
// Read in the asset data.
zip_int64_t bytesRead = zip_fread(file, data, fileSize);
if(bytesRead == 0 || bytesRead > fileSize) {
memoryFree(data);
zip_fclose(file);
errorThrow("Failed to read asset data for file: %s", filename);
}
fileSize -= bytesRead;
// Close the file now we have the data
zip_fclose(file);
// Pass to the asset type loader
assetentire_t entire = {
.data = data,
.output = output
};
errorret_t ret = def->entire(entire);
memoryFree(data);
errorChain(ret);
break;
case ASSET_LOAD_STRAT_CUSTOM:
assertNotNull(def->custom, "Asset load function cannot be NULL.");
assetcustom_t customData = {
.zipFile = file,
.output = output
};
errorChain(def->custom(customData));
break;
default:
assertUnreachable("Unknown asset load strategy.");
}
errorOk();
}
void assetDispose(void) {
if(ASSET.zip != NULL) {
zip_close(ASSET.zip);
ASSET.zip = NULL;
}
#if PSP
if(ASSET.pbpFile != NULL) {
fclose(ASSET.pbpFile);
ASSET.pbpFile = NULL;
}
#endif
}

View File

@@ -1,114 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include "assettype.h"
#if PSP
#define ASSET_PBP_READ_PBP_FROM_HOST 0
#define ASSET_PBP_SIGNATURE_SIZE 4
#define ASSET_PBP_SIGNATURE "\0PBP"
typedef struct {
char_t signature[ASSET_PBP_SIGNATURE_SIZE];
uint32_t version;
uint32_t sfoOffset;
uint32_t icon0Offset;
uint32_t icon1Offset;
uint32_t pic0Offset;
uint32_t pic1Offset;
uint32_t snd0Offset;
uint32_t pspOffset;
uint32_t psarOffset;
} assetpbp_t;
#elif DOLPHIN
#include <fat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
static const char_t *ASSET_DOLPHIN_PATHS[] = {
"/",
"/Dusk",
"/dusk",
"/DUSK",
"/apps",
"/apps/Dusk",
"/apps/dusk",
"/apps/DUSK",
".",
"./",
"./Dusk",
"./dusk",
"./DUSK",
"./apps",
"./apps/Dusk",
"./apps/dusk",
"./apps/DUSK",
NULL
};
#endif
#define ASSET_FILE "dusk.dsk"
#define ASSET_HEADER_SIZE 3
static const char_t *ASSET_SEARCH_PATHS[] = {
"%s/%s",
"%s",
"../%s",
"../../%s",
"data/%s",
"../data/%s",
NULL
};
typedef struct {
zip_t *zip;
char_t systemPath[FILENAME_MAX];
uint8_t assetCount;
// PSP specific information.
#if PSP
FILE *pbpFile;
assetpbp_t pbpHeader;
#endif
} asset_t;
static asset_t ASSET;
/**
* Initializes the asset system.
*/
errorret_t assetInit(void);
/**
* Checks if an asset file exists.
*
* @param filename The filename of the asset to check.
* @return true if the asset file exists, false otherwise.
*/
bool_t assetFileExists(const char_t *filename);
/**
* Loads an asset by its filename, the output type depends on the asset type.
*
* @param filename The filename of the asset to retrieve.
* @param output The output pointer to store the loaded asset data.
* @return An error code if the asset could not be loaded.
*/
errorret_t assetLoad(const char_t *filename, void *output);
/**
* Disposes/cleans up the asset system.
*/
void assetDispose(void);

View File

@@ -1,95 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "debug.h"
#if DOLPHIN
#include "display/display.h"
static char_t DEBUG_ERROR_BUFFER[16*1024] = {0};
#endif
void debugPrint(const char_t *message, ...) {
va_list args;
va_start(args, message);
vprintf(message, args);
va_end(args);
#if PSP
FILE *file = fopen("ms0:/PSP/GAME/Dusk/debug.log", "a");
if(file) {
va_start(args, message);
vfprintf(file, message, args);
va_end(args);
fclose(file);
}
#elif DOLPHIN
// append to error buffer
size_t start = strlen(DEBUG_ERROR_BUFFER);
va_start(args, message);
vsnprintf(
DEBUG_ERROR_BUFFER + start,
sizeof(DEBUG_ERROR_BUFFER) - start,
message,
args
);
va_end(args);
#endif
}
void debugFlush() {
#if PSP
// No buffering, so nothing to flush
#elif DOLPHIN
// Either create graphics, or hijack the displays' graphics.
void *xfb = NULL;
GXRModeObj *rmode = NULL;
void *framebuffer;
if(DISPLAY.frameBuffer[0]) {
console_init(
DISPLAY.frameBuffer[0],
20,
20,
DISPLAY.screenMode->fbWidth,
DISPLAY.screenMode->xfbHeight,
DISPLAY.screenMode->fbWidth * VI_DISPLAY_PIX_SZ
);
} else {
VIDEO_Init();
rmode = VIDEO_GetPreferredMode(NULL);
framebuffer = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
console_init(
framebuffer,
20,
20,
rmode->fbWidth,
rmode->xfbHeight,
rmode->fbWidth*VI_DISPLAY_PIX_SZ
);
VIDEO_Configure(rmode);
VIDEO_SetNextFramebuffer(framebuffer);
VIDEO_SetBlack(FALSE);
VIDEO_Flush();
VIDEO_WaitVSync();
if(rmode->viTVMode&VI_NON_INTERLACE) VIDEO_WaitVSync();
}
// Printf
printf("SOB\n");
printf(DEBUG_ERROR_BUFFER);
printf("\nEOB.");
while(SYS_MainLoop()) {
VIDEO_WaitVSync();
}
#else
fflush(stdout);
#endif
}

View File

@@ -1,267 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "camera.h"
#include "display/display.h"
#include "assert/assert.h"
#include "display/framebuffer.h"
#include "display/screen.h"
void cameraInit(camera_t *camera) {
cameraInitPerspective(camera);
}
void cameraInitPerspective(camera_t *camera) {
assertNotNull(camera, "Not a camera component");
camera->projType = CAMERA_PROJECTION_TYPE_PERSPECTIVE;
camera->perspective.fov = glm_rad(45.0f);
camera->nearClip = 0.1f;
camera->farClip = 10000.0f;
camera->viewType = CAMERA_VIEW_TYPE_LOOKAT;
glm_vec3_copy((vec3){ 5.0f, 5.0f, 5.0f }, camera->lookat.position);
glm_vec3_copy((vec3){ 0.0f, 1.0f, 0.0f }, camera->lookat.up);
glm_vec3_copy((vec3){ 0.0f, 0.0f, 0.0f }, camera->lookat.target);
}
void cameraInitOrthographic(camera_t *camera) {
assertNotNull(camera, "Not a camera component");
camera->projType = CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC;
camera->orthographic.left = 0.0f;
camera->orthographic.right = SCREEN.width;
camera->orthographic.top = 0.0f;
camera->orthographic.bottom = SCREEN.height;
camera->nearClip = -1.0f;
camera->farClip = 1.0f;
camera->viewType = CAMERA_VIEW_TYPE_2D;
glm_vec2_copy((vec2){ 0.0f, 0.0f }, camera->_2d.position);
camera->_2d.zoom = 1.0f;
}
void cameraPushMatrix(camera_t *camera) {
assertNotNull(camera, "Not a camera component");
#if DOLPHIN
Mtx44 guProjection;
Mtx guView;
Mtx modelView;
switch(camera->projType) {
case CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC:
guOrtho(
guProjection,
camera->orthographic.top,
camera->orthographic.bottom,
camera->orthographic.left,
camera->orthographic.right,
camera->nearClip,
camera->farClip
);
break;
case CAMERA_PROJECTION_TYPE_PERSPECTIVE:
guPerspective(
guProjection,
// FOV is in degrees.
camera->perspective.fov * (180.0f / GLM_PIf),
(float_t)frameBufferGetWidth(FRAMEBUFFER_BOUND) /
(float_t)frameBufferGetHeight(FRAMEBUFFER_BOUND),
camera->nearClip,
camera->farClip
);
break;
case CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED:
assertUnreachable("Flipped perspective not implemented on Dolphin");
break;
default:
assertUnreachable("Invalid camera projection type");
}
switch(camera->viewType) {
case CAMERA_VIEW_TYPE_LOOKAT:
guVector eye = {
camera->lookat.position[0],
camera->lookat.position[1],
camera->lookat.position[2]
};
guVector up = {
camera->lookat.up[0],
camera->lookat.up[1],
camera->lookat.up[2]
};
guVector look = {
camera->lookat.target[0],
camera->lookat.target[1],
camera->lookat.target[2]
};
guLookAt(guView, &eye, &up, &look);
break;
case CAMERA_VIEW_TYPE_MATRIX:
assertUnreachable("Matrix camera not implemented");
break;
case CAMERA_VIEW_TYPE_LOOKAT_PIXEL_PERFECT:
assertUnreachable("Pixel perfect camera not implemented");
break;
case CAMERA_VIEW_TYPE_2D:
guMtxIdentity(guView);
guMtxTrans(guView, -camera->_2d.position[0], -camera->_2d.position[1], 0.0f);
guMtxScale(guView, camera->_2d.zoom, camera->_2d.zoom, 1.0f);
break;
default:
assertUnreachable("Invalid camera view type");
}
// Set Projection Matrix
GX_LoadProjectionMtx(
guProjection,
camera->projType == CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC ?
GX_ORTHOGRAPHIC :
GX_PERSPECTIVE
);
// Set view and model matrix. Dunno how I'll handle models but whatever.
guMtxIdentity(modelView);
guMtxTransApply(modelView, modelView, 0.0F, 0.0F, 0.0F);
guMtxConcat(guView,modelView,modelView);
GX_LoadPosMtxImm(modelView, GX_PNMTX0);
return;
#endif
mat4 projection;
mat4 view;
switch(camera->projType) {
case CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC: {
assertTrue(
camera->orthographic.right != camera->orthographic.left &&
camera->orthographic.top != camera->orthographic.bottom,
"Invalid orthographic projection parameters"
);
glm_ortho(
camera->orthographic.left,
camera->orthographic.right,
camera->orthographic.bottom,
camera->orthographic.top,
camera->nearClip,
camera->farClip,
projection
);
break;
}
case CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED:
case CAMERA_PROJECTION_TYPE_PERSPECTIVE: {
const float_t aspect = (
(float_t)frameBufferGetWidth(FRAMEBUFFER_BOUND) /
(float_t)frameBufferGetHeight(FRAMEBUFFER_BOUND)
);
glm_perspective(
camera->perspective.fov,
aspect,
camera->nearClip,
camera->farClip,
projection
);
if(camera->projType == CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED) {
// Flip Y axis
projection[1][1] *= -1;
}
break;
}
}
switch(camera->viewType) {
case CAMERA_VIEW_TYPE_MATRIX:
glm_mat4_copy(camera->view, view);
break;
case CAMERA_VIEW_TYPE_LOOKAT:
glm_lookat(
camera->lookat.position,
camera->lookat.target,
camera->lookat.up,
view
);
break;
case CAMERA_VIEW_TYPE_LOOKAT_PIXEL_PERFECT:
assertTrue(
camera->projType == CAMERA_PROJECTION_TYPE_PERSPECTIVE ||
camera->projType == CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED,
"Pixel perfect camera view requires perspective projection"
);
// const float_t viewportHeight = (
// (float_t)frameBufferGetHeight(FRAMEBUFFER_BOUND)
// );
const float_t viewportHeight = (float_t)SCREEN.height;
const float_t z = (viewportHeight / 2.0f) / (
camera->lookatPixelPerfect.pixelsPerUnit *
tanf(camera->perspective.fov / 2.0f)
);
vec3 position;
glm_vec3_copy(camera->lookatPixelPerfect.target, position);
glm_vec3_add(position, camera->lookatPixelPerfect.offset, position);
position[2] += z;
glm_lookat(
position,
camera->lookatPixelPerfect.target,
camera->lookatPixelPerfect.up,
view
);
break;
case CAMERA_VIEW_TYPE_2D:
glm_mat4_identity(view);
glm_translate(view, (vec3){
-camera->_2d.position[0],
-camera->_2d.position[1],
0.0f
});
glm_scale(view, (vec3){
camera->_2d.zoom,
camera->_2d.zoom,
1.0f
});
break;
default:
assertUnreachable("Invalid camera view type");
}
#if DISPLAY_SDL2
// mat4 pv;
// glm_mat4_mul(projection, camera->transform, pv);
glPushMatrix();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glLoadMatrixf((const GLfloat*)projection);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glLoadMatrixf((const GLfloat*)view);
#endif
}
void cameraPopMatrix(void) {
#if DISPLAY_SDL2
glPopMatrix();
#endif
}

View File

@@ -1,287 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/display.h"
#include "engine/engine.h"
#include "display/framebuffer.h"
#include "scene/scene.h"
#include "display/spritebatch.h"
#include "display/mesh/quad.h"
#include "display/screen.h"
#include "ui/ui.h"
#include "debug/debug.h"
#include "display/text.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/string.h"
#include "asset/asset.h"
display_t DISPLAY = { 0 };
errorret_t displayInit(void) {
memoryZero(&DISPLAY, sizeof(DISPLAY));
#if DISPLAY_SDL2
uint32_t flags = SDL_INIT_VIDEO;
#if INPUT_GAMEPAD == 1
flags |= SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK;
#endif
if(SDL_Init(flags) != 0) {
errorThrow("SDL Failed to Initialize: %s", SDL_GetError());
}
// Set OpenGL attributes (Needs to be done now or later?)
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
// Create window with OpenGL flag.
DISPLAY.window = SDL_CreateWindow(
"Dusk",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
DISPLAY_WINDOW_WIDTH_DEFAULT,
DISPLAY_WINDOW_HEIGHT_DEFAULT,
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI |
SDL_WINDOW_OPENGL
);
if(!DISPLAY.window) {
errorThrow("SDL_CreateWindow failed: %s", SDL_GetError());
}
// Create OpenGL context
DISPLAY.glContext = SDL_GL_CreateContext(DISPLAY.window);
if(!DISPLAY.glContext) {
errorThrow("SDL_GL_CreateContext failed: %s", SDL_GetError());
}
SDL_GL_SetSwapInterval(1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
glDisable(GL_CULL_FACE);
glDisable(GL_LIGHTING);// PSP defaults this on?
glShadeModel(GL_SMOOTH); // Fixes color on PSP?
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glClearDepth(1.0f);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glEnableClientState(GL_COLOR_ARRAY);// To confirm: every frame on PSP?
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
// Get and validate GL state.
GLenum err = glGetError();
if(err != GL_NO_ERROR) {
assertUnreachable("GL Error before checking support");
}
// Check if paletted textures are supported.
#if PSP
DISPLAY.usingShaderedPalettes = false;
#else
GLint mask = 0;
glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &mask);
DISPLAY.usingShaderedPalettes = true;
if(mask & GL_CONTEXT_CORE_PROFILE_BIT) {
GLint numExtens = 0;
glGetIntegerv(GL_NUM_EXTENSIONS, &numExtens);
for(GLint i = 0; i < numExtens; ++i) {
const char* e = (const char*)glGetStringi(GL_EXTENSIONS, i);
if(!e) continue;
if(stringCompare(e, "GL_EXT_paletted_texture") != 0) continue;
DISPLAY.usingShaderedPalettes = false;
break;
}
} else {
const char* ext = (const char*)glGetString(GL_EXTENSIONS);
DISPLAY.usingShaderedPalettes = !(
ext && strstr(ext, "GL_EXT_paletted_texture")
);
}
#endif
#elif DOLPHIN
VIDEO_Init();
DISPLAY.screenMode = VIDEO_GetPreferredMode(NULL);
DISPLAY.frameBuffer[0] = MEM_K0_TO_K1(
SYS_AllocateFramebuffer(DISPLAY.screenMode)
);
DISPLAY.frameBuffer[1] = MEM_K0_TO_K1(
SYS_AllocateFramebuffer(DISPLAY.screenMode)
);
VIDEO_Configure(DISPLAY.screenMode);
VIDEO_SetNextFramebuffer(DISPLAY.frameBuffer[DISPLAY.whichFrameBuffer]);
// VIDEO_SetPostRetraceCallback(copy_buffers);
VIDEO_SetBlack(FALSE);
VIDEO_Flush();
VIDEO_WaitVSync();
if(DISPLAY.screenMode->viTVMode & VI_NON_INTERLACE) VIDEO_WaitVSync();
DISPLAY.fifoBuffer = memalign(32, DISPLAY_FIFO_SIZE);
memoryZero(DISPLAY.fifoBuffer, DISPLAY_FIFO_SIZE);
GX_Init(DISPLAY.fifoBuffer, DISPLAY_FIFO_SIZE);
// This seems to be mostly related to interlacing vs progressive
GX_SetViewport(
0, 0,
DISPLAY.screenMode->fbWidth, DISPLAY.screenMode->efbHeight,
0, 1
);
float_t yscale = GX_GetYScaleFactor(
DISPLAY.screenMode->efbHeight, DISPLAY.screenMode->xfbHeight
);
uint32_t xfbHeight = GX_SetDispCopyYScale(yscale);
GX_SetScissor(
0, 0,
DISPLAY.screenMode->fbWidth, DISPLAY.screenMode->efbHeight
);
GX_SetDispCopySrc(
0, 0,
DISPLAY.screenMode->fbWidth, DISPLAY.screenMode->efbHeight
);
GX_SetDispCopyDst(DISPLAY.screenMode->fbWidth, xfbHeight);
GX_SetCopyFilter(
DISPLAY.screenMode->aa,
DISPLAY.screenMode->sample_pattern,
GX_TRUE,
DISPLAY.screenMode->vfilter
);
GX_SetFieldMode(
DISPLAY.screenMode->field_rendering,
(
(DISPLAY.screenMode->viHeight == 2 * DISPLAY.screenMode->xfbHeight) ?
GX_ENABLE :
GX_DISABLE
)
);
// Setup cull modes
GX_SetCullMode(GX_CULL_NONE);
GX_SetZMode(GX_FALSE, GX_ALWAYS, GX_FALSE);
GX_CopyDisp(DISPLAY.frameBuffer[DISPLAY.whichFrameBuffer], GX_TRUE);
GX_SetDispCopyGamma(GX_GM_1_0);
GX_ClearVtxDesc();
GX_SetVtxDesc(GX_VA_POS, GX_INDEX16);
GX_SetVtxDesc(GX_VA_CLR0, GX_INDEX16);
GX_SetVtxDesc(GX_VA_TEX0, GX_INDEX16);
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_U8, 0);
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
#endif
quadInit();
frameBufferInitBackbuffer();
spriteBatchInit();
errorChain(textInit());
errorChain(assetLoad("main_palette.dpf", &PALETTES[0]));
screenInit();
errorOk();
}
errorret_t displayUpdate(void) {
#if DISPLAY_SDL2
SDL_Event event;
while(SDL_PollEvent(&event)) {
switch(event.type) {
case SDL_QUIT: {
ENGINE.running = false;
break;
}
case SDL_WINDOWEVENT: {
switch(event.window.event) {
case SDL_WINDOWEVENT_CLOSE: {
ENGINE.running = false;
break;
}
default: {
break;
}
}
}
default: {
break;
}
}
}
SDL_GL_MakeCurrent(DISPLAY.window, DISPLAY.glContext);
#endif
// Reset state
spriteBatchClear();
frameBufferBind(NULL);
// Bind screen and render scene
screenBind();
frameBufferClear(
FRAMEBUFFER_CLEAR_COLOR | FRAMEBUFFER_CLEAR_DEPTH,
SCREEN.background
);
errorChain(sceneRender());
// Render UI
// uiRender();
// Finish up
screenUnbind();
screenRender();
#if DISPLAY_SDL2
SDL_GL_SwapWindow(DISPLAY.window);
GLenum err;
while((err = glGetError()) != GL_NO_ERROR) {
debugPrint("GL Error: %d\n", err);
}
#elif DOLPHIN
GX_DrawDone();
DISPLAY.whichFrameBuffer ^= 1;
GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
GX_SetColorUpdate(GX_TRUE);
GX_CopyDisp(DISPLAY.frameBuffer[DISPLAY.whichFrameBuffer], GX_TRUE);
VIDEO_SetNextFramebuffer(DISPLAY.frameBuffer[DISPLAY.whichFrameBuffer]);
VIDEO_Flush();
VIDEO_WaitVSync();
#endif
// For now, we just return an OK error.
errorOk();
}
errorret_t displayDispose(void) {
spriteBatchDispose();
screenDispose();
textDispose();
#if DISPLAY_SDL2
if(DISPLAY.glContext) {
SDL_GL_DeleteContext(DISPLAY.glContext);
DISPLAY.glContext = NULL;
}
if(DISPLAY.window) {
SDL_DestroyWindow(DISPLAY.window);
DISPLAY.window = NULL;
}
SDL_Quit();
#endif
// For now, we just return an OK error.
errorOk();
}

View File

@@ -1,44 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "displaydefs.h"
#include "error/error.h"
#include "display/camera/camera.h"
#include "display/framebuffer.h"
typedef struct {
#if DISPLAY_SDL2
SDL_Window *window;
SDL_GLContext glContext;
bool_t usingShaderedPalettes;
#elif DOLPHIN
void *frameBuffer[2];// Double-Bufferred
int whichFrameBuffer;
GXRModeObj *screenMode;
void *fifoBuffer;
#endif
} display_t;
extern display_t DISPLAY;
/**
* Initializes the display system.
*/
errorret_t displayInit(void);
/**
* Tells the display system to actually draw the frame.
*/
errorret_t displayUpdate(void);
/**
* Disposes of the display system.
*/
errorret_t displayDispose(void);

View File

@@ -1,42 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#if DISPLAY_SDL2
#include <SDL2/SDL.h>
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glext.h>
#ifndef DISPLAY_SIZE_DYNAMIC
#define DISPLAY_SIZE_DYNAMIC 1
#endif
#elif DOLPHIN
// Dolphin.
#define DISPLAY_FIFO_SIZE (256*1024)
#else
#error "Need to specify display backend."
#endif
#if DISPLAY_SIZE_DYNAMIC == 0
#ifndef DISPLAY_WIDTH
#error "DISPLAY_WIDTH must be defined when DISPLAY_SIZE_DYNAMIC is 0."
#endif
#ifndef DISPLAY_HEIGHT
#error "DISPLAY_HEIGHT must be defined when DISPLAY_SIZE_DYNAMIC is 0."
#endif
#endif
#ifndef DISPLAY_WINDOW_WIDTH_DEFAULT
#error "DISPLAY_WINDOW_WIDTH_DEFAULT must be defined."
#endif
#ifndef DISPLAY_WINDOW_HEIGHT_DEFAULT
#define DISPLAY_WINDOW_HEIGHT_DEFAULT DISPLAY_HEIGHT
#endif

View File

@@ -1,192 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "framebuffer.h"
#include "display/display.h"
#include "assert/assert.h"
#include "util/memory.h"
framebuffer_t FRAMEBUFFER_BACKBUFFER = {0};
const framebuffer_t *FRAMEBUFFER_BOUND = &FRAMEBUFFER_BACKBUFFER;
void frameBufferInitBackbuffer() {
memoryZero(&FRAMEBUFFER_BACKBUFFER, sizeof(framebuffer_t));
FRAMEBUFFER_BACKBUFFER.id = -1;
FRAMEBUFFER_BOUND = &FRAMEBUFFER_BACKBUFFER;
}
#if DISPLAY_SIZE_DYNAMIC == 1
void frameBufferInit(
framebuffer_t *framebuffer,
const uint32_t width,
const uint32_t height
) {
#if DISPLAY_SDL2 == 1
assertNotNull(framebuffer, "Framebuffer cannot be NULL");
assertTrue(width > 0 && height > 0, "W/H must be greater than 0");
memoryZero(framebuffer, sizeof(framebuffer_t));
textureInit(&framebuffer->texture, width, height, TEXTURE_FORMAT_RGBA,(texturedata_t){
.rgbaColors = NULL
});
glGenFramebuffersEXT(1, &framebuffer->id);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer->id);
glFramebufferTexture2DEXT(
GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_TEXTURE_2D, framebuffer->texture.id, 0
);
if(
glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) !=
GL_FRAMEBUFFER_COMPLETE_EXT
) {
assertUnreachable("Framebuffer is not complete");
}
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
#endif
}
#endif
int32_t frameBufferGetWidth(const framebuffer_t *framebuffer) {
#if DISPLAY_SDL2
if(framebuffer == &FRAMEBUFFER_BACKBUFFER) {
#if DISPLAY_SIZE_DYNAMIC == 0
return DISPLAY_WIDTH;
#else
int32_t windowWidth, windowHeight;
SDL_GetWindowSize(DISPLAY.window, &windowWidth, &windowHeight);
return windowWidth;
#endif
}
return framebuffer->texture.width;
#elif DOLPHIN
return DISPLAY.screenMode->fbWidth;
#else
#error "Unsupported DISPLAY_TYPE."
#endif
}
int32_t frameBufferGetHeight(const framebuffer_t *framebuffer) {
#if DISPLAY_SDL2
if(framebuffer == &FRAMEBUFFER_BACKBUFFER) {
#if DISPLAY_SIZE_DYNAMIC == 0
return DISPLAY_HEIGHT;
#else
int32_t windowWidth, windowHeight;
SDL_GetWindowSize(DISPLAY.window, &windowWidth, &windowHeight);
return windowHeight;
#endif
}
return framebuffer->texture.height;
#elif DOLPHIN
return DISPLAY.screenMode->efbHeight;
#else
#error "Unsupported DISPLAY_TYPE."
#endif
}
void frameBufferBind(const framebuffer_t *framebuffer) {
if(framebuffer == NULL) {
frameBufferBind(&FRAMEBUFFER_BACKBUFFER);
FRAMEBUFFER_BOUND = &FRAMEBUFFER_BACKBUFFER;
return;
}
// Bind the framebuffer for rendering
#if DISPLAY_SDL2
if(framebuffer == &FRAMEBUFFER_BACKBUFFER) {
#if PSP
#else
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
#endif
} else {
#if PSP
assertUnreachable("Framebuffers not supported on PSP");
#else
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer->id);
#endif
}
glViewport(
0, 0,
frameBufferGetWidth(framebuffer), frameBufferGetHeight(framebuffer)
);
#elif DOLPHIN
GX_InvVtxCache();
GX_InvalidateTexAll();
GX_SetZMode(GX_FALSE, GX_ALWAYS, GX_FALSE);
GX_SetViewport(
0, 0,
frameBufferGetWidth(framebuffer),
frameBufferGetHeight(framebuffer),
0, 1
);
#endif
FRAMEBUFFER_BOUND = framebuffer;
}
void frameBufferClear(uint8_t flags, color_t color) {
#if DISPLAY_SDL2
GLbitfield glFlags = 0;
if(flags & FRAMEBUFFER_CLEAR_COLOR) {
glFlags |= GL_COLOR_BUFFER_BIT;
glClearColor(
color.r / 255.0f,
color.g / 255.0f,
color.b / 255.0f,
color.a / 255.0f
);
}
if(flags & FRAMEBUFFER_CLEAR_DEPTH) {
glFlags |= GL_DEPTH_BUFFER_BIT;
}
glClear(glFlags);
#elif DOLPHIN
GX_SetCopyClear(
(GXColor){ color.r, color.g, color.b, color.a },
GX_MAX_Z24
);
#endif
}
void frameBufferDispose(framebuffer_t *framebuffer) {
assertNotNull(framebuffer, "Framebuffer cannot be NULL");
#if DISPLAY_SDL2
if(framebuffer == &FRAMEBUFFER_BACKBUFFER) {
assertUnreachable("Cannot dispose of backbuffer");
}
#if DISPLAY_SIZE_DYNAMIC == 0
assertUnreachable("Dynamic size framebuffers not supported");
#else
textureDispose(&framebuffer->texture);
glDeleteFramebuffersEXT(1, &framebuffer->id);
#endif
#endif
}

View File

@@ -1,109 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/texture/texture.h"
#define FRAMEBUFFER_CLEAR_COLOR (1 << 0)
#define FRAMEBUFFER_CLEAR_DEPTH (1 << 1)
typedef struct {
#if DISPLAY_SDL2 == 1
// OpenGL Framebuffer Object ID
GLuint id;
texture_t texture;
#elif DOLPHIN
// --- IGNORE ---
uint8_t id;
#else
#error "Framebuffers not implemented on this platform."
#endif
} framebuffer_t;
extern framebuffer_t FRAMEBUFFER_BACKBUFFER;
extern const framebuffer_t *FRAMEBUFFER_BOUND;
/**
* Initializes the backbuffer framebuffer.
*/
void frameBufferInitBackbuffer(void);
/**
* Initializes a framebuffer.
*
* @param framebuffer The framebuffer to initialize.
* @param width The width of the framebuffer.
* @param height The height of the framebuffer.
*/
#if DISPLAY_SIZE_DYNAMIC == 1
void frameBufferInit(
framebuffer_t *framebuffer,
const uint32_t width,
const uint32_t height
);
#endif
/**
* Gets the width of the framebuffer.
*
* @param framebuffer The framebuffer to get the width of.
* @return The width of the framebuffer, or 0 if the framebuffer is NULL.
*/
int32_t frameBufferGetWidth(const framebuffer_t *framebuffer);
/**
* Gets the height of the framebuffer.
*
* @param framebuffer The framebuffer to get the height of.
* @return The height of the framebuffer, or 0 if the framebuffer is NULL.
*/
int32_t frameBufferGetHeight(const framebuffer_t *framebuffer);
/**
* Binds the framebuffer for rendering, or the backbuffer if the framebuffer
* provided is NULL.
*
* @param framebuffer The framebuffer to bind, or NULL to bind the backbuffer.
*/
void frameBufferBind(const framebuffer_t *framebuffer);
/**
* Clears the currently bound framebuffer.
*
* @param flags The clear flags.
* @param color The color to clear the color buffer to (if clearing color).
*/
void frameBufferClear(uint8_t flags, color_t color);
/**
* Disposes of the framebuffer using EXT methods.
*
* @param framebuffer The framebuffer to dispose of.
*/
void frameBufferDispose(framebuffer_t *framebuffer);
// #if RENDER_USE_FRAMEBUFFER
// typedef struct {
// GLuint id;
// texture_t texture;
// } framebuffer_t;
// /**
// * Initializes a framebuffer using EXT methods.
// *
// * @param framebuffer The framebuffer to initialize.
// * @param width The width of the framebuffer.
// * @param height The height of the framebuffer.
// * @return An error code indicating success or failure.
// */
// void frameBufferInit(
// framebuffer_t *framebuffer,
// const uint32_t width,
// const uint32_t height
// );
// #endif

View File

@@ -1,104 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "mesh.h"
#include "util/memory.h"
#include "assert/assert.h"
#if DOLPHIN
#include "display/texture.h"
#endif
void meshInit(
mesh_t *mesh,
const meshprimitivetype_t primitiveType,
const int32_t vertexCount,
const meshvertex_t *vertices
) {
assertNotNull(mesh, "Mesh cannot be NULL");
assertNotNull(vertices, "Vertices cannot be NULL");
assertTrue(vertexCount > 0, "Vertex count must be greater than 0");
memoryZero(mesh, sizeof(mesh_t));
mesh->primitiveType = primitiveType;
mesh->vertexCount = vertexCount;
mesh->vertices = vertices;
}
void meshDraw(
const mesh_t *mesh,
const int32_t vertexOffset,
const int32_t vertexCount
) {
const int32_t offset = vertexOffset == -1 ? 0 : vertexOffset;
const int32_t count = vertexCount == -1 ? mesh->vertexCount : vertexCount;
assertNotNull(mesh, "Mesh cannot be NULL");
assertTrue(offset >= 0, "Vertex offset must be non-negative");
assertTrue(count >= 0, "Vertex count must be non-negative");
assertTrue(offset + count <= mesh->vertexCount,
"Vertex offset + count must not exceed vertex count"
);
#if DISPLAY_SDL2
// PSP style pointer legacy OpenGL
const GLsizei stride = sizeof(meshvertex_t);
glColorPointer(
sizeof(color4b_t),
GL_UNSIGNED_BYTE,
stride,
(const GLvoid*)&mesh->vertices[offset].color
);
glTexCoordPointer(
MESH_VERTEX_UV_SIZE,
GL_FLOAT,
stride,
(const GLvoid*)&mesh->vertices[offset].uv[0]
);
glVertexPointer(
MESH_VERTEX_POS_SIZE,
GL_FLOAT,
stride,
(const GLvoid*)&mesh->vertices[offset].pos[0]
);
glDrawArrays(
mesh->primitiveType,
0,
count
);
#elif DOLPHIN
// Prepare Vertex descriptor
DCFlushRange(
(void*)&mesh->vertices[offset],
sizeof(meshvertex_t) * count
);
const u8 stride = (u8)sizeof(meshvertex_t);
GX_SetArray(GX_VA_POS, (void*)&mesh->vertices[offset].pos[0], stride);
GX_SetArray(GX_VA_CLR0, (void*)&mesh->vertices[offset].color, stride);
GX_SetArray(GX_VA_TEX0, (void*)&mesh->vertices[offset].uv[0], stride);
textureDolphinUploadTEV();
GX_Begin(mesh->primitiveType, GX_VTXFMT0, (uint16_t)count);
for(u16 i = 0; i < (u16)count; ++i) {
GX_Position1x16(i);
GX_Color1x16(i);
GX_TexCoord1x16(i);
}
GX_End();
#endif
}
void meshDispose(mesh_t *mesh) {
assertNotNull(mesh, "Mesh cannot be NULL");
memoryZero(mesh, sizeof(mesh_t));
}

View File

@@ -1,70 +0,0 @@
// Copyright (c) 2025 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "display/display.h"
#include "display/color.h"
typedef enum {
#if DISPLAY_SDL2
MESH_PRIMITIVE_TRIANGLES = GL_TRIANGLES,
MESH_PRIMITIVE_LINES = GL_LINES,
MESH_PRIMITIVE_POINTS = GL_POINTS,
#elif DOLPHIN
MESH_PRIMITIVE_TRIANGLES = GX_TRIANGLES,
MESH_PRIMITIVE_LINES = GX_LINES,
MESH_PRIMITIVE_POINTS = GX_POINTS,
#endif
} meshprimitivetype_t;
#define MESH_VERTEX_UV_SIZE 2
#define MESH_VERTEX_POS_SIZE 3
typedef struct {
color_t color;
float_t uv[MESH_VERTEX_UV_SIZE];
float_t pos[MESH_VERTEX_POS_SIZE];
} meshvertex_t;
typedef struct {
const meshvertex_t *vertices;
int32_t vertexCount;
meshprimitivetype_t primitiveType;
} mesh_t;
/**
* Initializes a mesh.
*
* @param mesh The mesh to initialize.
* @param primitiveType The OpenGL primitive type (e.g., GL_TRIANGLES).
* @param vertexCount The number of vertices in the mesh.
* @param vertices The vertex data for the mesh.
*/
void meshInit(
mesh_t *mesh,
const meshprimitivetype_t primitiveType,
const int32_t vertexCount,
const meshvertex_t *vertices
);
/**
* Draws a mesh.
*
* @param mesh The mesh to draw.
* @param vertexOffset The offset in the vertex array to start drawing from.
* @param vertexCount The number of vertices to draw. If -1, draws all vertices.
*/
void meshDraw(
const mesh_t *mesh,
const int32_t vertexOffset,
const int32_t vertexCount
);
/**
* Disposes a mesh.
*
* @param mesh The mesh to dispose.
*/
void meshDispose(mesh_t *mesh);

View File

@@ -1,105 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "spritebatch.h"
#include "assert/assert.h"
#include "util/memory.h"
meshvertex_t SPRITEBATCH_VERTICES[SPRITEBATCH_VERTEX_COUNT];
spritebatch_t SPRITEBATCH;
void spriteBatchInit() {
memoryZero(&SPRITEBATCH, sizeof(spritebatch_t));
meshInit(
&SPRITEBATCH.mesh,
MESH_PRIMITIVE_TRIANGLES,
SPRITEBATCH_VERTEX_COUNT,
&SPRITEBATCH_VERTICES[0]
);
}
void spriteBatchPush(
texture_t *texture,
const float_t minX,
const float_t minY,
const float_t maxX,
const float_t maxY,
const color_t color,
const float_t u0,
const float_t v0,
const float_t u1,
const float_t v1
) {
return spriteBatchPush3D(
texture,
(vec3){ minX, minY, 0 },
(vec3){ maxX, maxY, 0 },
color,
(vec2){ u0, v0 },
(vec2){ u1, v1 }
);
}
void spriteBatchPush3D(
texture_t *texture,
const vec3 min,
const vec3 max,
const color_t color,
const vec2 uv0,
const vec2 uv1
) {
// Need to flush?
if(
SPRITEBATCH.currentTexture != texture ||
SPRITEBATCH.spriteCount >= SPRITEBATCH_SPRITES_MAX
) {
spriteBatchFlush();
SPRITEBATCH.currentTexture = texture;
}
size_t vertexOffset = SPRITEBATCH.spriteCount * QUAD_VERTEX_COUNT;
#if DOLPHIN
vertexOffset += (
SPRITEBATCH.batchIndex * SPRITEBATCH_SPRITES_MAX * QUAD_VERTEX_COUNT
);
#endif
quadBuffer3D(
&SPRITEBATCH_VERTICES[vertexOffset],
min, max, color, uv0, uv1
);
SPRITEBATCH.spriteCount++;
}
void spriteBatchClear() {
SPRITEBATCH.spriteCount = 0;
SPRITEBATCH.currentTexture = NULL;
}
void spriteBatchFlush() {
if(SPRITEBATCH.spriteCount == 0) return;
textureBind(SPRITEBATCH.currentTexture);
#if DOLPHIN
meshDraw(
&SPRITEBATCH.mesh,
QUAD_VERTEX_COUNT * SPRITEBATCH.batchIndex * SPRITEBATCH_SPRITES_MAX,
QUAD_VERTEX_COUNT * SPRITEBATCH.spriteCount
);
SPRITEBATCH.batchIndex = (
(SPRITEBATCH.batchIndex + 1) % SPRITEBATCH_BATCH_COUNT
);
#else
meshDraw(&SPRITEBATCH.mesh, 0, QUAD_VERTEX_COUNT * SPRITEBATCH.spriteCount);
#endif
spriteBatchClear();
}
void spriteBatchDispose() {
meshDispose(&SPRITEBATCH.mesh);
}

View File

@@ -1,43 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "palette.h"
#include "assert/assert.h"
#include "util/memory.h"
palette_t PALETTES[PALETTE_COUNT_MAX] = { 0 };
void paletteInit(
palette_t *palette,
const uint8_t colorCount,
const color_t *colors
) {
assertNotNull(palette, "Palette cannot be NULL");
assertTrue(colorCount > 0, "Color count must be greater than 0");
assertNotNull(colors, "Colors array cannot be NULL");
assertTrue(colorCount <= PALETTE_COLOR_COUNT_MAX, "Color count too big");
assertTrue(
palette - PALETTES < PALETTE_COUNT_MAX,
"Palette index out of range"
);
memoryZero(palette, sizeof(palette_t));
palette->colorCount = colorCount;
memoryCopy(palette->colors, colors, colorCount * sizeof(color_t));
}
void paletteBind(palette_t *palette, const uint8_t slot) {
assertNotNull(palette, "Palette cannot be NULL");
assertTrue(slot < PALETTE_COUNT_MAX, "Palette slot out of range");
// Nothing yet.
}
void paletteDispose(palette_t *palette) {
assertNotNull(palette, "Palette cannot be NULL");
}

View File

@@ -1,59 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/color.h"
#if DISPLAY_SDL2
#if DISPLAY_SHADER == 1
#elif DISPLAY_COLOR_TABLE == 1
#else
#error "Unsupported palette mode"
#endif
#else
#error "Unsupported palette mode"
#endif
#define PALETTE_COUNT_MAX 4
#define PALETTE_COLOR_COUNT_MAX 0xFF
typedef struct {
uint8_t colorCount;
color_t colors[PALETTE_COLOR_COUNT_MAX];
} palette_t;
extern palette_t PALETTES[PALETTE_COUNT_MAX];
/**
* Initializes a palette with the given colors.
*
* @param palette The palette to initialize.
* @param colorCount The number of colors in the palette.
* @param colors An array of colors for the palette.
*/
void paletteInit(
palette_t *palette,
const uint8_t colorCount,
const color_t *colors
);
/**
* Binds a palette for use in rendering.
*
* @param palette The palette to bind.
* @param slot The slot to bind the palette to.
*/
void paletteBind(palette_t *palette, const uint8_t slot);
/**
* Disposes of a palette, freeing any associated resources.
*
* @param palette The palette to dispose.
*/
void paletteDispose(palette_t *palette);

View File

@@ -1,51 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "palettetexture.h"
#include "assert/assert.h"
void paletteTextureInit(
palettetexture_t *texture,
const int32_t width,
const int32_t height,
const uint8_t *data
) {
assertNotNull(texture, "Palette texture cannot be NULL");
assertTrue(width > 0 && height > 0, "width/height must be greater than 0");
assertNotNull(data, "Palette texture data cannot be NULL");
#if DISPLAY_SDL2
#if DISPLAY_SHADER == 1
// Palette textures not supported, convert to GL_RED style texture
// so shader can perform the lookup.
uint8_t formatted[width * height];
for(int32_t i = 0; i < width * height; i++) {
uint8_t index = data.paletteData[i];
formatted[i] = index * 128;
}
glTexImage2D(
GL_TEXTURE_2D, 0, GL_R8, width, height, 0,
GL_RED, GL_UNSIGNED_BYTE, (void*)formatted
);
#else
glTexImage2D(
GL_TEXTURE_2D,
0, GL_COLOR_INDEX8_EXT,
width, height,
0, GL_COLOR_INDEX8_EXT,
GL_UNSIGNED_BYTE, (void*)data.paletteData
);
// glColorTableEXT(
// GL_TEXTURE_2D, GL_RGBA, data.palette.palette->colorCount, GL_RGBA,
// GL_UNSIGNED_BYTE, (const void*)data.palette.palette->colors
// );
#endif
#else
#error "Palette textures not supported on this platform"
#endif
}

View File

@@ -1,28 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct {
} palettetexture_t;
/**
* Initializes a palette texture.
*
* @param texture The palette texture to initialize.
* @param width The width of the texture. Must be a power of 2.
* @param height The height of the texture. Must be a power of 2.
* @param data The palette index data for the texture.
*/
void paletteTextureInit(
palettetexture_t *texture,
const int32_t width,
const int32_t height,
const uint8_t *data
);

View File

@@ -1,422 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "texture.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/math.h"
#include "display/display.h"
const texture_t *TEXTURE_BOUND = NULL;
void textureInit(
texture_t *texture,
const int32_t width,
const int32_t height,
const textureformat_t format,
const texturedata_t data
) {
assertNotNull(texture, "Texture cannot be NULL");
assertTrue(width > 0 && height > 0, "width/height must be greater than 0");
memoryZero(texture, sizeof(texture_t));
texture->width = width;
texture->height = height;
texture->format = format;
assertTrue(width == mathNextPowTwo(width), "Width must be a power of 2.");
assertTrue(height == mathNextPowTwo(height), "Height must be a power of 2.");
#if DISPLAY_SDL2
glGenTextures(1, &texture->id);
glBindTexture(GL_TEXTURE_2D, texture->id);
switch(format) {
case TEXTURE_FORMAT_RGBA:
glTexImage2D(
GL_TEXTURE_2D, 0, format, width, height, 0,
format, GL_UNSIGNED_BYTE, (void*)data.rgbaColors
);
break;
case TEXTURE_FORMAT_PALETTE:
assertNotNull(data.paletteData, "Palette texture data cannot be NULL");
if(DISPLAY.usingShaderedPalettes) {
// Palette textures not supported, convert to GL_RED style texture
// so shader can perform the lookup.
uint8_t formatted[width * height];
for(int32_t i = 0; i < width * height; i++) {
uint8_t index = data.paletteData[i];
formatted[i] = index * 128;
}
glTexImage2D(
GL_TEXTURE_2D, 0, GL_R8, width, height, 0,
GL_RED, GL_UNSIGNED_BYTE, (void*)formatted
);
} else {
glTexImage2D(
GL_TEXTURE_2D,
0, GL_COLOR_INDEX8_EXT,
width, height,
0, GL_COLOR_INDEX8_EXT,
GL_UNSIGNED_BYTE, (void*)data.paletteData
);
// glColorTableEXT(
// GL_TEXTURE_2D, GL_RGBA, data.palette.palette->colorCount, GL_RGBA,
// GL_UNSIGNED_BYTE, (const void*)data.palette.palette->colors
// );
}
GLenum err = glGetError();
if(err != GL_NO_ERROR) {
printf("GL Error uploading palette texture: %d\n", err);
assertUnreachable("GL error uploading palette texture");
}
break;
default:
assertUnreachable("Unknown texture format");
break;
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glBindTexture(GL_TEXTURE_2D, 0);
texture->ready = true;
#elif DOLPHIN
switch(format) {
case TEXTURE_FORMAT_RGBA:
assertTrue(
(width % 4) == 0 && (height % 4) == 0,
"RGB5A3 requires w/h multiple of 4 (or pad)"
);
// Convert to RGB5A3 format
size_t rgbaSize = width * height * sizeof(u16);
texture->rgba = (u16*)memalign(32, rgbaSize);
assertNotNull(texture->rgba, "Failed to allocate texture RGBA data");
for(uint32_t y = 0; y < height; ++y) {
for(uint32_t x = 0; x < width; ++x) {
const int src = y * width + x;
const int tileX = x >> 2;
const int tileY = y >> 2;
const int tilesPerRow = width >> 2;
const int tileIndex = tileY * tilesPerRow + tileX;
const int tileBaseWords = tileIndex * 16;
const int inTile = ((y & 3) << 2) + (x & 3);
const int dest = tileBaseWords + inTile;
color4b_t col = data.rgba.colors[src];
u16 outCol;
if(col.a < 255) {
// 0AAA RRRR GGGG BBBB
outCol = (
(0u << 15) |
((u16)(col.a >> 5) << 12) |
((u16)(col.r >> 4) << 8) |
((u16)(col.g >> 4) << 4) |
((u16)(col.b >> 4) << 0)
);
} else {
// 1RRRR RRGG GGGB BBBB
outCol = (
(1u << 15) |
((u16)(col.r >> 3) << 10) |
((u16)(col.g >> 3) << 5) |
((u16)(col.b >> 3) << 0)
);
}
texture->rgba[dest] = outCol;
}
}
DCFlushRange(texture->rgba, rgbaSize);
GX_InitTexObj(
&texture->texObj,
texture->rgba,
width, height,
GX_TF_RGB5A3,
GX_REPEAT, GX_REPEAT,
GX_FALSE
);
DCFlushRange(texture->rgba, rgbaSize);
GX_InvalidateTexAll();
GX_InitTexObjLOD(
&texture->texObj,
GX_NEAR, GX_NEAR,
0.0f, 0.0f, 0.0f,
GX_FALSE,
GX_FALSE,
GX_ANISO_1
);
break;
case TEXTURE_FORMAT_ALPHA: {
assertTrue(
(width % 4) == 0 && (height % 4) == 0,
"GX_TF_I8 requires w/h multiple of 4 (or pad)"
);
// 1 byte per pixel (I8), GX expects 4x4 tiled layout
const size_t alphaSize = (size_t)width * (size_t)height;
texture->alpha = (u8*)memalign(32, alphaSize);
assertNotNull(texture->alpha, "Failed to allocate alpha texture data");
const u32 tilesPerRow = ((u32)width) >> 3; // /8
for (u32 y = 0; y < (u32)height; ++y) {
const u32 tileY = y >> 2; // /4
const u32 inTileY = (y & 3) << 3; // (y%4)*8
for (u32 x = 0; x < (u32)width; ++x) {
const u32 srcI = y * (u32)width + x;
const u8 srcA = data.alpha.data[srcI]; // linear input
const u32 tileX = x >> 3; // /8
const u32 tileIndex = tileY * tilesPerRow + tileX;
const u32 tileBase = tileIndex * 32; // 8*4*1 = 32 bytes per tile
const u32 inTile = inTileY + (x & 7); // (y%4)*8 + (x%8)
texture->alpha[tileBase + inTile] = 0xFF - srcA;// Fixes inverted alpha.
}
}
// Flush CPU cache so GX sees the swizzled I8 texture data
DCFlushRange(texture->alpha, alphaSize);
// Initialize GX texture object with swizzled data
GX_InitTexObj(
&texture->texObj,
texture->alpha,
width, height,
GX_TF_I8,
GX_REPEAT, GX_REPEAT,
GX_FALSE
);
GX_InitTexObjLOD(
&texture->texObj,
GX_NEAR, GX_NEAR,
0.0f, 0.0f, 0.0f,
GX_FALSE,
GX_FALSE,
GX_ANISO_1
);
break;
}
case TEXTURE_FORMAT_PALETTE: {
// Not supported, convert to RGBA using lookup
color_t* formatted = memoryAllocate(width * height * sizeof(color_t));
for(int32_t i = 0; i < width * height; i++) {
uint8_t index = data.palette.data[i];
assertTrue(
index < data.palette.palette->colorCount,
"Palette index out of range"
);
formatted[i] = data.palette.palette->colors[index];
}
textureInit(
texture, width, height, TEXTURE_FORMAT_RGBA,
(texturedata_t){
.rgba = { .colors = formatted }
}
);
memoryFree(formatted);
break;
}
default:
assertUnreachable("Unsupported texture format for Dolphin");
break;
}
texture->ready = true;
#endif
}
void textureBind(texture_t *texture) {
if(TEXTURE_BOUND == texture) return;
if(texture == NULL) {
#if DISPLAY_SDL2
glDisable(GL_TEXTURE_2D);
#elif DOLPHIN
GX_SetNumChans(0);
#endif
TEXTURE_BOUND = NULL;
return;
}
assertTrue(texture->ready, "Texture ID must be ready");
assertTrue(
texture->width > 0 && texture->height > 0,
"Texture width and height must be greater than 0"
);
#if DISPLAY_SDL2
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texture->id);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
#elif DOLPHIN
GX_SetNumChans(1);
GX_LoadTexObj(&texture->texObj, GX_TEXMAP0);
#endif
TEXTURE_BOUND = texture;
}
void textureDispose(texture_t *texture) {
assertNotNull(texture, "Texture cannot be NULL");
assertTrue(texture->ready, "Texture ID must be ready");
if(TEXTURE_BOUND == texture) {
textureBind(NULL);
}
#if DISPLAY_SDL2
glDeleteTextures(1, &texture->id);
#elif DOLPHIN
switch(texture->format) {
case TEXTURE_FORMAT_RGBA:
free(texture->rgba);
break;
case TEXTURE_FORMAT_ALPHA:
free(texture->alpha);
break;
default:
break;
}
memoryZero(texture, sizeof(texture_t));
#endif
}
#if DOLPHIN
void textureDolphinUploadTEV() {
if(TEXTURE_BOUND == NULL) {
GX_SetNumTexGens(0);
GX_SetNumTevStages(1);
GX_SetTevOp(GX_TEVSTAGE0, GX_PASSCLR);
return;
}
// Add channel for vertex color
GX_SetNumChans(1);
GX_SetChanCtrl(
GX_COLOR0A0,// Store in color channel 0
GX_DISABLE,// Lighting disabled
GX_SRC_REG,// Ambient color?
GX_SRC_VTX,// Material color?
GX_LIGHTNULL,// Light Mask
GX_DF_NONE,// Diffuse function
GX_AF_NONE// Attenuation function
);
// One set of UVs
GX_SetNumTexGens(1);
GX_SetTexCoordGen(
GX_TEXCOORD0,
GX_TG_MTX2x4,
GX_TG_TEX0,
GX_IDENTITY
);
// Basically the shader setup
switch(TEXTURE_BOUND->format) {
case TEXTURE_FORMAT_RGBA:
// One TEV stage: vertex color * texture color
GX_SetNumTevStages(1);
GX_SetTevOp(GX_TEVSTAGE0, GX_MODULATE);
GX_SetTevOrder(
GX_TEVSTAGE0,
GX_TEXCOORD0,
GX_TEXMAP0,
GX_COLOR0A0
);
GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR);
GX_SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0);
break;
case TEXTURE_FORMAT_ALPHA:
// One TEV stage: vertex color * texture color
GX_SetNumTevStages(1);
GX_SetTevOrder(
GX_TEVSTAGE0,
GX_TEXCOORD0,
GX_TEXMAP0,
GX_COLOR0A0
);
// Color = vertex color
GX_SetTevColorIn(
GX_TEVSTAGE0,
GX_CC_RASC,
GX_CC_ZERO,
GX_CC_ZERO,
GX_CC_ZERO
);
GX_SetTevColorOp(
GX_TEVSTAGE0,
GX_TEV_ADD,
GX_TB_ZERO,
GX_CS_SCALE_1,
GX_TRUE,
GX_TEVPREV
);
// Alpha = vertex alpha * I8 intensity
GX_SetTevAlphaIn(
GX_TEVSTAGE0,
GX_CA_RASA,
GX_CA_ZERO,
GX_CA_TEXA,
GX_CA_ZERO
);
GX_SetTevAlphaOp(
GX_TEVSTAGE0,
GX_TEV_ADD,
GX_TB_ZERO,
GX_CS_SCALE_1,
GX_TRUE,
GX_TEVPREV
);
GX_SetBlendMode(
GX_BM_BLEND,
GX_BL_SRCALPHA,
GX_BL_INVSRCALPHA,
GX_LO_CLEAR
);
GX_SetColorUpdate(GX_TRUE);
GX_SetAlphaUpdate(GX_TRUE);
break;
default:
assertUnreachable("Unknown texture format in meshDraw");
break;
}
}
#endif

View File

@@ -1,84 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/color.h"
#include "display/displaydefs.h"
#include "display/texture/palette.h"
typedef enum {
#if DISPLAY_SDL2
TEXTURE_FORMAT_RGBA = GL_RGBA,
TEXTURE_FORMAT_PALETTE = GL_COLOR_INDEX8_EXT,
#elif DOLPHIN
// TEXTURE_FORMAT_RGBA = GX_TF_RGBA8,
// TEXTURE_FORMAT_ALPHA = GX_TF_A8,
TEXTURE_FORMAT_PALETTE = GX_TF_CI8,
#endif
} textureformat_t;
typedef struct {
#if DISPLAY_SDL2
GLuint id;
#elif DOLPHIN
GXTexObj texObj;
union {
u16 *rgba;
u8 *alpha;
};
#endif
textureformat_t format;
bool_t ready;
int32_t width;
int32_t height;
} texture_t;
typedef union {
uint8_t *paletteData;
color_t *rgbaColors;
} texturedata_t;
extern const texture_t *TEXTURE_BOUND;
/**
* Initializes a texture.
*
* @param texture The texture to initialize.
* @param width The width of the texture.
* @param height The height of the texture.
* @param format The format of the texture (e.g., GL_RGBA, GL_ALPHA).
* @param data The data for the texture, the format changes per format.
*/
void textureInit(
texture_t *texture,
const int32_t width,
const int32_t height,
const textureformat_t format,
const texturedata_t data
);
/**
* Binds a texture for rendering. Providing NULL will unbind any texture.
*
* @param texture The texture to bind.
*/
void textureBind(texture_t *texture);
/**
* Disposes a texture.
*
* @param texture The texture to dispose.
*/
void textureDispose(texture_t *texture);
#if DOLPHIN
/**
* Uploads the TEV settings for the currently bound texture.
*/
void textureDolphinUploadTEV();
#endif

61
src/dusk/CMakeLists.txt Normal file
View File

@@ -0,0 +1,61 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Required Libraries
if(NOT cglm_FOUND)
find_package(cglm REQUIRED)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC cglm)
endif()
if(NOT libzip_FOUND)
find_package(libzip REQUIRED)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC zip)
endif()
if(NOT Lua_FOUND)
find_package(Lua REQUIRED)
if(Lua_FOUND AND NOT TARGET Lua::Lua)
add_library(Lua::Lua INTERFACE IMPORTED)
set_target_properties(
Lua::Lua
PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${LUA_INCLUDE_DIR}"
INTERFACE_LINK_LIBRARIES "${LUA_LIBRARIES}"
)
endif()
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC Lua::Lua)
endif()
# Includes
target_include_directories(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
${CMAKE_CURRENT_LIST_DIR}
)
# Main Binary Source
target_sources(${DUSK_BINARY_TARGET_NAME}
PUBLIC
main.c
)
# Subdirs
add_subdirectory(assert)
add_subdirectory(asset)
add_subdirectory(log)
add_subdirectory(display)
add_subdirectory(engine)
add_subdirectory(error)
add_subdirectory(event)
add_subdirectory(input)
add_subdirectory(locale)
add_subdirectory(scene)
add_subdirectory(script)
add_subdirectory(time)
add_subdirectory(ui)
add_subdirectory(util)
# if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "psp")
# add_subdirectory(thread)
# endif()

View File

@@ -6,9 +6,10 @@
*/
#include "assert.h"
#include "debug/debug.h"
#include "log/log.h"
#include "util/string.h"
#ifndef ASSERTIONS_FAKED
#ifndef DUSK_ASSERTIONS_FAKED
#ifdef DUSK_TEST_ASSERT
void assertTrueImpl(
const char *file,
@@ -31,14 +32,12 @@
const char *message
) {
if(x != true) {
debugPrint(
logError(
"Assertion Failed in %s:%i\n\n%s\n",
file,
line,
message
);
debugFlush();
abort();
}
}
@@ -100,4 +99,14 @@
) {
assertUnreachableImpl(file, line, message);
}
void assertStringEqualImpl(
const char *file,
const int32_t line,
const char *a,
const char *b,
const char *message
) {
assertTrueImpl(file, line, stringCompare(a, b) == 0, message);
}
#endif

View File

@@ -11,12 +11,12 @@
#ifdef DUSK_TEST_ASSERT
#include <cmocka.h>
#ifdef ASSERTIONS_FAKED
#ifdef DUSK_ASSERTIONS_FAKED
#error "Cannot fake assertions when DUSK_TEST_ASSERT is set."
#endif
#endif
#ifndef ASSERTIONS_FAKED
#ifndef DUSK_ASSERTIONS_FAKED
/**
* Assert a given value to be true.
*
@@ -104,6 +104,23 @@
const char *message
);
/**
* Asserts two strings to be equal.
*
* @param file File that the assertion is being made from.
* @param line Line that the assertion is being made from.
* @param a First string to compare.
* @param b Second string to compare.
* @param message Message to throw against assertion failure.
*/
void assertStringEqualImpl(
const char *file,
const int32_t line,
const char *a,
const char *b,
const char *message
);
/**
* Asserts a given value to be true.
*
@@ -178,6 +195,16 @@
#define assertStrLenMin(str, len, message) \
assertTrue(strlen(str) >= len, message)
/**
* Asserts two strings to be equal.
*
* @param a First string to compare.
* @param b Second string to compare.
* @param message Message to throw against assertion failure.
*/
#define assertStringEqual(a, b, message) \
assertStringEqualImpl(__FILE__, __LINE__, a, b, message)
#else
// If assertions are faked, we define the macros to do nothing.
#define assertTrue(x, message) ((void)0)

150
src/dusk/asset/asset.c Normal file
View File

@@ -0,0 +1,150 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "asset.h"
#include "util/memory.h"
#include "util/string.h"
#include "assert/assert.h"
#include "asset/assettype.h"
#include "engine/engine.h"
#include "util/string.h"
asset_t ASSET;
errorret_t assetInit(void) {
memoryZero(&ASSET, sizeof(asset_t));
// assetInitPlatform must either define ASSET.zip or throw an error.
errorChain(assetInitPlatform());
assertNotNull(ASSET.zip, "Asset zip null without error.");
errorOk();
}
bool_t assetFileExists(const char_t *filename) {
assertStrLenMax(filename, FILENAME_MAX, "Filename too long.");
zip_int64_t idx = zip_name_locate(ASSET.zip, filename, 0);
if(idx < 0) return false;
return true;
}
errorret_t assetLoad(const char_t *filename, void *output) {
assertStrLenMax(filename, FILENAME_MAX, "Filename too long.");
assertNotNull(output, "Output pointer cannot be NULL.");
// Determine the asset type by reading the extension
const assettypedef_t *def = NULL;
for(uint_fast8_t i = 0; i < ASSET_TYPE_COUNT; i++) {
const assettypedef_t *cmp = &ASSET_TYPE_DEFINITIONS[i];
assertNotNull(cmp, "Asset type definition cannot be NULL.");
if(cmp->extension == NULL) continue;
if(!stringEndsWithCaseInsensitive(filename, cmp->extension)) continue;
def = cmp;
break;
}
if(def == NULL) {
errorThrow("Unknown asset type for file: %s", filename);
}
// Get file size of the asset.
zip_stat_t st;
zip_stat_init(&st);
if(!zip_stat(ASSET.zip, filename, 0, &st) == 0) {
errorThrow("Failed to stat asset file: %s", filename);
}
// Minimum file size.
zip_int64_t fileSize = (zip_int64_t)st.size;
if(fileSize <= 0) {
errorThrow("Asset file is empty: %s", filename);
}
// Try to open the file
zip_file_t *file = zip_fopen(ASSET.zip, filename, 0);
if(file == NULL) {
errorThrow("Failed to open asset file: %s", filename);
}
// Load the asset data
switch(def->loadStrategy) {
case ASSET_LOAD_STRAT_ENTIRE:
assertNotNull(def->entire, "Asset load function cannot be NULL.");
// Must have more to read
if(fileSize <= 0) {
zip_fclose(file);
errorThrow("No data remaining to read for asset: %s", filename);
}
if(fileSize > def->dataSize) {
zip_fclose(file);
errorThrow(
"Asset file has too much data remaining after header: %s",
filename
);
}
// Create space to read the entire asset data
void *data = memoryAllocate(fileSize);
if(!data) {
zip_fclose(file);
errorThrow(
"Failed to allocate memory for asset data of file: %s", filename
);
}
// Read in the asset data.
zip_int64_t bytesRead = zip_fread(file, data, fileSize);
if(bytesRead == 0 || bytesRead > fileSize) {
memoryFree(data);
zip_fclose(file);
errorThrow("Failed to read asset data for file: %s", filename);
}
fileSize -= bytesRead;
// Close the file now we have the data
zip_fclose(file);
// Pass to the asset type loader
assetentire_t entire = {
.data = data,
.output = output
};
errorret_t ret = def->entire(entire);
memoryFree(data);
errorChain(ret);
break;
case ASSET_LOAD_STRAT_CUSTOM:
assertNotNull(def->custom, "Asset load function cannot be NULL.");
assetcustom_t customData = {
.zipFile = file,
.output = output
};
errorChain(def->custom(customData));
break;
default:
assertUnreachable("Unknown asset load strategy.");
}
errorOk();
}
errorret_t assetDispose(void) {
if(ASSET.zip != NULL) {
if(zip_close(ASSET.zip) != 0) {
errorThrow("Failed to close asset zip archive.");
}
ASSET.zip = NULL;
}
errorChain(assetDisposePlatform());
errorOk();
}

57
src/dusk/asset/asset.h Normal file
View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include "assettype.h"
#include "asset/assetplatform.h"
#ifndef assetInitPlatform
#error "Platform must define assetInitPlatform function."
#endif
#ifndef assetDisposePlatform
#error "Platform must define assetDisposePlatform function."
#endif
#define ASSET_FILE_NAME "dusk.dsk"
#define ASSET_HEADER_SIZE 3
typedef struct {
zip_t *zip;
assetplatform_t platform;
} asset_t;
extern asset_t ASSET;
/**
* Initializes the asset system.
*/
errorret_t assetInit(void);
/**
* Checks if an asset file exists.
*
* @param filename The filename of the asset to check.
* @return true if the asset file exists, false otherwise.
*/
bool_t assetFileExists(const char_t *filename);
/**
* Loads an asset by its filename, the output type depends on the asset type.
*
* @param filename The filename of the asset to retrieve.
* @param output The output pointer to store the loaded asset data.
* @return An error code if the asset could not be loaded.
*/
errorret_t assetLoad(const char_t *filename, void *output);
/**
* Disposes/cleans up the asset system.
*
* @return An error code if the asset system could not be disposed properly.
*/
errorret_t assetDispose(void);

View File

@@ -7,7 +7,7 @@
#pragma once
#include "type/assettexture.h"
#include "type/assetpalette.h"
// #include "type/assetpalette.h"
#include "type/assettileset.h"
#include "type/assetlanguage.h"
#include "type/assetscript.h"
@@ -19,12 +19,9 @@ typedef enum {
ASSET_TYPE_NULL,
ASSET_TYPE_TEXTURE,
ASSET_TYPE_PALETTE,
ASSET_TYPE_TILESET,
ASSET_TYPE_LANGUAGE,
ASSET_TYPE_SCRIPT,
ASSET_TYPE_MAP,
ASSET_TYPE_MAP_CHUNK,
ASSET_TYPE_COUNT,
} assettype_t;
@@ -60,21 +57,14 @@ static const assettypedef_t ASSET_TYPE_DEFINITIONS[ASSET_TYPE_COUNT] = {
},
[ASSET_TYPE_TEXTURE] = {
.extension = "dpt",
.extension = "DTX",
.loadStrategy = ASSET_LOAD_STRAT_ENTIRE,
.dataSize = sizeof(assettexture_t),
.entire = assetTextureLoad
},
[ASSET_TYPE_PALETTE] = {
.extension = "dpf",
.loadStrategy = ASSET_LOAD_STRAT_ENTIRE,
.dataSize = sizeof(palette_t),
.entire = assetPaletteLoad
},
[ASSET_TYPE_TILESET] = {
.extension = "dtf",
.extension = "DTF",
.loadStrategy = ASSET_LOAD_STRAT_ENTIRE,
.dataSize = sizeof(assettileset_t),
.entire = assetTilesetLoad
@@ -91,16 +81,4 @@ static const assettypedef_t ASSET_TYPE_DEFINITIONS[ASSET_TYPE_COUNT] = {
.loadStrategy = ASSET_LOAD_STRAT_CUSTOM,
.custom = assetScriptHandler
},
// [ASSET_TYPE_MAP] = {
// .extension = "DMF",
// .loadStrategy = ASSET_LOAD_STRAT_CUSTOM,
// .custom = assetMapHandler
// },
// [ASSET_TYPE_MAP_CHUNK] = {
// .extension = "DMC",
// .loadStrategy = ASSET_LOAD_STRAT_CUSTOM,
// .custom = assetMapChunkHandler
// },
};

View File

@@ -7,7 +7,6 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
assettexture.c
assetpalette.c
assettileset.c
assetlanguage.c
assetscript.c

View File

@@ -138,14 +138,14 @@ errorret_t assetMapChunkHandler(assetcustom_t custom) {
mesh_t *mesh = &chunk->meshes[i];
meshInit(
mesh,
MESH_PRIMITIVE_TRIANGLES,
MESH_PRIMITIVE_TYPE_TRIANGLES,
modelHeader.vertexCount,
&chunk->vertices[vertexIndex]
);
vertexIndex += modelHeader.vertexCount;
} else {
chunk->meshes[i].vertexCount = 0;
// chunk->meshes[i].vertexCount = 0;
}
}

View File

@@ -21,8 +21,8 @@ errorret_t assetTextureLoad(assetentire_t entire) {
// Read header and version (first 4 bytes)
if(
assetData->header[0] != 'D' ||
assetData->header[1] != 'P' ||
assetData->header[2] != 'T'
assetData->header[1] != 'T' ||
assetData->header[2] != 'X'
) {
errorThrow("Invalid texture header");
}
@@ -44,15 +44,35 @@ errorret_t assetTextureLoad(assetentire_t entire) {
errorThrow("Invalid texture dimensions");
}
textureInit(
texture,
assetData->width,
assetData->height,
TEXTURE_FORMAT_PALETTE,
(texturedata_t){
.paletteData = assetData->palette
// Validate format
textureformat_t format;
texturedata_t data;
switch(assetData->type) {
case 0x00: // RGBA8888
format = TEXTURE_FORMAT_RGBA;
data.rgbaColors = (color_t *)assetData->data;
break;
// case 0x01:
// format = TEXTURE_FORMAT_RGB;
// break;
// case 0x02:
// format = TEXTURE_FORMAT_RGB565;
// break;
// case 0x03:
// format = TEXTURE_FORMAT_RGB5A3;
// break;
default:
errorThrow("Unsupported texture format");
}
);
errorChain(textureInit(
texture, assetData->width, assetData->height, format, data
));
errorOk();
}

View File

@@ -7,6 +7,7 @@
#pragma once
#include "error/error.h"
#include "display/color.h"
#define ASSET_TEXTURE_WIDTH_MAX 2048
#define ASSET_TEXTURE_HEIGHT_MAX 2048
@@ -20,9 +21,10 @@ typedef struct assetentire_s assetentire_t;
typedef struct {
char_t header[3];
uint8_t version;
uint8_t type;
uint32_t width;
uint32_t height;
uint8_t palette[ASSET_TEXTURE_SIZE_MAX];
uint8_t data[ASSET_TEXTURE_SIZE_MAX * sizeof(color4b_t)];
} assettexture_t;
#pragma pack(pop)

View File

@@ -7,15 +7,16 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
display.c
framebuffer.c
screen.c
spritebatch.c
text.c
)
# Subdirectories
add_subdirectory(camera)
add_subdirectory(framebuffer)
add_subdirectory(mesh)
add_subdirectory(screen)
add_subdirectory(shader)
add_subdirectory(spritebatch)
add_subdirectory(text)
add_subdirectory(texture)
# Color definitions

View File

@@ -0,0 +1,107 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "camera.h"
#include "display/display.h"
#include "assert/assert.h"
#include "display/framebuffer/framebuffer.h"
#include "display/screen/screen.h"
void cameraInit(camera_t *camera) {
cameraInitPerspective(camera);
}
void cameraInitPerspective(camera_t *camera) {
assertNotNull(camera, "Not a camera component");
camera->projType = CAMERA_PROJECTION_TYPE_PERSPECTIVE;
camera->perspective.fov = glm_rad(45.0f);
camera->nearClip = 0.1f;
camera->farClip = 10000.0f;
camera->viewType = CAMERA_VIEW_TYPE_LOOKAT;
glm_vec3_copy((vec3){ 5.0f, 5.0f, 5.0f }, camera->lookat.position);
glm_vec3_copy((vec3){ 0.0f, 1.0f, 0.0f }, camera->lookat.up);
glm_vec3_copy((vec3){ 0.0f, 0.0f, 0.0f }, camera->lookat.target);
}
void cameraInitOrthographic(camera_t *camera) {
assertNotNull(camera, "Not a camera component");
camera->projType = CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC;
camera->orthographic.left = 0.0f;
camera->orthographic.right = SCREEN.width;
camera->orthographic.top = 0.0f;
camera->orthographic.bottom = SCREEN.height;
camera->nearClip = 0.1f;
camera->farClip = 1.0f;
camera->viewType = CAMERA_VIEW_TYPE_2D;
glm_vec2_copy((vec2){ 0.0f, 0.0f }, camera->_2d.position);
camera->_2d.zoom = 1.0f;
}
void cameraGetProjectionMatrix(camera_t *camera, mat4 dest) {
assertNotNull(camera, "Not a camera component");
assertNotNull(dest, "Destination matrix must not be null");
if(
camera->projType == CAMERA_PROJECTION_TYPE_PERSPECTIVE ||
camera->projType == CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED
) {
glm_mat4_identity(dest);
glm_perspective(
camera->perspective.fov,
SCREEN.aspect,
camera->nearClip,
camera->farClip,
dest
);
if(camera->projType == CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED) {
dest[1][1] *= -1.0f;
}
} else if(camera->projType == CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) {
glm_mat4_identity(dest);
glm_ortho(
camera->orthographic.left,
camera->orthographic.right,
camera->orthographic.top,
camera->orthographic.bottom,
camera->nearClip,
camera->farClip,
dest
);
}
}
void cameraGetViewMatrix(camera_t *camera, mat4 dest) {
assertNotNull(camera, "Not a camera component");
assertNotNull(dest, "Destination matrix must not be null");
if(camera->viewType == CAMERA_VIEW_TYPE_MATRIX) {
glm_mat4_ucopy(camera->view, dest);
} else if(camera->viewType == CAMERA_VIEW_TYPE_LOOKAT) {
glm_mat4_identity(dest);
glm_lookat(
camera->lookat.position,
camera->lookat.target,
camera->lookat.up,
dest
);
} else if(camera->viewType == CAMERA_VIEW_TYPE_2D) {
glm_mat4_identity(dest);
glm_lookat(
(vec3){ camera->_2d.position[0], camera->_2d.position[1], 0.5f },
(vec3){ camera->_2d.position[0], camera->_2d.position[1], 0.0f },
(vec3){ 0.0f, 1.0f, 0.0f },
dest
);
} else if(camera->viewType == CAMERA_VIEW_TYPE_LOOKAT_PIXEL_PERFECT) {
assertUnreachable("LOOKAT_PIXEL_PERFECT view type is not implemented yet");
}
}

Some files were not shown because too many files have changed in this diff Show More