From 9d8f794cb934b6e98dd0383782febf822aeb889e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Sun, 31 May 2026 19:52:29 +0800 Subject: [PATCH] Codec (Windows): detects encoder support via d3d12va --- src/detection/codec/codec_windows.cpp | 252 ++++++++++++++++++++++++-- 1 file changed, 236 insertions(+), 16 deletions(-) diff --git a/src/detection/codec/codec_windows.cpp b/src/detection/codec/codec_windows.cpp index f9b256248..b4dd83210 100644 --- a/src/detection/codec/codec_windows.cpp +++ b/src/detection/codec/codec_windows.cpp @@ -6,9 +6,11 @@ extern "C" { } #include +#include #include #include #include +#include static const DXGI_FORMAT FF_NATIVE_CODEC_FORMATS[] = { DXGI_FORMAT_420_OPAQUE, @@ -23,7 +25,7 @@ static const DXGI_FORMAT FF_NATIVE_CODEC_FORMATS[] = { DXGI_FORMAT_Y416, }; -static FFCodecType ffCodecProfileToType(const GUID& profile) { +static FFCodecType ffCodecProfileToTypeDx11(const GUID& profile) { // clang-format off if (IsEqualGUID(profile, DXVA_ModeH261_A) || IsEqualGUID(profile, DXVA_ModeH261_B)) { @@ -130,6 +132,59 @@ static FFCodecType ffCodecProfileToType(const GUID& profile) { 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; @@ -141,22 +196,65 @@ static bool ffCodecProfileHasNativeOutput(ID3D11VideoDevice* videoDevice, const return false; } -const char* ffDetectCodec(FF_A_UNUSED FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/) { - FF_LIBRARY_LOAD_MESSAGE(dxgi, "dxgi" FF_LIBRARY_EXTENSION, 1) - FF_LIBRARY_LOAD_SYMBOL_MESSAGE(dxgi, CreateDXGIFactory1) - FF_LIBRARY_LOAD_MESSAGE(d3d11, "d3d11" FF_LIBRARY_EXTENSION, 1) - FF_LIBRARY_LOAD_SYMBOL_MESSAGE(d3d11, D3D11CreateDevice) +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, + }; - const char* error = ffInitCom(); - if (error) { - return error; + if (SUCCEEDED(videoDevice->CheckFeatureSupport(D3D12_FEATURE_VIDEO_DECODE_SUPPORT, &support, sizeof(support))) && + (support.SupportFlags & D3D12_VIDEO_DECODE_SUPPORT_FLAG_SUPPORTED)) { + return true; + } } - IDXGIFactory1* FF_AUTO_RELEASE_COM_OBJECT factory = nullptr; - if (FAILED(ffCreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**) &factory)) || !factory) { - return "CreateDXGIFactory1() failed"; - } + return false; +} +static bool ffCodecEncoderSupportedD3d12(ID3D12VideoDevice* videoDevice, D3D12_VIDEO_ENCODER_CODEC codec, UINT nodeIndex) { + D3D12_FEATURE_DATA_VIDEO_ENCODER_RATE_CONTROL_MODE encoderMode = {}; + encoderMode.NodeIndex = nodeIndex; + encoderMode.Codec = codec; + encoderMode.RateControlMode = D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CQP; + + return SUCCEEDED(videoDevice->CheckFeatureSupport( + D3D12_FEATURE_VIDEO_ENCODER_RATE_CONTROL_MODE, + &encoderMode, + sizeof(encoderMode))) && + encoderMode.IsSupported; +} + +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 +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); @@ -175,6 +273,15 @@ const char* ffDetectCodec(FF_A_UNUSED FFCodecOptions* options, FFlist* result /* continue; } + onAdapter(adapter, desc); + } +} + +const char* detectD3d11va(FFlist* result /*list of FFCodecResult*/, IDXGIFactory1* factory) { + FF_LIBRARY_LOAD_MESSAGE(d3d11, "d3d11" FF_LIBRARY_EXTENSION, 1) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(d3d11, D3D11CreateDevice) + + ffEnumHardwareAdapters(factory, [&](IDXGIAdapter1* adapter, const DXGI_ADAPTER_DESC1& desc) { ID3D11Device* FF_AUTO_RELEASE_COM_OBJECT d3dDevice = nullptr; D3D_FEATURE_LEVEL featureLevel; HRESULT deviceStatus = ffD3D11CreateDevice( @@ -189,12 +296,12 @@ const char* ffDetectCodec(FF_A_UNUSED FFCodecOptions* options, FFlist* result /* &featureLevel, nullptr); if (FAILED(deviceStatus) || !d3dDevice) { - continue; + return; } ID3D11VideoDevice* FF_AUTO_RELEASE_COM_OBJECT videoDevice = nullptr; if (FAILED(d3dDevice->QueryInterface(__uuidof(ID3D11VideoDevice), (void**) &videoDevice)) || !videoDevice) { - continue; + return; } FFCodecResult* gpuResult = nullptr; @@ -214,10 +321,123 @@ const char* ffDetectCodec(FF_A_UNUSED FFCodecOptions* options, FFlist* result /* ffStrbufInitWS(&gpuResult->gpu, desc.Description); gpuResult->decoders = FF_CODEC_TYPE_NONE; gpuResult->encoders = FF_CODEC_TYPE_NONE; + gpuResult->platformApi = "d3d11va"; } - gpuResult->decoders = (FFCodecType) (((uint32_t) gpuResult->decoders) | ((uint32_t) ffCodecProfileToType(profile))); + gpuResult->decoders = (FFCodecType) (((uint32_t) gpuResult->decoders) | ((uint32_t) ffCodecProfileToTypeDx11(profile))); } + }); + + return nullptr; +} + +const char* detectD3d12va(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; + HRESULT deviceStatus = ffD3D12CreateDevice( + adapter, + D3D_FEATURE_LEVEL_11_0, + __uuidof(ID3D12Device), + (void**) &d3dDevice); + if (FAILED(deviceStatus) || !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; + FFCodecType encoders = FF_CODEC_TYPE_NONE; + + for (uint32_t profileIndex = 0; profileIndex < ARRAY_SIZE(FF_D3D12_DECODE_PROFILES); ++profileIndex) { + const GUID& profile = FF_D3D12_DECODE_PROFILES[profileIndex]; + if (!ffCodecProfileHasNativeOutput(videoDevice, profile, 0)) { + continue; + } + + FFCodecType codecType = ffCodecProfileToTypeDx12(profile); + decoders = (FFCodecType) (((uint32_t) decoders) | ((uint32_t) codecType)); + } + + for (uint32_t codecIndex = 0; codecIndex < ARRAY_SIZE(FF_D3D12_ENCODER_CODECS); ++codecIndex) { + D3D12_VIDEO_ENCODER_CODEC codec = FF_D3D12_ENCODER_CODECS[codecIndex]; + if (!ffCodecEncoderSupportedD3d12(videoDevice, codec, 0)) { + continue; + } + + FFCodecType codecType = ffCodecEncoderToType(codec); + 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* ffDetectCodec(FF_A_UNUSED 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 (detectD3d12va(result, factory) == nullptr) { + return nullptr; + } + return detectD3d11va(result, factory); +}