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