RHI: QShaderDescription storage buffer qualifiers / run time stride

Add storage buffer memory qualifier and run time array stride information
to QShaderDescription::StorageBlock.

Memory qualifiers allow more informed selection of RHI resource buffer
binding (bufferLoad / bufferStore / bufferLoadStore) function.

Run time array stride (for last block member unsized array) allows
packing of buffer data for transfer to / from GPU. Without this
information, applications must infer or guess which packing rules
(std430 / std140) are in use.

Change-Id: I676d7e848afefd40d01cdd463c569b07022b683e
Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
This commit is contained in:
Ben Fletcher 2022-12-06 19:52:59 -08:00
parent 2fe3b0e564
commit 0943b5d65d
6 changed files with 62 additions and 2 deletions

View File

@ -467,6 +467,7 @@ QShader QShader::fromSerialized(const QByteArray &data)
ds >> intVal; ds >> intVal;
d->qsbVersion = intVal; d->qsbVersion = intVal;
if (d->qsbVersion != QShaderPrivate::QSB_VERSION if (d->qsbVersion != QShaderPrivate::QSB_VERSION
&& d->qsbVersion != QShaderPrivate::QSB_VERSION_WITHOUT_EXTENDED_STORAGE_BUFFER_INFO
&& d->qsbVersion != QShaderPrivate::QSB_VERSION_WITHOUT_NATIVE_SHADER_INFO && d->qsbVersion != QShaderPrivate::QSB_VERSION_WITHOUT_NATIVE_SHADER_INFO
&& d->qsbVersion != QShaderPrivate::QSB_VERSION_WITHOUT_SEPARATE_IMAGES_AND_SAMPLERS && d->qsbVersion != QShaderPrivate::QSB_VERSION_WITHOUT_SEPARATE_IMAGES_AND_SAMPLERS
&& d->qsbVersion != QShaderPrivate::QSB_VERSION_WITHOUT_VAR_ARRAYDIMS && d->qsbVersion != QShaderPrivate::QSB_VERSION_WITHOUT_VAR_ARRAYDIMS

View File

@ -24,7 +24,8 @@ QT_BEGIN_NAMESPACE
struct Q_GUI_EXPORT QShaderPrivate struct Q_GUI_EXPORT QShaderPrivate
{ {
static const int QSB_VERSION = 7; static const int QSB_VERSION = 8;
static const int QSB_VERSION_WITHOUT_EXTENDED_STORAGE_BUFFER_INFO = 7;
static const int QSB_VERSION_WITHOUT_NATIVE_SHADER_INFO = 6; static const int QSB_VERSION_WITHOUT_NATIVE_SHADER_INFO = 6;
static const int QSB_VERSION_WITHOUT_SEPARATE_IMAGES_AND_SAMPLERS = 5; 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_WITHOUT_VAR_ARRAYDIMS = 4;

View File

@ -403,6 +403,7 @@ QList<QShaderDescription::PushConstantBlock> QShaderDescription::pushConstantBlo
"blockName": "StuffSsbo", "blockName": "StuffSsbo",
"instanceName": "buf", "instanceName": "buf",
"knownSize": 16, "knownSize": 16,
"runtimeArrayStride": 16
"members": [ "members": [
{ {
"name": "whatever", "name": "whatever",
@ -440,7 +441,10 @@ QList<QShaderDescription::PushConstantBlock> QShaderDescription::pushConstantBlo
\note The size of the last member in the storage block is undefined. This shows \note The size of the last member in the storage block is undefined. This shows
up as \c size 0 and an array dimension of \c{[0]}. The storage block's \c knownSize up as \c size 0 and an array dimension of \c{[0]}. The storage block's \c knownSize
excludes the size of the last member since that will only be known at run time. excludes the size of the last member since that will only be known at run time. The
stride in bytes between array items for a last member with undefined array size is
\c runtimeArrayStride. This value is determined according to the specified buffer
memory layout standard (std140, std430) rules.
\note SSBOs are not available with some graphics APIs, such as, OpenGL 2.x or \note SSBOs are not available with some graphics APIs, such as, OpenGL 2.x or
OpenGL ES older than 3.1. OpenGL ES older than 3.1.
@ -984,6 +988,10 @@ QDebug operator<<(QDebug dbg, const QShaderDescription::StorageBlock &blk)
dbg.nospace() << " binding=" << blk.binding; dbg.nospace() << " binding=" << blk.binding;
if (blk.descriptorSet >= 0) if (blk.descriptorSet >= 0)
dbg.nospace() << " set=" << blk.descriptorSet; dbg.nospace() << " set=" << blk.descriptorSet;
if (blk.runtimeArrayStride)
dbg.nospace() << " runtimeArrayStride=" << blk.runtimeArrayStride;
if (blk.qualifierFlags)
dbg.nospace() << " qualifierFlags=" << blk.qualifierFlags;
dbg.nospace() << ' ' << blk.members << ')'; dbg.nospace() << ' ' << blk.members << ')';
return dbg; return dbg;
} }
@ -1033,6 +1041,8 @@ JSON_KEY(tessellationWindingOrder)
JSON_KEY(tessellationPartitioning) JSON_KEY(tessellationPartitioning)
JSON_KEY(separateImages) JSON_KEY(separateImages)
JSON_KEY(separateSamplers) JSON_KEY(separateSamplers)
JSON_KEY(runtimeArrayStride)
JSON_KEY(qualifierFlags)
#undef JSON_KEY #undef JSON_KEY
static void addDeco(QJsonObject *obj, const QShaderDescription::InOutVariable &v) static void addDeco(QJsonObject *obj, const QShaderDescription::InOutVariable &v)
@ -1190,6 +1200,10 @@ QJsonDocument QShaderDescriptionPrivate::makeDoc()
jstorageBlock[bindingKey()] = b.binding; jstorageBlock[bindingKey()] = b.binding;
if (b.descriptorSet >= 0) if (b.descriptorSet >= 0)
jstorageBlock[setKey()] = b.descriptorSet; jstorageBlock[setKey()] = b.descriptorSet;
if (b.runtimeArrayStride)
jstorageBlock[runtimeArrayStrideKey()] = b.runtimeArrayStride;
if (b.qualifierFlags)
jstorageBlock[qualifierFlagsKey()] = int(b.qualifierFlags);
QJsonArray members; QJsonArray members;
for (const QShaderDescription::BlockVariable &v : b.members) for (const QShaderDescription::BlockVariable &v : b.members)
members.append(blockMemberObject(v)); members.append(blockMemberObject(v));
@ -1324,6 +1338,8 @@ void QShaderDescriptionPrivate::writeToStream(QDataStream *stream)
(*stream) << int(b.members.size()); (*stream) << int(b.members.size());
for (const QShaderDescription::BlockVariable &v : b.members) for (const QShaderDescription::BlockVariable &v : b.members)
serializeBlockMemberVar(stream, v); serializeBlockMemberVar(stream, v);
(*stream) << b.runtimeArrayStride;
(*stream) << b.qualifierFlags;
} }
(*stream) << int(combinedImageSamplers.size()); (*stream) << int(combinedImageSamplers.size());
@ -1498,6 +1514,11 @@ void QShaderDescriptionPrivate::loadFromStream(QDataStream *stream, int version)
storageBlocks[i].members.resize(memberCount); storageBlocks[i].members.resize(memberCount);
for (int memberIdx = 0; memberIdx < memberCount; ++memberIdx) for (int memberIdx = 0; memberIdx < memberCount; ++memberIdx)
storageBlocks[i].members[memberIdx] = deserializeBlockMemberVar(stream, version); storageBlocks[i].members[memberIdx] = deserializeBlockMemberVar(stream, version);
if (version > QShaderPrivate::QSB_VERSION_WITHOUT_EXTENDED_STORAGE_BUFFER_INFO) {
(*stream) >> storageBlocks[i].runtimeArrayStride;
(*stream) >> storageBlocks[i].qualifierFlags;
}
} }
(*stream) >> count; (*stream) >> count;
@ -1694,6 +1715,8 @@ bool operator==(const QShaderDescription::StorageBlock &lhs, const QShaderDescri
&& lhs.knownSize == rhs.knownSize && lhs.knownSize == rhs.knownSize
&& lhs.binding == rhs.binding && lhs.binding == rhs.binding
&& lhs.descriptorSet == rhs.descriptorSet && lhs.descriptorSet == rhs.descriptorSet
&& lhs.runtimeArrayStride == rhs.runtimeArrayStride
&& lhs.qualifierFlags == rhs.qualifierFlags
&& lhs.members == rhs.members; && lhs.members == rhs.members;
} }

View File

@ -170,6 +170,15 @@ public:
}; };
Q_DECLARE_FLAGS(ImageFlags, ImageFlag) Q_DECLARE_FLAGS(ImageFlags, ImageFlag)
enum QualifierFlag {
QualifierReadOnly = 1 << 0,
QualifierWriteOnly = 1 << 1,
QualifierCoherent = 1 << 2,
QualifierVolatile = 1 << 3,
QualifierRestrict = 1 << 4,
};
Q_DECLARE_FLAGS(QualifierFlags, QualifierFlag)
// Optional data (like decorations) usually default to an otherwise invalid value (-1 or 0). This is intentional. // Optional data (like decorations) usually default to an otherwise invalid value (-1 or 0). This is intentional.
struct InOutVariable { struct InOutVariable {
@ -218,6 +227,8 @@ public:
int binding = -1; int binding = -1;
int descriptorSet = -1; int descriptorSet = -1;
QList<BlockVariable> members; QList<BlockVariable> members;
int runtimeArrayStride = 0;
QualifierFlags qualifierFlags;
}; };
QList<InOutVariable> inputVariables() const; QList<InOutVariable> inputVariables() const;
@ -310,6 +321,7 @@ private:
}; };
Q_DECLARE_OPERATORS_FOR_FLAGS(QShaderDescription::ImageFlags) Q_DECLARE_OPERATORS_FOR_FLAGS(QShaderDescription::ImageFlags)
Q_DECLARE_OPERATORS_FOR_FLAGS(QShaderDescription::QualifierFlags)
#ifndef QT_NO_DEBUG_STREAM #ifndef QT_NO_DEBUG_STREAM
Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription &); Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription &);

View File

@ -26,6 +26,7 @@ private slots:
void manualShaderPackCreation(); void manualShaderPackCreation();
void loadV6WithSeparateImagesAndSamplers(); void loadV6WithSeparateImagesAndSamplers();
void loadV7(); void loadV7();
void loadV8();
}; };
static QShader getShader(const QString &name) static QShader getShader(const QString &name)
@ -673,5 +674,27 @@ void tst_QShader::loadV7()
QCOMPARE(QShaderPrivate::get(&frag)->qsbVersion, 7); QCOMPARE(QShaderPrivate::get(&frag)->qsbVersion, 7);
} }
void tst_QShader::loadV8()
{
QShader s = getShader(QLatin1String(":/data/storage_buffer_info_v8.comp.qsb"));
QVERIFY(s.isValid());
QCOMPARE(QShaderPrivate::get(&s)->qsbVersion, 8);
const QList<QShaderKey> availableShaders = s.availableShaders();
QCOMPARE(availableShaders.size(), 5);
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(310, QShaderVersion::GlslEs))));
QVERIFY(availableShaders.contains(QShaderKey(QShader::GlslShader, QShaderVersion(430))));
QCOMPARE(s.description().storageBlocks().size(), 1);
QCOMPARE(s.description().storageBlocks().last().runtimeArrayStride, 4);
QCOMPARE(s.description().storageBlocks().last().qualifierFlags,
QShaderDescription::QualifierFlags(QShaderDescription::QualifierWriteOnly
| QShaderDescription::QualifierRestrict));
}
#include <tst_qshader.moc> #include <tst_qshader.moc>
QTEST_MAIN(tst_QShader) QTEST_MAIN(tst_QShader)