/*------------------------------------------------------------------------ * 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 "vkBufferWithMemory.hpp" #include "vkImageWithMemory.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 ""; } } //! 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 >& objects) { int result = 0; for (int i = 0; i < static_cast(objects.size()); ++i) result += objects[i]->numBasicSubobjectsInElementType(); return result; } std::string basicSubobjectAtIndex (const int subobjectIndex, const std::vector >& 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 ""; } 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& 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 m_members; }; std::string IOBlock::declare (void) const { std::ostringstream buf; buf << m_blockName << "\n" << "{\n"; for (int i = 0; i < static_cast(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(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(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(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(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(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(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 ""; } class UserDefinedIOTest : public TestCase { public: UserDefinedIOTest (tcu::TestContext& testCtx, const std::string& name, const CaseDefinition caseDef); void initPrograms (vk::SourceCollections& programCollection) const; void checkSupport (Context& context) const; TestInstance* createInstance (Context& context) const; private: const CaseDefinition m_caseDef; std::vector m_structTypes; std::vector > m_tcsOutputs; std::vector > m_tesInputs; std::string m_tcsDeclarations; std::string m_tcsStatements; std::string m_tesDeclarations; std::string m_tesStatements; }; void UserDefinedIOTest::checkSupport (Context& context) const { checkSupportCase(context, m_caseDef); } UserDefinedIOTest::UserDefinedIOTest (tcu::TestContext& testCtx, const std::string& name, const CaseDefinition caseDef) : TestCase (testCtx, name) , 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) : deFatalStr("Invalid vertexIOArraySize"); 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 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(new IOBlock("TheBlock", "tcBlock", blockMembers))); m_tesInputs.push_back (de::SharedPtr(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(new Variable(var0))); m_tesInputs.push_back (de::SharedPtr(new Variable(var0))); usedStruct = true; } m_tcsOutputs.push_back (de::SharedPtr(new Variable(var1))); m_tesInputs.push_back (de::SharedPtr(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(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(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 >& tesInputs); tcu::TestStatus iterate (void); private: const CaseDefinition m_caseDef; const std::vector > m_tesInputs; }; UserDefinedIOTestInstance::UserDefinedIOTestInstance (Context& context, const CaseDefinition caseDef, const std::vector >& 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 BufferWithMemory 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(vertexDataSizeBytes)); flushAlloc(vk, device, alloc); } // Output buffer: number of invocations and verification indices const int resultBufferMaxVertices = refNumVertices; const VkDeviceSize resultBufferSizeBytes = sizeof(deInt32) + resultBufferMaxVertices * sizeof(deUint32); const BufferWithMemory 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(resultBufferSizeBytes)); flushAlloc(vk, device, alloc); } // 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 ImageWithMemory 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 BufferWithMemory colorBuffer (vk, device, allocator, makeBufferCreateInfo(colorBufferSizeBytes, VK_BUFFER_USAGE_TRANSFER_DST_BIT), MemoryRequirement::HostVisible); // Descriptors const Unique descriptorSetLayout(DescriptorSetLayoutBuilder() .addSingleBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT) .build(vk, device)); const Unique descriptorPool(DescriptorPoolBuilder() .addType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER) .build(vk, device, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 1u)); const Unique 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 colorAttachmentView (makeImageView(vk, device, *colorAttachmentImage, VK_IMAGE_VIEW_TYPE_2D, colorFormat, colorImageSubresourceRange)); const Unique renderPass (makeRenderPass(vk, device, colorFormat)); const Unique framebuffer (makeFramebuffer(vk, device, *renderPass, *colorAttachmentView, renderSize.x(), renderSize.y())); const Unique pipelineLayout (makePipelineLayout(vk, device, *descriptorSetLayout)); const Unique cmdPool (makeCommandPool(vk, device, queueFamilyIndex)); const Unique cmdBuffer (allocateCommandBuffer (vk, device, *cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY)); const Unique 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(); invalidateAlloc(vk, device, colorBufferAlloc); // 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(); invalidateAlloc(vk, device, resultAlloc); const deInt32 numVertices = *static_cast(resultAlloc.getHostPtr()); const deUint32* const vertices = reinterpret_cast(static_cast(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 group (new tcu::TestCaseGroup(testCtx, "user_defined_io")); static const struct { const char* name; IOType ioType; } ioCases[] = { // Per-patch TCS outputs { "per_patch",IO_TYPE_PER_PATCH }, // Per-patch array TCS outputs { "per_patch_array",IO_TYPE_PER_PATCH_ARRAY }, // Per-patch TCS outputs in IO block { "per_patch_block",IO_TYPE_PER_PATCH_BLOCK }, // Per-patch TCS outputs in IO block array { "per_patch_block_array",IO_TYPE_PER_PATCH_BLOCK_ARRAY }, // Per-vertex TCS outputs { "per_vertex",IO_TYPE_PER_VERTEX }, // Per-vertex TCS outputs in IO block { "per_vertex_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 ioTypeGroup (new tcu::TestCaseGroup(testCtx, ioCases[caseNdx].name)); for (int arrayCaseNdx = 0; arrayCaseNdx < DE_LENGTH_OF_ARRAY(vertexArraySizeCases); ++arrayCaseNdx) { de::MovePtr vertexArraySizeGroup (new tcu::TestCaseGroup(testCtx, vertexArraySizeCases[arrayCaseNdx].name)); for (int primitiveTypeNdx = 0; primitiveTypeNdx < TESSPRIMITIVETYPE_LAST; ++primitiveTypeNdx) { const TessPrimitiveType primitiveType = static_cast(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