Revision control

++ // Copyright (C) 2008-2012 NVIDIA Corporation.
++ // Copyright (C) 2019 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++
++ #ifndef QSSG_RENDER_SHADER_KEY_H
++ #define QSSG_RENDER_SHADER_KEY_H
++
++ //
++ // W A R N I N G
++ // -------------
++ //
++ // This file is not part of the Qt API. It exists purely as an
++ // implementation detail. This header file may change from version to
++ // version without notice, or even be removed.
++ //
++ // We mean it.
++ //
++
++ #include <QtQuick3DUtils/private/qssgdataref_p.h>
++ #include <QtQuick3DUtils/private/qssgrenderbasetypes_p.h>
++ #include <QtQuick3DRuntimeRender/private/qssgrenderdefaultmaterial_p.h>
++ #include <QtQuick3DRuntimeRender/private/qssgrenderlight_p.h>
++ #include <QtQuick3DRuntimeRender/private/qssgrhicontext_p.h>
++
++ QT_BEGIN_NAMESPACE
++ // We have an ever expanding set of properties we like to hash into one or more 32 bit
++ // quantities.
++ // Furthermore we would like this set of properties to be convertable to string
++ // So the shader cache file itself is somewhat human readable/diagnosable.
++ // To do this we create a set of objects that act as properties to the master shader key.
++ // These objects are tallied in order to figure out their actual offset into the shader key's
++ // data store. They are also run through in order to create the string shader cache key.
++
++ struct QSSGShaderKeyPropertyBase
++ {
++ QByteArrayView name;
++ quint32 offset;
++ explicit constexpr QSSGShaderKeyPropertyBase(const char *inName = "") : name(inName), offset(0) {}
++ quint32 getOffset() const { return offset; }
++ void setOffset(quint32 of) { offset = of; }
++
++ template<quint32 TBitWidth>
++ quint32 getMaskTemplate() const
++ {
++ quint32 bit = offset % 32;
++ quint32 startValue = (1 << TBitWidth) - 1;
++ quint32 mask = startValue << bit;
++ return mask;
++ }
++
++ quint32 getIdx() const { return offset / 32; }
++
++ protected:
++ void internalToString(QByteArray &ioStr, const QByteArrayView &inBuffer) const
++ {
++ ioStr.append(name);
++ ioStr.append('=');
++ ioStr.append(inBuffer);
++ }
++
++ static void internalToString(QByteArray &ioStr, const QByteArrayView &name, bool inValue)
++ {
++ if (inValue) {
++ ioStr.append(name);
++ ioStr.append('=');
++ ioStr.append(inValue ? QByteArrayView("true") : QByteArrayView("false"));
++ }
++ }
++ static bool getBoolValue(const QByteArray& str, const QByteArrayView &name)
++ {
++ const int index = str.indexOf(name);
++ if (index < 0)
++ return false;
++ const qsizetype nameLen = name.size();
++ if (str[index + nameLen] != '=')
++ return false;
++ if (str.mid(index + nameLen + 1, 4) == QByteArrayView("true"))
++ return true;
++ return false;
++ }
++ };
++
++ struct QSSGShaderKeyBoolean : public QSSGShaderKeyPropertyBase
++ {
++ enum {
++ BitWidth = 1,
++ };
++
++ explicit constexpr QSSGShaderKeyBoolean(const char *inName = "") : QSSGShaderKeyPropertyBase(inName) {}
++
++ quint32 getMask() const { return getMaskTemplate<BitWidth>(); }
++ void setValue(QSSGDataRef<quint32> inDataStore, bool inValue) const
++ {
++ const qint32 idx = qint32(getIdx());
++ Q_ASSERT(idx >= 0 && idx <= INT32_MAX);
++ Q_ASSERT(inDataStore.size() > idx);
++ quint32 mask = getMask();
++ quint32 &target = inDataStore[idx];
++ if (inValue) {
++ target = target | mask;
++ } else {
++ mask = ~mask;
++ target = target & mask;
++ }
++ }
++
++ bool getValue(QSSGDataView<quint32> inDataStore) const
++ {
++ quint32 idx = getIdx();
++ quint32 mask = getMask();
++ const quint32 &target = inDataStore[idx];
++ return (target & mask) ? true : false;
++ }
++
++ void toString(QByteArray &ioStr, QSSGDataView<quint32> inKeySet) const
++ {
++ bool isHigh = getValue(inKeySet);
++ internalToString(ioStr, name, isHigh);
++ }
++ void fromString(const QByteArray &ioStr, QSSGDataRef<quint32> inKeySet)
++ {
++ setValue(inKeySet, getBoolValue(ioStr, name));
++ }
++ };
++
++ template<quint32 TBitWidth>
++ struct QSSGShaderKeyUnsigned : public QSSGShaderKeyPropertyBase
++ {
++ enum {
++ BitWidth = TBitWidth,
++ };
++ explicit constexpr QSSGShaderKeyUnsigned(const char *inName = "") : QSSGShaderKeyPropertyBase(inName) {}
++ quint32 getMask() const { return getMaskTemplate<BitWidth>(); }
++ void setValue(QSSGDataRef<quint32> inDataStore, quint32 inValue) const
++ {
++ quint32 startValue = (1 << TBitWidth) - 1;
++ // Ensure inValue is within range of bit width.
++ inValue = inValue & startValue;
++ quint32 bit = offset % 32;
++ quint32 mask = getMask();
++ quint32 idx = getIdx();
++ inValue = inValue << bit;
++ quint32 &target = inDataStore[idx];
++ // Get rid of existing value
++ quint32 inverseMask = ~mask;
++ target = target & inverseMask;
++ target = target | inValue;
++ }
++
++ quint32 getValue(QSSGDataView<quint32> inDataStore) const
++ {
++ quint32 idx = getIdx();
++ quint32 bit = offset % 32;
++ quint32 mask = getMask();
++ const quint32 &target = inDataStore[idx];
++
++ quint32 retval = target & mask;
++ retval = retval >> bit;
++ return retval;
++ }
++
++ void toString(QByteArray &ioStr, QSSGDataView<quint32> inKeySet) const
++ {
++ quint32 value = getValue(inKeySet);
++ char buf[64];
++ memset(buf, 0, sizeof (buf));
++ toStr(value, toDataRef(buf, 64));
++ internalToString(ioStr, buf);
++ }
++
++ void fromString(const QByteArray &ioStr, QSSGDataRef<quint32> inKeySet)
++ {
++ const qsizetype nameLen = name.size();
++ const qsizetype strOffset = ioStr.indexOf(name);
++ if (strOffset >= 0) {
++ /* The key is stored as name=val */
++ if (ioStr[strOffset + nameLen] != '=')
++ return;
++ const QByteArray s = ioStr.right(ioStr.size() - strOffset - nameLen - 1);
++ int i = 0;
++ while (QChar(QLatin1Char(s[i])).isDigit())
++ i++;
++ const quint32 value = s.left(i).toInt();
++ setValue(inKeySet, value);
++ }
++ }
++
++ private:
++ static quint32 toStr(quint32 item, QSSGDataRef<char> buffer)
++ {
++ // hope the buffer is big enough...
++ return static_cast<quint32>(::snprintf(buffer.begin(), buffer.size(), "%u", item));
++ }
++ };
++
++ struct QSSGShaderKeyTextureChannel : public QSSGShaderKeyUnsigned<2>
++ {
++ enum TexturChannelBits {
++ R = 0,
++ G = 1,
++ B = 2,
++ A = 3,
++ };
++ explicit QSSGShaderKeyTextureChannel(const char *inName = "") : QSSGShaderKeyUnsigned<2>(inName) {}
++
++ TexturChannelBits getTextureChannel(QSSGDataView<quint32> inKeySet) const
++ {
++ return TexturChannelBits(getValue(inKeySet));
++ }
++
++ void setTextureChannel(TexturChannelBits channel, QSSGDataRef<quint32> inKeySet)
++ {
++ setValue(inKeySet, quint32(channel));
++ }
++ static constexpr char textureChannelToChar[4] = {
++ 'R',
++ 'G',
++ 'B',
++ 'A'
++ };
++ void toString(QByteArray &ioStr, QSSGDataView<quint32> inKeySet) const
++ {
++ ioStr.append(name);
++ ioStr.append('=');
++ ioStr.append(textureChannelToChar[getTextureChannel(inKeySet)]);
++ }
++ void fromString(const QByteArray &ioStr, QSSGDataRef<quint32> inKeySet)
++ {
++ const qsizetype nameLen = name.size();
++ const qsizetype strOffset = ioStr.indexOf(name);
++ if (strOffset >= 0) {
++ /* The key is stored as name=ch */
++ if (ioStr[strOffset + nameLen] != '=')
++ return;
++ const char ch = ioStr[strOffset + nameLen + 1];
++ if (ch == 'R')
++ setValue(inKeySet, TexturChannelBits::R);
++ else if (ch == 'G')
++ setValue(inKeySet, TexturChannelBits::G);
++ else if (ch == 'B')
++ setValue(inKeySet, TexturChannelBits::B);
++ else if (ch == 'A')
++ setValue(inKeySet, TexturChannelBits::A);
++ }
++ }
++ };
++
++ struct QSSGShaderKeyImageMap : public QSSGShaderKeyUnsigned<7>
++ {
++ enum ImageMapBits {
++ Enabled = 1 << 0,
++ EnvMap = 1 << 1,
++ LightProbe = 1 << 2,
++ Identity = 1 << 3,
++ UsesUV1 = 1 << 4,
++ Linear = 1 << 5,
++ PreMultipliedAlpha = 1 << 6,
++ };
++
++ explicit QSSGShaderKeyImageMap(const char *inName = "") : QSSGShaderKeyUnsigned<7>(inName) {}
++
++ bool getBitValue(ImageMapBits imageBit, QSSGDataView<quint32> inKeySet) const
++ {
++ return (getValue(inKeySet) & imageBit) ? true : false;
++ }
++
++ void setBitValue(ImageMapBits imageBit, bool inValue, QSSGDataRef<quint32> inKeySet)
++ {
++ quint32 theValue = getValue(inKeySet);
++ quint32 mask = imageBit;
++ if (inValue) {
++ theValue = theValue | mask;
++ } else {
++ mask = ~mask;
++ theValue = theValue & mask;
++ }
++ setValue(inKeySet, theValue);
++ }
++
++ bool isEnabled(QSSGDataView<quint32> inKeySet) const { return getBitValue(Enabled, inKeySet); }
++ void setEnabled(QSSGDataRef<quint32> inKeySet, bool val) { setBitValue(Enabled, val, inKeySet); }
++
++ bool isEnvMap(QSSGDataView<quint32> inKeySet) const { return getBitValue(EnvMap, inKeySet); }
++ void setEnvMap(QSSGDataRef<quint32> inKeySet, bool val) { setBitValue(EnvMap, val, inKeySet); }
++
++ bool isLightProbe(QSSGDataView<quint32> inKeySet) const { return getBitValue(LightProbe, inKeySet); }
++ void setLightProbe(QSSGDataRef<quint32> inKeySet, bool val) { setBitValue(LightProbe, val, inKeySet); }
++
++ bool isIdentityTransform(QSSGDataView<quint32> inKeySet) const { return getBitValue(Identity, inKeySet); }
++ void setIdentityTransform(QSSGDataRef<quint32> inKeySet, bool val) { setBitValue(Identity, val, inKeySet); }
++
++ bool isUsingUV1(QSSGDataView<quint32> inKeySet) const { return getBitValue(UsesUV1, inKeySet); }
++ void setUsesUV1(QSSGDataRef<quint32> inKeySet, bool val) { setBitValue(UsesUV1, val, inKeySet); }
++
++ bool isLinear(QSSGDataView<quint32> inKeySet) const { return getBitValue(Linear, inKeySet); }
++ void setLinear(QSSGDataRef<quint32> inKeySet, bool val) { setBitValue(Linear, val, inKeySet); }
++
++ bool isPreMultipliedAlpha(QSSGDataView<quint32> inKeySet) const { return getBitValue(PreMultipliedAlpha, inKeySet); }
++ void setPreMultipliedAlpha(QSSGDataRef<quint32> inKeySet, bool val) { setBitValue(PreMultipliedAlpha, val, inKeySet); }
++
++ void toString(QByteArray &ioStr, QSSGDataView<quint32> inKeySet) const
++ {
++ ioStr.append(name);
++ ioStr.append(QByteArrayView("={"));
++ internalToString(ioStr, QByteArrayView("enabled"), isEnabled(inKeySet));
++ ioStr.append(';');
++ internalToString(ioStr, QByteArrayView("envMap"), isEnvMap(inKeySet));
++ ioStr.append(';');
++ internalToString(ioStr, QByteArrayView("lightProbe"), isLightProbe(inKeySet));
++ ioStr.append(';');
++ internalToString(ioStr, QByteArrayView("identity"), isIdentityTransform(inKeySet));
++ ioStr.append(';');
++ internalToString(ioStr, QByteArrayView("usesUV1"), isUsingUV1(inKeySet));
++ ioStr.append(';');
++ internalToString(ioStr, QByteArrayView("linear"), isLinear(inKeySet));
++ ioStr.append(';');
++ internalToString(ioStr, QByteArrayView("preMultipliedAlpha"), isPreMultipliedAlpha(inKeySet));
++ ioStr.append('}');
++ }
++ };
++
++ struct QSSGShaderKeySpecularModel : QSSGShaderKeyUnsigned<2>
++ {
++ explicit QSSGShaderKeySpecularModel(const char *inName = "") : QSSGShaderKeyUnsigned<2>(inName) {}
++
++ void setSpecularModel(QSSGDataRef<quint32> inKeySet, QSSGRenderDefaultMaterial::MaterialSpecularModel inModel)
++ {
++ setValue(inKeySet, quint32(inModel));
++ }
++
++ QSSGRenderDefaultMaterial::MaterialSpecularModel getSpecularModel(QSSGDataView<quint32> inKeySet) const
++ {
++ return static_cast<QSSGRenderDefaultMaterial::MaterialSpecularModel>(getValue(inKeySet));
++ }
++
++ void toString(QByteArray &ioStr, QSSGDataView<quint32> inKeySet) const
++ {
++ ioStr.append(name);
++ ioStr.append('=');
++ switch (getSpecularModel(inKeySet)) {
++ case QSSGRenderDefaultMaterial::MaterialSpecularModel::SchlickGGX:
++ ioStr.append(QByteArrayView("SchlickGGX"));
++ break;
++ case QSSGRenderDefaultMaterial::MaterialSpecularModel::BlinnPhong:
++ ioStr.append(QByteArrayView("BlinnPhong"));
++ break;
++ }
++ }
++ void fromString(const QByteArray &ioStr, QSSGDataRef<quint32> inKeySet)
++ {
++ const qsizetype nameLen = name.size();
++ const int strOffset = ioStr.indexOf(name);
++ if (strOffset >= 0) {
++ /* The key is stored as name=specularMode; */
++ if (ioStr[strOffset + nameLen] != '=')
++ return;
++ const int codeOffsetBegin = strOffset + nameLen + 1;
++ int codeOffset = 0;
++ while (ioStr[codeOffsetBegin + codeOffset] != ';')
++ codeOffset++;
++ const QByteArray val = ioStr.mid(codeOffsetBegin, codeOffset);
++ if (val == QByteArrayView("SchlickGGX"))
++ setSpecularModel(inKeySet, QSSGRenderDefaultMaterial::MaterialSpecularModel::SchlickGGX);
++ if (val == QByteArrayView("BlinnPhong"))
++ setSpecularModel(inKeySet, QSSGRenderDefaultMaterial::MaterialSpecularModel::BlinnPhong);
++ }
++ }
++ };
++
++ struct QSSGShaderKeyDiffuseModel : QSSGShaderKeyUnsigned<2>
++ {
++ explicit QSSGShaderKeyDiffuseModel(const char *inName = "") : QSSGShaderKeyUnsigned<2>(inName) {}
++
++ void setDiffuseModel(QSSGDataRef<quint32> inKeySet, QSSGRenderDefaultMaterial::MaterialDiffuseModel inModel)
++ {
++ setValue(inKeySet, quint32(inModel));
++ }
++
++ QSSGRenderDefaultMaterial::MaterialDiffuseModel getDiffuseModel(QSSGDataView<quint32> inKeySet) const
++ {
++ return static_cast<QSSGRenderDefaultMaterial::MaterialDiffuseModel>(getValue(inKeySet));
++ }
++
++ void toString(QByteArray &ioStr, QSSGDataView<quint32> inKeySet) const
++ {
++ ioStr.append(name);
++ ioStr.append('=');
++ switch (getDiffuseModel(inKeySet)) {
++ case QSSGRenderDefaultMaterial::MaterialDiffuseModel::Burley:
++ ioStr.append(QByteArrayView("Burley"));
++ break;
++ case QSSGRenderDefaultMaterial::MaterialDiffuseModel::Lambert:
++ ioStr.append(QByteArrayView("Lambert"));
++ break;
++ case QSSGRenderDefaultMaterial::MaterialDiffuseModel::LambertWrap:
++ ioStr.append(QByteArrayView("LambertWrap"));
++ break;
++ }
++ }
++ void fromString(const QByteArray &ioStr, QSSGDataRef<quint32> inKeySet)
++ {
++ const qsizetype nameLen = name.size();
++ const int strOffset = ioStr.indexOf(name);
++ if (strOffset >= 0) {
++ /* The key is stored as name=specularMode; */
++ if (ioStr[strOffset + nameLen] != '=')
++ return;
++ const int codeOffsetBegin = strOffset + nameLen + 1;
++ int codeOffset = 0;
++ while (ioStr[codeOffsetBegin + codeOffset] != ';')
++ codeOffset++;
++ const QByteArray val = ioStr.mid(codeOffsetBegin, codeOffset);
++ if (val == QByteArrayView("Burley"))
++ setDiffuseModel(inKeySet, QSSGRenderDefaultMaterial::MaterialDiffuseModel::Burley);
++ if (val == QByteArrayView("Lambert"))
++ setDiffuseModel(inKeySet, QSSGRenderDefaultMaterial::MaterialDiffuseModel::Lambert);
++ if (val == QByteArrayView("LambertWrap"))
++ setDiffuseModel(inKeySet, QSSGRenderDefaultMaterial::MaterialDiffuseModel::LambertWrap);
++ }
++ }
++ };
++
++ struct QSSGShaderKeyShadowSoftness : QSSGShaderKeyUnsigned<2>
++ {
++ // possible values:
++ // SoftShadowQuality::Hard : 00
++ // SoftShadowQuality::PCF4 : 01
++ // SoftShadowQuality::PCF8 : 10
++ // SoftShadowQuality::PCF16 : 11
++ // All others map to PCF16
++
++ explicit QSSGShaderKeyShadowSoftness(const char *inName = "") : QSSGShaderKeyUnsigned<2>(inName) {}
++
++ void setShadowSoftness(QSSGDataRef<quint32> inKeySet, QSSGRenderLight::SoftShadowQuality inQuality)
++ {
++ quint32 val = 0;
++ switch (inQuality) {
++ case QSSGRenderLight::SoftShadowQuality::Hard:
++ val = 0;
++ break;
++ case QSSGRenderLight::SoftShadowQuality::PCF4:
++ val = 1;
++ break;
++ case QSSGRenderLight::SoftShadowQuality::PCF8:
++ val = 2;
++ break;
++ case QSSGRenderLight::SoftShadowQuality::PCF16:
++ default:
++ val = 3;
++ break;
++ }
++ setValue(inKeySet, val);
++ }
++
++ QSSGRenderLight::SoftShadowQuality getShadowSoftness(QSSGDataView<quint32> inKeySet) const
++ {
++ quint32 val = getValue(inKeySet);
++ switch (val) {
++ case 0:
++ return QSSGRenderLight::SoftShadowQuality::Hard;
++ case 1:
++ return QSSGRenderLight::SoftShadowQuality::PCF4;
++ case 2:
++ return QSSGRenderLight::SoftShadowQuality::PCF8;
++ case 3:
++ default:
++ return QSSGRenderLight::SoftShadowQuality::PCF16;
++ }
++ }
++
++ void toString(QByteArray &ioStr, QSSGDataView<quint32> inKeySet) const
++ {
++ ioStr.append(name);
++ ioStr.append('=');
++ switch (getShadowSoftness(inKeySet)) {
++ case QSSGRenderLight::SoftShadowQuality::Hard:
++ ioStr.append(QByteArrayView("Hard"));
++ break;
++ case QSSGRenderLight::SoftShadowQuality::PCF4:
++ ioStr.append(QByteArrayView("PCF4"));
++ break;
++ case QSSGRenderLight::SoftShadowQuality::PCF8:
++ ioStr.append(QByteArrayView("PCF8"));
++ break;
++ case QSSGRenderLight::SoftShadowQuality::PCF16:
++ case QSSGRenderLight::SoftShadowQuality::PCF32:
++ case QSSGRenderLight::SoftShadowQuality::PCF64:
++ ioStr.append(QByteArrayView("PCF16"));
++ break;
++ }
++ }
++
++ void fromString(const QByteArray &ioStr, QSSGDataRef<quint32> inKeySet)
++ {
++ const qsizetype nameLen = name.size();
++ const int strOffset = ioStr.indexOf(name);
++ if (strOffset >= 0) {
++ /* The key is stored as name=shadowSoftness; */
++ if (ioStr[strOffset + nameLen] != '=')
++ return;
++ const int codeOffsetBegin = strOffset + nameLen + 1;
++ int codeOffset = 0;
++ while (ioStr[codeOffsetBegin + codeOffset] != ';')
++ codeOffset++;
++ const QByteArray val = ioStr.mid(codeOffsetBegin, codeOffset);
++ if (val == QByteArrayView("Hard"))
++ setShadowSoftness(inKeySet, QSSGRenderLight::SoftShadowQuality::Hard);
++ if (val == QByteArrayView("PCF4"))
++ setShadowSoftness(inKeySet, QSSGRenderLight::SoftShadowQuality::PCF4);
++ if (val == QByteArrayView("PCF8"))
++ setShadowSoftness(inKeySet, QSSGRenderLight::SoftShadowQuality::PCF8);
++ if (val == QByteArrayView("PCF16"))
++ setShadowSoftness(inKeySet, QSSGRenderLight::SoftShadowQuality::PCF16);
++ }
++ }
++
++ };
++
++ struct QSSGShaderKeyAlphaMode : QSSGShaderKeyUnsigned<2>
++ {
++ explicit QSSGShaderKeyAlphaMode(const char *inName = "") : QSSGShaderKeyUnsigned<2>(inName) {}
++
++ void setAlphaMode(QSSGDataRef<quint32> inKeySet, QSSGRenderDefaultMaterial::MaterialAlphaMode inMode)
++ {
++ setValue(inKeySet, quint32(inMode));
++ }
++
++ QSSGRenderDefaultMaterial::MaterialAlphaMode getAlphaMode(QSSGDataView<quint32> inKeySet) const
++ {
++ return static_cast<QSSGRenderDefaultMaterial::MaterialAlphaMode>(getValue(inKeySet));
++ }
++
++ void toString(QByteArray &ioStr, QSSGDataView<quint32> inKeySet) const
++ {
++ ioStr.append(name);
++ ioStr.append('=');
++ switch (getAlphaMode(inKeySet)) {
++ case QSSGRenderDefaultMaterial::MaterialAlphaMode::Default:
++ ioStr.append(QByteArrayView("Default"));
++ break;
++ case QSSGRenderDefaultMaterial::MaterialAlphaMode::Mask:
++ ioStr.append(QByteArrayView("Mask"));
++ break;
++ case QSSGRenderDefaultMaterial::MaterialAlphaMode::Blend:
++ ioStr.append(QByteArrayView("Blend"));
++ break;
++ case QSSGRenderDefaultMaterial::MaterialAlphaMode::Opaque:
++ ioStr.append(QByteArrayView("Opaque"));
++ break;
++ }
++ }
++ void fromString(const QByteArray &ioStr, QSSGDataRef<quint32> inKeySet)
++ {
++ const qsizetype nameLen = name.size();
++ const qsizetype strOffset = ioStr.indexOf(name);
++ if (strOffset >= 0) {
++ /* The key is stored as name=alphaMode; */
++ if (ioStr[strOffset + nameLen] != '=')
++ return;
++ const int codeOffsetBegin = strOffset + nameLen + 1;
++ int codeOffset = 0;
++ while (ioStr[codeOffsetBegin + codeOffset] != ';')
++ codeOffset++;
++ const QByteArray val = ioStr.mid(codeOffsetBegin, codeOffset);
++ if (val == QByteArrayView("Default"))
++ setAlphaMode(inKeySet, QSSGRenderDefaultMaterial::MaterialAlphaMode::Default);
++ if (val == QByteArrayView("Mask"))
++ setAlphaMode(inKeySet, QSSGRenderDefaultMaterial::MaterialAlphaMode::Mask);
++ if (val == QByteArrayView("Blend"))
++ setAlphaMode(inKeySet, QSSGRenderDefaultMaterial::MaterialAlphaMode::Blend);
++ if (val == QByteArrayView("Opaque"))
++ setAlphaMode(inKeySet, QSSGRenderDefaultMaterial::MaterialAlphaMode::Opaque);
++ }
++ }
++ };
++
++ struct QSSGShaderKeyVertexAttribute : public QSSGShaderKeyUnsigned<9>
++ {
++ enum VertexAttributeBits {
++ Position = 1 << 0,
++ Normal = 1 << 1,
++ TexCoord0 = 1 << 2,
++ TexCoord1 = 1 << 3,
++ Tangent = 1 << 4,
++ Binormal = 1 << 5,
++ Color = 1 << 6,
++ JointAndWeight = 1 << 7,
++ TexCoordLightmap = 1 << 8
++ };
++
++ explicit QSSGShaderKeyVertexAttribute(const char *inName = "") : QSSGShaderKeyUnsigned<9>(inName) {}
++
++ bool getBitValue(VertexAttributeBits bit, QSSGDataView<quint32> inKeySet) const
++ {
++ return (getValue(inKeySet) & bit) ? true : false;
++ }
++ void setBitValue(VertexAttributeBits bit, QSSGDataRef<quint32> inKeySet, bool value) const
++ {
++ quint32 v = getValue(inKeySet);
++ v = value ? (v | bit) : (v & ~bit);
++ setValue(inKeySet, v);
++ }
++
++ void toString(QByteArray &ioStr, QSSGDataView<quint32> inKeySet) const
++ {
++ ioStr.append(name);
++ ioStr.append(QByteArrayView("={"));
++ internalToString(ioStr, QByteArrayView("position"), getBitValue(Position, inKeySet));
++ ioStr.append(';');
++ internalToString(ioStr, QByteArrayView("normal"), getBitValue(Normal, inKeySet));
++ ioStr.append(';');
++ internalToString(ioStr, QByteArrayView("texcoord0"), getBitValue(TexCoord0, inKeySet));
++ ioStr.append(';');
++ internalToString(ioStr, QByteArrayView("texcoord1"), getBitValue(TexCoord1, inKeySet));
++ ioStr.append(';');
++ internalToString(ioStr, QByteArrayView("tangent"), getBitValue(Tangent, inKeySet));
++ ioStr.append(';');
++ internalToString(ioStr, QByteArrayView("binormal"), getBitValue(Binormal, inKeySet));
++ ioStr.append(';');
++ internalToString(ioStr, QByteArrayView("color"), getBitValue(Color, inKeySet));
++ ioStr.append(';');
++ internalToString(ioStr, QByteArrayView("texcoordlightmap"), getBitValue(TexCoordLightmap, inKeySet));
++ ioStr.append(';');
++ internalToString(ioStr, QByteArrayView("joint&weight"), getBitValue(JointAndWeight, inKeySet));
++ ioStr.append('}');
++ }
++ void fromString(const QByteArray &ioStr, QSSGDataRef<quint32> inKeySet)
++ {
++ const qsizetype nameLen = name.size();
++ const qsizetype strOffset = ioStr.indexOf(name);
++ if (strOffset >= 0) {
++ /* The key is stored as name={;;;;;;} */
++ if (ioStr[strOffset + nameLen] != '=')
++ return;
++ if (ioStr[strOffset + nameLen + 1] != '{')
++ return;
++ const int codeOffsetBegin = strOffset + nameLen + 2;
++ int codeOffset = 0;
++ while (ioStr[codeOffsetBegin + codeOffset] != '}')
++ codeOffset++;
++ const QByteArray val = ioStr.mid(codeOffsetBegin, codeOffset);
++ const QVector<QByteArray> list = val.split(';');
++ if (list.size() != 8)
++ return;
++ setBitValue(Position, inKeySet, getBoolValue(list[0], QByteArrayView("position")));
++ setBitValue(Normal, inKeySet, getBoolValue(list[1], QByteArrayView("normal")));
++ setBitValue(TexCoord0, inKeySet, getBoolValue(list[2], QByteArrayView("texcoord0")));
++ setBitValue(TexCoord1, inKeySet, getBoolValue(list[3], QByteArrayView("texcoord1")));
++ setBitValue(Tangent, inKeySet, getBoolValue(list[4], QByteArrayView("tangent")));
++ setBitValue(Binormal, inKeySet, getBoolValue(list[5], QByteArrayView("binormal")));
++ setBitValue(Color, inKeySet, getBoolValue(list[6], QByteArrayView("color")));
++ setBitValue(TexCoordLightmap, inKeySet, getBoolValue(list[7], QByteArrayView("texcoordlightmap")));
++ }
++ }
++ };
++
++ struct QSSGShaderDefaultMaterialKeyProperties
++ {
++ enum {
++ SingleChannelImageCount = 16,
++ };
++ enum ImageMapNames {
++ DiffuseMap = 0,
++ BumpMap,
++ SpecularMap,
++ NormalMap,
++ ClearcoatNormalMap,
++ SheenColorMap,
++ AnisotropyMap,
++ // single channel images
++ OpacityMap,
++ RoughnessMap,
++ MetalnessMap,
++ OcclusionMap,
++ TranslucencyMap,
++ HeightMap,
++ ClearcoatMap,
++ ClearcoatRoughnessMap,
++ TransmissionMap,
++ ThicknessMap,
++ BaseColorMap,
++ SpecularAmountMap,
++ EmissiveMap,
++ SheenRoughnessMap,
++ IridescenceMap,
++ IridescenceThicknessMap,
++
++ ImageMapCount,
++ SingleChannelImagesFirst = OpacityMap
++ };
++ enum ImageChannelNames {
++ OpacityChannel = 0,
++ RoughnessChannel,
++ MetalnessChannel,
++ OcclusionChannel,
++ TranslucencyChannel,
++ HeightChannel,
++ ClearcoatChannel,
++ ClearcoatRoughnessChannel,
++ TransmissionChannel,
++ ThicknessChannel,
++ BaseColorChannel,
++ SpecularAmountChannel,
++ EmissiveChannel,
++ SheenRoughnessChannel,
++ IridescenceChannel,
++ IridescenceThicknessChannel,
++ };
++
++ QSSGShaderKeyBoolean m_hasLighting;
++ QSSGShaderKeyBoolean m_hasPunctualLights;
++ QSSGShaderKeyBoolean m_hasShadows;
++ QSSGShaderKeyBoolean m_hasIbl;
++ QSSGShaderKeyBoolean m_specularEnabled;
++ QSSGShaderKeyBoolean m_fresnelScaleBiasEnabled;
++ QSSGShaderKeyBoolean m_clearcoatFresnelScaleBiasEnabled;
++ QSSGShaderKeyBoolean m_fresnelEnabled;
++ QSSGShaderKeyBoolean m_baseColorSingleChannelEnabled;
++ QSSGShaderKeyBoolean m_specularSingleChannelEnabled;
++ QSSGShaderKeyBoolean m_emissiveSingleChannelEnabled;
++ QSSGShaderKeyBoolean m_invertOpacityMapValue;
++ QSSGShaderKeyBoolean m_vertexColorsEnabled;
++ QSSGShaderKeyBoolean m_vertexColorsMaskEnabled;
++ QSSGShaderKeyUnsigned<16> m_vertexColorRedMask;
++ QSSGShaderKeyUnsigned<16> m_vertexColorGreenMask;
++ QSSGShaderKeyUnsigned<16> m_vertexColorBlueMask;
++ QSSGShaderKeyUnsigned<16> m_vertexColorAlphaMask;
++ QSSGShaderKeySpecularModel m_specularModel;
++ QSSGShaderKeyDiffuseModel m_diffuseModel;
++ QSSGShaderKeyImageMap m_imageMaps[ImageMapCount];
++ QSSGShaderKeyTextureChannel m_textureChannels[SingleChannelImageCount];
++ QSSGShaderKeyUnsigned<16> m_boneCount;
++ QSSGShaderKeyBoolean m_isDoubleSided;
++ QSSGShaderKeyBoolean m_overridesPosition;
++ QSSGShaderKeyBoolean m_usesProjectionMatrix;
++ QSSGShaderKeyBoolean m_usesInverseProjectionMatrix;
++ QSSGShaderKeyBoolean m_usesPointsTopology;
++ QSSGShaderKeyBoolean m_usesVarColor;
++ QSSGShaderKeyAlphaMode m_alphaMode;
++ QSSGShaderKeyVertexAttribute m_vertexAttributes;
++ QSSGShaderKeyBoolean m_usesFloatJointIndices;
++ qsizetype m_stringBufferSizeHint = 0;
++ QSSGShaderKeyBoolean m_usesInstancing;
++ QSSGShaderKeyUnsigned<8> m_targetCount;
++ QSSGShaderKeyUnsigned<8> m_targetPositionOffset;
++ QSSGShaderKeyUnsigned<8> m_targetNormalOffset;
++ QSSGShaderKeyUnsigned<8> m_targetTangentOffset;
++ QSSGShaderKeyUnsigned<8> m_targetBinormalOffset;
++ QSSGShaderKeyUnsigned<8> m_targetTexCoord0Offset;
++ QSSGShaderKeyUnsigned<8> m_targetTexCoord1Offset;
++ QSSGShaderKeyUnsigned<8> m_targetColorOffset;
++ QSSGShaderKeyBoolean m_blendParticles;
++ QSSGShaderKeyBoolean m_clearcoatEnabled;
++ QSSGShaderKeyBoolean m_sheenEnabled;
++ QSSGShaderKeyBoolean m_anisotropyEnabled;
++ QSSGShaderKeyBoolean m_iridescenceEnabled;
++ QSSGShaderKeyBoolean m_dispersionEnabled;
++ QSSGShaderKeyBoolean m_transmissionEnabled;
++ QSSGShaderKeyBoolean m_specularAAEnabled;
++ QSSGShaderKeyBoolean m_lightmapEnabled;
++ QSSGShaderKeyBoolean m_specularGlossyEnabled;
++ QSSGShaderKeyBoolean m_metallicRoughnessEnabled;
++ QSSGShaderKeyUnsigned<4> m_debugMode;
++ QSSGShaderKeyBoolean m_fogEnabled;
++ QSSGShaderKeyUnsigned<3> m_viewCount;
++ QSSGShaderKeyBoolean m_usesViewIndex;
++ QSSGShaderKeyBoolean m_oitMSAA;
++ QSSGShaderKeyUnsigned<3> m_orderIndependentTransparency;
++ QSSGShaderKeyShadowSoftness m_shadowSoftness;
++
++ QSSGShaderDefaultMaterialKeyProperties()
++ : m_hasLighting("hasLighting")
++ , m_hasPunctualLights("hasPunctualLights")
++ , m_hasShadows("hasShadows")
++ , m_hasIbl("hasIbl")
++ , m_specularEnabled("specularEnabled")
++ , m_fresnelScaleBiasEnabled("fresnelScaleBiasEnabled")
++ , m_clearcoatFresnelScaleBiasEnabled("clearcoatFresnelScaleBiasEnabled")
++ , m_fresnelEnabled("fresnelEnabled")
++ , m_baseColorSingleChannelEnabled("baseColorSingleChannelEnabled")
++ , m_specularSingleChannelEnabled("specularSingleChannelEnabled")
++ , m_emissiveSingleChannelEnabled("emissiveSingleChannelEnabled")
++ , m_invertOpacityMapValue("invertOpacityMapValue")
++ , m_vertexColorsEnabled("vertexColorsEnabled")
++ , m_vertexColorsMaskEnabled("vertexColorsMaskEnabled")
++ , m_vertexColorRedMask("vertexColorRedMask")
++ , m_vertexColorGreenMask("vertexColorGreenMask")
++ , m_vertexColorBlueMask("vertexColorBlueMask")
++ , m_vertexColorAlphaMask("vertexColorAlphaMask")
++ , m_specularModel("specularModel")
++ , m_diffuseModel("diffuseModel")
++ , m_boneCount("boneCount")
++ , m_isDoubleSided("isDoubleSided")
++ , m_overridesPosition("overridesPosition")
++ , m_usesProjectionMatrix("usesProjectionMatrix")
++ , m_usesInverseProjectionMatrix("usesInverseProjectionMatrix")
++ , m_usesPointsTopology("usesPointsTopology")
++ , m_usesVarColor("usesVarColor")
++ , m_alphaMode("alphaMode")
++ , m_vertexAttributes("vertexAttributes")
++ , m_usesFloatJointIndices("usesFloatJointIndices")
++ , m_usesInstancing("usesInstancing")
++ , m_targetCount("targetCount")
++ , m_targetPositionOffset("targetPositionOffset")
++ , m_targetNormalOffset("targetNormalOffset")
++ , m_targetTangentOffset("targetTangentOffset")
++ , m_targetBinormalOffset("targetBinormalOffset")
++ , m_targetTexCoord0Offset("targetTexCoord0Offset")
++ , m_targetTexCoord1Offset("targetTexCoord1Offset")
++ , m_targetColorOffset("targetColorOffset")
++ , m_blendParticles("blendParticles")
++ , m_clearcoatEnabled("clearcoatEnabled")
++ , m_sheenEnabled("sheenEnabled")
++ , m_anisotropyEnabled("anisotropyEnabled")
++ , m_iridescenceEnabled("iridescenceEnabled")
++ , m_dispersionEnabled("dispersionEnabled")
++ , m_transmissionEnabled("transmissionEnabled")
++ , m_specularAAEnabled("specularAAEnabled")
++ , m_lightmapEnabled("lightmapEnabled")
++ , m_specularGlossyEnabled("specularGlossyEnabled")
++ , m_metallicRoughnessEnabled("metallicRoughnessEnabled")
++ , m_debugMode("debugMode")
++ , m_fogEnabled("fogEnabled")
++ , m_viewCount("viewCount")
++ , m_usesViewIndex("usesViewIndex")
++ , m_oitMSAA("oitMSAA")
++ , m_orderIndependentTransparency("orderIndependentTransparency")
++ , m_shadowSoftness("shadowSoftness")
++ {
++ m_imageMaps[DiffuseMap].name = "diffuseMap";
++ m_imageMaps[BumpMap].name = "bumpMap";
++ m_imageMaps[SpecularMap].name = "specularMap";
++ m_imageMaps[NormalMap].name = "normalMap";
++ m_imageMaps[ClearcoatNormalMap].name = "clearcoatNormalMap";
++ m_imageMaps[SheenColorMap].name = "sheenColorMap";
++ m_imageMaps[AnisotropyMap].name = "anisotropyMap";
++ m_imageMaps[OpacityMap].name = "opacityMap";
++ m_imageMaps[RoughnessMap].name = "roughnessMap";
++ m_imageMaps[MetalnessMap].name = "metalnessMap";
++ m_imageMaps[OcclusionMap].name = "occlusionMap";
++ m_imageMaps[TranslucencyMap].name = "translucencyMap";
++ m_imageMaps[HeightMap].name = "heightMap";
++ m_imageMaps[ClearcoatMap].name = "clearcoatMap";
++ m_imageMaps[ClearcoatRoughnessMap].name = "clearcoatRoughnessMap";
++ m_imageMaps[TransmissionMap].name = "transmissionMap";
++ m_imageMaps[ThicknessMap].name = "thicknessMap";
++ m_imageMaps[BaseColorMap].name = "baseColorMap";
++ m_imageMaps[SpecularAmountMap].name = "specularAmountMap";
++ m_imageMaps[EmissiveMap].name = "emissiveMap";
++ m_imageMaps[SheenRoughnessMap].name = "sheenRoughnessMap";
++ m_imageMaps[IridescenceMap].name = "iridescenceMap";
++ m_imageMaps[IridescenceThicknessMap].name = "iridescenceThicknessMap";
++
++ m_textureChannels[OpacityChannel].name = "opacityMap_channel";
++ m_textureChannels[RoughnessChannel].name = "roughnessMap_channel";
++ m_textureChannels[MetalnessChannel].name = "metalnessMap_channel";
++ m_textureChannels[OcclusionChannel].name = "occlusionMap_channel";
++ m_textureChannels[TranslucencyChannel].name = "translucencyMap_channel";
++ m_textureChannels[HeightChannel].name = "heightMap_channel";
++ m_textureChannels[ClearcoatChannel].name = "clearcoatMap_channel";
++ m_textureChannels[ClearcoatRoughnessChannel].name = "clearcoatRoughnessMap_channel";
++ m_textureChannels[TransmissionChannel].name = "transmissionMap_channel";
++ m_textureChannels[ThicknessChannel].name = "thicknessMap_channel";
++ m_textureChannels[BaseColorChannel].name = "baseColorMap_channel";
++ m_textureChannels[SpecularAmountChannel].name = "specularAmountMap_channel";
++ m_textureChannels[EmissiveChannel].name = "emissiveMap_channel";
++ m_textureChannels[SheenRoughnessChannel].name = "sheenRoughnessMap_channel";
++ m_textureChannels[IridescenceChannel].name = "iridescenceMap_channel";
++ m_textureChannels[IridescenceThicknessChannel].name = "iridescenceThicknessMap_channel";
++
++ init();
++ }
++
++ template<typename TVisitor>
++ void visitProperties(TVisitor &inVisitor)
++ {
++ inVisitor.visit(m_hasLighting);
++ inVisitor.visit(m_hasPunctualLights);
++ inVisitor.visit(m_hasShadows);
++ inVisitor.visit(m_hasIbl);
++ inVisitor.visit(m_specularEnabled);
++ inVisitor.visit(m_fresnelEnabled);
++ inVisitor.visit(m_fresnelScaleBiasEnabled);
++ inVisitor.visit(m_clearcoatFresnelScaleBiasEnabled);
++ inVisitor.visit(m_baseColorSingleChannelEnabled);
++ inVisitor.visit(m_specularSingleChannelEnabled);
++ inVisitor.visit(m_emissiveSingleChannelEnabled);
++ inVisitor.visit(m_invertOpacityMapValue);
++ inVisitor.visit(m_vertexColorsEnabled);
++ inVisitor.visit(m_vertexColorsMaskEnabled);
++ inVisitor.visit(m_vertexColorRedMask);
++ inVisitor.visit(m_vertexColorGreenMask);
++ inVisitor.visit(m_vertexColorBlueMask);
++ inVisitor.visit(m_vertexColorAlphaMask);
++ inVisitor.visit(m_specularModel);
++ inVisitor.visit(m_diffuseModel);
++
++ for (quint32 idx = 0, end = ImageMapCount; idx < end; ++idx)
++ inVisitor.visit(m_imageMaps[idx]);
++
++ for (auto &textureChannel : m_textureChannels)
++ inVisitor.visit(textureChannel);
++
++ inVisitor.visit(m_boneCount);
++ inVisitor.visit(m_isDoubleSided);
++ inVisitor.visit(m_overridesPosition);
++ inVisitor.visit(m_usesProjectionMatrix);
++ inVisitor.visit(m_usesInverseProjectionMatrix);
++ inVisitor.visit(m_usesPointsTopology);
++ inVisitor.visit(m_usesVarColor);
++ inVisitor.visit(m_alphaMode);
++ inVisitor.visit(m_vertexAttributes);
++ inVisitor.visit(m_usesFloatJointIndices);
++ inVisitor.visit(m_usesInstancing);
++ inVisitor.visit(m_targetCount);
++ inVisitor.visit(m_targetPositionOffset);
++ inVisitor.visit(m_targetNormalOffset);
++ inVisitor.visit(m_targetTangentOffset);
++ inVisitor.visit(m_targetBinormalOffset);
++ inVisitor.visit(m_targetTexCoord0Offset);
++ inVisitor.visit(m_targetTexCoord1Offset);
++ inVisitor.visit(m_targetColorOffset);
++ inVisitor.visit(m_blendParticles);
++ inVisitor.visit(m_clearcoatEnabled);
++ inVisitor.visit(m_sheenEnabled);
++ inVisitor.visit(m_anisotropyEnabled);
++ inVisitor.visit(m_iridescenceEnabled);
++ inVisitor.visit(m_dispersionEnabled);
++ inVisitor.visit(m_transmissionEnabled);
++ inVisitor.visit(m_specularAAEnabled);
++ inVisitor.visit(m_lightmapEnabled);
++ inVisitor.visit(m_specularGlossyEnabled);
++ inVisitor.visit(m_metallicRoughnessEnabled);
++ inVisitor.visit(m_debugMode);
++ inVisitor.visit(m_fogEnabled);
++ inVisitor.visit(m_viewCount);
++ inVisitor.visit(m_usesViewIndex);
++ inVisitor.visit(m_oitMSAA);
++ inVisitor.visit(m_orderIndependentTransparency);
++ inVisitor.visit(m_shadowSoftness);
++ }
++
++ struct OffsetVisitor
++ {
++ quint32 m_offset;
++ OffsetVisitor() : m_offset(0) {}
++ template<typename TPropType>
++ void visit(TPropType &inProp)
++ {
++ // if we cross the 32 bit border we just move
++ // to the next dword.
++ // This cost a few extra bits but prevents tedious errors like
++ // loosing shader key bits because they got moved beyond the 32 border
++ quint32 bit = m_offset % 32;
++ if (bit + TPropType::BitWidth > 32) {
++ m_offset += 32 - bit;
++ }
++
++ inProp.setOffset(m_offset);
++ m_offset += TPropType::BitWidth;
++ }
++ };
++
++ struct StringSizeVisitor
++ {
++ qsizetype size = 0;
++ template<typename P>
++ constexpr void visit(const P &prop)
++ {
++ size += prop.name.size();
++ }
++ };
++
++ struct InitVisitor
++ {
++ OffsetVisitor offsetVisitor;
++ StringSizeVisitor stringSizeVisitor;
++
++ template<typename P>
++ void visit(P &prop)
++ {
++ offsetVisitor.visit(prop);
++ stringSizeVisitor.visit(prop);
++ }
++ };
++
++ void init()
++ {
++ InitVisitor visitor;
++ visitProperties(visitor);
++
++ // If this assert fires, then the default material key needs more bits.
++ Q_ASSERT(visitor.offsetVisitor.m_offset < 800);
++ // This is so we can do some guestimate of how big the string buffer needs
++ // to be to avoid doing a lot of allocations when concatenating the strings.
++ m_stringBufferSizeHint = visitor.stringSizeVisitor.size;
++ }
++ };
++
++ template <typename Props, size_t size>
++ struct QSSGShaderBaseMaterialKey
++ {
++ enum {
++ DataBufferSize = size,
++ };
++ quint32 m_dataBuffer[DataBufferSize];
++ size_t m_featureSetHash;
++
++ explicit QSSGShaderBaseMaterialKey(size_t inFeatureSetHash) : m_featureSetHash(inFeatureSetHash)
++ {
++ for (size_t idx = 0; idx < DataBufferSize; ++idx)
++ m_dataBuffer[idx] = 0;
++ }
++
++ QSSGShaderBaseMaterialKey() : m_featureSetHash(0)
++ {
++ for (size_t idx = 0; idx < DataBufferSize; ++idx)
++ m_dataBuffer[idx] = 0;
++ }
++
++ size_t hash() const
++ {
++ size_t retval = 0;
++ for (size_t idx = 0; idx < DataBufferSize; ++idx)
++ retval = retval ^ qHash(m_dataBuffer[idx]);
++ return retval ^ m_featureSetHash;
++ }
++
++ bool operator==(const QSSGShaderBaseMaterialKey<Props, size> &other) const
++ {
++ bool retval = true;
++ for (size_t idx = 0; idx < DataBufferSize && retval; ++idx)
++ retval = m_dataBuffer[idx] == other.m_dataBuffer[idx];
++ return retval && m_featureSetHash == other.m_featureSetHash;
++ }
++
++ // Cast operators to make getting properties easier.
++ operator QSSGDataRef<quint32>() { return toDataRef(m_dataBuffer, DataBufferSize); }
++ operator QSSGDataView<quint32>() const { return toDataView(m_dataBuffer, DataBufferSize); }
++
++ struct StringVisitor
++ {
++ QByteArray &m_str;
++ QSSGDataView<quint32> m_keyStore;
++ StringVisitor(QByteArray &s, QSSGDataView<quint32> ks) : m_str(s), m_keyStore(ks) {}
++ template<typename TPropType>
++ void visit(const TPropType &prop)
++ {
++ const qsizetype originalSize = m_str.size();
++ if (m_str.size())
++ m_str.append(';');
++ prop.toString(m_str, m_keyStore);
++ // if the only thing we added was the semicolon
++ // then nuke the semicolon
++ if (originalSize && m_str.size() == (originalSize + 1))
++ m_str.resize(originalSize);
++ }
++ };
++
++ struct StringInVisitor
++ {
++ const QByteArray &m_str;
++ QSSGDataRef<quint32> m_keyStore;
++ StringInVisitor(const QByteArray &s, QSSGDataRef<quint32> ks) : m_str(s), m_keyStore(ks) {}
++
++ template<typename TPropType>
++ void visit(TPropType &prop)
++ {
++ prop.fromString(m_str, m_keyStore);
++ }
++ };
++
++ void toString(QByteArray &ioString, const Props &inProperties) const
++ {
++ ioString.reserve(inProperties.m_stringBufferSizeHint);
++ StringVisitor theVisitor(ioString, *this);
++ const_cast<Props &>(inProperties).visitProperties(theVisitor);
++ }
++ void fromString(QByteArray &ioString, Props &inProperties)
++ {
++ StringInVisitor theVisitor(ioString, *this);
++ inProperties.visitProperties(theVisitor);
++ }
++ QByteArray toByteArray() const
++ {
++ QByteArray ret;
++ ret.resize(sizeof(m_dataBuffer));
++ memcpy(ret.data(), m_dataBuffer, sizeof(m_dataBuffer));
++ return ret;
++ }
++ bool fromByteArray(const QByteArray &data) const
++ {
++ if (data.size() != sizeof(m_dataBuffer))
++ return false;
++ memcpy((void *)m_dataBuffer, data.data(), sizeof(m_dataBuffer));
++ return true;
++ }
++ };
++
++ typedef QSSGShaderBaseMaterialKey<QSSGShaderDefaultMaterialKeyProperties, 25> QSSGShaderDefaultMaterialKey;
++
++ Q_STATIC_ASSERT(std::is_trivially_destructible<QSSGShaderDefaultMaterialKey>::value);
++
++
++ inline size_t qHash(const QSSGShaderDefaultMaterialKey &key)
++ {
++ return key.hash();
++ }
++
++ QT_END_NAMESPACE
++
++ #endif