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

source code of qtdeclarative/src/quick/scenegraph/qsgrhilayer.cpp