/*------------------------------------------------------------------------ * Vulkan Conformance Tests * ------------------------ * * Copyright (c) 2019 Google LLC * Copyright (c) 2019 The Khronos Group Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *//*! * \file * \brief Functional tests using amber *//*--------------------------------------------------------------------*/ #include #include "amber/recipe.h" #include #include "deDefs.hpp" #include "deUniquePtr.hpp" #include "deFilePath.hpp" #include "vktTestCaseUtil.hpp" #include "tcuTestLog.hpp" #include "vktAmberTestCase.hpp" #include "vktAmberHelper.hpp" #include "tcuResource.hpp" #include "tcuTestLog.hpp" #include "vkSpirVProgram.hpp" #include "vkImageUtil.hpp" namespace vkt { namespace cts_amber { AmberTestCase::AmberTestCase (tcu::TestContext& testCtx, const char* name, const char* description, const std::string& readFilename) : TestCase(testCtx, name, description), m_recipe(DE_NULL), m_readFilename(readFilename) { } AmberTestCase::~AmberTestCase (void) { delete m_recipe; } TestInstance* AmberTestCase::createInstance (Context& ctx) const { return new AmberTestInstance(ctx, m_recipe); } static amber::EngineConfig* createEngineConfig (Context& ctx) { amber::EngineConfig* vkConfig = GetVulkanConfig(ctx.getInstance(), ctx.getPhysicalDevice(), ctx.getDevice(), &ctx.getDeviceFeatures(), &ctx.getDeviceFeatures2(), ctx.getInstanceExtensions(), ctx.getDeviceExtensions(), ctx.getUniversalQueueFamilyIndex(), ctx.getUniversalQueue(), ctx.getInstanceProcAddr()); return vkConfig; } // Returns true if the given feature is supported by the device. // Throws an internal error If the feature is not recognized at all. static bool isFeatureSupported(const vkt::Context& ctx, const std::string& feature) { if (feature == "Storage16BitFeatures.storageBuffer16BitAccess") return ctx.get16BitStorageFeatures().storageBuffer16BitAccess; if (feature == "Float16Int8Features.shaderFloat16") return ctx.getShaderFloat16Int8Features().shaderFloat16; if (feature == "Features.shaderFloat64") return ctx.getDeviceFeatures().shaderFloat64; if (feature == "Features.shaderInt16") return ctx.getDeviceFeatures().shaderInt16; if (feature == "Features.shaderInt64") return ctx.getDeviceFeatures().shaderInt64; if (feature == "Features.tessellationShader") return ctx.getDeviceFeatures().tessellationShader; if (feature == "Features.geometryShader") return ctx.getDeviceFeatures().geometryShader; if (feature == "Features.fragmentStoresAndAtomics") return ctx.getDeviceFeatures().fragmentStoresAndAtomics; if (feature == "Features.vertexPipelineStoresAndAtomics") return ctx.getDeviceFeatures().vertexPipelineStoresAndAtomics; if (feature == "Features.fillModeNonSolid") return ctx.getDeviceFeatures().fillModeNonSolid; if (feature == "Features.shaderStorageImageMultisample") return ctx.getDeviceFeatures().shaderStorageImageMultisample; if (feature == "Features.sampleRateShading") return ctx.getDeviceFeatures().sampleRateShading; if (feature == "VariablePointerFeatures.variablePointersStorageBuffer") return ctx.getVariablePointersFeatures().variablePointersStorageBuffer; if (feature == "VariablePointerFeatures.variablePointers") return ctx.getVariablePointersFeatures().variablePointers; if (feature == "SubgroupSupportedStages.fragment") return (ctx.getSubgroupProperties().supportedStages & vk::VK_SHADER_STAGE_FRAGMENT_BIT) != 0; if (feature == "SubgroupSupportedOperations.vote") return (ctx.getSubgroupProperties().supportedOperations & vk::VK_SUBGROUP_FEATURE_VOTE_BIT) != 0; if (feature == "SubgroupSupportedOperations.ballot") return (ctx.getSubgroupProperties().supportedOperations & vk::VK_SUBGROUP_FEATURE_BALLOT_BIT) != 0; if (feature == "Storage16BitFeatures.storageBuffer16BitAccess") return ctx.get16BitStorageFeatures().storageBuffer16BitAccess; std::string message = std::string("Unexpected feature name: ") + feature; TCU_THROW(InternalError, message.c_str()); } void AmberTestCase::delayedInit(void) { // Make sure the input can be parsed before we use it. if (!parse(m_readFilename)) { std::string message = "Failed to parse Amber file: " + m_readFilename; TCU_THROW(InternalError, message.c_str()); } } void AmberTestCase::checkSupport(Context& ctx) const { // Check for instance and device extensions as declared by the test code. if (m_required_extensions.size()) { std::set device_extensions(ctx.getDeviceExtensions().begin(), ctx.getDeviceExtensions().end()); std::set instance_extensions(ctx.getInstanceExtensions().begin(), ctx.getInstanceExtensions().end()); std::string missing; for (std::set::iterator iter = m_required_extensions.begin(); iter != m_required_extensions.end(); ++iter) { const std::string extension = *iter; if ((device_extensions.count(extension) == 0) && (instance_extensions.count(extension) == 0)) { missing += " " + extension; } } if (missing.size() > 0) { std::string message("Test requires unsupported extensions:"); message += missing; TCU_THROW(NotSupportedError, message.c_str()); } } // Check for required features. Do this after extensions are checked because // some feature checks are only valid when corresponding extensions are enabled. if (m_required_features.size()) { std::string missing; for (std::set::iterator iter = m_required_features.begin(); iter != m_required_features.end(); ++iter) { const std::string feature = *iter; if (!isFeatureSupported(ctx, feature)) { missing += " " + feature; } } if (missing.size() > 0) { std::string message("Test requires unsupported features:"); message += missing; TCU_THROW(NotSupportedError, message.c_str()); } } for (auto req : m_imageRequirements) checkImageSupport(ctx.getInstanceInterface(), ctx.getPhysicalDevice(), req); for (auto req : m_bufferRequirements) { vk::VkFormatProperties prop; ctx.getInstanceInterface().getPhysicalDeviceFormatProperties(ctx.getPhysicalDevice(), req.m_format, &prop); if ((req.m_featureFlags & prop.bufferFeatures) != req.m_featureFlags) { TCU_THROW(NotSupportedError, "Buffer format doesn't support required feature flags"); } } // when checkSupport is called script is not yet parsed so we need to determine // unsupported tests by name ; in AmberTestCase we do not have access to actual // m_recipe implementation - we can't scan it to see if test can be executed; // alternatively portability extension and its features could be checked in amber.cc if (ctx.isDeviceFunctionalitySupported("VK_KHR_portability_subset")) { if (m_name == "triangle_fan" && !ctx.getPortabilitySubsetFeatures().triangleFans) TCU_THROW(NotSupportedError, "VK_KHR_portability_subset: Triangle fans are not supported by this implementation"); if (ctx.getPortabilitySubsetProperties().minVertexInputBindingStrideAlignment == 4) { const std::set casesToSkip { "line-strip", "polygon-mode-lines", "r8g8-uint-highp", "r8g8-uint-highp-output-uint", "r8g8-uint-mediump", "r8g8-uint-mediump-output-uint", "inputs-outputs-mod", }; if (casesToSkip.count(m_name)) TCU_THROW(NotSupportedError, "VK_KHR_portability_subset: Stride is not multiply of minVertexInputBindingStrideAlignment"); } } } class Delegate : public amber::Delegate { public: Delegate (tcu::TestContext& testCtx); amber::Result LoadBufferData (const std::string file_name, amber::BufferDataFileType file_type, amber::BufferInfo* buffer) const override; void Log (const std::string& /*message*/) override { DE_FATAL("amber::Delegate::Log unimplemented"); } bool LogGraphicsCalls (void) const override { return m_logGraphicsCalls; } void SetLogGraphicsCalls (bool log_graphics_calls) { m_logGraphicsCalls = log_graphics_calls; } bool LogExecuteCalls (void) const override { return m_logExecuteCalls; } void SetLogExecuteCalls (bool log_execute_calls) { m_logExecuteCalls = log_execute_calls; } bool LogGraphicsCallsTime (void) const override { return m_logGraphicsCallsTime; } void SetLogGraphicsCallsTime (bool log_graphics_calls_time) { m_logGraphicsCallsTime = log_graphics_calls_time; } deUint64 GetTimestampNs (void) const override { DE_FATAL("amber::Delegate::GetTimestampNs unimplemented"); return 0; } void SetScriptPath (std::string path) { m_path = path; } private: tcu::TestContext& m_testCtx; std::string m_path; bool m_logGraphicsCalls; bool m_logGraphicsCallsTime; bool m_logExecuteCalls; }; Delegate::Delegate (tcu::TestContext& testCtx) : m_testCtx (testCtx) , m_path ("") , m_logGraphicsCalls (false) , m_logGraphicsCallsTime (false) , m_logExecuteCalls (false) { } amber::Result Delegate::LoadBufferData (const std::string file_name, amber::BufferDataFileType file_type, amber::BufferInfo* buffer) const { const tcu::Archive& archive = m_testCtx.getArchive(); const de::FilePath filePath = de::FilePath(m_path).join(file_name); de::UniquePtr file (archive.getResource(filePath.getPath())); int numBytes = file->getSize(); std::vector bytes (numBytes); if (file_type == amber::BufferDataFileType::kPng) return amber::Result("Amber PNG loading unimplemented"); file->read(bytes.data(), numBytes); if (bytes.empty()) return amber::Result("Failed to load buffer data " + file_name); for (deUint8 byte : bytes) { amber::Value value; value.SetIntValue(static_cast(byte)); buffer->values.push_back(value); } buffer->width = 1; buffer->height = 1; return {}; } bool AmberTestCase::parse (const std::string& readFilename) { std::string script = ShaderSourceProvider::getSource(m_testCtx.getArchive(), readFilename.c_str()); if (script.empty()) return false; Delegate delegate (m_testCtx); delegate.SetScriptPath(de::FilePath(readFilename).getDirName()); m_recipe = new amber::Recipe(); amber::Amber am (&delegate); amber::Result r = am.Parse(script, m_recipe); m_recipe->SetFenceTimeout(~0u); // infinity of miliseconds if (!r.IsSuccess()) { getTestContext().getLog() << tcu::TestLog::Message << "Failed to parse Amber test " << readFilename << ": " << r.Error() << "\n" << tcu::TestLog::EndMessage; // TODO(dneto): Enhance Amber to not require this. m_recipe->SetImpl(DE_NULL); return false; } return true; } void AmberTestCase::initPrograms (vk::SourceCollections& programCollection) const { std::vector shaders = m_recipe->GetShaderInfo(); for (size_t i = 0; i < shaders.size(); ++i) { const amber::ShaderInfo& shader = shaders[i]; vk::SpirvVersion spirvVersion = vk::SPIRV_VERSION_1_0; DE_STATIC_ASSERT(vk::SPIRV_VERSION_LAST == vk::SPIRV_VERSION_1_5 + 1); if (shader.target_env == "spv1.5") spirvVersion = vk::SPIRV_VERSION_1_5; else if (shader.target_env == "spv1.4") spirvVersion = vk::SPIRV_VERSION_1_4; else if (shader.target_env == "spv1.3") spirvVersion = vk::SPIRV_VERSION_1_3; else if (shader.target_env == "spv1.2") spirvVersion = vk::SPIRV_VERSION_1_2; else if (shader.target_env == "spv1.1") spirvVersion = vk::SPIRV_VERSION_1_1; /* Hex encoded shaders do not need to be pre-compiled */ if (shader.format == amber::kShaderFormatSpirvHex) continue; if (shader.format == amber::kShaderFormatSpirvAsm) { programCollection.spirvAsmSources.add(shader.shader_name) << shader.shader_source << m_asm_options; } else if (shader.format == amber::kShaderFormatGlsl) { bool allowSpirv14 = (spirvVersion == vk::SPIRV_VERSION_1_4); switch (shader.type) { case amber::kShaderTypeCompute: programCollection.glslSources.add(shader.shader_name) << glu::ComputeSource(shader.shader_source) << vk::ShaderBuildOptions(programCollection.usedVulkanVersion, spirvVersion, 0u, allowSpirv14); break; case amber::kShaderTypeGeometry: programCollection.glslSources.add(shader.shader_name) << glu::GeometrySource(shader.shader_source) << vk::ShaderBuildOptions(programCollection.usedVulkanVersion, spirvVersion, 0u, allowSpirv14); break; case amber::kShaderTypeFragment: programCollection.glslSources.add(shader.shader_name) << glu::FragmentSource(shader.shader_source) << vk::ShaderBuildOptions(programCollection.usedVulkanVersion, spirvVersion, 0u, allowSpirv14); break; case amber::kShaderTypeVertex: programCollection.glslSources.add(shader.shader_name) << glu::VertexSource(shader.shader_source) << vk::ShaderBuildOptions(programCollection.usedVulkanVersion, spirvVersion, 0u, allowSpirv14); break; case amber::kShaderTypeTessellationControl: programCollection.glslSources.add(shader.shader_name) << glu::TessellationControlSource(shader.shader_source) << vk::ShaderBuildOptions(programCollection.usedVulkanVersion, spirvVersion, 0u, allowSpirv14); break; case amber::kShaderTypeTessellationEvaluation: programCollection.glslSources.add(shader.shader_name) << glu::TessellationEvaluationSource(shader.shader_source) << vk::ShaderBuildOptions(programCollection.usedVulkanVersion, spirvVersion, 0u, allowSpirv14); break; case amber::kShaderTypeMulti: DE_ASSERT(false && "Multi shaders not supported"); break; } } else { DE_ASSERT(false && "Shader format not supported"); } } } tcu::TestStatus AmberTestInstance::iterate (void) { amber::Amber am (DE_NULL); amber::Options amber_options; amber::ShaderMap shaderMap; amber::Result r; amber_options.engine = amber::kEngineTypeVulkan; amber_options.config = createEngineConfig(m_context); amber_options.execution_type = amber::ExecutionType::kExecute; // Check for extensions as declared by the Amber script itself. Throw an internal // error if that's more demanding. r = am.AreAllRequirementsSupported(m_recipe, &amber_options); if (!r.IsSuccess()) { // dEQP does not to rely on external code to determine whether // a test is supported. So throw an internal error here instead // of a NotSupportedError. If an Amber test is not supported, then // you must override this method and throw a NotSupported exception // before reach here. TCU_THROW(InternalError, r.Error().c_str()); } std::vector shaders = m_recipe->GetShaderInfo(); for (size_t i = 0; i < shaders.size(); ++i) { const amber::ShaderInfo& shader = shaders[i]; if (!m_context.getBinaryCollection().contains(shader.shader_name)) continue; size_t len = m_context.getBinaryCollection().get(shader.shader_name).getSize(); /* This is a compiled spir-v binary which must be made of 4-byte words. We * are moving into a word sized vector so divide by 4 */ std::vector data; data.resize(len >> 2); deMemcpy(data.data(), m_context.getBinaryCollection().get(shader.shader_name).getBinary(), len); shaderMap[shader.shader_name] = data; } r = am.ExecuteWithShaderData(m_recipe, &amber_options, shaderMap); if (!r.IsSuccess()) { m_context.getTestContext().getLog() << tcu::TestLog::Message << r.Error() << "\n" << tcu::TestLog::EndMessage; } delete amber_options.config; return r.IsSuccess() ? tcu::TestStatus::pass("Pass") :tcu::TestStatus::fail("Fail"); } void AmberTestCase::setSpirVAsmBuildOptions (const vk::SpirVAsmBuildOptions& asm_options) { m_asm_options = asm_options; } void AmberTestCase::addRequirement (const std::string& requirement) { if (requirement.find(".") != std::string::npos) m_required_features.insert(requirement); else m_required_extensions.insert(requirement); } void AmberTestCase::addImageRequirement (vk::VkImageCreateInfo info) { m_imageRequirements.push_back(info); } void AmberTestCase::addBufferRequirement (BufferRequirement req) { m_bufferRequirements.push_back(req); } bool AmberTestCase::validateRequirements() { if (!parse(m_readFilename)) { std::string message = "Failed to parse Amber file: " + m_readFilename; m_testCtx.getLog() << tcu::TestLog::Message << message << tcu::TestLog::EndMessage; return false; } // Check if the list of required CTS features and extensions matches the // one in the recipe. Throw InternalError if they do not match. const auto& deviceExtensions = m_recipe->GetRequiredInstanceExtensions(); const auto& instanceExtensions = m_recipe->GetRequiredDeviceExtensions(); auto requiredFeatures = m_recipe->GetRequiredFeatures(); for (auto& req : requiredFeatures) { if (req.find(".") == std::string::npos) req = "Features." + req; } std::set allRequirements; allRequirements.insert(begin(deviceExtensions), end(deviceExtensions)); allRequirements.insert(begin(instanceExtensions), end(instanceExtensions)); allRequirements.insert(begin(requiredFeatures), end(requiredFeatures)); std::set ctsRequirements = m_required_features; ctsRequirements.insert(begin(m_required_extensions), end(m_required_extensions)); if (allRequirements != ctsRequirements) { auto& log = m_testCtx.getLog(); log << tcu::TestLog::Message << "ERROR: CTS and Amber test requirement mismatch." << tcu::TestLog::EndMessage; log << tcu::TestLog::Message << "Amber filename: " << m_readFilename << tcu::TestLog::EndMessage; log << tcu::TestLog::Message << "CTS requirements:" << tcu::TestLog::EndMessage; for (const auto& ctsReq : ctsRequirements) log << tcu::TestLog::Message << " " << ctsReq << tcu::TestLog::EndMessage; log << tcu::TestLog::Message << "Amber requirements:" << tcu::TestLog::EndMessage; for (const auto& amberReq : allRequirements) log << tcu::TestLog::Message << " " << amberReq << tcu::TestLog::EndMessage; return false; } return true; } } // cts_amber } // vkt