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

source code of qtquick3d/src/runtimerender/rendererimpl/qssglayerrenderdata.cpp