Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qsgrhiatlastexture.cpp
Go to the documentation of this file.
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
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
19
20int qt_sg_envInt(const char *name, int defaultValue);
21
23
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(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(1, surfacePixelSize.width());
38 const int heightHint = qMax(1, surfacePixelSize.height());
39 int w = qMin(maxSize, qt_sg_envInt("QSG_ATLAS_WIDTH", qMax(512U, qNextPowerOfTwo(widthHint - 1))));
40 int h = qMin(maxSize, qt_sg_envInt("QSG_ATLAS_HEIGHT", qMax(512U, qNextPowerOfTwo(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("QSG_ATLAS_SIZE_LIMIT", qMax(w, 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
58{
59 Q_ASSERT(m_atlas == nullptr);
60 Q_ASSERT(m_atlases.isEmpty());
61}
62
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.isNull())
84 return t;
85 if (image.width() < m_atlas_size_limit && image.height() < m_atlas_size_limit) {
86 if (!m_atlas)
87 m_atlas = new Atlas(m_rc, m_atlas_size);
88 t = m_atlas->create(image);
89 if (t && !hasAlphaChannel && t->hasAlphaChannel())
91 }
92 return t;
93}
94
95QSGTexture *Manager::create(const QSGCompressedTextureFactory *factory)
96{
97 QSGTexture *t = nullptr;
98 if (!qsgEnableCompressedAtlas() || !factory->textureData()->isValid())
99 return t;
100
101 unsigned int format = factory->textureData()->glInternalFormat();
102 QSGCompressedTexture::FormatInfo fmt = QSGCompressedTexture::formatInfo(format);
103 if (!m_rhi->isTextureFormatSupported(fmt.rhiFormat))
104 return t;
105
106 QSize size = factory->textureData()->size();
107 if (size.width() < m_atlas_size_limit && size.height() < m_atlas_size_limit) {
108 QHash<unsigned int, QSGCompressedAtlasTexture::Atlas*>::const_iterator i = m_atlases.constFind(format);
109 if (i == m_atlases.cend()) {
110 auto newAtlas = new QSGCompressedAtlasTexture::Atlas(m_rc, m_atlas_size, format);
111 i = m_atlases.insert(format, newAtlas);
112 }
113 const QTextureFileData *cmpData = factory->textureData();
114 t = i.value()->create(cmpData->getDataView(), size);
115 }
116
117 return t;
118}
119
120AtlasBase::AtlasBase(QSGDefaultRenderContext *rc, const QSize &size)
121 : m_rc(rc)
122 , m_rhi(rc->rhi())
123 , m_allocator(size)
124 , m_size(size)
125{
126}
127
129{
130 Q_ASSERT(!m_texture);
131}
132
134{
135 delete m_texture;
136 m_texture = nullptr;
137}
138
139void AtlasBase::commitTextureOperations(QRhiResourceUpdateBatch *resourceUpdates)
140{
141 if (!m_allocated) {
142 m_allocated = true;
143 if (!generateTexture()) {
144 qWarning("QSGTextureAtlas: Failed to create texture");
145 return;
146 }
147 }
148
149 for (TextureBase *t : std::as_const(m_pending_uploads))
150 enqueueTextureUpload(t, resourceUpdates);
151
152 m_pending_uploads.clear();
153}
154
156{
157 QRect atlasRect = t->atlasSubRect();
158 m_allocator.deallocate(atlasRect);
159 m_pending_uploads.removeOne(t);
160}
161
162Atlas::Atlas(QSGDefaultRenderContext *rc, const QSize &size)
163 : AtlasBase(rc, size)
164{
165 m_format = QRhiTexture::RGBA8;
166
167 // Mirror QSGPlainTexture by playing nice with ARGB32[_Pre], because due to
168 // legacy that's what most images come in, not the byte-ordered
169 // RGBA8888[_Pre]. (i.e. with this the behavior matches 5.15) However,
170 // QSGPlainTexture can make a separate decision for each image (texture),
171 // the atlas cannot, so the downside is that now images that come in the
172 // modern byte-ordered formats need a conversion. So perhaps reconsider this
173 // at some point in the future.
174#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
175 if (rc->rhi()->isTextureFormatSupported(QRhiTexture::BGRA8))
176 m_format = QRhiTexture::BGRA8;
177#endif
178
179 m_debug_overlay = qt_sg_envInt("QSG_ATLAS_OVERLAY", 0);
180
181 // images smaller than this will retain their QImage.
182 // by default no images are retained (favoring memory)
183 // set to a very large value to retain all images (allowing quick removal from the atlas)
184 m_atlas_transient_image_threshold = qt_sg_envInt("QSG_ATLAS_TRANSIENT_IMAGE_THRESHOLD", 0);
185}
186
188{
189}
190
191Texture *Atlas::create(const QImage &image)
192{
193 // No need to lock, as manager already locked it.
194 QRect rect = m_allocator.allocate(QSize(image.width() + 2, image.height() + 2));
195 if (rect.width() > 0 && rect.height() > 0) {
196 Texture *t = new Texture(this, rect, image);
197 m_pending_uploads << t;
198 return t;
199 }
200 return nullptr;
201}
202
204{
205 m_texture = m_rhi->newTexture(m_format, m_size, 1, QRhiTexture::UsedAsTransferSource);
206 if (!m_texture)
207 return false;
208
209 if (!m_texture->create()) {
210 delete m_texture;
211 m_texture = nullptr;
212 return false;
213 }
214
215 return true;
216}
217
218void Atlas::enqueueTextureUpload(TextureBase *t, QRhiResourceUpdateBatch *resourceUpdates)
219{
220 Texture *tex = static_cast<Texture *>(t);
221 const QRect &r = tex->atlasSubRect();
222 QImage image = tex->image();
223
224 if (image.isNull())
225 return;
226
227 if (m_format == QRhiTexture::BGRA8) {
228 if (image.format() != QImage::Format_RGB32 && image.format() != QImage::Format_ARGB32_Premultiplied)
229 image = std::move(image).convertToFormat(QImage::Format_ARGB32_Premultiplied);
230 } else if (image.format() != QImage::Format_RGBA8888_Premultiplied) {
231 image = std::move(image).convertToFormat(QImage::Format_RGBA8888_Premultiplied);
232 }
233
234 if (m_debug_overlay) {
235 QPainter p(&image);
236 p.setCompositionMode(QPainter::CompositionMode_SourceAtop);
237 p.fillRect(0, 0, image.width(), image.height(), QBrush(QColor::fromRgbF(0, 1, 1, 0.5)));
238 }
239
240 const int iw = image.width();
241 const int ih = image.height();
242 const int bpl = image.bytesPerLine() / 4;
243 QVarLengthArray<quint32, 1024> tmpBits(qMax(iw + 2, ih + 2));
244 const int tmpBitsSize = tmpBits.size() * 4;
245 const quint32 *src = reinterpret_cast<const quint32 *>(image.constBits());
246 quint32 *dst = tmpBits.data();
247 QVarLengthArray<QRhiTextureUploadEntry, 5> entries;
248
249 // top row, padding corners
250 dst[0] = src[0];
251 memcpy(dst + 1, src, iw * sizeof(quint32));
252 dst[1 + iw] = src[iw - 1];
253 {
254 QRhiTextureSubresourceUploadDescription subresDesc(dst, tmpBitsSize);
255 subresDesc.setDestinationTopLeft(QPoint(r.x(), r.y()));
256 subresDesc.setSourceSize(QSize(iw + 2, 1));
257 entries.append(QRhiTextureUploadEntry(0, 0, subresDesc));
258 }
259
260 // bottom row, padded corners
261 const quint32 *lastRow = src + bpl * (ih - 1);
262 dst[0] = lastRow[0];
263 memcpy(dst + 1, lastRow, iw * sizeof(quint32));
264 dst[1 + iw] = lastRow[iw - 1];
265 {
266 QRhiTextureSubresourceUploadDescription subresDesc(dst, tmpBitsSize);
267 subresDesc.setDestinationTopLeft(QPoint(r.x(), r.y() + ih + 1));
268 subresDesc.setSourceSize(QSize(iw + 2, 1));
269 entries.append(QRhiTextureUploadEntry(0, 0, subresDesc));
270 }
271
272 // left column
273 for (int i = 0; i < ih; ++i)
274 dst[i] = src[i * bpl];
275 {
276 QRhiTextureSubresourceUploadDescription subresDesc(dst, tmpBitsSize);
277 subresDesc.setDestinationTopLeft(QPoint(r.x(), r.y() + 1));
278 subresDesc.setSourceSize(QSize(1, ih));
279 entries.append(QRhiTextureUploadEntry(0, 0, subresDesc));
280 }
281
282
283 // right column
284 for (int i = 0; i < ih; ++i)
285 dst[i] = src[i * bpl + iw - 1];
286 {
287 QRhiTextureSubresourceUploadDescription subresDesc(dst, tmpBitsSize);
288 subresDesc.setDestinationTopLeft(QPoint(r.x() + iw + 1, r.y() + 1));
289 subresDesc.setSourceSize(QSize(1, ih));
290 entries.append(QRhiTextureUploadEntry(0, 0, subresDesc));
291 }
292
293 // Inner part of the image....
294 if (bpl != iw) {
295 int sy = r.y() + 1;
296 int ey = sy + r.height() - 2;
297 entries.reserve(4 + (ey - sy));
298 for (int y = sy; y < ey; ++y) {
299 QRhiTextureSubresourceUploadDescription subresDesc(src, image.bytesPerLine());
300 subresDesc.setDestinationTopLeft(QPoint(r.x() + 1, y));
301 subresDesc.setSourceSize(QSize(r.width() - 2, 1));
302 entries.append(QRhiTextureUploadEntry(0, 0, subresDesc));
303 src += bpl;
304 }
305 } else {
306 QRhiTextureSubresourceUploadDescription subresDesc(src, image.sizeInBytes());
307 subresDesc.setDestinationTopLeft(QPoint(r.x() + 1, r.y() + 1));
308 subresDesc.setSourceSize(QSize(r.width() - 2, r.height() - 2));
309 entries.append(QRhiTextureUploadEntry(0, 0, subresDesc));
310 }
311
313 desc.setEntries(entries.cbegin(), entries.cend());
314 resourceUpdates->uploadTexture(m_texture, desc);
315
316 const QSize textureSize = t->textureSize();
317 if (textureSize.width() > m_atlas_transient_image_threshold || textureSize.height() > m_atlas_transient_image_threshold)
319
320 qCDebug(QSG_LOG_TIME_TEXTURE, "atlastexture upload enqueued in: %lldms (%dx%d)",
321 qsg_renderer_timer.elapsed(),
322 t->textureSize().width(),
323 t->textureSize().height());
324}
325
326TextureBase::TextureBase(AtlasBase *atlas, const QRect &textureRect)
327 : QSGTexture(*(new QSGTexturePrivate(this)))
328 , m_allocated_rect(textureRect)
329 , m_atlas(atlas)
330{
331}
332
337
339{
340 // We need special care here: a typical comparisonKey() implementation
341 // returns a unique result when there is no underlying texture yet. This is
342 // not quite ideal for atlasing however since textures with the same atlas
343 // should be considered equal regardless of the state of the underlying
344 // graphics resources.
345
346 // base the comparison on the atlas ptr; this way textures for the same
347 // atlas are considered equal
348 return qint64(m_atlas);
349}
350
351QRhiTexture *TextureBase::rhiTexture() const
352{
353 return m_atlas->m_texture;
354}
355
356void TextureBase::commitTextureOperations(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates)
357{
358#ifdef QT_NO_DEBUG
359 Q_UNUSED(rhi);
360#endif
361 Q_ASSERT(rhi == m_atlas->m_rhi);
363}
364
365Texture::Texture(Atlas *atlas, const QRect &textureRect, const QImage &image)
366 : TextureBase(atlas, textureRect)
367 , m_image(image)
368 , m_has_alpha(image.hasAlphaChannel())
369{
370 float w = atlas->size().width();
371 float h = atlas->size().height();
372 QRect nopad = atlasSubRectWithoutPadding();
373 m_texture_coords_rect = QRectF(nopad.x() / w,
374 nopad.y() / h,
375 nopad.width() / w,
376 nopad.height() / h);
377}
378
380{
381 if (m_nonatlas_texture)
382 delete m_nonatlas_texture;
383}
384
385QSGTexture *Texture::removedFromAtlas(QRhiResourceUpdateBatch *resourceUpdates) const
386{
387 if (!m_nonatlas_texture) {
388 m_nonatlas_texture = new QSGPlainTexture;
389 if (!m_image.isNull()) {
390 m_nonatlas_texture->setImage(m_image);
391 m_nonatlas_texture->setFiltering(filtering());
392 } else {
393 QSGDefaultRenderContext *rc = m_atlas->renderContext();
394 QRhi *rhi = m_atlas->rhi();
395 Q_ASSERT(rhi->isRecordingFrame());
396 const QRect r = atlasSubRectWithoutPadding();
397
398 QRhiTexture *extractTex = rhi->newTexture(m_atlas->texture()->format(), r.size());
399 if (extractTex->create()) {
400 bool ownResUpd = false;
401 QRhiResourceUpdateBatch *resUpd = resourceUpdates;
402 if (!resUpd) {
403 ownResUpd = true;
404 resUpd = rhi->nextResourceUpdateBatch();
405 }
407 desc.setSourceTopLeft(r.topLeft());
408 desc.setPixelSize(r.size());
409 resUpd->copyTexture(extractTex, m_atlas->texture(), desc);
410 if (ownResUpd)
411 rc->currentFrameCommandBuffer()->resourceUpdate(resUpd);
412 }
413
414 m_nonatlas_texture->setTexture(extractTex);
415 m_nonatlas_texture->setOwnsTexture(true);
416 m_nonatlas_texture->setHasAlphaChannel(m_has_alpha);
417 m_nonatlas_texture->setTextureSize(r.size());
418 }
419 }
420
421 m_nonatlas_texture->setMipmapFiltering(mipmapFiltering());
422 m_nonatlas_texture->setFiltering(filtering());
423 return m_nonatlas_texture;
424}
425
426}
427
428QT_END_NAMESPACE
429
430#include "moc_qsgrhiatlastexture_p.cpp"
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:799
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:694
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:754
void commitTextureOperations(QRhiResourceUpdateBatch *resourceUpdates)
QSGDefaultRenderContext * m_rc
virtual bool generateTexture()=0
Texture * create(const QImage &image)
Atlas(QSGDefaultRenderContext *rc, const QSize &size)
void enqueueTextureUpload(TextureBase *t, QRhiResourceUpdateBatch *resourceUpdates) override
QSGTexture * create(const QSGCompressedTextureFactory *factory)
QSGTexture * create(const QImage &image, bool hasAlphaChannel)
qint64 comparisonKey() const override
Returns a key suitable for comparing textures.
QRhiTexture * rhiTexture() const override
void commitTextureOperations(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates) override
Call this function to enqueue image upload operations to resourceUpdates, in case there are any pendi...
bool hasAlphaChannel() const override
Returns true if the texture data contains an alpha channel.
QSGTexture * removedFromAtlas(QRhiResourceUpdateBatch *resourceUpdates) const override
This function returns a copy of the current texture which is removed from its atlas.
Combined button and popup list for selecting options.
QT_BEGIN_NAMESPACE int qt_sg_envInt(const char *name, int defaultValue)
static QElapsedTimer qsg_renderer_timer
DEFINE_BOOL_CONFIG_OPTION(forceDiskCache, QML_FORCE_DISK_CACHE)