diff options
author | Ben Fletcher <ben.fletcher@me.com> | 2023-02-16 16:46:22 -0800 |
---|---|---|
committer | Ben Fletcher <ben.fletcher@me.com> | 2023-03-09 15:24:45 +0000 |
commit | 4201cdab1886b5c39fe722a95e1b0ba462b63b71 (patch) | |
tree | 242bec9a73e8bb0e09fbdc74c8f2fabd98b7eba1 | |
parent | 17c9001a53a2377cc38bdf4b1982cab2dca94573 (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.cpp | 9 | ||||
-rw-r--r-- | src/gui/rhi/qrhimetal.mm | 448 | ||||
-rw-r--r-- | src/gui/rhi/qshader.cpp | 1 | ||||
-rw-r--r-- | src/gui/rhi/qshader_p_p.h | 3 | ||||
-rw-r--r-- | src/gui/rhi/qshaderdescription.cpp | 165 | ||||
-rw-r--r-- | src/gui/rhi/qshaderdescription_p.h | 33 | ||||
-rw-r--r-- | tests/auto/gui/rhi/qrhi/data/buildshaders.bat | 4 | ||||
-rw-r--r-- | tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.frag.qsb | bin | 0 -> 590 bytes | |||
-rw-r--r-- | tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tesc | 56 | ||||
-rw-r--r-- | tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tesc.qsb | bin | 0 -> 2873 bytes | |||
-rw-r--r-- | tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tese | 96 | ||||
-rw-r--r-- | tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tese.qsb | bin | 0 -> 4526 bytes | |||
-rw-r--r-- | tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.vert | 20 | ||||
-rw-r--r-- | tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.vert.qsb | bin | 0 -> 1306 bytes | |||
-rw-r--r-- | tests/auto/gui/rhi/qrhi/tst_qrhi.cpp | 318 |
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 Binary files differnew file mode 100644 index 0000000000..7eda4bed2d --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.frag.qsb 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 Binary files differnew file mode 100644 index 0000000000..b503d596c6 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tesc.qsb 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 Binary files differnew file mode 100644 index 0000000000..898bda454a --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.tese.qsb 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 Binary files differnew file mode 100644 index 0000000000..07384d643c --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/tessinterfaceblocks.vert.qsb 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(); |