summaryrefslogtreecommitdiffstats
path: root/src/detection/codec
diff options
context:
space:
mode:
authorsumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
committersumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
commit76424950e373d3b04ac3dd13019151bfba3e8423 (patch)
tree4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/detection/codec
Add the files
Diffstat (limited to 'src/detection/codec')
-rw-r--r--src/detection/codec/codec.c14
-rw-r--r--src/detection/codec/codec.h43
-rw-r--r--src/detection/codec/codec_android.c102
-rw-r--r--src/detection/codec/codec_apple.c96
-rw-r--r--src/detection/codec/codec_linux.c470
-rw-r--r--src/detection/codec/codec_vulkan.c270
-rw-r--r--src/detection/codec/codec_windows.cpp553
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);
+}