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.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())
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(format);
101 if (!m_rhi->isTextureFormatSupported(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(format);
107 if (i == m_atlases.cend()) {
108 auto newAtlas = new QSGCompressedAtlasTexture::Atlas(m_rc, m_atlas_size, format);
109 i = m_atlases.insert(format, newAtlas);
110 }
111 const QTextureFileData *cmpData = factory->textureData();
112 t = i.value()->create(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
127{
128 Q_ASSERT(!m_texture);
129}
130
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("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
154{
155 QRect atlasRect = t->atlasSubRect();
156 m_allocator.deallocate(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(QRhiTexture::BGRA8))
174 m_format = QRhiTexture::BGRA8;
175#endif
176
177 m_debug_overlay = qt_sg_envInt("QSG_ATLAS_OVERLAY", 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("QSG_ATLAS_TRANSIENT_IMAGE_THRESHOLD", 0);
183}
184
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(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
202{
203 m_texture = m_rhi->newTexture(m_format, m_size, 1, 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(QImage::Format_ARGB32_Premultiplied);
228 } else if (image.format() != QImage::Format_RGBA8888_Premultiplied) {
229 image = std::move(image).convertToFormat(QImage::Format_RGBA8888_Premultiplied);
230 }
231
232 if (m_debug_overlay) {
233 QPainter p(&image);
234 p.setCompositionMode(QPainter::CompositionMode_SourceAtop);
235 p.fillRect(0, 0, image.width(), image.height(), QBrush(QColor::fromRgbF(0, 1, 1, 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(iw + 2, 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(dst + 1, src, 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(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(dst + 1, lastRow, 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(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(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(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(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(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(QRhiTextureUploadEntry(0, 0, subresDesc));
308 }
309
311 desc.setEntries(entries.cbegin(), entries.cend());
312 resourceUpdates->uploadTexture(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)
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
335
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);
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
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(m_atlas->texture()->format(), 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 }
405 desc.setSourceTopLeft(r.topLeft());
406 desc.setPixelSize(r.size());
407 resUpd->copyTexture(extractTex, m_atlas->texture(), desc);
408 if (ownResUpd)
409 rc->currentFrameCommandBuffer()->resourceUpdate(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"
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:744
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:661
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:721
void commitTextureOperations(QRhiResourceUpdateBatch *resourceUpdates)
QSGDefaultRenderContext * m_rc
virtual bool generateTexture()=0
QSGDefaultRenderContext * renderContext() const
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)