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