1 /****************************************************************************
2 **
3 ** Copyright (C) 2020 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 "texture_p.h"
42
43 #include <private/qdebug_p.h>
44 #include <private/qrhi_p.h>
45 #include <QDebug>
46 #include <Qt3DRender/qtexture.h>
47 #include <Qt3DRender/qtexturedata.h>
48 #include <Qt3DRender/qtextureimagedata.h>
49 #include <Qt3DRender/private/managers_p.h>
50 #include <Qt3DRender/private/qabstracttexture_p.h>
51 #include <Qt3DRender/private/qtextureimagedata_p.h>
52 #include <renderbuffer_p.h>
53 #include <submissioncontext_p.h>
54
55 QT_BEGIN_NAMESPACE
56
57 using namespace Qt3DCore;
58
59 namespace Qt3DRender {
60 namespace Render {
61 namespace Rhi {
62
63 namespace {
64
issRGBFormat(QAbstractTexture::TextureFormat format)65 bool issRGBFormat(QAbstractTexture::TextureFormat format) noexcept
66 {
67 switch (format) {
68 case QAbstractTexture::SRGB8:
69 case QAbstractTexture::SRGB8_ETC2:
70 case QAbstractTexture::SRGB8_PunchThrough_Alpha1_ETC2:
71 return true;
72 default:
73 return false;
74 }
75 }
76
rhiFormatFromTextureFormat(QAbstractTexture::TextureFormat format)77 QRhiTexture::Format rhiFormatFromTextureFormat(QAbstractTexture::TextureFormat format) noexcept
78 {
79 switch (format) {
80 case QAbstractTexture::RGBA8_UNorm:
81 case QAbstractTexture::SRGB8:
82 return QRhiTexture::RGBA8;
83 case QAbstractTexture::R8_UNorm:
84 return QRhiTexture::R8;
85 case QAbstractTexture::R16_UNorm:
86 return QRhiTexture::R16;
87 case QAbstractTexture::RGBA16F:
88 return QRhiTexture::RGBA16F;
89 case QAbstractTexture::RGBA32F:
90 return QRhiTexture::RGBA32F;
91 case QAbstractTexture::R16F:
92 return QRhiTexture::R16F;
93 case QAbstractTexture::R32F:
94 return QRhiTexture::R32F;
95 case QAbstractTexture::D16:
96 return QRhiTexture::D16;
97 case QAbstractTexture::D32F:
98 return QRhiTexture::D32F;
99 case QAbstractTexture::RGB_DXT1:
100 case QAbstractTexture::RGBA_DXT1:
101 return QRhiTexture::BC1;
102 case QAbstractTexture::RGBA_DXT3:
103 return QRhiTexture::BC2;
104 case QAbstractTexture::RGBA_DXT5:
105 return QRhiTexture::BC3;
106 case QAbstractTexture::RGB8_ETC2:
107 case QAbstractTexture::SRGB8_ETC2:
108 return QRhiTexture::ETC2_RGB8;
109 case QAbstractTexture::RGB8_PunchThrough_Alpha1_ETC2:
110 case QAbstractTexture::SRGB8_PunchThrough_Alpha1_ETC2:
111 return QRhiTexture::ETC2_RGB8A1;
112 case QAbstractTexture::RGBA8_ETC2_EAC:
113 return QRhiTexture::ETC2_RGBA8;
114 default:
115 Q_UNREACHABLE();
116 return QRhiTexture::UnknownFormat;
117 }
118 }
119
rhiFilterFromTextureFilter(QAbstractTexture::Filter filter)120 QRhiSampler::Filter rhiFilterFromTextureFilter(QAbstractTexture::Filter filter) noexcept
121 {
122 switch (filter) {
123 case QAbstractTexture::Nearest:
124 case QAbstractTexture::NearestMipMapNearest:
125 case QAbstractTexture::NearestMipMapLinear:
126 return QRhiSampler::Nearest;
127 case QAbstractTexture::Linear:
128 case QAbstractTexture::LinearMipMapNearest:
129 case QAbstractTexture::LinearMipMapLinear:
130 return QRhiSampler::Linear;
131 default:
132 Q_UNREACHABLE();
133 return QRhiSampler::Nearest;
134 }
135 }
136
rhiMipMapFilterFromTextureFilter(QAbstractTexture::Filter filter)137 QRhiSampler::Filter rhiMipMapFilterFromTextureFilter(QAbstractTexture::Filter filter) noexcept
138 {
139 switch (filter) {
140 case QAbstractTexture::NearestMipMapNearest:
141 case QAbstractTexture::LinearMipMapNearest:
142 return QRhiSampler::Nearest;
143 case QAbstractTexture::Linear:
144 case QAbstractTexture::NearestMipMapLinear:
145 case QAbstractTexture::LinearMipMapLinear:
146 return QRhiSampler::Linear;
147 default:
148 Q_UNREACHABLE();
149 return QRhiSampler::None;
150 }
151 }
152
153 std::tuple<QRhiSampler::AddressMode, QRhiSampler::AddressMode, QRhiSampler::AddressMode>
rhiWrapModeFromTextureWrapMode(QTextureWrapMode::WrapMode x,QTextureWrapMode::WrapMode y,QTextureWrapMode::WrapMode z)154 rhiWrapModeFromTextureWrapMode(QTextureWrapMode::WrapMode x, QTextureWrapMode::WrapMode y,
155 QTextureWrapMode::WrapMode z) noexcept
156 {
157 auto toRhiAddress = [](QTextureWrapMode::WrapMode mode) noexcept {
158 switch (mode) {
159 case Qt3DRender::QTextureWrapMode::Repeat:
160 return QRhiSampler::Repeat;
161 case Qt3DRender::QTextureWrapMode::ClampToEdge:
162 case Qt3DRender::QTextureWrapMode::ClampToBorder:
163 return QRhiSampler::ClampToEdge;
164 case Qt3DRender::QTextureWrapMode::MirroredRepeat:
165 return QRhiSampler::Mirror;
166 default:
167 Q_UNREACHABLE();
168 }
169 };
170
171 return { toRhiAddress(x), toRhiAddress(y), toRhiAddress(z) };
172 }
173
174 QRhiSampler::CompareOp
rhiCompareOpFromTextureCompareOp(QAbstractTexture::ComparisonFunction mode)175 rhiCompareOpFromTextureCompareOp(QAbstractTexture::ComparisonFunction mode) noexcept
176 {
177 switch (mode) {
178 case QAbstractTexture::CompareLessEqual:
179 return QRhiSampler::LessOrEqual;
180 case QAbstractTexture::CompareGreaterEqual:
181 return QRhiSampler::GreaterOrEqual;
182 case QAbstractTexture::CompareLess:
183 return QRhiSampler::Less;
184 case QAbstractTexture::CompareGreater:
185 return QRhiSampler::Greater;
186 case QAbstractTexture::CompareEqual:
187 return QRhiSampler::Equal;
188 case QAbstractTexture::CommpareNotEqual:
189 return QRhiSampler::NotEqual;
190 case QAbstractTexture::CompareAlways:
191 return QRhiSampler::Always;
192 case QAbstractTexture::CompareNever:
193 return QRhiSampler::Never;
194 default:
195 return QRhiSampler::Always;
196 }
197 }
198
199 // This uploadGLData where the data is a fullsize subimage
200 // as QOpenGLTexture doesn't allow partial subimage uploads
createUploadEntry(int level,int layer,const QByteArray & bytes)201 QRhiTextureUploadEntry createUploadEntry(int level, int layer, const QByteArray &bytes) noexcept
202 {
203 QRhiTextureSubresourceUploadDescription description;
204 description.setData(bytes);
205 return QRhiTextureUploadEntry(layer, level, description);
206 }
207
208 template<typename F>
filterLayersAndFaces(const QTextureImageData & data,F f)209 void filterLayersAndFaces(const QTextureImageData &data, F f)
210 {
211 const int layers = data.layers();
212 const int faces = data.faces();
213 const int miplevels = data.mipLevels();
214
215 if (layers == 1 && faces == 1) {
216 for (int level = 0; level < miplevels; level++) {
217 f(createUploadEntry(level, 0, data.data(0, 0, level)));
218 }
219 } else if (layers > 1 && faces == 1) {
220 qWarning() << Q_FUNC_INFO << "Unsupported case, see QTBUG-83343";
221 /*
222 for (int layer = 0; layer < data.layers(); layer++) {
223 for (int level = 0; level < mipLevels; level++) {
224 f(createUploadEntry(level, layer, data.data(layer, 0, level)));
225 }
226 }
227 */
228 } else if (faces > 1 && layers == 1) {
229 // Mip levels do not seem to be supported by cubemaps...
230 for (int face = 0; face < data.faces(); face++) {
231 f(createUploadEntry(0, face, data.data(0, face, 0)));
232 }
233 } else {
234 qWarning() << Q_FUNC_INFO << "Unsupported case";
235 }
236 }
237
238 template<typename F>
filterLayerAndFace(int layer,int face,F f)239 void filterLayerAndFace(int layer, int face, F f)
240 {
241 if (layer == 0 && face == 0) {
242 f(0);
243 } else if (layer > 0 && face == 0) {
244 qWarning() << Q_FUNC_INFO << "Unsupported case, see QTBUG-83343";
245 // f(layer);
246 } else if (layer == 0 && face > 0) {
247 f(face);
248 } else {
249 qWarning() << Q_FUNC_INFO << "Unsupported case";
250 }
251 }
252
253 // For partial sub image uploads
createUploadEntry(int mipLevel,int layer,int xOffset,int yOffset,int zOffset,const QByteArray & bytes,const QTextureImageDataPtr & data)254 QRhiTextureUploadEntry createUploadEntry(int mipLevel, int layer, int xOffset, int yOffset,
255 int zOffset, const QByteArray &bytes,
256 const QTextureImageDataPtr &data) noexcept
257 {
258 QRhiTextureSubresourceUploadDescription description;
259 description.setData(bytes);
260 description.setSourceTopLeft(QPoint(xOffset, yOffset));
261 return QRhiTextureUploadEntry(layer, mipLevel, description);
262 }
263
264 } // anonymous
265
RHITexture()266 RHITexture::RHITexture()
267 : m_dirtyFlags(None),
268 m_rhi(nullptr),
269 m_rhiSampler(nullptr),
270 m_renderBuffer(nullptr),
271 m_dataFunctor(),
272 m_pendingDataFunctor(nullptr),
273 m_sharedTextureId(-1),
274 m_externalRendering(false),
275 m_wasTextureRecreated(false)
276 {
277 }
278
~RHITexture()279 RHITexture::~RHITexture() { }
280
281 // Must be called from RenderThread with active GL context
destroy()282 void RHITexture::destroy()
283 {
284 delete m_rhi;
285 m_rhi = nullptr;
286 delete m_rhiSampler;
287 m_rhiSampler = nullptr;
288 delete m_renderBuffer;
289 m_renderBuffer = nullptr;
290
291 m_dirtyFlags = None;
292 m_sharedTextureId = -1;
293 m_externalRendering = false;
294 m_wasTextureRecreated = false;
295 m_dataFunctor.reset();
296 m_pendingDataFunctor = nullptr;
297
298 m_properties = {};
299 m_parameters = {};
300 m_textureData.reset();
301 m_images.clear();
302 m_imageData.clear();
303 m_pendingTextureDataUpdates.clear();
304 }
305
loadTextureDataFromGenerator()306 bool RHITexture::loadTextureDataFromGenerator()
307 {
308 m_textureData = m_dataFunctor->operator()();
309 // if there is a texture generator, most properties will be defined by it
310 if (m_textureData) {
311 const QAbstractTexture::Target target = m_textureData->target();
312
313 // If both target and functor return Automatic we are still
314 // probably loading the texture, return false
315 if (m_properties.target == QAbstractTexture::TargetAutomatic
316 && target == QAbstractTexture::TargetAutomatic) {
317 m_textureData.reset();
318 return false;
319 }
320
321 if (m_properties.target != QAbstractTexture::TargetAutomatic
322 && target != QAbstractTexture::TargetAutomatic && m_properties.target != target) {
323 qWarning() << Q_FUNC_INFO
324 << "Generator and Properties not requesting the same texture target";
325 m_textureData.reset();
326 return false;
327 }
328
329 // We take target type from generator if it wasn't explicitly set by the user
330 if (m_properties.target == QAbstractTexture::TargetAutomatic)
331 m_properties.target = target;
332 m_properties.width = m_textureData->width();
333 m_properties.height = m_textureData->height();
334 m_properties.depth = m_textureData->depth();
335 m_properties.layers = m_textureData->layers();
336 m_properties.format = m_textureData->format();
337
338 const QVector<QTextureImageDataPtr> imageData = m_textureData->imageData();
339
340 if (!imageData.empty()) {
341 // Set the mips level based on the first image if autoMipMapGeneration is disabled
342 if (!m_properties.generateMipMaps)
343 m_properties.mipLevels = imageData.first()->mipLevels();
344 }
345 }
346 return !m_textureData.isNull();
347 }
348
loadTextureDataFromImages()349 void RHITexture::loadTextureDataFromImages()
350 {
351 int maxMipLevel = 0;
352 for (const Image &img : qAsConst(m_images)) {
353 const QTextureImageDataPtr imgData = img.generator->operator()();
354 // imgData may be null in the following cases:
355 // - Texture is created with TextureImages which have yet to be
356 // loaded (skybox where you don't yet know the path, source set by
357 // a property binding, queued connection ...)
358 // - TextureImage whose generator failed to return a valid data
359 // (invalid url, error opening file...)
360 if (imgData.isNull())
361 continue;
362
363 m_imageData.push_back(imgData);
364 maxMipLevel = qMax(maxMipLevel, img.mipLevel);
365
366 // If the texture doesn't have a texture generator, we will
367 // derive some properties from the first TextureImage (layer=0, miplvl=0, face=0)
368 if (!m_textureData && img.layer == 0 && img.mipLevel == 0
369 && img.face == QAbstractTexture::CubeMapPositiveX) {
370 if (imgData->width() != -1 && imgData->height() != -1 && imgData->depth() != -1) {
371 m_properties.width = imgData->width();
372 m_properties.height = imgData->height();
373 m_properties.depth = imgData->depth();
374 }
375 // Set the format of the texture if the texture format is set to Automatic
376 if (m_properties.format == QAbstractTexture::Automatic) {
377 m_properties.format =
378 static_cast<QAbstractTexture::TextureFormat>(imgData->format());
379 }
380 setDirtyFlag(Properties, true);
381 }
382 }
383
384 // make sure the number of mip levels is set when there is no texture data generator
385 if (!m_dataFunctor) {
386 m_properties.mipLevels = maxMipLevel + 1;
387 setDirtyFlag(Properties, true);
388 }
389 }
390
391 // Called from RenderThread
createOrUpdateRhiTexture(SubmissionContext * ctx)392 RHITexture::TextureUpdateInfo RHITexture::createOrUpdateRhiTexture(SubmissionContext *ctx)
393 {
394 TextureUpdateInfo textureInfo;
395 m_wasTextureRecreated = false;
396
397 const bool hasSharedTextureId = m_sharedTextureId > 0;
398 // Only load texture data if we are not using a sharedTextureId
399 // Check if dataFunctor or images have changed
400 if (!hasSharedTextureId) {
401 // If dataFunctor exists and we have no data and it hasn´t run yet
402 if (m_dataFunctor && !m_textureData && m_dataFunctor.get() != m_pendingDataFunctor) {
403 const bool successfullyLoadedTextureData = loadTextureDataFromGenerator();
404 // If successful, m_textureData has content
405 if (successfullyLoadedTextureData) {
406 setDirtyFlag(Properties, true);
407 setDirtyFlag(TextureData, true);
408 } else {
409 if (m_pendingDataFunctor != m_dataFunctor.get()) {
410 qWarning() << "[Qt3DRender::RHITexture] No QTextureData generated from Texture "
411 "Generator yet. Texture will be invalid for this frame";
412 m_pendingDataFunctor = m_dataFunctor.get();
413 }
414 textureInfo.properties.status = QAbstractTexture::Loading;
415 return textureInfo;
416 }
417 }
418
419 // If images have changed, clear previous images data
420 // and regenerate m_imageData for the images
421 if (testDirtyFlag(TextureImageData)) {
422 m_imageData.clear();
423 loadTextureDataFromImages();
424 // Mark for upload if we actually have something to upload
425 if (!m_imageData.empty()) {
426 setDirtyFlag(TextureData, true);
427 }
428 // Reset image flag
429 setDirtyFlag(TextureImageData, false);
430 }
431
432 // Don't try to create the texture if the target or format was still not set
433 // Format should either be set by user or if Automatic
434 // by either the dataGenerator of the texture or the first Image
435 // Target should explicitly be set by the user or the dataGenerator
436 if (m_properties.target == QAbstractTexture::TargetAutomatic
437 || m_properties.format == QAbstractTexture::Automatic
438 || m_properties.format == QAbstractTexture::NoFormat) {
439 textureInfo.properties.status = QAbstractTexture::Error;
440 return textureInfo;
441 }
442 }
443
444 // If the properties changed or texture has become a shared texture from a
445 // 3rd party engine, we need to destroy and maybe re-allocate the texture
446 if (testDirtyFlag(Properties) || testDirtyFlag(SharedTextureId)) {
447 delete m_rhi;
448 m_rhi = nullptr;
449 textureInfo.wasUpdated = true;
450 // If we are destroyed because of some property change but still have (some) of
451 // our content data make sure we are marked for upload
452 // TO DO: We should actually check if the textureData is still correct
453 // in regard to the size, target and format of the texture though.
454 if (!testDirtyFlag(SharedTextureId)
455 && (m_textureData || !m_imageData.empty() || !m_pendingTextureDataUpdates.empty()))
456 setDirtyFlag(TextureData, true);
457 }
458
459 m_properties.status = QAbstractTexture::Ready;
460
461 if (testDirtyFlag(SharedTextureId) || hasSharedTextureId) {
462 // Update m_properties by doing introspection on the texture
463 if (hasSharedTextureId)
464 introspectPropertiesFromSharedTextureId();
465 setDirtyFlag(SharedTextureId, false);
466 } else {
467 // We only build a QOpenGLTexture if we have no shared textureId set
468 if (!m_rhi) {
469 m_rhi = buildRhiTexture(ctx);
470 if (!m_rhi) {
471 qWarning() << "[Qt3DRender::RHITexture] failed to create texture";
472 textureInfo.properties.status = QAbstractTexture::Error;
473 return textureInfo;
474 }
475 m_wasTextureRecreated = true;
476 }
477
478 textureInfo.texture = m_rhi;
479
480 // need to (re-)upload texture data?
481 const bool needsUpload = testDirtyFlag(TextureData);
482 if (needsUpload) {
483 uploadRhiTextureData(ctx);
484 setDirtyFlag(TextureData, false);
485 }
486
487 // need to set texture parameters?
488 if (testDirtyFlag(Properties) || testDirtyFlag(Parameters)) {
489 updateRhiTextureParameters(ctx);
490 setDirtyFlag(Properties, false);
491 setDirtyFlag(Parameters, false);
492 }
493 }
494
495 textureInfo.properties = m_properties;
496
497 return textureInfo;
498 }
499
getOrCreateRenderBuffer()500 RenderBuffer *RHITexture::getOrCreateRenderBuffer()
501 {
502 if (m_dataFunctor && !m_textureData) {
503 m_textureData = m_dataFunctor->operator()();
504 if (m_textureData) {
505 if (m_properties.target != QAbstractTexture::TargetAutomatic)
506 qWarning() << "[Qt3DRender::RHITexture] [renderbuffer] When a texture provides a "
507 "generator, it's target is expected to be TargetAutomatic";
508
509 m_properties.width = m_textureData->width();
510 m_properties.height = m_textureData->height();
511 m_properties.format = m_textureData->format();
512
513 setDirtyFlag(Properties);
514 } else {
515 if (m_pendingDataFunctor != m_dataFunctor.get()) {
516 qWarning() << "[Qt3DRender::RHITexture] [renderbuffer] No QTextureData generated "
517 "from Texture Generator yet. Texture will be invalid for this frame";
518 m_pendingDataFunctor = m_dataFunctor.get();
519 }
520 return nullptr;
521 }
522 }
523
524 if (testDirtyFlag(Properties)) {
525 delete m_renderBuffer;
526 m_renderBuffer = nullptr;
527 }
528
529 if (!m_renderBuffer)
530 m_renderBuffer =
531 new RenderBuffer(m_properties.width, m_properties.height, m_properties.format);
532
533 setDirtyFlag(Properties, false);
534 setDirtyFlag(Parameters, false);
535
536 return m_renderBuffer;
537 }
538
539 // This must be called from the RenderThread
540 // So RHITexture release from the manager can only be done from that thread
cleanup()541 void RHITexture::cleanup()
542 {
543 destroy();
544 }
545
setParameters(const TextureParameters & params)546 void RHITexture::setParameters(const TextureParameters ¶ms)
547 {
548 if (m_parameters != params) {
549 m_parameters = params;
550 setDirtyFlag(Parameters);
551 }
552 }
553
setProperties(const TextureProperties & props)554 void RHITexture::setProperties(const TextureProperties &props)
555 {
556 if (m_properties != props) {
557 m_properties = props;
558 setDirtyFlag(Properties);
559 }
560 }
561
setImages(const QVector<Image> & images)562 void RHITexture::setImages(const QVector<Image> &images)
563 {
564 // check if something has changed at all
565 bool same = (images.size() == m_images.size());
566 if (same) {
567 for (int i = 0; i < images.size(); i++) {
568 if (images[i] != m_images[i]) {
569 same = false;
570 break;
571 }
572 }
573 }
574
575 if (!same) {
576 m_images = images;
577 requestImageUpload();
578 }
579 }
580
setGenerator(const QTextureGeneratorPtr & generator)581 void RHITexture::setGenerator(const QTextureGeneratorPtr &generator)
582 {
583 m_textureData.reset();
584 m_dataFunctor = generator;
585 m_pendingDataFunctor = nullptr;
586 requestUpload();
587 }
588
setSharedTextureId(int textureId)589 void RHITexture::setSharedTextureId(int textureId)
590 {
591 if (m_sharedTextureId != textureId) {
592 m_sharedTextureId = textureId;
593 setDirtyFlag(SharedTextureId);
594 }
595 }
596
addTextureDataUpdates(const QVector<QTextureDataUpdate> & updates)597 void RHITexture::addTextureDataUpdates(const QVector<QTextureDataUpdate> &updates)
598 {
599 m_pendingTextureDataUpdates += updates;
600 requestUpload();
601 }
602
603 // Return nullptr if
604 // - context cannot be obtained
605 // - texture hasn't yet been loaded
buildRhiTexture(SubmissionContext * ctx)606 QRhiTexture *RHITexture::buildRhiTexture(SubmissionContext *ctx)
607 {
608 const QAbstractTexture::Target actualTarget = m_properties.target;
609 if (actualTarget == QAbstractTexture::TargetAutomatic) {
610 // If the target is automatic at this point, it means that the texture
611 // hasn't been loaded yet (case of remote urls) and that loading failed
612 // and that target format couldn't be deduced
613 return nullptr;
614 }
615
616 const QRhiTexture::Format rhiFormat = rhiFormatFromTextureFormat(m_properties.format);
617 const QSize pixelSize(m_properties.width, m_properties.height);
618 QRhiTexture::Flags rhiFlags {};
619 int sampleCount = 1;
620
621 const bool issRGB8Format = issRGBFormat(m_properties.format);
622 if (issRGB8Format)
623 rhiFlags |= QRhiTexture::sRGB;
624
625 if (actualTarget == QAbstractTexture::Target2DMultisample
626 || actualTarget == QAbstractTexture::Target2DMultisampleArray) {
627 // Set samples count if multisampled texture
628 // (multisampled textures don't have mipmaps)
629 sampleCount = m_properties.samples;
630 }
631
632 switch (actualTarget) {
633 case QAbstractTexture::TargetCubeMap:
634 case QAbstractTexture::TargetCubeMapArray: {
635 rhiFlags |= QRhiTexture::CubeMap;
636 break;
637 }
638 default: {
639 // Mipmaps don't see to work with cubemaps at the moment
640 if (m_properties.generateMipMaps) {
641 rhiFlags |= QRhiTexture::UsedWithGenerateMips;
642 rhiFlags |= QRhiTexture::MipMapped;
643 } else if (m_properties.mipLevels > 1) {
644 rhiFlags |= QRhiTexture::MipMapped;
645 }
646 break;
647 }
648 }
649
650 QRhiTexture *rhiTexture = ctx->rhi()->newTexture(rhiFormat, pixelSize, sampleCount, rhiFlags);
651
652 if (!rhiTexture->build()) {
653 qWarning() << Q_FUNC_INFO << "creating QRhiTexture failed";
654 delete rhiTexture;
655 return nullptr;
656 }
657 return rhiTexture;
658 }
659
uploadRhiTextureData(SubmissionContext * ctx)660 void RHITexture::uploadRhiTextureData(SubmissionContext *ctx)
661 {
662 QVarLengthArray<QRhiTextureUploadEntry> uploadEntries;
663
664 // Upload all QTexImageData set by the QTextureGenerator
665 if (m_textureData) {
666 const QVector<QTextureImageDataPtr> &imgData = m_textureData->imageData();
667
668 for (const QTextureImageDataPtr &data : imgData) {
669 const int mipLevels = data->mipLevels();
670 Q_ASSERT(mipLevels <= ctx->rhi()->mipLevelsForSize({ data->width(), data->height() }));
671
672 filterLayersAndFaces(*data, [&](QRhiTextureUploadEntry &&entry) {
673 uploadEntries.push_back(std::move(entry));
674 });
675 }
676 }
677
678 // Upload all QTexImageData references by the TextureImages
679 for (int i = 0; i < std::min(m_images.size(), m_imageData.size()); i++) {
680 const QTextureImageDataPtr &imgData = m_imageData.at(i);
681 // Here the bytes in the QTextureImageData contain data for a single
682 // layer, face or mip level, unlike the QTextureGenerator case where
683 // they are in a single blob. Hence QTextureImageData::data() is not suitable.
684 const QByteArray bytes(QTextureImageDataPrivate::get(imgData.get())->m_data);
685 const int layer = m_images[i].layer;
686 const int face = m_images[i].face;
687 filterLayerAndFace(layer, face, [&](int rhiLayer) {
688 uploadEntries.push_back(createUploadEntry(m_images[i].mipLevel, rhiLayer, bytes));
689 });
690 }
691
692 // Free up image data once content has been uploaded
693 // Note: if data functor stores the data, this won't really free anything though
694 m_imageData.clear();
695
696 // Update data from TextureUpdates
697 const QVector<QTextureDataUpdate> textureDataUpdates = std::move(m_pendingTextureDataUpdates);
698 for (const QTextureDataUpdate &update : textureDataUpdates) {
699 const QTextureImageDataPtr imgData = update.data();
700
701 if (!imgData) {
702 qWarning() << Q_FUNC_INFO << "QTextureDataUpdate no QTextureImageData set";
703 continue;
704 }
705
706 // TO DO: There's currently no way to check the depth of an existing QRhiTexture
707 const int xOffset = update.x();
708 const int yOffset = update.y();
709 const int xExtent = xOffset + imgData->width();
710 const int yExtent = yOffset + imgData->height();
711
712 // Check update is compatible with our texture
713 if (xOffset >= m_rhi->pixelSize().width() || yOffset >= m_rhi->pixelSize().height()
714 || xExtent > m_rhi->pixelSize().width() || yExtent > m_rhi->pixelSize().height()) {
715 qWarning() << Q_FUNC_INFO << "QTextureDataUpdate incompatible with texture";
716 continue;
717 }
718
719 const QByteArray bytes = (QTextureImageDataPrivate::get(imgData.get())->m_data);
720 // Here the bytes in the QTextureImageData contain data for a single
721 // layer, face or mip level, unlike the QTextureGenerator case where
722 // they are in a single blob. Hence QTextureImageData::data() is not suitable.
723
724 const int layer = update.layer();
725 const int face = update.face();
726 filterLayerAndFace(layer, face, [&](int rhiLayer) {
727 const QRhiTextureUploadEntry entry = createUploadEntry(
728 update.mipLevel(), rhiLayer, xOffset, yOffset, 0, bytes, imgData);
729 uploadEntries.push_back(entry);
730 });
731 }
732
733 QRhiTextureUploadDescription uploadDescription;
734 uploadDescription.setEntries(uploadEntries.begin(), uploadEntries.end());
735
736 ctx->m_currentUpdates->uploadTexture(m_rhi, uploadDescription);
737 if (m_properties.generateMipMaps)
738 ctx->m_currentUpdates->generateMips(m_rhi);
739 }
740
updateRhiTextureParameters(SubmissionContext * ctx)741 void RHITexture::updateRhiTextureParameters(SubmissionContext *ctx)
742 {
743 const QAbstractTexture::Target actualTarget = m_properties.target;
744 const bool isMultisampledTexture =
745 (actualTarget == QAbstractTexture::Target2DMultisample
746 || actualTarget == QAbstractTexture::Target2DMultisampleArray);
747 // Multisampled textures can only be accessed by texelFetch in shaders
748 // and don't support wrap modes and mig/mag filtes
749 if (isMultisampledTexture)
750 return;
751
752 // TO DO:
753 if (m_rhiSampler) {
754 delete m_rhiSampler;
755 m_rhiSampler = nullptr;
756 }
757
758 const QRhiSampler::Filter magFilter =
759 rhiFilterFromTextureFilter(m_parameters.magnificationFilter);
760 const QRhiSampler::Filter minFilter =
761 rhiFilterFromTextureFilter(m_parameters.minificationFilter);
762 const QRhiSampler::Filter mipMapFilter =
763 rhiMipMapFilterFromTextureFilter(m_parameters.magnificationFilter);
764 const auto wrapMode = rhiWrapModeFromTextureWrapMode(
765 m_parameters.wrapModeX, m_parameters.wrapModeY, m_parameters.wrapModeZ);
766 const QRhiSampler::CompareOp compareOp =
767 rhiCompareOpFromTextureCompareOp(m_parameters.comparisonFunction);
768 m_rhiSampler = ctx->rhi()->newSampler(magFilter, minFilter, mipMapFilter, std::get<0>(wrapMode),
769 std::get<1>(wrapMode), std::get<2>(wrapMode));
770
771 m_rhiSampler->setTextureCompareOp(compareOp);
772
773 if (!m_rhiSampler->build()) {
774 qDebug("Could not build RHI texture sampler");
775 }
776 }
777
introspectPropertiesFromSharedTextureId()778 void RHITexture::introspectPropertiesFromSharedTextureId()
779 {
780 // // We know that the context is active when this function is called
781 // QOpenGLContext *ctx = QOpenGLContext::currentContext();
782 // if (!ctx) {
783 // qWarning() << Q_FUNC_INFO << "requires an OpenGL context";
784 // return;
785 // }
786 // QOpenGLFunctions *gl = ctx->functions();
787
788 // // If the user has set the target format himself, we won't try to deduce it
789 // if (m_properties.target != QAbstractTexture::TargetAutomatic)
790 // return;
791
792 // const QAbstractTexture::Target targets[] = {
793 // QAbstractTexture::Target2D,
794 // QAbstractTexture::TargetCubeMap,
795 //#ifndef QT_OPENGL_ES_2
796 // QAbstractTexture::Target1D,
797 // QAbstractTexture::Target1DArray,
798 // QAbstractTexture::Target3D,
799 // QAbstractTexture::Target2DArray,
800 // QAbstractTexture::TargetCubeMapArray,
801 // QAbstractTexture::Target2DMultisample,
802 // QAbstractTexture::Target2DMultisampleArray,
803 // QAbstractTexture::TargetRectangle,
804 // QAbstractTexture::TargetBuffer,
805 //#endif
806 // };
807
808 //#ifndef QT_OPENGL_ES_2
809 // // Try to find texture target with GL 4.5 functions
810 // const QPair<int, int> ctxGLVersion = ctx->format().version();
811 // if (ctxGLVersion.first > 4 || (ctxGLVersion.first == 4 && ctxGLVersion.second >= 5)) {
812 // // Only for GL 4.5+
813 //#ifdef GL_TEXTURE_TARGET
814 // QOpenGLFunctions_4_5_Core *gl5 = ctx->versionFunctions<QOpenGLFunctions_4_5_Core>();
815 // if (gl5 != nullptr)
816 // gl5->glGetTextureParameteriv(m_sharedTextureId, GL_TEXTURE_TARGET,
817 // reinterpret_cast<int *>(&m_properties.target));
818 //#endif
819 // }
820 //#endif
821
822 // // If GL 4.5 function unavailable or not working, try a slower way
823 // if (m_properties.target == QAbstractTexture::TargetAutomatic) {
824 // // // OpenGL offers no proper way of querying for the target of a texture given its
825 // id gl->glActiveTexture(GL_TEXTURE0);
826
827 // const GLenum targetBindings[] = {
828 // GL_TEXTURE_BINDING_2D,
829 // GL_TEXTURE_BINDING_CUBE_MAP,
830 //#ifndef QT_OPENGL_ES_2
831 // GL_TEXTURE_BINDING_1D,
832 // GL_TEXTURE_BINDING_1D_ARRAY,
833 // GL_TEXTURE_BINDING_3D,
834 // GL_TEXTURE_BINDING_2D_ARRAY,
835 // GL_TEXTURE_BINDING_CUBE_MAP_ARRAY,
836 // GL_TEXTURE_BINDING_2D_MULTISAMPLE,
837 // GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY,
838 // GL_TEXTURE_BINDING_RECTANGLE,
839 // GL_TEXTURE_BINDING_BUFFER
840 //#endif
841 // };
842
843 // Q_ASSERT(sizeof(targetBindings) / sizeof(targetBindings[0] == sizeof(targets) /
844 // sizeof(targets[0])));
845
846 // for (uint i = 0; i < sizeof(targetBindings) / sizeof(targetBindings[0]); ++i) {
847 // const int target = targets[i];
848 // gl->glBindTexture(target, m_sharedTextureId);
849 // int boundId = 0;
850 // gl->glGetIntegerv(targetBindings[i], &boundId);
851 // gl->glBindTexture(target, 0);
852 // if (boundId == m_sharedTextureId) {
853 // m_properties.target = static_cast<QAbstractTexture::Target>(target);
854 // break;
855 // }
856 // }
857 // }
858
859 // // Return early if we weren't able to find texture target
860 // if (std::find(std::begin(targets), std::end(targets), m_properties.target) ==
861 // std::end(targets)) {
862 // qWarning() << "Unable to determine texture target for shared GL texture";
863 // return;
864 // }
865
866 // // Bind texture once we know its target
867 // gl->glBindTexture(m_properties.target, m_sharedTextureId);
868
869 // // TO DO: Improve by using glGetTextureParameters when available which
870 // // support direct state access
871 //#ifndef GL_TEXTURE_MAX_LEVEL
872 //#define GL_TEXTURE_MAX_LEVEL 0x813D
873 //#endif
874
875 //#ifndef GL_TEXTURE_WRAP_R
876 //#define GL_TEXTURE_WRAP_R 0x8072
877 //#endif
878
879 // gl->glGetTexParameteriv(int(m_properties.target), GL_TEXTURE_MAX_LEVEL,
880 // reinterpret_cast<int *>(&m_properties.mipLevels));
881 // gl->glGetTexParameteriv(int(m_properties.target), GL_TEXTURE_MIN_FILTER,
882 // reinterpret_cast<int *>(&m_parameters.minificationFilter));
883 // gl->glGetTexParameteriv(int(m_properties.target), GL_TEXTURE_MAG_FILTER,
884 // reinterpret_cast<int *>(&m_parameters.magnificationFilter));
885 // gl->glGetTexParameteriv(int(m_properties.target), GL_TEXTURE_WRAP_R, reinterpret_cast<int
886 // *>(&m_parameters.wrapModeX)); gl->glGetTexParameteriv(int(m_properties.target),
887 // GL_TEXTURE_WRAP_S, reinterpret_cast<int *>(&m_parameters.wrapModeY));
888 // gl->glGetTexParameteriv(int(m_properties.target), GL_TEXTURE_WRAP_T, reinterpret_cast<int
889 // *>(&m_parameters.wrapModeZ));
890
891 //#ifndef QT_OPENGL_ES_2
892 // // Try to retrieve dimensions (not available on ES 2.0)
893 // if (!ctx->isOpenGLES()) {
894 // QOpenGLFunctions_3_1 *gl3 = ctx->versionFunctions<QOpenGLFunctions_3_1>();
895 // if (!gl3) {
896 // qWarning() << "Failed to retrieve shared texture dimensions";
897 // return;
898 // }
899
900 // gl3->glGetTexLevelParameteriv(int(m_properties.target), 0, GL_TEXTURE_WIDTH,
901 // reinterpret_cast<int *>(&m_properties.width));
902 // gl3->glGetTexLevelParameteriv(int(m_properties.target), 0, GL_TEXTURE_HEIGHT,
903 // reinterpret_cast<int *>(&m_properties.height));
904 // gl3->glGetTexLevelParameteriv(int(m_properties.target), 0, GL_TEXTURE_DEPTH,
905 // reinterpret_cast<int *>(&m_properties.depth));
906 // gl3->glGetTexLevelParameteriv(int(m_properties.target), 0, GL_TEXTURE_INTERNAL_FORMAT,
907 // reinterpret_cast<int *>(&m_properties.format));
908 // }
909 //#endif
910
911 // gl->glBindTexture(m_properties.target, 0);
912 }
913
914 } // namespace Rhi
915 } // namespace Render
916 } // namespace Qt3DRender
917
918 QT_END_NAMESPACE
919