1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qsgrhiatlastexture_p.h"
5
6#include <QtCore/QVarLengthArray>
7#include <QtCore/QElapsedTimer>
8#include <QtCore/QtMath>
9
10#include <QtGui/QWindow>
11
12#include <private/qqmlglobal_p.h>
13#include <private/qsgdefaultrendercontext_p.h>
14#include <private/qsgtexture_p.h>
15#include <private/qsgcompressedtexture_p.h>
16#include <private/qsgcompressedatlastexture_p.h>
17
18QT_BEGIN_NAMESPACE
19
20int qt_sg_envInt(const char *name, int defaultValue);
21
22static QElapsedTimer qsg_renderer_timer;
23
24DEFINE_BOOL_CONFIG_OPTION(qsgEnableCompressedAtlas, QSG_ENABLE_COMPRESSED_ATLAS)
25
26namespace QSGRhiAtlasTexture
27{
28
29Manager::Manager(QSGDefaultRenderContext *rc, const QSize &surfacePixelSize, QSurface *maybeSurface)
30 : m_rc(rc)
31 , m_rhi(rc->rhi())
32{
33 const int maxSize = m_rhi->resourceLimit(limit: QRhi::TextureSizeMax);
34 // surfacePixelSize is just a hint that was passed in when initializing the
35 // rendercontext, likely based on the window size, if it was available,
36 // that is. Therefore, it may be anything, incl. zero and negative.
37 const int widthHint = qMax(a: 1, b: surfacePixelSize.width());
38 const int heightHint = qMax(a: 1, b: surfacePixelSize.height());
39 int w = qMin(a: maxSize, b: qt_sg_envInt(name: "QSG_ATLAS_WIDTH", defaultValue: qMax(a: 512U, b: qNextPowerOfTwo(v: widthHint - 1))));
40 int h = qMin(a: maxSize, b: qt_sg_envInt(name: "QSG_ATLAS_HEIGHT", defaultValue: qMax(a: 512U, b: qNextPowerOfTwo(v: heightHint - 1))));
41
42 if (maybeSurface && maybeSurface->surfaceClass() == QSurface::Window) {
43 QWindow *window = static_cast<QWindow *>(maybeSurface);
44 // Coverwindows, optimize for memory rather than speed
45 if ((window->type() & Qt::CoverWindow) == Qt::CoverWindow) {
46 w /= 2;
47 h /= 2;
48 }
49 }
50
51 m_atlas_size_limit = qt_sg_envInt(name: "QSG_ATLAS_SIZE_LIMIT", defaultValue: qMax(a: w, b: h) / 2);
52 m_atlas_size = QSize(w, h);
53
54 qCDebug(QSG_LOG_INFO, "rhi texture atlas dimensions: %dx%d", w, h);
55}
56
57Manager::~Manager()
58{
59 Q_ASSERT(m_atlas == nullptr);
60 Q_ASSERT(m_atlases.isEmpty());
61}
62
63void Manager::invalidate()
64{
65 if (m_atlas) {
66 m_atlas->invalidate();
67 m_atlas->deleteLater();
68 m_atlas = nullptr;
69 }
70
71 QHash<unsigned int, QSGCompressedAtlasTexture::Atlas*>::iterator i = m_atlases.begin();
72 while (i != m_atlases.end()) {
73 i.value()->invalidate();
74 i.value()->deleteLater();
75 ++i;
76 }
77 m_atlases.clear();
78}
79
80QSGTexture *Manager::create(const QImage &image, bool hasAlphaChannel)
81{
82 Texture *t = nullptr;
83 if (image.width() < m_atlas_size_limit && image.height() < m_atlas_size_limit) {
84 if (!m_atlas)
85 m_atlas = new Atlas(m_rc, m_atlas_size);
86 t = m_atlas->create(image);
87 if (t && !hasAlphaChannel && t->hasAlphaChannel())
88 t->setHasAlphaChannel(false);
89 }
90 return t;
91}
92
93QSGTexture *Manager::create(const QSGCompressedTextureFactory *factory)
94{
95 QSGTexture *t = nullptr;
96 if (!qsgEnableCompressedAtlas() || !factory->textureData()->isValid())
97 return t;
98
99 unsigned int format = factory->textureData()->glInternalFormat();
100 QSGCompressedTexture::FormatInfo fmt = QSGCompressedTexture::formatInfo(glTextureFormat: format);
101 if (!m_rhi->isTextureFormatSupported(format: fmt.rhiFormat))
102 return t;
103
104 QSize size = factory->textureData()->size();
105 if (size.width() < m_atlas_size_limit && size.height() < m_atlas_size_limit) {
106 QHash<unsigned int, QSGCompressedAtlasTexture::Atlas*>::iterator i = m_atlases.find(key: format);
107 if (i == m_atlases.cend()) {
108 auto newAtlas = new QSGCompressedAtlasTexture::Atlas(m_rc, m_atlas_size, format);
109 i = m_atlases.insert(key: format, value: newAtlas);
110 }
111 const QTextureFileData *cmpData = factory->textureData();
112 t = i.value()->create(data: cmpData->getDataView(), size);
113 }
114
115 return t;
116}
117
118AtlasBase::AtlasBase(QSGDefaultRenderContext *rc, const QSize &size)
119 : m_rc(rc)
120 , m_rhi(rc->rhi())
121 , m_allocator(size)
122 , m_size(size)
123{
124}
125
126AtlasBase::~AtlasBase()
127{
128 Q_ASSERT(!m_texture);
129}
130
131void AtlasBase::invalidate()
132{
133 delete m_texture;
134 m_texture = nullptr;
135}
136
137void AtlasBase::commitTextureOperations(QRhiResourceUpdateBatch *resourceUpdates)
138{
139 if (!m_allocated) {
140 m_allocated = true;
141 if (!generateTexture()) {
142 qWarning(msg: "QSGTextureAtlas: Failed to create texture");
143 return;
144 }
145 }
146
147 for (TextureBase *t : m_pending_uploads)
148 enqueueTextureUpload(t, resourceUpdates);
149
150 m_pending_uploads.clear();
151}
152
153void AtlasBase::remove(TextureBase *t)
154{
155 QRect atlasRect = t->atlasSubRect();
156 m_allocator.deallocate(rect: atlasRect);
157 m_pending_uploads.removeOne(t);
158}
159
160Atlas::Atlas(QSGDefaultRenderContext *rc, const QSize &size)
161 : AtlasBase(rc, size)
162{
163 m_format = QRhiTexture::RGBA8;
164
165 // Mirror QSGPlainTexture by playing nice with ARGB32[_Pre], because due to
166 // legacy that's what most images come in, not the byte-ordered
167 // RGBA8888[_Pre]. (i.e. with this the behavior matches 5.15) However,
168 // QSGPlainTexture can make a separate decision for each image (texture),
169 // the atlas cannot, so the downside is that now images that come in the
170 // modern byte-ordered formats need a conversion. So perhaps reconsider this
171 // at some point in the future.
172#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
173 if (rc->rhi()->isTextureFormatSupported(format: QRhiTexture::BGRA8))
174 m_format = QRhiTexture::BGRA8;
175#endif
176
177 m_debug_overlay = qt_sg_envInt(name: "QSG_ATLAS_OVERLAY", defaultValue: 0);
178
179 // images smaller than this will retain their QImage.
180 // by default no images are retained (favoring memory)
181 // set to a very large value to retain all images (allowing quick removal from the atlas)
182 m_atlas_transient_image_threshold = qt_sg_envInt(name: "QSG_ATLAS_TRANSIENT_IMAGE_THRESHOLD", defaultValue: 0);
183}
184
185Atlas::~Atlas()
186{
187}
188
189Texture *Atlas::create(const QImage &image)
190{
191 // No need to lock, as manager already locked it.
192 QRect rect = m_allocator.allocate(size: QSize(image.width() + 2, image.height() + 2));
193 if (rect.width() > 0 && rect.height() > 0) {
194 Texture *t = new Texture(this, rect, image);
195 m_pending_uploads << t;
196 return t;
197 }
198 return nullptr;
199}
200
201bool Atlas::generateTexture()
202{
203 m_texture = m_rhi->newTexture(format: m_format, pixelSize: m_size, sampleCount: 1, flags: QRhiTexture::UsedAsTransferSource);
204 if (!m_texture)
205 return false;
206
207 if (!m_texture->create()) {
208 delete m_texture;
209 m_texture = nullptr;
210 return false;
211 }
212
213 return true;
214}
215
216void Atlas::enqueueTextureUpload(TextureBase *t, QRhiResourceUpdateBatch *resourceUpdates)
217{
218 Texture *tex = static_cast<Texture *>(t);
219 const QRect &r = tex->atlasSubRect();
220 QImage image = tex->image();
221
222 if (image.isNull())
223 return;
224
225 if (m_format == QRhiTexture::BGRA8) {
226 if (image.format() != QImage::Format_RGB32 && image.format() != QImage::Format_ARGB32_Premultiplied)
227 image = std::move(image).convertToFormat(f: QImage::Format_ARGB32_Premultiplied);
228 } else if (image.format() != QImage::Format_RGBA8888_Premultiplied) {
229 image = std::move(image).convertToFormat(f: QImage::Format_RGBA8888_Premultiplied);
230 }
231
232 if (m_debug_overlay) {
233 QPainter p(&image);
234 p.setCompositionMode(QPainter::CompositionMode_SourceAtop);
235 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)));
236 }
237
238 const int iw = image.width();
239 const int ih = image.height();
240 const int bpl = image.bytesPerLine() / 4;
241 QVarLengthArray<quint32, 1024> tmpBits(qMax(a: iw + 2, b: ih + 2));
242 const int tmpBitsSize = tmpBits.size() * 4;
243 const quint32 *src = reinterpret_cast<const quint32 *>(image.constBits());
244 quint32 *dst = tmpBits.data();
245 QVarLengthArray<QRhiTextureUploadEntry, 5> entries;
246
247 // top row, padding corners
248 dst[0] = src[0];
249 memcpy(dest: dst + 1, src: src, n: iw * sizeof(quint32));
250 dst[1 + iw] = src[iw - 1];
251 {
252 QRhiTextureSubresourceUploadDescription subresDesc(dst, tmpBitsSize);
253 subresDesc.setDestinationTopLeft(QPoint(r.x(), r.y()));
254 subresDesc.setSourceSize(QSize(iw + 2, 1));
255 entries.append(t: QRhiTextureUploadEntry(0, 0, subresDesc));
256 }
257
258 // bottom row, padded corners
259 const quint32 *lastRow = src + bpl * (ih - 1);
260 dst[0] = lastRow[0];
261 memcpy(dest: dst + 1, src: lastRow, n: iw * sizeof(quint32));
262 dst[1 + iw] = lastRow[iw - 1];
263 {
264 QRhiTextureSubresourceUploadDescription subresDesc(dst, tmpBitsSize);
265 subresDesc.setDestinationTopLeft(QPoint(r.x(), r.y() + ih + 1));
266 subresDesc.setSourceSize(QSize(iw + 2, 1));
267 entries.append(t: QRhiTextureUploadEntry(0, 0, subresDesc));
268 }
269
270 // left column
271 for (int i = 0; i < ih; ++i)
272 dst[i] = src[i * bpl];
273 {
274 QRhiTextureSubresourceUploadDescription subresDesc(dst, tmpBitsSize);
275 subresDesc.setDestinationTopLeft(QPoint(r.x(), r.y() + 1));
276 subresDesc.setSourceSize(QSize(1, ih));
277 entries.append(t: QRhiTextureUploadEntry(0, 0, subresDesc));
278 }
279
280
281 // right column
282 for (int i = 0; i < ih; ++i)
283 dst[i] = src[i * bpl + iw - 1];
284 {
285 QRhiTextureSubresourceUploadDescription subresDesc(dst, tmpBitsSize);
286 subresDesc.setDestinationTopLeft(QPoint(r.x() + iw + 1, r.y() + 1));
287 subresDesc.setSourceSize(QSize(1, ih));
288 entries.append(t: QRhiTextureUploadEntry(0, 0, subresDesc));
289 }
290
291 // Inner part of the image....
292 if (bpl != iw) {
293 int sy = r.y() + 1;
294 int ey = sy + r.height() - 2;
295 entries.reserve(sz: 4 + (ey - sy));
296 for (int y = sy; y < ey; ++y) {
297 QRhiTextureSubresourceUploadDescription subresDesc(src, image.bytesPerLine());
298 subresDesc.setDestinationTopLeft(QPoint(r.x() + 1, y));
299 subresDesc.setSourceSize(QSize(r.width() - 2, 1));
300 entries.append(t: QRhiTextureUploadEntry(0, 0, subresDesc));
301 src += bpl;
302 }
303 } else {
304 QRhiTextureSubresourceUploadDescription subresDesc(src, image.sizeInBytes());
305 subresDesc.setDestinationTopLeft(QPoint(r.x() + 1, r.y() + 1));
306 subresDesc.setSourceSize(QSize(r.width() - 2, r.height() - 2));
307 entries.append(t: QRhiTextureUploadEntry(0, 0, subresDesc));
308 }
309
310 QRhiTextureUploadDescription desc;
311 desc.setEntries(first: entries.cbegin(), last: entries.cend());
312 resourceUpdates->uploadTexture(tex: m_texture, desc);
313
314 const QSize textureSize = t->textureSize();
315 if (textureSize.width() > m_atlas_transient_image_threshold || textureSize.height() > m_atlas_transient_image_threshold)
316 tex->releaseImage();
317
318 qCDebug(QSG_LOG_TIME_TEXTURE, "atlastexture upload enqueued in: %lldms (%dx%d)",
319 qsg_renderer_timer.elapsed(),
320 t->textureSize().width(),
321 t->textureSize().height());
322}
323
324TextureBase::TextureBase(AtlasBase *atlas, const QRect &textureRect)
325 : QSGTexture(*(new QSGTexturePrivate(this)))
326 , m_allocated_rect(textureRect)
327 , m_atlas(atlas)
328{
329}
330
331TextureBase::~TextureBase()
332{
333 m_atlas->remove(t: this);
334}
335
336qint64 TextureBase::comparisonKey() const
337{
338 // We need special care here: a typical comparisonKey() implementation
339 // returns a unique result when there is no underlying texture yet. This is
340 // not quite ideal for atlasing however since textures with the same atlas
341 // should be considered equal regardless of the state of the underlying
342 // graphics resources.
343
344 // base the comparison on the atlas ptr; this way textures for the same
345 // atlas are considered equal
346 return qint64(m_atlas);
347}
348
349QRhiTexture *TextureBase::rhiTexture() const
350{
351 return m_atlas->m_texture;
352}
353
354void TextureBase::commitTextureOperations(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates)
355{
356#ifdef QT_NO_DEBUG
357 Q_UNUSED(rhi);
358#endif
359 Q_ASSERT(rhi == m_atlas->m_rhi);
360 m_atlas->commitTextureOperations(resourceUpdates);
361}
362
363Texture::Texture(Atlas *atlas, const QRect &textureRect, const QImage &image)
364 : TextureBase(atlas, textureRect)
365 , m_image(image)
366 , m_has_alpha(image.hasAlphaChannel())
367{
368 float w = atlas->size().width();
369 float h = atlas->size().height();
370 QRect nopad = atlasSubRectWithoutPadding();
371 m_texture_coords_rect = QRectF(nopad.x() / w,
372 nopad.y() / h,
373 nopad.width() / w,
374 nopad.height() / h);
375}
376
377Texture::~Texture()
378{
379 if (m_nonatlas_texture)
380 delete m_nonatlas_texture;
381}
382
383QSGTexture *Texture::removedFromAtlas(QRhiResourceUpdateBatch *resourceUpdates) const
384{
385 if (!m_nonatlas_texture) {
386 m_nonatlas_texture = new QSGPlainTexture;
387 if (!m_image.isNull()) {
388 m_nonatlas_texture->setImage(m_image);
389 m_nonatlas_texture->setFiltering(filtering());
390 } else {
391 QSGDefaultRenderContext *rc = m_atlas->renderContext();
392 QRhi *rhi = m_atlas->rhi();
393 Q_ASSERT(rhi->isRecordingFrame());
394 const QRect r = atlasSubRectWithoutPadding();
395
396 QRhiTexture *extractTex = rhi->newTexture(format: m_atlas->texture()->format(), pixelSize: r.size());
397 if (extractTex->create()) {
398 bool ownResUpd = false;
399 QRhiResourceUpdateBatch *resUpd = resourceUpdates;
400 if (!resUpd) {
401 ownResUpd = true;
402 resUpd = rhi->nextResourceUpdateBatch();
403 }
404 QRhiTextureCopyDescription desc;
405 desc.setSourceTopLeft(r.topLeft());
406 desc.setPixelSize(r.size());
407 resUpd->copyTexture(dst: extractTex, src: m_atlas->texture(), desc);
408 if (ownResUpd)
409 rc->currentFrameCommandBuffer()->resourceUpdate(resourceUpdates: resUpd);
410 }
411
412 m_nonatlas_texture->setTexture(extractTex);
413 m_nonatlas_texture->setOwnsTexture(true);
414 m_nonatlas_texture->setHasAlphaChannel(m_has_alpha);
415 m_nonatlas_texture->setTextureSize(r.size());
416 }
417 }
418
419 m_nonatlas_texture->setMipmapFiltering(mipmapFiltering());
420 m_nonatlas_texture->setFiltering(filtering());
421 return m_nonatlas_texture;
422}
423
424}
425
426QT_END_NAMESPACE
427
428#include "moc_qsgrhiatlastexture_p.cpp"
429

source code of qtdeclarative/src/quick/scenegraph/util/qsgrhiatlastexture.cpp