obs-studio/plugins/obs-qsv11/common_utils_linux.cpp
Kurt Kartaltepe 368082d6c0 obs-qsv11: Use device enumeration for choosing DRM device
Trying to use the display server as the QSV device was found to be
generally wrong in beta, so instead lets save defaults from the earlier
device enumeration similar to obs-ffmpeg-vaapi which is known to work
well.
2023-08-26 16:40:46 -07:00

360 lines
8.9 KiB
C++

#include "common_utils.h"
#include <time.h>
#include <cpuid.h>
#include <util/c99defs.h>
#include <util/dstr.h>
#include <va/va_drm.h>
#include <va/va_str.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <dirent.h>
#include <obs.h>
#include <obs-nix-platform.h>
// Set during check_adapters to work-around VPL dispatcher not setting a VADisplay
// for the MSDK runtime.
static const char *default_h264_device = nullptr;
static const char *default_hevc_device = nullptr;
static const char *default_av1_device = nullptr;
mfxStatus simple_alloc(mfxHDL pthis, mfxFrameAllocRequest *request,
mfxFrameAllocResponse *response)
{
UNUSED_PARAMETER(pthis);
UNUSED_PARAMETER(request);
UNUSED_PARAMETER(response);
return MFX_ERR_UNSUPPORTED;
}
mfxStatus simple_lock(mfxHDL pthis, mfxMemId mid, mfxFrameData *ptr)
{
UNUSED_PARAMETER(pthis);
UNUSED_PARAMETER(mid);
UNUSED_PARAMETER(ptr);
return MFX_ERR_UNSUPPORTED;
}
mfxStatus simple_unlock(mfxHDL pthis, mfxMemId mid, mfxFrameData *ptr)
{
UNUSED_PARAMETER(pthis);
UNUSED_PARAMETER(mid);
UNUSED_PARAMETER(ptr);
return MFX_ERR_UNSUPPORTED;
}
mfxStatus simple_gethdl(mfxHDL pthis, mfxMemId mid, mfxHDL *handle)
{
UNUSED_PARAMETER(pthis);
UNUSED_PARAMETER(mid);
UNUSED_PARAMETER(handle);
return MFX_ERR_UNSUPPORTED;
}
mfxStatus simple_free(mfxHDL pthis, mfxFrameAllocResponse *response)
{
UNUSED_PARAMETER(pthis);
UNUSED_PARAMETER(response);
return MFX_ERR_UNSUPPORTED;
}
mfxStatus simple_copytex(mfxHDL pthis, mfxMemId mid, mfxU32 tex_handle,
mfxU64 lock_key, mfxU64 *next_key)
{
UNUSED_PARAMETER(pthis);
UNUSED_PARAMETER(mid);
UNUSED_PARAMETER(tex_handle);
UNUSED_PARAMETER(lock_key);
UNUSED_PARAMETER(next_key);
return MFX_ERR_UNSUPPORTED;
}
#if 0
void ClearYUVSurfaceVMem(mfxMemId memId);
void ClearRGBSurfaceVMem(mfxMemId memId);
#endif
// Initialize Intel VPL Session, device/display and memory manager
mfxStatus Initialize(mfxVersion ver, mfxSession *pSession,
mfxFrameAllocator *pmfxAllocator, mfxHDL *deviceHandle,
bool bCreateSharedHandles, bool dx9hack,
enum qsv_codec codec)
{
UNUSED_PARAMETER(ver);
UNUSED_PARAMETER(pmfxAllocator);
UNUSED_PARAMETER(deviceHandle);
UNUSED_PARAMETER(bCreateSharedHandles);
UNUSED_PARAMETER(dx9hack);
mfxStatus sts = MFX_ERR_NONE;
mfxVariant impl;
// Initialize Intel VPL Session
mfxLoader loader = MFXLoad();
mfxConfig cfg = MFXCreateConfig(loader);
impl.Type = MFX_VARIANT_TYPE_U32;
impl.Data.U32 = MFX_IMPL_TYPE_HARDWARE;
MFXSetConfigFilterProperty(
cfg, (const mfxU8 *)"mfxImplDescription.Impl", impl);
impl.Type = MFX_VARIANT_TYPE_U32;
impl.Data.U32 = INTEL_VENDOR_ID;
MFXSetConfigFilterProperty(
cfg, (const mfxU8 *)"mfxImplDescription.VendorID", impl);
impl.Type = MFX_VARIANT_TYPE_U32;
impl.Data.U32 = MFX_ACCEL_MODE_VIA_VAAPI_DRM_RENDER_NODE;
MFXSetConfigFilterProperty(
cfg, (const mfxU8 *)"mfxImplDescription.AccelerationMode",
impl);
// We cant cleanup FDs because Release() is only called for gpu texture sharing cases.
int fd = -1;
if (codec == QSV_CODEC_AVC)
fd = open(default_h264_device, O_RDWR);
if (codec == QSV_CODEC_HEVC)
fd = open(default_hevc_device, O_RDWR);
if (codec == QSV_CODEC_AV1)
fd = open(default_av1_device, O_RDWR);
if (fd < 0) {
blog(LOG_ERROR, "Failed to open device '%s'",
default_h264_device);
return MFX_ERR_DEVICE_FAILED;
}
mfxHDL vaDisplay = vaGetDisplayDRM(fd);
if (!vaDisplay) {
return MFX_ERR_DEVICE_FAILED;
}
sts = MFXCreateSession(loader, 0, pSession);
if (MFX_ERR_NONE > sts) {
blog(LOG_ERROR, "Failed to initialize MFX");
MSDK_PRINT_RET_MSG(sts);
close(fd);
return sts;
}
// VPL expects the VADisplay to be initialized.
int major;
int minor;
if (vaInitialize(vaDisplay, &major, &minor) != VA_STATUS_SUCCESS) {
blog(LOG_ERROR, "Failed to initialize VA-API");
close(fd);
vaTerminate(vaDisplay);
return MFX_ERR_DEVICE_FAILED;
}
sts = MFXVideoCORE_SetHandle(*pSession, MFX_HANDLE_VA_DISPLAY,
vaDisplay);
MSDK_CHECK_RESULT(sts, MFX_ERR_NONE, sts);
return sts;
}
// Release resources (device/display)
void Release(){};
void mfxGetTime(mfxTime *timestamp)
{
clock_gettime(CLOCK_MONOTONIC, timestamp);
}
double TimeDiffMsec(mfxTime tfinish, mfxTime tstart)
{
UNUSED_PARAMETER(tfinish);
UNUSED_PARAMETER(tstart);
//TODO, unused so far it seems
return 0.0;
}
extern "C" void util_cpuid(int cpuinfo[4], int level)
{
__get_cpuid(level, (unsigned int *)&cpuinfo[0],
(unsigned int *)&cpuinfo[1], (unsigned int *)&cpuinfo[2],
(unsigned int *)&cpuinfo[3]);
}
struct vaapi_device {
int fd;
VADisplay display;
const char *driver;
};
static void vaapi_open(char *device_path, struct vaapi_device *device)
{
int fd = open(device_path, O_RDWR);
if (fd < 0) {
return;
}
VADisplay display = vaGetDisplayDRM(fd);
if (!display) {
close(fd);
return;
}
// VA-API is noisy by default.
vaSetInfoCallback(display, nullptr, nullptr);
vaSetErrorCallback(display, nullptr, nullptr);
int major;
int minor;
if (vaInitialize(display, &major, &minor) != VA_STATUS_SUCCESS) {
vaTerminate(display);
close(fd);
return;
}
const char *driver = vaQueryVendorString(display);
if (strstr(driver, "Intel i965 driver") != nullptr) {
blog(LOG_WARNING,
"Legacy intel-vaapi-driver detected, incompatible with QSV");
vaTerminate(display);
close(fd);
return;
}
device->fd = fd;
device->display = display;
device->driver = driver;
}
static void vaapi_close(struct vaapi_device *device)
{
vaTerminate(device->display);
close(device->fd);
}
static uint32_t vaapi_check_support(VADisplay display, VAProfile profile,
VAEntrypoint entrypoint)
{
bool ret = false;
VAConfigAttrib attrib[1];
attrib->type = VAConfigAttribRateControl;
VAStatus va_status =
vaGetConfigAttributes(display, profile, entrypoint, attrib, 1);
uint32_t rc = 0;
switch (va_status) {
case VA_STATUS_SUCCESS:
rc = attrib->value;
break;
case VA_STATUS_ERROR_UNSUPPORTED_PROFILE:
case VA_STATUS_ERROR_UNSUPPORTED_ENTRYPOINT:
default:
break;
}
return (rc & VA_RC_CBR || rc & VA_RC_CQP || rc & VA_RC_VBR);
}
static bool vaapi_supports_h264(VADisplay display)
{
bool ret = false;
ret |= vaapi_check_support(display, VAProfileH264ConstrainedBaseline,
VAEntrypointEncSlice);
ret |= vaapi_check_support(display, VAProfileH264Main,
VAEntrypointEncSlice);
ret |= vaapi_check_support(display, VAProfileH264High,
VAEntrypointEncSlice);
if (!ret) {
ret |= vaapi_check_support(display,
VAProfileH264ConstrainedBaseline,
VAEntrypointEncSliceLP);
ret |= vaapi_check_support(display, VAProfileH264Main,
VAEntrypointEncSliceLP);
ret |= vaapi_check_support(display, VAProfileH264High,
VAEntrypointEncSliceLP);
}
return ret;
}
static bool vaapi_supports_av1(VADisplay display)
{
bool ret = false;
// Are there any devices with non-LowPower entrypoints?
ret |= vaapi_check_support(display, VAProfileAV1Profile0,
VAEntrypointEncSlice);
ret |= vaapi_check_support(display, VAProfileAV1Profile0,
VAEntrypointEncSliceLP);
return ret;
}
static bool vaapi_supports_hevc(VADisplay display)
{
bool ret = false;
ret |= vaapi_check_support(display, VAProfileHEVCMain,
VAEntrypointEncSlice);
ret |= vaapi_check_support(display, VAProfileHEVCMain,
VAEntrypointEncSliceLP);
return ret;
}
void check_adapters(struct adapter_info *adapters, size_t *adapter_count)
{
struct dstr full_path;
struct dirent **namelist;
int no;
int adapter_idx;
const char *base_dir = "/dev/dri/";
dstr_init(&full_path);
if ((no = scandir(base_dir, &namelist, 0, alphasort)) > 0) {
for (int i = 0; i < no; i++) {
struct adapter_info *adapter;
struct dirent *dp;
struct vaapi_device device = {0};
dp = namelist[i];
if (strstr(dp->d_name, "renderD") == nullptr)
goto next_entry;
adapter_idx = atoi(&dp->d_name[7]) - 128;
if (adapter_idx >= (ssize_t)*adapter_count ||
adapter_idx < 0)
goto next_entry;
*adapter_count = adapter_idx + 1;
dstr_copy(&full_path, base_dir);
dstr_cat(&full_path, dp->d_name);
vaapi_open(full_path.array, &device);
if (!device.display)
goto next_entry;
adapter = &adapters[adapter_idx];
adapter->is_intel = strstr(device.driver, "Intel") !=
nullptr;
// This is currently only used for LowPower coding which is busted on VA-API anyway.
adapter->is_dgpu = false;
adapter->supports_av1 =
vaapi_supports_av1(device.display);
adapter->supports_hevc =
vaapi_supports_hevc(device.display);
if (adapter->is_intel && default_h264_device == nullptr)
default_h264_device = strdup(full_path.array);
if (adapter->supports_av1 &&
default_av1_device == nullptr)
default_av1_device = strdup(full_path.array);
if (adapter->supports_hevc &&
default_hevc_device == nullptr)
default_hevc_device = strdup(full_path.array);
vaapi_close(&device);
next_entry:
free(dp);
}
free(namelist);
}
dstr_free(&full_path);
}