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

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