diff options
Diffstat (limited to 'src/quick/scenegraph/util')
22 files changed, 1675 insertions, 1095 deletions
diff --git a/src/quick/scenegraph/util/qsgdefaultpainternode.cpp b/src/quick/scenegraph/util/qsgdefaultpainternode.cpp index 981ea089be..f15ea67b46 100644 --- a/src/quick/scenegraph/util/qsgdefaultpainternode.cpp +++ b/src/quick/scenegraph/util/qsgdefaultpainternode.cpp @@ -55,7 +55,7 @@ QT_BEGIN_NAMESPACE #define QT_MINIMUM_DYNAMIC_FBO_SIZE 64U QSGPainterTexture::QSGPainterTexture() - : QSGPlainTexture() + : QSGPlainTexture(*(new QSGPainterTexturePrivate)) { m_retain_image = true; } @@ -73,6 +73,16 @@ void QSGPainterTexture::bind() m_dirty_rect = QRect(); } +void QSGPainterTexturePrivate::updateRhiTexture(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates) +{ + Q_Q(QSGPainterTexture); + if (!q->m_dirty_rect.isNull()) { + q->setImage(q->m_image); + q->m_dirty_rect = QRect(); + } + QSGPlainTexturePrivate::updateRhiTexture(rhi, resourceUpdates); +} + QSGDefaultPainterNode::QSGDefaultPainterNode(QQuickPaintedItem *item) : QSGPainterNode() , m_preferredRenderTarget(QQuickPaintedItem::Image) @@ -126,6 +136,7 @@ void QSGDefaultPainterNode::paint() return; painter.begin(&m_image); } else { + Q_ASSERT(!m_context->rhi()); if (!m_gl_device) { m_gl_device = new QOpenGLPaintDevice(m_fboSize); m_gl_device->setPaintFlipped(true); @@ -237,7 +248,7 @@ void QSGDefaultPainterNode::updateGeometry() void QSGDefaultPainterNode::updateRenderTarget() { - if (!m_extensionsChecked) { + if (!m_extensionsChecked && !m_context->rhi()) { QOpenGLExtensions *e = static_cast<QOpenGLExtensions *>(QOpenGLContext::currentContext()->functions()); m_multisamplingSupported = e->hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample) && e->hasOpenGLExtension(QOpenGLExtensions::FramebufferBlit); @@ -250,7 +261,10 @@ void QSGDefaultPainterNode::updateRenderTarget() if (m_preferredRenderTarget == QQuickPaintedItem::Image) { m_actualRenderTarget = QQuickPaintedItem::Image; } else { - if (!m_multisamplingSupported && m_smoothPainting) + // Image is the only option when there is no multisample framebuffer + // support and smooth painting is wanted, and when using the RHI. The + // latter may change in the future. + if ((!m_multisamplingSupported && m_smoothPainting) || m_context->rhi()) m_actualRenderTarget = QQuickPaintedItem::Image; else m_actualRenderTarget = m_preferredRenderTarget; @@ -265,7 +279,9 @@ void QSGDefaultPainterNode::updateRenderTarget() } if (m_actualRenderTarget == QQuickPaintedItem::FramebufferObject || - m_actualRenderTarget == QQuickPaintedItem::InvertedYFramebufferObject) { + m_actualRenderTarget == QQuickPaintedItem::InvertedYFramebufferObject) + { + Q_ASSERT(!m_context->rhi()); const QOpenGLContext *ctx = m_context->openglContext(); if (m_fbo && !m_dirtyGeometry && (!ctx->format().samples() || !m_multisamplingSupported)) return; diff --git a/src/quick/scenegraph/util/qsgdefaultpainternode_p.h b/src/quick/scenegraph/util/qsgdefaultpainternode_p.h index 084fc1e004..a86f7397be 100644 --- a/src/quick/scenegraph/util/qsgdefaultpainternode_p.h +++ b/src/quick/scenegraph/util/qsgdefaultpainternode_p.h @@ -53,7 +53,7 @@ #include <private/qsgadaptationlayer_p.h> #include "qsgtexturematerial.h" -#include "qsgtexture_p.h" +#include "qsgplaintexture_p.h" #include <QtQuick/qquickpainteditem.h> @@ -64,9 +64,11 @@ QT_BEGIN_NAMESPACE class QOpenGLFramebufferObject; class QOpenGLPaintDevice; class QSGDefaultRenderContext; +class QSGPainterTexturePrivate; class Q_QUICK_PRIVATE_EXPORT QSGPainterTexture : public QSGPlainTexture { + Q_DECLARE_PRIVATE(QSGPainterTexture) public: QSGPainterTexture(); @@ -78,6 +80,13 @@ private: QRect m_dirty_rect; }; +class QSGPainterTexturePrivate : public QSGPlainTexturePrivate +{ + Q_DECLARE_PUBLIC(QSGPainterTexture) +public: + void updateRhiTexture(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates) override; +}; + class Q_QUICK_PRIVATE_EXPORT QSGDefaultPainterNode : public QSGPainterNode { public: diff --git a/src/quick/scenegraph/util/qsgdepthstencilbuffer.cpp b/src/quick/scenegraph/util/qsgdepthstencilbuffer.cpp index 94912778f8..1154c06d7c 100644 --- a/src/quick/scenegraph/util/qsgdepthstencilbuffer.cpp +++ b/src/quick/scenegraph/util/qsgdepthstencilbuffer.cpp @@ -183,7 +183,9 @@ void QSGDefaultDepthStencilBuffer::free() QSGDepthStencilBufferManager::~QSGDepthStencilBufferManager() { for (Hash::const_iterator it = m_buffers.constBegin(), cend = m_buffers.constEnd(); it != cend; ++it) { - QSGDepthStencilBuffer *buffer = it.value().data(); + QSharedPointer<QSGDepthStencilBuffer> buffer = it.value().toStrongRef(); + Q_ASSERT_X(buffer, "~QSGDepthStencilBufferManager", + "~QSGDepthStencilBuffer is supposed to unregister from the manager"); buffer->free(); buffer->m_manager = nullptr; } diff --git a/src/quick/scenegraph/util/qsgengine.cpp b/src/quick/scenegraph/util/qsgengine.cpp index 91fa46033c..4880d98871 100644 --- a/src/quick/scenegraph/util/qsgengine.cpp +++ b/src/quick/scenegraph/util/qsgengine.cpp @@ -42,7 +42,7 @@ #include <QtQuick/qsgtexture.h> #include <private/qsgcontext_p.h> #include <private/qsgrenderer_p.h> -#include <private/qsgtexture_p.h> +#include <private/qsgplaintexture_p.h> #if QT_CONFIG(opengl) # include <QtGui/QOpenGLContext> @@ -69,6 +69,10 @@ QT_BEGIN_NAMESPACE Most of the time you will instead want to subclass QQuickItem and insert your QSGNode in a normal QtQuick scene by overriding QQuickItem::updatePaintNode(). + \warning This class is only suitable when working directly with OpenGL. It + is not compatible with the \l{Scene Graph Adaptations}{RHI-based rendering + path}. + \sa QSGAbstractRenderer */ @@ -126,12 +130,29 @@ void QSGEngine::initialize(QOpenGLContext *context) #endif if (d->sgRenderContext && !d->sgRenderContext->isValid()) { d->sgRenderContext->setAttachToGraphicsContext(false); - d->sgRenderContext->initialize(context); +#if QT_CONFIG(opengl) + QSGDefaultRenderContext *rc = qobject_cast<QSGDefaultRenderContext *>(d->sgRenderContext.data()); + if (rc) { + QSGDefaultRenderContext::InitParams params; + params.sampleCount = qMax(1, context->format().samples()); + params.openGLContext = context; + // leave the size hint and surface unset, we do not know, that's fine + rc->initialize(¶ms); + } else { + d->sgRenderContext->initialize(nullptr); + } +#else + d->sgRenderContext->initialize(nullptr); +#endif #if QT_CONFIG(opengl) if (context) connect(context, &QOpenGLContext::aboutToBeDestroyed, this, &QSGEngine::invalidate); #endif } + +#if !QT_CONFIG(opengl) + Q_UNUSED(context); +#endif } /*! diff --git a/src/quick/scenegraph/util/qsgengine.h b/src/quick/scenegraph/util/qsgengine.h index e48b7784ae..c5a59b47e7 100644 --- a/src/quick/scenegraph/util/qsgengine.h +++ b/src/quick/scenegraph/util/qsgengine.h @@ -54,6 +54,8 @@ class QSGRectangleNode; class QSGImageNode; class QSGNinePatchNode; +// ### Qt 6: Remove or redesign. + class Q_QUICK_EXPORT QSGEngine : public QObject { Q_OBJECT diff --git a/src/quick/scenegraph/util/qsgflatcolormaterial.cpp b/src/quick/scenegraph/util/qsgflatcolormaterial.cpp index 28f6113a60..87941bf31a 100644 --- a/src/quick/scenegraph/util/qsgflatcolormaterial.cpp +++ b/src/quick/scenegraph/util/qsgflatcolormaterial.cpp @@ -116,6 +116,51 @@ void FlatColorMaterialShader::initialize() } +class FlatColorMaterialRhiShader : public QSGMaterialRhiShader +{ +public: + FlatColorMaterialRhiShader(); + + bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override; +}; + +FlatColorMaterialRhiShader::FlatColorMaterialRhiShader() +{ + setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/flatcolor.vert.qsb")); + setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/flatcolor.frag.qsb")); +} + +bool FlatColorMaterialRhiShader::updateUniformData(RenderState &state, + QSGMaterial *newMaterial, + QSGMaterial *oldMaterial) +{ + Q_ASSERT(!oldMaterial || newMaterial->type() == oldMaterial->type()); + QSGFlatColorMaterial *oldMat = static_cast<QSGFlatColorMaterial *>(oldMaterial); + QSGFlatColorMaterial *mat = static_cast<QSGFlatColorMaterial *>(newMaterial); + bool changed = false; + QByteArray *buf = state.uniformData(); + + if (state.isMatrixDirty()) { + const QMatrix4x4 m = state.combinedMatrix(); + memcpy(buf->data(), m.constData(), 64); + changed = true; + } + + const QColor &c = mat->color(); + if (!oldMat || c != oldMat->color() || state.isOpacityDirty()) { + const float opacity = state.opacity() * c.alphaF(); + QVector4D v(c.redF() * opacity, + c.greenF() * opacity, + c.blueF() * opacity, + opacity); + Q_ASSERT(sizeof(v) == 16); + memcpy(buf->data() + 64, &v, 16); + changed = true; + } + + return changed; +} + /*! \class QSGFlatColorMaterial @@ -126,7 +171,7 @@ void FlatColorMaterialShader::initialize() \inmodule QtQuick \ingroup qtquick-scenegraph-materials - \warning This utility class is only functional when running with the OpenGL + \warning This utility class is only functional when running with the default backend of the Qt Quick scenegraph. The flat color material will fill every pixel in a geometry using @@ -150,10 +195,9 @@ void FlatColorMaterialShader::initialize() QSGFlatColorMaterial::QSGFlatColorMaterial() : m_color(QColor(255, 255, 255)) { + setFlag(SupportsRhiShader, true); } - - /*! \fn const QColor &QSGFlatColorMaterial::color() const @@ -193,7 +237,10 @@ QSGMaterialType *QSGFlatColorMaterial::type() const QSGMaterialShader *QSGFlatColorMaterial::createShader() const { - return new FlatColorMaterialShader; + if (flags().testFlag(RhiShaderWanted)) + return new FlatColorMaterialRhiShader; + else + return new FlatColorMaterialShader; } diff --git a/src/quick/scenegraph/util/qsgatlastexture.cpp b/src/quick/scenegraph/util/qsgopenglatlastexture.cpp index 921ed0c1fc..75a874424a 100644 --- a/src/quick/scenegraph/util/qsgatlastexture.cpp +++ b/src/quick/scenegraph/util/qsgopenglatlastexture.cpp @@ -37,7 +37,7 @@ ** ****************************************************************************/ -#include "qsgatlastexture_p.h" +#include "qsgopenglatlastexture_p.h" #include <QtCore/QVarLengthArray> #include <QtCore/QElapsedTimer> @@ -71,24 +71,22 @@ static QElapsedTimer qsg_renderer_timer; DEFINE_BOOL_CONFIG_OPTION(qsgEnableCompressedAtlas, QSG_ENABLE_COMPRESSED_ATLAS) -namespace QSGAtlasTexture +namespace QSGOpenGLAtlasTexture { -Manager::Manager() +Manager::Manager(const QSize &surfacePixelSize) : m_atlas(nullptr) { QOpenGLContext *gl = QOpenGLContext::currentContext(); Q_ASSERT(gl); - QSurface *surface = gl->surface(); - QSize surfaceSize = surface->size(); int max; gl->functions()->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max); - int w = qMin(max, qt_sg_envInt("QSG_ATLAS_WIDTH", qMax(512U, qNextPowerOfTwo(surfaceSize.width() - 1)))); - int h = qMin(max, qt_sg_envInt("QSG_ATLAS_HEIGHT", qMax(512U, qNextPowerOfTwo(surfaceSize.height() - 1)))); + int w = qMin(max, qt_sg_envInt("QSG_ATLAS_WIDTH", qMax(512U, qNextPowerOfTwo(surfacePixelSize.width() - 1)))); + int h = qMin(max, qt_sg_envInt("QSG_ATLAS_HEIGHT", qMax(512U, qNextPowerOfTwo(surfacePixelSize.height() - 1)))); - if (surface->surfaceClass() == QSurface::Window) { - QWindow *window = static_cast<QWindow *>(surface); + if (gl->surface()->surfaceClass() == QSurface::Window) { + QWindow *window = static_cast<QWindow *>(gl->surface()); // Coverwindows, optimize for memory rather than speed if ((window->type() & Qt::CoverWindow) == Qt::CoverWindow) { w /= 2; @@ -99,10 +97,9 @@ Manager::Manager() m_atlas_size_limit = qt_sg_envInt("QSG_ATLAS_SIZE_LIMIT", qMax(w, h) / 2); m_atlas_size = QSize(w, h); - qCDebug(QSG_LOG_INFO, "texture atlas dimensions: %dx%d", w, h); + qCDebug(QSG_LOG_INFO, "opengl texture atlas dimensions: %dx%d", w, h); } - Manager::~Manager() { Q_ASSERT(m_atlas == nullptr); @@ -153,6 +150,10 @@ QSGTexture *Manager::create(const QSGCompressedTextureFactory *factory) case QOpenGLTexture::RGB8_ETC2: case QOpenGLTexture::RGBA8_ETC2_EAC: case QOpenGLTexture::RGB8_PunchThrough_Alpha1_ETC2: + case QOpenGLTexture::RGB_DXT1: + case QOpenGLTexture::RGBA_DXT1: + case QOpenGLTexture::RGBA_DXT3: + case QOpenGLTexture::RGBA_DXT5: break; default: return t; @@ -161,8 +162,12 @@ QSGTexture *Manager::create(const QSGCompressedTextureFactory *factory) QSize size = factory->m_textureData.size(); if (size.width() < m_atlas_size_limit && size.height() < m_atlas_size_limit) { QHash<unsigned int, QSGCompressedAtlasTexture::Atlas*>::iterator i = m_atlases.find(format); - if (i == m_atlases.end()) - i = m_atlases.insert(format, new QSGCompressedAtlasTexture::Atlas(m_atlas_size, format)); + if (i == m_atlases.end()) { + // must be multiple of 4 + QSize paddedSize(((m_atlas_size.width() + 3) / 4) * 4, ((m_atlas_size.height() + 3) / 4) * 4); + i = m_atlases.insert(format, new QSGCompressedAtlasTexture::Atlas(paddedSize, format)); + } + // must be multiple of 4 QSize paddedSize(((size.width() + 3) / 4) * 4, ((size.height() + 3) / 4) * 4); QByteArray data = factory->m_textureData.data(); @@ -593,4 +598,4 @@ QSGTexture *Texture::removedFromAtlas() const QT_END_NAMESPACE -#include "moc_qsgatlastexture_p.cpp" +#include "moc_qsgopenglatlastexture_p.cpp" diff --git a/src/quick/scenegraph/util/qsgatlastexture_p.h b/src/quick/scenegraph/util/qsgopenglatlastexture_p.h index 14dc8f7958..f8dd7cdf02 100644 --- a/src/quick/scenegraph/util/qsgatlastexture_p.h +++ b/src/quick/scenegraph/util/qsgopenglatlastexture_p.h @@ -37,8 +37,8 @@ ** ****************************************************************************/ -#ifndef QSGATLASTEXTURE_P_H -#define QSGATLASTEXTURE_P_H +#ifndef QSGOPENGLATLASTEXTURE_P_H +#define QSGOPENGLATLASTEXTURE_P_H // // W A R N I N G @@ -56,7 +56,7 @@ #include <QtGui/qopengl.h> #include <QtQuick/QSGTexture> -#include <QtQuick/private/qsgtexture_p.h> +#include <QtQuick/private/qsgplaintexture_p.h> #include <QtQuick/private/qsgareaallocator_p.h> QT_BEGIN_NAMESPACE @@ -66,7 +66,7 @@ namespace QSGCompressedAtlasTexture { } class QSGCompressedTextureFactory; -namespace QSGAtlasTexture +namespace QSGOpenGLAtlasTexture { class Texture; @@ -78,7 +78,7 @@ class Manager : public QObject Q_OBJECT public: - Manager(); + Manager(const QSize &surfacePixelSize); ~Manager(); QSGTexture *create(const QImage &image, bool hasAlphaChannel); diff --git a/src/quick/scenegraph/util/qsgplaintexture.cpp b/src/quick/scenegraph/util/qsgplaintexture.cpp new file mode 100644 index 0000000000..fdebe03494 --- /dev/null +++ b/src/quick/scenegraph/util/qsgplaintexture.cpp @@ -0,0 +1,457 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgplaintexture_p.h" +#include "qsgrhinativetextureimporter_p.h" +#include <QtQuick/private/qsgcontext_p.h> +#include <qmath.h> +#include <private/qquickprofiler_p.h> +#include <private/qqmlglobal_p.h> +#include <QtGui/qguiapplication.h> +#include <QtGui/qpa/qplatformnativeinterface.h> +#if QT_CONFIG(opengl) +# include <QtGui/qopenglcontext.h> +# include <QtGui/qopenglfunctions.h> +# include <QtGui/private/qopengltextureuploader_p.h> +# include <private/qsgdefaultrendercontext_p.h> +#endif +#include <QtGui/private/qrhi_p.h> + +#if QT_CONFIG(opengl) +static QElapsedTimer qsg_renderer_timer; +#endif + +#ifndef GL_BGRA +#define GL_BGRA 0x80E1 +#endif + +#ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#endif + +QT_BEGIN_NAMESPACE + +QSGPlainTexture::QSGPlainTexture() + : QSGTexture(*(new QSGPlainTexturePrivate)) + , m_texture_id(0) + , m_texture(nullptr) + , m_has_alpha(false) + , m_dirty_texture(false) + , m_dirty_bind_options(false) + , m_owns_texture(true) + , m_mipmaps_generated(false) + , m_retain_image(false) + , m_mipmap_warned(false) +{ +} + +QSGPlainTexture::QSGPlainTexture(QSGPlainTexturePrivate &dd) + : QSGTexture(dd) + , m_texture_id(0) + , m_texture(nullptr) + , m_has_alpha(false) + , m_dirty_texture(false) + , m_dirty_bind_options(false) + , m_owns_texture(true) + , m_mipmaps_generated(false) + , m_retain_image(false) + , m_mipmap_warned(false) +{ +} + +QSGPlainTexture::~QSGPlainTexture() +{ +#if QT_CONFIG(opengl) + if (m_texture_id && m_owns_texture && QOpenGLContext::currentContext()) + QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &m_texture_id); +#endif + if (m_texture && m_owns_texture) + delete m_texture; +} + +void QSGPlainTexture::setImage(const QImage &image) +{ + m_image = image; + m_texture_size = image.size(); + m_has_alpha = image.hasAlphaChannel(); + m_dirty_texture = true; + m_dirty_bind_options = true; + m_mipmaps_generated = false; + } + +int QSGPlainTexture::textureId() const // legacy (GL-only) +{ + if (m_dirty_texture) { + if (m_image.isNull()) { + // The actual texture and id will be updated/deleted in a later bind() + // or ~QSGPlainTexture so just keep it minimal here. + return 0; + } else if (m_texture_id == 0){ +#if QT_CONFIG(opengl) + // Generate a texture id for use later and return it. + QOpenGLContext::currentContext()->functions()->glGenTextures(1, &const_cast<QSGPlainTexture *>(this)->m_texture_id); +#endif + return m_texture_id; + } + } + return m_texture_id; +} + +void QSGPlainTexture::setTextureId(int id) // legacy (GL-only) +{ +#if QT_CONFIG(opengl) + if (m_texture_id && m_owns_texture) + QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &m_texture_id); +#endif + + m_texture_id = id; + m_dirty_texture = false; + m_dirty_bind_options = true; + m_image = QImage(); + m_mipmaps_generated = false; +} + +void QSGPlainTexture::bind() // legacy (GL-only) +{ +#if QT_CONFIG(opengl) + QOpenGLContext *context = QOpenGLContext::currentContext(); + QOpenGLFunctions *funcs = context->functions(); + if (!m_dirty_texture) { + funcs->glBindTexture(GL_TEXTURE_2D, m_texture_id); + if (mipmapFiltering() != QSGTexture::None && !m_mipmaps_generated) { + funcs->glGenerateMipmap(GL_TEXTURE_2D); + m_mipmaps_generated = true; + } + updateBindOptions(m_dirty_bind_options); + m_dirty_bind_options = false; + return; + } + + m_dirty_texture = false; + + bool profileFrames = QSG_LOG_TIME_TEXTURE().isDebugEnabled(); + if (profileFrames) + qsg_renderer_timer.start(); + Q_QUICK_SG_PROFILE_START_SYNCHRONIZED(QQuickProfiler::SceneGraphTexturePrepare, + QQuickProfiler::SceneGraphTextureDeletion); + + + if (m_image.isNull()) { + if (m_texture_id && m_owns_texture) { + funcs->glDeleteTextures(1, &m_texture_id); + qCDebug(QSG_LOG_TIME_TEXTURE, "plain texture deleted in %dms - %dx%d", + (int) qsg_renderer_timer.elapsed(), + m_texture_size.width(), + m_texture_size.height()); + Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphTextureDeletion, + QQuickProfiler::SceneGraphTextureDeletionDelete); + } + m_texture_id = 0; + m_texture_size = QSize(); + m_has_alpha = false; + + return; + } + + if (m_texture_id == 0) + funcs->glGenTextures(1, &m_texture_id); + funcs->glBindTexture(GL_TEXTURE_2D, m_texture_id); + + qint64 bindTime = 0; + if (profileFrames) + bindTime = qsg_renderer_timer.nsecsElapsed(); + Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphTexturePrepare, + QQuickProfiler::SceneGraphTexturePrepareBind); + + // ### TODO: check for out-of-memory situations... + + QOpenGLTextureUploader::BindOptions options = QOpenGLTextureUploader::PremultipliedAlphaBindOption; + + // Downscale the texture to fit inside the max texture limit if it is too big. + // It would be better if the image was already downscaled to the right size, + // but this information is not always available at that time, so as a last + // resort we can do it here. Texture coordinates are normalized, so it + // won't cause any problems and actual texture sizes will be written + // based on QSGTexture::textureSize which is updated after this, so that + // should be ok. + int max; + if (auto rc = QSGDefaultRenderContext::from(context)) + max = rc->maxTextureSize(); + else + funcs->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max); + + m_texture_size = m_texture_size.boundedTo(QSize(max, max)); + + // Scale to a power of two size if mipmapping is requested and the + // texture is npot and npot textures are not properly supported. + if (mipmapFiltering() != QSGTexture::None + && !funcs->hasOpenGLFeature(QOpenGLFunctions::NPOTTextures)) { + options |= QOpenGLTextureUploader::PowerOfTwoBindOption; + } + + updateBindOptions(m_dirty_bind_options); + + QOpenGLTextureUploader::textureImage(GL_TEXTURE_2D, m_image, options, QSize(max, max)); + + qint64 uploadTime = 0; + if (profileFrames) + uploadTime = qsg_renderer_timer.nsecsElapsed(); + Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphTexturePrepare, + QQuickProfiler::SceneGraphTexturePrepareUpload); + + if (mipmapFiltering() != QSGTexture::None) { + funcs->glGenerateMipmap(GL_TEXTURE_2D); + m_mipmaps_generated = true; + } + + qint64 mipmapTime = 0; + if (profileFrames) { + mipmapTime = qsg_renderer_timer.nsecsElapsed(); + qCDebug(QSG_LOG_TIME_TEXTURE, + "plain texture uploaded in: %dms (%dx%d), bind=%d, upload=%d, mipmap=%d%s", + int(mipmapTime / 1000000), + m_texture_size.width(), m_texture_size.height(), + int(bindTime / 1000000), + int((uploadTime - bindTime)/1000000), + int((mipmapTime - uploadTime)/1000000), + m_texture_size != m_image.size() ? " (scaled to GL_MAX_TEXTURE_SIZE)" : ""); + } + Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphTexturePrepare, + QQuickProfiler::SceneGraphTexturePrepareMipmap); + + m_texture_rect = QRectF(0, 0, 1, 1); + + m_dirty_bind_options = false; + if (!m_retain_image) + m_image = QImage(); +#endif +} + +void QSGPlainTexture::setTexture(QRhiTexture *texture) // RHI only +{ + if (m_texture && m_owns_texture && m_texture != texture) + delete m_texture; + + m_texture = texture; + m_dirty_texture = false; + m_dirty_bind_options = true; + m_image = QImage(); + m_mipmaps_generated = false; +} + +void QSGPlainTexture::setTextureFromNativeObject(QRhi *rhi, QQuickWindow::NativeObjectType type, + const void *nativeObjectPtr, int nativeLayout, + const QSize &size, bool mipmap) +{ + Q_UNUSED(type); + + QRhiTexture::Flags flags = 0; + if (mipmap) + flags |= QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips; + + QRhiTexture *t = rhi->newTexture(QRhiTexture::RGBA8, size, 1, flags); + + // ownership of the native object is never taken + QSGRhiNativeTextureImporter::buildWrapper(rhi, t, nativeObjectPtr, nativeLayout); + + setTexture(t); +} + +int QSGPlainTexturePrivate::comparisonKey() const +{ + Q_Q(const QSGPlainTexture); + + // not textureId() as that would create an id when not yet done - that's not wanted here + if (q->m_texture_id) + return q->m_texture_id; + + if (q->m_texture) + return int(qintptr(q->m_texture)); + + // two textures (and so materials) with not-yet-created texture underneath are never equal + return int(qintptr(q)); +} + +QRhiTexture *QSGPlainTexturePrivate::rhiTexture() const +{ + Q_Q(const QSGPlainTexture); + return q->m_texture; +} + +void QSGPlainTexturePrivate::updateRhiTexture(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates) +{ + Q_Q(QSGPlainTexture); + + const bool hasMipMaps = q->mipmapFiltering() != QSGTexture::None; + const bool mipmappingChanged = q->m_texture && ((hasMipMaps && !q->m_texture->flags().testFlag(QRhiTexture::MipMapped)) // did not have it before + || (!hasMipMaps && q->m_texture->flags().testFlag(QRhiTexture::MipMapped))); // does not have it anymore + + if (!q->m_dirty_texture) { + if (!q->m_texture) + return; + if (q->m_texture && !mipmappingChanged) { + if (hasMipMaps && !q->m_mipmaps_generated) { + resourceUpdates->generateMips(q->m_texture); + q->m_mipmaps_generated = true; + } + return; + } + } + + if (q->m_image.isNull()) { + if (!q->m_dirty_texture && mipmappingChanged) { + // Full Mipmap Panic! + if (!q->m_mipmap_warned) { + qWarning("QSGPlainTexture: Mipmap settings changed without having image data available. " + "Call setImage() again or enable m_retain_image. " + "Falling back to previous mipmap filtering mode."); + q->m_mipmap_warned = true; + } + // leave the texture valid and rather ignore the mipmap mode change attempt + q->setMipmapFiltering(m_last_mipmap_filter); + return; + } + + if (q->m_texture && q->m_owns_texture) + delete q->m_texture; + + q->m_texture = nullptr; + q->m_texture_size = QSize(); + q->m_has_alpha = false; + + q->m_dirty_texture = false; + return; + } + + q->m_dirty_texture = false; + + QImage tmp; + bool bgra = false; + bool needsConvert = false; + if (q->m_image.format() == QImage::Format_RGB32 || q->m_image.format() == QImage::Format_ARGB32_Premultiplied) { +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + if (rhi->isTextureFormatSupported(QRhiTexture::BGRA8)) { + tmp = q->m_image; + bgra = true; + } else { + needsConvert = true; + } +#else + needsConvert = true; +#endif + } else if (q->m_image.format() == QImage::Format_RGBX8888 || q->m_image.format() == QImage::Format_RGBA8888_Premultiplied) { + tmp = q->m_image; + } else { + needsConvert = true; + } + + if (needsConvert) + tmp = q->m_image.convertToFormat(QImage::Format_RGBA8888_Premultiplied); + + // Downscale the texture to fit inside the max texture limit if it is too big. + // It would be better if the image was already downscaled to the right size, + // but this information is not always available at that time, so as a last + // resort we can do it here. Texture coordinates are normalized, so it + // won't cause any problems and actual texture sizes will be written + // based on QSGTexture::textureSize which is updated after this, so that + // should be ok. + const int max = rhi->resourceLimit(QRhi::TextureSizeMax); + if (tmp.width() > max || tmp.height() > max) { + tmp = tmp.scaled(qMin(max, tmp.width()), qMin(max, tmp.height()), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + q->m_texture_size = tmp.size(); + } + + if ((q->mipmapFiltering() != QSGTexture::None + || q->horizontalWrapMode() != QSGTexture::ClampToEdge + || q->verticalWrapMode() != QSGTexture::ClampToEdge) + && !rhi->isFeatureSupported(QRhi::NPOTTextureRepeat)) + { + const int w = qNextPowerOfTwo(tmp.width() - 1); + const int h = qNextPowerOfTwo(tmp.height() - 1); + if (tmp.width() != w || tmp.height() != h) { + tmp = tmp.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + q->m_texture_size = tmp.size(); + } + } + + bool needsRebuild = q->m_texture && q->m_texture->pixelSize() != q->m_texture_size; + + if (mipmappingChanged) { + QRhiTexture::Flags f = q->m_texture->flags(); + f.setFlag(QRhiTexture::MipMapped, hasMipMaps); + f.setFlag(QRhiTexture::UsedWithGenerateMips, hasMipMaps); + q->m_texture->setFlags(f); + needsRebuild = true; + } + + if (!q->m_texture) { + QRhiTexture::Flags f = 0; + if (hasMipMaps) + f |= QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips; + + q->m_texture = rhi->newTexture(bgra ? QRhiTexture::BGRA8 : QRhiTexture::RGBA8, q->m_texture_size, 1, f); + needsRebuild = true; + } + + if (needsRebuild) { + if (!q->m_texture->build()) { + qWarning("Failed to build texture for QSGPlainTexture (size %dx%d)", + q->m_texture_size.width(), q->m_texture_size.height()); + return; + } + } + + if (tmp.width() * 4 != tmp.bytesPerLine()) + tmp = tmp.copy(); + + resourceUpdates->uploadTexture(q->m_texture, tmp); + + if (hasMipMaps) { + resourceUpdates->generateMips(q->m_texture); + q->m_mipmaps_generated = true; + } + + m_last_mipmap_filter = q->mipmapFiltering(); + q->m_texture_rect = QRectF(0, 0, 1, 1); + + if (!q->m_retain_image) + q->m_image = QImage(); +} + +QT_END_NAMESPACE diff --git a/src/quick/scenegraph/util/qsgtexture_p.h b/src/quick/scenegraph/util/qsgplaintexture_p.h index 18dd5eff68..1eb0b59d2e 100644 --- a/src/quick/scenegraph/util/qsgtexture_p.h +++ b/src/quick/scenegraph/util/qsgplaintexture_p.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQuick module of the Qt Toolkit. @@ -37,8 +37,8 @@ ** ****************************************************************************/ -#ifndef QSGTEXTURE_P_H -#define QSGTEXTURE_P_H +#ifndef QSGPLAINTEXTURE_P_H +#define QSGPLAINTEXTURE_P_H // // W A R N I N G @@ -51,36 +51,18 @@ // We mean it. // -#include <QtQuick/qtquickglobal.h> -#include <private/qobject_p.h> -#if QT_CONFIG(opengl) -# include <QtGui/qopengl.h> -#endif -#include "qsgtexture.h" -#include <QtQuick/private/qsgcontext_p.h> +#include <QtQuick/private/qtquickglobal_p.h> +#include <QtQuick/private/qsgtexture_p.h> +#include <QtQuick/qquickwindow.h> QT_BEGIN_NAMESPACE -class QSGTexturePrivate : public QObjectPrivate -{ - Q_DECLARE_PUBLIC(QSGTexture) -public: - QSGTexturePrivate(); - - uint wrapChanged : 1; - uint filteringChanged : 1; - uint anisotropyChanged : 1; - - uint horizontalWrap : 2; - uint verticalWrap : 2; - uint mipmapMode : 2; - uint filterMode : 2; - uint anisotropyLevel: 3; -}; +class QSGPlainTexturePrivate; class Q_QUICK_PRIVATE_EXPORT QSGPlainTexture : public QSGTexture { Q_OBJECT + Q_DECLARE_PRIVATE(QSGPlainTexture) public: QSGPlainTexture(); ~QSGPlainTexture() override; @@ -103,6 +85,11 @@ public: void bind() override; + void setTexture(QRhiTexture *texture); + void setTextureFromNativeObject(QRhi *rhi, QQuickWindow::NativeObjectType type, + const void *nativeObjectPtr, int nativeLayout, + const QSize &size, bool mipmap); + static QSGPlainTexture *fromImage(const QImage &image) { QSGPlainTexture *t = new QSGPlainTexture(); t->setImage(image); @@ -110,22 +97,35 @@ public: } protected: + QSGPlainTexture(QSGPlainTexturePrivate &dd); + QImage m_image; uint m_texture_id; QSize m_texture_size; QRectF m_texture_rect; + QRhiTexture *m_texture; uint m_has_alpha : 1; uint m_dirty_texture : 1; - uint m_dirty_bind_options : 1; + uint m_dirty_bind_options : 1; // legacy (GL-only) uint m_owns_texture : 1; uint m_mipmaps_generated : 1; - uint m_retain_image: 1; + uint m_retain_image : 1; + uint m_mipmap_warned : 1; // RHI only }; -Q_QUICK_PRIVATE_EXPORT bool qsg_safeguard_texture(QSGTexture *); +class QSGPlainTexturePrivate : public QSGTexturePrivate +{ + Q_DECLARE_PUBLIC(QSGPlainTexture) +public: + int comparisonKey() const override; + QRhiTexture *rhiTexture() const override; + void updateRhiTexture(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates) override; + + QSGTexture::Filtering m_last_mipmap_filter = QSGTexture::None; +}; QT_END_NAMESPACE -#endif // QSGTEXTURE_P_H +#endif // QSGPLAINTEXTURE_P_H diff --git a/src/quick/scenegraph/util/qsgrhiatlastexture.cpp b/src/quick/scenegraph/util/qsgrhiatlastexture.cpp new file mode 100644 index 0000000000..3dc1f5f526 --- /dev/null +++ b/src/quick/scenegraph/util/qsgrhiatlastexture.cpp @@ -0,0 +1,491 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgrhiatlastexture_p.h" + +#include <QtCore/QVarLengthArray> +#include <QtCore/QElapsedTimer> +#include <QtCore/QtMath> + +#include <QtGui/QWindow> + +#include <private/qqmlglobal_p.h> +#include <private/qquickprofiler_p.h> +#include <private/qsgdefaultrendercontext_p.h> +#include <private/qsgtexture_p.h> +#if 0 +#include <private/qsgcompressedtexture_p.h> +#include <private/qsgcompressedatlastexture_p.h> +#endif + +QT_BEGIN_NAMESPACE + +int qt_sg_envInt(const char *name, int defaultValue); + +static QElapsedTimer qsg_renderer_timer; + +//DEFINE_BOOL_CONFIG_OPTION(qsgEnableCompressedAtlas, QSG_ENABLE_COMPRESSED_ATLAS) + +namespace QSGRhiAtlasTexture +{ + +Manager::Manager(QSGDefaultRenderContext *rc, const QSize &surfacePixelSize, QSurface *maybeSurface) + : m_rc(rc) + , m_rhi(rc->rhi()) +{ + const int maxSize = m_rhi->resourceLimit(QRhi::TextureSizeMax); + int w = qMin(maxSize, qt_sg_envInt("QSG_ATLAS_WIDTH", qMax(512U, qNextPowerOfTwo(surfacePixelSize.width() - 1)))); + int h = qMin(maxSize, qt_sg_envInt("QSG_ATLAS_HEIGHT", qMax(512U, qNextPowerOfTwo(surfacePixelSize.height() - 1)))); + + if (maybeSurface && maybeSurface->surfaceClass() == QSurface::Window) { + QWindow *window = static_cast<QWindow *>(maybeSurface); + // Coverwindows, optimize for memory rather than speed + if ((window->type() & Qt::CoverWindow) == Qt::CoverWindow) { + w /= 2; + h /= 2; + } + } + + m_atlas_size_limit = qt_sg_envInt("QSG_ATLAS_SIZE_LIMIT", qMax(w, h) / 2); + m_atlas_size = QSize(w, h); + + qCDebug(QSG_LOG_INFO, "rhi texture atlas dimensions: %dx%d", w, h); +} + +Manager::~Manager() +{ + Q_ASSERT(m_atlas == nullptr); + Q_ASSERT(m_atlases.isEmpty()); +} + +void Manager::invalidate() +{ + if (m_atlas) { + m_atlas->invalidate(); + m_atlas->deleteLater(); + m_atlas = nullptr; + } + + #if 0 + QHash<unsigned int, QSGCompressedAtlasTexture::Atlas*>::iterator i = m_atlases.begin(); + while (i != m_atlases.end()) { + i.value()->invalidate(); + i.value()->deleteLater(); + ++i; + } + m_atlases.clear(); +#endif +} + +QSGTexture *Manager::create(const QImage &image, bool hasAlphaChannel) +{ + Texture *t = nullptr; + if (image.width() < m_atlas_size_limit && image.height() < m_atlas_size_limit) { + if (!m_atlas) + m_atlas = new Atlas(m_rc, m_atlas_size); + t = m_atlas->create(image); + if (t && !hasAlphaChannel && t->hasAlphaChannel()) + t->setHasAlphaChannel(false); + } + return t; +} + +QSGTexture *Manager::create(const QSGCompressedTextureFactory *factory) +{ + Q_UNUSED(factory); + return nullptr; + // ### + +#if 0 + QSGTexture *t = nullptr; + if (!qsgEnableCompressedAtlas() || !factory->m_textureData.isValid()) + return t; + + // TODO: further abstract the atlas and remove this restriction + unsigned int format = factory->m_textureData.glInternalFormat(); + switch (format) { + case QOpenGLTexture::RGB8_ETC1: + case QOpenGLTexture::RGB8_ETC2: + case QOpenGLTexture::RGBA8_ETC2_EAC: + case QOpenGLTexture::RGB8_PunchThrough_Alpha1_ETC2: + break; + default: + return t; + } + + QSize size = factory->m_textureData.size(); + if (size.width() < m_atlas_size_limit && size.height() < m_atlas_size_limit) { + QHash<unsigned int, QSGCompressedAtlasTexture::Atlas*>::iterator i = m_atlases.find(format); + if (i == m_atlases.end()) + i = m_atlases.insert(format, new QSGCompressedAtlasTexture::Atlas(m_atlas_size, format)); + // must be multiple of 4 + QSize paddedSize(((size.width() + 3) / 4) * 4, ((size.height() + 3) / 4) * 4); + QByteArray data = factory->m_textureData.data(); + t = i.value()->create(data, factory->m_textureData.dataLength(), factory->m_textureData.dataOffset(), size, paddedSize); + } +#endif +} + +AtlasBase::AtlasBase(QSGDefaultRenderContext *rc, const QSize &size) + : m_rc(rc) + , m_rhi(rc->rhi()) + , m_allocator(size) + , m_size(size) +{ +} + +AtlasBase::~AtlasBase() +{ + Q_ASSERT(!m_texture); +} + +void AtlasBase::invalidate() +{ + delete m_texture; + m_texture = nullptr; +} + +void AtlasBase::updateRhiTexture(QRhiResourceUpdateBatch *resourceUpdates) +{ + if (!m_allocated) { + m_allocated = true; + if (!generateTexture()) { + qWarning("QSGTextureAtlas: Failed to create texture"); + return; + } + } + + for (TextureBase *t : m_pending_uploads) { + // ### this profiling is all wrong, the real work is done elsewhere + bool profileFrames = QSG_LOG_TIME_TEXTURE().isDebugEnabled(); + if (profileFrames) + qsg_renderer_timer.start(); + + Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphTexturePrepare); + + // Skip bind, convert, swizzle; they're irrelevant + Q_QUICK_SG_PROFILE_SKIP(QQuickProfiler::SceneGraphTexturePrepare, + QQuickProfiler::SceneGraphTexturePrepareStart, 3); + + enqueueTextureUpload(t, resourceUpdates); + + Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphTexturePrepare, + QQuickProfiler::SceneGraphTexturePrepareUpload); + + // Skip mipmap; unused + Q_QUICK_SG_PROFILE_SKIP(QQuickProfiler::SceneGraphTexturePrepare, + QQuickProfiler::SceneGraphTexturePrepareUpload, 1); + Q_QUICK_SG_PROFILE_REPORT(QQuickProfiler::SceneGraphTexturePrepare, + QQuickProfiler::SceneGraphTexturePrepareMipmap); + } + + m_pending_uploads.clear(); +} + +void AtlasBase::remove(TextureBase *t) +{ + QRect atlasRect = t->atlasSubRect(); + m_allocator.deallocate(atlasRect); + m_pending_uploads.removeOne(t); +} + +Atlas::Atlas(QSGDefaultRenderContext *rc, const QSize &size) + : AtlasBase(rc, size) +{ + // use RGBA texture internally as that is the only one guaranteed to be always supported + m_format = QRhiTexture::RGBA8; + + m_debug_overlay = qt_sg_envInt("QSG_ATLAS_OVERLAY", 0); + + // images smaller than this will retain their QImage. + // by default no images are retained (favoring memory) + // set to a very large value to retain all images (allowing quick removal from the atlas) + m_atlas_transient_image_threshold = qt_sg_envInt("QSG_ATLAS_TRANSIENT_IMAGE_THRESHOLD", 0); +} + +Atlas::~Atlas() +{ +} + +Texture *Atlas::create(const QImage &image) +{ + // No need to lock, as manager already locked it. + QRect rect = m_allocator.allocate(QSize(image.width() + 2, image.height() + 2)); + if (rect.width() > 0 && rect.height() > 0) { + Texture *t = new Texture(this, rect, image); + m_pending_uploads << t; + return t; + } + return nullptr; +} + +bool Atlas::generateTexture() +{ + m_texture = m_rhi->newTexture(m_format, m_size, 1, QRhiTexture::UsedAsTransferSource); + if (!m_texture) + return false; + + if (!m_texture->build()) { + delete m_texture; + m_texture = nullptr; + return false; + } + + return true; +} + +void Atlas::enqueueTextureUpload(TextureBase *t, QRhiResourceUpdateBatch *resourceUpdates) +{ + Texture *tex = static_cast<Texture *>(t); + const QRect &r = tex->atlasSubRect(); + QImage image = tex->image(); + + if (image.isNull()) + return; + + if (image.format() != QImage::Format_RGBA8888_Premultiplied) + image = std::move(image).convertToFormat(QImage::Format_RGBA8888_Premultiplied); + + if (m_debug_overlay) { + QPainter p(&image); + p.setCompositionMode(QPainter::CompositionMode_SourceAtop); + p.fillRect(0, 0, image.width(), image.height(), QBrush(QColor::fromRgbF(0, 1, 1, 0.5))); + } + + const int iw = image.width(); + const int ih = image.height(); + const int bpl = image.bytesPerLine() / 4; + QVarLengthArray<quint32, 1024> tmpBits(qMax(iw + 2, ih + 2)); + const int tmpBitsSize = tmpBits.size() * 4; + const quint32 *src = reinterpret_cast<const quint32 *>(image.constBits()); + quint32 *dst = tmpBits.data(); + QVarLengthArray<QRhiTextureUploadEntry, 5> entries; + + // top row, padding corners + dst[0] = src[0]; + memcpy(dst + 1, src, iw * sizeof(quint32)); + dst[1 + iw] = src[iw - 1]; + { + QRhiTextureSubresourceUploadDescription subresDesc(dst, tmpBitsSize); + subresDesc.setDestinationTopLeft(QPoint(r.x(), r.y())); + subresDesc.setSourceSize(QSize(iw + 2, 1)); + entries.append(QRhiTextureUploadEntry(0, 0, subresDesc)); + } + + // bottom row, padded corners + const quint32 *lastRow = src + bpl * (ih - 1); + dst[0] = lastRow[0]; + memcpy(dst + 1, lastRow, iw * sizeof(quint32)); + dst[1 + iw] = lastRow[iw - 1]; + { + QRhiTextureSubresourceUploadDescription subresDesc(dst, tmpBitsSize); + subresDesc.setDestinationTopLeft(QPoint(r.x(), r.y() + ih + 1)); + subresDesc.setSourceSize(QSize(iw + 2, 1)); + entries.append(QRhiTextureUploadEntry(0, 0, subresDesc)); + } + + // left column + for (int i = 0; i < ih; ++i) + dst[i] = src[i * bpl]; + { + QRhiTextureSubresourceUploadDescription subresDesc(dst, tmpBitsSize); + subresDesc.setDestinationTopLeft(QPoint(r.x(), r.y() + 1)); + subresDesc.setSourceSize(QSize(1, ih)); + entries.append(QRhiTextureUploadEntry(0, 0, subresDesc)); + } + + + // right column + for (int i = 0; i < ih; ++i) + dst[i] = src[i * bpl + iw - 1]; + { + QRhiTextureSubresourceUploadDescription subresDesc(dst, tmpBitsSize); + subresDesc.setDestinationTopLeft(QPoint(r.x() + iw + 1, r.y() + 1)); + subresDesc.setSourceSize(QSize(1, ih)); + entries.append(QRhiTextureUploadEntry(0, 0, subresDesc)); + } + + // Inner part of the image.... + if (bpl != iw) { + int sy = r.y() + 1; + int ey = sy + r.height() - 2; + entries.reserve(4 + (ey - sy)); + for (int y = sy; y < ey; ++y) { + QRhiTextureSubresourceUploadDescription subresDesc(src, image.bytesPerLine()); + subresDesc.setDestinationTopLeft(QPoint(r.x() + 1, y)); + subresDesc.setSourceSize(QSize(r.width() - 2, 1)); + entries.append(QRhiTextureUploadEntry(0, 0, subresDesc)); + src += bpl; + } + } else { + QRhiTextureSubresourceUploadDescription subresDesc(src, image.sizeInBytes()); + subresDesc.setDestinationTopLeft(QPoint(r.x() + 1, r.y() + 1)); + subresDesc.setSourceSize(QSize(r.width() - 2, r.height() - 2)); + entries.append(QRhiTextureUploadEntry(0, 0, subresDesc)); + } + + QRhiTextureUploadDescription desc; + desc.setEntries(entries.cbegin(), entries.cend()); + resourceUpdates->uploadTexture(m_texture, desc); + + const QSize textureSize = t->textureSize(); + if (textureSize.width() > m_atlas_transient_image_threshold || textureSize.height() > m_atlas_transient_image_threshold) + tex->releaseImage(); + + qCDebug(QSG_LOG_TIME_TEXTURE, "atlastexture upload enqueued in: %lldms (%dx%d)", + qsg_renderer_timer.elapsed(), + t->textureSize().width(), + t->textureSize().height()); +} + +TextureBase::TextureBase(AtlasBase *atlas, const QRect &textureRect) + : QSGTexture(*(new TextureBasePrivate)) + , m_allocated_rect(textureRect) + , m_atlas(atlas) +{ +} + +TextureBase::~TextureBase() +{ + m_atlas->remove(this); +} + +QRhiResourceUpdateBatch *TextureBase::workResourceUpdateBatch() const +{ + Q_D(const TextureBase); + return d->workResourceUpdateBatch; +} + +int TextureBasePrivate::comparisonKey() const +{ + Q_Q(const TextureBase); + + // We need special care here: a typical comparisonKey() implementation + // returns a unique result when there is no underlying texture yet. This is + // not quite ideal for atlasing however since textures with the same atlas + // should be considered equal regardless of the state of the underlying + // graphics resources. + + // base the comparison on the atlas ptr; this way textures for the same + // atlas are considered equal + return int(qintptr(q->m_atlas)); +} + +QRhiTexture *TextureBasePrivate::rhiTexture() const +{ + Q_Q(const TextureBase); + return q->m_atlas->m_texture; +} + +void TextureBasePrivate::updateRhiTexture(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates) +{ + Q_Q(TextureBase); +#ifdef QT_NO_DEBUG + Q_UNUSED(rhi); +#endif + Q_ASSERT(rhi == q->m_atlas->m_rhi); + q->m_atlas->updateRhiTexture(resourceUpdates); +} + +Texture::Texture(Atlas *atlas, const QRect &textureRect, const QImage &image) + : TextureBase(atlas, textureRect) + , m_image(image) + , m_has_alpha(image.hasAlphaChannel()) +{ + float w = atlas->size().width(); + float h = atlas->size().height(); + QRect nopad = atlasSubRectWithoutPadding(); + m_texture_coords_rect = QRectF(nopad.x() / w, + nopad.y() / h, + nopad.width() / w, + nopad.height() / h); +} + +Texture::~Texture() +{ + if (m_nonatlas_texture) + delete m_nonatlas_texture; +} + +QSGTexture *Texture::removedFromAtlas() const +{ + if (!m_nonatlas_texture) { + m_nonatlas_texture = new QSGPlainTexture; + if (!m_image.isNull()) { + m_nonatlas_texture->setImage(m_image); + m_nonatlas_texture->setFiltering(filtering()); + } else { + QSGDefaultRenderContext *rc = m_atlas->renderContext(); + QRhi *rhi = m_atlas->rhi(); + Q_ASSERT(rhi->isRecordingFrame()); + const QRect r = atlasSubRectWithoutPadding(); + + QRhiTexture *extractTex = rhi->newTexture(m_atlas->texture()->format(), r.size()); + if (extractTex->build()) { + bool ownResUpd = false; + QRhiResourceUpdateBatch *resUpd = workResourceUpdateBatch(); // ### Qt 6: should be an arg to this function + if (!resUpd) { + ownResUpd = true; + resUpd = rhi->nextResourceUpdateBatch(); + } + QRhiTextureCopyDescription desc; + desc.setSourceTopLeft(r.topLeft()); + desc.setPixelSize(r.size()); + resUpd->copyTexture(extractTex, m_atlas->texture(), desc); + if (ownResUpd) + rc->currentFrameCommandBuffer()->resourceUpdate(resUpd); + } + + m_nonatlas_texture->setTexture(extractTex); + m_nonatlas_texture->setOwnsTexture(true); + m_nonatlas_texture->setHasAlphaChannel(m_has_alpha); + m_nonatlas_texture->setTextureSize(r.size()); + } + } + + m_nonatlas_texture->setMipmapFiltering(mipmapFiltering()); + m_nonatlas_texture->setFiltering(filtering()); + return m_nonatlas_texture; +} + +} + +QT_END_NAMESPACE + +#include "moc_qsgrhiatlastexture_p.cpp" diff --git a/src/quick/scenegraph/util/qsgrhiatlastexture_p.h b/src/quick/scenegraph/util/qsgrhiatlastexture_p.h new file mode 100644 index 0000000000..50d7b2a53f --- /dev/null +++ b/src/quick/scenegraph/util/qsgrhiatlastexture_p.h @@ -0,0 +1,217 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGRHIATLASTEXTURE_P_H +#define QSGRHIATLASTEXTURE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/QSize> +#include <QtQuick/private/qsgplaintexture_p.h> +#include <QtQuick/private/qsgareaallocator_p.h> +#include <QtGui/QSurface> +#include <QtGui/private/qrhi_p.h> + +QT_BEGIN_NAMESPACE + +class QSGDefaultRenderContext; + +namespace QSGCompressedAtlasTexture { + class Atlas; +} +class QSGCompressedTextureFactory; + +namespace QSGRhiAtlasTexture +{ + +class Texture; +class TextureBase; +class TextureBasePrivate; +class Atlas; + +class Manager : public QObject +{ + Q_OBJECT + +public: + Manager(QSGDefaultRenderContext *rc, const QSize &surfacePixelSize, QSurface *maybeSurface); + ~Manager(); + + QSGTexture *create(const QImage &image, bool hasAlphaChannel); + QSGTexture *create(const QSGCompressedTextureFactory *factory); + void invalidate(); + +private: + QSGDefaultRenderContext *m_rc; + QRhi *m_rhi; + Atlas *m_atlas = nullptr; + // set of atlases for different compressed formats + QHash<unsigned int, QSGCompressedAtlasTexture::Atlas*> m_atlases; + + QSize m_atlas_size; + int m_atlas_size_limit; +}; + +class AtlasBase : public QObject +{ + Q_OBJECT +public: + AtlasBase(QSGDefaultRenderContext *rc, const QSize &size); + ~AtlasBase(); + + void invalidate(); + void updateRhiTexture(QRhiResourceUpdateBatch *resourceUpdates); + void remove(TextureBase *t); + + QSGDefaultRenderContext *renderContext() const { return m_rc; } + QRhi *rhi() const { return m_rhi; } + QRhiTexture *texture() const { return m_texture; } + QSize size() const { return m_size; } + +protected: + virtual bool generateTexture() = 0; + virtual void enqueueTextureUpload(TextureBase *t, QRhiResourceUpdateBatch *resourceUpdates) = 0; + +protected: + QSGDefaultRenderContext *m_rc; + QRhi *m_rhi; + QSGAreaAllocator m_allocator; + QRhiTexture *m_texture = nullptr; + QSize m_size; + QVector<TextureBase *> m_pending_uploads; + friend class TextureBase; + friend class TextureBasePrivate; + +private: + bool m_allocated = false; +}; + +class Atlas : public AtlasBase +{ +public: + Atlas(QSGDefaultRenderContext *rc, const QSize &size); + ~Atlas(); + + bool generateTexture() override; + void enqueueTextureUpload(TextureBase *t, QRhiResourceUpdateBatch *resourceUpdates) override; + + Texture *create(const QImage &image); + + QRhiTexture::Format format() const { return m_format; } + +private: + QRhiTexture::Format m_format; + int m_atlas_transient_image_threshold = 0; + + uint m_debug_overlay : 1; +}; + +class TextureBase : public QSGTexture +{ + Q_DECLARE_PRIVATE(TextureBase) + Q_OBJECT +public: + TextureBase(AtlasBase *atlas, const QRect &textureRect); + ~TextureBase(); + + int textureId() const override { return 0; } // not used + void bind() override { } // not used + + bool isAtlasTexture() const override { return true; } + QRect atlasSubRect() const { return m_allocated_rect; } + + QRhiResourceUpdateBatch *workResourceUpdateBatch() const; + +protected: + QRect m_allocated_rect; + AtlasBase *m_atlas; +}; + +class TextureBasePrivate : public QSGTexturePrivate +{ + Q_DECLARE_PUBLIC(TextureBase) +public: + int comparisonKey() const override; + QRhiTexture *rhiTexture() const override; + void updateRhiTexture(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates) override; +}; + +class Texture : public TextureBase +{ + Q_OBJECT +public: + Texture(Atlas *atlas, const QRect &textureRect, const QImage &image); + ~Texture(); + + QSize textureSize() const override { return atlasSubRectWithoutPadding().size(); } + void setHasAlphaChannel(bool alpha) { m_has_alpha = alpha; } + bool hasAlphaChannel() const override { return m_has_alpha; } + bool hasMipmaps() const override { return false; } + + QRectF normalizedTextureSubRect() const override { return m_texture_coords_rect; } + + QRect atlasSubRect() const { return m_allocated_rect; } + QRect atlasSubRectWithoutPadding() const { return m_allocated_rect.adjusted(1, 1, -1, -1); } + + QSGTexture *removedFromAtlas() const override; + + void releaseImage() { m_image = QImage(); } + const QImage &image() const { return m_image; } + +private: + QRectF m_texture_coords_rect; + QImage m_image; + mutable QSGPlainTexture *m_nonatlas_texture = nullptr; + bool m_has_alpha; +}; + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/quick/scenegraph/util/qsgrhinativetextureimporter.cpp b/src/quick/scenegraph/util/qsgrhinativetextureimporter.cpp new file mode 100644 index 0000000000..7a7c19f587 --- /dev/null +++ b/src/quick/scenegraph/util/qsgrhinativetextureimporter.cpp @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgrhinativetextureimporter_p.h" +#include <private/qsgrhisupport_p.h> // to get all the relevant qrhi headers + +QT_BEGIN_NAMESPACE + +void QSGRhiNativeTextureImporter::buildWrapper(QRhi *rhi, QRhiTexture *t, + const void *nativeObjectPtr, int nativeLayout) +{ +#if !QT_CONFIG(vulkan) + Q_UNUSED(nativeLayout); +#endif +#if !QT_CONFIG(opengl) && !QT_CONFIG(vulkan) && !defined(Q_OS_WIN) && !defined(Q_OS_DARWIN) + Q_UNUSED(nativeObjectPtr); +#endif + + switch (rhi->backend()) { + case QRhi::OpenGLES2: + { +#if QT_CONFIG(opengl) + QRhiGles2TextureNativeHandles h; + h.texture = *reinterpret_cast<const uint *>(nativeObjectPtr); + t->buildFrom(&h); +#endif + } + break; + case QRhi::Vulkan: + { +#if QT_CONFIG(vulkan) + QRhiVulkanTextureNativeHandles h; + h.image = *reinterpret_cast<const VkImage *>(nativeObjectPtr); + h.layout = VkImageLayout(nativeLayout); + t->buildFrom(&h); +#endif + } + break; + case QRhi::D3D11: + { +#ifdef Q_OS_WIN + QRhiD3D11TextureNativeHandles h; + h.texture = *reinterpret_cast<void * const *>(nativeObjectPtr); + t->buildFrom(&h); +#endif + } + break; + case QRhi::Metal: + { +#ifdef Q_OS_DARWIN + QRhiMetalTextureNativeHandles h; + h.texture = *reinterpret_cast<void * const *>(nativeObjectPtr); + t->buildFrom(&h); +#endif + } + break; + case QRhi::Null: + t->build(); + break; + default: + qWarning("QSGRhiNativeTextureImporter: encountered an unsupported QRhi backend (%d)", + int(rhi->backend())); + t->build(); + break; + } +} + +QT_END_NAMESPACE diff --git a/src/quick/scenegraph/util/qsgrhinativetextureimporter_p.h b/src/quick/scenegraph/util/qsgrhinativetextureimporter_p.h new file mode 100644 index 0000000000..e811109a94 --- /dev/null +++ b/src/quick/scenegraph/util/qsgrhinativetextureimporter_p.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGRHINATIVETEXTUREIMPORTER_P_H +#define QSGRHINATIVETEXTUREIMPORTER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuick/private/qtquickglobal_p.h> + +QT_BEGIN_NAMESPACE + +class QRhi; +class QRhiTexture; + +class QSGRhiNativeTextureImporter +{ +public: + static void buildWrapper(QRhi *rhi, QRhiTexture *t, + const void *nativeObjectPtr, int nativeLayout); +}; + +QT_END_NAMESPACE + +#endif // QSGRHINATIVETEXTUREIMPORTER_P_H diff --git a/src/quick/scenegraph/util/qsgsimplematerial.cpp b/src/quick/scenegraph/util/qsgsimplematerial.cpp index 376f7dce5c..1064caccc7 100644 --- a/src/quick/scenegraph/util/qsgsimplematerial.cpp +++ b/src/quick/scenegraph/util/qsgsimplematerial.cpp @@ -46,8 +46,9 @@ \inmodule QtQuick \ingroup qtquick-scenegraph-materials - \warning This utility class is only functional when running with the OpenGL - backend of the Qt Quick scenegraph. + \warning This utility class is only functional when running with the legacy + OpenGL renderer of the Qt Quick scenegraph. Its usage is not recommended in + new application code. Where the QSGMaterial and QSGMaterialShader API requires a bit of boilerplate code to create a functioning material, the diff --git a/src/quick/scenegraph/util/qsgsimplerectnode.cpp b/src/quick/scenegraph/util/qsgsimplerectnode.cpp index 28b177be84..7b96a3fdde 100644 --- a/src/quick/scenegraph/util/qsgsimplerectnode.cpp +++ b/src/quick/scenegraph/util/qsgsimplerectnode.cpp @@ -49,10 +49,10 @@ QT_BEGIN_NAMESPACE solid filled rectangles using scenegraph. \inmodule QtQuick - \warning This utility class is only functional when running with the OpenGL - or software backends of the Qt Quick scenegraph. For a proper cross-platform - alternative prefer using QSGRectangleNode via - QQuickWindow::createRectangleNode() or QSGEngine::createRectangleNode(). + \warning This utility class is only functional when running with the default + or software backends of the Qt Quick scenegraph. As an alternative, prefer + using QSGRectangleNode via QQuickWindow::createRectangleNode() or + QSGEngine::createRectangleNode(). \deprecated */ diff --git a/src/quick/scenegraph/util/qsgsimpletexturenode.cpp b/src/quick/scenegraph/util/qsgsimpletexturenode.cpp index 0c49ca9aa5..1d0a423aa9 100644 --- a/src/quick/scenegraph/util/qsgsimpletexturenode.cpp +++ b/src/quick/scenegraph/util/qsgsimpletexturenode.cpp @@ -97,10 +97,10 @@ static void qsgsimpletexturenode_update(QSGGeometry *g, \warning The simple texture node class must have a texture before being added to the scene graph to be rendered. - \warning This utility class is only functional when running with the OpenGL - or software backends of the Qt Quick scenegraph. For a proper cross-platform - alternative prefer using QSGImageNode via - QQuickWindow::createImageNode() or QSGEngine::createImageNode(). + \warning This utility class is only functional when running with the default + or software backends of the Qt Quick scenegraph. As an alternative, prefer + using QSGImageNode via QQuickWindow::createImageNode() or + QSGEngine::createImageNode(). \deprecated */ diff --git a/src/quick/scenegraph/util/qsgtexture.cpp b/src/quick/scenegraph/util/qsgtexture.cpp deleted file mode 100644 index 042eee19f5..0000000000 --- a/src/quick/scenegraph/util/qsgtexture.cpp +++ /dev/null @@ -1,851 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQuick module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qsgtexture_p.h" -#include <QtQuick/private/qsgcontext_p.h> -#include <qthread.h> -#include <qmath.h> -#include <private/qquickprofiler_p.h> -#include <private/qqmlglobal_p.h> -#include <QtGui/qguiapplication.h> -#include <QtGui/qpa/qplatformnativeinterface.h> -#if QT_CONFIG(opengl) -# include <qopenglfunctions.h> -# include <QtGui/qopenglcontext.h> -# include <QtGui/qopenglfunctions.h> -# include <QtGui/private/qopengltextureuploader_p.h> -# include <private/qsgdefaultrendercontext_p.h> -#endif -#include <private/qsgmaterialshader_p.h> - -#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) && defined(__GLIBC__) -#define CAN_BACKTRACE_EXECINFO -#endif - -#if defined(Q_OS_MAC) -#define CAN_BACKTRACE_EXECINFO -#endif - -#if defined(QT_NO_DEBUG) -#undef CAN_BACKTRACE_EXECINFO -#endif - -#if defined(CAN_BACKTRACE_EXECINFO) -#include <execinfo.h> -#include <QHash> -#endif - -#if QT_CONFIG(opengl) -static QElapsedTimer qsg_renderer_timer; -#endif - -#ifndef QT_NO_DEBUG -static const bool qsg_leak_check = !qEnvironmentVariableIsEmpty("QML_LEAK_CHECK"); -#endif - - -#ifndef GL_BGRA -#define GL_BGRA 0x80E1 -#endif - -#ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT -#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE -#endif - -QT_BEGIN_NAMESPACE - -#if QT_CONFIG(opengl) && !defined(QT_NO_DEBUG) -inline static bool isPowerOfTwo(int x) -{ - // Assumption: x >= 1 - return x == (x & -x); -} -#endif - -QSGTexturePrivate::QSGTexturePrivate() - : wrapChanged(false) - , filteringChanged(false) - , anisotropyChanged(false) - , horizontalWrap(QSGTexture::ClampToEdge) - , verticalWrap(QSGTexture::ClampToEdge) - , mipmapMode(QSGTexture::None) - , filterMode(QSGTexture::Nearest) - , anisotropyLevel(QSGTexture::AnisotropyNone) -{ -} - -#ifndef QT_NO_DEBUG - -static int qt_debug_texture_count = 0; - -#if (defined(Q_OS_LINUX) || defined (Q_OS_MAC)) && !defined(Q_OS_ANDROID) -DEFINE_BOOL_CONFIG_OPTION(qmlDebugLeakBacktrace, QML_DEBUG_LEAK_BACKTRACE) - -#define BACKTRACE_SIZE 20 -class SGTextureTraceItem -{ -public: - void *backTrace[BACKTRACE_SIZE]; - size_t backTraceSize; -}; - -static QHash<QSGTexture*, SGTextureTraceItem*> qt_debug_allocated_textures; -#endif - -inline static void qt_debug_print_texture_count() -{ - qDebug("Number of leaked textures: %i", qt_debug_texture_count); - qt_debug_texture_count = -1; - -#if defined(CAN_BACKTRACE_EXECINFO) - if (qmlDebugLeakBacktrace()) { - while (!qt_debug_allocated_textures.isEmpty()) { - QHash<QSGTexture*, SGTextureTraceItem*>::Iterator it = qt_debug_allocated_textures.begin(); - QSGTexture* texture = it.key(); - SGTextureTraceItem* item = it.value(); - - qt_debug_allocated_textures.erase(it); - - qDebug() << "------"; - qDebug() << "Leaked" << texture << "backtrace:"; - - char** symbols = backtrace_symbols(item->backTrace, item->backTraceSize); - - if (symbols) { - for (int i=0; i<(int) item->backTraceSize; i++) - qDebug("Backtrace <%02d>: %s", i, symbols[i]); - free(symbols); - } - - qDebug() << "------"; - - delete item; - } - } -#endif -} - -inline static void qt_debug_add_texture(QSGTexture* texture) -{ -#if defined(CAN_BACKTRACE_EXECINFO) - if (qmlDebugLeakBacktrace()) { - SGTextureTraceItem* item = new SGTextureTraceItem; - item->backTraceSize = backtrace(item->backTrace, BACKTRACE_SIZE); - qt_debug_allocated_textures.insert(texture, item); - } -#else - Q_UNUSED(texture); -#endif // Q_OS_LINUX - - ++qt_debug_texture_count; - - static bool atexit_registered = false; - if (!atexit_registered) { - atexit(qt_debug_print_texture_count); - atexit_registered = true; - } -} - -static void qt_debug_remove_texture(QSGTexture* texture) -{ -#if defined(CAN_BACKTRACE_EXECINFO) - if (qmlDebugLeakBacktrace()) { - SGTextureTraceItem* item = qt_debug_allocated_textures.value(texture, 0); - if (item) { - qt_debug_allocated_textures.remove(texture); - delete item; - } - } -#else - Q_UNUSED(texture) -#endif - - --qt_debug_texture_count; - - if (qt_debug_texture_count < 0) - qDebug("Texture destroyed after qt_debug_print_texture_count() was called."); -} - -#endif // QT_NO_DEBUG - -/*! - \class QSGTexture - - \inmodule QtQuick - - \brief The QSGTexture class is a baseclass for textures used in - the scene graph. - - - Users can freely implement their own texture classes to support - arbitrary input textures, such as YUV video frames or 8 bit alpha - masks. The scene graph backend provides a default implementation - of normal color textures. As the implementation of these may be - hardware specific, they are constructed via the factory - function QQuickWindow::createTextureFromImage(). - - The texture is a wrapper around an OpenGL texture, which texture - id is given by textureId() and which size in pixels is given by - textureSize(). hasAlphaChannel() reports if the texture contains - opacity values and hasMipmaps() reports if the texture contains - mipmap levels. - - To use a texture, call the bind() function. The texture parameters - specifying how the texture is bound, can be specified with - setMipmapFiltering(), setFiltering(), setHorizontalWrapMode() and - setVerticalWrapMode(). The texture will internally try to store - these values to minimize the OpenGL state changes when the texture - is bound. - - \section1 Texture Atlasses - - Some scene graph backends use texture atlasses, grouping multiple - small textures into one large texture. If this is the case, the - function isAtlasTexture() will return true. Atlasses are used to - aid the rendering algorithm to do better sorting which increases - performance. The location of the texture inside the atlas is - given with the normalizedTextureSubRect() function. - - If the texture is used in such a way that atlas is not preferable, - the function removedFromAtlas() can be used to extract a - non-atlassed copy. - - \note All classes with QSG prefix should be used solely on the scene graph's - rendering thread. See \l {Scene Graph and Rendering} for more information. - - \sa {Scene Graph - Rendering FBOs}, {Scene Graph - Rendering FBOs in a thread} - */ - -/*! - \enum QSGTexture::WrapMode - - Specifies how the texture should treat texture coordinates. - - \value Repeat Only the fractional part of the texture coordinate is - used, causing values above 1 and below 0 to repeat. - - \value ClampToEdge Values above 1 are clamped to 1 and values - below 0 are clamped to 0. - - \value MirroredRepeat When the texture coordinate is even, only the - fractional part is used. When odd, the texture coordinate is set to - \c{1 - fractional part}. This value has been introduced in Qt 5.10. - */ - -/*! - \enum QSGTexture::Filtering - - Specifies how sampling of texels should filter when texture - coordinates are not pixel aligned. - - \value None No filtering should occur. This value is only used - together with setMipmapFiltering(). - - \value Nearest Sampling returns the nearest texel. - - \value Linear Sampling returns a linear interpolation of the - neighboring texels. -*/ - -/*! - \enum QSGTexture::AnisotropyLevel - - Specifies the anisotropic filtering level to be used when - the texture is not screen aligned. - - \value AnisotropyNone No anisotropic filtering. - - \value Anisotropy2x 2x anisotropic filtering. - - \value Anisotropy4x 4x anisotropic filtering. - - \value Anisotropy8x 8x anisotropic filtering. - - \value Anisotropy16x 16x anisotropic filtering. - - \since 5.9 -*/ - -/*! - \fn QSGTexture::QSGTexture(QSGTexturePrivate &dd) - \internal - */ - -#ifndef QT_NO_DEBUG -Q_GLOBAL_STATIC(QSet<QSGTexture *>, qsg_valid_texture_set) -Q_GLOBAL_STATIC(QMutex, qsg_valid_texture_mutex) - -bool qsg_safeguard_texture(QSGTexture *texture) -{ -#if QT_CONFIG(opengl) - QMutexLocker locker(qsg_valid_texture_mutex()); - if (!qsg_valid_texture_set()->contains(texture)) { - qWarning() << "Invalid texture accessed:" << (void *) texture; - qsg_set_material_failure(); - QOpenGLContext::currentContext()->functions()->glBindTexture(GL_TEXTURE_2D, 0); - return false; - } -#else - Q_UNUSED(texture) -#endif - return true; -} -#endif - -/*! - Constructs the QSGTexture base class. - */ -QSGTexture::QSGTexture() - : QObject(*(new QSGTexturePrivate)) -{ -#ifndef QT_NO_DEBUG - if (qsg_leak_check) - qt_debug_add_texture(this); - - QMutexLocker locker(qsg_valid_texture_mutex()); - qsg_valid_texture_set()->insert(this); -#endif -} - -/*! - Destroys the QSGTexture. - */ -QSGTexture::~QSGTexture() -{ -#ifndef QT_NO_DEBUG - if (qsg_leak_check) - qt_debug_remove_texture(this); - - QMutexLocker locker(qsg_valid_texture_mutex()); - qsg_valid_texture_set()->remove(this); -#endif -} - - -/*! - \fn void QSGTexture::bind() - - Call this function to bind this texture to the current texture - target. - - Binding a texture may also include uploading the texture data from - a previously set QImage. - - \warning This function can only be called from the rendering thread. - */ - -/*! - \fn QRectF QSGTexture::convertToNormalizedSourceRect(const QRectF &rect) const - - Returns \a rect converted to normalized coordinates. - - \sa normalizedTextureSubRect() - */ - -/*! - This function returns a copy of the current texture which is removed - from its atlas. - - The current texture remains unchanged, so texture coordinates do not - need to be updated. - - Removing a texture from an atlas is primarily useful when passing - it to a shader that operates on the texture coordinates 0-1 instead - of the texture subrect inside the atlas. - - If the texture is not part of a texture atlas, this function returns \nullptr. - - Implementations of this function are recommended to return the same instance - for multiple calls to limit memory usage. - - \warning This function can only be called from the rendering thread. - */ - -QSGTexture *QSGTexture::removedFromAtlas() const -{ - Q_ASSERT_X(!isAtlasTexture(), "QSGTexture::removedFromAtlas()", "Called on a non-atlas texture"); - return nullptr; -} - -/*! - Returns weither this texture is part of an atlas or not. - - The default implementation returns false. - */ -bool QSGTexture::isAtlasTexture() const -{ - return false; -} - -/*! - \fn int QSGTexture::textureId() const - - Returns the OpenGL texture id for this texture. - - The default value is 0, indicating that it is an invalid texture id. - - The function should at all times return the correct texture id. - - \warning This function can only be called from the rendering thread. - */ - -/*! - \fn QSize QSGTexture::textureSize() const - - Returns the size of the texture. - */ - -/*! - Returns the rectangle inside textureSize() that this texture - represents in normalized coordinates. - - The default implementation returns a rect at position (0, 0) with - width and height of 1. - */ -QRectF QSGTexture::normalizedTextureSubRect() const -{ - return QRectF(0, 0, 1, 1); -} - -/*! - \fn bool QSGTexture::hasAlphaChannel() const - - Returns true if the texture data contains an alpha channel. - */ - -/*! - \fn bool QSGTexture::hasMipmaps() const - - Returns true if the texture data contains mipmap levels. - */ - - -/*! - Sets the mipmap sampling mode to be used for the upcoming bind() call to \a filter. - - Setting the mipmap filtering has no effect it the texture does not have mipmaps. - - \sa hasMipmaps() - */ -void QSGTexture::setMipmapFiltering(Filtering filter) -{ - Q_D(QSGTexture); - if (d->mipmapMode != (uint) filter) { - d->mipmapMode = filter; - d->filteringChanged = true; - } -} - -/*! - Returns whether mipmapping should be used when sampling from this texture. - */ -QSGTexture::Filtering QSGTexture::mipmapFiltering() const -{ - return (QSGTexture::Filtering) d_func()->mipmapMode; -} - - -/*! - Sets the sampling mode to be used for the upcoming bind() call to \a filter. - */ -void QSGTexture::setFiltering(QSGTexture::Filtering filter) -{ - Q_D(QSGTexture); - if (d->filterMode != (uint) filter) { - d->filterMode = filter; - d->filteringChanged = true; - } -} - -/*! - Returns the sampling mode to be used for this texture. - */ -QSGTexture::Filtering QSGTexture::filtering() const -{ - return (QSGTexture::Filtering) d_func()->filterMode; -} - -/*! - Sets the level of anisotropic filtering to be used for the upcoming bind() call to \a level. - The default value is QSGTexture::AnisotropyNone, which means no anisotropic filtering is enabled. - - \since 5.9 - */ -void QSGTexture::setAnisotropyLevel(AnisotropyLevel level) -{ - Q_D(QSGTexture); - if (d->anisotropyLevel != (uint) level) { - d->anisotropyLevel = level; - d->anisotropyChanged = true; - } -} - -/*! - Returns the anisotropy level in use for filtering this texture. - - \since 5.9 - */ -QSGTexture::AnisotropyLevel QSGTexture::anisotropyLevel() const -{ - return (QSGTexture::AnisotropyLevel) d_func()->anisotropyLevel; -} - - - -/*! - Sets the horizontal wrap mode to be used for the upcoming bind() call to \a hwrap - */ - -void QSGTexture::setHorizontalWrapMode(WrapMode hwrap) -{ - Q_D(QSGTexture); - if ((uint) hwrap != d->horizontalWrap) { - d->horizontalWrap = hwrap; - d->wrapChanged = true; - } -} - -/*! - Returns the horizontal wrap mode to be used for this texture. - */ -QSGTexture::WrapMode QSGTexture::horizontalWrapMode() const -{ - return (QSGTexture::WrapMode) d_func()->horizontalWrap; -} - - - -/*! - Sets the vertical wrap mode to be used for the upcoming bind() call to \a vwrap - */ -void QSGTexture::setVerticalWrapMode(WrapMode vwrap) -{ - Q_D(QSGTexture); - if ((uint) vwrap != d->verticalWrap) { - d->verticalWrap = vwrap; - d->wrapChanged = true; - } -} - -/*! - Returns the vertical wrap mode to be used for this texture. - */ -QSGTexture::WrapMode QSGTexture::verticalWrapMode() const -{ - return (QSGTexture::WrapMode) d_func()->verticalWrap; -} - - -/*! - Update the texture state to match the filtering, mipmap and wrap options - currently set. - - If \a force is true, all properties will be updated regardless of weither - they have changed or not. - */ -void QSGTexture::updateBindOptions(bool force) -{ -#if QT_CONFIG(opengl) - Q_D(QSGTexture); - QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions(); - force |= isAtlasTexture(); - - if (force || d->filteringChanged) { - bool linear = d->filterMode == Linear; - GLint minFilter = linear ? GL_LINEAR : GL_NEAREST; - GLint magFilter = linear ? GL_LINEAR : GL_NEAREST; - - if (hasMipmaps()) { - if (d->mipmapMode == Nearest) - minFilter = linear ? GL_LINEAR_MIPMAP_NEAREST : GL_NEAREST_MIPMAP_NEAREST; - else if (d->mipmapMode == Linear) - minFilter = linear ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_LINEAR; - } - funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter); - funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter); - d->filteringChanged = false; - } - - if (force || d->anisotropyChanged) { - d->anisotropyChanged = false; - if (QOpenGLContext::currentContext()->hasExtension(QByteArrayLiteral("GL_EXT_texture_filter_anisotropic"))) - funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, float(1 << (d->anisotropyLevel))); - } - - if (force || d->wrapChanged) { -#ifndef QT_NO_DEBUG - if (d->horizontalWrap == Repeat || d->verticalWrap == Repeat - || d->horizontalWrap == MirroredRepeat || d->verticalWrap == MirroredRepeat) - { - bool npotSupported = QOpenGLFunctions(QOpenGLContext::currentContext()).hasOpenGLFeature(QOpenGLFunctions::NPOTTextures); - QSize size = textureSize(); - bool isNpot = !isPowerOfTwo(size.width()) || !isPowerOfTwo(size.height()); - if (!npotSupported && isNpot) - qWarning("Scene Graph: This system does not support the REPEAT wrap mode for non-power-of-two textures."); - } -#endif - GLenum wrapS = GL_CLAMP_TO_EDGE; - if (d->horizontalWrap == Repeat) - wrapS = GL_REPEAT; - else if (d->horizontalWrap == MirroredRepeat) - wrapS = GL_MIRRORED_REPEAT; - GLenum wrapT = GL_CLAMP_TO_EDGE; - if (d->verticalWrap == Repeat) - wrapT = GL_REPEAT; - else if (d->verticalWrap == MirroredRepeat) - wrapT = GL_MIRRORED_REPEAT; - funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapS); - funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapT); - d->wrapChanged = false; - } -#else - Q_UNUSED(force) -#endif -} - -QSGPlainTexture::QSGPlainTexture() - : QSGTexture() - , m_texture_id(0) - , m_has_alpha(false) - , m_dirty_texture(false) - , m_dirty_bind_options(false) - , m_owns_texture(true) - , m_mipmaps_generated(false) - , m_retain_image(false) -{ -} - - -QSGPlainTexture::~QSGPlainTexture() -{ -#if QT_CONFIG(opengl) - if (m_texture_id && m_owns_texture && QOpenGLContext::currentContext()) - QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &m_texture_id); -#endif -} - -void QSGPlainTexture::setImage(const QImage &image) -{ - m_image = image; - m_texture_size = image.size(); - m_has_alpha = image.hasAlphaChannel(); - m_dirty_texture = true; - m_dirty_bind_options = true; - m_mipmaps_generated = false; - } - -int QSGPlainTexture::textureId() const -{ - if (m_dirty_texture) { - if (m_image.isNull()) { - // The actual texture and id will be updated/deleted in a later bind() - // or ~QSGPlainTexture so just keep it minimal here. - return 0; - } else if (m_texture_id == 0){ -#if QT_CONFIG(opengl) - // Generate a texture id for use later and return it. - QOpenGLContext::currentContext()->functions()->glGenTextures(1, &const_cast<QSGPlainTexture *>(this)->m_texture_id); -#endif - return m_texture_id; - } - } - return m_texture_id; -} - -void QSGPlainTexture::setTextureId(int id) -{ -#if QT_CONFIG(opengl) - if (m_texture_id && m_owns_texture) - QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &m_texture_id); -#endif - - m_texture_id = id; - m_dirty_texture = false; - m_dirty_bind_options = true; - m_image = QImage(); - m_mipmaps_generated = false; -} - -void QSGPlainTexture::bind() -{ -#if QT_CONFIG(opengl) - QOpenGLContext *context = QOpenGLContext::currentContext(); - QOpenGLFunctions *funcs = context->functions(); - if (!m_dirty_texture) { - funcs->glBindTexture(GL_TEXTURE_2D, m_texture_id); - if (mipmapFiltering() != QSGTexture::None && !m_mipmaps_generated) { - funcs->glGenerateMipmap(GL_TEXTURE_2D); - m_mipmaps_generated = true; - } - updateBindOptions(m_dirty_bind_options); - m_dirty_bind_options = false; - return; - } - - m_dirty_texture = false; - - bool profileFrames = QSG_LOG_TIME_TEXTURE().isDebugEnabled(); - if (profileFrames) - qsg_renderer_timer.start(); - Q_QUICK_SG_PROFILE_START_SYNCHRONIZED(QQuickProfiler::SceneGraphTexturePrepare, - QQuickProfiler::SceneGraphTextureDeletion); - - - if (m_image.isNull()) { - if (m_texture_id && m_owns_texture) { - funcs->glDeleteTextures(1, &m_texture_id); - qCDebug(QSG_LOG_TIME_TEXTURE, "plain texture deleted in %dms - %dx%d", - (int) qsg_renderer_timer.elapsed(), - m_texture_size.width(), - m_texture_size.height()); - Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphTextureDeletion, - QQuickProfiler::SceneGraphTextureDeletionDelete); - } - m_texture_id = 0; - m_texture_size = QSize(); - m_has_alpha = false; - - return; - } - - if (m_texture_id == 0) - funcs->glGenTextures(1, &m_texture_id); - funcs->glBindTexture(GL_TEXTURE_2D, m_texture_id); - - qint64 bindTime = 0; - if (profileFrames) - bindTime = qsg_renderer_timer.nsecsElapsed(); - Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphTexturePrepare, - QQuickProfiler::SceneGraphTexturePrepareBind); - - // ### TODO: check for out-of-memory situations... - - QOpenGLTextureUploader::BindOptions options = QOpenGLTextureUploader::PremultipliedAlphaBindOption; - - // Downscale the texture to fit inside the max texture limit if it is too big. - // It would be better if the image was already downscaled to the right size, - // but this information is not always available at that time, so as a last - // resort we can do it here. Texture coordinates are normalized, so it - // won't cause any problems and actual texture sizes will be written - // based on QSGTexture::textureSize which is updated after this, so that - // should be ok. - int max; - if (auto rc = QSGDefaultRenderContext::from(context)) - max = rc->maxTextureSize(); - else - funcs->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max); - - m_texture_size = m_texture_size.boundedTo(QSize(max, max)); - - // Scale to a power of two size if mipmapping is requested and the - // texture is npot and npot textures are not properly supported. - if (mipmapFiltering() != QSGTexture::None - && !funcs->hasOpenGLFeature(QOpenGLFunctions::NPOTTextures)) { - options |= QOpenGLTextureUploader::PowerOfTwoBindOption; - } - - updateBindOptions(m_dirty_bind_options); - - QOpenGLTextureUploader::textureImage(GL_TEXTURE_2D, m_image, options, QSize(max, max)); - - qint64 uploadTime = 0; - if (profileFrames) - uploadTime = qsg_renderer_timer.nsecsElapsed(); - Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphTexturePrepare, - QQuickProfiler::SceneGraphTexturePrepareUpload); - - if (mipmapFiltering() != QSGTexture::None) { - funcs->glGenerateMipmap(GL_TEXTURE_2D); - m_mipmaps_generated = true; - } - - qint64 mipmapTime = 0; - if (profileFrames) { - mipmapTime = qsg_renderer_timer.nsecsElapsed(); - qCDebug(QSG_LOG_TIME_TEXTURE, - "plain texture uploaded in: %dms (%dx%d), bind=%d, upload=%d, mipmap=%d%s", - int(mipmapTime / 1000000), - m_texture_size.width(), m_texture_size.height(), - int(bindTime / 1000000), - int((uploadTime - bindTime)/1000000), - int((mipmapTime - uploadTime)/1000000), - m_texture_size != m_image.size() ? " (scaled to GL_MAX_TEXTURE_SIZE)" : ""); - } - Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphTexturePrepare, - QQuickProfiler::SceneGraphTexturePrepareMipmap); - - m_texture_rect = QRectF(0, 0, 1, 1); - - m_dirty_bind_options = false; - if (!m_retain_image) - m_image = QImage(); -#endif -} - - -/*! - \class QSGDynamicTexture - \brief The QSGDynamicTexture class serves as a baseclass for dynamically changing textures, - such as content that is rendered to FBO's. - \inmodule QtQuick - - To update the content of the texture, call updateTexture() explicitly. Simply calling bind() - will not update the texture. - - \note All classes with QSG prefix should be used solely on the scene graph's - rendering thread. See \l {Scene Graph and Rendering} for more information. - */ - - -/*! - \fn bool QSGDynamicTexture::updateTexture() - - Call this function to explicitly update the dynamic texture. Calling bind() will bind - the content that was previously updated. - - The function returns true if the texture was changed as a resul of the update; otherwise - returns false. - */ - - - -QT_END_NAMESPACE - -#include "moc_qsgtexture.cpp" -#include "moc_qsgtexture_p.cpp" diff --git a/src/quick/scenegraph/util/qsgtexture.h b/src/quick/scenegraph/util/qsgtexture.h deleted file mode 100644 index 7bd57a16e3..0000000000 --- a/src/quick/scenegraph/util/qsgtexture.h +++ /dev/null @@ -1,138 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQuick module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QSGTEXTURE_H -#define QSGTEXTURE_H - -#include <QtQuick/qtquickglobal.h> -#include <QtCore/QObject> -#include <QtGui/QImage> - -QT_BEGIN_NAMESPACE - -class QSGTexturePrivate; -class Q_QUICK_EXPORT QSGTexture : public QObject -{ - Q_OBJECT - Q_DECLARE_PRIVATE(QSGTexture) - -public: - QSGTexture(); - ~QSGTexture() override; - - enum WrapMode { - Repeat, - ClampToEdge, - MirroredRepeat - }; - - enum Filtering { - None, - Nearest, - Linear - }; - - enum AnisotropyLevel { - AnisotropyNone, - Anisotropy2x, - Anisotropy4x, - Anisotropy8x, - Anisotropy16x - }; - - virtual int textureId() const = 0; - virtual QSize textureSize() const = 0; - virtual bool hasAlphaChannel() const = 0; - virtual bool hasMipmaps() const = 0; - - virtual QRectF normalizedTextureSubRect() const; - - virtual bool isAtlasTexture() const; - - virtual QSGTexture *removedFromAtlas() const; - - virtual void bind() = 0; - void updateBindOptions(bool force = false); - - void setMipmapFiltering(Filtering filter); - QSGTexture::Filtering mipmapFiltering() const; - - void setFiltering(Filtering filter); - QSGTexture::Filtering filtering() const; - - void setAnisotropyLevel(AnisotropyLevel level); - QSGTexture::AnisotropyLevel anisotropyLevel() const; - - void setHorizontalWrapMode(WrapMode hwrap); - QSGTexture::WrapMode horizontalWrapMode() const; - - void setVerticalWrapMode(WrapMode vwrap); - QSGTexture::WrapMode verticalWrapMode() const; - - inline QRectF convertToNormalizedSourceRect(const QRectF &rect) const; - -protected: - QSGTexture(QSGTexturePrivate &dd); -}; - -QRectF QSGTexture::convertToNormalizedSourceRect(const QRectF &rect) const -{ - QSize s = textureSize(); - QRectF r = normalizedTextureSubRect(); - - qreal sx = r.width() / s.width(); - qreal sy = r.height() / s.height(); - - return QRectF(r.x() + rect.x() * sx, - r.y() + rect.y() * sy, - rect.width() * sx, - rect.height() * sy); -} - - -class Q_QUICK_EXPORT QSGDynamicTexture : public QSGTexture -{ - Q_OBJECT -public: - virtual bool updateTexture() = 0; -}; - -QT_END_NAMESPACE - -#endif diff --git a/src/quick/scenegraph/util/qsgtexturematerial.cpp b/src/quick/scenegraph/util/qsgtexturematerial.cpp index 7b1d5abb26..67b8748119 100644 --- a/src/quick/scenegraph/util/qsgtexturematerial.cpp +++ b/src/quick/scenegraph/util/qsgtexturematerial.cpp @@ -38,23 +38,20 @@ ****************************************************************************/ #include "qsgtexturematerial_p.h" -#include "qsgtexture_p.h" +#include <private/qsgtexture_p.h> #if QT_CONFIG(opengl) # include <QtGui/qopenglshaderprogram.h> # include <QtGui/qopenglfunctions.h> #endif +#include <QtGui/private/qrhi_p.h> QT_BEGIN_NAMESPACE -#if QT_CONFIG(opengl) inline static bool isPowerOfTwo(int x) { // Assumption: x >= 1 return x == (x & -x); } -#endif - -QSGMaterialType QSGOpaqueTextureMaterialShader::type; QSGOpaqueTextureMaterialShader::QSGOpaqueTextureMaterialShader() { @@ -122,6 +119,60 @@ void QSGOpaqueTextureMaterialShader::updateState(const RenderState &state, QSGMa } +QSGOpaqueTextureMaterialRhiShader::QSGOpaqueTextureMaterialRhiShader() +{ + setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/opaquetexture.vert.qsb")); + setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/opaquetexture.frag.qsb")); +} + +bool QSGOpaqueTextureMaterialRhiShader::updateUniformData(RenderState &state, QSGMaterial *, QSGMaterial *) +{ + bool changed = false; + QByteArray *buf = state.uniformData(); + + if (state.isMatrixDirty()) { + const QMatrix4x4 m = state.combinedMatrix(); + memcpy(buf->data(), m.constData(), 64); + changed = true; + } + + return changed; +} + +void QSGOpaqueTextureMaterialRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture, + QSGMaterial *newMaterial, QSGMaterial *oldMaterial) +{ + if (binding != 1) + return; + +#ifdef QT_NO_DEBUG + Q_UNUSED(oldMaterial); +#endif + Q_ASSERT(oldMaterial == nullptr || newMaterial->type() == oldMaterial->type()); + QSGOpaqueTextureMaterial *tx = static_cast<QSGOpaqueTextureMaterial *>(newMaterial); + QSGTexture *t = tx->texture(); + + t->setFiltering(tx->filtering()); + t->setMipmapFiltering(tx->mipmapFiltering()); + t->setAnisotropyLevel(tx->anisotropyLevel()); + + t->setHorizontalWrapMode(tx->horizontalWrapMode()); + t->setVerticalWrapMode(tx->verticalWrapMode()); + if (!state.rhi()->isFeatureSupported(QRhi::NPOTTextureRepeat)) { + QSize size = t->textureSize(); + const bool isNpot = !isPowerOfTwo(size.width()) || !isPowerOfTwo(size.height()); + if (isNpot) { + t->setHorizontalWrapMode(QSGTexture::ClampToEdge); + t->setVerticalWrapMode(QSGTexture::ClampToEdge); + t->setMipmapFiltering(QSGTexture::None); + } + } + + t->updateRhiTexture(state.rhi(), state.resourceUpdateBatch()); + *texture = t; +} + + /*! \class QSGOpaqueTextureMaterial \brief The QSGOpaqueTextureMaterial class provides a convenient way of @@ -129,8 +180,8 @@ void QSGOpaqueTextureMaterialShader::updateState(const RenderState &state, QSGMa \inmodule QtQuick \ingroup qtquick-scenegraph-materials - \warning This utility class is only functional when running with the OpenGL - backend of the Qt Quick scenegraph. + \warning This utility class is only functional when running with the + default backend of the Qt Quick scenegraph. The opaque textured material will fill every pixel in a geometry with the supplied texture. The material does not respect the opacity of the @@ -175,6 +226,7 @@ QSGOpaqueTextureMaterial::QSGOpaqueTextureMaterial() , m_vertical_wrap(QSGTexture::ClampToEdge) , m_anisotropy_level(QSGTexture::AnisotropyNone) { + setFlag(SupportsRhiShader, true); } @@ -183,7 +235,8 @@ QSGOpaqueTextureMaterial::QSGOpaqueTextureMaterial() */ QSGMaterialType *QSGOpaqueTextureMaterial::type() const { - return &QSGOpaqueTextureMaterialShader::type; + static QSGMaterialType type; + return &type; } /*! @@ -191,11 +244,13 @@ QSGMaterialType *QSGOpaqueTextureMaterial::type() const */ QSGMaterialShader *QSGOpaqueTextureMaterial::createShader() const { - return new QSGOpaqueTextureMaterialShader; + if (flags().testFlag(RhiShaderWanted)) + return new QSGOpaqueTextureMaterialRhiShader; + else + return new QSGOpaqueTextureMaterialShader; } - /*! \fn QSGTexture *QSGOpaqueTextureMaterial::texture() const @@ -323,7 +378,7 @@ int QSGOpaqueTextureMaterial::compare(const QSGMaterial *o) const { Q_ASSERT(o && type() == o->type()); const QSGOpaqueTextureMaterial *other = static_cast<const QSGOpaqueTextureMaterial *>(o); - if (int diff = m_texture->textureId() - other->texture()->textureId()) + if (int diff = m_texture->comparisonKey() - other->texture()->comparisonKey()) return diff; return int(m_filtering) - int(other->m_filtering); } @@ -337,8 +392,8 @@ int QSGOpaqueTextureMaterial::compare(const QSGMaterial *o) const \inmodule QtQuick \ingroup qtquick-scenegraph-materials - \warning This utility class is only functional when running with the OpenGL - backend of the Qt Quick scenegraph. + \warning This utility class is only functional when running with the + default backend of the Qt Quick scenegraph. The textured material will fill every pixel in a geometry with the supplied texture. @@ -363,32 +418,30 @@ int QSGOpaqueTextureMaterial::compare(const QSGMaterial *o) const a material in the scene graph. */ -QSGMaterialType QSGTextureMaterialShader::type; - - - /*! \internal */ QSGMaterialType *QSGTextureMaterial::type() const { - return &QSGTextureMaterialShader::type; + static QSGMaterialType type; + return &type; } - - /*! \internal */ QSGMaterialShader *QSGTextureMaterial::createShader() const { - return new QSGTextureMaterialShader; + if (flags().testFlag(RhiShaderWanted)) + return new QSGTextureMaterialRhiShader; + else + return new QSGTextureMaterialShader; } + QSGTextureMaterialShader::QSGTextureMaterialShader() - : QSGOpaqueTextureMaterialShader() { #if QT_CONFIG(opengl) setShaderSourceFile(QOpenGLShader::Fragment, QStringLiteral(":/qt-project.org/scenegraph/shaders/texture.frag")); @@ -413,4 +466,27 @@ void QSGTextureMaterialShader::initialize() #endif } + +QSGTextureMaterialRhiShader::QSGTextureMaterialRhiShader() +{ + setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/texture.vert.qsb")); + setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/texture.frag.qsb")); +} + +bool QSGTextureMaterialRhiShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) +{ + bool changed = false; + QByteArray *buf = state.uniformData(); + + if (state.isOpacityDirty()) { + const float opacity = state.opacity(); + memcpy(buf->data() + 64, &opacity, 4); + changed = true; + } + + changed |= QSGOpaqueTextureMaterialRhiShader::updateUniformData(state, newMaterial, oldMaterial); + + return changed; +} + QT_END_NAMESPACE diff --git a/src/quick/scenegraph/util/qsgtexturematerial_p.h b/src/quick/scenegraph/util/qsgtexturematerial_p.h index a99e872580..d1ef7d1d7f 100644 --- a/src/quick/scenegraph/util/qsgtexturematerial_p.h +++ b/src/quick/scenegraph/util/qsgtexturematerial_p.h @@ -64,15 +64,22 @@ public: void updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override; char const *const *attributeNames() const override; - static QSGMaterialType type; - protected: void initialize() override; int m_matrix_id; }; -class Q_QUICK_PRIVATE_EXPORT QSGTextureMaterialShader : public QSGOpaqueTextureMaterialShader +class Q_QUICK_PRIVATE_EXPORT QSGOpaqueTextureMaterialRhiShader : public QSGMaterialRhiShader +{ +public: + QSGOpaqueTextureMaterialRhiShader(); + + bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override; + void updateSampledImage(RenderState &state, int binding, QSGTexture **texture, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override; +}; + +class QSGTextureMaterialShader : public QSGOpaqueTextureMaterialShader { public: QSGTextureMaterialShader(); @@ -80,12 +87,18 @@ public: void updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override; void initialize() override; - static QSGMaterialType type; - protected: int m_opacity_id; }; +class QSGTextureMaterialRhiShader : public QSGOpaqueTextureMaterialRhiShader +{ +public: + QSGTextureMaterialRhiShader(); + + bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override; +}; + QT_END_NAMESPACE #endif // QSGTEXTUREMATERIAL_P_H diff --git a/src/quick/scenegraph/util/qsgvertexcolormaterial.cpp b/src/quick/scenegraph/util/qsgvertexcolormaterial.cpp index cb61430e2e..c27dd7d1f0 100644 --- a/src/quick/scenegraph/util/qsgvertexcolormaterial.cpp +++ b/src/quick/scenegraph/util/qsgvertexcolormaterial.cpp @@ -51,8 +51,6 @@ public: void updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override; char const *const *attributeNames() const override; - static QSGMaterialType type; - private: void initialize() override; #if QT_CONFIG(opengl) @@ -61,8 +59,6 @@ private: #endif }; -QSGMaterialType QSGVertexColorMaterialShader::type; - QSGVertexColorMaterialShader::QSGVertexColorMaterialShader() { #if QT_CONFIG(opengl) @@ -99,6 +95,43 @@ void QSGVertexColorMaterialShader::initialize() } +class QSGVertexColorMaterialRhiShader : public QSGMaterialRhiShader +{ +public: + QSGVertexColorMaterialRhiShader(); + + bool updateUniformData(RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override; +}; + +QSGVertexColorMaterialRhiShader::QSGVertexColorMaterialRhiShader() +{ + setShaderFileName(VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/vertexcolor.vert.qsb")); + setShaderFileName(FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/vertexcolor.frag.qsb")); +} + +bool QSGVertexColorMaterialRhiShader::updateUniformData(RenderState &state, + QSGMaterial * /*newEffect*/, + QSGMaterial * /*oldEffect*/) +{ + bool changed = false; + QByteArray *buf = state.uniformData(); + + if (state.isMatrixDirty()) { + const QMatrix4x4 m = state.combinedMatrix(); + memcpy(buf->data(), m.constData(), 64); + changed = true; + } + + if (state.isOpacityDirty()) { + const float opacity = state.opacity(); + memcpy(buf->data() + 64, &opacity, 4); + changed = true; + } + + return changed; +} + + /*! \class QSGVertexColorMaterial \brief The QSGVertexColorMaterial class provides a convenient way of rendering per-vertex @@ -107,8 +140,8 @@ void QSGVertexColorMaterialShader::initialize() \inmodule QtQuick \ingroup qtquick-scenegraph-materials - \warning This utility class is only functional when running with the OpenGL - backend of the Qt Quick scenegraph. + \warning This utility class is only functional when running with the + default backend of the Qt Quick scenegraph. The vertex color material will give each vertex in a geometry a color. Pixels between vertices will be linearly interpolated. The colors can contain transparency. @@ -135,6 +168,7 @@ void QSGVertexColorMaterialShader::initialize() QSGVertexColorMaterial::QSGVertexColorMaterial() { setFlag(Blending, true); + setFlag(SupportsRhiShader, true); } @@ -158,7 +192,8 @@ int QSGVertexColorMaterial::compare(const QSGMaterial * /* other */) const QSGMaterialType *QSGVertexColorMaterial::type() const { - return &QSGVertexColorMaterialShader::type; + static QSGMaterialType type; + return &type; } @@ -169,7 +204,10 @@ QSGMaterialType *QSGVertexColorMaterial::type() const QSGMaterialShader *QSGVertexColorMaterial::createShader() const { - return new QSGVertexColorMaterialShader; + if (flags().testFlag(RhiShaderWanted)) + return new QSGVertexColorMaterialRhiShader; + else + return new QSGVertexColorMaterialShader; } QT_END_NAMESPACE |