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 const int textureSizeMax = m_rhi->resourceLimit(limit: QRhi::TextureSizeMax);
119 m_pixelSize = pixelSize.boundedTo(otherSize: QSize(textureSizeMax, textureSizeMax));
120
121 if (Q_UNLIKELY(m_pixelSize != pixelSize)) {
122 qWarning(msg: "QSGRhiLayer: Unsupported size requested: [%d, %d]. "
123 "Maximum texture size: %d",
124 pixelSize.width(),
125 pixelSize.height(),
126 textureSizeMax);
127 }
128
129 if (m_live && m_pixelSize.isNull())
130 releaseResources();
131
132 markDirtyTexture();
133}
134
135void QSGRhiLayer::setFormat(Format format)
136{
137 QRhiTexture::Format rhiFormat = QRhiTexture::RGBA8;
138 switch (format) {
139 case RGBA16F:
140 rhiFormat = QRhiTexture::RGBA16F;
141 break;
142 case RGBA32F:
143 rhiFormat = QRhiTexture::RGBA32F;
144 break;
145 default:
146 break;
147 }
148
149 if (rhiFormat == m_format)
150 return;
151
152 if (m_rhi->isTextureFormatSupported(format: rhiFormat)) {
153 m_format = rhiFormat;
154 markDirtyTexture();
155 } else {
156 qWarning(msg: "QSGRhiLayer: Attempted to set unsupported texture format %d", int(rhiFormat));
157 }
158}
159
160void QSGRhiLayer::setLive(bool live)
161{
162 if (live == m_live)
163 return;
164
165 m_live = live;
166
167 if (m_live && (!m_item || m_pixelSize.isNull()))
168 releaseResources();
169
170 markDirtyTexture();
171}
172
173void QSGRhiLayer::scheduleUpdate()
174{
175 if (m_grab)
176 return;
177
178 m_grab = true;
179 if (m_dirtyTexture)
180 emit updateRequested();
181}
182
183void QSGRhiLayer::setRecursive(bool recursive)
184{
185 m_recursive = recursive;
186}
187
188void QSGRhiLayer::setMirrorHorizontal(bool mirror)
189{
190 m_mirrorHorizontal = mirror;
191}
192
193void QSGRhiLayer::setMirrorVertical(bool mirror)
194{
195 m_mirrorVertical = mirror;
196}
197
198void QSGRhiLayer::markDirtyTexture()
199{
200 m_dirtyTexture = true;
201 if (m_live || m_grab)
202 emit updateRequested();
203}
204
205void QSGRhiLayer::releaseResources()
206{
207 delete m_rt;
208 m_rt = nullptr;
209
210 delete m_rtRp;
211 m_rtRp = nullptr;
212
213 delete m_ds;
214 m_ds = nullptr;
215
216 delete m_msaaColorBuffer;
217 m_msaaColorBuffer = nullptr;
218
219 delete m_texture;
220 m_texture = nullptr;
221
222 delete m_secondaryTexture;
223 m_secondaryTexture = nullptr;
224}
225
226void QSGRhiLayer::grab()
227{
228 if (!m_item || m_pixelSize.isEmpty()) {
229 releaseResources();
230 m_dirtyTexture = false;
231 return;
232 }
233
234 int effectiveSamples = m_samples;
235 // if no layer.samples was provided use the window's msaa setting
236 if (effectiveSamples <= 1)
237 effectiveSamples = m_context->msaaSampleCount();
238
239 const bool needsNewRt = !m_rt || m_rt->pixelSize() != m_pixelSize || (m_recursive && !m_secondaryTexture) || (m_texture && m_texture->format() != m_format);
240 const bool mipmapSettingChanged = m_texture && m_texture->flags().testFlag(flag: QRhiTexture::MipMapped) != m_mipmap;
241 const bool msaaSettingChanged = (effectiveSamples > 1 && !m_msaaColorBuffer) || (effectiveSamples <= 1 && m_msaaColorBuffer);
242
243 if (needsNewRt ||mipmapSettingChanged || msaaSettingChanged) {
244 if (effectiveSamples <= 1) {
245 m_multisampling = false;
246 } else {
247 m_multisampling = m_rhi->isFeatureSupported(feature: QRhi::MultisampleRenderBuffer);
248 if (!m_multisampling)
249 qWarning(msg: "Layer requested %d samples but multisample renderbuffers are not supported", effectiveSamples);
250 }
251
252 QRhiTexture::Flags textureFlags = QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource;
253 if (m_mipmap)
254 textureFlags |= QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips;
255
256 // Not the same as m_context->useDepthBufferFor2D(), only the env.var
257 // is to be checked here. Consider a layer with a non-offscreen View3D
258 // in it. That still needs a depth buffer, even when the 2D content
259 // renders without relying on it (i.e. RenderMode2DNoDepthBuffer does
260 // not imply not having a depth/stencil attachment for the render
261 // target! The env.var serves as a hard switch, on the other hand, and
262 // that will likely break 3D for instance but that's fine)
263 static bool depthBufferEnabled = qEnvironmentVariableIsEmpty(varName: "QSG_NO_DEPTH_BUFFER");
264
265 if (m_multisampling) {
266 releaseResources();
267 m_msaaColorBuffer = m_rhi->newRenderBuffer(type: QRhiRenderBuffer::Color, pixelSize: m_pixelSize, sampleCount: effectiveSamples);
268 if (!m_msaaColorBuffer->create()) {
269 qWarning(msg: "Failed to build multisample color buffer for layer of size %dx%d, sample count %d",
270 m_pixelSize.width(), m_pixelSize.height(), effectiveSamples);
271 releaseResources();
272 return;
273 }
274 m_texture = m_rhi->newTexture(format: m_format, pixelSize: m_pixelSize, sampleCount: 1, flags: textureFlags);
275 if (!m_texture->create()) {
276 qWarning(msg: "Failed to build texture for layer of size %dx%d", m_pixelSize.width(), m_pixelSize.height());
277 releaseResources();
278 return;
279 }
280 if (depthBufferEnabled) {
281 m_ds = m_rhi->newRenderBuffer(type: QRhiRenderBuffer::DepthStencil, pixelSize: m_pixelSize, sampleCount: effectiveSamples);
282 if (!m_ds->create()) {
283 qWarning(msg: "Failed to build depth-stencil buffer for layer");
284 releaseResources();
285 return;
286 }
287 }
288 QRhiTextureRenderTargetDescription desc;
289 QRhiColorAttachment color0(m_msaaColorBuffer);
290 color0.setResolveTexture(m_texture);
291 desc.setColorAttachments({ color0 });
292 if (depthBufferEnabled)
293 desc.setDepthStencilBuffer(m_ds);
294 m_rt = m_rhi->newTextureRenderTarget(desc);
295 m_rtRp = m_rt->newCompatibleRenderPassDescriptor();
296 if (!m_rtRp) {
297 qWarning(msg: "Failed to build render pass descriptor for layer");
298 releaseResources();
299 return;
300 }
301 m_rt->setRenderPassDescriptor(m_rtRp);
302 if (!m_rt->create()) {
303 qWarning(msg: "Failed to build texture render target for layer");
304 releaseResources();
305 return;
306 }
307 } else {
308 releaseResources();
309 m_texture = m_rhi->newTexture(format: m_format, pixelSize: m_pixelSize, sampleCount: 1, flags: textureFlags);
310 if (!m_texture->create()) {
311 qWarning(msg: "Failed to build texture for layer of size %dx%d", m_pixelSize.width(), m_pixelSize.height());
312 releaseResources();
313 return;
314 }
315 if (depthBufferEnabled) {
316 m_ds = m_rhi->newRenderBuffer(type: QRhiRenderBuffer::DepthStencil, pixelSize: m_pixelSize);
317 if (!m_ds->create()) {
318 qWarning(msg: "Failed to build depth-stencil buffer for layer");
319 releaseResources();
320 return;
321 }
322 }
323 QRhiColorAttachment color0(m_texture);
324 if (m_recursive) {
325 // Here rt is associated with m_secondaryTexture instead of m_texture.
326 // We will issue a copy to m_texture afterwards.
327 m_secondaryTexture = m_rhi->newTexture(format: m_format, pixelSize: m_pixelSize, sampleCount: 1, flags: textureFlags);
328 if (!m_secondaryTexture->create()) {
329 qWarning(msg: "Failed to build texture for layer of size %dx%d", m_pixelSize.width(), m_pixelSize.height());
330 releaseResources();
331 return;
332 }
333 color0.setTexture(m_secondaryTexture);
334 }
335 if (depthBufferEnabled)
336 m_rt = m_rhi->newTextureRenderTarget(desc: { color0, m_ds });
337 else
338 m_rt = m_rhi->newTextureRenderTarget(desc: { color0 });
339 m_rtRp = m_rt->newCompatibleRenderPassDescriptor();
340 if (!m_rtRp) {
341 qWarning(msg: "Failed to build render pass descriptor for layer");
342 releaseResources();
343 return;
344 }
345 m_rt->setRenderPassDescriptor(m_rtRp);
346 if (!m_rt->create()) {
347 qWarning(msg: "Failed to build texture render target for layer");
348 releaseResources();
349 return;
350 }
351 }
352 }
353
354 QSGNode *root = m_item;
355 while (root->firstChild() && root->type() != QSGNode::RootNodeType)
356 root = root->firstChild();
357 if (root->type() != QSGNode::RootNodeType)
358 return;
359
360 if (!m_renderer) {
361 const bool useDepth = m_context->useDepthBufferFor2D();
362 const QSGRendererInterface::RenderMode renderMode = useDepth ? QSGRendererInterface::RenderMode2D
363 : QSGRendererInterface::RenderMode2DNoDepthBuffer;
364 m_renderer = m_context->createRenderer(renderMode);
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_pixelSize);
378 m_renderer->setViewportRect(m_pixelSize);
379 QRectF mirrored; // in logical coordinates (no dpr) since this gets passed to setProjectionMatrixToRect()
380 if (m_rhi->isYUpInFramebuffer()) {
381 mirrored = QRectF(m_mirrorHorizontal ? m_logicalRect.right() : m_logicalRect.left(),
382 m_mirrorVertical ? m_logicalRect.bottom() : m_logicalRect.top(),
383 m_mirrorHorizontal ? -m_logicalRect.width() : m_logicalRect.width(),
384 m_mirrorVertical ? -m_logicalRect.height() : m_logicalRect.height());
385 } else {
386 mirrored = QRectF(m_mirrorHorizontal ? m_logicalRect.right() : m_logicalRect.left(),
387 m_mirrorVertical ? m_logicalRect.top() : m_logicalRect.bottom(),
388 m_mirrorHorizontal ? -m_logicalRect.width() : m_logicalRect.width(),
389 m_mirrorVertical ? m_logicalRect.height() : -m_logicalRect.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, m_rtRp, m_context->currentFrameCommandBuffer() });
397
398 QRhiResourceUpdateBatch *resourceUpdates = nullptr;
399
400 // render with our own "sub-renderer" (this will just add a render pass to the command buffer)
401 if (m_multisampling) {
402 m_context->renderNextFrame(renderer: m_renderer);
403 } else {
404 if (m_recursive) {
405 m_context->renderNextFrame(renderer: m_renderer);
406 if (!resourceUpdates)
407 resourceUpdates = m_rhi->nextResourceUpdateBatch();
408 resourceUpdates->copyTexture(dst: m_texture, src: m_secondaryTexture);
409 } else {
410 m_context->renderNextFrame(renderer: m_renderer);
411 }
412 }
413
414 if (m_mipmap) {
415 if (!resourceUpdates)
416 resourceUpdates = m_rhi->nextResourceUpdateBatch();
417 // going to be expensive - if done every frame - but the user asked for it...
418 resourceUpdates->generateMips(tex: m_texture);
419 }
420
421 // Do not defer committing the resource updates to the main pass - with
422 // multiple layers there can be dependencies, so the texture should be
423 // usable once we return.
424 m_context->currentFrameCommandBuffer()->resourceUpdate(resourceUpdates);
425
426 root->markDirty(bits: QSGNode::DirtyForceUpdate); // Force matrix, clip, opacity and render list update.
427
428 if (m_recursive)
429 markDirtyTexture(); // Continuously update if 'live' and 'recursive'.
430}
431
432QImage QSGRhiLayer::toImage() const
433{
434 if (!m_texture)
435 return QImage();
436
437 QRhiCommandBuffer *cb = m_context->currentFrameCommandBuffer();
438 QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch();
439 QRhiReadbackResult result;
440 QRhiReadbackDescription readbackDesc(m_texture);
441 resourceUpdates->readBackTexture(rb: readbackDesc, result: &result);
442
443 cb->resourceUpdate(resourceUpdates);
444
445 // Inefficient but what can you do. We need the results right away. This is
446 // not something that occurs in a normal rendering process anyway. (used by
447 // QQuickItem's grabToImage).
448 m_rhi->finish();
449
450 if (result.data.isEmpty()) {
451 qWarning(msg: "Layer grab failed");
452 return QImage();
453 }
454
455 // There is little room for negotiation here, the texture is one of the formats from setFormat.
456 // Also, Quick is always premultiplied alpha.
457 QImage::Format imageFormat = QImage::Format_RGBA8888_Premultiplied;
458 if (m_format == QRhiTexture::RGBA16F)
459 imageFormat = QImage::Format_RGBA16FPx4_Premultiplied;
460 else if (m_format == QRhiTexture::RGBA32F)
461 imageFormat = QImage::Format_RGBA32FPx4_Premultiplied;
462
463 const uchar *p = reinterpret_cast<const uchar *>(result.data.constData());
464 return QImage(p, result.pixelSize.width(), result.pixelSize.height(), imageFormat).mirrored();
465}
466
467QRectF QSGRhiLayer::normalizedTextureSubRect() const
468{
469 return QRectF(m_mirrorHorizontal ? 1 : 0,
470 m_mirrorVertical ? 0 : 1,
471 m_mirrorHorizontal ? -1 : 1,
472 m_mirrorVertical ? 1 : -1);
473}
474
475#include "moc_qsgrhilayer_p.cpp"
476

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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