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::TransmissionMap:
1097 value = inMaterial->transmissionChannel;
1098 break;
1099 case QSSGShaderDefaultMaterialKeyProperties::ThicknessMap:
1100 value = inMaterial->thicknessChannel;
1101 break;
1102 case QSSGShaderDefaultMaterialKeyProperties::BaseColorMap:
1103 value = inMaterial->baseColorChannel;
1104 break;
1105 case QSSGShaderDefaultMaterialKeyProperties::SpecularAmountMap:
1106 value = inMaterial->specularAmountChannel;
1107 break;
1108 case QSSGShaderDefaultMaterialKeyProperties::EmissiveMap:
1109 value = inMaterial->emissiveChannel;
1110 break;
1111 default:
1112 break;
1113 }
1114 bool useDefault = false;
1115 switch (value) {
1116 case QSSGRenderDefaultMaterial::TextureChannelMapping::G:
1117 useDefault = !hasG;
1118 break;
1119 case QSSGRenderDefaultMaterial::TextureChannelMapping::B:
1120 useDefault = !hasB;
1121 break;
1122 case QSSGRenderDefaultMaterial::TextureChannelMapping::A:
1123 useDefault = !hasA;
1124 break;
1125 default:
1126 break;
1127 }
1128 if (useDefault)
1129 value = QSSGRenderDefaultMaterial::R; // Always Fallback to Red
1130 channelKey.setTextureChannel(QSSGShaderKeyTextureChannel::TexturChannelBits(value), inShaderKey);
1131 }
1132 }
1133}
1134
1135void QSSGLayerRenderData::setVertexInputPresence(const QSSGRenderableObjectFlags &renderableFlags,
1136 QSSGShaderDefaultMaterialKey &key)
1137{
1138 quint32 vertexAttribs = 0;
1139 if (renderableFlags.hasAttributePosition())
1140 vertexAttribs |= QSSGShaderKeyVertexAttribute::Position;
1141 if (renderableFlags.hasAttributeNormal())
1142 vertexAttribs |= QSSGShaderKeyVertexAttribute::Normal;
1143 if (renderableFlags.hasAttributeTexCoord0())
1144 vertexAttribs |= QSSGShaderKeyVertexAttribute::TexCoord0;
1145 if (renderableFlags.hasAttributeTexCoord1())
1146 vertexAttribs |= QSSGShaderKeyVertexAttribute::TexCoord1;
1147 if (renderableFlags.hasAttributeTexCoordLightmap())
1148 vertexAttribs |= QSSGShaderKeyVertexAttribute::TexCoordLightmap;
1149 if (renderableFlags.hasAttributeTangent())
1150 vertexAttribs |= QSSGShaderKeyVertexAttribute::Tangent;
1151 if (renderableFlags.hasAttributeBinormal())
1152 vertexAttribs |= QSSGShaderKeyVertexAttribute::Binormal;
1153 if (renderableFlags.hasAttributeColor())
1154 vertexAttribs |= QSSGShaderKeyVertexAttribute::Color;
1155 if (renderableFlags.hasAttributeJointAndWeight())
1156 vertexAttribs |= QSSGShaderKeyVertexAttribute::JointAndWeight;
1157 defaultMaterialShaderKeyProperties.m_vertexAttributes.setValue(key, vertexAttribs);
1158}
1159
1160QSSGDefaultMaterialPreparationResult QSSGLayerRenderData::prepareDefaultMaterialForRender(
1161 QSSGRenderDefaultMaterial &inMaterial,
1162 QSSGRenderableObjectFlags &inExistingFlags,
1163 float inOpacity, bool hasAnyLights,
1164 bool anyLightHasShadows,
1165 QSSGLayerRenderPreparationResultFlags &ioFlags)
1166{
1167 QSSGRenderDefaultMaterial *theMaterial = &inMaterial;
1168 QSSGDefaultMaterialPreparationResult retval(QSSGShaderDefaultMaterialKey(qHash(features)));
1169 retval.renderableFlags = inExistingFlags;
1170 QSSGRenderableObjectFlags &renderableFlags(retval.renderableFlags);
1171 QSSGShaderDefaultMaterialKey &theGeneratedKey(retval.materialKey);
1172 retval.opacity = inOpacity;
1173 float &subsetOpacity(retval.opacity);
1174
1175 if (theMaterial->isDirty())
1176 renderableFlags |= QSSGRenderableObjectFlag::Dirty;
1177
1178 subsetOpacity *= theMaterial->opacity;
1179
1180 QSSGRenderableImage *firstImage = nullptr;
1181
1182 const bool lighting = theMaterial->lighting != QSSGRenderDefaultMaterial::MaterialLighting::NoLighting;
1183 defaultMaterialShaderKeyProperties.m_hasLighting.setValue(theGeneratedKey, lighting);
1184 if (lighting) {
1185 defaultMaterialShaderKeyProperties.m_hasPunctualLights.setValue(theGeneratedKey, hasAnyLights);
1186 defaultMaterialShaderKeyProperties.m_hasShadows.setValue(theGeneratedKey, anyLightHasShadows);
1187 const bool validSky = layer.skyMaterial != nullptr && layer.skyMaterial->enableIBL;
1188 defaultMaterialShaderKeyProperties.m_hasIbl.setValue(theGeneratedKey, layer.lightProbe != nullptr || validSky);
1189 }
1190
1191 defaultMaterialShaderKeyProperties.m_specularAAEnabled.setValue(theGeneratedKey, layer.specularAAEnabled);
1192
1193 // isDoubleSided
1194 defaultMaterialShaderKeyProperties.m_isDoubleSided.setValue(theGeneratedKey, theMaterial->cullMode == QSSGCullFaceMode::Disabled);
1195
1196 // default materials never define their on position
1197 defaultMaterialShaderKeyProperties.m_overridesPosition.setValue(theGeneratedKey, false);
1198
1199 // default materials dont make use of raw projection or inverse projection matrices
1200 defaultMaterialShaderKeyProperties.m_usesProjectionMatrix.setValue(theGeneratedKey, false);
1201 defaultMaterialShaderKeyProperties.m_usesInverseProjectionMatrix.setValue(theGeneratedKey, false);
1202 // nor they do rely on VAR_COLOR
1203 defaultMaterialShaderKeyProperties.m_usesVarColor.setValue(theGeneratedKey, false);
1204
1205 // alpha Mode
1206 defaultMaterialShaderKeyProperties.m_alphaMode.setValue(theGeneratedKey, theMaterial->alphaMode);
1207
1208 // vertex attribute presence flags
1209 setVertexInputPresence(renderableFlags, theGeneratedKey);
1210
1211 // set the flag indicating the need for gl_PointSize
1212 defaultMaterialShaderKeyProperties.m_usesPointsTopology.setValue(theGeneratedKey, renderableFlags.isPointsTopology());
1213
1214 // propagate the flag indicating the presence of a lightmap
1215 defaultMaterialShaderKeyProperties.m_lightmapEnabled.setValue(theGeneratedKey, renderableFlags.rendersWithLightmap());
1216 defaultMaterialShaderKeyProperties.m_metallicRoughnessEnabled.setValue(theGeneratedKey, theMaterial->type == QSSGRenderDefaultMaterial::Type::PrincipledMaterial);
1217 defaultMaterialShaderKeyProperties.m_specularGlossyEnabled.setValue(theGeneratedKey, theMaterial->type == QSSGRenderGraphObject::Type::SpecularGlossyMaterial);
1218
1219
1220 // debug modes
1221 defaultMaterialShaderKeyProperties.m_debugMode.setValue(theGeneratedKey, int(layer.debugMode));
1222
1223 // fog
1224 defaultMaterialShaderKeyProperties.m_fogEnabled.setValue(theGeneratedKey, layer.fog.enabled);
1225
1226 // multiview
1227 defaultMaterialShaderKeyProperties.m_viewCount.setValue(theGeneratedKey, layer.viewCount);
1228 defaultMaterialShaderKeyProperties.m_usesViewIndex.setValue(theGeneratedKey, layer.viewCount >= 2);
1229
1230 if (!defaultMaterialShaderKeyProperties.m_hasIbl.getValue(theGeneratedKey) && theMaterial->iblProbe) {
1231 features.set(QSSGShaderFeatures::Feature::LightProbe, true);
1232 defaultMaterialShaderKeyProperties.m_hasIbl.setValue(theGeneratedKey, true);
1233 // features.set(ShaderFeatureDefines::enableIblFov(),
1234 // m_Renderer.GetLayerRenderData()->m_Layer.m_ProbeFov < 180.0f );
1235 }
1236
1237 if (subsetOpacity >= QSSGRendererPrivate::minimumRenderOpacity) {
1238
1239 // Set the semi-transparency flag as specified in PrincipledMaterial's
1240 // blendMode and alphaMode:
1241 // - the default SourceOver blendMode does not imply alpha blending on
1242 // its own,
1243 // - but other blendMode values do,
1244 // - an alphaMode of Blend guarantees blending to be enabled regardless
1245 // of anything else.
1246 // Additionally:
1247 // - Opacity and texture map alpha are handled elsewhere (that's when a
1248 // blendMode of SourceOver or an alphaMode of Default/Opaque can in the
1249 // end still result in HasTransparency),
1250 // - the presence of an opacityMap guarantees alpha blending regardless
1251 // of its content.
1252
1253 if (theMaterial->blendMode != QSSGRenderDefaultMaterial::MaterialBlendMode::SourceOver
1254 || theMaterial->opacityMap
1255 || theMaterial->alphaMode == QSSGRenderDefaultMaterial::Blend)
1256 {
1257 renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
1258 }
1259
1260 const bool specularEnabled = theMaterial->isSpecularEnabled();
1261 const bool metalnessEnabled = theMaterial->isMetalnessEnabled();
1262 defaultMaterialShaderKeyProperties.m_specularEnabled.setValue(theGeneratedKey, specularEnabled || metalnessEnabled);
1263 defaultMaterialShaderKeyProperties.m_specularModel.setSpecularModel(theGeneratedKey, theMaterial->specularModel);
1264 defaultMaterialShaderKeyProperties.m_diffuseModel.setDiffuseModel(theGeneratedKey, theMaterial->diffuseModel);
1265 defaultMaterialShaderKeyProperties.m_fresnelScaleBiasEnabled.setValue(theGeneratedKey, theMaterial->isFresnelScaleBiasEnabled());
1266 defaultMaterialShaderKeyProperties.m_clearcoatFresnelScaleBiasEnabled.setValue(theGeneratedKey, theMaterial->isClearcoatFresnelScaleBiasEnabled());
1267 defaultMaterialShaderKeyProperties.m_fresnelEnabled.setValue(theGeneratedKey, theMaterial->isFresnelEnabled());
1268 defaultMaterialShaderKeyProperties.m_baseColorSingleChannelEnabled.setValue(theGeneratedKey, theMaterial->isBaseColorSingleChannelEnabled());
1269 defaultMaterialShaderKeyProperties.m_specularSingleChannelEnabled.setValue(theGeneratedKey, theMaterial->isSpecularAmountSingleChannelEnabled());
1270 defaultMaterialShaderKeyProperties.m_emissiveSingleChannelEnabled.setValue(theGeneratedKey, theMaterial->isEmissiveSingleChannelEnabled());
1271 defaultMaterialShaderKeyProperties.m_invertOpacityMapValue.setValue(theGeneratedKey, theMaterial->isInvertOpacityMapValue());
1272 defaultMaterialShaderKeyProperties.m_vertexColorsEnabled.setValue(theGeneratedKey, theMaterial->isVertexColorsEnabled());
1273 defaultMaterialShaderKeyProperties.m_vertexColorsMaskEnabled.setValue(theGeneratedKey, theMaterial->isVertexColorsMaskEnabled());
1274 defaultMaterialShaderKeyProperties.m_vertexColorRedMask.setValue(theGeneratedKey, quint16(theMaterial->vertexColorRedMask.toInt()));
1275 defaultMaterialShaderKeyProperties.m_vertexColorGreenMask.setValue(theGeneratedKey, quint16(theMaterial->vertexColorGreenMask.toInt()));
1276 defaultMaterialShaderKeyProperties.m_vertexColorBlueMask.setValue(theGeneratedKey, quint16(theMaterial->vertexColorBlueMask.toInt()));
1277 defaultMaterialShaderKeyProperties.m_vertexColorAlphaMask.setValue(theGeneratedKey, quint16(theMaterial->vertexColorAlphaMask.toInt()));
1278 defaultMaterialShaderKeyProperties.m_clearcoatEnabled.setValue(theGeneratedKey, theMaterial->isClearcoatEnabled());
1279 defaultMaterialShaderKeyProperties.m_transmissionEnabled.setValue(theGeneratedKey, theMaterial->isTransmissionEnabled());
1280
1281 // Run through the material's images and prepare them for render.
1282 // this may in fact set pickable on the renderable flags if one of the images
1283 // links to a sub presentation or any offscreen rendered object.
1284 QSSGRenderableImage *nextImage = nullptr;
1285#define CHECK_IMAGE_AND_PREPARE(img, imgtype, shadercomponent)
1286 if ((img))
1287 prepareImageForRender(*(img), imgtype, firstImage, nextImage, renderableFlags,
1288 theGeneratedKey, shadercomponent, &inMaterial)
1289
1290 if (theMaterial->type == QSSGRenderGraphObject::Type::PrincipledMaterial ||
1291 theMaterial->type == QSSGRenderGraphObject::Type::SpecularGlossyMaterial) {
1292 CHECK_IMAGE_AND_PREPARE(theMaterial->colorMap,
1293 QSSGRenderableImage::Type::BaseColor,
1294 QSSGShaderDefaultMaterialKeyProperties::BaseColorMap);
1295 CHECK_IMAGE_AND_PREPARE(theMaterial->occlusionMap,
1296 QSSGRenderableImage::Type::Occlusion,
1297 QSSGShaderDefaultMaterialKeyProperties::OcclusionMap);
1298 CHECK_IMAGE_AND_PREPARE(theMaterial->heightMap,
1299 QSSGRenderableImage::Type::Height,
1300 QSSGShaderDefaultMaterialKeyProperties::HeightMap);
1301 CHECK_IMAGE_AND_PREPARE(theMaterial->clearcoatMap,
1302 QSSGRenderableImage::Type::Clearcoat,
1303 QSSGShaderDefaultMaterialKeyProperties::ClearcoatMap);
1304 CHECK_IMAGE_AND_PREPARE(theMaterial->clearcoatRoughnessMap,
1305 QSSGRenderableImage::Type::ClearcoatRoughness,
1306 QSSGShaderDefaultMaterialKeyProperties::ClearcoatRoughnessMap);
1307 CHECK_IMAGE_AND_PREPARE(theMaterial->clearcoatNormalMap,
1308 QSSGRenderableImage::Type::ClearcoatNormal,
1309 QSSGShaderDefaultMaterialKeyProperties::ClearcoatNormalMap);
1310 CHECK_IMAGE_AND_PREPARE(theMaterial->transmissionMap,
1311 QSSGRenderableImage::Type::Transmission,
1312 QSSGShaderDefaultMaterialKeyProperties::TransmissionMap);
1313 CHECK_IMAGE_AND_PREPARE(theMaterial->thicknessMap,
1314 QSSGRenderableImage::Type::Thickness,
1315 QSSGShaderDefaultMaterialKeyProperties::ThicknessMap);
1316 if (theMaterial->type == QSSGRenderGraphObject::Type::PrincipledMaterial) {
1317 CHECK_IMAGE_AND_PREPARE(theMaterial->metalnessMap,
1318 QSSGRenderableImage::Type::Metalness,
1319 QSSGShaderDefaultMaterialKeyProperties::MetalnessMap);
1320 }
1321 } else {
1322 CHECK_IMAGE_AND_PREPARE(theMaterial->colorMap,
1323 QSSGRenderableImage::Type::Diffuse,
1324 QSSGShaderDefaultMaterialKeyProperties::DiffuseMap);
1325 }
1326 CHECK_IMAGE_AND_PREPARE(theMaterial->emissiveMap, QSSGRenderableImage::Type::Emissive, QSSGShaderDefaultMaterialKeyProperties::EmissiveMap);
1327 CHECK_IMAGE_AND_PREPARE(theMaterial->specularReflection,
1328 QSSGRenderableImage::Type::Specular,
1329 QSSGShaderDefaultMaterialKeyProperties::SpecularMap);
1330 CHECK_IMAGE_AND_PREPARE(theMaterial->roughnessMap,
1331 QSSGRenderableImage::Type::Roughness,
1332 QSSGShaderDefaultMaterialKeyProperties::RoughnessMap);
1333 CHECK_IMAGE_AND_PREPARE(theMaterial->opacityMap, QSSGRenderableImage::Type::Opacity, QSSGShaderDefaultMaterialKeyProperties::OpacityMap);
1334 CHECK_IMAGE_AND_PREPARE(theMaterial->bumpMap, QSSGRenderableImage::Type::Bump, QSSGShaderDefaultMaterialKeyProperties::BumpMap);
1335 CHECK_IMAGE_AND_PREPARE(theMaterial->specularMap,
1336 QSSGRenderableImage::Type::SpecularAmountMap,
1337 QSSGShaderDefaultMaterialKeyProperties::SpecularAmountMap);
1338 CHECK_IMAGE_AND_PREPARE(theMaterial->normalMap, QSSGRenderableImage::Type::Normal, QSSGShaderDefaultMaterialKeyProperties::NormalMap);
1339 CHECK_IMAGE_AND_PREPARE(theMaterial->translucencyMap,
1340 QSSGRenderableImage::Type::Translucency,
1341 QSSGShaderDefaultMaterialKeyProperties::TranslucencyMap);
1342 }
1343#undef CHECK_IMAGE_AND_PREPARE
1344
1345 if (subsetOpacity < QSSGRendererPrivate::minimumRenderOpacity) {
1346 subsetOpacity = 0.0f;
1347 // You can still pick against completely transparent objects(or rather their bounding
1348 // box)
1349 // you just don't render them.
1350 renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
1351 renderableFlags |= QSSGRenderableObjectFlag::CompletelyTransparent;
1352 }
1353
1354 if (subsetOpacity > 1.f - QSSGRendererPrivate::minimumRenderOpacity)
1355 subsetOpacity = 1.f;
1356 else
1357 renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
1358
1359 if (inMaterial.isTransmissionEnabled()) {
1360 ioFlags.setRequiresScreenTexture(true);
1361 ioFlags.setRequiresMipmapsForScreenTexture(true);
1362 renderableFlags |= QSSGRenderableObjectFlag::RequiresScreenTexture;
1363 }
1364
1365 if (renderableFlags.hasTransparency()) {
1366 if (orderIndependentTransparencyEnabled)
1367 defaultMaterialShaderKeyProperties.m_orderIndependentTransparency.setValue(theGeneratedKey, int(layer.oitMethod));
1368 if (layer.oitMethodDirty)
1369 renderableFlags |= QSSGRenderableObjectFlag::Dirty;
1370 }
1371
1372 retval.firstImage = firstImage;
1373 if (retval.renderableFlags.isDirty())
1374 retval.dirty = true;
1375 if (retval.dirty)
1376 renderer->addMaterialDirtyClear(&inMaterial);
1377 return retval;
1378}
1379
1380QSSGDefaultMaterialPreparationResult QSSGLayerRenderData::prepareCustomMaterialForRender(
1381 QSSGRenderCustomMaterial &inMaterial, QSSGRenderableObjectFlags &inExistingFlags,
1382 float inOpacity, bool alreadyDirty, bool hasAnyLights, bool anyLightHasShadows,
1383 QSSGLayerRenderPreparationResultFlags &ioFlags)
1384{
1385 QSSGDefaultMaterialPreparationResult retval(QSSGShaderDefaultMaterialKey(qHash(features)));
1386 retval.renderableFlags = inExistingFlags;
1387 QSSGRenderableObjectFlags &renderableFlags(retval.renderableFlags);
1388 QSSGShaderDefaultMaterialKey &theGeneratedKey(retval.materialKey);
1389 retval.opacity = inOpacity;
1390 float &subsetOpacity(retval.opacity);
1391
1392 if (subsetOpacity < QSSGRendererPrivate::minimumRenderOpacity) {
1393 subsetOpacity = 0.0f;
1394 // You can still pick against completely transparent objects(or rather their bounding
1395 // box)
1396 // you just don't render them.
1397 renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
1398 renderableFlags |= QSSGRenderableObjectFlag::CompletelyTransparent;
1399 }
1400
1401 if (subsetOpacity > 1.f - QSSGRendererPrivate::minimumRenderOpacity)
1402 subsetOpacity = 1.f;
1403 else
1404 renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
1405
1406 defaultMaterialShaderKeyProperties.m_hasLighting.setValue(theGeneratedKey, true);
1407 defaultMaterialShaderKeyProperties.m_hasPunctualLights.setValue(theGeneratedKey, hasAnyLights);
1408 defaultMaterialShaderKeyProperties.m_hasShadows.setValue(theGeneratedKey, anyLightHasShadows);
1409 defaultMaterialShaderKeyProperties.m_hasIbl.setValue(theGeneratedKey, layer.lightProbe != nullptr || layer.skyMaterial != nullptr);
1410 defaultMaterialShaderKeyProperties.m_specularEnabled.setValue(theGeneratedKey, true);
1411
1412 defaultMaterialShaderKeyProperties.m_specularAAEnabled.setValue(theGeneratedKey, layer.specularAAEnabled);
1413
1414 // isDoubleSided
1415 defaultMaterialShaderKeyProperties.m_isDoubleSided.setValue(theGeneratedKey, inMaterial.m_cullMode == QSSGCullFaceMode::Disabled);
1416
1417 // Custom Materials are always Metallic Roughness Workflow
1418 defaultMaterialShaderKeyProperties.m_metallicRoughnessEnabled.setValue(theGeneratedKey, true);
1419
1420 // Does the material override the position output
1421 const bool overridesPosition = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::OverridesPosition);
1422 defaultMaterialShaderKeyProperties.m_overridesPosition.setValue(theGeneratedKey, overridesPosition);
1423
1424 // Optional usage of PROJECTION_MATRIX and/or INVERSE_PROJECTION_MATRIX
1425 const bool usesProjectionMatrix = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::ProjectionMatrix);
1426 defaultMaterialShaderKeyProperties.m_usesProjectionMatrix.setValue(theGeneratedKey, usesProjectionMatrix);
1427 const bool usesInvProjectionMatrix = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::InverseProjectionMatrix);
1428 defaultMaterialShaderKeyProperties.m_usesInverseProjectionMatrix.setValue(theGeneratedKey, usesInvProjectionMatrix);
1429
1430 // vertex attribute presence flags
1431 setVertexInputPresence(renderableFlags, theGeneratedKey);
1432
1433 // set the flag indicating the need for gl_PointSize
1434 defaultMaterialShaderKeyProperties.m_usesPointsTopology.setValue(theGeneratedKey, renderableFlags.isPointsTopology());
1435
1436 // propagate the flag indicating the presence of a lightmap
1437 defaultMaterialShaderKeyProperties.m_lightmapEnabled.setValue(theGeneratedKey, renderableFlags.rendersWithLightmap());
1438
1439 // debug modes
1440 defaultMaterialShaderKeyProperties.m_debugMode.setValue(theGeneratedKey, int(layer.debugMode));
1441
1442 // fog
1443 defaultMaterialShaderKeyProperties.m_fogEnabled.setValue(theGeneratedKey, layer.fog.enabled);
1444
1445 // multiview
1446 defaultMaterialShaderKeyProperties.m_viewCount.setValue(theGeneratedKey, layer.viewCount);
1447 defaultMaterialShaderKeyProperties.m_usesViewIndex.setValue(theGeneratedKey,
1448 inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::ViewIndex));
1449
1450 // Knowing whether VAR_COLOR is used becomes relevant when there is no
1451 // custom vertex shader, but VAR_COLOR is present in the custom fragment
1452 // snippet, because that case needs special care.
1453 const bool usesVarColor = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::VarColor);
1454 defaultMaterialShaderKeyProperties.m_usesVarColor.setValue(theGeneratedKey, usesVarColor);
1455
1456 const bool usesClearcoat = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Clearcoat);
1457 defaultMaterialShaderKeyProperties.m_clearcoatEnabled.setValue(theGeneratedKey, usesClearcoat);
1458
1459 const bool usesClearcoatFresnelScaleBias = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::ClearcoatFresnelScaleBias);
1460 defaultMaterialShaderKeyProperties.m_clearcoatFresnelScaleBiasEnabled.setValue(theGeneratedKey, usesClearcoatFresnelScaleBias);
1461
1462 const bool usesFresnelScaleBias = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::FresnelScaleBias);
1463 defaultMaterialShaderKeyProperties.m_fresnelScaleBiasEnabled.setValue(theGeneratedKey, usesFresnelScaleBias);
1464
1465 const bool usesTransmission = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Transmission);
1466 defaultMaterialShaderKeyProperties.m_transmissionEnabled.setValue(theGeneratedKey, usesTransmission);
1467
1468 if (inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Blending))
1469 renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
1470
1471 if (inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::ScreenTexture)) {
1472 ioFlags.setRequiresScreenTexture(true);
1473 renderableFlags |= QSSGRenderableObjectFlag::RequiresScreenTexture;
1474 }
1475
1476 if (inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::ScreenMipTexture)) {
1477 ioFlags.setRequiresScreenTexture(true);
1478 ioFlags.setRequiresMipmapsForScreenTexture(true);
1479 renderableFlags |= QSSGRenderableObjectFlag::RequiresScreenTexture;
1480 }
1481
1482 if (inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::DepthTexture))
1483 ioFlags.setRequiresDepthTexture(true);
1484
1485 if (inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::NormalTexture)) {
1486 ioFlags.setRequiresNormalTexture(true);
1487 renderableFlags |= QSSGRenderableObjectFlag::RequiresNormalTexture;
1488 }
1489
1490 if (inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::AoTexture)) {
1491 ioFlags.setRequiresDepthTexture(true);
1492 ioFlags.setRequiresSsaoPass(true);
1493 }
1494 if (orderIndependentTransparencyEnabled && renderableFlags.hasTransparency())
1495 defaultMaterialShaderKeyProperties.m_orderIndependentTransparency.setValue(theGeneratedKey, int(layer.oitMethod));
1496
1497 retval.firstImage = nullptr;
1498
1499 if (inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::MotionVectorTexture))
1500 ioFlags.setRequiresMotionVectorPass(true);
1501
1502 if (retval.dirty || alreadyDirty)
1503 renderer->addMaterialDirtyClear(&inMaterial);
1504 return retval;
1505}
1506
1507void QSSGLayerRenderData::setLightmapTexture(const QSSGModelContext &modelContext, QRhiTexture *lightmapTexture)
1508{
1509 lightmapTextures[&modelContext] = lightmapTexture;
1510}
1511
1512QRhiTexture *QSSGLayerRenderData::getLightmapTexture(const QSSGModelContext &modelContext) const
1513{
1514 QRhiTexture *ret = nullptr;
1515 if (modelContext.model.hasLightmap()) {
1516 const auto it = lightmapTextures.constFind(&modelContext);
1517 ret = (it != lightmapTextures.cend()) ? *it : nullptr;
1518 }
1519
1520 return ret;
1521}
1522
1523void QSSGLayerRenderData::setBonemapTexture(const QSSGModelContext &modelContext, QRhiTexture *bonemapTexture)
1524{
1525 bonemapTextures[&modelContext] = bonemapTexture;
1526}
1527
1528QRhiTexture *QSSGLayerRenderData::getBonemapTexture(const QSSGModelContext &modelContext) const
1529{
1530 QRhiTexture *ret = nullptr;
1531 if (modelContext.model.usesBoneTexture()) {
1532 const auto it = bonemapTextures.constFind(&modelContext);
1533 ret = (it != bonemapTextures.cend()) ? *it : nullptr;
1534 }
1535
1536 return ret;
1537}
1538
1539static bool hasCustomBlendMode(const QSSGRenderCustomMaterial &material)
1540{
1541 // Check SrcOver
1542
1543 // srcAlpha is same for all
1544 if (material.m_srcAlphaBlend != QRhiGraphicsPipeline::One)
1545 return true;
1546
1547 // SrcOver srcColor is SrcAlpha
1548 if (material.m_srcBlend != QRhiGraphicsPipeline::SrcAlpha)
1549 return true;
1550
1551 if (material.m_dstBlend == QRhiGraphicsPipeline::OneMinusSrcAlpha
1552 && material.m_dstAlphaBlend == QRhiGraphicsPipeline::OneMinusSrcAlpha)
1553 return false;
1554 return true;
1555}
1556
1557// inModel is const to emphasize the fact that its members cannot be written
1558// here: in case there is a scene shared between multiple View3Ds in different
1559// QQuickWindows, each window may run this in their own render thread, while
1560// inModel is the same.
1561bool QSSGLayerRenderData::prepareModelsForRender(QSSGRenderContextInterface &contextInterface,
1562 const RenderableNodeEntries &renderableModels,
1563 QSSGLayerRenderPreparationResultFlags &ioFlags,
1564 const QSSGRenderCameraList &allCameras,
1565 const QSSGRenderCameraDataList &allCameraData,
1566 TModelContextPtrList &modelContexts,
1567 QSSGRenderableObjectList &opaqueObjects,
1568 QSSGRenderableObjectList &transparentObjects,
1569 QSSGRenderableObjectList &screenTextureObjects,
1570 float lodThreshold)
1571{
1572 const auto &rhiCtx = contextInterface.rhiContext();
1573 const auto &bufferManager = contextInterface.bufferManager();
1574
1575 const auto &debugDrawSystem = contextInterface.debugDrawSystem();
1576 const bool maybeDebugDraw = debugDrawSystem && debugDrawSystem->isEnabled();
1577
1578 bool wasDirty = false;
1579
1580 for (const QSSGRenderableNodeEntry &renderable : renderableModels) {
1581 if ((renderable.overridden & QSSGRenderableNodeEntry::Overridden::Disabled) != 0)
1582 continue;
1583
1584 const QSSGRenderModel &model = *static_cast<QSSGRenderModel *>(renderable.node);
1585 const auto &lights = renderable.lights;
1586 QSSGRenderMesh *theMesh = modelData->getMesh(model);
1587 if (!theMesh)
1588 continue;
1589
1590 const bool altGlobalTransform = ((renderable.overridden & QSSGRenderableNodeEntry::Overridden::GlobalTransform) != 0);
1591 const auto &globalTransform = altGlobalTransform ? renderable.extOverrides.globalTransform : getGlobalTransform(model);
1592 QMatrix3x3 normalMatrix { Qt::Uninitialized };
1593 QSSGLayerRenderData::ModelViewProjections mvps;
1594 if (altGlobalTransform) {
1595 QSSGRenderNode::calculateNormalMatrix(globalTransform, normalMatrix);
1596 size_t mvpCount = 0;
1597 for (const auto &cameraData : allCameraData) {
1598 QSSGRenderNode::calculateMVPAndNormalMatrix(globalTransform, cameraData.viewProjection, mvps[mvpCount++], normalMatrix);
1599 }
1600 } else {
1601 if (model.usesBoneTexture()) {
1602 // FIXME:
1603 // For skinning, node's global transformation will be ignored and
1604 // an identity matrix will be used for the normalMatrix
1605 size_t mvpCount = 0;
1606 for (const QSSGRenderCameraData &cameraData : allCameraData) {
1607 mvps[mvpCount++] = cameraData.viewProjection;
1608 normalMatrix = QMatrix3x3();
1609 }
1610 } else {
1611 normalMatrix = getNormalMatrix(model);
1612 mvps = getModelMvps(model);
1613 }
1614 }
1615 const bool altModelOpacity = ((renderable.overridden & QSSGRenderableNodeEntry::Overridden::GlobalOpacity) != 0);
1616 const float modelGlobalOpacity = altModelOpacity ? renderable.extOverrides.globalOpacity : getGlobalOpacity(model);
1617 QSSGModelContext &theModelContext = *RENDER_FRAME_NEW<QSSGModelContext>(contextInterface, model, globalTransform, normalMatrix, mvps);
1618 modelContexts.push_back(&theModelContext);
1619 // We might over-allocate here, as the material list technically can contain an invalid (nullptr) material.
1620 // We'll fix that by adjusting the size at the end for now...
1621 const auto &meshSubsets = theMesh->subsets;
1622 const auto meshSubsetCount = meshSubsets.size();
1623 theModelContext.subsets = RENDER_FRAME_NEW_BUFFER<QSSGSubsetRenderable>(contextInterface, meshSubsetCount);
1624
1625 // Prepare boneTexture for skinning
1626 if (model.skin) {
1627 auto boneTexture = bufferManager->loadSkinmap(model.skin);
1628 setBonemapTexture(theModelContext, boneTexture.m_texture);
1629 } else if (model.skeleton) {
1630 auto boneTexture = bufferManager->loadSkinmap(&(model.skeleton->boneTexData));
1631 setBonemapTexture(theModelContext, boneTexture.m_texture);
1632 }
1633
1634 // many renderableFlags are the same for all the subsets
1635 QSSGRenderableObjectFlags renderableFlagsForModel;
1636
1637 if (meshSubsetCount > 0) {
1638 const QSSGRenderSubset &theSubset = meshSubsets.at(0);
1639
1640 renderableFlagsForModel.setMotionVectorParticipant(model.motionVectorEnabled);
1641
1642 renderableFlagsForModel.setCastsShadows(model.castsShadows);
1643 renderableFlagsForModel.setReceivesShadows(model.receivesShadows);
1644 renderableFlagsForModel.setReceivesReflections(model.receivesReflections);
1645 renderableFlagsForModel.setCastsReflections(model.castsReflections);
1646
1647 renderableFlagsForModel.setUsedInBakedLighting(model.usedInBakedLighting);
1648 if (model.hasLightmap()) {
1649 QSSGRenderImageTexture lmImageTexture = bufferManager->loadLightmap(model);
1650 if (lmImageTexture.m_texture) {
1651 renderableFlagsForModel.setRendersWithLightmap(true);
1652 setLightmapTexture(theModelContext, lmImageTexture.m_texture);
1653 }
1654 }
1655
1656 // TODO: This should be a oneshot thing, move the flags over!
1657 // With the RHI we need to be able to tell the material shader
1658 // generator to not generate vertex input attributes that are not
1659 // provided by the mesh. (because unlike OpenGL, other graphics
1660 // APIs may treat unbound vertex inputs as a fatal error)
1661 bool hasJoint = false;
1662 bool hasWeight = false;
1663 bool hasMorphTarget = theSubset.rhi.targetsTexture != nullptr;
1664 for (const QSSGRhiInputAssemblerState::InputSemantic &sem : std::as_const(theSubset.rhi.ia.inputs)) {
1665 if (sem == QSSGRhiInputAssemblerState::PositionSemantic) {
1666 renderableFlagsForModel.setHasAttributePosition(true);
1667 } else if (sem == QSSGRhiInputAssemblerState::NormalSemantic) {
1668 renderableFlagsForModel.setHasAttributeNormal(true);
1669 } else if (sem == QSSGRhiInputAssemblerState::TexCoord0Semantic) {
1670 renderableFlagsForModel.setHasAttributeTexCoord0(true);
1671 } else if (sem == QSSGRhiInputAssemblerState::TexCoord1Semantic) {
1672 renderableFlagsForModel.setHasAttributeTexCoord1(true);
1673 } else if (sem == QSSGRhiInputAssemblerState::TexCoordLightmapSemantic) {
1674 renderableFlagsForModel.setHasAttributeTexCoordLightmap(true);
1675 } else if (sem == QSSGRhiInputAssemblerState::TangentSemantic) {
1676 renderableFlagsForModel.setHasAttributeTangent(true);
1677 } else if (sem == QSSGRhiInputAssemblerState::BinormalSemantic) {
1678 renderableFlagsForModel.setHasAttributeBinormal(true);
1679 } else if (sem == QSSGRhiInputAssemblerState::ColorSemantic) {
1680 renderableFlagsForModel.setHasAttributeColor(true);
1681 // For skinning, we will set the HasAttribute only
1682 // if the mesh has both joint and weight
1683 } else if (sem == QSSGRhiInputAssemblerState::JointSemantic) {
1684 hasJoint = true;
1685 } else if (sem == QSSGRhiInputAssemblerState::WeightSemantic) {
1686 hasWeight = true;
1687 }
1688 }
1689 renderableFlagsForModel.setHasAttributeJointAndWeight(hasJoint && hasWeight);
1690 renderableFlagsForModel.setHasAttributeMorphTarget(hasMorphTarget);
1691 }
1692
1693 QSSGRenderableObjectList bakedLightingObjects;
1694 bool usesBlendParticles = particlesEnabled && theModelContext.model.particleBuffer != nullptr
1695 && model.particleBuffer->particleCount();
1696 const bool anyLightHasShadows = std::find_if(lights.begin(),
1697 lights.end(),
1698 [](const QSSGShaderLight &light) { return light.shadows; })
1699 != lights.end();
1700 const bool hasAnyLights = !lights.isEmpty();
1701 QSSGRenderLight::SoftShadowQuality maxSoftShadowQuality = QSSGRenderLight::SoftShadowQuality::Hard;
1702 if (anyLightHasShadows) {
1703 // Iterate the light list to find the maximum shadow quality of lights that cast shadows
1704 for (const QSSGShaderLight &light : lights) {
1705 if (light.shadows && light.light->m_softShadowQuality > maxSoftShadowQuality)
1706 maxSoftShadowQuality = light.light->m_softShadowQuality;
1707 }
1708 }
1709
1710
1711 // Subset(s)
1712 auto &renderableSubsets = theModelContext.subsets;
1713 const bool hasMaterialOverrides = ((renderable.overridden & QSSGRenderableNodeEntry::Overridden::Materials) != 0);
1714 const auto &materials = hasMaterialOverrides ? renderable.extOverrides.materials : modelData->getMaterials(model);
1715 const auto materialCount = materials.size();
1716 QSSGRenderGraphObject *lastMaterial = !materials.isEmpty() ? materials.last() : nullptr;
1717 int idx = 0, subsetIdx = 0;
1718 for (; idx < meshSubsetCount; ++idx) {
1719 // If the materials list < size of subsets, then use the last material for the rest
1720 QSSGRenderGraphObject *theMaterialObject = (idx >= materialCount) ? lastMaterial : materials[idx];
1721 if (!theMaterialObject)
1722 continue;
1723
1724 const QSSGRenderSubset &theSubset = meshSubsets.at(idx);
1725 QSSGRenderableObjectFlags renderableFlags = renderableFlagsForModel;
1726 float subsetOpacity = modelGlobalOpacity;
1727
1728 renderableFlags.setPointsTopology(theSubset.rhi.ia.topology == QRhiGraphicsPipeline::Points);
1729 QSSGRenderableObject *theRenderableObject = &renderableSubsets[subsetIdx++];
1730
1731 const bool usesInstancing = theModelContext.model.instancing()
1732 && rhiCtx->rhi()->isFeatureSupported(QRhi::Instancing);
1733 if (usesInstancing && theModelContext.model.instanceTable->hasTransparency())
1734 renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
1735 if (theModelContext.model.hasTransparency)
1736 renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
1737
1738 // Level Of Detail
1739 quint32 subsetLevelOfDetail = 0;
1740 if (!theSubset.lods.isEmpty() && lodThreshold > 0.0f) {
1741 // Accounts for FOV
1742 float lodDistanceMultiplier = camerasView[0]->getLevelOfDetailMultiplier();
1743 float distanceThreshold = 0.0f;
1744 const auto scale = QSSGUtils::mat44::getScale(globalTransform);
1745 float modelScale = qMax(scale.x(), qMax(scale.y(), scale.z()));
1746 QSSGBounds3 transformedBounds = theSubset.bounds;
1747 if (camerasView[0]->type != QSSGRenderGraphObject::Type::OrthographicCamera) {
1748 transformedBounds.transform(globalTransform);
1749 if (maybeDebugDraw && debugDrawSystem->isEnabled(QSSGDebugDrawSystem::Mode::MeshLod))
1750 debugDrawSystem->drawBounds(transformedBounds, QColor(Qt::red));
1751 const QMatrix4x4 cameraGlobalTranform = getGlobalTransform(*camerasView[0]);
1752 const QVector3D cameraNormal = QSSGRenderNode::getScalingCorrectDirection(cameraGlobalTranform);
1753 const QVector3D cameraPosition = QSSGRenderNode::getGlobalPos(cameraGlobalTranform);
1754 const QSSGPlane cameraPlane = QSSGPlane(cameraPosition, cameraNormal);
1755 const QVector3D lodSupportMin = transformedBounds.getSupport(-cameraNormal);
1756 const QVector3D lodSupportMax = transformedBounds.getSupport(cameraNormal);
1757 if (maybeDebugDraw && debugDrawSystem->isEnabled(QSSGDebugDrawSystem::Mode::MeshLod))
1758 debugDrawSystem->drawPoint(lodSupportMin, QColor("orange"));
1759
1760 const float distanceMin = cameraPlane.distance(lodSupportMin);
1761 const float distanceMax = cameraPlane.distance(lodSupportMax);
1762
1763 if (distanceMin * distanceMax < 0.0)
1764 distanceThreshold = 0.0;
1765 else if (distanceMin >= 0.0)
1766 distanceThreshold = distanceMin;
1767 else if (distanceMax <= 0.0)
1768 distanceThreshold = -distanceMax;
1769
1770 } else {
1771 // Orthographic Projection
1772 distanceThreshold = 1.0;
1773 }
1774
1775 int currentLod = -1;
1776 if (model.levelOfDetailBias > 0.0f) {
1777
1778 // At this stage, the distance and position of the most detailed instance are unknown;
1779 // therefore, the level of detail is managed manually using instancingLodFactor.
1780 int lodsCount = theSubset.lods.size();
1781 if (model.instanceTable && lodsCount) {
1782 currentLod = qRound((lodsCount - 1) * qBound(0.f, model.instancingLodFactor, 1.f));
1783 } else {
1784 const float threshold = distanceThreshold * lodDistanceMultiplier;
1785 const float modelBias = 1 / model.levelOfDetailBias;
1786 for (qsizetype i = 0; i < lodsCount; ++i) {
1787 float subsetDistance = theSubset.lods[i].distance * modelScale * modelBias;
1788 float screenSize = subsetDistance / threshold;
1789 if (screenSize > lodThreshold)
1790 break;
1791 currentLod = i;
1792 }
1793 }
1794
1795 }
1796 if (currentLod == -1)
1797 subsetLevelOfDetail = 0;
1798 else
1799 subsetLevelOfDetail = currentLod + 1;
1800 if (maybeDebugDraw && debugDrawSystem->isEnabled(QSSGDebugDrawSystem::Mode::MeshLod))
1801 debugDrawSystem->drawBounds(transformedBounds, QSSGDebugDrawSystem::levelOfDetailColor(subsetLevelOfDetail));
1802 }
1803
1804 QVector3D theModelCenter(theSubset.bounds.center());
1805 theModelCenter = QSSGUtils::mat44::transform(globalTransform, theModelCenter);
1806 if (maybeDebugDraw && debugDrawSystem->isEnabled(QSSGDebugDrawSystem::Mode::MeshLodNormal)) {
1807 const QMatrix4x4 allCamera0GlobalTransform = getGlobalTransform(*allCameras[0]);
1808 debugDrawSystem->debugNormals(*bufferManager, theModelContext, theSubset, subsetLevelOfDetail, (theModelCenter - QSSGRenderNode::getGlobalPos(allCamera0GlobalTransform)).length() * 0.01);
1809 }
1810
1811 auto checkF32TypeIndex = [&rhiCtx](QRhiVertexInputAttribute::Format f) {
1812 if ((f == QRhiVertexInputAttribute::Format::Float4)
1813 || (f == QRhiVertexInputAttribute::Format::Float3)
1814 || (f == QRhiVertexInputAttribute::Format::Float2)
1815 || (f == QRhiVertexInputAttribute::Format::Float)) {
1816 return true;
1817 }
1818 if (!rhiCtx->rhi()->isFeatureSupported(QRhi::IntAttributes))
1819 qWarning() << "WARN: Model has non-integer type indices for skinning but current RHI backend doesn't support it!";
1820 return false;
1821 };
1822
1823 if (theMaterialObject->type == QSSGRenderGraphObject::Type::DefaultMaterial ||
1824 theMaterialObject->type == QSSGRenderGraphObject::Type::PrincipledMaterial ||
1825 theMaterialObject->type == QSSGRenderGraphObject::Type::SpecularGlossyMaterial) {
1826 QSSGRenderDefaultMaterial &theMaterial(static_cast<QSSGRenderDefaultMaterial &>(*theMaterialObject));
1827 QSSGDefaultMaterialPreparationResult theMaterialPrepResult(prepareDefaultMaterialForRender(theMaterial, renderableFlags, subsetOpacity, hasAnyLights, anyLightHasShadows, ioFlags));
1828 QSSGShaderDefaultMaterialKey &theGeneratedKey(theMaterialPrepResult.materialKey);
1829 subsetOpacity = theMaterialPrepResult.opacity;
1830 QSSGRenderableImage *firstImage(theMaterialPrepResult.firstImage);
1831 wasDirty |= theMaterialPrepResult.dirty;
1832 renderableFlags = theMaterialPrepResult.renderableFlags;
1833 if (renderableFlags.hasTransparency())
1834 ioFlags.setHasCustomBlendMode(theMaterial.blendMode != QSSGRenderDefaultMaterial::MaterialBlendMode::SourceOver);
1835
1836 // Blend particles
1837 defaultMaterialShaderKeyProperties.m_blendParticles.setValue(theGeneratedKey, usesBlendParticles);
1838
1839 if (defaultMaterialShaderKeyProperties.m_orderIndependentTransparency.getValue(theGeneratedKey) == int(QSSGRenderLayer::OITMethod::LinkedList))
1840 defaultMaterialShaderKeyProperties.m_oitMSAA.setValue(theGeneratedKey, rhiCtx->mainPassSampleCount() > 1);
1841
1842 // Skin
1843 const auto boneCount = model.skin ? model.skin->boneCount :
1844 model.skeleton ? model.skeleton->boneCount : 0;
1845 defaultMaterialShaderKeyProperties.m_boneCount.setValue(theGeneratedKey, boneCount);
1846 if (auto idJoint = theSubset.rhi.ia.inputs.indexOf(QSSGRhiInputAssemblerState::JointSemantic); idJoint != -1) {
1847 const auto attr = theSubset.rhi.ia.inputLayout.attributeAt(idJoint);
1848 defaultMaterialShaderKeyProperties.m_usesFloatJointIndices.setValue(theGeneratedKey, checkF32TypeIndex(attr->format()));
1849 }
1850
1851 // SoftShadow quality
1852 defaultMaterialShaderKeyProperties.m_shadowSoftness.setShadowSoftness(theGeneratedKey, maxSoftShadowQuality);
1853
1854 // Instancing
1855 defaultMaterialShaderKeyProperties.m_usesInstancing.setValue(theGeneratedKey, usesInstancing);
1856 // Morphing
1857 defaultMaterialShaderKeyProperties.m_targetCount.setValue(theGeneratedKey,
1858 theSubset.rhi.ia.targetCount);
1859 defaultMaterialShaderKeyProperties.m_targetPositionOffset.setValue(theGeneratedKey,
1860 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::PositionSemantic]);
1861 defaultMaterialShaderKeyProperties.m_targetNormalOffset.setValue(theGeneratedKey,
1862 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::NormalSemantic]);
1863 defaultMaterialShaderKeyProperties.m_targetTangentOffset.setValue(theGeneratedKey,
1864 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::TangentSemantic]);
1865 defaultMaterialShaderKeyProperties.m_targetBinormalOffset.setValue(theGeneratedKey,
1866 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::BinormalSemantic]);
1867 defaultMaterialShaderKeyProperties.m_targetTexCoord0Offset.setValue(theGeneratedKey,
1868 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::TexCoord0Semantic]);
1869 defaultMaterialShaderKeyProperties.m_targetTexCoord1Offset.setValue(theGeneratedKey,
1870 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::TexCoord1Semantic]);
1871 defaultMaterialShaderKeyProperties.m_targetColorOffset.setValue(theGeneratedKey,
1872 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::ColorSemantic]);
1873
1874 new (theRenderableObject) QSSGSubsetRenderable(QSSGSubsetRenderable::Type::DefaultMaterialMeshSubset,
1875 renderableFlags,
1876 theModelCenter,
1877 renderer,
1878 theSubset,
1879 theModelContext,
1880 subsetOpacity,
1881 subsetLevelOfDetail,
1882 theMaterial,
1883 firstImage,
1884 theGeneratedKey,
1885 lights,
1886 anyLightHasShadows);
1887 wasDirty = wasDirty || renderableFlags.isDirty();
1888 } else if (theMaterialObject->type == QSSGRenderGraphObject::Type::CustomMaterial) {
1889 QSSGRenderCustomMaterial &theMaterial(static_cast<QSSGRenderCustomMaterial &>(*theMaterialObject));
1890
1891 const auto &theMaterialSystem(contextInterface.customMaterialSystem());
1892 wasDirty |= theMaterialSystem->prepareForRender(theModelContext.model, theSubset, theMaterial);
1893
1894 if (theMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Blending))
1895 ioFlags.setHasCustomBlendMode(!hasCustomBlendMode(theMaterial));
1896
1897 QSSGDefaultMaterialPreparationResult theMaterialPrepResult(
1898 prepareCustomMaterialForRender(theMaterial, renderableFlags, subsetOpacity, wasDirty,
1899 hasAnyLights, anyLightHasShadows, ioFlags));
1900 QSSGShaderDefaultMaterialKey &theGeneratedKey(theMaterialPrepResult.materialKey);
1901 subsetOpacity = theMaterialPrepResult.opacity;
1902 QSSGRenderableImage *firstImage(theMaterialPrepResult.firstImage);
1903 renderableFlags = theMaterialPrepResult.renderableFlags;
1904
1905 if (model.particleBuffer && model.particleBuffer->particleCount())
1906 defaultMaterialShaderKeyProperties.m_blendParticles.setValue(theGeneratedKey, true);
1907 else
1908 defaultMaterialShaderKeyProperties.m_blendParticles.setValue(theGeneratedKey, false);
1909
1910 if (defaultMaterialShaderKeyProperties.m_orderIndependentTransparency.getValue(theGeneratedKey) == int(QSSGRenderLayer::OITMethod::LinkedList))
1911 defaultMaterialShaderKeyProperties.m_oitMSAA.setValue(theGeneratedKey, rhiCtx->mainPassSampleCount() > 1);
1912
1913 // SoftShadow quality
1914 defaultMaterialShaderKeyProperties.m_shadowSoftness.setShadowSoftness(theGeneratedKey, maxSoftShadowQuality);
1915
1916 // Skin
1917 const auto boneCount = model.skin ? model.skin->boneCount :
1918 model.skeleton ? model.skeleton->boneCount : 0;
1919 defaultMaterialShaderKeyProperties.m_boneCount.setValue(theGeneratedKey, boneCount);
1920 if (auto idJoint = theSubset.rhi.ia.inputs.indexOf(QSSGRhiInputAssemblerState::JointSemantic); idJoint != -1) {
1921 const auto attr = theSubset.rhi.ia.inputLayout.attributeAt(idJoint);
1922 defaultMaterialShaderKeyProperties.m_usesFloatJointIndices.setValue(theGeneratedKey, checkF32TypeIndex(attr->format()));
1923 }
1924
1925 // Instancing
1926 bool usesInstancing = theModelContext.model.instancing()
1927 && rhiCtx->rhi()->isFeatureSupported(QRhi::Instancing);
1928 defaultMaterialShaderKeyProperties.m_usesInstancing.setValue(theGeneratedKey, usesInstancing);
1929 // Morphing
1930 defaultMaterialShaderKeyProperties.m_targetCount.setValue(theGeneratedKey,
1931 theSubset.rhi.ia.targetCount);
1932 defaultMaterialShaderKeyProperties.m_targetPositionOffset.setValue(theGeneratedKey,
1933 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::PositionSemantic]);
1934 defaultMaterialShaderKeyProperties.m_targetNormalOffset.setValue(theGeneratedKey,
1935 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::NormalSemantic]);
1936 defaultMaterialShaderKeyProperties.m_targetTangentOffset.setValue(theGeneratedKey,
1937 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::TangentSemantic]);
1938 defaultMaterialShaderKeyProperties.m_targetBinormalOffset.setValue(theGeneratedKey,
1939 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::BinormalSemantic]);
1940 defaultMaterialShaderKeyProperties.m_targetTexCoord0Offset.setValue(theGeneratedKey,
1941 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::TexCoord0Semantic]);
1942 defaultMaterialShaderKeyProperties.m_targetTexCoord1Offset.setValue(theGeneratedKey,
1943 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::TexCoord1Semantic]);
1944 defaultMaterialShaderKeyProperties.m_targetColorOffset.setValue(theGeneratedKey,
1945 theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::ColorSemantic]);
1946
1947 if (theMaterial.m_iblProbe)
1948 theMaterial.m_iblProbe->clearDirty();
1949
1950 new (theRenderableObject) QSSGSubsetRenderable(QSSGSubsetRenderable::Type::CustomMaterialMeshSubset,
1951 renderableFlags,
1952 theModelCenter,
1953 renderer,
1954 theSubset,
1955 theModelContext,
1956 subsetOpacity,
1957 subsetLevelOfDetail,
1958 theMaterial,
1959 firstImage,
1960 theGeneratedKey,
1961 lights,
1962 anyLightHasShadows);
1963 }
1964 if (theRenderableObject) // NOTE: Should just go in with the ctor args
1965 theRenderableObject->camdistSq = getCameraDistanceSq(*theRenderableObject, allCameraData[0]);
1966 }
1967
1968 // If the indices don't match then something's off and we need to adjust the subset renderable list size.
1969 if (Q_UNLIKELY(idx != subsetIdx))
1970 renderableSubsets.mSize = subsetIdx; // subsetIdx == next_subsetIdx == size
1971
1972 for (auto &ro : renderableSubsets) {
1973 const auto depthMode = ro.depthWriteMode;
1974 hasDepthWriteObjects |= (depthMode == QSSGDepthDrawMode::Always || depthMode == QSSGDepthDrawMode::OpaqueOnly);
1975 enum ObjectType : quint8 { ScreenTexture, Transparent, Opaque };
1976 static constexpr DepthPrepassObject ppState[][2] = { {DepthPrepassObject::None, DepthPrepassObject::ScreenTexture},
1977 {DepthPrepassObject::None, DepthPrepassObject::Transparent},
1978 {DepthPrepassObject::None, DepthPrepassObject::Opaque} };
1979
1980 if (ro.renderableFlags.requiresScreenTexture()) {
1981 depthPrepassObjectsState |= DepthPrepassObjectStateT(ppState[ObjectType::ScreenTexture][size_t(depthMode == QSSGDepthDrawMode::OpaquePrePass)]);
1982 screenTextureObjects.push_back({&ro, ro.camdistSq, model.tag});
1983 } else if (ro.renderableFlags.hasTransparency()) {
1984 depthPrepassObjectsState |= DepthPrepassObjectStateT(ppState[ObjectType::Transparent][size_t(depthMode == QSSGDepthDrawMode::OpaquePrePass)]);
1985 transparentObjects.push_back({&ro, ro.camdistSq, model.tag});
1986 } else {
1987 depthPrepassObjectsState |= DepthPrepassObjectStateT(ppState[ObjectType::Opaque][size_t(depthMode == QSSGDepthDrawMode::OpaquePrePass)]);
1988 opaqueObjects.push_back({&ro, ro.camdistSq, model.tag});
1989 }
1990
1991 if (ro.renderableFlags.usedInBakedLighting())
1992 bakedLightingObjects.push_back({&ro, ro.camdistSq, model.tag});
1993 }
1994
1995 if (!bakedLightingObjects.isEmpty())
1996 bakedLightingModels.push_back(QSSGBakedLightingModel(&model, bakedLightingObjects));
1997 }
1998
1999 return wasDirty;
2000}
2001
2002bool QSSGLayerRenderData::prepareParticlesForRender(const RenderableNodeEntries &renderableParticles, const QSSGRenderCameraData &cameraData, QSSGLayerRenderPreparationResultFlags &ioFlags)
2003{
2004 QSSG_ASSERT(particlesEnabled, return false);
2005
2006 QSSGRenderContextInterface &contextInterface = *renderer->contextInterface();
2007
2008 bool dirty = false;
2009
2010 //
2011 auto &opaqueObjects = opaqueObjectStore[0];
2012 auto &transparentObjects = transparentObjectStore[0];
2013 auto &screenTextureObjects = screenTextureObjectStore[0];
2014
2015 for (auto &renderable : renderableParticles) {
2016 QSSGShaderParticleMaterialKeyProperties &properties = particleMaterialShaderKeyProperties;
2017
2018 QSSGRenderParticles &particles = *static_cast<QSSGRenderParticles *>(renderable.node);
2019 QSSGShaderParticleMaterialKey &theGeneratedKey(particles.materialKey);
2020 const auto &lights = renderable.lights;
2021
2022 QSSGRenderableObjectFlags renderableFlags;
2023 renderableFlags.setCastsShadows(false);
2024 renderableFlags.setReceivesShadows(false);
2025 renderableFlags.setHasAttributePosition(true);
2026 renderableFlags.setHasAttributeNormal(true);
2027 renderableFlags.setHasAttributeTexCoord0(true);
2028 renderableFlags.setHasAttributeColor(true);
2029 renderableFlags.setHasTransparency(particles.m_hasTransparency);
2030 renderableFlags.setCastsReflections(particles.m_castsReflections);
2031 if (particles.m_hasTransparency && particles.m_blendMode != QSSGRenderParticles::BlendMode::SourceOver)
2032 ioFlags.setHasCustomBlendMode(true);
2033
2034 properties.m_isLineParticle.setValue(theGeneratedKey, particles.m_featureLevel >= QSSGRenderParticles::FeatureLevel::Line);
2035 const bool animated = particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::LineAnimated
2036 || particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::Animated
2037 || particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::AnimatedVLight
2038 || particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::LineAnimatedVLight;
2039 const bool mapped = particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::LineMapped
2040 || particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::Mapped
2041 || particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::MappedVLight
2042 || particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::LineMappedVLight;
2043 const bool lit = particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::LineVLight
2044 || particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::AnimatedVLight
2045 || particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::MappedVLight
2046 || particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::LineMappedVLight
2047 || particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::LineAnimatedVLight
2048 || particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::SimpleVLight;
2049 properties.m_isAnimated.setValue(theGeneratedKey, animated);
2050 properties.m_isMapped.setValue(theGeneratedKey, mapped);
2051 properties.m_hasLighting.setValue(theGeneratedKey, lit);
2052 properties.m_viewCount.setValue(theGeneratedKey, layer.viewCount);
2053 if (renderableFlags.hasTransparency() && orderIndependentTransparencyEnabled) {
2054 properties.m_orderIndependentTransparency.setValue(theGeneratedKey, int(layer.oitMethod));
2055 if (layer.oitMethod == QSSGRenderLayer::OITMethod::LinkedList)
2056 properties.m_oitMSAA.setValue(theGeneratedKey, contextInterface.rhiContext()->mainPassSampleCount() > 1);
2057 } else {
2058 properties.m_orderIndependentTransparency.setValue(theGeneratedKey, int(0));
2059 }
2060
2061 float opacity = getGlobalOpacity(particles);
2062 const QMatrix4x4 globalTransform = getGlobalTransform(particles);
2063 QVector3D center(particles.m_particleBuffer.bounds().center());
2064 center = QSSGUtils::mat44::transform(globalTransform, center);
2065
2066 QSSGRenderableImage *firstImage = nullptr;
2067 if (particles.m_sprite) {
2068 const auto &bufferManager = contextInterface.bufferManager();
2069
2070 if (particles.m_sprite->clearDirty())
2071 dirty = true;
2072
2073 const QSSGRenderImageTexture texture = bufferManager->loadRenderImage(particles.m_sprite);
2074 QSSGRenderableImage *theImage = RENDER_FRAME_NEW<QSSGRenderableImage>(contextInterface, QSSGRenderableImage::Type::Diffuse, *particles.m_sprite, texture);
2075 firstImage = theImage;
2076 properties.m_isSpriteLinear.setValue(theGeneratedKey, texture.m_flags.isLinear());
2077 }
2078
2079 QSSGRenderableImage *colorTable = nullptr;
2080 if (particles.m_colorTable) {
2081 const auto &bufferManager = contextInterface.bufferManager();
2082
2083 if (particles.m_colorTable->clearDirty())
2084 dirty = true;
2085
2086 const QSSGRenderImageTexture texture = bufferManager->loadRenderImage(particles.m_colorTable);
2087
2088 QSSGRenderableImage *theImage = RENDER_FRAME_NEW<QSSGRenderableImage>(contextInterface, QSSGRenderableImage::Type::Diffuse, *particles.m_colorTable, texture);
2089 colorTable = theImage;
2090 properties.m_isColorTableLinear.setValue(theGeneratedKey, texture.m_flags.isLinear());
2091 }
2092
2093 if (opacity > 0.0f && particles.m_particleBuffer.particleCount()) {
2094 auto *theRenderableObject = RENDER_FRAME_NEW<QSSGParticlesRenderable>(contextInterface,
2095 renderableFlags,
2096 center,
2097 renderer,
2098 globalTransform,
2099 particles,
2100 firstImage,
2101 colorTable,
2102 lights,
2103 opacity,
2104 theGeneratedKey);
2105 if (theRenderableObject) {
2106 if (theRenderableObject->renderableFlags.requiresScreenTexture())
2107 screenTextureObjects.push_back({theRenderableObject, getCameraDistanceSq(*theRenderableObject, cameraData), particles.tag});
2108 else if (theRenderableObject->renderableFlags.hasTransparency())
2109 transparentObjects.push_back({theRenderableObject, getCameraDistanceSq(*theRenderableObject, cameraData), particles.tag});
2110 else
2111 opaqueObjects.push_back({theRenderableObject, getCameraDistanceSq(*theRenderableObject, cameraData), particles.tag});
2112 }
2113 }
2114 }
2115
2116 return dirty;
2117}
2118
2119void QSSGLayerRenderData::prepareResourceLoaders()
2120{
2121 QSSGRenderContextInterface &contextInterface = *renderer->contextInterface();
2122 const auto &bufferManager = contextInterface.bufferManager();
2123
2124 for (const auto resourceLoader : std::as_const(layer.resourceLoaders))
2125 bufferManager->processResourceLoader(static_cast<QSSGRenderResourceLoader *>(resourceLoader));
2126}
2127
2128void QSSGLayerRenderData::prepareReflectionProbesForRender()
2129{
2130 const auto probeCount = reflectionProbesView.size();
2131 requestReflectionMapManager(); // ensure that we have a reflection map manager
2132
2133 for (int i = 0; i < probeCount; i++) {
2134 QSSGRenderReflectionProbe* probe = reflectionProbesView[i];
2135
2136 QMatrix4x4 probeTransform = getGlobalTransform(*probe);
2137
2138 int reflectionObjectCount = 0;
2139 QVector3D probeExtent = probe->boxSize / 2;
2140 QSSGBounds3 probeBound = QSSGBounds3::centerExtents(QSSGRenderNode::getGlobalPos(probeTransform) + probe->boxOffset, probeExtent);
2141
2142 const auto injectProbe = [&](const QSSGRenderableObjectHandle &handle) {
2143 if (handle.obj->renderableFlags.testFlag(QSSGRenderableObjectFlag::ReceivesReflections)
2144 && !(handle.obj->type == QSSGRenderableObject::Type::Particles)) {
2145 QSSGSubsetRenderable* renderableObj = static_cast<QSSGSubsetRenderable*>(handle.obj);
2146 QSSGBounds3 nodeBound = renderableObj->bounds;
2147 QVector4D vmin(nodeBound.minimum, 1.0);
2148 QVector4D vmax(nodeBound.maximum, 1.0);
2149 const QMatrix4x4 &renderableTransform = renderableObj->modelContext.globalTransform;
2150 vmin = renderableTransform * vmin;
2151 vmax = renderableTransform * vmax;
2152 nodeBound.minimum = vmin.toVector3D();
2153 nodeBound.maximum = vmax.toVector3D();
2154 if (probeBound.intersects(nodeBound)) {
2155 QVector3D nodeBoundCenter = nodeBound.center();
2156 QVector3D probeBoundCenter = probeBound.center();
2157 float distance = nodeBoundCenter.distanceToPoint(probeBoundCenter);
2158 if (renderableObj->reflectionProbeIndex == -1 || distance < renderableObj->distanceFromReflectionProbe) {
2159 renderableObj->reflectionProbeIndex = i;
2160 renderableObj->distanceFromReflectionProbe = distance;
2161 renderableObj->reflectionProbe.parallaxCorrection = probe->parallaxCorrection;
2162 renderableObj->reflectionProbe.probeCubeMapCenter = QSSGRenderNode::getGlobalPos(probeTransform);
2163 renderableObj->reflectionProbe.probeBoxMax = probeBound.maximum;
2164 renderableObj->reflectionProbe.probeBoxMin = probeBound.minimum;
2165 renderableObj->reflectionProbe.enabled = true;
2166 reflectionObjectCount++;
2167 }
2168 }
2169 }
2170 };
2171
2172 const auto &transparentObjects = std::as_const(transparentObjectStore[0]);
2173 const auto &opaqueObjects = std::as_const(opaqueObjectStore[0]);
2174 const auto &screenTextureObjects = std::as_const(screenTextureObjectStore[0]);
2175
2176 for (const auto &handle : std::as_const(transparentObjects))
2177 injectProbe(handle);
2178
2179 for (const auto &handle : std::as_const(opaqueObjects))
2180 injectProbe(handle);
2181
2182 for (const auto &handle : std::as_const(screenTextureObjects))
2183 injectProbe(handle);
2184
2185 if (probe->texture)
2186 reflectionMapManager->addTexturedReflectionMapEntry(i, *probe);
2187 else if (reflectionObjectCount > 0)
2188 reflectionMapManager->addReflectionMapEntry(i, *probe);
2189 }
2190}
2191
2192static bool scopeLight(QSSGRenderNode *node, QSSGRenderNode *lightScope)
2193{
2194 // check if the node is parent of the lightScope
2195 while (node) {
2196 if (node == lightScope)
2197 return true;
2198 node = node->parent;
2199 }
2200 return false;
2201}
2202
2203static const int REDUCED_MAX_LIGHT_COUNT_THRESHOLD_BYTES = 5200; // 325 vec4s
2204
2205static inline int effectiveMaxLightCount(const QSSGShaderFeatures &features)
2206{
2207 if (features.isSet(QSSGShaderFeatures::Feature::ReduceMaxNumLights))
2208 return QSSG_REDUCED_MAX_NUM_LIGHTS;
2209
2210 return QSSG_MAX_NUM_LIGHTS;
2211}
2212
2213static inline int effectiveMaxDirectionalLightCount(const QSSGShaderFeatures &features)
2214{
2215 if (features.isSet(QSSGShaderFeatures::Feature::ReduceMaxNumLights))
2216 return QSSG_REDUCED_MAX_NUM_DIRECTIONAL_LIGHTS;
2217
2218 return QSSG_MAX_NUM_DIRECTIONAL_LIGHTS;
2219}
2220
2221static void updateDirtySkeletons(const QSSGLayerRenderData &renderData, const QSSGLayerRenderData::QSSGModelsView &renderableNodes)
2222{
2223 // First model using skeleton clears the dirty flag so we need another mechanism
2224 // to tell to the other models the skeleton is dirty.
2225 QSet<QSSGRenderSkeleton *> dirtySkeletons;
2226 for (const auto &modelNode : std::as_const(renderableNodes)) {
2227 QSSGRenderSkeleton *skeletonNode = modelNode->skeleton;
2228 bool hcj = false;
2229 if (skeletonNode) {
2230 const bool dirtySkeleton = dirtySkeletons.contains(skeletonNode);
2231 const bool hasDirtyNonJoints = (skeletonNode->containsNonJointNodes
2232 && (hasDirtyNonJointNodes(skeletonNode, hcj) || dirtySkeleton));
2233 if (skeletonNode->skinningDirty || hasDirtyNonJoints) {
2234 Q_ASSERT(!skeletonNode->isDirty(QSSGRenderNode::DirtyFlag::GlobalValuesDirty));
2235 skeletonNode->boneTransformsDirty = false;
2236 if (hasDirtyNonJoints && !dirtySkeleton)
2237 dirtySkeletons.insert(skeletonNode);
2238 skeletonNode->skinningDirty = false;
2239 const qsizetype dataSize = BONEDATASIZE4ID(skeletonNode->maxIndex);
2240 if (skeletonNode->boneData.size() < dataSize)
2241 skeletonNode->boneData.resize(dataSize);
2242 skeletonNode->containsNonJointNodes = false;
2243 for (auto &child : skeletonNode->children)
2244 collectBoneTransforms(renderData, &child, skeletonNode, modelNode->inverseBindPoses);
2245 }
2246 skeletonNode->boneCount = skeletonNode->boneData.size() / 2 / 4 / 16;
2247 const int boneTexWidth = qCeil(qSqrt(skeletonNode->boneCount * 4 * 2));
2248 skeletonNode->boneTexData.setSize(QSize(boneTexWidth, boneTexWidth));
2249 skeletonNode->boneData.resize(boneTexWidth * boneTexWidth * 16);
2250 skeletonNode->boneTexData.setTextureData(skeletonNode->boneData);
2251 }
2252 const int numMorphTarget = modelNode->morphTargets.size();
2253 for (int i = 0; i < numMorphTarget; ++i) {
2254 auto morphTarget = static_cast<const QSSGRenderMorphTarget *>(modelNode->morphTargets.at(i));
2255 modelNode->morphWeights[i] = morphTarget->weight;
2256 modelNode->morphAttributes[i] = morphTarget->attributes;
2258 modelNode->morphAttributes[i] &= 0x1; // MorphTarget.Position
2260 modelNode->morphAttributes[i] &= 0x3; // MorphTarget.Position | MorphTarget.Normal
2261 }
2262 }
2263
2264 dirtySkeletons.clear();
2265}
2266
2267QSSGNodeIdList QSSGLayerRenderData::filter(const QSSGGlobalRenderNodeData::LayerNodeView &layerNodes,
2268 quint32 layerMask,
2269 quint32 typeMask)
2270{
2271 QSSGNodeIdList res;
2272 for (auto *n : layerNodes) {
2273 // Check mask
2274 if (((quint32(n->type) & typeMask) == typeMask) && n->tag.isSet(layerMask))
2275 res.push_back(QSSGNodeId(reinterpret_cast<quintptr>(n)));
2276 }
2277
2278 return res;
2279}
2280
2281void QSSGLayerRenderData::categorizeAndFilterNodes(const QSSGGlobalRenderNodeData::LayerNodeView &layerNodes,
2282 QSSGLayerRenderData::NodeCollection &nodeCollection,
2283 quint32 layerMask)
2284{
2285 auto &modelsView = nodeCollection.modelsView;
2286 auto &particlesView = nodeCollection.particlesView;
2287 auto &item2DsView = nodeCollection.item2DsView;
2288 auto &camerasView = nodeCollection.camerasView;
2289 auto &lightsView = nodeCollection.lightsView;
2290 auto &reflectionProbesView = nodeCollection.reflectionProbesView;
2291 auto &nonCategorizedView = nodeCollection.nonCategorizedView;
2292
2293 auto &layerNodesCategorized = nodeCollection.layerNodesCategorized;
2294
2295 modelsView.clear();
2296 particlesView.clear();
2297 item2DsView.clear();
2298 camerasView.clear();
2299 lightsView.clear();
2300 reflectionProbesView.clear();
2301 nonCategorizedView.clear();
2302
2303 enum NodeType : size_t { Model = 0, Particles, Item2D, Camera, ImportedCamera, Light, ReflectionProbe, Other, Inactive };
2304 const auto nodeType = [layerMask](QSSGRenderNode *node) -> NodeType {
2305 if (!(node->getGlobalState(QSSGRenderNode::GlobalState::Active)))
2306 return NodeType::Inactive;
2307 if (!(node->tag.isSet(layerMask)))
2308 return NodeType::Other;
2309 switch (node->type) {
2310 case QSSGRenderGraphObject::Type::Model: return NodeType::Model;
2311 case QSSGRenderGraphObject::Type::Particles: return NodeType::Particles;
2312 case QSSGRenderGraphObject::Type::Item2D: return NodeType::Item2D;
2313 case QSSGRenderGraphObject::Type::ReflectionProbe: return NodeType::ReflectionProbe;
2314 default: break;
2315 }
2316
2317 if (QSSGRenderGraphObject::isCamera(node->type)) {
2318 // NOTE: To keep compatibility with old code, we collect shared and non-shared cameras differently,
2319 // so that shared cameras (import scene) are picked after non-shared ones.
2320 const bool isImported = node->getGlobalState(QSSGRenderNode::GlobalState::Imported);
2321 constexpr NodeType cameraTypes[2] { NodeType::Camera, NodeType::ImportedCamera };
2322 return cameraTypes[size_t(isImported)];
2323 }
2324 if (QSSGRenderGraphObject::isLight(node->type))
2325 return NodeType::Light;
2326
2327 return NodeType::Other;
2328 };
2329 // sort nodes by type - We could do this on insert, but it's not given that it would be beneficial.
2330 // Depending on how we want to handle the nodes later it might just not give us anything
2331 // so, keep it simple for now.
2332 // We could also speed up this by having the pointer and the type in the same struct and sort without
2333 // indirection. However, that' slightly less convenient and the idea here is that we don't process
2334 // this list unless things change, which is not something that should happen often if the user is
2335 // concerned about performance, as it means we need to reevaluate the whole scene anyway.
2336 {
2337 // Sort the nodes by type (we copy the pointers to avoid sorting the original list,
2338 // which is stored based on the nodes' order in the world tree).
2339 layerNodesCategorized = { layerNodes.begin(), layerNodes.end() };
2340 // NOTE: Due to the ordering of item2ds, we need to use stable_sort.
2341 std::stable_sort(layerNodesCategorized.begin(), layerNodesCategorized.end(), [nodeType](QSSGRenderNode *a, QSSGRenderNode *b) {
2342 return nodeType(a) < nodeType(b);
2343 });
2344 }
2345
2346 // Group nodes by type inline and keep track of the individual parts using QSSGDataViews
2347 const LayerNodeStatResult stat = statLayerNodes(layerNodesCategorized, layerMask);
2348
2349 // Go through the sorted nodes and create the views
2350 size_t next = 0;
2351
2352 if (stat.modelCount > 0) {
2353 modelsView = QSSGModelsView((QSSGRenderModel **)(layerNodesCategorized.data() + next), stat.modelCount);
2354 next = modelsView.size();
2355 }
2356 if (stat.particlesCount > 0) {
2357 particlesView = QSSGParticlesView((QSSGRenderParticles **)(layerNodesCategorized.data() + next), stat.particlesCount);
2358 next += particlesView.size();
2359 }
2360 if (stat.item2DCount > 0) {
2361 item2DsView = QSSGItem2DsView((QSSGRenderItem2D **)(layerNodesCategorized.data() + next), stat.item2DCount);
2362 next += item2DsView.size();
2363 }
2364 if (stat.cameraCount > 0) {
2365 camerasView = QSSGCamerasView((QSSGRenderCamera **)(layerNodesCategorized.data() + next), stat.cameraCount);
2366 next += camerasView.size();
2367 }
2368 if (stat.lightCount > 0) {
2369 lightsView = QSSGLightsView((QSSGRenderLight **)(layerNodesCategorized.data() + next), stat.lightCount);
2370 next += lightsView.size();
2371 }
2372 if (stat.reflectionProbeCount > 0) {
2373 reflectionProbesView = QSSGReflectionProbesView((QSSGRenderReflectionProbe **)(layerNodesCategorized.data() + next), stat.reflectionProbeCount);
2374 next += reflectionProbesView.size();
2375 }
2376 if (stat.otherCount > 0) {
2377 nonCategorizedView = QSSGNonCategorizedView((QSSGRenderNode **)(layerNodesCategorized.data() + next), stat.otherCount);
2378 next += nonCategorizedView.size();
2379 (void)next;
2380 }
2381}
2382
2383void QSSGLayerRenderData::updateFilteredLayerNodes(quint32 layerMask)
2384{
2385 categorizeAndFilterNodes(layerNodes, nodeCollection, layerMask);
2386
2387 // FIXME: Compatability with old code (Will remove later).
2388 // NOTE: see resetForFrame() as well for extensions usage
2389 renderableModels.clear();
2390 renderableParticles.clear();
2391 renderableModels.reserve(modelsView.size());
2392 renderableParticles.reserve(particlesView.size());
2393
2394 renderableModels = {modelsView.begin(), modelsView.end()};
2395 renderableParticles = {particlesView.begin(), particlesView.end()};
2396}
2397
2398void QSSGLayerRenderData::prepareForRender()
2399{
2400 QSSG_ASSERT_X(layerPrepResult.isNull(), "Prep-result was not reset for render!", layerPrepResult = {});
2401
2402 QRect theViewport(renderer->viewport());
2403
2404 // NOTE: The renderer won't change in practice (after being set the first time), but just update
2405 // it anyways.
2406 frameData.m_ctx = renderer->contextInterface();
2407 frameData.clear();
2408
2409 // Create base pipeline state
2410 ps = {}; // Reset
2411 ps.viewport = { float(theViewport.x()), float(theViewport.y()), float(theViewport.width()), float(theViewport.height()), 0.0f, 1.0f };
2412 if (layer.scissorRect.isValid()) {
2413 ps.flags |= QSSGRhiGraphicsPipelineState::Flag::UsesScissor;
2414 ps.scissor = { layer.scissorRect.x(),
2415 theViewport.height() - (layer.scissorRect.y() + layer.scissorRect.height()),
2416 layer.scissorRect.width(),
2417 layer.scissorRect.height() };
2418 }
2419
2420 ps.depthFunc = QRhiGraphicsPipeline::LessOrEqual;
2421 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, false);
2422
2423 // Enable Wireframe mode
2424 ps.polygonMode = layer.wireframeMode ? QRhiGraphicsPipeline::Line : QRhiGraphicsPipeline::Fill;
2425
2426 bool wasDirty = false;
2427 bool wasDataDirty = false;
2428 wasDirty = layer.isDirty();
2429
2430 const bool shouldDisableInternalPasses = (layer.renderOverrides & size_t(QSSGRenderLayer::RenderOverrides::DisableInternalPasses)) != 0;
2431 wasDirty |= (shouldDisableInternalPasses != disableMainPasses);
2432 disableMainPasses = shouldDisableInternalPasses;
2433
2434 // NOTE: Prep-state is implicitly set to "DataPrep"
2435 layerPrepResult = { theViewport, layer };
2436
2437 // SSAO
2438 const bool SSAOEnabled = layer.ssaoEnabled();
2439 layerPrepResult.flags.setRequiresSsaoPass(SSAOEnabled);
2440 features.set(QSSGShaderFeatures::Feature::Ssao, SSAOEnabled);
2441
2442 // Effects
2443 bool requiresDepthTexture = SSAOEnabled;
2444 bool requiresNormalTexture = false;
2445 bool requiresMotionVectorTexture = false;
2446 for (QSSGRenderEffect *theEffect = layer.firstEffect; theEffect; theEffect = theEffect->m_nextEffect) {
2447 if (theEffect->isDirty()) {
2448 wasDirty = true;
2449 theEffect->clearDirty();
2450 }
2451 if (theEffect->testFlag(QSSGRenderEffect::Flags::UsesDepthTexture))
2452 requiresDepthTexture = true;
2453 if (theEffect->testFlag(QSSGRenderEffect::Flags::UsesNormalTexture))
2454 requiresNormalTexture = true;
2455 if (theEffect->testFlag(QSSGRenderEffect::Flags::UsesMotionVectorTexture))
2456 requiresMotionVectorTexture = true;
2457 }
2458
2459 const auto &rhiCtx = renderer->contextInterface()->rhiContext();
2460 orderIndependentTransparencyEnabled = (layer.oitMethod != QSSGRenderLayer::OITMethod::None);
2461 if (layer.oitMethod == QSSGRenderLayer::OITMethod::WeightedBlended) {
2462 orderIndependentTransparencyEnabled = rhiCtx->rhi()->isFeatureSupported(QRhi::PerRenderTargetBlending);
2463 if (rhiCtx->mainPassSampleCount() > 1)
2464 orderIndependentTransparencyEnabled &= rhiCtx->rhi()->isFeatureSupported(QRhi::TexelFetch) && rhiCtx->rhi()->isFeatureSupported(QRhi::SampleVariables);
2465 if (!orderIndependentTransparencyEnabled && !oitWarningUnsupportedShown) {
2466 qCWarning(lcQuick3DRender) << "WeightedBlended OIT is requested, but it is not supported.";
2467 oitWarningUnsupportedShown = true;
2468 }
2469 } else if (layer.oitMethod == QSSGRenderLayer::OITMethod::LinkedList) {
2470 orderIndependentTransparencyEnabled = rhiCtx->rhi()->isTextureFormatSupported(QRhiTexture::RGBA32UI, QRhiTexture::UsedWithLoadStore);
2471 if (rhiCtx->mainPassSampleCount() > 1)
2472 orderIndependentTransparencyEnabled &= rhiCtx->rhi()->isFeatureSupported(QRhi::SampleVariables);
2473 if (!orderIndependentTransparencyEnabled && !oitWarningUnsupportedShown) {
2474 qCWarning(lcQuick3DRender) << "LinkedList OIT is requested, but it is not supported.";
2475 oitWarningUnsupportedShown = true;
2476 }
2477 }
2478 if (layer.oitMethodDirty) {
2479 oitRenderContext.reset();
2480 for (auto &renderResult : renderResults)
2481 renderResult.reset();
2482 }
2483
2484 layerPrepResult.flags.setRequiresDepthTexture(requiresDepthTexture);
2485
2486 layerPrepResult.flags.setRequiresNormalTexture(requiresNormalTexture);
2487
2488 layerPrepResult.flags.setRequiresMotionVectorPass(requiresMotionVectorTexture);
2489
2490 // Tonemapping. Except when there are effects, then it is up to the
2491 // last pass of the last effect to perform tonemapping.
2492 if (!layer.firstEffect)
2493 QSSGLayerRenderData::setTonemapFeatures(features, layer.tonemapMode);
2494
2495 // We may not be able to have an array of 15 light struct elements in
2496 // the shaders. Switch on the reduced-max-number-of-lights feature
2497 // if necessary. In practice this is relevant with OpenGL ES 3.0 or
2498 // 2.0, because there are still implementations in use that only
2499 // support the spec mandated minimum of 224 vec4s (so 3584 bytes).
2500 if (rhiCtx->rhi()->resourceLimit(QRhi::MaxUniformBufferRange) < REDUCED_MAX_LIGHT_COUNT_THRESHOLD_BYTES) {
2501 features.set(QSSGShaderFeatures::Feature::ReduceMaxNumLights, true);
2502 static bool notified = false;
2503 if (!notified) {
2504 notified = true;
2505 qCDebug(lcQuick3DRender, "Qt Quick 3D maximum number of lights has been reduced from %d to %d due to the graphics driver's limitations",
2506 QSSG_MAX_NUM_LIGHTS, QSSG_REDUCED_MAX_NUM_LIGHTS);
2507 }
2508 }
2509
2510 // IBL source configuration
2511 const auto &lightProbeSettings = layer.lightProbeSettings;
2512 if (layer.lightProbe) {
2513 if (layer.lightProbe->m_format == QSSGRenderTextureFormat::Unknown) {
2514 // Choose on a format that makes sense for a light probe
2515 // At this point it's just a suggestion
2516 if (renderer->contextInterface()->rhiContext()->rhi()->isTextureFormatSupported(QRhiTexture::RGBA16F))
2517 layer.lightProbe->m_format = QSSGRenderTextureFormat::RGBA16F;
2518 else
2519 layer.lightProbe->m_format = QSSGRenderTextureFormat::RGBE8;
2520 }
2521
2522 if (layer.lightProbe->clearDirty())
2523 wasDataDirty = true;
2524
2525 features.set(QSSGShaderFeatures::Feature::LightProbe, true);
2526 features.set(QSSGShaderFeatures::Feature::IblOrientation, !lightProbeSettings.probeOrientation.isIdentity());
2527 if (layer.lightProbe->m_format.format == QSSGRenderTextureFormat::Format::RGBE8)
2528 features.set(QSSGShaderFeatures::Feature::RGBELightProbe, true);
2529
2530 } else if (layer.skyMaterial) {
2531 if (layer.skyMaterial->enableIBL) {
2532 features.set(QSSGShaderFeatures::Feature::LightProbe, true);
2533 features.set(QSSGShaderFeatures::Feature::IblOrientation, !lightProbeSettings.probeOrientation.isIdentity());
2534 }
2535 layerPrepResult.flags.setRequiresSkyMaterialPass(true);
2536 }
2537
2538 const bool forceIblExposureValues = (features.isSet(QSSGShaderFeatures::Feature::LightProbe)
2539 && layer.tonemapMode == QSSGRenderLayer::TonemapMode::Custom);
2540 features.set(QSSGShaderFeatures::Feature::ForceIblExposure, forceIblExposureValues);
2541
2542 frameData.m_ctx->bufferManager()->setLightmapSource(layer.lightmapSource);
2543
2544 // Update the node data version for this layer.
2545 // This version should only change if the world tree was re-indexed.
2546 version = nodeData->version();
2547
2548 // We're using/updating the node data directly.
2549 // NOTE: These are the transforms and opacities for all nodes for all layers/views.
2550 // We'll just use or update the ones the one for this layer.
2551 auto &globalTransforms = nodeData->globalTransforms;
2552 auto &globalOpacities = nodeData->globalOpacities;
2553 auto &instanceTransforms = nodeData->instanceTransforms;
2554
2555 ///////// 2 - START LAYER
2556 QSSGRenderDataHelpers::GlobalStateResultT globalStateResult = QSSGRenderDataHelpers::GlobalStateResult::None;
2557
2558 const bool layerTreeWasDirty = layer.isDirty(QSSGRenderLayer::DirtyFlag::TreeDirty);
2559 layer.clearDirty(QSSGRenderLayer::DirtyFlag::TreeDirty);
2560 if (layerTreeWasDirty) {
2561 wasDataDirty = true;
2562 layerNodes = nodeData->getLayerNodeView(layer);
2563 } else {
2564 for (auto &node : layerNodes)
2565 globalStateResult |= QSSGRenderDataHelpers::updateGlobalNodeState(node, version);
2566
2567 bool transformAndOpacityDirty = false;
2568 for (auto &node : layerNodes)
2569 transformAndOpacityDirty |= QSSGRenderDataHelpers::calcGlobalNodeData<QSSGRenderDataHelpers::Strategy::Update>(node, version, globalTransforms, globalOpacities);
2570
2571 // FIXME: We shouldn't need to re-create all the instance transforms even when instancing isn't used...
2572 if (transformAndOpacityDirty) {
2573 for (const auto &node : layerNodes)
2574 wasDataDirty |= QSSGRenderDataHelpers::calcInstanceTransforms(node, version, globalTransforms, instanceTransforms);
2575 }
2576
2577 wasDataDirty |= transformAndOpacityDirty;
2578 }
2579
2580 // If the viewport visibility changed, we mark the data as dirty to ensure a proper update
2581 // happens. This is important because if there are shared imported nodes between layers,
2582 // they might have been updated by one of the other layers and therefore considered "clean", causing
2583 // things like progressiveAA to not work correctly when toggling the visibility of a layer.
2584 const bool viewportVisibilityDirty = layer.isDirty(QSSGRenderLayer::DirtyFlag::VisibilityDirty);
2585 layer.clearDirty(QSSGRenderLayer::DirtyFlag::VisibilityDirty);
2586 if (viewportVisibilityDirty)
2587 wasDataDirty = true;
2588
2589 // Check if we have an explicit camera!
2590 // NOTE: We only do layering if we have an explicit camera!!!
2591
2592 const bool hasExplicitCamera = (layer.explicitCameras.size() != 0);
2593 bool cameraLayerMaskDirty = false;
2594 quint32 layerMask = QSSGRenderCamera::LayerMaskAll;
2595 if (hasExplicitCamera) {
2596 QSSGRenderCamera *explicitCamera = layer.explicitCameras[0];
2597 // For now we add the reserved bits to the camera layer mask.
2598 // In the future we might want to control which reserved bits are added.
2599 layerMask = explicitCamera->tag.value() | QSSGRenderCamera::ReservedLayerMask;
2600 cameraLayerMaskDirty = explicitCamera->isDirty(QSSGRenderCamera::DirtyFlag::LayerMaskDirty);
2601 explicitCamera->clearDirty(QSSGRenderCamera::DirtyFlag::LayerMaskDirty);
2602 }
2603
2604 const bool restatNodes = (layerTreeWasDirty || (globalStateResult & QSSGRenderDataHelpers::GlobalStateResult::ActiveChanged) || cameraLayerMaskDirty);
2605
2606 if (restatNodes)
2607 updateFilteredLayerNodes(layerMask);
2608
2609 // Cameras
2610 // 1. If there's an explicit camera set and it's active (visible) we'll use that.
2611 // 2. ... if the explicitly set camera is not visible, no further attempts will be done.
2612 // 3. If no explicit camera is set, we'll search and pick the first active camera.
2613 QSSGRenderCamera::Configuration cameraConfig { renderer->dpr(), layer.isSsaaEnabled() ? layer.ssaaMultiplier : 1.0f };
2614 renderedCameras.clear();
2615 if (!layer.explicitCameras.isEmpty()) {
2616 for (QSSGRenderCamera *cam : std::as_const(layer.explicitCameras)) {
2617 // 1.
2618 if (cam->getGlobalState(QSSGRenderCamera::GlobalState::Active)) {
2619 const bool computeFrustumSucceeded = cam->calculateProjection(theViewport, cameraConfig);
2620 if (Q_LIKELY(computeFrustumSucceeded))
2621 renderedCameras.append(cam);
2622 else
2623 qCCritical(INTERNAL_ERROR, "Failed to calculate camera frustum");
2624 }
2625 }
2626 // 2.
2627 } else if (QSSG_GUARD_X(layer.viewCount == 1, "Multiview rendering requires explicit cameras to be set!.")) {
2628 // NOTE: This path can never be hit with multiview, hence the guard.
2629 // (Multiview will always have explicit cameras set.)
2630
2631 // 3.
2632 for (auto iter = camerasView.begin(); renderedCameras.isEmpty() && iter != camerasView.end(); iter++) {
2633 QSSGRenderCamera *theCamera = *iter;
2634 if (theCamera->getGlobalState(QSSGRenderCamera::GlobalState::Active)) {
2635 const bool computeFrustumSucceeded = theCamera->calculateProjection(theViewport, cameraConfig);
2636 if (Q_LIKELY(computeFrustumSucceeded))
2637 renderedCameras.append(theCamera);
2638 else
2639 qCCritical(INTERNAL_ERROR, "Failed to calculate camera frustum");
2640 }
2641 }
2642
2643 // Now check if the camera has a layer mask, if it does we need to filter the nodes again!
2644 // Issue a warning letting the user know that they should set an explicit camera!
2645 // If you are reading this due to seeing the warning: Not requiring an explicit camera was
2646 // a mistake, but we kept it for compatability reasons unfortunately.
2647 if (renderedCameras.size() > 0 && (renderedCameras[0]->layerMask & QSSGRenderCamera::LayerMaskUserAll) != QSSGRenderCamera::LayerMaskUserAll) {
2648 if (!nonExplicitCameraWithLayerMaskWarningShown) {
2649 nonExplicitCameraWithLayerMaskWarningShown = true;
2650 qWarning() << "Scenes with non-explicit cameras with a layer detected!"
2651 " Set the camera explicitly to avoid unnecessary evaluation of scene nodes!";
2652 }
2653
2654 // Now filter the nodes, again.
2655 layerMask = renderedCameras[0]->layerMask;
2656 updateFilteredLayerNodes(layerMask);
2657 }
2658 }
2659
2660 float meshLodThreshold = 1.0f;
2661 if (!renderedCameras.isEmpty())
2662 meshLodThreshold = renderedCameras[0]->levelOfDetailPixelThreshold / theViewport.width();
2663
2664 layer.renderedCamerasMutex.lock();
2665 layer.renderedCameras = renderedCameras;
2666 layer.renderedCamerasMutex.unlock();
2667
2668 // Meshes, materials, MVP, and normal matrices for the models
2669 const QSSGRenderCameraDataList &renderCameraData = getCachedCameraDatas();
2670 modelData->updateModelData(modelsView, renderer, renderCameraData);
2671
2672 // Item2Ds
2673 item2DData->updateItem2DData(item2DsView, renderer, renderCameraData);
2674
2675 // ResourceLoaders
2676 prepareResourceLoaders();
2677
2678 // Skeletons
2679 updateDirtySkeletons(*this, modelsView);
2680
2681 // Lights
2682 int directionalLightsCount = 0;
2683 int positionalLightsCount = 0;
2684 const int maxLightCount = effectiveMaxLightCount(features);
2685 const int maxDirectionalLights = effectiveMaxDirectionalLightCount(features);
2686 QSSGShaderLightList renderableLights;
2687 int shadowMapCount = 0;
2688 bool hasScopedLights = false;
2689
2690 // Determine which lights will actually Render
2691 // Determine how many lights will need shadow maps
2692 // NOTE: This culling is specific to our Forward renderer
2693 {
2694 auto it = std::make_reverse_iterator(lightsView.end());
2695 const auto end = it + qMin(maxLightCount, lightsView.size());
2696 for (; it != end; ++it) {
2697 QSSGRenderLight *renderLight = (*it);
2698 QMatrix4x4 renderLightTransform = getGlobalTransform(*renderLight);
2699 if (renderLight->type == QSSGRenderGraphObject::Type::DirectionalLight)
2700 directionalLightsCount++;
2701 else
2702 positionalLightsCount++;
2703
2704 if (positionalLightsCount > maxLightCount)
2705 continue;
2706 if (directionalLightsCount > maxDirectionalLights)
2707 continue;
2708
2709
2710 hasScopedLights |= (renderLight->m_scope != nullptr);
2711 const bool castShadows = renderLight->m_castShadow && !renderLight->m_fullyBaked;
2712 shadowMapCount += int(castShadows);
2713 const auto &direction = renderLight->getScalingCorrectDirection(renderLightTransform);
2714 renderableLights.push_back(QSSGShaderLight{ renderLight, castShadows, direction });
2715 }
2716 }
2717
2718 const bool showLightCountWarning = !tooManyLightsWarningShown && (positionalLightsCount > maxLightCount);
2719 if (showLightCountWarning) {
2720 qWarning("Too many lights in scene, maximum is %d", maxLightCount);
2721 tooManyLightsWarningShown = true;
2722 }
2723
2724 const bool showDirectionalLightCountWarning = !tooManyDirectionalLightsWarningShown && (directionalLightsCount > maxDirectionalLights);
2725 if (showDirectionalLightCountWarning) {
2726 qWarning("Too many directional lights in scene, maximum is %d", maxDirectionalLights);
2727 tooManyDirectionalLightsWarningShown = true;
2728 }
2729
2730 if (shadowMapCount > 0) { // Setup Shadow Maps Entries for Lights casting shadows
2731 requestShadowMapManager(); // Ensure we have a shadow map manager
2732 layerPrepResult.flags.setRequiresShadowMapPass(true);
2733 // Any light with castShadow=true triggers shadow mapping
2734 // in the generated shaders. The fact that some (or even
2735 // all) objects may opt out from receiving shadows plays no
2736 // role here whatsoever.
2737 features.set(QSSGShaderFeatures::Feature::Ssm, true);
2738 shadowMapManager->addShadowMaps(renderableLights);
2739 } else if (shadowMapManager) {
2740 // No shadows but a shadow manager so clear old resources
2741 shadowMapManager->releaseCachedResources();
2742 }
2743
2744 // Give each renderable a copy of the lights available
2745 // Also setup scoping for scoped lights
2746
2747 QSSG_ASSERT(globalLights.isEmpty(), globalLights.clear());
2748 if (hasScopedLights) { // Filter out scoped lights from the global lights list
2749 for (const auto &shaderLight : std::as_const(renderableLights)) {
2750 if (!shaderLight.light->m_scope)
2751 globalLights.push_back(shaderLight);
2752 }
2753
2754 const auto prepareLightsWithScopedLights = [&renderableLights, this](QVector<QSSGRenderableNodeEntry> &renderableNodes) {
2755 for (qint32 idx = 0, end = renderableNodes.size(); idx < end; ++idx) {
2756 QSSGRenderableNodeEntry &theNodeEntry(renderableNodes[idx]);
2757 QSSGShaderLightList filteredLights;
2758 for (const auto &light : std::as_const(renderableLights)) {
2759 if (light.light->m_scope && !scopeLight(theNodeEntry.node, light.light->m_scope))
2760 continue;
2761 filteredLights.push_back(light);
2762 }
2763
2764 if (filteredLights.isEmpty()) { // Node without scoped lights, just reference the global light list.
2765 theNodeEntry.lights = QSSGDataView(globalLights);
2766 } else {
2767 // This node has scoped lights, i.e., it's lights differ from the global list
2768 // we therefore create a bespoke light list for it. Technically this might be the same for
2769 // more then this one node, but the overhead for tracking that is not worth it.
2770 auto customLightList = RENDER_FRAME_NEW_BUFFER<QSSGShaderLight>(*renderer->contextInterface(), filteredLights.size());
2771 std::copy(filteredLights.cbegin(), filteredLights.cend(), customLightList.begin());
2772 theNodeEntry.lights = customLightList;
2773 }
2774 }
2775 };
2776
2777 prepareLightsWithScopedLights(renderableModels);
2778 prepareLightsWithScopedLights(renderableParticles);
2779 } else { // Just a simple copy
2780 globalLights = renderableLights;
2781 // No scoped lights, all nodes can just reference the global light list.
2782 const auto prepareLights = [this](QVector<QSSGRenderableNodeEntry> &renderableNodes) {
2783 for (qint32 idx = 0, end = renderableNodes.size(); idx < end; ++idx) {
2784 QSSGRenderableNodeEntry &theNodeEntry(renderableNodes[idx]);
2785 theNodeEntry.lights = QSSGDataView(globalLights);
2786 }
2787 };
2788
2789 prepareLights(renderableModels);
2790 prepareLights(renderableParticles);
2791 }
2792
2793 {
2794 // Give user provided passes a chance to modify the renderable data before starting
2795 // Note: All non-active extensions should be filtered out by now
2796 Q_STATIC_ASSERT(USERPASSES == size_t(QSSGRenderLayer::RenderExtensionStage::Count));
2797 for (size_t i = 0; i != size_t(QSSGRenderLayer::RenderExtensionStage::Count); ++i) {
2798 const auto &renderExtensions = layer.renderExtensions[i];
2799 auto &userPass = userPasses[i];
2800 for (auto rit = renderExtensions.crbegin(), rend = renderExtensions.crend(); rit != rend; ++rit) {
2801 if ((*rit)->prepareData(frameData)) {
2802 wasDirty |= true;
2803 userPass.extensions.push_back(*rit);
2804 }
2805 }
2806 }
2807 }
2808
2809 // User render passes
2810 {
2811 const auto &userRenderPassManager = requestUserRenderPassManager();
2812 userRenderPassManager->updateUserPassOrder(layerTreeWasDirty); // NOTE: If the tree was dirty we need to force an update.
2813 userRenderPasses.userPasses = userRenderPassManager->scheduledUserPasses();
2814 }
2815
2816 auto &opaqueObjects = opaqueObjectStore[0];
2817 auto &transparentObjects = transparentObjectStore[0];
2818 auto &screenTextureObjects = screenTextureObjectStore[0];
2819
2820 if (!renderedCameras.isEmpty()) { // NOTE: We shouldn't really get this far without a camera...
2821 wasDirty |= prepareModelsForRender(*renderer->contextInterface(), renderableModels, layerPrepResult.flags, renderedCameras, getCachedCameraDatas(), modelContexts, opaqueObjects, transparentObjects, screenTextureObjects, meshLodThreshold);
2822 if (particlesEnabled) {
2823 const auto &cameraDatas = getCachedCameraDatas();
2824 wasDirty |= prepareParticlesForRender(renderableParticles, cameraDatas[0], layerPrepResult.flags);
2825 }
2826 // If there's item2Ds we set wasDirty.
2827 wasDirty |= (item2DsView.size() != 0);
2828 }
2829 if (orderIndependentTransparencyEnabled) {
2830 // OIT blending mode must be SourceOver and have transparent objects
2831 if (transparentObjects.size() > 0 && !layerPrepResult.flags.hasCustomBlendMode()) {
2832 if (layer.oitMethod == QSSGRenderLayer::OITMethod::WeightedBlended) {
2833 if (rhiCtx->mainPassSampleCount() > 1)
2834 layerPrepResult.flags.setRequiresDepthTextureMS(true);
2835 else
2836 layerPrepResult.flags.setRequiresDepthTexture(true);
2837 }
2838 } else {
2839 orderIndependentTransparencyEnabled = false;
2840 if (!oitWarningInvalidBlendModeShown) {
2841 qCWarning(lcQuick3DRender) << "Order Independent Transparency requested, but disabled due to invalid blend modes.";
2842 qCWarning(lcQuick3DRender) << "Use SourceOver blend mode for Order Independent Transparency.";
2843 oitWarningInvalidBlendModeShown = true;
2844 }
2845 }
2846 }
2847 layer.oitMethodDirty = false;
2848
2849 prepareReflectionProbesForRender();
2850
2851 wasDirty = wasDirty || wasDataDirty;
2852 layerPrepResult.flags.setWasDirty(wasDirty);
2853 layerPrepResult.flags.setLayerDataDirty(wasDataDirty);
2854
2855 //
2856 const bool animating = wasDirty;
2857 if (animating)
2858 layer.progAAPassIndex = 0;
2859
2860 const bool progressiveAA = layer.isProgressiveAAEnabled() && !animating;
2861 layer.progressiveAAIsActive = progressiveAA;
2862 const bool temporalAA = layer.isTemporalAAEnabled() && !progressiveAA;
2863
2864 layer.temporalAAIsActive = temporalAA;
2865
2866 QVector2D vertexOffsetsAA;
2867
2868 if (progressiveAA && layer.progAAPassIndex > 0 && layer.progAAPassIndex < quint32(layer.antialiasingQuality)) {
2869 int idx = layer.progAAPassIndex - 1;
2870 vertexOffsetsAA = s_ProgressiveAAVertexOffsets[idx] / QVector2D{ float(theViewport.width()/2.0), float(theViewport.height()/2.0) };
2871 }
2872
2873 if (temporalAA) {
2874 if (layer.temporalAAMode == QSSGRenderLayer::TAAMode::MotionVector && layer.tempAAPassIndex > 0) {
2875 if (layer.tempAAPassIndex >= quint32(MAX_AA_LEVELS) + 1)
2876 layer.tempAAPassIndex = 1;
2877 int idx = layer.tempAAPassIndex - 1;
2878 vertexOffsetsAA = s_ProgressiveAAVertexOffsets[idx] / QVector2D{ float(theViewport.width()/2.0), float(theViewport.height()/2.0) };
2879 layer.currentAndLastJitter = QVector4D(vertexOffsetsAA, layer.currentAndLastJitter.x(), layer.currentAndLastJitter.y());
2880 layerPrepResult.flags.setRequiresMotionVectorPass(true);
2881 layerPrepResult.flags.setRequiresDepthTexture(true);
2882 }else {
2883 const int t = 1 - 2 * (layer.tempAAPassIndex % 2);
2884 const float f = t * layer.temporalAAStrength;
2885 vertexOffsetsAA = { f / float(theViewport.width()/2.0), f / float(theViewport.height()/2.0) };
2886 }
2887 }
2888
2889 if (!renderedCameras.isEmpty()) {
2890 if (temporalAA || progressiveAA /*&& !vertexOffsetsAA.isNull()*/) {
2891 QMatrix4x4 offsetProjection = renderedCameras[0]->projection;
2892 QMatrix4x4 invProjection = renderedCameras[0]->projection.inverted();
2893 if (renderedCameras[0]->type == QSSGRenderCamera::Type::OrthographicCamera) {
2894 offsetProjection(0, 3) -= vertexOffsetsAA.x();
2895 offsetProjection(1, 3) -= vertexOffsetsAA.y();
2896 } else if (renderedCameras[0]->type == QSSGRenderCamera::Type::PerspectiveCamera) {
2897 offsetProjection(0, 2) += vertexOffsetsAA.x();
2898 offsetProjection(1, 2) += vertexOffsetsAA.y();
2899 }
2900 for (auto &modelContext : std::as_const(modelContexts)) {
2901 for (int mvpIdx = 0; mvpIdx < renderedCameras.count(); ++mvpIdx)
2902 modelContext->modelViewProjections[mvpIdx] = offsetProjection * invProjection * modelContext->modelViewProjections[mvpIdx];
2903 }
2904 }
2905 }
2906
2907 const bool hasItem2Ds = (item2DsView.size() > 0);
2908 const bool layerEnableDepthTest = layer.layerFlags.testFlag(QSSGRenderLayer::LayerFlag::EnableDepthTest);
2909 const bool layerEnabledDepthPrePass = layer.layerFlags.testFlag(QSSGRenderLayer::LayerFlag::EnableDepthPrePass);
2910 const bool depthTestEnableDefault = layerEnableDepthTest && (!opaqueObjects.isEmpty() || depthPrepassObjectsState || hasDepthWriteObjects);
2911 const bool zPrePassForced = (depthPrepassObjectsState != 0);
2912 zPrePassActive = zPrePassForced || (layerEnabledDepthPrePass && layerEnableDepthTest && (hasDepthWriteObjects || hasItem2Ds));
2913 const bool depthWriteEnableDefault = depthTestEnableDefault && (!layerEnabledDepthPrePass || !zPrePassActive);
2914
2915 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, depthTestEnableDefault);
2916 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, depthWriteEnableDefault);
2917
2918
2919 // All data prep is done, from now on the layer content shouldn't change.
2920 layerPrepResult.setState(QSSGLayerRenderPreparationResult::State::Done);
2921
2922 // Prepare passes
2923 QSSG_ASSERT(activePasses.isEmpty(), activePasses.clear());
2924 // If needed, generate a depth texture with the opaque objects. This
2925 // and normal and the SSAO texture must come first since other passes may want to
2926 // expose these textures to their shaders.
2927 if (layerPrepResult.flags.requiresDepthTexture())
2928 activePasses.push_back(&depthMapPass);
2929 if (layerPrepResult.flags.requiresDepthTextureMS())
2930 activePasses.push_back(&depthMapPassMS);
2931
2932 if (layerPrepResult.flags.requiresNormalTexture())
2933 activePasses.push_back(&normalPass);
2934
2935 // Screen space ambient occlusion. Relies on the depth texture and generates an AO map.
2936 if (layerPrepResult.flags.requiresSsaoPass())
2937 activePasses.push_back(&ssaoMapPass);
2938
2939 // Shadows. Generates a 2D or cube shadow map. (opaque + pre-pass transparent objects)
2940 if (layerPrepResult.flags.requiresShadowMapPass())
2941 activePasses.push_back(&shadowMapPass);
2942
2943 if (zPrePassActive && !disableMainPasses)
2944 activePasses.push_back(&zPrePassPass);
2945
2946 // Screen texture with opaque objects.
2947 if (layerPrepResult.flags.requiresScreenTexture())
2948 activePasses.push_back(&screenMapPass);
2949
2950 // Layer has SkyMaterial.
2951 if (layerPrepResult.flags.requiresSkyMaterialPass())
2952 activePasses.push_back(&skyMaterialPass);
2953
2954 // Reflection pass
2955 if (reflectionProbesView.size() != 0)
2956 activePasses.push_back(&reflectionMapPass);
2957
2958 auto &textureExtensionPass = userPasses[size_t(QSSGRenderLayer::RenderExtensionStage::TextureProviders)];
2959 if (textureExtensionPass.hasData())
2960 activePasses.push_back(&textureExtensionPass);
2961
2962 auto &underlayPass = userPasses[size_t(QSSGRenderLayer::RenderExtensionStage::Underlay)];
2963 if (underlayPass.hasData())
2964 activePasses.push_back(&underlayPass);
2965
2966 // Generated User render passes (QML)
2967 if (userRenderPasses.hasData())
2968 activePasses.push_back(&userRenderPasses);
2969
2970 const bool hasOpaqueObjects = (opaqueObjects.size() > 0);
2971
2972 if (hasOpaqueObjects && !disableMainPasses)
2973 activePasses.push_back(&opaquePass);
2974
2975 // MotionVector Pass
2976 if (layerPrepResult.flags.requiresMotionVectorPass())
2977 activePasses.push_back(&motionVectorMapPass);
2978
2979 // NOTE: When the a screen texture is used, the skybox pass will be called twice. First from
2980 // the screen texture pass and later as part of the normal run through the list.
2981 if (renderer->contextInterface()->rhiContext()->rhi()->isFeatureSupported(QRhi::TexelFetch)) {
2982 if (layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap && layer.skyBoxCubeMap && !disableMainPasses)
2983 activePasses.push_back(&skyboxCubeMapPass);
2984 else if (layer.background == QSSGRenderLayer::Background::SkyBox && layer.lightProbe && !disableMainPasses)
2985 activePasses.push_back(&skyboxPass);
2986 else if (layer.background == QSSGRenderLayer::Background::SkyMaterial && layer.skyMaterial && !disableMainPasses) {
2987 if (layer.skyMaterial->skyboxMode != QSSGRenderSkyMaterial::SkyboxMode::Cubemap)
2988 activePasses.push_back(&skyMaterialBackgroundPass);
2989 else
2990 activePasses.push_back(&skyboxPass);
2991 }
2992 }
2993
2994 if (hasItem2Ds && !disableMainPasses)
2995 activePasses.push_back(&item2DPass);
2996
2997 if (layerPrepResult.flags.requiresScreenTexture())
2998 activePasses.push_back(&reflectionPass);
2999
3000 // Note: Transparent pass includeds opaque objects when layerEnableDepthTest is false.
3001 if ((transparentObjects.size() > 0 || (!layerEnableDepthTest && hasOpaqueObjects)) && !disableMainPasses) {
3002 if (orderIndependentTransparencyEnabled) {
3003 activePasses.push_back(&oitRenderPass);
3004 activePasses.push_back(&oitCompositePass);
3005 oitRenderPass.setMethod(layer.oitMethod);
3006 oitCompositePass.setMethod(layer.oitMethod);
3007 } else {
3008 activePasses.push_back(&transparentPass);
3009 }
3010 }
3011
3012 auto &overlayPass = userPasses[size_t(QSSGRenderLayer::RenderExtensionStage::Overlay)];
3013 if (overlayPass.hasData())
3014 activePasses.push_back(&overlayPass);
3015
3016 if (layer.gridEnabled)
3017 activePasses.push_back(&infiniteGridPass);
3018
3019 if (const auto &dbgDrawSystem = renderer->contextInterface()->debugDrawSystem(); dbgDrawSystem && dbgDrawSystem->isEnabled() && !disableMainPasses)
3020 activePasses.push_back(&debugDrawPass);
3021}
3022
3023template<typename T>
3024static void clearTable(std::vector<T> &entry)
3025{
3026 for (auto &e : entry)
3027 e.clear();
3028}
3029void QSSGLayerRenderData::resolveLayerIblTexture()
3030{
3031 if (layer.skyMaterial) {
3032 // In the ScreenSpace skybox modes the visible background is evaluated directly on
3033 // screen (SkyMaterialBackgroundPass), so the cube is only produced when something
3034 // actually consumes it: image-based lighting, reflection probes, the cube-sampling
3035 // background (Cubemap mode), or a screen-read texture (SkyboxPass draws the cube
3036 // into it for e.g. refraction).
3037 const bool needCube = layer.skyMaterial->enableIBL
3038 || layer.skyMaterial->skyboxMode == QSSGRenderSkyMaterial::SkyboxMode::Cubemap
3039 || reflectionProbesView.size() != 0 || layerPrepResult.flags.requiresScreenTexture();
3040 if (needCube)
3041 skyMaterialTexture = requestSkyMaterialManager()->resolve(layer.skyMaterial);
3042 else
3043 skyMaterialTexture = {};
3044 }
3045}
3046
3047void QSSGLayerRenderData::resetForFrame()
3048{
3049 for (const auto &pass : activePasses)
3050 pass->resetForFrame();
3051 activePasses.clear();
3052 bakedLightingModels.clear();
3053 layerPrepResult = {};
3054 renderedCameraData.reset();
3055 renderedItem2Ds.clear();
3056 renderedBakedLightingModels.clear();
3057 lightmapTextures.clear();
3058 bonemapTextures.clear();
3059 globalLights.clear();
3060 modelContexts.clear();
3061 features = QSSGShaderFeatures();
3062 skyMaterialTexture = { };
3063 hasDepthWriteObjects = false;
3064 depthPrepassObjectsState = { DepthPrepassObjectStateT(DepthPrepassObject::None) };
3065 zPrePassActive = false;
3066 savedRenderState.reset();
3067
3068 clearTable(renderableModelStore);
3069 clearTable(modelContextStore);
3070 clearTable(renderableObjectStore);
3071 clearTable(opaqueObjectStore);
3072 clearTable(transparentObjectStore);
3073 clearTable(screenTextureObjectStore);
3074
3075 clearTable(sortedOpaqueObjectCache);
3076 clearTable(sortedTransparentObjectCache);
3077 clearTable(sortedScreenTextureObjectCache);
3078 clearTable(sortedOpaqueDepthPrepassCache);
3079 clearTable(sortedDepthWriteCache);
3080
3081 // Until we have a better solution for extensions...
3082 if (renderablesModifiedByExtension) {
3083 renderableModels.clear();
3084 renderableParticles.clear();
3085 renderableModels.reserve(modelsView.size());
3086 renderableParticles.reserve(particlesView.size());
3087
3088 renderableModels = {modelsView.begin(), modelsView.end()};
3089 renderableParticles = {particlesView.begin(), particlesView.end()};
3090
3091 renderablesModifiedByExtension = false;
3092 }
3093
3094 if (userRenderPassManager)
3095 userRenderPassManager->resetForFrame();
3096}
3097
3098QSSGLayerRenderPreparationResult::QSSGLayerRenderPreparationResult(const QRectF &inViewport, QSSGRenderLayer &inLayer)
3099 : layer(&inLayer)
3100 , m_state(State::DataPrep)
3101{
3102 viewport = inViewport;
3103}
3104
3106{
3107 return viewport.height() >= 2.0f && viewport.width() >= 2.0f;
3108}
3109
3111{
3112 const auto size = viewport.size().toSize();
3113 return QSize(QSSGRendererUtil::nextMultipleOf4(size.width()), QSSGRendererUtil::nextMultipleOf4(size.height()));
3114}
3115
3116QSSGLayerRenderData::QSSGLayerRenderData(QSSGRenderLayer &inLayer, QSSGRenderer &inRenderer)
3117 : layer(inLayer)
3118 , renderer(&inRenderer)
3119 , orderIndependentTransparencyEnabled(false)
3120 , particlesEnabled(checkParticleSupport(inRenderer.contextInterface()->rhi()))
3121{
3122 depthMapPassMS.setMultisamplingEnabled(true);
3123 Q_ASSERT(extContexts.size() == 1);
3124
3125 // Set-up the world root node and create the data store for the models.
3126 auto *root = layer.rootNode;
3127 nodeData = root->globalNodeData();
3128 modelData = std::make_unique<QSSGRenderModelData>(nodeData);
3129 item2DData = std::make_unique<QSSGRenderItem2DData>(nodeData);
3130
3131 inRenderer.registerItem2DData(*item2DData);
3132}
3133
3134QSSGLayerRenderData::~QSSGLayerRenderData()
3135{
3136 for (auto &renderResult : renderResults)
3137 renderResult.reset();
3138 oitRenderContext.reset();
3139
3140 if (userRenderPassManager)
3141 userRenderPassManager->releaseAll();
3142
3143 renderer->unregisterItem2DData(*item2DData);
3144}
3145
3146// Instance buffers are passed around as bytes: convert to entries in one place,
3147// clamped to what the buffer can actually hold.
3148static QSpan<const QSSGRenderInstanceTableEntry> instanceEntries(const void *data, qsizetype byteSize, int count)
3149{
3150 const qsizetype available = byteSize / qsizetype(sizeof(QSSGRenderInstanceTableEntry));
3151 return { reinterpret_cast<const QSSGRenderInstanceTableEntry *>(data),
3152 qBound(qsizetype(0), qsizetype(count), available) };
3153}
3154
3156{
3157 return { reinterpret_cast<QSSGRenderInstanceTableEntry *>(data.data()),
3158 data.size() / qsizetype(sizeof(QSSGRenderInstanceTableEntry)) };
3159}
3160
3161static void sortInstances(QSpan<QSSGRenderInstanceTableEntry> sortedInstances, QList<QSSGRhiSortData> &sortData,
3162 QSpan<const QSSGRenderInstanceTableEntry> instances, const QVector3D &cameraDirection)
3163{
3164 const int count = int(qMin(instances.size(), sortedInstances.size()));
3165 sortData.resize(count);
3166 // create sort data
3167 for (int i = 0; i < count; ++i) {
3168 const QSSGRenderInstanceTableEntry &instance = instances[i];
3169 const QVector3D pos = QVector3D(instance.row0.w(), instance.row1.w(), instance.row2.w());
3170 sortData[i] = {QVector3D::dotProduct(pos, cameraDirection), i};
3171 }
3172
3173 // sort
3174 std::sort(sortData.begin(), sortData.end(), [](const QSSGRhiSortData &a, const QSSGRhiSortData &b){
3175 return a.d > b.d;
3176 });
3177
3178 // copy instances
3179 for (int i = 0; i < count; ++i)
3180 sortedInstances[i] = instances[sortData[i].indexOrOffset];
3181}
3182
3183static int cullLodInstances(QSpan<QSSGRenderInstanceTableEntry> lodInstances,
3184 QSpan<const QSSGRenderInstanceTableEntry> instances,
3185 const QVector3D &cameraPosition, float minThreshold, float maxThreshold)
3186{
3187 const qsizetype count = qMin(instances.size(), lodInstances.size());
3188
3189 int realSize = 0;
3190 for (qsizetype i = 0; i < count; ++i) {
3191 const QSSGRenderInstanceTableEntry &instance = instances[i];
3192 const float x = cameraPosition.x() - instance.row0.w();
3193 const float y = cameraPosition.y() - instance.row1.w();
3194 const float z = cameraPosition.z() - instance.row2.w();
3195 const float distanceSq = x * x + y * y + z * z;
3196 if (distanceSq >= minThreshold * minThreshold && (maxThreshold < 0 || distanceSq < maxThreshold * maxThreshold))
3197 lodInstances[realSize++] = instance;
3198 }
3199
3200 return realSize;
3201}
3202
3203bool QSSGLayerRenderData::prepareInstancing(QSSGRhiContext *rhiCtx,
3204 QSSGSubsetRenderable *renderable,
3205 const QVector3D &cameraDirection,
3206 const QVector3D &cameraPosition,
3207 float minThreshold,
3208 float maxThreshold)
3209{
3210 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
3211 auto &modelContext = renderable->modelContext;
3212 auto &instanceBuffer = renderable->instanceBuffer; // intentional ref2ptr
3213 const QMatrix4x4 &renderableGlobalTransform = renderable->modelContext.globalTransform;
3214 if (!modelContext.model.instancing() || instanceBuffer)
3215 return instanceBuffer;
3216 auto *table = modelContext.model.instanceTable;
3217 bool usesLod = minThreshold >= 0 || maxThreshold >= 0;
3218 QSSGRhiInstanceBufferData &instanceData(usesLod ? rhiCtxD->instanceBufferData(&modelContext.model) : rhiCtxD->instanceBufferData(table));
3219 quint32 instanceBufferSize = table->dataSize();
3220 // Create or resize the instance buffer ### if (instanceData.owned)
3221 bool sortingChanged = table->isDepthSortingEnabled() != instanceData.sorting;
3222 bool cameraDirectionChanged = !qFuzzyCompare(instanceData.sortedCameraDirection, cameraDirection);
3223 bool cameraPositionChanged = !qFuzzyCompare(instanceData.cameraPosition, cameraPosition);
3224 bool updateInstanceBuffer = table->serial() != instanceData.serial || sortingChanged || (cameraDirectionChanged && table->isDepthSortingEnabled());
3225 bool instanceBufferSizeChanged = instanceData.buffer && instanceBufferSize != instanceData.buffer->size();
3226 bool updateForLod = (cameraPositionChanged || instanceBufferSizeChanged) && usesLod;
3227 if (sortingChanged && !table->isDepthSortingEnabled()) {
3228 instanceData.sortedData.clear();
3229 instanceData.sortData.clear();
3230 instanceData.sortedCameraDirection = {};
3231 }
3232 instanceData.sorting = table->isDepthSortingEnabled();
3233 if (instanceData.buffer && instanceData.buffer->size() < instanceBufferSize) {
3234 updateInstanceBuffer = true;
3235 // qDebug() << "Resizing instance buffer";
3236 instanceData.buffer->setSize(instanceBufferSize);
3237 instanceData.buffer->create();
3238 }
3239 if (!instanceData.buffer) {
3240 // qDebug() << "Creating instance buffer";
3241 updateInstanceBuffer = true;
3242 instanceData.buffer = rhiCtx->rhi()->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, instanceBufferSize);
3243 instanceData.buffer->create();
3244 }
3245 if (updateInstanceBuffer || updateForLod) {
3246 Q_ASSERT(table->stride() == sizeof(QSSGRenderInstanceTableEntry));
3247 const void *data = nullptr;
3248 if (table->isDepthSortingEnabled()) {
3249 if (updateInstanceBuffer) {
3250 QMatrix4x4 invGlobalTransform = renderableGlobalTransform.inverted();
3251 instanceData.sortedData.resize(table->dataSize());
3252 sortInstances(instanceEntries(instanceData.sortedData),
3253 instanceData.sortData,
3254 instanceEntries(table->constData(), table->dataSize(), table->count()),
3255 invGlobalTransform.map(cameraDirection).normalized());
3256 }
3257 data = instanceData.sortedData.constData();
3258 instanceData.sortedCameraDirection = cameraDirection;
3259 } else {
3260 data = table->constData();
3261 }
3262 if (data) {
3263 if (updateForLod) {
3264 // Either the depth sorted copy or the table itself, both with count() entries.
3265 const auto instances = table->isDepthSortingEnabled()
3266 ? instanceEntries(instanceData.sortedData.constData(), instanceData.sortedData.size(), table->count())
3267 : instanceEntries(table->constData(), table->dataSize(), table->count());
3268 instanceData.lodData.resize(table->dataSize());
3269 modelContext.model.instanceLodCount = cullLodInstances(instanceEntries(instanceData.lodData),
3270 instances,
3271 cameraPosition,
3272 minThreshold,
3273 maxThreshold);
3274 data = instanceData.lodData.constData();
3275
3276 // Force clear the buffer to upload empty instance data.
3277 // If this step is skipped, the updateDynamicBuffer function will load data for all instances.
3278 if (!modelContext.model.instanceLodCount)
3279 instanceData.lodData.clear();
3280
3281 instanceBufferSize = modelContext.model.instanceLodCount * sizeof(QSSGRenderInstanceTableEntry);
3282 }
3283
3284 QRhiResourceUpdateBatch *rub = rhiCtx->rhi()->nextResourceUpdateBatch();
3285 rub->updateDynamicBuffer(instanceData.buffer, 0, instanceBufferSize, data);
3286 rhiCtx->commandBuffer()->resourceUpdate(rub);
3287 //qDebug() << "****** UPDATING INST BUFFER. Size" << instanceBufferSize;
3288 } else {
3289 qWarning() << "NO DATA IN INSTANCE TABLE";
3290 }
3291 instanceData.serial = table->serial();
3292 instanceData.cameraPosition = cameraPosition;
3293 }
3294 instanceBuffer = instanceData.buffer;
3295 return instanceBuffer;
3296}
3297
3298QSSGFrameData &QSSGLayerRenderData::getFrameData()
3299{
3300 return frameData;
3301}
3302
3303void QSSGLayerRenderData::initializeLightmapBaking(QSSGLightmapBaker::Context &ctx)
3304{
3305 ctx.callbacks.modelsToBake = [this]() { return getSortedBakedLightingModels(); };
3306
3307 lightmapBaker = std::make_unique<QSSGLightmapBaker>(ctx);
3308}
3309
3310void QSSGLayerRenderData::maybeProcessLightmapBaking()
3311{
3312 if (lightmapBaker) {
3313 const QSSGLightmapBaker::Status status = lightmapBaker->process();
3314 if (status == QSSGLightmapBaker::Status::Finished)
3315 lightmapBaker.reset();
3316 }
3317}
3318
3319QSSGRenderGraphObject *QSSGLayerRenderData::getCamera(QSSGCameraId id) const
3320{
3321 QSSGRenderGraphObject *ret = nullptr;
3322 if (auto res = reinterpret_cast<QSSGRenderGraphObject *>(id))
3323 ret = res;
3324
3325 return ret;
3326}
3327
3328QSSGRenderCameraData QSSGLayerRenderData::getCameraRenderData(const QSSGRenderCamera *camera_)
3329{
3330 if ((!camera_ || camera_ == renderedCameras[0]) && renderedCameraData.has_value())
3331 return renderedCameraData.value()[0];
3332 if (camera_)
3333 return getCameraDataImpl(camera_);
3334 return {};
3335}
3336
3337QSSGRenderCameraData QSSGLayerRenderData::getCameraRenderData(const QSSGRenderCamera *camera_) const
3338{
3339 if ((!camera_ || camera_ == renderedCameras[0]) && renderedCameraData.has_value())
3340 return renderedCameraData.value()[0];
3341 if (camera_)
3342 return getCameraDataImpl(camera_);
3343 return {};
3344}
3345
3346QSSGRenderContextInterface *QSSGLayerRenderData::contextInterface() const
3347{
3348 return renderer ? renderer->contextInterface() : nullptr;
3349}
3350
3351QSSGLayerRenderData::GlobalRenderProperties QSSGLayerRenderData::globalRenderProperties(const QSSGRenderContextInterface &ctx)
3352{
3353 GlobalRenderProperties props {};
3354 if (const auto &rhiCtx = ctx.rhiContext(); rhiCtx->isValid()) {
3355 QRhi *rhi = rhiCtx->rhi();
3356 props.isYUpInFramebuffer = rhi->isYUpInFramebuffer();
3357 props.isYUpInNDC = rhi->isYUpInNDC();
3358 props.isClipDepthZeroToOne = rhi->isClipDepthZeroToOne();
3359 }
3360
3361 return props;
3362}
3363
3364const QSSGRenderShadowMapPtr &QSSGLayerRenderData::requestShadowMapManager()
3365{
3366 if (!shadowMapManager && QSSG_GUARD(renderer && renderer->contextInterface()))
3367 shadowMapManager.reset(new QSSGRenderShadowMap(*renderer->contextInterface()));
3368 return shadowMapManager;
3369}
3370
3371const QSSGRenderReflectionMapPtr &QSSGLayerRenderData::requestReflectionMapManager()
3372{
3373 if (!reflectionMapManager && QSSG_GUARD(renderer && renderer->contextInterface()))
3374 reflectionMapManager.reset(new QSSGRenderReflectionMap(*renderer->contextInterface()));
3375 return reflectionMapManager;
3376}
3377
3378const QSSGRenderSkyMaterialManagerPtr &QSSGLayerRenderData::requestSkyMaterialManager()
3379{
3380 if (!skyMaterialManager && QSSG_GUARD(renderer && renderer->contextInterface()))
3381 skyMaterialManager.reset(new QSSGRenderSkyMaterialManager(*renderer->contextInterface()));
3382 return skyMaterialManager;
3383}
3384
3385const QSSGUserRenderPassManagerPtr &QSSGLayerRenderData::requestUserRenderPassManager()
3386{
3387 if (!userRenderPassManager) {
3388 userRenderPassManager = QSSGUserRenderPassManager::create();
3389 renderer->contextInterface()->bufferManager()->registerUserRenderPassManager(userRenderPassManager);
3390 }
3391 return userRenderPassManager;
3392}
3393
3394const QSSGRenderMotionVectorMapPtr &QSSGLayerRenderData::requestMotionVectorMapManager()
3395{
3396 if (!motionVectorMapManager && QSSG_GUARD(renderer && renderer->contextInterface()))
3397 motionVectorMapManager.reset(new QSSGRenderMotionVectorMap(*renderer->contextInterface()));
3398 return motionVectorMapManager;
3399}
3400
3401QT_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)