Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
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(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
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
94{
95 QSGTexture *t = nullptr;
96 if (!qsgEnableCompressedAtlas() || !factory->textureData()->isValid())
97 return t;
98
99 unsigned int format = factory->textureData()->glInternalFormat();
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) {
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
119 : m_rc(rc)
120 , m_rhi(rc->rhi())
121 , m_allocator(size)
122 , m_size(size)
123{
124}
125
130
132{
133 delete m_texture;
134 m_texture = nullptr;
135}
136
138{
139 if (!m_allocated) {
140 m_allocated = true;
141 if (!generateTexture()) {
142 qWarning("QSGTextureAtlas: Failed to create texture");
143 return;
144 }
145 }
146
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
161 : AtlasBase(rc, size)
162{
163 // use RGBA texture internally as that is the only one guaranteed to be always supported
164 m_format = QRhiTexture::RGBA8;
165
166 m_debug_overlay = qt_sg_envInt("QSG_ATLAS_OVERLAY", 0);
167
168 // images smaller than this will retain their QImage.
169 // by default no images are retained (favoring memory)
170 // set to a very large value to retain all images (allowing quick removal from the atlas)
171 m_atlas_transient_image_threshold = qt_sg_envInt("QSG_ATLAS_TRANSIENT_IMAGE_THRESHOLD", 0);
172}
173
175{
176}
177
179{
180 // No need to lock, as manager already locked it.
181 QRect rect = m_allocator.allocate(QSize(image.width() + 2, image.height() + 2));
182 if (rect.width() > 0 && rect.height() > 0) {
183 Texture *t = new Texture(this, rect, image);
185 return t;
186 }
187 return nullptr;
188}
189
191{
193 if (!m_texture)
194 return false;
195
196 if (!m_texture->create()) {
197 delete m_texture;
198 m_texture = nullptr;
199 return false;
200 }
201
202 return true;
203}
204
206{
207 Texture *tex = static_cast<Texture *>(t);
208 const QRect &r = tex->atlasSubRect();
209 QImage image = tex->image();
210
211 if (image.isNull())
212 return;
213
215 image = std::move(image).convertToFormat(QImage::Format_RGBA8888_Premultiplied);
216
217 if (m_debug_overlay) {
218 QPainter p(&image);
219 p.setCompositionMode(QPainter::CompositionMode_SourceAtop);
220 p.fillRect(0, 0, image.width(), image.height(), QBrush(QColor::fromRgbF(0, 1, 1, 0.5)));
221 }
222
223 const int iw = image.width();
224 const int ih = image.height();
225 const int bpl = image.bytesPerLine() / 4;
226 QVarLengthArray<quint32, 1024> tmpBits(qMax(iw + 2, ih + 2));
227 const int tmpBitsSize = tmpBits.size() * 4;
228 const quint32 *src = reinterpret_cast<const quint32 *>(image.constBits());
229 quint32 *dst = tmpBits.data();
230 QVarLengthArray<QRhiTextureUploadEntry, 5> entries;
231
232 // top row, padding corners
233 dst[0] = src[0];
234 memcpy(dst + 1, src, iw * sizeof(quint32));
235 dst[1 + iw] = src[iw - 1];
236 {
237 QRhiTextureSubresourceUploadDescription subresDesc(dst, tmpBitsSize);
238 subresDesc.setDestinationTopLeft(QPoint(r.x(), r.y()));
239 subresDesc.setSourceSize(QSize(iw + 2, 1));
240 entries.append(QRhiTextureUploadEntry(0, 0, subresDesc));
241 }
242
243 // bottom row, padded corners
244 const quint32 *lastRow = src + bpl * (ih - 1);
245 dst[0] = lastRow[0];
246 memcpy(dst + 1, lastRow, iw * sizeof(quint32));
247 dst[1 + iw] = lastRow[iw - 1];
248 {
249 QRhiTextureSubresourceUploadDescription subresDesc(dst, tmpBitsSize);
250 subresDesc.setDestinationTopLeft(QPoint(r.x(), r.y() + ih + 1));
251 subresDesc.setSourceSize(QSize(iw + 2, 1));
252 entries.append(QRhiTextureUploadEntry(0, 0, subresDesc));
253 }
254
255 // left column
256 for (int i = 0; i < ih; ++i)
257 dst[i] = src[i * bpl];
258 {
259 QRhiTextureSubresourceUploadDescription subresDesc(dst, tmpBitsSize);
260 subresDesc.setDestinationTopLeft(QPoint(r.x(), r.y() + 1));
261 subresDesc.setSourceSize(QSize(1, ih));
262 entries.append(QRhiTextureUploadEntry(0, 0, subresDesc));
263 }
264
265
266 // right column
267 for (int i = 0; i < ih; ++i)
268 dst[i] = src[i * bpl + iw - 1];
269 {
270 QRhiTextureSubresourceUploadDescription subresDesc(dst, tmpBitsSize);
271 subresDesc.setDestinationTopLeft(QPoint(r.x() + iw + 1, r.y() + 1));
272 subresDesc.setSourceSize(QSize(1, ih));
273 entries.append(QRhiTextureUploadEntry(0, 0, subresDesc));
274 }
275
276 // Inner part of the image....
277 if (bpl != iw) {
278 int sy = r.y() + 1;
279 int ey = sy + r.height() - 2;
280 entries.reserve(4 + (ey - sy));
281 for (int y = sy; y < ey; ++y) {
282 QRhiTextureSubresourceUploadDescription subresDesc(src, image.bytesPerLine());
283 subresDesc.setDestinationTopLeft(QPoint(r.x() + 1, y));
284 subresDesc.setSourceSize(QSize(r.width() - 2, 1));
285 entries.append(QRhiTextureUploadEntry(0, 0, subresDesc));
286 src += bpl;
287 }
288 } else {
289 QRhiTextureSubresourceUploadDescription subresDesc(src, image.sizeInBytes());
290 subresDesc.setDestinationTopLeft(QPoint(r.x() + 1, r.y() + 1));
291 subresDesc.setSourceSize(QSize(r.width() - 2, r.height() - 2));
292 entries.append(QRhiTextureUploadEntry(0, 0, subresDesc));
293 }
294
296 desc.setEntries(entries.cbegin(), entries.cend());
297 resourceUpdates->uploadTexture(m_texture, desc);
298
299 const QSize textureSize = t->textureSize();
300 if (textureSize.width() > m_atlas_transient_image_threshold || textureSize.height() > m_atlas_transient_image_threshold)
301 tex->releaseImage();
302
303 qCDebug(QSG_LOG_TIME_TEXTURE, "atlastexture upload enqueued in: %lldms (%dx%d)",
304 qsg_renderer_timer.elapsed(),
305 t->textureSize().width(),
306 t->textureSize().height());
307}
308
309TextureBase::TextureBase(AtlasBase *atlas, const QRect &textureRect)
311 , m_allocated_rect(textureRect)
312 , m_atlas(atlas)
313{
314}
315
320
322{
323 // We need special care here: a typical comparisonKey() implementation
324 // returns a unique result when there is no underlying texture yet. This is
325 // not quite ideal for atlasing however since textures with the same atlas
326 // should be considered equal regardless of the state of the underlying
327 // graphics resources.
328
329 // base the comparison on the atlas ptr; this way textures for the same
330 // atlas are considered equal
331 return qint64(m_atlas);
332}
333
335{
336 return m_atlas->m_texture;
337}
338
340{
341#ifdef QT_NO_DEBUG
342 Q_UNUSED(rhi);
343#endif
344 Q_ASSERT(rhi == m_atlas->m_rhi);
345 m_atlas->commitTextureOperations(resourceUpdates);
346}
347
348Texture::Texture(Atlas *atlas, const QRect &textureRect, const QImage &image)
349 : TextureBase(atlas, textureRect)
350 , m_image(image)
351 , m_has_alpha(image.hasAlphaChannel())
352{
353 float w = atlas->size().width();
354 float h = atlas->size().height();
356 m_texture_coords_rect = QRectF(nopad.x() / w,
357 nopad.y() / h,
358 nopad.width() / w,
359 nopad.height() / h);
360}
361
363{
364 if (m_nonatlas_texture)
365 delete m_nonatlas_texture;
366}
367
369{
370 if (!m_nonatlas_texture) {
371 m_nonatlas_texture = new QSGPlainTexture;
372 if (!m_image.isNull()) {
373 m_nonatlas_texture->setImage(m_image);
374 m_nonatlas_texture->setFiltering(filtering());
375 } else {
377 QRhi *rhi = m_atlas->rhi();
380
381 QRhiTexture *extractTex = rhi->newTexture(m_atlas->texture()->format(), r.size());
382 if (extractTex->create()) {
383 bool ownResUpd = false;
384 QRhiResourceUpdateBatch *resUpd = resourceUpdates;
385 if (!resUpd) {
386 ownResUpd = true;
387 resUpd = rhi->nextResourceUpdateBatch();
388 }
390 desc.setSourceTopLeft(r.topLeft());
391 desc.setPixelSize(r.size());
392 resUpd->copyTexture(extractTex, m_atlas->texture(), desc);
393 if (ownResUpd)
395 }
396
397 m_nonatlas_texture->setTexture(extractTex);
398 m_nonatlas_texture->setOwnsTexture(true);
399 m_nonatlas_texture->setHasAlphaChannel(m_has_alpha);
400 m_nonatlas_texture->setTextureSize(r.size());
401 }
402 }
403
404 m_nonatlas_texture->setMipmapFiltering(mipmapFiltering());
405 m_nonatlas_texture->setFiltering(filtering());
406 return m_nonatlas_texture;
407}
408
409}
410
412
413#include "moc_qsgrhiatlastexture_p.cpp"
\inmodule QtGui
Definition qbrush.h:30
static QColor fromRgbF(float r, float g, float b, float a=1.0)
Static convenience function that returns a QColor constructed from the RGB color values,...
Definition qcolor.cpp:2427
\inmodule QtCore
\inmodule QtCore
Definition qhash.h:1103
iterator begin()
Returns an \l{STL-style iterators}{STL-style iterator} pointing to the first item in the hash.
Definition qhash.h:1212
iterator find(const Key &key)
Returns an iterator pointing to the item with the key in the hash.
Definition qhash.h:1291
iterator end() noexcept
Returns an \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the last ...
Definition qhash.h:1216
const_iterator cend() const noexcept
Definition qhash.h:1218
void clear() noexcept(std::is_nothrow_destructible< Node >::value)
Removes all items from the hash and frees up all memory used by it.
Definition qhash.h:951
bool isEmpty() const noexcept
Returns true if the hash contains no items; otherwise returns false.
Definition qhash.h:928
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
Definition qhash.h:1303
\inmodule QtGui
Definition qimage.h:37
bool isNull() const
Returns true if it is a null image, otherwise returns false.
Definition qimage.cpp:1222
@ Format_RGBA8888_Premultiplied
Definition qimage.h:60
void deleteLater()
\threadsafe
Definition qobject.cpp:2435
The QPainter class performs low-level painting on widgets and other paint devices.
Definition qpainter.h:46
@ CompositionMode_SourceAtop
Definition qpainter.h:107
\inmodule QtCore\reentrant
Definition qpoint.h:25
\inmodule QtCore\reentrant
Definition qrect.h:484
\inmodule QtCore\reentrant
Definition qrect.h:30
void resourceUpdate(QRhiResourceUpdateBatch *resourceUpdates)
Sometimes committing resource updates is necessary or just more convenient without starting a render ...
Definition qrhi.cpp:9384
\inmodule QtGui
Definition qrhi.h:1731
\inmodule QtGui
Definition qrhi.h:739
void setDestinationTopLeft(const QPoint &p)
Sets the destination top-left position p.
Definition qrhi.h:673
void setSourceSize(const QSize &size)
Sets the source size in pixels.
Definition qrhi.h:676
\inmodule QtGui
Definition qrhi.h:716
\inmodule QtGui
Definition qrhi.h:693
\inmodule QtGui
Definition qrhi.h:895
Format format() const
Definition qrhi.h:972
@ UsedAsTransferSource
Definition qrhi.h:902
virtual bool create()=0
Creates the corresponding native graphics resources.
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:1804
bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags={}) const
Definition qrhi.cpp:10102
int resourceLimit(ResourceLimit limit) const
Definition qrhi.cpp:10121
bool isRecordingFrame() const
Definition qrhi.cpp:10802
@ TextureSizeMax
Definition qrhi.h:1888
QRhiTexture * newTexture(QRhiTexture::Format format, const QSize &pixelSize, int sampleCount=1, QRhiTexture::Flags flags={})
Definition qrhi.cpp:10562
QRhiResourceUpdateBatch * nextResourceUpdateBatch()
Definition qrhi.cpp:9252
QRect allocate(const QSize &size)
bool deallocate(const QRect &rect)
static FormatInfo formatInfo(quint32 glTextureFormat)
QRhiCommandBuffer * currentFrameCommandBuffer() const
void setTextureSize(const QSize &size)
void setImage(const QImage &image)
void setTexture(QRhiTexture *texture)
void setOwnsTexture(bool owns)
void setHasAlphaChannel(bool alpha)
AtlasBase(QSGDefaultRenderContext *rc, const QSize &size)
void commitTextureOperations(QRhiResourceUpdateBatch *resourceUpdates)
virtual void enqueueTextureUpload(TextureBase *t, QRhiResourceUpdateBatch *resourceUpdates)=0
virtual bool generateTexture()=0
QVector< TextureBase * > m_pending_uploads
QSGDefaultRenderContext * renderContext() const
Texture * create(const QImage &image)
Atlas(QSGDefaultRenderContext *rc, const QSize &size)
void enqueueTextureUpload(TextureBase *t, QRhiResourceUpdateBatch *resourceUpdates) override
Manager(QSGDefaultRenderContext *rc, const QSize &surfacePixelSize, QSurface *maybeSurface)
QSGTexture * create(const QImage &image, bool hasAlphaChannel)
TextureBase(AtlasBase *atlas, const QRect &textureRect)
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...
Texture(Atlas *atlas, const QRect &textureRect, const QImage &image)
QSGTexture * removedFromAtlas(QRhiResourceUpdateBatch *resourceUpdates) const override
This function returns a copy of the current texture which is removed from its atlas.
\inmodule QtQuick
Definition qsgtexture.h:20
QSGTexture::Filtering mipmapFiltering() const
Returns whether mipmapping should be used when sampling from this texture.
void setFiltering(Filtering filter)
Sets the sampling mode to filter.
QSGTexture::Filtering filtering() const
Returns the sampling mode to be used for this texture.
void setMipmapFiltering(Filtering filter)
Sets the mipmap sampling mode to filter.
\inmodule QtCore
Definition qsize.h:25
constexpr int height() const noexcept
Returns the height.
Definition qsize.h:133
constexpr int width() const noexcept
Returns the width.
Definition qsize.h:130
\inmodule QtGui
Definition qsurface.h:21
SurfaceClass surfaceClass() const
Returns the surface class of this surface.
Definition qsurface.cpp:121
\inmodule QtGui
Definition qwindow.h:63
#define this
Definition dialogs.cpp:9
rect
[4]
Combined button and popup list for selecting options.
@ CoverWindow
Definition qnamespace.h:218
Definition image.cpp:4
#define qWarning
Definition qlogging.h:166
#define qCDebug(category,...)
constexpr quint32 qNextPowerOfTwo(quint32 v)
Definition qmath.h:335
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLfloat GLfloat GLfloat w
[0]
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLboolean r
[2]
GLenum src
GLenum GLenum dst
GLuint name
GLint GLsizei GLsizei GLenum format
GLint y
GLfloat GLfloat GLfloat GLfloat h
GLdouble GLdouble t
Definition qopenglext.h:243
GLfloat GLfloat p
[1]
#define DEFINE_BOOL_CONFIG_OPTION(name, var)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
int qt_sg_envInt(const char *name, int defaultValue)
QT_BEGIN_NAMESPACE int qt_sg_envInt(const char *name, int defaultValue)
static QElapsedTimer qsg_renderer_timer
#define Q_UNUSED(x)
unsigned int quint32
Definition qtypes.h:50
long long qint64
Definition qtypes.h:60
QVideoFrameFormat::PixelFormat fmt
QItemEditorFactory * factory
aWidget window() -> setWindowTitle("New Window Title")
[2]