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