| /*------------------------------------------------------------------------ |
| * Vulkan Conformance Tests |
| * ------------------------ |
| * |
| * Copyright (c) 2021 The Khronos Group Inc. |
| * Copyright (c) 2021 Google LLC. |
| * |
| * 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 Shared memory layout test case. |
| *//*--------------------------------------------------------------------*/ |
| |
| #include <vkDefs.hpp> |
| #include "deRandom.hpp" |
| #include "gluContextInfo.hpp" |
| #include "gluVarTypeUtil.hpp" |
| #include "tcuTestLog.hpp" |
| |
| #include "vkBuilderUtil.hpp" |
| #include "vkMemUtil.hpp" |
| #include "vkQueryUtil.hpp" |
| #include "vkRefUtil.hpp" |
| #include "vkRef.hpp" |
| #include "vkTypeUtil.hpp" |
| #include "vkCmdUtil.hpp" |
| |
| #include "vktMemoryModelSharedLayoutCase.hpp" |
| #include "util/vktTypeComparisonUtil.hpp" |
| |
| namespace vkt |
| { |
| namespace MemoryModel |
| { |
| |
| using tcu::TestLog; |
| using std::string; |
| using std::vector; |
| using glu::VarType; |
| using glu::StructMember; |
| |
| namespace |
| { |
| void computeReferenceLayout (const VarType& type, vector<SharedStructVarEntry>& entries) |
| { |
| if (type.isBasicType()) |
| entries.push_back(SharedStructVarEntry(type.getBasicType(), 1)); |
| else if (type.isArrayType()) |
| { |
| const VarType &elemType = type.getElementType(); |
| |
| // Array of scalars, vectors or matrices. |
| if (elemType.isBasicType()) |
| entries.push_back(SharedStructVarEntry(elemType.getBasicType(), type.getArraySize())); |
| else |
| { |
| DE_ASSERT(elemType.isStructType() || elemType.isArrayType()); |
| for (int i = 0; i < type.getArraySize(); i++) |
| computeReferenceLayout(type.getElementType(), entries); |
| } |
| } |
| else |
| { |
| DE_ASSERT(type.isStructType()); |
| for (const auto& member : *type.getStructPtr()) |
| computeReferenceLayout(member.getType(), entries); |
| } |
| } |
| |
| void computeReferenceLayout (SharedStructVar& var) |
| { |
| // Top-level arrays need special care. |
| if (var.type.isArrayType()) |
| computeReferenceLayout(var.type.getElementType(), var.entries); |
| else |
| computeReferenceLayout(var.type, var.entries); |
| } |
| |
| void generateValue (const SharedStructVarEntry& entry, de::Random& rnd, vector<string>& values) |
| { |
| const glu::DataType scalarType = glu::getDataTypeScalarType(entry.type); |
| const int scalarSize = glu::getDataTypeScalarSize(entry.type); |
| const int arraySize = entry.arraySize; |
| const bool isMatrix = glu::isDataTypeMatrix(entry.type); |
| const int numVecs = isMatrix ? glu::getDataTypeMatrixNumColumns(entry.type) : 1; |
| const int vecSize = scalarSize / numVecs; |
| |
| DE_ASSERT(scalarSize % numVecs == 0); |
| DE_ASSERT(arraySize >= 0); |
| |
| string generatedValue; |
| for (int elemNdx = 0; elemNdx < arraySize; elemNdx++) |
| { |
| for (int vecNdx = 0; vecNdx < numVecs; vecNdx++) |
| { |
| for (int compNdx = 0; compNdx < vecSize; compNdx++) |
| { |
| switch (scalarType) |
| { |
| case glu::TYPE_INT: |
| case glu::TYPE_INT8: |
| case glu::TYPE_INT16: |
| // Fall through. This fits into all the types above. |
| generatedValue = de::toString(rnd.getInt(-9, 9)); |
| break; |
| case glu::TYPE_UINT: |
| case glu::TYPE_UINT8: |
| case glu::TYPE_UINT16: |
| // Fall through. This fits into all the types above. |
| generatedValue = de::toString(rnd.getInt(0, 9)).append("u"); |
| break; |
| case glu::TYPE_FLOAT: |
| case glu::TYPE_FLOAT16: |
| // Fall through. This fits into all the types above. |
| generatedValue = de::floatToString(static_cast<float>(rnd.getInt(-9, 9)), 1); |
| break; |
| case glu::TYPE_BOOL: |
| generatedValue = rnd.getBool() ? "true" : "false"; |
| break; |
| default: |
| DE_ASSERT(false); |
| } |
| |
| values.push_back(generatedValue); |
| } |
| } |
| } |
| } |
| |
| string getStructMemberName (const SharedStructVar& var, const glu::TypeComponentVector& accessPath) |
| { |
| std::ostringstream name; |
| |
| name << "." << var.name; |
| |
| for (auto pathComp = accessPath.begin(); pathComp != accessPath.end(); pathComp++) |
| { |
| if (pathComp->type == glu::VarTypeComponent::STRUCT_MEMBER) |
| { |
| const VarType curType = glu::getVarType(var.type, accessPath.begin(), pathComp); |
| const glu::StructType *structPtr = curType.getStructPtr(); |
| |
| name << "." << structPtr->getMember(pathComp->index).getName(); |
| } |
| else if (pathComp->type == glu::VarTypeComponent::ARRAY_ELEMENT) |
| name << "[" << pathComp->index << "]"; |
| else |
| DE_ASSERT(false); |
| } |
| |
| return name.str(); |
| } |
| } // anonymous |
| |
| NamedStructSP ShaderInterface::allocStruct (const string& name) |
| { |
| m_structs.emplace_back(new glu::StructType(name.c_str())); |
| return m_structs.back(); |
| } |
| |
| SharedStruct& ShaderInterface::allocSharedObject (const string& name, const string& instanceName) |
| { |
| m_sharedMemoryObjects.emplace_back(name, instanceName); |
| return m_sharedMemoryObjects.back(); |
| } |
| |
| void generateCompareFuncs (std::ostream &str, const ShaderInterface &interface) |
| { |
| std::set<glu::DataType> types; |
| std::set<glu::DataType> compareFuncs; |
| |
| // Collect unique basic types. |
| for (const auto& sharedObj : interface.getSharedObjects()) |
| for (const auto& var : sharedObj) |
| vkt::typecomputil::collectUniqueBasicTypes(types, var.type); |
| |
| // Set of compare functions required. |
| for (const auto& type : types) |
| vkt::typecomputil::getCompareDependencies(compareFuncs, type); |
| |
| for (int type = 0; type < glu::TYPE_LAST; ++type) |
| if (compareFuncs.find(glu::DataType(type)) != compareFuncs.end()) |
| str << vkt::typecomputil::getCompareFuncForType(glu::DataType(type)); |
| } |
| |
| void generateSharedMemoryWrites (std::ostream &src, const SharedStruct &object, |
| const SharedStructVar &var, const glu::SubTypeAccess &accessPath, |
| vector<string>::const_iterator &valueIter, bool compare) |
| { |
| const VarType curType = accessPath.getType(); |
| |
| if (curType.isArrayType()) |
| { |
| const int arraySize = curType.getArraySize(); |
| for (int i = 0; i < arraySize; i++) |
| generateSharedMemoryWrites(src, object, var, accessPath.element(i), valueIter, compare); |
| } |
| else if (curType.isStructType()) |
| { |
| const int numMembers = curType.getStructPtr()->getNumMembers(); |
| for (int i = 0; i < numMembers; i++) |
| generateSharedMemoryWrites(src, object, var, accessPath.member(i), valueIter, compare); |
| } |
| else |
| { |
| DE_ASSERT(curType.isBasicType()); |
| |
| const glu::DataType basicType = curType.getBasicType(); |
| const string typeName = glu::getDataTypeName(basicType); |
| const string sharedObjectVarName = object.getInstanceName(); |
| const string structMember = getStructMemberName(var, accessPath.getPath()); |
| const glu::DataType promoteType = vkt::typecomputil::getPromoteType(basicType); |
| |
| int numElements = glu::getDataTypeScalarSize(basicType); |
| if (glu::isDataTypeMatrix(basicType)) |
| numElements = glu::getDataTypeMatrixNumColumns(basicType) * glu::getDataTypeMatrixNumRows(basicType); |
| |
| if (compare) |
| { |
| src << "\t" << "allOk" << " = " << "allOk" << " && compare_" << typeName << "("; |
| // Comparison functions use 32-bit values. Convert 8/16-bit scalar and vector types if necessary. |
| // E.g. uint8_t becomes int. |
| if (basicType != promoteType || numElements > 1) |
| src << glu::getDataTypeName(promoteType) << "("; |
| } |
| else |
| { |
| src << "\t" << sharedObjectVarName << structMember << " = " << ""; |
| // If multiple literals or a 8/16-bit literal is assigned, the variable must be |
| // initialized with the constructor. |
| if (basicType != promoteType || numElements > 1) |
| src << glu::getDataTypeName(basicType) << "("; |
| } |
| |
| for (int i = 0; i < numElements; i++) |
| src << (i != 0 ? ", " : "") << *valueIter++; |
| |
| if (basicType != promoteType) |
| src << ")"; |
| else if (numElements > 1) |
| src << ")"; |
| |
| // Write the variable in the shared memory as the next argument for the comparison function. |
| // Initialize it as a new 32-bit variable in the case it's a 8-bit or a 16-bit variable. |
| if (compare) |
| { |
| if (basicType != promoteType) |
| src << ", " << glu::getDataTypeName(promoteType) << "(" << sharedObjectVarName |
| << structMember |
| << "))"; |
| else |
| src << ", " << sharedObjectVarName << structMember << ")"; |
| } |
| |
| src << ";\n"; |
| } |
| } |
| |
| string generateComputeShader (ShaderInterface &interface) |
| { |
| std::ostringstream src; |
| |
| src << "#version 450\n"; |
| |
| if (interface.is16BitTypesEnabled()) |
| src << "#extension GL_EXT_shader_explicit_arithmetic_types : enable\n"; |
| if (interface.is8BitTypesEnabled()) |
| src << "#extension GL_EXT_shader_explicit_arithmetic_types_int8 : enable\n"; |
| |
| src << "layout(local_size_x = 1) in;\n"; |
| src << "\n"; |
| |
| src << "layout(std140, binding = 0) buffer block { highp uint passed; };\n"; |
| |
| // Output definitions for the struct fields of the shared memory objects. |
| std::vector<NamedStructSP>& namedStructs = interface.getStructs(); |
| |
| for (const auto& s: namedStructs) |
| src << glu::declare(s.get()) << ";\n"; |
| |
| // Output definitions for the shared memory structs. |
| for (auto& sharedObj : interface.getSharedObjects()) |
| { |
| src << "struct " << sharedObj.getName() << " {\n"; |
| |
| for (auto& var : sharedObj) |
| src << "\t" << glu::declare(var.type, var.name, 1) << ";\n"; |
| |
| src << "};\n"; |
| } |
| |
| // Comparison utilities. |
| src << "\n"; |
| generateCompareFuncs(src, interface); |
| |
| src << "\n"; |
| for (auto& sharedObj : interface.getSharedObjects()) |
| src << "shared " << sharedObj.getName() << " " << sharedObj.getInstanceName() << ";\n"; |
| |
| src << "\n"; |
| src << "void main (void) {\n"; |
| |
| for (auto& sharedObj : interface.getSharedObjects()) |
| { |
| for (const auto& var : sharedObj) |
| { |
| vector<string>::const_iterator valueIter = var.entryValues.begin(); |
| generateSharedMemoryWrites(src, sharedObj, var, glu::SubTypeAccess(var.type), valueIter, false); |
| } |
| } |
| |
| src << "\n"; |
| src << "\tbarrier();\n"; |
| src << "\tmemoryBarrier();\n"; |
| src << "\tbool allOk = true;\n"; |
| |
| for (auto& sharedObj : interface.getSharedObjects()) |
| { |
| for (const auto& var : sharedObj) |
| { |
| vector<string>::const_iterator valueIter = var.entryValues.begin(); |
| generateSharedMemoryWrites(src, sharedObj, var, glu::SubTypeAccess(var.type), valueIter, true); |
| } |
| } |
| |
| src << "\tif (allOk)\n" |
| << "\t\tpassed++;\n" |
| << "\n"; |
| |
| src << "}\n"; |
| |
| return src.str(); |
| } |
| |
| void SharedLayoutCase::checkSupport(Context& context) const |
| { |
| if ((m_interface.is16BitTypesEnabled() || m_interface.is8BitTypesEnabled()) |
| && !context.isDeviceFunctionalitySupported("VK_KHR_shader_float16_int8")) |
| TCU_THROW(NotSupportedError, "VK_KHR_shader_float16_int8 extension for 16-/8-bit types not supported"); |
| |
| const vk::VkPhysicalDeviceVulkan12Features features = context.getDeviceVulkan12Features(); |
| if (m_interface.is16BitTypesEnabled() && !features.shaderFloat16) |
| TCU_THROW(NotSupportedError, "16-bit types not supported"); |
| if (m_interface.is8BitTypesEnabled() && !features.shaderInt8) |
| TCU_THROW(NotSupportedError, "8-bit types not supported"); |
| } |
| |
| tcu::TestStatus SharedLayoutCaseInstance::iterate (void) |
| { |
| const vk::DeviceInterface &vk = m_context.getDeviceInterface(); |
| const vk::VkDevice device = m_context.getDevice(); |
| const vk::VkQueue queue = m_context.getUniversalQueue(); |
| const deUint32 queueFamilyIndex = m_context.getUniversalQueueFamilyIndex(); |
| const deUint32 bufferSize = 4; |
| |
| // Create descriptor set |
| const vk::VkBufferCreateInfo params = |
| { |
| vk::VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, // sType |
| DE_NULL, // pNext |
| 0u, // flags |
| bufferSize, // size |
| vk::VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, // usage |
| vk::VK_SHARING_MODE_EXCLUSIVE, // sharingMode |
| 1u, // queueFamilyCount |
| &queueFamilyIndex // pQueueFamilyIndices |
| }; |
| |
| vk::Move<vk::VkBuffer> buffer (vk::createBuffer(vk, device, ¶ms)); |
| |
| de::MovePtr<vk::Allocation> bufferAlloc (vk::bindBuffer (m_context.getDeviceInterface(), m_context.getDevice(), |
| m_context.getDefaultAllocator(), *buffer, vk::MemoryRequirement::HostVisible)); |
| |
| deMemset(bufferAlloc->getHostPtr(), 0, bufferSize); |
| flushMappedMemoryRange(vk, device, bufferAlloc->getMemory(), bufferAlloc->getOffset(), bufferSize); |
| |
| vk::DescriptorSetLayoutBuilder setLayoutBuilder; |
| vk::DescriptorPoolBuilder poolBuilder; |
| |
| setLayoutBuilder.addSingleBinding(vk::VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, vk::VK_SHADER_STAGE_COMPUTE_BIT); |
| |
| poolBuilder.addType(vk::VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, deUint32(1)); |
| |
| const vk::Unique<vk::VkDescriptorSetLayout> descriptorSetLayout (setLayoutBuilder.build(vk, device)); |
| const vk::Unique<vk::VkDescriptorPool> descriptorPool (poolBuilder.build(vk, device, |
| vk::VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 1u)); |
| |
| const vk::VkDescriptorSetAllocateInfo allocInfo = |
| { |
| vk::VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, // VkStructureType sType; |
| DE_NULL, // const void* pNext; |
| *descriptorPool, // VkDescriptorPool descriptorPool; |
| 1u, // deUint32 descriptorSetCount; |
| &descriptorSetLayout.get(), // const VkDescriptorSetLayout *pSetLayouts; |
| }; |
| |
| const vk::Unique<vk::VkDescriptorSet> descriptorSet (allocateDescriptorSet(vk, device, &allocInfo)); |
| const vk::VkDescriptorBufferInfo descriptorInfo = makeDescriptorBufferInfo(*buffer, 0ull, bufferSize); |
| |
| vk::DescriptorSetUpdateBuilder setUpdateBuilder; |
| std::vector<vk::VkDescriptorBufferInfo> descriptors; |
| |
| setUpdateBuilder.writeSingle(*descriptorSet, vk::DescriptorSetUpdateBuilder::Location::binding(0u), |
| vk::VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, &descriptorInfo); |
| |
| setUpdateBuilder.update(vk, device); |
| |
| const vk::VkPipelineLayoutCreateInfo pipelineLayoutParams = |
| { |
| vk::VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, // VkStructureType sType; |
| DE_NULL, // const void* pNext; |
| (vk::VkPipelineLayoutCreateFlags) 0, // VkPipelineLayoutCreateFlags flags; |
| 1u, // deUint32 descriptorSetCount; |
| &*descriptorSetLayout, // const VkDescriptorSetLayout* pSetLayouts; |
| 0u, // deUint32 pushConstantRangeCount; |
| DE_NULL // const VkPushConstantRange* pPushConstantRanges; |
| }; |
| vk::Move<vk::VkPipelineLayout> pipelineLayout (createPipelineLayout(vk, device, &pipelineLayoutParams)); |
| |
| vk::Move<vk::VkShaderModule> shaderModule (createShaderModule(vk, device, m_context.getBinaryCollection().get("compute"), 0)); |
| const vk::VkPipelineShaderStageCreateInfo pipelineShaderStageParams = |
| { |
| vk::VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, // VkStructureType sType; |
| DE_NULL, // const void* pNext; |
| (vk::VkPipelineShaderStageCreateFlags) 0, // VkPipelineShaderStageCreateFlags flags; |
| vk::VK_SHADER_STAGE_COMPUTE_BIT, // VkShaderStage stage; |
| *shaderModule, // VkShaderModule module; |
| "main", // const char* pName; |
| DE_NULL, // const VkSpecializationInfo* pSpecializationInfo; |
| }; |
| const vk::VkComputePipelineCreateInfo pipelineCreateInfo = |
| { |
| vk::VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, // VkStructureType sType; |
| DE_NULL, // const void* pNext; |
| 0, // VkPipelineCreateFlags flags; |
| pipelineShaderStageParams, // VkPipelineShaderStageCreateInfo stage; |
| *pipelineLayout, // VkPipelineLayout layout; |
| DE_NULL, // VkPipeline basePipelineHandle; |
| 0, // deInt32 basePipelineIndex; |
| }; |
| |
| vk::Move<vk::VkPipeline> pipeline (createComputePipeline(vk, device, DE_NULL, &pipelineCreateInfo)); |
| vk::Move<vk::VkCommandPool> cmdPool (createCommandPool(vk, device, vk::VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, queueFamilyIndex)); |
| vk::Move<vk::VkCommandBuffer> cmdBuffer (allocateCommandBuffer(vk, device, *cmdPool, vk::VK_COMMAND_BUFFER_LEVEL_PRIMARY)); |
| |
| beginCommandBuffer(vk, *cmdBuffer, 0u); |
| |
| vk.cmdBindPipeline(*cmdBuffer, vk::VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline); |
| |
| vk.cmdBindDescriptorSets(*cmdBuffer, vk::VK_PIPELINE_BIND_POINT_COMPUTE, *pipelineLayout, |
| 0u, 1u, &descriptorSet.get(), 0u, DE_NULL); |
| |
| vk.cmdDispatch(*cmdBuffer, 1, 1, 1); |
| |
| endCommandBuffer(vk, *cmdBuffer); |
| |
| submitCommandsAndWait(vk, device, queue, cmdBuffer.get()); |
| |
| // Read back passed data |
| bool counterOk; |
| const int refCount = 1; |
| int resCount = 0; |
| |
| invalidateAlloc(vk, device, *bufferAlloc); |
| |
| resCount = *(static_cast<const int *>(bufferAlloc->getHostPtr())); |
| |
| counterOk = (refCount == resCount); |
| if (!counterOk) |
| m_context.getTestContext().getLog() << TestLog::Message << "Error: passed = " << resCount |
| << ", expected " << refCount << TestLog::EndMessage; |
| |
| // Validate result |
| if (counterOk) |
| return tcu::TestStatus::pass("Counter value OK"); |
| |
| return tcu::TestStatus::fail("Counter value incorrect"); |
| } |
| |
| void SharedLayoutCase::initPrograms (vk::SourceCollections &programCollection) const |
| { |
| DE_ASSERT(!m_computeShaderSrc.empty()); |
| programCollection.glslSources.add("compute") << glu::ComputeSource(m_computeShaderSrc); |
| } |
| |
| TestInstance* SharedLayoutCase::createInstance (Context &context) const |
| { |
| return new SharedLayoutCaseInstance(context); |
| } |
| |
| void SharedLayoutCase::delayedInit (void) |
| { |
| |
| for (auto& sharedObj : m_interface.getSharedObjects()) |
| for (auto &var : sharedObj) |
| computeReferenceLayout(var); |
| |
| deUint32 seed = deStringHash(getName()) ^ 0xad2f7214; |
| de::Random rnd (seed); |
| |
| for (auto& sharedObj : m_interface.getSharedObjects()) |
| for (auto &var : sharedObj) |
| for (int i = 0; i < var.topLevelArraySize; i++) |
| for (auto &entry : var.entries) |
| generateValue(entry, rnd, var.entryValues); |
| |
| m_computeShaderSrc = generateComputeShader(m_interface); |
| } |
| |
| } // MemoryModel |
| } // vkt |