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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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