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