summaryrefslogtreecommitdiffstats
path: root/src/plugins/multimedia/ffmpeg/qffmpeghwaccel_d3d11.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/multimedia/ffmpeg/qffmpeghwaccel_d3d11.cpp')
-rw-r--r--src/plugins/multimedia/ffmpeg/qffmpeghwaccel_d3d11.cpp353
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(&currentDesc);
+
+ 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