1 | // Copyright (C) 2008-2012 NVIDIA Corporation. |
2 | // Copyright (C) 2019 The Qt Company Ltd. |
3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
4 | |
5 | #ifndef QSSG_RENDER_IMPL_RENDERABLE_OBJECTS_H |
6 | #define QSSG_RENDER_IMPL_RENDERABLE_OBJECTS_H |
7 | |
8 | // |
9 | // W A R N I N G |
10 | // ------------- |
11 | // |
12 | // This file is not part of the Qt API. It exists purely as an |
13 | // implementation detail. This header file may change from version to |
14 | // version without notice, or even be removed. |
15 | // |
16 | // We mean it. |
17 | // |
18 | |
19 | #include <QtQuick3DRuntimeRender/private/qssgrendermodel_p.h> |
20 | #include <QtQuick3DRuntimeRender/private/qssgrenderdefaultmaterial_p.h> |
21 | #include <QtQuick3DRuntimeRender/private/qssgrendercustommaterial_p.h> |
22 | #include <QtQuick3DRuntimeRender/private/qssgrenderparticles_p.h> |
23 | #include <QtQuick3DRuntimeRender/private/qssgrendermesh_p.h> |
24 | #include <QtQuick3DRuntimeRender/private/qssgrendershaderkeys_p.h> |
25 | #include <QtQuick3DRuntimeRender/private/qssgrendershadercache_p.h> |
26 | #include <QtQuick3DRuntimeRender/private/qssgrenderableimage_p.h> |
27 | #include <QtQuick3DRuntimeRender/private/qssgrenderlight_p.h> |
28 | #include <QtQuick3DRuntimeRender/private/qssgrenderreflectionprobe_p.h> |
29 | #include <QtQuick3DRuntimeRender/private/qssgrenderclippingfrustum_p.h> |
30 | |
31 | #include <QtQuick3DUtils/private/qssginvasivelinkedlist_p.h> |
32 | |
33 | QT_BEGIN_NAMESPACE |
34 | |
35 | enum class QSSGRenderableObjectFlag : quint32 |
36 | { |
37 | HasTransparency = 1 << 0, |
38 | CompletelyTransparent = 1 << 1, |
39 | Dirty = 1 << 2, |
40 | CastsShadows = 1 << 3, |
41 | ReceivesShadows = 1 << 4, |
42 | HasAttributePosition = 1 << 5, |
43 | HasAttributeNormal = 1 << 6, |
44 | HasAttributeTexCoord0 = 1 << 7, |
45 | HasAttributeTexCoord1 = 1 << 8, |
46 | HasAttributeTangent = 1 << 9, |
47 | HasAttributeBinormal = 1 << 10, |
48 | HasAttributeColor = 1 << 11, |
49 | HasAttributeJointAndWeight = 1 << 12, |
50 | IsPointsTopology = 1 << 13, |
51 | // The number of target models' attributes are too many |
52 | // to store in a renderable flag. |
53 | // They will be recorded in shaderKey. |
54 | HasAttributeMorphTarget = 1 << 14, |
55 | RequiresScreenTexture = 1 << 15, |
56 | ReceivesReflections = 1 << 16, |
57 | UsedInBakedLighting = 1 << 17, |
58 | RendersWithLightmap = 1 << 18, |
59 | HasAttributeTexCoordLightmap = 1 << 19, |
60 | CastsReflections = 1 << 20 |
61 | }; |
62 | |
63 | struct QSSGRenderableObjectFlags : public QFlags<QSSGRenderableObjectFlag> |
64 | { |
65 | void setHasTransparency(bool inHasTransparency) |
66 | { |
67 | setFlag(flag: QSSGRenderableObjectFlag::HasTransparency, on: inHasTransparency); |
68 | } |
69 | bool hasTransparency() const { return this->operator&(other: QSSGRenderableObjectFlag::HasTransparency); } |
70 | void setCompletelyTransparent(bool inTransparent) |
71 | { |
72 | setFlag(flag: QSSGRenderableObjectFlag::CompletelyTransparent, on: inTransparent); |
73 | } |
74 | bool isCompletelyTransparent() const |
75 | { |
76 | return this->operator&(other: QSSGRenderableObjectFlag::CompletelyTransparent); |
77 | } |
78 | void setDirty(bool inDirty) { setFlag(flag: QSSGRenderableObjectFlag::Dirty, on: inDirty); } |
79 | bool isDirty() const { return this->operator&(other: QSSGRenderableObjectFlag::Dirty); } |
80 | |
81 | void setCastsShadows(bool inCastsShadows) { setFlag(flag: QSSGRenderableObjectFlag::CastsShadows, on: inCastsShadows); } |
82 | bool castsShadows() const { return this->operator&(other: QSSGRenderableObjectFlag::CastsShadows); } |
83 | |
84 | void setReceivesShadows(bool inReceivesShadows) { setFlag(flag: QSSGRenderableObjectFlag::ReceivesShadows, on: inReceivesShadows); } |
85 | bool receivesShadows() const { return this->operator&(other: QSSGRenderableObjectFlag::ReceivesShadows); } |
86 | |
87 | void setReceivesReflections(bool inReceivesReflections) { setFlag(flag: QSSGRenderableObjectFlag::ReceivesReflections, on: inReceivesReflections); } |
88 | bool receivesReflections() const { return this->operator&(other: QSSGRenderableObjectFlag::ReceivesReflections); } |
89 | |
90 | void setCastsReflections(bool inCastsReflections) { setFlag(flag: QSSGRenderableObjectFlag::CastsReflections, on: inCastsReflections); } |
91 | bool castsReflections() const { return this->operator&(other: QSSGRenderableObjectFlag::CastsReflections); } |
92 | |
93 | void setUsedInBakedLighting(bool inUsedInBakedLighting) { setFlag(flag: QSSGRenderableObjectFlag::UsedInBakedLighting, on: inUsedInBakedLighting); } |
94 | bool usedInBakedLighting() const { return this->operator&(other: QSSGRenderableObjectFlag::UsedInBakedLighting); } |
95 | |
96 | void setRendersWithLightmap(bool inRendersWithLightmap) { setFlag(flag: QSSGRenderableObjectFlag::RendersWithLightmap, on: inRendersWithLightmap); } |
97 | bool rendersWithLightmap() const { return this->operator&(other: QSSGRenderableObjectFlag::RendersWithLightmap); } |
98 | |
99 | void setHasAttributePosition(bool b) { setFlag(flag: QSSGRenderableObjectFlag::HasAttributePosition, on: b); } |
100 | bool hasAttributePosition() const { return this->operator&(other: QSSGRenderableObjectFlag::HasAttributePosition); } |
101 | |
102 | void setHasAttributeNormal(bool b) { setFlag(flag: QSSGRenderableObjectFlag::HasAttributeNormal, on: b); } |
103 | bool hasAttributeNormal() const { return this->operator&(other: QSSGRenderableObjectFlag::HasAttributeNormal); } |
104 | |
105 | void setHasAttributeTexCoord0(bool b) { setFlag(flag: QSSGRenderableObjectFlag::HasAttributeTexCoord0, on: b); } |
106 | bool hasAttributeTexCoord0() const { return this->operator&(other: QSSGRenderableObjectFlag::HasAttributeTexCoord0); } |
107 | |
108 | void setHasAttributeTexCoord1(bool b) { setFlag(flag: QSSGRenderableObjectFlag::HasAttributeTexCoord1, on: b); } |
109 | bool hasAttributeTexCoord1() const { return this->operator&(other: QSSGRenderableObjectFlag::HasAttributeTexCoord1); } |
110 | |
111 | void setHasAttributeTexCoordLightmap(bool b) { setFlag(flag: QSSGRenderableObjectFlag::HasAttributeTexCoordLightmap, on: b); } |
112 | bool hasAttributeTexCoordLightmap() const { return this->operator&(other: QSSGRenderableObjectFlag::HasAttributeTexCoordLightmap); } |
113 | |
114 | void setHasAttributeTangent(bool b) { setFlag(flag: QSSGRenderableObjectFlag::HasAttributeTangent, on: b); } |
115 | bool hasAttributeTangent() const { return this->operator&(other: QSSGRenderableObjectFlag::HasAttributeTangent); } |
116 | |
117 | void setHasAttributeBinormal(bool b) { setFlag(flag: QSSGRenderableObjectFlag::HasAttributeBinormal, on: b); } |
118 | bool hasAttributeBinormal() const { return this->operator&(other: QSSGRenderableObjectFlag::HasAttributeBinormal); } |
119 | |
120 | void setHasAttributeColor(bool b) { setFlag(flag: QSSGRenderableObjectFlag::HasAttributeColor, on: b); } |
121 | bool hasAttributeColor() const { return this->operator&(other: QSSGRenderableObjectFlag::HasAttributeColor); } |
122 | |
123 | void setHasAttributeJointAndWeight(bool b) { setFlag(flag: QSSGRenderableObjectFlag::HasAttributeJointAndWeight, on: b); |
124 | } |
125 | bool hasAttributeJointAndWeight() const { return this->operator&(other: QSSGRenderableObjectFlag::HasAttributeJointAndWeight); } |
126 | |
127 | void setHasAttributeMorphTarget(bool b) { setFlag(flag: QSSGRenderableObjectFlag::HasAttributeMorphTarget, on: b); |
128 | } |
129 | bool hasAttributeMorphTarget() const { return this->operator&(other: QSSGRenderableObjectFlag::HasAttributeMorphTarget); } |
130 | |
131 | void setPointsTopology(bool v) |
132 | { |
133 | setFlag(flag: QSSGRenderableObjectFlag::IsPointsTopology, on: v); |
134 | } |
135 | bool isPointsTopology() const |
136 | { |
137 | return this->operator&(other: QSSGRenderableObjectFlag::IsPointsTopology); |
138 | } |
139 | void setRequiresScreenTexture(bool v) |
140 | { |
141 | setFlag(flag: QSSGRenderableObjectFlag::RequiresScreenTexture, on: v); |
142 | } |
143 | bool requiresScreenTexture() const { |
144 | return this->operator&(other: QSSGRenderableObjectFlag::RequiresScreenTexture); |
145 | } |
146 | }; |
147 | |
148 | struct QSSGShaderLight |
149 | { |
150 | QSSGRenderLight *light = nullptr; |
151 | bool shadows = false; |
152 | QVector3D direction; |
153 | |
154 | inline bool operator < (const QSSGShaderLight &o) const |
155 | { |
156 | // sort by light type |
157 | if (light->type < o.light->type) |
158 | return true; |
159 | // then shadow lights first |
160 | if (shadows > o.shadows) |
161 | return true; |
162 | return false; |
163 | } |
164 | }; |
165 | |
166 | struct QSSGShaderReflectionProbe |
167 | { |
168 | QVector3D probeCubeMapCenter; |
169 | QVector3D probeBoxMax; |
170 | QVector3D probeBoxMin; |
171 | bool enabled = false; |
172 | int parallaxCorrection = 0; |
173 | }; |
174 | |
175 | // Having this as a QVLA is beneficial mainly because QVector would need to |
176 | // detach somewhere in QSSGLayerRenderPreparationData::prepareForRender so the |
177 | // implicit sharing's benefits do not outweigh the cost of allocations in this case. |
178 | typedef QVarLengthArray<QSSGShaderLight, 16> QSSGShaderLightList; |
179 | using QSSGShaderLightListView = QSSGDataView<QSSGShaderLight>; |
180 | |
181 | struct QSSGRenderableObject; |
182 | |
183 | struct QSSGRenderableNodeEntry |
184 | { |
185 | enum Overridden : quint16 |
186 | { |
187 | Original = 0, |
188 | Disabled = 0x1, |
189 | GlobalTransform = 0x2, |
190 | Materials = 0x4, |
191 | GlobalOpacity = 0x5 |
192 | }; |
193 | |
194 | QSSGRenderNode *node = nullptr; |
195 | // TODO: We should have an index here for look-up and store the data in a table, |
196 | // er already have the index from when we collect the nodes. We might cull some items at a later |
197 | // stage but that should be fine. The sort data can be just a float and the index to this... |
198 | mutable QMatrix4x4 globalTransform; |
199 | mutable QSSGRenderMesh *mesh = nullptr; |
200 | mutable QVector<QSSGRenderGraphObject *> materials; |
201 | mutable QSSGShaderLightListView lights; |
202 | mutable float globalOpacity { 1.0f }; |
203 | mutable quint16 overridden { Original }; |
204 | |
205 | bool isNull() const { return (node == nullptr); } |
206 | QSSGRenderableNodeEntry() = default; |
207 | QSSGRenderableNodeEntry(QSSGRenderNode &inNode) : node(&inNode) {} |
208 | }; |
209 | |
210 | // Used for sorting |
211 | struct QSSGRenderableObjectHandle |
212 | { |
213 | QSSGRenderableObjectHandle() = default; |
214 | QSSGRenderableObjectHandle(QSSGRenderableObject *o, float camDistSq) |
215 | : obj(o) |
216 | , cameraDistanceSq(camDistSq) |
217 | {} |
218 | QSSGRenderableObject *obj = nullptr; |
219 | float cameraDistanceSq = 0.0f; |
220 | }; |
221 | Q_DECLARE_TYPEINFO(QSSGRenderableObjectHandle, Q_PRIMITIVE_TYPE); |
222 | |
223 | using QSSGRenderableObjectList = QVector<QSSGRenderableObjectHandle>; |
224 | |
225 | struct QSSGRenderableObject |
226 | { |
227 | enum class Type : quint8 |
228 | { |
229 | DefaultMaterialMeshSubset, |
230 | CustomMaterialMeshSubset, |
231 | Particles |
232 | }; |
233 | |
234 | // Variables used for picking |
235 | const QMatrix4x4 &globalTransform; |
236 | const QSSGBounds3 &bounds; |
237 | QSSGBounds3 globalBounds; |
238 | |
239 | QSSGRenderableObjectFlags renderableFlags; |
240 | // For rough sorting for transparency and for depth |
241 | QVector3D worldCenterPoint; |
242 | float depthBiasSq; // Squared as our sorting is based on the square distance! |
243 | float camdistSq = 0.0f; |
244 | QSSGDepthDrawMode depthWriteMode = QSSGDepthDrawMode::OpaqueOnly; |
245 | const Type type; |
246 | float instancingLodMin = -1; |
247 | float instancingLodMax = -1; |
248 | |
249 | QSSGRenderableObject(Type ty, |
250 | QSSGRenderableObjectFlags inFlags, |
251 | const QVector3D &inWorldCenterPt, |
252 | const QMatrix4x4 &inGlobalTransform, |
253 | const QSSGBounds3 &inBounds, |
254 | float inDepthBias, |
255 | float inMinThreshold = -1, |
256 | float inMaxThreshold = -1) |
257 | |
258 | : globalTransform(inGlobalTransform) |
259 | , bounds(inBounds) |
260 | , globalBounds(inBounds) |
261 | , renderableFlags(inFlags) |
262 | , worldCenterPoint(inWorldCenterPt) |
263 | , depthBiasSq(inDepthBias) |
264 | , type(ty) |
265 | , instancingLodMin(inMinThreshold) |
266 | , instancingLodMax(inMaxThreshold) |
267 | { |
268 | } |
269 | }; |
270 | |
271 | Q_STATIC_ASSERT(std::is_trivially_destructible<QSSGRenderableObject>::value); |
272 | |
273 | struct QSSGRenderCameraData |
274 | { |
275 | QMatrix4x4 viewProjection; |
276 | std::optional<QSSGClippingFrustum> clippingFrustum; |
277 | QVector3D direction { 0.0f, 0.0f, -1.0f }; |
278 | QVector3D position; |
279 | }; |
280 | |
281 | using QSSGRenderCameraList = QVarLengthArray<QSSGRenderCamera *, 2>; |
282 | using QSSGRenderCameraDataList = QVarLengthArray<QSSGRenderCameraData, 2>; |
283 | using QSSGRenderMvpArray = std::array<QMatrix4x4, 2>; // cannot be dynamic due to QSSGModelContext, must stick with 2 for now |
284 | |
285 | struct QSSGSubsetRenderable; |
286 | |
287 | // Different subsets from the same model will get the same |
288 | // model context so we can generate the MVP and normal matrix once |
289 | // and only once per subset. |
290 | struct QSSGModelContext |
291 | { |
292 | const QSSGRenderModel &model; |
293 | QSSGRenderMvpArray modelViewProjections; |
294 | QMatrix3x3 normalMatrix; |
295 | |
296 | QSSGModelContext(const QSSGRenderModel &inModel, |
297 | const QMatrix4x4 &globalTransform, |
298 | const QSSGRenderCameraDataList &allCameraData) |
299 | : model(inModel) |
300 | { |
301 | Q_ASSERT_X(allCameraData.size() <= qsizetype(modelViewProjections.size()), __FUNCTION__, "QSSGModelContext has no space for all MVPs" ); |
302 | int mvpCount = 0; |
303 | // For skinning, node's global transformation will be ignored and |
304 | // an identity matrix will be used for the normalMatrix |
305 | if (model.usesBoneTexture()) { |
306 | for (const QSSGRenderCameraData &cameraData : allCameraData) { |
307 | modelViewProjections[mvpCount++] = cameraData.viewProjection; |
308 | normalMatrix = QMatrix3x3(); |
309 | } |
310 | } else { |
311 | for (const QSSGRenderCameraData &cameraData : allCameraData) |
312 | QSSGRenderNode::calculateMVPAndNormalMatrix(globalTransfor: globalTransform, inViewProjection: cameraData.viewProjection, outMVP&: modelViewProjections[mvpCount++], outNormalMatrix&: normalMatrix); |
313 | } |
314 | } |
315 | |
316 | QSSGDataRef<QSSGSubsetRenderable> subsets; |
317 | }; |
318 | |
319 | Q_STATIC_ASSERT(std::is_trivially_destructible<QSSGModelContext>::value); |
320 | |
321 | class QSSGRenderer; |
322 | class QSSGLayerRenderData; |
323 | struct QSSGShadowMapEntry; |
324 | |
325 | struct Q_QUICK3DRUNTIMERENDER_EXPORT QSSGSubsetRenderable : public QSSGRenderableObject |
326 | { |
327 | int reflectionProbeIndex = -1; |
328 | float distanceFromReflectionProbe; |
329 | quint32 subsetLevelOfDetail = 0; |
330 | QSSGShaderReflectionProbe reflectionProbe; |
331 | QSSGRenderer *renderer = nullptr; |
332 | const QSSGModelContext &modelContext; |
333 | const QSSGRenderSubset ⊂ |
334 | QRhiBuffer *instanceBuffer = nullptr; |
335 | float opacity; |
336 | const QSSGRenderGraphObject &material; |
337 | QSSGRenderableImage *firstImage; |
338 | QSSGShaderDefaultMaterialKey shaderDescription; |
339 | const QSSGShaderLightListView &lights; |
340 | |
341 | struct { |
342 | // Transient (due to the subsetRenderable being allocated using a |
343 | // per-frame allocator on every frame), not owned refs from the |
344 | // rhi-prepare step, used by the rhi-render step. |
345 | struct { |
346 | QRhiGraphicsPipeline *pipeline = nullptr; |
347 | QRhiShaderResourceBindings *srb = nullptr; |
348 | } mainPass; |
349 | struct { |
350 | QRhiGraphicsPipeline *pipeline = nullptr; |
351 | QRhiShaderResourceBindings *srb = nullptr; |
352 | } depthPrePass; |
353 | struct { |
354 | QRhiGraphicsPipeline *pipeline = nullptr; |
355 | QRhiShaderResourceBindings *srb[6] = {}; |
356 | } shadowPass; |
357 | struct { |
358 | QRhiGraphicsPipeline *pipeline = nullptr; |
359 | QRhiShaderResourceBindings *srb[6] = {}; |
360 | } reflectionPass; |
361 | } rhiRenderData; |
362 | |
363 | QSSGSubsetRenderable(Type type, |
364 | QSSGRenderableObjectFlags inFlags, |
365 | const QVector3D &inWorldCenterPt, |
366 | QSSGRenderer *rendr, |
367 | const QSSGRenderSubset &inSubset, |
368 | const QSSGModelContext &inModelContext, |
369 | float inOpacity, |
370 | quint32 inSubsetLevelOfDetail, |
371 | const QSSGRenderGraphObject &mat, |
372 | QSSGRenderableImage *inFirstImage, |
373 | QSSGShaderDefaultMaterialKey inShaderKey, |
374 | const QSSGShaderLightListView &inLights); |
375 | |
376 | [[nodiscard]] const QSSGRenderGraphObject &getMaterial() const { return material; } |
377 | }; |
378 | |
379 | Q_STATIC_ASSERT(std::is_trivially_destructible<QSSGSubsetRenderable>::value); |
380 | |
381 | /** |
382 | * A renderable that corresponds to a particles. |
383 | */ |
384 | struct Q_QUICK3DRUNTIMERENDER_EXPORT QSSGParticlesRenderable : public QSSGRenderableObject |
385 | { |
386 | QSSGRenderer *renderer = nullptr; |
387 | const QSSGRenderParticles &particles; |
388 | QSSGRenderableImage *firstImage; |
389 | QSSGRenderableImage *colorTable; |
390 | const QSSGShaderLightListView &lights; |
391 | float opacity; |
392 | |
393 | struct { |
394 | // Transient (due to the subsetRenderable being allocated using a |
395 | // per-frame allocator on every frame), not owned refs from the |
396 | // rhi-prepare step, used by the rhi-render step. |
397 | struct { |
398 | QRhiGraphicsPipeline *pipeline = nullptr; |
399 | QRhiShaderResourceBindings *srb = nullptr; |
400 | } mainPass; |
401 | struct { |
402 | QRhiGraphicsPipeline *pipeline = nullptr; |
403 | QRhiShaderResourceBindings *srb = nullptr; |
404 | } depthPrePass; |
405 | struct { |
406 | QRhiGraphicsPipeline *pipeline = nullptr; |
407 | QRhiShaderResourceBindings *srb[6] = {}; |
408 | } shadowPass; |
409 | struct { |
410 | QRhiGraphicsPipeline *pipeline = nullptr; |
411 | QRhiShaderResourceBindings *srb[6] = {}; |
412 | } reflectionPass; |
413 | } rhiRenderData; |
414 | |
415 | QSSGParticlesRenderable(QSSGRenderableObjectFlags inFlags, |
416 | const QVector3D &inWorldCenterPt, |
417 | QSSGRenderer *rendr, |
418 | const QSSGRenderParticles &inParticles, |
419 | QSSGRenderableImage *inFirstImage, |
420 | QSSGRenderableImage *inColorTable, |
421 | const QSSGShaderLightListView &inLights, |
422 | float inOpacity); |
423 | }; |
424 | |
425 | Q_STATIC_ASSERT(std::is_trivially_destructible<QSSGParticlesRenderable>::value); |
426 | |
427 | QT_END_NAMESPACE |
428 | |
429 | #endif |
430 | |