| /*------------------------------------------------------------------------ |
| * Vulkan Conformance Tests |
| * ------------------------ |
| * |
| * Copyright (c) 2014 The Android Open Source Project |
| * Copyright (c) 2016 The Khronos Group Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| *//*! |
| * \file |
| * \brief Tessellation User Defined IO Tests |
| *//*--------------------------------------------------------------------*/ |
| |
| #include "vktTessellationUserDefinedIO.hpp" |
| #include "vktTestCaseUtil.hpp" |
| #include "vktTessellationUtil.hpp" |
| |
| #include "tcuTestLog.hpp" |
| #include "tcuImageCompare.hpp" |
| #include "tcuImageIO.hpp" |
| |
| #include "gluVarType.hpp" |
| #include "gluVarTypeUtil.hpp" |
| |
| #include "vkDefs.hpp" |
| #include "vkBarrierUtil.hpp" |
| #include "vkQueryUtil.hpp" |
| #include "vkImageUtil.hpp" |
| #include "vkBuilderUtil.hpp" |
| #include "vkTypeUtil.hpp" |
| #include "vkCmdUtil.hpp" |
| #include "vkObjUtil.hpp" |
| |
| #include "deUniquePtr.hpp" |
| #include "deSharedPtr.hpp" |
| |
| namespace vkt |
| { |
| namespace tessellation |
| { |
| |
| using namespace vk; |
| |
| namespace |
| { |
| |
| enum Constants |
| { |
| NUM_PER_PATCH_BLOCKS = 2, |
| NUM_PER_PATCH_ARRAY_ELEMS = 3, |
| NUM_OUTPUT_VERTICES = 5, |
| NUM_TESS_LEVELS = 6, |
| MAX_TESSELLATION_PATCH_SIZE = 32, |
| RENDER_SIZE = 256, |
| }; |
| |
| enum IOType |
| { |
| IO_TYPE_PER_PATCH = 0, |
| IO_TYPE_PER_PATCH_ARRAY, |
| IO_TYPE_PER_PATCH_BLOCK, |
| IO_TYPE_PER_PATCH_BLOCK_ARRAY, |
| IO_TYPE_PER_VERTEX, |
| IO_TYPE_PER_VERTEX_BLOCK, |
| |
| IO_TYPE_LAST |
| }; |
| |
| enum VertexIOArraySize |
| { |
| VERTEX_IO_ARRAY_SIZE_IMPLICIT = 0, |
| VERTEX_IO_ARRAY_SIZE_EXPLICIT_SHADER_BUILTIN, //!< Use gl_MaxPatchVertices as size for per-vertex input array. |
| VERTEX_IO_ARRAY_SIZE_EXPLICIT_SPEC_MIN, //!< Minimum maxTessellationPatchSize required by the spec. |
| |
| VERTEX_IO_ARRAY_SIZE_LAST |
| }; |
| |
| struct CaseDefinition |
| { |
| TessPrimitiveType primitiveType; |
| IOType ioType; |
| VertexIOArraySize vertexIOArraySize; |
| std::string referenceImagePath; |
| }; |
| |
| typedef std::string (*BasicTypeVisitFunc)(const std::string& name, glu::DataType type, int indentationDepth); //!< See glslTraverseBasicTypes below. |
| |
| class TopLevelObject |
| { |
| public: |
| virtual ~TopLevelObject (void) {} |
| |
| virtual std::string name (void) const = 0; |
| virtual std::string declare (void) const = 0; |
| virtual std::string declareArray (const std::string& arraySizeExpr) const = 0; |
| virtual std::string glslTraverseBasicTypeArray (const int numArrayElements, //!< If negative, traverse just array[gl_InvocationID], not all indices. |
| const int indentationDepth, |
| BasicTypeVisitFunc) const = 0; |
| virtual std::string glslTraverseBasicType (const int indentationDepth, |
| BasicTypeVisitFunc) const = 0; |
| virtual int numBasicSubobjectsInElementType (void) const = 0; |
| virtual std::string basicSubobjectAtIndex (const int index, const int arraySize) const = 0; |
| }; |
| |
| std::string glslTraverseBasicTypes (const std::string& rootName, |
| const glu::VarType& rootType, |
| const int arrayNestingDepth, |
| const int indentationDepth, |
| const BasicTypeVisitFunc visit) |
| { |
| if (rootType.isBasicType()) |
| return visit(rootName, rootType.getBasicType(), indentationDepth); |
| else if (rootType.isArrayType()) |
| { |
| const std::string indentation = std::string(indentationDepth, '\t'); |
| const std::string loopIndexName = "i" + de::toString(arrayNestingDepth); |
| const std::string arrayLength = de::toString(rootType.getArraySize()); |
| return indentation + "for (int " + loopIndexName + " = 0; " + loopIndexName + " < " + de::toString(rootType.getArraySize()) + "; ++" + loopIndexName + ")\n" + |
| indentation + "{\n" + |
| glslTraverseBasicTypes(rootName + "[" + loopIndexName + "]", rootType.getElementType(), arrayNestingDepth+1, indentationDepth+1, visit) + |
| indentation + "}\n"; |
| } |
| else if (rootType.isStructType()) |
| { |
| const glu::StructType& structType = *rootType.getStructPtr(); |
| const int numMembers = structType.getNumMembers(); |
| std::string result; |
| |
| for (int membNdx = 0; membNdx < numMembers; ++membNdx) |
| { |
| const glu::StructMember& member = structType.getMember(membNdx); |
| result += glslTraverseBasicTypes(rootName + "." + member.getName(), member.getType(), arrayNestingDepth, indentationDepth, visit); |
| } |
| |
| return result; |
| } |
| else |
| { |
| DE_ASSERT(false); |
| return DE_NULL; |
| } |
| } |
| |
| //! Used as the 'visit' argument for glslTraverseBasicTypes. |
| std::string glslAssignBasicTypeObject (const std::string& name, const glu::DataType type, const int indentationDepth) |
| { |
| const int scalarSize = glu::getDataTypeScalarSize(type); |
| const std::string indentation = std::string(indentationDepth, '\t'); |
| std::ostringstream result; |
| |
| result << indentation << name << " = "; |
| |
| if (type != glu::TYPE_FLOAT) |
| result << std::string() << glu::getDataTypeName(type) << "("; |
| for (int i = 0; i < scalarSize; ++i) |
| result << (i > 0 ? ", v+" + de::floatToString(0.8f*(float)i, 1) : "v"); |
| if (type != glu::TYPE_FLOAT) |
| result << ")"; |
| result << ";\n" |
| << indentation << "v += 0.4;\n"; |
| return result.str(); |
| } |
| |
| //! Used as the 'visit' argument for glslTraverseBasicTypes. |
| std::string glslCheckBasicTypeObject (const std::string& name, const glu::DataType type, const int indentationDepth) |
| { |
| const int scalarSize = glu::getDataTypeScalarSize(type); |
| const std::string indentation = std::string(indentationDepth, '\t'); |
| std::ostringstream result; |
| |
| result << indentation << "allOk = allOk && compare_" << glu::getDataTypeName(type) << "(" << name << ", "; |
| |
| if (type != glu::TYPE_FLOAT) |
| result << std::string() << glu::getDataTypeName(type) << "("; |
| for (int i = 0; i < scalarSize; ++i) |
| result << (i > 0 ? ", v+" + de::floatToString(0.8f*(float)i, 1) : "v"); |
| if (type != glu::TYPE_FLOAT) |
| result << ")"; |
| result << ");\n" |
| << indentation << "v += 0.4;\n" |
| << indentation << "if (allOk) ++firstFailedInputIndex;\n"; |
| |
| return result.str(); |
| } |
| |
| int numBasicSubobjectsInElementType (const std::vector<de::SharedPtr<TopLevelObject> >& objects) |
| { |
| int result = 0; |
| for (int i = 0; i < static_cast<int>(objects.size()); ++i) |
| result += objects[i]->numBasicSubobjectsInElementType(); |
| return result; |
| } |
| |
| std::string basicSubobjectAtIndex (const int subobjectIndex, const std::vector<de::SharedPtr<TopLevelObject> >& objects, const int topLevelArraySize) |
| { |
| int currentIndex = 0; |
| int objectIndex = 0; |
| |
| for (; currentIndex < subobjectIndex; ++objectIndex) |
| currentIndex += objects[objectIndex]->numBasicSubobjectsInElementType() * topLevelArraySize; |
| |
| if (currentIndex > subobjectIndex) |
| { |
| --objectIndex; |
| currentIndex -= objects[objectIndex]->numBasicSubobjectsInElementType() * topLevelArraySize; |
| } |
| |
| return objects[objectIndex]->basicSubobjectAtIndex(subobjectIndex - currentIndex, topLevelArraySize); |
| } |
| |
| int numBasicSubobjects (const glu::VarType& type) |
| { |
| if (type.isBasicType()) |
| return 1; |
| else if (type.isArrayType()) |
| return type.getArraySize()*numBasicSubobjects(type.getElementType()); |
| else if (type.isStructType()) |
| { |
| const glu::StructType& structType = *type.getStructPtr(); |
| int result = 0; |
| for (int i = 0; i < structType.getNumMembers(); ++i) |
| result += numBasicSubobjects(structType.getMember(i).getType()); |
| return result; |
| } |
| else |
| { |
| DE_ASSERT(false); |
| return -1; |
| } |
| } |
| |
| class Variable : public TopLevelObject |
| { |
| public: |
| Variable (const std::string& name_, const glu::VarType& type, const bool isArray) |
| : m_name (name_) |
| , m_type (type) |
| , m_isArray (isArray) |
| { |
| DE_ASSERT(!type.isArrayType()); |
| } |
| |
| std::string name (void) const { return m_name; } |
| std::string declare (void) const; |
| std::string declareArray (const std::string& arraySizeExpr) const; |
| std::string glslTraverseBasicTypeArray (const int numArrayElements, const int indentationDepth, BasicTypeVisitFunc) const; |
| std::string glslTraverseBasicType (const int indentationDepth, BasicTypeVisitFunc) const; |
| int numBasicSubobjectsInElementType (void) const; |
| std::string basicSubobjectAtIndex (const int index, const int arraySize) const; |
| |
| private: |
| std::string m_name; |
| glu::VarType m_type; //!< If this Variable is an array element, m_type is the element type; otherwise just the variable type. |
| const bool m_isArray; |
| }; |
| |
| std::string Variable::declare (void) const |
| { |
| DE_ASSERT(!m_isArray); |
| return de::toString(glu::declare(m_type, m_name)) + ";\n"; |
| } |
| |
| std::string Variable::declareArray (const std::string& sizeExpr) const |
| { |
| DE_ASSERT(m_isArray); |
| return de::toString(glu::declare(m_type, m_name)) + "[" + sizeExpr + "];\n"; |
| } |
| |
| std::string Variable::glslTraverseBasicTypeArray (const int numArrayElements, const int indentationDepth, BasicTypeVisitFunc visit) const |
| { |
| DE_ASSERT(m_isArray); |
| |
| const bool traverseAsArray = numArrayElements >= 0; |
| const std::string traversedName = m_name + (!traverseAsArray ? "[gl_InvocationID]" : ""); |
| const glu::VarType type = traverseAsArray ? glu::VarType(m_type, numArrayElements) : m_type; |
| |
| return glslTraverseBasicTypes(traversedName, type, 0, indentationDepth, visit); |
| } |
| |
| std::string Variable::glslTraverseBasicType (const int indentationDepth, BasicTypeVisitFunc visit) const |
| { |
| DE_ASSERT(!m_isArray); |
| return glslTraverseBasicTypes(m_name, m_type, 0, indentationDepth, visit); |
| } |
| |
| int Variable::numBasicSubobjectsInElementType (void) const |
| { |
| return numBasicSubobjects(m_type); |
| } |
| |
| std::string Variable::basicSubobjectAtIndex (const int subobjectIndex, const int arraySize) const |
| { |
| const glu::VarType type = m_isArray ? glu::VarType(m_type, arraySize) : m_type; |
| int currentIndex = 0; |
| |
| for (glu::BasicTypeIterator basicIt = glu::BasicTypeIterator::begin(&type); basicIt != glu::BasicTypeIterator::end(&type); ++basicIt) |
| { |
| if (currentIndex == subobjectIndex) |
| return m_name + de::toString(glu::TypeAccessFormat(type, basicIt.getPath())); |
| ++currentIndex; |
| } |
| DE_ASSERT(false); |
| return DE_NULL; |
| } |
| |
| class IOBlock : public TopLevelObject |
| { |
| public: |
| struct Member |
| { |
| std::string name; |
| glu::VarType type; |
| |
| Member (const std::string& n, const glu::VarType& t) : name(n), type(t) {} |
| }; |
| |
| IOBlock (const std::string& blockName, const std::string& interfaceName, const std::vector<Member>& members) |
| : m_blockName (blockName) |
| , m_interfaceName (interfaceName) |
| , m_members (members) |
| { |
| } |
| |
| std::string name (void) const { return m_interfaceName; } |
| std::string declare (void) const; |
| std::string declareArray (const std::string& arraySizeExpr) const; |
| std::string glslTraverseBasicTypeArray (const int numArrayElements, const int indentationDepth, BasicTypeVisitFunc) const; |
| std::string glslTraverseBasicType (const int indentationDepth, BasicTypeVisitFunc) const; |
| int numBasicSubobjectsInElementType (void) const; |
| std::string basicSubobjectAtIndex (const int index, const int arraySize) const; |
| |
| private: |
| std::string m_blockName; |
| std::string m_interfaceName; |
| std::vector<Member> m_members; |
| }; |
| |
| std::string IOBlock::declare (void) const |
| { |
| std::ostringstream buf; |
| |
| buf << m_blockName << "\n" |
| << "{\n"; |
| |
| for (int i = 0; i < static_cast<int>(m_members.size()); ++i) |
| buf << "\t" << glu::declare(m_members[i].type, m_members[i].name) << ";\n"; |
| |
| buf << "} " << m_interfaceName << ";\n"; |
| return buf.str(); |
| } |
| |
| std::string IOBlock::declareArray (const std::string& sizeExpr) const |
| { |
| std::ostringstream buf; |
| |
| buf << m_blockName << "\n" |
| << "{\n"; |
| |
| for (int i = 0; i < static_cast<int>(m_members.size()); ++i) |
| buf << "\t" << glu::declare(m_members[i].type, m_members[i].name) << ";\n"; |
| |
| buf << "} " << m_interfaceName << "[" << sizeExpr << "];\n"; |
| return buf.str(); |
| } |
| |
| std::string IOBlock::glslTraverseBasicTypeArray (const int numArrayElements, const int indentationDepth, BasicTypeVisitFunc visit) const |
| { |
| if (numArrayElements >= 0) |
| { |
| const std::string indentation = std::string(indentationDepth, '\t'); |
| std::ostringstream result; |
| |
| result << indentation << "for (int i0 = 0; i0 < " << numArrayElements << "; ++i0)\n" |
| << indentation << "{\n"; |
| for (int i = 0; i < static_cast<int>(m_members.size()); ++i) |
| result << glslTraverseBasicTypes(m_interfaceName + "[i0]." + m_members[i].name, m_members[i].type, 1, indentationDepth + 1, visit); |
| result << indentation + "}\n"; |
| return result.str(); |
| } |
| else |
| { |
| std::ostringstream result; |
| for (int i = 0; i < static_cast<int>(m_members.size()); ++i) |
| result << glslTraverseBasicTypes(m_interfaceName + "[gl_InvocationID]." + m_members[i].name, m_members[i].type, 0, indentationDepth, visit); |
| return result.str(); |
| } |
| } |
| |
| std::string IOBlock::glslTraverseBasicType (const int indentationDepth, BasicTypeVisitFunc visit) const |
| { |
| std::ostringstream result; |
| for (int i = 0; i < static_cast<int>(m_members.size()); ++i) |
| result << glslTraverseBasicTypes(m_interfaceName + "." + m_members[i].name, m_members[i].type, 0, indentationDepth, visit); |
| return result.str(); |
| } |
| |
| int IOBlock::numBasicSubobjectsInElementType (void) const |
| { |
| int result = 0; |
| for (int i = 0; i < static_cast<int>(m_members.size()); ++i) |
| result += numBasicSubobjects(m_members[i].type); |
| return result; |
| } |
| |
| std::string IOBlock::basicSubobjectAtIndex (const int subobjectIndex, const int arraySize) const |
| { |
| int currentIndex = 0; |
| for (int arrayNdx = 0; arrayNdx < arraySize; ++arrayNdx) |
| for (int memberNdx = 0; memberNdx < static_cast<int>(m_members.size()); ++memberNdx) |
| { |
| const glu::VarType& membType = m_members[memberNdx].type; |
| for (glu::BasicTypeIterator basicIt = glu::BasicTypeIterator::begin(&membType); basicIt != glu::BasicTypeIterator::end(&membType); ++basicIt) |
| { |
| if (currentIndex == subobjectIndex) |
| return m_interfaceName + "[" + de::toString(arrayNdx) + "]." + m_members[memberNdx].name + de::toString(glu::TypeAccessFormat(membType, basicIt.getPath())); |
| currentIndex++; |
| } |
| } |
| DE_ASSERT(false); |
| return DE_NULL; |
| } |
| |
| class UserDefinedIOTest : public TestCase |
| { |
| public: |
| UserDefinedIOTest (tcu::TestContext& testCtx, const std::string& name, const std::string& description, const CaseDefinition caseDef); |
| void initPrograms (vk::SourceCollections& programCollection) const; |
| TestInstance* createInstance (Context& context) const; |
| |
| private: |
| const CaseDefinition m_caseDef; |
| std::vector<glu::StructType> m_structTypes; |
| std::vector<de::SharedPtr<TopLevelObject> > m_tcsOutputs; |
| std::vector<de::SharedPtr<TopLevelObject> > m_tesInputs; |
| std::string m_tcsDeclarations; |
| std::string m_tcsStatements; |
| std::string m_tesDeclarations; |
| std::string m_tesStatements; |
| }; |
| |
| UserDefinedIOTest::UserDefinedIOTest (tcu::TestContext& testCtx, const std::string& name, const std::string& description, const CaseDefinition caseDef) |
| : TestCase (testCtx, name, description) |
| , m_caseDef (caseDef) |
| { |
| const bool isPerPatchIO = m_caseDef.ioType == IO_TYPE_PER_PATCH || |
| m_caseDef.ioType == IO_TYPE_PER_PATCH_ARRAY || |
| m_caseDef.ioType == IO_TYPE_PER_PATCH_BLOCK || |
| m_caseDef.ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY; |
| |
| const bool isExplicitVertexArraySize = m_caseDef.vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_EXPLICIT_SHADER_BUILTIN || |
| m_caseDef.vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_EXPLICIT_SPEC_MIN; |
| |
| const std::string vertexAttrArrayInputSize = m_caseDef.vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_IMPLICIT ? "" |
| : m_caseDef.vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_EXPLICIT_SHADER_BUILTIN ? "gl_MaxPatchVertices" |
| : m_caseDef.vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_EXPLICIT_SPEC_MIN ? de::toString(MAX_TESSELLATION_PATCH_SIZE) |
| : DE_NULL; |
| |
| const char* const maybePatch = isPerPatchIO ? "patch " : ""; |
| const std::string outMaybePatch = std::string() + maybePatch + "out "; |
| const std::string inMaybePatch = std::string() + maybePatch + "in "; |
| const bool useBlock = m_caseDef.ioType == IO_TYPE_PER_VERTEX_BLOCK || |
| m_caseDef.ioType == IO_TYPE_PER_PATCH_BLOCK || |
| m_caseDef.ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY; |
| const int wrongNumElements = -2; |
| |
| std::ostringstream tcsDeclarations; |
| std::ostringstream tcsStatements; |
| std::ostringstream tesDeclarations; |
| std::ostringstream tesStatements; |
| |
| // Indices 0 and 1 are taken, see initPrograms() |
| int tcsNextOutputLocation = 2; |
| int tesNextInputLocation = 2; |
| |
| m_structTypes.push_back(glu::StructType("S")); |
| |
| const glu::VarType highpFloat (glu::TYPE_FLOAT, glu::PRECISION_HIGHP); |
| glu::StructType& structType = m_structTypes.back(); |
| const glu::VarType structVarType (&structType); |
| bool usedStruct = false; |
| |
| structType.addMember("x", glu::VarType(glu::TYPE_INT, glu::PRECISION_HIGHP)); |
| structType.addMember("y", glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP)); |
| |
| // It is illegal to have a structure containing an array as an output variable |
| if (useBlock) |
| structType.addMember("z", glu::VarType(highpFloat, 2)); |
| |
| if (useBlock) |
| { |
| std::vector<IOBlock::Member> blockMembers; |
| |
| // use leaner block to make sure it is not larger than allowed (per-patch storage is very limited) |
| const bool useLightweightBlock = (m_caseDef.ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY); |
| |
| if (!useLightweightBlock) |
| blockMembers.push_back(IOBlock::Member("blockS", structVarType)); |
| |
| blockMembers.push_back(IOBlock::Member("blockFa", glu::VarType(highpFloat, 3))); |
| blockMembers.push_back(IOBlock::Member("blockSa", glu::VarType(structVarType, 2))); |
| blockMembers.push_back(IOBlock::Member("blockF", highpFloat)); |
| |
| m_tcsOutputs.push_back (de::SharedPtr<TopLevelObject>(new IOBlock("TheBlock", "tcBlock", blockMembers))); |
| m_tesInputs.push_back (de::SharedPtr<TopLevelObject>(new IOBlock("TheBlock", "teBlock", blockMembers))); |
| |
| usedStruct = true; |
| } |
| else |
| { |
| const Variable var0("in_te_s", structVarType, m_caseDef.ioType != IO_TYPE_PER_PATCH); |
| const Variable var1("in_te_f", highpFloat, m_caseDef.ioType != IO_TYPE_PER_PATCH); |
| |
| if (m_caseDef.ioType != IO_TYPE_PER_PATCH_ARRAY) |
| { |
| // Arrays of structures are disallowed, add struct cases only if not arrayed variable |
| m_tcsOutputs.push_back (de::SharedPtr<TopLevelObject>(new Variable(var0))); |
| m_tesInputs.push_back (de::SharedPtr<TopLevelObject>(new Variable(var0))); |
| |
| usedStruct = true; |
| } |
| |
| m_tcsOutputs.push_back (de::SharedPtr<TopLevelObject>(new Variable(var1))); |
| m_tesInputs.push_back (de::SharedPtr<TopLevelObject>(new Variable(var1))); |
| } |
| |
| if (usedStruct) |
| tcsDeclarations << de::toString(glu::declare(structType)) + ";\n"; |
| |
| tcsStatements << "\t{\n" |
| << "\t\thighp float v = 1.3;\n"; |
| |
| for (int tcsOutputNdx = 0; tcsOutputNdx < static_cast<int>(m_tcsOutputs.size()); ++tcsOutputNdx) |
| { |
| const TopLevelObject& output = *m_tcsOutputs[tcsOutputNdx]; |
| const int numElements = !isPerPatchIO ? -1 //!< \note -1 means indexing with gl_InstanceID |
| : m_caseDef.ioType == IO_TYPE_PER_PATCH ? 1 |
| : m_caseDef.ioType == IO_TYPE_PER_PATCH_ARRAY ? NUM_PER_PATCH_ARRAY_ELEMS |
| : m_caseDef.ioType == IO_TYPE_PER_PATCH_BLOCK ? 1 |
| : m_caseDef.ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY ? NUM_PER_PATCH_BLOCKS |
| : wrongNumElements; |
| const bool isArray = (numElements != 1); |
| |
| DE_ASSERT(numElements != wrongNumElements); |
| |
| // \note: TCS output arrays are always implicitly-sized |
| tcsDeclarations << "layout(location = " << tcsNextOutputLocation << ") "; |
| if (isArray) |
| tcsDeclarations << outMaybePatch << output.declareArray(m_caseDef.ioType == IO_TYPE_PER_PATCH_ARRAY ? de::toString(NUM_PER_PATCH_ARRAY_ELEMS) |
| : m_caseDef.ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY ? de::toString(NUM_PER_PATCH_BLOCKS) |
| : ""); |
| else |
| tcsDeclarations << outMaybePatch << output.declare(); |
| |
| tcsNextOutputLocation += output.numBasicSubobjectsInElementType(); |
| |
| if (!isPerPatchIO) |
| tcsStatements << "\t\tv += float(gl_InvocationID)*" << de::floatToString(0.4f * (float)output.numBasicSubobjectsInElementType(), 1) << ";\n"; |
| |
| tcsStatements << "\n\t\t// Assign values to output " << output.name() << "\n"; |
| if (isArray) |
| tcsStatements << output.glslTraverseBasicTypeArray(numElements, 2, glslAssignBasicTypeObject); |
| else |
| tcsStatements << output.glslTraverseBasicType(2, glslAssignBasicTypeObject); |
| |
| if (!isPerPatchIO) |
| tcsStatements << "\t\tv += float(" << de::toString(NUM_OUTPUT_VERTICES) << "-gl_InvocationID-1)*" << de::floatToString(0.4f * (float)output.numBasicSubobjectsInElementType(), 1) << ";\n"; |
| } |
| tcsStatements << "\t}\n"; |
| |
| tcsDeclarations << "\n" |
| << "layout(location = 0) in " + Variable("in_tc_attr", highpFloat, true).declareArray(vertexAttrArrayInputSize); |
| |
| if (usedStruct) |
| tesDeclarations << de::toString(glu::declare(structType)) << ";\n"; |
| |
| tesStatements << "\tbool allOk = true;\n" |
| << "\thighp uint firstFailedInputIndex = 0u;\n" |
| << "\t{\n" |
| << "\t\thighp float v = 1.3;\n"; |
| |
| for (int tesInputNdx = 0; tesInputNdx < static_cast<int>(m_tesInputs.size()); ++tesInputNdx) |
| { |
| const TopLevelObject& input = *m_tesInputs[tesInputNdx]; |
| const int numElements = !isPerPatchIO ? NUM_OUTPUT_VERTICES |
| : m_caseDef.ioType == IO_TYPE_PER_PATCH ? 1 |
| : m_caseDef.ioType == IO_TYPE_PER_PATCH_BLOCK ? 1 |
| : m_caseDef.ioType == IO_TYPE_PER_PATCH_ARRAY ? NUM_PER_PATCH_ARRAY_ELEMS |
| : m_caseDef.ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY ? NUM_PER_PATCH_BLOCKS |
| : wrongNumElements; |
| const bool isArray = (numElements != 1); |
| |
| DE_ASSERT(numElements != wrongNumElements); |
| |
| tesDeclarations << "layout(location = " << tesNextInputLocation << ") "; |
| if (isArray) |
| tesDeclarations << inMaybePatch << input.declareArray(m_caseDef.ioType == IO_TYPE_PER_PATCH_ARRAY ? de::toString(NUM_PER_PATCH_ARRAY_ELEMS) |
| : m_caseDef.ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY ? de::toString(NUM_PER_PATCH_BLOCKS) |
| : isExplicitVertexArraySize ? de::toString(vertexAttrArrayInputSize) |
| : ""); |
| else |
| tesDeclarations << inMaybePatch + input.declare(); |
| |
| tesNextInputLocation += input.numBasicSubobjectsInElementType(); |
| |
| tesStatements << "\n\t\t// Check values in input " << input.name() << "\n"; |
| if (isArray) |
| tesStatements << input.glslTraverseBasicTypeArray(numElements, 2, glslCheckBasicTypeObject); |
| else |
| tesStatements << input.glslTraverseBasicType(2, glslCheckBasicTypeObject); |
| } |
| tesStatements << "\t}\n"; |
| |
| m_tcsDeclarations = tcsDeclarations.str(); |
| m_tcsStatements = tcsStatements.str(); |
| m_tesDeclarations = tesDeclarations.str(); |
| m_tesStatements = tesStatements.str(); |
| } |
| |
| void UserDefinedIOTest::initPrograms (vk::SourceCollections& programCollection) const |
| { |
| // Vertex shader |
| { |
| std::ostringstream src; |
| src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n" |
| << "\n" |
| << "layout(location = 0) in highp float in_v_attr;\n" |
| << "layout(location = 0) out highp float in_tc_attr;\n" |
| << "\n" |
| << "void main (void)\n" |
| << "{\n" |
| << " in_tc_attr = in_v_attr;\n" |
| << "}\n"; |
| |
| programCollection.glslSources.add("vert") << glu::VertexSource(src.str()); |
| } |
| |
| // Tessellation control shader |
| { |
| std::ostringstream src; |
| src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n" |
| << "#extension GL_EXT_tessellation_shader : require\n" |
| << "\n" |
| << "layout(vertices = " << NUM_OUTPUT_VERTICES << ") out;\n" |
| << "\n" |
| << "layout(location = 0) patch out highp vec2 in_te_positionScale;\n" |
| << "layout(location = 1) patch out highp vec2 in_te_positionOffset;\n" |
| << "\n" |
| << m_tcsDeclarations |
| << "\n" |
| << "void main (void)\n" |
| << "{\n" |
| << m_tcsStatements |
| << "\n" |
| << " gl_TessLevelInner[0] = in_tc_attr[0];\n" |
| << " gl_TessLevelInner[1] = in_tc_attr[1];\n" |
| << "\n" |
| << " gl_TessLevelOuter[0] = in_tc_attr[2];\n" |
| << " gl_TessLevelOuter[1] = in_tc_attr[3];\n" |
| << " gl_TessLevelOuter[2] = in_tc_attr[4];\n" |
| << " gl_TessLevelOuter[3] = in_tc_attr[5];\n" |
| << "\n" |
| << " in_te_positionScale = vec2(in_tc_attr[6], in_tc_attr[7]);\n" |
| << " in_te_positionOffset = vec2(in_tc_attr[8], in_tc_attr[9]);\n" |
| << "}\n"; |
| |
| programCollection.glslSources.add("tesc") << glu::TessellationControlSource(src.str()); |
| } |
| |
| // Tessellation evaluation shader |
| { |
| std::ostringstream src; |
| src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n" |
| << "#extension GL_EXT_tessellation_shader : require\n" |
| << "\n" |
| << "layout(" << getTessPrimitiveTypeShaderName(m_caseDef.primitiveType) << ") in;\n" |
| << "\n" |
| << "layout(location = 0) patch in highp vec2 in_te_positionScale;\n" |
| << "layout(location = 1) patch in highp vec2 in_te_positionOffset;\n" |
| << "\n" |
| << m_tesDeclarations |
| << "\n" |
| << "layout(location = 0) out highp vec4 in_f_color;\n" |
| << "\n" |
| << "// Will contain the index of the first incorrect input,\n" |
| << "// or the number of inputs if all are correct\n" |
| << "layout (set = 0, binding = 0, std430) coherent restrict buffer Output {\n" |
| << " int numInvocations;\n" |
| << " uint firstFailedInputIndex[];\n" |
| << "} sb_out;\n" |
| << "\n" |
| << "bool compare_int (int a, int b) { return a == b; }\n" |
| << "bool compare_float (float a, float b) { return abs(a - b) < 0.01f; }\n" |
| << "bool compare_vec4 (vec4 a, vec4 b) { return all(lessThan(abs(a - b), vec4(0.01f))); }\n" |
| << "\n" |
| << "void main (void)\n" |
| << "{\n" |
| << m_tesStatements |
| << "\n" |
| << " gl_Position = vec4(gl_TessCoord.xy*in_te_positionScale + in_te_positionOffset, 0.0, 1.0);\n" |
| << " in_f_color = allOk ? vec4(0.0, 1.0, 0.0, 1.0)\n" |
| << " : vec4(1.0, 0.0, 0.0, 1.0);\n" |
| << "\n" |
| << " int index = atomicAdd(sb_out.numInvocations, 1);\n" |
| << " sb_out.firstFailedInputIndex[index] = firstFailedInputIndex;\n" |
| << "}\n"; |
| |
| programCollection.glslSources.add("tese") << glu::TessellationEvaluationSource(src.str()); |
| } |
| |
| // Fragment shader |
| { |
| std::ostringstream src; |
| src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n" |
| << "\n" |
| << "layout(location = 0) in highp vec4 in_f_color;\n" |
| << "layout(location = 0) out mediump vec4 o_color;\n" |
| << "\n" |
| << "void main (void)\n" |
| << "{\n" |
| << " o_color = in_f_color;\n" |
| << "}\n"; |
| |
| programCollection.glslSources.add("frag") << glu::FragmentSource(src.str()); |
| } |
| } |
| |
| class UserDefinedIOTestInstance : public TestInstance |
| { |
| public: |
| UserDefinedIOTestInstance (Context& context, |
| const CaseDefinition caseDef, |
| const std::vector<de::SharedPtr<TopLevelObject> >& tesInputs); |
| tcu::TestStatus iterate (void); |
| |
| private: |
| const CaseDefinition m_caseDef; |
| const std::vector<de::SharedPtr<TopLevelObject> > m_tesInputs; |
| }; |
| |
| UserDefinedIOTestInstance::UserDefinedIOTestInstance (Context& context, const CaseDefinition caseDef, const std::vector<de::SharedPtr<TopLevelObject> >& tesInputs) |
| : TestInstance (context) |
| , m_caseDef (caseDef) |
| , m_tesInputs (tesInputs) |
| { |
| } |
| |
| tcu::TestStatus UserDefinedIOTestInstance::iterate (void) |
| { |
| requireFeatures(m_context.getInstanceInterface(), m_context.getPhysicalDevice(), FEATURE_TESSELLATION_SHADER | FEATURE_VERTEX_PIPELINE_STORES_AND_ATOMICS); |
| |
| const DeviceInterface& vk = m_context.getDeviceInterface(); |
| const VkDevice device = m_context.getDevice(); |
| const VkQueue queue = m_context.getUniversalQueue(); |
| const deUint32 queueFamilyIndex = m_context.getUniversalQueueFamilyIndex(); |
| Allocator& allocator = m_context.getDefaultAllocator(); |
| |
| const int numAttributes = NUM_TESS_LEVELS + 2 + 2; |
| static const float attributes[numAttributes] = { /* inner */ 3.0f, 4.0f, /* outer */ 5.0f, 6.0f, 7.0f, 8.0f, /* pos. scale */ 1.2f, 1.3f, /* pos. offset */ -0.3f, -0.4f }; |
| const int refNumVertices = referenceVertexCount(m_caseDef.primitiveType, SPACINGMODE_EQUAL, false, &attributes[0], &attributes[2]); |
| const int refNumUniqueVertices = referenceVertexCount(m_caseDef.primitiveType, SPACINGMODE_EQUAL, true, &attributes[0], &attributes[2]); |
| |
| // Vertex input attributes buffer: to pass tessellation levels |
| |
| const VkFormat vertexFormat = VK_FORMAT_R32_SFLOAT; |
| const deUint32 vertexStride = tcu::getPixelSize(mapVkFormat(vertexFormat)); |
| const VkDeviceSize vertexDataSizeBytes = numAttributes * vertexStride; |
| const Buffer vertexBuffer (vk, device, allocator, makeBufferCreateInfo(vertexDataSizeBytes, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT), MemoryRequirement::HostVisible); |
| |
| { |
| const Allocation& alloc = vertexBuffer.getAllocation(); |
| deMemcpy(alloc.getHostPtr(), &attributes[0], static_cast<std::size_t>(vertexDataSizeBytes)); |
| flushMappedMemoryRange(vk, device, alloc.getMemory(), alloc.getOffset(), vertexDataSizeBytes); |
| } |
| |
| // Output buffer: number of invocations and verification indices |
| |
| const int resultBufferMaxVertices = refNumVertices; |
| const VkDeviceSize resultBufferSizeBytes = sizeof(deInt32) + resultBufferMaxVertices * sizeof(deUint32); |
| const Buffer resultBuffer (vk, device, allocator, makeBufferCreateInfo(resultBufferSizeBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT), MemoryRequirement::HostVisible); |
| |
| { |
| const Allocation& alloc = resultBuffer.getAllocation(); |
| deMemset(alloc.getHostPtr(), 0, static_cast<std::size_t>(resultBufferSizeBytes)); |
| flushMappedMemoryRange(vk, device, alloc.getMemory(), alloc.getOffset(), resultBufferSizeBytes); |
| } |
| |
| // Color attachment |
| |
| const tcu::IVec2 renderSize = tcu::IVec2(RENDER_SIZE, RENDER_SIZE); |
| const VkFormat colorFormat = VK_FORMAT_R8G8B8A8_UNORM; |
| const VkImageSubresourceRange colorImageSubresourceRange = makeImageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u); |
| const Image colorAttachmentImage (vk, device, allocator, |
| makeImageCreateInfo(renderSize, colorFormat, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, 1u), |
| MemoryRequirement::Any); |
| |
| // Color output buffer: image will be copied here for verification |
| |
| const VkDeviceSize colorBufferSizeBytes = renderSize.x()*renderSize.y() * tcu::getPixelSize(mapVkFormat(colorFormat)); |
| const Buffer colorBuffer (vk, device, allocator, makeBufferCreateInfo(colorBufferSizeBytes, VK_BUFFER_USAGE_TRANSFER_DST_BIT), MemoryRequirement::HostVisible); |
| |
| // Descriptors |
| |
| const Unique<VkDescriptorSetLayout> descriptorSetLayout(DescriptorSetLayoutBuilder() |
| .addSingleBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT) |
| .build(vk, device)); |
| |
| const Unique<VkDescriptorPool> descriptorPool(DescriptorPoolBuilder() |
| .addType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER) |
| .build(vk, device, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 1u)); |
| |
| const Unique<VkDescriptorSet> descriptorSet (makeDescriptorSet(vk, device, *descriptorPool, *descriptorSetLayout)); |
| const VkDescriptorBufferInfo resultBufferInfo = makeDescriptorBufferInfo(resultBuffer.get(), 0ull, resultBufferSizeBytes); |
| |
| DescriptorSetUpdateBuilder() |
| .writeSingle(*descriptorSet, DescriptorSetUpdateBuilder::Location::binding(0u), VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, &resultBufferInfo) |
| .update(vk, device); |
| |
| // Pipeline |
| |
| const Unique<VkImageView> colorAttachmentView(makeImageView(vk, device, *colorAttachmentImage, VK_IMAGE_VIEW_TYPE_2D, colorFormat, colorImageSubresourceRange)); |
| const Unique<VkRenderPass> renderPass (makeRenderPass(vk, device, colorFormat)); |
| const Unique<VkFramebuffer> framebuffer (makeFramebuffer(vk, device, *renderPass, *colorAttachmentView, renderSize.x(), renderSize.y(), 1u)); |
| const Unique<VkPipelineLayout> pipelineLayout (makePipelineLayout(vk, device, *descriptorSetLayout)); |
| const Unique<VkCommandPool> cmdPool (makeCommandPool(vk, device, queueFamilyIndex)); |
| const Unique<VkCommandBuffer> cmdBuffer (allocateCommandBuffer (vk, device, *cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY)); |
| |
| const Unique<VkPipeline> pipeline(GraphicsPipelineBuilder() |
| .setRenderSize (renderSize) |
| .setPatchControlPoints (numAttributes) |
| .setVertexInputSingleAttribute(vertexFormat, vertexStride) |
| .setShader (vk, device, VK_SHADER_STAGE_VERTEX_BIT, m_context.getBinaryCollection().get("vert"), DE_NULL) |
| .setShader (vk, device, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, m_context.getBinaryCollection().get("tesc"), DE_NULL) |
| .setShader (vk, device, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, m_context.getBinaryCollection().get("tese"), DE_NULL) |
| .setShader (vk, device, VK_SHADER_STAGE_FRAGMENT_BIT, m_context.getBinaryCollection().get("frag"), DE_NULL) |
| .build (vk, device, *pipelineLayout, *renderPass)); |
| |
| // Begin draw |
| |
| beginCommandBuffer(vk, *cmdBuffer); |
| |
| // Change color attachment image layout |
| { |
| const VkImageMemoryBarrier colorAttachmentLayoutBarrier = makeImageMemoryBarrier( |
| (VkAccessFlags)0, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, |
| VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, |
| *colorAttachmentImage, colorImageSubresourceRange); |
| |
| vk.cmdPipelineBarrier(*cmdBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0u, |
| 0u, DE_NULL, 0u, DE_NULL, 1u, &colorAttachmentLayoutBarrier); |
| } |
| |
| { |
| const VkRect2D renderArea = makeRect2D(renderSize); |
| const tcu::Vec4 clearColor(0.0f, 0.0f, 0.0f, 1.0f); |
| |
| beginRenderPass(vk, *cmdBuffer, *renderPass, *framebuffer, renderArea, clearColor); |
| } |
| |
| vk.cmdBindPipeline(*cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline); |
| vk.cmdBindDescriptorSets(*cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipelineLayout, 0u, 1u, &descriptorSet.get(), 0u, DE_NULL); |
| { |
| const VkDeviceSize vertexBufferOffset = 0ull; |
| vk.cmdBindVertexBuffers(*cmdBuffer, 0u, 1u, &vertexBuffer.get(), &vertexBufferOffset); |
| } |
| |
| vk.cmdDraw(*cmdBuffer, numAttributes, 1u, 0u, 0u); |
| endRenderPass(vk, *cmdBuffer); |
| |
| // Copy render result to a host-visible buffer |
| copyImageToBuffer(vk, *cmdBuffer, *colorAttachmentImage, *colorBuffer, renderSize); |
| |
| endCommandBuffer(vk, *cmdBuffer); |
| submitCommandsAndWait(vk, device, queue, *cmdBuffer); |
| |
| // Verification |
| |
| bool isImageCompareOK = false; |
| { |
| const Allocation& colorBufferAlloc = colorBuffer.getAllocation(); |
| invalidateMappedMemoryRange(vk, device, colorBufferAlloc.getMemory(), colorBufferAlloc.getOffset(), colorBufferSizeBytes); |
| |
| // Load reference image |
| tcu::TextureLevel referenceImage; |
| tcu::ImageIO::loadPNG(referenceImage, m_context.getTestContext().getArchive(), m_caseDef.referenceImagePath.c_str()); |
| |
| // Verify case result |
| const tcu::ConstPixelBufferAccess resultImageAccess(mapVkFormat(colorFormat), renderSize.x(), renderSize.y(), 1, colorBufferAlloc.getHostPtr()); |
| isImageCompareOK = tcu::fuzzyCompare(m_context.getTestContext().getLog(), "ImageComparison", "Image Comparison", |
| referenceImage.getAccess(), resultImageAccess, 0.02f, tcu::COMPARE_LOG_RESULT); |
| } |
| { |
| const Allocation& resultAlloc = resultBuffer.getAllocation(); |
| invalidateMappedMemoryRange(vk, device, resultAlloc.getMemory(), resultAlloc.getOffset(), resultBufferSizeBytes); |
| |
| const deInt32 numVertices = *static_cast<deInt32*>(resultAlloc.getHostPtr()); |
| const deUint32* const vertices = reinterpret_cast<deUint32*>(static_cast<deUint8*>(resultAlloc.getHostPtr()) + sizeof(deInt32)); |
| |
| // If this fails then we didn't read all vertices from shader and test must be changed to allow more. |
| DE_ASSERT(numVertices <= refNumVertices); |
| |
| if (numVertices < refNumUniqueVertices) |
| { |
| m_context.getTestContext().getLog() |
| << tcu::TestLog::Message << "Failure: got " << numVertices << " vertices, but expected at least " << refNumUniqueVertices << tcu::TestLog::EndMessage; |
| |
| return tcu::TestStatus::fail("Wrong number of vertices"); |
| } |
| else |
| { |
| tcu::TestLog& log = m_context.getTestContext().getLog(); |
| const int topLevelArraySize = (m_caseDef.ioType == IO_TYPE_PER_PATCH ? 1 |
| : m_caseDef.ioType == IO_TYPE_PER_PATCH_ARRAY ? NUM_PER_PATCH_ARRAY_ELEMS |
| : m_caseDef.ioType == IO_TYPE_PER_PATCH_BLOCK ? 1 |
| : m_caseDef.ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY ? NUM_PER_PATCH_BLOCKS |
| : NUM_OUTPUT_VERTICES); |
| const deUint32 numTEInputs = numBasicSubobjectsInElementType(m_tesInputs) * topLevelArraySize; |
| |
| for (int vertexNdx = 0; vertexNdx < numVertices; ++vertexNdx) |
| if (vertices[vertexNdx] > numTEInputs) |
| { |
| log << tcu::TestLog::Message |
| << "Failure: out_te_firstFailedInputIndex has value " << vertices[vertexNdx] |
| << ", but should be in range [0, " << numTEInputs << "]" << tcu::TestLog::EndMessage; |
| |
| return tcu::TestStatus::fail("Invalid values returned from shader"); |
| } |
| else if (vertices[vertexNdx] != numTEInputs) |
| { |
| log << tcu::TestLog::Message << "Failure: in tessellation evaluation shader, check for input " |
| << basicSubobjectAtIndex(vertices[vertexNdx], m_tesInputs, topLevelArraySize) << " failed" << tcu::TestLog::EndMessage; |
| |
| return tcu::TestStatus::fail("Invalid input value in tessellation evaluation shader"); |
| } |
| } |
| } |
| return (isImageCompareOK ? tcu::TestStatus::pass("OK") : tcu::TestStatus::fail("Image comparison failed")); |
| } |
| |
| TestInstance* UserDefinedIOTest::createInstance (Context& context) const |
| { |
| return new UserDefinedIOTestInstance(context, m_caseDef, m_tesInputs); |
| } |
| |
| } // anonymous |
| |
| //! These tests correspond roughly to dEQP-GLES31.functional.tessellation.user_defined_io.* |
| //! Original GLES test queried maxTessellationPatchSize, but this can't be done at the stage the shader source is prepared. |
| //! Instead, we use minimum supported value. |
| //! Negative tests weren't ported because vktShaderLibrary doesn't support tests that are expected to fail shader compilation. |
| tcu::TestCaseGroup* createUserDefinedIOTests (tcu::TestContext& testCtx) |
| { |
| de::MovePtr<tcu::TestCaseGroup> group (new tcu::TestCaseGroup(testCtx, "user_defined_io", "Test non-built-in per-patch and per-vertex inputs and outputs")); |
| |
| static const struct |
| { |
| const char* name; |
| const char* description; |
| IOType ioType; |
| } ioCases[] = |
| { |
| { "per_patch", "Per-patch TCS outputs", IO_TYPE_PER_PATCH }, |
| { "per_patch_array", "Per-patch array TCS outputs", IO_TYPE_PER_PATCH_ARRAY }, |
| { "per_patch_block", "Per-patch TCS outputs in IO block", IO_TYPE_PER_PATCH_BLOCK }, |
| { "per_patch_block_array", "Per-patch TCS outputs in IO block array", IO_TYPE_PER_PATCH_BLOCK_ARRAY }, |
| { "per_vertex", "Per-vertex TCS outputs", IO_TYPE_PER_VERTEX }, |
| { "per_vertex_block", "Per-vertex TCS outputs in IO block", IO_TYPE_PER_VERTEX_BLOCK }, |
| }; |
| |
| static const struct |
| { |
| const char* name; |
| VertexIOArraySize vertexIOArraySize; |
| } vertexArraySizeCases[] = |
| { |
| { "vertex_io_array_size_implicit", VERTEX_IO_ARRAY_SIZE_IMPLICIT }, |
| { "vertex_io_array_size_shader_builtin", VERTEX_IO_ARRAY_SIZE_EXPLICIT_SHADER_BUILTIN }, |
| { "vertex_io_array_size_spec_min", VERTEX_IO_ARRAY_SIZE_EXPLICIT_SPEC_MIN }, |
| }; |
| |
| for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(ioCases); ++caseNdx) |
| { |
| de::MovePtr<tcu::TestCaseGroup> ioTypeGroup (new tcu::TestCaseGroup(testCtx, ioCases[caseNdx].name, ioCases[caseNdx].description)); |
| for (int arrayCaseNdx = 0; arrayCaseNdx < DE_LENGTH_OF_ARRAY(vertexArraySizeCases); ++arrayCaseNdx) |
| { |
| de::MovePtr<tcu::TestCaseGroup> vertexArraySizeGroup (new tcu::TestCaseGroup(testCtx, vertexArraySizeCases[arrayCaseNdx].name, "")); |
| for (int primitiveTypeNdx = 0; primitiveTypeNdx < TESSPRIMITIVETYPE_LAST; ++primitiveTypeNdx) |
| { |
| const TessPrimitiveType primitiveType = static_cast<TessPrimitiveType>(primitiveTypeNdx); |
| const std::string primitiveName = getTessPrimitiveTypeShaderName(primitiveType); |
| const CaseDefinition caseDef = { primitiveType, ioCases[caseNdx].ioType, vertexArraySizeCases[arrayCaseNdx].vertexIOArraySize, |
| std::string() + "vulkan/data/tessellation/user_defined_io_" + primitiveName + "_ref.png" }; |
| |
| vertexArraySizeGroup->addChild(new UserDefinedIOTest(testCtx, primitiveName, "", caseDef)); |
| } |
| ioTypeGroup->addChild(vertexArraySizeGroup.release()); |
| } |
| group->addChild(ioTypeGroup.release()); |
| } |
| |
| return group.release(); |
| } |
| |
| } // tessellation |
| } // vkt |