diff --git a/.codeql-prebuild-cpp-Windows.sh b/.codeql-prebuild-cpp-Windows.sh index 7bee4f65..e03f8740 100644 --- a/.codeql-prebuild-cpp-Windows.sh +++ b/.codeql-prebuild-cpp-Windows.sh @@ -7,7 +7,6 @@ pacman --noconfirm -Syu # install dependencies dependencies=( "git" - "mingw-w64-ucrt-x86_64-boost" "mingw-w64-ucrt-x86_64-cmake" "mingw-w64-ucrt-x86_64-cppwinrt" "mingw-w64-ucrt-x86_64-curl-winssl" diff --git a/.codeql-prebuild-cpp-macOS.sh b/.codeql-prebuild-cpp-macOS.sh index 1a6be508..a21a69c3 100644 --- a/.codeql-prebuild-cpp-macOS.sh +++ b/.codeql-prebuild-cpp-macOS.sh @@ -7,7 +7,6 @@ eval "$(/usr/local/bin/brew shellenv)" # install dependencies dependencies=( - "boost" "cmake" "miniupnpc" "ninja" diff --git a/cmake/compile_definitions/linux.cmake b/cmake/compile_definitions/linux.cmake index 08b2417e..b4267ecb 100644 --- a/cmake/compile_definitions/linux.cmake +++ b/cmake/compile_definitions/linux.cmake @@ -137,7 +137,8 @@ if(WAYLAND_FOUND) endif() GEN_WAYLAND("${WAYLAND_PROTOCOLS_DIR}" "unstable/xdg-output" xdg-output-unstable-v1) - GEN_WAYLAND("${CMAKE_SOURCE_DIR}/third-party/wlr-protocols" "unstable" wlr-export-dmabuf-unstable-v1) + GEN_WAYLAND("${WAYLAND_PROTOCOLS_DIR}" "unstable/linux-dmabuf" linux-dmabuf-unstable-v1) + GEN_WAYLAND("${CMAKE_SOURCE_DIR}/third-party/wlr-protocols" "unstable" wlr-screencopy-unstable-v1) include_directories( SYSTEM @@ -145,7 +146,7 @@ if(WAYLAND_FOUND) ${CMAKE_BINARY_DIR}/generated-src ) - list(APPEND PLATFORM_LIBRARIES ${WAYLAND_LIBRARIES}) + list(APPEND PLATFORM_LIBRARIES ${WAYLAND_LIBRARIES} gbm) list(APPEND PLATFORM_TARGET_FILES "${CMAKE_SOURCE_DIR}/src/platform/linux/wlgrab.cpp" "${CMAKE_SOURCE_DIR}/src/platform/linux/wayland.h" diff --git a/cmake/compile_definitions/windows.cmake b/cmake/compile_definitions/windows.cmake index 20a177b0..60e10b41 100644 --- a/cmake/compile_definitions/windows.cmake +++ b/cmake/compile_definitions/windows.cmake @@ -38,7 +38,7 @@ if(NOT DEFINED SUNSHINE_ICON_PATH) set(SUNSHINE_ICON_PATH "${CMAKE_SOURCE_DIR}/apollo.ico") endif() -configure_file("${CMAKE_SOURCE_DIR}/src/platform/windows/windows.rs.in" windows.rc @ONLY) +configure_file("${CMAKE_SOURCE_DIR}/src/platform/windows/windows.rc.in" windows.rc @ONLY) set(PLATFORM_TARGET_FILES "${CMAKE_CURRENT_BINARY_DIR}/windows.rc" diff --git a/cmake/dependencies/Boost_Sunshine.cmake b/cmake/dependencies/Boost_Sunshine.cmake index dc13bb0e..eb2ac409 100644 --- a/cmake/dependencies/Boost_Sunshine.cmake +++ b/cmake/dependencies/Boost_Sunshine.cmake @@ -3,13 +3,25 @@ # include_guard(GLOBAL) -set(BOOST_VERSION 1.86) +set(BOOST_VERSION "1.87.0") set(BOOST_COMPONENTS filesystem locale log program_options - system) # system is not used by Sunshine, but by Simple-Web-Server, added here for convenience + system +) +# system is not used by Sunshine, but by Simple-Web-Server, added here for convenience + +# algorithm, preprocessor, scope, and uuid are not used by Sunshine, but by libdisplaydevice, added here for convenience +if(WIN32) + list(APPEND BOOST_COMPONENTS + algorithm + preprocessor + scope + uuid + ) +endif() if(BOOST_USE_STATIC) set(Boost_USE_STATIC_LIBS ON) # cmake-lint: disable=C0103 @@ -18,9 +30,9 @@ endif() if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.30") cmake_policy(SET CMP0167 NEW) # Get BoostConfig.cmake from upstream endif() -find_package(Boost CONFIG ${BOOST_VERSION} COMPONENTS ${BOOST_COMPONENTS}) +find_package(Boost CONFIG ${BOOST_VERSION} EXACT COMPONENTS ${BOOST_COMPONENTS}) if(NOT Boost_FOUND) - message(STATUS "Boost v${BOOST_VERSION}.x package not found in the system. Falling back to FetchContent.") + message(STATUS "Boost v${BOOST_VERSION} package not found in the system. Falling back to FetchContent.") include(FetchContent) if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") @@ -41,12 +53,9 @@ if(NOT Boost_FOUND) set(BOOST_ENABLE_CMAKE ON) # Limit boost to the required libraries only - set(BOOST_INCLUDE_LIBRARIES - ${BOOST_COMPONENTS}) - set(BOOST_URL - "https://github.com/boostorg/boost/releases/download/boost-1.86.0/boost-1.86.0-cmake.tar.xz") - set(BOOST_HASH - "MD5=D02759931CEDC02ADED80402906C5EB6") + set(BOOST_INCLUDE_LIBRARIES ${BOOST_COMPONENTS}) + set(BOOST_URL "https://github.com/boostorg/boost/releases/download/boost-${BOOST_VERSION}/boost-${BOOST_VERSION}-cmake.tar.xz") # cmake-lint: disable=C0301 + set(BOOST_HASH "SHA256=7da75f171837577a52bbf217e17f8ea576c7c246e4594d617bfde7fafd408be5") if(CMAKE_VERSION VERSION_LESS "3.24.0") FetchContent_Declare( @@ -77,7 +86,7 @@ if(NOT Boost_FOUND) set(Boost_FOUND TRUE) # cmake-lint: disable=C0103 set(Boost_INCLUDE_DIRS # cmake-lint: disable=C0103 - "$;$") + "$") if(WIN32) # Windows build is failing to create .h file in this directory diff --git a/docs/getting_started.md b/docs/getting_started.md index 554db9db..adc7a171 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -30,7 +30,9 @@ See [Docker](../DOCKER_README.md) for more information. CUDA is used for NVFBC capture. -@tip{See [CUDA GPUS](https://developer.nvidia.com/cuda-gpus) to cross-reference Compute Capability to your GPU.} +@tip{See [CUDA GPUS](https://developer.nvidia.com/cuda-gpus) to cross-reference Compute Capability to your GPU. +The table below applies to packages provided by LizardByte. If you use an official LizardByte package then you do not +need to install CUDA.} @@ -55,7 +57,7 @@ CUDA is used for NVFBC capture. - + @@ -68,7 +70,12 @@ CUDA is used for NVFBC capture. - + + + + + +
CUDA Compatibility
12.0.0 525.60.1350;52;60;61;62;70;72;75;80;86;87;89;9050;52;60;61;62;70;72;75;80;86;87;89;90 sunshine-debian-bookworm-{arch}.deb
sunshine_{arch}.flatpak
Sunshine (copr)Sunshine (copr - Fedora 40/41)
12.8.1570.124.06Sunshine (copr - Fedora 42)
diff --git a/package.json b/package.json index fc8f7993..96fc8fc8 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "dependencies": { "@lizardbyte/shared-web": "2025.326.11214", "vue": "3.5.13", - "vue-i18n": "11.1.2" + "vue-i18n": "11.1.3" }, "devDependencies": { "@vitejs/plugin-vue": "4.6.2", diff --git a/packaging/linux/fedora/Sunshine.spec b/packaging/linux/fedora/Sunshine.spec index e6afbce1..4d2d1d02 100644 --- a/packaging/linux/fedora/Sunshine.spec +++ b/packaging/linux/fedora/Sunshine.spec @@ -17,8 +17,6 @@ Source0: tarball.tar.gz # BuildRequires: boost-devel >= 1.86.0 BuildRequires: cmake >= 3.25.0 -BuildRequires: gcc -BuildRequires: gcc-c++ BuildRequires: libayatana-appindicator3-devel BuildRequires: libcap-devel BuildRequires: libcurl-devel @@ -37,6 +35,7 @@ BuildRequires: libXrandr-devel BuildRequires: libXtst-devel BuildRequires: git BuildRequires: mesa-libGL-devel +BuildRequires: mesa-libgbm-devel BuildRequires: miniupnpc-devel BuildRequires: npm BuildRequires: numactl-devel @@ -54,11 +53,22 @@ BuildRequires: which BuildRequires: xorg-x11-server-Xvfb # Conditional BuildRequires for cuda-gcc based on Fedora version -%if 0%{?fedora} >= 40 -# this package conflicts with gcc on f39 -BuildRequires: cuda-gcc-c++ +%if 0%{?fedora} >= 40 && 0%{?fedora} <= 41 +BuildRequires: gcc13 +BuildRequires: gcc13-c++ +%global gcc_version 13 +%global cuda_version 12.6.3 +%global cuda_build 560.35.05 +%elif %{?fedora} >= 42 +BuildRequires: gcc14 +BuildRequires: gcc14-c++ +%global gcc_version 14 +%global cuda_version 12.8.1 +%global cuda_build 570.124.06 %endif +%global cuda_dir %{_builddir}/cuda + Requires: libcap >= 2.22 Requires: libcurl >= 7.0 Requires: libdrm > 2.4.97 @@ -88,20 +98,14 @@ ls -a %{_builddir}/Sunshine %autopatch -p1 %build +# exit on error +set -e + # Detect the architecture and Fedora version architecture=$(uname -m) -fedora_version=%{fedora} cuda_supported_architectures=("x86_64" "aarch64") -# set cuda_version based on Fedora version -case "$fedora_version" in - *) - cuda_version="12.6.3" - cuda_build="560.35.05" - ;; -esac - # prepare CMAKE args cmake_args=( "-B=%{_builddir}/Sunshine/build" @@ -121,27 +125,23 @@ cmake_args=( "-DSUNSHINE_PUBLISHER_ISSUE_URL=https://app.lizardbyte.dev/support" ) +export CC=gcc-%{gcc_version} +export CXX=g++-%{gcc_version} + function install_cuda() { # check if we need to install cuda - if [ -f "%{_builddir}/cuda/bin/nvcc" ]; then + if [ -f "%{cuda_dir}/bin/nvcc" ]; then echo "cuda already installed" return fi - if [ "$fedora_version" -ge 40 ]; then - # update environment variables for CUDA, necessary when using cuda-gcc-c++ - export NVCC_PREPEND_FLAGS='-ccbin /usr/bin/g++-13' - export PATH=/usr/bin/cuda:"%{_builddir}/cuda/bin:${PATH}" - export LD_LIBRARY_PATH="%{_builddir}/cuda/lib64:${LD_LIBRARY_PATH}" - fi - local cuda_prefix="https://developer.download.nvidia.com/compute/cuda/" local cuda_suffix="" if [ "$architecture" == "aarch64" ]; then local cuda_suffix="_sbsa" fi - local url="${cuda_prefix}${cuda_version}/local_installers/cuda_${cuda_version}_${cuda_build}_linux${cuda_suffix}.run" + local url="${cuda_prefix}%{cuda_version}/local_installers/cuda_%{cuda_version}_%{cuda_build}_linux${cuda_suffix}.run" echo "cuda url: ${url}" wget \ "$url" \ @@ -157,23 +157,31 @@ function install_cuda() { --override \ --silent \ --toolkit \ - --toolkitpath="%{_builddir}/cuda" + --toolkitpath="%{cuda_dir}" rm "%{_builddir}/cuda.run" + + # we need to patch math_functions.h on fedora 42 + # see https://forums.developer.nvidia.com/t/error-exception-specification-is-incompatible-for-cospi-sinpi-cospif-sinpif-with-glibc-2-41/323591/3 + if [ "%{?fedora}" -eq 42 ]; then + echo "Original math_functions.h:" + find "%{cuda_dir}" -name math_functions.h -exec cat {} \; + + # Apply the patch + patch -p2 \ + --backup \ + --directory="%{cuda_dir}" \ + --verbose \ + < "%{_builddir}/Sunshine/packaging/linux/fedora/patches/f42/${architecture}/01-math_functions.patch" + fi } -# we need to clear these flags to avoid linkage errors with cuda-gcc-c++ -export CFLAGS="" -export CXXFLAGS="" -export FFLAGS="" -export FCFLAGS="" -export LDFLAGS="" -export CC=gcc-13 -export CXX=g++-13 - -if [ -n "$cuda_version" ] && [[ " ${cuda_supported_architectures[@]} " =~ " ${architecture} " ]]; then +if [ -n "%{cuda_version}" ] && [[ " ${cuda_supported_architectures[@]} " =~ " ${architecture} " ]]; then install_cuda cmake_args+=("-DSUNSHINE_ENABLE_CUDA=ON") - cmake_args+=("-DCMAKE_CUDA_COMPILER:PATH=%{_builddir}/cuda/bin/nvcc") + cmake_args+=("-DCMAKE_CUDA_COMPILER:PATH=%{cuda_dir}/bin/nvcc") + cmake_args+=("-DCMAKE_CUDA_HOST_COMPILER=gcc-%{gcc_version}") +else + cmake_args+=("-DSUNSHINE_ENABLE_CUDA=OFF") fi # setup the version diff --git a/packaging/linux/fedora/patches/f42/aarch64/01-math_functions.patch b/packaging/linux/fedora/patches/f42/aarch64/01-math_functions.patch new file mode 100644 index 00000000..322fef1c --- /dev/null +++ b/packaging/linux/fedora/patches/f42/aarch64/01-math_functions.patch @@ -0,0 +1,39 @@ +diff '--color=auto' -ur a/cuda/targets/sbsa-linux/include/crt/math_functions.h b/cuda/targets/sbsa-linux/include/crt/math_functions.h +--- a/cuda/targets/sbsa-linux/include/crt/math_functions.h 2024-08-23 00:25:39.000000000 +0200 ++++ b/cuda/targets/sbsa-linux/include/crt/math_functions.h 2025-02-17 01:19:44.270292640 +0100 +@@ -2553,7 +2553,7 @@ + * + * \note_accuracy_double + */ +-extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ double sinpi(double x); ++extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ double sinpi(double x) noexcept (true); + /** + * \ingroup CUDA_MATH_SINGLE + * \brief Calculate the sine of the input argument +@@ -2576,7 +2576,7 @@ + * + * \note_accuracy_single + */ +-extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ float sinpif(float x); ++extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ float sinpif(float x) noexcept (true); + /** + * \ingroup CUDA_MATH_DOUBLE + * \brief Calculate the cosine of the input argument +@@ -2598,7 +2598,7 @@ + * + * \note_accuracy_double + */ +-extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ double cospi(double x); ++extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ double cospi(double x) noexcept (true); + /** + * \ingroup CUDA_MATH_SINGLE + * \brief Calculate the cosine of the input argument +@@ -2620,7 +2620,7 @@ + * + * \note_accuracy_single + */ +-extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ float cospif(float x); ++extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ float cospif(float x) noexcept (true); + /** + * \ingroup CUDA_MATH_DOUBLE + * \brief Calculate the sine and cosine of the first input argument diff --git a/packaging/linux/fedora/patches/f42/x86_64/01-math_functions.patch b/packaging/linux/fedora/patches/f42/x86_64/01-math_functions.patch new file mode 100644 index 00000000..5e522eb1 --- /dev/null +++ b/packaging/linux/fedora/patches/f42/x86_64/01-math_functions.patch @@ -0,0 +1,39 @@ +diff '--color=auto' -ur a/cuda/targets/x86_64-linux/include/crt/math_functions.h b/cuda/targets/x86_64-linux/include/crt/math_functions.h +--- a/cuda/targets/x86_64-linux/include/crt/math_functions.h 2024-08-23 00:25:39.000000000 +0200 ++++ b/cuda/targets/x86_64-linux/include/crt/math_functions.h 2025-02-17 01:19:44.270292640 +0100 +@@ -2553,7 +2553,7 @@ + * + * \note_accuracy_double + */ +-extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ double sinpi(double x); ++extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ double sinpi(double x) noexcept (true); + /** + * \ingroup CUDA_MATH_SINGLE + * \brief Calculate the sine of the input argument +@@ -2576,7 +2576,7 @@ + * + * \note_accuracy_single + */ +-extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ float sinpif(float x); ++extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ float sinpif(float x) noexcept (true); + /** + * \ingroup CUDA_MATH_DOUBLE + * \brief Calculate the cosine of the input argument +@@ -2598,7 +2598,7 @@ + * + * \note_accuracy_double + */ +-extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ double cospi(double x); ++extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ double cospi(double x) noexcept (true); + /** + * \ingroup CUDA_MATH_SINGLE + * \brief Calculate the cosine of the input argument +@@ -2620,7 +2620,7 @@ + * + * \note_accuracy_single + */ +-extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ float cospif(float x); ++extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ float cospif(float x) noexcept (true); + /** + * \ingroup CUDA_MATH_DOUBLE + * \brief Calculate the sine and cosine of the first input argument diff --git a/packaging/linux/flatpak/modules/boost.json b/packaging/linux/flatpak/modules/boost.json index da111f64..79eadba4 100644 --- a/packaging/linux/flatpak/modules/boost.json +++ b/packaging/linux/flatpak/modules/boost.json @@ -9,8 +9,8 @@ "sources": [ { "type": "archive", - "url": "https://github.com/boostorg/boost/releases/download/boost-1.86.0/boost-1.86.0-cmake.tar.xz", - "sha256": "2c5ec5edcdff47ff55e27ed9560b0a0b94b07bd07ed9928b476150e16b0efc57" + "url": "https://github.com/boostorg/boost/releases/download/boost-1.87.0/boost-1.87.0-cmake.tar.xz", + "sha256": "7da75f171837577a52bbf217e17f8ea576c7c246e4594d617bfde7fafd408be5" } ] } diff --git a/packaging/sunshine.rb b/packaging/sunshine.rb index 3b5be19f..81e42b09 100644 --- a/packaging/sunshine.rb +++ b/packaging/sunshine.rb @@ -35,7 +35,6 @@ class @PROJECT_NAME@ < Formula depends_on "miniupnpc" depends_on "openssl" depends_on "opus" - depends_on "boost" => :recommended depends_on "icu4c" => :recommended on_linux do @@ -52,6 +51,7 @@ class @PROJECT_NAME@ < Formula depends_on "libxinerama" depends_on "libxrandr" depends_on "libxtst" + depends_on "mesa" depends_on "numactl" depends_on "pulseaudio" depends_on "systemd" diff --git a/scripts/linux_build.sh b/scripts/linux_build.sh index 368edcef..f80ed7bf 100644 --- a/scripts/linux_build.sh +++ b/scripts/linux_build.sh @@ -105,6 +105,7 @@ function add_debian_based_deps() { "libcurl4-openssl-dev" "libdrm-dev" # KMS "libevdev-dev" + "libgbm-dev" "libminiupnpc-dev" "libnotify-dev" "libnuma-dev" @@ -175,6 +176,7 @@ function add_fedora_deps() { "libXrandr-devel" # X11 "libXtst-devel" # X11 "mesa-libGL-devel" + "mesa-libgbm-devel" "miniupnpc-devel" "ninja-build" "npm" diff --git a/src/platform/linux/wayland.cpp b/src/platform/linux/wayland.cpp index c6e02d72..4fa05a27 100644 --- a/src/platform/linux/wayland.cpp +++ b/src/platform/linux/wayland.cpp @@ -6,9 +6,14 @@ #include // platform includes +#include +#include +#include #include +#include #include #include +#include // local includes #include "graphics.h" @@ -37,6 +42,12 @@ namespace wl { #define CLASS_CALL(c, m) classCall + // Define buffer params listener + static const struct zwp_linux_buffer_params_v1_listener params_listener = { + .created = dmabuf_t::buffer_params_created, + .failed = dmabuf_t::buffer_params_failed + }; + int display_t::init(const char *display_name) { if (!display_name) { display_name = std::getenv("WAYLAND_DISPLAY"); @@ -136,7 +147,13 @@ namespace wl { BOOST_LOG(info) << "Logical size: "sv << width << 'x' << height; } - void monitor_t::wl_mode(wl_output *wl_output, std::uint32_t flags, std::int32_t width, std::int32_t height, std::int32_t refresh) { + void monitor_t::wl_mode( + wl_output *wl_output, + std::uint32_t flags, + std::int32_t width, + std::int32_t height, + std::int32_t refresh + ) { viewport.width = width; viewport.height = height; @@ -151,6 +168,8 @@ namespace wl { interface_t::interface_t() noexcept : + screencopy_manager {nullptr}, + dmabuf_interface {nullptr}, output_manager {nullptr}, listener { &CLASS_CALL(interface_t, add_interface), @@ -162,7 +181,12 @@ namespace wl { wl_registry_add_listener(registry, &listener, this); } - void interface_t::add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version) { + void interface_t::add_interface( + wl_registry *registry, + std::uint32_t id, + const char *interface, + std::uint32_t version + ) { BOOST_LOG(debug) << "Available interface: "sv << interface << '(' << id << ") version "sv << version; if (!std::strcmp(interface, wl_output_interface.name)) { @@ -177,11 +201,16 @@ namespace wl { output_manager = (zxdg_output_manager_v1 *) wl_registry_bind(registry, id, &zxdg_output_manager_v1_interface, version); this->interface[XDG_OUTPUT] = true; - } else if (!std::strcmp(interface, zwlr_export_dmabuf_manager_v1_interface.name)) { + } else if (!std::strcmp(interface, zwlr_screencopy_manager_v1_interface.name)) { BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version; - dmabuf_manager = (zwlr_export_dmabuf_manager_v1 *) wl_registry_bind(registry, id, &zwlr_export_dmabuf_manager_v1_interface, version); + screencopy_manager = (zwlr_screencopy_manager_v1 *) wl_registry_bind(registry, id, &zwlr_screencopy_manager_v1_interface, version); this->interface[WLR_EXPORT_DMABUF] = true; + } else if (!std::strcmp(interface, zwp_linux_dmabuf_v1_interface.name)) { + BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version; + dmabuf_interface = (zwp_linux_dmabuf_v1 *) wl_registry_bind(registry, id, &zwp_linux_dmabuf_v1_interface, version); + + this->interface[LINUX_DMABUF] = true; } } @@ -189,94 +218,306 @@ namespace wl { BOOST_LOG(info) << "Delete: "sv << id; } + // Initialize GBM + bool dmabuf_t::init_gbm() { + if (gbm_device) { + return true; + } + + // Find render node + drmDevice *devices[16]; + int n = drmGetDevices2(0, devices, 16); + if (n <= 0) { + BOOST_LOG(error) << "No DRM devices found"sv; + return false; + } + + int drm_fd = -1; + for (int i = 0; i < n; i++) { + if (devices[i]->available_nodes & (1 << DRM_NODE_RENDER)) { + drm_fd = open(devices[i]->nodes[DRM_NODE_RENDER], O_RDWR); + if (drm_fd >= 0) { + break; + } + } + } + drmFreeDevices(devices, n); + + if (drm_fd < 0) { + BOOST_LOG(error) << "Failed to open DRM render node"sv; + return false; + } + + gbm_device = gbm_create_device(drm_fd); + if (!gbm_device) { + close(drm_fd); + BOOST_LOG(error) << "Failed to create GBM device"sv; + return false; + } + + return true; + } + + // Cleanup GBM + void dmabuf_t::cleanup_gbm() { + if (current_bo) { + gbm_bo_destroy(current_bo); + current_bo = nullptr; + } + + if (current_wl_buffer) { + wl_buffer_destroy(current_wl_buffer); + current_wl_buffer = nullptr; + } + } + dmabuf_t::dmabuf_t(): status {READY}, frames {}, current_frame {&frames[0]}, listener { - &CLASS_CALL(dmabuf_t, frame), - &CLASS_CALL(dmabuf_t, object), + &CLASS_CALL(dmabuf_t, buffer), + &CLASS_CALL(dmabuf_t, flags), &CLASS_CALL(dmabuf_t, ready), - &CLASS_CALL(dmabuf_t, cancel) + &CLASS_CALL(dmabuf_t, failed), + &CLASS_CALL(dmabuf_t, damage), + &CLASS_CALL(dmabuf_t, linux_dmabuf), + &CLASS_CALL(dmabuf_t, buffer_done), } { } - void dmabuf_t::listen(zwlr_export_dmabuf_manager_v1 *dmabuf_manager, wl_output *output, bool blend_cursor) { - auto frame = zwlr_export_dmabuf_manager_v1_capture_output(dmabuf_manager, blend_cursor, output); - zwlr_export_dmabuf_frame_v1_add_listener(frame, &listener, this); + // Start capture + void dmabuf_t::listen( + zwlr_screencopy_manager_v1 *screencopy_manager, + zwp_linux_dmabuf_v1 *dmabuf_interface, + wl_output *output, + bool blend_cursor + ) { + this->dmabuf_interface = dmabuf_interface; + // Reset state + shm_info.supported = false; + dmabuf_info.supported = false; + + // Create new frame + auto frame = zwlr_screencopy_manager_v1_capture_output( + screencopy_manager, + blend_cursor ? 1 : 0, + output + ); + + // Store frame data pointer for callbacks + zwlr_screencopy_frame_v1_set_user_data(frame, this); + + // Add listener + zwlr_screencopy_frame_v1_add_listener(frame, &listener, this); status = WAITING; } dmabuf_t::~dmabuf_t() { + cleanup_gbm(); + for (auto &frame : frames) { frame.destroy(); } + + if (gbm_device) { + // We should close the DRM FD, but it's owned by GBM + gbm_device_destroy(gbm_device); + gbm_device = nullptr; + } } - void dmabuf_t::frame( - zwlr_export_dmabuf_frame_v1 *frame, - std::uint32_t width, - std::uint32_t height, - std::uint32_t x, - std::uint32_t y, - std::uint32_t buffer_flags, - std::uint32_t flags, + // Buffer format callback + void dmabuf_t::buffer( + zwlr_screencopy_frame_v1 *frame, + uint32_t format, + uint32_t width, + uint32_t height, + uint32_t stride + ) { + shm_info.supported = true; + shm_info.format = format; + shm_info.width = width; + shm_info.height = height; + shm_info.stride = stride; + + BOOST_LOG(debug) << "Screencopy supports SHM format: "sv << format; + } + + // DMA-BUF format callback + void dmabuf_t::linux_dmabuf( + zwlr_screencopy_frame_v1 *frame, std::uint32_t format, - std::uint32_t high, - std::uint32_t low, - std::uint32_t obj_count + std::uint32_t width, + std::uint32_t height ) { - auto next_frame = get_next_frame(); + dmabuf_info.supported = true; + dmabuf_info.format = format; + dmabuf_info.width = width; + dmabuf_info.height = height; - next_frame->sd.fourcc = format; - next_frame->sd.width = width; - next_frame->sd.height = height; - next_frame->sd.modifier = (((std::uint64_t) high) << 32) | low; + BOOST_LOG(debug) << "Screencopy supports DMA-BUF format: "sv << format; } - void dmabuf_t::object( - zwlr_export_dmabuf_frame_v1 *frame, - std::uint32_t index, - std::int32_t fd, - std::uint32_t size, - std::uint32_t offset, - std::uint32_t stride, - std::uint32_t plane_index - ) { - auto next_frame = get_next_frame(); - - next_frame->sd.fds[plane_index] = fd; - next_frame->sd.pitches[plane_index] = stride; - next_frame->sd.offsets[plane_index] = offset; + // Flags callback + void dmabuf_t::flags(zwlr_screencopy_frame_v1 *frame, std::uint32_t flags) { + y_invert = flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT; + BOOST_LOG(debug) << "Frame flags: "sv << flags << (y_invert ? " (y_invert)" : ""); } + // DMA-BUF creation helper + void dmabuf_t::create_and_copy_dmabuf(zwlr_screencopy_frame_v1 *frame) { + if (!init_gbm()) { + BOOST_LOG(error) << "Failed to initialize GBM"sv; + zwlr_screencopy_frame_v1_destroy(frame); + status = REINIT; + return; + } + + // Create GBM buffer + current_bo = gbm_bo_create(gbm_device, dmabuf_info.width, dmabuf_info.height, dmabuf_info.format, GBM_BO_USE_RENDERING); + if (!current_bo) { + BOOST_LOG(error) << "Failed to create GBM buffer"sv; + zwlr_screencopy_frame_v1_destroy(frame); + status = REINIT; + return; + } + + // Get buffer info + int fd = gbm_bo_get_fd(current_bo); + if (fd < 0) { + BOOST_LOG(error) << "Failed to get buffer FD"sv; + gbm_bo_destroy(current_bo); + current_bo = nullptr; + zwlr_screencopy_frame_v1_destroy(frame); + status = REINIT; + return; + } + + uint32_t stride = gbm_bo_get_stride(current_bo); + uint64_t modifier = gbm_bo_get_modifier(current_bo); + + // Store in surface descriptor for later use + auto next_frame = get_next_frame(); + next_frame->sd.fds[0] = fd; + next_frame->sd.pitches[0] = stride; + next_frame->sd.offsets[0] = 0; + next_frame->sd.modifier = modifier; + + // Create linux-dmabuf buffer + auto params = zwp_linux_dmabuf_v1_create_params(dmabuf_interface); + zwp_linux_buffer_params_v1_add(params, fd, 0, 0, stride, modifier >> 32, modifier & 0xffffffff); + + // Add listener for buffer creation + zwp_linux_buffer_params_v1_add_listener(params, ¶ms_listener, frame); + + // Create Wayland buffer (async - callback will handle copy) + zwp_linux_buffer_params_v1_create(params, dmabuf_info.width, dmabuf_info.height, dmabuf_info.format, 0); + } + + // Buffer done callback - time to create buffer + void dmabuf_t::buffer_done(zwlr_screencopy_frame_v1 *frame) { + auto next_frame = get_next_frame(); + + // Prefer DMA-BUF if supported + if (dmabuf_info.supported && dmabuf_interface) { + // Store format info first + next_frame->sd.fourcc = dmabuf_info.format; + next_frame->sd.width = dmabuf_info.width; + next_frame->sd.height = dmabuf_info.height; + + // Create and start copy + create_and_copy_dmabuf(frame); + } else if (shm_info.supported) { + // SHM fallback would go here + BOOST_LOG(warning) << "SHM capture not implemented"sv; + zwlr_screencopy_frame_v1_destroy(frame); + status = REINIT; + } else { + BOOST_LOG(error) << "No supported buffer types"sv; + zwlr_screencopy_frame_v1_destroy(frame); + status = REINIT; + } + } + + // Buffer params created callback + void dmabuf_t::buffer_params_created( + void *data, + struct zwp_linux_buffer_params_v1 *params, + struct wl_buffer *buffer + ) { + auto frame = static_cast(data); + auto self = static_cast(zwlr_screencopy_frame_v1_get_user_data(frame)); + + // Store for cleanup + self->current_wl_buffer = buffer; + + // Start the actual copy + zwlr_screencopy_frame_v1_copy(frame, buffer); + } + + // Buffer params failed callback + void dmabuf_t::buffer_params_failed( + void *data, + struct zwp_linux_buffer_params_v1 *params + ) { + auto frame = static_cast(data); + auto self = static_cast(zwlr_screencopy_frame_v1_get_user_data(frame)); + + BOOST_LOG(error) << "Failed to create buffer from params"sv; + self->cleanup_gbm(); + + zwlr_screencopy_frame_v1_destroy(frame); + self->status = REINIT; + } + + // Ready callback void dmabuf_t::ready( - zwlr_export_dmabuf_frame_v1 *frame, + zwlr_screencopy_frame_v1 *frame, std::uint32_t tv_sec_hi, std::uint32_t tv_sec_lo, std::uint32_t tv_nsec ) { - zwlr_export_dmabuf_frame_v1_destroy(frame); + BOOST_LOG(debug) << "Frame ready"sv; + // Frame is ready for use, GBM buffer now contains screen content current_frame->destroy(); current_frame = get_next_frame(); + // Keep the GBM buffer alive but destroy the Wayland objects + if (current_wl_buffer) { + wl_buffer_destroy(current_wl_buffer); + current_wl_buffer = nullptr; + } + + cleanup_gbm(); + + zwlr_screencopy_frame_v1_destroy(frame); status = READY; } - void dmabuf_t::cancel( - zwlr_export_dmabuf_frame_v1 *frame, - std::uint32_t reason - ) { - zwlr_export_dmabuf_frame_v1_destroy(frame); + // Failed callback + void dmabuf_t::failed(zwlr_screencopy_frame_v1 *frame) { + BOOST_LOG(error) << "Frame capture failed"sv; + // Clean up resources + cleanup_gbm(); auto next_frame = get_next_frame(); next_frame->destroy(); + zwlr_screencopy_frame_v1_destroy(frame); status = REINIT; } + void dmabuf_t::damage( + zwlr_screencopy_frame_v1 *frame, + std::uint32_t x, + std::uint32_t y, + std::uint32_t width, + std::uint32_t height + ) {}; + void frame_t::destroy() { for (auto x = 0; x < 4; ++x) { if (sd.fds[x] >= 0) { diff --git a/src/platform/linux/wayland.h b/src/platform/linux/wayland.h index 6a6986f6..08d5acbd 100644 --- a/src/platform/linux/wayland.h +++ b/src/platform/linux/wayland.h @@ -8,7 +8,8 @@ #include #ifdef SUNSHINE_BUILD_WAYLAND - #include + #include + #include #include #endif @@ -27,9 +28,9 @@ namespace wl { class frame_t { public: frame_t(); - egl::surface_descriptor_t sd; - void destroy(); + + egl::surface_descriptor_t sd; }; class dmabuf_t { @@ -40,104 +41,91 @@ namespace wl { REINIT, ///< Reinitialize the frame }; + dmabuf_t(); + ~dmabuf_t(); + dmabuf_t(dmabuf_t &&) = delete; dmabuf_t(const dmabuf_t &) = delete; - dmabuf_t &operator=(const dmabuf_t &) = delete; dmabuf_t &operator=(dmabuf_t &&) = delete; - dmabuf_t(); + void listen(zwlr_screencopy_manager_v1 *screencopy_manager, zwp_linux_dmabuf_v1 *dmabuf_interface, wl_output *output, bool blend_cursor = false); + static void buffer_params_created(void *data, struct zwp_linux_buffer_params_v1 *params, struct wl_buffer *wl_buffer); + static void buffer_params_failed(void *data, struct zwp_linux_buffer_params_v1 *params); + void buffer(zwlr_screencopy_frame_v1 *frame, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t stride); + void linux_dmabuf(zwlr_screencopy_frame_v1 *frame, std::uint32_t format, std::uint32_t width, std::uint32_t height); + void buffer_done(zwlr_screencopy_frame_v1 *frame); + void flags(zwlr_screencopy_frame_v1 *frame, std::uint32_t flags); + void damage(zwlr_screencopy_frame_v1 *frame, std::uint32_t x, std::uint32_t y, std::uint32_t width, std::uint32_t height); + void ready(zwlr_screencopy_frame_v1 *frame, std::uint32_t tv_sec_hi, std::uint32_t tv_sec_lo, std::uint32_t tv_nsec); + void failed(zwlr_screencopy_frame_v1 *frame); - void listen(zwlr_export_dmabuf_manager_v1 *dmabuf_manager, wl_output *output, bool blend_cursor = false); - - ~dmabuf_t(); - - void frame( - zwlr_export_dmabuf_frame_v1 *frame, - std::uint32_t width, - std::uint32_t height, - std::uint32_t x, - std::uint32_t y, - std::uint32_t buffer_flags, - std::uint32_t flags, - std::uint32_t format, - std::uint32_t high, - std::uint32_t low, - std::uint32_t obj_count - ); - - void object( - zwlr_export_dmabuf_frame_v1 *frame, - std::uint32_t index, - std::int32_t fd, - std::uint32_t size, - std::uint32_t offset, - std::uint32_t stride, - std::uint32_t plane_index - ); - - void ready( - zwlr_export_dmabuf_frame_v1 *frame, - std::uint32_t tv_sec_hi, - std::uint32_t tv_sec_lo, - std::uint32_t tv_nsec - ); - - void cancel( - zwlr_export_dmabuf_frame_v1 *frame, - std::uint32_t reason - ); - - inline frame_t *get_next_frame() { + frame_t *get_next_frame() { return current_frame == &frames[0] ? &frames[1] : &frames[0]; } status_e status; - std::array frames; frame_t *current_frame; + zwlr_screencopy_frame_v1_listener listener; - zwlr_export_dmabuf_frame_v1_listener listener; + private: + bool init_gbm(); + void cleanup_gbm(); + void create_and_copy_dmabuf(zwlr_screencopy_frame_v1 *frame); + + zwp_linux_dmabuf_v1 *dmabuf_interface {nullptr}; + + struct { + bool supported {false}; + std::uint32_t format; + std::uint32_t width; + std::uint32_t height; + std::uint32_t stride; + } shm_info; + + struct { + bool supported {false}; + std::uint32_t format; + std::uint32_t width; + std::uint32_t height; + } dmabuf_info; + + struct gbm_device *gbm_device {nullptr}; + struct gbm_bo *current_bo {nullptr}; + struct wl_buffer *current_wl_buffer {nullptr}; + bool y_invert {false}; }; class monitor_t { public: + explicit monitor_t(wl_output *output); + monitor_t(monitor_t &&) = delete; monitor_t(const monitor_t &) = delete; - monitor_t &operator=(const monitor_t &) = delete; monitor_t &operator=(monitor_t &&) = delete; - monitor_t(wl_output *output); - + void listen(zxdg_output_manager_v1 *output_manager); void xdg_name(zxdg_output_v1 *, const char *name); void xdg_description(zxdg_output_v1 *, const char *description); void xdg_position(zxdg_output_v1 *, std::int32_t x, std::int32_t y); void xdg_size(zxdg_output_v1 *, std::int32_t width, std::int32_t height); - void xdg_done(zxdg_output_v1 *) { - } + void xdg_done(zxdg_output_v1 *) {} - void wl_geometry(wl_output *wl_output, std::int32_t x, std::int32_t y, std::int32_t physical_width, std::int32_t physical_height, std::int32_t subpixel, const char *make, const char *model, std::int32_t transform) { - } + void wl_geometry(wl_output *wl_output, std::int32_t x, std::int32_t y, std::int32_t physical_width, std::int32_t physical_height, std::int32_t subpixel, const char *make, const char *model, std::int32_t transform) {} void wl_mode(wl_output *wl_output, std::uint32_t flags, std::int32_t width, std::int32_t height, std::int32_t refresh); - void wl_done(wl_output *wl_output) { - } + void wl_done(wl_output *wl_output) {} - void wl_scale(wl_output *wl_output, std::int32_t factor) { - } - - void listen(zxdg_output_manager_v1 *output_manager); + void wl_scale(wl_output *wl_output, std::int32_t factor) {} wl_output *output; - std::string name; std::string description; - platf::touch_port_t viewport; - wl_output_listener wl_listener; zxdg_output_v1_listener xdg_listener; }; @@ -151,35 +139,34 @@ namespace wl { public: enum interface_e { XDG_OUTPUT, ///< xdg-output - WLR_EXPORT_DMABUF, ///< Export dmabuf + WLR_EXPORT_DMABUF, ///< screencopy manager + LINUX_DMABUF, ///< linux-dmabuf protocol MAX_INTERFACES, ///< Maximum number of interfaces }; + interface_t() noexcept; + interface_t(interface_t &&) = delete; interface_t(const interface_t &) = delete; - interface_t &operator=(const interface_t &) = delete; interface_t &operator=(interface_t &&) = delete; - interface_t() noexcept; - void listen(wl_registry *registry); - std::vector> monitors; - - zwlr_export_dmabuf_manager_v1 *dmabuf_manager; - zxdg_output_manager_v1 *output_manager; - bool operator[](interface_e bit) const { return interface[bit]; } + std::vector> monitors; + zwlr_screencopy_manager_v1 *screencopy_manager {nullptr}; + zwp_linux_dmabuf_v1 *dmabuf_interface {nullptr}; + zxdg_output_manager_v1 *output_manager {nullptr}; + private: void add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version); void del_interface(wl_registry *registry, uint32_t id); std::bitset interface; - wl_registry_listener listener; }; @@ -212,7 +199,6 @@ namespace wl { }; std::vector> monitors(const char *display_name = nullptr); - int init(); } // namespace wl #else @@ -223,21 +209,18 @@ struct zxdg_output_manager_v1; namespace wl { class monitor_t { public: + monitor_t(wl_output *output); + monitor_t(monitor_t &&) = delete; monitor_t(const monitor_t &) = delete; - monitor_t &operator=(const monitor_t &) = delete; monitor_t &operator=(monitor_t &&) = delete; - monitor_t(wl_output *output); - void listen(zxdg_output_manager_v1 *output_manager); wl_output *output; - std::string name; std::string description; - platf::touch_port_t viewport; }; diff --git a/src/platform/linux/wlgrab.cpp b/src/platform/linux/wlgrab.cpp index b867b908..9ef3e09f 100644 --- a/src/platform/linux/wlgrab.cpp +++ b/src/platform/linux/wlgrab.cpp @@ -90,7 +90,7 @@ namespace wl { auto to = std::chrono::steady_clock::now() + timeout; // Dispatch events until we get a new frame or the timeout expires - dmabuf.listen(interface.dmabuf_manager, output, cursor); + dmabuf.listen(interface.screencopy_manager, interface.dmabuf_interface, output, cursor); do { auto remaining_time_ms = std::chrono::duration_cast(to - std::chrono::steady_clock::now()); if (remaining_time_ms.count() < 0 || !display.dispatch(remaining_time_ms)) { diff --git a/src/platform/windows/windows.rs.in b/src/platform/windows/windows.rc.in similarity index 93% rename from src/platform/windows/windows.rs.in rename to src/platform/windows/windows.rc.in index d042504e..d583d2dc 100644 --- a/src/platform/windows/windows.rs.in +++ b/src/platform/windows/windows.rc.in @@ -1,7 +1,7 @@ /** - * @file src/platform/windows/windows.rs.in + * @file src/platform/windows/windows.rc.in * @brief Windows resource file template. - * @note The final `windows.rs` is generated from this file during the CMake build. + * @note The final `windows.rc` is generated from this file during the CMake build. * @todo Use CMake definitions directly, instead of configuring this file. */ #include "winver.h" diff --git a/third-party/libdisplaydevice b/third-party/libdisplaydevice index 591387c5..13a4aca3 160000 --- a/third-party/libdisplaydevice +++ b/third-party/libdisplaydevice @@ -1 +1 @@ -Subproject commit 591387c58465376540fc9f505e938860e08fd47e +Subproject commit 13a4aca3c2f3eb78adef8f70231920f93f91f833