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>bb10
parent
17c9001a53
commit
4201cdab18
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
teseInVars.remove(tescOutVar.location);
|
||||
}
|
||||
|
||||
const QVector<QShaderDescription::BuiltinVariable> tescOutBuiltins = compTesc.desc.outputBuiltinVariables();
|
||||
const QVector<QShaderDescription::BuiltinVariable> teseInBuiltins = vertTese.desc.inputBuiltinVariables();
|
||||
for (const QShaderDescription::InOutVariable &teseInVar : teseInVars)
|
||||
qWarning() << "missing tessellation control output for tessellation evaluation input:" << teseInVar;
|
||||
|
||||
// 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.
|
||||
// tesc output builtins in ascending location order
|
||||
QMap<QShaderDescription::BuiltinType, QShaderDescription::BuiltinVariable> tescOutBuiltins;
|
||||
for (const auto &tescOutBuiltin : compTesc.desc.outputBuiltinVariables())
|
||||
tescOutBuiltins[tescOutBuiltin.type] = tescOutBuiltin;
|
||||
|
||||
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);
|
||||
}
|
||||
// tese input builtins in ascending location order
|
||||
QMap<QShaderDescription::BuiltinType, QShaderDescription::BuiltinVariable> teseInBuiltins;
|
||||
for (const auto &teseInBuiltin : vertTese.desc.inputBuiltinVariables())
|
||||
teseInBuiltins[teseInBuiltin.type] = teseInBuiltin;
|
||||
|
||||
// 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");
|
||||
}
|
||||
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
|
||||
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 (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 {
|
||||
loc0 = ++lastLocation; // float4 gl_TessLevelOuter
|
||||
loc1 = ++lastLocation; // float2 gl_TessLevelInner
|
||||
addUnusedVertexAttribute(variable, rhiD, *offset, *alignment);
|
||||
}
|
||||
if (loc0 >= 0) {
|
||||
vertexDesc.attributes[loc0].bufferIndex = tessFactorBufferBinding;
|
||||
vertexDesc.attributes[loc0].format = MTLVertexFormatHalf4;
|
||||
vertexDesc.attributes[loc0].offset = 0;
|
||||
}
|
||||
if (loc1 >= 0) {
|
||||
vertexDesc.attributes[loc1].bufferIndex = tessFactorBufferBinding;
|
||||
vertexDesc.attributes[loc1].format = MTLVertexFormatHalf2;
|
||||
vertexDesc.attributes[loc1].offset = 8;
|
||||
}
|
||||
vertexDesc.layouts[tessFactorBufferBinding].stepFunction = MTLVertexStepFunctionPerPatch;
|
||||
vertexDesc.layouts[tessFactorBufferBinding].stride = trianglesMode ? 8 : 12;
|
||||
|
||||
teseInBuiltins.remove(builtin.type);
|
||||
}
|
||||
|
||||
if (offsetInTescOutput > 0) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
static void serializeBuiltinVar(QDataStream *stream, const QShaderDescription::BuiltinVariable &v, int version)
|
||||
{
|
||||
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)
|
||||
{
|
||||
(*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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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;
|
||||
}
|
||||
Binary file not shown.
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
Binary file not shown.
|
|
@ -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;
|
||||
}
|
||||
Binary file not shown.
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in New Issue