fix(vaapi): use matched system libva libraries (#5943)

This commit is contained in:
Dave Lane authored and GitHub committed 2026-10-16 20:01:49 -04:00
1 parent 7fa08df407
commit d2250f24e7
11 files changed
+268 -116

No files matched your search

+7 -18
View File
@@ -68,24 +68,6 @@ jobs:
libxfixes-dev \
libxtst-dev
- name: Build latest libva
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
timeout-minutes: 5
run: |
gh release download --archive=tar.gz --repo=intel/libva
tar xzf libva-*.tar.gz && rm libva-*.tar.gz
cd libva-*
./autogen.sh --prefix=/usr --libdir="/usr/lib/${M_ARCH}-linux-gnu" \
--enable-drm \
--enable-x11 \
--enable-glx \
--enable-wayland \
--without-legacy
make -j "$(nproc)"
sudo make install
cd .. && rm -rf libva-*
- name: Build Linux
env:
BRANCH: ${{ github.head_ref || github.ref_name }}
@@ -142,10 +124,17 @@ jobs:
--appdir ./AppDir \
--plugin qt \
--executable ./sunshine \
--exclude-library 'libva.so*' \
--exclude-library 'libva-*.so*' \
--icon-file "../$ICON_FILE" \
--desktop-file "./$DESKTOP_FILE" \
--output appimage
# Keep the host's libva core, backends, and driver configuration together.
readelf -d ./sunshine | grep -F 'Shared library: [libva.so.2]'
readelf -d ./sunshine | grep -F 'Shared library: [libva-drm.so.2]'
test -z "$(find ./AppDir \( -name 'libva.so*' -o -name 'libva-*.so*' \) -print -quit)"
# print original name of AppImage file
echo "AppImage file: $(ls Sunshine*.AppImage)"
+40 -45
View File
@@ -10,47 +10,48 @@
# find_path() and find_library() calls
# cmake-format: on
if(EXISTS "${FFMPEG_PREPARED_BINARIES}/lib/libva.a")
# The display context ABI is private to libva. Keep its core, DRM backend,
# and headers from the same build as FFmpeg instead of mixing system libva.
set(LIBVA_INCLUDE_DIR "${FFMPEG_PREPARED_BINARIES}/include")
set(LIBVA_LIB "${FFMPEG_PREPARED_BINARIES}/lib/libva.a")
set(LIBVA_DRM_LIB "${FFMPEG_PREPARED_BINARIES}/lib/libva-drm.a")
foreach(required_file IN ITEMS
"${LIBVA_INCLUDE_DIR}/va/va.h"
"${LIBVA_INCLUDE_DIR}/va/va_drm.h"
"${LIBVA_DRM_LIB}")
if(NOT EXISTS "${required_file}")
message(FATAL_ERROR "The bundled libva package is incomplete: missing ${required_file}")
# The core and DRM backend share a private ABI. Use the host's matched libraries
# and headers so libva also retains the host's driver discovery configuration.
set(_libva_saved_ignore_prefix_path "${CMAKE_IGNORE_PREFIX_PATH}")
set(_libva_saved_library_suffixes "${CMAKE_FIND_LIBRARY_SUFFIXES}")
if(FFMPEG_PREPARED_BINARIES)
list(APPEND CMAKE_IGNORE_PREFIX_PATH "${FFMPEG_PREPARED_BINARIES}")
# Discard paths cached by builds that selected the bundled libva.
foreach(variable LIBVA_INCLUDE_DIR LIBVA_LIB LIBVA_DRM_LIB)
cmake_path(IS_PREFIX FFMPEG_PREPARED_BINARIES "${${variable}}" NORMALIZE _libva_bundled)
if(_libva_bundled)
unset(${variable} CACHE)
unset(${variable})
endif()
endforeach()
# libva-drm.pc does not declare the libdrm dependency of its static archive.
find_package(LIBDRM REQUIRED)
else()
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
pkg_check_modules(_LIBVA libva)
pkg_check_modules(_LIBVA_DRM libva-drm)
endif()
find_path(
LIBVA_INCLUDE_DIR
NAMES va/va.h va/va_drm.h
HINTS ${_LIBVA_INCLUDE_DIRS}
PATHS /usr/include /usr/local/include /opt/local/include)
find_library(
LIBVA_LIB
NAMES ${_LIBVA_LIBRARIES} libva
HINTS ${_LIBVA_LIBRARY_DIRS}
PATHS /usr/lib /usr/local/lib /opt/local/lib)
find_library(
LIBVA_DRM_LIB
NAMES ${_LIBVA_DRM_LIBRARIES} libva-drm
HINTS ${_LIBVA_DRM_LIBRARY_DIRS}
PATHS /usr/lib /usr/local/lib /opt/local/lib)
endif()
set(CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_SHARED_LIBRARY_SUFFIX}")
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
pkg_check_modules(_LIBVA libva)
pkg_check_modules(_LIBVA_DRM libva-drm)
endif()
find_path(
LIBVA_INCLUDE_DIR
NAMES va/va.h va/va_drm.h
HINTS ${_LIBVA_INCLUDE_DIRS}
PATHS /usr/include /usr/local/include /opt/local/include)
find_library(
LIBVA_LIB
NAMES va
HINTS ${_LIBVA_LIBRARY_DIRS}
PATHS /usr/lib /usr/local/lib /opt/local/lib)
get_filename_component(_libva_library_dir "${LIBVA_LIB}" DIRECTORY)
find_library(
LIBVA_DRM_LIB
NAMES va-drm
PATHS "${_libva_library_dir}"
NO_DEFAULT_PATH)
set(CMAKE_IGNORE_PREFIX_PATH "${_libva_saved_ignore_prefix_path}")
set(CMAKE_FIND_LIBRARY_SUFFIXES "${_libva_saved_library_suffixes}")
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Libva REQUIRED_VARS LIBVA_INCLUDE_DIR LIBVA_LIB LIBVA_DRM_LIB)
@@ -83,13 +84,7 @@ if(LIBVA_FOUND)
endif()
set_target_properties(Libva::drm PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${LIBVA_INCLUDE_DIRS}")
if(EXISTS "${FFMPEG_PREPARED_BINARIES}/lib/libva.a")
set_target_properties(Libva::drm PROPERTIES
INTERFACE_LINK_LIBRARIES "Libva::va;${LIBDRM_LIBRARIES}")
endif()
endif()
if(EXISTS "${FFMPEG_PREPARED_BINARIES}/lib/libva.a")
set(LIBVA_DRM_LIBRARIES Libva::drm)
set_target_properties(Libva::drm PROPERTIES INTERFACE_LINK_LIBRARIES Libva::va)
endif()
endif()
+4
View File
@@ -114,6 +114,10 @@ endif()
include(dependencies/libevdev_Sunshine)
# vaapi
# FFmpeg's static archives need these compatibility symbols even when Sunshine's
# VA-API encoder is disabled. The host's core and DRM libraries are always linked.
include_directories(SYSTEM ${LIBVA_INCLUDE_DIR})
list(APPEND PLATFORM_TARGET_FILES "${CMAKE_SOURCE_DIR}/src/platform/linux/vaapi_compat.cpp")
if(${SUNSHINE_ENABLE_VAAPI})
find_package(Libva REQUIRED)
else()
+1 -1
View File
@@ -103,7 +103,7 @@ include("${CMAKE_MODULE_PATH}/dependencies/ffmpeg.cmake")
if(TARGET PkgConfig::SUNSHINE_BUNDLED_OPUS)
# Match the Opus version linked by the shared FFmpeg package.
set(Opus_LIBRARY PkgConfig::SUNSHINE_BUNDLED_OPUS) # cmake-lint: disable=C0103
set(Opus_INCLUDE_DIR "${FFMPEG_PREPARED_BINARIES}/include") # cmake-lint: disable=C0103
set(Opus_INCLUDE_DIR "${FFMPEG_INCLUDE_DIRS}") # cmake-lint: disable=C0103
else()
# Homebrew provides opus as a dynamic library only, so disable static linking for Homebrew builds
if(SUNSHINE_BUILD_HOMEBREW)
+76 -9
View File
@@ -176,15 +176,6 @@ else()
list(APPEND FFMPEG_LIBRARIES ${FFMPEG_PLATFORM_LIBRARIES})
endif()
# Legacy library lists can also refer to libva backends by name. Resolve those
# names to the matching archives whenever the FFmpeg package supplies libva.
if(EXISTS "${FFMPEG_PREPARED_BINARIES}/lib/libva.a")
foreach(library IN ITEMS va va-drm va-x11)
list(TRANSFORM FFMPEG_LIBRARIES REPLACE
"^${library}$" "${FFMPEG_PREPARED_BINARIES}/lib/lib${library}.a")
endforeach()
endif()
# Shared packages supply the full static dependency list, including decoders.
# Older packages retain the library list above.
set(_sunshine_ffmpeg_pc "${FFMPEG_PREPARED_BINARIES}/lib/pkgconfig/libavcodec.pc")
@@ -248,3 +239,79 @@ if(_sunshine_ffmpeg_relocatable GREATER_EQUAL 0)
endif()
set(FFMPEG_INCLUDE_DIRS "${FFMPEG_PREPARED_BINARIES}/include")
if(UNIX AND NOT APPLE)
find_package(Libva REQUIRED)
# Static FFmpeg metadata can pull bundled libva archives back into the link.
# Resolve every VA backend to the host, including legacy library-name lists.
set(_sunshine_saved_ignore_prefix_path "${CMAKE_IGNORE_PREFIX_PATH}")
set(_sunshine_saved_library_suffixes "${CMAKE_FIND_LIBRARY_SUFFIXES}")
list(APPEND CMAKE_IGNORE_PREFIX_PATH "${FFMPEG_PREPARED_BINARIES}")
set(CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_SHARED_LIBRARY_SUFFIX}")
set(_sunshine_ffmpeg_library_lists FFMPEG_LIBRARIES)
if(TARGET PkgConfig::SUNSHINE_BUNDLED_FFMPEG)
get_target_property(_sunshine_ffmpeg_link_libraries
PkgConfig::SUNSHINE_BUNDLED_FFMPEG INTERFACE_LINK_LIBRARIES)
list(APPEND _sunshine_ffmpeg_library_lists _sunshine_ffmpeg_link_libraries)
endif()
foreach(library_list IN LISTS _sunshine_ffmpeg_library_lists)
set(_sunshine_system_libraries)
foreach(library IN LISTS ${library_list})
get_filename_component(_sunshine_library_name "${library}" NAME)
if(_sunshine_library_name MATCHES "^(-l|lib)?(va(-[a-z0-9]+)?)(\\.(a|so)(\\.[0-9]+)*)?$")
set(_sunshine_va_name "${CMAKE_MATCH_2}")
string(MAKE_C_IDENTIFIER "${_sunshine_va_name}" _sunshine_va_id)
if(_sunshine_va_name STREQUAL "va")
list(APPEND _sunshine_system_libraries "${LIBVA_LIB}")
elseif(_sunshine_va_name STREQUAL "va-drm")
list(APPEND _sunshine_system_libraries "${LIBVA_DRM_LIB}")
else()
# Backends must come from the same installation as the core.
unset(_sunshine_system_${_sunshine_va_id} CACHE)
find_library(_sunshine_system_${_sunshine_va_id} NAMES "${_sunshine_va_name}"
PATHS "${_libva_library_dir}" NO_DEFAULT_PATH REQUIRED)
list(APPEND _sunshine_system_libraries "${_sunshine_system_${_sunshine_va_id}}")
endif()
else()
list(APPEND _sunshine_system_libraries "${library}")
endif()
endforeach()
set(${library_list} "${_sunshine_system_libraries}")
endforeach()
if(TARGET PkgConfig::SUNSHINE_BUNDLED_FFMPEG)
set_property(TARGET PkgConfig::SUNSHINE_BUNDLED_FFMPEG PROPERTY
INTERFACE_LINK_LIBRARIES "${_sunshine_ffmpeg_link_libraries}")
endif()
set(CMAKE_IGNORE_PREFIX_PATH "${_sunshine_saved_ignore_prefix_path}")
set(CMAKE_FIND_LIBRARY_SUFFIXES "${_sunshine_saved_library_suffixes}")
# /usr/include is a compiler default: putting it before the bundled include
# directory cannot override bundled VA headers. Expose a header tree without
# va/ so Sunshine's compatibility shims follow the host libva version.
set(FFMPEG_INCLUDE_DIRS "${CMAKE_BINARY_DIR}/_deps/ffmpeg-system-includes")
file(COPY "${FFMPEG_PREPARED_BINARIES}/include/" DESTINATION "${FFMPEG_INCLUDE_DIRS}" PATTERN "va" EXCLUDE)
set(_sunshine_bundled_include "${FFMPEG_PREPARED_BINARIES}/include")
if(NV_CODEC_HEADERS_13_1_INCLUDE_DIR STREQUAL _sunshine_bundled_include)
set(NV_CODEC_HEADERS_13_1_INCLUDE_DIR "${FFMPEG_INCLUDE_DIRS}")
endif()
foreach(dependency FFMPEG OPUS)
if(TARGET PkgConfig::SUNSHINE_BUNDLED_${dependency})
get_target_property(_sunshine_include_dirs
PkgConfig::SUNSHINE_BUNDLED_${dependency} INTERFACE_INCLUDE_DIRECTORIES)
set(_sunshine_system_include_dirs)
foreach(_sunshine_include_dir IN LISTS _sunshine_include_dirs) # cmake-lint: disable=C0103
cmake_path(NORMAL_PATH _sunshine_include_dir)
cmake_path(IS_PREFIX _sunshine_bundled_include "${_sunshine_include_dir}" NORMALIZE _sunshine_bundled)
if(_sunshine_bundled)
cmake_path(RELATIVE_PATH _sunshine_include_dir BASE_DIRECTORY "${_sunshine_bundled_include}"
OUTPUT_VARIABLE _sunshine_include_suffix)
set(_sunshine_include_dir "${FFMPEG_INCLUDE_DIRS}/${_sunshine_include_suffix}")
endif()
list(APPEND _sunshine_system_include_dirs "${_sunshine_include_dir}")
endforeach()
set_property(TARGET PkgConfig::SUNSHINE_BUNDLED_${dependency} PROPERTY
INTERFACE_INCLUDE_DIRECTORIES "${_sunshine_system_include_dirs}")
endif()
endforeach()
endif()
+5 -3
View File
@@ -22,9 +22,11 @@ Sunshine's build and packaging automation uses packaged static Boost libraries o
Ubuntu 26.04 or newer, macOS with Homebrew, Windows with MSYS2, and FreeBSD. Older supported distributions continue
to use the CPM fallback because their repositories do not meet the minimum Boost version.
When the prebuilt FFmpeg package supplies libva, Sunshine uses its bundled headers, core library, and DRM backend
together. These components share a private display-context ABI and must come from the same libva build. The system
still provides libdrm and the GPU's VA-API driver. FFmpeg packages without bundled libva use the system libva libraries.
On Linux and FreeBSD, Sunshine and the prebuilt FFmpeg libraries use the system's shared libva core and backends.
These components share a private display-context ABI and must come from the same libva build. Sunshine also uses
the system libva headers so its compatibility shims match the installed API. Bundled libva archives and headers
are excluded from the build. The system provides libdrm, the GPU's VA-API driver, and libva's driver configuration.
AppImages use the same system libraries at runtime; they do not bundle libva.
#### Prebuilt Web UI
+2
View File
@@ -129,6 +129,8 @@ apk del sunshine
> [!NOTE]
> The AppImage is built on Ubuntu 22.04, which requires `glibc 2.35` or newer and `libstdc++ 3.4.11` or newer.
> It also requires the host's libva core and DRM libraries (`libva2` and `libva-drm2` on Debian/Ubuntu).
> Install your GPU's VA-API driver to use VA-API encoding. Both libva libraries must come from the same system package version.
##### Install
1. Download `Sunshine_{version}_{arch}.AppImage`
+1 -2
View File
@@ -197,7 +197,7 @@ Options:
If you provide a modified version of Sunshine, we kindly request that you use your own url.
--skip-cleanup Do not restore the original gcc alternatives, or the math-vector.h file.
--skip-cuda Skip CUDA installation.
--skip-libva Skip libva installation. This will automatically be enabled if passing --appimage-build.
--skip-libva Skip libva installation.
--skip-package Skip creating DEB, or RPM package.
--ubuntu-test-repo Install ppa:ubuntu-toolchain-r/test repo on Ubuntu.
--step Which step(s) to run: deps, cmake, validation, build, package, cleanup, or all (default: all)
@@ -225,7 +225,6 @@ while getopts ":hs-:" opt; do
help) _usage 0 ;;
appimage-build)
appimage_build=1
skip_libva=1
;;
cuda-patches)
cuda_patches=1
-38
View File
@@ -16,44 +16,6 @@ extern "C" {
#include <va/va.h>
#include <va/va_drm.h>
#include <va/va_drmcommon.h>
#if !VA_CHECK_VERSION(1, 9, 0)
/**
* @brief Stub vaSyncBuffer when building against libva before 2.9.0.
*
* @param dpy VA display.
* @param buf_id VA buffer ID.
* @param timeout_ns Sync timeout in nanoseconds.
* @return VA status code.
*/
VAStatus
vaSyncBuffer(
VADisplay dpy,
VABufferID buf_id,
uint64_t timeout_ns
) {
return VA_STATUS_ERROR_UNIMPLEMENTED;
}
#endif
#if !VA_CHECK_VERSION(1, 21, 0)
/**
* @brief Stub vaMapBuffer2 when building against libva before 2.21.0.
* @details The older vaMapBuffer API does not accept the mapping flags supplied by the new ABI.
*
* @param dpy VA display.
* @param buf_id VA buffer ID.
* @param pbuf Output mapped buffer pointer.
* @return VA status code.
*/
VAStatus
vaMapBuffer2(
VADisplay dpy,
VABufferID buf_id,
void **pbuf,
uint32_t
) {
return vaMapBuffer(dpy, buf_id, pbuf);
}
#endif
}
// local includes
+47
View File
@@ -0,0 +1,47 @@
/**
* @file src/platform/linux/vaapi_compat.cpp
* @brief Compatibility functions for prebuilt FFmpeg with older system libva.
*/
#include <cstdint>
extern "C" {
#include <va/va.h>
#if !VA_CHECK_VERSION(1, 9, 0)
/**
* @brief Stub vaSyncBuffer when building against libva before 2.9.0.
*
* @param dpy VA display.
* @param buf_id VA buffer ID.
* @param timeout_ns Sync timeout in nanoseconds.
* @return VA status code.
*/
VAStatus
vaSyncBuffer(
VADisplay dpy,
VABufferID buf_id,
uint64_t timeout_ns
) {
return VA_STATUS_ERROR_UNIMPLEMENTED;
}
#endif
#if !VA_CHECK_VERSION(1, 21, 0)
/**
* @brief Stub vaMapBuffer2 when building against libva before 2.21.0.
* @details The older vaMapBuffer API does not accept the mapping flags supplied by the new ABI.
*
* @param dpy VA display.
* @param buf_id VA buffer ID.
* @param pbuf Output mapped buffer pointer.
* @return VA status code.
*/
VAStatus
vaMapBuffer2(
VADisplay dpy,
VABufferID buf_id,
void **pbuf,
uint32_t
) {
return vaMapBuffer(dpy, buf_id, pbuf);
}
#endif
}
+85
View File
@@ -0,0 +1,85 @@
/**
* @file tests/unit/test_vaapi_compat.cpp
* @brief Verify the system libva compatibility functions used by prebuilt FFmpeg.
*/
// test includes
#include "../tests_common.h"
#if defined(__linux__) || defined(__FreeBSD__)
// standard includes
#include <cstdint>
extern "C" {
#include <va/va.h>
}
#if !VA_CHECK_VERSION(1, 21, 0)
/**
* @brief Compatibility entry point supplied for prebuilt FFmpeg.
* @param display VA display.
* @param buffer VA buffer ID.
* @param output Output mapped buffer pointer.
* @param flags Mapping flags ignored by the legacy API.
* @return VA status code from the legacy mapping API.
*/
extern "C" VAStatus vaMapBuffer2(VADisplay display, VABufferID buffer, void **output, uint32_t flags);
TEST(VaapiCompatibilityTest, InvalidDisplayReturnsLegacyMappingError) {
void *output = nullptr;
EXPECT_EQ(vaMapBuffer2(nullptr, VA_INVALID_ID, &output, 0), VA_STATUS_ERROR_INVALID_DISPLAY);
EXPECT_EQ(vaMapBuffer2(nullptr, VA_INVALID_ID, &output, UINT32_MAX), VA_STATUS_ERROR_INVALID_DISPLAY);
}
namespace {
VADisplay mapped_display; ///< Display passed to the legacy mapping API.
VABufferID mapped_buffer; ///< Buffer passed to the legacy mapping API.
void **mapped_output; ///< Output pointer passed to the legacy mapping API.
VAStatus mapping_status; ///< Status returned by the legacy mapping API.
} // namespace
/**
* @brief Record the legacy mapping arguments and return the configured status.
* @param display VA display.
* @param buffer VA buffer ID.
* @param output Output mapped buffer pointer.
* @return Configured VA status code.
*/
extern "C" VAStatus sunshine_test_vaMapBuffer(VADisplay display, VABufferID buffer, void **output) {
mapped_display = display;
mapped_buffer = buffer;
mapped_output = output;
return mapping_status;
}
// Compile the production shims with isolated symbols so the fake mapping API
// cannot intercept VA-API calls elsewhere in test_sunshine.
#define vaMapBuffer sunshine_test_vaMapBuffer
#define vaMapBuffer2 sunshine_test_vaMapBuffer2
#define vaSyncBuffer sunshine_test_vaSyncBuffer
#include "src/platform/linux/vaapi_compat.cpp"
#undef vaSyncBuffer
#undef vaMapBuffer2
#undef vaMapBuffer
TEST(VaapiCompatibilityTest, MapBufferPreservesArgumentsAndStatus) {
int display_storage = 0;
VADisplay display = &display_storage;
void *output = nullptr;
for (const auto status : {VA_STATUS_SUCCESS, VA_STATUS_ERROR_INVALID_BUFFER}) {
mapping_status = status;
EXPECT_EQ(sunshine_test_vaMapBuffer2(display, 42, &output, UINT32_MAX), status);
EXPECT_EQ(mapped_display, display);
EXPECT_EQ(mapped_buffer, 42);
EXPECT_EQ(mapped_output, &output);
}
}
#if !VA_CHECK_VERSION(1, 9, 0)
TEST(VaapiCompatibilityTest, SyncBufferReportsUnsupportedOperation) {
EXPECT_EQ(sunshine_test_vaSyncBuffer(nullptr, VA_INVALID_ID, 0), VA_STATUS_ERROR_UNIMPLEMENTED);
}
#endif
#endif
#endif