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