1/****************************************************************************
2**
3** Copyright (C) 2019 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQuick module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qsgrhilayer_p.h"
41
42#include <private/qqmlglobal_p.h>
43#include <private/qsgrenderer_p.h>
44#include <private/qsgdefaultrendercontext_p.h>
45
46QSGRhiLayer::QSGRhiLayer(QSGRenderContext *context)
47 : QSGLayer(*(new QSGRhiLayerPrivate))
48 , m_mipmap(false)
49 , m_live(true)
50 , m_recursive(false)
51 , m_dirtyTexture(true)
52 , m_multisampling(false)
53 , m_grab(false)
54 , m_mirrorHorizontal(false)
55 , m_mirrorVertical(true)
56{
57 m_context = static_cast<QSGDefaultRenderContext *>(context);
58 m_rhi = m_context->rhi();
59 Q_ASSERT(m_rhi);
60}
61
62QSGRhiLayer::~QSGRhiLayer()
63{
64 invalidated();
65}
66
67void QSGRhiLayer::invalidated()
68{
69 releaseResources();
70
71 delete m_renderer;
72 m_renderer = nullptr;
73}
74
75int QSGRhiLayerPrivate::comparisonKey() const
76{
77 Q_Q(const QSGRhiLayer);
78 return int(qintptr(q->m_texture));
79}
80
81bool QSGRhiLayer::hasAlphaChannel() const
82{
83 return true;
84}
85
86bool QSGRhiLayer::hasMipmaps() const
87{
88 return m_mipmap;
89}
90
91int QSGRhiLayer::textureId() const
92{
93 Q_ASSERT_X(false, "QSGRhiLayer::textureId()", "Not implemented for RHI");
94 return 0;
95}
96
97void QSGRhiLayer::bind()
98{
99 Q_ASSERT_X(false, "QSGRhiLayer::bind()", "Not implemented for RHI");
100}
101
102QRhiTexture *QSGRhiLayerPrivate::rhiTexture() const
103{
104 Q_Q(const QSGRhiLayer);
105 return q->m_texture;
106}
107
108void QSGRhiLayerPrivate::updateRhiTexture(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates)
109{
110 Q_UNUSED(rhi);
111 Q_UNUSED(resourceUpdates);
112}
113
114bool QSGRhiLayer::updateTexture()
115{
116 // called during the preprocess phase, outside of frame rendering -> good
117
118 bool doGrab = (m_live || m_grab) && m_dirtyTexture;
119 if (doGrab)
120 grab();
121
122 if (m_grab)
123 emit scheduledUpdateCompleted();
124
125 m_grab = false;
126 return doGrab;
127}
128
129void QSGRhiLayer::setHasMipmaps(bool mipmap)
130{
131 if (mipmap == m_mipmap)
132 return;
133
134 m_mipmap = mipmap;
135 if (m_mipmap && m_texture)
136 markDirtyTexture();
137}
138
139
140void QSGRhiLayer::setItem(QSGNode *item)
141{
142 if (item == m_item)
143 return;
144
145 m_item = item;
146
147 if (m_live && !m_item)
148 releaseResources();
149
150 markDirtyTexture();
151}
152
153void QSGRhiLayer::setRect(const QRectF &rect)
154{
155 if (rect == m_rect)
156 return;
157
158 m_rect = rect;
159 markDirtyTexture();
160}
161
162void QSGRhiLayer::setSize(const QSize &size)
163{
164 if (size == m_size)
165 return;
166
167 m_size = size;
168
169 if (m_live && m_size.isNull())
170 releaseResources();
171
172 markDirtyTexture();
173}
174
175void QSGRhiLayer::setFormat(uint format)
176{
177 Q_UNUSED(format);
178}
179
180void QSGRhiLayer::setLive(bool live)
181{
182 if (live == m_live)
183 return;
184
185 m_live = live;
186
187 if (m_live && (!m_item || m_size.isNull()))
188 releaseResources();
189
190 markDirtyTexture();
191}
192
193void QSGRhiLayer::scheduleUpdate()
194{
195 if (m_grab)
196 return;
197
198 m_grab = true;
199 if (m_dirtyTexture)
200 emit updateRequested();
201}
202
203void QSGRhiLayer::setRecursive(bool recursive)
204{
205 m_recursive = recursive;
206}
207
208void QSGRhiLayer::setMirrorHorizontal(bool mirror)
209{
210 m_mirrorHorizontal = mirror;
211}
212
213void QSGRhiLayer::setMirrorVertical(bool mirror)
214{
215 m_mirrorVertical = mirror;
216}
217
218void QSGRhiLayer::markDirtyTexture()
219{
220 m_dirtyTexture = true;
221 if (m_live || m_grab)
222 emit updateRequested();
223}
224
225void QSGRhiLayer::releaseResources()
226{
227 delete m_rt;
228 m_rt = nullptr;
229
230 delete m_rtRp;
231 m_rtRp = nullptr;
232
233 delete m_ds;
234 m_ds = nullptr;
235
236 delete m_msaaColorBuffer;
237 m_msaaColorBuffer = nullptr;
238
239 delete m_texture;
240 m_texture = nullptr;
241
242 delete m_secondaryTexture;
243 m_secondaryTexture = nullptr;
244}
245
246void QSGRhiLayer::grab()
247{
248 if (!m_item || m_size.isNull()) {
249 releaseResources();
250 m_dirtyTexture = false;
251 return;
252 }
253
254 int effectiveSamples = m_samples;
255 // if no layer.samples was provided use the window's msaa setting
256 if (effectiveSamples <= 1)
257 effectiveSamples = m_context->msaaSampleCount();
258
259 const bool needsNewRt = !m_rt || m_rt->pixelSize() != m_size || (m_recursive && !m_secondaryTexture);
260 const bool mipmapSettingChanged = m_texture && m_texture->flags().testFlag(QRhiTexture::MipMapped) != m_mipmap;
261 const bool msaaSettingChanged = (effectiveSamples > 1 && !m_msaaColorBuffer) || (effectiveSamples <= 1 && m_msaaColorBuffer);
262
263 if (needsNewRt ||mipmapSettingChanged || msaaSettingChanged) {
264 if (effectiveSamples <= 1) {
265 m_multisampling = false;
266 } else {
267 m_multisampling = m_rhi->isFeatureSupported(QRhi::MultisampleRenderBuffer);
268 if (!m_multisampling)
269 qWarning("Layer requested %d samples but multisample renderbuffers are not supported", effectiveSamples);
270 }
271
272 QRhiTexture::Flags textureFlags = QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource;
273 if (m_mipmap)
274 textureFlags |= QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips;
275
276 if (m_multisampling) {
277 releaseResources();
278 m_msaaColorBuffer = m_rhi->newRenderBuffer(QRhiRenderBuffer::Color, m_size, effectiveSamples);
279 if (!m_msaaColorBuffer->build()) {
280 qWarning("Failed to build multisample color buffer for layer of size %dx%d, sample count %d",
281 m_size.width(), m_size.height(), effectiveSamples);
282 releaseResources();
283 return;
284 }
285 m_texture = m_rhi->newTexture(m_format, m_size, 1, textureFlags);
286 if (!m_texture->build()) {
287 qWarning("Failed to build texture for layer of size %dx%d", m_size.width(), m_size.height());
288 releaseResources();
289 return;
290 }
291 m_ds = m_rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, m_size, effectiveSamples);
292 if (!m_ds->build()) {
293 qWarning("Failed to build depth-stencil buffer for layer");
294 releaseResources();
295 return;
296 }
297 QRhiTextureRenderTargetDescription desc;
298 QRhiColorAttachment color0(m_msaaColorBuffer);
299 color0.setResolveTexture(m_texture);
300 desc.setColorAttachments({ color0 });
301 desc.setDepthStencilBuffer(m_ds);
302 m_rt = m_rhi->newTextureRenderTarget(desc);
303 m_rtRp = m_rt->newCompatibleRenderPassDescriptor();
304 if (!m_rtRp) {
305 qWarning("Failed to build render pass descriptor for layer");
306 releaseResources();
307 return;
308 }
309 m_rt->setRenderPassDescriptor(m_rtRp);
310 if (!m_rt->build()) {
311 qWarning("Failed to build texture render target for layer");
312 releaseResources();
313 return;
314 }
315 } else {
316 releaseResources();
317 m_texture = m_rhi->newTexture(m_format, m_size, 1, textureFlags);
318 if (!m_texture->build()) {
319 qWarning("Failed to build texture for layer of size %dx%d", m_size.width(), m_size.height());
320 releaseResources();
321 return;
322 }
323 m_ds = m_rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, m_size);
324 if (!m_ds->build()) {
325 qWarning("Failed to build depth-stencil buffer for layer");
326 releaseResources();
327 return;
328 }
329 QRhiColorAttachment color0(m_texture);
330 if (m_recursive) {
331 // Here rt is associated with m_secondaryTexture instead of m_texture.
332 // We will issue a copy to m_texture afterwards.
333 m_secondaryTexture = m_rhi->newTexture(m_format, m_size, 1, textureFlags);
334 if (!m_secondaryTexture->build()) {
335 qWarning("Failed to build texture for layer of size %dx%d", m_size.width(), m_size.height());
336 releaseResources();
337 return;
338 }
339 color0.setTexture(m_secondaryTexture);
340 }
341 m_rt = m_rhi->newTextureRenderTarget({ color0, m_ds });
342 m_rtRp = m_rt->newCompatibleRenderPassDescriptor();
343 if (!m_rtRp) {
344 qWarning("Failed to build render pass descriptor for layer");
345 releaseResources();
346 return;
347 }
348 m_rt->setRenderPassDescriptor(m_rtRp);
349 if (!m_rt->build()) {
350 qWarning("Failed to build texture render target for layer");
351 releaseResources();
352 return;
353 }
354 }
355 }
356
357 QSGNode *root = m_item;
358 while (root->firstChild() && root->type() != QSGNode::RootNodeType)
359 root = root->firstChild();
360 if (root->type() != QSGNode::RootNodeType)
361 return;
362
363 if (!m_renderer) {
364 m_renderer = m_context->createRenderer();
365 connect(m_renderer, SIGNAL(sceneGraphChanged()), this, SLOT(markDirtyTexture()));
366 }
367 m_renderer->setRootNode(static_cast<QSGRootNode *>(root));
368 root->markDirty(QSGNode::DirtyForceUpdate); // Force matrix, clip and opacity update.
369 m_renderer->nodeChanged(root, QSGNode::DirtyForceUpdate); // Force render list update.
370
371 // This must not be moved. The flag must be reset only after the
372 // nodeChanged otherwise we end up with constantly updating even when the
373 // layer contents do not change.
374 m_dirtyTexture = false;
375
376 m_renderer->setDevicePixelRatio(m_dpr);
377 m_renderer->setDeviceRect(m_size);
378 m_renderer->setViewportRect(m_size);
379 QRectF mirrored;
380 if (m_rhi->isYUpInFramebuffer()) {
381 mirrored = QRectF(m_mirrorHorizontal ? m_rect.right() : m_rect.left(),
382 m_mirrorVertical ? m_rect.bottom() : m_rect.top(),
383 m_mirrorHorizontal ? -m_rect.width() : m_rect.width(),
384 m_mirrorVertical ? -m_rect.height() : m_rect.height());
385 } else {
386 mirrored = QRectF(m_mirrorHorizontal ? m_rect.right() : m_rect.left(),
387 m_mirrorVertical ? m_rect.top() : m_rect.bottom(),
388 m_mirrorHorizontal ? -m_rect.width() : m_rect.width(),
389 m_mirrorVertical ? m_rect.height() : -m_rect.height());
390 }
391 m_renderer->setProjectionMatrixToRect(mirrored, !m_rhi->isYUpInNDC());
392 m_renderer->setClearColor(Qt::transparent);
393 m_renderer->setRenderTarget(m_rt);
394 m_renderer->setCommandBuffer(m_context->currentFrameCommandBuffer());
395 m_renderer->setRenderPassDescriptor(m_rtRp);
396
397 QRhiResourceUpdateBatch *resourceUpdates = nullptr;
398
399 // render with our own "sub-renderer" (this will just add a render pass to the command buffer)
400 if (m_multisampling) {
401 m_context->renderNextRhiFrame(m_renderer);
402 } else {
403 if (m_recursive) {
404 m_context->renderNextRhiFrame(m_renderer);
405 if (!resourceUpdates)
406 resourceUpdates = m_rhi->nextResourceUpdateBatch();
407 resourceUpdates->copyTexture(m_texture, m_secondaryTexture);
408 } else {
409 m_context->renderNextRhiFrame(m_renderer);
410 }
411 }
412
413 if (m_mipmap) {
414 if (!resourceUpdates)
415 resourceUpdates = m_rhi->nextResourceUpdateBatch();
416 // going to be expensive - if done every frame - but the user asked for it...
417 resourceUpdates->generateMips(m_texture);
418 }
419
420 // Do not defer committing the resource updates to the main pass - with
421 // multiple layers there can be dependencies, so the texture should be
422 // usable once we return.
423 m_context->currentFrameCommandBuffer()->resourceUpdate(resourceUpdates);
424
425 root->markDirty(QSGNode::DirtyForceUpdate); // Force matrix, clip, opacity and render list update.
426
427 if (m_recursive)
428 markDirtyTexture(); // Continuously update if 'live' and 'recursive'.
429}
430
431QImage QSGRhiLayer::toImage() const
432{
433 if (!m_texture)
434 return QImage();
435
436 QRhiCommandBuffer *cb = m_context->currentFrameCommandBuffer();
437 QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch();
438 QRhiReadbackResult result;
439 QRhiReadbackDescription readbackDesc(m_texture);
440 resourceUpdates->readBackTexture(readbackDesc, &result);
441
442 cb->resourceUpdate(resourceUpdates);
443
444 // Inefficient but what can you do. We need the results right away. This is
445 // not something that occurs in a normal rendering process anyway. (used by
446 // QQuickItem's grabToImage).
447 m_rhi->finish();
448
449 if (result.data.isEmpty()) {
450 qWarning("Layer grab failed");
451 return QImage();
452 }
453
454 // There is no room for negotiation here, the texture is RGBA8, and the
455 // readback happens with GL_RGBA on GL, so RGBA8888 is the only option.
456 // Also, Quick is always premultiplied alpha.
457 const QImage::Format imageFormat = QImage::Format_RGBA8888_Premultiplied;
458
459 const uchar *p = reinterpret_cast<const uchar *>(result.data.constData());
460 return QImage(p, result.pixelSize.width(), result.pixelSize.height(), imageFormat).mirrored();
461}
462
463QRectF QSGRhiLayer::normalizedTextureSubRect() const
464{
465 return QRectF(m_mirrorHorizontal ? 1 : 0,
466 m_mirrorVertical ? 0 : 1,
467 m_mirrorHorizontal ? -1 : 1,
468 m_mirrorVertical ? 1 : -1);
469}
470
471#include "moc_qsgrhilayer_p.cpp"
472