/*------------------------------------------------------------------------ * Vulkan Conformance Tests * ------------------------ * * Copyright (c) 2015 The Khronos Group Inc. * Copyright (c) 2015 Imagination Technologies Ltd. * * 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 Vertex Input Tests *//*--------------------------------------------------------------------*/ #include "vktPipelineVertexInputTests.hpp" #include "vktTestGroupUtil.hpp" #include "vktPipelineClearUtil.hpp" #include "vktPipelineImageUtil.hpp" #include "vktPipelineVertexUtil.hpp" #include "vktPipelineReferenceRenderer.hpp" #include "vktTestCase.hpp" #include "vktTestCaseUtil.hpp" #include "vkImageUtil.hpp" #include "vkMemUtil.hpp" #include "vkPrograms.hpp" #include "vkQueryUtil.hpp" #include "vkRef.hpp" #include "vkRefUtil.hpp" #include "vkTypeUtil.hpp" #include "vkCmdUtil.hpp" #include "vkObjUtil.hpp" #include "tcuFloat.hpp" #include "tcuImageCompare.hpp" #include "deFloat16.h" #include "deMemory.h" #include "deRandom.hpp" #include "deStringUtil.hpp" #include "deUniquePtr.hpp" #include #include namespace vkt { namespace pipeline { using namespace vk; namespace { bool isSupportedVertexFormat (Context& context, VkFormat format) { if (isVertexFormatDouble(format) && !context.getDeviceFeatures().shaderFloat64) return false; VkFormatProperties formatProps; deMemset(&formatProps, 0, sizeof(VkFormatProperties)); context.getInstanceInterface().getPhysicalDeviceFormatProperties(context.getPhysicalDevice(), format, &formatProps); return (formatProps.bufferFeatures & VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT) != 0u; } float getRepresentableDifferenceUnorm (VkFormat format) { DE_ASSERT(isVertexFormatUnorm(format) || isVertexFormatSRGB(format)); return 1.0f / float((1 << (getVertexFormatComponentSize(format) * 8)) - 1); } float getRepresentableDifferenceUnormPacked(VkFormat format, deUint32 componentNdx) { DE_ASSERT((isVertexFormatUnorm(format) || isVertexFormatSRGB(format)) && isVertexFormatPacked(format)); return 1.0f / float((1 << (getPackedVertexFormatComponentWidth(format, componentNdx))) - 1); } float getRepresentableDifferenceSnorm (VkFormat format) { DE_ASSERT(isVertexFormatSnorm(format)); return 1.0f / float((1 << (getVertexFormatComponentSize(format) * 8 - 1)) - 1); } float getRepresentableDifferenceSnormPacked(VkFormat format, deUint32 componentNdx) { DE_ASSERT(isVertexFormatSnorm(format) && isVertexFormatPacked(format)); return 1.0f / float((1 << (getPackedVertexFormatComponentWidth(format, componentNdx) - 1)) - 1); } deUint32 getNextMultipleOffset (deUint32 divisor, deUint32 value) { if (value % divisor == 0) return 0; else return divisor - (value % divisor); } class VertexInputTest : public vkt::TestCase { public: enum GlslType { GLSL_TYPE_INT, GLSL_TYPE_IVEC2, GLSL_TYPE_IVEC3, GLSL_TYPE_IVEC4, GLSL_TYPE_UINT, GLSL_TYPE_UVEC2, GLSL_TYPE_UVEC3, GLSL_TYPE_UVEC4, GLSL_TYPE_FLOAT, GLSL_TYPE_VEC2, GLSL_TYPE_VEC3, GLSL_TYPE_VEC4, GLSL_TYPE_MAT2, GLSL_TYPE_MAT3, GLSL_TYPE_MAT4, GLSL_TYPE_DOUBLE, GLSL_TYPE_DVEC2, GLSL_TYPE_DVEC3, GLSL_TYPE_DVEC4, GLSL_TYPE_DMAT2, GLSL_TYPE_DMAT3, GLSL_TYPE_DMAT4, GLSL_TYPE_COUNT }; enum GlslBasicType { GLSL_BASIC_TYPE_INT, GLSL_BASIC_TYPE_UINT, GLSL_BASIC_TYPE_FLOAT, GLSL_BASIC_TYPE_DOUBLE }; enum BindingMapping { BINDING_MAPPING_ONE_TO_ONE, //!< Vertex input bindings will not contain data for more than one attribute. BINDING_MAPPING_ONE_TO_MANY //!< Vertex input bindings can contain data for more than one attribute. }; enum AttributeLayout { ATTRIBUTE_LAYOUT_INTERLEAVED, //!< Attribute data is bundled together as if in a structure: [pos 0][color 0][pos 1][color 1]... ATTRIBUTE_LAYOUT_SEQUENTIAL //!< Data for each attribute is laid out separately: [pos 0][pos 1]...[color 0][color 1]... // Sequential only makes a difference if ONE_TO_MANY mapping is used (more than one attribute in a binding). }; enum LayoutSkip { LAYOUT_SKIP_ENABLED, //!< Skip one location slot after each attribute LAYOUT_SKIP_DISABLED //!< Consume locations sequentially }; enum LayoutOrder { LAYOUT_ORDER_IN_ORDER, //!< Assign locations in order LAYOUT_ORDER_OUT_OF_ORDER //!< Assign locations out of order }; struct AttributeInfo { GlslType glslType; VkFormat vkType; VkVertexInputRate inputRate; }; struct GlslTypeDescription { const char* name; int vertexInputComponentCount; int vertexInputCount; GlslBasicType basicType; }; static const GlslTypeDescription s_glslTypeDescriptions[GLSL_TYPE_COUNT]; VertexInputTest (tcu::TestContext& testContext, const std::string& name, const std::string& description, const std::vector& attributeInfos, BindingMapping bindingMapping, AttributeLayout attributeLayout, LayoutSkip layoutSkip = LAYOUT_SKIP_DISABLED, LayoutOrder layoutOrder = LAYOUT_ORDER_IN_ORDER); virtual ~VertexInputTest (void) {} virtual void initPrograms (SourceCollections& programCollection) const; virtual void checkSupport (Context& context) const; virtual TestInstance* createInstance (Context& context) const; static bool isCompatibleType (VkFormat format, GlslType glslType); private: AttributeInfo getAttributeInfo (size_t attributeNdx) const; size_t getNumAttributes (void) const; std::string getGlslInputDeclarations (void) const; std::string getGlslVertexCheck (void) const; std::string getGlslAttributeConditions (const AttributeInfo& attributeInfo, const std::string attributeIndex) const; static tcu::Vec4 getFormatThreshold (VkFormat format); const std::vector m_attributeInfos; const BindingMapping m_bindingMapping; const AttributeLayout m_attributeLayout; const LayoutSkip m_layoutSkip; mutable std::vector m_locations; const bool m_queryMaxAttributes; bool m_usesDoubleType; mutable size_t m_maxAttributes; }; class VertexInputInstance : public vkt::TestInstance { public: struct VertexInputAttributeDescription { VertexInputTest::GlslType glslType; int vertexInputIndex; VkVertexInputAttributeDescription vkDescription; }; typedef std::vector AttributeDescriptionList; VertexInputInstance (Context& context, const AttributeDescriptionList& attributeDescriptions, const std::vector& bindingDescriptions, const std::vector& bindingOffsets); virtual ~VertexInputInstance (void); virtual tcu::TestStatus iterate (void); static void writeVertexInputData (deUint8* destPtr, const VkVertexInputBindingDescription& bindingDescription, const VkDeviceSize bindingOffset, const AttributeDescriptionList& attributes); static void writeVertexInputValue (deUint8* destPtr, const VertexInputAttributeDescription& attributes, int indexId); private: tcu::TestStatus verifyImage (void); private: std::vector m_vertexBuffers; std::vector m_vertexBufferAllocs; const tcu::UVec2 m_renderSize; const VkFormat m_colorFormat; Move m_colorImage; de::MovePtr m_colorImageAlloc; Move m_depthImage; Move m_colorAttachmentView; Move m_renderPass; Move m_framebuffer; Move m_vertexShaderModule; Move m_fragmentShaderModule; Move m_pipelineLayout; Move m_graphicsPipeline; Move m_cmdPool; Move m_cmdBuffer; }; const VertexInputTest::GlslTypeDescription VertexInputTest::s_glslTypeDescriptions[GLSL_TYPE_COUNT] = { { "int", 1, 1, GLSL_BASIC_TYPE_INT }, { "ivec2", 2, 1, GLSL_BASIC_TYPE_INT }, { "ivec3", 3, 1, GLSL_BASIC_TYPE_INT }, { "ivec4", 4, 1, GLSL_BASIC_TYPE_INT }, { "uint", 1, 1, GLSL_BASIC_TYPE_UINT }, { "uvec2", 2, 1, GLSL_BASIC_TYPE_UINT }, { "uvec3", 3, 1, GLSL_BASIC_TYPE_UINT }, { "uvec4", 4, 1, GLSL_BASIC_TYPE_UINT }, { "float", 1, 1, GLSL_BASIC_TYPE_FLOAT }, { "vec2", 2, 1, GLSL_BASIC_TYPE_FLOAT }, { "vec3", 3, 1, GLSL_BASIC_TYPE_FLOAT }, { "vec4", 4, 1, GLSL_BASIC_TYPE_FLOAT }, { "mat2", 2, 2, GLSL_BASIC_TYPE_FLOAT }, { "mat3", 3, 3, GLSL_BASIC_TYPE_FLOAT }, { "mat4", 4, 4, GLSL_BASIC_TYPE_FLOAT }, { "double", 1, 1, GLSL_BASIC_TYPE_DOUBLE }, { "dvec2", 2, 1, GLSL_BASIC_TYPE_DOUBLE }, { "dvec3", 3, 1, GLSL_BASIC_TYPE_DOUBLE }, { "dvec4", 4, 1, GLSL_BASIC_TYPE_DOUBLE }, { "dmat2", 2, 2, GLSL_BASIC_TYPE_DOUBLE }, { "dmat3", 3, 3, GLSL_BASIC_TYPE_DOUBLE }, { "dmat4", 4, 4, GLSL_BASIC_TYPE_DOUBLE } }; deUint32 getAttributeBinding (const VertexInputTest::BindingMapping bindingMapping, const VkVertexInputRate firstInputRate, const VkVertexInputRate inputRate, const deUint32 attributeNdx) { if (bindingMapping == VertexInputTest::BINDING_MAPPING_ONE_TO_ONE) { // Each attribute uses a unique binding return attributeNdx; } else // bindingMapping == BINDING_MAPPING_ONE_TO_MANY { // Alternate between two bindings return deUint32(firstInputRate + inputRate) % 2u; } } //! Number of locations used up by an attribute. deUint32 getConsumedLocations (const VertexInputTest::AttributeInfo& attributeInfo) { // double formats with more than 2 components will take 2 locations const VertexInputTest::GlslType type = attributeInfo.glslType; if ((type == VertexInputTest::GLSL_TYPE_DMAT2 || type == VertexInputTest::GLSL_TYPE_DMAT3 || type == VertexInputTest::GLSL_TYPE_DMAT4) && (attributeInfo.vkType == VK_FORMAT_R64G64B64_SFLOAT || attributeInfo.vkType == VK_FORMAT_R64G64B64A64_SFLOAT)) { return 2u; } else return 1u; } VertexInputTest::VertexInputTest (tcu::TestContext& testContext, const std::string& name, const std::string& description, const std::vector& attributeInfos, BindingMapping bindingMapping, AttributeLayout attributeLayout, LayoutSkip layoutSkip, LayoutOrder layoutOrder) : vkt::TestCase (testContext, name, description) , m_attributeInfos (attributeInfos) , m_bindingMapping (bindingMapping) , m_attributeLayout (attributeLayout) , m_layoutSkip (layoutSkip) , m_queryMaxAttributes (attributeInfos.size() == 0) , m_maxAttributes (16) { DE_ASSERT(m_attributeLayout == ATTRIBUTE_LAYOUT_INTERLEAVED || m_bindingMapping == BINDING_MAPPING_ONE_TO_MANY); m_usesDoubleType = false; for (size_t attributeNdx = 0; attributeNdx < m_attributeInfos.size(); attributeNdx++) { if (s_glslTypeDescriptions[m_attributeInfos[attributeNdx].glslType].basicType == GLSL_BASIC_TYPE_DOUBLE) { m_usesDoubleType = true; break; } } // Determine number of location slots required for each attribute deUint32 attributeLocation = 0; std::vector locationSlotsNeeded; const size_t numAttributes = getNumAttributes(); for (size_t attributeNdx = 0; attributeNdx < numAttributes; ++attributeNdx) { const AttributeInfo& attributeInfo = getAttributeInfo(attributeNdx); const GlslTypeDescription& glslTypeDescription = s_glslTypeDescriptions[attributeInfo.glslType]; const deUint32 prevAttributeLocation = attributeLocation; attributeLocation += glslTypeDescription.vertexInputCount * getConsumedLocations(attributeInfo); if (m_layoutSkip == LAYOUT_SKIP_ENABLED) attributeLocation++; locationSlotsNeeded.push_back(attributeLocation - prevAttributeLocation); } if (layoutOrder == LAYOUT_ORDER_IN_ORDER) { deUint32 loc = 0; // Assign locations in order for (size_t attributeNdx = 0; attributeNdx < numAttributes; ++attributeNdx) { m_locations.push_back(loc); loc += locationSlotsNeeded[attributeNdx]; } } else { // Assign locations out of order std::vector indices; std::vector slots; deUint32 slot = 0; // Mix the location slots: first all even and then all odd attributes. for (deUint32 attributeNdx = 0; attributeNdx < numAttributes; ++attributeNdx) if (attributeNdx % 2 == 0) indices.push_back(attributeNdx); for (deUint32 attributeNdx = 0; attributeNdx < numAttributes; ++attributeNdx) if (attributeNdx % 2 != 0) indices.push_back(attributeNdx); for (size_t i = 0; i < indices.size(); i++) { slots.push_back(slot); slot += locationSlotsNeeded[indices[i]]; } for (size_t attributeNdx = 0; attributeNdx < numAttributes; ++attributeNdx) { deUint32 slotIdx = 0; for (deUint32 i = 0; i < (deUint32)indices.size(); i++) if (attributeNdx == indices[i]) slotIdx = i; m_locations.push_back(slots[slotIdx]); } } } VertexInputTest::AttributeInfo VertexInputTest::getAttributeInfo (size_t attributeNdx) const { if (m_queryMaxAttributes) { AttributeInfo attributeInfo = { GLSL_TYPE_VEC4, VK_FORMAT_R8G8B8A8_SNORM, (attributeNdx % 2 == 0) ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE }; return attributeInfo; } else { return m_attributeInfos.at(attributeNdx); } } size_t VertexInputTest::getNumAttributes (void) const { if (m_queryMaxAttributes) return m_maxAttributes; else return m_attributeInfos.size(); } void VertexInputTest::checkSupport (Context& context) const { const deUint32 maxAttributes = context.getDeviceProperties().limits.maxVertexInputAttributes; if (m_attributeInfos.size() > maxAttributes) TCU_THROW(NotSupportedError, "Unsupported number of vertex input attributes, maxVertexInputAttributes: " + de::toString(maxAttributes)); } TestInstance* VertexInputTest::createInstance (Context& context) const { typedef VertexInputInstance::VertexInputAttributeDescription VertexInputAttributeDescription; // Check upfront for maximum number of vertex input attributes { const InstanceInterface& vki = context.getInstanceInterface(); const VkPhysicalDevice physDevice = context.getPhysicalDevice(); const VkPhysicalDeviceLimits limits = getPhysicalDeviceProperties(vki, physDevice).limits; const deUint32 maxAttributes = limits.maxVertexInputAttributes; // Use VkPhysicalDeviceLimits::maxVertexInputAttributes if (m_queryMaxAttributes) { m_maxAttributes = maxAttributes; m_locations.clear(); for (deUint32 i = 0; i < maxAttributes; i++) m_locations.push_back(i); } } // Create enough binding descriptions with random offsets std::vector bindingDescriptions; std::vector bindingOffsets; const size_t numAttributes = getNumAttributes(); const size_t numBindings = (m_bindingMapping == BINDING_MAPPING_ONE_TO_ONE) ? numAttributes : ((numAttributes > 1) ? 2 : 1); const VkVertexInputRate firstInputrate = getAttributeInfo(0).inputRate; for (size_t bindingNdx = 0; bindingNdx < numBindings; ++bindingNdx) { // Bindings alternate between STEP_RATE_VERTEX and STEP_RATE_INSTANCE const VkVertexInputRate inputRate = ((firstInputrate + bindingNdx) % 2 == 0) ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE; // Stride will be updated when creating the attribute descriptions const VkVertexInputBindingDescription bindingDescription = { static_cast(bindingNdx), // deUint32 binding; 0u, // deUint32 stride; inputRate // VkVertexInputRate inputRate; }; bindingDescriptions.push_back(bindingDescription); bindingOffsets.push_back(4 * bindingNdx); } std::vector attributeDescriptions; std::vector attributeOffsets (bindingDescriptions.size(), 0); std::vector attributeMaxSizes (bindingDescriptions.size(), 0); // max component or vector size, depending on which layout we are using // To place the attributes sequentially we need to know the largest attribute and use its size in stride and offset calculations. if (m_attributeLayout == ATTRIBUTE_LAYOUT_SEQUENTIAL) for (size_t attributeNdx = 0; attributeNdx < numAttributes; ++attributeNdx) { const AttributeInfo& attributeInfo = getAttributeInfo(attributeNdx); const deUint32 attributeBinding = getAttributeBinding(m_bindingMapping, firstInputrate, attributeInfo.inputRate, static_cast(attributeNdx)); const deUint32 inputSize = getVertexFormatSize(attributeInfo.vkType); attributeMaxSizes[attributeBinding] = de::max(attributeMaxSizes[attributeBinding], inputSize); } // Create attribute descriptions, assign them to bindings and update stride. for (size_t attributeNdx = 0; attributeNdx < numAttributes; ++attributeNdx) { const AttributeInfo& attributeInfo = getAttributeInfo(attributeNdx); const GlslTypeDescription& glslTypeDescription = s_glslTypeDescriptions[attributeInfo.glslType]; const deUint32 inputSize = getVertexFormatSize(attributeInfo.vkType); const deUint32 attributeBinding = getAttributeBinding(m_bindingMapping, firstInputrate, attributeInfo.inputRate, static_cast(attributeNdx)); const deUint32 vertexCount = (attributeInfo.inputRate == VK_VERTEX_INPUT_RATE_VERTEX) ? (4 * 2) : 2; VertexInputAttributeDescription attributeDescription = { attributeInfo.glslType, // GlslType glslType; 0, // int vertexInputIndex; { 0u, // uint32_t location; attributeBinding, // uint32_t binding; attributeInfo.vkType, // VkFormat format; 0u, // uint32_t offset; }, }; // Matrix types add each column as a separate attribute. for (int descNdx = 0; descNdx < glslTypeDescription.vertexInputCount; ++descNdx) { attributeDescription.vertexInputIndex = descNdx; attributeDescription.vkDescription.location = m_locations[attributeNdx] + getConsumedLocations(attributeInfo) * descNdx; if (m_attributeLayout == ATTRIBUTE_LAYOUT_INTERLEAVED) { const deUint32 offsetToComponentAlignment = getNextMultipleOffset(getVertexFormatSize(attributeInfo.vkType), (deUint32)bindingOffsets[attributeBinding] + attributeOffsets[attributeBinding]); attributeOffsets[attributeBinding] += offsetToComponentAlignment; attributeDescription.vkDescription.offset = attributeOffsets[attributeBinding]; attributeDescriptions.push_back(attributeDescription); bindingDescriptions[attributeBinding].stride += offsetToComponentAlignment + inputSize; attributeOffsets[attributeBinding] += inputSize; attributeMaxSizes[attributeBinding] = de::max(attributeMaxSizes[attributeBinding], getVertexFormatSize(attributeInfo.vkType)); } else // m_attributeLayout == ATTRIBUTE_LAYOUT_SEQUENTIAL { attributeDescription.vkDescription.offset = attributeOffsets[attributeBinding]; attributeDescriptions.push_back(attributeDescription); attributeOffsets[attributeBinding] += vertexCount * attributeMaxSizes[attributeBinding]; } } if (m_attributeLayout == ATTRIBUTE_LAYOUT_SEQUENTIAL) bindingDescriptions[attributeBinding].stride = attributeMaxSizes[attributeBinding]; } // Make sure the stride results in aligned access for (size_t bindingNdx = 0; bindingNdx < bindingDescriptions.size(); ++bindingNdx) { if (attributeMaxSizes[bindingNdx] > 0) bindingDescriptions[bindingNdx].stride += getNextMultipleOffset(attributeMaxSizes[bindingNdx], bindingDescriptions[bindingNdx].stride); } // Check upfront for maximum number of vertex input bindings { const InstanceInterface& vki = context.getInstanceInterface(); const VkPhysicalDevice physDevice = context.getPhysicalDevice(); const VkPhysicalDeviceLimits limits = getPhysicalDeviceProperties(vki, physDevice).limits; const deUint32 maxBindings = limits.maxVertexInputBindings; if (bindingDescriptions.size() > maxBindings) { const std::string notSupportedStr = "Unsupported number of vertex input bindings, maxVertexInputBindings: " + de::toString(maxBindings); TCU_THROW(NotSupportedError, notSupportedStr.c_str()); } } // Portability requires stride to be multiply of minVertexInputBindingStrideAlignment if (context.isDeviceFunctionalitySupported("VK_KHR_portability_subset")) { deUint32 minStrideAlignment = context.getPortabilitySubsetProperties().minVertexInputBindingStrideAlignment; for (size_t bindingNdx = 0; bindingNdx < bindingDescriptions.size(); ++bindingNdx) { if ((bindingDescriptions[bindingNdx].stride % minStrideAlignment) != 0) TCU_THROW(NotSupportedError, "VK_KHR_portability_subset: stride is not multiply of minVertexInputBindingStrideAlignment"); } } return new VertexInputInstance(context, attributeDescriptions, bindingDescriptions, bindingOffsets); } void VertexInputTest::initPrograms (SourceCollections& programCollection) const { std::ostringstream vertexSrc; vertexSrc << "#version 440\n" << "layout(constant_id = 0) const int numAttributes = " << m_maxAttributes << ";\n" << getGlslInputDeclarations() << "layout(location = 0) out highp vec4 vtxColor;\n" << "out gl_PerVertex {\n" << " vec4 gl_Position;\n" << "};\n"; // NOTE: double abs(double x) undefined in glslang ?? if (m_usesDoubleType) vertexSrc << "double abs (double x) { if (x < 0.0LF) return -x; else return x; }\n"; vertexSrc << "void main (void)\n" << "{\n" << getGlslVertexCheck() << "}\n"; programCollection.glslSources.add("attribute_test_vert") << glu::VertexSource(vertexSrc.str()); programCollection.glslSources.add("attribute_test_frag") << glu::FragmentSource( "#version 440\n" "layout(location = 0) in highp vec4 vtxColor;\n" "layout(location = 0) out highp vec4 fragColor;\n" "void main (void)\n" "{\n" " fragColor = vtxColor;\n" "}\n"); } std::string VertexInputTest::getGlslInputDeclarations (void) const { std::ostringstream glslInputs; if (m_queryMaxAttributes) { const GlslTypeDescription& glslTypeDesc = s_glslTypeDescriptions[GLSL_TYPE_VEC4]; glslInputs << "layout(location = 0) in " << glslTypeDesc.name << " attr[numAttributes];\n"; } else { for (size_t attributeNdx = 0; attributeNdx < m_attributeInfos.size(); attributeNdx++) { const GlslTypeDescription& glslTypeDesc = s_glslTypeDescriptions[m_attributeInfos[attributeNdx].glslType]; glslInputs << "layout(location = " << m_locations[attributeNdx] << ") in " << glslTypeDesc.name << " attr" << attributeNdx << ";\n"; } } return glslInputs.str(); } std::string VertexInputTest::getGlslVertexCheck (void) const { std::ostringstream glslCode; int totalInputComponentCount = 0; glslCode << " int okCount = 0;\n"; if (m_queryMaxAttributes) { const AttributeInfo attributeInfo = getAttributeInfo(0); glslCode << " for (int checkNdx = 0; checkNdx < numAttributes; checkNdx++)\n" << " {\n" << " uint index = (checkNdx % 2 == 0) ? gl_VertexIndex : gl_InstanceIndex;\n"; glslCode << getGlslAttributeConditions(attributeInfo, "checkNdx") << " }\n"; const int vertexInputCount = VertexInputTest::s_glslTypeDescriptions[attributeInfo.glslType].vertexInputCount; totalInputComponentCount += vertexInputCount * VertexInputTest::s_glslTypeDescriptions[attributeInfo.glslType].vertexInputComponentCount; glslCode << " if (okCount == " << totalInputComponentCount << " * numAttributes)\n" " {\n" " if (gl_InstanceIndex == 0)\n" " vtxColor = vec4(1.0, 0.0, 0.0, 1.0);\n" " else\n" " vtxColor = vec4(0.0, 0.0, 1.0, 1.0);\n" " }\n" " else\n" " {\n" " vtxColor = vec4(okCount / float(" << totalInputComponentCount << " * numAttributes), 0.0f, 0.0f, 1.0);\n" << " }\n\n" " if (gl_InstanceIndex == 0)\n" " {\n" " if (gl_VertexIndex == 0) gl_Position = vec4(-1.0, -1.0, 0.0, 1.0);\n" " else if (gl_VertexIndex == 1) gl_Position = vec4(0.0, -1.0, 0.0, 1.0);\n" " else if (gl_VertexIndex == 2) gl_Position = vec4(-1.0, 1.0, 0.0, 1.0);\n" " else if (gl_VertexIndex == 3) gl_Position = vec4(0.0, 1.0, 0.0, 1.0);\n" " else gl_Position = vec4(0.0);\n" " }\n" " else\n" " {\n" " if (gl_VertexIndex == 0) gl_Position = vec4(0.0, -1.0, 0.0, 1.0);\n" " else if (gl_VertexIndex == 1) gl_Position = vec4(1.0, -1.0, 0.0, 1.0);\n" " else if (gl_VertexIndex == 2) gl_Position = vec4(0.0, 1.0, 0.0, 1.0);\n" " else if (gl_VertexIndex == 3) gl_Position = vec4(1.0, 1.0, 0.0, 1.0);\n" " else gl_Position = vec4(0.0);\n" " }\n"; } else { for (size_t attributeNdx = 0; attributeNdx < m_attributeInfos.size(); attributeNdx++) { glslCode << getGlslAttributeConditions(m_attributeInfos[attributeNdx], de::toString(attributeNdx)); const int vertexInputCount = VertexInputTest::s_glslTypeDescriptions[m_attributeInfos[attributeNdx].glslType].vertexInputCount; totalInputComponentCount += vertexInputCount * VertexInputTest::s_glslTypeDescriptions[m_attributeInfos[attributeNdx].glslType].vertexInputComponentCount; } glslCode << " if (okCount == " << totalInputComponentCount << ")\n" " {\n" " if (gl_InstanceIndex == 0)\n" " vtxColor = vec4(1.0, 0.0, 0.0, 1.0);\n" " else\n" " vtxColor = vec4(0.0, 0.0, 1.0, 1.0);\n" " }\n" " else\n" " {\n" " vtxColor = vec4(okCount / float(" << totalInputComponentCount << "), 0.0f, 0.0f, 1.0);\n" << " }\n\n" " if (gl_InstanceIndex == 0)\n" " {\n" " if (gl_VertexIndex == 0) gl_Position = vec4(-1.0, -1.0, 0.0, 1.0);\n" " else if (gl_VertexIndex == 1) gl_Position = vec4(0.0, -1.0, 0.0, 1.0);\n" " else if (gl_VertexIndex == 2) gl_Position = vec4(-1.0, 1.0, 0.0, 1.0);\n" " else if (gl_VertexIndex == 3) gl_Position = vec4(0.0, 1.0, 0.0, 1.0);\n" " else gl_Position = vec4(0.0);\n" " }\n" " else\n" " {\n" " if (gl_VertexIndex == 0) gl_Position = vec4(0.0, -1.0, 0.0, 1.0);\n" " else if (gl_VertexIndex == 1) gl_Position = vec4(1.0, -1.0, 0.0, 1.0);\n" " else if (gl_VertexIndex == 2) gl_Position = vec4(0.0, 1.0, 0.0, 1.0);\n" " else if (gl_VertexIndex == 3) gl_Position = vec4(1.0, 1.0, 0.0, 1.0);\n" " else gl_Position = vec4(0.0);\n" " }\n"; } return glslCode.str(); } std::string VertexInputTest::getGlslAttributeConditions (const AttributeInfo& attributeInfo, const std::string attributeIndex) const { std::ostringstream glslCode; std::ostringstream attributeVar; const int componentCount = VertexInputTest::s_glslTypeDescriptions[attributeInfo.glslType].vertexInputComponentCount; const int vertexInputCount = VertexInputTest::s_glslTypeDescriptions[attributeInfo.glslType].vertexInputCount; const deUint32 totalComponentCount = componentCount * vertexInputCount; const tcu::Vec4 threshold = getFormatThreshold(attributeInfo.vkType); const std::string indexStr = m_queryMaxAttributes ? "[" + attributeIndex + "]" : attributeIndex; const std::string indentStr = m_queryMaxAttributes ? "\t\t" : "\t"; deUint32 componentIndex = 0; deUint32 orderNdx; std::string indexId; const deUint32 BGROrder[] = { 2, 1, 0, 3 }; const deUint32 ABGROrder[] = { 3, 2, 1, 0 }; const deUint32 ARGBOrder[] = { 1, 2, 3, 0 }; if (m_queryMaxAttributes) indexId = "index"; else indexId = (attributeInfo.inputRate == VK_VERTEX_INPUT_RATE_VERTEX) ? "gl_VertexIndex" : "gl_InstanceIndex"; attributeVar << "attr" << indexStr; glslCode << std::fixed; for (int columnNdx = 0; columnNdx< vertexInputCount; columnNdx++) { for (int rowNdx = 0; rowNdx < componentCount; rowNdx++) { if (isVertexFormatComponentOrderABGR(attributeInfo.vkType)) orderNdx = ABGROrder[rowNdx]; else if (isVertexFormatComponentOrderARGB(attributeInfo.vkType)) orderNdx = ARGBOrder[rowNdx]; else orderNdx = BGROrder[rowNdx]; std::string accessStr; { // Build string representing the access to the attribute component std::ostringstream accessStream; accessStream << attributeVar.str(); if (vertexInputCount == 1) { if (componentCount > 1) accessStream << "[" << rowNdx << "]"; } else { accessStream << "[" << columnNdx << "][" << rowNdx << "]"; } accessStr = accessStream.str(); } if (isVertexFormatSint(attributeInfo.vkType)) { if (isVertexFormatPacked(attributeInfo.vkType)) { const deInt32 maxIntValue = (1 << (getPackedVertexFormatComponentWidth(attributeInfo.vkType, orderNdx) - 1)) - 1; const deInt32 minIntValue = -maxIntValue; glslCode << indentStr << "if (" << accessStr << " == clamp(-(" << totalComponentCount << " * " << indexId << " + " << componentIndex << "), " << minIntValue << ", " << maxIntValue << "))\n"; } else glslCode << indentStr << "if (" << accessStr << " == -(" << totalComponentCount << " * " << indexId << " + " << componentIndex << "))\n"; } else if (isVertexFormatUint(attributeInfo.vkType)) { if (isVertexFormatPacked(attributeInfo.vkType)) { const deUint32 maxUintValue = (1 << getPackedVertexFormatComponentWidth(attributeInfo.vkType, orderNdx)) - 1; glslCode << indentStr << "if (" << accessStr << " == clamp(uint(" << totalComponentCount << " * " << indexId << " + " << componentIndex << "), 0, " << maxUintValue << "))\n"; } else glslCode << indentStr << "if (" << accessStr << " == uint(" << totalComponentCount << " * " << indexId << " + " << componentIndex << "))\n"; } else if (isVertexFormatSfloat(attributeInfo.vkType)) { if (VertexInputTest::s_glslTypeDescriptions[attributeInfo.glslType].basicType == VertexInputTest::GLSL_BASIC_TYPE_DOUBLE) { glslCode << indentStr << "if (abs(" << accessStr << " + double(0.01 * (" << totalComponentCount << ".0 * float(" << indexId << ") + " << componentIndex << ".0))) < double(" << threshold[rowNdx] << "))\n"; } else { glslCode << indentStr << "if (abs(" << accessStr << " + (0.01 * (" << totalComponentCount << ".0 * float(" << indexId << ") + " << componentIndex << ".0))) < " << threshold[rowNdx] << ")\n"; } } else if (isVertexFormatSscaled(attributeInfo.vkType)) { if (isVertexFormatPacked(attributeInfo.vkType)) { const float maxScaledValue = float((1 << (getPackedVertexFormatComponentWidth(attributeInfo.vkType, orderNdx) - 1)) - 1); const float minScaledValue = -maxScaledValue - 1.0f; glslCode << indentStr << "if (abs(" << accessStr << " + clamp(" << totalComponentCount << ".0 * float(" << indexId << ") + " << componentIndex << ".0, " << minScaledValue << ", " << maxScaledValue << ")) < " << threshold[orderNdx] << ")\n"; } else glslCode << indentStr << "if (abs(" << accessStr << " + (" << totalComponentCount << ".0 * float(" << indexId << ") + " << componentIndex << ".0)) < " << threshold[rowNdx] << ")\n"; } else if (isVertexFormatUscaled(attributeInfo.vkType)) { if (isVertexFormatPacked(attributeInfo.vkType)) { const float maxScaledValue = float((1 << getPackedVertexFormatComponentWidth(attributeInfo.vkType, orderNdx)) - 1); glslCode << indentStr << "if (abs(" << accessStr << " - clamp(" << totalComponentCount << ".0 * float(" << indexId << ") + " << componentIndex << ".0, 0, " << maxScaledValue << ")) < " << threshold[orderNdx] << ")\n"; } else glslCode << indentStr << "if (abs(" << accessStr << " - (" << totalComponentCount << ".0 * float(" << indexId << ") + " << componentIndex << ".0)) < " << threshold[rowNdx] << ")\n"; } else if (isVertexFormatSnorm(attributeInfo.vkType)) { const float representableDiff = isVertexFormatPacked(attributeInfo.vkType) ? getRepresentableDifferenceSnormPacked(attributeInfo.vkType, orderNdx) : getRepresentableDifferenceSnorm(attributeInfo.vkType); if(isVertexFormatPacked(attributeInfo.vkType)) glslCode << indentStr << "if (abs(" << accessStr << " - clamp((-1.0 + " << representableDiff << " * (" << totalComponentCount << ".0 * float(" << indexId << ") + " << componentIndex << ".0)), -1.0, 1.0)) < " << threshold[orderNdx] << ")\n"; else glslCode << indentStr << "if (abs(" << accessStr << " - (-1.0 + " << representableDiff << " * (" << totalComponentCount << ".0 * float(" << indexId << ") + " << componentIndex << ".0))) < " << threshold[rowNdx] << ")\n"; } else if (isVertexFormatUnorm(attributeInfo.vkType) || isVertexFormatSRGB(attributeInfo.vkType)) { const float representableDiff = isVertexFormatPacked(attributeInfo.vkType) ? getRepresentableDifferenceUnormPacked(attributeInfo.vkType, orderNdx) : getRepresentableDifferenceUnorm(attributeInfo.vkType); if (isVertexFormatPacked(attributeInfo.vkType)) glslCode << indentStr << "if (abs(" << accessStr << " - " << "clamp((" << representableDiff << " * (" << totalComponentCount << ".0 * float(" << indexId << ") + " << componentIndex << ".0)), 0.0, 1.0)) < " << threshold[orderNdx] << ")\n"; else glslCode << indentStr << "if (abs(" << accessStr << " - " << "(" << representableDiff << " * (" << totalComponentCount << ".0 * float(" << indexId << ") + " << componentIndex << ".0))) < " << threshold[rowNdx] << ")\n"; } else if (isVertexFormatUfloat(attributeInfo.vkType)) { if (VertexInputTest::s_glslTypeDescriptions[attributeInfo.glslType].basicType == VertexInputTest::GLSL_BASIC_TYPE_DOUBLE) { glslCode << indentStr << "if (abs(" << accessStr << " - double(0.01 * (" << totalComponentCount << ".0 * float(" << indexId << ") + " << componentIndex << ".0))) < double(" << threshold[rowNdx] << "))\n"; } else { glslCode << indentStr << "if (abs(" << accessStr << " - (0.01 * (" << totalComponentCount << ".0 * float(" << indexId << ") + " << componentIndex << ".0))) < (" << threshold[rowNdx] << "))\n"; } } else { DE_ASSERT(false); } glslCode << indentStr << "\tokCount++;\n\n"; componentIndex++; } } return glslCode.str(); } tcu::Vec4 VertexInputTest::getFormatThreshold (VkFormat format) { using tcu::Vec4; switch (format) { case VK_FORMAT_R32_SFLOAT: case VK_FORMAT_R32G32_SFLOAT: case VK_FORMAT_R32G32B32_SFLOAT: case VK_FORMAT_R32G32B32A32_SFLOAT: case VK_FORMAT_R64_SFLOAT: case VK_FORMAT_R64G64_SFLOAT: case VK_FORMAT_R64G64B64_SFLOAT: case VK_FORMAT_R64G64B64A64_SFLOAT: return Vec4(0.00001f); default: break; } if (isVertexFormatSnorm(format)) { return (isVertexFormatPacked(format) ? Vec4(1.5f * getRepresentableDifferenceSnormPacked(format, 0), 1.5f * getRepresentableDifferenceSnormPacked(format, 1), 1.5f * getRepresentableDifferenceSnormPacked(format, 2), 1.5f * getRepresentableDifferenceSnormPacked(format, 3)) : Vec4(1.5f * getRepresentableDifferenceSnorm(format))); } else if (isVertexFormatUnorm(format)) { return (isVertexFormatPacked(format) ? Vec4(1.5f * getRepresentableDifferenceUnormPacked(format, 0), 1.5f * getRepresentableDifferenceUnormPacked(format, 1), 1.5f * getRepresentableDifferenceUnormPacked(format, 2), 1.5f * getRepresentableDifferenceUnormPacked(format, 3)) : Vec4(1.5f * getRepresentableDifferenceUnorm(format))); } else if (isVertexFormatUfloat(format)) { return Vec4(0.008f); } return Vec4(0.001f); } VertexInputInstance::VertexInputInstance (Context& context, const AttributeDescriptionList& attributeDescriptions, const std::vector& bindingDescriptions, const std::vector& bindingOffsets) : vkt::TestInstance (context) , m_renderSize (16, 16) , m_colorFormat (VK_FORMAT_R8G8B8A8_UNORM) { DE_ASSERT(bindingDescriptions.size() == bindingOffsets.size()); const DeviceInterface& vk = context.getDeviceInterface(); const VkDevice vkDevice = context.getDevice(); const deUint32 queueFamilyIndex = context.getUniversalQueueFamilyIndex(); SimpleAllocator memAlloc (vk, vkDevice, getPhysicalDeviceMemoryProperties(context.getInstanceInterface(), context.getPhysicalDevice())); const VkComponentMapping componentMappingRGBA = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }; // Check upfront for unsupported features for (size_t attributeNdx = 0; attributeNdx < attributeDescriptions.size(); attributeNdx++) { const VkVertexInputAttributeDescription& attributeDescription = attributeDescriptions[attributeNdx].vkDescription; if (!isSupportedVertexFormat(context, attributeDescription.format)) { throw tcu::NotSupportedError(std::string("Unsupported format for vertex input: ") + getFormatName(attributeDescription.format)); } } // Create color image { const VkImageCreateInfo colorImageParams = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, // VkStructureType sType; DE_NULL, // const void* pNext; 0u, // VkImageCreateFlags flags; VK_IMAGE_TYPE_2D, // VkImageType imageType; m_colorFormat, // VkFormat format; { m_renderSize.x(), m_renderSize.y(), 1u }, // VkExtent3D extent; 1u, // deUint32 mipLevels; 1u, // deUint32 arrayLayers; VK_SAMPLE_COUNT_1_BIT, // VkSampleCountFlagBits samples; VK_IMAGE_TILING_OPTIMAL, // VkImageTiling tiling; VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, // VkImageUsageFlags usage; VK_SHARING_MODE_EXCLUSIVE, // VkSharingMode sharingMode; 1u, // deUint32 queueFamilyIndexCount; &queueFamilyIndex, // const deUint32* pQueueFamilyIndices; VK_IMAGE_LAYOUT_UNDEFINED, // VkImageLayout initialLayout; }; m_colorImage = createImage(vk, vkDevice, &colorImageParams); // Allocate and bind color image memory m_colorImageAlloc = memAlloc.allocate(getImageMemoryRequirements(vk, vkDevice, *m_colorImage), MemoryRequirement::Any); VK_CHECK(vk.bindImageMemory(vkDevice, *m_colorImage, m_colorImageAlloc->getMemory(), m_colorImageAlloc->getOffset())); } // Create color attachment view { const VkImageViewCreateInfo colorAttachmentViewParams = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, // VkStructureType sType; DE_NULL, // const void* pNext; 0u, // VkImageViewCreateFlags flags; *m_colorImage, // VkImage image; VK_IMAGE_VIEW_TYPE_2D, // VkImageViewType viewType; m_colorFormat, // VkFormat format; componentMappingRGBA, // VkComponentMapping components; { VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u }, // VkImageSubresourceRange subresourceRange; }; m_colorAttachmentView = createImageView(vk, vkDevice, &colorAttachmentViewParams); } // Create render pass m_renderPass = makeRenderPass(vk, vkDevice, m_colorFormat); // Create framebuffer { const VkFramebufferCreateInfo framebufferParams = { VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, // VkStructureType sType; DE_NULL, // const void* pNext; 0u, // VkFramebufferCreateFlags flags; *m_renderPass, // VkRenderPass renderPass; 1u, // deUint32 attachmentCount; &m_colorAttachmentView.get(), // const VkImageView* pAttachments; (deUint32)m_renderSize.x(), // deUint32 width; (deUint32)m_renderSize.y(), // deUint32 height; 1u // deUint32 layers; }; m_framebuffer = createFramebuffer(vk, vkDevice, &framebufferParams); } // Create pipeline layout { const VkPipelineLayoutCreateInfo pipelineLayoutParams = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, // VkStructureType sType; DE_NULL, // const void* pNext; 0u, // VkPipelineLayoutCreateFlags flags; 0u, // deUint32 setLayoutCount; DE_NULL, // const VkDescriptorSetLayout* pSetLayouts; 0u, // deUint32 pushConstantRangeCount; DE_NULL // const VkPushConstantRange* pPushConstantRanges; }; m_pipelineLayout = createPipelineLayout(vk, vkDevice, &pipelineLayoutParams); } m_vertexShaderModule = createShaderModule(vk, vkDevice, m_context.getBinaryCollection().get("attribute_test_vert"), 0); m_fragmentShaderModule = createShaderModule(vk, vkDevice, m_context.getBinaryCollection().get("attribute_test_frag"), 0); // Create specialization constant deUint32 specializationData = static_cast(attributeDescriptions.size()); const VkSpecializationMapEntry specializationMapEntry = { 0, // uint32_t constantID 0, // uint32_t offset sizeof(specializationData), // uint32_t size }; const VkSpecializationInfo specializationInfo = { 1, // uint32_t mapEntryCount &specializationMapEntry, // const void* pMapEntries sizeof(specializationData), // size_t dataSize &specializationData // const void* pData }; // Create pipeline { const VkPipelineShaderStageCreateInfo shaderStageParams[2] = { { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, // VkStructureType sType; DE_NULL, // const void* pNext; 0u, // VkPipelineShaderStageCreateFlags flags; VK_SHADER_STAGE_VERTEX_BIT, // VkShaderStageFlagBits stage; *m_vertexShaderModule, // VkShaderModule module; "main", // const char* pName; &specializationInfo // const VkSpecializationInfo* pSpecializationInfo; }, { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, // VkStructureType sType; DE_NULL, // const void* pNext; 0u, // VkPipelineShaderStageCreateFlags flags; VK_SHADER_STAGE_FRAGMENT_BIT, // VkShaderStageFlagBits stage; *m_fragmentShaderModule, // VkShaderModule module; "main", // const char* pName; DE_NULL // const VkSpecializationInfo* pSpecializationInfo; } }; // Create vertex attribute array and check if their VK formats are supported std::vector vkAttributeDescriptions; for (size_t attributeNdx = 0; attributeNdx < attributeDescriptions.size(); attributeNdx++) { const VkVertexInputAttributeDescription& attributeDescription = attributeDescriptions[attributeNdx].vkDescription; vkAttributeDescriptions.push_back(attributeDescription); } const VkPipelineVertexInputStateCreateInfo vertexInputStateParams = { VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, // VkStructureType sType; DE_NULL, // const void* pNext; 0u, // VkPipelineVertexInputStateCreateFlags flags; (deUint32)bindingDescriptions.size(), // deUint32 vertexBindingDescriptionCount; bindingDescriptions.data(), // const VkVertexInputBindingDescription* pVertexBindingDescriptions; (deUint32)vkAttributeDescriptions.size(), // deUint32 vertexAttributeDescriptionCount; vkAttributeDescriptions.data() // const VkVertexInputAttributeDescription* pVertexAttributeDescriptions; }; const VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateParams = { VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, // VkStructureType sType; DE_NULL, // const void* pNext; 0u, // VkPipelineInputAssemblyStateCreateFlags flags; VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, // VkPrimitiveTopology topology; false // VkBool32 primitiveRestartEnable; }; const VkViewport viewport = makeViewport(m_renderSize); const VkRect2D scissor = makeRect2D(m_renderSize); const VkPipelineViewportStateCreateInfo viewportStateParams = { VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, // VkStructureType sType; DE_NULL, // const void* pNext; 0u, // VkPipelineViewportStateCreateFlags flags; 1u, // deUint32 viewportCount; &viewport, // const VkViewport* pViewports; 1u, // deUint32 scissorCount; &scissor // const VkRect2D* pScissors; }; const VkPipelineRasterizationStateCreateInfo rasterStateParams = { VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, // VkStructureType sType; DE_NULL, // const void* pNext; 0u, // VkPipelineRasterizationStateCreateFlags flags; false, // VkBool32 depthClampEnable; false, // VkBool32 rasterizerDiscardEnable; VK_POLYGON_MODE_FILL, // VkPolygonMode polygonMode; VK_CULL_MODE_NONE, // VkCullModeFlags cullMode; VK_FRONT_FACE_COUNTER_CLOCKWISE, // VkFrontFace frontFace; VK_FALSE, // VkBool32 depthBiasEnable; 0.0f, // float depthBiasConstantFactor; 0.0f, // float depthBiasClamp; 0.0f, // float depthBiasSlopeFactor; 1.0f, // float lineWidth; }; const VkPipelineColorBlendAttachmentState colorBlendAttachmentState = { false, // VkBool32 blendEnable; VK_BLEND_FACTOR_ONE, // VkBlendFactor srcColorBlendFactor; VK_BLEND_FACTOR_ZERO, // VkBlendFactor dstColorBlendFactor; VK_BLEND_OP_ADD, // VkBlendOp colorBlendOp; VK_BLEND_FACTOR_ONE, // VkBlendFactor srcAlphaBlendFactor; VK_BLEND_FACTOR_ZERO, // VkBlendFactor dstAlphaBlendFactor; VK_BLEND_OP_ADD, // VkBlendOp alphaBlendOp; VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | // VkColorComponentFlags colorWriteMask; VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT }; const VkPipelineColorBlendStateCreateInfo colorBlendStateParams = { VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, // VkStructureType sType; DE_NULL, // const void* pNext; 0u, // VkPipelineColorBlendStateCreateFlags flags; false, // VkBool32 logicOpEnable; VK_LOGIC_OP_COPY, // VkLogicOp logicOp; 1u, // deUint32 attachmentCount; &colorBlendAttachmentState, // const VkPipelineColorBlendAttachmentState* pAttachments; { 0.0f, 0.0f, 0.0f, 0.0f }, // float blendConstants[4]; }; const VkPipelineMultisampleStateCreateInfo multisampleStateParams = { VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, // VkStructureType sType; DE_NULL, // const void* pNext; 0u, // VkPipelineMultisampleStateCreateFlags flags; VK_SAMPLE_COUNT_1_BIT, // VkSampleCountFlagBits rasterizationSamples; false, // VkBool32 sampleShadingEnable; 0.0f, // float minSampleShading; DE_NULL, // const VkSampleMask* pSampleMask; false, // VkBool32 alphaToCoverageEnable; false // VkBool32 alphaToOneEnable; }; VkPipelineDepthStencilStateCreateInfo depthStencilStateParams = { VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, // VkStructureType sType; DE_NULL, // const void* pNext; 0u, // VkPipelineDepthStencilStateCreateFlags flags; false, // VkBool32 depthTestEnable; false, // VkBool32 depthWriteEnable; VK_COMPARE_OP_LESS, // VkCompareOp depthCompareOp; false, // VkBool32 depthBoundsTestEnable; false, // VkBool32 stencilTestEnable; // VkStencilOpState front; { VK_STENCIL_OP_KEEP, // VkStencilOp failOp; VK_STENCIL_OP_KEEP, // VkStencilOp passOp; VK_STENCIL_OP_KEEP, // VkStencilOp depthFailOp; VK_COMPARE_OP_NEVER, // VkCompareOp compareOp; 0u, // deUint32 compareMask; 0u, // deUint32 writeMask; 0u, // deUint32 reference; }, // VkStencilOpState back; { VK_STENCIL_OP_KEEP, // VkStencilOp failOp; VK_STENCIL_OP_KEEP, // VkStencilOp passOp; VK_STENCIL_OP_KEEP, // VkStencilOp depthFailOp; VK_COMPARE_OP_NEVER, // VkCompareOp compareOp; 0u, // deUint32 compareMask; 0u, // deUint32 writeMask; 0u, // deUint32 reference; }, 0.0f, // float minDepthBounds; 1.0f, // float maxDepthBounds; }; const VkGraphicsPipelineCreateInfo graphicsPipelineParams = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, // VkStructureType sType; DE_NULL, // const void* pNext; 0u, // VkPipelineCreateFlags flags; 2u, // deUint32 stageCount; shaderStageParams, // const VkPipelineShaderStageCreateInfo* pStages; &vertexInputStateParams, // const VkPipelineVertexInputStateCreateInfo* pVertexInputState; &inputAssemblyStateParams, // const VkPipelineInputAssemblyStateCreateInfo* pInputAssemblyState; DE_NULL, // const VkPipelineTessellationStateCreateInfo* pTessellationState; &viewportStateParams, // const VkPipelineViewportStateCreateInfo* pViewportState; &rasterStateParams, // const VkPipelineRasterizationStateCreateInfo* pRasterizationState; &multisampleStateParams, // const VkPipelineMultisampleStateCreateInfo* pMultisampleState; &depthStencilStateParams, // const VkPipelineDepthStencilStateCreateInfo* pDepthStencilState; &colorBlendStateParams, // const VkPipelineColorBlendStateCreateInfo* pColorBlendState; (const VkPipelineDynamicStateCreateInfo*)DE_NULL, // const VkPipelineDynamicStateCreateInfo* pDynamicState; *m_pipelineLayout, // VkPipelineLayout layout; *m_renderPass, // VkRenderPass renderPass; 0u, // deUint32 subpass; 0u, // VkPipeline basePipelineHandle; 0u // deInt32 basePipelineIndex; }; m_graphicsPipeline = createGraphicsPipeline(vk, vkDevice, DE_NULL, &graphicsPipelineParams); } // Create vertex buffer { // calculate buffer size // 32 is maximal attribute size (4*sizeof(double)), // 8 maximal vertex count used in writeVertexInputData VkDeviceSize bufferSize = 32 * 8 * attributeDescriptions.size(); const VkBufferCreateInfo vertexBufferParams = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, // VkStructureType sType; DE_NULL, // const void* pNext; 0u, // VkBufferCreateFlags flags; bufferSize, // VkDeviceSize size; VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, // VkBufferUsageFlags usage; VK_SHARING_MODE_EXCLUSIVE, // VkSharingMode sharingMode; 1u, // deUint32 queueFamilyIndexCount; &queueFamilyIndex // const deUint32* pQueueFamilyIndices; }; // Upload data for each vertex input binding for (deUint32 bindingNdx = 0; bindingNdx < bindingDescriptions.size(); bindingNdx++) { Move vertexBuffer = createBuffer(vk, vkDevice, &vertexBufferParams); de::MovePtr vertexBufferAlloc = memAlloc.allocate(getBufferMemoryRequirements(vk, vkDevice, *vertexBuffer), MemoryRequirement::HostVisible); VK_CHECK(vk.bindBufferMemory(vkDevice, *vertexBuffer, vertexBufferAlloc->getMemory(), vertexBufferAlloc->getOffset())); writeVertexInputData((deUint8*)vertexBufferAlloc->getHostPtr(), bindingDescriptions[bindingNdx], bindingOffsets[bindingNdx], attributeDescriptions); flushAlloc(vk, vkDevice, *vertexBufferAlloc); m_vertexBuffers.push_back(vertexBuffer.disown()); m_vertexBufferAllocs.push_back(vertexBufferAlloc.release()); } } // Create command pool m_cmdPool = createCommandPool(vk, vkDevice, VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, queueFamilyIndex); // Create command buffer { const VkClearValue attachmentClearValue = defaultClearValue(m_colorFormat); const VkImageMemoryBarrier attachmentLayoutBarrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, // VkStructureType sType; DE_NULL, // const void* pNext; 0u, // VkAccessFlags srcAccessMask; VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, // VkAccessFlags dstAccessMask; VK_IMAGE_LAYOUT_UNDEFINED, // VkImageLayout oldLayout; VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, // VkImageLayout newLayout; VK_QUEUE_FAMILY_IGNORED, // deUint32 srcQueueFamilyIndex; VK_QUEUE_FAMILY_IGNORED, // deUint32 dstQueueFamilyIndex; *m_colorImage, // VkImage image; { VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u }, // VkImageSubresourceRange subresourceRange; }; m_cmdBuffer = allocateCommandBuffer(vk, vkDevice, *m_cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY); beginCommandBuffer(vk, *m_cmdBuffer, 0u); vk.cmdPipelineBarrier(*m_cmdBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, (VkDependencyFlags)0, 0u, DE_NULL, 0u, DE_NULL, 1u, &attachmentLayoutBarrier); beginRenderPass(vk, *m_cmdBuffer, *m_renderPass, *m_framebuffer, makeRect2D(0, 0, m_renderSize.x(), m_renderSize.y()), attachmentClearValue); vk.cmdBindPipeline(*m_cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *m_graphicsPipeline); std::vector vertexBuffers; for (size_t bufferNdx = 0; bufferNdx < m_vertexBuffers.size(); bufferNdx++) vertexBuffers.push_back(m_vertexBuffers[bufferNdx]); if (vertexBuffers.size() <= 1) { // One vertex buffer vk.cmdBindVertexBuffers(*m_cmdBuffer, 0, (deUint32)vertexBuffers.size(), vertexBuffers.data(), bindingOffsets.data()); } else { // Smoke-test vkCmdBindVertexBuffers(..., startBinding, ... ) const deUint32 firstHalfLength = (deUint32)vertexBuffers.size() / 2; const deUint32 secondHalfLength = firstHalfLength + (deUint32)(vertexBuffers.size() % 2); // Bind first half of vertex buffers vk.cmdBindVertexBuffers(*m_cmdBuffer, 0, firstHalfLength, vertexBuffers.data(), bindingOffsets.data()); // Bind second half of vertex buffers vk.cmdBindVertexBuffers(*m_cmdBuffer, firstHalfLength, secondHalfLength, vertexBuffers.data() + firstHalfLength, bindingOffsets.data() + firstHalfLength); } vk.cmdDraw(*m_cmdBuffer, 4, 2, 0, 0); endRenderPass(vk, *m_cmdBuffer); endCommandBuffer(vk, *m_cmdBuffer); } } VertexInputInstance::~VertexInputInstance (void) { const DeviceInterface& vk = m_context.getDeviceInterface(); const VkDevice vkDevice = m_context.getDevice(); for (size_t bufferNdx = 0; bufferNdx < m_vertexBuffers.size(); bufferNdx++) vk.destroyBuffer(vkDevice, m_vertexBuffers[bufferNdx], DE_NULL); for (size_t allocNdx = 0; allocNdx < m_vertexBufferAllocs.size(); allocNdx++) delete m_vertexBufferAllocs[allocNdx]; } void VertexInputInstance::writeVertexInputData(deUint8* destPtr, const VkVertexInputBindingDescription& bindingDescription, const VkDeviceSize bindingOffset, const AttributeDescriptionList& attributes) { const deUint32 vertexCount = (bindingDescription.inputRate == VK_VERTEX_INPUT_RATE_VERTEX) ? (4 * 2) : 2; deUint8* destOffsetPtr = ((deUint8 *)destPtr) + bindingOffset; for (deUint32 vertexNdx = 0; vertexNdx < vertexCount; vertexNdx++) { for (size_t attributeNdx = 0; attributeNdx < attributes.size(); attributeNdx++) { const VertexInputAttributeDescription& attribDesc = attributes[attributeNdx]; // Only write vertex input data to bindings referenced by attribute descriptions if (attribDesc.vkDescription.binding == bindingDescription.binding) { writeVertexInputValue(destOffsetPtr + attribDesc.vkDescription.offset, attribDesc, vertexNdx); } } destOffsetPtr += bindingDescription.stride; } } void writeVertexInputValueSint (deUint8* destPtr, VkFormat format, int componentNdx, deInt32 value) { const deUint32 componentSize = getVertexFormatComponentSize(format); deUint8* destFormatPtr = ((deUint8*)destPtr) + componentSize * componentNdx; switch (componentSize) { case 1: *((deInt8*)destFormatPtr) = (deInt8)value; break; case 2: *((deInt16*)destFormatPtr) = (deInt16)value; break; case 4: *((deInt32*)destFormatPtr) = (deInt32)value; break; default: DE_ASSERT(false); } } void writeVertexInputValueIntPacked(deUint8* destPtr, deUint32& packedFormat, deUint32& componentOffset, VkFormat format, deUint32 componentNdx, deUint32 value) { const deUint32 componentWidth = getPackedVertexFormatComponentWidth(format, componentNdx); const deUint32 componentCount = getVertexFormatComponentCount(format); const deUint32 usedBits = ~(deUint32)0 >> ((getVertexFormatSize(format) * 8) - componentWidth); componentOffset -= componentWidth; packedFormat |= (((deUint32)value & usedBits) << componentOffset); if (componentNdx == componentCount - 1) *((deUint32*)destPtr) = (deUint32)packedFormat; } void writeVertexInputValueUint (deUint8* destPtr, VkFormat format, int componentNdx, deUint32 value) { const deUint32 componentSize = getVertexFormatComponentSize(format); deUint8* destFormatPtr = ((deUint8*)destPtr) + componentSize * componentNdx; switch (componentSize) { case 1: *((deUint8 *)destFormatPtr) = (deUint8)value; break; case 2: *((deUint16 *)destFormatPtr) = (deUint16)value; break; case 4: *((deUint32 *)destFormatPtr) = (deUint32)value; break; default: DE_ASSERT(false); } } void writeVertexInputValueSfloat (deUint8* destPtr, VkFormat format, int componentNdx, float value) { const deUint32 componentSize = getVertexFormatComponentSize(format); deUint8* destFormatPtr = ((deUint8*)destPtr) + componentSize * componentNdx; switch (componentSize) { case 2: { deFloat16 f16 = deFloat32To16(value); deMemcpy(destFormatPtr, &f16, sizeof(f16)); break; } case 4: deMemcpy(destFormatPtr, &value, sizeof(value)); break; default: DE_ASSERT(false); } } void writeVertexInputValueUfloat (deUint8* destPtr, deUint32& packedFormat, deUint32& componentOffset, VkFormat format, deUint32 componentNdx, float value) { deFloat16 f16 = deFloat32To16(value); const deUint32 componentWidth = getPackedVertexFormatComponentWidth(format, componentNdx); const deUint32 componentCount = getVertexFormatComponentCount(format); const deUint32 usedBits = ~(deUint32)0 >> ((getVertexFormatSize(format) * 8) - componentWidth); // The ufloat 10 or 11 has no sign bit, but the same exponent bits than float16. // The sign bit will be removed by the mask. Therefore we pick one more mantissa bit. deUint32 valueUFloat = f16 >> (16 - componentWidth - 1); // TODO: VK_FORMAT_E5B9G9R9_UFLOAT_PACK32 not supported. DE_ASSERT(format == VK_FORMAT_B10G11R11_UFLOAT_PACK32); componentOffset -= componentWidth; packedFormat |= (valueUFloat & usedBits) << componentOffset; if (componentNdx == componentCount - 1) *((deUint32*)destPtr) = (deUint32)packedFormat; } void VertexInputInstance::writeVertexInputValue (deUint8* destPtr, const VertexInputAttributeDescription& attribute, int indexId) { const int vertexInputCount = VertexInputTest::s_glslTypeDescriptions[attribute.glslType].vertexInputCount; const int componentCount = VertexInputTest::s_glslTypeDescriptions[attribute.glslType].vertexInputComponentCount; const deUint32 totalComponentCount = componentCount * vertexInputCount; const deUint32 vertexInputIndex = indexId * totalComponentCount + attribute.vertexInputIndex * componentCount; const bool hasBGROrder = isVertexFormatComponentOrderBGR(attribute.vkDescription.format); const bool hasABGROrder = isVertexFormatComponentOrderABGR(attribute.vkDescription.format); const bool hasARGBOrder = isVertexFormatComponentOrderARGB(attribute.vkDescription.format); deUint32 componentOffset = getVertexFormatSize(attribute.vkDescription.format) * 8; deUint32 packedFormat32 = 0; deUint32 swizzledNdx; const deUint32 BGRSwizzle[] = { 2, 1, 0, 3 }; const deUint32 ABGRSwizzle[] = { 3, 2, 1, 0 }; const deUint32 ARGBSwizzle[] = { 3, 0, 1, 2 }; for (int componentNdx = 0; componentNdx < componentCount; componentNdx++) { if (hasABGROrder) swizzledNdx = ABGRSwizzle[componentNdx]; else if (hasARGBOrder) swizzledNdx = ARGBSwizzle[componentNdx]; else if (hasBGROrder) swizzledNdx = BGRSwizzle[componentNdx]; else swizzledNdx = componentNdx; const deInt32 maxIntValue = isVertexFormatPacked(attribute.vkDescription.format) ? (1 << (getPackedVertexFormatComponentWidth(attribute.vkDescription.format, componentNdx) - 1)) - 1 : (1 << (getVertexFormatComponentSize(attribute.vkDescription.format) * 8 - 1)) - 1; const deUint32 maxUintValue = isVertexFormatPacked(attribute.vkDescription.format) ? ((1 << getPackedVertexFormatComponentWidth(attribute.vkDescription.format, componentNdx)) - 1) : (1 << (getVertexFormatComponentSize(attribute.vkDescription.format) * 8 )) - 1; const deInt32 minIntValue = -maxIntValue; const deUint32 minUintValue = 0; switch (attribute.glslType) { case VertexInputTest::GLSL_TYPE_INT: case VertexInputTest::GLSL_TYPE_IVEC2: case VertexInputTest::GLSL_TYPE_IVEC3: case VertexInputTest::GLSL_TYPE_IVEC4: { if (isVertexFormatPacked(attribute.vkDescription.format)) writeVertexInputValueIntPacked(destPtr, packedFormat32, componentOffset, attribute.vkDescription.format, componentNdx, deClamp32(-(deInt32)(vertexInputIndex + swizzledNdx), minIntValue, maxIntValue)); else writeVertexInputValueSint(destPtr, attribute.vkDescription.format, componentNdx, -(deInt32)(vertexInputIndex + swizzledNdx)); break; } case VertexInputTest::GLSL_TYPE_UINT: case VertexInputTest::GLSL_TYPE_UVEC2: case VertexInputTest::GLSL_TYPE_UVEC3: case VertexInputTest::GLSL_TYPE_UVEC4: { if (isVertexFormatPacked(attribute.vkDescription.format)) writeVertexInputValueIntPacked(destPtr, packedFormat32, componentOffset, attribute.vkDescription.format, componentNdx, deClamp32(vertexInputIndex + swizzledNdx, minUintValue, maxUintValue)); else writeVertexInputValueUint(destPtr, attribute.vkDescription.format, componentNdx, vertexInputIndex + swizzledNdx); break; } case VertexInputTest::GLSL_TYPE_FLOAT: case VertexInputTest::GLSL_TYPE_VEC2: case VertexInputTest::GLSL_TYPE_VEC3: case VertexInputTest::GLSL_TYPE_VEC4: case VertexInputTest::GLSL_TYPE_MAT2: case VertexInputTest::GLSL_TYPE_MAT3: case VertexInputTest::GLSL_TYPE_MAT4: { if (isVertexFormatSfloat(attribute.vkDescription.format)) { writeVertexInputValueSfloat(destPtr, attribute.vkDescription.format, componentNdx, -(0.01f * (float)(vertexInputIndex + swizzledNdx))); } else if (isVertexFormatUfloat(attribute.vkDescription.format)) { writeVertexInputValueUfloat(destPtr, packedFormat32, componentOffset, attribute.vkDescription.format, componentNdx, 0.01f * (float)(vertexInputIndex + swizzledNdx)); } else if (isVertexFormatSscaled(attribute.vkDescription.format)) { if (isVertexFormatPacked(attribute.vkDescription.format)) writeVertexInputValueIntPacked(destPtr, packedFormat32, componentOffset, attribute.vkDescription.format, componentNdx, deClamp32(-(deInt32)(vertexInputIndex + swizzledNdx), minIntValue, maxIntValue)); else writeVertexInputValueSint(destPtr, attribute.vkDescription.format, componentNdx, -(deInt32)(vertexInputIndex + swizzledNdx)); } else if (isVertexFormatUscaled(attribute.vkDescription.format) || isVertexFormatUnorm(attribute.vkDescription.format) || isVertexFormatSRGB(attribute.vkDescription.format)) { if (isVertexFormatPacked(attribute.vkDescription.format)) writeVertexInputValueIntPacked(destPtr, packedFormat32, componentOffset, attribute.vkDescription.format, componentNdx, deClamp32(vertexInputIndex + swizzledNdx, minUintValue, maxUintValue)); else writeVertexInputValueUint(destPtr, attribute.vkDescription.format, componentNdx, vertexInputIndex + swizzledNdx); } else if (isVertexFormatSnorm(attribute.vkDescription.format)) { if (isVertexFormatPacked(attribute.vkDescription.format)) writeVertexInputValueIntPacked(destPtr, packedFormat32, componentOffset, attribute.vkDescription.format, componentNdx, deClamp32(minIntValue + (vertexInputIndex + swizzledNdx), minIntValue, maxIntValue)); else writeVertexInputValueSint(destPtr, attribute.vkDescription.format, componentNdx, minIntValue + (vertexInputIndex + swizzledNdx)); } else DE_ASSERT(false); break; } case VertexInputTest::GLSL_TYPE_DOUBLE: case VertexInputTest::GLSL_TYPE_DVEC2: case VertexInputTest::GLSL_TYPE_DVEC3: case VertexInputTest::GLSL_TYPE_DVEC4: case VertexInputTest::GLSL_TYPE_DMAT2: case VertexInputTest::GLSL_TYPE_DMAT3: case VertexInputTest::GLSL_TYPE_DMAT4: *(reinterpret_cast(destPtr) + componentNdx) = -0.01 * (vertexInputIndex + swizzledNdx); break; default: DE_ASSERT(false); } } } tcu::TestStatus VertexInputInstance::iterate (void) { const DeviceInterface& vk = m_context.getDeviceInterface(); const VkDevice vkDevice = m_context.getDevice(); const VkQueue queue = m_context.getUniversalQueue(); submitCommandsAndWait(vk, vkDevice, queue, m_cmdBuffer.get()); return verifyImage(); } bool VertexInputTest::isCompatibleType (VkFormat format, GlslType glslType) { const GlslTypeDescription glslTypeDesc = s_glslTypeDescriptions[glslType]; if ((deUint32)s_glslTypeDescriptions[glslType].vertexInputComponentCount == getVertexFormatComponentCount(format)) { switch (glslTypeDesc.basicType) { case GLSL_BASIC_TYPE_INT: return isVertexFormatSint(format); case GLSL_BASIC_TYPE_UINT: return isVertexFormatUint(format); case GLSL_BASIC_TYPE_FLOAT: return (isVertexFormatPacked(format) ? (getVertexFormatSize(format) <= 4) : getVertexFormatComponentSize(format) <= 4) && (isVertexFormatSfloat(format) || isVertexFormatSnorm(format) || isVertexFormatUnorm(format) || isVertexFormatSscaled(format) || isVertexFormatUscaled(format) || isVertexFormatSRGB(format) || isVertexFormatUfloat(format)); case GLSL_BASIC_TYPE_DOUBLE: return isVertexFormatSfloat(format) && getVertexFormatComponentSize(format) == 8; default: DE_ASSERT(false); return false; } } else return false; } tcu::TestStatus VertexInputInstance::verifyImage (void) { bool compareOk = false; const tcu::TextureFormat tcuColorFormat = mapVkFormat(m_colorFormat); tcu::TextureLevel reference (tcuColorFormat, m_renderSize.x(), m_renderSize.y()); const tcu::PixelBufferAccess refRedSubregion (tcu::getSubregion(reference.getAccess(), deRoundFloatToInt32((float)m_renderSize.x() * 0.0f), deRoundFloatToInt32((float)m_renderSize.y() * 0.0f), deRoundFloatToInt32((float)m_renderSize.x() * 0.5f), deRoundFloatToInt32((float)m_renderSize.y() * 1.0f))); const tcu::PixelBufferAccess refBlueSubregion (tcu::getSubregion(reference.getAccess(), deRoundFloatToInt32((float)m_renderSize.x() * 0.5f), deRoundFloatToInt32((float)m_renderSize.y() * 0.0f), deRoundFloatToInt32((float)m_renderSize.x() * 0.5f), deRoundFloatToInt32((float)m_renderSize.y() * 1.0f))); // Create reference image tcu::clear(reference.getAccess(), defaultClearColor(tcuColorFormat)); tcu::clear(refRedSubregion, tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f)); tcu::clear(refBlueSubregion, tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f)); // Compare result with reference image { const DeviceInterface& vk = m_context.getDeviceInterface(); const VkDevice vkDevice = m_context.getDevice(); const VkQueue queue = m_context.getUniversalQueue(); const deUint32 queueFamilyIndex = m_context.getUniversalQueueFamilyIndex(); SimpleAllocator allocator (vk, vkDevice, getPhysicalDeviceMemoryProperties(m_context.getInstanceInterface(), m_context.getPhysicalDevice())); de::MovePtr result = readColorAttachment(vk, vkDevice, queue, queueFamilyIndex, allocator, *m_colorImage, m_colorFormat, m_renderSize); compareOk = tcu::intThresholdPositionDeviationCompare(m_context.getTestContext().getLog(), "IntImageCompare", "Image comparison", reference.getAccess(), result->getAccess(), tcu::UVec4(2, 2, 2, 2), tcu::IVec3(1, 1, 0), true, tcu::COMPARE_LOG_RESULT); } if (compareOk) return tcu::TestStatus::pass("Result image matches reference"); else return tcu::TestStatus::fail("Image mismatch"); } std::string getAttributeInfoCaseName (const VertexInputTest::AttributeInfo& attributeInfo) { std::ostringstream caseName; const std::string formatName = getFormatName(attributeInfo.vkType); caseName << "as_" << de::toLower(formatName.substr(10)) << "_rate_"; if (attributeInfo.inputRate == VK_VERTEX_INPUT_RATE_VERTEX) caseName << "vertex"; else caseName << "instance"; return caseName.str(); } std::string getAttributeInfoDescription (const VertexInputTest::AttributeInfo& attributeInfo) { std::ostringstream caseDesc; caseDesc << std::string(VertexInputTest::s_glslTypeDescriptions[attributeInfo.glslType].name) << " from type " << getFormatName(attributeInfo.vkType) << " with "; if (attributeInfo.inputRate == VK_VERTEX_INPUT_RATE_VERTEX) caseDesc << "vertex input rate "; else caseDesc << "instance input rate "; return caseDesc.str(); } std::string getAttributeInfosDescription (const std::vector& attributeInfos) { std::ostringstream caseDesc; caseDesc << "Uses vertex attributes:\n"; for (size_t attributeNdx = 0; attributeNdx < attributeInfos.size(); attributeNdx++) caseDesc << "\t- " << getAttributeInfoDescription (attributeInfos[attributeNdx]) << "\n"; return caseDesc.str(); } struct CompatibleFormats { VertexInputTest::GlslType glslType; std::vector compatibleVkFormats; }; void createSingleAttributeCases (tcu::TestCaseGroup* singleAttributeTests, VertexInputTest::GlslType glslType) { const VkFormat vertexFormats[] = { // Required, unpacked VK_FORMAT_R8_UNORM, VK_FORMAT_R8_SNORM, VK_FORMAT_R8_UINT, VK_FORMAT_R8_SINT, VK_FORMAT_R8G8_UNORM, VK_FORMAT_R8G8_SNORM, VK_FORMAT_R8G8_UINT, VK_FORMAT_R8G8_SINT, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_SNORM, VK_FORMAT_R8G8B8A8_UINT, VK_FORMAT_R8G8B8A8_SINT, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R16_UNORM, VK_FORMAT_R16_SNORM, VK_FORMAT_R16_UINT, VK_FORMAT_R16_SINT, VK_FORMAT_R16_SFLOAT, VK_FORMAT_R16G16_UNORM, VK_FORMAT_R16G16_SNORM, VK_FORMAT_R16G16_UINT, VK_FORMAT_R16G16_SINT, VK_FORMAT_R16G16_SFLOAT, VK_FORMAT_R16G16B16A16_UNORM, VK_FORMAT_R16G16B16A16_SNORM, VK_FORMAT_R16G16B16A16_UINT, VK_FORMAT_R16G16B16A16_SINT, VK_FORMAT_R16G16B16A16_SFLOAT, VK_FORMAT_R32_UINT, VK_FORMAT_R32_SINT, VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32G32_UINT, VK_FORMAT_R32G32_SINT, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32B32_UINT, VK_FORMAT_R32G32B32_SINT, VK_FORMAT_R32G32B32_SFLOAT, VK_FORMAT_R32G32B32A32_UINT, VK_FORMAT_R32G32B32A32_SINT, VK_FORMAT_R32G32B32A32_SFLOAT, // Scaled formats VK_FORMAT_R8G8_USCALED, VK_FORMAT_R8G8_SSCALED, VK_FORMAT_R16_USCALED, VK_FORMAT_R16_SSCALED, VK_FORMAT_R8G8B8_USCALED, VK_FORMAT_R8G8B8_SSCALED, VK_FORMAT_B8G8R8_USCALED, VK_FORMAT_B8G8R8_SSCALED, VK_FORMAT_R8G8B8A8_USCALED, VK_FORMAT_R8G8B8A8_SSCALED, VK_FORMAT_B8G8R8A8_USCALED, VK_FORMAT_B8G8R8A8_SSCALED, VK_FORMAT_R16G16_USCALED, VK_FORMAT_R16G16_SSCALED, VK_FORMAT_R16G16B16_USCALED, VK_FORMAT_R16G16B16_SSCALED, VK_FORMAT_R16G16B16A16_USCALED, VK_FORMAT_R16G16B16A16_SSCALED, // SRGB formats VK_FORMAT_R8_SRGB, VK_FORMAT_R8G8_SRGB, VK_FORMAT_R8G8B8_SRGB, VK_FORMAT_B8G8R8_SRGB, VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_B8G8R8A8_SRGB, // Double formats VK_FORMAT_R64_SFLOAT, VK_FORMAT_R64G64_SFLOAT, VK_FORMAT_R64G64B64_SFLOAT, VK_FORMAT_R64G64B64A64_SFLOAT, // Packed formats VK_FORMAT_A2R10G10B10_USCALED_PACK32, VK_FORMAT_A2R10G10B10_SSCALED_PACK32, VK_FORMAT_A2R10G10B10_UINT_PACK32, VK_FORMAT_A2R10G10B10_SINT_PACK32, VK_FORMAT_A8B8G8R8_UNORM_PACK32, VK_FORMAT_A8B8G8R8_SNORM_PACK32, VK_FORMAT_A2R10G10B10_UNORM_PACK32, VK_FORMAT_A2R10G10B10_SNORM_PACK32, VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_FORMAT_A2B10G10R10_SNORM_PACK32, VK_FORMAT_B10G11R11_UFLOAT_PACK32 }; for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(vertexFormats); formatNdx++) { if (VertexInputTest::isCompatibleType(vertexFormats[formatNdx], glslType)) { // Create test case for RATE_VERTEX VertexInputTest::AttributeInfo attributeInfo; attributeInfo.vkType = vertexFormats[formatNdx]; attributeInfo.glslType = glslType; attributeInfo.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; singleAttributeTests->addChild(new VertexInputTest(singleAttributeTests->getTestContext(), getAttributeInfoCaseName(attributeInfo), getAttributeInfoDescription(attributeInfo), std::vector(1, attributeInfo), VertexInputTest::BINDING_MAPPING_ONE_TO_ONE, VertexInputTest::ATTRIBUTE_LAYOUT_INTERLEAVED)); // Create test case for RATE_INSTANCE attributeInfo.inputRate = VK_VERTEX_INPUT_RATE_INSTANCE; singleAttributeTests->addChild(new VertexInputTest(singleAttributeTests->getTestContext(), getAttributeInfoCaseName(attributeInfo), getAttributeInfoDescription(attributeInfo), std::vector(1, attributeInfo), VertexInputTest::BINDING_MAPPING_ONE_TO_ONE, VertexInputTest::ATTRIBUTE_LAYOUT_INTERLEAVED)); } } } void createSingleAttributeTests (tcu::TestCaseGroup* singleAttributeTests) { for (int glslTypeNdx = 0; glslTypeNdx < VertexInputTest::GLSL_TYPE_COUNT; glslTypeNdx++) { VertexInputTest::GlslType glslType = (VertexInputTest::GlslType)glslTypeNdx; addTestGroup(singleAttributeTests, VertexInputTest::s_glslTypeDescriptions[glslType].name, "", createSingleAttributeCases, glslType); } } // Create all unique GlslType combinations recursively void createMultipleAttributeCases (deUint32 depth, deUint32 firstNdx, CompatibleFormats* compatibleFormats, de::Random& randomFunc, tcu::TestCaseGroup& testGroup, VertexInputTest::BindingMapping bindingMapping, VertexInputTest::AttributeLayout attributeLayout, VertexInputTest::LayoutSkip layoutSkip, VertexInputTest::LayoutOrder layoutOrder, const std::vector& attributeInfos = std::vector(0)) { tcu::TestContext& testCtx = testGroup.getTestContext(); // Exclude double values, which are not included in vertexFormats for (deUint32 currentNdx = firstNdx; currentNdx < VertexInputTest::GLSL_TYPE_DOUBLE - depth; currentNdx++) { std::vector newAttributeInfos = attributeInfos; { VertexInputTest::AttributeInfo attributeInfo; attributeInfo.glslType = (VertexInputTest::GlslType)currentNdx; attributeInfo.inputRate = (depth % 2 == 0) ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE; attributeInfo.vkType = VK_FORMAT_UNDEFINED; newAttributeInfos.push_back(attributeInfo); } // Add test case if (depth == 0) { // Select a random compatible format for each attribute for (size_t i = 0; i < newAttributeInfos.size(); i++) { const std::vector& formats = compatibleFormats[newAttributeInfos[i].glslType].compatibleVkFormats; newAttributeInfos[i].vkType = formats[randomFunc.getUint32() % formats.size()]; } const std::string caseName = VertexInputTest::s_glslTypeDescriptions[currentNdx].name; const std::string caseDesc = getAttributeInfosDescription(newAttributeInfos); testGroup.addChild(new VertexInputTest(testCtx, caseName, caseDesc, newAttributeInfos, bindingMapping, attributeLayout, layoutSkip, layoutOrder)); } // Add test group else { const std::string name = VertexInputTest::s_glslTypeDescriptions[currentNdx].name; de::MovePtr newTestGroup (new tcu::TestCaseGroup(testCtx, name.c_str(), "")); createMultipleAttributeCases(depth - 1u, currentNdx + 1u, compatibleFormats, randomFunc, *newTestGroup, bindingMapping, attributeLayout, layoutSkip, layoutOrder, newAttributeInfos); testGroup.addChild(newTestGroup.release()); } } } void createMultipleAttributeTests (tcu::TestCaseGroup* multipleAttributeTests) { // Required vertex formats, unpacked const VkFormat vertexFormats[] = { VK_FORMAT_R8_UNORM, VK_FORMAT_R8_SNORM, VK_FORMAT_R8_UINT, VK_FORMAT_R8_SINT, VK_FORMAT_R8G8_UNORM, VK_FORMAT_R8G8_SNORM, VK_FORMAT_R8G8_UINT, VK_FORMAT_R8G8_SINT, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_SNORM, VK_FORMAT_R8G8B8A8_UINT, VK_FORMAT_R8G8B8A8_SINT, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R16_UNORM, VK_FORMAT_R16_SNORM, VK_FORMAT_R16_UINT, VK_FORMAT_R16_SINT, VK_FORMAT_R16_SFLOAT, VK_FORMAT_R16G16_UNORM, VK_FORMAT_R16G16_SNORM, VK_FORMAT_R16G16_UINT, VK_FORMAT_R16G16_SINT, VK_FORMAT_R16G16_SFLOAT, VK_FORMAT_R16G16B16A16_UNORM, VK_FORMAT_R16G16B16A16_SNORM, VK_FORMAT_R16G16B16A16_UINT, VK_FORMAT_R16G16B16A16_SINT, VK_FORMAT_R16G16B16A16_SFLOAT, VK_FORMAT_R32_UINT, VK_FORMAT_R32_SINT, VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32G32_UINT, VK_FORMAT_R32G32_SINT, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32B32_UINT, VK_FORMAT_R32G32B32_SINT, VK_FORMAT_R32G32B32_SFLOAT, VK_FORMAT_R32G32B32A32_UINT, VK_FORMAT_R32G32B32A32_SINT, VK_FORMAT_R32G32B32A32_SFLOAT }; const VertexInputTest::LayoutSkip layoutSkips[] = { VertexInputTest::LAYOUT_SKIP_DISABLED, VertexInputTest::LAYOUT_SKIP_ENABLED }; const VertexInputTest::LayoutOrder layoutOrders[] = { VertexInputTest::LAYOUT_ORDER_IN_ORDER, VertexInputTest::LAYOUT_ORDER_OUT_OF_ORDER }; // Find compatible VK formats for each GLSL vertex type CompatibleFormats compatibleFormats[VertexInputTest::GLSL_TYPE_COUNT]; { for (int glslTypeNdx = 0; glslTypeNdx < VertexInputTest::GLSL_TYPE_COUNT; glslTypeNdx++) { for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(vertexFormats); formatNdx++) { if (VertexInputTest::isCompatibleType(vertexFormats[formatNdx], (VertexInputTest::GlslType)glslTypeNdx)) compatibleFormats[glslTypeNdx].compatibleVkFormats.push_back(vertexFormats[formatNdx]); } } } de::Random randomFunc(102030); tcu::TestContext& testCtx = multipleAttributeTests->getTestContext(); for (deUint32 layoutSkipNdx = 0; layoutSkipNdx < DE_LENGTH_OF_ARRAY(layoutSkips); layoutSkipNdx++) for (deUint32 layoutOrderNdx = 0; layoutOrderNdx < DE_LENGTH_OF_ARRAY(layoutOrders); layoutOrderNdx++) { const VertexInputTest::LayoutSkip layoutSkip = layoutSkips[layoutSkipNdx]; const VertexInputTest::LayoutOrder layoutOrder = layoutOrders[layoutOrderNdx]; de::MovePtr oneToOneAttributeTests(new tcu::TestCaseGroup(testCtx, "attributes", "")); de::MovePtr oneToManyAttributeTests(new tcu::TestCaseGroup(testCtx, "attributes", "")); de::MovePtr oneToManySequentialAttributeTests(new tcu::TestCaseGroup(testCtx, "attributes_sequential", "")); if (layoutSkip == VertexInputTest::LAYOUT_SKIP_ENABLED && layoutOrder == VertexInputTest::LAYOUT_ORDER_OUT_OF_ORDER) continue; createMultipleAttributeCases(2u, 0u, compatibleFormats, randomFunc, *oneToOneAttributeTests, VertexInputTest::BINDING_MAPPING_ONE_TO_ONE, VertexInputTest::ATTRIBUTE_LAYOUT_INTERLEAVED, layoutSkip, layoutOrder); createMultipleAttributeCases(2u, 0u, compatibleFormats, randomFunc, *oneToManyAttributeTests, VertexInputTest::BINDING_MAPPING_ONE_TO_MANY, VertexInputTest::ATTRIBUTE_LAYOUT_INTERLEAVED, layoutSkip, layoutOrder); createMultipleAttributeCases(2u, 0u, compatibleFormats, randomFunc, *oneToManySequentialAttributeTests, VertexInputTest::BINDING_MAPPING_ONE_TO_MANY, VertexInputTest::ATTRIBUTE_LAYOUT_SEQUENTIAL, layoutSkip, layoutOrder); if (layoutSkip == VertexInputTest::LAYOUT_SKIP_ENABLED) { de::MovePtr layoutSkipTests(new tcu::TestCaseGroup(testCtx, "layout_skip", "Skip one layout after each attribute")); de::MovePtr bindingOneToOneTests(new tcu::TestCaseGroup(testCtx, "binding_one_to_one", "Each attribute uses a unique binding")); bindingOneToOneTests->addChild(oneToOneAttributeTests.release()); layoutSkipTests->addChild(bindingOneToOneTests.release()); de::MovePtr bindingOneToManyTests(new tcu::TestCaseGroup(testCtx, "binding_one_to_many", "Attributes share the same binding")); bindingOneToManyTests->addChild(oneToManyAttributeTests.release()); bindingOneToManyTests->addChild(oneToManySequentialAttributeTests.release()); layoutSkipTests->addChild(bindingOneToManyTests.release()); multipleAttributeTests->addChild(layoutSkipTests.release()); } else if (layoutOrder == VertexInputTest::LAYOUT_ORDER_OUT_OF_ORDER) { de::MovePtr layoutOutOfOrderTests(new tcu::TestCaseGroup(testCtx, "out_of_order", "Layout slots out of order")); de::MovePtr bindingOneToOneTests(new tcu::TestCaseGroup(testCtx, "binding_one_to_one", "Each attribute uses a unique binding")); bindingOneToOneTests->addChild(oneToOneAttributeTests.release()); layoutOutOfOrderTests->addChild(bindingOneToOneTests.release()); de::MovePtr bindingOneToManyTests(new tcu::TestCaseGroup(testCtx, "binding_one_to_many", "Attributes share the same binding")); bindingOneToManyTests->addChild(oneToManyAttributeTests.release()); bindingOneToManyTests->addChild(oneToManySequentialAttributeTests.release()); layoutOutOfOrderTests->addChild(bindingOneToManyTests.release()); multipleAttributeTests->addChild(layoutOutOfOrderTests.release()); } else { de::MovePtr bindingOneToOneTests(new tcu::TestCaseGroup(testCtx, "binding_one_to_one", "Each attribute uses a unique binding")); bindingOneToOneTests->addChild(oneToOneAttributeTests.release()); multipleAttributeTests->addChild(bindingOneToOneTests.release()); de::MovePtr bindingOneToManyTests(new tcu::TestCaseGroup(testCtx, "binding_one_to_many", "Attributes share the same binding")); bindingOneToManyTests->addChild(oneToManyAttributeTests.release()); bindingOneToManyTests->addChild(oneToManySequentialAttributeTests.release()); multipleAttributeTests->addChild(bindingOneToManyTests.release()); } } } void createMaxAttributeTests (tcu::TestCaseGroup* maxAttributeTests) { // Required vertex formats, unpacked const VkFormat vertexFormats[] = { VK_FORMAT_R8_UNORM, VK_FORMAT_R8_SNORM, VK_FORMAT_R8_UINT, VK_FORMAT_R8_SINT, VK_FORMAT_R8G8_UNORM, VK_FORMAT_R8G8_SNORM, VK_FORMAT_R8G8_UINT, VK_FORMAT_R8G8_SINT, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_SNORM, VK_FORMAT_R8G8B8A8_UINT, VK_FORMAT_R8G8B8A8_SINT, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R16_UNORM, VK_FORMAT_R16_SNORM, VK_FORMAT_R16_UINT, VK_FORMAT_R16_SINT, VK_FORMAT_R16_SFLOAT, VK_FORMAT_R16G16_UNORM, VK_FORMAT_R16G16_SNORM, VK_FORMAT_R16G16_UINT, VK_FORMAT_R16G16_SINT, VK_FORMAT_R16G16_SFLOAT, VK_FORMAT_R16G16B16A16_UNORM, VK_FORMAT_R16G16B16A16_SNORM, VK_FORMAT_R16G16B16A16_UINT, VK_FORMAT_R16G16B16A16_SINT, VK_FORMAT_R16G16B16A16_SFLOAT, VK_FORMAT_R32_UINT, VK_FORMAT_R32_SINT, VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32G32_UINT, VK_FORMAT_R32G32_SINT, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32B32_UINT, VK_FORMAT_R32G32B32_SINT, VK_FORMAT_R32G32B32_SFLOAT, VK_FORMAT_R32G32B32A32_UINT, VK_FORMAT_R32G32B32A32_SINT, VK_FORMAT_R32G32B32A32_SFLOAT }; // VkPhysicalDeviceLimits::maxVertexInputAttributes is used when attributeCount is 0 const deUint32 attributeCount[] = { 16, 32, 64, 128, 0 }; tcu::TestContext& testCtx (maxAttributeTests->getTestContext()); de::Random randomFunc (132030); // Find compatible VK formats for each GLSL vertex type CompatibleFormats compatibleFormats[VertexInputTest::GLSL_TYPE_COUNT]; { for (int glslTypeNdx = 0; glslTypeNdx < VertexInputTest::GLSL_TYPE_COUNT; glslTypeNdx++) { for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(vertexFormats); formatNdx++) { if (VertexInputTest::isCompatibleType(vertexFormats[formatNdx], (VertexInputTest::GlslType)glslTypeNdx)) compatibleFormats[glslTypeNdx].compatibleVkFormats.push_back(vertexFormats[formatNdx]); } } } for (deUint32 attributeCountNdx = 0; attributeCountNdx < DE_LENGTH_OF_ARRAY(attributeCount); attributeCountNdx++) { const std::string groupName = (attributeCount[attributeCountNdx] == 0 ? "query_max" : de::toString(attributeCount[attributeCountNdx])) + "_attributes"; const std::string groupDesc = de::toString(attributeCount[attributeCountNdx]) + " vertex input attributes"; de::MovePtr numAttributeTests(new tcu::TestCaseGroup(testCtx, groupName.c_str(), groupDesc.c_str())); de::MovePtr bindingOneToOneTests(new tcu::TestCaseGroup(testCtx, "binding_one_to_one", "Each attribute uses a unique binding")); de::MovePtr bindingOneToManyTests(new tcu::TestCaseGroup(testCtx, "binding_one_to_many", "Attributes share the same binding")); std::vector attributeInfos(attributeCount[attributeCountNdx]); for (deUint32 attributeNdx = 0; attributeNdx < attributeCount[attributeCountNdx]; attributeNdx++) { // Use random glslTypes, each consuming one attribute location const VertexInputTest::GlslType glslType = (VertexInputTest::GlslType)(randomFunc.getUint32() % VertexInputTest::GLSL_TYPE_MAT2); const std::vector& formats = compatibleFormats[glslType].compatibleVkFormats; const VkFormat format = formats[randomFunc.getUint32() % formats.size()]; attributeInfos[attributeNdx].glslType = glslType; attributeInfos[attributeNdx].inputRate = ((attributeCountNdx + attributeNdx) % 2 == 0) ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE; attributeInfos[attributeNdx].vkType = format; } bindingOneToOneTests->addChild(new VertexInputTest(testCtx, "interleaved", "Interleaved attribute layout", attributeInfos, VertexInputTest::BINDING_MAPPING_ONE_TO_ONE, VertexInputTest::ATTRIBUTE_LAYOUT_INTERLEAVED)); bindingOneToManyTests->addChild(new VertexInputTest(testCtx, "interleaved", "Interleaved attribute layout", attributeInfos, VertexInputTest::BINDING_MAPPING_ONE_TO_MANY, VertexInputTest::ATTRIBUTE_LAYOUT_INTERLEAVED)); bindingOneToManyTests->addChild(new VertexInputTest(testCtx, "sequential", "Sequential attribute layout", attributeInfos, VertexInputTest::BINDING_MAPPING_ONE_TO_MANY, VertexInputTest::ATTRIBUTE_LAYOUT_SEQUENTIAL)); numAttributeTests->addChild(bindingOneToOneTests.release()); numAttributeTests->addChild(bindingOneToManyTests.release()); maxAttributeTests->addChild(numAttributeTests.release()); } } } // anonymous void createVertexInputTests (tcu::TestCaseGroup* vertexInputTests) { addTestGroup(vertexInputTests, "single_attribute", "Uses one attribute", createSingleAttributeTests); addTestGroup(vertexInputTests, "multiple_attributes", "Uses more than one attribute", createMultipleAttributeTests); addTestGroup(vertexInputTests, "max_attributes", "Implementations can use as many vertex input attributes as they advertise", createMaxAttributeTests); } } // pipeline } // vkt