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