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
qsgvideonode_p.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QtMultimedia/private/qhwvideobuffer_p.h>
7#include <QtMultimedia/private/qvideoframetexturepool_p.h>
8#include <QtMultimedia/private/qvideotexturehelper_p.h>
9
10#include <QtMultimediaQuick/private/qsgvideotexture_p.h>
11#include <QtMultimediaQuick/private/qquickvideooutput_p.h>
12
13#include <QtQuick/qsgmaterial.h>
14#include <QtQuick/private/qquickitem_p.h>
15#include <QtQuick/private/qsginternaltextnode_p.h>
16
17#if QT_CONFIG(opengles2)
18#include <QtGui/private/qshaderdescription_p.h>
19#endif
20
22
23/* Helpers */
24static inline void qSetGeom(QSGGeometry::TexturedPoint2D *v, const QPointF &p)
25{
26 v->x = p.x();
27 v->y = p.y();
28}
29
30static inline void qSetTex(QSGGeometry::TexturedPoint2D *v, const QPointF &p)
31{
32 v->tx = p.x();
33 v->ty = p.y();
34}
35
36static inline void qSwapTex(QSGGeometry::TexturedPoint2D *v0, QSGGeometry::TexturedPoint2D *v1)
37{
38 auto tvx = v0->tx;
39 auto tvy = v0->ty;
40 v0->tx = v1->tx;
41 v0->ty = v1->ty;
42 v1->tx = tvx;
43 v1->ty = tvy;
44}
45
46class QSGVideoMaterial;
47
49{
50public:
51 QSGVideoMaterialRhiShader(const QVideoFrameFormat &videoFormat,
52 const QRhiSwapChain::Format surfaceFormat,
53 const QRhiSwapChainHdrInfo &hdrInfo,
54 QRhi *rhi)
58 {
59 setShaderFileName(VertexStage, QVideoTextureHelper::vertexShaderFileName(m_videoFormat));
60 if (QVideoTextureHelper::forceGlTextureExternalOesIsSet()
61 && rhi && rhi->backend() == QRhi::OpenGLES2)
63 else
64 setShaderFileName(FragmentStage, QVideoTextureHelper::fragmentShaderFileName(
65 m_videoFormat, rhi, m_surfaceFormat));
66 }
67
69
70 bool updateUniformData(RenderState &state, QSGMaterial *newMaterial,
71 QSGMaterial *oldMaterial) override;
72
73 void updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
74 QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
75
76protected:
80};
81
83{
84#if QT_CONFIG(opengles2)
85 qDebug() << "QSGVideoMaterialRhiShader: Setting up external OES shader for OpenGLES2";
86
87 using namespace Qt::Literals::StringLiterals;
88 QByteArray fragmentShader = R"(
89 #extension GL_OES_EGL_image_external : require
90 precision highp float;
91 varying vec2 texCoord;
92 uniform samplerExternalOES tex0;
93 void main()
94 {
95 gl_FragColor = texture2D(tex0, texCoord);
96 }
97 )"_ba;
98
99 QShaderDescription desc;
100 QShaderDescriptionPrivate *descData = QShaderDescriptionPrivate::get(&desc);
101
102 QShaderDescription::InOutVariable texCoordInput;
103 texCoordInput.name = "texCoord";
104 texCoordInput.type = QShaderDescription::Vec2;
105 texCoordInput.location = 0;
106
107 descData->inVars = { texCoordInput };
108
109 QShaderDescription::InOutVariable fragColorOutput;
110 fragColorOutput.name = "gl_FragColor";
111 fragColorOutput.type = QShaderDescription::Vec4;
112 fragColorOutput.location = 0;
113
114 descData->outVars = { fragColorOutput };
115
116 QShaderDescription::InOutVariable samplerTex0;
117 samplerTex0.name = "tex0";
118 samplerTex0.type = QShaderDescription::SamplerExternalOES;
119 samplerTex0.binding = 1;
120
121 descData->combinedImageSamplers = { samplerTex0 };
122
123 QShader shaderPack;
124 shaderPack.setStage(QShader::FragmentStage);
125 shaderPack.setDescription(desc);
126 shaderPack.setShader(QShaderKey(QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs)),
127 QShaderCode(fragmentShader));
128
129 setShader(FragmentStage, shaderPack);
130#endif
131}
132
133class QSGVideoMaterial final : public QSGMaterial
134{
135public:
136 QSGVideoMaterial(const QVideoFrameFormat &videoFormat, QRhi *rhi);
137
138 [[nodiscard]] QSGMaterialType *type() const override {
139 static constexpr int NFormats = QRhiSwapChain::HDRExtendedDisplayP3Linear + 1;
140 static QSGMaterialType type[QVideoFrameFormat::NPixelFormats][NFormats];
141 return &type[m_videoFormat.pixelFormat()][m_surfaceFormat];
142 }
143
145 return new QSGVideoMaterialRhiShader(m_videoFormat, m_surfaceFormat, m_hdrInfo, m_rhi);
146 }
147
148 int compare(const QSGMaterial *other) const override {
149 const QSGVideoMaterial *m = static_cast<const QSGVideoMaterial *>(other);
150
151 qint64 diff = m_textures[0].comparisonKey() - m->m_textures[0].comparisonKey();
152 if (!diff)
153 diff = m_textures[1].comparisonKey() - m->m_textures[1].comparisonKey();
154 if (!diff)
155 diff = m_textures[2].comparisonKey() - m->m_textures[2].comparisonKey();
156
157 return diff < 0 ? -1 : (diff > 0 ? 1 : 0);
158 }
159
161 // ### respect video formats with Alpha
162 setFlag(Blending, !QtPrivate::fuzzyCompare(m_opacity, float(1.0)));
163 }
164
165 void setSurfaceFormat(const QRhiSwapChain::Format surfaceFormat)
166 {
167 m_surfaceFormat = surfaceFormat;
168 }
169
170 void setHdrInfo(const QRhiSwapChainHdrInfo &hdrInfo)
171 {
172 m_hdrInfo = hdrInfo;
173 }
174
175 void updateTextures(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates);
176
178 QRhiSwapChain::Format m_surfaceFormat = QRhiSwapChain::SDR;
179 float m_opacity = 1.0f;
181
182 QVideoFrameTexturePoolPtr m_texturePool = std::make_shared<QVideoFrameTexturePool>();
184
185 QRhi *const m_rhi;
186};
187
188void QSGVideoMaterial::updateTextures(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates)
189{
190 if (!m_texturePool->texturesDirty())
191 return;
192
193 QVideoFrameTextures *textures = m_texturePool->updateTextures(*rhi, *resourceUpdates);
194 if (!textures)
195 return;
196
197 for (int plane = 0; plane < 3; ++plane)
198 m_textures[plane].setRhiTexture(textures->texture(plane));
199}
200
201
202bool QSGVideoMaterialRhiShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial,
203 QSGMaterial *oldMaterial)
204{
205 Q_UNUSED(oldMaterial);
206
207 auto m = static_cast<QSGVideoMaterial *>(newMaterial);
208
209 if (!state.isMatrixDirty() && !state.isOpacityDirty())
210 return false;
211
212 if (state.isOpacityDirty()) {
213 m->m_opacity = state.opacity();
214 m->updateBlending();
215 }
216
217 // Do this here, not in updateSampledImage. First, with multiple textures we want to
218 // do this once. More importantly, on some platforms (Android) the externalMatrix is
219 // updated by this function and we need that already in updateUniformData.
220 m->updateTextures(state.rhi(), state.resourceUpdateBatch());
221
222 float maxNits = 100; // Default to de-facto SDR nits
223 if (m_surfaceFormat == QRhiSwapChain::HDRExtendedSrgbLinear) {
224 if (m_hdrInfo.limitsType == QRhiSwapChainHdrInfo::ColorComponentValue)
225 maxNits = 100 * m_hdrInfo.limits.colorComponentValue.maxColorComponentValue;
226 else
227 maxNits = m_hdrInfo.limits.luminanceInNits.maxLuminance;
228 }
229
230 QVideoTextureHelper::updateUniformData(state.uniformData(), m->m_rhi, m_videoFormat,
231 m->m_texturePool->currentFrame(), state.combinedMatrix(),
232 state.opacity(), maxNits);
233
234 return true;
235}
236
237void QSGVideoMaterialRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
238 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
239{
240 Q_UNUSED(state);
241 Q_UNUSED(oldMaterial);
242 if (binding < 1 || binding > 3)
243 return;
244
245 auto m = static_cast<QSGVideoMaterial *>(newMaterial);
246 *texture = &m->m_textures[binding - 1];
247}
248
249QSGVideoMaterial::QSGVideoMaterial(const QVideoFrameFormat &videoFormat, QRhi *rhi)
251 m_rhi(rhi)
252{
253 setFlag(Blending, false);
254}
255
256QSGVideoNode::QSGVideoNode(QQuickVideoOutput *parent, const QVideoFrameFormat &videoFormat,
257 QRhi *rhi)
258 : m_parent(parent),
260 m_material{
261 new QSGVideoMaterial(videoFormat, rhi),
262 }
263{
264 setFlag(QSGNode::OwnsMaterial);
265 setFlag(QSGNode::OwnsGeometry);
266 setMaterial(m_material);
267}
268
269QSGVideoNode::~QSGVideoNode()
270{
271 delete m_subtitleTextNode;
272}
273
274void QSGVideoNode::setCurrentFrame(const QVideoFrame &frame)
275{
276 texturePool()->setCurrentFrame(frame);
277 markDirty(DirtyMaterial);
278 updateSubtitle(frame);
279}
280
281void QSGVideoNode::setSurfaceFormat(const QRhiSwapChain::Format surfaceFormat)
282{
283 m_material->setSurfaceFormat(surfaceFormat);
284 markDirty(DirtyMaterial);
285}
286
287void QSGVideoNode::setHdrInfo(const QRhiSwapChainHdrInfo &hdrInfo)
288{
289 m_material->setHdrInfo(hdrInfo);
290 markDirty(DirtyMaterial);
291}
292
293void QSGVideoNode::updateSubtitle(const QVideoFrame &frame)
294{
295 QSize subtitleFrameSize = m_rect.size().toSize();
296 if (subtitleFrameSize.isEmpty())
297 return;
298
299 subtitleFrameSize = qRotatedFrameSize(subtitleFrameSize, m_videoOutputTransformation.rotation);
300
301 if (!m_subtitleLayout.update(subtitleFrameSize, frame.subtitleText()))
302 return;
303
304 delete m_subtitleTextNode;
305 m_subtitleTextNode = nullptr;
306 if (frame.subtitleText().isEmpty())
307 return;
308
309 QQuickItemPrivate *parent_d = QQuickItemPrivate::get(m_parent);
310
311 m_subtitleTextNode = parent_d->sceneGraphContext()->createInternalTextNode(parent_d->sceneGraphRenderContext());
312 m_subtitleTextNode->setColor(Qt::white);
313 QColor bgColor = Qt::black;
314 bgColor.setAlpha(128);
315 m_subtitleTextNode->addRectangleNode(m_subtitleLayout.bounds, bgColor, nullptr);
316 m_subtitleTextNode->addTextLayout(m_subtitleLayout.layout.position(),
317 &m_subtitleLayout.layout, nullptr);
318 appendChildNode(m_subtitleTextNode);
319 setSubtitleGeometry();
320}
321
322void QSGVideoNode::setSubtitleGeometry()
323{
324 if (!m_subtitleTextNode)
325 return;
326
327 if (m_material)
328 updateSubtitle(texturePool()->currentFrame());
329
330 float rotate = -1.f * qToUnderlying(m_videoOutputTransformation.rotation);
331 float yTranslate = 0;
332 float xTranslate = 0;
333 if (m_videoOutputTransformation.rotation == QtVideo::Rotation::Clockwise90) {
334 yTranslate = m_rect.height();
335 } else if (m_videoOutputTransformation.rotation == QtVideo::Rotation::Clockwise180) {
336 yTranslate = m_rect.height();
337 xTranslate = m_rect.width();
338 } else if (m_videoOutputTransformation.rotation == QtVideo::Rotation::Clockwise270) {
339 xTranslate = m_rect.width();
340 }
341
342 QMatrix4x4 transform;
343 transform.translate(m_rect.x() + xTranslate, m_rect.y() + yTranslate);
344 transform.rotate(rotate, 0, 0, 1);
345 // TODO: Investigate if we should we mirror subtitles
346 // if (m_videoOutputTransformation.mirroredHorizontallyAfterRotation)
347 // transform.scale(-1.f, 1.f);
348
349 m_subtitleTextNode->setMatrix(transform);
350 m_subtitleTextNode->markDirty(DirtyGeometry);
351}
352
353/* Update the vertices and texture coordinates.*/
354void QSGVideoNode::setTexturedRectGeometry(const QRectF &rect, const QRectF &textureRect,
355 VideoTransformation videoOutputTransformation)
356{
357 const VideoTransformation currentFrameTransformation = qNormalizedFrameTransformation(
358 m_material ? texturePool()->currentFrame() : QVideoFrame{}, videoOutputTransformation);
359
360 if (rect == m_rect && textureRect == m_textureRect
361 && videoOutputTransformation == m_videoOutputTransformation
362 && currentFrameTransformation == m_frameTransformation)
363 return;
364
365 m_rect = rect;
366 m_textureRect = textureRect;
367 m_videoOutputTransformation = videoOutputTransformation;
368 m_frameTransformation = currentFrameTransformation;
369
370 QSGGeometry *g = geometry();
371
372 if (g == nullptr)
373 g = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4);
374
375 QSGGeometry::TexturedPoint2D *v = g->vertexDataAsTexturedPoint2D();
376
377 // Vertexes:
378 // 0 2
379 //
380 // 1 3
381
382 // Set geometry first
383 qSetGeom(v + 0, rect.topLeft());
384 qSetGeom(v + 1, rect.bottomLeft());
385 qSetGeom(v + 2, rect.topRight());
386 qSetGeom(v + 3, rect.bottomRight());
387
388 // and then texture coordinates
389 switch (currentFrameTransformation.rotation) {
390 default:
391 // tl, bl, tr, br
392 qSetTex(v + 0, textureRect.topLeft());
393 qSetTex(v + 1, textureRect.bottomLeft());
394 qSetTex(v + 2, textureRect.topRight());
395 qSetTex(v + 3, textureRect.bottomRight());
396 break;
397
398 case QtVideo::Rotation::Clockwise90:
399 // bl, br, tl, tr
400 qSetTex(v + 0, textureRect.bottomLeft());
401 qSetTex(v + 1, textureRect.bottomRight());
402 qSetTex(v + 2, textureRect.topLeft());
403 qSetTex(v + 3, textureRect.topRight());
404 break;
405
406 case QtVideo::Rotation::Clockwise180:
407 // br, tr, bl, tl
408 qSetTex(v + 0, textureRect.bottomRight());
409 qSetTex(v + 1, textureRect.topRight());
410 qSetTex(v + 2, textureRect.bottomLeft());
411 qSetTex(v + 3, textureRect.topLeft());
412 break;
413
414 case QtVideo::Rotation::Clockwise270:
415 // tr, tl, br, bl
416 qSetTex(v + 0, textureRect.topRight());
417 qSetTex(v + 1, textureRect.topLeft());
418 qSetTex(v + 2, textureRect.bottomRight());
419 qSetTex(v + 3, textureRect.bottomLeft());
420 break;
421 }
422
423 if (m_frameTransformation.mirroredHorizontallyAfterRotation) {
424 qSwapTex(v + 0, v + 2);
425 qSwapTex(v + 1, v + 3);
426 }
427
428 if (!geometry())
429 setGeometry(g);
430
431 markDirty(DirtyGeometry);
432
433 setSubtitleGeometry();
434}
435
436const QVideoFrameTexturePoolPtr &QSGVideoNode::texturePool() const
437{
438 return m_material->m_texturePool;
439}
440
441QT_END_NAMESPACE
QVideoFrameFormat m_videoFormat
QRhiSwapChainHdrInfo m_hdrInfo
bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override
This function is called by the scene graph to get the contents of the shader program's uniform buffer...
QSGVideoMaterialRhiShader(const QVideoFrameFormat &videoFormat, const QRhiSwapChain::Format surfaceFormat, const QRhiSwapChainHdrInfo &hdrInfo, QRhi *rhi)
QRhiSwapChain::Format m_surfaceFormat
void updateSampledImage(RenderState &state, int binding, QSGTexture **texture, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override
This function is called by the scene graph to prepare use of sampled images in the shader,...
QVideoFrameTexturePoolPtr m_texturePool
QSGMaterialShader * createShader(QSGRendererInterface::RenderMode) const override
This function returns a new instance of a the QSGMaterialShader implementation used to render geometr...
QSGMaterialType * type() const override
This function is called by the scene graph to query an identifier that is unique to the QSGMaterialSh...
void updateTextures(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates)
void setHdrInfo(const QRhiSwapChainHdrInfo &hdrInfo)
int compare(const QSGMaterial *other) const override
Compares this material to other and returns 0 if they are equal; -1 if this material should sort befo...
QRhiSwapChainHdrInfo m_hdrInfo
std::array< QSGVideoTexture, 3 > m_textures
const QVideoFrameFormat m_videoFormat
void setSurfaceFormat(const QRhiSwapChain::Format surfaceFormat)
QSGVideoMaterial(const QVideoFrameFormat &videoFormat, QRhi *rhi)
QRhiSwapChain::Format m_surfaceFormat
const QVideoFrameTexturePoolPtr & texturePool() const
void setHdrInfo(const QRhiSwapChainHdrInfo &hdrInfo)
void setSurfaceFormat(const QRhiSwapChain::Format surfaceFormat)
QSGVideoNode(QQuickVideoOutput *parent, const QVideoFrameFormat &videoFormat, QRhi *rhi)
~QSGVideoNode() override
void setCurrentFrame(const QVideoFrame &frame)
void setTexturedRectGeometry(const QRectF &boundingRect, const QRectF &textureRect, VideoTransformation videoOutputTransformation)
Combined button and popup list for selecting options.
static void qSetTex(QSGGeometry::TexturedPoint2D *v, const QPointF &p)
static QT_BEGIN_NAMESPACE void qSetGeom(QSGGeometry::TexturedPoint2D *v, const QPointF &p)
static void qSwapTex(QSGGeometry::TexturedPoint2D *v0, QSGGeometry::TexturedPoint2D *v1)