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
qvideotexturehelper.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
6#include <QtMultimedia/private/qvideoframetexturefromsource_p.h>
7#include <QtMultimedia/private/qvideoframe_p.h>
8#include <QtMultimedia/private/qvideoframeconverter_p.h>
9#include <QtMultimedia/private/qmultimediautils_p.h>
10#include <QtMultimedia/private/qtmm_alignment_support_p.h>
11
12#include <QtGui/qpainter.h>
13
14#include <QtCore/qfile.h>
15#include <QtCore/qloggingcategory.h>
16#include <QtCore/qoperatingsystemversion.h>
17
18#include <optional>
19
21
22Q_STATIC_LOGGING_CATEGORY(qLcVideoTextureHelper, "qt.multimedia.video.texturehelper")
23
24namespace QVideoTextureHelper
25{
26
27using namespace Qt::Literals;
28
29namespace {
30
31bool &glTextureExternalOesSetting()
32{
33 static bool isSet =
34 qEnvironmentVariableIntValue("QT_MULTIMEDIA_FORCE_GL_TEXTURE_EXTERNAL_OES") != 0;
35 return isSet;
36}
37
38} // namespace
39
41{
42 return glTextureExternalOesSetting();
43}
44
46{
47 glTextureExternalOesSetting() = isSet;
48}
49
51 // Format_Invalid
52 { 0, 0,
53 [](int, int) { return 0; },
55 { { 1, 1 }, { 1, 1 }, { 1, 1 } }
56 },
57 // Format_ARGB8888
58 { 1, 4,
59 [](int stride, int height) { return stride*height; },
61 { { 1, 1 }, { 1, 1 }, { 1, 1 } }
62 },
63 // Format_ARGB8888_Premultiplied
64 { 1, 4,
65 [](int stride, int height) { return stride*height; },
67 { { 1, 1 }, { 1, 1 }, { 1, 1 } }
68 },
69 // Format_XRGB8888
70 { 1, 4,
71 [](int stride, int height) { return stride*height; },
73 { { 1, 1 }, { 1, 1 }, { 1, 1 } }
74 },
75 // Format_BGRA8888
76 { 1, 4,
77 [](int stride, int height) { return stride*height; },
79 { { 1, 1 }, { 1, 1 }, { 1, 1 } }
80 },
81 // Format_BGRA8888_Premultiplied
82 { 1, 4,
83 [](int stride, int height) { return stride*height; },
85 { { 1, 1 }, { 1, 1 }, { 1, 1 } }
86 },
87 // Format_BGRX8888
88 { 1, 4,
89 [](int stride, int height) { return stride*height; },
91 { { 1, 1 }, { 1, 1 }, { 1, 1 } }
92 },
93 // Format_ABGR8888
94 { 1, 4,
95 [](int stride, int height) { return stride*height; },
97 { { 1, 1 }, { 1, 1 }, { 1, 1 } }
98 },
99 // Format_XBGR8888
100 { 1, 4,
101 [](int stride, int height) { return stride*height; },
103 { { 1, 1 }, { 1, 1 }, { 1, 1 } }
104 },
105 // Format_RGBA8888
106 { 1, 4,
107 [](int stride, int height) { return stride*height; },
109 { { 1, 1 }, { 1, 1 }, { 1, 1 } }
110 },
111 // Format_RGBX8888
112 { 1, 4,
113 [](int stride, int height) { return stride*height; },
115 { { 1, 1 }, { 1, 1 }, { 1, 1 } }
116 },
117 // Format_AYUV
118 { 1, 4,
119 [](int stride, int height) { return stride*height; },
121 { { 1, 1 }, { 1, 1 }, { 1, 1 } }
122 },
123 // Format_AYUV_Premultiplied
124 { 1, 4,
125 [](int stride, int height) { return stride*height; },
127 { { 1, 1 }, { 1, 1 }, { 1, 1 } }
128 },
129 // Format_YUV420P
130 { 3, 1,
131 [](int stride, int height) { return stride * (height + QtMultimediaPrivate::alignUp(height, 2) / 2); },
133 { { 1, 1 }, { 2, 2 }, { 2, 2 } }
134 },
135 // Format_YUV422P
136 { 3, 1,
137 [](int stride, int height) { return stride * height * 2; },
139 { { 1, 1 }, { 2, 1 }, { 2, 1 } }
140 },
141 // Format_YV12
142 { 3, 1,
143 [](int stride, int height) { return stride * (height + QtMultimediaPrivate::alignUp(height, 2) / 2); },
145 { { 1, 1 }, { 2, 2 }, { 2, 2 } }
146 },
147 // Format_UYVY
148 { 1, 2,
149 [](int stride, int height) { return stride*height; },
151 { { 2, 1 }, { 1, 1 }, { 1, 1 } }
152 },
153 // Format_YUYV
154 { 1, 2,
155 [](int stride, int height) { return stride*height; },
157 { { 2, 1 }, { 1, 1 }, { 1, 1 } }
158 },
159 // Format_NV12
160 { 2, 1,
161 [](int stride, int height) { return stride * ((height * 3 / 2 + 1) & ~1); },
163 { { 1, 1 }, { 2, 2 }, { 1, 1 } }
164 },
165 // Format_NV21
166 { 2, 1,
167 [](int stride, int height) { return stride * ((height * 3 / 2 + 1) & ~1); },
169 { { 1, 1 }, { 2, 2 }, { 1, 1 } }
170 },
171 // Format_IMC1
172 { 3, 1,
173 [](int stride, int height) {
174 // IMC1 requires that U and V components are aligned on a multiple of 16 lines
175 int h = (height + 15) & ~15;
176 h += 2*(((h/2) + 15) & ~15);
177 return stride * h;
178 },
180 { { 1, 1 }, { 2, 2 }, { 2, 2 } }
181 },
182 // Format_IMC2
183 { 2, 1,
184 [](int stride, int height) { return 2*stride*height; },
186 { { 1, 1 }, { 1, 2 }, { 1, 1 } }
187 },
188 // Format_IMC3
189 { 3, 1,
190 [](int stride, int height) {
191 // IMC3 requires that U and V components are aligned on a multiple of 16 lines
192 int h = (height + 15) & ~15;
193 h += 2*(((h/2) + 15) & ~15);
194 return stride * h;
195 },
197 { { 1, 1 }, { 2, 2 }, { 2, 2 } }
198 },
199 // Format_IMC4
200 { 2, 1,
201 [](int stride, int height) { return 2*stride*height; },
203 { { 1, 1 }, { 1, 2 }, { 1, 1 } }
204 },
205 // Format_Y8
206 { 1, 1,
207 [](int stride, int height) { return stride*height; },
209 { { 1, 1 }, { 1, 1 }, { 1, 1 } }
210 },
211 // Format_Y16
212 { 1, 2,
213 [](int stride, int height) { return stride*height; },
215 { { 1, 1 }, { 1, 1 }, { 1, 1 } }
216 },
217 // Format_P010
218 { 2, 2,
219 [](int stride, int height) { return stride * ((height * 3 / 2 + 1) & ~1); },
221 { { 1, 1 }, { 2, 2 }, { 1, 1 } }
222 },
223 // Format_P016
224 { 2, 2,
225 [](int stride, int height) { return stride * ((height * 3 / 2 + 1) & ~1); },
227 { { 1, 1 }, { 2, 2 }, { 1, 1 } }
228 },
229 // Format_SamplerExternalOES
230 {
231 1, 0,
232 [](int, int) { return 0; },
234 { { 1, 1 }, { 1, 1 }, { 1, 1 } }
235 },
236 // Format_Jpeg
237 { 1, 4,
238 [](int stride, int height) { return stride*height; },
240 { { 1, 1 }, { 1, 1 }, { 1, 1 } }
241 },
242 // Format_SamplerRect
243 {
244 1, 0,
245 [](int, int) { return 0; },
247 { { 1, 1 }, { 1, 1 }, { 1, 1 } }
248 },
249 // Format_YUV420P10
250 { 3, 2,
251 [](int stride, int height) { return stride * (height + QtMultimediaPrivate::alignUp(height, 2) / 2); },
253 { { 1, 1 }, { 2, 2 }, { 2, 2 } }
254 },
255};
256
257Q_GLOBAL_STATIC(QList<QRhiTexture::Format>, g_excludedRhiTextureFormats) // for tests only
258
259static bool isRhiTextureFormatSupported(const QRhi *rhi, QRhiTexture::Format format)
260{
261 if (g_excludedRhiTextureFormats->contains(format))
262 return false;
263 if (!rhi) // consider the format is supported if no rhi specified
264 return true;
265 return rhi->isTextureFormatSupported(format);
266}
267
301
302QRhiTexture::Format resolvedRhiTextureFormat(QRhiTexture::Format format, QRhi *rhi)
303{
304 if (isRhiTextureFormatSupported(rhi, format))
305 return format;
306
307 QRhiTexture::Format fallbackFormat;
308 switch (format) {
309 case QRhiTexture::R8:
310 fallbackFormat = resolvedRhiTextureFormat(QRhiTexture::RED_OR_ALPHA8, rhi);
311 break;
312 case QRhiTexture::RG8:
313 case QRhiTexture::RG16:
314 fallbackFormat = resolvedRhiTextureFormat(QRhiTexture::RGBA8, rhi);
315 break;
316 case QRhiTexture::R16:
317 fallbackFormat = resolvedRhiTextureFormat(QRhiTexture::RG8, rhi);
318 break;
319 case QRhiTexture::BGRA8:
320 // WebGL 2 and GLES without EXT_texture_format_BGRA8888 cannot take a BGRA
321 // texture. Upload the data as RGBA and let the shader swizzle instead.
322 fallbackFormat = resolvedRhiTextureFormat(QRhiTexture::RGBA8, rhi);
323 break;
324 default:
325 // End fallback chain here, and return UnknownFormat
326 return QRhiTexture::UnknownFormat;
327 }
328
329 if (fallbackFormat == QRhiTexture::UnknownFormat) {
330 // TODO: QTBUG-135911: In some cases rhi claims format and fallbacks are all
331 // unsupported, but when using preferred format video plays fine
332 qCDebug(qLcVideoTextureHelper) << "Cannot determine any usable texture format, using preferred format" << format;
333 return format;
334 }
335
336 qCDebug(qLcVideoTextureHelper) << "Using fallback texture format" << fallbackFormat;
337 return fallbackFormat;
338}
339
340void setExcludedRhiTextureFormats(QList<QRhiTexture::Format> formats)
341{
342 g_excludedRhiTextureFormats->swap(formats);
343}
344
345const TextureDescription *textureDescription(QVideoFrameFormat::PixelFormat format)
346{
347 return descriptions + format;
348}
349
350QString vertexShaderFileName(const QVideoFrameFormat &format)
351{
352 switch (format.pixelFormat()) {
353 case QVideoFrameFormat::Format_SamplerExternalOES:
354 return u":/qt-project.org/multimedia/shaders/externalsampler.vert.qsb"_s;
355 case QVideoFrameFormat::Format_SamplerRect:
356 return u":/qt-project.org/multimedia/shaders/rectsampler.vert.qsb"_s;
357 default:
358 return u":/qt-project.org/multimedia/shaders/vertex.vert.qsb"_s;
359 }
360}
361
362QString fragmentShaderFileName(const QVideoFrameFormat &format, QRhi *rhi,
363 QRhiSwapChain::Format surfaceFormat)
364{
365 QString shaderFile;
366 switch (format.pixelFormat()) {
367 case QVideoFrameFormat::Format_Y8:
368 shaderFile = QStringLiteral("y");
369 break;
370 case QVideoFrameFormat::Format_Y16:
371 shaderFile = QStringLiteral("y16");
372 break;
373 case QVideoFrameFormat::Format_AYUV:
374 case QVideoFrameFormat::Format_AYUV_Premultiplied:
375 shaderFile = QStringLiteral("ayuv");
376 break;
377 case QVideoFrameFormat::Format_ARGB8888:
378 case QVideoFrameFormat::Format_ARGB8888_Premultiplied:
379 case QVideoFrameFormat::Format_XRGB8888:
380 shaderFile = QStringLiteral("argb");
381 break;
382 case QVideoFrameFormat::Format_ABGR8888:
383 case QVideoFrameFormat::Format_XBGR8888:
384 shaderFile = QStringLiteral("abgr");
385 break;
386 case QVideoFrameFormat::Format_Jpeg: // Jpeg is decoded transparently into an ARGB texture
387 shaderFile = QStringLiteral("bgra");
388 break;
389 case QVideoFrameFormat::Format_RGBA8888:
390 case QVideoFrameFormat::Format_RGBX8888:
391 shaderFile = QStringLiteral("rgba");
392 break;
393 case QVideoFrameFormat::Format_BGRA8888:
394 case QVideoFrameFormat::Format_BGRA8888_Premultiplied:
395 case QVideoFrameFormat::Format_BGRX8888:
396 // A BGRA texture has the channels reordered by the driver already. Where the
397 // format is unsupported and the data went into an RGBA texture instead, the
398 // shader has to do the reordering.
399 shaderFile = !isRhiTextureFormatSupported(rhi, QRhiTexture::BGRA8)
400 ? QStringLiteral("bgra")
401 : QStringLiteral("rgba");
402 break;
403 case QVideoFrameFormat::Format_YUV420P:
404 case QVideoFrameFormat::Format_YUV422P:
405 case QVideoFrameFormat::Format_IMC3:
406 shaderFile = QStringLiteral("yuv_triplanar");
407 break;
408 case QVideoFrameFormat::Format_YUV420P10:
409 shaderFile = QStringLiteral("yuv_triplanar_p10");
410 break;
411 case QVideoFrameFormat::Format_YV12:
412 case QVideoFrameFormat::Format_IMC1:
413 shaderFile = QStringLiteral("yvu_triplanar");
414 break;
415 case QVideoFrameFormat::Format_IMC2:
416 shaderFile = QStringLiteral("imc2");
417 break;
418 case QVideoFrameFormat::Format_IMC4:
419 shaderFile = QStringLiteral("imc4");
420 break;
421 case QVideoFrameFormat::Format_UYVY:
422 shaderFile = QStringLiteral("uyvy");
423 break;
424 case QVideoFrameFormat::Format_YUYV:
425 shaderFile = QStringLiteral("yuyv");
426 break;
427 case QVideoFrameFormat::Format_P010:
428 case QVideoFrameFormat::Format_P016:
429 // P010/P016 have the same layout as NV12, just 16 instead of 8 bits per pixel
430 switch (format.colorTransfer()) {
431 case QVideoFrameFormat::ColorTransfer_ST2084:
432 shaderFile = QStringLiteral("nv12_bt2020_pq");
433 break;
434 case QVideoFrameFormat::ColorTransfer_STD_B67:
435 shaderFile = QStringLiteral("nv12_bt2020_hlg");
436 break;
437 default:
438 shaderFile = QStringLiteral("p016");
439 break;
440 }
441 break;
442 case QVideoFrameFormat::Format_NV12:
443 shaderFile = QStringLiteral("nv12");
444 break;
445 case QVideoFrameFormat::Format_NV21:
446 shaderFile = QStringLiteral("nv21");
447 break;
448 case QVideoFrameFormat::Format_SamplerExternalOES:
449 shaderFile = QStringLiteral("externalsampler");
450 break;
451 case QVideoFrameFormat::Format_SamplerRect:
452 shaderFile = QStringLiteral("rectsampler_bgra");
453 break;
454 case QVideoFrameFormat::Format_Invalid:
455 default:
456 break;
457 }
458
459 if (shaderFile.isEmpty())
460 return QString();
461
462 shaderFile.prepend(u":/qt-project.org/multimedia/shaders/");
463
464 if (surfaceFormat == QRhiSwapChain::HDRExtendedSrgbLinear)
465 shaderFile.append(u"_linear");
466
467 shaderFile.append(u".frag.qsb");
468
469 Q_ASSERT_X(
470 QFile::exists(shaderFile), Q_FUNC_INFO,
471 QStringLiteral("Shader file %1 does not exist").arg(shaderFile).toLatin1().constData());
472 qCDebug(qLcVideoTextureHelper) << "fragmentShaderFileName returns" << shaderFile;
473 return shaderFile;
474}
475
476// Matrices are calculated from
477// https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.601-7-201103-I!!PDF-E.pdf
478// https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.709-6-201506-I!!PDF-E.pdf
479// https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2020-2-201510-I!!PDF-E.pdf
480//
481// For BT2020, we also need to convert the Rec2020 RGB colorspace to sRGB see
482// shaders/colorconvert.glsl for details.
483//
484// Doing the math gives the following (Y, U & V normalized to [0..1] range):
485//
486// Y = a*R + b*G + c*B
487// R = Y + e*V
488// G = Y - c*d/b*U - a*e/b*V
489// B = Y + d*U
490
491// BT2020:
492// a = .2627, b = 0.6780, c = 0.0593
493// d = 1.8814
494// e = 1.4746
495//
496// BT709:
497// a = 0.2126, b = 0.7152, c = 0.0722
498// d = 1.8556
499// e = 1.5748
500//
501// BT601:
502// a = 0.299, b = 0.578, c = 0.114
503// d = 1.42
504// e = 1.772
505//
506
507// clang-format off
508static QMatrix4x4 colorConversionMatrix(const QVideoFrameFormat &format)
509{
510 auto colorSpace = format.colorSpace();
511 if (colorSpace == QVideoFrameFormat::ColorSpace_Undefined) {
512 if (format.frameHeight() > 576)
513 // HD video, assume BT709
514 colorSpace = QVideoFrameFormat::ColorSpace_BT709;
515 else
516 // SD video, assume BT601
517 colorSpace = QVideoFrameFormat::ColorSpace_BT601;
518 }
519 switch (colorSpace) {
520 case QVideoFrameFormat::ColorSpace_AdobeRgb:
521 return {
522 1.0f, 0.000f, 1.402f, -0.701f,
523 1.0f, -0.344f, -0.714f, 0.529f,
524 1.0f, 1.772f, 0.000f, -0.886f,
525 0.0f, 0.000f, 0.000f, 1.000f
526 };
527 default:
528 case QVideoFrameFormat::ColorSpace_BT709:
529 if (format.colorRange() == QVideoFrameFormat::ColorRange_Full)
530 return {
531 1.0f, 0.0f, 1.5748f, -0.790488f,
532 1.0f, -0.187324f, -0.468124f, 0.329010f,
533 1.0f, 1.855600f, 0.0f, -0.931439f,
534 0.0f, 0.0f, 0.0f, 1.0f
535 };
536 return {
537 1.1644f, 0.0000f, 1.7927f, -0.9729f,
538 1.1644f, -0.2132f, -0.5329f, 0.3015f,
539 1.1644f, 2.1124f, 0.0000f, -1.1334f,
540 0.0000f, 0.0000f, 0.0000f, 1.0000f
541 };
542 case QVideoFrameFormat::ColorSpace_BT2020:
543 if (format.colorRange() == QVideoFrameFormat::ColorRange_Full)
544 return {
545 1.f, 0.0000f, 1.4746f, -0.7402f,
546 1.f, -0.1646f, -0.5714f, 0.3694f,
547 1.f, 1.8814f, 0.000f, -0.9445f,
548 0.0f, 0.0000f, 0.000f, 1.0000f
549 };
550 return {
551 1.1644f, 0.000f, 1.6787f, -0.9157f,
552 1.1644f, -0.1874f, -0.6504f, 0.3475f,
553 1.1644f, 2.1418f, 0.0000f, -1.1483f,
554 0.0000f, 0.0000f, 0.0000f, 1.0000f
555 };
556 case QVideoFrameFormat::ColorSpace_BT601:
557 // Corresponds to the primaries used by NTSC BT601. For PAL BT601, we use the BT709 conversion
558 // as those are very close.
559 if (format.colorRange() == QVideoFrameFormat::ColorRange_Full)
560 return {
561 1.f, 0.000f, 1.772f, -0.886f,
562 1.f, -0.1646f, -0.57135f, 0.36795f,
563 1.f, 1.42f, 0.000f, -0.71f,
564 0.0f, 0.000f, 0.000f, 1.0000f
565 };
566 return {
567 1.164f, 0.000f, 1.596f, -0.8708f,
568 1.164f, -0.392f, -0.813f, 0.5296f,
569 1.164f, 2.017f, 0.000f, -1.0810f,
570 0.000f, 0.000f, 0.000f, 1.0000f
571 };
572 }
573}
574// clang-format on
575
576// PQ transfer function, see also https://en.wikipedia.org/wiki/Perceptual_quantizer
577// or https://ieeexplore.ieee.org/document/7291452
578static float convertPQFromLinear(float sig)
579{
580 const float m1 = 1305.f/8192.f;
581 const float m2 = 2523.f/32.f;
582 const float c1 = 107.f/128.f;
583 const float c2 = 2413.f/128.f;
584 const float c3 = 2392.f/128.f;
585
586 const float SDR_LEVEL = 100.f;
587 sig *= SDR_LEVEL/10000.f;
588 float psig = powf(sig, m1);
589 float num = c1 + c2*psig;
590 float den = 1 + c3*psig;
591 return powf(num/den, m2);
592}
593
594float convertHLGFromLinear(float sig)
595{
596 const float a = 0.17883277f;
597 const float b = 0.28466892f; // = 1 - 4a
598 const float c = 0.55991073f; // = 0.5 - a ln(4a)
599
600 if (sig < 1.f/12.f)
601 return sqrtf(3.f*sig);
602 return a*logf(12.f*sig - b) + c;
603}
604
605static float convertSDRFromLinear(float sig)
606{
607 return sig;
608}
609
610void updateUniformData(QByteArray *dst, QRhi *rhi, const QVideoFrameFormat &format,
611 const QVideoFrame &frame, const QMatrix4x4 &transform, float opacity,
612 float maxNits)
613{
614 QMatrix4x4 cmat;
615 switch (format.pixelFormat()) {
616 case QVideoFrameFormat::Format_Invalid:
617 return;
618
619 case QVideoFrameFormat::Format_ARGB8888:
620 case QVideoFrameFormat::Format_ARGB8888_Premultiplied:
621 case QVideoFrameFormat::Format_XRGB8888:
622 case QVideoFrameFormat::Format_BGRA8888:
623 case QVideoFrameFormat::Format_BGRA8888_Premultiplied:
624 case QVideoFrameFormat::Format_BGRX8888:
625 case QVideoFrameFormat::Format_ABGR8888:
626 case QVideoFrameFormat::Format_XBGR8888:
627 case QVideoFrameFormat::Format_RGBA8888:
628 case QVideoFrameFormat::Format_RGBX8888: {
629 if (format.colorRange() == QVideoFrameFormat::ColorRange_Video) {
630 constexpr float scale = 255.0f / 219.0f; // (255 - 0) / (235 - 16)
631 constexpr float offset = -16.0f / 219.0f; // -16 / (235 - 16)
632 // clang-format off
633 cmat = QMatrix4x4 {
634 scale, 0.f, 0.f, offset,
635 0.f, scale, 0.f, offset,
636 0.f, 0.f, scale, offset,
637 0.f, 0.f, 0.f, 1.f,
638 };
639 // clang-format on
640 }
641
642 break;
643 }
644
645 case QVideoFrameFormat::Format_Jpeg:
646 case QVideoFrameFormat::Format_Y8:
647 case QVideoFrameFormat::Format_Y16:
648 break;
649 case QVideoFrameFormat::Format_IMC1:
650 case QVideoFrameFormat::Format_IMC2:
651 case QVideoFrameFormat::Format_IMC3:
652 case QVideoFrameFormat::Format_IMC4:
653 case QVideoFrameFormat::Format_AYUV:
654 case QVideoFrameFormat::Format_AYUV_Premultiplied:
655 case QVideoFrameFormat::Format_YUV420P:
656 case QVideoFrameFormat::Format_YUV420P10:
657 case QVideoFrameFormat::Format_YUV422P:
658 case QVideoFrameFormat::Format_YV12:
659 case QVideoFrameFormat::Format_UYVY:
660 case QVideoFrameFormat::Format_YUYV:
661 case QVideoFrameFormat::Format_NV12:
662 case QVideoFrameFormat::Format_NV21:
663 case QVideoFrameFormat::Format_P010:
664 case QVideoFrameFormat::Format_P016:
665 cmat = colorConversionMatrix(format);
666 break;
667 case QVideoFrameFormat::Format_SamplerExternalOES:
668 // get Android specific transform for the externalsampler texture
669 if (auto hwBuffer = QVideoFramePrivate::hwBuffer(frame))
670 cmat = hwBuffer->externalTextureMatrix();
671 break;
672 case QVideoFrameFormat::Format_SamplerRect:
673 {
674 // Similarly to SamplerExternalOES, the "color matrix" is used here to
675 // transform the texture coordinates. OpenGL texture rectangles expect
676 // non-normalized UVs, so apply a scale to have the fragment shader see
677 // UVs in range [width,height] instead of [0,1].
678 const QSize videoSize = frame.size();
679 cmat.scale(videoSize.width(), videoSize.height());
680 }
681 break;
682 }
683
684 // HDR with a PQ or HLG transfer function uses a BT2390 based tone mapping to cut off the HDR peaks
685 // This requires that we pass the max luminance the tonemapper should clip to over to the fragment
686 // shader. To reduce computations there, it's precomputed in PQ values here.
687 auto fromLinear = convertSDRFromLinear;
688 switch (format.colorTransfer()) {
689 case QVideoFrameFormat::ColorTransfer_ST2084:
690 fromLinear = convertPQFromLinear;
691 break;
692 case QVideoFrameFormat::ColorTransfer_STD_B67:
693 fromLinear = convertHLGFromLinear;
694 break;
695 default:
696 break;
697 }
698
699 if (dst->size() < qsizetype(sizeof(UniformData)))
700 dst->resize(sizeof(UniformData));
701
702 auto ud = reinterpret_cast<UniformData*>(dst->data());
703 memcpy(ud->transformMatrix, transform.constData(), sizeof(ud->transformMatrix));
704 memcpy(ud->colorMatrix, cmat.constData(), sizeof(ud->transformMatrix));
705 ud->opacity = opacity;
706 ud->width = float(format.frameWidth());
707 ud->masteringWhite = fromLinear(float(format.maxLuminance())/100.f);
708 ud->maxLum = fromLinear(float(maxNits)/100.f);
709 const TextureDescription* desc = textureDescription(format.pixelFormat());
710
711 const bool isDmaBuf = QVideoFramePrivate::hasDmaBuf(frame);
712 using FallbackPolicy = QVideoTextureHelper::TextureDescription::FallbackPolicy;
713 auto fallbackPolicy = isDmaBuf
714 ? FallbackPolicy::Disable
715 : FallbackPolicy::Enable;
716
717 // Let's consider using the red component if Red_8 is not used,
718 // it's useful for compatibility the shaders with 16bit formats.
719
720 const bool useRedComponent =
721 !desc->hasTextureFormat(TextureDescription::Red_8)
722 || isRhiTextureFormatSupported(rhi, QRhiTexture::R8)
723 || rhi->isFeatureSupported(QRhi::RedOrAlpha8IsRed)
724 || isDmaBuf;
725 ud->redOrAlphaIndex = useRedComponent ? 0 : 3; // r:0 g:1 b:2 a:3
726
727 for (int plane = 0; plane < desc->nplanes; ++plane)
728 ud->planeFormats[plane] = desc->rhiTextureFormat(plane, rhi, fallbackPolicy);
729}
730
731namespace {
732
733QRhiTexture::Flags textureFlagsFor(QVideoFrameFormat::PixelFormat pixelFormat, QRhi &rhi)
734{
735 switch (pixelFormat) {
736 case QVideoFrameFormat::Format_SamplerExternalOES:
737 if (rhi.backend() == QRhi::OpenGLES2)
738 return QRhiTexture::ExternalOES;
739 break;
740 case QVideoFrameFormat::Format_SamplerRect:
741 if constexpr (QOperatingSystemVersion::currentType() == QOperatingSystemVersion::MacOS) {
742 if (rhi.backend() == QRhi::OpenGLES2)
743 return QRhiTexture::TextureRectangleGL;
744 }
745 break;
746 default:
747 break;
748 }
749 return {};
750}
751
752std::unique_ptr<QRhiTexture> enqueuePlaneTextureUpload(std::unique_ptr<QRhiTexture> tex,
753 const QVideoFrame &frame, QRhi &rhi,
754 QRhiResourceUpdateBatch &rub, int plane)
755{
756 Q_ASSERT(frame.isMapped());
757
758 QVideoFrameFormat fmt = frame.surfaceFormat();
759 QVideoFrameFormat::PixelFormat pixelFormat = fmt.pixelFormat();
760 QSize size = fmt.frameSize();
761
762 const TextureDescription &texDesc = descriptions[pixelFormat];
763 QSize planeSize = texDesc.rhiPlaneSize(size, plane, &rhi);
764 QRhiTexture::Format format = texDesc.rhiTextureFormat(plane, &rhi);
765
766 bool needsRebuild = !tex || tex->pixelSize() != planeSize || tex->format() != format;
767 if (!tex) {
768 tex.reset(rhi.newTexture(format, planeSize, 1, {}));
769 if (!tex) {
770 qWarning("Failed to create new texture (size %dx%d)", planeSize.width(), planeSize.height());
771 return nullptr;
772 }
773 }
774
775 if (needsRebuild) {
776 tex->setFormat(format);
777 tex->setPixelSize(planeSize);
778 if (!tex->create()) {
779 qWarning("Failed to create texture (size %dx%d)", planeSize.width(), planeSize.height());
780 return nullptr;
781 }
782 }
783
784 QRhiTextureSubresourceUploadDescription subresDesc;
785
786 if (pixelFormat == QVideoFrameFormat::Format_Jpeg) {
787 Q_ASSERT(plane == 0);
788
789 QImage image;
790
791 // calling QVideoFrame::toImage is not accurate. To be fixed.
792 // frame transformation will be considered later
793 const QVideoFrameFormat surfaceFormat = frame.surfaceFormat();
794
795 const bool hasSurfaceTransform = surfaceFormat.isMirrored()
796 || surfaceFormat.scanLineDirection() == QVideoFrameFormat::BottomToTop
797 || surfaceFormat.rotation() != QtVideo::Rotation::None;
798
799 if (hasSurfaceTransform)
800 image = qImageFromVideoFrame(frame, VideoTransformation{});
801 else
802 image = frame.toImage(); // use the frame cache, no surface transforms applied
803
804 image.convertTo(QImage::Format_ARGB32);
805 subresDesc.setImage(image);
806
807 } else {
808 // Note, QByteArray::fromRawData creare QByteArray as a view without data copying
809 subresDesc.setData(QByteArray::fromRawData(
810 reinterpret_cast<const char *>(frame.bits(plane)), frame.mappedBytes(plane)));
811 subresDesc.setDataStride(frame.bytesPerLine(plane));
812 }
813
814 QRhiTextureUploadDescription desc({
815 QRhiTextureUploadEntry(0, 0, subresDesc),
816 });
817 rub.uploadTexture(tex.get(), desc);
818
819 return tex;
820}
821
822std::unique_ptr<QRhiTexture> createTextureFromHandle(QVideoFrameTexturesHandles &texturesSet,
823 QRhi &rhi,
824 QVideoFrameFormat::PixelFormat pixelFormat,
825 QSize size, int plane)
826{
827 const TextureDescription &texDesc = descriptions[pixelFormat];
828 QSize planeSize = texDesc.rhiPlaneSize(size, plane, &rhi);
829
830 if (quint64 handle = texturesSet.textureHandle(rhi, plane); handle) {
831 std::unique_ptr<QRhiTexture> tex(rhi.newTexture(texDesc.rhiTextureFormat(plane, &rhi),
832 planeSize, 1,
833 textureFlagsFor(pixelFormat, rhi)));
834 if (tex->createFrom({ handle, 0 }))
835 return tex;
836
837 qWarning("Failed to initialize QRhiTexture wrapper for native texture object %llu", handle);
838 }
839 return {};
840}
841
842std::optional<RhiTextureArray> createTextureWrappers(QVideoFrameTexturesHandles &texturesSet,
843 QRhi &rhi,
844 QVideoFrameFormat::PixelFormat pixelFormat,
845 QSize size)
846{
847 const QVideoFrameFormat::PixelFormat texturesFormat =
848 texturesSet.texturePixelFormat().value_or(pixelFormat);
849 const TextureDescription &texDesc = descriptions[texturesFormat];
850 RhiTextureArray textures;
851 for (int plane = 0; plane < texDesc.nplanes; ++plane) {
852 textures[plane] = QVideoTextureHelper::createTextureFromHandle(texturesSet, rhi,
853 texturesFormat, size, plane);
854 if (!textures[plane])
855 return std::nullopt;
856 }
857 return std::optional<RhiTextureArray>(std::move(textures));
858}
859
860QVideoFrameTexturesUPtr makeBorrowedTextures(QRhi &rhi, QVideoFrameTexturesHandles &texturesSet,
861 QVideoFrameFormat::PixelFormat pixelFormat, QSize size,
862 QVideoFrame sourceFrame)
863{
864 auto wrappers = createTextureWrappers(texturesSet, rhi, pixelFormat, size);
865 if (!wrappers)
866 return {};
867 return std::make_unique<QVideoFrameTexturesFromRhiTextureArray>(std::move(*wrappers),
868 std::move(sourceFrame));
869}
870
871QVideoFrameTexturesUPtr makeOwnedTextures(QRhi &rhi, QVideoFrameTexturesHandlesUPtr handles,
872 QVideoFrameFormat::PixelFormat pixelFormat, QSize size,
873 QVideoFrame sourceFrame)
874{
875 auto wrappers = createTextureWrappers(*handles, rhi, pixelFormat, size);
876 if (!wrappers)
877 return {};
878 return std::make_unique<QVideoFrameTexturesFromHandlesSet>(
879 std::move(*wrappers), std::move(handles), std::move(sourceFrame));
880}
881
882} // namespace
883
884QVideoFrameTexturesUPtr createTexturesFromHandles(QVideoFrameTexturesHandlesUPtr texturesSet,
885 QRhi &rhi,
886 QVideoFrameFormat::PixelFormat pixelFormat,
887 QSize size, QVideoFrame sourceFrame)
888{
889 if (!texturesSet)
890 return nullptr;
891
892 if (pixelFormat == QVideoFrameFormat::Format_Invalid)
893 return nullptr;
894
895 if (size.isEmpty())
896 return nullptr;
897
898 return makeOwnedTextures(rhi, std::move(texturesSet), pixelFormat, size,
899 std::move(sourceFrame));
900}
901
903tryRecycleMemory(QVideoFrameTexturesUPtr oldTextures)
904{
905 auto *raw = oldTextures.release();
906 auto *rawFromMemory = dynamic_cast<QVideoFrameTexturesFromMemory *>(raw);
907 if (!rawFromMemory) {
908 delete raw;
909 return nullptr;
910 }
911 return std::unique_ptr<QVideoFrameTexturesFromMemory>(rawFromMemory);
912}
913
914static QVideoFrameTexturesUPtr createTexturesFromMemory(QVideoFrame frame, QRhi &rhi,
915 QRhiResourceUpdateBatch &rub,
916 QVideoFrameTexturesUPtr oldTextures)
917{
918 qCDebug(qLcVideoTextureHelper) << "createTexturesFromMemory, pixelFormat:" << frame.pixelFormat();
919 if (!frame.map(QVideoFrame::ReadOnly)) {
920 qWarning() << "Cannot map a video frame in ReadOnly mode!";
921 return {};
922 }
923
924 auto unmapFrameGuard = qScopeGuard([&] {
925 frame.unmap();
926 });
927
928 const TextureDescription &texDesc = descriptions[frame.surfaceFormat().pixelFormat()];
929
930 std::optional<RhiTextureArray> recycledArray;
931 if (auto recycled = tryRecycleMemory(std::move(oldTextures)))
932 recycledArray = std::move(*recycled).takeTextureArray();
933
934 RhiTextureArray textureArray;
935 for (quint8 plane = 0; plane < texDesc.nplanes; ++plane) {
936 std::unique_ptr<QRhiTexture> slot =
937 recycledArray ? std::move((*recycledArray)[plane]) : nullptr;
938
939 auto planeTexture = enqueuePlaneTextureUpload(std::move(slot), frame, rhi, rub, plane);
940 if (!planeTexture)
941 return {};
942 textureArray[plane] = std::move(planeTexture);
943 }
944
945 unmapFrameGuard.dismiss();
946 return std::make_unique<QVideoFrameTexturesFromMemory>(std::move(textureArray),
947 std::move(frame));
948}
949
950QVideoFrameTexturesUPtr createTextures(const QVideoFrame &frame, QRhi &rhi,
951 QRhiResourceUpdateBatch &rub,
952 QVideoFrameTexturesUPtr &oldTextures)
953{
954 if (!frame.isValid())
955 return {};
956
957 if (QHwVideoBuffer *hwBuffer = QVideoFramePrivate::hwBuffer(frame)) {
958 if (auto textures = hwBuffer->mapTextures(rhi, oldTextures))
959 return textures;
960
961 QVideoFrameFormat format = frame.surfaceFormat();
962 if (auto textures = makeBorrowedTextures(rhi, *hwBuffer, format.pixelFormat(),
963 format.frameSize(), frame))
964 return textures;
965 }
966
967 return createTexturesFromMemory(frame, rhi, rub, std::move(oldTextures));
968}
969
971{
973 if (layout.text() == text && videoSize == frameSize)
974 return false;
975
977 QFont font;
978 // 0.045 - based on this https://www.md-subs.com/saa-subtitle-font-size
979 qreal fontSize = frameSize.height() * 0.045;
981
983 if (text.isEmpty()) {
984 bounds = {};
985 return true;
986 }
992
994 int leading = metrics.leading();
995
997 qreal margin = videoSize.width()*.05;
998 qreal height = 0;
999 qreal textWidth = 0;
1001 while (1) {
1003 if (!line.isValid())
1004 break;
1005
1007 height += leading;
1009 height += line.height();
1011 }
1012 layout.endLayout();
1013
1014 // put subtitles vertically in lower part of the video but not stuck to the bottom
1015 int bottomMargin = videoSize.height() / 20;
1018 textWidth += fontSize/4.;
1019
1021 return true;
1022}
1023
1043
1045{
1046 auto size = bounds.size().toSize();
1047 if (size.isEmpty())
1048 return QImage();
1051 bgColor.setAlpha(128);
1052 img.fill(bgColor);
1053
1057 range.start = 0;
1058 range.length = layout.text().size();
1060 layout.draw(&painter, {}, { range });
1061 return img;
1062}
1063
1064} // namespace QVideoTextureHelper
1065
1066QT_END_NAMESPACE
QRhiTexture::Format resolvedRhiTextureFormat(QRhiTexture::Format format, QRhi *rhi)
Returns a QRhiTexture::Format taking into account rhi capabilities and explicitly excluded formats us...
float convertHLGFromLinear(float sig)
static float convertPQFromLinear(float sig)
static float convertSDRFromLinear(float sig)
void setExcludedRhiTextureFormats(QList< QRhiTexture::Format > formats)
static std::unique_ptr< QVideoFrameTexturesFromMemory > tryRecycleMemory(QVideoFrameTexturesUPtr oldTextures)
QVideoFrameTexturesUPtr createTextures(const QVideoFrame &frame, QRhi &rhi, QRhiResourceUpdateBatch &rub, QVideoFrameTexturesUPtr &oldTextures)
Creates plane textures from a video frame by the specified rhi. If possible, the function modifies 'o...
QString fragmentShaderFileName(const QVideoFrameFormat &format, QRhi *rhi, QRhiSwapChain::Format surfaceFormat)
const TextureDescription * textureDescription(QVideoFrameFormat::PixelFormat format)
static bool isRhiTextureFormatSupported(const QRhi *rhi, QRhiTexture::Format format)
static QVideoFrameTexturesUPtr createTexturesFromMemory(QVideoFrame frame, QRhi &rhi, QRhiResourceUpdateBatch &rub, QVideoFrameTexturesUPtr oldTextures)
QString vertexShaderFileName(const QVideoFrameFormat &format)
static const TextureDescription descriptions[QVideoFrameFormat::NPixelFormats]
void updateUniformData(QByteArray *dst, QRhi *rhi, const QVideoFrameFormat &format, const QVideoFrame &frame, const QMatrix4x4 &transform, float opacity, float maxNits)
static QMatrix4x4 colorConversionMatrix(const QVideoFrameFormat &format)
QVideoFrameTexturesUPtr createTexturesFromHandles(QVideoFrameTexturesHandlesUPtr texturesSet, QRhi &rhi, QVideoFrameFormat::PixelFormat pixelFormat, QSize size, QVideoFrame sourceFrame)
Creates plane textures from texture handles set by the specified rhi. The result owns the specified h...
void setForceGlTextureExternalOesIsSet(bool isSet)
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)