diff options
Diffstat (limited to 'src/plugins/multimedia/ffmpeg/qffmpeghwaccel_d3d11.cpp')
-rw-r--r-- | src/plugins/multimedia/ffmpeg/qffmpeghwaccel_d3d11.cpp | 353 |
1 files changed, 237 insertions, 116 deletions
diff --git a/src/plugins/multimedia/ffmpeg/qffmpeghwaccel_d3d11.cpp b/src/plugins/multimedia/ffmpeg/qffmpeghwaccel_d3d11.cpp index 350545f8a..a2533a132 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpeghwaccel_d3d11.cpp +++ b/src/plugins/multimedia/ffmpeg/qffmpeghwaccel_d3d11.cpp @@ -1,161 +1,271 @@ -/**************************************************************************** -** -** Copyright (C) 2022 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qffmpeghwaccel_d3d11_p.h" +#include "playbackengine/qffmpegstreamdecoder_p.h" #include <qvideoframeformat.h> #include "qffmpegvideobuffer_p.h" - #include <private/qvideotexturehelper_p.h> -#include <private/qrhi_p.h> -#include <private/qrhid3d11_p.h> +#include <private/qcomptr_p.h> +#include <private/quniquehandle_p.h> + +#include <rhi/qrhi.h> #include <qopenglfunctions.h> #include <qdebug.h> #include <qloggingcategory.h> #include <libavutil/hwcontext_d3d11va.h> +#include <d3d11_1.h> +#include <dxgi1_2.h> QT_BEGIN_NAMESPACE -Q_LOGGING_CATEGORY(qLcMediaFFmpegHWAccel, "qt.multimedia.hwaccel") +namespace { + +Q_LOGGING_CATEGORY(qLcMediaFFmpegHWAccel, "qt.multimedia.hwaccel"); + +ComPtr<ID3D11Device1> GetD3DDevice(QRhi *rhi) +{ + const auto native = static_cast<const QRhiD3D11NativeHandles *>(rhi->nativeHandles()); + if (!native) + return {}; + + const ComPtr<ID3D11Device> rhiDevice = static_cast<ID3D11Device *>(native->dev); + ComPtr<ID3D11Device1> dev1; + if (rhiDevice.As(&dev1) != S_OK) + return nullptr; + + return dev1; +} + +} // namespace namespace QFFmpeg { -class D3D11TextureSet : public TextureSet +bool TextureBridge::copyToSharedTex(ID3D11Device *dev, ID3D11DeviceContext *ctx, + const ComPtr<ID3D11Texture2D> &tex, UINT index, + const QSize &frameSize) { -public: - D3D11TextureSet(QRhi *rhi, QVideoFrameFormat::PixelFormat format, ID3D11Texture2D *tex, int index) - : m_rhi(rhi) - , m_format(format) - , m_tex(tex) - , m_index(index) - {} - - ~D3D11TextureSet() override { - if (m_tex) - m_tex->Release(); + if (!ensureSrcTex(dev, tex, frameSize)) + return false; + + // Flush to ensure that texture is fully updated before we share it. + ctx->Flush(); + + if (m_srcMutex->AcquireSync(m_srcKey, INFINITE) != S_OK) + return false; + + const UINT width = static_cast<UINT>(frameSize.width()); + const UINT height = static_cast<UINT>(frameSize.height()); + + // A crop box is needed because FFmpeg may have created textures + // that are bigger than the frame size to account for the decoder's + // surface alignment requirements. + const D3D11_BOX crop{ 0, 0, 0, width, height, 1 }; + ctx->CopySubresourceRegion(m_srcTex.Get(), 0, 0, 0, 0, tex.Get(), index, &crop); + + m_srcMutex->ReleaseSync(m_destKey); + return true; +} + +ComPtr<ID3D11Texture2D> TextureBridge::copyFromSharedTex(const ComPtr<ID3D11Device1> &dev, + const ComPtr<ID3D11DeviceContext> &ctx) +{ + if (!ensureDestTex(dev)) + return {}; + + if (m_destMutex->AcquireSync(m_destKey, INFINITE) != S_OK) + return {}; + + ctx->CopySubresourceRegion(m_outputTex.Get(), 0, 0, 0, 0, m_destTex.Get(), 0, nullptr); + + m_destMutex->ReleaseSync(m_srcKey); + + return m_outputTex; +} + +bool TextureBridge::ensureDestTex(const ComPtr<ID3D11Device1> &dev) +{ + if (m_destDevice != dev) { + // Destination device changed. Recreate texture. + m_destTex = nullptr; + m_destDevice = dev; } - std::unique_ptr<QRhiTexture> texture(int plane) override { - auto desc = QVideoTextureHelper::textureDescription(m_format); - if (!m_tex || !m_rhi || !desc || plane >= desc->nplanes) - return {}; + if (m_destTex) + return true; - D3D11_TEXTURE2D_DESC d3d11desc = {}; - m_tex->GetDesc(&d3d11desc); + if (m_destDevice->OpenSharedResource1(m_sharedHandle.get(), IID_PPV_ARGS(&m_destTex)) != S_OK) + return false; - QSize planeSize(desc->widthForPlane(int(d3d11desc.Width), plane), - desc->heightForPlane(int(d3d11desc.Height), plane)); + CD3D11_TEXTURE2D_DESC desc{}; + m_destTex->GetDesc(&desc); - std::unique_ptr<QRhiTexture> tex(m_rhi->newTextureArray(desc->textureFormat[plane], - int(d3d11desc.ArraySize), - planeSize, 1, {})); - if (tex) { - tex->setArrayRange(m_index, 1); - if (!tex->createFrom({quint64(m_tex), 0})) - tex.reset(); - } - return tex; + desc.MiscFlags = 0; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + + if (m_destDevice->CreateTexture2D(&desc, nullptr, m_outputTex.ReleaseAndGetAddressOf()) != S_OK) + return false; + + if (m_destTex.As(&m_destMutex) != S_OK) + return false; + + return true; +} + +bool TextureBridge::ensureSrcTex(ID3D11Device *dev, const ComPtr<ID3D11Texture2D> &tex, const QSize &frameSize) +{ + if (!isSrcInitialized(dev, tex, frameSize)) + return recreateSrc(dev, tex, frameSize); + + return true; +} + +bool TextureBridge::isSrcInitialized(const ID3D11Device *dev, + const ComPtr<ID3D11Texture2D> &tex, + const QSize &frameSize) const +{ + if (!m_srcTex) + return false; + + // Check if device has changed + ComPtr<ID3D11Device> texDevice; + m_srcTex->GetDevice(texDevice.GetAddressOf()); + if (dev != texDevice.Get()) + return false; + + // Check if shared texture has correct size and format + CD3D11_TEXTURE2D_DESC inputDesc{}; + tex->GetDesc(&inputDesc); + + CD3D11_TEXTURE2D_DESC currentDesc{}; + m_srcTex->GetDesc(¤tDesc); + + if (inputDesc.Format != currentDesc.Format) + return false; + + const UINT width = static_cast<UINT>(frameSize.width()); + const UINT height = static_cast<UINT>(frameSize.height()); + + if (currentDesc.Width != width || currentDesc.Height != height) + return false; + + return true; +} + +bool TextureBridge::recreateSrc(ID3D11Device *dev, const ComPtr<ID3D11Texture2D> &tex, const QSize &frameSize) +{ + m_sharedHandle.close(); + + CD3D11_TEXTURE2D_DESC desc{}; + tex->GetDesc(&desc); + + const UINT width = static_cast<UINT>(frameSize.width()); + const UINT height = static_cast<UINT>(frameSize.height()); + + CD3D11_TEXTURE2D_DESC texDesc{ desc.Format, width, height }; + texDesc.MipLevels = 1; + texDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX | D3D11_RESOURCE_MISC_SHARED_NTHANDLE; + + if (dev->CreateTexture2D(&texDesc, nullptr, m_srcTex.ReleaseAndGetAddressOf()) != S_OK) + return false; + + ComPtr<IDXGIResource1> res; + if (m_srcTex.As(&res) != S_OK) + return false; + + const HRESULT hr = + res->CreateSharedHandle(nullptr, DXGI_SHARED_RESOURCE_READ, nullptr, &m_sharedHandle); + + if (hr != S_OK || !m_sharedHandle) + return false; + + if (m_srcTex.As(&m_srcMutex) != S_OK || !m_srcMutex) + return false; + + m_destTex = nullptr; + m_destMutex = nullptr; + return true; +} + +class D3D11TextureSet : public TextureSet +{ +public: + D3D11TextureSet(QRhi *rhi, ComPtr<ID3D11Texture2D> &&tex) + : m_owner{ rhi }, m_tex(std::move(tex)) + { + } + + qint64 textureHandle(QRhi *rhi, int /*plane*/) override + { + if (rhi != m_owner) + return 0u; + return reinterpret_cast<qint64>(m_tex.Get()); } private: - QRhi *m_rhi = nullptr; - QVideoFrameFormat::PixelFormat m_format; - ID3D11Texture2D *m_tex = nullptr; - int m_index = 0; + QRhi *m_owner = nullptr; + ComPtr<ID3D11Texture2D> m_tex; }; - D3D11TextureConverter::D3D11TextureConverter(QRhi *rhi) - : TextureConverterBackend(rhi) + : TextureConverterBackend(rhi), m_rhiDevice{ GetD3DDevice(rhi) } { + if (!m_rhiDevice) + return; + + m_rhiDevice->GetImmediateContext(m_rhiCtx.GetAddressOf()); } TextureSet *D3D11TextureConverter::getTextures(AVFrame *frame) { - if (!frame || !frame->hw_frames_ctx || frame->format != AV_PIX_FMT_D3D11) + if (!m_rhiDevice) return nullptr; - auto *fCtx = (AVHWFramesContext *)frame->hw_frames_ctx->data; - auto *ctx = fCtx->device_ctx; - if (!ctx || ctx->type != AV_HWDEVICE_TYPE_D3D11VA) + if (!frame || !frame->hw_frames_ctx || frame->format != AV_PIX_FMT_D3D11) return nullptr; - auto nh = static_cast<const QRhiD3D11NativeHandles *>(rhi->nativeHandles()); - if (!nh) - return nullptr; + const auto *fCtx = reinterpret_cast<AVHWFramesContext *>(frame->hw_frames_ctx->data); + const auto *ctx = fCtx->device_ctx; - auto dev = reinterpret_cast<ID3D11Device *>(nh->dev); - if (!dev) + if (!ctx || ctx->type != AV_HWDEVICE_TYPE_D3D11VA) return nullptr; - auto ffmpegTex = (ID3D11Texture2D *)frame->data[0]; - int index = (intptr_t)frame->data[1]; - - IDXGIResource *dxgiResource = nullptr; - HRESULT hr = ffmpegTex->QueryInterface(__uuidof(IDXGIResource), (void **)&dxgiResource); - if (FAILED(hr)) { - qCDebug(qLcMediaFFmpegHWAccel) << "Failed to obtain resource handle from FFMpeg texture" << hr; - return nullptr; - } - HANDLE shared = nullptr; - hr = dxgiResource->GetSharedHandle(&shared); - dxgiResource->Release(); - if (FAILED(hr)) { - qCDebug(qLcMediaFFmpegHWAccel) << "Failed to obtain shared handle for FFmpeg texture" << hr; - return nullptr; - } + const ComPtr<ID3D11Texture2D> ffmpegTex = reinterpret_cast<ID3D11Texture2D *>(frame->data[0]); + const int index = static_cast<int>(reinterpret_cast<intptr_t>(frame->data[1])); if (rhi->backend() == QRhi::D3D11) { - ID3D11Texture2D *sharedTex = nullptr; - hr = dev->OpenSharedResource(shared, __uuidof(ID3D11Texture2D), (void **)(&sharedTex)); - if (FAILED(hr)) { - qCDebug(qLcMediaFFmpegHWAccel) << "Failed to share FFmpeg texture" << hr; - return nullptr; + { + const auto *avDeviceCtx = static_cast<AVD3D11VADeviceContext *>(ctx->hwctx); + + if (!avDeviceCtx) + return nullptr; + + // Lock the FFmpeg device context while we copy from FFmpeg's + // frame pool into a shared texture because the underlying ID3D11DeviceContext + // is not thread safe. + avDeviceCtx->lock(avDeviceCtx->lock_ctx); + QScopeGuard autoUnlock([&] { avDeviceCtx->unlock(avDeviceCtx->lock_ctx); }); + + // Populate the shared texture with one slice from the frame pool, cropping away + // extra surface alignment areas that FFmpeg adds to the textures + QSize frameSize{ frame->width, frame->height }; + if (!m_bridge.copyToSharedTex(avDeviceCtx->device, avDeviceCtx->device_context, + ffmpegTex, index, frameSize)) { + return nullptr; + } } - QVideoFrameFormat::PixelFormat format = QFFmpegVideoBuffer::toQtPixelFormat(AVPixelFormat(fCtx->sw_format)); - return new D3D11TextureSet(rhi, format, sharedTex, index); - } else if (rhi->backend() == QRhi::OpenGLES2) { + // Get a copy of the texture on the RHI device + ComPtr<ID3D11Texture2D> output = m_bridge.copyFromSharedTex(m_rhiDevice, m_rhiCtx); + if (!output) + return nullptr; + + return new D3D11TextureSet(rhi, std::move(output)); } return nullptr; @@ -163,17 +273,28 @@ TextureSet *D3D11TextureConverter::getTextures(AVFrame *frame) void D3D11TextureConverter::SetupDecoderTextures(AVCodecContext *s) { - int ret = avcodec_get_hw_frames_parameters(s, - s->hw_device_ctx, - AV_PIX_FMT_D3D11, + // We are holding pool frames alive for quite long, which may cause + // codecs to run out of frames because FFmpeg has a fixed size + // decoder frame pool. We must therefore add extra frames to the pool + // to account for the frames we keep alive. First, we need to account + // for the maximum number of queued frames during rendering. In + // addition, we add one frame for the RHI rendering pipeline, and one + // additional frame because we may hold one in the Qt event loop. + + const qint32 maxRenderQueueSize = StreamDecoder::maxQueueSize(QPlatformMediaPlayer::VideoStream); + constexpr qint32 framesHeldByRhi = 1; + constexpr qint32 framesHeldByQtEventLoop = 1; + s->extra_hw_frames = maxRenderQueueSize + framesHeldByRhi + framesHeldByQtEventLoop; + + int ret = avcodec_get_hw_frames_parameters(s, s->hw_device_ctx, AV_PIX_FMT_D3D11, &s->hw_frames_ctx); if (ret < 0) { qCDebug(qLcMediaFFmpegHWAccel) << "Failed to allocate HW frames context" << ret; return; } - auto *frames_ctx = (AVHWFramesContext *)s->hw_frames_ctx->data; - auto *hwctx = (AVD3D11VAFramesContext *)frames_ctx->hwctx; + const auto *frames_ctx = reinterpret_cast<const AVHWFramesContext *>(s->hw_frames_ctx->data); + auto *hwctx = static_cast<AVD3D11VAFramesContext *>(frames_ctx->hwctx); hwctx->MiscFlags = D3D11_RESOURCE_MISC_SHARED; hwctx->BindFlags = D3D11_BIND_DECODER | D3D11_BIND_SHADER_RESOURCE; ret = av_hwframe_ctx_init(s->hw_frames_ctx); @@ -183,6 +304,6 @@ void D3D11TextureConverter::SetupDecoderTextures(AVCodecContext *s) } } -} +} // namespace QFFmpeg QT_END_NAMESPACE |