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 | |
20 | QT_BEGIN_NAMESPACE |
21 | |
22 | namespace Qt3DRender { |
23 | namespace Render { |
24 | namespace Rhi { |
25 | |
26 | namespace { |
27 | |
28 | bool 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 | |
40 | QRhiTexture::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 | |
91 | QRhiSampler::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 | |
107 | QRhiSampler::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 | |
126 | std::tuple<QRhiSampler::AddressMode, QRhiSampler::AddressMode, QRhiSampler::AddressMode> |
127 | rhiWrapModeFromTextureWrapMode(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 | |
147 | QRhiSampler::CompareOp |
148 | rhiCompareOpFromTextureCompareOp(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 |
174 | QRhiTextureUploadEntry 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 |
182 | template<typename F> |
183 | void 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 |
210 | template<typename F> |
211 | void 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 |
225 | QRhiTextureUploadEntry 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 | |
239 | RHITexture::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 | |
252 | RHITexture::~RHITexture() { } |
253 | |
254 | // Must be called from RenderThread with active GL context |
255 | void 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 | |
283 | bool 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 | |
326 | void 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 |
369 | RHITexture::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 | |
479 | RenderBuffer *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 |
520 | void RHITexture::cleanup() |
521 | { |
522 | destroy(); |
523 | } |
524 | |
525 | void RHITexture::setParameters(const TextureParameters ¶ms) |
526 | { |
527 | if (m_parameters != params) { |
528 | m_parameters = params; |
529 | setDirtyFlag(flag: Parameters); |
530 | } |
531 | } |
532 | |
533 | void RHITexture::setProperties(const TextureProperties &props) |
534 | { |
535 | if (m_properties != props) { |
536 | m_properties = props; |
537 | setDirtyFlag(flag: Properties); |
538 | } |
539 | } |
540 | |
541 | void 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 | |
560 | void RHITexture::setGenerator(const QTextureGeneratorPtr &generator) |
561 | { |
562 | m_textureData.reset(); |
563 | m_dataFunctor = generator; |
564 | m_pendingDataFunctor = nullptr; |
565 | requestUpload(); |
566 | } |
567 | |
568 | void RHITexture::setSharedTextureId(int textureId) |
569 | { |
570 | if (m_sharedTextureId != textureId) { |
571 | m_sharedTextureId = textureId; |
572 | setDirtyFlag(flag: SharedTextureId); |
573 | } |
574 | } |
575 | |
576 | void 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 |
585 | QRhiTexture *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 | |
649 | void 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 | |
737 | void 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 | |
782 | void 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 | |
922 | QT_END_NAMESPACE |
923 | |