From 7e7f0e267ea6d3fb746bd250f3744a737502f770 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Sun, 9 Aug 2026 21:18:11 +0300 Subject: [PATCH 01/14] First buildable version, with threads, time and timer only --- CMakeLists.txt | 19 ++- cmake/sdlplatform.cmake | 2 + include/SDL3/SDL_platform_defines.h | 10 ++ include/build_config/SDL_build_config.h.cmake | 2 + src/dynapi/SDL_dynapi.h | 2 + src/thread/pthread/SDL_systhread.c | 4 +- src/time/ogc/SDL_systime.c | 133 ++++++++++++++++++ src/timer/ogc/SDL_systimer.c | 49 +++++++ 8 files changed, 218 insertions(+), 3 deletions(-) create mode 100644 src/time/ogc/SDL_systime.c create mode 100644 src/timer/ogc/SDL_systimer.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 1928e514edeed..021391f5aa8e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -207,7 +207,7 @@ if(EMSCRIPTEN) set(SDL_SHARED_AVAILABLE OFF) endif() -if(VITA OR PSP OR PS2 OR N3DS OR RISCOS OR NGAGE) +if(VITA OR PSP OR PS2 OR N3DS OR RISCOS OR NGAGE OR OGC) set(SDL_SHARED_AVAILABLE OFF) endif() @@ -304,7 +304,7 @@ dep_option(SDL_SSE3 "Use SSE3 assembly routines" ON "SDL_ASSEMBLY dep_option(SDL_SSE4_1 "Use SSE4.1 assembly routines" ON "SDL_ASSEMBLY;SDL_CPU_X86 OR SDL_CPU_X64" OFF) dep_option(SDL_SSE4_2 "Use SSE4.2 assembly routines" ON "SDL_ASSEMBLY;SDL_CPU_X86 OR SDL_CPU_X64" OFF) dep_option(SDL_MMX "Use MMX assembly routines" ON "SDL_ASSEMBLY;SDL_CPU_X86 OR SDL_CPU_X64" OFF) -dep_option(SDL_ALTIVEC "Use Altivec assembly routines" ON "SDL_ASSEMBLY;SDL_CPU_POWERPC32 OR SDL_CPU_POWERPC64" OFF) +dep_option(SDL_ALTIVEC "Use Altivec assembly routines" ON "SDL_ASSEMBLY;SDL_CPU_POWERPC32 OR SDL_CPU_POWERPC64;NOT OGC" OFF) dep_option(SDL_ARMNEON "Use NEON assembly routines" ON "SDL_ASSEMBLY;SDL_CPU_ARM32 OR SDL_CPU_ARM64" OFF) dep_option(SDL_LSX "Use LSX assembly routines" ON "SDL_ASSEMBLY;SDL_CPU_LOONGARCH64" OFF) dep_option(SDL_LASX "Use LASX assembly routines" ON "SDL_ASSEMBLY;SDL_CPU_LOONGARCH64" OFF) @@ -3336,6 +3336,21 @@ elseif(N3DS) "${SDL3_SOURCE_DIR}/src/io/n3ds/*.h" ) +elseif(OGC) + set(SDL_TIME_OGC 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/ogc/*.c") + set(HAVE_SDL_TIME TRUE) + + set(SDL_TIMER_OGC 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/ogc/*.c") + set(HAVE_SDL_TIMERS TRUE) + + set(SDL_PTHREADS ON) + set(HAVE_SDL_THREADS TRUE) + set(SDL_PTHREADS_SEM ON) + message(STATUS "pthread") + CheckPTHREAD() + elseif(NGAGE) enable_language(CXX) diff --git a/cmake/sdlplatform.cmake b/cmake/sdlplatform.cmake index f16fe3f2efad9..3ca0734062cf3 100644 --- a/cmake/sdlplatform.cmake +++ b/cmake/sdlplatform.cmake @@ -30,6 +30,8 @@ function(SDL_DetectCMakePlatform) set(sdl_cmake_platform RISCOS) elseif(VITA) set(sdl_cmake_platform Vita) + elseif(CMAKE_SYSTEM_NAME MATCHES "NintendoWii|NintendoGameCube") + set(sdl_cmake_platform OGC) elseif(CMAKE_SYSTEM_NAME MATCHES ".*Linux") set(sdl_cmake_platform Linux) elseif(CMAKE_SYSTEM_NAME MATCHES "kFreeBSD.*") diff --git a/include/SDL3/SDL_platform_defines.h b/include/SDL3/SDL_platform_defines.h index 79631491de2f3..82a88bdcc5d4a 100644 --- a/include/SDL3/SDL_platform_defines.h +++ b/include/SDL3/SDL_platform_defines.h @@ -473,6 +473,16 @@ #define SDL_PLATFORM_3DS 1 #endif +#if defined(__wii__) || defined(__gamecube__) + +/** + * A preprocessor macro that is only defined if compiling for Nintendo Wii or GameCube. + * + * \since This macro is available since SDL 3.4.0. + */ +#define SDL_PLATFORM_OGC 1 +#endif + #ifdef __NGAGE__ /** diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake index 17cb8f2574998..6c218a53bdf34 100644 --- a/include/build_config/SDL_build_config.h.cmake +++ b/include/build_config/SDL_build_config.h.cmake @@ -379,6 +379,7 @@ #cmakedefine SDL_TIME_PSP 1 #cmakedefine SDL_TIME_PS2 1 #cmakedefine SDL_TIME_N3DS 1 +#cmakedefine SDL_TIME_OGC 1 #cmakedefine SDL_TIME_NGAGE 1 #cmakedefine SDL_TIME_PRIVATE 1 @@ -391,6 +392,7 @@ #cmakedefine SDL_TIMER_PSP 1 #cmakedefine SDL_TIMER_PS2 1 #cmakedefine SDL_TIMER_N3DS 1 +#cmakedefine SDL_TIMER_OGC 1 #cmakedefine SDL_TIMER_PRIVATE 1 diff --git a/src/dynapi/SDL_dynapi.h b/src/dynapi/SDL_dynapi.h index 786d86ee473eb..566130cacdef0 100644 --- a/src/dynapi/SDL_dynapi.h +++ b/src/dynapi/SDL_dynapi.h @@ -63,6 +63,8 @@ #define SDL_DYNAMIC_API 0 // vitasdk doesn't support dynamic linking #elif defined(SDL_PLATFORM_3DS) #define SDL_DYNAMIC_API 0 // devkitARM doesn't support dynamic linking +#elif defined(SDL_PLATFORM_OGC) +#define SDL_DYNAMIC_API 0 // devkitPPC doesn't support dynamic linking #elif defined(SDL_PLATFORM_NGAGE) #define SDL_DYNAMIC_API 0 #elif defined(DYNAPI_NEEDS_DLOPEN) && !defined(HAVE_DLOPEN) diff --git a/src/thread/pthread/SDL_systhread.c b/src/thread/pthread/SDL_systhread.c index f4d4c4d872a5e..faf17c1a0be1d 100644 --- a/src/thread/pthread/SDL_systhread.c +++ b/src/thread/pthread/SDL_systhread.c @@ -162,6 +162,7 @@ void SDL_SYS_SetupThread(const char *name) #endif } +#ifndef SDL_PLATFORM_OGC #ifdef HAVE_SIGNAL_H // Mask asynchronous signals for this thread sigemptyset(&mask); @@ -170,6 +171,7 @@ void SDL_SYS_SetupThread(const char *name) } pthread_sigmask(SIG_BLOCK, &mask, NULL); #endif +#endif #ifdef PTHREAD_CANCEL_ASYNCHRONOUS // Allow ourselves to be asynchronously cancelled @@ -187,7 +189,7 @@ SDL_ThreadID SDL_GetCurrentThreadID(void) bool SDL_SYS_SetThreadPriority(SDL_ThreadPriority priority) { -#ifdef SDL_PLATFORM_RISCOS +#ifndef _POSIX_PRIORITY_SCHEDULING // FIXME: Setting thread priority does not seem to be supported return true; #else diff --git a/src/time/ogc/SDL_systime.c b/src/time/ogc/SDL_systime.c new file mode 100644 index 0000000000000..3d658977b3cab --- /dev/null +++ b/src/time/ogc/SDL_systime.c @@ -0,0 +1,133 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_TIME_OGC + +#include "../SDL_time_c.h" +#include +#include +#include + +/* + * The GameCube clock is essentially a simple digital watch and provides + * no timezone or DST functionality. + */ + +/* SYS_Time returns the number of ticks since 2000, but SDL uses the Epoch as a + * reference; so here is the number of nanoseconds from the Epoch to 2000. */ +#define DELTA_EPOCH_2000_OFFSET_NS SDL_SECONDS_TO_NS(946684800) + +/* Returns year/month/day triple in civil calendar + * Preconditions: z is number of days since 1970-01-01 and is in the range: + * [INT_MIN, INT_MAX-719468]. + * + * http://howardhinnant.github.io/date_algorithms.html#civil_from_days + */ +static void civil_from_days(int days, int *year, int *month, int *day) +{ + days += 719468; + const int era = (days >= 0 ? days : days - 146096) / 146097; + const unsigned doe = (unsigned)(days - era * 146097); // [0, 146096] + const unsigned yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // [0, 399] + const int y = (int)(yoe) + era * 400; + const unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365] + const unsigned mp = (5 * doy + 2) / 153; // [0, 11] + const unsigned d = doy - (153 * mp + 2) / 5 + 1; // [1, 31] + const unsigned m = mp < 10 ? mp + 3 : mp - 9; // [1, 12] + + *year = y + (m <= 2); + *month = (int)m; + *day = (int)d; +} + +void SDL_GetSystemTimeLocalePreferences(SDL_DateFormat *df, SDL_TimeFormat *tf) +{ + if (tf) { + *tf = SDL_TIME_FORMAT_24HR; + } + + if (df) { + *df = SDL_DATE_FORMAT_YYYYMMDD; + } + +#ifdef __wii__ + // The Standard formats for the languages supported by the Wii + static const SDL_DateFormat LANG_TO_DATE_FORMAT[] = { + SDL_DATE_FORMAT_YYYYMMDD, // JP + SDL_DATE_FORMAT_DDMMYYYY, // EN, assume non-american format + SDL_DATE_FORMAT_DDMMYYYY, // DE + SDL_DATE_FORMAT_DDMMYYYY, // FR + SDL_DATE_FORMAT_DDMMYYYY, // ES + SDL_DATE_FORMAT_DDMMYYYY, // IT + SDL_DATE_FORMAT_DDMMYYYY, // NL + SDL_DATE_FORMAT_YYYYMMDD, // ZH (CN) + SDL_DATE_FORMAT_YYYYMMDD, // ZH (TW) + SDL_DATE_FORMAT_YYYYMMDD, // KR + }; + + if (df) { + s32 lang_id = CONF_GetLanguage(); + if (lang_id >= 0 && lang_id <= CONF_LANG_KOREAN) { + *df = LANG_TO_DATE_FORMAT[lang_id]; + } + } +#endif +} + +bool SDL_GetCurrentTime(SDL_Time *ticks) +{ + CHECK_PARAM(!ticks) { + return SDL_InvalidParamError("ticks"); + } + + /* SYS_Time() returns the number of ticks since 2000 */ + const Uint64 ns = PPCTicksToNs(SYS_Time()); + + *ticks = ns + DELTA_EPOCH_2000_OFFSET_NS; + + return true; +} + +bool SDL_TimeToDateTime(SDL_Time ticks, SDL_DateTime *dt, bool localTime) +{ + CHECK_PARAM(!dt) { + return SDL_InvalidParamError("dt"); + } + + const int days = (int)(SDL_NS_TO_SECONDS(ticks) / SDL_SECONDS_PER_DAY); + civil_from_days(days, &dt->year, &dt->month, &dt->day); + + int rem = (int)(SDL_NS_TO_SECONDS(ticks) - (days * SDL_SECONDS_PER_DAY)); + dt->hour = rem / (60 * 60); + rem -= dt->hour * 60 * 60; + dt->minute = rem / 60; + rem -= dt->minute * 60; + dt->second = rem; + dt->nanosecond = ticks % SDL_NS_PER_SECOND; + dt->utc_offset = 0; // Unknown + + SDL_CivilToDays(dt->year, dt->month, dt->day, &dt->day_of_week, NULL); + + return true; +} + +#endif // SDL_TIME_OGC diff --git a/src/timer/ogc/SDL_systimer.c b/src/timer/ogc/SDL_systimer.c new file mode 100644 index 0000000000000..134567d74e840 --- /dev/null +++ b/src/timer/ogc/SDL_systimer.c @@ -0,0 +1,49 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2023 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#ifdef SDL_TIMER_OGC + +#include +#include + +Uint64 SDL_GetPerformanceCounter(void) +{ + return gettime(); +} + +Uint64 SDL_GetPerformanceFrequency(void) +{ + return secs_to_ticks(1); +} + +void SDL_SYS_DelayNS(Uint64 ns) +{ + struct timespec elapsed, tv; + elapsed.tv_sec = ns / SDL_NS_PER_SECOND; + elapsed.tv_nsec = ns % SDL_NS_PER_SECOND; + tv.tv_sec = elapsed.tv_sec; + tv.tv_nsec = elapsed.tv_nsec; + nanosleep(&tv, &elapsed); +} + +#endif /* SDL_TIMER_OGC */ From 59d49a1dba3b0e4c82b43dd915122cd438c59db0 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Sun, 9 Aug 2026 21:27:55 +0300 Subject: [PATCH 02/14] ogc: add filesystem implementation --- CMakeLists.txt | 4 + include/build_config/SDL_build_config.h.cmake | 1 + src/filesystem/ogc/SDL_sysfilesystem.c | 100 ++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 src/filesystem/ogc/SDL_sysfilesystem.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 021391f5aa8e6..d61532c89fd2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3337,6 +3337,10 @@ elseif(N3DS) ) elseif(OGC) + set(SDL_FILESYSTEM_OGC 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/ogc/*.c") + set(HAVE_SDL_FILESYSTEM TRUE) + set(SDL_TIME_OGC 1) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/ogc/*.c") set(HAVE_SDL_TIME TRUE) diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake index 6c218a53bdf34..06d80fb4befd6 100644 --- a/include/build_config/SDL_build_config.h.cmake +++ b/include/build_config/SDL_build_config.h.cmake @@ -520,6 +520,7 @@ #cmakedefine SDL_FILESYSTEM_PSP 1 #cmakedefine SDL_FILESYSTEM_PS2 1 #cmakedefine SDL_FILESYSTEM_N3DS 1 +#cmakedefine SDL_FILESYSTEM_OGC 1 #cmakedefine SDL_FILESYSTEM_PRIVATE 1 diff --git a/src/filesystem/ogc/SDL_sysfilesystem.c b/src/filesystem/ogc/SDL_sysfilesystem.c new file mode 100644 index 0000000000000..7382c4f886d3c --- /dev/null +++ b/src/filesystem/ogc/SDL_sysfilesystem.c @@ -0,0 +1,100 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_FILESYSTEM_OGC + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* System dependent filesystem routines */ + +#include "../SDL_sysfilesystem.h" + +#include +#include +#include + +static inline int create_dir(const char *dirname) +{ + int result = mkdir(dirname, 0666); + + if (result == -1 && errno != EEXIST) { + return SDL_SetError("Failed to create '%s' (%s)", dirname, strerror(errno)); + } + return 0; +} + +char *SDL_SYS_GetBasePath(void) +{ + char buffer[256]; + size_t len; + + if (!getcwd(buffer, sizeof(buffer) - 1)) { + return "/"; + } + + len = strlen(buffer); + if (len > 0 && buffer[len - 1] != '/') { + buffer[len] = '/'; + buffer[len + 1] = '\0'; + } + + return SDL_strdup(buffer); +} + +char *SDL_SYS_GetExeName(void) +{ + return NULL; // there isn't an "exe name" on this platform. +} + +char *SDL_SYS_GetPrefPath(const char *org, const char *app) +{ + char *pref_path = NULL; + if (!app) { + SDL_InvalidParamError("app"); + return NULL; + } + + SDL_asprintf(&pref_path, "/apps/%s/", app); + if (!pref_path) { + return NULL; + } + + if (create_dir(pref_path) < 0) { + SDL_free(pref_path); + return NULL; + } + + return pref_path; +} + +// TODO +char *SDL_SYS_GetUserFolder(SDL_Folder folder) +{ + SDL_Unsupported(); + return NULL; +} + +char *SDL_SYS_GetCurrentDirectory(void) +{ + return SDL_SYS_GetBasePath(); +} + +#endif /* SDL_FILESYSTEM_OGC */ From c276b799f6ba29051ca0eeb9b51acd75bbc4a41d Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Mon, 10 Aug 2026 22:40:41 +0300 Subject: [PATCH 03/14] Add fsops --- CMakeLists.txt | 4 ++++ src/filesystem/ogc/SDL_sysfilesystem.c | 5 ----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d61532c89fd2d..7781f1c88bff9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3349,6 +3349,10 @@ elseif(OGC) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/ogc/*.c") set(HAVE_SDL_TIMERS TRUE) + set(SDL_FSOPS_POSIX 1) + sdl_sources("${SDL3_SOURCE_DIR}/src/filesystem/posix/SDL_sysfsops.c") + set(HAVE_SDL_FSOPS TRUE) + set(SDL_PTHREADS ON) set(HAVE_SDL_THREADS TRUE) set(SDL_PTHREADS_SEM ON) diff --git a/src/filesystem/ogc/SDL_sysfilesystem.c b/src/filesystem/ogc/SDL_sysfilesystem.c index 7382c4f886d3c..862ed72b74312 100644 --- a/src/filesystem/ogc/SDL_sysfilesystem.c +++ b/src/filesystem/ogc/SDL_sysfilesystem.c @@ -92,9 +92,4 @@ char *SDL_SYS_GetUserFolder(SDL_Folder folder) return NULL; } -char *SDL_SYS_GetCurrentDirectory(void) -{ - return SDL_SYS_GetBasePath(); -} - #endif /* SDL_FILESYSTEM_OGC */ From 49a61a496db1f39ee3ab3eb5f465d70443903405 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Tue, 11 Aug 2026 20:59:07 +0300 Subject: [PATCH 04/14] ogc: add "main" module --- CMakeLists.txt | 6 +++ include/SDL3/SDL_main.h | 10 ++++ src/main/SDL_runapp.c | 1 + src/main/ogc/SDL_sysmain_runapp.c | 84 +++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+) create mode 100644 src/main/ogc/SDL_sysmain_runapp.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 7781f1c88bff9..8c678bed12f8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3337,6 +3337,12 @@ elseif(N3DS) ) elseif(OGC) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/ogc/*.c") + sdl_link_dependency(main LIBS fat) + if(NINTENDO_WII) + sdl_link_dependency(keyboard LIBS wiikeyboard) + endif() + set(SDL_FILESYSTEM_OGC 1) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/ogc/*.c") set(HAVE_SDL_FILESYSTEM TRUE) diff --git a/include/SDL3/SDL_main.h b/include/SDL3/SDL_main.h index 771572f29f1aa..526f9f08459e2 100644 --- a/include/SDL3/SDL_main.h +++ b/include/SDL3/SDL_main.h @@ -228,6 +228,16 @@ */ #define SDL_MAIN_AVAILABLE + #elif defined(SDL_PLATFORM_OGC) + /* + On OGC, SDL provides a main function that sets up the filesystem, + checks the IOS and registers callbacks for the power and reset + buttons. + + If you provide this yourself, you may define SDL_MAIN_HANDLED + */ + #define SDL_MAIN_AVAILABLE + #endif #endif /* SDL_MAIN_HANDLED */ diff --git a/src/main/SDL_runapp.c b/src/main/SDL_runapp.c index c0ccd8fa6a988..89a3707d0c0a7 100644 --- a/src/main/SDL_runapp.c +++ b/src/main/SDL_runapp.c @@ -27,6 +27,7 @@ !defined(SDL_PLATFORM_IOS) && \ !defined(SDL_PLATFORM_TVOS) && \ !defined(SDL_PLATFORM_EMSCRIPTEN) && \ + !defined(SDL_PLATFORM_OGC) && \ !defined(SDL_PLATFORM_PSP) && \ !defined(SDL_PLATFORM_PS2) && \ !defined(SDL_PLATFORM_3DS) diff --git a/src/main/ogc/SDL_sysmain_runapp.c b/src/main/ogc/SDL_sysmain_runapp.c new file mode 100644 index 0000000000000..e78e73dbbfffb --- /dev/null +++ b/src/main/ogc/SDL_sysmain_runapp.c @@ -0,0 +1,84 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#ifdef SDL_PLATFORM_OGC + +#include "../SDL_main_callbacks.h" + +#include "../../video/ogc/SDL_ogcevents_c.h" + +/* Standard includes */ +#include + +/* OGC includes */ +#include +#include +#include + +static void ShutdownCB() +{ + OGC_PowerOffRequested = true; +} + +static void ResetCB(u32, void *) +{ + OGC_ResetRequested = true; +} + +int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) +{ + int result; + +#ifdef __wii__ + u32 version; + s32 preferred; + + L2Enhance(); + version = IOS_GetVersion(); + preferred = IOS_GetPreferredVersion(); + + if (preferred > 0 && version != (u32)preferred) + IOS_ReloadIOS(preferred); + + // Wii Power/Reset buttons + SYS_SetPowerCallback(ShutdownCB); + SYS_SetResetCallback(ResetCB); + + MOUSE_Init(); +#endif + + fatInitDefault(); + + /* Call the user's main function. Make sure that argv contains at least one + * element. */ + if (!argv || argv[0] == NULL) { + static const char *dummy_argv[2] = { "app", NULL }; + argc = 1; + argv = (char**)dummy_argv; + } + + result = SDL_CallMainFunction(argc, argv, mainFunction); + return result; +} + +#endif /* SDL_PLATFORM_OGC */ From d42846c54e7c2bbccd678ff2e6c089cea79ee934 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Tue, 11 Aug 2026 22:44:55 +0300 Subject: [PATCH 05/14] ogc: add "video" submodule with OpenGL support --- CMakeLists.txt | 26 + include/build_config/SDL_build_config.h.cmake | 1 + src/video/SDL_sysvideo.h | 1 + src/video/SDL_video.c | 3 + src/video/ogc/SDL_ogccursors.h | 480 ++++++++++++++++++ src/video/ogc/SDL_ogcevents.c | 203 ++++++++ src/video/ogc/SDL_ogcevents_c.h | 66 +++ src/video/ogc/SDL_ogcframebuffer.c | 127 +++++ src/video/ogc/SDL_ogcframebuffer_c.h | 31 ++ src/video/ogc/SDL_ogcgl.c | 137 +++++ src/video/ogc/SDL_ogcgl.h | 42 ++ src/video/ogc/SDL_ogcgxcommon.c | 127 +++++ src/video/ogc/SDL_ogcgxcommon.h | 35 ++ src/video/ogc/SDL_ogckeyboard.c | 59 +++ src/video/ogc/SDL_ogckeyboard.h | 34 ++ src/video/ogc/SDL_ogcmouse.c | 420 +++++++++++++++ src/video/ogc/SDL_ogcmouse.h | 35 ++ src/video/ogc/SDL_ogcpixels.c | 353 +++++++++++++ src/video/ogc/SDL_ogcpixels.h | 37 ++ src/video/ogc/SDL_ogcvideo.c | 386 ++++++++++++++ src/video/ogc/SDL_ogcvideo.h | 50 ++ 21 files changed, 2653 insertions(+) create mode 100644 src/video/ogc/SDL_ogccursors.h create mode 100644 src/video/ogc/SDL_ogcevents.c create mode 100644 src/video/ogc/SDL_ogcevents_c.h create mode 100644 src/video/ogc/SDL_ogcframebuffer.c create mode 100644 src/video/ogc/SDL_ogcframebuffer_c.h create mode 100644 src/video/ogc/SDL_ogcgl.c create mode 100644 src/video/ogc/SDL_ogcgl.h create mode 100644 src/video/ogc/SDL_ogcgxcommon.c create mode 100644 src/video/ogc/SDL_ogcgxcommon.h create mode 100644 src/video/ogc/SDL_ogckeyboard.c create mode 100644 src/video/ogc/SDL_ogckeyboard.h create mode 100644 src/video/ogc/SDL_ogcmouse.c create mode 100644 src/video/ogc/SDL_ogcmouse.h create mode 100644 src/video/ogc/SDL_ogcpixels.c create mode 100644 src/video/ogc/SDL_ogcpixels.h create mode 100644 src/video/ogc/SDL_ogcvideo.c create mode 100644 src/video/ogc/SDL_ogcvideo.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c678bed12f8a..e2c47bdcc741f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3359,6 +3359,32 @@ elseif(OGC) sdl_sources("${SDL3_SOURCE_DIR}/src/filesystem/posix/SDL_sysfsops.c") set(HAVE_SDL_FSOPS TRUE) + if(SDL_VIDEO) + set(SDL_VIDEO_DRIVER_OGC 1) + set(SDL_VIDEO_RENDER_OGC 1) + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/render/ogc/*.c" + "${SDL3_SOURCE_DIR}/src/render/ogc/*.h" + "${SDL3_SOURCE_DIR}/src/video/ogc/*.c" + "${SDL3_SOURCE_DIR}/src/video/ogc/*.h" + ) + pkg_search_module(EGC embedded-game-controller REQUIRED) + sdl_include_directories(PRIVATE SYSTEM ${EGC_INCLUDE_DIRS}) + sdl_link_dependency(egc LIBS embedded-game-controller) + if(NINTENDO_WII) + sdl_link_dependency(egc LIBS bt-embedded) + endif() + set(SDL_VIDEO_OPENGL 0) + set(HAVE_SDL_VIDEO TRUE) + if(SDL_OPENGL) + # TODO: is the next line needed? + pkg_search_module(OPENGX opengl REQUIRED IMPORTED_TARGET) + set(SDL_VIDEO_OPENGL 1) + set(HAVE_OPENGL TRUE) + sdl_link_dependency(opengx LIBS opengx) + endif() + endif() + set(SDL_PTHREADS ON) set(HAVE_SDL_THREADS TRUE) set(SDL_PTHREADS_SEM ON) diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake index 06d80fb4befd6..2b6ac32d04150 100644 --- a/include/build_config/SDL_build_config.h.cmake +++ b/include/build_config/SDL_build_config.h.cmake @@ -408,6 +408,7 @@ #cmakedefine SDL_VIDEO_DRIVER_N3DS 1 #cmakedefine SDL_VIDEO_DRIVER_NGAGE 1 #cmakedefine SDL_VIDEO_DRIVER_OFFSCREEN 1 +#cmakedefine SDL_VIDEO_DRIVER_OGC 1 #cmakedefine SDL_VIDEO_DRIVER_PS2 1 #cmakedefine SDL_VIDEO_DRIVER_PSP 1 #cmakedefine SDL_VIDEO_DRIVER_RISCOS 1 diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index 1fdc5bf527e68..fbe5b156de50d 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -534,6 +534,7 @@ extern VideoBootStrap PSP_bootstrap; extern VideoBootStrap VITA_bootstrap; extern VideoBootStrap RISCOS_bootstrap; extern VideoBootStrap N3DS_bootstrap; +extern VideoBootStrap OGC_bootstrap; extern VideoBootStrap NGAGE_bootstrap; extern VideoBootStrap RPI_bootstrap; extern VideoBootStrap KMSDRM_bootstrap; diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index ce942bfd42c31..6de1e1572ace2 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -128,6 +128,9 @@ static VideoBootStrap *bootstrap[] = { #ifdef SDL_VIDEO_DRIVER_N3DS &N3DS_bootstrap, #endif +#ifdef SDL_VIDEO_DRIVER_OGC + &OGC_bootstrap, +#endif #ifdef SDL_VIDEO_DRIVER_NGAGE &NGAGE_bootstrap, #endif diff --git a/src/video/ogc/SDL_ogccursors.h b/src/video/ogc/SDL_ogccursors.h new file mode 100644 index 0000000000000..24d0608c374da --- /dev/null +++ b/src/video/ogc/SDL_ogccursors.h @@ -0,0 +1,480 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2023 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +typedef struct _OGC_Cursor { + uint16_t width; + uint16_t height; + uint8_t hot_x; + uint8_t hot_y; + uint8_t bytes_per_pixel; + uint8_t pixel_data[]; +} OGC_Cursor; + +static const OGC_Cursor OGC_cursor_hand = { + 37, 52, 11, 0, 4, + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000Q\000" + "\000\000\262\000\000\000\337\000\000\000\345\000\000\000\313\000\000\000\203\000\000\000\032\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\005" + "\000\000\000\224\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\330\000\000\000\062\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\177\000\000\000\377\000\000\000\377\026\026\026\377bbb\377ttt\377" + "<<<\377\000\000\000\377\000\000\000\377\000\000\000\334\000\000\000\030\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000%\000\000\000\364\000\000\000\377...\377\327\327" + "\327\377\377\377\377\377\377\377\377\377\373\373\373\377\177\177\177\377" + "\000\000\000\377\000\000\000\377\000\000\000\206\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000n\000\000\000\377\005\005\005\377\277\277\277\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377BBB\377\000" + "\000\000\377\000\000\000\323\000\000\000\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\225\000\000\000\377###\377\366\366\366\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\377\377\377\206\206\206\377\000\000" + "\000\377\000\000\000\356\000\000\000\033\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\231\000\000\000\377+++\377\370\370\370\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\377\377\217\217\217\377\000\000\000\377" + "\000\000\000\361\000\000\000\040\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\230\000\000\000\377***\377\367\367\367\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\377\215\215\215\377\000\000\000\377\000" + "\000\000\360\000\000\000\037\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\230" + "\000\000\000\377***\377\367\367\367\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\215\215\215\377\000\000\000\377\000\000\000" + "\360\000\000\000\037\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\230\000" + "\000\000\377***\377\367\367\367\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\215\215\215\377\000\000\000\377\000\000\000\360" + "\000\000\000\037\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\230\000\000\000" + "\377***\377\367\367\367\377\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\215\215\215\377\000\000\000\377\000\000\000\360\000\000" + "\000\037\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\230\000\000\000\377" + "***\377\367\367\367\377\377\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\215\215\215\377\000\000\000\377\000\000\000\360\000\000\000\037" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\230\000\000\000\377***\377" + "\367\367\367\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\215\215\215\377\000\000\000\377\000\000\000\360\000\000\000\037\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\230\000\000\000\377***\377\367" + "\367\367\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\215\215\215\377\000\000\000\377\000\000\000\357\000\000\000\026\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\230\000\000\000\377***\377\367\367" + "\367\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\215\215\215\377\000\000\000\377\000\000\000\370\000\000\000\244\000\000\000\223" + "\000\000\000e\000\000\000\032\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\230\000\000\000\377***\377\367\367" + "\367\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\215\215\215\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\340\000\000\000Z\000\000\000\000\000\000\000\003\000\000\000\022\000\000\000\006\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\230\000\000\000\377***\377\367" + "\367\367\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\215\215\215\377\000\000\000\377\001\001\001\377%%%\377\036\036\036" + "\377\000\000\000\377\000\000\000\377\000\000\000\373\000\000\000\244\000\000\000\310\000\000\000\341\000\000\000\320" + "\000\000\000\220\000\000\000*\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\230\000\000\000" + "\377***\377\367\367\367\377\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\210\210\210\377\022\022\022\377\270\270\270" + "\377\356\356\356\377\351\351\351\377\236\236\236\377\022\022\022\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\357\000\000\000[\000\000\000" + "\030\000\000\000<\000\000\000B\000\000\000(\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\227\000\000\000\377***\377\367\367\367\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377" + "\202\202\202\377\065\065\065\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\246\246\246\377\001\001\001\377\003\003\003\377@@@\377iii\377DD" + "D\377\002\002\002\377\000\000\000\377\000\000\000\372\000\000\000\342\000\000\000\375\000\000\000\377\000\000\000\360" + "\000\000\000\264\000\000\000:\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\233\000\000\000\377***\377\367\367\367\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\377\377\203\203\203\377\061\061\061" + "\377\375\375\375\377\377\377\377\377\377\377\377\377\377\377\377\377\372" + "\372\372\377\036\036\036\377kkk\377\375\375\375\377\377\377\377\377\377\377" + "\377\377\253\253\253\377\015\015\015\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\366\000\000\000U\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\016" + "\000\000\000n\000\000\000\272\000\000\000\360\000\000\000\377***\377\367\367\367\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\203" + "\203\203\377\061\061\061\377\375\375\375\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\376\376\376\377\063\063\063\377\211\211\211\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\377\377\377\177\177\177\377\000\000" + "\000\377AAA\377\236\236\236\377\256\256\256\377lll\377\011\011\011\377\000\000\000\377" + "\000\000\000\360\000\000\000)\000\000\000\000\000\000\000!\000\000\000\304\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377***\377\367\367\367\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\203\203\203\377\061\061\061\377\375" + "\375\375\377\377\377\377\377\377\377\377\377\377\377\377\377\375\375\375" + "\377\061\061\061\377\203\203\203\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\304\304\304\377\011\011\011\377\327\327\327\377\377" + "\377\377\377\377\377\377\377\377\377\377\377\247\247\247\377\006\006\006\377\000" + "\000\000\377\000\000\000\225\000\000\000\016\000\000\000\312\000\000\000\377\000\000\000\377(((\377aaa\377\067" + "\067\067\377'''\377\366\366\366\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\203\203\203\377\061\061\061\377\375" + "\375\375\377\377\377\377\377\377\377\377\377\377\377\377\377\375\375\375" + "\377\061\061\061\377\203\203\203\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\313\313\313\377\015\015\015\377\323\323\323\377\377" + "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377NNN\377\000\000\000" + "\377\000\000\000\324\000\000\000s\000\000\000\377\000\000\000\377fff\377\357\357\357\377\377\377" + "\377\377\244\244\244\377###\377\365\365\365\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\377\377\377|||\377&&&\377\375\375" + "\375\377\377\377\377\377\377\377\377\377\377\377\377\377\375\375\375\377" + "&&&\377|||\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\306\306\306\377\001\001\001\377\317\317\317\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\200\200\200\377\000\000\000\377\000\000\000\345" + "\000\000\000\305\000\000\000\377\062\062\062\377\371\371\371\377\377\377\377\377\377\377" + "\377\377\235\235\235\377###\377\365\365\365\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\377\377\377\274\274\274\377\221" + "\221\221\377\376\376\376\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\376\376\376\377\221\221\221\377\274\274\274\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\377\342\342\340\377\200\200\200" + "\377\347\347\347\377\377\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\202\202\202\377\000\000\000\377\000\000\000\345\000\000\000\342\000\000\000\377www\377" + "\377\377\377\377\376\376\376\377\377\377\377\377\235\235\235\377!##\377\365" + "\365\365\377\377\377\377\377\374\376\376\377\374\376\376\377\374\376\376" + "\377\374\376\376\377\377\377\377\377\377\377\377\377\374\376\376\377\374" + "\376\376\377\374\376\376\377\374\376\376\377\374\376\376\377\377\377\377" + "\377\377\377\377\377\374\376\376\377\374\376\376\377\374\376\376\377\374" + "\376\376\377\377\377\377\377\377\377\377\377\377\377\377\377\374\376\376" + "\377\374\376\376\377\374\376\376\377\377\377\377\377\201\201\201\377\000\000" + "\000\377\000\000\000\345\000\000\000\346\000\000\000\377\203\203\203\377\377\377\377\377\375" + "\373\375\377\377\377\377\377\233\233\235\377!!#\377\363\363\365\377\377\377" + "\377\377\373\373\376\377\373\373\376\377\373\373\376\377\373\373\376\377" + "\373\373\376\377\373\373\376\377\373\373\376\377\373\373\375\377\373\373" + "\376\377\373\373\376\377\373\373\376\377\373\373\375\377\373\373\376\377" + "\373\373\376\377\373\373\376\377\373\373\376\377\373\373\376\377\373\373" + "\376\377\373\373\375\377\373\373\375\377\373\373\375\377\373\373\376\377" + "\373\374\376\377\377\377\377\377\201\201\201\377\000\000\000\377\000\000\000\345\000\000" + "\000\345\000\000\000\377\177\201\201\377\377\377\377\377\371\373\376\377\377\377" + "\377\377\233\233\235\377\"\"\"\377\361\363\366\377\375\377\377\377\371\373" + "\376\377\371\373\376\377\371\373\376\377\367\372\376\377\367\372\376\377" + "\371\371\376\377\371\371\376\377\371\373\376\377\371\373\376\377\371\373" + "\376\377\371\373\376\377\367\372\376\377\367\372\376\377\371\373\376\377" + "\367\372\376\377\367\372\376\377\371\373\376\377\367\372\376\377\371\373" + "\376\377\371\373\376\377\367\372\376\377\367\372\376\377\371\373\376\377" + "\377\377\377\377\177\201\201\377\000\000\000\377\000\000\000\345\000\000\000\345\000\000\000\377" + "\177\177\201\377\377\377\377\377\365\370\377\377\377\377\377\377\234\234" + "\234\377\"\"\"\377\356\361\365\377\372\375\377\377\365\370\375\377\365\370" + "\375\377\365\370\375\377\365\370\375\377\365\370\375\377\365\370\375\377" + "\365\370\375\377\365\370\375\377\365\370\375\377\365\370\375\377\365\370" + "\375\377\365\370\375\377\365\370\375\377\365\370\375\377\365\370\375\377" + "\365\370\375\377\365\370\375\377\365\370\375\377\365\370\375\377\365\370" + "\375\377\365\370\375\377\365\370\375\377\366\371\376\377\377\377\377\377" + "\177\177\201\377\000\000\000\377\000\000\000\345\000\000\000\345\000\000\000\377}\200\202\377\377" + "\377\377\377\363\366\375\377\377\377\377\377\232\234\234\377\"\"\"\377\355" + "\357\364\377\370\372\377\377\363\366\375\377\363\366\375\377\363\366\375" + "\377\363\366\375\377\363\366\375\377\363\366\375\377\363\366\375\377\363" + "\366\375\377\363\366\375\377\363\366\375\377\363\366\375\377\363\366\375" + "\377\363\366\375\377\363\366\375\377\363\366\375\377\363\366\375\377\363" + "\366\375\377\363\366\375\377\363\366\375\377\363\366\375\377\363\366\375" + "\377\363\366\375\377\364\367\376\377\377\377\377\377~\177\201\377\000\000\000\377" + "\000\000\000\345\000\000\000\345\000\000\000\377|~\201\377\377\377\377\377\357\362\374\377" + "\377\377\377\377\231\232\234\377\"\"\"\377\352\355\364\377\364\370\377\377" + "\357\362\374\377\357\364\374\377\357\364\374\377\357\364\374\377\357\362" + "\374\377\357\364\374\377\357\364\374\377\357\364\374\377\357\364\374\377" + "\357\362\374\377\357\364\374\377\357\364\374\377\357\364\374\377\357\364" + "\374\377\357\364\374\377\357\364\374\377\357\364\374\377\357\364\374\377" + "\357\364\374\377\357\364\374\377\357\362\374\377\357\364\374\377\360\363" + "\375\377\377\377\377\377|~\201\377\000\000\000\377\000\000\000\345\000\000\000\345\000\000\000\377" + "z|\201\377\377\377\377\377\353\360\374\377\375\377\377\377\230\232\235\377" + "\"\"\"\377\347\352\364\377\360\364\377\377\353\360\374\377\355\360\374\377" + "\355\360\374\377\355\360\374\377\353\360\374\377\355\360\374\377\353\360" + "\374\377\353\360\374\377\353\360\374\377\355\360\374\377\355\360\374\377" + "\355\360\374\377\355\362\374\377\355\360\374\377\353\360\374\377\353\360" + "\374\377\355\360\374\377\353\360\374\377\355\360\374\377\355\360\374\377" + "\355\360\374\377\353\360\374\377\355\362\374\377\377\377\377\377z|\201\377" + "\000\000\000\377\000\000\000\345\000\000\000\345\000\000\000\377y{\201\377\377\377\377\377\351\356" + "\372\377\373\377\377\377\220\223\230\377\026\026\026\377\344\351\363\377\353" + "\362\377\377\350\357\374\377\350\357\372\377\350\357\372\377\350\357\374" + "\377\350\357\374\377\350\357\372\377\350\357\374\377\350\357\372\377\350" + "\357\372\377\350\357\374\377\350\357\374\377\350\357\374\377\350\357\372" + "\377\350\357\372\377\350\357\372\377\350\357\374\377\350\357\374\377\350" + "\357\372\377\350\357\372\377\350\357\372\377\350\357\372\377\350\357\372" + "\377\351\360\373\377\377\377\377\377y{\201\377\000\000\000\377\000\000\000\345\000\000\000" + "\345\000\000\000\377xz\200\377\375\377\377\377\346\354\373\377\352\362\376\377" + "\304\313\325\377\226\233\243\377\343\353\370\377\345\355\374\377\345\355" + "\372\377\345\355\372\377\345\353\372\377\345\353\372\377\345\355\372\377" + "\345\355\372\377\345\353\372\377\345\353\372\377\345\353\372\377\345\355" + "\372\377\345\355\372\377\345\353\372\377\345\353\372\377\345\353\372\377" + "\345\353\372\377\345\353\372\377\345\353\372\377\345\353\372\377\345\353" + "\372\377\345\353\372\377\345\353\372\377\345\353\372\377\344\354\373\377" + "\375\377\377\377xz\200\377\000\000\000\377\000\000\000\345\000\000\000\345\000\000\000\377ux\201" + "\377\373\377\377\377\342\352\373\377\340\350\371\377\350\360\377\377\362" + "\370\377\377\341\351\373\377\341\351\372\377\341\351\372\377\341\351\372" + "\377\341\351\372\377\341\351\372\377\341\351\372\377\341\351\372\377\341" + "\351\372\377\341\351\372\377\341\351\372\377\341\351\372\377\341\351\372" + "\377\341\351\372\377\341\351\372\377\341\351\372\377\341\351\372\377\341" + "\351\372\377\341\351\372\377\341\351\372\377\341\351\372\377\341\351\372" + "\377\341\351\372\377\341\351\372\377\342\352\373\377\373\377\377\377ux\201" + "\377\000\000\000\377\000\000\000\345\000\000\000\345\000\000\000\377ty\200\377\367\375\377\377\336" + "\346\372\377\334\346\372\377\334\346\372\377\334\346\370\377\335\345\371" + "\377\335\345\371\377\335\345\371\377\335\345\371\377\335\345\371\377\335" + "\345\371\377\335\345\371\377\335\345\371\377\335\345\371\377\335\345\371" + "\377\335\345\371\377\335\345\371\377\335\345\371\377\334\346\372\377\335" + "\345\371\377\335\345\371\377\335\345\371\377\335\345\371\377\335\345\371" + "\377\335\345\371\377\335\345\371\377\334\346\372\377\335\345\371\377\335" + "\345\371\377\335\347\373\377\367\375\377\377ty\200\377\000\000\000\377\000\000\000\345" + "\000\000\000\345\000\000\000\377sx\200\377\366\373\377\377\334\344\372\377\331\344\370" + "\377\330\343\371\377\331\344\370\377\330\343\371\377\330\343\371\377\331" + "\344\370\377\331\344\370\377\331\344\370\377\331\344\370\377\330\343\371" + "\377\331\344\370\377\331\344\370\377\331\344\370\377\331\344\370\377\331" + "\344\370\377\330\343\371\377\330\343\371\377\330\343\371\377\331\344\370" + "\377\331\344\370\377\331\344\370\377\330\343\371\377\330\342\371\377\331" + "\344\370\377\331\343\370\377\331\344\370\377\331\343\370\377\334\344\370" + "\377\366\373\377\377sx\200\377\000\000\000\377\000\000\000\345\000\000\000\346\000\000\000\377QV" + "[\377\365\373\377\377\334\346\372\377\325\341\370\377\325\337\370\377\325" + "\341\370\377\325\341\370\377\325\341\370\377\325\341\370\377\325\341\370" + "\377\325\341\370\377\325\341\370\377\325\341\370\377\325\341\370\377\325" + "\341\370\377\325\341\370\377\325\341\370\377\325\341\370\377\325\341\370" + "\377\325\341\370\377\325\341\370\377\325\341\370\377\325\341\370\377\325" + "\341\370\377\325\341\370\377\325\341\370\377\325\341\370\377\325\341\370" + "\377\324\341\370\377\324\341\370\377\334\346\372\377\365\373\377\377PU\\" + "\377\000\000\000\377\000\000\000\346\000\000\000\314\000\000\000\377\012\012\014\377\254\264\301\377" + "\362\370\377\377\325\341\370\377\321\335\367\377\320\335\370\377\320\335" + "\370\377\320\335\370\377\320\335\370\377\320\335\370\377\320\335\370\377" + "\320\335\370\377\320\335\370\377\321\335\367\377\320\335\370\377\320\335" + "\370\377\320\335\370\377\320\335\370\377\320\335\370\377\321\335\367\377" + "\321\335\367\377\320\335\370\377\320\335\370\377\320\335\370\377\320\335" + "\370\377\320\335\370\377\321\335\367\377\320\335\370\377\321\335\367\377" + "\326\340\371\377\362\370\377\377\254\264\301\377\012\012\014\377\000\000\000\377\000" + "\000\000\314\000\000\000d\000\000\000\377\000\000\000\377\024\025\027\377\262\274\314\377\355\365" + "\377\377\323\336\367\377\315\334\366\377\315\334\366\377\314\333\367\377" + "\315\334\366\377\314\333\367\377\314\333\367\377\314\333\367\377\314\333" + "\367\377\316\333\367\377\314\333\367\377\314\333\367\377\315\334\366\377" + "\315\334\366\377\315\334\366\377\316\333\367\377\316\333\367\377\314\333" + "\367\377\314\333\367\377\315\334\366\377\315\334\366\377\314\333\367\377" + "\314\333\367\377\314\333\367\377\323\340\367\377\356\365\377\377\262\274" + "\314\377\024\025\027\377\000\000\000\377\000\000\000\377\000\000\000d\000\000\000\002\000\000\000\220\000\000\000" + "\377\000\000\000\377\025\027\032\377\261\273\315\377\352\362\377\377\317\333\367\377" + "\311\326\366\377\311\330\366\377\311\330\366\377\311\330\366\377\311\330" + "\366\377\311\330\366\377\311\330\366\377\311\330\366\377\311\330\366\377" + "\311\330\366\377\311\330\366\377\311\330\366\377\311\330\366\377\311\330" + "\366\377\311\330\366\377\311\330\366\377\311\330\366\377\311\330\366\377" + "\311\330\366\377\311\330\366\377\311\330\366\377\317\333\367\377\352\362" + "\377\377\261\271\315\377\025\027\032\377\000\000\000\377\000\000\000\377\000\000\000\220\000\000\000" + "\002\000\000\000\000\000\000\000\002\000\000\000\224\000\000\000\377\000\000\000\377\025\030\032\377\257\272\320" + "\377\346\356\377\377\313\330\366\377\305\325\365\377\305\324\365\377\305" + "\324\365\377\305\325\365\377\305\324\365\377\305\324\365\377\305\325\365" + "\377\305\325\365\377\305\324\365\377\305\325\365\377\305\325\365\377\305" + "\324\365\377\305\325\365\377\305\325\365\377\305\325\365\377\305\325\365" + "\377\305\325\365\377\305\324\365\377\305\324\365\377\313\332\366\377\346" + "\356\377\377\257\272\320\377\025\027\032\377\000\000\000\377\000\000\000\377\000\000\000\224\000" + "\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\235\000\000\000\377\000\000\000\377\031\032" + "\034\377\267\304\335\377\327\345\377\377\307\326\365\377\302\322\365\377\302" + "\322\365\377\302\322\365\377\302\322\365\377\302\322\365\377\302\322\365" + "\377\302\322\365\377\302\322\365\377\302\322\365\377\302\322\365\377\302" + "\322\365\377\302\322\365\377\302\322\365\377\302\322\365\377\302\322\365" + "\377\302\322\365\377\302\322\365\377\305\326\367\377\327\343\377\377\267" + "\304\335\377\031\032\034\377\000\000\000\377\000\000\000\377\000\000\000\235\000\000\000\004\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\006\000\000\000\241\000\000\000\377\000\000\000\377GJV\377" + "\327\345\377\377\311\330\367\377\275\317\365\377\276\317\365\377\276\320" + "\365\377\276\320\365\377\276\320\365\377\276\320\365\377\276\320\365\377" + "\276\320\365\377\276\320\365\377\276\320\365\377\276\320\365\377\276\320" + "\365\377\276\320\365\377\276\320\365\377\276\320\365\377\276\320\365\377" + "\275\317\365\377\311\330\367\377\326\345\377\377GJV\377\000\000\000\377\000\000\000\377" + "\000\000\000\241\000\000\000\006\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\011\000\000\000\313\000\000\000\377\015\015\017\377\270\305\341\377\312\334\374" + "\377\271\315\364\377\272\314\364\377\272\316\364\377\272\316\364\377\272" + "\316\364\377\272\316\364\377\272\316\364\377\272\316\364\377\272\316\364" + "\377\272\316\364\377\272\316\364\377\272\316\364\377\272\316\364\377\272" + "\316\364\377\272\316\364\377\272\316\364\377\271\316\364\377\312\334\374" + "\377\270\305\341\377\015\015\017\377\000\000\000\377\000\000\000\313\000\000\000\011\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000s\000\000" + "\000\377\004\004\006\377\254\270\324\377\311\332\376\377\267\313\364\377\270\312" + "\363\377\270\314\363\377\270\314\363\377\270\314\363\377\270\314\363\377" + "\270\312\363\377\270\312\363\377\270\314\363\377\270\314\363\377\270\314" + "\363\377\270\314\363\377\270\312\363\377\270\314\363\377\270\314\363\377" + "\270\314\363\377\267\313\364\377\311\332\376\377\254\270\324\377\004\004\006\377" + "\000\000\000\377\000\000\000s\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000a\000\000\000\377\005\005\007\377\253\270\324\377\306\332" + "\376\377\263\310\363\377\264\311\363\377\264\311\363\377\264\311\363\377" + "\264\311\363\377\264\311\363\377\264\311\364\377\264\311\364\377\264\311" + "\363\377\264\311\363\377\264\311\363\377\264\311\363\377\264\311\363\377" + "\264\311\363\377\264\311\364\377\264\311\364\377\263\310\363\377\306\332" + "\376\377\253\270\324\377\005\005\007\377\000\000\000\377\000\000\000a\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000d\000\000\000\377" + "\005\005\007\377\255\274\332\377\306\330\376\377\261\306\363\377\262\307\362\377" + "\262\307\362\377\262\307\362\377\262\307\362\377\262\307\362\377\262\307" + "\362\377\262\307\362\377\262\307\362\377\262\307\362\377\262\307\362\377" + "\262\307\362\377\262\307\362\377\262\307\362\377\262\307\362\377\262\307" + "\362\377\261\306\363\377\306\330\376\377\255\274\332\377\005\005\007\377\000\000\000" + "\377\000\000\000d\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000W\000\000\000\377\002\002\002\377\205\220\251\377\341\354\377" + "\377\313\333\377\377\311\333\377\377\311\333\377\377\311\333\377\377\311" + "\333\377\377\311\333\377\377\311\333\377\377\311\333\377\377\311\333\377" + "\377\311\333\377\377\310\332\377\377\311\333\377\377\311\333\377\377\311" + "\333\377\377\310\332\377\377\310\332\377\377\313\333\377\377\340\354\377" + "\377\205\220\251\377\002\002\002\377\000\000\000\377\000\000\000W\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\034\000\000\000\354" + "\000\000\000\377\020\022\025\377RZi\377]ev\377Zdt\377Zdt\377[ct\377[ct\377Zdt\377" + "Zdt\377Zdt\377[ct\377[ct\377Zds\377[ct\377[ct\377Zdt\377[cr\377Zds\377]e" + "v\377RZi\377\020\022\025\377\000\000\000\377\000\000\000\354\000\000\000\034\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000f\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000f\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000R\000\000\000\275\000" + "\000\000\344\000\000\000\345\000\000\000\344\000\000\000\344\000\000\000\344\000\000\000\344\000\000\000\344\000\000" + "\000\344\000\000\000\344\000\000\000\344\000\000\000\344\000\000\000\344\000\000\000\344\000\000\000\344\000\000\000" + "\344\000\000\000\344\000\000\000\344\000\000\000\345\000\000\000\344\000\000\000\275\000\000\000R\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", +}; + +static const OGC_Cursor OGC_cursor_arrow = { + 24, 31, 0, 0, 4, + "\002\002\002\337\000\000\000\224\000\000\000\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\000\000\000\377" + "\005\005\005\321\000\000\000$\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377ddd\377\037\037\037\373\003\003\003\364" + "\000\000\000V\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\377\225\225\225\377\361\361\361\377VVV\376\000\000\000\377" + "\002\002\002\243\000\000\000\012\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\377\225\225\225\377\377\377\377\377\377\377\377\377\241" + "\241\241\377\007\007\007\375\005\005\005\322\000\000\000%\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\225\225\225\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\315\315\315\377!!!\373\003\003\003\365\000\000\000Y\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\225\225\225\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\362\362\362\377" + "WWW\376\000\000\000\377\002\002\002\243\000\000\000\012\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\377\225\225\225\377\377\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\377\241\241\241\377\007\007\007\375\005" + "\005\005\331\000\000\000+\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\225\225\225\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\316\316\316\377!!!\373\003\003\003\365\000\000\000Z" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\377\225\225\225\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\362\362\362\377XXX\376\000\000\000\377\002\002\002\243\000\000\000" + "\012\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\377\225\225\225\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\375\375\377\377\373\374\376\377\235\236\240\377\007\007\007\374\005" + "\005\005\331\000\000\000+\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\377\225\225\225\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\375\375\377\377" + "\373\374\376\377\371\373\376\377\367\371\376\377\366\370\375\377\305\310" + "\314\377\037!\"\373\003\003\003\366\000\000\000[\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\377\225\225\225\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\375\375\377\377\373\374\376\377\371" + "\373\376\377\367\371\376\377\366\370\375\377\364\367\375\377\362\365\375" + "\377\360\364\374\377\342\347\357\377QSW\376\000\000\000\377\002\002\002\243\000\000\000\012" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\225\225\225\377\377\377" + "\377\377\377\377\377\377\375\375\377\377\373\374\376\377\371\373\376\377" + "\367\371\376\377\366\370\375\377\364\367\375\377\362\365\375\377\360\364" + "\374\377\356\363\374\377\354\362\374\377\353\360\374\377\351\357\373\377" + "\222\226\236\377\007\007\007\374\004\005\005\331\000\000\000+\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\377\225\225\225\377\375\375\377\377\373\374\376\377\371\373\376" + "\377\367\371\376\377\366\370\375\377\364\367\375\377\362\365\375\377\360" + "\364\374\377\356\363\374\377\354\362\374\377\353\360\374\377\351\357\373" + "\377\347\356\373\377\345\354\373\377\343\353\372\377\275\303\321\377#%'\373" + "\003\003\003\366\000\000\000[\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\223\223\224\377\371\373" + "\376\377\367\371\376\377\366\370\375\377\364\367\375\377\362\365\375\377" + "\360\364\374\377\356\363\374\377\354\362\374\377\353\360\374\377\351\357" + "\373\377\347\356\373\377\345\354\373\377\343\353\372\377\342\352\372\377" + "\340\350\372\377\336\347\371\377\321\332\354\377LOW\376\000\000\000\377\002\002\002\243" + "\000\000\000\012\000\000\000\000\000\000\000\377\220\221\224\377\366\370\375\377\364\367\375\377" + "\362\365\375\377\360\364\374\377\356\363\374\377\354\362\374\377\353\360" + "\374\377\351\357\373\377\347\356\373\377\345\354\373\377\343\353\372\377" + "\342\352\372\377\340\350\372\377\336\347\371\377\334\346\371\377\332\344" + "\371\377\330\343\370\377\327\342\370\377\206\215\235\377\006\006\007\374\004\004\005" + "\332\000\000\000,\000\000\000\377\217\220\224\377\362\365\375\377\360\364\374\377\356" + "\363\374\377\354\361\374\377\353\360\374\377\351\357\373\377\347\356\373" + "\377\345\354\373\377\343\353\372\377\342\352\372\377\340\350\372\377\336" + "\347\371\377\334\346\371\377\332\344\371\377\330\343\370\377\327\342\370" + "\377\325\340\370\377\323\337\370\377\321\336\367\377pw\206\377\000\000\000\377" + "\001\001\001\347\000\000\000\377\214\217\223\377\356\363\374\377\354\361\374\377\353" + "\360\374\377\351\357\373\377\347\356\373\377\345\354\373\377\343\353\372" + "\377\342\352\372\377\340\350\372\377\336\347\371\377\334\346\371\377\332" + "\344\371\377\330\343\370\377\327\342\370\377\325\340\370\377\323\337\370" + "\377\321\336\367\377\225\236\261\377,/\065\374\000\000\000\377\004\004\005\316\000\000\000F" + "\000\000\000\377\212\215\223\377\353\360\374\377\351\357\373\377\347\356\373\377" + "\345\354\373\377\343\353\372\377\342\352\372\377\340\350\372\377\336\347" + "\371\377\334\346\371\377\332\344\371\377\330\343\370\377\327\342\370\377" + "\325\340\370\377\323\337\370\377\321\336\367\377\236\250\275\377\062\065<\374" + "\000\000\000\377\002\004\004\323\000\000\000U\000\000\000\001\000\000\000\000\000\000\000\377\210\214\223\377\347" + "\356\373\377\345\354\373\377\343\353\372\377\342\352\372\377\340\350\372" + "\377\336\347\371\377\334\346\371\377\332\344\371\377\330\343\370\377\327" + "\342\370\377\325\340\370\377\323\337\370\377\321\336\367\377\230\241\265" + "\377,/\064\374\000\000\000\377\002\004\004\323\000\000\000U\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\377\206\212\223\377\343\353\372\377\341\352\372\377\340\350\372\377" + "\336\347\371\377\334\346\371\377\332\344\371\377\330\343\370\377\327\342" + "\370\377\325\340\370\377\323\337\370\377\321\336\367\377\317\334\367\377" + "DHQ\374\000\000\000\377\004\004\004\314\000\000\000J\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\377\203\211\222\377\340\350\372\377\336\347\371\377\334" + "\346\371\377\332\344\371\377\330\343\370\377\327\342\370\377\325\340\370" + "\377\323\337\370\377\321\336\367\377\317\334\367\377\315\333\367\377\314" + "\332\366\377JOZ\376\002\002\003\362\000\000\000\012\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\202\207\221\377\334\346\371\377\332\344" + "\371\377\330\343\370\377\327\342\370\377\325\340\370\377\323\337\370\377" + "\321\336\367\377\316\333\366\377\315\333\367\377\314\332\366\377\312\330" + "\366\377\310\327\366\377\250\266\320\377\002\002\002\376\000\000\000r\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\177\205\221\377\330\343" + "\370\377\327\342\370\377\325\340\370\377\323\337\370\377\321\336\367\377" + "\223\234\257\377),\062\374\036\040$\374\306\324\361\377\310\327\366\377\306" + "\326\365\377\304\324\365\377\303\323\365\377FLX\376\003\003\004\354\000\000\000\012\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377~\204\221\377\325" + "\340\370\377\323\337\370\377\321\336\367\377\222\233\256\377)-\063\374\000\000" + "\000\377\002\004\004\321\000\000\000\376y\203\226\377\304\324\365\377\303\323\365\377\301" + "\322\364\377\277\320\364\377\242\261\321\377\001\002\002\376\000\000\000s\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377{\202\221\377\321\336\367\377" + "\222\233\256\377),\062\374\000\000\000\377\004\004\005\313\000\000\000I\000\000\000\000\001\001\003\263\026" + "\030\034\373\275\316\357\377\277\320\364\377\275\317\364\377\273\316\364\377" + "\271\314\363\377CJY\376\003\003\004\354\000\000\000\012\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\377INW\377)+\061\373\000\000\000\377\004\004\004\312\000\000\000H\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\062\000\000\000\376t\177\225\377\273\316\364\377\271\314\363" + "\377\270\313\363\377\212\232\271\377(,\066\375\000\000\000\377\000\000\000F\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\377\000\000\000\377\004\004\004\311\000\000\000H\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\003\003\262\025\027\034\373\264\307" + "\356\377\212\232\271\377.\063>\374\000\000\000\377\004\004\005\330\000\000\000V\000\000\000\001\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000t\000\000\000G\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\062\000\000\000\376\032\035$\375" + "\000\000\000\377\004\004\005\327\000\000\000V\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\002\003\003\247\004\004\005\325\000\000\000U\000\000\000\002\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", +}; + diff --git a/src/video/ogc/SDL_ogcevents.c b/src/video/ogc/SDL_ogcevents.c new file mode 100644 index 0000000000000..12671dbe74baf --- /dev/null +++ b/src/video/ogc/SDL_ogcevents.c @@ -0,0 +1,203 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2023 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_VIDEO_DRIVER_OGC + +#include "../../events/SDL_events_c.h" + +#include "SDL_ogcevents_c.h" +#include "SDL_ogckeyboard.h" +#include "SDL_ogcmouse.h" +#include "SDL_ogcvideo.h" + +#include + +/* These variables can be set from the handlers registered in SDL_main() */ +bool OGC_PowerOffRequested = false; +bool OGC_ResetRequested = false; + +int OGC_NumControllers = 0; + +/* This is the array of structs holding the controller data */ +static _OGC_Controller s_controllers[OGC_MAX_CONTROLLERS]; +/* This is an array of indexes to the previous array: this is done to + * avoid moving the data when a controller is removed, and to preserve the + * IDs. */ +static u8 s_controller_indices[OGC_MAX_CONTROLLERS]; +static SDL_JoystickID s_next_instance_id = 1; +static _OGC_ControllerCb s_joystick_added_cb = NULL; +static _OGC_ControllerCb s_joystick_removed_cb = NULL; + +#ifdef __wii__ +#define MAX_WII_MOUSE_BUTTONS 2 +static const struct { + int wii; + int mouse; +} s_mouse_button_map[MAX_WII_MOUSE_BUTTONS] = { + { EGC_GAMEPAD_BUTTON_SOUTH, SDL_BUTTON_LEFT }, + { EGC_GAMEPAD_BUTTON_EAST, SDL_BUTTON_RIGHT }, +}; + +static void pump_ir_events(SDL_VideoDevice *_this) +{ + int screen_w, screen_h; + + if (!_this->windows) return; + + screen_w = _this->displays[0]->current_mode->w; + screen_h = _this->displays[0]->current_mode->h; + + for (int i = 0; i < OGC_NumControllers; i++) { + _OGC_Controller *controller = OGC_get_controller(i); + egc_input_device_t *device = controller->egc_device; + egc_point_t point; + + if (device->desc->num_touch_points == 0) continue; + + point = egc_input_device_read_touch_point(device, 0); + if (point.x < 0) continue; + + SDL_SendMouseMotion(0, _this->windows, i, 0, + point.x * screen_w / EGC_GAMEPAD_TOUCH_RES, + point.y * screen_h / EGC_GAMEPAD_TOUCH_RES); + + for (int b = 0; b < MAX_WII_MOUSE_BUTTONS; b++) { + if (controller->btns_pressed & s_mouse_button_map[b].wii) { + SDL_SendMouseButton(0, _this->windows, i, + s_mouse_button_map[b].mouse, true); + } + if (controller->btns_released & s_mouse_button_map[b].wii) { + SDL_SendMouseButton(0, _this->windows, i, + s_mouse_button_map[b].mouse, false); + } + } + } + + if (OGC_prep_draw_cursor(_this)) { + OGC_video_flip(_this, false); + } +} +#endif + +_OGC_Controller *OGC_get_controller(int i) +{ + if (i < 0 || i >= OGC_NumControllers) return NULL; + return &s_controllers[s_controller_indices[i]]; +} + +void OGC_PumpEvents(SDL_VideoDevice *_this) +{ + if (OGC_ResetRequested || OGC_PowerOffRequested) { + SDL_Event ev; + ev.type = SDL_EVENT_QUIT; + SDL_PushEvent(&ev); + if (OGC_PowerOffRequested) { + SYS_ResetSystem(SYS_POWEROFF, 0, 0); + } + } + + egc_handle_events(); + for (int i = 0; i < OGC_NumControllers; i++) { + _OGC_Controller *controller = OGC_get_controller(i); + u32 buttons = egc_input_device_read_buttons(controller->egc_device); + controller->btns_held = controller->btns_prev & buttons; + controller->btns_released = controller->btns_prev & ~buttons; + controller->btns_pressed = buttons & controller->btns_prev; + controller->btns_prev = buttons; + } + +#ifdef __wii__ + pump_ir_events(_this); + OGC_PumpKeyboardEvents(_this); +#endif +} + +void OGC_device_added_cb(egc_input_device_t *device, void *userdata) +{ + _OGC_Controller *controller; + + int free_index = -1; + for (int i = 0; i < OGC_MAX_CONTROLLERS; i++) { + controller = &s_controllers[i]; + if (controller->egc_device == NULL) { + free_index = i; + break; + } + } + if (free_index < 0) return; + + memset(controller, 0, sizeof(*controller)); + controller->egc_device = device; + controller->instance_id = s_next_instance_id++; + s_controller_indices[OGC_NumControllers++] = free_index; + +#ifdef __wii__ + egc_bt_stop_scan(); +#endif + + if (s_joystick_added_cb) { + s_joystick_added_cb(controller); + } +} + +void OGC_device_removed_cb(egc_input_device_t *device, void *userdata) +{ + _OGC_Controller *controller = NULL; + int found_index = -1; + for (int i = 0; i < OGC_NumControllers; i++) { + controller = OGC_get_controller(i); + if (controller->egc_device == device) { + found_index = i; + controller->egc_device = NULL; + break; + } + } + if (found_index < 0) return; + + if (s_joystick_removed_cb) { + s_joystick_removed_cb(controller); + } + + OGC_NumControllers--; + /* Move back all later indices by one position */ + for (int i = found_index; i < OGC_NumControllers; i++) { + s_controller_indices[i] = s_controller_indices[i + 1]; + } + +#ifdef __wii__ + if (OGC_NumControllers == 0) { + egc_bt_start_scan(); + } +#endif + +} + +void OGC_register_joystick_callbacks(_OGC_ControllerCb added_cb, + _OGC_ControllerCb removed_cb) +{ + s_joystick_added_cb = added_cb; + s_joystick_removed_cb = removed_cb; +} + +#endif /* SDL_VIDEO_DRIVER_OGC */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/video/ogc/SDL_ogcevents_c.h b/src/video/ogc/SDL_ogcevents_c.h new file mode 100644 index 0000000000000..b11c746a90cec --- /dev/null +++ b/src/video/ogc/SDL_ogcevents_c.h @@ -0,0 +1,66 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2023 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_ogcevents_c_h_ +#define SDL_ogcevents_c_h_ + +#include "SDL_internal.h" + +#include "SDL_ogcvideo.h" +#include "../../joystick/SDL_sysjoystick.h" + +#include + +#ifdef __wii__ +/* 4 GameCube controllers + 4 Wiimotes + possibly 4 separate expansions. In + * theory there could be even more controller, connected via USB and bluetooth, + * but let's be realistic :-) + */ +#define OGC_MAX_CONTROLLERS 12 +#else +#define OGC_MAX_CONTROLLERS 4 +#endif + +typedef struct { + egc_input_device_t *egc_device; + SDL_JoystickID instance_id; + u32 btns_prev; + u32 btns_pressed; + u32 btns_held; + u32 btns_released; +} _OGC_Controller; + +extern int OGC_NumControllers; + +extern bool OGC_ResetRequested; +extern bool OGC_PowerOffRequested; + +extern void OGC_PumpEvents(SDL_VideoDevice *_this); + +void OGC_device_added_cb(egc_input_device_t *device, void *userdata); +void OGC_device_removed_cb(egc_input_device_t *device, void *userdata); +_OGC_Controller *OGC_get_controller(int i); + +typedef void (*_OGC_ControllerCb)(_OGC_Controller *controller); +void OGC_register_joystick_callbacks(_OGC_ControllerCb added_cb, + _OGC_ControllerCb removed_cb); + +#endif /* SDL_ogcevents_c_h_ */ diff --git a/src/video/ogc/SDL_ogcframebuffer.c b/src/video/ogc/SDL_ogcframebuffer.c new file mode 100644 index 0000000000000..74ab0c078866b --- /dev/null +++ b/src/video/ogc/SDL_ogcframebuffer.c @@ -0,0 +1,127 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "../../SDL_internal.h" + +#ifdef SDL_VIDEO_DRIVER_OGC + +#include "../SDL_sysvideo.h" +#include "SDL_ogcframebuffer_c.h" +#include "SDL_ogcgxcommon.h" +#include "SDL_ogcpixels.h" +#include "SDL_ogcvideo.h" + +#include +#include +#include +#include +#include + +static void draw_screen_rect(SDL_Window *window) +{ + s16 z = 0; + + GX_Begin(GX_QUADS, GX_VTXFMT0, 4); + GX_Position3s16(0, 0, z); + GX_TexCoord1x8(0); + GX_Position3s16(window->w, 0, z); + GX_TexCoord1x8(1); + GX_Position3s16(window->w, window->h, z); + GX_TexCoord1x8(2); + GX_Position3s16(0, window->h, z); + GX_TexCoord1x8(3); + GX_End(); +} + +static void free_window_data(SDL_Window *window) +{ + SDL_WindowData *windowdata = window->internal; + if (windowdata) { + if (windowdata->pixels) { + SDL_free(windowdata->pixels); + } + if (windowdata->texels) { + free(windowdata->texels); + } + SDL_free(windowdata); + window->internal = NULL; + } +} + +bool SDL_OGC_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, Uint32 *format, void **pixels, int *pitch) +{ + SDL_WindowData *windowdata; + int bytes_per_pixel = 4; + size_t texture_size; + int w, h; + + free_window_data(window); + windowdata = (SDL_WindowData *)SDL_calloc(1, sizeof(SDL_WindowData)); + if (!windowdata) { + SDL_OutOfMemory(); + return false; + } + + w = window->w; + h = window->h; + windowdata->pixels = SDL_malloc(w * h * bytes_per_pixel); + texture_size = GX_GetTexBufferSize(w, h, GX_TF_RGBA8, GX_FALSE, 0); + windowdata->texels = memalign(32, texture_size); + windowdata->surface_format = SDL_PIXELFORMAT_RGBA8888; + window->internal = windowdata; + + *pitch = w * bytes_per_pixel; + *format = windowdata->surface_format; + *pixels = windowdata->pixels; + return true; +} + +bool SDL_OGC_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, const SDL_Rect *rects, int numrects) +{ + SDL_WindowData *windowdata = window->internal; + u32 texture_size; + u8 gx_format; + + gx_format = OGC_texture_format_from_SDL(windowdata->surface_format); + for (int i = 0; i < numrects; i++) { + OGC_pixels_to_texture(windowdata->pixels, windowdata->surface_format, &rects[i], + window->surface->pitch, windowdata->texels, window->w); + } + texture_size = GX_GetTexBufferSize(window->w, window->h, gx_format, + GX_FALSE, 0); + DCStoreRange(windowdata->texels, texture_size); + GX_InvalidateTexAll(); + OGC_load_texture(windowdata->texels, window->w, window->h, gx_format, + SDL_SCALEMODE_NEAREST); + draw_screen_rect(window); + GX_DrawDone(); + + OGC_video_flip(_this, true); + + return true; +} + +void SDL_OGC_DestroyWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Destroying window"); + free_window_data(window); +} + +#endif /* SDL_VIDEO_DRIVER_OGC */ diff --git a/src/video/ogc/SDL_ogcframebuffer_c.h b/src/video/ogc/SDL_ogcframebuffer_c.h new file mode 100644 index 0000000000000..c173bb58eed3e --- /dev/null +++ b/src/video/ogc/SDL_ogcframebuffer_c.h @@ -0,0 +1,31 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_ogcframebuffer_c_h_ +#define SDL_ogcframebuffer_c_h_ + +#include "../../SDL_internal.h" + +extern bool SDL_OGC_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, Uint32 *format, void **pixels, int *pitch); +extern bool SDL_OGC_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, const SDL_Rect *rects, int numrects); +extern void SDL_OGC_DestroyWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window); + +#endif /* SDL_ogcframebuffer_c_h_ */ diff --git a/src/video/ogc/SDL_ogcgl.c b/src/video/ogc/SDL_ogcgl.c new file mode 100644 index 0000000000000..588a9da000376 --- /dev/null +++ b/src/video/ogc/SDL_ogcgl.c @@ -0,0 +1,137 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#if defined(SDL_VIDEO_DRIVER_OGC) && defined(SDL_VIDEO_OPENGL) + +#include "../SDL_sysvideo.h" + +#include "SDL_ogcgl.h" +#include "SDL_ogcvideo.h" + +#include + +typedef struct SDL_GLContextState +{ + SDL_Window *window; + int swap_interval; +} OGC_GL_Context; + +/* Weak symbols for the opengx functions used by SDL, so that the client does + * not need to link to opengx, unless it actually uses OpenGL. */ +void __attribute__((weak)) ogx_initialize(void) +{ + SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, + "%s() called but opengx not used in build!", __func__); +} + +void __attribute__((weak)) ogx_stencil_create(OgxStencilFlags) +{ + SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, + "%s() called but opengx not used in build!", __func__); +} + +void __attribute__((weak)) *ogx_get_proc_address(const char *) +{ + SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, + "%s() called but opengx not used in build!", __func__); + return NULL; +} + +int __attribute__((weak)) ogx_prepare_swap_buffers() +{ + SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, + "%s() called but opengx not used in build!", __func__); + return 0; +} + +bool SDL_OGC_GL_LoadLibrary(SDL_VideoDevice *_this, const char *path) +{ + return true; +} + +SDL_FunctionPointer SDL_OGC_GL_GetProcAddress(SDL_VideoDevice *_this, const char *proc) +{ + return ogx_get_proc_address(proc); +} + +void SDL_OGC_GL_UnloadLibrary(SDL_VideoDevice *_this) +{ + // nothing to do +} + +SDL_GLContext SDL_OGC_GL_CreateContext(SDL_VideoDevice *_this, SDL_Window * window) +{ + OGC_GL_Context *context = SDL_calloc(1, sizeof(*context)); + context->window = window; + context->swap_interval = 1; + ogx_initialize(); + glViewport(0, 0, window->w, window->h); + if (_this->gl_config.stencil_size > 0) { + OgxStencilFlags flags = 0; /* Don't care if Z gets dirty on discarded fragments */ + if (_this->gl_config.stencil_size > 4) flags |= OGX_STENCIL_8BIT; + ogx_stencil_create(flags); + } + return context; +} + +bool SDL_OGC_GL_MakeCurrent(SDL_VideoDevice *_this, SDL_Window * window, SDL_GLContext context) +{ + return true; +} + +bool SDL_OGC_GL_SetSwapInterval(SDL_VideoDevice *_this, int interval) +{ + OGC_GL_Context *context = _this->current_glctx; + context->swap_interval = interval; + return true; +} + +bool SDL_OGC_GL_GetSwapInterval(SDL_VideoDevice *_this, int *interval) +{ + OGC_GL_Context *context = _this->current_glctx; + *interval = context->swap_interval; + return true; +} + +bool SDL_OGC_GL_SwapWindow(SDL_VideoDevice *_this, SDL_Window * window) +{ + OGC_GL_Context *context = _this->current_glctx; + + bool vsync = context->swap_interval == 1; + OGC_video_flip(_this, vsync); + return true; +} + +bool SDL_OGC_GL_DestroyContext(SDL_VideoDevice *_this, SDL_GLContext context) +{ + SDL_free(context); + return true; +} + +void SDL_OGC_GL_DefaultProfileConfig(SDL_VideoDevice *_this, int *mask, int *major, int *minor) +{ + *mask = SDL_GL_CONTEXT_PROFILE_COMPATIBILITY; + *major = 1; + *minor = 1; +} + +#endif /* SDL_VIDEO_DRIVER_OGC */ diff --git a/src/video/ogc/SDL_ogcgl.h b/src/video/ogc/SDL_ogcgl.h new file mode 100644 index 0000000000000..4f5883ec21aec --- /dev/null +++ b/src/video/ogc/SDL_ogcgl.h @@ -0,0 +1,42 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_OGC_gl_h_ +#define SDL_OGC_gl_h_ + +#ifdef SDL_VIDEO_OPENGL + +#include "../SDL_sysvideo.h" + +bool SDL_OGC_GL_LoadLibrary(SDL_VideoDevice *_this, const char *path); +SDL_FunctionPointer SDL_OGC_GL_GetProcAddress(SDL_VideoDevice *_this, const char *proc); +void SDL_OGC_GL_UnloadLibrary(SDL_VideoDevice *_this); +SDL_GLContext SDL_OGC_GL_CreateContext(SDL_VideoDevice *_this, SDL_Window * window); +bool SDL_OGC_GL_MakeCurrent(SDL_VideoDevice *_this, SDL_Window * window, SDL_GLContext context); +bool SDL_OGC_GL_SetSwapInterval(SDL_VideoDevice *_this, int interval); +bool SDL_OGC_GL_GetSwapInterval(SDL_VideoDevice *_this, int *interval); +bool SDL_OGC_GL_SwapWindow(SDL_VideoDevice *_this, SDL_Window * window); +bool SDL_OGC_GL_DestroyContext(SDL_VideoDevice *_this, SDL_GLContext context); +void SDL_OGC_GL_DefaultProfileConfig(SDL_VideoDevice *_this, int *mask, int *major, int *minor); + +#endif /* SDL_VIDEO_OPENGL */ + +#endif /* SDL_OGC_gl_h_ */ diff --git a/src/video/ogc/SDL_ogcgxcommon.c b/src/video/ogc/SDL_ogcgxcommon.c new file mode 100644 index 0000000000000..cd70f7c3cca5e --- /dev/null +++ b/src/video/ogc/SDL_ogcgxcommon.c @@ -0,0 +1,127 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_VIDEO_DRIVER_OGC + +#include "SDL_ogcgxcommon.h" +#include "SDL_ogcpixels.h" + +#include +#include +#include + +static const f32 tex_pos[] __attribute__((aligned(32))) = { + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0, +}; + +void OGC_set_viewport(int x, int y, int w, int h) +{ + Mtx44 proj; + + GX_SetViewport(x, y, w, h, 0, 1); + GX_SetScissor(x, y, w, h); + + // matrix, t, b, l, r, n, f + guOrtho(proj, 0, h, 0, w, 0, 1); + GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC); +} + +void OGC_draw_init(int w, int h) +{ + SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "OGC_draw_init called with %d, %d", w, h); + + GX_ClearVtxDesc(); + GX_SetVtxDesc(GX_VA_POS, GX_DIRECT); + GX_SetVtxDesc(GX_VA_TEX0, GX_INDEX8); + + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_S16, 0); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0); + + GX_SetArray(GX_VA_TEX0, (void *)tex_pos, 2 * sizeof(f32)); + GX_SetNumTexGens(1); + GX_SetNumChans(1); + GX_SetChanCtrl(GX_COLOR0A0, GX_DISABLE, GX_SRC_VTX, GX_SRC_VTX, 0, + GX_DF_NONE, GX_AF_NONE); + + GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); + + GX_SetTevOp(GX_TEVSTAGE0, GX_REPLACE); + GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0); + + OGC_set_viewport(0, 0, w, h); + + GX_InvVtxCache(); // update vertex cache +} + +void OGC_load_texture(void *texels, int w, int h, u8 format, + SDL_ScaleMode scale_mode) +{ + GXTexObj texobj_a, texobj_b; + + if (format == GX_TF_CI8) { + GX_InitTexObjCI(&texobj_a, texels, w, h, GX_TF_CI8, GX_CLAMP, GX_CLAMP, 0, GX_TLUT0); + GX_InitTexObjCI(&texobj_b, texels, w, h, GX_TF_CI8, GX_CLAMP, GX_CLAMP, 0, GX_TLUT1); + GX_LoadTexObj(&texobj_b, GX_TEXMAP1); + + // Setup TEV to combine Red+Green and Blue paletted images + GX_SetTevColor(GX_TEVREG0, (GXColor){ 255, 255, 0, 0 }); + GX_SetTevSwapModeTable(GX_TEV_SWAP1, GX_CH_RED, GX_CH_ALPHA, GX_CH_BLUE, GX_CH_ALPHA); + GX_SetTevSwapModeTable(GX_TEV_SWAP2, GX_CH_ALPHA, GX_CH_ALPHA, GX_CH_BLUE, GX_CH_ALPHA); + // first stage = red and green + GX_SetTevSwapMode(GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP1); + GX_SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_C0, GX_CC_ZERO); + // second stage = add blue (and opaque alpha) + GX_SetTevOp(GX_TEVSTAGE1, GX_BLEND); + GX_SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD0, GX_TEXMAP1, GX_COLORNULL); + GX_SetTevSwapMode(GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP2); + GX_SetTevColorIn(GX_TEVSTAGE1, GX_CC_TEXC, GX_CC_ZERO, GX_CC_ZERO, GX_CC_CPREV); + GX_SetTevAlphaIn(GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_KONST); + + GX_SetNumTevStages(2); + } else { + GX_InitTexObj(&texobj_a, texels, w, h, format, GX_CLAMP, GX_CLAMP, GX_FALSE); + } + + switch (scale_mode) { + case SDL_SCALEMODE_LINEAR: + GX_InitTexObjLOD(&texobj_a, GX_LINEAR, GX_LINEAR, + 0.0f, 0.0f, 0.0f, 0, 0, GX_ANISO_1); + break; + case SDL_SCALEMODE_PIXELART: + GX_InitTexObjLOD(&texobj_a, GX_LIN_MIP_LIN, GX_LINEAR, + 0.0f, 10.0f, 0.0f, 0, GX_ENABLE, GX_ANISO_4); + break; + default: + GX_InitTexObjLOD(&texobj_a, GX_NEAR, GX_NEAR, + 0.0f, 0.0f, 0.0f, 0, 0, GX_ANISO_1); + } + GX_LoadTexObj(&texobj_a, GX_TEXMAP0); // load texture object so its ready to use +} + +#endif /* SDL_VIDEO_DRIVER_OGC */ diff --git a/src/video/ogc/SDL_ogcgxcommon.h b/src/video/ogc/SDL_ogcgxcommon.h new file mode 100644 index 0000000000000..40371c989ef1b --- /dev/null +++ b/src/video/ogc/SDL_ogcgxcommon.h @@ -0,0 +1,35 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_ogcgxcommon_h_ +#define SDL_ogcgxcommon_h_ + +#include + +#define GX_COLOR_AS_U32(c) *((u32*)&c) + +void OGC_draw_init(int w, int h); +void OGC_set_viewport(int x, int y, int w, int h); +void OGC_load_texture(void *texels, int w, int h, u8 gx_format, + SDL_ScaleMode scale_mode); + +#endif /* SDL_ogcgxcommon_h_ */ diff --git a/src/video/ogc/SDL_ogckeyboard.c b/src/video/ogc/SDL_ogckeyboard.c new file mode 100644 index 0000000000000..08b2220cfc84d --- /dev/null +++ b/src/video/ogc/SDL_ogckeyboard.c @@ -0,0 +1,59 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_ogckeyboard.h" +#include "../../events/SDL_keyboard_c.h" + +#if defined(SDL_VIDEO_DRIVER_OGC) && defined(__wii__) +#include +#include + +void OGC_PumpKeyboardEvents(SDL_VideoDevice *_this) { + keyboard_event ke; + + s32 res = KEYBOARD_GetEvent(&ke); + if (res && (ke.type == KEYBOARD_RELEASED || ke.type == KEYBOARD_PRESSED)) { + SDL_SendKeyboardKey(0, 0, ke.keycode, (SDL_Scancode)ke.keycode, ke.type == KEYBOARD_PRESSED); + + if (ke.type == KEYBOARD_PRESSED) { + const Uint16 symbol = ke.symbol; + char utf8[4] = {'\0'}; + + /* ignore private symbols, used by wiikeyboard for special keys */ + if ((symbol >= 0xE000 && symbol <= 0xF8FF) || symbol == 0xFFFF) + return; + + /* convert UCS-2 to UTF-8 */ + if (symbol < 0x80) { + utf8[0] = symbol; + } else if (symbol < 0x800) { + utf8[0] = 0xC0 | (symbol >> 6); + utf8[1] = 0x80 | (symbol & 0x3F); + } else { + utf8[0] = 0xE0 | (symbol >> 12); + utf8[1] = 0x80 | ((symbol >> 6) & 0x3F); + utf8[2] = 0x80 | (symbol & 0x3F); + } + + SDL_SendKeyboardText(utf8); + } + } +} +#endif diff --git a/src/video/ogc/SDL_ogckeyboard.h b/src/video/ogc/SDL_ogckeyboard.h new file mode 100644 index 0000000000000..8ebe1eef4efc8 --- /dev/null +++ b/src/video/ogc/SDL_ogckeyboard.h @@ -0,0 +1,34 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2023 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_OGC_keyboard_h_ +#define SDL_OGC_keyboard_h_ + +#include "../../SDL_internal.h" +#include "SDL_ogcvideo.h" + +#ifdef __wii__ +void OGC_PumpKeyboardEvents(SDL_VideoDevice *_this); +#endif + +#endif /* SDL_OGC_keyboard_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/video/ogc/SDL_ogcmouse.c b/src/video/ogc/SDL_ogcmouse.c new file mode 100644 index 0000000000000..96ad1b51fa994 --- /dev/null +++ b/src/video/ogc/SDL_ogcmouse.c @@ -0,0 +1,420 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#if defined(SDL_VIDEO_DRIVER_OGC) && defined(__wii__) + +#include "SDL_ogccursors.h" +#include "SDL_ogcevents_c.h" +#include "SDL_ogcgxcommon.h" +#include "SDL_ogcmouse.h" +#include "SDL_ogcpixels.h" + +#include "../SDL_sysvideo.h" +#include "../../render/SDL_sysrender.h" + +#include +#include +#include +#include +#include +#include + +typedef struct SDL_CursorData +{ + void *texels; + int hot_x, hot_y; + int w, h; +} OGC_CursorData; + +typedef struct +{ + void *texels; + int16_t x, y; + uint16_t w, h, maxside; +} OGC_CursorBackground; + +static OGC_CursorBackground s_cursor_background; +static int s_draw_counter = 0; +static bool s_extra_draw_enabled = false; +static bool s_2d_viewport_setup = false; + +static void draw_rect(s16 x, s16 y, u16 w, u16 h) +{ + GX_Begin(GX_QUADS, GX_VTXFMT0, 4); + GX_Position2s16(x, y); + GX_TexCoord2u8(0, 0); + GX_Position2s16(x + w, y); + GX_TexCoord2u8(1, 0); + GX_Position2s16(x + w, h + y); + GX_TexCoord2u8(1, 1); + GX_Position2s16(x, h + y); + GX_TexCoord2u8(0, 1); + GX_End(); +} + +static void draw_cursor_rect(OGC_CursorData *curdata) +{ + draw_rect(-curdata->hot_x, -curdata->hot_y, curdata->w, curdata->h); +} + +static void setup_2d_viewport(SDL_VideoDevice *_this) +{ + int screen_w, screen_h; + + if (s_2d_viewport_setup) return; + + screen_w = _this->displays[0]->current_mode->w; + screen_h = _this->displays[0]->current_mode->h; + + OGC_set_viewport(0, 0, screen_w, screen_h); + + GX_ClearVtxDesc(); + GX_SetVtxDesc(GX_VA_POS, GX_DIRECT); + GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_U8, 0); + GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); + + GX_SetTevOp(GX_TEVSTAGE0, GX_REPLACE); + GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0); + GX_SetNumTevStages(1); + GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR); + GX_SetZMode(GX_DISABLE, GX_ALWAYS, GX_FALSE); + GX_SetCullMode(GX_CULL_NONE); + GX_SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0); + + GX_SetNumTexGens(1); + GX_SetCurrentMtx(GX_PNMTX1); + + s_2d_viewport_setup = true; +} + +/* Create a cursor from a surface */ +static SDL_Cursor *OGC_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y) +{ + OGC_CursorData *curdata; + SDL_Cursor *cursor; + u32 texture_size; + SDL_Rect rect; + + SDL_assert(surface->pitch == surface->w * 4); + + cursor = SDL_calloc(1, sizeof(*cursor)); + if (!cursor) { + SDL_OutOfMemory(); + return NULL; + } + + curdata = SDL_calloc(1, sizeof(*curdata)); + if (!curdata) { + SDL_OutOfMemory(); + SDL_free(cursor); + return NULL; + } + + curdata->hot_x = hot_x; + curdata->hot_y = hot_y; + curdata->w = surface->w; + curdata->h = surface->h; + + texture_size = GX_GetTexBufferSize(surface->w, surface->h, GX_TF_RGBA8, + GX_FALSE, 0); + curdata->texels = memalign(32, texture_size); + if (!curdata->texels) { + SDL_OutOfMemory(); + SDL_free(curdata); + SDL_free(cursor); + return NULL; + } + + rect.x = rect.y = 0; + rect.w = surface->w; + rect.h = surface->h; + OGC_pixels_to_texture(surface->pixels, surface->format, &rect, + surface->pitch, curdata->texels, surface->w); + DCStoreRange(curdata->texels, texture_size); + GX_InvalidateTexAll(); + + cursor->internal = curdata; + + return cursor; +} + +SDL_Cursor *OGC_CreateSystemCursor(SDL_SystemCursor id) +{ + const OGC_Cursor *cursor; + SDL_Surface *surface; + SDL_Cursor *c; + + switch (id) { + case SDL_SYSTEM_CURSOR_DEFAULT: + cursor = &OGC_cursor_arrow; + break; + case SDL_SYSTEM_CURSOR_POINTER: + cursor = &OGC_cursor_hand; + break; + default: + SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, + "System cursor %d not implemented", id); + return NULL; + } + surface = + SDL_CreateSurfaceFrom(cursor->width, + cursor->height, + SDL_PIXELFORMAT_RGBA8888, + (void*)cursor->pixel_data, + cursor->width * cursor->bytes_per_pixel); + c = OGC_CreateCursor(surface, cursor->hot_x, cursor->hot_y); + SDL_DestroySurface(surface); + return c; +} + +/* Free a window manager cursor */ +static void OGC_FreeCursor(SDL_Cursor *cursor) +{ + OGC_CursorData *curdata = cursor->internal; + + if (curdata) { + if (curdata->texels) { + free(curdata->texels); + } + SDL_free(curdata); + } + + SDL_free(cursor); +} + +void OGC_InitMouse(SDL_VideoDevice *_this) +{ + SDL_Mouse *mouse = SDL_GetMouse(); + + mouse->CreateCursor = OGC_CreateCursor; + mouse->CreateSystemCursor = OGC_CreateSystemCursor; + mouse->FreeCursor = OGC_FreeCursor; + + SDL_SetDefaultCursor(OGC_CreateSystemCursor(SDL_SYSTEM_CURSOR_POINTER)); +} + +void OGC_QuitMouse(SDL_VideoDevice *_this) +{ +} + +void OGC_draw_cursor(SDL_VideoDevice *_this) +{ + SDL_Mouse *mouse = SDL_GetMouse(); + OGC_CursorData *curdata; + Mtx mv; + int screen_w, screen_h; + float angle = 0.0f; + + s_draw_counter++; + + /* mark the texture as invalid */ + s_cursor_background.x = SHRT_MIN; + + if (!mouse || !mouse->cursor_visible || + !mouse->cur_cursor || !mouse->cur_cursor->internal) { + return; + } + + /* If this is the default cursor, rotate it, and if it's not pointed at the + * screen, hide it */ + if (mouse->cur_cursor == mouse->def_cursor) { + egc_input_device_t *device; + egc_point_t point; + const egc_accelerometer_t *accel; + + SDL_MouseID mouse_id = mouse->num_sources > 0 ? mouse->sources[0].mouseID : 0; + _OGC_Controller *controller = OGC_get_controller(mouse_id); + if (!controller) return; + + device = controller->egc_device; + point = egc_input_device_read_touch_point(device, 0); + if (point.x < 0) return; + accel = egc_input_device_read_accelerometer(device, 0); + angle = atan2f(accel->x, accel->y); + } + + screen_w = _this->displays[0]->current_mode->w; + screen_h = _this->displays[0]->current_mode->h; + + curdata = mouse->cur_cursor->internal; + + if (s_extra_draw_enabled) { + /* Save the are behind the cursor. We could use GX_ReadBoundingBox() to + * figure out which area to save, but that would require calling the + * drawing function once mode. So, let's just take a guess at the area, + * taking into account possible cursor rotation. */ + s16 x, y; + u16 w, h, radius, side; + u32 texture_size; + + /* +1 is for the rounding of x and y */ + radius = MAX(curdata->w, curdata->h) + 1; + x = (s16)mouse->x - radius; + y = (s16)mouse->y - radius; + /* x and y must be multiples of 2 */ + if (x % 2) x--; + if (y % 2) y--; + w = h = side = radius * 2; + if (x < 0) { + w += x; + x = 0; + } else if (x + w > screen_w) { + w = screen_w - x; + } + + if (y < 0) { + h += y; + y = 0; + } else if (y + h > screen_h) { + h = screen_h - y; + } + + /* Make sure all our variables are properly aligned */ + while (side % 4) side++; + while (w % 4) w++; + while (h % 4) h++; + + if (w > 0 && h > 0) { + texture_size = GX_GetTexBufferSize(side, side, GX_TF_RGBA8, + GX_FALSE, 0); + if (!s_cursor_background.texels || side > s_cursor_background.maxside) { + free(s_cursor_background.texels); + s_cursor_background.texels = memalign(32, texture_size); + s_cursor_background.maxside = side; + } + DCInvalidateRange(s_cursor_background.texels, texture_size); + GX_SetTexCopySrc(x, y, w, h); + GX_SetTexCopyDst(w, h, GX_TF_RGBA8, GX_FALSE); + GX_CopyTex(s_cursor_background.texels, GX_FALSE); + s_cursor_background.x = x; + s_cursor_background.y = y; + s_cursor_background.w = w; + s_cursor_background.h = h; + } + } + + OGC_load_texture(curdata->texels, curdata->w, curdata->h, GX_TF_RGBA8, + SDL_SCALEMODE_NEAREST); + + guMtxIdentity(mv); + guMtxScaleApply(mv, mv, screen_w / 640.0f, screen_h / 480.0f, 1.0f); + if (angle != 0.0f) { + Mtx rot; + guMtxRotRad(rot, 'z', angle); + guMtxConcat(mv, rot, mv); + } + guMtxTransApply(mv, mv, mouse->x, mouse->y, 0); + GX_LoadPosMtxImm(mv, GX_PNMTX1); + + setup_2d_viewport(_this); + + draw_cursor_rect(curdata); + GX_DrawDone(); +} + +void OGC_restore_viewport(SDL_VideoDevice *_this) +{ + /* Restore default state for SDL (opengx restores it at every frame, so we + * don't care about it) */ + s_2d_viewport_setup = false; + GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE); + GX_SetCurrentMtx(GX_PNMTX0); + if (_this->windows) { + /* Restore previous viewport for the renderer */ + SDL_Renderer *renderer = SDL_GetRenderer(_this->windows); + if (renderer) { + OGC_set_viewport(renderer->view->viewport.x, renderer->view->viewport.y, + renderer->view->viewport.w, renderer->view->viewport.h); + } + } +} + +bool OGC_prep_draw_cursor(SDL_VideoDevice *_this) +{ + GXTexObj background; + Mtx mv; + static u32 last_draw_ms = 0; + u32 current_time_ms, elapsed_ms; + static int call_counter = 0; + static int last_draw_counter = 0; + + /* Ignore calls when a render target is set or OpenGL is not ready to swap + * the framebuffer */ + SDL_Renderer *renderer = SDL_GetRenderer(_this->windows); + if (renderer && renderer->target) return false; + + if (_this->gl_config.driver_loaded && + ogx_prepare_swap_buffers() < 0) return false; + + /* If this function is called repeatedly during the same frame, we assume + * that this is one of those applications that call SDL_OGC_GL_SwapWindow, + * SDL_UpdateWindowSurface or SDL_RenderPresent only if the video contents + * have actually changed. + * If that's the case, we toggle a flag that makes us redraw the screen + * when the mouse position has changed. + */ + if (!s_extra_draw_enabled) { + if (last_draw_counter != s_draw_counter) { + call_counter = 1; + last_draw_counter = s_draw_counter; + return false; + } + + if (call_counter++ > 10) { + s_extra_draw_enabled = true; + } else { + return false; + } + } + + /* Avoid drawing too often. 30 FPS should be enough */ + current_time_ms = gettime() / TB_TIMER_CLOCK; + elapsed_ms = current_time_ms - last_draw_ms; + if (elapsed_ms < 33) return false; + + /* If we have a texture for the cursor background, restore it; otherwise, + * we shouldn't draw the cursor. */ + if (!s_cursor_background.texels) return false; + + if (s_cursor_background.x != SHRT_MIN) { + setup_2d_viewport(_this); + + GX_PixModeSync(); + GX_InitTexObj(&background, s_cursor_background.texels, + s_cursor_background.w, s_cursor_background.h, + GX_TF_RGBA8, GX_CLAMP, GX_CLAMP, GX_FALSE); + GX_InitTexObjLOD(&background, GX_NEAR, GX_NEAR, + 0.0f, 0.0f, 0.0f, 0, 0, GX_ANISO_1); + GX_LoadTexObj(&background, GX_TEXMAP0); + GX_InvalidateTexAll(); + + guMtxIdentity(mv); + GX_LoadPosMtxImm(mv, GX_PNMTX1); + draw_rect(s_cursor_background.x, s_cursor_background.y, + s_cursor_background.w, s_cursor_background.h); + last_draw_ms = current_time_ms; + } + return true; +} +#endif /* SDL_VIDEO_DRIVER_OGC */ diff --git a/src/video/ogc/SDL_ogcmouse.h b/src/video/ogc/SDL_ogcmouse.h new file mode 100644 index 0000000000000..a9c76eb46621f --- /dev/null +++ b/src/video/ogc/SDL_ogcmouse.h @@ -0,0 +1,35 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_OGC_mouse_h_ +#define SDL_OGC_mouse_h_ + +#include "../SDL_sysvideo.h" +#include "../../events/SDL_mouse_c.h" + +void OGC_InitMouse(SDL_VideoDevice *_this); +void OGC_QuitMouse(SDL_VideoDevice *_this); +void OGC_draw_cursor(SDL_VideoDevice *_this); +void OGC_restore_viewport(SDL_VideoDevice *_this); +bool OGC_prep_draw_cursor(SDL_VideoDevice *_this); +SDL_Cursor *OGC_CreateSystemCursor(SDL_SystemCursor id); + +#endif /* SDL_OGC_mouse_h_ */ diff --git a/src/video/ogc/SDL_ogcpixels.c b/src/video/ogc/SDL_ogcpixels.c new file mode 100644 index 0000000000000..a61d4a517cd43 --- /dev/null +++ b/src/video/ogc/SDL_ogcpixels.c @@ -0,0 +1,353 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include "SDL_ogcpixels.h" + +#include + +#define PIXELS_TO_TEXTURE_32(format_func) \ + static void pixels_to_texture_ ## format_func( \ + void *pixels, const SDL_Rect *rect, int16_t pitch, void *texture, int16_t tex_width) \ + { \ + int16_t tex_pitch = (tex_width + 3) / 4 * 4; \ + for (int row = 0; row < rect->h; row++) { \ + int y = rect->y + row; \ + u32 *src = (u32 *)((u8 *)pixels + pitch * row); \ + for (int col = 0; col < rect->w; col++) { \ + int x = rect->x + col; \ + u32 offset = (((y >> 2) << 4) * tex_pitch) + \ + ((x >> 2) << 6) + (((y % 4 << 2) + x % 4) << 1); \ + set_pixel_to_texture_ ## format_func(texture, offset, *src++); \ + } \ + } \ + } + +#define PIXELS_FROM_TEXTURE_32(format_func) \ + static void pixels_from_texture_ ## format_func( \ + void *pixels, int16_t w, int16_t h, int16_t pitch, void *texture) \ + { \ + int tex_width = (w + 3) / 4 * 4; \ + for (int y = 0; y < h; y++) { \ + u32 *dst = (u32 *)((u8 *)pixels + pitch * y); \ + for (int x = 0; x < w; x++) { \ + u32 offset = (((y >> 2) << 4) * tex_width) + \ + ((x >> 2) << 6) + (((y % 4 << 2) + x % 4) << 1); \ + *dst++ = get_pixel_from_texture_ ## format_func(texture, offset); \ + } \ + } \ + } + +static u8 texture_format_from_SDL(const SDL_PixelFormat format) +{ + switch (format) { + case SDL_PIXELFORMAT_INDEX8: + return GX_TF_CI8; + case SDL_PIXELFORMAT_RGB565: + return GX_TF_RGB565; + case SDL_PIXELFORMAT_RGB24: + case SDL_PIXELFORMAT_RGBA8888: + case SDL_PIXELFORMAT_ARGB8888: + case SDL_PIXELFORMAT_XRGB8888: + return GX_TF_RGBA8; + default: + SDL_LogError(SDL_LOG_CATEGORY_VIDEO, + "(texture_format_from_SDL) Unsupported SDL pixel format %d", + format); + } + return 0xff; // invalid +} + +static inline void set_pixel_to_texture_ARGB(void *texture, u32 offset, u32 color) +{ + *(u16*)(texture + offset) = color >> 16; + *(u16*)(texture + offset + 32) = color; +} + +static inline u32 get_pixel_from_texture_ARGB(void *texture, u32 offset) +{ + return *(u16*)(texture + offset) << 16 | + *(u16*)(texture + offset + 32); +} + +static inline void set_pixel_to_texture_RGBA(void *texture, u32 offset, u32 color) +{ + set_pixel_to_texture_ARGB(texture, offset, (color << 24) | (color >> 8)); +} + +static inline u32 get_pixel_from_texture_32(int x, int y, void *texture, int tex_width) +{ + u8 *tex = texture; + u32 offset; + + offset = (((y >> 2) << 4) * tex_width) + ((x >> 2) << 6) + (((y % 4 << 2) + x % 4) << 1); + + return *(tex + offset) | + *(tex + offset + 1) << 24 | + *(tex + offset + 32) << 16 | + *(tex + offset + 33) << 8; +} + +PIXELS_TO_TEXTURE_32(RGBA) + +static void pixels_RGBA_from_texture(void *pixels, int16_t w, int16_t h, + int16_t pitch, void *texture) +{ + u32 *dst = pixels; + + int tex_width = (w + 3) / 4 * 4; + for (int y = 0; y < h; y++) { + dst = (u32 *)((u8 *)pixels + pitch * y); + for (int x = 0; x < w; x++) { + *dst++ = get_pixel_from_texture_32(x, y, texture, tex_width); + } + } +} + +PIXELS_TO_TEXTURE_32(ARGB) +PIXELS_FROM_TEXTURE_32(ARGB) + +static inline void set_pixel_to_texture_XRGB(void *texture, u32 offset, u32 color) +{ + set_pixel_to_texture_ARGB(texture, offset, 0xff000000 | color); +} + +PIXELS_TO_TEXTURE_32(XRGB) + +static void pixels_XRGB_from_texture(void *pixels, int16_t w, int16_t h, + int16_t pitch, void *texture) +{ + u32 *dst = pixels; + + int tex_width = (w + 3) / 4 * 4; + for (int y = 0; y < h; y++) { + dst = (u32 *)((u8 *)pixels + pitch * y); + for (int x = 0; x < w; x++) { + *dst++ = get_pixel_from_texture_32(x, y, texture, tex_width) >> 8; + } + } +} + +static void pixels_RGB_to_texture(void *pixels, const SDL_Rect *rect, + int16_t pitch, void *texture, int16_t tex_width) +{ + u8 *src = pixels; + + int tex_pitch = (tex_width + 3) / 4 * 4; + for (int row = 0; row < rect->h; row++) { + int y = rect->y + row; + src = (u8 *)pixels + pitch * row; + for (int col = 0; col < rect->w; col++) { + int x = rect->x + col; + u32 offset = (((y >> 2) << 4) * tex_pitch) + + ((x >> 2) << 6) + (((y % 4 << 2) + x % 4) << 1); + u8 r = *src++; + u8 g = *src++; + u8 b = *src++; + set_pixel_to_texture_ARGB(texture, offset, 0xff000000 | r << 16 | g << 8 | b); + } + } +} + +static void pixels_RGB_from_texture(void *pixels, int16_t w, int16_t h, + int16_t pitch, void *texture) +{ + u8 *dst = pixels; + + int tex_width = (w + 3) / 4 * 4; + for (int y = 0; y < h; y++) { + dst = (u8 *)pixels + pitch * y; + for (int x = 0; x < w; x++) { + u32 color = get_pixel_from_texture_32(x, y, texture, tex_width); + *dst++ = color >> 24; + *dst++ = color >> 16; + *dst++ = color >> 8; + } + } +} + +static void pixels_16_to_texture(void *pixels, const SDL_Rect *rect, int16_t pitch, + void *texture, int16_t tex_width) +{ + /* We only support coordinates multiple of 4. While we don't add support + * for arbitrary coordinates (TODO), let's restrict the paint area to the + * full 4x4 cells covered by the rect. In the future we can add code to + * fill up the remaining borders. */ + int x0 = (rect->x + 3) & ~0x3; + int x1 = (rect->x + rect->w) & ~0x3; + int y0 = (rect->y + 3) & ~0x3; + int y1 = (rect->y + rect->h) & ~0x3; + int skipped_bytes_left = (x0 - rect->x) * 2; + int tex_pitch = tex_width * 2; + + + for (int row = 0; row < y1 - y0; row += 4) { + u64 *src1 = pixels + (skipped_bytes_left + row * pitch); + u64 *src2 = (void*)src1 + (pitch * 1); + u64 *src3 = (void*)src1 + (pitch * 2); + u64 *src4 = (void*)src1 + (pitch * 3); + u64 *dst = texture + (x0 * 8 + (y0 + row) * tex_pitch); + for (int col = x0; col < x1; col += 4) { + *dst++ = *src1++; + *dst++ = *src2++; + *dst++ = *src3++; + *dst++ = *src4++; + } + } +} + +static void pixels_16_from_texture(void *pixels, int16_t pitch, int16_t h, + void *texture) +{ + long long int *src = texture; + long long int *dst1 = pixels; + long long int *dst2 = (long long int *)((char *)pixels + (pitch * 1)); + long long int *dst3 = (long long int *)((char *)pixels + (pitch * 2)); + long long int *dst4 = (long long int *)((char *)pixels + (pitch * 3)); + int rowpitch = (pitch >> 3) * 3; + + for (int y = 0; y < h; y += 4) { + for (int x = 0; x < pitch; x += 8) { + *dst1++ = *src++; + *dst2++ = *src++; + *dst3++ = *src++; + *dst4++ = *src++; + } + + dst1 = dst4; + dst2 += rowpitch; + dst3 += rowpitch; + dst4 += rowpitch; + } +} + +static inline void set_pixel_8_to_texture(int x, int y, u8 color, void *texture, int tex_width) +{ + u8 *tex = texture; + u32 offset; + + offset = ((y & ~3) * tex_width) + ((x & ~7) << 2) + ((y & 3) << 3) + (x & 7); + + tex[offset] = color; +} + +static inline u8 get_pixel_8_from_texture(int x, int y, void *texture, int tex_width) +{ + u8 *tex = texture; + u32 offset; + + offset = ((y & ~3) * tex_width) + ((x & ~7) << 2) + ((y & 3) << 3) + (x & 7); + + return tex[offset]; +} + +static void pixels_8_to_texture(void *pixels, int16_t w, int16_t h, + int16_t pitch, void *texture) +{ + u8 *src = pixels; + int tex_width = (w + 7) / 8 * 8; + + for (int y = 0; y < h; y++) { + src = (u8 *)pixels + pitch * y; + for (int x = 0; x < w; x++) { + set_pixel_8_to_texture(x, y, *src++, texture, tex_width); + } + } +} + +static void pixels_8_from_texture(void *pixels, int16_t w, int16_t h, + int16_t pitch, void *texture) +{ + u8 *dst = pixels; + int tex_width = (w + 7) / 8 * 8; + + for (int y = 0; y < h; y++) { + dst = (u8 *)pixels + pitch * y; + for (int x = 0; x < w; x++) { + *dst++ = get_pixel_8_from_texture(x, y, texture, tex_width); + } + } +} + +void OGC_pixels_to_texture(void *pixels, const SDL_PixelFormat format, + const SDL_Rect *rect, int16_t pitch, + void *texture, int16_t tex_width) +{ + switch (format) { + case SDL_PIXELFORMAT_INDEX8: + pixels_8_to_texture(pixels, rect->w, rect->h, pitch, texture); + break; + case SDL_PIXELFORMAT_RGB565: + pixels_16_to_texture(pixels, rect, pitch, texture, tex_width); + break; + case SDL_PIXELFORMAT_RGB24: + pixels_RGB_to_texture(pixels, rect, pitch, texture, tex_width); + break; + case SDL_PIXELFORMAT_RGBA8888: + pixels_to_texture_RGBA(pixels, rect, pitch, texture, tex_width); + break; + case SDL_PIXELFORMAT_ARGB8888: + pixels_to_texture_ARGB(pixels, rect, pitch, texture, tex_width); + break; + case SDL_PIXELFORMAT_XRGB8888: + pixels_to_texture_XRGB(pixels, rect, pitch, texture, tex_width); + break; + default: + SDL_LogError(SDL_LOG_CATEGORY_VIDEO, + "ptt: unsupported SDL pixel format %d", format); + // TODO support more formats + } +} + +void OGC_pixels_from_texture(void *pixels, const SDL_PixelFormat format, + int16_t w, int16_t h, int16_t pitch, + void *texture) +{ + switch (format) { + case SDL_PIXELFORMAT_INDEX8: + pixels_8_from_texture(pixels, w, h, pitch, texture); + break; + case SDL_PIXELFORMAT_RGB565: + pixels_16_from_texture(pixels, pitch, h, texture); + break; + case SDL_PIXELFORMAT_RGB24: + pixels_RGB_from_texture(pixels, w, h, pitch, texture); + break; + case SDL_PIXELFORMAT_RGBA8888: + pixels_RGBA_from_texture(pixels, w, h, pitch, texture); + break; + case SDL_PIXELFORMAT_ARGB8888: + pixels_from_texture_ARGB(pixels, w, h, pitch, texture); + break; + case SDL_PIXELFORMAT_XRGB8888: + pixels_XRGB_from_texture(pixels, w, h, pitch, texture); + break; + default: + SDL_LogError(SDL_LOG_CATEGORY_VIDEO, + "pft: unsupported SDL pixel format %d", format); + // TODO support more formats + } +} + +u8 OGC_texture_format_from_SDL(const SDL_PixelFormat format) +{ + return texture_format_from_SDL(format); +} diff --git a/src/video/ogc/SDL_ogcpixels.h b/src/video/ogc/SDL_ogcpixels.h new file mode 100644 index 0000000000000..5d9d440140218 --- /dev/null +++ b/src/video/ogc/SDL_ogcpixels.h @@ -0,0 +1,37 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_ogcpixels_h_ +#define SDL_ogcpixels_h_ + +#include + +void OGC_pixels_to_texture(void *pixels, SDL_PixelFormat format, + const SDL_Rect *rect, int16_t pitch, + void *texels, int16_t tex_width); +void OGC_pixels_from_texture(void *pixels, SDL_PixelFormat format, + int16_t w, int16_t h, int16_t pitch, + void *texels); + +u8 OGC_texture_format_from_SDL(const SDL_PixelFormat format); + +#endif /* SDL_ogcpixels_h_ */ diff --git a/src/video/ogc/SDL_ogcvideo.c b/src/video/ogc/SDL_ogcvideo.c new file mode 100644 index 0000000000000..c161683914851 --- /dev/null +++ b/src/video/ogc/SDL_ogcvideo.c @@ -0,0 +1,386 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_VIDEO_DRIVER_OGC + +#include "../../events/SDL_events_c.h" +#include "../SDL_pixels_c.h" +#include "../SDL_sysvideo.h" + +#include "SDL_ogcevents_c.h" +#include "SDL_ogcframebuffer_c.h" +#include "SDL_ogcgl.h" +#include "SDL_ogcgxcommon.h" +#include "SDL_ogcmouse.h" +#include "SDL_ogcvideo.h" + +#include +#include +#include +#include +#include +#include + +#include + +#define DEFAULT_FIFO_SIZE 256 * 1024 + +// Inverse of the VI_TVMODE macro +#define VI_FORMAT_FROM_MODE(tvmode) (tvmode >> 2) + +/* A video mode with a 320 width; we'll build it programmatically. */ +static GXRModeObj s_mode320; + +static const GXRModeObj *s_ntsc_modes[] = { + &TVNtsc240Ds, + &TVNtsc480Prog, + NULL, +}; + +static const GXRModeObj *s_mpal_modes[] = { + &TVMpal240Ds, + &TVMpal480Prog, + NULL, +}; + +static const GXRModeObj *s_eurgb60_modes[] = { + &TVEurgb60Hz240Ds, + &TVEurgb60Hz480Prog, + // Also add some PAL modes, since EURGB60 supports them too + &TVPal264Ds, + &TVPal528Prog, + &TVPal576ProgScale, + NULL, +}; + +static const GXRModeObj *s_pal_modes[] = { + &TVPal264Ds, + &TVPal528Prog, + &TVPal576ProgScale, + NULL, +}; + +/* Initialization/Query functions */ +static bool OGC_VideoInit(SDL_VideoDevice *_this); +static void OGC_VideoQuit(SDL_VideoDevice *_this); + +static void init_display_mode(SDL_DisplayMode *mode, const GXRModeObj *vmode) +{ + u32 format = VI_FORMAT_FROM_MODE(vmode->viTVMode); + + /* Use a fake 32-bpp desktop mode */ + SDL_zero(*mode); + mode->format = SDL_PIXELFORMAT_ARGB8888; + mode->w = vmode->fbWidth; + mode->h = vmode->efbHeight; + switch (format) { + case VI_DEBUG: + case VI_NTSC: + case VI_EURGB60: + case VI_MPAL: + mode->refresh_rate = 60; + break; + case VI_PAL: + case VI_DEBUG_PAL: + mode->refresh_rate = 50; + break; + } + mode->internal = (SDL_DisplayModeData *)vmode; +} + +static void add_supported_modes(SDL_VideoDisplay *display, u32 tv_format) +{ + const GXRModeObj **gx_modes; + SDL_DisplayMode mode; + + switch (tv_format) { + case VI_DEBUG: + case VI_NTSC: + gx_modes = s_ntsc_modes; + break; + case VI_MPAL: + gx_modes = s_mpal_modes; + break; + case VI_EURGB60: + gx_modes = s_eurgb60_modes; + break; + case VI_PAL: + case VI_DEBUG_PAL: + gx_modes = s_pal_modes; + break; + default: + return; + } + + /* All libogc video modes are 640 pixel wide, even the 240p ones. While + * this can be useful for some applications, others might prefer a video + * mode with less elongated pixels, such as 320x240. Therefore, let's + * create one: we take the first video mode in the array (which has always + * a height of approximately 240p) and we use it as template to build the + * "mode320": we just set the fbWidth field to 320: the VI interface will + * take care of the horizontal scale for us. */ + memcpy(&s_mode320, gx_modes[0], sizeof(s_mode320)); + s_mode320.fbWidth = 320; + init_display_mode(&mode, &s_mode320); + SDL_AddFullscreenDisplayMode(display, &mode); + + /* Now add all the "standard" modes from libogc */ + while (*gx_modes) { + init_display_mode(&mode, *gx_modes); + SDL_AddFullscreenDisplayMode(display, &mode); + gx_modes++; + } +} + +static void setup_video_mode(SDL_VideoDevice *_this, GXRModeObj *vmode) +{ + SDL_VideoData *videodata = _this->internal; + + VIDEO_SetBlack(true); + VIDEO_Configure(vmode); + + /* Allocate the XFB */ + videodata->xfb[0] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(vmode)); + videodata->xfb[1] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(vmode)); + + VIDEO_ClearFrameBuffer(vmode, videodata->xfb[0], COLOR_BLACK); + VIDEO_SetNextFramebuffer(videodata->xfb[0]); + VIDEO_SetBlack(false); + VIDEO_Flush(); + + VIDEO_WaitVSync(); + if (vmode->viTVMode & VI_NON_INTERLACE) VIDEO_WaitVSync(); + + /* Setup the EFB -> XFB copy operation */ + GX_SetDispCopySrc(0, 0, vmode->fbWidth, vmode->efbHeight); + GX_SetDispCopyDst(vmode->fbWidth, vmode->xfbHeight); + GX_SetDispCopyYScale((f32)vmode->xfbHeight / (f32)vmode->efbHeight); + GX_SetCopyFilter(vmode->aa, vmode->sample_pattern, GX_FALSE, vmode->vfilter); + GX_SetFieldMode(vmode->field_rendering, + ((vmode->viHeight == 2 * vmode->xfbHeight) ? GX_ENABLE : GX_DISABLE)); + + OGC_draw_init(vmode->fbWidth, vmode->efbHeight); +} + +static bool OGC_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, + SDL_DisplayMode *mode) +{ + SDL_VideoData *videodata = _this->internal; + /* The GX video mode is stored in the internal pointer */ + GXRModeObj *vmode = (GXRModeObj *)mode->internal; + + if (videodata->xfb[0]) + free(MEM_K1_TO_K0(videodata->xfb[0])); + if (videodata->xfb[1]) + free(MEM_K1_TO_K0(videodata->xfb[1])); + + setup_video_mode(_this, vmode); + return true; +} + +static void OGC_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_SetMouseFocus(window); + SDL_SetKeyboardFocus(window); +} + +static bool OGC_ShowMessageBox(SDL_VideoDevice *_this, const SDL_MessageBoxData *messageboxdata, + int *buttonid) +{ + /* Unimplemented, but at least show the message in the log */ + SDL_SetError("ShowMessageBox unimplemented: \"%s\", \"%s\"", + messageboxdata->title, messageboxdata->message); + return true; +} + +/* OGC driver bootstrap functions */ + +static void OGC_DeleteDevice(SDL_VideoDevice *device) +{ + SDL_free(device->internal); + SDL_free(device); +} + +static SDL_VideoDevice *OGC_CreateDevice(void) +{ + SDL_VideoDevice *device; + SDL_VideoData *videodata; + + /* Initialize all variables that we clean on shutdown */ + device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); + if (!device) { + SDL_OutOfMemory(); + return NULL; + } + + videodata = (SDL_VideoData *)SDL_calloc(1, sizeof(SDL_VideoData)); + if (!videodata) { + SDL_OutOfMemory(); + SDL_free(device); + return NULL; + } + + device->internal = videodata; + + /* Set the function pointers */ + device->VideoInit = OGC_VideoInit; + device->VideoQuit = OGC_VideoQuit; + device->SetDisplayMode = OGC_SetDisplayMode; + device->PumpEvents = OGC_PumpEvents; + device->ShowWindow = OGC_ShowWindow; + device->CreateWindowFramebuffer = SDL_OGC_CreateWindowFramebuffer; + device->UpdateWindowFramebuffer = SDL_OGC_UpdateWindowFramebuffer; + device->DestroyWindowFramebuffer = SDL_OGC_DestroyWindowFramebuffer; + device->ShowMessageBox = OGC_ShowMessageBox; + +#ifdef SDL_VIDEO_OPENGL + device->GL_LoadLibrary = SDL_OGC_GL_LoadLibrary; + device->GL_GetProcAddress = SDL_OGC_GL_GetProcAddress; + device->GL_UnloadLibrary = SDL_OGC_GL_UnloadLibrary; + device->GL_CreateContext = SDL_OGC_GL_CreateContext; + device->GL_MakeCurrent = SDL_OGC_GL_MakeCurrent; + device->GL_SetSwapInterval = SDL_OGC_GL_SetSwapInterval; + device->GL_GetSwapInterval = SDL_OGC_GL_GetSwapInterval; + device->GL_SwapWindow = SDL_OGC_GL_SwapWindow; + device->GL_DestroyContext = SDL_OGC_GL_DestroyContext; + device->GL_DefaultProfileConfig = SDL_OGC_GL_DefaultProfileConfig; +#endif + + device->free = OGC_DeleteDevice; + + return device; +} + +VideoBootStrap OGC_bootstrap = { + "ogc", "ogc video driver", + OGC_CreateDevice, NULL, false +}; + +bool OGC_VideoInit(SDL_VideoDevice *_this) +{ + SDL_VideoData *videodata = _this->internal; + SDL_DisplayMode mode; + GXRModeObj *vmode; + static const GXColor background = { 0, 0, 0, 255 }; + + VIDEO_Init(); + + vmode = VIDEO_GetPreferredMode(NULL); + + videodata->gp_fifo = memalign(32, DEFAULT_FIFO_SIZE); + memset(videodata->gp_fifo, 0, DEFAULT_FIFO_SIZE); + GX_Init(videodata->gp_fifo, DEFAULT_FIFO_SIZE); + + setup_video_mode(_this, vmode); + GX_SetCopyClear(background, GX_MAX_Z24); + + GX_SetPixelFmt(GX_PF_RGB8_Z24, GX_ZC_LINEAR); + GX_SetCullMode(GX_CULL_NONE); + GX_SetBlendMode(GX_BM_NONE, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR); + + GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE); + + GX_Flush(); + + init_display_mode(&mode, vmode); + if (SDL_AddBasicVideoDisplay(&mode) < 0) { + return false; + } + + SDL_AddFullscreenDisplayMode(_this->displays[0], &mode); + add_supported_modes(_this->displays[0], VI_FORMAT_FROM_MODE(vmode->viTVMode)); + + videodata->vmode = vmode; + + egc_initialize(OGC_device_added_cb, OGC_device_removed_cb, NULL); + +#ifdef __wii__ + /* Page mode does not slow BT down, so we keep it on all the time; but + * scanning has a noticeable effect on BT performance, so we leave it on + * only until we get the first controller connected. */ + egc_bt_enter_page_mode(); + egc_bt_start_scan(); + + OGC_InitMouse(_this); + /* OGC_PumpEvents reads the keyboard, so we need to initialize it here */ + KEYBOARD_Init(NULL); +#endif + return true; +} + +void OGC_VideoQuit(SDL_VideoDevice *_this) +{ + SDL_VideoData *videodata = _this->internal; + SDL_VideoDisplay *display; + +#ifdef __wii__ + OGC_QuitMouse(_this); +#endif + + SDL_free(videodata->gp_fifo); + if (videodata->xfb[0]) + free(MEM_K1_TO_K0(videodata->xfb[0])); + if (videodata->xfb[1]) + free(MEM_K1_TO_K0(videodata->xfb[1])); + + /* During shutdown, SDL_ResetDisplayModes() will be called and will invoke + * SDL_free() on internal. Nullify the pointers in order to avoid a + * crash, since we didn't actually allocate this memory. */ + display = _this->displays[0]; + for (int i = display->num_fullscreen_modes; i--;) { + display->fullscreen_modes[i].internal = NULL; + } + display->desktop_mode.internal = NULL; +} + +void *OGC_video_get_xfb(SDL_VideoDevice *_this) +{ + SDL_VideoData *videodata = _this->internal; + return videodata->xfb[videodata->fb_index]; +} + +void OGC_video_flip(SDL_VideoDevice *_this, bool vsync) +{ + SDL_VideoData *videodata = _this->internal; + void *xfb = OGC_video_get_xfb(_this); + + if (_this->gl_config.driver_loaded && + ogx_prepare_swap_buffers() < 0) return; + +#ifdef __wii__ + OGC_draw_cursor(_this); + OGC_restore_viewport(_this); +#endif + GX_CopyDisp(xfb, GX_FALSE); + GX_DrawDone(); + GX_Flush(); + + VIDEO_SetNextFramebuffer(xfb); + VIDEO_Flush(); + if (vsync) { + VIDEO_WaitVSync(); + } + + videodata->fb_index ^= 1; +} + +#endif /* SDL_VIDEO_DRIVER_OGC */ diff --git a/src/video/ogc/SDL_ogcvideo.h b/src/video/ogc/SDL_ogcvideo.h new file mode 100644 index 0000000000000..8fec5169cf06e --- /dev/null +++ b/src/video/ogc/SDL_ogcvideo.h @@ -0,0 +1,50 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2023 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "../../SDL_internal.h" + +#ifndef SDL_ogcvideo_h_ +#define SDL_ogcvideo_h_ + +#include "../SDL_sysvideo.h" + +#include + +typedef struct SDL_VideoData +{ + GXRModeObj *vmode; + u8 *gp_fifo; + void *xfb[2]; + u8 fb_index; +} SDL_VideoData; + +typedef struct SDL_WindowData +{ + void *pixels; + u8 *texels; + SDL_PixelFormat surface_format; +} SDL_WindowData; + +void *OGC_video_get_xfb(SDL_VideoDevice *device); +void OGC_video_flip(SDL_VideoDevice *device, bool vsync); + +#endif /* SDL_ogcvideo_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ From f841ed0c0b669690f1e482a3d82be3888fb176cd Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Tue, 11 Aug 2026 22:46:37 +0300 Subject: [PATCH 06/14] ogc: fix detection of absolute paths We should consider absolute even paths like sd:/ and usb:/ --- src/storage/generic/SDL_genericstorage.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/storage/generic/SDL_genericstorage.c b/src/storage/generic/SDL_genericstorage.c index d6de12883feae..24c3a1472e36c 100644 --- a/src/storage/generic/SDL_genericstorage.c +++ b/src/storage/generic/SDL_genericstorage.c @@ -359,6 +359,8 @@ SDL_Storage *GENERIC_OpenFileStorage(const char *path) is_absolute = (ch == '/') || // some sort of absolute Unix-style path. (ch == '\\') || // some sort of absolute Windows-style path. (((ch >= 'A') && (ch <= 'Z')) && (path[1] == ':') && ((path[2] == '\\') || (path[2] == '/'))); // an absolute path with a drive letter. +#elif defined(SDL_PLATFORM_OGC) + is_absolute = (path[0] == '/') || (strchr(path, ':') != NULL); #else is_absolute = (path[0] == '/'); // some sort of absolute Unix-style path. #endif From ba1c5a34a29b08253e0e0fc88f48521e0f7ddfc5 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Wed, 12 Aug 2026 22:26:17 +0300 Subject: [PATCH 07/14] ogc: don't restore viewport if renderer inactive --- src/video/ogc/SDL_ogcmouse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video/ogc/SDL_ogcmouse.c b/src/video/ogc/SDL_ogcmouse.c index 96ad1b51fa994..873f6a6f31927 100644 --- a/src/video/ogc/SDL_ogcmouse.c +++ b/src/video/ogc/SDL_ogcmouse.c @@ -343,7 +343,7 @@ void OGC_restore_viewport(SDL_VideoDevice *_this) if (_this->windows) { /* Restore previous viewport for the renderer */ SDL_Renderer *renderer = SDL_GetRenderer(_this->windows); - if (renderer) { + if (renderer && renderer->view->viewport.w > 0 && renderer->view->viewport.h > 0) { OGC_set_viewport(renderer->view->viewport.x, renderer->view->viewport.y, renderer->view->viewport.w, renderer->view->viewport.h); } From 69626efa9b169bdb0e24b8d526e7250c72498dff Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Thu, 13 Aug 2026 18:18:02 +0300 Subject: [PATCH 08/14] ogc: fix framebuffer drawing The draw_screen_rect() function was assuming that the GX vertex format was left the same since the initialization, but that's not the case if the mouse gets drawn (or if GL calls happen in between frames). This issue appears only when directly drawing to the framebuffer, which is something games rarely do; however, the "software" renderer in SDL operates on the FB, therefore the problem can be currently seen until we implement the render backend in SDL3. The render backend in SDL2 directly draws using GX functions, so it's not affected by this issue (but it might be worth fixing it there, too). --- src/video/ogc/SDL_ogcframebuffer.c | 19 ++++++------- src/video/ogc/SDL_ogcgxcommon.c | 45 ++++++++++++++---------------- src/video/ogc/SDL_ogcgxcommon.h | 1 + src/video/ogc/SDL_ogcmouse.c | 16 ++--------- 4 files changed, 33 insertions(+), 48 deletions(-) diff --git a/src/video/ogc/SDL_ogcframebuffer.c b/src/video/ogc/SDL_ogcframebuffer.c index 74ab0c078866b..c2cb9553f594f 100644 --- a/src/video/ogc/SDL_ogcframebuffer.c +++ b/src/video/ogc/SDL_ogcframebuffer.c @@ -36,17 +36,15 @@ static void draw_screen_rect(SDL_Window *window) { - s16 z = 0; - GX_Begin(GX_QUADS, GX_VTXFMT0, 4); - GX_Position3s16(0, 0, z); - GX_TexCoord1x8(0); - GX_Position3s16(window->w, 0, z); - GX_TexCoord1x8(1); - GX_Position3s16(window->w, window->h, z); - GX_TexCoord1x8(2); - GX_Position3s16(0, window->h, z); - GX_TexCoord1x8(3); + GX_Position2s16(0, 0); + GX_TexCoord2u8(0, 0); + GX_Position2s16(window->w, 0); + GX_TexCoord2u8(1, 0); + GX_Position2s16(window->w, window->h); + GX_TexCoord2u8(1, 1); + GX_Position2s16(0, window->h); + GX_TexCoord2u8(0, 1); GX_End(); } @@ -110,6 +108,7 @@ bool SDL_OGC_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, GX_InvalidateTexAll(); OGC_load_texture(windowdata->texels, window->w, window->h, gx_format, SDL_SCALEMODE_NEAREST); + OGC_setup_2d_drawing(); draw_screen_rect(window); GX_DrawDone(); diff --git a/src/video/ogc/SDL_ogcgxcommon.c b/src/video/ogc/SDL_ogcgxcommon.c index cd70f7c3cca5e..1a82e5fda6354 100644 --- a/src/video/ogc/SDL_ogcgxcommon.c +++ b/src/video/ogc/SDL_ogcgxcommon.c @@ -29,17 +29,6 @@ #include #include -static const f32 tex_pos[] __attribute__((aligned(32))) = { - 0.0, - 0.0, - 1.0, - 0.0, - 1.0, - 1.0, - 0.0, - 1.0, -}; - void OGC_set_viewport(int x, int y, int w, int h) { Mtx44 proj; @@ -52,27 +41,35 @@ void OGC_set_viewport(int x, int y, int w, int h) GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC); } -void OGC_draw_init(int w, int h) +void OGC_setup_2d_drawing() { - SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "OGC_draw_init called with %d, %d", w, h); - GX_ClearVtxDesc(); GX_SetVtxDesc(GX_VA_POS, GX_DIRECT); - GX_SetVtxDesc(GX_VA_TEX0, GX_INDEX8); - - GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_S16, 0); - GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0); - - GX_SetArray(GX_VA_TEX0, (void *)tex_pos, 2 * sizeof(f32)); - GX_SetNumTexGens(1); - GX_SetNumChans(1); - GX_SetChanCtrl(GX_COLOR0A0, GX_DISABLE, GX_SRC_VTX, GX_SRC_VTX, 0, - GX_DF_NONE, GX_AF_NONE); + GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_U8, 0); GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); + GX_SetNumTexGens(1); GX_SetTevOp(GX_TEVSTAGE0, GX_REPLACE); GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0); + GX_SetNumTevStages(1); + + GX_SetZMode(GX_DISABLE, GX_ALWAYS, GX_FALSE); + GX_SetCullMode(GX_CULL_NONE); + GX_SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0); +} + +void OGC_draw_init(int w, int h) +{ + SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "OGC_draw_init called with %d, %d", w, h); + + OGC_setup_2d_drawing(); + + GX_SetNumChans(1); + GX_SetChanCtrl(GX_COLOR0A0, GX_DISABLE, GX_SRC_VTX, GX_SRC_VTX, 0, + GX_DF_NONE, GX_AF_NONE); OGC_set_viewport(0, 0, w, h); diff --git a/src/video/ogc/SDL_ogcgxcommon.h b/src/video/ogc/SDL_ogcgxcommon.h index 40371c989ef1b..66d5935b56bf8 100644 --- a/src/video/ogc/SDL_ogcgxcommon.h +++ b/src/video/ogc/SDL_ogcgxcommon.h @@ -29,6 +29,7 @@ void OGC_draw_init(int w, int h); void OGC_set_viewport(int x, int y, int w, int h); +void OGC_setup_2d_drawing(); void OGC_load_texture(void *texels, int w, int h, u8 gx_format, SDL_ScaleMode scale_mode); diff --git a/src/video/ogc/SDL_ogcmouse.c b/src/video/ogc/SDL_ogcmouse.c index 873f6a6f31927..c6953c5c04804 100644 --- a/src/video/ogc/SDL_ogcmouse.c +++ b/src/video/ogc/SDL_ogcmouse.c @@ -87,22 +87,10 @@ static void setup_2d_viewport(SDL_VideoDevice *_this) OGC_set_viewport(0, 0, screen_w, screen_h); - GX_ClearVtxDesc(); - GX_SetVtxDesc(GX_VA_POS, GX_DIRECT); - GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT); - GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0); - GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_U8, 0); - GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); - - GX_SetTevOp(GX_TEVSTAGE0, GX_REPLACE); - GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0); - GX_SetNumTevStages(1); + OGC_setup_2d_drawing(); + GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR); - GX_SetZMode(GX_DISABLE, GX_ALWAYS, GX_FALSE); - GX_SetCullMode(GX_CULL_NONE); - GX_SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0); - GX_SetNumTexGens(1); GX_SetCurrentMtx(GX_PNMTX1); s_2d_viewport_setup = true; From f684b6d58723c42f90d38f1256a4fa51364aebe1 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Thu, 13 Aug 2026 22:23:22 +0300 Subject: [PATCH 09/14] ogc: add joystick support --- CMakeLists.txt | 6 + include/build_config/SDL_build_config.h.cmake | 1 + src/joystick/SDL_joystick.c | 3 + src/joystick/SDL_sysjoystick.h | 1 + src/joystick/ogc/SDL_sysjoystick.c | 659 ++++++++++++++++++ 5 files changed, 670 insertions(+) create mode 100644 src/joystick/ogc/SDL_sysjoystick.c diff --git a/CMakeLists.txt b/CMakeLists.txt index e2c47bdcc741f..b7be4a5908136 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3347,6 +3347,12 @@ elseif(OGC) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/ogc/*.c") set(HAVE_SDL_FILESYSTEM TRUE) + if(SDL_JOYSTICK) + set(SDL_JOYSTICK_OGC 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/ogc/*.c") + set(HAVE_SDL_JOYSTICK TRUE) + endif() + set(SDL_TIME_OGC 1) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/ogc/*.c") set(HAVE_SDL_TIME TRUE) diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake index 2b6ac32d04150..02625e514caee 100644 --- a/include/build_config/SDL_build_config.h.cmake +++ b/include/build_config/SDL_build_config.h.cmake @@ -311,6 +311,7 @@ #cmakedefine SDL_JOYSTICK_LINUX 1 #cmakedefine SDL_JOYSTICK_MFI 1 #cmakedefine SDL_JOYSTICK_N3DS 1 +#cmakedefine SDL_JOYSTICK_OGC 1 #cmakedefine SDL_JOYSTICK_PS2 1 #cmakedefine SDL_JOYSTICK_PSP 1 #cmakedefine SDL_JOYSTICK_RAWINPUT 1 diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c index 77539c9bd1432..e4b5e2f9e5dc2 100644 --- a/src/joystick/SDL_joystick.c +++ b/src/joystick/SDL_joystick.c @@ -106,6 +106,9 @@ static SDL_JoystickDriver *SDL_joystick_drivers[] = { #ifdef SDL_JOYSTICK_N3DS &SDL_N3DS_JoystickDriver, #endif +#ifdef SDL_JOYSTICK_OGC + &SDL_OGC_JoystickDriver, +#endif #if defined(SDL_JOYSTICK_DUMMY) || defined(SDL_JOYSTICK_DISABLED) &SDL_DUMMY_JoystickDriver #endif diff --git a/src/joystick/SDL_sysjoystick.h b/src/joystick/SDL_sysjoystick.h index 47ce42cdfaea5..c8f8ecb6a52ae 100644 --- a/src/joystick/SDL_sysjoystick.h +++ b/src/joystick/SDL_sysjoystick.h @@ -262,6 +262,7 @@ extern SDL_JoystickDriver SDL_PS2_JoystickDriver; extern SDL_JoystickDriver SDL_PSP_JoystickDriver; extern SDL_JoystickDriver SDL_VITA_JoystickDriver; extern SDL_JoystickDriver SDL_N3DS_JoystickDriver; +extern SDL_JoystickDriver SDL_OGC_JoystickDriver; extern SDL_JoystickDriver SDL_GAMEINPUT_JoystickDriver; // Ends C function definitions when using C++ diff --git a/src/joystick/ogc/SDL_sysjoystick.c b/src/joystick/ogc/SDL_sysjoystick.c new file mode 100644 index 0000000000000..15d485dcc5b51 --- /dev/null +++ b/src/joystick/ogc/SDL_sysjoystick.c @@ -0,0 +1,659 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#ifdef SDL_JOYSTICK_OGC + +#include "../SDL_joystick_c.h" +#include "../SDL_sysjoystick.h" +#include "../usb_ids.h" +#include "../../SDL_hints_c.h" +#include "../../video/ogc/SDL_ogcevents_c.h" + +#include +#include +#include +#include +#include + +#define MAX_RUMBLE 8 + +/* The private structure used to keep track of a joystick */ +typedef struct joystick_hwdata +{ + _OGC_Controller *controller; +#ifdef __wii__ + bool rotated; /* Wiimote rotated sideways */ + EgcWiimoteExpType expansion; +#endif + char sensors_disabled; + /* This must be big enough for MAX_RUMBLE */ + char rumble_intensity; + u16 rumble_loop; + u32 prev_buttons; +} joystick_hwdata; + +static char joy_name[128]; + +static int print_controller_name(char *buffer, size_t size, + _OGC_Controller *controller); + +#define DPAD_MASK \ + ((1 << EGC_GAMEPAD_BUTTON_DPAD_UP) | \ + (1 << EGC_GAMEPAD_BUTTON_DPAD_DOWN) | \ + (1 << EGC_GAMEPAD_BUTTON_DPAD_LEFT) | \ + (1 << EGC_GAMEPAD_BUTTON_DPAD_RIGHT)) + +#ifdef __wii__ +static bool s_wiimote_sideways = false; + +static bool is_wiimote(egc_input_device_t *device) +{ + const egc_device_description_t *desc = device->desc; + return desc->vendor_id == USB_VENDOR_NINTENDO && + desc->product_id == USB_PRODUCT_NINTENDO_WII_REMOTE; +} + +/* Return EGC_WIIMOTE_EXP_NONE if this is not a wiimote or if no expansion + * is connected (the Wiimote+ also count as no expansion, since it's not + * usable as a separate controller). */ +static EgcWiimoteExpType get_wiimote_expansion(egc_input_device_t *device) +{ + return is_wiimote(device) ? + egc_driver_wiimote_get_exp_type(device) : EGC_WIIMOTE_EXP_NONE; +} + +static const char *expansion_name(EgcWiimoteExpType expansion) +{ + switch (expansion) { + case EGC_WIIMOTE_EXP_MOTION_PLUS: + return "Motion+"; + case EGC_WIIMOTE_EXP_NUNCHUCK: + return "Nunchuk"; + case EGC_WIIMOTE_EXP_CLASSIC: + case EGC_WIIMOTE_EXP_CLASSIC_PRO: + return "Classic"; + case EGC_WIIMOTE_EXP_GUITAR_HERO_3: + return "Guitar Hero 3"; + case EGC_WIIMOTE_EXP_BALANCE_BOARD: + return "Balance board"; + default: + return "Unknown"; + } +} + +const char *connection_name(egc_input_device_t *device) +{ + switch (device->connection) { + case EGC_CONNECTION_USB: + return "Usb"; + case EGC_CONNECTION_BT: + return "Bt"; + default: + return ""; + } +} + +static void handle_accelerometer(Uint64 timestamp, + SDL_Joystick *joystick, + SDL_SensorType type, + int start_axis, + const egc_accelerometer_t *accel, + bool rotated) +{ + float values[3]; + egc_accelerometer_t a = *accel; + if (rotated) { + a.x = accel->z; + a.z = -accel->x; + } + + values[0] = a.x * SDL_STANDARD_GRAVITY / EGC_ACCELEROMETER_RES_PER_G; + values[1] = a.y * SDL_STANDARD_GRAVITY / EGC_ACCELEROMETER_RES_PER_G; + values[2] = a.z * SDL_STANDARD_GRAVITY / EGC_ACCELEROMETER_RES_PER_G; + SDL_SendJoystickSensor(timestamp, joystick, type, 0, values, 3); +} +#endif + +static inline int count_bits(u32 mask) +{ + return __builtin_popcount(mask); +} + +static int controller_index_by_device_index(int device_index) +{ + return device_index; +} + +static _OGC_Controller *controller_by_device_index(int device_index) +{ + return OGC_get_controller(controller_index_by_device_index(device_index)); +} + +static int print_controller_name(char *buffer, size_t size, + _OGC_Controller *controller) +{ + const egc_device_description_t *desc = controller->egc_device->desc; + if (desc->vendor_id == USB_VENDOR_NINTENDO) { + /* egc's GameCube driver reports this ID for GameCube controllers */ + if (desc->product_id == USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) + return snprintf(buffer, size, "Gamecube"); +#ifdef __wii__ + if (desc->product_id == USB_PRODUCT_NINTENDO_WII_REMOTE) + return snprintf(buffer, size, "Wiimote"); +#endif + } + +#ifdef __wii__ + return snprintf(buffer, size, "%s:%04x:%04x", + connection_name(controller->egc_device), + desc->vendor_id, desc->product_id); +#else + return 0; +#endif +} + +static inline _OGC_Controller *get_controller(SDL_Joystick *joystick) +{ + return joystick->hwdata ? joystick->hwdata->controller : NULL; +} + +static inline egc_input_device_t *get_egc_device(SDL_Joystick *joystick) +{ + _OGC_Controller *controller = get_controller(joystick); + return controller ? controller->egc_device : NULL; +} + +static int device_index_to_instance(int device_index) +{ + _OGC_Controller *controller = controller_by_device_index(device_index); + if (!controller) + return -1; + return controller->instance_id; +} + +static void joystick_added_cb(_OGC_Controller *controller) +{ + SDL_PrivateJoystickAdded(controller->instance_id); +} + +static void joystick_removed_cb(_OGC_Controller *controller) +{ + SDL_PrivateJoystickRemoved(controller->instance_id); +} + +/* Function to scan the system for joysticks. + * This function should return the number of available + * joysticks. Joystick 0 should be the system default joystick. + * It should return -1 on an unrecoverable fatal error. + */ +static bool OGC_JoystickInit(void) +{ +#ifdef __wii__ + /* If this is set, the Wiimote directional keys will be translated. */ + { + const char *sideways_joystick_env = getenv("SDL_WII_JOYSTICK_SIDEWAYS"); + s_wiimote_sideways = + sideways_joystick_env && strcmp(sideways_joystick_env, "1") == 0; + } +#endif + + OGC_register_joystick_callbacks(joystick_added_cb, joystick_removed_cb); + return true; +} + +static int OGC_JoystickGetCount(void) +{ + return OGC_NumControllers; +} + +static void OGC_JoystickDetect(void) +{ +} + +static bool OGC_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + // We don't override any other drivers + return false; +} + +/* Function to get the device-dependent name of a joystick */ +static const char *OGC_JoystickGetDeviceName(int device_index) +{ + _OGC_Controller *controller; + const egc_device_description_t *desc; + int offset = 0; + + controller = OGC_get_controller(device_index); + if (!controller) + return NULL; + + offset += print_controller_name(joy_name, sizeof(joy_name), controller); + offset += snprintf(joy_name + offset, sizeof(joy_name) - offset, + " %d", device_index); +#ifdef __wii__ + desc = controller->egc_device->desc; + if (desc->vendor_id == USB_VENDOR_NINTENDO && + desc->product_id == USB_PRODUCT_NINTENDO_WII_REMOTE) { + EgcWiimoteExpType expansion = + egc_driver_wiimote_get_exp_type(controller->egc_device); + if (expansion != EGC_WIIMOTE_EXP_NONE) { + offset += snprintf(joy_name + offset, sizeof(joy_name) - offset, + " + %s", expansion_name(expansion)); + } + } +#endif + return joy_name; +} + +static const char *OGC_JoystickGetDevicePath(int index) +{ + return NULL; +} + +static int OGC_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; +} + +static int OGC_JoystickGetDevicePlayerIndex(int device_index) +{ + return -1; +} + +static void OGC_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ + int index = controller_index_by_device_index(device_index); + _OGC_Controller *controller = OGC_get_controller(index); + egc_input_device_t *device; + u32 num_leds, leds; + + if (!controller) return; + + device = controller->egc_device; + if (!device) return; + + num_leds = device->desc->num_leds; + if (num_leds <= 1) return; + + if (player_index < num_leds) { + leds = 1 << player_index; + } else { + /* Invert the leds; first, set all leds on: */ + leds = (1 << num_leds) - 1; + /* then remove the led */ + leds ^= (1 << (player_index - num_leds)); + } + + egc_input_device_set_leds(device, leds); +} + +static SDL_GUID OGC_JoystickGetDeviceGUID(int device_index) +{ + int index = controller_index_by_device_index(device_index); + _OGC_Controller *controller = OGC_get_controller(index); + const egc_device_description_t *desc; + Uint16 bus, product, version; + Uint8 driver_signature, driver_data; + const char *name; + + desc = controller->egc_device->desc; + product = desc->product_id; +#ifdef __wii__ + /* Return a different product ID depending on the connected expansion */ + product += get_wiimote_expansion(controller->egc_device); +#endif + if (controller->egc_device->connection == EGC_CONNECTION_BT) { + bus = SDL_HARDWARE_BUS_BLUETOOTH; + } else if (controller->egc_device->connection == EGC_CONNECTION_USB) { + bus = SDL_HARDWARE_BUS_USB; + } else { + bus = SDL_HARDWARE_BUS_UNKNOWN; + } + version = 1; + driver_signature = 0; + driver_data = 0; + + name = OGC_JoystickGetDeviceName(device_index); + return SDL_CreateJoystickGUID(bus, desc->vendor_id, product, version, + NULL, name, driver_signature, driver_data); +} + +static SDL_JoystickID OGC_JoystickGetDeviceInstanceID(int device_index) +{ + return device_index_to_instance(device_index); +} + +static bool OGC_JoystickOpen(SDL_Joystick *joystick, int device_index) +{ + int index = controller_index_by_device_index(device_index); + _OGC_Controller *controller; + const egc_device_description_t *desc; + u32 dpad_buttons, buttons_excluding_dpad; + + SDL_Log("Open joystick %d (our index: %d)", device_index, index); + + if (index < 0) + return false; + + /* allocate memory for system specific hardware data */ + joystick->hwdata = SDL_malloc(sizeof(joystick_hwdata)); + if (joystick->hwdata == NULL) { + SDL_OutOfMemory(); + return false; + } + controller = OGC_get_controller(index); + desc = controller->egc_device->desc; + joystick->instance_id = controller->instance_id; + + SDL_memset(joystick->hwdata, 0, sizeof(joystick_hwdata)); + joystick->hwdata->controller = controller; + /* Because of https://github.com/libsdl-org/SDL/issues/8754 (hats and axes + * being the only way to provide direction information with the SDL + * joystick API) we need to convert dpad buttons into a hat. + */ + dpad_buttons = desc->available_buttons & DPAD_MASK; + buttons_excluding_dpad = desc->available_buttons & ~DPAD_MASK; + joystick->nbuttons = count_bits(buttons_excluding_dpad); + joystick->naxes = count_bits(desc->available_axes); + joystick->nhats = dpad_buttons != 0 ? 1 : 0; + SDL_PropertiesID props = SDL_GetJoystickProperties(joystick); + SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_PLAYER_LED_BOOLEAN, + desc->num_leds > 0); + SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, + desc->has_rumble); + +#ifdef __wii__ + if (is_wiimote(controller->egc_device)) { + EgcWiimoteExpType expansion = joystick->hwdata->expansion = + get_wiimote_expansion(controller->egc_device); + if (s_wiimote_sideways && + (expansion == EGC_WIIMOTE_EXP_NONE || + expansion == EGC_WIIMOTE_EXP_MOTION_PLUS)) { + joystick->hwdata->rotated = true; + } + } + + if (desc->num_accelerometers > 0) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 100.0f); + if (desc->num_accelerometers > 1) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL_L, 100.0f); + } + } +#endif /* __wii__ */ + return true; +} + +static bool OGC_JoystickRumble(SDL_Joystick *joystick, + Uint16 low_frequency_rumble, + Uint16 high_frequency_rumble) +{ + int rc; + egc_input_device_t *device = get_egc_device(joystick); + if (!device->desc->has_rumble) { + return SDL_Unsupported(); + } + + rc = egc_input_device_set_rumble(device, + low_frequency_rumble, + high_frequency_rumble); + return rc == 0; +} + +static bool OGC_JoystickRumbleTriggers(SDL_Joystick *joystick, + Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static bool OGC_JoystickSetLED(SDL_Joystick *joystick, + Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool OGC_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool OGC_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) +{ + /* EGC at the moment does not supports disabling the sensors */ + return enabled ? true : SDL_Unsupported(); +} + +static void OGC_JoystickUpdate(SDL_Joystick *joystick) +{ + _OGC_Controller *controller; + egc_input_device_t *device; + u32 buttons, prev_buttons, changed, available_buttons; + int i_button = 0, i_axis = 0; + + Uint64 timestamp = SDL_GetTicksNS(); + + controller = get_controller(joystick); + if (!controller) return; + + device = get_egc_device(joystick); + if (!device) return; + +#ifdef __wii__ + if (get_wiimote_expansion(device) != joystick->hwdata->expansion) { + /* If the expansion changes we need setup a different mapping, and the + * simplest way to do this is to pretend that the joystick was + * disconnected and reconnected. */ + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "Expansion changed, reconnecting joystick"); + SDL_PrivateJoystickRemoved(controller->instance_id); + SDL_PrivateJoystickAdded(controller->instance_id); + return; + } +#endif /* __wii__ */ + + available_buttons = device->desc->available_buttons & ~DPAD_MASK; + prev_buttons = joystick->hwdata->prev_buttons; + buttons = egc_input_device_read_buttons(device); + changed = buttons ^ prev_buttons; + + for (int i = 0; i < EGC_GAMEPAD_BUTTON_COUNT; i++) { + u32 mask = 1 << i; + if (!(available_buttons & mask)) continue; + if (changed & mask) { + SDL_SendJoystickButton(timestamp, joystick, i_button, (buttons & mask) != 0); + } + i_button++; + } + /* Handle D-Pad as a hat */ + if (changed & DPAD_MASK) { + int hat = SDL_HAT_CENTERED; + if (buttons & (1 << EGC_GAMEPAD_BUTTON_DPAD_UP)) hat |= SDL_HAT_UP; + if (buttons & (1 << EGC_GAMEPAD_BUTTON_DPAD_RIGHT)) hat |= SDL_HAT_RIGHT; + if (buttons & (1 << EGC_GAMEPAD_BUTTON_DPAD_DOWN)) hat |= SDL_HAT_DOWN; + if (buttons & (1 << EGC_GAMEPAD_BUTTON_DPAD_LEFT)) hat |= SDL_HAT_LEFT; +#ifdef __wii__ + if (joystick->hwdata->rotated) { + /* We can just rotate the bits of the hat mask */ + hat = (hat >> 1) | ((hat & 0x1) << 3); + } +#endif /* __wii__ */ + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + + joystick->hwdata->prev_buttons = buttons; + + for (int i = 0; i < EGC_GAMEPAD_AXIS_COUNT; i++) { + u32 mask = 1 << i; + if (device->desc->available_axes & mask) { + s16 value = egc_input_device_read_axis(device, i); + if (i == EGC_GAMEPAD_AXIS_LEFT_TRIGGER || + i == EGC_GAMEPAD_AXIS_RIGHT_TRIGGER) { + /* The SDL joystick API expects trigger values to be in the + * full range of an int16, so we need to do some scaling here. + */ + value = (value * 2) + INT16_MIN; + } + SDL_SendJoystickAxis(timestamp, joystick, i_axis, value); + i_axis++; + } + } + +#ifdef __wii__ + if (device->desc->num_accelerometers > 0) { + const egc_accelerometer_t *accel = + egc_input_device_read_accelerometer(device, 0); + handle_accelerometer(timestamp, joystick, SDL_SENSOR_ACCEL, + i_axis, accel, + joystick->hwdata->rotated); + i_axis += 2; + if (device->desc->num_accelerometers > 1) { + accel = egc_input_device_read_accelerometer(device, 1); + handle_accelerometer(timestamp, joystick, SDL_SENSOR_ACCEL_L, + i_axis, accel, false); + } + } + + if (device->desc->num_gyroscopes) { + const egc_gyroscope_t *gyro = + egc_input_device_read_gyroscope(device, 0); + float values[3]; + values[0] = (float)gyro->x / EGC_GYROSCOPE_RES; + values[1] = (float)gyro->y / EGC_GYROSCOPE_RES; + values[2] = (float)gyro->z / EGC_GYROSCOPE_RES; + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, 0, values, 3); + } +#endif +} + +static void OGC_JoystickClose(SDL_Joystick *joystick) +{ + if (!joystick || !joystick->hwdata) // joystick already closed + return; + + SDL_free(joystick->hwdata); + joystick->hwdata = NULL; +} + +void OGC_JoystickQuit(void) +{ +} + +static const SDL_InputMapping s_invalid_mapping = { EMappingKind_None, 255 }; + +static SDL_InputMapping egc_map_button(u32 available, int index, int *i_button) +{ + return (1 << index) & available ? + (SDL_InputMapping){ EMappingKind_Button, (*i_button)++ } : s_invalid_mapping; +} + +static SDL_InputMapping egc_map_hat(u32 available, int index, int i_hat) +{ + return (1 << index) & available ? + (SDL_InputMapping){ EMappingKind_Hat, i_hat } : s_invalid_mapping; +} + +static SDL_InputMapping egc_map_axis(u32 available, int index, int *i_axis) +{ + return (1 << index) & available ? + (SDL_InputMapping){ EMappingKind_Axis, (*i_axis)++ } : s_invalid_mapping; +} + +static bool OGC_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) +{ + egc_input_device_t *device; + u32 buttons, axes; + int i_button = 0, i_axis = 0; + _OGC_Controller *controller = OGC_get_controller(device_index); + if (!controller) + return false; + + device = controller->egc_device; + buttons = device->desc->available_buttons; + axes = device->desc->available_axes; + + *out = (SDL_GamepadMapping){ + .b = egc_map_button(buttons, EGC_GAMEPAD_BUTTON_SOUTH, &i_button), + .a = egc_map_button(buttons, EGC_GAMEPAD_BUTTON_EAST, &i_button), + .y = egc_map_button(buttons, EGC_GAMEPAD_BUTTON_WEST, &i_button), + .x = egc_map_button(buttons, EGC_GAMEPAD_BUTTON_NORTH, &i_button), + .back = egc_map_button(buttons, EGC_GAMEPAD_BUTTON_BACK, &i_button), + .guide = egc_map_button(buttons, EGC_GAMEPAD_BUTTON_GUIDE, &i_button), + .start = egc_map_button(buttons, EGC_GAMEPAD_BUTTON_START, &i_button), + .leftstick = egc_map_button(buttons, EGC_GAMEPAD_BUTTON_LEFT_STICK, &i_button), + .rightstick = egc_map_button(buttons, EGC_GAMEPAD_BUTTON_RIGHT_STICK, &i_button), + .leftshoulder = egc_map_button(buttons, EGC_GAMEPAD_BUTTON_LEFT_SHOULDER, &i_button), + .rightshoulder = egc_map_button(buttons, EGC_GAMEPAD_BUTTON_RIGHT_SHOULDER, &i_button), + .dpup = egc_map_hat(buttons, EGC_GAMEPAD_BUTTON_DPAD_UP, SDL_HAT_UP), + .dpdown = egc_map_hat(buttons, EGC_GAMEPAD_BUTTON_DPAD_DOWN, SDL_HAT_DOWN), + .dpleft = egc_map_hat(buttons, EGC_GAMEPAD_BUTTON_DPAD_LEFT, SDL_HAT_LEFT), + .dpright = egc_map_hat(buttons, EGC_GAMEPAD_BUTTON_DPAD_RIGHT, SDL_HAT_RIGHT), + .misc1 = egc_map_button(buttons, EGC_GAMEPAD_BUTTON_MISC1, &i_button), + .right_paddle1 = egc_map_button(buttons, EGC_GAMEPAD_BUTTON_RIGHT_PADDLE1, &i_button), + .left_paddle1 = egc_map_button(buttons, EGC_GAMEPAD_BUTTON_LEFT_PADDLE1, &i_button), + .right_paddle2 = egc_map_button(buttons, EGC_GAMEPAD_BUTTON_RIGHT_PADDLE2, &i_button), + .left_paddle2 = egc_map_button(buttons, EGC_GAMEPAD_BUTTON_LEFT_PADDLE2, &i_button), + .leftx = egc_map_axis(axes, EGC_GAMEPAD_AXIS_LEFTX, &i_axis), + .lefty = egc_map_axis(axes, EGC_GAMEPAD_AXIS_LEFTY, &i_axis), + .rightx = egc_map_axis(axes, EGC_GAMEPAD_AXIS_RIGHTX, &i_axis), + .righty = egc_map_axis(axes, EGC_GAMEPAD_AXIS_RIGHTY, &i_axis), + .lefttrigger = egc_map_axis(axes, EGC_GAMEPAD_AXIS_LEFT_TRIGGER, &i_axis), + .righttrigger = egc_map_axis(axes, EGC_GAMEPAD_AXIS_RIGHT_TRIGGER, &i_axis), + }; + +#ifdef __wii__ + if (s_wiimote_sideways && is_wiimote(device)) { + /* Remap buttons so that the 1 and 2 buttons on the wiimote become the + * primary ones */ + out->a = (SDL_InputMapping){ EMappingKind_Button, EGC_GAMEPAD_BUTTON_WEST }; /* 2 */ + out->b = (SDL_InputMapping){ EMappingKind_Button, EGC_GAMEPAD_BUTTON_NORTH }; /* 1 */ + out->x = (SDL_InputMapping){ EMappingKind_Button, EGC_GAMEPAD_BUTTON_EAST }; /* B */ + out->y = (SDL_InputMapping){ EMappingKind_Button, EGC_GAMEPAD_BUTTON_SOUTH }; /* A */ + } +#endif /* __wii__ */ + return true; +} + +SDL_JoystickDriver SDL_OGC_JoystickDriver = { + OGC_JoystickInit, + OGC_JoystickGetCount, + OGC_JoystickDetect, + OGC_JoystickIsDevicePresent, + OGC_JoystickGetDeviceName, + OGC_JoystickGetDevicePath, + OGC_JoystickGetDeviceSteamVirtualGamepadSlot, + OGC_JoystickGetDevicePlayerIndex, + OGC_JoystickSetDevicePlayerIndex, + OGC_JoystickGetDeviceGUID, + OGC_JoystickGetDeviceInstanceID, + OGC_JoystickOpen, + OGC_JoystickRumble, + OGC_JoystickRumbleTriggers, + OGC_JoystickSetLED, + OGC_JoystickSendEffect, + OGC_JoystickSetSensorsEnabled, + OGC_JoystickUpdate, + OGC_JoystickClose, + OGC_JoystickQuit, + OGC_JoystickGetGamepadMapping, +}; + +#endif /* SDL_JOYSTICK_OGC */ From b3af786127d977236f1bd4146a8ffc8a83198d86 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Fri, 14 Aug 2026 18:12:51 +0300 Subject: [PATCH 10/14] ogc: add audio backend based on libaesnd --- CMakeLists.txt | 10 + include/build_config/SDL_build_config.h.cmake | 1 + src/audio/SDL_audio.c | 3 + src/audio/SDL_sysaudio.h | 1 + src/audio/ogc/SDL_ogcaudio.c | 259 ++++++++++++++++++ src/audio/ogc/SDL_ogcaudio.h | 50 ++++ 6 files changed, 324 insertions(+) create mode 100644 src/audio/ogc/SDL_ogcaudio.c create mode 100644 src/audio/ogc/SDL_ogcaudio.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b7be4a5908136..5df924f021b40 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3343,6 +3343,16 @@ elseif(OGC) sdl_link_dependency(keyboard LIBS wiikeyboard) endif() + if(SDL_AUDIO) + set(SDL_AUDIO_DRIVER_OGC 1) + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/audio/ogc/*.c" + "${SDL3_SOURCE_DIR}/src/audio/ogc/*.h" + ) + sdl_link_dependency(audio LIBS aesnd) + set(HAVE_SDL_AUDIO TRUE) + endif() + set(SDL_FILESYSTEM_OGC 1) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/ogc/*.c") set(HAVE_SDL_FILESYSTEM TRUE) diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake index 02625e514caee..156da5ff244c3 100644 --- a/include/build_config/SDL_build_config.h.cmake +++ b/include/build_config/SDL_build_config.h.cmake @@ -290,6 +290,7 @@ #cmakedefine SDL_AUDIO_DRIVER_PS2 1 #cmakedefine SDL_AUDIO_DRIVER_N3DS 1 #cmakedefine SDL_AUDIO_DRIVER_NGAGE 1 +#cmakedefine SDL_AUDIO_DRIVER_OGC 1 #cmakedefine SDL_AUDIO_DRIVER_QNX 1 #cmakedefine SDL_AUDIO_DRIVER_PRIVATE 1 diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index aed406cc2c938..425a256e65aaf 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -80,6 +80,9 @@ static const AudioBootStrap *const bootstrap[] = { #ifdef SDL_AUDIO_DRIVER_NGAGE &NGAGEAUDIO_bootstrap, #endif +#ifdef SDL_AUDIO_DRIVER_OGC + &OGCAUDIO_bootstrap, +#endif #ifdef SDL_AUDIO_DRIVER_EMSCRIPTEN &EMSCRIPTENAUDIO_bootstrap, #endif diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index 9104be82a3a2e..266eee6535db3 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -388,6 +388,7 @@ extern AudioBootStrap PSPAUDIO_bootstrap; extern AudioBootStrap VITAAUD_bootstrap; extern AudioBootStrap N3DSAUDIO_bootstrap; extern AudioBootStrap NGAGEAUDIO_bootstrap; +extern AudioBootStrap OGCAUDIO_bootstrap; extern AudioBootStrap EMSCRIPTENAUDIO_bootstrap; extern AudioBootStrap QSAAUDIO_bootstrap; diff --git a/src/audio/ogc/SDL_ogcaudio.c b/src/audio/ogc/SDL_ogcaudio.c new file mode 100644 index 0000000000000..84835ebcef449 --- /dev/null +++ b/src/audio/ogc/SDL_ogcaudio.c @@ -0,0 +1,259 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_AUDIO_DRIVER_OGC + +/* OGC Audio driver */ + +#include "../SDL_sysaudio.h" +#include "SDL_ogcaudio.h" + +#include + +#define OGCAUDIO_DRIVER_NAME "ogc" + +/** + * Cleans up all allocated memory, safe to call with null pointers + */ +static void FreePrivateData(SDL_AudioDevice *this) +{ + if (!this->hidden) { + return; + } + + SDL_free(this->hidden); + this->hidden = NULL; +} + +static bool FindAudioFormat(SDL_AudioDevice *this) +{ + bool found_valid_format = false; + const SDL_AudioFormat *closefmts; + Uint16 test_format; + + closefmts = SDL_ClosestAudioFormats(this->spec.format); + while (!found_valid_format && (test_format = *(closefmts++)) != 0) { + this->spec.format = test_format; + SDL_LogDebug(SDL_LOG_CATEGORY_AUDIO, "Trying format %x", test_format); + switch (test_format) { + case SDL_AUDIO_S8: + this->hidden->format = VOICE_MONO8; + this->hidden->bytes_per_sample = this->spec.channels; + found_valid_format = true; + break; + case SDL_AUDIO_U8: + this->hidden->format = VOICE_MONO8_UNSIGNED; + this->hidden->bytes_per_sample = this->spec.channels; + found_valid_format = true; + break; + case SDL_AUDIO_S16LE: + case SDL_AUDIO_S16BE: + this->hidden->format = VOICE_MONO16; + this->hidden->bytes_per_sample = this->spec.channels * 2; + this->spec.format = SDL_AUDIO_S16BE; + found_valid_format = true; + break; + default: + break; + } + } + + if (found_valid_format && this->spec.channels == 2) { + this->hidden->format++; + } + + return found_valid_format; +} + +/* fully local functions related to the wavebufs / DSP, not the same as the SDL-wide mixer lock */ +static SDL_INLINE void contextLock(SDL_AudioDevice *this) +{ + LWP_MutexLock(this->hidden->lock); +} + +static SDL_INLINE void contextUnlock(SDL_AudioDevice *this) +{ + LWP_MutexUnlock(this->hidden->lock); +} + +static void audio_frame_finished(AESNDPB *pb, u32 state, void *arg) +{ + SDL_AudioDevice *this = (SDL_AudioDevice *)arg; + + if (state == VOICE_STATE_STREAM) { + const size_t buffer_size = DMA_BUFFER_SIZE; + s8 playing_buffer; + void *buffer; + + /* Immediately send the next buffer to the DSP. It's important that + * AESND_SetVoiceBuffer() gets called before this callback returns, or + * some audio gaps might be audible. */ + contextLock(this); + playing_buffer = (this->hidden->playing_buffer + 1) % NUM_BUFFERS; + buffer = this->hidden->dma_buffers[playing_buffer]; + AESND_SetVoiceBuffer(pb, buffer, buffer_size); + this->hidden->playing_buffer = playing_buffer; + contextUnlock(this); + + /* If a frame has finished playing, it means that the corresponding + * buffer is no longer in use and can be filled up again. We signal + * this event to the audio thread via a semaphore. */ + LWP_SemPost(this->hidden->available_buffers); + } if (state == VOICE_STATE_STOPPED) { + contextLock(this); + this->hidden->playing_buffer = -1; + contextUnlock(this); + } +} + +static bool OGCAUDIO_OpenDevice(SDL_AudioDevice *this) +{ + struct SDL_PrivateAudioData *hidden = + memalign(32, sizeof(struct SDL_PrivateAudioData)); + if (!hidden) { + return SDL_OutOfMemory(); + } + + SDL_LogDebug(SDL_LOG_CATEGORY_AUDIO, + "OGCAUDIO_OpenDevice, freq=%d, channels=%d\n", + this->spec.freq, this->spec.channels); + + memset(hidden, 0, sizeof(*hidden)); + hidden->playing_buffer = -1; + this->hidden = hidden; + + AESND_Init(); + AESND_Pause(1); + + /* Initialise internal state */ + LWP_MutexInit(&hidden->lock, false); + /* We set the initial number of available buffers to NUM_BUFFERS - 1, since + * SDL first calls GetDeviceBuf() and starts filling it without first + * calling WaitDevice(). So we consider the first buffer to be busy already + * at start. */ + LWP_SemInit(&hidden->available_buffers, NUM_BUFFERS - 1, NUM_BUFFERS); + + if (this->spec.freq <= 0 || this->spec.freq > 144000) + this->spec.freq = DSP_DEFAULT_FREQ; + + if (this->spec.channels > 2) { + this->spec.channels = 2; + } + + /* Should not happen but better be safe. */ + if (!FindAudioFormat(this)) { + return SDL_SetError("No supported audio format found."); + } + + this->sample_frames = DMA_BUFFER_SIZE / this->hidden->bytes_per_sample; + + /* Update the fragment size as size in bytes */ + SDL_UpdatedAudioDeviceFormat(this); + + hidden->voice = AESND_AllocateVoiceWithArg(audio_frame_finished, this); + if (hidden->voice == NULL) + return false; + + // start audio + AESND_SetVoiceFormat(hidden->voice, hidden->format); + AESND_SetVoiceFrequency(hidden->voice, this->spec.freq); + AESND_SetVoiceBuffer(hidden->voice, hidden->dma_buffers[0], DMA_BUFFER_SIZE); + AESND_SetVoiceStream(hidden->voice, true); + AESND_SetVoiceStop(hidden->voice, 0); + AESND_Pause(0); + + return true; +} + +static bool OGCAUDIO_PlayDevice(SDL_AudioDevice *this, + const Uint8 *buffer_unused, int buflen_unused) +{ + void *buffer; + + contextLock(this); + if (this->hidden->playing_buffer < 0) { + buffer = this->hidden->dma_buffers[++this->hidden->playing_buffer]; + AESND_SetVoiceBuffer(this->hidden->voice, buffer, DMA_BUFFER_SIZE); + } + contextUnlock(this); + return true; +} + +static bool OGCAUDIO_WaitDevice(SDL_AudioDevice *this) +{ + s8 nextbuf; + + /* This will block until at least one buffer is available for writing. */ + LWP_SemWait(this->hidden->available_buffers); + + nextbuf = this->hidden->nextbuf; + this->hidden->nextbuf = (nextbuf + 1) % NUM_BUFFERS; + return true; +} + +static Uint8 *OGCAUDIO_GetDeviceBuf(SDL_AudioDevice *this, int *buffer_size) +{ + return this->hidden->dma_buffers[this->hidden->nextbuf]; +} + +static void OGCAUDIO_CloseDevice(SDL_AudioDevice *this) +{ + struct SDL_PrivateAudioData *hidden = this->hidden; + + LWP_SemDestroy(hidden->available_buffers); + if (hidden->voice) { + AESND_SetVoiceStop(hidden->voice, true); + AESND_FreeVoice(hidden->voice); + hidden->voice = NULL; + } + + AESND_Pause(1); + FreePrivateData(this); +} + +static void OGCAUDIO_ThreadInit(SDL_AudioDevice *this) +{ + LWP_SetThreadPriority(LWP_THREAD_NULL, 80); +} + +static bool OGCAUDIO_Init(SDL_AudioDriverImpl *impl) +{ + /* Set the function pointers */ + impl->OpenDevice = OGCAUDIO_OpenDevice; + impl->PlayDevice = OGCAUDIO_PlayDevice; + impl->WaitDevice = OGCAUDIO_WaitDevice; + impl->GetDeviceBuf = OGCAUDIO_GetDeviceBuf; + impl->CloseDevice = OGCAUDIO_CloseDevice; + impl->ThreadInit = OGCAUDIO_ThreadInit; + impl->OnlyHasDefaultPlaybackDevice = true; + + return true; /* this audio target is available. */ +} + +AudioBootStrap OGCAUDIO_bootstrap = { + OGCAUDIO_DRIVER_NAME, + "SDL OGC audio driver", + OGCAUDIO_Init, + false, false +}; + +#endif /* SDL_AUDIO_DRIVER_OGC */ diff --git a/src/audio/ogc/SDL_ogcaudio.h b/src/audio/ogc/SDL_ogcaudio.h new file mode 100644 index 0000000000000..43e73887619fb --- /dev/null +++ b/src/audio/ogc/SDL_ogcaudio.h @@ -0,0 +1,50 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef _SDL_ogcaudio_h_ +#define _SDL_ogcaudio_h_ + +#include +#include + +#include +#include + +#define NUM_BUFFERS 4 /* -- Minimum 2! */ +#define SAMPLES_PER_DMA_BUFFER (DSP_STREAMBUFFER_SIZE) +#define DMA_BUFFER_SIZE (SAMPLES_PER_DMA_BUFFER * 2 * sizeof(short)) + +struct SDL_PrivateAudioData +{ + /* these go first so they will be aligned */ + Uint8 dma_buffers[NUM_BUFFERS][DMA_BUFFER_SIZE]; + AESNDPB *voice; + + /* Speaker data */ + Uint32 format; + Uint8 bytes_per_sample; + s8 nextbuf; + s8 playing_buffer; + mutex_t lock; + sem_t available_buffers; +}; + +#endif /* _SDL_ogcaudio_h_ */ From 9e6f091cf38dc3157a754bc1186cd2f6e3512c38 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Sat, 15 Aug 2026 10:30:54 +0300 Subject: [PATCH 11/14] ogc: make the OpenGL renderer backend work with opengx We are not enabling it since the GX backend is preferrable (it doesn't depend on opengx), but it's good to know that this also works. --- CMakeLists.txt | 3 +++ src/render/opengl/SDL_render_gl.c | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5df924f021b40..b1eb97d174120 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3378,6 +3378,9 @@ elseif(OGC) if(SDL_VIDEO) set(SDL_VIDEO_DRIVER_OGC 1) set(SDL_VIDEO_RENDER_OGC 1) + # Disabled since we have the GX renderer, but this can also be used as an + # option: + # set(SDL_VIDEO_RENDER_OGL 1) sdl_glob_sources( "${SDL3_SOURCE_DIR}/src/render/ogc/*.c" "${SDL3_SOURCE_DIR}/src/render/ogc/*.h" diff --git a/src/render/opengl/SDL_render_gl.c b/src/render/opengl/SDL_render_gl.c index 64402dae8514c..bdb3041bac1f0 100644 --- a/src/render/opengl/SDL_render_gl.c +++ b/src/render/opengl/SDL_render_gl.c @@ -1760,6 +1760,10 @@ static bool GL_SetVSync(SDL_Renderer *renderer, const int vsync) return true; } +#ifdef SDL_VIDEO_DRIVER_OGC +#include +#endif + static bool GL_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_PropertiesID create_props) { GL_RenderData *data = NULL; @@ -1772,6 +1776,9 @@ static bool GL_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_Pr bool non_power_of_two_supported = false; bool bgra_supported = false; +#ifdef SDL_VIDEO_DRIVER_OGC + ogx_enable_module_fbo(); +#endif SDL_SetupRendererColorspace(renderer, create_props); if (renderer->output_colorspace != SDL_COLORSPACE_SRGB) { From 1c5d159afd16cd3a46846fe290172c6239fcffcd Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Sat, 15 Aug 2026 15:28:32 +0300 Subject: [PATCH 12/14] ogc: add GX renderer backend --- CMakeLists.txt | 1 - include/build_config/SDL_build_config.h.cmake | 1 + src/render/SDL_render.c | 3 + src/render/SDL_sysrender.h | 1 + src/render/ogc/SDL_render_ogc.c | 757 ++++++++++++++++++ 5 files changed, 762 insertions(+), 1 deletion(-) create mode 100644 src/render/ogc/SDL_render_ogc.c diff --git a/CMakeLists.txt b/CMakeLists.txt index b1eb97d174120..9135567c935a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3383,7 +3383,6 @@ elseif(OGC) # set(SDL_VIDEO_RENDER_OGL 1) sdl_glob_sources( "${SDL3_SOURCE_DIR}/src/render/ogc/*.c" - "${SDL3_SOURCE_DIR}/src/render/ogc/*.h" "${SDL3_SOURCE_DIR}/src/video/ogc/*.c" "${SDL3_SOURCE_DIR}/src/video/ogc/*.h" ) diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake index 156da5ff244c3..7a8a53f5d36be 100644 --- a/include/build_config/SDL_build_config.h.cmake +++ b/include/build_config/SDL_build_config.h.cmake @@ -464,6 +464,7 @@ #cmakedefine SDL_VIDEO_RENDER_OGL 1 #cmakedefine SDL_VIDEO_RENDER_OGL_ES2 1 #cmakedefine SDL_VIDEO_RENDER_NGAGE 1 +#cmakedefine SDL_VIDEO_RENDER_OGC 1 #cmakedefine SDL_VIDEO_RENDER_PS2 1 #cmakedefine SDL_VIDEO_RENDER_PSP 1 #cmakedefine SDL_VIDEO_RENDER_VITA_GXM 1 diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c index b77d33e6efdb1..790f8ae641556 100644 --- a/src/render/SDL_render.c +++ b/src/render/SDL_render.c @@ -123,6 +123,9 @@ static const SDL_RenderDriver *render_drivers[] = { #ifdef SDL_VIDEO_RENDER_NGAGE &NGAGE_RenderDriver, #endif +#ifdef SDL_VIDEO_RENDER_OGC + &OGC_RenderDriver, +#endif #ifdef SDL_VIDEO_RENDER_OGL &GL_RenderDriver, #endif diff --git a/src/render/SDL_sysrender.h b/src/render/SDL_sysrender.h index a1bb44a12d2e8..5f2780838b93b 100644 --- a/src/render/SDL_sysrender.h +++ b/src/render/SDL_sysrender.h @@ -378,6 +378,7 @@ extern SDL_RenderDriver GL_RenderDriver; extern SDL_RenderDriver GLES2_RenderDriver; extern SDL_RenderDriver METAL_RenderDriver; extern SDL_RenderDriver NGAGE_RenderDriver; +extern SDL_RenderDriver OGC_RenderDriver; extern SDL_RenderDriver VULKAN_RenderDriver; extern SDL_RenderDriver PS2_RenderDriver; extern SDL_RenderDriver PSP_RenderDriver; diff --git a/src/render/ogc/SDL_render_ogc.c b/src/render/ogc/SDL_render_ogc.c new file mode 100644 index 0000000000000..e35a879bab1dc --- /dev/null +++ b/src/render/ogc/SDL_render_ogc.c @@ -0,0 +1,757 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_VIDEO_RENDER_OGC + +#include "../SDL_sysrender.h" + +#include "../../video/ogc/SDL_ogcgxcommon.h" +#include "../../video/ogc/SDL_ogcpixels.h" +#include "../../video/ogc/SDL_ogcvideo.h" + +#include +#include +#include +#include + +#define MAX_EFB_WIDTH 640 +#define MAX_EFB_HEIGHT 528 + +typedef struct +{ + SDL_BlendMode current_blend_mode; + int ops_after_present; + bool vsync; + u8 efb_pixel_format; + SDL_Texture *render_target; + SDL_Texture *saved_efb_texture; +} OGC_RenderData; + +typedef struct +{ + void *texels; + void *pixels; + SDL_Rect pixels_rect; + u16 pitch; + u16 pixels_pitch; + u8 format; + u8 needed_stages; // Normally 1, set to 2 for palettized formats +} OGC_TextureData; + +static void OGC_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture); + +static GXColor scaled_color(const SDL_FColor *color, float color_scale) +{ + GXColor c = { + (uint8_t)SDL_roundf(SDL_clamp(color->r * color_scale, 0.0f, 1.0f) * 255.0f), + (uint8_t)SDL_roundf(SDL_clamp(color->g * color_scale, 0.0f, 1.0f) * 255.0f), + (uint8_t)SDL_roundf(SDL_clamp(color->b * color_scale, 0.0f, 1.0f) * 255.0f), + (uint8_t)SDL_roundf(SDL_clamp(color->a, 0.0f, 1.0f) * 255.0f), + }; + return c; +} + +static void OGC_WindowEvent(SDL_Renderer *renderer, const SDL_WindowEvent *event) +{ +} + +static void set_blend_mode_real(SDL_Renderer *renderer, SDL_BlendMode blend_mode) +{ + switch (blend_mode) { + case SDL_BLENDMODE_NONE: + GX_SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_INVSRCALPHA, GX_LO_CLEAR); + break; + case SDL_BLENDMODE_BLEND: + GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR); + break; + case SDL_BLENDMODE_ADD: + GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_ONE, GX_LO_CLEAR); + break; + case SDL_BLENDMODE_MOD: + GX_SetBlendMode(GX_BM_BLEND, GX_BL_DSTCLR, GX_BL_ZERO, GX_LO_CLEAR); + break; + case SDL_BLENDMODE_MUL: + GX_SetBlendMode(GX_BM_BLEND, GX_BL_DSTCLR, GX_BL_INVSRCALPHA, GX_LO_CLEAR); + break; + default: + return; + } +} + +static inline void OGC_SetBlendMode(SDL_Renderer *renderer, SDL_BlendMode blend_mode) +{ + OGC_RenderData *data = renderer->internal; + + if (blend_mode == data->current_blend_mode) { + /* Nothing to do */ + return; + } + + set_blend_mode_real(renderer, blend_mode); + data->current_blend_mode = blend_mode; +} + +static void load_efb_from_texture(SDL_Renderer *renderer, SDL_Texture *texture) +{ + OGC_TextureData *ogc_tex = texture->internal; + + OGC_load_texture(ogc_tex->texels, texture->w, texture->h, + ogc_tex->format, SDL_SCALEMODE_NEAREST); + OGC_SetBlendMode(renderer, SDL_BLENDMODE_NONE); + + /* The viewport is reset when OGC_SetRenderTarget() returns. */ + OGC_set_viewport(0, 0, texture->w, texture->h); + + GX_ClearVtxDesc(); + GX_SetVtxDesc(GX_VA_POS, GX_DIRECT); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0); + + GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_U8, 0); + GX_SetNumTexGens(1); + GX_SetNumChans(0); + + GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); + GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLORNULL); + GX_SetTevOp(GX_TEVSTAGE0, GX_REPLACE); + GX_SetNumTevStages(1); + + GX_Begin(GX_QUADS, GX_VTXFMT0, 4); + GX_Position2s16(0, 0); + GX_TexCoord2u8(0, 0); + GX_Position2s16(texture->w, 0); + GX_TexCoord2u8(1, 0); + GX_Position2s16(texture->w, texture->h); + GX_TexCoord2u8(1, 1); + GX_Position2s16(0, texture->h); + GX_TexCoord2u8(0, 1); + GX_End(); +} + +static void save_efb_to_texture(SDL_Texture *texture, bool must_clear) +{ + OGC_TextureData *ogc_tex = texture->internal; + u32 texture_size; + + texture_size = GX_GetTexBufferSize(texture->w, texture->h, ogc_tex->format, + GX_FALSE, 0); + DCInvalidateRange(ogc_tex->texels, texture_size); + + GX_SetTexCopySrc(0, 0, texture->w, texture->h); + GX_SetTexCopyDst(texture->w, texture->h, ogc_tex->format, GX_FALSE); + GX_CopyTex(ogc_tex->texels, must_clear ? GX_TRUE : GX_FALSE); + GX_PixModeSync(); +} + +static void update_texture(SDL_Texture *texture, const SDL_Rect *rect, + const void *pixels, int pitch) +{ + OGC_TextureData *ogc_tex = texture->internal; + u32 texture_size; + + OGC_pixels_to_texture((void*)pixels, texture->format, rect, + pitch, ogc_tex->texels, texture->w); + texture_size = GX_GetTexBufferSize(texture->w, texture->h, ogc_tex->format, + GX_FALSE, 0); + /* It would be more effective if we updated only the changed range here, + * but the complexity is probably not worth the effort. */ + DCStoreRange(ogc_tex->texels, texture_size); + GX_InvalidateTexAll(); +} + +static bool OGC_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, + SDL_PropertiesID create_props) +{ + u32 texture_size; + OGC_TextureData *ogc_tex; + + ogc_tex = SDL_calloc(1, sizeof(OGC_TextureData)); + if (!ogc_tex) { + return SDL_OutOfMemory(); + } + + ogc_tex->format = OGC_texture_format_from_SDL(texture->format); + ogc_tex->needed_stages = (ogc_tex->format == GX_TF_CI8) ? 2 : 1; + texture_size = GX_GetTexBufferSize(texture->w, texture->h, ogc_tex->format, + GX_FALSE, 0); + ogc_tex->texels = memalign(32, texture_size); + if (!ogc_tex->texels) { + SDL_free(ogc_tex); + return SDL_OutOfMemory(); + } + + texture->internal = ogc_tex; + return true; +} + +static bool OGC_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture, + const SDL_Rect *rect, void **pixels, int *pitch) +{ + OGC_TextureData *ogc_tex = texture->internal; + + ogc_tex->pixels = SDL_malloc(rect->w * rect->h * SDL_BYTESPERPIXEL(texture->format)); + ogc_tex->pixels_pitch = rect->w * SDL_BYTESPERPIXEL(texture->format); + ogc_tex->pixels_rect = *rect; + *pixels = ogc_tex->pixels; + *pitch = ogc_tex->pixels_pitch; + return true; +} + +static void OGC_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture) +{ + OGC_TextureData *ogc_tex = texture->internal; + + update_texture(texture, &ogc_tex->pixels_rect, + ogc_tex->pixels, ogc_tex->pixels_pitch); + + if (ogc_tex->pixels) { + SDL_free(ogc_tex->pixels); + ogc_tex->pixels = NULL; + } +} + +static bool OGC_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, + const SDL_Rect *rect, const void *pixels, int pitch) +{ + update_texture(texture, rect, pixels, pitch); + return true; +} + +static SDL_Texture *create_efb_texture(OGC_RenderData *data, SDL_Window *window) +{ + /* Note: we do return a SDL_Texture, but not via SDL's API, since that does + * a bunch of other stuffs we don't care about. We create this texture for + * our internal use, so we initialize only those fields we care about. */ + SDL_Texture *texture; + OGC_TextureData *ogc_tex; + u32 texture_size; + + texture = SDL_calloc(1, sizeof(*texture)); + if (!texture) goto fail_texture_alloc; + + ogc_tex = SDL_calloc(1, sizeof(OGC_TextureData)); + if (!ogc_tex) goto fail_ogc_tex_alloc; + + ogc_tex->format = data->efb_pixel_format == GX_PF_RGBA6_Z24 ? + GX_TF_RGBA8 : GX_TF_RGB565; + texture->w = window->w; + texture->h = window->h; + texture_size = GX_GetTexBufferSize(texture->w, texture->h, ogc_tex->format, + GX_FALSE, 0); + ogc_tex->texels = memalign(32, texture_size); + if (!ogc_tex->texels) goto fail_texels_alloc; + + texture->internal = ogc_tex; + return texture; + +fail_texels_alloc: + SDL_free(ogc_tex->texels); +fail_ogc_tex_alloc: + SDL_free(texture); +fail_texture_alloc: + SDL_OutOfMemory(); + return NULL; +} + +static bool OGC_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture) +{ + OGC_RenderData *data = renderer->internal; + u8 desired_efb_pixel_format = GX_PF_RGB8_Z24; + + if (data->render_target) { + save_efb_to_texture(data->render_target, false); + } else if (data->ops_after_present > 0) { + /* Save the current EFB contents if we already drew something onto + * it. We'll restore it later, when the rendering target is reset + * to NULL (the screen). */ + if (!data->saved_efb_texture) + data->saved_efb_texture = create_efb_texture(data, renderer->window); + save_efb_to_texture(data->saved_efb_texture, false); + } + + if (texture) { + if (texture->w > MAX_EFB_WIDTH || texture->h > MAX_EFB_HEIGHT) { + return SDL_SetError("Render target (%dx%d) bigger than EFB", texture->w, texture->h); + } + + if (SDL_ISPIXELFORMAT_ALPHA(texture->format)) { + desired_efb_pixel_format = GX_PF_RGBA6_Z24; + } + } + + data->render_target = texture; + + if (desired_efb_pixel_format != data->efb_pixel_format) { + data->efb_pixel_format = desired_efb_pixel_format; + GX_SetPixelFmt(data->efb_pixel_format, GX_ZC_LINEAR); + } + + if (texture) { + load_efb_from_texture(renderer, texture); + } else if (data->saved_efb_texture) { + /* Restore the EFB to how it was before the we started to render to a + * texture. */ + load_efb_from_texture(renderer, data->saved_efb_texture); + /* We don't free data->saved_efb_texture, it will be reused */ + } + + return true; +} + +static bool OGC_QueueNoOp(SDL_Renderer *renderer, SDL_RenderCommand *cmd) +{ + return true; /* nothing to do in this backend. */ +} + +static bool OGC_QueueDrawPoints(SDL_Renderer *renderer, SDL_RenderCommand *cmd, + const SDL_FPoint *points, int count) +{ + size_t size = count * sizeof(SDL_FPoint); + SDL_FPoint *vertices = SDL_AllocateRenderVertices(renderer, size, + 4, &cmd->data.draw.first); + if (!vertices) { + return false; + } + + cmd->data.draw.count = count; + SDL_memcpy(vertices, points, size); + return true; +} + +static bool OGC_QueueFillRects(SDL_Renderer *renderer, SDL_RenderCommand *cmd, + const SDL_FRect *rects, int count) +{ + size_t size = count * sizeof(SDL_FPoint) * 4; + SDL_FPoint *vertices = SDL_AllocateRenderVertices(renderer, size, + 4, &cmd->data.draw.first); + if (!vertices) { + return false; + } + + cmd->data.draw.count = count; + for (int i = 0; i < count; i++) { + vertices[i].x = rects[i].x; + vertices[i].y = rects[i].y; + vertices[i+1].x = rects[i].x + rects[i].w; + vertices[i+1].y = rects[i].y; + vertices[i+2].x = rects[i].x + rects[i].w; + vertices[i+2].y = rects[i].y + rects[i].h; + vertices[i+3].x = rects[i].x; + vertices[i+3].y = rects[i].y + rects[i].h; + } + return true; +} + +static bool OGC_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture, + const float *xy, int xy_stride, const SDL_FColor *color, int color_stride, + const float *uv, int uv_stride, + int num_vertices, const void *indices, int num_indices, int size_indices, + float scale_x, float scale_y) +{ + int i; + int count = indices ? num_indices : num_vertices; + size_t size_per_element; + char *vertices; + const float color_scale = cmd->data.draw.color_scale; + + cmd->data.draw.count = count; + size_indices = indices ? size_indices : 0; + + size_per_element = sizeof(SDL_FPoint) + sizeof(GXColor); + if (texture) { + size_per_element += sizeof(SDL_FPoint); + } + + vertices = SDL_AllocateRenderVertices(renderer, count * size_per_element, + 4, &cmd->data.draw.first); + if (!vertices) { + return false; + } + + for (i = 0; i < count; i++) { + int j; + float *xy_; + float *uv_; + SDL_FColor *col_; + char *vertex; + SDL_FPoint *vertex_xy; + if (size_indices == 4) { + j = ((const Uint32 *)indices)[i]; + } else if (size_indices == 2) { + j = ((const Uint16 *)indices)[i]; + } else if (size_indices == 1) { + j = ((const Uint8 *)indices)[i]; + } else { + j = i; + } + + xy_ = (float *)((char *)xy + j * xy_stride); + col_ = (SDL_FColor *)((char *)color + j * color_stride); + uv_ = (float *)((char *)uv + j * uv_stride); + + vertex = vertices + size_per_element * i; + + vertex_xy = (SDL_FPoint *)vertex; + vertex_xy->x = xy_[0] * scale_x; + vertex_xy->y = xy_[1] * scale_x; + + *(GXColor *)(vertex + sizeof(SDL_FPoint)) = scaled_color(col_, color_scale); + + if (texture) { + SDL_FPoint *vertex_uv = (SDL_FPoint *)(vertex + sizeof(SDL_FPoint) + sizeof(GXColor)); + vertex_uv->x = uv_[0]; + vertex_uv->y = uv_[1]; + } + } + + return true; +} + +static bool OGC_RenderSetViewPort(SDL_Renderer *renderer, SDL_RenderCommand *cmd) +{ + const SDL_Rect *viewport = &cmd->data.viewport.rect; + + OGC_set_viewport(viewport->x, viewport->y, viewport->w, viewport->h); + return true; +} + +static bool OGC_RenderSetClipRect(SDL_Renderer *renderer, SDL_RenderCommand *cmd) +{ + const SDL_Rect *rect = &cmd->data.cliprect.rect; + + SDL_Rect viewport; + SDL_GetRenderViewport(renderer, &viewport); + if (cmd->data.cliprect.enabled) { + GX_SetScissor(viewport.x + rect->x, + viewport.y + rect->y, + rect->w, rect->h); + } else { + GX_SetScissor(viewport.x, + viewport.y, + viewport.w, + viewport.h); + } + + return true; +} + +static int OGC_RenderClear(SDL_Renderer *renderer, SDL_RenderCommand *cmd) +{ + OGC_RenderData *data = renderer->internal; + + GXColor c = scaled_color(&cmd->data.color.color, cmd->data.color.color_scale); + int16_t x1 = 0; + int16_t y1 = 0; + int16_t x2 = renderer->window->w; + int16_t y2 = renderer->window->h; + OGC_set_viewport(0, 0, renderer->window->w, renderer->window->h); + OGC_SetBlendMode(renderer, SDL_BLENDMODE_NONE); + + GX_ClearVtxDesc(); + GX_SetVtxDesc(GX_VA_POS, GX_DIRECT); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0); + GX_SetTevColor(GX_TEVREG0, c); + GX_SetTevColorIn(GX_TEVSTAGE0, GX_CC_C0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO); + GX_SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_A0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO); + GX_SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); + GX_SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); + GX_SetNumTevStages(1); + GX_SetNumChans(0); + + GX_Begin(GX_QUADS, GX_VTXFMT0, 4); + GX_Position2s16(x1, y1); + GX_Position2s16(x2, y1); + GX_Position2s16(x2, y2); + GX_Position2s16(x1, y2); + GX_End(); + data->ops_after_present++; + + /* Restore the viewport */ + SDL_Rect viewport; + SDL_GetRenderViewport(renderer, &viewport); + OGC_set_viewport(viewport.x, viewport.y, viewport.w, viewport.h); + return 0; +} + +static int OGC_RenderGeometry(SDL_Renderer *renderer, void *vertices, + SDL_RenderCommand *cmd) +{ + OGC_RenderData *data = renderer->internal; + const size_t count = cmd->data.draw.count; + SDL_Texture *texture = cmd->data.draw.texture; + size_t size_per_element; + + data->ops_after_present++; + OGC_SetBlendMode(renderer, cmd->data.draw.blend); + + size_per_element = sizeof(SDL_FPoint) + sizeof(GXColor); + + GX_ClearVtxDesc(); + GX_SetVtxDesc(GX_VA_POS, GX_DIRECT); + GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_F32, 0); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0); + if (texture) { + OGC_TextureData *ogc_tex = texture->internal; + u8 stage; + + size_per_element += sizeof(SDL_FPoint); + OGC_load_texture(ogc_tex->texels, texture->w, texture->h, + ogc_tex->format, texture->scaleMode); + stage = GX_TEVSTAGE0 + ogc_tex->needed_stages - 1; + + GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0); + GX_SetNumTexGens(1); + + GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); + GX_SetTevOrder(stage, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0); + GX_SetTevOp(stage, GX_MODULATE); + GX_SetNumTevStages(stage - GX_TEVSTAGE0 + 1); + } else { + GX_SetTevOp(GX_TEVSTAGE0, GX_PASSCLR); + GX_SetNumTevStages(1); + } + GX_SetNumChans(1); + + GX_Begin(GX_TRIANGLES, GX_VTXFMT0, count); + for (int i = 0; i < count; i++) { + void *vertex = vertices + cmd->data.draw.first + size_per_element * i; + SDL_FPoint *vertex_xy = vertex; + GXColor c = *(GXColor*)(vertex + sizeof(SDL_FPoint)); + + GX_Position2f32(vertex_xy->x, vertex_xy->y); + GX_Color4u8(c.r, c.g, c.b, c.a); + if (texture) { + SDL_FPoint *vertex_uv = (SDL_FPoint *)(vertex + sizeof(SDL_FPoint) + sizeof(GXColor)); + GX_TexCoord2f32(vertex_uv->x, vertex_uv->y); + } + } + GX_End(); + return 0; +} + +int OGC_RenderPrimitive(SDL_Renderer *renderer, u8 primitive, + void *vertices, SDL_RenderCommand *cmd) +{ + OGC_RenderData *data = renderer->internal; + size_t count = cmd->data.draw.count; + const SDL_FPoint *verts = (SDL_FPoint *)((Uint8 *)vertices + cmd->data.draw.first); + Mtx mv; + bool did_change_matrix = false; + GXColor c = scaled_color(&cmd->data.draw.color, cmd->data.draw.color_scale); + + data->ops_after_present++; + OGC_SetBlendMode(renderer, cmd->data.draw.blend); + + if (primitive == GX_LINESTRIP || primitive == GX_POINTS) { + float adjustment = 0.5; + guMtxIdentity(mv); + guMtxTransApply(mv, mv, adjustment, adjustment, 0); + GX_LoadPosMtxImm(mv, GX_PNMTX0); + did_change_matrix = true; + } + + /* TODO: optimize state changes. */ + GX_SetTevColor(GX_TEVREG0, c); + GX_SetTevColorIn(GX_TEVSTAGE0, GX_CC_C0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO); + GX_SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_A0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO); + GX_SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); + GX_SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); + + GX_ClearVtxDesc(); + GX_SetVtxDesc(GX_VA_POS, GX_DIRECT); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_F32, 0); + + if (primitive == GX_QUADS) count *= 4; + + GX_Begin(primitive, GX_VTXFMT0, count); + for (int i = 0; i < count; i++) { + GX_Position2f32(verts[i].x, verts[i].y); + } + GX_End(); + + /* The last point is not drawn */ + if (primitive == GX_LINESTRIP) { + GX_Begin(GX_POINTS, GX_VTXFMT0, count); + for (int i = 0; i < count; i++) { + GX_Position2f32(verts[i].x, verts[i].y); + } + GX_End(); + } + + if (did_change_matrix) { + guMtxIdentity(mv); + GX_LoadPosMtxImm(mv, GX_PNMTX0); + } + + return 0; +} + +static bool OGC_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize) +{ + while (cmd) { + switch (cmd->command) { + case SDL_RENDERCMD_SETVIEWPORT: + OGC_RenderSetViewPort(renderer, cmd); + break; + case SDL_RENDERCMD_SETCLIPRECT: + OGC_RenderSetClipRect(renderer, cmd); + break; + case SDL_RENDERCMD_SETDRAWCOLOR: + /* This is a no-op, since every command carries the color, and + * setting it on the FIFO is not expensive. */ + break; + case SDL_RENDERCMD_CLEAR: + OGC_RenderClear(renderer, cmd); + break; + case SDL_RENDERCMD_DRAW_POINTS: + OGC_RenderPrimitive(renderer, GX_POINTS, vertices, cmd); + break; + case SDL_RENDERCMD_DRAW_LINES: + OGC_RenderPrimitive(renderer, GX_LINESTRIP, vertices, cmd); + break; + case SDL_RENDERCMD_FILL_RECTS: + OGC_RenderPrimitive(renderer, GX_QUADS, vertices, cmd); + break; + case SDL_RENDERCMD_COPY: /* unused */ + break; + case SDL_RENDERCMD_COPY_EX: /* unused */ + break; + case SDL_RENDERCMD_GEOMETRY: + OGC_RenderGeometry(renderer, vertices, cmd); + break; + case SDL_RENDERCMD_NO_OP: + break; + } + cmd = cmd->next; + } + + GX_DrawDone(); + return true; +} + +static bool OGC_RenderPresent(SDL_Renderer *renderer) +{ + OGC_RenderData *data = renderer->internal; + + GX_DrawDone(); + + OGC_video_flip(SDL_GetVideoDevice(), data->vsync); + + /* Mouse cursor and OSK can change the blending mode; restore it. */ + set_blend_mode_real(renderer, data->current_blend_mode); + + data->ops_after_present = 0; + return true; +} + +static void OGC_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture) +{ + OGC_TextureData *ogc_tex = texture->internal; + + if (ogc_tex) { + free(ogc_tex->texels); + SDL_free(ogc_tex); + texture->internal = NULL; + } +} + +static void OGC_DestroyRenderer(SDL_Renderer *renderer) +{ + OGC_RenderData *data = renderer->internal; + + if (data) { + GX_DrawDone(); + if (data->saved_efb_texture) { + OGC_DestroyTexture(renderer, data->saved_efb_texture); + SDL_free(data->saved_efb_texture); + } + + SDL_free(data); + } +} + +static bool OGC_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, + SDL_PropertiesID create_props) +{ + OGC_RenderData *data; + + SDL_SetupRendererColorspace(renderer, create_props); + + if (renderer->output_colorspace != SDL_COLORSPACE_SRGB) { + return SDL_SetError("Unsupported output colorspace"); + } + + data = (OGC_RenderData *)SDL_calloc(1, sizeof(*data)); + if (!data) { + OGC_DestroyRenderer(renderer); + SDL_OutOfMemory(); + return NULL; + } + + data->efb_pixel_format = GX_PF_RGB8_Z24; + data->current_blend_mode = SDL_BLENDMODE_INVALID; + data->vsync = true; + + renderer->WindowEvent = OGC_WindowEvent; + renderer->CreateTexture = OGC_CreateTexture; + renderer->UpdateTexture = OGC_UpdateTexture; + renderer->LockTexture = OGC_LockTexture; + renderer->UnlockTexture = OGC_UnlockTexture; + renderer->SetRenderTarget = OGC_SetRenderTarget; + renderer->QueueSetViewport = OGC_QueueNoOp; + renderer->QueueSetDrawColor = OGC_QueueNoOp; + renderer->QueueDrawPoints = OGC_QueueDrawPoints; + renderer->QueueDrawLines = OGC_QueueDrawPoints; + renderer->QueueFillRects = OGC_QueueFillRects; + renderer->QueueGeometry = OGC_QueueGeometry; + renderer->RunCommandQueue = OGC_RunCommandQueue; + renderer->RenderPresent = OGC_RenderPresent; + renderer->DestroyTexture = OGC_DestroyTexture; + renderer->DestroyRenderer = OGC_DestroyRenderer; + renderer->internal = data; + renderer->window = window; + + renderer->name = OGC_RenderDriver.name; + renderer->npot_texture_wrap_unsupported = true; + + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGB565); + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA8888); + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ARGB8888); + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGB24); + SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_XRGB8888); + SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), + SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, 1024); + if (!SDL_GetHint(SDL_HINT_RENDER_LINE_METHOD)) { + /* SDL sets the default one to point drawing, but we prefer lines */ + SDL_SetHint(SDL_HINT_RENDER_LINE_METHOD, "2"); + } + + return renderer; +} + +SDL_RenderDriver OGC_RenderDriver = { + OGC_CreateRenderer, "ogc" +}; + +#endif /* SDL_VIDEO_RENDER_OGC */ From 7bc726114342c657525387d54ed6d1f61668f465 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Fri, 14 Aug 2026 18:13:13 +0300 Subject: [PATCH 13/14] ogc: disable unused/undesired modules These only take up space in the program's code. --- CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9135567c935a0..471f2cbdf1a3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -430,6 +430,15 @@ if (NGAGE) set(SDL_VIRTUAL_JOYSTICK OFF) endif() +if (OGC) + set(SDL_DISKAUDIO OFF) + set(SDL_DUMMYAUDIO OFF) + set(SDL_DUMMYCAMERA OFF) + set(SDL_DUMMYVIDEO OFF) + set(SDL_OFFSCREEN OFF) + set(SDL_VIRTUAL_JOYSTICK OFF) +endif() + if(NOT (SDL_SHARED OR SDL_STATIC)) message(FATAL_ERROR "SDL_SHARED and SDL_STATIC cannot both be disabled") endif() From c6346983e5f4e32c3b1a8d4f5a6a61eb214b2c82 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Sat, 15 Aug 2026 17:47:12 +0300 Subject: [PATCH 14/14] ogc: Revert "Make sure native textures have the same channel precision if possible" This reverts commit be45038432d8e492755e00624cf80ca15156aaad. Causes https://github.com/libsdl-org/SDL/issues/16156 --- src/render/SDL_render.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c index 790f8ae641556..abc24dfebd2a4 100644 --- a/src/render/SDL_render.c +++ b/src/render/SDL_render.c @@ -1478,12 +1478,10 @@ static SDL_PixelFormat GetClosestSupportedFormat(SDL_Renderer *renderer, SDL_Pix } else { bool hasAlpha = SDL_ISPIXELFORMAT_ALPHA(format); bool isIndexed = SDL_ISPIXELFORMAT_INDEXED(format); - int size = SDL_BYTESPERPIXEL(format); // We just want to match the first format that has the same channels for (i = 0; i < renderer->num_texture_formats; ++i) { if (!SDL_ISPIXELFORMAT_FOURCC(renderer->texture_formats[i]) && - SDL_BYTESPERPIXEL(renderer->texture_formats[i]) == size && SDL_ISPIXELFORMAT_ALPHA(renderer->texture_formats[i]) == hasAlpha && SDL_ISPIXELFORMAT_INDEXED(renderer->texture_formats[i]) == isIndexed) { return renderer->texture_formats[i]; @@ -1857,11 +1855,9 @@ SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *s // Indexed formats don't support the transparency needed for color-keyed surfaces bool preferIndexed = SDL_ISPIXELFORMAT_INDEXED(surface->format) && !needAlpha; - int size = SDL_BYTESPERPIXEL(format); for (i = 0; i < renderer->num_texture_formats; ++i) { if (!SDL_ISPIXELFORMAT_FOURCC(renderer->texture_formats[i]) && - SDL_BYTESPERPIXEL(renderer->texture_formats[i]) == size && SDL_ISPIXELFORMAT_ALPHA(renderer->texture_formats[i]) == needAlpha && SDL_ISPIXELFORMAT_INDEXED(renderer->texture_formats[i]) == preferIndexed) { format = renderer->texture_formats[i];