| 1 | /**************************************************************************** | 
| 2 | ** | 
| 3 | ** Copyright (C) 2019 The Qt Company Ltd. | 
| 4 | ** Contact: https://www.qt.io/licensing/ | 
| 5 | ** | 
| 6 | ** This file is part of the QtQuick module of the Qt Toolkit. | 
| 7 | ** | 
| 8 | ** $QT_BEGIN_LICENSE:LGPL$ | 
| 9 | ** Commercial License Usage | 
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in | 
| 11 | ** accordance with the commercial license agreement provided with the | 
| 12 | ** Software or, alternatively, in accordance with the terms contained in | 
| 13 | ** a written agreement between you and The Qt Company. For licensing terms | 
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further | 
| 15 | ** information use the contact form at https://www.qt.io/contact-us. | 
| 16 | ** | 
| 17 | ** GNU Lesser General Public License Usage | 
| 18 | ** Alternatively, this file may be used under the terms of the GNU Lesser | 
| 19 | ** General Public License version 3 as published by the Free Software | 
| 20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the | 
| 21 | ** packaging of this file. Please review the following information to | 
| 22 | ** ensure the GNU Lesser General Public License version 3 requirements | 
| 23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. | 
| 24 | ** | 
| 25 | ** GNU General Public License Usage | 
| 26 | ** Alternatively, this file may be used under the terms of the GNU | 
| 27 | ** General Public License version 2.0 or (at your option) the GNU General | 
| 28 | ** Public license version 3 or any later version approved by the KDE Free | 
| 29 | ** Qt Foundation. The licenses are as published by the Free Software | 
| 30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 | 
| 31 | ** included in the packaging of this file. Please review the following | 
| 32 | ** information to ensure the GNU General Public License requirements will | 
| 33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and | 
| 34 | ** https://www.gnu.org/licenses/gpl-3.0.html. | 
| 35 | ** | 
| 36 | ** $QT_END_LICENSE$ | 
| 37 | ** | 
| 38 | ****************************************************************************/ | 
| 39 |  | 
| 40 | #include "qsgrhiatlastexture_p.h" | 
| 41 |  | 
| 42 | #include <QtCore/QVarLengthArray> | 
| 43 | #include <QtCore/QElapsedTimer> | 
| 44 | #include <QtCore/QtMath> | 
| 45 |  | 
| 46 | #include <QtGui/QWindow> | 
| 47 |  | 
| 48 | #include <private/qqmlglobal_p.h> | 
| 49 | #include <private/qquickprofiler_p.h> | 
| 50 | #include <private/qsgdefaultrendercontext_p.h> | 
| 51 | #include <private/qsgtexture_p.h> | 
| 52 |  | 
| 53 | #include <qtquick_tracepoints_p.h> | 
| 54 |  | 
| 55 | #if 0 | 
| 56 | #include <private/qsgcompressedtexture_p.h> | 
| 57 | #include <private/qsgcompressedatlastexture_p.h> | 
| 58 | #endif | 
| 59 |  | 
| 60 | QT_BEGIN_NAMESPACE | 
| 61 |  | 
| 62 | int qt_sg_envInt(const char *name, int defaultValue); | 
| 63 |  | 
| 64 | static QElapsedTimer qsg_renderer_timer; | 
| 65 |  | 
| 66 | //DEFINE_BOOL_CONFIG_OPTION(qsgEnableCompressedAtlas, QSG_ENABLE_COMPRESSED_ATLAS) | 
| 67 |  | 
| 68 | namespace QSGRhiAtlasTexture | 
| 69 | { | 
| 70 |  | 
| 71 | Manager::Manager(QSGDefaultRenderContext *rc, const QSize &surfacePixelSize, QSurface *maybeSurface) | 
| 72 |     : m_rc(rc) | 
| 73 |     , m_rhi(rc->rhi()) | 
| 74 | { | 
| 75 |     const int maxSize = m_rhi->resourceLimit(limit: QRhi::TextureSizeMax); | 
| 76 |     int w = qMin(a: maxSize, b: qt_sg_envInt(name: "QSG_ATLAS_WIDTH" , defaultValue: qMax(a: 512U, b: qNextPowerOfTwo(v: surfacePixelSize.width() - 1)))); | 
| 77 |     int h = qMin(a: maxSize, b: qt_sg_envInt(name: "QSG_ATLAS_HEIGHT" , defaultValue: qMax(a: 512U, b: qNextPowerOfTwo(v: surfacePixelSize.height() - 1)))); | 
| 78 |  | 
| 79 |     if (maybeSurface && maybeSurface->surfaceClass() == QSurface::Window) { | 
| 80 |         QWindow *window = static_cast<QWindow *>(maybeSurface); | 
| 81 |         // Coverwindows, optimize for memory rather than speed | 
| 82 |         if ((window->type() & Qt::CoverWindow) == Qt::CoverWindow) { | 
| 83 |             w /= 2; | 
| 84 |             h /= 2; | 
| 85 |         } | 
| 86 |     } | 
| 87 |  | 
| 88 |     m_atlas_size_limit = qt_sg_envInt(name: "QSG_ATLAS_SIZE_LIMIT" , defaultValue: qMax(a: w, b: h) / 2); | 
| 89 |     m_atlas_size = QSize(w, h); | 
| 90 |  | 
| 91 |     qCDebug(QSG_LOG_INFO, "rhi texture atlas dimensions: %dx%d" , w, h); | 
| 92 | } | 
| 93 |  | 
| 94 | Manager::~Manager() | 
| 95 | { | 
| 96 |     Q_ASSERT(m_atlas == nullptr); | 
| 97 |     Q_ASSERT(m_atlases.isEmpty()); | 
| 98 | } | 
| 99 |  | 
| 100 | void Manager::invalidate() | 
| 101 | { | 
| 102 |     if (m_atlas) { | 
| 103 |         m_atlas->invalidate(); | 
| 104 |         m_atlas->deleteLater(); | 
| 105 |         m_atlas = nullptr; | 
| 106 |     } | 
| 107 |  | 
| 108 |  #if 0 | 
| 109 |     QHash<unsigned int, QSGCompressedAtlasTexture::Atlas*>::iterator i = m_atlases.begin(); | 
| 110 |     while (i != m_atlases.end()) { | 
| 111 |         i.value()->invalidate(); | 
| 112 |         i.value()->deleteLater(); | 
| 113 |         ++i; | 
| 114 |     } | 
| 115 |     m_atlases.clear(); | 
| 116 | #endif | 
| 117 | } | 
| 118 |  | 
| 119 | QSGTexture *Manager::create(const QImage &image, bool hasAlphaChannel) | 
| 120 | { | 
| 121 |     Texture *t = nullptr; | 
| 122 |     if (image.width() < m_atlas_size_limit && image.height() < m_atlas_size_limit) { | 
| 123 |         if (!m_atlas) | 
| 124 |             m_atlas = new Atlas(m_rc, m_atlas_size); | 
| 125 |         t = m_atlas->create(image); | 
| 126 |         if (t && !hasAlphaChannel && t->hasAlphaChannel()) | 
| 127 |             t->setHasAlphaChannel(false); | 
| 128 |     } | 
| 129 |     return t; | 
| 130 | } | 
| 131 |  | 
| 132 | QSGTexture *Manager::create(const QSGCompressedTextureFactory *factory) | 
| 133 | { | 
| 134 |     Q_UNUSED(factory); | 
| 135 |     return nullptr; | 
| 136 |     // ### | 
| 137 |  | 
| 138 | #if 0 | 
| 139 |     QSGTexture *t = nullptr; | 
| 140 |     if (!qsgEnableCompressedAtlas() || !factory->m_textureData.isValid()) | 
| 141 |         return t; | 
| 142 |  | 
| 143 |     // TODO: further abstract the atlas and remove this restriction | 
| 144 |     unsigned int format = factory->m_textureData.glInternalFormat(); | 
| 145 |     switch (format) { | 
| 146 |     case QOpenGLTexture::RGB8_ETC1: | 
| 147 |     case QOpenGLTexture::RGB8_ETC2: | 
| 148 |     case QOpenGLTexture::RGBA8_ETC2_EAC: | 
| 149 |     case QOpenGLTexture::RGB8_PunchThrough_Alpha1_ETC2: | 
| 150 |         break; | 
| 151 |     default: | 
| 152 |         return t; | 
| 153 |     } | 
| 154 |  | 
| 155 |     QSize size = factory->m_textureData.size(); | 
| 156 |     if (size.width() < m_atlas_size_limit && size.height() < m_atlas_size_limit) { | 
| 157 |         QHash<unsigned int, QSGCompressedAtlasTexture::Atlas*>::iterator i = m_atlases.find(format); | 
| 158 |         if (i == m_atlases.end()) | 
| 159 |             i = m_atlases.insert(format, new QSGCompressedAtlasTexture::Atlas(m_atlas_size, format)); | 
| 160 |         // must be multiple of 4 | 
| 161 |         QSize paddedSize(((size.width() + 3) / 4) * 4, ((size.height() + 3) / 4) * 4); | 
| 162 |         QByteArray data = factory->m_textureData.data(); | 
| 163 |         t = i.value()->create(data, factory->m_textureData.dataLength(), factory->m_textureData.dataOffset(), size, paddedSize); | 
| 164 |     } | 
| 165 | #endif | 
| 166 | } | 
| 167 |  | 
| 168 | AtlasBase::AtlasBase(QSGDefaultRenderContext *rc, const QSize &size) | 
| 169 |     : m_rc(rc) | 
| 170 |     , m_rhi(rc->rhi()) | 
| 171 |     , m_allocator(size) | 
| 172 |     , m_size(size) | 
| 173 | { | 
| 174 | } | 
| 175 |  | 
| 176 | AtlasBase::~AtlasBase() | 
| 177 | { | 
| 178 |     Q_ASSERT(!m_texture); | 
| 179 | } | 
| 180 |  | 
| 181 | void AtlasBase::invalidate() | 
| 182 | { | 
| 183 |     delete m_texture; | 
| 184 |     m_texture = nullptr; | 
| 185 | } | 
| 186 |  | 
| 187 | void AtlasBase::updateRhiTexture(QRhiResourceUpdateBatch *resourceUpdates) | 
| 188 | { | 
| 189 |     if (!m_allocated) { | 
| 190 |         m_allocated = true; | 
| 191 |         if (!generateTexture()) { | 
| 192 |             qWarning(msg: "QSGTextureAtlas: Failed to create texture" ); | 
| 193 |             return; | 
| 194 |         } | 
| 195 |     } | 
| 196 |  | 
| 197 |     for (TextureBase *t : m_pending_uploads) { | 
| 198 |         // ### this profiling is all wrong, the real work is done elsewhere | 
| 199 |         bool profileFrames = QSG_LOG_TIME_TEXTURE().isDebugEnabled(); | 
| 200 |         if (profileFrames) | 
| 201 |             qsg_renderer_timer.start(); | 
| 202 |  | 
| 203 |         Q_TRACE_SCOPE(QSG_texture_prepare); | 
| 204 |         Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphTexturePrepare); | 
| 205 |  | 
| 206 |         // Skip bind, convert, swizzle; they're irrelevant | 
| 207 |         Q_QUICK_SG_PROFILE_SKIP(QQuickProfiler::SceneGraphTexturePrepare, | 
| 208 |                                 QQuickProfiler::SceneGraphTexturePrepareStart, 3); | 
| 209 |         Q_TRACE(QSG_texture_upload_entry); | 
| 210 |  | 
| 211 |         enqueueTextureUpload(t, resourceUpdates); | 
| 212 |  | 
| 213 |         Q_TRACE(QSG_texture_upload_exit); | 
| 214 |         Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphTexturePrepare, | 
| 215 |                                   QQuickProfiler::SceneGraphTexturePrepareUpload); | 
| 216 |  | 
| 217 |         // Skip mipmap; unused | 
| 218 |         Q_QUICK_SG_PROFILE_SKIP(QQuickProfiler::SceneGraphTexturePrepare, | 
| 219 |                                 QQuickProfiler::SceneGraphTexturePrepareUpload, 1); | 
| 220 |         Q_QUICK_SG_PROFILE_REPORT(QQuickProfiler::SceneGraphTexturePrepare, | 
| 221 |                                   QQuickProfiler::SceneGraphTexturePrepareMipmap); | 
| 222 |     } | 
| 223 |  | 
| 224 |     m_pending_uploads.clear(); | 
| 225 | } | 
| 226 |  | 
| 227 | void AtlasBase::remove(TextureBase *t) | 
| 228 | { | 
| 229 |     QRect atlasRect = t->atlasSubRect(); | 
| 230 |     m_allocator.deallocate(rect: atlasRect); | 
| 231 |     m_pending_uploads.removeOne(t); | 
| 232 | } | 
| 233 |  | 
| 234 | Atlas::Atlas(QSGDefaultRenderContext *rc, const QSize &size) | 
| 235 |     : AtlasBase(rc, size) | 
| 236 | { | 
| 237 |     // use RGBA texture internally as that is the only one guaranteed to be always supported | 
| 238 |     m_format = QRhiTexture::RGBA8; | 
| 239 |  | 
| 240 |     m_debug_overlay = qt_sg_envInt(name: "QSG_ATLAS_OVERLAY" , defaultValue: 0); | 
| 241 |  | 
| 242 |     // images smaller than this will retain their QImage. | 
| 243 |     // by default no images are retained (favoring memory) | 
| 244 |     // set to a very large value to retain all images (allowing quick removal from the atlas) | 
| 245 |     m_atlas_transient_image_threshold = qt_sg_envInt(name: "QSG_ATLAS_TRANSIENT_IMAGE_THRESHOLD" , defaultValue: 0); | 
| 246 | } | 
| 247 |  | 
| 248 | Atlas::~Atlas() | 
| 249 | { | 
| 250 | } | 
| 251 |  | 
| 252 | Texture *Atlas::create(const QImage &image) | 
| 253 | { | 
| 254 |     // No need to lock, as manager already locked it. | 
| 255 |     QRect rect = m_allocator.allocate(size: QSize(image.width() + 2, image.height() + 2)); | 
| 256 |     if (rect.width() > 0 && rect.height() > 0) { | 
| 257 |         Texture *t = new Texture(this, rect, image); | 
| 258 |         m_pending_uploads << t; | 
| 259 |         return t; | 
| 260 |     } | 
| 261 |     return nullptr; | 
| 262 | } | 
| 263 |  | 
| 264 | bool Atlas::generateTexture() | 
| 265 | { | 
| 266 |     m_texture = m_rhi->newTexture(format: m_format, pixelSize: m_size, sampleCount: 1, flags: QRhiTexture::UsedAsTransferSource); | 
| 267 |     if (!m_texture) | 
| 268 |         return false; | 
| 269 |  | 
| 270 |     if (!m_texture->build()) { | 
| 271 |         delete m_texture; | 
| 272 |         m_texture = nullptr; | 
| 273 |         return false; | 
| 274 |     } | 
| 275 |  | 
| 276 |     return true; | 
| 277 | } | 
| 278 |  | 
| 279 | void Atlas::enqueueTextureUpload(TextureBase *t, QRhiResourceUpdateBatch *resourceUpdates) | 
| 280 | { | 
| 281 |     Texture *tex = static_cast<Texture *>(t); | 
| 282 |     const QRect &r = tex->atlasSubRect(); | 
| 283 |     QImage image = tex->image(); | 
| 284 |  | 
| 285 |     if (image.isNull()) | 
| 286 |         return; | 
| 287 |  | 
| 288 |     if (image.format() != QImage::Format_RGBA8888_Premultiplied) | 
| 289 |         image = std::move(image).convertToFormat(f: QImage::Format_RGBA8888_Premultiplied); | 
| 290 |  | 
| 291 |     if (m_debug_overlay) { | 
| 292 |         QPainter p(&image); | 
| 293 |         p.setCompositionMode(QPainter::CompositionMode_SourceAtop); | 
| 294 |         p.fillRect(x: 0, y: 0, w: image.width(), h: image.height(), b: QBrush(QColor::fromRgbF(r: 0, g: 1, b: 1, a: 0.5))); | 
| 295 |     } | 
| 296 |  | 
| 297 |     const int iw = image.width(); | 
| 298 |     const int ih = image.height(); | 
| 299 |     const int bpl = image.bytesPerLine() / 4; | 
| 300 |     QVarLengthArray<quint32, 1024> tmpBits(qMax(a: iw + 2, b: ih + 2)); | 
| 301 |     const int tmpBitsSize = tmpBits.size() * 4; | 
| 302 |     const quint32 *src = reinterpret_cast<const quint32 *>(image.constBits()); | 
| 303 |     quint32 *dst = tmpBits.data(); | 
| 304 |     QVarLengthArray<QRhiTextureUploadEntry, 5> entries; | 
| 305 |  | 
| 306 |     // top row, padding corners | 
| 307 |     dst[0] = src[0]; | 
| 308 |     memcpy(dest: dst + 1, src: src, n: iw * sizeof(quint32)); | 
| 309 |     dst[1 + iw] = src[iw - 1]; | 
| 310 |     { | 
| 311 |         QRhiTextureSubresourceUploadDescription subresDesc(dst, tmpBitsSize); | 
| 312 |         subresDesc.setDestinationTopLeft(QPoint(r.x(), r.y())); | 
| 313 |         subresDesc.setSourceSize(QSize(iw + 2, 1)); | 
| 314 |         entries.append(t: QRhiTextureUploadEntry(0, 0, subresDesc)); | 
| 315 |     } | 
| 316 |  | 
| 317 |     // bottom row, padded corners | 
| 318 |     const quint32 *lastRow = src + bpl * (ih - 1); | 
| 319 |     dst[0] = lastRow[0]; | 
| 320 |     memcpy(dest: dst + 1, src: lastRow, n: iw * sizeof(quint32)); | 
| 321 |     dst[1 + iw] = lastRow[iw - 1]; | 
| 322 |     { | 
| 323 |         QRhiTextureSubresourceUploadDescription subresDesc(dst, tmpBitsSize); | 
| 324 |         subresDesc.setDestinationTopLeft(QPoint(r.x(), r.y() + ih + 1)); | 
| 325 |         subresDesc.setSourceSize(QSize(iw + 2, 1)); | 
| 326 |         entries.append(t: QRhiTextureUploadEntry(0, 0, subresDesc)); | 
| 327 |     } | 
| 328 |  | 
| 329 |     // left column | 
| 330 |     for (int i = 0; i < ih; ++i) | 
| 331 |         dst[i] = src[i * bpl]; | 
| 332 |     { | 
| 333 |         QRhiTextureSubresourceUploadDescription subresDesc(dst, tmpBitsSize); | 
| 334 |         subresDesc.setDestinationTopLeft(QPoint(r.x(), r.y() + 1)); | 
| 335 |         subresDesc.setSourceSize(QSize(1, ih)); | 
| 336 |         entries.append(t: QRhiTextureUploadEntry(0, 0, subresDesc)); | 
| 337 |     } | 
| 338 |  | 
| 339 |  | 
| 340 |     // right column | 
| 341 |     for (int i = 0; i < ih; ++i) | 
| 342 |         dst[i] = src[i * bpl + iw - 1]; | 
| 343 |     { | 
| 344 |         QRhiTextureSubresourceUploadDescription subresDesc(dst, tmpBitsSize); | 
| 345 |         subresDesc.setDestinationTopLeft(QPoint(r.x() + iw + 1, r.y() + 1)); | 
| 346 |         subresDesc.setSourceSize(QSize(1, ih)); | 
| 347 |         entries.append(t: QRhiTextureUploadEntry(0, 0, subresDesc)); | 
| 348 |     } | 
| 349 |  | 
| 350 |     // Inner part of the image.... | 
| 351 |     if (bpl != iw) { | 
| 352 |         int sy = r.y() + 1; | 
| 353 |         int ey = sy + r.height() - 2; | 
| 354 |         entries.reserve(size: 4 + (ey - sy)); | 
| 355 |         for (int y = sy; y < ey; ++y) { | 
| 356 |             QRhiTextureSubresourceUploadDescription subresDesc(src, image.bytesPerLine()); | 
| 357 |             subresDesc.setDestinationTopLeft(QPoint(r.x() + 1, y)); | 
| 358 |             subresDesc.setSourceSize(QSize(r.width() - 2, 1)); | 
| 359 |             entries.append(t: QRhiTextureUploadEntry(0, 0, subresDesc)); | 
| 360 |             src += bpl; | 
| 361 |         } | 
| 362 |     } else { | 
| 363 |         QRhiTextureSubresourceUploadDescription subresDesc(src, image.sizeInBytes()); | 
| 364 |         subresDesc.setDestinationTopLeft(QPoint(r.x() + 1, r.y() + 1)); | 
| 365 |         subresDesc.setSourceSize(QSize(r.width() - 2, r.height() - 2)); | 
| 366 |         entries.append(t: QRhiTextureUploadEntry(0, 0, subresDesc)); | 
| 367 |     } | 
| 368 |  | 
| 369 |     QRhiTextureUploadDescription desc; | 
| 370 |     desc.setEntries(first: entries.cbegin(), last: entries.cend()); | 
| 371 |     resourceUpdates->uploadTexture(tex: m_texture, desc); | 
| 372 |  | 
| 373 |     const QSize textureSize = t->textureSize(); | 
| 374 |     if (textureSize.width() > m_atlas_transient_image_threshold || textureSize.height() > m_atlas_transient_image_threshold) | 
| 375 |         tex->releaseImage(); | 
| 376 |  | 
| 377 |     qCDebug(QSG_LOG_TIME_TEXTURE, "atlastexture upload enqueued in: %lldms (%dx%d)" , | 
| 378 |             qsg_renderer_timer.elapsed(), | 
| 379 |             t->textureSize().width(), | 
| 380 |             t->textureSize().height()); | 
| 381 | } | 
| 382 |  | 
| 383 | TextureBase::TextureBase(AtlasBase *atlas, const QRect &textureRect) | 
| 384 |     : QSGTexture(*(new TextureBasePrivate)) | 
| 385 |     , m_allocated_rect(textureRect) | 
| 386 |     , m_atlas(atlas) | 
| 387 | { | 
| 388 | } | 
| 389 |  | 
| 390 | TextureBase::~TextureBase() | 
| 391 | { | 
| 392 |     m_atlas->remove(t: this); | 
| 393 | } | 
| 394 |  | 
| 395 | QRhiResourceUpdateBatch *TextureBase::workResourceUpdateBatch() const | 
| 396 | { | 
| 397 |     Q_D(const TextureBase); | 
| 398 |     return d->workResourceUpdateBatch; | 
| 399 | } | 
| 400 |  | 
| 401 | int TextureBasePrivate::comparisonKey() const | 
| 402 | { | 
| 403 |     Q_Q(const TextureBase); | 
| 404 |  | 
| 405 |     // We need special care here: a typical comparisonKey() implementation | 
| 406 |     // returns a unique result when there is no underlying texture yet. This is | 
| 407 |     // not quite ideal for atlasing however since textures with the same atlas | 
| 408 |     // should be considered equal regardless of the state of the underlying | 
| 409 |     // graphics resources. | 
| 410 |  | 
| 411 |     // base the comparison on the atlas ptr; this way textures for the same | 
| 412 |     // atlas are considered equal | 
| 413 |     return int(qintptr(q->m_atlas)); | 
| 414 | } | 
| 415 |  | 
| 416 | QRhiTexture *TextureBasePrivate::rhiTexture() const | 
| 417 | { | 
| 418 |     Q_Q(const TextureBase); | 
| 419 |     return q->m_atlas->m_texture; | 
| 420 | } | 
| 421 |  | 
| 422 | void TextureBasePrivate::updateRhiTexture(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates) | 
| 423 | { | 
| 424 |     Q_Q(TextureBase); | 
| 425 | #ifdef QT_NO_DEBUG | 
| 426 |     Q_UNUSED(rhi); | 
| 427 | #endif | 
| 428 |     Q_ASSERT(rhi == q->m_atlas->m_rhi); | 
| 429 |     q->m_atlas->updateRhiTexture(resourceUpdates); | 
| 430 | } | 
| 431 |  | 
| 432 | Texture::Texture(Atlas *atlas, const QRect &textureRect, const QImage &image) | 
| 433 |     : TextureBase(atlas, textureRect) | 
| 434 |     , m_image(image) | 
| 435 |     , m_has_alpha(image.hasAlphaChannel()) | 
| 436 | { | 
| 437 |     float w = atlas->size().width(); | 
| 438 |     float h = atlas->size().height(); | 
| 439 |     QRect nopad = atlasSubRectWithoutPadding(); | 
| 440 |     m_texture_coords_rect = QRectF(nopad.x() / w, | 
| 441 |                                    nopad.y() / h, | 
| 442 |                                    nopad.width() / w, | 
| 443 |                                    nopad.height() / h); | 
| 444 | } | 
| 445 |  | 
| 446 | Texture::~Texture() | 
| 447 | { | 
| 448 |     if (m_nonatlas_texture) | 
| 449 |         delete m_nonatlas_texture; | 
| 450 | } | 
| 451 |  | 
| 452 | QSGTexture *Texture::removedFromAtlas() const | 
| 453 | { | 
| 454 |     if (!m_nonatlas_texture) { | 
| 455 |         m_nonatlas_texture = new QSGPlainTexture; | 
| 456 |         if (!m_image.isNull()) { | 
| 457 |             m_nonatlas_texture->setImage(m_image); | 
| 458 |             m_nonatlas_texture->setFiltering(filtering()); | 
| 459 |         } else { | 
| 460 |             QSGDefaultRenderContext *rc = m_atlas->renderContext(); | 
| 461 |             QRhi *rhi = m_atlas->rhi(); | 
| 462 |             Q_ASSERT(rhi->isRecordingFrame()); | 
| 463 |             const QRect r = atlasSubRectWithoutPadding(); | 
| 464 |  | 
| 465 |             QRhiTexture * = rhi->newTexture(format: m_atlas->texture()->format(), pixelSize: r.size()); | 
| 466 |             if (extractTex->build()) { | 
| 467 |                 bool ownResUpd = false; | 
| 468 |                 QRhiResourceUpdateBatch *resUpd = workResourceUpdateBatch(); // ### Qt 6: should be an arg to this function | 
| 469 |                 if (!resUpd) { | 
| 470 |                     ownResUpd = true; | 
| 471 |                     resUpd = rhi->nextResourceUpdateBatch(); | 
| 472 |                 } | 
| 473 |                 QRhiTextureCopyDescription desc; | 
| 474 |                 desc.setSourceTopLeft(r.topLeft()); | 
| 475 |                 desc.setPixelSize(r.size()); | 
| 476 |                 resUpd->copyTexture(dst: extractTex, src: m_atlas->texture(), desc); | 
| 477 |                 if (ownResUpd) | 
| 478 |                     rc->currentFrameCommandBuffer()->resourceUpdate(resourceUpdates: resUpd); | 
| 479 |             } | 
| 480 |  | 
| 481 |             m_nonatlas_texture->setTexture(extractTex); | 
| 482 |             m_nonatlas_texture->setOwnsTexture(true); | 
| 483 |             m_nonatlas_texture->setHasAlphaChannel(m_has_alpha); | 
| 484 |             m_nonatlas_texture->setTextureSize(r.size()); | 
| 485 |         } | 
| 486 |     } | 
| 487 |  | 
| 488 |     m_nonatlas_texture->setMipmapFiltering(mipmapFiltering()); | 
| 489 |     m_nonatlas_texture->setFiltering(filtering()); | 
| 490 |     return m_nonatlas_texture; | 
| 491 | } | 
| 492 |  | 
| 493 | } | 
| 494 |  | 
| 495 | QT_END_NAMESPACE | 
| 496 |  | 
| 497 | #include "moc_qsgrhiatlastexture_p.cpp" | 
| 498 |  |