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 | |
46 | QSGRhiLayer::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 | |
62 | QSGRhiLayer::~QSGRhiLayer() |
63 | { |
64 | invalidated(); |
65 | } |
66 | |
67 | void QSGRhiLayer::invalidated() |
68 | { |
69 | releaseResources(); |
70 | |
71 | delete m_renderer; |
72 | m_renderer = nullptr; |
73 | } |
74 | |
75 | int QSGRhiLayerPrivate::comparisonKey() const |
76 | { |
77 | Q_Q(const QSGRhiLayer); |
78 | return int(qintptr(q->m_texture)); |
79 | } |
80 | |
81 | bool QSGRhiLayer::hasAlphaChannel() const |
82 | { |
83 | return true; |
84 | } |
85 | |
86 | bool QSGRhiLayer::hasMipmaps() const |
87 | { |
88 | return m_mipmap; |
89 | } |
90 | |
91 | int QSGRhiLayer::textureId() const |
92 | { |
93 | Q_ASSERT_X(false, "QSGRhiLayer::textureId()" , "Not implemented for RHI" ); |
94 | return 0; |
95 | } |
96 | |
97 | void QSGRhiLayer::bind() |
98 | { |
99 | Q_ASSERT_X(false, "QSGRhiLayer::bind()" , "Not implemented for RHI" ); |
100 | } |
101 | |
102 | QRhiTexture *QSGRhiLayerPrivate::rhiTexture() const |
103 | { |
104 | Q_Q(const QSGRhiLayer); |
105 | return q->m_texture; |
106 | } |
107 | |
108 | void QSGRhiLayerPrivate::updateRhiTexture(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates) |
109 | { |
110 | Q_UNUSED(rhi); |
111 | Q_UNUSED(resourceUpdates); |
112 | } |
113 | |
114 | bool 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 | |
129 | void 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 | |
140 | void 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 | |
153 | void QSGRhiLayer::setRect(const QRectF &rect) |
154 | { |
155 | if (rect == m_rect) |
156 | return; |
157 | |
158 | m_rect = rect; |
159 | markDirtyTexture(); |
160 | } |
161 | |
162 | void 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 | |
175 | void QSGRhiLayer::setFormat(uint format) |
176 | { |
177 | Q_UNUSED(format); |
178 | } |
179 | |
180 | void 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 | |
193 | void QSGRhiLayer::scheduleUpdate() |
194 | { |
195 | if (m_grab) |
196 | return; |
197 | |
198 | m_grab = true; |
199 | if (m_dirtyTexture) |
200 | emit updateRequested(); |
201 | } |
202 | |
203 | void QSGRhiLayer::setRecursive(bool recursive) |
204 | { |
205 | m_recursive = recursive; |
206 | } |
207 | |
208 | void QSGRhiLayer::setMirrorHorizontal(bool mirror) |
209 | { |
210 | m_mirrorHorizontal = mirror; |
211 | } |
212 | |
213 | void QSGRhiLayer::setMirrorVertical(bool mirror) |
214 | { |
215 | m_mirrorVertical = mirror; |
216 | } |
217 | |
218 | void QSGRhiLayer::markDirtyTexture() |
219 | { |
220 | m_dirtyTexture = true; |
221 | if (m_live || m_grab) |
222 | emit updateRequested(); |
223 | } |
224 | |
225 | void 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 | |
246 | void 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(flag: 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(feature: QRhi::MultisampleRenderBuffer); |
268 | if (!m_multisampling) |
269 | qWarning(msg: "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(type: QRhiRenderBuffer::Color, pixelSize: m_size, sampleCount: effectiveSamples); |
279 | if (!m_msaaColorBuffer->build()) { |
280 | qWarning(msg: "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(format: m_format, pixelSize: m_size, sampleCount: 1, flags: textureFlags); |
286 | if (!m_texture->build()) { |
287 | qWarning(msg: "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(type: QRhiRenderBuffer::DepthStencil, pixelSize: m_size, sampleCount: effectiveSamples); |
292 | if (!m_ds->build()) { |
293 | qWarning(msg: "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(msg: "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(msg: "Failed to build texture render target for layer" ); |
312 | releaseResources(); |
313 | return; |
314 | } |
315 | } else { |
316 | releaseResources(); |
317 | m_texture = m_rhi->newTexture(format: m_format, pixelSize: m_size, sampleCount: 1, flags: textureFlags); |
318 | if (!m_texture->build()) { |
319 | qWarning(msg: "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(type: QRhiRenderBuffer::DepthStencil, pixelSize: m_size); |
324 | if (!m_ds->build()) { |
325 | qWarning(msg: "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(format: m_format, pixelSize: m_size, sampleCount: 1, flags: textureFlags); |
334 | if (!m_secondaryTexture->build()) { |
335 | qWarning(msg: "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(desc: { color0, m_ds }); |
342 | m_rtRp = m_rt->newCompatibleRenderPassDescriptor(); |
343 | if (!m_rtRp) { |
344 | qWarning(msg: "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(msg: "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(sender: m_renderer, SIGNAL(sceneGraphChanged()), receiver: this, SLOT(markDirtyTexture())); |
366 | } |
367 | m_renderer->setRootNode(static_cast<QSGRootNode *>(root)); |
368 | root->markDirty(bits: QSGNode::DirtyForceUpdate); // Force matrix, clip and opacity update. |
369 | m_renderer->nodeChanged(node: root, state: 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 | QSGAbstractRenderer::MatrixTransformFlags matrixFlags; |
392 | if (!m_rhi->isYUpInNDC()) |
393 | matrixFlags |= QSGAbstractRenderer::MatrixTransformFlipY; |
394 | m_renderer->setProjectionMatrixToRect(rect: mirrored, flags: matrixFlags); |
395 | m_renderer->setClearColor(Qt::transparent); |
396 | m_renderer->setRenderTarget(m_rt); |
397 | m_renderer->setCommandBuffer(m_context->currentFrameCommandBuffer()); |
398 | m_renderer->setRenderPassDescriptor(m_rtRp); |
399 | |
400 | QRhiResourceUpdateBatch *resourceUpdates = nullptr; |
401 | |
402 | // render with our own "sub-renderer" (this will just add a render pass to the command buffer) |
403 | if (m_multisampling) { |
404 | m_context->renderNextRhiFrame(renderer: m_renderer); |
405 | } else { |
406 | if (m_recursive) { |
407 | m_context->renderNextRhiFrame(renderer: m_renderer); |
408 | if (!resourceUpdates) |
409 | resourceUpdates = m_rhi->nextResourceUpdateBatch(); |
410 | resourceUpdates->copyTexture(dst: m_texture, src: m_secondaryTexture); |
411 | } else { |
412 | m_context->renderNextRhiFrame(renderer: m_renderer); |
413 | } |
414 | } |
415 | |
416 | if (m_mipmap) { |
417 | if (!resourceUpdates) |
418 | resourceUpdates = m_rhi->nextResourceUpdateBatch(); |
419 | // going to be expensive - if done every frame - but the user asked for it... |
420 | resourceUpdates->generateMips(tex: m_texture); |
421 | } |
422 | |
423 | // Do not defer committing the resource updates to the main pass - with |
424 | // multiple layers there can be dependencies, so the texture should be |
425 | // usable once we return. |
426 | m_context->currentFrameCommandBuffer()->resourceUpdate(resourceUpdates); |
427 | |
428 | root->markDirty(bits: QSGNode::DirtyForceUpdate); // Force matrix, clip, opacity and render list update. |
429 | |
430 | if (m_recursive) |
431 | markDirtyTexture(); // Continuously update if 'live' and 'recursive'. |
432 | } |
433 | |
434 | QImage QSGRhiLayer::toImage() const |
435 | { |
436 | if (!m_texture) |
437 | return QImage(); |
438 | |
439 | QRhiCommandBuffer *cb = m_context->currentFrameCommandBuffer(); |
440 | QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch(); |
441 | QRhiReadbackResult result; |
442 | QRhiReadbackDescription readbackDesc(m_texture); |
443 | resourceUpdates->readBackTexture(rb: readbackDesc, result: &result); |
444 | |
445 | cb->resourceUpdate(resourceUpdates); |
446 | |
447 | // Inefficient but what can you do. We need the results right away. This is |
448 | // not something that occurs in a normal rendering process anyway. (used by |
449 | // QQuickItem's grabToImage). |
450 | m_rhi->finish(); |
451 | |
452 | if (result.data.isEmpty()) { |
453 | qWarning(msg: "Layer grab failed" ); |
454 | return QImage(); |
455 | } |
456 | |
457 | // There is no room for negotiation here, the texture is RGBA8, and the |
458 | // readback happens with GL_RGBA on GL, so RGBA8888 is the only option. |
459 | // Also, Quick is always premultiplied alpha. |
460 | const QImage::Format imageFormat = QImage::Format_RGBA8888_Premultiplied; |
461 | |
462 | const uchar *p = reinterpret_cast<const uchar *>(result.data.constData()); |
463 | return QImage(p, result.pixelSize.width(), result.pixelSize.height(), imageFormat).mirrored(); |
464 | } |
465 | |
466 | QRectF QSGRhiLayer::normalizedTextureSubRect() const |
467 | { |
468 | return QRectF(m_mirrorHorizontal ? 1 : 0, |
469 | m_mirrorVertical ? 0 : 1, |
470 | m_mirrorHorizontal ? -1 : 1, |
471 | m_mirrorVertical ? 1 : -1); |
472 | } |
473 | |
474 | #include "moc_qsgrhilayer_p.cpp" |
475 | |