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