diff --git a/src/gui/rhi/qshader.cpp b/src/gui/rhi/qshader.cpp index 74255dcf5c..b29abe2778 100644 --- a/src/gui/rhi/qshader.cpp +++ b/src/gui/rhi/qshader.cpp @@ -391,6 +391,18 @@ QByteArray QShader::serialized() const ds << mapIt.value().second; } } + ds << int(d->combinedImageMap.count()); + for (auto it = d->combinedImageMap.cbegin(), itEnd = d->combinedImageMap.cend(); it != itEnd; ++it) { + const QShaderKey &k(it.key()); + writeShaderKey(&ds, k); + const SeparateToCombinedImageSamplerMappingList &list(it.value()); + ds << int(list.count()); + for (auto listIt = list.cbegin(), listItEnd = list.cend(); listIt != listItEnd; ++listIt) { + ds << listIt->combinedSamplerName; + ds << listIt->textureBinding; + ds << listIt->samplerBinding; + } + } return qCompress(buf.buffer()); } @@ -431,6 +443,7 @@ QShader QShader::fromSerialized(const QByteArray &data) ds >> intVal; d->qsbVersion = intVal; if (d->qsbVersion != QShaderPrivate::QSB_VERSION + && d->qsbVersion != QShaderPrivate::QSB_VERSION_WITHOUT_SEPARATE_IMAGES_AND_SAMPLERS && d->qsbVersion != QShaderPrivate::QSB_VERSION_WITHOUT_VAR_ARRAYDIMS && d->qsbVersion != QShaderPrivate::QSB_VERSION_WITH_CBOR && d->qsbVersion != QShaderPrivate::QSB_VERSION_WITH_BINARY_JSON @@ -486,6 +499,27 @@ QShader QShader::fromSerialized(const QByteArray &data) } } + if (d->qsbVersion > QShaderPrivate::QSB_VERSION_WITHOUT_SEPARATE_IMAGES_AND_SAMPLERS) { + ds >> count; + for (int i = 0; i < count; ++i) { + QShaderKey k; + readShaderKey(&ds, &k); + SeparateToCombinedImageSamplerMappingList list; + int listSize; + ds >> listSize; + for (int b = 0; b < listSize; ++b) { + QByteArray combinedSamplerName; + ds >> combinedSamplerName; + int textureBinding; + ds >> textureBinding; + int samplerBinding; + ds >> samplerBinding; + list.append({ combinedSamplerName, textureBinding, samplerBinding }); + } + d->combinedImageMap.insert(k, list); + } + } + return bs; } @@ -684,17 +718,20 @@ QDebug operator<<(QDebug dbg, const QShaderVersion &v) \c binding layout qualifier in the Vulkan-compatible GLSL shader. Graphics APIs other than Vulkan may use a resource binding model that is - not fully compatible with this. In addition, the generator of the shader - code translated from SPIR-V may choose not to take the SPIR-V binding - qualifiers into account, for various reasons. (this is the case with the - Metal backend of SPIRV-Cross, for example). + not fully compatible with this. The generator of the shader code translated + from SPIR-V may choose not to take the SPIR-V binding qualifiers into + account, for various reasons. This is the case with the Metal backend of + SPIRV-Cross, for example. In addition, even when an automatic, implicit + translation is mostly possible (e.g. by using SPIR-V binding points as HLSL + resource register indices), assigning resource bindings without being + constrained by the SPIR-V binding points can lead to better results. Therefore, a QShader may expose an additional map that describes what the - native binding point for a given SPIR-V binding is. The QRhi backends are - expected to use this map automatically, as appropriate. The value is a - pair, because combined image samplers may map to two native resources (a - texture and a sampler) in some shading languages. In that case the second - value refers to the sampler. + native binding point for a given SPIR-V binding is. The QRhi backends, for + which this is relevant, are expected to use this map automatically, as + appropriate. The value is a pair, because combined image samplers may map + to two native resources (a texture and a sampler) in some shading + languages. In that case the second value refers to the sampler. \note The native binding may be -1, in case there is no active binding for the resource in the shader. (for example, there is a uniform block @@ -741,4 +778,62 @@ void QShader::removeResourceBindingMap(const QShaderKey &key) d->bindings.erase(it); } +/*! + \typedef QShader::SeparateToCombinedImageSamplerMappingList + + Synonym for QList. + */ + +/*! + \struct QShader::SeparateToCombinedImageSamplerMapping + + Describes a mapping from a traditional combined image sampler uniform to + binding points for a separate texture and sampler. + + For example, if \c combinedImageSampler is \c{"_54"}, \c textureBinding is + \c 1, and \c samplerBinding is \c 2, this means that the GLSL shader code + contains a \c sampler2D (or sampler3D, etc.) uniform with the name of + \c{_54} which corresponds to two separate resource bindings (\c 1 and \c 2) + in the original shader. + */ + +/*! + \return the combined image sampler mapping list for \a key or null if there + is no data available for \a key, for example because such a mapping is not + applicable for the shading language. + */ +const QShader::SeparateToCombinedImageSamplerMappingList *QShader::separateToCombinedImageSamplerMappingList(const QShaderKey &key) const +{ + auto it = d->combinedImageMap.constFind(key); + if (it == d->combinedImageMap.cend()) + return nullptr; + + return &it.value(); +} + +/*! + Stores the given combined image sampler mapping \a list associated with \a key. + + \sa separateToCombinedImageSamplerMappingList() + */ +void QShader::setSeparateToCombinedImageSamplerMappingList(const QShaderKey &key, + const SeparateToCombinedImageSamplerMappingList &list) +{ + detach(); + d->combinedImageMap[key] = list; +} + +/*! + Removes the combined image sampler mapping list for \a key. + */ +void QShader::removeSeparateToCombinedImageSamplerMappingList(const QShaderKey &key) +{ + auto it = d->combinedImageMap.find(key); + if (it == d->combinedImageMap.end()) + return; + + detach(); + d->combinedImageMap.erase(it); +} + QT_END_NAMESPACE diff --git a/src/gui/rhi/qshader_p.h b/src/gui/rhi/qshader_p.h index b320340229..6dca86b1c4 100644 --- a/src/gui/rhi/qshader_p.h +++ b/src/gui/rhi/qshader_p.h @@ -167,6 +167,17 @@ public: void setResourceBindingMap(const QShaderKey &key, const NativeResourceBindingMap &map); void removeResourceBindingMap(const QShaderKey &key); + struct SeparateToCombinedImageSamplerMapping { + QByteArray combinedSamplerName; + int textureBinding; + int samplerBinding; + }; + using SeparateToCombinedImageSamplerMappingList = QList; + const SeparateToCombinedImageSamplerMappingList *separateToCombinedImageSamplerMappingList(const QShaderKey &key) const; + void setSeparateToCombinedImageSamplerMappingList(const QShaderKey &key, + const SeparateToCombinedImageSamplerMappingList &list); + void removeSeparateToCombinedImageSamplerMappingList(const QShaderKey &key); + private: QShaderPrivate *d; friend struct QShaderPrivate; diff --git a/src/gui/rhi/qshader_p_p.h b/src/gui/rhi/qshader_p_p.h index 2f6afb4a6d..51c3e29d45 100644 --- a/src/gui/rhi/qshader_p_p.h +++ b/src/gui/rhi/qshader_p_p.h @@ -60,7 +60,8 @@ QT_BEGIN_NAMESPACE struct Q_GUI_EXPORT QShaderPrivate { - static const int QSB_VERSION = 5; + static const int QSB_VERSION = 6; + static const int QSB_VERSION_WITHOUT_SEPARATE_IMAGES_AND_SAMPLERS = 5; static const int QSB_VERSION_WITHOUT_VAR_ARRAYDIMS = 4; static const int QSB_VERSION_WITH_CBOR = 3; static const int QSB_VERSION_WITH_BINARY_JSON = 2; @@ -77,7 +78,8 @@ struct Q_GUI_EXPORT QShaderPrivate stage(other->stage), desc(other->desc), shaders(other->shaders), - bindings(other->bindings) + bindings(other->bindings), + combinedImageMap(other->combinedImageMap) { } @@ -90,6 +92,7 @@ struct Q_GUI_EXPORT QShaderPrivate QShaderDescription desc; QHash shaders; QHash bindings; + QHash combinedImageMap; }; QT_END_NAMESPACE diff --git a/src/gui/rhi/qshaderdescription.cpp b/src/gui/rhi/qshaderdescription.cpp index 2c79acb1c7..67d70281e6 100644 --- a/src/gui/rhi/qshaderdescription.cpp +++ b/src/gui/rhi/qshaderdescription.cpp @@ -222,6 +222,7 @@ QT_BEGIN_NAMESPACE \value SamplerRect \value SamplerBuffer \value SamplerExternalOES + \value Sampler For separate samplers. \value Image1D \value Image2D \value Image2DMS @@ -336,7 +337,8 @@ bool QShaderDescription::isValid() const { return !d->inVars.isEmpty() || !d->outVars.isEmpty() || !d->uniformBlocks.isEmpty() || !d->pushConstantBlocks.isEmpty() || !d->storageBlocks.isEmpty() - || !d->combinedImageSamplers.isEmpty() || !d->storageImages.isEmpty(); + || !d->combinedImageSamplers.isEmpty() || !d->storageImages.isEmpty() + || !d->separateImages.isEmpty() || !d->separateSamplers.isEmpty(); } /*! @@ -510,6 +512,16 @@ QList QShaderDescription::combinedImageSample return d->combinedImageSamplers; } +QList QShaderDescription::separateImages() const +{ + return d->separateImages; +} + +QList QShaderDescription::separateSamplers() const +{ + return d->separateSamplers; +} + /*! \return the list of image variables. @@ -579,6 +591,7 @@ static struct TypeTab { { QLatin1String("samplerRect"), QShaderDescription::SamplerRect }, { QLatin1String("samplerBuffer"), QShaderDescription::SamplerBuffer }, { QLatin1String("samplerExternalOES"), QShaderDescription::SamplerExternalOES }, + { QLatin1String("sampler"), QShaderDescription::Sampler }, { QLatin1String("mat2x3"), QShaderDescription::Mat2x3 }, { QLatin1String("mat2x4"), QShaderDescription::Mat2x4 }, @@ -708,7 +721,9 @@ QDebug operator<<(QDebug dbg, const QShaderDescription &sd) << " pcBlocks " << d->pushConstantBlocks << " storageBlocks " << d->storageBlocks << " combinedSamplers " << d->combinedImageSamplers - << " images " << d->storageImages + << " storageImages " << d->storageImages + << " separateImages " << d->separateImages + << " separateSamplers " << d->separateSamplers << ')'; } else { dbg.nospace() << "QShaderDescription(null)"; @@ -818,6 +833,8 @@ static const QString storageBlocksKey = QLatin1String("storageBlocks"); static const QString combinedImageSamplersKey = QLatin1String("combinedImageSamplers"); static const QString storageImagesKey = QLatin1String("storageImages"); static const QString localSizeKey = QLatin1String("localSize"); +static const QString separateImagesKey = QLatin1String("separateImages"); +static const QString separateSamplersKey = QLatin1String("separateSamplers"); static void addDeco(QJsonObject *obj, const QShaderDescription::InOutVariable &v) { @@ -1007,6 +1024,28 @@ QJsonDocument QShaderDescriptionPrivate::makeDoc() jlocalSize.append(QJsonValue(int(localSize[i]))); root[localSizeKey] = jlocalSize; + QJsonArray jseparateImages; + for (const QShaderDescription::InOutVariable &v : qAsConst(separateImages)) { + QJsonObject image; + image[nameKey] = QString::fromUtf8(v.name); + image[typeKey] = typeStr(v.type); + addDeco(&image, v); + jseparateImages.append(image); + } + if (!jseparateImages.isEmpty()) + root[separateImagesKey] = jseparateImages; + + QJsonArray jseparateSamplers; + for (const QShaderDescription::InOutVariable &v : qAsConst(separateSamplers)) { + QJsonObject sampler; + sampler[nameKey] = QString::fromUtf8(v.name); + sampler[typeKey] = typeStr(v.type); + addDeco(&sampler, v); + jseparateSamplers.append(sampler); + } + if (!jseparateSamplers.isEmpty()) + root[separateSamplersKey] = jseparateSamplers; + return QJsonDocument(root); } @@ -1069,6 +1108,20 @@ void QShaderDescriptionPrivate::writeToStream(QDataStream *stream) for (size_t i = 0; i < 3; ++i) (*stream) << localSize[i]; + + (*stream) << int(separateImages.count()); + for (const QShaderDescription::InOutVariable &v : qAsConst(separateImages)) { + (*stream) << QString::fromUtf8(v.name); + (*stream) << int(v.type); + serializeDecorations(stream, v); + } + + (*stream) << int(separateSamplers.count()); + for (const QShaderDescription::InOutVariable &v : qAsConst(separateSamplers)) { + (*stream) << QString::fromUtf8(v.name); + (*stream) << int(v.type); + serializeDecorations(stream, v); + } } static void deserializeDecorations(QDataStream *stream, int version, QShaderDescription::InOutVariable *v) @@ -1220,6 +1273,32 @@ void QShaderDescriptionPrivate::loadFromStream(QDataStream *stream, int version) for (size_t i = 0; i < 3; ++i) (*stream) >> localSize[i]; + + if (version > QShaderPrivate::QSB_VERSION_WITHOUT_SEPARATE_IMAGES_AND_SAMPLERS) { + (*stream) >> count; + separateImages.resize(count); + for (int i = 0; i < count; ++i) { + QString tmp; + (*stream) >> tmp; + separateImages[i].name = tmp.toUtf8(); + int t; + (*stream) >> t; + separateImages[i].type = QShaderDescription::VariableType(t); + deserializeDecorations(stream, version, &separateImages[i]); + } + + (*stream) >> count; + separateSamplers.resize(count); + for (int i = 0; i < count; ++i) { + QString tmp; + (*stream) >> tmp; + separateSamplers[i].name = tmp.toUtf8(); + int t; + (*stream) >> t; + separateSamplers[i].type = QShaderDescription::VariableType(t); + deserializeDecorations(stream, version, &separateSamplers[i]); + } + } } /*! @@ -1239,6 +1318,8 @@ bool operator==(const QShaderDescription &lhs, const QShaderDescription &rhs) no && lhs.d->pushConstantBlocks == rhs.d->pushConstantBlocks && lhs.d->storageBlocks == rhs.d->storageBlocks && lhs.d->combinedImageSamplers == rhs.d->combinedImageSamplers + && lhs.d->separateImages == rhs.d->separateImages + && lhs.d->separateSamplers == rhs.d->separateSamplers && lhs.d->storageImages == rhs.d->storageImages && lhs.d->localSize == rhs.d->localSize; } diff --git a/src/gui/rhi/qshaderdescription_p.h b/src/gui/rhi/qshaderdescription_p.h index edaa964527..347fbfd36d 100644 --- a/src/gui/rhi/qshaderdescription_p.h +++ b/src/gui/rhi/qshaderdescription_p.h @@ -137,6 +137,7 @@ public: SamplerRect, SamplerBuffer, SamplerExternalOES, + Sampler, Image1D, Image2D, @@ -259,6 +260,8 @@ public: QList pushConstantBlocks() const; QList storageBlocks() const; QList combinedImageSamplers() const; + QList separateImages() const; + QList separateSamplers() const; QList storageImages() const; std::array computeShaderLocalSize() const; diff --git a/src/gui/rhi/qshaderdescription_p_p.h b/src/gui/rhi/qshaderdescription_p_p.h index e0bed856b7..0ef7869d7b 100644 --- a/src/gui/rhi/qshaderdescription_p_p.h +++ b/src/gui/rhi/qshaderdescription_p_p.h @@ -74,6 +74,8 @@ struct Q_GUI_EXPORT QShaderDescriptionPrivate pushConstantBlocks(other->pushConstantBlocks), storageBlocks(other->storageBlocks), combinedImageSamplers(other->combinedImageSamplers), + separateImages(other->separateImages), + separateSamplers(other->separateSamplers), storageImages(other->storageImages), localSize(other->localSize) { @@ -93,6 +95,8 @@ struct Q_GUI_EXPORT QShaderDescriptionPrivate QList pushConstantBlocks; QList storageBlocks; QList combinedImageSamplers; + QList separateImages; + QList separateSamplers; QList storageImages; std::array localSize; }; diff --git a/tests/auto/gui/rhi/qshader/data/texture_sep.frag b/tests/auto/gui/rhi/qshader/data/texture_sep.frag new file mode 100644 index 0000000000..368e851bb4 --- /dev/null +++ b/tests/auto/gui/rhi/qshader/data/texture_sep.frag @@ -0,0 +1,17 @@ +#version 440 + +layout(location = 0) in vec2 v_texcoord; + +layout(location = 0) out vec4 fragColor; + +layout(binding = 1) uniform sampler2D combinedTexSampler; +layout(binding = 2) uniform texture2D sepTex; +layout(binding = 3) uniform sampler sepSampler; +layout(binding = 4) uniform sampler sepSampler2; + +void main() +{ + fragColor = texture(sampler2D(sepTex, sepSampler), v_texcoord); + fragColor *= texture(sampler2D(sepTex, sepSampler2), v_texcoord); + fragColor *= texture(combinedTexSampler, v_texcoord); +} diff --git a/tests/auto/gui/rhi/qshader/data/texture_sep_v6.frag.qsb b/tests/auto/gui/rhi/qshader/data/texture_sep_v6.frag.qsb new file mode 100644 index 0000000000..b654ee576d Binary files /dev/null and b/tests/auto/gui/rhi/qshader/data/texture_sep_v6.frag.qsb differ diff --git a/tests/auto/gui/rhi/qshader/tst_qshader.cpp b/tests/auto/gui/rhi/qshader/tst_qshader.cpp index 1b3d861756..587838bc08 100644 --- a/tests/auto/gui/rhi/qshader/tst_qshader.cpp +++ b/tests/auto/gui/rhi/qshader/tst_qshader.cpp @@ -48,6 +48,7 @@ private slots: void comparison(); void loadV4(); void manualShaderPackCreation(); + void loadV6WithSeparateImagesAndSamplers(); }; static QShader getShader(const QString &name) @@ -570,5 +571,39 @@ void tst_QShader::manualShaderPackCreation() QCOMPARE(newShaderPack.shader(QShaderKey(QShader::GlslShader, QShaderVersion(120))).shader(), fs_gl); } +void tst_QShader::loadV6WithSeparateImagesAndSamplers() +{ + QShader s = getShader(QLatin1String(":/data/texture_sep_v6.frag.qsb")); + QVERIFY(s.isValid()); + QCOMPARE(QShaderPrivate::get(&s)->qsbVersion, 6); + + const QList availableShaders = s.availableShaders(); + QCOMPARE(availableShaders.count(), 6); + QVERIFY(availableShaders.contains(QShaderKey(QShader::SpirvShader, QShaderVersion(100)))); + QVERIFY(availableShaders.contains(QShaderKey(QShader::MslShader, QShaderVersion(12)))); + QVERIFY(availableShaders.contains(QShaderKey(QShader::HlslShader, QShaderVersion(50)))); + QVERIFY(availableShaders.contains(QShaderKey(QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs)))); + QVERIFY(availableShaders.contains(QShaderKey(QShader::GlslShader, QShaderVersion(120)))); + QVERIFY(availableShaders.contains(QShaderKey(QShader::GlslShader, QShaderVersion(150)))); + + const QShader::NativeResourceBindingMap *resMap = + s.nativeResourceBindingMap(QShaderKey(QShader::HlslShader, QShaderVersion(50))); + QVERIFY(resMap && resMap->count() == 4); + QVERIFY(!s.separateToCombinedImageSamplerMappingList(QShaderKey(QShader::HlslShader, QShaderVersion(50)))); + resMap = s.nativeResourceBindingMap(QShaderKey(QShader::MslShader, QShaderVersion(12))); + QVERIFY(resMap && resMap->count() == 4); + QVERIFY(!s.separateToCombinedImageSamplerMappingList(QShaderKey(QShader::MslShader, QShaderVersion(12)))); + + for (auto key : { + QShaderKey(QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs)), + QShaderKey(QShader::GlslShader, QShaderVersion(120)), + QShaderKey(QShader::GlslShader, QShaderVersion(150)) }) + { + auto list = s.separateToCombinedImageSamplerMappingList(key); + QVERIFY(list); + QCOMPARE(list->count(), 2); + } +} + #include QTEST_MAIN(tst_QShader)