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 "qsgplaintexture_p.h"
41#include <QtQuick/private/qsgcontext_p.h>
42#include <qmath.h>
43#include <private/qquickprofiler_p.h>
44#include <private/qqmlglobal_p.h>
45#include <QtGui/qguiapplication.h>
46#include <QtGui/qpa/qplatformnativeinterface.h>
47#if QT_CONFIG(opengl)
48# include <QtGui/qopenglcontext.h>
49# include <QtGui/qopenglfunctions.h>
50# include <QtGui/private/qopengltextureuploader_p.h>
51# include <private/qsgdefaultrendercontext_p.h>
52#endif
53#include <QtGui/private/qrhi_p.h>
54
55#include <qtquick_tracepoints_p.h>
56
57#if QT_CONFIG(opengl)
58static QElapsedTimer qsg_renderer_timer;
59#endif
60
61#ifndef GL_BGRA
62#define GL_BGRA 0x80E1
63#endif
64
65#ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT
66#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE
67#endif
68
69QT_BEGIN_NAMESPACE
70
71QSGPlainTexture::QSGPlainTexture()
72 : QSGTexture(*(new QSGPlainTexturePrivate))
73 , m_texture_id(0)
74 , m_texture(nullptr)
75 , m_has_alpha(false)
76 , m_dirty_texture(false)
77 , m_dirty_bind_options(false)
78 , m_owns_texture(true)
79 , m_mipmaps_generated(false)
80 , m_retain_image(false)
81 , m_mipmap_warned(false)
82{
83}
84
85QSGPlainTexture::QSGPlainTexture(QSGPlainTexturePrivate &dd)
86 : QSGTexture(dd)
87 , m_texture_id(0)
88 , m_texture(nullptr)
89 , m_has_alpha(false)
90 , m_dirty_texture(false)
91 , m_dirty_bind_options(false)
92 , m_owns_texture(true)
93 , m_mipmaps_generated(false)
94 , m_retain_image(false)
95 , m_mipmap_warned(false)
96{
97}
98
99QSGPlainTexture::~QSGPlainTexture()
100{
101#if QT_CONFIG(opengl)
102 if (m_texture_id && m_owns_texture && QOpenGLContext::currentContext())
103 QOpenGLContext::currentContext()->functions()->glDeleteTextures(n: 1, textures: &m_texture_id);
104#endif
105 if (m_texture && m_owns_texture)
106 delete m_texture;
107}
108
109void QSGPlainTexture::setImage(const QImage &image)
110{
111 m_image = image;
112 m_texture_size = image.size();
113 m_has_alpha = image.hasAlphaChannel();
114 m_dirty_texture = true;
115 m_dirty_bind_options = true;
116 m_mipmaps_generated = false;
117 }
118
119int QSGPlainTexture::textureId() const // legacy (GL-only)
120{
121 if (m_dirty_texture) {
122 if (m_image.isNull()) {
123 // The actual texture and id will be updated/deleted in a later bind()
124 // or ~QSGPlainTexture so just keep it minimal here.
125 return 0;
126 } else if (m_texture_id == 0){
127#if QT_CONFIG(opengl)
128 // Generate a texture id for use later and return it.
129 QOpenGLContext::currentContext()->functions()->glGenTextures(n: 1, textures: &const_cast<QSGPlainTexture *>(this)->m_texture_id);
130#endif
131 return m_texture_id;
132 }
133 }
134 return m_texture_id;
135}
136
137void QSGPlainTexture::setTextureId(int id) // legacy (GL-only)
138{
139#if QT_CONFIG(opengl)
140 if (m_texture_id && m_owns_texture)
141 QOpenGLContext::currentContext()->functions()->glDeleteTextures(n: 1, textures: &m_texture_id);
142#endif
143
144 m_texture_id = id;
145 m_dirty_texture = false;
146 m_dirty_bind_options = true;
147 m_image = QImage();
148 m_mipmaps_generated = false;
149}
150
151void QSGPlainTexture::bind() // legacy (GL-only)
152{
153#if QT_CONFIG(opengl)
154 Q_TRACE_SCOPE(QSG_texture_prepare);
155 QOpenGLContext *context = QOpenGLContext::currentContext();
156 QOpenGLFunctions *funcs = context->functions();
157 if (!m_dirty_texture) {
158 Q_TRACE_SCOPE(QSG_texture_bind);
159 funcs->glBindTexture(GL_TEXTURE_2D, texture: m_texture_id);
160 if (mipmapFiltering() != QSGTexture::None && !m_mipmaps_generated) {
161 funcs->glGenerateMipmap(GL_TEXTURE_2D);
162 m_mipmaps_generated = true;
163 }
164 updateBindOptions(force: m_dirty_bind_options);
165 m_dirty_bind_options = false;
166 return;
167 }
168
169 m_dirty_texture = false;
170
171 bool profileFrames = QSG_LOG_TIME_TEXTURE().isDebugEnabled();
172 if (profileFrames)
173 qsg_renderer_timer.start();
174 Q_QUICK_SG_PROFILE_START_SYNCHRONIZED(QQuickProfiler::SceneGraphTexturePrepare,
175 QQuickProfiler::SceneGraphTextureDeletion);
176
177
178 if (m_image.isNull()) {
179 if (m_texture_id && m_owns_texture) {
180 Q_TRACE_SCOPE(QSG_texture_delete);
181 funcs->glDeleteTextures(n: 1, textures: &m_texture_id);
182 qCDebug(QSG_LOG_TIME_TEXTURE, "plain texture deleted in %dms - %dx%d",
183 (int) qsg_renderer_timer.elapsed(),
184 m_texture_size.width(),
185 m_texture_size.height());
186 Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphTextureDeletion,
187 QQuickProfiler::SceneGraphTextureDeletionDelete);
188 }
189 m_texture_id = 0;
190 m_texture_size = QSize();
191 m_has_alpha = false;
192
193 return;
194 }
195
196 Q_TRACE(QSG_texture_bind_entry);
197
198 if (m_texture_id == 0)
199 funcs->glGenTextures(n: 1, textures: &m_texture_id);
200 funcs->glBindTexture(GL_TEXTURE_2D, texture: m_texture_id);
201
202 qint64 bindTime = 0;
203 if (profileFrames)
204 bindTime = qsg_renderer_timer.nsecsElapsed();
205 Q_TRACE(QSG_texture_bind_exit);
206 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphTexturePrepare,
207 QQuickProfiler::SceneGraphTexturePrepareBind);
208 Q_TRACE(QSG_texture_upload_entry);
209
210 // ### TODO: check for out-of-memory situations...
211
212 QOpenGLTextureUploader::BindOptions options = QOpenGLTextureUploader::PremultipliedAlphaBindOption;
213
214 // Downscale the texture to fit inside the max texture limit if it is too big.
215 // It would be better if the image was already downscaled to the right size,
216 // but this information is not always available at that time, so as a last
217 // resort we can do it here. Texture coordinates are normalized, so it
218 // won't cause any problems and actual texture sizes will be written
219 // based on QSGTexture::textureSize which is updated after this, so that
220 // should be ok.
221 int max;
222 if (auto rc = QSGDefaultRenderContext::from(context))
223 max = rc->maxTextureSize();
224 else
225 funcs->glGetIntegerv(GL_MAX_TEXTURE_SIZE, params: &max);
226
227 m_texture_size = m_texture_size.boundedTo(otherSize: QSize(max, max));
228
229 // Scale to a power of two size if mipmapping is requested and the
230 // texture is npot and npot textures are not properly supported.
231 if (mipmapFiltering() != QSGTexture::None
232 && !funcs->hasOpenGLFeature(feature: QOpenGLFunctions::NPOTTextures)) {
233 options |= QOpenGLTextureUploader::PowerOfTwoBindOption;
234 }
235
236 updateBindOptions(force: m_dirty_bind_options);
237
238 QOpenGLTextureUploader::textureImage(GL_TEXTURE_2D, image: m_image, options, maxSize: QSize(max, max));
239
240 qint64 uploadTime = 0;
241 if (profileFrames)
242 uploadTime = qsg_renderer_timer.nsecsElapsed();
243 Q_TRACE(QSG_texture_upload_exit);
244 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphTexturePrepare,
245 QQuickProfiler::SceneGraphTexturePrepareUpload);
246 Q_TRACE(QSG_texture_mipmap_entry);
247
248 if (mipmapFiltering() != QSGTexture::None) {
249 funcs->glGenerateMipmap(GL_TEXTURE_2D);
250 m_mipmaps_generated = true;
251 }
252
253 qint64 mipmapTime = 0;
254 if (profileFrames) {
255 mipmapTime = qsg_renderer_timer.nsecsElapsed();
256 qCDebug(QSG_LOG_TIME_TEXTURE,
257 "plain texture uploaded in: %dms (%dx%d), bind=%d, upload=%d, mipmap=%d%s",
258 int(mipmapTime / 1000000),
259 m_texture_size.width(), m_texture_size.height(),
260 int(bindTime / 1000000),
261 int((uploadTime - bindTime)/1000000),
262 int((mipmapTime - uploadTime)/1000000),
263 m_texture_size != m_image.size() ? " (scaled to GL_MAX_TEXTURE_SIZE)" : "");
264 }
265 Q_TRACE(QSG_texture_mipmap_exit);
266 Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphTexturePrepare,
267 QQuickProfiler::SceneGraphTexturePrepareMipmap);
268
269 m_texture_rect = QRectF(0, 0, 1, 1);
270
271 m_dirty_bind_options = false;
272 if (!m_retain_image)
273 m_image = QImage();
274#endif
275}
276
277void QSGPlainTexture::setTexture(QRhiTexture *texture) // RHI only
278{
279 if (m_texture && m_owns_texture && m_texture != texture)
280 delete m_texture;
281
282 m_texture = texture;
283 m_dirty_texture = false;
284 m_dirty_bind_options = true;
285 m_image = QImage();
286 m_mipmaps_generated = false;
287}
288
289void QSGPlainTexture::setTextureFromNativeObject(QRhi *rhi, QQuickWindow::NativeObjectType type,
290 const void *nativeObjectPtr, int nativeLayout,
291 const QSize &size, bool mipmap)
292{
293 Q_UNUSED(type);
294
295 QRhiTexture::Flags flags;
296 if (mipmap)
297 flags |= QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips;
298
299 QRhiTexture *t = rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: size, sampleCount: 1, flags);
300
301 // ownership of the native object is never taken
302 t->buildFrom(src: {.object: nativeObjectPtr, .layout: nativeLayout});
303
304 setTexture(t);
305}
306
307int QSGPlainTexturePrivate::comparisonKey() const
308{
309 Q_Q(const QSGPlainTexture);
310
311 // not textureId() as that would create an id when not yet done - that's not wanted here
312 if (q->m_texture_id)
313 return q->m_texture_id;
314
315 if (q->m_texture)
316 return int(qintptr(q->m_texture));
317
318 // two textures (and so materials) with not-yet-created texture underneath are never equal
319 return int(qintptr(q));
320}
321
322QRhiTexture *QSGPlainTexturePrivate::rhiTexture() const
323{
324 Q_Q(const QSGPlainTexture);
325 return q->m_texture;
326}
327
328void QSGPlainTexturePrivate::updateRhiTexture(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates)
329{
330 Q_Q(QSGPlainTexture);
331
332 const bool hasMipMaps = q->mipmapFiltering() != QSGTexture::None;
333 const bool mipmappingChanged = q->m_texture && ((hasMipMaps && !q->m_texture->flags().testFlag(flag: QRhiTexture::MipMapped)) // did not have it before
334 || (!hasMipMaps && q->m_texture->flags().testFlag(flag: QRhiTexture::MipMapped))); // does not have it anymore
335
336 if (!q->m_dirty_texture) {
337 if (!q->m_texture)
338 return;
339 if (q->m_texture && !mipmappingChanged) {
340 if (hasMipMaps && !q->m_mipmaps_generated) {
341 resourceUpdates->generateMips(tex: q->m_texture);
342 q->m_mipmaps_generated = true;
343 }
344 return;
345 }
346 }
347
348 if (q->m_image.isNull()) {
349 if (!q->m_dirty_texture && mipmappingChanged) {
350 // Full Mipmap Panic!
351 if (!q->m_mipmap_warned) {
352 qWarning(msg: "QSGPlainTexture: Mipmap settings changed without having image data available. "
353 "Call setImage() again or enable m_retain_image. "
354 "Falling back to previous mipmap filtering mode.");
355 q->m_mipmap_warned = true;
356 }
357 // leave the texture valid and rather ignore the mipmap mode change attempt
358 q->setMipmapFiltering(m_last_mipmap_filter);
359 return;
360 }
361
362 if (q->m_texture && q->m_owns_texture)
363 delete q->m_texture;
364
365 q->m_texture = nullptr;
366 q->m_texture_size = QSize();
367 q->m_has_alpha = false;
368
369 q->m_dirty_texture = false;
370 return;
371 }
372
373 q->m_dirty_texture = false;
374
375 QImage tmp;
376 bool bgra = false;
377 bool needsConvert = false;
378 if (q->m_image.format() == QImage::Format_RGB32 || q->m_image.format() == QImage::Format_ARGB32_Premultiplied) {
379#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
380 if (rhi->isTextureFormatSupported(format: QRhiTexture::BGRA8)) {
381 tmp = q->m_image;
382 bgra = true;
383 } else {
384 needsConvert = true;
385 }
386#else
387 needsConvert = true;
388#endif
389 } else if (q->m_image.format() == QImage::Format_RGBX8888 || q->m_image.format() == QImage::Format_RGBA8888_Premultiplied) {
390 tmp = q->m_image;
391 } else {
392 needsConvert = true;
393 }
394
395 if (needsConvert)
396 tmp = q->m_image.convertToFormat(f: QImage::Format_RGBA8888_Premultiplied);
397
398 // Downscale the texture to fit inside the max texture limit if it is too big.
399 // It would be better if the image was already downscaled to the right size,
400 // but this information is not always available at that time, so as a last
401 // resort we can do it here. Texture coordinates are normalized, so it
402 // won't cause any problems and actual texture sizes will be written
403 // based on QSGTexture::textureSize which is updated after this, so that
404 // should be ok.
405 const int max = rhi->resourceLimit(limit: QRhi::TextureSizeMax);
406 if (tmp.width() > max || tmp.height() > max) {
407 tmp = tmp.scaled(w: qMin(a: max, b: tmp.width()), h: qMin(a: max, b: tmp.height()), aspectMode: Qt::IgnoreAspectRatio, mode: Qt::SmoothTransformation);
408 q->m_texture_size = tmp.size();
409 }
410
411 if ((q->mipmapFiltering() != QSGTexture::None
412 || q->horizontalWrapMode() != QSGTexture::ClampToEdge
413 || q->verticalWrapMode() != QSGTexture::ClampToEdge)
414 && !rhi->isFeatureSupported(feature: QRhi::NPOTTextureRepeat))
415 {
416 const int w = qNextPowerOfTwo(v: tmp.width() - 1);
417 const int h = qNextPowerOfTwo(v: tmp.height() - 1);
418 if (tmp.width() != w || tmp.height() != h) {
419 tmp = tmp.scaled(w, h, aspectMode: Qt::IgnoreAspectRatio, mode: Qt::SmoothTransformation);
420 q->m_texture_size = tmp.size();
421 }
422 }
423
424 bool needsRebuild = q->m_texture && q->m_texture->pixelSize() != q->m_texture_size;
425
426 if (mipmappingChanged) {
427 QRhiTexture::Flags f = q->m_texture->flags();
428 f.setFlag(flag: QRhiTexture::MipMapped, on: hasMipMaps);
429 f.setFlag(flag: QRhiTexture::UsedWithGenerateMips, on: hasMipMaps);
430 q->m_texture->setFlags(f);
431 needsRebuild = true;
432 }
433
434 if (!q->m_texture) {
435 QRhiTexture::Flags f;
436 if (hasMipMaps)
437 f |= QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips;
438
439 q->m_texture = rhi->newTexture(format: bgra ? QRhiTexture::BGRA8 : QRhiTexture::RGBA8, pixelSize: q->m_texture_size, sampleCount: 1, flags: f);
440 needsRebuild = true;
441 }
442
443 if (needsRebuild) {
444 if (!q->m_texture->build()) {
445 qWarning(msg: "Failed to build texture for QSGPlainTexture (size %dx%d)",
446 q->m_texture_size.width(), q->m_texture_size.height());
447 return;
448 }
449 }
450
451 if (tmp.width() * 4 != tmp.bytesPerLine())
452 tmp = tmp.copy();
453
454 resourceUpdates->uploadTexture(tex: q->m_texture, image: tmp);
455
456 if (hasMipMaps) {
457 resourceUpdates->generateMips(tex: q->m_texture);
458 q->m_mipmaps_generated = true;
459 }
460
461 m_last_mipmap_filter = q->mipmapFiltering();
462 q->m_texture_rect = QRectF(0, 0, 1, 1);
463
464 if (!q->m_retain_image)
465 q->m_image = QImage();
466}
467
468QT_END_NAMESPACE
469

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