aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/scenegraph/qsgrhilayer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/scenegraph/qsgrhilayer.cpp')
-rw-r--r--src/quick/scenegraph/qsgrhilayer.cpp466
1 files changed, 466 insertions, 0 deletions
diff --git a/src/quick/scenegraph/qsgrhilayer.cpp b/src/quick/scenegraph/qsgrhilayer.cpp
new file mode 100644
index 0000000000..5299cb54ce
--- /dev/null
+++ b/src/quick/scenegraph/qsgrhilayer.cpp
@@ -0,0 +1,466 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsgrhilayer_p.h"
+
+#include <private/qqmlglobal_p.h>
+#include <private/qsgrenderer_p.h>
+#include <private/qsgdefaultrendercontext_p.h>
+
+QSGRhiLayer::QSGRhiLayer(QSGRenderContext *context)
+ : QSGLayer(*(new QSGTexturePrivate(this)))
+ , m_mipmap(false)
+ , m_live(true)
+ , m_recursive(false)
+ , m_dirtyTexture(true)
+ , m_multisampling(false)
+ , m_grab(false)
+ , m_mirrorHorizontal(false)
+ , m_mirrorVertical(true)
+{
+ m_context = static_cast<QSGDefaultRenderContext *>(context);
+ m_rhi = m_context->rhi();
+ Q_ASSERT(m_rhi);
+}
+
+QSGRhiLayer::~QSGRhiLayer()
+{
+ invalidated();
+}
+
+void QSGRhiLayer::invalidated()
+{
+ releaseResources();
+
+ delete m_renderer;
+ m_renderer = nullptr;
+}
+
+qint64 QSGRhiLayer::comparisonKey() const
+{
+ return qint64(m_texture);
+}
+
+bool QSGRhiLayer::hasAlphaChannel() const
+{
+ return true;
+}
+
+bool QSGRhiLayer::hasMipmaps() const
+{
+ return m_mipmap;
+}
+
+QRhiTexture *QSGRhiLayer::rhiTexture() const
+{
+ return m_texture;
+}
+
+void QSGRhiLayer::commitTextureOperations(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ Q_UNUSED(rhi);
+ Q_UNUSED(resourceUpdates);
+}
+
+bool QSGRhiLayer::updateTexture()
+{
+ // called during the preprocess phase, outside of frame rendering -> good
+
+ bool doGrab = (m_live || m_grab) && m_dirtyTexture;
+ if (doGrab)
+ grab();
+
+ if (m_grab)
+ emit scheduledUpdateCompleted();
+
+ m_grab = false;
+ return doGrab;
+}
+
+void QSGRhiLayer::setHasMipmaps(bool mipmap)
+{
+ if (mipmap == m_mipmap)
+ return;
+
+ m_mipmap = mipmap;
+ if (m_mipmap && m_texture)
+ markDirtyTexture();
+}
+
+
+void QSGRhiLayer::setItem(QSGNode *item)
+{
+ if (item == m_item)
+ return;
+
+ m_item = item;
+
+ if (m_live && !m_item)
+ releaseResources();
+
+ markDirtyTexture();
+}
+
+void QSGRhiLayer::setRect(const QRectF &logicalRect)
+{
+ if (logicalRect == m_logicalRect)
+ return;
+
+ m_logicalRect = logicalRect;
+ markDirtyTexture();
+}
+
+void QSGRhiLayer::setSize(const QSize &pixelSize)
+{
+ if (pixelSize == m_pixelSize)
+ return;
+
+ m_pixelSize = pixelSize;
+
+ if (m_live && m_pixelSize.isNull())
+ releaseResources();
+
+ markDirtyTexture();
+}
+
+void QSGRhiLayer::setFormat(Format format)
+{
+ QRhiTexture::Format rhiFormat = QRhiTexture::RGBA8;
+ switch (format) {
+ case RGBA16F:
+ rhiFormat = QRhiTexture::RGBA16F;
+ break;
+ case RGBA32F:
+ rhiFormat = QRhiTexture::RGBA32F;
+ break;
+ default:
+ break;
+ }
+
+ if (rhiFormat == m_format)
+ return;
+
+ if (m_rhi->isTextureFormatSupported(rhiFormat)) {
+ m_format = rhiFormat;
+ markDirtyTexture();
+ } else {
+ qWarning("QSGRhiLayer: Attempted to set unsupported texture format %d", int(rhiFormat));
+ }
+}
+
+void QSGRhiLayer::setLive(bool live)
+{
+ if (live == m_live)
+ return;
+
+ m_live = live;
+
+ if (m_live && (!m_item || m_pixelSize.isNull()))
+ releaseResources();
+
+ markDirtyTexture();
+}
+
+void QSGRhiLayer::scheduleUpdate()
+{
+ if (m_grab)
+ return;
+
+ m_grab = true;
+ if (m_dirtyTexture)
+ emit updateRequested();
+}
+
+void QSGRhiLayer::setRecursive(bool recursive)
+{
+ m_recursive = recursive;
+}
+
+void QSGRhiLayer::setMirrorHorizontal(bool mirror)
+{
+ m_mirrorHorizontal = mirror;
+}
+
+void QSGRhiLayer::setMirrorVertical(bool mirror)
+{
+ m_mirrorVertical = mirror;
+}
+
+void QSGRhiLayer::markDirtyTexture()
+{
+ m_dirtyTexture = true;
+ if (m_live || m_grab)
+ emit updateRequested();
+}
+
+void QSGRhiLayer::releaseResources()
+{
+ delete m_rt;
+ m_rt = nullptr;
+
+ delete m_rtRp;
+ m_rtRp = nullptr;
+
+ delete m_ds;
+ m_ds = nullptr;
+
+ delete m_msaaColorBuffer;
+ m_msaaColorBuffer = nullptr;
+
+ delete m_texture;
+ m_texture = nullptr;
+
+ delete m_secondaryTexture;
+ m_secondaryTexture = nullptr;
+}
+
+void QSGRhiLayer::grab()
+{
+ if (!m_item || m_pixelSize.isEmpty()) {
+ releaseResources();
+ m_dirtyTexture = false;
+ return;
+ }
+
+ int effectiveSamples = m_samples;
+ // if no layer.samples was provided use the window's msaa setting
+ if (effectiveSamples <= 1)
+ effectiveSamples = m_context->msaaSampleCount();
+
+ const bool needsNewRt = !m_rt || m_rt->pixelSize() != m_pixelSize || (m_recursive && !m_secondaryTexture) || (m_texture && m_texture->format() != m_format);
+ const bool mipmapSettingChanged = m_texture && m_texture->flags().testFlag(QRhiTexture::MipMapped) != m_mipmap;
+ const bool msaaSettingChanged = (effectiveSamples > 1 && !m_msaaColorBuffer) || (effectiveSamples <= 1 && m_msaaColorBuffer);
+
+ if (needsNewRt ||mipmapSettingChanged || msaaSettingChanged) {
+ if (effectiveSamples <= 1) {
+ m_multisampling = false;
+ } else {
+ m_multisampling = m_rhi->isFeatureSupported(QRhi::MultisampleRenderBuffer);
+ if (!m_multisampling)
+ qWarning("Layer requested %d samples but multisample renderbuffers are not supported", effectiveSamples);
+ }
+
+ QRhiTexture::Flags textureFlags = QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource;
+ if (m_mipmap)
+ textureFlags |= QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips;
+
+ // Not the same as m_context->useDepthBufferFor2D(), only the env.var
+ // is to be checked here. Consider a layer with a non-offscreen View3D
+ // in it. That still needs a depth buffer, even when the 2D content
+ // renders without relying on it (i.e. RenderMode2DNoDepthBuffer does
+ // not imply not having a depth/stencil attachment for the render
+ // target! The env.var serves as a hard switch, on the other hand, and
+ // that will likely break 3D for instance but that's fine)
+ static bool depthBufferEnabled = qEnvironmentVariableIsEmpty("QSG_NO_DEPTH_BUFFER");
+
+ if (m_multisampling) {
+ releaseResources();
+ m_msaaColorBuffer = m_rhi->newRenderBuffer(QRhiRenderBuffer::Color, m_pixelSize, effectiveSamples);
+ if (!m_msaaColorBuffer->create()) {
+ qWarning("Failed to build multisample color buffer for layer of size %dx%d, sample count %d",
+ m_pixelSize.width(), m_pixelSize.height(), effectiveSamples);
+ releaseResources();
+ return;
+ }
+ m_texture = m_rhi->newTexture(m_format, m_pixelSize, 1, textureFlags);
+ if (!m_texture->create()) {
+ qWarning("Failed to build texture for layer of size %dx%d", m_pixelSize.width(), m_pixelSize.height());
+ releaseResources();
+ return;
+ }
+ if (depthBufferEnabled) {
+ m_ds = m_rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, m_pixelSize, effectiveSamples);
+ if (!m_ds->create()) {
+ qWarning("Failed to build depth-stencil buffer for layer");
+ releaseResources();
+ return;
+ }
+ }
+ QRhiTextureRenderTargetDescription desc;
+ QRhiColorAttachment color0(m_msaaColorBuffer);
+ color0.setResolveTexture(m_texture);
+ desc.setColorAttachments({ color0 });
+ if (depthBufferEnabled)
+ desc.setDepthStencilBuffer(m_ds);
+ m_rt = m_rhi->newTextureRenderTarget(desc);
+ m_rtRp = m_rt->newCompatibleRenderPassDescriptor();
+ if (!m_rtRp) {
+ qWarning("Failed to build render pass descriptor for layer");
+ releaseResources();
+ return;
+ }
+ m_rt->setRenderPassDescriptor(m_rtRp);
+ if (!m_rt->create()) {
+ qWarning("Failed to build texture render target for layer");
+ releaseResources();
+ return;
+ }
+ } else {
+ releaseResources();
+ m_texture = m_rhi->newTexture(m_format, m_pixelSize, 1, textureFlags);
+ if (!m_texture->create()) {
+ qWarning("Failed to build texture for layer of size %dx%d", m_pixelSize.width(), m_pixelSize.height());
+ releaseResources();
+ return;
+ }
+ if (depthBufferEnabled) {
+ m_ds = m_rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, m_pixelSize);
+ if (!m_ds->create()) {
+ qWarning("Failed to build depth-stencil buffer for layer");
+ releaseResources();
+ return;
+ }
+ }
+ QRhiColorAttachment color0(m_texture);
+ if (m_recursive) {
+ // Here rt is associated with m_secondaryTexture instead of m_texture.
+ // We will issue a copy to m_texture afterwards.
+ m_secondaryTexture = m_rhi->newTexture(m_format, m_pixelSize, 1, textureFlags);
+ if (!m_secondaryTexture->create()) {
+ qWarning("Failed to build texture for layer of size %dx%d", m_pixelSize.width(), m_pixelSize.height());
+ releaseResources();
+ return;
+ }
+ color0.setTexture(m_secondaryTexture);
+ }
+ if (depthBufferEnabled)
+ m_rt = m_rhi->newTextureRenderTarget({ color0, m_ds });
+ else
+ m_rt = m_rhi->newTextureRenderTarget({ color0 });
+ m_rtRp = m_rt->newCompatibleRenderPassDescriptor();
+ if (!m_rtRp) {
+ qWarning("Failed to build render pass descriptor for layer");
+ releaseResources();
+ return;
+ }
+ m_rt->setRenderPassDescriptor(m_rtRp);
+ if (!m_rt->create()) {
+ qWarning("Failed to build texture render target for layer");
+ releaseResources();
+ return;
+ }
+ }
+ }
+
+ QSGNode *root = m_item;
+ while (root->firstChild() && root->type() != QSGNode::RootNodeType)
+ root = root->firstChild();
+ if (root->type() != QSGNode::RootNodeType)
+ return;
+
+ if (!m_renderer) {
+ const bool useDepth = m_context->useDepthBufferFor2D();
+ const QSGRendererInterface::RenderMode renderMode = useDepth ? QSGRendererInterface::RenderMode2D
+ : QSGRendererInterface::RenderMode2DNoDepthBuffer;
+ m_renderer = m_context->createRenderer(renderMode);
+ connect(m_renderer, SIGNAL(sceneGraphChanged()), this, SLOT(markDirtyTexture()));
+ }
+ m_renderer->setRootNode(static_cast<QSGRootNode *>(root));
+ root->markDirty(QSGNode::DirtyForceUpdate); // Force matrix, clip and opacity update.
+ m_renderer->nodeChanged(root, QSGNode::DirtyForceUpdate); // Force render list update.
+
+ // This must not be moved. The flag must be reset only after the
+ // nodeChanged otherwise we end up with constantly updating even when the
+ // layer contents do not change.
+ m_dirtyTexture = false;
+
+ m_renderer->setDevicePixelRatio(m_dpr);
+ m_renderer->setDeviceRect(m_pixelSize);
+ m_renderer->setViewportRect(m_pixelSize);
+ QRectF mirrored; // in logical coordinates (no dpr) since this gets passed to setProjectionMatrixToRect()
+ if (m_rhi->isYUpInFramebuffer()) {
+ mirrored = QRectF(m_mirrorHorizontal ? m_logicalRect.right() : m_logicalRect.left(),
+ m_mirrorVertical ? m_logicalRect.bottom() : m_logicalRect.top(),
+ m_mirrorHorizontal ? -m_logicalRect.width() : m_logicalRect.width(),
+ m_mirrorVertical ? -m_logicalRect.height() : m_logicalRect.height());
+ } else {
+ mirrored = QRectF(m_mirrorHorizontal ? m_logicalRect.right() : m_logicalRect.left(),
+ m_mirrorVertical ? m_logicalRect.top() : m_logicalRect.bottom(),
+ m_mirrorHorizontal ? -m_logicalRect.width() : m_logicalRect.width(),
+ m_mirrorVertical ? m_logicalRect.height() : -m_logicalRect.height());
+ }
+ QSGAbstractRenderer::MatrixTransformFlags matrixFlags;
+ if (!m_rhi->isYUpInNDC())
+ matrixFlags |= QSGAbstractRenderer::MatrixTransformFlipY;
+ m_renderer->setProjectionMatrixToRect(mirrored, matrixFlags);
+ m_renderer->setClearColor(Qt::transparent);
+ m_renderer->setRenderTarget({ m_rt, m_rtRp, m_context->currentFrameCommandBuffer() });
+
+ QRhiResourceUpdateBatch *resourceUpdates = nullptr;
+
+ // render with our own "sub-renderer" (this will just add a render pass to the command buffer)
+ if (m_multisampling) {
+ m_context->renderNextFrame(m_renderer);
+ } else {
+ if (m_recursive) {
+ m_context->renderNextFrame(m_renderer);
+ if (!resourceUpdates)
+ resourceUpdates = m_rhi->nextResourceUpdateBatch();
+ resourceUpdates->copyTexture(m_texture, m_secondaryTexture);
+ } else {
+ m_context->renderNextFrame(m_renderer);
+ }
+ }
+
+ if (m_mipmap) {
+ if (!resourceUpdates)
+ resourceUpdates = m_rhi->nextResourceUpdateBatch();
+ // going to be expensive - if done every frame - but the user asked for it...
+ resourceUpdates->generateMips(m_texture);
+ }
+
+ // Do not defer committing the resource updates to the main pass - with
+ // multiple layers there can be dependencies, so the texture should be
+ // usable once we return.
+ m_context->currentFrameCommandBuffer()->resourceUpdate(resourceUpdates);
+
+ root->markDirty(QSGNode::DirtyForceUpdate); // Force matrix, clip, opacity and render list update.
+
+ if (m_recursive)
+ markDirtyTexture(); // Continuously update if 'live' and 'recursive'.
+}
+
+QImage QSGRhiLayer::toImage() const
+{
+ if (!m_texture)
+ return QImage();
+
+ QRhiCommandBuffer *cb = m_context->currentFrameCommandBuffer();
+ QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch();
+ QRhiReadbackResult result;
+ QRhiReadbackDescription readbackDesc(m_texture);
+ resourceUpdates->readBackTexture(readbackDesc, &result);
+
+ cb->resourceUpdate(resourceUpdates);
+
+ // Inefficient but what can you do. We need the results right away. This is
+ // not something that occurs in a normal rendering process anyway. (used by
+ // QQuickItem's grabToImage).
+ m_rhi->finish();
+
+ if (result.data.isEmpty()) {
+ qWarning("Layer grab failed");
+ return QImage();
+ }
+
+ // There is little room for negotiation here, the texture is one of the formats from setFormat.
+ // Also, Quick is always premultiplied alpha.
+ QImage::Format imageFormat = QImage::Format_RGBA8888_Premultiplied;
+ if (m_format == QRhiTexture::RGBA16F)
+ imageFormat = QImage::Format_RGBA16FPx4_Premultiplied;
+ else if (m_format == QRhiTexture::RGBA32F)
+ imageFormat = QImage::Format_RGBA32FPx4_Premultiplied;
+
+ const uchar *p = reinterpret_cast<const uchar *>(result.data.constData());
+ return QImage(p, result.pixelSize.width(), result.pixelSize.height(), imageFormat).mirrored();
+}
+
+QRectF QSGRhiLayer::normalizedTextureSubRect() const
+{
+ return QRectF(m_mirrorHorizontal ? 1 : 0,
+ m_mirrorVertical ? 0 : 1,
+ m_mirrorHorizontal ? -1 : 1,
+ m_mirrorVertical ? 1 : -1);
+}
+
+#include "moc_qsgrhilayer_p.cpp"