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
qsgrhilayer.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 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// Qt-Security score:significant reason:default
4
6
7#include <private/qqmlglobal_p.h>
8#include <private/qsgrenderer_p.h>
9
10QSGRhiLayer::QSGRhiLayer(QSGRenderContext *context)
11 : QSGLayer(*(new QSGTexturePrivate(this)))
12 , m_mipmap(false)
13 , m_live(true)
14 , m_recursive(false)
15 , m_dirtyTexture(true)
16 , m_multisampling(false)
17 , m_grab(false)
18 , m_mirrorHorizontal(false)
19 , m_mirrorVertical(true)
20{
21 m_context = static_cast<QSGDefaultRenderContext *>(context);
22 m_rhi = m_context->rhi();
23 Q_ASSERT(m_rhi);
24}
25
26QSGRhiLayer::~QSGRhiLayer()
27{
28 invalidated();
29}
30
31void QSGRhiLayer::invalidated()
32{
33 releaseResources();
34
35 delete m_prevTexture;
36 m_prevTexture = nullptr;
37
38 delete m_renderer;
39 m_renderer = nullptr;
40}
41
42qint64 QSGRhiLayer::comparisonKey() const
43{
44 return qint64(m_texture);
45}
46
47bool QSGRhiLayer::hasAlphaChannel() const
48{
49 return true;
50}
51
52bool QSGRhiLayer::hasMipmaps() const
53{
54 return m_mipmap;
55}
56
57QRhiTexture *QSGRhiLayer::rhiTexture() const
58{
59 return m_prevTexture ? m_prevTexture : m_texture;
60}
61
62void QSGRhiLayer::commitTextureOperations(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates)
63{
64 Q_UNUSED(rhi);
65 Q_UNUSED(resourceUpdates);
66}
67
68bool QSGRhiLayer::updateTexture()
69{
70 // called during the preprocess phase, outside of frame rendering -> good
71
72 bool doGrab = (m_live || m_grab) && m_dirtyTexture;
73 if (doGrab)
74 grab();
75
76 if (m_grab)
77 emit scheduledUpdateCompleted();
78
79 m_grab = false;
80 return doGrab;
81}
82
83void QSGRhiLayer::setHasMipmaps(bool mipmap)
84{
85 if (mipmap == m_mipmap)
86 return;
87
88 m_mipmap = mipmap;
89 if (m_mipmap && m_texture)
90 markDirtyTexture();
91}
92
93
94void QSGRhiLayer::setItem(QSGNode *item)
95{
96 if (item == m_item)
97 return;
98
99 m_item = item;
100
101 if (m_live && !m_item)
102 releaseResources();
103
104 markDirtyTexture();
105}
106
107void QSGRhiLayer::setRect(const QRectF &logicalRect)
108{
109 if (logicalRect == m_logicalRect)
110 return;
111
112 m_logicalRect = logicalRect;
113 markDirtyTexture();
114}
115
116void QSGRhiLayer::setSize(const QSize &pixelSize)
117{
118 if (pixelSize == m_pixelSize)
119 return;
120
121 const int textureSizeMax = m_rhi->resourceLimit(QRhi::TextureSizeMax);
122 m_pixelSize = pixelSize.boundedTo(QSize(textureSizeMax, textureSizeMax));
123
124 if (Q_UNLIKELY(m_pixelSize != pixelSize)) {
125 qWarning("QSGRhiLayer: Unsupported size requested: [%d, %d]. "
126 "Maximum texture size: %d",
127 pixelSize.width(),
128 pixelSize.height(),
129 textureSizeMax);
130 }
131
132 if (m_live && m_pixelSize.isNull())
133 releaseResources();
134
135 markDirtyTexture();
136}
137
138void QSGRhiLayer::setFormat(Format format)
139{
140 QRhiTexture::Format rhiFormat = QRhiTexture::RGBA8;
141 switch (format) {
142 case RGBA16F:
143 rhiFormat = QRhiTexture::RGBA16F;
144 break;
145 case RGBA32F:
146 rhiFormat = QRhiTexture::RGBA32F;
147 break;
148 default:
149 break;
150 }
151
152 if (rhiFormat == m_format)
153 return;
154
155 if (m_rhi->isTextureFormatSupported(rhiFormat)) {
156 m_format = rhiFormat;
157 markDirtyTexture();
158 } else {
159 qWarning("QSGRhiLayer: Attempted to set unsupported texture format %d", int(rhiFormat));
160 }
161}
162
163void QSGRhiLayer::setLive(bool live)
164{
165 if (live == m_live)
166 return;
167
168 m_live = live;
169
170 if (m_live && (!m_item || m_pixelSize.isNull()))
171 releaseResources();
172
173 markDirtyTexture();
174}
175
176void QSGRhiLayer::scheduleUpdate()
177{
178 if (m_grab)
179 return;
180
181 m_grab = true;
182 if (m_dirtyTexture)
183 emit updateRequested();
184}
185
186void QSGRhiLayer::setRecursive(bool recursive)
187{
188 m_recursive = recursive;
189}
190
191void QSGRhiLayer::setMirrorHorizontal(bool mirror)
192{
193 m_mirrorHorizontal = mirror;
194}
195
196void QSGRhiLayer::setMirrorVertical(bool mirror)
197{
198 m_mirrorVertical = mirror;
199}
200
201void QSGRhiLayer::markDirtyTexture()
202{
203 m_dirtyTexture = true;
204 if (m_live || m_grab)
205 emit updateRequested();
206}
207
208void QSGRhiLayer::releaseResources()
209{
210 delete m_rt;
211 m_rt = nullptr;
212
213 delete m_rtRp;
214 m_rtRp = nullptr;
215
216 m_ds.clear();
217
218 delete m_msaaColorBuffer;
219 m_msaaColorBuffer = nullptr;
220
221 if (m_prevTexture != m_texture)
222 delete m_texture;
223
224 m_texture = nullptr;
225
226 delete m_secondaryTexture;
227 m_secondaryTexture = nullptr;
228}
229
230void QSGRhiLayer::clearMainTexture()
231{
232 std::unique_ptr<QRhiTextureRenderTarget> tempRt(m_rhi->newTextureRenderTarget({ m_texture }));
233 std::unique_ptr<QRhiRenderPassDescriptor> tempRp(tempRt->newCompatibleRenderPassDescriptor());
234 tempRt->setRenderPassDescriptor(tempRp.get());
235 if (tempRt->create()) {
236 m_context->currentFrameCommandBuffer()->beginPass(tempRt.get(), Qt::transparent, { 1.0f, 0 });
237 m_context->currentFrameCommandBuffer()->endPass();
238 // Keep the objects alive until a later frame, because the current frame
239 // is only going to be submitted (endFrame) by the scenegraph at a
240 // later point.
241 tempRt.release()->deleteLater();
242 tempRp.release()->deleteLater();
243 } else {
244 qWarning("Failed to clear layer main texture in recursive mode");
245 }
246}
247
248void QSGRhiLayer::grab()
249{
250 if (!m_item || m_pixelSize.isEmpty()) {
251 releaseResources();
252 m_dirtyTexture = false;
253 return;
254 }
255
256 int effectiveSamples = m_samples;
257 // if no layer.samples was provided use the window's msaa setting
258 if (effectiveSamples <= 1)
259 effectiveSamples = m_context->msaaSampleCount();
260
261 const bool needsNewRt = !m_rt || m_rt->pixelSize() != m_pixelSize || (m_recursive && !m_secondaryTexture) || (m_texture && m_texture->format() != m_format);
262 const bool mipmapSettingChanged = m_texture && m_texture->flags().testFlag(QRhiTexture::MipMapped) != m_mipmap;
263 const bool msaaSettingChanged = (effectiveSamples > 1 && !m_msaaColorBuffer) || (effectiveSamples <= 1 && m_msaaColorBuffer);
264
265 if (needsNewRt ||mipmapSettingChanged || msaaSettingChanged) {
266 if (effectiveSamples <= 1) {
267 m_multisampling = false;
268 } else {
269 m_multisampling = m_rhi->isFeatureSupported(QRhi::MultisampleRenderBuffer);
270 if (!m_multisampling)
271 qWarning("Layer requested %d samples but multisample renderbuffers are not supported", effectiveSamples);
272 }
273
274 QRhiTexture::Flags textureFlags = QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource;
275 if (m_mipmap)
276 textureFlags |= QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips;
277
278 // Not the same as m_context->useDepthBufferFor2D(), only the env.var
279 // is to be checked here. Consider a layer with a non-offscreen View3D
280 // in it. That still needs a depth buffer, even when the 2D content
281 // renders without relying on it (i.e. RenderMode2DNoDepthBuffer does
282 // not imply not having a depth/stencil attachment for the render
283 // target! The env.var serves as a hard switch, on the other hand, and
284 // that will likely break 3D for instance but that's fine)
285 static bool depthBufferEnabled = qEnvironmentVariableIsEmpty("QSG_NO_DEPTH_BUFFER");
286
287 if (m_recursive && m_texture) {
288 if (m_prevTexture != m_texture)
289 delete m_prevTexture;
290 m_prevTexture = m_texture;
291 }
292
293 releaseResources();
294
295 if (m_multisampling) {
296 m_msaaColorBuffer = m_rhi->newRenderBuffer(QRhiRenderBuffer::Color, m_pixelSize, effectiveSamples);
297 if (!m_msaaColorBuffer->create()) {
298 qWarning("Failed to build multisample color buffer for layer of size %dx%d, sample count %d",
299 m_pixelSize.width(), m_pixelSize.height(), effectiveSamples);
300 releaseResources();
301 return;
302 }
303 m_texture = m_rhi->newTexture(m_format, m_pixelSize, 1, textureFlags);
304 if (!m_texture->create()) {
305 qWarning("Failed to build texture for layer of size %dx%d", m_pixelSize.width(), m_pixelSize.height());
306 releaseResources();
307 return;
308 }
309 if (depthBufferEnabled) {
310 m_ds = m_context->getDepthStencilBuffer(m_pixelSize, effectiveSamples);
311 if (!m_ds) {
312 releaseResources();
313 return;
314 }
315 }
316 QRhiTextureRenderTargetDescription desc;
317 QRhiColorAttachment color0(m_msaaColorBuffer);
318 if (m_recursive) {
319 m_secondaryTexture = m_rhi->newTexture(m_format, m_pixelSize, 1, textureFlags);
320 if (!m_secondaryTexture->create()) {
321 qWarning("Failed to build secondary texture for layer of size %dx%d", m_pixelSize.width(), m_pixelSize.height());
322 releaseResources();
323 return;
324 }
325 color0.setResolveTexture(m_secondaryTexture);
326 if (!m_prevTexture)
327 clearMainTexture();
328 } else {
329 color0.setResolveTexture(m_texture);
330 }
331 desc.setColorAttachments({ color0 });
332 if (depthBufferEnabled)
333 desc.setDepthStencilBuffer(m_ds->ds);
334 m_rt = m_rhi->newTextureRenderTarget(desc);
335 m_rtRp = m_rt->newCompatibleRenderPassDescriptor();
336 if (!m_rtRp) {
337 qWarning("Failed to build render pass descriptor for layer");
338 releaseResources();
339 return;
340 }
341 m_rt->setRenderPassDescriptor(m_rtRp);
342 if (!m_rt->create()) {
343 qWarning("Failed to build texture render target for layer");
344 releaseResources();
345 return;
346 }
347 } else {
348 m_texture = m_rhi->newTexture(m_format, m_pixelSize, 1, textureFlags);
349 if (!m_texture->create()) {
350 qWarning("Failed to build texture for layer of size %dx%d", m_pixelSize.width(), m_pixelSize.height());
351 releaseResources();
352 return;
353 }
354 if (depthBufferEnabled) {
355 m_ds = m_context->getDepthStencilBuffer(m_pixelSize, 1);
356 if (!m_ds) {
357 releaseResources();
358 return;
359 }
360 }
361 QRhiColorAttachment color0(m_texture);
362 if (m_recursive) {
363 // Here rt is associated with m_secondaryTexture instead of m_texture.
364 // We will issue a copy to m_texture afterwards.
365 m_secondaryTexture = m_rhi->newTexture(m_format, m_pixelSize, 1, textureFlags);
366 if (!m_secondaryTexture->create()) {
367 qWarning("Failed to build texture for layer of size %dx%d", m_pixelSize.width(), m_pixelSize.height());
368 releaseResources();
369 return;
370 }
371 color0.setTexture(m_secondaryTexture);
372 if (!m_prevTexture)
373 clearMainTexture();
374 }
375 QRhiTextureRenderTargetDescription desc({ color0 });
376 if (depthBufferEnabled)
377 desc.setDepthStencilBuffer(m_ds->ds);
378 m_rt = m_rhi->newTextureRenderTarget(desc);
379 m_rtRp = m_rt->newCompatibleRenderPassDescriptor();
380 if (!m_rtRp) {
381 qWarning("Failed to build render pass descriptor for layer");
382 releaseResources();
383 return;
384 }
385 m_rt->setRenderPassDescriptor(m_rtRp);
386 if (!m_rt->create()) {
387 qWarning("Failed to build texture render target for layer");
388 releaseResources();
389 return;
390 }
391 }
392 }
393
394 QSGNode *root = m_item;
395 while (root->firstChild() && root->type() != QSGNode::RootNodeType)
396 root = root->firstChild();
397 if (root->type() != QSGNode::RootNodeType)
398 return;
399
400 if (!m_renderer) {
401 const bool useDepth = m_context->useDepthBufferFor2D();
402 const QSGRendererInterface::RenderMode renderMode = useDepth ? QSGRendererInterface::RenderMode2D
403 : QSGRendererInterface::RenderMode2DNoDepthBuffer;
404 m_renderer = m_context->createRenderer(renderMode);
405 connect(m_renderer, SIGNAL(sceneGraphChanged()), this, SLOT(markDirtyTexture()));
406 }
407 m_renderer->setRootNode(static_cast<QSGRootNode *>(root));
408 root->markDirty(QSGNode::DirtyForceUpdate); // Force matrix, clip and opacity update.
409 m_renderer->nodeChanged(root, QSGNode::DirtyForceUpdate); // Force render list update.
410
411 // This must not be moved. The flag must be reset only after the
412 // nodeChanged otherwise we end up with constantly updating even when the
413 // layer contents do not change.
414 m_dirtyTexture = false;
415
416 // The texture is essentially a virtual screen with a dpr == m_pixelSize / m_logicalRect
417 // For automatic texture sizes this factor will be equal to m_dpr.
418 // If the texture size is manually set, we need to know the actual ratio. This only
419 // works correctly for uniform rects, but this is already the prerequisite everywhere this
420 // factor is used.
421 if (m_logicalRect.isValid() && !m_pixelSize.isNull()) {
422 qreal actualDpr = std::max(m_pixelSize.width() / m_logicalRect.width(),
423 m_pixelSize.height() / m_logicalRect.height());
424 m_renderer->setDevicePixelRatio(actualDpr);
425 } else {
426 m_renderer->setDevicePixelRatio(m_dpr);
427 }
428 m_renderer->setDeviceRect(m_pixelSize);
429 m_renderer->setViewportRect(m_pixelSize);
430
431 QRectF mirrored; // in logical coordinates (no dpr) since this gets passed to setProjectionMatrixToRect()
432
433 // In the unlikely event of back/front face culling used by a custom
434 // material or effect in the layer, the default front face setting may be
435 // wrong. Rather, it needs to invert based on what the vertex shader does,
436 // and so on the rect (and so matrix) generated here.
437 bool frontFaceSwap = false;
438
439 if (m_rhi->isYUpInFramebuffer()) { // basically OpenGL
440 mirrored = QRectF(m_mirrorHorizontal ? m_logicalRect.right() : m_logicalRect.left(),
441 m_mirrorVertical ? m_logicalRect.bottom() : m_logicalRect.top(),
442 m_mirrorHorizontal ? -m_logicalRect.width() : m_logicalRect.width(),
443 m_mirrorVertical ? -m_logicalRect.height() : m_logicalRect.height());
444 if (m_mirrorHorizontal)
445 frontFaceSwap = !frontFaceSwap;
446 if (m_mirrorVertical)
447 frontFaceSwap = !frontFaceSwap;
448 } else { // APIs other than OpenGL
449 mirrored = QRectF(m_mirrorHorizontal ? m_logicalRect.right() : m_logicalRect.left(),
450 m_mirrorVertical ? m_logicalRect.top() : m_logicalRect.bottom(),
451 m_mirrorHorizontal ? -m_logicalRect.width() : m_logicalRect.width(),
452 m_mirrorVertical ? m_logicalRect.height() : -m_logicalRect.height());
453 if (m_mirrorHorizontal)
454 frontFaceSwap = !frontFaceSwap;
455 if (!m_mirrorVertical)
456 frontFaceSwap = !frontFaceSwap;
457 }
458
459 QSGAbstractRenderer::MatrixTransformFlags matrixFlags;
460 if (!m_rhi->isYUpInNDC())
461 matrixFlags |= QSGAbstractRenderer::MatrixTransformFlipY;
462
463 m_renderer->setProjectionMatrixToRect(mirrored, matrixFlags);
464 m_renderer->setInvertFrontFace(frontFaceSwap);
465 m_renderer->setClearColor(Qt::transparent);
466 m_renderer->setRenderTarget({ m_rt, m_rtRp, m_context->currentFrameCommandBuffer() });
467
468 QRhiResourceUpdateBatch *resourceUpdates = nullptr;
469
470 // render with our own "sub-renderer" (this will just add a render pass to the command buffer)
471 if (m_recursive) {
472 m_context->renderNextFrame(m_renderer);
473 if (!resourceUpdates)
474 resourceUpdates = m_rhi->nextResourceUpdateBatch();
475 resourceUpdates->copyTexture(m_texture, m_secondaryTexture);
476 if (m_prevTexture) {
477 delete m_prevTexture;
478 m_prevTexture = nullptr;
479 }
480 } else {
481 m_context->renderNextFrame(m_renderer);
482 }
483
484 if (m_mipmap) {
485 if (!resourceUpdates)
486 resourceUpdates = m_rhi->nextResourceUpdateBatch();
487 // going to be expensive - if done every frame - but the user asked for it...
488 resourceUpdates->generateMips(m_texture);
489 }
490
491 // Do not defer committing the resource updates to the main pass - with
492 // multiple layers there can be dependencies, so the texture should be
493 // usable once we return.
494 m_context->currentFrameCommandBuffer()->resourceUpdate(resourceUpdates);
495
496 root->markDirty(QSGNode::DirtyForceUpdate); // Force matrix, clip, opacity and render list update.
497
498 if (m_recursive)
499 markDirtyTexture(); // Continuously update if 'live' and 'recursive'.
500}
501
502QImage QSGRhiLayer::toImage() const
503{
504 if (!m_texture)
505 return QImage();
506
507 QRhiCommandBuffer *cb = m_context->currentFrameCommandBuffer();
508 QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch();
509 QRhiReadbackResult result;
510 QRhiReadbackDescription readbackDesc(m_texture);
511 resourceUpdates->readBackTexture(readbackDesc, &result);
512
513 cb->resourceUpdate(resourceUpdates);
514
515 // Inefficient but what can you do. We need the results right away. This is
516 // not something that occurs in a normal rendering process anyway. (used by
517 // QQuickItem's grabToImage).
518 m_rhi->finish();
519
520 if (result.data.isEmpty()) {
521 qWarning("Layer grab failed");
522 return QImage();
523 }
524
525 // There is little room for negotiation here, the texture is one of the formats from setFormat.
526 // Also, Quick is always premultiplied alpha.
527 QImage::Format imageFormat = QImage::Format_RGBA8888_Premultiplied;
528 if (m_format == QRhiTexture::RGBA16F)
529 imageFormat = QImage::Format_RGBA16FPx4_Premultiplied;
530 else if (m_format == QRhiTexture::RGBA32F)
531 imageFormat = QImage::Format_RGBA32FPx4_Premultiplied;
532
533 const uchar *p = reinterpret_cast<const uchar *>(result.data.constData());
534 return QImage(p, result.pixelSize.width(), result.pixelSize.height(), imageFormat).flipped();
535}
536
537QRectF QSGRhiLayer::normalizedTextureSubRect() const
538{
539 return QRectF(m_mirrorHorizontal ? 1 : 0,
540 m_mirrorVertical ? 0 : 1,
541 m_mirrorHorizontal ? -1 : 1,
542 m_mirrorVertical ? 1 : -1);
543}
544
545#include "moc_qsgrhilayer_p.cpp"