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

source code of qt3d/src/plugins/renderers/rhi/textures/texture.cpp