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
60QT_BEGIN_NAMESPACE
61
62int qt_sg_envInt(const char *name, int defaultValue);
63
64static QElapsedTimer qsg_renderer_timer;
65
66//DEFINE_BOOL_CONFIG_OPTION(qsgEnableCompressedAtlas, QSG_ENABLE_COMPRESSED_ATLAS)
67
68namespace QSGRhiAtlasTexture
69{
70
71Manager::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
94Manager::~Manager()
95{
96 Q_ASSERT(m_atlas == nullptr);
97 Q_ASSERT(m_atlases.isEmpty());
98}
99
100void 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
119QSGTexture *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
132QSGTexture *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
168AtlasBase::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
176AtlasBase::~AtlasBase()
177{
178 Q_ASSERT(!m_texture);
179}
180
181void AtlasBase::invalidate()
182{
183 delete m_texture;
184 m_texture = nullptr;
185}
186
187void 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
227void AtlasBase::remove(TextureBase *t)
228{
229 QRect atlasRect = t->atlasSubRect();
230 m_allocator.deallocate(rect: atlasRect);
231 m_pending_uploads.removeOne(t);
232}
233
234Atlas::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
248Atlas::~Atlas()
249{
250}
251
252Texture *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
264bool 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
279void 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
383TextureBase::TextureBase(AtlasBase *atlas, const QRect &textureRect)
384 : QSGTexture(*(new TextureBasePrivate))
385 , m_allocated_rect(textureRect)
386 , m_atlas(atlas)
387{
388}
389
390TextureBase::~TextureBase()
391{
392 m_atlas->remove(t: this);
393}
394
395QRhiResourceUpdateBatch *TextureBase::workResourceUpdateBatch() const
396{
397 Q_D(const TextureBase);
398 return d->workResourceUpdateBatch;
399}
400
401int 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
416QRhiTexture *TextureBasePrivate::rhiTexture() const
417{
418 Q_Q(const TextureBase);
419 return q->m_atlas->m_texture;
420}
421
422void 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
432Texture::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
446Texture::~Texture()
447{
448 if (m_nonatlas_texture)
449 delete m_nonatlas_texture;
450}
451
452QSGTexture *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 *extractTex = 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
495QT_END_NAMESPACE
496
497#include "moc_qsgrhiatlastexture_p.cpp"
498

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