Revision control

++ // Copyright (C) 2019 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qsgplaintexture_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>
++ #include <rhi/qrhi.h>
++ #include <QtQuick/private/qsgrhisupport_p.h>
++
++ #include <qtquick_tracepoints_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ QSGPlainTexture::QSGPlainTexture()
++ : QSGTexture(*(new QSGPlainTexturePrivate(this)))
++ , m_texture(nullptr)
++ , m_has_alpha(false)
++ , m_dirty_texture(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(nullptr)
++ , m_has_alpha(false)
++ , m_dirty_texture(false)
++ , m_owns_texture(true)
++ , m_mipmaps_generated(false)
++ , m_retain_image(false)
++ , m_mipmap_warned(false)
++ {
++ }
++
++ QSGPlainTexture::~QSGPlainTexture()
++ {
++ 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_mipmaps_generated = false;
++ }
++
++ 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_image = QImage();
++ m_mipmaps_generated = false;
++ }
++
++ void QSGPlainTexture::setTextureFromNativeTexture(QRhi *rhi,
++ quint64 nativeObjectHandle,
++ int nativeLayoutOrState,
++ uint nativeFormat,
++ const QSize &size,
++ QQuickWindow::CreateTextureOptions options,
++ QQuickWindowPrivate::TextureFromNativeTextureFlags flags)
++ {
++ QRhiTexture::Flags texFlags;
++ if (options.testFlag(QQuickWindow::TextureHasMipmaps))
++ texFlags |= QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips;
++ if (flags.testFlag(QQuickWindowPrivate::NativeTextureIsExternalOES))
++ texFlags |= QRhiTexture::ExternalOES;
++
++ QRhiTexture::Format format = QRhiTexture::RGBA8;
++
++ QRhiTexture::Flags formatFlags;
++ auto rhiFormat = QSGRhiSupport::instance()->toRhiTextureFormat(nativeFormat, &formatFlags);
++ if (rhiFormat != QRhiTexture::UnknownFormat) {
++ format = rhiFormat;
++ texFlags |= formatFlags;
++ }
++
++ QRhiTexture *t = rhi->newTexture(format, size, 1, texFlags);
++
++ // ownership of the native object is never taken
++ t->createFrom({nativeObjectHandle, nativeLayoutOrState});
++
++ setTexture(t);
++ }
++
++ qint64 QSGPlainTexture::comparisonKey() const
++ {
++ if (m_texture)
++ return qint64(m_texture);
++
++ // two textures (and so materials) with not-yet-created texture underneath are never equal
++ return qint64(this);
++ }
++
++ QRhiTexture *QSGPlainTexture::rhiTexture() const
++ {
++ return m_texture;
++ }
++
++ void QSGPlainTexture::commitTextureOperations(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates)
++ {
++ Q_D(QSGPlainTexture);
++
++ const bool hasMipMaps = mipmapFiltering() != QSGTexture::None;
++ const bool mipmappingChanged = m_texture && ((hasMipMaps && !m_texture->flags().testFlag(QRhiTexture::MipMapped)) // did not have it before
++ || (!hasMipMaps && m_texture->flags().testFlag(QRhiTexture::MipMapped))); // does not have it anymore
++
++ if (!m_dirty_texture) {
++ if (!m_texture)
++ return;
++ if (m_texture && !mipmappingChanged) {
++ if (hasMipMaps && !m_mipmaps_generated) {
++ resourceUpdates->generateMips(m_texture);
++ m_mipmaps_generated = true;
++ }
++ return;
++ }
++ }
++
++ if (m_image.isNull()) {
++ if (!m_dirty_texture && mipmappingChanged) {
++ // Full Mipmap Panic!
++ if (!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.");
++ m_mipmap_warned = true;
++ }
++ // leave the texture valid and rather ignore the mipmap mode change attempt
++ setMipmapFiltering(d->m_last_mipmap_filter);
++ return;
++ }
++
++ if (m_texture && m_owns_texture)
++ delete m_texture;
++
++ m_texture = nullptr;
++ m_texture_size = QSize();
++ m_has_alpha = false;
++
++ m_dirty_texture = false;
++ return;
++ }
++
++ m_dirty_texture = false;
++
++ QImage tmp;
++ bool bgra = false;
++ bool fp16 = false;
++ bool fp32 = false;
++ bool needsConvert8 = false;
++ bool needsConvertFP16 = false;
++ bool needsConvertFP32 = false;
++
++ if (m_image.format() == QImage::Format_RGB32 || m_image.format() == QImage::Format_ARGB32_Premultiplied) {
++ #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
++ if (rhi->isTextureFormatSupported(QRhiTexture::BGRA8)) {
++ tmp = m_image;
++ bgra = true;
++ } else {
++ needsConvert8 = true;
++ }
++ #else
++ needsConvert8 = true;
++ #endif
++ } else if (m_image.format() == QImage::Format_RGBX16FPx4 || m_image.format() == QImage::Format_RGBA16FPx4_Premultiplied) {
++ #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
++ if (rhi->isTextureFormatSupported(QRhiTexture::RGBA16F)) {
++ tmp = m_image;
++ fp16 = true;
++ } else {
++ needsConvert8 = true;
++ }
++ #else
++ needsConvert8 = true;
++ #endif
++ } else if (m_image.format() == QImage::Format_RGBX32FPx4 || m_image.format() == QImage::Format_RGBA32FPx4_Premultiplied) {
++ #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
++ if (rhi->isTextureFormatSupported(QRhiTexture::RGBA32F)) {
++ tmp = m_image;
++ fp32 = true;
++ } else if (rhi->isTextureFormatSupported(QRhiTexture::RGBA16F)) {
++ tmp = m_image;
++ fp16 = true;
++ } else {
++ needsConvert8 = true;
++ }
++ #else
++ needsConvert8 = true;
++ #endif
++ } else if (m_image.format() == QImage::Format_RGBX8888 || m_image.format() == QImage::Format_RGBA8888_Premultiplied) {
++ tmp = m_image;
++ } else if (m_image.format() == QImage::Format_RGBA16FPx4) {
++ needsConvertFP16 = true;
++ } else if (m_image.format() == QImage::Format_RGBA32FPx4) {
++ needsConvertFP32 = true;
++ } else {
++ needsConvert8 = true;
++ }
++
++ if (needsConvertFP16) {
++ tmp = m_image.convertToFormat(QImage::Format_RGBA16FPx4_Premultiplied);
++ fp16 = true;
++ } else if (needsConvertFP32) {
++ tmp = m_image.convertToFormat(QImage::Format_RGBA32FPx4_Premultiplied);
++ fp32 = true;
++ } else if (needsConvert8) {
++ tmp = 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);
++ m_texture_size = tmp.size();
++ }
++
++ if ((mipmapFiltering() != QSGTexture::None
++ || horizontalWrapMode() != QSGTexture::ClampToEdge
++ || 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);
++ m_texture_size = tmp.size();
++ }
++ }
++
++ bool needsRebuild = false;
++
++ if (m_texture && m_texture->pixelSize() != m_texture_size) {
++ m_texture->setPixelSize(m_texture_size);
++ needsRebuild = true;
++ }
++
++ if (mipmappingChanged) {
++ QRhiTexture::Flags f = m_texture->flags();
++ f.setFlag(QRhiTexture::MipMapped, hasMipMaps);
++ f.setFlag(QRhiTexture::UsedWithGenerateMips, hasMipMaps);
++ m_texture->setFlags(f);
++ needsRebuild = true;
++ }
++
++ if (!m_texture) {
++ QRhiTexture::Flags f;
++ if (hasMipMaps)
++ f |= QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips;
++
++ QRhiTexture::Format format = QRhiTexture::RGBA8;
++ if (fp16)
++ format = QRhiTexture::RGBA16F;
++ else if (fp32)
++ format = QRhiTexture::RGBA32F;
++ else if (bgra)
++ format = QRhiTexture::BGRA8;
++
++ m_texture = rhi->newTexture(format, m_texture_size, 1, f);
++ needsRebuild = true;
++ }
++
++ if (needsRebuild) {
++ if (!m_texture->create()) {
++ qWarning("Failed to build texture for QSGPlainTexture (size %dx%d)",
++ m_texture_size.width(), m_texture_size.height());
++ return;
++ }
++ }
++
++ if (tmp.width() * 4 != tmp.bytesPerLine())
++ tmp = tmp.copy();
++
++ resourceUpdates->uploadTexture(m_texture, tmp);
++
++ if (hasMipMaps) {
++ resourceUpdates->generateMips(m_texture);
++ m_mipmaps_generated = true;
++ }
++
++ d->m_last_mipmap_filter = mipmapFiltering();
++ m_texture_rect = QRectF(0, 0, 1, 1);
++
++ if (!m_retain_image)
++ m_image = QImage();
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qsgplaintexture_p.cpp"