diff --git a/cmake/targets/saturn.cmake b/cmake/targets/saturn.cmake index 3e338ab6..5bd38376 100644 --- a/cmake/targets/saturn.cmake +++ b/cmake/targets/saturn.cmake @@ -157,6 +157,8 @@ add_custom_command(TARGET ${DUSK_BINARY_TARGET_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory "${DUSK_SAT_CD_DIR}" COMMAND ${CMAKE_COMMAND} -E make_directory "${DUSK_SAT_AUDIO_DIR}" COMMAND ${CMAKE_COMMAND} -E copy "${DUSK_SAT_BIN}" "${DUSK_SAT_CD_DIR}/A.BIN" + # Asset archive — read at runtime via cdfs + zip_source_buffer + COMMAND ${CMAKE_COMMAND} -E copy "${DUSK_ASSETS_ZIP}" "${DUSK_SAT_CD_DIR}/DUSK.DSK" # ISO 9660 requires these auxiliary text files COMMAND ${CMAKE_COMMAND} -E touch "${DUSK_SAT_CD_DIR}/ABS.TXT" COMMAND ${CMAKE_COMMAND} -E touch "${DUSK_SAT_CD_DIR}/BIB.TXT" diff --git a/docker/saturn/Dockerfile b/docker/saturn/Dockerfile index e9ac8cdf..1139e504 100644 --- a/docker/saturn/Dockerfile +++ b/docker/saturn/Dockerfile @@ -40,7 +40,6 @@ RUN apt-get update && apt-get install -y \ libgmp-dev \ libmpfr-dev \ libmpc-dev \ - xorriso \ && rm -rf /var/lib/apt/lists/* RUN mkdir -p "${YAUL_INSTALL_ROOT}" @@ -358,5 +357,12 @@ RUN wget -q https://github.com/nih-at/libzip/releases/download/v1.10.1/libzip-1. && rm -rf /tmp/libzip-1.10.1 /tmp/libzip-build /tmp/sat-xc.cmake \ ; rm -f /tmp/libzip.tar.gz /tmp/zlib.tar.gz 2>/dev/null ; true +# --------------------------------------------------------------------------- +# 15. Disc-image tools (separate layer so it does not invalidate the +# expensive cross-compiler cache layers above on changes). +# xorriso provides xorrisofs, needed by Yaul's make-iso script. +# --------------------------------------------------------------------------- +RUN apt-get update && apt-get install -y xorriso && rm -rf /var/lib/apt/lists/* + WORKDIR /workdir VOLUME ["/workdir"] diff --git a/src/dusk/engine/engine.c b/src/dusk/engine/engine.c index d7f4021c..f34aa72b 100644 --- a/src/dusk/engine/engine.c +++ b/src/dusk/engine/engine.c @@ -35,8 +35,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) { errorChain(sceneInit()); consolePrint("Engine initialized"); - // sceneSet(SCENE_TYPE_SPINNINGBOX); - sceneSet(SCENE_TYPE_SPINNINGBOX); + sceneSet(SCENE_TYPE_RAINBOWNOTHING); errorOk(); } diff --git a/src/dusk/input/input.c b/src/dusk/input/input.c index 111c5f6d..be89ee71 100644 --- a/src/dusk/input/input.c +++ b/src/dusk/input/input.c @@ -161,39 +161,8 @@ int16_t inputAxis(const inputaction_t neg, const inputaction_t pos) { assertTrue(neg < INPUT_ACTION_COUNT, "Negative input action out of bounds"); assertTrue(pos < INPUT_ACTION_COUNT, "Positive input action out of bounds"); - return (int16_t)(inputGetCurrentValue(pos) - inputGetCurrentValue(neg)); -} - -void inputAxis2D( - const inputaction_t negX, const inputaction_t posX, - const inputaction_t negY, const inputaction_t posY, - vec2 result -) { - assertNotNull(result, "Result vector cannot be null"); - result[0] = (float_t)inputAxis(negX, posX) / (float_t)INT16_MAX; - result[1] = (float_t)inputAxis(negY, posY) / (float_t)INT16_MAX; -} - -void inputAngle2D( - const inputaction_t negX, const inputaction_t posX, - const inputaction_t negY, const inputaction_t posY, - vec2 result -) { - assertNotNull(result, "Result vector cannot be null"); - float_t x = (float_t)inputAxis(negX, posX) / (float_t)INT16_MAX; - float_t y = (float_t)inputAxis(negY, posY) / (float_t)INT16_MAX; - float_t mag = sqrtf(x * x + y * y); - if(mag <= 0.0f) { - result[0] = 0.0f; - result[1] = 0.0f; - return; - } - if(mag > 1.0f) { - x /= mag; - y /= mag; - } - result[0] = x; - result[1] = y; + int32_t raw = (int32_t)inputGetCurrentValue(pos) - (int32_t)inputGetCurrentValue(neg); + return (int16_t)mathClamp(raw, -INT16_MAX, INT16_MAX); } void inputBind(const inputbutton_t button, const inputaction_t act) { diff --git a/src/dusk/input/input.h b/src/dusk/input/input.h index 049fe77b..2051184a 100644 --- a/src/dusk/input/input.h +++ b/src/dusk/input/input.h @@ -110,37 +110,6 @@ bool_t inputReleased(const inputaction_t action); */ int16_t inputAxis(const inputaction_t neg, const inputaction_t pos); -/** - * Gets the values of a 2D input axis, defined by two pairs of actions (negative - * and positive for each axis). - * - * @param negX The action representing the negative direction of the X axis. - * @param posX The action representing the positive direction of the X axis. - * @param negY The action representing the negative direction of the Y axis. - * @param posY The action representing the positive direction of the Y axis. - * @param result A vec2 to store the resulting axis values (-1.0f to 1.0f). - */ -void inputAxis2D( - const inputaction_t negX, const inputaction_t posX, - const inputaction_t negY, const inputaction_t posY, - vec2 result -); - -/** - * Returns an angled 2D vector based on the input of 4 actions. Functionally - * using atan2 to get the angle of the input then multiplying by a unit vector. - * - * @param negX The action representing the negative direction of the X axis. - * @param posX The action representing the positive direction of the X axis. - * @param negY The action representing the negative direction of the Y axis. - * @param posY The action representing the positive direction of the Y axis. - * @param result A vec2 to store the resulting axis values (-1.0f to 1.0f). - */ -void inputAngle2D( - const inputaction_t negX, const inputaction_t posX, - const inputaction_t negY, const inputaction_t posY, - vec2 result -); /** * Binds an input button to an action. diff --git a/src/dusk/scene/CMakeLists.txt b/src/dusk/scene/CMakeLists.txt index 2ba4664e..e26e5abd 100644 --- a/src/dusk/scene/CMakeLists.txt +++ b/src/dusk/scene/CMakeLists.txt @@ -10,8 +10,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} ) # Subdirs -add_subdirectory(full) add_subdirectory(overworld) -add_subdirectory(rainbownothing) -add_subdirectory(spinningbox) -add_subdirectory(white32) \ No newline at end of file +add_subdirectory(testscenes) \ No newline at end of file diff --git a/src/dusk/scene/scenetype.c b/src/dusk/scene/scenetype.c index bf48c371..14ddae18 100644 --- a/src/dusk/scene/scenetype.c +++ b/src/dusk/scene/scenetype.c @@ -10,11 +10,11 @@ scenecallbacks_t SCENE_TYPES[SCENE_TYPE_COUNT] = { [SCENE_TYPE_NULL] = { 0 }, - [SCENE_TYPE_FULL] = { - .init = sceneFullInit, - .update = sceneFullUpdate, - .render = sceneFullRender, - .dispose = sceneFullDispose + [SCENE_TYPE_INPUT] = { + .init = sceneInputInit, + .update = sceneInputUpdate, + .render = sceneInputRender, + .dispose = sceneInputDispose }, [SCENE_TYPE_OVERWORLD] = { @@ -24,6 +24,13 @@ scenecallbacks_t SCENE_TYPES[SCENE_TYPE_COUNT] = { .dispose = sceneOverworldDispose }, + [SCENE_TYPE_FULL] = { + .init = sceneFullInit, + .update = sceneFullUpdate, + .render = sceneFullRender, + .dispose = sceneFullDispose + }, + [SCENE_TYPE_RAINBOWNOTHING] = { .init = sceneRainbowNothingInit, .update = sceneRainbowNothingUpdate, @@ -38,6 +45,13 @@ scenecallbacks_t SCENE_TYPES[SCENE_TYPE_COUNT] = { .dispose = sceneSpinningBoxDispose }, + [SCENE_TYPE_TEST] = { + .init = sceneTestInit, + .update = sceneTestUpdate, + .render = sceneTestRender, + .dispose = sceneTestDispose + }, + [SCENE_TYPE_WHITE32] = { .init = sceneWhite32Init, .update = sceneWhite32Update, diff --git a/src/dusk/scene/scenetype.h b/src/dusk/scene/scenetype.h index 9b7b3a74..2d2a4242 100644 --- a/src/dusk/scene/scenetype.h +++ b/src/dusk/scene/scenetype.h @@ -7,17 +7,21 @@ #pragma once #include "scene/scenebase.h" -#include "scene/full/scenefull.h" +#include "scene/testscenes/inputscene/sceneinput.h" #include "scene/overworld/sceneoverworld.h" -#include "scene/rainbownothing/scenerainbownothing.h" -#include "scene/spinningbox/scenespinningbox.h" -#include "scene/white32/scenewhite32.h" +#include "scene/testscenes/full/scenefull.h" +#include "scene/testscenes/rainbownothing/scenerainbownothing.h" +#include "scene/testscenes/spinningbox/scenespinningbox.h" +#include "scene/testscenes/test/scenetest.h" +#include "scene/testscenes/white32/scenewhite32.h" typedef union scenedata_u { - scenefull_t full; + sceneinput_t input; sceneoverworld_t overworld; + scenefull_t full; scenerainbownothing_t rainbownothing; scenespinningbox_t spinningbox; + scenetest_t test; scenewhite32_t white32; } scenedata_t; @@ -32,12 +36,14 @@ typedef struct { typedef enum { SCENE_TYPE_NULL, - SCENE_TYPE_FULL, + SCENE_TYPE_INPUT, SCENE_TYPE_OVERWORLD, + SCENE_TYPE_FULL, SCENE_TYPE_RAINBOWNOTHING, SCENE_TYPE_SPINNINGBOX, + SCENE_TYPE_TEST, SCENE_TYPE_WHITE32, SCENE_TYPE_COUNT } scenetype_t; -extern scenecallbacks_t SCENE_TYPES[SCENE_TYPE_COUNT]; \ No newline at end of file +extern scenecallbacks_t SCENE_TYPES[SCENE_TYPE_COUNT]; diff --git a/src/dusk/scene/testscenes/CMakeLists.txt b/src/dusk/scene/testscenes/CMakeLists.txt new file mode 100644 index 00000000..062f193e --- /dev/null +++ b/src/dusk/scene/testscenes/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +add_subdirectory(inputscene) +add_subdirectory(full) +add_subdirectory(rainbownothing) +add_subdirectory(spinningbox) +add_subdirectory(test) +add_subdirectory(white32) diff --git a/src/dusk/scene/full/CMakeLists.txt b/src/dusk/scene/testscenes/full/CMakeLists.txt similarity index 100% rename from src/dusk/scene/full/CMakeLists.txt rename to src/dusk/scene/testscenes/full/CMakeLists.txt diff --git a/src/dusk/scene/full/scenefull.c b/src/dusk/scene/testscenes/full/scenefull.c similarity index 99% rename from src/dusk/scene/full/scenefull.c rename to src/dusk/scene/testscenes/full/scenefull.c index 5d305f02..21f67ce6 100644 --- a/src/dusk/scene/full/scenefull.c +++ b/src/dusk/scene/testscenes/full/scenefull.c @@ -5,7 +5,7 @@ * https://opensource.org/licenses/MIT */ -#include "scene/full/scenefull.h" +#include "scene/testscenes/full/scenefull.h" #include "assert/assert.h" #include "display/render/render.h" #include "display/screen.h" diff --git a/src/dusk/scene/full/scenefull.h b/src/dusk/scene/testscenes/full/scenefull.h similarity index 100% rename from src/dusk/scene/full/scenefull.h rename to src/dusk/scene/testscenes/full/scenefull.h diff --git a/src/dusk/scene/testscenes/inputscene/CMakeLists.txt b/src/dusk/scene/testscenes/inputscene/CMakeLists.txt new file mode 100644 index 00000000..1fbad41f --- /dev/null +++ b/src/dusk/scene/testscenes/inputscene/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + sceneinput.c +) diff --git a/src/dusk/scene/testscenes/inputscene/sceneinput.c b/src/dusk/scene/testscenes/inputscene/sceneinput.c new file mode 100644 index 00000000..3d3130bb --- /dev/null +++ b/src/dusk/scene/testscenes/inputscene/sceneinput.c @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "scene/scenetype.h" +#include "assert/assert.h" +#include "display/render/render.h" +#include "display/screen.h" +#include "display/color.h" +#include "input/input.h" +#include "time/time.h" +#include "util/short.h" + +// Rotations per second at full stick deflection. +#define INPUT_SCENE_ROTATE_SPEED 0.5f + +static const uint8_t BOX_INDICES[3 * 3] = { + 0, 1, 2, + 3, 4, 5, + 6, 7, 8, +}; + +static const color_t BOX_PALETTE[256] = { + [0] = { 255, 0, 0, 255 }, + [1] = { 0, 255, 0, 255 }, + [2] = { 0, 0, 255, 255 }, + [3] = { 255, 255, 0, 255 }, + [4] = { 255, 0, 255, 255 }, + [5] = { 0, 255, 255, 255 }, + [6] = { 255, 165, 0, 255 }, + [7] = { 128, 0, 255, 255 }, + [8] = { 255, 255, 255, 255 }, +}; + +static rtexture_t inputTex; + +#define ASPECT FIXED((float_t)SCREEN.width / (float_t)SCREEN.height) +#define FOV_Y FIXED(1.0472f) + +errorret_t sceneInputInit(scenedata_t *data) { + data->input.angleX = SHORT_ZERO; + data->input.angleY = SHORT_ZERO; + inputTex = renderTextureCreate(3, 3, BOX_INDICES, BOX_PALETTE); + assertTrue(inputTex != RTEXTURE_NONE, "Failed to create input scene texture"); + errorOk(); +} + +errorret_t sceneInputUpdate(scenedata_t *data) { + // 65536 binary angle units = 1 full rotation; 65536 / SHORT_MAX ≈ 2 + int16_t axisY = inputAxis(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT); + int32_t dY = (int32_t)((float_t)axisY * 2.0f * INPUT_SCENE_ROTATE_SPEED * TIME.delta); + data->input.angleY = shortAdd(data->input.angleY, (short_t)dY); + + int16_t axisX = inputAxis(INPUT_ACTION_UP, INPUT_ACTION_DOWN); + int32_t dX = (int32_t)((float_t)axisX * 2.0f * INPUT_SCENE_ROTATE_SPEED * TIME.delta); + data->input.angleX = shortAdd(data->input.angleX, (short_t)dX); + errorOk(); +} + +errorret_t sceneInputRender(scenedata_t *data) { + renderClear(color(16, 16, 24, 255)); + + renderSetProjection(FOV_Y, ASPECT, FIXED(10), FIXED(10000)); + renderSetView(0, 0, 270, 0, 0, 0); + + short_t aX = data->input.angleX; + short_t aY = data->input.angleY; + renderQuad3D( + 0, 0, 0, + (int16_t)((int32_t)shortCos(aY) * 72 / SHORT_MAX), 0, + (int16_t)((int32_t)shortSin(aY) * 72 / SHORT_MAX), + 0, + (int16_t)((int32_t)shortCos(aX) * 72 / SHORT_MAX), + (int16_t)((int32_t)shortSin(aX) * 72 / SHORT_MAX), + 0, inputTex, COLOR_WHITE + ); + + errorOk(); +} + +errorret_t sceneInputDispose(scenedata_t *data) { + renderTextureDispose(inputTex); + errorOk(); +} diff --git a/src/dusk/scene/testscenes/inputscene/sceneinput.h b/src/dusk/scene/testscenes/inputscene/sceneinput.h new file mode 100644 index 00000000..af1d01e9 --- /dev/null +++ b/src/dusk/scene/testscenes/inputscene/sceneinput.h @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "scene/scenebase.h" +#include "util/short.h" + +typedef struct { + short_t angleX; + short_t angleY; +} sceneinput_t; + +errorret_t sceneInputInit(scenedata_t *data); +errorret_t sceneInputUpdate(scenedata_t *data); +errorret_t sceneInputRender(scenedata_t *data); +errorret_t sceneInputDispose(scenedata_t *data); diff --git a/src/dusk/scene/rainbownothing/CMakeLists.txt b/src/dusk/scene/testscenes/rainbownothing/CMakeLists.txt similarity index 100% rename from src/dusk/scene/rainbownothing/CMakeLists.txt rename to src/dusk/scene/testscenes/rainbownothing/CMakeLists.txt diff --git a/src/dusk/scene/rainbownothing/scenerainbownothing.c b/src/dusk/scene/testscenes/rainbownothing/scenerainbownothing.c similarity index 94% rename from src/dusk/scene/rainbownothing/scenerainbownothing.c rename to src/dusk/scene/testscenes/rainbownothing/scenerainbownothing.c index 33ca2e20..03a97935 100644 --- a/src/dusk/scene/rainbownothing/scenerainbownothing.c +++ b/src/dusk/scene/testscenes/rainbownothing/scenerainbownothing.c @@ -5,7 +5,7 @@ * https://opensource.org/licenses/MIT */ -#include "scene/rainbownothing/scenerainbownothing.h" +#include "scene/testscenes/rainbownothing/scenerainbownothing.h" #include "display/render/render.h" #include "display/color.h" diff --git a/src/dusk/scene/rainbownothing/scenerainbownothing.h b/src/dusk/scene/testscenes/rainbownothing/scenerainbownothing.h similarity index 100% rename from src/dusk/scene/rainbownothing/scenerainbownothing.h rename to src/dusk/scene/testscenes/rainbownothing/scenerainbownothing.h diff --git a/src/dusk/scene/spinningbox/CMakeLists.txt b/src/dusk/scene/testscenes/spinningbox/CMakeLists.txt similarity index 100% rename from src/dusk/scene/spinningbox/CMakeLists.txt rename to src/dusk/scene/testscenes/spinningbox/CMakeLists.txt diff --git a/src/dusk/scene/spinningbox/scenespinningbox.c b/src/dusk/scene/testscenes/spinningbox/scenespinningbox.c similarity index 96% rename from src/dusk/scene/spinningbox/scenespinningbox.c rename to src/dusk/scene/testscenes/spinningbox/scenespinningbox.c index 9200dd05..c86cb962 100644 --- a/src/dusk/scene/spinningbox/scenespinningbox.c +++ b/src/dusk/scene/testscenes/spinningbox/scenespinningbox.c @@ -5,7 +5,7 @@ * https://opensource.org/licenses/MIT */ -#include "scene/spinningbox/scenespinningbox.h" +#include "scene/testscenes/spinningbox/scenespinningbox.h" #include "assert/assert.h" #include "display/render/render.h" #include "display/screen.h" diff --git a/src/dusk/scene/spinningbox/scenespinningbox.h b/src/dusk/scene/testscenes/spinningbox/scenespinningbox.h similarity index 100% rename from src/dusk/scene/spinningbox/scenespinningbox.h rename to src/dusk/scene/testscenes/spinningbox/scenespinningbox.h diff --git a/src/dusk/scene/test/CMakeLists.txt b/src/dusk/scene/testscenes/test/CMakeLists.txt similarity index 100% rename from src/dusk/scene/test/CMakeLists.txt rename to src/dusk/scene/testscenes/test/CMakeLists.txt diff --git a/src/dusk/scene/test/scenetest.c b/src/dusk/scene/testscenes/test/scenetest.c similarity index 94% rename from src/dusk/scene/test/scenetest.c rename to src/dusk/scene/testscenes/test/scenetest.c index b3d7511d..bed915a9 100644 --- a/src/dusk/scene/test/scenetest.c +++ b/src/dusk/scene/testscenes/test/scenetest.c @@ -5,7 +5,7 @@ * https://opensource.org/licenses/MIT */ -#include "scene/test/scenetest.h" +#include "scene/testscenes/test/scenetest.h" #include "display/render/render.h" #include "display/color.h" #include "engine/engine.h" diff --git a/src/dusk/scene/test/scenetest.h b/src/dusk/scene/testscenes/test/scenetest.h similarity index 100% rename from src/dusk/scene/test/scenetest.h rename to src/dusk/scene/testscenes/test/scenetest.h diff --git a/src/dusk/scene/white32/CMakeLists.txt b/src/dusk/scene/testscenes/white32/CMakeLists.txt similarity index 100% rename from src/dusk/scene/white32/CMakeLists.txt rename to src/dusk/scene/testscenes/white32/CMakeLists.txt diff --git a/src/dusk/scene/white32/scenewhite32.c b/src/dusk/scene/testscenes/white32/scenewhite32.c similarity index 92% rename from src/dusk/scene/white32/scenewhite32.c rename to src/dusk/scene/testscenes/white32/scenewhite32.c index b1ec592e..ce2f5a10 100644 --- a/src/dusk/scene/white32/scenewhite32.c +++ b/src/dusk/scene/testscenes/white32/scenewhite32.c @@ -5,7 +5,7 @@ * https://opensource.org/licenses/MIT */ -#include "scene/white32/scenewhite32.h" +#include "scene/testscenes/white32/scenewhite32.h" #include "display/render/render.h" #include "display/color.h" #include "engine/engine.h" diff --git a/src/dusk/scene/white32/scenewhite32.h b/src/dusk/scene/testscenes/white32/scenewhite32.h similarity index 100% rename from src/dusk/scene/white32/scenewhite32.h rename to src/dusk/scene/testscenes/white32/scenewhite32.h diff --git a/src/dusk/thread/thread.c b/src/dusk/thread/thread.c index 96e928a5..aa6d0e9f 100644 --- a/src/dusk/thread/thread.c +++ b/src/dusk/thread/thread.c @@ -41,6 +41,11 @@ void threadStartRequest(thread_t *thread) { thread ); pthread_detach(thread->threadId); + #else + // No threading: skip straight to STOPPED so threadStart's wait loop exits. + threadMutexLock(&thread->stateMutex); + thread->state = THREAD_STATE_STOPPED; + threadMutexUnlock(&thread->stateMutex); #endif } diff --git a/src/dusk/util/CMakeLists.txt b/src/dusk/util/CMakeLists.txt index 95e34534..18ababdd 100644 --- a/src/dusk/util/CMakeLists.txt +++ b/src/dusk/util/CMakeLists.txt @@ -15,4 +15,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} math.c sort.c ref.c + short.c ) \ No newline at end of file diff --git a/src/dusk/util/short.c b/src/dusk/util/short.c new file mode 100644 index 00000000..9528c709 --- /dev/null +++ b/src/dusk/util/short.c @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "short.h" + +/* LUT: 256 entries covering one full rotation (0..2π). + Generated: sin(i * 2π / 256) * SHORT_MAX, i = 0..255. */ +static const short_t SHORT_SIN_LUT[256] = { + 0, 804, 1608, 2410, 3212, 4011, 4808, 5602, + 6393, 7179, 7962, 8739, 9512, 10278, 11039, 11793, + 12539, 13279, 14010, 14732, 15446, 16151, 16846, 17530, + 18204, 18868, 19519, 20159, 20787, 21403, 22005, 22594, + 23170, 23731, 24279, 24811, 25329, 25832, 26319, 26790, + 27245, 27683, 28105, 28510, 28898, 29268, 29621, 29956, + 30273, 30571, 30852, 31113, 31356, 31580, 31785, 31971, + 32137, 32285, 32412, 32521, 32609, 32678, 32728, 32757, + 32767, 32757, 32728, 32678, 32609, 32521, 32412, 32285, + 32137, 31971, 31785, 31580, 31356, 31113, 30852, 30571, + 30273, 29956, 29621, 29268, 28898, 28510, 28105, 27683, + 27245, 26790, 26319, 25832, 25329, 24811, 24279, 23731, + 23170, 22594, 22005, 21403, 20787, 20159, 19519, 18868, + 18204, 17530, 16846, 16151, 15446, 14732, 14010, 13279, + 12539, 11793, 11039, 10278, 9512, 8739, 7962, 7179, + 6393, 5602, 4808, 4011, 3212, 2410, 1608, 804, + 0, -804, -1608, -2410, -3212, -4011, -4808, -5602, + -6393, -7179, -7962, -8739, -9512, -10278, -11039, -11793, + -12539, -13279, -14010, -14732, -15446, -16151, -16846, -17530, + -18204, -18868, -19519, -20159, -20787, -21403, -22005, -22594, + -23170, -23731, -24279, -24811, -25329, -25832, -26319, -26790, + -27245, -27683, -28105, -28510, -28898, -29268, -29621, -29956, + -30273, -30571, -30852, -31113, -31356, -31580, -31785, -31971, + -32137, -32285, -32412, -32521, -32609, -32678, -32728, -32757, + -32767, -32757, -32728, -32678, -32609, -32521, -32412, -32285, + -32137, -31971, -31785, -31580, -31356, -31113, -30852, -30571, + -30273, -29956, -29621, -29268, -28898, -28510, -28105, -27683, + -27245, -26790, -26319, -25832, -25329, -24811, -24279, -23731, + -23170, -22594, -22005, -21403, -20787, -20159, -19519, -18868, + -18204, -17530, -16846, -16151, -15446, -14732, -14010, -13279, + -12539, -11793, -11039, -10278, -9512, -8739, -7962, -7179, + -6393, -5602, -4808, -4011, -3212, -2410, -1608, -804, +}; + +short_t shortSin(short_t angle) { + return SHORT_SIN_LUT[(uint16_t)angle >> 8]; +} + +short_t shortCos(short_t angle) { + return SHORT_SIN_LUT[(uint8_t)(((uint16_t)angle >> 8) + 64u)]; +} diff --git a/src/dusk/util/short.h b/src/dusk/util/short.h new file mode 100644 index 00000000..f893d852 --- /dev/null +++ b/src/dusk/util/short.h @@ -0,0 +1,116 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" +#include "util/math.h" + +/** + * Signed 16-bit integer used to represent normalised directional values. + * Range: -SHORT_MAX to SHORT_MAX (-32767 to 32767). + * INT16_MIN (-32768) is intentionally excluded to keep the range symmetric. + */ +typedef int16_t short_t; + +#define SHORT_MAX ((short_t)INT16_MAX) +#define SHORT_MIN ((short_t)-INT16_MAX) +#define SHORT_ZERO ((short_t)0) + +/** + * Converts a float in [-1, 1] to a short_t. + * + * @param f Float value in [-1, 1]. + * @returns Scaled short_t in [-SHORT_MAX, SHORT_MAX]. + */ +#define shortFromFloat(f) \ + ((short_t)mathClamp((int32_t)((f) * (float_t)SHORT_MAX), SHORT_MIN, SHORT_MAX)) + +/** + * Converts a short_t to a float in [-1, 1]. + * + * @param s short_t value. + * @returns Float in [-1, 1]. + */ +#define shortToFloat(s) ((float_t)(s) / (float_t)SHORT_MAX) + +/** + * Adds two short_t values, clamping to [-SHORT_MAX, SHORT_MAX]. + * + * @param a First operand. + * @param b Second operand. + * @returns Clamped a + b. + */ +#define shortAdd(a, b) \ + ((short_t)mathClamp((int32_t)(a) + (int32_t)(b), SHORT_MIN, SHORT_MAX)) + +/** + * Subtracts two short_t values, clamping to [-SHORT_MAX, SHORT_MAX]. + * + * @param a First operand. + * @param b Second operand. + * @returns Clamped a - b. + */ +#define shortSub(a, b) \ + ((short_t)mathClamp((int32_t)(a) - (int32_t)(b), SHORT_MIN, SHORT_MAX)) + +/** + * Negates a short_t, clamping to [-SHORT_MAX, SHORT_MAX]. + * + * @param s Value to negate. + * @returns Clamped -s. + */ +#define shortNeg(s) ((short_t)mathClamp(-(int32_t)(s), SHORT_MIN, SHORT_MAX)) + +/** + * Returns the absolute value of a short_t. + * + * @param s Value. + * @returns |s|, in [0, SHORT_MAX]. + */ +#define shortAbs(s) ((short_t)((s) < 0 ? -(s) : (s))) + +/** + * Clamps a short_t between lo and hi. + * + * @param s Value to clamp. + * @param lo Lower bound. + * @param hi Upper bound. + * @returns Clamped value. + */ +#define shortClamp(s, lo, hi) ((short_t)mathClamp((int32_t)(s), (int32_t)(lo), (int32_t)(hi))) + +/** + * Returns the sine of an angle as a short_t. + * Angle is a binary angle: the full uint16_t range (0..65535) = one full + * rotation. Key values: 0=0°, 16384=90°, 32767≈180°, -16384=270°. + * Result is in [-SHORT_MAX, SHORT_MAX]. + * + * @param angle Binary angle. + * @returns Sine scaled to [-SHORT_MAX, SHORT_MAX]. + */ +short_t shortSin(short_t angle); + +/** + * Returns the cosine of an angle as a short_t. + * Same angle convention as shortSin. + * + * @param angle Binary angle. + * @returns Cosine scaled to [-SHORT_MAX, SHORT_MAX]. + */ +short_t shortCos(short_t angle); + +/** + * Linearly interpolates between two short_t values. + * t is a short_t in [0, SHORT_MAX] representing 0..1. + * + * @param a Start value. + * @param b End value. + * @param t Interpolation factor in [0, SHORT_MAX]. + * @returns Interpolated short_t. + */ +#define shortLerp(a, b, t) \ + ((short_t)((int32_t)(a) + (((int32_t)((b) - (a)) * (int32_t)(t)) / SHORT_MAX))) diff --git a/src/dusksaturn/asset/assetsaturn.c b/src/dusksaturn/asset/assetsaturn.c index e5d2055c..cb1efcb1 100644 --- a/src/dusksaturn/asset/assetsaturn.c +++ b/src/dusksaturn/asset/assetsaturn.c @@ -6,11 +6,84 @@ */ #include "assetsaturn.h" +#include "asset/asset.h" #include "log/log.h" +#include "util/memory.h" +#include "util/string.h" +#include +#include +#include errorret_t assetInitSaturn(void) { logDebug("[Saturn] assetInitSaturn: initializing CD-Block\n"); cd_block_init(); + + cdfs_init(); + cdfs_config_default_set(); + + cdfs_filelist_entry_t *entries = cdfs_entries_alloc(CDFS_FILELIST_ENTRIES_COUNT); + if(!entries) errorThrow("Failed to alloc CDFS entries"); + + cdfs_filelist_t filelist; + cdfs_filelist_init(&filelist, entries, CDFS_FILELIST_ENTRIES_COUNT); + cdfs_filelist_root_read(&filelist); + + // Find dusk.dsk — ISO 9660 Level-1 names are uppercase and may carry a ";N" + // version suffix ("DUSK.DSK;1"). Compare only the base 8.3 name. + const cdfs_filelist_entry_t *dskEntry = NULL; + for(uint32_t i = 0; i < filelist.entries_count; i++) { + if(filelist.entries[i].type != CDFS_ENTRY_TYPE_FILE) continue; + + char baseName[ISO_FILENAME_MAX_LENGTH + 1]; + uint8_t j = 0; + while(j < ISO_FILENAME_MAX_LENGTH) { + char c = filelist.entries[i].name[j]; + if(c == '\0' || c == ';') break; + baseName[j++] = c; + } + baseName[j] = '\0'; + + if(stringCompareInsensitive(baseName, ASSET_FILE_NAME) == 0) { + dskEntry = &filelist.entries[i]; + break; + } + } + + if(!dskEntry) { + cdfs_entries_free(entries); + errorThrow("dusk.dsk not found on disc"); + } + + const size_t fileSize = dskEntry->size; + const fad_t startFad = dskEntry->starting_fad; + cdfs_entries_free(entries); + + // cd_block_sectors_read requires byte-length to be sector-aligned. + uint32_t readLen = ((uint32_t)fileSize + CDFS_SECTOR_SIZE - 1u) & + ~(CDFS_SECTOR_SIZE - 1u); + void *data = malloc(readLen); + if(!data) errorThrow("Failed to alloc buffer for dusk.dsk"); + + if(cd_block_sectors_read(startFad, data, readLen) != 0) { + free(data); + errorThrow("CD read error reading dusk.dsk"); + } + + // Open zip from the in-memory buffer (flag=1 → libzip owns and frees data). + zip_error_t zerr; + zip_source_t *src = zip_source_buffer_create(data, (zip_uint64_t)fileSize, 1, &zerr); + if(!src) { + free(data); + errorThrow("Failed to create zip source from disc buffer"); + } + + ASSET.zip = zip_open_from_source(src, ZIP_RDONLY, &zerr); + if(!ASSET.zip) { + zip_source_free(src); + errorThrow("Failed to open dusk.dsk from disc"); + } + + logDebug("[Saturn] dusk.dsk opened (%u bytes)\n", (unsigned)fileSize); errorOk(); } diff --git a/src/dusksaturn/compat.c b/src/dusksaturn/compat.c index ad41319b..81950d4b 100644 --- a/src/dusksaturn/compat.c +++ b/src/dusksaturn/compat.c @@ -269,6 +269,31 @@ float fmodf(float x, float y) { * stdio.h FILE typedef that the sysroot may or may not provide. * ========================================================================= */ +/* fdopen / fileno / ftello / fseeko — POSIX file-descriptor/stdio bridge. + * libzip's stdio source objects reference these even when only the buffer + * source path is used (no -ffunction-sections in the pre-built sysroot lib). + * Saturn has no file descriptors; return errors unconditionally. */ + +void *fdopen(int fd, const void *mode) { + (void)fd; (void)mode; + return NULL; +} + +int fileno(void *stream) { + (void)stream; + return -1; +} + +long long ftello(void *stream) { + (void)stream; + return -1LL; +} + +int fseeko(void *stream, long long offset, int whence) { + (void)stream; (void)offset; (void)whence; + return -1; +} + size_t fread(void *ptr, size_t size, size_t nmemb, void *stream) { (void)ptr; (void)size; (void)nmemb; (void)stream; return 0; diff --git a/src/dusksaturn/display/displaysaturn.c b/src/dusksaturn/display/displaysaturn.c index 81b4a42a..0ac98832 100644 --- a/src/dusksaturn/display/displaysaturn.c +++ b/src/dusksaturn/display/displaysaturn.c @@ -18,7 +18,6 @@ #include errorret_t displaySaturnInit(void) { - logDebug("[Saturn] displaySaturnInit: start\n"); DISPLAY.whichBuffer = 0; vdp2_tvmd_display_res_set( @@ -31,10 +30,7 @@ errorret_t displaySaturnInit(void) { /* Disable all NBG/RBG scroll planes; game content drawn entirely via VDP1. */ vdp2_scrn_display_set(VDP2_SCRN_DISP_NONE); - logDebug("[Saturn] displaySaturnInit: calling renderSaturnInit\n"); errorChain(renderSaturnInit()); - - logDebug("[Saturn] displaySaturnInit: done\n"); errorOk(); } @@ -45,7 +41,6 @@ errorret_t displaySaturnFlush(ropbuffer_t *buf) { } errorret_t displaySaturnSwap(void) { - logDebug("[Saturn] displaySaturnSwap\n"); vdp1_sync_render(); vdp1_sync(); vdp2_sync(); @@ -55,6 +50,5 @@ errorret_t displaySaturnSwap(void) { } void displaySaturnDispose(void) { - logDebug("[Saturn] displaySaturnDispose\n"); renderSaturnDispose(); } diff --git a/src/dusksaturn/display/render/rendersaturn.c b/src/dusksaturn/display/render/rendersaturn.c index 89d91006..1b0a5c02 100644 --- a/src/dusksaturn/display/render/rendersaturn.c +++ b/src/dusksaturn/display/render/rendersaturn.c @@ -12,6 +12,7 @@ #include "util/memory.h" #include "log/log.h" #include +#include #include #include @@ -19,15 +20,19 @@ * VDP1 renders sprites and polygons from a command table stored in VRAM. * VDP2 handles the scroll plane background (used for tilemap chunks). * - * VRAM layout (VDP1, 4MB total): - * 0x000000 – 0x00FFFF : VDP1 command table (64KB = 2048 entries max) - * 0x010000 – 0x1FFFFF : texture pool (1984KB) - * Framebuffers are managed automatically by the VDP1 hardware in the - * upper half of VRAM when double-buffering is enabled. + * VRAM layout (VDP1, 512KB total): + * 0x000000 – 0x007FFF : VDP1 command table (32KB = 1024 entries max) + * 0x010000 – 0x07FFFF : texture pool + * Framebuffers are managed automatically by the VDP1 hardware. * * VDP2 CRAM (4KB) holds palettes: * Each 256-color palette occupies 512 bytes (256 × 2-byte RGB1555 entries). * We reserve one palette slot per texture handle (up to SATURN_PALETTE_MAX). + * + * Every frame, renderSaturnFlush() builds a command table in saturnCmdBuf, + * then submits it to Yaul via vdp1_sync_cmdt_put(). Yaul DMA-transfers the + * table to VDP1 VRAM and, after vdp1_sync_render() (called from + * displaySaturnSwap), triggers VDP1 to draw. */ /* ---- Limits -------------------------------------------------------------- */ @@ -35,7 +40,14 @@ #define SATURN_RTEXTURE_MAX 128 #define SATURN_CMDT_MAX 1024 #define SATURN_TEXTURE_VRAM_BASE 0x010000u /* byte offset in VDP1 VRAM */ -#define SATURN_TEXTURE_VRAM_SIZE (0x200000u - SATURN_TEXTURE_VRAM_BASE) +#define SATURN_TEXTURE_VRAM_SIZE (0x080000u - SATURN_TEXTURE_VRAM_BASE) + +/* Preamble slots at the front of every command table: + * [0] SYSCLIP – system clip coordinate (full screen) + * [1] USERCLIP – user clip coordinate (full screen, disabled by default) + * [2] LOCALCRD – local coordinate (origin at top-left) + * All drawing commands follow from index 3 onward. */ +#define SATURN_CMDT_PREAMBLE 3 /* ---- VDP1 command table entry (hardware layout, 32 bytes) ---------------- */ @@ -55,20 +67,24 @@ typedef struct __attribute__((packed)) { } saturncmd_t; _Static_assert(sizeof(saturncmd_t) == 32, "saturncmd_t must be 32 bytes"); +_Static_assert(sizeof(saturncmd_t) == sizeof(vdp1_cmdt_t), + "saturncmd_t/vdp1_cmdt_t size mismatch"); -/* CTRL command type bits (bits 2:0) */ -#define SATURNCMD_CTRL_NORMAL_SPRITE (0x0000u) /* aligned rect */ +/* CTRL command type bits */ +#define SATURNCMD_CTRL_NORMAL_SPRITE (0x0000u) #define SATURNCMD_CTRL_SCALED_SPRITE (0x0001u) -#define SATURNCMD_CTRL_DISTORTED_SPRITE (0x0002u) /* arbitrary quad */ -#define SATURNCMD_CTRL_POLYGON (0x0004u) /* solid polygon */ -#define SATURNCMD_CTRL_SYSCLIP (0x0009u) /* system clipping */ -#define SATURNCMD_CTRL_END (0x8000u) /* end of list */ +#define SATURNCMD_CTRL_DISTORTED_SPRITE (0x0002u) +#define SATURNCMD_CTRL_POLYGON (0x0004u) +#define SATURNCMD_CTRL_USERCLIP (0x0008u) +#define SATURNCMD_CTRL_SYSCLIP (0x0009u) +#define SATURNCMD_CTRL_LOCALCRD (0x000Au) +#define SATURNCMD_CTRL_END (0x8000u) /* PMOD draw mode */ -#define SATURNCMD_PMOD_TRANS (0x0000u) /* transparent pixel 0 */ -#define SATURNCMD_PMOD_8BPP_CBANK (0x0038u) /* 256-color, color bank */ -#define SATURNCMD_PMOD_ECD (0x0080u) /* extend color depth */ +#define SATURNCMD_PMOD_8BPP_CBANK (4u << 3) /* 256-color, color bank */ +#define SATURNCMD_PMOD_RGB_DIRECT (5u << 3) /* RGB1555 in COLR, no palette */ #define SATURNCMD_PMOD_SPD (0x0040u) /* do not skip index 0 */ +#define SATURNCMD_PMOD_ECD (0x0080u) /* end-code disable */ /* ---- Texture table ------------------------------------------------------- */ @@ -102,7 +118,7 @@ static float saturnViewTgtX = 0.0f, saturnViewTgtY = 0.0f, saturnViewTgtZ = 0.0f /* ---- Helpers ------------------------------------------------------------- */ -/* Convert color_t RGBA → VDP2 CRAM RGB1555 (1 MSB unused, RGB555). */ +/* Convert color_t RGBA → VDP1/VDP2 RGB1555. */ static uint16_t toRGB1555(color_t c) { return (uint16_t)( ((uint16_t)(c.b >> 3) << 10) | @@ -112,8 +128,7 @@ static uint16_t toRGB1555(color_t c) { } /* Write palette into VDP2 CRAM at the texture's slot. - * CRAM is mapped at 0x25F00000 (Saturn memory map). - * Each palette slot is 512 bytes = 256 × uint16_t. */ + * CRAM is mapped at 0x25F00000. Each 256-entry slot = 512 bytes. */ static void uploadPalette(saturntexentry_t *e) { volatile uint16_t *cram = (volatile uint16_t *)0x25F00000; uint32_t base = (uint32_t)e->cramWordOffset * 256u; @@ -122,9 +137,8 @@ static void uploadPalette(saturntexentry_t *e) { } } -/* Copy indices row-by-row into VDP1 VRAM. - * VDP1 VRAM is at 0x05C00000. Textures must be stored starting on an - * 8-byte boundary; we keep our pool 8-byte aligned already. */ +/* Copy indices into VDP1 VRAM texture pool. + * VDP1 VRAM is at 0x05C00000; textures start at SATURN_TEXTURE_VRAM_BASE. */ static void uploadIndices(saturntexentry_t *e) { volatile uint8_t *vram = (volatile uint8_t *)(0x05C00000u + SATURN_TEXTURE_VRAM_BASE + e->vramByteOffset); @@ -134,7 +148,7 @@ static void uploadIndices(saturntexentry_t *e) { } } -/* Return a fresh command slot or NULL if full. */ +/* Reserve a command slot (saturnCmdCount starts at SATURN_CMDT_PREAMBLE). */ static saturncmd_t *allocCmd(void) { if(saturnCmdCount >= SATURN_CMDT_MAX) return NULL; saturncmd_t *c = &saturnCmdBuf[saturnCmdCount++]; @@ -145,13 +159,18 @@ static saturncmd_t *allocCmd(void) { /* ---- Init ---------------------------------------------------------------- */ errorret_t renderSaturnInit(void) { - logDebug("[Saturn] renderSaturnInit\n"); memoryZero(saturnTexTable, sizeof(saturnTexTable)); saturnTexNext = 1; saturnTexVramUsed = 0; saturnTexCramUsed = 0; saturnCmdCount = 0; + /* Initialise VDP1 hardware: sets TVMR (bpp/rotation), EWDR (erase colour), + * EWLR/EWRR (erase area = full screen), and updates VDP2 SPCTL sprite type. + * Must be called after vdp2_tvmd_display_res_set() so the erase area is + * computed from the correct TV resolution. */ + vdp1_env_default_set(); + /* White 1×1 fallback: slot 0 */ saturntexentry_t *e = &saturnTexTable[0]; e->cpuIndices = (uint8_t *)malloc(1); @@ -223,25 +242,24 @@ uint8_t *renderSaturnTextureGetIndices(rtexture_t tex) { /* ---- Flush --------------------------------------------------------------- */ -/* Fill in the SRCA/CMDSIZE/CMDCOLR fields from a texture handle. */ +/* Fill in the texture fields of a sprite/distorted-sprite command. */ static void cmdSetTexture(saturncmd_t *cmd, rtexture_t tex) { saturntexentry_t *e = (tex < SATURN_RTEXTURE_MAX && saturnTexTable[tex].cpuIndices) ? &saturnTexTable[tex] : &saturnTexTable[0]; - /* SRCA = byte offset from VDP1 VRAM base / 8. */ + /* SRCA = byte offset from VDP1 VRAM start / 8. */ uint32_t srcByteAddr = SATURN_TEXTURE_VRAM_BASE + e->vramByteOffset; cmd->srca = (uint16_t)(srcByteAddr / 8u); - /* SIZE = ((width/8) << 8) | height (each axis limited to 0-255 after /8). */ + /* SIZE = ((width/8) << 8) | height. */ cmd->size = (uint16_t)(((e->w / 8u) << 8) | e->h); - /* COLR = CRAM word address of palette / 16 (256-color bank mode). - * Each 256-entry slot is 512 bytes = 256 words. Word offset / 16. */ + /* COLR for 256-color bank mode = CRAM word base / 16. */ uint32_t cramWordBase = (uint32_t)e->cramWordOffset * 256u; cmd->colr = (uint16_t)(cramWordBase / 16u); - cmd->pmod = SATURNCMD_PMOD_8BPP_CBANK; /* 256-color, index 0 transparent */ + cmd->pmod = SATURNCMD_PMOD_8BPP_CBANK; } static void flush2DSprite(const ropsprite_t *s) { @@ -256,32 +274,26 @@ static void flush2DSprite(const ropsprite_t *s) { cmd->link = 0; cmdSetTexture(cmd, s->texture); - /* VDP1 normal sprite: XA/YA = top-left, XB/YB = size (w-1, h-1). */ + /* Normal sprite: XA/YA = top-left, XB/YB = (w-1, h-1). */ cmd->xa = (int16_t)s->x; cmd->ya = (int16_t)s->y; cmd->xb = (int16_t)(s->w > 0 ? s->w - 1 : 0); cmd->yb = (int16_t)(s->h > 0 ? s->h - 1 : 0); - /* UV sub-region: the VDP1 always draws the full texture, so to support - * sprite atlases we would need a clipped intermediate. For now we treat - * the full texture as the sprite frame (atlas sub-rect is a TODO). */ (void)e; } /* * Project a 3D world-space point onto the VDP1 2D screen. - * Uses a simple perspective divide; view/projection state is kept CPU-side. */ static void project( float wx, float wy, float wz, float *sx, float *sy ) { - /* Translate relative to eye. */ float rx = wx - saturnViewEyeX; float ry = wy - saturnViewEyeY; float rz = wz - saturnViewEyeZ; - /* TODO: replace with a proper view-matrix multiply for non-axis-aligned cameras. */ float fwd_z = saturnViewTgtZ - saturnViewEyeZ; (void)fwd_z; @@ -310,7 +322,6 @@ static void flush3DQuad(const ropquad3d_t *q) { float rx = (float)q->rx, ry = (float)q->ry, rz = (float)q->rz; float ux = (float)q->ux, uy = (float)q->uy, uz = (float)q->uz; - /* Corners: TL = center - right + up, etc. */ float tlx = cx-rx+ux, tly = cy-ry+uy, tlz = cz-rz+uz; float trx = cx+rx+ux, try_ = cy+ry+uy, trz = cz+rz+uz; float blx = cx-rx-ux, bly = cy-ry-uy, blz = cz-rz-uz; @@ -328,7 +339,9 @@ static void flush3DQuad(const ropquad3d_t *q) { cmd->xd = (int16_t)sxd; cmd->yd = (int16_t)syd; } -/* Submit the finished command table to VDP1 VRAM and trigger rendering. */ +/* Submit command table to Yaul's VDP1 sync queue. + * vdp1_sync_cmdt_put() sets VDP1_FLAG_REQUEST_XFER_LIST which vdp1_sync_render() + * (called from displaySaturnSwap) requires before it will trigger VDP1 drawing. */ static void submitCmdTable(void) { /* Append end-of-list sentinel. */ if(saturnCmdCount < SATURN_CMDT_MAX) { @@ -337,31 +350,41 @@ static void submitCmdTable(void) { end->ctrl = SATURNCMD_CTRL_END; } - /* DMA or CPU-copy the command table to VDP1 VRAM at offset 0x000000. - * VDP1 VRAM starts at 0x05C00000 in the Saturn memory map. */ - volatile saturncmd_t *vdp1CmdTable = (volatile saturncmd_t *)0x05C00000u; - uint32_t count = saturnCmdCount + 1u; /* include the END entry */ - for(uint32_t i = 0; i < count; i++) { - vdp1CmdTable[i] = saturnCmdBuf[i]; - } - - /* Set VDP1 command table top address to 0x000000 (the default). */ - volatile uint16_t *vdp1Regs = (volatile uint16_t *)0x25D00000u; - /* PTMR: plot trigger — VDP1 draws on frame change */ - vdp1Regs[0] = 0x0000; - /* EWDR: erase write data (black) */ - vdp1Regs[2] = 0x0000; - /* EWLR: top-left (0,0) */ - vdp1Regs[3] = 0x0000; - /* EWRR: bottom-right */ - vdp1Regs[4] = (uint16_t)(((DUSK_DISPLAY_HEIGHT - 1) << 9) | - ((DUSK_DISPLAY_WIDTH / 2) - 1)); + /* Hand the completed list to Yaul. Yaul SCU-DMA's it to VDP1 VRAM and + * sets the transfer-complete flag that vdp1_sync_render() waits for. */ + vdp1_sync_cmdt_put( + (const vdp1_cmdt_t *)saturnCmdBuf, + (uint16_t)(saturnCmdCount + 1u), + 0 + ); } errorret_t renderSaturnFlush(ropbuffer_t *buf) { - logDebug("[Saturn] renderSaturnFlush: count=%u\n", (unsigned)buf->count); + /* Write fixed preamble into the first SATURN_CMDT_PREAMBLE slots. */ + { + saturncmd_t *sc; - saturnCmdCount = 0; + /* [0] System clip – defines the hardware clip boundary (full screen). */ + sc = &saturnCmdBuf[0]; + memoryZero(sc, sizeof(saturncmd_t)); + sc->ctrl = SATURNCMD_CTRL_SYSCLIP; + sc->xb = (int16_t)(DUSK_DISPLAY_WIDTH - 1); + sc->yb = (int16_t)(DUSK_DISPLAY_HEIGHT - 1); + + /* [1] User clip – mirrors full screen; disabled per-command by default. */ + sc = &saturnCmdBuf[1]; + memoryZero(sc, sizeof(saturncmd_t)); + sc->ctrl = SATURNCMD_CTRL_USERCLIP; + sc->xc = (int16_t)(DUSK_DISPLAY_WIDTH - 1); + sc->yc = (int16_t)(DUSK_DISPLAY_HEIGHT - 1); + + /* [2] Local coordinate – place origin at screen top-left. */ + sc = &saturnCmdBuf[2]; + memoryZero(sc, sizeof(saturncmd_t)); + sc->ctrl = SATURNCMD_CTRL_LOCALCRD; + /* xa = ya = 0 (origin at TL), already zero'd */ + } + saturnCmdCount = SATURN_CMDT_PREAMBLE; uint32_t offset = 0; while(offset < buf->byteCount) { @@ -371,19 +394,22 @@ errorret_t renderSaturnFlush(ropbuffer_t *buf) { switch(op) { case ROP_CLEAR: { const ropclear_t *c = (const ropclear_t *)hdr; - /* Set VDP2 back-screen color. */ - volatile uint16_t *cram = (volatile uint16_t *)0x25F00000u; - cram[0] = toRGB1555(c->color); - - /* Issue a VDP1 system clipping command to reset the clip window. */ - saturncmd_t *clip = allocCmd(); - if(clip) { - clip->ctrl = SATURNCMD_CTRL_SYSCLIP; - clip->link = 0; - clip->xa = 0; - clip->ya = 0; - clip->xb = (int16_t)(DUSK_DISPLAY_WIDTH - 1); - clip->yb = (int16_t)(DUSK_DISPLAY_HEIGHT - 1); + /* Fill the entire screen with a solid RGB polygon. + * RGB direct mode (PMOD = SATURNCMD_PMOD_RGB_DIRECT) places the + * RGB1555 color in COLR directly — no palette lookup required. */ + saturncmd_t *poly = allocCmd(); + if(poly) { + poly->ctrl = SATURNCMD_CTRL_POLYGON; + poly->pmod = SATURNCMD_PMOD_RGB_DIRECT; + poly->colr = toRGB1555(c->color); + poly->xa = 0; + poly->ya = 0; + poly->xb = (int16_t)(DUSK_DISPLAY_WIDTH - 1); + poly->yb = 0; + poly->xc = (int16_t)(DUSK_DISPLAY_WIDTH - 1); + poly->yc = (int16_t)(DUSK_DISPLAY_HEIGHT - 1); + poly->xd = 0; + poly->yd = (int16_t)(DUSK_DISPLAY_HEIGHT - 1); } break; } @@ -417,13 +443,10 @@ errorret_t renderSaturnFlush(ropbuffer_t *buf) { break; case ROP_DRAW_TILEMAP_CHUNK: - /* TODO: Saturn tilemap chunks drive VDP2 scroll plane registers. - * For now we fall through and emit nothing; a proper implementation - * writes tile indices to VDP2 VRAM and sets scroll offsets. */ + /* TODO: drive VDP2 scroll plane registers for tilemap support. */ break; default: - logDebug("[Saturn] unknown ROP op=%u\n", (unsigned)op); break; } diff --git a/src/dusksaturn/log/log.c b/src/dusksaturn/log/log.c index bbaf9455..709773be 100644 --- a/src/dusksaturn/log/log.c +++ b/src/dusksaturn/log/log.c @@ -6,27 +6,45 @@ */ #include "log/log.h" +#include +#include #include /* - * On Saturn, stdout goes to the debug serial port (via Yaul's dbgio module). - * With a comm link or emulator (Mednafen, SSF) this is visible on the host. - * - * TODO: add dbgio_dev_default_set(DBGIO_DEV_USB_CART, NULL) in - * systemSaturnInit() and replace vprintf with dbgio_printf() for - * hardware-accurate serial output. + * Yaul's bare-metal stdio (stdout/stderr) has uninitialised function pointers + * and will crash if called via vprintf/vfprintf. Use Yaul's dbgio subsystem + * instead: DBGIO_DEV_MEDNAFEN_DEBUG outputs to Mednafen's debug console; + * the same code works with USB cart or serial on hardware by changing the + * device constant. */ +static int _dbgioReady = 0; + +static void _ensureDbgio(void) { + if (!_dbgioReady) { + dbgio_dev_default_init(DBGIO_DEV_MEDNAFEN_DEBUG); + _dbgioReady = 1; + } +} + +static void _logWrite(const char_t *message, va_list args) { + char buf[256]; + vsnprintf(buf, sizeof(buf), message, args); + dbgio_puts(buf); +} + void logDebug(const char_t *message, ...) { + _ensureDbgio(); va_list args; va_start(args, message); - vprintf(message, args); + _logWrite(message, args); va_end(args); } void logError(const char_t *message, ...) { + _ensureDbgio(); va_list args; va_start(args, message); - vfprintf(stderr, message, args); + _logWrite(message, args); va_end(args); }