// 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 #include "qffmpegvideobuffer_p.h" #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE namespace { Q_LOGGING_CATEGORY(qLcMediaFFmpegHWAccel, "qt.multimedia.hwaccel"); ComPtr GetD3DDevice(QRhi *rhi) { const auto native = static_cast(rhi->nativeHandles()); if (!native) return {}; const ComPtr rhiDevice = static_cast(native->dev); ComPtr dev1; if (rhiDevice.As(&dev1) != S_OK) return nullptr; return dev1; } } // namespace namespace QFFmpeg { bool TextureBridge::copyToSharedTex(ID3D11Device *dev, ID3D11DeviceContext *ctx, const ComPtr &tex, UINT index) { if (!ensureSrcTex(dev, tex)) 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; ctx->CopySubresourceRegion(m_srcTex.Get(), 0, 0, 0, 0, tex.Get(), index, nullptr); m_srcMutex->ReleaseSync(m_destKey); return true; } ComPtr TextureBridge::copyFromSharedTex(const ComPtr &dev, const ComPtr &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 &dev) { if (m_destTex) return true; if (dev->OpenSharedResource1(m_sharedHandle.get(), IID_PPV_ARGS(&m_destTex)) != S_OK) return false; CD3D11_TEXTURE2D_DESC desc{}; m_destTex->GetDesc(&desc); desc.MiscFlags = 0; desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; if (dev->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 &tex) { if (!isSrcInitialized(dev, tex)) return recreateSrc(dev, tex); return true; } bool TextureBridge::isSrcInitialized(const ID3D11Device *dev, const ComPtr &tex) const { if (!m_srcTex) return false; // Check if device has changed ComPtr texDevice; tex->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; if (inputDesc.Width != currentDesc.Width || inputDesc.Height != currentDesc.Height) return false; return true; } bool TextureBridge::recreateSrc(ID3D11Device *dev, const ComPtr &tex) { m_sharedHandle.close(); CD3D11_TEXTURE2D_DESC desc{}; tex->GetDesc(&desc); CD3D11_TEXTURE2D_DESC texDesc{ desc.Format, desc.Width, desc.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 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(ComPtr &&tex) : m_tex(std::move(tex)) { } qint64 textureHandle(int /*plane*/) override { return reinterpret_cast(m_tex.Get()); } private: ComPtr m_tex; }; D3D11TextureConverter::D3D11TextureConverter(QRhi *rhi) : TextureConverterBackend(rhi), m_rhiDevice{ GetD3DDevice(rhi) } { if (!m_rhiDevice) return; m_rhiDevice->GetImmediateContext(m_rhiCtx.GetAddressOf()); } TextureSet *D3D11TextureConverter::getTextures(AVFrame *frame) { if (!m_rhiDevice) return nullptr; if (!frame || !frame->hw_frames_ctx || frame->format != AV_PIX_FMT_D3D11) return nullptr; const auto *fCtx = reinterpret_cast(frame->hw_frames_ctx->data); const auto *ctx = fCtx->device_ctx; if (!ctx || ctx->type != AV_HWDEVICE_TYPE_D3D11VA) return nullptr; const ComPtr ffmpegTex = reinterpret_cast(frame->data[0]); const int index = static_cast(reinterpret_cast(frame->data[1])); if (rhi->backend() == QRhi::D3D11) { { const auto *avDeviceCtx = static_cast(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 if (!m_bridge.copyToSharedTex(avDeviceCtx->device, avDeviceCtx->device_context, ffmpegTex, index)) { return nullptr; } } // Get a copy of the texture on the RHI device ComPtr output = m_bridge.copyFromSharedTex(m_rhiDevice, m_rhiCtx); if (!output) return nullptr; return new D3D11TextureSet(std::move(output)); } return nullptr; } void D3D11TextureConverter::SetupDecoderTextures(AVCodecContext *s) { 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; } const auto *frames_ctx = reinterpret_cast(s->hw_frames_ctx->data); auto *hwctx = static_cast(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); if (ret < 0) { qCDebug(qLcMediaFFmpegHWAccel) << "Failed to initialize HW frames context" << ret; av_buffer_unref(&s->hw_frames_ctx); } } } // namespace QFFmpeg QT_END_NAMESPACE