Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qffmpeghwaccel_vaapi.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
7
8#include <QtMultimedia/private/qmultimedia_drm_support_p.h>
9#include <QtMultimedia/private/qvideotexturehelper_p.h>
10
11#include <QtGui/qguiapplication.h>
12#include <QtGui/qopenglfunctions.h>
13#include <QtGui/qpa/qplatformnativeinterface.h>
14
15#include <QtCore/qloggingcategory.h>
16
17extern "C" {
18#include <libavutil/hwcontext_vaapi.h>
19}
20
21#include <va/va.h>
22#include <va/va_drmcommon.h>
23
24#include <EGL/egl.h>
25#include <EGL/eglext.h>
26
27#include <unistd.h>
28
29static_assert(QT_CONFIG(vaapi));
30
32
33Q_STATIC_LOGGING_CATEGORY(qLHWAccelVAAPI, "qt.multimedia.ffmpeg.hwaccelvaapi");
34
35namespace QFFmpeg {
36
38
39constexpr bool VAExportUseLayers = false;
40
41namespace {
42class VAAPITextureHandles : public QVideoFrameTexturesHandles
43{
44public:
45 ~VAAPITextureHandles() override;
46 quint64 textureHandle(QRhi &, int plane) override {
47 return textures[plane];
48 }
49
50 TextureConverterBackendPtr parentConverterBackend; // ensures the backend is deleted after the texture
51 QRhi *rhi = nullptr;
52 QOpenGLContext *glContext = nullptr;
53 int nPlanes = 0;
54 std::array<GLuint, 4> textures = {};
55};
56} // namespace
57
60{
61 qCDebug(qLHWAccelVAAPI) << ">>>> Creating VAAPI HW accelerator";
62
63 if (!rhi || rhi->backend() != QRhi::OpenGLES2) {
64 qWarning() << "VAAPITextureConverter: No rhi or non openGL based RHI";
65 this->rhi = nullptr;
66 return;
67 }
68
69 auto *nativeHandles = static_cast<const QRhiGles2NativeHandles *>(rhi->nativeHandles());
70 glContext = nativeHandles->context;
71 if (!glContext) {
72 qCDebug(qLHWAccelVAAPI) << " no GL context, disabling";
73 return;
74 }
75
76 QPlatformNativeInterface *pni = QGuiApplication::platformNativeInterface();
77 eglDisplay = pni->nativeResourceForIntegration(QByteArrayLiteral("egldisplay"));
78 qCDebug(qLHWAccelVAAPI) << " platform is" << QGuiApplication::platformName() << eglDisplay;
79 if (!eglDisplay) {
80 qCDebug(qLHWAccelVAAPI) << " no egl display, disabling";
81 return;
82 }
83
84 eglImageTargetTexture2D = eglGetProcAddress("glEGLImageTargetTexture2DOES");
85 if (!eglImageTargetTexture2D) {
86 qCDebug(qLHWAccelVAAPI) << " no eglImageTargetTexture2D, disabling";
87 return;
88 }
89
90 // everything ok, indicate that we can do zero copy
91 this->rhi = rhi;
92}
93
95
98 QVideoFrameTexturesHandlesUPtr /*oldHandles*/)
99{
100 // qCDebug(qLHWAccelVAAPI) << "VAAPIAccel::createTextureHandles";
101 if (frame->format != AV_PIX_FMT_VAAPI || !eglDisplay) {
102 qCDebug(qLHWAccelVAAPI) << "format/egl error" << frame->format << eglDisplay;
103 return nullptr;
104 }
105
106 if (!frame->hw_frames_ctx)
107 return nullptr;
108
109 auto *ctx = avFrameDeviceContext(frame);
110 if (!ctx)
111 return nullptr;
112
113 auto *vaCtx = (AVVAAPIDeviceContext *)ctx->hwctx;
114 auto vaDisplay = vaCtx->display;
115 if (!vaDisplay) {
116 qCDebug(qLHWAccelVAAPI) << " no VADisplay, disabling";
117 return nullptr;
118 }
119
120 VASurfaceID vaSurface = (uintptr_t)frame->data[3];
121
122 VADRMPRIMESurfaceDescriptor prime = {};
123 if (vaExportSurfaceHandle(vaDisplay, vaSurface, VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
124 VA_EXPORT_SURFACE_READ_ONLY
125 | (VAExportUseLayers ? VA_EXPORT_SURFACE_SEPARATE_LAYERS
126 : VA_EXPORT_SURFACE_COMPOSED_LAYERS),
127 &prime)
128 != VA_STATUS_SUCCESS) {
129 qWarning() << "vaExportSurfaceHandle failed";
130 return nullptr;
131 }
132
133 // Make sure all fd's in 'prime' are closed when we return from this function
134 QScopeGuard closeObjectsGuard([&prime]() {
135 for (uint32_t i = 0; i < prime.num_objects; ++i)
136 close(prime.objects[i].fd);
137 });
138
139 // ### Check that prime.fourcc is what we expect
140 vaSyncSurface(vaDisplay, vaSurface);
141
142// qCDebug(qLHWAccelVAAPI) << "VAAPIAccel: vaSufraceDesc: width/height" << prime.width << prime.height << "num objects"
143// << prime.num_objects << "num layers" << prime.num_layers;
144
145 QOpenGLFunctions functions(glContext);
146
147 AVPixelFormat fmt = HWAccel::format(frame);
148 bool needsConversion;
149 auto qtFormat = QFFmpegVideoBuffer::toQtPixelFormat(fmt, &needsConversion);
150 QSpan<const DRMFormat> drm_formats = QtMultimediaPrivate::dmaBufFourccFromPixelFormat(qtFormat);
151 if (drm_formats.empty() || needsConversion) {
152 qWarning() << "can't use DMA transfer for pixel format" << fmt << qtFormat;
153 return nullptr;
154 }
155
156 auto *desc = QVideoTextureHelper::textureDescription(qtFormat);
157 int nPlanes = int(drm_formats.size());
158 Q_ASSERT(nPlanes == desc->nplanes);
159 nPlanes = desc->nplanes;
160// qCDebug(qLHWAccelVAAPI) << "VAAPIAccel: nPlanes" << nPlanes;
161
162 rhi->makeThreadLocalNativeContextCurrent();
163
164 std::array<EGLImage, 4> images = {};
165 std::array<GLuint, 4> glTextures = {};
166 functions.glGenTextures(nPlanes, glTextures.data());
167
168 auto releaseTextures = qScopeGuard([&] {
169 for (EGLImage img : images) {
170 if (img)
171 eglDestroyImage(eglDisplay, img);
172 }
173 functions.glDeleteTextures(nPlanes, glTextures.data());
174 });
175
176 for (int i = 0; i < nPlanes; ++i) {
177 const int layer = VAExportUseLayers ? i : 0;
178 const int plane = VAExportUseLayers ? 0 : i;
179
180 if constexpr (VAExportUseLayers) {
181 if (prime.layers[i].drm_format != quint32(drm_formats[i])) {
182 qWarning() << "expected DRM format check failed expected" << Qt::hex
183 << quint32(drm_formats[i]) << "got" << prime.layers[i].drm_format;
184 }
185 }
186
187 QSize planeSize = desc->rhiPlaneSize(QSize(frame->width, frame->height), i, rhi);
188 constexpr uint32_t maxAttrCount = 18;
189 EGLAttrib img_attr[maxAttrCount] = {
190 EGL_LINUX_DRM_FOURCC_EXT, EGLint(drm_formats[i]),
191 EGL_WIDTH, planeSize.width(),
192 EGL_HEIGHT, planeSize.height(),
193 EGL_DMA_BUF_PLANE0_FD_EXT, prime.objects[prime.layers[layer].object_index[plane]].fd,
194 EGL_DMA_BUF_PLANE0_OFFSET_EXT, (EGLint)prime.layers[layer].offset[plane],
195 EGL_DMA_BUF_PLANE0_PITCH_EXT, (EGLint)prime.layers[layer].pitch[plane],
196 };
197 uint32_t img_attr_idx = 12;
198 uint64_t modifier = prime.objects[prime.layers[layer].object_index[plane]].drm_format_modifier;
199 if (modifier != QtMultimediaPrivate::DmaBufFormatModifierInvalid) {
200 img_attr[img_attr_idx++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT;
201 img_attr[img_attr_idx++] = modifier & 0xFFFFFFFF;
202 img_attr[img_attr_idx++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT;
203 img_attr[img_attr_idx++] = modifier >> 32;
204 }
205 img_attr[img_attr_idx++] = EGL_NONE;
206 Q_ASSERT(img_attr_idx <= maxAttrCount);
207 images[i] = eglCreateImage(eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, img_attr);
208 if (!images[i]) {
209 const GLenum error = eglGetError();
210 if (error == EGL_BAD_MATCH) {
211 qWarning() << "eglCreateImage failed for plane" << i << "with error code EGL_BAD_MATCH, "
212 "disabling hardware acceleration. This could indicate an EGL implementation issue."
213 "\nVAAPI driver: " << vaQueryVendorString(vaDisplay)
214 << "\nEGL vendor:" << eglQueryString(eglDisplay, EGL_VENDOR);
215 this->rhi = nullptr; // Disabling texture conversion here to fix QTBUG-112312
216 return nullptr;
217 }
218 if (error) {
219 qWarning() << "eglCreateImage failed for plane" << i << "with error code" << error;
220 return nullptr;
221 }
222 }
223 functions.glActiveTexture(GL_TEXTURE0 + i);
224 functions.glBindTexture(GL_TEXTURE_2D, glTextures[i]);
225
226 PFNGLEGLIMAGETARGETTEXTURE2DOESPROC eglImageTargetTexture2D = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)this->eglImageTargetTexture2D;
227 eglImageTargetTexture2D(GL_TEXTURE_2D, images[i]);
228 GLenum error = glGetError();
229 if (error)
230 qWarning() << "eglImageTargetTexture2D failed with error code" << error;
231 }
232
233 releaseTextures.dismiss();
234
235 for (int i = 0; i < nPlanes; ++i) {
236 functions.glActiveTexture(GL_TEXTURE0 + i);
237 functions.glBindTexture(GL_TEXTURE_2D, 0);
238 eglDestroyImage(eglDisplay, images[i]);
239 }
240
241 auto textureHandles = std::make_unique<VAAPITextureHandles>();
242 textureHandles->parentConverterBackend = shared_from_this();
243 textureHandles->nPlanes = nPlanes;
244 textureHandles->rhi = rhi;
245 textureHandles->glContext = glContext;
246 textureHandles->textures = glTextures;
247 // qCDebug(qLHWAccelVAAPI) << "VAAPIAccel: got textures" << textures[0] << textures[1] << textures[2] << textures[3];
248
249 return textureHandles;
250}
251
252VAAPITextureHandles::~VAAPITextureHandles()
253{
254 if (rhi) {
255 rhi->makeThreadLocalNativeContextCurrent();
256 QOpenGLFunctions functions(glContext);
257 functions.glDeleteTextures(nPlanes, textures.data());
258 }
259}
260
261} // namespace QFFmpeg
262
263QT_END_NAMESPACE
QVideoFrameTexturesHandlesUPtr createTextureHandles(AVFrame *frame, QVideoFrameTexturesHandlesUPtr oldHandles) override
QT_MANGLE_NAMESPACE(QMacScreenCaptureStreamDelegate) QMacScreenCaptureStreamDelegate
constexpr bool VAExportUseLayers
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)