summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBen Fletcher <ben.fletcher@me.com>2023-02-16 16:46:22 -0800
committerBen Fletcher <ben.fletcher@me.com>2023-03-09 15:24:45 +0000
commit4201cdab1886b5c39fe722a95e1b0ba462b63b71 (patch)
tree242bec9a73e8bb0e09fbdc74c8f2fabd98b7eba1
parent17c9001a53a2377cc38bdf4b1982cab2dca94573 (diff)
rhi: Metal tessellation shader input output interface blocks
Add support for shader input output interface blocks in Metal tessellation pipelines. This feature is builtin to other rhi supported tessellation backends (OpenGL/Vulkan). Metal tessellation is implemented as compute pipelines for vert and tesc, and a render pipeline for tese and frag. The shader conversion from GLSL is handled by SPIRV-Cross, which has a particular way of doing things. Rhi must setup the vertex inputs for the tese - frag render pipeline to read from buffers written by the tesc compute pipeline, following SPIRV-Cross conventions. This includes ensuring correct memory alignment per MSL Specification. In order to enable input output interface blocks, reflection of struct members of QShaderDescription::InOutVariable is required. Reflection of QShaderDescription::BuiltinVariable array dimensions is also required to support variable size tese builtin input gl_ClipDistance. An acompanying patch to QtShaderTools is required. Change-Id: Id94e86caef211485afc187bb79fe3d0619d02cf0 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
-rw-r--r--src/gui/rhi/qrhi.cpp9
-rw-r--r--src/gui/rhi/qrhimetal.mm448
-rw-r--r--src/gui/rhi/qshader.cpp1
-rw-r--r--src/gui/rhi/qshader_p_p.h3
-rw-r--r--src/gui/rhi/qshaderdescription.cpp165
-rw-r--r--src/gui/rhi/qshaderdescription_p.h33
-rw-r--r--tests/auto/gui/rhi/qrhi/data/buildshaders.bat4
-rw-r--r--tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.frag.qsbbin0 -> 590 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tesc56
-rw-r--r--tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tesc.qsbbin0 -> 2873 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tese96
-rw-r--r--tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tese.qsbbin0 -> 4526 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.vert20
-rw-r--r--tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.vert.qsbbin0 -> 1306 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/tst_qrhi.cpp318
15 files changed, 964 insertions, 189 deletions
diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp
index 801a8984f1..3b9c150166 100644
--- a/src/gui/rhi/qrhi.cpp
+++ b/src/gui/rhi/qrhi.cpp
@@ -1405,6 +1405,15 @@ QRhiVertexInputAttribute::Format QRhiImplementation::shaderDescVariableFormatToV
case QShaderDescription::Uint:
return QRhiVertexInputAttribute::UInt;
+ case QShaderDescription::Half4:
+ return QRhiVertexInputAttribute::Half4;
+ case QShaderDescription::Half3:
+ return QRhiVertexInputAttribute::Half3;
+ case QShaderDescription::Half2:
+ return QRhiVertexInputAttribute::Half2;
+ case QShaderDescription::Half:
+ return QRhiVertexInputAttribute::Half;
+
default:
Q_UNREACHABLE_RETURN(QRhiVertexInputAttribute::Float);
}
diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm
index 5cb9ae0a05..2da7d24daf 100644
--- a/src/gui/rhi/qrhimetal.mm
+++ b/src/gui/rhi/qrhimetal.mm
@@ -5013,19 +5013,176 @@ id<MTLComputePipelineState> QMetalGraphicsPipelineData::Tessellation::tescCompPi
return ps;
}
-static inline bool hasBuiltin(const QVector<QShaderDescription::BuiltinVariable> &builtinList, QShaderDescription::BuiltinType builtin)
+static inline bool indexTaken(quint32 index, quint64 indices)
{
- return std::find_if(builtinList.cbegin(), builtinList.cend(),
- [builtin](const QShaderDescription::BuiltinVariable &b) { return b.type == builtin; }) != builtinList.cend();
+ return (indices >> index) & 0x1;
+}
+
+static inline void takeIndex(quint32 index, quint64 &indices)
+{
+ indices |= 1 << index;
+}
+
+static inline int nextAttributeIndex(quint64 indices)
+{
+ // Maximum number of vertex attributes per vertex descriptor. There does
+ // not appear to be a way to query this from the implementation.
+ // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf indicates
+ // that all GPU families have a value of 31.
+ static const int maxVertexAttributes = 31;
+
+ for (int index = 0; index < maxVertexAttributes; ++index) {
+ if (!indexTaken(index, indices))
+ return index;
+ }
+
+ Q_UNREACHABLE_RETURN(-1);
+}
+
+static inline int aligned(quint32 offset, quint32 alignment)
+{
+ return ((offset + alignment - 1) / alignment) * alignment;
+}
+
+template<typename T>
+static void addUnusedVertexAttribute(const T &variable, QRhiMetal *rhiD, quint32 &offset, quint32 &vertexAlignment)
+{
+
+ int elements = 1;
+ for (const int dim : variable.arrayDims)
+ elements *= dim;
+
+ if (variable.type == QShaderDescription::VariableType::Struct) {
+ for (int element = 0; element < elements; ++element) {
+ for (const auto &member : variable.structMembers) {
+ addUnusedVertexAttribute(member, rhiD, offset, vertexAlignment);
+ }
+ }
+ } else {
+ const QRhiVertexInputAttribute::Format format = rhiD->shaderDescVariableFormatToVertexInputFormat(variable.type);
+ const quint32 size = rhiD->byteSizePerVertexForVertexInputFormat(format);
+
+ // MSL specification 3.0 says alignment = size for non packed scalars and vectors
+ const quint32 alignment = size;
+ vertexAlignment = std::max(vertexAlignment, alignment);
+
+ for (int element = 0; element < elements; ++element) {
+ // adjust alignment
+ offset = aligned(offset, alignment);
+ offset += size;
+ }
+ }
+}
+
+template<typename T>
+static void addVertexAttribute(const T &variable, int binding, QRhiMetal *rhiD, int &index, quint32 &offset, MTLVertexAttributeDescriptorArray *attributes, quint64 &indices, quint32 &vertexAlignment)
+{
+
+ int elements = 1;
+ for (const int dim : variable.arrayDims)
+ elements *= dim;
+
+ if (variable.type == QShaderDescription::VariableType::Struct) {
+ for (int element = 0; element < elements; ++element) {
+ for (const auto &member : variable.structMembers) {
+ addVertexAttribute(member, binding, rhiD, index, offset, attributes, indices, vertexAlignment);
+ }
+ }
+ } else {
+ const QRhiVertexInputAttribute::Format format = rhiD->shaderDescVariableFormatToVertexInputFormat(variable.type);
+ const quint32 size = rhiD->byteSizePerVertexForVertexInputFormat(format);
+
+ // MSL specification 3.0 says alignment = size for non packed scalars and vectors
+ const quint32 alignment = size;
+ vertexAlignment = std::max(vertexAlignment, alignment);
+
+ for (int element = 0; element < elements; ++element) {
+ Q_ASSERT(!indexTaken(index, indices));
+
+ // adjust alignment
+ offset = aligned(offset, alignment);
+
+ attributes[index].bufferIndex = binding;
+ attributes[index].format = toMetalAttributeFormat(format);
+ attributes[index].offset = offset;
+
+ takeIndex(index, indices);
+ index++;
+ if (indexTaken(index, indices))
+ index = nextAttributeIndex(indices);
+
+ offset += size;
+ }
+ }
+}
+
+static inline bool matches(const QList<QShaderDescription::BlockVariable> &a, const QList<QShaderDescription::BlockVariable> &b)
+{
+ if (a.size() == b.size()) {
+ bool match = true;
+ for (int i = 0; i < a.size() && match; ++i) {
+ match &= a[i].type == b[i].type
+ && a[i].arrayDims == b[i].arrayDims
+ && matches(a[i].structMembers, b[i].structMembers);
+ }
+ return match;
+ }
+
+ return false;
}
static inline bool matches(const QShaderDescription::InOutVariable &a, const QShaderDescription::InOutVariable &b)
{
return a.location == b.location
&& a.type == b.type
- && a.perPatch == b.perPatch;
-}
-
+ && a.perPatch == b.perPatch
+ && matches(a.structMembers, b.structMembers);
+}
+
+//
+// Create the tessellation evaluation render pipeline state
+//
+// The tesc runs as a compute shader in a compute pipeline and writes per patch and per patch
+// control point data into separate storage buffers. The tese runs as a vertex shader in a render
+// pipeline. Our task is to generate a render pipeline descriptor for the tese that pulls vertices
+// from these buffers.
+//
+// As the buffers we are pulling vertices from are written by a compute pipeline, they follow the
+// MSL alignment conventions which we must take into account when generating our
+// MTLVertexDescriptor. We must include the user defined tese input attributes, and any builtins
+// that were used.
+//
+// SPIRV-Cross generates the MSL tese shader code with input attribute indices that reflect the
+// specified GLSL locations. Interface blocks are flattened with each member having an incremented
+// attribute index. SPIRV-Cross reports an error on compilation if there are clashes in the index
+// address space.
+//
+// After the user specified attributes are processed, SPIRV-Cross places the in-use builtins at the
+// next available (lowest value) attribute index. Tese builtins are processed in the following
+// order:
+//
+// in gl_PerVertex
+// {
+// vec4 gl_Position;
+// float gl_PointSize;
+// float gl_ClipDistance[];
+// };
+//
+// patch in float gl_TessLevelOuter[4];
+// patch in float gl_TessLevelInner[2];
+//
+// Enumerations in QShaderDescription::BuiltinType are defined in this order.
+//
+// For quads, SPIRV-Cross places MTLQuadTessellationFactorsHalf per patch in the tessellation
+// factor buffer. For triangles it uses MTLTriangleTessellationFactorsHalf.
+//
+// It should be noted that SPIRV-Cross handles the following builtin inputs internally, with no
+// host side support required.
+//
+// in vec3 gl_TessCoord;
+// in int gl_PatchVerticesIn;
+// in int gl_PrimitiveID;
+//
id<MTLRenderPipelineState> QMetalGraphicsPipelineData::Tessellation::teseFragRenderPipeline(QRhiMetal *rhiD, QMetalGraphicsPipeline *pipeline)
{
if (pipeline->d->ps)
@@ -5034,154 +5191,191 @@ id<MTLRenderPipelineState> QMetalGraphicsPipelineData::Tessellation::teseFragRen
MTLRenderPipelineDescriptor *rpDesc = [[MTLRenderPipelineDescriptor alloc] init];
MTLVertexDescriptor *vertexDesc = [MTLVertexDescriptor vertexDescriptor];
- // Going to use the same buffer indices for the extra buffers as the tess.control compute shader did.
+ // tesc output buffers
const QMap<int, int> &ebb(compTesc.nativeShaderInfo.extraBufferBindings);
const int tescOutputBufferBinding = ebb.value(QShaderPrivate::MslTessVertTescOutputBufferBinding, -1);
const int tescPatchOutputBufferBinding = ebb.value(QShaderPrivate::MslTessTescPatchOutputBufferBinding, -1);
const int tessFactorBufferBinding = ebb.value(QShaderPrivate::MslTessTescTessLevelBufferBinding, -1);
-
- QMap<int, QShaderDescription::InOutVariable> teseInVars;
- for (const QShaderDescription::InOutVariable &teseInVar : vertTese.desc.inputVariables())
- teseInVars[teseInVar.location] = teseInVar;
-
quint32 offsetInTescOutput = 0;
quint32 offsetInTescPatchOutput = 0;
- int lastLocation = -1;
+ quint32 offsetInTessFactorBuffer = 0;
+ quint32 tescOutputAlignment = 0;
+ quint32 tescPatchOutputAlignment = 0;
+ quint32 tessFactorAlignment = 0;
+ QSet<int> usedBuffers;
- // these need to be sorted in location order so that lastLocation is calculated correctly - use QMap.
+ // tesc output variables in ascending location order
QMap<int, QShaderDescription::InOutVariable> tescOutVars;
- for (const QShaderDescription::InOutVariable &tescOutVar : compTesc.desc.outputVariables())
+ for (const auto &tescOutVar : compTesc.desc.outputVariables())
tescOutVars[tescOutVar.location] = tescOutVar;
- for (const QShaderDescription::InOutVariable &tescOutVar : tescOutVars) {
- const int location = tescOutVar.location;
- lastLocation = location;
- const QRhiVertexInputAttribute::Format format = rhiD->shaderDescVariableFormatToVertexInputFormat(tescOutVar.type);
- if (teseInVars.contains(location)) {
- if (!matches(teseInVars[location], tescOutVar)) {
- qWarning() << "mismatched tessellation control output -> tesssellation evaluation input at location" << location;
- qWarning() << "tesc out:" << tescOutVar << "tese in:" << teseInVars[location];
+ // tese input variables in ascending location order
+ QMap<int, QShaderDescription::InOutVariable> teseInVars;
+ for (const auto &teseInVar : vertTese.desc.inputVariables())
+ teseInVars[teseInVar.location] = teseInVar;
+
+ // bit mask tracking usage of vertex attribute indices
+ quint64 indices = 0;
+
+ for (QShaderDescription::InOutVariable &tescOutVar : tescOutVars) {
+
+ int index = tescOutVar.location;
+ int binding = -1;
+ quint32 *offset = nullptr;
+ quint32 *alignment = nullptr;
+
+ if (tescOutVar.perPatch) {
+ binding = tescPatchOutputBufferBinding;
+ offset = &offsetInTescPatchOutput;
+ alignment = &tescPatchOutputAlignment;
+ } else {
+ tescOutVar.arrayDims.removeLast();
+ binding = tescOutputBufferBinding;
+ offset = &offsetInTescOutput;
+ alignment = &tescOutputAlignment;
+ }
+
+ if (teseInVars.contains(index)) {
+
+ if (!matches(teseInVars[index], tescOutVar)) {
+ qWarning() << "mismatched tessellation control output -> tesssellation evaluation input at location" << index;
+ qWarning() << " tesc out:" << tescOutVar;
+ qWarning() << " tese in:" << teseInVars[index];
}
- if (tescOutVar.perPatch) {
- if (tescPatchOutputBufferBinding >= 0) {
- vertexDesc.attributes[location].bufferIndex = tescPatchOutputBufferBinding;
- vertexDesc.attributes[location].format = toMetalAttributeFormat(format);
- vertexDesc.attributes[location].offset = offsetInTescPatchOutput;
- }
+
+ if (binding != -1) {
+ addVertexAttribute(tescOutVar, binding, rhiD, index, *offset, vertexDesc.attributes, indices, *alignment);
+ usedBuffers << binding;
} else {
- if (tescOutputBufferBinding >= 0) {
- vertexDesc.attributes[location].bufferIndex = tescOutputBufferBinding;
- vertexDesc.attributes[location].format = toMetalAttributeFormat(format);
- vertexDesc.attributes[location].offset = offsetInTescOutput;
- }
+ qWarning() << "baked tessellation control shader missing output buffer binding information";
+ addUnusedVertexAttribute(tescOutVar, rhiD, *offset, *alignment);
}
+
} else {
qWarning() << "missing tessellation evaluation input for tessellation control output:" << tescOutVar;
+ addUnusedVertexAttribute(tescOutVar, rhiD, *offset, *alignment);
}
- if (tescOutVar.perPatch)
- offsetInTescPatchOutput += rhiD->byteSizePerVertexForVertexInputFormat(format);
- else
- offsetInTescOutput += rhiD->byteSizePerVertexForVertexInputFormat(format);
- }
-
- const QVector<QShaderDescription::BuiltinVariable> tescOutBuiltins = compTesc.desc.outputBuiltinVariables();
- const QVector<QShaderDescription::BuiltinVariable> teseInBuiltins = vertTese.desc.inputBuiltinVariables();
-
- // Take a tess.control shader with an output variable layout(location = 0) out vec3 outColor[].
- // Assume it also writes to glPosition, e.g. gl_out[gl_InvocationID].gl_Position = ...
- // The tess.eval. shader translated to a Metal vertex function will then contain:
- //
- // struct main0_in {
- // float3 inColor [[attribute(0)]];
- // float4 gl_Position [[attribute(1)]]; }
- //
- // The vertex description has to be set up accordingly. The color is
- // simple because that will be in the input/output variable list with
- // location 0. The position is a builtin however. So for now just
- // assume that builtins such as that come after the other variables,
- // with increasing location values.
-
- if (hasBuiltin(tescOutBuiltins, QShaderDescription::PositionBuiltin)
- && hasBuiltin(teseInBuiltins, QShaderDescription::PositionBuiltin)
- && tescOutputBufferBinding >= 0)
- {
- const int location = ++lastLocation;
- vertexDesc.attributes[location].bufferIndex = tescOutputBufferBinding;
- vertexDesc.attributes[location].format = toMetalAttributeFormat(QRhiVertexInputAttribute::Float4);
- vertexDesc.attributes[location].offset = offsetInTescOutput;
- offsetInTescOutput += 4 * sizeof(float);
- }
-
- // Per-patch outputs from the tess.control stage. are mostly handled above.
- // Consider:
- // layout(location = 1) patch in vec3 stuff;
- // layout(location = 2) patch in float more_stuff;
- //
- // This maps to:
- //
- // struct main0_patchIn {
- // float3 stuff [[attribute(1)]];
- // float more_stuff [[attribute(2)]];
- // patch_control_point<main0_in> gl_in; };
- //
- // These are already in place (location 1 and 2, referencing the per-patch
- // output buffer of tesc) at this point. But now if the tess.eval.shader
- // reads gl_TessLevelInner and gl_TessLevelOuter, which are also per-patch,
- // that adds, if the mode is triangles:
- // (assuming gl_Position got location 3, sorted based on the builtin type
- // (Position < Outer < Inner))
- //
- // float4 gl_TessLevel [[attribute(4)]];
- //
- // or if the mode is quads:
- //
- // float4 gl_TessLevelOuter [[attribute(4)]];
- // float2 gl_TessLevelInner [[attribute(5)]];
- //
- // Like gl_Position, these built-ins needs to be handled specially.
- // Note that the data is in a dedicated buffer, not in the patch buffer.
-
- const bool hasTessLevelOuter = hasBuiltin(tescOutBuiltins, QShaderDescription::TessLevelOuterBuiltin)
- && hasBuiltin(teseInBuiltins, QShaderDescription::TessLevelOuterBuiltin);
- const bool hasTessLevelInner = hasBuiltin(tescOutBuiltins, QShaderDescription::TessLevelInnerBuiltin)
- && hasBuiltin(teseInBuiltins, QShaderDescription::TessLevelInnerBuiltin);
- if (vertTese.desc.tessellationMode() != QShaderDescription::TrianglesTessellationMode
- && vertTese.desc.tessellationMode() != QShaderDescription::QuadTessellationMode)
- {
- qWarning("Tessellation evaluation stage mode is neither 'triangles' nor 'quads', this should not happen");
+
+ teseInVars.remove(tescOutVar.location);
}
+
+ for (const QShaderDescription::InOutVariable &teseInVar : teseInVars)
+ qWarning() << "missing tessellation control output for tessellation evaluation input:" << teseInVar;
+
+ // tesc output builtins in ascending location order
+ QMap<QShaderDescription::BuiltinType, QShaderDescription::BuiltinVariable> tescOutBuiltins;
+ for (const auto &tescOutBuiltin : compTesc.desc.outputBuiltinVariables())
+ tescOutBuiltins[tescOutBuiltin.type] = tescOutBuiltin;
+
+ // tese input builtins in ascending location order
+ QMap<QShaderDescription::BuiltinType, QShaderDescription::BuiltinVariable> teseInBuiltins;
+ for (const auto &teseInBuiltin : vertTese.desc.inputBuiltinVariables())
+ teseInBuiltins[teseInBuiltin.type] = teseInBuiltin;
+
const bool trianglesMode = vertTese.desc.tessellationMode() == QShaderDescription::TrianglesTessellationMode;
- if ((hasTessLevelOuter || hasTessLevelInner) && tessFactorBufferBinding >= 0) {
- int loc0 = -1;
- int loc1 = -1;
- if (trianglesMode) {
- loc0 = ++lastLocation; // float4 gl_TessLevel
- } else {
- loc0 = ++lastLocation; // float4 gl_TessLevelOuter
- loc1 = ++lastLocation; // float2 gl_TessLevelInner
+ bool tessLevelAdded = false;
+
+ for (const QShaderDescription::BuiltinVariable &builtin : tescOutBuiltins) {
+
+ QShaderDescription::InOutVariable variable;
+ int binding = -1;
+ quint32 *offset = nullptr;
+ quint32 *alignment = nullptr;
+
+ switch (builtin.type) {
+ case QShaderDescription::BuiltinType::PositionBuiltin:
+ variable.type = QShaderDescription::VariableType::Vec4;
+ binding = tescOutputBufferBinding;
+ offset = &offsetInTescOutput;
+ alignment = &tescOutputAlignment;
+ break;
+ case QShaderDescription::BuiltinType::PointSizeBuiltin:
+ variable.type = QShaderDescription::VariableType::Float;
+ binding = tescOutputBufferBinding;
+ offset = &offsetInTescOutput;
+ alignment = &tescOutputAlignment;
+ break;
+ case QShaderDescription::BuiltinType::ClipDistanceBuiltin:
+ variable.type = QShaderDescription::VariableType::Float;
+ variable.arrayDims = builtin.arrayDims;
+ binding = tescOutputBufferBinding;
+ offset = &offsetInTescOutput;
+ alignment = &tescOutputAlignment;
+ break;
+ case QShaderDescription::BuiltinType::TessLevelOuterBuiltin:
+ variable.type = QShaderDescription::VariableType::Half4;
+ binding = tessFactorBufferBinding;
+ offset = &offsetInTessFactorBuffer;
+ tessLevelAdded = trianglesMode;
+ alignment = &tessFactorAlignment;
+ break;
+ case QShaderDescription::BuiltinType::TessLevelInnerBuiltin:
+ if (trianglesMode) {
+ if (!tessLevelAdded) {
+ variable.type = QShaderDescription::VariableType::Half4;
+ binding = tessFactorBufferBinding;
+ offsetInTessFactorBuffer = 0;
+ offset = &offsetInTessFactorBuffer;
+ alignment = &tessFactorAlignment;
+ tessLevelAdded = true;
+ } else {
+ teseInBuiltins.remove(builtin.type);
+ continue;
+ }
+ } else {
+ variable.type = QShaderDescription::VariableType::Half2;
+ binding = tessFactorBufferBinding;
+ offsetInTessFactorBuffer = 8;
+ offset = &offsetInTessFactorBuffer;
+ alignment = &tessFactorAlignment;
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
}
- if (loc0 >= 0) {
- vertexDesc.attributes[loc0].bufferIndex = tessFactorBufferBinding;
- vertexDesc.attributes[loc0].format = MTLVertexFormatHalf4;
- vertexDesc.attributes[loc0].offset = 0;
+
+ if (teseInBuiltins.contains(builtin.type)) {
+ if (binding != -1) {
+ int index = nextAttributeIndex(indices);
+ addVertexAttribute(variable, binding, rhiD, index, *offset, vertexDesc.attributes, indices, *alignment);
+ usedBuffers << binding;
+ } else {
+ qWarning() << "baked tessellation control shader missing output buffer binding information";
+ addUnusedVertexAttribute(variable, rhiD, *offset, *alignment);
+ }
+ } else {
+ addUnusedVertexAttribute(variable, rhiD, *offset, *alignment);
}
- if (loc1 >= 0) {
- vertexDesc.attributes[loc1].bufferIndex = tessFactorBufferBinding;
- vertexDesc.attributes[loc1].format = MTLVertexFormatHalf2;
- vertexDesc.attributes[loc1].offset = 8;
+
+ teseInBuiltins.remove(builtin.type);
+ }
+
+ for (const QShaderDescription::BuiltinVariable &builtin : teseInBuiltins) {
+ switch (builtin.type) {
+ case QShaderDescription::BuiltinType::PositionBuiltin:
+ case QShaderDescription::BuiltinType::PointSizeBuiltin:
+ case QShaderDescription::BuiltinType::ClipDistanceBuiltin:
+ qWarning() << "missing tessellation control output for tessellation evaluation builtin input:" << builtin;
+ break;
+ default:
+ break;
}
- vertexDesc.layouts[tessFactorBufferBinding].stepFunction = MTLVertexStepFunctionPerPatch;
- vertexDesc.layouts[tessFactorBufferBinding].stride = trianglesMode ? 8 : 12;
}
- if (offsetInTescOutput > 0) {
+ if (usedBuffers.contains(tescOutputBufferBinding)) {
vertexDesc.layouts[tescOutputBufferBinding].stepFunction = MTLVertexStepFunctionPerPatchControlPoint;
- vertexDesc.layouts[tescOutputBufferBinding].stride = offsetInTescOutput;
+ vertexDesc.layouts[tescOutputBufferBinding].stride = aligned(offsetInTescOutput, tescOutputAlignment);
}
- if (offsetInTescPatchOutput > 0) {
+ if (usedBuffers.contains(tescPatchOutputBufferBinding)) {
vertexDesc.layouts[tescPatchOutputBufferBinding].stepFunction = MTLVertexStepFunctionPerPatch;
- vertexDesc.layouts[tescPatchOutputBufferBinding].stride = offsetInTescPatchOutput;
+ vertexDesc.layouts[tescPatchOutputBufferBinding].stride = aligned(offsetInTescPatchOutput, tescPatchOutputAlignment);
+ }
+
+ if (usedBuffers.contains(tessFactorBufferBinding)) {
+ vertexDesc.layouts[tessFactorBufferBinding].stepFunction = MTLVertexStepFunctionPerPatch;
+ vertexDesc.layouts[tessFactorBufferBinding].stride = trianglesMode ? sizeof(MTLTriangleTessellationFactorsHalf) : sizeof(MTLQuadTessellationFactorsHalf);
}
rpDesc.vertexDescriptor = vertexDesc;
diff --git a/src/gui/rhi/qshader.cpp b/src/gui/rhi/qshader.cpp
index c35445f7b8..52cb503521 100644
--- a/src/gui/rhi/qshader.cpp
+++ b/src/gui/rhi/qshader.cpp
@@ -474,6 +474,7 @@ QShader QShader::fromSerialized(const QByteArray &data)
ds >> intVal;
d->qsbVersion = intVal;
if (d->qsbVersion != QShaderPrivate::QSB_VERSION
+ && d->qsbVersion != QShaderPrivate::QSB_VERSION_WITHOUT_INPUT_OUTPUT_INTERFACE_BLOCKS
&& 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_SEPARATE_IMAGES_AND_SAMPLERS
diff --git a/src/gui/rhi/qshader_p_p.h b/src/gui/rhi/qshader_p_p.h
index f0f990935a..9cc9207f69 100644
--- a/src/gui/rhi/qshader_p_p.h
+++ b/src/gui/rhi/qshader_p_p.h
@@ -24,7 +24,8 @@ QT_BEGIN_NAMESPACE
struct Q_GUI_EXPORT QShaderPrivate
{
- static const int QSB_VERSION = 8;
+ static const int QSB_VERSION = 9;
+ static const int QSB_VERSION_WITHOUT_INPUT_OUTPUT_INTERFACE_BLOCKS = 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_SEPARATE_IMAGES_AND_SAMPLERS = 5;
diff --git a/src/gui/rhi/qshaderdescription.cpp b/src/gui/rhi/qshaderdescription.cpp
index ca697d8edb..44fcd936fd 100644
--- a/src/gui/rhi/qshaderdescription.cpp
+++ b/src/gui/rhi/qshaderdescription.cpp
@@ -724,8 +724,12 @@ static const struct TypeTab {
{ "image3DArray", QShaderDescription::Image3DArray },
{ "imageCubeArray", QShaderDescription::ImageCubeArray },
{ "imageRect", QShaderDescription::ImageRect },
- { "imageBuffer", QShaderDescription::ImageBuffer }
-};
+ { "imageBuffer", QShaderDescription::ImageBuffer },
+
+ { "half", QShaderDescription::Half },
+ { "half2", QShaderDescription::Half2 },
+ { "half3", QShaderDescription::Half3 },
+ { "half4", QShaderDescription::Half4 } };
static QLatin1StringView typeStr(QShaderDescription::VariableType t)
{
@@ -936,6 +940,8 @@ QDebug operator<<(QDebug dbg, const QShaderDescription::InOutVariable &var)
dbg.nospace() << " imageFlags=" << var.imageFlags;
if (!var.arrayDims.isEmpty())
dbg.nospace() << " array=" << var.arrayDims;
+ if (!var.structMembers.isEmpty())
+ dbg.nospace() << " structMembers=" << var.structMembers;
dbg.nospace() << ')';
return dbg;
}
@@ -943,8 +949,10 @@ QDebug operator<<(QDebug dbg, const QShaderDescription::InOutVariable &var)
QDebug operator<<(QDebug dbg, const QShaderDescription::BlockVariable &var)
{
QDebugStateSaver saver(dbg);
- dbg.nospace() << "BlockVariable(" << typeStr(var.type) << ' ' << var.name
- << " offset=" << var.offset << " size=" << var.size;
+ dbg.nospace() << "BlockVariable(" << typeStr(var.type) << ' ' << var.name;
+ if (var.offset != -1)
+ dbg.nospace() << " offset=" << var.offset;
+ dbg.nospace() << " size=" << var.size;
if (!var.arrayDims.isEmpty())
dbg.nospace() << " array=" << var.arrayDims;
if (var.arrayStride)
@@ -1000,7 +1008,11 @@ QDebug operator<<(QDebug dbg, const QShaderDescription::StorageBlock &blk)
QDebug operator<<(QDebug dbg, const QShaderDescription::BuiltinVariable &builtin)
{
QDebugStateSaver saver(dbg);
- dbg.nospace() << "BuiltinVariable(type=" << builtinTypeStr(builtin.type) << ")";
+ dbg.nospace() << "BuiltinVariable(type=" << builtinTypeStr(builtin.type);
+ dbg.nospace() << " varType=" << typeStr(builtin.varType);
+ if (!builtin.arrayDims.isEmpty())
+ dbg.nospace() << " array=" << builtin.arrayDims;
+ dbg.nospace() << ")";
return dbg;
}
#endif
@@ -1082,20 +1094,15 @@ static void serializeDecorations(QDataStream *stream, const QShaderDescription::
(*stream) << quint8(v.perPatch);
}
-static QJsonObject inOutObject(const QShaderDescription::InOutVariable &v)
-{
- QJsonObject obj;
- obj[nameKey()] = QString::fromUtf8(v.name);
- obj[typeKey()] = typeStr(v.type);
- addDeco(&obj, v);
- return obj;
-}
-
-static void serializeInOutVar(QDataStream *stream, const QShaderDescription::InOutVariable &v, int version)
+static void serializeBuiltinVar(QDataStream *stream, const QShaderDescription::BuiltinVariable &v, int version)
{
- (*stream) << QString::fromUtf8(v.name);
(*stream) << int(v.type);
- serializeDecorations(stream, v, version);
+ if (version > QShaderPrivate::QSB_VERSION_WITHOUT_INPUT_OUTPUT_INTERFACE_BLOCKS) {
+ (*stream) << int(v.varType);
+ (*stream) << int(v.arrayDims.size());
+ for (int dim : v.arrayDims)
+ (*stream) << dim;
+ }
}
static QJsonObject blockMemberObject(const QShaderDescription::BlockVariable &v)
@@ -1103,7 +1110,8 @@ static QJsonObject blockMemberObject(const QShaderDescription::BlockVariable &v)
QJsonObject obj;
obj[nameKey()] = QString::fromUtf8(v.name);
obj[typeKey()] = typeStr(v.type);
- obj[offsetKey()] = v.offset;
+ if (v.offset != -1)
+ obj[offsetKey()] = v.offset;
obj[sizeKey()] = v.size;
if (!v.arrayDims.isEmpty()) {
QJsonArray dimArr;
@@ -1126,6 +1134,36 @@ static QJsonObject blockMemberObject(const QShaderDescription::BlockVariable &v)
return obj;
}
+static QJsonObject inOutObject(const QShaderDescription::InOutVariable &v)
+{
+ QJsonObject obj;
+ obj[nameKey()] = QString::fromUtf8(v.name);
+ obj[typeKey()] = typeStr(v.type);
+ addDeco(&obj, v);
+ if (!v.structMembers.isEmpty()) {
+ QJsonArray arr;
+ for (const QShaderDescription::BlockVariable &sv : v.structMembers)
+ arr.append(blockMemberObject(sv));
+ obj[structMembersKey()] = arr;
+ }
+ return obj;
+}
+
+static QJsonObject builtinObject(const QShaderDescription::BuiltinVariable &v)
+{
+ QJsonObject obj;
+
+ obj[nameKey()] = builtinTypeStr(v.type);
+ obj[typeKey()] = typeStr(v.varType);
+ if (!v.arrayDims.isEmpty()) {
+ QJsonArray dimArr;
+ for (int dim : v.arrayDims)
+ dimArr.append(dim);
+ obj[arrayDimsKey()] = dimArr;
+ }
+ return obj;
+}
+
static void serializeBlockMemberVar(QDataStream *stream, const QShaderDescription::BlockVariable &v)
{
(*stream) << QString::fromUtf8(v.name);
@@ -1143,6 +1181,19 @@ static void serializeBlockMemberVar(QDataStream *stream, const QShaderDescriptio
serializeBlockMemberVar(stream, sv);
}
+static void serializeInOutVar(QDataStream *stream, const QShaderDescription::InOutVariable &v,
+ int version)
+{
+ (*stream) << QString::fromUtf8(v.name);
+ (*stream) << int(v.type);
+ serializeDecorations(stream, v, version);
+ if (version > QShaderPrivate::QSB_VERSION_WITHOUT_INPUT_OUTPUT_INTERFACE_BLOCKS) {
+ (*stream) << int(v.structMembers.size());
+ for (const QShaderDescription::BlockVariable &sv : v.structMembers)
+ serializeBlockMemberVar(stream, sv);
+ }
+}
+
QJsonDocument QShaderDescriptionPrivate::makeDoc()
{
QJsonObject root;
@@ -1238,20 +1289,14 @@ QJsonDocument QShaderDescriptionPrivate::makeDoc()
root[storageImagesKey()] = jstorageImages;
QJsonArray jinBuiltins;
- for (const QShaderDescription::BuiltinVariable &v : std::as_const(inBuiltins)) {
- QJsonObject builtin;
- builtin[typeKey()] = builtinTypeStr(v.type);
- jinBuiltins.append(builtin);
- }
+ for (const QShaderDescription::BuiltinVariable &v : std::as_const(inBuiltins))
+ jinBuiltins.append(builtinObject(v));
if (!jinBuiltins.isEmpty())
root[inBuiltinsKey()] = jinBuiltins;
QJsonArray joutBuiltins;
- for (const QShaderDescription::BuiltinVariable &v : std::as_const(outBuiltins)) {
- QJsonObject builtin;
- builtin[typeKey()] = builtinTypeStr(v.type);
- joutBuiltins.append(builtin);
- }
+ for (const QShaderDescription::BuiltinVariable &v : std::as_const(outBuiltins))
+ joutBuiltins.append(builtinObject(v));
if (!joutBuiltins.isEmpty())
root[outBuiltinsKey()] = joutBuiltins;
@@ -1385,11 +1430,11 @@ void QShaderDescriptionPrivate::writeToStream(QDataStream *stream, int version)
(*stream) << int(inBuiltins.size());
for (const QShaderDescription::BuiltinVariable &v : std::as_const(inBuiltins))
- (*stream) << int(v.type);
+ serializeBuiltinVar(stream, v, version);
(*stream) << int(outBuiltins.size());
for (const QShaderDescription::BuiltinVariable &v : std::as_const(outBuiltins))
- (*stream) << int(v.type);
+ serializeBuiltinVar(stream, v, version);
}
}
@@ -1418,16 +1463,21 @@ static void deserializeDecorations(QDataStream *stream, int version, QShaderDesc
}
}
-static QShaderDescription::InOutVariable deserializeInOutVar(QDataStream *stream, int version)
+static QShaderDescription::BuiltinVariable deserializeBuiltinVar(QDataStream *stream, int version)
{
- QShaderDescription::InOutVariable var;
- QString tmp;
- (*stream) >> tmp;
- var.name = tmp.toUtf8();
+ QShaderDescription::BuiltinVariable var;
int t;
(*stream) >> t;
- var.type = QShaderDescription::VariableType(t);
- deserializeDecorations(stream, version, &var);
+ var.type = QShaderDescription::BuiltinType(t);
+ if (version > QShaderPrivate::QSB_VERSION_WITHOUT_INPUT_OUTPUT_INTERFACE_BLOCKS) {
+ (*stream) >> t;
+ var.varType = QShaderDescription::VariableType(t);
+ int count;
+ (*stream) >> count;
+ var.arrayDims.resize(count);
+ for (int i = 0; i < count; ++i)
+ (*stream) >> var.arrayDims[i];
+ }
return var;
}
@@ -1457,6 +1507,26 @@ static QShaderDescription::BlockVariable deserializeBlockMemberVar(QDataStream *
return var;
}
+static QShaderDescription::InOutVariable deserializeInOutVar(QDataStream *stream, int version)
+{
+ QShaderDescription::InOutVariable var;
+ QString tmp;
+ (*stream) >> tmp;
+ var.name = tmp.toUtf8();
+ int t;
+ (*stream) >> t;
+ var.type = QShaderDescription::VariableType(t);
+ deserializeDecorations(stream, version, &var);
+ if (version > QShaderPrivate::QSB_VERSION_WITHOUT_INPUT_OUTPUT_INTERFACE_BLOCKS) {
+ int count;
+ (*stream) >> count;
+ var.structMembers.resize(count);
+ for (int i = 0; i < count; ++i)
+ var.structMembers[i] = deserializeBlockMemberVar(stream, version);
+ }
+ return var;
+}
+
void QShaderDescriptionPrivate::loadFromStream(QDataStream *stream, int version)
{
Q_ASSERT(ref.loadRelaxed() == 1); // must be detached
@@ -1596,19 +1666,13 @@ void QShaderDescriptionPrivate::loadFromStream(QDataStream *stream, int version)
(*stream) >> count;
inBuiltins.resize(count);
- for (int i = 0; i < count; ++i) {
- int t;
- (*stream) >> t;
- inBuiltins[i].type = QShaderDescription::BuiltinType(t);
- }
+ for (int i = 0; i < count; ++i)
+ inBuiltins[i] = deserializeBuiltinVar(stream, version);
(*stream) >> count;
outBuiltins.resize(count);
- for (int i = 0; i < count; ++i) {
- int t;
- (*stream) >> t;
- outBuiltins[i].type = QShaderDescription::BuiltinType(t);
- }
+ for (int i = 0; i < count; ++i)
+ outBuiltins[i] = deserializeBuiltinVar(stream, version);
}
}
@@ -1657,7 +1721,8 @@ bool operator==(const QShaderDescription::InOutVariable &lhs, const QShaderDescr
&& lhs.imageFormat == rhs.imageFormat
&& lhs.imageFlags == rhs.imageFlags
&& lhs.arrayDims == rhs.arrayDims
- && lhs.perPatch == rhs.perPatch;
+ && lhs.perPatch == rhs.perPatch
+ && lhs.structMembers == rhs.structMembers;
}
/*!
@@ -1734,7 +1799,9 @@ bool operator==(const QShaderDescription::StorageBlock &lhs, const QShaderDescri
*/
bool operator==(const QShaderDescription::BuiltinVariable &lhs, const QShaderDescription::BuiltinVariable &rhs) noexcept
{
- return lhs.type == rhs.type;
+ return lhs.type == rhs.type
+ && lhs.varType == rhs.varType
+ && lhs.arrayDims == rhs.arrayDims;
}
QT_END_NAMESPACE
diff --git a/src/gui/rhi/qshaderdescription_p.h b/src/gui/rhi/qshaderdescription_p.h
index 13e72ad134..990cb12003 100644
--- a/src/gui/rhi/qshaderdescription_p.h
+++ b/src/gui/rhi/qshaderdescription_p.h
@@ -117,7 +117,12 @@ public:
ImageRect,
ImageBuffer,
- Struct
+ Struct,
+
+ Half,
+ Half2,
+ Half3,
+ Half4
};
enum ImageFormat {
@@ -181,6 +186,19 @@ public:
// Optional data (like decorations) usually default to an otherwise invalid value (-1 or 0). This is intentional.
+ struct BlockVariable
+ {
+ QByteArray name;
+ VariableType type = Unknown;
+ int offset = 0;
+ int size = 0;
+ QList<int> arrayDims;
+ int arrayStride = 0;
+ int matrixStride = 0;
+ bool matrixIsRowMajor = false;
+ QList<BlockVariable> structMembers;
+ };
+
struct InOutVariable {
QByteArray name;
VariableType type = Unknown;
@@ -191,17 +209,6 @@ public:
ImageFlags imageFlags;
QList<int> arrayDims;
bool perPatch = false;
- };
-
- struct BlockVariable {
- QByteArray name;
- VariableType type = Unknown;
- int offset = 0;
- int size = 0;
- QList<int> arrayDims;
- int arrayStride = 0;
- int matrixStride = 0;
- bool matrixIsRowMajor = false;
QList<BlockVariable> structMembers;
};
@@ -276,6 +283,8 @@ public:
struct BuiltinVariable {
BuiltinType type;
+ VariableType varType;
+ QList<int> arrayDims;
};
QList<BuiltinVariable> inputBuiltinVariables() const;
diff --git a/tests/auto/gui/rhi/qrhi/data/buildshaders.bat b/tests/auto/gui/rhi/qrhi/data/buildshaders.bat
index 8fdca5623c..5b07c7bf2b 100644
--- a/tests/auto/gui/rhi/qrhi/data/buildshaders.bat
+++ b/tests/auto/gui/rhi/qrhi/data/buildshaders.bat
@@ -22,3 +22,7 @@ qsb --glsl 320es,430 --msl 12 --tess-vertex-count 3 storagebuffer_runtime.tese -
qsb --glsl 320es,430 --msl 12 storagebuffer_runtime.frag -o storagebuffer_runtime.frag.qsb
qsb --glsl 320es,430 --hlsl 50 -c --msl 12 storagebuffer_runtime.comp -o storagebuffer_runtime.comp.qsb
qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o half.vert.qsb half.vert
+qsb --glsl 320es,430 --msl 21 --msltess tessinterfaceblocks.vert -o tessinterfaceblocks.vert.qsb
+qsb --glsl 320es,430 --msl 21 --tess-mode triangles tessinterfaceblocks.tesc -o tessinterfaceblocks.tesc.qsb
+qsb --glsl 320es,430 --msl 21 --tess-vertex-count 3 tessinterfaceblocks.tese -o tessinterfaceblocks.tese.qsb
+qsb --glsl 320es,430 --msl 21 simpletess.frag -o tessinterfaceblocks.frag.qsb
diff --git a/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.frag.qsb b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.frag.qsb
new file mode 100644
index 0000000000..7eda4bed2d
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.frag.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tesc b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tesc
new file mode 100644
index 0000000000..92a2dc28fa
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tesc
@@ -0,0 +1,56 @@
+#version 440
+
+layout(vertices = 3) out;
+
+layout(location = 4) in VertOut
+{
+ vec3 v_color;
+ int a;
+ float b;
+}vOut[];
+
+layout(location = 5) out TescOutA {
+ vec3 color;
+ int id;
+}tcOutA[];
+
+layout(location = 10) out TescOutB {
+ vec2 some;
+ int other[3];
+ vec3 variables;
+}tcOutB[];
+
+layout(location = 2) patch out TescOutC {
+ vec3 stuff;
+ float more_stuff;
+}tcOutC;
+
+void main()
+{
+ // tesc builtin outputs
+ gl_TessLevelOuter[0] = 1.0;
+ gl_TessLevelOuter[1] = 2.0;
+ gl_TessLevelOuter[2] = 3.0;
+ gl_TessLevelOuter[3] = 4.0;
+ gl_TessLevelInner[0] = 5.0;
+ gl_TessLevelInner[1] = 6.0;
+
+ gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
+ gl_out[gl_InvocationID].gl_PointSize = 10 + gl_InvocationID;
+ gl_out[gl_InvocationID].gl_ClipDistance[0] = 20.0 + gl_InvocationID;
+ gl_out[gl_InvocationID].gl_ClipDistance[1] = 40.0 + gl_InvocationID;
+ gl_out[gl_InvocationID].gl_ClipDistance[2] = 60.0 + gl_InvocationID;
+ gl_out[gl_InvocationID].gl_ClipDistance[3] = 80.0 + gl_InvocationID;
+ gl_out[gl_InvocationID].gl_ClipDistance[4] = 100.0 + gl_InvocationID;
+
+ // outputs
+ tcOutA[gl_InvocationID].color = vOut[gl_InvocationID].v_color;
+ tcOutA[gl_InvocationID].id = gl_InvocationID + 91;
+ tcOutB[gl_InvocationID].some = vec2(gl_InvocationID, vOut[gl_InvocationID].a);
+ tcOutB[gl_InvocationID].other[0] = gl_PrimitiveID + 10;
+ tcOutB[gl_InvocationID].other[1] = gl_PrimitiveID + 20;
+ tcOutB[gl_InvocationID].other[2] = gl_PrimitiveID + 30;
+ tcOutB[gl_InvocationID].variables = vec3(3.0f, vOut[gl_InvocationID].b, 17.0f);
+ tcOutC.stuff = vec3(1.0, 2.0, 3.0);
+ tcOutC.more_stuff = 4.0;
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tesc.qsb b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tesc.qsb
new file mode 100644
index 0000000000..b503d596c6
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tesc.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tese b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tese
new file mode 100644
index 0000000000..05430a5f63
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tese
@@ -0,0 +1,96 @@
+#version 440
+
+layout(triangles, fractional_odd_spacing, ccw) in;
+
+layout(std140, binding = 0) uniform buf {
+ mat4 mvp;
+};
+
+layout(location = 5) in TescOutA {
+ vec3 color;
+ int id;
+}tcOutA[];
+
+layout(location = 10) in TescOutB {
+ vec2 some;
+ int other[3];
+ vec3 variables;
+}tcOutB[];
+
+layout(location = 2) patch in TescOutC {
+ vec3 stuff;
+ float more_stuff;
+}tcOutC;
+
+layout(location = 0) out vec3 outColor;
+
+struct A {
+ vec3 color;
+ int id;
+};
+
+struct B {
+ vec2 some;
+ int other[3];
+ vec3 variables;
+};
+
+struct C {
+ vec3 stuff;
+ float more_stuff;
+};
+
+struct Element {
+ A a[3];
+ B b[3];
+ C c;
+ vec4 tesslevelOuter;
+ vec2 tessLevelInner;
+ float pointSize[3];
+ float clipDistance[3][5];
+ vec3 tessCoord;
+ int patchVerticesIn;
+ int primitiveID;
+};
+
+layout(std430, binding = 1) buffer result {
+ int count;
+ Element elements[];
+};
+
+void main()
+{
+ gl_Position = mvp * ((gl_TessCoord.x * gl_in[0].gl_Position) + (gl_TessCoord.y * gl_in[1].gl_Position) + (gl_TessCoord.z * gl_in[2].gl_Position));
+ outColor = gl_TessCoord.x * tcOutA[0].color + gl_TessCoord.y * tcOutA[1].color + gl_TessCoord.z * tcOutA[2].color;
+
+ count = 1;
+
+ elements[gl_PrimitiveID].c.stuff = tcOutC.stuff;
+ elements[gl_PrimitiveID].c.more_stuff = tcOutC.more_stuff;
+ elements[gl_PrimitiveID].tesslevelOuter = vec4(gl_TessLevelOuter[0], gl_TessLevelOuter[1], gl_TessLevelOuter[2], gl_TessLevelOuter[3]);
+ elements[gl_PrimitiveID].tessLevelInner = vec2(gl_TessLevelInner[0], gl_TessLevelInner[1]);
+
+ for (int i = 0; i < 3; ++i) {
+
+ elements[gl_PrimitiveID].a[i].color = tcOutA[i].color;
+ elements[gl_PrimitiveID].a[i].id = tcOutA[i].id;
+
+ elements[gl_PrimitiveID].b[i].some = tcOutB[i].some;
+ elements[gl_PrimitiveID].b[i].other = tcOutB[i].other;
+ elements[gl_PrimitiveID].b[i].variables = tcOutB[i].variables;
+
+ elements[gl_PrimitiveID].pointSize[i] = gl_in[i].gl_PointSize;
+ elements[gl_PrimitiveID].clipDistance[i][0] = gl_in[i].gl_ClipDistance[0];
+ elements[gl_PrimitiveID].clipDistance[i][1] = gl_in[i].gl_ClipDistance[1];
+ elements[gl_PrimitiveID].clipDistance[i][2] = gl_in[i].gl_ClipDistance[2];
+ elements[gl_PrimitiveID].clipDistance[i][3] = gl_in[i].gl_ClipDistance[3];
+ elements[gl_PrimitiveID].clipDistance[i][4] = gl_in[i].gl_ClipDistance[4];
+
+ }
+
+ elements[gl_PrimitiveID].tessCoord = gl_TessCoord;
+ elements[gl_PrimitiveID].patchVerticesIn = 3;
+ elements[gl_PrimitiveID].primitiveID = gl_PrimitiveID;
+
+}
+
diff --git a/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tese.qsb b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tese.qsb
new file mode 100644
index 0000000000..898bda454a
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tese.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.vert b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.vert
new file mode 100644
index 0000000000..7c722bb374
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.vert
@@ -0,0 +1,20 @@
+#version 440
+
+layout(location = 0) in vec3 position;
+layout(location = 1) in vec3 color;
+
+
+layout(location = 4) out VertOut
+{
+ vec3 v_color;
+ int a;
+ float b;
+};
+
+void main()
+{
+ gl_Position = vec4(position, 1.0);
+ v_color = color;
+ a = gl_VertexIndex;
+ b = 13.0f + gl_VertexIndex;
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.vert.qsb b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.vert.qsb
new file mode 100644
index 0000000000..07384d643c
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.vert.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
index f0b9904834..5723d3f8c8 100644
--- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
+++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
@@ -145,6 +145,9 @@ private slots:
void tessellation_data();
void tessellation();
+ void tessellationInterfaceBlocks_data();
+ void tessellationInterfaceBlocks();
+
void storageBuffer_data();
void storageBuffer();
void storageBufferRuntimeSizeCompute_data();
@@ -5796,6 +5799,321 @@ void tst_QRhi::tessellation()
QVERIFY(greenCount > 50);
}
+void tst_QRhi::tessellationInterfaceBlocks_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::tessellationInterfaceBlocks()
+{
+ QFETCH(QRhi::Implementation, impl);
+ QFETCH(QRhiInitParams *, initParams);
+
+ // This test is intended for Metal, but will run on other tessellation render pipelines
+ //
+ // Metal tessellation uses a combination of compute pipelines for the vert and tesc, and a
+ // render pipeline for the tese and frag. This test uses input output interface blocks between
+ // the tesc and tese, and all tese stage builtin inputs to check that the Metal tese-frag
+ // pipeline vertex inputs are correctly configured. The tese writes the values to a storage
+ // buffer whose values are checked by the unit test. MSL 2.1 is required for this test.
+ // (requires support for writing to a storage buffer in the vertex shader within a render
+ // pipeline)
+
+ QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr));
+ if (!rhi)
+ QSKIP("QRhi could not be created, skipping testing rendering");
+
+ if (!rhi->isFeatureSupported(QRhi::Tessellation)) {
+ // From a Vulkan or Metal implementation we expect tessellation to work,
+ // even though it is optional (as per spec) for Vulkan.
+ QVERIFY(rhi->backend() != QRhi::Vulkan);
+ QVERIFY(rhi->backend() != QRhi::Metal);
+ QSKIP("Tessellation is not supported with this graphics API, skipping test");
+ }
+
+ if (rhi->backend() == QRhi::D3D11 || rhi->backend() == QRhi::D3D12)
+ QSKIP("Skipping tessellation test on D3D for now, test assets not prepared for HLSL yet");
+
+ if (rhi->backend() == QRhi::OpenGLES2)
+ QSKIP("Skipping test on OpenGL as gl_ClipDistance[] support inconsistent");
+
+ QScopedPointer<QRhiTexture> texture(
+ rhi->newTexture(QRhiTexture::RGBA8, QSize(1280, 720), 1,
+ QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
+ QVERIFY(texture->create());
+
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() }));
+ QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
+ rt->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(rt->create());
+
+ static const float triangleVertices[] = {
+ 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, -0.5f, -0.5f, 0.0f,
+ 1.0f, 0.0f, 0.0f, 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
+ };
+
+ QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch();
+ QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer,
+ sizeof(triangleVertices)));
+ QVERIFY(vbuf->create());
+ u->uploadStaticBuffer(vbuf.data(), triangleVertices);
+
+ QScopedPointer<QRhiBuffer> ubuf(
+ rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64));
+ QVERIFY(ubuf->create());
+
+ // Use the 3D API specific correction matrix that flips Y, so we can use
+ // the OpenGL-targeted vertex data and the tessellation winding order of
+ // counter-clockwise to get uniform results.
+ QMatrix4x4 mvp = rhi->clipSpaceCorrMatrix();
+ u->updateDynamicBuffer(ubuf.data(), 0, 64, mvp.constData());
+
+ QScopedPointer<QRhiBuffer> buffer(
+ rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::UsageFlag::StorageBuffer, 1024));
+ QVERIFY(buffer->create());
+
+ u->uploadStaticBuffer(buffer.data(), 0, 1024, QByteArray(1024, 0).constData());
+
+ QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
+ srb->setBindings(
+ { QRhiShaderResourceBinding::uniformBuffer(
+ 0, QRhiShaderResourceBinding::TessellationEvaluationStage, ubuf.data()),
+ QRhiShaderResourceBinding::bufferLoadStore(
+ 1, QRhiShaderResourceBinding::TessellationEvaluationStage, buffer.data()) });
+ QVERIFY(srb->create());
+
+ QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline());
+
+ pipeline->setTopology(QRhiGraphicsPipeline::Patches);
+ pipeline->setPatchControlPointCount(3);
+
+ pipeline->setShaderStages(
+ { { QRhiShaderStage::Vertex, loadShader(":/data/tessinterfaceblocks.vert.qsb") },
+ { QRhiShaderStage::TessellationControl,
+ loadShader(":/data/tessinterfaceblocks.tesc.qsb") },
+ { QRhiShaderStage::TessellationEvaluation,
+ loadShader(":/data/tessinterfaceblocks.tese.qsb") },
+ { QRhiShaderStage::Fragment, loadShader(":/data/tessinterfaceblocks.frag.qsb") } });
+
+ pipeline->setCullMode(QRhiGraphicsPipeline::Back); // to ensure the winding order is correct
+
+ // won't get the wireframe with OpenGL ES
+ if (rhi->isFeatureSupported(QRhi::NonFillPolygonMode))
+ pipeline->setPolygonMode(QRhiGraphicsPipeline::Line);
+
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({ { 6 * sizeof(float) } });
+ inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float3, 0 },
+ { 0, 1, QRhiVertexInputAttribute::Float3, 3 * sizeof(float) } });
+
+ pipeline->setVertexInputLayout(inputLayout);
+ pipeline->setShaderResourceBindings(srb.data());
+ pipeline->setRenderPassDescriptor(rpDesc.data());
+
+ QVERIFY(pipeline->create());
+
+ QRhiCommandBuffer *cb = nullptr;
+ QCOMPARE(rhi->beginOffscreenFrame(&cb), QRhi::FrameOpSuccess);
+
+ cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, u);
+ cb->setGraphicsPipeline(pipeline.data());
+ cb->setViewport({ 0, 0, float(rt->pixelSize().width()), float(rt->pixelSize().height()) });
+ cb->setShaderResources();
+ QRhiCommandBuffer::VertexInput vbufBinding(vbuf.data(), 0);
+ cb->setVertexInput(0, 1, &vbufBinding);
+ cb->draw(3);
+
+ QRhiReadbackResult readResult;
+ QImage result;
+ readResult.completed = [&readResult, &result] {
+ result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
+ readResult.pixelSize.width(), readResult.pixelSize.height(),
+ QImage::Format_RGBA8888);
+ };
+ QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
+ readbackBatch->readBackTexture({ texture.data() }, &readResult);
+
+ QRhiBufferReadbackResult bufferReadResult;
+ bufferReadResult.completed = []() {};
+ readbackBatch->readBackBuffer(buffer.data(), 0, 1024, &bufferReadResult);
+
+ cb->endPass(readbackBatch);
+
+ rhi->endOffscreenFrame();
+
+ if (rhi->isYUpInFramebuffer()) // we used clipSpaceCorrMatrix so this is different from many
+ // other tests
+ result = std::move(result).mirrored();
+
+ QCOMPARE(result.size(), rt->pixelSize());
+
+ // cannot check rendering results with Null, because there is no rendering there
+ if (impl == QRhi::Null)
+ return;
+
+ int redCount = 0, greenCount = 0, blueCount = 0;
+ for (int y = 0; y < result.height(); ++y) {
+ const quint32 *p = reinterpret_cast<const quint32 *>(result.constScanLine(y));
+ int x = result.width() - 1;
+ while (x-- >= 0) {
+ const QRgb c(*p++);
+ const int red = qRed(c);
+ const int green = qGreen(c);
+ const int blue = qBlue(c);
+ // just count the color components that are above a certain threshold
+ if (red > 240)
+ ++redCount;
+ if (green > 240)
+ ++greenCount;
+ if (blue > 240)
+ ++blueCount;
+ }
+ }
+
+ // make sure we drew something
+ QVERIFY(redCount > 50);
+ QVERIFY(blueCount > 50);
+ QVERIFY(greenCount > 50);
+
+ // StorageBlock("result" "" knownSize=16 binding=1 set=0 runtimeArrayStride=336 QList(
+ // BlockVariable("int" "count" offset=0 size=4),
+ // BlockVariable("struct" "elements" offset=16 size=0 array=QList(0) structMembers=QList(
+ // BlockVariable("struct" "a" offset=0 size=48 array=QList(3) structMembers=QList(
+ // BlockVariable("vec3" "color" offset=0 size=12),
+ // BlockVariable("int" "id" offset=12 size=4))),
+ // BlockVariable("struct" "b" offset=48 size=144 array=QList(3) structMembers=QList(
+ // BlockVariable("vec2" "some" offset=0 size=8),
+ // BlockVariable("int" "other" offset=8 size=12 array=QList(3)),
+ // BlockVariable("vec3" "variables" offset=32 size=12))),
+ // BlockVariable("struct" "c" offset=192 size=16 structMembers=QList(
+ // BlockVariable("vec3" "stuff" offset=0 size=12),
+ // BlockVariable("float" "more_stuff" offset=12 size=4))),
+ // BlockVariable("vec4" "tesslevelOuter" offset=208 size=16),
+ // BlockVariable("vec2" "tessLevelInner" offset=224 size=8),
+ // BlockVariable("float" "pointSize" offset=232 size=12 array=QList(3)),
+ // BlockVariable("float" "clipDistance" offset=244 size=60 array=QList(5, 3)),
+ // BlockVariable("vec3" "tessCoord" offset=304 size=12),
+ // BlockVariable("int" "patchVerticesIn" offset=316 size=4),
+ // BlockVariable("int" "primitiveID" offset=320 size=4)))))
+
+ // int count
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[0])[0], 1);
+
+ // a[0].color
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 0 + 0])[0], 0.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 0 + 0])[1], 0.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 0 + 0])[2], 1.0f);
+
+ // a[0].id
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 0 + 12])[0], 91);
+
+ // a[1].color
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 16 + 0])[0], 1.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 16 + 0])[1], 0.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 16 + 0])[2], 0.0f);
+
+ // a[1].id
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 16 + 12])[0], 92);
+
+ // a[2].color
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 32 + 0])[0], 0.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 32 + 0])[1], 1.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 32 + 0])[2], 0.0f);
+
+ // a[2].id
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 32 + 12])[0], 93);
+
+ // b[0].some
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 48 + 0])[0], 0.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 48 + 0])[1], 0.0f);
+
+ // b[0].other[0]
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 48 + 8])[0], 10.0f);
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 48 + 8])[1], 20.0f);
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 48 + 8])[2], 30.0f);
+
+ // b[0].variables
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 48 + 32])[0], 3.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 48 + 32])[1], 13.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 48 + 32])[2], 17.0f);
+
+ // b[1].some
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 96 + 0])[0], 1.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 96 + 0])[1], 1.0f);
+
+ // b[1].other[0]
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 96 + 8])[0], 10.0f);
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 96 + 8])[1], 20.0f);
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 96 + 8])[2], 30.0f);
+
+ // b[1].variables
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 96 + 32])[0], 3.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 96 + 32])[1], 14.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 96 + 32])[2], 17.0f);
+
+ // b[2].some
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 144 + 0])[0], 2.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 144 + 0])[1], 2.0f);
+
+ // b[2].other[0]
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 144 + 8])[0], 10.0f);
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 144 + 8])[1], 20.0f);
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 144 + 8])[2], 30.0f);
+
+ // b[2].variables
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 144 + 32])[0], 3.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 144 + 32])[1], 15.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 144 + 32])[2], 17.0f);
+
+ // c.stuff
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 192 + 0])[0], 1.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 192 + 0])[1], 2.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 192 + 0])[2], 3.0f);
+
+ // c.more_stuff
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 192 + 12])[0], 4.0f);
+
+ // tessLevelOuter
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 208 + 0])[0], 1.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 208 + 0])[1], 2.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 208 + 0])[2], 3.0f);
+
+ // tessLevelInner
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 224 + 0])[0], 5.0f);
+
+ // pointSize[0]
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 232 + 0])[0], 10.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 232 + 0])[1], 11.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 232 + 0])[2], 12.0f);
+
+ // clipDistance[0][0]
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 0])[0], 20.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 0])[1], 40.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 0])[2], 60.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 0])[3], 80.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 0])[4], 100.0f);
+
+ // clipDistance[1][0]
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 20])[0], 21.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 20])[1], 41.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 20])[2], 61.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 20])[3], 81.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 20])[4], 101.0f);
+
+ // clipDistance[2][0]
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 40])[0], 22.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 40])[1], 42.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 40])[2], 62.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 40])[3], 82.0f);
+ QCOMPARE(reinterpret_cast<const float *>(&bufferReadResult.data.constData()[16 + 244 + 40])[4], 102.0f);
+
+ // patchVerticesIn
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 316 + 0])[0], 3);
+
+ // primitiveID
+ QCOMPARE(reinterpret_cast<const int *>(&bufferReadResult.data.constData()[16 + 320 + 0])[0], 0);
+}
+
void tst_QRhi::storageBuffer_data()
{
rhiTestData();