From 43da727dd956f78d4987e8e4ee461a77b37cb3ee Mon Sep 17 00:00:00 2001 From: Don Collins Date: Tue, 21 Jul 2026 13:39:09 -0700 Subject: [PATCH] feat: add dual SDL2/SDL3 support with CMake option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add USE_SDL CMake option (Auto/SDL2/SDL3) — prefers SDL3 when available - All source files use #ifdef USE_SDL3 guards for API differences: Event types, key struct members, modifier keys, timer/window functions, ImGui backend (sdl2/sdl3), GLSL version (150/300 es), audio capture API, OpenGL proc loader, drop file handling - Conditional SDL linkage: SDL3::SDL3 vs SDL2::SDL2 + SDL2::SDL2main - New cmake/SDL3Target.cmake for SDL3 target verification - Updated ImGui.cmake for conditional backend and kept ImGuiDemo target - Updated dependencies_check.cmake and packaging-linux.cmake for SDL version - Added audio device hotplug support for SDL3 - Added CurrentAudioLevel() and RefreshDeviceList() to AudioCapture - Refactored drop file handler into HandleDropFile() method - SDL3 audio capture uses SDL_AudioStream API - SDL key constants remapped via #undef/#define for SDL3 naming - Guarded projectm_create_with_opengl_load_proc for older libprojectM - CI builds both SDL2 and SDL3 on all four platforms: Ubuntu Linux, Arch Linux, Windows (vcpkg), macOS (Homebrew) - vcpkg.json includes both sdl2 and sdl3 - Windows runner pinned to windows-2022 for VS 2022 compatibility --- .github/workflows/buildcheck.yaml | 197 +++++++++++++++++++---- CMakeLists.txt | 62 ++++++-- ImGui.cmake | 80 +++++++--- cmake/SDL3Target.cmake | 13 ++ dependencies_check.cmake | 12 +- packaging-linux.cmake | 12 +- src/AudioCapture.cpp | 17 ++ src/AudioCapture.h | 13 ++ src/AudioCaptureImpl_SDL.cpp | 177 +++++++++++++++++++-- src/AudioCaptureImpl_SDL.h | 31 +++- src/AudioCaptureImpl_WASAPI.cpp | 10 ++ src/AudioCaptureImpl_WASAPI.h | 11 ++ src/CMakeLists.txt | 35 +++- src/FPSLimiter.cpp | 8 +- src/FPSLimiter.h | 2 +- src/ProjectMSDLApplication.cpp | 13 ++ src/ProjectMWrapper.cpp | 22 ++- src/RenderLoop.cpp | 256 +++++++++++++++++++----------- src/RenderLoop.h | 6 + src/SDLRenderingWindow.cpp | 84 +++++++++- src/SDLRenderingWindow.h | 6 +- src/gui/PresetSelection.h | 6 +- src/gui/ProjectMGUI.cpp | 38 +++++ src/gui/ProjectMGUI.h | 6 +- src/main.cpp | 6 +- vcpkg.json | 1 + 26 files changed, 922 insertions(+), 202 deletions(-) create mode 100644 cmake/SDL3Target.cmake diff --git a/.github/workflows/buildcheck.yaml b/.github/workflows/buildcheck.yaml index af3a1755..9d2f7832 100644 --- a/.github/workflows/buildcheck.yaml +++ b/.github/workflows/buildcheck.yaml @@ -1,5 +1,6 @@ # Build check workflow # Used to check if projectMSDL compiles with upstream master of libprojectM. +# Tests both SDL2 and SDL3 builds on all platforms. # The resulting binaries are not considered for public use though. name: Build Check @@ -7,15 +8,41 @@ on: [ push, pull_request ] jobs: build-linux: - name: Ubuntu Linux, x86_64 + name: Ubuntu Linux ${{ matrix.sdl_version }}, x86_64 runs-on: ubuntu-latest + strategy: + matrix: + sdl_version: [SDL2, SDL3] steps: - - name: Install Build Dependencies run: | sudo apt update - sudo apt install build-essential libgl1-mesa-dev mesa-common-dev libsdl2-dev libpoco-dev ninja-build libssl-dev libfreetype6-dev + if [ "${{ matrix.sdl_version }}" = "SDL2" ]; then + sudo apt install build-essential libgl1-mesa-dev mesa-common-dev libsdl2-dev libpoco-dev ninja-build libssl-dev libfreetype6-dev + else + sudo apt install build-essential libgl1-mesa-dev mesa-common-dev libpoco-dev ninja-build libssl-dev libfreetype6-dev libasound2-dev libpulse-dev libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxi-dev libxfixes-dev libxss-dev libxtst-dev libxkbcommon-dev libdrm-dev libgbm-dev libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev + fi + + - name: Checkout SDL3 Sources + if: matrix.sdl_version == 'SDL3' + uses: actions/checkout@v6 + with: + repository: libsdl-org/SDL + path: sdl3 + ref: 'main' + submodules: recursive + + - name: Build/Install SDL3 + if: matrix.sdl_version == 'SDL3' + run: | + mkdir cmake-build-sdl3 + cmake -G Ninja -S sdl3 -B cmake-build-sdl3 \ + -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install-sdl3 + cmake --build cmake-build-sdl3 --parallel + cmake --install ${{ github.workspace }}/cmake-build-sdl3 # We need to build/link Poco ourselves as static libraries, because Ubuntu Jammy ships with a broken Poco 1.11.0 - name: Checkout Poco Sources @@ -64,42 +91,113 @@ jobs: cmake --build cmake-build-libprojectm --parallel cmake --install "${{ github.workspace }}/cmake-build-libprojectm" - - name: Checkout frontend-sdl2 Sources + - name: Checkout frontend-sdl-cpp Sources uses: actions/checkout@v6 with: - path: frontend-sdl2 + path: frontend-sdl-cpp submodules: recursive - - name: Build frontend-sdl2 + - name: Build frontend-sdl-cpp + env: + CMAKE_PREFIX_PATH: "${{ github.workspace }}/install-libprojectm;${{ github.workspace }}/install-poco" run: | - mkdir cmake-build-frontend-sdl2 - cmake -G Ninja -S frontend-sdl2 -B cmake-build-frontend-sdl2 \ + if [ "${{ matrix.sdl_version }}" = "SDL3" ]; then + CMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH};${{ github.workspace }}/install-sdl3" + fi + mkdir cmake-build-frontend + cmake -G Ninja -S frontend-sdl-cpp -B cmake-build-frontend \ -DCMAKE_BUILD_TYPE=Release \ - "-DCMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/install-libprojectm;${GITHUB_WORKSPACE}/install-poco" \ - "-DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install-frontend-sdl2" - cmake --build cmake-build-frontend-sdl2 --parallel + "-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}" \ + "-DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install-frontend" \ + -DUSE_SDL=${{ matrix.sdl_version }} + cmake --build cmake-build-frontend --parallel - name: Package projectMSDL run: | - cd cmake-build-frontend-sdl2 + cd cmake-build-frontend cpack -G DEB - name: Upload Artifact uses: actions/upload-artifact@v6 with: - name: projectMSDL-buildcheck-linux - path: cmake-build-frontend-sdl2/*.deb + name: projectMSDL-buildcheck-linux-${{ matrix.sdl_version }} + path: cmake-build-frontend/*.deb + + build-arch: + name: Arch Linux ${{ matrix.sdl_version }}, x86_64 + runs-on: ubuntu-latest + container: archlinux:latest + strategy: + matrix: + sdl_version: [SDL2, SDL3] + + steps: + - name: Install Build Dependencies + run: | + pacman -Syu --noconfirm + if [ "${{ matrix.sdl_version }}" = "SDL2" ]; then + pacman -S --noconfirm base-devel cmake ninja git poco glew freetype2 sdl2 + else + pacman -S --noconfirm base-devel cmake ninja git poco glew freetype2 sdl3 + fi + + - name: Checkout libprojectM Sources + uses: actions/checkout@v6 + with: + repository: projectM-visualizer/projectm + path: projectm + submodules: recursive + + - name: Build/Install libprojectM + run: | + mkdir cmake-build-libprojectm + cmake -G Ninja -S projectm -B cmake-build-libprojectm \ + -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install-libprojectm + cmake --build cmake-build-libprojectm --parallel + cmake --install cmake-build-libprojectm || \ + cmake --build cmake-build-libprojectm --target install || true + + - name: Checkout frontend-sdl-cpp Sources + uses: actions/checkout@v6 + with: + path: frontend-sdl-cpp + submodules: recursive + + - name: Build frontend-sdl-cpp + run: | + mkdir cmake-build-frontend + cmake -G Ninja -S frontend-sdl-cpp -B cmake-build-frontend \ + -DCMAKE_BUILD_TYPE=Release \ + "-DCMAKE_PREFIX_PATH=${{ github.workspace }}/install-libprojectm" \ + "-DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install-frontend" \ + -DUSE_SDL=${{ matrix.sdl_version }} + cmake --build cmake-build-frontend --parallel + + - name: Package projectMSDL + run: | + cd cmake-build-frontend + cpack -G TGZ + + - name: Upload Artifact + uses: actions/upload-artifact@v6 + with: + name: projectMSDL-buildcheck-arch-${{ matrix.sdl_version }} + path: cmake-build-frontend/*.tar.gz build-windows: - name: Windows, x64 - runs-on: windows-latest + name: Windows ${{ matrix.sdl_version }}, x64 + runs-on: windows-2022 + strategy: + matrix: + sdl_version: [SDL2, SDL3] env: USERNAME: projectM-visualizer VCPKG_EXE: ${{ github.workspace }}/vcpkg/vcpkg FEED_URL: https://nuget.pkg.github.com/projectM-visualizer/index.json VCPKG_BINARY_SOURCES: "clear;nuget,https://nuget.pkg.github.com/projectM-visualizer/index.json,readwrite" - steps: - name: Checkout vcpkg uses: actions/checkout@v6 @@ -143,33 +241,61 @@ jobs: - name: Checkout projectMSDL Sources uses: actions/checkout@v6 with: - path: frontend-sdl2 + path: frontend-sdl-cpp submodules: recursive - name: Build projectMSDL run: | - mkdir cmake-build-frontend-sdl2 - cmake -G "Visual Studio 17 2022" -A "X64" -S "${{ github.workspace }}/frontend-sdl2" -B "${{ github.workspace }}/cmake-build-frontend-sdl2" -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=x64-windows-static -DCMAKE_PREFIX_PATH="${{ github.workspace }}/install-libprojectm" -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/install-frontend-sdl2" -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$:Debug>" -DCMAKE_VERBOSE_MAKEFILE=YES -DSDL2_LINKAGE=static -DBUILD_TESTING=YES - cmake --build "${{ github.workspace }}/cmake-build-frontend-sdl2" --parallel --config Release + mkdir cmake-build-frontend + cmake -G "Visual Studio 17 2022" -A "X64" -S "${{ github.workspace }}/frontend-sdl-cpp" -B "${{ github.workspace }}/cmake-build-frontend" -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=x64-windows-static -DCMAKE_PREFIX_PATH="${{ github.workspace }}/install-libprojectm" -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/install-frontend" -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$:Debug>" -DCMAKE_VERBOSE_MAKEFILE=YES -DSDL_LINKAGE=static -DBUILD_TESTING=YES -DUSE_SDL=${{ matrix.sdl_version }} + cmake --build "${{ github.workspace }}/cmake-build-frontend" --parallel --config Release - name: Package projectMSDL run: | - cd cmake-build-frontend-sdl2 + cd cmake-build-frontend cpack -G ZIP - name: Upload Artifact uses: actions/upload-artifact@v6 with: - name: projectMSDL-buildcheck-windows - path: cmake-build-frontend-sdl2/*.zip + name: projectMSDL-buildcheck-windows-${{ matrix.sdl_version }} + path: cmake-build-frontend/*.zip build-darwin: - name: macOS, x86_64 + name: macOS ${{ matrix.sdl_version }}, x86_64 runs-on: macos-latest + strategy: + matrix: + sdl_version: [SDL2, SDL3] steps: - name: Install Build Dependencies - run: brew install sdl2 ninja googletest poco + run: | + if [ "${{ matrix.sdl_version }}" = "SDL2" ]; then + brew install sdl2 ninja googletest poco + else + brew install ninja googletest poco + fi + + - name: Checkout SDL3 Sources + if: matrix.sdl_version == 'SDL3' + uses: actions/checkout@v6 + with: + repository: libsdl-org/SDL + path: sdl3 + ref: 'main' + submodules: recursive + + - name: Build/Install SDL3 + if: matrix.sdl_version == 'SDL3' + run: | + mkdir cmake-build-sdl3 + cmake -G Ninja -S sdl3 -B cmake-build-sdl3 \ + -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install-sdl3 + cmake --build cmake-build-sdl3 --parallel + cmake --install ${{ github.workspace }}/cmake-build-sdl3 - name: Checkout libprojectM Sources uses: actions/checkout@v6 @@ -188,22 +314,27 @@ jobs: - name: Checkout projectMSDL Sources uses: actions/checkout@v6 with: - path: frontend-sdl2 + path: frontend-sdl-cpp submodules: recursive - name: Build projectMSDL + env: + CMAKE_PREFIX_PATH: "${{ github.workspace }}/install-libprojectm" run: | - mkdir cmake-build-frontend-sdl2 - cmake -G Ninja -S "${{ github.workspace }}/frontend-sdl2" -B "${{ github.workspace }}/cmake-build-frontend-sdl2" -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="${{ github.workspace }}/install-libprojectm" -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/install-frontend-sdl2" - cmake --build "${{ github.workspace }}/cmake-build-frontend-sdl2" --parallel + if [ "${{ matrix.sdl_version }}" = "SDL3" ]; then + CMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH};${{ github.workspace }}/install-sdl3" + fi + mkdir cmake-build-frontend + cmake -G Ninja -S "${{ github.workspace }}/frontend-sdl-cpp" -B "${{ github.workspace }}/cmake-build-frontend" -DCMAKE_BUILD_TYPE=Release "-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}" -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/install-frontend" -DUSE_SDL=${{ matrix.sdl_version }} + cmake --build "${{ github.workspace }}/cmake-build-frontend" --parallel - name: Package projectMSDL run: | - cd cmake-build-frontend-sdl2 + cd cmake-build-frontend cpack -G TGZ - name: Upload Artifact uses: actions/upload-artifact@v6 with: - name: projectMSDL-buildcheck-macos - path: cmake-build-frontend-sdl2/*.tar.gz + name: projectMSDL-buildcheck-macos-${{ matrix.sdl_version }} + path: cmake-build-frontend/*.tar.gz diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ee70505..41685b8b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,21 +83,60 @@ endif() set(PACKAGING_CONFIG_FILE "${DEFAULT_PACKAGING_CONFIG}" CACHE FILEPATH "CPack configuration file to use for packaging. This file must \"include(CPack)\" at the end to work properly.") -set(SDL2_LINKAGE "shared" CACHE STRING "Set to either shared or static to specify how libSDL2 should be linked. Defaults to shared.") -option(ENABLE_FREETYPE "Use the Freetype font rendering library instead of the built-in stb_truetype if available" ON) +# === SDL version selection === +set(USE_SDL "Auto" CACHE STRING "Which SDL version to use: Auto (prefer SDL3, fall back to SDL2), SDL2, or SDL3") +set_property(CACHE USE_SDL PROPERTY STRINGS "Auto" "SDL2" "SDL3") +option(ENABLE_FREETYPE "Use the Freetype font rendering library instead of the built-in stb_truetype if available" ON) set(PRESET_DIRS "" CACHE STRING "List of paths with presets. Will be installed in \"presets\" ") set(TEXTURE_DIRS "" CACHE STRING "List of paths with presets.") -if(NOT SDL2_LINKAGE STREQUAL "shared" AND NOT SDL2_LINKAGE STREQUAL "static") - message(FATAL_ERROR "Invalid libSDL2 linkage provided in SDL2_LINKAGE: \"${SDL2_LINKAGE}\".\n" - "Please specify either \"shared\" or \"static\"." - ) +find_package(projectM4 REQUIRED COMPONENTS Playlist) + +# --- Resolve SDL version --- +if(USE_SDL STREQUAL "Auto") + find_package(SDL3 QUIET) + if(SDL3_FOUND) + set(USE_SDL3 ON) + set(SDL_VERSION "${SDL3_VERSION}") + message(STATUS "Using SDL3 (found ${SDL3_VERSION})") + else() + find_package(SDL2 REQUIRED) + set(SDL_VERSION "${SDL2_VERSION}") + message(STATUS "Using SDL2 (found ${SDL2_VERSION})") + endif() +elseif(USE_SDL STREQUAL "SDL3") + find_package(SDL3 REQUIRED) + set(USE_SDL3 ON) + set(SDL_VERSION "${SDL3_VERSION}") + message(STATUS "Using SDL3 (requested, found ${SDL3_VERSION})") +else() + find_package(SDL2 REQUIRED) + set(SDL_VERSION "${SDL2_VERSION}") + message(STATUS "Using SDL2 (requested, found ${SDL2_VERSION})") +endif() + +# --- SDL linkage type --- +if(USE_SDL3) + set(SDL_LINKAGE "shared" CACHE STRING "Set to either shared or static to specify how libSDL3 should be linked. Defaults to shared.") + if(NOT SDL_LINKAGE STREQUAL "shared" AND NOT SDL_LINKAGE STREQUAL "static") + message(FATAL_ERROR "Invalid libSDL3 linkage: \"${SDL_LINKAGE}\". Please specify \"shared\" or \"static\".") + endif() + include(SDL3Target) +else() + set(SDL_LINKAGE "shared" CACHE STRING "Set to either shared or static to specify how libSDL2 should be linked. Defaults to shared.") + if(NOT SDL_LINKAGE STREQUAL "shared" AND NOT SDL_LINKAGE STREQUAL "static") + message(FATAL_ERROR "Invalid libSDL2 linkage: \"${SDL_LINKAGE}\". Please specify \"shared\" or \"static\".") + endif() + include(SDL2Target) +endif() + +# --- Propagate SDL version to all targets --- +if(USE_SDL3) + add_compile_definitions(USE_SDL3) endif() -find_package(projectM4 REQUIRED COMPONENTS Playlist) -find_package(SDL2 REQUIRED) if (NOT ENABLE_GLES) find_package(OpenGL REQUIRED) else () @@ -117,7 +156,6 @@ if(ENABLE_FREETYPE) find_package(Freetype) endif() -include(SDL2Target) include(dependencies_check.cmake) include(ImGui.cmake) @@ -133,7 +171,11 @@ if(NOT PACKAGING_CONFIG_FILE STREQUAL "") include(${PACKAGING_CONFIG_FILE}) endif() -message(STATUS "SDL version: ${SDL2_VERSION}") +if(USE_SDL3) + message(STATUS "SDL version: ${SDL3_VERSION} (SDL3)") +else() + message(STATUS "SDL version: ${SDL2_VERSION} (SDL2)") +endif() message(STATUS "Poco version: ${Poco_VERSION}") message(STATUS "projectM version: ${projectM4_VERSION}") if(Freetype_FOUND) diff --git a/ImGui.cmake b/ImGui.cmake index 74d48d9e..caf2b24d 100644 --- a/ImGui.cmake +++ b/ImGui.cmake @@ -1,21 +1,41 @@ +if(USE_SDL3) + set(SDL_IMGUI_BACKEND_SOURCES + vendor/imgui/backends/imgui_impl_sdl3.cpp + vendor/imgui/backends/imgui_impl_sdl3.h + ) + set(SDL_IMGUI_DEMO_MAIN vendor/imgui/examples/example_sdl3_opengl3/main.cpp) +else() + set(SDL_IMGUI_BACKEND_SOURCES + vendor/imgui/backends/imgui_impl_sdl2.cpp + vendor/imgui/backends/imgui_impl_sdl2.h + ) + set(SDL_IMGUI_DEMO_MAIN vendor/imgui/examples/example_sdl2_opengl3/main.cpp) +endif() + add_library(ImGui STATIC vendor/imgui/imgui.cpp vendor/imgui/imgui.h vendor/imgui/imgui_draw.cpp vendor/imgui/imgui_tables.cpp vendor/imgui/imgui_widgets.cpp - vendor/imgui/backends/imgui_impl_sdl2.cpp - vendor/imgui/backends/imgui_impl_sdl2.h + ${SDL_IMGUI_BACKEND_SOURCES} vendor/imgui/backends/imgui_impl_opengl3.cpp vendor/imgui/backends/imgui_impl_opengl3.h vendor/imgui/backends/imgui_impl_opengl3_loader.h ) -target_link_libraries(ImGui - PUBLIC - SDL2::SDL2$<$:-static> - ) +if(USE_SDL3) + target_link_libraries(ImGui + PUBLIC + SDL3::SDL3$<$:-static> + ) +else() + target_link_libraries(ImGui + PUBLIC + SDL2::SDL2$<$:-static> + ) +endif() if(ENABLE_FREETYPE AND Freetype_FOUND) target_sources(ImGui @@ -35,12 +55,21 @@ if(ENABLE_FREETYPE AND Freetype_FOUND) ) endif() -target_include_directories(ImGui - PUBLIC - ${CMAKE_SOURCE_DIR}/vendor/imgui - ${CMAKE_SOURCE_DIR}/vendor/imgui/backends - ${SDL2_INCLUDE_DIRS} - ) +if(USE_SDL3) + target_include_directories(ImGui + PUBLIC + ${CMAKE_SOURCE_DIR}/vendor/imgui + ${CMAKE_SOURCE_DIR}/vendor/imgui/backends + ${SDL3_INCLUDE_DIRS} + ) +else() + target_include_directories(ImGui + PUBLIC + ${CMAKE_SOURCE_DIR}/vendor/imgui + ${CMAKE_SOURCE_DIR}/vendor/imgui/backends + ${SDL2_INCLUDE_DIRS} + ) +endif() if (ENABLE_GLES) target_compile_definitions(ImGui @@ -54,16 +83,25 @@ add_executable(ImGuiBinaryToCompressedC EXCLUDE_FROM_ALL vendor/imgui/misc/fonts/binary_to_compressed_c.cpp ) -# Add SDL2/OpenGL 3 Dear ImGui example application target for testing +# Add SDL/OpenGL 3 Dear ImGui example application target for testing add_executable(ImGuiDemo EXCLUDE_FROM_ALL vendor/imgui/imgui_demo.cpp - vendor/imgui/examples/example_sdl2_opengl3/main.cpp + ${SDL_IMGUI_DEMO_MAIN} ) -target_link_libraries(ImGuiDemo - PRIVATE - ImGui - SDL2::SDL2 - SDL2::SDL2main - OpenGL::GL - ) +if(USE_SDL3) + target_link_libraries(ImGuiDemo + PRIVATE + ImGui + SDL3::SDL3 + OpenGL::GL + ) +else() + target_link_libraries(ImGuiDemo + PRIVATE + ImGui + SDL2::SDL2 + SDL2::SDL2main + OpenGL::GL + ) +endif() diff --git a/cmake/SDL3Target.cmake b/cmake/SDL3Target.cmake new file mode 100644 index 00000000..93e86d26 --- /dev/null +++ b/cmake/SDL3Target.cmake @@ -0,0 +1,13 @@ +# Helper script to set up the SDL3 CMake target and version variable. +# SDL3 has proper CMake config support, so this is simpler than SDL2. +# +# This also defines SDL3_VERSION from the found package and can be used +# to verify a minimum version requirement. + +if(NOT TARGET SDL3::SDL3) + message(FATAL_ERROR "SDL3::SDL3 target not found. Ensure SDL3 is properly installed.") +endif() + +if(SDL3_VERSION AND SDL3_VERSION VERSION_LESS "3.0.0") + message(FATAL_ERROR "SDL3 libraries were found, but have version ${SDL3_VERSION}. At least version 3.0.0 is required.") +endif() diff --git a/dependencies_check.cmake b/dependencies_check.cmake index 1dd04827..25d172ad 100644 --- a/dependencies_check.cmake +++ b/dependencies_check.cmake @@ -2,8 +2,14 @@ if(projectM4_VERSION VERSION_LESS 4.0.0) message(FATAL_ERROR "libprojectM version 4.0.0 or higher is required. Version found: ${projectM4_VERSION}.") endif() -if(SDL2_VERSION VERSION_LESS 2.0.5) - message(FATAL_ERROR "libSDL version 2.0.5 or higher is required. Version found: ${SDL2_VERSION}.") +if(USE_SDL3) + if(SDL3_VERSION VERSION_LESS 3.0.0) + message(FATAL_ERROR "libSDL3 version 3.0.0 or higher is required. Version found: ${SDL3_VERSION}.") + endif() +else() + if(SDL2_VERSION VERSION_LESS 2.0.5) + message(FATAL_ERROR "libSDL2 version 2.0.5 or higher is required. Version found: ${SDL2_VERSION}.") + endif() endif() if(Poco_VERSION VERSION_LESS 1.11.2 AND Poco_VERSION VERSION_GREATER_EQUAL 1.10.0) @@ -14,7 +20,7 @@ See https://github.com/pocoproject/poco/issues/3507 for details on this particul ") endif() -if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND SDL2_VERSION VERSION_LESS 2.0.16) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND NOT USE_SDL3 AND SDL2_VERSION VERSION_LESS 2.0.16) message(AUTHOR_WARNING "NOTE: libSDL 2.0.15 and lower do not support capture from PulseAudio \"monitor\" devices.\n" "It is highly recommended to use at least version 2.0.16!" diff --git a/packaging-linux.cmake b/packaging-linux.cmake index 7b4afdd6..042b9bc7 100644 --- a/packaging-linux.cmake +++ b/packaging-linux.cmake @@ -18,16 +18,20 @@ set(CPACK_SOURCE_GENERATOR TGZ) set(CPACK_DEBIAN_PACKAGE_MAINTAINER "\"The projectM Development Team\" ") set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "A standalone, Milkdrop-like audio visualization application") set(CPACK_DEBIAN_PACKAGE_SECTION "sound") -# Require SDL2 2.0.16 or higher, lower versions can't use monitor devices -set(CPACK_DEBIAN_PACKAGE_DEPENDS "libsdl2-2.0-0 (>=2.0.16), libgl1, libfreetype6") +# Require SDL2 2.0.16 or higher, lower versions can't use monitor devices (SDL2 only) +if(USE_SDL3) + set(CPACK_DEBIAN_PACKAGE_DEPENDS "libgl1, libfreetype6") +else() + set(CPACK_DEBIAN_PACKAGE_DEPENDS "libsdl2-2.0-0 (>=2.0.16), libgl1, libfreetype6") +endif() set(CPACK_DEBIAN_ARCHIVE_TYPE "gnutar") set(CPACK_DEBIAN_COMPRESSION_TYPE "xz") set(CPACK_DEBIAN_PACKAGE_PRIORITY "standard") -set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/projectM-visualizer/frontend-sdl2/") +set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/projectM-visualizer/frontend-sdl-cpp/") # RPM generator set(CPACK_RPM_PACKAGE_LICENSE "GPL") set(CPACK_RPM_PACKAGE_GROUP "Applications/Multimedia") -set(CPACK_RPM_PACKAGE_URL "https://github.com/projectM-visualizer/frontend-sdl2/") +set(CPACK_RPM_PACKAGE_URL "https://github.com/projectM-visualizer/frontend-sdl-cpp/") include(CPack) diff --git a/src/AudioCapture.cpp b/src/AudioCapture.cpp index 44d23638..622469ad 100644 --- a/src/AudioCapture.cpp +++ b/src/AudioCapture.cpp @@ -150,4 +150,21 @@ int AudioCapture::GetInitialAudioDeviceIndex(const AudioDeviceMap& deviceList) deviceList.at(audioDeviceIndex), audioDeviceIndex); return audioDeviceIndex; +} + +float AudioCapture::CurrentAudioLevel() const +{ + if (!_impl) + { + return -1.0f; + } + return _impl->CurrentAudioLevel(); +} + +void AudioCapture::RefreshDeviceList() +{ + if (_impl) + { + _impl->RefreshDeviceList(); + } } \ No newline at end of file diff --git a/src/AudioCapture.h b/src/AudioCapture.h index d3206662..a82dd8aa 100644 --- a/src/AudioCapture.h +++ b/src/AudioCapture.h @@ -59,6 +59,19 @@ class AudioCapture : public Poco::Util::Subsystem */ void FillBuffer(); + /** + * @brief Returns the current audio peak level (0.0-1.0) for display. + * @return The current audio level, or -1.0 if not available. + */ + float CurrentAudioLevel() const; + + /** + * @brief Refreshes the list of available audio devices. + * + * Called on SDL3 audio device hotplug events to update the device list. + */ + void RefreshDeviceList(); + protected: /** * @brief Prints a list of available audio devices on standard output if requested by the user. diff --git a/src/AudioCaptureImpl_SDL.cpp b/src/AudioCaptureImpl_SDL.cpp index 2b9c2b58..eb12bf24 100644 --- a/src/AudioCaptureImpl_SDL.cpp +++ b/src/AudioCaptureImpl_SDL.cpp @@ -4,6 +4,10 @@ #include +#ifdef USE_SDL3 +#include +#endif + AudioCaptureImpl::AudioCaptureImpl() : _requestedSampleCount(projectm_pcm_get_max_samples()) { @@ -11,27 +15,48 @@ AudioCaptureImpl::AudioCaptureImpl() if (targetFps > 0) { _requestedSampleCount = std::min(_requestedSampleFrequency / targetFps, _requestedSampleCount); - // Don't let the buffer get too small to prevent excessive updates calls. - // 300 samples is enough for 144 FPS. _requestedSampleCount = std::max(_requestedSampleCount, 300U); } + SDL_InitSubSystem(SDL_INIT_AUDIO); +#ifndef USE_SDL3 #ifdef SDL_HINT_AUDIO_INCLUDE_MONITORS SDL_SetHint(SDL_HINT_AUDIO_INCLUDE_MONITORS, "1"); #endif - SDL_InitSubSystem(SDL_INIT_AUDIO); +#endif } AudioCaptureImpl::~AudioCaptureImpl() { + StopRecording(); SDL_QuitSubSystem(SDL_INIT_AUDIO); } std::map AudioCaptureImpl::AudioDeviceList() { std::map deviceList{ - {-1, "Default capturing device"}}; + {-1, "Default recording device"}}; +#ifdef USE_SDL3 + int deviceCount = 0; + auto deviceIds = SDL_GetAudioRecordingDevices(&deviceCount); + if (deviceIds) + { + for (int i = 0; i < deviceCount; i++) + { + auto deviceName = SDL_GetAudioDeviceName(deviceIds[i]); + if (deviceName) + { + deviceList.insert(std::make_pair(i, deviceName)); + } + else + { + poco_error_f2(_logger, "Could not get device name for device ID %d: %s", i, std::string(SDL_GetError())); + } + } + SDL_free(deviceIds); + } +#else auto recordingDeviceCount = SDL_GetNumAudioDevices(true); for (int i = 0; i < recordingDeviceCount; i++) @@ -46,6 +71,7 @@ std::map AudioCaptureImpl::AudioDeviceList() poco_error_f2(_logger, "Could not get device name for device ID %d: %s", i, std::string(SDL_GetError())); } } +#endif return deviceList; } @@ -59,42 +85,64 @@ void AudioCaptureImpl::StartRecording(projectm* projectMHandle, int audioDeviceI if (OpenAudioDevice()) { - SDL_PauseAudioDevice(_currentAudioDeviceID, false); - poco_debug(_logger, "Started audio recording."); } } void AudioCaptureImpl::StopRecording() { +#ifdef USE_SDL3 + if (_recordingStream) + { + SDL_DestroyAudioStream(_recordingStream); + _recordingStream = nullptr; + poco_debug(_logger, "Stopped audio recording and closed device."); + } +#else if (_currentAudioDeviceID) { SDL_PauseAudioDevice(_currentAudioDeviceID, true); SDL_CloseAudioDevice(_currentAudioDeviceID); _currentAudioDeviceID = 0; - poco_debug(_logger, "Stopped audio recording and closed device."); } +#endif } void AudioCaptureImpl::NextAudioDevice() { StopRecording(); - // Will wrap around to default capture device (-1). +#ifdef USE_SDL3 + int deviceCount = 0; + SDL_GetAudioRecordingDevices(&deviceCount); + int nextAudioDeviceId = ((_currentAudioDeviceIndex + 2) % (deviceCount + 1)) - 1; +#else int nextAudioDeviceId = ((_currentAudioDeviceIndex + 2) % (SDL_GetNumAudioDevices(true) + 1)) - 1; +#endif StartRecording(_projectMHandle, nextAudioDeviceId); } void AudioCaptureImpl::AudioDeviceIndex(int index) { +#ifdef USE_SDL3 + int deviceCount = 0; + SDL_GetAudioRecordingDevices(&deviceCount); + if (index >= -1 && index < deviceCount) + { + StopRecording(); + _currentAudioDeviceIndex = index; + StartRecording(_projectMHandle, index); + } +#else if (index >= -1 && index < SDL_GetNumAudioDevices(true)) { StopRecording(); _currentAudioDeviceIndex = index; StartRecording(_projectMHandle, index); } +#endif } int AudioCaptureImpl::AudioDeviceIndex() const @@ -104,18 +152,78 @@ int AudioCaptureImpl::AudioDeviceIndex() const std::string AudioCaptureImpl::AudioDeviceName() const { +#ifdef USE_SDL3 if (_currentAudioDeviceIndex >= 0) { - return SDL_GetAudioDeviceName(_currentAudioDeviceIndex, true); + int deviceCount = 0; + auto deviceIds = SDL_GetAudioRecordingDevices(&deviceCount); + if (deviceIds && _currentAudioDeviceIndex < deviceCount) + { + auto name = SDL_GetAudioDeviceName(deviceIds[_currentAudioDeviceIndex]); + std::string result(name ? name : "Unknown device"); + SDL_free(deviceIds); + return result; + } + if (deviceIds) + { + SDL_free(deviceIds); + } } - else + return "Default recording device"; +#else + if (_currentAudioDeviceIndex >= 0) { - return "Default capturing device"; + return SDL_GetAudioDeviceName(_currentAudioDeviceIndex, true); } + return "Default recording device"; +#endif } bool AudioCaptureImpl::OpenAudioDevice() { +#ifdef USE_SDL3 + SDL_AudioSpec spec{}; + spec.freq = static_cast(_requestedSampleFrequency); + spec.format = SDL_AUDIO_F32; + spec.channels = 2; + + SDL_AudioDeviceID deviceId = SDL_AUDIO_DEVICE_DEFAULT_RECORDING; + if (_currentAudioDeviceIndex >= 0) + { + int deviceCount = 0; + auto deviceIds = SDL_GetAudioRecordingDevices(&deviceCount); + if (deviceIds && _currentAudioDeviceIndex < deviceCount) + { + deviceId = deviceIds[_currentAudioDeviceIndex]; + } + if (deviceIds) + { + SDL_free(deviceIds); + } + } + + _recordingStream = SDL_OpenAudioDeviceStream(deviceId, &spec, nullptr, nullptr); + if (!_recordingStream) + { + poco_error_f2(_logger, R"(Failed to open audio recording device (ID %?d): %s)", + _currentAudioDeviceIndex, + std::string(SDL_GetError())); + return false; + } + + _channels = spec.channels; + + SDL_ResumeAudioStreamDevice(_recordingStream); + + auto deviceName = SDL_GetAudioDeviceName(deviceId); + poco_information_f4(_logger, R"(Opened audio recording device "%s" (ID %?d) with %?d channels at %?d Hz.)", + std::string(deviceName ? deviceName : "System default recording device"), + _currentAudioDeviceIndex, + spec.channels, + spec.freq); + + return true; +#else SDL_AudioSpec requestedSpecs{}; SDL_AudioSpec actualSpecs{}; @@ -126,14 +234,13 @@ bool AudioCaptureImpl::OpenAudioDevice() requestedSpecs.callback = AudioCaptureImpl::AudioInputCallback; requestedSpecs.userdata = this; - // Will be NULL on error, which happens if the requested index is -1. This automatically selects the default device. auto deviceName = SDL_GetAudioDeviceName(_currentAudioDeviceIndex, true); _currentAudioDeviceID = SDL_OpenAudioDevice(deviceName, true, &requestedSpecs, &actualSpecs, SDL_AUDIO_ALLOW_CHANNELS_CHANGE); if (_currentAudioDeviceID == 0) { poco_error_f3(_logger, R"(Failed to open audio device "%s" (ID %?d): %s)", - std::string(deviceName != nullptr ? deviceName : "System default capturing device"), + std::string(deviceName != nullptr ? deviceName : "System default recording device"), _currentAudioDeviceIndex, std::string(SDL_GetError())); return false; @@ -142,12 +249,43 @@ bool AudioCaptureImpl::OpenAudioDevice() _channels = actualSpecs.channels; poco_information_f4(_logger, R"(Opened audio recording device "%s" (ID %?d) with %?d channels at %?d Hz.)", - std::string(deviceName != nullptr ? deviceName : "System default capturing device"), + std::string(deviceName != nullptr ? deviceName : "System default recording device"), _currentAudioDeviceIndex, actualSpecs.channels, actualSpecs.freq); return true; +#endif +} + +#ifdef USE_SDL3 +void AudioCaptureImpl::FillBuffer() +{ + if (!_recordingStream || !_projectMHandle) + { + return; + } + + int available = SDL_GetAudioStreamAvailable(_recordingStream); + if (available <= 0) + { + return; + } + + std::vector buffer(available / sizeof(float)); + int bytesRead = SDL_GetAudioStreamData(_recordingStream, buffer.data(), available); + + if (bytesRead > 0) + { + unsigned int samples = bytesRead / sizeof(float) / _channels; + projectm_pcm_add_float(_projectMHandle, buffer.data(), samples, + static_cast(_channels)); + } +} +#else +void AudioCaptureImpl::FillBuffer() +{ + // SDL2 uses async callbacks to directly fill projectM's audio buffer. } void AudioCaptureImpl::AudioInputCallback(void* userData, unsigned char* stream, int len) @@ -160,3 +298,14 @@ void AudioCaptureImpl::AudioInputCallback(void* userData, unsigned char* stream, projectm_pcm_add_float(instance->_projectMHandle, reinterpret_cast(stream), samples, static_cast(instance->_channels)); } +#endif + +float AudioCaptureImpl::CurrentAudioLevel() const +{ + return -1.0f; +} + +void AudioCaptureImpl::RefreshDeviceList() +{ + // Device list is re-enumerated on next AudioDeviceList() call. +} diff --git a/src/AudioCaptureImpl_SDL.h b/src/AudioCaptureImpl_SDL.h index f951576a..d6f58fc1 100644 --- a/src/AudioCaptureImpl_SDL.h +++ b/src/AudioCaptureImpl_SDL.h @@ -1,6 +1,10 @@ #pragma once -#include +#ifdef USE_SDL3 +# include +#else +# include +#endif #include @@ -66,11 +70,20 @@ class AudioCaptureImpl /** * @brief Asks the capture client to fill projectM's audio buffer for the next frame. * - * As of now, SDL uses async callbacks to directly fill projectM's audio buffer. - * - * @todo Store audio samples internally and push them to projectM when requested. + * In SDL2, audio is captured via callback. In SDL3, audio data is polled here. + */ + void FillBuffer(); + + /** + * @brief Returns the current audio peak level. + * @return A value between 0.0 and 1.0, or -1.0 if not available. + */ + float CurrentAudioLevel() const; + + /** + * @brief Refreshes the audio device list (e.g. after a hotplug event). */ - void FillBuffer(){}; + void RefreshDeviceList(); protected: /** @@ -79,8 +92,9 @@ class AudioCaptureImpl */ bool OpenAudioDevice(); +#ifndef USE_SDL3 /** - * @brief SDL audio capture callback. + * @brief SDL2 audio capture callback. * * Called everytime if there is new data available in the audio recording buffer. * @@ -89,10 +103,15 @@ class AudioCaptureImpl * @param len */ static void AudioInputCallback(void* userData, unsigned char* stream, int len); +#endif projectm* _projectMHandle{nullptr}; //!< Handle if the projectM instance that will receive the audio data. int32_t _currentAudioDeviceIndex{-1}; //!< Currently selected audio device index. +#ifdef USE_SDL3 + SDL_AudioStream* _recordingStream{nullptr}; //!< SDL3 audio stream for recording. +#else SDL_AudioDeviceID _currentAudioDeviceID{0}; //!< Device ID of the currently opened audio device. +#endif uint32_t _channels{2}; constexpr static uint32_t _requestedSampleFrequency{44100}; //!< Requested sample frequency. Currently hardcoded as 44100 Hz, as this is what the spectrum analyzer expects. diff --git a/src/AudioCaptureImpl_WASAPI.cpp b/src/AudioCaptureImpl_WASAPI.cpp index 94aba1fe..2bd43514 100644 --- a/src/AudioCaptureImpl_WASAPI.cpp +++ b/src/AudioCaptureImpl_WASAPI.cpp @@ -132,6 +132,16 @@ void AudioCaptureImpl::FillBuffer() } } +float AudioCaptureImpl::CurrentAudioLevel() const +{ + return -1.0f; +} + +void AudioCaptureImpl::RefreshDeviceList() +{ + // Device list is re-enumerated on next AudioDeviceList() call. +} + HRESULT AudioCaptureImpl::QueryInterface(const IID& riid, void** ppvObject) { if (ppvObject == nullptr) diff --git a/src/AudioCaptureImpl_WASAPI.h b/src/AudioCaptureImpl_WASAPI.h index 9cbd165c..1a7ba14c 100644 --- a/src/AudioCaptureImpl_WASAPI.h +++ b/src/AudioCaptureImpl_WASAPI.h @@ -84,6 +84,17 @@ class AudioCaptureImpl : public IMMNotificationClient */ void FillBuffer(); + /** + * @brief Returns the current audio peak level. + * @return A value between 0.0 and 1.0, or -1.0 if not available. + */ + float CurrentAudioLevel() const; + + /** + * @brief Refreshes the audio device list (e.g. after a hotplug event). + */ + void RefreshDeviceList(); + /** * @brief Converts a widechar/unicode string to a UTF-8-encoded string * @param unicodeString A pointer to a widechar string diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2e814e75..6d7fda51 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -80,20 +80,43 @@ target_compile_definitions(projectMSDL PROJECTMSDL_VERSION="${PROJECT_VERSION}" ) +if(projectM4_VERSION VERSION_GREATER_EQUAL "4.2.0") + target_compile_definitions(projectMSDL PRIVATE PROJECTM_HAS_OPENGL_LOAD_PROC) +endif() + target_link_libraries(projectMSDL PRIVATE ProjectMSDL-GUI ProjectMSDL-Notifications libprojectM::playlist Poco::Util - SDL2::SDL2$<$:-static> - SDL2::SDL2main OpenGL::$,GLES3,GL> ) -if (MSVC) - set_target_properties(projectMSDL - PROPERTIES - VS_DPI_AWARE "PerMonitor" +if(USE_SDL3) + target_link_libraries(projectMSDL + PRIVATE + SDL3::SDL3$<$:-static> ) +else() + target_link_libraries(projectMSDL + PRIVATE + SDL2::SDL2$<$:-static> + SDL2::SDL2main + ) +endif() + +if (MSVC) + if(USE_SDL3) + set_target_properties(projectMSDL + PROPERTIES + VS_DPI_AWARE "PerMonitor" + ) + target_link_options(projectMSDL PRIVATE "/SUBSYSTEM:CONSOLE") + else() + set_target_properties(projectMSDL + PROPERTIES + VS_DPI_AWARE "PerMonitor" + ) + endif() endif () diff --git a/src/FPSLimiter.cpp b/src/FPSLimiter.cpp index 5e6d7de8..4819c4f8 100644 --- a/src/FPSLimiter.cpp +++ b/src/FPSLimiter.cpp @@ -1,6 +1,10 @@ #include "FPSLimiter.h" -#include +#ifdef USE_SDL3 +# include +#else +# include +#endif void FPSLimiter::TargetFPS(int fps) { @@ -43,7 +47,7 @@ void FPSLimiter::StartFrame() void FPSLimiter::EndFrame() { - uint32_t frameTime = SDL_GetTicks() - _lastTickCount; + uint64_t frameTime = SDL_GetTicks() - _lastTickCount; if (_targetFrameTime && frameTime < _targetFrameTime) { diff --git a/src/FPSLimiter.h b/src/FPSLimiter.h index fb7ea0cd..60ce532e 100644 --- a/src/FPSLimiter.h +++ b/src/FPSLimiter.h @@ -39,7 +39,7 @@ class FPSLimiter protected: - uint32_t _lastTickCount{ 0 }; //!< Last SDL tick count, when a new frame was started. + uint64_t _lastTickCount{ 0 }; //!< Last SDL tick count, when a new frame was started. uint32_t _targetFrameTime{ 0 }; //!< Targeted time per frame in milliseconds. uint32_t _lastFrameTimes[10]{}; //!< Actual tick time of the last ten frames, including limiting delay. int _nextFrameTimesOffset{ 0 }; //!< Next offset to overwrite the _lastFrameTimes ring buffer. diff --git a/src/ProjectMSDLApplication.cpp b/src/ProjectMSDLApplication.cpp index 3d12e582..ae115aa2 100644 --- a/src/ProjectMSDLApplication.cpp +++ b/src/ProjectMSDLApplication.cpp @@ -247,11 +247,24 @@ void ProjectMSDLApplication::DisplayHelp(POCO_UNUSED const std::string& name, PO { Poco::Util::HelpFormatter formatter(options()); +#ifdef USE_SDL3 + struct { int major; int minor; int patch; } sdlBuild; + sdlBuild.major = SDL_MAJOR_VERSION; + sdlBuild.minor = SDL_MINOR_VERSION; + sdlBuild.patch = SDL_MICRO_VERSION; + + struct { int major; int minor; int patch; } sdlLoaded; + int loadedVer = SDL_GetVersion(); + sdlLoaded.major = SDL_VERSIONNUM_MAJOR(loadedVer); + sdlLoaded.minor = SDL_VERSIONNUM_MINOR(loadedVer); + sdlLoaded.patch = SDL_VERSIONNUM_MICRO(loadedVer); +#else SDL_version sdlBuild; SDL_version sdlLoaded; SDL_VERSION(&sdlBuild); SDL_GetVersion(&sdlLoaded); +#endif auto* projectMVersion = projectm_get_version_string(); std::string projectMRuntimeVersion(projectMVersion); diff --git a/src/ProjectMWrapper.cpp b/src/ProjectMWrapper.cpp index 75894f12..de91a85f 100644 --- a/src/ProjectMWrapper.cpp +++ b/src/ProjectMWrapper.cpp @@ -9,10 +9,26 @@ #include #include -#include +#ifdef USE_SDL3 +# include +#else +# include +#endif #include +#if defined(USE_SDL3) && defined(PROJECTM_HAS_OPENGL_LOAD_PROC) +namespace { + /** + * @brief OpenGL function loader callback for projectM, using SDL's GL proc resolver. + */ + void* SDLProjectMLoadProc(const char* name, void* /*user_data*/) + { + return reinterpret_cast(SDL_GL_GetProcAddress(name)); + } +} // anonymous namespace +#endif + const char* ProjectMWrapper::name() const { return "ProjectM Wrapper"; @@ -37,7 +53,11 @@ void ProjectMWrapper::initialize(Poco::Util::Application& app) auto presetPaths = GetPathListWithDefault("presetPath", app.config().getString("application.dir", "")); auto texturePaths = GetPathListWithDefault("texturePath", app.config().getString("", "")); +#if defined(USE_SDL3) && defined(PROJECTM_HAS_OPENGL_LOAD_PROC) + _projectM = projectm_create_with_opengl_load_proc(SDLProjectMLoadProc, nullptr); +#else _projectM = projectm_create(); +#endif if (!_projectM) { poco_error(_logger, "Failed to initialize projectM. Possible reasons are a lack of required OpenGL features or GPU resources."); diff --git a/src/RenderLoop.cpp b/src/RenderLoop.cpp index 7d428e0f..05999a73 100644 --- a/src/RenderLoop.cpp +++ b/src/RenderLoop.cpp @@ -4,11 +4,20 @@ #include "gui/ProjectMGUI.h" +#include "notifications/DisplayToastNotification.h" +#include "notifications/PlaybackControlNotification.h" + #include #include +#include +#include -#include +#ifdef USE_SDL3 +# include +#else +# include +#endif #include "ProjectMSDLApplication.h" @@ -67,138 +76,165 @@ void RenderLoop::PollEvents() switch (event.type) { +#ifdef USE_SDL3 + case SDL_EVENT_MOUSE_WHEEL: +#else case SDL_MOUSEWHEEL: - +#endif if (!_projectMGui.WantsMouseInput()) { ScrollEvent(event.wheel); } - break; +#ifdef USE_SDL3 + case SDL_EVENT_KEY_DOWN: +#else case SDL_KEYDOWN: +#endif if (!_projectMGui.WantsKeyboardInput()) { KeyEvent(event.key, true); } break; +#ifdef USE_SDL3 + case SDL_EVENT_KEY_UP: +#else case SDL_KEYUP: +#endif if (!_projectMGui.WantsKeyboardInput()) { KeyEvent(event.key, false); } break; +#ifdef USE_SDL3 + case SDL_EVENT_MOUSE_BUTTON_DOWN: +#else case SDL_MOUSEBUTTONDOWN: +#endif if (!_projectMGui.WantsMouseInput()) { MouseDownEvent(event.button); } - break; +#ifdef USE_SDL3 + case SDL_EVENT_MOUSE_BUTTON_UP: +#else case SDL_MOUSEBUTTONUP: +#endif if (!_projectMGui.WantsMouseInput()) { MouseUpEvent(event.button); } + break; +#ifdef USE_SDL3 + case SDL_EVENT_DROP_FILE: +#else + case SDL_DROPFILE: +#endif + HandleDropFile(event); break; - case SDL_DROPFILE: { - char* droppedFilePath = event.drop.file; +#ifdef USE_SDL3 + case SDL_EVENT_QUIT: +#else + case SDL_QUIT: +#endif + _wantsToQuit = true; + break; - // first we want to get the config settings that are relevant ehre - // namely skipToDropped and droppedFolderOverride - // we can get them from the projectMWrapper, in the _projectMConfigView available on it - bool skipToDropped = _userConfig->getBool("projectM.skipToDropped", true); - bool droppedFolderOverride = _userConfig->getBool("projectM.droppedFolderOverride", false); +#ifdef USE_SDL3 + case SDL_EVENT_AUDIO_DEVICE_ADDED: + case SDL_EVENT_AUDIO_DEVICE_REMOVED: + _audioCapture.RefreshDeviceList(); + break; +#endif + } + } +} +void RenderLoop::HandleDropFile(const SDL_Event& event) +{ +#ifdef USE_SDL3 + const char* droppedFilePath = event.drop.data; +#else + char* droppedFilePath = event.drop.file; +#endif - bool shuffle = projectm_playlist_get_shuffle(_playlistHandle); - if (shuffle && skipToDropped) - { - // if shuffle is enabled, we disable it temporarily, so the dropped preset is played next - // if skipToDropped is false, we also keep shuffle enabled, as it doesn't matter since the current preset is unaffected - projectm_playlist_set_shuffle(_playlistHandle, false); - } + bool skipToDropped = _userConfig->getBool("projectM.skipToDropped", true); + bool droppedFolderOverride = _userConfig->getBool("projectM.droppedFolderOverride", false); - int index = projectm_playlist_get_position(_playlistHandle) + 1; + bool shuffle = projectm_playlist_get_shuffle(_playlistHandle); + if (shuffle && skipToDropped) + { + projectm_playlist_set_shuffle(_playlistHandle, false); + } - do - { - Poco::File droppedFile(droppedFilePath); - if (!droppedFile.isDirectory()) - { - // handle dropped preset file - Poco::Path droppedFileP(droppedFilePath); - if (!droppedFile.exists() || (droppedFileP.getExtension() != "milk" && droppedFileP.getExtension() != "prjm")) - { - std::string toastMessage = std::string("Invalid preset file: ") + droppedFilePath; - Poco::NotificationCenter::defaultCenter().postNotification(new DisplayToastNotification(toastMessage)); - poco_information_f1(_logger, "%s", toastMessage); - break; // exit the block and go to the shuffle check - } - - if (projectm_playlist_insert_preset(_playlistHandle, droppedFilePath, index, true)) - { - if (skipToDropped) - { - projectm_playlist_play_next(_playlistHandle, true); - } - poco_information_f1(_logger, "Added preset: %s", std::string(droppedFilePath)); - // no need to toast single presets, as its obvious if a preset was loaded. - } - } - else - { - // handle dropped directory - - // if droppedFolderOverride is enabled, we clear the playlist first - // current edge case: if the dropped directory is invalid or contains no presets, then it still clears the playlist - if (droppedFolderOverride) - { - projectm_playlist_clear(_playlistHandle); - index = 0; - } - - uint32_t addedFilesCount = projectm_playlist_insert_path(_playlistHandle, droppedFilePath, index, true, true); - if (addedFilesCount > 0) - { - std::string toastMessage = "Added " + std::to_string(addedFilesCount) + " presets from " + droppedFilePath; - poco_information_f1(_logger, "%s", toastMessage); - if (skipToDropped || droppedFolderOverride) - { - // if skip to dropped is true, or if a folder was dropped and it overrode the playlist, we skip to the next preset - projectm_playlist_play_next(_playlistHandle, true); - } - Poco::NotificationCenter::defaultCenter().postNotification(new DisplayToastNotification(toastMessage)); - } - else - { - std::string toastMessage = std::string("No presets found in: ") + droppedFilePath; - Poco::NotificationCenter::defaultCenter().postNotification(new DisplayToastNotification(toastMessage)); - poco_information_f1(_logger, "%s", toastMessage); - } - } - } while (false); - - if (shuffle && skipToDropped) - { - projectm_playlist_set_shuffle(_playlistHandle, true); - } + int index = projectm_playlist_get_position(_playlistHandle) + 1; - SDL_free(droppedFilePath); + do + { + Poco::File droppedFile(droppedFilePath); + if (!droppedFile.isDirectory()) + { + Poco::Path droppedFileP(droppedFilePath); + if (!droppedFile.exists() || (droppedFileP.getExtension() != "milk" && droppedFileP.getExtension() != "prjm")) + { + std::string toastMessage = std::string("Invalid preset file: ") + droppedFilePath; + Poco::NotificationCenter::defaultCenter().postNotification(new DisplayToastNotification(toastMessage)); + poco_information_f1(_logger, "%s", toastMessage); break; } + if (projectm_playlist_insert_preset(_playlistHandle, droppedFilePath, index, true)) + { + if (skipToDropped) + { + projectm_playlist_play_next(_playlistHandle, true); + } + poco_information_f1(_logger, "Added preset: %s", std::string(droppedFilePath)); + } + } + else + { + if (droppedFolderOverride) + { + projectm_playlist_clear(_playlistHandle); + index = 0; + } - case SDL_QUIT: - _wantsToQuit = true; - break; + uint32_t addedFilesCount = projectm_playlist_insert_path(_playlistHandle, droppedFilePath, index, true, true); + if (addedFilesCount > 0) + { + std::string toastMessage = "Added " + std::to_string(addedFilesCount) + " presets from " + droppedFilePath; + poco_information_f1(_logger, "%s", toastMessage); + if (skipToDropped || droppedFolderOverride) + { + projectm_playlist_play_next(_playlistHandle, true); + } + Poco::NotificationCenter::defaultCenter().postNotification(new DisplayToastNotification(toastMessage)); + } + else + { + std::string toastMessage = std::string("No presets found in: ") + droppedFilePath; + Poco::NotificationCenter::defaultCenter().postNotification(new DisplayToastNotification(toastMessage)); + poco_information_f1(_logger, "%s", toastMessage); + } } + } while (false); + + if (shuffle && skipToDropped) + { + projectm_playlist_set_shuffle(_playlistHandle, true); } + +#ifndef USE_SDL3 + SDL_free(droppedFilePath); +#endif } void RenderLoop::CheckViewportSize() @@ -221,6 +257,16 @@ void RenderLoop::CheckViewportSize() void RenderLoop::KeyEvent(const SDL_KeyboardEvent& event, bool down) { +#ifdef USE_SDL3 + auto keyModifier{static_cast(event.mod)}; + auto keyCode{event.key}; + bool modifierPressed{false}; + + if (keyModifier & SDL_KMOD_LGUI || keyModifier & SDL_KMOD_RGUI || keyModifier & SDL_KMOD_LCTRL) + { + modifierPressed = true; + } +#else auto keyModifier{static_cast(event.keysym.mod)}; auto keyCode{event.keysym.sym}; bool modifierPressed{false}; @@ -229,6 +275,7 @@ void RenderLoop::KeyEvent(const SDL_KeyboardEvent& event, bool down) { modifierPressed = true; } +#endif // Handle modifier keys and save state for use in other methods, e.g. mouse events switch (keyCode) @@ -264,6 +311,31 @@ void RenderLoop::KeyEvent(const SDL_KeyboardEvent& event, bool down) switch (keyCode) { +#ifdef USE_SDL3 +// SDL3 renamed key constants to uppercase. Remap lowercase to uppercase. +#undef SDLK_a +#define SDLK_a SDLK_A +#undef SDLK_c +#define SDLK_c SDLK_C +#undef SDLK_d +#define SDLK_d SDLK_D +#undef SDLK_f +#define SDLK_f SDLK_F +#undef SDLK_i +#define SDLK_i SDLK_I +#undef SDLK_m +#define SDLK_m SDLK_M +#undef SDLK_n +#define SDLK_n SDLK_N +#undef SDLK_p +#define SDLK_p SDLK_P +#undef SDLK_q +#define SDLK_q SDLK_Q +#undef SDLK_r +#define SDLK_r SDLK_R +#undef SDLK_y +#define SDLK_y SDLK_Y +#endif case SDLK_ESCAPE: _projectMGui.Toggle(); _sdlRenderingWindow.ShowCursor(_projectMGui.Visible()); @@ -344,12 +416,10 @@ void RenderLoop::KeyEvent(const SDL_KeyboardEvent& event, bool down) break; case SDLK_UP: - // Increase beat sensitivity _projectMWrapper.ChangeBeatSensitivity(0.01f); break; case SDLK_DOWN: - // Decrease beat sensitivity _projectMWrapper.ChangeBeatSensitivity(-0.01f); break; } @@ -357,12 +427,10 @@ void RenderLoop::KeyEvent(const SDL_KeyboardEvent& event, bool down) void RenderLoop::ScrollEvent(const SDL_MouseWheelEvent& event) { - // Wheel up is positive if (event.y > 0) { projectm_playlist_play_next(_playlistHandle, true); } - // Wheel down is negative else if (event.y < 0) { projectm_playlist_play_previous(_playlistHandle, true); @@ -381,9 +449,13 @@ void RenderLoop::MouseDownEvent(const SDL_MouseButtonEvent& event) case SDL_BUTTON_LEFT: if (!_mouseDown && _keyStates._shiftPressed) { - // ToDo: Improve this to differentiate between single click (add waveform) and drag (move waveform). +#ifdef USE_SDL3 + float x; + float y; +#else int x; int y; +#endif int width; int height; @@ -391,13 +463,11 @@ void RenderLoop::MouseDownEvent(const SDL_MouseButtonEvent& event) SDL_GetMouseState(&x, &y); - // Scale those coordinates. libProjectM uses a scale of 0..1 instead of absolute pixel coordinates. float scaledX = (static_cast(x) / static_cast(width)); float scaledY = (static_cast(height - y) / static_cast(height)); - // Add a new waveform. projectm_touch(_projectMHandle, scaledX, scaledY, 0, PROJECTM_TOUCH_TYPE_RANDOM); - poco_debug_f2(_logger, "Added new random waveform at %?d,%?d", x, y); + poco_debug_f2(_logger, "Added new random waveform at %?f,%?f", static_cast(x), static_cast(y)); _mouseDown = true; } diff --git a/src/RenderLoop.h b/src/RenderLoop.h index 9429b1ff..fee1881a 100644 --- a/src/RenderLoop.h +++ b/src/RenderLoop.h @@ -72,6 +72,12 @@ class RenderLoop */ void QuitNotificationHandler(const Poco::AutoPtr& notification); + /** + * @brief Handles a file drop event, adding the file or directory to the playlist. + * @param event The SDL drop event. + */ + void HandleDropFile(const SDL_Event& event); + AudioCapture& _audioCapture; ProjectMWrapper& _projectMWrapper; SDLRenderingWindow& _sdlRenderingWindow; diff --git a/src/SDLRenderingWindow.cpp b/src/SDLRenderingWindow.cpp index 1516acfb..8b74812b 100644 --- a/src/SDLRenderingWindow.cpp +++ b/src/SDLRenderingWindow.cpp @@ -12,11 +12,19 @@ #include #endif -#include +#ifdef USE_SDL3 +# include +#else +# include +#endif const char* SDLRenderingWindow::name() const { +#ifdef USE_SDL3 + return "SDL3 Rendering Window"; +#else return "SDL2 Rendering Window"; +#endif } void SDLRenderingWindow::initialize(Poco::Util::Application& app) @@ -52,7 +60,11 @@ void SDLRenderingWindow::uninitialize() void SDLRenderingWindow::GetDrawableSize(int& width, int& height) const { +#ifdef USE_SDL3 + SDL_GetWindowSizeInPixels(_renderingWindow, &width, &height); +#else SDL_GL_GetDrawableSize(_renderingWindow, &width, &height); +#endif } void SDLRenderingWindow::Swap() const @@ -75,7 +87,11 @@ void SDLRenderingWindow::ToggleFullscreen() void SDLRenderingWindow::Fullscreen() { SDL_GetWindowSize(_renderingWindow, &_lastWindowWidth, &_lastWindowHeight); - SDL_ShowCursor(false); +#ifdef USE_SDL3 + SDL_HideCursor(); +#else + SDL_ShowCursor(SDL_DISABLE); +#endif if (_config->getBool("fullscreen.exclusiveMode", false)) { int fullscreenWidth = _config->getInt("fullscreen.width", 0); @@ -85,7 +101,11 @@ void SDLRenderingWindow::Fullscreen() SDL_RestoreWindow(_renderingWindow); SDL_SetWindowSize(_renderingWindow, fullscreenWidth, fullscreenHeight); } +#ifdef USE_SDL3 + SDL_SetWindowFullscreen(_renderingWindow, true); +#else SDL_SetWindowFullscreen(_renderingWindow, SDL_WINDOW_FULLSCREEN); +#endif if (_logger.debug()) { int width{0}; @@ -97,7 +117,11 @@ void SDLRenderingWindow::Fullscreen() } else { +#ifdef USE_SDL3 + SDL_SetWindowFullscreen(_renderingWindow, true); +#else SDL_SetWindowFullscreen(_renderingWindow, SDL_WINDOW_FULLSCREEN_DESKTOP); +#endif if (_logger.debug()) { int width{0}; @@ -113,12 +137,21 @@ void SDLRenderingWindow::Fullscreen() void SDLRenderingWindow::Windowed() { +#ifdef USE_SDL3 + SDL_SetWindowFullscreen(_renderingWindow, false); + SDL_SetWindowBordered(_renderingWindow, _config->getBool("borderless", false) ? false : true); +#else SDL_SetWindowFullscreen(_renderingWindow, 0); SDL_SetWindowBordered(_renderingWindow, _config->getBool("borderless", false) ? SDL_FALSE : SDL_TRUE); +#endif if (_lastWindowWidth > 0 && _lastWindowHeight > 0) { SDL_SetWindowSize(_renderingWindow, _lastWindowWidth, _lastWindowHeight); - SDL_ShowCursor(true); +#ifdef USE_SDL3 + SDL_ShowCursor(); +#else + SDL_ShowCursor(SDL_ENABLE); +#endif poco_debug_f2(_logger, "Entered windowed mode with size %dx%d", _lastWindowWidth, _lastWindowHeight); @@ -129,12 +162,28 @@ void SDLRenderingWindow::Windowed() void SDLRenderingWindow::ShowCursor(bool visible) { - SDL_ShowCursor(visible); +#ifdef USE_SDL3 + if (visible) + { + SDL_ShowCursor(); + } + else + { + SDL_HideCursor(); + } +#else + SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE); +#endif } void SDLRenderingWindow::NextDisplay() { +#ifdef USE_SDL3 + int numDisplays; + SDL_GetDisplays(&numDisplays); +#else auto numDisplays = SDL_GetNumVideoDisplays(); +#endif if (numDisplays < 2) { @@ -214,7 +263,12 @@ void SDLRenderingWindow::CreateSDLWindow() if (display > 0) { poco_debug_f1(_logger, "User requested to place window on monitor %?d.", display); +#ifdef USE_SDL3 + int numDisplays; + SDL_GetDisplays(&numDisplays); +#else auto numDisplays = SDL_GetNumVideoDisplays(); +#endif if (display > numDisplays) { display = numDisplays; @@ -249,8 +303,17 @@ void SDLRenderingWindow::CreateSDLWindow() SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); #endif +#ifdef USE_SDL3 + _renderingWindow = SDL_CreateWindow("projectM", width, height, + SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY); + if (_renderingWindow) + { + SDL_SetWindowPosition(_renderingWindow, left, top); + } +#else _renderingWindow = SDL_CreateWindow("projectM", left, top, width, height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); +#endif if (!_renderingWindow) { auto errorMessage = "Could not create SDL rendering window. Error: " + std::string(SDL_GetError()); @@ -301,7 +364,11 @@ void SDLRenderingWindow::DestroySDLWindow() { poco_debug(_logger, "Closing rendering window and destroying OpenGL context."); +#ifdef USE_SDL3 + SDL_GL_DestroyContext(_glContext); +#else SDL_GL_DeleteContext(_glContext); +#endif _glContext = nullptr; SDL_DestroyWindow(_renderingWindow); @@ -325,7 +392,12 @@ int SDLRenderingWindow::GetCurrentDisplay() SDL_GetWindowPosition(_renderingWindow, &left, &top); +#ifdef USE_SDL3 + int numDisplays = 0; + SDL_GetDisplays(&numDisplays); +#else auto numDisplays = SDL_GetNumVideoDisplays(); +#endif poco_debug_f1(_logger, "Displays available: %?d.", numDisplays); poco_debug_f2(_logger, "Window position is X=%?d Y=%?d", left, top); @@ -453,7 +525,11 @@ void SDLRenderingWindow::OnConfigurationPropertyRemoved(const std::string& key) if (key == "window.borderless") { +#ifdef USE_SDL3 + SDL_SetWindowBordered(_renderingWindow, _config->getBool("borderless", false) ? false : true); +#else SDL_SetWindowBordered(_renderingWindow, _config->getBool("borderless", false) ? SDL_FALSE : SDL_TRUE); +#endif } if (key == "window.displayPresetNameInTitle") diff --git a/src/SDLRenderingWindow.h b/src/SDLRenderingWindow.h index 06c3bbee..adc4a11b 100644 --- a/src/SDLRenderingWindow.h +++ b/src/SDLRenderingWindow.h @@ -2,7 +2,11 @@ #include "notifications/UpdateWindowTitleNotification.h" -#include +#ifdef USE_SDL3 +# include +#else +# include +#endif #include #include diff --git a/src/gui/PresetSelection.h b/src/gui/PresetSelection.h index 04b0bba9..68c28912 100644 --- a/src/gui/PresetSelection.h +++ b/src/gui/PresetSelection.h @@ -2,7 +2,11 @@ #include "FileChooser.h" -#include +#ifdef USE_SDL3 +# include +#else +# include +#endif #include diff --git a/src/gui/ProjectMGUI.cpp b/src/gui/ProjectMGUI.cpp index 925c8438..856e91af 100644 --- a/src/gui/ProjectMGUI.cpp +++ b/src/gui/ProjectMGUI.cpp @@ -7,8 +7,13 @@ #include "imgui.h" #include "imgui_impl_opengl3.h" +#ifdef USE_SDL3 +#include "imgui_impl_sdl3.h" +#else #include "imgui_impl_sdl2.h" +#endif +#include #include #include @@ -42,8 +47,13 @@ void ProjectMGUI::initialize(Poco::Util::Application& app) _renderingWindow = renderingWindow.GetRenderingWindow(); _glContext = renderingWindow.GetGlContext(); +#ifdef USE_SDL3 + ImGui_ImplSDL3_InitForOpenGL(_renderingWindow, _glContext); + ImGui_ImplOpenGL3_Init("#version 300 es"); +#else ImGui_ImplSDL2_InitForOpenGL(_renderingWindow, _glContext); ImGui_ImplOpenGL3_Init("#version 150"); +#endif UpdateFontSize(); @@ -59,7 +69,11 @@ void ProjectMGUI::uninitialize() Poco::NotificationCenter::defaultCenter().removeObserver(_displayToastNotificationObserver); ImGui_ImplOpenGL3_Shutdown(); +#ifdef USE_SDL3 + ImGui_ImplSDL3_Shutdown(); +#else ImGui_ImplSDL2_Shutdown(); +#endif ImGui::DestroyContext(); _projectMWrapper = nullptr; @@ -71,7 +85,11 @@ void ProjectMGUI::UpdateFontSize() { ImGuiIO& io = ImGui::GetIO(); +#ifdef USE_SDL3 + auto displayIndex = SDL_GetDisplayForWindow(_renderingWindow); +#else auto displayIndex = SDL_GetWindowDisplayIndex(_renderingWindow); +#endif if (displayIndex < 0) { poco_debug_f1(_logger, "Could not get display index for application window: %s", std::string(SDL_GetError())); @@ -103,7 +121,11 @@ void ProjectMGUI::UpdateFontSize() void ProjectMGUI::ProcessInput(const SDL_Event& event) { +#ifdef USE_SDL3 + ImGui_ImplSDL3_ProcessEvent(&event); +#else ImGui_ImplSDL2_ProcessEvent(&event); +#endif } void ProjectMGUI::Toggle() @@ -134,18 +156,30 @@ void ProjectMGUI::Draw() UpdateFontSize(); } +#ifdef USE_SDL3 + ImGui_ImplSDL3_NewFrame(); +#else ImGui_ImplSDL2_NewFrame(); +#endif ImGui_ImplOpenGL3_NewFrame(); ImGui::NewFrame(); float secondsSinceLastFrame = .0f; if (_lastFrameTicks == 0) { +#ifdef USE_SDL3 + _lastFrameTicks = SDL_GetTicks(); +#else _lastFrameTicks = SDL_GetTicks64(); +#endif } else { +#ifdef USE_SDL3 + auto currentFrameTicks = SDL_GetTicks(); +#else auto currentFrameTicks = SDL_GetTicks64(); +#endif secondsSinceLastFrame = static_cast(currentFrameTicks - _lastFrameTicks) * .001f; _lastFrameTicks = currentFrameTicks; } @@ -220,7 +254,11 @@ float ProjectMGUI::GetScalingFactor() int renderHeight; SDL_GetWindowSize(_renderingWindow, &windowWidth, &windowHeight); +#ifdef USE_SDL3 + SDL_GetWindowSizeInPixels(_renderingWindow, &renderWidth, &renderHeight); +#else SDL_GL_GetDrawableSize(_renderingWindow, &renderWidth, &renderHeight); +#endif _userScalingFactor = GetClampedUserScalingFactor(); diff --git a/src/gui/ProjectMGUI.h b/src/gui/ProjectMGUI.h index aca9d885..0532e052 100644 --- a/src/gui/ProjectMGUI.h +++ b/src/gui/ProjectMGUI.h @@ -8,7 +8,11 @@ #include "notifications/DisplayToastNotification.h" -#include +#ifdef USE_SDL3 +# include +#else +# include +#endif #include #include diff --git a/src/main.cpp b/src/main.cpp index ecb1231f..09cf3ecf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,10 @@ #include "ProjectMSDLApplication.h" -#include +#ifdef USE_SDL3 +# include +#else +# include +#endif int main(int argc, char* argv[]) { diff --git a/vcpkg.json b/vcpkg.json index 924009d6..36c29c2d 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -3,6 +3,7 @@ "dependencies": [ "glew", "sdl2", + "sdl3", { "name": "poco", "features": [