// 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 #include #include #include #include #include #include #include #include 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"