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