diff options
Diffstat (limited to 'src/detection/codec')
| -rw-r--r-- | src/detection/codec/codec.c | 14 | ||||
| -rw-r--r-- | src/detection/codec/codec.h | 43 | ||||
| -rw-r--r-- | src/detection/codec/codec_android.c | 102 | ||||
| -rw-r--r-- | src/detection/codec/codec_apple.c | 96 | ||||
| -rw-r--r-- | src/detection/codec/codec_linux.c | 470 | ||||
| -rw-r--r-- | src/detection/codec/codec_vulkan.c | 270 | ||||
| -rw-r--r-- | src/detection/codec/codec_windows.cpp | 553 |
7 files changed, 1548 insertions, 0 deletions
diff --git a/src/detection/codec/codec.c b/src/detection/codec/codec.c new file mode 100644 index 0000000..c7b3c45 --- /dev/null +++ b/src/detection/codec/codec.c @@ -0,0 +1,14 @@ +#include "codec.h" + +const char* ffDetectCodec(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/) { + if (options->useVulkan) { +#ifdef FF_HAVE_VULKAN + return ffDetectCodecVulkan(options, result); +#else + FF_UNUSED(options, result); + return "Fastfetch was built without Vulkan support"; +#endif + } + + return ffDetectCodecNative(options, result); +} diff --git a/src/detection/codec/codec.h b/src/detection/codec/codec.h new file mode 100644 index 0000000..d0f48d9 --- /dev/null +++ b/src/detection/codec/codec.h @@ -0,0 +1,43 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/codec/option.h" + +typedef enum FF_A_PACKED FFCodecType { + FF_CODEC_TYPE_NONE = 0, + FF_CODEC_TYPE_UNKNOWN = UINT32_C(1) << 0, + FF_CODEC_TYPE_H261 = UINT32_C(1) << 1, + FF_CODEC_TYPE_H263 = UINT32_C(1) << 2, + FF_CODEC_TYPE_MJPEG = UINT32_C(1) << 3, + FF_CODEC_TYPE_MPEG1 = UINT32_C(1) << 4, + FF_CODEC_TYPE_MPEG2 = UINT32_C(1) << 5, + FF_CODEC_TYPE_DIVX_XVID = UINT32_C(1) << 6, + FF_CODEC_TYPE_H264 = UINT32_C(1) << 7, + FF_CODEC_TYPE_WMV8 = UINT32_C(1) << 8, + FF_CODEC_TYPE_WMV9 = UINT32_C(1) << 9, + FF_CODEC_TYPE_VC1 = UINT32_C(1) << 10, + FF_CODEC_TYPE_VP8 = UINT32_C(1) << 11, + FF_CODEC_TYPE_HEVC = UINT32_C(1) << 12, + FF_CODEC_TYPE_VP9 = UINT32_C(1) << 13, + FF_CODEC_TYPE_AV1 = UINT32_C(1) << 14, + FF_CODEC_TYPE_VVC = UINT32_C(1) << 15, + FF_CODEC_TYPE_DOLBY_VISION_HEVC = UINT32_C(1) << 16, + FF_CODEC_TYPE_PRORES = UINT32_C(1) << 17, + FF_CODEC_TYPE_PRORES_RAW = UINT32_C(1) << 18, + FF_CODEC_TYPE_MAX = FF_CODEC_TYPE_PRORES_RAW, + FF_CODEC_TYPE_FORCE_UNSIGNED = UINT32_MAX, +} FFCodecType; + +typedef struct FFCodecResult { + FFstrbuf gpu; + FFCodecType decoders; + FFCodecType encoders; + const char* platformApi; +} FFCodecResult; + +const char* ffDetectCodec(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/); +const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/); + +#ifdef FF_HAVE_VULKAN +const char* ffDetectCodecVulkan(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/); +#endif diff --git a/src/detection/codec/codec_android.c b/src/detection/codec/codec_android.c new file mode 100644 index 0000000..238dab7 --- /dev/null +++ b/src/detection/codec/codec_android.c @@ -0,0 +1,102 @@ +#include "codec.h" + +#include "common/library.h" +#include "common/strutil.h" +#undef __INTRODUCED_IN +#define __INTRODUCED_IN(...) +#include <media/NdkMediaCodec.h> + +static const struct { + const char* mime; + FFCodecType type; +} FF_CODEC_MIME_TO_TYPE[] = { + { "video/3gpp", FF_CODEC_TYPE_H263 }, + { "video/h263", FF_CODEC_TYPE_H263 }, + { "video/mjpeg", FF_CODEC_TYPE_MJPEG }, + { "video/mpeg2", FF_CODEC_TYPE_MPEG2 }, + { "video/mp2v-es", FF_CODEC_TYPE_MPEG2 }, + { "video/mp4v-es", FF_CODEC_TYPE_DIVX_XVID }, + { "video/avc", FF_CODEC_TYPE_H264 }, + { "video/hevc", FF_CODEC_TYPE_HEVC }, + { "video/x-vnd.on2.vp8", FF_CODEC_TYPE_VP8 }, + { "video/x-vnd.on2.vp9", FF_CODEC_TYPE_VP9 }, + { "video/av01", FF_CODEC_TYPE_AV1 }, + { "video/vvc", FF_CODEC_TYPE_VVC }, +}; + +static bool ffCodecIsLikelySoftware(const char* codecName) { + if (!codecName) { + return true; + } + + return ffStrStartsWith(codecName, "OMX.google.") || + ffStrStartsWith(codecName, "c2.android.") || + ffStrStartsWith(codecName, "OMX.ffmpeg.") || + ffStrStartsWith(codecName, "OMX.PV."); +} + +static bool ffCodecIsHardwareAccelerated( + AMediaCodec* codec, + __typeof__(&AMediaCodec_getName) ffAMediaCodec_getName, + __typeof__(&AMediaCodec_releaseName) ffAMediaCodec_releaseName) { + if (!codec) { + return false; + } + + char* codecName = NULL; + media_status_t status = ffAMediaCodec_getName(codec, &codecName); + if (status != AMEDIA_OK || !codecName) { + return false; + } + + bool isHardware = !ffCodecIsLikelySoftware(codecName); + ffAMediaCodec_releaseName(codec, codecName); + return isHardware; +} + +const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/) { + FF_LIBRARY_LOAD_MESSAGE(mediandk, "libmediandk.so", 0) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mediandk, AMediaCodec_createDecoderByType) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mediandk, AMediaCodec_createEncoderByType) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mediandk, AMediaCodec_delete) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mediandk, AMediaCodec_getName) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mediandk, AMediaCodec_releaseName) + + FFCodecType decoders = FF_CODEC_TYPE_NONE; + FFCodecType encoders = FF_CODEC_TYPE_NONE; + + for (uint32_t i = 0; i < ARRAY_SIZE(FF_CODEC_MIME_TO_TYPE); ++i) { + const char* mime = FF_CODEC_MIME_TO_TYPE[i].mime; + FFCodecType type = FF_CODEC_MIME_TO_TYPE[i].type; + + if ((options->showType & FF_CODEC_SHOW_TYPE_DECODER) && !(decoders & type)) { + AMediaCodec* decoder = ffAMediaCodec_createDecoderByType(mime); + if (decoder) { + if (ffCodecIsHardwareAccelerated(decoder, ffAMediaCodec_getName, ffAMediaCodec_releaseName)) { + decoders |= type; + } + ffAMediaCodec_delete(decoder); + } + } + + if ((options->showType & FF_CODEC_SHOW_TYPE_ENCODER) && !(encoders & type)) { + AMediaCodec* encoder = ffAMediaCodec_createEncoderByType(mime); + if (encoder) { + if (ffCodecIsHardwareAccelerated(encoder, ffAMediaCodec_getName, ffAMediaCodec_releaseName)) { + encoders |= type; + } + ffAMediaCodec_delete(encoder); + } + } + } + + if (decoders != FF_CODEC_TYPE_NONE || encoders != FF_CODEC_TYPE_NONE) { + FFCodecResult* item = FF_LIST_ADD(FFCodecResult, *result); + ffStrbufInitStatic(&item->gpu, "Default"); + item->decoders = decoders; + item->encoders = encoders; + item->platformApi = "AMediaCodec"; + } + + return NULL; +} diff --git a/src/detection/codec/codec_apple.c b/src/detection/codec/codec_apple.c new file mode 100644 index 0000000..3778373 --- /dev/null +++ b/src/detection/codec/codec_apple.c @@ -0,0 +1,96 @@ +#include "codec.h" + +#include <VideoToolbox/VideoToolbox.h> +#include "common/apple/cf_helpers.h" + +static const struct { + CMVideoCodecType codec; + FFCodecType type; +} FF_CODEC_CODECS[] = { + { 'h263', FF_CODEC_TYPE_H263 }, // kCMVideoCodecType_H263 + { 'jpeg', FF_CODEC_TYPE_MJPEG }, // kCMVideoCodecType_JPEG + { 'dmb1', FF_CODEC_TYPE_MJPEG }, // kCMVideoCodecType_JPEG_OpenDML + { 'mp1v', FF_CODEC_TYPE_MPEG1 }, // kCMVideoCodecType_MPEG1Video + { 'mp2v', FF_CODEC_TYPE_MPEG2 }, // kCMVideoCodecType_MPEG2Video + { 'mp4v', FF_CODEC_TYPE_DIVX_XVID }, // kCMVideoCodecType_MPEG4Video + { 'avc1', FF_CODEC_TYPE_H264 }, // kCMVideoCodecType_H264 + { 'hvc1', FF_CODEC_TYPE_HEVC }, // kCMVideoCodecType_HEVC + { 'muxa', FF_CODEC_TYPE_HEVC }, // kCMVideoCodecType_HEVCWithAlpha + { 'dvh1', FF_CODEC_TYPE_DOLBY_VISION_HEVC }, // kCMVideoCodecType_DolbyVisionHEVC + { 'dish', FF_CODEC_TYPE_HEVC }, // kCMVideoCodecType_DisparityHEVC + { 'deph', FF_CODEC_TYPE_HEVC }, // kCMVideoCodecType_DepthHEVC + { 'vp09', FF_CODEC_TYPE_VP9 }, // kCMVideoCodecType_VP9 + { 'av01', FF_CODEC_TYPE_AV1 }, // kCMVideoCodecType_AV1 + { 'ap4x', FF_CODEC_TYPE_PRORES }, // kCMVideoCodecType_AppleProRes4444XQ + { 'ap4h', FF_CODEC_TYPE_PRORES }, // kCMVideoCodecType_AppleProRes4444 + { 'apch', FF_CODEC_TYPE_PRORES }, // kCMVideoCodecType_AppleProRes422HQ + { 'apcn', FF_CODEC_TYPE_PRORES }, // kCMVideoCodecType_AppleProRes422 + { 'apcs', FF_CODEC_TYPE_PRORES }, // kCMVideoCodecType_AppleProRes422LT + { 'apco', FF_CODEC_TYPE_PRORES }, // kCMVideoCodecType_AppleProRes422Proxy + { 'aprn', FF_CODEC_TYPE_PRORES_RAW }, // kCMVideoCodecType_AppleProResRAW + { 'aprh', FF_CODEC_TYPE_PRORES_RAW }, // kCMVideoCodecType_AppleProResRAWHQ +}; + +static FFCodecType ffCodecCodecToType(CMVideoCodecType codec) { + for (uint32_t i = 0; i < ARRAY_SIZE(FF_CODEC_CODECS); ++i) { + if (FF_CODEC_CODECS[i].codec == codec) { + return FF_CODEC_CODECS[i].type; + } + } + return FF_CODEC_TYPE_NONE; +} + +static FFCodecType ffCodecDetectEncoders(void) { + CFArrayRef encoderList = NULL; + if (VTCopyVideoEncoderList(NULL, &encoderList) != noErr || !encoderList) { + return FF_CODEC_TYPE_NONE; + } + + FFCodecType types = FF_CODEC_TYPE_NONE; + for (uint32_t i = 0; i < CFArrayGetCount(encoderList); ++i) { + CFDictionaryRef encoder = CFArrayGetValueAtIndex(encoderList, i); + bool isHardwareAccelerated; + int codec; + if (ffCfDictGetBool(encoder, CFSTR("IsHardwareAccelerated"), &isHardwareAccelerated) != NULL || + !isHardwareAccelerated || + ffCfDictGetInt(encoder, CFSTR("CodecType"), &codec) != NULL) { + continue; + } + types |= ffCodecCodecToType((CMVideoCodecType) codec); + } + + return types; +} + +static FFCodecType ffCodecDetectDecoders() { + FFCodecType types = FF_CODEC_TYPE_NONE; + for (uint32_t i = 0; i < ARRAY_SIZE(FF_CODEC_CODECS); ++i) { + if (types & FF_CODEC_CODECS[i].type) { + continue; + } + + bool supported = VTIsHardwareDecodeSupported(FF_CODEC_CODECS[i].codec); + if (!supported) { + continue; + } + + types |= FF_CODEC_CODECS[i].type; + } + + return types; +} + +const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /* list of FFCodecResult */) { + FFCodecType decoders = options->showType & FF_CODEC_SHOW_TYPE_DECODER ? ffCodecDetectDecoders() : FF_CODEC_TYPE_NONE; + FFCodecType encoders = options->showType & FF_CODEC_SHOW_TYPE_ENCODER ? ffCodecDetectEncoders() : FF_CODEC_TYPE_NONE; + + if (decoders != FF_CODEC_TYPE_NONE || encoders != FF_CODEC_TYPE_NONE) { + FFCodecResult* item = FF_LIST_ADD(FFCodecResult, *result); + ffStrbufInitStatic(&item->gpu, "Default"); + item->decoders = decoders; + item->encoders = encoders; + item->platformApi = "VideoToolbox"; + } + + return NULL; +} diff --git a/src/detection/codec/codec_linux.c b/src/detection/codec/codec_linux.c new file mode 100644 index 0000000..c25614e --- /dev/null +++ b/src/detection/codec/codec_linux.c @@ -0,0 +1,470 @@ +#include "codec.h" + +#if (FF_HAVE_DRM && FF_HAVE_VA) || FF_HAVE_VDPAU + + #include "common/library.h" + #include "common/mallocHelper.h" + #include "common/io.h" + + #if FF_HAVE_DRM && FF_HAVE_VA + #include "detection/gpu/gpu.h" + + #include <fcntl.h> + #include <va/va.h> + #include <va/va_drm.h> + #include <xf86drm.h> + #endif + + #include <string.h> + + #if FF_HAVE_VDPAU + #include <stdlib.h> + #include <vdpau/vdpau.h> + +VdpStatus vdp_device_create_x11(void* display, int screen, VdpDevice* device, VdpGetProcAddress** get_proc_address); +void* XOpenDisplay(const char* display_name); +int XCloseDisplay(void* display); +int XDefaultScreen(void* display); + #endif + + #if FF_HAVE_DRM && FF_HAVE_VA + +static FFCodecType ffCodecProfileToType(VAProfile profile) { + switch (profile) { + case 11: // VAProfileH263Baseline + return FF_CODEC_TYPE_H263; + + case 12: // VAProfileJPEGBaseline + return FF_CODEC_TYPE_MJPEG; + + case 0: // VAProfileMPEG2Simple + case 1: // VAProfileMPEG2Main + return FF_CODEC_TYPE_MPEG2; + + case 2: // VAProfileMPEG4Simple + case 3: // VAProfileMPEG4AdvancedSimple + case 4: // VAProfileMPEG4Main + return FF_CODEC_TYPE_DIVX_XVID; + + case 5: // VAProfileH264Baseline + case 6: // VAProfileH264Main + case 7: // VAProfileH264High + case 13: // VAProfileH264ConstrainedBaseline + case 15: // VAProfileH264MultiviewHigh + case 16: // VAProfileH264StereoHigh + case 36: // VAProfileH264High10 + case 40: // VAProfileH264High422 + return FF_CODEC_TYPE_H264; + + case 8: // VAProfileVC1Simple + case 9: // VAProfileVC1Main + case 10: // VAProfileVC1Advanced + return FF_CODEC_TYPE_VC1; + + case 14: // VAProfileVP8Version0_3 + return FF_CODEC_TYPE_VP8; + + case 17: // VAProfileHEVCMain + case 18: // VAProfileHEVCMain10 + case 23: // VAProfileHEVCMain12 + case 24: // VAProfileHEVCMain422_10 + case 25: // VAProfileHEVCMain422_12 + case 26: // VAProfileHEVCMain444 + case 27: // VAProfileHEVCMain444_10 + case 28: // VAProfileHEVCMain444_12 + case 29: // VAProfileHEVCSccMain + case 30: // VAProfileHEVCSccMain10 + case 31: // VAProfileHEVCSccMain444 + case 34: // VAProfileHEVCSccMain444_10 + return FF_CODEC_TYPE_HEVC; + + case 19: // VAProfileVP9Profile0 + case 20: // VAProfileVP9Profile1 + case 21: // VAProfileVP9Profile2 + case 22: // VAProfileVP9Profile3 + return FF_CODEC_TYPE_VP9; + + case 32: // VAProfileAV1Profile0 + case 33: // VAProfileAV1Profile1 + case 39: // VAProfileAV1Profile2 + return FF_CODEC_TYPE_AV1; + + case 37: // VAProfileVVCMain10 + case 38: // VAProfileVVCMultilayerMain10 + return FF_CODEC_TYPE_VVC; + + default: + return FF_CODEC_TYPE_UNKNOWN; + } +} + +static FFCodecShowType ffCodecGetEntrypointType(VAEntrypoint entrypoint) { + switch (entrypoint) { + case VAEntrypointVLD: + case VAEntrypointIDCT: + case VAEntrypointMoComp: + return FF_CODEC_SHOW_TYPE_DECODER; + case VAEntrypointEncSlice: + case VAEntrypointEncSliceLP: + case VAEntrypointFEI: + return FF_CODEC_SHOW_TYPE_ENCODER; + default: + return FF_CODEC_SHOW_TYPE_NONE; + } +} + +static bool ffCodecProfileHasOutput( + VADisplay display, + __typeof__(vaQueryConfigEntrypoints)* ffvaQueryConfigEntrypoints, + __typeof__(vaGetConfigAttributes)* ffvaGetConfigAttributes, + int maxEntrypoints, + VAEntrypoint* entrypoints, + VAProfile profile, + FFCodecShowType entrypointType) { + int numEntrypoints = maxEntrypoints; + if (ffvaQueryConfigEntrypoints(display, profile, entrypoints, &numEntrypoints) != VA_STATUS_SUCCESS) { + return false; + } + + for (int i = 0; i < numEntrypoints; ++i) { + if (ffCodecGetEntrypointType(entrypoints[i]) != entrypointType) { + continue; + } + + VAConfigAttrib attrib = { + .type = VAConfigAttribRTFormat, + .value = 0, + }; + + if (ffvaGetConfigAttributes(display, profile, entrypoints[i], &attrib, 1) == VA_STATUS_SUCCESS && + attrib.value != VA_ATTRIB_NOT_SUPPORTED && + attrib.value != 0) { + return true; + } + } + + return false; +} + +static void ffCodecFillGpuName(const drmDevice* dev, const char* path, FFstrbuf* name) { + ffStrbufInit(name); + + switch (dev->bustype) { + case DRM_BUS_PCI: { + FFGPUResult gpu = { + .vendor = ffStrbufCreateStatic(ffGPUGetVendorString(dev->deviceinfo.pci->vendor_id)), + .name = ffStrbufCreate(), + }; + + ffGPUFillVendorAndName(0, dev->deviceinfo.pci->vendor_id, dev->deviceinfo.pci->device_id, &gpu); + ffStrbufSetF(name, "%s %s", gpu.vendor.chars, gpu.name.chars); + + ffStrbufDestroy(&gpu.vendor); + ffStrbufDestroy(&gpu.name); + break; + } + case DRM_BUS_PLATFORM: + ffStrbufSetS(name, dev->deviceinfo.platform->compatible[0]); + return; + case DRM_BUS_HOST1X: + ffStrbufSetS(name, dev->deviceinfo.host1x->compatible[0]); + return; + case DRM_BUS_USB: + ffStrbufSetF(name, "0x%04X 0x%04X", dev->deviceinfo.usb->vendor, dev->deviceinfo.usb->product); + return; + default: + ffStrbufSetStatic(name, "Unknown GPU"); + return; + } + + if (!name->length && path && *path) { + const char* base = strrchr(path, '/'); + ffStrbufSetS(name, base ? base + 1 : path); + } + + if (!name->length) { + ffStrbufSetStatic(name, "Unknown GPU"); + } +} + +static const char* ffDetectCodecByVa(FFCodecOptions* options, FFlist* result) { + FF_LIBRARY_LOAD_MESSAGE(libdrm, "libdrm" FF_LIBRARY_EXTENSION, 2) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmGetDevices) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmFreeDevices) + + FF_LIBRARY_LOAD_MESSAGE(libva, "libva" FF_LIBRARY_EXTENSION, 2) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libva, vaInitialize) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libva, vaTerminate) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libva, vaMaxNumProfiles) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libva, vaMaxNumEntrypoints) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libva, vaQueryConfigProfiles) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libva, vaQueryConfigEntrypoints) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libva, vaGetConfigAttributes) + + FF_LIBRARY_LOAD_MESSAGE(libvaDrm, "libva-drm" FF_LIBRARY_EXTENSION, 2) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libvaDrm, vaGetDisplayDRM) + + drmDevicePtr devices[64]; + int numDevices = ffdrmGetDevices(devices, ARRAY_SIZE(devices)); + if (numDevices < 0) { + return "drmGetDevices() failed"; + } + if (numDevices == 0) { + return "No DRM devices found"; + } + + const char* error = "No DRM device could initialize VA-API"; + + for (int i = 0; i < numDevices; ++i) { + drmDevice* dev = devices[i]; + + const char* path = NULL; + if (dev->available_nodes & (1 << DRM_NODE_RENDER)) { + path = dev->nodes[DRM_NODE_RENDER]; + } else if (dev->available_nodes & (1 << DRM_NODE_PRIMARY)) { + path = dev->nodes[DRM_NODE_PRIMARY]; + } else { + continue; + } + + FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC); + if (fd < 0) { + fd = open(path, O_RDONLY | O_CLOEXEC); + } + if (fd < 0) { + continue; + } + + VADisplay display = ffvaGetDisplayDRM(fd); + if (!display) { + continue; + } + + int major = 0, minor = 0; + if (ffvaInitialize(display, &major, &minor) != VA_STATUS_SUCCESS) { + continue; + } + error = NULL; + + int maxProfiles = ffvaMaxNumProfiles(display); + int maxEntrypoints = ffvaMaxNumEntrypoints(display); + if (maxProfiles <= 0 || maxEntrypoints <= 0) { + ffvaTerminate(display); + continue; + } + + FF_AUTO_FREE VAProfile* profiles = (VAProfile*) malloc(sizeof(VAProfile) * (size_t) maxProfiles); + FF_AUTO_FREE VAEntrypoint* entrypoints = (VAEntrypoint*) malloc(sizeof(VAEntrypoint) * (size_t) maxEntrypoints); + + int numProfiles = maxProfiles; + if (ffvaQueryConfigProfiles(display, profiles, &numProfiles) != VA_STATUS_SUCCESS) { + ffvaTerminate(display); + continue; + } + + FFCodecType decoderTypes = FF_CODEC_TYPE_NONE; + FFCodecType encoderTypes = FF_CODEC_TYPE_NONE; + for (int j = 0; j < numProfiles; ++j) { + FFCodecType type = ffCodecProfileToType(profiles[j]); + + bool hasDecoder = (options->showType & FF_CODEC_SHOW_TYPE_DECODER) && + !(decoderTypes & type) && + ffCodecProfileHasOutput( + display, + ffvaQueryConfigEntrypoints, + ffvaGetConfigAttributes, + maxEntrypoints, + entrypoints, + profiles[j], + FF_CODEC_SHOW_TYPE_DECODER); + + bool hasEncoder = (options->showType & FF_CODEC_SHOW_TYPE_ENCODER) && + !(encoderTypes & type) && + ffCodecProfileHasOutput( + display, + ffvaQueryConfigEntrypoints, + ffvaGetConfigAttributes, + maxEntrypoints, + entrypoints, + profiles[j], + FF_CODEC_SHOW_TYPE_ENCODER); + + if (!hasDecoder && !hasEncoder) { + continue; + } + + if (hasDecoder) { + decoderTypes |= type; + } + if (hasEncoder) { + encoderTypes |= type; + } + } + + if (decoderTypes != FF_CODEC_TYPE_NONE || encoderTypes != FF_CODEC_TYPE_NONE) { + FFCodecResult* item = FF_LIST_ADD(FFCodecResult, *result); + ffCodecFillGpuName(dev, path, &item->gpu); + item->decoders = decoderTypes; + item->encoders = encoderTypes; + item->platformApi = "VA-API"; + } + + ffvaTerminate(display); + } + + ffdrmFreeDevices(devices, numDevices); + return error; +} + #endif + + #if FF_HAVE_VDPAU + +static const struct FFCodecVdpauCodec { + VdpDecoderProfile profile; + FFCodecType type; +} FF_CODEC_VDPAU_CODECS[] = { + { 0, FF_CODEC_TYPE_MPEG1 }, // VDP_DECODER_PROFILE_MPEG1 + { 1, FF_CODEC_TYPE_MPEG2 }, // VDP_DECODER_PROFILE_MPEG2_SIMPLE + { 2, FF_CODEC_TYPE_MPEG2 }, // VDP_DECODER_PROFILE_MPEG2_MAIN + { 12, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_MPEG4_PART2_SP + { 13, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_MPEG4_PART2_ASP + { 14, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX4_QMOBILE + { 15, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX4_MOBILE + { 16, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX4_HOME_THEATER + { 17, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX4_HD_1080P + { 18, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX5_QMOBILE + { 19, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX5_MOBILE + { 20, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX5_HOME_THEATER + { 21, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX5_HD_1080P + { 6, FF_CODEC_TYPE_H264 }, // VDP_DECODER_PROFILE_H264_BASELINE + { 7, FF_CODEC_TYPE_H264 }, // VDP_DECODER_PROFILE_H264_MAIN + { 8, FF_CODEC_TYPE_H264 }, // VDP_DECODER_PROFILE_H264_HIGH + { 22, FF_CODEC_TYPE_H264 }, // VDP_DECODER_PROFILE_H264_CONSTRAINED_BASELINE + { 23, FF_CODEC_TYPE_H264 }, // VDP_DECODER_PROFILE_H264_EXTENDED + { 24, FF_CODEC_TYPE_H264 }, // VDP_DECODER_PROFILE_H264_PROGRESSIVE_HIGH + { 25, FF_CODEC_TYPE_H264 }, // VDP_DECODER_PROFILE_H264_CONSTRAINED_HIGH + { 26, FF_CODEC_TYPE_H264 }, // VDP_DECODER_PROFILE_H264_HIGH_444_PREDICTIVE + { 9, FF_CODEC_TYPE_VC1 }, // VDP_DECODER_PROFILE_VC1_SIMPLE + { 10, FF_CODEC_TYPE_VC1 }, // VDP_DECODER_PROFILE_VC1_MAIN + { 11, FF_CODEC_TYPE_VC1 }, // VDP_DECODER_PROFILE_VC1_ADVANCED + { 100, FF_CODEC_TYPE_HEVC }, // VDP_DECODER_PROFILE_HEVC_MAIN + { 101, FF_CODEC_TYPE_HEVC }, // VDP_DECODER_PROFILE_HEVC_MAIN_10 + { 102, FF_CODEC_TYPE_HEVC }, // VDP_DECODER_PROFILE_HEVC_MAIN_STILL + { 103, FF_CODEC_TYPE_HEVC }, // VDP_DECODER_PROFILE_HEVC_MAIN_12 + { 104, FF_CODEC_TYPE_HEVC }, // VDP_DECODER_PROFILE_HEVC_MAIN_444 + { 105, FF_CODEC_TYPE_HEVC }, // VDP_DECODER_PROFILE_HEVC_MAIN_444_10 + { 106, FF_CODEC_TYPE_HEVC }, // VDP_DECODER_PROFILE_HEVC_MAIN_444_12 + { 27, FF_CODEC_TYPE_VP9 }, // VDP_DECODER_PROFILE_VP9_PROFILE_0 + { 28, FF_CODEC_TYPE_VP9 }, // VDP_DECODER_PROFILE_VP9_PROFILE_1 + { 29, FF_CODEC_TYPE_VP9 }, // VDP_DECODER_PROFILE_VP9_PROFILE_2 + { 30, FF_CODEC_TYPE_VP9 }, // VDP_DECODER_PROFILE_VP9_PROFILE_3 + { 107, FF_CODEC_TYPE_AV1 }, // VDP_DECODER_PROFILE_AV1_MAIN + { 108, FF_CODEC_TYPE_AV1 }, // VDP_DECODER_PROFILE_AV1_HIGH + { 109, FF_CODEC_TYPE_AV1 }, // VDP_DECODER_PROFILE_AV1_PROFESSIONAL +}; + +static const char* ffDetectCodecByVdpau(FFCodecOptions* options, FFlist* result) { + if (options->showType == FF_CODEC_SHOW_TYPE_DECODER) { + return "VDPAU only supports decoding"; + } + + FF_LIBRARY_LOAD_MESSAGE(libX11, "libX11" FF_LIBRARY_EXTENSION, 6) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libX11, XOpenDisplay) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libX11, XCloseDisplay) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libX11, XDefaultScreen) + + FF_LIBRARY_LOAD_MESSAGE(libvdpau, "libvdpau" FF_LIBRARY_EXTENSION, 1) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libvdpau, vdp_device_create_x11) + + void* x11Display = ffXOpenDisplay(NULL); + if (!x11Display) { + return "XOpenDisplay() failed"; + } + + VdpDevice device = VDP_INVALID_HANDLE; + VdpGetProcAddress* ffvdp_get_proc_address = NULL; + if (ffvdp_device_create_x11(x11Display, ffXDefaultScreen(x11Display), &device, &ffvdp_get_proc_address) != VDP_STATUS_OK || + device == VDP_INVALID_HANDLE || + ffvdp_get_proc_address == NULL) { + ffXCloseDisplay(x11Display); + return "vdp_device_create_x11() failed"; + } + + VdpDeviceDestroy* ffvdp_device_destroy = NULL; + VdpDecoderQueryCapabilities* ffvdp_decoder_query_capabilities = NULL; + if (ffvdp_get_proc_address(device, VDP_FUNC_ID_DEVICE_DESTROY, (void**) &ffvdp_device_destroy) != VDP_STATUS_OK || + ffvdp_get_proc_address(device, VDP_FUNC_ID_DECODER_QUERY_CAPABILITIES, (void**) &ffvdp_decoder_query_capabilities) != VDP_STATUS_OK || + ffvdp_device_destroy == NULL || + ffvdp_decoder_query_capabilities == NULL) { + if (ffvdp_device_destroy) { + ffvdp_device_destroy(device); + } + ffXCloseDisplay(x11Display); + return "ffvdp_get_proc_address() failed"; + } + + FFCodecType decoderTypes = FF_CODEC_TYPE_NONE; + + for (uint32_t i = 0; i < ARRAY_SIZE(FF_CODEC_VDPAU_CODECS); ++i) { + const struct FFCodecVdpauCodec* codec = &FF_CODEC_VDPAU_CODECS[i]; + if (decoderTypes & codec->type) { + continue; + } + VdpBool isSupported = VDP_FALSE; + uint32_t maxLevel = 0, maxMacroblocks = 0, maxWidth = 0, maxHeight = 0; + if (ffvdp_decoder_query_capabilities(device, codec->profile, &isSupported, &maxLevel, &maxMacroblocks, &maxWidth, &maxHeight) != VDP_STATUS_OK || + !isSupported) { + continue; + } + + decoderTypes |= codec->type; + } + + ffvdp_device_destroy(device); + ffXCloseDisplay(x11Display); + + if (decoderTypes == FF_CODEC_TYPE_NONE) { + return NULL; + } + + FFCodecResult* item = FF_LIST_ADD(FFCodecResult, *result); + ffStrbufInit(&item->gpu); + const char* driver = getenv("VDPAU_DRIVER"); + if (driver && *driver) { + ffStrbufSetS(&item->gpu, driver); + } else { + ffStrbufSetStatic(&item->gpu, "Default"); + } + item->decoders = decoderTypes; + item->encoders = FF_CODEC_TYPE_NONE; + item->platformApi = "VDPAU"; + + return NULL; +} + #endif + +const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /* list of FFCodecResult */) { + FF_SUPPRESS_IO(); + + #if FF_HAVE_DRM && FF_HAVE_VA + if (ffDetectCodecByVa(options, result) == NULL) { + return NULL; + } + #endif + #if FF_HAVE_VDPAU + if (ffDetectCodecByVdpau(options, result) == NULL) { + return NULL; + } + #endif + + return "Both libva and libvdpau fail to initialize"; +} + +#else + +const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /* list of FFCodecResult */) { + FF_UNUSED(options, result); + return "Fastfetch was built without DRM / VA-API / VDPAU headers"; +} + +#endif diff --git a/src/detection/codec/codec_vulkan.c b/src/detection/codec/codec_vulkan.c new file mode 100644 index 0000000..eabfe26 --- /dev/null +++ b/src/detection/codec/codec_vulkan.c @@ -0,0 +1,270 @@ +#include "codec.h" + +#ifdef FF_HAVE_VULKAN + #include <vulkan/vulkan.h> + #include "common/strutil.h" + + #if VK_KHR_video_decode_queue && VK_KHR_video_encode_queue + + #include "common/debug.h" + #include "common/io.h" + #include "common/library.h" + #include "common/mallocHelper.h" + +static FFCodecType ffCodecDecodeOperationsToTypes(VkVideoCodecOperationFlagsKHR operations) { + FFCodecType types = FF_CODEC_TYPE_NONE; + + if (operations & 0x00000001 /*VK_VIDEO_CODEC_OPERATION_DECODE_H264_BIT_KHR*/) { + types |= FF_CODEC_TYPE_H264; + } + if (operations & 0x00000002 /*VK_VIDEO_CODEC_OPERATION_DECODE_H265_BIT_KHR*/) { + types |= FF_CODEC_TYPE_HEVC; + } + if (operations & 0x00000004 /*VK_VIDEO_CODEC_OPERATION_DECODE_AV1_BIT_KHR*/) { + types |= FF_CODEC_TYPE_AV1; + } + if (operations & 0x00000008 /*VK_VIDEO_CODEC_OPERATION_DECODE_VP9_BIT_KHR*/) { + types |= FF_CODEC_TYPE_VP9; + } + + return types; +} + +static FFCodecType ffCodecEncodeOperationsToTypes(VkVideoCodecOperationFlagsKHR operations) { + FFCodecType types = FF_CODEC_TYPE_NONE; + + if (operations & 0x00010000 /*VK_VIDEO_CODEC_OPERATION_ENCODE_H264_BIT_KHR*/) { + types |= FF_CODEC_TYPE_H264; + } + if (operations & 0x00020000 /*VK_VIDEO_CODEC_OPERATION_ENCODE_H265_BIT_KHR*/) { + types |= FF_CODEC_TYPE_HEVC; + } + if (operations & 0x00040000 /*VK_VIDEO_CODEC_OPERATION_ENCODE_AV1_BIT_KHR*/) { + types |= FF_CODEC_TYPE_AV1; + } + + return types; +} + +static bool ffCodecHasDeviceExtension(const VkExtensionProperties* extensions, uint32_t extensionCount, const char* extensionName) { + for (uint32_t i = 0; i < extensionCount; ++i) { + if (ffStrEquals(extensions[i].extensionName, extensionName)) { + return true; + } + } + + return false; +} + +const char* ffDetectCodecVulkan(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/) { + FF_DEBUG("Starting Vulkan codec detection"); + + FF_LIBRARY_LOAD_MESSAGE(vulkan, + #if __APPLE__ + "libMoltenVK" FF_LIBRARY_EXTENSION, + -1 + #elif _WIN32 + "vulkan-1" FF_LIBRARY_EXTENSION, + -1 + #else + "libvulkan" FF_LIBRARY_EXTENSION, + 2 + #endif + ) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkGetInstanceProcAddr) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkCreateInstance) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkDestroyInstance) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkEnumeratePhysicalDevices) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkEnumerateDeviceExtensionProperties) + + FF_SUPPRESS_IO(); + + uint32_t apiVersion = VK_API_VERSION_1_0; + PFN_vkEnumerateInstanceVersion ffvkEnumerateInstanceVersion = (PFN_vkEnumerateInstanceVersion) ffvkGetInstanceProcAddr(NULL, "vkEnumerateInstanceVersion"); + if (ffvkEnumerateInstanceVersion != NULL) { + uint32_t detectedApiVersion = 0; + if (ffvkEnumerateInstanceVersion(&detectedApiVersion) == VK_SUCCESS) { + apiVersion = detectedApiVersion; + } + } + + const uint32_t projectVersion = VK_MAKE_VERSION( + FASTFETCH_PROJECT_VERSION_MAJOR, + FASTFETCH_PROJECT_VERSION_MINOR, + FASTFETCH_PROJECT_VERSION_PATCH); + + VkInstance vkInstance = VK_NULL_HANDLE; + VkResult res = ffvkCreateInstance(&(VkInstanceCreateInfo){ + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &(VkApplicationInfo){ + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = FASTFETCH_PROJECT_NAME, + .applicationVersion = projectVersion, + .pEngineName = "fastfetch-codec-vulkan", + .engineVersion = projectVersion, + .apiVersion = apiVersion, + }, + }, + NULL, + &vkInstance); + if (res != VK_SUCCESS) { + FF_DEBUG("ffvkCreateInstance() failed with VkResult=%d", res); + switch (res) { + case VK_ERROR_OUT_OF_HOST_MEMORY: + return "ffvkCreateInstance() failed: VK_ERROR_OUT_OF_HOST_MEMORY"; + case VK_ERROR_OUT_OF_DEVICE_MEMORY: + return "ffvkCreateInstance() failed: VK_ERROR_OUT_OF_DEVICE_MEMORY"; + case VK_ERROR_INITIALIZATION_FAILED: + return "ffvkCreateInstance() failed: VK_ERROR_INITIALIZATION_FAILED"; + case VK_ERROR_LAYER_NOT_PRESENT: + return "ffvkCreateInstance() failed: VK_ERROR_LAYER_NOT_PRESENT"; + case VK_ERROR_EXTENSION_NOT_PRESENT: + return "ffvkCreateInstance() failed: VK_ERROR_EXTENSION_NOT_PRESENT"; + case VK_ERROR_INCOMPATIBLE_DRIVER: + return "ffvkCreateInstance() failed: VK_ERROR_INCOMPATIBLE_DRIVER"; + default: + return "ffvkCreateInstance() failed: unknown error"; + } + } + + PFN_vkGetPhysicalDeviceProperties ffvkGetPhysicalDeviceProperties = (PFN_vkGetPhysicalDeviceProperties) ffvkGetInstanceProcAddr(vkInstance, "vkGetPhysicalDeviceProperties"); + if (!ffvkGetPhysicalDeviceProperties) { + ffvkDestroyInstance(vkInstance, NULL); + return "vkGetPhysicalDeviceProperties is not available"; + } + + PFN_vkGetPhysicalDeviceQueueFamilyProperties2 ffvkGetPhysicalDeviceQueueFamilyProperties2 = (PFN_vkGetPhysicalDeviceQueueFamilyProperties2) ffvkGetInstanceProcAddr(vkInstance, "vkGetPhysicalDeviceQueueFamilyProperties2"); + if (!ffvkGetPhysicalDeviceQueueFamilyProperties2) { + ffvkDestroyInstance(vkInstance, NULL); + return "vkGetPhysicalDeviceQueueFamilyProperties2 is not available"; + } + + uint32_t physicalDeviceCount = 0; + res = ffvkEnumeratePhysicalDevices(vkInstance, &physicalDeviceCount, NULL); + if (res != VK_SUCCESS) { + FF_DEBUG("ffvkEnumeratePhysicalDevices(count) failed with VkResult=%d", res); + ffvkDestroyInstance(vkInstance, NULL); + return "ffvkEnumeratePhysicalDevices() failed during Vulkan codec detection"; + } + if (physicalDeviceCount == 0) { + ffvkDestroyInstance(vkInstance, NULL); + return "No Vulkan physical devices found"; + } + + FF_AUTO_FREE VkPhysicalDevice* physicalDevices = (VkPhysicalDevice*) malloc(sizeof(VkPhysicalDevice) * (size_t) physicalDeviceCount); + + res = ffvkEnumeratePhysicalDevices(vkInstance, &physicalDeviceCount, physicalDevices); + if (res != VK_SUCCESS) { + FF_DEBUG("ffvkEnumeratePhysicalDevices(list) failed with VkResult=%d", res); + ffvkDestroyInstance(vkInstance, NULL); + return "ffvkEnumeratePhysicalDevices() failed during Vulkan codec detection"; + } + + bool sawVideoQueueExtension = false; + + for (uint32_t i = 0; i < physicalDeviceCount; ++i) { + VkPhysicalDeviceProperties properties = {}; + ffvkGetPhysicalDeviceProperties(physicalDevices[i], &properties); + + uint32_t extensionCount = 0; + res = ffvkEnumerateDeviceExtensionProperties(physicalDevices[i], NULL, &extensionCount, NULL); + if (res != VK_SUCCESS) { + FF_DEBUG("vkEnumerateDeviceExtensionProperties(count) failed for '%s' with VkResult=%d", properties.deviceName, res); + continue; + } + + if (extensionCount == 0) { + FF_DEBUG("Skipping Vulkan device '%s' because it has no extensions", properties.deviceName); + continue; + } + + FF_AUTO_FREE VkExtensionProperties* extensions = (VkExtensionProperties*) malloc(sizeof(VkExtensionProperties) * (size_t) extensionCount); + + res = ffvkEnumerateDeviceExtensionProperties(physicalDevices[i], NULL, &extensionCount, extensions); + if (res != VK_SUCCESS) { + FF_DEBUG("vkEnumerateDeviceExtensionProperties(list) failed for '%s' with VkResult=%d", properties.deviceName, res); + continue; + } + + bool hasVideoDecode = (options->showType & FF_CODEC_SHOW_TYPE_DECODER) && + ffCodecHasDeviceExtension(extensions, extensionCount, VK_KHR_VIDEO_DECODE_QUEUE_EXTENSION_NAME); + bool hasVideoEncode = (options->showType & FF_CODEC_SHOW_TYPE_ENCODER) && + ffCodecHasDeviceExtension(extensions, extensionCount, VK_KHR_VIDEO_ENCODE_QUEUE_EXTENSION_NAME); + + if (!hasVideoDecode && !hasVideoEncode) { + FF_DEBUG("Skipping Vulkan device '%s' because it does not support video queue extensions", properties.deviceName); + continue; + } + sawVideoQueueExtension = true; + + uint32_t queueFamilyCount = 0; + ffvkGetPhysicalDeviceQueueFamilyProperties2(physicalDevices[i], &queueFamilyCount, NULL); + if (queueFamilyCount == 0) { + FF_DEBUG("Skipping Vulkan device '%s' because it has no queue families", properties.deviceName); + continue; + } + + FF_AUTO_FREE VkQueueFamilyProperties2* queueFamilyProperties = (VkQueueFamilyProperties2*) malloc(sizeof(VkQueueFamilyProperties2) * (size_t) queueFamilyCount); + FF_AUTO_FREE VkQueueFamilyVideoPropertiesKHR* queueFamilyVideoProperties = (VkQueueFamilyVideoPropertiesKHR*) malloc(sizeof(VkQueueFamilyVideoPropertiesKHR) * (size_t) queueFamilyCount); + + for (uint32_t queueIndex = 0; queueIndex < queueFamilyCount; ++queueIndex) { + queueFamilyVideoProperties[queueIndex] = (VkQueueFamilyVideoPropertiesKHR){ + .sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_VIDEO_PROPERTIES_KHR, + }; + queueFamilyProperties[queueIndex] = (VkQueueFamilyProperties2){ + .sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2, + .pNext = &queueFamilyVideoProperties[queueIndex], + }; + } + + ffvkGetPhysicalDeviceQueueFamilyProperties2(physicalDevices[i], &queueFamilyCount, queueFamilyProperties); + + FFCodecType decoders = FF_CODEC_TYPE_NONE; + FFCodecType encoders = FF_CODEC_TYPE_NONE; + + for (uint32_t queueIndex = 0; queueIndex < queueFamilyCount; ++queueIndex) { + const VkQueueFlags queueFlags = queueFamilyProperties[queueIndex].queueFamilyProperties.queueFlags; + const VkVideoCodecOperationFlagsKHR operations = queueFamilyVideoProperties[queueIndex].videoCodecOperations; + + if (hasVideoDecode && (queueFlags & VK_QUEUE_VIDEO_DECODE_BIT_KHR)) { + decoders |= ffCodecDecodeOperationsToTypes(operations); + } + + if (hasVideoEncode && (queueFlags & VK_QUEUE_VIDEO_ENCODE_BIT_KHR)) { + encoders |= ffCodecEncodeOperationsToTypes(operations); + } + } + + if (decoders == FF_CODEC_TYPE_NONE && encoders == FF_CODEC_TYPE_NONE) { + FF_DEBUG("Skipping Vulkan device '%s' because no supported codec operations were reported", properties.deviceName); + continue; + } + + FFCodecResult* item = FF_LIST_ADD(FFCodecResult, *result); + ffStrbufInitS(&item->gpu, properties.deviceName); + item->decoders = decoders; + item->encoders = encoders; + item->platformApi = "Vulkan Video"; + FF_DEBUG("Added Vulkan codec result for '%s': decoders=%u encoders=%u", properties.deviceName, (unsigned) decoders, (unsigned) encoders); + } + + ffvkDestroyInstance(vkInstance, NULL); + + if (result->length > 0) { + return NULL; + } + + return sawVideoQueueExtension ? "No supported Vulkan video codec operations found" + : "VK_KHR_video_queue is not supported by any Vulkan physical device"; +} + + #else + +const char* ffDetectCodecVulkan(FFCodecOptions* options, FFlist* result) { + FF_UNUSED(options, result); + return "Vulkan video queue extensions are not supported by this Vulkan implementation"; +} + + #endif + +#endif diff --git a/src/detection/codec/codec_windows.cpp b/src/detection/codec/codec_windows.cpp new file mode 100644 index 0000000..df01a63 --- /dev/null +++ b/src/detection/codec/codec_windows.cpp @@ -0,0 +1,553 @@ +extern "C" { +#include "codec.h" +#include "common/library.h" +#include "common/windows/com.h" +#include "common/windows/unicode.h" +#include "common/windows/nt.h" +} + +#include <d3d11.h> +#include <d3d12.h> +#include <dxgi.h> +#include <initguid.h> +#include <dxva.h> +#include <d3d12video.h> +#include <mfapi.h> + +typedef struct D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC { + UINT NodeIndex; + D3D12_VIDEO_ENCODER_CODEC Codec; + BOOL IsSupported; +} D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC; + +HRESULT MFTEnum2( + _In_ GUID guidCategory, + _In_ UINT32 Flags, + _In_ const MFT_REGISTER_TYPE_INFO* pInputType, + _In_ const MFT_REGISTER_TYPE_INFO* pOutputType, + _In_opt_ IMFAttributes* pAttributes, + _Out_ IMFActivate*** pppMFTActivate, + _Out_ UINT32* pnumMFTActivate); + +// clang-format off +#ifndef MFT_ENUM_ADAPTER_LUID +// {1D39518C-E220-4DA8-A07F-BA172552D6B1} +DEFINE_GUID(MFT_ENUM_ADAPTER_LUID, + 0x1d39518c, 0xe220, 0x4da8, 0xa0, 0x7f, 0xba, 0x17, 0x25, 0x52, 0xd6, 0xb1); +#endif +// clang-format on + +static const DXGI_FORMAT FF_NATIVE_CODEC_FORMATS[] = { + DXGI_FORMAT_420_OPAQUE, + DXGI_FORMAT_NV12, + DXGI_FORMAT_P010, + DXGI_FORMAT_P016, + DXGI_FORMAT_YUY2, + DXGI_FORMAT_Y210, + DXGI_FORMAT_Y216, + DXGI_FORMAT_AYUV, + DXGI_FORMAT_Y410, + DXGI_FORMAT_Y416, +}; + +static FFCodecType ffCodecProfileToTypeDx11(const GUID& profile) { + // clang-format off + if (IsEqualGUID(profile, DXVA_ModeH261_A) || + IsEqualGUID(profile, DXVA_ModeH261_B)) { + return FF_CODEC_TYPE_H261; + } + + if (IsEqualGUID(profile, DXVA_ModeH263_A) || + IsEqualGUID(profile, DXVA_ModeH263_B) || + IsEqualGUID(profile, DXVA_ModeH263_C) || + IsEqualGUID(profile, DXVA_ModeH263_D) || + IsEqualGUID(profile, DXVA_ModeH263_E) || + IsEqualGUID(profile, DXVA_ModeH263_F)) { + return FF_CODEC_TYPE_H263; + } + + if (IsEqualGUID(profile, DXVA_ModeMJPEG_VLD_420) || + IsEqualGUID(profile, DXVA_ModeMJPEG_VLD_422) || + IsEqualGUID(profile, DXVA_ModeMJPEG_VLD_444) || + IsEqualGUID(profile, DXVA_ModeMJPEG_VLD_4444) || + IsEqualGUID(profile, DXVA_ModeJPEG_VLD_420) || + IsEqualGUID(profile, DXVA_ModeJPEG_VLD_422) || + IsEqualGUID(profile, DXVA_ModeJPEG_VLD_444)) { + return FF_CODEC_TYPE_MJPEG; + } + + if (IsEqualGUID(profile, DXVA_ModeMPEG1_A) || + IsEqualGUID(profile, DXVA_ModeMPEG1_VLD)) { + return FF_CODEC_TYPE_MPEG1; + } + + if (IsEqualGUID(profile, DXVA_ModeMPEG2_A) || + IsEqualGUID(profile, DXVA_ModeMPEG2_B) || + IsEqualGUID(profile, DXVA_ModeMPEG2_C) || + IsEqualGUID(profile, DXVA_ModeMPEG2_D) || + IsEqualGUID(profile, DXVA_ModeMPEG2and1_VLD)) { + return FF_CODEC_TYPE_MPEG2; + } + + if (IsEqualGUID(profile, DXVA_ModeMPEG4pt2_VLD_Simple) || + IsEqualGUID(profile, DXVA_ModeMPEG4pt2_VLD_AdvSimple_NoGMC) || + IsEqualGUID(profile, DXVA_ModeMPEG4pt2_VLD_AdvSimple_GMC)) { + return FF_CODEC_TYPE_DIVX_XVID; + } + + if (IsEqualGUID(profile, DXVA_ModeH264_E) || + IsEqualGUID(profile, DXVA_ModeH264_F) || + IsEqualGUID(profile, DXVA_ModeH264_VLD_WithFMOASO_NoFGT) || + IsEqualGUID(profile, DXVA_ModeH264_VLD_Stereo_Progressive_NoFGT) || + IsEqualGUID(profile, DXVA_ModeH264_VLD_Stereo_NoFGT) || + IsEqualGUID(profile, DXVA_ModeH264_VLD_Multiview_NoFGT)) { + return FF_CODEC_TYPE_H264; + } + + if (IsEqualGUID(profile, DXVA_ModeWMV8_A) || + IsEqualGUID(profile, DXVA_ModeWMV8_B)) { + return FF_CODEC_TYPE_WMV8; + } + + if (IsEqualGUID(profile, DXVA_ModeWMV9_A) || + IsEqualGUID(profile, DXVA_ModeWMV9_B) || + IsEqualGUID(profile, DXVA_ModeWMV9_C)) { + return FF_CODEC_TYPE_WMV9; + } + + if (IsEqualGUID(profile, DXVA_ModeVC1_A) || + IsEqualGUID(profile, DXVA_ModeVC1_B) || + IsEqualGUID(profile, DXVA_ModeVC1_C) || + IsEqualGUID(profile, DXVA_ModeVC1_D) || + IsEqualGUID(profile, DXVA_ModeVC1_D2010)) { + return FF_CODEC_TYPE_VC1; + } + + if (IsEqualGUID(profile, DXVA_ModeVP8_VLD)) { + return FF_CODEC_TYPE_VP8; + } + + if (IsEqualGUID(profile, DXVA_ModeHEVC_VLD_Main) || + IsEqualGUID(profile, DXVA_ModeHEVC_VLD_Main10) || + IsEqualGUID(profile, DXVA_ModeHEVC_VLD_Main12) || + IsEqualGUID(profile, DXVA_ModeHEVC_VLD_Main10_422) || + IsEqualGUID(profile, DXVA_ModeHEVC_VLD_Main12_422) || + IsEqualGUID(profile, DXVA_ModeHEVC_VLD_Main_444) || + IsEqualGUID(profile, DXVA_ModeHEVC_VLD_Main10_Ext) || + IsEqualGUID(profile, DXVA_ModeHEVC_VLD_Main10_444) || + IsEqualGUID(profile, DXVA_ModeHEVC_VLD_Main12_444) || + IsEqualGUID(profile, DXVA_ModeHEVC_VLD_Main16)) { + return FF_CODEC_TYPE_HEVC; + } + + if (IsEqualGUID(profile, DXVA_ModeVP9_VLD_Profile0) || + IsEqualGUID(profile, DXVA_ModeVP9_VLD_10bit_Profile2)) { + return FF_CODEC_TYPE_VP9; + } + + if (IsEqualGUID(profile, DXVA_ModeAV1_VLD_Profile0) || + IsEqualGUID(profile, DXVA_ModeAV1_VLD_Profile1) || + IsEqualGUID(profile, DXVA_ModeAV1_VLD_Profile2) || + IsEqualGUID(profile, DXVA_ModeAV1_VLD_12bit_Profile2) || + IsEqualGUID(profile, DXVA_ModeAV1_VLD_12bit_Profile2_420)) { + return FF_CODEC_TYPE_AV1; + } + // clang-format on + + return FF_CODEC_TYPE_UNKNOWN; +} + +static FFCodecType ffCodecProfileToTypeDx12(const GUID& profile) { + // clang-format off + if (IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_MPEG1_AND_MPEG2)) { + return FFCodecType(FF_CODEC_TYPE_MPEG1 | FF_CODEC_TYPE_MPEG2); + } + + if (IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_MPEG2)) { + return FF_CODEC_TYPE_MPEG2; + } + + if (IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_MPEG4PT2_SIMPLE) || + IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_MPEG4PT2_ADVSIMPLE_NOGMC)) { + return FF_CODEC_TYPE_DIVX_XVID; + } + + if (IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_H264) || + IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_H264_STEREO_PROGRESSIVE) || + IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_H264_STEREO) || + IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_H264_MULTIVIEW)) { + return FF_CODEC_TYPE_H264; + } + + if (IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_VC1) || + IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_VC1_D2010)) { + return FF_CODEC_TYPE_VC1; + } + + if (IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_VP8)) { + return FF_CODEC_TYPE_VP8; + } + + if (IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN) || + IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN10)) { + return FF_CODEC_TYPE_HEVC; + } + + if (IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_VP9) || + IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_VP9_10BIT_PROFILE2)) { + return FF_CODEC_TYPE_VP9; + } + + if (IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE0) || + IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE1) || + IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE2) || + IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_AV1_12BIT_PROFILE2) || + IsEqualGUID(profile, D3D12_VIDEO_DECODE_PROFILE_AV1_12BIT_PROFILE2_420)) { + return FF_CODEC_TYPE_AV1; + } + // clang-format on + + return FF_CODEC_TYPE_UNKNOWN; +} + +static bool ffCodecProfileHasNativeOutput(ID3D11VideoDevice* videoDevice, const GUID& profile) { + for (uint32_t i = 0; i < ARRAY_SIZE(FF_NATIVE_CODEC_FORMATS); ++i) { + BOOL supported = FALSE; + if (SUCCEEDED(videoDevice->CheckVideoDecoderFormat(&profile, FF_NATIVE_CODEC_FORMATS[i], &supported)) && supported) { + return true; + } + } + + return false; +} + +static bool ffCodecProfileHasNativeOutput(ID3D12VideoDevice* videoDevice, const GUID& profile, UINT nodeIndex) { + for (uint32_t i = 0; i < ARRAY_SIZE(FF_NATIVE_CODEC_FORMATS); ++i) { + D3D12_FEATURE_DATA_VIDEO_DECODE_SUPPORT support = { + .NodeIndex = nodeIndex, + .Configuration = { + .DecodeProfile = profile, + .BitstreamEncryption = D3D12_BITSTREAM_ENCRYPTION_TYPE_NONE, + .InterlaceType = D3D12_VIDEO_FRAME_CODED_INTERLACE_TYPE_NONE, + }, + .Width = 1920, + .Height = 1080, + .DecodeFormat = FF_NATIVE_CODEC_FORMATS[i], + .FrameRate = { + .Numerator = 30, + .Denominator = 1, + }, + .BitRate = 10000000, + .SupportFlags = D3D12_VIDEO_DECODE_SUPPORT_FLAG_NONE, + .ConfigurationFlags = D3D12_VIDEO_DECODE_CONFIGURATION_FLAG_NONE, + .DecodeTier = D3D12_VIDEO_DECODE_TIER_NOT_SUPPORTED, + }; + + if (SUCCEEDED(videoDevice->CheckFeatureSupport(D3D12_FEATURE_VIDEO_DECODE_SUPPORT, &support, sizeof(support))) && + (support.SupportFlags & D3D12_VIDEO_DECODE_SUPPORT_FLAG_SUPPORTED)) { + return true; + } + } + + return false; +} + +static bool ffCodecEncoderSupportedD3d12(ID3D12VideoDevice* videoDevice, D3D12_VIDEO_ENCODER_CODEC codec, UINT nodeIndex) { + D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC encoderCodec = { + .NodeIndex = nodeIndex, + .Codec = codec, + .IsSupported = FALSE, + }; + + return SUCCEEDED(videoDevice->CheckFeatureSupport( + D3D12_FEATURE_VIDEO_ENCODER_CODEC, + &encoderCodec, + sizeof(encoderCodec))) && + encoderCodec.IsSupported; +} + +typedef struct FFCodecMftEncoderSubtype { + const GUID* subtype; + FFCodecType codecType; +} FFCodecMftEncoderSubtype; + +static const FFCodecMftEncoderSubtype FF_D3D11VA_MFT_ENCODER_SUBTYPES[] = { + { &MFVideoFormat_H264, FF_CODEC_TYPE_H264 }, + { &MFVideoFormat_HEVC, FF_CODEC_TYPE_HEVC }, + { &MFVideoFormat_AV1, FF_CODEC_TYPE_AV1 }, +}; + +static FFCodecType ffDetectD3d11vaDecoders(IDXGIAdapter1* adapter, __typeof__(&D3D11CreateDevice) ffD3D11CreateDevice) { + ID3D11Device* FF_AUTO_RELEASE_COM_OBJECT d3dDevice = nullptr; + D3D_FEATURE_LEVEL featureLevel; + if (FAILED(ffD3D11CreateDevice( + adapter, + D3D_DRIVER_TYPE_UNKNOWN, + nullptr, + D3D11_CREATE_DEVICE_VIDEO_SUPPORT, + nullptr, + 0, + D3D11_SDK_VERSION, + &d3dDevice, + &featureLevel, + nullptr)) || + !d3dDevice) { + return FF_CODEC_TYPE_NONE; + } + + ID3D11VideoDevice* FF_AUTO_RELEASE_COM_OBJECT videoDevice = nullptr; + if (FAILED(d3dDevice->QueryInterface(__uuidof(ID3D11VideoDevice), (void**) &videoDevice)) || !videoDevice) { + return FF_CODEC_TYPE_NONE; + } + + FFCodecType decoders = FF_CODEC_TYPE_NONE; + UINT profileCount = videoDevice->GetVideoDecoderProfileCount(); + for (UINT profileIndex = 0; profileIndex < profileCount; ++profileIndex) { + GUID profile; + if (FAILED(videoDevice->GetVideoDecoderProfile(profileIndex, &profile))) { + continue; + } + + FFCodecType type = ffCodecProfileToTypeDx11(profile); + + if ((decoders & type) || !ffCodecProfileHasNativeOutput(videoDevice, profile)) { + continue; + } + + decoders = (FFCodecType) (((uint32_t) decoders) | ((uint32_t) type)); + } + return decoders; +} + +static FFCodecType ffDetectD3d11MftEncoders(const LUID& adapterLuid, __typeof__(&MFCreateAttributes) ffMFCreateAttributes, __typeof__(&MFTEnum2) ffMFTEnum2) { + IMFAttributes* FF_AUTO_RELEASE_COM_OBJECT attributes = nullptr; + if (FAILED(ffMFCreateAttributes(&attributes, 1)) || !attributes) { + return FF_CODEC_TYPE_NONE; + } + + if (FAILED(attributes->SetBlob(MFT_ENUM_ADAPTER_LUID, (const UINT8*) &adapterLuid, sizeof(adapterLuid)))) { + return FF_CODEC_TYPE_NONE; + } + + FFCodecType encoders = FF_CODEC_TYPE_NONE; + + for (uint32_t subtypeIndex = 0; subtypeIndex < ARRAY_SIZE(FF_D3D11VA_MFT_ENCODER_SUBTYPES); ++subtypeIndex) { + const FFCodecMftEncoderSubtype& subtype = FF_D3D11VA_MFT_ENCODER_SUBTYPES[subtypeIndex]; + if (encoders & subtype.codecType) { + continue; + } + + MFT_REGISTER_TYPE_INFO outputType = { + .guidMajorType = MFMediaType_Video, + .guidSubtype = *subtype.subtype, + }; + + IMFActivate** activateList = nullptr; + UINT32 activateCount = 0; + + if (SUCCEEDED(ffMFTEnum2( + MFT_CATEGORY_VIDEO_ENCODER, + MFT_ENUM_FLAG_HARDWARE | MFT_ENUM_FLAG_SORTANDFILTER, + nullptr, + &outputType, + attributes, + &activateList, + &activateCount)) && + activateCount > 0) { + encoders = (FFCodecType) (((uint32_t) encoders) | ((uint32_t) subtype.codecType)); + } + + for (uint32_t i = 0; i < activateCount; ++i) { + activateList[i]->Release(); + } + CoTaskMemFree(activateList); + } + + return encoders; +} + +static FFCodecType ffCodecEncoderToType(D3D12_VIDEO_ENCODER_CODEC codec) { + switch (codec) { + case D3D12_VIDEO_ENCODER_CODEC_H264: + return FF_CODEC_TYPE_H264; + case D3D12_VIDEO_ENCODER_CODEC_HEVC: + return FF_CODEC_TYPE_HEVC; + case D3D12_VIDEO_ENCODER_CODEC_AV1: + return FF_CODEC_TYPE_AV1; + default: + return FF_CODEC_TYPE_UNKNOWN; + } +} + +template <typename Func> +static void ffEnumHardwareAdapters(IDXGIFactory1* factory, Func&& onAdapter) { + for (UINT adapterIndex = 0;; ++adapterIndex) { + IDXGIAdapter1* FF_AUTO_RELEASE_COM_OBJECT adapter = nullptr; + HRESULT adapterStatus = factory->EnumAdapters1(adapterIndex, &adapter); + if (adapterStatus == DXGI_ERROR_NOT_FOUND) { + break; + } + if (FAILED(adapterStatus) || !adapter) { + continue; + } + + DXGI_ADAPTER_DESC1 desc; + if (FAILED(adapter->GetDesc1(&desc))) { + continue; + } + if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) { + continue; + } + + onAdapter(adapter, desc); + } +} + +const char* detectD3d11va(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/, IDXGIFactory1* factory) { + FF_LIBRARY_LOAD_MESSAGE(d3d11, "d3d11" FF_LIBRARY_EXTENSION, 1) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(d3d11, D3D11CreateDevice) + FF_LIBRARY_LOAD_MESSAGE(mfplat, "mfplat" FF_LIBRARY_EXTENSION, 1) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mfplat, MFCreateAttributes) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mfplat, MFTEnum2) + + ffEnumHardwareAdapters(factory, [&](IDXGIAdapter1* adapter, const DXGI_ADAPTER_DESC1& desc) { + FFCodecType decoders = (options->showType & FF_CODEC_SHOW_TYPE_DECODER) + ? ffDetectD3d11vaDecoders(adapter, ffD3D11CreateDevice) + : FF_CODEC_TYPE_NONE; + FFCodecType encoders = (options->showType & FF_CODEC_SHOW_TYPE_ENCODER) + ? ffDetectD3d11MftEncoders(desc.AdapterLuid, ffMFCreateAttributes, ffMFTEnum2) + : FF_CODEC_TYPE_NONE; + + if (decoders == FF_CODEC_TYPE_NONE && encoders == FF_CODEC_TYPE_NONE) { + return; + } + + FFCodecResult* gpuResult = FF_LIST_ADD(FFCodecResult, *result); + ffStrbufInitWS(&gpuResult->gpu, desc.Description); + gpuResult->decoders = decoders; + gpuResult->encoders = encoders; + gpuResult->platformApi = "D3D11VA+MFT"; + }); + + return nullptr; +} + +const char* detectD3d12va(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/, IDXGIFactory1* factory) { + FF_LIBRARY_LOAD_MESSAGE(d3d12, "d3d12" FF_LIBRARY_EXTENSION, 1) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(d3d12, D3D12CreateDevice) + + static const GUID FF_D3D12_DECODE_PROFILES[] = { + D3D12_VIDEO_DECODE_PROFILE_MPEG2, + D3D12_VIDEO_DECODE_PROFILE_MPEG1_AND_MPEG2, + D3D12_VIDEO_DECODE_PROFILE_H264, + D3D12_VIDEO_DECODE_PROFILE_H264_STEREO_PROGRESSIVE, + D3D12_VIDEO_DECODE_PROFILE_H264_STEREO, + D3D12_VIDEO_DECODE_PROFILE_H264_MULTIVIEW, + D3D12_VIDEO_DECODE_PROFILE_VC1, + D3D12_VIDEO_DECODE_PROFILE_VC1_D2010, + D3D12_VIDEO_DECODE_PROFILE_MPEG4PT2_SIMPLE, + D3D12_VIDEO_DECODE_PROFILE_MPEG4PT2_ADVSIMPLE_NOGMC, + D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN, + D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN10, + D3D12_VIDEO_DECODE_PROFILE_VP9, + D3D12_VIDEO_DECODE_PROFILE_VP9_10BIT_PROFILE2, + D3D12_VIDEO_DECODE_PROFILE_VP8, + D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE0, + D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE1, + D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE2, + D3D12_VIDEO_DECODE_PROFILE_AV1_12BIT_PROFILE2, + D3D12_VIDEO_DECODE_PROFILE_AV1_12BIT_PROFILE2_420, + }; + + static const D3D12_VIDEO_ENCODER_CODEC FF_D3D12_ENCODER_CODECS[] = { + D3D12_VIDEO_ENCODER_CODEC_H264, + D3D12_VIDEO_ENCODER_CODEC_HEVC, + D3D12_VIDEO_ENCODER_CODEC_AV1, + }; + + const uint32_t resultLengthBefore = result->length; + + ffEnumHardwareAdapters(factory, [&](IDXGIAdapter1* adapter, const DXGI_ADAPTER_DESC1& desc) { + ID3D12Device* FF_AUTO_RELEASE_COM_OBJECT d3dDevice = nullptr; + if (FAILED(ffD3D12CreateDevice( + adapter, + D3D_FEATURE_LEVEL_11_0, + __uuidof(ID3D12Device), + (void**) &d3dDevice)) || + !d3dDevice) { + return; + } + + ID3D12VideoDevice* FF_AUTO_RELEASE_COM_OBJECT videoDevice = nullptr; + if (FAILED(d3dDevice->QueryInterface(__uuidof(ID3D12VideoDevice), (void**) &videoDevice)) || !videoDevice) { + return; + } + + FFCodecType decoders = FF_CODEC_TYPE_NONE; + if (options->showType & FF_CODEC_SHOW_TYPE_DECODER) { + for (uint32_t profileIndex = 0; profileIndex < ARRAY_SIZE(FF_D3D12_DECODE_PROFILES); ++profileIndex) { + const GUID& profile = FF_D3D12_DECODE_PROFILES[profileIndex]; + FFCodecType codecType = ffCodecProfileToTypeDx12(profile); + + if ((decoders & codecType) || !ffCodecProfileHasNativeOutput(videoDevice, profile, 0)) { + continue; + } + + decoders = (FFCodecType) (((uint32_t) decoders) | ((uint32_t) codecType)); + } + } + + FFCodecType encoders = FF_CODEC_TYPE_NONE; + if (options->showType & FF_CODEC_SHOW_TYPE_ENCODER) { + for (uint32_t codecIndex = 0; codecIndex < ARRAY_SIZE(FF_D3D12_ENCODER_CODECS); ++codecIndex) { + D3D12_VIDEO_ENCODER_CODEC codec = FF_D3D12_ENCODER_CODECS[codecIndex]; + FFCodecType codecType = ffCodecEncoderToType(codec); + + if ((encoders & codecType) || !ffCodecEncoderSupportedD3d12(videoDevice, codec, 0)) { + continue; + } + + encoders = (FFCodecType) (((uint32_t) encoders) | ((uint32_t) codecType)); + } + } + + if (decoders == FF_CODEC_TYPE_NONE && encoders == FF_CODEC_TYPE_NONE) { + return; + } + + FFCodecResult* gpuResult = FF_LIST_ADD(FFCodecResult, *result); + ffStrbufInitWS(&gpuResult->gpu, desc.Description); + gpuResult->decoders = decoders; + gpuResult->encoders = encoders; + gpuResult->platformApi = "D3D12VA"; + }); + + if (result->length == resultLengthBefore) { + return "No D3D12 video acceleration support"; + } + + return nullptr; +} + +const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/) { + FF_LIBRARY_LOAD_MESSAGE(dxgi, "dxgi" FF_LIBRARY_EXTENSION, 1) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(dxgi, CreateDXGIFactory1) + + const char* error = ffInitCom(); + if (error) { + return error; + } + + IDXGIFactory1* FF_AUTO_RELEASE_COM_OBJECT factory = nullptr; + if (FAILED(ffCreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**) &factory)) || !factory) { + return "CreateDXGIFactory1() failed"; + } + + if (ffIsWindows11OrGreater()) { + // D3D12 video encoding is supported only on Windows 11 + if (detectD3d12va(options, result, factory) == nullptr) { + return nullptr; + } + } + return detectD3d11va(options, result, factory); +} |