Revision control
Copy as Markdown
// Copyright (C) 2026 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 "qwindowsd3d11videoprocessor_p.h"
#include <QtCore/qloggingcategory.h>
#include <QtCore/private/qsystemerror_p.h>
#include <dxgi1_2.h>
QT_BEGIN_NAMESPACE
Q_STATIC_LOGGING_CATEGORY(lcD3D11VideoProcessor, "qt.multimedia.windows.d3d11videoprocessor");
namespace QtMultimediaPrivate {
namespace {
DXGI_COLOR_SPACE_TYPE toDxgiColorSpace(QVideoFrameFormat::ColorSpace colorSpace,
QVideoFrameFormat::ColorRange colorRange,
QVideoFrameFormat::ColorTransfer colorTransfer)
{
const bool fullRange = colorRange == QVideoFrameFormat::ColorRange_Full;
switch (colorSpace) {
case QVideoFrameFormat::ColorSpace_BT2020:
switch (colorTransfer) {
case QVideoFrameFormat::ColorTransfer_ST2084:
// DXGI defines no full-range YCbCr/PQ/2020 space; HDR10 input is always
// studio-range, tone-mapped to the SDR output space by the processor.
return DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_LEFT_P2020;
case QVideoFrameFormat::ColorTransfer_STD_B67:
return fullRange ? DXGI_COLOR_SPACE_YCBCR_FULL_GHLG_TOPLEFT_P2020
: DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020;
case QVideoFrameFormat::ColorTransfer_Unknown:
case QVideoFrameFormat::ColorTransfer_BT709:
case QVideoFrameFormat::ColorTransfer_BT601:
case QVideoFrameFormat::ColorTransfer_Linear:
case QVideoFrameFormat::ColorTransfer_Gamma22:
case QVideoFrameFormat::ColorTransfer_Gamma28:
// BT.2020 primaries with a gamma transfer are wide-gamut SDR, not HDR.
return fullRange ? DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020
: DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020;
}
Q_UNREACHABLE_RETURN(DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020);
case QVideoFrameFormat::ColorSpace_BT601:
return fullRange ? DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P601
: DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P601;
case QVideoFrameFormat::ColorSpace_BT709:
case QVideoFrameFormat::ColorSpace_AdobeRgb:
case QVideoFrameFormat::ColorSpace_Undefined:
// Fallback for color spaces with no DXGI equivalent (e.g. AdobeRgb).
return fullRange ? DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P709
: DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709;
}
Q_UNREACHABLE_RETURN(DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709);
}
} // namespace
QWindowsD3D11VideoProcessor::QWindowsD3D11VideoProcessor() = default;
QWindowsD3D11VideoProcessor::~QWindowsD3D11VideoProcessor() = default;
bool QWindowsD3D11VideoProcessor::isPixelFormatSupported(QVideoFrameFormat::PixelFormat pixelFormat)
{
// Matches the D3D11VA surface formats in FFmpeg's hwcontext_d3d11va supported_formats[].
switch (pixelFormat) {
case QVideoFrameFormat::Format_NV12:
case QVideoFrameFormat::Format_P010:
return true;
default:
return false;
}
}
bool QWindowsD3D11VideoProcessor::ensureVideoProcessor(ID3D11Device *device, const QSize &frameSize)
{
if (m_videoProcessor && m_processorSize == frameSize)
return true;
m_videoProcessor.Reset();
m_enumerator.Reset();
m_processorSize = {};
if (!m_videoDevice) {
if (FAILED(device->QueryInterface(IID_PPV_ARGS(&m_videoDevice))))
return false;
}
if (!m_videoContext) {
ComPtr<ID3D11DeviceContext> ctx;
device->GetImmediateContext(&ctx);
if (FAILED(ctx.As(&m_videoContext)))
return false;
}
D3D11_VIDEO_PROCESSOR_CONTENT_DESC contentDesc{};
contentDesc.InputFrameFormat = D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE;
contentDesc.InputWidth = static_cast<UINT>(frameSize.width());
contentDesc.InputHeight = static_cast<UINT>(frameSize.height());
contentDesc.OutputWidth = static_cast<UINT>(frameSize.width());
contentDesc.OutputHeight = static_cast<UINT>(frameSize.height());
contentDesc.Usage = D3D11_VIDEO_USAGE_PLAYBACK_NORMAL;
HRESULT hr = m_videoDevice->CreateVideoProcessorEnumerator(&contentDesc, &m_enumerator);
if (FAILED(hr)) {
qCWarning(lcD3D11VideoProcessor)
<< "CreateVideoProcessorEnumerator:" << QSystemError::windowsComString(hr);
return false;
}
hr = m_videoDevice->CreateVideoProcessor(m_enumerator.Get(), 0, &m_videoProcessor);
if (FAILED(hr)) {
qCWarning(lcD3D11VideoProcessor)
<< "CreateVideoProcessor:" << QSystemError::windowsComString(hr);
m_enumerator.Reset();
return false;
}
m_processorSize = frameSize;
return true;
}
bool QWindowsD3D11VideoProcessor::ensureOutputTexture(ID3D11Device *device, const QSize &frameSize)
{
if (m_outputTex) {
D3D11_TEXTURE2D_DESC desc{};
m_outputTex->GetDesc(&desc);
if (desc.Width == static_cast<UINT>(frameSize.width())
&& desc.Height == static_cast<UINT>(frameSize.height()))
return true;
}
m_outputView.Reset();
m_outputTex.Reset();
D3D11_TEXTURE2D_DESC desc{};
desc.Width = static_cast<UINT>(frameSize.width());
desc.Height = static_cast<UINT>(frameSize.height());
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
desc.SampleDesc = { 1, 0 };
desc.Usage = D3D11_USAGE_DEFAULT;
desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
if (FAILED(device->CreateTexture2D(&desc, nullptr, &m_outputTex)))
return false;
D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC outputViewDesc{};
outputViewDesc.ViewDimension = D3D11_VPOV_DIMENSION_TEXTURE2D;
outputViewDesc.Texture2D.MipSlice = 0;
HRESULT hr = m_videoDevice->CreateVideoProcessorOutputView(
m_outputTex.Get(), m_enumerator.Get(), &outputViewDesc, &m_outputView);
if (FAILED(hr)) {
qCWarning(lcD3D11VideoProcessor)
<< "CreateVideoProcessorOutputView:" << QSystemError::windowsComString(hr);
m_outputTex.Reset();
return false;
}
return true;
}
ComPtr<ID3D11Texture2D> QWindowsD3D11VideoProcessor::convertToBGRA(
ID3D11Device *device, ID3D11DeviceContext *context, const ComPtr<ID3D11Texture2D> &texture,
const QSize &frameSize, QVideoFrameFormat::ColorSpace colorSpace,
QVideoFrameFormat::ColorRange colorRange, QVideoFrameFormat::ColorTransfer colorTransfer,
UINT subresourceIndex)
{
Q_ASSERT(device);
Q_ASSERT(context);
Q_ASSERT(texture);
if (frameSize.isEmpty())
return nullptr;
if (!ensureVideoProcessor(device, frameSize))
return nullptr;
if (!ensureOutputTexture(device, frameSize))
return nullptr;
D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC inputViewDesc{};
inputViewDesc.FourCC = 0;
inputViewDesc.ViewDimension = D3D11_VPIV_DIMENSION_TEXTURE2D;
inputViewDesc.Texture2D.MipSlice = 0;
inputViewDesc.Texture2D.ArraySlice = subresourceIndex;
ComPtr<ID3D11VideoProcessorInputView> inputView;
HRESULT hr = m_videoDevice->CreateVideoProcessorInputView(texture.Get(), m_enumerator.Get(),
&inputViewDesc, &inputView);
if (FAILED(hr)) {
qCWarning(lcD3D11VideoProcessor)
<< "CreateVideoProcessorInputView:" << QSystemError::windowsComString(hr);
return nullptr;
}
m_videoContext->VideoProcessorSetStreamColorSpace1(
m_videoProcessor.Get(), 0, toDxgiColorSpace(colorSpace, colorRange, colorTransfer));
m_videoContext->VideoProcessorSetOutputColorSpace1(m_videoProcessor.Get(),
DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709);
D3D11_VIDEO_PROCESSOR_STREAM stream{};
stream.Enable = TRUE;
stream.pInputSurface = inputView.Get();
// ID3D11VideoContext is not thread-safe; the caller must hold the D3D11 device lock.
hr = m_videoContext->VideoProcessorBlt(m_videoProcessor.Get(), m_outputView.Get(), 0, 1,
&stream);
if (FAILED(hr)) {
qCWarning(lcD3D11VideoProcessor)
<< "VideoProcessorBlt:" << QSystemError::windowsComString(hr);
return nullptr;
}
context->Flush();
return m_outputTex;
}
} // namespace QtMultimediaPrivate
QT_END_NAMESPACE