Game no longer crashes on Dolphin

This commit is contained in:
2026-03-09 08:05:26 -05:00
parent 23eaffa3a7
commit c5f5b025a6
39 changed files with 1227 additions and 160 deletions

View File

@@ -22,12 +22,13 @@ errorret_t textureInit(
) {
assertNotNull(texture, "Texture cannot be NULL");
assertTrue(width > 0 && height > 0, "width/height must be greater than 0");
assertTrue(width == mathNextPowTwo(width), "Width must be a power of 2.");
assertTrue(height == mathNextPowTwo(height), "Height must be a power of 2.");
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.");
errorChain(textureInitPlatform(texture, width, height, format, data));
errorOk();

View File

@@ -50,10 +50,6 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
}
errorret_t engineUpdate(void) {
#if DOLPHIN
ENGINE.running = SYS_MainLoop();
#endif
timeUpdate();
inputUpdate();

View File

@@ -131,5 +131,6 @@ errorret_t errorPrint(const errorret_t retval) {
retval.state->message,
retval.state->lines
);
debugFlush();
return retval;
}

View File

@@ -10,61 +10,6 @@
#include "assert/assert.h"
#include "util/string.h"
// inputbuttondata_t INPUT_BUTTON_DATA[] = {
// #if INPUT_SDL2 == 1
// #if INPUT_GAMEPAD == 1
// #if PSP
// { .name = "triangle", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_Y } },
// { .name = "cross", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_A } },
// { .name = "circle", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_B } },
// { .name = "square", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_X } },
// { .name = "start", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_START } },
// { .name = "select", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_BACK } },
// { .name = "up", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_DPAD_UP } },
// { .name = "down", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_DPAD_DOWN } },
// { .name = "left", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_DPAD_LEFT } },
// { .name = "right", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_DPAD_RIGHT } },
// { .name = "l", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_LEFTSHOULDER } },
// { .name = "r", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER } },
// { .name = "lstick_down", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = SDL_CONTROLLER_AXIS_LEFTX, .positive = true } } },
// { .name = "lstick_up", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = SDL_CONTROLLER_AXIS_LEFTX, .positive = false } } },
// { .name = "lstick_right", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = SDL_CONTROLLER_AXIS_LEFTY, .positive = true } } },
// { .name = "lstick_left", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = SDL_CONTROLLER_AXIS_LEFTY, .positive = false } } },
// #endif
// #elif DOLPHIN
// #if INPUT_GAMEPAD == 1
// { .name = "a", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_BUTTON_A } },
// { .name = "b", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_BUTTON_B } },
// { .name = "x", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_BUTTON_X } },
// { .name = "y", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_BUTTON_Y } },
// { .name = "start", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_BUTTON_START } },
// { .name = "up", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_BUTTON_UP } },
// { .name = "down", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_BUTTON_DOWN } },
// { .name = "left", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_BUTTON_LEFT } },
// { .name = "right", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_BUTTON_RIGHT } },
// { .name = "l", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_TRIGGER_L } },
// { .name = "r", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_TRIGGER_R } },
// { .name = "z", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_TRIGGER_Z } },
// { .name = "menu", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_BUTTON_MENU } },
// { .name = "lstick_up", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = INPUT_GAMEPAD_AXIS_LEFT_X, .positive = true } } },
// { .name = "lstick_down", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = INPUT_GAMEPAD_AXIS_LEFT_X, .positive = false } } },
// { .name = "lstick_left", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = INPUT_GAMEPAD_AXIS_LEFT_Y, .positive = true } } },
// { .name = "lstick_right", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = INPUT_GAMEPAD_AXIS_LEFT_Y, .positive = false } } },
// { .name = "rstick_up", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = INPUT_GAMEPAD_AXIS_C_X, .positive = true } } },
// { .name = "rstick_down", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = INPUT_GAMEPAD_AXIS_C_X, .positive = false } } },
// { .name = "rstick_left", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = INPUT_GAMEPAD_AXIS_C_Y, .positive = true } } },
// { .name = "rstick_right", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = INPUT_GAMEPAD_AXIS_C_Y, .positive = false } } },
// { .name = "ltrigger", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = INPUT_GAMEPAD_AXIS_TRIGGER_LEFT, .positive = true } } },
// { .name = "rtrigger", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = INPUT_GAMEPAD_AXIS_TRIGGER_RIGHT, .positive = true } } },
// #endif
// #endif
// { .name = NULL }
// };
inputbutton_t inputButtonGetByName(const char_t *name) {
assertNotNull(name, "name must not be NULL");
@@ -77,4 +22,8 @@ inputbutton_t inputButtonGetByName(const char_t *name) {
}
return (inputbutton_t){ .type = INPUT_BUTTON_TYPE_NONE };
}
float_t inputButtonGetValue(const inputbutton_t button) {
return inputButtonGetValuePlatform(button);
}

View File

@@ -7,13 +7,14 @@
#pragma once
#include "dusk.h"
#include "time/timeplatform.h"
#ifndef DUSK_TIME_STEP
#define DUSK_TIME_STEP (16.0f / 1000.0f)
#endif
#ifdef DUSK_TIME_DYNAMIC
#include "time/timeplatform.h"
#ifndef timeTickPlatform
#error "DUSK_TIME_DYNAMIC needs tick method defined"
#endif

View File

@@ -1,5 +1,5 @@
# Copyright (c) 2026 Dominic Masters
#
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
@@ -9,5 +9,13 @@ target_include_directories(${DUSK_LIBRARY_TARGET_NAME}
${CMAKE_CURRENT_LIST_DIR}
)
# Sources
target_sources(${DUSK_BINARY_TARGET_NAME}
PUBLIC
)
# Subdirs
add_subdirectory(debug)
add_subdirectory(asset)
add_subdirectory(debug)
add_subdirectory(display)
add_subdirectory(input)

View File

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

View File

@@ -1,53 +1,73 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
// Init FAT driver.
if(!fatInitDefault()) errorThrow("Failed to initialize FAT filesystem.");
#include "asset/asset.h"
#include "util/string.h"
#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>
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;
errorret_t assetInitDolphin(void) {
// 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;
// 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;
if(stringCompareInsensitive(pent->d_name, ASSET_FILE_NAME) != 0) {
continue;
}
// Close dir.
closedir(pdir);
// Did we find the file here?
if(foundPath[0] != '\0') break;
} while(*(++dolphinSearchPath) != NULL);
if(foundPath[0] != '\0') {
// Copy out filename
snprintf(
foundPath,
FILENAME_MAX,
"%s/%s",
*dolphinSearchPath,
ASSET_FILE_NAME
);
break;
}
// Close dir.
closedir(pdir);
// Did we find the asset file?
if(foundPath[0] == '\0') {
errorThrow("Failed to find asset file on FAT filesystem.");
}
// Did we find the file here?
if(foundPath[0] != '\0') break;
} while(*(++dolphinSearchPath) != NULL);
ASSET.zip = zip_open(foundPath, ZIP_RDONLY, NULL);
if(ASSET.zip == NULL) {
errorThrow("Failed to open asset file on FAT filesystem.");
}
errorOk();
// 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();
}
errorret_t assetDisposeDolphin() {
// Nothing doing.
errorOk();
}

View File

@@ -6,14 +6,7 @@
*/
#pragma once
#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>
#include "error/error.h"
static const char_t *ASSET_DOLPHIN_PATHS[] = {
"/",
@@ -34,4 +27,22 @@ static const char_t *ASSET_DOLPHIN_PATHS[] = {
"./apps/dusk",
"./apps/DUSK",
NULL
};
};
typedef struct {
uint8_t nothing;
} assetdolphin_t;
/**
* Initializes the Dolphin asset system.
*
* @return An error code indicating success or failure.
*/
errorret_t assetInitDolphin(void);
/**
* Disposes of the Dolphin asset system, freeing any allocated resources.
*
* @return An error code indicating success or failure.
*/
errorret_t assetDisposeDolphin(void);

View File

@@ -0,0 +1,14 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "assetdolphin.h"
#define assetInitPlatform assetInitDolphin
#define assetDisposePlatform assetDisposeDolphin
typedef assetdolphin_t assetplatform_t;

View File

@@ -5,8 +5,9 @@
* https://opensource.org/licenses/MIT
*/
#include "dolphin.h"
#include "debug/debug.h"
#include "display/display.h"
#include <debug.h>
static char_t DEBUG_ERROR_BUFFER[16*1024] = {0};
@@ -14,6 +15,10 @@ void debugPrint(const char_t *message, ...) {
// append to error buffer
size_t start = strlen(DEBUG_ERROR_BUFFER);
va_list args;
va_start(args, message);
fprintf(stdout, message, args);
va_end(args);
va_start(args, message);
vsnprintf(
DEBUG_ERROR_BUFFER + start,
@@ -25,6 +30,8 @@ void debugPrint(const char_t *message, ...) {
}
void debugFlush() {
fflush(stdout);
// Either create graphics, or hijack the displays' graphics.
void *xfb = NULL;
GXRModeObj *rmode = NULL;

View File

@@ -0,0 +1,17 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
displaydolphin.c
# debug.c
)
# Subdirs
add_subdirectory(camera)
add_subdirectory(framebuffer)
add_subdirectory(mesh)
add_subdirectory(texture)

View File

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

View File

@@ -0,0 +1,107 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/camera/camera.h"
#include "display/framebuffer/framebuffer.h"
#include "assert/assert.h"
void cameraPushMatrixDolphin(camera_t *camera) {
assertNotNull(camera, "Camera cannot be null");
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);
}
void cameraPopMatrixDolphin(void) {
}

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct camera_s camera_t;
/**
* Pushes the camera's transformation matrix onto the graphics stack.
*
* @param camera The camera to push the matrix of.
*/
void cameraPushMatrixDolphin(camera_t *camera);
/**
* Pops the camera's transformation matrix from the graphics stack.
*/
void cameraPopMatrixDolphin(void);

View File

@@ -0,0 +1,12 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "cameradolphin.h"
#define cameraPushMatrixPlatform cameraPushMatrixDolphin
#define cameraPopMatrixPlatform cameraPopMatrixDolphin

View File

@@ -0,0 +1,102 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/display.h"
#include "util/memory.h"
#include "engine/engine.h"
errorret_t 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_DOLPHIN_FIFO_SIZE);
memoryZero(DISPLAY.fifoBuffer, DISPLAY_DOLPHIN_FIFO_SIZE);
GX_Init(DISPLAY.fifoBuffer, DISPLAY_DOLPHIN_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);
errorOk();
}
errorret_t displayUpdateDolphin(void) {
ENGINE.running = SYS_MainLoop();
}
errorret_t displaySwapDolphin(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();
errorOk();
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#define DISPLAY_DOLPHIN_FIFO_SIZE (256*1024)
typedef struct {
void *frameBuffer[2];// Double-Bufferred
int whichFrameBuffer;
GXRModeObj *screenMode;
void *fifoBuffer;
} displaydolphin_t;
/**
* Initializes the display system on Dolphin.
*/
errorret_t displayInitDolphin(void);
/**
* Tells the display system to actually draw the frame on Dolphin.
*/
errorret_t displayUpdateDolphin(void);
/**
* Swaps the display buffers on Dolphin.
*/
errorret_t displaySwapDolphin(void);

View File

@@ -0,0 +1,15 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "displaydolphin.h"
#define displayPlatformInit displayInitDolphin
#define displayPlatformUpdate displayUpdateDolphin
#define displayPlatformSwap displaySwapDolphin
typedef displaydolphin_t displayplatform_t;

View File

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

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/framebuffer/framebuffer.h"
#include "display/display.h"
#include "assert/assert.h"
errorret_t frameBufferInitBackBufferDolphin(void) {
errorOk();
}
uint32_t frameBufferGetWidthDolphin(const framebufferdolphin_t *framebuffer) {
assertNotNull(framebuffer, "Cannot get width of NULL framebuffer.");
return DISPLAY.screenMode->fbWidth;
}
uint32_t frameBufferGetHeightDolphin(const framebufferdolphin_t *framebuffer) {
assertNotNull(framebuffer, "Cannot get height of NULL framebuffer.");
return DISPLAY.screenMode->efbHeight;
}
errorret_t frameBufferBindDolphin(framebufferdolphin_t *framebuffer) {
assertNotNull(framebuffer, "Cannot bind NULL framebuffer.");
assertTrue(
framebuffer == &FRAMEBUFFER_BACKBUFFER,
"Cannot bind framebuffer that is not the back buffer."
);
GX_InvVtxCache();
GX_InvalidateTexAll();
GX_SetZMode(GX_FALSE, GX_ALWAYS, GX_FALSE);
GX_SetViewport(
0, 0,
frameBufferGetWidth(framebuffer),
frameBufferGetHeight(framebuffer),
0, 1
);
errorOk();
}
void frameBufferClearDolphin(const uint8_t flags, const color_t color) {
GX_SetCopyClear(
(GXColor){ color.r, color.g, color.b, color.a },
GX_MAX_Z24
);
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/color.h"
#include "error/error.h"
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
#error "Dolphin does not support dynamic display sizes."
#endif
typedef struct {
uint8_t nothing;
} framebufferdolphin_t;
/**
* Initializes the backbuffer framebuffer. (Dolphin implementation).
*/
errorret_t frameBufferInitBackBufferDolphin(void);
/**
* Gets the height of the framebuffer. (Dolphin implementation).
*
* @param framebuffer The framebuffer to get the height of.
* @return The height of the framebuffer, or 0 if the framebuffer is NULL.
*/
uint32_t frameBufferGetWidthDolphin(const framebufferdolphin_t *framebuffer);
/**
* Gets the width of the framebuffer. (Dolphin implementation).
*
* @param framebuffer The framebuffer to get the width of.
* @return The width of the framebuffer, or 0 if the framebuffer is NULL.
*/
uint32_t frameBufferGetHeightDolphin(const framebufferdolphin_t *framebuffer);
/**
* Binds the framebuffer for rendering. (Dolphin implementation).
*
* @param framebuffer The framebuffer to bind.
* @return Either error or not.
*/
errorret_t frameBufferBindDolphin(framebufferdolphin_t *framebuffer);
/**
* Clears the framebuffer with the specified flags and color.
*
* @param flags The clear flags.
* @param color The clear color.
*/
void frameBufferClearDolphin(const uint8_t flags, const color_t color);

View File

@@ -0,0 +1,17 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "framebufferdolphin.h"
typedef framebufferdolphin_t framebufferplatform_t;
#define frameBufferPlatformInitBackBuffer frameBufferInitBackBufferDolphin
#define frameBufferPlatformGetWidth frameBufferGetWidthDolphin
#define frameBufferPlatformGetHeight frameBufferGetHeightDolphin
#define frameBufferPlatformBind frameBufferBindDolphin
#define frameBufferPlatformClear frameBufferClearDolphin

View File

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

View File

@@ -0,0 +1,73 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/mesh/mesh.h"
#include "display/texture/texture.h"
#include "assert/assert.h"
errorret_t meshInitDolphin(
meshdolphin_t *mesh,
const meshprimitivetypedolphin_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.");
mesh->primitiveType = primitiveType;
mesh->vertexCount = vertexCount;
mesh->vertices = vertices;
errorOk();
}
errorret_t meshDrawDolphin(
const meshdolphin_t *mesh,
const int32_t vertexOffset,
const int32_t vertexCount
) {
assertNotNull(mesh, "Mesh cannot be NULL.");
assertTrue(vertexOffset >= 0, "Vertex offset must be >= 0");
assertTrue(vertexCount > 0, "Vertex count must be > 0");
assertTrue(
vertexOffset + vertexCount <= mesh->vertexCount,
"Requested vertex range is invalid"
);
// Prepare Vertex descriptor
DCFlushRange(
(void*)&mesh->vertices[vertexOffset],
sizeof(meshvertex_t) * vertexCount
);
const u8 stride = (u8)sizeof(meshvertex_t);
GX_SetArray(GX_VA_POS, (void*)&mesh->vertices[vertexOffset].pos[0], stride);
GX_SetArray(GX_VA_CLR0, (void*)&mesh->vertices[vertexOffset].color, stride);
GX_SetArray(GX_VA_TEX0, (void*)&mesh->vertices[vertexOffset].uv[0], stride);
textureDolphinUploadTEV();
GX_Begin(mesh->primitiveType, GX_VTXFMT0, (uint16_t)vertexCount);
for(u16 i = 0; i < (u16)vertexCount; ++i) {
GX_Position1x16(i);
GX_Color1x16(i);
GX_TexCoord1x16(i);
}
GX_End();
errorOk();
}
int32_t meshGetVertexCountDolphin(const meshdolphin_t *mesh) {
assertNotNull(mesh, "Mesh cannot be NULL.");
return mesh->vertexCount;
}
errorret_t meshDisposeDolphin(meshdolphin_t *mesh) {
errorOk();
}

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/mesh/meshvertex.h"
#include "error/error.h"
typedef enum {
MESH_PRIMITIVE_TYPE_TRIANGLES = GX_TRIANGLES,
MESH_PRIMITIVE_TYPE_LINES = GX_LINES,
MESH_PRIMITIVE_TYPE_POINTS = GX_POINTS,
} meshprimitivetypedolphin_t;
typedef struct {
const meshvertex_t *vertices;
int32_t vertexCount;
meshprimitivetypedolphin_t primitiveType;
} meshdolphin_t;
/**
* Initializes a mesh.
*
* @param mesh The mesh to initialize.
* @param primitiveType The Dolphin primitive type (e.g., GX_TRIANGLES).
* @param vertexCount The number of vertices in the mesh.
* @param vertices The vertex data for the mesh.
* @return An error indicating success or failure.
*/
errorret_t meshInitDolphin(
meshdolphin_t *mesh,
const meshprimitivetypedolphin_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.
* @return An error indicating success or failure.
*/
errorret_t meshDrawDolphin(
const meshdolphin_t *mesh,
const int32_t vertexOffset,
const int32_t vertexCount
);
/**
* Gets the vertex count of a mesh.
*
* @param mesh The mesh to get the vertex count from.
* @return The vertex count of the mesh.
*/
int32_t meshGetVertexCountDolphin(const meshdolphin_t *mesh);
/**
* Disposes of a mesh, freeing any associated resources.
*
* @param mesh The mesh to dispose of.
* @return An error indicating success or failure.
*/
errorret_t meshDisposeDolphin(meshdolphin_t *mesh);

View File

@@ -0,0 +1,16 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "meshdolphin.h"
#define meshInitPlatform meshInitDolphin
#define meshDrawPlatform meshDrawDolphin
#define meshGetVertexCountPlatform meshGetVertexCountDolphin
#define meshDisposePlatform meshDisposeDolphin
typedef meshprimitivetypedolphin_t meshprimitivetypeplatform_t;
typedef meshdolphin_t meshplatform_t;

View File

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

View File

@@ -0,0 +1,188 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/texture/texture.h"
#include "assert/assert.h"
errorret_t textureInitDolphin(
texturedolphin_t *texture,
const int32_t width,
const int32_t height,
const textureformatdolphin_t format,
const texturedata_t data
) {
// 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;
errorOk();
}
errorret_t textureBindDolphin(texturedolphin_t *texture) {
errorOk();
}
errorret_t textureDisposeDolphin(texturedolphin_t *texture) {
errorOk();
}
void textureDolphinUploadTEV(void) {
}

View File

@@ -0,0 +1,63 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef union texturedata_u texturedata_t;
typedef enum {
TEXTURE_FORMAT_RGBA = GX_TF_RGBA8,
TEXTURE_FORMAT_PALETTE = GX_TF_CI8,
} textureformatdolphin_t;
typedef struct {
GXTexObj texObj;
textureformatdolphin_t format;
int32_t width;
int32_t height;
} texturedolphin_t;
/**
* 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., GX_TF_RGBA8, GX_TF_CI8).
* @param data The data for the texture, the format changes per format.
* @return An error if the texture failed to initialize, otherwise success.
*/
errorret_t textureInitDolphin(
texturedolphin_t *texture,
const int32_t width,
const int32_t height,
const textureformatdolphin_t format,
const texturedata_t data
);
/**
* Binds a texture for rendering. Providing NULL will unbind any texture.
*
* @param texture The texture to bind.
* @return An error if the texture failed to bind, otherwise success.
*/
errorret_t textureBindDolphin(texturedolphin_t *texture);
/**
* Disposes a texture.
*
* @param texture The texture to dispose.
* @return An error if the texture failed to dispose, otherwise success.
*/
errorret_t textureDisposeDolphin(texturedolphin_t *texture);
/**
* Internal method that uploads the texture environment variables to the GPU
* for rendering. This is basically uploading the shader information.
*/
void textureDolphinUploadTEV(void);

View File

@@ -0,0 +1,16 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "texturedolphin.h"
typedef textureformatdolphin_t textureformatplatform_t;
typedef texturedolphin_t textureplatform_t;
#define textureInitPlatform textureInitDolphin
#define textureBindPlatform textureBindDolphin
#define textureDisposePlatform textureDisposeDolphin

View File

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

View File

@@ -0,0 +1,96 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "input/input.h"
#include "assert/assert.h"
inputbuttondata_t INPUT_BUTTON_DATA[] = {
#ifdef DUSK_INPUT_GAMEPAD
{ .name = "a", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_BUTTON_A } },
{ .name = "b", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_BUTTON_B } },
{ .name = "x", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_BUTTON_X } },
{ .name = "y", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_BUTTON_Y } },
{ .name = "start", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_BUTTON_START } },
{ .name = "up", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_BUTTON_UP } },
{ .name = "down", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_BUTTON_DOWN } },
{ .name = "left", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_BUTTON_LEFT } },
{ .name = "right", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_BUTTON_RIGHT } },
{ .name = "l", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_TRIGGER_L } },
{ .name = "r", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_TRIGGER_R } },
{ .name = "z", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_TRIGGER_Z } },
{ .name = "menu", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = PAD_BUTTON_MENU } },
{ .name = "lstick_up", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = INPUT_GAMEPAD_AXIS_LEFT_X, .positive = true } } },
{ .name = "lstick_down", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = INPUT_GAMEPAD_AXIS_LEFT_X, .positive = false } } },
{ .name = "lstick_left", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = INPUT_GAMEPAD_AXIS_LEFT_Y, .positive = true } } },
{ .name = "lstick_right", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = INPUT_GAMEPAD_AXIS_LEFT_Y, .positive = false } } },
{ .name = "rstick_up", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = INPUT_GAMEPAD_AXIS_C_X, .positive = true } } },
{ .name = "rstick_down", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = INPUT_GAMEPAD_AXIS_C_X, .positive = false } } },
{ .name = "rstick_left", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = INPUT_GAMEPAD_AXIS_C_Y, .positive = true } } },
{ .name = "rstick_right", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = INPUT_GAMEPAD_AXIS_C_Y, .positive = false } } },
{ .name = "ltrigger", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = INPUT_GAMEPAD_AXIS_TRIGGER_LEFT, .positive = true } } },
{ .name = "rtrigger", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = INPUT_GAMEPAD_AXIS_TRIGGER_RIGHT, .positive = true } } },
#endif
{ .name = NULL }
};
void inputUpdateDolphin(void) {
PAD_ScanPads();
#ifdef DUSK_INPUT_GAMEPAD
for(uint8_t i = 0; i < INPUT_PAD_COUNT; i++) {
INPUT.platform.padState[i] = PAD_ButtonsDown(i);
INPUT.platform.pads[i][INPUT_GAMEPAD_AXIS_LEFT_X] = (
INPUT_DOLPHIN_AXIS(PAD_StickX(i))
);
INPUT.platform.pads[i][INPUT_GAMEPAD_AXIS_LEFT_Y] = (
INPUT_DOLPHIN_AXIS(PAD_StickY(i))
);
INPUT.platform.pads[i][INPUT_GAMEPAD_AXIS_C_X] = (
INPUT_DOLPHIN_AXIS(PAD_SubStickX(i))
);
INPUT.platform.pads[i][INPUT_GAMEPAD_AXIS_C_Y] = (
INPUT_DOLPHIN_AXIS(PAD_SubStickY(i))
);
INPUT.platform.pads[i][INPUT_GAMEPAD_AXIS_TRIGGER_LEFT] = (
INPUT_DOLPHIN_AXIS(PAD_TriggerL(i))
);
INPUT.platform.pads[i][INPUT_GAMEPAD_AXIS_TRIGGER_RIGHT] = (
INPUT_DOLPHIN_AXIS(PAD_TriggerR(i))
);
}
#endif
}
float_t inputButtonGetValueDolphin(const inputbutton_t button) {
switch(button.type) {
#ifdef DUSK_INPUT_GAMEPAD
case INPUT_BUTTON_TYPE_GAMEPAD: {
if(INPUT.padState[0] & button.gpButton) return 1.0f;
return 0.0f;
}
case INPUT_BUTTON_TYPE_GAMEPAD_AXIS: {
float_t value = INPUT.pads[0][button.gpAxis.axis];
if(!button.gpAxis.positive) value = -value;
if(value >= INPUT.deadzone) return value;
return 0.0f;
}
#endif
default: {
assertUnreachable("Unknown input button type");
return 0.0f;
}
}
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#define INPUT_DOLPHIN_PAD_COUNT PAD_CHANMAX
#define INPUT_DOLPHIN_AXIS(value) ((float_t)(value) / 128.0f)
typedef struct inputbutton_s inputbutton_t;
#ifdef DUSK_INPUT_POINTER
#error "Wii not implemented"
#endif
#ifdef DUSK_INPUT_KEYBOARD
#error "Keyboard not implemented"
#endif
#ifdef DUSK_INPUT_GAMEPAD
// TODO: Can I support wiimote, gamecube, pro controller, etc?
typedef u16 inputgamepadbuttondolphin_t;
typedef enum {
INPUT_GAMEPAD_AXIS_LEFT_X,
INPUT_GAMEPAD_AXIS_LEFT_Y,
INPUT_GAMEPAD_AXIS_C_X,
INPUT_GAMEPAD_AXIS_C_Y,
INPUT_GAMEPAD_AXIS_TRIGGER_LEFT,
INPUT_GAMEPAD_AXIS_TRIGGER_RIGHT,
INPUT_GAMEPAD_AXIS_COUNT
} inputgamepadaxisdolphin_t;
#endif
typedef struct {
#ifdef DUSK_INPUT_GAMEPAD
int padState[INPUT_DOLPHIN_PAD_COUNT];
float_t pads[INPUT_DOLPHIN_PAD_COUNT][INPUT_GAMEPAD_AXIS_COUNT];
#endif
} inputdolphin_t;
/**
* Updates the input state for Dolphin.
*/
void inputUpdateDolphin(void);
/**
* Returns the input value (between 0 and 1) of the given button.
*
* @param button The button to get the value of.
* @return The value of the button, between 0 and 1.
*/
float_t inputButtonGetValueDolphin(const inputbutton_t button);

View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "inputdolphin.h"
#ifdef DUSK_INPUT_GAMEPAD
typedef inputgamepadbuttondolphin_t inputgamepadbuttonplatform_t;
typedef inputgamepadaxisdolphin_t inputgamepadaxissplatform_t;
#endif
typedef inputdolphin_t inputplatform_t;
#define inputUpdatePlatform inputUpdateDolphin
#define inputButtonGetValuePlatform inputButtonGetValueDolphin

View File

@@ -12,7 +12,6 @@ target_include_directories(${DUSK_LIBRARY_TARGET_NAME}
# Sources
target_sources(${DUSK_BINARY_TARGET_NAME}
PUBLIC
# hello.c
)
# Subdirs

View File

@@ -1,41 +0,0 @@
#include <pspuser.h>
#include <pspdebug.h>
#include <pspdisplay.h>
// PSP_MODULE_INFO is required
PSP_MODULE_INFO("Hello World", 0, 1, 0);
PSP_MAIN_THREAD_ATTR(PSP_THREAD_ATTR_USER);
int exit_callback(int arg1, int arg2, void *common) {
sceKernelExitGame();
return 0;
}
int callback_thread(SceSize args, void *argp) {
int cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL);
sceKernelRegisterExitCallback(cbid);
sceKernelSleepThreadCB();
return 0;
}
int setup_callbacks(void) {
int thid = sceKernelCreateThread("update_thread", callback_thread, 0x11, 0xFA0, 0, 0);
if(thid >= 0)
sceKernelStartThread(thid, 0, 0);
return thid;
}
int main(void) {
// Use above functions to make exiting possible
setup_callbacks();
// Print Hello World! on a debug screen on a loop
pspDebugScreenInit();
while(1) {
pspDebugScreenSetXY(0, 0);
pspDebugScreenPrintf("Hello World!");
sceDisplayWaitVblankStart();
}
return 0;
}

View File

@@ -38,7 +38,7 @@ void inputUpdateSDL2(void) {
#endif
}
float_t inputButtonGetValue(const inputbutton_t button) {
float_t inputButtonGetValueSDL2(const inputbutton_t button) {
switch(button.type) {
#ifdef DUSK_INPUT_KEYBOARD
case INPUT_BUTTON_TYPE_KEYBOARD: {