1/****************************************************************************
2**
3** Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB).
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt3D 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 <QtCore/qhash.h>
41#include "gltexture_p.h"
42
43#include <private/qdebug_p.h>
44#include <private/qopengltexture_p.h>
45#include <private/qopengltexturehelper_p.h>
46#include <QDebug>
47#include <QOpenGLFunctions>
48#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
49#include <QtOpenGL/QOpenGLVersionFunctionsFactory>
50#endif
51#include <QOpenGLTexture>
52#include <QOpenGLPixelTransferOptions>
53#include <Qt3DRender/qtexture.h>
54#include <Qt3DRender/qtexturedata.h>
55#include <Qt3DRender/qtextureimagedata.h>
56#include <Qt3DRender/private/managers_p.h>
57#include <Qt3DRender/private/qabstracttexture_p.h>
58#include <Qt3DRender/private/qtextureimagedata_p.h>
59#include <renderbuffer_p.h>
60#include <Qt3DCore/private/vector_helper_p.h>
61
62#if !QT_CONFIG(opengles2)
63#include <QOpenGLFunctions_3_1>
64#include <QOpenGLFunctions_4_5_Core>
65#endif
66
67QT_BEGIN_NAMESPACE
68
69using namespace Qt3DCore;
70
71namespace Qt3DRender {
72namespace Render {
73namespace OpenGL {
74
75namespace {
76
77// This uploadGLData where the data is a fullsize subimage
78// as QOpenGLTexture doesn't allow partial subimage uploads
79void uploadGLData(QOpenGLTexture *glTex,
80 int level, int layer, QOpenGLTexture::CubeMapFace face,
81 const QByteArray &bytes, const QTextureImageDataPtr &data)
82{
83 const auto alignment = data->alignment();
84 QOpenGLPixelTransferOptions uploadOptions;
85 uploadOptions.setAlignment(alignment);
86 if (data->isCompressed())
87 glTex->setCompressedData(level, layer, face, bytes.size(), bytes.constData());
88 else
89 glTex->setData(level, layer, face, data->pixelFormat(), data->pixelType(), bytes.constData(), &uploadOptions);
90}
91
92// For partial sub image uploads
93void uploadGLData(QOpenGLTexture *glTex,
94 int mipLevel, int layer, QOpenGLTexture::CubeMapFace cubeFace,
95 int xOffset, int yOffset, int zOffset,
96 const QByteArray &bytes, const QTextureImageDataPtr &data)
97{
98 if (data->isCompressed()) {
99 Q_UNREACHABLE();
100 } else {
101 const auto alignment = data->alignment();
102 QOpenGLPixelTransferOptions uploadOptions;
103 uploadOptions.setAlignment(alignment);
104 glTex->setData(xOffset, yOffset, zOffset,
105 data->width(), data->height(), data->depth(),
106 mipLevel, layer, cubeFace, data->layers(),
107 data->pixelFormat(), data->pixelType(),
108 bytes.constData(), &uploadOptions);
109 }
110}
111
112} // anonymous
113
114
115GLTexture::GLTexture()
116 : m_dirtyFlags(None)
117 , m_gl(nullptr)
118 , m_renderBuffer(nullptr)
119 , m_dataFunctor()
120 , m_pendingDataFunctor(nullptr)
121 , m_sharedTextureId(-1)
122 , m_externalRendering(false)
123 , m_wasTextureRecreated(false)
124{
125}
126
127GLTexture::~GLTexture()
128{
129}
130
131// Must be called from RenderThread with active GL context
132void GLTexture::destroy()
133{
134 delete m_gl;
135 m_gl = nullptr;
136 delete m_renderBuffer;
137 m_renderBuffer = nullptr;
138
139 m_dirtyFlags = None;
140 m_sharedTextureId = -1;
141 m_externalRendering = false;
142 m_wasTextureRecreated = false;
143 m_dataFunctor.reset();
144 m_pendingDataFunctor = nullptr;
145
146 m_properties = {};
147 m_parameters = {};
148 m_textureData.reset();
149 m_images.clear();
150 m_imageData.clear();
151 m_pendingTextureDataUpdates.clear();
152}
153
154bool GLTexture::loadTextureDataFromGenerator()
155{
156 m_textureData = m_dataFunctor->operator()();
157 // if there is a texture generator, most properties will be defined by it
158 if (m_textureData) {
159 const QAbstractTexture::Target target = m_textureData->target();
160
161 // If both target and functor return Automatic we are still
162 // probably loading the texture, return false
163 if (m_properties.target == QAbstractTexture::TargetAutomatic &&
164 target == QAbstractTexture::TargetAutomatic) {
165 m_textureData.reset();
166 return false;
167 }
168
169 if (m_properties.target != QAbstractTexture::TargetAutomatic &&
170 target != QAbstractTexture::TargetAutomatic &&
171 m_properties.target != target) {
172 qWarning() << Q_FUNC_INFO << "Generator and Properties not requesting the same texture target";
173 m_textureData.reset();
174 return false;
175 }
176
177 // We take target type from generator if it wasn't explicitly set by the user
178 if (m_properties.target == QAbstractTexture::TargetAutomatic)
179 m_properties.target = target;
180 m_properties.width = m_textureData->width();
181 m_properties.height = m_textureData->height();
182 m_properties.depth = m_textureData->depth();
183 m_properties.layers = m_textureData->layers();
184 m_properties.format = m_textureData->format();
185
186 const QList<QTextureImageDataPtr> imageData = m_textureData->imageData();
187
188 if (imageData.size() > 0) {
189 // Set the mips level based on the first image if autoMipMapGeneration is disabled
190 if (!m_properties.generateMipMaps)
191 m_properties.mipLevels = imageData.first()->mipLevels();
192 }
193 }
194 return !m_textureData.isNull();
195}
196
197void GLTexture::loadTextureDataFromImages()
198{
199 int maxMipLevel = 0;
200 for (const Image &img : qAsConst(m_images)) {
201 const QTextureImageDataPtr imgData = img.generator->operator()();
202 // imgData may be null in the following cases:
203 // - Texture is created with TextureImages which have yet to be
204 // loaded (skybox where you don't yet know the path, source set by
205 // a property binding, queued connection ...)
206 // - TextureImage whose generator failed to return a valid data
207 // (invalid url, error opening file...)
208 if (imgData.isNull())
209 continue;
210
211 m_imageData.push_back(imgData);
212 maxMipLevel = qMax(maxMipLevel, img.mipLevel);
213
214 // If the texture doesn't have a texture generator, we will
215 // derive some properties from the first TextureImage (layer=0, miplvl=0, face=0)
216 if (!m_textureData && img.layer == 0 && img.mipLevel == 0 && img.face == QAbstractTexture::CubeMapPositiveX) {
217 if (imgData->width() != -1 && imgData->height() != -1 && imgData->depth() != -1) {
218 m_properties.width = imgData->width();
219 m_properties.height = imgData->height();
220 m_properties.depth = imgData->depth();
221 }
222 // Set the format of the texture if the texture format is set to Automatic
223 if (m_properties.format == QAbstractTexture::Automatic) {
224 m_properties.format = static_cast<QAbstractTexture::TextureFormat>(imgData->format());
225 }
226 setDirtyFlag(Properties, true);
227 }
228 }
229
230 // make sure the number of mip levels is set when there is no texture data generator
231 if (!m_dataFunctor) {
232 m_properties.mipLevels = maxMipLevel + 1;
233 setDirtyFlag(Properties, true);
234 }
235}
236
237// Called from RenderThread
238GLTexture::TextureUpdateInfo GLTexture::createOrUpdateGLTexture()
239{
240 TextureUpdateInfo textureInfo;
241 m_wasTextureRecreated = false;
242
243 const bool hasSharedTextureId = m_sharedTextureId > 0;
244 // Only load texture data if we are not using a sharedTextureId
245 // Check if dataFunctor or images have changed
246 if (!hasSharedTextureId) {
247 // If dataFunctor exists and we have no data and it hasnĀ“t run yet
248 if (m_dataFunctor && !m_textureData && m_dataFunctor.get() != m_pendingDataFunctor ) {
249 const bool successfullyLoadedTextureData = loadTextureDataFromGenerator();
250 // If successful, m_textureData has content
251 if (successfullyLoadedTextureData) {
252 setDirtyFlag(Properties, true);
253 setDirtyFlag(TextureData, true);
254 } else {
255 if (m_pendingDataFunctor != m_dataFunctor.get()) {
256 qWarning() << "[Qt3DRender::GLTexture] No QTextureData generated from Texture Generator yet. Texture will be invalid for this frame";
257 m_pendingDataFunctor = m_dataFunctor.get();
258 }
259 textureInfo.properties.status = QAbstractTexture::Loading;
260 return textureInfo;
261 }
262 }
263
264 // If images have changed, clear previous images data
265 // and regenerate m_imageData for the images
266 if (testDirtyFlag(TextureImageData)) {
267 m_imageData.clear();
268 loadTextureDataFromImages();
269 // Mark for upload if we actually have something to upload
270 if (!m_imageData.empty()) {
271 setDirtyFlag(TextureData, true);
272 }
273 // Reset image flag
274 setDirtyFlag(TextureImageData, false);
275 }
276
277 // Don't try to create the texture if the target or format was still not set
278 // Format should either be set by user or if Automatic
279 // by either the dataGenerator of the texture or the first Image
280 // Target should explicitly be set by the user or the dataGenerator
281 if (m_properties.target == QAbstractTexture::TargetAutomatic ||
282 m_properties.format == QAbstractTexture::Automatic ||
283 m_properties.format == QAbstractTexture::NoFormat) {
284 textureInfo.properties.status = QAbstractTexture::Error;
285 return textureInfo;
286 }
287 }
288
289 // If the properties changed or texture has become a shared texture from a
290 // 3rd party engine, we need to destroy and maybe re-allocate the texture
291 if (testDirtyFlag(Properties) || testDirtyFlag(SharedTextureId)) {
292 delete m_gl;
293 m_gl = nullptr;
294 textureInfo.wasUpdated = true;
295 // If we are destroyed because of some property change but still have (some) of
296 // our content data make sure we are marked for upload
297 // TO DO: We should actually check if the textureData is still correct
298 // in regard to the size, target and format of the texture though.
299 if (!testDirtyFlag(SharedTextureId) &&
300 (m_textureData || !m_imageData.empty() || !m_pendingTextureDataUpdates.empty()))
301 setDirtyFlag(TextureData, true);
302 }
303
304 m_properties.status = QAbstractTexture::Ready;
305
306 if (testDirtyFlag(SharedTextureId) || hasSharedTextureId) {
307 // Update m_properties by doing introspection on the texture
308 if (hasSharedTextureId)
309 introspectPropertiesFromSharedTextureId();
310 setDirtyFlag(SharedTextureId, false);
311 } else {
312 // We only build a QOpenGLTexture if we have no shared textureId set
313 if (!m_gl) {
314 m_gl = buildGLTexture();
315 if (!m_gl) {
316 qWarning() << "[Qt3DRender::GLTexture] failed to create texture";
317 textureInfo.properties.status = QAbstractTexture::Error;
318 return textureInfo;
319 }
320
321 m_gl->allocateStorage();
322 if (!m_gl->isStorageAllocated()) {
323 qWarning() << "[Qt3DRender::GLTexture] failed to allocate texture";
324 textureInfo.properties.status = QAbstractTexture::Error;
325 return textureInfo;
326 }
327 m_wasTextureRecreated = true;
328 }
329
330 textureInfo.texture = m_gl;
331
332 // need to (re-)upload texture data?
333 const bool needsUpload = testDirtyFlag(TextureData);
334 if (needsUpload) {
335 uploadGLTextureData();
336 setDirtyFlag(TextureData, false);
337 }
338
339 // need to set texture parameters?
340 if (testDirtyFlag(Properties) || testDirtyFlag(Parameters)) {
341 updateGLTextureParameters();
342 setDirtyFlag(Properties, false);
343 setDirtyFlag(Parameters, false);
344 }
345 }
346
347 textureInfo.properties = m_properties;
348
349 return textureInfo;
350}
351
352RenderBuffer *GLTexture::getOrCreateRenderBuffer()
353{
354 if (m_dataFunctor && !m_textureData) {
355 m_textureData = m_dataFunctor->operator()();
356 if (m_textureData) {
357 if (m_properties.target != QAbstractTexture::TargetAutomatic)
358 qWarning() << "[Qt3DRender::GLTexture] [renderbuffer] When a texture provides a generator, it's target is expected to be TargetAutomatic";
359
360 m_properties.width = m_textureData->width();
361 m_properties.height = m_textureData->height();
362 m_properties.format = m_textureData->format();
363
364 setDirtyFlag(Properties);
365 } else {
366 if (m_pendingDataFunctor != m_dataFunctor.get()) {
367 qWarning() << "[Qt3DRender::GLTexture] [renderbuffer] No QTextureData generated from Texture Generator yet. Texture will be invalid for this frame";
368 m_pendingDataFunctor = m_dataFunctor.get();
369 }
370 return nullptr;
371 }
372 }
373
374 if (testDirtyFlag(Properties)) {
375 delete m_renderBuffer;
376 m_renderBuffer = nullptr;
377 }
378
379 if (!m_renderBuffer)
380 m_renderBuffer = new RenderBuffer(m_properties.width, m_properties.height, m_properties.format);
381
382 setDirtyFlag(Properties, false);
383 setDirtyFlag(Parameters, false);
384
385 return m_renderBuffer;
386}
387
388// This must be called from the RenderThread
389// So GLTexture release from the manager can only be done from that thread
390void GLTexture::cleanup()
391{
392 destroy();
393}
394
395void GLTexture::setParameters(const TextureParameters &params)
396{
397 if (m_parameters != params) {
398 m_parameters = params;
399 setDirtyFlag(Parameters);
400 }
401}
402
403void GLTexture::setProperties(const TextureProperties &props)
404{
405 if (m_properties != props) {
406 m_properties = props;
407 setDirtyFlag(Properties);
408 }
409}
410
411void GLTexture::setImages(const std::vector<Image> &images)
412{
413 // check if something has changed at all
414 const bool same = (images == m_images);
415
416 if (!same) {
417 m_images = images;
418 requestImageUpload();
419 }
420}
421
422void GLTexture::setGenerator(const QTextureGeneratorPtr &generator)
423{
424 m_textureData.reset();
425 m_dataFunctor = generator;
426 m_pendingDataFunctor = nullptr;
427 requestUpload();
428}
429
430void GLTexture::setSharedTextureId(int textureId)
431{
432 if (m_sharedTextureId != textureId) {
433 m_sharedTextureId = textureId;
434 setDirtyFlag(SharedTextureId);
435 }
436}
437
438void GLTexture::addTextureDataUpdates(const std::vector<QTextureDataUpdate> &updates)
439{
440 Qt3DCore::append(m_pendingTextureDataUpdates, updates);
441 requestUpload();
442}
443
444// Return nullptr if
445// - context cannot be obtained
446// - texture hasn't yet been loaded
447QOpenGLTexture *GLTexture::buildGLTexture()
448{
449 QOpenGLContext *ctx = QOpenGLContext::currentContext();
450 if (!ctx) {
451 qWarning() << Q_FUNC_INFO << "requires an OpenGL context";
452 return nullptr;
453 }
454
455 const QAbstractTexture::Target actualTarget = m_properties.target;
456 if (actualTarget == QAbstractTexture::TargetAutomatic) {
457 // If the target is automatic at this point, it means that the texture
458 // hasn't been loaded yet (case of remote urls) and that loading failed
459 // and that target format couldn't be deduced
460 return nullptr;
461 }
462
463 QOpenGLTexture* glTex = new QOpenGLTexture(static_cast<QOpenGLTexture::Target>(actualTarget));
464
465 // m_format may not be ES2 compatible. Now it's time to convert it, if necessary.
466 QAbstractTexture::TextureFormat format = m_properties.format;
467 if (ctx->isOpenGLES() && ctx->format().majorVersion() < 3) {
468 switch (m_properties.format) {
469 case QAbstractTexture::RGBA8_UNorm:
470 case QAbstractTexture::RGBAFormat:
471 format = QAbstractTexture::RGBAFormat;
472 break;
473 case QAbstractTexture::RGB8_UNorm:
474 case QAbstractTexture::RGBFormat:
475 format = QAbstractTexture::RGBFormat;
476 break;
477 case QAbstractTexture::DepthFormat:
478 format = QAbstractTexture::DepthFormat;
479 break;
480 default:
481 auto warning = qWarning();
482 warning << "Could not find a matching OpenGL ES 2.0 texture format:";
483 QtDebugUtils::formatQEnum(warning, m_properties.format);
484 break;
485 }
486 }
487
488 // Map ETC1 to ETC2 when supported. This allows using features like
489 // immutable storage as ETC2 is standard in GLES 3.0, while the ETC1 extension
490 // is written against GLES 1.0.
491 if (m_properties.format == QAbstractTexture::RGB8_ETC1) {
492 if ((ctx->isOpenGLES() && ctx->format().majorVersion() >= 3)
493 || ctx->hasExtension(QByteArrayLiteral("GL_OES_compressed_ETC2_RGB8_texture"))
494 || ctx->hasExtension(QByteArrayLiteral("GL_ARB_ES3_compatibility")))
495 format = m_properties.format = QAbstractTexture::RGB8_ETC2;
496 }
497
498 glTex->setFormat(m_properties.format == QAbstractTexture::Automatic ?
499 QOpenGLTexture::NoFormat :
500 static_cast<QOpenGLTexture::TextureFormat>(format));
501 glTex->setSize(m_properties.width, m_properties.height, m_properties.depth);
502 // Set layers count if texture array
503 if (actualTarget == QAbstractTexture::Target1DArray ||
504 actualTarget == QAbstractTexture::Target2DArray ||
505 actualTarget == QAbstractTexture::Target2DMultisampleArray ||
506 actualTarget == QAbstractTexture::TargetCubeMapArray) {
507 glTex->setLayers(m_properties.layers);
508 }
509
510 if (actualTarget == QAbstractTexture::Target2DMultisample ||
511 actualTarget == QAbstractTexture::Target2DMultisampleArray) {
512 // Set samples count if multisampled texture
513 // (multisampled textures don't have mipmaps)
514 glTex->setSamples(m_properties.samples);
515 } else if (m_properties.generateMipMaps) {
516 glTex->setMipLevels(glTex->maximumMipLevels());
517 } else {
518 glTex->setAutoMipMapGenerationEnabled(false);
519 if (glTex->hasFeature(QOpenGLTexture::TextureMipMapLevel)) {
520 glTex->setMipBaseLevel(0);
521 glTex->setMipMaxLevel(m_properties.mipLevels - 1);
522 }
523 glTex->setMipLevels(m_properties.mipLevels);
524 }
525
526 if (!glTex->create()) {
527 qWarning() << Q_FUNC_INFO << "creating QOpenGLTexture failed";
528 return nullptr;
529 }
530
531 return glTex;
532}
533
534void GLTexture::uploadGLTextureData()
535{
536 // Upload all QTexImageData set by the QTextureGenerator
537 if (m_textureData) {
538 const QList<QTextureImageDataPtr> imgData = m_textureData->imageData();
539
540 for (const QTextureImageDataPtr &data : imgData) {
541 const int mipLevels = m_properties.generateMipMaps ? 1 : data->mipLevels();
542
543 for (int layer = 0; layer < data->layers(); layer++) {
544 for (int face = 0; face < data->faces(); face++) {
545 for (int level = 0; level < mipLevels; level++) {
546 // ensure we don't accidentally cause a detach / copy of the raw bytes
547 const QByteArray bytes(data->data(layer, face, level));
548 uploadGLData(m_gl, level, layer,
549 static_cast<QOpenGLTexture::CubeMapFace>(QOpenGLTexture::CubeMapPositiveX + face),
550 bytes, data);
551 }
552 }
553 }
554 }
555 }
556
557 // Upload all QTexImageData references by the TextureImages
558 for (size_t i = 0; i < std::min(m_images.size(), m_imageData.size()); i++) {
559 const QTextureImageDataPtr &imgData = m_imageData.at(i);
560 // Here the bytes in the QTextureImageData contain data for a single
561 // layer, face or mip level, unlike the QTextureGenerator case where
562 // they are in a single blob. Hence QTextureImageData::data() is not suitable.
563 const QByteArray bytes(QTextureImageDataPrivate::get(imgData.get())->m_data);
564 uploadGLData(m_gl, m_images[i].mipLevel, m_images[i].layer,
565 static_cast<QOpenGLTexture::CubeMapFace>(m_images[i].face),
566 bytes, imgData);
567 }
568 // Free up image data once content has been uploaded
569 // Note: if data functor stores the data, this won't really free anything though
570 m_imageData.clear();
571
572 // Update data from TextureUpdates
573 const std::vector<QTextureDataUpdate> textureDataUpdates = Qt3DCore::moveAndClear(m_pendingTextureDataUpdates);
574 for (const QTextureDataUpdate &update : textureDataUpdates) {
575 const QTextureImageDataPtr imgData = update.data();
576
577 if (!imgData) {
578 qWarning() << Q_FUNC_INFO << "QTextureDataUpdate no QTextureImageData set";
579 continue;
580 }
581
582 const int xOffset = update.x();
583 const int yOffset = update.y();
584 const int zOffset = update.z();
585 const int xExtent = xOffset + imgData->width();
586 const int yExtent = yOffset + imgData->height();
587 const int zExtent = zOffset + imgData->depth();
588
589 // Check update is compatible with our texture
590 if (xOffset >= m_gl->width() ||
591 yOffset >= m_gl->height() ||
592 zOffset >= m_gl->depth() ||
593 xExtent > m_gl->width() ||
594 yExtent > m_gl->height() ||
595 zExtent > m_gl->depth() ||
596 update.mipLevel() >= m_gl->mipLevels() ||
597 update.layer() >= m_gl->layers()) {
598 qWarning() << Q_FUNC_INFO << "QTextureDataUpdate incompatible with texture";
599 continue;
600 }
601
602 const QByteArray bytes = (QTextureImageDataPrivate::get(imgData.get())->m_data);
603 // Here the bytes in the QTextureImageData contain data for a single
604 // layer, face or mip level, unlike the QTextureGenerator case where
605 // they are in a single blob. Hence QTextureImageData::data() is not suitable.
606
607 // Check if this is a full sized update
608 if (xOffset == 0 &&
609 yOffset == 0 &&
610 zOffset == 0 &&
611 xExtent == m_gl->width() &&
612 yExtent == m_gl->height() &&
613 zExtent == m_gl->depth()) {
614 uploadGLData(m_gl, update.mipLevel(), update.layer(),
615 static_cast<QOpenGLTexture::CubeMapFace>(update.face()),
616 bytes, imgData);
617 } else {
618 if (imgData->isCompressed()) {
619 qWarning() << Q_FUNC_INFO << "Uploading non full sized Compressed Data not supported yet";
620 } else {
621
622 uploadGLData(m_gl,
623 update.mipLevel(), update.layer(),
624 static_cast<QOpenGLTexture::CubeMapFace>(update.face()),
625 xOffset, yOffset, zOffset,
626 bytes, imgData);
627 }
628 }
629 }
630}
631
632void GLTexture::updateGLTextureParameters()
633{
634 const QAbstractTexture::Target actualTarget = m_properties.target;
635 const bool isMultisampledTexture = (actualTarget == QAbstractTexture::Target2DMultisample ||
636 actualTarget == QAbstractTexture::Target2DMultisampleArray);
637 // Multisampled textures can only be accessed by texelFetch in shaders
638 // and don't support wrap modes and mig/mag filtes
639 if (isMultisampledTexture)
640 return;
641
642 m_gl->setWrapMode(QOpenGLTexture::DirectionS, static_cast<QOpenGLTexture::WrapMode>(m_parameters.wrapModeX));
643 if (actualTarget != QAbstractTexture::Target1D &&
644 actualTarget != QAbstractTexture::Target1DArray &&
645 actualTarget != QAbstractTexture::TargetBuffer)
646 m_gl->setWrapMode(QOpenGLTexture::DirectionT, static_cast<QOpenGLTexture::WrapMode>(m_parameters.wrapModeY));
647 if (actualTarget == QAbstractTexture::Target3D)
648 m_gl->setWrapMode(QOpenGLTexture::DirectionR, static_cast<QOpenGLTexture::WrapMode>(m_parameters.wrapModeZ));
649 m_gl->setMinMagFilters(static_cast<QOpenGLTexture::Filter>(m_parameters.minificationFilter),
650 static_cast<QOpenGLTexture::Filter>(m_parameters.magnificationFilter));
651 if (m_gl->hasFeature(QOpenGLTexture::AnisotropicFiltering))
652 m_gl->setMaximumAnisotropy(m_parameters.maximumAnisotropy);
653 if (m_gl->hasFeature(QOpenGLTexture::TextureComparisonOperators)) {
654 m_gl->setComparisonFunction(static_cast<QOpenGLTexture::ComparisonFunction>(m_parameters.comparisonFunction));
655 m_gl->setComparisonMode(static_cast<QOpenGLTexture::ComparisonMode>(m_parameters.comparisonMode));
656 }
657}
658
659void GLTexture::introspectPropertiesFromSharedTextureId()
660{
661 // We know that the context is active when this function is called
662 QOpenGLContext *ctx = QOpenGLContext::currentContext();
663 if (!ctx) {
664 qWarning() << Q_FUNC_INFO << "requires an OpenGL context";
665 return;
666 }
667 QOpenGLFunctions *gl = ctx->functions();
668
669 // If the user has set the target format himself, we won't try to deduce it
670 if (m_properties.target != QAbstractTexture::TargetAutomatic)
671 return;
672
673 const QAbstractTexture::Target targets[] = {
674 QAbstractTexture::Target2D,
675 QAbstractTexture::TargetCubeMap,
676#if !QT_CONFIG(opengles2)
677 QAbstractTexture::Target1D,
678 QAbstractTexture::Target1DArray,
679 QAbstractTexture::Target3D,
680 QAbstractTexture::Target2DArray,
681 QAbstractTexture::TargetCubeMapArray,
682 QAbstractTexture::Target2DMultisample,
683 QAbstractTexture::Target2DMultisampleArray,
684 QAbstractTexture::TargetRectangle,
685 QAbstractTexture::TargetBuffer,
686#endif
687 };
688
689#if !QT_CONFIG(opengles2)
690 // Try to find texture target with GL 4.5 functions
691 const QPair<int, int> ctxGLVersion = ctx->format().version();
692 if (ctxGLVersion.first > 4 || (ctxGLVersion.first == 4 && ctxGLVersion.second >= 5)) {
693 // Only for GL 4.5+
694#ifdef GL_TEXTURE_TARGET
695#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
696 QOpenGLFunctions_4_5_Core *gl5 = QOpenGLVersionFunctionsFactory::get<QOpenGLFunctions_4_5_Core>();
697#else
698 QOpenGLFunctions_4_5_Core *gl5 = ctx->versionFunctions<QOpenGLFunctions_4_5_Core>();
699#endif
700 if (gl5 != nullptr)
701 gl5->glGetTextureParameteriv(m_sharedTextureId, GL_TEXTURE_TARGET, reinterpret_cast<int *>(&m_properties.target));
702#endif
703 }
704#endif
705
706 // If GL 4.5 function unavailable or not working, try a slower way
707 if (m_properties.target == QAbstractTexture::TargetAutomatic) {
708 // // OpenGL offers no proper way of querying for the target of a texture given its id
709 gl->glActiveTexture(GL_TEXTURE0);
710
711 const GLenum targetBindings[] = {
712 GL_TEXTURE_BINDING_2D,
713 GL_TEXTURE_BINDING_CUBE_MAP,
714#if !QT_CONFIG(opengles2)
715 GL_TEXTURE_BINDING_1D,
716 GL_TEXTURE_BINDING_1D_ARRAY,
717 GL_TEXTURE_BINDING_3D,
718 GL_TEXTURE_BINDING_2D_ARRAY,
719 GL_TEXTURE_BINDING_CUBE_MAP_ARRAY,
720 GL_TEXTURE_BINDING_2D_MULTISAMPLE,
721 GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY,
722 GL_TEXTURE_BINDING_RECTANGLE,
723 GL_TEXTURE_BINDING_BUFFER
724#endif
725 };
726
727 Q_STATIC_ASSERT(sizeof(targetBindings) / sizeof(targetBindings[0]) == sizeof(targets) / sizeof(targets[0]));
728
729 for (uint i = 0; i < sizeof(targetBindings) / sizeof(targetBindings[0]); ++i) {
730 const int target = targets[i];
731 gl->glBindTexture(target, m_sharedTextureId);
732 int boundId = 0;
733 gl->glGetIntegerv(targetBindings[i], &boundId);
734 gl->glBindTexture(target, 0);
735 if (boundId == m_sharedTextureId) {
736 m_properties.target = static_cast<QAbstractTexture::Target>(target);
737 break;
738 }
739 }
740 }
741
742 // Return early if we weren't able to find texture target
743 if (std::find(std::begin(targets), std::end(targets), m_properties.target) == std::end(targets)) {
744 qWarning() << "Unable to determine texture target for shared GL texture";
745 return;
746 }
747
748 // Bind texture once we know its target
749 gl->glBindTexture(m_properties.target, m_sharedTextureId);
750
751 // TO DO: Improve by using glGetTextureParameters when available which
752 // support direct state access
753#ifndef GL_TEXTURE_MAX_LEVEL
754#define GL_TEXTURE_MAX_LEVEL 0x813D
755#endif
756
757#ifndef GL_TEXTURE_WRAP_R
758#define GL_TEXTURE_WRAP_R 0x8072
759#endif
760
761 gl->glGetTexParameteriv(int(m_properties.target), GL_TEXTURE_MAX_LEVEL, reinterpret_cast<int *>(&m_properties.mipLevels));
762 gl->glGetTexParameteriv(int(m_properties.target), GL_TEXTURE_MIN_FILTER, reinterpret_cast<int *>(&m_parameters.minificationFilter));
763 gl->glGetTexParameteriv(int(m_properties.target), GL_TEXTURE_MAG_FILTER, reinterpret_cast<int *>(&m_parameters.magnificationFilter));
764 gl->glGetTexParameteriv(int(m_properties.target), GL_TEXTURE_WRAP_R, reinterpret_cast<int *>(&m_parameters.wrapModeX));
765 gl->glGetTexParameteriv(int(m_properties.target), GL_TEXTURE_WRAP_S, reinterpret_cast<int *>(&m_parameters.wrapModeY));
766 gl->glGetTexParameteriv(int(m_properties.target), GL_TEXTURE_WRAP_T, reinterpret_cast<int *>(&m_parameters.wrapModeZ));
767
768#if !QT_CONFIG(opengles2)
769 // Try to retrieve dimensions (not available on ES 2.0)
770 if (!ctx->isOpenGLES()) {
771#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
772 QOpenGLFunctions_3_1 *gl3 = QOpenGLVersionFunctionsFactory::get<QOpenGLFunctions_3_1>();
773#else
774 QOpenGLFunctions_3_1 *gl3 = ctx->versionFunctions<QOpenGLFunctions_3_1>();
775#endif
776 if (!gl3) {
777 qWarning() << "Failed to retrieve shared texture dimensions";
778 return;
779 }
780
781 gl3->glGetTexLevelParameteriv(int(m_properties.target), 0, GL_TEXTURE_WIDTH, reinterpret_cast<int *>(&m_properties.width));
782 gl3->glGetTexLevelParameteriv(int(m_properties.target), 0, GL_TEXTURE_HEIGHT, reinterpret_cast<int *>(&m_properties.height));
783 gl3->glGetTexLevelParameteriv(int(m_properties.target), 0, GL_TEXTURE_DEPTH, reinterpret_cast<int *>(&m_properties.depth));
784 gl3->glGetTexLevelParameteriv(int(m_properties.target), 0, GL_TEXTURE_INTERNAL_FORMAT, reinterpret_cast<int *>(&m_properties.format));
785 }
786#endif
787
788 gl->glBindTexture(m_properties.target, 0);
789}
790
791} // namespace OpenGL
792} // namespace Render
793} // namespace Qt3DRender
794
795QT_END_NAMESPACE
796