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