From 212aa5a7912d6c7ea508815c2dfa0c00dbe4208a Mon Sep 17 00:00:00 2001 From: Ghabry Date: Fri, 20 Dec 2024 00:29:16 +0100 Subject: [PATCH 1/5] Wii: Add SDL2 support Fix #3054 Fix #3236 --- CMakeLists.txt | 137 +++++++++++++++++------------ src/game_config.cpp | 2 +- src/options.h | 4 + src/platform/sdl/sdl2_ui.cpp | 17 +++- src/platform/sdl/sdl_ui.cpp | 4 +- src/platform/wii/audio.cpp | 6 +- src/platform/wii/input_buttons.cpp | 46 +++++++++- src/platform/wii/main.cpp | 6 -- 8 files changed, 152 insertions(+), 70 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cb50b102d2..913ca18fe0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -555,7 +555,10 @@ elseif(NINTENDO_SWITCH) elseif(VITA) set(PLAYER_TARGET_PLATFORM "psvita" CACHE STRING "Platform to compile for.") elseif(NINTENDO_WII) - set(PLAYER_TARGET_PLATFORM "wii" CACHE STRING "Platform to compile for.") + set(PLAYER_TARGET_PLATFORM "SDL2" CACHE STRING "Platform to compile for. Options: SDL2 SDL1") + set(PLAYER_AUDIO_BACKEND "AESND" CACHE STRING "Audio system to use. Options: SDL2 SDL1 AESND OFF") + set_property(CACHE PLAYER_TARGET_PLATFORM PROPERTY STRINGS SDL2 SDL1) + set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS SDL2 SDL1 AESND) elseif(NINTENDO_WIIU) set(PLAYER_TARGET_PLATFORM "SDL2" CACHE STRING "Platform to compile for.") elseif(AMIGA) @@ -578,8 +581,6 @@ endif() if(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL2") target_sources(${PROJECT_NAME} PRIVATE - src/platform/sdl/sdl_audio.cpp - src/platform/sdl/sdl_audio.h src/platform/sdl/sdl2_ui.cpp src/platform/sdl/sdl2_ui.h) target_compile_definitions(${PROJECT_NAME} PUBLIC USE_SDL=2) @@ -597,9 +598,13 @@ if(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL2") if(ANDROID) set(PLAYER_BUILD_EXECUTABLE OFF) - endif() - - if(NINTENDO_WIIU) + elseif(NINTENDO_WII) + target_compile_definitions(${PROJECT_NAME} PUBLIC PLAYER_NINTENDO) + target_sources(${PROJECT_NAME} PRIVATE + src/platform/wii/clock.cpp + src/platform/wii/clock.h + src/platform/wii/input_buttons.cpp) + elseif(NINTENDO_WIIU) target_compile_definitions(${PROJECT_NAME} PUBLIC PLAYER_NINTENDO) target_sources(${PROJECT_NAME} PRIVATE src/platform/wiiu/main.h @@ -610,15 +615,26 @@ if(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL2") target_link_libraries(${PROJECT_NAME} "Dwmapi") endif() elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL1") + if(NINTENDO_WII) + find_package(SDL REQUIRED) + target_compile_definitions(${PROJECT_NAME} PUBLIC PLAYER_NINTENDO) + target_include_directories(${PROJECT_NAME} PUBLIC ${SDL_INCLUDE_DIR}) + target_sources(${PROJECT_NAME} PRIVATE + src/platform/wii/clock.cpp + src/platform/wii/clock.h + src/platform/wii/input_buttons.cpp) + else() + player_find_package(NAME SDL1 TARGET SDL::SDLmain REQUIRED) + endif() + target_sources(${PROJECT_NAME} PRIVATE - src/platform/sdl/sdl_audio.cpp - src/platform/sdl/sdl_audio.h src/platform/sdl/axis.h src/platform/sdl/sdl_ui.cpp src/platform/sdl/sdl_ui.h) - target_compile_definitions(${PROJECT_NAME} PUBLIC USE_SDL=1) - - player_find_package(NAME SDL1 TARGET SDL::SDLmain REQUIRED) + target_compile_definitions(${PROJECT_NAME} PUBLIC + USE_SDL=1 + EASYRPG_CONFIG_NAME="config_sdl1.ini" + ) elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "libretro") target_compile_definitions(${PROJECT_NAME} PUBLIC PLAYER_UI=LibretroUi USE_LIBRETRO=1) set(PLAYER_BUILD_EXECUTABLE OFF) @@ -682,31 +698,63 @@ elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "switch") src/platform/switch/ui.cpp src/platform/switch/ui.h) target_link_libraries(${PROJECT_NAME} switch-assets) -elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "wii") - find_package(SDL REQUIRED) - target_compile_definitions(${PROJECT_NAME} PUBLIC USE_SDL=1 PLAYER_NINTENDO) - target_include_directories(${PROJECT_NAME} PUBLIC ${SDL_INCLUDE_DIR}) +else() + message(FATAL_ERROR "Invalid target platform") +endif() + +# Sound system to use +if(PLAYER_AUDIO_BACKEND) + # already set earlier in the platform config +elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL2") + set(PLAYER_AUDIO_BACKEND "SDL2" CACHE STRING "Audio system to use. Options: SDL2 OFF") + set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS SDL2 OFF) + + if(${PLAYER_AUDIO_BACKEND} STREQUAL "SDL2_mixer") + message(FATAL_ERROR "SDL2_mixer is not supported anymore. Use SDL2 instead.") + endif() +elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL1") + set(PLAYER_AUDIO_BACKEND "SDL1" CACHE STRING "Audio system to use. Options: SDL1 OFF") + set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS SDL1 OFF) +else() + # Assuming that all platforms not targeting SDL have only one audio backend + set(PLAYER_AUDIO_BACKEND ${PLAYER_TARGET_PLATFORM} CACHE STRING "Audio system to use. Options: ${PLAYER_TARGET_PLATFORM} OFF") + set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS ${PLAYER_TARGET_PLATFORM} OFF) +endif() + +set(PLAYER_HAS_AUDIO ON) +if(${PLAYER_AUDIO_BACKEND} STREQUAL "SDL1") + target_sources(${PROJECT_NAME} PRIVATE + src/platform/sdl/sdl_audio.cpp + src/platform/sdl/sdl_audio.h) +elseif(${PLAYER_AUDIO_BACKEND} STREQUAL "SDL2") + target_sources(${PROJECT_NAME} PRIVATE + src/platform/sdl/sdl_audio.cpp + src/platform/sdl/sdl_audio.h) +elseif(${PLAYER_AUDIO_BACKEND} STREQUAL "AESND") target_sources(${PROJECT_NAME} PRIVATE src/platform/wii/audio.cpp - src/platform/wii/audio.h - src/platform/wii/clock.h - src/platform/wii/input_buttons.cpp - src/platform/sdl/axis.h - src/platform/sdl/sdl_ui.cpp - src/platform/sdl/sdl_ui.h) + src/platform/wii/audio.h) + target_compile_definitions(${PROJECT_NAME} PUBLIC AUDIO_AESND=1) +elseif(${PLAYER_AUDIO_BACKEND} STREQUAL ${PLAYER_TARGET_PLATFORM}) + +elseif(${PLAYER_AUDIO_BACKED} STREQUAL "OFF") + set(PLAYER_HAS_AUDIO OFF) else() - message(FATAL_ERROR "Invalid target platform") + message(FATAL_ERROR "Invalid Audio Backend ${PLAYER_AUDIO_BACKEND}") endif() # Shared by homebrew platforms -if(${PLAYER_TARGET_PLATFORM} MATCHES "^(3ds|psvita|switch|wii)$" OR NINTENDO_WIIU) +if(NINTENDO_3DS OR NINTENDO_WII OR NINTENDO_WIIU OR NINTENDO_WIIU OR NINTENDO_SWITCH OR VITA) target_compile_options(${PROJECT_NAME} PUBLIC -fno-exceptions -fno-rtti) set(CMAKE_DL_LIBS "") # hack4icu! set(PLAYER_ENABLE_TESTS OFF) option(PLAYER_VERSIONED_PACKAGES "Create zip packages with versioned name (for internal use)" ON) + set(PLAYER_CONSOLE_PORT ON) +else() + set(PLAYER_CONSOLE_PORT OFF) endif() # Make content available (romfs/wuhb bundle) -if(${PLAYER_TARGET_PLATFORM} MATCHES "^(3ds|switch)$" OR NINTENDO_WIIU) +if(NINTENDO_3DS OR NINTENDO_WIIU OR NINTENDO_SWITCH) option(PLAYER_BUNDLE "Embed a directory in the executable" OFF) set(PLAYER_BUNDLE_PATH "content" CACHE PATH "Directory to include in executable") set(BUNDLE_ARG "_IGNORE_ME") @@ -851,8 +899,7 @@ target_link_libraries(${PROJECT_NAME} PIXMAN::PIXMAN) # Always enable Wine registry support on non-Windows, but not for console ports if(NOT CMAKE_SYSTEM_NAME STREQUAL "Windows" - AND NOT ${PLAYER_TARGET_PLATFORM} MATCHES "^(psvita|3ds|switch|wii)$" - AND NOT NINTENDO_WIIU) + AND NOT PLAYER_CONSOLE) target_compile_definitions(${PROJECT_NAME} PUBLIC HAVE_WINE=1) endif() @@ -903,26 +950,8 @@ else() ) endif() -# Sound system to use -if(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL2") - set(PLAYER_AUDIO_BACKEND "SDL2" CACHE STRING "Audio system to use. Options: SDL2 OFF") - set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS SDL2 OFF) - - if(${PLAYER_AUDIO_BACKEND} STREQUAL "SDL2_mixer") - message(FATAL_ERROR "SDL2_mixer is not supported anymore. Use SDL2 instead.") - endif() -elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL1") - set(PLAYER_AUDIO_BACKEND "SDL1" CACHE STRING "Audio system to use. Options: SDL1 OFF") - set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS SDL1 OFF) -else() - # Assuming that all platforms not targeting SDL have only one audio backend - set(PLAYER_AUDIO_BACKEND "Default" CACHE STRING "Audio system to use. Options: Default OFF") - set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS Default OFF) -endif() - # Configure Audio backends -if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL[12]|Default)$") - set(PLAYER_HAS_AUDIO ON) +if(PLAYER_HAS_AUDIO) target_compile_definitions(${PROJECT_NAME} PUBLIC SUPPORT_AUDIO=1) if(${PLAYER_TARGET_PLATFORM} STREQUAL "libretro") @@ -964,10 +993,6 @@ if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL[12]|Default)$") if(PLAYER_ENABLE_FMMIDI) target_compile_definitions(${PROJECT_NAME} PUBLIC WANT_FMMIDI=1) endif() -elseif(NOT PLAYER_AUDIO_BACKEND) - set(PLAYER_HAS_AUDIO OFF) -else() - message(FATAL_ERROR "Invalid Audio Backend ${PLAYER_AUDIO_BACKEND}") endif() CMAKE_DEPENDENT_OPTION(PLAYER_WITH_MPG123 "Play MP3 audio with libmpg123" ON "PLAYER_HAS_AUDIO" OFF) @@ -980,7 +1005,7 @@ CMAKE_DEPENDENT_OPTION(PLAYER_WITH_FLUIDLITE "Play MIDI audio with fluidlite" ON CMAKE_DEPENDENT_OPTION(PLAYER_WITH_XMP "Play MOD audio with libxmp" ON "PLAYER_HAS_AUDIO" OFF) CMAKE_DEPENDENT_OPTION(PLAYER_ENABLE_DRWAV "Play WAV audio with dr_wav (built-in). Unsupported files are played by libsndfile." ON "PLAYER_HAS_AUDIO" OFF) -if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL[12]|Default)$") +if(PLAYER_HAS_AUDIO) set(PLAYER_AUDIO_RESAMPLER "Auto" CACHE STRING "Audio resampler to use. Options: Auto speexdsp samplerate OFF") set_property(CACHE PLAYER_AUDIO_RESAMPLER PROPERTY STRINGS Auto speexdsp samplerate OFF) @@ -1096,7 +1121,7 @@ if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL[12]|Default)$") endif() # Executable -if(${PLAYER_BUILD_EXECUTABLE} AND ${PLAYER_TARGET_PLATFORM} MATCHES "^SDL[12]$" AND NOT NINTENDO_WIIU) +if(${PLAYER_BUILD_EXECUTABLE} AND ${PLAYER_TARGET_PLATFORM} MATCHES "^SDL.*$" AND NOT PLAYER_CONSOLE_PORT) if(APPLE) set(EXE_NAME "EasyRPG-Player.app") set_source_files_properties(${${PROJECT_NAME}_BUNDLE_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") @@ -1198,7 +1223,7 @@ if(${PLAYER_BUILD_EXECUTABLE} AND ${PLAYER_TARGET_PLATFORM} MATCHES "^SDL[12]$" endif() install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PLAYER_JS_OUTPUT_NAME}.wasm DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() -elseif(${PLAYER_TARGET_PLATFORM} MATCHES "^(psvita|3ds|switch|wii)$" OR NINTENDO_WIIU) +elseif(PLAYER_CONSOLE_PORT) set(CPACK_PLATFORM "${PLAYER_TARGET_PLATFORM}") if(NINTENDO_3DS) add_executable(easyrpg-player src/platform/3ds/main.cpp) @@ -1240,6 +1265,10 @@ elseif(${PLAYER_TARGET_PLATFORM} MATCHES "^(psvita|3ds|switch|wii)$" OR NINTENDO -laesnd -lfat -lwiikeyboard) + if(TARGET SDL2::SDL2main) + # Missing dependency? + target_link_libraries(SDL2::SDL2main INTERFACE -lfat) + endif() ogc_create_dol(easyrpg-player) string(TIMESTAMP WII_DATE "%Y%m%d000000") configure_file(resources/wii/meta.xml.in resources/wii/meta.xml @ONLY) @@ -1299,7 +1328,7 @@ elseif(${PLAYER_TARGET_PLATFORM} MATCHES "^(psvita|3ds|switch|wii)$" OR NINTENDO endif() install(TARGETS easyrpg-player RUNTIME DESTINATION . COMPONENT debug) # debug information - if(${PLAYER_TARGET_PLATFORM} MATCHES "^(3ds|switch|wii)$" OR NINTENDO_WIIU) + if(NINTENDO_3DS OR NINTENDO_WII OR NINTENDO_WIIU OR NINTENDO_SWITCH) dkp_target_generate_symbol_list(easyrpg-player) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/easyrpg-player.map ${CMAKE_CURRENT_BINARY_DIR}/easyrpg-player.lst @@ -1506,7 +1535,7 @@ if(PLAYER_BUILD_LIBLCF) endif() message(STATUS "Audio backend: ${PLAYER_AUDIO_BACKEND}") -if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL[12]|Default)$") +if(PLAYER_HAS_AUDIO) message(STATUS "") set(WAV_LIBS) diff --git a/src/game_config.cpp b/src/game_config.cpp index 7eb6b9da05..cdcd5977bf 100644 --- a/src/game_config.cpp +++ b/src/game_config.cpp @@ -37,7 +37,7 @@ namespace { std::string config_path; std::string soundfont_path; std::string font_path; - StringView config_name = "config.ini"; + StringView config_name = EASYRPG_CONFIG_NAME; } void Game_ConfigPlayer::Hide() { diff --git a/src/options.h b/src/options.h index fc05038da0..226927ec17 100644 --- a/src/options.h +++ b/src/options.h @@ -56,6 +56,10 @@ #define INI_NAME "RPG_RT.ini" #define EASYRPG_INI_NAME "EasyRPG.ini" +#ifndef EASYRPG_CONFIG_NAME +# define EASYRPG_CONFIG_NAME "config.ini" +#endif + /** Prefix for .ldb and .lmt files; used when guessing non-standard extensions. */ #define RPG_RT_PREFIX "RPG_RT" #define EASY_RT_PREFIX "EASY_RT" diff --git a/src/platform/sdl/sdl2_ui.cpp b/src/platform/sdl/sdl2_ui.cpp index b97eea818d..355f6184be 100644 --- a/src/platform/sdl/sdl2_ui.cpp +++ b/src/platform/sdl/sdl2_ui.cpp @@ -48,7 +48,11 @@ #endif #ifdef SUPPORT_AUDIO -# include "sdl_audio.h" +# if AUDIO_AESND +# include "platform/wii/audio.h" +# else +# include "sdl_audio.h" +# endif AudioInterface& Sdl2Ui::GetAudio() { return *audio_; @@ -198,10 +202,11 @@ Sdl2Ui::Sdl2Ui(long width, long height, const Game_Config& cfg) : BaseUi(cfg) #endif #ifdef SUPPORT_AUDIO - if (!Player::no_audio_flag) { +# ifdef AUDIO_AESND + audio_ = std::make_unique(cfg.audio); +# else audio_ = std::make_unique(cfg.audio); - return; - } +# endif #endif } @@ -1263,6 +1268,8 @@ int FilterUntilFocus(const SDL_Event* evnt) { void Sdl2Ui::vGetConfig(Game_ConfigVideo& cfg) const { #ifdef EMSCRIPTEN cfg.renderer.Lock("SDL2 (Software, Emscripten)"); +#elif defined(__wii__) + cfg.renderer.Lock("SDL2 (Software, Wii)"); #elif defined(__WIIU__) cfg.renderer.Lock("SDL2 (Software, Wii U)"); #else @@ -1296,6 +1303,8 @@ void Sdl2Ui::vGetConfig(Game_ConfigVideo& cfg) const { cfg.vsync.SetOptionVisible(false); cfg.pause_when_focus_lost.Lock(false); cfg.pause_when_focus_lost.SetOptionVisible(false); +#elif defined(__wii__) + cfg.fullscreen.SetOptionVisible(false); #elif defined(__WIIU__) // Only makes the screen flicker cfg.fullscreen.SetOptionVisible(false); diff --git a/src/platform/sdl/sdl_ui.cpp b/src/platform/sdl/sdl_ui.cpp index 29fc05f7d2..93f0d0d30c 100644 --- a/src/platform/sdl/sdl_ui.cpp +++ b/src/platform/sdl/sdl_ui.cpp @@ -29,7 +29,7 @@ #include "bitmap.h" #ifdef SUPPORT_AUDIO -# ifdef __wii__ +# if AUDIO_AESND # include "platform/wii/audio.h" # else # include "sdl_audio.h" @@ -167,7 +167,7 @@ SdlUi::SdlUi(long width, long height, const Game_Config& cfg) : BaseUi(cfg) #ifdef SUPPORT_AUDIO if (!Player::no_audio_flag) { -# ifdef __wii__ +# ifdef AUDIO_AESND audio_ = std::make_unique(cfg.audio); # else audio_ = std::make_unique(cfg.audio); diff --git a/src/platform/wii/audio.cpp b/src/platform/wii/audio.cpp index 0c38c13653..7c440994a8 100644 --- a/src/platform/wii/audio.cpp +++ b/src/platform/wii/audio.cpp @@ -53,16 +53,18 @@ static void VoiceStreamCallback(AESNDPB *pb, u32 state) { static void *AudioThread (void *) { while (!stopaudio) { + instance->LockMutex(); + // clear old data memset(buffer[cur_buf], 0, SNDBUFFERSIZE); - instance->LockMutex(); instance->Decode(buffer[cur_buf], SNDBUFFERSIZE); - instance->UnlockMutex(); // make sure data is in main memory DCFlushRange(buffer[cur_buf], SNDBUFFERSIZE); + instance->UnlockMutex(); + LWP_ThreadSleep(audioqueue); } return nullptr; diff --git a/src/platform/wii/input_buttons.cpp b/src/platform/wii/input_buttons.cpp index da44992212..a291f84ea5 100644 --- a/src/platform/wii/input_buttons.cpp +++ b/src/platform/wii/input_buttons.cpp @@ -19,10 +19,11 @@ #include "input_buttons.h" #include "keys.h" #include "game_config.h" -#include "platform/sdl/axis.h" + Input::ButtonMappingArray Input::GetDefaultButtonMappings() { return { +#if USE_SDL==1 // Wiimote {DECISION, Keys::JOY_OTHER_0}, // A, shared with Classic Controller (CC) {CANCEL, Keys::JOY_OTHER_1}, // B, shared with CC @@ -43,7 +44,47 @@ Input::ButtonMappingArray Input::GetDefaultButtonMappings() { {FAST_FORWARD_B, Keys::JOY_OTHER_12}, // R {N5, Keys::JOY_OTHER_13}, // ZL {TOGGLE_FPS, Keys::JOY_OTHER_14}, // ZR +#else +#if defined(USE_JOYSTICK) && defined(SUPPORT_JOYSTICK) + {UP, Keys::JOY_DPAD_UP}, + {DOWN, Keys::JOY_DPAD_DOWN}, + {LEFT, Keys::JOY_DPAD_LEFT}, + {RIGHT, Keys::JOY_DPAD_RIGHT}, + {DECISION, Keys::JOY_B}, + {CANCEL, Keys::JOY_A}, + {CANCEL, Keys::JOY_Y}, + {SHIFT, Keys::JOY_X}, + {N0, Keys::JOY_LSTICK}, + {N5, Keys::JOY_RSTICK}, + {DEBUG_ABORT_EVENT, Keys::JOY_SHOULDER_LEFT}, + {TOGGLE_FPS, Keys::JOY_SHOULDER_RIGHT}, + {SETTINGS_MENU, Keys::JOY_START}, + {RESET, Keys::JOY_BACK}, +#endif + +#if defined(USE_JOYSTICK_AXIS) && defined(SUPPORT_JOYSTICK_AXIS) + {UP, Keys::JOY_LSTICK_UP}, + {DOWN, Keys::JOY_LSTICK_DOWN}, + {LEFT, Keys::JOY_LSTICK_LEFT}, + {RIGHT, Keys::JOY_LSTICK_RIGHT}, + {N1, Keys::JOY_RSTICK_DOWN_LEFT}, + {N2, Keys::JOY_RSTICK_DOWN}, + {N3, Keys::JOY_RSTICK_DOWN_RIGHT}, + {N4, Keys::JOY_RSTICK_LEFT}, + {N6, Keys::JOY_RSTICK_RIGHT}, + {N7, Keys::JOY_RSTICK_UP_LEFT}, + {N8, Keys::JOY_RSTICK_UP}, + {N9, Keys::JOY_RSTICK_UP_RIGHT}, + {FAST_FORWARD_A, Keys::JOY_RTRIGGER_FULL}, + {DEBUG_MENU, Keys::JOY_LTRIGGER_FULL}, +#endif +#if defined(USE_TOUCH) && defined(SUPPORT_TOUCH) + {MOUSE_LEFT, Keys::ONE_FINGER}, + {MOUSE_RIGHT, Keys::TWO_FINGERS}, + {MOUSE_MIDDLE, Keys::THREE_FINGERS}, +#endif +#endif {UP, Keys::JOY_LSTICK_UP}, {DOWN, Keys::JOY_LSTICK_DOWN}, {LEFT, Keys::JOY_LSTICK_LEFT}, @@ -97,6 +138,8 @@ void Input::GetSupportedConfig(Game_ConfigInput& cfg) { cfg.gamepad_swap_dpad_with_buttons.SetOptionVisible(true); } +#if USE_SDL==1 +#include "platform/sdl/axis.h" SdlAxis Input::GetSdlAxis() { // Classic Controller L/R Trigger axis do not report proper values // Handled above as Button 11/12 @@ -104,4 +147,5 @@ SdlAxis Input::GetSdlAxis() { 0, 1, 2, 3, -1, -1, false, false }; } +#endif diff --git a/src/platform/wii/main.cpp b/src/platform/wii/main.cpp index 2f3d2808b0..f276f68704 100644 --- a/src/platform/wii/main.cpp +++ b/src/platform/wii/main.cpp @@ -38,9 +38,6 @@ namespace { bool is_emu = false; } -// in sdl-wii -extern "C" void OGC_ChangeSquare(int xscale, int yscale, int xshift, int yshift); - static void GekkoResetCallback(u32 /* irq */ , void* /* ctx */) { Player::reset_flag = true; } @@ -86,9 +83,6 @@ extern "C" int main(int argc, char* argv[]) { SYS_SetResetCallback(GekkoResetCallback); - // Eliminate overscan / add 5% borders - OGC_ChangeSquare(304, 228, 0, 0); - // Working directory not correctly handled, provide it manually bool want_cwd = true; if(is_emu || argv[0][0] == '/') { From cdac650c3d7d81b28f5bc424c85b88cf4d5f814c Mon Sep 17 00:00:00 2001 From: Ghabry Date: Thu, 16 Jan 2025 16:14:10 +0100 Subject: [PATCH 2/5] Wii: Further fixes - Improve button mapping and comments - Write SDL1 config in a different file --- CMakeLists.txt | 2 -- src/game_config.cpp | 6 +++- src/platform/wii/input_buttons.cpp | 54 +++++++++++++++++++++++------- 3 files changed, 47 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 913ca18fe0..87859f18f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -601,7 +601,6 @@ if(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL2") elseif(NINTENDO_WII) target_compile_definitions(${PROJECT_NAME} PUBLIC PLAYER_NINTENDO) target_sources(${PROJECT_NAME} PRIVATE - src/platform/wii/clock.cpp src/platform/wii/clock.h src/platform/wii/input_buttons.cpp) elseif(NINTENDO_WIIU) @@ -620,7 +619,6 @@ elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL1") target_compile_definitions(${PROJECT_NAME} PUBLIC PLAYER_NINTENDO) target_include_directories(${PROJECT_NAME} PUBLIC ${SDL_INCLUDE_DIR}) target_sources(${PROJECT_NAME} PRIVATE - src/platform/wii/clock.cpp src/platform/wii/clock.h src/platform/wii/input_buttons.cpp) else() diff --git a/src/game_config.cpp b/src/game_config.cpp index cdcd5977bf..82bd6d8f92 100644 --- a/src/game_config.cpp +++ b/src/game_config.cpp @@ -37,7 +37,12 @@ namespace { std::string config_path; std::string soundfont_path; std::string font_path; +#if USE_SDL == 1 + // For SDL1 hardcode a different config file because it uses a completely different mapping for gamepads + StringView config_name = "config_sdl1.ini"; +#else StringView config_name = EASYRPG_CONFIG_NAME; +#endif } void Game_ConfigPlayer::Hide() { @@ -93,7 +98,6 @@ Game_Config Game_Config::Create(CmdlineParser& cp) { cfg.input.gamepad_swap_ab_and_xy.Set(true); #endif - cp.Rewind(); config_path = GetConfigPath(cp); diff --git a/src/platform/wii/input_buttons.cpp b/src/platform/wii/input_buttons.cpp index a291f84ea5..3b9feb3ac4 100644 --- a/src/platform/wii/input_buttons.cpp +++ b/src/platform/wii/input_buttons.cpp @@ -50,16 +50,15 @@ Input::ButtonMappingArray Input::GetDefaultButtonMappings() { {DOWN, Keys::JOY_DPAD_DOWN}, {LEFT, Keys::JOY_DPAD_LEFT}, {RIGHT, Keys::JOY_DPAD_RIGHT}, - {DECISION, Keys::JOY_B}, - {CANCEL, Keys::JOY_A}, - {CANCEL, Keys::JOY_Y}, - {SHIFT, Keys::JOY_X}, - {N0, Keys::JOY_LSTICK}, - {N5, Keys::JOY_RSTICK}, + {DECISION, Keys::JOY_A}, + {CANCEL, Keys::JOY_B}, + {CANCEL, Keys::JOY_X}, + {SHIFT, Keys::JOY_Y}, {DEBUG_ABORT_EVENT, Keys::JOY_SHOULDER_LEFT}, {TOGGLE_FPS, Keys::JOY_SHOULDER_RIGHT}, - {SETTINGS_MENU, Keys::JOY_START}, - {RESET, Keys::JOY_BACK}, + {SETTINGS_MENU, Keys::JOY_BACK}, + {RESET, Keys::JOY_GUIDE}, + {N5, Keys::N5}, #endif #if defined(USE_JOYSTICK_AXIS) && defined(SUPPORT_JOYSTICK_AXIS) @@ -109,15 +108,16 @@ Input::ButtonMappingArray Input::GetDefaultButtonMappings() { Input::KeyNamesArray Input::GetInputKeyNames() { return { + // SDL1 {Keys::JOY_OTHER_0, "A (Wiimote / CC)"}, {Keys::JOY_OTHER_1, "B (Wiimote / CC)"}, - {Keys::JOY_OTHER_2, "1 (Wiimote"}, + {Keys::JOY_OTHER_2, "1 (Wiimote)"}, {Keys::JOY_OTHER_3, "2 (Wiimote)"}, - {Keys::JOY_OTHER_4, "- (Wiimote /CC )"}, + {Keys::JOY_OTHER_4, "- (Wiimote / CC)"}, {Keys::JOY_OTHER_5, "+ (Wiimote / CC)"}, {Keys::JOY_OTHER_6, "Home (Wiimote / CC)"}, {Keys::JOY_OTHER_7, "Z (Nunchuck)"}, - {Keys::JOY_OTHER_8, "C (Wiimote)"}, + {Keys::JOY_OTHER_8, "C (Nunchuck)"}, {Keys::JOY_OTHER_9, "X (CC)"}, {Keys::JOY_OTHER_10, "Y (CC)"}, {Keys::JOY_OTHER_11, "L (CC)"}, @@ -125,10 +125,40 @@ Input::KeyNamesArray Input::GetInputKeyNames() { {Keys::JOY_OTHER_13, "ZL (CC)"}, {Keys::JOY_OTHER_14, "ZR (CC)"}, + // SDL2 + // FIXME: The ABXY buttons are swapped when a CC is connected, automatically handle this? + // For Wiimote the reported buttons change depending whether a Nunchuck is connected + // When only a Wiimote is connected it is handled like a sideways Wiimote + // With Nunchuck/CC connected the buttons 1 and 2 report nothing + {Keys::JOY_A, "A"}, // 1 (Wiimote), A (+Nunchuck), B (+CC) + {Keys::JOY_B, "B"}, // 2 (Wiimote), B (+Nunchuck), A (+CC) + {Keys::JOY_X, "X (CC) / Z (Nunchuck)"}, // B (Wiimote) + {Keys::JOY_Y, "Y (CC) / C (Nunchuck)"}, // A (Wiimote) + {Keys::JOY_BACK, "HOME"}, + {Keys::JOY_START, "+"}, + {Keys::JOY_GUIDE, "-"}, + {Keys::JOY_SHOULDER_LEFT, "L"}, + {Keys::JOY_SHOULDER_RIGHT, "R"}, + {Keys::JOY_LTRIGGER_FULL, "ZL"}, + {Keys::JOY_RTRIGGER_FULL, "ZR"}, + + // Shared {Keys::JOY_DPAD_UP, "D-Pad Up"}, {Keys::JOY_DPAD_DOWN, "D-Pad Down"}, {Keys::JOY_DPAD_LEFT, "D-Pad Left"}, - {Keys::JOY_DPAD_RIGHT, "D-Pad Up"} + {Keys::JOY_DPAD_RIGHT, "D-Pad Up"}, + + {Keys::JOY_LSTICK, "Left Stick Press"}, + {Keys::JOY_LSTICK_UP, "Left Stick Up"}, + {Keys::JOY_LSTICK_DOWN, "Left Stick Down"}, + {Keys::JOY_LSTICK_LEFT, "Left Stick Left"}, + {Keys::JOY_LSTICK_RIGHT, "Left Stick Right"}, + + {Keys::JOY_RSTICK, "Right Stick Press"}, + {Keys::JOY_RSTICK_UP, "Right Stick Up"}, + {Keys::JOY_RSTICK_DOWN, "Right Stick Down"}, + {Keys::JOY_RSTICK_LEFT, "Right Stick Left"}, + {Keys::JOY_RSTICK_RIGHT, "Right Stick Right"}, }; } From fa09f8923e3bf6b48009527c0b7dbda4b2fc1014 Mon Sep 17 00:00:00 2001 From: Ghabry Date: Thu, 16 Jan 2025 16:22:31 +0100 Subject: [PATCH 3/5] WiiU: Further fixes - Fix OGG playback (Wii uses tremor, playback is fine) - Prevent Fullscreen toggling via command when the setting is disabled Allowing fullscreen toggling messes up the rendering on WiiU --- src/decoder_oggvorbis.cpp | 9 ++++++++- src/game_interpreter.cpp | 6 ++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/decoder_oggvorbis.cpp b/src/decoder_oggvorbis.cpp index 1a69564fa6..536558d492 100644 --- a/src/decoder_oggvorbis.cpp +++ b/src/decoder_oggvorbis.cpp @@ -207,7 +207,14 @@ int OggVorbisDecoder::FillBuffer(uint8_t* buffer, int length) { #ifdef HAVE_TREMOR read = ov_read(ovf, reinterpret_cast(buffer + length - to_read), to_read, §ion); #else - read = ov_read(ovf, reinterpret_cast(buffer + length - to_read), to_read, 0/*LE*/, 2/*16bit*/, 1/*signed*/, §ion); +# if defined(__WIIU__) + // FIXME: This is the endianess of the audio and not of the host but the byteswapping in ov_read does + // not sound like it works + int byte_order = 1; // BE +# else + int byte_order = 0; // LE +#endif + read = ov_read(ovf, reinterpret_cast(buffer + length - to_read), to_read, byte_order, 2/*16bit*/, 1/*signed*/, §ion); #endif // stop decoding when error or end of file if (read <= 0) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 9330e2089f..5126a9ffa5 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -4041,6 +4041,12 @@ bool Game_Interpreter::CommandToggleFullscreen(lcf::rpg::EventCommand const& /* return true; } + auto cfg = DisplayUi->GetConfig(); + if (!cfg.fullscreen.IsOptionVisible() || cfg.fullscreen.IsLocked()) { + Output::Debug("ToggleFullscreen: Not supported on this platform"); + return true; + } + DisplayUi->ToggleFullscreen(); return true; } From 13e106ecb3c70f545006c146673a99519e231482 Mon Sep 17 00:00:00 2001 From: Ghabry Date: Wed, 22 Jan 2025 16:23:52 +0100 Subject: [PATCH 4/5] Add SDL3 support Fix #2865 --- CMakeLists.txt | 40 +- Makefile.am | 15 +- README.md | 2 +- configure.ac | 13 +- src/baseui.cpp | 8 +- src/platform/sdl/main.cpp | 4 +- src/platform/sdl/sdl2_audio.cpp | 128 ++++ src/platform/sdl/sdl2_audio.h | 32 + src/platform/sdl/sdl2_ui.cpp | 8 +- src/platform/sdl/sdl3_audio.cpp | 131 ++++ src/platform/sdl/sdl3_audio.h | 32 + src/platform/sdl/sdl3_ui.cpp | 1256 +++++++++++++++++++++++++++++++ src/platform/sdl/sdl3_ui.h | 153 ++++ src/platform/sdl/sdl_audio.cpp | 51 -- src/window_settings.cpp | 7 +- 15 files changed, 1802 insertions(+), 78 deletions(-) create mode 100644 src/platform/sdl/sdl2_audio.cpp create mode 100644 src/platform/sdl/sdl2_audio.h create mode 100644 src/platform/sdl/sdl3_audio.cpp create mode 100644 src/platform/sdl/sdl3_audio.h create mode 100644 src/platform/sdl/sdl3_ui.cpp create mode 100644 src/platform/sdl/sdl3_ui.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 87859f18f0..105830855c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -564,8 +564,8 @@ elseif(NINTENDO_WIIU) elseif(AMIGA) set(PLAYER_TARGET_PLATFORM "SDL1" CACHE STRING "Platform to compile for.") else() - set(PLAYER_TARGET_PLATFORM "SDL2" CACHE STRING "Platform to compile for. Options: SDL2 SDL1 libretro") - set_property(CACHE PLAYER_TARGET_PLATFORM PROPERTY STRINGS SDL2 SDL1 libretro) + set(PLAYER_TARGET_PLATFORM "SDL3" CACHE STRING "Platform to compile for. Options: SDL3 SDL2 SDL1 libretro") + set_property(CACHE PLAYER_TARGET_PLATFORM PROPERTY STRINGS SDL3 SDL2 SDL1 libretro) endif() set(PLAYER_BUILD_EXECUTABLE ON) set(PLAYER_TEST_LIBRARIES ${PROJECT_NAME}) @@ -579,7 +579,20 @@ if(ANDROID AND PLAYER_GRADLE_BUILD) endforeach(f) endif() -if(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL2") +if(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL3") + target_sources(${PROJECT_NAME} PRIVATE + src/platform/sdl/sdl3_ui.cpp + src/platform/sdl/sdl3_ui.h) + target_compile_definitions(${PROJECT_NAME} PUBLIC USE_SDL=3) + + player_find_package(NAME SDL3 + TARGET SDL3::SDL3 + REQUIRED) + + if(ANDROID) + set(PLAYER_BUILD_EXECUTABLE OFF) + endif() +elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL2") target_sources(${PROJECT_NAME} PRIVATE src/platform/sdl/sdl2_ui.cpp src/platform/sdl/sdl2_ui.h) @@ -703,6 +716,9 @@ endif() # Sound system to use if(PLAYER_AUDIO_BACKEND) # already set earlier in the platform config +elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL3") + set(PLAYER_AUDIO_BACKEND "SDL3" CACHE STRING "Audio system to use. Options: SDL3 OFF") + set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS SDL3 OFF) elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL2") set(PLAYER_AUDIO_BACKEND "SDL2" CACHE STRING "Audio system to use. Options: SDL2 OFF") set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS SDL2 OFF) @@ -720,14 +736,18 @@ else() endif() set(PLAYER_HAS_AUDIO ON) -if(${PLAYER_AUDIO_BACKEND} STREQUAL "SDL1") +if(${PLAYER_AUDIO_BACKEND} STREQUAL "SDL3") target_sources(${PROJECT_NAME} PRIVATE - src/platform/sdl/sdl_audio.cpp - src/platform/sdl/sdl_audio.h) + src/platform/sdl/sdl3_audio.cpp + src/platform/sdl/sdl3_audio.h) elseif(${PLAYER_AUDIO_BACKEND} STREQUAL "SDL2") target_sources(${PROJECT_NAME} PRIVATE - src/platform/sdl/sdl_audio.cpp - src/platform/sdl/sdl_audio.h) + src/platform/sdl/sdl2_audio.cpp + src/platform/sdl/sdl2_audio.h) +elseif(${PLAYER_AUDIO_BACKEND} STREQUAL "SDL1") + target_sources(${PROJECT_NAME} PRIVATE + src/platform/sdl/sdl1_audio.cpp + src/platform/sdl/sdl1_audio.h) elseif(${PLAYER_AUDIO_BACKEND} STREQUAL "AESND") target_sources(${PROJECT_NAME} PRIVATE src/platform/wii/audio.cpp @@ -735,7 +755,7 @@ elseif(${PLAYER_AUDIO_BACKEND} STREQUAL "AESND") target_compile_definitions(${PROJECT_NAME} PUBLIC AUDIO_AESND=1) elseif(${PLAYER_AUDIO_BACKEND} STREQUAL ${PLAYER_TARGET_PLATFORM}) -elseif(${PLAYER_AUDIO_BACKED} STREQUAL "OFF") +elseif(${PLAYER_AUDIO_BACKEND} STREQUAL "OFF") set(PLAYER_HAS_AUDIO OFF) else() message(FATAL_ERROR "Invalid Audio Backend ${PLAYER_AUDIO_BACKEND}") @@ -1366,7 +1386,7 @@ else() # library if(WIN32) set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT easyrpg_libretro) endif() - elseif(ANDROID AND ${PLAYER_TARGET_PLATFORM} STREQUAL "SDL2") + elseif(ANDROID AND ${PLAYER_TARGET_PLATFORM} MATCHES "^SDL.*$") add_library(easyrpg_android src/platform/android/android.cpp src/platform/android/android.h diff --git a/Makefile.am b/Makefile.am index 02992d0f91..b2a4669d1b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -452,11 +452,22 @@ libeasyrpg_player_a_SOURCES = \ src/window_varlist.cpp \ src/window_varlist.h +SOURCEFILES_SDL3 += \ + src/platform/sdl/sdl3_ui.cpp \ + src/platform/sdl/sdl3_ui.h \ + src/platform/sdl/sdl3_audio.cpp \ + src/platform/sdl/sdl3_audio.h +if HAVE_SDL3 +libeasyrpg_player_a_SOURCES += $(SOURCEFILES_SDL3) +else +EXTRA_DIST += $(SOURCEFILES_SDL3) +endif + SOURCEFILES_SDL2 = \ src/platform/sdl/sdl2_ui.cpp \ src/platform/sdl/sdl2_ui.h \ - src/platform/sdl/sdl_audio.cpp \ - src/platform/sdl/sdl_audio.h + src/platform/sdl/sdl2_audio.cpp \ + src/platform/sdl/sdl2_audio.h if HAVE_SDL2 libeasyrpg_player_a_SOURCES += $(SOURCEFILES_SDL2) else diff --git a/README.md b/README.md index 180008aba6..243d3d8384 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Documentation is available at the documentation wiki: https://wiki.easyrpg.org ### minimal / required - [liblcf] for RPG Maker data reading. -- SDL2 >= 2.0.5 for screen backend support. +- SDL3 or SDL2 >= 2.0.5 for screen backend support. - Pixman for low level pixel manipulation. - libpng for PNG image support. - zlib for XYZ image and ZIP archive support. diff --git a/configure.ac b/configure.ac index 1802c78d05..ca852dccf2 100644 --- a/configure.ac +++ b/configure.ac @@ -83,12 +83,15 @@ PKG_CHECK_MODULES([FMT],[fmt],,[ AC_MSG_ERROR([Could not find libfmt! Consider installing version 5.3 or newer.]) ],[AC_MSG_RESULT([yes])]) ]) -PKG_CHECK_MODULES([SDL],[sdl2 >= 2.0.5],[sdl_version=2],[ - PKG_CHECK_MODULES([SDL],[sdl],[sdl_version=1],[ - AC_MSG_ERROR([Could not find SDL! Consider installing version 2.0.5 or newer.]) +PKG_CHECK_MODULES([SDL],[sdl3],[sdl_version=3],[ + PKG_CHECK_MODULES([SDL],[sdl2 >= 2.0.5],[sdl_version=2],[ + PKG_CHECK_MODULES([SDL],[sdl],[sdl_version=1],[ + AC_MSG_ERROR([Could not find SDL! Consider installing version 2.0.5 or newer.]) + ]) ]) ]) -AC_DEFINE_UNQUOTED([USE_SDL],[$sdl_version],[Enable SDL, version 2 or 1.2]) +AC_DEFINE_UNQUOTED([USE_SDL],[$sdl_version],[Enable SDL, version 3, 2 or 1.2]) +AM_CONDITIONAL([HAVE_SDL3], [test "x$sdl_version" = "x3"]) AM_CONDITIONAL([HAVE_SDL2], [test "x$sdl_version" = "x2"]) AM_CONDITIONAL([HAVE_SDL1], [test "x$sdl_version" = "x1"]) EP_PKG_CHECK([FREETYPE],[freetype2],[Custom Font rendering.]) @@ -219,6 +222,8 @@ if test "yes" != "$silent"; then echo " prefix: $prefix" echo " bash completion: $BASHCOMPLETION_DIR" + test "$sdl_version" = "3" && \ + echo "Backend: SDL3" test "$sdl_version" = "2" && \ echo "Backend: SDL2" test "$sdl_version" = "1" && \ diff --git a/src/baseui.cpp b/src/baseui.cpp index 482d9d02cb..486a2d5fce 100644 --- a/src/baseui.cpp +++ b/src/baseui.cpp @@ -20,7 +20,9 @@ #include "bitmap.h" #include "player.h" -#if USE_SDL==2 +#if USE_SDL==3 +# include "platform/sdl/sdl3_ui.h" +#elif USE_SDL==2 # include "platform/sdl/sdl2_ui.h" #elif USE_SDL==1 # include "platform/sdl/sdl_ui.h" @@ -37,7 +39,9 @@ std::shared_ptr DisplayUi; std::shared_ptr BaseUi::CreateUi(long width, long height, const Game_Config& cfg) { -#if USE_SDL==2 +#if USE_SDL==3 + return std::make_shared(width, height, cfg); +#elif USE_SDL==2 return std::make_shared(width, height, cfg); #elif USE_SDL==1 return std::make_shared(width, height, cfg); diff --git a/src/platform/sdl/main.cpp b/src/platform/sdl/main.cpp index aeffa5b805..b0fc202f83 100644 --- a/src/platform/sdl/main.cpp +++ b/src/platform/sdl/main.cpp @@ -22,7 +22,9 @@ #include "utils.h" #include "output.h" -#ifdef USE_SDL // This is needed on Windows, SDL wraps main() +#if USE_SDL == 3 // This is needed on Windows, SDL wraps main() +# include +#elif USE_SDL <= 2 # include #endif #ifdef _WIN32 diff --git a/src/platform/sdl/sdl2_audio.cpp b/src/platform/sdl/sdl2_audio.cpp new file mode 100644 index 0000000000..055f8b8c64 --- /dev/null +++ b/src/platform/sdl/sdl2_audio.cpp @@ -0,0 +1,128 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +#include "system.h" + +#ifdef SUPPORT_AUDIO + +#include +#include +#include +#include +#include +#include + +#ifdef EMSCRIPTEN +# include +#endif + +#include "sdl2_audio.h" +#include "output.h" + +using namespace std::chrono_literals; + +namespace { + SDL_AudioDeviceID audio_dev_id = 0; +} + +void sdl_audio_callback(void* userdata, uint8_t* stream, int length) { + // no mutex locking required, SDL does this before calling + + static_cast(userdata)->Decode(stream, length); +} + +AudioDecoder::Format sdl_format_to_format(Uint16 format) { + switch (format) { + case AUDIO_U8: + return AudioDecoder::Format::U8; + case AUDIO_S8: + return AudioDecoder::Format::S8; + case AUDIO_U16SYS: + return AudioDecoder::Format::U16; + case AUDIO_S16SYS: + return AudioDecoder::Format::S16; + case AUDIO_S32: + return AudioDecoder::Format::S32; + case AUDIO_F32: + return AudioDecoder::Format::F32; + default: + Output::Warning("Couldn't find GenericAudio format for {:#x}", format); + assert(false); + } + + return (AudioDecoder::Format)-1; +} + +Sdl2Audio::Sdl2Audio(const Game_ConfigAudio& cfg) : + GenericAudio(cfg) +{ + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + Output::Warning("Couldn't init audio: {}", SDL_GetError()); + return; + } + +#ifdef EMSCRIPTEN + // Get preferred sample rate from Browser (-> OS) + const int frequency = EM_ASM_INT_V({ + var context; + try { + context = new AudioContext(); + } catch (e) { + context = new webkitAudioContext(); + } + return context.sampleRate; + }); +#else + const int frequency = 44100; +#endif + + SDL_AudioSpec want = {}; + SDL_AudioSpec have = {}; + want.freq = frequency; + want.format = AUDIO_S16SYS; + want.channels = 2; + want.samples = 2048; + want.callback = sdl_audio_callback; + want.userdata = this; + + audio_dev_id = SDL_OpenAudioDevice(nullptr, 0, &want, &have, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); + bool init_success = audio_dev_id > 0; + + if (!init_success) { + Output::Warning("Couldn't open audio: {}", SDL_GetError()); + return; + } + + SetFormat(have.freq, sdl_format_to_format(have.format), have.channels); + + // Start Audio + SDL_PauseAudioDevice(audio_dev_id, 0); +} + +Sdl2Audio::~Sdl2Audio() { + SDL_CloseAudioDevice(audio_dev_id); +} + +void Sdl2Audio::LockMutex() const { + SDL_LockAudioDevice(audio_dev_id); +} + +void Sdl2Audio::UnlockMutex() const { + SDL_UnlockAudioDevice(audio_dev_id); +} + +#endif diff --git a/src/platform/sdl/sdl2_audio.h b/src/platform/sdl/sdl2_audio.h new file mode 100644 index 0000000000..6535ad3e35 --- /dev/null +++ b/src/platform/sdl/sdl2_audio.h @@ -0,0 +1,32 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +#ifndef EP_AUDIO_SDL2_H +#define EP_AUDIO_SDL2_H + +#include "audio_generic.h" + +class Sdl2Audio : public GenericAudio { +public: + Sdl2Audio(const Game_ConfigAudio& cfg); + ~Sdl2Audio(); + + void LockMutex() const override; + void UnlockMutex() const override; +}; + +#endif diff --git a/src/platform/sdl/sdl2_ui.cpp b/src/platform/sdl/sdl2_ui.cpp index 355f6184be..de40e1490d 100644 --- a/src/platform/sdl/sdl2_ui.cpp +++ b/src/platform/sdl/sdl2_ui.cpp @@ -51,7 +51,7 @@ # if AUDIO_AESND # include "platform/wii/audio.h" # else -# include "sdl_audio.h" +# include "sdl2_audio.h" # endif AudioInterface& Sdl2Ui::GetAudio() { @@ -205,7 +205,7 @@ Sdl2Ui::Sdl2Ui(long width, long height, const Game_Config& cfg) : BaseUi(cfg) # ifdef AUDIO_AESND audio_ = std::make_unique(cfg.audio); # else - audio_ = std::make_unique(cfg.audio); + audio_ = std::make_unique(cfg.audio); # endif #endif } @@ -1269,7 +1269,7 @@ void Sdl2Ui::vGetConfig(Game_ConfigVideo& cfg) const { #ifdef EMSCRIPTEN cfg.renderer.Lock("SDL2 (Software, Emscripten)"); #elif defined(__wii__) - cfg.renderer.Lock("SDL2 (Software, Wii)"); + cfg.renderer.Lock("SDL2 (Software, Wii)"); #elif defined(__WIIU__) cfg.renderer.Lock("SDL2 (Software, Wii U)"); #else @@ -1304,7 +1304,7 @@ void Sdl2Ui::vGetConfig(Game_ConfigVideo& cfg) const { cfg.pause_when_focus_lost.Lock(false); cfg.pause_when_focus_lost.SetOptionVisible(false); #elif defined(__wii__) - cfg.fullscreen.SetOptionVisible(false); + cfg.fullscreen.SetOptionVisible(false); #elif defined(__WIIU__) // Only makes the screen flicker cfg.fullscreen.SetOptionVisible(false); diff --git a/src/platform/sdl/sdl3_audio.cpp b/src/platform/sdl/sdl3_audio.cpp new file mode 100644 index 0000000000..01215df94a --- /dev/null +++ b/src/platform/sdl/sdl3_audio.cpp @@ -0,0 +1,131 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +#include "SDL3/SDL_audio.h" +#include "system.h" + +#ifdef SUPPORT_AUDIO + +#include +#include +#include +#include + +#ifdef EMSCRIPTEN +# include +#endif + +#include "sdl3_audio.h" +#include "output.h" + +using namespace std::chrono_literals; + +namespace { + SDL_AudioStream* audio_stream = nullptr; +} + +void sdl_audio_callback(void* userdata, SDL_AudioStream* stream, int additional_amount, int total_amount) { + // no mutex locking required, SDL does this before calling + + if (additional_amount > 0) { + static std::vector buffer; + buffer.resize(total_amount); + + static_cast(userdata)->Decode(buffer.data(), additional_amount); + SDL_PutAudioStreamData(stream, buffer.data(), additional_amount); + } +} + +AudioDecoder::Format sdl_format_to_format(Uint16 format) { + switch (format) { + case SDL_AUDIO_U8: + return AudioDecoder::Format::U8; + case SDL_AUDIO_S8: + return AudioDecoder::Format::S8; + case SDL_AUDIO_S16: + return AudioDecoder::Format::S16; + case SDL_AUDIO_S32: + return AudioDecoder::Format::S32; + case SDL_AUDIO_F32: + return AudioDecoder::Format::F32; + default: + Output::Warning("Couldn't find GenericAudio format for {:#x}", format); + assert(false); + } + + return (AudioDecoder::Format)-1; +} + +Sdl3Audio::Sdl3Audio(const Game_ConfigAudio& cfg) : + GenericAudio(cfg) +{ + if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) { + Output::Warning("Couldn't init audio: {}", SDL_GetError()); + return; + } + +#ifdef EMSCRIPTEN + // Get preferred sample rate from Browser (-> OS) + const int frequency = EM_ASM_INT_V({ + var context; + try { + context = new AudioContext(); + } catch (e) { + context = new webkitAudioContext(); + } + return context.sampleRate; + }); +#else + const int frequency = 44100; +#endif + + const SDL_AudioSpec spec = { SDL_AUDIO_S16, 2, frequency }; + audio_stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, sdl_audio_callback, this); + + if (!audio_stream) { + Output::Warning("Couldn't open audio: {}", SDL_GetError()); + return; + } + + SetFormat(frequency, sdl_format_to_format(SDL_AUDIO_S16), 2); + + // Start Audio + SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(audio_stream)); +} + +Sdl3Audio::~Sdl3Audio() { + // FIXME: Call crashes with "invalid pointer" + //SDL_DestroyAudioStream(audio_stream); +} + +void Sdl3Audio::LockMutex() const { + if (!audio_stream) { + return; + } + + SDL_LockAudioStream(audio_stream); +} + +void Sdl3Audio::UnlockMutex() const { + if (!audio_stream) { + return; + } + + SDL_UnlockAudioStream(audio_stream); +} + +#endif diff --git a/src/platform/sdl/sdl3_audio.h b/src/platform/sdl/sdl3_audio.h new file mode 100644 index 0000000000..d3cd642bcf --- /dev/null +++ b/src/platform/sdl/sdl3_audio.h @@ -0,0 +1,32 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +#ifndef EP_AUDIO_SDL3_H +#define EP_AUDIO_SDL3_H + +#include "audio_generic.h" + +class Sdl3Audio : public GenericAudio { +public: + Sdl3Audio(const Game_ConfigAudio& cfg); + ~Sdl3Audio(); + + void LockMutex() const override; + void UnlockMutex() const override; +}; + +#endif diff --git a/src/platform/sdl/sdl3_ui.cpp b/src/platform/sdl/sdl3_ui.cpp new file mode 100644 index 0000000000..b1c102ef37 --- /dev/null +++ b/src/platform/sdl/sdl3_ui.cpp @@ -0,0 +1,1256 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ +#include +#include +#include +#include "SDL3/SDL_mouse.h" +#include "SDL3/SDL_video.h" +#include "game_config.h" +#include "system.h" +#include "sdl3_ui.h" + +#ifdef _WIN32 +# include +# include +# include +#elif defined(__ANDROID__) +# include +# include +#elif defined(EMSCRIPTEN) +# include +#elif defined(__WIIU__) +# include "platform/wiiu/main.h" +#endif +#include "icon.h" + +#include "color.h" +#include "graphics.h" +#include "keys.h" +#include "output.h" +#include "player.h" +#include "bitmap.h" +#include "lcf/scope_guard.h" + +#if defined(__APPLE__) && TARGET_OS_OSX +# include "platform/macos/macos_utils.h" +#endif + +#ifdef SUPPORT_AUDIO +# if AUDIO_AESND +# include "platform/wii/audio.h" +# else +# include "sdl3_audio.h" +# endif + +AudioInterface& Sdl3Ui::GetAudio() { + return *audio_; +} +#endif + +static SDL_PixelFormat GetDefaultFormat() { +#ifdef WORDS_BIGENDIAN + return SDL_PIXELFORMAT_ABGR32; +#else + return SDL_PIXELFORMAT_RGBA32; +#endif +} + +static DynamicFormat GetDynamicFormat(uint32_t fmt) { + switch (fmt) { + case SDL_PIXELFORMAT_RGBA32: + return format_R8G8B8A8_n().format(); + case SDL_PIXELFORMAT_BGRA32: + return format_B8G8R8A8_n().format(); + case SDL_PIXELFORMAT_ARGB32: + return format_A8R8G8B8_n().format(); + case SDL_PIXELFORMAT_ABGR32: + return format_A8B8G8R8_n().format(); + default: + return DynamicFormat(); + } +} + +#ifdef _WIN32 +HWND GetWindowHandle(SDL_Window* window) { + SDL_SysWMinfo wminfo; + SDL_VERSION(&wminfo.version) + SDL_bool success = SDL_GetWindowWMInfo(window, &wminfo); + + if (success < 0) + Output::Error("Wrong SDL version"); + + return wminfo.info.win.window; +} +#endif + +static int FilterUntilFocus(const SDL_Event* evnt); + +#if defined(USE_KEYBOARD) && defined(SUPPORT_KEYBOARD) +static Input::Keys::InputKey SdlKey2InputKey(SDL_Keycode sdlkey); +#endif + +#if defined(USE_JOYSTICK) && defined(SUPPORT_JOYSTICK) +static Input::Keys::InputKey SdlJKey2InputKey(int button_index); +#endif + +Sdl3Ui::Sdl3Ui(long width, long height, const Game_Config& cfg) : BaseUi(cfg) +{ + // Set some SDL environment variables before starting. These are platform + // dependent, so every port needs to set them manually +#ifdef __LINUX__ + // Set the application class name + setenv("SDL_VIDEO_X11_WMCLASS", GAME_TITLE, 0); +#endif +#ifdef EMSCRIPTEN + SDL_SetHint(SDL_HINT_EMSCRIPTEN_ASYNCIFY, "0"); + // Only handle keyboard events when the canvas has focus + SDL_SetHint(SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT, "#canvas"); +#endif + + if (!SDL_Init(SDL_INIT_VIDEO)) { + Output::Error("Couldn't initialize SDL.\n{}\n", SDL_GetError()); + } + + RequestVideoMode(width, height, + cfg.video.window_zoom.Get(), + cfg.video.fullscreen.Get(), + cfg.video.vsync.Get()); + + SetTitle(GAME_TITLE); + +#if (defined(USE_JOYSTICK) && defined(SUPPORT_JOYSTICK)) || (defined(USE_JOYSTICK_AXIS) && defined(SUPPORT_JOYSTICK_AXIS)) + if (!SDL_InitSubSystem(SDL_INIT_GAMEPAD)) { + Output::Warning("Couldn't initialize joystick. {}", SDL_GetError()); + } + + SDL_SetJoystickEventsEnabled(true); + sdl_joystick = SDL_OpenJoystick(0); +#endif + +#if defined(USE_MOUSE) && defined(SUPPORT_MOUSE) + ShowCursor(true); +#else + ShowCursor(false); +#endif + +#ifdef SUPPORT_AUDIO +# ifdef AUDIO_AESND + audio_ = std::make_unique(cfg.audio); +# else + audio_ = std::make_unique(cfg.audio); +# endif +#endif +} + +Sdl3Ui::~Sdl3Ui() { + if (sdl_joystick) { + SDL_CloseJoystick(sdl_joystick); + } + if (sdl_texture_game) { + SDL_DestroyTexture(sdl_texture_game); + } + if (sdl_texture_scaled) { + SDL_DestroyTexture(sdl_texture_scaled); + } + if (sdl_renderer) { + SDL_DestroyRenderer(sdl_renderer); + } + if (sdl_window) { + SDL_DestroyWindow(sdl_window); + } + SDL_Quit(); +} + +bool Sdl3Ui::vChangeDisplaySurfaceResolution(int new_width, int new_height) { + SDL_Texture* new_sdl_texture_game = SDL_CreateTexture(sdl_renderer, + texture_format, + SDL_TEXTUREACCESS_STREAMING, + new_width, new_height); + + if (!new_sdl_texture_game) { + Output::Warning("ChangeDisplaySurfaceResolution SDL_CreateTexture failed: {}", SDL_GetError()); + return false; + } + + if (sdl_texture_game) { + SDL_DestroyTexture(sdl_texture_game); + } + + sdl_texture_game = new_sdl_texture_game; + SDL_SetTextureScaleMode(sdl_texture_game, SDL_SCALEMODE_NEAREST); + + BitmapRef new_main_surface = Bitmap::Create(new_width, new_height, Color(0, 0, 0, 255)); + + if (!new_main_surface) { + Output::Warning("ChangeDisplaySurfaceResolution Bitmap::Create failed"); + return false; + } + + main_surface = new_main_surface; + window.size_changed = true; + + BeginDisplayModeChange(); + + current_display_mode.width = new_width; + current_display_mode.height = new_height; + + EndDisplayModeChange(); + + return true; +} + +void Sdl3Ui::RequestVideoMode(int width, int height, int zoom, bool fullscreen, bool vsync) { + BeginDisplayModeChange(); + + // SDL2 documentation says that resolution dependent code should not be used + // anymore. The library takes care of it now. + current_display_mode.width = width; + current_display_mode.height = height; + current_display_mode.bpp = 32; + current_display_mode.zoom = zoom; + current_display_mode.vsync = vsync; + + EndDisplayModeChange(); + + // Work around some SDL bugs, window properties are incorrect when started + // as full screen, e.g. height lacks title bar size, icon is not added, etc. + if (fullscreen) + ToggleFullscreen(); +} + +void Sdl3Ui::BeginDisplayModeChange() { + last_display_mode = current_display_mode; + current_display_mode.effective = false; +} + +void Sdl3Ui::EndDisplayModeChange() { + // Check if the new display mode is different from last one + if (current_display_mode.flags != last_display_mode.flags || + current_display_mode.zoom != last_display_mode.zoom || + current_display_mode.width != last_display_mode.width || + current_display_mode.height != last_display_mode.height) { + + if (!RefreshDisplayMode()) { + // Mode change failed, check if last one was effective + if (last_display_mode.effective) { + current_display_mode = last_display_mode; + + // Try a rollback to last mode + if (!RefreshDisplayMode()) { + Output::Error("Couldn't rollback to last display mode.\n{}", SDL_GetError()); + } + } else { + Output::Error("Couldn't set display mode.\n{}", SDL_GetError()); + } + } + + current_display_mode.effective = true; +#ifdef EMSCRIPTEN + SetIsFullscreen(true); +#else + SetIsFullscreen((current_display_mode.flags & SDL_WINDOW_FULLSCREEN) == SDL_WINDOW_FULLSCREEN); +#endif + } +} + +bool Sdl3Ui::RefreshDisplayMode() { + uint32_t flags = current_display_mode.flags; + int display_width = current_display_mode.width; + int display_height = current_display_mode.height; + bool& vsync = current_display_mode.vsync; + +#ifdef SUPPORT_ZOOM + int display_width_zoomed = display_width * current_display_mode.zoom; + int display_height_zoomed = display_height * current_display_mode.zoom; +#else + int display_width_zoomed = display_width; + int display_height_zoomed = display_height; +#endif + + if (!sdl_window) { + #ifdef __ANDROID__ + // Workaround SDL bug: https://bugzilla.libsdl.org/show_bug.cgi?id=2291 + // Set back buffer format to 565 + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); + #endif + + #if defined(__APPLE__) && TARGET_OS_OSX + // Use OpenGL on Mac only -- to work around an SDL Metal deficiency + // where it will always use discrete GPU. + // See SDL source code: + // http://hg.libsdl.org/SDL/file/aa9d7c43a982/src/render/metal/SDL_render_metal.m#l1613 + SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl"); + #endif + + #if defined(EMSCRIPTEN) || defined(_WIN32) + // FIXME: This will not DPI-scale on Windows due to SDL2 limitations. + // Is properly fixed in SDL3. See #2764 + flags |= SDL_WINDOW_ALLOW_HIGHDPI; + #endif + + // Create our window + SDL_PropertiesID wprops = SDL_CreateProperties(); + SDL_SetStringProperty(wprops, SDL_PROP_WINDOW_CREATE_TITLE_STRING, GAME_TITLE); + SDL_SetNumberProperty(wprops, SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER, SDL_WINDOW_RESIZABLE | flags); + + if (vcfg.window_x.Get() < 0 || vcfg.window_y.Get() < 0 || vcfg.window_height.Get() <= 0 || vcfg.window_width.Get() <= 0) { + SDL_SetNumberProperty(wprops, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED); + SDL_SetNumberProperty(wprops, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED); + SDL_SetNumberProperty(wprops, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, display_width_zoomed); + SDL_SetNumberProperty(wprops, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, display_height_zoomed); + } else { + SDL_SetNumberProperty(wprops, SDL_PROP_WINDOW_CREATE_X_NUMBER, vcfg.window_x.Get()); + SDL_SetNumberProperty(wprops, SDL_PROP_WINDOW_CREATE_Y_NUMBER, vcfg.window_y.Get()); + SDL_SetNumberProperty(wprops, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, vcfg.window_width.Get()); + SDL_SetNumberProperty(wprops, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, vcfg.window_height.Get()); + } + + sdl_window = SDL_CreateWindowWithProperties(wprops); + SDL_DestroyProperties(wprops); + + if (!sdl_window) { + Output::Debug("SDL_CreateWindow failed : {}", SDL_GetError()); + return false; + } + + SDL_GetWindowSize(sdl_window, &window.width, &window.height); + window.size_changed = true; + + auto window_sg = lcf::makeScopeGuard([&]() { + SDL_DestroyWindow(sdl_window); + sdl_window = nullptr; + }); + + SetAppIcon(); + + sdl_renderer = SDL_CreateRenderer(sdl_window, nullptr); + if (!sdl_renderer) { + Output::Debug("SDL_CreateRenderer failed : {}", SDL_GetError()); + return false; + } + if (vsync) { + SetFrameRateSynchronized(SDL_SetRenderVSync(sdl_renderer, 1)); + } else { + SetFrameRateSynchronized(false); + } + + auto renderer_sg = lcf::makeScopeGuard([&]() { + SDL_DestroyRenderer(sdl_renderer); + sdl_renderer = nullptr; + }); + + texture_format = GetDefaultFormat(); + + Output::Debug("SDL3: Selected Pixel Format {}", SDL_GetPixelFormatName(texture_format)); + + // Flush display + SDL_RenderClear(sdl_renderer); + SDL_RenderPresent(sdl_renderer); + + sdl_texture_game = SDL_CreateTexture(sdl_renderer, + texture_format, + SDL_TEXTUREACCESS_STREAMING, + display_width, display_height); + + if (!sdl_texture_game) { + Output::Debug("SDL_CreateTexture failed : {}", SDL_GetError()); + return false; + } + + SDL_SetTextureScaleMode(sdl_texture_game, SDL_SCALEMODE_NEAREST); + +#ifdef _WIN32 + HWND window = GetWindowHandle(sdl_window); + // Not using the enum names because this will fail to build when not using a recent Windows 11 SDK + int window_rounding = 1; // DWMWCP_DONOTROUND + DwmSetWindowAttribute(window, 33 /* DWMWA_WINDOW_CORNER_PREFERENCE */, &window_rounding, sizeof(window_rounding)); +#endif + + renderer_sg.Dismiss(); + window_sg.Dismiss(); + } else { + // Browser handles fast resizing for emscripten, TODO: use fullscreen API +#ifndef EMSCRIPTEN + bool is_fullscreen = (flags & SDL_WINDOW_FULLSCREEN) == SDL_WINDOW_FULLSCREEN; + if (is_fullscreen) { + SDL_SetWindowFullscreen(sdl_window, SDL_WINDOW_FULLSCREEN); + } else { + SDL_SetWindowFullscreen(sdl_window, 0); + if ((last_display_mode.flags & SDL_WINDOW_FULLSCREEN) + == SDL_WINDOW_FULLSCREEN) { + // Restore to pre-fullscreen size + SDL_SetWindowSize(sdl_window, 0, 0); + } else { + SDL_SetWindowSize(sdl_window, display_width_zoomed, display_height_zoomed); + } + } +#endif + } + // Need to set up icon again, some platforms recreate the window when + // creating the renderer (i.e. Windows), see also comment in SetAppIcon() + SetAppIcon(); + + uint32_t sdl_pixel_fmt = GetDefaultFormat(); + + auto format = GetDynamicFormat(sdl_pixel_fmt); + Bitmap::SetFormat(Bitmap::ChooseFormat(format)); + + if (!main_surface) { + // Drawing surface will be the window itself + main_surface = Bitmap::Create( + display_width, display_height, Color(0, 0, 0, 255)); + } + + return true; +} + +void Sdl3Ui::ToggleFullscreen() { + BeginDisplayModeChange(); + if ((current_display_mode.flags & SDL_WINDOW_FULLSCREEN) == SDL_WINDOW_FULLSCREEN) { + current_display_mode.flags &= ~SDL_WINDOW_FULLSCREEN; + } else { + current_display_mode.flags |= SDL_WINDOW_FULLSCREEN; + SDL_GetWindowPosition(sdl_window, &window_mode_metrics.x, &window_mode_metrics.y); + window_mode_metrics.width = window.width; + window_mode_metrics.height = window.height; + } + EndDisplayModeChange(); +} + +void Sdl3Ui::ToggleZoom() { +#ifdef SUPPORT_ZOOM + BeginDisplayModeChange(); + // Work around a SDL bug which doesn't demaximize the window when the size + // is changed + int flags = SDL_GetWindowFlags(sdl_window); + if ((flags & SDL_WINDOW_MAXIMIZED) == SDL_WINDOW_MAXIMIZED) { + SDL_RestoreWindow(sdl_window); + } + + // get current window size, calculate next bigger zoom factor + int w, h; + SDL_GetWindowSize(sdl_window, &w, &h); + last_display_mode.zoom = std::min(w / main_surface->width(), h / main_surface->height()); + current_display_mode.zoom = last_display_mode.zoom + 1; + + // get maximum usable window size + int display_index = SDL_GetDisplayForWindow(sdl_window); + SDL_Rect max_mode; + // this takes account of the menu bar and dock on macOS and task bar on windows + SDL_GetDisplayUsableBounds(display_index, &max_mode); + + // reset zoom, if it does not fit + if ((max_mode.h < main_surface->height() * current_display_mode.zoom) || + (max_mode.w < main_surface->width() * current_display_mode.zoom)) { + current_display_mode.zoom = 1; + } + EndDisplayModeChange(); +#endif +} + +bool Sdl3Ui::ProcessEvents() { +#if defined(__WIIU__) + if (!WiiU_ProcessProcUI()) { + return false; + } +#endif + + SDL_Event evnt; + +#if defined(USE_MOUSE) && defined(SUPPORT_MOUSE) + // Reset Mouse scroll + keys[Input::Keys::MOUSE_SCROLLUP] = false; + keys[Input::Keys::MOUSE_SCROLLDOWN] = false; +#endif + + // Poll SDL events and process them + while (SDL_PollEvent(&evnt)) { + ProcessEvent(evnt); + + if (Player::exit_flag) + break; + } + + return true; +} + +void Sdl3Ui::SetScalingMode(ConfigEnum::ScalingMode mode) { + window.size_changed = true; + vcfg.scaling_mode.Set(mode); +} + +void Sdl3Ui::ToggleStretch() { + window.size_changed = true; + vcfg.stretch.Toggle(); +} + +void Sdl3Ui::ToggleVsync() { + // Modifying vsync requires recreating the renderer + vcfg.vsync.Toggle(); + + if (SDL_SetRenderVSync(sdl_renderer, vcfg.vsync.Get() ? 1 : 0)) { + current_display_mode.vsync = vcfg.vsync.Get(); + SetFrameRateSynchronized(vcfg.vsync.Get()); + } else { + Output::Warning("Unable to toggle vsync. This is likely a problem with your system configuration."); + } +} + +void Sdl3Ui::UpdateDisplay() { +#ifdef __WIIU__ + if (vcfg.scaling_mode.Get() == ConfigEnum::ScalingMode::Bilinear && window.scale > 0.f) { + // Workaround WiiU bug: Bilinear uses a render target and for these the format is not converted + void* target_pixels; + int target_pitch; + + SDL_LockTexture(sdl_texture_game, nullptr, &target_pixels, &target_pitch); + SDL_ConvertPixels(main_surface->width(), main_surface->height(), GetDefaultFormat(), main_surface->pixels(), + main_surface->pitch(), SDL_PIXELFORMAT_RGBA8888, target_pixels, target_pitch); + SDL_UnlockTexture(sdl_texture_game); + } else { + SDL_UpdateTexture(sdl_texture_game, nullptr, main_surface->pixels(), main_surface->pitch()); + } +#else + // SDL_UpdateTexture was found to be faster than SDL_LockTexture / SDL_UnlockTexture. + SDL_UpdateTexture(sdl_texture_game, nullptr, main_surface->pixels(), main_surface->pitch()); +#endif + + if (window.size_changed && window.width > 0 && window.height > 0) { + // Based on SDL2 function UpdateLogicalSize + window.size_changed = false; + + float width_float = static_cast(window.width); + float height_float = static_cast(window.height); + + float want_aspect = (float)main_surface->width() / main_surface->height(); + float real_aspect = width_float / height_float; + + auto do_stretch = [this]() { + if (vcfg.stretch.Get()) { + viewport.x = 0; + viewport.w = window.width; + } + }; + + if (vcfg.scaling_mode.Get() == ConfigEnum::ScalingMode::Integer) { + // Integer division on purpose + if (want_aspect > real_aspect) { + window.scale = static_cast(window.width / main_surface->width()); + } else { + window.scale = static_cast(window.height / main_surface->height()); + } + + viewport.w = static_cast(ceilf(main_surface->width() * window.scale)); + viewport.x = (window.width - viewport.w) / 2; + viewport.h = static_cast(ceilf(main_surface->height() * window.scale)); + viewport.y = (window.height - viewport.h) / 2; + do_stretch(); + + SDL_SetRenderViewport(sdl_renderer, &viewport); + } else if (fabs(want_aspect - real_aspect) < 0.0001) { + // The aspect ratios are the same, let SDL2 scale it + window.scale = width_float / main_surface->width(); + SDL_SetRenderViewport(sdl_renderer, nullptr); + + // Only used here for the mouse coordinates + viewport.x = 0; + viewport.y = 0; + viewport.w = window.width; + viewport.h = window.height; + } else if (want_aspect > real_aspect) { + // Letterboxing (black bars top and bottom) + window.scale = width_float / main_surface->width(); + viewport.x = 0; + viewport.w = window.width; + viewport.h = static_cast(ceilf(main_surface->height() * window.scale)); + viewport.y = (window.height - viewport.h) / 2; + do_stretch(); + SDL_SetRenderViewport(sdl_renderer, &viewport); + } else { + // black bars left and right + window.scale = height_float / main_surface->height(); + viewport.y = 0; + viewport.h = window.height; + viewport.w = static_cast(ceilf(main_surface->width() * window.scale)); + viewport.x = (window.width - viewport.w) / 2; + do_stretch(); + SDL_SetRenderViewport(sdl_renderer, &viewport); + } + + if (vcfg.scaling_mode.Get() == ConfigEnum::ScalingMode::Bilinear && window.scale > 0.f) { + if (sdl_texture_scaled) { + SDL_DestroyTexture(sdl_texture_scaled); + } + sdl_texture_scaled = SDL_CreateTexture(sdl_renderer, texture_format, SDL_TEXTUREACCESS_TARGET, + static_cast(ceilf(window.scale)) * main_surface->width(), static_cast(ceilf(window.scale)) * main_surface->height()); + if (!sdl_texture_scaled) { + Output::Debug("SDL_CreateTexture failed : {}", SDL_GetError()); + } + } + } + + SDL_RenderClear(sdl_renderer); + if (vcfg.scaling_mode.Get() == ConfigEnum::ScalingMode::Bilinear && window.scale > 0.f) { + // Render game texture on the scaled texture + SDL_SetRenderTarget(sdl_renderer, sdl_texture_scaled); + SDL_RenderClear(sdl_renderer); + SDL_RenderTexture(sdl_renderer, sdl_texture_game, nullptr, nullptr); + + SDL_SetRenderTarget(sdl_renderer, nullptr); + SDL_RenderTexture(sdl_renderer, sdl_texture_scaled, nullptr, nullptr); + } else { + SDL_RenderTexture(sdl_renderer, sdl_texture_game, nullptr, nullptr); + } + SDL_RenderPresent(sdl_renderer); +} + +void Sdl3Ui::SetTitle(const std::string &title) { + SDL_SetWindowTitle(sdl_window, title.c_str()); +} + +bool Sdl3Ui::ShowCursor(bool flag) { + bool temp_flag = cursor_visible; + cursor_visible = flag; + if (cursor_visible) { + SDL_ShowCursor(); + } else { + SDL_HideCursor(); + } + return temp_flag; +} + +bool Sdl3Ui::HandleErrorOutput(const std::string &message) { + std::string title = Player::GetFullVersionString(); + + // Manually Restore window from fullscreen, since message would not be visible otherwise + if ((current_display_mode.flags & SDL_WINDOW_FULLSCREEN) + == SDL_WINDOW_FULLSCREEN) { + SDL_SetWindowFullscreen(sdl_window, 0); + SDL_SetWindowSize(sdl_window, 0, 0); + } + + if(SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title.c_str(), + message.c_str(), sdl_window) != 0) { + return false; + } + + return true; +} + +void Sdl3Ui::ProcessEvent(SDL_Event &evnt) { + switch (evnt.type) { + case SDL_EVENT_QUIT: + Player::exit_flag = true; + return; + + case SDL_EVENT_KEY_DOWN: + ProcessKeyDownEvent(evnt); + return; + + case SDL_EVENT_KEY_UP: + ProcessKeyUpEvent(evnt); + return; + + case SDL_EVENT_MOUSE_MOTION: + ProcessMouseMotionEvent(evnt); + return; + + case SDL_EVENT_MOUSE_WHEEL: + ProcessMouseWheelEvent(evnt); + return; + + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: + ProcessMouseButtonEvent(evnt); + return; + + case SDL_EVENT_GAMEPAD_ADDED: + ProcessControllerAdded(evnt); + return; + + case SDL_EVENT_GAMEPAD_REMOVED: + ProcessControllerRemoved(evnt); + return; + + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: + case SDL_EVENT_GAMEPAD_BUTTON_UP: + ProcessControllerButtonEvent(evnt); + return; + + case SDL_EVENT_GAMEPAD_AXIS_MOTION: + ProcessControllerAxisEvent(evnt); + return; + + case SDL_EVENT_FINGER_DOWN: + case SDL_EVENT_FINGER_UP: + case SDL_EVENT_FINGER_MOTION: + ProcessFingerEvent(evnt); + return; + default: + if (evnt.type >= SDL_EVENT_WINDOW_FIRST && evnt.type <= SDL_EVENT_WINDOW_LAST) { + ProcessWindowEvent(evnt); + } + } +} + +void Sdl3Ui::ProcessWindowEvent(SDL_Event &evnt) { + int state = evnt.type; + + if (state == SDL_EVENT_WINDOW_FOCUS_LOST) { + auto cfg = vcfg; + vGetConfig(cfg); + if (!cfg.pause_when_focus_lost.Get()) { + return; + } + + Player::Pause(); + + bool last = ShowCursor(true); + + // Filter SDL events until focus is regained + SDL_Event wait_event; + + while (SDL_WaitEvent(&wait_event)) { + if (FilterUntilFocus(&wait_event)) { + break; + } + } + + ShowCursor(last); + + Player::Resume(); + ResetKeys(); + + return; + } + +#if defined(USE_MOUSE_OR_TOUCH) && defined(SUPPORT_MOUSE_OR_TOUCH) + if (state == SDL_EVENT_WINDOW_MOUSE_ENTER) { + mouse_focus = true; + } else if (state == SDL_EVENT_WINDOW_MOUSE_LEAVE) { + mouse_focus = false; + } +#endif + if (state == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED || state == SDL_EVENT_WINDOW_RESIZED) { + window.width = evnt.window.data1; + window.height = evnt.window.data2; + +#ifdef EMSCRIPTEN + double display_ratio = emscripten_get_device_pixel_ratio(); + window.width = static_cast(window.width * display_ratio); + window.height = static_cast(window.height * display_ratio); +#endif + + window.size_changed = true; + } +} + +void Sdl3Ui::ProcessKeyDownEvent(SDL_Event &evnt) { +#if defined(USE_KEYBOARD) && defined(SUPPORT_KEYBOARD) + if (evnt.key.key == SDLK_F4 && (evnt.key.mod & SDL_KMOD_LALT)) { // Close program on LeftAlt+F4 + Player::exit_flag = true; + return; + } else if (evnt.key.key == SDLK_RETURN || + evnt.key.key == SDLK_KP_ENTER) { + if (evnt.key.mod & SDL_KMOD_LALT || (evnt.key.mod & SDL_KMOD_RALT)) { + // Toggle fullscreen on Alt+Enter + ToggleFullscreen(); + return; + } + } + + // Update key state + keys[SdlKey2InputKey(evnt.key.scancode)] = true; +#else + /* unused */ + (void) evnt; +#endif +} + +void Sdl3Ui::ProcessKeyUpEvent(SDL_Event &evnt) { +#if defined(USE_KEYBOARD) && defined(SUPPORT_KEYBOARD) + keys[SdlKey2InputKey(evnt.key.scancode)] = false; +#else + /* unused */ + (void) evnt; +#endif +} + +void Sdl3Ui::ProcessMouseMotionEvent(SDL_Event& evnt) { +#if defined(USE_MOUSE_OR_TOUCH) && defined(SUPPORT_MOUSE_OR_TOUCH) + mouse_focus = true; + + int xw = viewport.w; + int yh = viewport.h; + + if (xw == 0 || yh == 0) { + // Startup. No viewport yet + return; + } + +#ifdef EMSCRIPTEN + double display_ratio = emscripten_get_device_pixel_ratio(); + mouse_pos.x = (evnt.motion.x * display_ratio - viewport.x) * main_surface->width() / xw; + mouse_pos.y = (evnt.motion.y * display_ratio - viewport.y) * main_surface->height() / yh; +#else + mouse_pos.x = (evnt.motion.x - viewport.x) * main_surface->width() / xw; + mouse_pos.y = (evnt.motion.y - viewport.y) * main_surface->height() / yh; +#endif + +#else + /* unused */ + (void) evnt; +#endif +} + +void Sdl3Ui::ProcessMouseWheelEvent(SDL_Event& evnt) { +#if defined(USE_MOUSE) && defined(SUPPORT_MOUSE) + // Ignore Finger (touch) events here + if (evnt.wheel.which == SDL_TOUCH_MOUSEID) + return; + + int amount = evnt.wheel.y; + + // translate direction + if (evnt.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) + amount *= -1; + + keys[Input::Keys::MOUSE_SCROLLUP] = amount > 0; + keys[Input::Keys::MOUSE_SCROLLDOWN] = amount < 0; +#else + /* unused */ + (void) evnt; +#endif +} + +void Sdl3Ui::ProcessMouseButtonEvent(SDL_Event& evnt) { +#if defined(USE_MOUSE) && defined(SUPPORT_MOUSE) + // Ignore Finger (touch) events here + if (evnt.button.which == SDL_TOUCH_MOUSEID) + return; + + switch (evnt.button.button) { + case SDL_BUTTON_LEFT: + keys[Input::Keys::MOUSE_LEFT] = evnt.button.down; + break; + case SDL_BUTTON_MIDDLE: + keys[Input::Keys::MOUSE_MIDDLE] = evnt.button.down; + break; + case SDL_BUTTON_RIGHT: + keys[Input::Keys::MOUSE_RIGHT] = evnt.button.down; + break; + } +#else + /* unused */ + (void) evnt; +#endif +} + +void Sdl3Ui::ProcessControllerAdded(SDL_Event& evnt) { +#if defined(USE_JOYSTICK) && defined(SUPPORT_JOYSTICK) + int id = evnt.cdevice.which; + if (SDL_IsGamepad(id)) { + SDL_Gamepad* controller = SDL_OpenGamepad(id); + if (controller) { + Output::Debug("Controller {} ({}) added", id, SDL_GetGamepadName(controller)); + } + } +#endif +} + +void Sdl3Ui::ProcessControllerRemoved(SDL_Event &evnt) { +#if defined(USE_JOYSTICK) && defined(SUPPORT_JOYSTICK) + int id = evnt.cdevice.which; + SDL_Gamepad* controller = SDL_GetGamepadFromID(id); + if (controller) { + Output::Debug("Controller {} ({}) removed", id, SDL_GetGamepadName(controller)); + SDL_CloseGamepad(controller); + } +#endif +} + +void Sdl3Ui::ProcessControllerButtonEvent(SDL_Event &evnt) { +#if defined(USE_JOYSTICK) && defined(SUPPORT_JOYSTICK) + keys[SdlJKey2InputKey(evnt.gbutton.button)] = evnt.gbutton.down; +#endif +} + +void Sdl3Ui::ProcessControllerAxisEvent(SDL_Event &evnt) { +#if defined(USE_JOYSTICK_AXIS) && defined(SUPPORT_JOYSTICK_AXIS) + int axis = evnt.gaxis.axis; + int value = evnt.gaxis.value; + + auto normalize = [](int value) { + return static_cast(value) / 32768.f; + }; + + switch (axis) { + case SDL_GAMEPAD_AXIS_LEFTX: + analog_input.primary.x = normalize(value); + break; + case SDL_GAMEPAD_AXIS_LEFTY: + analog_input.primary.y = normalize(value); + break; + case SDL_GAMEPAD_AXIS_RIGHTX: + analog_input.secondary.x = normalize(value); + break; + case SDL_GAMEPAD_AXIS_RIGHTY: + analog_input.secondary.y = normalize(value); + break; + case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: + analog_input.trigger_left = normalize(value); + break; + case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: + analog_input.trigger_right = normalize(value); + break; + } +#endif +} + +void Sdl3Ui::ProcessFingerEvent(SDL_Event& evnt) { +#if defined(USE_TOUCH) && defined(SUPPORT_TOUCH) + int xw = viewport.w; + int yh = viewport.h; + + if (xw == 0 || yh == 0) { + // Startup. No viewport yet + return; + } + + // We currently ignore swipe gestures + // A finger touch is detected when the fingers go up a brief delay after going down + if (evnt.type == SDL_EVENT_FINGER_DOWN) { + auto fi = std::find_if(touch_input.begin(), touch_input.end(), [&](const auto& input) { + return input.id == -1; + }); + if (fi == touch_input.end()) { + // already tracking 5 fingers + return; + } + +#ifdef EMSCRIPTEN + double display_ratio = emscripten_get_device_pixel_ratio(); + int x = (evnt.tfinger.x * display_ratio - viewport.x) * main_surface->width() / xw; + int y = (evnt.tfinger.y * display_ratio - viewport.y) * main_surface->height() / yh; +#else + int x = (evnt.tfinger.x - viewport.x) * main_surface->width() / xw; + int y = (evnt.tfinger.y - viewport.y) * main_surface->height() / yh; +#endif + + fi->Down(evnt.tfinger.fingerID, x, y); + } else if (evnt.type == SDL_EVENT_FINGER_UP) { + auto fi = std::find_if(touch_input.begin(), touch_input.end(), [&](const auto& input) { + return input.id == evnt.tfinger.fingerID; + }); + if (fi == touch_input.end()) { + // Finger is not tracked + return; + } + fi->Up(); + } +#else + /* unused */ + (void) evnt; +#endif +} + +void Sdl3Ui::SetAppIcon() { +#if defined(__MORPHOS__) + // do nothing +#elif defined(_WIN32) + HINSTANCE handle = GetModuleHandle(NULL); + HICON icon = LoadIcon(handle, MAKEINTRESOURCE(23456)); + HICON icon_small = (HICON) LoadImage(handle, MAKEINTRESOURCE(23456), IMAGE_ICON, + GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); + + if (icon == NULL || icon_small == NULL) + Output::Warning("Could not load window icon."); + + HWND window = GetWindowHandle(sdl_window); + SetClassLongPtr(window, GCLP_HICON, (LONG_PTR) icon); + SetClassLongPtr(window, GCLP_HICONSM, (LONG_PTR) icon_small); +#else + #if defined(__APPLE__) && TARGET_OS_OSX + if (MacOSUtils::IsAppBundle()) { + // Do nothing if running as a .app. In this case macOS uses the + // icon packaged with the .app's resources, which is a much higher + // resolution version than the one that would be set below. + return; + } + #endif + /* SDL handles transfering the application icon to new or recreated windows, + if initially set through it (see below). So no need to set again for all + platforms relying on it. Platforms defined above need special treatment. + */ + static bool icon_set = false; + + if (icon_set) + return; + + //Linux, OS X + #if SDL_BYTEORDER == SDL_LIL_ENDIAN + uint32_t Rmask = 0x000000FF; + uint32_t Gmask = 0x0000FF00; + uint32_t Bmask = 0x00FF0000; + uint32_t Amask = 0xFF000000; + #else + uint32_t Rmask = 0xFF000000; + uint32_t Gmask = 0x00FF0000; + uint32_t Bmask = 0x0000FF00; + uint32_t Amask = 0x000000FF; + #endif + SDL_Surface *icon = SDL_CreateSurfaceFrom(ICON_SIZE, ICON_SIZE, SDL_GetPixelFormatForMasks(32, Rmask, Gmask, Bmask, Amask), icon32, ICON_SIZE*4); + + if (icon == NULL) + Output::Warning("Could not load window icon."); + + SDL_SetWindowIcon(sdl_window, icon); + SDL_DestroySurface(icon); + icon_set = true; +#endif +} + +void Sdl3Ui::ResetKeys() { + for (size_t i = 0; i < keys.size(); i++) { + keys[i] = false; + } +} + +#if defined(USE_KEYBOARD) && defined(SUPPORT_KEYBOARD) +Input::Keys::InputKey SdlKey2InputKey(SDL_Keycode sdlkey) { + switch (sdlkey) { + case SDL_SCANCODE_BACKSPACE : return Input::Keys::BACKSPACE; + case SDL_SCANCODE_TAB : return Input::Keys::TAB; + case SDL_SCANCODE_CLEAR : return Input::Keys::CLEAR; + case SDL_SCANCODE_RETURN : return Input::Keys::RETURN; + case SDL_SCANCODE_PAUSE : return Input::Keys::PAUSE; + case SDL_SCANCODE_ESCAPE : return Input::Keys::ESCAPE; + case SDL_SCANCODE_SPACE : return Input::Keys::SPACE; + case SDL_SCANCODE_PAGEUP : return Input::Keys::PGUP; + case SDL_SCANCODE_PAGEDOWN : return Input::Keys::PGDN; + case SDL_SCANCODE_END : return Input::Keys::ENDS; + case SDL_SCANCODE_HOME : return Input::Keys::HOME; + case SDL_SCANCODE_LEFT : return Input::Keys::LEFT; + case SDL_SCANCODE_UP : return Input::Keys::UP; + case SDL_SCANCODE_RIGHT : return Input::Keys::RIGHT; + case SDL_SCANCODE_DOWN : return Input::Keys::DOWN; + case SDL_SCANCODE_PRINTSCREEN : return Input::Keys::SNAPSHOT; + case SDL_SCANCODE_INSERT : return Input::Keys::INSERT; + case SDL_SCANCODE_DELETE : return Input::Keys::DEL; + case SDL_SCANCODE_LSHIFT : return Input::Keys::LSHIFT; + case SDL_SCANCODE_RSHIFT : return Input::Keys::RSHIFT; + case SDL_SCANCODE_LCTRL : return Input::Keys::LCTRL; + case SDL_SCANCODE_RCTRL : return Input::Keys::RCTRL; + case SDL_SCANCODE_LALT : return Input::Keys::LALT; + case SDL_SCANCODE_RALT : return Input::Keys::RALT; + case SDL_SCANCODE_0 : return Input::Keys::N0; + case SDL_SCANCODE_1 : return Input::Keys::N1; + case SDL_SCANCODE_2 : return Input::Keys::N2; + case SDL_SCANCODE_3 : return Input::Keys::N3; + case SDL_SCANCODE_4 : return Input::Keys::N4; + case SDL_SCANCODE_5 : return Input::Keys::N5; + case SDL_SCANCODE_6 : return Input::Keys::N6; + case SDL_SCANCODE_7 : return Input::Keys::N7; + case SDL_SCANCODE_8 : return Input::Keys::N8; + case SDL_SCANCODE_9 : return Input::Keys::N9; + case SDL_SCANCODE_A : return Input::Keys::A; + case SDL_SCANCODE_B : return Input::Keys::B; + case SDL_SCANCODE_C : return Input::Keys::C; + case SDL_SCANCODE_D : return Input::Keys::D; + case SDL_SCANCODE_E : return Input::Keys::E; + case SDL_SCANCODE_F : return Input::Keys::F; + case SDL_SCANCODE_G : return Input::Keys::G; + case SDL_SCANCODE_H : return Input::Keys::H; + case SDL_SCANCODE_I : return Input::Keys::I; + case SDL_SCANCODE_J : return Input::Keys::J; + case SDL_SCANCODE_K : return Input::Keys::K; + case SDL_SCANCODE_L : return Input::Keys::L; + case SDL_SCANCODE_M : return Input::Keys::M; + case SDL_SCANCODE_N : return Input::Keys::N; + case SDL_SCANCODE_O : return Input::Keys::O; + case SDL_SCANCODE_P : return Input::Keys::P; + case SDL_SCANCODE_Q : return Input::Keys::Q; + case SDL_SCANCODE_R : return Input::Keys::R; + case SDL_SCANCODE_S : return Input::Keys::S; + case SDL_SCANCODE_T : return Input::Keys::T; + case SDL_SCANCODE_U : return Input::Keys::U; + case SDL_SCANCODE_V : return Input::Keys::V; + case SDL_SCANCODE_W : return Input::Keys::W; + case SDL_SCANCODE_X : return Input::Keys::X; + case SDL_SCANCODE_Y : return Input::Keys::Y; + case SDL_SCANCODE_Z : return Input::Keys::Z; + case SDL_SCANCODE_MENU : return Input::Keys::MENU; + case SDL_SCANCODE_KP_0 : return Input::Keys::KP0; + case SDL_SCANCODE_KP_1 : return Input::Keys::KP1; + case SDL_SCANCODE_KP_2 : return Input::Keys::KP2; + case SDL_SCANCODE_KP_3 : return Input::Keys::KP3; + case SDL_SCANCODE_KP_4 : return Input::Keys::KP4; + case SDL_SCANCODE_KP_5 : return Input::Keys::KP5; + case SDL_SCANCODE_KP_6 : return Input::Keys::KP6; + case SDL_SCANCODE_KP_7 : return Input::Keys::KP7; + case SDL_SCANCODE_KP_8 : return Input::Keys::KP8; + case SDL_SCANCODE_KP_9 : return Input::Keys::KP9; + case SDL_SCANCODE_KP_MULTIPLY : return Input::Keys::KP_MULTIPLY; + case SDL_SCANCODE_KP_PLUS : return Input::Keys::KP_ADD; + case SDL_SCANCODE_KP_ENTER : return Input::Keys::RETURN; + case SDL_SCANCODE_KP_MINUS : return Input::Keys::KP_SUBTRACT; + case SDL_SCANCODE_KP_PERIOD : return Input::Keys::KP_PERIOD; + case SDL_SCANCODE_KP_DIVIDE : return Input::Keys::KP_DIVIDE; + case SDL_SCANCODE_COMMA : return Input::Keys::COMMA; + case SDL_SCANCODE_PERIOD : return Input::Keys::PERIOD; + case SDL_SCANCODE_SLASH : return Input::Keys::SLASH; + case SDL_SCANCODE_F1 : return Input::Keys::F1; + case SDL_SCANCODE_F2 : return Input::Keys::F2; + case SDL_SCANCODE_F3 : return Input::Keys::F3; + case SDL_SCANCODE_F4 : return Input::Keys::F4; + case SDL_SCANCODE_F5 : return Input::Keys::F5; + case SDL_SCANCODE_F6 : return Input::Keys::F6; + case SDL_SCANCODE_F7 : return Input::Keys::F7; + case SDL_SCANCODE_F8 : return Input::Keys::F8; + case SDL_SCANCODE_F9 : return Input::Keys::F9; + case SDL_SCANCODE_F10 : return Input::Keys::F10; + case SDL_SCANCODE_F11 : return Input::Keys::F11; + case SDL_SCANCODE_F12 : return Input::Keys::F12; + case SDL_SCANCODE_CAPSLOCK : return Input::Keys::CAPS_LOCK; + case SDL_SCANCODE_NUMLOCKCLEAR : return Input::Keys::NUM_LOCK; + case SDL_SCANCODE_SCROLLLOCK : return Input::Keys::SCROLL_LOCK; + case SDL_SCANCODE_AC_BACK : return Input::Keys::AC_BACK; + case SDL_SCANCODE_SELECT : return Input::Keys::SELECT; + case SDL_SCANCODE_LEFTBRACKET : return Input::Keys::LEFT_BRACKET; + case SDL_SCANCODE_RIGHTBRACKET : return Input::Keys::RIGHT_BRACKET; + case SDL_SCANCODE_BACKSLASH : return Input::Keys::BACKSLASH; + case SDL_SCANCODE_SEMICOLON : return Input::Keys::SEMICOLON; + case SDL_SCANCODE_APOSTROPHE : return Input::Keys::APOSTROPH; + + default : return Input::Keys::NONE; + } +} +#endif + +#if defined(USE_JOYSTICK) && defined(SUPPORT_JOYSTICK) +Input::Keys::InputKey SdlJKey2InputKey(int button_index) { + switch (button_index) { + case SDL_GAMEPAD_BUTTON_SOUTH: return Input::Keys::JOY_A; + case SDL_GAMEPAD_BUTTON_EAST: return Input::Keys::JOY_B; + case SDL_GAMEPAD_BUTTON_WEST: return Input::Keys::JOY_X; + case SDL_GAMEPAD_BUTTON_NORTH: return Input::Keys::JOY_Y; + case SDL_GAMEPAD_BUTTON_BACK: return Input::Keys::JOY_BACK; + case SDL_GAMEPAD_BUTTON_GUIDE: return Input::Keys::JOY_GUIDE; + case SDL_GAMEPAD_BUTTON_START: return Input::Keys::JOY_START; + case SDL_GAMEPAD_BUTTON_LEFT_STICK: return Input::Keys::JOY_LSTICK; + case SDL_GAMEPAD_BUTTON_RIGHT_STICK: return Input::Keys::JOY_RSTICK; + case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: return Input::Keys::JOY_SHOULDER_LEFT; + case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: return Input::Keys::JOY_SHOULDER_RIGHT; + case SDL_GAMEPAD_BUTTON_DPAD_UP: return Input::Keys::JOY_DPAD_UP; + case SDL_GAMEPAD_BUTTON_DPAD_DOWN: return Input::Keys::JOY_DPAD_DOWN; + case SDL_GAMEPAD_BUTTON_DPAD_LEFT: return Input::Keys::JOY_DPAD_LEFT; + case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: return Input::Keys::JOY_DPAD_RIGHT; + case SDL_GAMEPAD_BUTTON_MISC1: return Input::Keys::JOY_OTHER_1; + case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1: return Input::Keys::JOY_REAR_RIGHT_1; + case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2: return Input::Keys::JOY_REAR_RIGHT_2; + case SDL_GAMEPAD_BUTTON_LEFT_PADDLE1: return Input::Keys::JOY_REAR_LEFT_1; + case SDL_GAMEPAD_BUTTON_LEFT_PADDLE2: return Input::Keys::JOY_REAR_LEFT_2; + case SDL_GAMEPAD_BUTTON_TOUCHPAD: return Input::Keys::JOY_TOUCH; + default : return Input::Keys::NONE; + } +} +#endif + +int FilterUntilFocus(const SDL_Event* evnt) { + // Prevent throwing events away received after focus gained but filter + // not detached. + if (evnt->type == SDL_EVENT_QUIT) { + Player::exit_flag = true; + return 1; + } else if (evnt->type == SDL_EVENT_WINDOW_FOCUS_GAINED) { + return 1; + } else { + return 0; + } +} + +void Sdl3Ui::vGetConfig(Game_ConfigVideo& cfg) const { +#ifdef EMSCRIPTEN + cfg.renderer.Lock("SDL2 (Software, Emscripten)"); +#elif defined(__wii__) + cfg.renderer.Lock("SDL2 (Software, Wii)"); +#elif defined(__WIIU__) + cfg.renderer.Lock("SDL2 (Software, Wii U)"); +#else + cfg.renderer.Lock("SDL2 (Software)"); +#endif + + cfg.vsync.SetOptionVisible(true); + cfg.fullscreen.SetOptionVisible(true); + cfg.fps_limit.SetOptionVisible(true); +#if defined(SUPPORT_ZOOM) && !defined(__ANDROID__) + // An initial zoom level is needed on Android however changing it looks awful + cfg.window_zoom.SetOptionVisible(true); +#endif + cfg.scaling_mode.SetOptionVisible(true); + cfg.stretch.SetOptionVisible(true); + cfg.game_resolution.SetOptionVisible(true); + cfg.pause_when_focus_lost.SetOptionVisible(true); + + cfg.vsync.Set(current_display_mode.vsync); + cfg.window_zoom.Set(current_display_mode.zoom); + cfg.fullscreen.Set(IsFullscreen()); + +#ifdef EMSCRIPTEN + // Fullscreen is handled by the browser + cfg.fullscreen.SetOptionVisible(false); + cfg.fps_limit.SetOptionVisible(false); + cfg.window_zoom.SetOptionVisible(false); + // Toggling this freezes the web player + cfg.vsync.SetOptionVisible(false); + cfg.pause_when_focus_lost.Lock(false); + cfg.pause_when_focus_lost.SetOptionVisible(false); +#elif defined(__wii__) + cfg.fullscreen.SetOptionVisible(false); +#elif defined(__WIIU__) + // Only makes the screen flicker + cfg.fullscreen.SetOptionVisible(false); + // WiiU always pauses apps in the background + cfg.pause_when_focus_lost.SetOptionVisible(false); +#endif +} + +Rect Sdl3Ui::GetWindowMetrics() const { + if (!IsFullscreen()) { + Rect metrics; + SDL_GetWindowSize(sdl_window, &metrics.width, &metrics.height); + SDL_GetWindowPosition(sdl_window, &metrics.x, &metrics.y); + return metrics; + } else { + return window_mode_metrics; + } +} + +bool Sdl3Ui::OpenURL(StringView url) { + if (IsFullscreen()) { + ToggleFullscreen(); + } + + if (!SDL_OpenURL(ToString(url).c_str())) { + Output::Warning("Open URL {} failed: {}", url, SDL_GetError()); + return false; + } + + return true; +} diff --git a/src/platform/sdl/sdl3_ui.h b/src/platform/sdl/sdl3_ui.h new file mode 100644 index 0000000000..69fe2295af --- /dev/null +++ b/src/platform/sdl/sdl3_ui.h @@ -0,0 +1,153 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +#ifndef EP_SDL3_UI_H +#define EP_SDL3_UI_H + +// Headers +#include "baseui.h" +#include "color.h" +#include "rect.h" +#include "system.h" + +#include +#include + +extern "C" { + union SDL_Event; + struct SDL_Texture; + struct SDL_Window; + struct SDL_Renderer; +} + +struct AudioInterface; + +/** + * Sdl3Ui class. + */ +class Sdl3Ui final : public BaseUi { +public: + /** + * Constructor. + * + * @param width window client width. + * @param height window client height. + * @param cfg config options + */ + Sdl3Ui(long width, long height, const Game_Config& cfg); + + /** + * Destructor. + */ + ~Sdl3Ui() override; + + /** + * Inherited from BaseUi. + */ + /** @{ */ + bool vChangeDisplaySurfaceResolution(int new_width, int new_height) override; + void ToggleFullscreen() override; + void ToggleZoom() override; + void UpdateDisplay() override; + void SetTitle(const std::string &title) override; + bool ShowCursor(bool flag) override; + bool ProcessEvents() override; + void SetScalingMode(ConfigEnum::ScalingMode) override; + void ToggleStretch() override; + void ToggleVsync() override; + void vGetConfig(Game_ConfigVideo& cfg) const override; + bool OpenURL(StringView url) override; + Rect GetWindowMetrics() const override; + bool HandleErrorOutput(const std::string &message) override; + +#ifdef SUPPORT_AUDIO + AudioInterface& GetAudio() override; +#endif + + /** @} */ + +private: + /** + * Refreshes the display mode after it was changed. + * + * @return whether the change was successful. + */ + bool RefreshDisplayMode(); + + void BeginDisplayModeChange(); + void EndDisplayModeChange(); + + /** + * Processes a SDL Event. + */ + /** @{ */ + + void ProcessEvent(SDL_Event &sdl_event); + + void ProcessWindowEvent(SDL_Event &evnt); + void ProcessKeyDownEvent(SDL_Event &evnt); + void ProcessKeyUpEvent(SDL_Event &evnt); + void ProcessMouseMotionEvent(SDL_Event &evnt); + void ProcessMouseButtonEvent(SDL_Event &evnt); + void ProcessMouseWheelEvent(SDL_Event &evnt); + void ProcessControllerAdded(SDL_Event &evnt); + void ProcessControllerRemoved(SDL_Event &evnt); + void ProcessControllerButtonEvent(SDL_Event &evnt); + void ProcessControllerAxisEvent(SDL_Event &evnt); + void ProcessFingerEvent(SDL_Event & evnt); + + /** @} */ + + /** + * Sets app icon. + */ + void SetAppIcon(); + + /** + * Resets keys states. + */ + void ResetKeys(); + + void RequestVideoMode(int width, int height, int zoom, bool fullscreen, bool vsync); + + /** Last display mode. */ + DisplayMode last_display_mode; + + /** Main SDL window. */ + SDL_Texture* sdl_texture_game = nullptr; + SDL_Texture* sdl_texture_scaled = nullptr; + SDL_Window* sdl_window = nullptr; + SDL_Renderer* sdl_renderer = nullptr; + SDL_Joystick *sdl_joystick = nullptr; + + Rect window_mode_metrics; + SDL_Rect viewport = {}; + struct { + int width = 0; + int height = 0; + bool size_changed = true; + float scale = 0.f; + } window = {}; + + SDL_PixelFormat texture_format = SDL_PIXELFORMAT_UNKNOWN; + +#ifdef SUPPORT_AUDIO + std::unique_ptr audio_; +#endif +}; + +#endif diff --git a/src/platform/sdl/sdl_audio.cpp b/src/platform/sdl/sdl_audio.cpp index 16d0a4ecfa..e9bfa987aa 100644 --- a/src/platform/sdl/sdl_audio.cpp +++ b/src/platform/sdl/sdl_audio.cpp @@ -26,21 +26,11 @@ #include #include -#ifdef EMSCRIPTEN -# include -#endif - #include "sdl_audio.h" #include "output.h" using namespace std::chrono_literals; -namespace { -#if SDL_MAJOR_VERSION >= 2 - SDL_AudioDeviceID audio_dev_id = 0; -#endif -} - void sdl_audio_callback(void* userdata, uint8_t* stream, int length) { // no mutex locking required, SDL does this before calling @@ -57,12 +47,6 @@ AudioDecoder::Format sdl_format_to_format(Uint16 format) { return AudioDecoder::Format::U16; case AUDIO_S16SYS: return AudioDecoder::Format::S16; -#if SDL_MAJOR_VERSION > 1 - case AUDIO_S32: - return AudioDecoder::Format::S32; - case AUDIO_F32: - return AudioDecoder::Format::F32; -#endif default: Output::Warning("Couldn't find GenericAudio format for {:#x}", format); assert(false); @@ -79,20 +63,7 @@ SdlAudio::SdlAudio(const Game_ConfigAudio& cfg) : return; } -#ifdef EMSCRIPTEN - // Get preferred sample rate from Browser (-> OS) - const int frequency = EM_ASM_INT_V({ - var context; - try { - context = new AudioContext(); - } catch (e) { - context = new webkitAudioContext(); - } - return context.sampleRate; - }); -#else const int frequency = 44100; -#endif SDL_AudioSpec want = {}; SDL_AudioSpec have = {}; @@ -102,13 +73,7 @@ SdlAudio::SdlAudio(const Game_ConfigAudio& cfg) : want.samples = 2048; want.callback = sdl_audio_callback; want.userdata = this; - -#if SDL_MAJOR_VERSION >= 2 - audio_dev_id = SDL_OpenAudioDevice(nullptr, 0, &want, &have, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); - bool init_success = audio_dev_id > 0; -#else bool init_success = SDL_OpenAudio(&want, &have) >= 0; -#endif if (!init_success) { Output::Warning("Couldn't open audio: {}", SDL_GetError()); @@ -118,35 +83,19 @@ SdlAudio::SdlAudio(const Game_ConfigAudio& cfg) : SetFormat(have.freq, sdl_format_to_format(have.format), have.channels); // Start Audio -#if SDL_MAJOR_VERSION >= 2 - SDL_PauseAudioDevice(audio_dev_id, 0); -#else SDL_PauseAudio(0); -#endif } SdlAudio::~SdlAudio() { -#if SDL_MAJOR_VERSION >= 2 - SDL_CloseAudioDevice(audio_dev_id); -#else SDL_CloseAudio(); -#endif } void SdlAudio::LockMutex() const { -#if SDL_MAJOR_VERSION >= 2 - SDL_LockAudioDevice(audio_dev_id); -#else SDL_LockAudio(); -#endif } void SdlAudio::UnlockMutex() const { -#if SDL_MAJOR_VERSION >= 2 - SDL_UnlockAudioDevice(audio_dev_id); -#else SDL_UnlockAudio(); -#endif } #endif diff --git a/src/window_settings.cpp b/src/window_settings.cpp index 82e20ee4ae..a27d013a9c 100644 --- a/src/window_settings.cpp +++ b/src/window_settings.cpp @@ -525,10 +525,11 @@ void Window_Settings::RefreshLicense() { AddOption(MenuItem("expat", "XML parser", "MIT"), [](){}); AddOption(MenuItem("ICU", "Unicode library", "ICU"), [](){}); #if USE_SDL == 1 - AddOption(MenuItem("SDL", "Abstraction layer for graphic, audio, input and more", "LGPLv2.1+"), [](){}); -#endif -#if USE_SDL == 2 + AddOption(MenuItem("SDL1", "Abstraction layer for graphic, audio, input and more", "LGPLv2.1+"), [](){}); +#elif USE_SDL == 2 AddOption(MenuItem("SDL2", "Abstraction layer for graphic, audio, input and more", "zlib"), [](){}); +#elif USE_SDL == 3 + AddOption(MenuItem("SDL3", "Abstraction layer for graphic, audio, input and more", "zlib"), [](){}); #endif #ifdef HAVE_FREETYPE AddOption(MenuItem("Freetype", "Font parsing and rasterization library", "Freetype"), [](){}); From a9c9264fa4a2dc5a50f6a6867fe946f3ec998bef Mon Sep 17 00:00:00 2001 From: Ghabry Date: Wed, 22 Jan 2025 17:14:15 +0100 Subject: [PATCH 5/5] CMake: Do not autoselect between SDL3,2,1 anymore --- CMakeLists.txt | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 105830855c..ce126ab3b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -564,7 +564,7 @@ elseif(NINTENDO_WIIU) elseif(AMIGA) set(PLAYER_TARGET_PLATFORM "SDL1" CACHE STRING "Platform to compile for.") else() - set(PLAYER_TARGET_PLATFORM "SDL3" CACHE STRING "Platform to compile for. Options: SDL3 SDL2 SDL1 libretro") + set(PLAYER_TARGET_PLATFORM "Unset" CACHE STRING "Platform to compile for. Options: SDL3 SDL2 SDL1 libretro") set_property(CACHE PLAYER_TARGET_PLATFORM PROPERTY STRINGS SDL3 SDL2 SDL1 libretro) endif() set(PLAYER_BUILD_EXECUTABLE ON) @@ -579,7 +579,10 @@ if(ANDROID AND PLAYER_GRADLE_BUILD) endforeach(f) endif() -if(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL3") +if(${PLAYER_TARGET_PLATFORM} STREQUAL "Unset") + get_property(PLAT_PROP CACHE PLAYER_TARGET_PLATFORM PROPERTY STRINGS) + message(FATAL_ERROR "Please specify the target platform with -DPLAYER_TARGET_PLATFORM.\nOptions: ${PLAT_PROP}") +elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL3") target_sources(${PROJECT_NAME} PRIVATE src/platform/sdl/sdl3_ui.cpp src/platform/sdl/sdl3_ui.h) @@ -714,9 +717,7 @@ else() endif() # Sound system to use -if(PLAYER_AUDIO_BACKEND) - # already set earlier in the platform config -elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL3") +if(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL3") set(PLAYER_AUDIO_BACKEND "SDL3" CACHE STRING "Audio system to use. Options: SDL3 OFF") set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS SDL3 OFF) elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL2") @@ -735,6 +736,7 @@ else() set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS ${PLAYER_TARGET_PLATFORM} OFF) endif() +get_property(AUDIO_PROP CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS) set(PLAYER_HAS_AUDIO ON) if(${PLAYER_AUDIO_BACKEND} STREQUAL "SDL3") target_sources(${PROJECT_NAME} PRIVATE @@ -758,7 +760,11 @@ elseif(${PLAYER_AUDIO_BACKEND} STREQUAL ${PLAYER_TARGET_PLATFORM}) elseif(${PLAYER_AUDIO_BACKEND} STREQUAL "OFF") set(PLAYER_HAS_AUDIO OFF) else() - message(FATAL_ERROR "Invalid Audio Backend ${PLAYER_AUDIO_BACKEND}") + message(FATAL_ERROR "Invalid Audio Backend ${PLAYER_AUDIO_BACKEND}\nOptions: ${AUDIO_PROP}") +endif() + +if(NOT PLAYER_AUDIO_BACKEND IN_LIST AUDIO_PROP) + message(FATAL_ERROR "Incompatible audio backend configuration ${PLAYER_AUDIO_BACKEND}.\nPlease clear the CMake cache.\nOptions: ${AUDIO_PROP}") endif() # Shared by homebrew platforms