diff --git a/CMakeLists.txt b/CMakeLists.txt index 87859f18f0..52b599a4ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.16...3.28 FATAL_ERROR) +cmake_minimum_required(VERSION 3.16...3.31 FATAL_ERROR) project(EasyRPG_Player VERSION 0.8 DESCRIPTION "Interpreter for RPG Maker 2000/2003 games" @@ -556,16 +556,14 @@ elseif(VITA) set(PLAYER_TARGET_PLATFORM "psvita" CACHE STRING "Platform to compile for.") elseif(NINTENDO_WII) 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) 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 "SDL2" 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 +577,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) @@ -613,7 +624,7 @@ if(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL2") if(WIN32) target_link_libraries(${PROJECT_NAME} "Dwmapi") endif() -elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL1") +elseif(PLAYER_TARGET_PLATFORM STREQUAL "SDL1") if(NINTENDO_WII) find_package(SDL REQUIRED) target_compile_definitions(${PROJECT_NAME} PUBLIC PLAYER_NINTENDO) @@ -633,12 +644,12 @@ elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "SDL1") USE_SDL=1 EASYRPG_CONFIG_NAME="config_sdl1.ini" ) -elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "libretro") +elseif(PLAYER_TARGET_PLATFORM STREQUAL "libretro") target_compile_definitions(${PROJECT_NAME} PUBLIC PLAYER_UI=LibretroUi USE_LIBRETRO=1) set(PLAYER_BUILD_EXECUTABLE OFF) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/builds/libretro) target_link_libraries(${PROJECT_NAME} retro_common) -elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "3ds") +elseif(PLAYER_TARGET_PLATFORM STREQUAL "3ds") target_compile_definitions(${PROJECT_NAME} PUBLIC PLAYER_UI=CtrUi PLAYER_NINTENDO) target_compile_options(${PROJECT_NAME} PUBLIC -Wno-psabi) # Remove abi warning after devkitarm ships newer gcc # generate gfx assets @@ -667,7 +678,7 @@ elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "3ds") src/platform/3ds/ui.cpp src/platform/3ds/ui.h) target_link_libraries(${PROJECT_NAME} 3ds-assets) -elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "psvita") +elseif(PLAYER_TARGET_PLATFORM STREQUAL "psvita") include("$ENV{VITASDK}/share/vita.cmake" REQUIRED) target_compile_definitions(${PROJECT_NAME} PUBLIC PLAYER_UI=Psp2Ui) target_compile_options(${PROJECT_NAME} PUBLIC -Wno-psabi) # Remove abi warning after vitasdk ships newer gcc @@ -678,7 +689,7 @@ elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "psvita") src/platform/psvita/input_buttons.cpp src/platform/psvita/ui.cpp src/platform/psvita/ui.h) -elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "switch") +elseif(PLAYER_TARGET_PLATFORM STREQUAL "switch") target_compile_definitions(${PROJECT_NAME} PUBLIC PLAYER_UI=NxUi PLAYER_NINTENDO) find_package(OpenGL CONFIG REQUIRED) find_library(GLAD glad REQUIRED) @@ -701,18 +712,25 @@ else() 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.") +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") + if(NINTENDO_WII) + set(PLAYER_AUDIO_BACKEND "SDL2" CACHE STRING "Audio system to use. Options: SDL2 AESND OFF") + set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS SDL2 AESND) + else() + set(PLAYER_AUDIO_BACKEND "SDL2" CACHE STRING "Audio system to use. Options: SDL2 OFF") + set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS SDL2 OFF) + endif() +elseif(PLAYER_TARGET_PLATFORM STREQUAL "SDL1") + if(NINTENDO_WII) + set(PLAYER_AUDIO_BACKEND "AESND" CACHE STRING "Audio system to use. Options: SDL1 AESND OFF") + set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS SDL1 AESND) + else() + set(PLAYER_AUDIO_BACKEND "SDL1" CACHE STRING "Audio system to use. Options: SDL1 OFF") + set_property(CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS SDL1 OFF) 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") @@ -720,25 +738,34 @@ 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) -elseif(${PLAYER_AUDIO_BACKEND} STREQUAL "SDL2") + 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/sdl2_audio.cpp + src/platform/sdl/sdl2_audio.h) +elseif(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 "AESND") +elseif(PLAYER_AUDIO_BACKEND STREQUAL "AESND") target_sources(${PROJECT_NAME} PRIVATE src/platform/wii/audio.cpp 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_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}") + message(FATAL_ERROR "Invalid Audio Backend ${PLAYER_AUDIO_BACKEND}\nOptions: ${AUDIO_PROP}") +endif() + +get_property(AUDIO_PROP CACHE PLAYER_AUDIO_BACKEND PROPERTY STRINGS) +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 @@ -834,7 +861,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten") endif() # Endianess check -if(${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.20) +if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.20) if (CMAKE_CXX_BYTE_ORDER STREQUAL "BIG_ENDIAN") target_compile_definitions(${PROJECT_NAME} PRIVATE WORDS_BIGENDIAN=1) endif() @@ -897,7 +924,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_CONSOLE) + AND NOT PLAYER_CONSOLE_PORT) target_compile_definitions(${PROJECT_NAME} PUBLIC HAVE_WINE=1) endif() @@ -952,7 +979,7 @@ endif() if(PLAYER_HAS_AUDIO) target_compile_definitions(${PROJECT_NAME} PUBLIC SUPPORT_AUDIO=1) - if(${PLAYER_TARGET_PLATFORM} STREQUAL "libretro") + if(PLAYER_TARGET_PLATFORM STREQUAL "libretro") if (WIN32 OR UNIX OR APPLE) set(SUPPORT_NATIVE_MIDI ON) endif() @@ -1007,7 +1034,7 @@ 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) - if(${PLAYER_AUDIO_RESAMPLER} STREQUAL "Auto") + if(PLAYER_AUDIO_RESAMPLER STREQUAL "Auto") set(PLAYER_AUDIO_RESAMPLER_IS_AUTO ON) endif() @@ -1026,12 +1053,12 @@ if(PLAYER_HAS_AUDIO) DEFINITION HAVE_LIBSAMPLERATE TARGET Samplerate::Samplerate) endif() - elseif(${PLAYER_AUDIO_RESAMPLER} STREQUAL "speexdsp") + elseif(PLAYER_AUDIO_RESAMPLER STREQUAL "speexdsp") player_find_package(NAME speexdsp DEFINITION HAVE_LIBSPEEXDSP TARGET speexdsp::speexdsp REQUIRED) - elseif(${PLAYER_AUDIO_RESAMPLER} STREQUAL "samplerate") + elseif(PLAYER_AUDIO_RESAMPLER STREQUAL "samplerate") player_find_package(NAME Samplerate DEFINITION HAVE_LIBSAMPLERATE TARGET Samplerate::Samplerate @@ -1119,7 +1146,7 @@ if(PLAYER_HAS_AUDIO) endif() # Executable -if(${PLAYER_BUILD_EXECUTABLE} AND ${PLAYER_TARGET_PLATFORM} MATCHES "^SDL.*$" AND NOT PLAYER_CONSOLE_PORT) +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") @@ -1344,7 +1371,7 @@ elseif(PLAYER_CONSOLE_PORT) set(CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY OFF) # we do this manually include(CPack) else() # library - if(${PLAYER_TARGET_PLATFORM} STREQUAL "libretro") + if(PLAYER_TARGET_PLATFORM STREQUAL "libretro") add_library(easyrpg_libretro src/platform/libretro/audio.cpp src/platform/libretro/audio.h @@ -1366,7 +1393,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 @@ -1640,5 +1667,5 @@ message(STATUS "") if (${PLAYER_TARGET_PLATFORM} STREQUAL "SDL1") message(WARNING "SDL1 is deprecated!") - message(WARNING "Please migrate to SDL2!") + message(WARNING "Please migrate to SDL2 or SDL3!") endif() diff --git a/CMakePresets.json b/CMakePresets.json index 53ff62878a..27fd1c5a5d 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -23,7 +23,7 @@ }, { "name": "debug", - "displayName": "Use system environment (Debug)", + "displayName": "System environment (Debug)", "inherits": [ "parent", "type-debug" @@ -31,7 +31,7 @@ }, { "name": "relwithdebinfo", - "displayName": "Use system environment (RelWithDebInfo)", + "displayName": "System environment (RelWithDebInfo)", "inherits": [ "parent", "type-relwithdebinfo" @@ -39,15 +39,96 @@ }, { "name": "release", - "displayName": "Use system environment (Release)", + "displayName": "System environment (Release)", "inherits": [ "parent", "type-release" ] }, + { + "name": "sdl3-debug", + "displayName": "System environment (SDL3, Debug)", + "inherits": [ + "build-sdl3", + "parent", + "type-debug" + ] + }, + { + "name": "sdl3-relwithdebinfo", + "displayName": "System environment (SDL3, RelWithDebInfo)", + "inherits": [ + "build-sdl3", + "parent", + "type-relwithdebinfo" + ] + }, + { + "name": "sdl3-release", + "displayName": "System environment (SDL3, Release)", + "inherits": [ + "build-sdl3", + "parent", + "type-release" + ] + }, + { + "name": "sdl2-debug", + "displayName": "System environment (SDL2, Debug)", + "inherits": [ + "build-sdl2", + "parent", + "type-debug" + ] + }, + { + "name": "sdl2-relwithdebinfo", + "displayName": "System environment (SDL2, RelWithDebInfo)", + "inherits": [ + "build-sdl2", + "parent", + "type-relwithdebinfo" + ] + }, + { + "name": "sdl2-release", + "displayName": "System environment (SDL2, Release)", + "inherits": [ + "build-sdl2", + "parent", + "type-release" + ] + }, + { + "name": "sdl1-debug", + "displayName": "System environment (SDL1, Debug)", + "inherits": [ + "build-sdl1", + "parent", + "type-debug" + ] + }, + { + "name": "sdl1-relwithdebinfo", + "displayName": "System environment (SDL1, RelWithDebInfo)", + "inherits": [ + "build-sdl1", + "parent", + "type-relwithdebinfo" + ] + }, + { + "name": "sdl1-release", + "displayName": "System environment (SDL1, Release)", + "inherits": [ + "build-sdl1", + "parent", + "type-release" + ] + }, { "name": "libretro-debug", - "displayName": "Use system environment (libretro core) (Debug)", + "displayName": "System environment (libretro core, Debug)", "inherits": [ "build-libretro", "parent", @@ -56,7 +137,7 @@ }, { "name": "libretro-relwithdebinfo", - "displayName": "Use system environment (libretro core) (RelWithDebInfo)", + "displayName": "System environment (libretro core, RelWithDebInfo)", "inherits": [ "build-libretro", "parent", @@ -65,7 +146,7 @@ }, { "name": "libretro-release", - "displayName": "Use system environment (libretro core) (Release)", + "displayName": "System environment (libretro core, Release)", "inherits": [ "build-libretro", "parent", @@ -105,9 +186,90 @@ "type-release" ] }, + { + "name": "linux-sdl3-debug", + "displayName": "Linux (SDL3, Debug)", + "inherits": [ + "build-sdl3", + "linux-parent", + "type-debug" + ] + }, + { + "name": "linux-sdl3-relwithdebinfo", + "displayName": "Linux (SDL3, RelWithDebInfo)", + "inherits": [ + "build-sdl3", + "linux-parent", + "type-relwithdebinfo" + ] + }, + { + "name": "linux-sdl3-release", + "displayName": "Linux (SDL3, Release)", + "inherits": [ + "build-sdl3", + "linux-parent", + "type-release" + ] + }, + { + "name": "linux-sdl2-debug", + "displayName": "Linux (SDL2, Debug)", + "inherits": [ + "build-sdl2", + "linux-parent", + "type-debug" + ] + }, + { + "name": "linux-sdl2-relwithdebinfo", + "displayName": "Linux (SDL2, RelWithDebInfo)", + "inherits": [ + "build-sdl2", + "linux-parent", + "type-relwithdebinfo" + ] + }, + { + "name": "linux-sdl2-release", + "displayName": "Linux (SDL2, Release)", + "inherits": [ + "build-sdl2", + "linux-parent", + "type-release" + ] + }, + { + "name": "linux-sdl1-debug", + "displayName": "Linux (SDL1, Debug)", + "inherits": [ + "build-sdl1", + "linux-parent", + "type-debug" + ] + }, + { + "name": "linux-sdl1-relwithdebinfo", + "displayName": "Linux (SDL1, RelWithDebInfo)", + "inherits": [ + "build-sdl1", + "linux-parent", + "type-relwithdebinfo" + ] + }, + { + "name": "linux-sdl1-release", + "displayName": "Linux (SDL1, Release)", + "inherits": [ + "build-sdl1", + "linux-parent", + "type-release" + ] + }, { "name": "linux-libretro-debug", - "displayName": "Linux (libretro core) (Debug)", + "displayName": "Linux (libretro core, Debug)", "inherits": [ "build-libretro", "linux-parent", @@ -116,7 +278,7 @@ }, { "name": "linux-libretro-relwithdebinfo", - "displayName": "Linux (libretro core) (RelWithDebInfo)", + "displayName": "Linux (libretro core, RelWithDebInfo)", "inherits": [ "build-libretro", "linux-parent", @@ -125,7 +287,7 @@ }, { "name": "linux-libretro-release", - "displayName": "Linux (libretro core) (Release)", + "displayName": "Linux (libretro core, Release)", "inherits": [ "build-libretro", "linux-parent", @@ -164,9 +326,63 @@ "type-release" ] }, + { + "name": "windows-sdl3-debug", + "displayName": "Windows (SDL3, Debug)", + "inherits": [ + "build-sdl3", + "windows-parent", + "type-debug" + ] + }, + { + "name": "windows-sdl3-relwithdebinfo", + "displayName": "Windows (SDL3, RelWithDebInfo)", + "inherits": [ + "build-sdl3", + "windows-parent", + "type-relwithdebinfo" + ] + }, + { + "name": "windows-sdl3-release", + "displayName": "Windows (SDL3, Release)", + "inherits": [ + "build-sdl3", + "windows-parent", + "type-release" + ] + }, + { + "name": "windows-sdl2-debug", + "displayName": "Windows (SDL2, Debug)", + "inherits": [ + "build-sdl2", + "windows-parent", + "type-debug" + ] + }, + { + "name": "windows-sdl2-relwithdebinfo", + "displayName": "Windows (SDL2, RelWithDebInfo)", + "inherits": [ + "build-sdl2", + "windows-parent", + "type-relwithdebinfo" + ] + }, + { + "name": "windows-sdl2-release", + "displayName": "Windows (SDL2, Release)", + "inherits": [ + "build-sdl2", + "windows-parent", + "type-release" + ] + }, { "name": "windows-libretro-debug", - "displayName": "Windows (libretro core) (Debug)", + "displayName": "Windows (libretro core, Debug)", "inherits": [ "build-libretro", "windows-parent", @@ -175,7 +391,7 @@ }, { "name": "windows-libretro-relwithdebinfo", - "displayName": "Windows (libretro core) (RelWithDebInfo)", + "displayName": "Windows (libretro core, RelWithDebInfo)", "inherits": [ "build-libretro", "windows-parent", @@ -184,7 +400,7 @@ }, { "name": "windows-libretro-release", - "displayName": "Windows (libretro core) (Release)", + "displayName": "Windows (libretro core, Release)", "inherits": [ "build-libretro", "windows-parent", @@ -225,9 +441,63 @@ "type-release" ] }, + { + "name": "windows-x86-vs2022-sdl3-debug", + "displayName": "Windows (x86) using Visual Studio 2022 (SDL3, Debug)", + "inherits": [ + "build-sdl3", + "windows-x86-vs2022-parent", + "type-debug" + ] + }, + { + "name": "windows-x86-vs2022-sdl3-relwithdebinfo", + "displayName": "Windows (x86) using Visual Studio 2022 (SDL3, RelWithDebInfo)", + "inherits": [ + "build-sdl3", + "windows-x86-vs2022-parent", + "type-relwithdebinfo" + ] + }, + { + "name": "windows-x86-vs2022-sdl3-release", + "displayName": "Windows (x86) using Visual Studio 2022 (SDL3, Release)", + "inherits": [ + "build-sdl3", + "windows-x86-vs2022-parent", + "type-release" + ] + }, + { + "name": "windows-x86-vs2022-sdl2-debug", + "displayName": "Windows (x86) using Visual Studio 2022 (SDL2, Debug)", + "inherits": [ + "build-sdl2", + "windows-x86-vs2022-parent", + "type-debug" + ] + }, + { + "name": "windows-x86-vs2022-sdl2-relwithdebinfo", + "displayName": "Windows (x86) using Visual Studio 2022 (SDL2, RelWithDebInfo)", + "inherits": [ + "build-sdl2", + "windows-x86-vs2022-parent", + "type-relwithdebinfo" + ] + }, + { + "name": "windows-x86-vs2022-sdl2-release", + "displayName": "Windows (x86) using Visual Studio 2022 (SDL2, Release)", + "inherits": [ + "build-sdl2", + "windows-x86-vs2022-parent", + "type-release" + ] + }, { "name": "windows-x86-vs2022-libretro-debug", - "displayName": "Windows (x86) using Visual Studio 2022 (libretro core) (Debug)", + "displayName": "Windows (x86) using Visual Studio 2022 (libretro core, Debug)", "inherits": [ "build-libretro", "windows-x86-vs2022-parent", @@ -236,7 +506,7 @@ }, { "name": "windows-x86-vs2022-libretro-relwithdebinfo", - "displayName": "Windows (x86) using Visual Studio 2022 (libretro core) (RelWithDebInfo)", + "displayName": "Windows (x86) using Visual Studio 2022 (libretro core, RelWithDebInfo)", "inherits": [ "build-libretro", "windows-x86-vs2022-parent", @@ -245,7 +515,7 @@ }, { "name": "windows-x86-vs2022-libretro-release", - "displayName": "Windows (x86) using Visual Studio 2022 (libretro core) (Release)", + "displayName": "Windows (x86) using Visual Studio 2022 (libretro core, Release)", "inherits": [ "build-libretro", "windows-x86-vs2022-parent", @@ -286,9 +556,63 @@ "type-release" ] }, + { + "name": "windows-x64-vs2022-sdl3-debug", + "displayName": "Windows (x64) using Visual Studio 2022 (SDL3, Debug)", + "inherits": [ + "build-sdl3", + "windows-x64-vs2022-parent", + "type-debug" + ] + }, + { + "name": "windows-x64-vs2022-sdl3-relwithdebinfo", + "displayName": "Windows (x64) using Visual Studio 2022 (SDL3, RelWithDebInfo)", + "inherits": [ + "build-sdl3", + "windows-x64-vs2022-parent", + "type-relwithdebinfo" + ] + }, + { + "name": "windows-x64-vs2022-sdl3-release", + "displayName": "Windows (x64) using Visual Studio 2022 (SDL3, Release)", + "inherits": [ + "build-sdl3", + "windows-x64-vs2022-parent", + "type-release" + ] + }, + { + "name": "windows-x64-vs2022-sdl2-debug", + "displayName": "Windows (x64) using Visual Studio 2022 (SDL2, Debug)", + "inherits": [ + "build-sdl2", + "windows-x64-vs2022-parent", + "type-debug" + ] + }, + { + "name": "windows-x64-vs2022-sdl2-relwithdebinfo", + "displayName": "Windows (x64) using Visual Studio 2022 (SDL2, RelWithDebInfo)", + "inherits": [ + "build-sdl2", + "windows-x64-vs2022-parent", + "type-relwithdebinfo" + ] + }, + { + "name": "windows-x64-vs2022-sdl2-release", + "displayName": "Windows (x64) using Visual Studio 2022 (SDL2, Release)", + "inherits": [ + "build-sdl2", + "windows-x64-vs2022-parent", + "type-release" + ] + }, { "name": "windows-x64-vs2022-libretro-debug", - "displayName": "Windows (x64) using Visual Studio 2022 (libretro core) (Debug)", + "displayName": "Windows (x64) using Visual Studio 2022 (libretro core, Debug)", "inherits": [ "build-libretro", "windows-x64-vs2022-parent", @@ -297,7 +621,7 @@ }, { "name": "windows-x64-vs2022-libretro-relwithdebinfo", - "displayName": "Windows (x64) using Visual Studio 2022 (libretro core) (RelWithDebInfo)", + "displayName": "Windows (x64) using Visual Studio 2022 (libretro core, RelWithDebInfo)", "inherits": [ "build-libretro", "windows-x64-vs2022-parent", @@ -306,7 +630,7 @@ }, { "name": "windows-x64-vs2022-libretro-release", - "displayName": "Windows (x64) using Visual Studio 2022 (libretro core) (Release)", + "displayName": "Windows (x64) using Visual Studio 2022 (libretro core, Release)", "inherits": [ "build-libretro", "windows-x64-vs2022-parent", @@ -351,9 +675,63 @@ "type-release" ] }, + { + "name": "macos-sdl3-debug", + "displayName": "macOS (SDL3, Debug)", + "inherits": [ + "build-sdl3", + "macos-parent", + "type-debug" + ] + }, + { + "name": "macos-sdl3-relwithdebinfo", + "displayName": "macOS (SDL3, RelWithDebInfo)", + "inherits": [ + "build-sdl3", + "macos-parent", + "type-relwithdebinfo" + ] + }, + { + "name": "macos-sdl3-release", + "displayName": "macOS (SDL3, Release)", + "inherits": [ + "build-sdl3", + "macos-parent", + "type-release" + ] + }, + { + "name": "macos-sdl2-debug", + "displayName": "macOS (SDL2, Debug)", + "inherits": [ + "build-sdl2", + "macos-parent", + "type-debug" + ] + }, + { + "name": "macos-sdl2-relwithdebinfo", + "displayName": "macOS (SDL2, RelWithDebInfo)", + "inherits": [ + "build-sdl2", + "macos-parent", + "type-relwithdebinfo" + ] + }, + { + "name": "macos-sdl2-release", + "displayName": "macOS (SDL2, Release)", + "inherits": [ + "build-sdl2", + "macos-parent", + "type-release" + ] + }, { "name": "macos-libretro-debug", - "displayName": "macOS (libretro core) (Debug)", + "displayName": "macOS (libretro core, Debug)", "inherits": [ "build-libretro", "macos-parent", @@ -362,7 +740,7 @@ }, { "name": "macos-libretro-relwithdebinfo", - "displayName": "macOS (libretro core) (RelWithDebInfo)", + "displayName": "macOS (libretro core, RelWithDebInfo)", "inherits": [ "build-libretro", "macos-parent", @@ -371,7 +749,7 @@ }, { "name": "macos-libretro-release", - "displayName": "macOS (libretro core) (Release)", + "displayName": "macOS (libretro core, Release)", "inherits": [ "build-libretro", "macos-parent", @@ -391,7 +769,7 @@ }, { "name": "emscripten-debug", - "displayName": "Emscripten (Web) (Debug)", + "displayName": "Emscripten Web (Debug)", "inherits": [ "emscripten-parent", "type-debug" @@ -399,7 +777,7 @@ }, { "name": "emscripten-relwithdebinfo", - "displayName": "Emscripten (Web) (RelWithDebInfo)", + "displayName": "Emscripten Web (RelWithDebInfo)", "inherits": [ "emscripten-parent", "type-relwithdebinfo" @@ -407,68 +785,95 @@ }, { "name": "emscripten-release", - "displayName": "Emscripten (Web) (Release)", + "displayName": "Emscripten Web (Release)", "inherits": [ "emscripten-parent", "type-release" ] }, { - "name": "3ds-parent", - "toolchainFile": "$env{DEVKITPRO}/cmake/3DS.cmake", - "cacheVariables": { - "PLAYER_PREFIX_PATH_APPEND": "$env{EASYRPG_BUILDSCRIPTS}/3ds" - }, - "inherits": "dkp-user", - "hidden": true + "name": "emscripten-sdl3-debug", + "displayName": "Emscripten Web (SDL3, Debug)", + "inherits": [ + "build-sdl3", + "emscripten-parent", + "type-debug" + ] }, { - "name": "3ds-debug", - "displayName": "Nintendo 3DS (Debug)", + "name": "emscripten-sdl3-relwithdebinfo", + "displayName": "Emscripten Web (SDL3, RelWithDebInfo)", "inherits": [ - "3ds-parent", + "build-sdl3", + "emscripten-parent", + "type-relwithdebinfo" + ] + }, + { + "name": "emscripten-sdl3-release", + "displayName": "Emscripten Web (SDL3, Release)", + "inherits": [ + "build-sdl3", + "emscripten-parent", + "type-release" + ] + }, + { + "name": "emscripten-sdl2-debug", + "displayName": "Emscripten Web (SDL2, Debug)", + "inherits": [ + "build-sdl2", + "emscripten-parent", "type-debug" ] }, { - "name": "3ds-relwithdebinfo", - "displayName": "Nintendo 3DS (RelWithDebInfo)", + "name": "emscripten-sdl2-relwithdebinfo", + "displayName": "Emscripten Web (SDL2, RelWithDebInfo)", "inherits": [ - "3ds-parent", + "build-sdl2", + "emscripten-parent", "type-relwithdebinfo" ] }, { - "name": "3ds-release", - "displayName": "Nintendo 3DS (Release)", + "name": "emscripten-sdl2-release", + "displayName": "Emscripten Web (SDL2, Release)", "inherits": [ - "3ds-parent", + "build-sdl2", + "emscripten-parent", "type-release" ] }, { - "name": "3ds-libretro-debug", - "displayName": "Nintendo 3DS (libretro core) (Debug)", + "name": "3ds-parent", + "toolchainFile": "$env{DEVKITPRO}/cmake/3DS.cmake", + "cacheVariables": { + "PLAYER_PREFIX_PATH_APPEND": "$env{EASYRPG_BUILDSCRIPTS}/3ds" + }, + "inherits": "dkp-user", + "hidden": true + }, + { + "name": "3ds-debug", + "displayName": "Nintendo 3DS (Debug)", "inherits": [ - "build-libretro", "3ds-parent", "type-debug" ] }, { - "name": "3ds-libretro-relwithdebinfo", - "displayName": "Nintendo 3DS (libretro core) (RelWithDebInfo)", + "name": "3ds-relwithdebinfo", + "displayName": "Nintendo 3DS (RelWithDebInfo)", "inherits": [ - "build-libretro", "3ds-parent", "type-relwithdebinfo" ] }, { - "name": "3ds-libretro-release", - "displayName": "Nintendo 3DS (libretro core) (Release)", + "name": "3ds-release", + "displayName": "Nintendo 3DS (Release)", "inherits": [ - "build-libretro", "3ds-parent", "type-release" ] @@ -508,7 +913,7 @@ }, { "name": "switch-libretro-debug", - "displayName": "Nintendo Switch (libretro core) (Debug)", + "displayName": "Nintendo Switch (libretro core, Debug)", "inherits": [ "build-libretro", "switch-parent", @@ -517,7 +922,7 @@ }, { "name": "switch-libretro-relwithdebinfo", - "displayName": "Nintendo Switch (libretro core) (RelWithDebInfo)", + "displayName": "Nintendo Switch (libretro core, RelWithDebInfo)", "inherits": [ "build-libretro", "switch-parent", @@ -526,7 +931,7 @@ }, { "name": "switch-libretro-release", - "displayName": "Nintendo Switch (libretro core) (Release)", + "displayName": "Nintendo Switch (libretro core, Release)", "inherits": [ "build-libretro", "switch-parent", @@ -566,9 +971,63 @@ "type-release" ] }, + { + "name": "wii-sdl2-debug", + "displayName": "Nintendo Wii (SDL2, Debug)", + "inherits": [ + "build-sdl2", + "wii-parent", + "type-debug" + ] + }, + { + "name": "wii-sdl2-relwithdebinfo", + "displayName": "Nintendo Wii (SDL2, RelWithDebInfo)", + "inherits": [ + "build-sdl2", + "wii-parent", + "type-relwithdebinfo" + ] + }, + { + "name": "wii-sdl2-release", + "displayName": "Nintendo Wii (SDL2, Release)", + "inherits": [ + "build-sdl2", + "wii-parent", + "type-release" + ] + }, + { + "name": "wii-sdl1-debug", + "displayName": "Nintendo Wii (SDL1, Debug)", + "inherits": [ + "build-sdl1", + "wii-parent", + "type-debug" + ] + }, + { + "name": "wii-sdl1-relwithdebinfo", + "displayName": "Nintendo Wii (SDL1, RelWithDebInfo)", + "inherits": [ + "build-sdl1", + "wii-parent", + "type-relwithdebinfo" + ] + }, + { + "name": "wii-sdl1-release", + "displayName": "Nintendo Wii (SDL1, Release)", + "inherits": [ + "build-sdl1", + "wii-parent", + "type-release" + ] + }, { "name": "wii-libretro-debug", - "displayName": "Nintendo Wii (libretro core) (Debug)", + "displayName": "Nintendo Wii (libretro core, Debug)", "inherits": [ "build-libretro", "wii-parent", @@ -577,7 +1036,7 @@ }, { "name": "wii-libretro-relwithdebinfo", - "displayName": "Nintendo Wii (libretro core) (RelWithDebInfo)", + "displayName": "Nintendo Wii (libretro core, RelWithDebInfo)", "inherits": [ "build-libretro", "wii-parent", @@ -586,7 +1045,7 @@ }, { "name": "wii-libretro-release", - "displayName": "Nintendo Wii (libretro core) (Release)", + "displayName": "Nintendo Wii (libretro core, Release)", "inherits": [ "build-libretro", "wii-parent", @@ -628,7 +1087,7 @@ }, { "name": "wiiu-libretro-debug", - "displayName": "Nintendo WiiU (libretro core) (Debug)", + "displayName": "Nintendo WiiU (libretro core, Debug)", "inherits": [ "build-libretro", "wiiu-parent", @@ -637,7 +1096,7 @@ }, { "name": "wiiu-libretro-relwithdebinfo", - "displayName": "Nintendo WiiU (libretro core) (RelWithDebInfo)", + "displayName": "Nintendo WiiU (libretro core, RelWithDebInfo)", "inherits": [ "build-libretro", "wiiu-parent", @@ -646,7 +1105,7 @@ }, { "name": "wiiu-libretro-release", - "displayName": "Nintendo WiiU (libretro core) (Release)", + "displayName": "Nintendo WiiU (libretro core, Release)", "inherits": [ "build-libretro", "wiiu-parent", @@ -688,7 +1147,7 @@ }, { "name": "psvita-libretro-debug", - "displayName": "PlayStation Vita (libretro core) (Debug)", + "displayName": "PlayStation Vita (libretro core, Debug)", "inherits": [ "build-libretro", "psvita-parent", @@ -697,7 +1156,7 @@ }, { "name": "psvita-libretro-relwithdebinfo", - "displayName": "PlayStation Vita (libretro core) (RelWithDebInfo)", + "displayName": "PlayStation Vita (libretro core, RelWithDebInfo)", "inherits": [ "build-libretro", "psvita-parent", @@ -706,7 +1165,7 @@ }, { "name": "psvita-libretro-release", - "displayName": "PlayStation Vita (libretro core) (Release)", + "displayName": "PlayStation Vita (libretro core, Release)", "inherits": [ "build-libretro", "psvita-parent", @@ -727,6 +1186,42 @@ "name": "release", "configurePreset": "release" }, + { + "name": "sdl3-debug", + "configurePreset": "sdl3-debug" + }, + { + "name": "sdl3-relwithdebinfo", + "configurePreset": "sdl3-relwithdebinfo" + }, + { + "name": "sdl3-release", + "configurePreset": "sdl3-release" + }, + { + "name": "sdl2-debug", + "configurePreset": "sdl2-debug" + }, + { + "name": "sdl2-relwithdebinfo", + "configurePreset": "sdl2-relwithdebinfo" + }, + { + "name": "sdl2-release", + "configurePreset": "sdl2-release" + }, + { + "name": "sdl1-debug", + "configurePreset": "sdl1-debug" + }, + { + "name": "sdl1-relwithdebinfo", + "configurePreset": "sdl1-relwithdebinfo" + }, + { + "name": "sdl1-release", + "configurePreset": "sdl1-release" + }, { "name": "libretro-debug", "configurePreset": "libretro-debug" @@ -751,6 +1246,42 @@ "name": "linux-release", "configurePreset": "linux-release" }, + { + "name": "linux-sdl3-debug", + "configurePreset": "linux-sdl3-debug" + }, + { + "name": "linux-sdl3-relwithdebinfo", + "configurePreset": "linux-sdl3-relwithdebinfo" + }, + { + "name": "linux-sdl3-release", + "configurePreset": "linux-sdl3-release" + }, + { + "name": "linux-sdl2-debug", + "configurePreset": "linux-sdl2-debug" + }, + { + "name": "linux-sdl2-relwithdebinfo", + "configurePreset": "linux-sdl2-relwithdebinfo" + }, + { + "name": "linux-sdl2-release", + "configurePreset": "linux-sdl2-release" + }, + { + "name": "linux-sdl1-debug", + "configurePreset": "linux-sdl1-debug" + }, + { + "name": "linux-sdl1-relwithdebinfo", + "configurePreset": "linux-sdl1-relwithdebinfo" + }, + { + "name": "linux-sdl1-release", + "configurePreset": "linux-sdl1-release" + }, { "name": "linux-libretro-debug", "configurePreset": "linux-libretro-debug" @@ -775,6 +1306,30 @@ "name": "windows-release", "configurePreset": "windows-release" }, + { + "name": "windows-sdl3-debug", + "configurePreset": "windows-sdl3-debug" + }, + { + "name": "windows-sdl3-relwithdebinfo", + "configurePreset": "windows-sdl3-relwithdebinfo" + }, + { + "name": "windows-sdl3-release", + "configurePreset": "windows-sdl3-release" + }, + { + "name": "windows-sdl2-debug", + "configurePreset": "windows-sdl2-debug" + }, + { + "name": "windows-sdl2-relwithdebinfo", + "configurePreset": "windows-sdl2-relwithdebinfo" + }, + { + "name": "windows-sdl2-release", + "configurePreset": "windows-sdl2-release" + }, { "name": "windows-libretro-debug", "configurePreset": "windows-libretro-debug" @@ -799,6 +1354,30 @@ "name": "windows-x86-vs2022-release", "configurePreset": "windows-x86-vs2022-release" }, + { + "name": "windows-x86-vs2022-sdl3-debug", + "configurePreset": "windows-x86-vs2022-sdl3-debug" + }, + { + "name": "windows-x86-vs2022-sdl3-relwithdebinfo", + "configurePreset": "windows-x86-vs2022-sdl3-relwithdebinfo" + }, + { + "name": "windows-x86-vs2022-sdl3-release", + "configurePreset": "windows-x86-vs2022-sdl3-release" + }, + { + "name": "windows-x86-vs2022-sdl2-debug", + "configurePreset": "windows-x86-vs2022-sdl2-debug" + }, + { + "name": "windows-x86-vs2022-sdl2-relwithdebinfo", + "configurePreset": "windows-x86-vs2022-sdl2-relwithdebinfo" + }, + { + "name": "windows-x86-vs2022-sdl2-release", + "configurePreset": "windows-x86-vs2022-sdl2-release" + }, { "name": "windows-x86-vs2022-libretro-debug", "configurePreset": "windows-x86-vs2022-libretro-debug" @@ -823,6 +1402,30 @@ "name": "windows-x64-vs2022-release", "configurePreset": "windows-x64-vs2022-release" }, + { + "name": "windows-x64-vs2022-sdl3-debug", + "configurePreset": "windows-x64-vs2022-sdl3-debug" + }, + { + "name": "windows-x64-vs2022-sdl3-relwithdebinfo", + "configurePreset": "windows-x64-vs2022-sdl3-relwithdebinfo" + }, + { + "name": "windows-x64-vs2022-sdl3-release", + "configurePreset": "windows-x64-vs2022-sdl3-release" + }, + { + "name": "windows-x64-vs2022-sdl2-debug", + "configurePreset": "windows-x64-vs2022-sdl2-debug" + }, + { + "name": "windows-x64-vs2022-sdl2-relwithdebinfo", + "configurePreset": "windows-x64-vs2022-sdl2-relwithdebinfo" + }, + { + "name": "windows-x64-vs2022-sdl2-release", + "configurePreset": "windows-x64-vs2022-sdl2-release" + }, { "name": "windows-x64-vs2022-libretro-debug", "configurePreset": "windows-x64-vs2022-libretro-debug" @@ -847,6 +1450,30 @@ "name": "macos-release", "configurePreset": "macos-release" }, + { + "name": "macos-sdl3-debug", + "configurePreset": "macos-sdl3-debug" + }, + { + "name": "macos-sdl3-relwithdebinfo", + "configurePreset": "macos-sdl3-relwithdebinfo" + }, + { + "name": "macos-sdl3-release", + "configurePreset": "macos-sdl3-release" + }, + { + "name": "macos-sdl2-debug", + "configurePreset": "macos-sdl2-debug" + }, + { + "name": "macos-sdl2-relwithdebinfo", + "configurePreset": "macos-sdl2-relwithdebinfo" + }, + { + "name": "macos-sdl2-release", + "configurePreset": "macos-sdl2-release" + }, { "name": "macos-libretro-debug", "configurePreset": "macos-libretro-debug" @@ -872,28 +1499,40 @@ "configurePreset": "emscripten-release" }, { - "name": "3ds-debug", - "configurePreset": "3ds-debug" + "name": "emscripten-sdl3-debug", + "configurePreset": "emscripten-sdl3-debug" }, { - "name": "3ds-relwithdebinfo", - "configurePreset": "3ds-relwithdebinfo" + "name": "emscripten-sdl3-relwithdebinfo", + "configurePreset": "emscripten-sdl3-relwithdebinfo" }, { - "name": "3ds-release", - "configurePreset": "3ds-release" + "name": "emscripten-sdl3-release", + "configurePreset": "emscripten-sdl3-release" + }, + { + "name": "emscripten-sdl2-debug", + "configurePreset": "emscripten-sdl2-debug" + }, + { + "name": "emscripten-sdl2-relwithdebinfo", + "configurePreset": "emscripten-sdl2-relwithdebinfo" }, { - "name": "3ds-libretro-debug", - "configurePreset": "3ds-libretro-debug" + "name": "emscripten-sdl2-release", + "configurePreset": "emscripten-sdl2-release" }, { - "name": "3ds-libretro-relwithdebinfo", - "configurePreset": "3ds-libretro-relwithdebinfo" + "name": "3ds-debug", + "configurePreset": "3ds-debug" + }, + { + "name": "3ds-relwithdebinfo", + "configurePreset": "3ds-relwithdebinfo" }, { - "name": "3ds-libretro-release", - "configurePreset": "3ds-libretro-release" + "name": "3ds-release", + "configurePreset": "3ds-release" }, { "name": "switch-debug", @@ -931,6 +1570,30 @@ "name": "wii-release", "configurePreset": "wii-release" }, + { + "name": "wii-sdl2-debug", + "configurePreset": "wii-sdl2-debug" + }, + { + "name": "wii-sdl2-relwithdebinfo", + "configurePreset": "wii-sdl2-relwithdebinfo" + }, + { + "name": "wii-sdl2-release", + "configurePreset": "wii-sdl2-release" + }, + { + "name": "wii-sdl1-debug", + "configurePreset": "wii-sdl1-debug" + }, + { + "name": "wii-sdl1-relwithdebinfo", + "configurePreset": "wii-sdl1-relwithdebinfo" + }, + { + "name": "wii-sdl1-release", + "configurePreset": "wii-sdl1-release" + }, { "name": "wii-libretro-debug", "configurePreset": "wii-libretro-debug" diff --git a/Makefile.am b/Makefile.am index 02992d0f91..9399a00f56 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/builds/cmake/CMakePresets.json.template b/builds/cmake/CMakePresets.json.template index c611d25086..08a2ebfdd2 100644 --- a/builds/cmake/CMakePresets.json.template +++ b/builds/cmake/CMakePresets.json.template @@ -11,7 +11,8 @@ "configurePresets": [ { "name": "", - "displayName": "Use system environment" + "displayName": "System environment", + "easyrpg_platforms": ["sdl3", "sdl2", "sdl1", "libretro"] }, { "name": "linux", @@ -19,7 +20,8 @@ "toolchainFile": "${sourceDir}/builds/cmake/LinuxToolchain.cmake", "cacheVariables": { "PLAYER_PREFIX_PATH_APPEND": "$env{EASYRPG_BUILDSCRIPTS}/linux-static" - } + }, + "easyrpg_platforms": ["sdl3", "sdl2", "sdl1", "libretro"] }, { "name": "windows", @@ -27,7 +29,8 @@ "cacheVariables": { "VCPKG_TARGET_TRIPLET": "$env{VSCMD_ARG_TGT_ARCH}-windows-static" }, - "inherits": "win-user" + "inherits": "win-user", + "easyrpg_platforms": ["sdl3", "sdl2", "libretro"] }, { "name": "windows-x86-vs2022", @@ -37,7 +40,8 @@ "cacheVariables": { "VCPKG_TARGET_TRIPLET": "x86-windows-static" }, - "inherits": "win-user" + "inherits": "win-user", + "easyrpg_platforms": ["sdl3", "sdl2", "libretro"] }, { "name": "windows-x64-vs2022", @@ -47,7 +51,8 @@ "cacheVariables": { "VCPKG_TARGET_TRIPLET": "x64-windows-static" }, - "inherits": "win-user" + "inherits": "win-user", + "easyrpg_platforms": ["sdl3", "sdl2", "libretro"] }, { "name": "macos", @@ -60,17 +65,19 @@ "type": "equals", "lhs": "${hostSystemName}", "rhs": "Darwin" - } + }, + "easyrpg_platforms": ["sdl3", "sdl2", "libretro"] }, { "name": "emscripten", - "displayName": "Emscripten (Web)", + "displayName": "Emscripten Web", "toolchainFile": "$env{EASYRPG_BUILDSCRIPTS}/emscripten/emsdk-portable/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake", "cacheVariables": { "PLAYER_PREFIX_PATH_APPEND": "$env{EASYRPG_BUILDSCRIPTS}/emscripten", "CMAKE_FIND_ROOT_PATH": "$env{EASYRPG_BUILDSCRIPTS}/emscripten", "PLAYER_JS_BUILD_SHELL": "ON" - } + }, + "easyrpg_platforms": ["sdl3", "sdl2"] }, { "name": "3ds", @@ -88,7 +95,8 @@ "cacheVariables": { "PLAYER_PREFIX_PATH_APPEND": "$env{EASYRPG_BUILDSCRIPTS}/switch" }, - "inherits": "dkp-user" + "inherits": "dkp-user", + "easyrpg_platforms": ["libretro"] }, { "name": "wii", @@ -97,7 +105,8 @@ "cacheVariables": { "PLAYER_PREFIX_PATH_APPEND": "$env{EASYRPG_BUILDSCRIPTS}/wii" }, - "inherits": "dkp-user" + "inherits": "dkp-user", + "easyrpg_platforms": ["sdl2", "sdl1", "libretro"] }, { "name": "wiiu", @@ -106,7 +115,8 @@ "cacheVariables": { "PLAYER_PREFIX_PATH_APPEND": "$env{EASYRPG_BUILDSCRIPTS}/wiiu" }, - "inherits": "dkp-user" + "inherits": "dkp-user", + "easyrpg_platforms": ["libretro"] }, { "name": "psvita", @@ -114,7 +124,8 @@ "toolchainFile": "$env{EASYRPG_BUILDSCRIPTS}/vita/vitasdk/share/vita.toolchain.cmake", "cacheVariables": { "BUILD_SHARED_LIBS": "OFF" - } + }, + "easyrpg_platforms": ["libretro"] } ], "buildPresets": [], diff --git a/builds/cmake/CMakePresetsBase.json b/builds/cmake/CMakePresetsBase.json index b7fc303c80..6b6c39602b 100644 --- a/builds/cmake/CMakePresetsBase.json +++ b/builds/cmake/CMakePresetsBase.json @@ -56,6 +56,30 @@ "BUILD_SHARED_LIBS": "OFF" } }, + { + "name": "build-sdl1", + "displayName": "builds using SDL1 backend (deprecated)", + "hidden": true, + "cacheVariables": { + "PLAYER_TARGET_PLATFORM": "SDL1" + } + }, + { + "name": "build-sdl2", + "displayName": "builds using SDL2 backend", + "hidden": true, + "cacheVariables": { + "PLAYER_TARGET_PLATFORM": "SDL2" + } + }, + { + "name": "build-sdl3", + "displayName": "builds using SDL3 backend", + "hidden": true, + "cacheVariables": { + "PLAYER_TARGET_PLATFORM": "SDL3" + } + }, { "name": "build-libretro", "displayName": "enables building of a libretro core", diff --git a/builds/cmake/LinuxToolchain.cmake b/builds/cmake/LinuxToolchain.cmake index 3913022556..76c1b0bd2c 100644 --- a/builds/cmake/LinuxToolchain.cmake +++ b/builds/cmake/LinuxToolchain.cmake @@ -7,4 +7,4 @@ set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) -set(CMAKE_FIND_ROOT_PATH ${CMAKE_PREFIX_PATH}) +set(CMAKE_FIND_ROOT_PATH ${CMAKE_PREFIX_PATH} ${PLAYER_PREFIX_PATH_APPEND}) diff --git a/builds/cmake/Modules/PlayerConfigureWindows.cmake b/builds/cmake/Modules/PlayerConfigureWindows.cmake index f901c00364..e181614a7d 100644 --- a/builds/cmake/Modules/PlayerConfigureWindows.cmake +++ b/builds/cmake/Modules/PlayerConfigureWindows.cmake @@ -13,42 +13,8 @@ if(WIN32) endif() if(MSVC) - if(${CMAKE_VERSION} VERSION_LESS "3.15.0") - message(WARNING "Your CMake version is older than 3.15") - message(WARNING "For proper MSVC runtime library support upgrade to a newer version") - - option(SHARED_RUNTIME "Windows: Build using the shared runtime library (/MD), disable for static runtime (/MT)" ON) - - # Set compiler options. - set(variables - CMAKE_C_FLAGS_DEBUG - CMAKE_C_FLAGS_MINSIZEREL - CMAKE_C_FLAGS_RELEASE - CMAKE_C_FLAGS_RELWITHDEBINFO - CMAKE_CXX_FLAGS_DEBUG - CMAKE_CXX_FLAGS_MINSIZEREL - CMAKE_CXX_FLAGS_RELEASE - CMAKE_CXX_FLAGS_RELWITHDEBINFO - ) - if(SHARED_RUNTIME) - message(STATUS "Windows: Using dynamic runtime library (/MD)") - foreach(variable ${variables}) - if(${variable} MATCHES "/MT") - string(REGEX REPLACE "/MT" "/MD" ${variable} "${${variable}}") - endif() - endforeach() - else() - message(STATUS "Windows: Using static runtime library (/MT)") - foreach(variable ${variables}) - if(${variable} MATCHES "/MD") - string(REGEX REPLACE "/MD" "/MT" ${variable} "${${variable}}") - endif() - endforeach() - endif() - else() - # Depends on vcpkg but we don't support anything else - set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>$<$:DLL>" CACHE STRING "") - endif() + # Depends on vcpkg but we don't support anything else + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>$<$:DLL>" CACHE STRING "") option(MSVC_MULTICORE "MSVC: Build using multiple cores (/MP)" ON) if (MSVC_MULTICORE) diff --git a/builds/cmake/gen-cmake-presets.py b/builds/cmake/gen-cmake-presets.py index 586387065e..e4bbad46af 100755 --- a/builds/cmake/gen-cmake-presets.py +++ b/builds/cmake/gen-cmake-presets.py @@ -9,29 +9,32 @@ import json import os -# Omit libretro preset generation -no_libretro = ["emscripten"] - script_dir = os.path.dirname(os.path.abspath(__file__)) repo_dir = f"{script_dir}/../.." with open(f"{script_dir}/CMakePresets.json.template", "r") as f: j = json.load(f) -cp = j["configurePresets"] -cp_out = [] +conf_presets = j["configurePresets"] +conf_presets_out = [] def append_name(name): if len(item["name"]) > 0: item["name"] += "-" item["name"] += name +platform_display = dict( + sdl1="SDL1", + sdl2="SDL2", + sdl3="SDL3", + libretro="libretro core" +) + # This creates the following configurePresets from the one in the template: # - As specified in the template -# - As specified but build a libretro core -# - libretro +# - For every entry in easyrpg_platforms (N) # For all of them the build types Debug, RelWithDebInfo and Release are generated. -# Making this 4 * 3 entries per preset. +# Making this (N+1) * 3 entries per preset. # The resulting "triplet" is always: # {name_from_template}-{libretro}-{build_type} @@ -40,9 +43,9 @@ def append_name(name): # The build dirs are always: # build/{name_from_template}-{libretro}-{build_type} -for base_item in cp: +for base_item in conf_presets: if base_item.get("hidden"): - cp_out.append(base_item) + conf_presets_out.append(base_item) continue # Create "base class" the build types inherit from @@ -54,10 +57,15 @@ def append_name(name): if item.get("inherits") is None: item["inherits"] = "base-user" - parent_item = item - cp_out.append(parent_item) + ep_platforms = ["default"] + if "easyrpg_platforms" in item: + ep_platforms += item["easyrpg_platforms"] + del item["easyrpg_platforms"] - for libretro in False, True: + parent_item = deepcopy(item) + conf_presets_out.append(parent_item) + + for platform in ep_platforms: # Ugly: Generates a huge amount of configurePresets # Cannot be improved until limitations in buildPresets are resolved # (see comment below) @@ -65,27 +73,25 @@ def append_name(name): item = dict(name=base_item["name"], displayName=base_item["displayName"]) name = item["name"] - if libretro and name in no_libretro: - continue - item["inherits"] = [parent_item["name"]] - if libretro: - append_name("libretro") - item["displayName"] += " (libretro core)" - item["inherits"].insert(0, "build-libretro") + if platform != "default": + append_name(platform) + item["inherits"].insert(0, f"build-{platform}") + item["displayName"] += f" ({platform_display[platform]}, {build_type})" + else: + item["displayName"] += f" ({build_type})" item["inherits"] += [f"type-{build_type.lower()}"] append_name(build_type.lower()) - item["displayName"] += f" ({build_type})" - cp_out.append(item) + conf_presets_out.append(item) -j["configurePresets"] = cp_out +j["configurePresets"] = conf_presets_out bp = j["buildPresets"] -for item in cp_out: +for item in conf_presets_out: if item.get("hidden"): continue diff --git a/builds/libretro/CMakeLists.txt b/builds/libretro/CMakeLists.txt index 2ceb863269..cc24b87101 100644 --- a/builds/libretro/CMakeLists.txt +++ b/builds/libretro/CMakeLists.txt @@ -1,12 +1,7 @@ -cmake_minimum_required(VERSION 3.7) +cmake_minimum_required(VERSION 3.16...3.31 FATAL_ERROR) project(retro_common VERSION 1.0 LANGUAGES C) -# Only for ugly MD-MT patching on old versions, new CMAKE_MSVC_RUNTIME_LIBRARY is inherited -if(${CMAKE_VERSION} VERSION_LESS "3.15.0") - include(ConfigureWindows) -endif() - # Check availability of libretro-common submodule file(GLOB RETRO_COMMON_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libretro-common/*) list(LENGTH RETRO_COMMON_DIR RETRO_COMMON_DIR_LEN) 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..3b86cfc3b7 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 } @@ -226,6 +226,11 @@ Sdl2Ui::~Sdl2Ui() { if (sdl_window) { SDL_DestroyWindow(sdl_window); } + +#ifdef SUPPORT_AUDIO + audio_.reset(); +#endif + SDL_Quit(); } @@ -1269,7 +1274,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 +1309,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..8e5d9e38a8 --- /dev/null +++ b/src/platform/sdl/sdl3_audio.cpp @@ -0,0 +1,130 @@ +/* + * 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() { + 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..25f31c081c --- /dev/null +++ b/src/platform/sdl/sdl3_ui.cpp @@ -0,0 +1,1261 @@ +/* + * 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); + } + +#ifdef SUPPORT_AUDIO + audio_.reset(); +#endif + + 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/platform/sdl/sdl_ui.cpp b/src/platform/sdl/sdl_ui.cpp index 93f0d0d30c..c806e51270 100644 --- a/src/platform/sdl/sdl_ui.cpp +++ b/src/platform/sdl/sdl_ui.cpp @@ -182,6 +182,10 @@ SdlUi::~SdlUi() { SDL_FreeSurface(main_surface_sdl); } +#ifdef SUPPORT_AUDIO + audio_.reset(); +#endif + SDL_Quit(); } 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"), [](){});