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_SHADER_KEY_H
6#define QSSG_RENDER_SHADER_KEY_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 <QtQuick3DUtils/private/qssgdataref_p.h>
20#include <QtQuick3DUtils/private/qssgrenderbasetypes_p.h>
21#include <QtQuick3DRuntimeRender/private/qssgrenderdefaultmaterial_p.h>
22#include <QtQuick3DRuntimeRender/private/qssgrhicontext_p.h>
23
24QT_BEGIN_NAMESPACE
25// We have an ever expanding set of properties we like to hash into one or more 32 bit
26// quantities.
27// Furthermore we would like this set of properties to be convertable to string
28// So the shader cache file itself is somewhat human readable/diagnosable.
29// To do this we create a set of objects that act as properties to the master shader key.
30// These objects are tallied in order to figure out their actual offset into the shader key's
31// data store. They are also run through in order to create the string shader cache key.
32
33struct QSSGShaderKeyPropertyBase
34{
35 QByteArrayView name;
36 quint32 offset;
37 explicit constexpr QSSGShaderKeyPropertyBase(const char *inName = "") : name(inName), offset(0) {}
38 quint32 getOffset() const { return offset; }
39 void setOffset(quint32 of) { offset = of; }
40
41 template<quint32 TBitWidth>
42 quint32 getMaskTemplate() const
43 {
44 quint32 bit = offset % 32;
45 quint32 startValue = (1 << TBitWidth) - 1;
46 quint32 mask = startValue << bit;
47 return mask;
48 }
49
50 quint32 getIdx() const { return offset / 32; }
51
52protected:
53 void internalToString(QByteArray &ioStr, const QByteArrayView &inBuffer) const
54 {
55 ioStr.append(a: name);
56 ioStr.append(c: '=');
57 ioStr.append(a: inBuffer);
58 }
59
60 static void internalToString(QByteArray &ioStr, const QByteArrayView &name, bool inValue)
61 {
62 if (inValue) {
63 ioStr.append(a: name);
64 ioStr.append(c: '=');
65 ioStr.append(a: inValue ? QByteArrayView("true") : QByteArrayView("false"));
66 }
67 }
68 static bool getBoolValue(const QByteArray& str, const QByteArrayView &name)
69 {
70 const int index = str.indexOf(bv: name);
71 if (index < 0)
72 return false;
73 const qsizetype nameLen = name.size();
74 if (str[index + nameLen] != '=')
75 return false;
76 if (str.mid(index: index + nameLen + 1, len: 4) == QByteArrayView("true"))
77 return true;
78 return false;
79 }
80};
81
82struct QSSGShaderKeyBoolean : public QSSGShaderKeyPropertyBase
83{
84 enum {
85 BitWidth = 1,
86 };
87
88 explicit constexpr QSSGShaderKeyBoolean(const char *inName = "") : QSSGShaderKeyPropertyBase(inName) {}
89
90 quint32 getMask() const { return getMaskTemplate<BitWidth>(); }
91 void setValue(QSSGDataRef<quint32> inDataStore, bool inValue) const
92 {
93 const qint32 idx = qint32(getIdx());
94 Q_ASSERT(idx >= 0 && idx <= INT32_MAX);
95 Q_ASSERT(inDataStore.size() > idx);
96 quint32 mask = getMask();
97 quint32 &target = inDataStore[idx];
98 if (inValue) {
99 target = target | mask;
100 } else {
101 mask = ~mask;
102 target = target & mask;
103 }
104 }
105
106 bool getValue(QSSGDataView<quint32> inDataStore) const
107 {
108 quint32 idx = getIdx();
109 quint32 mask = getMask();
110 const quint32 &target = inDataStore[idx];
111 return (target & mask) ? true : false;
112 }
113
114 void toString(QByteArray &ioStr, QSSGDataView<quint32> inKeySet) const
115 {
116 bool isHigh = getValue(inDataStore: inKeySet);
117 internalToString(ioStr, name, inValue: isHigh);
118 }
119 void fromString(const QByteArray &ioStr, QSSGDataRef<quint32> inKeySet)
120 {
121 setValue(inDataStore: inKeySet, inValue: getBoolValue(str: ioStr, name));
122 }
123};
124
125template<quint32 TBitWidth>
126struct QSSGShaderKeyUnsigned : public QSSGShaderKeyPropertyBase
127{
128 enum {
129 BitWidth = TBitWidth,
130 };
131 explicit constexpr QSSGShaderKeyUnsigned(const char *inName = "") : QSSGShaderKeyPropertyBase(inName) {}
132 quint32 getMask() const { return getMaskTemplate<BitWidth>(); }
133 void setValue(QSSGDataRef<quint32> inDataStore, quint32 inValue) const
134 {
135 quint32 startValue = (1 << TBitWidth) - 1;
136 // Ensure inValue is within range of bit width.
137 inValue = inValue & startValue;
138 quint32 bit = offset % 32;
139 quint32 mask = getMask();
140 quint32 idx = getIdx();
141 inValue = inValue << bit;
142 quint32 &target = inDataStore[idx];
143 // Get rid of existing value
144 quint32 inverseMask = ~mask;
145 target = target & inverseMask;
146 target = target | inValue;
147 }
148
149 quint32 getValue(QSSGDataView<quint32> inDataStore) const
150 {
151 quint32 idx = getIdx();
152 quint32 bit = offset % 32;
153 quint32 mask = getMask();
154 const quint32 &target = inDataStore[idx];
155
156 quint32 retval = target & mask;
157 retval = retval >> bit;
158 return retval;
159 }
160
161 void toString(QByteArray &ioStr, QSSGDataView<quint32> inKeySet) const
162 {
163 quint32 value = getValue(inDataStore: inKeySet);
164 char buf[64];
165 memset(s: buf, c: 0, n: sizeof (buf));
166 toStr(item: value, buffer: toDataRef(type: buf, count: 64));
167 internalToString(ioStr, buf);
168 }
169
170 void fromString(const QByteArray &ioStr, QSSGDataRef<quint32> inKeySet)
171 {
172 const qsizetype nameLen = name.size();
173 const qsizetype strOffset = ioStr.indexOf(name);
174 if (strOffset >= 0) {
175 /* The key is stored as name=val */
176 if (ioStr[strOffset + nameLen] != '=')
177 return;
178 const QByteArray s = ioStr.right(n: ioStr.size() - strOffset - nameLen - 1);
179 int i = 0;
180 while (QChar(QLatin1Char(s[i])).isDigit())
181 i++;
182 const quint32 value = s.left(n: i).toInt();
183 setValue(inDataStore: inKeySet, inValue: value);
184 }
185 }
186
187private:
188 static quint32 toStr(quint32 item, QSSGDataRef<char> buffer)
189 {
190 // hope the buffer is big enough...
191 return static_cast<quint32>(::snprintf(s: buffer.begin(), maxlen: buffer.size(), format: "%u", item));
192 }
193};
194
195struct QSSGShaderKeyTextureChannel : public QSSGShaderKeyUnsigned<2>
196{
197 enum TexturChannelBits {
198 R = 0,
199 G = 1,
200 B = 2,
201 A = 3,
202 };
203 explicit QSSGShaderKeyTextureChannel(const char *inName = "") : QSSGShaderKeyUnsigned<2>(inName) {}
204
205 TexturChannelBits getTextureChannel(QSSGDataView<quint32> inKeySet) const
206 {
207 return TexturChannelBits(getValue(inDataStore: inKeySet));
208 }
209
210 void setTextureChannel(TexturChannelBits channel, QSSGDataRef<quint32> inKeySet)
211 {
212 setValue(inDataStore: inKeySet, inValue: quint32(channel));
213 }
214 static constexpr char textureChannelToChar[4] = {
215 'R',
216 'G',
217 'B',
218 'A'
219 };
220 void toString(QByteArray &ioStr, QSSGDataView<quint32> inKeySet) const
221 {
222 ioStr.append(a: name);
223 ioStr.append(c: '=');
224 ioStr.append(c: textureChannelToChar[getTextureChannel(inKeySet)]);
225 }
226 void fromString(const QByteArray &ioStr, QSSGDataRef<quint32> inKeySet)
227 {
228 const qsizetype nameLen = name.size();
229 const qsizetype strOffset = ioStr.indexOf(bv: name);
230 if (strOffset >= 0) {
231 /* The key is stored as name=ch */
232 if (ioStr[strOffset + nameLen] != '=')
233 return;
234 const char ch = ioStr[strOffset + nameLen + 1];
235 if (ch == 'R')
236 setValue(inDataStore: inKeySet, inValue: TexturChannelBits::R);
237 else if (ch == 'G')
238 setValue(inDataStore: inKeySet, inValue: TexturChannelBits::G);
239 else if (ch == 'B')
240 setValue(inDataStore: inKeySet, inValue: TexturChannelBits::B);
241 else if (ch == 'A')
242 setValue(inDataStore: inKeySet, inValue: TexturChannelBits::A);
243 }
244 }
245};
246
247struct QSSGShaderKeyImageMap : public QSSGShaderKeyUnsigned<6>
248{
249 enum ImageMapBits {
250 Enabled = 1 << 0,
251 EnvMap = 1 << 1,
252 LightProbe = 1 << 2,
253 Identity = 1 << 3,
254 UsesUV1 = 1 << 4,
255 Linear = 1 << 5
256 };
257
258 explicit QSSGShaderKeyImageMap(const char *inName = "") : QSSGShaderKeyUnsigned<6>(inName) {}
259
260 bool getBitValue(ImageMapBits imageBit, QSSGDataView<quint32> inKeySet) const
261 {
262 return (getValue(inDataStore: inKeySet) & imageBit) ? true : false;
263 }
264
265 void setBitValue(ImageMapBits imageBit, bool inValue, QSSGDataRef<quint32> inKeySet)
266 {
267 quint32 theValue = getValue(inDataStore: inKeySet);
268 quint32 mask = imageBit;
269 if (inValue) {
270 theValue = theValue | mask;
271 } else {
272 mask = ~mask;
273 theValue = theValue & mask;
274 }
275 setValue(inDataStore: inKeySet, inValue: theValue);
276 }
277
278 bool isEnabled(QSSGDataView<quint32> inKeySet) const { return getBitValue(imageBit: Enabled, inKeySet); }
279 void setEnabled(QSSGDataRef<quint32> inKeySet, bool val) { setBitValue(imageBit: Enabled, inValue: val, inKeySet); }
280
281 bool isEnvMap(QSSGDataView<quint32> inKeySet) const { return getBitValue(imageBit: EnvMap, inKeySet); }
282 void setEnvMap(QSSGDataRef<quint32> inKeySet, bool val) { setBitValue(imageBit: EnvMap, inValue: val, inKeySet); }
283
284 bool isLightProbe(QSSGDataView<quint32> inKeySet) const { return getBitValue(imageBit: LightProbe, inKeySet); }
285 void setLightProbe(QSSGDataRef<quint32> inKeySet, bool val) { setBitValue(imageBit: LightProbe, inValue: val, inKeySet); }
286
287 bool isIdentityTransform(QSSGDataView<quint32> inKeySet) const { return getBitValue(imageBit: Identity, inKeySet); }
288 void setIdentityTransform(QSSGDataRef<quint32> inKeySet, bool val) { setBitValue(imageBit: Identity, inValue: val, inKeySet); }
289
290 bool isUsingUV1(QSSGDataView<quint32> inKeySet) const { return getBitValue(imageBit: UsesUV1, inKeySet); }
291 void setUsesUV1(QSSGDataRef<quint32> inKeySet, bool val) { setBitValue(imageBit: UsesUV1, inValue: val, inKeySet); }
292
293 bool isLinear(QSSGDataView<quint32> inKeySet) const { return getBitValue(imageBit: Linear, inKeySet); }
294 void setLinear(QSSGDataRef<quint32> inKeySet, bool val) { setBitValue(imageBit: Linear, inValue: val, inKeySet); }
295
296 void toString(QByteArray &ioStr, QSSGDataView<quint32> inKeySet) const
297 {
298 ioStr.append(a: name);
299 ioStr.append(a: QByteArrayView("={"));
300 internalToString(ioStr, name: QByteArrayView("enabled"), inValue: isEnabled(inKeySet));
301 ioStr.append(c: ';');
302 internalToString(ioStr, name: QByteArrayView("envMap"), inValue: isEnvMap(inKeySet));
303 ioStr.append(c: ';');
304 internalToString(ioStr, name: QByteArrayView("lightProbe"), inValue: isLightProbe(inKeySet));
305 ioStr.append(c: ';');
306 internalToString(ioStr, name: QByteArrayView("identity"), inValue: isIdentityTransform(inKeySet));
307 ioStr.append(c: ';');
308 internalToString(ioStr, name: QByteArrayView("usesUV1"), inValue: isUsingUV1(inKeySet));
309 ioStr.append(c: ';');
310 internalToString(ioStr, name: QByteArrayView("linear"), inValue: isLinear(inKeySet));
311 ioStr.append(c: '}');
312 }
313};
314
315struct QSSGShaderKeySpecularModel : QSSGShaderKeyUnsigned<2>
316{
317 explicit QSSGShaderKeySpecularModel(const char *inName = "") : QSSGShaderKeyUnsigned<2>(inName) {}
318
319 void setSpecularModel(QSSGDataRef<quint32> inKeySet, QSSGRenderDefaultMaterial::MaterialSpecularModel inModel)
320 {
321 setValue(inDataStore: inKeySet, inValue: quint32(inModel));
322 }
323
324 QSSGRenderDefaultMaterial::MaterialSpecularModel getSpecularModel(QSSGDataView<quint32> inKeySet) const
325 {
326 return static_cast<QSSGRenderDefaultMaterial::MaterialSpecularModel>(getValue(inDataStore: inKeySet));
327 }
328
329 void toString(QByteArray &ioStr, QSSGDataView<quint32> inKeySet) const
330 {
331 ioStr.append(a: name);
332 ioStr.append(c: '=');
333 switch (getSpecularModel(inKeySet)) {
334 case QSSGRenderDefaultMaterial::MaterialSpecularModel::KGGX:
335 ioStr.append(a: QByteArrayView("KGGX"));
336 break;
337 case QSSGRenderDefaultMaterial::MaterialSpecularModel::Default:
338 ioStr.append(a: QByteArrayView("Default"));
339 break;
340 }
341 }
342 void fromString(const QByteArray &ioStr, QSSGDataRef<quint32> inKeySet)
343 {
344 const qsizetype nameLen = name.size();
345 const int strOffset = ioStr.indexOf(bv: name);
346 if (strOffset >= 0) {
347 /* The key is stored as name=specularMode; */
348 if (ioStr[strOffset + nameLen] != '=')
349 return;
350 const int codeOffsetBegin = strOffset + nameLen + 1;
351 int codeOffset = 0;
352 while (ioStr[codeOffsetBegin + codeOffset] != ';')
353 codeOffset++;
354 const QByteArray val = ioStr.mid(index: codeOffsetBegin, len: codeOffset);
355 if (val == QByteArrayView("KGGX"))
356 setSpecularModel(inKeySet, inModel: QSSGRenderDefaultMaterial::MaterialSpecularModel::KGGX);
357 if (val == QByteArrayView("Default"))
358 setSpecularModel(inKeySet, inModel: QSSGRenderDefaultMaterial::MaterialSpecularModel::Default);
359 }
360 }
361};
362
363struct QSSGShaderKeyAlphaMode : QSSGShaderKeyUnsigned<2>
364{
365 explicit QSSGShaderKeyAlphaMode(const char *inName = "") : QSSGShaderKeyUnsigned<2>(inName) {}
366
367 void setAlphaMode(QSSGDataRef<quint32> inKeySet, QSSGRenderDefaultMaterial::MaterialAlphaMode inMode)
368 {
369 setValue(inDataStore: inKeySet, inValue: quint32(inMode));
370 }
371
372 QSSGRenderDefaultMaterial::MaterialAlphaMode getAlphaMode(QSSGDataView<quint32> inKeySet) const
373 {
374 return static_cast<QSSGRenderDefaultMaterial::MaterialAlphaMode>(getValue(inDataStore: inKeySet));
375 }
376
377 void toString(QByteArray &ioStr, QSSGDataView<quint32> inKeySet) const
378 {
379 ioStr.append(a: name);
380 ioStr.append(c: '=');
381 switch (getAlphaMode(inKeySet)) {
382 case QSSGRenderDefaultMaterial::MaterialAlphaMode::Default:
383 ioStr.append(a: QByteArrayView("Default"));
384 break;
385 case QSSGRenderDefaultMaterial::MaterialAlphaMode::Mask:
386 ioStr.append(a: QByteArrayView("Mask"));
387 break;
388 case QSSGRenderDefaultMaterial::MaterialAlphaMode::Blend:
389 ioStr.append(a: QByteArrayView("Blend"));
390 break;
391 case QSSGRenderDefaultMaterial::MaterialAlphaMode::Opaque:
392 ioStr.append(a: QByteArrayView("Opaque"));
393 break;
394 }
395 }
396 void fromString(const QByteArray &ioStr, QSSGDataRef<quint32> inKeySet)
397 {
398 const qsizetype nameLen = name.size();
399 const qsizetype strOffset = ioStr.indexOf(bv: name);
400 if (strOffset >= 0) {
401 /* The key is stored as name=alphaMode; */
402 if (ioStr[strOffset + nameLen] != '=')
403 return;
404 const int codeOffsetBegin = strOffset + nameLen + 1;
405 int codeOffset = 0;
406 while (ioStr[codeOffsetBegin + codeOffset] != ';')
407 codeOffset++;
408 const QByteArray val = ioStr.mid(index: codeOffsetBegin, len: codeOffset);
409 if (val == QByteArrayView("Default"))
410 setAlphaMode(inKeySet, inMode: QSSGRenderDefaultMaterial::MaterialAlphaMode::Default);
411 if (val == QByteArrayView("Mask"))
412 setAlphaMode(inKeySet, inMode: QSSGRenderDefaultMaterial::MaterialAlphaMode::Mask);
413 if (val == QByteArrayView("Blend"))
414 setAlphaMode(inKeySet, inMode: QSSGRenderDefaultMaterial::MaterialAlphaMode::Blend);
415 if (val == QByteArrayView("Opaque"))
416 setAlphaMode(inKeySet, inMode: QSSGRenderDefaultMaterial::MaterialAlphaMode::Opaque);
417 }
418 }
419};
420
421struct QSSGShaderKeyVertexAttribute : public QSSGShaderKeyUnsigned<9>
422{
423 enum VertexAttributeBits {
424 Position = 1 << 0,
425 Normal = 1 << 1,
426 TexCoord0 = 1 << 2,
427 TexCoord1 = 1 << 3,
428 Tangent = 1 << 4,
429 Binormal = 1 << 5,
430 Color = 1 << 6,
431 JointAndWeight = 1 << 7,
432 TexCoordLightmap = 1 << 8
433 };
434
435 explicit QSSGShaderKeyVertexAttribute(const char *inName = "") : QSSGShaderKeyUnsigned<9>(inName) {}
436
437 bool getBitValue(VertexAttributeBits bit, QSSGDataView<quint32> inKeySet) const
438 {
439 return (getValue(inDataStore: inKeySet) & bit) ? true : false;
440 }
441 void setBitValue(VertexAttributeBits bit, QSSGDataRef<quint32> inKeySet, bool value) const
442 {
443 quint32 v = getValue(inDataStore: inKeySet);
444 v = value ? (v | bit) : (v & ~bit);
445 setValue(inDataStore: inKeySet, inValue: v);
446 }
447
448 void toString(QByteArray &ioStr, QSSGDataView<quint32> inKeySet) const
449 {
450 ioStr.append(a: name);
451 ioStr.append(a: QByteArrayView("={"));
452 internalToString(ioStr, name: QByteArrayView("position"), inValue: getBitValue(bit: Position, inKeySet));
453 ioStr.append(c: ';');
454 internalToString(ioStr, name: QByteArrayView("normal"), inValue: getBitValue(bit: Normal, inKeySet));
455 ioStr.append(c: ';');
456 internalToString(ioStr, name: QByteArrayView("texcoord0"), inValue: getBitValue(bit: TexCoord0, inKeySet));
457 ioStr.append(c: ';');
458 internalToString(ioStr, name: QByteArrayView("texcoord1"), inValue: getBitValue(bit: TexCoord1, inKeySet));
459 ioStr.append(c: ';');
460 internalToString(ioStr, name: QByteArrayView("tangent"), inValue: getBitValue(bit: Tangent, inKeySet));
461 ioStr.append(c: ';');
462 internalToString(ioStr, name: QByteArrayView("binormal"), inValue: getBitValue(bit: Binormal, inKeySet));
463 ioStr.append(c: ';');
464 internalToString(ioStr, name: QByteArrayView("color"), inValue: getBitValue(bit: Color, inKeySet));
465 ioStr.append(c: ';');
466 internalToString(ioStr, name: QByteArrayView("texcoordlightmap"), inValue: getBitValue(bit: TexCoordLightmap, inKeySet));
467 ioStr.append(c: ';');
468 internalToString(ioStr, name: QByteArrayView("joint&weight"), inValue: getBitValue(bit: JointAndWeight, inKeySet));
469 ioStr.append(c: '}');
470 }
471 void fromString(const QByteArray &ioStr, QSSGDataRef<quint32> inKeySet)
472 {
473 const qsizetype nameLen = name.size();
474 const qsizetype strOffset = ioStr.indexOf(bv: name);
475 if (strOffset >= 0) {
476 /* The key is stored as name={;;;;;;} */
477 if (ioStr[strOffset + nameLen] != '=')
478 return;
479 if (ioStr[strOffset + nameLen + 1] != '{')
480 return;
481 const int codeOffsetBegin = strOffset + nameLen + 2;
482 int codeOffset = 0;
483 while (ioStr[codeOffsetBegin + codeOffset] != '}')
484 codeOffset++;
485 const QByteArray val = ioStr.mid(index: codeOffsetBegin, len: codeOffset);
486 const QVector<QByteArray> list = val.split(sep: ';');
487 if (list.size() != 8)
488 return;
489 setBitValue(bit: Position, inKeySet, value: getBoolValue(str: list[0], name: QByteArrayView("position")));
490 setBitValue(bit: Normal, inKeySet, value: getBoolValue(str: list[1], name: QByteArrayView("normal")));
491 setBitValue(bit: TexCoord0, inKeySet, value: getBoolValue(str: list[2], name: QByteArrayView("texcoord0")));
492 setBitValue(bit: TexCoord1, inKeySet, value: getBoolValue(str: list[3], name: QByteArrayView("texcoord1")));
493 setBitValue(bit: Tangent, inKeySet, value: getBoolValue(str: list[4], name: QByteArrayView("tangent")));
494 setBitValue(bit: Binormal, inKeySet, value: getBoolValue(str: list[5], name: QByteArrayView("binormal")));
495 setBitValue(bit: Color, inKeySet, value: getBoolValue(str: list[6], name: QByteArrayView("color")));
496 setBitValue(bit: TexCoordLightmap, inKeySet, value: getBoolValue(str: list[7], name: QByteArrayView("texcoordlightmap")));
497 }
498 }
499};
500
501struct QSSGShaderDefaultMaterialKeyProperties
502{
503 enum {
504 LightCount = QSSG_MAX_NUM_LIGHTS,
505 };
506 enum {
507 SingleChannelImageCount = 13,
508 };
509 enum ImageMapNames {
510 DiffuseMap = 0,
511 BumpMap,
512 SpecularMap,
513 NormalMap,
514 ClearcoatNormalMap,
515 // single channel images
516 OpacityMap,
517 RoughnessMap,
518 MetalnessMap,
519 OcclusionMap,
520 TranslucencyMap,
521 HeightMap,
522 ClearcoatMap,
523 ClearcoatRoughnessMap,
524 TransmissionMap,
525 ThicknessMap,
526 BaseColorMap,
527 SpecularAmountMap,
528 EmissiveMap,
529
530 ImageMapCount,
531 SingleChannelImagesFirst = OpacityMap
532 };
533 enum ImageChannelNames {
534 OpacityChannel = 0,
535 RoughnessChannel,
536 MetalnessChannel,
537 OcclusionChannel,
538 TranslucencyChannel,
539 HeightChannel,
540 ClearcoatChannel,
541 ClearcoatRoughnessChannel,
542 TransmissionChannel,
543 ThicknessChannel,
544 BaseColorChannel,
545 SpecularAmountChannel,
546 EmissiveChannel,
547 };
548
549 QSSGShaderKeyBoolean m_hasLighting;
550 QSSGShaderKeyBoolean m_hasIbl;
551 QSSGShaderKeyUnsigned<4> m_lightCount;
552 QSSGShaderKeyBoolean m_lightFlags[LightCount];
553 QSSGShaderKeyBoolean m_lightSpotFlags[LightCount];
554 QSSGShaderKeyBoolean m_lightAreaFlags[LightCount];
555 QSSGShaderKeyBoolean m_lightShadowFlags[LightCount];
556 QSSGShaderKeyUnsigned<16> m_lightShadowMapSize[LightCount];
557 QSSGShaderKeyUnsigned<4> m_lightSoftShadowQuality[LightCount];
558 QSSGShaderKeyBoolean m_specularEnabled;
559 QSSGShaderKeyBoolean m_fresnelScaleBiasEnabled;
560 QSSGShaderKeyBoolean m_clearcoatFresnelScaleBiasEnabled;
561 QSSGShaderKeyBoolean m_fresnelEnabled;
562 QSSGShaderKeyBoolean m_baseColorSingleChannelEnabled;
563 QSSGShaderKeyBoolean m_specularSingleChannelEnabled;
564 QSSGShaderKeyBoolean m_emissiveSingleChannelEnabled;
565 QSSGShaderKeyBoolean m_invertOpacityMapValue;
566 QSSGShaderKeyBoolean m_vertexColorsEnabled;
567 QSSGShaderKeyBoolean m_vertexColorsMaskEnabled;
568 QSSGShaderKeyUnsigned<16> m_vertexColorRedMask;
569 QSSGShaderKeyUnsigned<16> m_vertexColorGreenMask;
570 QSSGShaderKeyUnsigned<16> m_vertexColorBlueMask;
571 QSSGShaderKeyUnsigned<16> m_vertexColorAlphaMask;
572 QSSGShaderKeySpecularModel m_specularModel;
573 QSSGShaderKeyImageMap m_imageMaps[ImageMapCount];
574 QSSGShaderKeyTextureChannel m_textureChannels[SingleChannelImageCount];
575 QSSGShaderKeyUnsigned<16> m_boneCount;
576 QSSGShaderKeyBoolean m_isDoubleSided;
577 QSSGShaderKeyBoolean m_overridesPosition;
578 QSSGShaderKeyBoolean m_usesProjectionMatrix;
579 QSSGShaderKeyBoolean m_usesInverseProjectionMatrix;
580 QSSGShaderKeyBoolean m_usesPointsTopology;
581 QSSGShaderKeyBoolean m_usesVarColor;
582 QSSGShaderKeyAlphaMode m_alphaMode;
583 QSSGShaderKeyVertexAttribute m_vertexAttributes;
584 QSSGShaderKeyBoolean m_usesFloatJointIndices;
585 qsizetype m_stringBufferSizeHint = 0;
586 QSSGShaderKeyBoolean m_usesInstancing;
587 QSSGShaderKeyUnsigned<8> m_targetCount;
588 QSSGShaderKeyUnsigned<8> m_targetPositionOffset;
589 QSSGShaderKeyUnsigned<8> m_targetNormalOffset;
590 QSSGShaderKeyUnsigned<8> m_targetTangentOffset;
591 QSSGShaderKeyUnsigned<8> m_targetBinormalOffset;
592 QSSGShaderKeyUnsigned<8> m_targetTexCoord0Offset;
593 QSSGShaderKeyUnsigned<8> m_targetTexCoord1Offset;
594 QSSGShaderKeyUnsigned<8> m_targetColorOffset;
595 QSSGShaderKeyBoolean m_blendParticles;
596 QSSGShaderKeyBoolean m_clearcoatEnabled;
597 QSSGShaderKeyBoolean m_transmissionEnabled;
598 QSSGShaderKeyBoolean m_specularAAEnabled;
599 QSSGShaderKeyBoolean m_lightmapEnabled;
600 QSSGShaderKeyBoolean m_specularGlossyEnabled;
601 QSSGShaderKeyUnsigned<4> m_debugMode;
602 QSSGShaderKeyBoolean m_fogEnabled;
603 QSSGShaderKeyUnsigned<3> m_viewCount;
604 QSSGShaderKeyBoolean m_usesViewIndex;
605 QSSGShaderKeyUnsigned<3> m_orderIndependentTransparency;
606
607 QSSGShaderDefaultMaterialKeyProperties()
608 : m_hasLighting("hasLighting")
609 , m_hasIbl("hasIbl")
610 , m_lightCount("lightCount")
611 , m_specularEnabled("specularEnabled")
612 , m_fresnelScaleBiasEnabled("fresnelScaleBiasEnabled")
613 , m_clearcoatFresnelScaleBiasEnabled("clearcoatFresnelScaleBiasEnabled")
614 , m_fresnelEnabled("fresnelEnabled")
615 , m_baseColorSingleChannelEnabled("baseColorSingleChannelEnabled")
616 , m_specularSingleChannelEnabled("specularSingleChannelEnabled")
617 , m_emissiveSingleChannelEnabled("emissiveSingleChannelEnabled")
618 , m_invertOpacityMapValue("invertOpacityMapValue")
619 , m_vertexColorsEnabled("vertexColorsEnabled")
620 , m_vertexColorsMaskEnabled("vertexColorsMaskEnabled")
621 , m_vertexColorRedMask("vertexColorRedMask")
622 , m_vertexColorGreenMask("vertexColorGreenMask")
623 , m_vertexColorBlueMask("vertexColorBlueMask")
624 , m_vertexColorAlphaMask("vertexColorAlphaMask")
625 , m_specularModel("specularModel")
626 , m_boneCount("boneCount")
627 , m_isDoubleSided("isDoubleSided")
628 , m_overridesPosition("overridesPosition")
629 , m_usesProjectionMatrix("usesProjectionMatrix")
630 , m_usesInverseProjectionMatrix("usesInverseProjectionMatrix")
631 , m_usesPointsTopology("usesPointsTopology")
632 , m_usesVarColor("usesVarColor")
633 , m_alphaMode("alphaMode")
634 , m_vertexAttributes("vertexAttributes")
635 , m_usesFloatJointIndices("usesFloatJointIndices")
636 , m_usesInstancing("usesInstancing")
637 , m_targetCount("targetCount")
638 , m_targetPositionOffset("targetPositionOffset")
639 , m_targetNormalOffset("targetNormalOffset")
640 , m_targetTangentOffset("targetTangentOffset")
641 , m_targetBinormalOffset("targetBinormalOffset")
642 , m_targetTexCoord0Offset("targetTexCoord0Offset")
643 , m_targetTexCoord1Offset("targetTexCoord1Offset")
644 , m_targetColorOffset("targetColorOffset")
645 , m_blendParticles("blendParticles")
646 , m_clearcoatEnabled("clearcoatEnabled")
647 , m_transmissionEnabled("transmissionEnabled")
648 , m_specularAAEnabled("specularAAEnabled")
649 , m_lightmapEnabled("lightmapEnabled")
650 , m_specularGlossyEnabled("specularGlossyEnabled")
651 , m_debugMode("debugMode")
652 , m_fogEnabled("fogEnabled")
653 , m_viewCount("viewCount")
654 , m_usesViewIndex("usesViewIndex")
655 , m_orderIndependentTransparency("orderIndependentTransparency")
656 {
657 m_lightFlags[0].name = "light0HasPosition";
658 m_lightFlags[1].name = "light1HasPosition";
659 m_lightFlags[2].name = "light2HasPosition";
660 m_lightFlags[3].name = "light3HasPosition";
661 m_lightFlags[4].name = "light4HasPosition";
662 m_lightFlags[5].name = "light5HasPosition";
663 m_lightFlags[6].name = "light6HasPosition";
664 m_lightFlags[7].name = "light7HasPosition";
665 m_lightFlags[8].name = "light8HasPosition";
666 m_lightFlags[9].name = "light9HasPosition";
667 m_lightFlags[10].name = "light10HasPosition";
668 m_lightFlags[11].name = "light11HasPosition";
669 m_lightFlags[12].name = "light12HasPosition";
670 m_lightFlags[13].name = "light13HasPosition";
671 m_lightFlags[14].name = "light14HasPosition";
672
673 m_lightSpotFlags[0].name = "light0HasSpot";
674 m_lightSpotFlags[1].name = "light1HasSpot";
675 m_lightSpotFlags[2].name = "light2HasSpot";
676 m_lightSpotFlags[3].name = "light3HasSpot";
677 m_lightSpotFlags[4].name = "light4HasSpot";
678 m_lightSpotFlags[5].name = "light5HasSpot";
679 m_lightSpotFlags[6].name = "light6HasSpot";
680 m_lightSpotFlags[7].name = "light7HasSpot";
681 m_lightSpotFlags[8].name = "light8HasSpot";
682 m_lightSpotFlags[9].name = "light9HasSpot";
683 m_lightSpotFlags[10].name = "light10HasSpot";
684 m_lightSpotFlags[11].name = "light11HasSpot";
685 m_lightSpotFlags[12].name = "light12HasSpot";
686 m_lightSpotFlags[13].name = "light13HasSpot";
687 m_lightSpotFlags[14].name = "light14HasSpot";
688
689 m_lightAreaFlags[0].name = "light0HasArea";
690 m_lightAreaFlags[1].name = "light1HasArea";
691 m_lightAreaFlags[2].name = "light2HasArea";
692 m_lightAreaFlags[3].name = "light3HasArea";
693 m_lightAreaFlags[4].name = "light4HasArea";
694 m_lightAreaFlags[5].name = "light5HasArea";
695 m_lightAreaFlags[6].name = "light6HasArea";
696 m_lightAreaFlags[7].name = "light7HasArea";
697 m_lightAreaFlags[8].name = "light8HasArea";
698 m_lightAreaFlags[9].name = "light9HasArea";
699 m_lightAreaFlags[10].name = "light10HasArea";
700 m_lightAreaFlags[11].name = "light11HasArea";
701 m_lightAreaFlags[12].name = "light12HasArea";
702 m_lightAreaFlags[13].name = "light13HasArea";
703 m_lightAreaFlags[14].name = "light14HasArea";
704
705 m_lightShadowFlags[0].name = "light0HasShadow";
706 m_lightShadowFlags[1].name = "light1HasShadow";
707 m_lightShadowFlags[2].name = "light2HasShadow";
708 m_lightShadowFlags[3].name = "light3HasShadow";
709 m_lightShadowFlags[4].name = "light4HasShadow";
710 m_lightShadowFlags[5].name = "light5HasShadow";
711 m_lightShadowFlags[6].name = "light6HasShadow";
712 m_lightShadowFlags[7].name = "light7HasShadow";
713 m_lightShadowFlags[8].name = "light8HasShadow";
714 m_lightShadowFlags[9].name = "light9HasShadow";
715 m_lightShadowFlags[10].name = "light10HasShadow";
716 m_lightShadowFlags[11].name = "light11HasShadow";
717 m_lightShadowFlags[12].name = "light12HasShadow";
718 m_lightShadowFlags[13].name = "light13HasShadow";
719 m_lightShadowFlags[14].name = "light14HasShadow";
720
721 m_lightShadowMapSize[0].name = "light0ShadowMapSize";
722 m_lightShadowMapSize[1].name = "light1ShadowMapSize";
723 m_lightShadowMapSize[2].name = "light2ShadowMapSize";
724 m_lightShadowMapSize[3].name = "light3ShadowMapSize";
725 m_lightShadowMapSize[4].name = "light4ShadowMapSize";
726 m_lightShadowMapSize[5].name = "light5ShadowMapSize";
727 m_lightShadowMapSize[6].name = "light6ShadowMapSize";
728 m_lightShadowMapSize[7].name = "light7ShadowMapSize";
729 m_lightShadowMapSize[8].name = "light8ShadowMapSize";
730 m_lightShadowMapSize[9].name = "light9ShadowMapSize";
731 m_lightShadowMapSize[10].name = "light10ShadowMapSize";
732 m_lightShadowMapSize[11].name = "light11ShadowMapSize";
733 m_lightShadowMapSize[12].name = "light12ShadowMapSize";
734 m_lightShadowMapSize[13].name = "light13ShadowMapSize";
735 m_lightShadowMapSize[14].name = "light14ShadowMapSize";
736
737 m_lightSoftShadowQuality[0].name = "light0SoftShadowQuality";
738 m_lightSoftShadowQuality[1].name = "light1SoftShadowQuality";
739 m_lightSoftShadowQuality[2].name = "light2SoftShadowQuality";
740 m_lightSoftShadowQuality[3].name = "light3SoftShadowQuality";
741 m_lightSoftShadowQuality[4].name = "light4SoftShadowQuality";
742 m_lightSoftShadowQuality[5].name = "light5SoftShadowQuality";
743 m_lightSoftShadowQuality[6].name = "light6SoftShadowQuality";
744 m_lightSoftShadowQuality[7].name = "light7SoftShadowQuality";
745 m_lightSoftShadowQuality[8].name = "light8SoftShadowQuality";
746 m_lightSoftShadowQuality[9].name = "light9SoftShadowQuality";
747 m_lightSoftShadowQuality[10].name = "light10SoftShadowQuality";
748 m_lightSoftShadowQuality[11].name = "light11SoftShadowQuality";
749 m_lightSoftShadowQuality[12].name = "light12SoftShadowQuality";
750 m_lightSoftShadowQuality[13].name = "light13SoftShadowQuality";
751 m_lightSoftShadowQuality[14].name = "light14SoftShadowQuality";
752
753 m_imageMaps[0].name = "diffuseMap";
754 m_imageMaps[1].name = "emissiveMap";
755 m_imageMaps[2].name = "specularMap";
756 m_imageMaps[3].name = "baseColorMap";
757 m_imageMaps[4].name = "bumpMap";
758 m_imageMaps[5].name = "specularAmountMap";
759 m_imageMaps[6].name = "normalMap";
760 m_imageMaps[7].name = "clearcoatNormalMap";
761 m_imageMaps[8].name = "opacityMap";
762 m_imageMaps[9].name = "roughnessMap";
763 m_imageMaps[10].name = "metalnessMap";
764 m_imageMaps[11].name = "occlusionMap";
765 m_imageMaps[12].name = "translucencyMap";
766 m_imageMaps[13].name = "heightMap";
767 m_imageMaps[14].name = "clearcoatMap";
768 m_imageMaps[15].name = "clearcoatRoughnessMap";
769 m_imageMaps[16].name = "transmissionMap";
770 m_imageMaps[17].name = "thicknessMap";
771
772 m_textureChannels[0].name = "opacityMap_channel";
773 m_textureChannels[1].name = "roughnessMap_channel";
774 m_textureChannels[2].name = "metalnessMap_channel";
775 m_textureChannels[3].name = "occlusionMap_channel";
776 m_textureChannels[4].name = "translucencyMap_channel";
777 m_textureChannels[5].name = "heightMap_channel";
778 m_textureChannels[6].name = "clearcoatMap_channel";
779 m_textureChannels[7].name = "clearcoatRoughnessMap_channel";
780 m_textureChannels[8].name = "transmissionMap_channel";
781 m_textureChannels[9].name = "thicknessMap_channel";
782 m_textureChannels[10].name = "baseColorMap_channel";
783 m_textureChannels[11].name = "specularAmountMap_channel";
784 m_textureChannels[12].name = "emissiveMap_channel";
785
786 init();
787 }
788
789 template<typename TVisitor>
790 void visitProperties(TVisitor &inVisitor)
791 {
792 inVisitor.visit(m_hasLighting);
793 inVisitor.visit(m_hasIbl);
794 inVisitor.visit(m_lightCount);
795
796 for (auto &lightFlag : m_lightFlags)
797 inVisitor.visit(lightFlag);
798
799 for (auto &lightSpotFlag : m_lightSpotFlags)
800 inVisitor.visit(lightSpotFlag);
801
802 for (auto &lightAreaFlag : m_lightAreaFlags)
803 inVisitor.visit(lightAreaFlag);
804
805 for (auto &lightShadowFlag : m_lightShadowFlags)
806 inVisitor.visit(lightShadowFlag);
807
808 for (auto &lightShadowMapSize : m_lightShadowMapSize)
809 inVisitor.visit(lightShadowMapSize);
810
811 for (auto &softShadowQuality : m_lightSoftShadowQuality)
812 inVisitor.visit(softShadowQuality);
813
814 inVisitor.visit(m_specularEnabled);
815 inVisitor.visit(m_fresnelEnabled);
816 inVisitor.visit(m_fresnelScaleBiasEnabled);
817 inVisitor.visit(m_clearcoatFresnelScaleBiasEnabled);
818 inVisitor.visit(m_baseColorSingleChannelEnabled);
819 inVisitor.visit(m_specularSingleChannelEnabled);
820 inVisitor.visit(m_emissiveSingleChannelEnabled);
821 inVisitor.visit(m_invertOpacityMapValue);
822 inVisitor.visit(m_vertexColorsEnabled);
823 inVisitor.visit(m_vertexColorsMaskEnabled);
824 inVisitor.visit(m_vertexColorRedMask);
825 inVisitor.visit(m_vertexColorGreenMask);
826 inVisitor.visit(m_vertexColorBlueMask);
827 inVisitor.visit(m_vertexColorAlphaMask);
828 inVisitor.visit(m_specularModel);
829
830 for (quint32 idx = 0, end = ImageMapCount; idx < end; ++idx)
831 inVisitor.visit(m_imageMaps[idx]);
832
833 for (auto &textureChannel : m_textureChannels)
834 inVisitor.visit(textureChannel);
835
836 inVisitor.visit(m_boneCount);
837 inVisitor.visit(m_isDoubleSided);
838 inVisitor.visit(m_overridesPosition);
839 inVisitor.visit(m_usesProjectionMatrix);
840 inVisitor.visit(m_usesInverseProjectionMatrix);
841 inVisitor.visit(m_usesPointsTopology);
842 inVisitor.visit(m_usesVarColor);
843 inVisitor.visit(m_alphaMode);
844 inVisitor.visit(m_vertexAttributes);
845 inVisitor.visit(m_usesFloatJointIndices);
846 inVisitor.visit(m_usesInstancing);
847 inVisitor.visit(m_targetCount);
848 inVisitor.visit(m_targetPositionOffset);
849 inVisitor.visit(m_targetNormalOffset);
850 inVisitor.visit(m_targetTangentOffset);
851 inVisitor.visit(m_targetBinormalOffset);
852 inVisitor.visit(m_targetTexCoord0Offset);
853 inVisitor.visit(m_targetTexCoord1Offset);
854 inVisitor.visit(m_targetColorOffset);
855 inVisitor.visit(m_blendParticles);
856 inVisitor.visit(m_clearcoatEnabled);
857 inVisitor.visit(m_transmissionEnabled);
858 inVisitor.visit(m_specularAAEnabled);
859 inVisitor.visit(m_lightmapEnabled);
860 inVisitor.visit(m_specularGlossyEnabled);
861 inVisitor.visit(m_debugMode);
862 inVisitor.visit(m_fogEnabled);
863 inVisitor.visit(m_viewCount);
864 inVisitor.visit(m_usesViewIndex);
865 inVisitor.visit(m_orderIndependentTransparency);
866 }
867
868 struct OffsetVisitor
869 {
870 quint32 m_offset;
871 OffsetVisitor() : m_offset(0) {}
872 template<typename TPropType>
873 void visit(TPropType &inProp)
874 {
875 // if we cross the 32 bit border we just move
876 // to the next dword.
877 // This cost a few extra bits but prevents tedious errors like
878 // loosing shader key bits because they got moved beyond the 32 border
879 quint32 bit = m_offset % 32;
880 if (bit + TPropType::BitWidth > 32) {
881 m_offset += 32 - bit;
882 }
883
884 inProp.setOffset(m_offset);
885 m_offset += TPropType::BitWidth;
886 }
887 };
888
889 struct StringSizeVisitor
890 {
891 qsizetype size = 0;
892 template<typename P>
893 constexpr void visit(const P &prop)
894 {
895 size += prop.name.size();
896 }
897 };
898
899 struct InitVisitor
900 {
901 OffsetVisitor offsetVisitor;
902 StringSizeVisitor stringSizeVisitor;
903
904 template<typename P>
905 void visit(P &prop)
906 {
907 offsetVisitor.visit(prop);
908 stringSizeVisitor.visit(prop);
909 }
910 };
911
912 void init()
913 {
914 InitVisitor visitor;
915 visitProperties(inVisitor&: visitor);
916
917 // If this assert fires, then the default material key needs more bits.
918 Q_ASSERT(visitor.offsetVisitor.m_offset < 768);
919 // This is so we can do some guestimate of how big the string buffer needs
920 // to be to avoid doing a lot of allocations when concatenating the strings.
921 m_stringBufferSizeHint = visitor.stringSizeVisitor.size;
922 }
923};
924
925struct QSSGShaderDefaultMaterialKey
926{
927 enum {
928 DataBufferSize = 24,
929 };
930 quint32 m_dataBuffer[DataBufferSize]; // 24 * 4 * 8 = 768 bits
931 size_t m_featureSetHash;
932
933 explicit QSSGShaderDefaultMaterialKey(size_t inFeatureSetHash) : m_featureSetHash(inFeatureSetHash)
934 {
935 for (size_t idx = 0; idx < DataBufferSize; ++idx)
936 m_dataBuffer[idx] = 0;
937 }
938
939 QSSGShaderDefaultMaterialKey() : m_featureSetHash(0)
940 {
941 for (size_t idx = 0; idx < DataBufferSize; ++idx)
942 m_dataBuffer[idx] = 0;
943 }
944
945 size_t hash() const
946 {
947 size_t retval = 0;
948 for (size_t idx = 0; idx < DataBufferSize; ++idx)
949 retval = retval ^ qHash(key: m_dataBuffer[idx]);
950 return retval ^ m_featureSetHash;
951 }
952
953 bool operator==(const QSSGShaderDefaultMaterialKey &other) const
954 {
955 bool retval = true;
956 for (size_t idx = 0; idx < DataBufferSize && retval; ++idx)
957 retval = m_dataBuffer[idx] == other.m_dataBuffer[idx];
958 return retval && m_featureSetHash == other.m_featureSetHash;
959 }
960
961 // Cast operators to make getting properties easier.
962 operator QSSGDataRef<quint32>() { return toDataRef(type: m_dataBuffer, count: DataBufferSize); }
963 operator QSSGDataView<quint32>() const { return toDataView(type: m_dataBuffer, count: DataBufferSize); }
964
965 struct StringVisitor
966 {
967 QByteArray &m_str;
968 QSSGDataView<quint32> m_keyStore;
969 StringVisitor(QByteArray &s, QSSGDataView<quint32> ks) : m_str(s), m_keyStore(ks) {}
970 template<typename TPropType>
971 void visit(const TPropType &prop)
972 {
973 const qsizetype originalSize = m_str.size();
974 if (m_str.size())
975 m_str.append(c: ';');
976 prop.toString(m_str, m_keyStore);
977 // if the only thing we added was the semicolon
978 // then nuke the semicolon
979 if (originalSize && m_str.size() == (originalSize + 1))
980 m_str.resize(size: originalSize);
981 }
982 };
983
984 struct StringInVisitor
985 {
986 const QByteArray &m_str;
987 QSSGDataRef<quint32> m_keyStore;
988 StringInVisitor(const QByteArray &s, QSSGDataRef<quint32> ks) : m_str(s), m_keyStore(ks) {}
989
990 template<typename TPropType>
991 void visit(TPropType &prop)
992 {
993 prop.fromString(m_str, m_keyStore);
994 }
995 };
996
997 void toString(QByteArray &ioString, const QSSGShaderDefaultMaterialKeyProperties &inProperties) const
998 {
999 ioString.reserve(asize: inProperties.m_stringBufferSizeHint);
1000 StringVisitor theVisitor(ioString, *this);
1001 const_cast<QSSGShaderDefaultMaterialKeyProperties &>(inProperties).visitProperties(inVisitor&: theVisitor);
1002 }
1003 void fromString(QByteArray &ioString, QSSGShaderDefaultMaterialKeyProperties &inProperties)
1004 {
1005 StringInVisitor theVisitor(ioString, *this);
1006 inProperties.visitProperties(inVisitor&: theVisitor);
1007 }
1008 QByteArray toByteArray() const
1009 {
1010 QByteArray ret;
1011 ret.resize(size: sizeof(m_dataBuffer));
1012 memcpy(dest: ret.data(), src: m_dataBuffer, n: sizeof(m_dataBuffer));
1013 return ret;
1014 }
1015 bool fromByteArray(const QByteArray &data) const
1016 {
1017 if (data.size() != sizeof(m_dataBuffer))
1018 return false;
1019 memcpy(dest: (void *)m_dataBuffer, src: data.data(), n: sizeof(m_dataBuffer));
1020 return true;
1021 }
1022};
1023
1024Q_STATIC_ASSERT(std::is_trivially_destructible<QSSGShaderDefaultMaterialKey>::value);
1025
1026
1027inline size_t qHash(const QSSGShaderDefaultMaterialKey &key)
1028{
1029 return key.hash();
1030}
1031
1032QT_END_NAMESPACE
1033
1034#endif
1035

source code of qtquick3d/src/runtimerender/qssgrendershaderkeys_p.h