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