From 9cea872dbcf5e933295ed5041e76bbc59989717b Mon Sep 17 00:00:00 2001 From: Chris Thrasher Date: Tue, 12 Dec 2023 10:09:22 -0700 Subject: [PATCH 01/38] Update changelog for #2821 --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index db2f95beb..ea9410cea 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,7 @@ - Ensure GNUInstallDirs cache vars are included before first used (#2778, #2779) - [macOS] Fix incorrect variable expansion (#2780) +- Issue warning when trying to use UCRT MinGW with precompiled MSVCRT depenencies (#2821) ### Audio From 5e18e5403b3ef583064723a3f7169a76df6d521a Mon Sep 17 00:00:00 2001 From: Chris Thrasher Date: Tue, 12 Dec 2023 11:09:39 -0700 Subject: [PATCH 02/38] Fix Nix pkg-config support CMAKE_INSTALL_LIBDIR is an absolute path on Nix (which is a valid thing to do). In such a case two absolute paths would get appended resulting in a nonsense path that broke pkg-config support. --- CMakeLists.txt | 5 ++++- changelog.md | 1 + tools/pkg-config/sfml-all.pc.in | 2 +- tools/pkg-config/sfml-audio.pc.in | 2 +- tools/pkg-config/sfml-graphics.pc.in | 2 +- tools/pkg-config/sfml-network.pc.in | 2 +- tools/pkg-config/sfml-system.pc.in | 2 +- tools/pkg-config/sfml-window.pc.in | 2 +- 8 files changed, 11 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 52041b069..40cfa1b2a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -303,9 +303,12 @@ endif() sfml_set_option(SFML_INSTALL_PKGCONFIG_FILES ${SFML_INSTALL_PKGCONFIG_DEFAULT} BOOL "TRUE to automatically install pkg-config files so other projects can find SFML") if(SFML_INSTALL_PKGCONFIG_FILES) + # account for CMAKE_INSTALL_LIBDIR potentially being an absolute path + file(RELATIVE_PATH SFML_RELATIVE_INSTALL_LIBDIR ${CMAKE_INSTALL_PREFIX} ${CMAKE_INSTALL_FULL_LIBDIR}) + # set pkgconfig install directory # this could be e.g. macports on mac or msys2 on windows etc. - set(SFML_PKGCONFIG_DIR "/${CMAKE_INSTALL_LIBDIR}/pkgconfig") + set(SFML_PKGCONFIG_DIR "/${SFML_RELATIVE_INSTALL_LIBDIR}/pkgconfig") if(SFML_OS_FREEBSD OR SFML_OS_OPENBSD OR SFML_OS_NETBSD) set(SFML_PKGCONFIG_DIR "/libdata/pkgconfig") diff --git a/changelog.md b/changelog.md index ea9410cea..365e5aecd 100644 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,7 @@ - Ensure GNUInstallDirs cache vars are included before first used (#2778, #2779) - [macOS] Fix incorrect variable expansion (#2780) - Issue warning when trying to use UCRT MinGW with precompiled MSVCRT depenencies (#2821) +- Fix Nix pkg-config support ### Audio diff --git a/tools/pkg-config/sfml-all.pc.in b/tools/pkg-config/sfml-all.pc.in index 3dba4f7c8..7f5f1b9f1 100644 --- a/tools/pkg-config/sfml-all.pc.in +++ b/tools/pkg-config/sfml-all.pc.in @@ -1,6 +1,6 @@ prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=${prefix} -libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ +libdir=${exec_prefix}/@SFML_RELATIVE_INSTALL_LIBDIR@ includedir=${prefix}/include Name: SFML-all diff --git a/tools/pkg-config/sfml-audio.pc.in b/tools/pkg-config/sfml-audio.pc.in index ad7fad727..1d26fa8fc 100644 --- a/tools/pkg-config/sfml-audio.pc.in +++ b/tools/pkg-config/sfml-audio.pc.in @@ -1,6 +1,6 @@ prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=${prefix} -libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ +libdir=${exec_prefix}/@SFML_RELATIVE_INSTALL_LIBDIR@ includedir=${prefix}/include Name: SFML-audio diff --git a/tools/pkg-config/sfml-graphics.pc.in b/tools/pkg-config/sfml-graphics.pc.in index 46f53874c..868be2211 100644 --- a/tools/pkg-config/sfml-graphics.pc.in +++ b/tools/pkg-config/sfml-graphics.pc.in @@ -1,6 +1,6 @@ prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=${prefix} -libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ +libdir=${exec_prefix}/@SFML_RELATIVE_INSTALL_LIBDIR@ includedir=${prefix}/include Name: SFML-graphics diff --git a/tools/pkg-config/sfml-network.pc.in b/tools/pkg-config/sfml-network.pc.in index c0199350c..f57c7f1a4 100644 --- a/tools/pkg-config/sfml-network.pc.in +++ b/tools/pkg-config/sfml-network.pc.in @@ -1,6 +1,6 @@ prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=${prefix} -libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ +libdir=${exec_prefix}/@SFML_RELATIVE_INSTALL_LIBDIR@ includedir=${prefix}/include Name: SFML-network diff --git a/tools/pkg-config/sfml-system.pc.in b/tools/pkg-config/sfml-system.pc.in index 285852d7b..feb3f6b97 100644 --- a/tools/pkg-config/sfml-system.pc.in +++ b/tools/pkg-config/sfml-system.pc.in @@ -1,6 +1,6 @@ prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=${prefix} -libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ +libdir=${exec_prefix}/@SFML_RELATIVE_INSTALL_LIBDIR@ includedir=${prefix}/include Name: SFML-system diff --git a/tools/pkg-config/sfml-window.pc.in b/tools/pkg-config/sfml-window.pc.in index e216473ca..e287d9201 100644 --- a/tools/pkg-config/sfml-window.pc.in +++ b/tools/pkg-config/sfml-window.pc.in @@ -1,6 +1,6 @@ prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=${prefix} -libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ +libdir=${exec_prefix}/@SFML_RELATIVE_INSTALL_LIBDIR@ includedir=${prefix}/include Name: SFML-window From 67feaa0bd38da657c01ff7256e0aa9dc981aaa5d Mon Sep 17 00:00:00 2001 From: Bruno Van de Velde Date: Tue, 23 Jan 2024 15:36:53 +0100 Subject: [PATCH 03/38] Update location of sdkmanager in Android CI jobs --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f0841e4e..31afe8e45 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,7 +53,7 @@ jobs: - name: Install Android Components if: matrix.platform.name == 'Android' run: | - echo "y" | /usr/local/lib/android/sdk/tools/bin/sdkmanager --install "cmake;3.10.2.4988404" --sdk_root=ANDROID_SDK_ROOT + echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "cmake;3.10.2.4988404" --sdk_root=ANDROID_SDK_ROOT sudo ln -sf /usr/local/lib/android/sdk/cmake/3.10.2.4988404/bin/cmake /usr/bin/cmake wget -nv https://dl.google.com/android/repository/android-ndk-r18b-linux-x86_64.zip -P $GITHUB_WORKSPACE unzip -qq -d $GITHUB_WORKSPACE android-ndk-r18b-linux-x86_64.zip From 5a74cf33e92008920bfd04698dbd6a7733e1c269 Mon Sep 17 00:00:00 2001 From: Bruno Van de Velde Date: Tue, 23 Jan 2024 15:41:41 +0100 Subject: [PATCH 04/38] Replaced deprecated exec_program function in CMake scripts with execute_process, as this was causing iOS build to fail with CMake 3.28 --- cmake/Config.cmake | 2 +- cmake/toolchains/iOS.toolchain.cmake | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmake/Config.cmake b/cmake/Config.cmake index 51ac8b0b8..11f3dd5cd 100644 --- a/cmake/Config.cmake +++ b/cmake/Config.cmake @@ -59,7 +59,7 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") set(OPENGL_ES 0) # detect OS X version. (use '/usr/bin/sw_vers -productVersion' to extract V from '10.V.x'.) - EXEC_PROGRAM(/usr/bin/sw_vers ARGS -productVersion OUTPUT_VARIABLE MACOSX_VERSION_RAW) + execute_process(COMMAND /usr/bin/sw_vers -productVersion OUTPUT_VARIABLE MACOSX_VERSION_RAW) STRING(REGEX REPLACE "10\\.([0-9]+).*" "\\1" MACOSX_VERSION "${MACOSX_VERSION_RAW}") if(${MACOSX_VERSION} LESS 7) diff --git a/cmake/toolchains/iOS.toolchain.cmake b/cmake/toolchains/iOS.toolchain.cmake index c697857fa..ca98b0b43 100644 --- a/cmake/toolchains/iOS.toolchain.cmake +++ b/cmake/toolchains/iOS.toolchain.cmake @@ -54,7 +54,7 @@ set (CMAKE_OSX_DEPLOYMENT_TARGET "" CACHE STRING "Force unset of the deployment # Determine the cmake host system version so we know where to find the iOS SDKs find_program (CMAKE_UNAME uname /bin /usr/bin /usr/local/bin) if (CMAKE_UNAME) - exec_program(uname ARGS -r OUTPUT_VARIABLE CMAKE_HOST_SYSTEM_VERSION) + execute_process(COMMAND uname -r OUTPUT_VARIABLE CMAKE_HOST_SYSTEM_VERSION) string (REGEX REPLACE "^([0-9]+)\\.([0-9]+).*$" "\\1" DARWIN_MAJOR_VERSION "${CMAKE_HOST_SYSTEM_VERSION}") endif (CMAKE_UNAME) @@ -123,7 +123,7 @@ else () endif () # Setup iOS developer location unless specified manually with IOS_DEVELOPER_ROOT -exec_program(/usr/bin/xcode-select ARGS -print-path OUTPUT_VARIABLE XCODE_DEVELOPER_DIR) +execute_process(COMMAND /usr/bin/xcode-select -print-path OUTPUT_VARIABLE XCODE_DEVELOPER_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) set (IOS_DEVELOPER_ROOT "${XCODE_DEVELOPER_DIR}/Platforms/${IOS_PLATFORM_LOCATION}/Developer" CACHE PATH "Location of iOS Platform") # Find and use the most recent iOS sdk unless specified manually with IOS_SDK_ROOT From b0e25088a291772fad8364372bcff9728512579c Mon Sep 17 00:00:00 2001 From: Chris Thrasher Date: Sun, 7 Apr 2024 12:06:06 -0600 Subject: [PATCH 05/38] Initialize all `sf::Glyph` members This silences an MSVC warning about lsbDelta and rsbDelta not being initialized. Whether or not this fixes any bugs, it provides a better user experience if nobody sees those warnings. --- include/SFML/Graphics/Glyph.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/SFML/Graphics/Glyph.hpp b/include/SFML/Graphics/Glyph.hpp index 05f78760a..cd6cfb946 100644 --- a/include/SFML/Graphics/Glyph.hpp +++ b/include/SFML/Graphics/Glyph.hpp @@ -46,7 +46,7 @@ public: /// \brief Default constructor /// //////////////////////////////////////////////////////////// - Glyph() : advance(0) {} + Glyph() : advance(0), lsbDelta(0), rsbDelta(0) {} //////////////////////////////////////////////////////////// // Member data From 174448f5fab0258507228973b19e196df4327184 Mon Sep 17 00:00:00 2001 From: Lorenzooone Date: Fri, 26 Apr 2024 14:01:37 +0200 Subject: [PATCH 06/38] Fix joystickButton being used for Joystick(Dis)Connected event --- src/SFML/Window/WindowImpl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SFML/Window/WindowImpl.cpp b/src/SFML/Window/WindowImpl.cpp index d1e224dd3..9617c8fb0 100644 --- a/src/SFML/Window/WindowImpl.cpp +++ b/src/SFML/Window/WindowImpl.cpp @@ -200,7 +200,7 @@ void WindowImpl::processJoystickEvents() { Event event; event.type = connected ? Event::JoystickConnected : Event::JoystickDisconnected; - event.joystickButton.joystickId = i; + event.joystickConnect.joystickId = i; pushEvent(event); // Clear previous axes positions From 4daeac6459aaf2ed64d5e271c5917c8ed96b24f7 Mon Sep 17 00:00:00 2001 From: binary1248 Date: Sun, 14 Apr 2024 01:09:48 +0200 Subject: [PATCH 07/38] Fall back to stdio when performing file input on Android without an activity. --- include/SFML/System/FileInputStream.hpp | 5 +- src/SFML/Graphics/Image.cpp | 18 ++++---- src/SFML/System/FileInputStream.cpp | 61 ++++++++++++++++--------- 3 files changed, 51 insertions(+), 33 deletions(-) diff --git a/include/SFML/System/FileInputStream.hpp b/include/SFML/System/FileInputStream.hpp index 20899ba97..edbb83ed9 100644 --- a/include/SFML/System/FileInputStream.hpp +++ b/include/SFML/System/FileInputStream.hpp @@ -147,8 +147,8 @@ private: // Member data //////////////////////////////////////////////////////////// #ifdef SFML_SYSTEM_ANDROID - std::unique_ptr m_file; -#else + std::unique_ptr m_androidFile; +#endif //////////////////////////////////////////////////////////// /// \brief Deleter for stdio file stream that closes the file stream /// @@ -159,7 +159,6 @@ private: }; std::unique_ptr m_file; //!< stdio file stream -#endif }; } // namespace sf diff --git a/src/SFML/Graphics/Image.cpp b/src/SFML/Graphics/Image.cpp index ded88ea1a..62d9fe940 100644 --- a/src/SFML/Graphics/Image.cpp +++ b/src/SFML/Graphics/Image.cpp @@ -31,6 +31,7 @@ #include #include #ifdef SFML_SYSTEM_ANDROID +#include #include #endif @@ -157,7 +158,15 @@ void Image::create(const Vector2u& size, const std::uint8_t* pixels) //////////////////////////////////////////////////////////// bool Image::loadFromFile(const std::filesystem::path& filename) { -#ifndef SFML_SYSTEM_ANDROID +#ifdef SFML_SYSTEM_ANDROID + + if (priv::getActivityStatesPtr() != nullptr) + { + priv::ResourceStream stream(filename); + return loadFromStream(stream); + } + +#endif // Clear the array (just in case) m_pixels.clear(); @@ -186,13 +195,6 @@ bool Image::loadFromFile(const std::filesystem::path& filename) return false; } - -#else - - priv::ResourceStream stream(filename); - return loadFromStream(stream); - -#endif } diff --git a/src/SFML/System/FileInputStream.cpp b/src/SFML/System/FileInputStream.cpp index 38d8467b8..b523ff797 100644 --- a/src/SFML/System/FileInputStream.cpp +++ b/src/SFML/System/FileInputStream.cpp @@ -27,6 +27,7 @@ //////////////////////////////////////////////////////////// #include #ifdef SFML_SYSTEM_ANDROID +#include #include #endif #include @@ -36,12 +37,10 @@ namespace sf { //////////////////////////////////////////////////////////// -#ifndef SFML_SYSTEM_ANDROID void FileInputStream::FileCloser::operator()(std::FILE* file) { std::fclose(file); } -#endif //////////////////////////////////////////////////////////// @@ -64,69 +63,88 @@ FileInputStream& FileInputStream::operator=(FileInputStream&&) noexcept = defaul bool FileInputStream::open(const std::filesystem::path& filename) { #ifdef SFML_SYSTEM_ANDROID - m_file = std::make_unique(filename); - return m_file->tell() != -1; -#else + if (priv::getActivityStatesPtr() != nullptr) + { + m_androidFile = std::make_unique(filename); + return m_androidFile->tell() != -1; + } +#endif #ifdef SFML_SYSTEM_WINDOWS m_file.reset(_wfopen(filename.c_str(), L"rb")); #else m_file.reset(std::fopen(filename.c_str(), "rb")); #endif return m_file != nullptr; -#endif } //////////////////////////////////////////////////////////// std::int64_t FileInputStream::read(void* data, std::int64_t size) { +#ifdef SFML_SYSTEM_ANDROID + if (priv::getActivityStatesPtr() != nullptr) + { + if (!m_androidFile) + return -1; + return m_androidFile->read(data, size); + } +#endif if (!m_file) return -1; -#ifdef SFML_SYSTEM_ANDROID - return m_file->read(data, size); -#else return static_cast(std::fread(data, 1, static_cast(size), m_file.get())); -#endif } //////////////////////////////////////////////////////////// std::int64_t FileInputStream::seek(std::int64_t position) { +#ifdef SFML_SYSTEM_ANDROID + if (priv::getActivityStatesPtr() != nullptr) + { + if (!m_androidFile) + return -1; + return m_androidFile->seek(position); + } +#endif if (!m_file) return -1; -#ifdef SFML_SYSTEM_ANDROID - return m_file->seek(position); -#else if (std::fseek(m_file.get(), static_cast(position), SEEK_SET)) return -1; return tell(); -#endif } //////////////////////////////////////////////////////////// std::int64_t FileInputStream::tell() { +#ifdef SFML_SYSTEM_ANDROID + if (priv::getActivityStatesPtr() != nullptr) + { + if (!m_androidFile) + return -1; + return m_androidFile->tell(); + } +#endif if (!m_file) return -1; -#ifdef SFML_SYSTEM_ANDROID - return m_file->tell(); -#else return std::ftell(m_file.get()); -#endif } //////////////////////////////////////////////////////////// std::int64_t FileInputStream::getSize() { +#ifdef SFML_SYSTEM_ANDROID + if (priv::getActivityStatesPtr() != nullptr) + { + if (!m_androidFile) + return -1; + return m_androidFile->getSize(); + } +#endif if (!m_file) return -1; -#ifdef SFML_SYSTEM_ANDROID - return m_file->getSize(); -#else const std::int64_t position = tell(); std::fseek(m_file.get(), 0, SEEK_END); const std::int64_t size = tell(); @@ -135,7 +153,6 @@ std::int64_t FileInputStream::getSize() return -1; return size; -#endif } } // namespace sf From 2386653bd3df5639b9ae537e671d7c9557a774c1 Mon Sep 17 00:00:00 2001 From: binary1248 Date: Sun, 14 Apr 2024 01:23:36 +0200 Subject: [PATCH 08/38] Add GitHub actions support for running x86, x86_64 and arm64-v8a Android tests using an Android emulator. --- .github/workflows/ci.yml | 103 +++++++++++++++++++++++++++++++++------ cmake/Macros.cmake | 5 ++ test/CMakeLists.txt | 35 +++++++++++++ 3 files changed, 129 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 197444968..b7de50fcc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,7 @@ concurrency: env: DISPLAY: ":99" # Display number to use for the X server GALLIUM_DRIVER: llvmpipe # Use Mesa 3D software OpenGL renderer + ANDROID_NDK_VERSION: "26.1.10909125" # Android NDK version to use defaults: run: @@ -64,20 +65,47 @@ jobs: config: { name: Static with PCH (GCC), flags: -GNinja -DSFML_USE_MESA3D=TRUE -DCMAKE_CXX_COMPILER=g++ -DBUILD_SHARED_LIBS=FALSE -DSFML_ENABLE_PCH=1 } - platform: { name: macOS, os: macos-12 } config: { name: Frameworks, flags: -GNinja -DSFML_BUILD_FRAMEWORKS=TRUE -DBUILD_SHARED_LIBS=TRUE } - - platform: { name: Android, os: ubuntu-22.04 } - config: { name: x86 (API 21), flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=x86 -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=21 -DCMAKE_ANDROID_NDK=$ANDROID_SDK_ROOT/ndk/26.1.10909125 -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared, arch: x86, api: 21 } - type: { name: Release } - - platform: { name: Android, os: ubuntu-22.04 } - config: { name: x86_64 (API 24), flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=x86_64 -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=24 -DCMAKE_ANDROID_NDK=$ANDROID_SDK_ROOT/ndk/26.1.10909125 -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared, arch: x86_64, api: 24 } - type: { name: Release } - - platform: { name: Android, os: ubuntu-22.04 } - config: { name: armeabi-v7a (API 29), flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=armeabi-v7a -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=29 -DCMAKE_ANDROID_NDK=$ANDROID_SDK_ROOT/ndk/26.1.10909125 -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared, arch: armeabi-v7a, api: 29 } - type: { name: Debug, flags: -DCMAKE_BUILD_TYPE=Debug } - - platform: { name: Android, os: ubuntu-22.04 } - config: { name: arm64-v8a (API 33), flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=arm64-v8a -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=33 -DCMAKE_ANDROID_NDK=$ANDROID_SDK_ROOT/ndk/26.1.10909125 -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared, arch: arm64-v8a, api: 33 } - type: { name: Debug, flags: -DCMAKE_BUILD_TYPE=Debug } - platform: { name: macOS , os: macos-12 } config: { name: System Deps, flags: -GNinja -DBUILD_SHARED_LIBS=TRUE -DSFML_USE_SYSTEM_DEPS=TRUE } + - platform: { name: Android, os: ubuntu-latest } + config: + name: x86 (API 21) + flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=x86 -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=21 -DCMAKE_ANDROID_NDK=$ANDROID_NDK_ROOT -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared -DSFML_RUN_DISPLAY_TESTS=OFF + arch: x86 + api: 21 + libcxx: i686-linux-android/libc++_shared.so + emuarch: x86 + emuapi: 29 + type: { name: Release } + - platform: { name: Android, os: ubuntu-latest } + config: + name: x86_64 (API 24) + flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=x86_64 -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=24 -DCMAKE_ANDROID_NDK=$ANDROID_NDK_ROOT -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared -DSFML_RUN_DISPLAY_TESTS=OFF + arch: x86_64 + api: 24 + libcxx: x86_64-linux-android/libc++_shared.so + emuarch: x86_64 + emuapi: 34 + type: { name: Release } + - platform: { name: Android, os: ubuntu-latest } + config: + name: armeabi-v7a (API 29) + flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=armeabi-v7a -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=29 -DCMAKE_ANDROID_NDK=$ANDROID_NDK_ROOT -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared + arch: armeabi-v7a + api: 29 + # There are no emulators available for armeabi-v7a so we skip running the tests (we still build them) by not specifying emuapi + type: { name: Debug, flags: -DCMAKE_BUILD_TYPE=Debug } + - platform: { name: Android, os: ubuntu-latest } + config: + name: arm64-v8a (API 33) + flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=arm64-v8a -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=33 -DCMAKE_ANDROID_NDK=$ANDROID_NDK_ROOT -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared -DSFML_RUN_DISPLAY_TESTS=OFF + arch: arm64-v8a + api: 33 + libcxx: aarch64-linux-android/libc++_shared.so + emuarch: arm64-v8a + emuapi: 27 + emuflags: -qemu -machine virt + type: { name: Debug, flags: -DCMAKE_BUILD_TYPE=Debug } steps: @@ -107,11 +135,17 @@ jobs: echo "CLANG_VERSION=$CLANG_VERSION" >> $GITHUB_ENV sudo apt-get update && sudo apt-get install xorg-dev libxrandr-dev libxcursor-dev libudev-dev libflac-dev libvorbis-dev libgl1-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev xvfb fluxbox ccache gcovr ${{ matrix.platform.name == 'Linux Clang' && 'llvm-$CLANG_VERSION' || '' }} + # LIBCXX_SHARED_SO is the path used by CMake to copy the necessary runtime library to the AVD + # We find it by searching ANDROID_NDK_ROOT for file paths ending with matrix.config.libcxx - name: Install Android Components if: matrix.platform.name == 'Android' run: | echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "build-tools;33.0.2" echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "ndk;26.1.10909125" + ANDROID_NDK_ROOT=$(echo $ANDROID_SDK_ROOT/ndk/$ANDROID_NDK_VERSION) + echo "ANDROID_NDK_ROOT=$ANDROID_NDK_ROOT" >> $GITHUB_ENV + LIBCXX_SHARED_SO=$(find $ANDROID_NDK_ROOT -path \*/${{ matrix.config.libcxx }}) + echo "LIBCXX_SHARED_SO=$LIBCXX_SHARED_SO" >> $GITHUB_ENV - name: Install macOS Tools if: runner.os == 'macOS' @@ -197,11 +231,11 @@ jobs: # Make use of a test to print OpenGL vendor/renderer/version info to the console find build/bin -name test-sfml-window -or -name test-sfml-window.exe -exec sh -c "{} *sf::Context* --section=\"Version String\" --success | grep OpenGL" \; - - name: Test + - name: Test (Windows) if: runner.os == 'Windows' && !contains(matrix.platform.name, 'MinGW') run: cmake --build build --target runtests --config ${{ matrix.type.name == 'Debug' && 'Debug' || 'Release' }} - - name: Test + - name: Test (Linux/macOS/MinGW) if: (runner.os != 'Windows' || contains(matrix.platform.name, 'MinGW')) && !contains(matrix.platform.name, 'iOS') && !contains(matrix.platform.name, 'Android') run: | ctest --test-dir build --output-on-failure -C ${{ matrix.type.name == 'Debug' && 'Debug' || 'Release' }} --repeat until-pass:3 @@ -210,6 +244,47 @@ jobs: gcovr -r $GITHUB_WORKSPACE -x build/coverage.out -s -f 'src/SFML/.*' -f 'include/SFML/.*' ${{ matrix.platform.gcovr_options }} $GITHUB_WORKSPACE fi + - name: Enable KVM + if: contains(matrix.platform.name, 'Android') && matrix.config.emuapi + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Cache AVD + if: contains(matrix.platform.name, 'Android') && matrix.config.emuapi + uses: actions/cache@v4 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ matrix.config.emuarch }}-${{ matrix.config.emuapi }} + + - name: Create AVD and Generate Snapshot for Caching + if: contains(matrix.platform.name, 'Android') && matrix.config.emuapi && steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.config.emuapi }} + arch: ${{ matrix.config.emuarch }} + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none ${{ matrix.config.emuflags }} + disable-animations: false + script: echo "Generated AVD snapshot for caching." + + - name: Test (Android) + if: contains(matrix.platform.name, 'Android') && matrix.config.emuapi + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.config.emuapi }} + arch: ${{ matrix.config.emuarch }} + force-avd-creation: false + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none ${{ matrix.config.emuflags }} + disable-animations: true + script: | + cmake --build build --config ${{ matrix.type.name == 'Debug' && 'Debug' || 'Release' }} --target prepare-android-files + ctest --test-dir build --output-on-failure -C ${{ matrix.type.name == 'Debug' && 'Debug' || 'Release' }} --repeat until-pass:3 + - name: Upload Coverage Report to Coveralls if: matrix.type.name == 'Debug' && github.repository == 'SFML/SFML' && !contains(matrix.platform.name, 'iOS') && !contains(matrix.platform.name, 'Android') # Disable upload in forks uses: coverallsapp/github-action@v2 diff --git a/cmake/Macros.cmake b/cmake/Macros.cmake index 38e97098a..919b6d4f4 100644 --- a/cmake/Macros.cmake +++ b/cmake/Macros.cmake @@ -368,6 +368,11 @@ function(sfml_add_test target SOURCES DEPENDS) # Delay test registration when cross compiling to avoid running crosscompiled app on host OS if(CMAKE_CROSSCOMPILING) set(CMAKE_CATCH_DISCOVER_TESTS_DISCOVERY_MODE PRE_TEST) + + # When running tests on Android, use a custom shell script to invoke commands using adb shell + if(SFML_OS_ANDROID) + set_target_properties(${target} PROPERTIES CROSSCOMPILING_EMULATOR "${PROJECT_BINARY_DIR}/run-in-adb-shell.sh") + endif() endif() # Add the test diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6d60480e0..26fc8a2ff 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -146,6 +146,41 @@ set(AUDIO_SRC ) sfml_add_test(test-sfml-audio "${AUDIO_SRC}" SFML::Audio) +if(SFML_OS_ANDROID AND DEFINED ENV{LIBCXX_SHARED_SO}) + # Because we can only write to the tmp directory on the Android virtual device we will need to build our directory tree under it + set(TARGET_DIR "/data/local/tmp/$") + + # Generate script that copies necessary files over to the Android virtual device + file(GENERATE OUTPUT "${PROJECT_BINARY_DIR}/prepare-android-files.sh" CONTENT + "#!/bin/bash\n\ + adb shell \"mkdir -p ${TARGET_DIR}\"\n\ + adb push $ ${TARGET_DIR}\n\ + adb push $ ${TARGET_DIR}\n\ + adb push $ ${TARGET_DIR}\n\ + adb push $ ${TARGET_DIR}\n\ + adb push $ ${TARGET_DIR}\n\ + adb push $ ${TARGET_DIR}\n\ + adb push $ ${TARGET_DIR}\n\ + adb push $ ${TARGET_DIR}\n\ + adb push $ ${TARGET_DIR}\n\ + adb push $ ${TARGET_DIR}\n\ + adb push $ ${TARGET_DIR}\n\ + adb push $ ${TARGET_DIR}\n\ + adb push $ENV{LIBCXX_SHARED_SO} ${TARGET_DIR}\n\ + adb push ${CMAKE_CURRENT_LIST_DIR} ${TARGET_DIR}\n\ + adb shell \"chmod -R 775 ${TARGET_DIR} && ls -la ${TARGET_DIR}\"\n" + FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE) + + # Add the target to invoke the file copy script + add_custom_target(prepare-android-files COMMAND "${PROJECT_BINARY_DIR}/prepare-android-files.sh") + + # Generate proxy script that translates CTest commands into adb shell commands + file(GENERATE OUTPUT "${PROJECT_BINARY_DIR}/run-in-adb-shell.sh" CONTENT + "#!/bin/bash\n\ + adb shell \"cd ${TARGET_DIR}/test; LD_LIBRARY_PATH=${TARGET_DIR} /data/local/tmp/$1 \\\"$2\\\" \\\"$3\\\" \\\"$4\\\" \\\"$5\\\" \\\"$6\\\" \\\"$7\\\" \\\"$8\\\" \\\"$9\\\"\"\n" + FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE) +endif() + if(SFML_ENABLE_COVERAGE AND SFML_OS_WINDOWS AND NOT SFML_COMPILER_GCC) # Try to find and use OpenCppCoverage for coverage reporting when building with MSVC find_program(OpenCppCoverage_BINARY "OpenCppCoverage.exe") From 2d9cc5424215cb57daeeedbd2d66cc91ddabdc8e Mon Sep 17 00:00:00 2001 From: Chris Thrasher Date: Sun, 5 May 2024 13:05:21 -0600 Subject: [PATCH 09/38] Work around failing network test on Android x86_64 API 24 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b7de50fcc..448364795 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,7 +85,7 @@ jobs: api: 24 libcxx: x86_64-linux-android/libc++_shared.so emuarch: x86_64 - emuapi: 34 + # emuapi: 34 # Removing this causes the tests to not run. This works around an issue that causes the Network module tests to fail. type: { name: Release } - platform: { name: Android, os: ubuntu-latest } config: From ae269e1149f4232edf59c3b8eb08d388bc0d95e8 Mon Sep 17 00:00:00 2001 From: vittorioromeo Date: Mon, 6 May 2024 03:40:31 +0200 Subject: [PATCH 10/38] Make 'Event.inl' self-contained --- include/SFML/Window/Event.hpp | 4 ++-- include/SFML/Window/Event.inl | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/include/SFML/Window/Event.hpp b/include/SFML/Window/Event.hpp index e73515c73..627144915 100644 --- a/include/SFML/Window/Event.hpp +++ b/include/SFML/Window/Event.hpp @@ -329,10 +329,10 @@ private: static constexpr bool isEventType = isInParameterPack(decltype(m_data)()); }; -#include - } // namespace sf +#include + //////////////////////////////////////////////////////////// /// \class sf::Event diff --git a/include/SFML/Window/Event.inl b/include/SFML/Window/Event.inl index eded3de39..ef62cac1e 100644 --- a/include/SFML/Window/Event.inl +++ b/include/SFML/Window/Event.inl @@ -30,6 +30,14 @@ // to compile the code within the compiletime conditional when // an incorrect template parameter is provided. +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include // NOLINT(misc-header-include-cycle) + + +namespace sf +{ //////////////////////////////////////////////////////////// template Event::Event(const T& t) @@ -58,3 +66,5 @@ const T* Event::getIf() const if constexpr (isEventType) return std::get_if(&m_data); } + +} // namespace sf From 67fae8a3013eeb9dca346133b74cfa7fb0035411 Mon Sep 17 00:00:00 2001 From: Chris Thrasher Date: Mon, 6 May 2024 18:27:20 -0600 Subject: [PATCH 11/38] Work around stalled Android CI job --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 448364795..10697cc40 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,7 +75,7 @@ jobs: api: 21 libcxx: i686-linux-android/libc++_shared.so emuarch: x86 - emuapi: 29 + # emuapi: 29 # Removing this causes the tests to not run. This works around an issue that causes the test step to hang indefinitely. type: { name: Release } - platform: { name: Android, os: ubuntu-latest } config: From 415668cb82db71dc6975b97e76adc8540fdfa34a Mon Sep 17 00:00:00 2001 From: vittorioromeo Date: Tue, 7 May 2024 02:09:24 +0200 Subject: [PATCH 12/38] Update 'stb_image' to 2.29 --- extlibs/headers/stb_image/stb_image.h | 348 +++++++++++++------------- 1 file changed, 173 insertions(+), 175 deletions(-) diff --git a/extlibs/headers/stb_image/stb_image.h b/extlibs/headers/stb_image/stb_image.h index 5e807a0a6..a632d5435 100644 --- a/extlibs/headers/stb_image/stb_image.h +++ b/extlibs/headers/stb_image/stb_image.h @@ -1,4 +1,4 @@ -/* stb_image - v2.28 - public domain image loader - http://nothings.org/stb +/* stb_image - v2.29 - public domain image loader - http://nothings.org/stb no warranty implied; use at your own risk Do this: @@ -48,6 +48,7 @@ LICENSE RECENT REVISION HISTORY: + 2.29 (2023-05-xx) optimizations 2.28 (2023-01-29) many error fixes, security errors, just tons of stuff 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes 2.26 (2020-07-13) many minor fixes @@ -1072,8 +1073,8 @@ static int stbi__addints_valid(int a, int b) return a <= INT_MAX - b; } -// returns 1 if the product of two signed shorts is valid, 0 on overflow. -static int stbi__mul2shorts_valid(short a, short b) +// returns 1 if the product of two ints fits in a signed short, 0 on overflow. +static int stbi__mul2shorts_valid(int a, int b) { if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid @@ -3384,13 +3385,13 @@ static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) return 1; } -static int stbi__skip_jpeg_junk_at_end(stbi__jpeg *j) +static stbi_uc stbi__skip_jpeg_junk_at_end(stbi__jpeg *j) { // some JPEGs have junk at end, skip over it but if we find what looks // like a valid marker, resume there while (!stbi__at_eof(j->s)) { - int x = stbi__get8(j->s); - while (x == 255) { // might be a marker + stbi_uc x = stbi__get8(j->s); + while (x == 0xff) { // might be a marker if (stbi__at_eof(j->s)) return STBI__MARKER_none; x = stbi__get8(j->s); if (x != 0x00 && x != 0xff) { @@ -4176,6 +4177,7 @@ typedef struct { stbi_uc *zbuffer, *zbuffer_end; int num_bits; + int hit_zeof_once; stbi__uint32 code_buffer; char *zout; @@ -4242,9 +4244,20 @@ stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) int b,s; if (a->num_bits < 16) { if (stbi__zeof(a)) { - return -1; /* report error for unexpected end of data. */ + if (!a->hit_zeof_once) { + // This is the first time we hit eof, insert 16 extra padding btis + // to allow us to keep going; if we actually consume any of them + // though, that is invalid data. This is caught later. + a->hit_zeof_once = 1; + a->num_bits += 16; // add 16 implicit zero bits + } else { + // We already inserted our extra 16 padding bits and are again + // out, this stream is actually prematurely terminated. + return -1; + } + } else { + stbi__fill_bits(a); } - stbi__fill_bits(a); } b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; if (b) { @@ -4309,6 +4322,13 @@ static int stbi__parse_huffman_block(stbi__zbuf *a) int len,dist; if (z == 256) { a->zout = zout; + if (a->hit_zeof_once && a->num_bits < 16) { + // The first time we hit zeof, we inserted 16 extra zero bits into our bit + // buffer so the decoder can just do its speculative decoding. But if we + // actually consumed any of those bits (which is the case when num_bits < 16), + // the stream actually read past the end so it is malformed. + return stbi__err("unexpected end","Corrupt PNG"); + } return 1; } if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data @@ -4320,7 +4340,7 @@ static int stbi__parse_huffman_block(stbi__zbuf *a) dist = stbi__zdist_base[z]; if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); - if (zout + len > a->zout_end) { + if (len > a->zout_end - zout) { if (!stbi__zexpand(a, zout, len)) return 0; zout = a->zout; } @@ -4464,6 +4484,7 @@ static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) if (!stbi__parse_zlib_header(a)) return 0; a->num_bits = 0; a->code_buffer = 0; + a->hit_zeof_once = 0; do { final = stbi__zreceive(a,1); type = stbi__zreceive(a,2); @@ -4619,9 +4640,8 @@ enum { STBI__F_up=2, STBI__F_avg=3, STBI__F_paeth=4, - // synthetic filters used for first scanline to avoid needing a dummy row of 0s - STBI__F_avg_first, - STBI__F_paeth_first + // synthetic filter used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first }; static stbi_uc first_row_filter[5] = @@ -4630,29 +4650,56 @@ static stbi_uc first_row_filter[5] = STBI__F_sub, STBI__F_none, STBI__F_avg_first, - STBI__F_paeth_first + STBI__F_sub // Paeth with b=c=0 turns out to be equivalent to sub }; static int stbi__paeth(int a, int b, int c) { - int p = a + b - c; - int pa = abs(p-a); - int pb = abs(p-b); - int pc = abs(p-c); - if (pa <= pb && pa <= pc) return a; - if (pb <= pc) return b; - return c; + // This formulation looks very different from the reference in the PNG spec, but is + // actually equivalent and has favorable data dependencies and admits straightforward + // generation of branch-free code, which helps performance significantly. + int thresh = c*3 - (a + b); + int lo = a < b ? a : b; + int hi = a < b ? b : a; + int t0 = (hi <= thresh) ? lo : c; + int t1 = (thresh <= lo) ? hi : t0; + return t1; } static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; +// adds an extra all-255 alpha channel +// dest == src is legal +// img_n must be 1 or 3 +static void stbi__create_png_alpha_expand8(stbi_uc *dest, stbi_uc *src, stbi__uint32 x, int img_n) +{ + int i; + // must process data backwards since we allow dest==src + if (img_n == 1) { + for (i=x-1; i >= 0; --i) { + dest[i*2+1] = 255; + dest[i*2+0] = src[i]; + } + } else { + STBI_ASSERT(img_n == 3); + for (i=x-1; i >= 0; --i) { + dest[i*4+3] = 255; + dest[i*4+2] = src[i*3+2]; + dest[i*4+1] = src[i*3+1]; + dest[i*4+0] = src[i*3+0]; + } + } +} + // create the png data from post-deflated data static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) { - int bytes = (depth == 16? 2 : 1); + int bytes = (depth == 16 ? 2 : 1); stbi__context *s = a->s; stbi__uint32 i,j,stride = x*out_n*bytes; stbi__uint32 img_len, img_width_bytes; + stbi_uc *filter_buf; + int all_ok = 1; int k; int img_n = s->img_n; // copy it into a local for later @@ -4664,8 +4711,11 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into if (!a->out) return stbi__err("outofmem", "Out of memory"); + // note: error exits here don't need to clean up a->out individually, + // stbi__do_png always does on error. if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); img_width_bytes = (((img_n * x * depth) + 7) >> 3); + if (!stbi__mad2sizes_valid(img_width_bytes, y, img_width_bytes)) return stbi__err("too large", "Corrupt PNG"); img_len = (img_width_bytes + 1) * y; // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, @@ -4673,189 +4723,137 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r // so just check for raw_len < img_len always. if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + // Allocate two scan lines worth of filter workspace buffer. + filter_buf = (stbi_uc *) stbi__malloc_mad2(img_width_bytes, 2, 0); + if (!filter_buf) return stbi__err("outofmem", "Out of memory"); + + // Filtering for low-bit-depth images + if (depth < 8) { + filter_bytes = 1; + width = img_width_bytes; + } + for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *prior; + // cur/prior filter buffers alternate + stbi_uc *cur = filter_buf + (j & 1)*img_width_bytes; + stbi_uc *prior = filter_buf + (~j & 1)*img_width_bytes; + stbi_uc *dest = a->out + stride*j; + int nk = width * filter_bytes; int filter = *raw++; - if (filter > 4) - return stbi__err("invalid filter","Corrupt PNG"); - - if (depth < 8) { - if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG"); - cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place - filter_bytes = 1; - width = img_width_bytes; + // check filter type + if (filter > 4) { + all_ok = stbi__err("invalid filter","Corrupt PNG"); + break; } - prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above // if first row, use special filter that doesn't sample previous row if (j == 0) filter = first_row_filter[filter]; - // handle first byte explicitly - for (k=0; k < filter_bytes; ++k) { - switch (filter) { - case STBI__F_none : cur[k] = raw[k]; break; - case STBI__F_sub : cur[k] = raw[k]; break; - case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; - case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; - case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; - case STBI__F_avg_first : cur[k] = raw[k]; break; - case STBI__F_paeth_first: cur[k] = raw[k]; break; - } + // perform actual filtering + switch (filter) { + case STBI__F_none: + memcpy(cur, raw, nk); + break; + case STBI__F_sub: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); + break; + case STBI__F_up: + for (k = 0; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); + break; + case STBI__F_avg: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); + break; + case STBI__F_paeth: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); // prior[k] == stbi__paeth(0,prior[k],0) + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes], prior[k], prior[k-filter_bytes])); + break; + case STBI__F_avg_first: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); + break; } - if (depth == 8) { - if (img_n != out_n) - cur[img_n] = 255; // first pixel - raw += img_n; - cur += out_n; - prior += out_n; - } else if (depth == 16) { - if (img_n != out_n) { - cur[filter_bytes] = 255; // first pixel top byte - cur[filter_bytes+1] = 255; // first pixel bottom byte - } - raw += filter_bytes; - cur += output_bytes; - prior += output_bytes; - } else { - raw += 1; - cur += 1; - prior += 1; - } + raw += nk; - // this is a little gross, so that we don't switch per-pixel or per-component - if (depth < 8 || img_n == out_n) { - int nk = (width - 1)*filter_bytes; - #define STBI__CASE(f) \ - case f: \ - for (k=0; k < nk; ++k) - switch (filter) { - // "none" filter turns into a memcpy here; make that explicit. - case STBI__F_none: memcpy(cur, raw, nk); break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; - } - #undef STBI__CASE - raw += nk; - } else { - STBI_ASSERT(img_n+1 == out_n); - #define STBI__CASE(f) \ - case f: \ - for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ - for (k=0; k < filter_bytes; ++k) - switch (filter) { - STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; - } - #undef STBI__CASE - - // the loop above sets the high byte of the pixels' alpha, but for - // 16 bit png files we also need the low byte set. we'll do that here. - if (depth == 16) { - cur = a->out + stride*j; // start at the beginning of the row again - for (i=0; i < x; ++i,cur+=output_bytes) { - cur[filter_bytes+1] = 255; - } - } - } - } - - // we make a separate pass to expand bits to pixels; for performance, - // this could run two scanlines behind the above code, so it won't - // intefere with filtering but will still be in the cache. - if (depth < 8) { - for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; - // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit - // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + // expand decoded bits in cur to dest, also adding an extra alpha channel if desired + if (depth < 8) { stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + stbi_uc *in = cur; + stbi_uc *out = dest; + stbi_uc inb = 0; + stbi__uint32 nsmp = x*img_n; - // note that the final byte might overshoot and write more data than desired. - // we can allocate enough data that this never writes out of memory, but it - // could also overwrite the next scanline. can it overwrite non-empty data - // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. - // so we need to explicitly clamp the final ones - + // expand bits to bytes first if (depth == 4) { - for (k=x*img_n; k >= 2; k-=2, ++in) { - *cur++ = scale * ((*in >> 4) ); - *cur++ = scale * ((*in ) & 0x0f); + for (i=0; i < nsmp; ++i) { + if ((i & 1) == 0) inb = *in++; + *out++ = scale * (inb >> 4); + inb <<= 4; } - if (k > 0) *cur++ = scale * ((*in >> 4) ); } else if (depth == 2) { - for (k=x*img_n; k >= 4; k-=4, ++in) { - *cur++ = scale * ((*in >> 6) ); - *cur++ = scale * ((*in >> 4) & 0x03); - *cur++ = scale * ((*in >> 2) & 0x03); - *cur++ = scale * ((*in ) & 0x03); + for (i=0; i < nsmp; ++i) { + if ((i & 3) == 0) inb = *in++; + *out++ = scale * (inb >> 6); + inb <<= 2; } - if (k > 0) *cur++ = scale * ((*in >> 6) ); - if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); - if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); - } else if (depth == 1) { - for (k=x*img_n; k >= 8; k-=8, ++in) { - *cur++ = scale * ((*in >> 7) ); - *cur++ = scale * ((*in >> 6) & 0x01); - *cur++ = scale * ((*in >> 5) & 0x01); - *cur++ = scale * ((*in >> 4) & 0x01); - *cur++ = scale * ((*in >> 3) & 0x01); - *cur++ = scale * ((*in >> 2) & 0x01); - *cur++ = scale * ((*in >> 1) & 0x01); - *cur++ = scale * ((*in ) & 0x01); + } else { + STBI_ASSERT(depth == 1); + for (i=0; i < nsmp; ++i) { + if ((i & 7) == 0) inb = *in++; + *out++ = scale * (inb >> 7); + inb <<= 1; } - if (k > 0) *cur++ = scale * ((*in >> 7) ); - if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); - if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); - if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); - if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); - if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); - if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); } - if (img_n != out_n) { - int q; - // insert alpha = 255 - cur = a->out + stride*j; + + // insert alpha=255 values if desired + if (img_n != out_n) + stbi__create_png_alpha_expand8(dest, dest, x, img_n); + } else if (depth == 8) { + if (img_n == out_n) + memcpy(dest, cur, x*img_n); + else + stbi__create_png_alpha_expand8(dest, cur, x, img_n); + } else if (depth == 16) { + // convert the image data from big-endian to platform-native + stbi__uint16 *dest16 = (stbi__uint16*)dest; + stbi__uint32 nsmp = x*img_n; + + if (img_n == out_n) { + for (i = 0; i < nsmp; ++i, ++dest16, cur += 2) + *dest16 = (cur[0] << 8) | cur[1]; + } else { + STBI_ASSERT(img_n+1 == out_n); if (img_n == 1) { - for (q=x-1; q >= 0; --q) { - cur[q*2+1] = 255; - cur[q*2+0] = cur[q]; + for (i = 0; i < x; ++i, dest16 += 2, cur += 2) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = 0xffff; } } else { STBI_ASSERT(img_n == 3); - for (q=x-1; q >= 0; --q) { - cur[q*4+3] = 255; - cur[q*4+2] = cur[q*3+2]; - cur[q*4+1] = cur[q*3+1]; - cur[q*4+0] = cur[q*3+0]; + for (i = 0; i < x; ++i, dest16 += 4, cur += 6) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = (cur[2] << 8) | cur[3]; + dest16[2] = (cur[4] << 8) | cur[5]; + dest16[3] = 0xffff; } } } } - } else if (depth == 16) { - // force the image data from big-endian to platform-native. - // this is done in a separate pass due to the decoding relying - // on the data being untouched, but could probably be done - // per-line during decode if care is taken. - stbi_uc *cur = a->out; - stbi__uint16 *cur16 = (stbi__uint16*)cur; - - for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { - *cur16 = (cur[0] << 8) | cur[1]; - } } + STBI_FREE(filter_buf); + if (!all_ok) return 0; + return 1; } From 0777ce5eadd68cdc280ba902099595b3602295d6 Mon Sep 17 00:00:00 2001 From: Chris Thrasher Date: Sun, 5 May 2024 13:13:12 -0600 Subject: [PATCH 13/38] Update to Catch2 v3.6.0 --- test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 26fc8a2ff..36111b233 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -7,7 +7,7 @@ set(CATCH_CONFIG_FAST_COMPILE ON CACHE BOOL "") set(CATCH_CONFIG_NO_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT ON CACHE BOOL "") FetchContent_Declare(Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2.git - GIT_TAG v3.5.4 + GIT_TAG v3.6.0 GIT_SHALLOW ON) FetchContent_MakeAvailable(Catch2) include(Catch) From d95f99acf13cd887351fe627631eb4e8304becce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20D=C3=BCrrenberger?= Date: Tue, 7 May 2024 20:50:12 +0200 Subject: [PATCH 14/38] Fix configuration conflict in doxygen 1.10.0 --- doc/CMakeLists.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 7b3146dd8..3a0dbebee 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -1,4 +1,3 @@ - # find doxygen if(SFML_OS_MACOSX) # Add some path to search doxygen in more directories. @@ -27,7 +26,12 @@ if(SFML_OS_WINDOWS) PATHS "C:/Program Files/HTML Help Workshop" "C:/Program Files (x86)/HTML Help Workshop" DOC "HTML Help Compiler program") if(DOXYGEN_HHC_PROGRAM) - set(DOXYGEN_GENERATE_HTMLHELP YES) + if(DOXYGEN_VERSION VERSION_LESS "1.10.0") + set(DOXYGEN_GENERATE_HTMLHELP YES) + else() + message("Due to conflicts with the HTML output settings in Doxygen ${DOXYGEN_VERSION}, the HTML Help generation will be disabled") + set(DOXYGEN_GENERATE_HTMLHELP NO) + endif() else() set(DOXYGEN_GENERATE_HTMLHELP NO) endif() From 8d88e633e56ede8738dc29f2a7add12d51ff1a31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20D=C3=BCrrenberger?= Date: Tue, 7 May 2024 20:50:41 +0200 Subject: [PATCH 15/38] Update doxygen input file for doxygen 1.10.0 --- doc/doxyfile.in | 243 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 176 insertions(+), 67 deletions(-) diff --git a/doc/doxyfile.in b/doc/doxyfile.in index 67cf6b40b..2f111c9b3 100644 --- a/doc/doxyfile.in +++ b/doc/doxyfile.in @@ -1,4 +1,4 @@ -# Doxyfile 1.9.6 +# Doxyfile 1.10.0 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -63,6 +63,12 @@ PROJECT_BRIEF = PROJECT_LOGO = +# With the PROJECT_ICON tag one can specify an icon that is included in the tabs +# when the HTML document is shown. Doxygen will copy the logo to the output +# directory. + +PROJECT_ICON = + # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If @@ -380,6 +386,17 @@ MARKDOWN_SUPPORT = YES TOC_INCLUDE_HEADINGS = 0 +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or @@ -504,6 +521,14 @@ LOOKUP_CACHE_SIZE = 0 NUM_PROC_THREADS = 1 +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -889,7 +914,14 @@ WARN_IF_UNDOC_ENUM_VAL = NO # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but # at the end of the doxygen process doxygen will return with a non-zero status. -# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. # The default value is: NO. WARN_AS_ERROR = NO @@ -968,12 +1000,12 @@ INPUT_FILE_ENCODING = # Note the list of default checked file patterns might differ from the list of # default file extension mappings. # -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, -# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, -# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C -# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, -# *.vhdl, *.ucf, *.qsf and *.ice. +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, +# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, +# *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to +# be provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.hpp @@ -1020,9 +1052,6 @@ EXCLUDE_PATTERNS = .git \ # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # ANamespace::AClass, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = priv @@ -1136,7 +1165,8 @@ FORTRAN_COMMENT_AFTER = 72 SOURCE_BROWSER = YES # Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. +# multi-line macros, enums or list initialized variables directly into the +# documentation. # The default value is: NO. INLINE_SOURCES = NO @@ -1350,7 +1380,7 @@ HTML_STYLESHEET = "@DOXYGEN_INPUT_DIR@/doc/doxygen.css" # documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_STYLESHEET = +HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note @@ -1405,15 +1435,6 @@ HTML_COLORSTYLE_SAT = 100 HTML_COLORSTYLE_GAMMA = 80 -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = NO - # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that # are dynamically created via JavaScript. If disabled, the navigation index will @@ -1433,6 +1454,33 @@ HTML_DYNAMIC_MENUS = NO HTML_DYNAMIC_SECTIONS = NO +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = YES + +# If the HTML_COPY_CLIPBOARD tag is set to YES then doxygen will show an icon in +# the top right corner of code and text fragments that allows the user to copy +# its content to the clipboard. Note this only works if supported by the browser +# and the web page is served via a secure context (see: +# https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file: +# protocol. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COPY_CLIPBOARD = YES + +# Doxygen stores a couple of settings persistently in the browser (via e.g. +# cookies). By default these settings apply to all HTML pages generated by +# doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store +# the settings under a project specific key, such that the user preferences will +# be stored separately. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_PROJECT_COOKIE = + # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to @@ -1563,6 +1611,16 @@ BINARY_TOC = NO TOC_EXPAND = NO +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help @@ -2051,9 +2109,16 @@ PDF_HYPERLINKS = YES USE_PDFLATEX = YES -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode -# command to the generated LaTeX files. This will instruct LaTeX to keep running -# if errors occur, instead of asking the user for help. +# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error. +# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch +# mode nothing is printed on the terminal, errors are scrolled as if is +# hit at every error; missing files that TeX tries to input or request from +# keyboard input (\read on a not open input stream) cause the job to abort, +# NON_STOP In nonstop mode the diagnostic message will appear on the terminal, +# but there is no possibility of user interaction just like in batch mode, +# SCROLL In scroll mode, TeX will stop only for missing files to input or if +# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at +# each error, asking for user intervention. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -2074,14 +2139,6 @@ LATEX_HIDE_INDICES = NO LATEX_BIB_STYLE = plain -# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated -# page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_TIMESTAMP = NO - # The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) # path from which the emoji images will be read. If a relative path is entered, # it will be relative to the LATEX_OUTPUT directory. If left blank the @@ -2247,7 +2304,7 @@ DOCBOOK_OUTPUT = docbook #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an -# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures # the structure of the code including all documentation. Note that this feature # is still experimental and incomplete at the moment. # The default value is: NO. @@ -2258,6 +2315,28 @@ GENERATE_AUTOGEN_DEF = NO # Configuration options related to Sqlite3 output #--------------------------------------------------------------------------- +# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3 +# database with symbols found by doxygen stored in tables. +# The default value is: NO. + +GENERATE_SQLITE3 = NO + +# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be +# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put +# in front of it. +# The default directory is: sqlite3. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_OUTPUT = sqlite3 + +# The SQLITE3_RECREATE_DB tag is set to YES, the existing doxygen_sqlite3.db +# database file will be recreated with each doxygen run. If set to NO, doxygen +# will warn if a database file is already found and not modify it. +# The default value is: YES. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_RECREATE_DB = YES + #--------------------------------------------------------------------------- # Configuration options related to the Perl module output #--------------------------------------------------------------------------- @@ -2406,15 +2485,15 @@ TAGFILES = GENERATE_TAGFILE = "@DOXYGEN_OUTPUT_DIR@/SFML.tag" -# If the ALLEXTERNALS tag is set to YES, all external class will be listed in -# the class index. If set to NO, only the inherited external classes will be -# listed. +# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces +# will be listed in the class and namespace index. If set to NO, only the +# inherited external classes will be listed. # The default value is: NO. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will be +# in the topic index. If set to NO, only the current project's groups will be # listed. # The default value is: YES. @@ -2428,16 +2507,9 @@ EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES #--------------------------------------------------------------------------- -# Configuration options related to the dot tool +# Configuration options related to diagram generator tools #--------------------------------------------------------------------------- -# You can include diagrams made with dia in doxygen documentation. Doxygen will -# then run dia to produce the diagram and insert it in the documentation. The -# DIA_PATH tag allows you to specify the directory where the dia binary resides. -# If left empty dia is assumed to be found in the default search path. - -DIA_PATH = - # If set to YES the inheritance and collaboration graphs will hide inheritance # and usage relations if the target is undocumented or is not a class. # The default value is: YES. @@ -2446,7 +2518,7 @@ HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz (see: -# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent # Bell Labs. The other options in this section have no effect if this option is # set to NO # The default value is: NO. @@ -2499,13 +2571,19 @@ DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" DOT_FONTPATH = -# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a -# graph for each documented class showing the direct and indirect inheritance -# relations. In case HAVE_DOT is set as well dot will be used to draw the graph, -# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set -# to TEXT the direct and indirect inheritance relations will be shown as texts / -# links. -# Possible values are: NO, YES, TEXT and GRAPH. +# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will +# generate a graph for each documented class showing the direct and indirect +# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and +# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case +# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the +# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used. +# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance +# relations will be shown as texts / links. Explicit enabling an inheritance +# graph or choosing a different representation for an inheritance graph of a +# specific class, can be accomplished by means of the command \inheritancegraph. +# Disabling an inheritance graph can be accomplished by means of the command +# \hideinheritancegraph. +# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN. # The default value is: YES. CLASS_GRAPH = YES @@ -2513,15 +2591,21 @@ CLASS_GRAPH = YES # If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a # graph for each documented class showing the direct and indirect implementation # dependencies (inheritance, containment, and class references variables) of the -# class with other documented classes. +# class with other documented classes. Explicit enabling a collaboration graph, +# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the +# command \collaborationgraph. Disabling a collaboration graph can be +# accomplished by means of the command \hidecollaborationgraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for -# groups, showing the direct groups dependencies. See also the chapter Grouping -# in the manual. +# groups, showing the direct groups dependencies. Explicit enabling a group +# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means +# of the command \groupgraph. Disabling a directory graph can be accomplished by +# means of the command \hidegroupgraph. See also the chapter Grouping in the +# manual. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2563,8 +2647,8 @@ DOT_UML_DETAILS = NO # The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters # to display on a single line. If the actual line length exceeds this threshold -# significantly it will wrapped across multiple lines. Some heuristics are apply -# to avoid ugly line breaks. +# significantly it will be wrapped across multiple lines. Some heuristics are +# applied to avoid ugly line breaks. # Minimum value: 0, maximum value: 1000, default value: 17. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2581,7 +2665,9 @@ TEMPLATE_RELATIONS = NO # If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to # YES then doxygen will generate a graph for each documented file showing the # direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO, +# can be accomplished by means of the command \includegraph. Disabling an +# include graph can be accomplished by means of the command \hideincludegraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2590,7 +2676,10 @@ INCLUDE_GRAPH = YES # If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are # set to YES then doxygen will generate a graph for each documented file showing # the direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set +# to NO, can be accomplished by means of the command \includedbygraph. Disabling +# an included by graph can be accomplished by means of the command +# \hideincludedbygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2630,7 +2719,10 @@ GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the # dependencies a directory has on other directories in a graphical way. The # dependency relations are determined by the #include relations between the -# files in the directories. +# files in the directories. Explicit enabling a directory graph, when +# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command +# \directorygraph. Disabling a directory graph can be accomplished by means of +# the command \hidedirectorygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2646,7 +2738,7 @@ DIR_GRAPH_MAX_DEPTH = 1 # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. For an explanation of the image formats see the section # output formats in the documentation of the dot tool (Graphviz (see: -# http://www.graphviz.org/)). +# https://www.graphviz.org/)). # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order # to make the SVG files visible in IE 9+ (other browsers do not have this # requirement). @@ -2683,11 +2775,12 @@ DOT_PATH = DOTFILE_DIRS = -# The MSCFILE_DIRS tag can be used to specify one or more directories that -# contain msc files that are included in the documentation (see the \mscfile -# command). +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. -MSCFILE_DIRS = +DIA_PATH = # The DIAFILE_DIRS tag can be used to specify one or more directories that # contain dia files that are included in the documentation (see the \diafile @@ -2764,3 +2857,19 @@ GENERATE_LEGEND = YES # The default value is: YES. DOT_CLEANUP = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will +# use a built-in version of mscgen tool to produce the charts. Alternatively, +# the MSCGEN_TOOL tag can also specify the name an external tool. For instance, +# specifying prog as the value, doxygen will call the tool as prog -T +# -o . The external tool should support +# output file formats "png", "eps", "svg", and "ismap". + +MSCGEN_TOOL = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = From 891e5673467bc891f471eae8f756e0c4c2b68e48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20D=C3=BCrrenberger?= Date: Tue, 7 May 2024 21:04:34 +0200 Subject: [PATCH 16/38] Fix missing namespace and file references --- doc/doxyfile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/doxyfile.in b/doc/doxyfile.in index 2f111c9b3..3cb4116e7 100644 --- a/doc/doxyfile.in +++ b/doc/doxyfile.in @@ -541,7 +541,7 @@ TIMESTAMP = NO # normally produced when WARNINGS is set to YES. # The default value is: NO. -EXTRACT_ALL = NO +EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will # be included in the documentation. From 66c5fae7c1f3743ef07b239b40b4e0a890ace82a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20D=C3=BCrrenberger?= Date: Tue, 7 May 2024 23:53:10 +0200 Subject: [PATCH 17/38] Fix XHTML Doxygen docs inconsistencies --- doc/footer.html | 2 +- doc/header.html.in | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/footer.html b/doc/footer.html index c9cd93754..c42870da2 100644 --- a/doc/footer.html +++ b/doc/footer.html @@ -1,7 +1,7 @@
+
From 7f27cad9ae139270781c13b88976ff3c1c855817 Mon Sep 17 00:00:00 2001 From: kimci86 Date: Wed, 8 May 2024 12:37:16 +0200 Subject: [PATCH 18/38] Fix search.js "Uncaught ReferenceError: Cookie is not defined" --- doc/header.html.in | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/header.html.in b/doc/header.html.in index 26c65b11c..9790b2d00 100644 --- a/doc/header.html.in +++ b/doc/header.html.in @@ -6,6 +6,7 @@ + From 0d6a5f7fbe4b57c6b724cdca96f4d742628d2d8c Mon Sep 17 00:00:00 2001 From: kimci86 Date: Wed, 8 May 2024 12:46:57 +0200 Subject: [PATCH 19/38] Make the new doxygen clipboard feature work and look good CSS rules copied from doxygen default stylesheet generated with: doxygen -w html new_header.html new_footer.html new_stylesheet.css --- doc/doxygen.css | 41 +++++++++++++++++++++++++++++++++++++++++ doc/header.html.in | 1 + 2 files changed, 42 insertions(+) diff --git a/doc/doxygen.css b/doc/doxygen.css index 632fbbaab..81655d3db 100644 --- a/doc/doxygen.css +++ b/doc/doxygen.css @@ -147,12 +147,53 @@ div.fragment { font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 10pt; + position: relative; padding: 0.5em 1em; background-color: #f5f5f5; border: 1px solid #bbb; border-radius(5px); } +.clipboard { + width: 24px; + height: 24px; + right: 5px; + top: 5px; + opacity: 0; + position: absolute; + display: inline; + overflow: auto; + fill: black; + justify-content: center; + align-items: center; + cursor: pointer; +} + +.clipboard.success { + border: 1px solid black; + border-radius: 4px; +} + +.fragment:hover .clipboard, .clipboard.success { + opacity: .28; +} + +.clipboard:hover, .clipboard.success { + opacity: 1 !important; +} + +.clipboard:active:not([class~=success]) svg { + transform: scale(.91); +} + +.clipboard.success svg { + fill: #2EC82E; +} + +.clipboard.success { + border-color: #2EC82E; +} + div.line { min-height: 13px; text-wrap: unrestricted; diff --git a/doc/header.html.in b/doc/header.html.in index 9790b2d00..8510ad541 100644 --- a/doc/header.html.in +++ b/doc/header.html.in @@ -6,6 +6,7 @@ + From b7e1a259f0550d8d1781af1d3d31ddfecf1c92bf Mon Sep 17 00:00:00 2001 From: kimci86 Date: Wed, 8 May 2024 20:01:07 +0200 Subject: [PATCH 20/38] Update icons urls and color --- doc/doxyfile.in | 2 +- doc/doxygen.css | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/doxyfile.in b/doc/doxyfile.in index 3cb4116e7..a50712af4 100644 --- a/doc/doxyfile.in +++ b/doc/doxyfile.in @@ -1414,7 +1414,7 @@ HTML_COLORSTYLE = LIGHT # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_HUE = 85 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use gray-scales only. A diff --git a/doc/doxygen.css b/doc/doxygen.css index 81655d3db..a7a144818 100644 --- a/doc/doxygen.css +++ b/doc/doxygen.css @@ -1295,7 +1295,7 @@ div.contents ul li { width: 24px; height: 18px; margin-bottom: 4px; - background-image:url('folderopen.png'); + background-image:url('folderopen.svg'); background-position: 0px -4px; background-repeat: repeat-y; vertical-align:top; @@ -1306,7 +1306,7 @@ div.contents ul li { width: 24px; height: 18px; margin-bottom: 4px; - background-image:url('folderclosed.png'); + background-image:url('folderclosed.svg'); background-position: 0px -4px; background-repeat: repeat-y; vertical-align:top; @@ -1317,7 +1317,7 @@ div.contents ul li { width: 24px; height: 18px; margin-bottom: 4px; - background-image:url('doc.png'); + background-image:url('doc.svg'); background-position: 0px -4px; background-repeat: repeat-y; vertical-align:top; From b79d5553fd9dd174deab7d6f4f05fe7e16178eab Mon Sep 17 00:00:00 2001 From: alexv Date: Mon, 6 May 2024 17:26:38 +0300 Subject: [PATCH 21/38] Move the CPack configuration after SFML_CONFIGURE_EXTRAS --- CMakeLists.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 090d77d0f..98491a90b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -465,6 +465,13 @@ endif() sfml_export_targets() +# configure extras by default when building SFML directly, otherwise hide them +sfml_set_option(SFML_CONFIGURE_EXTRAS ${PROJECT_IS_TOP_LEVEL} BOOL "TRUE to configure extras, FALSE to ignore them") + +if(NOT SFML_CONFIGURE_EXTRAS) + return() +endif() + set(CPACK_PACKAGE_NAME_SUMMARY "Simple and Fast Multimedia Library") set(CPACK_PACKAGE_VENDOR "SFML Team") set(CPACK_PACKAGE_FILE_NAME "SFML-${PROJECT_VERSION}-${CMAKE_CXX_COMPILER_ID}-${CMAKE_CXX_COMPILER_VERSION}-${CMAKE_BUILD_TYPE}") @@ -485,13 +492,6 @@ set(CPACK_NSIS_INSTALLER_MUI_ICON_CODE "!define MUI_WELCOMEFINISHPAGE_BITMAP \\\ include(CPack) -# configure extras by default when building SFML directly, otherwise hide them -sfml_set_option(SFML_CONFIGURE_EXTRAS ${PROJECT_IS_TOP_LEVEL} BOOL "TRUE to configure extras, FALSE to ignore them") - -if(NOT SFML_CONFIGURE_EXTRAS) - return() -endif() - # add an option for building the API documentation sfml_set_option(SFML_BUILD_DOC FALSE BOOL "TRUE to generate the API documentation, FALSE to ignore it") if(SFML_BUILD_DOC) From 1a4003fcc0ce9f81d506ceee64d567a008d599d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20D=C3=BCrrenberger?= Date: Sun, 12 May 2024 22:50:22 +0200 Subject: [PATCH 22/38] Add clarification about the RenderTexture state after creation --- include/SFML/Graphics/RenderTexture.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/SFML/Graphics/RenderTexture.hpp b/include/SFML/Graphics/RenderTexture.hpp index 8e75397af..959293f23 100644 --- a/include/SFML/Graphics/RenderTexture.hpp +++ b/include/SFML/Graphics/RenderTexture.hpp @@ -72,11 +72,15 @@ public: /// Before calling this function, the render-texture is in /// an invalid state, thus it is mandatory to call it before /// doing anything with the render-texture. + /// /// The last parameter, \a depthBuffer, is useful if you want /// to use the render-texture for 3D OpenGL rendering that requires /// a depth buffer. Otherwise it is unnecessary, and you should /// leave this parameter to false (which is its default value). /// + /// After creation, the contents of the render-texture are undefined. + /// Call `RenderTexture::clear` first to ensure a single color fill. + /// /// \param width Width of the render-texture /// \param height Height of the render-texture /// \param depthBuffer Do you want this render-texture to have a depth buffer? @@ -94,11 +98,15 @@ public: /// Before calling this function, the render-texture is in /// an invalid state, thus it is mandatory to call it before /// doing anything with the render-texture. + /// /// The last parameter, \a settings, is useful if you want to enable /// multi-sampling or use the render-texture for OpenGL rendering that /// requires a depth or stencil buffer. Otherwise it is unnecessary, and /// you should leave this parameter at its default value. /// + /// After creation, the contents of the render-texture are undefined. + /// Call `RenderTexture::clear` first to ensure a single color fill. + /// /// \param width Width of the render-texture /// \param height Height of the render-texture /// \param settings Additional settings for the underlying OpenGL texture and context From 31503844cd08558cf9acc8d24a0e9d4a9caed190 Mon Sep 17 00:00:00 2001 From: Chris Thrasher Date: Mon, 22 Apr 2024 10:56:20 -0600 Subject: [PATCH 23/38] Remove unnecessary `sf::` namespace usage --- src/SFML/Audio/AudioDevice.cpp | 4 ++-- src/SFML/Audio/AudioDevice.hpp | 10 +++++----- src/SFML/Audio/AudioResource.cpp | 4 ++-- src/SFML/Audio/MiniaudioUtils.cpp | 6 +++--- src/SFML/Audio/MiniaudioUtils.hpp | 8 ++++---- src/SFML/Audio/SoundFileReaderFlac.hpp | 2 +- src/SFML/Audio/SoundFileReaderMp3.cpp | 8 ++++---- src/SFML/Audio/SoundFileReaderWav.hpp | 2 +- src/SFML/Audio/SoundSource.cpp | 10 +++++----- src/SFML/Graphics/CircleShape.cpp | 2 +- src/SFML/Graphics/Font.cpp | 2 +- src/SFML/Graphics/GLExtensions.cpp | 4 ++-- src/SFML/Graphics/Shader.cpp | 4 ++-- src/SFML/Graphics/Text.cpp | 2 +- src/SFML/Graphics/Texture.cpp | 2 +- src/SFML/System/String.cpp | 4 ++-- src/SFML/Window/Android/ClipboardImpl.cpp | 4 ++-- src/SFML/Window/Unix/JoystickImpl.hpp | 8 ++++---- src/SFML/Window/WindowBase.cpp | 4 ++-- src/SFML/Window/WindowImpl.cpp | 4 ++-- src/SFML/Window/macOS/JoystickImpl.hpp | 4 ++-- src/SFML/Window/macOS/SFContext.mm | 14 +++++++------- 22 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/SFML/Audio/AudioDevice.cpp b/src/SFML/Audio/AudioDevice.cpp index edab9ad62..a7693976c 100644 --- a/src/SFML/Audio/AudioDevice.cpp +++ b/src/SFML/Audio/AudioDevice.cpp @@ -304,8 +304,8 @@ void AudioDevice::setCone(const Listener::Cone& cone) ma_engine_listener_set_cone(&*instance->m_engine, 0, - std::clamp(cone.innerAngle.asRadians(), 0.f, sf::degrees(360).asRadians()), - std::clamp(cone.outerAngle.asRadians(), 0.f, sf::degrees(360).asRadians()), + std::clamp(cone.innerAngle.asRadians(), 0.f, degrees(360).asRadians()), + std::clamp(cone.outerAngle.asRadians(), 0.f, degrees(360).asRadians()), cone.outerGain); } diff --git a/src/SFML/Audio/AudioDevice.hpp b/src/SFML/Audio/AudioDevice.hpp index b092aadec..04981eaed 100644 --- a/src/SFML/Audio/AudioDevice.hpp +++ b/src/SFML/Audio/AudioDevice.hpp @@ -228,11 +228,11 @@ private: struct ListenerProperties { float volume{100.f}; - sf::Vector3f position{0, 0, 0}; - sf::Vector3f direction{0, 0, -1}; - sf::Vector3f velocity{0, 0, 0}; - Listener::Cone cone{sf::degrees(360), sf::degrees(360), 1}; - sf::Vector3f upVector{0, 1, 0}; + Vector3f position{0, 0, 0}; + Vector3f direction{0, 0, -1}; + Vector3f velocity{0, 0, 0}; + Listener::Cone cone{degrees(360), degrees(360), 1}; + Vector3f upVector{0, 1, 0}; }; //////////////////////////////////////////////////////////// diff --git a/src/SFML/Audio/AudioResource.cpp b/src/SFML/Audio/AudioResource.cpp index 8df7a3f3d..f691c7ad3 100644 --- a/src/SFML/Audio/AudioResource.cpp +++ b/src/SFML/Audio/AudioResource.cpp @@ -41,8 +41,8 @@ m_device( { // Ensure we only ever create a single instance of an // AudioDevice that is shared between all AudioResources - static std::mutex mutex; - static std::weak_ptr weakAudioDevice; + static std::mutex mutex; + static std::weak_ptr weakAudioDevice; const std::lock_guard lock(mutex); diff --git a/src/SFML/Audio/MiniaudioUtils.cpp b/src/SFML/Audio/MiniaudioUtils.cpp index 85c70cba2..d1c6383f7 100644 --- a/src/SFML/Audio/MiniaudioUtils.cpp +++ b/src/SFML/Audio/MiniaudioUtils.cpp @@ -63,8 +63,8 @@ struct SavedSettings float minGain{0.f}; float maxGain{1.f}; float rollOff{1.f}; - float innerAngle{sf::degrees(360).asRadians()}; - float outerAngle{sf::degrees(360).asRadians()}; + float innerAngle{degrees(360).asRadians()}; + float outerAngle{degrees(360).asRadians()}; float outerGain{0.f}; }; @@ -135,7 +135,7 @@ void initializeDataSource(ma_data_source_base& dataSourceBase, const ma_data_sou //////////////////////////////////////////////////////////// -ma_channel MiniaudioUtils::soundChannelToMiniaudioChannel(sf::SoundChannel soundChannel) +ma_channel MiniaudioUtils::soundChannelToMiniaudioChannel(SoundChannel soundChannel) { switch (soundChannel) { diff --git a/src/SFML/Audio/MiniaudioUtils.hpp b/src/SFML/Audio/MiniaudioUtils.hpp index f5717d05b..cc61a3c73 100644 --- a/src/SFML/Audio/MiniaudioUtils.hpp +++ b/src/SFML/Audio/MiniaudioUtils.hpp @@ -41,11 +41,10 @@ namespace sf { class Time; -} -namespace sf::priv::MiniaudioUtils +namespace priv::MiniaudioUtils { -[[nodiscard]] ma_channel soundChannelToMiniaudioChannel(sf::SoundChannel soundChannel); +[[nodiscard]] ma_channel soundChannelToMiniaudioChannel(SoundChannel soundChannel); [[nodiscard]] Time getPlayingOffset(ma_sound& sound); [[nodiscard]] ma_uint64 getFrameIndex(ma_sound& sound, Time timeOffset); @@ -54,4 +53,5 @@ void initializeSound(const ma_data_source_vtable& vtable, ma_data_source_base& dataSourceBase, ma_sound& sound, const std::function& initializeFn); -} // namespace sf::priv::MiniaudioUtils +} // namespace priv::MiniaudioUtils +} // namespace sf diff --git a/src/SFML/Audio/SoundFileReaderFlac.hpp b/src/SFML/Audio/SoundFileReaderFlac.hpp index ba97637c5..0ecb52068 100644 --- a/src/SFML/Audio/SoundFileReaderFlac.hpp +++ b/src/SFML/Audio/SoundFileReaderFlac.hpp @@ -69,7 +69,7 @@ public: /// \return Properties of the loaded sound if the file was successfully opened /// //////////////////////////////////////////////////////////// - [[nodiscard]] std::optional open(sf::InputStream& stream) override; + [[nodiscard]] std::optional open(InputStream& stream) override; //////////////////////////////////////////////////////////// /// \brief Change the current read position to the given sample offset diff --git a/src/SFML/Audio/SoundFileReaderMp3.cpp b/src/SFML/Audio/SoundFileReaderMp3.cpp index 32f0dbce0..d0ca6574c 100644 --- a/src/SFML/Audio/SoundFileReaderMp3.cpp +++ b/src/SFML/Audio/SoundFileReaderMp3.cpp @@ -142,16 +142,16 @@ std::optional SoundFileReaderMp3::open(InputStream& strea switch (info.channelCount) { case 0: - sf::err() << "No channels in MP3 file" << std::endl; + err() << "No channels in MP3 file" << std::endl; break; case 1: - info.channelMap = {sf::SoundChannel::Mono}; + info.channelMap = {SoundChannel::Mono}; break; case 2: - info.channelMap = {sf::SoundChannel::SideLeft, sf::SoundChannel::SideRight}; + info.channelMap = {SoundChannel::SideLeft, SoundChannel::SideRight}; break; default: - sf::err() << "MP3 files with more than 2 channels not supported" << std::endl; + err() << "MP3 files with more than 2 channels not supported" << std::endl; assert(false); break; } diff --git a/src/SFML/Audio/SoundFileReaderWav.hpp b/src/SFML/Audio/SoundFileReaderWav.hpp index 479f350b0..fc47c061a 100644 --- a/src/SFML/Audio/SoundFileReaderWav.hpp +++ b/src/SFML/Audio/SoundFileReaderWav.hpp @@ -66,7 +66,7 @@ public: /// \return Properties of the loaded sound if the file was successfully opened /// //////////////////////////////////////////////////////////// - [[nodiscard]] std::optional open(sf::InputStream& stream) override; + [[nodiscard]] std::optional open(InputStream& stream) override; //////////////////////////////////////////////////////////// /// \brief Change the current read position to the given sample offset diff --git a/src/SFML/Audio/SoundSource.cpp b/src/SFML/Audio/SoundSource.cpp index 7b7d67477..8f1b3dbdd 100644 --- a/src/SFML/Audio/SoundSource.cpp +++ b/src/SFML/Audio/SoundSource.cpp @@ -88,8 +88,8 @@ void SoundSource::setCone(const Cone& cone) { if (auto* sound = static_cast(getSound())) ma_sound_set_cone(sound, - std::clamp(cone.innerAngle, sf::degrees(0), sf::degrees(360)).asRadians(), - std::clamp(cone.outerAngle, sf::degrees(0), sf::degrees(360)).asRadians(), + std::clamp(cone.innerAngle, degrees(0), degrees(360)).asRadians(), + std::clamp(cone.outerAngle, degrees(0), degrees(360)).asRadians(), cone.outerGain); } @@ -241,12 +241,12 @@ SoundSource::Cone SoundSource::getCone() const float outerAngle = 0.f; Cone cone; ma_sound_get_cone(sound, &innerAngle, &outerAngle, &cone.outerGain); - cone.innerAngle = sf::radians(innerAngle); - cone.outerAngle = sf::radians(outerAngle); + cone.innerAngle = radians(innerAngle); + cone.outerAngle = radians(outerAngle); return cone; } - return Cone{sf::radians(0), sf::radians(0), 0.f}; + return Cone{radians(0), radians(0), 0.f}; } diff --git a/src/SFML/Graphics/CircleShape.cpp b/src/SFML/Graphics/CircleShape.cpp index f2b5901f3..4df41e516 100644 --- a/src/SFML/Graphics/CircleShape.cpp +++ b/src/SFML/Graphics/CircleShape.cpp @@ -71,7 +71,7 @@ std::size_t CircleShape::getPointCount() const //////////////////////////////////////////////////////////// Vector2f CircleShape::getPoint(std::size_t index) const { - const Angle angle = static_cast(index) / static_cast(m_pointCount) * sf::degrees(360) - sf::degrees(90); + const Angle angle = static_cast(index) / static_cast(m_pointCount) * degrees(360) - degrees(90); return Vector2f(m_radius, m_radius) + Vector2f(m_radius, angle); } diff --git a/src/SFML/Graphics/Font.cpp b/src/SFML/Graphics/Font.cpp index b009d65e4..97a566330 100644 --- a/src/SFML/Graphics/Font.cpp +++ b/src/SFML/Graphics/Font.cpp @@ -784,7 +784,7 @@ bool Font::setCurrentSize(unsigned int characterSize) const Font::Page::Page(bool smooth) { // Make sure that the texture is initialized by default - sf::Image image; + Image image; image.create({128, 128}, Color::Transparent); // Reserve a 2x2 white square for texturing underlines diff --git a/src/SFML/Graphics/GLExtensions.cpp b/src/SFML/Graphics/GLExtensions.cpp index ce8bb4eb4..310644308 100644 --- a/src/SFML/Graphics/GLExtensions.cpp +++ b/src/SFML/Graphics/GLExtensions.cpp @@ -61,9 +61,9 @@ void ensureExtensionsInit() initialized = true; #ifdef SFML_OPENGL_ES - gladLoadGLES1(sf::Context::getFunction); + gladLoadGLES1(Context::getFunction); #else - gladLoadGL(sf::Context::getFunction); + gladLoadGL(Context::getFunction); #endif // Retrieve the context version number diff --git a/src/SFML/Graphics/Shader.cpp b/src/SFML/Graphics/Shader.cpp index f1a97e2db..f7d669ae7 100644 --- a/src/SFML/Graphics/Shader.cpp +++ b/src/SFML/Graphics/Shader.cpp @@ -740,7 +740,7 @@ bool Shader::isAvailable() const TransientContextLock contextLock; // Make sure that extensions are initialized - sf::priv::ensureExtensionsInit(); + priv::ensureExtensionsInit(); return GLEXT_multitexture && GLEXT_shading_language_100 && GLEXT_shader_objects && GLEXT_vertex_shader && GLEXT_fragment_shader; @@ -758,7 +758,7 @@ bool Shader::isGeometryAvailable() const TransientContextLock contextLock; // Make sure that extensions are initialized - sf::priv::ensureExtensionsInit(); + priv::ensureExtensionsInit(); return isAvailable() && (GLEXT_geometry_shader4 || GLEXT_GL_VERSION_3_2); }(); diff --git a/src/SFML/Graphics/Text.cpp b/src/SFML/Graphics/Text.cpp index 33ca33c88..518e535d4 100644 --- a/src/SFML/Graphics/Text.cpp +++ b/src/SFML/Graphics/Text.cpp @@ -381,7 +381,7 @@ void Text::ensureGeometryUpdate() const const bool isBold = m_style & Bold; const bool isUnderlined = m_style & Underlined; const bool isStrikeThrough = m_style & StrikeThrough; - const float italicShear = (m_style & Italic) ? sf::degrees(12).asRadians() : 0.f; + const float italicShear = (m_style & Italic) ? degrees(12).asRadians() : 0.f; const float underlineOffset = m_font->getUnderlinePosition(m_characterSize); const float underlineThickness = m_font->getUnderlineThickness(m_characterSize); diff --git a/src/SFML/Graphics/Texture.cpp b/src/SFML/Graphics/Texture.cpp index a66699b9b..113557515 100644 --- a/src/SFML/Graphics/Texture.cpp +++ b/src/SFML/Graphics/Texture.cpp @@ -921,7 +921,7 @@ unsigned int Texture::getMaximumSize() GLint value = 0; // Make sure that extensions are initialized - sf::priv::ensureExtensionsInit(); + priv::ensureExtensionsInit(); glCheck(glGetIntegerv(GL_MAX_TEXTURE_SIZE, &value)); diff --git a/src/SFML/System/String.cpp b/src/SFML/System/String.cpp index 99b381fce..36c9fb250 100644 --- a/src/SFML/System/String.cpp +++ b/src/SFML/System/String.cpp @@ -264,10 +264,10 @@ std::wstring String::toWideString() const //////////////////////////////////////////////////////////// -sf::U8String String::toUtf8() const +U8String String::toUtf8() const { // Prepare the output string - sf::U8String output; + U8String output; output.reserve(m_string.length()); // Convert diff --git a/src/SFML/Window/Android/ClipboardImpl.cpp b/src/SFML/Window/Android/ClipboardImpl.cpp index a6a55964f..8acff3268 100644 --- a/src/SFML/Window/Android/ClipboardImpl.cpp +++ b/src/SFML/Window/Android/ClipboardImpl.cpp @@ -38,7 +38,7 @@ namespace sf::priv //////////////////////////////////////////////////////////// String ClipboardImpl::getString() { - sf::err() << "Clipboard API not implemented for Android.\n"; + err() << "Clipboard API not implemented for Android.\n"; return {}; } @@ -46,7 +46,7 @@ String ClipboardImpl::getString() //////////////////////////////////////////////////////////// void ClipboardImpl::setString(const String& /* text */) { - sf::err() << "Clipboard API not implemented for Android.\n"; + err() << "Clipboard API not implemented for Android.\n"; } } // namespace sf::priv diff --git a/src/SFML/Window/Unix/JoystickImpl.hpp b/src/SFML/Window/Unix/JoystickImpl.hpp index 1d16750de..b65b9a0f2 100644 --- a/src/SFML/Window/Unix/JoystickImpl.hpp +++ b/src/SFML/Window/Unix/JoystickImpl.hpp @@ -105,10 +105,10 @@ private: //////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// - int m_file{-1}; ///< File descriptor of the joystick - char m_mapping[ABS_MAX + 1]{0}; ///< Axes mapping (index to axis id) - JoystickState m_state; ///< Current state of the joystick - sf::Joystick::Identification m_identification; ///< Identification of the joystick + int m_file{-1}; ///< File descriptor of the joystick + char m_mapping[ABS_MAX + 1]{0}; ///< Axes mapping (index to axis id) + JoystickState m_state; ///< Current state of the joystick + Joystick::Identification m_identification; ///< Identification of the joystick }; } // namespace sf::priv diff --git a/src/SFML/Window/WindowBase.cpp b/src/SFML/Window/WindowBase.cpp index f437be7fc..aa72f8e93 100644 --- a/src/SFML/Window/WindowBase.cpp +++ b/src/SFML/Window/WindowBase.cpp @@ -138,7 +138,7 @@ bool WindowBase::isOpen() const //////////////////////////////////////////////////////////// Event WindowBase::pollEvent() { - sf::Event event; + Event event; if (m_impl && (event = m_impl->popEvent(false))) filterEvent(event); return event; @@ -148,7 +148,7 @@ Event WindowBase::pollEvent() //////////////////////////////////////////////////////////// Event WindowBase::waitEvent() { - sf::Event event; + Event event; if (m_impl && (event = m_impl->popEvent(true))) filterEvent(event); return event; diff --git a/src/SFML/Window/WindowImpl.cpp b/src/SFML/Window/WindowImpl.cpp index 5825766ef..8306f9abb 100644 --- a/src/SFML/Window/WindowImpl.cpp +++ b/src/SFML/Window/WindowImpl.cpp @@ -131,7 +131,7 @@ WindowImpl::WindowImpl() : m_joystickStatesImpl(std::make_unique; + using AxisMap = std::unordered_map; using ButtonsVector = std::vector; AxisMap m_axis; ///< Axes (but not POV/Hat) of the joystick @@ -121,7 +121,7 @@ private: Joystick::Identification m_identification; ///< Joystick identification // NOLINTNEXTLINE(readability-identifier-naming) - static inline Location m_locationIDs[sf::Joystick::Count]{}; ///< Global Joystick register + static inline Location m_locationIDs[Joystick::Count]{}; ///< Global Joystick register /// For a corresponding SFML index, m_locationIDs is either some USB /// location or 0 if there isn't currently a connected joystick device }; diff --git a/src/SFML/Window/macOS/SFContext.mm b/src/SFML/Window/macOS/SFContext.mm index 7bffa1b02..f55eacfb5 100644 --- a/src/SFML/Window/macOS/SFContext.mm +++ b/src/SFML/Window/macOS/SFContext.mm @@ -229,7 +229,7 @@ void SFContext::createContext(SFContext* shared, unsigned int bitsPerPixel, cons { if (!(m_settings.attributeFlags & ContextSettings::Core)) { - sf::err() << "Warning. Compatibility profile not supported on this platform." << std::endl; + err() << "Warning. Compatibility profile not supported on this platform." << std::endl; m_settings.attributeFlags |= ContextSettings::Core; } m_settings.majorVersion = 3; @@ -240,7 +240,7 @@ void SFContext::createContext(SFContext* shared, unsigned int bitsPerPixel, cons if (m_settings.attributeFlags & ContextSettings::Debug) { - sf::err() << "Warning. OpenGL debugging not supported on this platform." << std::endl; + err() << "Warning. OpenGL debugging not supported on this platform." << std::endl; m_settings.attributeFlags &= ~static_cast(ContextSettings::Debug); } @@ -254,7 +254,7 @@ void SFContext::createContext(SFContext* shared, unsigned int bitsPerPixel, cons if (pixFmt == nil) { - sf::err() << "Error. Unable to find a suitable pixel format." << std::endl; + err() << "Error. Unable to find a suitable pixel format." << std::endl; return; } @@ -267,7 +267,7 @@ void SFContext::createContext(SFContext* shared, unsigned int bitsPerPixel, cons if (sharedContext == [NSOpenGLContext currentContext]) { - sf::err() << "Failed to deactivate shared context before sharing" << std::endl; + err() << "Failed to deactivate shared context before sharing" << std::endl; return; } } @@ -277,13 +277,13 @@ void SFContext::createContext(SFContext* shared, unsigned int bitsPerPixel, cons if (m_context == nil) { - sf::err() << "Error. Unable to create the context. Retrying without shared context." << std::endl; + err() << "Error. Unable to create the context. Retrying without shared context." << std::endl; m_context = [[NSOpenGLContext alloc] initWithFormat:pixFmt shareContext:nil]; if (m_context == nil) - sf::err() << "Error. Unable to create the context." << std::endl; + err() << "Error. Unable to create the context." << std::endl; else - sf::err() << "Warning. New context created without shared context." << std::endl; + err() << "Warning. New context created without shared context." << std::endl; } // Free up. From b9b8366a454edf3a5971c00b00cbecc4db223310 Mon Sep 17 00:00:00 2001 From: Chris Thrasher Date: Sun, 12 May 2024 18:42:47 -0500 Subject: [PATCH 24/38] Fix run-on sentences --- include/SFML/Graphics/Texture.hpp | 26 +++++++++++++------------- include/SFML/Graphics/VertexBuffer.hpp | 4 ++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/include/SFML/Graphics/Texture.hpp b/include/SFML/Graphics/Texture.hpp index 5f1c84527..cdc143cb0 100644 --- a/include/SFML/Graphics/Texture.hpp +++ b/include/SFML/Graphics/Texture.hpp @@ -261,7 +261,7 @@ public: /// the \a area rectangle, and to contain 32-bits RGBA pixels. /// /// No additional check is performed on the size of the pixel - /// array, passing invalid arguments will lead to an undefined + /// array. Passing invalid arguments will lead to an undefined /// behavior. /// /// This function does nothing if \a pixels is null or if the @@ -279,7 +279,7 @@ public: /// \a height arguments, and it must contain 32-bits RGBA pixels. /// /// No additional check is performed on the size of the pixel - /// array or the bounds of the area to update, passing invalid + /// array or the bounds of the area to update. Passing invalid /// arguments will lead to an undefined behavior. /// /// This function does nothing if \a pixels is null or if the @@ -301,7 +301,7 @@ public: /// is more convenient for updating a sub-area of this texture. /// /// No additional check is performed on the size of the passed - /// texture, passing a texture bigger than this texture + /// texture. Passing a texture bigger than this texture /// will lead to an undefined behavior. /// /// This function does nothing if either texture was not @@ -315,8 +315,8 @@ public: //////////////////////////////////////////////////////////// /// \brief Update a part of this texture from another texture /// - /// No additional check is performed on the size of the texture, - /// passing an invalid combination of texture size and destination + /// No additional check is performed on the size of the texture. + /// Passing an invalid combination of texture size and destination /// will lead to an undefined behavior. /// /// This function does nothing if either texture was not @@ -336,8 +336,8 @@ public: /// The other overload, which has (x, y) additional arguments, /// is more convenient for updating a sub-area of the texture. /// - /// No additional check is performed on the size of the image, - /// passing an image bigger than the texture will lead to an + /// No additional check is performed on the size of the image. + /// Passing an image bigger than the texture will lead to an /// undefined behavior. /// /// This function does nothing if the texture was not @@ -351,8 +351,8 @@ public: //////////////////////////////////////////////////////////// /// \brief Update a part of the texture from an image /// - /// No additional check is performed on the size of the image, - /// passing an invalid combination of image size and destination + /// No additional check is performed on the size of the image. + /// Passing an invalid combination of image size and destination /// will lead to an undefined behavior. /// /// This function does nothing if the texture was not @@ -372,8 +372,8 @@ public: /// The other overload, which has (x, y) additional arguments, /// is more convenient for updating a sub-area of the texture. /// - /// No additional check is performed on the size of the window, - /// passing a window bigger than the texture will lead to an + /// No additional check is performed on the size of the window. + /// Passing a window bigger than the texture will lead to an /// undefined behavior. /// /// This function does nothing if either the texture or the window @@ -387,8 +387,8 @@ public: //////////////////////////////////////////////////////////// /// \brief Update a part of the texture from the contents of a window /// - /// No additional check is performed on the size of the window, - /// passing an invalid combination of window size and destination + /// No additional check is performed on the size of the window. + /// Passing an invalid combination of window size and destination /// will lead to an undefined behavior. /// /// This function does nothing if either the texture or the window diff --git a/include/SFML/Graphics/VertexBuffer.hpp b/include/SFML/Graphics/VertexBuffer.hpp index 5b01996aa..c7b5f0698 100644 --- a/include/SFML/Graphics/VertexBuffer.hpp +++ b/include/SFML/Graphics/VertexBuffer.hpp @@ -154,7 +154,7 @@ public: /// the \a created buffer. /// /// No additional check is performed on the size of the vertex - /// array, passing invalid arguments will lead to undefined + /// array. Passing invalid arguments will lead to undefined /// behavior. /// /// This function does nothing if \a vertices is null or if the @@ -188,7 +188,7 @@ public: /// than the size of the currently created buffer, the update fails. /// /// No additional check is performed on the size of the vertex - /// array, passing invalid arguments will lead to undefined + /// array. Passing invalid arguments will lead to undefined /// behavior. /// /// \param vertices Array of vertices to copy to the buffer From 742dffa0de3162aa832b320dd66f04f57ced1055 Mon Sep 17 00:00:00 2001 From: Chris Thrasher Date: Sun, 12 May 2024 18:44:44 -0500 Subject: [PATCH 25/38] Remove reference to old API 0785093 changed the API to use a vector instead of two scalar values --- include/SFML/Graphics/Texture.hpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/include/SFML/Graphics/Texture.hpp b/include/SFML/Graphics/Texture.hpp index cdc143cb0..456a8c515 100644 --- a/include/SFML/Graphics/Texture.hpp +++ b/include/SFML/Graphics/Texture.hpp @@ -297,8 +297,9 @@ public: /// /// Although the source texture can be smaller than this texture, /// this function is usually used for updating the whole texture. - /// The other overload, which has (x, y) additional arguments, - /// is more convenient for updating a sub-area of this texture. + /// The other overload, which has an additional destination + /// argument, is more convenient for updating a sub-area of this + /// texture. /// /// No additional check is performed on the size of the passed /// texture. Passing a texture bigger than this texture @@ -333,8 +334,9 @@ public: /// /// Although the source image can be smaller than the texture, /// this function is usually used for updating the whole texture. - /// The other overload, which has (x, y) additional arguments, - /// is more convenient for updating a sub-area of the texture. + /// The other overload, which has an additional destination + /// argument, is more convenient for updating a sub-area of the + /// texture. /// /// No additional check is performed on the size of the image. /// Passing an image bigger than the texture will lead to an @@ -369,8 +371,9 @@ public: /// /// Although the source window can be smaller than the texture, /// this function is usually used for updating the whole texture. - /// The other overload, which has (x, y) additional arguments, - /// is more convenient for updating a sub-area of the texture. + /// The other overload, which has an additional destination + /// argument, is more convenient for updating a sub-area of the + /// texture. /// /// No additional check is performed on the size of the window. /// Passing a window bigger than the texture will lead to an From 8c9fa1087e197f1575caf656fc4ecb98d376ce93 Mon Sep 17 00:00:00 2001 From: Chris Thrasher Date: Sun, 12 May 2024 20:11:29 -0500 Subject: [PATCH 26/38] Remove misplaced comma --- include/SFML/Audio/SoundRecorder.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/SFML/Audio/SoundRecorder.hpp b/include/SFML/Audio/SoundRecorder.hpp index 50e135ebd..dd9548816 100644 --- a/include/SFML/Audio/SoundRecorder.hpp +++ b/include/SFML/Audio/SoundRecorder.hpp @@ -63,7 +63,7 @@ public: /// This function uses its own thread so that it doesn't block /// the rest of the program while the capture runs. /// Please note that only one capture can happen at the same time. - /// You can select which capture device will be used, by passing + /// You can select which capture device will be used by passing /// the name to the setDevice() method. If none was selected /// before, the default capture device will be used. You can get a /// list of the names of all available capture devices by calling From 3acc332de06f732331b7e4f989315ab2d5928f99 Mon Sep 17 00:00:00 2001 From: Chris Thrasher Date: Sun, 12 May 2024 20:15:44 -0500 Subject: [PATCH 27/38] Use American spellings --- include/SFML/Graphics/VertexBuffer.hpp | 2 +- include/SFML/Window/Cursor.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/SFML/Graphics/VertexBuffer.hpp b/include/SFML/Graphics/VertexBuffer.hpp index c7b5f0698..510a3e182 100644 --- a/include/SFML/Graphics/VertexBuffer.hpp +++ b/include/SFML/Graphics/VertexBuffer.hpp @@ -386,7 +386,7 @@ SFML_GRAPHICS_API void swap(VertexBuffer& left, VertexBuffer& right) noexcept; /// Simultaneous updates to the vertex buffer are not guaranteed to be /// carried out by the driver in any specific order. Updating the same /// region of the buffer from multiple threads will not cause undefined -/// behaviour, however the final state of the buffer will be unpredictable. +/// behavior, however the final state of the buffer will be unpredictable. /// /// Simultaneous updates of distinct non-overlapping regions of the buffer /// are also not guaranteed to complete in a specific order. However, in diff --git a/include/SFML/Window/Cursor.hpp b/include/SFML/Window/Cursor.hpp index 11510485d..e721f01dc 100644 --- a/include/SFML/Window/Cursor.hpp +++ b/include/SFML/Window/Cursor.hpp @@ -240,7 +240,7 @@ private: /// with either loadFromPixels() or loadFromSystem(), the /// cursor can be changed with sf::WindowBase::setMouseCursor(). /// -/// The behaviour is undefined if the cursor is destroyed while +/// The behavior is undefined if the cursor is destroyed while /// in use by the window. /// /// Usage example: From 593c4fe173a9a8a057a42b224ff1e676fb170c35 Mon Sep 17 00:00:00 2001 From: Chris Thrasher Date: Tue, 16 Apr 2024 23:19:52 -0600 Subject: [PATCH 28/38] Replace C arrays with `std::array` --- include/SFML/Graphics/Transform.hpp | 10 +++-- include/SFML/Graphics/Transform.inl | 6 +-- include/SFML/System/Utf.hpp | 1 + include/SFML/System/Utf.inl | 12 +++--- src/SFML/Audio/SoundFileWriterOgg.hpp | 13 ++++--- src/SFML/Audio/SoundFileWriterWav.cpp | 41 ++++++++++---------- src/SFML/Audio/SoundFileWriterWav.hpp | 7 ++-- src/SFML/Graphics/Texture.cpp | 11 +++--- src/SFML/Network/Packet.cpp | 38 +++++++++--------- src/SFML/Network/TcpSocket.cpp | 7 ++-- src/SFML/Window/JoystickImpl.hpp | 2 +- src/SFML/Window/JoystickManager.hpp | 4 +- src/SFML/Window/Unix/GlxContext.cpp | 9 +++-- src/SFML/Window/Unix/JoystickImpl.cpp | 11 +++--- src/SFML/Window/Unix/JoystickImpl.hpp | 8 ++-- src/SFML/Window/WindowImpl.cpp | 3 +- src/SFML/Window/macOS/HIDJoystickManager.cpp | 9 ++--- src/SFML/Window/macOS/JoystickImpl.hpp | 2 +- test/System/MemoryInputStream.test.cpp | 7 ++-- 19 files changed, 107 insertions(+), 94 deletions(-) diff --git a/include/SFML/Graphics/Transform.hpp b/include/SFML/Graphics/Transform.hpp index 8ccf091fe..8d9182462 100644 --- a/include/SFML/Graphics/Transform.hpp +++ b/include/SFML/Graphics/Transform.hpp @@ -33,6 +33,8 @@ #include +#include + namespace sf { @@ -267,10 +269,10 @@ private: // Member data //////////////////////////////////////////////////////////// // clang-format off - float m_matrix[16]{1.f, 0.f, 0.f, 0.f, - 0.f, 1.f, 0.f, 0.f, - 0.f, 0.f, 1.f, 0.f, - 0.f, 0.f, 0.f, 1.f}; //!< 4x4 matrix defining the transformation + std::array m_matrix{1.f, 0.f, 0.f, 0.f, + 0.f, 1.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, 0.f, 0.f, 1.f}; //!< 4x4 matrix defining the transformation // clang-format off }; diff --git a/include/SFML/Graphics/Transform.inl b/include/SFML/Graphics/Transform.inl index 945c1d615..6aee63b2d 100644 --- a/include/SFML/Graphics/Transform.inl +++ b/include/SFML/Graphics/Transform.inl @@ -55,7 +55,7 @@ constexpr Transform::Transform(float a00, float a01, float a02, //////////////////////////////////////////////////////////// constexpr const float* Transform::getMatrix() const { - return m_matrix; + return m_matrix.data(); } @@ -133,8 +133,8 @@ constexpr FloatRect Transform::transformRect(const FloatRect& rectangle) const //////////////////////////////////////////////////////////// constexpr Transform& Transform::combine(const Transform& transform) { - const float* a = m_matrix; - const float* b = transform.m_matrix; + const auto& a = m_matrix; + const auto& b = transform.m_matrix; // clang-format off *this = Transform(a[0] * b[0] + a[4] * b[1] + a[12] * b[3], diff --git a/include/SFML/System/Utf.hpp b/include/SFML/System/Utf.hpp index f0759f2ec..8773af89d 100644 --- a/include/SFML/System/Utf.hpp +++ b/include/SFML/System/Utf.hpp @@ -29,6 +29,7 @@ //////////////////////////////////////////////////////////// #include +#include #include #include diff --git a/include/SFML/System/Utf.inl b/include/SFML/System/Utf.inl index be072f6e5..4bdf48632 100644 --- a/include/SFML/System/Utf.inl +++ b/include/SFML/System/Utf.inl @@ -56,7 +56,7 @@ In Utf<8>::decode(In begin, In end, std::uint32_t& output, std::uint32_t replace { // clang-format off // Some useful precomputed data - static constexpr int trailing[256] = + static constexpr std::array trailing = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -68,14 +68,14 @@ In Utf<8>::decode(In begin, In end, std::uint32_t& output, std::uint32_t replace 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5 }; - static constexpr std::uint32_t offsets[6] = + static constexpr std::array offsets = { 0x00000000, 0x00003080, 0x000E2080, 0x03C82080, 0xFA082080, 0x82082080 }; // clang-format on // decode the character - const int trailingBytes = trailing[static_cast(*begin)]; + const auto trailingBytes = trailing[static_cast(*begin)]; if (trailingBytes < std::distance(begin, end)) { output = 0; @@ -110,7 +110,7 @@ template Out Utf<8>::encode(std::uint32_t input, Out output, std::uint8_t replacement) { // Some useful precomputed data - static constexpr std::uint8_t firstBytes[7] = {0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC}; + static constexpr std::array firstBytes = {0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC}; // encode the character if ((input > 0x0010FFFF) || ((input >= 0xD800) && (input <= 0xDBFF))) @@ -134,7 +134,7 @@ Out Utf<8>::encode(std::uint32_t input, Out output, std::uint8_t replacement) // clang-format on // Extract the bytes to write - std::byte bytes[4]; + std::array bytes{}; // clang-format off switch (bytestoWrite) @@ -147,7 +147,7 @@ Out Utf<8>::encode(std::uint32_t input, Out output, std::uint8_t replacement) // clang-format on // Add them to the output - output = priv::copy(bytes, bytes + bytestoWrite, output); + output = priv::copy(bytes.data(), bytes.data() + bytestoWrite, output); } return output; diff --git a/src/SFML/Audio/SoundFileWriterOgg.hpp b/src/SFML/Audio/SoundFileWriterOgg.hpp index 50cf05b4c..7e9ce98e3 100644 --- a/src/SFML/Audio/SoundFileWriterOgg.hpp +++ b/src/SFML/Audio/SoundFileWriterOgg.hpp @@ -31,6 +31,7 @@ #include +#include #include #include @@ -103,12 +104,12 @@ private: //////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// - unsigned int m_channelCount{}; //!< Channel count of the sound being written - std::size_t m_remapTable[8]{}; //!< Table we use to remap source to target channel order - std::ofstream m_file; //!< Output file - ogg_stream_state m_ogg{}; //!< OGG stream - vorbis_info m_vorbis{}; //!< Vorbis handle - vorbis_dsp_state m_state{}; //!< Current encoding state + unsigned int m_channelCount{}; //!< Channel count of the sound being written + std::array m_remapTable{}; //!< Table we use to remap source to target channel order + std::ofstream m_file; //!< Output file + ogg_stream_state m_ogg{}; //!< OGG stream + vorbis_info m_vorbis{}; //!< Vorbis handle + vorbis_dsp_state m_state{}; //!< Current encoding state }; } // namespace sf::priv diff --git a/src/SFML/Audio/SoundFileWriterWav.cpp b/src/SFML/Audio/SoundFileWriterWav.cpp index 9af6e43fe..d7fdaf8fd 100644 --- a/src/SFML/Audio/SoundFileWriterWav.cpp +++ b/src/SFML/Audio/SoundFileWriterWav.cpp @@ -31,6 +31,7 @@ #include #include +#include #include #include @@ -45,25 +46,25 @@ namespace void encode(std::ostream& stream, std::int16_t value) { - const std::byte bytes[] = {static_cast(value & 0xFF), static_cast(value >> 8)}; - stream.write(reinterpret_cast(bytes), sizeof(bytes)); + const std::array bytes = {static_cast(value & 0xFF), static_cast(value >> 8)}; + stream.write(bytes.data(), bytes.size()); } void encode(std::ostream& stream, std::uint16_t value) { - const std::byte bytes[] = {static_cast(value & 0xFF), static_cast(value >> 8)}; - stream.write(reinterpret_cast(bytes), sizeof(bytes)); + const std::array bytes = {static_cast(value & 0xFF), static_cast(value >> 8)}; + stream.write(bytes.data(), bytes.size()); } void encode(std::ostream& stream, std::uint32_t value) { - const std::byte bytes[] = { - static_cast(value & 0x000000FF), - static_cast((value & 0x0000FF00) >> 8), - static_cast((value & 0x00FF0000) >> 16), - static_cast((value & 0xFF000000) >> 24), + const std::array bytes = { + static_cast(value & 0x000000FF), + static_cast((value & 0x0000FF00) >> 8), + static_cast((value & 0x00FF0000) >> 16), + static_cast((value & 0xFF000000) >> 24), }; - stream.write(reinterpret_cast(bytes), sizeof(bytes)); + stream.write(bytes.data(), bytes.size()); } } // namespace @@ -247,17 +248,17 @@ void SoundFileWriterWav::writeHeader(unsigned int sampleRate, unsigned int chann assert(m_file.good() && "Most recent I/O operation failed"); // Write the main chunk ID - char mainChunkId[4] = {'R', 'I', 'F', 'F'}; - m_file.write(mainChunkId, sizeof(mainChunkId)); + std::array mainChunkId = {'R', 'I', 'F', 'F'}; + m_file.write(mainChunkId.data(), mainChunkId.size()); // Write the main chunk header encode(m_file, static_cast(0)); // 0 is a placeholder, will be written later - char mainChunkFormat[4] = {'W', 'A', 'V', 'E'}; - m_file.write(mainChunkFormat, sizeof(mainChunkFormat)); + std::array mainChunkFormat = {'W', 'A', 'V', 'E'}; + m_file.write(mainChunkFormat.data(), mainChunkFormat.size()); // Write the sub-chunk 1 ("format") id and size - char fmtChunkId[4] = {'f', 'm', 't', ' '}; - m_file.write(fmtChunkId, sizeof(fmtChunkId)); + std::array fmtChunkId = {'f', 'm', 't', ' '}; + m_file.write(fmtChunkId.data(), fmtChunkId.size()); if (channelCount > 2) { @@ -295,14 +296,14 @@ void SoundFileWriterWav::writeHeader(unsigned int sampleRate, unsigned int chann encode(m_file, bitsPerSample); encode(m_file, channelMask); // Write the subformat (PCM) - char subformat[16] = + std::array subformat = {'\x01', '\x00', '\x00', '\x00', '\x00', '\x00', '\x10', '\x00', '\x80', '\x00', '\x00', '\xAA', '\x00', '\x38', '\x9B', '\x71'}; - m_file.write(subformat, sizeof(subformat)); + m_file.write(subformat.data(), subformat.size()); } // Write the sub-chunk 2 ("data") id and size - char dataChunkId[4] = {'d', 'a', 't', 'a'}; - m_file.write(dataChunkId, sizeof(dataChunkId)); + std::array dataChunkId = {'d', 'a', 't', 'a'}; + m_file.write(dataChunkId.data(), dataChunkId.size()); const std::uint32_t dataChunkSize = 0; // placeholder, will be written later encode(m_file, dataChunkSize); } diff --git a/src/SFML/Audio/SoundFileWriterWav.hpp b/src/SFML/Audio/SoundFileWriterWav.hpp index 4faf8f652..75f7c95ab 100644 --- a/src/SFML/Audio/SoundFileWriterWav.hpp +++ b/src/SFML/Audio/SoundFileWriterWav.hpp @@ -29,6 +29,7 @@ //////////////////////////////////////////////////////////// #include +#include #include #include @@ -105,9 +106,9 @@ private: //////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// - std::ofstream m_file; //!< File stream to write to - unsigned int m_channelCount{}; //!< Channel count of the sound being written - std::size_t m_remapTable[18]{}; //!< Table we use to remap source to target channel order + std::ofstream m_file; //!< File stream to write to + unsigned int m_channelCount{}; //!< Channel count of the sound being written + std::array m_remapTable{}; //!< Table we use to remap source to target channel order }; } // namespace sf::priv diff --git a/src/SFML/Graphics/Texture.cpp b/src/SFML/Graphics/Texture.cpp index 113557515..dd933de10 100644 --- a/src/SFML/Graphics/Texture.cpp +++ b/src/SFML/Graphics/Texture.cpp @@ -37,6 +37,7 @@ #include #include +#include #include #include #include @@ -861,10 +862,10 @@ void Texture::bind(const Texture* texture, CoordinateType coordinateType) if ((coordinateType == CoordinateType::Pixels) || texture->m_pixelsFlipped) { // clang-format off - GLfloat matrix[16] = {1.f, 0.f, 0.f, 0.f, - 0.f, 1.f, 0.f, 0.f, - 0.f, 0.f, 1.f, 0.f, - 0.f, 0.f, 0.f, 1.f}; + std::array matrix = {1.f, 0.f, 0.f, 0.f, + 0.f, 1.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, 0.f, 0.f, 1.f}; // clang-format on // If non-normalized coordinates (= pixels) are requested, we need to @@ -884,7 +885,7 @@ void Texture::bind(const Texture* texture, CoordinateType coordinateType) // Load the matrix glCheck(glMatrixMode(GL_TEXTURE)); - glCheck(glLoadMatrixf(matrix)); + glCheck(glLoadMatrixf(matrix.data())); } else { diff --git a/src/SFML/Network/Packet.cpp b/src/SFML/Network/Packet.cpp index 498c14677..e639c049a 100644 --- a/src/SFML/Network/Packet.cpp +++ b/src/SFML/Network/Packet.cpp @@ -31,6 +31,8 @@ #include #include +#include + #include #include @@ -212,8 +214,8 @@ Packet& Packet::operator>>(std::uint64_t& data) { // Since ntohll is not available everywhere, we have to convert // to network byte order (big endian) manually - std::byte bytes[sizeof(data)]; - std::memcpy(bytes, &m_data[m_readPos], sizeof(data)); + std::array bytes{}; + std::memcpy(bytes.data(), &m_data[m_readPos], sizeof(data)); data = toInteger(bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]); @@ -427,14 +429,14 @@ Packet& Packet::operator<<(std::int64_t data) // Since htonll is not available everywhere, we have to convert // to network byte order (big endian) manually - std::uint8_t toWrite[] = {static_cast((data >> 56) & 0xFF), - static_cast((data >> 48) & 0xFF), - static_cast((data >> 40) & 0xFF), - static_cast((data >> 32) & 0xFF), - static_cast((data >> 24) & 0xFF), - static_cast((data >> 16) & 0xFF), - static_cast((data >> 8) & 0xFF), - static_cast((data)&0xFF)}; + std::array toWrite = {static_cast((data >> 56) & 0xFF), + static_cast((data >> 48) & 0xFF), + static_cast((data >> 40) & 0xFF), + static_cast((data >> 32) & 0xFF), + static_cast((data >> 24) & 0xFF), + static_cast((data >> 16) & 0xFF), + static_cast((data >> 8) & 0xFF), + static_cast((data)&0xFF)}; append(&toWrite, sizeof(toWrite)); return *this; @@ -447,14 +449,14 @@ Packet& Packet::operator<<(std::uint64_t data) // Since htonll is not available everywhere, we have to convert // to network byte order (big endian) manually - std::uint8_t toWrite[] = {static_cast((data >> 56) & 0xFF), - static_cast((data >> 48) & 0xFF), - static_cast((data >> 40) & 0xFF), - static_cast((data >> 32) & 0xFF), - static_cast((data >> 24) & 0xFF), - static_cast((data >> 16) & 0xFF), - static_cast((data >> 8) & 0xFF), - static_cast((data)&0xFF)}; + std::array toWrite = {static_cast((data >> 56) & 0xFF), + static_cast((data >> 48) & 0xFF), + static_cast((data >> 40) & 0xFF), + static_cast((data >> 32) & 0xFF), + static_cast((data >> 24) & 0xFF), + static_cast((data >> 16) & 0xFF), + static_cast((data >> 8) & 0xFF), + static_cast((data)&0xFF)}; append(&toWrite, sizeof(toWrite)); return *this; diff --git a/src/SFML/Network/TcpSocket.cpp b/src/SFML/Network/TcpSocket.cpp index 31a6249e0..fb24f2f16 100644 --- a/src/SFML/Network/TcpSocket.cpp +++ b/src/SFML/Network/TcpSocket.cpp @@ -33,6 +33,7 @@ #include #include +#include #include #include @@ -401,12 +402,12 @@ Socket::Status TcpSocket::receive(Packet& packet) } // Loop until we receive all the packet data - char buffer[1024]; + std::array buffer{}; while (m_pendingPacket.data.size() < packetSize) { // Receive a chunk of data const std::size_t sizeToGet = std::min(packetSize - m_pendingPacket.data.size(), sizeof(buffer)); - const Status status = receive(buffer, sizeToGet, received); + const Status status = receive(buffer.data(), sizeToGet, received); if (status != Status::Done) return status; @@ -415,7 +416,7 @@ Socket::Status TcpSocket::receive(Packet& packet) { m_pendingPacket.data.resize(m_pendingPacket.data.size() + received); std::byte* begin = m_pendingPacket.data.data() + m_pendingPacket.data.size() - received; - std::memcpy(begin, buffer, received); + std::memcpy(begin, buffer.data(), received); } } diff --git a/src/SFML/Window/JoystickImpl.hpp b/src/SFML/Window/JoystickImpl.hpp index 7ef509bc9..350a3fdf9 100644 --- a/src/SFML/Window/JoystickImpl.hpp +++ b/src/SFML/Window/JoystickImpl.hpp @@ -55,7 +55,7 @@ struct JoystickState { bool connected{}; //!< Is the joystick currently connected? EnumArray axes{}; //!< Position of each axis, in range [-100, 100] - bool buttons[Joystick::ButtonCount]{}; //!< Status of each button (true = pressed) + std::array buttons{}; //!< Status of each button (true = pressed) }; } // namespace sf::priv diff --git a/src/SFML/Window/JoystickManager.hpp b/src/SFML/Window/JoystickManager.hpp index a26ceaf5e..585b3ddf4 100644 --- a/src/SFML/Window/JoystickManager.hpp +++ b/src/SFML/Window/JoystickManager.hpp @@ -30,6 +30,8 @@ #include #include +#include + namespace sf::priv { @@ -124,7 +126,7 @@ private: //////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// - Item m_joysticks[Joystick::Count]; //!< Joysticks information and state + std::array m_joysticks; //!< Joysticks information and state }; } // namespace sf::priv diff --git a/src/SFML/Window/Unix/GlxContext.cpp b/src/SFML/Window/Unix/GlxContext.cpp index 842d43338..1f1629f8d 100644 --- a/src/SFML/Window/Unix/GlxContext.cpp +++ b/src/SFML/Window/Unix/GlxContext.cpp @@ -33,6 +33,7 @@ #include +#include #include #include #include @@ -513,10 +514,10 @@ void GlxContext::createSurface(GlxContext* shared, const Vector2u& size, unsigne if (config) { - int attributes[] = + std::array attributes = {GLX_PBUFFER_WIDTH, static_cast(size.x), GLX_PBUFFER_HEIGHT, static_cast(size.y), 0, 0}; - m_pbuffer = glXCreatePbuffer(m_display.get(), *config, attributes); + m_pbuffer = glXCreatePbuffer(m_display.get(), *config, attributes.data()); updateSettingsFromVisualInfo(&visualInfo); @@ -578,11 +579,11 @@ void GlxContext::createContext(GlxContext* shared) glXQueryDrawable(m_display.get(), m_pbuffer, GLX_FBCONFIG_ID, &fbConfigId); - int attributes[] = {GLX_FBCONFIG_ID, static_cast(fbConfigId), 0, 0}; + std::array attributes = {GLX_FBCONFIG_ID, static_cast(fbConfigId), 0, 0}; int count = 0; const auto fbconfig = X11Ptr( - glXChooseFBConfig(m_display.get(), DefaultScreen(m_display.get()), attributes, &count)); + glXChooseFBConfig(m_display.get(), DefaultScreen(m_display.get()), attributes.data(), &count)); if (count == 1) visualInfo = X11Ptr(glXGetVisualFromFBConfig(m_display.get(), *fbconfig)); diff --git a/src/SFML/Window/Unix/JoystickImpl.cpp b/src/SFML/Window/Unix/JoystickImpl.cpp index 308037993..316898cf6 100644 --- a/src/SFML/Window/Unix/JoystickImpl.cpp +++ b/src/SFML/Window/Unix/JoystickImpl.cpp @@ -400,14 +400,13 @@ std::string getJoystickName(unsigned int index) if (fd >= 0) { // Get the name - char name[128] = {}; - - const int result = ioctl(fd, JSIOCGNAME(sizeof(name)), name); + std::array name{}; + const int result = ioctl(fd, JSIOCGNAME(name.size()), name.data()); ::close(fd); if (result >= 0) - return name; + return name.data(); } // Fall back to manual USB chain walk via udev @@ -600,7 +599,7 @@ JoystickCaps JoystickImpl::getCapabilities() const ioctl(m_file, JSIOCGAXES, &axesCount); for (int i = 0; i < axesCount; ++i) { - switch (m_mapping[i]) + switch (m_mapping[static_cast(i)]) { // clang-format off case ABS_X: caps.axes[Joystick::Axis::X] = true; break; @@ -650,7 +649,7 @@ JoystickState JoystickImpl::JoystickImpl::update() { const float value = joyState.value * 100.f / 32767.f; - if (joyState.number < ABS_MAX + 1) + if (joyState.number < m_mapping.size()) { switch (m_mapping[joyState.number]) { diff --git a/src/SFML/Window/Unix/JoystickImpl.hpp b/src/SFML/Window/Unix/JoystickImpl.hpp index b65b9a0f2..843b983c5 100644 --- a/src/SFML/Window/Unix/JoystickImpl.hpp +++ b/src/SFML/Window/Unix/JoystickImpl.hpp @@ -105,10 +105,10 @@ private: //////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// - int m_file{-1}; ///< File descriptor of the joystick - char m_mapping[ABS_MAX + 1]{0}; ///< Axes mapping (index to axis id) - JoystickState m_state; ///< Current state of the joystick - Joystick::Identification m_identification; ///< Identification of the joystick + int m_file{-1}; ///< File descriptor of the joystick + std::array m_mapping{}; ///< Axes mapping (index to axis id) + JoystickState m_state; ///< Current state of the joystick + Joystick::Identification m_identification; ///< Identification of the joystick }; } // namespace sf::priv diff --git a/src/SFML/Window/WindowImpl.cpp b/src/SFML/Window/WindowImpl.cpp index 8306f9abb..c903a7f87 100644 --- a/src/SFML/Window/WindowImpl.cpp +++ b/src/SFML/Window/WindowImpl.cpp @@ -34,6 +34,7 @@ #include #include +#include #include #include @@ -96,7 +97,7 @@ namespace sf::priv //////////////////////////////////////////////////////////// struct WindowImpl::JoystickStatesImpl { - JoystickState states[Joystick::Count]{}; //!< Previous state of the joysticks + std::array states{}; //!< Previous state of the joysticks }; diff --git a/src/SFML/Window/macOS/HIDJoystickManager.cpp b/src/SFML/Window/macOS/HIDJoystickManager.cpp index 0589bdd18..3c05db8cc 100644 --- a/src/SFML/Window/macOS/HIDJoystickManager.cpp +++ b/src/SFML/Window/macOS/HIDJoystickManager.cpp @@ -29,6 +29,8 @@ #include #include +#include + //////////////////////////////////////////////////////////// // Private data //////////////////////////////////////////////////////////// @@ -75,11 +77,8 @@ HIDJoystickManager::HIDJoystickManager() CFDictionaryRef mask1 = HIDInputManager::copyDevicesMask(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad); - CFDictionaryRef maskArray[2]; - maskArray[0] = mask0; - maskArray[1] = mask1; - - CFArrayRef mask = CFArrayCreate(nullptr, reinterpret_cast(maskArray), 2, nullptr); + std::array maskArray = {mask0, mask1}; + CFArrayRef mask = CFArrayCreate(nullptr, reinterpret_cast(maskArray.data()), maskArray.size(), nullptr); IOHIDManagerSetDeviceMatchingMultiple(m_manager, mask); CFRelease(mask); diff --git a/src/SFML/Window/macOS/JoystickImpl.hpp b/src/SFML/Window/macOS/JoystickImpl.hpp index 6c411f27b..d998a1a10 100644 --- a/src/SFML/Window/macOS/JoystickImpl.hpp +++ b/src/SFML/Window/macOS/JoystickImpl.hpp @@ -121,7 +121,7 @@ private: Joystick::Identification m_identification; ///< Joystick identification // NOLINTNEXTLINE(readability-identifier-naming) - static inline Location m_locationIDs[Joystick::Count]{}; ///< Global Joystick register + static inline std::array m_locationIDs{}; ///< Global Joystick register /// For a corresponding SFML index, m_locationIDs is either some USB /// location or 0 if there isn't currently a connected joystick device }; diff --git a/test/System/MemoryInputStream.test.cpp b/test/System/MemoryInputStream.test.cpp index 06a12fddd..5b5845d65 100644 --- a/test/System/MemoryInputStream.test.cpp +++ b/test/System/MemoryInputStream.test.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -32,9 +33,9 @@ TEST_CASE("[System] sf::MemoryInputStream") sf::MemoryInputStream mis; mis.open(memoryContents.data(), sizeof(char) * memoryContents.size()); - char buffer[32]; - CHECK(mis.read(buffer, 5) == 5); - CHECK(std::string_view(buffer, 5) == std::string_view(memoryContents.data(), 5)); + std::array buffer{}; + CHECK(mis.read(buffer.data(), 5) == 5); + CHECK(std::string_view(buffer.data(), 5) == std::string_view(memoryContents.data(), 5)); CHECK(mis.seek(10) == 10); CHECK(mis.tell() == 10); CHECK(mis.getSize() == 11); From 33457969073abec4f40afb803e6b255ed1a624f8 Mon Sep 17 00:00:00 2001 From: binary1248 Date: Mon, 29 Apr 2024 21:51:40 +0200 Subject: [PATCH 29/38] Fall back to using the NULL audio backend if the default backends don't provide any devices. --- src/SFML/Audio/AudioDevice.cpp | 56 ++++++++++++++++++++++---------- src/SFML/Audio/SoundRecorder.cpp | 45 ++++++++++++++++++++++--- 2 files changed, 79 insertions(+), 22 deletions(-) diff --git a/src/SFML/Audio/AudioDevice.cpp b/src/SFML/Audio/AudioDevice.cpp index a7693976c..700bae11b 100644 --- a/src/SFML/Audio/AudioDevice.cpp +++ b/src/SFML/Audio/AudioDevice.cpp @@ -30,6 +30,7 @@ #include #include +#include #include @@ -68,33 +69,52 @@ AudioDevice::AudioDevice() // Create the context m_context.emplace(); - auto contextConfig = ma_context_config_init(); - contextConfig.pLog = &*m_log; + auto contextConfig = ma_context_config_init(); + contextConfig.pLog = &*m_log; + ma_uint32 deviceCount = 0; + const auto nullBackend = ma_backend_null; + const std::array backendLists{nullptr, &nullBackend}; - if (const auto result = ma_context_init(nullptr, 0, &contextConfig, &*m_context); result != MA_SUCCESS) + for (const auto* backendList : backendLists) { - m_context.reset(); - err() << "Failed to initialize the audio context: " << ma_result_description(result) << std::endl; - return; + // We can set backendCount to 1 since it is ignored when backends is set to nullptr + if (const auto result = ma_context_init(backendList, 1, &contextConfig, &*m_context); result != MA_SUCCESS) + { + m_context.reset(); + err() << "Failed to initialize the audio playback context: " << ma_result_description(result) << std::endl; + return; + } + + // Count the playback devices + if (const auto result = ma_context_get_devices(&*m_context, nullptr, &deviceCount, nullptr, nullptr); + result != MA_SUCCESS) + { + err() << "Failed to get audio playback devices: " << ma_result_description(result) << std::endl; + return; + } + + // Check if there are audio playback devices available on the system + if (deviceCount > 0) + break; + + // Warn if no devices were found using the default backend list + if (backendList == nullptr) + err() << "No audio playback devices available on the system" << std::endl; + + // Clean up the context if we didn't find any devices + ma_context_uninit(&*m_context); } - // Count the playback devices - ma_uint32 deviceCount = 0; - - if (const auto result = ma_context_get_devices(&*m_context, nullptr, &deviceCount, nullptr, nullptr); - result != MA_SUCCESS) - { - err() << "Failed to get audio playback devices: " << ma_result_description(result) << std::endl; - return; - } - - // Check if there are audio playback devices available on the system + // If the NULL audio backend also doesn't provide a device we give up if (deviceCount == 0) { - err() << "No audio playback devices available on the system" << std::endl; + m_context.reset(); return; } + if (m_context->backend == ma_backend_null) + err() << "Using NULL audio backend for playback" << std::endl; + // Create the playback device m_playbackDevice.emplace(); diff --git a/src/SFML/Audio/SoundRecorder.cpp b/src/SFML/Audio/SoundRecorder.cpp index 202ae0b04..da4493dce 100644 --- a/src/SFML/Audio/SoundRecorder.cpp +++ b/src/SFML/Audio/SoundRecorder.cpp @@ -33,6 +33,7 @@ #include #include +#include #include #include @@ -187,16 +188,52 @@ SoundRecorder::SoundRecorder() : m_impl(std::make_unique(this)) // Create the context m_impl->context.emplace(); - auto contextConfig = ma_context_config_init(); - contextConfig.pLog = &*m_impl->log; + auto contextConfig = ma_context_config_init(); + contextConfig.pLog = &*m_impl->log; + ma_uint32 deviceCount = 0; + const auto nullBackend = ma_backend_null; + const std::array backendLists{nullptr, &nullBackend}; - if (const auto result = ma_context_init(nullptr, 0, &contextConfig, &*m_impl->context); result != MA_SUCCESS) + for (const auto* backendList : backendLists) + { + // We can set backendCount to 1 since it is ignored when backends is set to nullptr + if (const auto result = ma_context_init(backendList, 1, &contextConfig, &*m_impl->context); result != MA_SUCCESS) + { + m_impl->context.reset(); + err() << "Failed to initialize the audio capture context: " << ma_result_description(result) << std::endl; + return; + } + + // Count the capture devices + if (const auto result = ma_context_get_devices(&*m_impl->context, nullptr, nullptr, nullptr, &deviceCount); + result != MA_SUCCESS) + { + err() << "Failed to get audio capture devices: " << ma_result_description(result) << std::endl; + return; + } + + // Check if there are audio capture devices available on the system + if (deviceCount > 0) + break; + + // Warn if no devices were found using the default backend list + if (backendList == nullptr) + err() << "No audio capture devices available on the system" << std::endl; + + // Clean up the context if we didn't find any devices + ma_context_uninit(&*m_impl->context); + } + + // If the NULL audio backend also doesn't provide a device we give up + if (deviceCount == 0) { m_impl->context.reset(); - err() << "Failed to initialize the audio context: " << ma_result_description(result) << std::endl; return; } + if (m_impl->context->backend == ma_backend_null) + err() << "Using NULL audio backend for capture" << std::endl; + // Create the capture device m_impl->initialize(); } From 884206ce0c372a68318eeee79ab9447c2ab22f6a Mon Sep 17 00:00:00 2001 From: vittorioromeo Date: Mon, 13 May 2024 20:30:31 +0200 Subject: [PATCH 30/38] Store angles internally as radians --- include/SFML/System/Angle.hpp | 14 ++++-- include/SFML/System/Angle.inl | 60 ++++++++++++------------ include/SFML/Window/Sensor.hpp | 4 +- src/SFML/Audio/AudioDevice.cpp | 4 +- src/SFML/Audio/AudioDevice.hpp | 2 +- src/SFML/Audio/MiniaudioUtils.cpp | 4 +- src/SFML/Audio/SoundSource.cpp | 4 +- src/SFML/Graphics/CircleShape.cpp | 2 +- src/SFML/Window/iOS/SensorImpl.mm | 21 ++++----- test/Graphics/Transformable.test.cpp | 16 +++---- test/Graphics/View.test.cpp | 6 +-- test/System/Angle.test.cpp | 70 ++++++++++++++-------------- test/TestUtilities/SystemUtil.cpp | 2 +- 13 files changed, 104 insertions(+), 105 deletions(-) diff --git a/include/SFML/System/Angle.hpp b/include/SFML/System/Angle.hpp index 2af146632..12aa32ed2 100644 --- a/include/SFML/System/Angle.hpp +++ b/include/SFML/System/Angle.hpp @@ -144,20 +144,20 @@ private: friend constexpr Angle radians(float angle); //////////////////////////////////////////////////////////// - /// \brief Construct from a number of degrees + /// \brief Construct from a number of radians /// /// This function is internal. To construct angle values, /// use sf::radians or sf::degrees instead. /// - /// \param degrees Angle in degrees + /// \param radians Angle in radians /// //////////////////////////////////////////////////////////// - constexpr explicit Angle(float degrees); + constexpr explicit Angle(float radians); //////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// - float m_degrees{}; //!< Angle value stored as degrees + float m_radians{}; //!< Angle value stored as radians }; //////////////////////////////////////////////////////////// @@ -187,6 +187,7 @@ private: //////////////////////////////////////////////////////////// /// \relates Angle /// \brief Overload of == operator to compare two angle values +/// \note Does not automatically wrap the angle value /// /// \param left Left operand (an angle) /// \param right Right operand (an angle) @@ -199,6 +200,7 @@ private: //////////////////////////////////////////////////////////// /// \relates Angle /// \brief Overload of != operator to compare two angle values +/// \note Does not automatically wrap the angle value /// /// \param left Left operand (an angle) /// \param right Right operand (an angle) @@ -211,6 +213,7 @@ private: //////////////////////////////////////////////////////////// /// \relates Angle /// \brief Overload of < operator to compare two angle values +/// \note Does not automatically wrap the angle value /// /// \param left Left operand (an angle) /// \param right Right operand (an angle) @@ -223,6 +226,7 @@ private: //////////////////////////////////////////////////////////// /// \relates Angle /// \brief Overload of > operator to compare two angle values +/// \note Does not automatically wrap the angle value /// /// \param left Left operand (an angle) /// \param right Right operand (an angle) @@ -235,6 +239,7 @@ private: //////////////////////////////////////////////////////////// /// \relates Angle /// \brief Overload of <= operator to compare two angle values +/// \note Does not automatically wrap the angle value /// /// \param left Left operand (an angle) /// \param right Right operand (an angle) @@ -247,6 +252,7 @@ private: //////////////////////////////////////////////////////////// /// \relates Angle /// \brief Overload of >= operator to compare two angle values +/// \note Does not automatically wrap the angle value /// /// \param left Left operand (an angle) /// \param right Right operand (an angle) diff --git a/include/SFML/System/Angle.inl b/include/SFML/System/Angle.inl index 53b41d526..0a6abbc64 100644 --- a/include/SFML/System/Angle.inl +++ b/include/SFML/System/Angle.inl @@ -34,16 +34,14 @@ namespace sf { namespace priv { -constexpr float pi = 3.141592654f; +constexpr float pi = 3.141592654f; +constexpr float tau = pi * 2.f; constexpr float positiveRemainder(float a, float b) { - assert(b > 0.0f && "Cannot calculate remainder with non-positive divisor"); + assert(b > 0.f && "Cannot calculate remainder with non-positive divisor"); const float val = a - static_cast(static_cast(a / b)) * b; - if (val >= 0.f) - return val; - else - return val + b; + return val >= 0.f ? val : val + b; } } // namespace priv @@ -55,33 +53,33 @@ constexpr Angle::Angle() = default; //////////////////////////////////////////////////////////// constexpr float Angle::asDegrees() const { - return m_degrees; + return m_radians * (180.f / priv::pi); } //////////////////////////////////////////////////////////// constexpr float Angle::asRadians() const { - return m_degrees * (priv::pi / 180); + return m_radians; } //////////////////////////////////////////////////////////// constexpr Angle Angle::wrapSigned() const { - return degrees(priv::positiveRemainder(m_degrees + 180, 360) - 180); + return radians(priv::positiveRemainder(m_radians + priv::pi, priv::tau) - priv::pi); } //////////////////////////////////////////////////////////// constexpr Angle Angle::wrapUnsigned() const { - return degrees(priv::positiveRemainder(m_degrees, 360)); + return radians(priv::positiveRemainder(m_radians, priv::tau)); } //////////////////////////////////////////////////////////// -constexpr Angle::Angle(float degrees) : m_degrees(degrees) +constexpr Angle::Angle(float radians) : m_radians(radians) { } @@ -89,70 +87,70 @@ constexpr Angle::Angle(float degrees) : m_degrees(degrees) //////////////////////////////////////////////////////////// constexpr Angle degrees(float angle) { - return Angle(angle); + return Angle(angle * (priv::pi / 180.f)); } //////////////////////////////////////////////////////////// constexpr Angle radians(float angle) { - return Angle(angle * (180 / priv::pi)); + return Angle(angle); } //////////////////////////////////////////////////////////// constexpr bool operator==(Angle left, Angle right) { - return left.asDegrees() == right.asDegrees(); + return left.asRadians() == right.asRadians(); } //////////////////////////////////////////////////////////// constexpr bool operator!=(Angle left, Angle right) { - return left.asDegrees() != right.asDegrees(); + return left.asRadians() != right.asRadians(); } //////////////////////////////////////////////////////////// constexpr bool operator<(Angle left, Angle right) { - return left.asDegrees() < right.asDegrees(); + return left.asRadians() < right.asRadians(); } //////////////////////////////////////////////////////////// constexpr bool operator>(Angle left, Angle right) { - return left.asDegrees() > right.asDegrees(); + return left.asRadians() > right.asRadians(); } //////////////////////////////////////////////////////////// constexpr bool operator<=(Angle left, Angle right) { - return left.asDegrees() <= right.asDegrees(); + return left.asRadians() <= right.asRadians(); } //////////////////////////////////////////////////////////// constexpr bool operator>=(Angle left, Angle right) { - return left.asDegrees() >= right.asDegrees(); + return left.asRadians() >= right.asRadians(); } //////////////////////////////////////////////////////////// constexpr Angle operator-(Angle right) { - return degrees(-right.asDegrees()); + return radians(-right.asRadians()); } //////////////////////////////////////////////////////////// constexpr Angle operator+(Angle left, Angle right) { - return degrees(left.asDegrees() + right.asDegrees()); + return radians(left.asRadians() + right.asRadians()); } @@ -166,7 +164,7 @@ constexpr Angle& operator+=(Angle& left, Angle right) //////////////////////////////////////////////////////////// constexpr Angle operator-(Angle left, Angle right) { - return degrees(left.asDegrees() - right.asDegrees()); + return radians(left.asRadians() - right.asRadians()); } @@ -180,7 +178,7 @@ constexpr Angle& operator-=(Angle& left, Angle right) //////////////////////////////////////////////////////////// constexpr Angle operator*(Angle left, float right) { - return degrees(left.asDegrees() * right); + return radians(left.asRadians() * right); } @@ -201,15 +199,15 @@ constexpr Angle& operator*=(Angle& left, float right) //////////////////////////////////////////////////////////// constexpr Angle operator/(Angle left, float right) { - assert(right != 0 && "Angle::operator/ cannot divide by 0"); - return degrees(left.asDegrees() / right); + assert(right != 0.f && "Angle::operator/ cannot divide by 0"); + return radians(left.asRadians() / right); } //////////////////////////////////////////////////////////// constexpr Angle& operator/=(Angle& left, float right) { - assert(right != 0 && "Angle::operator/= cannot divide by 0"); + assert(right != 0.f && "Angle::operator/= cannot divide by 0"); return left = left / right; } @@ -217,23 +215,23 @@ constexpr Angle& operator/=(Angle& left, float right) //////////////////////////////////////////////////////////// constexpr float operator/(Angle left, Angle right) { - assert(right.asDegrees() != 0 && "Angle::operator/ cannot divide by 0"); - return left.asDegrees() / right.asDegrees(); + assert(right.asRadians() != 0.f && "Angle::operator/ cannot divide by 0"); + return left.asRadians() / right.asRadians(); } //////////////////////////////////////////////////////////// constexpr Angle operator%(Angle left, Angle right) { - assert(right.asDegrees() != 0 && "Angle::operator% cannot modulus by 0"); - return degrees(priv::positiveRemainder(left.asDegrees(), right.asDegrees())); + assert(right.asRadians() != 0.f && "Angle::operator% cannot modulus by 0"); + return radians(priv::positiveRemainder(left.asRadians(), right.asRadians())); } //////////////////////////////////////////////////////////// constexpr Angle& operator%=(Angle& left, Angle right) { - assert(right.asDegrees() != 0 && "Angle::operator%= cannot modulus by 0"); + assert(right.asRadians() != 0.f && "Angle::operator%= cannot modulus by 0"); return left = left % right; } diff --git a/include/SFML/Window/Sensor.hpp b/include/SFML/Window/Sensor.hpp index 7662b4650..123ef358c 100644 --- a/include/SFML/Window/Sensor.hpp +++ b/include/SFML/Window/Sensor.hpp @@ -44,11 +44,11 @@ namespace sf::Sensor enum class Type { Accelerometer, //!< Measures the raw acceleration (m/s^2) - Gyroscope, //!< Measures the raw rotation rates (degrees/s) + Gyroscope, //!< Measures the raw rotation rates (radians/s) Magnetometer, //!< Measures the ambient magnetic field (micro-teslas) Gravity, //!< Measures the direction and intensity of gravity, independent of device acceleration (m/s^2) UserAcceleration, //!< Measures the direction and intensity of device acceleration, independent of the gravity (m/s^2) - Orientation //!< Measures the absolute 3D orientation (degrees) + Orientation //!< Measures the absolute 3D orientation (radians) }; // NOLINTNEXTLINE(readability-identifier-naming) diff --git a/src/SFML/Audio/AudioDevice.cpp b/src/SFML/Audio/AudioDevice.cpp index 700bae11b..dc8e45494 100644 --- a/src/SFML/Audio/AudioDevice.cpp +++ b/src/SFML/Audio/AudioDevice.cpp @@ -324,8 +324,8 @@ void AudioDevice::setCone(const Listener::Cone& cone) ma_engine_listener_set_cone(&*instance->m_engine, 0, - std::clamp(cone.innerAngle.asRadians(), 0.f, degrees(360).asRadians()), - std::clamp(cone.outerAngle.asRadians(), 0.f, degrees(360).asRadians()), + std::clamp(cone.innerAngle, Angle::Zero, degrees(360.f)).asRadians(), + std::clamp(cone.outerAngle, Angle::Zero, degrees(360.f)).asRadians(), cone.outerGain); } diff --git a/src/SFML/Audio/AudioDevice.hpp b/src/SFML/Audio/AudioDevice.hpp index 04981eaed..c2396ec62 100644 --- a/src/SFML/Audio/AudioDevice.hpp +++ b/src/SFML/Audio/AudioDevice.hpp @@ -231,7 +231,7 @@ private: Vector3f position{0, 0, 0}; Vector3f direction{0, 0, -1}; Vector3f velocity{0, 0, 0}; - Listener::Cone cone{degrees(360), degrees(360), 1}; + Listener::Cone cone{degrees(360.f), degrees(360.f), 1}; Vector3f upVector{0, 1, 0}; }; diff --git a/src/SFML/Audio/MiniaudioUtils.cpp b/src/SFML/Audio/MiniaudioUtils.cpp index d1c6383f7..8ea706494 100644 --- a/src/SFML/Audio/MiniaudioUtils.cpp +++ b/src/SFML/Audio/MiniaudioUtils.cpp @@ -63,8 +63,8 @@ struct SavedSettings float minGain{0.f}; float maxGain{1.f}; float rollOff{1.f}; - float innerAngle{degrees(360).asRadians()}; - float outerAngle{degrees(360).asRadians()}; + float innerAngle{degrees(360.f).asRadians()}; + float outerAngle{degrees(360.f).asRadians()}; float outerGain{0.f}; }; diff --git a/src/SFML/Audio/SoundSource.cpp b/src/SFML/Audio/SoundSource.cpp index 8f1b3dbdd..1b210d2ac 100644 --- a/src/SFML/Audio/SoundSource.cpp +++ b/src/SFML/Audio/SoundSource.cpp @@ -88,8 +88,8 @@ void SoundSource::setCone(const Cone& cone) { if (auto* sound = static_cast(getSound())) ma_sound_set_cone(sound, - std::clamp(cone.innerAngle, degrees(0), degrees(360)).asRadians(), - std::clamp(cone.outerAngle, degrees(0), degrees(360)).asRadians(), + std::clamp(cone.innerAngle, Angle::Zero, degrees(360.f)).asRadians(), + std::clamp(cone.outerAngle, Angle::Zero, degrees(360.f)).asRadians(), cone.outerGain); } diff --git a/src/SFML/Graphics/CircleShape.cpp b/src/SFML/Graphics/CircleShape.cpp index 4df41e516..7edb3eadb 100644 --- a/src/SFML/Graphics/CircleShape.cpp +++ b/src/SFML/Graphics/CircleShape.cpp @@ -71,7 +71,7 @@ std::size_t CircleShape::getPointCount() const //////////////////////////////////////////////////////////// Vector2f CircleShape::getPoint(std::size_t index) const { - const Angle angle = static_cast(index) / static_cast(m_pointCount) * degrees(360) - degrees(90); + const Angle angle = static_cast(index) / static_cast(m_pointCount) * degrees(360.f) - degrees(90.f); return Vector2f(m_radius, m_radius) + Vector2f(m_radius, angle); } diff --git a/src/SFML/Window/iOS/SensorImpl.mm b/src/SFML/Window/iOS/SensorImpl.mm index b6ef45944..1c86983b7 100644 --- a/src/SFML/Window/iOS/SensorImpl.mm +++ b/src/SFML/Window/iOS/SensorImpl.mm @@ -34,11 +34,6 @@ namespace { unsigned int deviceMotionEnabledCount = 0; - -float toDegrees(float radians) -{ - return sf::radians(radians).asDegrees(); -} } @@ -145,10 +140,10 @@ Vector3f SensorImpl::update() break; case Sensor::Type::Gyroscope: - // Rotation rates are given in rad/s, convert to deg/s - value.x = toDegrees(static_cast(manager.gyroData.rotationRate.x)); - value.y = toDegrees(static_cast(manager.gyroData.rotationRate.y)); - value.z = toDegrees(static_cast(manager.gyroData.rotationRate.z)); + // Rotation rates are given in rad/s + value.x = static_cast(manager.gyroData.rotationRate.x); + value.y = static_cast(manager.gyroData.rotationRate.y); + value.z = static_cast(manager.gyroData.rotationRate.z); break; case Sensor::Type::Magnetometer: @@ -166,10 +161,10 @@ Vector3f SensorImpl::update() break; case Sensor::Type::Orientation: - // Absolute rotation (Euler) angles are given in radians, convert to degrees - value.x = toDegrees(static_cast(manager.deviceMotion.attitude.yaw)); - value.y = toDegrees(static_cast(manager.deviceMotion.attitude.pitch)); - value.z = toDegrees(static_cast(manager.deviceMotion.attitude.roll)); + // Absolute rotation (Euler) angles are given in radians + value.x = static_cast(manager.deviceMotion.attitude.yaw); + value.y = static_cast(manager.deviceMotion.attitude.pitch); + value.z = static_cast(manager.deviceMotion.attitude.roll); break; default: diff --git a/test/Graphics/Transformable.test.cpp b/test/Graphics/Transformable.test.cpp index 9ed5e890e..b980fb282 100644 --- a/test/Graphics/Transformable.test.cpp +++ b/test/Graphics/Transformable.test.cpp @@ -34,11 +34,11 @@ TEST_CASE("[Graphics] sf::Transformable") CHECK(transformable.getPosition() == sf::Vector2f(3, 4)); transformable.setRotation(sf::degrees(3.14f)); - CHECK(transformable.getRotation() == sf::degrees(3.14f)); + CHECK(transformable.getRotation() == Approx(sf::degrees(3.14f))); transformable.setRotation(sf::degrees(540)); - CHECK(transformable.getRotation() == sf::degrees(180)); + CHECK(transformable.getRotation() == Approx(sf::degrees(180))); transformable.setRotation(sf::degrees(-72)); - CHECK(transformable.getRotation() == sf::degrees(288)); + CHECK(transformable.getRotation() == Approx(sf::degrees(288))); transformable.setScale({5, 6}); CHECK(transformable.getScale() == sf::Vector2f(5, 6)); @@ -102,15 +102,15 @@ TEST_CASE("[Graphics] sf::Transformable") sf::Transformable transformable; CHECK(transformable.getRotation() == sf::Angle::Zero); transformable.rotate(sf::degrees(15)); - CHECK(transformable.getRotation() == sf::degrees(15)); + CHECK(transformable.getRotation() == Approx(sf::degrees(15))); transformable.rotate(sf::degrees(360)); - CHECK(transformable.getRotation() == sf::degrees(15)); + CHECK(transformable.getRotation() == Approx(sf::degrees(15))); transformable.rotate(sf::degrees(-25)); - CHECK(transformable.getRotation() == sf::degrees(350)); + CHECK(transformable.getRotation() == Approx(sf::degrees(350))); transformable.rotate(sf::degrees(-720)); - CHECK(transformable.getRotation() == sf::degrees(350)); + CHECK(transformable.getRotation() == Approx(sf::degrees(350))); transformable.rotate(sf::degrees(-370)); - CHECK(transformable.getRotation() == sf::degrees(340)); + CHECK(transformable.getRotation() == Approx(sf::degrees(340))); } SECTION("scale()") diff --git a/test/Graphics/View.test.cpp b/test/Graphics/View.test.cpp index f9b3497d9..3cb829311 100644 --- a/test/Graphics/View.test.cpp +++ b/test/Graphics/View.test.cpp @@ -76,13 +76,13 @@ TEST_CASE("[Graphics] sf::View") { sf::View view; view.setRotation(sf::degrees(-345)); - CHECK(view.getRotation() == sf::degrees(15)); + CHECK(view.getRotation() == Approx(sf::degrees(15))); CHECK(view.getTransform() == Approx(sf::Transform(0.00193185f, 0.000517638f, -1.22474f, 0.000517638f, -0.00193185f, 0.707107f, 0, 0, 1))); CHECK(view.getInverseTransform() == Approx(sf::Transform(482.963f, 129.41f, 500, 129.41f, -482.963f, 500, 0, 0, 1))); view.setRotation(sf::degrees(400)); - CHECK(view.getRotation() == sf::degrees(40)); + CHECK(view.getRotation() == Approx(sf::degrees(40))); CHECK(view.getTransform() == Approx(sf::Transform(0.00153209f, 0.00128558f, -1.40883f, 0.00128558f, -0.00153209f, 0.123257f, 0, 0, 1))); CHECK(view.getInverseTransform() == @@ -140,7 +140,7 @@ TEST_CASE("[Graphics] sf::View") sf::View view; view.setRotation(sf::degrees(45)); view.rotate(sf::degrees(-15)); - CHECK(view.getRotation() == sf::degrees(30)); + CHECK(view.getRotation() == Approx(sf::degrees(30))); CHECK(view.getTransform() == Approx(sf::Transform(0.00173205f, 0.001f, -1.36603f, 0.001f, -0.00173205f, 0.366025f, 0, 0, 1))); CHECK(view.getInverseTransform() == Approx(sf::Transform(433.013f, 250, 500, 250, -433.013f, 500, 0, 0, 1))); diff --git a/test/System/Angle.test.cpp b/test/System/Angle.test.cpp index 4934a27f8..a438ea5f9 100644 --- a/test/System/Angle.test.cpp +++ b/test/System/Angle.test.cpp @@ -25,33 +25,33 @@ TEST_CASE("[System] sf::Angle") SECTION("wrapSigned()") { STATIC_CHECK(sf::Angle::Zero.wrapSigned() == sf::Angle::Zero); - STATIC_CHECK(sf::degrees(0).wrapSigned() == sf::degrees(0)); - STATIC_CHECK(sf::degrees(1).wrapSigned() == sf::degrees(1)); - STATIC_CHECK(sf::degrees(-1).wrapSigned() == sf::degrees(-1)); - STATIC_CHECK(sf::degrees(90).wrapSigned() == sf::degrees(90)); - STATIC_CHECK(sf::degrees(-90).wrapSigned() == sf::degrees(-90)); - STATIC_CHECK(sf::degrees(180).wrapSigned() == sf::degrees(-180)); - STATIC_CHECK(sf::degrees(-180).wrapSigned() == sf::degrees(-180)); - STATIC_CHECK(sf::degrees(360).wrapSigned() == sf::degrees(0)); - STATIC_CHECK(sf::degrees(-360).wrapSigned() == sf::degrees(0)); - STATIC_CHECK(sf::degrees(720).wrapSigned() == sf::degrees(0)); - STATIC_CHECK(sf::degrees(-720).wrapSigned() == sf::degrees(0)); + CHECK(sf::degrees(0).wrapSigned() == Approx(sf::degrees(0))); + CHECK(sf::degrees(1).wrapSigned() == Approx(sf::degrees(1))); + CHECK(sf::degrees(-1).wrapSigned() == Approx(sf::degrees(-1))); + CHECK(sf::degrees(90).wrapSigned() == Approx(sf::degrees(90))); + CHECK(sf::degrees(-90).wrapSigned() == Approx(sf::degrees(-90))); + CHECK(sf::degrees(180).wrapSigned() == Approx(sf::degrees(-180))); + CHECK(sf::degrees(-180).wrapSigned() == Approx(sf::degrees(-180))); + CHECK(sf::degrees(360).wrapSigned() == Approx(sf::degrees(0))); + CHECK(sf::degrees(-360).wrapSigned() == Approx(sf::degrees(0))); + CHECK(sf::degrees(720).wrapSigned() == Approx(sf::degrees(0))); + CHECK(sf::degrees(-720).wrapSigned() == Approx(sf::degrees(0))); } SECTION("wrapUnsigned()") { STATIC_CHECK(sf::Angle::Zero.wrapUnsigned() == sf::Angle::Zero); - STATIC_CHECK(sf::degrees(0).wrapUnsigned() == sf::degrees(0)); - STATIC_CHECK(sf::degrees(1).wrapUnsigned() == sf::degrees(1)); - STATIC_CHECK(sf::degrees(-1).wrapUnsigned() == sf::degrees(359)); - STATIC_CHECK(sf::degrees(90).wrapUnsigned() == sf::degrees(90)); - STATIC_CHECK(sf::degrees(-90).wrapUnsigned() == sf::degrees(270)); - STATIC_CHECK(sf::degrees(180).wrapUnsigned() == sf::degrees(180)); - STATIC_CHECK(sf::degrees(-180).wrapUnsigned() == sf::degrees(180)); - STATIC_CHECK(sf::degrees(360).wrapUnsigned() == sf::degrees(0)); - STATIC_CHECK(sf::degrees(-360).wrapUnsigned() == sf::degrees(0)); - STATIC_CHECK(sf::degrees(720).wrapUnsigned() == sf::degrees(0)); - STATIC_CHECK(sf::degrees(-720).wrapUnsigned() == sf::degrees(0)); + CHECK(sf::degrees(0).wrapUnsigned() == Approx(sf::degrees(0))); + CHECK(sf::degrees(1).wrapUnsigned() == Approx(sf::degrees(1))); + CHECK(sf::degrees(-1).wrapUnsigned() == Approx(sf::degrees(359))); + CHECK(sf::degrees(90).wrapUnsigned() == Approx(sf::degrees(90))); + CHECK(sf::degrees(-90).wrapUnsigned() == Approx(sf::degrees(270))); + CHECK(sf::degrees(180).wrapUnsigned() == Approx(sf::degrees(180))); + CHECK(sf::degrees(-180).wrapUnsigned() == Approx(sf::degrees(180))); + CHECK(sf::degrees(360).wrapUnsigned() == Approx(sf::degrees(0))); + CHECK(sf::degrees(-360).wrapUnsigned() == Approx(sf::degrees(0))); + CHECK(sf::degrees(720).wrapUnsigned() == Approx(sf::degrees(0))); + CHECK(sf::degrees(-720).wrapUnsigned() == Approx(sf::degrees(0))); } SECTION("degrees()") @@ -185,7 +185,7 @@ TEST_CASE("[System] sf::Angle") { sf::Angle angle = sf::degrees(-15); angle += sf::degrees(15); - CHECK(angle == sf::degrees(0)); + CHECK(angle == Approx(sf::degrees(0))); angle += sf::radians(10); CHECK(angle == sf::radians(10)); } @@ -202,7 +202,7 @@ TEST_CASE("[System] sf::Angle") { sf::Angle angle = sf::degrees(15); angle -= sf::degrees(15); - CHECK(angle == sf::degrees(0)); + CHECK(angle == Approx(sf::degrees(0))); angle -= sf::radians(10); CHECK(angle == sf::radians(-10)); } @@ -210,19 +210,19 @@ TEST_CASE("[System] sf::Angle") SECTION("operator*") { STATIC_CHECK(sf::radians(0) * 10 == sf::Angle::Zero); - STATIC_CHECK(sf::degrees(10) * 2.5f == sf::degrees(25)); - STATIC_CHECK(sf::degrees(100) * 10.0f == sf::degrees(1000)); + CHECK(sf::degrees(10) * 2.5f == Approx(sf::degrees(25))); + CHECK(sf::degrees(100) * 10.0f == Approx(sf::degrees(1000))); STATIC_CHECK(10 * sf::radians(0) == sf::Angle::Zero); - STATIC_CHECK(2.5f * sf::degrees(10) == sf::degrees(25)); - STATIC_CHECK(10.0f * sf::degrees(100) == sf::degrees(1000)); + CHECK(2.5f * sf::degrees(10) == Approx(sf::degrees(25))); + CHECK(10.0f * sf::degrees(100) == Approx(sf::degrees(1000))); } SECTION("operator*=") { sf::Angle angle = sf::degrees(1); angle *= 10; - CHECK(angle == sf::degrees(10)); + CHECK(angle == Approx(sf::degrees(10))); } SECTION("operator/") @@ -240,24 +240,24 @@ TEST_CASE("[System] sf::Angle") { sf::Angle angle = sf::degrees(60); angle /= 5; - CHECK(angle == sf::degrees(12)); + CHECK(angle == Approx(sf::degrees(12))); } SECTION("operator%") { STATIC_CHECK(sf::Angle::Zero % sf::radians(0.5f) == sf::Angle::Zero); STATIC_CHECK(sf::radians(10) % sf::radians(1) == sf::radians(0)); - STATIC_CHECK(sf::degrees(90) % sf::degrees(30) == sf::degrees(0)); - STATIC_CHECK(sf::degrees(90) % sf::degrees(40) == sf::degrees(10)); - STATIC_CHECK(sf::degrees(-90) % sf::degrees(30) == sf::degrees(0)); - STATIC_CHECK(sf::degrees(-90) % sf::degrees(40) == sf::degrees(30)); + CHECK(sf::degrees(90) % sf::degrees(30) == Approx(sf::degrees(0))); + CHECK(sf::degrees(90) % sf::degrees(40) == Approx(sf::degrees(10))); + CHECK(sf::degrees(-90) % sf::degrees(30) == Approx(sf::degrees(0))); + CHECK(sf::degrees(-90) % sf::degrees(40) == Approx(sf::degrees(30))); } SECTION("operator%=") { sf::Angle angle = sf::degrees(59); angle %= sf::degrees(10); - CHECK(angle == sf::degrees(9)); + CHECK(angle == Approx(sf::degrees(9))); } SECTION("operator _deg") diff --git a/test/TestUtilities/SystemUtil.cpp b/test/TestUtilities/SystemUtil.cpp index 95f5a78c8..c731012ea 100644 --- a/test/TestUtilities/SystemUtil.cpp +++ b/test/TestUtilities/SystemUtil.cpp @@ -77,7 +77,7 @@ bool operator==(const sf::Vector3f& lhs, const Approx& rhs) bool operator==(const sf::Angle& lhs, const Approx& rhs) { - return lhs.asDegrees() == Approx(rhs.value.asDegrees()); + return lhs.asRadians() == Approx(rhs.value.asRadians()); } std::vector loadIntoMemory(const std::filesystem::path& path) From c0f9c7830818a5eb7c485344676db7565d5e3972 Mon Sep 17 00:00:00 2001 From: Chris Thrasher Date: Tue, 14 May 2024 15:53:02 -0600 Subject: [PATCH 31/38] Remove redundant cache var assignment --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10697cc40..3a1e31a75 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,8 +36,8 @@ jobs: - { name: Windows VS2022 Unity, os: windows-2022, flags: -DSFML_USE_MESA3D=TRUE -DCMAKE_UNITY_BUILD=ON -GNinja } - { name: Windows LLVM/Clang, os: windows-2022, flags: -DSFML_USE_MESA3D=TRUE -DCMAKE_CXX_COMPILER=clang++ -GNinja } - { name: Windows MinGW, os: windows-2022, flags: -DSFML_USE_MESA3D=TRUE -DCMAKE_CXX_COMPILER=g++ -GNinja } - - { name: Linux GCC, os: ubuntu-22.04, flags: -DSFML_RUN_DISPLAY_TESTS=ON -GNinja } - - { name: Linux Clang, os: ubuntu-22.04, flags: -DCMAKE_CXX_COMPILER=clang++ -DSFML_RUN_DISPLAY_TESTS=ON -GNinja , gcovr_options: '--gcov-executable="llvm-cov-$CLANG_VERSION gcov"' } + - { name: Linux GCC, os: ubuntu-22.04, flags: -GNinja } + - { name: Linux Clang, os: ubuntu-22.04, flags: -DCMAKE_CXX_COMPILER=clang++ -GNinja , gcovr_options: '--gcov-executable="llvm-cov-$CLANG_VERSION gcov"' } - { name: Linux GCC DRM, os: ubuntu-22.04, flags: -DSFML_USE_DRM=ON -DSFML_RUN_DISPLAY_TESTS=OFF -GNinja } - { name: Linux GCC OpenGL ES, os: ubuntu-22.04, flags: -DSFML_OPENGL_ES=ON -DSFML_RUN_DISPLAY_TESTS=OFF -GNinja } - { name: macOS x64, os: macos-12, flags: -GNinja } @@ -56,9 +56,9 @@ jobs: - platform: { name: Windows VS2022 x64, os: windows-2022 } config: { name: Static with PCH (MSVC), flags: -DSFML_USE_MESA3D=TRUE -GNinja -DBUILD_SHARED_LIBS=FALSE -DSFML_ENABLE_PCH=1 } - platform: { name: Linux GCC, os: ubuntu-22.04 } - config: { name: Static with PCH (GCC), flags: -DSFML_RUN_DISPLAY_TESTS=ON -GNinja -DCMAKE_CXX_COMPILER=g++ -DBUILD_SHARED_LIBS=FALSE -DSFML_ENABLE_PCH=1 } + config: { name: Static with PCH (GCC), flags: -GNinja -DCMAKE_CXX_COMPILER=g++ -DBUILD_SHARED_LIBS=FALSE -DSFML_ENABLE_PCH=1 } - platform: { name: Linux Clang, os: ubuntu-22.04 } - config: { name: Static with PCH (Clang), flags: -DSFML_RUN_DISPLAY_TESTS=ON -GNinja -DCMAKE_CXX_COMPILER=clang++ -DBUILD_SHARED_LIBS=FALSE -DSFML_ENABLE_PCH=1 } + config: { name: Static with PCH (Clang), flags: -GNinja -DCMAKE_CXX_COMPILER=clang++ -DBUILD_SHARED_LIBS=FALSE -DSFML_ENABLE_PCH=1 } - platform: { name: Windows MinGW, os: windows-2022 } config: { name: Static Standard Libraries, flags: -GNinja -DSFML_USE_MESA3D=TRUE -DCMAKE_CXX_COMPILER=g++ -DSFML_USE_STATIC_STD_LIBS=TRUE } - platform: { name: Windows MinGW, os: windows-2022 } @@ -386,7 +386,7 @@ jobs: fail-fast: false matrix: platform: - - { name: Linux, os: ubuntu-22.04, flags: -DSFML_RUN_DISPLAY_TESTS=ON } + - { name: Linux, os: ubuntu-22.04 } - { name: Linux DRM, os: ubuntu-22.04, flags: -DSFML_RUN_DISPLAY_TESTS=OFF -DSFML_USE_DRM=ON } - { name: Linux GCC OpenGL ES, os: ubuntu-22.04, flags: -DSFML_RUN_DISPLAY_TESTS=OFF -DSFML_OPENGL_ES=ON } From 353cd9717f4226dd8b6a206f8b58ac4a64059678 Mon Sep 17 00:00:00 2001 From: Vittorio Romeo Date: Wed, 15 May 2024 00:02:54 +0200 Subject: [PATCH 32/38] Add `` to PCH.hpp --- src/SFML/PCH.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SFML/PCH.hpp b/src/SFML/PCH.hpp index 092a779ba..e75ee2ca8 100644 --- a/src/SFML/PCH.hpp +++ b/src/SFML/PCH.hpp @@ -45,6 +45,7 @@ #include #include +#include #include #include #include From a61eb6aeefe311219072cf17269005b5c3ad0322 Mon Sep 17 00:00:00 2001 From: Vittorio Romeo Date: Wed, 15 May 2024 03:00:19 +0200 Subject: [PATCH 33/38] Do not use 'sf::err' in examples --- examples/sound_effects/SoundEffects.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/sound_effects/SoundEffects.cpp b/examples/sound_effects/SoundEffects.cpp index 77404e4c0..cbbecd31c 100644 --- a/examples/sound_effects/SoundEffects.cpp +++ b/examples/sound_effects/SoundEffects.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -113,7 +114,7 @@ public: // Load the music file if (!m_music.openFromFile(resourcesDir() / "doodle_pop.ogg")) - sf::err() << "Failed to load " << (resourcesDir() / "doodle_pop.ogg").string() << std::endl; + std::cerr << "Failed to load " << (resourcesDir() / "doodle_pop.ogg").string() << std::endl; // Set the music to loop m_music.setLoop(true); @@ -172,7 +173,7 @@ public: { // Load the music file if (!m_music.openFromFile(resourcesDir() / "doodle_pop.ogg")) - sf::err() << "Failed to load " << (resourcesDir() / "doodle_pop.ogg").string() << std::endl; + std::cerr << "Failed to load " << (resourcesDir() / "doodle_pop.ogg").string() << std::endl; // Set the music to loop m_music.setLoop(true); @@ -275,7 +276,7 @@ public: // Load the music file if (!m_music.openFromFile(resourcesDir() / "doodle_pop.ogg")) - sf::err() << "Failed to load " << (resourcesDir() / "doodle_pop.ogg").string() << std::endl; + std::cerr << "Failed to load " << (resourcesDir() / "doodle_pop.ogg").string() << std::endl; // Set the music to loop m_music.setLoop(true); From 9722fb3724a6789c1b9258ade7eb89da59e27597 Mon Sep 17 00:00:00 2001 From: Chris Thrasher Date: Sat, 15 Apr 2023 20:50:24 -0600 Subject: [PATCH 34/38] Add tests for Audio module types --- .github/workflows/ci.yml | 26 ++-- test/Audio/Music.test.cpp | 209 ++++++++++++++++++++++++++++++- test/Audio/Sound.test.cpp | 89 +++++++++++-- test/Audio/SoundBuffer.test.cpp | 149 +++++++++++++++++++++- test/Audio/SoundSource.test.cpp | 136 +++++++++++++++++++- test/Audio/SoundStream.test.cpp | 44 ++++++- test/CMakeLists.txt | 7 ++ test/TestUtilities/AudioUtil.cpp | 12 ++ test/TestUtilities/AudioUtil.hpp | 5 + 9 files changed, 640 insertions(+), 37 deletions(-) create mode 100644 test/TestUtilities/AudioUtil.cpp create mode 100644 test/TestUtilities/AudioUtil.hpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a1e31a75..714826e73 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,13 +36,13 @@ jobs: - { name: Windows VS2022 Unity, os: windows-2022, flags: -DSFML_USE_MESA3D=TRUE -DCMAKE_UNITY_BUILD=ON -GNinja } - { name: Windows LLVM/Clang, os: windows-2022, flags: -DSFML_USE_MESA3D=TRUE -DCMAKE_CXX_COMPILER=clang++ -GNinja } - { name: Windows MinGW, os: windows-2022, flags: -DSFML_USE_MESA3D=TRUE -DCMAKE_CXX_COMPILER=g++ -GNinja } - - { name: Linux GCC, os: ubuntu-22.04, flags: -GNinja } - - { name: Linux Clang, os: ubuntu-22.04, flags: -DCMAKE_CXX_COMPILER=clang++ -GNinja , gcovr_options: '--gcov-executable="llvm-cov-$CLANG_VERSION gcov"' } - - { name: Linux GCC DRM, os: ubuntu-22.04, flags: -DSFML_USE_DRM=ON -DSFML_RUN_DISPLAY_TESTS=OFF -GNinja } - - { name: Linux GCC OpenGL ES, os: ubuntu-22.04, flags: -DSFML_OPENGL_ES=ON -DSFML_RUN_DISPLAY_TESTS=OFF -GNinja } + - { name: Linux GCC, os: ubuntu-22.04, flags: -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF -GNinja } + - { name: Linux Clang, os: ubuntu-22.04, flags: -DCMAKE_CXX_COMPILER=clang++ -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF -GNinja , gcovr_options: '--gcov-executable="llvm-cov-$CLANG_VERSION gcov"' } + - { name: Linux GCC DRM, os: ubuntu-22.04, flags: -DSFML_USE_DRM=ON -DSFML_RUN_DISPLAY_TESTS=OFF -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF -GNinja } + - { name: Linux GCC OpenGL ES, os: ubuntu-22.04, flags: -DSFML_OPENGL_ES=ON -DSFML_RUN_DISPLAY_TESTS=OFF -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF -GNinja } - { name: macOS x64, os: macos-12, flags: -GNinja } - { name: macOS x64 Xcode, os: macos-12, flags: -GXcode } - - { name: macOS arm64, os: macos-14, flags: -GNinja } + - { name: macOS arm64, os: macos-14, flags: -GNinja -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF } - { name: iOS, os: macos-12, flags: -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_ARCHITECTURES=arm64 } - { name: iOS Xcode, os: macos-12, flags: -DCMAKE_SYSTEM_NAME=iOS -GXcode -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=NO } config: @@ -56,9 +56,9 @@ jobs: - platform: { name: Windows VS2022 x64, os: windows-2022 } config: { name: Static with PCH (MSVC), flags: -DSFML_USE_MESA3D=TRUE -GNinja -DBUILD_SHARED_LIBS=FALSE -DSFML_ENABLE_PCH=1 } - platform: { name: Linux GCC, os: ubuntu-22.04 } - config: { name: Static with PCH (GCC), flags: -GNinja -DCMAKE_CXX_COMPILER=g++ -DBUILD_SHARED_LIBS=FALSE -DSFML_ENABLE_PCH=1 } + config: { name: Static with PCH (GCC), flags: -GNinja -DCMAKE_CXX_COMPILER=g++ -DBUILD_SHARED_LIBS=FALSE -DSFML_ENABLE_PCH=1 -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF } - platform: { name: Linux Clang, os: ubuntu-22.04 } - config: { name: Static with PCH (Clang), flags: -GNinja -DCMAKE_CXX_COMPILER=clang++ -DBUILD_SHARED_LIBS=FALSE -DSFML_ENABLE_PCH=1 } + config: { name: Static with PCH (Clang), flags: -GNinja -DCMAKE_CXX_COMPILER=clang++ -DBUILD_SHARED_LIBS=FALSE -DSFML_ENABLE_PCH=1 -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF } - platform: { name: Windows MinGW, os: windows-2022 } config: { name: Static Standard Libraries, flags: -GNinja -DSFML_USE_MESA3D=TRUE -DCMAKE_CXX_COMPILER=g++ -DSFML_USE_STATIC_STD_LIBS=TRUE } - platform: { name: Windows MinGW, os: windows-2022 } @@ -70,7 +70,7 @@ jobs: - platform: { name: Android, os: ubuntu-latest } config: name: x86 (API 21) - flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=x86 -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=21 -DCMAKE_ANDROID_NDK=$ANDROID_NDK_ROOT -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared -DSFML_RUN_DISPLAY_TESTS=OFF + flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=x86 -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=21 -DCMAKE_ANDROID_NDK=$ANDROID_NDK_ROOT -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared -DSFML_RUN_DISPLAY_TESTS=OFF -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF arch: x86 api: 21 libcxx: i686-linux-android/libc++_shared.so @@ -80,7 +80,7 @@ jobs: - platform: { name: Android, os: ubuntu-latest } config: name: x86_64 (API 24) - flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=x86_64 -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=24 -DCMAKE_ANDROID_NDK=$ANDROID_NDK_ROOT -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared -DSFML_RUN_DISPLAY_TESTS=OFF + flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=x86_64 -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=24 -DCMAKE_ANDROID_NDK=$ANDROID_NDK_ROOT -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared -DSFML_RUN_DISPLAY_TESTS=OFF -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF arch: x86_64 api: 24 libcxx: x86_64-linux-android/libc++_shared.so @@ -98,7 +98,7 @@ jobs: - platform: { name: Android, os: ubuntu-latest } config: name: arm64-v8a (API 33) - flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=arm64-v8a -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=33 -DCMAKE_ANDROID_NDK=$ANDROID_NDK_ROOT -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared -DSFML_RUN_DISPLAY_TESTS=OFF + flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=arm64-v8a -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=33 -DCMAKE_ANDROID_NDK=$ANDROID_NDK_ROOT -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared -DSFML_RUN_DISPLAY_TESTS=OFF -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF arch: arm64-v8a api: 33 libcxx: aarch64-linux-android/libc++_shared.so @@ -386,9 +386,9 @@ jobs: fail-fast: false matrix: platform: - - { name: Linux, os: ubuntu-22.04 } - - { name: Linux DRM, os: ubuntu-22.04, flags: -DSFML_RUN_DISPLAY_TESTS=OFF -DSFML_USE_DRM=ON } - - { name: Linux GCC OpenGL ES, os: ubuntu-22.04, flags: -DSFML_RUN_DISPLAY_TESTS=OFF -DSFML_OPENGL_ES=ON } + - { name: Linux, os: ubuntu-22.04, flags: -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF } + - { name: Linux DRM, os: ubuntu-22.04, flags: -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF -DSFML_RUN_DISPLAY_TESTS=OFF -DSFML_USE_DRM=ON } + - { name: Linux GCC OpenGL ES, os: ubuntu-22.04, flags: -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF -DSFML_RUN_DISPLAY_TESTS=OFF -DSFML_OPENGL_ES=ON } steps: - name: Checkout Code diff --git a/test/Audio/Music.test.cpp b/test/Audio/Music.test.cpp index 150771a1f..cf29595f0 100644 --- a/test/Audio/Music.test.cpp +++ b/test/Audio/Music.test.cpp @@ -1,8 +1,209 @@ #include +// Other 1st party headers +#include + +#include + +#include +#include +#include +#include +#include #include -static_assert(!std::is_copy_constructible_v); -static_assert(!std::is_copy_assignable_v); -static_assert(!std::is_nothrow_move_constructible_v); -static_assert(!std::is_nothrow_move_assignable_v); +TEST_CASE("[Audio] sf::Music", runAudioDeviceTests()) +{ + SECTION("Type traits") + { + STATIC_CHECK(!std::is_copy_constructible_v); + STATIC_CHECK(!std::is_copy_assignable_v); + STATIC_CHECK(!std::is_nothrow_move_constructible_v); + STATIC_CHECK(!std::is_nothrow_move_assignable_v); + STATIC_CHECK(std::has_virtual_destructor_v); + } + + SECTION("Span") + { + const sf::Music::Span span; + CHECK(span.offset == 0); + CHECK(span.length == 0); + + const sf::Music::TimeSpan timeSpan; + CHECK(timeSpan.offset == sf::Time::Zero); + CHECK(timeSpan.length == sf::Time::Zero); + } + + SECTION("Construction") + { + const sf::Music music; + CHECK(music.getDuration() == sf::Time::Zero); + const auto [offset, length] = music.getLoopPoints(); + CHECK(offset == sf::Time::Zero); + CHECK(length == sf::Time::Zero); + CHECK(music.getChannelCount() == 0); + CHECK(music.getSampleRate() == 0); + CHECK(music.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(music.getPlayingOffset() == sf::Time::Zero); + CHECK(!music.getLoop()); + } + + SECTION("openFromFile()") + { + sf::Music music; + + SECTION("Invalid file") + { + REQUIRE(!music.openFromFile("does/not/exist.wav")); + CHECK(music.getDuration() == sf::Time::Zero); + const auto [offset, length] = music.getLoopPoints(); + CHECK(offset == sf::Time::Zero); + CHECK(length == sf::Time::Zero); + CHECK(music.getChannelCount() == 0); + CHECK(music.getSampleRate() == 0); + CHECK(music.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(music.getPlayingOffset() == sf::Time::Zero); + CHECK(!music.getLoop()); + } + + SECTION("Valid file") + { + REQUIRE(music.openFromFile("Audio/ding.mp3")); + CHECK(music.getDuration() == sf::microseconds(1990884)); + const auto [offset, length] = music.getLoopPoints(); + CHECK(offset == sf::Time::Zero); + CHECK(length == sf::microseconds(1990884)); + CHECK(music.getChannelCount() == 1); + CHECK(music.getSampleRate() == 44100); + CHECK(music.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(music.getPlayingOffset() == sf::Time::Zero); + CHECK(!music.getLoop()); + } + } + + SECTION("openFromMemory()") + { + std::vector memory; + sf::Music music; + + SECTION("Invalid buffer") + { + REQUIRE(!music.openFromMemory(memory.data(), memory.size())); + CHECK(music.getDuration() == sf::Time::Zero); + const auto [offset, length] = music.getLoopPoints(); + CHECK(offset == sf::Time::Zero); + CHECK(length == sf::Time::Zero); + CHECK(music.getChannelCount() == 0); + CHECK(music.getSampleRate() == 0); + CHECK(music.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(music.getPlayingOffset() == sf::Time::Zero); + CHECK(!music.getLoop()); + } + + SECTION("Valid buffer") + { + memory = loadIntoMemory("Audio/ding.flac"); + REQUIRE(music.openFromMemory(memory.data(), memory.size())); + CHECK(music.getDuration() == sf::microseconds(1990884)); + const auto [offset, length] = music.getLoopPoints(); + CHECK(offset == sf::Time::Zero); + CHECK(length == sf::microseconds(1990884)); + CHECK(music.getChannelCount() == 1); + CHECK(music.getSampleRate() == 44100); + CHECK(music.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(music.getPlayingOffset() == sf::Time::Zero); + CHECK(!music.getLoop()); + } + } + + SECTION("openFromStream()") + { + sf::FileInputStream stream; + sf::Music music; + + SECTION("Invalid stream") + { + CHECK(!music.openFromStream(stream)); + CHECK(music.getDuration() == sf::Time::Zero); + const auto [offset, length] = music.getLoopPoints(); + CHECK(offset == sf::Time::Zero); + CHECK(length == sf::Time::Zero); + CHECK(music.getChannelCount() == 0); + CHECK(music.getSampleRate() == 0); + CHECK(music.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(music.getPlayingOffset() == sf::Time::Zero); + CHECK(!music.getLoop()); + } + + SECTION("Valid stream") + { + REQUIRE(stream.open("Audio/doodle_pop.ogg")); + REQUIRE(music.openFromStream(stream)); + CHECK(music.getDuration() == sf::microseconds(24002176)); + const auto [offset, length] = music.getLoopPoints(); + CHECK(offset == sf::Time::Zero); + CHECK(length == sf::microseconds(24002176)); + CHECK(music.getChannelCount() == 2); + CHECK(music.getSampleRate() == 44100); + CHECK(music.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(music.getPlayingOffset() == sf::Time::Zero); + CHECK(!music.getLoop()); + } + } + + SECTION("play/pause/stop") + { + sf::Music music; + REQUIRE(music.openFromFile("Audio/ding.mp3")); + + // Wait for background thread to start + music.play(); + while (music.getStatus() == sf::SoundSource::Status::Stopped) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + CHECK(music.getStatus() == sf::SoundSource::Status::Playing); + + // Wait for background thread to pause + music.pause(); + while (music.getStatus() == sf::SoundSource::Status::Playing) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + CHECK(music.getStatus() == sf::SoundSource::Status::Paused); + + // Wait for background thread to stop + music.stop(); + while (music.getStatus() == sf::SoundSource::Status::Paused) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + CHECK(music.getStatus() == sf::SoundSource::Status::Stopped); + } + + SECTION("setLoopPoints()") + { + sf::Music music; + + SECTION("No file") + { + music.setLoopPoints({sf::Time::Zero, sf::Time::Zero}); + const auto [offset, length] = music.getLoopPoints(); + CHECK(offset == sf::Time::Zero); + CHECK(length == sf::Time::Zero); + CHECK(music.getChannelCount() == 0); + CHECK(music.getSampleRate() == 0); + CHECK(music.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(music.getPlayingOffset() == sf::Time::Zero); + CHECK(!music.getLoop()); + } + + SECTION("Loaded file") + { + REQUIRE(music.openFromFile("Audio/killdeer.wav")); + music.setLoopPoints({sf::seconds(1), sf::seconds(2)}); + const auto [offset, length] = music.getLoopPoints(); + CHECK(offset == sf::seconds(1)); + CHECK(length == sf::seconds(2)); + CHECK(music.getChannelCount() == 1); + CHECK(music.getSampleRate() == 22050); + CHECK(music.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(music.getPlayingOffset() == sf::Time::Zero); + CHECK(!music.getLoop()); + } + } +} diff --git a/test/Audio/Sound.test.cpp b/test/Audio/Sound.test.cpp index f0944d743..e97006522 100644 --- a/test/Audio/Sound.test.cpp +++ b/test/Audio/Sound.test.cpp @@ -1,11 +1,86 @@ #include +// Other 1st party headers +#include + +#include + +#include + +#include +#include #include -static_assert(!std::is_constructible_v); -static_assert(std::is_copy_constructible_v); -static_assert(std::is_copy_assignable_v); -static_assert(std::is_move_constructible_v); -static_assert(!std::is_nothrow_move_constructible_v); -static_assert(std::is_move_assignable_v); -static_assert(!std::is_nothrow_move_assignable_v); +TEST_CASE("[Audio] sf::Sound", runAudioDeviceTests()) +{ + SECTION("Type traits") + { + STATIC_CHECK(!std::is_constructible_v); + STATIC_CHECK(std::is_copy_constructible_v); + STATIC_CHECK(std::is_copy_assignable_v); + STATIC_CHECK(std::is_move_constructible_v); + STATIC_CHECK(!std::is_nothrow_move_constructible_v); + STATIC_CHECK(std::is_move_assignable_v); + STATIC_CHECK(!std::is_nothrow_move_assignable_v); + STATIC_CHECK(std::has_virtual_destructor_v); + } + + sf::SoundBuffer soundBuffer; + REQUIRE(soundBuffer.loadFromFile("Audio/ding.flac")); + + SECTION("Construction") + { + const sf::Sound sound(soundBuffer); + CHECK(&sound.getBuffer() == &soundBuffer); + CHECK(!sound.getLoop()); + CHECK(sound.getPlayingOffset() == sf::Time::Zero); + CHECK(sound.getStatus() == sf::SoundSource::Status::Stopped); + } + + SECTION("Copy semantics") + { + const sf::Sound sound(soundBuffer); + + SECTION("Construction") + { + const sf::Sound soundCopy(sound); // NOLINT(performance-unnecessary-copy-initialization) + CHECK(&soundCopy.getBuffer() == &soundBuffer); + CHECK(!soundCopy.getLoop()); + CHECK(soundCopy.getPlayingOffset() == sf::Time::Zero); + CHECK(soundCopy.getStatus() == sf::SoundSource::Status::Stopped); + } + + SECTION("Assignment") + { + const sf::SoundBuffer emptySoundBuffer; + sf::Sound soundCopy(emptySoundBuffer); + soundCopy = sound; + CHECK(&soundCopy.getBuffer() == &soundBuffer); + CHECK(!soundCopy.getLoop()); + CHECK(soundCopy.getPlayingOffset() == sf::Time::Zero); + CHECK(soundCopy.getStatus() == sf::SoundSource::Status::Stopped); + } + } + + SECTION("Set/get buffer") + { + const sf::SoundBuffer otherSoundBuffer; + sf::Sound sound(soundBuffer); + sound.setBuffer(otherSoundBuffer); + CHECK(&sound.getBuffer() == &otherSoundBuffer); + } + + SECTION("Set/get loop") + { + sf::Sound sound(soundBuffer); + sound.setLoop(true); + CHECK(sound.getLoop()); + } + + SECTION("Set/get playing offset") + { + sf::Sound sound(soundBuffer); + sound.setPlayingOffset(sf::seconds(10)); + CHECK(sound.getPlayingOffset() == sf::seconds(10)); + } +} diff --git a/test/Audio/SoundBuffer.test.cpp b/test/Audio/SoundBuffer.test.cpp index c62ea06db..a907f2470 100644 --- a/test/Audio/SoundBuffer.test.cpp +++ b/test/Audio/SoundBuffer.test.cpp @@ -1,10 +1,147 @@ #include +// Other 1st party headers +#include + +#include + +#include +#include +#include #include -static_assert(std::is_copy_constructible_v); -static_assert(std::is_copy_assignable_v); -static_assert(std::is_move_constructible_v); -static_assert(!std::is_nothrow_move_constructible_v); -static_assert(std::is_move_assignable_v); -static_assert(!std::is_nothrow_move_assignable_v); +TEST_CASE("[Audio] sf::SoundBuffer", runAudioDeviceTests()) +{ + SECTION("Type traits") + { + STATIC_CHECK(std::is_copy_constructible_v); + STATIC_CHECK(std::is_copy_assignable_v); + STATIC_CHECK(std::is_move_constructible_v); + STATIC_CHECK(!std::is_nothrow_move_constructible_v); + STATIC_CHECK(std::is_move_assignable_v); + STATIC_CHECK(!std::is_nothrow_move_assignable_v); + } + + SECTION("Construction") + { + const sf::SoundBuffer soundBuffer; + CHECK(soundBuffer.getSamples() == nullptr); + CHECK(soundBuffer.getSampleCount() == 0); + CHECK(soundBuffer.getSampleRate() == 44100); + CHECK(soundBuffer.getChannelCount() == 1); + CHECK(soundBuffer.getDuration() == sf::Time::Zero); + } + + SECTION("Copy semantics") + { + sf::SoundBuffer soundBuffer; + REQUIRE(soundBuffer.loadFromFile("Audio/ding.flac")); + + SECTION("Construction") + { + const sf::SoundBuffer soundBufferCopy(soundBuffer); // NOLINT(performance-unnecessary-copy-initialization) + CHECK(soundBufferCopy.getSamples() != nullptr); + CHECK(soundBufferCopy.getSampleCount() == 87798); + CHECK(soundBufferCopy.getSampleRate() == 44100); + CHECK(soundBufferCopy.getChannelCount() == 1); + CHECK(soundBufferCopy.getDuration() == sf::microseconds(1990884)); + } + + SECTION("Assignment") + { + sf::SoundBuffer soundBufferCopy; + soundBufferCopy = soundBuffer; + CHECK(soundBufferCopy.getSamples() != nullptr); + CHECK(soundBufferCopy.getSampleCount() == 87798); + CHECK(soundBufferCopy.getSampleRate() == 44100); + CHECK(soundBufferCopy.getChannelCount() == 1); + CHECK(soundBufferCopy.getDuration() == sf::microseconds(1990884)); + } + } + + SECTION("loadFromFile()") + { + sf::SoundBuffer soundBuffer; + + SECTION("Invalid filename") + { + CHECK(!soundBuffer.loadFromFile("does/not/exist.wav")); + } + + SECTION("Valid file") + { + REQUIRE(soundBuffer.loadFromFile("Audio/ding.flac")); + CHECK(soundBuffer.getSamples() != nullptr); + CHECK(soundBuffer.getSampleCount() == 87798); + CHECK(soundBuffer.getSampleRate() == 44100); + CHECK(soundBuffer.getChannelCount() == 1); + CHECK(soundBuffer.getDuration() == sf::microseconds(1990884)); + } + } + + SECTION("loadFromMemory()") + { + sf::SoundBuffer soundBuffer; + + SECTION("Invalid memory") + { + CHECK(!soundBuffer.loadFromMemory(nullptr, 0)); + constexpr std::array memory{}; + CHECK(!soundBuffer.loadFromMemory(memory.data(), memory.size())); + } + + SECTION("Valid memory") + { + const auto memory = loadIntoMemory("Audio/ding.flac"); + REQUIRE(soundBuffer.loadFromMemory(memory.data(), memory.size())); + CHECK(soundBuffer.getSamples() != nullptr); + CHECK(soundBuffer.getSampleCount() == 87798); + CHECK(soundBuffer.getSampleRate() == 44100); + CHECK(soundBuffer.getChannelCount() == 1); + CHECK(soundBuffer.getDuration() == sf::microseconds(1990884)); + } + } + + SECTION("loadFromStream()") + { + sf::FileInputStream stream; + sf::SoundBuffer soundBuffer; + + SECTION("Invalid stream") + { + CHECK(!soundBuffer.loadFromStream(stream)); + } + + SECTION("Valid stream") + { + REQUIRE(stream.open("Audio/ding.flac")); + REQUIRE(soundBuffer.loadFromStream(stream)); + CHECK(soundBuffer.getSamples() != nullptr); + CHECK(soundBuffer.getSampleCount() == 87798); + CHECK(soundBuffer.getSampleRate() == 44100); + CHECK(soundBuffer.getChannelCount() == 1); + CHECK(soundBuffer.getDuration() == sf::microseconds(1990884)); + } + } + + SECTION("saveToFile()") + { + const auto filename = std::filesystem::temp_directory_path() / "ding.flac"; + + { + sf::SoundBuffer soundBuffer; + REQUIRE(soundBuffer.loadFromFile("Audio/ding.flac")); + REQUIRE(soundBuffer.saveToFile(filename)); + } + + sf::SoundBuffer soundBuffer; + REQUIRE(soundBuffer.loadFromFile(filename)); + CHECK(soundBuffer.getSamples() != nullptr); + CHECK(soundBuffer.getSampleCount() == 87798); + CHECK(soundBuffer.getSampleRate() == 44100); + CHECK(soundBuffer.getChannelCount() == 1); + CHECK(soundBuffer.getDuration() == sf::microseconds(1990884)); + + CHECK(std::filesystem::remove(filename)); + } +} diff --git a/test/Audio/SoundSource.test.cpp b/test/Audio/SoundSource.test.cpp index 4804e6781..8fafb31dc 100644 --- a/test/Audio/SoundSource.test.cpp +++ b/test/Audio/SoundSource.test.cpp @@ -1,10 +1,134 @@ #include +#include + +#include +#include #include -static_assert(!std::is_constructible_v); -static_assert(!std::is_copy_constructible_v); -static_assert(std::is_copy_assignable_v); -static_assert(!std::is_move_constructible_v); -static_assert(std::is_move_assignable_v); -static_assert(!std::is_nothrow_move_assignable_v); +namespace +{ +class SoundSource : public sf::SoundSource +{ + void play() override + { + } + + void pause() override + { + } + + void stop() override + { + } + + void* getSound() const override + { + return {}; + } + +public: + Status getStatus() const override + { + return {}; + } +}; +} // namespace + +TEST_CASE("[Audio] sf::SoundSource", runAudioDeviceTests()) +{ + SECTION("Type traits") + { + STATIC_CHECK(!std::is_constructible_v); + STATIC_CHECK(!std::is_copy_constructible_v); + STATIC_CHECK(std::is_copy_assignable_v); + STATIC_CHECK(!std::is_move_constructible_v); + STATIC_CHECK(std::is_move_assignable_v); + STATIC_CHECK(!std::is_nothrow_move_assignable_v); + STATIC_CHECK(std::has_virtual_destructor_v); + } + + SECTION("Construction") + { + const SoundSource soundSource; + CHECK(soundSource.getPitch() == 0); + CHECK(soundSource.getVolume() == 0); + CHECK(soundSource.getPosition() == sf::Vector3f()); + CHECK(!soundSource.isRelativeToListener()); + CHECK(soundSource.getMinDistance() == 0); + CHECK(soundSource.getAttenuation() == 0); + CHECK(soundSource.getStatus() == sf::SoundSource::Status::Stopped); + } + + SECTION("Copy semantics") + { + const SoundSource soundSource; + + SECTION("Construction") + { + const SoundSource soundSourceCopy(soundSource); // NOLINT(performance-unnecessary-copy-initialization) + CHECK(soundSourceCopy.getPitch() == 0); + CHECK(soundSourceCopy.getVolume() == 0); + CHECK(soundSourceCopy.getPosition() == sf::Vector3f()); + CHECK(!soundSourceCopy.isRelativeToListener()); + CHECK(soundSourceCopy.getMinDistance() == 0); + CHECK(soundSourceCopy.getAttenuation() == 0); + CHECK(soundSourceCopy.getStatus() == sf::SoundSource::Status::Stopped); + } + + SECTION("Assignment") + { + SoundSource soundSourceCopy; + soundSourceCopy = soundSource; + CHECK(soundSourceCopy.getPitch() == 0); + CHECK(soundSourceCopy.getVolume() == 0); + CHECK(soundSourceCopy.getPosition() == sf::Vector3f()); + CHECK(!soundSourceCopy.isRelativeToListener()); + CHECK(soundSourceCopy.getMinDistance() == 0); + CHECK(soundSourceCopy.getAttenuation() == 0); + CHECK(soundSourceCopy.getStatus() == sf::SoundSource::Status::Stopped); + } + } + + SECTION("Set/get pitch") + { + SoundSource soundSource; + soundSource.setPitch(42); + CHECK(soundSource.getPitch() == 0); + } + + SECTION("Set/get volume") + { + SoundSource soundSource; + soundSource.setVolume(0.5f); + CHECK(soundSource.getVolume() == 0); + } + + SECTION("Set/get position") + { + SoundSource soundSource; + soundSource.setPosition({1, 2, 3}); + CHECK(soundSource.getPosition() == sf::Vector3f()); + } + + SECTION("Set/get relative to listener") + { + SoundSource soundSource; + soundSource.setRelativeToListener(true); + CHECK(!soundSource.isRelativeToListener()); + } + + SECTION("Set/get min distance") + { + SoundSource soundSource; + soundSource.setMinDistance(12.34f); + CHECK(soundSource.getMinDistance() == 0); + } + + SECTION("Set/get attenuation") + { + SoundSource soundSource; + soundSource.setAttenuation(10); + CHECK(soundSource.getAttenuation() == 0); + } +} diff --git a/test/Audio/SoundStream.test.cpp b/test/Audio/SoundStream.test.cpp index aca5e5525..bc89d72f6 100644 --- a/test/Audio/SoundStream.test.cpp +++ b/test/Audio/SoundStream.test.cpp @@ -2,9 +2,26 @@ #include +#include +#include #include -TEST_CASE("[Audio] sf::SoundStream") +namespace +{ +class SoundStream : public sf::SoundStream +{ + [[nodiscard]] bool onGetData(Chunk& /* data */) override + { + return true; + } + + void onSeek(sf::Time /* timeOffset */) override + { + } +}; +} // namespace + +TEST_CASE("[Audio] sf::SoundStream", runAudioDeviceTests()) { SECTION("Type traits") { @@ -13,6 +30,7 @@ TEST_CASE("[Audio] sf::SoundStream") STATIC_CHECK(!std::is_copy_assignable_v); STATIC_CHECK(!std::is_nothrow_move_constructible_v); STATIC_CHECK(!std::is_nothrow_move_assignable_v); + STATIC_CHECK(std::has_virtual_destructor_v); } SECTION("Chunk") @@ -21,4 +39,28 @@ TEST_CASE("[Audio] sf::SoundStream") CHECK(chunk.samples == nullptr); CHECK(chunk.sampleCount == 0); } + + SECTION("Construction") + { + const SoundStream soundStream; + CHECK(soundStream.getChannelCount() == 0); + CHECK(soundStream.getSampleRate() == 0); + CHECK(soundStream.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(soundStream.getPlayingOffset() == sf::Time::Zero); + CHECK(!soundStream.getLoop()); + } + + SECTION("Set/get playing offset") + { + SoundStream soundStream; + soundStream.setPlayingOffset(sf::milliseconds(100)); + CHECK(soundStream.getPlayingOffset() == sf::milliseconds(0)); + } + + SECTION("Set/get loop") + { + SoundStream soundStream; + soundStream.setLoop(true); + CHECK(soundStream.getLoop()); + } } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 36111b233..563d6d6c2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -28,6 +28,8 @@ add_library(sfml-test-main STATIC TestUtilities/WindowUtil.cpp TestUtilities/GraphicsUtil.hpp TestUtilities/GraphicsUtil.cpp + TestUtilities/AudioUtil.hpp + TestUtilities/AudioUtil.cpp ) target_include_directories(sfml-test-main PUBLIC TestUtilities) target_link_libraries(sfml-test-main PUBLIC SFML::System Catch2::Catch2WithMain) @@ -43,6 +45,11 @@ if(SFML_RUN_DISPLAY_TESTS) target_compile_definitions(sfml-test-main PRIVATE SFML_RUN_DISPLAY_TESTS) endif() +sfml_set_option(SFML_RUN_AUDIO_DEVICE_TESTS ON BOOL "TRUE to run tests that require an audio device, FALSE to ignore it") +if(SFML_RUN_AUDIO_DEVICE_TESTS) + target_compile_definitions(sfml-test-main PRIVATE SFML_RUN_AUDIO_DEVICE_TESTS) +endif() + set(SYSTEM_SRC System/Angle.test.cpp System/Clock.test.cpp diff --git a/test/TestUtilities/AudioUtil.cpp b/test/TestUtilities/AudioUtil.cpp new file mode 100644 index 000000000..30b4f50ca --- /dev/null +++ b/test/TestUtilities/AudioUtil.cpp @@ -0,0 +1,12 @@ +#include + +std::string runAudioDeviceTests() +{ +#ifdef SFML_RUN_AUDIO_DEVICE_TESTS + return ""; +#else + // https://github.com/catchorg/Catch2/blob/devel/docs/test-cases-and-sections.md#special-tags + // This tag tells Catch2 to not run a given TEST_CASE + return "[.audio_device]"; +#endif +} diff --git a/test/TestUtilities/AudioUtil.hpp b/test/TestUtilities/AudioUtil.hpp new file mode 100644 index 000000000..c4d759c39 --- /dev/null +++ b/test/TestUtilities/AudioUtil.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +[[nodiscard]] std::string runAudioDeviceTests(); From 6766268f363bee92b81c3ec2d0d9da8a148fe69d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20D=C3=BCrrenberger?= Date: Thu, 16 May 2024 20:57:40 +0200 Subject: [PATCH 35/38] Use the SoundSource derived type specific status --- test/Audio/Music.test.cpp | 30 +++++++++++++++--------------- test/Audio/Sound.test.cpp | 6 +++--- test/Audio/SoundStream.test.cpp | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/test/Audio/Music.test.cpp b/test/Audio/Music.test.cpp index cf29595f0..68bd03a4d 100644 --- a/test/Audio/Music.test.cpp +++ b/test/Audio/Music.test.cpp @@ -43,7 +43,7 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests()) CHECK(length == sf::Time::Zero); CHECK(music.getChannelCount() == 0); CHECK(music.getSampleRate() == 0); - CHECK(music.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(music.getStatus() == sf::Music::Status::Stopped); CHECK(music.getPlayingOffset() == sf::Time::Zero); CHECK(!music.getLoop()); } @@ -61,7 +61,7 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests()) CHECK(length == sf::Time::Zero); CHECK(music.getChannelCount() == 0); CHECK(music.getSampleRate() == 0); - CHECK(music.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(music.getStatus() == sf::Music::Status::Stopped); CHECK(music.getPlayingOffset() == sf::Time::Zero); CHECK(!music.getLoop()); } @@ -75,7 +75,7 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests()) CHECK(length == sf::microseconds(1990884)); CHECK(music.getChannelCount() == 1); CHECK(music.getSampleRate() == 44100); - CHECK(music.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(music.getStatus() == sf::Music::Status::Stopped); CHECK(music.getPlayingOffset() == sf::Time::Zero); CHECK(!music.getLoop()); } @@ -95,7 +95,7 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests()) CHECK(length == sf::Time::Zero); CHECK(music.getChannelCount() == 0); CHECK(music.getSampleRate() == 0); - CHECK(music.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(music.getStatus() == sf::Music::Status::Stopped); CHECK(music.getPlayingOffset() == sf::Time::Zero); CHECK(!music.getLoop()); } @@ -110,7 +110,7 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests()) CHECK(length == sf::microseconds(1990884)); CHECK(music.getChannelCount() == 1); CHECK(music.getSampleRate() == 44100); - CHECK(music.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(music.getStatus() == sf::Music::Status::Stopped); CHECK(music.getPlayingOffset() == sf::Time::Zero); CHECK(!music.getLoop()); } @@ -130,7 +130,7 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests()) CHECK(length == sf::Time::Zero); CHECK(music.getChannelCount() == 0); CHECK(music.getSampleRate() == 0); - CHECK(music.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(music.getStatus() == sf::Music::Status::Stopped); CHECK(music.getPlayingOffset() == sf::Time::Zero); CHECK(!music.getLoop()); } @@ -145,7 +145,7 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests()) CHECK(length == sf::microseconds(24002176)); CHECK(music.getChannelCount() == 2); CHECK(music.getSampleRate() == 44100); - CHECK(music.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(music.getStatus() == sf::Music::Status::Stopped); CHECK(music.getPlayingOffset() == sf::Time::Zero); CHECK(!music.getLoop()); } @@ -158,21 +158,21 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests()) // Wait for background thread to start music.play(); - while (music.getStatus() == sf::SoundSource::Status::Stopped) + while (music.getStatus() == sf::Music::Status::Stopped) std::this_thread::sleep_for(std::chrono::milliseconds(10)); - CHECK(music.getStatus() == sf::SoundSource::Status::Playing); + CHECK(music.getStatus() == sf::Music::Status::Playing); // Wait for background thread to pause music.pause(); - while (music.getStatus() == sf::SoundSource::Status::Playing) + while (music.getStatus() == sf::Music::Status::Playing) std::this_thread::sleep_for(std::chrono::milliseconds(10)); - CHECK(music.getStatus() == sf::SoundSource::Status::Paused); + CHECK(music.getStatus() == sf::Music::Status::Paused); // Wait for background thread to stop music.stop(); - while (music.getStatus() == sf::SoundSource::Status::Paused) + while (music.getStatus() == sf::Music::Status::Paused) std::this_thread::sleep_for(std::chrono::milliseconds(10)); - CHECK(music.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(music.getStatus() == sf::Music::Status::Stopped); } SECTION("setLoopPoints()") @@ -187,7 +187,7 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests()) CHECK(length == sf::Time::Zero); CHECK(music.getChannelCount() == 0); CHECK(music.getSampleRate() == 0); - CHECK(music.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(music.getStatus() == sf::Music::Status::Stopped); CHECK(music.getPlayingOffset() == sf::Time::Zero); CHECK(!music.getLoop()); } @@ -201,7 +201,7 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests()) CHECK(length == sf::seconds(2)); CHECK(music.getChannelCount() == 1); CHECK(music.getSampleRate() == 22050); - CHECK(music.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(music.getStatus() == sf::Music::Status::Stopped); CHECK(music.getPlayingOffset() == sf::Time::Zero); CHECK(!music.getLoop()); } diff --git a/test/Audio/Sound.test.cpp b/test/Audio/Sound.test.cpp index e97006522..c650f1f16 100644 --- a/test/Audio/Sound.test.cpp +++ b/test/Audio/Sound.test.cpp @@ -34,7 +34,7 @@ TEST_CASE("[Audio] sf::Sound", runAudioDeviceTests()) CHECK(&sound.getBuffer() == &soundBuffer); CHECK(!sound.getLoop()); CHECK(sound.getPlayingOffset() == sf::Time::Zero); - CHECK(sound.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(sound.getStatus() == sf::Sound::Status::Stopped); } SECTION("Copy semantics") @@ -47,7 +47,7 @@ TEST_CASE("[Audio] sf::Sound", runAudioDeviceTests()) CHECK(&soundCopy.getBuffer() == &soundBuffer); CHECK(!soundCopy.getLoop()); CHECK(soundCopy.getPlayingOffset() == sf::Time::Zero); - CHECK(soundCopy.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(soundCopy.getStatus() == sf::Sound::Status::Stopped); } SECTION("Assignment") @@ -58,7 +58,7 @@ TEST_CASE("[Audio] sf::Sound", runAudioDeviceTests()) CHECK(&soundCopy.getBuffer() == &soundBuffer); CHECK(!soundCopy.getLoop()); CHECK(soundCopy.getPlayingOffset() == sf::Time::Zero); - CHECK(soundCopy.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(soundCopy.getStatus() == sf::Sound::Status::Stopped); } } diff --git a/test/Audio/SoundStream.test.cpp b/test/Audio/SoundStream.test.cpp index bc89d72f6..42d04897a 100644 --- a/test/Audio/SoundStream.test.cpp +++ b/test/Audio/SoundStream.test.cpp @@ -45,7 +45,7 @@ TEST_CASE("[Audio] sf::SoundStream", runAudioDeviceTests()) const SoundStream soundStream; CHECK(soundStream.getChannelCount() == 0); CHECK(soundStream.getSampleRate() == 0); - CHECK(soundStream.getStatus() == sf::SoundSource::Status::Stopped); + CHECK(soundStream.getStatus() == sf::SoundStream::Status::Stopped); CHECK(soundStream.getPlayingOffset() == sf::Time::Zero); CHECK(!soundStream.getLoop()); } From c89c32d7baa15927a39a161a4ead867c11d5ea21 Mon Sep 17 00:00:00 2001 From: Chris Thrasher Date: Sat, 15 Apr 2023 20:50:24 -0600 Subject: [PATCH 36/38] Allow more GitHub Actions runners to run the audio tests. --- .github/workflows/ci.yml | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 714826e73..ef3548170 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,10 +36,10 @@ jobs: - { name: Windows VS2022 Unity, os: windows-2022, flags: -DSFML_USE_MESA3D=TRUE -DCMAKE_UNITY_BUILD=ON -GNinja } - { name: Windows LLVM/Clang, os: windows-2022, flags: -DSFML_USE_MESA3D=TRUE -DCMAKE_CXX_COMPILER=clang++ -GNinja } - { name: Windows MinGW, os: windows-2022, flags: -DSFML_USE_MESA3D=TRUE -DCMAKE_CXX_COMPILER=g++ -GNinja } - - { name: Linux GCC, os: ubuntu-22.04, flags: -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF -GNinja } - - { name: Linux Clang, os: ubuntu-22.04, flags: -DCMAKE_CXX_COMPILER=clang++ -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF -GNinja , gcovr_options: '--gcov-executable="llvm-cov-$CLANG_VERSION gcov"' } - - { name: Linux GCC DRM, os: ubuntu-22.04, flags: -DSFML_USE_DRM=ON -DSFML_RUN_DISPLAY_TESTS=OFF -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF -GNinja } - - { name: Linux GCC OpenGL ES, os: ubuntu-22.04, flags: -DSFML_OPENGL_ES=ON -DSFML_RUN_DISPLAY_TESTS=OFF -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF -GNinja } + - { name: Linux GCC, os: ubuntu-22.04, flags: -GNinja } + - { name: Linux Clang, os: ubuntu-22.04, flags: -DCMAKE_CXX_COMPILER=clang++ -GNinja , gcovr_options: '--gcov-executable="llvm-cov-$CLANG_VERSION gcov"' } + - { name: Linux GCC DRM, os: ubuntu-22.04, flags: -DSFML_USE_DRM=ON -DSFML_RUN_DISPLAY_TESTS=OFF -GNinja } + - { name: Linux GCC OpenGL ES, os: ubuntu-22.04, flags: -DSFML_OPENGL_ES=ON -DSFML_RUN_DISPLAY_TESTS=OFF -GNinja } - { name: macOS x64, os: macos-12, flags: -GNinja } - { name: macOS x64 Xcode, os: macos-12, flags: -GXcode } - { name: macOS arm64, os: macos-14, flags: -GNinja -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF } @@ -56,9 +56,9 @@ jobs: - platform: { name: Windows VS2022 x64, os: windows-2022 } config: { name: Static with PCH (MSVC), flags: -DSFML_USE_MESA3D=TRUE -GNinja -DBUILD_SHARED_LIBS=FALSE -DSFML_ENABLE_PCH=1 } - platform: { name: Linux GCC, os: ubuntu-22.04 } - config: { name: Static with PCH (GCC), flags: -GNinja -DCMAKE_CXX_COMPILER=g++ -DBUILD_SHARED_LIBS=FALSE -DSFML_ENABLE_PCH=1 -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF } + config: { name: Static with PCH (GCC), flags: -GNinja -DCMAKE_CXX_COMPILER=g++ -DBUILD_SHARED_LIBS=FALSE -DSFML_ENABLE_PCH=1 } - platform: { name: Linux Clang, os: ubuntu-22.04 } - config: { name: Static with PCH (Clang), flags: -GNinja -DCMAKE_CXX_COMPILER=clang++ -DBUILD_SHARED_LIBS=FALSE -DSFML_ENABLE_PCH=1 -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF } + config: { name: Static with PCH (Clang), flags: -GNinja -DCMAKE_CXX_COMPILER=clang++ -DBUILD_SHARED_LIBS=FALSE -DSFML_ENABLE_PCH=1 } - platform: { name: Windows MinGW, os: windows-2022 } config: { name: Static Standard Libraries, flags: -GNinja -DSFML_USE_MESA3D=TRUE -DCMAKE_CXX_COMPILER=g++ -DSFML_USE_STATIC_STD_LIBS=TRUE } - platform: { name: Windows MinGW, os: windows-2022 } @@ -135,6 +135,10 @@ jobs: echo "CLANG_VERSION=$CLANG_VERSION" >> $GITHUB_ENV sudo apt-get update && sudo apt-get install xorg-dev libxrandr-dev libxcursor-dev libudev-dev libflac-dev libvorbis-dev libgl1-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev xvfb fluxbox ccache gcovr ${{ matrix.platform.name == 'Linux Clang' && 'llvm-$CLANG_VERSION' || '' }} + - name: Remove ALSA Library + if: runner.os == 'Linux' && matrix.platform.name != 'Android' + run: sudo apt-get remove -y libasound2 + # LIBCXX_SHARED_SO is the path used by CMake to copy the necessary runtime library to the AVD # We find it by searching ANDROID_NDK_ROOT for file paths ending with matrix.config.libcxx - name: Install Android Components @@ -386,9 +390,9 @@ jobs: fail-fast: false matrix: platform: - - { name: Linux, os: ubuntu-22.04, flags: -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF } - - { name: Linux DRM, os: ubuntu-22.04, flags: -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF -DSFML_RUN_DISPLAY_TESTS=OFF -DSFML_USE_DRM=ON } - - { name: Linux GCC OpenGL ES, os: ubuntu-22.04, flags: -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF -DSFML_RUN_DISPLAY_TESTS=OFF -DSFML_OPENGL_ES=ON } + - { name: Linux, os: ubuntu-22.04, flags: } + - { name: Linux DRM, os: ubuntu-22.04, flags: -DSFML_RUN_DISPLAY_TESTS=OFF -DSFML_USE_DRM=ON } + - { name: Linux GCC OpenGL ES, os: ubuntu-22.04, flags: -DSFML_RUN_DISPLAY_TESTS=OFF -DSFML_OPENGL_ES=ON } steps: - name: Checkout Code @@ -402,7 +406,7 @@ jobs: - name: Install Linux Dependencies if: runner.os == 'Linux' - run: sudo apt-get update && sudo apt-get install xorg-dev libxrandr-dev libxcursor-dev libudev-dev libflac-dev libvorbis-dev libgl1-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev xvfb fluxbox + run: sudo apt-get update && sudo apt-get install xorg-dev libxrandr-dev libxcursor-dev libudev-dev libflac-dev libvorbis-dev libgl1-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev xvfb fluxbox && sudo apt-get remove -y libasound2 - name: Configure run: cmake --preset dev -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++ -DSFML_BUILD_EXAMPLES=OFF -DSFML_ENABLE_SANITIZERS=ON ${{matrix.platform.flags}} From 002b8953faf6348ae4e67f90dfa43855d319e189 Mon Sep 17 00:00:00 2001 From: binary1248 Date: Sat, 4 May 2024 17:50:59 +0200 Subject: [PATCH 37/38] Added support for user defined sound effect implementations. --- examples/sound_effects/SoundEffects.cpp | 468 ++++++++++++++++++++++-- include/SFML/Audio/Sound.hpp | 11 + include/SFML/Audio/SoundSource.hpp | 83 +++++ include/SFML/Audio/SoundStream.hpp | 11 + src/SFML/Audio/Sound.cpp | 122 +++++- src/SFML/Audio/SoundSource.cpp | 7 + src/SFML/Audio/SoundStream.cpp | 121 +++++- 7 files changed, 800 insertions(+), 23 deletions(-) diff --git a/examples/sound_effects/SoundEffects.cpp b/examples/sound_effects/SoundEffects.cpp index cbbecd31c..704a0cb2a 100644 --- a/examples/sound_effects/SoundEffects.cpp +++ b/examples/sound_effects/SoundEffects.cpp @@ -19,6 +19,7 @@ namespace constexpr auto windowWidth = 800u; constexpr auto windowHeight = 600u; constexpr auto pi = 3.14159265359f; +constexpr auto sqrt2 = 2.0f * 0.707106781186547524401f; std::filesystem::path resourcesDir() { @@ -85,10 +86,10 @@ protected: private: // Virtual functions to be implemented in derived effects - virtual void onUpdate(float time, float x, float y) = 0; - virtual void onDraw(sf::RenderTarget& target, const sf::RenderStates& states) const = 0; - virtual void onStart() = 0; - virtual void onStop() = 0; + virtual void onUpdate(float time, float x, float y) = 0; + virtual void onDraw(sf::RenderTarget& target, sf::RenderStates states) const = 0; + virtual void onStart() = 0; + virtual void onStop() = 0; virtual void onKey(sf::Keyboard::Key) { @@ -129,7 +130,7 @@ public: m_music.setPosition({m_position.x, m_position.y, 0.f}); } - void onDraw(sf::RenderTarget& target, const sf::RenderStates& states) const override + void onDraw(sf::RenderTarget& target, sf::RenderStates states) const override { auto statesCopy(states); statesCopy.transform = sf::Transform::Identity; @@ -203,7 +204,7 @@ public: m_volumeText.setString("Volume: " + std::to_string(m_volume)); } - void onDraw(sf::RenderTarget& target, const sf::RenderStates& states) const override + void onDraw(sf::RenderTarget& target, sf::RenderStates states) const override { target.draw(m_pitchText, states); target.draw(m_volumeText, states); @@ -306,7 +307,7 @@ public: m_music.setPosition({m_position.x, m_position.y, 0.f}); } - void onDraw(sf::RenderTarget& target, const sf::RenderStates& states) const override + void onDraw(sf::RenderTarget& target, sf::RenderStates states) const override { auto statesCopy(states); @@ -376,7 +377,7 @@ public: m_currentFrequency.setString("Frequency: " + std::to_string(m_frequency) + " Hz"); } - void onDraw(sf::RenderTarget& target, const sf::RenderStates& states) const override + void onDraw(sf::RenderTarget& target, sf::RenderStates states) const override { target.draw(m_instruction, states); target.draw(m_currentType, states); @@ -548,7 +549,7 @@ public: setDopplerFactor(m_factor); } - void onDraw(sf::RenderTarget& target, const sf::RenderStates& states) const override + void onDraw(sf::RenderTarget& target, sf::RenderStates states) const override { auto statesCopy(states); statesCopy.transform = sf::Transform::Identity; @@ -616,6 +617,425 @@ private: }; +//////////////////////////////////////////////////////////// +// Processing base class +//////////////////////////////////////////////////////////// +class Processing : public Effect +{ +public: + void onUpdate([[maybe_unused]] float time, float x, float y) override + { + m_position = {windowWidth * x - 10.f, windowHeight * y - 10.f}; + m_music.setPosition({m_position.x, m_position.y, 0.f}); + } + + void onDraw(sf::RenderTarget& target, sf::RenderStates states) const override + { + auto statesCopy(states); + statesCopy.transform = sf::Transform::Identity; + statesCopy.transform.translate(m_position); + + target.draw(m_listener, states); + target.draw(m_soundShape, statesCopy); + target.draw(m_enabledText); + target.draw(m_instructions); + } + + void onStart() override + { + // Synchronize listener audio position with graphical position + sf::Listener::setPosition({m_listener.getPosition().x, m_listener.getPosition().y, 0.f}); + + m_music.play(); + } + + void onStop() override + { + m_music.stop(); + } + +protected: + Processing(std::string name) : + Effect(std::move(name)), + m_enabledText(getFont(), "Processing: Enabled"), + m_instructions(getFont(), "Press Space to enable/disable processing") + { + m_listener.setPosition({(windowWidth - 20.f) / 2.f, (windowHeight - 20.f) / 2.f}); + m_listener.setFillColor(sf::Color::Red); + + m_enabledText.setPosition({windowWidth / 2.f - 120.f, windowHeight * 3.f / 4.f - 50.f}); + m_instructions.setPosition({windowWidth / 2.f - 250.f, windowHeight * 3.f / 4.f}); + + // Load the music file + if (!m_music.openFromFile(resourcesDir() / "doodle_pop.ogg")) + std::cerr << "Failed to load " << (resourcesDir() / "doodle_pop.ogg").string() << std::endl; + + // Set the music to loop + m_music.setLoop(true); + + // Set attenuation to a nice value + m_music.setAttenuation(0.0f); + } + + sf::Music& getMusic() + { + return m_music; + } + + const std::shared_ptr& getEnabled() const + { + return m_enabled; + } + +private: + void onKey(sf::Keyboard::Key key) override + { + if (key == sf::Keyboard::Key::Space) + *m_enabled = !*m_enabled; + + m_enabledText.setString(*m_enabled ? "Processing: Enabled" : "Processing: Disabled"); + } + + sf::CircleShape m_listener{20.f}; + sf::CircleShape m_soundShape{20.f}; + sf::Vector2f m_position; + sf::Music m_music; + std::shared_ptr m_enabled{std::make_shared(true)}; + sf::Text m_enabledText; + sf::Text m_instructions; +}; + + +//////////////////////////////////////////////////////////// +// Biquad Filter (https://github.com/dimtass/DSP-Cpp-filters) +//////////////////////////////////////////////////////////// +class BiquadFilter : public Processing +{ +protected: + struct Coefficients + { + float a0{}; + float a1{}; + float a2{}; + float b1{}; + float b2{}; + float c0{}; + float d0{}; + }; + + using Processing::Processing; + + void setCoefficients(const Coefficients& coefficients) + { + auto& music = getMusic(); + + struct State + { + float xnz1{}; + float xnz2{}; + float ynz1{}; + float ynz2{}; + }; + + // We use a mutable lambda to tie the lifetime of the state and coefficients to the lambda itself + // This is necessary since the Echo object will be destroyed before the Music object + // While the Music object exists, it is possible that the audio engine will try to call + // this lambda hence we need to always have usable coefficients and state until the Music and the + // associated lambda are destroyed + music.setEffectProcessor( + [coefficients, + enabled = getEnabled(), + state = std::vector(music.getChannelCount())](const float* inputFrames, + unsigned int& inputFrameCount, + float* outputFrames, + unsigned int& outputFrameCount, + unsigned int frameChannelCount) mutable + { + for (auto frame = 0u; frame < outputFrameCount; ++frame) + { + for (auto channel = 0u; channel < frameChannelCount; ++channel) + { + auto& channelState = state[channel]; + + const auto xn = inputFrames ? inputFrames[channel] : 0.f; // Read silence if no input data available + const auto yn = coefficients.a0 * xn + coefficients.a1 * channelState.xnz1 + + coefficients.a2 * channelState.xnz2 - coefficients.b1 * channelState.ynz1 - + coefficients.b2 * channelState.ynz2; + + channelState.xnz2 = channelState.xnz1; + channelState.xnz1 = xn; + channelState.ynz2 = channelState.ynz1; + channelState.ynz1 = yn; + + outputFrames[channel] = *enabled ? yn : xn; + } + + inputFrames += (inputFrames ? frameChannelCount : 0u); + outputFrames += frameChannelCount; + } + + // We processed data 1:1 + inputFrameCount = outputFrameCount; + }); + } +}; + + +//////////////////////////////////////////////////////////// +// High-pass Filter (https://github.com/dimtass/DSP-Cpp-filters) +//////////////////////////////////////////////////////////// +struct HighPassFilter : BiquadFilter +{ + HighPassFilter() : BiquadFilter("High-pass Filter") + { + static constexpr auto cutoffFrequency = 2000.f; + + const auto c = std::tan(pi * cutoffFrequency / static_cast(getMusic().getSampleRate())); + + Coefficients coefficients; + + coefficients.a0 = 1.f / (1.f + sqrt2 * c + std::pow(c, 2.f)); + coefficients.a1 = -2.f * coefficients.a0; + coefficients.a2 = coefficients.a0; + coefficients.b1 = 2.f * coefficients.a0 * (std::pow(c, 2.f) - 1.f); + coefficients.b2 = coefficients.a0 * (1.f - sqrt2 * c + std::pow(c, 2.f)); + + setCoefficients(coefficients); + } +}; + + +//////////////////////////////////////////////////////////// +// Low-pass Filter (https://github.com/dimtass/DSP-Cpp-filters) +//////////////////////////////////////////////////////////// +struct LowPassFilter : BiquadFilter +{ + LowPassFilter() : BiquadFilter("Low-pass Filter") + { + static constexpr auto cutoffFrequency = 500.f; + + const auto c = 1.f / std::tan(pi * cutoffFrequency / static_cast(getMusic().getSampleRate())); + + Coefficients coefficients; + + coefficients.a0 = 1.f / (1.f + sqrt2 * c + std::pow(c, 2.f)); + coefficients.a1 = 2.f * coefficients.a0; + coefficients.a2 = coefficients.a0; + coefficients.b1 = 2.f * coefficients.a0 * (1.f - std::pow(c, 2.f)); + coefficients.b2 = coefficients.a0 * (1.f - sqrt2 * c + std::pow(c, 2.f)); + + setCoefficients(coefficients); + } +}; + + +//////////////////////////////////////////////////////////// +// Echo (miniaudio implementation) +//////////////////////////////////////////////////////////// +struct Echo : Processing +{ + Echo() : Processing("Echo") + { + auto& music = getMusic(); + + static constexpr auto delay = 0.2f; + static constexpr auto decay = 0.75f; + static constexpr auto wet = 0.8f; + static constexpr auto dry = 1.f; + + const auto channelCount = music.getChannelCount(); + const auto sampleRate = music.getSampleRate(); + const auto delayInFrames = static_cast(static_cast(sampleRate) * delay); + + // We use a mutable lambda to tie the lifetime of the state to the lambda itself + // This is necessary since the Echo object will be destroyed before the Music object + // While the Music object exists, it is possible that the audio engine will try to call + // this lambda hence we need to always have a usable state until the Music and the + // associated lambda are destroyed + music.setEffectProcessor( + [delayInFrames, + enabled = getEnabled(), + buffer = std::vector(delayInFrames * channelCount, 0.f), + cursor = 0u](const float* inputFrames, + unsigned int& inputFrameCount, + float* outputFrames, + unsigned int& outputFrameCount, + unsigned int frameChannelCount) mutable + { + for (auto frame = 0u; frame < outputFrameCount; ++frame) + { + for (auto channel = 0u; channel < frameChannelCount; ++channel) + { + const auto input = inputFrames ? inputFrames[channel] : 0.f; // Read silence if no input data available + const auto bufferIndex = (cursor * frameChannelCount) + channel; + buffer[bufferIndex] = (buffer[bufferIndex] * decay) + (input * dry); + outputFrames[channel] = *enabled ? buffer[bufferIndex] * wet : input; + } + + cursor = (cursor + 1) % delayInFrames; + + inputFrames += (inputFrames ? frameChannelCount : 0u); + outputFrames += frameChannelCount; + } + + // We processed data 1:1 + inputFrameCount = outputFrameCount; + }); + } +}; + + +//////////////////////////////////////////////////////////// +// Reverb (https://github.com/sellicott/DSP-FFMpeg-Reverb) +//////////////////////////////////////////////////////////// +class Reverb : public Processing +{ +public: + Reverb() : Processing("Reverb") + { + auto& music = getMusic(); + + static constexpr auto sustain = 0.7f; // [0.f; 1.f] + + const auto channelCount = music.getChannelCount(); + const auto sampleRate = music.getSampleRate(); + + std::vector> filters; + filters.reserve(channelCount); + + for (auto i = 0u; i < channelCount; ++i) + filters.emplace_back(sampleRate, sustain); + + // We use a mutable lambda to tie the lifetime of the state to the lambda itself + // This is necessary since the Echo object will be destroyed before the Music object + // While the Music object exists, it is possible that the audio engine will try to call + // this lambda hence we need to always have a usable state until the Music and the + // associated lambda are destroyed + music.setEffectProcessor( + [filters, enabled = getEnabled()](const float* inputFrames, + unsigned int& inputFrameCount, + float* outputFrames, + unsigned int& outputFrameCount, + unsigned int frameChannelCount) mutable + { + for (auto frame = 0u; frame < outputFrameCount; ++frame) + { + for (auto channel = 0u; channel < frameChannelCount; ++channel) + { + const auto input = inputFrames ? inputFrames[channel] : 0.f; // Read silence if no input data available + outputFrames[channel] = *enabled ? filters[channel](input) : input; + } + + inputFrames += (inputFrames ? frameChannelCount : 0u); + outputFrames += frameChannelCount; + } + + // We processed data 1:1 + inputFrameCount = outputFrameCount; + }); + } + +private: + template + class AllPassFilter + { + public: + AllPassFilter(std::size_t delay, float theGain) : m_buffer(delay, {}), m_gain(theGain) + { + } + + T operator()(T input) + { + const auto output = m_buffer[m_cursor]; + input = static_cast(input + m_gain * output); + m_buffer[m_cursor] = input; + m_cursor = (m_cursor + 1) % m_buffer.size(); + return static_cast(-m_gain * input + output); + } + + private: + std::vector m_buffer; + std::size_t m_cursor{}; + const float m_gain{}; + }; + + + template + class FIRFilter + { + public: + FIRFilter(std::vector taps) : m_taps(std::move(taps)) + { + } + + T operator()(T input) + { + m_buffer[m_cursor] = input; + m_cursor = (m_cursor + 1) % m_buffer.size(); + + T output{}; + + for (auto i = 0u; i < m_taps.size(); ++i) + output += static_cast(m_taps[i] * m_buffer[(m_cursor + i) % m_buffer.size()]); + + return output; + } + + private: + const std::vector m_taps; + std::vector m_buffer = std::vector(m_taps.size(), {}); + std::size_t m_cursor{}; + }; + + template + class ReverbFilter + { + public: + ReverbFilter(unsigned int sampleRate, float feedbackGain) : + m_allPass{{sampleRate / 10, 0.6f}, {sampleRate / 30, -0.6f}, {sampleRate / 90, 0.6f}, {sampleRate / 270, -0.6f}}, + m_fir({0.003369f, 0.002810f, 0.001758f, 0.000340f, -0.001255f, -0.002793f, -0.004014f, -0.004659f, + -0.004516f, -0.003464f, -0.001514f, 0.001148f, 0.004157f, 0.006986f, 0.009003f, 0.009571f, + 0.008173f, 0.004560f, -0.001120f, -0.008222f, -0.015581f, -0.021579f, -0.024323f, -0.021933f, + -0.012904f, 0.003500f, 0.026890f, 0.055537f, 0.086377f, 0.115331f, 0.137960f, 0.150407f, + 0.150407f, 0.137960f, 0.115331f, 0.086377f, 0.055537f, 0.026890f, 0.003500f, -0.012904f, + -0.021933f, -0.024323f, -0.021579f, -0.015581f, -0.008222f, -0.001120f, 0.004560f, 0.008173f, + 0.009571f, 0.009003f, 0.006986f, 0.004157f, 0.001148f, -0.001514f, -0.003464f, -0.004516f, + -0.004659f, -0.004014f, -0.002793f, -0.001255f, 0.000340f, 0.001758f, 0.002810f, 0.003369f}), + m_buffer(sampleRate / 5, {}), // sample rate / 5 = 200ms buffer size + m_feedbackGain(feedbackGain) + { + } + + T operator()(T input) + { + auto output = static_cast(0.7f * input + m_feedbackGain * m_buffer[m_cursor]); + + for (auto& f : m_allPass) + output = f(output); + + output = m_fir(output); + + m_buffer[m_cursor] = output; + m_cursor = (m_cursor + 1) % m_buffer.size(); + + output += 0.5f * m_buffer[(m_cursor + 1 * m_interval - 1) % m_buffer.size()]; + output += 0.25f * m_buffer[(m_cursor + 2 * m_interval - 1) % m_buffer.size()]; + output += 0.125f * m_buffer[(m_cursor + 3 * m_interval - 1) % m_buffer.size()]; + + return 0.6f * output + input; + } + + private: + AllPassFilter m_allPass[4]; + FIRFilter m_fir; + std::vector m_buffer; + std::size_t m_cursor{}; + const std::size_t m_interval{m_buffer.size() / 3}; + const float m_feedbackGain{}; + }; +}; + + //////////////////////////////////////////////////////////// /// Entry point of application /// @@ -637,19 +1057,25 @@ int main() Effect::setFont(font); // Create the effects - Surround surroundEffect; - PitchVolume pitchVolumeEffect; - Attenuation attenuationEffect; - Tone toneEffect; - Doppler dopplerEffect; + Surround surroundEffect; + PitchVolume pitchVolumeEffect; + Attenuation attenuationEffect; + Tone toneEffect; + Doppler dopplerEffect; + HighPassFilter highPassFilterEffect; + LowPassFilter lowPassFilterEffect; + Echo echoEffect; + Reverb reverbEffect; - const std::array effects{ - &surroundEffect, - &pitchVolumeEffect, - &attenuationEffect, - &toneEffect, - &dopplerEffect, - }; + const std::array effects{&surroundEffect, + &pitchVolumeEffect, + &attenuationEffect, + &toneEffect, + &dopplerEffect, + &highPassFilterEffect, + &lowPassFilterEffect, + &echoEffect, + &reverbEffect}; std::size_t current = 0; diff --git a/include/SFML/Audio/Sound.hpp b/include/SFML/Audio/Sound.hpp index 6720a56f9..ae9e3591f 100644 --- a/include/SFML/Audio/Sound.hpp +++ b/include/SFML/Audio/Sound.hpp @@ -162,6 +162,17 @@ public: //////////////////////////////////////////////////////////// void setPlayingOffset(Time timeOffset); + //////////////////////////////////////////////////////////// + /// \brief Set the effect processor to be applied to the sound + /// + /// The effect processor is a callable that will be called + /// with sound data to be processed. + /// + /// \param effectProcessor The effect processor to attach to this sound, attach an empty processor to disable processing + /// + //////////////////////////////////////////////////////////// + void setEffectProcessor(EffectProcessor effectProcessor) override; + //////////////////////////////////////////////////////////// /// \brief Get the audio buffer attached to the sound /// diff --git a/include/SFML/Audio/SoundSource.hpp b/include/SFML/Audio/SoundSource.hpp index c67da7084..46805d6e5 100644 --- a/include/SFML/Audio/SoundSource.hpp +++ b/include/SFML/Audio/SoundSource.hpp @@ -34,6 +34,8 @@ #include #include +#include + namespace sf { @@ -74,6 +76,76 @@ public: float outerGain{}; //!< Outer gain }; + //////////////////////////////////////////////////////////// + /// \brief Callable that is provided with sound data for processing + /// + /// When the audio engine sources sound data from sound + /// sources it will pass the data through an effects + /// processor if one is set. The sound data will already be + /// converted to the internal floating point format. + /// + /// Sound data that is processed this way is provided in + /// frames. Each frame contains 1 floating point sample per + /// channel. If e.g. the data source provides stereo data, + /// each frame will contain 2 floats. + /// + /// The effects processor function takes 4 parameters: + /// - The input data frames, channels interleaved + /// - The number of input data frames available + /// - The buffer to write output data frames to, channels interleaved + /// - The number of output data frames that the output buffer can hold + /// - The channel count + /// + /// The input and output frame counts are in/out parameters. + /// + /// When this function is called, the input count will + /// contain the number of frames available in the input + /// buffer. The output count will contain the size of the + /// output buffer i.e. the maximum number of frames that + /// can be written to the output buffer. + /// + /// Attempting to read more frames than the input frame + /// count or write more frames than the output frame count + /// will result in undefined behaviour. + /// + /// When done processing the frames, the input and output + /// frame counts must be updated to reflect the actual + /// number of frames that were read from the input and + /// written to the output. + /// + /// The processing function should always try to process as + /// much sound data as possible i.e. always try to fill the + /// output buffer to the maximum. In certain situations for + /// specific effects it can be possible that the input frame + /// count and output frame count aren't equal. As long as + /// the frame counts are updated accordingly this is + /// perfectly valid. + /// + /// If the audio engine determines that no audio data is + /// available from the data source, the input data frames + /// pointer is set to nullptr and the input frame count is + /// set to 0. In this case it is up to the function to + /// decide how to handle the situation. For specific effects + /// e.g. Echo/Delay buffered data might still be able to be + /// written to the output buffer even if there is no longer + /// any input data. + /// + /// An important thing to remember is that this function is + /// directly called by the audio engine. Because the audio + /// engine runs on an internal thread of its own, make sure + /// access to shared data is synchronized appropriately. + /// + /// Because this function is stored by the SoundSource + /// object it will be able to be called as long as the + /// SoundSource object hasn't yet been destroyed. Make sure + /// that any data this function references outlives the + /// SoundSource object otherwise use-after-free errors will + /// occur. + /// + //////////////////////////////////////////////////////////// + using EffectProcessor = std::function< + void(const float* inputFrames, unsigned int& inputFrameCount, float* outputFrames, unsigned int& outputFrameCount, unsigned int frameChannelCount)>; + //////////////////////////////////////////////////////////// /// \brief Copy constructor /// @@ -331,6 +403,17 @@ public: //////////////////////////////////////////////////////////// void setAttenuation(float attenuation); + //////////////////////////////////////////////////////////// + /// \brief Set the effect processor to be applied to the sound + /// + /// The effect processor is a callable that will be called + /// with sound data to be processed. + /// + /// \param effectProcessor The effect processor to attach to this sound, attach an empty processor to disable processing + /// + //////////////////////////////////////////////////////////// + virtual void setEffectProcessor(EffectProcessor effectProcessor); + //////////////////////////////////////////////////////////// /// \brief Get the pitch of the sound /// diff --git a/include/SFML/Audio/SoundStream.hpp b/include/SFML/Audio/SoundStream.hpp index 12c29353b..87ff8240c 100644 --- a/include/SFML/Audio/SoundStream.hpp +++ b/include/SFML/Audio/SoundStream.hpp @@ -194,6 +194,17 @@ public: //////////////////////////////////////////////////////////// bool getLoop() const; + //////////////////////////////////////////////////////////// + /// \brief Set the effect processor to be applied to the sound + /// + /// The effect processor is a callable that will be called + /// with sound data to be processed. + /// + /// \param effectProcessor The effect processor to attach to this sound, attach an empty processor to disable processing + /// + //////////////////////////////////////////////////////////// + void setEffectProcessor(EffectProcessor effectProcessor) override; + protected: //////////////////////////////////////////////////////////// /// \brief Default constructor diff --git a/src/SFML/Audio/Sound.cpp b/src/SFML/Audio/Sound.cpp index b683fdf59..c61a71570 100644 --- a/src/SFML/Audio/Sound.cpp +++ b/src/SFML/Audio/Sound.cpp @@ -55,6 +55,7 @@ struct Sound::Impl ~Impl() { ma_sound_uninit(&sound); + ma_node_uninit(&effectNode, nullptr); ma_data_source_uninit(&dataSourceBase); } @@ -90,6 +91,34 @@ struct Sound::Impl return; } + // Initialize the custom effect node + effectNodeVTable.onProcess = + [](ma_node* node, const float** framesIn, ma_uint32* frameCountIn, float** framesOut, ma_uint32* frameCountOut) + { static_cast(node)->impl->processEffect(framesIn, *frameCountIn, framesOut, *frameCountOut); }; + effectNodeVTable.onGetRequiredInputFrameCount = nullptr; + effectNodeVTable.inputBusCount = 1; + effectNodeVTable.outputBusCount = 1; + effectNodeVTable.flags = MA_NODE_FLAG_CONTINUOUS_PROCESSING | MA_NODE_FLAG_ALLOW_NULL_INPUT; + + const auto nodeChannelCount = ma_engine_get_channels(engine); + ma_node_config nodeConfig = ma_node_config_init(); + nodeConfig.vtable = &effectNodeVTable; + nodeConfig.pInputChannels = &nodeChannelCount; + nodeConfig.pOutputChannels = &nodeChannelCount; + + if (const ma_result result = ma_node_init(ma_engine_get_node_graph(engine), &nodeConfig, nullptr, &effectNode); + result != MA_SUCCESS) + { + err() << "Failed to initialize effect node: " << ma_result_description(result) << std::endl; + return; + } + + effectNode.impl = this; + effectNode.channelCount = nodeChannelCount; + + // Route the sound through the effect node depending on whether an effect processor is set + connectEffect(bool{effectProcessor}); + // Because we are providing a custom data source, we have to provide the channel map ourselves if (buffer && !buffer->getChannelMap().empty()) { @@ -110,7 +139,80 @@ struct Sound::Impl void reinitialize() { - priv::MiniaudioUtils::reinitializeSound(sound, [this] { initialize(); }); + priv::MiniaudioUtils::reinitializeSound(sound, + [this] + { + ma_node_uninit(&effectNode, nullptr); + initialize(); + }); + } + + void processEffect(const float** framesIn, ma_uint32& frameCountIn, float** framesOut, ma_uint32& frameCountOut) const + { + // If a processor is set, call it + if (effectProcessor) + { + if (!framesIn) + frameCountIn = 0; + + effectProcessor(framesIn ? framesIn[0] : nullptr, frameCountIn, framesOut[0], frameCountOut, effectNode.channelCount); + + return; + } + + // Otherwise just pass the data through 1:1 + if (framesIn == nullptr) + { + frameCountIn = 0; + frameCountOut = 0; + return; + } + + const auto toProcess = std::min(frameCountIn, frameCountOut); + std::memcpy(framesOut[0], framesIn[0], toProcess * effectNode.channelCount * sizeof(float)); + frameCountIn = toProcess; + frameCountOut = toProcess; + } + + void connectEffect(bool connect) + { + auto* engine = priv::AudioDevice::getEngine(); + + if (engine == nullptr) + { + err() << "Failed to connect effect: No engine available" << std::endl; + return; + } + + if (connect) + { + // Attach the custom effect node output to our engine endpoint + if (const ma_result result = ma_node_attach_output_bus(&effectNode, 0, ma_engine_get_endpoint(engine), 0); + result != MA_SUCCESS) + { + err() << "Failed to attach effect node output to endpoint: " << ma_result_description(result) << std::endl; + return; + } + } + else + { + // Detach the custom effect node output from our engine endpoint + if (const ma_result result = ma_node_detach_output_bus(&effectNode, 0); result != MA_SUCCESS) + { + err() << "Failed to detach effect node output from endpoint: " << ma_result_description(result) + << std::endl; + return; + } + } + + // Attach the sound output to the custom effect node or the engine endpoint + if (const ma_result + result = ma_node_attach_output_bus(&sound, 0, connect ? &effectNode : ma_engine_get_endpoint(engine), 0); + result != MA_SUCCESS) + { + err() << "Failed to attach sound node output to effect node: " << ma_result_description(result) << std::endl; + return; + } } static ma_result read(ma_data_source* dataSource, void* framesOut, ma_uint64 frameCount, ma_uint64* framesRead) @@ -208,13 +310,23 @@ struct Sound::Impl //////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// + struct EffectNode + { + ma_node_base base{}; + Impl* impl{}; + ma_uint32 channelCount{}; + }; + ma_data_source_base dataSourceBase{}; //!< The struct that makes this object a miniaudio data source (must be first member) + ma_node_vtable effectNodeVTable{}; //!< Vtable of the effect node + EffectNode effectNode; //!< The engine node that performs effect processing std::vector soundChannelMap; //!< The map of position in sample frame to sound channel (miniaudio channels) ma_sound sound{}; //!< The sound std::size_t cursor{}; //!< The current playing position bool looping{}; //!< True if we are looping the sound const SoundBuffer* buffer{}; //!< Sound buffer bound to the source Status status{Status::Stopped}; //!< The status + EffectProcessor effectProcessor; //!< The effect processor }; @@ -334,6 +446,14 @@ void Sound::setPlayingOffset(Time timeOffset) } +//////////////////////////////////////////////////////////// +void Sound::setEffectProcessor(EffectProcessor effectProcessor) +{ + m_impl->effectProcessor = std::move(effectProcessor); + m_impl->connectEffect(bool{m_impl->effectProcessor}); +} + + //////////////////////////////////////////////////////////// const SoundBuffer& Sound::getBuffer() const { diff --git a/src/SFML/Audio/SoundSource.cpp b/src/SFML/Audio/SoundSource.cpp index 1b210d2ac..3bb156232 100644 --- a/src/SFML/Audio/SoundSource.cpp +++ b/src/SFML/Audio/SoundSource.cpp @@ -166,6 +166,13 @@ void SoundSource::setAttenuation(float attenuation) } +//////////////////////////////////////////////////////////// +// NOLINTNEXTLINE(performance-unnecessary-value-param) +void SoundSource::setEffectProcessor(EffectProcessor) +{ +} + + //////////////////////////////////////////////////////////// float SoundSource::getPitch() const { diff --git a/src/SFML/Audio/SoundStream.cpp b/src/SFML/Audio/SoundStream.cpp index 7eeeef813..334cdac3a 100644 --- a/src/SFML/Audio/SoundStream.cpp +++ b/src/SFML/Audio/SoundStream.cpp @@ -55,6 +55,7 @@ struct SoundStream::Impl ~Impl() { ma_sound_uninit(&sound); + ma_node_uninit(&effectNode, nullptr); ma_data_source_uninit(&dataSourceBase); } @@ -91,6 +92,34 @@ struct SoundStream::Impl return; } + // Initialize the custom effect node + effectNodeVTable.onProcess = + [](ma_node* node, const float** framesIn, ma_uint32* frameCountIn, float** framesOut, ma_uint32* frameCountOut) + { static_cast(node)->impl->processEffect(framesIn, *frameCountIn, framesOut, *frameCountOut); }; + effectNodeVTable.onGetRequiredInputFrameCount = nullptr; + effectNodeVTable.inputBusCount = 1; + effectNodeVTable.outputBusCount = 1; + effectNodeVTable.flags = MA_NODE_FLAG_CONTINUOUS_PROCESSING | MA_NODE_FLAG_ALLOW_NULL_INPUT; + + const auto nodeChannelCount = ma_engine_get_channels(engine); + ma_node_config nodeConfig = ma_node_config_init(); + nodeConfig.vtable = &effectNodeVTable; + nodeConfig.pInputChannels = &nodeChannelCount; + nodeConfig.pOutputChannels = &nodeChannelCount; + + if (const ma_result result = ma_node_init(ma_engine_get_node_graph(engine), &nodeConfig, nullptr, &effectNode); + result != MA_SUCCESS) + { + err() << "Failed to initialize effect node: " << ma_result_description(result) << std::endl; + return; + } + + effectNode.impl = this; + effectNode.channelCount = nodeChannelCount; + + // Route the sound through the effect node depending on whether an effect processor is set + connectEffect(bool{effectProcessor}); + // Because we are providing a custom data source, we have to provide the channel map ourselves if (!channelMap.empty()) { @@ -111,7 +140,79 @@ struct SoundStream::Impl void reinitialize() { - priv::MiniaudioUtils::reinitializeSound(sound, [this] { initialize(); }); + priv::MiniaudioUtils::reinitializeSound(sound, + [this] + { + ma_node_uninit(&effectNode, nullptr); + initialize(); + }); + } + + void processEffect(const float** framesIn, ma_uint32& frameCountIn, float** framesOut, ma_uint32& frameCountOut) const + { + // If a processor is set, call it + if (effectProcessor) + { + if (!framesIn) + frameCountIn = 0; + + effectProcessor(framesIn ? framesIn[0] : nullptr, frameCountIn, framesOut[0], frameCountOut, effectNode.channelCount); + return; + } + + // Otherwise just pass the data through 1:1 + if (framesIn == nullptr) + { + frameCountIn = 0; + frameCountOut = 0; + return; + } + + const auto toProcess = std::min(frameCountIn, frameCountOut); + std::memcpy(framesOut[0], framesIn[0], toProcess * effectNode.channelCount * sizeof(float)); + frameCountIn = toProcess; + frameCountOut = toProcess; + } + + void connectEffect(bool connect) + { + auto* engine = priv::AudioDevice::getEngine(); + + if (engine == nullptr) + { + err() << "Failed to connect effect: No engine available" << std::endl; + return; + } + + if (connect) + { + // Attach the custom effect node output to our engine endpoint + if (const ma_result result = ma_node_attach_output_bus(&effectNode, 0, ma_engine_get_endpoint(engine), 0); + result != MA_SUCCESS) + { + err() << "Failed to attach effect node output to endpoint: " << ma_result_description(result) << std::endl; + return; + } + } + else + { + // Detach the custom effect node output from our engine endpoint + if (const ma_result result = ma_node_detach_output_bus(&effectNode, 0); result != MA_SUCCESS) + { + err() << "Failed to detach effect node output from endpoint: " << ma_result_description(result) + << std::endl; + return; + } + } + + // Attach the sound output to the custom effect node or the engine endpoint + if (const ma_result + result = ma_node_attach_output_bus(&sound, 0, connect ? &effectNode : ma_engine_get_endpoint(engine), 0); + result != MA_SUCCESS) + { + err() << "Failed to attach sound node output to effect node: " << ma_result_description(result) << std::endl; + return; + } } static ma_result read(ma_data_source* dataSource, void* framesOut, ma_uint64 frameCount, ma_uint64* framesRead) @@ -238,8 +339,17 @@ struct SoundStream::Impl //////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// + struct EffectNode + { + ma_node_base base{}; + Impl* impl{}; + ma_uint32 channelCount{}; + }; + ma_data_source_base dataSourceBase{}; //!< The struct that makes this object a miniaudio data source (must be first member) SoundStream* const owner; //!< Owning SoundStream object + ma_node_vtable effectNodeVTable{}; //!< Vtable of the effect node + EffectNode effectNode; //!< The engine node that performs effect processing std::vector soundChannelMap; //!< The map of position in sample frame to sound channel (miniaudio channels) ma_sound sound{}; //!< The sound std::vector sampleBuffer; //!< Our temporary sample buffer @@ -251,6 +361,7 @@ struct SoundStream::Impl bool loop{}; //!< Loop flag (true to loop, false to play once) bool streaming{true}; //!< True if we are still streaming samples from the source Status status{Status::Stopped}; //!< The status + EffectProcessor effectProcessor; //!< The effect processor }; @@ -395,6 +506,14 @@ bool SoundStream::getLoop() const } +//////////////////////////////////////////////////////////// +void SoundStream::setEffectProcessor(EffectProcessor effectProcessor) +{ + m_impl->effectProcessor = std::move(effectProcessor); + m_impl->connectEffect(bool{m_impl->effectProcessor}); +} + + //////////////////////////////////////////////////////////// std::optional SoundStream::onLoop() { From 1a40f0195788185d56eca04687a8cd793c90b2fc Mon Sep 17 00:00:00 2001 From: binary1248 Date: Fri, 17 May 2024 03:59:35 +0200 Subject: [PATCH 38/38] Replaced SoundFileReaderWav implementation with miniaudio (dr_)wav decoder. --- src/SFML/Audio/CMakeLists.txt | 2 +- src/SFML/Audio/MiniaudioUtils.cpp | 50 +++ src/SFML/Audio/MiniaudioUtils.hpp | 7 +- src/SFML/Audio/SoundFileReaderWav.cpp | 477 +++++++------------------- src/SFML/Audio/SoundFileReaderWav.hpp | 24 +- 5 files changed, 186 insertions(+), 374 deletions(-) diff --git a/src/SFML/Audio/CMakeLists.txt b/src/SFML/Audio/CMakeLists.txt index 258116ce8..a4bd1ed36 100644 --- a/src/SFML/Audio/CMakeLists.txt +++ b/src/SFML/Audio/CMakeLists.txt @@ -84,7 +84,7 @@ sfml_add_library(Audio target_compile_definitions(sfml-audio PRIVATE OV_EXCLUDE_STATIC_CALLBACKS FLAC__NO_DLL) # disable miniaudio features we do not use -target_compile_definitions(sfml-audio PRIVATE MA_NO_DECODING MA_NO_ENCODING MA_NO_RESOURCE_MANAGER MA_NO_GENERATION) +target_compile_definitions(sfml-audio PRIVATE MA_NO_MP3 MA_NO_FLAC MA_NO_ENCODING MA_NO_RESOURCE_MANAGER MA_NO_GENERATION) # setup dependencies target_link_libraries(sfml-audio diff --git a/src/SFML/Audio/MiniaudioUtils.cpp b/src/SFML/Audio/MiniaudioUtils.cpp index 8ea706494..44cd82e8a 100644 --- a/src/SFML/Audio/MiniaudioUtils.cpp +++ b/src/SFML/Audio/MiniaudioUtils.cpp @@ -184,6 +184,56 @@ ma_channel MiniaudioUtils::soundChannelToMiniaudioChannel(SoundChannel soundChan } +//////////////////////////////////////////////////////////// +SoundChannel MiniaudioUtils::miniaudioChannelToSoundChannel(ma_channel soundChannel) +{ + switch (soundChannel) + { + case MA_CHANNEL_NONE: + return SoundChannel::Unspecified; + case MA_CHANNEL_MONO: + return SoundChannel::Mono; + case MA_CHANNEL_FRONT_LEFT: + return SoundChannel::FrontLeft; + case MA_CHANNEL_FRONT_RIGHT: + return SoundChannel::FrontRight; + case MA_CHANNEL_FRONT_CENTER: + return SoundChannel::FrontCenter; + case MA_CHANNEL_FRONT_LEFT_CENTER: + return SoundChannel::FrontLeftOfCenter; + case MA_CHANNEL_FRONT_RIGHT_CENTER: + return SoundChannel::FrontRightOfCenter; + case MA_CHANNEL_LFE: + return SoundChannel::LowFrequencyEffects; + case MA_CHANNEL_BACK_LEFT: + return SoundChannel::BackLeft; + case MA_CHANNEL_BACK_RIGHT: + return SoundChannel::BackRight; + case MA_CHANNEL_BACK_CENTER: + return SoundChannel::BackCenter; + case MA_CHANNEL_SIDE_LEFT: + return SoundChannel::SideLeft; + case MA_CHANNEL_SIDE_RIGHT: + return SoundChannel::SideRight; + case MA_CHANNEL_TOP_CENTER: + return SoundChannel::TopCenter; + case MA_CHANNEL_TOP_FRONT_LEFT: + return SoundChannel::TopFrontLeft; + case MA_CHANNEL_TOP_FRONT_RIGHT: + return SoundChannel::TopFrontRight; + case MA_CHANNEL_TOP_FRONT_CENTER: + return SoundChannel::TopFrontCenter; + case MA_CHANNEL_TOP_BACK_LEFT: + return SoundChannel::TopBackLeft; + case MA_CHANNEL_TOP_BACK_RIGHT: + return SoundChannel::TopBackRight; + default: + assert(soundChannel == MA_CHANNEL_TOP_BACK_CENTER); + return SoundChannel::TopBackCenter; + } +} + + //////////////////////////////////////////////////////////// Time MiniaudioUtils::getPlayingOffset(ma_sound& sound) { diff --git a/src/SFML/Audio/MiniaudioUtils.hpp b/src/SFML/Audio/MiniaudioUtils.hpp index cc61a3c73..a9d86ce63 100644 --- a/src/SFML/Audio/MiniaudioUtils.hpp +++ b/src/SFML/Audio/MiniaudioUtils.hpp @@ -44,9 +44,10 @@ class Time; namespace priv::MiniaudioUtils { -[[nodiscard]] ma_channel soundChannelToMiniaudioChannel(SoundChannel soundChannel); -[[nodiscard]] Time getPlayingOffset(ma_sound& sound); -[[nodiscard]] ma_uint64 getFrameIndex(ma_sound& sound, Time timeOffset); +[[nodiscard]] ma_channel soundChannelToMiniaudioChannel(SoundChannel soundChannel); +[[nodiscard]] SoundChannel miniaudioChannelToSoundChannel(ma_channel soundChannel); +[[nodiscard]] Time getPlayingOffset(ma_sound& sound); +[[nodiscard]] ma_uint64 getFrameIndex(ma_sound& sound, Time timeOffset); void reinitializeSound(ma_sound& sound, const std::function& initializeFn); void initializeSound(const ma_data_source_vtable& vtable, diff --git a/src/SFML/Audio/SoundFileReaderWav.cpp b/src/SFML/Audio/SoundFileReaderWav.cpp index 6769f59d8..469109837 100644 --- a/src/SFML/Audio/SoundFileReaderWav.cpp +++ b/src/SFML/Audio/SoundFileReaderWav.cpp @@ -25,82 +25,64 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// +#include #include #include #include -#include +#include #include +#include #include #include -#include namespace { -// The following functions read integers as little endian and -// return them in the host byte order - -bool decode(sf::InputStream& stream, std::uint8_t& value) +ma_result onRead(ma_decoder* decoder, void* buffer, size_t bytesToRead, size_t* bytesRead) { - return static_cast(stream.read(&value, sizeof(value))) == sizeof(value); + auto* stream = static_cast(decoder->pUserData); + const auto count = stream->read(buffer, static_cast(bytesToRead)); + + if (count < 0) + return MA_ERROR; + + *bytesRead = static_cast(count); + return MA_SUCCESS; } -bool decode(sf::InputStream& stream, std::int16_t& value) +ma_result onSeek(ma_decoder* decoder, ma_int64 byteOffset, ma_seek_origin origin) { - std::byte bytes[sizeof(value)]; - if (static_cast(stream.read(bytes, static_cast(sizeof(bytes)))) != sizeof(bytes)) - return false; + auto* stream = static_cast(decoder->pUserData); - value = sf::toInteger(bytes[0], bytes[1]); + switch (origin) + { + case ma_seek_origin_start: + { + if (stream->seek(byteOffset) < 0) + return MA_ERROR; - return true; + return MA_SUCCESS; + } + case ma_seek_origin_current: + { + const auto currentPosition = stream->tell(); + + if (currentPosition < 0) + return MA_ERROR; + + if (stream->seek(stream->tell() + byteOffset) < 0) + return MA_ERROR; + + return MA_SUCCESS; + } + // According to miniaudio comments, ma_seek_origin_end is not used by decoders + default: + return MA_ERROR; + } } - -bool decode(sf::InputStream& stream, std::uint16_t& value) -{ - std::byte bytes[sizeof(value)]; - if (static_cast(stream.read(bytes, static_cast(sizeof(bytes)))) != sizeof(bytes)) - return false; - - value = sf::toInteger(bytes[0], bytes[1]); - - return true; -} - -bool decode24bit(sf::InputStream& stream, std::uint32_t& value) -{ - std::byte bytes[3]; - if (static_cast(stream.read(bytes, static_cast(sizeof(bytes)))) != sizeof(bytes)) - return false; - - value = sf::toInteger(bytes[0], bytes[1], bytes[2]); - - return true; -} - -bool decode(sf::InputStream& stream, std::uint32_t& value) -{ - std::byte bytes[sizeof(value)]; - if (static_cast(stream.read(bytes, static_cast(sizeof(bytes)))) != sizeof(bytes)) - return false; - - value = sf::toInteger(bytes[0], bytes[1], bytes[2], bytes[3]); - - return true; -} - -const std::uint64_t mainChunkSize = 12; - -const std::uint16_t waveFormatPcm = 1; - -const std::uint16_t waveFormatExtensible = 65534; - -const char* waveSubformatPcm = - "\x01\x00\x00\x00\x00\x00\x10\x00" - "\x80\x00\x00\xAA\x00\x38\x9B\x71"; } // namespace namespace sf::priv @@ -108,331 +90,114 @@ namespace sf::priv //////////////////////////////////////////////////////////// bool SoundFileReaderWav::check(InputStream& stream) { - char header[mainChunkSize]; - if (stream.read(header, sizeof(header)) < static_cast(sizeof(header))) - return false; + auto config = ma_decoder_config_init_default(); + config.encodingFormat = ma_encoding_format_wav; + config.format = ma_format_s16; + ma_decoder decoder{}; - return (header[0] == 'R') && (header[1] == 'I') && (header[2] == 'F') && (header[3] == 'F') && (header[8] == 'W') && - (header[9] == 'A') && (header[10] == 'V') && (header[11] == 'E'); + if (ma_decoder_init(&onRead, &onSeek, &stream, &config, &decoder) == MA_SUCCESS) + { + ma_decoder_uninit(&decoder); + return true; + } + + return false; +} + + +//////////////////////////////////////////////////////////// +SoundFileReaderWav::~SoundFileReaderWav() +{ + if (m_decoder) + { + if (const ma_result result = ma_decoder_uninit(&*m_decoder); result != MA_SUCCESS) + err() << "Failed to uninitialize wav decoder: " << ma_result_description(result) << std::endl; + } } //////////////////////////////////////////////////////////// std::optional SoundFileReaderWav::open(InputStream& stream) { - m_stream = &stream; + if (m_decoder) + { + if (const ma_result result = ma_decoder_uninit(&*m_decoder); result != MA_SUCCESS) + { + err() << "Failed to uninitialize wav decoder: " << ma_result_description(result) << std::endl; + return std::nullopt; + } + } + else + { + m_decoder.emplace(); + } - auto info = parseHeader(); - if (!info) - err() << "Failed to open WAV sound file (invalid or unsupported file)" << std::endl; + auto config = ma_decoder_config_init_default(); + config.encodingFormat = ma_encoding_format_wav; + config.format = ma_format_s16; - return info; + if (const ma_result result = ma_decoder_init(&onRead, &onSeek, &stream, &config, &*m_decoder); result != MA_SUCCESS) + { + err() << "Failed to initialize wav decoder: " << ma_result_description(result) << std::endl; + m_decoder = std::nullopt; + return std::nullopt; + } + + ma_uint64 frameCount{}; + if (const ma_result result = ma_decoder_get_available_frames(&*m_decoder, &frameCount); result != MA_SUCCESS) + { + err() << "Failed to get available frames from wav decoder: " << ma_result_description(result) << std::endl; + return std::nullopt; + } + + auto format = ma_format_unknown; + ma_uint32 sampleRate{}; + std::array channelMap{}; + if (const ma_result result = ma_decoder_get_data_format(&*m_decoder, + &format, + &m_channelCount, + &sampleRate, + channelMap.data(), + channelMap.size()); + result != MA_SUCCESS) + { + err() << "Failed to get data format from wav decoder: " << ma_result_description(result) << std::endl; + return std::nullopt; + } + + std::vector soundChannels; + soundChannels.reserve(m_channelCount); + + for (auto i = 0u; i < m_channelCount; ++i) + soundChannels.emplace_back(priv::MiniaudioUtils::miniaudioChannelToSoundChannel(channelMap[i])); + + return Info{frameCount * m_channelCount, m_channelCount, sampleRate, std::move(soundChannels)}; } //////////////////////////////////////////////////////////// void SoundFileReaderWav::seek(std::uint64_t sampleOffset) { - assert(m_stream && "Input stream cannot be null. Call SoundFileReaderWav::open() to initialize it."); + assert(m_decoder && "wav decoder not initialized. Call SoundFileReaderWav::open() to initialize it."); - if (m_stream->seek(static_cast(m_dataStart + sampleOffset * m_bytesPerSample) == -1)) - err() << "Failed to seek WAV sound stream" << std::endl; + if (const ma_result result = ma_decoder_seek_to_pcm_frame(&*m_decoder, sampleOffset / m_channelCount); + result != MA_SUCCESS) + err() << "Failed to seek wav sound stream: " << ma_result_description(result) << std::endl; } //////////////////////////////////////////////////////////// std::uint64_t SoundFileReaderWav::read(std::int16_t* samples, std::uint64_t maxCount) { - assert(m_stream && "Input stream cannot be null. Call SoundFileReaderWav::open() to initialize it."); + assert(m_decoder && "wav decoder not initialized. Call SoundFileReaderWav::open() to initialize it."); - std::uint64_t count = 0; - const auto startPos = static_cast(m_stream->tell()); + ma_uint64 framesRead{}; - // Tracking of m_dataEnd is important to prevent sf::Music from reading - // data until EOF, as WAV files may have metadata at the end. - while ((count < maxCount) && (startPos + count * m_bytesPerSample < m_dataEnd)) - { - switch (m_bytesPerSample) - { - case 1: - { - std::uint8_t sample = 0; - if (decode(*m_stream, sample)) - *samples++ = static_cast((static_cast(sample) - 128) << 8); - else - return count; - break; - } + if (const ma_result result = ma_decoder_read_pcm_frames(&*m_decoder, samples, maxCount / m_channelCount, &framesRead); + result != MA_SUCCESS) + err() << "Failed to read from wav sound stream: " << ma_result_description(result) << std::endl; - case 2: - { - std::int16_t sample = 0; - if (decode(*m_stream, sample)) - *samples++ = sample; - else - return count; - break; - } - - case 3: - { - std::uint32_t sample = 0; - if (decode24bit(*m_stream, sample)) - *samples++ = static_cast(sample >> 8); - else - return count; - break; - } - - case 4: - { - std::uint32_t sample = 0; - if (decode(*m_stream, sample)) - *samples++ = static_cast(sample >> 16); - else - return count; - break; - } - - default: - { - assert(false && "Invalid bytes per sample. Must be 1, 2, 3, or 4."); - return 0; - } - } - - ++count; - } - - return count; -} - - -//////////////////////////////////////////////////////////// -std::optional SoundFileReaderWav::parseHeader() -{ - assert(m_stream && "Input stream cannot be null. Call SoundFileReaderWav::open() to initialize it."); - - // If we are here, it means that the first part of the header - // (the format) has already been checked - char mainChunk[mainChunkSize]; - if (static_cast(m_stream->read(mainChunk, static_cast(sizeof(mainChunk)))) != - sizeof(mainChunk)) - return std::nullopt; - - // Parse all the sub-chunks - Info info; - bool dataChunkFound = false; - while (!dataChunkFound) - { - // Parse the sub-chunk id and size - char subChunkId[4]; - if (static_cast(m_stream->read(subChunkId, static_cast(sizeof(subChunkId)))) != - sizeof(subChunkId)) - return std::nullopt; - std::uint32_t subChunkSize = 0; - if (!decode(*m_stream, subChunkSize)) - return std::nullopt; - const std::int64_t subChunkStart = m_stream->tell(); - if (subChunkStart == -1) - return std::nullopt; - - // Check which chunk it is - if ((subChunkId[0] == 'f') && (subChunkId[1] == 'm') && (subChunkId[2] == 't') && (subChunkId[3] == ' ')) - { - // "fmt" chunk - - // Audio format - std::uint16_t format = 0; - if (!decode(*m_stream, format)) - return std::nullopt; - if ((format != waveFormatPcm) && (format != waveFormatExtensible)) - return std::nullopt; - - // Channel count - std::uint16_t channelCount = 0; - if (!decode(*m_stream, channelCount)) - return std::nullopt; - info.channelCount = channelCount; - - // Sample rate - std::uint32_t sampleRate = 0; - if (!decode(*m_stream, sampleRate)) - return std::nullopt; - info.sampleRate = sampleRate; - - // Byte rate - std::uint32_t byteRate = 0; - if (!decode(*m_stream, byteRate)) - return std::nullopt; - - // Block align - std::uint16_t blockAlign = 0; - if (!decode(*m_stream, blockAlign)) - return std::nullopt; - - // Bits per sample - std::uint16_t bitsPerSample = 0; - if (!decode(*m_stream, bitsPerSample)) - return std::nullopt; - if (bitsPerSample != 8 && bitsPerSample != 16 && bitsPerSample != 24 && bitsPerSample != 32) - { - err() << "Unsupported sample size: " << bitsPerSample - << " bit (Supported sample sizes are 8/16/24/32 bit)" << std::endl; - return std::nullopt; - } - m_bytesPerSample = bitsPerSample / 8; - - if (format == waveFormatExtensible) - { - // Extension size - std::uint16_t extensionSize = 0; - if (!decode(*m_stream, extensionSize)) - return std::nullopt; - - // Valid bits per sample - std::uint16_t validBitsPerSample = 0; - if (!decode(*m_stream, validBitsPerSample)) - return std::nullopt; - - // Channel mask - std::uint32_t channelMask = 0; - if (!decode(*m_stream, channelMask)) - return std::nullopt; - - // NOLINTBEGIN(readability-identifier-naming) - // For WAVE channel mapping refer to: https://learn.microsoft.com/en-us/previous-versions/windows/hardware/design/dn653308(v=vs.85)#default-channel-ordering - static constexpr auto SPEAKER_FRONT_LEFT = 0x1u; - static constexpr auto SPEAKER_FRONT_RIGHT = 0x2u; - static constexpr auto SPEAKER_FRONT_CENTER = 0x4u; - static constexpr auto SPEAKER_LOW_FREQUENCY = 0x8u; - static constexpr auto SPEAKER_BACK_LEFT = 0x10u; - static constexpr auto SPEAKER_BACK_RIGHT = 0x20u; - static constexpr auto SPEAKER_FRONT_LEFT_OF_CENTER = 0x40u; - static constexpr auto SPEAKER_FRONT_RIGHT_OF_CENTER = 0x80u; - static constexpr auto SPEAKER_BACK_CENTER = 0x100u; - static constexpr auto SPEAKER_SIDE_LEFT = 0x200u; - static constexpr auto SPEAKER_SIDE_RIGHT = 0x400u; - static constexpr auto SPEAKER_TOP_CENTER = 0x800u; - static constexpr auto SPEAKER_TOP_FRONT_LEFT = 0x1000u; - static constexpr auto SPEAKER_TOP_FRONT_CENTER = 0x2000u; - static constexpr auto SPEAKER_TOP_FRONT_RIGHT = 0x4000u; - static constexpr auto SPEAKER_TOP_BACK_LEFT = 0x8000u; - static constexpr auto SPEAKER_TOP_BACK_CENTER = 0x10000u; - static constexpr auto SPEAKER_TOP_BACK_RIGHT = 0x20000u; - // NOLINTEND(readability-identifier-naming) - - info.channelMap.clear(); - - const auto checkChannel = [channelMask, &info](auto bit, auto soundChannel) - { - if ((channelMask & bit) != 0) - info.channelMap.push_back(soundChannel); - }; - - checkChannel(SPEAKER_FRONT_LEFT, SoundChannel::FrontLeft); - checkChannel(SPEAKER_FRONT_RIGHT, SoundChannel::FrontRight); - checkChannel(SPEAKER_FRONT_CENTER, SoundChannel::FrontCenter); - checkChannel(SPEAKER_LOW_FREQUENCY, SoundChannel::LowFrequencyEffects); - checkChannel(SPEAKER_BACK_LEFT, SoundChannel::BackLeft); - checkChannel(SPEAKER_BACK_RIGHT, SoundChannel::BackRight); - checkChannel(SPEAKER_FRONT_LEFT_OF_CENTER, SoundChannel::FrontLeftOfCenter); - checkChannel(SPEAKER_FRONT_RIGHT_OF_CENTER, SoundChannel::FrontRightOfCenter); - checkChannel(SPEAKER_BACK_CENTER, SoundChannel::BackCenter); - checkChannel(SPEAKER_SIDE_LEFT, SoundChannel::SideLeft); - checkChannel(SPEAKER_SIDE_RIGHT, SoundChannel::SideRight); - checkChannel(SPEAKER_TOP_CENTER, SoundChannel::TopCenter); - checkChannel(SPEAKER_TOP_FRONT_LEFT, SoundChannel::TopFrontLeft); - checkChannel(SPEAKER_TOP_FRONT_CENTER, SoundChannel::TopFrontCenter); - checkChannel(SPEAKER_TOP_FRONT_RIGHT, SoundChannel::TopFrontRight); - checkChannel(SPEAKER_TOP_BACK_LEFT, SoundChannel::TopBackLeft); - checkChannel(SPEAKER_TOP_BACK_CENTER, SoundChannel::TopBackCenter); - checkChannel(SPEAKER_TOP_BACK_RIGHT, SoundChannel::TopBackRight); - - assert(info.channelCount == info.channelMap.size()); - - if (info.channelCount != info.channelMap.size()) - { - err() << "WAV sound file channel count does not match number of set bits in channel mask" << std::endl; - return std::nullopt; - } - - // Subformat - char subformat[16]; - if (static_cast(m_stream->read(subformat, static_cast(sizeof(subformat)))) != - sizeof(subformat)) - return std::nullopt; - - if (std::memcmp(subformat, waveSubformatPcm, sizeof(subformat)) != 0) - { - err() << "Unsupported format: extensible format with non-PCM subformat" << std::endl; - return std::nullopt; - } - - if (validBitsPerSample != bitsPerSample) - { - err() << "Unsupported format: sample size (" << validBitsPerSample - << " bits) and " - "sample container size (" - << bitsPerSample << " bits) differ" << std::endl; - return std::nullopt; - } - } - else - { - // If we don't have a waveFormatExtensible header, fill the channel map based on a best guess for mono/stereo - info.channelMap.clear(); - - if (info.channelCount == 0) - { - err() << "WAV sound file channel count 0" << std::endl; - return std::nullopt; - } - else if (info.channelCount == 1) - { - info.channelMap.push_back(SoundChannel::Mono); - } - else if (info.channelCount == 2) - { - info.channelMap.push_back(SoundChannel::FrontLeft); - info.channelMap.push_back(SoundChannel::FrontRight); - } - else - { - info.channelMap.push_back(SoundChannel::FrontLeft); - info.channelMap.push_back(SoundChannel::FrontRight); - - for (auto i = 2u; i < info.channelCount; ++i) - info.channelMap.push_back(SoundChannel::Unspecified); - } - } - - // Skip potential extra information - if (m_stream->seek(subChunkStart + subChunkSize) == -1) - return std::nullopt; - } - else if ((subChunkId[0] == 'd') && (subChunkId[1] == 'a') && (subChunkId[2] == 't') && (subChunkId[3] == 'a')) - { - // "data" chunk - - // Compute the total number of samples - info.sampleCount = subChunkSize / m_bytesPerSample; - - // Store the start and end position of samples in the file - m_dataStart = static_cast(subChunkStart); - m_dataEnd = m_dataStart + info.sampleCount * m_bytesPerSample; - - dataChunkFound = true; - } - else - { - // unknown chunk, skip it - if (m_stream->seek(m_stream->tell() + subChunkSize) == -1) - return std::nullopt; - } - } - - return info; + return framesRead * m_channelCount; } } // namespace sf::priv diff --git a/src/SFML/Audio/SoundFileReaderWav.hpp b/src/SFML/Audio/SoundFileReaderWav.hpp index fc47c061a..d666897bc 100644 --- a/src/SFML/Audio/SoundFileReaderWav.hpp +++ b/src/SFML/Audio/SoundFileReaderWav.hpp @@ -29,6 +29,8 @@ //////////////////////////////////////////////////////////// #include +#include + #include #include @@ -58,6 +60,12 @@ public: //////////////////////////////////////////////////////////// [[nodiscard]] static bool check(InputStream& stream); + //////////////////////////////////////////////////////////// + /// \brief Destructor + /// + //////////////////////////////////////////////////////////// + ~SoundFileReaderWav() override; + //////////////////////////////////////////////////////////// /// \brief Open a sound file for reading /// @@ -95,23 +103,11 @@ public: [[nodiscard]] std::uint64_t read(std::int16_t* samples, std::uint64_t maxCount) override; private: - //////////////////////////////////////////////////////////// - /// \brief Read the header of the open file - /// - /// \param info Attributes of the sound file - /// - /// \return True on success, false on error - /// - //////////////////////////////////////////////////////////// - std::optional parseHeader(); - //////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// - InputStream* m_stream{}; //!< Source stream to read from - unsigned int m_bytesPerSample{}; //!< Size of a sample, in bytes - std::uint64_t m_dataStart{}; //!< Starting position of the audio data in the open file - std::uint64_t m_dataEnd{}; //!< Position one byte past the end of the audio data in the open file + std::optional m_decoder; //!< wav decoder + ma_uint32 m_channelCount{}; //!< Number of channels }; } // namespace sf::priv