Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qssglayerrenderdata.cpp
Go to the documentation of this file.
1// Copyright (C) 2008-2012 NVIDIA Corporation.
2// Copyright (C) 2022 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
4// Qt-Security score:significant reason:default
5
6
8
9#include <QtQuick3DRuntimeRender/private/qssgrenderer_p.h>
10#include <QtQuick3DRuntimeRender/private/qssgrenderlight_p.h>
11#include <QtQuick3DRuntimeRender/private/qssgrhicustommaterialsystem_p.h>
12#include <QtQuick3DRuntimeRender/private/qssgrhiquadrenderer_p.h>
13#include <QtQuick3DRuntimeRender/private/qssgrhiparticles_p.h>
14#include <QtQuick3DRuntimeRender/private/qssgrenderlayer_p.h>
15#include <QtQuick3DRuntimeRender/private/qssgrendereffect_p.h>
16#include <QtQuick3DRuntimeRender/private/qssgrendercamera_p.h>
17#include <QtQuick3DRuntimeRender/private/qssgrenderskeleton_p.h>
18#include <QtQuick3DRuntimeRender/private/qssgrenderjoint_p.h>
19#include <QtQuick3DRuntimeRender/private/qssgrendermorphtarget_p.h>
20#include <QtQuick3DRuntimeRender/private/qssgrenderparticles_p.h>
21#include "../graphobjects/qssgrenderroot_p.h"
22#include "../qssgrendercontextcore.h"
23#include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h>
24#include <QtQuick3DRuntimeRender/private/qssgrendershadercache_p.h>
25#include <QtQuick3DRuntimeRender/private/qssgperframeallocator_p.h>
26#include <QtQuick3DRuntimeRender/private/qssgruntimerenderlogging_p.h>
27#include <QtQuick3DRuntimeRender/private/qssglightmapper_p.h>
28#include <QtQuick3DRuntimeRender/private/qssgdebugdrawsystem_p.h>
29#include <QtQuick3DRuntimeRender/private/qssgshadermaterialadapter_p.h>
30
31#include <QtQuick3DUtils/private/qssgutils_p.h>
32#include <QtQuick3DUtils/private/qssgassert_p.h>
33
34#include <QtQuick/private/qsgtexture_p.h>
35#include <QtQuick/private/qsgrenderer_p.h>
36
37#include <array>
38
40#include "rendererimpl/qssgrenderhelpers_p.h"
41
43
44Q_STATIC_LOGGING_CATEGORY(lcQuick3DRender, "qt.quick3d.render");
45
46#define POS4BONETRANS(x) (sizeof(float) * 16 * (x) * 2)
47#define POS4BONENORM(x) (sizeof(float) * 16 * ((x) * 2 + 1))
48#define BONEDATASIZE4ID(x) POS4BONETRANS(x + 1)
49
50static bool checkParticleSupport(QRhi *rhi)
51{
52 QSSG_ASSERT(rhi, return false);
53
54 bool ret = true;
55 const bool supportRgba32f = rhi->isTextureFormatSupported(QRhiTexture::RGBA32F);
56 const bool supportRgba16f = rhi->isTextureFormatSupported(QRhiTexture::RGBA16F);
57 if (!supportRgba32f && !supportRgba16f) {
58 static bool warningShown = false;
59 if (!warningShown) {
60 qWarning () << "Particles not supported due to missing RGBA32F and RGBA16F texture format support";
61 warningShown = true;
62 }
63 ret = false;
64 }
65
66 return ret;
67}
68
70{
78
80 {
81 lhs.modelCount += rhs.modelCount;
82 lhs.particlesCount += rhs.particlesCount;
83 lhs.item2DCount += rhs.item2DCount;
84 lhs.cameraCount += rhs.cameraCount;
85 lhs.lightCount += rhs.lightCount;
86 lhs.reflectionProbeCount += rhs.reflectionProbeCount;
87 lhs.otherCount += rhs.otherCount;
88 return lhs;
89 }
90
91 friend QDebug operator<<(QDebug dbg, const LayerNodeStatResult &stat)
92 {
93 dbg.nospace() << "LayerNodeStatResult(modelCount: " << stat.modelCount
94 << ", particlesCount: " << stat.particlesCount
95 << ", item2DCount: " << stat.item2DCount
96 << ", cameraCount: " << stat.cameraCount
97 << ", lightCount: " << stat.lightCount
98 << ", reflectionProbeCount: " << stat.reflectionProbeCount
99 << ", otherCount: " << stat.otherCount
100 << ")";
101 return dbg.space();
102 }
103};
104
105static LayerNodeStatResult statLayerNodes(const QSSGLayerRenderData::LayerNodes &layerNodes, quint32 layerMask) {
106
108
109 for (auto *node : layerNodes) {
110 if (node->getGlobalState(QSSGRenderNode::GlobalState::Active)) {
111 if (!node->tag.isSet(layerMask))
112 ++stat.otherCount;
113 else if (node->type == QSSGRenderGraphObject::Type::Model)
114 ++stat.modelCount;
115 else if (node->type == QSSGRenderGraphObject::Type::Particles)
116 ++stat.particlesCount;
117 else if (node->type == QSSGRenderGraphObject::Type::Item2D)
118 ++stat.item2DCount;
119 else if (node->type == QSSGRenderGraphObject::Type::ReflectionProbe)
120 ++stat.reflectionProbeCount;
121 else if (QSSGRenderGraphObject::isCamera(node->type))
122 ++stat.cameraCount;
123 else if (QSSGRenderGraphObject::isLight(node->type))
124 ++stat.lightCount;
125 else
126 ++stat.otherCount;
127 }
128 }
129
130 return stat;
131}
132
133// These are meant to be pixel offsets, so you need to divide them by the width/height
134// of the layer respectively.
136 QVector2D(-0.170840f, -0.553840f), // 1x
137 QVector2D(0.162960f, -0.319340f), // 2x
138 QVector2D(0.360260f, -0.245840f), // 3x
139 QVector2D(-0.561340f, -0.149540f), // 4x
140 QVector2D(0.249460f, 0.453460f), // 5x
141 QVector2D(-0.336340f, 0.378260f), // 6x
142 QVector2D(0.340000f, 0.166260f), // 7x
143 QVector2D(0.235760f, 0.527760f), // 8x
144};
145
146qsizetype QSSGLayerRenderData::frustumCullingInline(const QSSGClippingFrustum &clipFrustum, QSSGRenderableObjectList &renderables)
147{
148 const qint32 end = renderables.size();
149 qint32 front = 0;
150 qint32 back = end - 1;
151
152 while (front <= back) {
153 bool cull = true;
154 const QSSGRenderableObject &obj = *renderables.at(front).obj;
155 if (obj.isInstanced) {
156 // when instancing is enabled we can use the shadowmap bounds if they are set, otherwise we disable culling
157 if (obj.globalBoundsInstancing.isEmpty() || clipFrustum.intersectsWith(obj.globalBoundsInstancing)) {
158 cull = false;
159 }
160 } else if (clipFrustum.intersectsWith(obj.globalBounds)) {
161 cull = false;
162 }
163
164 if (cull)
165 renderables.swapItemsAt(front, back--);
166 else
167 ++front;
168 }
169
170 return back + 1;
171}
172
173qsizetype QSSGLayerRenderData::filterLayerMaskInline(quint32 layerMask, QSSGRenderableObjectList &renderables)
174{
175 const qint32 end = renderables.size();
176 qint32 front = 0;
177 qint32 back = end - 1;
178
179 while (front <= back) {
180 const auto &b = renderables.at(front).tag;
181 if (layerMask & b.value())
182 ++front;
183 else
184 renderables.swapItemsAt(front, back--);
185 }
186
187 return back + 1;
188}
189
190[[nodiscard]] constexpr static inline bool nearestToFurthestCompare(const QSSGRenderableObjectHandle &lhs, const QSSGRenderableObjectHandle &rhs) noexcept
191{
193}
194
195[[nodiscard]] constexpr static inline bool furthestToNearestCompare(const QSSGRenderableObjectHandle &lhs, const QSSGRenderableObjectHandle &rhs) noexcept
196{
198}
199
200static void collectBoneTransforms(const QSSGLayerRenderData &renderData, QSSGRenderNode *node, QSSGRenderSkeleton *skeletonNode, const QVector<QMatrix4x4> &poses)
201{
202 if (node->type == QSSGRenderGraphObject::Type::Joint) {
203 QSSGRenderJoint *jointNode = static_cast<QSSGRenderJoint *>(node);
204 Q_ASSERT(!jointNode->isDirty(QSSGRenderNode::DirtyFlag::GlobalValuesDirty));
205 QMatrix4x4 globalTrans = renderData.getGlobalTransform(*jointNode);
206 // if user doesn't give the inverseBindPose, identity matrices are used.
207 if (poses.size() > jointNode->index)
208 globalTrans *= poses[jointNode->index];
209 memcpy(skeletonNode->boneData.data() + POS4BONETRANS(jointNode->index),
210 reinterpret_cast<const void *>(globalTrans.constData()),
211 sizeof(float) * 16);
212 // only upper 3x3 is meaningful
213 memcpy(skeletonNode->boneData.data() + POS4BONENORM(jointNode->index),
214 reinterpret_cast<const void *>(QMatrix4x4(globalTrans.normalMatrix()).constData()),
215 sizeof(float) * 11);
216 } else {
217 skeletonNode->containsNonJointNodes = true;
218 }
219 for (auto &child : node->children)
220 collectBoneTransforms(renderData, &child, skeletonNode, poses);
221}
222
223static bool hasDirtyNonJointNodes(QSSGRenderNode *node, bool &hasChildJoints)
224{
225 if (!node)
226 return false;
227 // we might be non-joint dirty node, but if we do not have child joints we need to return false
228 // Note! The frontend clears TransformDirty. Use dirty instead.
229 bool dirtyNonJoint = ((node->type != QSSGRenderGraphObject::Type::Joint)
230 && node->isDirty());
231
232 // Tell our parent we are joint
233 if (node->type == QSSGRenderGraphObject::Type::Joint)
234 hasChildJoints = true;
235 bool nodeHasChildJoints = false;
236 for (auto &child : node->children) {
237 bool ret = hasDirtyNonJointNodes(&child, nodeHasChildJoints);
238 // return if we have child joints and non-joint dirty nodes, else check other children
239 hasChildJoints |= nodeHasChildJoints;
240 if (ret && nodeHasChildJoints)
241 return true;
242 }
243 // return true if we have child joints and we are dirty non-joint
244 hasChildJoints |= nodeHasChildJoints;
245 return dirtyNonJoint && nodeHasChildJoints;
246}
247
248#define MAX_MORPH_TARGET 8
249#define MAX_MORPH_TARGET_INDEX_SUPPORTS_NORMALS 3
250#define MAX_MORPH_TARGET_INDEX_SUPPORTS_TANGENTS 1
251
253 : firstImage(nullptr), opacity(1.0f), materialKey(inKey), dirty(false)
254{
255}
256
257QSSGRenderCameraData QSSGLayerRenderData::getCameraDataImpl(const QSSGRenderCamera *camera) const
258{
259 QSSGRenderCameraData ret;
260 if (camera) {
261 // Calculate viewProjection and clippingFrustum for Render Camera
262 QMatrix4x4 viewProjection(Qt::Uninitialized);
263 QMatrix4x4 cameraGlobalTransform = getGlobalTransform(*camera);
264 camera->calculateViewProjectionMatrix(cameraGlobalTransform, viewProjection);
265 std::optional<QSSGClippingFrustum> clippingFrustum;
266 const QMatrix4x4 camGlobalTransform = getGlobalTransform(*camera);
267 const QVector3D camGlobalPos = QSSGRenderNode::getGlobalPos(camGlobalTransform);
268 if (camera->enableFrustumClipping) {
269 QSSGClipPlane nearPlane;
270 QMatrix3x3 theUpper33(camGlobalTransform.normalMatrix());
271 QVector3D dir(QSSGUtils::mat33::transform(theUpper33, QVector3D(0, 0, -1)));
272 dir.normalize();
273 nearPlane.normal = dir;
274 QVector3D theGlobalPos = camGlobalPos + camera->clipPlanes.clipNear() * dir;
275 nearPlane.d = -(QVector3D::dotProduct(dir, theGlobalPos));
276 // the near plane's bbox edges are calculated in the clipping frustum's
277 // constructor.
278 clippingFrustum = QSSGClippingFrustum{viewProjection, nearPlane};
279 }
280 QMatrix4x4 globalTransform = getGlobalTransform(*camera);
281 ret = { viewProjection, clippingFrustum, camera->getScalingCorrectDirection(globalTransform), camGlobalPos };
282 }
283
284 return ret;
285}
286
287// Returns the cached data for the active render camera(s) (if any)
288const QSSGRenderCameraDataList &QSSGLayerRenderData::getCachedCameraDatas()
289{
290 ensureCachedCameraDatas();
291 return *renderedCameraData;
292}
293
294void QSSGLayerRenderData::ensureCachedCameraDatas()
295{
296 if (renderedCameraData.has_value())
297 return;
298
299 QSSGRenderCameraDataList cameraData;
300 for (QSSGRenderCamera *cam : std::as_const(renderedCameras))
301 cameraData.append(getCameraDataImpl(cam));
302 renderedCameraData = std::move(cameraData);
303}
304
305[[nodiscard]] static inline float getCameraDistanceSq(const QSSGRenderableObject &obj,
306 const QSSGRenderCameraData &camera) noexcept
307{
308 const QVector3D difference = obj.worldCenterPoint - camera.position;
309 return QVector3D::dotProduct(difference, camera.direction) + obj.depthBiasSq;
310}
311
312// Per-frame cache of renderable objects post-sort.
313const QVector<QSSGRenderableObjectHandle> &QSSGLayerRenderData::getSortedOpaqueRenderableObjects(const QSSGRenderCamera &camera, size_t index, quint32 layerMask)
314{
315 index = index * size_t(index < opaqueObjectStore.size());
316 auto &sortedOpaqueObjects = sortedOpaqueObjectCache[index][{&camera, layerMask}];
317 if (!sortedOpaqueObjects.empty())
318 return sortedOpaqueObjects;
319
320 if (layer.layerFlags.testFlag(QSSGRenderLayer::LayerFlag::EnableDepthTest))
321 sortedOpaqueObjects = std::as_const(opaqueObjectStore)[index];
322
323 if (camera.layerMask != layerMask) {
324 const auto filteredObjects = filterLayerMaskInline(layerMask, sortedOpaqueObjects);
325 sortedOpaqueObjects.resize(filteredObjects);
326 }
327
328 const auto &clippingFrustum = getCameraRenderData(&camera).clippingFrustum;
329 if (clippingFrustum.has_value()) { // Frustum culling
330 const auto visibleObjects = QSSGLayerRenderData::frustumCullingInline(clippingFrustum.value(), sortedOpaqueObjects);
331 sortedOpaqueObjects.resize(visibleObjects);
332 }
333
334 // Render nearest to furthest objects
335 std::sort(sortedOpaqueObjects.begin(), sortedOpaqueObjects.end(), nearestToFurthestCompare);
336
337 return sortedOpaqueObjects;
338}
339
340// If layer depth test is false, this may also contain opaque objects.
341const QVector<QSSGRenderableObjectHandle> &QSSGLayerRenderData::getSortedTransparentRenderableObjects(const QSSGRenderCamera &camera, size_t index, quint32 layerMask)
342{
343 index = index * size_t(index < transparentObjectStore.size());
344 auto &sortedTransparentObjects = sortedTransparentObjectCache[index][{&camera, layerMask}];
345
346 if (!sortedTransparentObjects.empty())
347 return sortedTransparentObjects;
348
349 sortedTransparentObjects = std::as_const(transparentObjectStore)[index];
350
351 if (!layer.layerFlags.testFlag(QSSGRenderLayer::LayerFlag::EnableDepthTest)) {
352 const auto &opaqueObjects = std::as_const(opaqueObjectStore)[index];
353 sortedTransparentObjects.append(opaqueObjects);
354 }
355
356 if (camera.layerMask != layerMask) {
357 const auto filteredObjects = filterLayerMaskInline(layerMask, sortedTransparentObjects);
358 sortedTransparentObjects.resize(filteredObjects);
359 }
360
361 const auto &clippingFrustum = getCameraRenderData(&camera).clippingFrustum;
362 if (clippingFrustum.has_value()) { // Frustum culling
363 const auto visibleObjects = QSSGLayerRenderData::frustumCullingInline(clippingFrustum.value(), sortedTransparentObjects);
364 sortedTransparentObjects.resize(visibleObjects);
365 }
366
367 // render furthest to nearest.
368 std::sort(sortedTransparentObjects.begin(), sortedTransparentObjects.end(), furthestToNearestCompare);
369
370 return sortedTransparentObjects;
371}
372
373const QVector<QSSGRenderableObjectHandle> &QSSGLayerRenderData::getSortedScreenTextureRenderableObjects(const QSSGRenderCamera &camera, size_t index)
374{
375 index = index * size_t(index < screenTextureObjectStore.size());
376 const auto &screenTextureObjects = std::as_const(screenTextureObjectStore)[index];
377 auto &renderedScreenTextureObjects = sortedScreenTextureObjectCache[index][{&camera, camera.layerMask}];
378
379 if (!renderedScreenTextureObjects.empty())
380 return renderedScreenTextureObjects;
381 renderedScreenTextureObjects = screenTextureObjects;
382 if (!renderedScreenTextureObjects.empty()) {
383 // render furthest to nearest.
384 std::sort(renderedScreenTextureObjects.begin(), renderedScreenTextureObjects.end(), furthestToNearestCompare);
385 }
386 return renderedScreenTextureObjects;
387}
388
389const QVector<QSSGBakedLightingModel> &QSSGLayerRenderData::getSortedBakedLightingModels()
390{
391 if (!renderedBakedLightingModels.empty() || renderedCameras.isEmpty())
392 return renderedBakedLightingModels;
393 if (layer.layerFlags.testFlag(QSSGRenderLayer::LayerFlag::EnableDepthTest) && !bakedLightingModels.empty()) {
394 renderedBakedLightingModels = bakedLightingModels;
395 for (QSSGBakedLightingModel &lm : renderedBakedLightingModels) {
396 // sort nearest to furthest (front to back)
397 std::sort(lm.renderables.begin(), lm.renderables.end(), nearestToFurthestCompare);
398 }
399 }
400 return renderedBakedLightingModels;
401}
402
403const QSSGLayerRenderData::RenderableItem2DEntries &QSSGLayerRenderData::getRenderableItem2Ds()
404{
405 if (!renderedItem2Ds.isEmpty() || renderedCameras.isEmpty())
406 return renderedItem2Ds;
407
408 // Maintain QML item order
409 renderedItem2Ds = { std::make_reverse_iterator(item2DsView.end()),
410 std::make_reverse_iterator(item2DsView.begin()) };
411
412 if (!renderedItem2Ds.isEmpty()) {
413 const QSSGRenderCameraDataList &cameraDatas(getCachedCameraDatas());
414 // with multiview this means using the left eye camera
415 const QSSGRenderCameraData &cameraDirectionAndPosition(cameraDatas[0]);
416 const QVector3D &cameraDirection = cameraDirectionAndPosition.direction;
417 const QVector3D &cameraPosition = cameraDirectionAndPosition.position;
418
419 const auto isItemNodeDistanceGreatThan = [this, cameraDirection, cameraPosition]
420 (const QSSGRenderItem2D *lhs, const QSSGRenderItem2D *rhs) {
421 if (!lhs->parent || !rhs->parent)
422 return false;
423
424 auto lhsGlobalTransform = getGlobalTransform(*lhs->parent);
425 auto rhsGlobalTransform = getGlobalTransform(*rhs->parent);
426
427 const QVector3D lhsDifference = QSSGRenderNode::getGlobalPos(lhsGlobalTransform) - cameraPosition;
428 const float lhsCameraDistanceSq = QVector3D::dotProduct(lhsDifference, cameraDirection);
429 const QVector3D rhsDifference = QSSGRenderNode::getGlobalPos(rhsGlobalTransform) - cameraPosition;
430 const float rhsCameraDistanceSq = QVector3D::dotProduct(rhsDifference, cameraDirection);
431 return lhsCameraDistanceSq > rhsCameraDistanceSq;
432 };
433
434 // Render furthest to nearest items (parent nodes).
435 std::stable_sort(renderedItem2Ds.begin(), renderedItem2Ds.end(), isItemNodeDistanceGreatThan);
436 }
437
438 return renderedItem2Ds;
439}
440
441// Depth Write List
442void QSSGLayerRenderData::updateSortedDepthObjectsListImp(const QSSGRenderCamera &camera, size_t index)
443{
444 auto &depthWriteObjects = sortedDepthWriteCache[index][{&camera, camera.layerMask}];
445 auto &depthPrepassObjects = sortedOpaqueDepthPrepassCache[index][{&camera, camera.layerMask}];
446
447 if (!depthWriteObjects.isEmpty() || !depthPrepassObjects.isEmpty())
448 return;
449
450 if (layer.layerFlags.testFlag(QSSGRenderLayer::LayerFlag::EnableDepthTest)) {
451 if (hasDepthWriteObjects || (depthPrepassObjectsState & DepthPrepassObjectStateT(DepthPrepassObject::Opaque)) != 0) {
452 const auto &sortedOpaqueObjects = getSortedOpaqueRenderableObjects(camera, index); // front to back
453 for (const auto &opaqueObject : sortedOpaqueObjects) {
454 const auto depthMode = opaqueObject.obj->depthWriteMode;
455 if (depthMode == QSSGDepthDrawMode::Always || depthMode == QSSGDepthDrawMode::OpaqueOnly)
456 depthWriteObjects.append(opaqueObject);
457 else if (depthMode == QSSGDepthDrawMode::OpaquePrePass)
458 depthPrepassObjects.append(opaqueObject);
459 }
460 }
461 if (hasDepthWriteObjects || (depthPrepassObjectsState & DepthPrepassObjectStateT(DepthPrepassObject::Transparent)) != 0) {
462 const auto &sortedTransparentObjects = getSortedTransparentRenderableObjects(camera, index); // back to front
463 for (const auto &transparentObject : sortedTransparentObjects) {
464 const auto depthMode = transparentObject.obj->depthWriteMode;
465 if (depthMode == QSSGDepthDrawMode::Always)
466 depthWriteObjects.append(transparentObject);
467 else if (depthMode == QSSGDepthDrawMode::OpaquePrePass)
468 depthPrepassObjects.append(transparentObject);
469 }
470 }
471 if (hasDepthWriteObjects || (depthPrepassObjectsState & DepthPrepassObjectStateT(DepthPrepassObject::ScreenTexture)) != 0) {
472 const auto &sortedScreenTextureObjects = getSortedScreenTextureRenderableObjects(camera, index); // back to front
473 for (const auto &screenTextureObject : sortedScreenTextureObjects) {
474 const auto depthMode = screenTextureObject.obj->depthWriteMode;
475 if (depthMode == QSSGDepthDrawMode::Always || depthMode == QSSGDepthDrawMode::OpaqueOnly)
476 depthWriteObjects.append(screenTextureObject);
477 else if (depthMode == QSSGDepthDrawMode::OpaquePrePass)
478 depthPrepassObjects.append(screenTextureObject);
479 }
480 }
481 }
482}
483
484const std::unique_ptr<QSSGPerFrameAllocator> &QSSGLayerRenderData::perFrameAllocator(QSSGRenderContextInterface &ctx)
485{
486 return ctx.perFrameAllocator();
487}
488
489void QSSGLayerRenderData::saveRenderState(const QSSGRenderer &renderer)
490{
491 QSSG_CHECK(!savedRenderState.has_value());
492 savedRenderState = std::make_optional<SavedRenderState>({ renderer.m_viewport, renderer.m_scissorRect, renderer.m_dpr });
493}
494
495void QSSGLayerRenderData::restoreRenderState(QSSGRenderer &renderer)
496{
497 QSSG_ASSERT(savedRenderState.has_value(), return);
498
499 renderer.m_viewport = savedRenderState->viewport;
500 renderer.m_scissorRect = savedRenderState->scissorRect;
501 renderer.m_dpr = savedRenderState->dpr;
502 savedRenderState.reset();
503}
504
505static constexpr quint16 PREP_CTX_INDEX_MASK = 0xffff;
506static constexpr QSSGPrepContextId createPrepId(size_t index, quint32 frame) { return QSSGPrepContextId { ((quint64(frame) << 32) | index ) * quint64(index <= std::numeric_limits<quint16>::max()) }; }
507static constexpr size_t getPrepContextIndex(QSSGPrepContextId id) { return (static_cast<quint64>(id) & PREP_CTX_INDEX_MASK); }
508static constexpr bool verifyPrepContext(QSSGPrepContextId id, const QSSGRenderer &renderer) { return (getPrepContextIndex(id) > 0) && ((static_cast<quint64>(id) >> 32) == renderer.frameCount()); }
509
510QSSGPrepContextId QSSGLayerRenderData::getOrCreateExtensionContext(const QSSGRenderExtension &ext, QSSGRenderCamera *camera, quint32 slot)
511{
512 const auto frame = renderer->frameCount();
513 const auto index = extContexts.size();
514 // Sanity check... Shouldn't get anywhere close to the max in real world usage (unless somethings broken).
515 QSSG_ASSERT_X(index < PREP_CTX_INDEX_MASK - 1, "Reached maximum entries!", return QSSGPrepContextId::Invalid);
516 auto it = std::find_if(extContexts.cbegin(), extContexts.cend(), [&ext, slot](const ExtensionContext &e){ return (e.owner == &ext) && (e.slot == slot); });
517 if (it == extContexts.cend()) {
518 extContexts.push_back(ExtensionContext{ ext, camera, index, slot });
519 it = extContexts.cbegin() + index;
520 renderableModelStore.emplace_back();
521 modelContextStore.emplace_back();
522 renderableObjectStore.emplace_back();
523 screenTextureObjectStore.emplace_back();
524 opaqueObjectStore.emplace_back();
525 transparentObjectStore.emplace_back();
526 sortedOpaqueObjectCache.emplace_back();
527 sortedTransparentObjectCache.emplace_back();
528 sortedScreenTextureObjectCache.emplace_back();
529 sortedOpaqueDepthPrepassCache.emplace_back();
530 sortedDepthWriteCache.emplace_back();
531 QSSG_ASSERT(renderableModelStore.size() == extContexts.size(), renderableModelStore.resize(extContexts.size()));
532 QSSG_ASSERT(modelContextStore.size() == extContexts.size(), modelContextStore.resize(extContexts.size()));
533 QSSG_ASSERT(renderableObjectStore.size() == extContexts.size(), renderableObjectStore.resize(extContexts.size()));
534 QSSG_ASSERT(screenTextureObjectStore.size() == extContexts.size(), screenTextureObjectStore.resize(extContexts.size()));
535 QSSG_ASSERT(opaqueObjectStore.size() == extContexts.size(), opaqueObjectStore.resize(extContexts.size()));
536 QSSG_ASSERT(transparentObjectStore.size() == extContexts.size(), transparentObjectStore.resize(extContexts.size()));
537 QSSG_ASSERT(sortedOpaqueObjectCache.size() == extContexts.size(), sortedOpaqueObjectCache.resize(extContexts.size()));
538 QSSG_ASSERT(sortedTransparentObjectCache.size() == extContexts.size(), sortedTransparentObjectCache.resize(extContexts.size()));
539 QSSG_ASSERT(sortedScreenTextureObjectCache.size() == extContexts.size(), sortedScreenTextureObjectCache.resize(extContexts.size()));
540 QSSG_ASSERT(sortedOpaqueDepthPrepassCache.size() == extContexts.size(), sortedOpaqueDepthPrepassCache.resize(extContexts.size()));
541 QSSG_ASSERT(sortedDepthWriteCache.size() == extContexts.size(), sortedDepthWriteCache.resize(extContexts.size()));
542 }
543
544 return createPrepId(it->index, frame);
545}
546
547static void createRenderablesHelper(QSSGLayerRenderData &layer, const QSSGRenderNode::ChildList &children, QSSGLayerRenderData::RenderableNodeEntries &renderables, QSSGRenderHelpers::CreateFlags createFlags)
548{
549 const bool steal = ((createFlags & QSSGRenderHelpers::CreateFlag::Steal) != 0);
550 for (auto &chld : children) {
551 if (chld.type == QSSGRenderGraphObject::Type::Model) {
552 const auto &renderModel = static_cast<const QSSGRenderModel &>(chld);
553 auto &renderableModels = layer.renderableModels;
554 if (auto it = std::find_if(renderableModels.cbegin(), renderableModels.cend(), [&renderModel](const QSSGRenderableNodeEntry &e) { return (e.node == &renderModel); }); it != renderableModels.cend()) {
555 renderables.emplace_back(*it);
556 if (steal)
557 renderableModels.erase(it);
558 }
559 }
560
561 createRenderablesHelper(layer, chld.children, renderables, createFlags);
562 }
563}
564
565QSSGRenderablesId QSSGLayerRenderData::createRenderables(QSSGPrepContextId prepId, const QSSGNodeIdList &nodes, QSSGRenderHelpers::CreateFlags createFlags)
566{
567 QSSG_ASSERT_X(verifyPrepContext(prepId, *renderer), "Expired or invalid prep id", return {});
568
569 const size_t index = getPrepContextIndex(prepId);
570 QSSG_ASSERT(index < renderableModelStore.size(), return {});
571
572 auto &renderables = renderableModelStore[index];
573 if (renderables.size() != 0) {
574 qWarning() << "Renderables already created for this context - Previous renderables will be overwritten";
575 renderables.clear();
576 }
577
578 renderables.reserve(nodes.size());
579
580 // We now create the renderable node entries for all the models.
581 // NOTE: The nodes are not complete at this point...
582 const bool steal = ((createFlags & QSSGRenderHelpers::CreateFlag::Steal) != 0);
583 for (const auto &nodeId : nodes) {
584 auto *node = QSSGRenderGraphObjectUtils::getNode<QSSGRenderNode>(nodeId);
585 if (node && node->type == QSSGRenderGraphObject::Type::Model) {
586 auto *renderModel = static_cast<QSSGRenderModel *>(node);
587 // NOTE: Not ideal.
588 if (auto it = std::find_if(renderableModels.cbegin(), renderableModels.cend(), [renderModel](const QSSGRenderableNodeEntry &e) { return (e.node == renderModel); }); it != renderableModels.cend()) {
589 auto &inserted = renderables.emplace_back(*it);
590 inserted.overridden = {};
591 if (steal)
592 it->overridden |= QSSGRenderableNodeEntry::Overridden::Disabled;
593 } else {
594 renderables.emplace_back(*renderModel);
595 }
596 }
597
598 if (node && ((createFlags & QSSGRenderHelpers::CreateFlag::Recurse) != 0)) {
599 const auto &children = node->children;
600 createRenderablesHelper(*this, children, renderables, createFlags);
601 }
602 }
603
604 // NOTE: Modifying the renderables list isn't ideal and should be done differently
605 // but for now this is the easiest way to get the job done.
606 // We still need to let the layer know it should reset the list once a new
607 // frame starts.
608 renderablesModifiedByExtension = true;
609
610 return (renderables.size() != 0) ? static_cast<QSSGRenderablesId>(prepId) : QSSGRenderablesId{ QSSGRenderablesId::Invalid };
611}
612
613void QSSGLayerRenderData::setGlobalTransform(QSSGRenderablesId renderablesId, const QSSGRenderModel &model, const QMatrix4x4 &globalTransform)
614{
615 const auto prepId = static_cast<QSSGPrepContextId>(renderablesId);
616 QSSG_ASSERT_X(verifyPrepContext(prepId, *renderer), "Expired or invalid renderables id", return);
617 const size_t index = getPrepContextIndex(prepId);
618 QSSG_ASSERT_X(index < renderableModelStore.size(), "Missing call to createRenderables()?", return);
619
620 auto &renderables = renderableModelStore[index];
621 auto it = std::find_if(renderables.cbegin(), renderables.cend(), [&model](const QSSGRenderableNodeEntry &e) { return e.node == &model; });
622 if (it != renderables.cend()) {
623 it->extOverrides.globalTransform = globalTransform;
624 it->overridden |= QSSGRenderableNodeEntry::Overridden::GlobalTransform;
625 }
626}
627
628QMatrix4x4 QSSGLayerRenderData::getGlobalTransform(QSSGPrepContextId prepId, const QSSGRenderModel &model)
629{
630 QSSG_ASSERT_X(verifyPrepContext(prepId, *renderer), "Expired or invalid prep id", return {});
631 const size_t index = getPrepContextIndex(prepId);
632 QSSG_ASSERT_X(index < renderableModelStore.size(), "Missing call to createRenderables()?", return {});
633
634 QMatrix4x4 ret = getGlobalTransform(model);
635 auto &renderables = renderableModelStore[index];
636 auto it = std::find_if(renderables.cbegin(), renderables.cend(), [&model](const QSSGRenderableNodeEntry &e) { return e.node == &model; });
637 if (it != renderables.cend() && (it->overridden & QSSGRenderableNodeEntry::Overridden::GlobalTransform))
638 ret = it->extOverrides.globalTransform;
639
640 return ret;
641}
642
643void QSSGLayerRenderData::setGlobalOpacity(QSSGRenderablesId renderablesId, const QSSGRenderModel &model, float opacity)
644{
645 const auto prepId = static_cast<QSSGPrepContextId>(renderablesId);
646 QSSG_ASSERT_X(verifyPrepContext(prepId, *renderer), "Expired or invalid renderables id", return);
647 const size_t index = getPrepContextIndex(prepId);
648 QSSG_ASSERT_X(index < renderableModelStore.size(), "Missing call to createRenderables()?", return);
649
650 auto &renderables = renderableModelStore[index];
651 auto it = std::find_if(renderables.cbegin(), renderables.cend(), [&model](const QSSGRenderableNodeEntry &e) { return e.node == &model; });
652 if (it != renderables.cend()) {
653 it->extOverrides.globalOpacity = opacity;
654 it->overridden |= QSSGRenderableNodeEntry::Overridden::GlobalOpacity;
655 }
656}
657
658float QSSGLayerRenderData::getGlobalOpacity(QSSGPrepContextId prepId, const QSSGRenderModel &model)
659{
660 QSSG_ASSERT_X(verifyPrepContext(prepId, *renderer), "Expired or invalid prep id", return {});
661 const size_t index = getPrepContextIndex(prepId);
662 QSSG_ASSERT_X(index < renderableModelStore.size(), "Missing call to createRenderables()?", return {});
663
664 float ret = getGlobalOpacity(model);
665 auto &renderables = renderableModelStore[index];
666 auto it = std::find_if(renderables.cbegin(), renderables.cend(), [&model](const QSSGRenderableNodeEntry &e) { return e.node == &model; });
667 if (it != renderables.cend() && (it->overridden & QSSGRenderableNodeEntry::Overridden::GlobalOpacity))
668 ret = it->extOverrides.globalOpacity;
669
670 return ret;
671}
672
673void QSSGLayerRenderData::setModelMaterials(QSSGRenderablesId renderablesId, const QSSGRenderModel &model, const QList<QSSGResourceId> &materials)
674{
675 const auto prepId = static_cast<QSSGPrepContextId>(renderablesId);
676 QSSG_ASSERT_X(verifyPrepContext(prepId, *renderer), "Expired or invalid renderable id", return);
677 const size_t index = getPrepContextIndex(prepId);
678 QSSG_ASSERT(index < renderableModelStore.size(), return);
679
680 // Sanity check
681 if (materials.size() > 0 && !QSSG_GUARD(QSSGRenderGraphObject::isMaterial(QSSGRenderGraphObjectUtils::getResource(materials.at(0))->type)))
682 return;
683
684 auto &renderables = renderableModelStore[index];
685 auto it = std::find_if(renderables.cbegin(), renderables.cend(), [&model](const QSSGRenderableNodeEntry &e) { return e.node == &model; });
686 if (it != renderables.cend()) {
687 it->extOverrides.materials.resize(materials.size());
688 std::memcpy(it->extOverrides.materials.data(), materials.data(), it->extOverrides.materials.size() * sizeof(QSSGRenderGraphObject *));
689 it->overridden |= QSSGRenderableNodeEntry::Overridden::Materials;
690 }
691}
692
693void QSSGLayerRenderData::setModelMaterials(const QSSGRenderablesId renderablesId, const QList<QSSGResourceId> &materials)
694{
695 const auto prepId = static_cast<QSSGPrepContextId>(renderablesId);
696 QSSG_ASSERT_X(verifyPrepContext(prepId, *renderer), "Expired or invalid renderablesId or renderables id", return);
697
698 const size_t index = getPrepContextIndex(prepId);
699 QSSG_ASSERT(index < renderableModelStore.size(), return);
700
701 auto &renderables = renderableModelStore[index];
702 for (auto &renderable : renderables) {
703 auto &renderableMaterials = renderable.extOverrides.materials;
704 renderableMaterials.resize(materials.size());
705 std::memcpy(renderableMaterials.data(), materials.data(), renderableMaterials.size() * sizeof(QSSGRenderGraphObject *));
706 renderable.overridden |= QSSGRenderableNodeEntry::Overridden::Materials;
707 }
708}
709
710QSSGPrepResultId QSSGLayerRenderData::prepareModelsForRender(QSSGRenderContextInterface &contextInterface,
711 QSSGPrepContextId prepId,
712 QSSGRenderablesId renderablesId,
713 float lodThreshold)
714{
715 QSSG_ASSERT_X(renderablesId != QSSGRenderablesId::Invalid && verifyPrepContext(prepId, *renderer),
716 "Expired or invalid prep or renderables id", return QSSGPrepResultId::Invalid);
717 const size_t index = getPrepContextIndex(prepId);
718 QSSG_ASSERT(index < renderableModelStore.size(), return {});
719
720 const auto &extContext = extContexts.at(index);
721
722 QSSG_ASSERT_X(extContext.camera != nullptr, "No camera set!", return {});
723
724 const auto vp = contextInterface.renderer()->viewport();
725 const float dpr = contextInterface.renderer()->dpr();
726 const float ssaaMultiplier = layer.isSsaaEnabled() ? layer.ssaaMultiplier : 1.0f;
727
728 QSSGRenderCamera::calculateProjectionInternal(*extContext.camera, vp, { dpr, ssaaMultiplier } );
729
730 auto &renderables = renderableModelStore[index];
731
732 static const auto prepareModelMaterials = [](const RenderableNodeEntries &renderables) {
733 for (auto &renderable : renderables) {
734 if ((renderable.overridden & QSSGRenderableNodeEntry::Overridden::Materials) == 0
735 && (renderable.overridden & QSSGRenderableNodeEntry::Overridden::Disabled) == 0) {
736 renderable.extOverrides.materials = static_cast<QSSGRenderModel *>(renderable.node)->materials;
737 }
738 }
739 };
740
741 prepareModelMaterials(renderables);
742
743 // ### multiview
744 QSSGRenderCameraList camera({ extContext.camera });
745 QSSGRenderCameraDataList cameraData({ getCameraRenderData(extContext.camera) });
746
747 auto &modelContexts = modelContextStore[index];
748 QSSG_ASSERT(modelContexts.isEmpty(), modelContexts.clear());
749
750 auto &renderableObjects = renderableObjectStore[index];
751 QSSG_ASSERT(renderableObjects.isEmpty(), renderableObjects.clear());
752
753 auto &opaqueObjects = opaqueObjectStore[index];
754 QSSG_ASSERT(opaqueObjects.isEmpty(), opaqueObjects.clear());
755
756 auto &transparentObjects = transparentObjectStore[index];
757 QSSG_ASSERT(transparentObjects.isEmpty(), transparentObjects.clear());
758
759 auto &screenTextureObjects = screenTextureObjectStore[index];
760 QSSG_ASSERT(screenTextureObjects.isEmpty(), screenTextureObjects.clear());
761
762 bool wasDirty = prepareModelsForRender(contextInterface,
763 renderables,
764 layerPrepResult.flags,
765 camera,
766 cameraData,
767 modelContexts,
768 opaqueObjects,
769 transparentObjects,
770 screenTextureObjects,
771 lodThreshold);
772
773 (void)wasDirty;
774
775 return static_cast<QSSGPrepResultId>(prepId);
776}
777
778static constexpr size_t pipelineStateIndex(QSSGRenderablesFilter filter)
779{
780 switch (filter) {
781 case QSSGRenderablesFilter::All:
782 return 0;
783 case QSSGRenderablesFilter::Opaque:
784 return 1;
785 case QSSGRenderablesFilter::Transparent:
786 return 2;
787 }
788
789 // GCC 8.x does not treat __builtin_unreachable() as constexpr
790# if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900)
791 // NOLINTNEXTLINE(qt-use-unreachable-return): Triggers on Clang, breaking GCC 8
792 Q_UNREACHABLE();
793# endif
794 return 0;
795}
796
797void QSSGLayerRenderData::prepareRenderables(QSSGRenderContextInterface &ctx,
798 QSSGPrepResultId prepId,
799 QRhiRenderPassDescriptor *renderPassDescriptor,
800 const QSSGRhiGraphicsPipelineState &ps,
801 QSSGRenderablesFilters filter)
802{
803 QSSG_ASSERT_X(verifyPrepContext(static_cast<QSSGPrepContextId>(prepId), *renderer), "Expired or invalid result id", return);
804 const size_t index = getPrepContextIndex(static_cast<QSSGPrepContextId>(prepId));
805 QSSG_ASSERT(index < renderableObjectStore.size() && index < extContexts.size(), return);
806
807 auto &extCtx = extContexts[index];
808 QSSG_ASSERT(extCtx.camera, return);
809 extCtx.filter |= filter;
810
811 QSSGShaderFeatures featureSet = getShaderFeatures();
812
813 QSSGPassKey passKey { reinterpret_cast<void *>(quintptr(extCtx.owner) ^ extCtx.slot) }; // TODO: Pass this along
814
815 if ((filter & QSSGRenderablesFilter::Opaque) != 0) {
816 auto psCpy = ps;
817 if (filter == QSSGRenderablesFilter::All) { // If 'All' we set our defaults
818 psCpy.depthFunc = QRhiGraphicsPipeline::LessOrEqual;
819 psCpy.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, false);
820 }
821 const auto &sortedRenderables = getSortedOpaqueRenderableObjects(*extCtx.camera, index);
822 OpaquePass::prep(ctx, *this, passKey, psCpy, featureSet, renderPassDescriptor, sortedRenderables);
823 const size_t psIndex = pipelineStateIndex(QSSGRenderablesFilter::Opaque);
824 extCtx.ps[psIndex] = psCpy;
825 }
826
827 if ((filter & QSSGRenderablesFilter::Transparent) != 0) {
828 auto psCpy = ps;
829 if (filter == QSSGRenderablesFilter::All) { // If 'All' we set our defaults
830 // transparent objects (or, without LayerEnableDepthTest, all objects)
831 psCpy.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, true);
832 psCpy.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, false);
833 }
834 const auto &sortedRenderables = getSortedTransparentRenderableObjects(*extCtx.camera, index);
835 TransparentPass::prep(ctx, *this, passKey, psCpy, featureSet, renderPassDescriptor, sortedRenderables);
836 const size_t psIndex = pipelineStateIndex(QSSGRenderablesFilter::Transparent);
837 extCtx.ps[psIndex] = psCpy;
838 }
839}
840
841void QSSGLayerRenderData::renderRenderables(QSSGRenderContextInterface &ctx, QSSGPrepResultId prepId)
842{
843 QSSG_ASSERT_X(verifyPrepContext(static_cast<QSSGPrepContextId>(prepId), *renderer), "Expired or invalid result id", return);
844 const size_t index = getPrepContextIndex(static_cast<QSSGPrepContextId>(prepId));
845 QSSG_ASSERT(index < renderableObjectStore.size() && index < extContexts.size(), return);
846
847 const auto &extCtx = extContexts.at(index);
848 const auto filter = extCtx.filter;
849
850 if ((filter & QSSGRenderablesFilter::Opaque) != 0) {
851 const size_t psIndex = pipelineStateIndex(QSSGRenderablesFilter::Opaque);
852 const auto &ps = extCtx.ps[psIndex];
853 const auto &sortedRenderables = getSortedOpaqueRenderableObjects(*extCtx.camera, index);
854 OpaquePass::render(ctx, ps, sortedRenderables);
855 }
856
857 if ((filter & QSSGRenderablesFilter::Transparent) != 0) {
858 const size_t psIndex = pipelineStateIndex(QSSGRenderablesFilter::Transparent);
859 const auto &ps = extCtx.ps[psIndex];
860 const auto &sortedRenderables = getSortedTransparentRenderableObjects(*extCtx.camera, index);
861 TransparentPass::render(ctx, ps, sortedRenderables);
862 }
863}
864
865const QSSGRenderableObjectList &QSSGLayerRenderData::getSortedRenderedDepthWriteObjects(const QSSGRenderCamera &camera, size_t index)
866{
867 updateSortedDepthObjectsListImp(camera, index);
868 return sortedDepthWriteCache[index][{&camera, camera.layerMask}];
869}
870
871const QSSGRenderableObjectList &QSSGLayerRenderData::getSortedrenderedOpaqueDepthPrepassObjects(const QSSGRenderCamera &camera, size_t index)
872{
873 updateSortedDepthObjectsListImp(camera, index);
874 return sortedOpaqueDepthPrepassCache[index][{&camera, camera.layerMask}];
875}
876
877/**
878 * Usage: T *ptr = RENDER_FRAME_NEW<T>(context, arg0, arg1, ...); is equivalent to: T *ptr = new T(arg0, arg1, ...);
879 * so RENDER_FRAME_NEW() takes the RCI + T's arguments
880 */
881template <typename T, typename... Args>
882[[nodiscard]] inline T *RENDER_FRAME_NEW(QSSGRenderContextInterface &ctx, Args&&... args)
883{
884 static_assert(std::is_trivially_destructible_v<T>, "Objects allocated using the per-frame allocator needs to be trivially destructible!");
885 return new (QSSGLayerRenderData::perFrameAllocator(ctx)->allocate(sizeof(T)))T(std::forward<Args>(args)...);
886}
887
888template <typename T>
889[[nodiscard]] inline QSSGDataRef<T> RENDER_FRAME_NEW_BUFFER(QSSGRenderContextInterface &ctx, size_t count)
890{
891 static_assert(std::is_trivially_destructible_v<T>, "Objects allocated using the per-frame allocator needs to be trivially destructible!");
892 const size_t asize = sizeof(T) * count;
893 return { reinterpret_cast<T *>(QSSGLayerRenderData::perFrameAllocator(ctx)->allocate(asize)), qsizetype(count) };
894}
895
896void QSSGLayerRenderData::prepareImageForRender(QSSGRenderImage &inImage,
897 QSSGRenderableImage::Type inMapType,
898 QSSGRenderableImage *&ioFirstImage,
899 QSSGRenderableImage *&ioNextImage,
900 QSSGRenderableObjectFlags &ioFlags,
901 QSSGShaderDefaultMaterialKey &inShaderKey,
902 quint32 inImageIndex,
903 QSSGRenderDefaultMaterial *inMaterial)
904{
905 QSSGRenderContextInterface &contextInterface = *renderer->contextInterface();
906 const auto &bufferManager = contextInterface.bufferManager();
907
908 if (inImage.clearDirty())
909 ioFlags |= QSSGRenderableObjectFlag::Dirty;
910
911 // This is where the QRhiTexture gets created, if not already done. Note
912 // that the bufferManager is per-QQuickWindow, and so per-render-thread.
913 // Hence using the same Texture (backed by inImage as the backend node) in
914 // multiple windows will work by each scene in each window getting its own
915 // QRhiTexture. And that's why the QSSGRenderImageTexture cannot be a
916 // member of the QSSGRenderImage. Conceptually this matches what we do for
917 // models (QSSGRenderModel -> QSSGRenderMesh retrieved from the
918 // bufferManager in each prepareModelForRender, etc.).
919
920 const QSSGRenderImageTexture texture = bufferManager->loadRenderImage(&inImage);
921
922 if (texture.m_texture) {
923 if (texture.m_flags.hasTransparency()
924 && (inMapType == QSSGRenderableImage::Type::Diffuse // note: Type::BaseColor is skipped here intentionally
925 || inMapType == QSSGRenderableImage::Type::Opacity
926 || inMapType == QSSGRenderableImage::Type::Translucency))
927 {
928 ioFlags |= QSSGRenderableObjectFlag::HasTransparency;
929 }
930
931 QSSGRenderableImage *theImage = RENDER_FRAME_NEW<QSSGRenderableImage>(contextInterface, inMapType, inImage, texture);
932 QSSGShaderKeyImageMap &theKeyProp = defaultMaterialShaderKeyProperties.m_imageMaps[inImageIndex];
933
934 theKeyProp.setEnabled(inShaderKey, true);
935 switch (inImage.m_mappingMode) {
936 case QSSGRenderImage::MappingModes::Normal:
937 break;
938 case QSSGRenderImage::MappingModes::Environment:
939 theKeyProp.setEnvMap(inShaderKey, true);
940 break;
941 case QSSGRenderImage::MappingModes::LightProbe:
942 theKeyProp.setLightProbe(inShaderKey, true);
943 break;
944 }
945
946 bool hasA = false;
947 bool hasG = false;
948 bool hasB = false;
949
950
951 //### TODO: More formats
952 switch (texture.m_texture->format()) {
953 case QRhiTexture::Format::RED_OR_ALPHA8:
954 hasA = !renderer->contextInterface()->rhiContext()->rhi()->isFeatureSupported(QRhi::RedOrAlpha8IsRed);
955 break;
956 case QRhiTexture::Format::R8:
957 // Leave BGA as false
958 break;
959 default:
960 hasA = true;
961 hasG = true;
962 hasB = true;
963 break;
964 }
965
966 if (inImage.isImageTransformIdentity())
967 theKeyProp.setIdentityTransform(inShaderKey, true);
968
969 if (inImage.m_indexUV == 1)
970 theKeyProp.setUsesUV1(inShaderKey, true);
971
972 if (texture.m_flags.isLinear())
973 theKeyProp.setLinear(inShaderKey, true);
974
975 if (texture.m_flags.isPreMultipliedAlpha())
976 theKeyProp.setPreMultipliedAlpha(inShaderKey, true);
977
978 if (ioFirstImage == nullptr)
979 ioFirstImage = theImage;
980 else
981 ioNextImage->m_nextImage = theImage;
982
983 ioNextImage = theImage;
984
985 if (inMaterial && inImageIndex >= QSSGShaderDefaultMaterialKeyProperties::SingleChannelImagesFirst) {
986 QSSGRenderDefaultMaterial::TextureChannelMapping value = QSSGRenderDefaultMaterial::R;
987
988 const quint32 scIndex = inImageIndex - QSSGShaderDefaultMaterialKeyProperties::SingleChannelImagesFirst;
989 QSSGShaderKeyTextureChannel &channelKey = defaultMaterialShaderKeyProperties.m_textureChannels[scIndex];
990 switch (inImageIndex) {
991 case QSSGShaderDefaultMaterialKeyProperties::OpacityMap:
992 value = inMaterial->opacityChannel;
993 break;
994 case QSSGShaderDefaultMaterialKeyProperties::RoughnessMap:
995 value = inMaterial->roughnessChannel;
996 break;
997 case QSSGShaderDefaultMaterialKeyProperties::MetalnessMap:
998 value = inMaterial->metalnessChannel;
999 break;
1000 case QSSGShaderDefaultMaterialKeyProperties::OcclusionMap:
1001 value = inMaterial->occlusionChannel;
1002 break;
1003 case QSSGShaderDefaultMaterialKeyProperties::TranslucencyMap:
1004 value = inMaterial->translucencyChannel;
1005 break;
1006 case QSSGShaderDefaultMaterialKeyProperties::HeightMap:
1007 value = inMaterial->heightChannel;
1008 break;
1009 case QSSGShaderDefaultMaterialKeyProperties::ClearcoatMap:
1010 value = inMaterial->clearcoatChannel;
1011 break;
1012 case QSSGShaderDefaultMaterialKeyProperties::ClearcoatRoughnessMap:
1013 value = inMaterial->clearcoatRoughnessChannel;
1014 break;
1015 case QSSGShaderDefaultMaterialKeyProperties::TransmissionMap:
1016 value = inMaterial->transmissionChannel;
1017 break;
1018 case QSSGShaderDefaultMaterialKeyProperties::ThicknessMap:
1019 value = inMaterial->thicknessChannel;
1020 break;
1021 case QSSGShaderDefaultMaterialKeyProperties::BaseColorMap:
1022 value = inMaterial->baseColorChannel;
1023 break;
1024 case QSSGShaderDefaultMaterialKeyProperties::SpecularAmountMap:
1025 value = inMaterial->specularAmountChannel;
1026 break;
1027 case QSSGShaderDefaultMaterialKeyProperties::EmissiveMap:
1028 value = inMaterial->emissiveChannel;
1029 break;
1030 default:
1031 break;
1032 }
1033 bool useDefault = false;
1034 switch (value) {
1035 case QSSGRenderDefaultMaterial::TextureChannelMapping::G:
1036 useDefault = !hasG;
1037 break;
1038 case QSSGRenderDefaultMaterial::TextureChannelMapping::B:
1039 useDefault = !hasB;
1040 break;
1041 case QSSGRenderDefaultMaterial::TextureChannelMapping::A:
1042 useDefault = !hasA;
1043 break;
1044 default:
1045 break;
1046 }
1047 if (useDefault)
1048 value = QSSGRenderDefaultMaterial::R; // Always Fallback to Red
1049 channelKey.setTextureChannel(QSSGShaderKeyTextureChannel::TexturChannelBits(value), inShaderKey);
1050 }
1051 }
1052}
1053
1054void QSSGLayerRenderData::setVertexInputPresence(const QSSGRenderableObjectFlags &renderableFlags,
1055 QSSGShaderDefaultMaterialKey &key)
1056{
1057 quint32 vertexAttribs = 0;
1058 if (renderableFlags.hasAttributePosition())
1059 vertexAttribs |= QSSGShaderKeyVertexAttribute::Position;
1060 if (renderableFlags.hasAttributeNormal())
1061 vertexAttribs |= QSSGShaderKeyVertexAttribute::Normal;
1062 if (renderableFlags.hasAttributeTexCoord0())
1063 vertexAttribs |= QSSGShaderKeyVertexAttribute::TexCoord0;
1064 if (renderableFlags.hasAttributeTexCoord1())
1065 vertexAttribs |= QSSGShaderKeyVertexAttribute::TexCoord1;
1066 if (renderableFlags.hasAttributeTexCoordLightmap())
1067 vertexAttribs |= QSSGShaderKeyVertexAttribute::TexCoordLightmap;
1068 if (renderableFlags.hasAttributeTangent())
1069 vertexAttribs |= QSSGShaderKeyVertexAttribute::Tangent;
1070 if (renderableFlags.hasAttributeBinormal())
1071 vertexAttribs |= QSSGShaderKeyVertexAttribute::Binormal;
1072 if (renderableFlags.hasAttributeColor())
1073 vertexAttribs |= QSSGShaderKeyVertexAttribute::Color;
1074 if (renderableFlags.hasAttributeJointAndWeight())
1075 vertexAttribs |= QSSGShaderKeyVertexAttribute::JointAndWeight;
1076 defaultMaterialShaderKeyProperties.m_vertexAttributes.setValue(key, vertexAttribs);
1077}
1078
1079QSSGDefaultMaterialPreparationResult QSSGLayerRenderData::prepareDefaultMaterialForRender(
1080 QSSGRenderDefaultMaterial &inMaterial,
1081 QSSGRenderableObjectFlags &inExistingFlags,
1082 float inOpacity, bool hasAnyLights,
1083 bool anyLightHasShadows,
1084 QSSGLayerRenderPreparationResultFlags &ioFlags)
1085{
1086 QSSGRenderDefaultMaterial *theMaterial = &inMaterial;
1087 QSSGDefaultMaterialPreparationResult retval(QSSGShaderDefaultMaterialKey(qHash(features)));
1088 retval.renderableFlags = inExistingFlags;
1089 QSSGRenderableObjectFlags &renderableFlags(retval.renderableFlags);
1090 QSSGShaderDefaultMaterialKey &theGeneratedKey(retval.materialKey);
1091 retval.opacity = inOpacity;
1092 float &subsetOpacity(retval.opacity);
1093
1094 if (theMaterial->isDirty())
1095 renderableFlags |= QSSGRenderableObjectFlag::Dirty;
1096
1097 subsetOpacity *= theMaterial->opacity;
1098
1099 QSSGRenderableImage *firstImage = nullptr;
1100
1101 const bool lighting = theMaterial->lighting != QSSGRenderDefaultMaterial::MaterialLighting::NoLighting;
1102 defaultMaterialShaderKeyProperties.m_hasLighting.setValue(theGeneratedKey, lighting);
1103 if (lighting) {
1104 defaultMaterialShaderKeyProperties.m_hasPunctualLights.setValue(theGeneratedKey, hasAnyLights);
1105 defaultMaterialShaderKeyProperties.m_hasShadows.setValue(theGeneratedKey, anyLightHasShadows);
1106 defaultMaterialShaderKeyProperties.m_hasIbl.setValue(theGeneratedKey, layer.lightProbe != nullptr);
1107 }
1108
1109 defaultMaterialShaderKeyProperties.m_specularAAEnabled.setValue(theGeneratedKey, layer.specularAAEnabled);
1110
1111 // isDoubleSided
1112 defaultMaterialShaderKeyProperties.m_isDoubleSided.setValue(theGeneratedKey, theMaterial->cullMode == QSSGCullFaceMode::Disabled);
1113
1114 // default materials never define their on position
1115 defaultMaterialShaderKeyProperties.m_overridesPosition.setValue(theGeneratedKey, false);
1116
1117 // default materials dont make use of raw projection or inverse projection matrices
1118 defaultMaterialShaderKeyProperties.m_usesProjectionMatrix.setValue(theGeneratedKey, false);
1119 defaultMaterialShaderKeyProperties.m_usesInverseProjectionMatrix.setValue(theGeneratedKey, false);
1120 // nor they do rely on VAR_COLOR
1121 defaultMaterialShaderKeyProperties.m_usesVarColor.setValue(theGeneratedKey, false);
1122
1123 // alpha Mode
1124 defaultMaterialShaderKeyProperties.m_alphaMode.setValue(theGeneratedKey, theMaterial->alphaMode);
1125
1126 // vertex attribute presence flags
1127 setVertexInputPresence(renderableFlags, theGeneratedKey);
1128
1129 // set the flag indicating the need for gl_PointSize
1130 defaultMaterialShaderKeyProperties.m_usesPointsTopology.setValue(theGeneratedKey, renderableFlags.isPointsTopology());
1131
1132 // propagate the flag indicating the presence of a lightmap
1133 defaultMaterialShaderKeyProperties.m_lightmapEnabled.setValue(theGeneratedKey, renderableFlags.rendersWithLightmap());
1134 defaultMaterialShaderKeyProperties.m_metallicRoughnessEnabled.setValue(theGeneratedKey, theMaterial->type == QSSGRenderDefaultMaterial::Type::PrincipledMaterial);
1135 defaultMaterialShaderKeyProperties.m_specularGlossyEnabled.setValue(theGeneratedKey, theMaterial->type == QSSGRenderGraphObject::Type::SpecularGlossyMaterial);
1136
1137
1138 // debug modes
1139 defaultMaterialShaderKeyProperties.m_debugMode.setValue(theGeneratedKey, int(layer.debugMode));
1140
1141 // fog
1142 defaultMaterialShaderKeyProperties.m_fogEnabled.setValue(theGeneratedKey, layer.fog.enabled);
1143
1144 // multiview
1145 defaultMaterialShaderKeyProperties.m_viewCount.setValue(theGeneratedKey, layer.viewCount);
1146 defaultMaterialShaderKeyProperties.m_usesViewIndex.setValue(theGeneratedKey, layer.viewCount >= 2);
1147
1148 if (!defaultMaterialShaderKeyProperties.m_hasIbl.getValue(theGeneratedKey) && theMaterial->iblProbe) {
1149 features.set(QSSGShaderFeatures::Feature::LightProbe, true);
1150 defaultMaterialShaderKeyProperties.m_hasIbl.setValue(theGeneratedKey, true);
1151 // features.set(ShaderFeatureDefines::enableIblFov(),
1152 // m_Renderer.GetLayerRenderData()->m_Layer.m_ProbeFov < 180.0f );
1153 }
1154
1155 if (subsetOpacity >= QSSGRendererPrivate::minimumRenderOpacity) {
1156
1157 // Set the semi-transparency flag as specified in PrincipledMaterial's
1158 // blendMode and alphaMode:
1159 // - the default SourceOver blendMode does not imply alpha blending on
1160 // its own,
1161 // - but other blendMode values do,
1162 // - an alphaMode of Blend guarantees blending to be enabled regardless
1163 // of anything else.
1164 // Additionally:
1165 // - Opacity and texture map alpha are handled elsewhere (that's when a
1166 // blendMode of SourceOver or an alphaMode of Default/Opaque can in the
1167 // end still result in HasTransparency),
1168 // - the presence of an opacityMap guarantees alpha blending regardless
1169 // of its content.
1170
1171 if (theMaterial->blendMode != QSSGRenderDefaultMaterial::MaterialBlendMode::SourceOver
1172 || theMaterial->opacityMap
1173 || theMaterial->alphaMode == QSSGRenderDefaultMaterial::Blend)
1174 {
1175 renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
1176 }
1177
1178 const bool specularEnabled = theMaterial->isSpecularEnabled();
1179 const bool metalnessEnabled = theMaterial->isMetalnessEnabled();
1180 defaultMaterialShaderKeyProperties.m_specularEnabled.setValue(theGeneratedKey, specularEnabled || metalnessEnabled);
1181 defaultMaterialShaderKeyProperties.m_specularModel.setSpecularModel(theGeneratedKey, theMaterial->specularModel);
1182 defaultMaterialShaderKeyProperties.m_diffuseModel.setDiffuseModel(theGeneratedKey, theMaterial->diffuseModel);
1183 defaultMaterialShaderKeyProperties.m_fresnelScaleBiasEnabled.setValue(theGeneratedKey, theMaterial->isFresnelScaleBiasEnabled());
1184 defaultMaterialShaderKeyProperties.m_clearcoatFresnelScaleBiasEnabled.setValue(theGeneratedKey, theMaterial->isClearcoatFresnelScaleBiasEnabled());
1185 defaultMaterialShaderKeyProperties.m_fresnelEnabled.setValue(theGeneratedKey, theMaterial->isFresnelEnabled());
1186 defaultMaterialShaderKeyProperties.m_baseColorSingleChannelEnabled.setValue(theGeneratedKey, theMaterial->isBaseColorSingleChannelEnabled());
1187 defaultMaterialShaderKeyProperties.m_specularSingleChannelEnabled.setValue(theGeneratedKey, theMaterial->isSpecularAmountSingleChannelEnabled());
1188 defaultMaterialShaderKeyProperties.m_emissiveSingleChannelEnabled.setValue(theGeneratedKey, theMaterial->isEmissiveSingleChannelEnabled());
1189 defaultMaterialShaderKeyProperties.m_invertOpacityMapValue.setValue(theGeneratedKey, theMaterial->isInvertOpacityMapValue());
1190 defaultMaterialShaderKeyProperties.m_vertexColorsEnabled.setValue(theGeneratedKey, theMaterial->isVertexColorsEnabled());
1191 defaultMaterialShaderKeyProperties.m_vertexColorsMaskEnabled.setValue(theGeneratedKey, theMaterial->isVertexColorsMaskEnabled());
1192 defaultMaterialShaderKeyProperties.m_vertexColorRedMask.setValue(theGeneratedKey, quint16(theMaterial->vertexColorRedMask.toInt()));
1193 defaultMaterialShaderKeyProperties.m_vertexColorGreenMask.setValue(theGeneratedKey, quint16(theMaterial->vertexColorGreenMask.toInt()));
1194 defaultMaterialShaderKeyProperties.m_vertexColorBlueMask.setValue(theGeneratedKey, quint16(theMaterial->vertexColorBlueMask.toInt()));
1195 defaultMaterialShaderKeyProperties.m_vertexColorAlphaMask.setValue(theGeneratedKey, quint16(theMaterial->vertexColorAlphaMask.toInt()));
1196 defaultMaterialShaderKeyProperties.m_clearcoatEnabled.setValue(theGeneratedKey, theMaterial->isClearcoatEnabled());
1197 defaultMaterialShaderKeyProperties.m_transmissionEnabled.setValue(theGeneratedKey, theMaterial->isTransmissionEnabled());
1198
1199 // Run through the material's images and prepare them for render.
1200 // this may in fact set pickable on the renderable flags if one of the images
1201 // links to a sub presentation or any offscreen rendered object.
1202 QSSGRenderableImage *nextImage = nullptr;
1203#define CHECK_IMAGE_AND_PREPARE(img, imgtype, shadercomponent)
1204 if ((img))
1205 prepareImageForRender(*(img), imgtype, firstImage, nextImage, renderableFlags,
1206 theGeneratedKey, shadercomponent, &inMaterial)
1207
1208 if (theMaterial->type == QSSGRenderGraphObject::Type::PrincipledMaterial ||
1209 theMaterial->type == QSSGRenderGraphObject::Type::SpecularGlossyMaterial) {
1210 CHECK_IMAGE_AND_PREPARE(theMaterial->colorMap,
1211 QSSGRenderableImage::Type::BaseColor,
1212 QSSGShaderDefaultMaterialKeyProperties::BaseColorMap);
1213 CHECK_IMAGE_AND_PREPARE(theMaterial->occlusionMap,
1214 QSSGRenderableImage::Type::Occlusion,
1215 QSSGShaderDefaultMaterialKeyProperties::OcclusionMap);
1216 CHECK_IMAGE_AND_PREPARE(theMaterial->heightMap,
1217 QSSGRenderableImage::Type::Height,
1218 QSSGShaderDefaultMaterialKeyProperties::HeightMap);
1219 CHECK_IMAGE_AND_PREPARE(theMaterial->clearcoatMap,
1220 QSSGRenderableImage::Type::Clearcoat,
1221 QSSGShaderDefaultMaterialKeyProperties::ClearcoatMap);
1222 CHECK_IMAGE_AND_PREPARE(theMaterial->clearcoatRoughnessMap,
1223 QSSGRenderableImage::Type::ClearcoatRoughness,
1224 QSSGShaderDefaultMaterialKeyProperties::ClearcoatRoughnessMap);
1225 CHECK_IMAGE_AND_PREPARE(theMaterial->clearcoatNormalMap,
1226 QSSGRenderableImage::Type::ClearcoatNormal,
1227 QSSGShaderDefaultMaterialKeyProperties::ClearcoatNormalMap);
1228 CHECK_IMAGE_AND_PREPARE(theMaterial->transmissionMap,
1229 QSSGRenderableImage::Type::Transmission,
1230 QSSGShaderDefaultMaterialKeyProperties::TransmissionMap);
1231 CHECK_IMAGE_AND_PREPARE(theMaterial->thicknessMap,
1232 QSSGRenderableImage::Type::Thickness,
1233 QSSGShaderDefaultMaterialKeyProperties::ThicknessMap);
1234 if (theMaterial->type == QSSGRenderGraphObject::Type::PrincipledMaterial) {
1235 CHECK_IMAGE_AND_PREPARE(theMaterial->metalnessMap,
1236 QSSGRenderableImage::Type::Metalness,
1237 QSSGShaderDefaultMaterialKeyProperties::MetalnessMap);
1238 }
1239 } else {
1240 CHECK_IMAGE_AND_PREPARE(theMaterial->colorMap,
1241 QSSGRenderableImage::Type::Diffuse,
1242 QSSGShaderDefaultMaterialKeyProperties::DiffuseMap);
1243 }
1244 CHECK_IMAGE_AND_PREPARE(theMaterial->emissiveMap, QSSGRenderableImage::Type::Emissive, QSSGShaderDefaultMaterialKeyProperties::EmissiveMap);
1245 CHECK_IMAGE_AND_PREPARE(theMaterial->specularReflection,
1246 QSSGRenderableImage::Type::Specular,
1247 QSSGShaderDefaultMaterialKeyProperties::SpecularMap);
1248 CHECK_IMAGE_AND_PREPARE(theMaterial->roughnessMap,
1249 QSSGRenderableImage::Type::Roughness,
1250 QSSGShaderDefaultMaterialKeyProperties::RoughnessMap);
1251 CHECK_IMAGE_AND_PREPARE(theMaterial->opacityMap, QSSGRenderableImage::Type::Opacity, QSSGShaderDefaultMaterialKeyProperties::OpacityMap);
1252 CHECK_IMAGE_AND_PREPARE(theMaterial->bumpMap, QSSGRenderableImage::Type::Bump, QSSGShaderDefaultMaterialKeyProperties::BumpMap);
1253 CHECK_IMAGE_AND_PREPARE(theMaterial->specularMap,
1254 QSSGRenderableImage::Type::SpecularAmountMap,
1255 QSSGShaderDefaultMaterialKeyProperties::SpecularAmountMap);
1256 CHECK_IMAGE_AND_PREPARE(theMaterial->normalMap, QSSGRenderableImage::Type::Normal, QSSGShaderDefaultMaterialKeyProperties::NormalMap);
1257 CHECK_IMAGE_AND_PREPARE(theMaterial->translucencyMap,
1258 QSSGRenderableImage::Type::Translucency,
1259 QSSGShaderDefaultMaterialKeyProperties::TranslucencyMap);
1260 }
1261#undef CHECK_IMAGE_AND_PREPARE
1262
1263 if (subsetOpacity < QSSGRendererPrivate::minimumRenderOpacity) {
1264 subsetOpacity = 0.0f;
1265 // You can still pick against completely transparent objects(or rather their bounding
1266 // box)
1267 // you just don't render them.
1268 renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
1269 renderableFlags |= QSSGRenderableObjectFlag::CompletelyTransparent;
1270 }
1271
1272 if (subsetOpacity > 1.f - QSSGRendererPrivate::minimumRenderOpacity)
1273 subsetOpacity = 1.f;
1274 else
1275 renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
1276
1277 if (inMaterial.isTransmissionEnabled()) {
1278 ioFlags.setRequiresScreenTexture(true);
1279 ioFlags.setRequiresMipmapsForScreenTexture(true);
1280 renderableFlags |= QSSGRenderableObjectFlag::RequiresScreenTexture;
1281 }
1282
1283 if (renderableFlags.hasTransparency()) {
1284 if (orderIndependentTransparencyEnabled)
1285 defaultMaterialShaderKeyProperties.m_orderIndependentTransparency.setValue(theGeneratedKey, int(layer.oitMethod));
1286 if (layer.oitMethodDirty)
1287 renderableFlags |= QSSGRenderableObjectFlag::Dirty;
1288 }
1289
1290 retval.firstImage = firstImage;
1291 if (retval.renderableFlags.isDirty())
1292 retval.dirty = true;
1293 if (retval.dirty)
1294 renderer->addMaterialDirtyClear(&inMaterial);
1295 return retval;
1296}
1297
1298QSSGDefaultMaterialPreparationResult QSSGLayerRenderData::prepareCustomMaterialForRender(
1299 QSSGRenderCustomMaterial &inMaterial, QSSGRenderableObjectFlags &inExistingFlags,
1300 float inOpacity, bool alreadyDirty, bool hasAnyLights, bool anyLightHasShadows,
1301 QSSGLayerRenderPreparationResultFlags &ioFlags)
1302{
1303 QSSGDefaultMaterialPreparationResult retval(QSSGShaderDefaultMaterialKey(qHash(features)));
1304 retval.renderableFlags = inExistingFlags;
1305 QSSGRenderableObjectFlags &renderableFlags(retval.renderableFlags);
1306 QSSGShaderDefaultMaterialKey &theGeneratedKey(retval.materialKey);
1307 retval.opacity = inOpacity;
1308 float &subsetOpacity(retval.opacity);
1309
1310 if (subsetOpacity < QSSGRendererPrivate::minimumRenderOpacity) {
1311 subsetOpacity = 0.0f;
1312 // You can still pick against completely transparent objects(or rather their bounding
1313 // box)
1314 // you just don't render them.
1315 renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
1316 renderableFlags |= QSSGRenderableObjectFlag::CompletelyTransparent;
1317 }
1318
1319 if (subsetOpacity > 1.f - QSSGRendererPrivate::minimumRenderOpacity)
1320 subsetOpacity = 1.f;
1321 else
1322 renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
1323
1324 defaultMaterialShaderKeyProperties.m_hasLighting.setValue(theGeneratedKey, true);
1325 defaultMaterialShaderKeyProperties.m_hasPunctualLights.setValue(theGeneratedKey, hasAnyLights);
1326 defaultMaterialShaderKeyProperties.m_hasShadows.setValue(theGeneratedKey, anyLightHasShadows);
1327 defaultMaterialShaderKeyProperties.m_hasIbl.setValue(theGeneratedKey, layer.lightProbe != nullptr);
1328 defaultMaterialShaderKeyProperties.m_specularEnabled.setValue(theGeneratedKey, true);
1329
1330 defaultMaterialShaderKeyProperties.m_specularAAEnabled.setValue(theGeneratedKey, layer.specularAAEnabled);
1331
1332 // isDoubleSided
1333 defaultMaterialShaderKeyProperties.m_isDoubleSided.setValue(theGeneratedKey, inMaterial.m_cullMode == QSSGCullFaceMode::Disabled);
1334
1335 // Custom Materials are always Metallic Roughness Workflow
1336 defaultMaterialShaderKeyProperties.m_metallicRoughnessEnabled.setValue(theGeneratedKey, true);
1337
1338 // Does the material override the position output
1339 const bool overridesPosition = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::OverridesPosition);
1340 defaultMaterialShaderKeyProperties.m_overridesPosition.setValue(theGeneratedKey, overridesPosition);
1341
1342 // Optional usage of PROJECTION_MATRIX and/or INVERSE_PROJECTION_MATRIX
1343 const bool usesProjectionMatrix = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::ProjectionMatrix);
1344 defaultMaterialShaderKeyProperties.m_usesProjectionMatrix.setValue(theGeneratedKey, usesProjectionMatrix);
1345 const bool usesInvProjectionMatrix = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::InverseProjectionMatrix);
1346 defaultMaterialShaderKeyProperties.m_usesInverseProjectionMatrix.setValue(theGeneratedKey, usesInvProjectionMatrix);
1347
1348 // vertex attribute presence flags
1349 setVertexInputPresence(renderableFlags, theGeneratedKey);
1350
1351 // set the flag indicating the need for gl_PointSize
1352 defaultMaterialShaderKeyProperties.m_usesPointsTopology.setValue(theGeneratedKey, renderableFlags.isPointsTopology());
1353
1354 // propagate the flag indicating the presence of a lightmap
1355 defaultMaterialShaderKeyProperties.m_lightmapEnabled.setValue(theGeneratedKey, renderableFlags.rendersWithLightmap());
1356
1357 // debug modes
1358 defaultMaterialShaderKeyProperties.m_debugMode.setValue(theGeneratedKey, int(layer.debugMode));
1359
1360 // fog
1361 defaultMaterialShaderKeyProperties.m_fogEnabled.setValue(theGeneratedKey, layer.fog.enabled);
1362
1363 // multiview
1364 defaultMaterialShaderKeyProperties.m_viewCount.setValue(theGeneratedKey, layer.viewCount);
1365 defaultMaterialShaderKeyProperties.m_usesViewIndex.setValue(theGeneratedKey,
1366 inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::ViewIndex));
1367
1368 // Knowing whether VAR_COLOR is used becomes relevant when there is no
1369 // custom vertex shader, but VAR_COLOR is present in the custom fragment
1370 // snippet, because that case needs special care.
1371 const bool usesVarColor = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::VarColor);
1372 defaultMaterialShaderKeyProperties.m_usesVarColor.setValue(theGeneratedKey, usesVarColor);
1373
1374 const bool usesClearcoat = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Clearcoat);
1375 defaultMaterialShaderKeyProperties.m_clearcoatEnabled.setValue(theGeneratedKey, usesClearcoat);
1376
1377 const bool usesClearcoatFresnelScaleBias = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::ClearcoatFresnelScaleBias);
1378 defaultMaterialShaderKeyProperties.m_clearcoatFresnelScaleBiasEnabled.setValue(theGeneratedKey, usesClearcoatFresnelScaleBias);
1379
1380 const bool usesFresnelScaleBias = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::FresnelScaleBias);
1381 defaultMaterialShaderKeyProperties.m_fresnelScaleBiasEnabled.setValue(theGeneratedKey, usesFresnelScaleBias);
1382
1383 const bool usesTransmission = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Transmission);
1384 defaultMaterialShaderKeyProperties.m_transmissionEnabled.setValue(theGeneratedKey, usesTransmission);
1385
1386 if (inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Blending))
1387 renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
1388
1389 if (inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::ScreenTexture)) {
1390 ioFlags.setRequiresScreenTexture(true);
1391 renderableFlags |= QSSGRenderableObjectFlag::RequiresScreenTexture;
1392 }
1393
1394 if (inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::ScreenMipTexture)) {
1395 ioFlags.setRequiresScreenTexture(true);
1396 ioFlags.setRequiresMipmapsForScreenTexture(true);
1397 renderableFlags |= QSSGRenderableObjectFlag::RequiresScreenTexture;
1398 }
1399
1400 if (inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::DepthTexture))
1401 ioFlags.setRequiresDepthTexture(true);
1402
1403 if (inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::NormalTexture)) {
1404 ioFlags.setRequiresNormalTexture(true);
1405 renderableFlags |= QSSGRenderableObjectFlag::RequiresNormalTexture;
1406 }
1407
1408 if (inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::AoTexture)) {
1409 ioFlags.setRequiresDepthTexture(true);
1410 ioFlags.setRequiresSsaoPass(true);
1411 }
1412 if (orderIndependentTransparencyEnabled && renderableFlags.hasTransparency())
1413 defaultMaterialShaderKeyProperties.m_orderIndependentTransparency.setValue(theGeneratedKey, int(layer.oitMethod));
1414
1415 retval.firstImage = nullptr;
1416
1417 if (inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::MotionVectorTexture))
1418 ioFlags.setRequiresMotionVectorPass(true);
1419
1420 if (retval.dirty || alreadyDirty)
1421 renderer->addMaterialDirtyClear(&inMaterial);
1422 return retval;
1423}
1424
1425void QSSGLayerRenderData::setLightmapTexture(const QSSGModelContext &modelContext, QRhiTexture *lightmapTexture)
1426{
1427 lightmapTextures[&modelContext] = lightmapTexture;
1428}
1429
1430QRhiTexture *QSSGLayerRenderData::getLightmapTexture(const QSSGModelContext &modelContext) const
1431{
1432 QRhiTexture *ret = nullptr;
1433 if (modelContext.model.hasLightmap()) {
1434 const auto it = lightmapTextures.constFind(&modelContext);
1435 ret = (it != lightmapTextures.cend()) ? *it : nullptr;
1436 }
1437
1438 return ret;
1439}
1440
1441void QSSGLayerRenderData::setBonemapTexture(const QSSGModelContext &modelContext, QRhiTexture *bonemapTexture)
1442{
1443 bonemapTextures[&modelContext] = bonemapTexture;
1444}
1445
1446QRhiTexture *QSSGLayerRenderData::getBonemapTexture(const QSSGModelContext &modelContext) const
1447{
1448 QRhiTexture *ret = nullptr;
1449 if (modelContext.model.usesBoneTexture()) {
1450 const auto it = bonemapTextures.constFind(&modelContext);
1451 ret = (it != bonemapTextures.cend()) ? *it : nullptr;
1452 }
1453
1454 return ret;
1455}
1456
1457static bool hasCustomBlendMode(const QSSGRenderCustomMaterial &material)
1458{
1459 // Check SrcOver
1460
1461 // srcAlpha is same for all
1462 if (material.m_srcAlphaBlend != QRhiGraphicsPipeline::One)
1463 return true;
1464
1465 // SrcOver srcColor is SrcAlpha
1466 if (material.m_srcBlend != QRhiGraphicsPipeline::SrcAlpha)
1467 return true;
1468
1469 if (material.m_dstBlend == QRhiGraphicsPipeline::OneMinusSrcAlpha
1470 && material.m_dstAlphaBlend == QRhiGraphicsPipeline::OneMinusSrcAlpha)
1471 return false;
1472 return true;
1473}
1474
1475// inModel is const to emphasize the fact that its members cannot be written
1476// here: in case there is a scene shared between multiple View3Ds in different
1477// QQuickWindows, each window may run this in their own render thread, while
1478// inModel is the same.
1479bool QSSGLayerRenderData::prepareModelsForRender(QSSGRenderContextInterface &contextInterface,
1480 const RenderableNodeEntries &renderableModels,
1481 QSSGLayerRenderPreparationResultFlags &ioFlags,
1482 const QSSGRenderCameraList &allCameras,
1483 const QSSGRenderCameraDataList &allCameraData,
1484 TModelContextPtrList &modelContexts,
1485 QSSGRenderableObjectList &opaqueObjects,
1486 QSSGRenderableObjectList &transparentObjects,
1487 QSSGRenderableObjectList &screenTextureObjects,
1488 float lodThreshold)
1489{
1490 const auto &rhiCtx = contextInterface.rhiContext();
1491 const auto &bufferManager = contextInterface.bufferManager();
1492
1493 const auto &debugDrawSystem = contextInterface.debugDrawSystem();
1494 const bool maybeDebugDraw = debugDrawSystem && debugDrawSystem->isEnabled();
1495
1496 bool wasDirty = false;
1497
1498 for (const QSSGRenderableNodeEntry &renderable : renderableModels) {
1499 if ((renderable.overridden & QSSGRenderableNodeEntry::Overridden::Disabled) != 0)
1500 continue;
1501
1502 const QSSGRenderModel &model = *static_cast<QSSGRenderModel *>(renderable.node);
1503 const auto &lights = renderable.lights;
1504 QSSGRenderMesh *theMesh = modelData->getMesh(model);
1505 if (!theMesh)
1506 continue;
1507
1508 const bool altGlobalTransform = ((renderable.overridden & QSSGRenderableNodeEntry::Overridden::GlobalTransform) != 0);
1509 const auto &globalTransform = altGlobalTransform ? renderable.extOverrides.globalTransform : getGlobalTransform(model);
1510 QMatrix3x3 normalMatrix { Qt::Uninitialized };
1511 QSSGLayerRenderData::ModelViewProjections mvps;
1512 if (altGlobalTransform) {
1513 QSSGRenderNode::calculateNormalMatrix(globalTransform, normalMatrix);
1514 size_t mvpCount = 0;
1515 for (const auto &cameraData : allCameraData) {
1516 QSSGRenderNode::calculateMVPAndNormalMatrix(globalTransform, cameraData.viewProjection, mvps[mvpCount++], normalMatrix);
1517 }
1518 } else {
1519 if (model.usesBoneTexture()) {
1520 // FIXME:
1521 // For skinning, node's global transformation will be ignored and
1522 // an identity matrix will be used for the normalMatrix
1523 size_t mvpCount = 0;
1524 for (const QSSGRenderCameraData &cameraData : allCameraData) {
1525 mvps[mvpCount++] = cameraData.viewProjection;
1526 normalMatrix = QMatrix3x3();
1527 }
1528 } else {
1529 normalMatrix = getNormalMatrix(model);
1530 mvps = getModelMvps(model);
1531 }
1532 }
1533 const bool altModelOpacity = ((renderable.overridden & QSSGRenderableNodeEntry::Overridden::GlobalOpacity) != 0);
1534 const float modelGlobalOpacity = altModelOpacity ? renderable.extOverrides.globalOpacity : getGlobalOpacity(model);
1535 QSSGModelContext &theModelContext = *RENDER_FRAME_NEW<QSSGModelContext>(contextInterface, model, globalTransform, normalMatrix, mvps);
1536 modelContexts.push_back(&theModelContext);
1537 // We might over-allocate here, as the material list technically can contain an invalid (nullptr) material.
1538 // We'll fix that by adjusting the size at the end for now...
1539 const auto &meshSubsets = theMesh->subsets;
1540 const auto meshSubsetCount = meshSubsets.size();
1541 theModelContext.subsets = RENDER_FRAME_NEW_BUFFER<QSSGSubsetRenderable>(contextInterface, meshSubsetCount);
1542
1543 // Prepare boneTexture for skinning
1544 if (model.skin) {
1545 auto boneTexture = bufferManager->loadSkinmap(model.skin);
1546 setBonemapTexture(theModelContext, boneTexture.m_texture);
1547 } else if (model.skeleton) {
1548 auto boneTexture = bufferManager->loadSkinmap(&(model.skeleton->boneTexData));
1549 setBonemapTexture(theModelContext, boneTexture.m_texture);
1550 }
1551
1552 // many renderableFlags are the same for all the subsets
1553 QSSGRenderableObjectFlags renderableFlagsForModel;
1554
1555 if (meshSubsetCount > 0) {
1556 const QSSGRenderSubset &theSubset = meshSubsets.at(0);
1557
1558 renderableFlagsForModel.setMotionVectorParticipant(model.motionVectorEnabled);
1559
1560 renderableFlagsForModel.setCastsShadows(model.castsShadows);
1561 renderableFlagsForModel.setReceivesShadows(model.receivesShadows);
1562 renderableFlagsForModel.setReceivesReflections(model.receivesReflections);
1563 renderableFlagsForModel.setCastsReflections(model.castsReflections);
1564
1565 renderableFlagsForModel.setUsedInBakedLighting(model.usedInBakedLighting);
1566 if (model.hasLightmap()) {
1567 QSSGRenderImageTexture lmImageTexture = bufferManager->loadLightmap(model);
1568 if (lmImageTexture.m_texture) {
1569 renderableFlagsForModel.setRendersWithLightmap(true);
1570 setLightmapTexture(theModelContext, lmImageTexture.m_texture);
1571 }
1572 }
1573
1574 // TODO: This should be a oneshot thing, move the flags over!
1575 // With the RHI we need to be able to tell the material shader
1576 // generator to not generate vertex input attributes that are not
1577 // provided by the mesh. (because unlike OpenGL, other graphics
1578 // APIs may treat unbound vertex inputs as a fatal error)
1579 bool hasJoint = false;
1580 bool hasWeight = false;
1581 bool hasMorphTarget = theSubset.rhi.targetsTexture != nullptr;
1582 for (const QSSGRhiInputAssemblerState::InputSemantic &sem : std::as_const(theSubset.rhi.ia.inputs)) {
1583 if (sem == QSSGRhiInputAssemblerState::PositionSemantic) {
1584 renderableFlagsForModel.setHasAttributePosition(true);
1585 } else if (sem == QSSGRhiInputAssemblerState::NormalSemantic) {
1586 renderableFlagsForModel.setHasAttributeNormal(true);
1587 } else if (sem == QSSGRhiInputAssemblerState::TexCoord0Semantic) {
1588 renderableFlagsForModel.setHasAttributeTexCoord0(true);
1589 } else if (sem == QSSGRhiInputAssemblerState::TexCoord1Semantic) {
1590 renderableFlagsForModel.setHasAttributeTexCoord1(true);
1591 } else if (sem == QSSGRhiInputAssemblerState::TexCoordLightmapSemantic) {
1592 renderableFlagsForModel.setHasAttributeTexCoordLightmap(true);
1593 } else if (sem == QSSGRhiInputAssemblerState::TangentSemantic) {
1594 renderableFlagsForModel.setHasAttributeTangent(true);
1595 } else if (sem == QSSGRhiInputAssemblerState::BinormalSemantic) {
1596 renderableFlagsForModel.setHasAttributeBinormal(true);
1597 } else if (sem == QSSGRhiInputAssemblerState::ColorSemantic) {
1598 renderableFlagsForModel.setHasAttributeColor(true);
1599 // For skinning, we will set the HasAttribute only
1600 // if the mesh has both joint and weight
1601 } else if (sem == QSSGRhiInputAssemblerState::JointSemantic) {
1602 hasJoint = true;
1603 } else if (sem == QSSGRhiInputAssemblerState::WeightSemantic) {
1604 hasWeight = true;
1605 }
1606 }
1607 renderableFlagsForModel.setHasAttributeJointAndWeight(hasJoint && hasWeight);
1608 renderableFlagsForModel.setHasAttributeMorphTarget(hasMorphTarget);
1609 }
1610
1611 QSSGRenderableObjectList bakedLightingObjects;
1612 bool usesBlendParticles = particlesEnabled && theModelContext.model.particleBuffer != nullptr
1613 && model.particleBuffer->particleCount();
1614 const bool anyLightHasShadows = std::find_if(lights.begin(),
1615 lights.end(),
1616 [](const QSSGShaderLight &light) { return light.shadows; })
1617 != lights.end();
1618 const bool hasAnyLights = !lights.isEmpty();
1619 QSSGRenderLight::SoftShadowQuality maxSoftShadowQuality = QSSGRenderLight::SoftShadowQuality::Hard;
1620 if (anyLightHasShadows) {
1621 // Iterate the light list to find the maximum shadow quality of lights that cast shadows
1622 for (const QSSGShaderLight &light : lights) {
1623 if (light.shadows && light.light->m_softShadowQuality > maxSoftShadowQuality)
1624 maxSoftShadowQuality = light.light->m_softShadowQuality;
1625 }
1626 }
1627
1628
1629 // Subset(s)
1630 auto &renderableSubsets = theModelContext.subsets;
1631 const bool hasMaterialOverrides = ((renderable.overridden & QSSGRenderableNodeEntry::Overridden::Materials) != 0);
1632 const auto &materials = hasMaterialOverrides ? renderable.extOverrides.materials : modelData->getMaterials(model);
1633 const auto materialCount = materials.size();
1634 QSSGRenderGraphObject *lastMaterial = !materials.isEmpty() ? materials.last() : nullptr;
1635 int idx = 0, subsetIdx = 0;
1636 for (; idx < meshSubsetCount; ++idx) {
1637 // If the materials list < size of subsets, then use the last material for the rest
1638 QSSGRenderGraphObject *theMaterialObject = (idx >= materialCount) ? lastMaterial : materials[idx];
1639 if (!theMaterialObject)
1640 continue;
1641
1642 const QSSGRenderSubset &theSubset = meshSubsets.at(idx);
1643 QSSGRenderableObjectFlags renderableFlags = renderableFlagsForModel;
1644 float subsetOpacity = modelGlobalOpacity;
1645
1646 renderableFlags.setPointsTopology(theSubset.rhi.ia.topology == QRhiGraphicsPipeline::Points);
1647 QSSGRenderableObject *theRenderableObject = &renderableSubsets[subsetIdx++];
1648
1649 const bool usesInstancing = theModelContext.model.instancing()
1650 && rhiCtx->rhi()->isFeatureSupported(QRhi::Instancing);
1651 if (usesInstancing && theModelContext.model.instanceTable->hasTransparency())
1652 renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
1653 if (theModelContext.model.hasTransparency)
1654 renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
1655
1656 // Level Of Detail
1657 quint32 subsetLevelOfDetail = 0;
1658 if (!theSubset.lods.isEmpty() && lodThreshold > 0.0f) {
1659 // Accounts for FOV
1660 float lodDistanceMultiplier = camerasView[0]->getLevelOfDetailMultiplier();
1661 float distanceThreshold = 0.0f;
1662 const auto scale = QSSGUtils::mat44::getScale(globalTransform);
1663 float modelScale = qMax(scale.x(), qMax(scale.y(), scale.z()));
1664 QSSGBounds3 transformedBounds = theSubset.bounds;
1665 if (camerasView[0]->type != QSSGRenderGraphObject::Type::OrthographicCamera) {
1666 transformedBounds.transform(globalTransform);
1667 if (maybeDebugDraw && debugDrawSystem->isEnabled(QSSGDebugDrawSystem::Mode::MeshLod))
1668 debugDrawSystem->drawBounds(transformedBounds, QColor(Qt::red));
1669 const QMatrix4x4 cameraGlobalTranform = getGlobalTransform(*camerasView[0]);
1670 const QVector3D cameraNormal = QSSGRenderNode::getScalingCorrectDirection(cameraGlobalTranform);
1671 const QVector3D cameraPosition = QSSGRenderNode::getGlobalPos(cameraGlobalTranform);
1672 const QSSGPlane cameraPlane = QSSGPlane(cameraPosition, cameraNormal);
1673 const QVector3D lodSupportMin = transformedBounds.getSupport(-cameraNormal);
1674 const QVector3D lodSupportMax = transformedBounds.getSupport(cameraNormal);
1675 if (maybeDebugDraw && debugDrawSystem->isEnabled(QSSGDebugDrawSystem::Mode::MeshLod))
1676 debugDrawSystem->drawPoint(lodSupportMin, QColor("orange"));
1677
1678 const float distanceMin = cameraPlane.distance(lodSupportMin);
1679 const float distanceMax = cameraPlane.distance(lodSupportMax);
1680
1681 if (distanceMin * distanceMax < 0.0)
1682 distanceThreshold = 0.0;
1683 else if (distanceMin >= 0.0)
1684 distanceThreshold = distanceMin;
1685 else if (distanceMax <= 0.0)
1686 distanceThreshold = -distanceMax;
1687
1688 } else {
1689 // Orthographic Projection
1690 distanceThreshold = 1.0;
1691 }
1692
1693 int currentLod = -1;
1694 if (model.levelOfDetailBias > 0.0f) {
1695 const float threshold = distanceThreshold * lodDistanceMultiplier;
1696 const float modelBias = 1 / model.levelOfDetailBias;
1697 for (qsizetype i = 0; i < theSubset.lods.count(); ++i) {
1698 float subsetDistance = theSubset.lods[i].distance * modelScale * modelBias;
1699 float screenSize = subsetDistance / threshold;
1700 if (screenSize > lodThreshold)
1701 break;
1702 currentLod = i;
1703 }
1704 }
1705 if (currentLod == -1)
1706 subsetLevelOfDetail = 0;
1707 else
1708 subsetLevelOfDetail = currentLod + 1;
1709 if (maybeDebugDraw && debugDrawSystem->isEnabled(QSSGDebugDrawSystem::Mode::MeshLod))
1710 debugDrawSystem->drawBounds(transformedBounds, QSSGDebugDrawSystem::levelOfDetailColor(subsetLevelOfDetail));
1711 }
1712
1713 QVector3D theModelCenter(theSubset.bounds.center());
1714 theModelCenter = QSSGUtils::mat44::transform(globalTransform, theModelCenter);
1715 if (maybeDebugDraw && debugDrawSystem->isEnabled(QSSGDebugDrawSystem::Mode::MeshLodNormal)) {
1716 const QMatrix4x4 allCamera0GlobalTransform = getGlobalTransform(*allCameras[0]);
1717 debugDrawSystem->debugNormals(*bufferManager, theModelContext, theSubset, subsetLevelOfDetail, (theModelCenter - QSSGRenderNode::getGlobalPos(allCamera0GlobalTransform)).length() * 0.01);
1718 }
1719
1720 auto checkF32TypeIndex = [&rhiCtx](QRhiVertexInputAttribute::Format f) {
1721 if ((f == QRhiVertexInputAttribute::Format::Float4)
1722 || (f == QRhiVertexInputAttribute::Format::Float3)
1723 || (f == QRhiVertexInputAttribute::Format::Float2)
1724 || (f == QRhiVertexInputAttribute::Format::Float)) {
1725 return true;
1726 }
1727 if (!rhiCtx->rhi()->isFeatureSupported(QRhi::IntAttributes))
1728 qWarning() << "WARN: Model has non-integer type indices for skinning but current RHI backend doesn't support it!";
1729 return false;
1730 };
1731
1732 if (theMaterialObject->type == QSSGRenderGraphObject::Type::DefaultMaterial ||
1733 theMaterialObject->type == QSSGRenderGraphObject::Type::PrincipledMaterial ||
1734 theMaterialObject->type == QSSGRenderGraphObject::Type::SpecularGlossyMaterial) {
1735 QSSGRenderDefaultMaterial &theMaterial(static_cast<QSSGRenderDefaultMaterial &>(*theMaterialObject));
1736 QSSGDefaultMaterialPreparationResult theMaterialPrepResult(prepareDefaultMaterialForRender(theMaterial, renderableFlags, subsetOpacity, hasAnyLights, anyLightHasShadows, ioFlags));
1737 QSSGShaderDefaultMaterialKey &theGeneratedKey(theMaterialPrepResult.materialKey);
1738 subsetOpacity = theMaterialPrepResult.opacity;
1739 QSSGRenderableImage *firstImage(theMaterialPrepResult.firstImage);
1740 wasDirty |= theMaterialPrepResult.dirty;
1741 renderableFlags = theMaterialPrepResult.renderableFlags;
1742 if (renderableFlags.hasTransparency())
1743 ioFlags.setHasCustomBlendMode(theMaterial.blendMode != QSSGRenderDefaultMaterial::MaterialBlendMode::SourceOver);
1744
1745 // Blend particles
1746 defaultMaterialShaderKeyProperties.m_blendParticles.setValue(theGeneratedKey, usesBlendParticles);
1747
1748 if (defaultMaterialShaderKeyProperties.m_orderIndependentTransparency.getValue(theGeneratedKey) == int(QSSGRenderLayer::OITMethod::LinkedList))
1749 defaultMaterialShaderKeyProperties.m_oitMSAA.setValue(theGeneratedKey, rhiCtx->mainPassSampleCount() > 1);
1750
1751 // Skin
1752 const auto boneCount = model.skin ? model.skin->boneCount :
1753 model.skeleton ? model.skeleton->boneCount : 0;
1754 defaultMaterialShaderKeyProperties.m_boneCount.setValue(theGeneratedKey, boneCount);
1755 if (auto idJoint = theSubset.rhi.ia.inputs.indexOf(QSSGRhiInputAssemblerState::JointSemantic); idJoint != -1) {
1756 const auto attr = theSubset.rhi.ia.inputLayout.attributeAt(idJoint);
1757 defaultMaterialShaderKeyProperties.m_usesFloatJointIndices.setValue(theGeneratedKey, checkF32TypeIndex(attr->format()));
1758 }
1759
1760 // SoftShadow quality
1761 defaultMaterialShaderKeyProperties.m_shadowSoftness.setShadowSoftness(theGeneratedKey, maxSoftShadowQuality);
1762
1763 // Instancing
1764 defaultMaterialShaderKeyProperties.m_usesInstancing.setValue(theGeneratedKey, usesInstancing);
1765 // Morphing
1766 defaultMaterialShaderKeyProperties.m_targetCount.setValue(theGeneratedKey,
1767 theSubset.rhi.ia.targetCount);
1768 defaultMaterialShaderKeyProperties.m_targetPositionOffset.setValue(theGeneratedKey,
1769 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::PositionSemantic]);
1770 defaultMaterialShaderKeyProperties.m_targetNormalOffset.setValue(theGeneratedKey,
1771 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::NormalSemantic]);
1772 defaultMaterialShaderKeyProperties.m_targetTangentOffset.setValue(theGeneratedKey,
1773 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::TangentSemantic]);
1774 defaultMaterialShaderKeyProperties.m_targetBinormalOffset.setValue(theGeneratedKey,
1775 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::BinormalSemantic]);
1776 defaultMaterialShaderKeyProperties.m_targetTexCoord0Offset.setValue(theGeneratedKey,
1777 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::TexCoord0Semantic]);
1778 defaultMaterialShaderKeyProperties.m_targetTexCoord1Offset.setValue(theGeneratedKey,
1779 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::TexCoord1Semantic]);
1780 defaultMaterialShaderKeyProperties.m_targetColorOffset.setValue(theGeneratedKey,
1781 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::ColorSemantic]);
1782
1783 new (theRenderableObject) QSSGSubsetRenderable(QSSGSubsetRenderable::Type::DefaultMaterialMeshSubset,
1784 renderableFlags,
1785 theModelCenter,
1786 renderer,
1787 theSubset,
1788 theModelContext,
1789 subsetOpacity,
1790 subsetLevelOfDetail,
1791 theMaterial,
1792 firstImage,
1793 theGeneratedKey,
1794 lights,
1795 anyLightHasShadows);
1796 wasDirty = wasDirty || renderableFlags.isDirty();
1797 } else if (theMaterialObject->type == QSSGRenderGraphObject::Type::CustomMaterial) {
1798 QSSGRenderCustomMaterial &theMaterial(static_cast<QSSGRenderCustomMaterial &>(*theMaterialObject));
1799
1800 const auto &theMaterialSystem(contextInterface.customMaterialSystem());
1801 wasDirty |= theMaterialSystem->prepareForRender(theModelContext.model, theSubset, theMaterial);
1802
1803 if (theMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Blending))
1804 ioFlags.setHasCustomBlendMode(!hasCustomBlendMode(theMaterial));
1805
1806 QSSGDefaultMaterialPreparationResult theMaterialPrepResult(
1807 prepareCustomMaterialForRender(theMaterial, renderableFlags, subsetOpacity, wasDirty,
1808 hasAnyLights, anyLightHasShadows, ioFlags));
1809 QSSGShaderDefaultMaterialKey &theGeneratedKey(theMaterialPrepResult.materialKey);
1810 subsetOpacity = theMaterialPrepResult.opacity;
1811 QSSGRenderableImage *firstImage(theMaterialPrepResult.firstImage);
1812 renderableFlags = theMaterialPrepResult.renderableFlags;
1813
1814 if (model.particleBuffer && model.particleBuffer->particleCount())
1815 defaultMaterialShaderKeyProperties.m_blendParticles.setValue(theGeneratedKey, true);
1816 else
1817 defaultMaterialShaderKeyProperties.m_blendParticles.setValue(theGeneratedKey, false);
1818
1819 if (defaultMaterialShaderKeyProperties.m_orderIndependentTransparency.getValue(theGeneratedKey) == int(QSSGRenderLayer::OITMethod::LinkedList))
1820 defaultMaterialShaderKeyProperties.m_oitMSAA.setValue(theGeneratedKey, rhiCtx->mainPassSampleCount() > 1);
1821
1822 // SoftShadow quality
1823 defaultMaterialShaderKeyProperties.m_shadowSoftness.setShadowSoftness(theGeneratedKey, maxSoftShadowQuality);
1824
1825 // Skin
1826 const auto boneCount = model.skin ? model.skin->boneCount :
1827 model.skeleton ? model.skeleton->boneCount : 0;
1828 defaultMaterialShaderKeyProperties.m_boneCount.setValue(theGeneratedKey, boneCount);
1829 if (auto idJoint = theSubset.rhi.ia.inputs.indexOf(QSSGRhiInputAssemblerState::JointSemantic); idJoint != -1) {
1830 const auto attr = theSubset.rhi.ia.inputLayout.attributeAt(idJoint);
1831 defaultMaterialShaderKeyProperties.m_usesFloatJointIndices.setValue(theGeneratedKey, checkF32TypeIndex(attr->format()));
1832 }
1833
1834 // Instancing
1835 bool usesInstancing = theModelContext.model.instancing()
1836 && rhiCtx->rhi()->isFeatureSupported(QRhi::Instancing);
1837 defaultMaterialShaderKeyProperties.m_usesInstancing.setValue(theGeneratedKey, usesInstancing);
1838 // Morphing
1839 defaultMaterialShaderKeyProperties.m_targetCount.setValue(theGeneratedKey,
1840 theSubset.rhi.ia.targetCount);
1841 defaultMaterialShaderKeyProperties.m_targetPositionOffset.setValue(theGeneratedKey,
1842 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::PositionSemantic]);
1843 defaultMaterialShaderKeyProperties.m_targetNormalOffset.setValue(theGeneratedKey,
1844 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::NormalSemantic]);
1845 defaultMaterialShaderKeyProperties.m_targetTangentOffset.setValue(theGeneratedKey,
1846 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::TangentSemantic]);
1847 defaultMaterialShaderKeyProperties.m_targetBinormalOffset.setValue(theGeneratedKey,
1848 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::BinormalSemantic]);
1849 defaultMaterialShaderKeyProperties.m_targetTexCoord0Offset.setValue(theGeneratedKey,
1850 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::TexCoord0Semantic]);
1851 defaultMaterialShaderKeyProperties.m_targetTexCoord1Offset.setValue(theGeneratedKey,
1852 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::TexCoord1Semantic]);
1853 defaultMaterialShaderKeyProperties.m_targetColorOffset.setValue(theGeneratedKey,
1854 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::ColorSemantic]);
1855
1856 if (theMaterial.m_iblProbe)
1857 theMaterial.m_iblProbe->clearDirty();
1858
1859 new (theRenderableObject) QSSGSubsetRenderable(QSSGSubsetRenderable::Type::CustomMaterialMeshSubset,
1860 renderableFlags,
1861 theModelCenter,
1862 renderer,
1863 theSubset,
1864 theModelContext,
1865 subsetOpacity,
1866 subsetLevelOfDetail,
1867 theMaterial,
1868 firstImage,
1869 theGeneratedKey,
1870 lights,
1871 anyLightHasShadows);
1872 }
1873 if (theRenderableObject) // NOTE: Should just go in with the ctor args
1874 theRenderableObject->camdistSq = getCameraDistanceSq(*theRenderableObject, allCameraData[0]);
1875 }
1876
1877 // If the indices don't match then something's off and we need to adjust the subset renderable list size.
1878 if (Q_UNLIKELY(idx != subsetIdx))
1879 renderableSubsets.mSize = subsetIdx; // subsetIdx == next_subsetIdx == size
1880
1881 for (auto &ro : renderableSubsets) {
1882 const auto depthMode = ro.depthWriteMode;
1883 hasDepthWriteObjects |= (depthMode == QSSGDepthDrawMode::Always || depthMode == QSSGDepthDrawMode::OpaqueOnly);
1884 enum ObjectType : quint8 { ScreenTexture, Transparent, Opaque };
1885 static constexpr DepthPrepassObject ppState[][2] = { {DepthPrepassObject::None, DepthPrepassObject::ScreenTexture},
1886 {DepthPrepassObject::None, DepthPrepassObject::Transparent},
1887 {DepthPrepassObject::None, DepthPrepassObject::Opaque} };
1888
1889 if (ro.renderableFlags.requiresScreenTexture()) {
1890 depthPrepassObjectsState |= DepthPrepassObjectStateT(ppState[ObjectType::ScreenTexture][size_t(depthMode == QSSGDepthDrawMode::OpaquePrePass)]);
1891 screenTextureObjects.push_back({&ro, ro.camdistSq, model.tag});
1892 } else if (ro.renderableFlags.hasTransparency()) {
1893 depthPrepassObjectsState |= DepthPrepassObjectStateT(ppState[ObjectType::Transparent][size_t(depthMode == QSSGDepthDrawMode::OpaquePrePass)]);
1894 transparentObjects.push_back({&ro, ro.camdistSq, model.tag});
1895 } else {
1896 depthPrepassObjectsState |= DepthPrepassObjectStateT(ppState[ObjectType::Opaque][size_t(depthMode == QSSGDepthDrawMode::OpaquePrePass)]);
1897 opaqueObjects.push_back({&ro, ro.camdistSq, model.tag});
1898 }
1899
1900 if (ro.renderableFlags.usedInBakedLighting())
1901 bakedLightingObjects.push_back({&ro, ro.camdistSq, model.tag});
1902 }
1903
1904 if (!bakedLightingObjects.isEmpty())
1905 bakedLightingModels.push_back(QSSGBakedLightingModel(&model, bakedLightingObjects));
1906 }
1907
1908 return wasDirty;
1909}
1910
1911bool QSSGLayerRenderData::prepareParticlesForRender(const RenderableNodeEntries &renderableParticles, const QSSGRenderCameraData &cameraData, QSSGLayerRenderPreparationResultFlags &ioFlags)
1912{
1913 QSSG_ASSERT(particlesEnabled, return false);
1914
1915 QSSGRenderContextInterface &contextInterface = *renderer->contextInterface();
1916
1917 bool dirty = false;
1918
1919 //
1920 auto &opaqueObjects = opaqueObjectStore[0];
1921 auto &transparentObjects = transparentObjectStore[0];
1922 auto &screenTextureObjects = screenTextureObjectStore[0];
1923
1924 for (auto &renderable : renderableParticles) {
1925 QSSGShaderParticleMaterialKeyProperties &properties = particleMaterialShaderKeyProperties;
1926
1927 QSSGRenderParticles &particles = *static_cast<QSSGRenderParticles *>(renderable.node);
1928 QSSGShaderParticleMaterialKey &theGeneratedKey(particles.materialKey);
1929 const auto &lights = renderable.lights;
1930
1931 QSSGRenderableObjectFlags renderableFlags;
1932 renderableFlags.setCastsShadows(false);
1933 renderableFlags.setReceivesShadows(false);
1934 renderableFlags.setHasAttributePosition(true);
1935 renderableFlags.setHasAttributeNormal(true);
1936 renderableFlags.setHasAttributeTexCoord0(true);
1937 renderableFlags.setHasAttributeColor(true);
1938 renderableFlags.setHasTransparency(particles.m_hasTransparency);
1939 renderableFlags.setCastsReflections(particles.m_castsReflections);
1940 if (particles.m_hasTransparency && particles.m_blendMode != QSSGRenderParticles::BlendMode::SourceOver)
1941 ioFlags.setHasCustomBlendMode(true);
1942
1943 properties.m_isLineParticle.setValue(theGeneratedKey, particles.m_featureLevel >= QSSGRenderParticles::FeatureLevel::Line);
1944 const bool animated = particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::LineAnimated
1945 || particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::Animated
1946 || particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::AnimatedVLight
1947 || particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::LineAnimatedVLight;
1948 const bool mapped = particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::LineMapped
1949 || particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::Mapped
1950 || particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::MappedVLight
1951 || particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::LineMappedVLight;
1952 const bool lit = particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::LineVLight
1953 || particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::AnimatedVLight
1954 || particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::MappedVLight
1955 || particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::LineMappedVLight
1956 || particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::LineAnimatedVLight
1957 || particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::SimpleVLight;
1958 properties.m_isAnimated.setValue(theGeneratedKey, animated);
1959 properties.m_isMapped.setValue(theGeneratedKey, mapped);
1960 properties.m_hasLighting.setValue(theGeneratedKey, lit);
1961 properties.m_viewCount.setValue(theGeneratedKey, layer.viewCount);
1962 if (renderableFlags.hasTransparency() && orderIndependentTransparencyEnabled) {
1963 properties.m_orderIndependentTransparency.setValue(theGeneratedKey, int(layer.oitMethod));
1964 if (layer.oitMethod == QSSGRenderLayer::OITMethod::LinkedList)
1965 properties.m_oitMSAA.setValue(theGeneratedKey, contextInterface.rhiContext()->mainPassSampleCount() > 1);
1966 } else {
1967 properties.m_orderIndependentTransparency.setValue(theGeneratedKey, int(0));
1968 }
1969
1970 float opacity = getGlobalOpacity(particles);
1971 QVector3D center(particles.m_particleBuffer.bounds().center());
1972 center = QSSGUtils::mat44::transform(getGlobalTransform(particles), center);
1973
1974 QSSGRenderableImage *firstImage = nullptr;
1975 if (particles.m_sprite) {
1976 const auto &bufferManager = contextInterface.bufferManager();
1977
1978 if (particles.m_sprite->clearDirty())
1979 dirty = true;
1980
1981 const QSSGRenderImageTexture texture = bufferManager->loadRenderImage(particles.m_sprite);
1982 QSSGRenderableImage *theImage = RENDER_FRAME_NEW<QSSGRenderableImage>(contextInterface, QSSGRenderableImage::Type::Diffuse, *particles.m_sprite, texture);
1983 firstImage = theImage;
1984 properties.m_isSpriteLinear.setValue(theGeneratedKey, texture.m_flags.isLinear());
1985 }
1986
1987 QSSGRenderableImage *colorTable = nullptr;
1988 if (particles.m_colorTable) {
1989 const auto &bufferManager = contextInterface.bufferManager();
1990
1991 if (particles.m_colorTable->clearDirty())
1992 dirty = true;
1993
1994 const QSSGRenderImageTexture texture = bufferManager->loadRenderImage(particles.m_colorTable);
1995
1996 QSSGRenderableImage *theImage = RENDER_FRAME_NEW<QSSGRenderableImage>(contextInterface, QSSGRenderableImage::Type::Diffuse, *particles.m_colorTable, texture);
1997 colorTable = theImage;
1998 properties.m_isColorTableLinear.setValue(theGeneratedKey, texture.m_flags.isLinear());
1999 }
2000
2001 if (opacity > 0.0f && particles.m_particleBuffer.particleCount()) {
2002 const auto globalTransform = getGlobalTransform(particles);
2003 auto *theRenderableObject = RENDER_FRAME_NEW<QSSGParticlesRenderable>(contextInterface,
2004 renderableFlags,
2005 center,
2006 renderer,
2007 globalTransform,
2008 particles,
2009 firstImage,
2010 colorTable,
2011 lights,
2012 opacity,
2013 theGeneratedKey);
2014 if (theRenderableObject) {
2015 if (theRenderableObject->renderableFlags.requiresScreenTexture())
2016 screenTextureObjects.push_back({theRenderableObject, getCameraDistanceSq(*theRenderableObject, cameraData), particles.tag});
2017 else if (theRenderableObject->renderableFlags.hasTransparency())
2018 transparentObjects.push_back({theRenderableObject, getCameraDistanceSq(*theRenderableObject, cameraData), particles.tag});
2019 else
2020 opaqueObjects.push_back({theRenderableObject, getCameraDistanceSq(*theRenderableObject, cameraData), particles.tag});
2021 }
2022 }
2023 }
2024
2025 return dirty;
2026}
2027
2028void QSSGLayerRenderData::prepareResourceLoaders()
2029{
2030 QSSGRenderContextInterface &contextInterface = *renderer->contextInterface();
2031 const auto &bufferManager = contextInterface.bufferManager();
2032
2033 for (const auto resourceLoader : std::as_const(layer.resourceLoaders))
2034 bufferManager->processResourceLoader(static_cast<QSSGRenderResourceLoader *>(resourceLoader));
2035}
2036
2037void QSSGLayerRenderData::prepareReflectionProbesForRender()
2038{
2039 const auto probeCount = reflectionProbesView.size();
2040 requestReflectionMapManager(); // ensure that we have a reflection map manager
2041
2042 for (int i = 0; i < probeCount; i++) {
2043 QSSGRenderReflectionProbe* probe = reflectionProbesView[i];
2044
2045 QMatrix4x4 probeTransform = getGlobalTransform(*probe);
2046
2047 int reflectionObjectCount = 0;
2048 QVector3D probeExtent = probe->boxSize / 2;
2049 QSSGBounds3 probeBound = QSSGBounds3::centerExtents(QSSGRenderNode::getGlobalPos(probeTransform) + probe->boxOffset, probeExtent);
2050
2051 const auto injectProbe = [&](const QSSGRenderableObjectHandle &handle) {
2052 if (handle.obj->renderableFlags.testFlag(QSSGRenderableObjectFlag::ReceivesReflections)
2053 && !(handle.obj->type == QSSGRenderableObject::Type::Particles)) {
2054 QSSGSubsetRenderable* renderableObj = static_cast<QSSGSubsetRenderable*>(handle.obj);
2055 QSSGBounds3 nodeBound = renderableObj->bounds;
2056 QVector4D vmin(nodeBound.minimum, 1.0);
2057 QVector4D vmax(nodeBound.maximum, 1.0);
2058 const QMatrix4x4 &renderableTransform = renderableObj->modelContext.globalTransform;
2059 vmin = renderableTransform * vmin;
2060 vmax = renderableTransform * vmax;
2061 nodeBound.minimum = vmin.toVector3D();
2062 nodeBound.maximum = vmax.toVector3D();
2063 if (probeBound.intersects(nodeBound)) {
2064 QVector3D nodeBoundCenter = nodeBound.center();
2065 QVector3D probeBoundCenter = probeBound.center();
2066 float distance = nodeBoundCenter.distanceToPoint(probeBoundCenter);
2067 if (renderableObj->reflectionProbeIndex == -1 || distance < renderableObj->distanceFromReflectionProbe) {
2068 renderableObj->reflectionProbeIndex = i;
2069 renderableObj->distanceFromReflectionProbe = distance;
2070 renderableObj->reflectionProbe.parallaxCorrection = probe->parallaxCorrection;
2071 renderableObj->reflectionProbe.probeCubeMapCenter = QSSGRenderNode::getGlobalPos(probeTransform);
2072 renderableObj->reflectionProbe.probeBoxMax = probeBound.maximum;
2073 renderableObj->reflectionProbe.probeBoxMin = probeBound.minimum;
2074 renderableObj->reflectionProbe.enabled = true;
2075 reflectionObjectCount++;
2076 }
2077 }
2078 }
2079 };
2080
2081 const auto &transparentObjects = std::as_const(transparentObjectStore[0]);
2082 const auto &opaqueObjects = std::as_const(opaqueObjectStore[0]);
2083 const auto &screenTextureObjects = std::as_const(screenTextureObjectStore[0]);
2084
2085 for (const auto &handle : std::as_const(transparentObjects))
2086 injectProbe(handle);
2087
2088 for (const auto &handle : std::as_const(opaqueObjects))
2089 injectProbe(handle);
2090
2091 for (const auto &handle : std::as_const(screenTextureObjects))
2092 injectProbe(handle);
2093
2094 if (probe->texture)
2095 reflectionMapManager->addTexturedReflectionMapEntry(i, *probe);
2096 else if (reflectionObjectCount > 0)
2097 reflectionMapManager->addReflectionMapEntry(i, *probe);
2098 }
2099}
2100
2101static bool scopeLight(QSSGRenderNode *node, QSSGRenderNode *lightScope)
2102{
2103 // check if the node is parent of the lightScope
2104 while (node) {
2105 if (node == lightScope)
2106 return true;
2107 node = node->parent;
2108 }
2109 return false;
2110}
2111
2112static const int REDUCED_MAX_LIGHT_COUNT_THRESHOLD_BYTES = 5200; // 325 vec4s
2113
2114static inline int effectiveMaxLightCount(const QSSGShaderFeatures &features)
2115{
2116 if (features.isSet(QSSGShaderFeatures::Feature::ReduceMaxNumLights))
2117 return QSSG_REDUCED_MAX_NUM_LIGHTS;
2118
2119 return QSSG_MAX_NUM_LIGHTS;
2120}
2121
2122static inline int effectiveMaxDirectionalLightCount(const QSSGShaderFeatures &features)
2123{
2124 if (features.isSet(QSSGShaderFeatures::Feature::ReduceMaxNumLights))
2125 return QSSG_REDUCED_MAX_NUM_DIRECTIONAL_LIGHTS;
2126
2127 return QSSG_MAX_NUM_DIRECTIONAL_LIGHTS;
2128}
2129
2130static void updateDirtySkeletons(const QSSGLayerRenderData &renderData, const QSSGLayerRenderData::QSSGModelsView &renderableNodes)
2131{
2132 // First model using skeleton clears the dirty flag so we need another mechanism
2133 // to tell to the other models the skeleton is dirty.
2134 QSet<QSSGRenderSkeleton *> dirtySkeletons;
2135 for (const auto &modelNode : std::as_const(renderableNodes)) {
2136 QSSGRenderSkeleton *skeletonNode = modelNode->skeleton;
2137 bool hcj = false;
2138 if (skeletonNode) {
2139 const bool dirtySkeleton = dirtySkeletons.contains(skeletonNode);
2140 const bool hasDirtyNonJoints = (skeletonNode->containsNonJointNodes
2141 && (hasDirtyNonJointNodes(skeletonNode, hcj) || dirtySkeleton));
2142 if (skeletonNode->skinningDirty || hasDirtyNonJoints) {
2143 Q_ASSERT(!skeletonNode->isDirty(QSSGRenderNode::DirtyFlag::GlobalValuesDirty));
2144 skeletonNode->boneTransformsDirty = false;
2145 if (hasDirtyNonJoints && !dirtySkeleton)
2146 dirtySkeletons.insert(skeletonNode);
2147 skeletonNode->skinningDirty = false;
2148 const qsizetype dataSize = BONEDATASIZE4ID(skeletonNode->maxIndex);
2149 if (skeletonNode->boneData.size() < dataSize)
2150 skeletonNode->boneData.resize(dataSize);
2151 skeletonNode->containsNonJointNodes = false;
2152 for (auto &child : skeletonNode->children)
2153 collectBoneTransforms(renderData, &child, skeletonNode, modelNode->inverseBindPoses);
2154 }
2155 skeletonNode->boneCount = skeletonNode->boneData.size() / 2 / 4 / 16;
2156 const int boneTexWidth = qCeil(qSqrt(skeletonNode->boneCount * 4 * 2));
2157 skeletonNode->boneTexData.setSize(QSize(boneTexWidth, boneTexWidth));
2158 skeletonNode->boneData.resize(boneTexWidth * boneTexWidth * 16);
2159 skeletonNode->boneTexData.setTextureData(skeletonNode->boneData);
2160 }
2161 const int numMorphTarget = modelNode->morphTargets.size();
2162 for (int i = 0; i < numMorphTarget; ++i) {
2163 auto morphTarget = static_cast<const QSSGRenderMorphTarget *>(modelNode->morphTargets.at(i));
2164 modelNode->morphWeights[i] = morphTarget->weight;
2165 modelNode->morphAttributes[i] = morphTarget->attributes;
2167 modelNode->morphAttributes[i] &= 0x1; // MorphTarget.Position
2169 modelNode->morphAttributes[i] &= 0x3; // MorphTarget.Position | MorphTarget.Normal
2170 }
2171 }
2172
2173 dirtySkeletons.clear();
2174}
2175
2176QSSGNodeIdList QSSGLayerRenderData::filter(const QSSGGlobalRenderNodeData::LayerNodeView &layerNodes,
2177 quint32 layerMask,
2178 quint32 typeMask)
2179{
2180 QSSGNodeIdList res;
2181 for (auto *n : layerNodes) {
2182 // Check mask
2183 if (((quint32(n->type) & typeMask) == typeMask) && n->tag.isSet(layerMask))
2184 res.push_back(QSSGNodeId(reinterpret_cast<quintptr>(n)));
2185 }
2186
2187 return res;
2188}
2189
2190void QSSGLayerRenderData::categorizeAndFilterNodes(const QSSGGlobalRenderNodeData::LayerNodeView &layerNodes,
2191 QSSGLayerRenderData::NodeCollection &nodeCollection,
2192 quint32 layerMask)
2193{
2194 auto &modelsView = nodeCollection.modelsView;
2195 auto &particlesView = nodeCollection.particlesView;
2196 auto &item2DsView = nodeCollection.item2DsView;
2197 auto &camerasView = nodeCollection.camerasView;
2198 auto &lightsView = nodeCollection.lightsView;
2199 auto &reflectionProbesView = nodeCollection.reflectionProbesView;
2200 auto &nonCategorizedView = nodeCollection.nonCategorizedView;
2201
2202 auto &layerNodesCategorized = nodeCollection.layerNodesCategorized;
2203
2204 modelsView.clear();
2205 particlesView.clear();
2206 item2DsView.clear();
2207 camerasView.clear();
2208 lightsView.clear();
2209 reflectionProbesView.clear();
2210 nonCategorizedView.clear();
2211
2212 enum NodeType : size_t { Model = 0, Particles, Item2D, Camera, ImportedCamera, Light, ReflectionProbe, Other, Inactive };
2213 const auto nodeType = [layerMask](QSSGRenderNode *node) -> NodeType {
2214 if (!(node->getGlobalState(QSSGRenderNode::GlobalState::Active)))
2215 return NodeType::Inactive;
2216 if (!(node->tag.isSet(layerMask)))
2217 return NodeType::Other;
2218 switch (node->type) {
2219 case QSSGRenderGraphObject::Type::Model: return NodeType::Model;
2220 case QSSGRenderGraphObject::Type::Particles: return NodeType::Particles;
2221 case QSSGRenderGraphObject::Type::Item2D: return NodeType::Item2D;
2222 case QSSGRenderGraphObject::Type::ReflectionProbe: return NodeType::ReflectionProbe;
2223 default: break;
2224 }
2225
2226 if (QSSGRenderGraphObject::isCamera(node->type)) {
2227 // NOTE: To keep compatibility with old code, we collect shared and non-shared cameras differently,
2228 // so that shared cameras (import scene) are picked after non-shared ones.
2229 const bool isImported = node->getGlobalState(QSSGRenderNode::GlobalState::Imported);
2230 constexpr NodeType cameraTypes[2] { NodeType::Camera, NodeType::ImportedCamera };
2231 return cameraTypes[size_t(isImported)];
2232 }
2233 if (QSSGRenderGraphObject::isLight(node->type))
2234 return NodeType::Light;
2235
2236 return NodeType::Other;
2237 };
2238 // sort nodes by type - We could do this on insert, but it's not given that it would be beneficial.
2239 // Depending on how we want to handle the nodes later it might just not give us anything
2240 // so, keep it simple for now.
2241 // We could also speed up this by having the pointer and the type in the same struct and sort without
2242 // indirection. However, that' slightly less convenient and the idea here is that we don't process
2243 // this list unless things change, which is not something that should happen often if the user is
2244 // concerned about performance, as it means we need to reevaluate the whole scene anyway.
2245 {
2246 // Sort the nodes by type (we copy the pointers to avoid sorting the original list,
2247 // which is stored based on the nodes' order in the world tree).
2248 layerNodesCategorized = { layerNodes.begin(), layerNodes.end() };
2249 // NOTE: Due to the ordering of item2ds, we need to use stable_sort.
2250 std::stable_sort(layerNodesCategorized.begin(), layerNodesCategorized.end(), [nodeType](QSSGRenderNode *a, QSSGRenderNode *b) {
2251 return nodeType(a) < nodeType(b);
2252 });
2253 }
2254
2255 // Group nodes by type inline and keep track of the individual parts using QSSGDataViews
2256 const LayerNodeStatResult stat = statLayerNodes(layerNodesCategorized, layerMask);
2257
2258 // Go through the sorted nodes and create the views
2259 size_t next = 0;
2260
2261 if (stat.modelCount > 0) {
2262 modelsView = QSSGModelsView((QSSGRenderModel **)(layerNodesCategorized.data() + next), stat.modelCount);
2263 next = modelsView.size();
2264 }
2265 if (stat.particlesCount > 0) {
2266 particlesView = QSSGParticlesView((QSSGRenderParticles **)(layerNodesCategorized.data() + next), stat.particlesCount);
2267 next += particlesView.size();
2268 }
2269 if (stat.item2DCount > 0) {
2270 item2DsView = QSSGItem2DsView((QSSGRenderItem2D **)(layerNodesCategorized.data() + next), stat.item2DCount);
2271 next += item2DsView.size();
2272 }
2273 if (stat.cameraCount > 0) {
2274 camerasView = QSSGCamerasView((QSSGRenderCamera **)(layerNodesCategorized.data() + next), stat.cameraCount);
2275 next += camerasView.size();
2276 }
2277 if (stat.lightCount > 0) {
2278 lightsView = QSSGLightsView((QSSGRenderLight **)(layerNodesCategorized.data() + next), stat.lightCount);
2279 next += lightsView.size();
2280 }
2281 if (stat.reflectionProbeCount > 0) {
2282 reflectionProbesView = QSSGReflectionProbesView((QSSGRenderReflectionProbe **)(layerNodesCategorized.data() + next), stat.reflectionProbeCount);
2283 next += reflectionProbesView.size();
2284 }
2285 if (stat.otherCount > 0) {
2286 nonCategorizedView = QSSGNonCategorizedView((QSSGRenderNode **)(layerNodesCategorized.data() + next), stat.otherCount);
2287 next += nonCategorizedView.size();
2288 (void)next;
2289 }
2290}
2291
2292void QSSGLayerRenderData::updateFilteredLayerNodes(quint32 layerMask)
2293{
2294 categorizeAndFilterNodes(layerNodes, nodeCollection, layerMask);
2295
2296 // FIXME: Compatability with old code (Will remove later).
2297 // NOTE: see resetForFrame() as well for extensions usage
2298 renderableModels.clear();
2299 renderableParticles.clear();
2300 renderableModels.reserve(modelsView.size());
2301 renderableParticles.reserve(particlesView.size());
2302
2303 renderableModels = {modelsView.begin(), modelsView.end()};
2304 renderableParticles = {particlesView.begin(), particlesView.end()};
2305}
2306
2307void QSSGLayerRenderData::prepareForRender()
2308{
2309 QSSG_ASSERT_X(layerPrepResult.isNull(), "Prep-result was not reset for render!", layerPrepResult = {});
2310
2311 QRect theViewport(renderer->viewport());
2312
2313 // NOTE: The renderer won't change in practice (after being set the first time), but just update
2314 // it anyways.
2315 frameData.m_ctx = renderer->contextInterface();
2316 frameData.clear();
2317
2318 // Create base pipeline state
2319 ps = {}; // Reset
2320 ps.viewport = { float(theViewport.x()), float(theViewport.y()), float(theViewport.width()), float(theViewport.height()), 0.0f, 1.0f };
2321 if (layer.scissorRect.isValid()) {
2322 ps.flags |= QSSGRhiGraphicsPipelineState::Flag::UsesScissor;
2323 ps.scissor = { layer.scissorRect.x(),
2324 theViewport.height() - (layer.scissorRect.y() + layer.scissorRect.height()),
2325 layer.scissorRect.width(),
2326 layer.scissorRect.height() };
2327 }
2328
2329 ps.depthFunc = QRhiGraphicsPipeline::LessOrEqual;
2330 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, false);
2331
2332 // Enable Wireframe mode
2333 ps.polygonMode = layer.wireframeMode ? QRhiGraphicsPipeline::Line : QRhiGraphicsPipeline::Fill;
2334
2335 bool wasDirty = false;
2336 bool wasDataDirty = false;
2337 wasDirty = layer.isDirty();
2338
2339 const bool shouldDisableInternalPasses = (layer.renderOverrides & size_t(QSSGRenderLayer::RenderOverrides::DisableInternalPasses)) != 0;
2340 wasDirty |= (shouldDisableInternalPasses != disableMainPasses);
2341 disableMainPasses = shouldDisableInternalPasses;
2342
2343 // NOTE: Prep-state is implicitly set to "DataPrep"
2344 layerPrepResult = { theViewport, layer };
2345
2346 // SSAO
2347 const bool SSAOEnabled = layer.ssaoEnabled();
2348 layerPrepResult.flags.setRequiresSsaoPass(SSAOEnabled);
2349 features.set(QSSGShaderFeatures::Feature::Ssao, SSAOEnabled);
2350
2351 // Effects
2352 bool requiresDepthTexture = SSAOEnabled;
2353 bool requiresNormalTexture = false;
2354 bool requiresMotionVectorTexture = false;
2355 for (QSSGRenderEffect *theEffect = layer.firstEffect; theEffect; theEffect = theEffect->m_nextEffect) {
2356 if (theEffect->isDirty()) {
2357 wasDirty = true;
2358 theEffect->clearDirty();
2359 }
2360 if (theEffect->testFlag(QSSGRenderEffect::Flags::UsesDepthTexture))
2361 requiresDepthTexture = true;
2362 if (theEffect->testFlag(QSSGRenderEffect::Flags::UsesNormalTexture))
2363 requiresNormalTexture = true;
2364 if (theEffect->testFlag(QSSGRenderEffect::Flags::UsesMotionVectorTexture))
2365 requiresMotionVectorTexture = true;
2366 }
2367
2368 const auto &rhiCtx = renderer->contextInterface()->rhiContext();
2369 orderIndependentTransparencyEnabled = (layer.oitMethod != QSSGRenderLayer::OITMethod::None);
2370 if (layer.oitMethod == QSSGRenderLayer::OITMethod::WeightedBlended) {
2371 orderIndependentTransparencyEnabled = rhiCtx->rhi()->isFeatureSupported(QRhi::PerRenderTargetBlending);
2372 if (rhiCtx->mainPassSampleCount() > 1)
2373 orderIndependentTransparencyEnabled |= rhiCtx->rhi()->isFeatureSupported(QRhi::TexelFetch) && rhiCtx->rhi()->isFeatureSupported(QRhi::SampleVariables);
2374 if (!orderIndependentTransparencyEnabled && !oitWarningUnsupportedShown) {
2375 qCWarning(lcQuick3DRender) << "WeightedBlended OIT is requested, but it is not supported.";
2376 oitWarningUnsupportedShown = true;
2377 }
2378 } else if (layer.oitMethod == QSSGRenderLayer::OITMethod::LinkedList) {
2379 if (rhiCtx->mainPassSampleCount() > 1)
2380 orderIndependentTransparencyEnabled |= rhiCtx->rhi()->isFeatureSupported(QRhi::SampleVariables);
2381 if (!orderIndependentTransparencyEnabled && !oitWarningUnsupportedShown) {
2382 qCWarning(lcQuick3DRender) << "LinkedList OIT is requested, but it is not supported.";
2383 oitWarningUnsupportedShown = true;
2384 }
2385 }
2386 if (layer.oitMethodDirty) {
2387 oitRenderContext.reset();
2388 for (auto &renderResult : renderResults)
2389 renderResult.reset();
2390 }
2391
2392 layerPrepResult.flags.setRequiresDepthTexture(requiresDepthTexture);
2393
2394 layerPrepResult.flags.setRequiresNormalTexture(requiresNormalTexture);
2395
2396 layerPrepResult.flags.setRequiresMotionVectorPass(requiresMotionVectorTexture);
2397
2398 // Tonemapping. Except when there are effects, then it is up to the
2399 // last pass of the last effect to perform tonemapping.
2400 if (!layer.firstEffect)
2401 QSSGLayerRenderData::setTonemapFeatures(features, layer.tonemapMode);
2402
2403 // We may not be able to have an array of 15 light struct elements in
2404 // the shaders. Switch on the reduced-max-number-of-lights feature
2405 // if necessary. In practice this is relevant with OpenGL ES 3.0 or
2406 // 2.0, because there are still implementations in use that only
2407 // support the spec mandated minimum of 224 vec4s (so 3584 bytes).
2408 if (rhiCtx->rhi()->resourceLimit(QRhi::MaxUniformBufferRange) < REDUCED_MAX_LIGHT_COUNT_THRESHOLD_BYTES) {
2409 features.set(QSSGShaderFeatures::Feature::ReduceMaxNumLights, true);
2410 static bool notified = false;
2411 if (!notified) {
2412 notified = true;
2413 qCDebug(lcQuick3DRender, "Qt Quick 3D maximum number of lights has been reduced from %d to %d due to the graphics driver's limitations",
2414 QSSG_MAX_NUM_LIGHTS, QSSG_REDUCED_MAX_NUM_LIGHTS);
2415 }
2416 }
2417
2418 // IBL Lightprobe Image
2419 QSSGRenderImageTexture lightProbeTexture;
2420 if (layer.lightProbe) {
2421 const auto &lightProbeSettings = layer.lightProbeSettings;
2422 if (layer.lightProbe->m_format == QSSGRenderTextureFormat::Unknown) {
2423 // Choose on a format that makes sense for a light probe
2424 // At this point it's just a suggestion
2425 if (renderer->contextInterface()->rhiContext()->rhi()->isTextureFormatSupported(QRhiTexture::RGBA16F))
2426 layer.lightProbe->m_format = QSSGRenderTextureFormat::RGBA16F;
2427 else
2428 layer.lightProbe->m_format = QSSGRenderTextureFormat::RGBE8;
2429 }
2430
2431 if (layer.lightProbe->clearDirty())
2432 wasDataDirty = true;
2433
2434 // NOTE: This call can lead to rendering (of envmap) and a texture upload
2435 lightProbeTexture = renderer->contextInterface()->bufferManager()->loadRenderImage(layer.lightProbe, QSSGBufferManager::MipModeBsdf);
2436 if (lightProbeTexture.m_texture) {
2437
2438 features.set(QSSGShaderFeatures::Feature::LightProbe, true);
2439 features.set(QSSGShaderFeatures::Feature::IblOrientation, !lightProbeSettings.probeOrientation.isIdentity());
2440
2441 // By this point we will know what the actual texture format of the light probe is
2442 // Check if using RGBE format light probe texture (the Rhi format will be RGBA8)
2443 if (lightProbeTexture.m_flags.isRgbe8())
2444 features.set(QSSGShaderFeatures::Feature::RGBELightProbe, true);
2445 } else {
2446 layer.lightProbe = nullptr;
2447 }
2448
2449 const bool forceIblExposureValues = (features.isSet(QSSGShaderFeatures::Feature::LightProbe) && layer.tonemapMode == QSSGRenderLayer::TonemapMode::Custom);
2450 features.set(QSSGShaderFeatures::Feature::ForceIblExposure, forceIblExposureValues);
2451 }
2452
2453 frameData.m_ctx->bufferManager()->setLightmapSource(layer.lightmapSource);
2454
2455 // Update the node data version for this layer.
2456 // This version should only change if the world tree was re-indexed.
2457 version = nodeData->version();
2458
2459 // We're using/updating the node data directly.
2460 // NOTE: These are the transforms and opacities for all nodes for all layers/views.
2461 // We'll just use or update the ones the one for this layer.
2462 auto &globalTransforms = nodeData->globalTransforms;
2463 auto &globalOpacities = nodeData->globalOpacities;
2464 auto &instanceTransforms = nodeData->instanceTransforms;
2465
2466 ///////// 2 - START LAYER
2467 QSSGRenderDataHelpers::GlobalStateResultT globalStateResult = QSSGRenderDataHelpers::GlobalStateResult::None;
2468
2469 const bool layerTreeWasDirty = layer.isDirty(QSSGRenderLayer::DirtyFlag::TreeDirty);
2470 layer.clearDirty(QSSGRenderLayer::DirtyFlag::TreeDirty);
2471 if (layerTreeWasDirty) {
2472 wasDataDirty = true;
2473 layerNodes = nodeData->getLayerNodeView(layer);
2474 } else {
2475 for (auto &node : layerNodes)
2476 globalStateResult |= QSSGRenderDataHelpers::updateGlobalNodeState(node, version);
2477
2478 bool transformAndOpacityDirty = false;
2479 for (auto &node : layerNodes)
2480 transformAndOpacityDirty |= QSSGRenderDataHelpers::calcGlobalNodeData<QSSGRenderDataHelpers::Strategy::Update>(node, version, globalTransforms, globalOpacities);
2481
2482 // FIXME: We shouldn't need to re-create all the instance transforms even when instancing isn't used...
2483 if (transformAndOpacityDirty) {
2484 for (const auto &node : layerNodes)
2485 wasDataDirty |= QSSGRenderDataHelpers::calcInstanceTransforms(node, version, globalTransforms, instanceTransforms);
2486 }
2487
2488 wasDataDirty |= transformAndOpacityDirty;
2489 }
2490
2491 // Check if we have an explicit camera!
2492 // NOTE: We only do layering if we have an explicit camera!!!
2493
2494 const bool hasExplicitCamera = (layer.explicitCameras.size() != 0);
2495 bool cameraLayerMaskDirty = false;
2496 quint32 layerMask = QSSGRenderCamera::LayerMaskAll;
2497 if (hasExplicitCamera) {
2498 QSSGRenderCamera *explicitCamera = layer.explicitCameras[0];
2499 layerMask = explicitCamera->tag.value();
2500 cameraLayerMaskDirty = explicitCamera->isDirty(QSSGRenderCamera::DirtyFlag::LayerMaskDirty);
2501 explicitCamera->clearDirty(QSSGRenderCamera::DirtyFlag::LayerMaskDirty);
2502 }
2503
2504 const bool restatNodes = (layerTreeWasDirty || (globalStateResult & QSSGRenderDataHelpers::GlobalStateResult::ActiveChanged) || cameraLayerMaskDirty);
2505
2506 if (restatNodes)
2507 updateFilteredLayerNodes(layerMask);
2508
2509 // Cameras
2510 // 1. If there's an explicit camera set and it's active (visible) we'll use that.
2511 // 2. ... if the explicitly set camera is not visible, no further attempts will be done.
2512 // 3. If no explicit camera is set, we'll search and pick the first active camera.
2513 QSSGRenderCamera::Configuration cameraConfig { renderer->dpr(), layer.isSsaaEnabled() ? layer.ssaaMultiplier : 1.0f };
2514 renderedCameras.clear();
2515 if (!layer.explicitCameras.isEmpty()) {
2516 for (QSSGRenderCamera *cam : std::as_const(layer.explicitCameras)) {
2517 // 1.
2518 if (cam->getGlobalState(QSSGRenderCamera::GlobalState::Active)) {
2519 const bool computeFrustumSucceeded = cam->calculateProjection(theViewport, cameraConfig);
2520 if (Q_LIKELY(computeFrustumSucceeded))
2521 renderedCameras.append(cam);
2522 else
2523 qCCritical(INTERNAL_ERROR, "Failed to calculate camera frustum");
2524 }
2525 }
2526 // 2.
2527 } else if (QSSG_GUARD_X(layer.viewCount == 1, "Multiview rendering requires explicit cameras to be set!.")) {
2528 // NOTE: This path can never be hit with multiview, hence the guard.
2529 // (Multiview will always have explicit cameras set.)
2530
2531 // 3.
2532 for (auto iter = camerasView.begin(); renderedCameras.isEmpty() && iter != camerasView.end(); iter++) {
2533 QSSGRenderCamera *theCamera = *iter;
2534 if (theCamera->getGlobalState(QSSGRenderCamera::GlobalState::Active)) {
2535 const bool computeFrustumSucceeded = theCamera->calculateProjection(theViewport, cameraConfig);
2536 if (Q_LIKELY(computeFrustumSucceeded))
2537 renderedCameras.append(theCamera);
2538 else
2539 qCCritical(INTERNAL_ERROR, "Failed to calculate camera frustum");
2540 }
2541 }
2542
2543 // Now check if the camera has a layer mask, if it does we need to filter the nodes again!
2544 // Issue a warning letting the user know that they should set an explicit camera!
2545 // If you are reading this due to seeing the warning: Not requiring an explicit camera was
2546 // a mistake, but we kept it for compatability reasons unfortunately.
2547 if (renderedCameras.size() > 0 && renderedCameras[0]->layerMask != QSSGRenderCamera::LayerMaskAll) {
2548 if (!nonExplicitCameraWithLayerMaskWarningShown) {
2549 nonExplicitCameraWithLayerMaskWarningShown = true;
2550 qWarning() << "Scenes with non-explicit cameras with a layer detected!"
2551 " Set the camera explicitly to avoid unecessery evaluation of scene nodes!";
2552 }
2553
2554 // Now filter the nodes, again.
2555 layerMask = renderedCameras[0]->layerMask;
2556 updateFilteredLayerNodes(layerMask);
2557 }
2558 }
2559
2560 float meshLodThreshold = 1.0f;
2561 if (!renderedCameras.isEmpty())
2562 meshLodThreshold = renderedCameras[0]->levelOfDetailPixelThreshold / theViewport.width();
2563
2564 layer.renderedCamerasMutex.lock();
2565 layer.renderedCameras = renderedCameras;
2566 layer.renderedCamerasMutex.unlock();
2567
2568 // Meshes, materials, MVP, and normal matrices for the models
2569 const QSSGRenderCameraDataList &renderCameraData = getCachedCameraDatas();
2570 modelData->updateModelData(modelsView, renderer, renderCameraData);
2571
2572 // Item2Ds
2573 item2DData->updateItem2DData(item2DsView, renderer, renderCameraData);
2574
2575 // ResourceLoaders
2576 prepareResourceLoaders();
2577
2578 // Skeletons
2579 updateDirtySkeletons(*this, modelsView);
2580
2581 // Lights
2582 int directionalLightsCount = 0;
2583 int positionalLightsCount = 0;
2584 const int maxLightCount = effectiveMaxLightCount(features);
2585 const int maxDirectionalLights = effectiveMaxDirectionalLightCount(features);
2586 QSSGShaderLightList renderableLights;
2587 int shadowMapCount = 0;
2588 bool hasScopedLights = false;
2589
2590 // Determine which lights will actually Render
2591 // Determine how many lights will need shadow maps
2592 // NOTE: This culling is specific to our Forward renderer
2593 {
2594 auto it = std::make_reverse_iterator(lightsView.end());
2595 const auto end = it + qMin(maxLightCount, lightsView.size());
2596 for (; it != end; ++it) {
2597 QSSGRenderLight *renderLight = (*it);
2598 QMatrix4x4 renderLightTransform = getGlobalTransform(*renderLight);
2599 if (renderLight->type == QSSGRenderGraphObject::Type::DirectionalLight)
2600 directionalLightsCount++;
2601 else
2602 positionalLightsCount++;
2603
2604 if (positionalLightsCount > maxLightCount)
2605 continue;
2606 if (directionalLightsCount > maxDirectionalLights)
2607 continue;
2608
2609
2610 hasScopedLights |= (renderLight->m_scope != nullptr);
2611 const bool castShadows = renderLight->m_castShadow && !renderLight->m_fullyBaked;
2612 shadowMapCount += int(castShadows);
2613 const auto &direction = renderLight->getScalingCorrectDirection(renderLightTransform);
2614 renderableLights.push_back(QSSGShaderLight{ renderLight, castShadows, direction });
2615 }
2616 }
2617
2618 const bool showLightCountWarning = !tooManyLightsWarningShown && (positionalLightsCount > maxLightCount);
2619 if (showLightCountWarning) {
2620 qWarning("Too many lights in scene, maximum is %d", maxLightCount);
2621 tooManyLightsWarningShown = true;
2622 }
2623
2624 const bool showDirectionalLightCountWarning = !tooManyDirectionalLightsWarningShown && (directionalLightsCount > maxDirectionalLights);
2625 if (showDirectionalLightCountWarning) {
2626 qWarning("Too many directional lights in scene, maximum is %d", maxDirectionalLights);
2627 tooManyDirectionalLightsWarningShown = true;
2628 }
2629
2630 if (shadowMapCount > 0) { // Setup Shadow Maps Entries for Lights casting shadows
2631 requestShadowMapManager(); // Ensure we have a shadow map manager
2632 layerPrepResult.flags.setRequiresShadowMapPass(true);
2633 // Any light with castShadow=true triggers shadow mapping
2634 // in the generated shaders. The fact that some (or even
2635 // all) objects may opt out from receiving shadows plays no
2636 // role here whatsoever.
2637 features.set(QSSGShaderFeatures::Feature::Ssm, true);
2638 shadowMapManager->addShadowMaps(renderableLights);
2639 } else if (shadowMapManager) {
2640 // No shadows but a shadow manager so clear old resources
2641 shadowMapManager->releaseCachedResources();
2642 }
2643
2644 // Give each renderable a copy of the lights available
2645 // Also setup scoping for scoped lights
2646
2647 QSSG_ASSERT(globalLights.isEmpty(), globalLights.clear());
2648 if (hasScopedLights) { // Filter out scoped lights from the global lights list
2649 for (const auto &shaderLight : std::as_const(renderableLights)) {
2650 if (!shaderLight.light->m_scope)
2651 globalLights.push_back(shaderLight);
2652 }
2653
2654 const auto prepareLightsWithScopedLights = [&renderableLights, this](QVector<QSSGRenderableNodeEntry> &renderableNodes) {
2655 for (qint32 idx = 0, end = renderableNodes.size(); idx < end; ++idx) {
2656 QSSGRenderableNodeEntry &theNodeEntry(renderableNodes[idx]);
2657 QSSGShaderLightList filteredLights;
2658 for (const auto &light : std::as_const(renderableLights)) {
2659 if (light.light->m_scope && !scopeLight(theNodeEntry.node, light.light->m_scope))
2660 continue;
2661 filteredLights.push_back(light);
2662 }
2663
2664 if (filteredLights.isEmpty()) { // Node without scoped lights, just reference the global light list.
2665 theNodeEntry.lights = QSSGDataView(globalLights);
2666 } else {
2667 // This node has scoped lights, i.e., it's lights differ from the global list
2668 // we therefore create a bespoke light list for it. Technically this might be the same for
2669 // more then this one node, but the overhead for tracking that is not worth it.
2670 auto customLightList = RENDER_FRAME_NEW_BUFFER<QSSGShaderLight>(*renderer->contextInterface(), filteredLights.size());
2671 std::copy(filteredLights.cbegin(), filteredLights.cend(), customLightList.begin());
2672 theNodeEntry.lights = customLightList;
2673 }
2674 }
2675 };
2676
2677 prepareLightsWithScopedLights(renderableModels);
2678 prepareLightsWithScopedLights(renderableParticles);
2679 } else { // Just a simple copy
2680 globalLights = renderableLights;
2681 // No scoped lights, all nodes can just reference the global light list.
2682 const auto prepareLights = [this](QVector<QSSGRenderableNodeEntry> &renderableNodes) {
2683 for (qint32 idx = 0, end = renderableNodes.size(); idx < end; ++idx) {
2684 QSSGRenderableNodeEntry &theNodeEntry(renderableNodes[idx]);
2685 theNodeEntry.lights = QSSGDataView(globalLights);
2686 }
2687 };
2688
2689 prepareLights(renderableModels);
2690 prepareLights(renderableParticles);
2691 }
2692
2693 {
2694 // Give user provided passes a chance to modify the renderable data before starting
2695 // Note: All non-active extensions should be filtered out by now
2696 Q_STATIC_ASSERT(USERPASSES == size_t(QSSGRenderLayer::RenderExtensionStage::Count));
2697 for (size_t i = 0; i != size_t(QSSGRenderLayer::RenderExtensionStage::Count); ++i) {
2698 const auto &renderExtensions = layer.renderExtensions[i];
2699 auto &userPass = userPasses[i];
2700 for (auto rit = renderExtensions.crbegin(), rend = renderExtensions.crend(); rit != rend; ++rit) {
2701 if ((*rit)->prepareData(frameData)) {
2702 wasDirty |= true;
2703 userPass.extensions.push_back(*rit);
2704 }
2705 }
2706 }
2707 }
2708
2709 // User render passes
2710 {
2711 const auto &userRenderPassManager = requestUserRenderPassManager();
2712 userRenderPassManager->updateUserPassOrder(layerTreeWasDirty); // NOTE: If the tree was dirty we need to force an update.
2713 userRenderPasses.userPasses = userRenderPassManager->scheduledUserPasses();
2714 }
2715
2716 auto &opaqueObjects = opaqueObjectStore[0];
2717 auto &transparentObjects = transparentObjectStore[0];
2718 auto &screenTextureObjects = screenTextureObjectStore[0];
2719
2720 if (!renderedCameras.isEmpty()) { // NOTE: We shouldn't really get this far without a camera...
2721 wasDirty |= prepareModelsForRender(*renderer->contextInterface(), renderableModels, layerPrepResult.flags, renderedCameras, getCachedCameraDatas(), modelContexts, opaqueObjects, transparentObjects, screenTextureObjects, meshLodThreshold);
2722 if (particlesEnabled) {
2723 const auto &cameraDatas = getCachedCameraDatas();
2724 wasDirty |= prepareParticlesForRender(renderableParticles, cameraDatas[0], layerPrepResult.flags);
2725 }
2726 // If there's item2Ds we set wasDirty.
2727 wasDirty |= (item2DsView.size() != 0);
2728 }
2729 if (orderIndependentTransparencyEnabled) {
2730 // OIT blending mode must be SourceOver and have transparent objects
2731 if (transparentObjects.size() > 0 && !layerPrepResult.flags.hasCustomBlendMode()) {
2732 if (layer.oitMethod == QSSGRenderLayer::OITMethod::WeightedBlended) {
2733 if (rhiCtx->mainPassSampleCount() > 1)
2734 layerPrepResult.flags.setRequiresDepthTextureMS(true);
2735 else
2736 layerPrepResult.flags.setRequiresDepthTexture(true);
2737 }
2738 } else {
2739 orderIndependentTransparencyEnabled = false;
2740 if (!oitWarningInvalidBlendModeShown) {
2741 qCWarning(lcQuick3DRender) << "Order Independent Transparency requested, but disabled due to invalid blend modes.";
2742 qCWarning(lcQuick3DRender) << "Use SourceOver blend mode for Order Independent Transparency.";
2743 oitWarningInvalidBlendModeShown = true;
2744 }
2745 }
2746 }
2747 layer.oitMethodDirty = false;
2748
2749 prepareReflectionProbesForRender();
2750
2751 wasDirty = wasDirty || wasDataDirty;
2752 layerPrepResult.flags.setWasDirty(wasDirty);
2753 layerPrepResult.flags.setLayerDataDirty(wasDataDirty);
2754
2755 //
2756 const bool animating = wasDirty;
2757 if (animating)
2758 layer.progAAPassIndex = 0;
2759
2760 const bool progressiveAA = layer.isProgressiveAAEnabled() && !animating;
2761 layer.progressiveAAIsActive = progressiveAA;
2762 const bool temporalAA = layer.isTemporalAAEnabled() && !progressiveAA;
2763
2764 layer.temporalAAIsActive = temporalAA;
2765
2766 QVector2D vertexOffsetsAA;
2767
2768 if (progressiveAA && layer.progAAPassIndex > 0 && layer.progAAPassIndex < quint32(layer.antialiasingQuality)) {
2769 int idx = layer.progAAPassIndex - 1;
2770 vertexOffsetsAA = s_ProgressiveAAVertexOffsets[idx] / QVector2D{ float(theViewport.width()/2.0), float(theViewport.height()/2.0) };
2771 }
2772
2773 if (temporalAA) {
2774 if (layer.temporalAAMode == QSSGRenderLayer::TAAMode::MotionVector && layer.tempAAPassIndex > 0) {
2775 if (layer.tempAAPassIndex >= quint32(MAX_AA_LEVELS) + 1)
2776 layer.tempAAPassIndex = 1;
2777 int idx = layer.tempAAPassIndex - 1;
2778 vertexOffsetsAA = s_ProgressiveAAVertexOffsets[idx] / QVector2D{ float(theViewport.width()/2.0), float(theViewport.height()/2.0) };
2779 layer.currentAndLastJitter = QVector4D(vertexOffsetsAA, layer.currentAndLastJitter.x(), layer.currentAndLastJitter.y());
2780 layerPrepResult.flags.setRequiresMotionVectorPass(true);
2781 layerPrepResult.flags.setRequiresDepthTexture(true);
2782 }else {
2783 const int t = 1 - 2 * (layer.tempAAPassIndex % 2);
2784 const float f = t * layer.temporalAAStrength;
2785 vertexOffsetsAA = { f / float(theViewport.width()/2.0), f / float(theViewport.height()/2.0) };
2786 }
2787 }
2788
2789 if (!renderedCameras.isEmpty()) {
2790 if (temporalAA || progressiveAA /*&& !vertexOffsetsAA.isNull()*/) {
2791 QMatrix4x4 offsetProjection = renderedCameras[0]->projection;
2792 QMatrix4x4 invProjection = renderedCameras[0]->projection.inverted();
2793 if (renderedCameras[0]->type == QSSGRenderCamera::Type::OrthographicCamera) {
2794 offsetProjection(0, 3) -= vertexOffsetsAA.x();
2795 offsetProjection(1, 3) -= vertexOffsetsAA.y();
2796 } else if (renderedCameras[0]->type == QSSGRenderCamera::Type::PerspectiveCamera) {
2797 offsetProjection(0, 2) += vertexOffsetsAA.x();
2798 offsetProjection(1, 2) += vertexOffsetsAA.y();
2799 }
2800 for (auto &modelContext : std::as_const(modelContexts)) {
2801 for (int mvpIdx = 0; mvpIdx < renderedCameras.count(); ++mvpIdx)
2802 modelContext->modelViewProjections[mvpIdx] = offsetProjection * invProjection * modelContext->modelViewProjections[mvpIdx];
2803 }
2804 }
2805 }
2806
2807 const bool hasItem2Ds = (item2DsView.size() > 0);
2808 const bool layerEnableDepthTest = layer.layerFlags.testFlag(QSSGRenderLayer::LayerFlag::EnableDepthTest);
2809 const bool layerEnabledDepthPrePass = layer.layerFlags.testFlag(QSSGRenderLayer::LayerFlag::EnableDepthPrePass);
2810 const bool depthTestEnableDefault = layerEnableDepthTest && (!opaqueObjects.isEmpty() || depthPrepassObjectsState || hasDepthWriteObjects);
2811 const bool zPrePassForced = (depthPrepassObjectsState != 0);
2812 zPrePassActive = zPrePassForced || (layerEnabledDepthPrePass && layerEnableDepthTest && (hasDepthWriteObjects || hasItem2Ds));
2813 const bool depthWriteEnableDefault = depthTestEnableDefault && (!layerEnabledDepthPrePass || !zPrePassActive);
2814
2815 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, depthTestEnableDefault);
2816 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, depthWriteEnableDefault);
2817
2818
2819 // All data prep is done, from now on the layer content shouldn't change.
2820 layerPrepResult.setState(QSSGLayerRenderPreparationResult::State::Done);
2821
2822 // Prepare passes
2823 QSSG_ASSERT(activePasses.isEmpty(), activePasses.clear());
2824 // If needed, generate a depth texture with the opaque objects. This
2825 // and normal and the SSAO texture must come first since other passes may want to
2826 // expose these textures to their shaders.
2827 if (layerPrepResult.flags.requiresDepthTexture())
2828 activePasses.push_back(&depthMapPass);
2829 if (layerPrepResult.flags.requiresDepthTextureMS())
2830 activePasses.push_back(&depthMapPassMS);
2831
2832 if (layerPrepResult.flags.requiresNormalTexture())
2833 activePasses.push_back(&normalPass);
2834
2835 // Screen space ambient occlusion. Relies on the depth texture and generates an AO map.
2836 if (layerPrepResult.flags.requiresSsaoPass())
2837 activePasses.push_back(&ssaoMapPass);
2838
2839 // Shadows. Generates a 2D or cube shadow map. (opaque + pre-pass transparent objects)
2840 if (layerPrepResult.flags.requiresShadowMapPass())
2841 activePasses.push_back(&shadowMapPass);
2842
2843 if (zPrePassActive && !disableMainPasses)
2844 activePasses.push_back(&zPrePassPass);
2845
2846 // Screen texture with opaque objects.
2847 if (layerPrepResult.flags.requiresScreenTexture())
2848 activePasses.push_back(&screenMapPass);
2849
2850 // Reflection pass
2851 activePasses.push_back(&reflectionMapPass);
2852
2853 auto &textureExtensionPass = userPasses[size_t(QSSGRenderLayer::RenderExtensionStage::TextureProviders)];
2854 if (textureExtensionPass.hasData())
2855 activePasses.push_back(&textureExtensionPass);
2856
2857 auto &underlayPass = userPasses[size_t(QSSGRenderLayer::RenderExtensionStage::Underlay)];
2858 if (underlayPass.hasData())
2859 activePasses.push_back(&underlayPass);
2860
2861 // Generated User render passes (QML)
2862 if (userRenderPasses.hasData())
2863 activePasses.push_back(&userRenderPasses);
2864
2865 const bool hasOpaqueObjects = (opaqueObjects.size() > 0);
2866
2867 if (hasOpaqueObjects && !disableMainPasses)
2868 activePasses.push_back(&opaquePass);
2869
2870 // MotionVector Pass
2871 if (layerPrepResult.flags.requiresMotionVectorPass())
2872 activePasses.push_back(&motionVectorMapPass);
2873
2874 // NOTE: When the a screen texture is used, the skybox pass will be called twice. First from
2875 // the screen texture pass and later as part of the normal run through the list.
2876 if (renderer->contextInterface()->rhiContext()->rhi()->isFeatureSupported(QRhi::TexelFetch)) {
2877 if (layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap && layer.skyBoxCubeMap && !disableMainPasses)
2878 activePasses.push_back(&skyboxCubeMapPass);
2879 else if (layer.background == QSSGRenderLayer::Background::SkyBox && layer.lightProbe && !disableMainPasses)
2880 activePasses.push_back(&skyboxPass);
2881 }
2882
2883 if (hasItem2Ds && !disableMainPasses)
2884 activePasses.push_back(&item2DPass);
2885
2886 if (layerPrepResult.flags.requiresScreenTexture())
2887 activePasses.push_back(&reflectionPass);
2888
2889 // Note: Transparent pass includeds opaque objects when layerEnableDepthTest is false.
2890 if ((transparentObjects.size() > 0 || (!layerEnableDepthTest && hasOpaqueObjects)) && !disableMainPasses) {
2891 if (orderIndependentTransparencyEnabled) {
2892 activePasses.push_back(&oitRenderPass);
2893 activePasses.push_back(&oitCompositePass);
2894 oitRenderPass.setMethod(layer.oitMethod);
2895 oitCompositePass.setMethod(layer.oitMethod);
2896 } else {
2897 activePasses.push_back(&transparentPass);
2898 }
2899 }
2900
2901 auto &overlayPass = userPasses[size_t(QSSGRenderLayer::RenderExtensionStage::Overlay)];
2902 if (overlayPass.hasData())
2903 activePasses.push_back(&overlayPass);
2904
2905 if (layer.gridEnabled)
2906 activePasses.push_back(&infiniteGridPass);
2907
2908 if (const auto &dbgDrawSystem = renderer->contextInterface()->debugDrawSystem(); dbgDrawSystem && dbgDrawSystem->isEnabled() && !disableMainPasses)
2909 activePasses.push_back(&debugDrawPass);
2910}
2911
2912template<typename T>
2913static void clearTable(std::vector<T> &entry)
2914{
2915 for (auto &e : entry)
2916 e.clear();
2917}
2918
2919void QSSGLayerRenderData::resetForFrame()
2920{
2921 for (const auto &pass : activePasses)
2922 pass->resetForFrame();
2923 activePasses.clear();
2924 bakedLightingModels.clear();
2925 layerPrepResult = {};
2926 renderedCameraData.reset();
2927 renderedItem2Ds.clear();
2928 renderedBakedLightingModels.clear();
2929 lightmapTextures.clear();
2930 bonemapTextures.clear();
2931 globalLights.clear();
2932 modelContexts.clear();
2933 features = QSSGShaderFeatures();
2934 hasDepthWriteObjects = false;
2935 depthPrepassObjectsState = { DepthPrepassObjectStateT(DepthPrepassObject::None) };
2936 zPrePassActive = false;
2937 savedRenderState.reset();
2938
2939 clearTable(renderableModelStore);
2940 clearTable(modelContextStore);
2941 clearTable(renderableObjectStore);
2942 clearTable(opaqueObjectStore);
2943 clearTable(transparentObjectStore);
2944 clearTable(screenTextureObjectStore);
2945
2946 clearTable(sortedOpaqueObjectCache);
2947 clearTable(sortedTransparentObjectCache);
2948 clearTable(sortedScreenTextureObjectCache);
2949 clearTable(sortedOpaqueDepthPrepassCache);
2950 clearTable(sortedDepthWriteCache);
2951
2952 // Until we have a better solution for extensions...
2953 if (renderablesModifiedByExtension) {
2954 renderableModels.clear();
2955 renderableParticles.clear();
2956 renderableModels.reserve(modelsView.size());
2957 renderableParticles.reserve(particlesView.size());
2958
2959 renderableModels = {modelsView.begin(), modelsView.end()};
2960 renderableParticles = {particlesView.begin(), particlesView.end()};
2961
2962 renderablesModifiedByExtension = false;
2963 }
2964
2965 if (userRenderPassManager)
2966 userRenderPassManager->resetForFrame();
2967}
2968
2969QSSGLayerRenderPreparationResult::QSSGLayerRenderPreparationResult(const QRectF &inViewport, QSSGRenderLayer &inLayer)
2970 : layer(&inLayer)
2971 , m_state(State::DataPrep)
2972{
2973 viewport = inViewport;
2974}
2975
2977{
2978 return viewport.height() >= 2.0f && viewport.width() >= 2.0f;
2979}
2980
2982{
2983 const auto size = viewport.size().toSize();
2984 return QSize(QSSGRendererUtil::nextMultipleOf4(size.width()), QSSGRendererUtil::nextMultipleOf4(size.height()));
2985}
2986
2987QSSGLayerRenderData::QSSGLayerRenderData(QSSGRenderLayer &inLayer, QSSGRenderer &inRenderer)
2988 : layer(inLayer)
2989 , renderer(&inRenderer)
2990 , orderIndependentTransparencyEnabled(false)
2991 , particlesEnabled(checkParticleSupport(inRenderer.contextInterface()->rhi()))
2992{
2993 depthMapPassMS.setMultisamplingEnabled(true);
2994 Q_ASSERT(extContexts.size() == 1);
2995
2996 // Set-up the world root node and create the data store for the models.
2997 auto *root = layer.rootNode;
2998 nodeData = root->globalNodeData();
2999 modelData = std::make_unique<QSSGRenderModelData>(nodeData);
3000 item2DData = std::make_unique<QSSGRenderItem2DData>(nodeData);
3001
3002 inRenderer.registerItem2DData(*item2DData);
3003}
3004
3005QSSGLayerRenderData::~QSSGLayerRenderData()
3006{
3007 for (auto &renderResult : renderResults)
3008 renderResult.reset();
3009 oitRenderContext.reset();
3010
3011 if (userRenderPassManager)
3012 userRenderPassManager->releaseAll();
3013
3014 renderer->unregisterItem2DData(*item2DData);
3015}
3016
3017static void sortInstances(QByteArray &sortedData, QList<QSSGRhiSortData> &sortData, const void *instances,
3018 int stride, int count, const QVector3D &cameraDirection)
3019{
3020 sortData.resize(count);
3021 Q_ASSERT(stride == sizeof(QSSGRenderInstanceTableEntry));
3022 // create sort data
3023 {
3024 const QSSGRenderInstanceTableEntry *instance = reinterpret_cast<const QSSGRenderInstanceTableEntry *>(instances);
3025 for (int i = 0; i < count; i++) {
3026 const QVector3D pos = QVector3D(instance->row0.w(), instance->row1.w(), instance->row2.w());
3027 sortData[i] = {QVector3D::dotProduct(pos, cameraDirection), i};
3028 instance++;
3029 }
3030 }
3031
3032 // sort
3033 std::sort(sortData.begin(), sortData.end(), [](const QSSGRhiSortData &a, const QSSGRhiSortData &b){
3034 return a.d > b.d;
3035 });
3036
3037 // copy instances
3038 {
3039 const QSSGRenderInstanceTableEntry *instance = reinterpret_cast<const QSSGRenderInstanceTableEntry *>(instances);
3040 QSSGRenderInstanceTableEntry *dest = reinterpret_cast<QSSGRenderInstanceTableEntry *>(sortedData.data());
3041 for (auto &s : sortData)
3042 *dest++ = instance[s.indexOrOffset];
3043 }
3044}
3045
3046static int cullLodInstances(QByteArray &lodData, const void *instances, int count,
3047 const QVector3D &cameraPosition, float minThreshold, float maxThreshold)
3048{
3049 const QSSGRenderInstanceTableEntry *instance = reinterpret_cast<const QSSGRenderInstanceTableEntry *>(instances);
3050 QSSGRenderInstanceTableEntry *dest = reinterpret_cast<QSSGRenderInstanceTableEntry *>(lodData.data());
3051
3052 int realSize = 0;
3053 for (int i = 0; i < count; ++i) {
3054 const float x = cameraPosition.x() - instance->row0.w();
3055 const float y = cameraPosition.y() - instance->row1.w();
3056 const float z = cameraPosition.z() - instance->row2.w();
3057 const float distanceSq = x * x + y * y + z * z;
3058 if (distanceSq >= minThreshold * minThreshold && (maxThreshold < 0 || distanceSq < maxThreshold * maxThreshold)) {
3059 realSize++;
3060 *dest = *instance;
3061 dest++;
3062 }
3063
3064 instance++;
3065 }
3066
3067 return realSize;
3068}
3069
3070bool QSSGLayerRenderData::prepareInstancing(QSSGRhiContext *rhiCtx,
3071 QSSGSubsetRenderable *renderable,
3072 const QVector3D &cameraDirection,
3073 const QVector3D &cameraPosition,
3074 float minThreshold,
3075 float maxThreshold)
3076{
3077 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
3078 auto &modelContext = renderable->modelContext;
3079 auto &instanceBuffer = renderable->instanceBuffer; // intentional ref2ptr
3080 const QMatrix4x4 &renderableGlobalTransform = renderable->modelContext.globalTransform;
3081 if (!modelContext.model.instancing() || instanceBuffer)
3082 return instanceBuffer;
3083 auto *table = modelContext.model.instanceTable;
3084 bool usesLod = minThreshold >= 0 || maxThreshold >= 0;
3085 QSSGRhiInstanceBufferData &instanceData(usesLod ? rhiCtxD->instanceBufferData(&modelContext.model) : rhiCtxD->instanceBufferData(table));
3086 quint32 instanceBufferSize = table->dataSize();
3087 // Create or resize the instance buffer ### if (instanceData.owned)
3088 bool sortingChanged = table->isDepthSortingEnabled() != instanceData.sorting;
3089 bool cameraDirectionChanged = !qFuzzyCompare(instanceData.sortedCameraDirection, cameraDirection);
3090 bool cameraPositionChanged = !qFuzzyCompare(instanceData.cameraPosition, cameraPosition);
3091 bool updateInstanceBuffer = table->serial() != instanceData.serial || sortingChanged || (cameraDirectionChanged && table->isDepthSortingEnabled());
3092 bool instanceBufferSizeChanged = instanceData.buffer && instanceBufferSize != instanceData.buffer->size();
3093 bool updateForLod = (cameraPositionChanged || instanceBufferSizeChanged) && usesLod;
3094 if (sortingChanged && !table->isDepthSortingEnabled()) {
3095 instanceData.sortedData.clear();
3096 instanceData.sortData.clear();
3097 instanceData.sortedCameraDirection = {};
3098 }
3099 instanceData.sorting = table->isDepthSortingEnabled();
3100 if (instanceData.buffer && instanceData.buffer->size() < instanceBufferSize) {
3101 updateInstanceBuffer = true;
3102 // qDebug() << "Resizing instance buffer";
3103 instanceData.buffer->setSize(instanceBufferSize);
3104 instanceData.buffer->create();
3105 }
3106 if (!instanceData.buffer) {
3107 // qDebug() << "Creating instance buffer";
3108 updateInstanceBuffer = true;
3109 instanceData.buffer = rhiCtx->rhi()->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, instanceBufferSize);
3110 instanceData.buffer->create();
3111 }
3112 if (updateInstanceBuffer || updateForLod) {
3113 const void *data = nullptr;
3114 if (table->isDepthSortingEnabled()) {
3115 if (updateInstanceBuffer) {
3116 QMatrix4x4 invGlobalTransform = renderableGlobalTransform.inverted();
3117 instanceData.sortedData.resize(table->dataSize());
3118 sortInstances(instanceData.sortedData,
3119 instanceData.sortData,
3120 table->constData(),
3121 table->stride(),
3122 table->count(),
3123 invGlobalTransform.map(cameraDirection).normalized());
3124 }
3125 data = instanceData.sortedData.constData();
3126 instanceData.sortedCameraDirection = cameraDirection;
3127 } else {
3128 data = table->constData();
3129 }
3130 if (data) {
3131 if (updateForLod) {
3132 instanceData.lodData.resize(table->dataSize());
3133 if (table->isDepthSortingEnabled()) {
3134 modelContext.model.instanceLodCount = cullLodInstances(instanceData.lodData,
3135 instanceData.sortedData.constData(),
3136 instanceData.sortedData.size(),
3137 cameraPosition,
3138 minThreshold,
3139 maxThreshold);
3140
3141 } else {
3142 modelContext.model.instanceLodCount = cullLodInstances(instanceData.lodData,
3143 table->constData(),
3144 table->count(),
3145 cameraPosition,
3146 minThreshold,
3147 maxThreshold);
3148 }
3149 data = instanceData.lodData.constData();
3150
3151 // Force clear the buffer to upload empty instance data.
3152 // If this step is skipped, the updateDynamicBuffer function will load data for all instances.
3153 if (!modelContext.model.instanceLodCount)
3154 instanceData.lodData.clear();
3155
3156 instanceBufferSize = modelContext.model.instanceLodCount * sizeof(QSSGRenderInstanceTableEntry);
3157 }
3158
3159 QRhiResourceUpdateBatch *rub = rhiCtx->rhi()->nextResourceUpdateBatch();
3160 rub->updateDynamicBuffer(instanceData.buffer, 0, instanceBufferSize, data);
3161 rhiCtx->commandBuffer()->resourceUpdate(rub);
3162 //qDebug() << "****** UPDATING INST BUFFER. Size" << instanceBufferSize;
3163 } else {
3164 qWarning() << "NO DATA IN INSTANCE TABLE";
3165 }
3166 instanceData.serial = table->serial();
3167 instanceData.cameraPosition = cameraPosition;
3168 }
3169 instanceBuffer = instanceData.buffer;
3170 return instanceBuffer;
3171}
3172
3173QSSGFrameData &QSSGLayerRenderData::getFrameData()
3174{
3175 return frameData;
3176}
3177
3178void QSSGLayerRenderData::initializeLightmapBaking(QSSGLightmapBaker::Context &ctx)
3179{
3180 ctx.callbacks.modelsToBake = [this]() { return getSortedBakedLightingModels(); };
3181
3182 lightmapBaker = std::make_unique<QSSGLightmapBaker>(ctx);
3183}
3184
3185void QSSGLayerRenderData::maybeProcessLightmapBaking()
3186{
3187 if (lightmapBaker) {
3188 const QSSGLightmapBaker::Status status = lightmapBaker->process();
3189 if (status == QSSGLightmapBaker::Status::Finished)
3190 lightmapBaker.reset();
3191 }
3192}
3193
3194QSSGRenderGraphObject *QSSGLayerRenderData::getCamera(QSSGCameraId id) const
3195{
3196 QSSGRenderGraphObject *ret = nullptr;
3197 if (auto res = reinterpret_cast<QSSGRenderGraphObject *>(id))
3198 ret = res;
3199
3200 return ret;
3201}
3202
3203QSSGRenderCameraData QSSGLayerRenderData::getCameraRenderData(const QSSGRenderCamera *camera_)
3204{
3205 if ((!camera_ || camera_ == renderedCameras[0]) && renderedCameraData.has_value())
3206 return renderedCameraData.value()[0];
3207 if (camera_)
3208 return getCameraDataImpl(camera_);
3209 return {};
3210}
3211
3212QSSGRenderCameraData QSSGLayerRenderData::getCameraRenderData(const QSSGRenderCamera *camera_) const
3213{
3214 if ((!camera_ || camera_ == renderedCameras[0]) && renderedCameraData.has_value())
3215 return renderedCameraData.value()[0];
3216 if (camera_)
3217 return getCameraDataImpl(camera_);
3218 return {};
3219}
3220
3221QSSGRenderContextInterface *QSSGLayerRenderData::contextInterface() const
3222{
3223 return renderer ? renderer->contextInterface() : nullptr;
3224}
3225
3226QSSGLayerRenderData::GlobalRenderProperties QSSGLayerRenderData::globalRenderProperties(const QSSGRenderContextInterface &ctx)
3227{
3228 GlobalRenderProperties props {};
3229 if (const auto &rhiCtx = ctx.rhiContext(); rhiCtx->isValid()) {
3230 QRhi *rhi = rhiCtx->rhi();
3231 props.isYUpInFramebuffer = rhi->isYUpInFramebuffer();
3232 props.isYUpInNDC = rhi->isYUpInNDC();
3233 props.isClipDepthZeroToOne = rhi->isClipDepthZeroToOne();
3234 }
3235
3236 return props;
3237}
3238
3239const QSSGRenderShadowMapPtr &QSSGLayerRenderData::requestShadowMapManager()
3240{
3241 if (!shadowMapManager && QSSG_GUARD(renderer && renderer->contextInterface()))
3242 shadowMapManager.reset(new QSSGRenderShadowMap(*renderer->contextInterface()));
3243 return shadowMapManager;
3244}
3245
3246const QSSGRenderReflectionMapPtr &QSSGLayerRenderData::requestReflectionMapManager()
3247{
3248 if (!reflectionMapManager && QSSG_GUARD(renderer && renderer->contextInterface()))
3249 reflectionMapManager.reset(new QSSGRenderReflectionMap(*renderer->contextInterface()));
3250 return reflectionMapManager;
3251}
3252
3253
3254const QSSGUserRenderPassManagerPtr &QSSGLayerRenderData::requestUserRenderPassManager()
3255{
3256 if (!userRenderPassManager) {
3257 userRenderPassManager = QSSGUserRenderPassManager::create();
3258 renderer->contextInterface()->bufferManager()->registerUserRenderPassManager(userRenderPassManager);
3259 }
3260 return userRenderPassManager;
3261}
3262
3263const QSSGRenderMotionVectorMapPtr &QSSGLayerRenderData::requestMotionVectorMapManager()
3264{
3265 if (!motionVectorMapManager && QSSG_GUARD(renderer && renderer->contextInterface()))
3266 motionVectorMapManager.reset(new QSSGRenderMotionVectorMap(*renderer->contextInterface()));
3267 return motionVectorMapManager;
3268}
3269
3270QT_END_NAMESPACE
QSSGLayerRenderPreparationResult(const QRectF &inViewport, QSSGRenderLayer &inLayer)
friend class QSSGRenderContextInterface
Combined button and popup list for selecting options.
#define POS4BONETRANS(x)
#define POS4BONENORM(x)
static constexpr size_t getPrepContextIndex(QSSGPrepContextId id)
static bool scopeLight(QSSGRenderNode *node, QSSGRenderNode *lightScope)
static constexpr bool furthestToNearestCompare(const QSSGRenderableObjectHandle &lhs, const QSSGRenderableObjectHandle &rhs) noexcept
static int effectiveMaxDirectionalLightCount(const QSSGShaderFeatures &features)
static constexpr bool verifyPrepContext(QSSGPrepContextId id, const QSSGRenderer &renderer)
#define MAX_MORPH_TARGET_INDEX_SUPPORTS_NORMALS
static int cullLodInstances(QByteArray &lodData, const void *instances, int count, const QVector3D &cameraPosition, float minThreshold, float maxThreshold)
static void clearTable(std::vector< T > &entry)
static constexpr quint16 PREP_CTX_INDEX_MASK
#define MAX_MORPH_TARGET_INDEX_SUPPORTS_TANGENTS
static void updateDirtySkeletons(const QSSGLayerRenderData &renderData, const QSSGLayerRenderData::QSSGModelsView &renderableNodes)
static bool hasCustomBlendMode(const QSSGRenderCustomMaterial &material)
static const int REDUCED_MAX_LIGHT_COUNT_THRESHOLD_BYTES
static bool checkParticleSupport(QRhi *rhi)
#define BONEDATASIZE4ID(x)
static void collectBoneTransforms(const QSSGLayerRenderData &renderData, QSSGRenderNode *node, QSSGRenderSkeleton *skeletonNode, const QVector< QMatrix4x4 > &poses)
static void sortInstances(QByteArray &sortedData, QList< QSSGRhiSortData > &sortData, const void *instances, int stride, int count, const QVector3D &cameraDirection)
#define CHECK_IMAGE_AND_PREPARE(img, imgtype, shadercomponent)
static constexpr bool nearestToFurthestCompare(const QSSGRenderableObjectHandle &lhs, const QSSGRenderableObjectHandle &rhs) noexcept
static constexpr size_t pipelineStateIndex(QSSGRenderablesFilter filter)
QSSGDataRef< T > RENDER_FRAME_NEW_BUFFER(QSSGRenderContextInterface &ctx, size_t count)
static const QVector2D s_ProgressiveAAVertexOffsets[QSSGLayerRenderData::MAX_AA_LEVELS]
static bool hasDirtyNonJointNodes(QSSGRenderNode *node, bool &hasChildJoints)
static int effectiveMaxLightCount(const QSSGShaderFeatures &features)
static constexpr QSSGPrepContextId createPrepId(size_t index, quint32 frame)
T * RENDER_FRAME_NEW(QSSGRenderContextInterface &ctx, Args &&... args)
static LayerNodeStatResult statLayerNodes(const QSSGLayerRenderData::LayerNodes &layerNodes, quint32 layerMask)
static float getCameraDistanceSq(const QSSGRenderableObject &obj, const QSSGRenderCameraData &camera) noexcept
static void createRenderablesHelper(QSSGLayerRenderData &layer, const QSSGRenderNode::ChildList &children, QSSGLayerRenderData::RenderableNodeEntries &renderables, QSSGRenderHelpers::CreateFlags createFlags)
friend QDebug operator<<(QDebug dbg, const LayerNodeStatResult &stat)
friend LayerNodeStatResult & operator+=(LayerNodeStatResult &lhs, const LayerNodeStatResult &rhs)
QSSGDefaultMaterialPreparationResult(QSSGShaderDefaultMaterialKey inMaterialKey)