build(linux) make vaapi optional without dlopen (#1979)

Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
This commit is contained in:
James Le Cuirot 2024-01-05 15:59:41 +00:00 committed by GitHub
parent 0dff8b16fd
commit bc6cc2078e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 136 additions and 252 deletions

70
cmake/FindLibva.cmake Normal file
View File

@ -0,0 +1,70 @@
# - Try to find Libva
# This module defines the following variables:
#
# * LIBVA_FOUND - The component was found
# * LIBVA_INCLUDE_DIRS - The component include directory
# * LIBVA_LIBRARIES - The component library Libva
# * LIBVA_DRM_LIBRARIES - The component library Libva DRM
# Use pkg-config to get the directories and then use these values in the
# find_path() and find_library() calls
# cmake-format: on
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)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Libva REQUIRED_VARS LIBVA_INCLUDE_DIR LIBVA_LIB LIBVA_DRM_LIB)
mark_as_advanced(LIBVA_INCLUDE_DIR LIBVA_LIB LIBVA_DRM_LIB)
if(LIBVA_FOUND)
set(LIBVA_INCLUDE_DIRS ${LIBVA_INCLUDE_DIR})
set(LIBVA_LIBRARIES ${LIBVA_LIB})
set(LIBVA_DRM_LIBRARIES ${LIBVA_DRM_LIB})
if(NOT TARGET Libva::va)
if(IS_ABSOLUTE "${LIBVA_LIBRARIES}")
add_library(Libva::va UNKNOWN IMPORTED)
set_target_properties(Libva::va PROPERTIES IMPORTED_LOCATION "${LIBVA_LIBRARIES}")
else()
add_library(Libva::va INTERFACE IMPORTED)
set_target_properties(Libva::va PROPERTIES IMPORTED_LIBNAME "${LIBVA_LIBRARIES}")
endif()
set_target_properties(Libva::va PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${LIBVA_INCLUDE_DIRS}")
endif()
if(NOT TARGET Libva::drm)
if(IS_ABSOLUTE "${LIBVA_DRM_LIBRARIES}")
add_library(Libva::drm UNKNOWN IMPORTED)
set_target_properties(Libva::drm PROPERTIES IMPORTED_LOCATION "${LIBVA_DRM_LIBRARIES}")
else()
add_library(Libva::drm INTERFACE IMPORTED)
set_target_properties(Libva::drm PROPERTIES IMPORTED_LIBNAME "${LIBVA_DRM_LIBRARIES}")
endif()
set_target_properties(Libva::drm PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${LIBVA_INCLUDE_DIRS}")
endif()
endif()

View File

@ -120,6 +120,21 @@ elseif(NOT LIBDRM_FOUND)
message(WARNING "Missing libcap")
endif()
# vaapi
if(${SUNSHINE_ENABLE_VAAPI})
find_package(Libva)
else()
set(LIBVA_FOUND OFF)
endif()
if(LIBVA_FOUND)
add_compile_definitions(SUNSHINE_BUILD_VAAPI)
include_directories(SYSTEM ${LIBVA_INCLUDE_DIR})
list(APPEND PLATFORM_LIBRARIES ${LIBVA_LIBRARIES} ${LIBVA_DRM_LIBRARIES})
list(APPEND PLATFORM_TARGET_FILES
src/platform/linux/vaapi.h
src/platform/linux/vaapi.cpp)
endif()
# wayland
if(${SUNSHINE_ENABLE_WAYLAND})
find_package(Wayland)
@ -167,8 +182,12 @@ if(X11_FOUND)
src/platform/linux/x11grab.cpp)
endif()
if(NOT ${CUDA_FOUND} AND NOT ${WAYLAND_FOUND} AND NOT ${X11_FOUND} AND NOT (${LIBDRM_FOUND} AND ${LIBCAP_FOUND}))
message(FATAL_ERROR "Couldn't find either x11, wayland, cuda or (libdrm and libcap)")
if(NOT ${CUDA_FOUND}
AND NOT ${WAYLAND_FOUND}
AND NOT ${X11_FOUND}
AND NOT (${LIBDRM_FOUND} AND ${LIBCAP_FOUND})
AND NOT ${LIBVA_FOUND})
message(FATAL_ERROR "Couldn't find either cuda, wayland, x11, (libdrm and libcap), or libva")
endif()
# tray icon
@ -206,8 +225,6 @@ endif()
list(APPEND PLATFORM_TARGET_FILES
src/platform/linux/publish.cpp
src/platform/linux/vaapi.h
src/platform/linux/vaapi.cpp
src/platform/linux/graphics.h
src/platform/linux/graphics.cpp
src/platform/linux/misc.h

View File

@ -26,6 +26,8 @@ elseif(UNIX) # Linux
"Enable cuda specific code." ON)
option(SUNSHINE_ENABLE_DRM
"Enable KMS grab if available." ON)
option(SUNSHINE_ENABLE_VAAPI
"Enable building vaapi specific code." ON)
option(SUNSHINE_ENABLE_WAYLAND
"Enable building wayland specific code." ON)
option(SUNSHINE_ENABLE_X11

View File

@ -32,7 +32,7 @@ Install Requirements
libopus-dev \
libpulse-dev \
libssl-dev \
libva-dev \
libva-dev \ # VA-API
libvdpau-dev \
libwayland-dev \ # Wayland
libx11-dev \ # X11
@ -67,7 +67,7 @@ Install Requirements
libdrm-devel \
libevdev-devel \
libnotify-devel \
libva-devel \
libva-devel \ # VA-API
libvdpau-devel \
libX11-devel \ # X11
libxcb-devel \ # X11
@ -115,7 +115,7 @@ Install Requirements
libopus-dev \
libpulse-dev \
libssl-dev \
libva-dev \
libva-dev \ # VA-API
libvdpau-dev \
libwayland-dev \ # Wayland
libx11-dev \ # X11
@ -165,6 +165,7 @@ Install Requirements
libopus-dev \
libpulse-dev \
libssl-dev \
libva-dev \ # VA-API
libwayland-dev \ # Wayland
libx11-dev \ # X11
libxcb-shm0-dev \ # X11

View File

@ -793,9 +793,11 @@ namespace platf {
std::unique_ptr<avcodec_encode_device_t>
make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
#ifdef SUNSHINE_BUILD_VAAPI
if (mem_type == mem_type_e::vaapi) {
return va::make_avcodec_encode_device(width, height, false);
}
#endif
return std::make_unique<avcodec_encode_device_t>();
}
@ -862,9 +864,11 @@ namespace platf {
std::unique_ptr<avcodec_encode_device_t>
make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
#ifdef SUNSHINE_BUILD_VAAPI
if (mem_type == mem_type_e::vaapi) {
return va::make_avcodec_encode_device(width, height, dup(card.render_fd.el), img_offset_x, img_offset_y, true);
}
#endif
BOOST_LOG(error) << "Unsupported pixel format for egl::display_vram_t: "sv << platf::from_pix_fmt(pix_fmt);
return nullptr;
@ -977,7 +981,11 @@ namespace platf {
return -1;
}
#ifdef SUNSHINE_BUILD_VAAPI
if (!va::validate(card.render_fd.el)) {
#else
if (true) {
#endif
BOOST_LOG(warning) << "Monitor "sv << display_name << " doesn't support hardware encoding. Reverting back to GPU -> RAM -> GPU"sv;
return -1;
}

View File

@ -724,7 +724,6 @@ namespace platf {
init() {
// These are allowed to fail.
gbm::init();
va::init();
window_system = window_system_e::NONE;
#ifdef SUNSHINE_BUILD_WAYLAND

View File

@ -10,6 +10,7 @@
extern "C" {
#include <libavcodec/avcodec.h>
#include <va/va.h>
#include <va/va_drm.h>
#if !VA_CHECK_VERSION(1, 9, 0)
// vaSyncBuffer stub allows Sunshine built against libva <2.9.0 to link against ffmpeg on libva 2.9.0 or later
VAStatus
@ -85,209 +86,7 @@ namespace va {
} layers[4];
};
/**
* @brief Defined profiles
*/
enum class profile_e {
// Profile ID used for video processing.
ProfileNone = -1,
MPEG2Simple = 0,
MPEG2Main = 1,
MPEG4Simple = 2,
MPEG4AdvancedSimple = 3,
MPEG4Main = 4,
H264Baseline = 5,
H264Main = 6,
H264High = 7,
VC1Simple = 8,
VC1Main = 9,
VC1Advanced = 10,
H263Baseline = 11,
JPEGBaseline = 12,
H264ConstrainedBaseline = 13,
VP8Version0_3 = 14,
H264MultiviewHigh = 15,
H264StereoHigh = 16,
HEVCMain = 17,
HEVCMain10 = 18,
VP9Profile0 = 19,
VP9Profile1 = 20,
VP9Profile2 = 21,
VP9Profile3 = 22,
HEVCMain12 = 23,
HEVCMain422_10 = 24,
HEVCMain422_12 = 25,
HEVCMain444 = 26,
HEVCMain444_10 = 27,
HEVCMain444_12 = 28,
HEVCSccMain = 29,
HEVCSccMain10 = 30,
HEVCSccMain444 = 31,
AV1Profile0 = 32,
AV1Profile1 = 33,
HEVCSccMain444_10 = 34,
// Profile ID used for protected video playback.
Protected = 35
};
enum class entry_e {
VLD = 1,
IZZ = 2,
IDCT = 3,
MoComp = 4,
Deblocking = 5,
EncSlice = 6, /** slice level encode */
EncPicture = 7, /** picture encode, JPEG, etc */
/**
* For an implementation that supports a low power/high performance variant
* for slice level encode, it can choose to expose the
* VAEntrypointEncSliceLP entrypoint. Certain encoding tools may not be
* available with this entrypoint (e.g. interlace, MBAFF) and the
* application can query the encoding configuration attributes to find
* out more details if this entrypoint is supported.
*/
EncSliceLP = 8,
VideoProc = 10, /**< Video pre/post-processing. */
/**
* @brief FEI
*
* The purpose of FEI (Flexible Encoding Infrastructure) is to allow applications to
* have more controls and trade off quality for speed with their own IPs.
* The application can optionally provide input to ENC for extra encode control
* and get the output from ENC. Application can chose to modify the ENC
* output/PAK input during encoding, but the performance impact is significant.
*
* On top of the existing buffers for normal encode, there will be
* one extra input buffer (VAEncMiscParameterFEIFrameControl) and
* three extra output buffers (VAEncFEIMVBufferType, VAEncFEIMBModeBufferType
* and VAEncFEIDistortionBufferType) for FEI entry function.
* If separate PAK is set, two extra input buffers
* (VAEncFEIMVBufferType, VAEncFEIMBModeBufferType) are needed for PAK input.
*/
FEI = 11,
/**
* @brief Stats
*
* A pre-processing function for getting some statistics and motion vectors is added,
* and some extra controls for Encode pipeline are provided. The application can
* optionally call the statistics function to get motion vectors and statistics like
* variances, distortions before calling Encode function via this entry point.
*
* Checking whether Statistics is supported can be performed with vaQueryConfigEntrypoints().
* If Statistics entry point is supported, then the list of returned entry-points will
* include #Stats. Supported pixel format, maximum resolution and statistics
* specific attributes can be obtained via normal attribute query. One input buffer
* (VAStatsStatisticsParameterBufferType) and one or two output buffers
* (VAStatsStatisticsBufferType, VAStatsStatisticsBottomFieldBufferType (for interlace only)
* and VAStatsMVBufferType) are needed for this entry point.
*/
Stats = 12,
/**
* @brief ProtectedTEEComm
*
* A function for communicating with TEE (Trusted Execution Environment).
*/
ProtectedTEEComm = 13,
/**
* @brief ProtectedContent
*
* A function for protected content to decrypt encrypted content.
*/
ProtectedContent = 14,
};
typedef VAStatus (*queryConfigEntrypoints_fn)(VADisplay dpy, profile_e profile, entry_e *entrypoint_list, int *num_entrypoints);
typedef int (*maxNumEntrypoints_fn)(VADisplay dpy);
typedef VADisplay (*getDisplayDRM_fn)(int fd);
typedef VAStatus (*terminate_fn)(VADisplay dpy);
typedef VAStatus (*initialize_fn)(VADisplay dpy, int *major_version, int *minor_version);
typedef const char *(*errorStr_fn)(VAStatus error_status);
typedef void (*VAMessageCallback)(void *user_context, const char *message);
typedef VAMessageCallback (*setErrorCallback_fn)(VADisplay dpy, VAMessageCallback callback, void *user_context);
typedef VAMessageCallback (*setInfoCallback_fn)(VADisplay dpy, VAMessageCallback callback, void *user_context);
typedef const char *(*queryVendorString_fn)(VADisplay dpy);
typedef VAStatus (*exportSurfaceHandle_fn)(
VADisplay dpy, VASurfaceID surface_id,
uint32_t mem_type, uint32_t flags,
void *descriptor);
static maxNumEntrypoints_fn maxNumEntrypoints;
static queryConfigEntrypoints_fn queryConfigEntrypoints;
static getDisplayDRM_fn getDisplayDRM;
static terminate_fn terminate;
static initialize_fn initialize;
static errorStr_fn errorStr;
static setErrorCallback_fn setErrorCallback;
static setInfoCallback_fn setInfoCallback;
static queryVendorString_fn queryVendorString;
static exportSurfaceHandle_fn exportSurfaceHandle;
using display_t = util::dyn_safe_ptr_v2<void, VAStatus, &terminate>;
int
init_main_va() {
static void *handle { nullptr };
static bool funcs_loaded = false;
if (funcs_loaded) return 0;
if (!handle) {
handle = dyn::handle({ "libva.so.2", "libva.so" });
if (!handle) {
return -1;
}
}
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *) &maxNumEntrypoints, "vaMaxNumEntrypoints" },
{ (dyn::apiproc *) &queryConfigEntrypoints, "vaQueryConfigEntrypoints" },
{ (dyn::apiproc *) &terminate, "vaTerminate" },
{ (dyn::apiproc *) &initialize, "vaInitialize" },
{ (dyn::apiproc *) &errorStr, "vaErrorStr" },
{ (dyn::apiproc *) &setErrorCallback, "vaSetErrorCallback" },
{ (dyn::apiproc *) &setInfoCallback, "vaSetInfoCallback" },
{ (dyn::apiproc *) &queryVendorString, "vaQueryVendorString" },
{ (dyn::apiproc *) &exportSurfaceHandle, "vaExportSurfaceHandle" },
};
if (dyn::load(handle, funcs)) {
return -1;
}
funcs_loaded = true;
return 0;
}
int
init() {
if (init_main_va()) {
return -1;
}
static void *handle { nullptr };
static bool funcs_loaded = false;
if (funcs_loaded) return 0;
if (!handle) {
handle = dyn::handle({ "libva-drm.so.2", "libva-drm.so" });
if (!handle) {
return -1;
}
}
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
{ (dyn::apiproc *) &getDisplayDRM, "vaGetDisplayDRM" },
};
if (dyn::load(handle, funcs)) {
return -1;
}
funcs_loaded = true;
return 0;
}
using display_t = util::safe_ptr_v2<void, VAStatus, vaTerminate>;
int
vaapi_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *encode_device, AVBufferRef **hw_device_buf);
@ -298,9 +97,8 @@ namespace va {
init(int in_width, int in_height, file_t &&render_device) {
file = std::move(render_device);
if (!va::initialize || !gbm::create_device) {
if (!va::initialize) BOOST_LOG(warning) << "libva not initialized"sv;
if (!gbm::create_device) BOOST_LOG(warning) << "libgbm not initialized"sv;
if (!gbm::create_device) {
BOOST_LOG(warning) << "libgbm not initialized"sv;
return -1;
}
@ -346,14 +144,14 @@ namespace va {
va::DRMPRIMESurfaceDescriptor prime;
va::VASurfaceID surface = (std::uintptr_t) frame->data[3];
auto status = va::exportSurfaceHandle(
auto status = vaExportSurfaceHandle(
this->va_display,
surface,
va::SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
va::EXPORT_SURFACE_WRITE_ONLY | va::EXPORT_SURFACE_SEPARATE_LAYERS,
&prime);
if (status) {
BOOST_LOG(error) << "Couldn't export va surface handle: ["sv << (int) surface << "]: "sv << va::errorStr(status);
BOOST_LOG(error) << "Couldn't export va surface handle: ["sv << (int) surface << "]: "sv << vaErrorStr(status);
return -1;
}
@ -524,23 +322,13 @@ namespace va {
auto hwctx = (AVVAAPIDeviceContext *) ctx->hwctx;
auto priv = (VAAPIDevicePriv *) ctx->user_opaque;
terminate(hwctx->display);
vaTerminate(hwctx->display);
close(priv->drm_fd);
av_freep(&priv);
}
int
vaapi_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *base, AVBufferRef **hw_device_buf) {
if (!va::initialize) {
BOOST_LOG(warning) << "libva not loaded"sv;
return -1;
}
if (!va::getDisplayDRM) {
BOOST_LOG(warning) << "libva-drm not loaded"sv;
return -1;
}
auto va = (va::va_t *) base;
auto fd = dup(va->file.el);
@ -552,7 +340,7 @@ namespace va {
av_free(priv);
});
va::display_t display { va::getDisplayDRM(fd) };
va::display_t display { vaGetDisplayDRM(fd) };
if (!display) {
auto render_device = config::video.adapter_name.empty() ? "/dev/dri/renderD128" : config::video.adapter_name.c_str();
@ -562,17 +350,17 @@ namespace va {
va->va_display = display.get();
va::setErrorCallback(display.get(), __log, &error);
va::setErrorCallback(display.get(), __log, &info);
vaSetErrorCallback(display.get(), __log, &error);
vaSetErrorCallback(display.get(), __log, &info);
int major, minor;
auto status = va::initialize(display.get(), &major, &minor);
auto status = vaInitialize(display.get(), &major, &minor);
if (status) {
BOOST_LOG(error) << "Couldn't initialize va display: "sv << va::errorStr(status);
BOOST_LOG(error) << "Couldn't initialize va display: "sv << vaErrorStr(status);
return -1;
}
BOOST_LOG(debug) << "vaapi vendor: "sv << va::queryVendorString(display.get());
BOOST_LOG(debug) << "vaapi vendor: "sv << vaQueryVendorString(display.get());
*hw_device_buf = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_VAAPI);
auto ctx = (AVHWDeviceContext *) (*hw_device_buf)->data;
@ -596,20 +384,20 @@ namespace va {
}
static bool
query(display_t::pointer display, profile_e profile) {
std::vector<entry_e> entrypoints;
entrypoints.resize(maxNumEntrypoints(display));
query(display_t::pointer display, VAProfile profile) {
std::vector<VAEntrypoint> entrypoints;
entrypoints.resize(vaMaxNumEntrypoints(display));
int count;
auto status = queryConfigEntrypoints(display, profile, entrypoints.data(), &count);
auto status = vaQueryConfigEntrypoints(display, profile, entrypoints.data(), &count);
if (status) {
BOOST_LOG(error) << "Couldn't query entrypoints: "sv << va::errorStr(status);
BOOST_LOG(error) << "Couldn't query entrypoints: "sv << vaErrorStr(status);
return false;
}
entrypoints.resize(count);
for (auto entrypoint : entrypoints) {
if (entrypoint == entry_e::EncSlice || entrypoint == entry_e::EncSliceLP) {
if (entrypoint == VAEntrypointEncSlice || entrypoint == VAEntrypointEncSliceLP) {
return true;
}
}
@ -619,11 +407,7 @@ namespace va {
bool
validate(int fd) {
if (init()) {
return false;
}
va::display_t display { va::getDisplayDRM(fd) };
va::display_t display { vaGetDisplayDRM(fd) };
if (!display) {
char string[1024];
@ -636,21 +420,21 @@ namespace va {
}
int major, minor;
auto status = initialize(display.get(), &major, &minor);
auto status = vaInitialize(display.get(), &major, &minor);
if (status) {
BOOST_LOG(error) << "Couldn't initialize va display: "sv << va::errorStr(status);
BOOST_LOG(error) << "Couldn't initialize va display: "sv << vaErrorStr(status);
return false;
}
if (!query(display.get(), profile_e::H264Main)) {
if (!query(display.get(), VAProfileH264Main)) {
return false;
}
if (video::active_hevc_mode > 1 && !query(display.get(), profile_e::HEVCMain)) {
if (video::active_hevc_mode > 1 && !query(display.get(), VAProfileHEVCMain)) {
return false;
}
if (video::active_hevc_mode > 2 && !query(display.get(), profile_e::HEVCMain10)) {
if (video::active_hevc_mode > 2 && !query(display.get(), VAProfileHEVCMain10)) {
return false;
}

View File

@ -28,7 +28,4 @@ namespace va {
// Ensure the render device pointed to by fd is capable of encoding h264 with the hevc_mode configured
bool
validate(int fd);
int
init();
} // namespace va

View File

@ -211,9 +211,11 @@ namespace wl {
std::unique_ptr<platf::avcodec_encode_device_t>
make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override {
#ifdef SUNSHINE_BUILD_VAAPI
if (mem_type == platf::mem_type_e::vaapi) {
return va::make_avcodec_encode_device(width, height, false);
}
#endif
return std::make_unique<platf::avcodec_encode_device_t>();
}
@ -321,9 +323,11 @@ namespace wl {
std::unique_ptr<platf::avcodec_encode_device_t>
make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override {
#ifdef SUNSHINE_BUILD_VAAPI
if (mem_type == platf::mem_type_e::vaapi) {
return va::make_avcodec_encode_device(width, height, 0, 0, true);
}
#endif
return std::make_unique<platf::avcodec_encode_device_t>();
}

View File

@ -555,9 +555,11 @@ namespace platf {
std::unique_ptr<avcodec_encode_device_t>
make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
#ifdef SUNSHINE_BUILD_VAAPI
if (mem_type == mem_type_e::vaapi) {
return va::make_avcodec_encode_device(width, height, false);
}
#endif
#ifdef SUNSHINE_BUILD_CUDA
if (mem_type == mem_type_e::cuda) {