diff --git a/CMakeLists.txt b/CMakeLists.txt index 1928e514edeed..471f2cbdf1a3a 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) @@ -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() @@ -3336,6 +3345,79 @@ elseif(N3DS) "${SDL3_SOURCE_DIR}/src/io/n3ds/*.h" ) +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() + + 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) + + 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) + + set(SDL_TIMER_OGC 1) + 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) + + 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/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) + 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_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/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..7a8a53f5d36be 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 @@ -311,6 +312,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 @@ -379,6 +381,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 +394,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 @@ -406,6 +410,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 @@ -459,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 @@ -518,6 +524,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/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_ */ 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/filesystem/ogc/SDL_sysfilesystem.c b/src/filesystem/ogc/SDL_sysfilesystem.c new file mode 100644 index 0000000000000..862ed72b74312 --- /dev/null +++ b/src/filesystem/ogc/SDL_sysfilesystem.c @@ -0,0 +1,95 @@ +/* + 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; +} + +#endif /* SDL_FILESYSTEM_OGC */ 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 */ 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 */ diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c index b77d33e6efdb1..abc24dfebd2a4 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 @@ -1475,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]; @@ -1854,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]; 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 */ 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) { 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 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 */ 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..c2cb9553f594f --- /dev/null +++ b/src/video/ogc/SDL_ogcframebuffer.c @@ -0,0 +1,126 @@ +/* + 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) +{ + GX_Begin(GX_QUADS, GX_VTXFMT0, 4); + 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(); +} + +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); + OGC_setup_2d_drawing(); + 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..1a82e5fda6354 --- /dev/null +++ b/src/video/ogc/SDL_ogcgxcommon.c @@ -0,0 +1,124 @@ +/* + 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 + +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_setup_2d_drawing() +{ + 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_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); + + 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..66d5935b56bf8 --- /dev/null +++ b/src/video/ogc/SDL_ogcgxcommon.h @@ -0,0 +1,36 @@ +/* + 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_setup_2d_drawing(); +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..c6953c5c04804 --- /dev/null +++ b/src/video/ogc/SDL_ogcmouse.c @@ -0,0 +1,408 @@ +/* + 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); + + OGC_setup_2d_drawing(); + + GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR); + + 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 && 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); + } + } +} + +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: */