diff --git a/app/streaming/video/ffmpeg-renderers/d3d11va.cpp b/app/streaming/video/ffmpeg-renderers/d3d11va.cpp index 1ac76d01..66364532 100644 --- a/app/streaming/video/ffmpeg-renderers/d3d11va.cpp +++ b/app/streaming/video/ffmpeg-renderers/d3d11va.cpp @@ -75,6 +75,8 @@ static_assert(sizeof(CSC_CONST_BUF) % 16 == 0, "Constant buffer sizes must be a D3D11VARenderer::D3D11VARenderer(int decoderSelectionPass) : m_DecoderSelectionPass(decoderSelectionPass), + m_DevicesWithFL11Support(0), + m_DevicesWithCodecSupport(0), m_Factory(nullptr), m_Device(nullptr), m_SwapChain(nullptr), @@ -160,6 +162,7 @@ bool D3D11VARenderer::createDeviceByAdapterIndex(int adapterIndex, bool* adapter bool success = false; IDXGIAdapter1* adapter = nullptr; DXGI_ADAPTER_DESC1 adapterDesc; + D3D_FEATURE_LEVEL featureLevel; HRESULT hr; SDL_assert(m_Device == nullptr); @@ -209,7 +212,7 @@ bool D3D11VARenderer::createDeviceByAdapterIndex(int adapterIndex, bool* adapter 0, D3D11_SDK_VERSION, &m_Device, - nullptr, + &featureLevel, &m_DeviceContext); if (FAILED(hr)) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, @@ -217,6 +220,11 @@ bool D3D11VARenderer::createDeviceByAdapterIndex(int adapterIndex, bool* adapter hr); goto Exit; } + else if (featureLevel >= D3D_FEATURE_LEVEL_11_0) { + // Remember that we found a non-software D3D11 devices with support for + // feature level 11.0 or later (Fermi, Terascale 2, or Ivy Bridge and later) + m_DevicesWithFL11Support++; + } if (!checkDecoderSupport(adapter)) { m_DeviceContext->Release(); @@ -226,6 +234,10 @@ bool D3D11VARenderer::createDeviceByAdapterIndex(int adapterIndex, bool* adapter goto Exit; } + else { + // Remember that we found a device with support for decoding this codec + m_DevicesWithCodecSupport++; + } success = true; @@ -997,6 +1009,29 @@ bool D3D11VARenderer::needsTestFrame() return true; } +IFFmpegRenderer::InitFailureReason D3D11VARenderer::getInitFailureReason() +{ + // In the specific case where we found at least one D3D11 hardware device but none of the + // enumerated devices have support for the specified codec, tell the FFmpeg decoder not to + // bother trying other hwaccels. We don't want to try loading D3D9 if the device doesn't + // even have hardware support for the codec. + // + // NB: We use feature level 11.0 support as a gate here because we want to avoid returning + // this failure reason in cases where we might have an extremely old GPU with support for + // DXVA2 on D3D9 but not D3D11VA on D3D11. I'm unsure if any such drivers/hardware exists, + // but better be safe than sorry. + // + // NB2: We're also assuming that no GPU exists which lacks any D3D11 driver but has drivers + // for non-DX APIs like Vulkan. I believe this is a Windows Logo requirement so it should be + // safe to assume. + if (m_DevicesWithFL11Support != 0 && m_DevicesWithCodecSupport == 0) { + return InitFailureReason::NoHardwareSupport; + } + else { + return InitFailureReason::Unknown; + } +} + void D3D11VARenderer::lockContext(void *lock_ctx) { auto me = (D3D11VARenderer*)lock_ctx; diff --git a/app/streaming/video/ffmpeg-renderers/d3d11va.h b/app/streaming/video/ffmpeg-renderers/d3d11va.h index 8421fdb4..88aa86fc 100644 --- a/app/streaming/video/ffmpeg-renderers/d3d11va.h +++ b/app/streaming/video/ffmpeg-renderers/d3d11va.h @@ -21,6 +21,7 @@ public: virtual int getRendererAttributes() override; virtual int getDecoderCapabilities() override; virtual bool needsTestFrame() override; + virtual InitFailureReason getInitFailureReason() override; private: static void lockContext(void* lock_ctx); @@ -35,6 +36,8 @@ private: bool createDeviceByAdapterIndex(int adapterIndex, bool* adapterNotFound = nullptr); int m_DecoderSelectionPass; + int m_DevicesWithFL11Support; + int m_DevicesWithCodecSupport; IDXGIFactory5* m_Factory; ID3D11Device* m_Device; diff --git a/app/streaming/video/ffmpeg-renderers/renderer.h b/app/streaming/video/ffmpeg-renderers/renderer.h index f0540ae4..1b2b3552 100644 --- a/app/streaming/video/ffmpeg-renderers/renderer.h +++ b/app/streaming/video/ffmpeg-renderers/renderer.h @@ -128,6 +128,24 @@ public: virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary** options) = 0; virtual void renderFrame(AVFrame* frame) = 0; + enum class InitFailureReason + { + Unknown, + + // Only return this reason code if the hardware physically lacks support for + // the specified codec. If the FFmpeg decoder code sees this value, it will + // assume trying additional hwaccel renderers will useless and give up. + // + // NB: This should only be used under very special circumstances for cases + // where trying additional hwaccels may be undesirable since it could lead + // to incorrectly skipping working hwaccels. + NoHardwareSupport, + }; + + virtual InitFailureReason getInitFailureReason() { + return InitFailureReason::Unknown; + } + // Called for threaded renderers to allow them to wait prior to us latching // the next frame for rendering (as opposed to waiting on buffer swap with // an older frame already queued for display). diff --git a/app/streaming/video/ffmpeg.cpp b/app/streaming/video/ffmpeg.cpp index ba11b8af..cb8bc161 100644 --- a/app/streaming/video/ffmpeg.cpp +++ b/app/streaming/video/ffmpeg.cpp @@ -888,6 +888,7 @@ bool FFmpegVideoDecoder::tryInitializeRenderer(const AVCodec* decoder, enum AVPixelFormat requiredFormat, PDECODER_PARAMETERS params, const AVCodecHWConfig* hwConfig, + IFFmpegRenderer::InitFailureReason* failureReason, // Out - Optional std::function createRendererFunc) { DECODER_PARAMETERS testFrameDecoderParams = *params; @@ -905,6 +906,10 @@ bool FFmpegVideoDecoder::tryInitializeRenderer(const AVCodec* decoder, m_HwDecodeCfg = hwConfig; + if (failureReason != nullptr) { + *failureReason = IFFmpegRenderer::InitFailureReason::Unknown; + } + // i == 0 - Indirect via EGL or DRM frontend with zero-copy DMA-BUF passing // i == 1 - Direct rendering or indirect via SDL read-back #ifdef HAVE_EGL @@ -936,6 +941,11 @@ bool FFmpegVideoDecoder::tryInitializeRenderer(const AVCodec* decoder, else { SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Decoder failed to initialize after successful test"); + + if (m_BackendRenderer != nullptr && failureReason != nullptr) { + *failureReason = m_BackendRenderer->getInitFailureReason(); + } + reset(); } } @@ -945,6 +955,10 @@ bool FFmpegVideoDecoder::tryInitializeRenderer(const AVCodec* decoder, } } else { + if (m_BackendRenderer != nullptr && failureReason != nullptr) { + *failureReason = m_BackendRenderer->getInitFailureReason(); + } + // Failed to initialize, so keep looking reset(); } @@ -962,7 +976,7 @@ bool FFmpegVideoDecoder::tryInitializeRenderer(const AVCodec* decoder, SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, \ "Trying " #RENDERER_TYPE " for codec %s due to preferred pixel format: 0x%x", \ decoder->name, decoder->pix_fmts[i]); \ - if (tryInitializeRenderer(decoder, decoder->pix_fmts[i], params, nullptr, \ + if (tryInitializeRenderer(decoder, decoder->pix_fmts[i], params, nullptr, nullptr, \ []() -> IFFmpegRenderer* { return new RENDERER_TYPE(); })) { \ SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, \ "Chose " #RENDERER_TYPE " for codec %s due to preferred pixel format: 0x%x", \ @@ -980,7 +994,7 @@ bool FFmpegVideoDecoder::tryInitializeRenderer(const AVCodec* decoder, SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, \ "Trying " #RENDERER_TYPE " for codec %s due to compatible pixel format: 0x%x", \ decoder->name, decoder->pix_fmts[i]); \ - if (tryInitializeRenderer(decoder, decoder->pix_fmts[i], params, nullptr, \ + if (tryInitializeRenderer(decoder, decoder->pix_fmts[i], params, nullptr, nullptr, \ []() -> IFFmpegRenderer* { return new RENDERER_TYPE(); })) { \ SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, \ "Chose " #RENDERER_TYPE " for codec %s due to compatible pixel format: 0x%x", \ @@ -1008,10 +1022,16 @@ bool FFmpegVideoDecoder::tryInitializeRendererForUnknownDecoder(const AVCodec* d } // Initialize the hardware codec and submit a test frame if the renderer needs it - if (tryInitializeRenderer(decoder, AV_PIX_FMT_NONE, params, config, + IFFmpegRenderer::InitFailureReason failureReason; + if (tryInitializeRenderer(decoder, AV_PIX_FMT_NONE, params, config, &failureReason, [config]() -> IFFmpegRenderer* { return createHwAccelRenderer(config, 0); })) { return true; } + else if (failureReason == IFFmpegRenderer::InitFailureReason::NoHardwareSupport) { + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Skipping remaining hwaccels due lack of hardware support for specified codec"); + break; + } } } @@ -1019,13 +1039,13 @@ bool FFmpegVideoDecoder::tryInitializeRendererForUnknownDecoder(const AVCodec* d // Supported output pixel formats are unknown. We'll just try DRM/SDL and hope it can cope. #if defined(HAVE_DRM) && defined(GL_IS_SLOW) - if (tryInitializeRenderer(decoder, AV_PIX_FMT_NONE, params, nullptr, + if (tryInitializeRenderer(decoder, AV_PIX_FMT_NONE, params, nullptr, nullptr, []() -> IFFmpegRenderer* { return new DrmRenderer(); })) { return true; } #endif - if (tryInitializeRenderer(decoder, AV_PIX_FMT_NONE, params, nullptr, + if (tryInitializeRenderer(decoder, AV_PIX_FMT_NONE, params, nullptr, nullptr, []() -> IFFmpegRenderer* { return new SdlRenderer(); })) { return true; } @@ -1178,6 +1198,8 @@ bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params) // Look for a hardware decoder first unless software-only if (params->vds != StreamingPreferences::VDS_FORCE_SOFTWARE) { + QSet terminallyFailedHardwareDecoders; + // Iterate through tier-1 hwaccel decoders codecIterator = NULL; while ((decoder = av_codec_iterate(&codecIterator))) { @@ -1203,6 +1225,11 @@ bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params) continue; } + // Skip hardware decoders that have returned a terminal failure status + if (terminallyFailedHardwareDecoders.contains(decoder)) { + continue; + } + // Look for the first matching hwaccel hardware decoder (pass 0) for (int i = 0;; i++) { const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i); @@ -1212,10 +1239,17 @@ bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params) } // Initialize the hardware codec and submit a test frame if the renderer needs it - if (tryInitializeRenderer(decoder, AV_PIX_FMT_NONE, params, config, + IFFmpegRenderer::InitFailureReason failureReason; + if (tryInitializeRenderer(decoder, AV_PIX_FMT_NONE, params, config, &failureReason, [config]() -> IFFmpegRenderer* { return createHwAccelRenderer(config, 0); })) { return true; } + else if (failureReason == IFFmpegRenderer::InitFailureReason::NoHardwareSupport) { + terminallyFailedHardwareDecoders.insert(decoder); + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Skipping remaining hwaccels due lack of hardware support for specified codec"); + break; + } } } @@ -1244,6 +1278,11 @@ bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params) continue; } + // Skip hardware decoders that have returned a terminal failure status + if (terminallyFailedHardwareDecoders.contains(decoder)) { + continue; + } + // Try to initialize this decoder both as hwaccel and non-hwaccel if (tryInitializeRendererForUnknownDecoder(decoder, params, true)) { return true; @@ -1270,6 +1309,11 @@ bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params) continue; } + // Skip hardware decoders that have returned a terminal failure status + if (terminallyFailedHardwareDecoders.contains(decoder)) { + continue; + } + // Look for the first matching hwaccel hardware decoder (pass 1) // This picks up "second-tier" hwaccels like CUDA. for (int i = 0;; i++) { @@ -1280,10 +1324,17 @@ bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params) } // Initialize the hardware codec and submit a test frame if the renderer needs it - if (tryInitializeRenderer(decoder, AV_PIX_FMT_NONE, params, config, + IFFmpegRenderer::InitFailureReason failureReason; + if (tryInitializeRenderer(decoder, AV_PIX_FMT_NONE, params, config, &failureReason, [config]() -> IFFmpegRenderer* { return createHwAccelRenderer(config, 1); })) { return true; } + else if (failureReason == IFFmpegRenderer::InitFailureReason::NoHardwareSupport) { + terminallyFailedHardwareDecoders.insert(decoder); + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Skipping remaining hwaccels due lack of hardware support for specified codec"); + break; + } } } } diff --git a/app/streaming/video/ffmpeg.h b/app/streaming/video/ffmpeg.h index eb3c1812..02a7b252 100644 --- a/app/streaming/video/ffmpeg.h +++ b/app/streaming/video/ffmpeg.h @@ -55,6 +55,7 @@ private: enum AVPixelFormat requiredFormat, PDECODER_PARAMETERS params, const AVCodecHWConfig* hwConfig, + IFFmpegRenderer::InitFailureReason* failureReason, std::function createRendererFunc); static IFFmpegRenderer* createHwAccelRenderer(const AVCodecHWConfig* hwDecodeCfg, int pass);